diff --git a/docs/docs/how-tos/persistence.ipynb b/docs/docs/how-tos/persistence.ipynb
index cf02c0b90..8c71bcb8e 100644
--- a/docs/docs/how-tos/persistence.ipynb
+++ b/docs/docs/how-tos/persistence.ipynb
@@ -7,6 +7,36 @@
"source": [
"# How to add persistence (\"memory\") to your graph\n",
"\n",
+ "
\n",
@@ -87,343 +104,157 @@
},
{
"cell_type": "markdown",
- "id": "21ac643b-cb06-4724-a80c-2862ba4773f1",
+ "id": "e20dd648-df7a-40f5-9b32-afbdcf1ee4d8",
"metadata": {},
"source": [
- "## Set up the tools\n",
- "\n",
- "We will first define the tools we want to use.\n",
- "For this simple example, we will use create a placeholder search engine.\n",
- "However, it is really easy to create your own tools - see documentation [here](https://python.langchain.com/docs/how_to/custom_tools) on how to do that.\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e",
- "metadata": {},
- "outputs": [],
- "source": [
- "from langchain_core.tools import tool\n",
- "\n",
- "\n",
- "@tool\n",
- "def search(query: str):\n",
- " \"\"\"Call to surf the web.\"\"\"\n",
- " # This is a placeholder for the actual implementation\n",
- " # Don't let the LLM know this though 😊\n",
- " return \"The answer to your question lies within.\"\n",
- "\n",
- "\n",
- "tools = [search]"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "01885785-b71a-44d1-b1d6-7b5b14d53b58",
- "metadata": {},
- "source": [
- "We can now wrap these tools in a simple [tool-calling node](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode)."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "id": "5cf3331e-ccb3-41c8-aeb9-a840a94d41e7",
- "metadata": {},
- "outputs": [],
- "source": [
- "from langgraph.prebuilt import ToolNode\n",
- "\n",
- "tool_node = ToolNode(tools)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "5497ed70-fce3-47f1-9cad-46f912bad6a5",
- "metadata": {},
- "source": [
- "## Set up the model\n",
- "\n",
- "Now we need to load the chat model we want to use.\n",
- "Importantly, this should satisfy two criteria:\n",
- "\n",
- "1. It should work with messages. We will represent all agent state in the form of messages, so it needs to be able to work well with them.\n",
- "2. It should work with OpenAI function calling. This means it should either be an OpenAI model or a model that exposes a similar interface.\n",
- "\n",
- "Note: these model requirements are not requirements for using LangGraph - they are just requirements for this one example.\n"
+ "## Input Validation"
]
},
{
"cell_type": "code",
"execution_count": 4,
- "id": "892b54b9-75f0-4804-9ed0-88b5e5532989",
+ "id": "efc46b36-425c-49c3-9f9e-d9785c70b034",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'a': 'goodbye'}"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
- "from langchain_openai import ChatOpenAI\n",
+ "from langgraph.graph import StateGraph, START, END\n",
+ "from typing_extensions import TypedDict\n",
"\n",
- "model = ChatOpenAI(temperature=0)"
+ "from pydantic import BaseModel\n",
+ "\n",
+ "\n",
+ "# The overall state of the graph (this is the public state shared across nodes)\n",
+ "class OverallState(BaseModel):\n",
+ " a: str\n",
+ "\n",
+ "\n",
+ "def node(state: OverallState):\n",
+ " return {\"a\": \"goodbye\"}\n",
+ "\n",
+ "\n",
+ "# Build the state graph\n",
+ "builder = StateGraph(OverallState)\n",
+ "builder.add_node(node) # node_1 is the first node\n",
+ "builder.add_edge(START, \"node\") # Start the graph with node_1\n",
+ "builder.add_edge(\"node\", END) # End the graph after node_1\n",
+ "graph = builder.compile()\n",
+ "\n",
+ "# Test the graph with a valid input\n",
+ "graph.invoke({\"a\": \"hello\"})"
]
},
{
"cell_type": "markdown",
- "id": "a77995c0-bae2-4cee-a036-8688a90f05b9",
+ "id": "25b594c2-8198-4f76-9606-ea47151ff9d1",
"metadata": {},
"source": [
- "\n",
- "After we've done this, we should make sure the model knows that it has these tools available to call.\n",
- "We can do this by converting the LangChain tools into the format for OpenAI function calling, and then bind them to the model class.\n"
+ "Invoke the graph with an **invalid** input"
]
},
{
"cell_type": "code",
"execution_count": 5,
- "id": "cd3cbae5-d92c-4559-a4aa-44721b80d107",
- "metadata": {},
- "outputs": [],
- "source": [
- "model = model.bind_tools(tools)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "8e8b9211-93d0-4ad5-aa7a-9c09099c53ff",
- "metadata": {},
- "source": [
- "## Define the agent state\n",
- "\n",
- "The main type of graph in `langgraph` is the [StateGraph](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.StateGraph).\n",
- "This graph is parameterized by a state object that it passes around to each node.\n",
- "Each node then returns operations to update that state.\n",
- "These operations can either SET specific attributes on the state (e.g. overwrite the existing values) or ADD to the existing attribute.\n",
- "Whether to set or add is denoted by annotating the state object you construct the graph with.\n",
- "\n",
- "For this example, the state we will track will just be a list of messages.\n",
- "We want each node to just add messages to that list.\n",
- "Therefore, we will use a `pydantic.BaseModel` with one key (`messages`) and annotate it so that the `messages` attribute is treated as \"append-only\".\n"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "2d928f61",
- "metadata": {},
- "source": [
- "
\n",
- "
Using Pydantic with LangChain
\n",
- "
\n",
- " This notebook uses Pydantic v2 BaseModel, which requires langchain-core >= 0.3. Using langchain-core < 0.3 will result in errors due to mixing of Pydantic v1 and v2 BaseModels.\n",
- "
\n",
- "
"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "id": "ea793afa-2eab-4901-910d-6eed90cd6564",
- "metadata": {},
- "outputs": [],
- "source": [
- "import operator\n",
- "from typing import Annotated, Sequence\n",
- "\n",
- "from langchain_core.messages import BaseMessage\n",
- "\n",
- "from pydantic import BaseModel\n",
- "\n",
- "\n",
- "class AgentState(BaseModel):\n",
- " messages: Annotated[Sequence[BaseMessage], operator.add]"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "e03c5094-9297-4d19-a04e-3eedc75cefb4",
- "metadata": {},
- "source": [
- "## Define the nodes\n",
- "\n",
- "We now need to define a few different nodes in our graph.\n",
- "In `langgraph`, a node can be either a function or a [runnable](https://python.langchain.com/docs/concepts/#langchain-expression-language-lcel).\n",
- "There are two main nodes we need for this:\n",
- "\n",
- "1. The agent: responsible for deciding what (if any) actions to take.\n",
- "2. A function to invoke tools: if the agent decides to take an action, this node will then execute that action.\n",
- "\n",
- "We will also need to define some edges.\n",
- "Some of these edges may be conditional.\n",
- "The reason they are conditional is that based on the output of a node, one of several paths may be taken.\n",
- "The path that is taken is not known until that node is run (the LLM decides).\n",
- "\n",
- "1. Conditional Edge: after the agent is called, we should either:\n",
- " a. If the agent said to take an action, then the function to invoke tools should be called\n",
- " b. If the agent said that it was finished, then it should finish\n",
- "2. Normal Edge: after the tools are invoked, it should always go back to the agent to decide what to do next\n",
- "\n",
- "Let's define the nodes, as well as a function to decide how what conditional edge to take.\n",
- "\n",
- "**MODIFICATION**\n",
- "\n",
- "We define each node to receive the AgentState base model as its first argument."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "id": "3b541bb9-900c-40d0-964d-7b5dfee30667",
- "metadata": {},
- "outputs": [],
- "source": [
- "# Define the function that determines whether to continue or not\n",
- "def should_continue(state):\n",
- " messages = state.messages\n",
- " last_message = messages[-1]\n",
- " # If there is no function call, then we finish\n",
- " if not last_message.tool_calls:\n",
- " return \"end\"\n",
- " # Otherwise if there is, we continue\n",
- " else:\n",
- " return \"continue\"\n",
- "\n",
- "\n",
- "# Define the function that calls the model\n",
- "def call_model(state):\n",
- " messages = state.messages\n",
- " response = model.invoke(messages)\n",
- " # We return a list, because this will get added to the existing list\n",
- " return {\"messages\": [response]}"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "ffd6e892-946c-4899-8cc0-7c9291c1f73b",
- "metadata": {},
- "source": [
- "## Define the graph\n",
- "\n",
- "We can now put it all together and define the graph!"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 8,
- "id": "813ae66c-3b58-4283-a02a-36da72a2ab90",
- "metadata": {},
- "outputs": [],
- "source": [
- "from langgraph.graph import END, StateGraph, START\n",
- "\n",
- "# Define a new graph\n",
- "workflow = StateGraph(AgentState)\n",
- "\n",
- "# Define the two nodes we will cycle between\n",
- "workflow.add_node(\"agent\", call_model)\n",
- "workflow.add_node(\"action\", tool_node)\n",
- "\n",
- "# Set the entrypoint as `agent`\n",
- "# This means that this node is the first one called\n",
- "workflow.add_edge(START, \"agent\")\n",
- "\n",
- "# We now add a conditional edge\n",
- "workflow.add_conditional_edges(\n",
- " # First, we define the start node. We use `agent`.\n",
- " # This means these are the edges taken after the `agent` node is called.\n",
- " \"agent\",\n",
- " # Next, we pass in the function that will determine which node is called next.\n",
- " should_continue,\n",
- " # Finally we pass in a mapping.\n",
- " # The keys are strings, and the values are other nodes.\n",
- " # END is a special node marking that the graph should finish.\n",
- " # What will happen is we will call `should_continue`, and then the output of that\n",
- " # will be matched against the keys in this mapping.\n",
- " # Based on which one it matches, that node will then be called.\n",
- " {\n",
- " # If `tools`, then we call the tool node.\n",
- " \"continue\": \"action\",\n",
- " # Otherwise we finish.\n",
- " \"end\": END,\n",
- " },\n",
- ")\n",
- "\n",
- "# We now add a normal edge from `tools` to `agent`.\n",
- "# This means that after `tools` is called, `agent` node is called next.\n",
- "workflow.add_edge(\"action\", \"agent\")\n",
- "\n",
- "# Finally, we compile it!\n",
- "# This compiles it into a LangChain Runnable,\n",
- "# meaning you can use it as you would any other runnable\n",
- "app = workflow.compile()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 9,
- "id": "e09aaa63",
- "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",
- "display(Image(app.get_graph().draw_mermaid_png()))"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "547c3931-3dae-4281-ad4e-4b51305594d4",
- "metadata": {},
- "source": [
- "## Use it!\n",
- "\n",
- "We can now use it!\n",
- "This now exposes the [same interface](https://python.langchain.com/docs/concepts/#langchain-expression-language-lcel) as all other LangChain runnables."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 10,
- "id": "8edb04b9-40b6-46f1-a7a8-4b2d8aba7752",
+ "id": "05d7d43b-0b71-4e25-af6f-61d1560a46cb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "================================\u001b[1m Human Message \u001b[0m=================================\n",
- "\n",
- "what is the weather in sf\n",
- "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
- "Tool Calls:\n",
- " search (call_eJMUn9rNv4abSfYe9kVmzk8E)\n",
- " Call ID: call_eJMUn9rNv4abSfYe9kVmzk8E\n",
- " Args:\n",
- " query: weather in San Francisco\n",
- "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
- "Name: search\n",
- "\n",
- "The answer to your question lies within.\n",
- "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
- "\n",
- "I have initiated a search for the weather in San Francisco. I will provide you with the information as soon as I receive the results.\n"
+ "An exception was raised because `a` is an integer rather than a string.\n",
+ "1 validation error for OverallState\n",
+ "a\n",
+ " Input should be a valid string [type=string_type, input_value=123, input_type=int]\n",
+ " For further information visit https://errors.pydantic.dev/2.9/v/string_type\n"
]
}
],
"source": [
- "from langchain_core.messages import HumanMessage\n",
+ "try:\n",
+ " graph.invoke({\"a\": 123}) # Should be a string\n",
+ "except Exception as e:\n",
+ " print(\"An exception was raised because `a` is an integer rather than a string.\")\n",
+ " print(e)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0aafc180-17b5-4364-b1df-fb41aa575067",
+ "metadata": {},
+ "source": [
+ "## Multiple Nodes\n",
"\n",
- "inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
- "for chunk in app.stream(inputs, stream_mode=\"values\"):\n",
- " chunk[\"messages\"][-1].pretty_print()"
+ "Run-time validation will also work in a multi-node graph. In the example below `bad_node` updates `a` to an integer. \n",
+ "\n",
+ "Because run-time validation occurs on **inputs**, the validation error will occur when `ok_node` is called (not when `bad_node` returns an update to the state which is inconsistent with the schema)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "25336b0d-2fe6-45c8-8204-f962c3995df7",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "An exception was raised because bad_node sets `a` to an integer.\n",
+ "1 validation error for OverallState\n",
+ "a\n",
+ " Input should be a valid string [type=string_type, input_value=123, input_type=int]\n",
+ " For further information visit https://errors.pydantic.dev/2.9/v/string_type\n"
+ ]
+ }
+ ],
+ "source": [
+ "from langgraph.graph import StateGraph, START, END\n",
+ "from typing_extensions import TypedDict\n",
+ "\n",
+ "from pydantic import BaseModel\n",
+ "\n",
+ "\n",
+ "# The overall state of the graph (this is the public state shared across nodes)\n",
+ "class OverallState(BaseModel):\n",
+ " a: str\n",
+ "\n",
+ "\n",
+ "def bad_node(state: OverallState):\n",
+ " return {\n",
+ " \"a\": 123 # Invalid\n",
+ " }\n",
+ "\n",
+ "\n",
+ "def ok_node(state: OverallState):\n",
+ " return {\"a\": \"goodbye\"}\n",
+ "\n",
+ "\n",
+ "# Build the state graph\n",
+ "builder = StateGraph(OverallState)\n",
+ "builder.add_node(bad_node)\n",
+ "builder.add_node(ok_node)\n",
+ "builder.add_edge(START, \"bad_node\")\n",
+ "builder.add_edge(\"bad_node\", \"ok_node\")\n",
+ "builder.add_edge(\"ok_node\", END)\n",
+ "graph = builder.compile()\n",
+ "\n",
+ "# Test the graph with a valid input\n",
+ "try:\n",
+ " graph.invoke({\"a\": \"hello\"})\n",
+ "except Exception as e:\n",
+ " print(\"An exception was raised because bad_node sets `a` to an integer.\")\n",
+ " print(e)"
]
}
],
@@ -443,7 +274,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.11.9"
+ "version": "3.11.4"
}
},
"nbformat": 4,