mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-09 19:27:54 +02:00
fxn api w agent
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user