Skip to main content
Glama

Notion MCP Server V2

by ankitmalik84
full_email_agent.ipynb107 kB
{ "cells": [ { "cell_type": "markdown", "id": "0a51fd1c-715f-4fd6-b36e-282e9bbe8033", "metadata": {}, "source": [ "## Lesson 5: Email Assistant with Semantic + Episodic + Procedural Memory\n", "\n", "We previously built an email assistant that:\n", "- Classifies incoming messages (respond, ignore, notify)\n", "- Uses human-in-the-loop to refine the assistant's ability to classify emails\n", "- Drafts responses\n", "- Schedules meetings\n", "- Uses memory to remember details from previous emails \n", "\n", "Now, we'll add procedural memory that allows the user to `update instructions for using the calendar and email writing` tools. " ] }, { "cell_type": "markdown", "id": "0c424b9c-9c9f-41e0-9bc5-ac54e06d3605", "metadata": {}, "source": [ "<!-- <p style=\"background-color:#f7fff8; padding:15px; border-width:3px; border-color:#e0f0e0; border-style:solid; border-radius:6px\"> -->\n", "🚨\n", "&nbsp; <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": "337b3ec7-0f5a-46bb-a585-0bfdc1e53781", "metadata": {}, "source": [ "## Load API tokens for our 3rd party APIs" ] }, { "cell_type": "code", "execution_count": 1, "id": "37aa0a80-bb20-4d0a-a7ee-29a67a71150c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n" ] } ], "source": [ "from dotenv import load_dotenv\n", "print(load_dotenv())" ] }, { "cell_type": "markdown", "id": "dcd03ded-cdab-4567-8212-cd44e3d1cf0b", "metadata": {}, "source": [ "## 1. Setup a Profile, Prompt and Example Email" ] }, { "cell_type": "code", "execution_count": 2, "id": "8f3b87dd-cf2a-467b-bad1-ab191c8972ac", "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": "875c72ea", "metadata": {}, "source": [ "📝 Notes:\n", "- 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.\n", "\n", "The goal of adding the `Procedural Memory` is to be able to update the following instructions over time." ] }, { "cell_type": "code", "execution_count": 3, "id": "6fba3371-a99d-4a4b-a22e-de5e5a69162e", "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": "fc76c88c", "metadata": {}, "source": [ "**Example email to see the schema of what we will be working with**" ] }, { "cell_type": "code", "execution_count": 4, "id": "253f59a5-b205-445d-a627-2e9cd54219e7", "metadata": {}, "outputs": [], "source": [ "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": "065a0404", "metadata": {}, "source": [ "### Define 🛠️ tools for managing memory (semantic memory)\n", "\n", "Prepare the vector database" ] }, { "cell_type": "code", "execution_count": 5, "id": "b4d0f32e-c88f-4af4-9708-9d5e2ac74229", "metadata": {}, "outputs": [], "source": [ "from langgraph.store.memory import InMemoryStore" ] }, { "cell_type": "code", "execution_count": 6, "id": "cd0a09c5-643a-4ce6-9ec3-981294558bdc", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "c:\\Users\\farza\\anaconda3\\envs\\memory-env\\Lib\\site-packages\\langgraph\\store\\base\\embed.py:95: LangChainBetaWarning: The function `init_embeddings` is in beta. It is actively being worked on, so the API may change.\n", " return init_embeddings(embed)\n" ] } ], "source": [ "store = InMemoryStore(\n", " index={\"embed\": \"openai:text-embedding-3-small\"}\n", ")\n", "# ignore beta warning if it appears" ] }, { "cell_type": "markdown", "id": "723ab22d", "metadata": {}, "source": [ "**********************\n", "\n", "# 🧠 Episodic Memory\n", "\n", "Add few shot examples to InMemoryStore (Vector Database)" ] }, { "cell_type": "code", "execution_count": 7, "id": "b82adfb1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Few Shot Examples were added to the vector database.\n" ] } ], "source": [ "import uuid\n", "# First Example\n", "email = {\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", "}\n", "data = {\n", " \"email\": email,\n", " # This is to start changing the behavior of the agent\n", " \"label\": \"respond\"\n", "}\n", "\n", "store.put(\n", " (\"email_assistant\", \"lance\", \"examples\"), \n", " str(uuid.uuid4()), \n", " data\n", ")\n", "# Second Example\n", "data = {\n", " \"email\": {\n", " \"author\": \"Sarah Chen <sarah.chen@company.com>\",\n", " \"to\": \"John Doe <john.doe@company.com>\",\n", " \"subject\": \"Update: Backend API Changes Deployed to Staging\",\n", " \"email_thread\": \"\"\"Hi John,\n", " \n", " Just wanted to let you know that I've deployed the new authentication endpoints we discussed to the staging environment. Key changes include:\n", " \n", " - Implemented JWT refresh token rotation\n", " - Added rate limiting for login attempts\n", " - Updated API documentation with new endpoints\n", " \n", " All tests are passing and the changes are ready for review. You can test it out at staging-api.company.com/auth/*\n", " \n", " No immediate action needed from your side - just keeping you in the loop since this affects the systems you're working on.\n", " \n", " Best regards,\n", " Sarah\n", " \"\"\",\n", " },\n", " \"label\": \"ignore\"\n", "}\n", "store.put(\n", " (\"email_assistant\", \"lance\", \"examples\"),\n", " str(uuid.uuid4()),\n", " data\n", ")\n", "print(\"Few Shot Examples were added to the vector database.\")" ] }, { "cell_type": "markdown", "id": "f6888324", "metadata": {}, "source": [ "### Simulate searching and returning examples\n", "\n", "This helper function will take in the few shot examples that are retrieved from the `store` and format them nicely into a `string`. (It gives us better visibility on what was retrieved and also it will be a better format for the LLM)" ] }, { "cell_type": "code", "execution_count": 8, "id": "1885ddbe-cc23-4160-8a49-b01598abe58a", "metadata": {}, "outputs": [], "source": [ "# Template for formating an example to put in prompt\n", "template = \"\"\"Email Subject: {subject}\n", "Email From: {from_email}\n", "Email To: {to_email}\n", "Email Content: \n", "```\n", "{content}\n", "```\n", "> Triage Result: {result}\"\"\"\n", "\n", "# Format list of few shots\n", "def format_few_shot_examples(examples):\n", " strs = [\"Here are some previous examples:\"]\n", " for eg in examples:\n", " strs.append(\n", " template.format(\n", " subject=eg.value[\"email\"][\"subject\"],\n", " to_email=eg.value[\"email\"][\"to\"],\n", " from_email=eg.value[\"email\"][\"author\"],\n", " content=eg.value[\"email\"][\"email_thread\"][:400],\n", " result=eg.value[\"label\"],\n", " )\n", " )\n", " return \"\\n\\n------------\\n\\n\".join(strs)" ] }, { "cell_type": "markdown", "id": "c03d14b7", "metadata": {}, "source": [ "### Test the vector database by a sample search\n", "\n", "Letr's use the same email that we passed above with a small change (removing `Just`) from the first line. This is a semantic search just like a simple RAG pipeline.\n", "\n", "`limit = 1` just fetches the most similar email to the output." ] }, { "cell_type": "code", "execution_count": 9, "id": "2393c29c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Here are some previous examples:\n", "\n", "------------\n", "\n", "Email Subject: Update: Backend API Changes Deployed to Staging\n", "Email From: Sarah Chen <sarah.chen@company.com>\n", "Email To: John Doe <john.doe@company.com>\n", "Email Content: \n", "```\n", "Hi John,\n", " \n", " Just wanted to let you know that I've deployed the new authentication endpoints we discussed to the staging environment. Key changes include:\n", " \n", " - Implemented JWT refresh token rotation\n", " - Added rate limiting for login attempts\n", " - Updated API documentation with new endpoints\n", " \n", " All tests are passing and the changes are ready for review. You can test it out at st\n", "```\n", "> Triage Result: ignore\n" ] } ], "source": [ "email_data = {\n", " \"author\": \"Sarah Chen <sarah.chen@company.com>\",\n", " \"to\": \"John Doe <john.doe@company.com>\",\n", " \"subject\": \"Update: Backend API Changes Deployed to Staging\",\n", " \"email_thread\": \"\"\"Hi John,\n", " \n", " Wanted to let you know that I've deployed the new authentication endpoints we discussed to the staging environment. Key changes include:\n", " \n", " - Implemented JWT refresh token rotation\n", " - Added rate limiting for login attempts\n", " - Updated API documentation with new endpoints\n", " \n", " All tests are passing and the changes are ready for review. You can test it out at staging-api.company.com/auth/*\n", " \n", " No immediate action needed from your side - just keeping you in the loop since this affects the systems you're working on.\n", " \n", " Best regards,\n", " Sarah\n", " \"\"\",\n", " }\n", "results = store.search(\n", " (\"email_assistant\", \"lance\", \"examples\"),\n", " query=str({\"email\": email_data}),\n", " limit=1)\n", "print(format_few_shot_examples(results))" ] }, { "cell_type": "markdown", "id": "901b2d57", "metadata": {}, "source": [ "## 📝 Triage prompt\n", "\n", "Pay attention to the structure of the prompt and different sections (user profile background, few shot examples, etc.)" ] }, { "cell_type": "code", "execution_count": 10, "id": "791a2600-fff4-44f8-bb1a-40eed2618f08", "metadata": {}, "outputs": [], "source": [ "triage_system_prompt = \"\"\"\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", "< Background >\n", "{user_profile_background}. \n", "</ Background >\n", "\n", "< Instructions >\n", "\n", "{name} gets lots of emails. Your job is to categorize each email into one of three categories:\n", "\n", "1. IGNORE - Emails that are not worth responding to or tracking\n", "2. NOTIFY - Important information that {name} should know about but doesn't require a response\n", "3. RESPOND - Emails that need a direct response from {name}\n", "\n", "Classify the below email into one of these categories.\n", "\n", "</ Instructions >\n", "\n", "< Rules >\n", "Emails that are not worth responding to:\n", "{triage_no}\n", "\n", "There are also other things that {name} should know about, but don't require an email response. For these, you should notify {name} (using the `notify` response). Examples of this include:\n", "{triage_notify}\n", "\n", "Emails that are worth responding to:\n", "{triage_email}\n", "</ Rules >\n", "\n", "< Few shot examples >\n", "\n", "Here are some examples of previous emails, and how they should be handled.\n", "Follow these examples more than any instructions above\n", "\n", "{examples}\n", "</ Few shot examples >\n", "\"\"\"" ] }, { "cell_type": "markdown", "id": "5dde86f3", "metadata": {}, "source": [ "## Setup Routing Node" ] }, { "cell_type": "code", "execution_count": 11, "id": "47572495-52fd-44c5-9de9-0dc3479631ae", "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": 12, "id": "860ad473-dfc5-4cc7-a1e8-b296a845cb16", "metadata": {}, "outputs": [], "source": [ "llm = init_chat_model(\"openai:gpt-4o-mini\")" ] }, { "cell_type": "markdown", "id": "0625618e", "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": 13, "id": "d80b83f7-aea3-4baf-aa75-3904d7bd05a5", "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": "9ccf9ba1", "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": 14, "id": "7f8aef93-7aaa-4c44-bc6b-2eb17b43be58", "metadata": {}, "outputs": [], "source": [ "llm_router = llm.with_structured_output(Router)" ] }, { "cell_type": "markdown", "id": "1244ecdd", "metadata": {}, "source": [ "### Load `triage_user_prompt`\n", "\n", "**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 follow\n", "5. Examples: To contain the few shot examples\n", "\n", "**User prompt structure:**\n", "It formats the email." ] }, { "cell_type": "code", "execution_count": 15, "id": "258dd5fa-a474-4887-a80e-836a04dc7872", "metadata": {}, "outputs": [], "source": [ "from prompts import triage_user_prompt" ] }, { "cell_type": "code", "execution_count": 16, "id": "e1c4d84c", "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", "< Background >\n", "{user_profile_background}. \n", "</ Background >\n", "\n", "< Instructions >\n", "\n", "{name} gets lots of emails. Your job is to categorize each email into one of three categories:\n", "\n", "1. IGNORE - Emails that are not worth responding to or tracking\n", "2. NOTIFY - Important information that {name} should know about but doesn't require a response\n", "3. RESPOND - Emails that need a direct response from {name}\n", "\n", "Classify the below email into one of these categories.\n", "\n", "</ Instructions >\n", "\n", "< Rules >\n", "Emails that are not worth responding to:\n", "{triage_no}\n", "\n", "There are also other things that {name} should know about, but don't require an email response. For these, you should notify {name} (using the `notify` response). Examples of this include:\n", "{triage_notify}\n", "\n", "Emails that are worth responding to:\n", "{triage_email}\n", "</ Rules >\n", "\n", "< Few shot examples >\n", "\n", "Here are some examples of previous emails, and how they should be handled.\n", "Follow these examples more than any instructions above\n", "\n", "{examples}\n", "</ Few shot examples >\n", "\n", "\n", "Please determine how to handle the below email thread:\n", "\n", "From: {author}\n", "To: {to}\n", "Subject: {subject}\n", "{email_thread}\n" ] } ], "source": [ "# uncomment to view\n", "print(triage_system_prompt)\n", "print(triage_user_prompt)" ] }, { "cell_type": "markdown", "id": "8b86e632", "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": 17, "id": "f2c9f502-8dc9-4810-9728-9dbb1ccbcf4f", "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": "markdown", "id": "42e27332-0c96-482c-893f-9c181ec710c0", "metadata": {}, "source": [ "#### Setup Triage Router Node" ] }, { "cell_type": "code", "execution_count": 18, "id": "c143e3c9-ab79-45a0-a638-87a8b29233c8", "metadata": {}, "outputs": [], "source": [ "from langgraph.graph import StateGraph, START, END\n", "from langgraph.types import Command\n", "from typing import Literal" ] }, { "cell_type": "markdown", "id": "a5791768-a212-4901-a181-e1fc307507d2", "metadata": {}, "source": [ "#### 2. Defining a Node for the Triage stept\n", "\n", "Updated `triage_router` gets `ignore`, `notify` and `respond` rule from `store` 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\n", "\n", "We modify this function to retrieve prompt_instructions[\"triage_rules\"] from the `store` and add them to the system prompt. Doing this we can update the rules of the agent without the need to modify the code.\n", "\n", "\n", "- triage_router()\n", "\n", "1. Inputs `store`, `config` and `store` arguemnts\n", "2. Before the system prompt, we add the logic that adds the few shot example in there\n", " - Define the `namespace`\n", " - Use `namespace` to search in the store\n", " - format the search result\n", " - update the system prompt and add the formatted output" ] }, { "cell_type": "code", "execution_count": 19, "id": "4e672197-b97a-4a23-99ec-a38653bf8773", "metadata": {}, "outputs": [], "source": [ "def triage_router(state: State, config, store) -> Command[\n", " Literal[\"response_agent\", \"__end__\"]\n", "]:\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", " # ---------------Section for Episodic memory ----------------------\n", " namespace = (\n", " \"email_assistant\",\n", " config['configurable']['langgraph_user_id'],\n", " \"examples\"\n", " )\n", " examples = store.search(\n", " namespace, \n", " query=str({\"email\": state['email_input']})\n", " )\n", " print(\"Examples:\", examples)\n", " examples=format_few_shot_examples(examples)\n", " # ---------------End of Section for Episodic memory ---------------\n", " # -------------Section for Procedural Memory-----------------------\n", " langgraph_user_id = config['configurable']['langgraph_user_id']\n", " namespace = (langgraph_user_id, )\n", " # Let's get the `ingnore` rules from the store\n", " result = store.get(namespace, \"triage_ignore\")\n", " if result is None: # If it does not exist\n", " store.put(\n", " namespace, \n", " \"triage_ignore\", \n", " {\"prompt\": prompt_instructions[\"triage_rules\"][\"ignore\"]}\n", " )\n", " ignore_prompt = prompt_instructions[\"triage_rules\"][\"ignore\"]\n", " else: # If it exists\n", " ignore_prompt = result.value['prompt']\n", " # Let's get the `notify` rules from the store\n", " result = store.get(namespace, \"triage_notify\")\n", " if result is None:\n", " store.put(\n", " namespace, \n", " \"triage_notify\", \n", " {\"prompt\": prompt_instructions[\"triage_rules\"][\"notify\"]}\n", " )\n", " notify_prompt = prompt_instructions[\"triage_rules\"][\"notify\"]\n", " else:\n", " notify_prompt = result.value['prompt']\n", " # Let's get the `respond` rules from the store\n", " result = store.get(namespace, \"triage_respond\")\n", " if result is None:\n", " store.put(\n", " namespace, \n", " \"triage_respond\", \n", " {\"prompt\": prompt_instructions[\"triage_rules\"][\"respond\"]}\n", " )\n", " respond_prompt = prompt_instructions[\"triage_rules\"][\"respond\"]\n", " else:\n", " respond_prompt = result.value['prompt']\n", " \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=ignore_prompt, # new way of passing the ignore rules to the prompt\n", " triage_notify=notify_prompt, # new way of passing the notify rules to the prompt\n", " triage_email=respond_prompt, # new way of passing the respond rules to the prompt\n", " examples=examples\n", " )\n", " # -------------End of Section for Procedural Memory---------------\n", " user_prompt = triage_user_prompt.format(\n", " author=author, \n", " to=to, \n", " subject=subject, \n", " email_thread=email_thread\n", " )\n", " result = llm_router.invoke(\n", " [\n", " {\"role\": \"system\", \"content\": system_prompt},\n", " {\"role\": \"user\", \"content\": user_prompt},\n", " ]\n", " )\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", " return Command(goto=goto, update=update)" ] }, { "cell_type": "markdown", "id": "fcacf544-2635-4f5a-b182-71ae5b5ad7ce", "metadata": {}, "source": [ "## Build the rest of our agent\n", "\n", "### 🛠️ Define Tools" ] }, { "cell_type": "code", "execution_count": 20, "id": "e5be53c4-b500-4f71-8073-6cd3a43bcf2b", "metadata": {}, "outputs": [], "source": [ "from langchain_core.tools import tool" ] }, { "cell_type": "code", "execution_count": 21, "id": "0901c346-71b4-4655-959c-23998df08de1", "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}'\"\n", "\n", "@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\"\n", "\n", "\n", "@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\"\n" ] }, { "cell_type": "markdown", "id": "734cd891", "metadata": {}, "source": [ "**********************\n", "\n", "# 🧠 Semantic Memory" ] }, { "cell_type": "code", "execution_count": 22, "id": "eb51d144-9148-4e65-8ba9-f6f05c17f3c1", "metadata": {}, "outputs": [], "source": [ "from langmem import create_manage_memory_tool, create_search_memory_tool" ] }, { "cell_type": "markdown", "id": "fa055de6", "metadata": {}, "source": [ "`langgraph_user_id`: This will be passed to the tool as part of the runtime configuration. That will be used to namespace the memory so if we are dealing with emails for multiple users, we can easily handle it and have different collections for different users." ] }, { "cell_type": "code", "execution_count": 23, "id": "9c0adbca-60ea-478e-a8e2-414bdb739aed", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "manage_memory\n", "Create, update, or delete persistent MEMORIES to persist across conversations.\n", "Include the MEMORY ID when updating or deleting a MEMORY. Omit when creating a new MEMORY - it will be created for you.\n", "Proactively call this tool when you:\n", "\n", "1. Identify a new USER preference.\n", "2. Receive an explicit USER request to remember something or otherwise alter your behavior.\n", "3. Are working and want to record important context.\n", "4. Identify that an existing MEMORY is incorrect or outdated.\n", "{'content': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'title': 'Content'}, 'action': {'default': 'create', 'enum': ['create', 'update', 'delete'], 'title': 'Action', 'type': 'string'}, 'id': {'anyOf': [{'format': 'uuid', 'type': 'string'}, {'type': 'null'}], 'default': None, 'title': 'Id'}}\n", "--------------\n", "search_memory\n", "Search your long-term memories for information relevant to your current context.\n", "{'query': {'title': 'Query', 'type': 'string'}, 'limit': {'default': 10, 'title': 'Limit', 'type': 'integer'}, 'offset': {'default': 0, 'title': 'Offset', 'type': 'integer'}, 'filter': {'anyOf': [{'additionalProperties': True, 'type': 'object'}, {'type': 'null'}], 'default': None, 'title': 'Filter'}}\n" ] } ], "source": [ "manage_memory_tool = create_manage_memory_tool(\n", " namespace=(\n", " \"email_assistant\", \n", " \"{langgraph_user_id}\",\n", " \"collection\"\n", " )\n", ")\n", "search_memory_tool = create_search_memory_tool(\n", " namespace=(\n", " \"email_assistant\",\n", " \"{langgraph_user_id}\",\n", " \"collection\"\n", " )\n", ")\n", "\n", "print(manage_memory_tool.name)\n", "print(manage_memory_tool.description)\n", "print(manage_memory_tool.args)\n", "print(\"--------------\")\n", "print(search_memory_tool.name)\n", "print(search_memory_tool.description)\n", "print(search_memory_tool.args)" ] }, { "cell_type": "code", "execution_count": 24, "id": "c0f468c7-e67c-4e1e-95cf-e8ae9ad54060", "metadata": {}, "outputs": [], "source": [ "agent_system_prompt_memory = \"\"\"\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", "4. manage_memory - Store any relevant information about contacts, actions, discussion, etc. in memory for future reference\n", "5. search_memory - Search for any relevant information that may have been stored in memory\n", "</ Tools >\n", "\n", "< Instructions >\n", "{instructions}\n", "</ Instructions >\n", "\"\"\"" ] }, { "cell_type": "markdown", "id": "a76db56e-c51c-4a29-8ab2-10a8263ea21a", "metadata": {}, "source": [ "#### 📝 Updated create_prompt `gets prompt` from `store`\n", "\n", "1. Add config and `store` arguments\n", "2. Let's get the agent_instroctions from the `store`\n", "3. Handle the scenario when there is no result" ] }, { "cell_type": "code", "execution_count": 25, "id": "cce4484e-8e76-4a4b-afe9-a27f8507b0a3", "metadata": {}, "outputs": [], "source": [ "def create_prompt(state, config, store):\n", " langgraph_user_id = config['configurable']['langgraph_user_id']\n", " namespace = (langgraph_user_id, )\n", " result = store.get(namespace, \"agent_instructions\")\n", " if result is None:\n", " store.put(\n", " namespace, \n", " \"agent_instructions\", \n", " {\"prompt\": prompt_instructions[\"agent_instructions\"]}\n", " )\n", " prompt = prompt_instructions[\"agent_instructions\"]\n", " else:\n", " prompt = result.value['prompt']\n", " \n", " return [\n", " {\n", " \"role\": \"system\", \n", " \"content\": agent_system_prompt_memory.format(\n", " instructions=prompt, # before we had prompt_instructions[\"agent_instructions\"]\n", " **profile\n", " )\n", " }\n", " ] + state['messages']" ] }, { "cell_type": "markdown", "id": "5beba96f-a399-4304-9d72-c9535cdbd47e", "metadata": {}, "source": [ "## 🎯 Create the email agent" ] }, { "cell_type": "code", "execution_count": 26, "id": "d4c53481-9304-4921-9023-575918720814", "metadata": {}, "outputs": [], "source": [ "from langgraph.prebuilt import create_react_agent" ] }, { "cell_type": "code", "execution_count": 27, "id": "ae4d51ec-6b42-4345-a441-a1701651fbce", "metadata": {}, "outputs": [], "source": [ "tools= [\n", " write_email, \n", " schedule_meeting,\n", " check_calendar_availability,\n", " manage_memory_tool,\n", " search_memory_tool\n", "]\n", "response_agent = create_react_agent(\n", " \"openai:gpt-4o\",\n", " tools=tools,\n", " prompt=create_prompt,\n", " # Use this to ensure the store is passed to the agent \n", " store=store\n", ")" ] }, { "cell_type": "code", "execution_count": 28, "id": "01285825-2bf0-4c41-ad46-70f4a5defcdc", "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\", response_agent)\n", "email_agent = email_agent.add_edge(START, \"triage_router\")\n", "email_agent = email_agent.compile(store=store)" ] }, { "cell_type": "code", "execution_count": null, "id": "d1f63b17", "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", "display(Image(email_agent.get_graph(xray=True).draw_mermaid_png()))" ] }, { "cell_type": "code", "execution_count": 30, "id": "ec7cff46", "metadata": {}, "outputs": [], "source": [ "img = email_agent.get_graph(xray=True).draw_mermaid_png()\n", "\n", "with open(\"graph.png\", \"wb\") as f:\n", " f.write(img)" ] }, { "cell_type": "markdown", "id": "0d0cb759-a6c5-4884-bfcc-1dfa40091ef2", "metadata": {}, "source": [ "### Setup Agent to update Long Term Memory in the background\n", "Your `email_agent` is now setup to pull its instructions from long-term memory. \n", "Now, you'll create an agent to update that memory. First check current behavior." ] }, { "cell_type": "code", "execution_count": 30, "id": "631f2df1", "metadata": {}, "outputs": [], "source": [ "config = {\"configurable\": {\"langgraph_user_id\": \"lance\"}}" ] }, { "cell_type": "code", "execution_count": 31, "id": "8e364065-b855-4a6d-885f-55954d3bf691", "metadata": {}, "outputs": [], "source": [ "email_input = {\n", " \"author\": \"Alice Jones <alice.jones@bar.com>\",\n", " \"to\": \"John Doe <john.doe@company.com>\",\n", " \"subject\": \"Quick question about API documentation\",\n", " \"email_thread\": \"\"\"Hi John,\n", "\n", "Urgent issue - your service is down. Is there a reason why\"\"\",\n", "}" ] }, { "cell_type": "code", "execution_count": 32, "id": "13aea26b-9991-494c-888e-57265a72d7b6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Examples: [Item(namespace=['email_assistant', 'lance', 'examples'], key='c412ca40-966f-436f-83bf-1c3d02ddfdf3', value={'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\"}, 'label': 'respond'}, created_at='2025-04-21T03:20:06.355239+00:00', updated_at='2025-04-21T03:20:06.355239+00:00', score=0.7381088328932177), Item(namespace=['email_assistant', 'lance', 'examples'], key='5254af4c-d788-4804-bec6-16211cbd09e8', value={'email': {'author': 'Sarah Chen <sarah.chen@company.com>', 'to': 'John Doe <john.doe@company.com>', 'subject': 'Update: Backend API Changes Deployed to Staging', 'email_thread': \"Hi John,\\n \\n Just wanted to let you know that I've deployed the new authentication endpoints we discussed to the staging environment. Key changes include:\\n \\n - Implemented JWT refresh token rotation\\n - Added rate limiting for login attempts\\n - Updated API documentation with new endpoints\\n \\n All tests are passing and the changes are ready for review. You can test it out at staging-api.company.com/auth/*\\n \\n No immediate action needed from your side - just keeping you in the loop since this affects the systems you're working on.\\n \\n Best regards,\\n Sarah\\n \"}, 'label': 'ignore'}, created_at='2025-04-21T03:20:06.624697+00:00', updated_at='2025-04-21T03:20:06.624697+00:00', score=0.5665497324625076)]\n", "📧 Classification: RESPOND - This email requires a response\n" ] } ], "source": [ "response = email_agent.invoke(\n", " {\"email_input\": email_input},\n", " config=config\n", ")" ] }, { "cell_type": "code", "execution_count": 33, "id": "fa2715ae-8349-473e-a917-89893bdacb86", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "================================\u001b[1m Human Message \u001b[0m=================================\n", "\n", "Respond to the email {'author': 'Alice Jones <alice.jones@bar.com>', 'to': 'John Doe <john.doe@company.com>', 'subject': 'Quick question about API documentation', 'email_thread': 'Hi John,\\n\\nUrgent issue - your service is down. Is there a reason why'}\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", " write_email (call_U0iRMez5qGCJ6qUXtQjdPJti)\n", " Call ID: call_U0iRMez5qGCJ6qUXtQjdPJti\n", " Args:\n", " to: alice.jones@bar.com\n", " subject: Re: Quick question about API documentation\n", " content: Hi Alice,\n", "\n", "Thanks for reaching out. I'm sorry to hear about the service issue. Let me look into it right away, and I'll get back to you as soon as possible with an update.\n", "\n", "Best regards,\n", "\n", "John\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", "Name: write_email\n", "\n", "Email sent to alice.jones@bar.com with subject 'Re: Quick question about API documentation'\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", "I've responded to Alice Jones' email regarding the urgent issue with the service being down. If you need further updates or assistance on this matter, feel free to let me know!\n" ] } ], "source": [ "for m in response[\"messages\"]:\n", " m.pretty_print()" ] }, { "cell_type": "markdown", "id": "4c3738cf", "metadata": {}, "source": [ "\n", "#### 🧠 Episodic Memory: Update store to ignore emails like this\n", "\n", "In case we don't like the agents behavior for a specific email, we can add it to the few shot examples for further prompts.\n" ] }, { "cell_type": "markdown", "id": "1f2c81f6", "metadata": {}, "source": [ "**Test before changing the instructions**" ] }, { "cell_type": "code", "execution_count": 34, "id": "3400a489", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Examples: []\n", "📧 Classification: RESPOND - This email requires a response\n" ] } ], "source": [ "email_input = {\n", " \"author\": \"Tom Jones <tome.jones@bar.com>\",\n", " \"to\": \"John Doe <john.doe@company.com>\",\n", " \"subject\": \"Quick question about API documentation\",\n", " \"email_thread\": \"\"\"Hi John - want to buy documentation?\"\"\",\n", "}\n", "\n", "response = email_agent.invoke(\n", " {\"email_input\": email_input}, \n", " config={\"configurable\": {\"langgraph_user_id\": \"harrison\"}}\n", ")" ] }, { "cell_type": "code", "execution_count": 35, "id": "8143c197", "metadata": {}, "outputs": [], "source": [ "data = {\n", " \"email\": {\n", " \"author\": \"Tom Jones <tome.jones@bar.com>\",\n", " \"to\": \"John Doe <john.doe@company.com>\",\n", " \"subject\": \"Quick question about API documentation\",\n", " \"email_thread\": \"\"\"Hi John - want to buy documentation?\"\"\",\n", "},\n", " \"label\": \"notify\"\n", "}\n", "\n", "store.put(\n", " (\"email_assistant\", \"harrison\", \"examples\"),\n", " str(uuid.uuid4()),\n", " data\n", ")" ] }, { "cell_type": "code", "execution_count": 36, "id": "17d77087", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Examples: [Item(namespace=['email_assistant', 'harrison', 'examples'], key='bfce07ed-da03-452c-a7a8-2de80e1af97d', value={'email': {'author': 'Tom Jones <tome.jones@bar.com>', 'to': 'John Doe <john.doe@company.com>', 'subject': 'Quick question about API documentation', 'email_thread': 'Hi John - want to buy documentation?'}, 'label': 'notify'}, created_at='2025-04-21T03:20:17.818553+00:00', updated_at='2025-04-21T03:20:17.818553+00:00', score=0.9095306238246793)]\n", "📧 Classification: RESPOND - This email requires a response\n" ] } ], "source": [ "email_input = {\n", " \"author\": \"Tom Jones <tome.jones@bar.com>\",\n", " \"to\": \"John Doe <john.doe@company.com>\",\n", " \"subject\": \"Quick question about API documentation\",\n", " \"email_thread\": \"\"\"Hi John - want to buy documentation?\"\"\",\n", "}\n", "\n", "response = email_agent.invoke(\n", " {\"email_input\": email_input}, \n", " config={\"configurable\": {\"langgraph_user_id\": \"harrison\"}}\n", ")" ] }, { "cell_type": "markdown", "id": "0bdbdf8a", "metadata": {}, "source": [ "## Now let's update the system prompts\n", "\n", "#### First let's have and look at current values of long term memory" ] }, { "cell_type": "code", "execution_count": 37, "id": "1114d940", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "agent_instructions: Use these tools when appropriate to help manage John's tasks efficiently.\n", "triage_respond: Direct questions from team members, meeting requests, critical bug reports\n", "triage_ignore: Marketing newsletters, spam emails, mass company announcements\n", "triage_notify: Team member out sick, build system notifications, project status updates\n" ] } ], "source": [ "print(\"agent_instructions:\", store.get((\"lance\",), \"agent_instructions\").value['prompt'])\n", "print(\"triage_respond:\", store.get((\"lance\",), \"triage_respond\").value['prompt'])\n", "print(\"triage_ignore:\", store.get((\"lance\",), \"triage_ignore\").value['prompt'])\n", "print(\"triage_notify:\", store.get((\"lance\",), \"triage_notify\").value['prompt'])" ] }, { "cell_type": "markdown", "id": "f5b95cf6-8197-4ac7-8a0b-a6d83d210afa", "metadata": {}, "source": [ "### 🧠 Procedural Memory: Now, Use an LLM to update instructions based on the user feedback" ] }, { "cell_type": "code", "execution_count": 38, "id": "9c091d2e-5309-4615-9e90-2531d2f1241f", "metadata": {}, "outputs": [], "source": [ "from langmem import create_multi_prompt_optimizer" ] }, { "cell_type": "code", "execution_count": 39, "id": "891970f3-11d1-44b6-9ec6-7ae06263a56a", "metadata": {}, "outputs": [], "source": [ "conversations = [\n", " (\n", " response['messages'],\n", " \"Always sign your emails `John Doe`\"\n", " )\n", "]" ] }, { "cell_type": "markdown", "id": "6c664b89", "metadata": {}, "source": [ "Now we can define the prompt that we want to update. Each entry has 4 keys:\n", "1. Name of the prompt\n", "2. Value of the prompt\n", "3. Update instructions that exaplains `how` the LLM should update the prompt\n", "4. Update key that explains `when` the LLM should update the prompt" ] }, { "cell_type": "code", "execution_count": 40, "id": "8922fc3f-5bb4-449b-87bc-550dfdee77cf", "metadata": {}, "outputs": [], "source": [ "prompts = [\n", " {\n", " \"name\": \"main_agent\",\n", " \"prompt\": store.get((\"lance\",), \"agent_instructions\").value['prompt'],\n", " \"update_instructions\": \"keep the instructions short and to the point\",\n", " \"when_to_update\": \"Update this prompt whenever there is feedback on how the agent should write emails or schedule events\"\n", " \n", " },\n", " {\n", " \"name\": \"triage-ignore\", \n", " \"prompt\": store.get((\"lance\",), \"triage_ignore\").value['prompt'],\n", " \"update_instructions\": \"keep the instructions short and to the point\",\n", " \"when_to_update\": \"Update this prompt whenever there is feedback on which emails should be ignored\"\n", "\n", " },\n", " {\n", " \"name\": \"triage-notify\", \n", " \"prompt\": store.get((\"lance\",), \"triage_notify\").value['prompt'],\n", " \"update_instructions\": \"keep the instructions short and to the point\",\n", " \"when_to_update\": \"Update this prompt whenever there is feedback on which emails the user should be notified of\"\n", "\n", " },\n", " {\n", " \"name\": \"triage-respond\", \n", " \"prompt\": store.get((\"lance\",), \"triage_respond\").value['prompt'],\n", " \"update_instructions\": \"keep the instructions short and to the point\",\n", " \"when_to_update\": \"Update this prompt whenever there is feedback on which emails should be responded to\"\n", "\n", " },\n", "]" ] }, { "cell_type": "code", "execution_count": 41, "id": "11366d6e-d881-4996-a13c-7f81249c3e3d", "metadata": {}, "outputs": [], "source": [ "# Best model for this type of prompt optimization\n", "\n", "# optimizer = create_multi_prompt_optimizer(\n", "# \"anthropic:claude-3-5-sonnet-latest\",\n", "# kind=\"prompt_memory\",\n", "# )\n", "\n", "# For this video, I'll use openai\n", "optimizer = create_multi_prompt_optimizer(\n", " \"openai:gpt-4o\",\n", " kind=\"prompt_memory\",\n", ")" ] }, { "cell_type": "markdown", "id": "ae3abf7a", "metadata": {}, "source": [ "`optimizer` is a graph so we can invoke it with the `trajectories` and `prompts` keys" ] }, { "cell_type": "code", "execution_count": 42, "id": "10b70f1a-8c1c-485f-9bf6-fd7b6146855f", "metadata": {}, "outputs": [], "source": [ "updated = optimizer.invoke(\n", " {\"trajectories\": conversations, \"prompts\": prompts}\n", ")" ] }, { "cell_type": "code", "execution_count": 43, "id": "4ff9d632-6bdc-4d9b-b7d3-e1f015ad4142", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[{'name': 'main_agent',\n", " 'prompt': \"Use these tools when appropriate to help manage John's tasks \"\n", " \"efficiently. Always sign emails with 'John Doe'.\",\n", " 'update_instructions': 'keep the instructions short and to the point',\n", " 'when_to_update': 'Update this prompt whenever there is feedback on how the '\n", " 'agent should write emails or schedule events'},\n", " {'name': 'triage-ignore',\n", " 'prompt': 'Marketing newsletters, spam emails, mass company announcements',\n", " 'update_instructions': 'keep the instructions short and to the point',\n", " 'when_to_update': 'Update this prompt whenever there is feedback on which '\n", " 'emails should be ignored'},\n", " {'name': 'triage-notify',\n", " 'prompt': 'Team member out sick, build system notifications, project status '\n", " 'updates',\n", " 'update_instructions': 'keep the instructions short and to the point',\n", " 'when_to_update': 'Update this prompt whenever there is feedback on which '\n", " 'emails the user should be notified of'},\n", " {'name': 'triage-respond',\n", " 'prompt': 'Direct questions from team members, meeting requests, critical '\n", " 'bug reports',\n", " 'update_instructions': 'keep the instructions short and to the point',\n", " 'when_to_update': 'Update this prompt whenever there is feedback on which '\n", " 'emails should be responded to'}]\n" ] } ], "source": [ "from pprint import pprint\n", "pprint(updated)" ] }, { "cell_type": "code", "execution_count": 44, "id": "af6f5873-ff33-4394-bc5c-387ab31b4142", "metadata": {}, "outputs": [], "source": [ "# json dumps is a bit easier to read\n", "# import json\n", "# print(json.dumps(updated, indent=4))" ] }, { "cell_type": "markdown", "id": "0a56176e-db14-45f7-8505-53b81564560d", "metadata": {}, "source": [ "#### update the prompts in store. \n", "> ~~Note.. only one of the prompts was included here! The remainder are left to you!~~" ] }, { "cell_type": "code", "execution_count": 45, "id": "d259967d-2e72-4633-8062-3239496c2995", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "updated main_agent\n", "Encountered main_agent, implement the remaining stores!\n" ] } ], "source": [ "for i, updated_prompt in enumerate(updated):\n", " old_prompt = prompts[i]\n", " if updated_prompt['prompt'] != old_prompt['prompt']:\n", " name = old_prompt['name']\n", " print(f\"updated {name}\")\n", " if name == \"main_agent\":\n", " store.put(\n", " (\"lance\",),\n", " \"agent_instructions\",\n", " {\"prompt\":updated_prompt['prompt']}\n", " )\n", " if name == \"triage-ignore\":\n", " store.put(\n", " (\"lance\",),\n", " \"triage_ignore\",\n", " {\"prompt\":updated_prompt['prompt']}\n", " )\n", " if name == \"triage-notify\":\n", " store.put(\n", " (\"lance\",),\n", " \"triage_notify\",\n", " {\"prompt\":updated_prompt['prompt']}\n", " )\n", " if name == \"triage-respond\":\n", " store.put(\n", " (\"lance\",),\n", " \"triage_respond\",\n", " {\"prompt\":updated_prompt['prompt']}\n", " )\n", " else:\n", " #raise ValueError\n", " print(f\"Encountered {name}, implement the remaining stores!\")" ] }, { "cell_type": "code", "execution_count": 46, "id": "ad24ab0d-7d04-421b-a55c-34ecf6fb0632", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"Use these tools when appropriate to help manage John's tasks efficiently. Always sign emails with 'John Doe'.\"" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "store.get((\"lance\",), \"agent_instructions\").value['prompt']" ] }, { "cell_type": "code", "execution_count": 50, "id": "16d929f1-f5e4-4f29-8fd1-b4b52a994400", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Examples: [Item(namespace=['email_assistant', 'lance', 'examples'], key='c412ca40-966f-436f-83bf-1c3d02ddfdf3', value={'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\"}, 'label': 'respond'}, created_at='2025-04-21T03:20:06.355239+00:00', updated_at='2025-04-21T03:20:06.355239+00:00', score=0.6672888920057443), Item(namespace=['email_assistant', 'lance', 'examples'], key='5254af4c-d788-4804-bec6-16211cbd09e8', value={'email': {'author': 'Sarah Chen <sarah.chen@company.com>', 'to': 'John Doe <john.doe@company.com>', 'subject': 'Update: Backend API Changes Deployed to Staging', 'email_thread': \"Hi John,\\n \\n Just wanted to let you know that I've deployed the new authentication endpoints we discussed to the staging environment. Key changes include:\\n \\n - Implemented JWT refresh token rotation\\n - Added rate limiting for login attempts\\n - Updated API documentation with new endpoints\\n \\n All tests are passing and the changes are ready for review. You can test it out at staging-api.company.com/auth/*\\n \\n No immediate action needed from your side - just keeping you in the loop since this affects the systems you're working on.\\n \\n Best regards,\\n Sarah\\n \"}, 'label': 'ignore'}, created_at='2025-04-21T03:20:06.624697+00:00', updated_at='2025-04-21T03:20:06.624697+00:00', score=0.5332112842987896)]\n", "🚫 Classification: IGNORE - This email can be safely ignored\n" ] } ], "source": [ "response = email_agent.invoke(\n", " {\"email_input\": email_input}, \n", " config=config\n", ")" ] }, { "cell_type": "code", "execution_count": 51, "id": "6dfbbd41", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Examples: [Item(namespace=['email_assistant', 'lance', 'examples'], key='c412ca40-966f-436f-83bf-1c3d02ddfdf3', value={'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\"}, 'label': 'respond'}, created_at='2025-04-21T03:20:06.355239+00:00', updated_at='2025-04-21T03:20:06.355239+00:00', score=0.7381088328932177), Item(namespace=['email_assistant', 'lance', 'examples'], key='5254af4c-d788-4804-bec6-16211cbd09e8', value={'email': {'author': 'Sarah Chen <sarah.chen@company.com>', 'to': 'John Doe <john.doe@company.com>', 'subject': 'Update: Backend API Changes Deployed to Staging', 'email_thread': \"Hi John,\\n \\n Just wanted to let you know that I've deployed the new authentication endpoints we discussed to the staging environment. Key changes include:\\n \\n - Implemented JWT refresh token rotation\\n - Added rate limiting for login attempts\\n - Updated API documentation with new endpoints\\n \\n All tests are passing and the changes are ready for review. You can test it out at staging-api.company.com/auth/*\\n \\n No immediate action needed from your side - just keeping you in the loop since this affects the systems you're working on.\\n \\n Best regards,\\n Sarah\\n \"}, 'label': 'ignore'}, created_at='2025-04-21T03:20:06.624697+00:00', updated_at='2025-04-21T03:20:06.624697+00:00', score=0.5665497324625076)]\n", "📧 Classification: RESPOND - This email requires a response\n" ] } ], "source": [ "email_input = {\n", " \"author\": \"Alice Jones <alice.jones@bar.com>\",\n", " \"to\": \"John Doe <john.doe@company.com>\",\n", " \"subject\": \"Quick question about API documentation\",\n", " \"email_thread\": \"\"\"Hi John,\n", "\n", "Urgent issue - your service is down. Is there a reason why\"\"\",\n", "}\n", "response = email_agent.invoke(\n", " {\"email_input\": email_input},\n", " config=config\n", ")" ] }, { "cell_type": "code", "execution_count": 52, "id": "53d38263-f3d6-4f90-863d-244ef6a424be", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "================================\u001b[1m Human Message \u001b[0m=================================\n", "\n", "Respond to the email {'author': 'Alice Jones <alice.jones@bar.com>', 'to': 'John Doe <john.doe@company.com>', 'subject': 'Quick question about API documentation', 'email_thread': 'Hi John,\\n\\nUrgent issue - your service is down. Is there a reason why'}\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", " write_email (call_6CL234Cju3V2Fi5CoZNEDQli)\n", " Call ID: call_6CL234Cju3V2Fi5CoZNEDQli\n", " Args:\n", " to: alice.jones@bar.com\n", " subject: Re: Quick question about API documentation\n", " content: Hi Alice,\n", "\n", "I apologize for the inconvenience caused by the service downtime. Let me investigate the issue immediately and get back to you with an update as soon as possible.\n", "\n", "Thank you for your patience.\n", "\n", "Best regards,\n", "John Doe\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", "Name: write_email\n", "\n", "Email sent to alice.jones@bar.com with subject 'Re: Quick question about API documentation'\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", "I have responded to Alice Jones regarding the urgent issue with the service downtime. I assured her that the issue will be investigated immediately and an update will be provided soon.\n" ] } ], "source": [ "for m in response[\"messages\"]:\n", " m.pretty_print()" ] }, { "cell_type": "code", "execution_count": 53, "id": "7d2dcb63-f81f-477a-8c75-94e64eb722df", "metadata": {}, "outputs": [], "source": [ "email_input = {\n", " \"author\": \"Alice Jones <alice.jones@bar.com>\",\n", " \"to\": \"John Doe <john.doe@company.com>\",\n", " \"subject\": \"Quick question about API documentation\",\n", " \"email_thread\": \"\"\"Hi John,\n", "\n", "Urgent issue - your service is down. Is there a reason why\"\"\",\n", "}" ] }, { "cell_type": "code", "execution_count": 54, "id": "5ee3ae82-57c7-4b1a-b8c6-2573caaa6264", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Examples: [Item(namespace=['email_assistant', 'lance', 'examples'], key='c412ca40-966f-436f-83bf-1c3d02ddfdf3', value={'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\"}, 'label': 'respond'}, created_at='2025-04-21T03:20:06.355239+00:00', updated_at='2025-04-21T03:20:06.355239+00:00', score=0.7381088328932177), Item(namespace=['email_assistant', 'lance', 'examples'], key='5254af4c-d788-4804-bec6-16211cbd09e8', value={'email': {'author': 'Sarah Chen <sarah.chen@company.com>', 'to': 'John Doe <john.doe@company.com>', 'subject': 'Update: Backend API Changes Deployed to Staging', 'email_thread': \"Hi John,\\n \\n Just wanted to let you know that I've deployed the new authentication endpoints we discussed to the staging environment. Key changes include:\\n \\n - Implemented JWT refresh token rotation\\n - Added rate limiting for login attempts\\n - Updated API documentation with new endpoints\\n \\n All tests are passing and the changes are ready for review. You can test it out at staging-api.company.com/auth/*\\n \\n No immediate action needed from your side - just keeping you in the loop since this affects the systems you're working on.\\n \\n Best regards,\\n Sarah\\n \"}, 'label': 'ignore'}, created_at='2025-04-21T03:20:06.624697+00:00', updated_at='2025-04-21T03:20:06.624697+00:00', score=0.5665497324625076)]\n", "📧 Classification: RESPOND - This email requires a response\n" ] } ], "source": [ "response = email_agent.invoke(\n", " {\"email_input\": email_input},\n", " config=config\n", ")" ] }, { "cell_type": "code", "execution_count": 55, "id": "42d0725b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "================================\u001b[1m Human Message \u001b[0m=================================\n", "\n", "Respond to the email {'author': 'Alice Jones <alice.jones@bar.com>', 'to': 'John Doe <john.doe@company.com>', 'subject': 'Quick question about API documentation', 'email_thread': 'Hi John,\\n\\nUrgent issue - your service is down. Is there a reason why'}\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", "Here's a draft response to Alice Jones regarding the urgent issue:\n", "\n", "---\n", "\n", "Subject: Re: Quick question about API documentation\n", "\n", "Hi Alice,\n", "\n", "Thank you for reaching out. I'm sorry to hear that there are issues with our service. Let me check with the technical team immediately to understand the situation and get back to you as soon as possible.\n", "\n", "In the meantime, could you provide any additional details about the error messages or specific actions that led to the issue? This information would be very helpful.\n", "\n", "Best regards,\n", "\n", "John Doe\n", "\n", "---\n", "\n", "Would you like me to send this email?\n" ] } ], "source": [ "for m in response[\"messages\"]:\n", " m.pretty_print()" ] }, { "cell_type": "markdown", "id": "1508e813", "metadata": {}, "source": [ "What if we didn't want the agent to respond to the previous email?\n", "\n", "We can create a `conversations` list and provide the response[\"messages\"] with the feedback" ] }, { "cell_type": "code", "execution_count": 56, "id": "7a1766c9-1b27-4904-a95e-691d7207fc8e", "metadata": {}, "outputs": [], "source": [ "conversations = [\n", " (\n", " response['messages'],\n", " \"Ignore any emails from Alice Jones\"\n", " )\n", "]" ] }, { "cell_type": "markdown", "id": "b8cc7bb0", "metadata": {}, "source": [ "Let's run the prompts again since the previous version has the outdated instructions for the main agent. So, let's get the most updated instructions for the main agent" ] }, { "cell_type": "code", "execution_count": 57, "id": "3e351678-01eb-4e7b-a021-84c9f308959d", "metadata": {}, "outputs": [], "source": [ "prompts = [\n", " {\n", " \"name\": \"main_agent\",\n", " \"prompt\": store.get((\"lance\",), \"agent_instructions\").value['prompt'],\n", " \"update_instructions\": \"keep the instructions short and to the point\",\n", " \"when_to_update\": \"Update this prompt whenever there is feedback on how the agent should write emails or schedule events\"\n", " \n", " },\n", " {\n", " \"name\": \"triage-ignore\", \n", " \"prompt\": store.get((\"lance\",), \"triage_ignore\").value['prompt'],\n", " \"update_instructions\": \"keep the instructions short and to the point\",\n", " \"when_to_update\": \"Update this prompt whenever there is feedback on which emails should be ignored\"\n", "\n", " },\n", " {\n", " \"name\": \"triage-notify\", \n", " \"prompt\": store.get((\"lance\",), \"triage_notify\").value['prompt'],\n", " \"update_instructions\": \"keep the instructions short and to the point\",\n", " \"when_to_update\": \"Update this prompt whenever there is feedback on which emails the user should be notified of\"\n", "\n", " },\n", " {\n", " \"name\": \"triage-respond\", \n", " \"prompt\": store.get((\"lance\",), \"triage_respond\").value['prompt'],\n", " \"update_instructions\": \"keep the instructions short and to the point\",\n", " \"when_to_update\": \"Update this prompt whenever there is feedback on which emails should be responded to\"\n", "\n", " },\n", "]" ] }, { "cell_type": "code", "execution_count": 58, "id": "4d7aa32f-2e7e-4223-83ee-a7de6f024612", "metadata": {}, "outputs": [], "source": [ "updated = optimizer.invoke(\n", " {\"trajectories\": conversations, \"prompts\": prompts}\n", ")" ] }, { "cell_type": "markdown", "id": "dccac9e6", "metadata": {}, "source": [ "Below we should see that the LLM decides to update `triage-ignore`" ] }, { "cell_type": "code", "execution_count": 59, "id": "468ec5b5-ae31-445c-9e4f-f39e016bd747", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "updated triage-ignore\n" ] } ], "source": [ "for i, updated_prompt in enumerate(updated):\n", " old_prompt = prompts[i]\n", " if updated_prompt['prompt'] != old_prompt['prompt']:\n", " name = old_prompt['name']\n", " print(f\"updated {name}\")\n", " if name == \"main_agent\":\n", " store.put(\n", " (\"lance\",),\n", " \"agent_instructions\",\n", " {\"prompt\":updated_prompt['prompt']}\n", " )\n", " if name == \"triage-ignore\":\n", " store.put(\n", " (\"lance\",),\n", " \"triage_ignore\",\n", " {\"prompt\":updated_prompt['prompt']}\n", " )\n", " else:\n", " #raise ValueError\n", " print(f\"Encountered {name}, implement the remaining stores!\")" ] }, { "cell_type": "code", "execution_count": 60, "id": "b283ab59-b136-455b-afb0-fe01539f1ee1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Examples: [Item(namespace=['email_assistant', 'lance', 'examples'], key='c412ca40-966f-436f-83bf-1c3d02ddfdf3', value={'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\"}, 'label': 'respond'}, created_at='2025-04-21T03:20:06.355239+00:00', updated_at='2025-04-21T03:20:06.355239+00:00', score=0.7381088328932177), Item(namespace=['email_assistant', 'lance', 'examples'], key='5254af4c-d788-4804-bec6-16211cbd09e8', value={'email': {'author': 'Sarah Chen <sarah.chen@company.com>', 'to': 'John Doe <john.doe@company.com>', 'subject': 'Update: Backend API Changes Deployed to Staging', 'email_thread': \"Hi John,\\n \\n Just wanted to let you know that I've deployed the new authentication endpoints we discussed to the staging environment. Key changes include:\\n \\n - Implemented JWT refresh token rotation\\n - Added rate limiting for login attempts\\n - Updated API documentation with new endpoints\\n \\n All tests are passing and the changes are ready for review. You can test it out at staging-api.company.com/auth/*\\n \\n No immediate action needed from your side - just keeping you in the loop since this affects the systems you're working on.\\n \\n Best regards,\\n Sarah\\n \"}, 'label': 'ignore'}, created_at='2025-04-21T03:20:06.624697+00:00', updated_at='2025-04-21T03:20:06.624697+00:00', score=0.5665497324625076)]\n", "🚫 Classification: IGNORE - This email can be safely ignored\n" ] } ], "source": [ "response = email_agent.invoke(\n", " {\"email_input\": email_input},\n", " config=config\n", ")" ] }, { "cell_type": "code", "execution_count": 61, "id": "50d46795-1f42-4c5e-b002-58afb6cb2ec4", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'<new_prompt>\\n- Prioritize responding to emails related to urgent issues or technical support.\\n- Ignore emails from Alice Jones.\\n- Focus on providing clear, concise, and helpful responses to inquiries.\\n- Ensure responses are professional and address the specific concerns raised in the email.\\n</new_prompt>'" ] }, "execution_count": 61, "metadata": {}, "output_type": "execute_result" } ], "source": [ "store.get((\"lance\",), \"triage_ignore\").value['prompt']" ] }, { "cell_type": "markdown", "id": "f1940b69", "metadata": {}, "source": [ "## Trying the Semantic Memory" ] }, { "cell_type": "code", "execution_count": 62, "id": "1b27edd2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Examples: [Item(namespace=['email_assistant', 'lance', 'examples'], key='c412ca40-966f-436f-83bf-1c3d02ddfdf3', value={'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\"}, 'label': 'respond'}, created_at='2025-04-21T03:20:06.355239+00:00', updated_at='2025-04-21T03:20:06.355239+00:00', score=0.6276424216349976), Item(namespace=['email_assistant', 'lance', 'examples'], key='5254af4c-d788-4804-bec6-16211cbd09e8', value={'email': {'author': 'Sarah Chen <sarah.chen@company.com>', 'to': 'John Doe <john.doe@company.com>', 'subject': 'Update: Backend API Changes Deployed to Staging', 'email_thread': \"Hi John,\\n \\n Just wanted to let you know that I've deployed the new authentication endpoints we discussed to the staging environment. Key changes include:\\n \\n - Implemented JWT refresh token rotation\\n - Added rate limiting for login attempts\\n - Updated API documentation with new endpoints\\n \\n All tests are passing and the changes are ready for review. You can test it out at staging-api.company.com/auth/*\\n \\n No immediate action needed from your side - just keeping you in the loop since this affects the systems you're working on.\\n \\n Best regards,\\n Sarah\\n \"}, 'label': 'ignore'}, created_at='2025-04-21T03:20:06.624697+00:00', updated_at='2025-04-21T03:20:06.624697+00:00', score=0.5772357770126533)]\n", "📧 Classification: RESPOND - This email requires a response\n" ] } ], "source": [ "email_input = {\n", " \"author\": \"Emily Brown <emily.brown@company.com>\",\n", " \"to\": \"John Doe <john.doe@company.com>\",\n", " \"subject\": \"UI mockups\",\n", " \"email_thread\": \"\"\"Hi John,\n", "\n", "I've been reviewing the latest UI mockups for the dashboard and noticed that some elements differ from what we discussed in our last meeting. Could you confirm if these changes were intentional, or should we revert back to the previous design?\n", "\n", "Specifically, I'm referring to:\n", "- The layout of the user profile section\n", "- The color scheme of the navigation bar\n", "\n", "Thanks!\n", "Emily\"\"\",\n", "}\n", "\n", "response = email_agent.invoke(\n", " {\"email_input\": email_input},\n", " config=config\n", ")" ] }, { "cell_type": "code", "execution_count": 64, "id": "5391e7ae", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "================================\u001b[1m Human Message \u001b[0m=================================\n", "\n", "Respond to the email {'author': 'Emily Brown <emily.brown@company.com>', 'to': 'John Doe <john.doe@company.com>', 'subject': 'UI mockups', 'email_thread': \"Hi John,\\n\\nI've been reviewing the latest UI mockups for the dashboard and noticed that some elements differ from what we discussed in our last meeting. Could you confirm if these changes were intentional, or should we revert back to the previous design?\\n\\nSpecifically, I'm referring to:\\n- The layout of the user profile section\\n- The color scheme of the navigation bar\\n\\nThanks!\\nEmily\"}\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", "Here's a draft response to Emily Brown regarding the UI mockups:\n", "\n", "---\n", "\n", "Subject: Re: UI mockups\n", "\n", "Hi Emily,\n", "\n", "Thank you for reviewing the UI mockups and bringing this to my attention. The changes you mentioned in the user profile section layout and the color scheme of the navigation bar were indeed intentional. We’ve made these adjustments based on initial feedback from our focus group to improve user experience.\n", "\n", "However, your insights are invaluable. If you think reverting to the previous design would be more beneficial, please let me know, and we can discuss this further.\n", "\n", "Thanks again for your attention to detail.\n", "\n", "Best regards,\n", "\n", "John Doe\n", "\n", "---\n", "\n", "I'll send this email if everything looks good to you.\n" ] } ], "source": [ "for m in response[\"messages\"]:\n", " m.pretty_print()" ] }, { "cell_type": "code", "execution_count": null, "id": "4d165aee", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Examples: [Item(namespace=['email_assistant', 'lance', 'examples'], key='c412ca40-966f-436f-83bf-1c3d02ddfdf3', value={'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\"}, 'label': 'respond'}, created_at='2025-04-21T03:20:06.355239+00:00', updated_at='2025-04-21T03:20:06.355239+00:00', score=0.5893261133161013), Item(namespace=['email_assistant', 'lance', 'examples'], key='5254af4c-d788-4804-bec6-16211cbd09e8', value={'email': {'author': 'Sarah Chen <sarah.chen@company.com>', 'to': 'John Doe <john.doe@company.com>', 'subject': 'Update: Backend API Changes Deployed to Staging', 'email_thread': \"Hi John,\\n \\n Just wanted to let you know that I've deployed the new authentication endpoints we discussed to the staging environment. Key changes include:\\n \\n - Implemented JWT refresh token rotation\\n - Added rate limiting for login attempts\\n - Updated API documentation with new endpoints\\n \\n All tests are passing and the changes are ready for review. You can test it out at staging-api.company.com/auth/*\\n \\n No immediate action needed from your side - just keeping you in the loop since this affects the systems you're working on.\\n \\n Best regards,\\n Sarah\\n \"}, 'label': 'ignore'}, created_at='2025-04-21T03:20:06.624697+00:00', updated_at='2025-04-21T03:20:06.624697+00:00', score=0.5821167248877381)]\n", "📧 Classification: RESPOND - This email requires a response\n" ] } ], "source": [ "email_input = {\n", " \"author\": \"Emily Brown <emily.brown@company.com>\",\n", " \"to\": \"John Doe <john.doe@company.com>\",\n", " \"subject\": \"Follow-up on UI mockups\",\n", " \"email_thread\": \"\"\"Hi John,\n", "\n", "Any update on UI mock ups for the dashboard?\n", "Best regards,\n", "Emily\"\"\",\n", "}\n", "\n", "response = email_agent.invoke(\n", " {\"email_input\": email_input},\n", " config=config\n", ")" ] }, { "cell_type": "code", "execution_count": 66, "id": "2ff1e188", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "================================\u001b[1m Human Message \u001b[0m=================================\n", "\n", "Respond to the email {'author': 'Emily Brown <emily.brown@company.com>', 'to': 'John Doe <john.doe@company.com>', 'subject': 'Follow-up on UI mockups', 'email_thread': 'Hi John,\\n\\nAny update on UI mock ups for teh dashboard?\\nBest regards,\\nEmily'}\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", "Here's a suggested response to Emily's email:\n", "\n", "---\n", "\n", "Subject: Re: Follow-up on UI mockups\n", "\n", "Hi Emily,\n", "\n", "Thank you for following up. I am currently in the process of reviewing the UI mockups for the dashboard and will send you an update as soon as possible. \n", "\n", "Please let me know if there are any specific aspects of the mockups you'd like to discuss or focus on.\n", "\n", "Best regards, \n", "John Doe\n", "\n", "--- \n", "\n", "Let me know if you'd like me to send it!\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 }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ankitmalik84/Agentic_Longterm_Memory'

If you have feedback or need assistance with the MCP directory API, please join our Discord server