MemGPT/examples/notebooks/Multi-agent recruiting workflow.ipynb
2024-11-06 21:39:43 -08:00

908 lines
41 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"id": "cac06555-9ce8-4f01-bbef-3f8407f4b54d",
"metadata": {},
"source": [
"# Multi-agent recruiting workflow \n",
"Last tested with letta version `0.5.3`"
]
},
{
"cell_type": "markdown",
"id": "aad3a8cc-d17a-4da1-b621-ecc93c9e2106",
"metadata": {},
"source": [
"## Section 0: Setup a MemGPT client "
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "7ccd43f2-164b-4d25-8465-894a3bb54c4b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Initializing database...\n"
]
}
],
"source": [
"from letta import create_client \n",
"\n",
"client = create_client() "
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "e9849ebf-1065-4ce1-9676-16fdd82bdd17",
"metadata": {},
"outputs": [],
"source": [
"from letta import LLMConfig, EmbeddingConfig\n",
"\n",
"client.set_default_llm_config(LLMConfig.default_config(\"gpt-4o-mini\")) \n",
"client.set_default_embedding_config(EmbeddingConfig.default_config(\"text-embedding-ada-002\")) "
]
},
{
"cell_type": "markdown",
"id": "99a61da5-f069-4538-a548-c7d0f7a70227",
"metadata": {},
"source": [
"## Section 1: Shared Memory Block \n",
"Each agent will have both its own memory, and shared memory. The shared memory will contain information about the organization that the agents are all a part of. If one agent updates this memory, the changes will be propaged to the memory of all the other agents. "
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "7770600d-5e83-4498-acf1-05f5bea216c3",
"metadata": {},
"outputs": [],
"source": [
"from letta.schemas.block import Block \n",
"\n",
"org_description = \"The company is called AgentOS \" \\\n",
"+ \"and is building AI tools to make it easier to create \" \\\n",
"+ \"and deploy LLM agents.\"\n",
"\n",
"org_block = Block(label=\"company\", value=org_description )"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "6c3d3a55-870a-4ff0-81c0-4072f783a940",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Block(value='The company is called AgentOS and is building AI tools to make it easier to create and deploy LLM agents.', limit=2000, template_name=None, template=False, label='company', description=None, metadata_={}, user_id=None, id='block-6db0fe1a-1f5e-44ab-852c-a2df8d7ab80e')"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"org_block"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "3e3ce7a4-cf4d-4d74-8d09-b4a35b8bb439",
"metadata": {},
"outputs": [],
"source": [
"from letta.schemas.memory import BasicBlockMemory\n",
"\n",
"class OrgMemory(BasicBlockMemory): \n",
"\n",
" def __init__(self, persona: str, org_block: Block): \n",
" persona_block = Block(label=\"persona\", value=persona)\n",
" super().__init__(blocks=[persona_block, org_block])\n",
" "
]
},
{
"cell_type": "markdown",
"id": "8448df7b-c321-4d90-ba52-003930a513cb",
"metadata": {},
"source": [
"## Section 2: Orchestrating Multiple Agents \n",
"We'll implement a recruiting workflow that involves evaluating an candidate, then if the candidate is a good fit, writing a personalized email on the human's behalf. Since this task involves multiple stages, sometimes breaking the task down to multiple agents can improve performance (though this is not always the case). We will break down the task into: \n",
"\n",
"1. `eval_agent`: This agent is responsible for evaluating candidates based on their resume\n",
"2. `outreach_agent`: This agent is responsible for writing emails to strong candidates\n",
"3. `recruiter_agent`: This agent is responsible for generating leads from a database \n",
"\n",
"Much like humans, these agents will communicate by sending each other messages. We can do this by giving agents that need to communicate with other agents access to a tool that allows them to message other agents. "
]
},
{
"cell_type": "markdown",
"id": "a065082a-d865-483c-b721-43c5a4d51afe",
"metadata": {},
"source": [
"#### Evaluator Agent\n",
"This agent will have tools to: \n",
"* Read a resume \n",
"* Submit a candidate for outreach (which sends the candidate information to the `outreach_agent`)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "c00232c5-4c37-436c-8ea4-602a31bd84fa",
"metadata": {},
"outputs": [],
"source": [
"def read_resume(self, name: str): \n",
" \"\"\"\n",
" Read the resume data for a candidate given the name\n",
"\n",
" Args: \n",
" name (str): Candidate name \n",
"\n",
" Returns: \n",
" resume_data (str): Candidate's resume data \n",
" \"\"\"\n",
" import os\n",
" filepath = os.path.join(\"data\", \"resumes\", name.lower().replace(\" \", \"_\") + \".txt\")\n",
" #print(\"read\", filepath)\n",
" return open(filepath).read()\n",
"\n",
"def submit_evaluation(self, candidate_name: str, reach_out: bool, resume: str, justification: str): \n",
" \"\"\"\n",
" Submit a candidate for outreach. \n",
"\n",
" Args: \n",
" candidate_name (str): The name of the candidate\n",
" reach_out (bool): Whether to reach out to the candidate\n",
" resume (str): The text representation of the candidate's resume \n",
" justification (str): Justification for reaching out or not\n",
" \"\"\"\n",
" from letta import create_client \n",
" client = create_client()\n",
" message = \"Reach out to the following candidate. \" \\\n",
" + f\"Name: {candidate_name}\\n\" \\\n",
" + f\"Resume Data: {resume}\\n\" \\\n",
" + f\"Justification: {justification}\"\n",
" # NOTE: we will define this agent later \n",
" if reach_out:\n",
" response = client.send_message(\n",
" agent_name=\"outreach_agent\", \n",
" role=\"user\", \n",
" message=message\n",
" ) \n",
" else: \n",
" print(f\"Candidate {candidate_name} is rejected: {justification}\")\n",
"\n",
"# TODO: add an archival andidate tool (provide justification) \n",
"\n",
"read_resume_tool = client.create_tool(read_resume) \n",
"submit_evaluation_tool = client.create_tool(submit_evaluation)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "12482994-03f4-4dda-8ea2-6492ec28f392",
"metadata": {},
"outputs": [],
"source": [
"skills = \"Front-end (React, Typescript), software engineering \" \\\n",
"+ \"(ideally Python), and experience with LLMs.\"\n",
"eval_persona = f\"You are responsible to finding good recruiting \" \\\n",
"+ \"candidates, for the company description. \" \\\n",
"+ f\"Ideal canddiates have skills: {skills}. \" \\\n",
"+ \"Submit your candidate evaluation with the submit_evaluation tool. \"\n",
"\n",
"eval_agent = client.create_agent(\n",
" name=\"eval_agent\", \n",
" memory=OrgMemory(\n",
" persona=eval_persona, \n",
" org_block=org_block,\n",
" ), \n",
" tools=[read_resume_tool.name, submit_evaluation_tool.name]\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "37c2d0be-b980-426f-ab24-1feaa8ed90ef",
"metadata": {},
"source": [
"#### Outreach agent \n",
"This agent will email candidates with customized emails. Since sending emails is a bit complicated, we'll just pretend we sent an email by printing it in the tool call. "
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "24e8942f-5b0e-4490-ac5f-f9e1f3178627",
"metadata": {},
"outputs": [],
"source": [
"def email_candidate(self, content: str): \n",
" \"\"\"\n",
" Send an email\n",
"\n",
" Args: \n",
" content (str): Content of the email \n",
" \"\"\"\n",
" print(\"Pretend to email:\", content)\n",
" return\n",
"\n",
"email_candidate_tool = client.create_tool(email_candidate)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "87416e00-c7a0-4420-be71-e2f5a6404428",
"metadata": {},
"outputs": [],
"source": [
"outreach_persona = \"You are responsible for sending outbound emails \" \\\n",
"+ \"on behalf of a company with the send_emails tool to \" \\\n",
"+ \"potential candidates. \" \\\n",
"+ \"If possible, make sure to personalize the email by appealing \" \\\n",
"+ \"to the recipient with details about the company. \" \\\n",
"+ \"You position is `Head Recruiter`, and you go by the name Bob, with contact info bob@gmail.com. \" \\\n",
"+ \"\"\"\n",
"Follow this email template: \n",
"\n",
"Hi <candidate name>, \n",
"\n",
"<content> \n",
"\n",
"Best, \n",
"<your name> \n",
"<company name> \n",
"\"\"\"\n",
"\n",
"outreach_agent = client.create_agent(\n",
" name=\"outreach_agent\", \n",
" memory=OrgMemory(\n",
" persona=outreach_persona, \n",
" org_block=org_block\n",
" ), \n",
" tools=[email_candidate_tool.name]\n",
")"
]
},
{
"cell_type": "markdown",
"id": "f69d38da-807e-4bb1-8adb-f715b24f1c34",
"metadata": {},
"source": [
"Next, we'll send a message from the user telling the `leadgen_agent` to evaluate a given candidate: "
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "f09ab5bd-e158-42ee-9cce-43f254c4d2b0",
"metadata": {},
"outputs": [],
"source": [
"response = client.send_message(\n",
" agent_name=\"eval_agent\", \n",
" role=\"user\", \n",
" message=\"Candidate: Tony Stark\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "cd8f1a1e-21eb-47ae-9eed-b1d3668752ff",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
" <style>\n",
" .message-container, .usage-container {\n",
" font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n",
" max-width: 800px;\n",
" margin: 20px auto;\n",
" background-color: #1e1e1e;\n",
" border-radius: 8px;\n",
" overflow: hidden;\n",
" color: #d4d4d4;\n",
" }\n",
" .message, .usage-stats {\n",
" padding: 10px 15px;\n",
" border-bottom: 1px solid #3a3a3a;\n",
" }\n",
" .message:last-child, .usage-stats:last-child {\n",
" border-bottom: none;\n",
" }\n",
" .title {\n",
" font-weight: bold;\n",
" margin-bottom: 5px;\n",
" color: #ffffff;\n",
" text-transform: uppercase;\n",
" font-size: 0.9em;\n",
" }\n",
" .content {\n",
" background-color: #2d2d2d;\n",
" border-radius: 4px;\n",
" padding: 5px 10px;\n",
" font-family: 'Consolas', 'Courier New', monospace;\n",
" white-space: pre-wrap;\n",
" }\n",
" .json-key, .function-name, .json-boolean { color: #9cdcfe; }\n",
" .json-string { color: #ce9178; }\n",
" .json-number { color: #b5cea8; }\n",
" .internal-monologue { font-style: italic; }\n",
" </style>\n",
" <div class=\"message-container\">\n",
" \n",
" <div class=\"message\">\n",
" <div class=\"title\">INTERNAL MONOLOGUE</div>\n",
" <div class=\"content\"><span class=\"internal-monologue\">Checking candidate details for Tony Stark. Need to assess suitability for our roles.</span></div>\n",
" </div>\n",
" \n",
" <div class=\"message\">\n",
" <div class=\"title\">FUNCTION CALL</div>\n",
" <div class=\"content\"><span class=\"function-name\">read_resume</span>({<br>&nbsp;&nbsp;<span class=\"json-key\">\"name\"</span>: <span class=\"json-key\">\"Tony Stark\",<br>&nbsp;&nbsp;\"request_heartbeat\"</span>: <span class=\"json-boolean\">true</span><br>})</div>\n",
" </div>\n",
" \n",
" <div class=\"message\">\n",
" <div class=\"title\">FUNCTION RETURN</div>\n",
" <div class=\"content\">{<br>&nbsp;&nbsp;<span class=\"json-key\">\"status\"</span>: <span class=\"json-key\">\"Failed\",<br>&nbsp;&nbsp;\"message\"</span>: <span class=\"json-key\">\"Error calling function read_resume: [Errno 2] No such file or directory: 'data/resumes/tony_stark.txt'\",<br>&nbsp;&nbsp;\"time\"</span>: <span class=\"json-string\">\"2024-11-06 08:09:57 PM PST-0800\"</span><br>}</div>\n",
" </div>\n",
" \n",
" <div class=\"message\">\n",
" <div class=\"title\">INTERNAL MONOLOGUE</div>\n",
" <div class=\"content\"><span class=\"internal-monologue\">I couldn&#x27;t retrieve the resume for Tony Stark. I&#x27;ll need to communicate that back to the user and suggest another action.</span></div>\n",
" </div>\n",
" \n",
" <div class=\"message\">\n",
" <div class=\"title\">FUNCTION CALL</div>\n",
" <div class=\"content\"><span class=\"function-name\">send_message</span>({<br>&nbsp;&nbsp;<span class=\"json-key\">\"message\"</span>: <span class=\"json-string\">\"It looks like I'm having trouble accessing Tony Stark's resume right now. Could you provide any details you have on his skills or experience?\"</span><br>})</div>\n",
" </div>\n",
" \n",
" <div class=\"message\">\n",
" <div class=\"title\">FUNCTION RETURN</div>\n",
" <div class=\"content\">{<br>&nbsp;&nbsp;<span class=\"json-key\">\"status\"</span>: <span class=\"json-key\">\"OK\",<br>&nbsp;&nbsp;\"message\"</span>: <span class=\"json-key\">\"None\",<br>&nbsp;&nbsp;\"time\"</span>: <span class=\"json-string\">\"2024-11-06 08:10:00 PM PST-0800\"</span><br>}</div>\n",
" </div>\n",
" </div>\n",
" <div class=\"usage-container\">\n",
" <div class=\"usage-stats\">\n",
" <div class=\"title\">USAGE STATISTICS</div>\n",
" <div class=\"content\">{<br>&nbsp;&nbsp;<span class=\"json-key\">\"completion_tokens\"</span>: <span class=\"json-number\">109</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"prompt_tokens\"</span>: <span class=\"json-number\">4997</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"total_tokens\"</span>: <span class=\"json-number\">5106</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"step_count\"</span>: <span class=\"json-number\">2</span><br>}</div>\n",
" </div>\n",
" </div>\n",
" "
],
"text/plain": [
"LettaResponse(messages=[InternalMonologue(id='message-eda4b3ed-c49b-46e0-a328-389e1a4f99f2', date=datetime.datetime(2024, 11, 7, 4, 9, 57, 382192, tzinfo=datetime.timezone.utc), message_type='internal_monologue', internal_monologue='Checking candidate details for Tony Stark. Need to assess suitability for our roles.'), FunctionCallMessage(id='message-eda4b3ed-c49b-46e0-a328-389e1a4f99f2', date=datetime.datetime(2024, 11, 7, 4, 9, 57, 382192, tzinfo=datetime.timezone.utc), message_type='function_call', function_call=FunctionCall(name='read_resume', arguments='{\\n \"name\": \"Tony Stark\",\\n \"request_heartbeat\": true\\n}', function_call_id='call_BkJmry1mIebLCRrMS0c5OyKh')), FunctionReturn(id='message-f25ef27a-8e64-489d-b106-c6cc8d7bfc91', date=datetime.datetime(2024, 11, 7, 4, 9, 57, 384351, tzinfo=datetime.timezone.utc), message_type='function_return', function_return='{\\n \"status\": \"Failed\",\\n \"message\": \"Error calling function read_resume: [Errno 2] No such file or directory: \\'data/resumes/tony_stark.txt\\'\",\\n \"time\": \"2024-11-06 08:09:57 PM PST-0800\"\\n}', status='error', function_call_id='call_BkJmry1mIebLCRrMS0c5OyKh'), InternalMonologue(id='message-8bd8537d-b07a-433e-8db2-cac21643b68b', date=datetime.datetime(2024, 11, 7, 4, 10, 0, 919112, tzinfo=datetime.timezone.utc), message_type='internal_monologue', internal_monologue=\"I couldn't retrieve the resume for Tony Stark. I'll need to communicate that back to the user and suggest another action.\"), FunctionCallMessage(id='message-8bd8537d-b07a-433e-8db2-cac21643b68b', date=datetime.datetime(2024, 11, 7, 4, 10, 0, 919112, tzinfo=datetime.timezone.utc), message_type='function_call', function_call=FunctionCall(name='send_message', arguments='{\\n \"message\": \"It looks like I\\'m having trouble accessing Tony Stark\\'s resume right now. Could you provide any details you have on his skills or experience?\"\\n}', function_call_id='call_AiGCTzL94JmsURKnLKLANRXL')), FunctionReturn(id='message-c3a0f8fd-f894-46df-a091-1fcdf2cb7d4b', date=datetime.datetime(2024, 11, 7, 4, 10, 0, 919561, tzinfo=datetime.timezone.utc), message_type='function_return', function_return='{\\n \"status\": \"OK\",\\n \"message\": \"None\",\\n \"time\": \"2024-11-06 08:10:00 PM PST-0800\"\\n}', status='success', function_call_id='call_AiGCTzL94JmsURKnLKLANRXL')], usage=LettaUsageStatistics(completion_tokens=109, prompt_tokens=4997, total_tokens=5106, step_count=2))"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"response"
]
},
{
"cell_type": "markdown",
"id": "67069247-e603-439c-b2df-9176c4eba957",
"metadata": {},
"source": [
"#### Providing feedback to agents \n",
"Since MemGPT agents are persisted, we can provide feedback to agents that is used in future agent executions if we want to modify the future behavior. "
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "19c57d54-a1fe-4244-b765-b996ba9a4788",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/sarahwooders/repos/letta/letta/helpers/tool_rule_solver.py:70: UserWarning: User provided tool rules and execution state resolved to no more possible tool calls.\n",
" warnings.warn(message)\n"
]
}
],
"source": [
"feedback = \"Our company pivoted to foundation model training\"\n",
"response = client.send_message(\n",
" agent_name=\"eval_agent\", \n",
" role=\"user\", \n",
" message=feedback\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "036b973f-209a-4ad9-90e7-fc827b5d92c7",
"metadata": {},
"outputs": [],
"source": [
"\n",
"feedback = \"The company is also renamed to FoundationAI\"\n",
"response = client.send_message(\n",
" agent_name=\"eval_agent\", \n",
" role=\"user\", \n",
" message=feedback\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "5d7a7633-35a3-4e41-b44a-be71067dd32a",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
" <style>\n",
" .message-container, .usage-container {\n",
" font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n",
" max-width: 800px;\n",
" margin: 20px auto;\n",
" background-color: #1e1e1e;\n",
" border-radius: 8px;\n",
" overflow: hidden;\n",
" color: #d4d4d4;\n",
" }\n",
" .message, .usage-stats {\n",
" padding: 10px 15px;\n",
" border-bottom: 1px solid #3a3a3a;\n",
" }\n",
" .message:last-child, .usage-stats:last-child {\n",
" border-bottom: none;\n",
" }\n",
" .title {\n",
" font-weight: bold;\n",
" margin-bottom: 5px;\n",
" color: #ffffff;\n",
" text-transform: uppercase;\n",
" font-size: 0.9em;\n",
" }\n",
" .content {\n",
" background-color: #2d2d2d;\n",
" border-radius: 4px;\n",
" padding: 5px 10px;\n",
" font-family: 'Consolas', 'Courier New', monospace;\n",
" white-space: pre-wrap;\n",
" }\n",
" .json-key, .function-name, .json-boolean { color: #9cdcfe; }\n",
" .json-string { color: #ce9178; }\n",
" .json-number { color: #b5cea8; }\n",
" .internal-monologue { font-style: italic; }\n",
" </style>\n",
" <div class=\"message-container\">\n",
" \n",
" <div class=\"message\">\n",
" <div class=\"title\">INTERNAL MONOLOGUE</div>\n",
" <div class=\"content\"><span class=\"internal-monologue\">Updating core memory to reflect the new company name as FoundationAI. This is crucial information for ongoing evaluations and discussions.</span></div>\n",
" </div>\n",
" \n",
" <div class=\"message\">\n",
" <div class=\"title\">FUNCTION CALL</div>\n",
" <div class=\"content\"><span class=\"function-name\">core_memory_replace</span>({<br>&nbsp;&nbsp;<span class=\"json-key\">\"label\"</span>: <span class=\"json-key\">\"company\",<br>&nbsp;&nbsp;\"old_content\"</span>: <span class=\"json-key\">\"The company has pivoted to foundation model training.\",<br>&nbsp;&nbsp;\"new_content\"</span>: <span class=\"json-key\">\"The company is now named FoundationAI and has pivoted to foundation model training.\",<br>&nbsp;&nbsp;\"request_heartbeat\"</span>: <span class=\"json-boolean\">true</span><br>})</div>\n",
" </div>\n",
" \n",
" <div class=\"message\">\n",
" <div class=\"title\">FUNCTION RETURN</div>\n",
" <div class=\"content\">{<br>&nbsp;&nbsp;<span class=\"json-key\">\"status\"</span>: <span class=\"json-key\">\"OK\",<br>&nbsp;&nbsp;\"message\"</span>: <span class=\"json-key\">\"None\",<br>&nbsp;&nbsp;\"time\"</span>: <span class=\"json-string\">\"2024-11-06 08:10:17 PM PST-0800\"</span><br>}</div>\n",
" </div>\n",
" \n",
" <div class=\"message\">\n",
" <div class=\"title\">INTERNAL MONOLOGUE</div>\n",
" <div class=\"content\"><span class=\"internal-monologue\">I need to confirm the updated company name and prompt for more information about Tony.</span></div>\n",
" </div>\n",
" \n",
" <div class=\"message\">\n",
" <div class=\"title\">FUNCTION CALL</div>\n",
" <div class=\"content\"><span class=\"function-name\">send_message</span>({<br>&nbsp;&nbsp;<span class=\"json-key\">\"message\"</span>: <span class=\"json-string\">\"Got it! I've updated the name to FoundationAI. Do you have any details on Tony Stark's skills that match our new focus?\"</span><br>})</div>\n",
" </div>\n",
" \n",
" <div class=\"message\">\n",
" <div class=\"title\">FUNCTION RETURN</div>\n",
" <div class=\"content\">{<br>&nbsp;&nbsp;<span class=\"json-key\">\"status\"</span>: <span class=\"json-key\">\"OK\",<br>&nbsp;&nbsp;\"message\"</span>: <span class=\"json-key\">\"None\",<br>&nbsp;&nbsp;\"time\"</span>: <span class=\"json-string\">\"2024-11-06 08:10:20 PM PST-0800\"</span><br>}</div>\n",
" </div>\n",
" </div>\n",
" <div class=\"usage-container\">\n",
" <div class=\"usage-stats\">\n",
" <div class=\"title\">USAGE STATISTICS</div>\n",
" <div class=\"content\">{<br>&nbsp;&nbsp;<span class=\"json-key\">\"completion_tokens\"</span>: <span class=\"json-number\">142</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"prompt_tokens\"</span>: <span class=\"json-number\">6387</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"total_tokens\"</span>: <span class=\"json-number\">6529</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"step_count\"</span>: <span class=\"json-number\">2</span><br>}</div>\n",
" </div>\n",
" </div>\n",
" "
],
"text/plain": [
"LettaResponse(messages=[InternalMonologue(id='message-de6a4e9f-225a-4239-beba-f53bab429083', date=datetime.datetime(2024, 11, 7, 4, 10, 17, 338163, tzinfo=datetime.timezone.utc), message_type='internal_monologue', internal_monologue='Updating core memory to reflect the new company name as FoundationAI. This is crucial information for ongoing evaluations and discussions.'), FunctionCallMessage(id='message-de6a4e9f-225a-4239-beba-f53bab429083', date=datetime.datetime(2024, 11, 7, 4, 10, 17, 338163, tzinfo=datetime.timezone.utc), message_type='function_call', function_call=FunctionCall(name='core_memory_replace', arguments='{\\n \"label\": \"company\",\\n \"old_content\": \"The company has pivoted to foundation model training.\",\\n \"new_content\": \"The company is now named FoundationAI and has pivoted to foundation model training.\",\\n \"request_heartbeat\": true\\n}', function_call_id='call_QBMhZrxD0oBavqAMOn5nAV2g')), FunctionReturn(id='message-a24ee34b-24d3-4af0-8568-bec3d9bc9ac1', date=datetime.datetime(2024, 11, 7, 4, 10, 17, 338911, tzinfo=datetime.timezone.utc), message_type='function_return', function_return='{\\n \"status\": \"OK\",\\n \"message\": \"None\",\\n \"time\": \"2024-11-06 08:10:17 PM PST-0800\"\\n}', status='success', function_call_id='call_QBMhZrxD0oBavqAMOn5nAV2g'), InternalMonologue(id='message-55c94d9f-de07-4721-b2bb-e447314e7865', date=datetime.datetime(2024, 11, 7, 4, 10, 20, 546442, tzinfo=datetime.timezone.utc), message_type='internal_monologue', internal_monologue='I need to confirm the updated company name and prompt for more information about Tony.'), FunctionCallMessage(id='message-55c94d9f-de07-4721-b2bb-e447314e7865', date=datetime.datetime(2024, 11, 7, 4, 10, 20, 546442, tzinfo=datetime.timezone.utc), message_type='function_call', function_call=FunctionCall(name='send_message', arguments='{\\n \"message\": \"Got it! I\\'ve updated the name to FoundationAI. Do you have any details on Tony Stark\\'s skills that match our new focus?\"\\n}', function_call_id='call_KUJ9Id8yXdj4gt48C1mKlXUg')), FunctionReturn(id='message-c1ebe9cc-529e-407f-a01d-c43ffddee52b', date=datetime.datetime(2024, 11, 7, 4, 10, 20, 547869, tzinfo=datetime.timezone.utc), message_type='function_return', function_return='{\\n \"status\": \"OK\",\\n \"message\": \"None\",\\n \"time\": \"2024-11-06 08:10:20 PM PST-0800\"\\n}', status='success', function_call_id='call_KUJ9Id8yXdj4gt48C1mKlXUg')], usage=LettaUsageStatistics(completion_tokens=142, prompt_tokens=6387, total_tokens=6529, step_count=2))"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"response"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "d04d4b3a-6df1-41a9-9a8e-037fbb45836d",
"metadata": {},
"outputs": [],
"source": [
"response = client.send_message(\n",
" agent_name=\"eval_agent\", \n",
" role=\"system\", \n",
" message=\"Candidate: Spongebob Squarepants\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "c60465f4-7977-4f70-9a75-d2ddebabb0fa",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Block(value='The company is called AgentOS and is building AI tools to make it easier to create and deploy LLM agents.\\nThe company is now named FoundationAI and has pivoted to foundation model training.', limit=2000, template_name=None, template=False, label='company', description=None, metadata_={}, user_id=None, id='block-6db0fe1a-1f5e-44ab-852c-a2df8d7ab80e')"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"client.get_core_memory(eval_agent.id).get_block(\"company\")"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "a51c6bb3-225d-47a4-88f1-9a26ff838dd3",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Block(value='The company is called AgentOS and is building AI tools to make it easier to create and deploy LLM agents.', limit=2000, template_name=None, template=False, label='company', description=None, metadata_={}, user_id=None, id='block-6db0fe1a-1f5e-44ab-852c-a2df8d7ab80e')"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"client.get_core_memory(outreach_agent.id).get_block(\"company\")"
]
},
{
"cell_type": "markdown",
"id": "8d181b1e-72da-4ebe-a872-293e3ce3a225",
"metadata": {},
"source": [
"## Section 3: Adding an orchestrator agent \n",
"So far, we've been triggering the `eval_agent` manually. We can also create an additional agent that is responsible for orchestrating tasks. "
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "80b23d46-ed4b-4457-810a-a819d724e146",
"metadata": {},
"outputs": [],
"source": [
"#re-create agents \n",
"client.delete_agent(eval_agent.id)\n",
"client.delete_agent(outreach_agent.id)\n",
"\n",
"eval_agent = client.create_agent(\n",
" name=\"eval_agent\", \n",
" memory=OrgMemory(\n",
" persona=eval_persona, \n",
" org_block=org_block,\n",
" ), \n",
" tools=[read_resume_tool.name, submit_evaluation_tool.name]\n",
")\n",
"\n",
"outreach_agent = client.create_agent(\n",
" name=\"outreach_agent\", \n",
" memory=OrgMemory(\n",
" persona=outreach_persona, \n",
" org_block=org_block\n",
" ), \n",
" tools=[email_candidate_tool.name]\n",
")"
]
},
{
"cell_type": "markdown",
"id": "a751d0f1-b52d-493c-bca1-67f88011bded",
"metadata": {},
"source": [
"The `recruiter_agent` will be linked to the same `org_block` that we created before - we can look up the current data in `org_block` by looking up its ID: "
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "bf6bd419-1504-4513-bc68-d4c717ea8e2d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Block(value='The company is called AgentOS and is building AI tools to make it easier to create and deploy LLM agents.\\nThe company is now named FoundationAI and has pivoted to foundation model training.', limit=2000, template_name=None, template=False, label='company', description=None, metadata_={}, user_id='user-00000000-0000-4000-8000-000000000000', id='block-6db0fe1a-1f5e-44ab-852c-a2df8d7ab80e')"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"client.get_block(org_block.id)"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "e2730626-1685-46aa-9b44-a59e1099e973",
"metadata": {},
"outputs": [],
"source": [
"from typing import Optional\n",
"\n",
"def search_candidates_db(self, page: int) -> Optional[str]: \n",
" \"\"\"\n",
" Returns 1 candidates per page. \n",
" Page 0 returns the first 1 candidate, \n",
" Page 1 returns the next 1, etc.\n",
" Returns `None` if no candidates remain. \n",
"\n",
" Args: \n",
" page (int): The page number to return candidates from \n",
"\n",
" Returns: \n",
" candidate_names (List[str]): Names of the candidates\n",
" \"\"\"\n",
" \n",
" names = [\"Tony Stark\", \"Spongebob Squarepants\", \"Gautam Fang\"]\n",
" if page >= len(names): \n",
" return None\n",
" return names[page]\n",
"\n",
"def consider_candidate(self, name: str): \n",
" \"\"\"\n",
" Submit a candidate for consideration. \n",
"\n",
" Args: \n",
" name (str): Candidate name to consider \n",
" \"\"\"\n",
" from letta import create_client \n",
" client = create_client()\n",
" message = f\"Consider candidate {name}\" \n",
" print(\"Sending message to eval agent: \", message)\n",
" response = client.send_message(\n",
" agent_name=\"eval_agent\", \n",
" role=\"user\", \n",
" message=message\n",
" ) \n",
"\n",
"\n",
"# create tools \n",
"search_candidate_tool = client.create_tool(search_candidates_db)\n",
"consider_candidate_tool = client.create_tool(consider_candidate)\n",
"\n",
"# create recruiter agent\n",
"recruiter_agent = client.create_agent(\n",
" name=\"recruiter_agent\", \n",
" memory=OrgMemory(\n",
" persona=\"You run a recruiting process for a company. \" \\\n",
" + \"Your job is to continue to pull candidates from the \" \n",
" + \"`search_candidates_db` tool until there are no more \" \\\n",
" + \"candidates left. \" \\\n",
" + \"For each candidate, consider the candidate by calling \"\n",
" + \"the `consider_candidate` tool. \" \\\n",
" + \"You should continue to call `search_candidates_db` \" \\\n",
" + \"followed by `consider_candidate` until there are no more \" \\\n",
" \" candidates. \",\n",
" org_block=org_block\n",
" ), \n",
" tools=[search_candidate_tool.name, consider_candidate_tool.name]\n",
")\n",
" \n"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "ecfd790c-0018-4fd9-bdaf-5a6b81f70adf",
"metadata": {},
"outputs": [],
"source": [
"response = client.send_message(\n",
" agent_name=\"recruiter_agent\", \n",
" role=\"system\", \n",
" message=\"Run generation\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "8065c179-cf90-4287-a6e5-8c009807b436",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
" <style>\n",
" .message-container, .usage-container {\n",
" font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n",
" max-width: 800px;\n",
" margin: 20px auto;\n",
" background-color: #1e1e1e;\n",
" border-radius: 8px;\n",
" overflow: hidden;\n",
" color: #d4d4d4;\n",
" }\n",
" .message, .usage-stats {\n",
" padding: 10px 15px;\n",
" border-bottom: 1px solid #3a3a3a;\n",
" }\n",
" .message:last-child, .usage-stats:last-child {\n",
" border-bottom: none;\n",
" }\n",
" .title {\n",
" font-weight: bold;\n",
" margin-bottom: 5px;\n",
" color: #ffffff;\n",
" text-transform: uppercase;\n",
" font-size: 0.9em;\n",
" }\n",
" .content {\n",
" background-color: #2d2d2d;\n",
" border-radius: 4px;\n",
" padding: 5px 10px;\n",
" font-family: 'Consolas', 'Courier New', monospace;\n",
" white-space: pre-wrap;\n",
" }\n",
" .json-key, .function-name, .json-boolean { color: #9cdcfe; }\n",
" .json-string { color: #ce9178; }\n",
" .json-number { color: #b5cea8; }\n",
" .internal-monologue { font-style: italic; }\n",
" </style>\n",
" <div class=\"message-container\">\n",
" \n",
" <div class=\"message\">\n",
" <div class=\"title\">INTERNAL MONOLOGUE</div>\n",
" <div class=\"content\"><span class=\"internal-monologue\">User has logged in for the first time. Exciting!</span></div>\n",
" </div>\n",
" \n",
" <div class=\"message\">\n",
" <div class=\"title\">FUNCTION CALL</div>\n",
" <div class=\"content\"><span class=\"function-name\">send_message</span>({<br>&nbsp;&nbsp;<span class=\"json-key\">\"message\"</span>: <span class=\"json-string\">\"Welcome! It's great to have you here. Let's dive into your journey together, shall we?\"</span><br>})</div>\n",
" </div>\n",
" \n",
" <div class=\"message\">\n",
" <div class=\"title\">FUNCTION RETURN</div>\n",
" <div class=\"content\">{<br>&nbsp;&nbsp;<span class=\"json-key\">\"status\"</span>: <span class=\"json-key\">\"OK\",<br>&nbsp;&nbsp;\"message\"</span>: <span class=\"json-key\">\"None\",<br>&nbsp;&nbsp;\"time\"</span>: <span class=\"json-string\">\"2024-11-06 08:11:04 PM PST-0800\"</span><br>}</div>\n",
" </div>\n",
" </div>\n",
" <div class=\"usage-container\">\n",
" <div class=\"usage-stats\">\n",
" <div class=\"title\">USAGE STATISTICS</div>\n",
" <div class=\"content\">{<br>&nbsp;&nbsp;<span class=\"json-key\">\"completion_tokens\"</span>: <span class=\"json-number\">50</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"prompt_tokens\"</span>: <span class=\"json-number\">2399</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"total_tokens\"</span>: <span class=\"json-number\">2449</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"step_count\"</span>: <span class=\"json-number\">1</span><br>}</div>\n",
" </div>\n",
" </div>\n",
" "
],
"text/plain": [
"LettaResponse(messages=[InternalMonologue(id='message-5fee1cc7-b1f8-442d-a0cb-f291d361c4bd', date=datetime.datetime(2024, 11, 7, 4, 11, 4, 98419, tzinfo=datetime.timezone.utc), message_type='internal_monologue', internal_monologue='User has logged in for the first time. Exciting!'), FunctionCallMessage(id='message-5fee1cc7-b1f8-442d-a0cb-f291d361c4bd', date=datetime.datetime(2024, 11, 7, 4, 11, 4, 98419, tzinfo=datetime.timezone.utc), message_type='function_call', function_call=FunctionCall(name='send_message', arguments='{\\n \"message\": \"Welcome! It\\'s great to have you here. Let\\'s dive into your journey together, shall we?\"\\n}', function_call_id='call_LtMblVxNr2FVPiCJicAtaji9')), FunctionReturn(id='message-567a8bfb-468b-4292-82b8-c9ecf20ec7e9', date=datetime.datetime(2024, 11, 7, 4, 11, 4, 98638, tzinfo=datetime.timezone.utc), message_type='function_return', function_return='{\\n \"status\": \"OK\",\\n \"message\": \"None\",\\n \"time\": \"2024-11-06 08:11:04 PM PST-0800\"\\n}', status='success', function_call_id='call_LtMblVxNr2FVPiCJicAtaji9')], usage=LettaUsageStatistics(completion_tokens=50, prompt_tokens=2399, total_tokens=2449, step_count=1))"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"response"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "4639bbca-e0c5-46a9-a509-56d35d26e97f",
"metadata": {},
"outputs": [],
"source": [
"client.delete_agent(eval_agent.id)\n",
"client.delete_agent(outreach_agent.id)"
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "672f941e-af17-4b5c-8a21-925a1d88c47f",
"metadata": {},
"outputs": [],
"source": [
"client.delete_agent(recruiter_agent.id)"
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "b2dfe638-4785-431d-8a0c-91f8dab66747",
"metadata": {},
"outputs": [],
"source": [
"for agent in client.list_agents(): \n",
" client.delete_agent(agent.id)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9cfb4b3e-4b39-4936-862d-8bd7ac357f4a",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "letta",
"language": "python",
"name": "letta"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}