diff --git a/deeplearning-ai/README.md b/deeplearning-ai/README.md new file mode 100644 index 000000000..bd26693c2 --- /dev/null +++ b/deeplearning-ai/README.md @@ -0,0 +1 @@ +# DeepLearning AI \ No newline at end of file diff --git a/.gitignore b/langchain-academy/.gitignore similarity index 100% rename from .gitignore rename to langchain-academy/.gitignore diff --git a/README.md b/langchain-academy/README.md similarity index 100% rename from README.md rename to langchain-academy/README.md diff --git a/module-0/basics.ipynb b/langchain-academy/module-0/basics.ipynb similarity index 73% rename from module-0/basics.ipynb rename to langchain-academy/module-0/basics.ipynb index e0b9ea883..c48453d28 100644 --- a/module-0/basics.ipynb +++ b/langchain-academy/module-0/basics.ipynb @@ -53,18 +53,36 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "c2a15227", "metadata": {}, "outputs": [], "source": [ - "import os, getpass\n", + "# import os, getpass\n", + "\n", + "# def _set_env(var: str):\n", + "# if not os.environ.get(var):\n", + "# os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "\n", + "# _set_env(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "0ffb6a23", + "metadata": {}, + "outputs": [], + "source": [ + "# load environment variables\n", + "from dotenv import load_dotenv\n", + "import os\n", "\n", - "def _set_env(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "load_dotenv(dotenv_path='C:\\MIConsulting\\LangChain\\langchain-academy\\.env')\n", "\n", - "_set_env(\"OPENAI_API_KEY\")" + "# OPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\")\n", + "GOOGLEAI_API_KEY = os.getenv(\"GOOGLEAI_API_KEY\")\n", + "TAVILY_API_KEY = os.getenv(\"TAVILY_API_KEY\")" ] }, { @@ -84,14 +102,34 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 10, "id": "e19a54d3", "metadata": {}, "outputs": [], "source": [ - "from langchain_openai import ChatOpenAI\n", - "gpt4o_chat = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n", - "gpt35_chat = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)" + "# from langchain_openai import ChatOpenAI\n", + "\n", + "# gpt4o_chat = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n", + "# gpt35_chat = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "16e79f2e", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_google_genai import ChatGoogleGenerativeAI\n", + "\n", + "gemini15_chat = ChatGoogleGenerativeAI(\n", + " model=\"gemini-1.5-pro\",\n", + " temperature=0,\n", + " max_tokens=None,\n", + " timeout=None,\n", + " max_retries=2,\n", + " api_key=GOOGLEAI_API_KEY,\n", + ")" ] }, { @@ -109,17 +147,17 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 19, "id": "b1280e1b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "AIMessage(content='Hello! How can I assist you today?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 11, 'total_tokens': 20}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5', 'finish_reason': 'stop', 'logprobs': None}, id='run-d3c4bc85-ef14-49f6-ba7e-91bf455cffee-0', usage_metadata={'input_tokens': 11, 'output_tokens': 9, 'total_tokens': 20})" + "AIMessage(content='Hello world back to you!\\n', additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-07967d2a-4feb-48f8-9f50-4c7aee2d331f-0', usage_metadata={'input_tokens': 3, 'output_tokens': 7, 'total_tokens': 10, 'input_token_details': {'cache_read': 0}})" ] }, - "execution_count": 3, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -134,7 +172,9 @@ "messages = [msg]\n", "\n", "# Invoke the model with a list of messages \n", - "gpt4o_chat.invoke(messages)" + "# gpt4o_chat.invoke(messages)\n", + "# gpt35_chat.invoke(messages)\n", + "gemini15_chat.invoke(messages)" ] }, { @@ -147,44 +187,24 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 20, "id": "f27c6c9a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "AIMessage(content='Hello! How can I assist you today?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 9, 'total_tokens': 18}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5', 'finish_reason': 'stop', 'logprobs': None}, id='run-d6f6b682-e29a-44de-b45e-79fad1e405e5-0', usage_metadata={'input_tokens': 9, 'output_tokens': 9, 'total_tokens': 18})" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "gpt4o_chat.invoke(\"hello world\")" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "fdc2f0ca", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "AIMessage(content='Hello! How can I assist you today?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 9, 'total_tokens': 18}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-c75d3f0f-2d71-47be-b14c-42b8dd2b4b08-0', usage_metadata={'input_tokens': 9, 'output_tokens': 9, 'total_tokens': 18})" + "AIMessage(content='Hello world!\\n', additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-add51968-5aad-45f1-b010-25e112d3b76f-0', usage_metadata={'input_tokens': 3, 'output_tokens': 4, 'total_tokens': 7, 'input_token_details': {'cache_read': 0}})" ] }, - "execution_count": 5, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "gpt35_chat.invoke(\"hello world\")" + "# gpt4o_chat.invoke(\"hello world\")\n", + "gemini15_chat.invoke(\"hello world\")" ] }, { @@ -214,39 +234,40 @@ "metadata": {}, "outputs": [], "source": [ - "_set_env(\"TAVILY_API_KEY\")" + "# _set_env(\"TAVILY_API_KEY\")" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 23, "id": "52d69da9", "metadata": {}, "outputs": [], "source": [ "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "\n", "tavily_search = TavilySearchResults(max_results=3)\n", "search_docs = tavily_search.invoke(\"What is LangGraph?\")" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 24, "id": "d06f87e6", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[{'url': 'https://www.datacamp.com/tutorial/langgraph-tutorial',\n", + "[{'url': 'https://langchain-ai.github.io/langgraph/',\n", + " 'content': 'Overview¶. LangGraph is a library for building stateful, multi-actor applications with LLMs, used to create agent and multi-agent workflows. Compared to other LLM frameworks, it offers these core benefits: cycles, controllability, and persistence. LangGraph allows you to define flows that involve cycles, essential for most agentic architectures, differentiating it from DAG-based solutions.'},\n", + " {'url': 'https://www.datacamp.com/tutorial/langgraph-tutorial',\n", " 'content': 'LangGraph is a library within the LangChain ecosystem designed to tackle these challenges head-on. LangGraph provides a framework for defining, coordinating, and executing multiple LLM agents (or chains) in a structured manner.'},\n", - " {'url': 'https://langchain-ai.github.io/langgraph/',\n", - " 'content': 'Overview LangGraph is a library for building stateful, multi-actor applications with LLMs, used to create agent and multi-agent workflows. Compared to other LLM frameworks, it offers these core benefits: cycles, controllability, and persistence. LangGraph allows you to define flows that involve cycles, essential for most agentic architectures, differentiating it from DAG-based solutions. As a ...'},\n", - " {'url': 'https://www.youtube.com/watch?v=nmDFSVRnr4Q',\n", - " 'content': 'LangGraph is an extension of LangChain enabling Multi-Agent conversation and cyclic chains. This video explains the basics of LangGraph and codesLangChain in...'}]" + " {'url': 'https://medium.com/@cplog/introduction-to-langgraph-a-beginners-guide-14f9be027141',\n", + " 'content': 'LangGraph is a versatile tool for building complex, stateful applications with LLMs. By understanding its core concepts and working through simple examples, beginners can start to leverage its'}]" ] }, - "execution_count": 7, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -266,7 +287,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "lc-academy", "language": "python", "name": "python3" }, @@ -280,7 +301,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.1" + "version": "3.11.10" } }, "nbformat": 4, diff --git a/langchain-academy/module-1/LangChain_Academy_-_Introduction_to_LangGraph_-_Motivation.pdf b/langchain-academy/module-1/LangChain_Academy_-_Introduction_to_LangGraph_-_Motivation.pdf new file mode 100644 index 000000000..e227fbb12 Binary files /dev/null and b/langchain-academy/module-1/LangChain_Academy_-_Introduction_to_LangGraph_-_Motivation.pdf differ diff --git a/langchain-academy/module-1/agent-memory.ipynb b/langchain-academy/module-1/agent-memory.ipynb new file mode 100644 index 000000000..0a1d58290 --- /dev/null +++ b/langchain-academy/module-1/agent-memory.ipynb @@ -0,0 +1,582 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "13cd1c3e", + "metadata": {}, + "source": [ + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-1/agent-memory.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239417-lesson-7-agent-with-memory)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "8c451ffd-a18b-4412-85fa-85186824dd03", + "metadata": {}, + "source": [ + "# Agent memory\n", + "\n", + "## Review\n", + "\n", + "Previously, we built an agent that can:\n", + "\n", + "* `act` - let the model call specific tools \n", + "* `observe` - pass the tool output back to the model \n", + "* `reason` - let the model reason about the tool output to decide what to do next (e.g., call another tool or just respond directly)\n", + "\n", + "![Screenshot 2024-08-21 at 12.45.32 PM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbab7453080e6802cd1703_agent-memory1.png)\n", + "\n", + "## Goals\n", + "\n", + "Now, we're going extend our agent by introducing memory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d2b4b45b-cbaa-41b1-b3ed-f6b0645be3f9", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install --quiet -U langchain_openai langchain_core langgraph" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "2b0cfa99", + "metadata": {}, + "outputs": [], + "source": [ + "# import os, getpass\n", + "\n", + "# def _set_env(var: str):\n", + "# if not os.environ.get(var):\n", + "# os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "\n", + "# _set_env(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "markdown", + "id": "02eff247-a2aa-4f7a-8be1-73dfebfecc63", + "metadata": {}, + "source": [ + "We'll use [LangSmith](https://docs.smith.langchain.com/) for [tracing](https://docs.smith.langchain.com/concepts/tracing)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "74ef2ff0", + "metadata": {}, + "outputs": [], + "source": [ + "# _set_env(\"LANGCHAIN_API_KEY\")\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_PROJECT\"] = \"langchain-academy\"" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "ab412db7", + "metadata": {}, + "outputs": [], + "source": [ + "# load environment variables\n", + "from dotenv import load_dotenv\n", + "import os\n", + "\n", + "load_dotenv(dotenv_path='C:\\MIConsulting\\LangChain\\langchain-academy\\.env')\n", + "\n", + "# OPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\")\n", + "GOOGLEAI_API_KEY = os.getenv(\"GOOGLEAI_API_KEY\")\n", + "LANGCHAIN_API_KEY = os.getenv('LANGCHAIN_API_KEY') \n", + "LANGCHAIN_ENDPOINT = os.getenv('LANGCHAIN_ENDPOINT')\n", + "LANGCHAIN_TRACING_V2 = os.getenv('LANGCHAIN_TRACING_V2')\n", + "LANGCHAIN_PROJECT = os.getenv('LANGCHAIN_PROJECT')" + ] + }, + { + "cell_type": "markdown", + "id": "9c5f123b-db5d-4816-a6a3-2e4247611512", + "metadata": {}, + "source": [ + "This follows what we did previously." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "46647bbe-def5-4ea7-a315-1de8d97c8288", + "metadata": {}, + "outputs": [], + "source": [ + "# from langchain_openai import ChatOpenAI\n", + "from langchain_google_genai import ChatGoogleGenerativeAI\n", + "\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", + "# This will be a 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", + "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", + "tools = [add, multiply, divide]\n", + "# llm = ChatOpenAI(model=\"gpt-4o\")\n", + "llm = ChatGoogleGenerativeAI(\n", + " model=\"gemini-1.5-pro\",\n", + " api_key=GOOGLEAI_API_KEY)\n", + "llm_with_tools = llm.bind_tools(tools)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "a9092b40-20c4-4872-b0ed-be1b53a15ef3", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import MessagesState\n", + "from langchain_core.messages import AIMessage, HumanMessage, SystemMessage\n", + "\n", + "# System message\n", + "sys_msg = SystemMessage(content=\"You are a helpful assistant tasked with performing arithmetic on a set of inputs.\")\n", + "\n", + "# Node\n", + "def assistant(state: MessagesState):\n", + " return {\"messages\": [llm_with_tools.invoke([sys_msg] + state[\"messages\"])]}" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "771123a3-91ac-4076-92c0-93bcd69cf048", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from langgraph.graph import START, StateGraph\n", + "from langgraph.prebuilt import tools_condition, ToolNode\n", + "from IPython.display import Image, display\n", + "\n", + "# Graph\n", + "builder = StateGraph(MessagesState)\n", + "\n", + "# Define nodes: these do the work\n", + "builder.add_node(\"assistant\", assistant)\n", + "builder.add_node(\"tools\", ToolNode(tools))\n", + "\n", + "# Define edges: these determine how the control flow moves\n", + "builder.add_edge(START, \"assistant\")\n", + "builder.add_conditional_edges(\n", + " \"assistant\",\n", + " # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools\n", + " # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END\n", + " tools_condition,\n", + ")\n", + "builder.add_edge(\"tools\", \"assistant\")\n", + "react_graph = builder.compile()\n", + "\n", + "# Show\n", + "display(Image(react_graph.get_graph(xray=True).draw_mermaid_png()))" + ] + }, + { + "cell_type": "markdown", + "id": "e830b7ae-3673-4cc6-8627-4740b7b8b217", + "metadata": {}, + "source": [ + "## Memory\n", + "\n", + "Let's run our agent, as before." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "596a71a0-1337-44d4-971d-f80c367bd868", + "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 (a6909437-3dbb-4f30-af6b-7eb19e71de54)\n", + " Call ID: a6909437-3dbb-4f30-af6b-7eb19e71de54\n", + " Args:\n", + " a: 3.0\n", + " b: 4.0\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: add\n", + "\n", + "7\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "7\n" + ] + } + ], + "source": [ + "messages = [HumanMessage(content=\"Add 3 and 4.\")]\n", + "messages = react_graph.invoke({\"messages\": messages})\n", + "for m in messages['messages']:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "92f8128c-f4a5-4dee-b20b-3245bd33f6b3", + "metadata": {}, + "source": [ + "Now, let's multiply by 2!" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b41cc1d7-e6de-4d86-8958-8cf7446f4c22", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Multiply that by 2.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "I'm sorry, I don't have a value to multiply. Can you provide me with one?\n" + ] + } + ], + "source": [ + "messages = [HumanMessage(content=\"Multiply that by 2.\")]\n", + "messages = react_graph.invoke({\"messages\": messages})\n", + "for m in messages['messages']:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "26e65f3c-e1dc-4a62-b8ab-02b33a6ff268", + "metadata": {}, + "source": [ + "We don't retain memory of 7 from our initial chat!\n", + "\n", + "This is because [state is transient](https://github.com/langchain-ai/langgraph/discussions/352#discussioncomment-9291220) to a single graph execution.\n", + "\n", + "Of course, this limits our ability to have multi-turn conversations with interruptions. \n", + "\n", + "We can use [persistence](https://langchain-ai.github.io/langgraph/how-tos/persistence/) to address this! \n", + "\n", + "LangGraph can use a checkpointer to automatically save the graph state after each step.\n", + "\n", + "This built-in persistence layer gives us memory, allowing LangGraph to pick up from the last state update. \n", + "\n", + "One of the easiest checkpointers to use is the `MemorySaver`, an in-memory key-value store for Graph state.\n", + "\n", + "All we need to do is simply compile the graph with a checkpointer, and our graph has memory!" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "637fcd79-3896-42e4-9131-e03b123a0a90", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.checkpoint.memory import MemorySaver\n", + "memory = MemorySaver()\n", + "react_graph_memory = builder.compile(checkpointer=memory)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "ff8fc3bf-3999-47cb-af34-06b2b94d7192", + "metadata": {}, + "source": [ + "When we use memory, we need to specify a `thread_id`.\n", + "\n", + "This `thread_id` will store our collection of graph states.\n", + "\n", + "Here is a cartoon:\n", + "\n", + "* The checkpointer write the state at every step of the graph\n", + "* These checkpoints are saved in a thread \n", + "* We can access that thread in the future using the `thread_id`\n", + "\n", + "![state.jpg](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e0e9f526b41a4ed9e2d28b_agent-memory2.png)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "f722a1d6-e73c-4023-86ed-8b07d392278d", + "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 (3c372d01-3506-48a7-a82e-dfa19b33a28d)\n", + " Call ID: 3c372d01-3506-48a7-a82e-dfa19b33a28d\n", + " Args:\n", + " a: 3.0\n", + " b: 4.0\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: add\n", + "\n", + "7\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "7\n" + ] + } + ], + "source": [ + "# Specify a thread\n", + "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", + "\n", + "# Specify an input\n", + "messages = [HumanMessage(content=\"Add 3 and 4.\")]\n", + "\n", + "# Run\n", + "messages = react_graph_memory.invoke({\"messages\": messages},config)\n", + "for m in messages['messages']:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "c91a8a16-6bf1-48e2-a889-ae04a37c7a2b", + "metadata": {}, + "source": [ + "If we pass the same `thread_id`, then we can proceed from from the previously logged state checkpoint! \n", + "\n", + "In this case, the above conversation is captured in the thread.\n", + "\n", + "The `HumanMessage` we pass (`\"Multiply that by 2.\"`) is appended to the above conversation.\n", + "\n", + "So, the model now know that `that` refers to the `The sum of 3 and 4 is 7.`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "ee38c6ef-8bfb-4c66-9214-6f474c9b8451", + "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 (3c372d01-3506-48a7-a82e-dfa19b33a28d)\n", + " Call ID: 3c372d01-3506-48a7-a82e-dfa19b33a28d\n", + " Args:\n", + " a: 3.0\n", + " b: 4.0\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: add\n", + "\n", + "7\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "7\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Multiply that by 2.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " multiply (39bf7dbd-7bf3-4ab4-8c5f-7cb285a2544e)\n", + " Call ID: 39bf7dbd-7bf3-4ab4-8c5f-7cb285a2544e\n", + " Args:\n", + " a: 7.0\n", + " b: 2.0\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: multiply\n", + "\n", + "14\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "14\n" + ] + } + ], + "source": [ + "messages = [HumanMessage(content=\"Multiply that by 2.\")]\n", + "messages = react_graph_memory.invoke({\"messages\": messages}, config)\n", + "for m in messages['messages']:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "c4b7774e-566f-4c92-9429-ed953bcacaa5", + "metadata": {}, + "source": [ + "## LangGraph Studio\n", + "\n", + "--\n", + "\n", + "**⚠️ DISCLAIMER**\n", + "\n", + "*Running Studio currently requires a Mac. If you are not using a Mac, then skip this step.*\n", + "\n", + "--\n", + "\n", + "Load the `agent` in the UI, which uses `module-1/studio/agent.py` set in `module-1/studio/langgraph.json`." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6d72986c-ff6f-4f81-b585-d268e2710e53", + "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 (3c372d01-3506-48a7-a82e-dfa19b33a28d)\n", + " Call ID: 3c372d01-3506-48a7-a82e-dfa19b33a28d\n", + " Args:\n", + " a: 3.0\n", + " b: 4.0\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: add\n", + "\n", + "7\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "7\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Multiply that by 2.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " multiply (39bf7dbd-7bf3-4ab4-8c5f-7cb285a2544e)\n", + " Call ID: 39bf7dbd-7bf3-4ab4-8c5f-7cb285a2544e\n", + " Args:\n", + " a: 7.0\n", + " b: 2.0\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: multiply\n", + "\n", + "14\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "14\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Divide that by 7.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " divide (96d0e26f-5ece-4ef0-b834-17a5c1d47ca1)\n", + " Call ID: 96d0e26f-5ece-4ef0-b834-17a5c1d47ca1\n", + " Args:\n", + " a: 14.0\n", + " b: 7.0\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: divide\n", + "\n", + "2.0\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "2\n" + ] + } + ], + "source": [ + "messages = [HumanMessage(content=\"Divide that by 7.\")]\n", + "messages = react_graph_memory.invoke({\"messages\": messages}, config)\n", + "for m in messages['messages']:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7248d52a", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "lc-academy", + "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.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/langchain-academy/module-1/agent.ipynb b/langchain-academy/module-1/agent.ipynb new file mode 100644 index 000000000..c1caf2a1b --- /dev/null +++ b/langchain-academy/module-1/agent.ipynb @@ -0,0 +1,365 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6a44f010", + "metadata": {}, + "source": [ + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-1/agent.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239232-lesson-6-agent)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "98f5e36a-da49-4ae2-8c74-b910a2f992fc", + "metadata": {}, + "source": [ + "# Agent\n", + "\n", + "## Review\n", + "\n", + "We built a router.\n", + "\n", + "* Our chat model will decide to make a tool call or not based upon the user input\n", + "* We use a conditional edge to route to a node that will call our tool or simply end\n", + "\n", + "![Screenshot 2024-08-21 at 12.44.33 PM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbac0ba0bd34b541c448cc_agent1.png)\n", + "\n", + "## Goals\n", + "\n", + "Now, we can extend this into a generic agent architecture.\n", + "\n", + "In the above router, we invoked the model and, if it chose to call a tool, we returned a `ToolMessage` to the user.\n", + " \n", + "But, what if we simply pass that `ToolMessage` *back to the model*?\n", + "\n", + "We can let it either (1) call another tool or (2) respond directly.\n", + "\n", + "This is the intuition behind [ReAct](https://react-lm.github.io/), a general agent architecture.\n", + " \n", + "* `act` - let the model call specific tools \n", + "* `observe` - pass the tool output back to the model \n", + "* `reason` - let the model reason about the tool output to decide what to do next (e.g., call another tool or just respond directly)\n", + "\n", + "This [general purpose architecture](https://blog.langchain.dev/planning-for-agents/) can be applied to many types of tools. \n", + "\n", + "![Screenshot 2024-08-21 at 12.45.43 PM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbac0b4a2c1e5e02f3e78b_agent2.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63edff5a-724b-474d-9db8-37f0ae936c76", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install --quiet -U langchain_openai langchain_core langgraph" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "356a6482", + "metadata": {}, + "outputs": [], + "source": [ + "# import os, getpass\n", + "\n", + "# def _set_env(var: str):\n", + "# if not os.environ.get(var):\n", + "# os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "\n", + "# _set_env(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "markdown", + "id": "dba35a12", + "metadata": {}, + "source": [ + "Here, we'll use [LangSmith](https://docs.smith.langchain.com/) for [tracing](https://docs.smith.langchain.com/concepts/tracing).\n", + "\n", + "We'll log to a project, `langchain-academy`. " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "60e6f1eb", + "metadata": {}, + "outputs": [], + "source": [ + "# _set_env(\"LANGCHAIN_API_KEY\")\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_PROJECT\"] = \"langchain-academy\"" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "c7c22832", + "metadata": {}, + "outputs": [], + "source": [ + "# load environment variables\n", + "from dotenv import load_dotenv\n", + "import os\n", + "\n", + "load_dotenv(dotenv_path='C:\\MIConsulting\\LangChain\\langchain-academy\\.env')\n", + "\n", + "# OPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\")\n", + "GOOGLEAI_API_KEY = os.getenv(\"GOOGLEAI_API_KEY\")\n", + "LANGCHAIN_API_KEY = os.getenv('LANGCHAIN_API_KEY') \n", + "LANGCHAIN_ENDPOINT = os.getenv('LANGCHAIN_ENDPOINT')\n", + "LANGCHAIN_TRACING_V2 = os.getenv('LANGCHAIN_TRACING_V2')\n", + "LANGCHAIN_PROJECT = os.getenv('LANGCHAIN_PROJECT')" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "71795ff1-d6a7-448d-8b55-88bbd1ed3dbe", + "metadata": {}, + "outputs": [], + "source": [ + "# from langchain_openai import ChatOpenAI\n", + "from langchain_google_genai import ChatGoogleGenerativeAI\n", + "\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", + "# This will be a 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", + "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", + "tools = [add, multiply, divide]\n", + "# llm = ChatOpenAI(model=\"gpt-4o\")\n", + "llm = ChatGoogleGenerativeAI(\n", + " model=\"gemini-1.5-pro\",\n", + " api_key=GOOGLEAI_API_KEY,\n", + " # verbose=True\n", + ")\n", + "\n", + "# For this ipynb we set parallel tool calling to false as math generally is done sequentially, and this time we have 3 tools that can do math\n", + "# the OpenAI model specifically defaults to parallel tool calling for efficiency, see https://python.langchain.com/docs/how_to/tool_calling_parallel/\n", + "# play around with it and see how the model behaves with math equations!\n", + "llm_with_tools = llm.bind_tools(\n", + " tools,\n", + " # parallel_tool_calls=False\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "a2cec014-3023-405c-be79-de8fc7adb346", + "metadata": {}, + "source": [ + "Let's create our LLM and prompt it with the overall desired agent behavior." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "d061813f-ebc0-432c-91ec-3b42b15c30b6", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import MessagesState\n", + "from langchain_core.messages import HumanMessage, SystemMessage\n", + "\n", + "# System message\n", + "sys_msg = SystemMessage(content=\"You are a helpful assistant tasked with performing arithmetic on a set of inputs.\")\n", + "\n", + "# Node\n", + "def assistant(state: MessagesState):\n", + " return {\"messages\": [llm_with_tools.invoke([sys_msg] + state[\"messages\"])]}" + ] + }, + { + "cell_type": "markdown", + "id": "4eb43343-9a6f-42cb-86e6-4380f928633c", + "metadata": {}, + "source": [ + "As before, we use `MessagesState` and define a `Tools` node with our list of tools.\n", + "\n", + "The `Assistant` node is just our model with bound tools.\n", + "\n", + "We create a graph with `Assistant` and `Tools` nodes.\n", + "\n", + "We add `tools_condition` edge, which routes to `End` or to `Tools` based on whether the `Assistant` calls a tool.\n", + "\n", + "Now, we add one new step:\n", + "\n", + "We connect the `Tools` node *back* to the `Assistant`, forming a loop.\n", + "\n", + "* After the `assistant` node executes, `tools_condition` checks if the model's output is a tool call.\n", + "* If it is a tool call, the flow is directed to the `tools` node.\n", + "* The `tools` node connects back to `assistant`.\n", + "* This loop continues as long as the model decides to call tools.\n", + "* If the model response is not a tool call, the flow is directed to END, terminating the process." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "aef13cd4-05a6-4084-a620-2e7b91d9a72f", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAANYAAAD5CAIAAADUe1yaAAAAAXNSR0IArs4c6QAAIABJREFUeJztnXdcU1fj/89NQnYChD1kiQgIjooTXFXqI44fUKt11Grr86271tX66GPt0Nplfdo+1rb6WBXrnlgVrKsuXBUVEESmjEBISEJCxk1yf3/EF6UYhpp7zw0571f/sMnNOZ/Am3PvPfcMjCAIgEDAgwE7AMLZQQoiIIMUREAGKYiADFIQARmkIAIyLNgBnge1AlfL8Ua1WdtgMhkdo1uJ5YIxWRhfxOSLWR5+bC6fCTsRXcAc4xcIAABAVqkvuqstydUKxCyzieCLmQIRi81jAEf4BiwOpqk3NTaYG9UmrcoscGWGxgi69RYK3V1gR4OMYyiokuNXj9cxXTB3b3ZoD4FnAAd2ohelskhXkqNVSA1uXuzB4z1YLs57ReQACl4/JS+41TB4gmd4LyHsLPbn7h/Kq+nyISmeMYNdYWeBA90VPPifiph4cWScGHYQcrmRoWhQ4COn+MAOAgH6KkgQxE8riye84+8XyoOdhQryrqtLc7VJb/nBDkI19FXwhxWPZqwOEYgd8p79+ci/qc65qp74biDsIJRCUwUPbqqIT/bwC3GK9q8596+o5FWG4a95ww5CHXS8Ecs6KY8dInZC/wAAsfGufBHzwQ017CDUQTsF62uNj7I13ft28vuPNnhppPuFAzLYKaiDdgpeTZcPHu8BOwVMWC6MvqPcr5+Sww5CEfRSUFqq5/AYYbGdsP/vmeg/WiIt1eNGC+wgVEAvBYvuaSS+bMqqy8nJMRgMsD7eNlwBsyRHS1LhtIJeCpbkakN7CKipKz09febMmTqdDsrH2yU0RoAUpJr6WqNYwnL3oagVfO4GzNqNRV77ZyUsVqCS46RWQRNopKCqDscwjIySy8rK5syZk5CQkJSUtH79eovFkp6evmHDBgDAqFGj4uLi0tPTAQDZ2dkLFixISEhISEh45513Hjx4YP24UqmMi4vbtWvX6tWrExIS/vnPf9r8uH1huTA0SpNWZbJ7yXSDRs8eGtVmvpiUUXSffPJJaWnp0qVLtVrtrVu3GAxGfHz89OnT09LSNm3aJBQKg4KCAABVVVUGg2H27NkMBuPAgQOLFi1KT0/ncrnWQrZt2/baa69t2bKFyWT6+Pg8/XG7IxCztGqTwJVGvyMyoNHX06pNJD2Oq6qqioyMTElJAQBMnz4dACCRSAIDAwEAMTExbm5u1sPGjBmTlJRk/Xd0dPScOXOys7MHDhxofSU2Nnb+/PlNZT79cbsjcGVqVWbQhaTi6QKNFASAYHFIOREnJSX98ssvX3zxxezZsyUSSWuHYRh2/vz5tLS0kpISPp8PAJDL/+qc69+/PxnZ2oDDZRIWOj4+tS80uhbkCVgNClIufebPn79kyZLMzMwJEybs37+/tcO2bt26fPny6OjojRs3Ll68GABgsfzVM8fjUf3AUFln5DvBKA0aKcgXMxvVZjJKxjBs6tSpx44dGzZs2BdffJGdnd30VtMoDYPBsH379uTk5KVLl/bu3Ts2NrYjJZM6yIO8i2NaQSMFRRIXF3JOxNYOFIFAMGfOHABAfn5+U6smkz15GqvT6QwGQ1RUlPV/lUpli1awBS0+TgYiCUvk1vlbQRp9Q68ATuUjnUZpEtr75/7+++8LhcKBAwdevnwZAGD1rFevXkwm86uvvpowYYLBYHj11VfDw8P37t3r4eGh0Wh++uknBoPx6NGj1sp8+uP2zVyap3VhMzAGKX+TtIK5du1a2Bn+QinDcb3FO4hr32IrKiouX758+vRpnU63cOHC4cOHAwDEYrGPj8+ZM2cuXbqkVqvHjRv30ksvXblyZf/+/WVlZQsXLgwODj506NC0adNwHN+5c2dCQkJ0dHRTmU9/3L6Z75xXBoTzvLvY+UdBQ+g1ZLU8X1ucox0+0YkGbLZG+k9VIyZ5Cd06/xRPGp2IAQBBkYLrpxTSMr1vsO2/fqVSmZycbPOtwMDAioqKp18fNmzYRx99ZO+kLZk9e7bNs3ZUVFTTU5bm9O3b9+uvv26ttJyrKqEbyxn8o10rCACofKS7flqeusD2/Amz2VxTU2PzLQyz/V14PJ67u7u9Y7ZEJpPhuI1Huq2l4nA4Hh6tDov8aWXxm2uCObzOfztMRwUBAOf313brIwzsxocdBA73r6iMekvfkaT/2dAEGnXKNDFikvfpHVKdhpQ+QppTXtBYfE/jPP7RVEEAwJQVQb9+Xg47BdU01ONn0mr+39wA2EEohY4nYisGnXn3hvJpHwQ5ySVRTZk+M61m2soghhP0BTaHvgpaW4U9Xzye8I6fb2ef0FlwW333D9Wk9zr7qBhb0FpBK2f31Oi05vjxnpQNqKaSisLGK+nywHBe/ARP2Fng4AAKAgBKcrRX0uvCYgU+QdzQGEEnOFXpteaSXG11iV5Vh8eP97D7AyEHwjEUtFJ4p6HwjqYkRxs1QMxiYwIxS+DK5HCZDvEFmExMqzY1qk0alUmtMNWU6UN7CCL6ioK6O2nfUxOOpGATpQ+0qlpcqzZpVWaTyWKxa+8NjuN5eXm9evWyZ6EA8IRMwkLwxSyhK8vDj+3ftZNf3XYch1SQVORy+ZQpUzIzM2EHcRZo2i+IcB6QggjIIAVbgmFYREQE7BROBFKwJQRBPHz4EHYKJwIp2BIMw1xdnXTxeyggBVtCEIRKpYKdwolACtrA19cXdgQnAiloA6lUCjuCE4EUbAmGYc1nyiHIBinYEoIg8vLyYKdwIpCCCMggBVuCYVgbq28h7A5SsCUEQSgUCtgpnAikoA08PZ10ADMUkII2qKurgx3BiUAKIiCDFGwJhmFdu3aFncKJQAq2hCCIoqIi2CmcCKQgAjJIQRs0LfeLoACkoA1srgiIIAmkIAIySMGWoJEyFIMUbAkaKUMxSEEEZJCCLUGTOCkGKdgSNImTYpCCCMggBVuC5hFTDFKwJWgeMcUgBVuCRspQDFKwJWikDMUgBRGQQQrawMfHB3YEJwIpaIPWdlpEkAFS0AZovCCVIAVtgMYLUglSsCVosBbFIAVbggZrUQxS0AaBgbb3hEeQAdr65glvv/22VCplMpkWi6W+vl4ikWAYZjKZTp48CTtaJwe1gk+YNGlSQ0NDVVWVVCo1GAzV1dVVVVUY5vD7LdIfpOATRo8eHRYW1vwVgiD69u0LL5GzgBT8iylTpvD5f+2L6evrO3XqVKiJnAKk4F+MHj06ODjY+m9rExgZGQk7VOcHKfg3ZsyYIRAIrE3glClTYMdxCpCCfyMxMTE4OJggiD59+qDHdNTAgh3ABhYLoZTh6jrcAqO/KPmVd0Dj0X8MfbM4R0t97UwmcPdmiz1cqK8aFrTrF8y/pc69qm7UmP3D+FqVCXYcqhG6s8rzte5e7H6j3f3DnGLndnop+OC6uvCudthrvgyGU3fI6XXmzB2ViVO9vbtwYWchHRpdCxZmawr+1IyY7Ofk/gEAuDzmhDlBp36RKmVG2FlIh0YK3rukjE9Gw5X/YtB471uZ9bBTkA5dFNRpzYpqI5fPhB2ERrh6sssLGmGnIB26KNigwH2CnOLqu+PwRSwun2kyWmAHIRe6KAgApm1wuvvfdlHJ8U4/VII+CiKcFKQgAjJIQQRkkIIIyCAFEZBBCiIggxREQAYpiIAMUhABGaQgAjJIQQRknFrBk6eOJaeOqqmRtnaA2Wy+fz/7xSuSSqurpVUvXk6nxKkVZLM5AoGQwWj1h/Dl159s3LT+BWuprKqYOn1CQQFaKsk2dJy+RBmjRv5j1Mh/tHGA0WB48VrMJhOtZkfQDQdW8P797F1pW+/nZAMAIrv3mDNncfeIKACAXq/f9O2Gq1f/AAD07Nlnwbxlvr5+WVmXf9r6XVVVha+v/4TxE1NTJm/4Ym1GxgkAwJmMLBaLZfOA8xfOAABGjIwDAPy6+7ifr/+p08ePHt1fXPKIx+P37zdowfxlbm7uAICDh349dz7ztYnTtm37r1xR161b5LIlq4OCQqqlVW/OmggA+OjjDz4CYPTocR+sWAv7J0cvHFhBqbTKYDS8MX02g8E4duzABysX7dmdzuVyf92zPSPjxKyZczw8PDMyT/B4vMbGxrUfvx8SHLZ0yeqSkkdyuQwAkJryusViOXPmJADA5gHTp74lq62prq5c+cHHAAAPiScAIC/vflBQSGJiUn294vCRvdpG7WfrNlnzPHiQs3//rqVLV5tMpo0b1332+Yc//HeHh8Rz1b8+Xbd+9ayZc/r0jnN3l8D+sdEOB1Zw1KgxiYlJ1n937x69ZOmc+znZ/eIGVkureDze1CkzWSzW2KRk69WYwWAYMuTlxFFjmj4e0S0yJPjJOkb1SsXTBwQGBrm6uinq5bGxvZteXPLev5rGkLJYrLTd/zMYDBwOx/rKuk+/kUg8AACpqa9v/uEblVrlKnaN6BYJAAgKCmleDqIJB1YQw7BLl8/vP5BWVlZiXY6oXiEHAIwaOebs2dPvf7Bw/rylYWHhAAB/v4AePXqm7d7G5fLGj0tls9ktimr3gCZwHD98ZO+Z30/W1ko5HK7FYlEq6318fK3vcrlP5h74+PgBAOR1Mlcx2s6uHRz4jnjnrq1rPlzePSJ63Scb57yzGABgISwAgAH9B3+2/j+Kevnb/3z9q68/NZlMGIZtWP/t6FfGbflx04yZqXfv/tmiqHYPsEIQxL9WLd796//G/GPC5xu+TxyV1FRpC1xYLgAAs8VMzlfvVDiqgjiO/7pn+9ik5AXzl8bG9o6Oim3+7oD+g7f9vHfe3Pd+O3l0z94dAAChULj43Q92/HJIIBCu/veSxsaWM9NaO6D5zezdu3/e/vPGu4s+mPjq1OiomLDQcEq+ayfHURU0Go0GgyEi4snKQyq1EgBgsVisbwEAGAzGaxOneXp6FRbmAwAMBoP1hJua8rpGq5E+1VFs8wAul6dQyK3FNtVivbZrUWkbcDhc60mZhB9DZ8BRrwUFAkFYWPjhI3slEg+tRrNj508MBqO4+BEA4PCRvVeuXkwclSSXy+rqZN27R+M4/uasV4cPSwwN6Xrs2AGhQOjv/7cFzVs7oFfPl06dPr7xm/WxMb1FInF0VCybzf556/djx6YUFxf+umc7AKCk+FGAf1vLo3t7+/j7Bew/mMbl8dRq1eRJb7TRGe6EOPDP4t+r1vO4vI8/WbnvwK65c997Y/rbGRnpOI77+wfiRuMPW7757eTR1NTXJ096Q6fX9end7/ezpzZ9u4Hl4rJ+3SYu929rtbR2QGJiUkrypAsXz/y09bvcvHteXt6rV60rfJS/9qMVt29f3/j1jwMHJhw+srftnBiGrV69ns8XfP/fr05npFsbaUQTdFnWqPax4eze2nH/1wV2EHqR9mnR/60PY7p05qnEDtwKIjoHSEEEZJCCCMggBRGQQQoiIIMUREAGKYiADFIQARmkIAIySEEEZJCCCMggBRGQQQoiIEMXBRlMTCxx1MGL5OEVyGEwO/MwGRop6OnPLsnV0mTkGE1QSA24wYLR5VdEFjT6fpH9RNUlOtgpaERNua5bHyHsFKRDIwVHTPK+fLhGp0Ub4AAAQGluQ2lOQ1xi55/6TpdR01YMOvOudeW9R0iEbi5u3mxAo2gUQQCgqNY3yPHyfM1r7wV2+q2XaKeglVu/KyoKdQSBqVrZCtVsNuM43mL+h70gCEKv1/N4FG2Ip9PpOBxO04QmzwAOACA4kheb4EZNAPgQDsjChQvJK3zTpk0JCQnHjx8nr4rm1NbWrlmzhpq66AkdW8E2OHfu3Msvv0xe+dXV1QsXLiwtLY2Kitq1axd5FT3Nzp07R44cGRAQQGWldIBGtyPtMnnyZLJ/QwcOHCgtLQUAlJeXnzhxgtS6WpCUlDR37lyDPVY0dCwcoxWUSqWurq6VlZXh4SSuoVFZWblo0aKysjLr/1LfEFovDe/duxcdHS0SiSiuGhYO0AoeOHAgKyuLx+OR6h8A4MiRI03+AQDKysqOHTtGao1Pw+PxunXrNn78eI1GQ3HVsHAABcvKypKTk8mupaqq6vz5881f0Wq1u3fvJrvep5FIJBcuXNDr9VJpq+uwdyZoreDVq1cBAMuWLaOgrr1791qbwKZlijAMe/z4MQVV28TT01MoFMbHxzdvmDsnsG/JbWM0GgcPHlxfX0991TKZ7JVXXqG+XpvodLrt27fDTkEudGwFlUplWVnZ2bNn3dwgdM+azebIyEjq67UJl8udOXMmAGDVqlVmc+dcMJN2Ch4/fry0tDQ8PJykhx/tguO4tV+GVsyaNWvx4sWwU5ACvRSUyWR37tzp3RvmsuA6nc7HxwdiAJuEh4d/9913AIALFy7AzmJnaKRgaWkphmEffvgh3BhyudzFxQVuhjbAcXzFihWwU9gTuii4Zs0aHo/n6ekJOwior68PCgqCnaJVEhMTx44dCwAwmTrJqDZaKFhRUTFgwACanP5KSkro8JfQBsOGDQMA7Nu37+HDh7Cz2AH4Cup0OqFQaP3LpgMGg6Fr166wU7TPtGnTPvzww05wmwxZweXLl1+7dg1K50trnDt3LiIiAnaKDrFnzx6TyVRQUAA7yAsBU8Hbt28vWrSI1MFXz4pSqRSLxf7+/rCDdBQOh6NQKHbu3Ak7yPMDTUGFQtGtW7cuXei1vnlWVlZISAjsFM/GoEGD6uvrYad4fuAoePDgwR9//FEsFkOpvQ3++OOPoUOHwk7xzLz77rvWvYBgB3keICgolUrd3NxWrlxJfdXtolKpHFFBAACbzd68eXNaWhrsIM+MYwxZpYaMjIyLFy+uX78edpDn5/r1656eng5xR98E1a3gggULcnJyKK60gxw5ciQlJQV2ihdiwIABwcHB7W6LRysoVfDixYvjx4+PiYmhstIOUlJSwmKx+vXrBzvIi8JisRITE5VKJewgHQWdiJ+wbNmysWPHjhgxAnYQO6BSqU6cODFt2jTYQToEda3gvn37aHsKzs/Pr66u7hz+AQBcXV0dxT/qFCwtLd2/fz89T8EAgG+++Yaa6QFUsnz58rt378JO0T4UKYhh2NatW6mp61k5evRoYGBgnz59YAexM8uXL//2229hp2gfZ78WNJlMo0ePPnv2LOwgzgsVreC5c+c+/vhjCip6DpYsWULbbHYhMzMTdoR2oELBrKysQYMGUVDRs7Jr166wsLD4+HjYQUjk4cOH27dvh52iLZz3RFxYWPjdd985xNXSi2AymdLT0+nc5U6Fgkajkc1mk13Ls9K/f/9r164xmUzYQZwd0k/Eubm5s2fPJruWZ2X69Ok7duxwEv9ycnI2b94MO0WrkK6gRqMhezmiZ+X777+fNm1aVFQU7CAUERMTs3v3br1eDzuIbZzuWnDr1q04js+dOxd2EEqpqKgQCATu7u6wg9iA9FbQZDIZjbaXjKae48ePV1ZWOpt/AIDAwEB6+keFgufOnYM+O93KzZs3c3NzaRKGYmpra+fNmwc7hW1I33PLw8ODDsPX7t27t3nzZpr3kJGHt7d3QUGBUqmk1WRFK05xLVhUVLRy5cr9+/fDDgITi8WCYRgNNzLp/P2CFRUVixYtOnz4MKwAiLah4gFdSkoKrDVrCwsL582bh/yz3or98MMPsFPYgIr9V4cPH/7mm2+azWa1Wu3t7U3ZZgr5+fl79+49fvw4NdXRHJFIVFRUBDuFDUhUcOjQoY2Njda1hK2XIARBREdHk1djc4qKilatWnXo0CFqqqM/Q4YM6dWrF+wUNiDxRPzyyy9bt1ZrugTmcDgDBgwgr8YmcnJyfv75Z+Rfc1gslkRCx309SVRw7dq10dHRzW93vLy8KPhDzM7O/vLLLzds2EB2RY6FTCYbN24c7BQ2IPd25PPPP29aooUgCD6fT/bz4kuXLp04cWLHjh2k1uKIsNls63UR3SBXQR8fn/fee8+6YiSGYWQ3gRkZGYcOHVq9ejWptTgoYrGYntN3SO+USUhISE1NFQgEQqGQ1AvBo0ePXrx4cdOmTeRV4dBgGBYWFgY7hQ06dEdswi06zfM/ZJvy2ltlRbVFRUVhQT0a6klZIfn8+fO594sdejkYssFxfOLEidTvqtcu7TwdeXBDfe+SSiE18oQvNLqzqV+GJIxGo3eAsKqoMaynsF+iu4c/h7y6HIvly5efPXu2qVPM2hwSBPHnn3/CjvaEtlrBG5mKuip8SKqvSELfTRCaYzETSpnx5C/SUVN9/ELg7JxDN+bOnZuXl1dTU9O8d4xWy3i2ei14/bRCJTMNSfFxFP8AAAwmJvHlJM8PPruntqacpoOEKSYsLKxv377Nz3UYhtFqDUXbCtbXGusqDQPHeVOexz68PMXvVqYDr31rX2bMmNF8Q43AwMDXX38daqK/YVvBukoDQdBuVE/HEbm7PC5sNBrgj1OkA+Hh4f3797f+myCIIUOG0GSLFyu2FdSozF5dHPtaKjhaoKh2yLWXyeCNN97w9vYGAAQEBNBt0S3bCuIGC6537CZELTcB4MANuX3p2rXrgAEDCIIYNmwYrZpAigZrIZ4Vi4Uoz2/U1Ju0apMJJ3RaO2yx1Mt/ur5Pt+6S+N/31Lx4aVwek81j8MVMsbtLUCT/RYpCCtKLBzfUBbc1FYWN/hFik5FgujAZLiyA2aNTgsHtP2gsbgG4PR4UN2gIM24ym3AXF8PxH6uCowURfYTd40TPURRSkC7kXVdfPlbnFSRiCUQxifQ6V7aNe7CkobYx97b+Srp8SLJHtz7PJiJSED46jfnk9hrczAgbEMhiO94aIxiGiX0EAAiEXuJb5xQPbmrGvu3LZHb0Qhz+TpxOTnmBdue6MmGAxLe7lyP61xw2j+UX7c12d9uyoqj2cUcfDSAFYVLzWH/xsKL70GAOz2EeQbULV8juMSr05PYatbxDq2ggBaFRkqvJTJN16e0wu34+EyH9Ag9vlkrL2m8LkYJw0ChNZ/d0Wv+shMQFHP6u0oS308GMFITD6Z01If0DYKcgna4D/X/7XzvdkEhBCNw6U28GbJaLY998dASOgK3VYrnXVG0cgxSEQNZJuXc4TZdaszveYZIr6Yo2DrCngnkPcl5wV+YLF38fMTKuvLzUfqFox+3fFQHREhouLwQA+PiLcQeP2XnyK4vD9AgS5VxttSG0m4KnM9LnL5ip1+vsVWBn5cFNDdfVsUchPSscITf/lqa1d+2moIPuSk8xagWu11p4Iuea2iL04Mke6/FWhm/a5wHd6Yz0Tf/ZAABITh0FAHh/xYf/GD0eAJCZ+dvuPdurqio8PDzHJqVMmzrLusSHyWTa/suWjMwTKpUyODh05pvvJMQPf7rYrKzLP239rqqqwtfXf8L4iakpk+2SFiKPCxrdA4UkFf6o+PbJM5urpA9FQkl4aNyYxLlikScAYPW6ka+Ofz/nwYW8gis8rnBgv5RXRjzZA8FsNv9+YVvWraNGo65rWF8cJ2u2g2eIqOxBY3hvG9/dPq3ggP7xk16bDgD4bN2mbzdtHdA/HgCQkXHis88/7NYt8t+r1w8flvi/7T/s/vXJIqdfff3pvv27xo1NWfWvT319/f+9Ztm9e3dalNnY2Lj24/fZLuylS1YPHjRULpfZJSpc6qpxgiDlFrCw6ObPOxf5eIdOSl41dPDU4tI7W7bPNxqfKLX38Ef+vhHz3t7yUq8xmed+ziu4Yn39yIkvz1zYFhkxOGXcMrYLV6dvICMbAMBsxuplth+W2KcVdHeX+PsHAgCiomJcXd2sA8S3/u+/sbG9V//rUwDA0CEvNzSo9+7b8WrqlLq62ozMEzPemD3zzXcAAMOGjpw+I+WXHT9u/HpL8zLrlQqDwTBkyMuJo8bYJSQd0KpMLA6PjJKP/vb1wLiUlHFPtrSNCB/w5beTCx5lxUYPBwD0f2nCyGEzAQD+vhE3bh97+Cgrunt8RVV+1q0jI4fNGjNqDgAgrs/YohKyZna6cFiaVqaQkzVSpqKivK5ONnnSG02v9Os36OSpYxWV5QUFeQCAhIQn+09jGNYvbuCZ30+2KMHfL6BHj55pu7dxubzx41JpuH/Tc6DTmDnu9u8OVNRX18hK6hSPs24dbf66UvWkW5jNfuI9k8l0FXur1DIAwP28CwCAoYOnNB2PYWR10rE4jEY1tQpqtBoAgJvbX6uJiURiAECdrFar1QAA3Ju9JRa7NjY2arXa5iVgGLZh/bdbt32/5cdNBw6mrXz/4169XiIpLWWQtKpyg0YOAEgcMbtn9N82lheJPJ8+mMFgWSxmAIBSKeVyhQK+KymZWkBglla+u52tb5qv6u3lAwBQqZRNb9XXK6wienp6AwDU6r86ihQKOYvF4nJbdlUIhcLF736w45dDAoFw9b+X0HNhqGdC4Mo0GewwCr8FPK4IAIDjBm+vkOb/8bht3foIBO56vQY3UbErjMlgErnbbu/spiCPywMA1NU9uWnw8PD09fG7ceNK0wEXL/7O5XLDw7tHRcVgGJZ1/bL1daPRmHX9co8ePZlMJtuF3dxOa0ePv19AasrrGq1GKq2yV1pYiFxZJqP9FfTyDHJz9b35Z7rB+KRf1mw2mUx4258KDIgEANy5l2H3PE9jMppFbrYVZK5du/bpVyuLdGYT8A15hgtnLo9/7PiB0rJiDGB5D+537x4tEor3HUiTyWpwHD98ZO/vZ09Nm/pWv7iBYpFYKq0+cnQfAFhdneyHH74pKS1avmyNn18Ay8XlyNF9+QW5QUEhnh5eM2am1tXJ5PK6I0f3GQ2Gt9+ax2J19Mqh8I46JIovbOVrw0KjwuVSE8/NznckGIa5u/nduH08L/8SAYiyx/ePnPjabDYGd4kFAJy7tDPQP7J7+JNlzbJuHuVyBX16vuLtGXov9+ztOyd1eo1GW3/t5pGikluB/lHRkQn2jQcA0Ku0odFciY+NC3q7KSgWib28fC5cOHPt2qWGBvXo0ePCwyPc3SXnzmeeOn1cWa+YOnXW9GlvWR9M9YsbpNU8IWSvAAADj0lEQVRqTp0+du5choAvWLZ0db9+gwAAIqHIz9f/zzs3GRgjKjq2oqL88pXzly6f8/Dw+mDF2oCAwI7noaeCfDHrxm91HsH2v/zy8QoJDIguLs2+nX2yvCLXzy+8b+8x1n7B1hRkMBhREQmyurJ7uWeLS7N9vcMU9VU+XqFkKFhyu2bUNB8Gw8ZjSdsra93IUBj1oNdwOi5N3EFObqsYlurpS7/FjX794rFbkAff1YkekDTUNZrUDSnzbQ+OpFcj4QxEDxQ+ytW1oeDDRzd27lv59Os8rqi1ruNxoxcOjEu2V8IHBVd2H1zz9OsEQQBA2Oy4mTPrv4H+ka0VaNAYevQXtPYuUpBqeg91v3aiyD1QzGTZvhcMCeq5ZN6up18nCNDa8Bo+z55n9q6hfW0GsFgsBEHY3EdcLPJqrTSjDldLNVH9Wl1ODikIgfjxHnm3Fb7dbXTaAQDYbK6EDXNAv30D1BXXD0n2aOMANGQVAj2HuPG4ZoOunU6TToC+weDmgbU9uR0pCIcxs3yLsyphpyAXi4UovlGVNMu37cOQgnBgcxjJc/1LbnRmC4uzKqasCGr3MKQgNPxCeakLfEtuVMAOYn/MJkvhlfKp7we6e7c/uAQpCBNXD/b42b45mSU6dedZGVtbry+8XD55SSBf2KGbXaQgZDwDOPM3drVo1JU5NQYtFSMGyEOnNjy+W+1i0cz5vKu4w6vko04Z+GAYNvZtv5Ic7R9HavluXBafI/biMx1nlrHJYFbLtGaDEdcahqd6dol4thUvkYJ0ITRGEBojKLqvKbyjfXRFIQnk4wYLk81icVg0XLGYIAizwWTGTS5sRr1UFxoj6BYvDIl+nmURkYL0omussGusEABQXaLTqsxalclosOjtsdCvfeHwGVw+my/mi9yZPkHtdLu0DVKQpviFkjLFhIbYVpDNxSz0a/yfCVcvF9ImQiDsie3fksjdRVbm2OsilNzTePh1hhlPnR7bCnp34dByzZOOopQZQ3rwWS6oGXQAWm0FA8K5fxySUp7HPpzdXTUwqa3RGQj60NZ+xLnXVIXZml7DPNx92K0NbqMVOo1JVYf/cVD66sIAtw48GkLQgXa2xC7J1WZfVEpL9EwW3U/MEj+OSmYMi+H3H+MhEKM7fYehHQWbMOjoviUdQQAu3wGaakQLOqogAkESqNlAQAYpiIAMUhABGaQgAjJIQQRkkIIIyPx/ohlWIXXfCHUAAAAASUVORK5CYII=", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from langgraph.graph import START, StateGraph\n", + "from langgraph.prebuilt import tools_condition\n", + "from langgraph.prebuilt import ToolNode\n", + "from IPython.display import Image, display\n", + "\n", + "# Graph\n", + "builder = StateGraph(MessagesState)\n", + "\n", + "# Define nodes: these do the work\n", + "builder.add_node(\"assistant\", assistant)\n", + "builder.add_node(\"tools\", ToolNode(tools))\n", + "\n", + "# Define edges: these determine how the control flow moves\n", + "builder.add_edge(START, \"assistant\")\n", + "builder.add_conditional_edges(\n", + " \"assistant\",\n", + " # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools\n", + " # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END\n", + " tools_condition,\n", + ")\n", + "builder.add_edge(\"tools\", \"assistant\")\n", + "react_graph = builder.compile()\n", + "\n", + "# Show\n", + "display(Image(react_graph.get_graph(xray=True).draw_mermaid_png()))" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "75602459-d8ca-47b4-9518-3f38343ebfe4", + "metadata": {}, + "outputs": [], + "source": [ + "messages = [HumanMessage(content=\"Add 3 and 4. Multiply the output by 2. Divide the output by 5\")]\n", + "messages = react_graph.invoke({\"messages\": messages})" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "b517142d-c40c-48bf-a5b8-c8409427aa79", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Add 3 and 4. Multiply the output by 2. Divide the output by 5\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " add (9aabb9a7-7364-4c36-80be-34aa7bc9d091)\n", + " Call ID: 9aabb9a7-7364-4c36-80be-34aa7bc9d091\n", + " Args:\n", + " a: 3.0\n", + " b: 4.0\n", + " multiply (bfe23f7e-2d85-4c90-bd7a-a7faf24c9b56)\n", + " Call ID: bfe23f7e-2d85-4c90-bd7a-a7faf24c9b56\n", + " Args:\n", + " a: 7.0\n", + " b: 2.0\n", + " divide (ebbbd39c-e48a-4c8d-9525-15af5eb8d4c9)\n", + " Call ID: ebbbd39c-e48a-4c8d-9525-15af5eb8d4c9\n", + " Args:\n", + " a: 14.0\n", + " b: 5.0\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: add\n", + "\n", + "7\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: multiply\n", + "\n", + "14\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: divide\n", + "\n", + "2.8\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "The output of adding 3 and 4 is 7. Multiplying 7 and 2 results in 14. Dividing 14 by 5 gives 2.8.\n" + ] + } + ], + "source": [ + "for m in messages['messages']:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "ad869f22-9bfb-4cbe-9f30-8a307c5cdda2", + "metadata": {}, + "source": [ + "## LangSmith\n", + "\n", + "We can look at traces in LangSmith." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "lc-academy", + "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.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/module-1/chain.ipynb b/langchain-academy/module-1/chain.ipynb similarity index 53% rename from module-1/chain.ipynb rename to langchain-academy/module-1/chain.ipynb index 778a93c57..d6e1de82c 100644 --- a/module-1/chain.ipynb +++ b/langchain-academy/module-1/chain.ipynb @@ -67,7 +67,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 8, "id": "866b5321-a238-4a9e-af9e-f11a131b5f11", "metadata": {}, "outputs": [ @@ -128,13 +128,30 @@ "metadata": {}, "outputs": [], "source": [ - "import os, getpass\n", + "# import os, getpass\n", "\n", - "def _set_env(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "# def _set_env(var: str):\n", + "# if not os.environ.get(var):\n", + "# os.environ[var] = getpass.getpass(f\"{var}: \")\n", "\n", - "_set_env(\"OPENAI_API_KEY\")" + "# _set_env(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ab863967", + "metadata": {}, + "outputs": [], + "source": [ + "# load environment variables\n", + "from dotenv import load_dotenv\n", + "import os\n", + "\n", + "load_dotenv(dotenv_path='C:\\MIConsulting\\LangChain\\langchain-academy\\.env')\n", + "\n", + "# OPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\")\n", + "GOOGLEAI_API_KEY = os.getenv(\"GOOGLEAI_API_KEY\")" ] }, { @@ -149,7 +166,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 9, "id": "95b99ad4-5753-49d3-a916-a9e949722c01", "metadata": {}, "outputs": [ @@ -159,31 +176,35 @@ "langchain_core.messages.ai.AIMessage" ] }, - "execution_count": 3, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "from langchain_openai import ChatOpenAI\n", - "llm = ChatOpenAI(model=\"gpt-4o\")\n", + "# from langchain_openai import ChatOpenAI\n", + "# llm = ChatOpenAI(model=\"gpt-4o\")\n", + "\n", + "from langchain_google_genai import ChatGoogleGenerativeAI\n", + "llm = ChatGoogleGenerativeAI(model=\"gemini-1.5-pro\", api_key=GOOGLEAI_API_KEY)\n", + "\n", "result = llm.invoke(messages)\n", "type(result)" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 11, "id": "88d60338-c892-4d04-a83f-878de4a76a6a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "AIMessage(content=\"Orcas, also known as killer whales, can be observed in several locations in the United States, but one of the best places to see them is the Pacific Northwest, particularly around the San Juan Islands in Washington State. \\n\\n### Key Locations:\\n\\n1. **San Juan Islands, Washington:**\\n - **Best Time:** The peak season is from May to September, but they can be seen year-round.\\n - **Why It's Great:** The nutrient-rich waters attract a variety of marine life, making it an ideal habitat for orcas. There are both resident and transient orca pods in the area.\\n\\n2. **Puget Sound, Washington:**\\n - **Best Time:** Summer months, primarily from May to September.\\n - **Why It's Great:** Puget Sound is home to the Southern Resident orcas, which are a unique and endangered population.\\n\\n3. **Monterey Bay, California:**\\n - **Best Time:** Spring and fall are the best times to see transient orcas.\\n - **Why It's Great:** The nutrient-rich waters of Monterey Bay attract a variety of marine life, including orcas, which come to prey on seals and sea lions.\\n\\n4. **Southeast Alaska:**\\n - **Best Time:** Summer months, from May to September.\\n - **Why It's Great:** The Inside Passage is a prime location for spotting orcas, along with humpback whales and other marine animals.\\n\\n### Tips for Whale Watching:\\n- **Tours:** Consider booking a whale-watching tour with a reputable company that follows responsible wildlife viewing guidelines.\\n- **Binoculars and Camera:** Bring binoculars for a closer look and a camera with a good zoom lens to capture the experience.\\n- **Weather:** Dress in layers and prepare for variable weather conditions, especially if you're going out on a boat.\\n\\n### Conservation Note:\\nOrcas face various threats, including habitat destruction, pollution, and reduced prey availability. Supporting conservation efforts and choosing eco-friendly tour operators can help protect these magnificent creatures for future generations.\\n\\nIs there anything more specific you’d like to know about orcas or whale watching?\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 427, 'prompt_tokens': 67, 'total_tokens': 494}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5', 'finish_reason': 'stop', 'logprobs': None}, id='run-98914d63-e629-401b-a74e-dea0e68eff03-0', usage_metadata={'input_tokens': 67, 'output_tokens': 427, 'total_tokens': 494})" + "AIMessage(content=\"The best place to see orcas in the US depends on the time of year. There are three main regions known for orca sightings:\\n\\n* **Pacific Northwest (Washington, Oregon, and British Columbia, Canada):** This is generally considered the *best* place to see orcas in the US, specifically the **Salish Sea**. The resident orcas here are most reliably seen from **April through October**, with peak season in the summer months (July-September). Locations like the San Juan Islands, Lime Kiln Point State Park, and areas around Seattle and Victoria, BC offer excellent viewing opportunities.\\n\\n* **Alaska:** Alaska offers opportunities to see transient orcas, which feed on marine mammals. **Southeast Alaska**, particularly around Juneau and Sitka, is a good spot during the **summer months**. **Resurrection Bay** near Seward is also known for orca sightings.\\n\\n* **California:** Orcas can be seen off the coast of California, though sightings are less predictable than in the Pacific Northwest. The **Monterey Bay** area is a popular spot, particularly during the **spring and fall** when orcas follow migrating gray whales. Southern California can also have sightings, but they are less frequent.\\n\\n**Here's a more detailed breakdown to help you choose:**\\n\\n* **Best Reliability:** San Juan Islands, Washington (summer months)\\n* **Best Land-Based Viewing:** Lime Kiln Point State Park, Washington (summer months)\\n* **Best for Transient Orcas:** Southeast Alaska (summer months)\\n* **Best for Whale Watching Tours:** San Juan Islands, Washington and Juneau/Sitka, Alaska (summer months)\\n* **Chance Encounters:** Monterey Bay, California (spring and fall)\\n\\n**Things to consider when planning your trip:**\\n\\n* **Time of Year:** As mentioned above, the best time to see orcas varies by location.\\n* **Type of Orca:** Resident orcas (fish-eating) are more predictable than transient orcas (mammal-eating).\\n* **Tour Operators:** Reputable whale watching tour operators increase your chances of seeing orcas and can provide educational information.\\n* **Regulations:** Be aware of regulations regarding approaching orcas, such as maintaining a safe distance.\\n\\nDo you have any other questions or would you like me to elaborate on a specific region? Perhaps you'd like to know about specific tour operators or types of whale watching experiences available?\\n\", additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-25c55652-f987-4f72-b2c2-03da6ee310b9-0', usage_metadata={'input_tokens': 46, 'output_tokens': 508, 'total_tokens': 554, 'input_token_details': {'cache_read': 0}})" ] }, - "execution_count": 4, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -194,23 +215,19 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 13, "id": "c3a29654-6b8e-4eda-9cec-22fabb9b8620", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'token_usage': {'completion_tokens': 427,\n", - " 'prompt_tokens': 67,\n", - " 'total_tokens': 494},\n", - " 'model_name': 'gpt-4o-2024-05-13',\n", - " 'system_fingerprint': 'fp_157b3831f5',\n", - " 'finish_reason': 'stop',\n", - " 'logprobs': None}" + "{'prompt_feedback': {'block_reason': 0, 'safety_ratings': []},\n", + " 'finish_reason': 'STOP',\n", + " 'safety_ratings': []}" ] }, - "execution_count": 5, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -219,6 +236,30 @@ "result.response_metadata" ] }, + { + "cell_type": "code", + "execution_count": 14, + "id": "356dfab2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'input_tokens': 46,\n", + " 'output_tokens': 508,\n", + " 'total_tokens': 554,\n", + " 'input_token_details': {'cache_read': 0}}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result.usage_metadata" + ] + }, { "attachments": {}, "cell_type": "markdown", @@ -256,7 +297,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 15, "id": "928faf56-1a1a-4c5f-b97d-bd64d8e166d1", "metadata": {}, "outputs": [], @@ -289,17 +330,17 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 19, "id": "9edbe13e-cc72-4685-ac97-2ebb4ceb2544", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_OP4qwtGk4gTIaIySPFHkpbB2', 'function': {'arguments': '{\"a\":2,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 62, 'total_tokens': 79}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-534c7657-e889-4a20-b00f-a7e9e1475d5f-0', tool_calls=[{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_OP4qwtGk4gTIaIySPFHkpbB2', 'type': 'tool_call'}], usage_metadata={'input_tokens': 62, 'output_tokens': 17, 'total_tokens': 79})" + "AIMessage(content='', additional_kwargs={'function_call': {'name': 'multiply', 'arguments': '{\"a\": 2.0, \"b\": 3.0}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-2781cca2-894b-4ea7-b045-b79f6405f9f3-0', tool_calls=[{'name': 'multiply', 'args': {'a': 2.0, 'b': 3.0}, 'id': 'c443ee9a-aa74-4118-a532-1c55ff37b8c7', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 3, 'total_tokens': 60, 'input_token_details': {'cache_read': 0}})" ] }, - "execution_count": 7, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -311,25 +352,24 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 27, "id": "a78178cb-fa43-45b5-be5e-5a22bda5a5e7", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "[{'id': 'call_OP4qwtGk4gTIaIySPFHkpbB2',\n", - " 'function': {'arguments': '{\"a\":2,\"b\":3}', 'name': 'multiply'},\n", - " 'type': 'function'}]" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "{'function_call': {'name': 'multiply', 'arguments': '{\"a\": 2.0, \"b\": 3.0}'}}\n", + "[{'name': 'multiply', 'args': {'a': 2.0, 'b': 3.0}, 'id': 'c443ee9a-aa74-4118-a532-1c55ff37b8c7', 'type': 'tool_call'}]\n" + ] } ], "source": [ - "tool_call.additional_kwargs['tool_calls']" + "# tool_call.additional_kwargs['tool_calls']\n", + "print(tool_call.additional_kwargs)\n", + "# tool_call.additional_kwargs['function_call']\n", + "print(tool_call.tool_calls)" ] }, { @@ -348,7 +388,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 28, "id": "3699dd5c-398c-43c7-b496-fd87e55e11ca", "metadata": {}, "outputs": [], @@ -390,12 +430,13 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 32, "id": "6b33eb72-3197-4870-b9a3-0da8056c40c5", "metadata": {}, "outputs": [], "source": [ "from typing import Annotated\n", + "from langchain_core.messages import AnyMessage\n", "from langgraph.graph.message import add_messages\n", "\n", "class MessagesState(TypedDict):\n", @@ -420,7 +461,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 33, "id": "9ab516ee-eab1-4856-8210-99f1fe499672", "metadata": {}, "outputs": [], @@ -442,19 +483,19 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 35, "id": "23ffea76-16a5-4053-a1bc-91e0101d91dc", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[AIMessage(content='Hello! How can I assist you?', name='Model', id='cd566566-0f42-46a4-b374-fe4d4770ffa7'),\n", - " HumanMessage(content=\"I'm looking for information on marine biology.\", name='Lance', id='9b6c4ddb-9de3-4089-8d22-077f53e7e915'),\n", - " AIMessage(content='Sure, I can help with that. What specifically are you interested in?', name='Model', id='74a549aa-8b8b-48d4-bdf1-12e98404e44e')]" + "[AIMessage(content='Hello! How can I assist you?', additional_kwargs={}, response_metadata={}, name='Model', id='378714e3-af65-4f27-8b84-b91c660679db'),\n", + " HumanMessage(content=\"I'm looking for information on marine biology.\", additional_kwargs={}, response_metadata={}, name='Lance', id='5658095e-4a5b-4277-9f7a-c72a3c1de150'),\n", + " AIMessage(content='Sure, I can help with that. What specifically are you interested in?', additional_kwargs={}, response_metadata={}, name='Model', id='c3bc1ba8-cab3-4736-b66a-c429dbcaa618')]" ] }, - "execution_count": 12, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } @@ -484,13 +525,13 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 36, "id": "b5306639-7e6a-44be-8471-8d2631701cfb", "metadata": {}, "outputs": [ { "data": { - "image/jpeg": "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", + "image/png": "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", "text/plain": [ "" ] @@ -502,6 +543,11 @@ "source": [ "from IPython.display import Image, display\n", "from langgraph.graph import StateGraph, START, END\n", + "\n", + "# State\n", + "class MessagesState(MessagesState):\n", + " # Add any keys needed beyond messages, which is pre-built\n", + " pass\n", " \n", "# Node\n", "def tool_calling_llm(state: MessagesState):\n", @@ -528,7 +574,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 37, "id": "983e2487-c0a5-40a2-afbc-aa53ff49fefc", "metadata": {}, "outputs": [ @@ -541,7 +587,7 @@ "Hello!\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "Hi there! How can I assist you today?\n" + "Hello there! How can I help you today?\n" ] } ], @@ -561,7 +607,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 38, "id": "7fe8b042-ecc8-426f-995e-cc1bbaf7cacc", "metadata": {}, "outputs": [ @@ -571,14 +617,14 @@ "text": [ "================================\u001b[1m Human Message \u001b[0m=================================\n", "\n", - "Multiply 2 and 3!\n", + "Multiply 2 and 3\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", - " multiply (call_Er4gChFoSGzU7lsuaGzfSGTQ)\n", - " Call ID: call_Er4gChFoSGzU7lsuaGzfSGTQ\n", + " multiply (41111ec3-876a-49fb-8bf0-06d13d7b983e)\n", + " Call ID: 41111ec3-876a-49fb-8bf0-06d13d7b983e\n", " Args:\n", - " a: 2\n", - " b: 3\n" + " a: 2.0\n", + " b: 3.0\n" ] } ], @@ -599,7 +645,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "lc-academy", "language": "python", "name": "python3" }, @@ -613,7 +659,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.1" + "version": "3.11.10" } }, "nbformat": 4, diff --git a/module-1/deployment.ipynb b/langchain-academy/module-1/deployment.ipynb similarity index 72% rename from module-1/deployment.ipynb rename to langchain-academy/module-1/deployment.ipynb index 1e9af8d87..be2ebb6d0 100644 --- a/module-1/deployment.ipynb +++ b/langchain-academy/module-1/deployment.ipynb @@ -109,7 +109,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "18b281d8-bd07-4721-922c-347838ceee6b", "metadata": {}, "outputs": [], @@ -119,7 +119,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "4c96f353-5dc3-41c8-a3e4-6bf07ca455f8", "metadata": {}, "outputs": [], @@ -134,26 +134,10 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "6a1352fa-68ad-4963-890e-c95d93570917", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", - " 'graph_id': 'agent',\n", - " 'created_at': '2024-08-30T23:58:18.505075+00:00',\n", - " 'updated_at': '2024-08-30T23:58:18.505075+00:00',\n", - " 'config': {},\n", - " 'metadata': {'created_by': 'system'}}" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "assistants[-3]" ] @@ -190,21 +174,10 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "f65a4480-66b3-48bf-9158-191a7b8c1c18", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'content': 'Multiply 3 by 2.', 'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'cdbd7bd8-c476-4ad4-8ab7-4ad9e3654267', 'example': False}\n", - "{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'function': {'arguments': '{\"a\":3,\"b\":2}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-06c7243c-426d-4c81-a113-f1335dda5fb2', 'example': False, 'tool_calls': [{'name': 'multiply', 'args': {'a': 3, 'b': 2}, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': None}\n", - "{'content': '6', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'multiply', 'id': '988cb170-f6e6-43c1-82fd-309f519abe6d', 'tool_call_id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'artifact': None, 'status': 'success'}\n", - "{'content': 'The result of multiplying 3 by 2 is 6.', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-7bda0aa0-6895-4250-9625-18419c5dc171', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}\n" - ] - } - ], + "outputs": [], "source": [ "from langchain_core.messages import HumanMessage\n", "\n", @@ -319,47 +292,20 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "id": "b810376e-f20f-443a-b1ca-d6793f358f82", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca',\n", - " 'graph_id': 'agent',\n", - " 'created_at': '2024-08-23T17:58:02.722920+00:00',\n", - " 'updated_at': '2024-08-23T17:58:02.722920+00:00',\n", - " 'config': {},\n", - " 'metadata': {'created_by': 'system'}}" - ] - }, - "execution_count": 38, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "agent" ] }, { "cell_type": "code", - "execution_count": 40, + "execution_count": null, "id": "32d65d84-1bcf-4af4-a7c9-55e73d6c1947", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'content': 'Multiply 3 by 2.', 'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '8ea04559-f7d4-4c82-89d9-c60fb0502f21', 'example': False}\n", - "{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_EQoolxFaaSVU8HrTnCmffLk7', 'function': {'arguments': '{\"a\":3,\"b\":2}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3aa7262c27'}, 'type': 'ai', 'name': None, 'id': 'run-b0ea5ddd-e9ba-4242-bb8c-80eb52466c76', 'example': False, 'tool_calls': [{'name': 'multiply', 'args': {'a': 3, 'b': 2}, 'id': 'call_EQoolxFaaSVU8HrTnCmffLk7', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': None}\n", - "{'content': '6', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'multiply', 'id': '1bf558e7-79ef-4f21-bb66-acafbd04677a', 'tool_call_id': 'call_EQoolxFaaSVU8HrTnCmffLk7', 'artifact': None, 'status': 'success'}\n", - "{'content': '3 multiplied by 2 equals 6.', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_3aa7262c27'}, 'type': 'ai', 'name': None, 'id': 'run-ecc4b6ad-af15-4a85-a76c-de2ed0ed8ed9', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}\n" - ] - } - ], + "outputs": [], "source": [ "from langchain_core.messages import HumanMessage\n", "\n", @@ -391,7 +337,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "lc-academy", "language": "python", "name": "python3" }, @@ -405,7 +351,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.1" + "version": "3.11.10" } }, "nbformat": 4, diff --git a/langchain-academy/module-1/langsmith_test.py b/langchain-academy/module-1/langsmith_test.py new file mode 100644 index 000000000..d0b8485ac --- /dev/null +++ b/langchain-academy/module-1/langsmith_test.py @@ -0,0 +1,28 @@ +# Library Imports +import os +from dotenv import load_dotenv +from langchain_google_genai import ChatGoogleGenerativeAI + +# Load .env file +load_dotenv( + dotenv_path='../.env', +) + +# Constants +GOOGLEAI_API_KEY = os.getenv('GOOGLEAI_API_KEY') +LANGCHAIN_API_KEY = os.getenv('LANGCHAIN_API_KEY') +LANGCHAIN_ENDPOINT = os.getenv('LANGCHAIN_ENDPOINT') +LANGCHAIN_TRACING_V2 = os.getenv('LANGCHAIN_TRACING_V2') +LANGCHAIN_PROJECT = os.getenv('LANGCHAIN_PROJECT') + +# Initialize Google Generative AI +llm = ChatGoogleGenerativeAI( + model="gemini-1.5-pro", + temperature=0, + max_tokens=None, + timeout=None, + max_retries=2, + api_key=GOOGLEAI_API_KEY, +) + +llm.invoke("Hello, world!") \ No newline at end of file diff --git a/langchain-academy/module-1/router.ipynb b/langchain-academy/module-1/router.ipynb new file mode 100644 index 000000000..729a94d65 --- /dev/null +++ b/langchain-academy/module-1/router.ipynb @@ -0,0 +1,285 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cb354baf", + "metadata": {}, + "source": [ + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-1/router.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239412-lesson-5-router)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "ce6fff79-25b5-4884-8aaa-e3ebb7ddd549", + "metadata": {}, + "source": [ + "# Router\n", + "\n", + "## Review\n", + "\n", + "We built a graph that uses `messages` as state and a chat model with bound tools.\n", + "\n", + "We saw that the graph can:\n", + "\n", + "* Return a tool call\n", + "* Return a natural language response\n", + "\n", + "## Goals\n", + "\n", + "We can think of this as a router, where the chat model routes between a direct response or a tool call based upon the user input.\n", + "\n", + "This is an simple example of an agent, where the LLM is directing the control flow either by calling a tool or just responding directly. \n", + "\n", + "![Screenshot 2024-08-21 at 9.24.09 AM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbac6543c3d4df239a4ed1_router1.png)\n", + "\n", + "Let's extend our graph to work with either output! \n", + "\n", + "For this, we can use two ideas:\n", + "\n", + "(1) Add a node that will call our tool.\n", + "\n", + "(2) Add a conditional edge that will look at the chat model model output, and route to our tool calling node or simply end if no tool call is performed. \n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ebb4fc6e-7c85-4fc8-a4a9-0c7a527c4e5b", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install --quiet -U langchain_openai langchain_core langgraph" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "885e92d9", + "metadata": {}, + "outputs": [], + "source": [ + "# import os, getpass\n", + "\n", + "# def _set_env(var: str):\n", + "# if not os.environ.get(var):\n", + "# os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "\n", + "# _set_env(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "2c91710d", + "metadata": {}, + "outputs": [], + "source": [ + "# load environment variables\n", + "from dotenv import load_dotenv\n", + "import os\n", + "\n", + "load_dotenv(dotenv_path='C:\\MIConsulting\\LangChain\\langchain-academy\\.env')\n", + "\n", + "# OPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\")\n", + "GOOGLEAI_API_KEY = os.getenv(\"GOOGLEAI_API_KEY\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e3ba4df4-3045-49b1-9299-ced1fce14d24", + "metadata": {}, + "outputs": [], + "source": [ + "# from langchain_openai import ChatOpenAI\n", + "from langchain_google_genai import ChatGoogleGenerativeAI\n", + "\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", + "# llm = ChatOpenAI(model=\"gpt-4o\")\n", + "llm = ChatGoogleGenerativeAI(model=\"gemini-1.5-pro\", api_key=GOOGLEAI_API_KEY)\n", + "llm_with_tools = llm.bind_tools([multiply])" + ] + }, + { + "cell_type": "markdown", + "id": "c77555a2", + "metadata": {}, + "source": [ + " We use the [built-in `ToolNode`](https://langchain-ai.github.io/langgraph/reference/prebuilt/?h=tools+condition#toolnode) and simply pass a list of our tools to initialize it. \n", + " \n", + " We use the [built-in `tools_condition`](https://langchain-ai.github.io/langgraph/reference/prebuilt/?h=tools+condition#tools_condition) as our conditional edge." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "9a6fde4e-cceb-4426-b770-97ee4b41e9da", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "from langgraph.graph import StateGraph, START, END\n", + "from langgraph.graph import MessagesState\n", + "from langgraph.prebuilt import ToolNode\n", + "from langgraph.prebuilt import tools_condition\n", + "\n", + "# Node\n", + "def tool_calling_llm(state: MessagesState):\n", + " return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n", + "\n", + "# Build graph\n", + "builder = StateGraph(MessagesState)\n", + "builder.add_node(\"tool_calling_llm\", tool_calling_llm)\n", + "builder.add_node(\"tools\", ToolNode([multiply]))\n", + "builder.add_edge(START, \"tool_calling_llm\")\n", + "builder.add_conditional_edges(\n", + " \"tool_calling_llm\",\n", + " # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools\n", + " # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END\n", + " tools_condition,\n", + ")\n", + "builder.add_edge(\"tools\", END)\n", + "graph = builder.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "11b608c5-0c15-4fb7-aa24-80ce5774fb85", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Hello world.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Hello to you too!\n" + ] + } + ], + "source": [ + "from langchain_core.messages import HumanMessage\n", + "messages = [HumanMessage(content=\"Hello world.\")]\n", + "messages = graph.invoke({\"messages\": messages})\n", + "for m in messages['messages']:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "c54b1868", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Multiply 3 and 4.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " multiply (1dd3cc5a-ba89-44fa-8b44-3d8e32544b85)\n", + " Call ID: 1dd3cc5a-ba89-44fa-8b44-3d8e32544b85\n", + " Args:\n", + " a: 3.0\n", + " b: 4.0\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: multiply\n", + "\n", + "12\n" + ] + } + ], + "source": [ + "from langchain_core.messages import HumanMessage\n", + "messages = [HumanMessage(content=\"Multiply 3 and 4.\")]\n", + "messages = graph.invoke({\"messages\": messages})\n", + "for m in messages['messages']:\n", + " m.pretty_print()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "34708377-16b6-4474-9e23-71890c1fb36e", + "metadata": {}, + "source": [ + "Now, we can see that the graph runs the tool!\n", + "\n", + "It responds with a `ToolMessage`. \n", + "\n", + "## LangGraph Studio\n", + "\n", + "--\n", + "\n", + "**⚠️ DISCLAIMER**\n", + "\n", + "*Running Studio currently requires a Mac. If you are not using a Mac, then skip this step.*\n", + "\n", + "--\n", + "\n", + "Load the `router` in Studio, which uses `module-1/studio/router.py` set in `module-1/studio/langgraph.json`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43782c33-0f41-47f2-ae38-ddb2cd4ba6f8", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "lc-academy", + "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.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/langchain-academy/module-1/simple-graph.ipynb b/langchain-academy/module-1/simple-graph.ipynb new file mode 100644 index 000000000..371b246c3 --- /dev/null +++ b/langchain-academy/module-1/simple-graph.ipynb @@ -0,0 +1,330 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8d5f3703", + "metadata": {}, + "source": [ + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-1/simple-graph.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58238187-lesson-2-simple-graph)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "50fa7f8a-8764-4bb9-9968-48b681a0e4f1", + "metadata": {}, + "source": [ + "# The Simplest Graph\n", + "\n", + "Let's build a simple graph with 3 nodes and one conditional edge. \n", + "\n", + "![Screenshot 2024-08-20 at 3.11.22 PM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dba5f465f6e9a2482ad935_simple-graph1.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ff151ef1-fa30-482a-94da-8f49964afbc3", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install --quiet -U langgraph" + ] + }, + { + "cell_type": "markdown", + "id": "5999f8d0-989f-4638-8ade-5c257cbadfe8", + "metadata": {}, + "source": [ + "## State\n", + "\n", + "First, define the [State](https://langchain-ai.github.io/langgraph/concepts/low_level/#state) of the graph. \n", + "\n", + "The State schema serves as the input schema for all Nodes and Edges in the graph.\n", + "\n", + "Let's use the `TypedDict` class from python's `typing` module as our schema, which provides type hints for the keys." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "6a90709b-ddfa-4671-8acc-c59969a29991", + "metadata": {}, + "outputs": [], + "source": [ + "from typing_extensions import TypedDict\n", + "\n", + "class State(TypedDict):\n", + " graph_state: str" + ] + }, + { + "cell_type": "markdown", + "id": "888509e1-cbde-4c03-99a0-2560dd2e262d", + "metadata": {}, + "source": [ + "## Nodes\n", + "\n", + "[Nodes](https://langchain-ai.github.io/langgraph/concepts/low_level/#nodes) are just python functions.\n", + "\n", + "The first positional argument is the state, as defined above.\n", + "\n", + "Because the state is a `TypedDict` with schema as defined above, each node can access the key, `graph_state`, with `state['graph_state']`.\n", + "\n", + "Each node returns a new value of the state key `graph_state`.\n", + " \n", + "By default, the new value returned by each node [will override](https://langchain-ai.github.io/langgraph/concepts/low_level/#reducers) the prior state value." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e8aabcb7-494c-4d35-be08-f81c76d75a6b", + "metadata": {}, + "outputs": [], + "source": [ + "def node_1(state):\n", + " print(\"---Node 1---\")\n", + " return {\"graph_state\": state['graph_state'] +\" I am\"}\n", + "\n", + "def node_2(state):\n", + " print(\"---Node 2---\")\n", + " return {\"graph_state\": state['graph_state'] +\" happy!\"}\n", + "\n", + "def node_3(state):\n", + " print(\"---Node 3---\")\n", + " return {\"graph_state\": state['graph_state'] +\" sad!\"}" + ] + }, + { + "cell_type": "markdown", + "id": "ad056608-8c8f-4999-bb53-10583efa4ed8", + "metadata": {}, + "source": [ + "## Edges\n", + "\n", + "[Edges](https://langchain-ai.github.io/langgraph/concepts/low_level/#edges) connect the nodes.\n", + "\n", + "Normal Edges are used if you want to *always* go from, for example, `node_1` to `node_2`.\n", + "\n", + "[Conditional Edges](https://langchain-ai.github.io/langgraph/reference/graphs/?h=conditional+edge#langgraph.graph.StateGraph.add_conditional_edges) are used want to *optionally* route between nodes.\n", + " \n", + "Conditional edges are implemented as functions that return the next node to visit based upon some logic." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "7e53543a-902a-4d41-ad3d-25eee260e819", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "from typing import Literal\n", + "\n", + "def decide_mood(state) -> Literal[\"node_2\", \"node_3\"]:\n", + " \n", + " # Often, we will use state to decide on the next node to visit\n", + " user_input = state['graph_state'] \n", + " \n", + " # Here, let's just do a 50 / 50 split between nodes 2, 3\n", + " if random.random() < 0.5:\n", + "\n", + " # 50% of the time, we return Node 2\n", + " return \"node_2\"\n", + " \n", + " # 50% of the time, we return Node 3\n", + " return \"node_3\"" + ] + }, + { + "cell_type": "markdown", + "id": "9282ea7a-5ed2-4641-bed8-c3472d54c951", + "metadata": {}, + "source": [ + "## Graph Construction\n", + "\n", + "Now, we build the graph from our [components](\n", + "https://langchain-ai.github.io/langgraph/concepts/low_level/) defined above.\n", + "\n", + "The [StateGraph class](https://langchain-ai.github.io/langgraph/concepts/low_level/#stategraph) is the graph class that we can use.\n", + " \n", + "First, we initialize a StateGraph with the `State` class we defined above.\n", + " \n", + "Then, we add our nodes and edges.\n", + "\n", + "We use the [`START` Node, a special node](https://langchain-ai.github.io/langgraph/concepts/low_level/#start-node) that sends user input to the graph, to indicate where to start our graph.\n", + " \n", + "The [`END` Node](https://langchain-ai.github.io/langgraph/concepts/low_level/#end-node) is a special node that represents a terminal node. \n", + "\n", + "Finally, we [compile our graph](https://langchain-ai.github.io/langgraph/concepts/low_level/#compiling-your-graph) to perform a few basic checks on the graph structure. \n", + "\n", + "We can visualize the graph as a [Mermaid diagram](https://github.com/mermaid-js/mermaid)." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7deb0359-55c1-4545-b52e-8252994befbb", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "from langgraph.graph import StateGraph, START, END\n", + "\n", + "# Build graph\n", + "builder = StateGraph(State)\n", + "builder.add_node(\"node_1\", node_1)\n", + "builder.add_node(\"node_2\", node_2)\n", + "builder.add_node(\"node_3\", node_3)\n", + "\n", + "# Logic\n", + "builder.add_edge(START, \"node_1\")\n", + "builder.add_conditional_edges(\"node_1\", decide_mood)\n", + "builder.add_edge(\"node_2\", END)\n", + "builder.add_edge(\"node_3\", END)\n", + "\n", + "# Add\n", + "graph = builder.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "markdown", + "id": "00617c74-2647-44ea-8a2e-310dd96c0d26", + "metadata": {}, + "source": [ + "## Graph Invocation\n", + "\n", + "The compiled graph implements the [runnable](https://python.langchain.com/v0.1/docs/expression_language/interface/) protocol.\n", + "\n", + "This provides a standard way to execute LangChain components. \n", + " \n", + "`invoke` is one of the standard methods in this interface.\n", + "\n", + "The input is a dictionary `{\"graph_state\": \"Hi, this is lance.\"}`, which sets the initial value for our graph state dict.\n", + "\n", + "When `invoke` is called, the graph starts execution from the `START` node.\n", + "\n", + "It progresses through the defined nodes (`node_1`, `node_2`, `node_3`) in order.\n", + "\n", + "The conditional edge will traverse from node `1` to node `2` or `3` using a 50/50 decision rule. \n", + "\n", + "Each node function receives the current state and returns a new value, which overrides the graph state.\n", + "\n", + "The execution continues until it reaches the `END` node." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e895f17a-e835-4e8a-8e1b-63fe6d27cc52", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---Node 1---\n", + "---Node 3---\n" + ] + }, + { + "data": { + "text/plain": [ + "{'graph_state': 'Hi, this is Lance. I am sad!'}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.invoke({\"graph_state\" : \"Hi, this is Lance.\"})" + ] + }, + { + "cell_type": "markdown", + "id": "082399c3-18bd-4b67-97c1-2005f268abc5", + "metadata": {}, + "source": [ + "`invoke` runs the entire graph synchronously.\n", + "\n", + "This waits for each step to complete before moving to the next.\n", + "\n", + "It returns the final state of the graph after all nodes have executed.\n", + "\n", + "In this case, it returns the state after `node_3` has completed: \n", + "\n", + "```\n", + "{'graph_state': 'Hi, this is Lance. I am sad!'}\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "db16ab8d-b817-4f3a-befc-a02b579c4fca", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---Node 1---\n", + "---Node 3---\n" + ] + }, + { + "data": { + "text/plain": [ + "{'graph_state': 'Hi, this is Bart. I am sad!'}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.invoke({\"graph_state\" : \"Hi, this is Bart.\"})" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "lc-academy", + "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.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/module-1/studio/.env.example b/langchain-academy/module-1/studio/.env.example similarity index 100% rename from module-1/studio/.env.example rename to langchain-academy/module-1/studio/.env.example diff --git a/module-1/studio/agent.py b/langchain-academy/module-1/studio/agent.py similarity index 100% rename from module-1/studio/agent.py rename to langchain-academy/module-1/studio/agent.py diff --git a/module-1/studio/langgraph.json b/langchain-academy/module-1/studio/langgraph.json similarity index 100% rename from module-1/studio/langgraph.json rename to langchain-academy/module-1/studio/langgraph.json diff --git a/module-1/studio/requirements.txt b/langchain-academy/module-1/studio/requirements.txt similarity index 100% rename from module-1/studio/requirements.txt rename to langchain-academy/module-1/studio/requirements.txt diff --git a/module-1/studio/router.py b/langchain-academy/module-1/studio/router.py similarity index 100% rename from module-1/studio/router.py rename to langchain-academy/module-1/studio/router.py diff --git a/module-1/studio/simple.py b/langchain-academy/module-1/studio/simple.py similarity index 100% rename from module-1/studio/simple.py rename to langchain-academy/module-1/studio/simple.py diff --git a/langchain-academy/module-2/chatbot-external-memory.ipynb b/langchain-academy/module-2/chatbot-external-memory.ipynb new file mode 100644 index 000000000..26fc90cd4 --- /dev/null +++ b/langchain-academy/module-2/chatbot-external-memory.ipynb @@ -0,0 +1,470 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cf7ccb32", + "metadata": {}, + "source": [ + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-2/chatbot-external-memory.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239440-lesson-6-chatbot-w-summarizing-messages-and-external-memory)" + ] + }, + { + "cell_type": "markdown", + "id": "af6c7afe-1037-41ab-98e4-494692e47402", + "metadata": {}, + "source": [ + "# Chatbot with message summarization & external DB memory\n", + "\n", + "## Review\n", + "\n", + "We've covered how to customize graph state schema and reducer. \n", + " \n", + "We've also shown a number of tricks for trimming or filtering messages in graph state. \n", + "\n", + "We've used these concepts in a Chatbot with memory that produces a running summary of the conversation.\n", + "\n", + "## Goals\n", + "\n", + "But, what if we want our Chatbot to have memory that persists indefinitely?\n", + "\n", + "Now, we'll introduce some more advanced checkpointers that support external databases. \n", + "\n", + "Here, we'll show how to use [Sqlite as a checkpointer](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer), but other checkpointers, such as [Postgres](https://langchain-ai.github.io/langgraph/how-tos/persistence_postgres/) are available!" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "85ed78d9-6ca2-45ac-96a9-52e341ec519d", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install --quiet -U langgraph-checkpoint-sqlite langchain_core langgraph langchain_openai" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2e10c4d4", + "metadata": {}, + "outputs": [], + "source": [ + "# import os, getpass\n", + "\n", + "# def _set_env(var: str):\n", + "# if not os.environ.get(var):\n", + "# os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "\n", + "# _set_env(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "markdown", + "id": "b40d25c0-e9b5-4854-bf07-3cc3ff07122e", + "metadata": {}, + "source": [ + "## Sqlite\n", + "\n", + "A good starting point here is the [SqliteSaver checkpointer](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer).\n", + "\n", + "Sqlite is a [small, fast, highly popular](https://x.com/karpathy/status/1819490455664685297) SQL database. \n", + " \n", + "If we supply `\":memory:\"` it creates an in-memory Sqlite database." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "fae15402-17ae-4e89-8ecf-4c89e08b22fe", + "metadata": {}, + "outputs": [], + "source": [ + "import sqlite3\n", + "# In memory\n", + "conn = sqlite3.connect(\":memory:\", check_same_thread = False)" + ] + }, + { + "cell_type": "markdown", + "id": "c2bf53ec-6d4a-42ce-8183-344795eed403", + "metadata": {}, + "source": [ + "But, if we supply a db path, then it will create a database for us!" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "58339167-920c-4994-a0a7-0a9c5d4f7cf7", + "metadata": {}, + "outputs": [], + "source": [ + "# pull file if it doesn't exist and connect to local db\n", + "# !mkdir -p state_db && [ ! -f state_db/example.db ] && wget -P state_db https://github.com/langchain-ai/langchain-academy/raw/main/module-2/state_db/example.db\n", + "\n", + "db_path = \"state_db/example.db\"\n", + "conn = sqlite3.connect(db_path, check_same_thread=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "3c7736b6-a750-48f8-a838-8e7616b12250", + "metadata": {}, + "outputs": [], + "source": [ + "# Here is our checkpointer \n", + "from langgraph.checkpoint.sqlite import SqliteSaver\n", + "memory = SqliteSaver(conn)" + ] + }, + { + "cell_type": "markdown", + "id": "9d8cb629-213f-4b87-965e-19b812c42da1", + "metadata": {}, + "source": [ + "Let's re-define our chatbot." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5a3ae800", + "metadata": {}, + "outputs": [], + "source": [ + "# load environment variables\n", + "from dotenv import load_dotenv\n", + "import os\n", + "\n", + "load_dotenv(dotenv_path='C:\\MIConsulting\\LangChain\\langchain-academy\\.env')\n", + "\n", + "# OPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\")\n", + "GOOGLEAI_API_KEY = os.getenv(\"GOOGLEAI_API_KEY\")\n", + "LANGCHAIN_API_KEY = os.getenv('LANGCHAIN_API_KEY')\n", + "LANGCHAIN_ENDPOINT = os.getenv('LANGCHAIN_ENDPOINT')\n", + "LANGCHAIN_TRACING_V2 = os.getenv('LANGCHAIN_TRACING_V2')\n", + "LANGCHAIN_PROJECT = os.getenv('LANGCHAIN_PROJECT')" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "dc414e29-2078-41a0-887c-af1a6a3d72c0", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\Users\\jmoyano\\AppData\\Local\\miniconda3\\envs\\lc-academy\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + } + ], + "source": [ + "# from langchain_openai import ChatOpenAI\n", + "from langchain_google_genai import ChatGoogleGenerativeAI\n", + "from langchain_core.messages import SystemMessage, HumanMessage, RemoveMessage\n", + "\n", + "from langgraph.graph import END\n", + "from langgraph.graph import MessagesState\n", + "\n", + "# model = ChatOpenAI(model=\"gpt-4o\",temperature=0)\n", + "model = ChatGoogleGenerativeAI(\n", + " model=\"gemini-1.5-pro\",\n", + " api_key=GOOGLEAI_API_KEY,\n", + " temperature=0,\n", + ")\n", + "\n", + "class State(MessagesState):\n", + " summary: str\n", + "\n", + "# Define the logic to call the model\n", + "def call_model(state: State):\n", + " \n", + " # Get summary if it exists\n", + " summary = state.get(\"summary\", \"\")\n", + "\n", + " # If there is summary, then we add it\n", + " if summary:\n", + " \n", + " # Add summary to system message\n", + " system_message = f\"Summary of conversation earlier: {summary}\"\n", + "\n", + " # Append summary to any newer messages\n", + " messages = [SystemMessage(content=system_message)] + state[\"messages\"]\n", + " \n", + " else:\n", + " messages = state[\"messages\"]\n", + " \n", + " response = model.invoke(messages)\n", + " return {\"messages\": response}\n", + "\n", + "def summarize_conversation(state: State):\n", + " \n", + " # First, we get any existing summary\n", + " summary = state.get(\"summary\", \"\")\n", + "\n", + " # Create our summarization prompt \n", + " if summary:\n", + " \n", + " # A summary already exists\n", + " summary_message = (\n", + " f\"This is summary of the conversation to date: {summary}\\n\\n\"\n", + " \"Extend the summary by taking into account the new messages above:\"\n", + " )\n", + " \n", + " else:\n", + " summary_message = \"Create a summary of the conversation above:\"\n", + "\n", + " # Add prompt to our history\n", + " messages = state[\"messages\"] + [HumanMessage(content=summary_message)]\n", + " response = model.invoke(messages)\n", + " \n", + " # Delete all but the 2 most recent messages\n", + " delete_messages = [RemoveMessage(id=m.id) for m in state[\"messages\"][:-2]]\n", + " return {\"summary\": response.content, \"messages\": delete_messages}\n", + "\n", + "# Determine whether to end or summarize the conversation\n", + "def should_continue(state: State):\n", + " \n", + " \"\"\"Return the next node to execute.\"\"\"\n", + " \n", + " messages = state[\"messages\"]\n", + " \n", + " # If there are more than six messages, then we summarize the conversation\n", + " if len(messages) > 6:\n", + " return \"summarize_conversation\"\n", + " \n", + " # Otherwise we can just end\n", + " return END" + ] + }, + { + "cell_type": "markdown", + "id": "41c13c0b-a383-4f73-9cc1-63f0eed8f190", + "metadata": {}, + "source": [ + "Now, we just re-compile with our sqlite checkpointer." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e867fd95-91eb-4ce1-82fc-bb72d611a96d", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "from langgraph.graph import StateGraph, START\n", + "\n", + "# Define a new graph\n", + "workflow = StateGraph(State)\n", + "workflow.add_node(\"conversation\", call_model)\n", + "workflow.add_node(summarize_conversation)\n", + "\n", + "# Set the entrypoint as conversation\n", + "workflow.add_edge(START, \"conversation\")\n", + "workflow.add_conditional_edges(\"conversation\", should_continue)\n", + "workflow.add_edge(\"summarize_conversation\", END)\n", + "\n", + "# Compile\n", + "graph = workflow.compile(checkpointer=memory)\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "markdown", + "id": "8769db99-3938-45e6-a594-56beb18d6c45", + "metadata": {}, + "source": [ + "Now, we can invoke the graph several times. " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "0f4094a0-d240-4be8-903a-7d9f605bdc5c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Hi Lance! It's nice to hear from you again. It seems like you're a 49ers fan. Perhaps we can talk about them. What aspects of the team are you most interested in discussing?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Your name is Lance. Do you want to talk about the 49ers, Lance?\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Retrying langchain_google_genai.chat_models._chat_with_retry.._chat_with_retry in 2.0 seconds as it raised ResourceExhausted: 429 Resource has been exhausted (e.g. check quota)..\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "That's great to hear, Lance! Is there anything specific about the 49ers you'd like to discuss? We could talk about their current roster, their upcoming season, their history, or even their rivals. What are you most interested in?\n" + ] + } + ], + "source": [ + "# Create a thread\n", + "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", + "\n", + "# Start conversation\n", + "input_message = HumanMessage(content=\"hi! I'm Lance\")\n", + "output = graph.invoke({\"messages\": [input_message]}, config) \n", + "for m in output['messages'][-1:]:\n", + " m.pretty_print()\n", + "\n", + "input_message = HumanMessage(content=\"what's my name?\")\n", + "output = graph.invoke({\"messages\": [input_message]}, config) \n", + "for m in output['messages'][-1:]:\n", + " m.pretty_print()\n", + "\n", + "input_message = HumanMessage(content=\"i like the 49ers!\")\n", + "output = graph.invoke({\"messages\": [input_message]}, config) \n", + "for m in output['messages'][-1:]:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "c0f3e842-4497-45e2-a924-69672a9bcb33", + "metadata": {}, + "source": [ + "Let's confirm that our state is saved locally." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "d2ab158a-5a82-417a-8841-730a4cc18ea7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "StateSnapshot(values={'messages': [HumanMessage(content=\"hi! I'm Lance\", additional_kwargs={}, response_metadata={}, id='38898c65-7d4f-4535-9211-d01af90c36f9'), AIMessage(content=\"Hi Lance! It's nice to hear from you again. It seems like you're a 49ers fan. Perhaps we can talk about them. What aspects of the team are you most interested in discussing?\\n\", additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-ce751c73-7191-4086-9846-7cd2b3b6307b-0', usage_metadata={'input_tokens': 308, 'output_tokens': 49, 'total_tokens': 357, 'input_token_details': {'cache_read': 0}}), HumanMessage(content=\"what's my name?\", additional_kwargs={}, response_metadata={}, id='80e0f11d-57f7-44fc-9171-3819e50b5aa4'), AIMessage(content='Your name is Lance. Do you want to talk about the 49ers, Lance?\\n', additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-b199ec6c-ea03-411b-8790-2d8af8cbb56d-0', usage_metadata={'input_tokens': 250, 'output_tokens': 21, 'total_tokens': 271, 'input_token_details': {'cache_read': 0}}), HumanMessage(content='i like the 49ers!', additional_kwargs={}, response_metadata={}, id='ce712e8d-204d-4a2e-81b6-aa4fb3631e5e'), AIMessage(content=\"That's great to hear, Lance! Is there anything specific about the 49ers you'd like to discuss? We could talk about their current roster, their upcoming season, their history, or even their rivals. What are you most interested in?\\n\", additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-0c9a455f-7781-48b0-b445-1b1125722a10-0', usage_metadata={'input_tokens': 281, 'output_tokens': 57, 'total_tokens': 338, 'input_token_details': {'cache_read': 0}})], 'summary': \"The conversation continues to be dominated by Lance repeatedly introducing himself, now *four* times. He continues to express his affinity for the 49ers, but still doesn't engage with the AI's attempts to delve deeper into that interest. The AI maintains its polite and helpful demeanor, consistently acknowledging Lance's name and reiterating its willingness to discuss various aspects of the 49ers. The core issue persists: Lance's repetitive introductions prevent the conversation from progressing beyond the initial pleasantries and the statement of interest in the team. This reinforces the observation that Lance may be struggling to maintain the conversational thread or may not be fully engaging with the AI's prompts, despite the AI's best efforts to facilitate a more meaningful exchange about the 49ers. The summary now demonstrates an even more pronounced pattern of circularity and lack of substantive interaction.\\n\"}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efce7c4-0b14-6dd1-8025-c1257672c796'}}, metadata={'source': 'loop', 'writes': {'conversation': {'messages': AIMessage(content=\"That's great to hear, Lance! Is there anything specific about the 49ers you'd like to discuss? We could talk about their current roster, their upcoming season, their history, or even their rivals. What are you most interested in?\\n\", additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-0c9a455f-7781-48b0-b445-1b1125722a10-0', usage_metadata={'input_tokens': 281, 'output_tokens': 57, 'total_tokens': 338, 'input_token_details': {'cache_read': 0}})}}, 'step': 37, 'parents': {}}, created_at='2025-01-09T11:24:12.840289+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efce7c3-e571-680f-8024-62b5d42ad46f'}}, tasks=())" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", + "graph_state = graph.get_state(config)\n", + "graph_state" + ] + }, + { + "cell_type": "markdown", + "id": "1e21152d-ed9c-408d-b7d5-f634c9ce81e2", + "metadata": {}, + "source": [ + "### Persisting state\n", + "\n", + "Using database like Sqlite means state is persisted! \n", + "\n", + "For example, we can re-start the notebook kernel and see that we can still load from Sqlite DB on disk.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "b9a44dc5-be04-45fa-a6fc-27b0f8ee4678", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "StateSnapshot(values={'messages': [HumanMessage(content=\"hi! I'm Lance\", additional_kwargs={}, response_metadata={}, id='38898c65-7d4f-4535-9211-d01af90c36f9'), AIMessage(content=\"Hi Lance! It's nice to hear from you again. It seems like you're a 49ers fan. Perhaps we can talk about them. What aspects of the team are you most interested in discussing?\\n\", additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-ce751c73-7191-4086-9846-7cd2b3b6307b-0', usage_metadata={'input_tokens': 308, 'output_tokens': 49, 'total_tokens': 357, 'input_token_details': {'cache_read': 0}}), HumanMessage(content=\"what's my name?\", additional_kwargs={}, response_metadata={}, id='80e0f11d-57f7-44fc-9171-3819e50b5aa4'), AIMessage(content='Your name is Lance. Do you want to talk about the 49ers, Lance?\\n', additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-b199ec6c-ea03-411b-8790-2d8af8cbb56d-0', usage_metadata={'input_tokens': 250, 'output_tokens': 21, 'total_tokens': 271, 'input_token_details': {'cache_read': 0}}), HumanMessage(content='i like the 49ers!', additional_kwargs={}, response_metadata={}, id='ce712e8d-204d-4a2e-81b6-aa4fb3631e5e'), AIMessage(content=\"That's great to hear, Lance! Is there anything specific about the 49ers you'd like to discuss? We could talk about their current roster, their upcoming season, their history, or even their rivals. What are you most interested in?\\n\", additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-0c9a455f-7781-48b0-b445-1b1125722a10-0', usage_metadata={'input_tokens': 281, 'output_tokens': 57, 'total_tokens': 338, 'input_token_details': {'cache_read': 0}})], 'summary': \"The conversation continues to be dominated by Lance repeatedly introducing himself, now *four* times. He continues to express his affinity for the 49ers, but still doesn't engage with the AI's attempts to delve deeper into that interest. The AI maintains its polite and helpful demeanor, consistently acknowledging Lance's name and reiterating its willingness to discuss various aspects of the 49ers. The core issue persists: Lance's repetitive introductions prevent the conversation from progressing beyond the initial pleasantries and the statement of interest in the team. This reinforces the observation that Lance may be struggling to maintain the conversational thread or may not be fully engaging with the AI's prompts, despite the AI's best efforts to facilitate a more meaningful exchange about the 49ers. The summary now demonstrates an even more pronounced pattern of circularity and lack of substantive interaction.\\n\"}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efce7c4-0b14-6dd1-8025-c1257672c796'}}, metadata={'source': 'loop', 'writes': {'conversation': {'messages': AIMessage(content=\"That's great to hear, Lance! Is there anything specific about the 49ers you'd like to discuss? We could talk about their current roster, their upcoming season, their history, or even their rivals. What are you most interested in?\\n\", additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-0c9a455f-7781-48b0-b445-1b1125722a10-0', usage_metadata={'input_tokens': 281, 'output_tokens': 57, 'total_tokens': 338, 'input_token_details': {'cache_read': 0}})}}, 'step': 37, 'parents': {}}, created_at='2025-01-09T11:24:12.840289+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efce7c3-e571-680f-8024-62b5d42ad46f'}}, tasks=())" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create a thread\n", + "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", + "graph_state = graph.get_state(config)\n", + "graph_state" + ] + }, + { + "cell_type": "markdown", + "id": "8466e418-1a46-4cdb-a51a-6ae14281bb85", + "metadata": {}, + "source": [ + "## LangGraph Studio\n", + "\n", + "--\n", + "\n", + "**⚠️ DISCLAIMER**\n", + "\n", + "*Running Studio currently requires a Mac. If you are not using a Mac, then skip this step.*\n", + "\n", + "--\n", + "\n", + "Now that we better understand external memory, recall that the LangGraph API packages your code and provides you with with built-in persistence.\n", + " \n", + "And the API is the back-end for Studio! \n", + "\n", + "Load the `chatbot` in the UI, which uses `module2-/studio/chatbot.py` set in `module2-/studio/langgraph.json`." + ] + }, + { + "cell_type": "markdown", + "id": "c4916d8b", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "lc-academy", + "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.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/langchain-academy/module-2/chatbot-summarization.ipynb b/langchain-academy/module-2/chatbot-summarization.ipynb new file mode 100644 index 000000000..449af7414 --- /dev/null +++ b/langchain-academy/module-2/chatbot-summarization.ipynb @@ -0,0 +1,588 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "83fcadf3", + "metadata": {}, + "source": [ + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-2/chatbot-summarization.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239436-lesson-5-chatbot-w-summarizing-messages-and-memory)" + ] + }, + { + "cell_type": "markdown", + "id": "b651ead9-5504-45ee-938d-f91ac78dddd1", + "metadata": {}, + "source": [ + "# Chatbot with message summarization\n", + "\n", + "## Review\n", + "\n", + "We've covered how to customize graph state schema and reducer. \n", + " \n", + "We've also shown a number of ways to trim or filter messages in graph state. \n", + "\n", + "## Goals\n", + "\n", + "Now, let's take it one step further! \n", + "\n", + "Rather than just trimming or filtering messages, we'll show how to use LLMs to produce a running summary of the conversation.\n", + " \n", + "This allows us to retain a compressed representation of the full conversation, rather than just removing it with trimming or filtering.\n", + "\n", + "We'll incorporate this summarization into a simple Chatbot. \n", + "\n", + "And we'll equip that Chatbot with memory, supporting long-running conversations without incurring high token cost / latency. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "000a6daa-92ad-4e57-a060-d1c81176eb0d", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install --quiet -U langchain_core langgraph langchain_openai" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "09201a62", + "metadata": {}, + "outputs": [], + "source": [ + "# import os, getpass\n", + "\n", + "# def _set_env(var: str):\n", + "# if not os.environ.get(var):\n", + "# os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "\n", + "# _set_env(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "markdown", + "id": "dfddfce9-3a9b-4b35-a76d-28265515aabd", + "metadata": {}, + "source": [ + "We'll use [LangSmith](https://docs.smith.langchain.com/) for [tracing](https://docs.smith.langchain.com/concepts/tracing)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "464856d4", + "metadata": {}, + "outputs": [], + "source": [ + "# _set_env(\"LANGCHAIN_API_KEY\")\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_PROJECT\"] = \"langchain-academy\"" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "97b89e2f", + "metadata": {}, + "outputs": [], + "source": [ + "# load environment variables\n", + "from dotenv import load_dotenv\n", + "import os\n", + "\n", + "load_dotenv(dotenv_path='C:\\MIConsulting\\LangChain\\langchain-academy\\.env')\n", + "\n", + "# OPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\")\n", + "GOOGLEAI_API_KEY = os.getenv(\"GOOGLEAI_API_KEY\")\n", + "LANGCHAIN_API_KEY = os.getenv('LANGCHAIN_API_KEY')\n", + "LANGCHAIN_ENDPOINT = os.getenv('LANGCHAIN_ENDPOINT')\n", + "LANGCHAIN_TRACING_V2 = os.getenv('LANGCHAIN_TRACING_V2')\n", + "LANGCHAIN_PROJECT = os.getenv('LANGCHAIN_PROJECT')" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "537ade30-6a0e-4b6b-8bcd-ce90790b6392", + "metadata": {}, + "outputs": [], + "source": [ + "# from langchain_openai import ChatOpenAI\n", + "# model = ChatOpenAI(model=\"gpt-4o\",temperature=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5661bb6d", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_google_genai import ChatGoogleGenerativeAI\n", + "model = ChatGoogleGenerativeAI(\n", + " model=\"gemini-1.5-pro\",\n", + " api_key=GOOGLEAI_API_KEY,\n", + " temperature=0\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "db3afac3-8b7a-45db-a3c1-7e4125c1bc8b", + "metadata": {}, + "source": [ + "We'll use `MessagesState`, as before.\n", + "\n", + "In addition to the built-in `messages` key, we'll now include a custom key (`summary`)." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "948e60f0-5c76-4235-b40e-cf523205d40e", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import MessagesState\n", + "class State(MessagesState):\n", + " summary: str" + ] + }, + { + "cell_type": "markdown", + "id": "6855ea31-5cc1-4277-a189-0b72459f67ec", + "metadata": {}, + "source": [ + "We'll define a node to call our LLM that incorporates a summary, if it exists, into the prompt." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c3f7d19b-afe0-4381-9b1a-0a832b162e7b", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.messages import SystemMessage, HumanMessage, RemoveMessage\n", + "\n", + "# Define the logic to call the model\n", + "def call_model(state: State):\n", + " \n", + " # Get summary if it exists\n", + " summary = state.get(\"summary\", \"\")\n", + "\n", + " # If there is summary, then we add it\n", + " if summary:\n", + " \n", + " # Add summary to system message\n", + " system_message = f\"Summary of conversation earlier: {summary}\"\n", + "\n", + " # Append summary to any newer messages\n", + " messages = [SystemMessage(content=system_message)] + state[\"messages\"]\n", + " \n", + " else:\n", + " messages = state[\"messages\"]\n", + " \n", + " response = model.invoke(messages)\n", + " return {\"messages\": response}" + ] + }, + { + "cell_type": "markdown", + "id": "6882042c-b42d-4d52-a6a7-6ec8efa72450", + "metadata": {}, + "source": [ + "We'll define a node to produce a summary.\n", + "\n", + "Note, here we'll use `RemoveMessage` to filter our state after we've produced the summary." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "78c7aa59-3760-4e76-93f1-bc713e3ec39e", + "metadata": {}, + "outputs": [], + "source": [ + "def summarize_conversation(state: State):\n", + " \n", + " # First, we get any existing summary\n", + " summary = state.get(\"summary\", \"\")\n", + "\n", + " # Create our summarization prompt \n", + " if summary:\n", + " \n", + " # A summary already exists\n", + " summary_message = (\n", + " f\"This is summary of the conversation to date: {summary}\\n\\n\"\n", + " \"Extend the summary by taking into account the new messages above:\"\n", + " )\n", + " \n", + " else:\n", + " summary_message = \"Create a summary of the conversation above:\"\n", + "\n", + " # Add prompt to our history\n", + " messages = state[\"messages\"] + [HumanMessage(content=summary_message)]\n", + " response = model.invoke(messages)\n", + " \n", + " # Delete all but the 2 most recent messages\n", + " delete_messages = [RemoveMessage(id=m.id) for m in state[\"messages\"][:-2]]\n", + " return {\"summary\": response.content, \"messages\": delete_messages}" + ] + }, + { + "cell_type": "markdown", + "id": "f982993e-f4be-4ff7-9a38-886f75398b3d", + "metadata": {}, + "source": [ + "We'll add a conditional edge to determine whether to produce a summary based on the conversation length." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "b507665d-7f5d-442a-b498-218c94c5dd8b", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import END\n", + "# Determine whether to end or summarize the conversation\n", + "def should_continue(state: State):\n", + " \n", + " \"\"\"Return the next node to execute.\"\"\"\n", + " \n", + " messages = state[\"messages\"]\n", + " \n", + " # If there are more than six messages, then we summarize the conversation\n", + " if len(messages) > 6:\n", + " return \"summarize_conversation\"\n", + " \n", + " # Otherwise we can just end\n", + " return END" + ] + }, + { + "cell_type": "markdown", + "id": "5a838f4c-7067-4f7f-a4c4-6654e11214cd", + "metadata": {}, + "source": [ + "## Adding memory\n", + "\n", + "Recall that [state is transient](https://github.com/langchain-ai/langgraph/discussions/352#discussioncomment-9291220) to a single graph execution.\n", + "\n", + "This limits our ability to have multi-turn conversations with interruptions. \n", + "\n", + "As introduced at the end of Module 1, we can use [persistence](https://langchain-ai.github.io/langgraph/how-tos/persistence/) to address this! \n", + " \n", + "LangGraph can use a checkpointer to automatically save the graph state after each step.\n", + "\n", + "This built-in persistence layer gives us memory, allowing LangGraph to pick up from the last state update. \n", + "\n", + "As we previously showed, one of the easiest to work with is `MemorySaver`, an in-memory key-value store for Graph state.\n", + "\n", + "All we need to do is compile the graph with a checkpointer, and our graph has memory!" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "1d57516d-f9f1-4d3c-a84a-7277b5ce6df6", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "from langgraph.checkpoint.memory import MemorySaver\n", + "from langgraph.graph import StateGraph, START\n", + "\n", + "# Define a new graph\n", + "workflow = StateGraph(State)\n", + "workflow.add_node(\"conversation\", call_model)\n", + "workflow.add_node(summarize_conversation)\n", + "\n", + "# Set the entrypoint as conversation\n", + "workflow.add_edge(START, \"conversation\")\n", + "workflow.add_conditional_edges(\"conversation\", should_continue)\n", + "workflow.add_edge(\"summarize_conversation\", END)\n", + "\n", + "# Compile\n", + "memory = MemorySaver()\n", + "graph = workflow.compile(checkpointer=memory)\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "d0bd5d23-ac3b-4496-a049-9a9f97d2feb9", + "metadata": {}, + "source": [ + "## Threads\n", + "\n", + "The checkpointer saves the state at each step as a checkpoint.\n", + "\n", + "These saved checkpoints can be grouped into a `thread` of conversation.\n", + "\n", + "Think about Slack as an analog: different channels carry different conversations.\n", + "\n", + "Threads are like Slack channels, capturing grouped collections of state (e.g., conversation).\n", + "\n", + "Below, we use `configurable` to set a thread ID.\n", + "\n", + "![state.jpg](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbadf3b379c2ee621adfd1_chatbot-summarization1.png)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "2566c93b-13e6-4a53-bc0f-b00fff691d30", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Hi Lance! It's nice to meet you. How can I help you today?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "You said your name is Lance.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "That's cool, Lance! They have a rich history and a passionate fan base. Are you excited for the upcoming season?\n" + ] + } + ], + "source": [ + "# Create a thread\n", + "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", + "\n", + "# Start conversation\n", + "input_message = HumanMessage(content=\"hi! I'm Lance\")\n", + "output = graph.invoke({\"messages\": [input_message]}, config) \n", + "for m in output['messages'][-1:]:\n", + " m.pretty_print()\n", + "\n", + "input_message = HumanMessage(content=\"what's my name?\")\n", + "output = graph.invoke({\"messages\": [input_message]}, config) \n", + "for m in output['messages'][-1:]:\n", + " m.pretty_print()\n", + "\n", + "input_message = HumanMessage(content=\"i like the 49ers!\")\n", + "output = graph.invoke({\"messages\": [input_message]}, config) \n", + "for m in output['messages'][-1:]:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "531e5b63-5e8b-486e-baa0-a45521e2fbc2", + "metadata": {}, + "source": [ + "Now, we don't yet have a summary of the state because we still have < = 6 messages.\n", + "\n", + "This was set in `should_continue`. \n", + "\n", + "```\n", + " # If there are more than six messages, then we summarize the conversation\n", + " if len(messages) > 6:\n", + " return \"summarize_conversation\"\n", + "```\n", + "\n", + "We can pick up the conversation because we have the thread." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "91b82aaa-17f9-49e2-9528-f4b22e23ebcb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "''" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.get_state(config).values.get(\"summary\",\"\")" + ] + }, + { + "cell_type": "markdown", + "id": "068a93e9-f716-4980-8edf-94115017d865", + "metadata": {}, + "source": [ + "The `config` with thread ID allows us to proceed from the previously logged state!" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "24b34f0f-62ef-4008-8e96-480cbe92ea3e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Yes, Nick Bosa is currently the highest-paid defensive player in NFL history. He signed a massive contract extension with the 49ers before the 2023 season.\n" + ] + } + ], + "source": [ + "input_message = HumanMessage(content=\"i like Nick Bosa, isn't he the highest paid defensive player?\")\n", + "output = graph.invoke({\"messages\": [input_message]}, config) \n", + "for m in output['messages'][-1:]:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "22f1b35f-e4bb-47f6-87b1-d84d8aed9aa9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"We had a brief conversation where I learned your name is Lance. You mentioned you're a 49ers fan and that you like Nick Bosa, correctly stating that he's the highest-paid defensive player in the NFL. We also briefly touched on your excitement for the upcoming season.\\n\"" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.get_state(config).values.get(\"summary\",\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "96e7f53f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "The 49ers definitely have a strong roster and are considered Super Bowl contenders. With a talented offense led by Christian McCaffrey and a powerful defense anchored by Nick Bosa, they have the potential to go all the way. However, the NFL is incredibly competitive, and a lot can happen during the season. Injuries, unexpected losses, and other teams' performances all play a factor. So, while they have a good shot, it's far from a guarantee. It should be an exciting season to watch!\n" + ] + } + ], + "source": [ + "input_message = HumanMessage(content=\"do you think 49ers are going to win the super bowl this year?\")\n", + "output = graph.invoke({\"messages\": [input_message]}, config) \n", + "for m in output['messages'][-1:]:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "c2d9ce4f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "The Warriors are always a team to watch, and with Steph Curry still playing at a high level, they can never be counted out. Their success will likely depend on how well their new additions integrate and if they can stay healthy. The Western Conference is incredibly tough, with teams like the Nuggets, Suns, and Lakers all vying for the top spot. It'll be a challenging road, but the Warriors definitely have a chance to make a deep playoff run. It will be interesting to see how they perform this season.\n" + ] + } + ], + "source": [ + "input_message = HumanMessage(content=\"I like NBA too, do you think the Warriors are going to win the championship this year?\")\n", + "output = graph.invoke({\"messages\": [input_message]}, config) \n", + "for m in output['messages'][-1:]:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "8198b439", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "It's certainly *possible*, but incredibly unlikely for an NBA player to successfully transition to the NFL. The two sports require vastly different skill sets, body types, and training regimens. \n", + "\n", + "While both demand athleticism, NBA players are generally taller and leaner, prioritizing agility, jumping, and precise shooting. NFL players, especially in positions an NBA player might realistically consider (like wide receiver or tight end), often need more bulk and explosive power for tackling and blocking.\n", + "\n", + "The biggest hurdle would be the sheer physicality and violence of the NFL. An NBA player's body isn't conditioned for the constant hits and tackles, and the risk of serious injury is significantly higher. \n", + "\n", + "There have been a few rare examples of athletes playing both sports professionally (like Antonio Gates, who played basketball at the University of Central Florida before becoming an NFL star), but these are exceptions, not the rule. The level of competition in both leagues is so high that specializing from a young age is almost essential for reaching the professional level.\n" + ] + } + ], + "source": [ + "input_message = HumanMessage(content=\"Is it possible for a NBA player become a NFL player?\")\n", + "output = graph.invoke({\"messages\": [input_message]}, config) \n", + "for m in output['messages'][-1:]:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "ad7cc0ab-905a-4037-b7cb-69db5b89591e", + "metadata": {}, + "source": [ + "## LangSmith\n", + "\n", + "Let's review the trace!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "lc-academy", + "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.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/langchain-academy/module-2/multiple-schemas.ipynb b/langchain-academy/module-2/multiple-schemas.ipynb new file mode 100644 index 000000000..3b7128efa --- /dev/null +++ b/langchain-academy/module-2/multiple-schemas.ipynb @@ -0,0 +1,353 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e2996fea", + "metadata": {}, + "source": [ + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-2/multiple-schemas.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239434-lesson-3-multiple-schemas)" + ] + }, + { + "cell_type": "markdown", + "id": "693d9912-8d56-46a2-a445-3ee5651fe433", + "metadata": {}, + "source": [ + "# Multiple Schemas\n", + "\n", + "## Review\n", + "\n", + "We just covered state schema and reducers.\n", + "\n", + "Typically, all graph nodes communicate with a single schema. \n", + "\n", + "Also, this single schema contains the graph's input and output keys / channels.\n", + "\n", + "## Goals\n", + "\n", + "But, there are cases where we may want a bit more control over this:\n", + "\n", + "* Internal nodes may pass information that is *not required* in the graph's input / output.\n", + "\n", + "* We may also want to use different input / output schemas for the graph. The output might, for example, only contain a single relevant output key.\n", + "\n", + "We'll discuss a few ways to customize graphs with multiple schemas." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "4d727cc2-5a43-4eb5-9d69-82bbbcc35bd9", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install --quiet -U langgraph" + ] + }, + { + "cell_type": "markdown", + "id": "29b3d109-6bf2-4271-9775-556ee4bd900d", + "metadata": {}, + "source": [ + "## Private State\n", + "\n", + "First, let's cover the case of passing [private state](https://langchain-ai.github.io/langgraph/how-tos/pass_private_state/) between nodes.\n", + "\n", + "This is useful for anything needed as part of the intermediate working logic of the graph, but not relevant for the overall graph input or output.\n", + "\n", + "We'll define an `OverallState` and a `PrivateState`.\n", + "\n", + "`node_2` uses `PrivateState` as input, but writes out to `OverallState`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "038ca2e4-7d6d-49d5-b213-b38469cde434", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from typing_extensions import TypedDict\n", + "from IPython.display import Image, display\n", + "from langgraph.graph import StateGraph, START, END\n", + "\n", + "class OverallState(TypedDict):\n", + " foo: int\n", + "\n", + "class PrivateState(TypedDict):\n", + " baz: int\n", + "\n", + "def node_1(state: OverallState) -> PrivateState:\n", + " print(\"---Node 1---\")\n", + " return {\"baz\": state['foo'] + 1}\n", + "\n", + "def node_2(state: PrivateState) -> OverallState:\n", + " print(\"---Node 2---\")\n", + " return {\"foo\": state['baz'] + 1}\n", + "\n", + "# Build graph\n", + "builder = StateGraph(OverallState)\n", + "builder.add_node(\"node_1\", node_1)\n", + "builder.add_node(\"node_2\", node_2)\n", + "\n", + "# Logic\n", + "builder.add_edge(START, \"node_1\")\n", + "builder.add_edge(\"node_1\", \"node_2\")\n", + "builder.add_edge(\"node_2\", END)\n", + "\n", + "# Add\n", + "graph = builder.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "3dc9cd64-4bd3-4c0a-8f8f-d58c551428e3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---Node 1---\n", + "---Node 2---\n" + ] + }, + { + "data": { + "text/plain": [ + "{'foo': 3}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.invoke({\"foo\" : 1})" + ] + }, + { + "cell_type": "markdown", + "id": "50a29f37-f653-4a56-ad0a-345d7f632ea0", + "metadata": {}, + "source": [ + "`baz` is only included in `PrivateState`.\n", + "\n", + "`node_2` uses `PrivateState` as input, but writes out to `OverallState`.\n", + "\n", + "So, we can see that `baz` is excluded from the graph output because it is not in `OverallState`." + ] + }, + { + "cell_type": "markdown", + "id": "75a8362f-009b-4ec2-abe5-8fb318e39966", + "metadata": {}, + "source": [ + "## Input / Output Schema\n", + "\n", + "By default, `StateGraph` takes in a single schema and all nodes are expected to communicate with that schema. \n", + "\n", + "However, it is also possible to [define explicit input and output schemas for a graph](https://langchain-ai.github.io/langgraph/how-tos/input_output_schema/?h=input+outp).\n", + "\n", + "Often, in these cases, we define an \"internal\" schema that contains *all* keys relevant to graph operations.\n", + "\n", + "But, we use specific `input` and `output` schemas to constrain the input and output.\n", + "\n", + "First, let's just run the graph with a single schema." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5323068a-907a-438c-8db5-46e5d452ad72", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "class OverallState(TypedDict):\n", + " question: str\n", + " answer: str\n", + " notes: str\n", + "\n", + "def thinking_node(state: OverallState):\n", + " return {\"answer\": \"bye\", \"notes\": \"... his is name is Lance\"}\n", + "\n", + "def answer_node(state: OverallState):\n", + " return {\"answer\": \"bye Lance\"}\n", + "\n", + "graph = StateGraph(OverallState)\n", + "graph.add_node(\"answer_node\", answer_node)\n", + "graph.add_node(\"thinking_node\", thinking_node)\n", + "graph.add_edge(START, \"thinking_node\")\n", + "graph.add_edge(\"thinking_node\", \"answer_node\")\n", + "graph.add_edge(\"answer_node\", END)\n", + "\n", + "graph = graph.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "markdown", + "id": "853fc90c-bf82-4d51-b3a5-ceb0b0ae5233", + "metadata": {}, + "source": [ + "Notice that the output of invoke contains all keys in `OverallState`. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "507d35e6-f65c-4e89-b26e-a0ef7b90be83", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'question': 'hi', 'answer': 'bye Lance', 'notes': '... his is name is Lance'}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.invoke({\"question\":\"hi\"})" + ] + }, + { + "cell_type": "markdown", + "id": "e5a899c3-e1b0-48eb-9a36-8c787e378ef0", + "metadata": {}, + "source": [ + "Now, let's use a specific `input` and `output` schema with our graph.\n", + "\n", + "Here, `input` / `output` schemas perform *filtering* on what keys are permitted on the input and output of the graph. \n", + "\n", + "In addition, we can use a type hint `state: InputState` to specify the input schema of each of our nodes.\n", + "\n", + "This is important when the graph is using multiple schemas.\n", + "\n", + "We use type hints below to, for example, show that the output of `answer_node` will be filtered to `OutputState`. " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "682b3d10-c78a-41c2-a5ff-842e1688c95f", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "{'answer': 'bye Lance'}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "class InputState(TypedDict):\n", + " question: str\n", + "\n", + "class OutputState(TypedDict):\n", + " answer: str\n", + "\n", + "class OverallState(TypedDict):\n", + " question: str\n", + " answer: str\n", + " notes: str\n", + "\n", + "def thinking_node(state: InputState):\n", + " return {\"answer\": \"bye\", \"notes\": \"... his is name is Lance\"}\n", + "\n", + "def answer_node(state: OverallState) -> OutputState:\n", + " return {\"answer\": \"bye Lance\"}\n", + "\n", + "graph = StateGraph(OverallState, input=InputState, output=OutputState)\n", + "graph.add_node(\"answer_node\", answer_node)\n", + "graph.add_node(\"thinking_node\", thinking_node)\n", + "graph.add_edge(START, \"thinking_node\")\n", + "graph.add_edge(\"thinking_node\", \"answer_node\")\n", + "graph.add_edge(\"answer_node\", END)\n", + "\n", + "graph = graph.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))\n", + "\n", + "graph.invoke({\"question\":\"hi\"})" + ] + }, + { + "cell_type": "markdown", + "id": "f1e5ff21", + "metadata": {}, + "source": [ + "We can see the `output` schema constrains the output to only the `answer` key." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "lc-academy", + "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.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/langchain-academy/module-2/state-reducers.ipynb b/langchain-academy/module-2/state-reducers.ipynb new file mode 100644 index 000000000..f16eb513d --- /dev/null +++ b/langchain-academy/module-2/state-reducers.ipynb @@ -0,0 +1,846 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "36b496da", + "metadata": {}, + "source": [ + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-2/state-reducers.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239428-lesson-2-state-reducers)" + ] + }, + { + "cell_type": "markdown", + "id": "b7ae0ff7-497d-4c31-a57a-00fe92799232", + "metadata": {}, + "source": [ + "# State Reducers \n", + "\n", + "## Review\n", + "\n", + "We covered a few different ways to define LangGraph state schema, including `TypedDict`, `Pydantic`, or `Dataclasses`.\n", + " \n", + "## Goals\n", + "\n", + "Now, we're going to dive into reducers, which specify how state updates are performed on specific keys / channels in the state schema." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "398c5e8e-641f-4be6-b1e8-7531f86bd2e9", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install --quiet -U langchain_core langgraph" + ] + }, + { + "cell_type": "markdown", + "id": "4d5bd534-c5be-48fe-91bc-af39ebee76b7", + "metadata": {}, + "source": [ + "## Default overwriting state\n", + "\n", + "Let's use a `TypedDict` as our state schema." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "64e2438c-9353-4256-bc3c-1bb830374c0b", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from typing_extensions import TypedDict\n", + "from IPython.display import Image, display\n", + "from langgraph.graph import StateGraph, START, END\n", + "\n", + "class State(TypedDict):\n", + " foo: int\n", + "\n", + "def node_1(state):\n", + " print(\"---Node 1---\")\n", + " return {\"foo\": state['foo'] + 1}\n", + "\n", + "# Build graph\n", + "builder = StateGraph(State)\n", + "builder.add_node(\"node_1\", node_1)\n", + "\n", + "# Logic\n", + "builder.add_edge(START, \"node_1\")\n", + "builder.add_edge(\"node_1\", END)\n", + "\n", + "# Add\n", + "graph = builder.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "69634df1-4f02-446f-b5cf-6a83d1e15e37", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---Node 1---\n" + ] + }, + { + "data": { + "text/plain": [ + "{'foo': 2}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.invoke({\"foo\" : 1})" + ] + }, + { + "cell_type": "markdown", + "id": "775a099c-c41c-412f-8f05-e7436388ae79", + "metadata": {}, + "source": [ + "Let's look at the state update, `return {\"foo\": state['foo'] + 1}`.\n", + "\n", + "As discussed before, by default LangGraph doesn't know the preferred way to update the state.\n", + " \n", + "So, it will just overwrite the value of `foo` in `node_1`: \n", + "\n", + "```\n", + "return {\"foo\": state['foo'] + 1}\n", + "```\n", + " \n", + "If we pass `{'foo': 1}` as input, the state returned from the graph is `{'foo': 2}`.\n", + "\n", + "## Branching\n", + "\n", + "Let's look at a case where our nodes branch." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "2b8d6ad4-2991-4325-933d-67057bc150f4", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "class State(TypedDict):\n", + " foo: int\n", + "\n", + "def node_1(state):\n", + " print(\"---Node 1---\")\n", + " return {\"foo\": state['foo'] + 1}\n", + "\n", + "def node_2(state):\n", + " print(\"---Node 2---\")\n", + " return {\"foo\": state['foo'] + 1}\n", + "\n", + "def node_3(state):\n", + " print(\"---Node 3---\")\n", + " return {\"foo\": state['foo'] + 1}\n", + "\n", + "# Build graph\n", + "builder = StateGraph(State)\n", + "builder.add_node(\"node_1\", node_1)\n", + "builder.add_node(\"node_2\", node_2)\n", + "builder.add_node(\"node_3\", node_3)\n", + "\n", + "# Logic\n", + "builder.add_edge(START, \"node_1\")\n", + "builder.add_edge(\"node_1\", \"node_2\")\n", + "builder.add_edge(\"node_1\", \"node_3\")\n", + "builder.add_edge(\"node_2\", END)\n", + "builder.add_edge(\"node_3\", END)\n", + "\n", + "# Add\n", + "graph = builder.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "106729b3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---Node 1---\n", + "---Node 2---\n", + "---Node 3---\n", + "InvalidUpdateError occurred: At key 'foo': Can receive only one value per step. Use an Annotated key to handle multiple values.\n" + ] + } + ], + "source": [ + "from langgraph.errors import InvalidUpdateError\n", + "try:\n", + " graph.invoke({\"foo\" : 1})\n", + "except InvalidUpdateError as e:\n", + " print(f\"InvalidUpdateError occurred: {e}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "b9717ccd-3d34-476a-8952-e6a7629ebefe", + "metadata": {}, + "source": [ + "We see a problem! \n", + "\n", + "Node 1 branches to nodes 2 and 3.\n", + "\n", + "Nodes 2 and 3 run in parallel, which means they run in the same step of the graph.\n", + "\n", + "They both attempt to overwrite the state *within the same step*. \n", + "\n", + "This is ambiguous for the graph! Which state should it keep? " + ] + }, + { + "cell_type": "markdown", + "id": "f1609cf7-dc47-4926-a154-77904b6cc550", + "metadata": {}, + "source": [ + "## Reducers\n", + "\n", + "[Reducers](https://langchain-ai.github.io/langgraph/concepts/low_level/#reducers) give us a general way to address this problem.\n", + "\n", + "They specify how to perform updates.\n", + "\n", + "We can use the `Annotated` type to specify a reducer function. \n", + "\n", + "For example, in this case let's append the value returned from each node rather than overwriting them.\n", + "\n", + "We just need a reducer that can perform this: `operator.add` is a function from Python's built-in operator module.\n", + "\n", + "When `operator.add` is applied to lists, it performs list concatenation." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "103d808c-55ec-44f2-a688-7b5e1572875a", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGsAAADqCAIAAAAqMSwmAAAAAXNSR0IArs4c6QAAFXxJREFUeJztnXtcE1e+wE8yk5B3CAmEpzxERAXRitRaH1jRqqUoasUHttrWrbtu7+dT7W73dtm1vW1dF23rva3VttI31VqrUtT1VaUVXarUPqCgFFARwiPvhLxnkvsHXuSWJDPJJORA5/uXzpwz+fHNmZMz55w5h+FyuQANBZihDmDYQxukCm2QKrRBqtAGqUIbpApKMb9R49CrHWYjbjbgmMPldA6DthGCAhRl8kQIT4hKolk8ASUJDP/ag+pOW8tPpht1JjaPAVwMnhDhiRAuH3Xiw8AgymL0GjCzATcbMZvFyWIzUzL5qVkCkZTlx9V8Ntirwy5VqlwAhMtYyZn8qHiOH58KFZ03LK11Jm23XSBBp+fL2BzfajbfDF45ram/pJ/+sGzsFKHvocJOXbX+0jHVtIekWTPDyefywWDFno7UyYIJ08T+Rjg8+O6sRt1ln18cTTI92RJb9rcbkx+QjHh9AIApeRGJ6fyKPR1kM7hIsK+kVaWwkkk5YvjlB+OBnW1kUhLfxRV7OiY/IBk1lheA73dY0fitoaPVkrdK7j0ZgcHaMxquAJlw38i/ed1Se1bD5RP8+d7qwV4dVndR/5vVBwDIzos4f1DpPY03g5cqVdMflgU6qmHGffnSS5UqLwk8GlR32lwAjMh2n09MmStRKWxWE+YpgUeDLT+ZwmX+POX4R319vc1mC1V27/BFaGu92dNZjwZv1JmSM/lBiulXVFZWrlu3zmKxhCQ7ISmZgta6Xk9n3Rs0aBxhPOaQPfP6XXz6GhLBK319JGfwe7WYp24nDwbVjiAN4d26dWvjxo0zZsxYtGjRtm3bnE5nZWXl9u3bAQB5eXnZ2dmVlZUAgO7u7q1bt+bl5U2bNq2oqOjkyZN92XU6XXZ29scff1xSUjJjxowNGza4zR5wMIdLr3K4PeW+a8xsxHlCJBihvPTSSzdv3tyyZYvJZKqtrWUymffff39xcfEnn3yya9cugUAwatQoAACGYT///PPy5cvDw8PPnTtXUlKSkJAwYcKEvouUlZU98sgje/fuRRBELpcPzh5weCLEbMAlUW5OeTBowHmioBhUKBTp6emFhYUAgOLiYgBAREREfHw8ACAjIyM8/E6nSFxc3Oeff85gMAAAixcvzsvLq6qq6jeYmZm5adOm/msOzh5w+CLUZHD/c+zxl4TFDsoAwKJFi2pqakpLSzUajfeUTU1NmzdvXrBgQWFhIY7jarW6/1ROTk4wYvMCm8P09PDmXhOHzzRqPbaAqLBp06bNmzefPn26oKDg4MGDnpJduXLlscces9vtW7duLS0tFYvFTqez/yyXyw1GbF7Qqxw8ofv71f1RnhA1G4NikMFgrF69evHixdu2bSstLU1LS5s0aVLfqYFf8r59++Lj43ft2oWiKEllQZ2+4uWHwX0ZFEiQMG5Q7uK+lgefz9+4cSMA4Nq1a/2ClMq7T6A6nS4tLa1Pn91uN5vNA8vgrxicPeDwxYhQ4v75wn0ZjJCHKdvtOqU9PJId2FCee+45gUAwbdq06upqAMC4ceMAAFlZWQiC7Ny5s6CgwGazLVu2rK9dUlFRIRaLy8vLDQZDS0uLp1I2OHtgY+5otjgx4Gn8BHnhhRfcnjBqMZMei0kOcI3T3t5eXV198uRJi8Xy9NNP5+bmAgBEIpFcLj9z5syFCxcMBkN+fn5WVlZra+uBAwdqa2vnzZtXVFR06tSp9PR0qVT60UcfzZgxY/z48f3XHJw9sDH/+LVOnsSJTnL/fOGxf1DRamn81jCXqH/xt8Dxss4Zi2ViD70EHgebY1O4l09qbjeZE9Lc904bDIaCggK3p+Lj49vb2wcfnz179osvvkg6cj958sknm5ubBx8fN25cY2Pj4OMZGRlvvvmmp6s1XjaEcZme9BH0Uffctp4/qCzakuD2rNPp7Orqcn9RhvvLcrlciUTi6eMChVKpdDjcPIF5iorNZstkHrtBy/52Y9WfEzw1ZYh7+b85ohyVxkuaMESdNLDxc43ebMCnzo/wkoagyTKrMPLrw0qD2v1D9chG0WK5dsXoXR8gM9pps+J7/9wciBHE4YTF5Hj7Ly1kUpIaL7bb8Lf/s7lX76Ac2PCgp91a9vdWDHOSSUx21oelF99f2vbgo/K41BE+cNz8o7H2tHbln8j2kvk28+j8Zz0GreP+h2WyuDB/I4SXjhbLvyvV8sSwmYWR5HP5PPut7Zr5YqVqVDpPnsBJzuAjKMP3UOHCbnW21vd23bRqOu33PSyNSfLtMczPGZgtP/U2XTXeqDeNnSJkhTH5IpQvRjg8ZDhMYQUIk2E2YiYDZjLgvXpHe5MlJUOQli1ITPen0eanwX7arpm1PXaTATPpcafThdkDqRDH8bq6uv7ur0ARxmP2dTvzRYg0hk2xZqdqMKj09vbm5+dXVVWFOhBv0HP5qUIbpArsBvu6YGEGdoNu+6OgAnaDwRsCDhSwG9TpdKEOgQDYDcbGxoY6BAJgN6hQKEIdAgGwG8zMzAx1CATAbrCuri7UIRAAu0H4gd2gl1E0SIDdoErl7U0EGIDdYGSkD93FIQF2g0GdkRUQYDcIP7AbTE1NDXUIBMBu0O0cIqiA3SD8wG5w4ExLOIHdYENDQ6hDIAB2g/ADu0G6b4YqdN/MyAd2g/RoJ1Xo0c6RD+wG6fFiqtDjxVQZM2ZMqEMgAHaDv/zyS6hDIAB2g/ADu8HoaLJrUYYK2A16evkRHmA3mJGREeoQCIDdYH19fahDIAB2g3QZpApdBqmSkOD+DXt4gPGNnA0bNigUChRFnU6nSqWSyWRMJtPhcJw4cSLUobkBxjK4Zs0ag8HQ0dHR2dnpcDg6Ozs7OjoQJCgrqVEHRoO5ubm/ehx2uVzQDpjAaBAAsHbtWh7v7guDMTExK1euDGlEHoHU4Jw5c5KTk/vr6KysrIkTJ4Y6KPdAahAAsH79+r7uVZlMBm0BhNpgbm5uSkpK35AxtJWgP/s09eowTZcdw4aiDbRk/lM27WeLcte31puG4ONYbIY0hu1leSO3+NAe1HTZq79UqTpsieMEJn1Q1ncMLVwhcqvRFJPEeaAoiisg23gia1CnclS+rchbGysQD91C6SFB02W7cLircFMcX0SqMJKqB+0254EdbUv+mDji9QEAIqLDFj4eX/6PNpLpSZXBCxUqsSwsOeM3tMtBQ40ORV1T5hKvVEeqDCqaLcKIkV/6BiKUsDpbSS30T6414wLCiAAvyAo5YikLc5D6hSBl0KhzgGGxFk/gcDqB2YCTSQlvi3q4QBukCm2QKrRBqtAGqUIbpAptkCq0QarQBqlCG6QKbZAq8Bp8eVvJo+sCsFfL/gMffnbw40BE5B54DVLH6XTuK9v9zrtvBPVTfB5pGi4oOjtKd7xYX/9jsD8oKAYPffHpufOnH1m+pqxst1qjGjMm/dnNJaNGJfWdPX36ePn+9xWKdqlU9tCiwjWr1zOZd26Fc+dPf/jRO93dnUmJKQO3trJarfvKdn917qTdbkuIT1yxYu0Dc+Z7j+HSpa+ZDObO0ree2fJUMP7GfoJVBhsb6w8e/HjLlhIMw1577ZV//HPrnt0fAgBOnTq2vfSFuXMXPPH4Hxoa6t57fw8AYG3xEwCAs1+dfGVbyeRJ2SseKe7qUny6/4O4uIS+m/GvJc90dSnWrF4fHh7xww+1L738vNVqWbRwsZcA5uTOX7Z0lVLZE6Q/sJ8g3sWvvPx6RIQUALB06cq39ryuN+hFQtG+93ZnZk4qef5lAMCsmQ8YjYYDn324bOkqBEHe3L1z4sTJO0p3903T6ui43dzSBAD45sK5n+q+319eKZNFAgDy5i6wWMxfHN7v3aBUOkQLdgXRIIdzZ4F7uTwGAKBWKQ16nUqlLFqxtj/N1Kn3nfhXRXtHm8Gg1+t1y5et7p/lxvy/f9TUVGMYtrr47p5QOI7z+YLgRe4TQ/FLwkJZAADcidtMNgBAePjdXWeEQhEAQKXs0em1AIDoaDdrhmq1aqlU9trOvQMPIigsv4FDGkdUpBwAoNfffdlQq9X0ewQA6HTawbmEQpFOp5XLY8LCYNzSY0jbg1KpLFoec/nyxf4jX399lsPhpKaOHT06jclknv3qX4Nz3XNPDo7jX1Ye6j8SvA3H/WCo74V1jz21vfSFHTtfmjr1vqtXL1dfrHrs0d9xuVwul7twQcHxE0ftNltOznS1WvXtt9USiRQAMC9vUeWxw3vf/u/OLkXamPTm5qbqi+c/eO8QhzNEm6R7Z6gNPvhgvtVm/fxQ+ekzx2XSyN9teHpl0aN9p57+45/YbPbZr07WfleTkTFp9Og0jUYNAGCxWDv+ufvdfW+cO3fq2LHD8fGjCh5ejkJTD5Ka9bGvpHXJpsQwHqRzwYOBrsd+4Yuu1X8h3q0Jlm/SD97d9+bAyrEfkVBc/knFkIUxjA2uWLE2P3/p4ONMxpD+PA5jg2KRWCwShzqKEd27NTTQBqlCG6QKbZAqtEGq0AapQhukCm2QKrRBqtAGqULKYFQcx/lbm8vvckmiSb0AQq4MMoG600Y1qGGFqsPK5pCSQypRSiZfpbBSjmo4oe2yJ08gta8xKYMZ08UGpb2hBvblKAPFd2dVKAukZJIaUPXh/eLKdxThUWHhUWGyuDAGY9hv3z4YJ+5StluV7RZ2GHPWUrID9r6t2NNw2XDzZ5MTB6qOIakWXS6b3T5kg5yy2DBWGGP0JEHqRB+G82Fc86gfehfy3wS0QarAbhDmdVL6gN0gvbsGVejd1qhC77ZGFXp/EqrQ+5NQha4HqULXgyMf2A2OHTs21CEQALvB69evhzoEAmA3CD+wG4RkurkXYDdotcI+PgO7QbE49LNUvQO7Qb1eH+oQCIDdIPzAbjA+Pj7UIRAAu8H29vZQh0AA7AbhB3aD9K6TVKF3nRz5wG6QHu2kCj3aOfKB3SA9TkIVepyEKhIJ8e4MoQV2g1qtmxVooAJ2g/ADu0F61gdV6FkfVBk/fnyoQyAAdoMNDQ2hDoEA2A3SZZAqdBmkyoQJE0IdAgEwvpGzadMmjUbDYrFwHG9paUlJSUFRFMfx8vLyUIfmBhhXjZo9e/arr76K43e26mpqaurbRjvUcbkHxrt4xYoVCQkJvzqYk5MTonAIgNEgAKC4uHjgC4kikWjVqlUhjcgjkBpcsmRJXFxc/3/HjBkza9askEbkEUgNAgBWrVrVVwzFYnFxcXGow/EIvAYLCwv7iuHo0aNnzpwZ6nA8EpTfYrMBw0nteUlA0bJ1ZWVlRcvWGbUB2LIbRRlcYeCXkg1Me7D7lrW13qTudHTesNjMuCSaY+2FbptylM00auwcPhIzmhsVx07J4EtjA/D2PFWDP13QNV7ptVpc/AieQMpD2QgaBu+SwS6XC7PjmA3vVZlMarNYyhqXI0ifKqJyTf8NNl01fnNEJYriS0aJWWwYW+aE2K2Y5qbWbrbNLpQljuf7dxE/DR5/v8dsBuGxYhZnWLobiLXXbuw2yGLQOcukfmT3x+CBnbe5EoE4llLhhw1NmxYB9sVPudmcwjs+GzzyloIlEgmkXF8/CX60CoOA45i3JsqnXL61B4/s7mCJBCNSHwBAEisyWVlnyrt9yuWDweoKFWBzBFI/a9xhQXisSKcFP3ztwyA1WYM9bdaWOrMkPtzf2IYNkaNll0/pTAay7VmyBi8cVUuTIkgkHAnIUyXVR1UkE5My2HbdbHcwRmr1NxhxjLDntp3kkoukDP74jZ4nhWVvrl/xX6X5hyq2B/yyPJmg7qKBTEpSBm81mkRRpJYzHDEII/mtdSYyKYkN3mwwhcu5I3LBQS+wuSgDYaoUxDcy8TNZz20rRxysGrC59bsTZ95SdDUJBRGpydkL5/1eJJQBAEpembvs4efqG6sarl/kcgTTphbOn/NkXxYcx89WldXUHrXbLaNTpjgcwXp9lh/B6b5llRH13xCXQYMaYyJB6Yj9peXKux/9hzwqecWSv86avrr15vd7399kt98xcuDwi7HRaX94Yu89WQtPn3u34fqdPdqOHNtxpqosPW16Yf6zbBbHYjUGIzYAAIPBJNMvSVwGe3U4SxCUDqujx1+dll1YmP9s33/TUu/d8T9F15trMsfnAgBy7imYO3sdACA2Ou3ydxVNzTXjx97frrhWU3tk7uz1C/M2AgCyJz/UcuNqMGIDACBstFdvJ0xGbBBlM5EgdPlptJ3dyhsqze2a2qMDj+v0dx6q2Ow7VQeCIGJRlN6gBADUNVQBAGZNvztuxwjarkwsDgIAce1PbBBzOJ02POAVobFXDQCYN+fJiePnDDwuFLpZQpbJRJ1OHACg03VxOAI+byhefHdYMa6AuNuF2CBfjBpNgRj1+P9wOUIAgMNhi4pMIp+Lz5dYrb0OzM5CSS1YTgXMhgvjiG8+4lsgPBJ1DdjOOlBEykaFi6OvXK202e9swonjGIY5vOeKj0sHAHz/06mAx+MOlzCCRC1HmCI6kXOtViMdFeAbh8FgLF70zIf7n3vj7Sfuy1nqdOK135+YMmnBwDpuMFkT8s5WvfdFxfau7ta4mLSbt+sMRmVgA+vHqDTHJBP/1cRlMCGNZ1TbnHjgi2Hm+NzHi19DENaXJ14/W/WeRBKdkjTZexYEQZ5cuyst9d5/X/ni2Kk3mAwmnxeU7iKbyYEwgUROXFeQ6qM+/l6XA3DDYyB9NA4Gqpt6eTQ+szCSMCWpcaJ75ojPfKryYvB687cff/b84OMsNMyBuX8wenrDPnlUMplPJ0Pj9Yvlh/4++LjL5QLA5bbF8/v1b8XFelwWTddhmF8U5+nsQMiOkxzdo2DyhJ76F+x2a69JM/g4hjlQlOU2i1gUhSABG+fzFIDT6XS5XP17ww9EJIz0FJu23SASOOauIjVgQtagustW+W53Ujapr2W403Th1mMlZHcbJtugl0aHjcsRqFrdfM8jjM5rPTMWy8hv1uzDI9G9D0ZwObiuM1hP8jCgvqWLTUTH3+vDULjP48UnPui24RxJ7Aj8XVbe0EXHg5kFvs1c8PmxfNE6OcNuUreNtP1yeprVYhHmqz7/581UV6gUtzBhtIgrHKLtV4KHSWs1qwypE7mTc/1pnPs/d+tWo/mbIyqEzYpIDOcIgv6cHwwsBrv6hobFds1eJo1O9LP7ier8waarxrpLRm23XRjJ48t4KAthhSEIC9IphH2TBzEHZuwxG5Xm6CTOxBmiJH/nvfURmDmserXjRp2pq83W3Wa19uJcIWo2QjeHlcVi4piTI0CjkzixSWHJmXy+KABN+qC8FYbZXTgO3StIKIuBoIEfcYTxvbrhBbxvQwwXaINUoQ1ShTZIFdogVWiDVPlfIhWXbCnRiFsAAAAASUVORK5CYII=", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from operator import add\n", + "from typing import Annotated\n", + "\n", + "class State(TypedDict):\n", + " foo: Annotated[list[int], add]\n", + "\n", + "def node_1(state):\n", + " print(\"---Node 1---\")\n", + " return {\"foo\": [state['foo'][0] + 1]}\n", + "\n", + "# Build graph\n", + "builder = StateGraph(State)\n", + "builder.add_node(\"node_1\", node_1)\n", + "\n", + "# Logic\n", + "builder.add_edge(START, \"node_1\")\n", + "builder.add_edge(\"node_1\", END)\n", + "\n", + "# Add\n", + "graph = builder.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "9e68cdff-f6e1-4de5-a7bf-6ca0cfee19bf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---Node 1---\n" + ] + }, + { + "data": { + "text/plain": [ + "{'foo': [1, 2]}" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.invoke({\"foo\" : [1]})" + ] + }, + { + "cell_type": "markdown", + "id": "63fbd6e0-0207-4049-b86d-c006cbba630b", + "metadata": {}, + "source": [ + "Now, our state key `foo` is a list.\n", + "\n", + "This `operator.add` reducer function will append updates from each node to this list. " + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "768fd0ed-5e24-44a4-b14d-0e299310e105", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAOYAAAFNCAIAAACbpIR1AAAAAXNSR0IArs4c6QAAIABJREFUeJztnXdAE2fjx58sSEjCChD2cuBCwV1UsG4RkDpa3HXito7qr+qrdUCp1qLWOtA66t57odJqcYATB4qCsmUkkJBAdvL74/pSXwyINZfn7vJ8/koud5fvJZ88ee7uGTSDwQAQCPJAhx0Agfg4kLIIkoGURZAMpCyCZCBlESQDKYsgGUzYAeCg1xlK85U1Ml1NlU6nM6iVetiJGoUVm862odvwmVw7hsDNGnYcONAs6rqsVqt/kS57/aS64GWNuz/HmkO3sWXYO1upFeRQ1mAwVFVoa2RaaxtGWb7Svw3PP5Dr3oQDO5dZsSBl7yZXvLgr82zG8Q/k+rTkwo7zqUhFmtdP5RVv1VUV2pBIgdCbDTuRmbAIZV8/lV/ZW9ouzL5ruAB2FtNT+Krm1lmxqy87dIgz7CzmgPrK3k2uqCxV9/zSxcqayueauZnVfx4tH7HQy5rDgJ0FXyiu7P1rlRqVnpKF6/vIKjUH1xSM/96XRekfJ5WVvXaolMNlhEQ6wQ5iVnYsfT1ioTfXlrLXgij7c8y4IWFZ0S3NVwDAqP/zObgmH3YKHKGmskU5CvFblYWcjtSBw2OET3BLOVQKOwheUFPZv06UB3a3h50CGu7+nOoqXW5mNewguEBBZV8+kDkIrZw9LPTmEEZIpODWWTHsFLhAQWVfPZSFRFnEJYIGELhZ+7ayefVIBjuI6aGasmWFSnmljm/PMs/bvX37tri4GNbmDSP0Yb96IMdp5xChmrJvnlT7BZrpZmxhYWFUVFRmZiaUzT+Ifxvu66cUrM5STdnyIlWTdmZSVqvV/rur2thW/3rzRkKj01p1sX2TSbWClmq3ErYuzJm42o9lZeKfolKpTEhIuHHjBgAgODh4wYIFBoMhKiqqdoWIiIjvv/9erVZv37798uXLpaWlTk5OgwYNio2NZTAYAIAvv/yySZMmTZo0OXTokFKp3LVr14gRI+psbtrMAIDUUyKuPSO4p4PJ9wwRSt0jUSv1NDowua8AgF27dp07d27q1KlOTk7nzp3jcDg2NjarV69eunTp1KlTO3bs6OjoCABgMBhpaWmhoaGenp5ZWVk7d+60tbUdPXo0tpPbt28rlcrExMSamhofH5/3Nzc5XDtGtVSHx54hQilla2RaGz4uR1RcXMzhcL7++msmkxkdHY0tbNGiBQDA19c3KCgIW8JgMPbs2UOj0bCnhYWFKSkptcoymcz4+HgOh1Pf5iaHa8ssK1ThtHNYUKouq9MaOFxcjmjgwIFKpXLWrFnZ2dkNr1lRUZGQkBAdHd2rV6+cnByx+J+Lo23atKn11TwwWDQ6nWbOdzQDlFKWa8usLNfgseeQkJANGzaIxeKYmJjVq1drtVqjq4nF4lGjRqWnp0+bNu2XX35p2bKlTvfP/7KZfQUAyCu11jaU+oqpVjFgcxkapV6nMzAYpi9aQkJCunbtevDgwcTERDc3t4kTJ76/zvHjxysqKnbv3u3q6goAcHV1zcvLM3mSxlNdpaVeky6q/QR9WnOrpcaLwE9BrVYDAOh0+qhRo5ydnV+8eAEAYLPZAIDy8vLa1SQSiYODA+Yr9rSBCzLvb25yDADYOZnprorZoNpP0NaB+fpJdVCYidvEHDp06Pr16+Hh4eXl5eXl5a1atQIACIVCDw+Pffv2cTgcqVQaExPTsWPHI0eObNmypV27dikpKTdv3tTr9RKJxN7eSJ73N7e2NnG7iCep0s4rcbkWARGqlbL+gbw3T0x/y8fT01OtVicmJp46dSomJmbMmDEAABqNFh8fz+Vyf/rpp7Nnz1ZUVPTq1WvSpElHjx5dsmSJRqPZvXu3r6/v4cOHje7z/c1Nm7ngZY2rD5t6PRSodisBAHByc1HERFeWNcW7QH2Qu8kVNraM1l3tYAcxMVSrGAAA/Fpz71yo6PFFve27IyMjZTIjTZzatm37+PHj95fb2dmdPn3a1DHrkpqaunTp0veXGwwGg8FApxspLJOTk62srIzuTVGty7gumRTnj0NSyFCwlAUA7Fz25qsFXvWdLJeUlOj1HzHWBp1Orz2jwg+lUmm0bqDX6/V6PZNp5Fjc3Nxqb1vU4dqhUjdfTquutjgkhQw1lX31QFZerAqJsLiOXxhSsebmaVH4BDfYQXCBanVzjGbt+RqV4fFfEthB4HBobX6fkULYKfCCmsoCAMKGOmdnyLMfUa3p3Qc58nNB5BR3KzZlv1lqVgxqubTnrX8gt3l7ClbpjHIksaDfaKG9s/FzMmpA2d8ixoBxbq+f1NxNNvElTwIiKVdv+7+c7oOdqO0r9UtZjAcplU9SpSGRgmbBfNhZTI9Crrt5VqRW6PuMFFK4PlCLRSgLAKiq0Nw6K1YrdL6tuX5tuHwHKtx5z39RU5KnePyXtFukU8sullL5sRRlMcoKlc/TZG+eVrNt6K7+bBse08aWwbdn6kjScl+v0csk2uoqHQCGJ6lV7k3YzYL5rSxGVgzLUraW8iJVab6yWqKtqdIxmDSZxMSNv7Kystzd3fl8E9dDOFymFYfGtWXYOrF8WtgwWdSvBryPhSqLN7GxsZMnT+7YsSPsIBTEEn+mCFKDlEWQDKQsLri7u2PDFyBMDlIWF4qLi3VkuQxBNpCyuGBjY1Nfs0DEJ4KUxYWamhp0KQYnkLK4YG9vb7QfAeLTQR8rLkgkko/q+IBoPEhZXPD09ERXDHACKYsLhYWF6IoBTiBlESQDKYsLfD4fXeTCCaQsLshkMnSRCyeQsriASln8QMriAipl8QMpiyAZSFlccHFxQXe/cAJ9rLhQVlaG7n7hBFIWQTKQsrjg4eGBbtjiBFIWF4qKitANW5xAyiJIBlIWF1BLLvxAyuICasmFH0hZBMlAyuIC6hSOH0hZXECdwvEDKYsgGUhZXEDjGOAHUhYX0DgG+IGUxQWhUIhacuEE+lhxobS0FLXkwgmkLIJkIGVxwc7ODp1+4QRSFhekUik6/cIJpCwuoGYx+IGUxQXULAY/kLK4gEpZ/EDK4gIqZfEDKYsLAoEA3UrACTRVnSnp378/k8lkMBiVlZU2NjbYYysrq2PHjsGORh2YsANQCg6HU1hYiD1WKBQAABqNNmXKFNi5KAX68zIl4eHhde4geHp6fvXVV/ASURCkrCkZPny4h4dH7VMajTZgwABbW8uayRtvkLKmxMHBYcCAAbVPPT09R44cCTURBUHKmpiYmBhvb2/s8YABA0w+vz0CKWti7O3t+/XrR6PRUBGLE5ZyxUCt1IuKVEqFOdqwdm8/9E7Kmy5dupTn0ctBNd5vR6cBexeWvbMV3m9EECziumzy3pI3z6rd/G0AFY+VZ88sfFXDc2AGh9n7teHCjoM7FFdWpzWc2FQU0MnOrw3F65Q6rf7qvuKOfR18W1HcWoore/yXwjbdHd39bWAHMRMXfivoEe3k7s+BHQRHqHz6lfNYbudkZTm+AgA+i3R5kCKBnQJfqKysqFhtzbGsFoD2zla5mbif8MGFysoqq3V2Aks5j8ag0WiuPmypSAM7CI5QWVmNSq/TU7mmbhS5VEujU7mnJJWVRVASpCyCZCBlESQDKYsgGUhZBMlAyiJIBlIWQTKQsgiSgZRFkAykLIJkIGURJAMpa2LOXzj1ee+OYrHoE/fz+PHD5d8vNFEoSoGUJSI3/kpZ9N0stUYNOwgRsZTuimRBqVRu3vLz2XMn0Ch09YGU/R8iB/f8Zs53qal/3ElL5XJ5kRFDx42djL2U+fzp1m3rs7Iy2WxOyGeh06bNteX/PQzMq+ysXzatzcrKFDg6eXn5vLvD02eOHTm6TyQqc3V1791rwFdfjrG2tm4gQEFh3r17d35auzkxMR7PAyUxSNm6JPy4/OtxsTEx4/7888ruPdsCmrfs2rV7bu7r+Qum+vo2WfjtcqmkctfurWVlJet+2gIAyM/PnTtvip2t/eRJMxkM5u97t9fuaveepKPH9g35IsbHx7+gIPfwkd8Li/IX/9/KBt7dzdXjtx2HORwqd976RJCydQkfOHjUyPEAgKZNmp+/cCr93u2uXbvv2/8bnU5f8+MmPo8PAODzbeMTlmVkPGjXrv3WpA10Gv3XTbvt7R0AAHQ6ff2GBACASFS+/8DOpUviwkJ7Y3sWCJwT1/8wc8aC2uL5fXg8nhmPlZQgZevCZv9dwjEYDGdnF7GoHADwKON+cHAnzFcAQKdOnwEAsl5mBgS0unv3dlTUMMxXAACT+fdHev9+mlarjYtfGhe/FFuCdWYWlZc1oCzigyBlG4LJYOr0OgBAdbXc3s6hdjmfb4uVo+IKkVardXN1f39bcYUIABAft97FWfjucnd3T7NkpyxI2Ubh5ORSVSWtfVpZWQEA4PH4mMfY0zrw/1uUenv7mjEp9UFXUhpF69ZtH2XcVyqV2NMbN64BAAIDg7hcroeH15/Xr2o0dTu1Bgd3otFoJ08drl2CjeuN+ESQso1i9MgJSqVi0Xezrl67dODg7m3bNwYHdQxq1wEAMG7slOLiwpmzxp88deT0mWOHj+zFNvH08BryRcytWzcWL5174eLpvft+Gz02+uWrF7APhfSgikGj8PT0XpOwKWnHL2vWruBwbPr2CZ8a+w02xnzfPgPlctmRI3u3JW3w9fFv1SqwoCAP22rG9HkuLsKTJw/fvXtbIHDq0f1zZycX2IdCeqg8JtfVA6UCD07TIMs6PT++IXfITE9bR8oWRpQ9MMJy505q3A9Ljb60aeMuHx8/syciGUhZcxMU1DFp2wGjL6FqQ2NAypobNptt9DouopGgKwYIkoGURZAMpCyCZCBlESQDKYsgGUhZBMlAyiJIBlIWQTKQsgiSgZRFkAwqK8u1ZdIpPTeLURxcrOiUnuyMysry7Jml+ZbVEUAh14qKVDw7KjcdobKyXgGcGqkWdgqzUpKrCOhA8W7lVFbW3tmqSTvu9aMlsIOYCVGx8mGKuHu0M+wg+ELlXgkYWfdkj25Imwbznd3ZVpSc0pYGKkpU8kpN1l3pyEXeDCbFq+/UVxYAUFagfHKzqkqswSZ3VSgUbDYb67lFXlQqFYPBYDKZDq5WdBrwbM4J7unQiO1Ij0UoW0tWVtaUKVNWrlwZFhYGO4sJmDt3rpeX17x582AHMSsWpOzBgwfPnj2blJREpXGv9u/ff/HixaSkJBsbG9hZzISlKDt//nw3N7cFCxbADmJ6nj9/PmXKlB9++KF79+6ws5gD6iubnZ29YcOGoUOH9uzZE3YWHFm4cKG7u/s333wDOwjuUFzZ06dPHzhwYMeOHXw+H3YW3Nm7d+/Vq1d37NjBYrFgZ8ERKiu7ePFiDw+PGTNmwA5iPp4+ffrjjz/OmTOnY8eOsLPgBTWVLS0tnTBhwuzZs/v37w87CwRiY2O7dOkyYcIE2EFwgYLKpqSknD9//ttvv3V1dYWdBRpJSUnPnj3bsGED7CCmh2rKbtq0KS8vb+3atbCDwCc1NXXFihX79u0TCoWNWJ00UErZ+Ph4Nze38ePHww5CFCoqKqZOnTp79mwqXf+iiLLV1dXDhw9ftWpVhw4dYGchHHPmzOnQocPYsWNhBzENVFD2+fPnsbGxR48epdg/oAnZsGGDRCJZvnw57CAmgPSND69du3bo0KEbN24gXxtgzpw5wcHBsbGxsIOYAHKXsvv378/IyFizZg3sIOTg0aNHS5cuPXfuHOwgnwSJld2xY0dVVZWltWP6RN6+fRsVFXX16lU7OzvYWf4lZK0YrFq1ytnZGfn6sbi5uaWlpU2cOLGsrAx2ln8JKZX9z3/+ExgYOHjwYNhBSAmdTj927Ni4ceMKCwthZ/k3kK9icPjwYQ6HExUVBTsI6YmIiDhx4oSVlRXsIB8HyUrZZcuWyeVy5KtJOHfuXHR0dEkJybpzkqmUXbt2rZeXV0xMDOwglCI0NPTixYtcLhd2kMZCmlJ29+7drq6uyFeT8+eff44fP55EJRc5lE1OTs7KyhozZgzsIBSETqfHx8eTqCwgQcWgsLBwxowZp0+fhh2Eypw5c+bhw4ekuKNLglJ25cqVBw4Yn9sNYSqioqJcXV3PnDkDO8iHIXopu2rVqsDAwOjoaNhBLIJ+/fodPHhQIBDADtIQhC5l7927V1hYiHw1G3FxcUuWLIGd4gMQWtn9+/fHxcXBTmFBdOrUqW3btpcuXYIdpCGIq+zx48ednZ2dnJxgB7Eshg0bRvAeY8RVNikpacqUKbBTWBwuLi4hISGnTp2CHaReCKrspUuXBg0ahIpYKEyaNOnatWuwU9QLQZU9fvw4lXrYkQs3NzeFQvHw4UPYQYxDRGWLiopUKlX79u1hB7FchgwZcvnyZdgpjENEZf/888+goCDYKSyabt26JScnw05hHCIqm5aW1qVLF9gpLBo7Ozs3N7cXL17ADmIEIiqrUCg6d+4MO4Wl07t372fPnsFOYQTCKZufny8Siag93CQpEAqFGRkZsFMYgXDK5ubmomsFRCAgIECn08FOYQTCKVtcXKzX62GnQACBQJCWlgY7hREIp6xSqfTz84OdAgEcHBz8/Py0WsJNT0k4ZUtKSgjeHtJyyM7OrqmpgZ2iLoRTlsfjkXcgE4rRrl07pVIJO0VdiNLEe/jw4QwGg0ajlZeXc7lcDodDo9FoNBrqj2B+hg0bZm1tzWAwcnJy3NzcsMfW1tbbt2+HHQ0AAIgyC7parS4qKsIeSyQSAIBerw8JCYGdyxJ58+ZN7Wypubm52INp06ZBDfUPRKkYREZG1plU1s7ObuLEifASWS6fffZZnYs2Xl5eI0eOhJfofyCKsjExMZ6enu8uadWqVXBwMLxElsuECRPs7e3fXRIREcHhcOAl+h+IoiyPxwsPD6fT/87j6Og4efJk2KEslPbt27du3br2JMfb23vUqFGwQ/0DUZQFAIwcOdLHxwd73LZtW9SYCyITJkzA+tkyGIzo6Gg2mw070T8QSFkulxsZGclgMBwdHSkzFwVJCQ4ObtOmDQDA09Nz+PDhsOP8D426YqDV6BVyc9xEHdDni/OnU/z8/Py8Wskqcb/votcb7AQka39TI9PqzHJD6quhX7/MLBg86EutkiVT4v6WNBrg2TfKxg9cl32eXvX4L2lFiZrDY5guHlGwE7CK3yj823A79HVw8STQf59Rbp8XPU+X2Tqy5BLC3UT9dJw8rItzFM2CeaFDnBlMWgNrNqRsenKFqFgTFObIdyRZUdR49HqDVKT+63hpz2HOHk2JclJcB73ecGpzsXdLrmdzHteWKJfSTY5aqRMXq67sK560ys/apt4isl5l0y5VVIm1XSNc8AxJIM4lFYQNdXL3J6K1J34pat7RzqcVD3YQc6DXG/atzpmxrml9Kxg//aosU4uKVJbjKwCg1wi3+1crYacwwot7Vc7ebAvxFQBAp9PChrmmnhbVu4LRpaIilcHQUH2CetjwmSV5SoWccI2a375RcriUrQwYxc6Jlfe83hZkxpWVS3XOXkQ/HTE53i14FSVq2CnqolUbHITWsFOYFXsXaysO3aA3XmU1/vPVqPQawjU6wx15pQZ2BCPIKrX6er48ClOaq6TRjf/PE+hWAgLRGJCyCJKBlEWQDKQsgmQgZREkAymLIBlIWQTJQMoiSAZSFkEykLIIkoGURZAMAil7/sKpz3t3FIvrbXXWMHfupE6JHdV/YMhXIwat35AgrZKaOqAFsTp+6divh/7rzdPSb02aMmLgoO6jx0Tv27/TtGPREUjZT6G8vGzpsvksK6vYybN7hvU9f+FUXBzRJ7akME+fPvL18Z88cWbz5i1/27l51+6tJtw5RRpiOju7LF+WEPJZKIPBAABUV8vPXzgll8t5PEtpGU0oJoyfho39M2RITH5B7pWrFyZPmmmqnZtM2cjBPb+Z811q6h930lK5XF5kxNBxY/8eO0MsFm3ZmpiWflOr1Qa2CZoa+42//9/dJF5lZ/2yaW1WVqbA0cnLy+fdHT58dG/7jk05OS8dHByDgzpNmjhDIGho5roe3T+vfcxmcwAAOvN0RSUYr7KzZs2ekBC/MWnHLzk5L4VCt9jJs7t1C8NezXz+dOu29VlZmWw2J+Sz0GnT5trybbGXUv5I3vN7UmnpW18f/zoDHJ0+c+zI0X0iUZmrq3vvXgO++nKMtXVDTXjfHavKwd6xprrahAdoyopBwo/LmzYNWJ+4vW+f8N17tt25k4oNcTxvwdT7D9KnTJ4975vFInH5vAVTZXIZACA/P3fuvCliUfnkSTOHDx/98tU/85/cf5C+cNFMXx//BfP/8+Ww0Y8fP5i3YGrjB468e+92s6YBdnb2jViXgqhUqhWr/m/Y0JHrf05yFbqtjl8ilUoAALm5r+cvmKrRaBZ+u3zcmMmpqX+sWLEI2+TqtUurVi8WODrNmvltp06f5bx+Vbu33XuSkrZv7PV5v28XLOsZ1ufwkd/XJTZqLmxplTTlj+RHGfejo7804dGZsmIQPnDwqJHjAQBNmzQ/f+FU+r3bXbt2v3L1Qn5+7rqftrQP7gQACAwMHjk66sSJQ+PGTt6atIFOo/+6abe9vQMAgE6nr9+QgO3ql01rIyOGzJ61EHvasWPXceOH3b13+92itD7+Sv0jPz938XerTHhopGPWzG97fd4PADBp0szYqaMzHj8I7dFr3/7f6HT6mh838Xl8AACfbxufsCwj40GLFq03/fpT27bBa9f8itWsiooKsnNeAgBEovL9B3YuXRIXFtob27NA4Jy4/oeZMxbUFs/1ERe35O69Oz3D+nw5fLQJD82UymJ/x9ioOM7OLmJROQAgI+M+j8vDfAUAuLq6eXv7Zr3MVCqVd+/ejooahvkKAGAy/w5TUvI2L+9NUVHBufMn391/WVnpBzMoFIpfN69rEdCqT+8BJjw00sH573chFLph5gEAHmXcDw7uhPkKAOjU6TMAQNbLTI1WI5VKhg0difkKAKD/98H9+2larTYufmlc/FJsCdYlW1Re9kFlx4+f1qxZi8NH9m7dtmFq7BxTHRpep19MBlOn1wEA5NVyu/9KiWFraycWlYsrRFqt1s3V/f1tKyvFAIBxY6eE9uj17nJHxw/Pwvzbzs1lZaUrvl9bZ+hPi4XFZAEA9HoddlZqb/fPd8Hn22I283h8AICrse9CXCECAMTHrXdxFr673N3d8/2V69CyReuWLVobDIaDh/ZEDPrC09PbJEeE+xUDZyeXzMwn7y6pqBALXVyxz66ysuL9TbBPUKVSenv7ftR7vcjKPHnqcPTg4QHNW35ycAri5ORS9c7lauzD5/H42HchkRjpE8//b1H6sd9FLS1atAYA5Lx+ZSplcb8u27p1W5ms6vnzp9jTnJxXRUUFgYFBXC7Xw8Prz+tXNZq6nQQ9Pb2FQteLl84oFApsiVarfX+1Omi12nXrVtvbO0wYPx2fQyE9rVu3fZRxv/Ys9saNawCAwMCgJk2a0+n0q9cuvr9JcHAnGo128tTh2iW1X0oDyOXy2scvXz7HrhuY6CDwL2X79B64/8Cu71cuGjN6Ep1O37t3h729w+Co4dhff/wP/5k5a/yAAVF0Ov34iYPYJjQabcb0+cuWfztj1tdRkcP0Ot3l5HN9+4YPG9rQQNJHj+3PznkZHNTxxMlD2BIHB8fIiCF4HyCJGD1yQkrK5UXfzYqMGFpWVrLn96TgoI5B7TrQaLSBA6LOXzilVqk6dw4Ri0VpaakODgIAgKeH15AvYo6fOLh46dzu3XqKxaJTp4/8EL+hebMW9b2LVqud/c1EL0+f1q3bFhbmX7x0pmmT5q1btzXVUeCuLJPJXPvjr5u3/Lxla6Jer28bGDxj+nwHB0cAQN8+A+Vy2ZEje7clbfD18W/VKrCgIA/bqkf3z3+IW79r99ZfN6/jcnltA4Pbtm1ornuxWPT73u3Y1dyHj+5hC319/ZGy7+Lp6b0mYVPSjl/WrF3B4dj07RM+NfYbrNI/a+a3VlZWV69dunf/Tps2QU2aNK+oEGNbzZg+z8VFePLk4bt3bwsETj26f+7s1NAwQgwGY1D4F2fOHrt95y9nJ5fIyKHjxkyuPbH7dIyPyZV+uUKtBO16mqwwJwXJe4q6hjsSbTC5E5uKAns4uvoSKxXe7Pk+e2ai8WG5yHTDVi6XjxgVYfSl2ClzIgZ9YfZElsudO6lxPyw1+tKmjbt8fHCcH5NMytrY2CRtMz4NmC0fzW5nVoKCOtb3XTRcbfh0yKQsnU43eh0XYX7YbDas74IijQ8RlgNSFkEykLIIkoGURZAMpCyCZCBlESQDKYsgGUhZBMlAyiJIBlIWQTKM37C1YtP0wOI6ovAFLBrxfsJ2jiw6BecP/gBu/hyDwWC0N5Txr4jvwCrP+3Djc4qR+1QucLOCnaIuTGuauFgFO4VZqShRqRW6+nrvGVfWxcva0nr7ySrVHk051hzCFWju/mwCzvmIK5JylW9rbn2v1lvKejRl3zhegmcwYnF139suA4nYpL1ZML9KrHqRLoEdxEzIpZo758s/GySob4WGJrd/dlv66pG8XZjAQWjFYBKvlmcKlNU6iUiVeqI0aqq7wJW4825e2PXW3sXaoxnXkbqTg8oqNRVvlamnyiat8mNa1etbQ8oCAN48q350XVLyRslgmqmioDfoAaDRzVIvcRCypCKNXxtu5/6OfAeWGd7xU3j4R+WLuzIanVYlNtPEpTq9nk6vZ1pOUyP0ZktE6qbteN2iPjBaxQeUrUWl0DdiLROQkJAQFBQ0YIA5xnox6AGbS7J/D53WoNWYaUrbmJiY9evXu7q6muPNDAZrm0adSDS2V4I1x0xfrYGmpjN1Zns70sFg0sz2j6fVK1jW5vvqGwmx0iAQH4RwytrZ2bFYRK9WWgi+vv9yUCNcIZyyUqn0g2MZIczD69ev6XTCGUK4QE5OTg2PEI0wGy1atDDhKC+mgnDKyuXyqqoq2CkQAADw8OFDKyvC3cGgbdEBAAALxklEQVQmnLJCoZCAf0aWSYsWLdhsNuwUdSGcHEwmMz8/H3YKBJDL5c+ePSNgJY1wygqFQlQxIAIikcjHx6cRK5obwinr5ub2+vVr2CkQoLCw0MHBoRErmhvCKevn5/fmzRvYKRDg9evX/v7+sFMYgXDK8ni8Dh06iET/ciZbhKmQyWQtWxJxygnCKQsA4HK56enpsFNYOhcvXgwMDISdwghEVLZTp053796FncKiKSwsZDAYbm5usIMYgYjKdu3atby8HHYKi+bRo0f9+/eHncI4RFTWxcVFo9Hcu3cPdhDL5cSJE926dYOdwjhEVBYAEB4efuHCBdgpLJSioiKRSNSuXTvYQYxDUGWjoqIyMzPrTLGOMA/JyckjRoyAnaJeGtuRxvz89ttvKpVq+nQ0VaJZUavVYWFht2/fhh2kXoirLHbpIC0tDbWSMSebNm3icrnjx4+HHaReCG3DwoULd+zYATuFBSGXy9PT04nsK9GVHT58eGpq6rNnz2AHsRQWLVpE/JoYoZUFAMTHxy9evBh2CovgzJkzLi4uXbt2hR3kAxC6Lotx4sSJkpIS4v/6SY1EIpk7d+6uXbtgB/kwRC9lAQBDhgyRyWRHjhyBHYTKREdHb9y4EXaKRkECZbE6VkpKCmp4gBPjx4/fuHEjn8+HHaRRkENZAMDWrVu3b9+O+tiYnFWrVo0dO7Zt27awgzQWEtRl32Xo0KHr1q0j5pAQZGThwoX9+/fv3bs37CAfAWlKWYzjx4/Pnz//1q1bsINQgenTp5POV/KVshhLlizp2bNn3759YQchMePGjVuzZo1QKIQd5KMhWSmLERcXd+XKlT179sAOQkoUCkXfvn3nz59PRl/JWspibNy4kcViTZs2DXYQMpGRkbFo0aIDBw44OhJxlP3GQGJlAQCXLl3atWvX77//TsARIgjIgQMHrl69unPnTthBPglyKwsAyM7OHjt27JYtWwjbJJkgLF68WCAQzJ8/H3aQT4X0ymIsWbLEx8dnypQpsIMQkYKCgoSEhKioKMJ25/ooKKIsAGDbtm3p6embN29GlYR3OXbs2L59+7Zs2ULM7rL/Auooi/ULnT59ekJCQmhoKOwshGDdunVqtfq7776DHcSUkPIiV30EBQXdunXr8uXLy5Ytg50FMjdu3OjcuXPnzp0p5ivVlMWIi4vr0qVLaGjo/fv3YWeBw/r160+ePHn79u0ePXrAzmJ6KKgsAGDQoEEXL148c+bMqlWrYGcxKzdv3gwNDQ0ICEhMTCTgmPEmgZrKYgN7rVixIjAwkOD9RU3Ixo0bDx8+fPHixYEDB8LOgiOUVRYjOjr6/PnzFy9eXLJkSZ2XevXqBSnUpzJnzpw6S65cudKlS5eAgICNGzdyufXOsU0NKK4sNvrnypUre/ToMWHChPPnz2MLo6Ojq6qqCN6V1CjJyclPnz7t0qUL9lQuly9fvvzatWs3b96kxmXXD0J9ZTEGDBiwc+fOtLS06dOni8XigoICAMCrV68OHToEO9rHsWXLFqlUqtPpwsPDjxw5MmjQoH79+iUkJDCZjZ3blexYynFirFy5Mi0trX///jQaDQCgVCoPHDgQERHB4/FgR2sUmzdvLi4uxh6Xlpa+efPm+vXrsEOZG0spZWtZs2bNu0+Li4t//vlneHE+gry8vAsXLuh0OuwpjUY7d+4c7FAQsDhlCwsL6yxJTU198uQJpDgfQWJiYklJybtLFApFREQEvERwsCxlIyMjra2tsWqfwWDAblaLxWLiF7RXrlx58OBB7VO9Xk+n021sbJRKJdRcEKBUG4PG8Pjx44qKisrKyrdv3xYXF6ukNnxGM2vg4unmr5TraDSgVhNrhFAHF2tltVYiK6/WlFWpcg02b13c7IVCoZOTk4ODQ+2lA8vB4pTFUMh1d5MlmWlSNo9lK+QyrZlMaybTisFk0Yn2cdCAQa3UaVU6nVYvF9XIRTV2LlbBYXYBHcgx7IDJsThlDQbDH0fFLx9UuTYX8J04DBb57mpWS5SVBVUGrabHFwLflhS/cfA+lqVsYbb6j6NlHHsbJ1872Fk+FUWVSpQncRKy+o9xtqgReC1I2efpVbfOV/p38cAuylIDcb5UW13z1TxP2EHMh6UoW5itvHZY5NOeIi3z30UmqlFJZMNmucMOYiYsQtm859XXT1V6B1HQVwyZqKamXGohZS31K0E1Mu2lPaUU9hUAwHeyYfFsrh4sgx3EHFBf2fO/lfp0cIWdAnccvezKS3S5mXLYQXCH4sq+fCBTa+hsnkX0uXXwsL9xsgJ2CtyhuLJ/nRI7NyHrSD4fC5tvxWSznqdXwQ6CL1RWNuexjGPPtuIQsYHl/qPLftzwpcl36+Bl9zgVKUtaXj6s4dixYacwKxy+dZVYI6vUwA6CI1RWNi+z2tbZ4u5n8pxtXj+php0CR4j4p2kSyvKVju4cBguX32RFZfGZi+tf5qSzmNYe7gED+0z18mgFANi1/1tnJx8Gg5l275RWp2nZvNuQyIUc9t9dHh49uZL8x45KyVuhs7/BgFd7MZ7ApqxQgdPOiQBlS9lqmU6DTzPCqirRpu2Ta2qqBofPG9R/pk6n+XVH7NvSHOzV6zf3V1QWTxi9Ljp83uOn1679+fdMWg8yLu87stSWJ4gOnx/QrGtxySs8sgEAGCyGqEiF086JAGVL2RqZls7EpZXWles7eVzH2PGbGAwmAKBDu4EJ64em3TsdPWgeAMBZ4D1y2Aoajebt2fpx5h9Z2XciwCyNRnX6ws/+PsGTx/2CjYghEhfgZC3LmlEj0+KxZ4JAWWU1Sj3LxgqPPb94eUsiLV28qmftEp1OI6kqxR6zWOzaZjeO9m65+Y8BAG/yMqprJD1CYmpHcKHT8Wr0yLRmsHlMg8FApdY/70JZZelMmqZGjceeZXJxq4Dug/rNeHch29pIH10Gg6XX6wAAldISzGA88tRBp9FXSzRU9ZXKytrwmXptDS575thW10hdnD9i7jEe1wEAIK+R4JGnDlqVjsOj7NdK5dMvGz5Dp9Hhsedm/p1y8zMKip7XLlGpP3CG7u7ajEajP8i4hEeeOmjVOq4t+bpaNB7K/hyF3my5GJcT576fT3r+8ub2PbNDu43kcx1fvLqt1+vGj1rbwCYO9q6d20em3T+t1aoCmn1WJRM9f3mTzxPgEU8hVXn4UrlNBWWVZTBprn4cmaiG72Rj2j07CTxnTt5+9vLGlOu7AY3m6daiW9fhH9wqetB8JtPq4ePLWdlpft7t3F2by+Ri0wbDqK6obhrpgseeCQKVm3g/TpVk3lO5BjjBDmI+NCpt3r3iSav9YAfBEcqWsgCAFp1s76cUNLBCTU1VfOIXRl9ycvQUVdQdVwYA0LpF6Iihy02VUKGUx60bbPQlno290dO1sJCRfT+fWN8OpSXVrUNsTRWPmFC5lAUA3DonLso3OPs5GH1Vr9dLpCVGXwKABoCRT8bKioOd/puEBgJotRomk/X+cg6bz+EYH8HAYDA8u5o78+empopHTCiuLABg84KcFj296QzKXhuppfSluGkbVofeJvtFERPqf5F9RrmU5+ByokMolHI1Ta+hvK8WoWzzYL6HH0ucWwk7CI4YDIbsW0VfzvWAHcQcUF9ZAED3KIGTkFaWTVlrCx+XjF3qAzuFmbAIZQEAYUMEXK62PIdqvflUNZrMlNzoqa62AiPnapSE+qdf75J+uSLvlcbW1daai0sjLzNTUVglLZaO/s6bZWUpRY/FKQsAyHtR/ccRkRXX2qWJA9OarJelJcXyspyKgA78sKEWdKMEw+KUxchMq3p2R15dpeMKbGyFXCsOk/it9fQ6vVyskItqaiqV7k3YYUOcuHZk/cl9ChaqLMbbN4pXj6pL8lRleQorNoPFYbA4DIOWWB8Im8+qKlOoFTq+kxXPlhHQnufXxobNtURZMSxa2XepkWmrpTq1klijzgMA6HQah0/n2jJZ1hZUYW0ApCyCZKAfLoJkIGURJAMpiyAZSFkEyUDKIkgGUhZBMv4fk62Fa4lxC00AAAAASUVORK5CYII=", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def node_1(state):\n", + " print(\"---Node 1---\")\n", + " return {\"foo\": [state['foo'][-1] + 1]}\n", + "\n", + "def node_2(state):\n", + " print(\"---Node 2---\")\n", + " return {\"foo\": [state['foo'][-1] + 1]}\n", + "\n", + "def node_3(state):\n", + " print(\"---Node 3---\")\n", + " return {\"foo\": [state['foo'][-1] + 1]}\n", + "\n", + "# Build graph\n", + "builder = StateGraph(State)\n", + "builder.add_node(\"node_1\", node_1)\n", + "builder.add_node(\"node_2\", node_2)\n", + "builder.add_node(\"node_3\", node_3)\n", + "\n", + "# Logic\n", + "builder.add_edge(START, \"node_1\")\n", + "builder.add_edge(\"node_1\", \"node_2\")\n", + "builder.add_edge(\"node_1\", \"node_3\")\n", + "builder.add_edge(\"node_2\", END)\n", + "builder.add_edge(\"node_3\", END)\n", + "\n", + "# Add\n", + "graph = builder.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "markdown", + "id": "5439baad-5a75-4188-b936-dbe74cdd9078", + "metadata": {}, + "source": [ + "We can see that updates in nodes 2 and 3 are performed concurrently because they are in the same step." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "44598f97-0a59-4ed4-9d9a-e15a98b3d8fb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---Node 1---\n", + "---Node 2---\n", + "---Node 3---\n" + ] + }, + { + "data": { + "text/plain": [ + "{'foo': [1, 2, 3, 3]}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.invoke({\"foo\" : [1]})" + ] + }, + { + "cell_type": "markdown", + "id": "87faaa07-2955-4466-9bca-4b536e05f260", + "metadata": {}, + "source": [ + "Now, let's see what happens if we pass `None` to `foo`.\n", + "\n", + "We see an error because our reducer, `operator.add`, attempts to concatenate `NoneType` pass as input to list in `node_1`. " + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "7f05984b-2bc7-48d1-b070-c8a001a6b59a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TypeError occurred: can only concatenate list (not \"NoneType\") to list\n" + ] + } + ], + "source": [ + "try:\n", + " graph.invoke({\"foo\" : None})\n", + "except TypeError as e:\n", + " print(f\"TypeError occurred: {e}\")" + ] + }, + { + "cell_type": "markdown", + "id": "4f9d4930-ee8f-4ffc-b9e1-3c910b2e15f6", + "metadata": {}, + "source": [ + "## Custom Reducers\n", + "\n", + "To address cases like this, [we can also define custom reducers](https://langchain-ai.github.io/langgraph/how-tos/subgraph/#custom-reducer-functions-to-manage-state). \n", + "\n", + "For example, lets define custom reducer logic to combine lists and handle cases where either or both of the inputs might be `None`." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "3314219d-29ff-4b78-b18e-fa9f7878a02f", + "metadata": {}, + "outputs": [], + "source": [ + "def reduce_list(left: list | None, right: list | None) -> list:\n", + " \"\"\"Safely combine two lists, handling cases where either or both inputs might be None.\n", + "\n", + " Args:\n", + " left (list | None): The first list to combine, or None.\n", + " right (list | None): The second list to combine, or None.\n", + "\n", + " Returns:\n", + " list: A new list containing all elements from both input lists.\n", + " If an input is None, it's treated as an empty list.\n", + " \"\"\"\n", + " if not left:\n", + " left = []\n", + " if not right:\n", + " right = []\n", + " return left + right\n", + "\n", + "class DefaultState(TypedDict):\n", + " foo: Annotated[list[int], add]\n", + "\n", + "class CustomReducerState(TypedDict):\n", + " foo: Annotated[list[int], reduce_list]" + ] + }, + { + "cell_type": "markdown", + "id": "dcdea26a-38d0-4faf-9bf6-cd52eb902635", + "metadata": {}, + "source": [ + "In `node_1`, we append the value 2." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "f5f270db-6eff-47c9-853b-dfb8108ff28c", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "TypeError occurred: can only concatenate list (not \"NoneType\") to list\n" + ] + } + ], + "source": [ + "def node_1(state):\n", + " print(\"---Node 1---\")\n", + " return {\"foo\": [2]}\n", + "\n", + "# Build graph\n", + "builder = StateGraph(DefaultState)\n", + "builder.add_node(\"node_1\", node_1)\n", + "\n", + "# Logic\n", + "builder.add_edge(START, \"node_1\")\n", + "builder.add_edge(\"node_1\", END)\n", + "\n", + "# Add\n", + "graph = builder.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))\n", + "\n", + "try:\n", + " print(graph.invoke({\"foo\" : None}))\n", + "except TypeError as e:\n", + " print(f\"TypeError occurred: {e}\")" + ] + }, + { + "cell_type": "markdown", + "id": "fd21936b-62f1-4311-9ce5-2c7d08aa35bf", + "metadata": {}, + "source": [ + "Now, try with our custom reducer. We can see that no error is thrown." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "867784bc-796c-4b1e-a4d3-2810395cf5e2", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---Node 1---\n", + "{'foo': [2]}\n" + ] + } + ], + "source": [ + "# Build graph\n", + "builder = StateGraph(CustomReducerState)\n", + "builder.add_node(\"node_1\", node_1)\n", + "\n", + "# Logic\n", + "builder.add_edge(START, \"node_1\")\n", + "builder.add_edge(\"node_1\", END)\n", + "\n", + "# Add\n", + "graph = builder.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))\n", + "\n", + "try:\n", + " print(graph.invoke({\"foo\" : None}))\n", + "except TypeError as e:\n", + " print(f\"TypeError occurred: {e}\")" + ] + }, + { + "cell_type": "markdown", + "id": "b7ebc65e-c185-4981-a6e7-20fe37d2f8fe", + "metadata": {}, + "source": [ + "## Messages\n", + "\n", + "In module 1, we showed how to use a built-in reducer, `add_messages`, to handle messages in state.\n", + "\n", + "We also showed that [`MessagesState` is a useful shortcut if you want to work with messages](https://langchain-ai.github.io/langgraph/concepts/low_level/#messagesstate). \n", + "\n", + "* `MessagesState` has a built-in `messages` key \n", + "* It also has a built-in `add_messages` reducer for this key\n", + "\n", + "These two are equivalent. \n", + "\n", + "We'll use the `MessagesState` class via `from langgraph.graph import MessagesState` for brevity.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "901e69e5-c4cb-4d58-82fb-3b7d968758e3", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Annotated\n", + "from langgraph.graph import MessagesState\n", + "from langchain_core.messages import AnyMessage\n", + "from langgraph.graph.message import add_messages\n", + "\n", + "# Define a custom TypedDict that includes a list of messages with add_messages reducer\n", + "class CustomMessagesState(TypedDict):\n", + " messages: Annotated[list[AnyMessage], add_messages]\n", + " added_key_1: str\n", + " added_key_2: str\n", + " # etc\n", + "\n", + "# Use MessagesState, which includes the messages key with add_messages reducer\n", + "class ExtendedMessagesState(MessagesState):\n", + " # Add any keys needed beyond messages, which is pre-built \n", + " added_key_1: str\n", + " added_key_2: str\n", + " # etc" + ] + }, + { + "cell_type": "markdown", + "id": "287805e4-722a-4428-b040-2892b29de870", + "metadata": {}, + "source": [ + "Let's talk a bit more about usage of the `add_messages` reducer." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "c8f61350-4fe0-4a2b-bb24-9305afb3c668", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[AIMessage(content='Hello! How can I assist you?', additional_kwargs={}, response_metadata={}, name='Model', id='a7930cc8-6d1a-4a56-b68e-5734d7f246e8'),\n", + " HumanMessage(content=\"I'm looking for information on marine biology.\", additional_kwargs={}, response_metadata={}, name='Lance', id='b8e1636e-9fbd-46bf-959f-68a74dec31cc'),\n", + " AIMessage(content='Sure, I can help with that. What specifically are you interested in?', additional_kwargs={}, response_metadata={}, name='Model', id='4b2ce061-8c39-46b9-aa24-50e48d0d2d15')]" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langgraph.graph.message import add_messages\n", + "from langchain_core.messages import AIMessage, HumanMessage\n", + "\n", + "# Initial state\n", + "initial_messages = [AIMessage(content=\"Hello! How can I assist you?\", name=\"Model\"),\n", + " HumanMessage(content=\"I'm looking for information on marine biology.\", name=\"Lance\")\n", + " ]\n", + "\n", + "# New message to add\n", + "new_message = AIMessage(content=\"Sure, I can help with that. What specifically are you interested in?\", name=\"Model\")\n", + "\n", + "# Test\n", + "add_messages(initial_messages , new_message)" + ] + }, + { + "cell_type": "markdown", + "id": "bc492370-0502-43e6-87cc-181c60b3dbdb", + "metadata": {}, + "source": [ + "So we can see that `add_messages` allows us to append messages to the `messages` key in our state.\n", + "\n", + "### Re-writing\n", + "\n", + "Let's show some useful tricks when working with the `add_messages` reducer.\n", + "\n", + "If we pass a message with the same ID as an existing one in our `messages` list, it will get overwritten!" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "1f6f82fd-a5a8-4e98-80f6-bb058f2acc47", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[AIMessage(content='Hello! How can I assist you?', additional_kwargs={}, response_metadata={}, name='Model', id='1'),\n", + " HumanMessage(content=\"I'm looking for information on whales, specifically\", additional_kwargs={}, response_metadata={}, name='Lance', id='2')]" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Initial state\n", + "initial_messages = [AIMessage(content=\"Hello! How can I assist you?\", name=\"Model\", id=\"1\"),\n", + " HumanMessage(content=\"I'm looking for information on marine biology.\", name=\"Lance\", id=\"2\")\n", + " ]\n", + "\n", + "# New message to add\n", + "new_message = HumanMessage(content=\"I'm looking for information on whales, specifically\", name=\"Lance\", id=\"2\")\n", + "\n", + "# Test\n", + "add_messages(initial_messages , new_message)" + ] + }, + { + "cell_type": "markdown", + "id": "f06e7788-7054-4752-99fe-27ebb901f263", + "metadata": {}, + "source": [ + "### Removal\n", + "\n", + "`add_messages` also [enables message removal](https://langchain-ai.github.io/langgraph/how-tos/memory/delete-messages/). \n", + "\n", + "For this, we simply use [RemoveMessage](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.modifier.RemoveMessage.html) from `langchain_core`." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "67ac97e5-efe2-40bc-9fe3-fd4f50922b8b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='1'), RemoveMessage(content='', additional_kwargs={}, response_metadata={}, id='2')]\n" + ] + } + ], + "source": [ + "from langchain_core.messages import RemoveMessage\n", + "\n", + "# Message list\n", + "messages = [AIMessage(\"Hi.\", name=\"Bot\", id=\"1\")]\n", + "messages.append(HumanMessage(\"Hi.\", name=\"Lance\", id=\"2\"))\n", + "messages.append(AIMessage(\"So you said you were researching ocean mammals?\", name=\"Bot\", id=\"3\"))\n", + "messages.append(HumanMessage(\"Yes, I know about whales. But what others should I learn about?\", name=\"Lance\", id=\"4\"))\n", + "\n", + "# Isolate messages to delete\n", + "delete_messages = [RemoveMessage(id=m.id) for m in messages[:-2]]\n", + "print(delete_messages)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "2d250578-3ec0-452e-91c0-072d785d96db", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[AIMessage(content='So you said you were researching ocean mammals?', additional_kwargs={}, response_metadata={}, name='Bot', id='3'),\n", + " HumanMessage(content='Yes, I know about whales. But what others should I learn about?', additional_kwargs={}, response_metadata={}, name='Lance', id='4')]" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "add_messages(messages , delete_messages)" + ] + }, + { + "cell_type": "markdown", + "id": "5db095c5-6d9a-4e62-a097-0403797511f6", + "metadata": {}, + "source": [ + "We can see that mesage IDs 1 and 2, as noted in `delete_messages` are removed by the reducer.\n", + "\n", + "We'll see this put into practice a bit later." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c8b0347d-cbf0-4164-9cf6-39c4e040a313", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "lc-academy", + "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.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/langchain-academy/module-2/state-schema.ipynb b/langchain-academy/module-2/state-schema.ipynb new file mode 100644 index 000000000..3e2cd0b5d --- /dev/null +++ b/langchain-academy/module-2/state-schema.ipynb @@ -0,0 +1,519 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "256d3948", + "metadata": {}, + "source": [ + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-2/state-schema.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239426-lesson-1-state-schema)" + ] + }, + { + "cell_type": "markdown", + "id": "f118fabe-37b7-4cd4-b7a4-9b0fc3875ca3", + "metadata": {}, + "source": [ + "# State Schema \n", + "\n", + "## Review\n", + "\n", + "In module 1, we laid the foundations! We built up to an agent that can: \n", + "\n", + "* `act` - let the model call specific tools \n", + "* `observe` - pass the tool output back to the model \n", + "* `reason` - let the model reason about the tool output to decide what to do next (e.g., call another tool or just respond directly)\n", + "* `persist state` - use an in memory checkpointer to support long-running conversations with interruptions\n", + " \n", + "And, we showed how to serve it locally in LangGraph Studio or deploy it with LangGraph Cloud. \n", + "\n", + "## Goals\n", + "\n", + "In this module, we're going to build a deeper understanding of both state and memory.\n", + "\n", + "First, let's review a few different ways to define your state schema." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "b9a896f4-8509-456a-9a25-46532342f459", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install --quiet -U langgraph" + ] + }, + { + "cell_type": "markdown", + "id": "9f7927b0-9909-4e54-b997-ac49c1aeaa09", + "metadata": {}, + "source": [ + "## Schema\n", + "\n", + "When we define a LangGraph `StateGraph`, we use a [state schema](https://langchain-ai.github.io/langgraph/concepts/low_level/#state).\n", + "\n", + "The state schema represents the structure and types of data that our graph will use.\n", + "\n", + "All nodes are expected to communicate with that schema.\n", + "\n", + "LangGraph offers flexibility in how you define your state schema, accommodating various Python [types](https://docs.python.org/3/library/stdtypes.html#type-objects) and validation approaches!\n", + "\n", + "## TypedDict\n", + "\n", + "As we mentioned in Module 1, we can use the `TypedDict` class from python's `typing` module.\n", + "\n", + "It allows you to specify keys and their corresponding value types.\n", + " \n", + "But, note that these are type hints. \n", + "\n", + "They can used by static type checkers (like [mypy](https://github.com/python/mypy)) or IDEs to catch potential type-related errors before the code is run. \n", + "\n", + "But they are not enforced at runtime!" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "eedb39f0-af0f-4794-bc16-65980d278b59", + "metadata": {}, + "outputs": [], + "source": [ + "from typing_extensions import TypedDict\n", + "\n", + "class TypedDictState(TypedDict):\n", + " foo: str\n", + " bar: str" + ] + }, + { + "cell_type": "markdown", + "id": "d5a71661-1086-455f-a5e0-a6d104034a95", + "metadata": {}, + "source": [ + "For more specific value constraints, you can use things like the `Literal` type hint.\n", + "\n", + "Here, `mood` can only be either \"happy\" or \"sad\"." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4ad9749c-b127-433f-baa3-189a9349e9f6", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Literal\n", + "\n", + "class TypedDictState(TypedDict):\n", + " name: str\n", + " mood: Literal[\"happy\",\"sad\"]" + ] + }, + { + "cell_type": "markdown", + "id": "c1a9152d-1728-4a67-9e23-1ef622525047", + "metadata": {}, + "source": [ + "We can use our defined state class (e.g., here `TypedDictState`) in LangGraph by simply passing it to `StateGraph`.\n", + "\n", + "And, we can think about each state key just a \"channel\" in our graph. \n", + "\n", + "As discussed in Module 1, we overwrite the value of a specified key or \"channel\" in each node." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2f7a0d6d-f70b-44ed-86e3-7cdb39873ba4", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import random\n", + "from IPython.display import Image, display\n", + "from langgraph.graph import StateGraph, START, END\n", + "\n", + "def node_1(state):\n", + " print(\"---Node 1---\")\n", + " return {\"name\": state['name'] + \" is ... \"}\n", + "\n", + "def node_2(state):\n", + " print(\"---Node 2---\")\n", + " return {\"mood\": \"happy\"}\n", + "\n", + "def node_3(state):\n", + " print(\"---Node 3---\")\n", + " return {\"mood\": \"sad\"}\n", + "\n", + "def decide_mood(state) -> Literal[\"node_2\", \"node_3\"]:\n", + " \n", + " # Here, let's just do a 50 / 50 split between nodes 2, 3\n", + " if random.random() < 0.5:\n", + "\n", + " # 50% of the time, we return Node 2\n", + " return \"node_2\"\n", + " \n", + " # 50% of the time, we return Node 3\n", + " return \"node_3\"\n", + "\n", + "# Build graph\n", + "builder = StateGraph(TypedDictState)\n", + "builder.add_node(\"node_1\", node_1)\n", + "builder.add_node(\"node_2\", node_2)\n", + "builder.add_node(\"node_3\", node_3)\n", + "\n", + "# Logic\n", + "builder.add_edge(START, \"node_1\")\n", + "builder.add_conditional_edges(\"node_1\", decide_mood)\n", + "builder.add_edge(\"node_2\", END)\n", + "builder.add_edge(\"node_3\", END)\n", + "\n", + "# Add\n", + "graph = builder.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "markdown", + "id": "724bb640-2b0e-46c1-9416-b5bcdb9c17c8", + "metadata": {}, + "source": [ + "Because our state is a dict, we simply invoke the graph with a dict to set an initial value of the `name` key in our state." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "74e09d32-6a08-4250-b19a-1f701828829d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---Node 1---\n", + "---Node 3---\n" + ] + }, + { + "data": { + "text/plain": [ + "{'name': 'Lance is ... ', 'mood': 'sad'}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.invoke({\"name\":\"Lance\"})" + ] + }, + { + "cell_type": "markdown", + "id": "70cc5368-18b8-49c7-b561-41888b092311", + "metadata": {}, + "source": [ + "## Dataclass\n", + "\n", + "Python's [dataclasses](https://docs.python.org/3/library/dataclasses.html) provide [another way to define structured data](https://www.datacamp.com/tutorial/python-data-classes).\n", + "\n", + "Dataclasses offer a concise syntax for creating classes that are primarily used to store data." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d576fc2c-350b-42ad-89e5-f93ae102dbf8", + "metadata": {}, + "outputs": [], + "source": [ + "from dataclasses import dataclass\n", + "\n", + "@dataclass\n", + "class DataclassState:\n", + " name: str\n", + " mood: Literal[\"happy\",\"sad\"]" + ] + }, + { + "cell_type": "markdown", + "id": "64482b93-3c8f-4a30-925f-9be64e4b8b6f", + "metadata": {}, + "source": [ + "To access the keys of a `dataclass`, we just need to modify the subscripting used in `node_1`: \n", + "\n", + "* We use `state.name` for the `dataclass` state rather than `state[\"name\"]` for the `TypedDict` above\n", + "\n", + "You'll notice something a bit odd: in each node, we still return a dictionary to perform the state updates.\n", + " \n", + "This is possible because LangGraph stores each key of your state object separately.\n", + "\n", + "The object returned by the node only needs to have keys (attributes) that match those in the state!\n", + "\n", + "In this case, the `dataclass` has key `name` so we can update it by passing a dict from our node, just as we did when state was a `TypedDict`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "1e1eda69-916f-4f6e-b400-6e65f73d8716", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def node_1(state):\n", + " print(\"---Node 1---\")\n", + " return {\"name\": state.name + \" is ... \"}\n", + "\n", + "# Build graph\n", + "builder = StateGraph(DataclassState)\n", + "builder.add_node(\"node_1\", node_1)\n", + "builder.add_node(\"node_2\", node_2)\n", + "builder.add_node(\"node_3\", node_3)\n", + "\n", + "# Logic\n", + "builder.add_edge(START, \"node_1\")\n", + "builder.add_conditional_edges(\"node_1\", decide_mood)\n", + "builder.add_edge(\"node_2\", END)\n", + "builder.add_edge(\"node_3\", END)\n", + "\n", + "# Add\n", + "graph = builder.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "markdown", + "id": "06beb50a-4878-4d7e-ac6c-d60a0f417eb3", + "metadata": {}, + "source": [ + "We invoke with a `dataclass` to set the initial values of each key / channel in our state!" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "8c042325-e93d-43e1-9ac7-a0e20c2fb08d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---Node 1---\n", + "---Node 3---\n" + ] + }, + { + "data": { + "text/plain": [ + "{'name': 'Lance is ... ', 'mood': 'sad'}" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.invoke(DataclassState(name=\"Lance\",mood=\"sad\"))" + ] + }, + { + "cell_type": "markdown", + "id": "2405e49b-e786-4bf9-ac85-1fb941d01bcd", + "metadata": {}, + "source": [ + "## Pydantic\n", + "\n", + "As mentioned, `TypedDict` and `dataclasses` provide type hints but they don't enforce types at runtime. \n", + " \n", + "This means you could potentially assign invalid values without raising an error!\n", + "\n", + "For example, we can set `mood` to `mad` even though our type hint specifies `mood: list[Literal[\"happy\",\"sad\"]]`." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "522fcc76-abf7-452a-9d7b-000e06942d94", + "metadata": {}, + "outputs": [], + "source": [ + "dataclass_instance = DataclassState(name=\"Lance\", mood=\"mad\")" + ] + }, + { + "cell_type": "markdown", + "id": "4f095c3a-96b5-4318-9303-20424b4455e9", + "metadata": {}, + "source": [ + "[Pydantic](https://docs.pydantic.dev/latest/api/base_model/) is a data validation and settings management library using Python type annotations. \n", + "\n", + "It's particularly well-suited [for defining state schemas in LangGraph](https://langchain-ai.github.io/langgraph/how-tos/state-model/) due to its validation capabilities.\n", + "\n", + "Pydantic can perform validation to check whether data conforms to the specified types and constraints at runtime." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "62e8720e-217f-4b98-837a-af45c3fa577f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Validation Error: 1 validation error for PydanticState\n", + "mood\n", + " Value error, Each mood must be either 'happy' or 'sad' [type=value_error, input_value='mad', input_type=str]\n", + " For further information visit https://errors.pydantic.dev/2.9/v/value_error\n" + ] + } + ], + "source": [ + "from pydantic import BaseModel, field_validator, ValidationError\n", + "\n", + "class PydanticState(BaseModel):\n", + " name: str\n", + " mood: str # \"happy\" or \"sad\" \n", + "\n", + " @field_validator('mood')\n", + " @classmethod\n", + " def validate_mood(cls, value):\n", + " # Ensure the mood is either \"happy\" or \"sad\"\n", + " if value not in [\"happy\", \"sad\"]:\n", + " raise ValueError(\"Each mood must be either 'happy' or 'sad'\")\n", + " return value\n", + "\n", + "try:\n", + " state = PydanticState(name=\"John Doe\", mood=\"mad\")\n", + "except ValidationError as e:\n", + " print(\"Validation Error:\", e)" + ] + }, + { + "cell_type": "markdown", + "id": "f29913ca-0295-48eb-af4e-cae515dd9a9c", + "metadata": {}, + "source": [ + "We can use `PydanticState` in our graph seamlessly. " + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "91db3393-b7f8-46e5-8129-0e7539b2804c", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Build graph\n", + "builder = StateGraph(PydanticState)\n", + "builder.add_node(\"node_1\", node_1)\n", + "builder.add_node(\"node_2\", node_2)\n", + "builder.add_node(\"node_3\", node_3)\n", + "\n", + "# Logic\n", + "builder.add_edge(START, \"node_1\")\n", + "builder.add_conditional_edges(\"node_1\", decide_mood)\n", + "builder.add_edge(\"node_2\", END)\n", + "builder.add_edge(\"node_3\", END)\n", + "\n", + "# Add\n", + "graph = builder.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "e96c78be-b483-4fa4-949b-62d4274e97ac", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---Node 1---\n", + "---Node 3---\n" + ] + }, + { + "data": { + "text/plain": [ + "{'name': 'Lance is ... ', 'mood': 'sad'}" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.invoke(PydanticState(name=\"Lance\",mood=\"sad\"))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "lc-academy", + "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.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/module-2/state_db/example.db b/langchain-academy/module-2/state_db/example.db similarity index 52% rename from module-2/state_db/example.db rename to langchain-academy/module-2/state_db/example.db index 6a87f2d5b..461e545db 100644 Binary files a/module-2/state_db/example.db and b/langchain-academy/module-2/state_db/example.db differ diff --git a/module-2/studio/.env.example b/langchain-academy/module-2/studio/.env.example similarity index 100% rename from module-2/studio/.env.example rename to langchain-academy/module-2/studio/.env.example diff --git a/module-2/studio/chatbot.py b/langchain-academy/module-2/studio/chatbot.py similarity index 100% rename from module-2/studio/chatbot.py rename to langchain-academy/module-2/studio/chatbot.py diff --git a/module-2/studio/langgraph.json b/langchain-academy/module-2/studio/langgraph.json similarity index 100% rename from module-2/studio/langgraph.json rename to langchain-academy/module-2/studio/langgraph.json diff --git a/module-2/studio/requirements.txt b/langchain-academy/module-2/studio/requirements.txt similarity index 100% rename from module-2/studio/requirements.txt rename to langchain-academy/module-2/studio/requirements.txt diff --git a/langchain-academy/module-2/trim-filter-messages.ipynb b/langchain-academy/module-2/trim-filter-messages.ipynb new file mode 100644 index 000000000..5189c0f5a --- /dev/null +++ b/langchain-academy/module-2/trim-filter-messages.ipynb @@ -0,0 +1,933 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "fb0ebaf1", + "metadata": {}, + "source": [ + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-2/trim-filter-messages.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239435-lesson-4-trim-and-filter-messages)" + ] + }, + { + "cell_type": "markdown", + "id": "c52ea2f9-03ff-4647-b782-46867ebed04e", + "metadata": {}, + "source": [ + "# Filtering and trimming messages\n", + "\n", + "## Review\n", + "\n", + "Now, we have a deeper understanding of a few things: \n", + "\n", + "* How to customize the graph state schema\n", + "* How to define custom state reducers\n", + "* How to use multiple graph state schemas\n", + "\n", + "## Goals\n", + "\n", + "Now, we can start using these concepts with models in LangGraph!\n", + " \n", + "In the next few sessions, we'll build towards a chatbot that has long-term memory.\n", + "\n", + "Because our chatbot will use messages, let's first talk a bit more about advanced ways to work with messages in graph state." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d5197aba-5d46-421b-ae3b-4e3034edcfda", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install --quiet -U langchain_core langgraph langchain_openai" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "768dc606-d5f2-468d-96ea-910b264e0f8a", + "metadata": {}, + "outputs": [], + "source": [ + "# import os, getpass\n", + "\n", + "# def _set_env(var: str):\n", + "# if not os.environ.get(var):\n", + "# os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "\n", + "# _set_env(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "markdown", + "id": "8b64d8d3-e4ac-4961-bdc0-688825eb5864", + "metadata": {}, + "source": [ + "We'll use [LangSmith](https://docs.smith.langchain.com/) for [tracing](https://docs.smith.langchain.com/concepts/tracing).\n", + "\n", + "We'll log to a project, `langchain-academy`. " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "dd020c79", + "metadata": {}, + "outputs": [], + "source": [ + "# _set_env(\"LANGCHAIN_API_KEY\")\n", + "# os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "# os.environ[\"LANGCHAIN_PROJECT\"] = \"langchain-academy\"" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "1205be63", + "metadata": {}, + "outputs": [], + "source": [ + "# load environment variables\n", + "from dotenv import load_dotenv\n", + "import os\n", + "\n", + "load_dotenv(dotenv_path='C:\\MIConsulting\\LangChain\\langchain-academy\\.env')\n", + "\n", + "# OPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\")\n", + "GOOGLEAI_API_KEY = os.getenv(\"GOOGLEAI_API_KEY\")\n", + "LANGCHAIN_API_KEY = os.getenv('LANGCHAIN_API_KEY')\n", + "LANGCHAIN_ENDPOINT = os.getenv('LANGCHAIN_ENDPOINT')\n", + "LANGCHAIN_TRACING_V2 = os.getenv('LANGCHAIN_TRACING_V2')\n", + "LANGCHAIN_PROJECT = os.getenv('LANGCHAIN_PROJECT')" + ] + }, + { + "cell_type": "markdown", + "id": "72f3fc90-58b6-4f7f-897e-dddf6ae532c7", + "metadata": {}, + "source": [ + "## Messages as state\n", + "\n", + "First, let's define some messages." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cf11a463-e27a-4a05-b41d-64882e38edca", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Name: Bot\n", + "\n", + "So you said you were researching ocean mammals?\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Yes, I know about whales. But what others should I learn about?\n" + ] + } + ], + "source": [ + "from pprint import pprint\n", + "from langchain_core.messages import AIMessage, HumanMessage\n", + "messages = [AIMessage(f\"So you said you were researching ocean mammals?\", name=\"Bot\")]\n", + "messages.append(HumanMessage(f\"Yes, I know about whales. But what others should I learn about?\", name=\"Lance\"))\n", + "\n", + "for m in messages:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "b814adcb-6bf9-4b75-be11-e59f933fbd0c", + "metadata": {}, + "source": [ + "Recall we can pass them to a chat model." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "4712e288-e622-48a2-ad3f-a52f65f3ab08", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "AIMessage(content='Beyond whales (which are a huge and diverse group themselves!), there are several other fascinating groups of marine mammals to explore:\\n\\n* **Pinnipeds:** This group includes seals, sea lions, and walruses. They are characterized by their flippers and spend time both on land and in water. There are differences between seals and sea lions – seals lack external ear flaps and have smaller front flippers, while sea lions have visible ears and larger front flippers that they use for walking on land. Walruses are easily identified by their tusks.\\n\\n* **Sirenians:** This group includes manatees and dugongs. They are herbivorous marine mammals often referred to as \"sea cows\" due to their slow-moving nature and grazing habits in shallow coastal waters.\\n\\n* **Sea Otters:** These adorable creatures are the smallest marine mammals in North America. They are known for their thick fur, playful behavior, and use of tools (like rocks) to crack open shellfish.\\n\\n* **Polar Bears:** While they spend a significant amount of time on land and ice, polar bears are classified as marine mammals due to their dependence on the ocean for food (primarily seals) and their ability to swim long distances.\\n\\nWithin each of these groups, there are numerous species with unique adaptations and characteristics. For example, there are several different species of seals, including harbor seals, harp seals, and elephant seals, each adapted to specific environments.\\n\\nTo help you focus your research, what specifically about marine mammals are you interested in? Their evolution? Conservation status? Specific adaptations? Knowing this will help me recommend further resources.\\n', additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-1a247e2c-0be5-4f35-bfa6-3c066ace15e5-0', usage_metadata={'input_tokens': 26, 'output_tokens': 335, 'total_tokens': 361, 'input_token_details': {'cache_read': 0}})" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# from langchain_openai import ChatOpenAI\n", + "# llm = ChatOpenAI(model=\"gpt-4o\")\n", + "\n", + "from langchain_google_genai import ChatGoogleGenerativeAI\n", + "llm = ChatGoogleGenerativeAI(\n", + " model=\"gemini-1.5-pro\",\n", + " api_key=GOOGLEAI_API_KEY,\n", + ")\n", + "\n", + "llm.invoke(messages)" + ] + }, + { + "cell_type": "markdown", + "id": "fbd1dab8-0af8-4621-8264-ce65065f76ec", + "metadata": {}, + "source": [ + "We can run our chat model in a simple graph with `MessagesState`." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "bbd8c39c-633b-4176-9cc6-8318e42bb5dd", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "from langgraph.graph import MessagesState\n", + "from langgraph.graph import StateGraph, START, END\n", + "\n", + "# Node\n", + "def chat_model_node(state: MessagesState):\n", + " return {\"messages\": llm.invoke(state[\"messages\"])}\n", + "\n", + "# Build graph\n", + "builder = StateGraph(MessagesState)\n", + "builder.add_node(\"chat_model\", chat_model_node)\n", + "builder.add_edge(START, \"chat_model\")\n", + "builder.add_edge(\"chat_model\", END)\n", + "graph = builder.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "3a5a3e4a-ccfd-4d14-81f1-f0de6e11a1e4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Name: Bot\n", + "\n", + "So you said you were researching ocean mammals?\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Yes, I know about whales. But what others should I learn about?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Beyond whales (which are a huge and diverse group themselves!), there are several other fascinating groups of marine mammals:\n", + "\n", + "* **Pinnipeds:** This group includes seals, sea lions, and walruses. They are characterized by their flippers and spend time both on land and in the water. There are differences between seals and sea lions, primarily in their ear structure (sea lions have external ear flaps) and how they move on land (sea lions can rotate their hind flippers forward). Walruses are easily identifiable by their tusks.\n", + "\n", + "* **Sirenians:** This group includes manatees and dugongs. They are herbivorous marine mammals, often called \"sea cows,\" and inhabit warm, shallow waters. They are gentle giants and unfortunately face threats from boat collisions and habitat loss.\n", + "\n", + "* **Sea Otters:** These playful creatures are the smallest marine mammals in North America. They are known for their thick fur, tool use (using rocks to crack open shellfish), and floating on their backs while eating. They play a vital role in kelp forest ecosystems.\n", + "\n", + "* **Polar Bears:** These apex predators are found in the Arctic regions and rely heavily on sea ice for hunting seals. They are powerful swimmers and well-adapted to the cold environment. They are currently listed as vulnerable due to the effects of climate change on their habitat.\n", + "\n", + "Within each of these groups, there are many different species, each with its own unique characteristics and adaptations. For example, within the whales, you have baleen whales (like humpbacks and blue whales) and toothed whales (like dolphins and orcas).\n", + "\n", + "Where should I focus next to help you with your research? Are you interested in a specific region, conservation efforts, or perhaps the adaptations that allow these animals to thrive in the ocean?\n" + ] + } + ], + "source": [ + "output = graph.invoke({'messages': messages})\n", + "for m in output['messages']:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "34c33e63-1ef4-412d-bb10-6a1b9e5b35a7", + "metadata": {}, + "source": [ + "## Reducer\n", + "\n", + "A practical challenge when working with messages is managing long-running conversations. \n", + "\n", + "Long-running conversations result in high token usage and latency if we are not careful, because we pass a growing list of messages to the model.\n", + "\n", + "We have a few ways to address this.\n", + "\n", + "First, recall the trick we saw using `RemoveMessage` and the `add_messages` reducer." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "222c6bc5-bb0e-4a43-80f5-c8ec38d99f3a", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from langchain_core.messages import RemoveMessage\n", + "\n", + "# Nodes\n", + "def filter_messages(state: MessagesState):\n", + " # Delete all but the 2 most recent messages\n", + " delete_messages = [RemoveMessage(id=m.id) for m in state[\"messages\"][:-2]]\n", + " return {\"messages\": delete_messages}\n", + "\n", + "def chat_model_node(state: MessagesState): \n", + " return {\"messages\": [llm.invoke(state[\"messages\"])]}\n", + "\n", + "# Build graph\n", + "builder = StateGraph(MessagesState)\n", + "builder.add_node(\"filter\", filter_messages)\n", + "builder.add_node(\"chat_model\", chat_model_node)\n", + "builder.add_edge(START, \"filter\")\n", + "builder.add_edge(\"filter\", \"chat_model\")\n", + "builder.add_edge(\"chat_model\", END)\n", + "graph = builder.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "95a7c2cc-54ce-43e7-9a90-abf37827d709", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Name: Bot\n", + "\n", + "So you said you were researching ocean mammals?\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Yes, I know about whales. But what others should I learn about?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Beyond whales (which are a huge group already!), there are several other fascinating groups of ocean mammals:\n", + "\n", + "* **Pinnipeds:** This group includes seals, sea lions, and walruses. There are many different species within each of these categories, adapted to various environments from the Arctic to the tropics. Learning about their different adaptations, social structures, and feeding habits can be quite interesting. For example, compare the crabeater seal (which, ironically, eats krill) to a leopard seal (a fierce predator).\n", + "\n", + "* **Sirenians:** This group includes manatees and dugongs. These gentle giants are herbivores, grazing on seagrass and other aquatic vegetation. They are often found in warmer, coastal waters. Researching their conservation status is important as many populations are threatened.\n", + "\n", + "* **Sea Otters:** These charismatic creatures are the smallest marine mammals in North America. They are known for their tool use (using rocks to crack open shellfish) and their thick fur, the densest of any mammal. They play a vital role in kelp forest ecosystems.\n", + "\n", + "* **Polar Bears:** While they spend a significant amount of time on land and ice, polar bears are classified as marine mammals due to their dependence on the ocean for food (primarily seals). Their adaptations to the Arctic environment and the threats they face from climate change are important research topics.\n", + "\n", + "Within each of these groups, there are many individual species with unique characteristics. You could focus your research on a particular species, a specific adaptation (like diving behavior or communication), or a conservation issue related to ocean mammals. Happy researching!\n" + ] + } + ], + "source": [ + "# Message list with a preamble\n", + "messages = [AIMessage(\"Hi.\", name=\"Bot\", id=\"1\")]\n", + "messages.append(HumanMessage(\"Hi.\", name=\"Lance\", id=\"2\"))\n", + "messages.append(AIMessage(\"So you said you were researching ocean mammals?\", name=\"Bot\", id=\"3\"))\n", + "messages.append(HumanMessage(\"Yes, I know about whales. But what others should I learn about?\", name=\"Lance\", id=\"4\"))\n", + "\n", + "# Invoke\n", + "output = graph.invoke({'messages': messages})\n", + "for m in output['messages']:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "f506457d-014b-4fee-a684-e5edfb4b8f0d", + "metadata": {}, + "source": [ + "## Filtering messages\n", + "\n", + "If you don't need or want to modify the graph state, you can just filter the messages you pass to the chat model.\n", + "\n", + "For example, just pass in a filtered list: `llm.invoke(messages[-1:])` to the model." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "22d0b904-7cd6-486b-8948-105bee3d4683", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Node\n", + "def chat_model_node(state: MessagesState):\n", + " return {\"messages\": [llm.invoke(state[\"messages\"][-1:])]}\n", + "\n", + "# Build graph\n", + "builder = StateGraph(MessagesState)\n", + "builder.add_node(\"chat_model\", chat_model_node)\n", + "builder.add_edge(START, \"chat_model\")\n", + "builder.add_edge(\"chat_model\", END)\n", + "graph = builder.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "markdown", + "id": "6f58c6fc-532f-418d-b70a-cfcb3307daf5", + "metadata": {}, + "source": [ + "Let's take our existing list of messages, append the above LLM response, and append a follow-up question." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "16956015-1dbe-4108-89b5-4209b68b51ca", + "metadata": {}, + "outputs": [], + "source": [ + "messages.append(output['messages'][-1])\n", + "messages.append(HumanMessage(f\"Tell me more about Narwhals!\", name=\"Lance\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "85563415-c085-46a8-a4ac-155df798c54e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Name: Bot\n", + "\n", + "Hi.\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Hi.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Name: Bot\n", + "\n", + "So you said you were researching ocean mammals?\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Yes, I know about whales. But what others should I learn about?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Beyond whales (which are a huge group already!), there are several other fascinating groups of ocean mammals:\n", + "\n", + "* **Pinnipeds:** This group includes seals, sea lions, and walruses. There are many different species within each of these categories, adapted to various environments from the Arctic to the tropics. Learning about their different adaptations, social structures, and feeding habits can be quite interesting. For example, compare the crabeater seal (which, ironically, eats krill) to a leopard seal (a fierce predator).\n", + "\n", + "* **Sirenians:** This group includes manatees and dugongs. These gentle giants are herbivores, grazing on seagrass and other aquatic vegetation. They are often found in warmer, coastal waters. Researching their conservation status is important as many populations are threatened.\n", + "\n", + "* **Sea Otters:** These charismatic creatures are the smallest marine mammals in North America. They are known for their tool use (using rocks to crack open shellfish) and their thick fur, the densest of any mammal. They play a vital role in kelp forest ecosystems.\n", + "\n", + "* **Polar Bears:** While they spend a significant amount of time on land and ice, polar bears are classified as marine mammals due to their dependence on the ocean for food (primarily seals). Their adaptations to the Arctic environment and the threats they face from climate change are important research topics.\n", + "\n", + "Within each of these groups, there are many individual species with unique characteristics. You could focus your research on a particular species, a specific adaptation (like diving behavior or communication), or a conservation issue related to ocean mammals. Happy researching!\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Tell me more about Narwhals!\n" + ] + } + ], + "source": [ + "for m in messages:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "23349705-a059-47b5-9760-d8f64e687393", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Name: Bot\n", + "\n", + "Hi.\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Hi.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Name: Bot\n", + "\n", + "So you said you were researching ocean mammals?\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Yes, I know about whales. But what others should I learn about?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Beyond whales (which are a huge group already!), there are several other fascinating groups of ocean mammals:\n", + "\n", + "* **Pinnipeds:** This group includes seals, sea lions, and walruses. There are many different species within each of these categories, adapted to various environments from the Arctic to the tropics. Learning about their different adaptations, social structures, and feeding habits can be quite interesting. For example, compare the crabeater seal (which, ironically, eats krill) to a leopard seal (a fierce predator).\n", + "\n", + "* **Sirenians:** This group includes manatees and dugongs. These gentle giants are herbivores, grazing on seagrass and other aquatic vegetation. They are often found in warmer, coastal waters. Researching their conservation status is important as many populations are threatened.\n", + "\n", + "* **Sea Otters:** These charismatic creatures are the smallest marine mammals in North America. They are known for their tool use (using rocks to crack open shellfish) and their thick fur, the densest of any mammal. They play a vital role in kelp forest ecosystems.\n", + "\n", + "* **Polar Bears:** While they spend a significant amount of time on land and ice, polar bears are classified as marine mammals due to their dependence on the ocean for food (primarily seals). Their adaptations to the Arctic environment and the threats they face from climate change are important research topics.\n", + "\n", + "Within each of these groups, there are many individual species with unique characteristics. You could focus your research on a particular species, a specific adaptation (like diving behavior or communication), or a conservation issue related to ocean mammals. Happy researching!\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Tell me more about Narwhals!\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Narwhals are medium-sized toothed whales found in the Arctic waters of Canada, Greenland, Norway, and Russia. They're often called the \"unicorns of the sea\" because of their most distinctive feature: a long, straight tusk that projects from their upper left jaw. Here's a breakdown of fascinating narwhal facts:\n", + "\n", + "**The Tusk:**\n", + "\n", + "* **What it is:** The tusk is actually a spiraled canine tooth. It can grow up to 10 feet long in males and occasionally in females, though much shorter.\n", + "* **What it's for:** The purpose of the tusk is still debated by scientists. Leading theories suggest it's used for:\n", + " * **Sensing:** It's richly innervated (has many nerves), allowing the narwhal to detect changes in water temperature, salinity, and pressure, and possibly even the presence of prey.\n", + " * **Male competition:** Tusks may be used in displays of dominance or to establish social hierarchy, though rarely for actual fighting.\n", + " * **Attracting mates:** Larger tusks could signal fitness and attract females.\n", + " * **Breaking through ice:** While possible, this is considered less likely as a primary function.\n", + "* **Unique properties:** The tusk is flexible and can bend up to a foot without breaking. It also has a porous surface that allows for water flow and environmental sensing.\n", + "\n", + "**Physical Characteristics:**\n", + "\n", + "* **Size:** Adults can reach lengths of 13-20 feet (excluding the tusk) and weigh between 1,760 and 3,530 pounds.\n", + "* **Appearance:** They have mottled grey and white skin, with darker spots becoming more prominent with age. They lack a dorsal fin, likely an adaptation for swimming under ice.\n", + "* **Swimming:** They are powerful swimmers and can dive to impressive depths, sometimes exceeding 5,000 feet, to hunt for prey.\n", + "\n", + "**Diet and Predators:**\n", + "\n", + "* **Food:** Their diet consists primarily of Arctic cod, Greenland halibut, shrimp, and squid. They use echolocation to find prey in the dark depths.\n", + "* **Predators:** Narwhals are preyed upon by polar bears, orcas, and occasionally Greenland sharks. Younger narwhals are also vulnerable to walruses.\n", + "\n", + "**Social Behavior and Reproduction:**\n", + "\n", + "* **Pods:** They are social animals and often travel in pods of up to 20 individuals, sometimes aggregating into larger groups during migrations.\n", + "* **Breeding:** Mating occurs in the spring, and after a gestation period of around 14 months, a single calf is born.\n", + "\n", + "**Conservation Status:**\n", + "\n", + "* **Near Threatened:** While not currently endangered, narwhals are considered near threatened due to climate change, which affects their sea ice habitat, and hunting pressure in some areas. Pollution and noise from increased shipping traffic in the Arctic also pose threats.\n", + "\n", + "\n", + "Narwhals are truly fascinating creatures, and ongoing research continues to unveil more about their unique adaptations and behaviors.\n" + ] + } + ], + "source": [ + "# Invoke, using message filtering\n", + "output = graph.invoke({'messages': messages})\n", + "for m in output['messages']:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "42e1d8d2-e297-4d78-b54c-d12b3c866745", + "metadata": {}, + "source": [ + "The state has all of the mesages.\n", + "\n", + "But, let's look at the LangSmith trace to see that the model invocation only uses the last message:\n", + "\n", + "https://smith.langchain.com/public/75aca3ce-ef19-4b92-94be-0178c7a660d9/r" + ] + }, + { + "cell_type": "markdown", + "id": "fc40d930-3c1f-47fe-8d2a-ce174873353c", + "metadata": {}, + "source": [ + "## Trim messages\n", + "\n", + "Another approach is to [trim messages](https://python.langchain.com/v0.2/docs/how_to/trim_messages/#getting-the-last-max_tokens-tokens), based upon a set number of tokens. \n", + "\n", + "This restricts the message history to a specified number of tokens.\n", + "\n", + "While filtering only returns a post-hoc subset of the messages between agents, trimming restricts the number of tokens that a chat model can use to respond.\n", + "\n", + "See the `trim_messages` below." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "2ff99b81-cf03-4cc2-b44f-44829a73e1fd", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from langchain_core.messages import trim_messages\n", + "\n", + "# Node\n", + "def chat_model_node(state: MessagesState):\n", + " messages = trim_messages(\n", + " state[\"messages\"],\n", + " max_tokens=100,\n", + " strategy=\"last\",\n", + " # token_counter=ChatOpenAI(model=\"gpt-4o\"),\n", + " token_counter=ChatGoogleGenerativeAI(\n", + " model=\"gemini-1.5-pro\",\n", + " api_key=GOOGLEAI_API_KEY,\n", + " ),\n", + " allow_partial=False,\n", + " )\n", + " return {\"messages\": [llm.invoke(messages)]}\n", + "\n", + "# Build graph\n", + "builder = StateGraph(MessagesState)\n", + "builder.add_node(\"chat_model\", chat_model_node)\n", + "builder.add_edge(START, \"chat_model\")\n", + "builder.add_edge(\"chat_model\", END)\n", + "graph = builder.compile()\n", + "\n", + "# View\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "24df63ac-da29-4874-b3df-7e390e97cc8a", + "metadata": {}, + "outputs": [], + "source": [ + "messages.append(output['messages'][-1])\n", + "messages.append(HumanMessage(f\"Tell me where Orcas live!\", name=\"Lance\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "edd2aa3e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Name: Bot\n", + "\n", + "Hi.\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Hi.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Name: Bot\n", + "\n", + "So you said you were researching ocean mammals?\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Yes, I know about whales. But what others should I learn about?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Beyond whales (which are a huge group already!), there are several other fascinating groups of ocean mammals:\n", + "\n", + "* **Pinnipeds:** This group includes seals, sea lions, and walruses. There are many different species within each of these categories, adapted to various environments from the Arctic to the tropics. Learning about their different adaptations, social structures, and feeding habits can be quite interesting. For example, compare the crabeater seal (which, ironically, eats krill) to a leopard seal (a fierce predator).\n", + "\n", + "* **Sirenians:** This group includes manatees and dugongs. These gentle giants are herbivores, grazing on seagrass and other aquatic vegetation. They are often found in warmer, coastal waters. Researching their conservation status is important as many populations are threatened.\n", + "\n", + "* **Sea Otters:** These charismatic creatures are the smallest marine mammals in North America. They are known for their tool use (using rocks to crack open shellfish) and their thick fur, the densest of any mammal. They play a vital role in kelp forest ecosystems.\n", + "\n", + "* **Polar Bears:** While they spend a significant amount of time on land and ice, polar bears are classified as marine mammals due to their dependence on the ocean for food (primarily seals). Their adaptations to the Arctic environment and the threats they face from climate change are important research topics.\n", + "\n", + "Within each of these groups, there are many individual species with unique characteristics. You could focus your research on a particular species, a specific adaptation (like diving behavior or communication), or a conservation issue related to ocean mammals. Happy researching!\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Tell me more about Narwhals!\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Narwhals are medium-sized toothed whales found in the Arctic waters of Canada, Greenland, Norway, and Russia. They're often called the \"unicorns of the sea\" because of their most distinctive feature: a long, straight tusk that projects from their upper left jaw. Here's a breakdown of fascinating narwhal facts:\n", + "\n", + "**The Tusk:**\n", + "\n", + "* **What it is:** The tusk is actually a spiraled canine tooth. It can grow up to 10 feet long in males and occasionally in females, though much shorter.\n", + "* **What it's for:** The purpose of the tusk is still debated by scientists. Leading theories suggest it's used for:\n", + " * **Sensing:** It's richly innervated (has many nerves), allowing the narwhal to detect changes in water temperature, salinity, and pressure, and possibly even the presence of prey.\n", + " * **Male competition:** Tusks may be used in displays of dominance or to establish social hierarchy, though rarely for actual fighting.\n", + " * **Attracting mates:** Larger tusks could signal fitness and attract females.\n", + " * **Breaking through ice:** While possible, this is considered less likely as a primary function.\n", + "* **Unique properties:** The tusk is flexible and can bend up to a foot without breaking. It also has a porous surface that allows for water flow and environmental sensing.\n", + "\n", + "**Physical Characteristics:**\n", + "\n", + "* **Size:** Adults can reach lengths of 13-20 feet (excluding the tusk) and weigh between 1,760 and 3,530 pounds.\n", + "* **Appearance:** They have mottled grey and white skin, with darker spots becoming more prominent with age. They lack a dorsal fin, likely an adaptation for swimming under ice.\n", + "* **Swimming:** They are powerful swimmers and can dive to impressive depths, sometimes exceeding 5,000 feet, to hunt for prey.\n", + "\n", + "**Diet and Predators:**\n", + "\n", + "* **Food:** Their diet consists primarily of Arctic cod, Greenland halibut, shrimp, and squid. They use echolocation to find prey in the dark depths.\n", + "* **Predators:** Narwhals are preyed upon by polar bears, orcas, and occasionally Greenland sharks. Younger narwhals are also vulnerable to walruses.\n", + "\n", + "**Social Behavior and Reproduction:**\n", + "\n", + "* **Pods:** They are social animals and often travel in pods of up to 20 individuals, sometimes aggregating into larger groups during migrations.\n", + "* **Breeding:** Mating occurs in the spring, and after a gestation period of around 14 months, a single calf is born.\n", + "\n", + "**Conservation Status:**\n", + "\n", + "* **Near Threatened:** While not currently endangered, narwhals are considered near threatened due to climate change, which affects their sea ice habitat, and hunting pressure in some areas. Pollution and noise from increased shipping traffic in the Arctic also pose threats.\n", + "\n", + "\n", + "Narwhals are truly fascinating creatures, and ongoing research continues to unveil more about their unique adaptations and behaviors.\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Tell me where Orcas live!\n" + ] + } + ], + "source": [ + "for m in messages:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "6d9d8971-c75c-43ca-a209-eb1d07b2ead0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[HumanMessage(content='Tell me where Orcas live!', additional_kwargs={}, response_metadata={}, name='Lance')]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Example of trimming messages\n", + "trim_messages(\n", + " messages,\n", + " max_tokens=100,\n", + " strategy=\"last\",\n", + " # token_counter=ChatOpenAI(model=\"gpt-4o\"),\n", + " token_counter=ChatGoogleGenerativeAI(\n", + " model=\"gemini-1.5-pro\",\n", + " api_key=GOOGLEAI_API_KEY,\n", + " ),\n", + " allow_partial=False\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "ed70a269-a869-4fa0-a1df-29736a432c51", + "metadata": {}, + "outputs": [], + "source": [ + "# Invoke, using message trimming in the chat_model_node \n", + "messages_out_trim = graph.invoke({'messages': messages})" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "552d2f4f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Name: Bot\n", + "\n", + "Hi.\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Hi.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Name: Bot\n", + "\n", + "So you said you were researching ocean mammals?\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Yes, I know about whales. But what others should I learn about?\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Beyond whales (which are a huge group already!), there are several other fascinating groups of ocean mammals:\n", + "\n", + "* **Pinnipeds:** This group includes seals, sea lions, and walruses. There are many different species within each of these categories, adapted to various environments from the Arctic to the tropics. Learning about their different adaptations, social structures, and feeding habits can be quite interesting. For example, compare the crabeater seal (which, ironically, eats krill) to a leopard seal (a fierce predator).\n", + "\n", + "* **Sirenians:** This group includes manatees and dugongs. These gentle giants are herbivores, grazing on seagrass and other aquatic vegetation. They are often found in warmer, coastal waters. Researching their conservation status is important as many populations are threatened.\n", + "\n", + "* **Sea Otters:** These charismatic creatures are the smallest marine mammals in North America. They are known for their tool use (using rocks to crack open shellfish) and their thick fur, the densest of any mammal. They play a vital role in kelp forest ecosystems.\n", + "\n", + "* **Polar Bears:** While they spend a significant amount of time on land and ice, polar bears are classified as marine mammals due to their dependence on the ocean for food (primarily seals). Their adaptations to the Arctic environment and the threats they face from climate change are important research topics.\n", + "\n", + "Within each of these groups, there are many individual species with unique characteristics. You could focus your research on a particular species, a specific adaptation (like diving behavior or communication), or a conservation issue related to ocean mammals. Happy researching!\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Tell me more about Narwhals!\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Narwhals are medium-sized toothed whales found in the Arctic waters of Canada, Greenland, Norway, and Russia. They're often called the \"unicorns of the sea\" because of their most distinctive feature: a long, straight tusk that projects from their upper left jaw. Here's a breakdown of fascinating narwhal facts:\n", + "\n", + "**The Tusk:**\n", + "\n", + "* **What it is:** The tusk is actually a spiraled canine tooth. It can grow up to 10 feet long in males and occasionally in females, though much shorter.\n", + "* **What it's for:** The purpose of the tusk is still debated by scientists. Leading theories suggest it's used for:\n", + " * **Sensing:** It's richly innervated (has many nerves), allowing the narwhal to detect changes in water temperature, salinity, and pressure, and possibly even the presence of prey.\n", + " * **Male competition:** Tusks may be used in displays of dominance or to establish social hierarchy, though rarely for actual fighting.\n", + " * **Attracting mates:** Larger tusks could signal fitness and attract females.\n", + " * **Breaking through ice:** While possible, this is considered less likely as a primary function.\n", + "* **Unique properties:** The tusk is flexible and can bend up to a foot without breaking. It also has a porous surface that allows for water flow and environmental sensing.\n", + "\n", + "**Physical Characteristics:**\n", + "\n", + "* **Size:** Adults can reach lengths of 13-20 feet (excluding the tusk) and weigh between 1,760 and 3,530 pounds.\n", + "* **Appearance:** They have mottled grey and white skin, with darker spots becoming more prominent with age. They lack a dorsal fin, likely an adaptation for swimming under ice.\n", + "* **Swimming:** They are powerful swimmers and can dive to impressive depths, sometimes exceeding 5,000 feet, to hunt for prey.\n", + "\n", + "**Diet and Predators:**\n", + "\n", + "* **Food:** Their diet consists primarily of Arctic cod, Greenland halibut, shrimp, and squid. They use echolocation to find prey in the dark depths.\n", + "* **Predators:** Narwhals are preyed upon by polar bears, orcas, and occasionally Greenland sharks. Younger narwhals are also vulnerable to walruses.\n", + "\n", + "**Social Behavior and Reproduction:**\n", + "\n", + "* **Pods:** They are social animals and often travel in pods of up to 20 individuals, sometimes aggregating into larger groups during migrations.\n", + "* **Breeding:** Mating occurs in the spring, and after a gestation period of around 14 months, a single calf is born.\n", + "\n", + "**Conservation Status:**\n", + "\n", + "* **Near Threatened:** While not currently endangered, narwhals are considered near threatened due to climate change, which affects their sea ice habitat, and hunting pressure in some areas. Pollution and noise from increased shipping traffic in the Arctic also pose threats.\n", + "\n", + "\n", + "Narwhals are truly fascinating creatures, and ongoing research continues to unveil more about their unique adaptations and behaviors.\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "Name: Lance\n", + "\n", + "Tell me where Orcas live!\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Orcas are found in all oceans of the world, but they prefer colder waters. They are most abundant in the Arctic and Antarctic regions, as well as in the higher latitudes of the North and South Pacific and Atlantic Oceans. Specific areas known for orca populations include:\n", + "\n", + "* **North Pacific:** Coastal waters of Alaska, British Columbia, Washington state, Oregon, and California.\n", + "* **North Atlantic:** Waters around Iceland, Norway, and the northeastern United States and Canada.\n", + "* **Southern Ocean:** Around Antarctica and subantarctic islands.\n", + "* **Other areas:** They can also be found in lower densities in tropical and subtropical waters, including the Indian Ocean, the Sea of Japan, and even the Mediterranean Sea.\n", + "\n", + "\n", + "It's important to note that orcas are highly mobile and can travel vast distances. While certain areas are known for resident populations that stay in a particular region, other orcas are transient and move around more widely.\n" + ] + } + ], + "source": [ + "for m in messages_out_trim['messages']:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "38b3db67-380e-46b5-9a6a-20100ba52008", + "metadata": {}, + "source": [ + "Let's look at the LangSmith trace to see the model invocation:\n", + "\n", + "https://smith.langchain.com/public/b153f7e9-f1a5-4d60-8074-f0d7ab5b42ef/r" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "lc-academy", + "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.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/module-3/breakpoints.ipynb b/langchain-academy/module-3/breakpoints.ipynb similarity index 51% rename from module-3/breakpoints.ipynb rename to langchain-academy/module-3/breakpoints.ipynb index 93e009fd4..d6956ede4 100644 --- a/module-3/breakpoints.ipynb +++ b/langchain-academy/module-3/breakpoints.ipynb @@ -56,13 +56,34 @@ "metadata": {}, "outputs": [], "source": [ - "import os, getpass\n", + "# import os, getpass\n", "\n", - "def _set_env(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "# def _set_env(var: str):\n", + "# if not os.environ.get(var):\n", + "# os.environ[var] = getpass.getpass(f\"{var}: \")\n", "\n", - "_set_env(\"OPENAI_API_KEY\")" + "# _set_env(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "669f9818", + "metadata": {}, + "outputs": [], + "source": [ + "# load environment variables\n", + "from dotenv import load_dotenv\n", + "import os\n", + "\n", + "load_dotenv(dotenv_path='C:\\MIConsulting\\LangChain\\langchain-academy\\.env')\n", + "\n", + "# OPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\")\n", + "GOOGLEAI_API_KEY = os.getenv(\"GOOGLEAI_API_KEY\")\n", + "LANGCHAIN_API_KEY = os.getenv('LANGCHAIN_API_KEY')\n", + "LANGCHAIN_ENDPOINT = os.getenv('LANGCHAIN_ENDPOINT')\n", + "LANGCHAIN_TRACING_V2 = os.getenv('LANGCHAIN_TRACING_V2')\n", + "LANGCHAIN_PROJECT = os.getenv('LANGCHAIN_PROJECT')" ] }, { @@ -83,12 +104,13 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "b94d1a90-2fe3-4b2a-a901-3bdb89e37edc", "metadata": {}, "outputs": [], "source": [ - "from langchain_openai import ChatOpenAI\n", + "# from langchain_openai import ChatOpenAI\n", + "from langchain_google_genai import ChatGoogleGenerativeAI\n", "\n", "def multiply(a: int, b: int) -> int:\n", " \"\"\"Multiply a and b.\n", @@ -119,19 +141,20 @@ " return a / b\n", "\n", "tools = [add, multiply, divide]\n", - "llm = ChatOpenAI(model=\"gpt-4o\")\n", + "# llm = ChatOpenAI(model=\"gpt-4o\")\n", + "llm = ChatGoogleGenerativeAI(api_key=GOOGLEAI_API_KEY, model=\"gemini-1.5-pro\")\n", "llm_with_tools = llm.bind_tools(tools)" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "ac06feae-d12b-490b-95e7-38cf40b74202", "metadata": {}, "outputs": [ { "data": { - "image/jpeg": "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", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAATAAAAEjCAIAAAA628qRAAAAAXNSR0IArs4c6QAAIABJREFUeJzt3XdcE/f/B/BPJgmBhL1EtiiKCoobVHAvxE1F1Bb3+lq1aqt11T2qVWv7a6mjzjpaqdqKbW2dKK2jgogKBNkYEsiC7Pz+iKUWAyImfC6X9/PRP2y4u7xCeOUuNz5H0ev1CABADFTcAQAA/4JCAkAgUEgACAQKCQCBQCEBIBAoJAAEQscdwKppNfrnBQq5VFst1eg0SKXU4U7UKEwWlcWh2trT7R3oju5M3HFIhQLHIZufSql7/Jc0L0NWnFPj6c9m2VJt7ek8V4aqxjIKqdPppSJNtVRjw6YJipX+oZyA9hyvADbuXGQAhWxut38W5mXIPP3ZAe3tfNrY4o7ztqoEKn6mXFimklVqeo5wdmvJwp3IskEhm0/O39Jfjjzv3N+x6yAn3FlMr/BJ9c1zQs8AVu9RrrizWDAoZDO59ZNQLtH0GeNKZ5B5Rxr/ofzq94J3lvowbcj8Ms0HCtkcbv8spNIoXQaScMX4KolQfXxrwXuf+DOY0Mk3BoU0u0uHy3iujG6DnXEHaVZff5Q36SNfth0NdxALA59h5nX3ciWHR7e2NiKEJi73Ob61AHcKywOFNKOCx3KpSNMr1gV3EAw4XPrARPffT5bjDmJhoJBmdPX7ig5RPNwpsPFuZSsRaQqyq3EHsSRQSHN5lC7x8GVZ+YksvUa43DhXgTuFJYFCmkvOfVmvWKv76liHSwsb39a2uQ9kuINYDCikWZTm1yirdWy7ZjpVuLS0tKSkBNfsDXNtafP0HhSysaCQZsHPlPuHcprnuYqKimJjY7OysrDM/lr+oRx+ptxMCycfKKRZCEtUAR2aqZAajaZpB5MNczV59kaiM6hB4XaFT6CTjQInBpjFvsU5M7cG0mgU0y5WoVBs3rz56tWrCKHw8PAlS5bo9frY2NjaCYYPH75mzRqVSvX111+npqaWl5e7uLgMGzZs5syZNBoNITR+/PjAwMDAwMATJ04oFIoDBw688847dWY3bWaE0B+nnzt72LSPtN4dzo0H10OaXo1cy2RRTd5GhNCBAwfOnz8/a9YsFxeX8+fPs9lsW1vb9evXr1y5ctasWREREU5OTgghGo12+/bt3r17e3t7P378eP/+/Vwud9KkSYaFpKWlKRSKnTt3VldX+/r6vjq7yXG4dLlEY44lkw8U0vSqxRpbnll+sSUlJWw2e+rUqXQ6PS4uzvBgmzZtEEJ+fn5hYWGGR2g02qFDhyiUF58IRUVFly9fri0knU7fuHEjm82ub3aT4/DopXk1Zlo4ycB3SNPT6vRsW7P8YocMGaJQKObPn5+Tk9PwlCKRaPPmzXFxcTExMbm5uUKhsPZHoaGhtW1sHnQ6hWKG7QVSgkKaHodLr3yuNseSe/bs+dlnnwmFwvj4+PXr12s0xrcDhUJhQkJCenr67Nmz9+zZExISotVqa3/azG1ECEmrNCw2/KU1Cmyymh6HS6+WahsxYVP07Nmze/fux48f37lzp6enZ1JS0qvTnDlzRiQSHTx40MPDAyHk4eHx7NkzM+VpDLlEw3NiYAxgQeBzyyz82trKqky/klSpVAghKpWakJDg6uqanZ2NEGKxWAghgUBQO1lVVZWjo6OhjYb/bWBf+quzmxwFIa4LfPQ3CvyazMLekZGXKe8Q6WDaxZ44ceLKlStDhw4VCAQCgaBt27YIIXd39xYtWhw5coTNZovF4vj4+IiIiJMnT37xxRcdO3a8fPnyjRs3dDpdVVWVg4ORPK/ObmNjY9rYD66LrfOSlyaANaRZmOn0FG9vb5VKtXPnzrNnz8bHxycmJiKEKBTKxo0bORzO9u3bz507JxKJYmJipk2bdurUqRUrVqjV6oMHD/r5+X333XdGl/nq7KbN/OyRvGWwLRV26jQOnBhgLt/vKYqb24JKtfY/xNupQnsHettucFZAo8Amq7n4hnBu/STsObzeTbXBgwcrFIpXH+/QocODBw9efZzH46WkpJg6Zl179+49ffr0q4/b29tLpVKjs/z66690uvE/pGqpJvO6JOkTf1PHJC1YQ5rRVx/mTVnla8M2Pq5MWVmZTvcGIyNTqdTa/TTmIxaL5fI329j29PSsPQmhjl+PlbcIYod05ZooHflBIc0o+0+JuELdbYiVXhVZJVClXRAOmeqJO4glgZ06ZtSmC1cu0T5ME+MOgseJbYX9J7rjTmFhoJDmFTPBLeuWJD/L6i4+OrG9IG5OCxia9U3BJmtzOP91SZuu9kEd7XEHaSbf7SgcPNWd52zV4wk1DXyANYfh072e3JHdvVyJO4jZVQlUXy7N7TPGFdrYNLCGbD5//SLKui3pOcIlqKMd7iymVy3V3DwnVKt0/Se6w5Zqk0Ehm5W4Qn3zXIVOh3xa2/qHcuwcyHAcuCC7uiy/JuOGpOcIZzjC8ZagkBiUPVNk/ynhZ8pt7ejufja29nQOl2bnQNea6xIRE9NpdNJKjVys1SN9xnVxiyB2cLh9SDeooglAIXF6XqR4XqCUizVyiZZGp8iqTDzORXZ2dsuWLTkcEw+3xbKl2tjSODwaz5nhG8Kh0a399EATgkKS2dSpUxcvXty+fXvcQUBjwZdvAAgECgkAgUAhyaxly5ZUKrzFlgTeLTIrLCx8owtKAHZQSDKzs7Or78IoQExQSDKTyWSwF92yQCHJzMnJCdaQlgUKSWYikQjWkJYFCklmvr6+sJfVssC7RWbPnj2DvayWBQoJAIFAIcmMy4UrMCwMFJLMJBIJ7gjgzUAhyYzH48FhD8sChSQzsVgMhz0sCxQSAAKBQpKZp6cnHIe0LPBukVlpaSkch7QsUEgACAQKSWY+Pj6wyWpZ4N0is4KCAthktSxQSAAIBApJZn5+frDJalng3SKz/Px82GS1LFBIAAgECklmMAykxYF3i8xgGEiLA4UEgECgkGQG47JaHCgkmcG4rBYHCklmXl5esFPHssC7RWYlJSWwU8eyQCEBIBAoJJk5OjrCTh3LAoUks8rKStipY1mgkGQGtxKwOPBukRncSsDiQCHJDNaQFgfeLTKDNaTFgUKSmaurK+xltSwU2AtHPoMGDWIymRQKRSQScTgcw7+ZTObp06dxRwOvQccdAJgeh8MpKCgw/FuhUBj+MWfOHKyhQKPAJisJDRw4sM4jPj4+77zzDqY44A1AIUlo3LhxPj4+tf9Lo9FGjhzJZrOxhgKNAoUkIWdn5/79+9f+r6+v79ixY7EmAo0FhSSn8ePH+/r6GlaPw4YN43A4uBOBRoFCkpOLi0tMTAyFQvHx8YHVowWBvazmJRdrhKUqjQbDsaVe4aP/vFIQFRVVzkcIyZv52SkI2TnSndyZNDocCH0DcBzSXKoEqmtnKwSFSt+2dnKxBnec5sZkU0WlSgqF0qaLXXi0I+44FgMKaRYSkTrly5KYdzy5TkzcWTBLO/+c50zvNtgJdxDLAN8hTU+n1R/e8Cxuri+0ESHUY7ibRKS5e7kSdxDLAIU0vVs/C3uOdMOdgkC6D3N7ek+mVmpxB7EAUEjTK8lV2DsycKcgFr0eicrVuFNYACik6el1iOsEhfwPFy+WRGR1e7aaAAppejKxRgdbZ/+lVGgRXJjZCFBIAAgECgkAgUAhASAQKCQABAKFBIBAoJAAEAgUEgACgUICQCBQSAAIBAoJAIFAIQEgECgkGeTl5cSOjL5+448Gpsl6lKlUKt/yiWQy2ZOn2W+5ENAAKCQZ0Ol0Ozt7Oq3eEZIupp6bO2+qQlHzlk80bUb8zz+nvOVCQANgkCsy8PHxO3b0xwYmePt1o4FKpTLJckB9YA2J3/Pn5Zu2rI4b3X/AoO7vTZvw628Xa3907PjB8fFDhwyLnP+/pDt30xFChYXPFi2eNWRY5Pj4oZ/u3KjT6S6mnovuFxHdL+KvO7frm2DXZ5sRQnGj+0f3i7iYeg4hlJFxf+myeUOGRQ4ZFvn+opmPnzwyPOPTnMeDh/a6f//OnHlTBw3pOXnqmBs3rhh+FD9xeGWl6GzKqeh+EfETh2P6bZEcFBI/jVaTnf1wZOzY2TMXcrm8DRtXPsp+iBC6czf96+S9HTp0WrTwIw93z5rqaoTQth2f5PFz5s5ZPHbMREHFcyqVGh7WZcb0+bVLe3WCbl17jR83CSG0acOu3buSu3XthRAqKytRqpSJk6ZNmTyjrKxk+YcLam/Lo1Qq136yfOyYibs+/crD3XP9xhVicRVCaM3qrfb23KjI6N27ktes3ort90VqsMmKn5dni4P7Txlu5DhkyMhRY/rfuPFHSJt2ZWUlCKFRI8e3a9dhwIChhonLykqCW7UZPmwUQshQM3d3j44dOtUu7dUJHB2dvLy8EUIhIaE8noNhsv79h9Qus3XrtosWz8rIvN8lorvhkfnzPoiJHogQmjZt3sxZk/5+cLd3VEyb1m3pdLqzs0v79mHN/kuyFrCGJISc3CcrPl40dvzgxCmjtFqtSCRECHXvFmlvz9246eNbt67XTjmg/9A//7q1e8/WykqR0UW9dgIDCoVy7frv8/+XFBsXs2XrGoRQpUhY+1M268WdedzdPRFCFRUC071W0BAoJH537/05Z+4UtUq19IPVa1dv5XJ5Or0OIeTs7LJ3937vlr4frlg4/39JAsFzhNC0pLlz5yy6/PuliZNifzh78tWlvXYCg28PJ69a/UHr4LYbPvl01syFCCHDk9bBoDMQQjoYkqS5QCHxO3w42cvLe+OGXV279GjXrkPt2smw+3TLpt07tn/B5+cY1mMUCmXsmIlHD6f06tln956tGRn36yytgQlqB8VWKpXHjh8YNjRu3tzF7duHtQ1p3/i0MLK2WUEh8RNLqoICg+l0uuG4QnVNtU73YmVlOMzQKbxL9+5RhiPyhgMYHA5n6tRZCKFXD9MbncBQ8totT4WiRqlUBgeH1AZACNU+aQPYLLZQWGHqXwD4F+zUwS8sLCI19dxPP6dw7XmnzhyVSiX5/Fy9Xp/9OGvtumVxI8ez2bbp6TfbtG6LEFqzbpkdxy6ic/dbt68jhFr/U6paRidoF9qRRqPt3bd9yKBYpUoZO2JMQEDQ9z+ccHJylstkh779ikql5uXlvDZq+/bhv12+eOz4QXt7bvduka6uMB60iUEh8Xtv6myRsGLP3m329tzhw0aPHzvp010b793/i8d18PXxP3bsgF6v7xjWecG8pQihkDahqZfOX7122cXFbfGiFaGhHesszegELby8Fy9akfzN53s/396qVZvYEWM+XrFxy9Y16z750NvbZ/bs93Nzn5w5c3zmjAUNR505Y4FIVHH4SLIDz7F167ZQSJODm+2Y3oE1+UPe8+bw4MPuX1fPlAWH2bXqZIc7CNHBd0gACAQKCQCBQCEBIBAoJAAEAoUEgECgkAAQCBQSAAKBQgJAIFBIAAgECgkAgUAhASAQKCQABAKFBIBAoJCm5+LFhEto6mBzaHQmBXcKCwCFND0qjSIsVeBOQSzPsuXOXkzcKSwAFNL0AkI5ohLTjBRODhKhysWTyXVi4A5iAaCQphfSjVstU2dcr8QdhBD0ev3v35VFjXbFHcQywIgB5vLzwTJbLsPRnenagkWhWt3XJwoFiYUqqUiddk4wZZVvjbpSIpEEBQXhzkV0UEgz+v5QGv9hTevgkIpiPFuwCoWCwWDQaLTmf2pbLo3OoHoFsLoPdUYIVVdXv/vuu/Hx8aNGjWr+MJZED8xALBbr9fotW7ZUV1fjyvDnn38OGjRo+fLluAK8KiMjQ6/Xnz9/vqqqCncWgoLvkKa3cePGe/fuIYSWLl3KZrMbMYdZHD58WCAQZGZmZmRk4MpQR2hoKELI3d191KhRIlFDdzqwWlBIE7t48WLr1q379OmDN0Z6enp2djaFQiktLT127BjeMHVERERcvnyZTqdXVVXt378fdxxigUKaRn5+/tKlSxFCgwcPHjNmDO446MiRIxUVL4YYz8jIyMzMxJ2oLi6X6+DgQKFQdu3ahTsLgUAh31ZNTQ1C6Nq1awsWvGaU4WaTnp7++PFjw/3tEEKlpaVHjhzBHcq4d999d/78+Qih6dOnX7x4sRFzkBwU8q0cPHhw5cqVCKHExERvb2/ccV44cOCAUPjvveUoFEpmZuaDBw+whqqXYSfwjh077t+/jxCSy+W4E+EEhWwijUYjkUikUumOHTtwZ6krOzu7zr670tLSgwcP4s7VEC6Xu3z5coRQZWXlvHnzBAJrvSMlpr27Fkyr1X700UfV1dVarRZ3lteYPHmy4UiDZbl58+a2bdtwp8AD1pBv7LPPPouKimKz2VQq0X97Xl5eTKblndLdo0ePJUuWGI4bXbhwAXecZkX0PyniEAgEmzdvRgi9//77gwcPxh2nUfLy8oj/qdGATZs23blzp3bPmTWw4Hermc2dO3fs2LG4U7wZBoPB4XBwp2g6Go22atUqhFBWVlZycjLuOM0BCvkaRUVFt2/fRgidPHnS4s6N5vP5jo6OuFOYQOfOndVqtTVsvkIhG8Ln8+fOnduuXTvcQZpCKpV6enqyWCzcQUxj9uzZkZGRCKGUlBTcWcwICmmcQqFACCmVypSUFDs7i7zNaGFhIcYzac2Bx+MhhCQSydatW3FnMRcopBFZWVmDBg1CCLVp0wZ3lqYrLy/v3Lkz7hSml5iYOHLkSMPpirizmB4U0oh79+5duXIFd4q39ddff3l4eOBOYRatW7dGCP3222/Hjx/HncXEoJD/0mq1GzZsQAglJCTgzmICJSUlhsudyCopKUkqleJOYWJQyH/NmDEjMTERdwrTkMlkd+/eJXchDW+ZYQc47iAmA4VECCHD1UnffPONj48P7iymcf36dcM+SWsQExOD/QJUU4FCokOHDpFv90B2dvaAAQNwp2gmLi4upDlECYVECoVi+PDhuFOYklwu//777/v27Ys7SPMxHJrasWNHdXU17ixvxapHnSsvL2exWIajW2Ry9OjR8vLyRYsW4Q6CwbRp03bu3Glvb487SBNZ7xpy27Ztv//+O/naiBC6evVqfHw87hR4JCcnW24brbeQQqFw+vTppPyrTUlJadGihZeXF+4g2CgUig8//BB3iiayxkJWVlbKZDIHBwfcQcxi3759c+bMwZ0CJxaLlZSUtGLFCtxBmsLqvkOKxeJRo0ZdvnwZdxCzOHTokEwmmzt3Lu4goImsbg2ZlpZGpuPILxMIBMePH4c21kpJScnKysKd4s1Y3RqSxBYuXJiQkNClSxfcQQgkKioqNTXV1tYWd5DGsq415IQJE8h39qPBiRMnWrRoAW2s4+rVqwyGJd2Xko47QPNJTU3t37+/Re8Tr09OTs4PP/zw3Xff4Q5COBQKpaioiMfjOTk54c7SKLDJSgaTJ0/euXOns7Mz7iBEVFFRkZCQkJqaijtIo1hLIRUKxfPnz0lz7vjLFixYMGHChF69euEOQlxpaWlsNjssLAx3kNezlkIeOnRILBYT5/YbprJ7924ejzdlyhTcQYBpWMtOHbVaPXDgQNwpTOy3337TarXQxsb44Ycfbty4gTvF61nLGpJ87t279/nnn1vJaKVvr7CwcP78+WfPnsUd5DWsopBarTY9Pb1Hjx64g5hMcXHx7Nmzf/zxR9xBLEl5eTmXyyX4SHxWsclaWVm5Zs0a3ClMRq1Wr1+/Htr4ptzd3QneRmspJEIoOjoadwSTiYyM3LNnD+4UFqlXr14ajQZ3ioZYxSYrmURHR6ekpHC5XNxBLNLWrVs7d+7cr18/3EHqZRWFVKvVf//9d0REBO4gb2vIkCFHjhyBEwBIzCo2WRkMxoIFC5RKJe4gb2XQoEGHDx+GNr6loqIi3BEaYhWFNFy0Onr06EGDBnXv3n3ChAm447yx9957LyUlxcXFBXcQi7d27dq7d+/iTlEvkp9cbri5BYVCMdykxXALd8N9OyxInz59Lly4QJr7WOE1ePDgnJycTp064Q5iHMkLGRkZWef8DFdX1549e+JL9GbUavX8+fMvXLhgoXfgIqAxY8bgjtAQkm+yrl279uUbzuj1end3d0u5p5VYLI6KitqzZw+00YT0er1hoHpiInkhHRwc5s2b9/IF45YyfHBpaenixYtv3bplWdfXEh+FQlm3bl1ubi7uIMaRvJCG7wy9evUyHN2xlO3VnJyc6dOnw3mqZjJixAiRSIQ7hXHWchxy3LhxRUVFISEhhw8fxh3nNR48eLBhwwa4/N86NWqnjkatq5HpzB/GfCgL5izbunVrTO9h0kpCnzmVn5//+d79yV8efcucFAqycyD5HrsmEwgERUVF4eHhuIMY8Zo15KN0yYNrYlGZim1Ha8ZU1kulUjGZzLdfjouXTQm/Jjjcvs8YFwqVYopo5JGXl7ds2bJTp07hDmJEQx+i6ZdEFSXqqNEe9k6wX8HyKGu0whLF54tzZ2wOYNqQf2dB47Vs2dLNzQ13CuPqXUPeviiSCDXdhxM0N2gktUp3cjt/1pZA3EFAoxj/4Kx8rqooVkIbSYDBpPYc4Zp2vgJ3EGLh8/kKhQJ3CiOMF7KiWKnXwxcPkuA6M59l1+BOQSw7duy4d+8e7hRGGC+kTKx1bQlnTpKEoweLwYTvkP/RsWNHYh7wM75TR63UqYm4PgdNodfpywvg7fyP6dOn445gHHxwAmtUVFRUWFiIO4URUEhgja5cuWJ5xyEBIKvAwEAbGxvcKYyAQgJr1L17d9wRjINNVmCNFApFZWUl7hRGQCGBNUpLS9uwYQPuFEZAIYE1srW1JeZ9zuE7JLBG3bp169atG+4URsAaElgjsVjM5/NxpzACCgms0ePHj48dO4Y7hRFQSGCNnJ2d/f39cacwgoSFzHqU+ZZ3Dfjjyq/R/SIKCvJNFwoQS2Bg4MSJE3GnMIJshbyYem7uvKkKBVxtBBry/PnztLQ03CmMIFshLf2OOqB55ObmHj16FHcKI0h12ONi6rldn21GCMWN7o8QWrZ09eBBIxBCly5dOHr8QElJkbOzy7ChoxImvkulUhFCQmHFF1/uvJ1+Q6PRtA8NmzVzYUBA0KuLPXb84NmUk1KpJCio9dQpMzt36orjxQETGDlyZGFhoeHd1+v1nTt3plAoer3+zp07uKO9QKo1ZLeuvcaPm4QQ2rRh1+5dyd269kIIpaae37RldatWbT5eubFvnwH7D3xx9NgBw8lTi5bMunM3fcb0BYsWflQhFCxaMksqk9ZZ5p276V8n7+3QodOihR95uHvWVFdjenHABJKSkmrvak6hUAxtJNR4kKRaQzo6Onl5eSOEQkJCeTwHw6dg8v7P27cPW/nReoRQ76gYqVRy4rtDY0a/89vliwUF+Tu2f9EpvAtCqH378ImTYr///sSUyf+5dLWsrAQhNGrk+HbtOgwYMBTfiwMmEBsbe+TIkby8vNpHOBxOQkIC1lD/Qao15KuKigoqKgS9o2JqH+nSpUd1dXVRccHff9+x49gZ2ogQ8vDw9PHxe/wkq84SuneLtLfnbtz08a1b15s3OzCLiRMnvjzybVBQUHR0NNZE/0HyQsrkMoSQg4NT7SP29lyEUIXguUwu4zk4vjwxl8sTVgjqLMHZ2WXv7v3eLX0/XLFw/v+SBILnzZUdmEVcXJyPj4/h37a2tpMmTcKd6D/IWcja8YvcXN0RQmJxVe2PKitFhlq6urhJJOKX5xKJhHZ29q8uzcfHb8um3Tu2f8Hn52zZusb88YF5xcfHG1aSQUFBMTExjZij+ZCtkGwWGyFU8c+KztnZxcPdMz3933u2XrnyK4vFCgpq3a5dB6lU8ujRi1sF5uY+LS4ubN8+DCHEZDARQrV1ValUCKFO4V26d4968jQbx8sCphQXF+fn58dms4m2eiTbTh2EULvQjjQabe++7UMGxSpVytgRY6ZOmbl565pt2z/p0qXH3bvp12/8MWXyDDab3b/fkKPHDqxZtyxx0jQqlXr4cLKDg+PI2HEIIf+AICqVuvOzTfPmLmGx2GvXLYsbOZ7Ntk1Pv9mmdVvcL9HqVEs1Jbk1com2WqJFFCSXmOB2Sf07vp9nn6crb/fr8fK3XBSdQaHSKBx7ui2X5ujG9PB7q/FTjd9KID1VpFKgjn2djM1CdD9f/DH5m89VSmWrVm0+3fElQijlx9OnTh8tLy91cXaNjR0bP2EyhUJBCJWVle774tM7d2/rdLoO7cPnzlns4+NnWMilSxe+PZLcq2efgQOGffX17kePMvV6fcewzgvmLXVzc8f9Et+MVqM/tilvznYLu5uASqF7cK0q52+5RKRxamGr01FoDBqdydARbTxVCtJrtFq1VqfWIqSTCJQBoZzgzpyWwZymLIx8hQR1WFwh9Xp92gXR31erXPy4HEdbWwdLGrNbo9RKBNV6tVKv1kSNcnb3ebPwZNtkBZYuL1P+y9FyFx9eSLQf7ixNQbehOXnbI2QvE9Vc/Fbg05odPc6l8bOTbacOsGh/XqpM+6kqOMrH2c8Bd5a3ZefE9u3sJa1mHtlU0Pi5oJCAKO79IX6Wo27R3sPwDZ8cuG4c50CXfR/k6LSN+uoLhQSEcPWHiqeZShd/Z9xBTI9tb9Omj++Xy/IaMS0UEhBA9p/Skny1WyAJ22hApVH9IzyOb3/93USgkAAzYZky85bMow3J7w7M5rHsXO1vnBM2PBkUEmB25YzQhmeHO0VzsHezf3RbKhGqG5gGCglwKsmtkVVp7V2JOGaxObgGOl79oaGVJBQS4HT/mtg5gIjnn1QIC5d83O3eg0umXSzPw65arq8oqXegGSgkwEZZoy14VM2xqBNxTIBG52fI6/shFBJgk5ch53lYy8ZqLXsXztP79RbSNKfO/Zx6xtGBtPusCcjGhhke1hN3irdVwlfaOTflDOzGuJl+5sqNY2LJcydHr/AOA/v2msRg2BSXPN6bPD0pcedPl/aVlD1xdPAcNnBeaEhvwywyeWXKTzsfZl9l0G0C/TubKRibZyNmUGWikU2cAAANFklEQVRVajsHxqs/NU0hlcqakJDWJlkUaAy2LRHv/vumyvg1Tv5mKeSly19fuXEssscEd1f/5xXP/rh2pKKi8J2xaxBCarXyyHcr4oYtdnTwTL381bFTH69YnMLhOKg1qv87OF8oLOzdK8HJ0fPm7TPmCGagkOukVRozFrJ/v6EcjlXsuSYInU6FO4IJ1Mi0dCbN5IsVSwS/XT2YMPaTDqEvRgPg2bucObdl5NBFhv+NG7Y4rP0AhNDQAXN2fTElN/9eh3bRN26dKi17OmPKnuCgrgghv5btt+6eYPJsBgwWTS7WGv2RaQppxyHijjISo1GZjZiK6NRKHd3G9IV8mpuu1WqOnl519PSqfx7TI4TE0hfjITEZL0aCdHTwRAhJpAKEUOajK57uQYY2IoSoVNMHq0Vj0mrqucwaLr8C2Oi0eqRHyNRnkkukFQihpEmfOvD+c/aPs5N3WXnuy4/QaQyEkE6nRQhVictaeDbT1y69DqF6TqCHQgJsWByaRqVlsEz8R8hmcw3/cHN9gysq7TiOMnmlaZPUR6fR2nKNr4HhsAfAhm1H1yiNf5V6G60CIigUyvXbJ2sfUapef/OlFp6tC4uznguemTzPqzRKLYdr/GMI1pAAGw9/lqxGw0Ym3mPs4twysvuEa2kn9h9Z3C6kj1RaceP26aTET7292jQwV3TU5L/u/7Rv/6zePeK59i53H6SaNtXLGCwK1xkKCQjGO4h153cZ1830Rz5ihyx04Lldv3Xqcc4trr1LaNu+PO5rriZxcfaePvmz86m7Uy9/7cBzbx/S90nObZMHQwhVVyloFGRrb7x6MMgV+RF2kCu1Svf1Cn7bGIscO6fJynNErdrRO8U4Gv0prCEBNgwmNaC9nVxUw3Fi1zfN9+e23X1w8dXHvT3bFJUaH7R6/vRkdzeT3a78p1/23Uw3cpIAg26j1hg/R3zVBxeYzHpP0KXoNP7tePX9FAoJcOoUzbtw4Lm/U4v6JhgUM71vpJG7UxnuJGd0ltdunb6RPr0SukfEvfq4RqOm042caoMQYjDq/VZcWSzlOlAd3es9jAyFBDi5tWQ5ezDEZXKeh/FvkhyOA4eDcwQ6ji2PY1vvCu1NPc8VJX7k08AEcNgDYBY9zlUpluFO0RyqSiRhfR3r251jAIUEmNk70rv25xU9KMMdxLykFdXa6upug4zvy6kFhQT4+YVygjvalmSR9t6bCpmy/HHFmPn1flWuBYUEhNB1sGPnvvZlT+reMJcE5KKakszn09Y3ascvFNI0Hj95FNO/i+FOko2R/Thr5qxJw2P7wA0na7XubBfahf3sTok5zqfDRVwuqRGK31vb2GOteAr5/qKZez7f3sAEQmHFylWLy8ub9XtFRsb9teuWN23efH6up4fXyzevb4BCoVi1esnAAcNOn0wN8A9q2jOSUmhP3uBE16K/S57nCHU6gt127g1JyuW5Nwvd3fVjF7x+S7UWnsMeXbr0cHf3bGCCu/f+zM5+6O7u0cgFarVaGo3W8COvlXrp/JvOUiuPn+Pt3dDu7JfduXO7pqY6Lm58I5+uCa/Fcrn7sqau9rt/perGj/lO3nZ2zhzLGiRSIVNJBdVIo2LbonELW3CdjR+rrA+GQk5KjCsuKdq4fidC6MDBL0vLSmhU2rXrl+l0xry5S/r3G/zrbxe3bF1DoVCGDIscOjRu/twlCKHU1PPfnTpcVFTg7OQyY8aC6L4Dbt26vm79h/ETplz65UJoaMflS9d88eWux0+y3Nw87ty5PS1pro0Na9v2dRfOXaVSqQih+InDx46ZOHbMxKTp8WFhEZkZ9wsK8wMDgz9Y/LGvr//OXZsu/HSWyWQOGRa5fNnaPr37vdGL4vNzVGrVlHfHikQVvaP6LZi/1MbGxmjssymnvvnmc61O+27S+KT35vTp3Y/Pz933xaeZD/+2teWMjB03OXEaQqjOaxkyODYrKyP5m8+zHmXY2LCGDxs1fdo8s71F+IX1cQjr45CZVvX0njTzfrmrD0enQzQGncFiEG3NSaEgrVqrVWs1Kg1Cep1KG9iB06qTo5t3U0bTw1DIzZt2J04Z7e8fZNh4S0+/+cHij+fNXbLj0/VHj+3v329w/36Dfzj7XVRkdPyEyYZZTp46cujbr5YvW9spvGvKj6e++mp3dN8BefwchULh6eF15NsfampqEEL5+bn5+Xnz5ixZvnSNWq0+cvQbf/8gQxtlMll5eVlgYDBCqKJCIBFXrf/kU5VatW7d8j17t23ftm/2rPcv/HR2186vQ9q0a8KLyuPnBAeHrFq5qbi4cMXK993dPScnTjMaO27kuPQ/b7q6uL2/8EOEUHFJ0f8WTps8efon63Y8ys5cvGR2WMfOHTqE13ktmZl/L1oya1JC0urVWwqe8RcsnEbuQhqE9nAI7eGg0+lLcmrkEq1cotFpdTUyHe5c/0FjUGg0CseBxbGnO7ozHFzfajAHDIXMf5bH4XA8PDwRQkXFBYMGDu/Vqw9CKCCg1bMCPkJIo9Hk5DyeMW2+YXqpTHrg4JeJk6ZFRUbLZLLc3Cd+/oGGDvTq2WfAgKEIITabbXgkMSEpKCgYIWRjY5PHzwkMaGVYCJ+fgxAK8A9SKBQSiThx0jRXVzeEUL9+g0+dPooQevw4i0qlBgUGvxo45cfTh7796uVHvj/9n/FzxRKxUFiRmJDk5OTs5OTct++AO3dvjxo1wWhshFBe3tOIzt0N/96/f1/Hjp3HjpmIEAoPi3Bzc8/Ne9qhQ3id1/LF/+0KD+8yOXGaRqPJfvzQ3p5rnjeHiKhUinewJW21vg0MhczLy/Hz++dPM/dp78gXIxEVFRf4tPRDCD3NeazRaIKDQwyPZ2c/VCgUp88cO378oFqj7tE9atkHqw3rw6FD/j3JUCqTVlQIwsO71D7Cz8vpMr6H4d+5eU9dXd14PIdH2Q+ZTGaLFi0Nj0skYh7PASH0KDszKKg1g2Fki39k7NiRsWMbeEX8vBwqler/z+4ZvV6v1Wrri21YV/v/U870P28mvTe3dkaxuMrR0anOa1GpVFlZGQ4OjsNG9NZoNK1atdm6ZW+TfveA6LAU8qlh16JcLi8rL/UPePF3nJvzJCoqBiH06FFmy5a+hpVere+OX6hR1Nhx7AyboBqNpqAg/+VdlPy8HDqd7uPzYv9yTU1NaVmJ/z/Nz3z4t2F7lc/P8fMNMOwj0el0abeude8WaXjS4FbGL2B97RoyN/eJr68/i8Uy9O1m2tURw8cYjV27rjYE0+l01dXVzs4vbnl9O/2mVqsND4uo81oMPl65MbhViI2NjdFPDUAOGA575PFzDCuTvLynVCrVzzfAULD8Z3mGgonFlVVVlSWlxcUlRQihoMBgJpN59Nh+vU6Xn59XVFyIECouLlSr1bXrGYQQPz/Xx8ePTn/xEaNSqxBCQlEFQuiXX3/+449fDJuveXk5NDq9qqqysPDZpi2r5XLZ+PGJCKHKKlFJSZFQWCEQ1D1fZGTs2O9PX3r5vzoTZD3KUCmV5eVlz57xV65aZGdnP25sgtHYhpfP4zk4ODgihKhUamBAq99/v6RQKPLz8/Z+vj1h4ns8nkOd18JkMlsFtT51+qhcLqusFGVlZZjz/QE4NXchlUplcXGhoXiGQwWGY3cFBfkajcawtuzbZwCLxZoydUxy8l6EkKOj0/Jla3/59edxE4as/WS5WqUyzOvs7GLY2jTg83Nq14cIIR6XFzdy3Lbt6yYlxuXlPaXT6QEBrQyTqVWqyVPHzJ47WaNWf7YzmcflIYRiR4x9mPUgIXHktWuX3+gV6XS6h1kP+vcfOnP2pPkL3vPw8Pps59ccDsdobMPH0Ms5P/hgVWlpcdzofitXLR4VN2HK5OmvvhaE0LKla8Tiqinvjpk7f6rhcwqQktWNGDB67MDly9Z27dIDV4BpM97pEtFj5owFzfaMhB0xALzKNN8h300aX+cRnU5HpVBfHXIz+avjGI9xV1VVVlaKDLuOsNi9d5tEIh4VZ64hsYGlM00hD3xzshFT4ZfHz7GxsWn8CUAm1ya47XtTZ9vZwW0XgHHWNWJAp/AuF3+6gTHAwIHDMD47ID642gMAAoFCAkAgUEgACAQKCQCBQCEBIBAoJAAEAoUEgECgkAAQCBQSAAKBQgJAIFBIAAgECgkAgUAhASAQ41d7MFkU3avXMgLLRKEgD7+mjBEKmp/xNaS9I0PwrKbZwwCzEJYqNSpijWUK6mO8kG4tbSiwgiQLcYXKt53x+xMDoql3DdkiiHX1DMnvoWkNqgTKe78Juw0i4fBIpGR8kCuDh2nip/dlHfs4O7ozaXTY/WNhpCK1sESRdl6QtN6fRoMNHsvQUCERQvyH8vtXqsr4Chod3lFL4ubDkghVQWF2PYe74M4C3sBrCllLWQN7BSwJhYKYLNiosTyNLSQAoBnAhygABAKFBIBAoJAAEAgUEgACgUICQCBQSAAI5P8B+Kj6L2Ce31YAAAAASUVORK5CYII=", "text/plain": [ "" ] @@ -183,7 +206,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "a783efac-46a9-4fb4-a1c6-a11b02540448", "metadata": {}, "outputs": [ @@ -196,11 +219,11 @@ "Multiply 2 and 3\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", - " multiply (call_eqlctYi3bluPXUgdW0Ac6Abr)\n", - " Call ID: call_eqlctYi3bluPXUgdW0Ac6Abr\n", + " multiply (e58fd83d-545a-48d4-8f8c-c53ccf58056d)\n", + " Call ID: e58fd83d-545a-48d4-8f8c-c53ccf58056d\n", " Args:\n", - " a: 2\n", - " b: 3\n" + " a: 2.0\n", + " b: 3.0\n" ] } ], @@ -228,7 +251,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "id": "61569596-8342-4a37-9c99-e3a9dccb18ee", "metadata": {}, "outputs": [ @@ -238,7 +261,7 @@ "('tools',)" ] }, - "execution_count": 4, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -269,7 +292,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "id": "896a5f41-7386-4bfa-a78e-3e6ca5e26641", "metadata": {}, "outputs": [ @@ -279,18 +302,18 @@ "text": [ "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", - " multiply (call_eqlctYi3bluPXUgdW0Ac6Abr)\n", - " Call ID: call_eqlctYi3bluPXUgdW0Ac6Abr\n", + " multiply (e58fd83d-545a-48d4-8f8c-c53ccf58056d)\n", + " Call ID: e58fd83d-545a-48d4-8f8c-c53ccf58056d\n", " Args:\n", - " a: 2\n", - " b: 3\n", + " a: 2.0\n", + " b: 3.0\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", "Name: multiply\n", "\n", "6\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "The result of multiplying 2 and 3 is 6.\n" + "6\n" ] } ], @@ -309,7 +332,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 9, "id": "95a0eb50-66e3-4538-8103-207aae175154", "metadata": {}, "outputs": [ @@ -322,25 +345,25 @@ "Multiply 2 and 3\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", - " multiply (call_2spIVnq7DwMdttrbKr7oY9Gs)\n", - " Call ID: call_2spIVnq7DwMdttrbKr7oY9Gs\n", + " multiply (6538ddc1-bf6b-44f8-87cc-dc9489a953a0)\n", + " Call ID: 6538ddc1-bf6b-44f8-87cc-dc9489a953a0\n", " Args:\n", - " a: 2\n", - " b: 3\n", + " a: 2.0\n", + " b: 3.0\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", - " multiply (call_2spIVnq7DwMdttrbKr7oY9Gs)\n", - " Call ID: call_2spIVnq7DwMdttrbKr7oY9Gs\n", + " multiply (6538ddc1-bf6b-44f8-87cc-dc9489a953a0)\n", + " Call ID: 6538ddc1-bf6b-44f8-87cc-dc9489a953a0\n", " Args:\n", - " a: 2\n", - " b: 3\n", + " a: 2.0\n", + " b: 3.0\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", "Name: multiply\n", "\n", "6\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "The result of multiplying 2 and 3 is 6.\n" + "6\n" ] } ], @@ -524,7 +547,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "lc-academy", "language": "python", "name": "python3" }, @@ -538,7 +561,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.1" + "version": "3.11.10" } }, "nbformat": 4, diff --git a/module-3/dynamic-breakpoints.ipynb b/langchain-academy/module-3/dynamic-breakpoints.ipynb similarity index 56% rename from module-3/dynamic-breakpoints.ipynb rename to langchain-academy/module-3/dynamic-breakpoints.ipynb index 23a0b1f50..695753d03 100644 --- a/module-3/dynamic-breakpoints.ipynb +++ b/langchain-academy/module-3/dynamic-breakpoints.ipynb @@ -65,7 +65,7 @@ "outputs": [ { "data": { - "image/jpeg": "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", + "image/png": "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", "text/plain": [ "" ] @@ -198,7 +198,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "(PregelTask(id='6eb3910d-e231-5ba2-b25e-28ad575690bd', name='step_2', error=None, interrupts=(Interrupt(value='Received input that is longer than 5 characters: hello world', when='during'),), state=None),)\n" + "(PregelTask(id='46ad0e90-8ea5-1556-2e91-ee08f00de266', name='step_2', path=('__pregel_pull', 'step_2'), error=None, interrupts=(Interrupt(value='Received input that is longer than 5 characters: hello world', when='during'),), state=None),)\n" ] } ], @@ -266,7 +266,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "6f08dff4-3399-46de-a9ba-ba89b8cdb61e", "metadata": {}, "outputs": [ @@ -275,10 +275,10 @@ "text/plain": [ "{'configurable': {'thread_id': '1',\n", " 'checkpoint_ns': '',\n", - " 'checkpoint_id': '1ef6a434-06cf-6f1e-8002-0ea6dc69e075'}}" + " 'checkpoint_id': '1efcead9-ec2b-6e2a-8003-7589bc567e09'}}" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -292,7 +292,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "4cb3f62b-fccd-47c3-af1e-541969e4d804", "metadata": {}, "outputs": [ @@ -559,7 +559,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "lc-academy", "language": "python", "name": "python3" }, @@ -573,7 +573,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.1" + "version": "3.11.10" } }, "nbformat": 4, diff --git a/langchain-academy/module-3/edit-state-human-feedback.ipynb b/langchain-academy/module-3/edit-state-human-feedback.ipynb new file mode 100644 index 000000000..cb888a9e3 --- /dev/null +++ b/langchain-academy/module-3/edit-state-human-feedback.ipynb @@ -0,0 +1,984 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "147e576c", + "metadata": {}, + "source": [ + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-3/edit-state-human-feedback.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239520-lesson-3-editing-state-and-human-feedback)" + ] + }, + { + "cell_type": "markdown", + "id": "3b2f2448-21c3-4196-9e61-0b47e7d0048b", + "metadata": {}, + "source": [ + "# Editing graph state\n", + "\n", + "## Review\n", + "\n", + "We discussed motivations for human-in-the-loop:\n", + "\n", + "(1) `Approval` - We can interrupt our agent, surface state to a user, and allow the user to accept an action\n", + "\n", + "(2) `Debugging` - We can rewind the graph to reproduce or avoid issues\n", + "\n", + "(3) `Editing` - You can modify the state \n", + "\n", + "We showed how breakpoints support user approval, but don't yet know how to modify our graph state once our graph is interrupted!\n", + "\n", + "## Goals\n", + "\n", + "Now, let's show how to directly edit the graph state and insert human feedback." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "95d26b8c-d958-4d21-9ca4-4636d3dfe45c", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install --quiet -U langgraph langchain_openai langgraph_sdk" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d5948594", + "metadata": {}, + "outputs": [], + "source": [ + "# import os, getpass\n", + "\n", + "# def _set_env(var: str):\n", + "# if not os.environ.get(var):\n", + "# os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "\n", + "# _set_env(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "8b71c68f", + "metadata": {}, + "outputs": [], + "source": [ + "# load environment variables\n", + "from dotenv import load_dotenv\n", + "import os\n", + "\n", + "load_dotenv(dotenv_path='C:\\MIConsulting\\LangChain\\langchain-academy\\.env')\n", + "\n", + "# OPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\")\n", + "GOOGLEAI_API_KEY = os.getenv(\"GOOGLEAI_API_KEY\")\n", + "LANGCHAIN_API_KEY = os.getenv('LANGCHAIN_API_KEY')\n", + "LANGCHAIN_ENDPOINT = os.getenv('LANGCHAIN_ENDPOINT')\n", + "LANGCHAIN_TRACING_V2 = os.getenv('LANGCHAIN_TRACING_V2')\n", + "LANGCHAIN_PROJECT = os.getenv('LANGCHAIN_PROJECT')" + ] + }, + { + "cell_type": "markdown", + "id": "65a8df1f-a76a-4803-a532-ea9802106ac8", + "metadata": {}, + "source": [ + "## Editing state \n", + "\n", + "Previously, we introduced breakpoints.\n", + "\n", + "We used them to interrupt the graph and await user approval before executing the next node.\n", + "\n", + "But breakpoints are also [opportunities to modify the graph state](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/edit-graph-state/).\n", + "\n", + "Let's set up our agent with a breakpoint before the `assistant` node." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "bcf24f05-ac2b-455e-846c-0c50ac86e1f4", + "metadata": {}, + "outputs": [], + "source": [ + "# from langchain_openai import ChatOpenAI\n", + "from langchain_google_genai import ChatGoogleGenerativeAI\n", + "\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", + "# This will be a 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", + "def divide(a: int, b: int) -> float:\n", + " \"\"\"Adds a and b.\n", + "\n", + " Args:\n", + " a: first int\n", + " b: second int\n", + " \"\"\"\n", + " return a / b\n", + "\n", + "tools = [add, multiply, divide]\n", + "# llm = ChatOpenAI(model=\"gpt-4o\")\n", + "llm = ChatGoogleGenerativeAI(api_key=GOOGLEAI_API_KEY, model=\"gemini-1.5-pro\")\n", + "llm_with_tools = llm.bind_tools(tools)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5dfe84af-5c62-4c3f-8ed7-96b5261f0b7b", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "from langgraph.checkpoint.memory import MemorySaver\n", + "from langgraph.graph import MessagesState\n", + "from langgraph.graph import START, StateGraph\n", + "from langgraph.prebuilt import tools_condition, ToolNode\n", + "\n", + "from langchain_core.messages import HumanMessage, SystemMessage\n", + "\n", + "# System message\n", + "sys_msg = SystemMessage(content=\"You are a helpful assistant tasked with performing arithmetic on a set of inputs.\")\n", + "\n", + "# Node\n", + "def assistant(state: MessagesState):\n", + " return {\"messages\": [llm_with_tools.invoke([sys_msg] + state[\"messages\"])]}\n", + "\n", + "# Graph\n", + "builder = StateGraph(MessagesState)\n", + "\n", + "# Define nodes: these do the work\n", + "builder.add_node(\"assistant\", assistant)\n", + "builder.add_node(\"tools\", ToolNode(tools))\n", + "\n", + "# Define edges: these determine the control flow\n", + "builder.add_edge(START, \"assistant\")\n", + "builder.add_conditional_edges(\n", + " \"assistant\",\n", + " # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools\n", + " # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END\n", + " tools_condition,\n", + ")\n", + "builder.add_edge(\"tools\", \"assistant\")\n", + "\n", + "memory = MemorySaver()\n", + "graph = builder.compile(interrupt_before=[\"assistant\"], checkpointer=memory)\n", + "\n", + "# Show\n", + "display(Image(graph.get_graph(xray=True).draw_mermaid_png()))" + ] + }, + { + "cell_type": "markdown", + "id": "92a47fd5-1f60-41dc-9206-698ed8ece530", + "metadata": {}, + "source": [ + "Let's run!\n", + "\n", + "We can see the graph is interrupted before the chat model responds. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a2ce488d-00e4-492e-a62c-dd98702c313f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Multiply 2 and 3\n" + ] + } + ], + "source": [ + "# Input\n", + "initial_input = {\"messages\": \"Multiply 2 and 3\"}\n", + "\n", + "# Thread\n", + "thread = {\"configurable\": {\"thread_id\": \"1\"}}\n", + "\n", + "# Run the graph until the first interruption\n", + "for event in graph.stream(initial_input, thread, stream_mode=\"values\"):\n", + " event['messages'][-1].pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "4be478ef-bd60-4d32-8a05-5f56c93a8396", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "StateSnapshot(values={'messages': [HumanMessage(content='Multiply 2 and 3', additional_kwargs={}, response_metadata={}, id='c0d27814-f483-4b6e-a000-2d7095ef4524')]}, next=('assistant',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efceaba-f2c6-66d0-8000-6859afa2e1f1'}}, metadata={'source': 'loop', 'writes': None, 'step': 0, 'parents': {}}, created_at='2025-01-09T17:03:44.542689+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efceaba-f2c1-68c2-bfff-f75fd053042b'}}, tasks=(PregelTask(id='8c5e0212-3db5-778a-a313-2392184087cf', name='assistant', path=('__pregel_pull', 'assistant'), error=None, interrupts=(), state=None),))" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "state = graph.get_state(thread)\n", + "state" + ] + }, + { + "cell_type": "markdown", + "id": "36ef63a1-2ab8-416d-babf-d35054e294f0", + "metadata": {}, + "source": [ + "Now, we can directly apply a state update.\n", + "\n", + "Remember, updates to the `messages` key will use the `add_messages` reducer:\n", + " \n", + "* If we want to over-write the existing message, we can supply the message `id`.\n", + "* If we simply want to append to our list of messages, then we can pass a message without an `id` specified, as shown below." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "9179cff1-e529-473a-9ce2-e23b932c2063", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'configurable': {'thread_id': '1',\n", + " 'checkpoint_ns': '',\n", + " 'checkpoint_id': '1efceabc-d379-69a2-8001-9a356f70b692'}}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.update_state(\n", + " thread,\n", + " {\"messages\": [HumanMessage(content=\"No, actually multiply 3 and 3!\")]},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "d77b8d6a-8c7b-4f7a-b723-121af25ac829", + "metadata": {}, + "source": [ + "Let's have a look.\n", + "\n", + "We called `update_state` with a new message. \n", + "\n", + "The `add_messages` reducer appends it to our state key, `messages`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "141b6aab-ec6d-44f3-beb1-6c22ac5f2158", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Multiply 2 and 3\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "No, actually multiply 3 and 3!\n" + ] + } + ], + "source": [ + "new_state = graph.get_state(thread).values\n", + "for m in new_state['messages']:\n", + " m.pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "e4041959-cc3a-4168-8cf7-06d1711921d8", + "metadata": {}, + "source": [ + "Now, let's proceed with our agent, simply by passing `None` and allowing it proceed from the current state.\n", + "\n", + "We emit the current and then proceed to execute the remaining nodes." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "f166bed2-87c9-41ec-b235-0305721c2d6b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "No, actually multiply 3 and 3!\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " multiply (963c49d2-6b1e-447b-9e2b-3c6cb0b66af2)\n", + " Call ID: 963c49d2-6b1e-447b-9e2b-3c6cb0b66af2\n", + " Args:\n", + " a: 3.0\n", + " b: 3.0\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: multiply\n", + "\n", + "9\n" + ] + } + ], + "source": [ + "for event in graph.stream(None, thread, stream_mode=\"values\"):\n", + " event['messages'][-1].pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "b18dc1ca", + "metadata": {}, + "source": [ + "Now, we're back at the `assistant`, which has our `breakpoint`.\n", + "\n", + "We can again pass `None` to proceed." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "f5952731-0170-4589-a399-ee787df35400", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: multiply\n", + "\n", + "9\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "9\n" + ] + } + ], + "source": [ + "for event in graph.stream(None, thread, stream_mode=\"values\"):\n", + " event['messages'][-1].pretty_print()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "bc22c3e9-b00c-4ead-b752-a682b45b3718", + "metadata": {}, + "source": [ + "### Editing graph state in Studio\n", + "\n", + "--\n", + "\n", + "**⚠️ DISCLAIMER**\n", + "\n", + "*Running Studio currently requires a Mac. If you are not using a Mac, then skip this step.*\n", + "\n", + "*Also, if you are running this notebook in CoLab, then skip this step.*\n", + "\n", + "--\n", + "\n", + "Let's load our `agent` in the Studio UI, which uses `module-3/studio/agent.py` set in `module-3/studio/langgraph.json`.\n", + "\n", + "### Editing graph state with LangGraph API\n", + "\n", + "We can interact with our agent via the SDK.\n", + "\n", + "![Screenshot 2024-08-26 at 9.59.19 AM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbaf2fbfb576f8e53ed930_edit-state-human-feedback1.png)\n", + "\n", + "Let's get the URL for the local deployment from Studio.\n", + "\n", + "The LangGraph API [supports editing graph state](https://langchain-ai.github.io/langgraph/cloud/how-tos/human_in_the_loop_edit_state/#initial-invocation). " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "020efeba-fa80-4839-81f9-9ce228f9844e", + "metadata": {}, + "outputs": [], + "source": [ + "import platform\n", + "\n", + "if 'google.colab' in str(get_ipython()) or platform.system() != 'Darwin':\n", + " raise Exception(\"Unfortunately LangGraph Studio is currently not supported on Google Colab or requires a Mac\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "642aabab-f822-4917-9d66-3314ac5008fd", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph_sdk import get_client\n", + "client = get_client(url=\"http://localhost:56091\")" + ] + }, + { + "cell_type": "markdown", + "id": "be74cb09", + "metadata": {}, + "source": [ + "Our agent is defined in `assistant/agent.py`. \n", + "\n", + "If you look at the code, you'll see that it *does not* have a breakpoint! \n", + " \n", + "Of course, we can add it to `agent.py`, but one very nice feature of the API is that we can pass in a breakpoint!\n", + "\n", + "Here, we pass a `interrupt_before=[\"assistant\"]`." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "1c352f9e-6a0f-4a94-a083-b85b0233efa9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Receiving new event of type: metadata...\n", + "--------------------------------------------------\n", + "Receiving new event of type: values...\n", + "{'content': 'Multiply 2 and 3', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '882dabe4-b877-4d71-bd09-c34cb97c4f46', 'example': False}\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "initial_input = {\"messages\": \"Multiply 2 and 3\"}\n", + "thread = await client.threads.create()\n", + "async for chunk in client.runs.stream(\n", + " thread[\"thread_id\"],\n", + " \"agent\",\n", + " input=initial_input,\n", + " stream_mode=\"values\",\n", + " interrupt_before=[\"assistant\"],\n", + "):\n", + " print(f\"Receiving new event of type: {chunk.event}...\")\n", + " messages = chunk.data.get('messages', [])\n", + " if messages:\n", + " print(messages[-1])\n", + " print(\"-\" * 50)" + ] + }, + { + "cell_type": "markdown", + "id": "13065dd9-5f43-47d6-ac2a-9dc15c0c54e6", + "metadata": {}, + "source": [ + "We can get the current state" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "4da2c464-3e71-496a-badc-671aeee168b6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'values': {'messages': [{'content': 'Multiply 2 and 3',\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'type': 'human',\n", + " 'name': None,\n", + " 'id': '882dabe4-b877-4d71-bd09-c34cb97c4f46',\n", + " 'example': False}]},\n", + " 'next': ['assistant'],\n", + " 'tasks': [{'id': 'a71c0b80-a679-57cb-aa59-a1655b763480',\n", + " 'name': 'assistant',\n", + " 'error': None,\n", + " 'interrupts': [],\n", + " 'state': None}],\n", + " 'metadata': {'step': 0,\n", + " 'run_id': '1ef6a41c-ea63-663f-b3e8-4f001bf0bf53',\n", + " 'source': 'loop',\n", + " 'writes': None,\n", + " 'parents': {},\n", + " 'user_id': '',\n", + " 'graph_id': 'agent',\n", + " 'thread_id': 'a95ffa54-2435-4a47-a9da-e886369ca8ee',\n", + " 'created_by': 'system',\n", + " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n", + " 'created_at': '2024-09-03T22:13:54.466695+00:00',\n", + " 'checkpoint_id': '1ef6a41c-ead7-637b-8000-8c6a7b98379e',\n", + " 'parent_checkpoint_id': '1ef6a41c-ead3-637d-bfff-397ebdb4f2ea'}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "current_state = await client.threads.get_state(thread['thread_id'])\n", + "current_state" + ] + }, + { + "cell_type": "markdown", + "id": "4527bbf1-0927-41a6-aeef-d15e32bbbdc3", + "metadata": {}, + "source": [ + "We can look at the last message in state." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "801ae2d9-0551-46b8-aee2-82293cee4011", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'content': 'Multiply 2 and 3',\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'type': 'human',\n", + " 'name': None,\n", + " 'id': '882dabe4-b877-4d71-bd09-c34cb97c4f46',\n", + " 'example': False}" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "last_message = current_state['values']['messages'][-1]\n", + "last_message" + ] + }, + { + "cell_type": "markdown", + "id": "f0581ba8-db3d-474d-9042-b1c7f3461caf", + "metadata": {}, + "source": [ + "We can edit it!" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "86b12be7-7e4a-40d0-8521-dced7c393c71", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'content': 'No, actually multiply 3 and 3!',\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'type': 'human',\n", + " 'name': None,\n", + " 'id': '882dabe4-b877-4d71-bd09-c34cb97c4f46',\n", + " 'example': False}" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "last_message['content'] = \"No, actually multiply 3 and 3!\"\n", + "last_message" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "f84f2c24-f281-4591-90e5-de3a5547c9da", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'content': 'No, actually multiply 3 and 3!',\n", + " 'additional_kwargs': {},\n", + " 'response_metadata': {},\n", + " 'type': 'human',\n", + " 'name': None,\n", + " 'id': '882dabe4-b877-4d71-bd09-c34cb97c4f46',\n", + " 'example': False}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "last_message" + ] + }, + { + "cell_type": "markdown", + "id": "ce7b4280-6ae7-4246-9c87-44e0daa6c654", + "metadata": {}, + "source": [ + "Remember, as we said before, updates to the `messages` key will use the same `add_messages` reducer. \n", + "\n", + "If we want to over-write the existing message, then we can supply the message `id`.\n", + "\n", + "Here, we did that. We only modified the message `content`, as shown above." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "84d33b6e-32ff-4eca-8114-345e508f3481", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'configurable': {'thread_id': 'a95ffa54-2435-4a47-a9da-e886369ca8ee',\n", + " 'checkpoint_ns': '',\n", + " 'checkpoint_id': '1ef6a41d-cc8e-6979-8001-8c7c283b636c'},\n", + " 'checkpoint_id': '1ef6a41d-cc8e-6979-8001-8c7c283b636c'}" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "await client.threads.update_state(thread['thread_id'], {\"messages\": last_message})" + ] + }, + { + "cell_type": "markdown", + "id": "1f07f0d1-7083-4827-babd-d3702eb59a37", + "metadata": {}, + "source": [ + "Now, we resume by passing `None`. " + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "ef18d12d-e0a6-487a-9f32-ad30e2634a20", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Receiving new event of type: metadata...\n", + "--------------------------------------------------\n", + "Receiving new event of type: values...\n", + "{'content': 'No, actually multiply 3 and 3!', 'additional_kwargs': {'additional_kwargs': {}, 'response_metadata': {}, 'example': False}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '882dabe4-b877-4d71-bd09-c34cb97c4f46', 'example': False}\n", + "--------------------------------------------------\n", + "Receiving new event of type: values...\n", + "{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_vi16je2EIikHuT7Aue2sd1qd', 'function': {'arguments': '{\"a\":3,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-775b42f7-0590-4c54-aaeb-78599b1f12d2', 'example': False, 'tool_calls': [{'name': 'multiply', 'args': {'a': 3, 'b': 3}, 'id': 'call_vi16je2EIikHuT7Aue2sd1qd', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': None}\n", + "--------------------------------------------------\n", + "Receiving new event of type: values...\n", + "{'content': '9', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'multiply', 'id': '226bfbad-0cea-4900-80c5-761a62bd4bc1', 'tool_call_id': 'call_vi16je2EIikHuT7Aue2sd1qd', 'artifact': None, 'status': 'success'}\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "async for chunk in client.runs.stream(\n", + " thread[\"thread_id\"],\n", + " assistant_id=\"agent\",\n", + " input=None,\n", + " stream_mode=\"values\",\n", + " interrupt_before=[\"assistant\"],\n", + "):\n", + " print(f\"Receiving new event of type: {chunk.event}...\")\n", + " messages = chunk.data.get('messages', [])\n", + " if messages:\n", + " print(messages[-1])\n", + " print(\"-\" * 50)" + ] + }, + { + "cell_type": "markdown", + "id": "6a82dd35-cbc8-486d-8e20-10d0c4d138d6", + "metadata": {}, + "source": [ + "We get the result of the tool call as `9`, as expected." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "1d1bb3c7-dc26-4c32-b3df-865f41ef3c73", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Receiving new event of type: metadata...\n", + "--------------------------------------------------\n", + "Receiving new event of type: values...\n", + "{'content': '9', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'multiply', 'id': '226bfbad-0cea-4900-80c5-761a62bd4bc1', 'tool_call_id': 'call_vi16je2EIikHuT7Aue2sd1qd', 'artifact': None, 'status': 'success'}\n", + "--------------------------------------------------\n", + "Receiving new event of type: values...\n", + "{'content': 'The result of multiplying 3 by 3 is 9.', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-859bbf47-9f35-4e71-ae98-9d93ee49d16c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "async for chunk in client.runs.stream(\n", + " thread[\"thread_id\"],\n", + " assistant_id=\"agent\",\n", + " input=None,\n", + " stream_mode=\"values\",\n", + " interrupt_before=[\"assistant\"],\n", + "):\n", + " print(f\"Receiving new event of type: {chunk.event}...\")\n", + " messages = chunk.data.get('messages', [])\n", + " if messages:\n", + " print(messages[-1])\n", + " print(\"-\" * 50)" + ] + }, + { + "cell_type": "markdown", + "id": "6914c5ca-27e4-421c-835a-9e4327dac12f", + "metadata": {}, + "source": [ + "## Awaiting user input\n", + "\n", + "So, it's clear that we can edit our agent state after a breakpoint.\n", + "\n", + "Now, what if we want to allow for human feedback to perform this state update?\n", + "\n", + "We'll add a node that [serves as a placeholder for human feedback](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/wait-user-input/#setup) within our agent.\n", + "\n", + "This `human_feedback` node allow the user to add feedback directly to state.\n", + " \n", + "We specify the breakpoint using `interrupt_before` our `human_feedback` node.\n", + "\n", + "We set up a checkpointer to save the state of the graph up until this node." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "e4b475ff-681f-4660-80dd-d6ade7bd48e3", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# System message\n", + "sys_msg = SystemMessage(content=\"You are a helpful assistant tasked with performing arithmetic on a set of inputs.\")\n", + "\n", + "# no-op node that should be interrupted on\n", + "def human_feedback(state: MessagesState):\n", + " pass\n", + "\n", + "# Assistant node\n", + "def assistant(state: MessagesState):\n", + " return {\"messages\": [llm_with_tools.invoke([sys_msg] + state[\"messages\"])]}\n", + "\n", + "# Graph\n", + "builder = StateGraph(MessagesState)\n", + "\n", + "# Define nodes: these do the work\n", + "builder.add_node(\"assistant\", assistant)\n", + "builder.add_node(\"tools\", ToolNode(tools))\n", + "builder.add_node(\"human_feedback\", human_feedback)\n", + "\n", + "# Define edges: these determine the control flow\n", + "builder.add_edge(START, \"human_feedback\")\n", + "builder.add_edge(\"human_feedback\", \"assistant\")\n", + "builder.add_conditional_edges(\n", + " \"assistant\",\n", + " # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools\n", + " # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END\n", + " tools_condition,\n", + ")\n", + "builder.add_edge(\"tools\", \"human_feedback\")\n", + "\n", + "memory = MemorySaver()\n", + "graph = builder.compile(interrupt_before=[\"human_feedback\"], checkpointer=memory)\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "markdown", + "id": "32d4ceb6-a224-4307-8196-3f53d367df5c", + "metadata": {}, + "source": [ + "We will get feedback from the user.\n", + "\n", + "We use `.update_state` to update the state of the graph with the human response we get, as before.\n", + "\n", + "We use the `as_node=\"human_feedback\"` parameter to apply this state update as the specified node, `human_feedback`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "3fc7bcd6-660c-4a8a-ad8d-e6698dcf6201", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Multiply 2 and 3\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " multiply (7dd123e8-2199-45e2-9d3d-da08b130933f)\n", + " Call ID: 7dd123e8-2199-45e2-9d3d-da08b130933f\n", + " Args:\n", + " a: 2.0\n", + " b: 3.0\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: multiply\n", + "\n", + "6\n" + ] + } + ], + "source": [ + "# Input\n", + "initial_input = {\"messages\": \"Multiply 2 and 3\"}\n", + "\n", + "# Thread\n", + "thread = {\"configurable\": {\"thread_id\": \"5\"}}\n", + "\n", + "# Run the graph until the first interruption\n", + "for event in graph.stream(initial_input, thread, stream_mode=\"values\"):\n", + " event[\"messages\"][-1].pretty_print()\n", + " \n", + "# Get user input\n", + "user_input = input(\"Tell me how you want to update the state: \")\n", + "\n", + "# We now update the state as if we are the human_feedback node\n", + "graph.update_state(thread, {\"messages\": user_input}, as_node=\"human_feedback\")\n", + "\n", + "# Continue the graph execution\n", + "for event in graph.stream(None, thread, stream_mode=\"values\"):\n", + " event[\"messages\"][-1].pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "abf4cf5f-c0cb-4fdb-be6b-271ae4e967e2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: multiply\n", + "\n", + "6\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "6\n" + ] + } + ], + "source": [ + "# Continue the graph execution\n", + "for event in graph.stream(None, thread, stream_mode=\"values\"):\n", + " event[\"messages\"][-1].pretty_print()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "lc-academy", + "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.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/langchain-academy/module-3/streaming-interruption.ipynb b/langchain-academy/module-3/streaming-interruption.ipynb new file mode 100644 index 000000000..a556ad999 --- /dev/null +++ b/langchain-academy/module-3/streaming-interruption.ipynb @@ -0,0 +1,932 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0c9e547f", + "metadata": {}, + "source": [ + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-3/streaming-interruption.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239464-lesson-1-streaming)" + ] + }, + { + "cell_type": "markdown", + "id": "319adfec-2d0a-49f2-87f9-275c4a32add2", + "metadata": {}, + "source": [ + "# Streaming\n", + "\n", + "## Review\n", + "\n", + "In module 2, covered a few ways to customize graph state and memory.\n", + " \n", + "We built up to a Chatbot with external memory that can sustain long-running conversations. \n", + "\n", + "## Goals\n", + "\n", + "This module will dive into `human-in-the-loop`, which builds on memory and allows users to interact directly with graphs in various ways. \n", + "\n", + "To set the stage for `human-in-the-loop`, we'll first dive into streaming, which provides several ways to visualize graph output (e.g., node state or chat model tokens) over the course of execution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "db024d1f-feb3-45a0-a55c-e7712a1feefa", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install --quiet -U langgraph langchain_openai langgraph_sdk" + ] + }, + { + "cell_type": "markdown", + "id": "70d7e41b-c6ba-4e47-b645-6c110bede549", + "metadata": {}, + "source": [ + "## Streaming\n", + "\n", + "LangGraph is built with [first class support for streaming](https://langchain-ai.github.io/langgraph/concepts/low_level/#streaming).\n", + "\n", + "Let's set up our Chatbot from Module 2, and show various way to stream outputs from the graph during execution. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "5b430d92-f595-4322-a56e-06de7485daa8", + "metadata": {}, + "outputs": [], + "source": [ + "# import os, getpass\n", + "\n", + "# def _set_env(var: str):\n", + "# if not os.environ.get(var):\n", + "# os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "\n", + "# _set_env(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "0892df6b", + "metadata": {}, + "outputs": [], + "source": [ + "# load environment variables\n", + "from dotenv import load_dotenv\n", + "import os\n", + "\n", + "load_dotenv(dotenv_path='C:\\MIConsulting\\LangChain\\langchain-academy\\.env')\n", + "\n", + "# OPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\")\n", + "GOOGLEAI_API_KEY = os.getenv(\"GOOGLEAI_API_KEY\")\n", + "LANGCHAIN_API_KEY = os.getenv('LANGCHAIN_API_KEY')\n", + "LANGCHAIN_ENDPOINT = os.getenv('LANGCHAIN_ENDPOINT')\n", + "LANGCHAIN_TRACING_V2 = os.getenv('LANGCHAIN_TRACING_V2')\n", + "LANGCHAIN_PROJECT = os.getenv('LANGCHAIN_PROJECT')" + ] + }, + { + "cell_type": "markdown", + "id": "4d0682fc", + "metadata": {}, + "source": [ + "Note that we use `RunnableConfig` with `call_model` to enable token-wise streaming. This is [only needed with python < 3.11](https://langchain-ai.github.io/langgraph/how-tos/streaming-tokens/). We include in case you are running this notebook in CoLab, which will use python 3.x. " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2d7321e0-0d99-4efe-a67b-74c12271859b", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "# from langchain_openai import ChatOpenAI\n", + "from langchain_google_genai import ChatGoogleGenerativeAI\n", + "from langchain_core.messages import SystemMessage, HumanMessage, RemoveMessage\n", + "from langchain_core.runnables import RunnableConfig\n", + "\n", + "from langgraph.checkpoint.memory import MemorySaver\n", + "from langgraph.graph import StateGraph, START, END\n", + "from langgraph.graph import MessagesState\n", + "\n", + "# LLM\n", + "# model = ChatOpenAI(model=\"gpt-4o\", temperature=0)\n", + "model = ChatGoogleGenerativeAI(model=\"gemini-1.5-pro\", temperature=0, api_key=GOOGLEAI_API_KEY)\n", + "\n", + "# State \n", + "class State(MessagesState):\n", + " summary: str\n", + "\n", + "# Define the logic to call the model\n", + "def call_model(state: State, config: RunnableConfig):\n", + " \n", + " # Get summary if it exists\n", + " summary = state.get(\"summary\", \"\")\n", + "\n", + " # If there is summary, then we add it\n", + " if summary:\n", + " \n", + " # Add summary to system message\n", + " system_message = f\"Summary of conversation earlier: {summary}\"\n", + "\n", + " # Append summary to any newer messages\n", + " messages = [SystemMessage(content=system_message)] + state[\"messages\"]\n", + " \n", + " else:\n", + " messages = state[\"messages\"]\n", + " \n", + " response = model.invoke(messages, config)\n", + " return {\"messages\": response}\n", + "\n", + "def summarize_conversation(state: State):\n", + " \n", + " # First, we get any existing summary\n", + " summary = state.get(\"summary\", \"\")\n", + "\n", + " # Create our summarization prompt \n", + " if summary:\n", + " \n", + " # A summary already exists\n", + " summary_message = (\n", + " f\"This is summary of the conversation to date: {summary}\\n\\n\"\n", + " \"Extend the summary by taking into account the new messages above:\"\n", + " )\n", + " \n", + " else:\n", + " summary_message = \"Create a summary of the conversation above:\"\n", + "\n", + " # Add prompt to our history\n", + " messages = state[\"messages\"] + [HumanMessage(content=summary_message)]\n", + " response = model.invoke(messages)\n", + " \n", + " # Delete all but the 2 most recent messages\n", + " delete_messages = [RemoveMessage(id=m.id) for m in state[\"messages\"][:-2]]\n", + " return {\"summary\": response.content, \"messages\": delete_messages}\n", + "\n", + "# Determine whether to end or summarize the conversation\n", + "def should_continue(state: State):\n", + " \n", + " \"\"\"Return the next node to execute.\"\"\"\n", + " \n", + " messages = state[\"messages\"]\n", + " \n", + " # If there are more than six messages, then we summarize the conversation\n", + " if len(messages) > 6:\n", + " return \"summarize_conversation\"\n", + " \n", + " # Otherwise we can just end\n", + " return END\n", + "\n", + "# Define a new graph\n", + "workflow = StateGraph(State)\n", + "workflow.add_node(\"conversation\", call_model)\n", + "workflow.add_node(summarize_conversation)\n", + "\n", + "# Set the entrypoint as conversation\n", + "workflow.add_edge(START, \"conversation\")\n", + "workflow.add_conditional_edges(\"conversation\", should_continue)\n", + "workflow.add_edge(\"summarize_conversation\", END)\n", + "\n", + "# Compile\n", + "memory = MemorySaver()\n", + "graph = workflow.compile(checkpointer=memory)\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "f847a787-b301-488c-9b58-cba9f389f55d", + "metadata": {}, + "source": [ + "### Streaming full state\n", + "\n", + "Now, let's talk about ways to [stream our graph state](https://langchain-ai.github.io/langgraph/concepts/low_level/#streaming).\n", + "\n", + "`.stream` and `.astream` are sync and async methods for streaming back results. \n", + " \n", + "LangGraph supports a few [different streaming modes](https://langchain-ai.github.io/langgraph/how-tos/stream-values/) for [graph state](https://langchain-ai.github.io/langgraph/how-tos/stream-values/):\n", + " \n", + "* `values`: This streams the full state of the graph after each node is called.\n", + "* `updates`: This streams updates to the state of the graph after each node is called.\n", + "\n", + "![values_vs_updates.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbaf892d24625a201744e5_streaming1.png)\n", + "\n", + "Let's look at `stream_mode=\"updates\"`.\n", + "\n", + "Because we stream with `updates`, we only see updates to the state after node in the graph is run.\n", + "\n", + "Each `chunk` is a dict with `node_name` as the key and the updated state as the value." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "9a6f8ae9-f244-40c5-a2da-618b72631b22", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'conversation': {'messages': AIMessage(content=\"Hi Lance! It's nice to meet you. How can I help you today?\\n\", additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-695a807e-1b45-4112-821b-d445e175f2f1-0', usage_metadata={'input_tokens': 7, 'output_tokens': 19, 'total_tokens': 26, 'input_token_details': {'cache_read': 0}})}}\n" + ] + } + ], + "source": [ + "# Create a thread\n", + "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", + "\n", + "# Start conversation\n", + "for chunk in graph.stream({\"messages\": [HumanMessage(content=\"hi! I'm Lance\")]}, config, stream_mode=\"updates\"):\n", + " print(chunk)" + ] + }, + { + "cell_type": "markdown", + "id": "0c4882e9-07dd-4d70-866b-dfc530418cad", + "metadata": {}, + "source": [ + "Let's now just print the state update." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c859c777-cb12-4682-9108-6b367e597b81", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Hi Lance! It's nice to meet you too. Is there anything I can do for you?\n" + ] + } + ], + "source": [ + "# Start conversation\n", + "for chunk in graph.stream({\"messages\": [HumanMessage(content=\"hi! I'm Lance\")]}, config, stream_mode=\"updates\"):\n", + " chunk['conversation'][\"messages\"].pretty_print()" + ] + }, + { + "cell_type": "markdown", + "id": "583bf219-6358-4d06-ae99-c40f43569fda", + "metadata": {}, + "source": [ + "Now, we can see `stream_mode=\"values\"`.\n", + "\n", + "This is the `full state` of the graph after the `conversation` node is called." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "6ee763f8-6d1f-491e-8050-fb1439e116df", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "hi! I'm Lance\n", + "---------------------------------------------------------------------------\n", + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "hi! I'm Lance\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Hi Lance! It's nice to meet you. How can I help you today?\n", + "---------------------------------------------------------------------------\n" + ] + } + ], + "source": [ + "# Start conversation, again\n", + "config = {\"configurable\": {\"thread_id\": \"2\"}}\n", + "\n", + "# Start conversation\n", + "input_message = HumanMessage(content=\"hi! I'm Lance\")\n", + "for event in graph.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n", + " for m in event['messages']:\n", + " m.pretty_print()\n", + " print(\"---\"*25)" + ] + }, + { + "cell_type": "markdown", + "id": "563c198a-d1a4-4700-b7a7-ff5b8e0b25d7", + "metadata": {}, + "source": [ + "### Streaming tokens\n", + "\n", + "We often want to stream more than graph state.\n", + "\n", + "In particular, with chat model calls it is common to stream the tokens as they are generated.\n", + "\n", + "We can do this [using the `.astream_events` method](https://langchain-ai.github.io/langgraph/how-tos/streaming-from-final-node/#stream-outputs-from-the-final-node), which streams back events as they happen inside nodes!\n", + "\n", + "Each event is a dict with a few keys:\n", + " \n", + "* `event`: This is the type of event that is being emitted. \n", + "* `name`: This is the name of event.\n", + "* `data`: This is the data associated with the event.\n", + "* `metadata`: Contains`langgraph_node`, the node emitting the event.\n", + "\n", + "Let's have a look." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "6ae8c7a6-c6e7-4cef-ac9f-190d2f4dd763", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Node: . Type: on_chain_start. Name: LangGraph\n", + "Node: __start__. Type: on_chain_start. Name: __start__\n", + "Node: __start__. Type: on_chain_end. Name: __start__\n", + "Node: conversation. Type: on_chain_start. Name: conversation\n", + "Node: conversation. Type: on_chat_model_start. Name: ChatGoogleGenerativeAI\n", + "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n", + "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n", + "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n", + "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n", + "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n", + "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n", + "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n", + "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n", + "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n", + "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n", + "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n", + "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n", + "Node: conversation. Type: on_chat_model_stream. Name: ChatGoogleGenerativeAI\n", + "Node: conversation. Type: on_chat_model_end. Name: ChatGoogleGenerativeAI\n", + "Node: conversation. Type: on_chain_start. Name: _write\n", + "Node: conversation. Type: on_chain_end. Name: _write\n", + "Node: conversation. Type: on_chain_start. Name: should_continue\n", + "Node: conversation. Type: on_chain_end. Name: should_continue\n", + "Node: conversation. Type: on_chain_stream. Name: conversation\n", + "Node: conversation. Type: on_chain_end. Name: conversation\n", + "Node: . Type: on_chain_stream. Name: LangGraph\n", + "Node: . Type: on_chain_end. Name: LangGraph\n" + ] + } + ], + "source": [ + "config = {\"configurable\": {\"thread_id\": \"3\"}}\n", + "input_message = HumanMessage(content=\"Tell me about the 49ers NFL team\")\n", + "async for event in graph.astream_events({\"messages\": [input_message]}, config, version=\"v2\"):\n", + " print(f\"Node: {event['metadata'].get('langgraph_node','')}. Type: {event['event']}. Name: {event['name']}\")" + ] + }, + { + "cell_type": "markdown", + "id": "0b63490f-3d24-4f68-95ca-5320ccb61d2d", + "metadata": {}, + "source": [ + "The central point is that tokens from chat models within your graph have the `on_chat_model_stream` type.\n", + "\n", + "We can use `event['metadata']['langgraph_node']` to select the node to stream from.\n", + "\n", + "And we can use `event['data']` to get the actual data for each event, which in this case is an `AIMessageChunk`. " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "cc3529f8-3960-4d41-9ed6-373f93183950", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'chunk': AIMessageChunk(content='The', additional_kwargs={}, response_metadata={'safety_ratings': []}, id='run-aea9eb2e-7a54-4859-a38f-13fd5c423300', usage_metadata={'input_tokens': 11, 'output_tokens': 0, 'total_tokens': 11, 'input_token_details': {'cache_read': 0}})}\n", + "{'chunk': AIMessageChunk(content=' San Francisco 49ers are a professional American football team based in the San', additional_kwargs={}, response_metadata={'safety_ratings': []}, id='run-aea9eb2e-7a54-4859-a38f-13fd5c423300', usage_metadata={'input_tokens': 0, 'output_tokens': 0, 'total_tokens': 0, 'input_token_details': {'cache_read': 0}})}\n", + "{'chunk': AIMessageChunk(content=' Francisco Bay Area. They compete in the National Football League (NFL) as', additional_kwargs={}, response_metadata={'safety_ratings': []}, id='run-aea9eb2e-7a54-4859-a38f-13fd5c423300', usage_metadata={'input_tokens': 0, 'output_tokens': 0, 'total_tokens': 0, 'input_token_details': {'cache_read': 0}})}\n", + "{'chunk': AIMessageChunk(content=\" a member club of the National Football Conference (NFC) West division. Here's a summary of key information about the team:\\n\\n**History:**\\n\\n\", additional_kwargs={}, response_metadata={'safety_ratings': []}, id='run-aea9eb2e-7a54-4859-a38f-13fd5c423300', usage_metadata={'input_tokens': 0, 'output_tokens': 0, 'total_tokens': 0, 'input_token_details': {'cache_read': 0}})}\n", + "{'chunk': AIMessageChunk(content='* **Founded:** 1946 (as a charter member of the All-American Football Conference (AAFC))\\n* **Joined NFL:** ', additional_kwargs={}, response_metadata={'safety_ratings': []}, id='run-aea9eb2e-7a54-4859-a38f-13fd5c423300', usage_metadata={'input_tokens': 0, 'output_tokens': 0, 'total_tokens': 0, 'input_token_details': {'cache_read': 0}})}\n", + "{'chunk': AIMessageChunk(content='1950 (after the AAFC merged with the NFL)\\n* **Dynasty Years:** The 49ers experienced periods of sustained success, most notably in the 1980s and 1990', additional_kwargs={}, response_metadata={'safety_ratings': []}, id='run-aea9eb2e-7a54-4859-a38f-13fd5c423300', usage_metadata={'input_tokens': 0, 'output_tokens': 0, 'total_tokens': 0, 'input_token_details': {'cache_read': 0}})}\n", + "{'chunk': AIMessageChunk(content=\"s, winning five Super Bowls (XVI, XIX, XXIII, XXIV, XXIX).\\n* **Recent Success:** They've seen a resurgence in recent years, reaching Super Bowl XLVII (losing to the Baltimore Ravens) and Super\", additional_kwargs={}, response_metadata={'safety_ratings': []}, id='run-aea9eb2e-7a54-4859-a38f-13fd5c423300', usage_metadata={'input_tokens': 0, 'output_tokens': 0, 'total_tokens': 0, 'input_token_details': {'cache_read': 0}})}\n", + "{'chunk': AIMessageChunk(content=' Bowl LIV (losing to the Kansas City Chiefs). They also made the NFC Championship game in 2021 and 2022.\\n\\n**Key Figures (Past & Present):**\\n\\n* **Owners:** York family (Denise York is the principal owner)\\n* **Head Coach:** Kyle', additional_kwargs={}, response_metadata={'safety_ratings': []}, id='run-aea9eb2e-7a54-4859-a38f-13fd5c423300', usage_metadata={'input_tokens': 0, 'output_tokens': 0, 'total_tokens': 0, 'input_token_details': {'cache_read': 0}})}\n", + "{'chunk': AIMessageChunk(content=\" Shanahan\\n* **Notable Players (Past):** Joe Montana, Jerry Rice, Steve Young, Ronnie Lott, Charles Haley\\n* **Notable Players (Present):** Christian McCaffrey, George Kittle, Nick Bosa, Trent Williams, Brock Purdy\\n\\n**Home Stadium:**\\n\\n* Levi's Stadium (\", additional_kwargs={}, response_metadata={'safety_ratings': []}, id='run-aea9eb2e-7a54-4859-a38f-13fd5c423300', usage_metadata={'input_tokens': 0, 'output_tokens': 0, 'total_tokens': 0, 'input_token_details': {'cache_read': 0}})}\n", + "{'chunk': AIMessageChunk(content='Santa Clara, California)\\n\\n**Team Colors:**\\n\\n* Red, gold, and white\\n\\n**Rivalries:**\\n\\n* **Los Angeles Rams:** A fierce divisional rivalry with a long history.\\n* **Dallas Cowboys:** Historically one of the biggest rivalries in the NFL, stemming from multiple playoff matchups in', additional_kwargs={}, response_metadata={'safety_ratings': []}, id='run-aea9eb2e-7a54-4859-a38f-13fd5c423300', usage_metadata={'input_tokens': 0, 'output_tokens': 0, 'total_tokens': 0, 'input_token_details': {'cache_read': 0}})}\n", + "{'chunk': AIMessageChunk(content=' the 1980s and 1990s.\\n* **Seattle Seahawks:** A more recent but intense rivalry, also within the NFC West.\\n\\n**Playing Style:**\\n\\nThe 49ers are traditionally known for a strong running game and a tough defense. Under Kyle Shanahan', additional_kwargs={}, response_metadata={'safety_ratings': []}, id='run-aea9eb2e-7a54-4859-a38f-13fd5c423300', usage_metadata={'input_tokens': 0, 'output_tokens': 0, 'total_tokens': 0, 'input_token_details': {'cache_read': 0}})}\n", + "{'chunk': AIMessageChunk(content=\", they've emphasized a creative and versatile offensive scheme that utilizes play-action passing and misdirection.\\n\\n**Overall:**\\n\\nThe San Francisco 49ers are a storied franchise with a rich history of success. They remain a competitive team in the NFL and boast a passionate fan base.\\n\", additional_kwargs={}, response_metadata={'finish_reason': 'STOP', 'safety_ratings': []}, id='run-aea9eb2e-7a54-4859-a38f-13fd5c423300', usage_metadata={'input_tokens': 0, 'output_tokens': 510, 'total_tokens': 510, 'input_token_details': {'cache_read': 0}})}\n" + ] + } + ], + "source": [ + "node_to_stream = 'conversation'\n", + "config = {\"configurable\": {\"thread_id\": \"4\"}}\n", + "input_message = HumanMessage(content=\"Tell me about the 49ers NFL team\")\n", + "async for event in graph.astream_events({\"messages\": [input_message]}, config, version=\"v2\"):\n", + " # Get chat model tokens from a particular node \n", + " if event[\"event\"] == \"on_chat_model_stream\" and event['metadata'].get('langgraph_node','') == node_to_stream:\n", + " print(event[\"data\"])" + ] + }, + { + "cell_type": "markdown", + "id": "226e569a-76c3-43d8-8f89-3ae687efde1c", + "metadata": {}, + "source": [ + "As you see above, just use the `chunk` key to get the `AIMessageChunk`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3aeae53d-6dcf-40d0-a0c6-c40de492cc83", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The| San Francisco 49ers are a professional American football team based in the San| Francisco Bay Area. They compete in the National Football League (NFL) as| a member club of the National Football Conference (NFC) West division. Here's a summary of key information about the team:\n", + "\n", + "* **Founded:**| 1946 (as a charter member of the All-America Football Conference)\n", + "* **Joined NFL:** 1950\n", + "* **|Home Stadium:** Levi's Stadium (Santa Clara, California)\n", + "* **Team Colors:** Red, gold, and white\n", + "* **Mascot:** Sourdough Sam\n", + "* **Owner:** Jed York\n", + "* **Head Coach:** Kyle| Shanahan\n", + "* **Notable Players (Past & Present):** Joe Montana, Jerry Rice, Steve Young, Ronnie Lott, Charles Haley, Patrick Willis, Frank Gore, Justin Smith, NaVorro Bowman, George Kittle, Nick B|osa, Christian McCaffrey\n", + "\n", + "**Key Achievements:**\n", + "\n", + "* **Super Bowl Championships (5):** XVI (1982), XIX (1985), XXIII (1989), XXIV (1990), XXIX (1995)\n", + "* **NFC Championships (7):**| 1981, 1984, 1988, 1989, 1994, 2012, 2019\n", + "* **Numerous Division Titles**\n", + "\n", + "**Team Identity and Style:**\n", + "\n", + "The 49ers are historically known| for their West Coast offense, a quick-passing game emphasizing short, high-percentage throws. They've also traditionally boasted strong defenses, particularly during their dominant periods in the 1980s and 1990s. In recent years, they've returned to prominence with a balanced| attack featuring a strong running game and a formidable defense.\n", + "\n", + "**Current Status:**\n", + "\n", + "The 49ers are consistently considered one of the top contenders in the NFC and are frequently in the playoff hunt. They have a dedicated fan base and a rich history, making them one of the most popular teams in the NFL|.\n", + "\n", + "**Rivalries:**\n", + "\n", + "The 49ers have several key rivalries, most notably with:\n", + "\n", + "* **Los Angeles Rams:** A long-standing divisional rivalry with intense games and a history of close matchups.\n", + "* **Seattle Seahawks:** Another NFC West rivalry that has become increasingly heated in recent years.|\n", + "* **Dallas Cowboys:** A historic rivalry stemming from multiple playoff meetings in the 1970s, 80s, and 90s, often with championship implications.\n", + "\n", + "\n", + "This information provides a good overview of the San Francisco 49ers. You can find more detailed information on| their official website and various sports news outlets.\n", + "|" + ] + } + ], + "source": [ + "config = {\"configurable\": {\"thread_id\": \"5\"}}\n", + "input_message = HumanMessage(content=\"Tell me about the 49ers NFL team\")\n", + "async for event in graph.astream_events({\"messages\": [input_message]}, config, version=\"v2\"):\n", + " # Get chat model tokens from a particular node \n", + " if event[\"event\"] == \"on_chat_model_stream\" and event['metadata'].get('langgraph_node','') == node_to_stream:\n", + " data = event[\"data\"]\n", + " print(data[\"chunk\"].content, end=\"|\")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "5826e4d8-846b-4f6c-a5c1-e781d43022db", + "metadata": {}, + "source": [ + "### Streaming with LangGraph API\n", + "\n", + "--\n", + "\n", + "**⚠️ DISCLAIMER**\n", + "\n", + "*Running Studio currently requires a Mac. If you are not using a Mac, then skip this step.*\n", + "\n", + "*Also, if you are running this notebook in CoLab, then skip this step.*\n", + "\n", + "--\n", + "\n", + "The LangGraph API [has first class support for streaming](https://langchain-ai.github.io/langgraph/cloud/concepts/api/#streaming). \n", + "\n", + "Let's load our `agent` in the Studio UI, which uses `module-3/studio/agent.py` set in `module-3/studio/langgraph.json`.\n", + "\n", + "The LangGraph API serves as the back-end for Studio.\n", + "\n", + "We can interact directly with the LangGraph API via the LangGraph SDK.\n", + "\n", + "We just need to get the URL for the local deployment from Studio.\n", + "\n", + "![Screenshot 2024-08-27 at 2.20.34 PM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbaf8943c3d4df239cbf0f_streaming2.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8925b632-512b-48e1-9220-61c06bfbf0b8", + "metadata": {}, + "outputs": [], + "source": [ + "import platform\n", + "\n", + "if 'google.colab' in str(get_ipython()) or platform.system() != 'Darwin':\n", + " raise Exception(\"Unfortunately LangGraph Studio is currently not supported on Google Colab or requires a Mac\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "079c2ad6", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph_sdk import get_client\n", + "\n", + "# Replace this with the URL of your own deployed graph\n", + "URL = \"http://localhost:56091\"\n", + "client = get_client(url=URL)\n", + "\n", + "# Search all hosted graphs\n", + "assistants = await client.assistants.search()" + ] + }, + { + "cell_type": "markdown", + "id": "4d15af9e-0e86-41e3-a5ba-ee2a4aa08a32", + "metadata": {}, + "source": [ + "Let's [stream `values`](https://langchain-ai.github.io/langgraph/cloud/how-tos/stream_values/), like before." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "63e3096f-5429-4d3c-8de2-2bddf7266ebf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "StreamPart(event='metadata', data={'run_id': '1ef6a3d0-41eb-66f4-a311-8ebdfa1b281f'})\n", + "StreamPart(event='values', data={'messages': [{'content': 'Multiply 2 and 3', 'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '345c67cf-c958-4f89-b787-540fc025080c', 'example': False}]})\n", + "StreamPart(event='values', data={'messages': [{'content': 'Multiply 2 and 3', 'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '345c67cf-c958-4f89-b787-540fc025080c', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'function': {'arguments': '{\"a\":2,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-88179a6d-eb1e-4953-ac42-0b533b6d76f6', 'example': False, 'tool_calls': [{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': None}]})\n", + "StreamPart(event='values', data={'messages': [{'content': 'Multiply 2 and 3', 'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '345c67cf-c958-4f89-b787-540fc025080c', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'function': {'arguments': '{\"a\":2,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-88179a6d-eb1e-4953-ac42-0b533b6d76f6', 'example': False, 'tool_calls': [{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': '6', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'multiply', 'id': '4dd5ce10-ac0b-4a91-b34b-c35109dcbf29', 'tool_call_id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'artifact': None, 'status': 'success'}]})\n", + "StreamPart(event='values', data={'messages': [{'content': 'Multiply 2 and 3', 'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '345c67cf-c958-4f89-b787-540fc025080c', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'function': {'arguments': '{\"a\":2,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-88179a6d-eb1e-4953-ac42-0b533b6d76f6', 'example': False, 'tool_calls': [{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': '6', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'multiply', 'id': '4dd5ce10-ac0b-4a91-b34b-c35109dcbf29', 'tool_call_id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'artifact': None, 'status': 'success'}, {'content': 'The result of multiplying 2 and 3 is 6.', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-b5862486-a25f-48fc-9a03-a8506a6692a8', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]})\n" + ] + } + ], + "source": [ + "# Create a new thread\n", + "thread = await client.threads.create()\n", + "# Input message\n", + "input_message = HumanMessage(content=\"Multiply 2 and 3\")\n", + "async for event in client.runs.stream(thread[\"thread_id\"], \n", + " assistant_id=\"agent\", \n", + " input={\"messages\": [input_message]}, \n", + " stream_mode=\"values\"):\n", + " print(event)" + ] + }, + { + "cell_type": "markdown", + "id": "556dc7fd-1cae-404f-816a-f13d772b3b14", + "metadata": {}, + "source": [ + "The streamed objects have: \n", + "\n", + "* `event`: Type\n", + "* `data`: State" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "57b735aa-139c-45a3-a850-63519c0004f0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "=========================\n", + "content='Multiply 2 and 3' additional_kwargs={'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'example': False} id='f51807de-6b99-4da4-a798-26cf59d16412'\n", + "=========================\n", + "content='' additional_kwargs={'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_imZHAw7kvMR2ZeKaQVSlj25C', 'function': {'arguments': '{\"a\":2,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'example': False, 'invalid_tool_calls': [], 'usage_metadata': None} id='run-fa4ab1c6-274d-4be5-8c4a-a6411c7c35cc' tool_calls=[{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_imZHAw7kvMR2ZeKaQVSlj25C', 'type': 'tool_call'}]\n", + "=========================\n", + "content='6' additional_kwargs={'additional_kwargs': {}, 'response_metadata': {}, 'status': 'success'} name='multiply' id='3e7bbfb6-aa82-453a-969c-9c753fbd1d74' tool_call_id='call_imZHAw7kvMR2ZeKaQVSlj25C'\n", + "=========================\n", + "content='The result of multiplying 2 and 3 is 6.' additional_kwargs={'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'example': False, 'invalid_tool_calls': [], 'usage_metadata': None} id='run-e8e0d672-cfb2-42be-850a-345df3718f69'\n", + "=========================\n" + ] + } + ], + "source": [ + "from langchain_core.messages import convert_to_messages\n", + "thread = await client.threads.create()\n", + "input_message = HumanMessage(content=\"Multiply 2 and 3\")\n", + "async for event in client.runs.stream(thread[\"thread_id\"], assistant_id=\"agent\", input={\"messages\": [input_message]}, stream_mode=\"values\"):\n", + " messages = event.data.get('messages',None)\n", + " if messages:\n", + " print(convert_to_messages(messages)[-1])\n", + " print('='*25)" + ] + }, + { + "cell_type": "markdown", + "id": "a555d186-27be-4ddf-934c-895a3105035d", + "metadata": {}, + "source": [ + "There are some new streaming mode that are only supported via the API.\n", + "\n", + "For example, we can [use `messages` mode](https://langchain-ai.github.io/langgraph/cloud/how-tos/stream_messages/) to better handle the above case!\n", + "\n", + "This mode currently assumes that you have a `messages` key in your graph, which is a list of messages.\n", + "\n", + "All events emitted using `messages` mode have two attributes:\n", + "\n", + "* `event`: This is the name of the event\n", + "* `data`: This is data associated with the event" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "4abd91f6-63c0-41ee-9988-7c8248b88a45", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "metadata\n", + "messages/complete\n", + "messages/metadata\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/complete\n", + "messages/complete\n", + "messages/metadata\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/partial\n", + "messages/complete\n" + ] + } + ], + "source": [ + "thread = await client.threads.create()\n", + "input_message = HumanMessage(content=\"Multiply 2 and 3\")\n", + "async for event in client.runs.stream(thread[\"thread_id\"], \n", + " assistant_id=\"agent\", \n", + " input={\"messages\": [input_message]}, \n", + " stream_mode=\"messages\"):\n", + " print(event.event)" + ] + }, + { + "cell_type": "markdown", + "id": "8de2f1ea-b232-43fc-af7a-320efce83381", + "metadata": {}, + "source": [ + "We can see a few events: \n", + "\n", + "* `metadata`: metadata about the run\n", + "* `messages/complete`: fully formed message \n", + "* `messages/partial`: chat model tokens\n", + "\n", + "You can dig further into the types [here](https://langchain-ai.github.io/langgraph/cloud/concepts/api/#modemessages).\n", + "\n", + "Now, let's show how to stream these messages. \n", + "\n", + "We'll define a helper function for better formatting of the tool calls in messages." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "50a85e16-6e3f-4f14-bcf9-8889a762f522", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Metadata: Run ID - 1ef6a3da-687f-6253-915a-701de5327165\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2, 'b': 3}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2, 'b': 3}\n", + "--------------------------------------------------\n", + "Tool Calls:\n", + "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2, 'b': 3}\n", + "Response Metadata: Finish Reason - tool_calls\n", + "--------------------------------------------------\n", + "--------------------------------------------------\n", + "AI: The\n", + "--------------------------------------------------\n", + "AI: The result\n", + "--------------------------------------------------\n", + "AI: The result of\n", + "--------------------------------------------------\n", + "AI: The result of multiplying\n", + "--------------------------------------------------\n", + "AI: The result of multiplying \n", + "--------------------------------------------------\n", + "AI: The result of multiplying 2\n", + "--------------------------------------------------\n", + "AI: The result of multiplying 2 and\n", + "--------------------------------------------------\n", + "AI: The result of multiplying 2 and \n", + "--------------------------------------------------\n", + "AI: The result of multiplying 2 and 3\n", + "--------------------------------------------------\n", + "AI: The result of multiplying 2 and 3 is\n", + "--------------------------------------------------\n", + "AI: The result of multiplying 2 and 3 is \n", + "--------------------------------------------------\n", + "AI: The result of multiplying 2 and 3 is 6\n", + "--------------------------------------------------\n", + "AI: The result of multiplying 2 and 3 is 6.\n", + "--------------------------------------------------\n", + "AI: The result of multiplying 2 and 3 is 6.\n", + "Response Metadata: Finish Reason - stop\n", + "--------------------------------------------------\n" + ] + } + ], + "source": [ + "thread = await client.threads.create()\n", + "input_message = HumanMessage(content=\"Multiply 2 and 3\")\n", + "\n", + "def format_tool_calls(tool_calls):\n", + " \"\"\"\n", + " Format a list of tool calls into a readable string.\n", + "\n", + " Args:\n", + " tool_calls (list): A list of dictionaries, each representing a tool call.\n", + " Each dictionary should have 'id', 'name', and 'args' keys.\n", + "\n", + " Returns:\n", + " str: A formatted string of tool calls, or \"No tool calls\" if the list is empty.\n", + "\n", + " \"\"\"\n", + "\n", + " if tool_calls:\n", + " formatted_calls = []\n", + " for call in tool_calls:\n", + " formatted_calls.append(\n", + " f\"Tool Call ID: {call['id']}, Function: {call['name']}, Arguments: {call['args']}\"\n", + " )\n", + " return \"\\n\".join(formatted_calls)\n", + " return \"No tool calls\"\n", + "\n", + "async for event in client.runs.stream(\n", + " thread[\"thread_id\"],\n", + " assistant_id=\"agent\",\n", + " input={\"messages\": [input_message]},\n", + " stream_mode=\"messages\",):\n", + " \n", + " # Handle metadata events\n", + " if event.event == \"metadata\":\n", + " print(f\"Metadata: Run ID - {event.data['run_id']}\")\n", + " print(\"-\" * 50)\n", + " \n", + " # Handle partial message events\n", + " elif event.event == \"messages/partial\":\n", + " for data_item in event.data:\n", + " # Process user messages\n", + " if \"role\" in data_item and data_item[\"role\"] == \"user\":\n", + " print(f\"Human: {data_item['content']}\")\n", + " else:\n", + " # Extract relevant data from the event\n", + " tool_calls = data_item.get(\"tool_calls\", [])\n", + " invalid_tool_calls = data_item.get(\"invalid_tool_calls\", [])\n", + " content = data_item.get(\"content\", \"\")\n", + " response_metadata = data_item.get(\"response_metadata\", {})\n", + "\n", + " if content:\n", + " print(f\"AI: {content}\")\n", + "\n", + " if tool_calls:\n", + " print(\"Tool Calls:\")\n", + " print(format_tool_calls(tool_calls))\n", + "\n", + " if invalid_tool_calls:\n", + " print(\"Invalid Tool Calls:\")\n", + " print(format_tool_calls(invalid_tool_calls))\n", + "\n", + " if response_metadata:\n", + " finish_reason = response_metadata.get(\"finish_reason\", \"N/A\")\n", + " print(f\"Response Metadata: Finish Reason - {finish_reason}\")\n", + " \n", + " print(\"-\" * 50)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1ae885f8-102f-448a-9d68-8ded8d2bbd18", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "lc-academy", + "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.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/module-3/studio/.env.example b/langchain-academy/module-3/studio/.env.example similarity index 100% rename from module-3/studio/.env.example rename to langchain-academy/module-3/studio/.env.example diff --git a/module-3/studio/agent.py b/langchain-academy/module-3/studio/agent.py similarity index 100% rename from module-3/studio/agent.py rename to langchain-academy/module-3/studio/agent.py diff --git a/module-3/studio/dynamic_breakpoints.py b/langchain-academy/module-3/studio/dynamic_breakpoints.py similarity index 100% rename from module-3/studio/dynamic_breakpoints.py rename to langchain-academy/module-3/studio/dynamic_breakpoints.py diff --git a/module-3/studio/langgraph.json b/langchain-academy/module-3/studio/langgraph.json similarity index 100% rename from module-3/studio/langgraph.json rename to langchain-academy/module-3/studio/langgraph.json diff --git a/module-3/studio/requirements.txt b/langchain-academy/module-3/studio/requirements.txt similarity index 100% rename from module-3/studio/requirements.txt rename to langchain-academy/module-3/studio/requirements.txt diff --git a/module-3/time-travel.ipynb b/langchain-academy/module-3/time-travel.ipynb similarity index 67% rename from module-3/time-travel.ipynb rename to langchain-academy/module-3/time-travel.ipynb index 83f28189c..c0c456bc7 100644 --- a/module-3/time-travel.ipynb +++ b/langchain-academy/module-3/time-travel.ipynb @@ -54,13 +54,34 @@ "metadata": {}, "outputs": [], "source": [ - "import os, getpass\n", + "# import os, getpass\n", "\n", - "def _set_env(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "# def _set_env(var: str):\n", + "# if not os.environ.get(var):\n", + "# os.environ[var] = getpass.getpass(f\"{var}: \")\n", "\n", - "_set_env(\"OPENAI_API_KEY\")" + "# _set_env(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "397bfeb5", + "metadata": {}, + "outputs": [], + "source": [ + "# load environment variables\n", + "from dotenv import load_dotenv\n", + "import os\n", + "\n", + "load_dotenv(dotenv_path='C:\\MIConsulting\\LangChain\\langchain-academy\\.env')\n", + "\n", + "# OPENAI_API_KEY = os.getenv(\"OPENAI_API_KEY\")\n", + "GOOGLEAI_API_KEY = os.getenv(\"GOOGLEAI_API_KEY\")\n", + "LANGCHAIN_API_KEY = os.getenv('LANGCHAIN_API_KEY')\n", + "LANGCHAIN_ENDPOINT = os.getenv('LANGCHAIN_ENDPOINT')\n", + "LANGCHAIN_TRACING_V2 = os.getenv('LANGCHAIN_TRACING_V2')\n", + "LANGCHAIN_PROJECT = os.getenv('LANGCHAIN_PROJECT')" ] }, { @@ -73,12 +94,13 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "d64ab3a1-b39c-4176-88c7-791a0b80c725", "metadata": {}, "outputs": [], "source": [ - "from langchain_openai import ChatOpenAI\n", + "# from langchain_openai import ChatOpenAI\n", + "from langchain_google_genai import ChatGoogleGenerativeAI\n", "\n", "def multiply(a: int, b: int) -> int:\n", " \"\"\"Multiply a and b.\n", @@ -109,19 +131,20 @@ " return a / b\n", "\n", "tools = [add, multiply, divide]\n", - "llm = ChatOpenAI(model=\"gpt-4o\")\n", + "# llm = ChatOpenAI(model=\"gpt-4o\")\n", + "llm = ChatGoogleGenerativeAI(api_key=GOOGLEAI_API_KEY, model=\"gemini-1.5-pro\")\n", "llm_with_tools = llm.bind_tools(tools)" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "1d8622a9-57cd-44dc-8696-46c5ab32d0b9", "metadata": {}, "outputs": [ { "data": { - "image/jpeg": "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", + "image/png": "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", "text/plain": [ "" ] @@ -181,7 +204,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "05b2ab62-82bc-4356-8d5b-2d4f49069fdd", "metadata": {}, "outputs": [ @@ -194,18 +217,18 @@ "Multiply 2 and 3\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", - " multiply (call_ikJxMpb777bKMYgmM3d9mYjW)\n", - " Call ID: call_ikJxMpb777bKMYgmM3d9mYjW\n", + " multiply (81045b3f-5780-4faf-8972-a953233060f0)\n", + " Call ID: 81045b3f-5780-4faf-8972-a953233060f0\n", " Args:\n", - " a: 2\n", - " b: 3\n", + " a: 2.0\n", + " b: 3.0\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", "Name: multiply\n", "\n", "6\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "The result of multiplying 2 and 3 is 6.\n" + "6\n" ] } ], @@ -233,17 +256,17 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "161eb053-18f6-4c99-8674-8cbd11cae57e", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "StateSnapshot(values={'messages': [HumanMessage(content='Multiply 2 and 3', id='4ee8c440-0e4a-47d7-852f-06e2a6c4f84d'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_ikJxMpb777bKMYgmM3d9mYjW', 'function': {'arguments': '{\"a\":2,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 131, 'total_tokens': 148}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-bc24d334-8013-4f85-826f-e1ed69c86df0-0', tool_calls=[{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_ikJxMpb777bKMYgmM3d9mYjW', 'type': 'tool_call'}], usage_metadata={'input_tokens': 131, 'output_tokens': 17, 'total_tokens': 148}), ToolMessage(content='6', name='multiply', id='1012611a-30c5-4732-b789-8c455580c7b4', tool_call_id='call_ikJxMpb777bKMYgmM3d9mYjW'), AIMessage(content='The result of multiplying 2 and 3 is 6.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 156, 'total_tokens': 170}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5', 'finish_reason': 'stop', 'logprobs': None}, id='run-b46f3fed-ca3b-4e09-83f4-77ea5071e9bf-0', usage_metadata={'input_tokens': 156, 'output_tokens': 14, 'total_tokens': 170})]}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a440-ac9e-6024-8003-6fd8435c1d3b'}}, metadata={'source': 'loop', 'writes': {'assistant': {'messages': [AIMessage(content='The result of multiplying 2 and 3 is 6.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 156, 'total_tokens': 170}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5', 'finish_reason': 'stop', 'logprobs': None}, id='run-b46f3fed-ca3b-4e09-83f4-77ea5071e9bf-0', usage_metadata={'input_tokens': 156, 'output_tokens': 14, 'total_tokens': 170})]}}, 'step': 3, 'parents': {}}, created_at='2024-09-03T22:29:54.309727+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a440-a759-6d02-8002-f1da6393e1ab'}}, tasks=())" + "StateSnapshot(values={'messages': [HumanMessage(content='Multiply 2 and 3', additional_kwargs={}, response_metadata={}, id='ad7ae38d-c5ea-4367-a135-ce22e374869f'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'multiply', 'arguments': '{\"a\": 2.0, \"b\": 3.0}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-282ce997-be97-4597-afed-c9c9fcc16ae0-0', tool_calls=[{'name': 'multiply', 'args': {'a': 2.0, 'b': 3.0}, 'id': '81045b3f-5780-4faf-8972-a953233060f0', 'type': 'tool_call'}], usage_metadata={'input_tokens': 166, 'output_tokens': 3, 'total_tokens': 169, 'input_token_details': {'cache_read': 0}}), ToolMessage(content='6', name='multiply', id='4536ff51-90c1-4a85-9f71-9c90a036ed87', tool_call_id='81045b3f-5780-4faf-8972-a953233060f0'), AIMessage(content='6\\n', additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-6c2f6a0d-e540-460a-a293-f06444f58247-0', usage_metadata={'input_tokens': 199, 'output_tokens': 2, 'total_tokens': 201, 'input_token_details': {'cache_read': 0}})]}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efceadf-b57b-699b-8003-d81dfa4ab930'}}, metadata={'source': 'loop', 'writes': {'assistant': {'messages': [AIMessage(content='6\\n', additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-6c2f6a0d-e540-460a-a293-f06444f58247-0', usage_metadata={'input_tokens': 199, 'output_tokens': 2, 'total_tokens': 201, 'input_token_details': {'cache_read': 0}})]}}, 'step': 3, 'parents': {}}, created_at='2025-01-09T17:20:11.326914+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efceadf-af4d-6529-8002-7bb96944a42d'}}, tasks=())" ] }, - "execution_count": 4, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -264,7 +287,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "3010169c-3bfa-498c-a30c-7ba53744e4d5", "metadata": {}, "outputs": [], @@ -274,7 +297,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "id": "c4612ccf-59fc-4848-8845-0433fee2ca8e", "metadata": {}, "outputs": [ @@ -284,7 +307,7 @@ "5" ] }, - "execution_count": 6, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -303,17 +326,17 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 10, "id": "4e60b292-8efc-4cc3-b836-51f060fa608b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "StateSnapshot(values={'messages': [HumanMessage(content='Multiply 2 and 3', id='4ee8c440-0e4a-47d7-852f-06e2a6c4f84d')]}, next=('assistant',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a440-a003-6c74-8000-8a2d82b0d126'}}, metadata={'source': 'loop', 'writes': None, 'step': 0, 'parents': {}}, created_at='2024-09-03T22:29:52.988265+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a440-9ffe-6512-bfff-9e6d8dc24bba'}}, tasks=(PregelTask(id='ca669906-0c4f-5165-840d-7a6a3fce9fb9', name='assistant', error=None, interrupts=(), state=None),))" + "StateSnapshot(values={'messages': [HumanMessage(content='Multiply 2 and 3', additional_kwargs={}, response_metadata={}, id='ad7ae38d-c5ea-4367-a135-ce22e374869f')]}, next=('assistant',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efceadf-a1aa-697d-8000-dea3c151a871'}}, metadata={'source': 'loop', 'writes': None, 'step': 0, 'parents': {}}, created_at='2025-01-09T17:20:09.249010+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efceadf-a1a3-645e-bfff-d377f53f38d9'}}, tasks=(PregelTask(id='e32085c4-a268-b383-ef7b-c7854acb9c2d', name='assistant', path=('__pregel_pull', 'assistant'), error=None, interrupts=(), state=None),))" ] }, - "execution_count": 7, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -356,7 +379,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 11, "id": "3688e511-a440-4330-a450-e5ed889c3b30", "metadata": {}, "outputs": [], @@ -366,17 +389,17 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 12, "id": "72adf296-d519-4bdc-af03-3b29799e9534", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "StateSnapshot(values={'messages': [HumanMessage(content='Multiply 2 and 3', id='4ee8c440-0e4a-47d7-852f-06e2a6c4f84d')]}, next=('assistant',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a440-a003-6c74-8000-8a2d82b0d126'}}, metadata={'source': 'loop', 'writes': None, 'step': 0, 'parents': {}}, created_at='2024-09-03T22:29:52.988265+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a440-9ffe-6512-bfff-9e6d8dc24bba'}}, tasks=(PregelTask(id='ca669906-0c4f-5165-840d-7a6a3fce9fb9', name='assistant', error=None, interrupts=(), state=None),))" + "StateSnapshot(values={'messages': [HumanMessage(content='Multiply 2 and 3', additional_kwargs={}, response_metadata={}, id='ad7ae38d-c5ea-4367-a135-ce22e374869f')]}, next=('assistant',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efceadf-a1aa-697d-8000-dea3c151a871'}}, metadata={'source': 'loop', 'writes': None, 'step': 0, 'parents': {}}, created_at='2025-01-09T17:20:09.249010+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efceadf-a1a3-645e-bfff-d377f53f38d9'}}, tasks=(PregelTask(id='e32085c4-a268-b383-ef7b-c7854acb9c2d', name='assistant', path=('__pregel_pull', 'assistant'), error=None, interrupts=(), state=None),))" ] }, - "execution_count": 9, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -395,17 +418,17 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 14, "id": "6fe69428-f364-4330-bf5d-aa966c7f3b07", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'messages': [HumanMessage(content='Multiply 2 and 3', id='4ee8c440-0e4a-47d7-852f-06e2a6c4f84d')]}" + "{'messages': [HumanMessage(content='Multiply 2 and 3', additional_kwargs={}, response_metadata={}, id='ad7ae38d-c5ea-4367-a135-ce22e374869f')]}" ] }, - "execution_count": 10, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -424,7 +447,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 16, "id": "d2f333f9-9b2b-46f6-ac3a-525f86b20f1b", "metadata": {}, "outputs": [ @@ -434,7 +457,7 @@ "('assistant',)" ] }, - "execution_count": 11, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -453,7 +476,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 17, "id": "b1298786-afa5-4277-927e-708a8629231b", "metadata": {}, "outputs": [ @@ -462,10 +485,10 @@ "text/plain": [ "{'configurable': {'thread_id': '1',\n", " 'checkpoint_ns': '',\n", - " 'checkpoint_id': '1ef6a440-a003-6c74-8000-8a2d82b0d126'}}" + " 'checkpoint_id': '1efceadf-a1aa-697d-8000-dea3c151a871'}}" ] }, - "execution_count": 12, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -488,7 +511,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 18, "id": "531b4cd1-54f6-44aa-9ffe-cf5403dad65d", "metadata": {}, "outputs": [ @@ -501,18 +524,18 @@ "Multiply 2 and 3\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", - " multiply (call_SABfB57CnDkMu9HJeUE0mvJ9)\n", - " Call ID: call_SABfB57CnDkMu9HJeUE0mvJ9\n", + " multiply (599462e6-cfab-4dd6-b393-17f66ea597d5)\n", + " Call ID: 599462e6-cfab-4dd6-b393-17f66ea597d5\n", " Args:\n", - " a: 2\n", - " b: 3\n", + " a: 2.0\n", + " b: 3.0\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", "Name: multiply\n", "\n", "6\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "The result of multiplying 2 and 3 is 6.\n" + "6\n" ] } ], @@ -546,17 +569,17 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 20, "id": "cdeb5bf2-1566-4d8c-8ea5-65894e3a7038", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[HumanMessage(content='Multiply 2 and 3', id='4ee8c440-0e4a-47d7-852f-06e2a6c4f84d')]" + "[HumanMessage(content='Multiply 2 and 3', additional_kwargs={}, response_metadata={}, id='ad7ae38d-c5ea-4367-a135-ce22e374869f')]" ] }, - "execution_count": 14, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -576,7 +599,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 22, "id": "d1621b27-ee51-4dc3-81c4-1d05317280db", "metadata": {}, "outputs": [ @@ -585,10 +608,10 @@ "text/plain": [ "{'configurable': {'thread_id': '1',\n", " 'checkpoint_ns': '',\n", - " 'checkpoint_id': '1ef6a440-a003-6c74-8000-8a2d82b0d126'}}" + " 'checkpoint_id': '1efceadf-a1aa-697d-8000-dea3c151a871'}}" ] }, - "execution_count": 15, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -616,7 +639,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 23, "id": "0b4a918d-858a-41ac-a5d4-e99260e2d6ec", "metadata": {}, "outputs": [], @@ -630,7 +653,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 24, "id": "8ff4e9bb-8221-42d1-b7d0-b0cbd5dc374a", "metadata": {}, "outputs": [ @@ -639,10 +662,10 @@ "text/plain": [ "{'configurable': {'thread_id': '1',\n", " 'checkpoint_ns': '',\n", - " 'checkpoint_id': '1ef6a442-3661-62f6-8001-d3c01b96f98b'}}" + " 'checkpoint_id': '1efceb20-09d7-6651-8001-704e8a34cf92'}}" ] }, - "execution_count": 17, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -665,17 +688,17 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 26, "id": "586ce86c-1257-45e9-ba30-6287932b9484", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[HumanMessage(content='Multiply 5 and 3', id='4ee8c440-0e4a-47d7-852f-06e2a6c4f84d')]" + "[HumanMessage(content='Multiply 5 and 3', additional_kwargs={}, response_metadata={}, id='ad7ae38d-c5ea-4367-a135-ce22e374869f')]" ] }, - "execution_count": 18, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -687,17 +710,17 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 27, "id": "12e19798-25d8-49e8-8542-13d2b3bdf58e", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "StateSnapshot(values={'messages': [HumanMessage(content='Multiply 5 and 3', id='4ee8c440-0e4a-47d7-852f-06e2a6c4f84d')]}, next=('assistant',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a442-3661-62f6-8001-d3c01b96f98b'}}, metadata={'source': 'update', 'step': 1, 'writes': {'__start__': {'messages': [HumanMessage(content='Multiply 5 and 3', id='4ee8c440-0e4a-47d7-852f-06e2a6c4f84d')]}}, 'parents': {}}, created_at='2024-09-03T22:30:35.598707+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a440-a003-6c74-8000-8a2d82b0d126'}}, tasks=(PregelTask(id='f8990132-a8d3-5ddd-8d9e-1efbfc220da1', name='assistant', error=None, interrupts=(), state=None),))" + "StateSnapshot(values={'messages': [HumanMessage(content='Multiply 5 and 3', additional_kwargs={}, response_metadata={}, id='ad7ae38d-c5ea-4367-a135-ce22e374869f')]}, next=('assistant',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efceb20-09d7-6651-8001-704e8a34cf92'}}, metadata={'source': 'update', 'step': 1, 'writes': {'__start__': {'messages': [HumanMessage(content='Multiply 5 and 3', additional_kwargs={}, response_metadata={}, id='ad7ae38d-c5ea-4367-a135-ce22e374869f')]}}, 'parents': {}}, created_at='2025-01-09T17:48:58.159470+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efceadf-a1aa-697d-8000-dea3c151a871'}}, tasks=(PregelTask(id='a7529420-f7ad-f4e1-fcdf-bd6cf6a796be', name='assistant', path=('__pregel_pull', 'assistant'), error=None, interrupts=(), state=None),))" ] }, - "execution_count": 19, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -718,7 +741,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 28, "id": "1c49f2a8-b325-45e4-b36c-17fab1b37cc0", "metadata": {}, "outputs": [ @@ -731,18 +754,18 @@ "Multiply 5 and 3\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "Tool Calls:\n", - " multiply (call_KP2CVNMMUKMJAQuFmamHB21r)\n", - " Call ID: call_KP2CVNMMUKMJAQuFmamHB21r\n", + " multiply (c3b3e0cc-5c10-47e1-b75c-86ad3ad2929a)\n", + " Call ID: c3b3e0cc-5c10-47e1-b75c-86ad3ad2929a\n", " Args:\n", - " a: 5\n", - " b: 3\n", + " a: 5.0\n", + " b: 3.0\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", "Name: multiply\n", "\n", "15\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "The result of multiplying 5 and 3 is 15.\n" + "The product of 5 and 3 is 15.\n" ] } ], @@ -761,17 +784,17 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 29, "id": "132ef840-64c7-479c-ad34-3f177f4b2524", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "StateSnapshot(values={'messages': [HumanMessage(content='Multiply 5 and 3', id='4ee8c440-0e4a-47d7-852f-06e2a6c4f84d'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_KP2CVNMMUKMJAQuFmamHB21r', 'function': {'arguments': '{\"a\":5,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 131, 'total_tokens': 148}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-bc420009-d1f6-49b8-bea7-dfc9fca7eb79-0', tool_calls=[{'name': 'multiply', 'args': {'a': 5, 'b': 3}, 'id': 'call_KP2CVNMMUKMJAQuFmamHB21r', 'type': 'tool_call'}], usage_metadata={'input_tokens': 131, 'output_tokens': 17, 'total_tokens': 148}), ToolMessage(content='15', name='multiply', id='9232e653-816d-471a-9002-9a1ecd453364', tool_call_id='call_KP2CVNMMUKMJAQuFmamHB21r'), AIMessage(content='The result of multiplying 5 and 3 is 15.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 156, 'total_tokens': 170}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5', 'finish_reason': 'stop', 'logprobs': None}, id='run-86c21888-d832-47c5-9e76-0aa2676116dc-0', usage_metadata={'input_tokens': 156, 'output_tokens': 14, 'total_tokens': 170})]}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a442-a2e2-6e98-8004-4a0b75537950'}}, metadata={'source': 'loop', 'writes': {'assistant': {'messages': [AIMessage(content='The result of multiplying 5 and 3 is 15.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 156, 'total_tokens': 170}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5', 'finish_reason': 'stop', 'logprobs': None}, id='run-86c21888-d832-47c5-9e76-0aa2676116dc-0', usage_metadata={'input_tokens': 156, 'output_tokens': 14, 'total_tokens': 170})]}}, 'step': 4, 'parents': {}}, created_at='2024-09-03T22:30:46.976463+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a442-9db0-6056-8003-7304cab7bed8'}}, tasks=())" + "StateSnapshot(values={'messages': [HumanMessage(content='Multiply 5 and 3', additional_kwargs={}, response_metadata={}, id='ad7ae38d-c5ea-4367-a135-ce22e374869f'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'multiply', 'arguments': '{\"a\": 5.0, \"b\": 3.0}'}}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-0812992c-b49c-42b9-bf12-bc860e659f99-0', tool_calls=[{'name': 'multiply', 'args': {'a': 5.0, 'b': 3.0}, 'id': 'c3b3e0cc-5c10-47e1-b75c-86ad3ad2929a', 'type': 'tool_call'}], usage_metadata={'input_tokens': 166, 'output_tokens': 3, 'total_tokens': 169, 'input_token_details': {'cache_read': 0}}), ToolMessage(content='15', name='multiply', id='7df36490-f266-4c59-a678-996b8a6aec80', tool_call_id='c3b3e0cc-5c10-47e1-b75c-86ad3ad2929a'), AIMessage(content='The product of 5 and 3 is 15.', additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-fdb550bb-33b5-4e2c-9c85-d81d2037e31a-0', usage_metadata={'input_tokens': 200, 'output_tokens': 13, 'total_tokens': 213, 'input_token_details': {'cache_read': 0}})]}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efceb22-269d-6ed1-8004-19e90d86c9c5'}}, metadata={'source': 'loop', 'writes': {'assistant': {'messages': [AIMessage(content='The product of 5 and 3 is 15.', additional_kwargs={}, response_metadata={'prompt_feedback': {'block_reason': 0, 'safety_ratings': []}, 'finish_reason': 'STOP', 'safety_ratings': []}, id='run-fdb550bb-33b5-4e2c-9c85-d81d2037e31a-0', usage_metadata={'input_tokens': 200, 'output_tokens': 13, 'total_tokens': 213, 'input_token_details': {'cache_read': 0}})]}}, 'step': 4, 'parents': {}}, created_at='2025-01-09T17:49:54.863892+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1efceb22-1ea7-67cd-8003-94e587cb86be'}}, tasks=())" ] }, - "execution_count": 21, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -841,23 +864,10 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "id": "9d4d01da-7b64-4c92-96b7-29ec93332d0b", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "--------------------Assistant Node--------------------\n", - "{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_SG7XYqDENGq7mwXrnioNLosS', 'function': {'arguments': '{\"a\":2,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-2c120fc3-3c82-4599-b8ec-24fbee207cad', 'example': False, 'tool_calls': [{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_SG7XYqDENGq7mwXrnioNLosS', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': None}\n", - "--------------------Tools Node--------------------\n", - "{'content': '6', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'multiply', 'id': '3b40d091-58b2-4566-a84c-60af67206307', 'tool_call_id': 'call_SG7XYqDENGq7mwXrnioNLosS', 'artifact': None, 'status': 'success'}\n", - "--------------------Assistant Node--------------------\n", - "{'content': 'The result of multiplying 2 and 3 is 6.', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_fde2829a40'}, 'type': 'ai', 'name': None, 'id': 'run-1272d9b0-a0aa-4ff7-8bad-fdffd27c5506', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}\n" - ] - } - ], + "outputs": [], "source": [ "initial_input = {\"messages\": HumanMessage(content=\"Multiply 2 and 3\")}\n", "thread = await client.threads.create()\n", @@ -1353,7 +1363,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "lc-academy", "language": "python", "name": "python3" }, @@ -1367,7 +1377,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.1" + "version": "3.11.10" } }, "nbformat": 4, diff --git a/module-4/map-reduce.ipynb b/langchain-academy/module-4/map-reduce.ipynb similarity index 100% rename from module-4/map-reduce.ipynb rename to langchain-academy/module-4/map-reduce.ipynb diff --git a/module-4/parallelization.ipynb b/langchain-academy/module-4/parallelization.ipynb similarity index 100% rename from module-4/parallelization.ipynb rename to langchain-academy/module-4/parallelization.ipynb diff --git a/module-4/research-assistant.ipynb b/langchain-academy/module-4/research-assistant.ipynb similarity index 100% rename from module-4/research-assistant.ipynb rename to langchain-academy/module-4/research-assistant.ipynb diff --git a/module-4/studio/.env.example b/langchain-academy/module-4/studio/.env.example similarity index 100% rename from module-4/studio/.env.example rename to langchain-academy/module-4/studio/.env.example diff --git a/module-4/studio/langgraph.json b/langchain-academy/module-4/studio/langgraph.json similarity index 100% rename from module-4/studio/langgraph.json rename to langchain-academy/module-4/studio/langgraph.json diff --git a/module-4/studio/map_reduce.py b/langchain-academy/module-4/studio/map_reduce.py similarity index 100% rename from module-4/studio/map_reduce.py rename to langchain-academy/module-4/studio/map_reduce.py diff --git a/module-4/studio/parallelization.py b/langchain-academy/module-4/studio/parallelization.py similarity index 100% rename from module-4/studio/parallelization.py rename to langchain-academy/module-4/studio/parallelization.py diff --git a/module-4/studio/requirements.txt b/langchain-academy/module-4/studio/requirements.txt similarity index 100% rename from module-4/studio/requirements.txt rename to langchain-academy/module-4/studio/requirements.txt diff --git a/module-4/studio/research_assistant.py b/langchain-academy/module-4/studio/research_assistant.py similarity index 100% rename from module-4/studio/research_assistant.py rename to langchain-academy/module-4/studio/research_assistant.py diff --git a/module-4/studio/sub_graphs.py b/langchain-academy/module-4/studio/sub_graphs.py similarity index 100% rename from module-4/studio/sub_graphs.py rename to langchain-academy/module-4/studio/sub_graphs.py diff --git a/module-4/sub-graph.ipynb b/langchain-academy/module-4/sub-graph.ipynb similarity index 100% rename from module-4/sub-graph.ipynb rename to langchain-academy/module-4/sub-graph.ipynb diff --git a/module-5/memory_agent.ipynb b/langchain-academy/module-5/memory_agent.ipynb similarity index 100% rename from module-5/memory_agent.ipynb rename to langchain-academy/module-5/memory_agent.ipynb diff --git a/module-5/memory_store.ipynb b/langchain-academy/module-5/memory_store.ipynb similarity index 100% rename from module-5/memory_store.ipynb rename to langchain-academy/module-5/memory_store.ipynb diff --git a/module-5/memoryschema_collection.ipynb b/langchain-academy/module-5/memoryschema_collection.ipynb similarity index 100% rename from module-5/memoryschema_collection.ipynb rename to langchain-academy/module-5/memoryschema_collection.ipynb diff --git a/module-5/memoryschema_profile.ipynb b/langchain-academy/module-5/memoryschema_profile.ipynb similarity index 100% rename from module-5/memoryschema_profile.ipynb rename to langchain-academy/module-5/memoryschema_profile.ipynb diff --git a/module-5/studio/.env.example b/langchain-academy/module-5/studio/.env.example similarity index 100% rename from module-5/studio/.env.example rename to langchain-academy/module-5/studio/.env.example diff --git a/module-5/studio/configuration.py b/langchain-academy/module-5/studio/configuration.py similarity index 100% rename from module-5/studio/configuration.py rename to langchain-academy/module-5/studio/configuration.py diff --git a/module-5/studio/langgraph.json b/langchain-academy/module-5/studio/langgraph.json similarity index 100% rename from module-5/studio/langgraph.json rename to langchain-academy/module-5/studio/langgraph.json diff --git a/module-5/studio/memory_agent.py b/langchain-academy/module-5/studio/memory_agent.py similarity index 100% rename from module-5/studio/memory_agent.py rename to langchain-academy/module-5/studio/memory_agent.py diff --git a/module-5/studio/memory_store.py b/langchain-academy/module-5/studio/memory_store.py similarity index 100% rename from module-5/studio/memory_store.py rename to langchain-academy/module-5/studio/memory_store.py diff --git a/module-5/studio/memoryschema_collection.py b/langchain-academy/module-5/studio/memoryschema_collection.py similarity index 100% rename from module-5/studio/memoryschema_collection.py rename to langchain-academy/module-5/studio/memoryschema_collection.py diff --git a/module-5/studio/memoryschema_profile.py b/langchain-academy/module-5/studio/memoryschema_profile.py similarity index 100% rename from module-5/studio/memoryschema_profile.py rename to langchain-academy/module-5/studio/memoryschema_profile.py diff --git a/module-5/studio/requirements.txt b/langchain-academy/module-5/studio/requirements.txt similarity index 100% rename from module-5/studio/requirements.txt rename to langchain-academy/module-5/studio/requirements.txt diff --git a/module-6/assistant.ipynb b/langchain-academy/module-6/assistant.ipynb similarity index 100% rename from module-6/assistant.ipynb rename to langchain-academy/module-6/assistant.ipynb diff --git a/module-6/connecting.ipynb b/langchain-academy/module-6/connecting.ipynb similarity index 100% rename from module-6/connecting.ipynb rename to langchain-academy/module-6/connecting.ipynb diff --git a/module-6/creating.ipynb b/langchain-academy/module-6/creating.ipynb similarity index 100% rename from module-6/creating.ipynb rename to langchain-academy/module-6/creating.ipynb diff --git a/module-6/deployment/configuration.py b/langchain-academy/module-6/deployment/configuration.py similarity index 100% rename from module-6/deployment/configuration.py rename to langchain-academy/module-6/deployment/configuration.py diff --git a/module-6/deployment/docker-compose-example.yml b/langchain-academy/module-6/deployment/docker-compose-example.yml similarity index 100% rename from module-6/deployment/docker-compose-example.yml rename to langchain-academy/module-6/deployment/docker-compose-example.yml diff --git a/module-6/deployment/langgraph.json b/langchain-academy/module-6/deployment/langgraph.json similarity index 100% rename from module-6/deployment/langgraph.json rename to langchain-academy/module-6/deployment/langgraph.json diff --git a/module-6/deployment/requirements.txt b/langchain-academy/module-6/deployment/requirements.txt similarity index 100% rename from module-6/deployment/requirements.txt rename to langchain-academy/module-6/deployment/requirements.txt diff --git a/module-6/deployment/task_maistro.py b/langchain-academy/module-6/deployment/task_maistro.py similarity index 100% rename from module-6/deployment/task_maistro.py rename to langchain-academy/module-6/deployment/task_maistro.py diff --git a/module-6/double-texting.ipynb b/langchain-academy/module-6/double-texting.ipynb similarity index 100% rename from module-6/double-texting.ipynb rename to langchain-academy/module-6/double-texting.ipynb diff --git a/requirements.txt b/langchain-academy/requirements.txt similarity index 100% rename from requirements.txt rename to langchain-academy/requirements.txt diff --git a/module-1/agent-memory.ipynb b/module-1/agent-memory.ipynb deleted file mode 100644 index 7d666ab1f..000000000 --- a/module-1/agent-memory.ipynb +++ /dev/null @@ -1,497 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "13cd1c3e", - "metadata": {}, - "source": [ - "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-1/agent-memory.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239417-lesson-7-agent-with-memory)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "8c451ffd-a18b-4412-85fa-85186824dd03", - "metadata": {}, - "source": [ - "# Agent memory\n", - "\n", - "## Review\n", - "\n", - "Previously, we built an agent that can:\n", - "\n", - "* `act` - let the model call specific tools \n", - "* `observe` - pass the tool output back to the model \n", - "* `reason` - let the model reason about the tool output to decide what to do next (e.g., call another tool or just respond directly)\n", - "\n", - "![Screenshot 2024-08-21 at 12.45.32 PM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbab7453080e6802cd1703_agent-memory1.png)\n", - "\n", - "## Goals\n", - "\n", - "Now, we're going extend our agent by introducing memory." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d2b4b45b-cbaa-41b1-b3ed-f6b0645be3f9", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "%pip install --quiet -U langchain_openai langchain_core langgraph" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "2b0cfa99", - "metadata": {}, - "outputs": [], - "source": [ - "import os, getpass\n", - "\n", - "def _set_env(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass.getpass(f\"{var}: \")\n", - "\n", - "_set_env(\"OPENAI_API_KEY\")" - ] - }, - { - "cell_type": "markdown", - "id": "02eff247-a2aa-4f7a-8be1-73dfebfecc63", - "metadata": {}, - "source": [ - "We'll use [LangSmith](https://docs.smith.langchain.com/) for [tracing](https://docs.smith.langchain.com/concepts/tracing)." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "74ef2ff0", - "metadata": {}, - "outputs": [], - "source": [ - "_set_env(\"LANGCHAIN_API_KEY\")\n", - "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", - "os.environ[\"LANGCHAIN_PROJECT\"] = \"langchain-academy\"" - ] - }, - { - "cell_type": "markdown", - "id": "9c5f123b-db5d-4816-a6a3-2e4247611512", - "metadata": {}, - "source": [ - "This follows what we did previously." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "46647bbe-def5-4ea7-a315-1de8d97c8288", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_openai import ChatOpenAI\n", - "\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", - "# This will be a 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", - "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", - "tools = [add, multiply, divide]\n", - "llm = ChatOpenAI(model=\"gpt-4o\")\n", - "llm_with_tools = llm.bind_tools(tools)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "a9092b40-20c4-4872-b0ed-be1b53a15ef3", - "metadata": {}, - "outputs": [], - "source": [ - "from langgraph.graph import MessagesState\n", - "from langchain_core.messages import AIMessage, HumanMessage, SystemMessage\n", - "\n", - "# System message\n", - "sys_msg = SystemMessage(content=\"You are a helpful assistant tasked with performing arithmetic on a set of inputs.\")\n", - "\n", - "# Node\n", - "def assistant(state: MessagesState):\n", - " return {\"messages\": [llm_with_tools.invoke([sys_msg] + state[\"messages\"])]}" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "771123a3-91ac-4076-92c0-93bcd69cf048", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from langgraph.graph import START, StateGraph\n", - "from langgraph.prebuilt import tools_condition, ToolNode\n", - "from IPython.display import Image, display\n", - "\n", - "# Graph\n", - "builder = StateGraph(MessagesState)\n", - "\n", - "# Define nodes: these do the work\n", - "builder.add_node(\"assistant\", assistant)\n", - "builder.add_node(\"tools\", ToolNode(tools))\n", - "\n", - "# Define edges: these determine how the control flow moves\n", - "builder.add_edge(START, \"assistant\")\n", - "builder.add_conditional_edges(\n", - " \"assistant\",\n", - " # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools\n", - " # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END\n", - " tools_condition,\n", - ")\n", - "builder.add_edge(\"tools\", \"assistant\")\n", - "react_graph = builder.compile()\n", - "\n", - "# Show\n", - "display(Image(react_graph.get_graph(xray=True).draw_mermaid_png()))" - ] - }, - { - "cell_type": "markdown", - "id": "e830b7ae-3673-4cc6-8627-4740b7b8b217", - "metadata": {}, - "source": [ - "## Memory\n", - "\n", - "Let's run our agent, as before." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "596a71a0-1337-44d4-971d-f80c367bd868", - "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_zZ4JPASfUinchT8wOqg9hCZO)\n", - " Call ID: call_zZ4JPASfUinchT8wOqg9hCZO\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": [ - "messages = [HumanMessage(content=\"Add 3 and 4.\")]\n", - "messages = react_graph.invoke({\"messages\": messages})\n", - "for m in messages['messages']:\n", - " m.pretty_print()" - ] - }, - { - "cell_type": "markdown", - "id": "92f8128c-f4a5-4dee-b20b-3245bd33f6b3", - "metadata": {}, - "source": [ - "Now, let's multiply by 2!" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "b41cc1d7-e6de-4d86-8958-8cf7446f4c22", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "Multiply that by 2.\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "Tool Calls:\n", - " multiply (call_prnkuG7OYQtbrtVQmH2d3Nl7)\n", - " Call ID: call_prnkuG7OYQtbrtVQmH2d3Nl7\n", - " Args:\n", - " a: 2\n", - " b: 2\n", - "=================================\u001b[1m Tool Message \u001b[0m=================================\n", - "Name: multiply\n", - "\n", - "4\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "The result of multiplying 2 by 2 is 4.\n" - ] - } - ], - "source": [ - "messages = [HumanMessage(content=\"Multiply that by 2.\")]\n", - "messages = react_graph.invoke({\"messages\": messages})\n", - "for m in messages['messages']:\n", - " m.pretty_print()" - ] - }, - { - "cell_type": "markdown", - "id": "26e65f3c-e1dc-4a62-b8ab-02b33a6ff268", - "metadata": {}, - "source": [ - "We don't retain memory of 7 from our initial chat!\n", - "\n", - "This is because [state is transient](https://github.com/langchain-ai/langgraph/discussions/352#discussioncomment-9291220) to a single graph execution.\n", - "\n", - "Of course, this limits our ability to have multi-turn conversations with interruptions. \n", - "\n", - "We can use [persistence](https://langchain-ai.github.io/langgraph/how-tos/persistence/) to address this! \n", - "\n", - "LangGraph can use a checkpointer to automatically save the graph state after each step.\n", - "\n", - "This built-in persistence layer gives us memory, allowing LangGraph to pick up from the last state update. \n", - "\n", - "One of the easiest checkpointers to use is the `MemorySaver`, an in-memory key-value store for Graph state.\n", - "\n", - "All we need to do is simply compile the graph with a checkpointer, and our graph has memory!" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "637fcd79-3896-42e4-9131-e03b123a0a90", - "metadata": {}, - "outputs": [], - "source": [ - "from langgraph.checkpoint.memory import MemorySaver\n", - "memory = MemorySaver()\n", - "react_graph_memory = builder.compile(checkpointer=memory)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "ff8fc3bf-3999-47cb-af34-06b2b94d7192", - "metadata": {}, - "source": [ - "When we use memory, we need to specify a `thread_id`.\n", - "\n", - "This `thread_id` will store our collection of graph states.\n", - "\n", - "Here is a cartoon:\n", - "\n", - "* The checkpointer write the state at every step of the graph\n", - "* These checkpoints are saved in a thread \n", - "* We can access that thread in the future using the `thread_id`\n", - "\n", - "![state.jpg](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e0e9f526b41a4ed9e2d28b_agent-memory2.png)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "f722a1d6-e73c-4023-86ed-8b07d392278d", - "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_MSupVAgej4PShIZs7NXOE6En)\n", - " Call ID: call_MSupVAgej4PShIZs7NXOE6En\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": [ - "# Specify a thread\n", - "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", - "\n", - "# Specify an input\n", - "messages = [HumanMessage(content=\"Add 3 and 4.\")]\n", - "\n", - "# Run\n", - "messages = react_graph_memory.invoke({\"messages\": messages},config)\n", - "for m in messages['messages']:\n", - " m.pretty_print()" - ] - }, - { - "cell_type": "markdown", - "id": "c91a8a16-6bf1-48e2-a889-ae04a37c7a2b", - "metadata": {}, - "source": [ - "If we pass the same `thread_id`, then we can proceed from from the previously logged state checkpoint! \n", - "\n", - "In this case, the above conversation is captured in the thread.\n", - "\n", - "The `HumanMessage` we pass (`\"Multiply that by 2.\"`) is appended to the above conversation.\n", - "\n", - "So, the model now know that `that` refers to the `The sum of 3 and 4 is 7.`." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "ee38c6ef-8bfb-4c66-9214-6f474c9b8451", - "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_MSupVAgej4PShIZs7NXOE6En)\n", - " Call ID: call_MSupVAgej4PShIZs7NXOE6En\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", - "Multiply that by 2.\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "Tool Calls:\n", - " multiply (call_fWN7lnSZZm82tAg7RGeuWusO)\n", - " Call ID: call_fWN7lnSZZm82tAg7RGeuWusO\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": [ - "messages = [HumanMessage(content=\"Multiply that by 2.\")]\n", - "messages = react_graph_memory.invoke({\"messages\": messages}, config)\n", - "for m in messages['messages']:\n", - " m.pretty_print()" - ] - }, - { - "cell_type": "markdown", - "id": "c4b7774e-566f-4c92-9429-ed953bcacaa5", - "metadata": {}, - "source": [ - "## LangGraph Studio\n", - "\n", - "--\n", - "\n", - "**⚠️ DISCLAIMER**\n", - "\n", - "*Running Studio currently requires a Mac. If you are not using a Mac, then skip this step.*\n", - "\n", - "--\n", - "\n", - "Load the `agent` in the UI, which uses `module-1/studio/agent.py` set in `module-1/studio/langgraph.json`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6d72986c-ff6f-4f81-b585-d268e2710e53", - "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.12.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/module-1/agent.ipynb b/module-1/agent.ipynb deleted file mode 100644 index 4f64a1182..000000000 --- a/module-1/agent.ipynb +++ /dev/null @@ -1,339 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "6a44f010", - "metadata": {}, - "source": [ - "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-1/agent.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239232-lesson-6-agent)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "98f5e36a-da49-4ae2-8c74-b910a2f992fc", - "metadata": {}, - "source": [ - "# Agent\n", - "\n", - "## Review\n", - "\n", - "We built a router.\n", - "\n", - "* Our chat model will decide to make a tool call or not based upon the user input\n", - "* We use a conditional edge to route to a node that will call our tool or simply end\n", - "\n", - "![Screenshot 2024-08-21 at 12.44.33 PM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbac0ba0bd34b541c448cc_agent1.png)\n", - "\n", - "## Goals\n", - "\n", - "Now, we can extend this into a generic agent architecture.\n", - "\n", - "In the above router, we invoked the model and, if it chose to call a tool, we returned a `ToolMessage` to the user.\n", - " \n", - "But, what if we simply pass that `ToolMessage` *back to the model*?\n", - "\n", - "We can let it either (1) call another tool or (2) respond directly.\n", - "\n", - "This is the intuition behind [ReAct](https://react-lm.github.io/), a general agent architecture.\n", - " \n", - "* `act` - let the model call specific tools \n", - "* `observe` - pass the tool output back to the model \n", - "* `reason` - let the model reason about the tool output to decide what to do next (e.g., call another tool or just respond directly)\n", - "\n", - "This [general purpose architecture](https://blog.langchain.dev/planning-for-agents/) can be applied to many types of tools. \n", - "\n", - "![Screenshot 2024-08-21 at 12.45.43 PM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbac0b4a2c1e5e02f3e78b_agent2.png)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "63edff5a-724b-474d-9db8-37f0ae936c76", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "%pip install --quiet -U langchain_openai langchain_core langgraph" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "356a6482", - "metadata": {}, - "outputs": [], - "source": [ - "import os, getpass\n", - "\n", - "def _set_env(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass.getpass(f\"{var}: \")\n", - "\n", - "_set_env(\"OPENAI_API_KEY\")" - ] - }, - { - "cell_type": "markdown", - "id": "dba35a12", - "metadata": {}, - "source": [ - "Here, we'll use [LangSmith](https://docs.smith.langchain.com/) for [tracing](https://docs.smith.langchain.com/concepts/tracing).\n", - "\n", - "We'll log to a project, `langchain-academy`. " - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "60e6f1eb", - "metadata": {}, - "outputs": [], - "source": [ - "_set_env(\"LANGCHAIN_API_KEY\")\n", - "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", - "os.environ[\"LANGCHAIN_PROJECT\"] = \"langchain-academy\"" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "71795ff1-d6a7-448d-8b55-88bbd1ed3dbe", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_openai import ChatOpenAI\n", - "\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", - "# This will be a 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", - "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", - "tools = [add, multiply, divide]\n", - "llm = ChatOpenAI(model=\"gpt-4o\")\n", - "\n", - "# For this ipynb we set parallel tool calling to false as math generally is done sequentially, and this time we have 3 tools that can do math\n", - "# the OpenAI model specifically defaults to parallel tool calling for efficiency, see https://python.langchain.com/docs/how_to/tool_calling_parallel/\n", - "# play around with it and see how the model behaves with math equations!\n", - "llm_with_tools = llm.bind_tools(tools, parallel_tool_calls=False)" - ] - }, - { - "cell_type": "markdown", - "id": "a2cec014-3023-405c-be79-de8fc7adb346", - "metadata": {}, - "source": [ - "Let's create our LLM and prompt it with the overall desired agent behavior." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "d061813f-ebc0-432c-91ec-3b42b15c30b6", - "metadata": {}, - "outputs": [], - "source": [ - "from langgraph.graph import MessagesState\n", - "from langchain_core.messages import HumanMessage, SystemMessage\n", - "\n", - "# System message\n", - "sys_msg = SystemMessage(content=\"You are a helpful assistant tasked with performing arithmetic on a set of inputs.\")\n", - "\n", - "# Node\n", - "def assistant(state: MessagesState):\n", - " return {\"messages\": [llm_with_tools.invoke([sys_msg] + state[\"messages\"])]}" - ] - }, - { - "cell_type": "markdown", - "id": "4eb43343-9a6f-42cb-86e6-4380f928633c", - "metadata": {}, - "source": [ - "As before, we use `MessagesState` and define a `Tools` node with our list of tools.\n", - "\n", - "The `Assistant` node is just our model with bound tools.\n", - "\n", - "We create a graph with `Assistant` and `Tools` nodes.\n", - "\n", - "We add `tools_condition` edge, which routes to `End` or to `Tools` based on whether the `Assistant` calls a tool.\n", - "\n", - "Now, we add one new step:\n", - "\n", - "We connect the `Tools` node *back* to the `Assistant`, forming a loop.\n", - "\n", - "* After the `assistant` node executes, `tools_condition` checks if the model's output is a tool call.\n", - "* If it is a tool call, the flow is directed to the `tools` node.\n", - "* The `tools` node connects back to `assistant`.\n", - "* This loop continues as long as the model decides to call tools.\n", - "* If the model response is not a tool call, the flow is directed to END, terminating the process." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "aef13cd4-05a6-4084-a620-2e7b91d9a72f", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from langgraph.graph import START, StateGraph\n", - "from langgraph.prebuilt import tools_condition\n", - "from langgraph.prebuilt import ToolNode\n", - "from IPython.display import Image, display\n", - "\n", - "# Graph\n", - "builder = StateGraph(MessagesState)\n", - "\n", - "# Define nodes: these do the work\n", - "builder.add_node(\"assistant\", assistant)\n", - "builder.add_node(\"tools\", ToolNode(tools))\n", - "\n", - "# Define edges: these determine how the control flow moves\n", - "builder.add_edge(START, \"assistant\")\n", - "builder.add_conditional_edges(\n", - " \"assistant\",\n", - " # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools\n", - " # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END\n", - " tools_condition,\n", - ")\n", - "builder.add_edge(\"tools\", \"assistant\")\n", - "react_graph = builder.compile()\n", - "\n", - "# Show\n", - "display(Image(react_graph.get_graph(xray=True).draw_mermaid_png()))" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "75602459-d8ca-47b4-9518-3f38343ebfe4", - "metadata": {}, - "outputs": [], - "source": [ - "messages = [HumanMessage(content=\"Add 3 and 4. Multiply the output by 2. Divide the output by 5\")]\n", - "messages = react_graph.invoke({\"messages\": messages})" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "b517142d-c40c-48bf-a5b8-c8409427aa79", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "Add 3 and 4. Multiply the output by 2. Divide the output by 5\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "Tool Calls:\n", - " add (call_i8zDfMTdvmIG34w4VBA3m93Z)\n", - " Call ID: call_i8zDfMTdvmIG34w4VBA3m93Z\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", - "Tool Calls:\n", - " multiply (call_nE62D40lrGQC7b67nVOzqGYY)\n", - " Call ID: call_nE62D40lrGQC7b67nVOzqGYY\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", - "Tool Calls:\n", - " divide (call_6Q9SjxD2VnYJqEBXFt7O1moe)\n", - " Call ID: call_6Q9SjxD2VnYJqEBXFt7O1moe\n", - " Args:\n", - " a: 14\n", - " b: 5\n", - "=================================\u001b[1m Tool Message \u001b[0m=================================\n", - "Name: divide\n", - "\n", - "2.8\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "The final result after performing the operations \\( (3 + 4) \\times 2 \\div 5 \\) is 2.8.\n" - ] - } - ], - "source": [ - "for m in messages['messages']:\n", - " m.pretty_print()" - ] - }, - { - "cell_type": "markdown", - "id": "ad869f22-9bfb-4cbe-9f30-8a307c5cdda2", - "metadata": {}, - "source": [ - "## LangSmith\n", - "\n", - "We can look at traces in LangSmith." - ] - } - ], - "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.12.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/module-1/router.ipynb b/module-1/router.ipynb deleted file mode 100644 index ea1c0e851..000000000 --- a/module-1/router.ipynb +++ /dev/null @@ -1,231 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "cb354baf", - "metadata": {}, - "source": [ - "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-1/router.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239412-lesson-5-router)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "ce6fff79-25b5-4884-8aaa-e3ebb7ddd549", - "metadata": {}, - "source": [ - "# Router\n", - "\n", - "## Review\n", - "\n", - "We built a graph that uses `messages` as state and a chat model with bound tools.\n", - "\n", - "We saw that the graph can:\n", - "\n", - "* Return a tool call\n", - "* Return a natural language response\n", - "\n", - "## Goals\n", - "\n", - "We can think of this as a router, where the chat model routes between a direct response or a tool call based upon the user input.\n", - "\n", - "This is an simple example of an agent, where the LLM is directing the control flow either by calling a tool or just responding directly. \n", - "\n", - "![Screenshot 2024-08-21 at 9.24.09 AM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbac6543c3d4df239a4ed1_router1.png)\n", - "\n", - "Let's extend our graph to work with either output! \n", - "\n", - "For this, we can use two ideas:\n", - "\n", - "(1) Add a node that will call our tool.\n", - "\n", - "(2) Add a conditional edge that will look at the chat model model output, and route to our tool calling node or simply end if no tool call is performed. \n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ebb4fc6e-7c85-4fc8-a4a9-0c7a527c4e5b", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "%pip install --quiet -U langchain_openai langchain_core langgraph" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "885e92d9", - "metadata": {}, - "outputs": [], - "source": [ - "import os, getpass\n", - "\n", - "def _set_env(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass.getpass(f\"{var}: \")\n", - "\n", - "_set_env(\"OPENAI_API_KEY\")" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "e3ba4df4-3045-49b1-9299-ced1fce14d24", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_openai import ChatOpenAI\n", - "\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", - "llm = ChatOpenAI(model=\"gpt-4o\")\n", - "llm_with_tools = llm.bind_tools([multiply])" - ] - }, - { - "cell_type": "markdown", - "id": "c77555a2", - "metadata": {}, - "source": [ - " We use the [built-in `ToolNode`](https://langchain-ai.github.io/langgraph/reference/prebuilt/?h=tools+condition#toolnode) and simply pass a list of our tools to initialize it. \n", - " \n", - " We use the [built-in `tools_condition`](https://langchain-ai.github.io/langgraph/reference/prebuilt/?h=tools+condition#tools_condition) as our conditional edge." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "9a6fde4e-cceb-4426-b770-97ee4b41e9da", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAFNAKEDASIAAhEBAxEB/8QAHQABAAIDAQEBAQAAAAAAAAAAAAUGAwQHCAIBCf/EAE8QAAEEAQIDAggHCwoEBwAAAAEAAgMEBQYRBxIhEzEIFBUiQVGU0xYXMjZUVmEjQlJxcnSBobGy0SQ0N1Vic3WVtNImM5GiCSVDRIKDpP/EABoBAQEAAwEBAAAAAAAAAAAAAAABAgMFBAb/xAAyEQEAAQIBCAgGAwEAAAAAAAAAAQIRAwQSFCExUZHRBTNBYnGSobETIjJSYYEjweHw/9oADAMBAAIRAxEAPwD+qaIiAiIgIiICLSzGWhwlB9qcPk2IayGJvNJK89GsYPS4noP17DcqEGlptRN7fUcr5mPHTEwyFtaIb9zuXYyu9BLiW+po7ztpoiYzqptH/bFsmbGoMXUkLJ8lUheOhbJO1pH6CVi+FWF/rih7Sz+KxV9GafqR9nBgsbCz8GOnG0f9AFl+CuF/qeh7Mz+Cz/h/Pouo+FWF/rih7Sz+K+4tR4md4bHlKUjj6GWGE/tXx8FcL/U9D2Zn8F8S6RwUzCyTC46Rh68rqkZH7E/h/PompLoqv8CY8KO101McNI3r4k0k0pf7Ji7o/wAqPlPdvzAcpl8JmW5mvITC+pageYrFWXbniePQduhBBBBHQggrGqiLZ1E3j1LbkiiItKCIiAiIgIiICIiAiIgIiIKxNtl+IEcDw10OHpttNad/+fO6SNrvVu1kco/+0qySysgjfJI9scbAXOe47BoHeSVXKbfE+I2Ta7m2v42vLEeXoTFJK2Tr9gli6fap7IQQ2aFmGxD4xXkicySHl5u0aQQW7encdF6MbbTEbLR/vrdZcayPhgcNpNGawzun82NQu05Qkuy14KtljZtndmwMkMRDmukLWc7eZrd9z0BKzcP/AAqdF6s4Mw8QMlefiqdaGs3Kt8RtObUsytZ9yZ9y5pQHPDQ9gcD06rhHDLTWtrNHXmhNJYDWeM4ZXNI5CCnjNdUm15cdkpWvayvVmJ5pIjzHvJaO/m379wav4izeDJoXT+C0jr7S9jT0+Kw+pnVMX2WTlpMhcyd+PDty/wA5jPPaNxzD0c23nR6Ch8JrhlZ4cXdeRaqgk0rSstp2rza85dXmc5rQySLk7Rp3e3vaOh37uqoHELw5NCaVp6UuYeebN0MxnG4uxb8RuRMrQta1807AYPuxa2SLZrPlc+7d+Uhecr/DPVl7hhxzx9fR2uZ4M1mcDfxkWoqstq/dgEzRK97gHFzwG7uaTzMaW822y9PeFxgsy/EcNs9hMBkNRRaW1hQy93HYeDtrRqxtka8xRjq8jmb5o9foAJAdu0/nqWqMFj8xjZXTY+/XZaryPifE50b2hzSWPAc07EdHAEekKIym2I1zhrTAGsyzJMfOOvnvYx00R9XRrJx/8h6lJaWz3wp07j8t5Ov4jxyITeI5SDsLUO/3ske55Xesb9FG6kb45qvSdRoJdDZnyD9huAxkD4up9HnWGfrXowfqmPxPtKws6Ii86CIiAiIgIiICIiAiIgIiIIfUeGlyLKtuk6OPK0JDNVfKSGOJaWujeR15HAkHodjyu2JaAsmF1FVzJkhbzVr8I/lFCcgTQn+0AeoPocN2u7wSFKKMzWmsbqAR+PVhJJHv2c8b3RTR79/JIwhzf0ELdTVTMZtfZ27l8Umiq/wGewEQ6kz0LPQ3xtsm36Xscf1rHPoyzHBI8aqz27WkjeaH1f3Sy+Hh/f6SWjetiLl/CzF5XWHDHSGeyOqcwMhlMPTvWRXlhEfayQse/l+5nzd3HbqenpVpGiJSNpNTZ2Ru++3jEbf1tjB/Wnw8P7/SS0b0tms/SwEDH25g18ruSGBg5pZ3/gRsHVzvsH4z0WpgMVZFy1l8kxseSttbGIWO5hXhaSWRg+k7uJcR0JO3UNBWTDaTxeBmfPVrufbeOV9uzK+edw9RkeS7b7N9vsUwpNVNMTTR29p4CIi0IIiICIiAiIgIiICIiAiIgIiICxW/5rN+Q79iyrDb/ms35Dv2IKTwELTwL4clhJadN47YkbEjxWP7T+0/jV8VE4Cb/EZw63LSfg5jtywAD+ax93L02/F0V7QEREBERAREQEREBERAREQEREBERAREQFht/wA1m/Id+xZlht/zWb8h37EFI4AgDgRw4Ac14Gm8b5zBsD/JY+o6Dp+hX1ULgBt8RHDflJLfg1jdiW8v/tY/R6FfUBERAREQEREBERAREQEX45wY0ucQ1oG5JPQBUo6wzeWAsYXGUTjX9YbGQsvjkmb6HiNsZ5WnvG53I7wFuw8KrFvm8ltddkVI8u6w+gYP2ub3aeXdYfQMH7XN7tbtFr3xxgsu6KkeXdYfQMH7XN7tPLusPoGD9rm92mi1744wWXdFSPLusPoGD9rm92nl3WH0DB+1ze7TRa98cYLLuuMeFLx8v+DtoarqWLSbtUYyWx4pbdHe8WdVLh9zcR2b+ZpIcCemx5e/m6XDy7rD6Bg/a5vdqucRsBneJ+hs3pXM4zCSY3K1nVpdrUpczfq17d4vlNcGuH2tCaLXvjjBZQ/AV49TcaeGDaA0vLg6OladHERXn2hK289kJa8taI2BnKGMOw3/AOYO7br6WXDOB3DrM8CeG2L0fhqeFsQVA581uSxK19mZx3fI4CPvPQfYAB6FfPLusPoGD9rm92mi1744wWXdFSPLusPoGD9rm92nl3WH0DB+1ze7TRa98cYLLuipHl3WH0DB+1ze7Ty7rD6Bg/a5vdpote+OMFl3RUjy7rD6Bg/a5vdp5d1h9Awftc3u00WvfHGCy7oqU3U+paX3a7iKFms3rI3H2pHTBvpLWujAee/puO7pueit1G7BkqUFutIJq08bZYpG9zmuG4I/GCtOJg14eurmWZ0RFpRF6oJbpnLkHYinMQR+QVXtMgDTeKAAAFSLYD8gKw6q+bGY/M5v3Cq9pr5uYr80i/cC6OD1M+P9L2JJEWjh85j9Q0vHMXdgyFTtZIe3rSB7OeN5Y9u46btc1zT6iCskbyIsVu1FRqzWZ3ckMLHSPdsTs0DcnYde4IMqKN0zqPHaw09jc5iLHjeLyNdlqrPyOZ2kT2hzXcrgHDcEdCAVJICIofUWrsTpN2Kblbfipyl6PG0x2b39rYeHFjPNB23DHdTsOnUqCYRV6PiBgJZtTxMv80mmiBlW9jJ/Jt4ROPvfP+5ua7zObv27+ilcLmKmocPRylCXt6F6COzXl5XN543tDmu2cARuCDsQD60G4iIqCKHyWrsTiNR4bBW7fZZXMNndRr9m93bCFrXS+cAWt2DmnziN9+m6aZ1didY1blnD2/HIad2fHzu7N7OSeF5jlZs4Dflc0jcdDt0JCgmERFQWDhad+HuC+ysAPsG52WdYOFv9HuC/Nx+0qYvUT4x7SvYtSIi5qIvVXzYzH5nN+4VXtNfNzFfmkX7gVh1V82Mx+ZzfuFV7TXzcxX5pF+4F0cHqZ8f6Xsb1iEWYJInOexsjS0ujcWuG423BHUH7QvHWiRc4YeCtnM/p/M5WtlrebsYp1q7kZrUNBj8y+u6dkUjixjwx5cXAAud5zt+q9kqiVuBuh6l7UNmPAx/8QMlZk6z55X1rAlIdIewLzG1zi0Eua0En0qTF0cQ4kam1B4PGc1Hj9O6hzWpIpNFXs22DPXXX5KVqvJGxlgOfuQxwlduz5JMfQDqpDJVclwx1RoahV1jndUVtX4rJxZOHMX3W2SOipGdtqEO6QjmHKWs2btI0bbgLsejuCui9BjJeR8IxrsjCK1uS7YluSSwgECIvme93Z7E+YDy9e5YtE8DND8O8o/I4HBNq3XQGq2WazNYMMJO5iiEr3CJm4HmM5R0HTopmyNHwaCD4PXDfY7/8P0u7+5aorjrmMpY1Vw50ZSzNvTmP1PkbEV/J0JBFYEcNZ8zYI5NvMdI4Acw87YHbvUxV4b5jQdKHE8N7On9NYBpfM6jksbZvFsr3EuMZFuMMZ3bMA2B3279lsWuG9nX2nrOI4l+Q9TVTNHPWGMoT0TA9u+zw42JHh436OY5pA3HXdXXawoXEzBjSOJ01ofFZjW2dzebyM09GNmpH1pjHFDvKJrpDpGwMBa7Yczy4gDcbhc8xGoM9mNFaBp6ktyXMjhOLAxHb2LPjMpjiM4Y183KztXNDuXnLWl2wJAJXeHeDzoF+Dr4o4WYV69x1+KcZK0LTJ3MDHPFgS9ru5gDT5+xAAPcsg8H3h83St7TTNNwxYO7bZflpxTysa2y0NDZoyHgxv2Y3zmFpJ3J3JJOObNxy63BJPZ8J/sb13HWIJa9mKzjrT608b48TBIwtkYQ5vnMG+x6jcHoStXSNTK8Sdeaaw2S1bqWnjn8NsTk5Y8ZlZazpbb5ZWmdz2nmL9u87+dsObm2AHcrfCrSt3OZzMS4lvlLOY/yXkp45pGeNV+Xl5XhrgC4N6B+3MB0BAWxg+HWntN5ark8dj/F71bEw4OKXtpHctOJxdHFs5xB2LieYjmO/UlXNHlvGcSspqTTXCmTXeq8/g9NZDB3u2y2Cklhnu5SGZscbZZIWl43ibI8NGwe7fffbZegPByr6gr8FdLjVJvHPSQyTWXZJ73WXc8r3tMnOSWuLHNJb96TygADYU/iV4PBtY7S1DReDwviGFhswMhyOaylCaNkr2vIZPWeXOaXNJLXh255di3ZTGhtA8U9F6RxuIj1pgLr4GyGSTLYu3ekaXSveI2zG4xzmMa5rGl4Ltm7k9dhIiYnWP3iQdvCK4N/bDnB/+eFclhyOSo8Kb0WKy13Cz3eLk+PltY+Xs5mxTZdzHgHqOrXHoQR6wV39vDmXVbMXY18MRncth8g2/i7eJqT0PFXtA2755HEkg7jm5XDYFp26/b+Cmi33shb8iMjmv5Wvm7PZWJWMkuwP54p+Rrw0O5up2ADz8oOVtMjg2Y0vlKeb42Y6trvWUVTSeHrZXDtOdne6CxJWmkcXvcS+VnNA3aOQuaA53Tr09IcPc1Y1JoDTOXuEG3fxla1MWjYF74mudsPR1JWOxw609auantS4/mn1LVjpZV/bSDxmFjHxsbtzbM2bI8bs2PXv3AUxh8RUwGIo4uhF2FGlAytXi5i7kjY0Na3ckk7AAbkkqxFhuLBwt/o9wX5uP2lZ1g4W/wBHuC/Nx+0rLF6ifGPaV7FqREXNRF6q+bGY/M5v3Cq9pr5uYr80i/cCuNiCO1BJDK3nikaWOafSCNiFQ4auf0zXhxzcJNnK9djYoblOxC1z2AbN7Rsr2bP2HXYkHv6b8o6GTzE0TRe03vrm3uyjXFk6ihPK2e+pmV9qpe/TytnvqZlfaqXv1vzO9HmjmWTaKE8rZ76mZX2ql79PK2e+pmV9qpe/TM70eaOZZNooTytnvqZlfaqXv1rZHU2axdN9mbRWbfGzYFtd9aZ53IA2YyYuPU+gdBuT0BTM70eaOZZZEUJ5Wz31MyvtVL36eVs99TMr7VS9+mZ3o80cyybRQnlbPfUzK+1Uvfp5Wz31MyvtVL36ZnejzRzLJtFW6Ops1kGzGLRWbZ2UroXCd9aIlzTsS3nmHM31OG7T6CVs+Vs99TMr7VS9+mZ3o80cyybRQnlbPfUzK+1Uvfp5Wz31MyvtVL36ZnejzRzLJtFCeVs99TMr7VS9+nlbPfUzK+1Uvfpmd6PNHMsm1g4W/wBHuC/Nx+0qNbY1JkPuEGnZcZI/p41kbMDo4v7XLFI9ziOpDem5Gxc3fcW7B4iHAYalja7nuhqwtha6Q7udsNtyfST3n7StOPMU4eZeJmZidUxOy+7xNkN5ERc5iIiICIiAiIgKu4wnUmWOTkjYcfTeW42eve7RlkOYA+VzGeb0Jcxu5cRs4+bzbL61BlDYyNXAULtSLKWGi1NDYidL/I2yNbMQ0dA5wdyNLiBuS7Z/IWmapUq+NpwVKkEVWpXjbFDBCwMZGxo2a1rR0AAAAA7tkGZERAREQV7UDX4S3Hna0LZQOSC/213sI46vMS6bZ3mEx7lx35SW8w3JDWmfY9srGvY4PY4btc07gj1hHsbKxzHtD2OGxa4bgj1FV3S9qLHXrumnSY6GXHsjlp0cfCYRDQduyAFh80bGORnmebswHZu/KAsiIiAiIgIiICIiAiIgIiIC/CQASTsAv1V3iFL2ei8tHvl2GzD4oJcCAb0RlIiEkO/QOZz83Mfk8u/oQfejbMuVx0uYkmvOiybxar1r9YV5KkJY1rIuTbmHyS88+7t5Hb8oAa2fXyxgjY1o3IaNhzEk/wDU96+kBERAREQFXNVX24O/hcnNkhQo+NsozxGn23jDp3CKBnOOsX3Z0fndWnfYjqCLGonVrbLtL5bxO9JjLYqyOiuQwCd0Lw0kPEZ+XsR8npv3bjfdBLItXF5KDM4ypkKri+tahZPE5zS0ljmhzSQeo6EdCtpAREQEREBERAREQEVeu8RNLY6xJXtajxcE8bix8clyMOa4d4I36Hu6fatf40tHfWnEe2x/xW+Mnxpi8UTwlbTuWlc94ucSNJaQxYpZ7VDcJcfLUmbXp5CKvedGbLAHhr3AmLdrg893I2T1KZ+NLR31pxHtsf8AFeMv/EW4c4Ti7htP6r0nlsdlNS42RuPnqVrTHyTVZHktIAd/6b3Enp3PcT0Cuj432Twlc2dz3Dp3VeE1hRfdwOYoZumyQxOsY60yxG14AJaXMJAOzmnbv2I9alVxPgBV0DwO4TYDSNTU+FMtSEPuTsuR/d7LvOleTv187oPsDR6F0P40tHfWnEe2x/xTR8b7J4SZs7lpRVb40tHfWnEe2x/xUnhdWYTUb5GYrL0ck+Mcz2VbDJHNHrIB3AWNWDi0ReqmYjwS0pZERaUEREFd4eXDd0ZinOyVjMSRRmvJftwdhLO+Nxje97PQS5h+w9471YlXNCXRdw9z/wAys5V0OUvwuntQ9k9pbalAiA9LYxtG133zWB3pVjQEREBERAREQFWeIlyWppkshlfAbVurTdJE4teGS2I438pBBBLXOAIO433HcrMqlxN+btT/ABbHf6uJenJovjURO+FjbDLVqQUa8detDHXgjaGsiiaGtaB3AAdAFlRF6tuuUEREBERAVe1sW0cQctEOS9j3slhmb0c3zwHN3/BcCQR3HdWFV3iH8zMn+Q399q3YPWUx+Vja6KiIuMgiIgrujLZtxZkG9bvmHKWYua3B2Ri2duI2fhMaDsHekKxKu6OteNPzw8euXuyyk0f8rh7Psdgw9nH+Ewb9Henc+pWJAREQEREBERAVS4m/N2p/i2O/1cStqqXE35u1P8Wx3+riXpyXr6PGFjbDaVK42ahzGk+EOss1gGxnMY/E2bNd0sgYI3MjJMnVrgSwAuDSNnFoaSAdxdVCa40y3WmitQaefMazMtj7FB0wbzGMSxuZzbenbm3XpnYjluM40alwugNENyumIclrXUro62LxlPKhzLbRWE0lmaZ0LBCA0Pc5oY/boBzb9E3hJvoUL2PvaUsQ69rZmvgmaaiuskbPYni7aF7LPKB2JiDnl5aC3kcC3fbfSj4VcRpsDoa7Yn0xDq/RMvJjjFLYfTv1nVjXmbMTGHxOcCHAtDw0tHetOx4PurcnZv6zt5XDxcRpc9UzdeKFsrsbEytA6uyqXECRzXRSS80nKDzOBDenXX8w0+K/GfN3+FuvsXax9nROtMBLi3yso5EzNdBYtxBksNhjWEtcGyscC1pGxB6FX+XjJk7nGPIaFw2l48gzFCo/I3rGVjrSsjnG/awwFhMzGD5R5m9QWjcqn5zgBqvXuD4h39RZTEUtWamr0atOLHiWWlRiqS9tE0ve1r5OeQuLjyjYEbDotvX/AAo17xJ1Dpi1fh0fjHYyxRu+XaD7JydN8Za6zDASwB8cjg9o5nNHK7zmEprHeFXeIfzMyf5Df32qxKu8Q/mZk/yG/vtXrwOtp8Y91jbDoqIi4yCIiCuaOui5LqEDJWcj2OVliIsQ9mK5DGHsmfhMG+4d6eY+pWNVzR17x2XUA8qTZPsMrLDyzV+y8W2Yw9i38No335vTzH1KxoCIiAiIgIiICqXE35u1P8Wx3+riVtVZ4iU5bemS6GJ87qturcdHE0ue5kU8cj+UAEk8rSQ0Dc7bDqV6cmmIxqJnfCxth9osNO7XyFaOxVnis15GhzJYXh7HA9xBHQhZl6pi2qUEREBERAVd4h/MzJ/kN/farEq9rTlv4o4iJwkv33sihgb1cRzgudt6GtG5JPTp39Qt2DqxKZ/KxtdEREXGQREQVzR18X5M/tlJsn2OVmh2mr9l4ts1n3Fv4bW7783p5j6lY1XdG3zfbmnHKS5QRZSxCO1rdj4vykDsW/hhp32f6d1YkBERAREQEREBERBX7/D3S2UsyWLmnMTankcXvlmpRuc5x7ySW9T9q1vir0Z9U8J/l8X+1WlFvjHxoi0Vzxlbyq3xV6M+qeE/y+L/AGqjcaOHel8ZoGSxS09iqVgZHGs7aCnEx3K6/A17d9h0c0uaR6QSOu+y7Eufcey6PhZlZmu5fF56dgnr0EduF57vsaVdIxvvnjJed6Y+KvRn1Twn+Xxf7U+KvRn1Twn+Xxf7VaUTSMb754yXneq3xV6M+qeE/wAvi/2qUwulMLpsyHE4ijjDINnmnWZEXD7eUDdSqLGrGxa4tVVMx4l5ERFpQREQVzQ9/wAp4/IzDLyZlgyt2FsslbsOx7Ow+MwAffCMsLOf77l39KsarnD/ACDctpeG6zMSZ2KxYsyxXZK/YExusSFkYZsOkbSIwfvgwH0qxoCIiAiIgIiICIiAiIgKo8XsDNqfhVq/FVYxLbt4m1FXYd+spid2fd1+Vy9ytyIIzTGci1NpvE5iAbQZCpFbjB/BkYHj9RUmuecK2/BS3m9DTNMYxM7reMJ+TJjrEj3xNb/cu7SDl7w2KMn5Y36GgIiICIiAtLN5erp/C38pel7ClRryWZ5eUu5I2NLnO2HU7AE7Dqt1QGrZZrDMfiq1i/SsX7LB43Rr9p2UcZ7WTncfNja9rDHzHrvINhv3BuaYgsVdN4qG3fmylqOrE2W9ZiEUth4YOaRzAAGucdyWgADfZSaIgIiICIiAiIgIiICIiAiIgqevdL3MrHTzODMMeqcP2kmPdO4sina4DtasrgCRFKGtBOx5XNjk5XGMAyultT0tXYhmQpdowc7oZq87eSavMw8skUjfvXtcCCOo9IJBBMuvKHhc+E3j/Ba1JFbwtKXIasz1AmfHTQvFB4ZzMr2ZJAQBI1wc0hm7pI2BrywNhc0PV6Lj/gmcWb3GrgJpnU2Xminzckb61+SFgYHzRPLC8tHQFwDXEAAbuOwA2C7AgIio3HPXTuGfB7WOp4pRBZxuMmmryFocGzcpbEdj0PnlvQ96C6WrcFGB01maOvC3bmklcGtG52G5P2kBRWn6VmWabMX4rFO/ciZG6g+4Zoq0bHPLA1oAY15D93lu+52bzvbGwryP4H/hlM8IbNYnSOtq4Op6tUyRGLGtfVuzxEPFp0m57GUNadmBrWAh7g8l7I4/aSAiIgIiICIiAiIgIiICIiAiIgLnXGe7pOxgfI2pcFV1QbbXGLFWImvBG23OXOH3MejmHX1bkK7Z7MQaewl/KWd/F6cD7Dw3vIa0nYfadtgvNMty3lLU+QyDhJkLbu1ncO4H0MH9lo2aPsH412+jMgjK6prxPpj1ndzXZrRfDPG2uEWFv4bSFluncLavyZBuPgYLAgc9jGljZJuYlvmA9QDuSrV8OtZfWy57JU9yohF9lTkmTUxaMOnhE+7HOlL/AA61l9bLnslT3Kr3EJ2Z4naNvaX1BmX5PDXnQmxBLAyIyNjlbJy80QYQHFgB7+hO3XYjaVYva8rUuImM0ia0rrd6jLebYBHZsYxwbsfTuSVK8nyWI+bDp16vpjkZ0u4cBW6J03ixp7TemKWj7TGc7qlZocLIH34mI5pSN+vP5w39XVddXld/aNcyWCZ1ezE4SQzs+VE8dzh+L1dxG4PQr0XofUo1fpTH5UsEU0zC2aJvcyVriyRo+wPa4D7Nl8l0p0fTksxi4X0z6Su3WnURFwAREQEREBERAREQEREBERBSuMweeGmaLN+jYnO2/AErC7/t3XDl6by+Lr5vFXMdbaX1bcL68rQdt2OaWuH/AEJXmm9ibmncjPiciD45V2BftsJmdeWVvrDgP0EOHeCvseg8Wn4dWD23v7R/XqTseZfCNlt5fizpnAZCXEt01JjJLMcGfvzUqNi0JCHB74huXNbyFoPTzj69jXbWkrsejeH2Jy+XoZrDXtatZSOGyE1iKKm5j2OrtncGvIBEje89D3+r1Pm9OYnU1UVsxi6WVrNdzCG9XZMwH17OBG6xjSuEbWoVxh6Ar4+UTU4hVZyVpBvs+MbbMcNz1Gx6ldKvIprxKq5nb/mrw1MXl3WMl3hrU404HSstnG4ek3EzRQ15HvNJk42sOi3JLdx3+rvG2yntAYHQ2B8IXTcehbNa1Rl0/YknkrXTZLn87dnPJceVxHeOn4l6KGAxjbd60MdUFq+xsducQN57DWghrZHbbvABIAO+wK0cRoPTWn7bbWL07isbaa1zWz06UUTwHfKAc1oOx2G/r2UjIpiuKomLRPD5pnV+pt+hOLsHAkP+Bllzt+zdkbHZ7+oOAP8A3By5BHBZu2IadGHxm/Zd2deEffO+31NHeT6ACV6N0jpyLSWm6GJhd2grR7Ok227R5Jc9+3o5nFx/SvF03i004EYXbM3/AFDKNiYREXxQIiICIiAiIgIiICIiAiIgKA1fonGa0pxxXmPjnh3MFyuQ2aAnv5SQRsdhu0gtOw3B2G0+izorqw6orom0wOG3+C+pqcpFK3jMnD02dM59aTb8Qa8E/pH6FpfFRrL6Djfb3e7Xf0Xap6ayqItNp/XJdW5wD4qNZfQcb7e73a2qPBvVVuVosy4rHQ79ZGyyWHgenzORg/7l3VFZ6ayqY1Wj9Grcq2iuHmN0Ux8sLpLuRlbyS3rAHaFu+/K0AANbv6B37DckjdWlEXGxMWvGqmvEm8ygiItQIiICIiAiIg//2Q==", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from IPython.display import Image, display\n", - "from langgraph.graph import StateGraph, START, END\n", - "from langgraph.graph import MessagesState\n", - "from langgraph.prebuilt import ToolNode\n", - "from langgraph.prebuilt import tools_condition\n", - "\n", - "# Node\n", - "def tool_calling_llm(state: MessagesState):\n", - " return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n", - "\n", - "# Build graph\n", - "builder = StateGraph(MessagesState)\n", - "builder.add_node(\"tool_calling_llm\", tool_calling_llm)\n", - "builder.add_node(\"tools\", ToolNode([multiply]))\n", - "builder.add_edge(START, \"tool_calling_llm\")\n", - "builder.add_conditional_edges(\n", - " \"tool_calling_llm\",\n", - " # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools\n", - " # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END\n", - " tools_condition,\n", - ")\n", - "builder.add_edge(\"tools\", END)\n", - "graph = builder.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "11b608c5-0c15-4fb7-aa24-80ce5774fb85", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "Hello world.\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "Hello! How can I assist you today?\n" - ] - } - ], - "source": [ - "from langchain_core.messages import HumanMessage\n", - "messages = [HumanMessage(content=\"Hello world.\")]\n", - "messages = graph.invoke({\"messages\": messages})\n", - "for m in messages['messages']:\n", - " m.pretty_print()" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "34708377-16b6-4474-9e23-71890c1fb36e", - "metadata": {}, - "source": [ - "Now, we can see that the graph runs the tool!\n", - "\n", - "It responds with a `ToolMessage`. \n", - "\n", - "## LangGraph Studio\n", - "\n", - "--\n", - "\n", - "**⚠️ DISCLAIMER**\n", - "\n", - "*Running Studio currently requires a Mac. If you are not using a Mac, then skip this step.*\n", - "\n", - "--\n", - "\n", - "Load the `router` in Studio, which uses `module-1/studio/router.py` set in `module-1/studio/langgraph.json`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "43782c33-0f41-47f2-ae38-ddb2cd4ba6f8", - "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.12.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/module-1/simple-graph.ipynb b/module-1/simple-graph.ipynb deleted file mode 100644 index bcb047591..000000000 --- a/module-1/simple-graph.ipynb +++ /dev/null @@ -1,309 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "8d5f3703", - "metadata": {}, - "source": [ - "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-1/simple-graph.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58238187-lesson-2-simple-graph)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "50fa7f8a-8764-4bb9-9968-48b681a0e4f1", - "metadata": {}, - "source": [ - "# The Simplest Graph\n", - "\n", - "Let's build a simple graph with 3 nodes and one conditional edge. \n", - "\n", - "![Screenshot 2024-08-20 at 3.11.22 PM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dba5f465f6e9a2482ad935_simple-graph1.png)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ff151ef1-fa30-482a-94da-8f49964afbc3", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "%pip install --quiet -U langgraph" - ] - }, - { - "cell_type": "markdown", - "id": "5999f8d0-989f-4638-8ade-5c257cbadfe8", - "metadata": {}, - "source": [ - "## State\n", - "\n", - "First, define the [State](https://langchain-ai.github.io/langgraph/concepts/low_level/#state) of the graph. \n", - "\n", - "The State schema serves as the input schema for all Nodes and Edges in the graph.\n", - "\n", - "Let's use the `TypedDict` class from python's `typing` module as our schema, which provides type hints for the keys." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "6a90709b-ddfa-4671-8acc-c59969a29991", - "metadata": {}, - "outputs": [], - "source": [ - "from typing_extensions import TypedDict\n", - "\n", - "class State(TypedDict):\n", - " graph_state: str" - ] - }, - { - "cell_type": "markdown", - "id": "888509e1-cbde-4c03-99a0-2560dd2e262d", - "metadata": {}, - "source": [ - "## Nodes\n", - "\n", - "[Nodes](https://langchain-ai.github.io/langgraph/concepts/low_level/#nodes) are just python functions.\n", - "\n", - "The first positional argument is the state, as defined above.\n", - "\n", - "Because the state is a `TypedDict` with schema as defined above, each node can access the key, `graph_state`, with `state['graph_state']`.\n", - "\n", - "Each node returns a new value of the state key `graph_state`.\n", - " \n", - "By default, the new value returned by each node [will override](https://langchain-ai.github.io/langgraph/concepts/low_level/#reducers) the prior state value." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "e8aabcb7-494c-4d35-be08-f81c76d75a6b", - "metadata": {}, - "outputs": [], - "source": [ - "def node_1(state):\n", - " print(\"---Node 1---\")\n", - " return {\"graph_state\": state['graph_state'] +\" I am\"}\n", - "\n", - "def node_2(state):\n", - " print(\"---Node 2---\")\n", - " return {\"graph_state\": state['graph_state'] +\" happy!\"}\n", - "\n", - "def node_3(state):\n", - " print(\"---Node 3---\")\n", - " return {\"graph_state\": state['graph_state'] +\" sad!\"}" - ] - }, - { - "cell_type": "markdown", - "id": "ad056608-8c8f-4999-bb53-10583efa4ed8", - "metadata": {}, - "source": [ - "## Edges\n", - "\n", - "[Edges](https://langchain-ai.github.io/langgraph/concepts/low_level/#edges) connect the nodes.\n", - "\n", - "Normal Edges are used if you want to *always* go from, for example, `node_1` to `node_2`.\n", - "\n", - "[Conditional Edges](https://langchain-ai.github.io/langgraph/reference/graphs/?h=conditional+edge#langgraph.graph.StateGraph.add_conditional_edges) are used want to *optionally* route between nodes.\n", - " \n", - "Conditional edges are implemented as functions that return the next node to visit based upon some logic." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "7e53543a-902a-4d41-ad3d-25eee260e819", - "metadata": {}, - "outputs": [], - "source": [ - "import random\n", - "from typing import Literal\n", - "\n", - "def decide_mood(state) -> Literal[\"node_2\", \"node_3\"]:\n", - " \n", - " # Often, we will use state to decide on the next node to visit\n", - " user_input = state['graph_state'] \n", - " \n", - " # Here, let's just do a 50 / 50 split between nodes 2, 3\n", - " if random.random() < 0.5:\n", - "\n", - " # 50% of the time, we return Node 2\n", - " return \"node_2\"\n", - " \n", - " # 50% of the time, we return Node 3\n", - " return \"node_3\"" - ] - }, - { - "cell_type": "markdown", - "id": "9282ea7a-5ed2-4641-bed8-c3472d54c951", - "metadata": {}, - "source": [ - "## Graph Construction\n", - "\n", - "Now, we build the graph from our [components](\n", - "https://langchain-ai.github.io/langgraph/concepts/low_level/) defined above.\n", - "\n", - "The [StateGraph class](https://langchain-ai.github.io/langgraph/concepts/low_level/#stategraph) is the graph class that we can use.\n", - " \n", - "First, we initialize a StateGraph with the `State` class we defined above.\n", - " \n", - "Then, we add our nodes and edges.\n", - "\n", - "We use the [`START` Node, a special node](https://langchain-ai.github.io/langgraph/concepts/low_level/#start-node) that sends user input to the graph, to indicate where to start our graph.\n", - " \n", - "The [`END` Node](https://langchain-ai.github.io/langgraph/concepts/low_level/#end-node) is a special node that represents a terminal node. \n", - "\n", - "Finally, we [compile our graph](https://langchain-ai.github.io/langgraph/concepts/low_level/#compiling-your-graph) to perform a few basic checks on the graph structure. \n", - "\n", - "We can visualize the graph as a [Mermaid diagram](https://github.com/mermaid-js/mermaid)." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "7deb0359-55c1-4545-b52e-8252994befbb", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from IPython.display import Image, display\n", - "from langgraph.graph import StateGraph, START, END\n", - "\n", - "# Build graph\n", - "builder = StateGraph(State)\n", - "builder.add_node(\"node_1\", node_1)\n", - "builder.add_node(\"node_2\", node_2)\n", - "builder.add_node(\"node_3\", node_3)\n", - "\n", - "# Logic\n", - "builder.add_edge(START, \"node_1\")\n", - "builder.add_conditional_edges(\"node_1\", decide_mood)\n", - "builder.add_edge(\"node_2\", END)\n", - "builder.add_edge(\"node_3\", END)\n", - "\n", - "# Add\n", - "graph = builder.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "markdown", - "id": "00617c74-2647-44ea-8a2e-310dd96c0d26", - "metadata": {}, - "source": [ - "## Graph Invocation\n", - "\n", - "The compiled graph implements the [runnable](https://python.langchain.com/v0.1/docs/expression_language/interface/) protocol.\n", - "\n", - "This provides a standard way to execute LangChain components. \n", - " \n", - "`invoke` is one of the standard methods in this interface.\n", - "\n", - "The input is a dictionary `{\"graph_state\": \"Hi, this is lance.\"}`, which sets the initial value for our graph state dict.\n", - "\n", - "When `invoke` is called, the graph starts execution from the `START` node.\n", - "\n", - "It progresses through the defined nodes (`node_1`, `node_2`, `node_3`) in order.\n", - "\n", - "The conditional edge will traverse from node `1` to node `2` or `3` using a 50/50 decision rule. \n", - "\n", - "Each node function receives the current state and returns a new value, which overrides the graph state.\n", - "\n", - "The execution continues until it reaches the `END` node." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "e895f17a-e835-4e8a-8e1b-63fe6d27cc52", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "---Node 1---\n", - "---Node 3---\n" - ] - }, - { - "data": { - "text/plain": [ - "{'graph_state': 'Hi, this is Lance. I am sad!'}" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "graph.invoke({\"graph_state\" : \"Hi, this is Lance.\"})" - ] - }, - { - "cell_type": "markdown", - "id": "082399c3-18bd-4b67-97c1-2005f268abc5", - "metadata": {}, - "source": [ - "`invoke` runs the entire graph synchronously.\n", - "\n", - "This waits for each step to complete before moving to the next.\n", - "\n", - "It returns the final state of the graph after all nodes have executed.\n", - "\n", - "In this case, it returns the state after `node_3` has completed: \n", - "\n", - "```\n", - "{'graph_state': 'Hi, this is Lance. I am sad!'}\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "db16ab8d-b817-4f3a-befc-a02b579c4fca", - "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.12.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/module-2/chatbot-external-memory.ipynb b/module-2/chatbot-external-memory.ipynb deleted file mode 100644 index 95c9f3a8e..000000000 --- a/module-2/chatbot-external-memory.ipynb +++ /dev/null @@ -1,437 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "cf7ccb32", - "metadata": {}, - "source": [ - "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-2/chatbot-external-memory.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239440-lesson-6-chatbot-w-summarizing-messages-and-external-memory)" - ] - }, - { - "cell_type": "markdown", - "id": "af6c7afe-1037-41ab-98e4-494692e47402", - "metadata": {}, - "source": [ - "# Chatbot with message summarization & external DB memory\n", - "\n", - "## Review\n", - "\n", - "We've covered how to customize graph state schema and reducer. \n", - " \n", - "We've also shown a number of tricks for trimming or filtering messages in graph state. \n", - "\n", - "We've used these concepts in a Chatbot with memory that produces a running summary of the conversation.\n", - "\n", - "## Goals\n", - "\n", - "But, what if we want our Chatbot to have memory that persists indefinitely?\n", - "\n", - "Now, we'll introduce some more advanced checkpointers that support external databases. \n", - "\n", - "Here, we'll show how to use [Sqlite as a checkpointer](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer), but other checkpointers, such as [Postgres](https://langchain-ai.github.io/langgraph/how-tos/persistence_postgres/) are available!" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "85ed78d9-6ca2-45ac-96a9-52e341ec519d", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "%pip install --quiet -U langgraph-checkpoint-sqlite langchain_core langgraph langchain_openai" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2e10c4d4", - "metadata": {}, - "outputs": [], - "source": [ - "import os, getpass\n", - "\n", - "def _set_env(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass.getpass(f\"{var}: \")\n", - "\n", - "_set_env(\"OPENAI_API_KEY\")" - ] - }, - { - "cell_type": "markdown", - "id": "b40d25c0-e9b5-4854-bf07-3cc3ff07122e", - "metadata": {}, - "source": [ - "## Sqlite\n", - "\n", - "A good starting point here is the [SqliteSaver checkpointer](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer).\n", - "\n", - "Sqlite is a [small, fast, highly popular](https://x.com/karpathy/status/1819490455664685297) SQL database. \n", - " \n", - "If we supply `\":memory:\"` it creates an in-memory Sqlite database." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fae15402-17ae-4e89-8ecf-4c89e08b22fe", - "metadata": {}, - "outputs": [], - "source": [ - "import sqlite3\n", - "# In memory\n", - "conn = sqlite3.connect(\":memory:\", check_same_thread = False)" - ] - }, - { - "cell_type": "markdown", - "id": "c2bf53ec-6d4a-42ce-8183-344795eed403", - "metadata": {}, - "source": [ - "But, if we supply a db path, then it will create a database for us!" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "58339167-920c-4994-a0a7-0a9c5d4f7cf7", - "metadata": {}, - "outputs": [], - "source": [ - "# pull file if it doesn't exist and connect to local db\n", - "!mkdir -p state_db && [ ! -f state_db/example.db ] && wget -P state_db https://github.com/langchain-ai/langchain-academy/raw/main/module-2/state_db/example.db\n", - "\n", - "db_path = \"state_db/example.db\"\n", - "conn = sqlite3.connect(db_path, check_same_thread=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "3c7736b6-a750-48f8-a838-8e7616b12250", - "metadata": {}, - "outputs": [], - "source": [ - "# Here is our checkpointer \n", - "from langgraph.checkpoint.sqlite import SqliteSaver\n", - "memory = SqliteSaver(conn)" - ] - }, - { - "cell_type": "markdown", - "id": "9d8cb629-213f-4b87-965e-19b812c42da1", - "metadata": {}, - "source": [ - "Let's re-define our chatbot." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "dc414e29-2078-41a0-887c-af1a6a3d72c0", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_openai import ChatOpenAI\n", - "from langchain_core.messages import SystemMessage, HumanMessage, RemoveMessage\n", - "\n", - "from langgraph.graph import END\n", - "from langgraph.graph import MessagesState\n", - "\n", - "model = ChatOpenAI(model=\"gpt-4o\",temperature=0)\n", - "\n", - "class State(MessagesState):\n", - " summary: str\n", - "\n", - "# Define the logic to call the model\n", - "def call_model(state: State):\n", - " \n", - " # Get summary if it exists\n", - " summary = state.get(\"summary\", \"\")\n", - "\n", - " # If there is summary, then we add it\n", - " if summary:\n", - " \n", - " # Add summary to system message\n", - " system_message = f\"Summary of conversation earlier: {summary}\"\n", - "\n", - " # Append summary to any newer messages\n", - " messages = [SystemMessage(content=system_message)] + state[\"messages\"]\n", - " \n", - " else:\n", - " messages = state[\"messages\"]\n", - " \n", - " response = model.invoke(messages)\n", - " return {\"messages\": response}\n", - "\n", - "def summarize_conversation(state: State):\n", - " \n", - " # First, we get any existing summary\n", - " summary = state.get(\"summary\", \"\")\n", - "\n", - " # Create our summarization prompt \n", - " if summary:\n", - " \n", - " # A summary already exists\n", - " summary_message = (\n", - " f\"This is summary of the conversation to date: {summary}\\n\\n\"\n", - " \"Extend the summary by taking into account the new messages above:\"\n", - " )\n", - " \n", - " else:\n", - " summary_message = \"Create a summary of the conversation above:\"\n", - "\n", - " # Add prompt to our history\n", - " messages = state[\"messages\"] + [HumanMessage(content=summary_message)]\n", - " response = model.invoke(messages)\n", - " \n", - " # Delete all but the 2 most recent messages\n", - " delete_messages = [RemoveMessage(id=m.id) for m in state[\"messages\"][:-2]]\n", - " return {\"summary\": response.content, \"messages\": delete_messages}\n", - "\n", - "# Determine whether to end or summarize the conversation\n", - "def should_continue(state: State):\n", - " \n", - " \"\"\"Return the next node to execute.\"\"\"\n", - " \n", - " messages = state[\"messages\"]\n", - " \n", - " # If there are more than six messages, then we summarize the conversation\n", - " if len(messages) > 6:\n", - " return \"summarize_conversation\"\n", - " \n", - " # Otherwise we can just end\n", - " return END" - ] - }, - { - "cell_type": "markdown", - "id": "41c13c0b-a383-4f73-9cc1-63f0eed8f190", - "metadata": {}, - "source": [ - "Now, we just re-compile with our sqlite checkpointer." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "e867fd95-91eb-4ce1-82fc-bb72d611a96d", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from IPython.display import Image, display\n", - "from langgraph.graph import StateGraph, START\n", - "\n", - "# Define a new graph\n", - "workflow = StateGraph(State)\n", - "workflow.add_node(\"conversation\", call_model)\n", - "workflow.add_node(summarize_conversation)\n", - "\n", - "# Set the entrypoint as conversation\n", - "workflow.add_edge(START, \"conversation\")\n", - "workflow.add_conditional_edges(\"conversation\", should_continue)\n", - "workflow.add_edge(\"summarize_conversation\", END)\n", - "\n", - "# Compile\n", - "graph = workflow.compile(checkpointer=memory)\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "markdown", - "id": "8769db99-3938-45e6-a594-56beb18d6c45", - "metadata": {}, - "source": [ - "Now, we can invoke the graph several times. " - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "0f4094a0-d240-4be8-903a-7d9f605bdc5c", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/var/folders/l9/bpjxdmfx7lvd1fbdjn38y5dh0000gn/T/ipykernel_18873/2173919996.py:55: LangChainBetaWarning: The class `RemoveMessage` is in beta. It is actively being worked on, so the API may change.\n", - " delete_messages = [RemoveMessage(id=m.id) for m in state[\"messages\"][:-2]]\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "Hello again, Lance! It's great to hear from you. Since you like the 49ers, is there a particular player or moment in their history that stands out to you? Or perhaps you'd like to discuss their current season? Let me know!\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "Your name is Lance! How can I assist you today? Would you like to talk more about the San Francisco 49ers or something else?\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "That's awesome, Lance! The San Francisco 49ers have a rich history and a passionate fan base. Is there a specific aspect of the team you'd like to discuss? For example, we could talk about:\n", - "\n", - "- Their legendary players like Joe Montana and Jerry Rice\n", - "- Memorable games and Super Bowl victories\n", - "- The current roster and season prospects\n", - "- Rivalries, like the one with the Seattle Seahawks\n", - "- Levi's Stadium and the fan experience\n", - "\n", - "Let me know what interests you!\n" - ] - } - ], - "source": [ - "# Create a thread\n", - "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", - "\n", - "# Start conversation\n", - "input_message = HumanMessage(content=\"hi! I'm Lance\")\n", - "output = graph.invoke({\"messages\": [input_message]}, config) \n", - "for m in output['messages'][-1:]:\n", - " m.pretty_print()\n", - "\n", - "input_message = HumanMessage(content=\"what's my name?\")\n", - "output = graph.invoke({\"messages\": [input_message]}, config) \n", - "for m in output['messages'][-1:]:\n", - " m.pretty_print()\n", - "\n", - "input_message = HumanMessage(content=\"i like the 49ers!\")\n", - "output = graph.invoke({\"messages\": [input_message]}, config) \n", - "for m in output['messages'][-1:]:\n", - " m.pretty_print()" - ] - }, - { - "cell_type": "markdown", - "id": "c0f3e842-4497-45e2-a924-69672a9bcb33", - "metadata": {}, - "source": [ - "Let's confirm that our state is saved locally." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "d2ab158a-5a82-417a-8841-730a4cc18ea7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "StateSnapshot(values={'messages': [HumanMessage(content=\"hi! I'm Lance\", id='d5bb4b3f-b1e9-4f61-8c75-7a7210b30253'), AIMessage(content=\"Hello again, Lance! It's great to hear from you. Since you like the 49ers, is there a particular player or moment in their history that stands out to you? Or perhaps you'd like to discuss their current season? Let me know!\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 50, 'prompt_tokens': 337, 'total_tokens': 387}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5', 'finish_reason': 'stop', 'logprobs': None}, id='run-dde04d51-d305-4a9e-8ad5-6bdf5583196e-0', usage_metadata={'input_tokens': 337, 'output_tokens': 50, 'total_tokens': 387}), HumanMessage(content=\"what's my name?\", id='d7530770-f130-4a05-a602-a96fd87859c6'), AIMessage(content='Your name is Lance! How can I assist you today? Would you like to talk more about the San Francisco 49ers or something else?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 29, 'prompt_tokens': 243, 'total_tokens': 272}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_fde2829a40', 'finish_reason': 'stop', 'logprobs': None}, id='run-763b6387-c4c9-4658-9d01-7c018dde7a62-0', usage_metadata={'input_tokens': 243, 'output_tokens': 29, 'total_tokens': 272}), HumanMessage(content='i like the 49ers!', id='235dcec4-b656-4bee-b741-e330e1a026e2'), AIMessage(content=\"That's awesome, Lance! The San Francisco 49ers have a rich history and a passionate fan base. Is there a specific aspect of the team you'd like to discuss? For example, we could talk about:\\n\\n- Their legendary players like Joe Montana and Jerry Rice\\n- Memorable games and Super Bowl victories\\n- The current roster and season prospects\\n- Rivalries, like the one with the Seattle Seahawks\\n- Levi's Stadium and the fan experience\\n\\nLet me know what interests you!\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 98, 'prompt_tokens': 287, 'total_tokens': 385}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_fde2829a40', 'finish_reason': 'stop', 'logprobs': None}, id='run-729860b2-16c3-46ff-ad1e-0a5475565b20-0', usage_metadata={'input_tokens': 287, 'output_tokens': 98, 'total_tokens': 385})], 'summary': 'Here\\'s an extended summary of the conversation:\\n\\nLance introduced himself multiple times during the conversation, each time stating, \"Hi! I\\'m Lance.\" He expressed his fondness for the San Francisco 49ers football team. The AI assistant acknowledged Lance\\'s name each time and showed willingness to discuss the 49ers, offering to talk about various aspects of the team such as their history, current roster, memorable games, prospects for the upcoming season, rivalries, and their home stadium, Levi\\'s Stadium. Despite the AI\\'s attempts to engage in a more detailed discussion about the 49ers, Lance reintroduced himself again without directly responding to the AI\\'s questions or prompts about the team. The conversation remained brief and somewhat repetitive, focusing mainly on Lance\\'s introductions and his interest in the 49ers.'}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a36d-ca9c-6144-801b-6d0cf97adc73'}}, metadata={'source': 'loop', 'writes': {'conversation': {'messages': AIMessage(content=\"That's awesome, Lance! The San Francisco 49ers have a rich history and a passionate fan base. Is there a specific aspect of the team you'd like to discuss? For example, we could talk about:\\n\\n- Their legendary players like Joe Montana and Jerry Rice\\n- Memorable games and Super Bowl victories\\n- The current roster and season prospects\\n- Rivalries, like the one with the Seattle Seahawks\\n- Levi's Stadium and the fan experience\\n\\nLet me know what interests you!\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 98, 'prompt_tokens': 287, 'total_tokens': 385}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_fde2829a40', 'finish_reason': 'stop', 'logprobs': None}, id='run-729860b2-16c3-46ff-ad1e-0a5475565b20-0', usage_metadata={'input_tokens': 287, 'output_tokens': 98, 'total_tokens': 385})}}, 'step': 27, 'parents': {}}, created_at='2024-09-03T20:55:33.466540+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a36d-b8f3-6b40-801a-494776d2e9e0'}}, tasks=())" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", - "graph_state = graph.get_state(config)\n", - "graph_state" - ] - }, - { - "cell_type": "markdown", - "id": "1e21152d-ed9c-408d-b7d5-f634c9ce81e2", - "metadata": {}, - "source": [ - "### Persisting state\n", - "\n", - "Using database like Sqlite means state is persisted! \n", - "\n", - "For example, we can re-start the notebook kernel and see that we can still load from Sqlite DB on disk.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "b9a44dc5-be04-45fa-a6fc-27b0f8ee4678", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "StateSnapshot(values={'messages': [HumanMessage(content=\"hi! I'm Lance\", id='d5bb4b3f-b1e9-4f61-8c75-7a7210b30253'), AIMessage(content=\"Hello again, Lance! It's great to hear from you. Since you like the 49ers, is there a particular player or moment in their history that stands out to you? Or perhaps you'd like to discuss their current season? Let me know!\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 50, 'prompt_tokens': 337, 'total_tokens': 387}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5', 'finish_reason': 'stop', 'logprobs': None}, id='run-dde04d51-d305-4a9e-8ad5-6bdf5583196e-0', usage_metadata={'input_tokens': 337, 'output_tokens': 50, 'total_tokens': 387}), HumanMessage(content=\"what's my name?\", id='d7530770-f130-4a05-a602-a96fd87859c6'), AIMessage(content='Your name is Lance! How can I assist you today? Would you like to talk more about the San Francisco 49ers or something else?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 29, 'prompt_tokens': 243, 'total_tokens': 272}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_fde2829a40', 'finish_reason': 'stop', 'logprobs': None}, id='run-763b6387-c4c9-4658-9d01-7c018dde7a62-0', usage_metadata={'input_tokens': 243, 'output_tokens': 29, 'total_tokens': 272}), HumanMessage(content='i like the 49ers!', id='235dcec4-b656-4bee-b741-e330e1a026e2'), AIMessage(content=\"That's awesome, Lance! The San Francisco 49ers have a rich history and a passionate fan base. Is there a specific aspect of the team you'd like to discuss? For example, we could talk about:\\n\\n- Their legendary players like Joe Montana and Jerry Rice\\n- Memorable games and Super Bowl victories\\n- The current roster and season prospects\\n- Rivalries, like the one with the Seattle Seahawks\\n- Levi's Stadium and the fan experience\\n\\nLet me know what interests you!\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 98, 'prompt_tokens': 287, 'total_tokens': 385}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_fde2829a40', 'finish_reason': 'stop', 'logprobs': None}, id='run-729860b2-16c3-46ff-ad1e-0a5475565b20-0', usage_metadata={'input_tokens': 287, 'output_tokens': 98, 'total_tokens': 385})], 'summary': 'Here\\'s an extended summary of the conversation:\\n\\nLance introduced himself multiple times during the conversation, each time stating, \"Hi! I\\'m Lance.\" He expressed his fondness for the San Francisco 49ers football team. The AI assistant acknowledged Lance\\'s name each time and showed willingness to discuss the 49ers, offering to talk about various aspects of the team such as their history, current roster, memorable games, prospects for the upcoming season, rivalries, and their home stadium, Levi\\'s Stadium. Despite the AI\\'s attempts to engage in a more detailed discussion about the 49ers, Lance reintroduced himself again without directly responding to the AI\\'s questions or prompts about the team. The conversation remained brief and somewhat repetitive, focusing mainly on Lance\\'s introductions and his interest in the 49ers.'}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a36d-ca9c-6144-801b-6d0cf97adc73'}}, metadata={'source': 'loop', 'writes': {'conversation': {'messages': AIMessage(content=\"That's awesome, Lance! The San Francisco 49ers have a rich history and a passionate fan base. Is there a specific aspect of the team you'd like to discuss? For example, we could talk about:\\n\\n- Their legendary players like Joe Montana and Jerry Rice\\n- Memorable games and Super Bowl victories\\n- The current roster and season prospects\\n- Rivalries, like the one with the Seattle Seahawks\\n- Levi's Stadium and the fan experience\\n\\nLet me know what interests you!\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 98, 'prompt_tokens': 287, 'total_tokens': 385}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_fde2829a40', 'finish_reason': 'stop', 'logprobs': None}, id='run-729860b2-16c3-46ff-ad1e-0a5475565b20-0', usage_metadata={'input_tokens': 287, 'output_tokens': 98, 'total_tokens': 385})}}, 'step': 27, 'parents': {}}, created_at='2024-09-03T20:55:33.466540+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a36d-b8f3-6b40-801a-494776d2e9e0'}}, tasks=())" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Create a thread\n", - "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", - "graph_state = graph.get_state(config)\n", - "graph_state" - ] - }, - { - "cell_type": "markdown", - "id": "8466e418-1a46-4cdb-a51a-6ae14281bb85", - "metadata": {}, - "source": [ - "## LangGraph Studio\n", - "\n", - "--\n", - "\n", - "**⚠️ DISCLAIMER**\n", - "\n", - "*Running Studio currently requires a Mac. If you are not using a Mac, then skip this step.*\n", - "\n", - "--\n", - "\n", - "Now that we better understand external memory, recall that the LangGraph API packages your code and provides you with with built-in persistence.\n", - " \n", - "And the API is the back-end for Studio! \n", - "\n", - "Load the `chatbot` in the UI, which uses `module2-/studio/chatbot.py` set in `module2-/studio/langgraph.json`." - ] - }, - { - "cell_type": "markdown", - "id": "c4916d8b", - "metadata": {}, - "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.12.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/module-2/chatbot-summarization.ipynb b/module-2/chatbot-summarization.ipynb deleted file mode 100644 index a41cbfed1..000000000 --- a/module-2/chatbot-summarization.ipynb +++ /dev/null @@ -1,485 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "83fcadf3", - "metadata": {}, - "source": [ - "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-2/chatbot-summarization.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239436-lesson-5-chatbot-w-summarizing-messages-and-memory)" - ] - }, - { - "cell_type": "markdown", - "id": "b651ead9-5504-45ee-938d-f91ac78dddd1", - "metadata": {}, - "source": [ - "# Chatbot with message summarization\n", - "\n", - "## Review\n", - "\n", - "We've covered how to customize graph state schema and reducer. \n", - " \n", - "We've also shown a number of ways to trim or filter messages in graph state. \n", - "\n", - "## Goals\n", - "\n", - "Now, let's take it one step further! \n", - "\n", - "Rather than just trimming or filtering messages, we'll show how to use LLMs to produce a running summary of the conversation.\n", - " \n", - "This allows us to retain a compressed representation of the full conversation, rather than just removing it with trimming or filtering.\n", - "\n", - "We'll incorporate this summarization into a simple Chatbot. \n", - "\n", - "And we'll equip that Chatbot with memory, supporting long-running conversations without incurring high token cost / latency. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "000a6daa-92ad-4e57-a060-d1c81176eb0d", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "%pip install --quiet -U langchain_core langgraph langchain_openai" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "09201a62", - "metadata": {}, - "outputs": [], - "source": [ - "import os, getpass\n", - "\n", - "def _set_env(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass.getpass(f\"{var}: \")\n", - "\n", - "_set_env(\"OPENAI_API_KEY\")" - ] - }, - { - "cell_type": "markdown", - "id": "dfddfce9-3a9b-4b35-a76d-28265515aabd", - "metadata": {}, - "source": [ - "We'll use [LangSmith](https://docs.smith.langchain.com/) for [tracing](https://docs.smith.langchain.com/concepts/tracing)." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "464856d4", - "metadata": {}, - "outputs": [], - "source": [ - "_set_env(\"LANGCHAIN_API_KEY\")\n", - "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", - "os.environ[\"LANGCHAIN_PROJECT\"] = \"langchain-academy\"" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "537ade30-6a0e-4b6b-8bcd-ce90790b6392", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_openai import ChatOpenAI\n", - "model = ChatOpenAI(model=\"gpt-4o\",temperature=0)" - ] - }, - { - "cell_type": "markdown", - "id": "db3afac3-8b7a-45db-a3c1-7e4125c1bc8b", - "metadata": {}, - "source": [ - "We'll use `MessagesState`, as before.\n", - "\n", - "In addition to the built-in `messages` key, we'll now include a custom key (`summary`)." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "948e60f0-5c76-4235-b40e-cf523205d40e", - "metadata": {}, - "outputs": [], - "source": [ - "from langgraph.graph import MessagesState\n", - "class State(MessagesState):\n", - " summary: str" - ] - }, - { - "cell_type": "markdown", - "id": "6855ea31-5cc1-4277-a189-0b72459f67ec", - "metadata": {}, - "source": [ - "We'll define a node to call our LLM that incorporates a summary, if it exists, into the prompt." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "c3f7d19b-afe0-4381-9b1a-0a832b162e7b", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_core.messages import SystemMessage, HumanMessage, RemoveMessage\n", - "\n", - "# Define the logic to call the model\n", - "def call_model(state: State):\n", - " \n", - " # Get summary if it exists\n", - " summary = state.get(\"summary\", \"\")\n", - "\n", - " # If there is summary, then we add it\n", - " if summary:\n", - " \n", - " # Add summary to system message\n", - " system_message = f\"Summary of conversation earlier: {summary}\"\n", - "\n", - " # Append summary to any newer messages\n", - " messages = [SystemMessage(content=system_message)] + state[\"messages\"]\n", - " \n", - " else:\n", - " messages = state[\"messages\"]\n", - " \n", - " response = model.invoke(messages)\n", - " return {\"messages\": response}" - ] - }, - { - "cell_type": "markdown", - "id": "6882042c-b42d-4d52-a6a7-6ec8efa72450", - "metadata": {}, - "source": [ - "We'll define a node to produce a summary.\n", - "\n", - "Note, here we'll use `RemoveMessage` to filter our state after we've produced the summary." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "78c7aa59-3760-4e76-93f1-bc713e3ec39e", - "metadata": {}, - "outputs": [], - "source": [ - "def summarize_conversation(state: State):\n", - " \n", - " # First, we get any existing summary\n", - " summary = state.get(\"summary\", \"\")\n", - "\n", - " # Create our summarization prompt \n", - " if summary:\n", - " \n", - " # A summary already exists\n", - " summary_message = (\n", - " f\"This is summary of the conversation to date: {summary}\\n\\n\"\n", - " \"Extend the summary by taking into account the new messages above:\"\n", - " )\n", - " \n", - " else:\n", - " summary_message = \"Create a summary of the conversation above:\"\n", - "\n", - " # Add prompt to our history\n", - " messages = state[\"messages\"] + [HumanMessage(content=summary_message)]\n", - " response = model.invoke(messages)\n", - " \n", - " # Delete all but the 2 most recent messages\n", - " delete_messages = [RemoveMessage(id=m.id) for m in state[\"messages\"][:-2]]\n", - " return {\"summary\": response.content, \"messages\": delete_messages}" - ] - }, - { - "cell_type": "markdown", - "id": "f982993e-f4be-4ff7-9a38-886f75398b3d", - "metadata": {}, - "source": [ - "We'll add a conditional edge to determine whether to produce a summary based on the conversation length." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "b507665d-7f5d-442a-b498-218c94c5dd8b", - "metadata": {}, - "outputs": [], - "source": [ - "from langgraph.graph import END\n", - "# Determine whether to end or summarize the conversation\n", - "def should_continue(state: State):\n", - " \n", - " \"\"\"Return the next node to execute.\"\"\"\n", - " \n", - " messages = state[\"messages\"]\n", - " \n", - " # If there are more than six messages, then we summarize the conversation\n", - " if len(messages) > 6:\n", - " return \"summarize_conversation\"\n", - " \n", - " # Otherwise we can just end\n", - " return END" - ] - }, - { - "cell_type": "markdown", - "id": "5a838f4c-7067-4f7f-a4c4-6654e11214cd", - "metadata": {}, - "source": [ - "## Adding memory\n", - "\n", - "Recall that [state is transient](https://github.com/langchain-ai/langgraph/discussions/352#discussioncomment-9291220) to a single graph execution.\n", - "\n", - "This limits our ability to have multi-turn conversations with interruptions. \n", - "\n", - "As introduced at the end of Module 1, we can use [persistence](https://langchain-ai.github.io/langgraph/how-tos/persistence/) to address this! \n", - " \n", - "LangGraph can use a checkpointer to automatically save the graph state after each step.\n", - "\n", - "This built-in persistence layer gives us memory, allowing LangGraph to pick up from the last state update. \n", - "\n", - "As we previously showed, one of the easiest to work with is `MemorySaver`, an in-memory key-value store for Graph state.\n", - "\n", - "All we need to do is compile the graph with a checkpointer, and our graph has memory!" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "1d57516d-f9f1-4d3c-a84a-7277b5ce6df6", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from IPython.display import Image, display\n", - "from langgraph.checkpoint.memory import MemorySaver\n", - "from langgraph.graph import StateGraph, START\n", - "\n", - "# Define a new graph\n", - "workflow = StateGraph(State)\n", - "workflow.add_node(\"conversation\", call_model)\n", - "workflow.add_node(summarize_conversation)\n", - "\n", - "# Set the entrypoint as conversation\n", - "workflow.add_edge(START, \"conversation\")\n", - "workflow.add_conditional_edges(\"conversation\", should_continue)\n", - "workflow.add_edge(\"summarize_conversation\", END)\n", - "\n", - "# Compile\n", - "memory = MemorySaver()\n", - "graph = workflow.compile(checkpointer=memory)\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "d0bd5d23-ac3b-4496-a049-9a9f97d2feb9", - "metadata": {}, - "source": [ - "## Threads\n", - "\n", - "The checkpointer saves the state at each step as a checkpoint.\n", - "\n", - "These saved checkpoints can be grouped into a `thread` of conversation.\n", - "\n", - "Think about Slack as an analog: different channels carry different conversations.\n", - "\n", - "Threads are like Slack channels, capturing grouped collections of state (e.g., conversation).\n", - "\n", - "Below, we use `configurable` to set a thread ID.\n", - "\n", - "![state.jpg](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbadf3b379c2ee621adfd1_chatbot-summarization1.png)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "2566c93b-13e6-4a53-bc0f-b00fff691d30", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "Hi Lance! How can I assist you today?\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "You mentioned that your name is Lance. How can I help you today, Lance?\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "That's awesome, Lance! The San Francisco 49ers have a rich history and a passionate fan base. Do you have a favorite player or a memorable game that stands out to you?\n" - ] - } - ], - "source": [ - "# Create a thread\n", - "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", - "\n", - "# Start conversation\n", - "input_message = HumanMessage(content=\"hi! I'm Lance\")\n", - "output = graph.invoke({\"messages\": [input_message]}, config) \n", - "for m in output['messages'][-1:]:\n", - " m.pretty_print()\n", - "\n", - "input_message = HumanMessage(content=\"what's my name?\")\n", - "output = graph.invoke({\"messages\": [input_message]}, config) \n", - "for m in output['messages'][-1:]:\n", - " m.pretty_print()\n", - "\n", - "input_message = HumanMessage(content=\"i like the 49ers!\")\n", - "output = graph.invoke({\"messages\": [input_message]}, config) \n", - "for m in output['messages'][-1:]:\n", - " m.pretty_print()" - ] - }, - { - "cell_type": "markdown", - "id": "531e5b63-5e8b-486e-baa0-a45521e2fbc2", - "metadata": {}, - "source": [ - "Now, we don't yet have a summary of the state because we still have < = 6 messages.\n", - "\n", - "This was set in `should_continue`. \n", - "\n", - "```\n", - " # If there are more than six messages, then we summarize the conversation\n", - " if len(messages) > 6:\n", - " return \"summarize_conversation\"\n", - "```\n", - "\n", - "We can pick up the conversation because we have the thread." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "91b82aaa-17f9-49e2-9528-f4b22e23ebcb", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "''" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "graph.get_state(config).values.get(\"summary\",\"\")" - ] - }, - { - "cell_type": "markdown", - "id": "068a93e9-f716-4980-8edf-94115017d865", - "metadata": {}, - "source": [ - "The `config` with thread ID allows us to proceed from the previously logged state!" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "24b34f0f-62ef-4008-8e96-480cbe92ea3e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "Yes, Nick Bosa is indeed one of the highest-paid defensive players in the NFL. In September 2023, he signed a record-breaking contract extension with the San Francisco 49ers, making him the highest-paid defensive player at that time. His performance on the field has certainly earned him that recognition. It's great to hear you're a fan of such a talented player!\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/var/folders/l9/bpjxdmfx7lvd1fbdjn38y5dh0000gn/T/ipykernel_18661/23381741.py:23: LangChainBetaWarning: The class `RemoveMessage` is in beta. It is actively being worked on, so the API may change.\n", - " delete_messages = [RemoveMessage(id=m.id) for m in state[\"messages\"][:-2]]\n" - ] - } - ], - "source": [ - "input_message = HumanMessage(content=\"i like Nick Bosa, isn't he the highest paid defensive player?\")\n", - "output = graph.invoke({\"messages\": [input_message]}, config) \n", - "for m in output['messages'][-1:]:\n", - " m.pretty_print()" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "22f1b35f-e4bb-47f6-87b1-d84d8aed9aa9", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "\"Lance introduced himself and mentioned that he is a fan of the San Francisco 49ers. He specifically likes Nick Bosa and inquired if Bosa is the highest-paid defensive player. I confirmed that Nick Bosa signed a record-breaking contract extension in September 2023, making him the highest-paid defensive player at that time, and acknowledged Bosa's talent and Lance's enthusiasm for the player.\"" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "graph.get_state(config).values.get(\"summary\",\"\")" - ] - }, - { - "cell_type": "markdown", - "id": "ad7cc0ab-905a-4037-b7cb-69db5b89591e", - "metadata": {}, - "source": [ - "## LangSmith\n", - "\n", - "Let's review the trace!" - ] - } - ], - "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.12.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/module-2/multiple-schemas.ipynb b/module-2/multiple-schemas.ipynb deleted file mode 100644 index cf27902bd..000000000 --- a/module-2/multiple-schemas.ipynb +++ /dev/null @@ -1,353 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "e2996fea", - "metadata": {}, - "source": [ - "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-2/multiple-schemas.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239434-lesson-3-multiple-schemas)" - ] - }, - { - "cell_type": "markdown", - "id": "693d9912-8d56-46a2-a445-3ee5651fe433", - "metadata": {}, - "source": [ - "# Multiple Schemas\n", - "\n", - "## Review\n", - "\n", - "We just covered state schema and reducers.\n", - "\n", - "Typically, all graph nodes communicate with a single schema. \n", - "\n", - "Also, this single schema contains the graph's input and output keys / channels.\n", - "\n", - "## Goals\n", - "\n", - "But, there are cases where we may want a bit more control over this:\n", - "\n", - "* Internal nodes may pass information that is *not required* in the graph's input / output.\n", - "\n", - "* We may also want to use different input / output schemas for the graph. The output might, for example, only contain a single relevant output key.\n", - "\n", - "We'll discuss a few ways to customize graphs with multiple schemas." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "4d727cc2-5a43-4eb5-9d69-82bbbcc35bd9", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "%pip install --quiet -U langgraph" - ] - }, - { - "cell_type": "markdown", - "id": "29b3d109-6bf2-4271-9775-556ee4bd900d", - "metadata": {}, - "source": [ - "## Private State\n", - "\n", - "First, let's cover the case of passing [private state](https://langchain-ai.github.io/langgraph/how-tos/pass_private_state/) between nodes.\n", - "\n", - "This is useful for anything needed as part of the intermediate working logic of the graph, but not relevant for the overall graph input or output.\n", - "\n", - "We'll define an `OverallState` and a `PrivateState`.\n", - "\n", - "`node_2` uses `PrivateState` as input, but writes out to `OverallState`." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "038ca2e4-7d6d-49d5-b213-b38469cde434", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from typing_extensions import TypedDict\n", - "from IPython.display import Image, display\n", - "from langgraph.graph import StateGraph, START, END\n", - "\n", - "class OverallState(TypedDict):\n", - " foo: int\n", - "\n", - "class PrivateState(TypedDict):\n", - " baz: int\n", - "\n", - "def node_1(state: OverallState) -> PrivateState:\n", - " print(\"---Node 1---\")\n", - " return {\"baz\": state['foo'] + 1}\n", - "\n", - "def node_2(state: PrivateState) -> OverallState:\n", - " print(\"---Node 2---\")\n", - " return {\"foo\": state['baz'] + 1}\n", - "\n", - "# Build graph\n", - "builder = StateGraph(OverallState)\n", - "builder.add_node(\"node_1\", node_1)\n", - "builder.add_node(\"node_2\", node_2)\n", - "\n", - "# Logic\n", - "builder.add_edge(START, \"node_1\")\n", - "builder.add_edge(\"node_1\", \"node_2\")\n", - "builder.add_edge(\"node_2\", END)\n", - "\n", - "# Add\n", - "graph = builder.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "3dc9cd64-4bd3-4c0a-8f8f-d58c551428e3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "---Node 1---\n", - "---Node 2---\n" - ] - }, - { - "data": { - "text/plain": [ - "{'foo': 3}" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "graph.invoke({\"foo\" : 1})" - ] - }, - { - "cell_type": "markdown", - "id": "50a29f37-f653-4a56-ad0a-345d7f632ea0", - "metadata": {}, - "source": [ - "`baz` is only included in `PrivateState`.\n", - "\n", - "`node_2` uses `PrivateState` as input, but writes out to `OverallState`.\n", - "\n", - "So, we can see that `baz` is excluded from the graph output because it is not in `OverallState`." - ] - }, - { - "cell_type": "markdown", - "id": "75a8362f-009b-4ec2-abe5-8fb318e39966", - "metadata": {}, - "source": [ - "## Input / Output Schema\n", - "\n", - "By default, `StateGraph` takes in a single schema and all nodes are expected to communicate with that schema. \n", - "\n", - "However, it is also possible to [define explicit input and output schemas for a graph](https://langchain-ai.github.io/langgraph/how-tos/input_output_schema/?h=input+outp).\n", - "\n", - "Often, in these cases, we define an \"internal\" schema that contains *all* keys relevant to graph operations.\n", - "\n", - "But, we use specific `input` and `output` schemas to constrain the input and output.\n", - "\n", - "First, let's just run the graph with a single schema." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "5323068a-907a-438c-8db5-46e5d452ad72", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "class OverallState(TypedDict):\n", - " question: str\n", - " answer: str\n", - " notes: str\n", - "\n", - "def thinking_node(state: OverallState):\n", - " return {\"answer\": \"bye\", \"notes\": \"... his is name is Lance\"}\n", - "\n", - "def answer_node(state: OverallState):\n", - " return {\"answer\": \"bye Lance\"}\n", - "\n", - "graph = StateGraph(OverallState)\n", - "graph.add_node(\"answer_node\", answer_node)\n", - "graph.add_node(\"thinking_node\", thinking_node)\n", - "graph.add_edge(START, \"thinking_node\")\n", - "graph.add_edge(\"thinking_node\", \"answer_node\")\n", - "graph.add_edge(\"answer_node\", END)\n", - "\n", - "graph = graph.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "markdown", - "id": "853fc90c-bf82-4d51-b3a5-ceb0b0ae5233", - "metadata": {}, - "source": [ - "Notice that the output of invoke contains all keys in `OverallState`. " - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "507d35e6-f65c-4e89-b26e-a0ef7b90be83", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'question': 'hi', 'answer': 'bye Lance', 'notes': '... his is name is Lance'}" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "graph.invoke({\"question\":\"hi\"})" - ] - }, - { - "cell_type": "markdown", - "id": "e5a899c3-e1b0-48eb-9a36-8c787e378ef0", - "metadata": {}, - "source": [ - "Now, let's use a specific `input` and `output` schema with our graph.\n", - "\n", - "Here, `input` / `output` schemas perform *filtering* on what keys are permitted on the input and output of the graph. \n", - "\n", - "In addition, we can use a type hint `state: InputState` to specify the input schema of each of our nodes.\n", - "\n", - "This is important when the graph is using multiple schemas.\n", - "\n", - "We use type hints below to, for example, show that the output of `answer_node` will be filtered to `OutputState`. " - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "682b3d10-c78a-41c2-a5ff-842e1688c95f", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "{'answer': 'bye Lance'}" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "class InputState(TypedDict):\n", - " question: str\n", - "\n", - "class OutputState(TypedDict):\n", - " answer: str\n", - "\n", - "class OverallState(TypedDict):\n", - " question: str\n", - " answer: str\n", - " notes: str\n", - "\n", - "def thinking_node(state: InputState):\n", - " return {\"answer\": \"bye\", \"notes\": \"... his is name is Lance\"}\n", - "\n", - "def answer_node(state: OverallState) -> OutputState:\n", - " return {\"answer\": \"bye Lance\"}\n", - "\n", - "graph = StateGraph(OverallState, input=InputState, output=OutputState)\n", - "graph.add_node(\"answer_node\", answer_node)\n", - "graph.add_node(\"thinking_node\", thinking_node)\n", - "graph.add_edge(START, \"thinking_node\")\n", - "graph.add_edge(\"thinking_node\", \"answer_node\")\n", - "graph.add_edge(\"answer_node\", END)\n", - "\n", - "graph = graph.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))\n", - "\n", - "graph.invoke({\"question\":\"hi\"})" - ] - }, - { - "cell_type": "markdown", - "id": "f1e5ff21", - "metadata": {}, - "source": [ - "We can see the `output` schema constrains the output to only the `answer` key." - ] - } - ], - "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.12.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/module-2/state-reducers.ipynb b/module-2/state-reducers.ipynb deleted file mode 100644 index f2d48eb4f..000000000 --- a/module-2/state-reducers.ipynb +++ /dev/null @@ -1,854 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "36b496da", - "metadata": {}, - "source": [ - "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-2/state-reducers.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239428-lesson-2-state-reducers)" - ] - }, - { - "cell_type": "markdown", - "id": "b7ae0ff7-497d-4c31-a57a-00fe92799232", - "metadata": {}, - "source": [ - "# State Reducers \n", - "\n", - "## Review\n", - "\n", - "We covered a few different ways to define LangGraph state schema, including `TypedDict`, `Pydantic`, or `Dataclasses`.\n", - " \n", - "## Goals\n", - "\n", - "Now, we're going to dive into reducers, which specify how state updates are performed on specific keys / channels in the state schema." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "398c5e8e-641f-4be6-b1e8-7531f86bd2e9", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "%pip install --quiet -U langchain_core langgraph" - ] - }, - { - "cell_type": "markdown", - "id": "4d5bd534-c5be-48fe-91bc-af39ebee76b7", - "metadata": {}, - "source": [ - "## Default overwriting state\n", - "\n", - "Let's use a `TypedDict` as our state schema." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "64e2438c-9353-4256-bc3c-1bb830374c0b", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from typing_extensions import TypedDict\n", - "from IPython.display import Image, display\n", - "from langgraph.graph import StateGraph, START, END\n", - "\n", - "class State(TypedDict):\n", - " foo: int\n", - "\n", - "def node_1(state):\n", - " print(\"---Node 1---\")\n", - " return {\"foo\": state['foo'] + 1}\n", - "\n", - "# Build graph\n", - "builder = StateGraph(State)\n", - "builder.add_node(\"node_1\", node_1)\n", - "\n", - "# Logic\n", - "builder.add_edge(START, \"node_1\")\n", - "builder.add_edge(\"node_1\", END)\n", - "\n", - "# Add\n", - "graph = builder.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "69634df1-4f02-446f-b5cf-6a83d1e15e37", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "---Node 1---\n" - ] - }, - { - "data": { - "text/plain": [ - "{'foo': 2}" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "graph.invoke({\"foo\" : 1})" - ] - }, - { - "cell_type": "markdown", - "id": "775a099c-c41c-412f-8f05-e7436388ae79", - "metadata": {}, - "source": [ - "Let's look at the state update, `return {\"foo\": state['foo'] + 1}`.\n", - "\n", - "As discussed before, by default LangGraph doesn't know the preferred way to update the state.\n", - " \n", - "So, it will just overwrite the value of `foo` in `node_1`: \n", - "\n", - "```\n", - "return {\"foo\": state['foo'] + 1}\n", - "```\n", - " \n", - "If we pass `{'foo': 1}` as input, the state returned from the graph is `{'foo': 2}`.\n", - "\n", - "## Branching\n", - "\n", - "Let's look at a case where our nodes branch." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "2b8d6ad4-2991-4325-933d-67057bc150f4", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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bX3IIiKAiIgIiICIiAiIgIiICIiAiIgIiICIiDlyeNr5jHWaNuMS1rMbopGHpu0jY/qUNis2/FWIcPm5mMunzKtt55WXR6Niena7fKZ395G47rGufIY+rlactS7Wit1ZRyyQTsD2PHqIPQrbRXFtWrh8P771dCKsDQNev0oZbMY2PrtFDedIxv6hJzgD5hsB6l+fgTY9qs9/+aH7pZ6mHPCv3T/JaM1oRZXpTH5XM6w1tjLOqcwK2Hu14Kpjlh5i19SGV3P8X380jtu7pt+tWz8CbHp1TniP/rQ/dJ9nh9fuktGax2bUNKvJPYmjggjaXPllcGtaB3kk9AFXIQ7WWSq2zG6PBUpBNX7Rpa65MNw2TY/7Ju+7Ser3bOGzWtc/61dB4uOeOe463l54yCx2Tsvna0g7giMnkBB67hu/d16BWNNajD8zfOf0/vsN0cBERedBERAREQEREBERAREQEREBERAREQEREBERAREQEREGe8PyDxJ4o7EkjJ0t/wB31/n+xaEs94f7/CTxQ7v6TpdwG/8AR9fv2/7rQkBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQZ5w+G3Eril5wP8ApOl0A7v9HVu9aGs84e7fCVxS2PXynS36bf8Al1b/AIrQ0BERAREQEREBERAREQEREBERAREQEREBERAREQERVXLarvOyE9HB0a9ySseSzZuTuiijeQCGN5WOL3bEE9wG46k7gbMPDqxJtStrrUipHl3WH6Bg/e5vu08u6w/QMH73N92vRstecd4LLuovVWVt4LTGYyVDHOy96nTmsV8ex/I61Ixhc2IO2PKXEBu+x237iq55d1h+gYP3ub7tPLusP0DB+9zfdpstecd4LPJXg4+HHc4qcdMhgcZw6lin1PeisTyvyw2oQw1o4pHu+IHPsIiQCRuSG7jvXuxeaOE/g/TcH+JmttaYfH4Y3tSyBwgdYlaykwnnkjj2j+S+Tzvm2aPR12Dy7rD9Awfvc33abLXnHeCy7oqR5d1h+gYP3ub7tPLusP0DB+9zfdpstecd4LLuipIzurweuPwhHqFyYb/39l0U9p7UIzQsQzQGnkKrg2xWLucN335XNdsOZjgDsdh3EEAgga68CuiNabW9E3LJhERedBERAREQEREBERAREQEREBERAREQFn2lDu/PE95y9rr/APfs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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "class State(TypedDict):\n", - " foo: int\n", - "\n", - "def node_1(state):\n", - " print(\"---Node 1---\")\n", - " return {\"foo\": state['foo'] + 1}\n", - "\n", - "def node_2(state):\n", - " print(\"---Node 2---\")\n", - " return {\"foo\": state['foo'] + 1}\n", - "\n", - "def node_3(state):\n", - " print(\"---Node 3---\")\n", - " return {\"foo\": state['foo'] + 1}\n", - "\n", - "# Build graph\n", - "builder = StateGraph(State)\n", - "builder.add_node(\"node_1\", node_1)\n", - "builder.add_node(\"node_2\", node_2)\n", - "builder.add_node(\"node_3\", node_3)\n", - "\n", - "# Logic\n", - "builder.add_edge(START, \"node_1\")\n", - "builder.add_edge(\"node_1\", \"node_2\")\n", - "builder.add_edge(\"node_1\", \"node_3\")\n", - "builder.add_edge(\"node_2\", END)\n", - "builder.add_edge(\"node_3\", END)\n", - "\n", - "# Add\n", - "graph = builder.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "106729b3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "---Node 1---\n", - "---Node 2---\n", - "---Node 3---\n", - "InvalidUpdateError occurred: At key 'foo': Can receive only one value per step. Use an Annotated key to handle multiple values.\n" - ] - } - ], - "source": [ - "from langgraph.errors import InvalidUpdateError\n", - "try:\n", - " graph.invoke({\"foo\" : 1})\n", - "except InvalidUpdateError as e:\n", - " print(f\"InvalidUpdateError occurred: {e}\")\n" - ] - }, - { - "cell_type": "markdown", - "id": "b9717ccd-3d34-476a-8952-e6a7629ebefe", - "metadata": {}, - "source": [ - "We see a problem! \n", - "\n", - "Node 1 branches to nodes 2 and 3.\n", - "\n", - "Nodes 2 and 3 run in parallel, which means they run in the same step of the graph.\n", - "\n", - "They both attempt to overwrite the state *within the same step*. \n", - "\n", - "This is ambiguous for the graph! Which state should it keep? " - ] - }, - { - "cell_type": "markdown", - "id": "f1609cf7-dc47-4926-a154-77904b6cc550", - "metadata": {}, - "source": [ - "## Reducers\n", - "\n", - "[Reducers](https://langchain-ai.github.io/langgraph/concepts/low_level/#reducers) give us a general way to address this problem.\n", - "\n", - "They specify how to perform updates.\n", - "\n", - "We can use the `Annotated` type to specify a reducer function. \n", - "\n", - "For example, in this case let's append the value returned from each node rather than overwriting them.\n", - "\n", - "We just need a reducer that can perform this: `operator.add` is a function from Python's built-in operator module.\n", - "\n", - "When `operator.add` is applied to lists, it performs list concatenation." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "103d808c-55ec-44f2-a688-7b5e1572875a", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCADqAGIDASIAAhEBAxEB/8QAHQABAAMAAwEBAQAAAAAAAAAAAAUGBwIECAMBCf/EAEUQAAEDBAADBAYGBgkDBQAAAAEAAgMEBQYRBxIhExQxQQgVIjJRlBZCYXF00yM2VFahshc1VWJydYGz0SRSkQklk5Wx/8QAGgEBAQADAQEAAAAAAAAAAAAAAAECAwUGBP/EADERAAIBAgEICgIDAQAAAAAAAAABAgMRMQQSIUFRcaHRBRQzYWKRkrHB8CNSEzLh8f/aAAwDAQACEQMRAD8A/qmiIgCLqXW5wWa3zVlSXCGIbIY0uc4k6DWtHVziSAAOpJAHioP6OVWTfp7/ACyx0ztmO0U8pZGxvl2zmncr/iN8g8AHa53bYwTWdJ2X3AqRM1N9ttFIWVFxpIHg6LZZ2tI/0JXx+lVl/tig+ZZ/yvlT4Xj9JGGQWK2wsAA5WUkYHToPJfX6K2X+x6D5Zn/Cz/D38BoH0qsv9sUHzLP+VyjyW0SvDWXWie4+DW1DCT/FcforZf7HoPlmf8LjJiNimYWSWW3PafFrqWMg/wAE/D38BoJVrg4Aggg9QR5r9VZdhEFtcZ8elNjqdl3ZQjdLKT5Ph8Nb82crv73ipKxXo3aOaKog7lcaV3Z1VKXcwY7yc12hzscOrXaGx0Ia4Oa3GUFbOg7riLbCUREWkgREQBERAVe56u2dWugfp1Pb6d9yew/WlJ7OE/aAO2Oj5hp8QCLQqw4dz4lMe/YbcLV2bDrpzQSkkb+JE+wPPlPwVnX0VcIpYW/7xuVmQt9K7hnXR5I205G271Vho6itqYqWkqHMcyEhryyQRlsgDnNG2F3vBRnB/wBLPEuJXCOqza4TPsotcMc14p+61MjKIyPc2NrXmIdtvl8Yw7r8FkvCKx5TZ+JFwxPBsezKw8L6yguXf7VmNvEFLbap/N2RoJiS57HyO2WgkAOLjs+7GYpf+Jtl9EmiwywYbmmNZTjMlLR3aoZbQ2eakdUSdu63OcSJpAwDq0dOfofNfOQ9HWr0muGd7wO+ZlRZTFPj1jc1tynFLOJaUuIDeeAx9qNk9PY69fgVQOJHpyYFi2LUN4x6pkySOou1PbXyCiq4YWMedySteYdS8rOoaw+1saK87XDh7lVXjHpDi3YnxCqaXJbJaZLU/KaaapuVe6GbkkDyA484JJEZ9oMAOgF6P9J3DbzX8B8TGP2GrutRjt1tFzktFvh5qh0NO4c7I4/NwB90fBAbliWV2zOMcob7Zp5Km11rO0gllgkgc5uyNlkjWub1B6EBR+Q6tOU2C5R6b3qV1sqfH22OY98f38r26HwEjvj17WD5V9N8Wob36outhFWHkW+90vdqyINe5v6SPZ5d8vMBvwcF1cxHe7njFC3ZkkuQqDoe6yKN7y4/Ac3I373BfRQ/vbVZ+xViWdERfOQIiIAiIgInIrM670sL6d7YbhSSipo5nglrJACNOA6lrmuc1wH1XHXXRSy5FBdZJKWVvc7rAP09DI722eXM3w54z5PHQ+HQggSyjb1jttyGKNlwpI6gxEuikO2yROI0Sx405h1020grdGUWs2eHt9+993kkirBwYsHLBkV9p2dNN74JdD75GuP/AJO1+fQmo/eq/f8AzQ/lLLMp/vwYstpaEWV8Kbfdcz4eWS9XHKbyK2shMkogkhDN8xHQdmfh8VbPoPM7o/J789vmO8Rt/i2MH+Kfx0/34MWW0mrteqKx03b1s4iYTysaGl75HeTWMaC57j5NaCT5BR1jt1RVXKa+XKHsKuaPsaamJ2aWDYdyuIJBkc4Bz9dOjGgu5OZ32tOIWuz1Rq4oXz1xBBrKyZ9RNo+ID3kloPT2W6HQdOgUyo5Rimoa9f378twREWggREQBERAEREAREQGe+j8QeDWLcpJHdTon/G77StCWe+j9v+hvFt633Y+7rXvu+HRaEgCIiAIiIAiIgCIiAIiIAiIgM89Hwa4M4qA4O/6U9WjofbctDWeej5r+hnFddR3U+I19dy0NAEREAREQBERAEX45wY0ucQ1oGySegCpRzC93YCostsoTbX9Yai4VL45Jm+TxG2M8rT4jZ2R4gLdTpSq3zeRbXLsipHr3MP2Cx/Nzflp69zD9gsfzc35a3dVntXmhYu6yz0keNVZwA4aS5jTYxJlNPTVUUNXTxVfdzBE/Y7Yu5H7Af2bda+vvfTrOevcw/YLH83N+WorLKO/5tjF1sF3tNiqbZc6aSkqIjVzdWPaQdfouhG9g+RAKdVntXmhYxz0EvSPq+NFgnx6HD32m145SNZLd3V4lEsz3ksjEYibr2eck76co6e109Xrz96PvCa7+jzw7gxWz01nrQJ5KmprpqiVslRK4+8QIzrTQ1oHwb9q0r17mH7BY/m5vy06rPavNCxd0VI9e5h+wWP5ub8tPXuYfsFj+bm/LTqs9q80LF3RUpmQ5XCeea1WmoY3qY6etka9w/u80et/AEgfaFaLPdqe+W6GtpS4wyb6PaWua4Etc1wPgQQQR5EFaqlGdNXeHc7ix3URFoIReUEtxm7kHRFHMQR/gKr2MgDG7UAAAKSLQH+AKw5V+rF4/BzfyFV7Gv1ctX4SL+QLo0exe/wCC6iSREWRAiKJyzK7Xg2N3G/3uq7labfCZ6mo7N8nZsHieVgLj9wBKgJZFxY8SMa9p21w2D9i5KgIojK8us2D2Se7364w2y2wkB8850OYnTWgDq5xJADQCSfAKXUAXV4aH/wBluA8hdq7QH4h67S6vDT+prj/m1d/vvVqdjLevkuotqIi5hCLyr9WLx+Dm/kKr2Nfq5avwkX8gVhyr9WLx+Dm/kKr2Nfq5avwkX8gXRo9i9/wXUSS8hcN7jkFq4d8D82ly/Irpdb/eqa1XKG43KSalnp5Wzt12J9kOb2bCJNc5IPM52169VQo+EmJ0GOY3YYLV2dpx2rjrrXT95lPd5o+bkdzF/M7XO7o4kdeo8FGrkPOEuSZIODU/GV+XXtuTsvzmNsQrT6tELbl3TuJpfdJMY9/Xacx3zKP4sUtx4o8HeN+W3XKL3TyWavuNoo7FQ1phooIKZ4YGzQjpK+Qbe5z9nT28utAr0c/gHgUmX/Sd2OxG7d89YbM8vd+9ftHd+fsu18+05ObfXe+q6eXejbw4zm7XS5XnHBUVV1YGV5hrainZVaGg6SOORrXOAA08jmGh1WLiwZFfqjPuKXFbN7NZp6qCixhtFS0kFHlUtldEZaZsveHsjpZe35nOIHOeUCPXLvZPonh/BkNLhNkhyyemqskjpI2XCejP6KWYDTnt6N8T18B4+Cgsz4E4NxAu0VzvljFRcI4BSmpp6qemfLCPCOQxPb2jP7r9jqV9brZc+grXRY5e8XttkiYyOlpK6x1FRLE1rANOkZWRtPUHWmDQ0OutnJJp3BnXpoYdZ7xweuF+raNtTdLTJStoZZHEinMlbTte5rd8vMWjl5tbAJAIBO99VSq8JkzXCZ7BnpoL6ypka6cW2CahheGSNkj03tnvBDmNJ9vrrw10VtVS03AXV4af1Ncf82rv9967S6vDT+prj/m1d/vvWVTsZb18l1FtREXMIReVfqxePwc38hVexr9XLV+Ei/kCuNRBHVQSQyt54pGljmnzBGiFQ4aW/wCM08NubZJr5T07GxQ1lHUQtc9gGm9o2V7NP0OuiQfHpvlHQydpwcL2d76Xb3MlpVidRQnra/fuZdfmqL89PW1+/cy6/NUX5635niXqXMWJtFCetr9+5l1+aovz09bX79zLr81RfnpmeJepcxYm0VTx3N6/K7JSXe14pdam31TOeGbt6RnMNkb06YEdQfEKR9bX79zLr81RfnpmeJepcxYm0UJ62v37mXX5qi/PT1tfv3MuvzVF+emZ4l6lzFibXV4af1Ncf82rv9966DK/Iqj2IsTqqeQ9GvraymbED8XGOR7gPuaT9hVnxmx/R60R0jpu8TF8k803LyiSWR5e8gbOhzOOhs6Ghs6Wqs1Gk43V21g09uwYIlURFzTEIiIAiIgCIiAz7gCNcHcXGtf9MemtfXd9g/8AxaCs99H5vLwaxYaI1THoRo++7yWhIAiIgCIiAIiIAiIgCIiAIiIDPPR8IPBnFdHY7qfLX13eS0NZ76P4cODmLcxcXd2Oy8aPvu8VoSAIiIAiIgCIiAIiIAiIgCisjyyx4dQx1t/vNvsdHJIIWVFyqmU8bpCCQwOeQC7TXHXjoH4JecqsuOlout4oLYXdWisqWRF33cxG1inpQWvB+PHBm+4scnsoufL3u2yPrYx2dXGCY/rdObbmE+QeVujRqzV4xbW5ls2WL0ac5xm+8M8etFryC1XC6U9E6SagpK2KWeJgkILnRtcXAbc0bP8A3D4rXl4b/wDTvwTGuD2B3LJclu9stuV32Tsu7VVTGyalpY3HlYQTtpe7biPgGL2Rbc6xu81Laagv9srKl2tQwVkb3nfh7IO1XQqxV3B+TFmTiIi0ECIiAIiIAiIgCyPiTxMqJKyosljqXU4gcYqyui98P844z5EfWd5HoNEEjQ8yvT8cxK83SMB0tHSSzRtPgXtaS0f+dLzbSQGmpo43PMjwPbkcdl7vEuJ+JOyfvXpOh8jhWk61RXSwXf8A4XBXPyKjhhlfK2MGZ55nzO9qR5+LnHq4/aSvsqnnvEu18Phb4quCuuVyuL3R0VrtcHb1VQWjby1mwNNHUkkAKtVHpFYzS45Bdn0d4533Vtmltnc9VtPVOa5zWPiLt9Q3Q5ebZI1569bKvSg3GUtKMMTUV8qmkgrYzHUQxzxnxbKwOB/0Ko9g40WC8UeQzVkVfj01gY2W40l5p+xmhjc0ua/lBcHBwB1ok/Z1G6JScbarNeMHDygtVDfbLY66OvlmbdKIQR3BrYOaJ8Z2S5oIJ8veaSOoWEsqpxs073aXG3uD0/hHESswqZkNbUT1tgOg+OQmWSkG/fjJ24sA8Wdeg9jRHK7fIZo6iJksT2yRPaHNew7DgeoIPmF5cWx8Dbo+swuShe4u9VVclEwk+EemyRt+5rJWtH2NC890zkcFHrEFZ308zJO6NDREXkgEREAREQEDnlolv2FXy3045qmoo5WQj4ycp5f46XnKkqWVlLDOz3JWB4/1G16rWH8SOHs2O1tVeLbC+e0VD3T1EMTS51I89XOAHUxk7J17pJ+r7vp+hcqhTcqE3a+lb9nIYqx5f49cLLnluTYxkdutAyWO2Mmp6qzi4uoJJWPA0+OZrm8pBB2CevRQbuDdaLBic9pw04/cW5fQ3W6Ubrwa1zKaEyDtXSyPPMQ1w9lmz16Ar0JDNHURNkie2SNw217DsEfYVyXo5ZHTlOU3i93K+raYmFZ5wevma5RxNDIm0lDfLLSU1DWPlbyvnidzcrmglzRsAEkeB6bXzsdm4gZHxM4cXS/4dFYqLHaetp6mphuUM7ZHSU4Y1wY08zWktGgOYjfXWtneU8EeSQzs5N431adOds2gLX+BFFJDiNZWv3yXC4SzRb/7GtbCP9CYiR9+/NZth2J1ef1fZUbnw2tjtVNyZ7rQD7UcR+tIeo2Nhni7rprvRFvoKe1UFNRUkTYKWmjbDDEzwYxoAa0fYAAFxumsqgodXi9N9PcZLQjsIiLxwCIiAIiIAiIgKneuFWKX6pfU1VniZUyHmkmpJH0z5D8XOic0uP2najjwOxEknutf1+F1qvzFfUX1xyzKYLNjUklvZbsoP9BuI/stf/8AbVf5q7FLwZw6mlEjrP3sjXs11TNUsP3ske5v8FdkWTy3KmrOrLzYuzhFEyniZFExscbGhrWMGg0DwAHkFzRF8RAiIgCIiA//2Q==", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from operator import add\n", - "from typing import Annotated\n", - "\n", - "class State(TypedDict):\n", - " foo: Annotated[list[int], add]\n", - "\n", - "def node_1(state):\n", - " print(\"---Node 1---\")\n", - " return {\"foo\": [state['foo'][0] + 1]}\n", - "\n", - "# Build graph\n", - "builder = StateGraph(State)\n", - "builder.add_node(\"node_1\", node_1)\n", - "\n", - "# Logic\n", - "builder.add_edge(START, \"node_1\")\n", - "builder.add_edge(\"node_1\", END)\n", - "\n", - "# Add\n", - "graph = builder.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "9e68cdff-f6e1-4de5-a7bf-6ca0cfee19bf", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "---Node 1---\n" - ] - }, - { - "data": { - "text/plain": [ - "{'foo': [1, 2]}" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "graph.invoke({\"foo\" : [1]})" - ] - }, - { - "cell_type": "markdown", - "id": "63fbd6e0-0207-4049-b86d-c006cbba630b", - "metadata": {}, - "source": [ - "Now, our state key `foo` is a list.\n", - "\n", - "This `operator.add` reducer function will append updates from each node to this list. " - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "768fd0ed-5e24-44a4-b14d-0e299310e105", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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bX3IIiKAiIgIiICIiAiIgIiICIiAiIgIiICIiDlyeNr5jHWaNuMS1rMbopGHpu0jY/qUNis2/FWIcPm5mMunzKtt55WXR6Niena7fKZ395G47rGufIY+rlactS7Wit1ZRyyQTsD2PHqIPQrbRXFtWrh8P771dCKsDQNev0oZbMY2PrtFDedIxv6hJzgD5hsB6l+fgTY9qs9/+aH7pZ6mHPCv3T/JaM1oRZXpTH5XM6w1tjLOqcwK2Hu14Kpjlh5i19SGV3P8X380jtu7pt+tWz8CbHp1TniP/rQ/dJ9nh9fuktGax2bUNKvJPYmjggjaXPllcGtaB3kk9AFXIQ7WWSq2zG6PBUpBNX7Rpa65MNw2TY/7Ju+7Ser3bOGzWtc/61dB4uOeOe463l54yCx2Tsvna0g7giMnkBB67hu/d16BWNNajD8zfOf0/vsN0cBERedBERAREQEREBERAREQEREBERAREQEREBERAREQEREGe8PyDxJ4o7EkjJ0t/wB31/n+xaEs94f7/CTxQ7v6TpdwG/8AR9fv2/7rQkBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQZ5w+G3Eril5wP8ApOl0A7v9HVu9aGs84e7fCVxS2PXynS36bf8Al1b/AIrQ0BERAREQEREBERAREQEREBERAREQEREBERAREQERVXLarvOyE9HB0a9ySseSzZuTuiijeQCGN5WOL3bEE9wG46k7gbMPDqxJtStrrUipHl3WH6Bg/e5vu08u6w/QMH73N92vRstecd4LLuovVWVt4LTGYyVDHOy96nTmsV8ex/I61Ixhc2IO2PKXEBu+x237iq55d1h+gYP3ub7tPLusP0DB+9zfdpstecd4LPJXg4+HHc4qcdMhgcZw6lin1PeisTyvyw2oQw1o4pHu+IHPsIiQCRuSG7jvXuxeaOE/g/TcH+JmttaYfH4Y3tSyBwgdYlaykwnnkjj2j+S+Tzvm2aPR12Dy7rD9Awfvc33abLXnHeCy7oqR5d1h+gYP3ub7tPLusP0DB+9zfdpstecd4LLuipIzurweuPwhHqFyYb/39l0U9p7UIzQsQzQGnkKrg2xWLucN335XNdsOZjgDsdh3EEAgga68CuiNabW9E3LJhERedBERAREQEREBERAREQEREBERAREQFn2lDu/PE95y9rr/APfs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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "def node_1(state):\n", - " print(\"---Node 1---\")\n", - " return {\"foo\": [state['foo'][-1] + 1]}\n", - "\n", - "def node_2(state):\n", - " print(\"---Node 2---\")\n", - " return {\"foo\": [state['foo'][-1] + 1]}\n", - "\n", - "def node_3(state):\n", - " print(\"---Node 3---\")\n", - " return {\"foo\": [state['foo'][-1] + 1]}\n", - "\n", - "# Build graph\n", - "builder = StateGraph(State)\n", - "builder.add_node(\"node_1\", node_1)\n", - "builder.add_node(\"node_2\", node_2)\n", - "builder.add_node(\"node_3\", node_3)\n", - "\n", - "# Logic\n", - "builder.add_edge(START, \"node_1\")\n", - "builder.add_edge(\"node_1\", \"node_2\")\n", - "builder.add_edge(\"node_1\", \"node_3\")\n", - "builder.add_edge(\"node_2\", END)\n", - "builder.add_edge(\"node_3\", END)\n", - "\n", - "# Add\n", - "graph = builder.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "markdown", - "id": "5439baad-5a75-4188-b936-dbe74cdd9078", - "metadata": {}, - "source": [ - "We can see that updates in nodes 2 and 3 are performed concurrently because they are in the same step." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "44598f97-0a59-4ed4-9d9a-e15a98b3d8fb", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "---Node 1---\n", - "---Node 2---\n", - "---Node 3---\n" - ] - }, - { - "data": { - "text/plain": [ - "{'foo': [1, 2, 3, 3]}" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "graph.invoke({\"foo\" : [1]})" - ] - }, - { - "cell_type": "markdown", - "id": "87faaa07-2955-4466-9bca-4b536e05f260", - "metadata": {}, - "source": [ - "Now, let's see what happens if we pass `None` to `foo`.\n", - "\n", - "We see an error because our reducer, `operator.add`, attempts to concatenate `NoneType` pass as input to list in `node_1`. " - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "7f05984b-2bc7-48d1-b070-c8a001a6b59a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TypeError occurred: can only concatenate list (not \"NoneType\") to list\n" - ] - } - ], - "source": [ - "try:\n", - " graph.invoke({\"foo\" : None})\n", - "except TypeError as e:\n", - " print(f\"TypeError occurred: {e}\")" - ] - }, - { - "cell_type": "markdown", - "id": "4f9d4930-ee8f-4ffc-b9e1-3c910b2e15f6", - "metadata": {}, - "source": [ - "## Custom Reducers\n", - "\n", - "To address cases like this, [we can also define custom reducers](https://langchain-ai.github.io/langgraph/how-tos/subgraph/#custom-reducer-functions-to-manage-state). \n", - "\n", - "For example, lets define custom reducer logic to combine lists and handle cases where either or both of the inputs might be `None`." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "3314219d-29ff-4b78-b18e-fa9f7878a02f", - "metadata": {}, - "outputs": [], - "source": [ - "def reduce_list(left: list | None, right: list | None) -> list:\n", - " \"\"\"Safely combine two lists, handling cases where either or both inputs might be None.\n", - "\n", - " Args:\n", - " left (list | None): The first list to combine, or None.\n", - " right (list | None): The second list to combine, or None.\n", - "\n", - " Returns:\n", - " list: A new list containing all elements from both input lists.\n", - " If an input is None, it's treated as an empty list.\n", - " \"\"\"\n", - " if not left:\n", - " left = []\n", - " if not right:\n", - " right = []\n", - " return left + right\n", - "\n", - "class DefaultState(TypedDict):\n", - " foo: Annotated[list[int], add]\n", - "\n", - "class CustomReducerState(TypedDict):\n", - " foo: Annotated[list[int], reduce_list]" - ] - }, - { - "cell_type": "markdown", - "id": "dcdea26a-38d0-4faf-9bf6-cd52eb902635", - "metadata": {}, - "source": [ - "In `node_1`, we append the value 2." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "f5f270db-6eff-47c9-853b-dfb8108ff28c", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TypeError occurred: can only concatenate list (not \"NoneType\") to list\n" - ] - } - ], - "source": [ - "def node_1(state):\n", - " print(\"---Node 1---\")\n", - " return {\"foo\": [2]}\n", - "\n", - "# Build graph\n", - "builder = StateGraph(DefaultState)\n", - "builder.add_node(\"node_1\", node_1)\n", - "\n", - "# Logic\n", - "builder.add_edge(START, \"node_1\")\n", - "builder.add_edge(\"node_1\", END)\n", - "\n", - "# Add\n", - "graph = builder.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))\n", - "\n", - "try:\n", - " print(graph.invoke({\"foo\" : None}))\n", - "except TypeError as e:\n", - " print(f\"TypeError occurred: {e}\")" - ] - }, - { - "cell_type": "markdown", - "id": "fd21936b-62f1-4311-9ce5-2c7d08aa35bf", - "metadata": {}, - "source": [ - "Now, try with our custom reducer. We can see that no error is thrown." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "867784bc-796c-4b1e-a4d3-2810395cf5e2", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "---Node 1---\n", - "{'foo': [2]}\n" - ] - } - ], - "source": [ - "# Build graph\n", - "builder = StateGraph(CustomReducerState)\n", - "builder.add_node(\"node_1\", node_1)\n", - "\n", - "# Logic\n", - "builder.add_edge(START, \"node_1\")\n", - "builder.add_edge(\"node_1\", END)\n", - "\n", - "# Add\n", - "graph = builder.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))\n", - "\n", - "try:\n", - " print(graph.invoke({\"foo\" : None}))\n", - "except TypeError as e:\n", - " print(f\"TypeError occurred: {e}\")" - ] - }, - { - "cell_type": "markdown", - "id": "b7ebc65e-c185-4981-a6e7-20fe37d2f8fe", - "metadata": {}, - "source": [ - "## Messages\n", - "\n", - "In module 1, we showed how to use a built-in reducer, `add_messages`, to handle messages in state.\n", - "\n", - "We also showed that [`MessagesState` is a useful shortcut if you want to work with messages](https://langchain-ai.github.io/langgraph/concepts/low_level/#messagesstate). \n", - "\n", - "* `MessagesState` has a built-in `messages` key \n", - "* It also has a built-in `add_messages` reducer for this key\n", - "\n", - "These two are equivalent. \n", - "\n", - "We'll use the `MessagesState` class via `from langgraph.graph import MessagesState` for brevity.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "901e69e5-c4cb-4d58-82fb-3b7d968758e3", - "metadata": {}, - "outputs": [], - "source": [ - "from typing import Annotated\n", - "from langgraph.graph import MessagesState\n", - "from langchain_core.messages import AnyMessage\n", - "from langgraph.graph.message import add_messages\n", - "\n", - "# Define a custom TypedDict that includes a list of messages with add_messages reducer\n", - "class CustomMessagesState(TypedDict):\n", - " messages: Annotated[list[AnyMessage], add_messages]\n", - " added_key_1: str\n", - " added_key_2: str\n", - " # etc\n", - "\n", - "# Use MessagesState, which includes the messages key with add_messages reducer\n", - "class ExtendedMessagesState(MessagesState):\n", - " # Add any keys needed beyond messages, which is pre-built \n", - " added_key_1: str\n", - " added_key_2: str\n", - " # etc" - ] - }, - { - "cell_type": "markdown", - "id": "287805e4-722a-4428-b040-2892b29de870", - "metadata": {}, - "source": [ - "Let's talk a bit more about usage of the `add_messages` reducer." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "c8f61350-4fe0-4a2b-bb24-9305afb3c668", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[AIMessage(content='Hello! How can I assist you?', name='Model', id='f470d868-cf1b-45b2-ae16-48154cd55c12'),\n", - " HumanMessage(content=\"I'm looking for information on marine biology.\", name='Lance', id='a07a88c5-cb2a-4cbd-9485-5edb9d658366'),\n", - " AIMessage(content='Sure, I can help with that. What specifically are you interested in?', name='Model', id='7938e615-86c2-4cbb-944b-c9b2342dee68')]" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from langgraph.graph.message import add_messages\n", - "from langchain_core.messages import AIMessage, HumanMessage\n", - "\n", - "# Initial state\n", - "initial_messages = [AIMessage(content=\"Hello! How can I assist you?\", name=\"Model\"),\n", - " HumanMessage(content=\"I'm looking for information on marine biology.\", name=\"Lance\")\n", - " ]\n", - "\n", - "# New message to add\n", - "new_message = AIMessage(content=\"Sure, I can help with that. What specifically are you interested in?\", name=\"Model\")\n", - "\n", - "# Test\n", - "add_messages(initial_messages , new_message)" - ] - }, - { - "cell_type": "markdown", - "id": "bc492370-0502-43e6-87cc-181c60b3dbdb", - "metadata": {}, - "source": [ - "So we can see that `add_messages` allows us to append messages to the `messages` key in our state.\n", - "\n", - "### Re-writing\n", - "\n", - "Let's show some useful tricks when working with the `add_messages` reducer.\n", - "\n", - "If we pass a message with the same ID as an existing one in our `messages` list, it will get overwritten!" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "1f6f82fd-a5a8-4e98-80f6-bb058f2acc47", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[AIMessage(content='Hello! How can I assist you?', name='Model', id='1'),\n", - " HumanMessage(content=\"I'm looking for information on whales, specifically\", name='Lance', id='2')]" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Initial state\n", - "initial_messages = [AIMessage(content=\"Hello! How can I assist you?\", name=\"Model\", id=\"1\"),\n", - " HumanMessage(content=\"I'm looking for information on marine biology.\", name=\"Lance\", id=\"2\")\n", - " ]\n", - "\n", - "# New message to add\n", - "new_message = HumanMessage(content=\"I'm looking for information on whales, specifically\", name=\"Lance\", id=\"2\")\n", - "\n", - "# Test\n", - "add_messages(initial_messages , new_message)" - ] - }, - { - "cell_type": "markdown", - "id": "f06e7788-7054-4752-99fe-27ebb901f263", - "metadata": {}, - "source": [ - "### Removal\n", - "\n", - "`add_messages` also [enables message removal](https://langchain-ai.github.io/langgraph/how-tos/memory/delete-messages/). \n", - "\n", - "For this, we simply use [RemoveMessage](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.modifier.RemoveMessage.html) from `langchain_core`." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "67ac97e5-efe2-40bc-9fe3-fd4f50922b8b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[RemoveMessage(content='', id='1'), RemoveMessage(content='', id='2')]\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/var/folders/l9/bpjxdmfx7lvd1fbdjn38y5dh0000gn/T/ipykernel_17703/3097054180.py:10: LangChainBetaWarning: The class `RemoveMessage` is in beta. It is actively being worked on, so the API may change.\n", - " delete_messages = [RemoveMessage(id=m.id) for m in messages[:-2]]\n" - ] - } - ], - "source": [ - "from langchain_core.messages import RemoveMessage\n", - "\n", - "# Message list\n", - "messages = [AIMessage(\"Hi.\", name=\"Bot\", id=\"1\")]\n", - "messages.append(HumanMessage(\"Hi.\", name=\"Lance\", id=\"2\"))\n", - "messages.append(AIMessage(\"So you said you were researching ocean mammals?\", name=\"Bot\", id=\"3\"))\n", - "messages.append(HumanMessage(\"Yes, I know about whales. But what others should I learn about?\", name=\"Lance\", id=\"4\"))\n", - "\n", - "# Isolate messages to delete\n", - "delete_messages = [RemoveMessage(id=m.id) for m in messages[:-2]]\n", - "print(delete_messages)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "2d250578-3ec0-452e-91c0-072d785d96db", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[AIMessage(content='So you said you were researching ocean mammals?', name='Bot', id='3'),\n", - " HumanMessage(content='Yes, I know about whales. But what others should I learn about?', name='Lance', id='4')]" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "add_messages(messages , delete_messages)" - ] - }, - { - "cell_type": "markdown", - "id": "5db095c5-6d9a-4e62-a097-0403797511f6", - "metadata": {}, - "source": [ - "We can see that mesage IDs 1 and 2, as noted in `delete_messages` are removed by the reducer.\n", - "\n", - "We'll see this put into practice a bit later." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c8b0347d-cbf0-4164-9cf6-39c4e040a313", - "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.12.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/module-2/state-schema.ipynb b/module-2/state-schema.ipynb deleted file mode 100644 index 189cfd4b5..000000000 --- a/module-2/state-schema.ipynb +++ /dev/null @@ -1,527 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "256d3948", - "metadata": {}, - "source": [ - "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-2/state-schema.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239426-lesson-1-state-schema)" - ] - }, - { - "cell_type": "markdown", - "id": "f118fabe-37b7-4cd4-b7a4-9b0fc3875ca3", - "metadata": {}, - "source": [ - "# State Schema \n", - "\n", - "## Review\n", - "\n", - "In module 1, we laid the foundations! We built up to an agent that can: \n", - "\n", - "* `act` - let the model call specific tools \n", - "* `observe` - pass the tool output back to the model \n", - "* `reason` - let the model reason about the tool output to decide what to do next (e.g., call another tool or just respond directly)\n", - "* `persist state` - use an in memory checkpointer to support long-running conversations with interruptions\n", - " \n", - "And, we showed how to serve it locally in LangGraph Studio or deploy it with LangGraph Cloud. \n", - "\n", - "## Goals\n", - "\n", - "In this module, we're going to build a deeper understanding of both state and memory.\n", - "\n", - "First, let's review a few different ways to define your state schema." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "b9a896f4-8509-456a-9a25-46532342f459", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "%pip install --quiet -U langgraph" - ] - }, - { - "cell_type": "markdown", - "id": "9f7927b0-9909-4e54-b997-ac49c1aeaa09", - "metadata": {}, - "source": [ - "## Schema\n", - "\n", - "When we define a LangGraph `StateGraph`, we use a [state schema](https://langchain-ai.github.io/langgraph/concepts/low_level/#state).\n", - "\n", - "The state schema represents the structure and types of data that our graph will use.\n", - "\n", - "All nodes are expected to communicate with that schema.\n", - "\n", - "LangGraph offers flexibility in how you define your state schema, accommodating various Python [types](https://docs.python.org/3/library/stdtypes.html#type-objects) and validation approaches!\n", - "\n", - "## TypedDict\n", - "\n", - "As we mentioned in Module 1, we can use the `TypedDict` class from python's `typing` module.\n", - "\n", - "It allows you to specify keys and their corresponding value types.\n", - " \n", - "But, note that these are type hints. \n", - "\n", - "They can used by static type checkers (like [mypy](https://github.com/python/mypy)) or IDEs to catch potential type-related errors before the code is run. \n", - "\n", - "But they are not enforced at runtime!" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "eedb39f0-af0f-4794-bc16-65980d278b59", - "metadata": {}, - "outputs": [], - "source": [ - "from typing_extensions import TypedDict\n", - "\n", - "class TypedDictState(TypedDict):\n", - " foo: str\n", - " bar: str" - ] - }, - { - "cell_type": "markdown", - "id": "d5a71661-1086-455f-a5e0-a6d104034a95", - "metadata": {}, - "source": [ - "For more specific value constraints, you can use things like the `Literal` type hint.\n", - "\n", - "Here, `mood` can only be either \"happy\" or \"sad\"." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "4ad9749c-b127-433f-baa3-189a9349e9f6", - "metadata": {}, - "outputs": [], - "source": [ - "from typing import Literal\n", - "\n", - "class TypedDictState(TypedDict):\n", - " name: str\n", - " mood: Literal[\"happy\",\"sad\"]" - ] - }, - { - "cell_type": "markdown", - "id": "c1a9152d-1728-4a67-9e23-1ef622525047", - "metadata": {}, - "source": [ - "We can use our defined state class (e.g., here `TypedDictState`) in LangGraph by simply passing it to `StateGraph`.\n", - "\n", - "And, we can think about each state key just a \"channel\" in our graph. \n", - "\n", - "As discussed in Module 1, we overwrite the value of a specified key or \"channel\" in each node." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "2f7a0d6d-f70b-44ed-86e3-7cdb39873ba4", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import random\n", - "from IPython.display import Image, display\n", - "from langgraph.graph import StateGraph, START, END\n", - "\n", - "def node_1(state):\n", - " print(\"---Node 1---\")\n", - " return {\"name\": state['name'] + \" is ... \"}\n", - "\n", - "def node_2(state):\n", - " print(\"---Node 2---\")\n", - " return {\"mood\": \"happy\"}\n", - "\n", - "def node_3(state):\n", - " print(\"---Node 3---\")\n", - " return {\"mood\": \"sad\"}\n", - "\n", - "def decide_mood(state) -> Literal[\"node_2\", \"node_3\"]:\n", - " \n", - " # Here, let's just do a 50 / 50 split between nodes 2, 3\n", - " if random.random() < 0.5:\n", - "\n", - " # 50% of the time, we return Node 2\n", - " return \"node_2\"\n", - " \n", - " # 50% of the time, we return Node 3\n", - " return \"node_3\"\n", - "\n", - "# Build graph\n", - "builder = StateGraph(TypedDictState)\n", - "builder.add_node(\"node_1\", node_1)\n", - "builder.add_node(\"node_2\", node_2)\n", - "builder.add_node(\"node_3\", node_3)\n", - "\n", - "# Logic\n", - "builder.add_edge(START, \"node_1\")\n", - "builder.add_conditional_edges(\"node_1\", decide_mood)\n", - "builder.add_edge(\"node_2\", END)\n", - "builder.add_edge(\"node_3\", END)\n", - "\n", - "# Add\n", - "graph = builder.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "markdown", - "id": "724bb640-2b0e-46c1-9416-b5bcdb9c17c8", - "metadata": {}, - "source": [ - "Because our state is a dict, we simply invoke the graph with a dict to set an initial value of the `name` key in our state." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "74e09d32-6a08-4250-b19a-1f701828829d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "---Node 1---\n", - "---Node 2---\n" - ] - }, - { - "data": { - "text/plain": [ - "{'name': 'Lance is ... ', 'mood': 'happy'}" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "graph.invoke({\"name\":\"Lance\"})" - ] - }, - { - "cell_type": "markdown", - "id": "70cc5368-18b8-49c7-b561-41888b092311", - "metadata": {}, - "source": [ - "## Dataclass\n", - "\n", - "Python's [dataclasses](https://docs.python.org/3/library/dataclasses.html) provide [another way to define structured data](https://www.datacamp.com/tutorial/python-data-classes).\n", - "\n", - "Dataclasses offer a concise syntax for creating classes that are primarily used to store data." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "d576fc2c-350b-42ad-89e5-f93ae102dbf8", - "metadata": {}, - "outputs": [], - "source": [ - "from dataclasses import dataclass\n", - "\n", - "@dataclass\n", - "class DataclassState:\n", - " name: str\n", - " mood: Literal[\"happy\",\"sad\"]" - ] - }, - { - "cell_type": "markdown", - "id": "64482b93-3c8f-4a30-925f-9be64e4b8b6f", - "metadata": {}, - "source": [ - "To access the keys of a `dataclass`, we just need to modify the subscripting used in `node_1`: \n", - "\n", - "* We use `state.name` for the `dataclass` state rather than `state[\"name\"]` for the `TypedDict` above\n", - "\n", - "You'll notice something a bit odd: in each node, we still return a dictionary to perform the state updates.\n", - " \n", - "This is possible because LangGraph stores each key of your state object separately.\n", - "\n", - "The object returned by the node only needs to have keys (attributes) that match those in the state!\n", - "\n", - "In this case, the `dataclass` has key `name` so we can update it by passing a dict from our node, just as we did when state was a `TypedDict`." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "1e1eda69-916f-4f6e-b400-6e65f73d8716", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "def node_1(state):\n", - " print(\"---Node 1---\")\n", - " return {\"name\": state.name + \" is ... \"}\n", - "\n", - "# Build graph\n", - "builder = StateGraph(DataclassState)\n", - "builder.add_node(\"node_1\", node_1)\n", - "builder.add_node(\"node_2\", node_2)\n", - "builder.add_node(\"node_3\", node_3)\n", - "\n", - "# Logic\n", - "builder.add_edge(START, \"node_1\")\n", - "builder.add_conditional_edges(\"node_1\", decide_mood)\n", - "builder.add_edge(\"node_2\", END)\n", - "builder.add_edge(\"node_3\", END)\n", - "\n", - "# Add\n", - "graph = builder.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "markdown", - "id": "06beb50a-4878-4d7e-ac6c-d60a0f417eb3", - "metadata": {}, - "source": [ - "We invoke with a `dataclass` to set the initial values of each key / channel in our state!" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "8c042325-e93d-43e1-9ac7-a0e20c2fb08d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "---Node 1---\n", - "---Node 3---\n" - ] - }, - { - "data": { - "text/plain": [ - "{'name': 'Lance is ... ', 'mood': 'sad'}" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "graph.invoke(DataclassState(name=\"Lance\",mood=\"sad\"))" - ] - }, - { - "cell_type": "markdown", - "id": "2405e49b-e786-4bf9-ac85-1fb941d01bcd", - "metadata": {}, - "source": [ - "## Pydantic\n", - "\n", - "As mentioned, `TypedDict` and `dataclasses` provide type hints but they don't enforce types at runtime. \n", - " \n", - "This means you could potentially assign invalid values without raising an error!\n", - "\n", - "For example, we can set `mood` to `mad` even though our type hint specifies `mood: list[Literal[\"happy\",\"sad\"]]`." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "522fcc76-abf7-452a-9d7b-000e06942d94", - "metadata": {}, - "outputs": [], - "source": [ - "dataclass_instance = DataclassState(name=\"Lance\", mood=\"mad\")" - ] - }, - { - "cell_type": "markdown", - "id": "4f095c3a-96b5-4318-9303-20424b4455e9", - "metadata": {}, - "source": [ - "[Pydantic](https://docs.pydantic.dev/latest/api/base_model/) is a data validation and settings management library using Python type annotations. \n", - "\n", - "It's particularly well-suited [for defining state schemas in LangGraph](https://langchain-ai.github.io/langgraph/how-tos/state-model/) due to its validation capabilities.\n", - "\n", - "Pydantic can perform validation to check whether data conforms to the specified types and constraints at runtime." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "62e8720e-217f-4b98-837a-af45c3fa577f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Validation Error: 1 validation error for PydanticState\n", - "mood\n", - " Input should be 'happy' or 'sad' [type=literal_error, input_value='mad', input_type=str]\n", - " For further information visit https://errors.pydantic.dev/2.8/v/literal_error\n" - ] - } - ], - "source": [ - "from pydantic import BaseModel, field_validator, ValidationError\n", - "\n", - "class PydanticState(BaseModel):\n", - " name: str\n", - " mood: str # \"happy\" or \"sad\" \n", - "\n", - " @field_validator('mood')\n", - " @classmethod\n", - " def validate_mood(cls, value):\n", - " # Ensure the mood is either \"happy\" or \"sad\"\n", - " if value not in [\"happy\", \"sad\"]:\n", - " raise ValueError(\"Each mood must be either 'happy' or 'sad'\")\n", - " return value\n", - "\n", - "try:\n", - " state = PydanticState(name=\"John Doe\", mood=\"mad\")\n", - "except ValidationError as e:\n", - " print(\"Validation Error:\", e)" - ] - }, - { - "cell_type": "markdown", - "id": "f29913ca-0295-48eb-af4e-cae515dd9a9c", - "metadata": {}, - "source": [ - "We can use `PydanticState` in our graph seamlessly. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "91db3393-b7f8-46e5-8129-0e7539b2804c", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Build graph\n", - "builder = StateGraph(PydanticState)\n", - "builder.add_node(\"node_1\", node_1)\n", - "builder.add_node(\"node_2\", node_2)\n", - "builder.add_node(\"node_3\", node_3)\n", - "\n", - "# Logic\n", - "builder.add_edge(START, \"node_1\")\n", - "builder.add_conditional_edges(\"node_1\", decide_mood)\n", - "builder.add_edge(\"node_2\", END)\n", - "builder.add_edge(\"node_3\", END)\n", - "\n", - "# Add\n", - "graph = builder.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e96c78be-b483-4fa4-949b-62d4274e97ac", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "---Node 1---\n", - "---Node 3---\n" - ] - }, - { - "data": { - "text/plain": [ - "{'name': 'Lance is ... ', 'mood': 'sad'}" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "graph.invoke(PydanticState(name=\"Lance\",mood=\"sad\"))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8119232a-7d56-4abc-b0ef-18bf5f0cc9fd", - "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.12.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/module-2/trim-filter-messages.ipynb b/module-2/trim-filter-messages.ipynb deleted file mode 100644 index 96c1782c5..000000000 --- a/module-2/trim-filter-messages.ipynb +++ /dev/null @@ -1,738 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "fb0ebaf1", - "metadata": {}, - "source": [ - "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-2/trim-filter-messages.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239435-lesson-4-trim-and-filter-messages)" - ] - }, - { - "cell_type": "markdown", - "id": "c52ea2f9-03ff-4647-b782-46867ebed04e", - "metadata": {}, - "source": [ - "# Filtering and trimming messages\n", - "\n", - "## Review\n", - "\n", - "Now, we have a deeper understanding of a few things: \n", - "\n", - "* How to customize the graph state schema\n", - "* How to define custom state reducers\n", - "* How to use multiple graph state schemas\n", - "\n", - "## Goals\n", - "\n", - "Now, we can start using these concepts with models in LangGraph!\n", - " \n", - "In the next few sessions, we'll build towards a chatbot that has long-term memory.\n", - "\n", - "Because our chatbot will use messages, let's first talk a bit more about advanced ways to work with messages in graph state." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d5197aba-5d46-421b-ae3b-4e3034edcfda", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "%pip install --quiet -U langchain_core langgraph langchain_openai" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "768dc606-d5f2-468d-96ea-910b264e0f8a", - "metadata": {}, - "outputs": [], - "source": [ - "import os, getpass\n", - "\n", - "def _set_env(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass.getpass(f\"{var}: \")\n", - "\n", - "_set_env(\"OPENAI_API_KEY\")" - ] - }, - { - "cell_type": "markdown", - "id": "8b64d8d3-e4ac-4961-bdc0-688825eb5864", - "metadata": {}, - "source": [ - "We'll use [LangSmith](https://docs.smith.langchain.com/) for [tracing](https://docs.smith.langchain.com/concepts/tracing).\n", - "\n", - "We'll log to a project, `langchain-academy`. " - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "dd020c79", - "metadata": {}, - "outputs": [], - "source": [ - "_set_env(\"LANGCHAIN_API_KEY\")\n", - "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", - "os.environ[\"LANGCHAIN_PROJECT\"] = \"langchain-academy\"" - ] - }, - { - "cell_type": "markdown", - "id": "72f3fc90-58b6-4f7f-897e-dddf6ae532c7", - "metadata": {}, - "source": [ - "## Messages as state\n", - "\n", - "First, let's define some messages." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "cf11a463-e27a-4a05-b41d-64882e38edca", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "Name: Bot\n", - "\n", - "So you said you were researching ocean mammals?\n", - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "Name: Lance\n", - "\n", - "Yes, I know about whales. But what others should I learn about?\n" - ] - } - ], - "source": [ - "from pprint import pprint\n", - "from langchain_core.messages import AIMessage, HumanMessage\n", - "messages = [AIMessage(f\"So you said you were researching ocean mammals?\", name=\"Bot\")]\n", - "messages.append(HumanMessage(f\"Yes, I know about whales. But what others should I learn about?\", name=\"Lance\"))\n", - "\n", - "for m in messages:\n", - " m.pretty_print()" - ] - }, - { - "cell_type": "markdown", - "id": "b814adcb-6bf9-4b75-be11-e59f933fbd0c", - "metadata": {}, - "source": [ - "Recall we can pass them to a chat model." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "4712e288-e622-48a2-ad3f-a52f65f3ab08", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "AIMessage(content='Great question, Lance! Ocean mammals are a fascinating group of animals. Here are a few more ocean mammals you might want to learn about:\\n\\n1. **Dolphins**: These intelligent and social creatures are known for their playful behavior and complex communication skills. There are several species of dolphins, including the bottlenose dolphin and the common dolphin.\\n\\n2. **Porpoises**: Similar to dolphins but typically smaller and stouter, porpoises are less well-known but equally interesting. The harbor porpoise is one example.\\n\\n3. **Seals**: These include both true seals (like the harbor seal) and eared seals (which include sea lions and fur seals). They are known for their ability to live both in the water and on land.\\n\\n4. **Sea Lions**: These are a type of eared seal, easily recognized by their external ear flaps and their ability to \"walk\" on land using their large flippers.\\n\\n5. **Walruses**: Known for their distinctive long tusks and whiskers, walruses are social animals that live in Arctic regions.\\n\\n6. **Manatees and Dugongs**: Often called \"sea cows,\" these gentle herbivores are found in warm coastal waters and rivers. Manatees are found in the Americas and Africa, while dugongs are found in the Indo-Pacific region.\\n\\n7. **Sea Otters**: Although not exclusively marine, sea otters spend much of their time in the water. They are known for their use of tools to open shellfish.\\n\\n8. **Polar Bears**: While primarily land animals, polar bears are excellent swimmers and spend a significant amount of time hunting on sea ice.\\n\\n9. **Sperm Whales**: Known for their large heads and deep diving abilities, sperm whales are the largest of the toothed whales.\\n\\n10. **Narwhals**: Often called the \"unicorns of the sea,\" these Arctic whales are known for their long, spiral tusk, which is actually an elongated tooth.\\n\\nEach of these animals has unique adaptations and behaviors that make them fascinating subjects of study. Happy researching!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 434, 'prompt_tokens': 39, 'total_tokens': 473}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_25624ae3a5', 'finish_reason': 'stop', 'logprobs': None}, id='run-513c189f-66e0-4c3c-bdb8-5d59934d10f9-0', usage_metadata={'input_tokens': 39, 'output_tokens': 434, 'total_tokens': 473})" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from langchain_openai import ChatOpenAI\n", - "llm = ChatOpenAI(model=\"gpt-4o\")\n", - "llm.invoke(messages)" - ] - }, - { - "cell_type": "markdown", - "id": "fbd1dab8-0af8-4621-8264-ce65065f76ec", - "metadata": {}, - "source": [ - "We can run our chat model in a simple graph with `MessagesState`." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "bbd8c39c-633b-4176-9cc6-8318e42bb5dd", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from IPython.display import Image, display\n", - "from langgraph.graph import MessagesState\n", - "from langgraph.graph import StateGraph, START, END\n", - "\n", - "# Node\n", - "def chat_model_node(state: MessagesState):\n", - " return {\"messages\": llm.invoke(state[\"messages\"])}\n", - "\n", - "# Build graph\n", - "builder = StateGraph(MessagesState)\n", - "builder.add_node(\"chat_model\", chat_model_node)\n", - "builder.add_edge(START, \"chat_model\")\n", - "builder.add_edge(\"chat_model\", END)\n", - "graph = builder.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "3a5a3e4a-ccfd-4d14-81f1-f0de6e11a1e4", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "Name: Bot\n", - "\n", - "So you said you were researching ocean mammals?\n", - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "Name: Lance\n", - "\n", - "Yes, I know about whales. But what others should I learn about?\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "Absolutely, whales are fascinating! But there are many other ocean mammals worth learning about. Here are a few you might find interesting:\n", - "\n", - "1. **Dolphins**: Highly intelligent and social, dolphins are known for their playful behavior and complex communication. There are many species, including the bottlenose dolphin and the orca (killer whale), which is actually the largest member of the dolphin family.\n", - "\n", - "2. **Seals and Sea Lions**: These pinnipeds are often found lounging on beaches or frolicking in the water. Seals tend to be more solitary, while sea lions are social and known for their barking calls.\n", - "\n", - "3. **Manatees and Dugongs**: Often referred to as sea cows, these gentle herbivores graze on seagrasses in shallow coastal areas. Manatees are found in the Atlantic waters, while dugongs are found in the Indo-Pacific region.\n", - "\n", - "4. **Walruses**: Known for their distinctive tusks, walruses are large, social pinnipeds that inhabit the Arctic region. They use their tusks for various purposes, including pulling themselves out of the water and breaking through ice.\n", - "\n", - "5. **Narwhals**: Sometimes called the \"unicorns of the sea,\" narwhals are known for their long, spiral tusks, which are actually elongated teeth. They live in Arctic waters and are relatively elusive.\n", - "\n", - "6. **Porpoises**: Similar to dolphins but generally smaller and with different physical characteristics, porpoises are also highly intelligent and social animals. They are less acrobatic than dolphins and have more triangular dorsal fins.\n", - "\n", - "7. **Sea Otters**: Found along the coasts of the northern and eastern North Pacific Ocean, sea otters are known for their use of tools and their dense fur, which is the thickest of any animal.\n", - "\n", - "8. **Polar Bears**: Though they spend a lot of time on ice, polar bears are excellent swimmers and are considered marine mammals because they depend on the ocean for their primary food source, seals.\n", - "\n", - "Each of these ocean mammals has unique adaptations and behaviors that make them interesting subjects of study. If you're into marine biology, you might find their various ecosystems, social structures, and survival strategies particularly compelling.\n" - ] - } - ], - "source": [ - "output = graph.invoke({'messages': messages})\n", - "for m in output['messages']:\n", - " m.pretty_print()" - ] - }, - { - "cell_type": "markdown", - "id": "34c33e63-1ef4-412d-bb10-6a1b9e5b35a7", - "metadata": {}, - "source": [ - "## Reducer\n", - "\n", - "A practical challenge when working with messages is managing long-running conversations. \n", - "\n", - "Long-running conversations result in high token usage and latency if we are not careful, because we pass a growing list of messages to the model.\n", - "\n", - "We have a few ways to address this.\n", - "\n", - "First, recall the trick we saw using `RemoveMessage` and the `add_messages` reducer." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "222c6bc5-bb0e-4a43-80f5-c8ec38d99f3a", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from langchain_core.messages import RemoveMessage\n", - "\n", - "# Nodes\n", - "def filter_messages(state: MessagesState):\n", - " # Delete all but the 2 most recent messages\n", - " delete_messages = [RemoveMessage(id=m.id) for m in state[\"messages\"][:-2]]\n", - " return {\"messages\": delete_messages}\n", - "\n", - "def chat_model_node(state: MessagesState): \n", - " return {\"messages\": [llm.invoke(state[\"messages\"])]}\n", - "\n", - "# Build graph\n", - "builder = StateGraph(MessagesState)\n", - "builder.add_node(\"filter\", filter_messages)\n", - "builder.add_node(\"chat_model\", chat_model_node)\n", - "builder.add_edge(START, \"filter\")\n", - "builder.add_edge(\"filter\", \"chat_model\")\n", - "builder.add_edge(\"chat_model\", END)\n", - "graph = builder.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "95a7c2cc-54ce-43e7-9a90-abf37827d709", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "Name: Bot\n", - "\n", - "So you said you were researching ocean mammals?\n", - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "Name: Lance\n", - "\n", - "Yes, I know about whales. But what others should I learn about?\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "That's great that you know about whales! There are a variety of other fascinating ocean mammals you might be interested in learning about. Here are a few:\n", - "\n", - "1. **Dolphins**: These highly intelligent and social animals are part of the cetacean family, which also includes whales and porpoises. There are many species of dolphins, including the common bottlenose dolphin and the orca, or killer whale, which is actually the largest member of the dolphin family.\n", - "\n", - "2. **Porpoises**: Similar to dolphins but generally smaller and with different facial structures and teeth. The harbor porpoise is one of the more well-known species.\n", - "\n", - "3. **Seals and Sea Lions**: These pinnipeds are known for their playful nature and agility in water. Seals typically have smaller flippers and no visible ear flaps, while sea lions have larger flippers and visible ear flaps.\n", - "\n", - "4. **Walruses**: Recognizable by their large tusks, whiskers, and significant bulk, walruses are pinnipeds as well and are usually found in Arctic regions.\n", - "\n", - "5. **Manatees and Dugongs**: These gentle giants, often called sea cows, are slow-moving and primarily herbivorous. Manatees are found in the Caribbean and the Gulf of Mexico, while dugongs inhabit the coastal waters of the Indian and western Pacific Oceans.\n", - "\n", - "6. **Sea Otters**: Known for their use of tools to open shells and their thick fur, sea otters are a keystone species in their ecosystems, particularly in kelp forest habitats along the Pacific coast of North America.\n", - "\n", - "7. **Polar Bears**: While not exclusively marine, polar bears depend heavily on the ocean for hunting seals and are excellent swimmers.\n", - "\n", - "Each of these groups has unique adaptations and behaviors that make them fascinating subjects of study. Happy researching!\n" - ] - } - ], - "source": [ - "# Message list with a preamble\n", - "messages = [AIMessage(\"Hi.\", name=\"Bot\", id=\"1\")]\n", - "messages.append(HumanMessage(\"Hi.\", name=\"Lance\", id=\"2\"))\n", - "messages.append(AIMessage(\"So you said you were researching ocean mammals?\", name=\"Bot\", id=\"3\"))\n", - "messages.append(HumanMessage(\"Yes, I know about whales. But what others should I learn about?\", name=\"Lance\", id=\"4\"))\n", - "\n", - "# Invoke\n", - "output = graph.invoke({'messages': messages})\n", - "for m in output['messages']:\n", - " m.pretty_print()" - ] - }, - { - "cell_type": "markdown", - "id": "f506457d-014b-4fee-a684-e5edfb4b8f0d", - "metadata": {}, - "source": [ - "## Filtering messages\n", - "\n", - "If you don't need or want to modify the graph state, you can just filter the messages you pass to the chat model.\n", - "\n", - "For example, just pass in a filtered list: `llm.invoke(messages[-1:])` to the model." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "22d0b904-7cd6-486b-8948-105bee3d4683", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Node\n", - "def chat_model_node(state: MessagesState):\n", - " return {\"messages\": [llm.invoke(state[\"messages\"][-1:])]}\n", - "\n", - "# Build graph\n", - "builder = StateGraph(MessagesState)\n", - "builder.add_node(\"chat_model\", chat_model_node)\n", - "builder.add_edge(START, \"chat_model\")\n", - "builder.add_edge(\"chat_model\", END)\n", - "graph = builder.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "markdown", - "id": "6f58c6fc-532f-418d-b70a-cfcb3307daf5", - "metadata": {}, - "source": [ - "Let's take our existing list of messages, append the above LLM response, and append a follow-up question." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "16956015-1dbe-4108-89b5-4209b68b51ca", - "metadata": {}, - "outputs": [], - "source": [ - "messages.append(output['messages'][-1])\n", - "messages.append(HumanMessage(f\"Tell me more about Narwhals!\", name=\"Lance\"))" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "85563415-c085-46a8-a4ac-155df798c54e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "Name: Bot\n", - "\n", - "Hi.\n", - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "Name: Lance\n", - "\n", - "Hi.\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "Name: Bot\n", - "\n", - "So you said you were researching ocean mammals?\n", - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "Name: Lance\n", - "\n", - "Yes, I know about whales. But what others should I learn about?\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "That's great that you know about whales! There are many other fascinating ocean mammals you can learn about. Here are a few:\n", - "\n", - "1. **Dolphins**: Highly intelligent and social animals, dolphins are known for their playful behavior and sophisticated communication skills. There are many species of dolphins, including the well-known bottlenose dolphin.\n", - "\n", - "2. **Porpoises**: Often confused with dolphins, porpoises are smaller and have different body shapes and teeth. They are generally more reclusive and less acrobatic than dolphins.\n", - "\n", - "3. **Seals**: Seals are part of the pinniped family, which also includes sea lions and walruses. They have streamlined bodies and flippers, making them excellent swimmers. Common types of seals include harbor seals and elephant seals.\n", - "\n", - "4. **Sea Lions**: Similar to seals but with some key differences, sea lions have external ear flaps and can rotate their hind flippers to walk on land. They are also very social and often gather in large groups.\n", - "\n", - "5. **Walruses**: Recognizable by their long tusks and whiskers, walruses are large marine mammals that are found in Arctic regions. They use their tusks to help them climb out of the water and to break through ice.\n", - "\n", - "6. **Manatees and Dugongs**: These gentle giants are often referred to as sea cows. Manatees are found in the Atlantic Ocean, while dugongs are found in the Indian and Pacific Oceans. They are herbivores and spend most of their time grazing on underwater vegetation.\n", - "\n", - "7. **Sea Otters**: Known for their playful behavior and use of tools, sea otters are an important part of the marine ecosystem. They have thick fur to keep them warm in cold waters and are often seen floating on their backs.\n", - "\n", - "8. **Polar Bears**: While not exclusively marine, polar bears spend a significant amount of time in the ocean, particularly in Arctic regions. They are excellent swimmers and rely on sea ice to hunt seals, their primary food source.\n", - "\n", - "9. **Narwhals**: Often called the \"unicorns of the sea,\" narwhals have a long, spiral tusk that is actually an elongated tooth. They are found in Arctic waters and are known for their deep diving abilities.\n", - "\n", - "10. **Orcas (Killer Whales)**: Though they are technically a type of dolphin, orcas are often considered separately due to their size and distinctive black-and-white coloring. They are apex predators and have complex social structures.\n", - "\n", - "Each of these ocean mammals has unique behaviors, adaptations, and ecological roles, making them fascinating subjects for study.\n", - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "Name: Lance\n", - "\n", - "Tell me more about Narwhals!\n" - ] - } - ], - "source": [ - "for m in messages:\n", - " m.pretty_print()" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "23349705-a059-47b5-9760-d8f64e687393", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "Name: Bot\n", - "\n", - "Hi.\n", - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "Name: Lance\n", - "\n", - "Hi.\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "Name: Bot\n", - "\n", - "So you said you were researching ocean mammals?\n", - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "Name: Lance\n", - "\n", - "Yes, I know about whales. But what others should I learn about?\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "That's great that you know about whales! There are many other fascinating ocean mammals you can learn about. Here are a few:\n", - "\n", - "1. **Dolphins**: Highly intelligent and social animals, dolphins are known for their playful behavior and sophisticated communication skills. There are many species of dolphins, including the well-known bottlenose dolphin.\n", - "\n", - "2. **Porpoises**: Often confused with dolphins, porpoises are smaller and have different body shapes and teeth. They are generally more reclusive and less acrobatic than dolphins.\n", - "\n", - "3. **Seals**: Seals are part of the pinniped family, which also includes sea lions and walruses. They have streamlined bodies and flippers, making them excellent swimmers. Common types of seals include harbor seals and elephant seals.\n", - "\n", - "4. **Sea Lions**: Similar to seals but with some key differences, sea lions have external ear flaps and can rotate their hind flippers to walk on land. They are also very social and often gather in large groups.\n", - "\n", - "5. **Walruses**: Recognizable by their long tusks and whiskers, walruses are large marine mammals that are found in Arctic regions. They use their tusks to help them climb out of the water and to break through ice.\n", - "\n", - "6. **Manatees and Dugongs**: These gentle giants are often referred to as sea cows. Manatees are found in the Atlantic Ocean, while dugongs are found in the Indian and Pacific Oceans. They are herbivores and spend most of their time grazing on underwater vegetation.\n", - "\n", - "7. **Sea Otters**: Known for their playful behavior and use of tools, sea otters are an important part of the marine ecosystem. They have thick fur to keep them warm in cold waters and are often seen floating on their backs.\n", - "\n", - "8. **Polar Bears**: While not exclusively marine, polar bears spend a significant amount of time in the ocean, particularly in Arctic regions. They are excellent swimmers and rely on sea ice to hunt seals, their primary food source.\n", - "\n", - "9. **Narwhals**: Often called the \"unicorns of the sea,\" narwhals have a long, spiral tusk that is actually an elongated tooth. They are found in Arctic waters and are known for their deep diving abilities.\n", - "\n", - "10. **Orcas (Killer Whales)**: Though they are technically a type of dolphin, orcas are often considered separately due to their size and distinctive black-and-white coloring. They are apex predators and have complex social structures.\n", - "\n", - "Each of these ocean mammals has unique behaviors, adaptations, and ecological roles, making them fascinating subjects for study.\n", - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "Name: Lance\n", - "\n", - "Tell me more about Narwhals!\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "Of course! Narwhals (Monodon monoceros) are fascinating marine mammals that belong to the family Monodontidae, which also includes the beluga whale. They are best known for the long, spiral tusk that protrudes from the head of the males, which has earned them the nickname \"unicorns of the sea.\"\n", - "\n", - "Here are some key facts about narwhals:\n", - "\n", - "### Physical Characteristics\n", - "- **Tusk**: The most distinctive feature of the narwhal is the tusk, which is actually an elongated tooth. It can grow up to 10 feet (3 meters) long and is usually found in males, though some females may also develop smaller tusks. The tusk grows in a spiral pattern and is thought to have sensory capabilities, with millions of nerve endings.\n", - "- **Body**: Narwhals have a stocky body with a mottled black and white skin pattern. They lack a dorsal fin, which is thought to be an adaptation to swimming under ice.\n", - "- **Size**: Adult narwhals typically range from 13 to 20 feet (4 to 6 meters) in length, with males generally being larger than females.\n", - "- **Weight**: They can weigh between 1,760 to 3,530 pounds (800 to 1,600 kilograms).\n", - "\n", - "### Habitat and Distribution\n", - "- Narwhals are native to the Arctic waters of Canada, Greenland, Norway, and Russia. They are especially common in the Baffin Bay and the waters surrounding Greenland.\n", - "- They prefer deep waters and are often found in areas with heavy sea ice.\n", - "\n", - "### Behavior and Diet\n", - "- **Diving**: Narwhals are deep divers and can reach depths of up to 5,000 feet (1,500 meters) in search of food. They can hold their breath for up to 25 minutes.\n", - "- **Diet**: Their diet primarily consists of fish such as Arctic cod and Greenland halibut, as well as squid and shrimp.\n", - "- **Social Structure**: Narwhals are social animals and are often found in groups called pods, which typically consist of 5 to 10 individuals but can sometimes number in the hundreds.\n", - "\n", - "### Reproduction and Lifespan\n", - "- Females give birth to a single calf after a gestation period of about 14 to 15 months. Calves are usually born in the spring or early summer.\n", - "- Narwhals have a long lifespan and can live up to 50 years, although some individuals may live even longer.\n", - "\n", - "### Conservation Status\n", - "- Narwhals are currently classified as \"Near Threatened\" by the International Union for Conservation of Nature (IUCN). Their main threats include climate change, which affects their sea ice habitat, and human activities such as shipping and oil exploration.\n", - "- Indigenous communities in the Arctic have traditionally hunted narwhals for their meat, blubber, and tusks, which are used for various purposes, including art and tools.\n", - "\n", - "### Cultural Significance\n", - "- The narwhal's tusk has fascinated humans for centuries and was often sold as a \"unicorn horn\" in medieval Europe, believed to possess magical properties.\n", - "- Narwhals hold significant cultural and economic value for indigenous Arctic communities.\n", - "\n", - "Overall, narwhals are remarkable creatures with unique adaptations that allow them to thrive in some of the planet's harshest environments.\n" - ] - } - ], - "source": [ - "# Invoke, using message filtering\n", - "output = graph.invoke({'messages': messages})\n", - "for m in output['messages']:\n", - " m.pretty_print()" - ] - }, - { - "cell_type": "markdown", - "id": "42e1d8d2-e297-4d78-b54c-d12b3c866745", - "metadata": {}, - "source": [ - "The state has all of the mesages.\n", - "\n", - "But, let's look at the LangSmith trace to see that the model invocation only uses the last message:\n", - "\n", - "https://smith.langchain.com/public/75aca3ce-ef19-4b92-94be-0178c7a660d9/r" - ] - }, - { - "cell_type": "markdown", - "id": "fc40d930-3c1f-47fe-8d2a-ce174873353c", - "metadata": {}, - "source": [ - "## Trim messages\n", - "\n", - "Another approach is to [trim messages](https://python.langchain.com/v0.2/docs/how_to/trim_messages/#getting-the-last-max_tokens-tokens), based upon a set number of tokens. \n", - "\n", - "This restricts the message history to a specified number of tokens.\n", - "\n", - "While filtering only returns a post-hoc subset of the messages between agents, trimming restricts the number of tokens that a chat model can use to respond.\n", - "\n", - "See the `trim_messages` below." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "2ff99b81-cf03-4cc2-b44f-44829a73e1fd", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCADbAHMDASIAAhEBAxEB/8QAHQABAAIDAQEBAQAAAAAAAAAAAAYHBAUIAwIBCf/EAFQQAAEDAwEDBQgKDA0FAQAAAAECAwQABREGBxIhCBMWMUEUFSJVgZTR0xc3UVRWYXaRlbQjMjM2UlNxdJKTstIJGCRCQ1dicnWEobGzJjhGc4LB/8QAGgEBAQADAQEAAAAAAAAAAAAAAAECAwQFB//EADERAAIAAwQHBwUBAQAAAAAAAAABAgMRBBIhURMxQWGRodEUFVJxscHwIiNTgeEyM//aAAwDAQACEQMRAD8A/qnSlY9wnx7XCelynA1HZSVLWQTgfEBxJ9wDiTwFVJt0QMisKVe7dBWUSZ8WOsfzXXkpP+prRt2eZqxIk3lciDBWCW7Oy5uHdPUX1p8Iq/sJVujODv43qzouidPQkbrFitrScY8CI2M/l4ca33JcOEbx3dfnmXDae/Sqy+OIHnKPTTpVZfHEDzlHpp0VsvieB5sj0U6K2XxPA82R6KfZ38i4DpVZfHEDzlHpp0qsvjiB5yj006K2XxPA82R6KdFbL4ngebI9FPs7+QwHSqy+OIHnKPTTpVZfHEDzlHpp0VsvieB5sj0U6K2XxPA82R6KfZ38hgfqdT2ZaglN2gqJ6gJKPTWxbcS6gLQoLQeIUk5BrWK0nY1pKVWa3qSesGKjB/0rXr0FbYiy9ZQrT0vIPOW4BDasdi2sbige3Iz7hB40pJeptfr57kwJLStNY709Left9xZTGusYArS3nm30HqdbJ47p6iDxScg5G6pW5rTFC4HRkFKUrECoxf8AF11ZYrQvCo7aXLo8g58ItKQloeRbgX+VsVJ6jE9Pcm0O0SFZ5uXAkRAccOcCm3EjPZlKXD/81vk/6b3P0KiT0pStBBUDjbc9EzdZy9KRryuVfIjjrLzLEKQ42l1tsuONc8lstlxKQSUBRVwxjNTyua7R3403yhBH0NY9W26z3W8SntUw7vbimzLHNq/l8WQrqcW4lHgIUQveJKEkZoCZbI+Uxp7aboe76jlMy7GzaVS3JndMGUlpqOy+62lwOrZSlaihsKUhGVIJKSARW8sXKF0BqOwajvMG/EwtOxjNuiX4Uhh+KwEKXzhYcbS4UlKFEFKTnBxmqb0pctdaK2H650VYNN36Drq0S7pLiTVWwqiyGnrit0ORXVfY3XOZfKkozneRgjhioXddJXW4TdqEmy6f2hT4F32ZzLZFm6qYlPSpk5CnFFpKHMrbJDo3UbqApXObiT2gXbrnlaaS05pq3XmzpnX+JKu8G2l9m2TQyW33AFOtLDBD26gKICM7ygEg7xANxWK9RdR2eJc4XP8AckpsOtd0x3I7m6erebcSlaD8SgD8VUvti0xdTsI0Z3qsku4SNO3CxXN60wmcyVMxX2Vuobb4ErCUnCOvhgceFXFpbULeq7DFurUK4W5uQFFMa6RFxZKMKKfDaWApOcZGR1EHtoDa0pSgIxrbFtVab2jCXoUttlauOVMPrQ04n8mShf5WxUnqMbQk902JiAnJdnTosdAAz/TJWs+RCFnyVJ66I8ZULevHhh71LsFKUrnIK1moLKm+wA0He55LLiZEaQE7xZdScpVjIyOwjIykqGeNbOlZQxOFqJawaWzaiRcHDb5zaYF5bT9mhLVneA61tE45xs9igO3CglQKREP4teyf+rbSv0Qx+7U6u9igX6OlmfFbkoSd5BUMKbV1ZSocUn4wQa03QQNcI2oL7FbxgIE3nsD8rqVq+c1upKjxrd5rr81lwI+vk27KHFFStm+llKJySbSwST+jVhQobFuhsRIrLcaKw2lpplpIShtCRhKUgdQAAAFR3oTI+FV+/XM+qp0JkfCq/frmfVU0cvx8mKLMlFKi/QmR8Kr9+uZ9VVS8mi9ah2t6Dud4vmqLqiXHvk63IERTSE80y6UoyC2eOOs00cvx8mKLM6CqGao2MaC1tdl3TUGjLFerktKUKlz7e086UgYAKlJJwKy+hMj4VX79cz6qnQmR8Kr9+uZ9VTRy/HyYosyPfxa9k/8AVvpb6IY/dqTWTTuldllhdj2m3WzS9o50vLaiMojMlxQA3sJABUcJHunAFeXQh85CtUX5QPWOfaH+obBrKtuirXbpaJikPz5yOKJVwkLkLQcYyjfJCOH4IHWfdNLspa4q+S6/0YHla4r9+u7d7msLisMIUi3RXklLiQoDfecSftVKxhKTxSnOcKWpCZHSlao4773BilKVgQUpSgFKUoBSlKAVzvyGfajvvyqu31g10RXO/IZ9qO+/Kq7fWDQHRFKUoBSlKAUpSgFKUoBSlKAUpSgFKUoBXO/IZ9qO+/Kq7fWDXRFc78hn2o778qrt9YNAdEUpSgFKUoBSlKAUpSgFK/FKCElSiEpAySTwAqFHWF7uwEiy2yCbavizIuElbbjyexYbS2d1J6xk5I6wK3S5UU2t3oWlSbUqEd/dYe8LH5296unf3WHvCx+dverrd2WPNcUKE3pUI7+6w94WPzt71dO/usPeFj87e9XTssea4oUOcf4TTY5L2gbH7dqy3IW/M0g86+8wnjvRHggPLx2lJbaV8SQs9lcU8hnYYNtu3O2pnR+e07Yd26XLfTlDgQoc0yew768ZB60pX7lf1bucvU15tsu3zrRYJUKW0th9h2U8UuNqBSpJHN8QQSPLVXcnLYXP5Nmm7tabFHtU5dynLlvTJUlwOlHU01kNcUoTnHulSjwzgOyx5rihQ6RpUI7+6w94WPzt71dO/usPeFj87e9XTssea4oUJvSoR391h7wsfnb3q6d/dYe8LH5296unZY81xQoTelQkX7WCTk22yLA/m92vJz5eaOPmNSHT1/bv0VxXMriy46+ZkxXDlTS8A4z1KBBBCh1gjqOQNcciOWrz1bnUUNrSlK5yGr1QSnTN3IOCIbxBH9w1HtMgDTdqAAAERrAH9wVIdVfexePzN79g1HtNfe5avzRr9gV6Mn/i/P2LsNlWHabzb79CEy2To1xiFa2xIiPJdbKkKKFp3kkjKVJUkjsIIPEVTexW7a52t2m1bQpGsRbLHcpDrzGl49sYW0mIlxaEIW8oc6XSEhRUFAAkjd4VVuzG9ax2fbMtN6oiamQ7p57Wci1vacXb2txTEi8PMLXz33TnQtZWCCE4ASUniTLxDsKvkOIUtSApJWkAqSDxAPVn5jXLu0DbRqrT2uZV10/frrftM2/UMWz3CJ3jit2uPzj7bDrHdSlh9byC59sgKQFYSR11JdlGnrsnlKbYJqtUz1wmJ8BTttMeNzUgOQEltKlBrfAaCgE7qgSEje3iSSvY0Bf9KVyVZtfbULnoXZvqfp8EO6o1CbC/CNnilllpTkhAeSd0KLo5gHircJP2mAQa3QHWtK5kuW1rXOm5GoND9+2rnf0avtunIGpJcJpKmWJkRMkuuMthLa1tp5xIwEpUSnI68+mrdsestjL+uNO3G6s6yukO2W64WW5TYrcYpXMlmGESEshKClDgSvKQklJI68GpeQOi13m3t3Zq1LnRk3N1lUluEXkh5bSSEqcCM7xSCpIKsYBUB21lkhIJJwBxJNck62vGp9h+1Z/U2otSr11Ntuz+6TGQ9AZhgOJlRPAw0B9jKinryoDPhHsluzrUe153VVlN5g3y46fuDLvfR27wLXFYhHmVLbXGVGkrcUnfARuuBRwrO9kUvY0Bf9pu8C/22PcbZNj3K3yUBxiXEdS606k9SkrSSFD4wa8tGH/q7VQ7P5KfLzZ9A+aq15Jn/bbs7/wlr/8AasnRv34aq/yn/Gqs61lRvcvVGS1MmlKUrzDE1eqvvYvH5m9+waj2mvvctX5o1+wKl02IifDfjO55p5tTasdeCMH/AHqv4lyk6XhRrZc7XcnXoraWRKgwXJLT4SAAsc0lRTnHFKgCDkcRgn0bP9UtwLXUyWKIrp/YHbNJ38TbJqTU1qtAmqnjTca4JFtDqlFawEFBWEKUSothYRknhXu1sIsDOgYOkUzLkbbDvAvbbpdb54viaZm6TuY3OcJGMZ3eGc8alHTON4sv30JL9VTpnG8WX76El+qrdoI/CxdeRXl/5L+nr+bwyrUGpYNruVwN3NqhzkIisTi4HTIbSWySecG/uLKkbxzudWJBc9lLcLXNy1zYrjc417lMN902lucli33N1ptSWefy04pOAQneR2AcFYqR9M43iy/fQkv1VOmcbxZfvoSX6qmgj8LF15Efi6g2mLlMpkaJ02zHKwHHG9UPLUlOeJCTAGSB2ZGfdFY9u2EWC2aT0jp5qZclQtMXcXqG4t1suOPBby91w7mCjL6+ACTwHHrzKOmcbxZfvoSX6qtbYdrGn9Uw3JdmNyu0Vt1cdb8G1yXkJcQcLQVJbICkngR1imgmbYWS6zUX/YLpvUknVkiY9cRJ1FOh3Jb7D4bcgyYrTbbDsZSU5QoBpJySrJJ7DisWJydNMLseqbfe5V11VI1M02xcrneZKVyltt/cUoLaUJbDaiVJ3EjwuJyamfTON4sv30JL9VTpnG8WX76El+qpoI/CW68iC2bk4WSHeXLjetQaj1i47Z5FhcZ1DMbfbXEeUhS0kJbQc/YwN7OTk5ycEbfZ7saj7OpbS4+q9UXiDHjGJEtl3uCXosZolOEpSEJKt0JABWVEDIB4mpH0zjeLL99CS/VU6ZxvFl++hJfqqaCPwsXXkYezPZvbtlOmE6fs8ufItTTy3IrM98O9yoUchls4B5tJzgHJGTx6q3Wjfvw1V/lP+NVYI1iwo4Rar6pR6k95pSc+VTYA8prfaOtEmK5crnNZMWTcXELEZSgpTLaEBKUqI4FX2yjgkDewCcZOMacqVEosKqi4p+w1J1JJSlK8oxFKUoBSlKAUpSgFc78hn2o778qrt9YNdEVzvyGfajvvyqu31g0B0RSlKAUpSgFKUoBSlKAUpSgFKUoBSlKAVzvyGfajvvyqu31g10RXO/IZ9qO+/Kq7fWDQHRFKUoBSlKAUpSgFKUoBSlKAUpSgFK8u6WfxqP0hTupn8c3+kKtGCguVtyq5HJbhaZmJ0avVMW8OSGnHhcDETGW2GyhJPMubxWFrIHD7mevs5L5J/LgumnnomzyzbOO/9zv9/kSWHe/RYDZku7xCgI6/BQMkqz1AnArt/lMbIoO3rY3ftKLcZTcFt90215ah9ilt5LZz2A8UE/grVXIH8GPsEcg3u/7R9QxO5ZFvccs9sYlJ3Voe6pLmCAQQCGwf7To7KUYP6LUry7qZ/HN/pCndTP45v9IUowetK+ULS4MpUFD3Qc19VAKUpQClKUApSvGbLbgQ35Txwyw2pxZHYkDJ/wBqqVXRAg+0faV0YULZa0tv3haQpanOLcVB6lKA61HsT5TwxmlrsX9ROFy8yn7w4rrExe835G+CE+RIr4YmyLtzlymHM2esynjnOFK47o+JIwkfEkV6V9Hsdil2OBJL6tr+bA3TBGAdPWonJtsPP/oR6K/Oj1q8WQ/N0eithWu1BqK26VtT1yu0tuFCawFOuZPEnASAOKiSQAACSTwr0XG0qtkvPM/ej1q8WQ/N0einR61eLIfm6PRUeY2waQfsc+79+UMwbe403MVIYdZcjlxQS3zja0haQSoeERjrOcAkZun9o2ndTquCYFx8OAgOyUSmXIym2yCQ4Q6lJKCEnwh4PDrrWp8LaSj17xV5m06PWrxZD83R6KdHrV4sh+bo9FQCBtvteptoOmrHp2U1Pgz2Zjsp5yM82oBtKC2ppSglKkkleVAKBxwIqz6QTlMq4Iq0FXmYTNlgxnQ7HitxHkjCXYw5pae3gpOCPnqf6O2qXHTkhti9SXbnZ1HCn3RvPxR+FkDLiR25yrjkE4xUNpWqfIl2mG5NVfVeQvPadRtOofaQ60tLja0hSVoOQoHqIPaK+6rXYXeFytOTbS6re71yOaZ45wwpIWgeQlaQPcSKsqvnFokuzzYpT2GTFKUrmIK1+obeu7WC5wWzhcmK6ykn3VIIH+9bClZQtwtRLYDk+1O8/bIq8FJLScpIwQccQR2EGsqphtN0O7pe6SrtFbK7LMdLru4M9yOq4q3vcQo5VnsUog4BFVpqHROndYKYcvVlt94UyCGlTI6HdwHGd3eBxnA+avpsqfDaJamysa8txGsTd1WO3nTdwvlm0/LhRZ9wZtN3anS4drfUzKcZCFoUWVJUlW+nfCgAQTg1vPYY0FjHQ2x4/wAPa/drbad0Lp3SLrztksdvtLjyQlxcKMhorA4gHdAzSOCObC4Ikknv/hCmdR6Tg3nQOo7hYrDqw3SQ/boxVfjKekSGm5jTngIeWpYSjKyTgfzj1ca2e17RN71ZqvVce1RXwZuje5GpG6UtOvCUpXM7/VvFORjPUr3Ku+lanZIYk03rywz6gpm33qTrDaZs+lRtK3yyQ7ZDntye+FuWw1HUttoJbCiMH7QgEcD2E9lzVj3G3RbvBfhTo7UuI+gtusPICkLSesEHgRUUOxbQJ/8ADLF9Htfu1thgjl1pjX9bEsnkCZ0qIxdkOh4MpmTH0jZWJDKw4263BbSpCgcgg44EEVNrXbJuoLm3bbY0H5rnElWebZT+G4R1JHznqHGtt9wwuKZRJb/4hSpYuwSIop1JO/onJLUZJx1ltveUfj+6Y/KD7lWzWo0ppuPpKwRLVGJWhhJ3nVfbOrJJWs/GpRJ8tbevnFtnq02iOatT9FgZsUpSuIgpSlAfK0JcSUqAUlQwQRkEVA7vsT03cXFORBKsi1cSLa6EI8jagpCfIkVPqVvlT5sh1lRNFqVadgUEk41JewPcxF9RX57AMH4S3v5ovqKtOldnedr/ACenQVKs9gGD8Jb380X1FPYBg/CW9/NF9RVp0p3na/yenQVKs9gGD8Jb380X1FPYBg/CW9/NF9RVp0p3na/yenQVKzjbBbOhwGTeLzNQOtpbzTST5W20q+Yip1YtOWzTEMxbXCahMk7yg2nitX4SlHio/GSTWypXPOtc+eqTI21y4CopSlchBSlKA//Z", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from langchain_core.messages import trim_messages\n", - "\n", - "# Node\n", - "def chat_model_node(state: MessagesState):\n", - " messages = trim_messages(\n", - " state[\"messages\"],\n", - " max_tokens=100,\n", - " strategy=\"last\",\n", - " token_counter=ChatOpenAI(model=\"gpt-4o\"),\n", - " allow_partial=False,\n", - " )\n", - " return {\"messages\": [llm.invoke(messages)]}\n", - "\n", - "# Build graph\n", - "builder = StateGraph(MessagesState)\n", - "builder.add_node(\"chat_model\", chat_model_node)\n", - "builder.add_edge(START, \"chat_model\")\n", - "builder.add_edge(\"chat_model\", END)\n", - "graph = builder.compile()\n", - "\n", - "# View\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "24df63ac-da29-4874-b3df-7e390e97cc8a", - "metadata": {}, - "outputs": [], - "source": [ - "messages.append(output['messages'][-1])\n", - "messages.append(HumanMessage(f\"Tell me where Orcas live!\", name=\"Lance\"))" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "6d9d8971-c75c-43ca-a209-eb1d07b2ead0", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[HumanMessage(content='Tell me where Orcas live!', name='Lance')]" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Example of trimming messages\n", - "trim_messages(\n", - " messages,\n", - " max_tokens=100,\n", - " strategy=\"last\",\n", - " token_counter=ChatOpenAI(model=\"gpt-4o\"),\n", - " allow_partial=False\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "ed70a269-a869-4fa0-a1df-29736a432c51", - "metadata": {}, - "outputs": [], - "source": [ - "# Invoke, using message trimming in the chat_model_node \n", - "messages_out_trim = graph.invoke({'messages': messages})" - ] - }, - { - "cell_type": "markdown", - "id": "38b3db67-380e-46b5-9a6a-20100ba52008", - "metadata": {}, - "source": [ - "Let's look at the LangSmith trace to see the model invocation:\n", - "\n", - "https://smith.langchain.com/public/b153f7e9-f1a5-4d60-8074-f0d7ab5b42ef/r" - ] - } - ], - "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.12.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/module-3/edit-state-human-feedback.ipynb b/module-3/edit-state-human-feedback.ipynb deleted file mode 100644 index c2cd3399f..000000000 --- a/module-3/edit-state-human-feedback.ipynb +++ /dev/null @@ -1,961 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "147e576c", - "metadata": {}, - "source": [ - "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-3/edit-state-human-feedback.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239520-lesson-3-editing-state-and-human-feedback)" - ] - }, - { - "cell_type": "markdown", - "id": "3b2f2448-21c3-4196-9e61-0b47e7d0048b", - "metadata": {}, - "source": [ - "# Editing graph state\n", - "\n", - "## Review\n", - "\n", - "We discussed motivations for human-in-the-loop:\n", - "\n", - "(1) `Approval` - We can interrupt our agent, surface state to a user, and allow the user to accept an action\n", - "\n", - "(2) `Debugging` - We can rewind the graph to reproduce or avoid issues\n", - "\n", - "(3) `Editing` - You can modify the state \n", - "\n", - "We showed how breakpoints support user approval, but don't yet know how to modify our graph state once our graph is interrupted!\n", - "\n", - "## Goals\n", - "\n", - "Now, let's show how to directly edit the graph state and insert human feedback." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "95d26b8c-d958-4d21-9ca4-4636d3dfe45c", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "%pip install --quiet -U langgraph langchain_openai langgraph_sdk" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d5948594", - "metadata": {}, - "outputs": [], - "source": [ - "import os, getpass\n", - "\n", - "def _set_env(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass.getpass(f\"{var}: \")\n", - "\n", - "_set_env(\"OPENAI_API_KEY\")" - ] - }, - { - "cell_type": "markdown", - "id": "65a8df1f-a76a-4803-a532-ea9802106ac8", - "metadata": {}, - "source": [ - "## Editing state \n", - "\n", - "Previously, we introduced breakpoints.\n", - "\n", - "We used them to interrupt the graph and await user approval before executing the next node.\n", - "\n", - "But breakpoints are also [opportunities to modify the graph state](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/edit-graph-state/).\n", - "\n", - "Let's set up our agent with a breakpoint before the `assistant` node." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "bcf24f05-ac2b-455e-846c-0c50ac86e1f4", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_openai import ChatOpenAI\n", - "\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", - "# This will be a 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", - "def divide(a: int, b: int) -> float:\n", - " \"\"\"Adds a and b.\n", - "\n", - " Args:\n", - " a: first int\n", - " b: second int\n", - " \"\"\"\n", - " return a / b\n", - "\n", - "tools = [add, multiply, divide]\n", - "llm = ChatOpenAI(model=\"gpt-4o\")\n", - "llm_with_tools = llm.bind_tools(tools)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "5dfe84af-5c62-4c3f-8ed7-96b5261f0b7b", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from IPython.display import Image, display\n", - "\n", - "from langgraph.checkpoint.memory import MemorySaver\n", - "from langgraph.graph import MessagesState\n", - "from langgraph.graph import START, StateGraph\n", - "from langgraph.prebuilt import tools_condition, ToolNode\n", - "\n", - "from langchain_core.messages import HumanMessage, SystemMessage\n", - "\n", - "# System message\n", - "sys_msg = SystemMessage(content=\"You are a helpful assistant tasked with performing arithmetic on a set of inputs.\")\n", - "\n", - "# Node\n", - "def assistant(state: MessagesState):\n", - " return {\"messages\": [llm_with_tools.invoke([sys_msg] + state[\"messages\"])]}\n", - "\n", - "# Graph\n", - "builder = StateGraph(MessagesState)\n", - "\n", - "# Define nodes: these do the work\n", - "builder.add_node(\"assistant\", assistant)\n", - "builder.add_node(\"tools\", ToolNode(tools))\n", - "\n", - "# Define edges: these determine the control flow\n", - "builder.add_edge(START, \"assistant\")\n", - "builder.add_conditional_edges(\n", - " \"assistant\",\n", - " # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools\n", - " # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END\n", - " tools_condition,\n", - ")\n", - "builder.add_edge(\"tools\", \"assistant\")\n", - "\n", - "memory = MemorySaver()\n", - "graph = builder.compile(interrupt_before=[\"assistant\"], checkpointer=memory)\n", - "\n", - "# Show\n", - "display(Image(graph.get_graph(xray=True).draw_mermaid_png()))" - ] - }, - { - "cell_type": "markdown", - "id": "92a47fd5-1f60-41dc-9206-698ed8ece530", - "metadata": {}, - "source": [ - "Let's run!\n", - "\n", - "We can see the graph is interrupted before the chat model responds. " - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "a2ce488d-00e4-492e-a62c-dd98702c313f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "Multiply 2 and 3\n" - ] - } - ], - "source": [ - "# Input\n", - "initial_input = {\"messages\": \"Multiply 2 and 3\"}\n", - "\n", - "# Thread\n", - "thread = {\"configurable\": {\"thread_id\": \"1\"}}\n", - "\n", - "# Run the graph until the first interruption\n", - "for event in graph.stream(initial_input, thread, stream_mode=\"values\"):\n", - " event['messages'][-1].pretty_print()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "4be478ef-bd60-4d32-8a05-5f56c93a8396", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "StateSnapshot(values={'messages': [HumanMessage(content='Multiply 2 and 3', id='e7edcaba-bfed-4113-a85b-25cc39d6b5a7')]}, next=('assistant',), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a412-5b2d-601a-8000-4af760ea1d0d'}}, metadata={'source': 'loop', 'writes': None, 'step': 0, 'parents': {}}, created_at='2024-09-03T22:09:10.966883+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6a412-5b28-6ace-bfff-55d7a2c719ae'}}, tasks=(PregelTask(id='dbee122a-db69-51a7-b05b-a21fab160696', name='assistant', error=None, interrupts=(), state=None),))" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "state = graph.get_state(thread)\n", - "state" - ] - }, - { - "cell_type": "markdown", - "id": "36ef63a1-2ab8-416d-babf-d35054e294f0", - "metadata": {}, - "source": [ - "Now, we can directly apply a state update.\n", - "\n", - "Remember, updates to the `messages` key will use the `add_messages` reducer:\n", - " \n", - "* If we want to over-write the existing message, we can supply the message `id`.\n", - "* If we simply want to append to our list of messages, then we can pass a message without an `id` specified, as shown below." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "9179cff1-e529-473a-9ce2-e23b932c2063", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'configurable': {'thread_id': '1',\n", - " 'checkpoint_ns': '',\n", - " 'checkpoint_id': '1ef6a414-f419-6182-8001-b9e899eca7e5'}}" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "graph.update_state(\n", - " thread,\n", - " {\"messages\": [HumanMessage(content=\"No, actually multiply 3 and 3!\")]},\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "d77b8d6a-8c7b-4f7a-b723-121af25ac829", - "metadata": {}, - "source": [ - "Let's have a look.\n", - "\n", - "We called `update_state` with a new message. \n", - "\n", - "The `add_messages` reducer appends it to our state key, `messages`." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "141b6aab-ec6d-44f3-beb1-6c22ac5f2158", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "Multiply 2 and 3\n", - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "No, actually multiply 3 and 3!\n" - ] - } - ], - "source": [ - "new_state = graph.get_state(thread).values\n", - "for m in new_state['messages']:\n", - " m.pretty_print()" - ] - }, - { - "cell_type": "markdown", - "id": "e4041959-cc3a-4168-8cf7-06d1711921d8", - "metadata": {}, - "source": [ - "Now, let's proceed with our agent, simply by passing `None` and allowing it proceed from the current state.\n", - "\n", - "We emit the current and then proceed to execute the remaining nodes." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "f166bed2-87c9-41ec-b235-0305721c2d6b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "No, actually multiply 3 and 3!\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "Tool Calls:\n", - " multiply (call_Mbu8MfA0krQh8rkZZALYiQMk)\n", - " Call ID: call_Mbu8MfA0krQh8rkZZALYiQMk\n", - " Args:\n", - " a: 3\n", - " b: 3\n", - "=================================\u001b[1m Tool Message \u001b[0m=================================\n", - "Name: multiply\n", - "\n", - "9\n" - ] - } - ], - "source": [ - "for event in graph.stream(None, thread, stream_mode=\"values\"):\n", - " event['messages'][-1].pretty_print()" - ] - }, - { - "cell_type": "markdown", - "id": "b18dc1ca", - "metadata": {}, - "source": [ - "Now, we're back at the `assistant`, which has our `breakpoint`.\n", - "\n", - "We can again pass `None` to proceed." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "f5952731-0170-4589-a399-ee787df35400", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=================================\u001b[1m Tool Message \u001b[0m=================================\n", - "Name: multiply\n", - "\n", - "9\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "3 multiplied by 3 equals 9.\n" - ] - } - ], - "source": [ - "for event in graph.stream(None, thread, stream_mode=\"values\"):\n", - " event['messages'][-1].pretty_print()" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "bc22c3e9-b00c-4ead-b752-a682b45b3718", - "metadata": {}, - "source": [ - "### Editing graph state in Studio\n", - "\n", - "--\n", - "\n", - "**⚠️ DISCLAIMER**\n", - "\n", - "*Running Studio currently requires a Mac. If you are not using a Mac, then skip this step.*\n", - "\n", - "*Also, if you are running this notebook in CoLab, then skip this step.*\n", - "\n", - "--\n", - "\n", - "Let's load our `agent` in the Studio UI, which uses `module-3/studio/agent.py` set in `module-3/studio/langgraph.json`.\n", - "\n", - "### Editing graph state with LangGraph API\n", - "\n", - "We can interact with our agent via the SDK.\n", - "\n", - "![Screenshot 2024-08-26 at 9.59.19 AM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbaf2fbfb576f8e53ed930_edit-state-human-feedback1.png)\n", - "\n", - "Let's get the URL for the local deployment from Studio.\n", - "\n", - "The LangGraph API [supports editing graph state](https://langchain-ai.github.io/langgraph/cloud/how-tos/human_in_the_loop_edit_state/#initial-invocation). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "020efeba-fa80-4839-81f9-9ce228f9844e", - "metadata": {}, - "outputs": [], - "source": [ - "import platform\n", - "\n", - "if 'google.colab' in str(get_ipython()) or platform.system() != 'Darwin':\n", - " raise Exception(\"Unfortunately LangGraph Studio is currently not supported on Google Colab or requires a Mac\")" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "642aabab-f822-4917-9d66-3314ac5008fd", - "metadata": {}, - "outputs": [], - "source": [ - "from langgraph_sdk import get_client\n", - "client = get_client(url=\"http://localhost:56091\")" - ] - }, - { - "cell_type": "markdown", - "id": "be74cb09", - "metadata": {}, - "source": [ - "Our agent is defined in `assistant/agent.py`. \n", - "\n", - "If you look at the code, you'll see that it *does not* have a breakpoint! \n", - " \n", - "Of course, we can add it to `agent.py`, but one very nice feature of the API is that we can pass in a breakpoint!\n", - "\n", - "Here, we pass a `interrupt_before=[\"assistant\"]`." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "1c352f9e-6a0f-4a94-a083-b85b0233efa9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Receiving new event of type: metadata...\n", - "--------------------------------------------------\n", - "Receiving new event of type: values...\n", - "{'content': 'Multiply 2 and 3', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '882dabe4-b877-4d71-bd09-c34cb97c4f46', 'example': False}\n", - "--------------------------------------------------\n" - ] - } - ], - "source": [ - "initial_input = {\"messages\": \"Multiply 2 and 3\"}\n", - "thread = await client.threads.create()\n", - "async for chunk in client.runs.stream(\n", - " thread[\"thread_id\"],\n", - " \"agent\",\n", - " input=initial_input,\n", - " stream_mode=\"values\",\n", - " interrupt_before=[\"assistant\"],\n", - "):\n", - " print(f\"Receiving new event of type: {chunk.event}...\")\n", - " messages = chunk.data.get('messages', [])\n", - " if messages:\n", - " print(messages[-1])\n", - " print(\"-\" * 50)" - ] - }, - { - "cell_type": "markdown", - "id": "13065dd9-5f43-47d6-ac2a-9dc15c0c54e6", - "metadata": {}, - "source": [ - "We can get the current state" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "4da2c464-3e71-496a-badc-671aeee168b6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'values': {'messages': [{'content': 'Multiply 2 and 3',\n", - " 'additional_kwargs': {},\n", - " 'response_metadata': {},\n", - " 'type': 'human',\n", - " 'name': None,\n", - " 'id': '882dabe4-b877-4d71-bd09-c34cb97c4f46',\n", - " 'example': False}]},\n", - " 'next': ['assistant'],\n", - " 'tasks': [{'id': 'a71c0b80-a679-57cb-aa59-a1655b763480',\n", - " 'name': 'assistant',\n", - " 'error': None,\n", - " 'interrupts': [],\n", - " 'state': None}],\n", - " 'metadata': {'step': 0,\n", - " 'run_id': '1ef6a41c-ea63-663f-b3e8-4f001bf0bf53',\n", - " 'source': 'loop',\n", - " 'writes': None,\n", - " 'parents': {},\n", - " 'user_id': '',\n", - " 'graph_id': 'agent',\n", - " 'thread_id': 'a95ffa54-2435-4a47-a9da-e886369ca8ee',\n", - " 'created_by': 'system',\n", - " 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca'},\n", - " 'created_at': '2024-09-03T22:13:54.466695+00:00',\n", - " 'checkpoint_id': '1ef6a41c-ead7-637b-8000-8c6a7b98379e',\n", - " 'parent_checkpoint_id': '1ef6a41c-ead3-637d-bfff-397ebdb4f2ea'}" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "current_state = await client.threads.get_state(thread['thread_id'])\n", - "current_state" - ] - }, - { - "cell_type": "markdown", - "id": "4527bbf1-0927-41a6-aeef-d15e32bbbdc3", - "metadata": {}, - "source": [ - "We can look at the last message in state." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "801ae2d9-0551-46b8-aee2-82293cee4011", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'content': 'Multiply 2 and 3',\n", - " 'additional_kwargs': {},\n", - " 'response_metadata': {},\n", - " 'type': 'human',\n", - " 'name': None,\n", - " 'id': '882dabe4-b877-4d71-bd09-c34cb97c4f46',\n", - " 'example': False}" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "last_message = current_state['values']['messages'][-1]\n", - "last_message" - ] - }, - { - "cell_type": "markdown", - "id": "f0581ba8-db3d-474d-9042-b1c7f3461caf", - "metadata": {}, - "source": [ - "We can edit it!" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "86b12be7-7e4a-40d0-8521-dced7c393c71", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'content': 'No, actually multiply 3 and 3!',\n", - " 'additional_kwargs': {},\n", - " 'response_metadata': {},\n", - " 'type': 'human',\n", - " 'name': None,\n", - " 'id': '882dabe4-b877-4d71-bd09-c34cb97c4f46',\n", - " 'example': False}" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "last_message['content'] = \"No, actually multiply 3 and 3!\"\n", - "last_message" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "f84f2c24-f281-4591-90e5-de3a5547c9da", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'content': 'No, actually multiply 3 and 3!',\n", - " 'additional_kwargs': {},\n", - " 'response_metadata': {},\n", - " 'type': 'human',\n", - " 'name': None,\n", - " 'id': '882dabe4-b877-4d71-bd09-c34cb97c4f46',\n", - " 'example': False}" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "last_message" - ] - }, - { - "cell_type": "markdown", - "id": "ce7b4280-6ae7-4246-9c87-44e0daa6c654", - "metadata": {}, - "source": [ - "Remember, as we said before, updates to the `messages` key will use the same `add_messages` reducer. \n", - "\n", - "If we want to over-write the existing message, then we can supply the message `id`.\n", - "\n", - "Here, we did that. We only modified the message `content`, as shown above." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "84d33b6e-32ff-4eca-8114-345e508f3481", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'configurable': {'thread_id': 'a95ffa54-2435-4a47-a9da-e886369ca8ee',\n", - " 'checkpoint_ns': '',\n", - " 'checkpoint_id': '1ef6a41d-cc8e-6979-8001-8c7c283b636c'},\n", - " 'checkpoint_id': '1ef6a41d-cc8e-6979-8001-8c7c283b636c'}" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "await client.threads.update_state(thread['thread_id'], {\"messages\": last_message})" - ] - }, - { - "cell_type": "markdown", - "id": "1f07f0d1-7083-4827-babd-d3702eb59a37", - "metadata": {}, - "source": [ - "Now, we resume by passing `None`. " - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "ef18d12d-e0a6-487a-9f32-ad30e2634a20", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Receiving new event of type: metadata...\n", - "--------------------------------------------------\n", - "Receiving new event of type: values...\n", - "{'content': 'No, actually multiply 3 and 3!', 'additional_kwargs': {'additional_kwargs': {}, 'response_metadata': {}, 'example': False}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '882dabe4-b877-4d71-bd09-c34cb97c4f46', 'example': False}\n", - "--------------------------------------------------\n", - "Receiving new event of type: values...\n", - "{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_vi16je2EIikHuT7Aue2sd1qd', 'function': {'arguments': '{\"a\":3,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-775b42f7-0590-4c54-aaeb-78599b1f12d2', 'example': False, 'tool_calls': [{'name': 'multiply', 'args': {'a': 3, 'b': 3}, 'id': 'call_vi16je2EIikHuT7Aue2sd1qd', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': None}\n", - "--------------------------------------------------\n", - "Receiving new event of type: values...\n", - "{'content': '9', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'multiply', 'id': '226bfbad-0cea-4900-80c5-761a62bd4bc1', 'tool_call_id': 'call_vi16je2EIikHuT7Aue2sd1qd', 'artifact': None, 'status': 'success'}\n", - "--------------------------------------------------\n" - ] - } - ], - "source": [ - "async for chunk in client.runs.stream(\n", - " thread[\"thread_id\"],\n", - " assistant_id=\"agent\",\n", - " input=None,\n", - " stream_mode=\"values\",\n", - " interrupt_before=[\"assistant\"],\n", - "):\n", - " print(f\"Receiving new event of type: {chunk.event}...\")\n", - " messages = chunk.data.get('messages', [])\n", - " if messages:\n", - " print(messages[-1])\n", - " print(\"-\" * 50)" - ] - }, - { - "cell_type": "markdown", - "id": "6a82dd35-cbc8-486d-8e20-10d0c4d138d6", - "metadata": {}, - "source": [ - "We get the result of the tool call as `9`, as expected." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "1d1bb3c7-dc26-4c32-b3df-865f41ef3c73", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Receiving new event of type: metadata...\n", - "--------------------------------------------------\n", - "Receiving new event of type: values...\n", - "{'content': '9', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'multiply', 'id': '226bfbad-0cea-4900-80c5-761a62bd4bc1', 'tool_call_id': 'call_vi16je2EIikHuT7Aue2sd1qd', 'artifact': None, 'status': 'success'}\n", - "--------------------------------------------------\n", - "Receiving new event of type: values...\n", - "{'content': 'The result of multiplying 3 by 3 is 9.', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-859bbf47-9f35-4e71-ae98-9d93ee49d16c', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}\n", - "--------------------------------------------------\n" - ] - } - ], - "source": [ - "async for chunk in client.runs.stream(\n", - " thread[\"thread_id\"],\n", - " assistant_id=\"agent\",\n", - " input=None,\n", - " stream_mode=\"values\",\n", - " interrupt_before=[\"assistant\"],\n", - "):\n", - " print(f\"Receiving new event of type: {chunk.event}...\")\n", - " messages = chunk.data.get('messages', [])\n", - " if messages:\n", - " print(messages[-1])\n", - " print(\"-\" * 50)" - ] - }, - { - "cell_type": "markdown", - "id": "6914c5ca-27e4-421c-835a-9e4327dac12f", - "metadata": {}, - "source": [ - "## Awaiting user input\n", - "\n", - "So, it's clear that we can edit our agent state after a breakpoint.\n", - "\n", - "Now, what if we want to allow for human feedback to perform this state update?\n", - "\n", - "We'll add a node that [serves as a placeholder for human feedback](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/wait-user-input/#setup) within our agent.\n", - "\n", - "This `human_feedback` node allow the user to add feedback directly to state.\n", - " \n", - "We specify the breakpoint using `interrupt_before` our `human_feedback` node.\n", - "\n", - "We set up a checkpointer to save the state of the graph up until this node." - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "e4b475ff-681f-4660-80dd-d6ade7bd48e3", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAFUAMcDASIAAhEBAxEB/8QAHQABAAIDAAMBAAAAAAAAAAAAAAYIBAUHAQIDCf/EAFgQAAEDBAADAgYJDwgJAgcAAAECAwQABQYRBxIhEzEIFBYiQVEVVFVhkZS00dMXIzI4QlZxdHWBk5Wy0tQzNTY3cnOSsQkkJUNSU2KWoSZEZHaEhaOzwv/EABsBAQEAAwEBAQAAAAAAAAAAAAABAgMEBQYH/8QANREBAAECAQgIBQUAAwAAAAAAAAECEQMEEiExQVGR0RMUM1JhcaHBBRUikrEjU4Hh8DJDsv/aAAwDAQACEQMRAD8A/VOlKUClKUClKUCsaZc4dv141LYjb6jtnAj/ADNaDtJeaFZjSnrbYQSgPx/MkTSDolC/uGuhAUPOX3pKUgKXlRMDx2Fst2WCpwkqU86ylxxRPeStW1E/hNdGZRRoxJ07o9/9K2jayvKqy+7ED4yj56eVVl92IHxlHz08lbL7jwPiyPmp5K2X3HgfFkfNT9Hx9F0HlVZfdiB8ZR89PKqy+7ED4yj56eStl9x4HxZHzU8lbL7jwPiyPmp+j4+hoPKqy+7ED4yj56eVVl92IHxlHz08lbL7jwPiyPmp5K2X3HgfFkfNT9Hx9DQDKbKToXeBv8ZR89Z8eUzMb7Rh5t9vu521BQ+EVgDFrKDsWiBv8WR81YEjh7YHHO3jW5u1zANJl2z/AFV4erzka5h/0q2O/YOzS2DO2Y4T7wmhI6VH7dcZtpns2q7umUXtiJcuQID+hvs3AOiXdbPQBKgCQBopEgrVXRNEk6ClKVghSlKBSlKBSlKBSlKBSlKBUbz59fsI1b2llty6SWoHOkkFKFq+ukEdQezDmiO467u+pJUYzwdhHs0877ODdI7rhA3pKyWSfwDtdn1AE1vwO1p/2nZ6rGtI2GG4zDbLKEtNNpCEIQNBKQNAAegV9KUrQhUHzTjZhnD7IIljvt4MW7SmRIRFZiPyFIaK+QOOdkhQbQVAgKXygkHr0qcVW7wjhdrHmzF/wOy5cOJCLc1HiTbVbTKtFya7dR8Tmk+YgJ2pXOSgpDmws/YgJ1jfhB2q/wDGvKOHioM9iVaTHbYlCBKW3IcW2445zr7Hs2kpCAEqUvS9nlJ7q2uJcfsCzjKPJ2zX7t7wpLi2o70N+OJAb+zLK3G0pd5fTyFWh17qhNslXrBvCIzt2Rjd2lM5bCtRtlyhQnJEFt5hp5txEh1I0yApSTtWtpOxXJcKt2WXbiDwfyC/2jiFOyS3XSQMmlXaO8m3QnX4j7ITGZB7PsudYHatJKQgArX1FB3e5eFBgqLFkM60Tpd9es0eW4+xDtkxSEuxypK2luJZKW1cydaV15SFgFPWpJwd4pQeL+B2zIobEqKt9hlUliTDfjht5TSFqSgvNo7VA59BxAKVa6Gud8H8GuqfB1y+wu2x623e6TchDbExksLWXpUkNLIUAdKSpBCu4p0R0qVeDhkD9y4U47aJtgveP3KxWuHbpbF5t7kXmebZShfZKUNOJ2g+cnY0R66DqNKUoNNl9pXecdmsMkJmJR20Vw/7t9HnNK/MoJ/CNj01lWC7N36xW65tDlamxm5KB6gtIUP869r3dG7JZp1wd2W4rC31BI2SEpJ0B6T0rCwq1OWLDrFbXv5aJBYYX/aS2lJ/8iujXg6d+jhp9l2N1SlK50KUpQKUpQKUpQKUpQKUpQKx7hAj3WBJhS2kvxZLamXWl9y0KGlA/hBNZFKsTMTeBG7Vd3LI8zZ709p/7CJOcJCJid6SCo9A9rW0/ddVJ6bCdVfuBXDrKbvJut5wbHrpc5Kgp+ZMtrTrrhAABUpSST0AHX1VMp0CNdIjsSZHalxXU8rjD6AtCx6ik9CKj/kDGY6QLpeLY31IaYnLWgb9SXOcJHvDQ96t/wCniaZm0+Wj+l0Sjx8GzhOe/hvix/8AtDH7tTLGMSsmE2lFrx+0wrLbUKUtMSAwlloKUdkhKQBsmtb5EyPvqv36Zn6KnkTI++q/fpmfoqdHh9/0ktG9KKVF/ImR99V+/TM/RVyXA71kOSeEJxRwqXlF1FnxqNa3YSm1NB0qkMqW5zq7PR6ga0Br36dHh9/0ktG9YKo1mPDTEuIZiHKMatWQmJz+Lm5w23+x5tc3LzA63yp3rv0PVXp5EyPvqv36Zn6KnkTI++q/fpmfoqdHh9/0ktG9oB4N/CkNqQOHGLhCiFFPsSxokb0dcvvn4TW8xPhhhfDhyXLxzGbNji3mwmQ9b4bccrQnrpRSBsDv617pwqQCD5U3069BeZ6//ir2b4fWtxxC7i7NvakHaU3OUt5oH19lsN79/l3TMwo118I52LQ+Snk54+wI+nMdYdS8uSN6muIUFIS36FNBQBKu5RAA2N1K68AADQ6CvNa66860RoiApSla0KUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKrvwk+3J4+fiVg+SrqxFV34Sfbk8fPxKwfJV0FiKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKrvwk+3J4+fiVg+SrqxFV34Sfbk8fPxKwfJV0FiKUpQKUpQKUpQKUpQKUpQKUpQKUpQKUpQKVrb/AHxmwQPGHG1vurWGmI7X2bzh+xQN9B3EknoACSQAajjl+y5SiUW2yoSe5KpryiPz9kN/BXRRgV4kXjV4zZbJrSoR7O5h7Qsfxt76Ons7mHtCx/G3vo62dVr3xxgsm9KhHs7mHtCx/G3vo6ezuYe0LH8be+jp1WvfHGCyb0qEezuYe0LH8be+jp7O5h7Qsfxt76OnVa98cYLIt4V/BRHHrglfMbabC7uyPH7UonXLLbCuQbPQc4Utsk9wcJ9Ffjhwu4XXbilxPsmEwWls3K4zBFX2jZ3HSCS6tSeh0hIUojv0k1+3Hs7mHtCx/G3vo645gHg9PcO+N+X8TLdAsxueQI0IipDoaiKWQp9aD2eyXFgKPq2oDorQdVr3xxgssNi2OQsOxi0WC2oU3brVDZgxkKVzFLTSAhAJ9J0kVtKhHs7mHtCx/G3vo6ezuYe0LH8be+jp1WvfHGCyb0qEezuYe0LH8be+jp7O5h7Qsfxt76OnVa98cYLJvSoR7O5h7Qsfxt76Ons7mHtCx/G3vo6dVr3xxgsm9KhHs7mHtCx/G3vo69k5Pk0L69NtFvkxk9XEwJTingn0lCVNgKPvbHvbPSnVcTfHGCya0r4QZzFzhR5cVwPRn20utuJ7lJI2D8FfeuSYmJtKFKUqBSlKBSlKCHZ8f9r4cPQbq53/AIlJrJly2LfEelSnm40ZhCnXXnlhCG0JG1KUo9AAASSaxs+/njDfyq58ik1oeLn9VOafkWb/APoXXpx2VHl7ys7EmhTY9yhR5kOQ1KiSG0usvsLC23EKG0qSodCCCCCO/dfaq0cJL3mWEO8FbdccmRfbFlllLAtyre0wLepmAJDRaWnz1DlQUK5yrZOxruGVj3FvLJ3AzgpkL9157xkV8tsO6SfFmh4w06twOJ5QjlTsJHVIBGuhFYZyLG0qp8viFxMexAZTGzhLBVm7uNN25dojLYEZVxXFQtZ0FqcQCkghSQQgBQJJUZS/xJv2GOcVMfyHNnVmwt2t625Aq0suzNzOdIYEdpKUOr52uVGkj+UG96pnCw9KqSeN3ELHcD4yR7jLuKbzjFqiXW1T75bYkeYEvdqCl1phS2SAWuh0DpRBAIrqmM3zLcY412/FL7knlNb7zYZF0QpyCzGMSQy8yhSW+zAJaUl7oFlagUjzjs0iq47FWqtuWWO83Wfa7febfOudvIEyFGlIcejE9wcQkko7j3gd1Zd2ivzrXMjRZrltlPMrbamsoQtcdZSQlxKVgpJSSCAoEHXUEVT/AIbZRf8AhR4MeLXC0yn7zkOY34WyMsQYpdirdfkFa0j60HlkNrUO2c1zrAJ5elJqsLlUqrd04u8VeGOI5XcL1Z7tcYiGYbVnuGQxLfHkCa/JSwW1NxHy2tADiXAT2fVKkk9QqslrihxS4aW/I75kdpvt5xu32OVPW/kEO2w3WpjYBabR4k+vmbXtQPMnaeUHmOzTOgWOm3y3W2bDhzLhFiy5nP4sw+8lDj/InmXyJJ2rlT5x13Dqa97VdoN9tsa4W2ZHuECSgOsSorqXWnUHuUlaSQoH1g1WG6WXM4PFPg9cMuy9GRPz493eVEZt7MdiG4bcpRSypHnKQN688qJ0Dsb1Wr4QXzNOGHCbgpfDk6L1jF7dttkfx963tNeKok+Y04y8nz1KQrl5gsqCgVa5elTO0i3lKq3M40ZVB4l2adab9dclwy45UmwOl6xxY9rbDjqmuViQFiQ4ttYAK9KbUUK6joKmnDO7ZzxekSsvazEY/jzV6kw4uPx7Yw8l2NHkKZUXnVguBxzs1nzCkJ2OiqsVXHZLbeYF5RIVb50acmO+uK8YzyXA08g6W2rlJ0tJ6FJ6g99ZlVD4RcSLm/xYuuA2uW7YIz2b3+fOursULTNLcgqFvYUtJR2ikntFnvShPmnmOxbyrTOdA+HC474f2T3mND3hzGpVUV4W/wBX9l/uT+0alVc2U9vX5z+VnXJSlK50KUpQKUpQQ7Pv54w38qufIpNeuRWNjJsfudnlLcbjXCK7EdWyQFpQ4gpUUkgjeidbB/BXtn388Yb+VXPkUmve5XaHaG2lzH0sIdX2aCr7pWirQ/Mkn81enHZUeXvKzsROPwjs8d7h+6mTOKsJaUzbgXEadCopjHtvM848h35vL53vdKisHwYMfgOWFpq/5IbRYLqi72qzKmNmJEdStSwhKey5lI2ogBalFIOklNdJ8rrP7eb+A/NTyus/t5v4D81YfSiIDgRYBiqLB45cvE0ZD5Shfat9p4z4343yb5Ndn2nTWt8vTm31plPAfHsuuOUzpsm5NysgTbu0djPpbVEchLWuO6weXaVhSySVFQOh01sGX+V1n9vN/Afmp5XWf2838B+an0jmsvwYrDcoeVtXDIsluL+UWxFrusuVMaW68hCyptxI7LlQtIUpICUhGidpJO6l2T4Ss5RCzO1MeyGSWu2vW2JAlTBGiOtvONKWXFhpxQUOyGiAR3gjrsbzyus/t5v4D81PK6z+3m/gPzU+kR+0XziLIucZu5Yfj8KApYD8iNkjr7jaPSUtmEgKPvFQ/DWuTwBxhXCiFw/eXPftMJzt4kwyAiZHfDynkPNuoSnlWlajogd3Q767mPldZ/bzfwH5qeV1n9vN/Afmpo2yIkxwQtcvEL9jeR3u/ZpAvSENyTfpiXFoSnqns+zQhLZB0rmSAdgEkkCvfHeDEO02y72675JkeZQLnDNvejZFOS+2lgghSUpQhA2QSCs7Uf8AiqVeV1n9vN/Afmp5XWf2838B+an0jnOO+DXacfvWPXFzK8rvAsDchm2xLpPbeZjtvMllSAOyCiAg9CVEjQ6kdKYd4M2PYhIxvd9yO923HOVdptN2nJdiRHUpKUuhCW0lSwFK1zEhPMeUJ6a6N5XWf2838B+anldZ/bzfwH5qfSOar8F/Hi5HbbyDJWLbBuqb1bbU1OQItvlB/t+dpHZ7UCsr81wrAC1aAPUbSPwDtlsymVd7PkmTWGJMni6SrJbbgluA/J5gpaygoKk86htaUrSlWzsdam3ldZ/bzfwH5qeV1n9vN/Afmp9IhsjgHj79im25My5sOP5E5lDM9p1sSYc1b3akskoKQnqpGlJVtKlAk73XSq0/ldZ/bzfwH5q2yFhxCVpO0qGwfeqxbYPlwt/q/sv9yf2jUqqK8Lf6v7L/AHJ/aNSqubKe3r85/KzrkpSlc6FKUoFKUoIdn388Yb+VXPkUms1SEq1sA6Oxsd1YWffzxhv5Vc+RSazq9SOyo8veVnY8co9Qpyj1CvNYs+7QbUYomzI8MynkxmA+6lHbOqBKW0bPnKIB0kdTo1jaEZPKPUKco9QrzXwhT41xaU7EktSmkrW0pbKwtIWlRSpJI9IUCCPQQRS0D7co9Qpyj1CvNKWgeOUeoU5R6hXmvVxxLTalrUEISCpSlHQAHeSaWgeeUeoU5R6hXziS2J8VmVFebkxnkJcaeZWFIcQRsKSR0IIIIIr60tA8co9Qpyj1CtYcmtoydOPeM/7YVDM8RghX8gFhHOVa5R5x1rez16dDW0paB45R6hTlHqFaSZm1ng5VDxtyS4u9Smu3RFYjOvdm11AcdUhJS0klKgFOFIUQQNnpW8paB45R6hXnupSlh8OFv9X9l/uT+0alVRXhb/V/Zf7k/tGpVXLlPb1+c/lZ1yUpSudClKUClKUEOz7+eMN/KrnyKTWdWFnqSbthyugCbo5sk6/9lJFfDIcciZPCRFmPT2WkOB0Kt1xkQnNgEaK2FoUR1Pmk63o62Br047Kjy95WdjaVyXiw827xX4Wx3ZL7DER65XiUluQtLSmGIvLtbYPKrTj7JBUCRo6I5juS/UhsXt/KP+7Lr/E1IcexyJjEJcWG9PeaW4XSq43GRNc2QBoLfWtQHQeaDrezrZO8dMorXgU69w3OFN2lXzI7lPv1gul9u0B65vuocihlC2m0tc3KhaVyWQlaQF9CCTUWst+TiHAPhn5M5KkW6+SknJ7tKySSwxHkFhchyN40Q8YRW8shSkJSSoaJSpfNV0aVjmit+P2S+3C/8OsYl5hcpUGazdckkm1XmUvcLTLcaN42opeeQDKCg4rSiU7HLoa+6FXBNj4x5m1dr/MOPuzYlitrdzkKZb8Ut6WlHswv66tTwdJ5+bzkhQ0rZNiaVc0U8z3iwp/HGImP5zJlR7DggalXiFcXFJdnzH40Rh5x0K89xBQ+rmJJSpR7jusrOr4HsT4sScVye83/AAjybZirur10flsJuTrykLcjvcxPKhpSVuJbPZjoCO8VZzLsPh5pEt8ac6+2zCuMW5pSwpI7RyO6l1tK9g7TzpSSBo9B1Fb2pmyIfhkC1WThtHawqW5dra3EUq2vLmruAd0k8gSt17zk7AAT2iUgdAUjqNVw1vXEC53WU3l1q8QhJY5ml+xkeLtzmHTmbucsnpvoUJH/AFeg9FpWdhU3i7kFtl8UeJkhvIbpbstttmg2fGrfZJjjMmZN5XZACWkH6+AuQyFJUFISN847iF2u+c5xxWu+L3DKIuLXmOuFGtjDd+kQnUDxdp1+U1Daa5JwUtTqfrjnIns9FI1tVsqVjmirt8kTLLL46Z/Y5M9q4x7pDsLEnxp95uKyhuMJL/YFRbUGTKkOJBSQnkVoAFQOFe71NdTkNvwXML5Oxi7PWKyw76u7OzD7IPTT425FkLUromNylQQeQK6ADShVlMQw62YRaVW+1tOJbcfdlPOvuqddfecUVOOuLUSVKUok7/ABoACt3UzRV/i9Ou2N8SrXh8e+rtmNuWky4zt5yydbnJ1wdfWghMpCHHXS0lKCI6VoB7YdCAALGYtbZtmxi0W+5XBV2uMSGyxJnrTyqkupQErdI9BUQVa9+tpSsoiw+HC3+r+y/wByf2jUqqLcLhrh/ZPfY2CO4gkkGpTXNlPbV+c/lZ1yUpSudClKUClKUGuvtkYv8AxnluMqCkuNSGCA4y4k7StJII2PUQQQSFAgkGOLxrK0nTV+tBSPS7aHSo9fTqSB/wCKmlK30Y9eHFqdXjET+Vu5ll0q7YHjc+/5DmGOWizQW+0kTJVqeShsbAH/ALrqSSEhI2VEgAEkCqmZ5/pIbDj6XG8blN5XJT9isWN2HHUP7a5RWP0dWw4/8Y8V4T4etnIYflDMvSVQIGLMtB9+8LWOUsBog8yDzaUSCNK1okhJ/KfPfAv4l4BkWGW+5WUNM5dJaiQnGX/GUxH3FdI0haUgJdSnziQOVQCiknkWE7etYnh9sci79IfBv4hZ3x94YRczks2XGGJkh1uJGVDekl1pBCe15u2Rraw4nWj9hvfXQ6j5N5f7vWT9TvfxVbrB8QgYBh1kxq1o5LfaYbUNnfeUoSE7PrJ1sn0kmt5TrWJ4fbHIuhPk3l/u9ZP1O9/FU8m8v93rJ+p3v4qptSnWsTw+2ORdCfJvL/d6yfqd7+Kp5N5f7vWT9TvfxVTalOtYnh9sci6E+TeX+71k/U738VTyby/3esn6ne/iqm1KdaxPD7Y5F3P7pYM7Ytst2BdcfmTkNLUxHetrzKHXADypUsSFcoJ0CrlOt70e6ue8C+JmRcabJcSubacdyezSlQb1j0u1urfgPgkAE+MjmQoAlKwADojvBqwVV28IThzfcOyqLxq4cRDIyq0s9jfbK10F+tw0VIIHe8gDaDonzQPO5UpLrWJ4fbHIu6n5N5f7vWT9TvfxVPJvL/d6yfqd7+KrP4acR7Hxawi1ZVjsoS7VcWg4gnQW2ruU2sehaVApI9Y9NSenWsTw+2ORdCfJvL/d6yfqd7+Kr2RiWRSz2VwyCGmKro4Lbb1sPKHpAcU8vl31GwN9ehB61NKVOtYvhwjkXfGHEZgRGYsdtLMdlCW2209yUgaAH4AK+1KVyzN9MoUpSoFKUoFKUoFcf47eEJF4VuQccsNuXlvEe8+ZaMaiHa1E7+vPn/dsp0SVHW+U60ApScnwpOKd14M8D8hymxsx3rvHLEeN42CW0LeeQ0FkDv5efm16dV8+BXg+W/hEiferlPdynP7z9cvOTTht59XQ9m2P920NDSB6hvuAAangl4PcrG8ge4hcRLijLOKNwb5XJxH+rWto7/1aGg/YJAJBVoE7PdzK33GlKBSlKBSlKBSlKBSlKBSlKCquZQn/AAPeJsjOrUy6vhFlEpIyW2sIKk2WashKZ7SR3NrJAWB6xrfmJFpIU2PcobEuI+3JivtpdafZUFIcQobSpJHQggggivjeLPByG0zLXc4rU63TGVMSIz6QpDrahpSVA94IJFV48GgXLhbxSzrguqcu74zj7DF1sUiQomRFjSCT4qsn7JKD9ifVv0EBIWTpSlApSlApSlApSlApSlBXbw/vtXck/HLd8tZqxNV28P77V3JPxy3fLWasTQKUpQKUpQKUpQKUpQKUpQKUpQKrtgv28XE//wCWbX+0qph4UeFZNnHBe+xsNvd2sWUw0eP296zzXYrrzjYJLJLaklQWkqSEk65ign7Gvx6xriZxRvmdMmzZplLmV3lxm3CSxeJAlSSVBLbSnOfmUAT0BOhug/d6laPBbJOxrCMetF0uLl4udvt0eJKuLy1LXKdQ0lK3VKUSolSgVEkk9etbygUpSgUpSgUpSgUpSgrt4f32ruSfjlu+Ws1Ymq7eH99q7kn45bvlrNWJoFKUoFKUoFKUoFKUoMG8XmJYoRlTHChvmCEpSCpbiz3ISkdVKPoAqOniDK35mIX5afQoKhjf5jIBH5xXpmCycwxdo9Ucst3W/ughCQfgWofnrYV6OHh0U0U1VReZ898xs8mWpg/VBl/edfv8cL+Jp9UGX951+/xwv4ms6lZ2wu5HGeaX8GD9UGX951+/xwv4mqs8PvBjbwjwrb7xQGKXJdgcC5dptba4naxpjw08pQL/AC8g5nCgA9OcdByDdtKUthdyOM8y/gwfqgy/vOv3+OF/E0+qDL+86/f44X8TWdSlsLuRxnmX8GD9UGX951+/xwv4mvI4hvIO38UvsdofZOFMZzlHr5W31KP5gTWbSmbhdyOM8y/g3cCfGukNmXEeRIjPJ5kOtnYUKyKiPDlZMW+N/cN3aQEjfdvlUf8Ayon89S6uDFo6OuaYJi0lKUrUhSlKCu3h/fau5J+OW75azViart4f32ruSfjlu+Ws1YmgUpSgUpSgUpSgUpSgheX/ANNsX/uZv+TVRvi/mErBsJkXWFc7JaZKXW20yL8HVsecrXKG2vPcWfuUJ6qNSTL/AOm2L/3M3/JqozxW4bvcRrdZPErsLJd7JdGrvAmLiiU0l5CFo040VJ50lLix0Ukg6IPSvU/6sO26f/UrOxx2L4TmUSOF+TXNNpgPX2w3yPa5c5MGa1CZiupbWZzkZYEhKEIcPMjv83YVqtpl/HPK8csWCPt3TCzGv65Qk5eUyHbI1yEeLtgpc2hToJ85a+VJQoedUksXBbLcacy2fb+IgTfMimRZ7056yNLQ26032S0BrtAC0pCW0hOwpPIfPUTsa6L4PF/suCP43Zs6YhIuUifKvJkWBl+PMXLUCrs2ecBlKOoSkKUPOPMFdNafqRgZPlmQ2LjQ5KNjtOQXONw+nXS3ItokCQ68h2Nzx0qLhQttxwgpPZc40nr3gxLOeKmZZl4LOWZHEyfGvHUCMC9jzcpp6IFLSHo7qVO87TqSpI2T1HNtI2K6vauBCsbuNil2TI34K7Lh7uKQ3HYyX3UFSmVIlEk8pUnsB5hTo77wBo6YeDQ5erPxCbyjKVXa8ZlEjRJM+BbkQUMiOFdisNBa+ZYUrZUpXXQA5RSYkZnErPsx4dYxYES8gw9nIZrzzbinrXOdTJ11SmNDZcceUQkjnPMQO/XXQjdm8IbKc0xjhc/Y7baIl3yy4XC2S03BL6mYzkVD/M4gApWQSwVBCtEghJKTtQk87g1mE+649kTmfxvK+0syYRuRsCSw9Ff7MqT2Hbea4FNAhYX6SCkjpXxw3wdXcSewwryhy5NYzerld2i/CSl2QJjTyVNrUlYSFJW+tfOEgEaHKO+r9Vxz/idxNzq+cKcgiJm22zZNjuZ2+yzZcBp8MSmlvxltqbT2oWgK7dsLQVK2lLid+cCLL2Bu6tWeKi9yIcq6hP8ArD1vYWwwpWz1Qha1qSNa6FRrmN+8Hxu/WTiDBVfnYz2UXmPfI8pmKOa3vsJj9l0KiHQFxkqO+XYUR0766Ri0G722xRo99urN7uqObtpzEPxRDm1Ep01zr5dJIH2R3rfpqxE30jK4cfyWQ/ld79lFS+ohw4/ksh/K737KKl9acp7Wr/bGVWspSlcrEpSlBXbw/vtXck/HLd8tZqxNV28P77V3JPxy3fLWasTQKUpQKUpQKUpQKUpQRDOY641xs167NTkWCXm5JbSVKbbcSPrmh15QUJ3regd9wJGvTnWNrSFJyC1KSRsETW9Ef4qnUmSzCjOyJDqGI7KC4466oJQhIGyok9AAOuzXLZeWz+JUXF7vwrcxe+Y9Kui27xd5pWrkjtLIcSwhIHOtRSpIUToeaQFBXMO2jHpimKa41bp/nct42tz5c42SR5QWvY/+Nb/ep5cY57v2v463+9WyxjhViWHXK93C0WKJEnXqUZk+Ryla33Dv0qJ0kbOkp0kbOgNmpF7FQvacf9En5qz6fC7s8Y5GhC/LjHPd+1/HW/3qeXGOffBa/jrf71TT2Khe04/6JPzVpsywCzZziV5x24RUog3WI7DeXHSlDqErSUlSFEHShvYOjogU6fC7s8Y5GhpPLjHPvgtfx1v96nlxjnu/a/jrf71YfAe9YfmfCuxycVQ/KssJs2tly6MgSv8AViWT2uxvmPJvZ6nYJ1uugexUL2nH/RJ+anT4XdnjHI0IX5cY57v2v463+9Xq5neNtIKjf7afQEploUon0AAHZJ9AHU1NvYqF7Tj/AKJPzV7tQIzCwtqO02sfdIQAadPhd2eMcjQ0OBW6RCtcuRJZXGcnzHZaWXBpaEK0EhQ9B5Ugkejejo9KktKVx4lc4lU1TtJm5SlK1oUpSgrt4f32ruSfjlu+Ws1Ymq7eH99q7kn45bvlrNWJoFKUoFKUoFKUoFRbiZxIs/CfDpuS33xpUCMUI7OFHU+864tQQ22hCe9SlKSkb0NkbIrAzPiTKxjKcQstvxa7ZJ7PSlsuz7elJi25pGud19ZOhrmGk/daVrZASffh5w5k4RcMnnTspu+TyL3cVTQLm6C1Db6htllsAJQlKdAkfZFIPTuoPjFseT3ziAi/PZChOCPWgMtYu7bA264851W4+tfnAhIACND7JQIGiVTC02iDYLZGt1shx7fb4rYaYixWg200gdyUpSAAB6hWXSgUpSgUpSggvD2dfhkmY2m4YpFx6w26a2LPMhlIRcGlo53HCkHaVBR0egBJ9OjU6rnNygybPx1td5l5y1CtFzs67VExGS9y+NzEu9sqQykqG1pbHKdJJ16q6NQKUpQKUpQKUpQKUpQcj8LXC1Z94OGfWppJVJTbVzmAn7IuxyH0Ae+S0B+epbwhzRPEbhZiWThQUu62uPLc19y4psFafzK5h+apW+w3JZcZdQlxpxJQtChsKBGiDVevAffcs/DLIMEkLUqThGSXCxjnO1KZDpdbX+Ah0ge8mgsTSlKBSlc/4Z8d8J4tw75Ix29sPiy3By3T23lBtbKw4pCF6J6tu8vM2sdFDY6KSpKQny1pbQpSlBKUjZUToAVAL1fr5kefy8J8lri1iUmyuOSstZmhgJdcJQhpjlPPzgBZKgQUnlPcQTqb7arzxnlZ/guU4zMsGDdkzEi3iNc+ylXEnS3ShKN8rWilPU9fOSQdkJ6TYrJCxqywLRbWBGt0BhuLGYCioNtoSEpTskk6AA2Tug1PDrh/Z+FuF2rFrA06zabc0W2UvuqdWSSVKUpR7ypSlKPcNk6AHSpJSlApSlApSlApSsC/XU2KxXG5CHKuJhxnJHicJAW+/wAiSrs20kgFataAJA2R1FBzji9dMJt/EHhY1k9nm3G+Sbu61j8qKspRDk9kSpbmnE7SU9NFK/wemurVRTJ/9KliIvWOjHrFdzafGlezXslBb7cR+Xp4tySeXn5u/n6aq0HAHj9jvhG4dMyXGYlyhwIs9duWi6NNtuFxLbbhICFrHLp1I3vewenrDpdKUoFKUoFKUoFK+M2W3AhvyXdhpltTi9d+gNn/ACrn8SBNymHGudxu1yjuSm0vJiQZao7UdKgCEDk0Va31Uokk7I0NJHRhYPSRNUzaIWzo1VzwH/0J4avEiwH63EzCxQckjJ+5DrCjGdCf+pRPMR7266X5IN+7F9/W0j96tXI4T2OXkMS/POXJ29xGVx49xXcHi+02r7JCV82wk+kA1v6vR3/T+zQ6rSufeSDfuxff1tI/ep5IN+7F9/W0j96nV6O/6f2aEV8MXjAeCvALI7zGf7C8TEexltIOlCQ8COZPvoQHHB/Yr8acGXdW8mgqtNpXf30vNurtPYuPNTUocS52TrbZCltlSE7AI9GiDo1+zmccC8Y4jQY0XIkzLyiK94xGFykqlJac0RzBLvMk9/UEaPpqY8KrLYrTjQFmx6048pt12LIYtENuO0pxpxTZUEoHcSnmAOyArRO91qxMDMpz6ZvBZIsavAyHHbVdRAmWoTojUrxC4s9jJjc6Ars3UbPI4nfKpOzogitlSlcqFKUoFKV6uOIZbU44pKG0AqUpR0AB3kmg9qVxnLeMc+e+5GxstxIaSU+yTqA44777SD5oT6lK3v8A4QNEwl++X2UsreyS8KWepKJamh8COUD4K+gwfguPiU51cxT5610RrWcpVXvZK8ffFe/1k9+9T2SvH3xXv9ZPfvV0fIq/3I4SaFIPDb8HGZw78IlEPHret22Zk+JNoYaT08YcWEux0+jYcUCB3BLiBX6fcA+EkPgdwmx/D4hQ45BYBlyEDXbyVec65166Kidb7khI9FcOuls9nJ1um3GbOuEy2ul6FIlSVuORXCNFbSlElCiOm06NbL2SvH3xXv8AWT371PkVf7kcJNC0NKq97JXj74r3+snv3q8i6XlJ2Mive/fuLp/zNPkWJ+5HCTQtBSq92TiTlFheQTcPZmKNc0a4pHNr08rqQFA++oLHvertOJZbBzG1iZDKkKSeR6M7oOML/wCFQBP4QR0I0RsV5WV/D8bJIzq9NO+Bu6UpXmI1eVf0YvH4m9+waj2Nf0ctX4o1+wKkOVf0YvH4m9+waj2Nf0ctX4o1+wK9HB7GfP2XYiPDTjph/FeXeothvEKRLtcuRGXFTMZW6420sIMlKELJ7FSiOVfcQR3b1UlxzOcbzByU3YcgtV7ciq5ZCbdNbkFk+pYQo8p6Hvqqsx9C+GfHrA7RuPn794vExm0stFEuRBcdS7trp5yVtEhOu8nQ61mXqdY+I2aWYcE2mmpFvxG8xJsq2RjGRGDsdCYUZ1WkgOB9IUEHzk8ijoVhnSiztnzvGshu8y1WrIbVc7pD34zChzWnXmNHR50JUVJ69OoqL4LxqsuScKrLnF+kwMQg3IKHLcbghLTag4tAT2qwgEnkJ7h/4rgmEXLE8gncB7NgUBEfKsfeSu+Nsw1MvW6KmE43LalnlBSpbqkDSj5yhsb761FgexVPAPh6u8ZPGxLKsPuc6Iz7NW5ciI3M+udrGlNFI6KbcSQoEEcwKST0LOkXQhTo1yhsy4chqVFeQHGn2FhaHEkbCkqHQg+sV8uGf8yXD8rTvlC6i3BbIpGWcKMWu8mxoxp6VAbWbU02W2441oBCSAUoIAKRroCBUp4Z/wAyXD8rTvlC62V6cCrzj3XYl1KUrzUKUpQK5bxxyB1mLb7AwopTP53pZHpZRoch/tqUn8IQoemupVw3jWytvPLe6ofW3baUtk+kodJXr/Gj/wAV6/wminEyunO2XnhH+lYQulKV+gtZWqm5bY7ddmbXLvNvi3N/XZQnpSEPOb7uVBOzv3hW1qrBstmm3fMrBmmUT7Jd7leX9QU22M6uYw4seLuMuKjrcICSkApX5nL9zquTKMacK1o1/wAKsdcc1x60S/FZ1+tkKT2oZ7GRMbbX2hSFBHKVA8xSpJ136UD6a+96yaz42llV3u0G1pfVyNGbJQyHFepPMRs+8K4nccfguyOPIlR25rzVuYaEiQ2lThCbYkglWu/Y5unp61iWG82Oz5pEn56GlsXDGLa3ZpM9gvMnzFGS2noR2ilFBI7yNVpnKqom0xEaeFpmNPDjI7Nw5zH6oGE2rIfFPEfH2y54v2vacmlFOubQ33eoVI65v4OOvqJYnyjSfFlaGtf7xddIrrwaprwqaqtcxAVt8MyB3Fsut0xCiI0l1EKYj0KbWrlQo/2FqCt+gFY9JNaisecyuS00w1/LPPstNevnU4lKde/siri4dOLRVRXqmFp1rXUpSvyxWrygFWM3cAbJhvaA/sGo9jJBxu1EHYMRrqP7AqaKSFpKVAKSRogjYIqEnEb7ZgI1lnQF21A0wxcGXC4yn0IC0q85I7hsbA1snvruwK6cyaKptpuyjVZtKVq/YbMf+fY/0b3z09hsx/59j/RvfPW+1Hfgs2lK1fsNmP8Az7H+je+ensNmP/Psf6N756Wo78Fm0rF4Z/zHcD6Ddp+j/wDUuVjJsOWv7bdn2iKhXQusR3FrSPWkKUBv8Ox7xqU2a0R7FbWYMUK7JvZ5lq5lLUSVKUo+lSlEkn1k1qxq6Yw5oibzMxq/k1QzaUpXnsSlKUCoTxVwx7K7I0/BQF3W3rLzCNgF5JGltbPQcw0RvQ5kp2QN1NqVuwcWrAxIxaNcCqu0yWlp2tP2Ta09ULQodFJI6FKgdgjoQR66iI4U2oHfsrk3/ck/6arS5jwrtWWSFzULdtd0UAFS4uvruhodog+avQ0N/ZaAG9dKg73A6/tq0ze7a+n/AInIjjZ+ALVX22H8TyPHpicWbTumPexbc4j9Sm1e6uTf9yT/AKapiy0GWkNpKlBCQkFaipR16yepPvmpx9RHJfdW0/oXfnp9RHJfdW0/oXfnropy7IaP+NcR/E8jNQmlTb6iOS+6tp/Qu/PT6iOS+6tp/Qu/PWfzHI/3I9eSZrk9+waDkM7xuROvMdzkCOSBeJUVvQ9PI24lO+vfrda76lFq91cm/wC5J/01dp+ojkvuraf0Lvz15HBHJSet2tQHrDDp/wD6rVOWZBM3muOE8lzXNMexyPjUVxiNInyUOL5yq4T3pawdAaCnVKIHTuB1XROFOIuZJfmLw8j/AGRbnCppZHmyJA2By+tLZ2Sf+MAdSlQEisvAphDyXL5dXLkgEHxWK2YzSveUeZS1D3goD1g10+NGZhRmo8dpDDDSA2200kJShIGgkAdAAOmq8rLviuH0c4OTbdurR4FrPrSlK+SClKUClKUClKUClKUClKUClKUClKUClKUClKUClKUClKUClKUClKUH/9k=", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# System message\n", - "sys_msg = SystemMessage(content=\"You are a helpful assistant tasked with performing arithmetic on a set of inputs.\")\n", - "\n", - "# no-op node that should be interrupted on\n", - "def human_feedback(state: MessagesState):\n", - " pass\n", - "\n", - "# Assistant node\n", - "def assistant(state: MessagesState):\n", - " return {\"messages\": [llm_with_tools.invoke([sys_msg] + state[\"messages\"])]}\n", - "\n", - "# Graph\n", - "builder = StateGraph(MessagesState)\n", - "\n", - "# Define nodes: these do the work\n", - "builder.add_node(\"assistant\", assistant)\n", - "builder.add_node(\"tools\", ToolNode(tools))\n", - "builder.add_node(\"human_feedback\", human_feedback)\n", - "\n", - "# Define edges: these determine the control flow\n", - "builder.add_edge(START, \"human_feedback\")\n", - "builder.add_edge(\"human_feedback\", \"assistant\")\n", - "builder.add_conditional_edges(\n", - " \"assistant\",\n", - " # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools\n", - " # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END\n", - " tools_condition,\n", - ")\n", - "builder.add_edge(\"tools\", \"human_feedback\")\n", - "\n", - "memory = MemorySaver()\n", - "graph = builder.compile(interrupt_before=[\"human_feedback\"], checkpointer=memory)\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "markdown", - "id": "32d4ceb6-a224-4307-8196-3f53d367df5c", - "metadata": {}, - "source": [ - "We will get feedback from the user.\n", - "\n", - "We use `.update_state` to update the state of the graph with the human response we get, as before.\n", - "\n", - "We use the `as_node=\"human_feedback\"` parameter to apply this state update as the specified node, `human_feedback`." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "3fc7bcd6-660c-4a8a-ad8d-e6698dcf6201", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "Multiply 2 and 3\n", - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "no, multiply 3 and 3\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "Tool Calls:\n", - " multiply (call_sewrDyCrAJBQQecusUoT6OJ6)\n", - " Call ID: call_sewrDyCrAJBQQecusUoT6OJ6\n", - " Args:\n", - " a: 3\n", - " b: 3\n", - "=================================\u001b[1m Tool Message \u001b[0m=================================\n", - "Name: multiply\n", - "\n", - "9\n" - ] - } - ], - "source": [ - "# Input\n", - "initial_input = {\"messages\": \"Multiply 2 and 3\"}\n", - "\n", - "# Thread\n", - "thread = {\"configurable\": {\"thread_id\": \"5\"}}\n", - "\n", - "# Run the graph until the first interruption\n", - "for event in graph.stream(initial_input, thread, stream_mode=\"values\"):\n", - " event[\"messages\"][-1].pretty_print()\n", - " \n", - "# Get user input\n", - "user_input = input(\"Tell me how you want to update the state: \")\n", - "\n", - "# We now update the state as if we are the human_feedback node\n", - "graph.update_state(thread, {\"messages\": user_input}, as_node=\"human_feedback\")\n", - "\n", - "# Continue the graph execution\n", - "for event in graph.stream(None, thread, stream_mode=\"values\"):\n", - " event[\"messages\"][-1].pretty_print()" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "abf4cf5f-c0cb-4fdb-be6b-271ae4e967e2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=================================\u001b[1m Tool Message \u001b[0m=================================\n", - "Name: multiply\n", - "\n", - "9\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "The result of multiplying 3 and 3 is 9.\n" - ] - } - ], - "source": [ - "# Continue the graph execution\n", - "for event in graph.stream(None, thread, stream_mode=\"values\"):\n", - " event[\"messages\"][-1].pretty_print()" - ] - } - ], - "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.8" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/module-3/streaming-interruption.ipynb b/module-3/streaming-interruption.ipynb deleted file mode 100644 index 9607e909d..000000000 --- a/module-3/streaming-interruption.ipynb +++ /dev/null @@ -1,1942 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0c9e547f", - "metadata": {}, - "source": [ - "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-3/streaming-interruption.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239464-lesson-1-streaming)" - ] - }, - { - "cell_type": "markdown", - "id": "319adfec-2d0a-49f2-87f9-275c4a32add2", - "metadata": {}, - "source": [ - "# Streaming\n", - "\n", - "## Review\n", - "\n", - "In module 2, covered a few ways to customize graph state and memory.\n", - " \n", - "We built up to a Chatbot with external memory that can sustain long-running conversations. \n", - "\n", - "## Goals\n", - "\n", - "This module will dive into `human-in-the-loop`, which builds on memory and allows users to interact directly with graphs in various ways. \n", - "\n", - "To set the stage for `human-in-the-loop`, we'll first dive into streaming, which provides several ways to visualize graph output (e.g., node state or chat model tokens) over the course of execution." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "db024d1f-feb3-45a0-a55c-e7712a1feefa", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "%pip install --quiet -U langgraph langchain_openai langgraph_sdk" - ] - }, - { - "cell_type": "markdown", - "id": "70d7e41b-c6ba-4e47-b645-6c110bede549", - "metadata": {}, - "source": [ - "## Streaming\n", - "\n", - "LangGraph is built with [first class support for streaming](https://langchain-ai.github.io/langgraph/concepts/low_level/#streaming).\n", - "\n", - "Let's set up our Chatbot from Module 2, and show various way to stream outputs from the graph during execution. " - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "5b430d92-f595-4322-a56e-06de7485daa8", - "metadata": {}, - "outputs": [], - "source": [ - "import os, getpass\n", - "\n", - "def _set_env(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass.getpass(f\"{var}: \")\n", - "\n", - "_set_env(\"OPENAI_API_KEY\")" - ] - }, - { - "cell_type": "markdown", - "id": "4d0682fc", - "metadata": {}, - "source": [ - "Note that we use `RunnableConfig` with `call_model` to enable token-wise streaming. This is [only needed with python < 3.11](https://langchain-ai.github.io/langgraph/how-tos/streaming-tokens/). We include in case you are running this notebook in CoLab, which will use python 3.x. " - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "2d7321e0-0d99-4efe-a67b-74c12271859b", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from IPython.display import Image, display\n", - "\n", - "from langchain_openai import ChatOpenAI\n", - "from langchain_core.messages import SystemMessage, HumanMessage, RemoveMessage\n", - "from langchain_core.runnables import RunnableConfig\n", - "\n", - "from langgraph.checkpoint.memory import MemorySaver\n", - "from langgraph.graph import StateGraph, START, END\n", - "from langgraph.graph import MessagesState\n", - "\n", - "# LLM\n", - "model = ChatOpenAI(model=\"gpt-4o\", temperature=0) \n", - "\n", - "# State \n", - "class State(MessagesState):\n", - " summary: str\n", - "\n", - "# Define the logic to call the model\n", - "def call_model(state: State, config: RunnableConfig):\n", - " \n", - " # Get summary if it exists\n", - " summary = state.get(\"summary\", \"\")\n", - "\n", - " # If there is summary, then we add it\n", - " if summary:\n", - " \n", - " # Add summary to system message\n", - " system_message = f\"Summary of conversation earlier: {summary}\"\n", - "\n", - " # Append summary to any newer messages\n", - " messages = [SystemMessage(content=system_message)] + state[\"messages\"]\n", - " \n", - " else:\n", - " messages = state[\"messages\"]\n", - " \n", - " response = model.invoke(messages, config)\n", - " return {\"messages\": response}\n", - "\n", - "def summarize_conversation(state: State):\n", - " \n", - " # First, we get any existing summary\n", - " summary = state.get(\"summary\", \"\")\n", - "\n", - " # Create our summarization prompt \n", - " if summary:\n", - " \n", - " # A summary already exists\n", - " summary_message = (\n", - " f\"This is summary of the conversation to date: {summary}\\n\\n\"\n", - " \"Extend the summary by taking into account the new messages above:\"\n", - " )\n", - " \n", - " else:\n", - " summary_message = \"Create a summary of the conversation above:\"\n", - "\n", - " # Add prompt to our history\n", - " messages = state[\"messages\"] + [HumanMessage(content=summary_message)]\n", - " response = model.invoke(messages)\n", - " \n", - " # Delete all but the 2 most recent messages\n", - " delete_messages = [RemoveMessage(id=m.id) for m in state[\"messages\"][:-2]]\n", - " return {\"summary\": response.content, \"messages\": delete_messages}\n", - "\n", - "# Determine whether to end or summarize the conversation\n", - "def should_continue(state: State):\n", - " \n", - " \"\"\"Return the next node to execute.\"\"\"\n", - " \n", - " messages = state[\"messages\"]\n", - " \n", - " # If there are more than six messages, then we summarize the conversation\n", - " if len(messages) > 6:\n", - " return \"summarize_conversation\"\n", - " \n", - " # Otherwise we can just end\n", - " return END\n", - "\n", - "# Define a new graph\n", - "workflow = StateGraph(State)\n", - "workflow.add_node(\"conversation\", call_model)\n", - "workflow.add_node(summarize_conversation)\n", - "\n", - "# Set the entrypoint as conversation\n", - "workflow.add_edge(START, \"conversation\")\n", - "workflow.add_conditional_edges(\"conversation\", should_continue)\n", - "workflow.add_edge(\"summarize_conversation\", END)\n", - "\n", - "# Compile\n", - "memory = MemorySaver()\n", - "graph = workflow.compile(checkpointer=memory)\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "f847a787-b301-488c-9b58-cba9f389f55d", - "metadata": {}, - "source": [ - "### Streaming full state\n", - "\n", - "Now, let's talk about ways to [stream our graph state](https://langchain-ai.github.io/langgraph/concepts/low_level/#streaming).\n", - "\n", - "`.stream` and `.astream` are sync and async methods for streaming back results. \n", - " \n", - "LangGraph supports a few [different streaming modes](https://langchain-ai.github.io/langgraph/how-tos/stream-values/) for [graph state](https://langchain-ai.github.io/langgraph/how-tos/stream-values/):\n", - " \n", - "* `values`: This streams the full state of the graph after each node is called.\n", - "* `updates`: This streams updates to the state of the graph after each node is called.\n", - "\n", - "![values_vs_updates.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbaf892d24625a201744e5_streaming1.png)\n", - "\n", - "Let's look at `stream_mode=\"updates\"`.\n", - "\n", - "Because we stream with `updates`, we only see updates to the state after node in the graph is run.\n", - "\n", - "Each `chunk` is a dict with `node_name` as the key and the updated state as the value." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "9a6f8ae9-f244-40c5-a2da-618b72631b22", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'conversation': {'messages': AIMessage(content='Hi Lance! How can I assist you today?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 11, 'total_tokens': 21}, 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_25624ae3a5', 'finish_reason': 'stop', 'logprobs': None}, id='run-6d58e31e-a278-4df6-ab0a-bb51d08ca037-0', usage_metadata={'input_tokens': 11, 'output_tokens': 10, 'total_tokens': 21})}}\n" - ] - } - ], - "source": [ - "# Create a thread\n", - "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", - "\n", - "# Start conversation\n", - "for chunk in graph.stream({\"messages\": [HumanMessage(content=\"hi! I'm Lance\")]}, config, stream_mode=\"updates\"):\n", - " print(chunk)" - ] - }, - { - "cell_type": "markdown", - "id": "0c4882e9-07dd-4d70-866b-dfc530418cad", - "metadata": {}, - "source": [ - "Let's now just print the state update." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "c859c777-cb12-4682-9108-6b367e597b81", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "Hi Lance! How are you doing today?\n" - ] - } - ], - "source": [ - "# Start conversation\n", - "for chunk in graph.stream({\"messages\": [HumanMessage(content=\"hi! I'm Lance\")]}, config, stream_mode=\"updates\"):\n", - " chunk['conversation'][\"messages\"].pretty_print()" - ] - }, - { - "cell_type": "markdown", - "id": "583bf219-6358-4d06-ae99-c40f43569fda", - "metadata": {}, - "source": [ - "Now, we can see `stream_mode=\"values\"`.\n", - "\n", - "This is the `full state` of the graph after the `conversation` node is called." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "6ee763f8-6d1f-491e-8050-fb1439e116df", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "hi! I'm Lance\n", - "---------------------------------------------------------------------------\n", - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "hi! I'm Lance\n", - "==================================\u001b[1m Ai Message \u001b[0m==================================\n", - "\n", - "Hi Lance! How can I assist you today?\n", - "---------------------------------------------------------------------------\n" - ] - } - ], - "source": [ - "# Start conversation, again\n", - "config = {\"configurable\": {\"thread_id\": \"2\"}}\n", - "\n", - "# Start conversation\n", - "input_message = HumanMessage(content=\"hi! I'm Lance\")\n", - "for event in graph.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n", - " for m in event['messages']:\n", - " m.pretty_print()\n", - " print(\"---\"*25)" - ] - }, - { - "cell_type": "markdown", - "id": "563c198a-d1a4-4700-b7a7-ff5b8e0b25d7", - "metadata": {}, - "source": [ - "### Streaming tokens\n", - "\n", - "We often want to stream more than graph state.\n", - "\n", - "In particular, with chat model calls it is common to stream the tokens as they are generated.\n", - "\n", - "We can do this [using the `.astream_events` method](https://langchain-ai.github.io/langgraph/how-tos/streaming-from-final-node/#stream-outputs-from-the-final-node), which streams back events as they happen inside nodes!\n", - "\n", - "Each event is a dict with a few keys:\n", - " \n", - "* `event`: This is the type of event that is being emitted. \n", - "* `name`: This is the name of event.\n", - "* `data`: This is the data associated with the event.\n", - "* `metadata`: Contains`langgraph_node`, the node emitting the event.\n", - "\n", - "Let's have a look." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "6ae8c7a6-c6e7-4cef-ac9f-190d2f4dd763", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/var/folders/l9/bpjxdmfx7lvd1fbdjn38y5dh0000gn/T/ipykernel_66136/946416104.py:3: LangChainBetaWarning: This API is in beta and may change in the future.\n", - " async for event in graph.astream_events({\"messages\": [input_message]}, config, version=\"v2\"):\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Node: . Type: on_chain_start. Name: LangGraph\n", - "Node: __start__. Type: on_chain_start. Name: __start__\n", - "Node: __start__. Type: on_chain_end. Name: __start__\n", - "Node: conversation. Type: on_chain_start. Name: conversation\n", - "Node: conversation. Type: on_chat_model_start. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_stream. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chat_model_end. Name: ChatOpenAI\n", - "Node: conversation. Type: on_chain_start. Name: ChannelWrite\n", - "Node: conversation. Type: on_chain_end. Name: ChannelWrite\n", - "Node: conversation. Type: on_chain_start. Name: should_continue\n", - "Node: conversation. Type: on_chain_end. Name: should_continue\n", - "Node: conversation. Type: on_chain_stream. Name: conversation\n", - "Node: conversation. Type: on_chain_end. Name: conversation\n", - "Node: . Type: on_chain_stream. Name: LangGraph\n", - "Node: . Type: on_chain_end. Name: LangGraph\n" - ] - } - ], - "source": [ - "config = {\"configurable\": {\"thread_id\": \"3\"}}\n", - "input_message = HumanMessage(content=\"Tell me about the 49ers NFL team\")\n", - "async for event in graph.astream_events({\"messages\": [input_message]}, config, version=\"v2\"):\n", - " print(f\"Node: {event['metadata'].get('langgraph_node','')}. Type: {event['event']}. Name: {event['name']}\")" - ] - }, - { - "cell_type": "markdown", - "id": "0b63490f-3d24-4f68-95ca-5320ccb61d2d", - "metadata": {}, - "source": [ - "The central point is that tokens from chat models within your graph have the `on_chat_model_stream` type.\n", - "\n", - "We can use `event['metadata']['langgraph_node']` to select the node to stream from.\n", - "\n", - "And we can use `event['data']` to get the actual data for each event, which in this case is an `AIMessageChunk`. " - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "cc3529f8-3960-4d41-9ed6-373f93183950", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'chunk': AIMessageChunk(content='', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='The', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' San', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Francisco', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' ', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='49', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ers', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' are', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' a', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' professional', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' American', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' football', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' team', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' based', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' in', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' San', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Francisco', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Bay', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Area', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' They', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' compete', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' in', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' National', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Football', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' League', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' (', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='NFL', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=')', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' as', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' a', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' member', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' of', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' league', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=\"'s\", id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' National', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Football', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Conference', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' (', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='N', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='FC', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=')', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' West', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' division', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Here', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' are', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' some', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' key', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' points', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' about', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' team', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=':\\n\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='###', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' History', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='-', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' **', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='Founded', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=':**', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' ', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='194', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='6', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='-', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' **', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='Joined', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' NFL', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=':**', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' ', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='194', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='9', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=',', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' as', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' part', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' of', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' merger', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' between', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' NFL', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' and', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' All', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='-Amer', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ica', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Football', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Conference', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' (', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='AA', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='FC', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=')\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='-', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' **', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='Team', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Name', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Origin', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=':**', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' The', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' name', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' \"', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='49', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ers', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='\"', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' is', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' a', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' reference', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' to', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' prospect', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ors', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' who', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' arrived', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' in', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Northern', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' California', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' during', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' ', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='184', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='9', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Gold', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Rush', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.\\n\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='###', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Ach', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ievements', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='-', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' **', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='Super', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Bowl', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Championships', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=':**', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' The', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' ', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='49', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ers', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' have', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' won', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' five', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Super', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Bowl', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' titles', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' (', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='X', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='VI', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=',', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' XIX', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=',', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' XX', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='III', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=',', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' XX', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='IV', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=',', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' and', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' XX', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='IX', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=').\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='-', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' **', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='Conference', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Championships', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=':**', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' They', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' have', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' won', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' NFC', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Championship', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' seven', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' times', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='-', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' **', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='Division', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Championships', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=':**', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' The', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' team', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' has', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' numerous', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' NFC', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' West', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' division', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' titles', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.\\n\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='###', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Not', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='able', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Figures', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='-', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' **', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='Joe', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Montana', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=':**', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Hall', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' of', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Fame', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' quarterback', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' who', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' led', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' team', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' to', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' four', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Super', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Bowl', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' victories', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='-', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' **', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='Jerry', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Rice', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=':**', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Wid', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ely', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' considered', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' greatest', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' wide', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' receiver', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' in', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' NFL', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' history', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='-', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' **', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='Steve', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Young', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=':**', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Another', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Hall', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' of', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Fame', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' quarterback', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' who', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' led', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' team', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' to', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' a', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Super', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Bowl', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' victory', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='-', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' **', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='Bill', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Walsh', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=':**', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Legendary', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' head', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' coach', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' known', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' for', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' developing', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' \"', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='West', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Coast', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Off', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ense', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.\"\\n\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='###', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Home', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Stadium', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='-', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' **', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='Le', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='vi', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=\"'s\", id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Stadium', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=':**', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Located', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' in', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Santa', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Clara', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=',', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' California', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=',', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' it', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' has', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' been', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=\" team's\", id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' home', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' since', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' ', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='201', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='4', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Prior', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' to', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' that', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=',', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' they', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' played', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' at', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Cand', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='lestick', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Park', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' in', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' San', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Francisco', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.\\n\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='###', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Team', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Colors', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' and', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Masc', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ot', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='-', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' **', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='Colors', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=':**', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Red', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=',', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' gold', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=',', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' and', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' white', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='-', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' **', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='Masc', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ot', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=':**', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' S', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ourd', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ough', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Sam', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.\\n\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='###', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Recent', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Performance', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='The', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' ', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='49', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ers', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' have', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' experienced', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' various', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' periods', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' of', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' success', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' and', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' rebuilding', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' They', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' reached', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Super', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Bowl', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' in', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' ', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='201', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='9', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' season', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' but', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' were', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' defeated', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' by', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Kansas', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' City', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Chiefs', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' The', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' team', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' has', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' been', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' competitive', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' in', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' recent', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' years', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=',', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' often', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' cont', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ending', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' for', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' playoff', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' spots', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.\\n\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='###', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Community', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' and', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Culture', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='The', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' ', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='49', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ers', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' have', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' a', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' strong', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' fan', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' base', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' and', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' are', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' known', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' for', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' their', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' rich', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' history', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' and', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' tradition', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' They', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' are', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' also', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' active', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' in', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' community', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' service', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' and', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' charitable', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' activities', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' through', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' ', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='49', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ers', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Foundation', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.\\n\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='###', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Rival', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ries', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='The', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' ', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='49', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ers', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' have', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' notable', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' rival', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ries', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' with', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' several', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' teams', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=',', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' including', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Dallas', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Cowboys', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=',', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Seattle', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Seahawks', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=',', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' and', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Los', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Angeles', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Rams', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' These', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' rival', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ries', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' are', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' often', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' marked', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' by', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' intense', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' and', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' memorable', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' games', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.\\n\\n', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='Overall', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=',', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' San', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' Francisco', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' ', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='49', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ers', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' are', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' one', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' of', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' most', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' stor', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='ied', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' franchises', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' in', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' NFL', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' history', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=',', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' known', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' for', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' their', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' success', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' in', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' ', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='198', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='0', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='s', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' and', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' ', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='199', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='0', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='s', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' and', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' their', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' continued', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' pursuit', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' of', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' excellence', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' on', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' the', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content=' field', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='.', id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n", - "{'chunk': AIMessageChunk(content='', response_metadata={'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_25624ae3a5'}, id='run-b76ec3b8-9c45-42fe-b321-4ec3a69c185c')}\n" - ] - } - ], - "source": [ - "node_to_stream = 'conversation'\n", - "config = {\"configurable\": {\"thread_id\": \"4\"}}\n", - "input_message = HumanMessage(content=\"Tell me about the 49ers NFL team\")\n", - "async for event in graph.astream_events({\"messages\": [input_message]}, config, version=\"v2\"):\n", - " # Get chat model tokens from a particular node \n", - " if event[\"event\"] == \"on_chat_model_stream\" and event['metadata'].get('langgraph_node','') == node_to_stream:\n", - " print(event[\"data\"])" - ] - }, - { - "cell_type": "markdown", - "id": "226e569a-76c3-43d8-8f89-3ae687efde1c", - "metadata": {}, - "source": [ - "As you see above, just use the `chunk` key to get the `AIMessageChunk`." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "3aeae53d-6dcf-40d0-a0c6-c40de492cc83", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "|The| San| Francisco| |49|ers| are| a| professional| American| football| team| based| in| the| San| Francisco| Bay| Area|.| They| compete| in| the| National| Football| League| (|NFL|)| as| a| member| of| the| league|'s| National| Football| Conference| (|N|FC|)| West| division|.| Here| are| some| key| points| about| the| team|:\n", - "\n", - "|###| History|\n", - "|-| **|Founded|:**| |194|6| as| a| charter| member| of| the| All|-Amer|ica| Football| Conference| (|AA|FC|)| and| joined| the| NFL| in| |194|9| when| the| leagues| merged|.\n", - "|-| **|Team| Name|:**| The| name| \"|49|ers|\"| is| a| reference| to| the| prospect|ors| who| arrived| in| Northern| California| during| the| |184|9| Gold| Rush|.\n", - "\n", - "|###| Ach|ievements|\n", - "|-| **|Super| Bowl| Championships|:**| The| |49|ers| have| won| five| Super| Bowl| titles| (|X|VI|,| XIX|,| XX|III|,| XX|IV|,| and| XX|IX|).\n", - "|-| **|Conference| Championships|:**| They| have| won| the| NFC| Championship| seven| times|.\n", - "|-| **|Division| Championships|:**| The| team| has| numerous| NFC| West| division| titles|.\n", - "\n", - "|###| Not|able| Figures|\n", - "|-| **|Co|aches|:**| Bill| Walsh|,| who| is| credited| with| developing| the| \"|West| Coast| offense|,\"| is| one| of| the| most| famous| coaches| in| |49|ers| history|.\n", - "|-| **|Players|:**| The| team| has| had| several| Hall| of| Fame| players|,| including| Joe| Montana|,| Jerry| Rice|,| Steve| Young|,| Ronnie| L|ott|,| and| many| others|.\n", - "\n", - "|###| Stadium|\n", - "|-| **|Le|vi|'s| Stadium|:**| Located| in| Santa| Clara|,| California|,| it| has| been| the| team's| home| since| |201|4|.| Before| that|,| they| played| at| Cand|lestick| Park| in| San| Francisco|.\n", - "\n", - "|###| Rival|ries|\n", - "|-| **|Dallas| Cowboys|:**| One| of| the| most| stor|ied| rival|ries|,| especially| prominent| during| the| |198|0|s| and| |199|0|s|.\n", - "|-| **|Seattle| Seahawks|:**| A| more| recent| but| intense| rivalry|,| particularly| since| the| Seahawks| joined| the| NFC| West| in| |200|2|.\n", - "|-| **|Los| Angeles| Rams|:**| A| long|-standing| divis|ional| rivalry|.\n", - "\n", - "|###| Recent| Performance|\n", - "|-| The| |49|ers| have| had| periods| of| both| success| and| struggle| in| recent| years|.| They| reached| the| Super| Bowl| in| the| |201|9| season| but| lost| to| the| Kansas| City| Chiefs|.| The| team| has| been| competitive| in| the| NFC| West| and| continues| to| build| a| strong| roster|.\n", - "\n", - "|###| Ownership| and| Management|\n", - "|-| **|Owner|:**| The| team| is| owned| by| Denise| De|Bart|olo| York| and| John| York|,| with| their| son| Jed| York| serving| as| the| CEO|.\n", - "|-| **|General| Manager|:**| John| Lynch|,| a| former| NFL| player| and| Hall| of| F|amer|,| has| been| the| GM| since| |201|7|.\n", - "|-| **|Head| Coach|:**| Kyle| Shan|ahan|,| known| for| his| offensive| ac|umen|,| has| been| the| head| coach| since| |201|7|.\n", - "\n", - "|The| |49|ers| are| known| for| their| rich| history|,| iconic| players|,| and| significant| contributions| to| the| game| of| football|.| They| continue| to| be| a| prominent| and| competitive| team| in| the| NFL|.||" - ] - } - ], - "source": [ - "config = {\"configurable\": {\"thread_id\": \"5\"}}\n", - "input_message = HumanMessage(content=\"Tell me about the 49ers NFL team\")\n", - "async for event in graph.astream_events({\"messages\": [input_message]}, config, version=\"v2\"):\n", - " # Get chat model tokens from a particular node \n", - " if event[\"event\"] == \"on_chat_model_stream\" and event['metadata'].get('langgraph_node','') == node_to_stream:\n", - " data = event[\"data\"]\n", - " print(data[\"chunk\"].content, end=\"|\")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "5826e4d8-846b-4f6c-a5c1-e781d43022db", - "metadata": {}, - "source": [ - "### Streaming with LangGraph API\n", - "\n", - "--\n", - "\n", - "**⚠️ DISCLAIMER**\n", - "\n", - "*Running Studio currently requires a Mac. If you are not using a Mac, then skip this step.*\n", - "\n", - "*Also, if you are running this notebook in CoLab, then skip this step.*\n", - "\n", - "--\n", - "\n", - "The LangGraph API [has first class support for streaming](https://langchain-ai.github.io/langgraph/cloud/concepts/api/#streaming). \n", - "\n", - "Let's load our `agent` in the Studio UI, which uses `module-3/studio/agent.py` set in `module-3/studio/langgraph.json`.\n", - "\n", - "The LangGraph API serves as the back-end for Studio.\n", - "\n", - "We can interact directly with the LangGraph API via the LangGraph SDK.\n", - "\n", - "We just need to get the URL for the local deployment from Studio.\n", - "\n", - "![Screenshot 2024-08-27 at 2.20.34 PM.png](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66dbaf8943c3d4df239cbf0f_streaming2.png)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8925b632-512b-48e1-9220-61c06bfbf0b8", - "metadata": {}, - "outputs": [], - "source": [ - "import platform\n", - "\n", - "if 'google.colab' in str(get_ipython()) or platform.system() != 'Darwin':\n", - " raise Exception(\"Unfortunately LangGraph Studio is currently not supported on Google Colab or requires a Mac\")" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "079c2ad6", - "metadata": {}, - "outputs": [], - "source": [ - "from langgraph_sdk import get_client\n", - "\n", - "# Replace this with the URL of your own deployed graph\n", - "URL = \"http://localhost:56091\"\n", - "client = get_client(url=URL)\n", - "\n", - "# Search all hosted graphs\n", - "assistants = await client.assistants.search()" - ] - }, - { - "cell_type": "markdown", - "id": "4d15af9e-0e86-41e3-a5ba-ee2a4aa08a32", - "metadata": {}, - "source": [ - "Let's [stream `values`](https://langchain-ai.github.io/langgraph/cloud/how-tos/stream_values/), like before." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "63e3096f-5429-4d3c-8de2-2bddf7266ebf", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "StreamPart(event='metadata', data={'run_id': '1ef6a3d0-41eb-66f4-a311-8ebdfa1b281f'})\n", - "StreamPart(event='values', data={'messages': [{'content': 'Multiply 2 and 3', 'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '345c67cf-c958-4f89-b787-540fc025080c', 'example': False}]})\n", - "StreamPart(event='values', data={'messages': [{'content': 'Multiply 2 and 3', 'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '345c67cf-c958-4f89-b787-540fc025080c', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'function': {'arguments': '{\"a\":2,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-88179a6d-eb1e-4953-ac42-0b533b6d76f6', 'example': False, 'tool_calls': [{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': None}]})\n", - "StreamPart(event='values', data={'messages': [{'content': 'Multiply 2 and 3', 'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '345c67cf-c958-4f89-b787-540fc025080c', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'function': {'arguments': '{\"a\":2,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-88179a6d-eb1e-4953-ac42-0b533b6d76f6', 'example': False, 'tool_calls': [{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': '6', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'multiply', 'id': '4dd5ce10-ac0b-4a91-b34b-c35109dcbf29', 'tool_call_id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'artifact': None, 'status': 'success'}]})\n", - "StreamPart(event='values', data={'messages': [{'content': 'Multiply 2 and 3', 'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '345c67cf-c958-4f89-b787-540fc025080c', 'example': False}, {'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'function': {'arguments': '{\"a\":2,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-88179a6d-eb1e-4953-ac42-0b533b6d76f6', 'example': False, 'tool_calls': [{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'type': 'tool_call'}], 'invalid_tool_calls': [], 'usage_metadata': None}, {'content': '6', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'multiply', 'id': '4dd5ce10-ac0b-4a91-b34b-c35109dcbf29', 'tool_call_id': 'call_iIPryzZZxRtXozwwhVtFObNO', 'artifact': None, 'status': 'success'}, {'content': 'The result of multiplying 2 and 3 is 6.', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-b5862486-a25f-48fc-9a03-a8506a6692a8', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]})\n" - ] - } - ], - "source": [ - "# Create a new thread\n", - "thread = await client.threads.create()\n", - "# Input message\n", - "input_message = HumanMessage(content=\"Multiply 2 and 3\")\n", - "async for event in client.runs.stream(thread[\"thread_id\"], \n", - " assistant_id=\"agent\", \n", - " input={\"messages\": [input_message]}, \n", - " stream_mode=\"values\"):\n", - " print(event)" - ] - }, - { - "cell_type": "markdown", - "id": "556dc7fd-1cae-404f-816a-f13d772b3b14", - "metadata": {}, - "source": [ - "The streamed objects have: \n", - "\n", - "* `event`: Type\n", - "* `data`: State" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "57b735aa-139c-45a3-a850-63519c0004f0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=========================\n", - "content='Multiply 2 and 3' additional_kwargs={'additional_kwargs': {'example': False, 'additional_kwargs': {}, 'response_metadata': {}}, 'response_metadata': {}, 'example': False} id='f51807de-6b99-4da4-a798-26cf59d16412'\n", - "=========================\n", - "content='' additional_kwargs={'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_imZHAw7kvMR2ZeKaQVSlj25C', 'function': {'arguments': '{\"a\":2,\"b\":3}', 'name': 'multiply'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'example': False, 'invalid_tool_calls': [], 'usage_metadata': None} id='run-fa4ab1c6-274d-4be5-8c4a-a6411c7c35cc' tool_calls=[{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_imZHAw7kvMR2ZeKaQVSlj25C', 'type': 'tool_call'}]\n", - "=========================\n", - "content='6' additional_kwargs={'additional_kwargs': {}, 'response_metadata': {}, 'status': 'success'} name='multiply' id='3e7bbfb6-aa82-453a-969c-9c753fbd1d74' tool_call_id='call_imZHAw7kvMR2ZeKaQVSlj25C'\n", - "=========================\n", - "content='The result of multiplying 2 and 3 is 6.' additional_kwargs={'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'example': False, 'invalid_tool_calls': [], 'usage_metadata': None} id='run-e8e0d672-cfb2-42be-850a-345df3718f69'\n", - "=========================\n" - ] - } - ], - "source": [ - "from langchain_core.messages import convert_to_messages\n", - "thread = await client.threads.create()\n", - "input_message = HumanMessage(content=\"Multiply 2 and 3\")\n", - "async for event in client.runs.stream(thread[\"thread_id\"], assistant_id=\"agent\", input={\"messages\": [input_message]}, stream_mode=\"values\"):\n", - " messages = event.data.get('messages',None)\n", - " if messages:\n", - " print(convert_to_messages(messages)[-1])\n", - " print('='*25)" - ] - }, - { - "cell_type": "markdown", - "id": "a555d186-27be-4ddf-934c-895a3105035d", - "metadata": {}, - "source": [ - "There are some new streaming mode that are only supported via the API.\n", - "\n", - "For example, we can [use `messages` mode](https://langchain-ai.github.io/langgraph/cloud/how-tos/stream_messages/) to better handle the above case!\n", - "\n", - "This mode currently assumes that you have a `messages` key in your graph, which is a list of messages.\n", - "\n", - "All events emitted using `messages` mode have two attributes:\n", - "\n", - "* `event`: This is the name of the event\n", - "* `data`: This is data associated with the event" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "4abd91f6-63c0-41ee-9988-7c8248b88a45", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "metadata\n", - "messages/complete\n", - "messages/metadata\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/complete\n", - "messages/complete\n", - "messages/metadata\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/partial\n", - "messages/complete\n" - ] - } - ], - "source": [ - "thread = await client.threads.create()\n", - "input_message = HumanMessage(content=\"Multiply 2 and 3\")\n", - "async for event in client.runs.stream(thread[\"thread_id\"], \n", - " assistant_id=\"agent\", \n", - " input={\"messages\": [input_message]}, \n", - " stream_mode=\"messages\"):\n", - " print(event.event)" - ] - }, - { - "cell_type": "markdown", - "id": "8de2f1ea-b232-43fc-af7a-320efce83381", - "metadata": {}, - "source": [ - "We can see a few events: \n", - "\n", - "* `metadata`: metadata about the run\n", - "* `messages/complete`: fully formed message \n", - "* `messages/partial`: chat model tokens\n", - "\n", - "You can dig further into the types [here](https://langchain-ai.github.io/langgraph/cloud/concepts/api/#modemessages).\n", - "\n", - "Now, let's show how to stream these messages. \n", - "\n", - "We'll define a helper function for better formatting of the tool calls in messages." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "50a85e16-6e3f-4f14-bcf9-8889a762f522", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Metadata: Run ID - 1ef6a3da-687f-6253-915a-701de5327165\n", - "--------------------------------------------------\n", - "Tool Calls:\n", - "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {}\n", - "--------------------------------------------------\n", - "Tool Calls:\n", - "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {}\n", - "--------------------------------------------------\n", - "Tool Calls:\n", - "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {}\n", - "--------------------------------------------------\n", - "Tool Calls:\n", - "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {}\n", - "--------------------------------------------------\n", - "Tool Calls:\n", - "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2}\n", - "--------------------------------------------------\n", - "Tool Calls:\n", - "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2}\n", - "--------------------------------------------------\n", - "Tool Calls:\n", - "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2}\n", - "--------------------------------------------------\n", - "Tool Calls:\n", - "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2}\n", - "--------------------------------------------------\n", - "Tool Calls:\n", - "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2, 'b': 3}\n", - "--------------------------------------------------\n", - "Tool Calls:\n", - "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2, 'b': 3}\n", - "--------------------------------------------------\n", - "Tool Calls:\n", - "Tool Call ID: call_IL4MGMtr1fEpR3Yd9c2goLd8, Function: multiply, Arguments: {'a': 2, 'b': 3}\n", - "Response Metadata: Finish Reason - tool_calls\n", - "--------------------------------------------------\n", - "--------------------------------------------------\n", - "AI: The\n", - "--------------------------------------------------\n", - "AI: The result\n", - "--------------------------------------------------\n", - "AI: The result of\n", - "--------------------------------------------------\n", - "AI: The result of multiplying\n", - "--------------------------------------------------\n", - "AI: The result of multiplying \n", - "--------------------------------------------------\n", - "AI: The result of multiplying 2\n", - "--------------------------------------------------\n", - "AI: The result of multiplying 2 and\n", - "--------------------------------------------------\n", - "AI: The result of multiplying 2 and \n", - "--------------------------------------------------\n", - "AI: The result of multiplying 2 and 3\n", - "--------------------------------------------------\n", - "AI: The result of multiplying 2 and 3 is\n", - "--------------------------------------------------\n", - "AI: The result of multiplying 2 and 3 is \n", - "--------------------------------------------------\n", - "AI: The result of multiplying 2 and 3 is 6\n", - "--------------------------------------------------\n", - "AI: The result of multiplying 2 and 3 is 6.\n", - "--------------------------------------------------\n", - "AI: The result of multiplying 2 and 3 is 6.\n", - "Response Metadata: Finish Reason - stop\n", - "--------------------------------------------------\n" - ] - } - ], - "source": [ - "thread = await client.threads.create()\n", - "input_message = HumanMessage(content=\"Multiply 2 and 3\")\n", - "\n", - "def format_tool_calls(tool_calls):\n", - " \"\"\"\n", - " Format a list of tool calls into a readable string.\n", - "\n", - " Args:\n", - " tool_calls (list): A list of dictionaries, each representing a tool call.\n", - " Each dictionary should have 'id', 'name', and 'args' keys.\n", - "\n", - " Returns:\n", - " str: A formatted string of tool calls, or \"No tool calls\" if the list is empty.\n", - "\n", - " \"\"\"\n", - "\n", - " if tool_calls:\n", - " formatted_calls = []\n", - " for call in tool_calls:\n", - " formatted_calls.append(\n", - " f\"Tool Call ID: {call['id']}, Function: {call['name']}, Arguments: {call['args']}\"\n", - " )\n", - " return \"\\n\".join(formatted_calls)\n", - " return \"No tool calls\"\n", - "\n", - "async for event in client.runs.stream(\n", - " thread[\"thread_id\"],\n", - " assistant_id=\"agent\",\n", - " input={\"messages\": [input_message]},\n", - " stream_mode=\"messages\",):\n", - " \n", - " # Handle metadata events\n", - " if event.event == \"metadata\":\n", - " print(f\"Metadata: Run ID - {event.data['run_id']}\")\n", - " print(\"-\" * 50)\n", - " \n", - " # Handle partial message events\n", - " elif event.event == \"messages/partial\":\n", - " for data_item in event.data:\n", - " # Process user messages\n", - " if \"role\" in data_item and data_item[\"role\"] == \"user\":\n", - " print(f\"Human: {data_item['content']}\")\n", - " else:\n", - " # Extract relevant data from the event\n", - " tool_calls = data_item.get(\"tool_calls\", [])\n", - " invalid_tool_calls = data_item.get(\"invalid_tool_calls\", [])\n", - " content = data_item.get(\"content\", \"\")\n", - " response_metadata = data_item.get(\"response_metadata\", {})\n", - "\n", - " if content:\n", - " print(f\"AI: {content}\")\n", - "\n", - " if tool_calls:\n", - " print(\"Tool Calls:\")\n", - " print(format_tool_calls(tool_calls))\n", - "\n", - " if invalid_tool_calls:\n", - " print(\"Invalid Tool Calls:\")\n", - " print(format_tool_calls(invalid_tool_calls))\n", - "\n", - " if response_metadata:\n", - " finish_reason = response_metadata.get(\"finish_reason\", \"N/A\")\n", - " print(f\"Response Metadata: Finish Reason - {finish_reason}\")\n", - " \n", - " print(\"-\" * 50)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1ae885f8-102f-448a-9d68-8ded8d2bbd18", - "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.12.1" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -}