diff --git a/docs/_scripts/copy_notebooks.py b/docs/_scripts/copy_notebooks.py index 5954e7ef5..1ef9d1cb8 100644 --- a/docs/_scripts/copy_notebooks.py +++ b/docs/_scripts/copy_notebooks.py @@ -27,6 +27,8 @@ _MANUAL = { "streaming-events-from-within-tools-without-langchain.ipynb", "streaming-from-final-node.ipynb", "persistence.ipynb", + "input_output_schema.ipynb", + "pass_private_state.ipynb", "memory/manage-conversation-history.ipynb", "memory/delete-messages.ipynb", "memory/add-summary-conversation-history.ipynb", diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index daf74c894..5e4b04f5c 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -157,12 +157,15 @@ nav: - Handle tool calling errors: how-tos/tool-calling-errors.ipynb - Pass graph state to tools: how-tos/pass-run-time-values-to-tools.ipynb - Pass config to tools: how-tos/pass-config-to-tools.ipynb + - State Management: + - Use Pydantic model as state: how-tos/state-model.ipynb + - Use a context object in state: how-tos/state-context-key.ipynb + - Have a separate input and output schema: how-tos/input_output_schema.ipynb + - Pass private state between nodes inside the graph: how-tos/pass_private_state.ipynb - Other: - Run graph asynchronously: how-tos/async.ipynb - Visualize your graph: how-tos/visualization.ipynb - Add runtime configuration: how-tos/configuration.ipynb - - Use Pydantic model as state: how-tos/state-model.ipynb - - Use a context object in state: how-tos/state-context-key.ipynb - Prebuilt ReAct Agent: - Create a ReAct agent: how-tos/create-react-agent.ipynb - Add memory to a ReAct agent: how-tos/create-react-agent-memory.ipynb diff --git a/examples/input_output_schema.ipynb b/examples/input_output_schema.ipynb new file mode 100644 index 000000000..59f902361 --- /dev/null +++ b/examples/input_output_schema.ipynb @@ -0,0 +1,93 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f262985e-e973-4a27-9c9e-dbb3a06a35b7", + "metadata": {}, + "source": [ + "# How to define input/output schema for your graph\n", + "\n", + "By default, `StateGraph` takes in a single schema and all nodes are expected to communicate with that schema. However, it is also possible to define explicit input and output schemas for a graph. This is helpful if you want to draw a distinction between input and output keys.\n", + "\n", + "In this notebook we'll walk through an example of this. At a high level, in order to do this you simply have to pass in `input=..., output=...` when defining the graph. Let's see an example below!" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "6ec0eb77-874e-443e-8c73-93125b515106", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'answer': 'bye'}" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langgraph.graph import StateGraph, START, END\n", + "from typing import TypedDict\n", + "\n", + "class InputState(TypedDict):\n", + " question: str\n", + "\n", + "class OutputState(TypedDict):\n", + " answer: str\n", + "\n", + "def answer_node(state: InputState):\n", + " return {\"answer\": \"bye\"}\n", + "\n", + "check = SqliteSaver.from_conn_string(\":memory:\")\n", + "graph = StateGraph(input=InputState, output=OutputState)\n", + "graph.add_node(answer_node)\n", + "graph.add_edge(START, \"answer_node\")\n", + "graph.add_edge(\"answer_node\", END)\n", + "graph = graph.compile()\n", + "\n", + "graph.invoke({\"question\": \"hi\"})" + ] + }, + { + "cell_type": "markdown", + "id": "6a68836f-98e1-4684-a8a6-c1473c73460c", + "metadata": {}, + "source": [ + "Notice that the output of invoke only includes the output schema." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b952a554-f2a4-4be3-81ab-2e08f0f441c2", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/pass_private_state.ipynb b/examples/pass_private_state.ipynb new file mode 100644 index 000000000..c540e1033 --- /dev/null +++ b/examples/pass_private_state.ipynb @@ -0,0 +1,126 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "47ed5db3-bda5-49e1-bf75-23e08c9a3af0", + "metadata": {}, + "source": [ + "# How to pass private state\n", + "\n", + "Oftentimes, you may want nodes to be able to pass state to eachother that should NOT be part of the main schema of the graph. This is often useful because there may be information that is not needed as input/output (and therefor doesn't really make sense to have in the main schema) but is ABSOLUTELY needed as part of the intermediate working logic.\n", + "\n", + "Let's take a look at an example below. In this example, we will create a RAG pipeline that:\n", + "1. Takes in a user question\n", + "2. Uses an LLM to generate a search query\n", + "3. Retrieves documents for that generated query\n", + "4. Generates a final answer based on those documents\n", + "\n", + "We will have a separate node for each step. We will only have the `question` and `answer` on the overall state. However, we will need separate states for the `search_query` and the `documents` - we will pass these as private state keys.\n", + "\n", + "Let's look at an example!" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "3114c3ad-0ade-47ba-9488-53d6f7671578", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'question': 'foo', 'answer': 'fo\\n\\nfo\\n\\nfoo'}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from langgraph.graph import StateGraph, START, END\n", + "from typing import TypedDict\n", + "\n", + "\n", + "# The overall state of the graph\n", + "class OverallState(TypedDict):\n", + " question: str\n", + " answer: str\n", + "\n", + "\n", + "# This is what the node that generates the query will return\n", + "class QueryOutputState(TypedDict):\n", + " query: str\n", + "\n", + "\n", + "# This is what the node that retrieves the documents will return\n", + "class DocumentOutputState(TypedDict):\n", + " docs: list[str]\n", + "\n", + "\n", + "# This is what the node that generates the final answer will take in\n", + "class GenerateInputState(OverallState, DocumentOutputState):\n", + " pass\n", + "\n", + "\n", + "# Node to generate query\n", + "def generate_query(state: OverallState) -> QueryOutputState:\n", + " # Replace this with real logic\n", + " return {\"query\": state[\"question\"][:2]}\n", + "\n", + "\n", + "# Node to retrieve documents\n", + "def retrieve_documents(state: QueryOutputState) -> DocumentOutputState:\n", + " # Replace this with real logic\n", + " return {\"docs\": [state['query']] * 2}\n", + "\n", + "\n", + "# Node to generate answer\n", + "def generate(state: GenerateInputState) -> OverallState:\n", + " return {\"answer\": \"\\n\\n\".join(state['docs'] + [state['question']])}\n", + "\n", + "\n", + "graph = StateGraph(OverallState)\n", + "graph.add_node(generate_query)\n", + "graph.add_node(retrieve_documents)\n", + "graph.add_node(generate)\n", + "graph.add_edge(START, \"generate_query\")\n", + "graph.add_edge(\"generate_query\", \"retrieve_documents\")\n", + "graph.add_edge(\"retrieve_documents\", \"generate\")\n", + "graph.add_edge(\"generate\", END)\n", + "graph = graph.compile()\n", + "\n", + "graph.invoke({\"question\": \"foo\"})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ffc2d8c-717f-42c9-b0aa-15b178a5cc8b", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.1" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}