lesson_2_baseline.ipynb•54 kB
{
"cells": [
{
"cell_type": "markdown",
"id": "9cc2b79f-fd52-4ba4-8c9a-bca09a9300b3",
"metadata": {},
"source": [
"# Lesson 2: Baseline Email Assistant\n",
"\n",
"This lesson builds an email assistant that:\n",
"- Classifies incoming messages (respond, ignore, notify)\n",
"- Drafts responses\n",
"- Schedules meetings\n",
"\n",
"We'll start with a simple implementation - one that uses hard-coded rules to handle emails.\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"id": "4cdab100-0257-45cd-9e7d-b5678c34c9f9",
"metadata": {},
"source": [
"\n",
"🚨\n",
" <b>Different Run Results:</b> The output generated by AI chat models can vary with each execution due to their dynamic, probabilistic nature. Don't be surprised if your results differ from those shown in the video.</p>"
]
},
{
"cell_type": "markdown",
"id": "789c5e0e-eb8a-4b1d-860d-c58b742292ce",
"metadata": {},
"source": [
"## Load API tokens for our 3rd party APIs"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "d88cbc41-1acd-436e-9184-1ce5bc95bb9b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"True\n"
]
}
],
"source": [
"import os\n",
"from dotenv import load_dotenv\n",
"print(load_dotenv())"
]
},
{
"cell_type": "markdown",
"id": "5138f1ec-689d-46a5-a99e-a5bd5a659a32",
"metadata": {},
"source": [
"## 1. Setup a Profile, Prompt and Example Email"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "6f03a2a0-41ff-4982-bf99-e15ce9361205",
"metadata": {},
"outputs": [],
"source": [
"profile = {\n",
" \"name\": \"John\",\n",
" \"full_name\": \"John Doe\",\n",
" \"user_profile_background\": \"Senior software engineer leading a team of 5 developers\",\n",
"}"
]
},
{
"cell_type": "markdown",
"id": "5bc80b5b",
"metadata": {},
"source": [
"- The system has a Triage prompt which is responsible for triaging the user's email into 3 different categories.\n",
"- We also have the instructions for the main agent. (This will be called after the system decides to response to an email)\n",
"\n",
"* Note: These are separate from the rest of the prompt since they will be updated as part of the memory."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "c7116293-3dc5-403c-979d-7e09dd1b74d8",
"metadata": {},
"outputs": [],
"source": [
"prompt_instructions = {\n",
" \"triage_rules\": {\n",
" \"ignore\": \"Marketing newsletters, spam emails, mass company announcements\",\n",
" \"notify\": \"Team member out sick, build system notifications, project status updates\",\n",
" \"respond\": \"Direct questions from team members, meeting requests, critical bug reports\",\n",
" },\n",
" \"agent_instructions\": \"Use these tools when appropriate to help manage John's tasks efficiently.\"\n",
"}"
]
},
{
"cell_type": "markdown",
"id": "e39dde5d",
"metadata": {},
"source": [
"**Example email to see the schema of what we will be working with**"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "673d5316-bbc1-4b27-8e0f-db407725134a",
"metadata": {},
"outputs": [],
"source": [
"# Example incoming email\n",
"email = {\n",
" \"from\": \"Alice Smith <alice.smith@company.com>\",\n",
" \"to\": \"John Doe <john.doe@company.com>\",\n",
" \"subject\": \"Quick question about API documentation\",\n",
" \"body\": \"\"\"\n",
"Hi John,\n",
"\n",
"I was reviewing the API documentation for the new authentication service and noticed a few endpoints seem to be missing from the specs. Could you help clarify if this was intentional or if we should update the docs?\n",
"\n",
"Specifically, I'm looking at:\n",
"- /auth/refresh\n",
"- /auth/validate\n",
"\n",
"Thanks!\n",
"Alice\"\"\",\n",
"}"
]
},
{
"cell_type": "markdown",
"id": "7e80d329-563a-4824-9e76-2588edacfb6e",
"metadata": {},
"source": [
"## 2. Define the first part of the agent: **Triage**\n",
"\n",
"### Setup Routing Node"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "499fbd0e-3fcc-400d-afc8-7f0729382a8d",
"metadata": {},
"outputs": [],
"source": [
"from pydantic import BaseModel, Field\n",
"from typing_extensions import TypedDict, Literal, Annotated\n",
"from langchain.chat_models import init_chat_model"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "8bcbd7bd-a6b2-429d-b29c-b1fb7ba7472d",
"metadata": {},
"outputs": [],
"source": [
"llm = init_chat_model(\"openai:gpt-4o-mini\")"
]
},
{
"cell_type": "markdown",
"id": "0da3dd8b",
"metadata": {},
"source": [
"**Defining the schema that we want the Triage step to output**\n",
"- Reasoning: So the LLM give us the reason for making a decision.\n",
"- Classification"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "abc57143-856c-4a86-a234-ef8355166b3d",
"metadata": {},
"outputs": [],
"source": [
"class Router(BaseModel):\n",
" \"\"\"Analyze the unread email and route it according to its content.\"\"\"\n",
"\n",
" reasoning: str = Field(\n",
" description=\"Step-by-step reasoning behind the classification.\"\n",
" )\n",
" classification: Literal[\"ignore\", \"respond\", \"notify\"] = Field(\n",
" description=\"The classification of an email: 'ignore' for irrelevant emails, \"\n",
" \"'notify' for important information that doesn't need a response, \"\n",
" \"'respond' for emails that need a reply\",\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "c4778e88",
"metadata": {},
"source": [
"Now let's bind the Router to an LLM. This LLM will always return information in the schema that we defined above."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "b4917430-3aae-44c6-954f-68e5372fcf81",
"metadata": {},
"outputs": [],
"source": [
"llm_router = llm.with_structured_output(Router)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "59525b9b-5f70-4bef-806a-30bdce14248e",
"metadata": {},
"outputs": [],
"source": [
"from prompts import triage_system_prompt, triage_user_prompt"
]
},
{
"cell_type": "markdown",
"id": "5b6a8181",
"metadata": {},
"source": [
"**System prompt structure:**\n",
"1. Role: System role\n",
"2. Background: User profile background\n",
"3. Instructions: Explaining the 3 different categories that we want to classify the emails into\n",
"4. Rule: We will add different rules for the LLM to followe\n",
"5. Examples: To contain the few shot examples\n",
"\n",
"**User prompt structure:**\n",
"It formats the email."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "3137c130-8880-4426-8f11-770393087627",
"metadata": {},
"outputs": [],
"source": [
"# uncomment to view\n",
"#print(triage_system_prompt)\n",
"#print(triage_user_prompt)"
]
},
{
"cell_type": "markdown",
"id": "307d00b3",
"metadata": {},
"source": [
"Now let's populate the prompt"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "ba0a5975-a685-4f07-b43f-9e05ba96cda1",
"metadata": {},
"outputs": [],
"source": [
"system_prompt = triage_system_prompt.format(\n",
" full_name=profile[\"full_name\"],\n",
" name=profile[\"name\"],\n",
" examples=None,\n",
" user_profile_background=profile[\"user_profile_background\"],\n",
" triage_no=prompt_instructions[\"triage_rules\"][\"ignore\"],\n",
" triage_notify=prompt_instructions[\"triage_rules\"][\"notify\"],\n",
" triage_email=prompt_instructions[\"triage_rules\"][\"respond\"],\n",
")"
]
},
{
"cell_type": "markdown",
"id": "19a353b2",
"metadata": {},
"source": [
"Populate the user prompt"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "28642bb7-145d-4561-9bfd-b15332d0b171",
"metadata": {},
"outputs": [],
"source": [
"user_prompt = triage_user_prompt.format(\n",
" author=email[\"from\"],\n",
" to=email[\"to\"],\n",
" subject=email[\"subject\"],\n",
" email_thread=email[\"body\"],\n",
")"
]
},
{
"cell_type": "markdown",
"id": "be68cb64",
"metadata": {},
"source": [
"We can now call the LLM router"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "dbcfd44f-4142-4e58-a5e0-5ddd178ade90",
"metadata": {},
"outputs": [],
"source": [
"result = llm_router.invoke(\n",
" [\n",
" {\"role\": \"system\", \"content\": system_prompt},\n",
" {\"role\": \"user\", \"content\": user_prompt},\n",
" ]\n",
")"
]
},
{
"cell_type": "markdown",
"id": "ae42890b",
"metadata": {},
"source": [
"The result should have two sections:\n",
"1. Reasoing \n",
"2. The classification"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "38da4397-517b-4d05-af08-4a83264d73f6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"reasoning=\"This email contains a direct question from Alice about missing API documentation that needs clarification. Since she is seeking John's help to determine whether the omission was intentional or if the documentation should be updated, it requires a response from John.\" classification='respond'\n"
]
}
],
"source": [
"print(result)"
]
},
{
"cell_type": "markdown",
"id": "384d952a-6a39-4710-afa9-54c734c0d87b",
"metadata": {},
"source": [
"## 3. Main agent, define tools"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "9fb152b1-01dc-4a26-870b-463e7f72a8cb",
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.tools import tool"
]
},
{
"cell_type": "markdown",
"id": "7957b07b",
"metadata": {},
"source": [
"**write_email**:We now define the write email with a mock up output. In a real world project you should connect it to your Gmail API for instance so it can send actual emails."
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "ec4884fc-cc9b-48e8-a8e7-cca1a1874ed9",
"metadata": {},
"outputs": [],
"source": [
"@tool\n",
"def write_email(to: str, subject: str, content: str) -> str:\n",
" \"\"\"Write and send an email.\"\"\"\n",
" # Placeholder response - in real app would send email\n",
" return f\"Email sent to {to} with subject '{subject}'\""
]
},
{
"cell_type": "markdown",
"id": "67561ede",
"metadata": {},
"source": [
"**schedule_meeting**: Again, in an actual project it should be connected to APIs for scheduling the meetings"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "8af7d49c-d85b-41cd-8f3d-8f239e4fe255",
"metadata": {},
"outputs": [],
"source": [
"@tool\n",
"def schedule_meeting(\n",
" attendees: list[str], \n",
" subject: str, \n",
" duration_minutes: int, \n",
" preferred_day: str\n",
") -> str:\n",
" \"\"\"Schedule a calendar meeting.\"\"\"\n",
" # Placeholder response - in real app would check calendar and schedule\n",
" return f\"Meeting '{subject}' scheduled for {preferred_day} with {len(attendees)} attendees\""
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "830bdccd-da79-48b3-9094-ae93a36a73b6",
"metadata": {},
"outputs": [],
"source": [
"@tool\n",
"def check_calendar_availability(day: str) -> str:\n",
" \"\"\"Check calendar availability for a given day.\"\"\"\n",
" # Placeholder response - in real app would check actual calendar\n",
" return f\"Available times on {day}: 9:00 AM, 2:00 PM, 4:00 PM\""
]
},
{
"cell_type": "markdown",
"id": "dfcb6b2c-adda-49d6-9d6c-b517ecf0bed3",
"metadata": {},
"source": [
"## 2.1. Main agent, define prompt\n",
"\n",
"Here we will create the prompt for our agent. The function gets the state of the agent and returns a list of messages. The output will be consist of two parts:\n",
"1. The messages that are already in the state\n",
"2. A system message that we put at the start"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "85758f8a-d58a-4f7f-8c86-809e1c250cba",
"metadata": {},
"outputs": [],
"source": [
"from prompts import agent_system_prompt\n",
"def create_prompt(state):\n",
" return [\n",
" {\n",
" \"role\": \"system\", \n",
" \"content\": agent_system_prompt.format(\n",
" instructions=prompt_instructions[\"agent_instructions\"],\n",
" **profile\n",
" )\n",
" }\n",
" ] + state['messages']"
]
},
{
"cell_type": "markdown",
"id": "ea56124d",
"metadata": {},
"source": [
"System message structure:\n",
"1. Role: System role\n",
"2. Tools: tool explanation\n",
"3. Instructions"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "8f2d35a8-5e2f-4d67-9c5f-76b0d28e6fc0",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"< Role >\n",
"You are {full_name}'s executive assistant. You are a top-notch executive assistant who cares about {name} performing as well as possible.\n",
"</ Role >\n",
"\n",
"< Tools >\n",
"You have access to the following tools to help manage {name}'s communications and schedule:\n",
"\n",
"1. write_email(to, subject, content) - Send emails to specified recipients\n",
"2. schedule_meeting(attendees, subject, duration_minutes, preferred_day) - Schedule calendar meetings\n",
"3. check_calendar_availability(day) - Check available time slots for a given day\n",
"</ Tools >\n",
"\n",
"< Instructions >\n",
"{instructions}\n",
"</ Instructions >\n",
"\n"
]
}
],
"source": [
"print(agent_system_prompt)"
]
},
{
"cell_type": "markdown",
"id": "e9a47112",
"metadata": {},
"source": [
"**Now we can build the agent**"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "ea1d9acd-4a00-4432-9ce8-ce868f2ef989",
"metadata": {},
"outputs": [],
"source": [
"from langgraph.prebuilt import create_react_agent"
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "6d8fceee-06a0-4f57-a4e7-8d5fccd75fcd",
"metadata": {},
"outputs": [],
"source": [
"tools=[write_email, schedule_meeting, check_calendar_availability]"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "4002b955-8496-46fa-97c3-15246dd7c621",
"metadata": {},
"outputs": [],
"source": [
"agent = create_react_agent(\n",
" \"openai:gpt-4o\",\n",
" tools=tools,\n",
" prompt=create_prompt,\n",
")"
]
},
{
"cell_type": "markdown",
"id": "ce1ba502",
"metadata": {},
"source": [
"Let's try the agent"
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "91c2dd9b-ab96-48f6-9867-7095214452ef",
"metadata": {},
"outputs": [],
"source": [
"response = agent.invoke(\n",
" {\"messages\": [{\n",
" \"role\": \"user\", \n",
" \"content\": \"what is my availability for tuesday?\"\n",
" }]}\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "86ea8bb3-8026-4e08-94a6-edea65509162",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"You have available time slots on Tuesday at 9:00 AM, 2:00 PM, and 4:00 PM. If you need to schedule anything, I can assist with that.\n"
]
}
],
"source": [
"response[\"messages\"][-1].pretty_print()"
]
},
{
"cell_type": "markdown",
"id": "f09f8848-3d73-44ba-99c7-36ace553b1db",
"metadata": {},
"source": [
"## Create the Overall Agent Using LangGraph\n",
"\n",
"1. Define the State of the agent\n",
" - email_input: It is what the user will pass in and will contain all the info about the email\n",
" - messages: The messages for the agent's workflow"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "356762e1-0fa4-422a-ba58-48596cd78c46",
"metadata": {},
"outputs": [],
"source": [
"from langgraph.graph import add_messages\n",
"\n",
"class State(TypedDict):\n",
" email_input: dict\n",
" messages: Annotated[list, add_messages]"
]
},
{
"cell_type": "code",
"execution_count": 33,
"id": "01a31298-4d6d-4a35-b489-92ce1b72519a",
"metadata": {},
"outputs": [],
"source": [
"from langgraph.graph import StateGraph, START, END\n",
"from langgraph.types import Command\n",
"from typing import Literal\n",
"from IPython.display import Image, display"
]
},
{
"cell_type": "markdown",
"id": "e4a562ab",
"metadata": {},
"source": [
"2. Defining a Node for the Triage stept\n",
"\n",
"It gets the State and returns a command.\n",
"\n",
"Note: Command will update the state and also it tells the agent where to go. Depending on the result of the triage step, The agent has two options to go after the triage node:\n",
"- response_agent\n",
"- end of the graph"
]
},
{
"cell_type": "code",
"execution_count": 35,
"id": "f28908e0-c4fa-4e88-8a7e-e1d6af09b697",
"metadata": {},
"outputs": [],
"source": [
"def triage_router(state: State) -> Command[\n",
" Literal[\"response_agent\", \"__end__\"]\n",
"]:\n",
" # Let's first divide the email input into a few different steps\n",
" author = state['email_input']['author']\n",
" to = state['email_input']['to']\n",
" subject = state['email_input']['subject']\n",
" email_thread = state['email_input']['email_thread']\n",
" # Create the system prompt\n",
" system_prompt = triage_system_prompt.format(\n",
" full_name=profile[\"full_name\"],\n",
" name=profile[\"name\"],\n",
" user_profile_background=profile[\"user_profile_background\"],\n",
" triage_no=prompt_instructions[\"triage_rules\"][\"ignore\"],\n",
" triage_notify=prompt_instructions[\"triage_rules\"][\"notify\"],\n",
" triage_email=prompt_instructions[\"triage_rules\"][\"respond\"],\n",
" examples=None\n",
" )\n",
" # Create the user prompt from the email input\n",
" user_prompt = triage_user_prompt.format(\n",
" author=author, \n",
" to=to, \n",
" subject=subject, \n",
" email_thread=email_thread\n",
" )\n",
" # Let's call the llm router that outputs the classification result\n",
" result = llm_router.invoke(\n",
" [\n",
" {\"role\": \"system\", \"content\": system_prompt},\n",
" {\"role\": \"user\", \"content\": user_prompt},\n",
" ]\n",
" )\n",
" # Let's handle the three types of classification that can occure (respond, ignore, notifu)\n",
" if result.classification == \"respond\":\n",
" print(\"📧 Classification: RESPOND - This email requires a response\")\n",
" goto = \"response_agent\"\n",
" update = {\n",
" \"messages\": [\n",
" {\n",
" \"role\": \"user\",\n",
" \"content\": f\"Respond to the email {state['email_input']}\",\n",
" }\n",
" ]\n",
" }\n",
" elif result.classification == \"ignore\":\n",
" print(\"🚫 Classification: IGNORE - This email can be safely ignored\")\n",
" update = None\n",
" goto = END\n",
" elif result.classification == \"notify\":\n",
" # If real life, this would do something else\n",
" print(\"🔔 Classification: NOTIFY - This email contains important information\")\n",
" update = None\n",
" goto = END\n",
" else:\n",
" raise ValueError(f\"Invalid classification: {result.classification}\")\n",
" # Now let's return the command that tells the graph which Node to go to and which update to apply.\n",
" return Command(goto=goto, update=update)"
]
},
{
"cell_type": "markdown",
"id": "f5c28180-e21c-42bc-9568-4c3834cb1491",
"metadata": {},
"source": [
"## Put it all together"
]
},
{
"cell_type": "code",
"execution_count": 36,
"id": "a4cb9bb4-905b-45e3-9b34-eb59f850f51e",
"metadata": {},
"outputs": [],
"source": [
"email_agent = StateGraph(State)\n",
"email_agent = email_agent.add_node(triage_router)\n",
"email_agent = email_agent.add_node(\"response_agent\", agent)\n",
"email_agent = email_agent.add_edge(START, \"triage_router\")\n",
"email_agent = email_agent.compile()"
]
},
{
"cell_type": "code",
"execution_count": 43,
"id": "ed4509b1-7f03-4936-acab-e2cb0fe1192a",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
"<IPython.core.display.Image object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from IPython.display import Image, display\n",
"\n",
"# Show the agent\n",
"display(Image(email_agent.get_graph(xray=True).draw_mermaid_png()))"
]
},
{
"cell_type": "markdown",
"id": "f7f90045",
"metadata": {},
"source": [
"Let's try out the agent using a spam email"
]
},
{
"cell_type": "code",
"execution_count": 44,
"id": "792c32cc-9b20-4f8a-900a-2158413317c0",
"metadata": {},
"outputs": [],
"source": [
"email_input = {\n",
" \"author\": \"Marketing Team <marketing@amazingdeals.com>\",\n",
" \"to\": \"John Doe <john.doe@company.com>\",\n",
" \"subject\": \"🔥 EXCLUSIVE OFFER: Limited Time Discount on Developer Tools! 🔥\",\n",
" \"email_thread\": \"\"\"Dear Valued Developer,\n",
"\n",
"Don't miss out on this INCREDIBLE opportunity! \n",
"\n",
"🚀 For a LIMITED TIME ONLY, get 80% OFF on our Premium Developer Suite! \n",
"\n",
"✨ FEATURES:\n",
"- Revolutionary AI-powered code completion\n",
"- Cloud-based development environment\n",
"- 24/7 customer support\n",
"- And much more!\n",
"\n",
"💰 Regular Price: $999/month\n",
"🎉 YOUR SPECIAL PRICE: Just $199/month!\n",
"\n",
"🕒 Hurry! This offer expires in:\n",
"24 HOURS ONLY!\n",
"\n",
"Click here to claim your discount: https://amazingdeals.com/special-offer\n",
"\n",
"Best regards,\n",
"Marketing Team\n",
"---\n",
"To unsubscribe, click here\n",
"\"\"\",\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 45,
"id": "fb6cf2b1-1739-4f70-ad6b-58d0aec8a219",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"🚫 Classification: IGNORE - This email can be safely ignored\n"
]
}
],
"source": [
"response = email_agent.invoke({\"email_input\": email_input})"
]
},
{
"cell_type": "markdown",
"id": "45cdc47d",
"metadata": {},
"source": [
"Let's try out the agent using an email that should get a response"
]
},
{
"cell_type": "code",
"execution_count": 46,
"id": "f62bb9ea-e964-4d38-a3f5-e890f32dc563",
"metadata": {},
"outputs": [],
"source": [
"email_input = {\n",
" \"author\": \"Alice Smith <alice.smith@company.com>\",\n",
" \"to\": \"John Doe <john.doe@company.com>\",\n",
" \"subject\": \"Quick question about API documentation\",\n",
" \"email_thread\": \"\"\"Hi John,\n",
"\n",
"I was reviewing the API documentation for the new authentication service and noticed a few endpoints seem to be missing from the specs. Could you help clarify if this was intentional or if we should update the docs?\n",
"\n",
"Specifically, I'm looking at:\n",
"- /auth/refresh\n",
"- /auth/validate\n",
"\n",
"Thanks!\n",
"Alice\"\"\",\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 47,
"id": "f6fd3a49-57ed-4da0-bd82-21c49b5f66cc",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"📧 Classification: RESPOND - This email requires a response\n"
]
}
],
"source": [
"response = email_agent.invoke({\"email_input\": email_input})"
]
},
{
"cell_type": "markdown",
"id": "5f042143",
"metadata": {},
"source": [
"Let's have a deeper look into what happened here:"
]
},
{
"cell_type": "code",
"execution_count": 48,
"id": "ec08077b-6e36-4f7f-a043-1f4a47ac8408",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"================================\u001b[1m Human Message \u001b[0m=================================\n",
"\n",
"Respond to the email {'author': 'Alice Smith <alice.smith@company.com>', 'to': 'John Doe <john.doe@company.com>', 'subject': 'Quick question about API documentation', 'email_thread': \"Hi John,\\n\\nI was reviewing the API documentation for the new authentication service and noticed a few endpoints seem to be missing from the specs. Could you help clarify if this was intentional or if we should update the docs?\\n\\nSpecifically, I'm looking at:\\n- /auth/refresh\\n- /auth/validate\\n\\nThanks!\\nAlice\"}\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"Tool Calls:\n",
" write_email (call_EYNyWNkHjKtMuv1hKHhDuGT3)\n",
" Call ID: call_EYNyWNkHjKtMuv1hKHhDuGT3\n",
" Args:\n",
" to: alice.smith@company.com\n",
" subject: Re: Quick question about API documentation\n",
" content: Hi Alice,\n",
"\n",
"Thank you for bringing this to my attention. I believe those endpoints should indeed be included in the documentation. I'll coordinate with the team responsible for API documentation to ensure they are added.\n",
"\n",
"In the meantime, if you need any specific details regarding those endpoints, please let me know and I'll provide the necessary information.\n",
"\n",
"Best regards,\n",
"\n",
"John Doe\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"Name: write_email\n",
"\n",
"Email sent to alice.smith@company.com with subject 'Re: Quick question about API documentation'\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"I have responded to Alice Smith's email regarding the API documentation, clarifying that the endpoints she mentioned should be included in the documentation and offering further assistance if needed.\n"
]
}
],
"source": [
"for m in response[\"messages\"]:\n",
" m.pretty_print()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "memory-env",
"language": "python",
"name": "python3"
},
"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.11.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}