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
+}