Skip to content

Commit

Permalink
fix: Fix message serialization in prebuilt reducer, add subgraph docs (
Browse files Browse the repository at this point in the history
…#516)

Co-authored-by: jacoblee93 <jacoblee93@gmail.com>
  • Loading branch information
bracesproul and jacoblee93 authored Sep 27, 2024
1 parent 7abbb44 commit 5d8716b
Show file tree
Hide file tree
Showing 17 changed files with 2,700 additions and 422 deletions.
2 changes: 1 addition & 1 deletion docs/_scripts/copy_notebooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def copy_notebooks():
if "Untitled" in file:
continue
dst_dir_ = dst_dir
if file.endswith((".ipynb", ".png")):
if file.endswith((".ipynb", ".png", ".jpg", ".jpeg")):
if file in _MAP:
dst_dir = os.path.join(dst_dir, _MAP[file])
src_path = os.path.join(root, file)
Expand Down
Binary file added docs/docs/how-tos/img/doubly-nested-subgraph.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/docs/how-tos/img/single-nested-subgraph.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions docs/docs/how-tos/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ These guides show how to use different streaming modes.
- [How to pass config to tools](pass-config-to-tools.ipynb)
- [How to pass graph state to tools](pass-graph-state-to-tools.ipynb)

## Subgraphs

- [How to create subgraphs](subgraph.ipynb)
- [How to manage state in subgraphs](subgraphs-manage-state.ipynb)
- [How to transform inputs and outputs of a subgraph](subgraph-transform-state.ipynb)

## State management

- [Have a separate input and output schema](input_output_schema.ipynb)
Expand Down
4 changes: 4 additions & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ nav:
- Force an agent to call a tool: "how-tos/force-calling-a-tool-first.ipynb"
- Pass config to tools: "how-tos/pass-config-to-tools.ipynb"
- Pass graph state to tools: "how-tos/pass-graph-state-to-tools.ipynb"
- Subgraphs:
- Create subgraphs: how-tos/subgraph.ipynb
- Manage state in subgraphs: how-tos/subgraphs-manage-state.ipynb
- Transform inputs and outputs of a subgraph: how-tos/subgraph-transform-state.ipynb
- State management:
- 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"
Expand Down
6 changes: 3 additions & 3 deletions examples/how-tos/breakpoints.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"\n",
"Below, we do two things:\n",
"\n",
"1) We specify the [breakpoint](https://langchain-ai.github.io/langgraph/concepts/low_level/#breakpoints) using `interruptBefore` the specified step.\n",
"1) We specify the [breakpoint](https://langchain-ai.github.io/langgraphjs/concepts/low_level/#breakpoints) using `interruptBefore` the specified step.\n",
"\n",
"2) We set up a [checkpointer](https://langchain-ai.github.io/langgraphjs/concepts/#checkpoints) to save the state of the graph."
]
Expand Down Expand Up @@ -141,11 +141,11 @@
"id": "d7d5f80f-9d8c-4a39-b198-24fe94132b41",
"metadata": {},
"source": [
"We create a [thread ID](https://langchain-ai.github.io/langgraph/concepts/low_level/#threads) for the checkpointer.\n",
"We create a [thread ID](https://langchain-ai.github.io/langgraphjs/concepts/low_level/#threads) for the checkpointer.\n",
"\n",
"We run until step 3, as defined with `interruptBefore`. \n",
"\n",
"After the user input / approval, [we resume execution](https://langchain-ai.github.io/langgraph/concepts/low_level/#breakpoints) by invoking the graph with `null`. "
"After the user input / approval, [we resume execution](https://langchain-ai.github.io/langgraphjs/concepts/low_level/#breakpoints) by invoking the graph with `null`. "
]
},
{
Expand Down
Binary file added examples/how-tos/img/doubly-nested-subgraph.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/how-tos/img/single-nested-subgraph.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
279 changes: 279 additions & 0 deletions examples/how-tos/subgraph-transform-state.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to transform inputs and outputs of a subgraph\n",
"\n",
"It's possible that your subgraph state is completely independent from the parent graph state, i.e. there are no overlapping channels (keys) between the two. For example, you might have a supervisor agent that needs to produce a report with a help of multiple ReAct agents. ReAct agent subgraphs might keep track of a list of messages whereas the supervisor only needs user input and final report in its state, and doesn't need to keep track of messages.\n",
"\n",
"In such cases you need to transform the inputs to the subgraph before calling it and then transform its outputs before returning. This guide shows how to do that.\n",
"\n",
"## Setup\n",
"\n",
"First, let's install the required packages\n",
"\n",
"```bash\n",
"npm install @langchain/langgraph @langchain/core\n",
"```\n",
"\n",
"<div class=\"admonition tip\">\n",
" <p class=\"admonition-title\">Set up <a href=\"https://smith.langchain.com\">LangSmith</a> for LangGraph development</p>\n",
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div> "
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"## Define graph and subgraphs\n",
"\n",
"Let's define 3 graphs:\n",
"- a parent graph\n",
"- a child subgraph that will be called by the parent graph\n",
"- a grandchild subgraph that will be called by the child graph"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define grandchild"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import { StateGraph, START, Annotation } from \"@langchain/langgraph\";\n",
"\n",
"const GrandChildAnnotation = Annotation.Root({\n",
" myGrandchildKey: Annotation<string>,\n",
"})\n",
"\n",
"const grandchild1 = (state: typeof GrandChildAnnotation.State) => {\n",
" // NOTE: child or parent keys will not be accessible here\n",
" return {\n",
" myGrandchildKey: state.myGrandchildKey + \", how are you\"\n",
" }\n",
"}\n",
"\n",
"const grandchild = new StateGraph(GrandChildAnnotation)\n",
" .addNode(\"grandchild1\", grandchild1)\n",
" .addEdge(START, \"grandchild1\")\n",
"\n",
"const grandchildGraph = grandchild.compile();"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{ myGrandchildKey: 'hi Bob, how are you' }\n"
]
}
],
"source": [
"await grandchildGraph.invoke({ myGrandchildKey: \"hi Bob\" })"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define child"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"import { StateGraph, START, Annotation } from \"@langchain/langgraph\";\n",
"\n",
"const ChildAnnotation = Annotation.Root({\n",
" myChildKey: Annotation<string>,\n",
"});\n",
"\n",
"const callGrandchildGraph = async (state: typeof ChildAnnotation.State) => {\n",
" // NOTE: parent or grandchild keys won't be accessible here\n",
" // we're transforming the state from the child state channels (`myChildKey`)\n",
" // to the grandchild state channels (`myGrandchildKey`)\n",
" const grandchildGraphInput = { myGrandchildKey: state.myChildKey };\n",
" // we're transforming the state from the grandchild state channels (`myGrandchildKey`)\n",
" // back to the child state channels (`myChildKey`)\n",
" const grandchildGraphOutput = await grandchildGraph.invoke(grandchildGraphInput);\n",
" return {\n",
" myChildKey: grandchildGraphOutput.myGrandchildKey + \" today?\"\n",
" };\n",
"};\n",
"\n",
"const child = new StateGraph(ChildAnnotation)\n",
" // NOTE: we're passing a function here instead of just compiled graph (`childGraph`)\n",
" .addNode(\"child1\", callGrandchildGraph)\n",
" .addEdge(START, \"child1\");\n",
"\n",
"const childGraph = child.compile();"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{ myChildKey: 'hi Bob, how are you today?' }\n"
]
}
],
"source": [
"await childGraph.invoke({ myChildKey: \"hi Bob\" })"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<div class=\"admonition info\">\n",
" <p class=\"admonition-title\">Note</p>\n",
" <p>\n",
" We're wrapping the <code>grandchildGraph</code> invocation in a separate function (<code>callGrandchildGraph</code>) that transforms the input state before calling the grandchild graph and then transforms the output of grandchild graph back to child graph state. If you just pass <code>grandchildGraph</code> directly to <code>.addNode</code> without the transformations, LangGraph will raise an error as there are no shared state channels (keys) between child and grandchild states.\n",
" </p>\n",
"</div> "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that child and grandchild subgraphs have their own, **independent** state that is not shared with the parent graph."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Define parent"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"import { StateGraph, START, END, Annotation } from \"@langchain/langgraph\";\n",
"\n",
"const ParentAnnotation = Annotation.Root({\n",
" myKey: Annotation<string>,\n",
"});\n",
"\n",
"const parent1 = (state: typeof ParentAnnotation.State) => {\n",
" // NOTE: child or grandchild keys won't be accessible here\n",
" return { myKey: \"hi \" + state.myKey };\n",
"};\n",
"\n",
"const parent2 = (state: typeof ParentAnnotation.State) => {\n",
" return { myKey: state.myKey + \" bye!\" };\n",
"};\n",
"\n",
"const callChildGraph = async (state: typeof ParentAnnotation.State) => {\n",
" // we're transforming the state from the parent state channels (`myKey`)\n",
" // to the child state channels (`myChildKey`)\n",
" const childGraphInput = { myChildKey: state.myKey };\n",
" // we're transforming the state from the child state channels (`myChildKey`)\n",
" // back to the parent state channels (`myKey`)\n",
" const childGraphOutput = await childGraph.invoke(childGraphInput);\n",
" return { myKey: childGraphOutput.myChildKey };\n",
"};\n",
"\n",
"const parent = new StateGraph(ParentAnnotation)\n",
" .addNode(\"parent1\", parent1)\n",
" // NOTE: we're passing a function here instead of just a compiled graph (`childGraph`)\n",
" .addNode(\"child\", callChildGraph)\n",
" .addNode(\"parent2\", parent2)\n",
" .addEdge(START, \"parent1\")\n",
" .addEdge(\"parent1\", \"child\")\n",
" .addEdge(\"child\", \"parent2\")\n",
" .addEdge(\"parent2\", END);\n",
"\n",
"const parentGraph = parent.compile();"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<div class=\"admonition info\">\n",
" <p class=\"admonition-title\">Note</p>\n",
" <p>\n",
" We're wrapping the <code>childGraph</code> invocation in a separate function (<code>callChildGraph</code>) that transforms the input state before calling the child graph and then transforms the output of the child graph back to parent graph state. If you just pass <code>childGraph</code> directly to <code>.addNode</code> without the transformations, LangGraph will raise an error as there are no shared state channels (keys) between parent and child states.\n",
" </p>\n",
"</div> \n",
"\n",
"Let's run the parent graph and make sure it correctly calls both the child and grandchild subgraphs:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{ myKey: 'hi Bob, how are you today? bye!' }\n"
]
}
],
"source": [
"await parentGraph.invoke({ myKey: \"Bob\" })"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Perfect! The parent graph correctly calls both the child and grandchild subgraphs (which we know since the \", how are you\" and \"today?\" are added to our original \"myKey\" state value)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "TypeScript",
"language": "typescript",
"name": "tslab"
},
"language_info": {
"codemirror_mode": {
"mode": "typescript",
"name": "javascript",
"typescript": true
},
"file_extension": ".ts",
"mimetype": "text/typescript",
"name": "typescript",
"version": "3.7.2"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Loading

0 comments on commit 5d8716b

Please sign in to comment.