fxn api w agent

This commit is contained in:
Lance Martin
2025-01-28 06:31:27 -08:00
parent 0a861815b7
commit f239b39060
@@ -0,0 +1,941 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"! pip install langchain_core langchain-anthropic langgraph "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os, getpass\n",
"\n",
"\n",
"def _set_env(var: str):\n",
" if not os.environ.get(var):\n",
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
"\n",
"\n",
"_set_env(\"ANTHROPIC_API_KEY\")"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"# LLM\n",
"from langchain_anthropic import ChatAnthropic\n",
"llm = ChatAnthropic(model=\"claude-3-5-sonnet-latest\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Vanilla Agent\n",
"\n",
"* No orchestration framework \n",
"* Optionally, use LangGraph to bind tools and specify tools "
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"from langchain_core.tools import tool\n",
"from langchain_openai import ChatOpenAI\n",
"\n",
"# LLM\n",
"llm = ChatOpenAI(model=\"gpt-4o\")\n",
"\n",
"# Define tools\n",
"@tool\n",
"def multiply(a: int, b: int) -> int:\n",
" \"\"\"Multiply a and b.\n",
"\n",
" Args:\n",
" a: first int\n",
" b: second int\n",
" \"\"\"\n",
" return a * b\n",
"\n",
"\n",
"@tool\n",
"def add(a: int, b: int) -> int:\n",
" \"\"\"Adds a and b.\n",
"\n",
" Args:\n",
" a: first int\n",
" b: second int\n",
" \"\"\"\n",
" return a + b\n",
"\n",
"\n",
"@tool\n",
"def divide(a: int, b: int) -> float:\n",
" \"\"\"Divide a and b.\n",
"\n",
" Args:\n",
" a: first int\n",
" b: second int\n",
" \"\"\"\n",
" return a / b\n",
"\n",
"# Augment the LLM with tools\n",
"tools = [add, multiply, divide]\n",
"tools_by_name = {tool.name: tool for tool in tools}\n",
"llm_with_tools = llm.bind_tools(tools)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"================================\u001b[1m Human Message \u001b[0m=================================\n",
"\n",
"Add 3 and 4.\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"Tool Calls:\n",
" add (call_N1trAdi9h9vK0IW3vsvCRaaA)\n",
" Call ID: call_N1trAdi9h9vK0IW3vsvCRaaA\n",
" Args:\n",
" a: 3\n",
" b: 4\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"Name: add\n",
"\n",
"7\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"The sum of 3 and 4 is 7.\n"
]
}
],
"source": [
"from langgraph.graph import add_messages\n",
"from langchain_core.messages import (\n",
" SystemMessage,\n",
" HumanMessage,\n",
" BaseMessage,\n",
" ToolCall,\n",
")\n",
"\n",
"def call_llm(messages: list[BaseMessage]):\n",
" \"\"\"LLM decides whether to call a tool or not\"\"\"\n",
" return llm_with_tools.invoke(\n",
" [\n",
" SystemMessage(\n",
" content=\"You are a helpful assistant tasked with performing arithmetic on a set of inputs.\"\n",
" )\n",
" ]\n",
" + messages\n",
" )\n",
"\n",
"def call_tool(tool_call: ToolCall):\n",
" \"\"\"Performs the tool call\"\"\"\n",
"\n",
" tool = tools_by_name[tool_call[\"name\"]]\n",
" return tool.invoke(tool_call)\n",
"\n",
"def agent(messages: list[BaseMessage]):\n",
" \"\"\" Tool calling agent \"\"\"\n",
" llm_response = call_llm(messages)\n",
"\n",
" while True:\n",
" if not llm_response.tool_calls:\n",
" break\n",
"\n",
" # Execute tools\n",
" tool_results = [\n",
" call_tool(tool_call) for tool_call in llm_response.tool_calls\n",
" ]\n",
" messages = add_messages(messages, [llm_response, *tool_results])\n",
" llm_response = call_llm(messages)\n",
"\n",
" messages = add_messages(messages, llm_response)\n",
" return messages\n",
"\n",
"# Stream\n",
"messages = agent([HumanMessage(content=\"Add 3 and 4.\")])\n",
"for m in messages:\n",
" m.pretty_print()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Agent with short-term memory\n",
"\n",
"* LangGraph persistence layer \n",
"* `@entrypoint` decorator indicates the start of a workflow. "
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"================================\u001b[1m Human Message \u001b[0m=================================\n",
"\n",
"Add 3 and 4.\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"Tool Calls:\n",
" add (call_wVN0NiCHHueHRdaDGFEt1hFh)\n",
" Call ID: call_wVN0NiCHHueHRdaDGFEt1hFh\n",
" Args:\n",
" a: 3\n",
" b: 4\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"Name: add\n",
"\n",
"7\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"The result of adding 3 and 4 is 7.\n"
]
}
],
"source": [
"import uuid\n",
"from langgraph.func import entrypoint # New \n",
"from langgraph.checkpoint.memory import MemorySaver # New \n",
"\n",
"def call_llm(messages: list[BaseMessage]):\n",
" \"\"\"LLM decides whether to call a tool or not\"\"\"\n",
" return llm_with_tools.invoke(\n",
" [\n",
" SystemMessage(\n",
" content=\"You are a helpful assistant tasked with performing arithmetic on a set of inputs.\"\n",
" )\n",
" ]\n",
" + messages\n",
" )\n",
"\n",
"def call_tool(tool_call: ToolCall):\n",
" \"\"\"Performs the tool call\"\"\"\n",
"\n",
" tool = tools_by_name[tool_call[\"name\"]]\n",
" return tool.invoke(tool_call)\n",
"\n",
"@entrypoint(checkpointer=MemorySaver()) # New \n",
"def agent(messages: list[BaseMessage], previous: list[BaseMessage]): # New \n",
" \"\"\" Tool calling agent \"\"\"\n",
"\n",
" # Add previous messages from short-term memory to the current messages\n",
" if previous is not None:\n",
" messages = add_messages(previous, messages)\n",
" \n",
" # Call the LLM\n",
" llm_response = call_llm(messages)\n",
"\n",
" while True:\n",
" if not llm_response.tool_calls:\n",
" break\n",
"\n",
" # Execute tools\n",
" tool_results = [\n",
" call_tool(tool_call) for tool_call in llm_response.tool_calls\n",
" ]\n",
" messages = add_messages(messages, [llm_response, *tool_results])\n",
" llm_response = call_llm(messages)\n",
"\n",
" messages = add_messages(messages, llm_response)\n",
" return messages\n",
"\n",
"# Thread ID\n",
"thread_id = str(uuid.uuid4())\n",
"\n",
"# Config\n",
"config = {\"configurable\": {\"thread_id\": thread_id}}\n",
"\n",
"# Run with checkpointer to persist state in memory\n",
"messages = agent.invoke([HumanMessage(content=\"Add 3 and 4.\")], config)\n",
"for m in messages:\n",
" m.pretty_print()"
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"================================\u001b[1m Human Message \u001b[0m=================================\n",
"\n",
"Add 3 and 4.\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"Tool Calls:\n",
" add (call_FisH9R1uplO9Nyx7fzWDY6uE)\n",
" Call ID: call_FisH9R1uplO9Nyx7fzWDY6uE\n",
" Args:\n",
" a: 3\n",
" b: 4\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"Name: add\n",
"\n",
"7\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"The sum of 3 and 4 is 7.\n"
]
}
],
"source": [
"# Checkpoint state\n",
"agent_state = agent.get_state(config)\n",
"for m in agent_state.values:\n",
" m.pretty_print()"
]
},
{
"cell_type": "code",
"execution_count": 58,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"================================\u001b[1m Human Message \u001b[0m=================================\n",
"\n",
"Add 3 and 4.\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"Tool Calls:\n",
" add (call_FisH9R1uplO9Nyx7fzWDY6uE)\n",
" Call ID: call_FisH9R1uplO9Nyx7fzWDY6uE\n",
" Args:\n",
" a: 3\n",
" b: 4\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"Name: add\n",
"\n",
"7\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"The sum of 3 and 4 is 7.\n",
"================================\u001b[1m Human Message \u001b[0m=================================\n",
"\n",
"Take the result and multiply it by 2.\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"Tool Calls:\n",
" multiply (call_Q7TTDpimEZf0QC6klEx2qTRJ)\n",
" Call ID: call_Q7TTDpimEZf0QC6klEx2qTRJ\n",
" Args:\n",
" a: 7\n",
" b: 2\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"Name: multiply\n",
"\n",
"14\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"The result of multiplying 7 by 2 is 14.\n"
]
}
],
"source": [
"# Continue with the same thread\n",
"messages = agent.invoke([HumanMessage(content=\"Take the result and multiply it by 2.\")], config)\n",
"for m in messages:\n",
" m.pretty_print()"
]
},
{
"cell_type": "code",
"execution_count": 60,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"The result of multiplying 42 by 3 is 126.\n"
]
}
],
"source": [
"# Continue with the same thread\n",
"for item in agent.stream([HumanMessage(content=\"Take the result and multiply it by 3.\")], config, stream_mode=\"values\"):\n",
" item[-1].pretty_print()"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"================================\u001b[1m Human Message \u001b[0m=================================\n",
"\n",
"Add 3 and 4.\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"Tool Calls:\n",
" add (call_5Ff1Bj5S4TYSYAoW1TpSFeyA)\n",
" Call ID: call_5Ff1Bj5S4TYSYAoW1TpSFeyA\n",
" Args:\n",
" a: 3\n",
" b: 4\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"Name: add\n",
"\n",
"7\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"The sum of 3 and 4 is 7.\n",
"================================\u001b[1m Human Message \u001b[0m=================================\n",
"\n",
"Take the result and multiply it by 2.\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"Tool Calls:\n",
" multiply (call_kk8W7CO9hAZgYwLVaISHGvKs)\n",
" Call ID: call_kk8W7CO9hAZgYwLVaISHGvKs\n",
" Args:\n",
" a: 7\n",
" b: 2\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"Name: multiply\n",
"\n",
"14\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"The result of multiplying 7 by 2 is 14.\n",
"================================\u001b[1m Human Message \u001b[0m=================================\n",
"\n",
"Take the result and multiply it by 3.\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"Tool Calls:\n",
" multiply (call_38ckbEtvlXDKAZLWMoTULVQs)\n",
" Call ID: call_38ckbEtvlXDKAZLWMoTULVQs\n",
" Args:\n",
" a: 14\n",
" b: 3\n",
"=================================\u001b[1m Tool Message \u001b[0m=================================\n",
"Name: multiply\n",
"\n",
"42\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"Multiplying 14 by 3 gives you 42.\n"
]
}
],
"source": [
"# Checkpoint state\n",
"agent_state = agent.get_state(config)\n",
"for m in agent_state.values:\n",
" m.pretty_print()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Agent with HITL\n",
"\n",
"* Add interrupt to the workflow to allow for HITL"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'tool_call': {'name': 'add', 'args': {'a': 3, 'b': 4}, 'id': 'call_Np3qpF1w2n6VHgIEXNvw7duZ', 'type': 'tool_call'}, 'action': 'Please approve/reject the tool call'}\n"
]
}
],
"source": [
"from langgraph.types import interrupt\n",
"\n",
"def call_llm(messages: list[BaseMessage]):\n",
" \"\"\"LLM decides whether to call a tool or not\"\"\"\n",
" return llm_with_tools.invoke(\n",
" [\n",
" SystemMessage(\n",
" content=\"You are a helpful assistant tasked with performing arithmetic on a set of inputs.\"\n",
" )\n",
" ]\n",
" + messages\n",
" )\n",
"\n",
"def call_tool(tool_call: ToolCall):\n",
" \"\"\"Performs the tool call\"\"\"\n",
"\n",
" # Interrupt the workflow to get a review from a human.\n",
" is_approved = interrupt({ # New \n",
" # Any json-serializable payload provided to interrupt as argument.\n",
" # It will be surfaced on the client side as an Interrupt when streaming data\n",
" # from the workflow.\n",
" \"tool_call\": tool_call, # The tool call we want reviewed.\n",
" # We can add any additional information that we need.\n",
" # For example, introduce a key called \"action\" with some instructions.\n",
" \"action\": \"Please approve/reject the tool call\",\n",
" })\n",
" \n",
" if is_approved:\n",
" tool = tools_by_name[tool_call[\"name\"]]\n",
" return tool.invoke(tool_call)\n",
" else:\n",
" return \"Tool call rejected\"\n",
"\n",
"@entrypoint(checkpointer=MemorySaver()) \n",
"def agent(messages: list[BaseMessage], previous: list[BaseMessage]): \n",
" \"\"\" Tool calling agent \"\"\"\n",
"\n",
" # Add previous messages from short-term memory to the current messages\n",
" if previous is not None:\n",
" messages = add_messages(previous, messages)\n",
" \n",
" # Call the LLM\n",
" llm_response = call_llm(messages)\n",
"\n",
" while True:\n",
" if not llm_response.tool_calls:\n",
" break\n",
"\n",
" # Execute tools\n",
" tool_results = [\n",
" call_tool(tool_call) for tool_call in llm_response.tool_calls\n",
" ]\n",
" messages = add_messages(messages, [llm_response, *tool_results])\n",
" llm_response = call_llm(messages)\n",
"\n",
" messages = add_messages(messages, llm_response)\n",
" return messages\n",
"\n",
"# Thread ID\n",
"thread_id = str(uuid.uuid4())\n",
"\n",
"# Config\n",
"config = {\"configurable\": {\"thread_id\": thread_id}}\n",
"\n",
"# Run until the interrupt \n",
"for item in agent.stream([HumanMessage(content=\"Add 3 and 4.\")], config, stream_mode=\"updates\"):\n",
" print(item['__interrupt__'][0].value)"
]
},
{
"cell_type": "code",
"execution_count": 83,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"The sum of 3 and 4 is 7.\n"
]
}
],
"source": [
"from langgraph.types import Command\n",
"for item in agent.stream(Command(resume=True), config, stream_mode=\"updates\"):\n",
" item['agent'][-1].pretty_print()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Agent with HITL and Long-term memory\n",
"\n",
"* Add interrupt to the workflow to allow for HITL\n",
"* Add tool for [long-term memory](https://langchain-ai.github.io/langgraph/concepts/memory/#long-term-memory)"
]
},
{
"cell_type": "code",
"execution_count": 164,
"metadata": {},
"outputs": [],
"source": [
"import uuid\n",
"from typing import Annotated, Optional\n",
"\n",
"from langchain_core.tools import InjectedToolArg\n",
"from langgraph.store.base import BaseStore\n",
"\n",
"@tool \n",
"def upsert_memory(\n",
" content: str,\n",
" *,\n",
" memory_id: Optional[uuid.UUID] = None,\n",
" # Hide these arguments from the model.\n",
" store: Annotated[BaseStore, InjectedToolArg],\n",
"):\n",
" \"\"\"Upsert a memory in the database.\n",
"\n",
" If a memory conflicts with an existing one, then just UPDATE the\n",
" existing one by passing in memory_id - don't create two memories\n",
" that are the same. If the user corrects a memory, UPDATE it.\n",
"\n",
" Args:\n",
" content: The main content of the memory. For example:\n",
" \"User expressed interest in learning about French.\"\n",
" memory_id: ONLY PROVIDE IF UPDATING AN EXISTING MEMORY.\n",
" The memory to overwrite.\n",
" \"\"\"\n",
" mem_id = memory_id or uuid.uuid4()\n",
"\n",
" # BaseStore is a LangGraph persistence layer\n",
" store.put(\n",
" (\"memories\"),\n",
" key=str(mem_id),\n",
" value={\"content\": content},\n",
" )\n",
" return f\"Stored memory {mem_id}\"\n",
"\n",
"# Augment the LLM with tools\n",
"tools = [upsert_memory]\n",
"tools_by_name = {tool.name: tool for tool in tools}\n",
"llm_with_memory_tool = llm.bind_tools(tools)"
]
},
{
"cell_type": "code",
"execution_count": 166,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'tool_call': {'name': 'upsert_memory', 'args': {'content': \"User's name is Lance and they live in San Francisco.\"}, 'id': 'call_4wMyPHYypNRscBdylzW6x3UD', 'type': 'tool_call'}, 'action': 'Please approve/reject the tool call'}\n"
]
}
],
"source": [
"from langgraph.store.memory import InMemoryStore # New \n",
"from langchain_core.messages import ToolMessage\n",
"\n",
"def call_llm(messages: list[BaseMessage]):\n",
" \"\"\"LLM decides whether to call a tool or not\"\"\"\n",
" return llm_with_memory_tool.invoke( # New \n",
" [\n",
" SystemMessage(\n",
" content=\"You are a helpful assistant tasked with storing memories.\" # New \n",
" )\n",
" ]\n",
" + messages\n",
" )\n",
"\n",
"def call_tool(tool_call: ToolCall, store: BaseStore):\n",
"\n",
" # Interrupt the workflow to get a review from a human.\n",
" is_approved = interrupt({ # New \n",
" # Any json-serializable payload provided to interrupt as argument.\n",
" # It will be surfaced on the client side as an Interrupt when streaming data\n",
" # from the workflow.\n",
" \"tool_call\": tool_call, # The tool call we want reviewed.\n",
" # We can add any additional information that we need.\n",
" # For example, introduce a key called \"action\" with some instructions.\n",
" \"action\": \"Please approve/reject the tool call\",\n",
" })\n",
" \n",
" if is_approved:\n",
"\n",
" print(\"Tool call approved, Memory Added!\")\n",
"\n",
" tool = tools_by_name[tool_call[\"name\"]]\n",
" tool.invoke({**tool_call[\"args\"], \"store\": store})\n",
"\n",
" # Tool message provides confirmation to the model that the actions it took were completed\n",
" results = ToolMessage(content=tool_call[\"args\"][\"content\"], tool_call_id=tool_call[\"id\"])\n",
" return results\n",
" else: \n",
" return \"Tool call rejected\"\n",
"\n",
"@entrypoint(checkpointer=MemorySaver(), store=InMemoryStore()) \n",
"def agent(messages: list[BaseMessage], previous: list[BaseMessage], store: BaseStore): \n",
" \"\"\" Tool calling agent \"\"\"\n",
"\n",
" # Add previous messages from short-term memory to the current messages\n",
" if previous is not None:\n",
" messages = add_messages(previous, messages)\n",
" \n",
" # New \n",
" # Retrieve the most recent memories for context\n",
" memories = store.search( \n",
" (\"memories\"),\n",
" limit=10,\n",
" )\n",
"\n",
" # New\n",
" # Format memories for inclusion in the prompt\n",
" formatted = \"\\n\".join(f\"[{mem.key}]: {mem.value} (similarity: {mem.score})\" for mem in memories)\n",
" if formatted:\n",
" formatted = f\"\"\"\n",
"<memories>\n",
"{formatted}\n",
"</memories>\"\"\"\n",
"\n",
" # New\n",
" # Call the LLM\n",
" llm_response = call_llm([SystemMessage(content=f\"Here is some context for you about the user: {formatted}\"), *messages])\n",
"\n",
" while True:\n",
" if not llm_response.tool_calls:\n",
" break\n",
"\n",
" # Execute tools\n",
" tool_results = [\n",
" call_tool(tool_call, store) for tool_call in llm_response.tool_calls\n",
" ]\n",
" messages = add_messages(messages, [llm_response, *tool_results])\n",
" llm_response = call_llm(messages)\n",
"\n",
" messages = add_messages(messages, llm_response)\n",
" return messages\n",
"\n",
"# Thread ID\n",
"thread_id = str(uuid.uuid4())\n",
"\n",
"# Config\n",
"config = {\"configurable\": {\"thread_id\": thread_id}}\n",
"\n",
"# Run until the interrupt \n",
"for item in agent.stream([HumanMessage(content=\"Hi my name is Lance and I live in San Francisco.\")], config, stream_mode=\"updates\"):\n",
" if '__interrupt__' in item:\n",
" print(item['__interrupt__'][0].value)"
]
},
{
"cell_type": "code",
"execution_count": 167,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Tool call approved, Memory Added!\n",
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"Nice to meet you, Lance! How can I assist you today?\n"
]
}
],
"source": [
"for item in agent.stream(Command(resume=True), config, stream_mode=\"updates\"):\n",
" item['agent'][-1].pretty_print()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"TODO: Clarify problem w/ *not* using `@task` in the above case!\n",
"\n",
"Seems it still runs once. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Adding tasks\n",
"\n",
"* TODO: Why?\n"
]
},
{
"cell_type": "code",
"execution_count": 162,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'tool_call': {'name': 'upsert_memory', 'args': {'content': \"User's name is Isaac and he lives in Palo Alto.\"}, 'id': 'call_OI9WYghIIgaw6WAlq35KftbA', 'type': 'tool_call'}, 'action': 'Please approve/reject the tool call'}\n"
]
}
],
"source": [
"from langgraph.func import task # New \n",
"\n",
"@task\n",
"def call_llm(messages: list[BaseMessage]):\n",
" \"\"\"LLM decides whether to call a tool or not\"\"\"\n",
" return llm_with_memory_tool.invoke( # New \n",
" [\n",
" SystemMessage(\n",
" content=\"You are a helpful assistant tasked with storing memories.\" # New \n",
" )\n",
" ]\n",
" + messages\n",
" )\n",
"\n",
"@task\n",
"def call_tool(tool_call: ToolCall, store: BaseStore):\n",
"\n",
" # Interrupt the workflow to get a review from a human.\n",
" is_approved = interrupt({ # New \n",
" # Any json-serializable payload provided to interrupt as argument.\n",
" # It will be surfaced on the client side as an Interrupt when streaming data\n",
" # from the workflow.\n",
" \"tool_call\": tool_call, # The tool call we want reviewed.\n",
" # We can add any additional information that we need.\n",
" # For example, introduce a key called \"action\" with some instructions.\n",
" \"action\": \"Please approve/reject the tool call\",\n",
" })\n",
" \n",
" if is_approved:\n",
"\n",
" tool = tools_by_name[tool_call[\"name\"]]\n",
" tool.invoke({**tool_call[\"args\"], \"store\": store})\n",
"\n",
" # Tool message provides confirmation to the model that the actions it took were completed\n",
" results = ToolMessage(content=tool_call[\"args\"][\"content\"], tool_call_id=tool_call[\"id\"])\n",
" return results\n",
" else: \n",
" return \"Tool call rejected\"\n",
"\n",
"@entrypoint(checkpointer=MemorySaver(), store=InMemoryStore()) \n",
"def agent(messages: list[BaseMessage], previous: list[BaseMessage], store: BaseStore): \n",
" \"\"\" Tool calling agent \"\"\"\n",
"\n",
" # Add previous messages from short-term memory to the current messages\n",
" if previous is not None:\n",
" messages = add_messages(previous, messages)\n",
" \n",
" # New \n",
" # Retrieve the most recent memories for context\n",
" memories = store.search( \n",
" (\"memories\"),\n",
" limit=10,\n",
" )\n",
"\n",
" # New\n",
" # Format memories for inclusion in the prompt\n",
" formatted = \"\\n\".join(f\"[{mem.key}]: {mem.value} (similarity: {mem.score})\" for mem in memories)\n",
" if formatted:\n",
" formatted = f\"\"\"\n",
"<memories>\n",
"{formatted}\n",
"</memories>\"\"\"\n",
"\n",
" # New\n",
" # Call the LLM\n",
" llm_response = call_llm([SystemMessage(content=f\"Here is some context for you about the user: {formatted}\"), *messages]).result()\n",
"\n",
" while True:\n",
" if not llm_response.tool_calls:\n",
" break\n",
"\n",
" # Execute tools\n",
" tool_results = [\n",
" call_tool(tool_call=tool_call, store=store).result() for tool_call in llm_response.tool_calls\n",
" ]\n",
" messages = add_messages(messages, [llm_response, *tool_results])\n",
" llm_response = call_llm(messages).result()\n",
"\n",
" messages = add_messages(messages, llm_response)\n",
" return messages\n",
"\n",
"# Thread ID\n",
"thread_id = str(uuid.uuid4())\n",
"\n",
"# Config\n",
"config = {\"configurable\": {\"thread_id\": thread_id}}\n",
"\n",
"# Run until the interrupt \n",
"for item in agent.stream([HumanMessage(content=\"Hi my name is Isaac and I live in Palo Alto.\")], config, stream_mode=\"updates\"):\n",
" if '__interrupt__' in item:\n",
" print(item['__interrupt__'][0].value)"
]
},
{
"cell_type": "code",
"execution_count": 163,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"==================================\u001b[1m Ai Message \u001b[0m==================================\n",
"\n",
"Hello Isaac! I've noted that you live in Palo Alto. How can I assist you today?\n",
"None\n"
]
}
],
"source": [
"for item in agent.stream(Command(resume=True), config, stream_mode=\"updates\"):\n",
" if 'agent' in item:\n",
" print(item['agent'][-1].pretty_print())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Adding Time Travel\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.6"
}
},
"nbformat": 4,
"nbformat_minor": 4
}