From 17ac804cfef5425a634a38715a855b1d3cd63797 Mon Sep 17 00:00:00 2001 From: Emmanuel Ezeokeke Date: Mon, 18 Nov 2024 05:39:43 +0100 Subject: [PATCH 1/2] Add HR AI agent --- all_agents_tutorials/Hr_AI_Agent.ipynb | 904 +++++++++++++++++++++++++ 1 file changed, 904 insertions(+) create mode 100644 all_agents_tutorials/Hr_AI_Agent.ipynb diff --git a/all_agents_tutorials/Hr_AI_Agent.ipynb b/all_agents_tutorials/Hr_AI_Agent.ipynb new file mode 100644 index 0000000..4327992 --- /dev/null +++ b/all_agents_tutorials/Hr_AI_Agent.ipynb @@ -0,0 +1,904 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "ATM_0NvmvJXw" + }, + "source": [ + "# HR AI Assistant" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "yNk-A0Wvuu1g" + }, + "source": [ + "## In this project, i create a recruitment workflow using LangGraph, LangChain, and various APIs to automate and streamline the job posting and candidate evaluation process. The workflow consists of the following key steps:\n", + "\n", + "* Requirements Gathering: The AI agent prompts the user for detailed job requirements, including the job title, company description, candidate responsibilities and qualifications, preferred location, and other relevant details.\n", + "\n", + "* Job Description Generation: Once the job requirements are gathered, the agent generates a professional and compelling job description. The user can then review and approve the description, or provide feedback for the agent to refine it.\n", + "\n", + "* LinkedIn Candidate Search and Outreach: If the user provides specific LinkedIn profiles of preferred candidates, the agent will directly message them about the opportunity. Alternatively, the agent can search LinkedIn for relevant candidates based on the job details and send outreach messages.\n", + "\n", + "* CV Analysis: As candidates submit their CVs, the agent evaluates them against the job requirements, providing a score and recommendation (approve or reject) for each applicant. The agent then sends the appropriate response message to the candidate via LinkedIn.\n", + "\n", + "* Interview Question Preparation: For approved candidates, the agent generates a set of interview questions covering technical skills, experience, and problem-solving. The user can review and approve the questions before the interviews are conducted." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "exsYcl5UvYc3" + }, + "source": [ + "## Installing required packages" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Js765NcYELqC", + "outputId": "90bd6ca9-4641-43dc-feb0-6c4ea253047a" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: langchain in /usr/local/lib/python3.10/dist-packages (0.3.7)\n", + "Collecting langchain-anthropic\n", + " Downloading langchain_anthropic-0.3.0-py3-none-any.whl.metadata (2.3 kB)\n", + "Collecting langgraph\n", + " Downloading langgraph-0.2.50-py3-none-any.whl.metadata (15 kB)\n", + "Collecting python-dotenv\n", + " Downloading python_dotenv-1.0.1-py3-none-any.whl.metadata (23 kB)\n", + "Requirement already satisfied: pydantic in /usr/local/lib/python3.10/dist-packages (2.9.2)\n", + "Collecting langchain_community\n", + " Downloading langchain_community-0.3.7-py3-none-any.whl.metadata (2.9 kB)\n", + "Requirement already satisfied: requests in /usr/local/lib/python3.10/dist-packages (2.32.3)\n", + "Requirement already satisfied: PyYAML>=5.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (6.0.2)\n", + "Requirement already satisfied: SQLAlchemy<3,>=1.4 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.0.36)\n", + "Requirement already satisfied: aiohttp<4.0.0,>=3.8.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (3.10.10)\n", + "Requirement already satisfied: async-timeout<5.0.0,>=4.0.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (4.0.3)\n", + "Requirement already satisfied: langchain-core<0.4.0,>=0.3.15 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.3.17)\n", + "Requirement already satisfied: langchain-text-splitters<0.4.0,>=0.3.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.3.2)\n", + "Requirement already satisfied: langsmith<0.2.0,>=0.1.17 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.1.142)\n", + "Requirement already satisfied: numpy<2,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain) (1.26.4)\n", + "Requirement already satisfied: tenacity!=8.4.0,<10,>=8.1.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (9.0.0)\n", + "Collecting anthropic<1,>=0.39.0 (from langchain-anthropic)\n", + " Downloading anthropic-0.39.0-py3-none-any.whl.metadata (22 kB)\n", + "Requirement already satisfied: defusedxml<0.8.0,>=0.7.1 in /usr/local/lib/python3.10/dist-packages (from langchain-anthropic) (0.7.1)\n", + "Collecting langgraph-checkpoint<3.0.0,>=2.0.4 (from langgraph)\n", + " Downloading langgraph_checkpoint-2.0.4-py3-none-any.whl.metadata (4.6 kB)\n", + "Collecting langgraph-sdk<0.2.0,>=0.1.32 (from langgraph)\n", + " Downloading langgraph_sdk-0.1.36-py3-none-any.whl.metadata (1.8 kB)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /usr/local/lib/python3.10/dist-packages (from pydantic) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.23.4 in /usr/local/lib/python3.10/dist-packages (from pydantic) (2.23.4)\n", + "Requirement already satisfied: typing-extensions>=4.6.1 in /usr/local/lib/python3.10/dist-packages (from pydantic) (4.12.2)\n", + "Collecting SQLAlchemy<3,>=1.4 (from langchain)\n", + " Downloading SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (9.6 kB)\n", + "Collecting dataclasses-json<0.7,>=0.5.7 (from langchain_community)\n", + " Downloading dataclasses_json-0.6.7-py3-none-any.whl.metadata (25 kB)\n", + "Collecting httpx-sse<0.5.0,>=0.4.0 (from langchain_community)\n", + " Downloading httpx_sse-0.4.0-py3-none-any.whl.metadata (9.0 kB)\n", + "Collecting pydantic-settings<3.0.0,>=2.4.0 (from langchain_community)\n", + " Downloading pydantic_settings-2.6.1-py3-none-any.whl.metadata (3.5 kB)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests) (3.4.0)\n", + "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests) (3.10)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests) (2.2.3)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests) (2024.8.30)\n", + "Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (2.4.3)\n", + "Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.3.1)\n", + "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (24.2.0)\n", + "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.5.0)\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (6.1.0)\n", + "Requirement already satisfied: yarl<2.0,>=1.12.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.17.1)\n", + "Requirement already satisfied: anyio<5,>=3.5.0 in /usr/local/lib/python3.10/dist-packages (from anthropic<1,>=0.39.0->langchain-anthropic) (3.7.1)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /usr/local/lib/python3.10/dist-packages (from anthropic<1,>=0.39.0->langchain-anthropic) (1.9.0)\n", + "Requirement already satisfied: httpx<1,>=0.23.0 in /usr/local/lib/python3.10/dist-packages (from anthropic<1,>=0.39.0->langchain-anthropic) (0.27.2)\n", + "Requirement already satisfied: jiter<1,>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from anthropic<1,>=0.39.0->langchain-anthropic) (0.7.1)\n", + "Requirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from anthropic<1,>=0.39.0->langchain-anthropic) (1.3.1)\n", + "Collecting marshmallow<4.0.0,>=3.18.0 (from dataclasses-json<0.7,>=0.5.7->langchain_community)\n", + " Downloading marshmallow-3.23.1-py3-none-any.whl.metadata (7.5 kB)\n", + "Collecting typing-inspect<1,>=0.4.0 (from dataclasses-json<0.7,>=0.5.7->langchain_community)\n", + " Downloading typing_inspect-0.9.0-py3-none-any.whl.metadata (1.5 kB)\n", + "Requirement already satisfied: jsonpatch<2.0,>=1.33 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.4.0,>=0.3.15->langchain) (1.33)\n", + "Requirement already satisfied: packaging<25,>=23.2 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.4.0,>=0.3.15->langchain) (24.2)\n", + "Requirement already satisfied: msgpack<2.0.0,>=1.1.0 in /usr/local/lib/python3.10/dist-packages (from langgraph-checkpoint<3.0.0,>=2.0.4->langgraph) (1.1.0)\n", + "Requirement already satisfied: orjson>=3.10.1 in /usr/local/lib/python3.10/dist-packages (from langgraph-sdk<0.2.0,>=0.1.32->langgraph) (3.10.11)\n", + "Requirement already satisfied: requests-toolbelt<2.0.0,>=1.0.0 in /usr/local/lib/python3.10/dist-packages (from langsmith<0.2.0,>=0.1.17->langchain) (1.0.0)\n", + "Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.10/dist-packages (from SQLAlchemy<3,>=1.4->langchain) (3.1.1)\n", + "Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->anthropic<1,>=0.39.0->langchain-anthropic) (1.2.2)\n", + "Requirement already satisfied: httpcore==1.* in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->anthropic<1,>=0.39.0->langchain-anthropic) (1.0.6)\n", + "Requirement already satisfied: h11<0.15,>=0.13 in /usr/local/lib/python3.10/dist-packages (from httpcore==1.*->httpx<1,>=0.23.0->anthropic<1,>=0.39.0->langchain-anthropic) (0.14.0)\n", + "Requirement already satisfied: jsonpointer>=1.9 in /usr/local/lib/python3.10/dist-packages (from jsonpatch<2.0,>=1.33->langchain-core<0.4.0,>=0.3.15->langchain) (3.0.0)\n", + "Collecting mypy-extensions>=0.3.0 (from typing-inspect<1,>=0.4.0->dataclasses-json<0.7,>=0.5.7->langchain_community)\n", + " Downloading mypy_extensions-1.0.0-py3-none-any.whl.metadata (1.1 kB)\n", + "Requirement already satisfied: propcache>=0.2.0 in /usr/local/lib/python3.10/dist-packages (from yarl<2.0,>=1.12.0->aiohttp<4.0.0,>=3.8.3->langchain) (0.2.0)\n", + "Downloading langchain_anthropic-0.3.0-py3-none-any.whl (22 kB)\n", + "Downloading langgraph-0.2.50-py3-none-any.whl (124 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m124.9/124.9 kB\u001b[0m \u001b[31m3.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading python_dotenv-1.0.1-py3-none-any.whl (19 kB)\n", + "Downloading langchain_community-0.3.7-py3-none-any.whl (2.4 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.4/2.4 MB\u001b[0m \u001b[31m18.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading anthropic-0.39.0-py3-none-any.whl (198 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m198.4/198.4 kB\u001b[0m \u001b[31m9.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading dataclasses_json-0.6.7-py3-none-any.whl (28 kB)\n", + "Downloading httpx_sse-0.4.0-py3-none-any.whl (7.8 kB)\n", + "Downloading langgraph_checkpoint-2.0.4-py3-none-any.whl (23 kB)\n", + "Downloading langgraph_sdk-0.1.36-py3-none-any.whl (29 kB)\n", + "Downloading pydantic_settings-2.6.1-py3-none-any.whl (28 kB)\n", + "Downloading SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.1 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m3.1/3.1 MB\u001b[0m \u001b[31m14.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading marshmallow-3.23.1-py3-none-any.whl (49 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m49.5/49.5 kB\u001b[0m \u001b[31m2.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading typing_inspect-0.9.0-py3-none-any.whl (8.8 kB)\n", + "Downloading mypy_extensions-1.0.0-py3-none-any.whl (4.7 kB)\n", + "Installing collected packages: SQLAlchemy, python-dotenv, mypy-extensions, marshmallow, httpx-sse, typing-inspect, pydantic-settings, langgraph-sdk, dataclasses-json, anthropic, langgraph-checkpoint, langchain-anthropic, langgraph, langchain_community\n", + " Attempting uninstall: SQLAlchemy\n", + " Found existing installation: SQLAlchemy 2.0.36\n", + " Uninstalling SQLAlchemy-2.0.36:\n", + " Successfully uninstalled SQLAlchemy-2.0.36\n", + "Successfully installed SQLAlchemy-2.0.35 anthropic-0.39.0 dataclasses-json-0.6.7 httpx-sse-0.4.0 langchain-anthropic-0.3.0 langchain_community-0.3.7 langgraph-0.2.50 langgraph-checkpoint-2.0.4 langgraph-sdk-0.1.36 marshmallow-3.23.1 mypy-extensions-1.0.0 pydantic-settings-2.6.1 python-dotenv-1.0.1 typing-inspect-0.9.0\n" + ] + } + ], + "source": [ + "!pip install langchain langchain-anthropic langgraph python-dotenv pydantic langchain_community requests" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "RcnKWPYtIiOs" + }, + "outputs": [], + "source": [ + "# Core imports\n", + "import os\n", + "from typing import Dict, Any, List, Optional, Literal, Annotated\n", + "from typing_extensions import TypedDict\n", + "from pydantic import BaseModel, Field\n", + "from datetime import datetime\n", + "import operator\n", + "import json\n", + "import uuid\n", + "import requests\n", + "\n", + "# LangChain imports\n", + "from langchain_anthropic import ChatAnthropic\n", + "from langchain_core.messages import SystemMessage, HumanMessage, AIMessage, BaseMessage\n", + "from langchain_core.tools import tool\n", + "from langchain_community.utilities import GoogleSerperAPIWrapper\n", + "\n", + "# LangGraph imports\n", + "from langgraph.graph import StateGraph, START, END, MessagesState\n", + "from langgraph.checkpoint.memory import MemorySaver\n", + "from langgraph.prebuilt import ToolNode\n", + "from langgraph.graph.message import add_messages\n", + "from langgraph.errors import NodeInterrupt\n", + "\n", + "# Display imports\n", + "from IPython.display import Image, display" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "jjK6aljDWuef" + }, + "outputs": [], + "source": [ + "# API Keys\n", + "os.environ[\"ANTHROPIC_API_KEY\"] = \"anthropic_api_key\"\n", + "os.environ[\"SERPER_API_KEY\"] = \"Serper_api_key\"\n", + "os.environ[\"LINKEDIN_COOKIE\"] = \"linkedin_cookie\"\n", + "\n", + "llm = ChatAnthropic(model=\"claude-3-sonnet-20240229\", temperature=0)\n", + "\n", + "# Initialize search utility\n", + "search = GoogleSerperAPIWrapper()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "txviulRpr2-n" + }, + "source": [ + " # Defining base models" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "yDqf9YahWx3r" + }, + "outputs": [], + "source": [ + "# Job requirements model\n", + "class JobRequirements(BaseModel):\n", + " # Details about the job requirements\n", + " title: str\n", + " company_description: str\n", + " job_requirements: List[str]\n", + " candidate_responsibilities: List[str]\n", + " candidate_qualifications: List[str]\n", + " company_benefits: List[str]\n", + " interview_date: datetime\n", + " preferred_country: str\n", + " years_experience: int\n", + " linkedin_profiles: Optional[List[str]] = None\n", + " skills_required: List[str]\n", + " salary_range: str\n", + "\n", + "\n", + "\n", + "# Candidate profile model\n", + "class CandidateProfile(BaseModel):\n", + " # Details about the candidate\n", + " name: str\n", + " linkedin_url: str\n", + " title: str\n", + " location: str\n", + " cv_content: Optional[str] = None\n", + " cv_score: Optional[float] = None\n", + " status: str = \"new\" # 'new', 'contacted', 'cv_received', 'approved', 'rejected'\n", + " feedback: Optional[str] = None\n", + " source: str = \"direct\" # 'direct' or 'search'\n", + " skill_matches: Optional[List[str]] = None\n", + " skill_gaps: Optional[List[str]] = None\n", + " message_sent: Optional[str] = None\n", + "\n", + "# Main State Model\n", + "class RecruitmentState(BaseModel):\n", + " # Overall state of the recruitment process\n", + " phase: str\n", + " messages: Annotated[List[BaseMessage], add_messages]\n", + " job_requirements: Optional[JobRequirements]\n", + " job_description: Optional[str]\n", + " job_description_approved: bool\n", + " candidates: Annotated[List[CandidateProfile], operator.add]\n", + " linkedin_process_complete: bool\n", + " cv_analysis_complete: bool\n", + " interview_questions: Optional[List[str]]\n", + " interview_questions_approved: bool" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4xM3gjh0sMPh" + }, + "source": [ + "# Defining tool functions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "gpcAMpRUW4WG" + }, + "outputs": [], + "source": [ + "# Search for LinkedIn candidates using Google Serper API\n", + "@tool\n", + "def search_linkedin_candidates(\n", + " job_title: str,\n", + " location: str,\n", + " skills: List[str],\n", + " limit: int = 5\n", + ") -> List[Dict[str, str]]:\n", + " \"\"\"Search for candidates on LinkedIn using Google Search API\"\"\"\n", + " search = GoogleSerperAPIWrapper()\n", + " search_query = f\"\"\"site:linkedin.com/in/\n", + " {job_title}\n", + " {location}\n", + " {' '.join(skills)}\"\"\"\n", + "\n", + " try:\n", + " results = search.results(search_query)\n", + " candidates = []\n", + " for result in results.get('organic', [])[:limit]:\n", + " if \"linkedin.com/in/\" in result.get(\"link\", \"\"):\n", + " candidates.append({\n", + " \"linkedin_url\": result[\"link\"],\n", + " \"title\": result.get(\"title\", \"\"),\n", + " \"snippet\": result.get(\"snippet\", \"\")\n", + " })\n", + " return candidates\n", + " except Exception as e:\n", + " return [{\"error\": str(e)}]\n", + "\n", + "\n", + "# Get detailed LinkedIn profile information\n", + "@tool\n", + "def get_linkedin_profile(profile_url: str) -> Dict[str, Any]:\n", + " \"\"\"Get detailed profile information from LinkedIn URL\"\"\"\n", + " headers = {\n", + " 'cookie': os.getenv(\"LINKEDIN_COOKIE\"),\n", + " 'accept': 'application/json'\n", + " }\n", + " try:\n", + " profile_id = profile_url.split('/in/')[-1].split('/')[0]\n", + " api_url = f\"https://www.linkedin.com/voyager/api/identity/profiles/{profile_id}/profileView\"\n", + " response = requests.get(api_url, headers=headers)\n", + " data = response.json()\n", + "\n", + " return {\n", + " \"name\": f\"{data.get('firstName', '')} {data.get('lastName', '')}\",\n", + " \"title\": data.get('headline', ''),\n", + " \"location\": data.get('locationName', ''),\n", + " \"skills\": [skill.get('name', '') for skill in data.get('skills', [])]\n", + " }\n", + " except Exception as e:\n", + " return {\"error\": str(e)}\n", + "\n", + "\n", + "# Send a message to a LinkedIn candidate about the job opportunity\n", + "@tool\n", + "def send_linkedin_message(profile_url: str, job_details: str) -> Dict[str, str]:\n", + " \"\"\"Send a LinkedIn message to a candidate about the job opportunity\"\"\"\n", + " headers = {\n", + " 'cookie': os.getenv(\"LINKEDIN_COOKIE\"),\n", + " 'accept': 'application/json'\n", + " }\n", + " try:\n", + " profile_id = profile_url.split('/in/')[-1].split('/')[0]\n", + "\n", + " # Generate personalized message\n", + " message_prompt = [\n", + " SystemMessage(content=\"Generate a personalized LinkedIn outreach message for a job opportunity. Ask them to submit their CV directly through LinkedIn messages if interested.\"),\n", + " HumanMessage(content=f\"Job Details:\\n{job_details}\")\n", + " ]\n", + " message_content = llm.invoke(message_prompt).content\n", + "\n", + " # Send message via LinkedIn API\n", + " message_endpoint = f\"https://www.linkedin.com/voyager/api/messaging/conversations\"\n", + " payload = {\n", + " \"recipients\": [profile_id],\n", + " \"message\": message_content\n", + " }\n", + " response = requests.post(message_endpoint, headers=headers, json=payload)\n", + "\n", + " return {\n", + " \"status\": \"sent\",\n", + " \"profile_id\": profile_id,\n", + " \"message\": message_content\n", + " }\n", + " except Exception as e:\n", + " return {\"error\": str(e)}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HXa3oKh0sfp7" + }, + "source": [ + "# Defining workflow nodes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "uPF4UD5vW7tx" + }, + "outputs": [], + "source": [ + "# Requirements gathering node\n", + "def requirements_gathering(state: RecruitmentState) -> Dict[str, Any]:\n", + " \"\"\"Initialize or continue requirements gathering process\"\"\"\n", + " if not state.messages:\n", + " job_title = input(\"What is the job title? \")\n", + " company_description = input(\"Provide a description of the company: \")\n", + " job_requirements = input(\"List the job requirements (comma-separated): \").split(\",\")\n", + " candidate_responsibilities = input(\"List the candidate responsibilities (comma-separated): \").split(\",\")\n", + " candidate_qualifications = input(\"List the candidate qualifications (comma-separated): \").split(\",\")\n", + " company_benefits = input(\"List the company benefits (comma-separated): \").split(\",\")\n", + " interview_date = input(\"What is the interview date (YYYY-MM-DD)? \")\n", + " preferred_country = input(\"What is the preferred country for the role? \")\n", + " years_experience = int(input(\"What is the required years of experience? \"))\n", + " linkedin_profiles = input(\"Provide any LinkedIn profile URLs (comma-separated, optional): \").split(\",\")\n", + " skills_required = input(\"List the required skills (comma-separated): \").split(\",\")\n", + " salary_range = input(\"What is the salary range for the role? \")\n", + " google_form_url = input(\"Provide the Google Form URL for CV submissions: \")\n", + "\n", + " job_requirements = JobRequirements(\n", + " title=job_title,\n", + " company_description=company_description,\n", + " job_requirements=job_requirements,\n", + " candidate_responsibilities=candidate_responsibilities,\n", + " candidate_qualifications=candidate_qualifications,\n", + " company_benefits=company_benefits,\n", + " interview_date=datetime.strptime(interview_date, \"%Y-%m-%d\"),\n", + " preferred_country=preferred_country,\n", + " years_experience=years_experience,\n", + " linkedin_profiles=[p.strip() for p in linkedin_profiles if p.strip()],\n", + " skills_required=skills_required,\n", + " salary_range=salary_range,\n", + " google_form_url=google_form_url\n", + " )\n", + "\n", + " return {\n", + " \"messages\": [\n", + " SystemMessage(content=\"You are an HR assistant gathering detailed job requirements.\"),\n", + " HumanMessage(content=\"Let's begin gathering the job requirements. What is the job title?\")\n", + " ],\n", + " \"job_requirements\": job_requirements,\n", + " \"phase\": \"requirements_gathering\"\n", + " }\n", + " else:\n", + " return state\n", + "\n", + "\n", + "# Job description generation node\n", + "def generate_job_desc(state: RecruitmentState) -> Dict[str, Any]:\n", + " \"\"\"Generate job description with human-in-the-loop review\"\"\"\n", + " if not state.job_requirements:\n", + " raise ValueError(\"Job requirements missing\")\n", + "\n", + " messages = [\n", + " SystemMessage(content=\"\"\"Create a professional and compelling job description with:\n", + " 1. About the Company\n", + " 2. Role Overview\n", + " 3. Key Responsibilities\n", + " 4. Required Qualifications\n", + " 5. What We Offer (Benefits)\n", + " 6. Location and Work Mode\"\"\"),\n", + " HumanMessage(content=str(state.job_requirements.model_dump()))\n", + " ]\n", + "\n", + " response = llm.invoke(messages)\n", + "\n", + " # Raise NodeInterrupt for human review\n", + " raise NodeInterrupt(\n", + " f\"Please review the generated job description:\\n\\n{response.content}\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "wEAg-s34sp1z" + }, + "source": [ + "# LinkedIn process node" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "hraKSYGjW--0" + }, + "outputs": [], + "source": [ + "def linkedin_process(state: RecruitmentState) -> Dict[str, Any]:\n", + " # Implementation to handle the LinkedIn candidate search and outreach process\n", + " \"\"\"Handle LinkedIn candidate search/outreach process\"\"\"\n", + " candidates = []\n", + " tool_node = ToolNode([search_linkedin_candidates, get_linkedin_profile, send_linkedin_message])\n", + "\n", + " job_details = f\"\"\"\n", + " Role: {state.job_requirements.title}\n", + " Company: {state.job_requirements.company_description}\n", + " Location: {state.job_requirements.preferred_country}\n", + " Description: {state.job_description}\n", + " \"\"\"\n", + "\n", + " if state.job_requirements.linkedin_profiles:\n", + " # Process provided profiles\n", + " for profile_url in state.job_requirements.linkedin_profiles:\n", + " profile = tool_node.invoke({\n", + " \"name\": \"get_linkedin_profile\",\n", + " \"args\": {\"profile_url\": profile_url}\n", + " })\n", + "\n", + " if \"error\" not in profile:\n", + " message_result = tool_node.invoke({\n", + " \"name\": \"send_linkedin_message\",\n", + " \"args\": {\n", + " \"profile_url\": profile_url,\n", + " \"job_details\": job_details\n", + " }\n", + " })\n", + "\n", + " if \"error\" not in message_result:\n", + " candidates.append(\n", + " CandidateProfile(\n", + " **profile,\n", + " linkedin_url=profile_url,\n", + " status=\"contacted\",\n", + " message_sent=message_result[\"message\"]\n", + " )\n", + " )\n", + " else:\n", + " # Search for candidates using Serper API\n", + " search_results = tool_node.invoke({\n", + " \"name\": \"search_linkedin_candidates\",\n", + " \"args\": {\n", + " \"job_title\": state.job_requirements.title,\n", + " \"location\": state.job_requirements.preferred_country,\n", + " \"skills\": state.job_requirements.skills_required\n", + " }\n", + " })\n", + "\n", + " for result in search_results:\n", + " if \"error\" not in result:\n", + " profile = tool_node.invoke({\n", + " \"name\": \"get_linkedin_profile\",\n", + " \"args\": {\"profile_url\": result[\"linkedin_url\"]}\n", + " })\n", + "\n", + " if \"error\" not in profile:\n", + " message_result = tool_node.invoke({\n", + " \"name\": \"send_linkedin_message\",\n", + " \"args\": {\n", + " \"profile_url\": result[\"linkedin_url\"],\n", + " \"job_details\": job_details\n", + " }\n", + " })\n", + "\n", + " if \"error\" not in message_result:\n", + " candidates.append(\n", + " CandidateProfile(\n", + " **profile,\n", + " linkedin_url=result[\"linkedin_url\"],\n", + " status=\"contacted\",\n", + " source=\"search\",\n", + " message_sent=message_result[\"message\"]\n", + " )\n", + " )\n", + "\n", + " return {\n", + " \"candidates\": candidates,\n", + " \"linkedin_process_complete\": True,\n", + " \"phase\": \"analyze_cv\"\n", + " }" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "qeW8KGpvs6aY" + }, + "source": [ + "# CV analysis node" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "C1KvOF62XBxt" + }, + "outputs": [], + "source": [ + "def analyze_cv(state: RecruitmentState) -> Dict[str, Any]:\n", + " # Implementation to analyze candidate CVs and send appropriate responses\n", + " \"\"\"Analyze CV and send appropriate response\"\"\"\n", + " messages = [\n", + " SystemMessage(content=\"\"\"Analyze the CV against job requirements. Score from 0-10 on:\n", + " 1. Skills Match\n", + " 2. Experience Level\n", + " 3. Overall Fit\n", + "\n", + " Provide detailed feedback and clear recommendation.\"\"\"),\n", + " HumanMessage(content=f\"\"\"\n", + " Job Requirements:\n", + " {state.job_requirements.model_dump_json()}\n", + "\n", + " CV Content:\n", + " {state.candidates[-1].cv_content}\n", + " \"\"\")\n", + " ]\n", + "\n", + " analysis = llm.invoke(messages)\n", + " score = float(re.search(r\"Overall Score:\\s*(\\d+\\.?\\d*)\", analysis.content).group(1))\n", + "\n", + " tool_node = ToolNode([send_linkedin_message])\n", + "\n", + " if score >= 7.0:\n", + " status = \"approved\"\n", + " message = f\"Congratulations! You've been selected for an interview on {state.job_requirements.interview_date}\"\n", + " else:\n", + " status = \"rejected\"\n", + " message = \"Thank you for your application. Unfortunately...\"\n", + "\n", + " # Send response via LinkedIn\n", + " tool_node.invoke({\n", + " \"name\": \"send_linkedin_message\",\n", + " \"args\": {\n", + " \"profile_url\": state.candidates[-1].linkedin_url,\n", + " \"message\": message\n", + " }\n", + " })\n", + "\n", + " return {\n", + " \"cv_score\": score,\n", + " \"status\": status,\n", + " \"feedback\": analysis.content,\n", + " \"phase\": \"prepare_interview\" if status == \"approved\" else \"complete\"\n", + " }\n", + "\n", + "\n", + "# Interview preparation node\n", + "def prepare_interview(state: RecruitmentState) -> Dict[str, Any]:\n", + " \"\"\"Generate interview questions with human approval\"\"\"\n", + " messages = [\n", + " SystemMessage(content=\"\"\"Generate 10 interview questions covering:\n", + " - Technical Skills (4)\n", + " - Experience (3)\n", + " - Problem Solving (3)\"\"\"),\n", + " HumanMessage(content=f\"\"\"\n", + " Position: {state.job_requirements.title}\n", + " Required Skills: {', '.join(state.job_requirements.skills_required)}\n", + " \"\"\")\n", + " ]\n", + "\n", + " response = llm.invoke(messages)\n", + "\n", + " # Raise NodeInterrupt for human review\n", + " raise NodeInterrupt(\n", + " f\"Please review the interview questions:\\n\\n{response.content}\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rjLDE9WhtXcx" + }, + "source": [ + "# Creating the recruitment workflow" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "r8tMD9d8ZdAg" + }, + "outputs": [], + "source": [ + "def create_recruitment_workflow():\n", + " \"\"\"Create the recruitment workflow graph\"\"\"\n", + " workflow = StateGraph(RecruitmentState)\n", + " memory = MemorySaver()\n", + "\n", + " # Add nodes\n", + " workflow.add_node(\"requirements_gathering\", requirements_gathering)\n", + " workflow.add_node(\"generate_job_desc\", generate_job_desc)\n", + " workflow.add_node(\"linkedin_process\", linkedin_process)\n", + " workflow.add_node(\"analyze_cv\", analyze_cv)\n", + " workflow.add_node(\"prepare_interview\", prepare_interview)\n", + "\n", + " # Add edges with human-in-the-loop cycles\n", + " workflow.add_edge(START, \"requirements_gathering\")\n", + " workflow.add_edge(\"requirements_gathering\", \"generate_job_desc\")\n", + "\n", + " def route_after_job_desc(state: RecruitmentState):\n", + " return \"linkedin_process\" if state.job_description_approved else \"generate_job_desc\"\n", + "\n", + " def route_after_cv(state: RecruitmentState):\n", + " return \"prepare_interview\" if state.status == \"approved\" else END\n", + "\n", + " workflow.add_conditional_edges(\n", + " \"generate_job_desc\",\n", + " route_after_job_desc,\n", + " [\"linkedin_process\", \"generate_job_desc\"]\n", + " )\n", + "\n", + " workflow.add_edge(\"linkedin_process\", \"analyze_cv\")\n", + " workflow.add_conditional_edges(\n", + " \"analyze_cv\",\n", + " route_after_cv,\n", + " [\"prepare_interview\", END]\n", + " )\n", + " workflow.add_edge(\"prepare_interview\", END)\n", + "\n", + " # Compile with breakpoints\n", + " graph = workflow.compile(\n", + " checkpointer=memory,\n", + " interrupt_before=[\"generate_job_desc\", \"prepare_interview\"]\n", + " )\n", + "\n", + " # Generate and display the Mermaid visualization\n", + " print(\"\"\"graph TD\n", + " Start --> requirements_gathering\n", + " requirements_gathering --> generate_job_desc\n", + " generate_job_desc -->|Approved| linkedin_process\n", + " generate_job_desc -->|Not Approved| generate_job_desc\n", + " linkedin_process --> analyze_cv\n", + " analyze_cv -->|CV-Good| prepare_interview\n", + " analyze_cv -->|CV-Bad| End\n", + " prepare_interview -->|Approved| End\n", + " prepare_interview -->|Not Approved| prepare_interview\n", + " \"\"\")\n", + " display(Image(graph.get_graph().draw_mermaid_png()))\n", + "\n", + " return graph\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "source": [ + "def get_job_requirements():\n", + " job_title = input(\"What is the job title? \")\n", + " company_description = input(\"Provide a description of the company: \")\n", + " job_requirements = input(\"List the job requirements (comma-separated): \").split(\",\")\n", + " candidate_responsibilities = input(\"List the candidate responsibilities (comma-separated): \").split(\",\")\n", + " candidate_qualifications = input(\"List the candidate qualifications (comma-separated): \").split(\",\")\n", + " company_benefits = input(\"List the company benefits (comma-separated): \").split(\",\")\n", + " interview_date = input(\"What is the interview date (YYYY-MM-DD)? \")\n", + " preferred_country = input(\"What is the preferred country for the role? \")\n", + " years_experience = int(input(\"What is the required years of experience? \"))\n", + " linkedin_profiles = input(\"Provide any LinkedIn profile URLs (comma-separated, optional): \").split(\",\")\n", + " skills_required = input(\"List the required skills (comma-separated): \").split(\",\")\n", + " salary_range = input(\"What is the salary range for the role? \")\n", + "\n", + " return JobRequirements(\n", + " title=job_title,\n", + " company_description=company_description,\n", + " job_requirements=job_requirements,\n", + " candidate_responsibilities = candidate_responsibilities,\n", + " candidate_qualifications = candidate_qualifications,\n", + " company_benefits = company_benefits,\n", + " interview_date = interview_date,\n", + " preferred_country = preferred_country,\n", + " years_experience = years_experience,\n", + " linkedin_profiles = linkedin_profiles,\n", + " skills_required = skills_required,\n", + " salary_range = salary_range,\n", + " )" + ], + "metadata": { + "id": "3HclsfHXg89P" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "background_save": true, + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "id": "BLNZoZ5HlYIM", + "outputId": "315bc15b-0f05-4cab-a5e8-f9ab8ec05a19" + }, + "outputs": [ + { + "metadata": { + "tags": null + }, + "name": "stdout", + "output_type": "stream", + "text": [ + "graph TD\n", + " Start --> requirements_gathering\n", + " requirements_gathering --> generate_job_desc\n", + " generate_job_desc -->|Approved| linkedin_process\n", + " generate_job_desc -->|Not Approved| generate_job_desc\n", + " linkedin_process --> analyze_cv\n", + " analyze_cv -->|CV-Good| prepare_interview\n", + " analyze_cv -->|CV-Bad| End\n", + " prepare_interview -->|Approved| End\n", + " prepare_interview -->|Not Approved| prepare_interview\n", + " \n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "metadata": { + "tags": null + }, + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "New State Update:\n", + "Phase: requirements_gathering\n" + ] + } + ], + "source": [ + "# Create the recruitment workflow instance\n", + "recruitment_workflow = create_recruitment_workflow()\n", + "\n", + "# Set the test configuration\n", + "config = {\"configurable\": {\"thread_id\": \"test_1\"}}\n", + "\n", + "# Define the initial state of the recruitment process\n", + "initial_state = {\n", + " \"phase\": \"requirements_gathering\",\n", + " \"messages\": [],\n", + " \"job_requirements\": None,\n", + " \"job_description\": None,\n", + " \"job_description_approved\": False,\n", + " \"candidates\": [],\n", + " \"linkedin_process_complete\": False,\n", + " \"cv_analysis_complete\": False,\n", + " \"interview_questions\": None,\n", + " \"interview_questions_approved\": False\n", + "}\n", + "\n", + "# Run the workflow and handle any interrupts\n", + "try:\n", + " for event in recruitment_workflow.stream(initial_state, config, stream_mode=\"values\"):\n", + " print(\"\\nNew State Update:\")\n", + " print(f\"Phase: {event.get('phase')}\")\n", + " if event.get('messages'):\n", + " print(f\"Latest Message: {event['messages'][-1].content}\")\n", + "except NodeInterrupt as e:\n", + " print(f\"\\nHuman Review Required: {str(e)}\")\n", + "\n", + " # Example of handling job description approval\n", + " recruitment_workflow.update_state(\n", + " config,\n", + " {\"job_description_approved\": True}\n", + " )\n", + "\n", + " # Continue the workflow execution\n", + " for event in recruitment_workflow.stream(None, config, stream_mode=\"values\"):\n", + " print(\"\\nNew State Update:\")\n", + " print(f\"Phase: {event.get('phase')}\")\n", + " if event.get('messages'):\n", + " print(f\"Latest Message: {event['messages'][-1].content}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "qDknbuO8zzwF" + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file From 83534787a261416d01e6b98dcbe860a6fe9a720b Mon Sep 17 00:00:00 2001 From: Emmanuel Ezeokeke Date: Mon, 25 Nov 2024 09:34:11 +0100 Subject: [PATCH 2/2] Add HR AI Agent tutorial --- all_agents_tutorials/HR_AI-Assistant.ipynb | 970 +++++++++++++++++++++ 1 file changed, 970 insertions(+) create mode 100644 all_agents_tutorials/HR_AI-Assistant.ipynb diff --git a/all_agents_tutorials/HR_AI-Assistant.ipynb b/all_agents_tutorials/HR_AI-Assistant.ipynb new file mode 100644 index 0000000..510096e --- /dev/null +++ b/all_agents_tutorials/HR_AI-Assistant.ipynb @@ -0,0 +1,970 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "ATM_0NvmvJXw" + }, + "source": [ + "# HR AI Assistant" + ] + }, + { + "cell_type": "markdown", + "source": [ + "### Overview\n", + "\n", + "In this project, I create a recruitment workflow using LangGraph, LangChain, and various APIs to automate and streamline the job posting and candidate evaluation process. The workflow consists of the following key steps:\n", + "\n", + "* Requirements Gathering: The AI agent prompts the user for detailed job requirements, including the job title, company description, candidate responsibilities and qualifications, preferred location, and other relevant details.\n", + "\n", + "* Job Description Generation: Once the job requirements are gathered, the agent generates a professional and compelling job description. The user can then review and approve the description, or provide feedback for the agent to refine it.\n", + "\n", + "* LinkedIn Candidate Search and Outreach: If the user provides specific LinkedIn profiles of preferred candidates, the agent will directly message them about the opportunity. Alternatively, the agent can search LinkedIn for relevant candidates based on the job details and send outreach messages.\n", + "\n", + "* CV Analysis: As candidates submit their CVs, the agent evaluates them against the job requirements, providing a score and recommendation (approve or reject) for each applicant. The agent then sends the appropriate response message to the candidate via LinkedIn.\n", + "\n", + "* Interview Question Preparation: For approved candidates, the agent generates a set of interview questions covering technical skills, experience, and problem-solving. The user can review and approve the questions before the interviews are conducted." + ], + "metadata": { + "id": "RR0G4A9yV85Y" + } + }, + { + "cell_type": "markdown", + "source": [ + "### Motivation\n", + "\n", + "The motivation for this project is to automate and streamline the recruitment process, which can be time-consuming and labor-intensive for HR professionals. By leveraging AI and various APIs, the goal is to create an efficient and effective system that can handle tasks such as job posting, candidate outreach, CV analysis, and interview preparation, allowing HR teams to focus on other important aspects of the hiring process." + ], + "metadata": { + "id": "71BPHxoDWDvv" + } + }, + { + "cell_type": "markdown", + "source": [ + "### Implementation\n", + "\n", + "The code is implemented using a combination of LangGraph, LangChain, and various APIs, including the Anthropic API for language modeling, the Google Serper API for LinkedIn searches, and the LinkedIn API for outreach and profile retrieval.\n", + "\n", + "The code is organized into several sections:\n", + "* Importing Required Libraries: The necessary libraries and APIs are imported at the beginning of the notebook.\n", + "* Defining Base Models: The code defines the base models for job requirements, candidate profiles, and the overall recruitment state.\n", + "* Defining Tool Functions: The code defines the tool functions that the agent will use, such as searching for LinkedIn candidates, retrieving LinkedIn profile information, and sending LinkedIn messages.\n", + "* Defining Workflow Nodes: The code defines the various nodes in the recruitment workflow, such as requirements gathering, job description generation, LinkedIn process, CV analysis, and interview question preparation.\n", + "* Creating the Recruitment Workflow: The code combines the workflow nodes and sets up the overall recruitment workflow, including the use of breakpoints for human-in-the-loop interactions.\n", + "* Running the Workflow: The code demonstrates how to run the recruitment workflow, handle interrupts, and update the state of the process.\n", + "\n", + "\n", + "\n", + "### Conclusion\n", + "This project demonstrates the power of AI-powered automation in the recruitment process. By leveraging LangGraph, LangChain, and various APIs, the agent is able to streamline tasks such as job posting, candidate outreach, CV analysis, and interview preparation, freeing up HR professionals to focus on other important aspects of the hiring process. The use of human-in-the-loop interactions allows for a balance between automation and human oversight, ensuring the quality and accuracy of the recruitment process." + ], + "metadata": { + "id": "DKRVCaYzXZ7J" + } + }, + { + "cell_type": "markdown", + "metadata": { + "id": "exsYcl5UvYc3" + }, + "source": [ + "# Installing required packages" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "collapsed": true, + "id": "Js765NcYELqC", + "outputId": "1236f2a6-977f-41c5-b92b-3bcaf7d645f2" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Requirement already satisfied: langchain in /usr/local/lib/python3.10/dist-packages (0.3.7)\n", + "Collecting langchain-anthropic\n", + " Downloading langchain_anthropic-0.3.0-py3-none-any.whl.metadata (2.3 kB)\n", + "Collecting langgraph\n", + " Downloading langgraph-0.2.53-py3-none-any.whl.metadata (15 kB)\n", + "Collecting python-dotenv\n", + " Downloading python_dotenv-1.0.1-py3-none-any.whl.metadata (23 kB)\n", + "Requirement already satisfied: pydantic in /usr/local/lib/python3.10/dist-packages (2.9.2)\n", + "Collecting langchain_community\n", + " Downloading langchain_community-0.3.8-py3-none-any.whl.metadata (2.9 kB)\n", + "Requirement already satisfied: requests in /usr/local/lib/python3.10/dist-packages (2.32.3)\n", + "Requirement already satisfied: PyYAML>=5.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (6.0.2)\n", + "Requirement already satisfied: SQLAlchemy<3,>=1.4 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.0.36)\n", + "Requirement already satisfied: aiohttp<4.0.0,>=3.8.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (3.11.2)\n", + "Requirement already satisfied: async-timeout<5.0.0,>=4.0.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (4.0.3)\n", + "Requirement already satisfied: langchain-core<0.4.0,>=0.3.15 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.3.19)\n", + "Requirement already satisfied: langchain-text-splitters<0.4.0,>=0.3.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.3.2)\n", + "Requirement already satisfied: langsmith<0.2.0,>=0.1.17 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.1.143)\n", + "Requirement already satisfied: numpy<2,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain) (1.26.4)\n", + "Requirement already satisfied: tenacity!=8.4.0,<10,>=8.1.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (9.0.0)\n", + "Collecting anthropic<1,>=0.39.0 (from langchain-anthropic)\n", + " Downloading anthropic-0.39.0-py3-none-any.whl.metadata (22 kB)\n", + "Requirement already satisfied: defusedxml<0.8.0,>=0.7.1 in /usr/local/lib/python3.10/dist-packages (from langchain-anthropic) (0.7.1)\n", + "Collecting langgraph-checkpoint<3.0.0,>=2.0.4 (from langgraph)\n", + " Downloading langgraph_checkpoint-2.0.5-py3-none-any.whl.metadata (4.6 kB)\n", + "Collecting langgraph-sdk<0.2.0,>=0.1.32 (from langgraph)\n", + " Downloading langgraph_sdk-0.1.36-py3-none-any.whl.metadata (1.8 kB)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /usr/local/lib/python3.10/dist-packages (from pydantic) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.23.4 in /usr/local/lib/python3.10/dist-packages (from pydantic) (2.23.4)\n", + "Requirement already satisfied: typing-extensions>=4.6.1 in /usr/local/lib/python3.10/dist-packages (from pydantic) (4.12.2)\n", + "Collecting SQLAlchemy<3,>=1.4 (from langchain)\n", + " Downloading SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (9.6 kB)\n", + "Collecting dataclasses-json<0.7,>=0.5.7 (from langchain_community)\n", + " Downloading dataclasses_json-0.6.7-py3-none-any.whl.metadata (25 kB)\n", + "Collecting httpx-sse<0.5.0,>=0.4.0 (from langchain_community)\n", + " Downloading httpx_sse-0.4.0-py3-none-any.whl.metadata (9.0 kB)\n", + "Collecting langchain\n", + " Downloading langchain-0.3.8-py3-none-any.whl.metadata (7.1 kB)\n", + "Collecting langchain-core<0.4.0,>=0.3.15 (from langchain)\n", + " Downloading langchain_core-0.3.21-py3-none-any.whl.metadata (6.3 kB)\n", + "Collecting pydantic-settings<3.0.0,>=2.4.0 (from langchain_community)\n", + " Downloading pydantic_settings-2.6.1-py3-none-any.whl.metadata (3.5 kB)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests) (3.4.0)\n", + "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests) (3.10)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests) (2.2.3)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests) (2024.8.30)\n", + "Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (2.4.3)\n", + "Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.3.1)\n", + "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (24.2.0)\n", + "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.5.0)\n", + "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (6.1.0)\n", + "Requirement already satisfied: propcache>=0.2.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (0.2.0)\n", + "Requirement already satisfied: yarl<2.0,>=1.17.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.17.2)\n", + "Requirement already satisfied: anyio<5,>=3.5.0 in /usr/local/lib/python3.10/dist-packages (from anthropic<1,>=0.39.0->langchain-anthropic) (3.7.1)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /usr/local/lib/python3.10/dist-packages (from anthropic<1,>=0.39.0->langchain-anthropic) (1.9.0)\n", + "Requirement already satisfied: httpx<1,>=0.23.0 in /usr/local/lib/python3.10/dist-packages (from anthropic<1,>=0.39.0->langchain-anthropic) (0.27.2)\n", + "Requirement already satisfied: jiter<1,>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from anthropic<1,>=0.39.0->langchain-anthropic) (0.7.1)\n", + "Requirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from anthropic<1,>=0.39.0->langchain-anthropic) (1.3.1)\n", + "Collecting marshmallow<4.0.0,>=3.18.0 (from dataclasses-json<0.7,>=0.5.7->langchain_community)\n", + " Downloading marshmallow-3.23.1-py3-none-any.whl.metadata (7.5 kB)\n", + "Collecting typing-inspect<1,>=0.4.0 (from dataclasses-json<0.7,>=0.5.7->langchain_community)\n", + " Downloading typing_inspect-0.9.0-py3-none-any.whl.metadata (1.5 kB)\n", + "Requirement already satisfied: jsonpatch<2.0,>=1.33 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.4.0,>=0.3.15->langchain) (1.33)\n", + "Requirement already satisfied: packaging<25,>=23.2 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.4.0,>=0.3.15->langchain) (24.2)\n", + "Requirement already satisfied: msgpack<2.0.0,>=1.1.0 in /usr/local/lib/python3.10/dist-packages (from langgraph-checkpoint<3.0.0,>=2.0.4->langgraph) (1.1.0)\n", + "Requirement already satisfied: orjson>=3.10.1 in /usr/local/lib/python3.10/dist-packages (from langgraph-sdk<0.2.0,>=0.1.32->langgraph) (3.10.11)\n", + "Requirement already satisfied: requests-toolbelt<2.0.0,>=1.0.0 in /usr/local/lib/python3.10/dist-packages (from langsmith<0.2.0,>=0.1.17->langchain) (1.0.0)\n", + "Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.10/dist-packages (from SQLAlchemy<3,>=1.4->langchain) (3.1.1)\n", + "Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->anthropic<1,>=0.39.0->langchain-anthropic) (1.2.2)\n", + "Requirement already satisfied: httpcore==1.* in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->anthropic<1,>=0.39.0->langchain-anthropic) (1.0.7)\n", + "Requirement already satisfied: h11<0.15,>=0.13 in /usr/local/lib/python3.10/dist-packages (from httpcore==1.*->httpx<1,>=0.23.0->anthropic<1,>=0.39.0->langchain-anthropic) (0.14.0)\n", + "Requirement already satisfied: jsonpointer>=1.9 in /usr/local/lib/python3.10/dist-packages (from jsonpatch<2.0,>=1.33->langchain-core<0.4.0,>=0.3.15->langchain) (3.0.0)\n", + "Collecting mypy-extensions>=0.3.0 (from typing-inspect<1,>=0.4.0->dataclasses-json<0.7,>=0.5.7->langchain_community)\n", + " Downloading mypy_extensions-1.0.0-py3-none-any.whl.metadata (1.1 kB)\n", + "Downloading langchain_anthropic-0.3.0-py3-none-any.whl (22 kB)\n", + "Downloading langgraph-0.2.53-py3-none-any.whl (125 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m125.1/125.1 kB\u001b[0m \u001b[31m11.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading python_dotenv-1.0.1-py3-none-any.whl (19 kB)\n", + "Downloading langchain_community-0.3.8-py3-none-any.whl (2.4 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.4/2.4 MB\u001b[0m \u001b[31m77.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading langchain-0.3.8-py3-none-any.whl (1.0 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.0/1.0 MB\u001b[0m \u001b[31m57.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading anthropic-0.39.0-py3-none-any.whl (198 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m198.4/198.4 kB\u001b[0m \u001b[31m18.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading dataclasses_json-0.6.7-py3-none-any.whl (28 kB)\n", + "Downloading httpx_sse-0.4.0-py3-none-any.whl (7.8 kB)\n", + "Downloading langchain_core-0.3.21-py3-none-any.whl (409 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m409.5/409.5 kB\u001b[0m \u001b[31m35.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading langgraph_checkpoint-2.0.5-py3-none-any.whl (24 kB)\n", + "Downloading langgraph_sdk-0.1.36-py3-none-any.whl (29 kB)\n", + "Downloading pydantic_settings-2.6.1-py3-none-any.whl (28 kB)\n", + "Downloading SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.1 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m3.1/3.1 MB\u001b[0m \u001b[31m78.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading marshmallow-3.23.1-py3-none-any.whl (49 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m49.5/49.5 kB\u001b[0m \u001b[31m4.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading typing_inspect-0.9.0-py3-none-any.whl (8.8 kB)\n", + "Downloading mypy_extensions-1.0.0-py3-none-any.whl (4.7 kB)\n", + "Installing collected packages: SQLAlchemy, python-dotenv, mypy-extensions, marshmallow, httpx-sse, typing-inspect, pydantic-settings, langgraph-sdk, dataclasses-json, anthropic, langchain-core, langgraph-checkpoint, langchain-anthropic, langgraph, langchain, langchain_community\n", + " Attempting uninstall: SQLAlchemy\n", + " Found existing installation: SQLAlchemy 2.0.36\n", + " Uninstalling SQLAlchemy-2.0.36:\n", + " Successfully uninstalled SQLAlchemy-2.0.36\n", + " Attempting uninstall: langchain-core\n", + " Found existing installation: langchain-core 0.3.19\n", + " Uninstalling langchain-core-0.3.19:\n", + " Successfully uninstalled langchain-core-0.3.19\n", + " Attempting uninstall: langchain\n", + " Found existing installation: langchain 0.3.7\n", + " Uninstalling langchain-0.3.7:\n", + " Successfully uninstalled langchain-0.3.7\n", + "Successfully installed SQLAlchemy-2.0.35 anthropic-0.39.0 dataclasses-json-0.6.7 httpx-sse-0.4.0 langchain-0.3.8 langchain-anthropic-0.3.0 langchain-core-0.3.21 langchain_community-0.3.8 langgraph-0.2.53 langgraph-checkpoint-2.0.5 langgraph-sdk-0.1.36 marshmallow-3.23.1 mypy-extensions-1.0.0 pydantic-settings-2.6.1 python-dotenv-1.0.1 typing-inspect-0.9.0\n" + ] + } + ], + "source": [ + "!pip install langchain langchain-anthropic langgraph python-dotenv pydantic langchain_community requests" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "RcnKWPYtIiOs" + }, + "outputs": [], + "source": [ + "# Core imports\n", + "import os\n", + "from typing import Dict, Any, List, Optional, Literal, Annotated, TypedDict\n", + "from typing_extensions import TypedDict\n", + "from pydantic import BaseModel, Field\n", + "from datetime import datetime\n", + "import operator\n", + "import json\n", + "import uuid\n", + "import requests\n", + "\n", + "# LangChain imports\n", + "from langchain_anthropic import ChatAnthropic\n", + "from langchain_core.messages import SystemMessage, HumanMessage, AIMessage, BaseMessage\n", + "from langchain_core.tools import tool\n", + "from langchain_community.utilities import GoogleSerperAPIWrapper\n", + "\n", + "# LangGraph imports\n", + "from langgraph.graph import StateGraph, START, END, MessagesState\n", + "from langgraph.checkpoint.memory import MemorySaver\n", + "from langgraph.prebuilt import ToolNode\n", + "from langgraph.graph.message import add_messages\n", + "from langgraph.errors import NodeInterrupt\n", + "\n", + "# Display imports\n", + "from IPython.display import Image, display" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "id": "jjK6aljDWuef" + }, + "outputs": [], + "source": [ + "# API Keys\n", + "os.environ[\"ANTHROPIC_API_KEY\"] = \"ANTHROPIC_API_KEY\"\n", + "os.environ[\"SERPER_API_KEY\"] = \"SERPER_API_KEY\"\n", + "os.environ[\"LINKEDIN_COOKIE\"] = \"LINKEDIN_COOKIE\"\n", + "\n", + "llm = ChatAnthropic(model=\"claude-3-sonnet-20240229\", temperature=0)\n", + "\n", + "# Initialize search utility\n", + "search = GoogleSerperAPIWrapper()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "txviulRpr2-n" + }, + "source": [ + " # Defining base models\n", + "\n", + " Defines models for job requirements, candidate profiles, and overall recruitment state." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "yDqf9YahWx3r" + }, + "outputs": [], + "source": [ + "# Job requirements model\n", + "class JobRequirements(BaseModel):\n", + " # Details about the job requirements\n", + " title: str\n", + " company_description: str\n", + " job_requirements: List[str]\n", + " candidate_responsibilities: List[str]\n", + " candidate_qualifications: List[str]\n", + " company_benefits: List[str]\n", + " interview_date: datetime\n", + " preferred_country: str\n", + " years_experience: int\n", + " linkedin_profiles: Optional[List[str]] = None\n", + " skills_required: List[str]\n", + " salary_range: str\n", + "\n", + "\n", + "\n", + "# Candidate profile model\n", + "class CandidateProfile(BaseModel):\n", + " # Details about the candidate\n", + " name: str\n", + " linkedin_url: str\n", + " title: str\n", + " location: str\n", + " cv_content: Optional[str] = None\n", + " cv_score: Optional[float] = None\n", + " status: str = \"new\" # 'new', 'contacted', 'cv_received', 'approved', 'rejected'\n", + " feedback: Optional[str] = None\n", + " source: str = \"direct\" # 'direct' or 'search'\n", + " skill_matches: Optional[List[str]] = None\n", + " skill_gaps: Optional[List[str]] = None\n", + " message_sent: Optional[str] = None\n", + "\n", + "# Main State Model\n", + "class RecruitmentState(BaseModel):\n", + " # Overall state of the recruitment process\n", + " phase: str\n", + " messages: Annotated[List[BaseMessage], add_messages]\n", + " job_requirements: Optional[JobRequirements]\n", + " job_description: Optional[str]\n", + " job_description_approved: bool\n", + " candidates: Annotated[List[CandidateProfile], operator.add]\n", + " linkedin_process_complete: bool\n", + " cv_analysis_complete: bool\n", + " interview_questions: Optional[List[str]]\n", + " interview_questions_approved: bool" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4xM3gjh0sMPh" + }, + "source": [ + "# Defining tool functions\n", + "Defines functions for LinkedIn searches, profile retrieval, and messaging." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "gpcAMpRUW4WG" + }, + "outputs": [], + "source": [ + "# Search for LinkedIn candidates using Google Serper API\n", + "@tool\n", + "def search_linkedin_candidates(\n", + " job_title: str,\n", + " location: str,\n", + " skills: List[str],\n", + " limit: int = 5\n", + ") -> List[Dict[str, str]]:\n", + " \"\"\"Search for candidates on LinkedIn using Google Search API\"\"\"\n", + " search = GoogleSerperAPIWrapper()\n", + " search_query = f\"\"\"site:linkedin.com/in/\n", + " {job_title}\n", + " {location}\n", + " {' '.join(skills)}\"\"\"\n", + "\n", + " try:\n", + " results = search.results(search_query)\n", + " candidates = []\n", + " for result in results.get('organic', [])[:limit]:\n", + " if \"linkedin.com/in/\" in result.get(\"link\", \"\"):\n", + " candidates.append({\n", + " \"linkedin_url\": result[\"link\"],\n", + " \"title\": result.get(\"title\", \"\"),\n", + " \"snippet\": result.get(\"snippet\", \"\")\n", + " })\n", + " return candidates\n", + " except Exception as e:\n", + " return [{\"error\": str(e)}]\n", + "\n", + "\n", + "# Get detailed LinkedIn profile information\n", + "@tool\n", + "def get_linkedin_profile(profile_url: str) -> Dict[str, Any]:\n", + " \"\"\"Get detailed profile information from LinkedIn URL\"\"\"\n", + " headers = {\n", + " 'cookie': os.getenv(\"LINKEDIN_COOKIE\"),\n", + " 'accept': 'application/json'\n", + " }\n", + " try:\n", + " profile_id = profile_url.split('/in/')[-1].split('/')[0]\n", + " api_url = f\"https://www.linkedin.com/voyager/api/identity/profiles/{profile_id}/profileView\"\n", + " response = requests.get(api_url, headers=headers)\n", + " data = response.json()\n", + "\n", + " return {\n", + " \"name\": f\"{data.get('firstName', '')} {data.get('lastName', '')}\",\n", + " \"title\": data.get('headline', ''),\n", + " \"location\": data.get('locationName', ''),\n", + " \"skills\": [skill.get('name', '') for skill in data.get('skills', [])]\n", + " }\n", + " except Exception as e:\n", + " return {\"error\": str(e)}\n", + "\n", + "\n", + "# Send a message to a LinkedIn candidate about the job opportunity\n", + "@tool\n", + "def send_linkedin_message(profile_url: str, job_details: str) -> Dict[str, str]:\n", + " \"\"\"Send a LinkedIn message to a candidate about the job opportunity\"\"\"\n", + " headers = {\n", + " 'cookie': os.getenv(\"LINKEDIN_COOKIE\"),\n", + " 'accept': 'application/json'\n", + " }\n", + " try:\n", + " profile_id = profile_url.split('/in/')[-1].split('/')[0]\n", + "\n", + " # Generate personalized message\n", + " message_prompt = [\n", + " SystemMessage(content=\"Generate a personalized LinkedIn outreach message for a job opportunity. Ask them to submit their CV directly through LinkedIn messages if interested.\"),\n", + " HumanMessage(content=f\"Job Details:\\n{job_details}\")\n", + " ]\n", + " message_content = llm.invoke(message_prompt).content\n", + "\n", + " # Send message via LinkedIn API\n", + " message_endpoint = f\"https://www.linkedin.com/voyager/api/messaging/conversations\"\n", + " payload = {\n", + " \"recipients\": [profile_id],\n", + " \"message\": message_content\n", + " }\n", + " response = requests.post(message_endpoint, headers=headers, json=payload)\n", + "\n", + " return {\n", + " \"status\": \"sent\",\n", + " \"profile_id\": profile_id,\n", + " \"message\": message_content\n", + " }\n", + " except Exception as e:\n", + " return {\"error\": str(e)}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HXa3oKh0sfp7" + }, + "source": [ + "# Defining workflow nodes\n", + "Defines nodes for requirements gathering, job description, CV analysis, etc\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "id": "uPF4UD5vW7tx" + }, + "outputs": [], + "source": [ + "def requirements_gathering(state: RecruitmentState) -> Dict[str, Any]:\n", + " \"\"\"\n", + " Initialize or continue requirements gathering process.\n", + "\n", + " Args:\n", + " state: Current recruitment workflow state\n", + "\n", + " Returns:\n", + " Dict with state updates\n", + " \"\"\"\n", + " if not state.messages: # Using dot notation instead of dict notation\n", + " # Get job requirements input using the correct JobRequirements model\n", + " job_requirements = JobRequirements(\n", + " title=input(\"What is the job title? \"),\n", + " company_description=input(\"Provide a description of the company: \"),\n", + " job_requirements=input(\"List the job requirements (comma-separated): \").split(\",\"),\n", + " candidate_responsibilities=input(\"List the candidate responsibilities (comma-separated): \").split(\",\"),\n", + " candidate_qualifications=input(\"List the candidate qualifications (comma-separated): \").split(\",\"),\n", + " company_benefits=input(\"List the company benefits (comma-separated): \").split(\",\"),\n", + " interview_date=datetime.strptime(\n", + " input(\"What is the interview date (YYYY-MM-DD)? \"),\n", + " \"%Y-%m-%d\"\n", + " ),\n", + " preferred_country=input(\"What is the preferred country for the role? \"),\n", + " years_experience=int(input(\"What is the required years of experience? \")),\n", + " linkedin_profiles=[\n", + " p.strip()\n", + " for p in input(\"Provide any LinkedIn profile URLs (comma-separated, optional): \").split(\",\")\n", + " if p.strip()\n", + " ],\n", + " skills_required=input(\"List the required skills (comma-separated): \").split(\",\"),\n", + " salary_range=input(\"What is the salary range for the role? \")\n", + " )\n", + "\n", + " return {\n", + " \"messages\": [\n", + " SystemMessage(content=\"You are an HR assistant gathering detailed job requirements.\"),\n", + " HumanMessage(content=\"Let's begin gathering the job requirements. What is the job title?\")\n", + " ],\n", + " \"job_requirements\": job_requirements,\n", + " \"phase\": \"requirements_gathering\"\n", + " }\n", + " else:\n", + " return {\"phase\": state.phase} # Return current state if messages exist\n", + "\n", + "\n", + "\n", + "def generate_job_desc(state: RecruitmentState) -> Dict[str, Any]:\n", + " \"\"\"\n", + " Generate job description with human-in-the-loop review.\n", + "\n", + " Args:\n", + " state: Current recruitment workflow state\n", + "\n", + " Returns:\n", + " Dict with state updates\n", + "\n", + " Raises:\n", + " NodeInterrupt: To get human review of generated description\n", + " \"\"\"\n", + " if not state[\"job_requirements\"]:\n", + " raise ValueError(\"Job requirements missing\")\n", + "\n", + " messages = [\n", + " SystemMessage(content=\"\"\"Create a professional and compelling job description with:\n", + " 1. About the Company\n", + " 2. Role Overview\n", + " 3. Key Responsibilities\n", + " 4. Required Qualifications\n", + " 5. What We Offer (Benefits)\n", + " 6. Location and Work Mode\"\"\"),\n", + " HumanMessage(content=str(state[\"job_requirements\"]))\n", + " ]\n", + "\n", + " response = llm.invoke(messages)\n", + "\n", + " raise NodeInterrupt(\n", + " f\"Please review the generated job description:\\n\\n{response.content}\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "wEAg-s34sp1z" + }, + "source": [ + "# LinkedIn process node\n", + "Handles LinkedIn candidate search, outreach, and messaging." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "id": "hraKSYGjW--0" + }, + "outputs": [], + "source": [ + "def linkedin_process(state: RecruitmentState) -> Dict[str, Any]:\n", + " # Implementation to handle the LinkedIn candidate search and outreach process\n", + " \"\"\"Handle LinkedIn candidate search/outreach process\"\"\"\n", + " candidates = []\n", + " tool_node = ToolNode([search_linkedin_candidates, get_linkedin_profile, send_linkedin_message])\n", + "\n", + " job_details = f\"\"\"\n", + " Role: {state.job_requirements.title}\n", + " Company: {state.job_requirements.company_description}\n", + " Location: {state.job_requirements.preferred_country}\n", + " Description: {state.job_description}\n", + " \"\"\"\n", + "\n", + " if state.job_requirements.linkedin_profiles:\n", + " # Process provided profiles\n", + " for profile_url in state.job_requirements.linkedin_profiles:\n", + " profile = tool_node.invoke({\n", + " \"name\": \"get_linkedin_profile\",\n", + " \"args\": {\"profile_url\": profile_url}\n", + " })\n", + "\n", + " if \"error\" not in profile:\n", + " message_result = tool_node.invoke({\n", + " \"name\": \"send_linkedin_message\",\n", + " \"args\": {\n", + " \"profile_url\": profile_url,\n", + " \"job_details\": job_details\n", + " }\n", + " })\n", + "\n", + " if \"error\" not in message_result:\n", + " candidates.append(\n", + " CandidateProfile(\n", + " **profile,\n", + " linkedin_url=profile_url,\n", + " status=\"contacted\",\n", + " message_sent=message_result[\"message\"]\n", + " )\n", + " )\n", + " else:\n", + " # Search for candidates using Serper API\n", + " search_results = tool_node.invoke({\n", + " \"name\": \"search_linkedin_candidates\",\n", + " \"args\": {\n", + " \"job_title\": state.job_requirements.title,\n", + " \"location\": state.job_requirements.preferred_country,\n", + " \"skills\": state.job_requirements.skills_required\n", + " }\n", + " })\n", + "\n", + " for result in search_results:\n", + " if \"error\" not in result:\n", + " profile = tool_node.invoke({\n", + " \"name\": \"get_linkedin_profile\",\n", + " \"args\": {\"profile_url\": result[\"linkedin_url\"]}\n", + " })\n", + "\n", + " if \"error\" not in profile:\n", + " message_result = tool_node.invoke({\n", + " \"name\": \"send_linkedin_message\",\n", + " \"args\": {\n", + " \"profile_url\": result[\"linkedin_url\"],\n", + " \"job_details\": job_details\n", + " }\n", + " })\n", + "\n", + " if \"error\" not in message_result:\n", + " candidates.append(\n", + " CandidateProfile(\n", + " **profile,\n", + " linkedin_url=result[\"linkedin_url\"],\n", + " status=\"contacted\",\n", + " source=\"search\",\n", + " message_sent=message_result[\"message\"]\n", + " )\n", + " )\n", + "\n", + " return {\n", + " \"candidates\": candidates,\n", + " \"linkedin_process_complete\": True,\n", + " \"phase\": \"analyze_cv\"\n", + " }" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "qeW8KGpvs6aY" + }, + "source": [ + "# CV analysis node\n", + "Evaluates candidate CVs and sends approval/rejection messages." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "id": "C1KvOF62XBxt" + }, + "outputs": [], + "source": [ + "def analyze_cv(state: RecruitmentState) -> Dict[str, Any]:\n", + " # Implementation to analyze candidate CVs and send appropriate responses\n", + " \"\"\"Analyze CV and send appropriate response\"\"\"\n", + " messages = [\n", + " SystemMessage(content=\"\"\"Analyze the CV against job requirements. Score from 0-10 on:\n", + " 1. Skills Match\n", + " 2. Experience Level\n", + " 3. Overall Fit\n", + "\n", + " Provide detailed feedback and clear recommendation.\"\"\"),\n", + " HumanMessage(content=f\"\"\"\n", + " Job Requirements:\n", + " {state.job_requirements.model_dump_json()}\n", + "\n", + " CV Content:\n", + " {state.candidates[-1].cv_content}\n", + " \"\"\")\n", + " ]\n", + "\n", + " analysis = llm.invoke(messages)\n", + " score = float(re.search(r\"Overall Score:\\s*(\\d+\\.?\\d*)\", analysis.content).group(1))\n", + "\n", + " tool_node = ToolNode([send_linkedin_message])\n", + "\n", + " if score >= 7.0:\n", + " status = \"approved\"\n", + " message = f\"Congratulations! You've been selected for an interview on {state.job_requirements.interview_date}\"\n", + " else:\n", + " status = \"rejected\"\n", + " message = \"Thank you for your application. Unfortunately...\"\n", + "\n", + " # Send response via LinkedIn\n", + " tool_node.invoke({\n", + " \"name\": \"send_linkedin_message\",\n", + " \"args\": {\n", + " \"profile_url\": state.candidates[-1].linkedin_url,\n", + " \"message\": message\n", + " }\n", + " })\n", + "\n", + " return {\n", + " \"cv_score\": score,\n", + " \"status\": status,\n", + " \"feedback\": analysis.content,\n", + " \"phase\": \"prepare_interview\" if status == \"approved\" else \"complete\"\n", + " }\n", + "\n", + "\n", + "# Interview preparation node\n", + "def prepare_interview(state: RecruitmentState) -> Dict[str, Any]:\n", + " \"\"\"Generate interview questions with human approval\"\"\"\n", + " messages = [\n", + " SystemMessage(content=\"\"\"Generate 10 interview questions covering:\n", + " - Technical Skills (4)\n", + " - Experience (3)\n", + " - Problem Solving (3)\"\"\"),\n", + " HumanMessage(content=f\"\"\"\n", + " Position: {state.job_requirements.title}\n", + " Required Skills: {', '.join(state.job_requirements.skills_required)}\n", + " \"\"\")\n", + " ]\n", + "\n", + " response = llm.invoke(messages)\n", + "\n", + " # Raise NodeInterrupt for human review\n", + " raise NodeInterrupt(\n", + " f\"Please review the interview questions:\\n\\n{response.content}\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rjLDE9WhtXcx" + }, + "source": [ + "# Creating the recruitment workflow\n", + "Combines workflow nodes into the overall recruitment process." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "id": "r8tMD9d8ZdAg" + }, + "outputs": [], + "source": [ + "def create_recruitment_workflow():\n", + " \"\"\"Create the recruitment workflow graph\"\"\"\n", + " workflow = StateGraph(RecruitmentState)\n", + " memory = MemorySaver()\n", + "\n", + " # Add nodes\n", + " workflow.add_node(\"requirements_gathering\", requirements_gathering)\n", + " workflow.add_node(\"generate_job_desc\", generate_job_desc)\n", + " workflow.add_node(\"linkedin_process\", linkedin_process)\n", + " workflow.add_node(\"analyze_cv\", analyze_cv)\n", + " workflow.add_node(\"prepare_interview\", prepare_interview)\n", + "\n", + " # Add edges with human-in-the-loop cycles\n", + " workflow.add_edge(START, \"requirements_gathering\")\n", + " workflow.add_edge(\"requirements_gathering\", \"generate_job_desc\")\n", + "\n", + " def route_after_job_desc(state: RecruitmentState):\n", + " return \"linkedin_process\" if state.job_description_approved else \"generate_job_desc\"\n", + "\n", + " def route_after_cv(state: RecruitmentState):\n", + " return \"prepare_interview\" if state.status == \"approved\" else END\n", + "\n", + " workflow.add_conditional_edges(\n", + " \"generate_job_desc\",\n", + " route_after_job_desc,\n", + " [\"linkedin_process\", \"generate_job_desc\"]\n", + " )\n", + "\n", + " workflow.add_edge(\"linkedin_process\", \"analyze_cv\")\n", + " workflow.add_conditional_edges(\n", + " \"analyze_cv\",\n", + " route_after_cv,\n", + " [\"prepare_interview\", END]\n", + " )\n", + " workflow.add_edge(\"prepare_interview\", END)\n", + "\n", + " # Compile with breakpoints\n", + " graph = workflow.compile(\n", + " checkpointer=memory,\n", + " interrupt_before=[\"generate_job_desc\", \"prepare_interview\"]\n", + " )\n", + "\n", + " # Generate and display the Mermaid visualization\n", + " print(\"\"\"graph TD\n", + " Start --> requirements_gathering\n", + " requirements_gathering --> generate_job_desc\n", + " generate_job_desc -->|Approved| linkedin_process\n", + " generate_job_desc -->|Not Approved| generate_job_desc\n", + " linkedin_process --> analyze_cv\n", + " analyze_cv -->|CV-Good| prepare_interview\n", + " analyze_cv -->|CV-Bad| End\n", + " prepare_interview -->|Approved| End\n", + " prepare_interview -->|Not Approved| prepare_interview\n", + " \"\"\")\n", + " display(Image(graph.get_graph().draw_mermaid_png()))\n", + "\n", + " return graph\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "source": [ + "### Get Job Requirements\n", + "Prompts user for job details and returns a JobRequirements object." + ], + "metadata": { + "id": "ocVanzGpamE7" + } + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "id": "3HclsfHXg89P" + }, + "outputs": [], + "source": [ + "def get_job_requirements():\n", + " job_title = input(\"What is the job title? \")\n", + " company_description = input(\"Provide a description of the company: \")\n", + " job_requirements = input(\"List the job requirements (comma-separated): \").split(\",\")\n", + " candidate_responsibilities = input(\"List the candidate responsibilities (comma-separated): \").split(\",\")\n", + " candidate_qualifications = input(\"List the candidate qualifications (comma-separated): \").split(\",\")\n", + " company_benefits = input(\"List the company benefits (comma-separated): \").split(\",\")\n", + " interview_date = input(\"What is the interview date (YYYY-MM-DD)? \")\n", + " preferred_country = input(\"What is the preferred country for the role? \")\n", + " years_experience = int(input(\"What is the required years of experience? \"))\n", + " linkedin_profiles = input(\"Provide any LinkedIn profile URLs (comma-separated, optional): \").split(\",\")\n", + " skills_required = input(\"List the required skills (comma-separated): \").split(\",\")\n", + " salary_range = input(\"What is the salary range for the role? \")\n", + "\n", + " return JobRequirements(\n", + " title=job_title,\n", + " company_description=company_description,\n", + " job_requirements=job_requirements,\n", + " candidate_responsibilities = candidate_responsibilities,\n", + " candidate_qualifications = candidate_qualifications,\n", + " company_benefits = company_benefits,\n", + " interview_date = interview_date,\n", + " preferred_country = preferred_country,\n", + " years_experience = years_experience,\n", + " linkedin_profiles = linkedin_profiles,\n", + " skills_required = skills_required,\n", + " salary_range = salary_range,\n", + " )" + ] + }, + { + "cell_type": "code", + "source": [ + "# Create the recruitment workflow instance\n", + "recruitment_workflow = create_recruitment_workflow()\n", + "\n", + "# Set the test configuration\n", + "config = {\"configurable\": {\"thread_id\": \"test_1\"}}\n", + "\n", + "# Define the initial state of the recruitment process\n", + "initial_state = {\n", + " \"phase\": \"requirements_gathering\",\n", + " \"messages\": [],\n", + " \"job_requirements\": None,\n", + " \"job_description\": None,\n", + " \"job_description_approved\": False,\n", + " \"candidates\": [],\n", + " \"linkedin_process_complete\": False,\n", + " \"cv_analysis_complete\": False,\n", + " \"interview_questions\": None,\n", + " \"interview_questions_approved\": False\n", + "}\n", + "\n", + "# Run the workflow and handle any interrupts\n", + "try:\n", + " for event in recruitment_workflow.stream(initial_state, config, stream_mode=\"values\"):\n", + " print(\"\\nNew State Update:\")\n", + " print(f\"Phase: {event.get('phase')}\")\n", + " if event.get('messages'):\n", + " print(f\"Latest Message: {event['messages'][-1].content}\")\n", + "except NodeInterrupt as e:\n", + " print(f\"\\nHuman Review Required: {str(e)}\")\n", + "\n", + " # Example of handling job description approval\n", + " recruitment_workflow.update_state(\n", + " config,\n", + " {\"job_description_approved\": True}\n", + " )\n", + "\n", + " # Continue the workflow execution\n", + " for event in recruitment_workflow.stream(None, config, stream_mode=\"values\"):\n", + " print(\"\\nNew State Update:\")\n", + " print(f\"Phase: {event.get('phase')}\")\n", + " if event.get('messages'):\n", + " print(f\"Latest Message: {event['messages'][-1].content}\")" + ], + "metadata": { + "id": "d6rGpKV4d8ue", + "outputId": "dc5ba606-30e5-4236-9ba3-47fa854a12f9", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + } + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "graph TD\n", + " Start --> requirements_gathering\n", + " requirements_gathering --> generate_job_desc\n", + " generate_job_desc -->|Approved| linkedin_process\n", + " generate_job_desc -->|Not Approved| generate_job_desc\n", + " linkedin_process --> analyze_cv\n", + " analyze_cv -->|CV-Good| prepare_interview\n", + " analyze_cv -->|CV-Bad| End\n", + " prepare_interview -->|Approved| End\n", + " prepare_interview -->|Not Approved| prepare_interview\n", + " \n" + ] + }, + { + "output_type": "display_data", + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {} + }, + { + "output_type": "stream", + "name": "stdout", + "text": [ + "\n", + "New State Update:\n", + "Phase: requirements_gathering\n" + ] + } + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file