From f239b39060096ab2c8bff0d6303781efee174a5c Mon Sep 17 00:00:00 2001 From: Lance Martin Date: Tue, 28 Jan 2025 06:31:27 -0800 Subject: [PATCH] fxn api w agent --- .../functional_api/functional_api_test.ipynb | 941 ++++++++++++++++++ 1 file changed, 941 insertions(+) create mode 100644 docs/docs/tutorials/functional_api/functional_api_test.ipynb diff --git a/docs/docs/tutorials/functional_api/functional_api_test.ipynb b/docs/docs/tutorials/functional_api/functional_api_test.ipynb new file mode 100644 index 000000000..ecb437454 --- /dev/null +++ b/docs/docs/tutorials/functional_api/functional_api_test.ipynb @@ -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", + "\n", + "{formatted}\n", + "\"\"\"\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", + "\n", + "{formatted}\n", + "\"\"\"\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 +}