Compare commits

...
Author SHA1 Message Date
Lance Martin 8aa7c46d81 Test hitl prebuilt 2025-05-21 14:01:48 -07:00
Eugene YurtsevandGitHub 364508ff20 docs: Add script to generate llms-txt links from yaml (#4771)
A first pass at the script to fix the immediate issue. Will follow up with a few additional improvements.
2025-05-21 11:54:23 -04:00
Eugene YurtsevandGitHub fa1af5c364 docs: replace llms.txt with updated links (#4772)
This is a stop gap solution to fix broken links.

This PR will be followed up with a better llms.txt file
2025-05-21 11:53:26 -04:00
le-codeur-rapideandGitHub 8ec29b4df7 docs: fix example pregel reducer (#4740) 2025-05-21 10:38:29 -04:00
Sydney RunkleandGitHub 06607e08ea prebuilts hitl: fix branching logic + add structural snapshot tests (#4767) 2025-05-21 09:29:59 -04:00
Lauren Hirata SinghandGitHub 0a8f54ca4a docs: add redirect (#4762) 2025-05-20 15:06:34 -04:00
Lauren Hirata Singh 9d41d439cc docs: add redirect 2025-05-20 14:52:02 -04:00
Leeroy BrunandGitHub ae5c01fbab docs: typo in npx command to install LangGraph CLI (#4761) 2025-05-20 17:08:02 +00:00
Sydney RunkleandGitHub bd52b1faa1 docs: deferred nodes (#4759) 2025-05-20 13:07:11 -04:00
Stefano LottiniandGitHub 4a04ca7268 [docs] Revise pip-install packages for PostgresSaver in "how-to/Persistence" notebook (#4752)
revise pip-install packages for PostgresSaver usage in how-to page
2025-05-20 15:46:39 +00:00
Yazan JianandGitHub 23b868da00 docs: fix async usage example from await .invoke() to `await .ainvo… (#4758)
docs: fix async usage example from `await .invoke()` to `await .ainvoke()`
2025-05-20 15:45:47 +00:00
David DuongandGitHub 51bfd16460 feat(sdk-js): add stream_resumable flag, that marks the stream as resumable (#4757) 2025-05-20 14:16:39 +02:00
Tat Dat Duong 325d9e3134 Bump to 0.0.77 2025-05-20 14:14:48 +02:00
Tat Dat Duong b052ebf984 feat(sdk-js): add stream_resumable flag, that marks the stream as resumable 2025-05-20 14:13:47 +02:00
Sydney RunkleandGitHub 1e938a692f docs: node caching (#4749) 2025-05-19 15:49:31 -04:00
Nuno CamposandGitHub 46a9d3159d Remove local_write utility (#4751)
- The validation isn't worth the cost of having to pass list of nodes to task config
2025-05-19 15:49:17 -04:00
Nuno CamposandGitHub d825e39df9 Print output for cached @task functions (#4750) 2025-05-19 11:42:16 -07:00
Nuno Campos 53a1e7c9de Print output for cached @task functions 2025-05-19 11:35:51 -07:00
Sydney RunkleandGitHub 7bd8616b1e feature: Implement post_model_hook and HumanInterruptNode (#4583) 2025-05-19 14:07:42 -04:00
William FHandGitHub 95f92069a7 sqlite: Add test for search with list filters (#4747) 2025-05-18 23:50:27 -07:00
William FHandGitHub 6b28319796 sqlite: update list_namespaces with max_depth (#4746)
sqlite: update on conflict
2025-05-18 23:27:06 -07:00
Didier DurandandGitHub 2ddf61201c docs: Fixing some typos (#4741) 2025-05-18 22:36:37 -07:00
Didier DurandandGitHub 9453ee08dc docs: Fix a few spelling mistakes (#4742)
Fixing some typos
2025-05-18 22:35:54 -07:00
54 changed files with 2600 additions and 764 deletions
+1 -1
View File
@@ -153,7 +153,7 @@ Each category serves a distinct purpose and requires a specific approach to writ
Here are some other guidelines you should think about when writing and organizing documentation.
We generally do not merge new tutorials from outside contributors without an actue need.
We generally do not merge new tutorials from outside contributors without an actual need.
We welcome updates as well as new integration docs, how-tos, and references.
### Avoid duplication
+112 -2
View File
@@ -2,9 +2,12 @@
import glob
import os
from typing import TypedDict, List
import yaml
from mkdocs.structure.files import File
from mkdocs.structure.pages import Page
from yaml import SafeLoader
from _scripts.notebook_hooks import _on_page_markdown_with_config
@@ -13,7 +16,7 @@ HERE = os.path.dirname(os.path.abspath(__file__))
SOURCE_DIR = os.path.abspath(os.path.join(os.path.dirname(HERE), "docs"))
def _make_llms_text(output_file: str) -> str:
def generate_full_llms_text(output_file: str) -> str:
"""Generate a consolidated text file from markdown/notebook files for LLM training.
Args:
@@ -69,6 +72,100 @@ def _make_llms_text(output_file: str) -> str:
f.write("\n\n".join(all_content))
def no_op_constructor(*args):
"""No-op"""
SafeLoader.add_multi_constructor(
"tag:yaml.org,2002:python/name",
no_op_constructor,
)
class NavItem(TypedDict):
title: str
url: str
hierarchy: tuple[str, ...]
def _flatten_nav(
nav: list[dict[str, str | list] | str], path: tuple[str, ...] = ()
) -> list[NavItem]:
flat: List[NavItem] = []
for item in nav:
if isinstance(item, dict):
for title, node in item.items():
new_path = path + (title,)
if isinstance(node, str):
# Leaf page
flat.append({"title": title, "url": node, "hierarchy": new_path})
elif isinstance(node, list):
# Dive in, carrying along the updated path
flat.extend(_flatten_nav(node, new_path))
else:
raise TypeError(
f"Unexpected node type {type(node)} under {title!r}"
)
elif isinstance(item, str):
# Bare string entry → use itself as title, and as URL
new_path = path + (item,)
flat.append({"title": item, "url": item, "hierarchy": new_path})
else:
raise TypeError(f"Unexpected item type {type(item)} in nav")
return flat
def generate_nav_links_text(output_file: str, *, replace_links: bool = False) -> None:
"""Generate a text file containing navigation structure and links from mkdocs.yaml."""
# Get path to mkdocs.yaml relative to this script
script_dir = os.path.dirname(os.path.abspath(__file__))
mkdocs_path = os.path.join(os.path.dirname(script_dir), "mkdocs.yml")
# Load and parse yaml
with open(mkdocs_path, "r") as f:
config = yaml.safe_load(f)
# Extract nav section
nav = config.get("nav", [])
flattened = _flatten_nav(nav)
with open(output_file, "w") as f:
current_section = None
for item in flattened:
# Get the top-level section (first item in hierarchy)
section = item["hierarchy"][0]
if section not in {
"Guides", "Examples", "Resources"
}:
continue
# If we're starting a new section, add a heading
if section != current_section:
f.write(f"\n# {section}\n\n")
current_section = section
# Add the item as a bullet point with title and link
# Include full hierarchy path in title, separated by " > "
hierarchy_path = " > ".join(item["hierarchy"][1:])
title = (
f"{item['title']} ({hierarchy_path})"
if hierarchy_path
else item["title"]
)
# Process URL based on replace_links flag
url = item["url"]
if replace_links:
# Remove .md extension and ensure single trailing slash
url = url.removesuffix(".md")
url = url.removesuffix(".ipynb")
url = url.rstrip("/") + "/"
url = f"https://langchain-ai.github.io/langgraph/{url}"
f.write(f"- [{title}]({url})\n")
if __name__ == "__main__":
import argparse
@@ -78,6 +175,19 @@ if __name__ == "__main__":
)
)
parser.add_argument("output_file", help="Path to output the consolidated text file")
parser.add_argument(
"--link-only",
action="store_true",
help="Only include link references in the output",
)
parser.add_argument(
"--replace-links",
action="store_true",
help="Replace markdown links with full URLs in the output",
)
args = parser.parse_args()
_make_llms_text(args.output_file)
if args.link_only:
generate_nav_links_text(args.output_file, replace_links=args.replace_links)
else:
generate_full_llms_text(args.output_file)
+1
View File
@@ -53,6 +53,7 @@ REDIRECT_MAP = {
"how-tos/persistence_redis.ipynb": "how-tos/persistence.ipynb#use-in-production",
"how-tos/subgraph-persistence.ipynb": "how-tos/persistence.ipynb#use-with-subgraphs",
"how-tos/cross-thread-persistence.ipynb": "how-tos/persistence.ipynb#add-long-term-memory",
"cloud/how-tos/copy_threads": "cloud/how-tos/use_threads",
# tool calling how-tos
"how-tos/tool-calling-errors.ipynb": "how-tos/tool-calling.ipynb#handle-errors",
"how-tos/pass-config-to-tools.ipynb": "how-tos/tool-calling.ipynb#access-config",
+2 -2
View File
@@ -10,7 +10,7 @@ hide:
# Running agents
Agents support both synchronous and asynchronous execution using either `.invoke()` / `await .invoke()` for full responses, or `.stream()` / `.astream()` for **incremental** [streaming](streaming.md) output. This section explains how to provide input, interpret output, enable streaming, and control execution limits.
Agents support both synchronous and asynchronous execution using either `.invoke()` / `await .ainvoke()` for full responses, or `.stream()` / `.astream()` for **incremental** [streaming](streaming.md) output. This section explains how to provide input, interpret output, enable streaming, and control execution limits.
## Basic usage
@@ -18,7 +18,7 @@ Agents support both synchronous and asynchronous execution using either `.invoke
Agents can be executed in two primary modes:
- **Synchronous** using `.invoke()` or `.stream()`
- **Asynchronous** using `await .invoke()` or `async for` with `.astream()`
- **Asynchronous** using `await .ainvoke()` or `async for` with `.astream()`
=== "Sync invocation"
```python
+1 -1
View File
@@ -2,6 +2,6 @@
Webhooks enable event-driven communication from your LangGraph Platform application to external services. For example, you may want to issue an update to a separate service once an API call to LangGraph Platform has finished running.
Many LangGraph Platform endpoints accept a `webhook` parameter. If this parameter is specified by a an endpoint that can accept POST requests, LangGraph Platform will send a request at the completion of a run.
Many LangGraph Platform endpoints accept a `webhook` parameter. If this parameter is specified by an endpoint that can accept POST requests, LangGraph Platform will send a request at the completion of a run.
See the corresponding [how-to guide](../../cloud/how-tos/webhooks.md) for more detail.
@@ -18,7 +18,7 @@ Before deploying, review the [conceptual guide for the Self-Hosted Data Plane](.
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda --namespace keda --create-namespace
1. A valid `Ingress` controller is install on your cluster.
1. A valid `Ingress` controller is installed on your cluster.
1. You have slack space in your cluster for multiple deployments. `Cluster-Autoscaler` is recommended to automatically provision new nodes.
### Setup
@@ -231,7 +231,7 @@ Inside your deployment, select the "Assistants" tab. For the assistant you would
To edit the assistant, use the `update` method. This will create a new version of the assistant with the provided edits. See the [Python](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.AssistantsClient.update) and [JS](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#update) SDK reference docs for more information.
!!! note "Note"
You must pass in the ENTIRE config (and metadata if you are using it). The update endpoint creates new versions completely from scratch and does not rely on previously versions.
You must pass in the ENTIRE config (and metadata if you are using it). The update endpoint creates new versions completely from scratch and does not rely on previous versions.
For example, to update your assistant's system prompt:
=== "Python"
@@ -321,7 +321,7 @@ If you now run your graph and pass in this assistant id, it will use the first v
### LangGraph Platform UI
If using LangGraph Studio, to set the active version of your asssistant, click the "Manage Assistants" button and locate the assistant you would like to use. Select the assistant and the version, and then click the "Active" toggle. This will update the assistant to make the selected version active.
If using LangGraph Studio, to set the active version of your assistant, click the "Manage Assistants" button and locate the assistant you would like to use. Select the assistant and the version, and then click the "Active" toggle. This will update the assistant to make the selected version active.
!!! warning "Deleting Assistants"
Deleting as assistant will delete ALL of it's versions. There is currently no way to delete a single version, but by pointing your assistant to the correct version you can skip any versions that you don't wish to use.
Deleting as assistant will delete ALL of its versions. There is currently no way to delete a single version, but by pointing your assistant to the correct version you can skip any versions that you don't wish to use.
+1 -1
View File
@@ -4,7 +4,7 @@ Sometimes you don't want to run your graph based on user interaction, but rather
## Setup
First, let's setup our SDK client, assistant, and thread:
First, let's set up our SDK client, assistant, and thread:
=== "Python"
+1 -1
View File
@@ -1,6 +1,6 @@
# Add node to dataset
This guide shows how to add examples to [LangSmith datasets](https://docs.smith.langchain.com/evaluation/how_to_guides#dataset-management) from nodes in the thread log. This is useful to evaluate indivudal steps of the agent.
This guide shows how to add examples to [LangSmith datasets](https://docs.smith.langchain.com/evaluation/how_to_guides#dataset-management) from nodes in the thread log. This is useful to evaluate individual steps of the agent.
1. Select a thread.
2. Click on the `Add to Dataset` button.
@@ -335,7 +335,7 @@ const { thread, submit } = useStream({
});
```
Then you can pushing updates to the UI component by calling `ui.push()` / `push_ui_message()` with the same ID as the UI message you wish to update.
Then you can push updates to the UI component by calling `ui.push()` / `push_ui_message()` with the same ID as the UI message you wish to update.
=== "Python"
+1 -1
View File
@@ -488,4 +488,4 @@ You can also view threads in a deployment via the LangGraph Platform UI.
Inside your deployment, select the "Threads" tab. This will load a table of all of the threads in your deployment.
Select a thread to inspect its current state. To view it's full history and for further debugging, open the thread in [LangGraph Studio](../../concepts//langgraph_studio.md).
Select a thread to inspect its current state. To view its full history and for further debugging, open the thread in [LangGraph Studio](../../concepts//langgraph_studio.md).
+48
View File
@@ -247,6 +247,54 @@ from langgraph.graph import END
graph.add_edge("node_a", END)
```
### Node Caching
LangGraph supports caching of tasks/nodes based on the input to the node. To use caching:
* Specify a cache when compiling a graph (or specifying an entrypoint)
* Specify a cache policy for nodes. Each cache policy supports:
* `key_func` used to generate a cache key based on the input to a node, which defaults to a `hash` of the input with pickle.
* `ttl`, the time to live for the cache in seconds. If not specified, the cache will never expire.
For example:
```py
import time
from typing_extensions import TypedDict
from langgraph.graph import StateGraph
from langgraph.cache.memory import InMemoryCache
from langgraph.types import CachePolicy
class State(TypedDict):
x: int
result: int
builder = StateGraph(State)
def expensive_node(state: State) -> dict[str, int]:
# expensive computation
time.sleep(2)
return {"result": state["x"] * 2}
builder.add_node("expensive_node", expensive_node, cache_policy=CachePolicy(ttl=3))
builder.set_entry_point("expensive_node")
builder.set_finish_point("expensive_node")
graph = builder.compile(cache=InMemoryCache())
print(graph.invoke({"x": 5}, stream_mode='updates')) # (1)!
[{'expensive_node': {'result': 10}}]
print(graph.invoke({"x": 5}, stream_mode='updates')) # (2)!
[{'expensive_node': {'result': 10}, '__metadata__': {'cached': True}}]
```
1. First run takes the full second to run (due to mocked expensive computation).
2. Second run utilizes cache and returns quickly.
## Edges
Edges define how the logic is routed and how the graph decides to stop. This is a big part of how your agents work and how different nodes communicate with each other. There are a few key types of edges:
+1 -1
View File
@@ -180,7 +180,7 @@ Below are a few different examples to give you a sense of the Pregel API.
def reducer(current, update):
if current:
return current + " | " + "update"
return current + " | " + update
else:
return update
+44 -24
View File
@@ -1288,12 +1288,43 @@
},
{
"cell_type": "markdown",
"id": "4eeb895c-adca-40ab-b289-93ee56e18661",
"id": "068f806a",
"metadata": {},
"source": [
"</details>"
]
},
{
"cell_type": "markdown",
"id": "6d99d63c",
"metadata": {},
"source": [
"## Add node caching\n",
"\n",
"Node caching is useful in cases where you want to avoid repeating operations, like when doing something expensive (either in terms of time or cost). LangGraph lets you add individualized caching policies to nodes in a graph.\n",
"\n",
"To configure a cache policy, pass the `cache_policy` parameter to the [add_node](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.state.StateGraph.add_node) function. In the following example, a [`CachePolicy`](https://langchain-ai.github.io/langgraph/reference/types/?h=cachepolicy#langgraph.types.CachePolicy) object is instantiated with a time to live of 120 seconds and the default `key_func` generator. Then it is associated with a node:\n",
"\n",
"```python\n",
"from langgraph.types import CachePolicy\n",
"\n",
"builder.add_node(\n",
" \"node_name\",\n",
" node_function,\n",
" cache_policy=CachePolicy(ttl=120),\n",
")\n",
"```\n",
"\n",
"Then, to enable node-level caching for a graph, set the `cache` argument when compiling the graph. The example below uses `InMemoryCache` to set up a graph with in-memory cache, but `SqliteCache` is also available.\n",
"\n",
"```python\n",
"from langgraph.cache.memory import InMemoryCache\n",
"\n",
"\n",
"graph = builder.compile(cache=InMemoryCache())\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "e1a0213e-282f-4fad-b048-5f7465edfccb",
@@ -1754,18 +1785,19 @@
},
{
"cell_type": "markdown",
"id": "205ff836-0f97-4ee8-9830-6bd8368e48c9",
"id": "48731230",
"metadata": {},
"source": [
"<details class=\"example\"><summary>Extended example: unequal length branches</summary>\n",
"### Defer node execution\n",
"\n",
"The above example showed how to fan-out and fan-in when each path was only one step. But what if one path had more than one step? Let's add a node <code>b_2</code> in the \"b\" branch:\n",
"<br>"
"Deferring node execution is useful when you want to delay the execution of a node until all other pending tasks are completed. This is particularly relevant when branches have different lengths, which is common in workflows like map-reduce flows.\n",
"\n",
"The above example showed how to fan-out and fan-in when each path was only one step. But what if one branch had more than one step? Let's add a node `\"b_2\"` in the `\"b\"` branch:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": 26,
"id": "3890af2f-fb14-4569-b48d-a91db2d3f026",
"metadata": {},
"outputs": [],
@@ -1813,13 +1845,14 @@
"builder.add_node(b)\n",
"builder.add_node(b_2)\n",
"builder.add_node(c)\n",
"builder.add_node(d)\n",
"# highlight-next-line\n",
"builder.add_node(d, defer=True)\n",
"builder.add_edge(START, \"a\")\n",
"builder.add_edge(\"a\", \"b\")\n",
"builder.add_edge(\"a\", \"c\")\n",
"builder.add_edge(\"b\", \"b_2\")\n",
"# highlight-next-line\n",
"builder.add_edge([\"b_2\", \"c\"], \"d\")\n",
"builder.add_edge(\"b_2\", \"d\")\n",
"builder.add_edge(\"c\", \"d\")\n",
"builder.add_edge(\"d\", END)\n",
"graph = builder.compile()"
]
@@ -1881,23 +1914,10 @@
},
{
"cell_type": "markdown",
"id": "903f0da5-8c2c-4a7e-96fb-0b16b4756eff",
"id": "70e67ced",
"metadata": {},
"source": [
"<div class=\"admonition note\">\n",
" <p class=\"admonition-title\">Note</p>\n",
"<p>In the above example, nodes <code>\"b\"</code> and <code>\"c\"</code> are executed concurrently in the same [superstep](../../concepts/low_level/#graphs). What happens in the next step?</p>\n",
" <p>We use <code>add_edge([\"b_2\", \"c\"], \"d\")</code> here to force node <code>\"d\"</code> to only run when both nodes <code>\"b_2\"</code> and <code>\"c\"</code> have finished execution. If we added two separate edges,\n",
" node <code>\"d\"</code> would run twice: after node <code>b2</code> finishes and once again after node <code>c</code> (in whichever order those nodes finish).</p>\n",
"</div>"
]
},
{
"cell_type": "markdown",
"id": "c1653341-3215-4ca0-b0e7-9be22f0adaa1",
"metadata": {},
"source": [
"</details>"
"In the above example, nodes `\"b\"` and `\"c\"` are executed concurrently in the same superstep. We set `defer=True` on node `d` so it will not execute until all pending tasks are finished. In this case, this means that `\"d\"` waits to execute until the entire `\"b\"` branch is finished."
]
},
{
+2 -2
View File
@@ -184,7 +184,7 @@
"??? example \"Example: using [Postgres](https://pypi.org/project/langgraph-checkpoint-postgres/) checkpointer\"\n",
"\n",
" ```\n",
" pip install -U psycopg psycopg-pool langgraph langgraph-checkpoint-postgres\n",
" pip install -U \"psycopg[binary,pool]\" langgraph langgraph-checkpoint-postgres\n",
" ```\n",
"\n",
" !!! Setup\n",
@@ -1129,7 +1129,7 @@
"??? example \"Example: using [Postgres](https://pypi.org/project/langgraph-checkpoint-postgres/) store\"\n",
"\n",
" ```\n",
" pip install -U psycopg psycopg-pool langgraph langgraph-checkpoint-postgres\n",
" pip install -U \"psycopg[binary,pool]\" langgraph langgraph-checkpoint-postgres\n",
" ```\n",
"\n",
" !!! Setup\n",
+32
View File
@@ -349,6 +349,38 @@ main.invoke({'any_input': 'foobar'}, config=config)
'OK'
```
## Caching Tasks
```python
import time
from langgraph.cache.memory import InMemoryCache
from langgraph.func import entrypoint, task
from langgraph.types import CachePolicy
@task(cache_policy=CachePolicy(ttl=120)) # (1)!
def slow_add(x: int) -> int:
time.sleep(1)
return x * 2
@entrypoint(cache=InMemoryCache())
def main(inputs: dict) -> dict[str, int]:
result1 = slow_add(inputs["x"]).result()
result2 = slow_add(inputs["x"]).result()
return {"result1": result1, "result2": result2}
for chunk in main.stream({"x": 5}, stream_mode="updates"):
print(chunk)
#> {'slow_add': 10}
#> {'slow_add': 10, '__metadata__': {'cached': True}}
#> {'main': {'result1': 10, 'result2': 10}}
```
1. `ttl` is specified in seconds. The cache will be invalidated after this time.
## Resuming after an error
```python
+149 -201
View File
@@ -1,202 +1,150 @@
# LangGraph
## Tutorials
[Learn the basics](https://langchain-ai.github.io/langgraph/tutorials/introduction/): LLM should read this page when needing to build a LangGraph chatbot or when learning about chat agents with memory, human-in-the-loop functionality, and state management. This page provides a comprehensive LangGraph quickstart tutorial covering building a support chatbot with web search capability, conversation memory, human review routing, custom state management, and time travel functionality to explore alternative conversation paths.
[Local Deploy](https://langchain-ai.github.io/langgraph/tutorials/langgraph-platform/local-server/): LLM should read this page when setting up a LangGraph app locally using `langgraph dev` and troubleshooting LangGraph server deployment. This page contains a quickstart guide for launching a LangGraph server locally, including installation steps, app creation from templates, environment setup, API testing with Python/JS SDKs, and links to deployment options and further documentation.
[Workflows and Agents](https://langchain-ai.github.io/langgraph/tutorials/workflows/): LLM should read this page when implementing agent systems, designing workflow architectures, or troubleshooting LLM orchestration strategies. The page covers patterns for LLM system design, comparing workflows (predefined paths) vs agents (dynamic control), with implementations of prompt chaining, parallelization, routing, orchestrator-worker, evaluator-optimizer, and agent patterns using both graph and functional APIs in LangGraph.
## Concepts
[Concepts](https://langchain-ai.github.io/langgraph/concepts/): LLM should read this page when needing to understand LangGraph's key concepts or when planning to deploy LangGraph applications. Comprehensive guide covering LangGraph fundamentals (graph primitives, agents, multi-agent systems, breakpoints, persistence), features (time travel, memory, streaming), and LangGraph Platform deployment options (self-hosted, cloud, enterprise).
[Agent architectures](https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/): LLM should read this page when designing agent architectures, implementing control flows for LLM applications, or customizing agent behavior patterns. This page covers different LLM agent architectures including routers, tool calling agents (ReAct), structured outputs, memory systems, planning capabilities, and advanced customization options like human-in-the-loop, parallelization, subgraphs, and reflection mechanisms.
[Application Structure](https://langchain-ai.github.io/langgraph/concepts/application_structure/): LLM should read this page when needing to understand LangGraph application structure, preparing to deploy a LangGraph application, or troubleshooting configuration issues. This page details the structure of LangGraph applications, including required components (graphs, langgraph.json config file, dependency files, optional .env), file organization patterns for Python/JavaScript projects, configuration file format with all supported fields, and how to specify dependencies, graphs, and environment variables.
[Assistants](https://langchain-ai.github.io/langgraph/concepts/assistants/): LLM should read this page when looking for information about LangGraph assistants, understanding assistant configuration in LangGraph Platform, or learning about versioning agent configurations. This page explains LangGraph assistants, which allow developers to modify agent configurations (prompts, models, etc.) without changing graph logic, supports versioning for tracking changes, and is available only in LangGraph Platform (not open source).
[Authentication & Access Control](https://langchain-ai.github.io/langgraph/concepts/auth/): LLM should read this page when implementing authentication in LangGraph Platform, designing access control for LangGraph applications, or troubleshooting security issues in LangGraph deployments. This page explains LangGraph's authentication and authorization system, covering the difference between authentication and authorization, system architecture, implementing custom auth handlers, common access patterns, and supported resources/actions for access control.
[Deployment Options](https://langchain-ai.github.io/langgraph/concepts/deployment_options/): LLM should read this page when needing information about LangGraph deployment options, comparing different deployment methods, or understanding LangGraph Platform plans. This page outlines four deployment options for LangGraph Platform: Self-Hosted Lite (available for all plans), Self-Hosted Enterprise (Enterprise plan only), Cloud SaaS (Plus and Enterprise plans), and Bring Your Own Cloud (Enterprise plan only, AWS-only).
[Double Texting](https://langchain-ai.github.io/langgraph/concepts/double_texting/): LLM should read this page when handling concurrent user interactions in LangGraph Platform, implementing double-texting safeguards, or designing stateful conversation systems. This page explains four approaches to handling "double texting" in LangGraph (when users send a second message before the first completes): Reject, Enqueue, Interrupt, and Rollback, noting these features are currently only available in LangGraph Platform.
[Durable Execution](https://langchain-ai.github.io/langgraph/concepts/durable_execution/): LLM should read this page when needing to understand durable execution in LangGraph, implementing workflow persistence, or troubleshooting workflow resumption. This page explains durable execution in LangGraph: how workflows save progress to resume later, requirements (checkpointers and thread IDs), determinism guidelines for consistent replay, using tasks to encapsulate non-deterministic operations, and approaches for pausing/resuming workflows.
[FAQ](https://langchain-ai.github.io/langgraph/concepts/faq/): LLM should read this page when needing to understand differences between LangGraph and LangChain, exploring deployment options for LangGraph Platform, or determining compatibility with various LLMs. FAQ covering LangGraph basics, comparisons with other frameworks, deployment options (free self-hosted, Cloud SaaS, Enterprise), compatibility with different LLMs including OSS models, and feature differences between open-source LangGraph and proprietary LangGraph Platform.
[Functional API](https://langchain-ai.github.io/langgraph/concepts/functional_api/): LLM should read this page when implementing workflows with persistent state, adding human-in-the-loop features, or converting existing code to use LangGraph. The page documents LangGraph's Functional API, which allows adding persistence, memory, and human-in-the-loop capabilities with minimal code changes using @entrypoint and @task decorators, handling serialization requirements, state management, and common patterns for parallel execution and error handling.
[Why LangGraph?](https://langchain-ai.github.io/langgraph/concepts/high_level/): LLM should read this page when understanding LangGraph's core capabilities, exploring LLM application infrastructure, or evaluating agent/workflow persistence options. LangGraph provides infrastructure for LLM applications with three key benefits: persistence for memory and human-in-the-loop capabilities, streaming of workflow events and LLM outputs, and tools for debugging and deployment via LangGraph Platform.
[Human-in-the-loop](https://langchain-ai.github.io/langgraph/concepts/human_in_the_loop/): LLM should read this page when implementing human-in-the-loop workflows in LangGraph, designing approval systems with LLMs, or creating interactive multi-turn conversation agents. This page explains human-in-the-loop patterns in LangGraph using the interrupt function, showing how to pause graph execution for human review/input and resume with Command. Includes design patterns for approval workflows, state editing, tool call reviews, and multi-turn conversations, with code examples and warnings about execution flow and common pitfalls.
[LangGraph CLI](https://langchain-ai.github.io/langgraph/concepts/langgraph_cli/): LLM should read this page when looking for information about LangGraph CLI installation or when needing to deploy a LangGraph API server locally. The page covers LangGraph CLI installation methods (Homebrew, pip), key commands (build, dev, up, dockerfile), and features like hot reloading, debugger support, and database management for running LangGraph servers.
[Cloud SaaS](https://langchain-ai.github.io/langgraph/concepts/langgraph_cloud/): LLM should read this page when learning about LangGraph's Cloud SaaS offering, understanding deployment options for LangGraph Servers, or planning autoscaling infrastructure for LangGraph applications. This page describes LangGraph Cloud SaaS, a managed deployment service for LangGraph Servers with details on deployment types (Development/Production), revisions, persistence, autoscaling capabilities (up to 10 containers), LangSmith integration, IP whitelisting, and automatic deletion policies after 28 days of non-use.
[LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/): LLM should read this page when seeking information about LangGraph Platform's components or evaluating production deployment options for agentic applications. The page details the LangGraph Platform, a commercial solution for deploying agentic applications, including its components (Server, Studio, CLI, SDK, Remote Graph) and key benefits like streaming support, background runs, long run handling, burstiness management, and human-in-the-loop capabilities.
[LangGraph Server](https://langchain-ai.github.io/langgraph/concepts/langgraph_server/): LLM should read this page when developing applications with LangGraph Server, deploying agent-based applications, or integrating persistent state management in agent workflows. LangGraph Server provides an API for creating and managing agent applications with key features like streaming endpoints, background runs, task queues, persistence, webhooks, cron jobs, and monitoring capabilities through a structured system of assistants, threads, runs, and stores.
[LangGraph Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/): LLM should read this page when looking for information about LangGraph Studio features, needing to troubleshoot LangGraph Studio issues, or learning how to connect a LangGraph application to the Studio. LangGraph Studio is a specialized agent IDE for visualizing, interacting with, and debugging LLM applications, offering features such as graph visualization, state editing, assistant management, and integration with LangSmith, with instructions for connecting via deployed applications or local development servers, plus troubleshooting FAQs.
[LangGraph Glossary](https://langchain-ai.github.io/langgraph/concepts/low_level/): LLM should read this page when needing to understand LangGraph terminology, implementing agent workflows as graphs, or developing modular multi-step AI systems. The page covers core LangGraph concepts including StateGraph, nodes, edges, state management, messaging, persistence, configuration, human-in-the-loop features, subgraphs, and visualization capabilities.
[Memory](https://langchain-ai.github.io/langgraph/concepts/memory/): LLM should read this page when implementing memory systems for AI agents, managing conversation context across sessions, or designing systems that require both short-term and long-term information retention. This page explains memory systems in LangGraph, covering short-term (thread-scoped) memory for managing conversation history and long-term memory across threads, with techniques for handling long conversations, summarizing past interactions, and organizing persistent memories in namespaces.
[Multi-agent Systems](https://langchain-ai.github.io/langgraph/concepts/multi_agent/): LLM should read this page when implementing multi-agent systems, troubleshooting complex agent architectures, or designing agent communication patterns. Multi-agent systems organize LLMs into modular architectures (network, supervisor, hierarchical, custom) with different communication patterns, using Command objects for handoffs between agents, and supporting various state management approaches.
[Persistence](https://langchain-ai.github.io/langgraph/concepts/persistence/): LLM should read this page when needing to understand LangGraph persistence mechanisms, implementing stateful workflows, or managing conversation history across interactions. This page covers LangGraph's persistence features including checkpointers, threads, state snapshots, replay functionality, forking state, cross-thread memory via InMemoryStore, and semantic search capabilities for stored memories.
[LangGraph Platform Plans](https://langchain-ai.github.io/langgraph/concepts/plans/): LLM should read this page when determining LangGraph Platform pricing tiers, comparing deployment options, or researching features available across different plans. This page outlines LangGraph Platform plans (Developer, Plus, Enterprise), detailing deployment options, usage limitations, feature availability, and pricing structure for agentic application deployment.
[LangGraph Platform Architecture](https://langchain-ai.github.io/langgraph/concepts/platform_architecture/): LLM should read this page when needing to understand LangGraph Platform's technical architecture or troubleshooting deployment issues. The page details how LangGraph Platform uses Postgres for persistent storage of user/run data and Redis for worker communication (run cancellation, output streaming) and ephemeral metadata storage (retry attempts).
[LangGraph's Runtime (Pregel)](https://langchain-ai.github.io/langgraph/concepts/pregel/): LLM should read this page when learning about LangGraph's runtime, implementing applications with Pregel directly, or understanding how LangGraph executes graph applications. Explains LangGraph's Pregel runtime which manages graph application execution through a three-phase process (Plan, Execution, Update), describes different channel types (LastValue, Topic, Context, BinaryOperatorAggregate), provides direct implementation examples, and contrasts the StateGraph API with the Functional API.
[LangGraph Platform: Scalability & Resilience](https://langchain-ai.github.io/langgraph/concepts/scalability_and_resilience/): LLM should read this page when needing to understand LangGraph Platform's scaling capabilities, designing high-availability LangGraph deployments, or troubleshooting resilience issues. This page details LangGraph Platform's horizontal scaling features including stateless server instances, queue worker scaling, resilience mechanisms for handling crashes, and database failover strategies in Postgres and Redis.
[LangGraph SDK](https://langchain-ai.github.io/langgraph/concepts/sdk/): LLM should read this page when looking for installation instructions for LangGraph SDK, needing to choose between sync and async Python clients, or requiring SDK API references. The page covers LangGraph SDK installation for Python and JS, provides API reference links, explains the difference between synchronous and asynchronous Python clients, and includes code examples for both client types.
[Self-Hosted](https://langchain-ai.github.io/langgraph/concepts/self_hosted/): LLM should read this page when looking for LangGraph deployment options, understanding self-hosted versions, or seeking requirements for self-hosting LangGraph. This page details two self-hosted deployment options for LangGraph Platform: Self-Hosted Lite (limited to 1M nodes/year) and Self-Hosted Enterprise (full version requiring license). Includes requirements, deployment process using Redis/Postgres, Docker, and optional Kubernetes deployment via Helm chart.
[Streaming](https://langchain-ai.github.io/langgraph/concepts/streaming/): LLM should read this page when implementing streaming features in LangGraph applications, understanding different streaming modes, or building responsive LLM applications. This page explains streaming in LangGraph, covering the main types (workflow progress, LLM tokens, custom updates) and streaming modes (values, updates, custom, messages, debug, events), with details on how to use multiple modes simultaneously and differences between LangGraph library and Platform implementations.
[Template Applications](https://langchain-ai.github.io/langgraph/concepts/template_applications/): LLM should read this page when looking for LangGraph template applications, setting up a new LangGraph project, or finding reference implementations for agentic workflows. This page presents LangGraph template applications with installation requirements, available templates (including ReAct Agent, Memory Agent, Retrieval Agent, etc.), instructions for creating new apps using the CLI, deployment options, and links to further learning resources.
[Time Travel ⏱️](https://langchain-ai.github.io/langgraph/concepts/time-travel/): LLM should read this page when debugging LLM-based agent behavior, analyzing decision-making paths, or exploring alternative execution branches in LangGraph. This page explains LangGraph's Time Travel debugging features: Replaying (reproducing past actions up to specific checkpoints) and Forking (creating alternative execution paths from specific points), with code examples for retrieving checkpoints, configuring replay, and creating forked states.
## How Tos
[How-to Guides](https://langchain-ai.github.io/langgraph/how-tos/): LLM should read this page when looking for specific implementation techniques in LangGraph or when trying to deploy LangGraph applications to production environments. This page contains an extensive collection of how-to guides for LangGraph, covering graph fundamentals, persistence, memory management, human-in-the-loop features, tool calling, multi-agent systems, streaming, and deployment options through LangGraph Platform.
[How to implement handoffs between agents](https://langchain-ai.github.io/langgraph/how-tos/agent-handoffs/): LLM should read this page when implementing multi-agent systems that require agent coordination, when building systems with specialized agents that need to work together, or when needing to implement handoffs between agents. This page explains how to implement handoffs between agents in LangGraph using Command objects, both directly from agent nodes and through specialized handoff tools, with code examples for creating multi-agent systems.
[How to run a graph asynchronously](https://langchain-ai.github.io/langgraph/how-tos/async/): LLM should read this page when needing to implement asynchronous graph execution in LangGraph or when optimizing IO-bound LLM applications. This page explains how to convert synchronous graphs to asynchronous in LangGraph, including updating node definitions with async/await, using StateGraph with TypedDict, implementing conditional edges, and streaming results.
[How to integrate LangGraph with AutoGen, CrewAI, and other frameworks](https://langchain-ai.github.io/langgraph/how-tos/autogen-integration/): LLM should read this page when integrating LangGraph with other agent frameworks, building multi-agent systems, or adding persistence features to agents. The page demonstrates how to combine LangGraph with AutoGen by calling AutoGen agents inside LangGraph nodes, showing code examples for setting up the integration with memory and conversation persistence.
[How to integrate LangGraph (functional API) with AutoGen, CrewAI, and other frameworks](https://langchain-ai.github.io/langgraph/how-tos/autogen-integration-functional/): LLM should read this page when integrating LangGraph with other agent frameworks, building multi-agent systems with different frameworks, or adding LangGraph features to existing agent systems. This page demonstrates how to integrate LangGraph's functional API with AutoGen, including code examples for creating a workflow that calls AutoGen agents, leveraging LangGraph's memory and persistence features.
[How to create branches for parallel node execution](https://langchain-ai.github.io/langgraph/how-tos/branching/): LLM should read this page when needing to implement parallel node execution in LangGraph, optimizing graph performance, or handling conditional branching in workflows. This page explains how to create branches for parallel execution in LangGraph using fan-out/fan-in mechanisms, reducer functions for state accumulation, handling exceptions during parallel execution, and implementing conditional branching logic between nodes.
[How to combine control flow and state updates with Command](https://langchain-ai.github.io/langgraph/how-tos/command): LLM should read this page when learning how to combine control flow with state updates in LangGraph, understanding Command objects, or navigating between parent graphs and subgraphs. This page explains how to use Command objects to simultaneously update state and control flow between nodes, demonstrates using Command.PARENT to navigate from subgraphs to parent graphs, and includes examples of implementing reducers for state updates across graph hierarchies.
[How to add runtime configuration to your graph](https://langchain-ai.github.io/langgraph/how-tos/configuration/): LLM should read this page when implementing runtime configuration for LangGraph, adding model selection options to agents, or enabling dynamic system messages. This page demonstrates how to configure LangGraph at runtime, including selecting different LLMs dynamically and adding custom configuration options like system messages through the configurable dictionary.
[How to use the pre-built ReAct agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent/): LLM should read this page when implementing a ReAct agent, needing pre-built agent solutions, or learning how to integrate tools with LLM agents. This page covers how to use the pre-built ReAct agent in LangGraph, including setup instructions, creating a weather checking tool, implementing the agent architecture, and examples of running the agent with and without tool calls.
[How to add human-in-the-loop processes to the prebuilt ReAct agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent-hitl/): LLM should read this page when implementing human-in-the-loop processes for ReAct agents, debugging tool calls, or learning about interrupts in LangGraph. This guide demonstrates how to add human-in-the-loop functionality to prebuilt ReAct agents using interrupt_before=["tools"], working with MemorySaver checkpoints, and showing how to approve or edit tool calls before they execute.
[How to add thread-level memory to a ReAct Agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent-memory/): LLM should read this page when adding memory to ReAct agents, implementing thread-level persistence in LangGraph, or building stateful conversational agents. This guide demonstrates how to add memory to a ReAct agent using LangGraph's checkpointer interface, with code examples showing MemorySaver implementation, thread_id configuration, and persistent chat context across multiple interactions.
[How to return structured output from the prebuilt ReAct agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent-structured-output/): LLM should read this page when implementing structured output with ReAct agents, customizing agent response formats, or working with LangGraph agents. This page explains how to return structured output from prebuilt ReAct agents by providing a response_format parameter with a Pydantic schema, including examples with weather data and options for customizing the prompt.
[How to add a custom system prompt to the prebuilt ReAct agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent-system-prompt/): LLM should read this page when learning to customize ReAct agents, needing to add system prompts to agents, or working with LangGraph's prebuilt agents. This tutorial demonstrates how to add a custom system prompt to a prebuilt ReAct agent, with code examples showing model setup, tool creation, and using the prompt parameter in the create_react_agent function.
[How to add cross-thread persistence to your graph](https://langchain-ai.github.io/langgraph/how-tos/cross-thread-persistence): LLM should read this page when needing to implement persistence across multiple threads in LangGraph, when storing user data between conversations, or when implementing shared memory in graph-based LLM applications. This page demonstrates how to use LangGraph's Store API to persist data across threads, including creating an InMemoryStore with embedding search capabilities, passing stores to graph nodes, and accessing user-specific memories in different conversation threads.
[How to add cross-thread persistence (functional API)](https://langchain-ai.github.io/langgraph/how-tos/cross-thread-persistence-functional): LLM should read this page when needing to implement cross-thread persistence in LangGraph functional API, storing user data across different conversation threads, or creating shared memory between workflows. This page explains how to add cross-thread persistence to LangGraph using the Store interface, including defining a store, configuring the entrypoint decorator, and implementing a workflow that can store and retrieve user information across different conversation threads.
[How to do a Self-hosted deployment of LangGraph](https://langchain-ai.github.io/langgraph/how-tos/deploy-self-hosted/): LLM should read this page when implementing a self-hosted deployment of LangGraph, configuring required environment variables, or building Docker images for LangGraph applications. This page explains how to deploy LangGraph applications using Docker, covering environment requirements (Redis, Postgres), how to build Docker images with the LangGraph CLI, configuration using environment variables, and deployment options using Docker or Docker Compose.
[How to disable streaming for models that don't support it](https://langchain-ai.github.io/langgraph/how-tos/disable-streaming/): LLM should read this page when handling models that don't support streaming, implementing LangGraph with non-streaming models, or troubleshooting streaming errors with OpenAI's O1 models. This page explains how to use the disable_streaming=True parameter with ChatOpenAI to make non-streaming models work with LangGraph's astream_events API, with code examples showing the error case and proper implementation.
[How to edit graph state](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/edit-graph-state/): LLM should read this page when needing to implement human intervention in LangGraph workflows, wanting to edit graph state during execution, or implementing breakpoints in agent systems. This page explains how to edit graph state in LangGraph using breakpoints, including implementing human-in-the-loop interactions, setting up interruptions before specific nodes, and updating state during agent execution.
[How to Review Tool Calls](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/review-tool-calls/): LLM should read this page when implementing human review of tool calls, creating interactive agent workflows, or building approval systems for AI actions. This page explains how to implement human-in-the-loop review for tool calls in LangGraph, including approving tool calls, modifying tool calls manually, and providing natural language feedback to agents with complete code examples and explanations.
[How to view and update past graph state](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/time-travel/): LLM should read this page when needing to access or modify past states in LangGraph, when debugging agent execution, or when implementing user interventions in agent workflows. This page demonstrates how to view and update past graph states in LangGraph using get_state and update_state methods, with examples of replaying execution from checkpoints and branching workflows.
[How to wait for user input using interrupt](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/wait-user-input/): LLM should read this page when implementing wait-for-user functions in LangGraph, implementing human-in-the-loop interactions, or learning how to use the interrupt() function. This page explains how to pause graph execution to collect user input using LangGraph's interrupt() function, with examples of simple feedback collection and more complex agent interactions that ask clarifying questions.
[How to define input/output schema for your graph](https://langchain-ai.github.io/langgraph/how-tos/input_output_schema/): LLM should read this page when needing to define separate input/output schemas for LangGraph, implementing schema-based data filtering, or understanding schema definitions in StateGraph. This page explains how to define distinct input and output schemas for a StateGraph, showing how input schema validates the provided data structure while output schema filters internal data to return only relevant information, with code examples demonstrating implementation.
[How to handle large numbers of tools](https://langchain-ai.github.io/langgraph/how-tos/many-tools/): LLM should read this page when handling large tool collections, implementing dynamic tool selection, or creating retrieval-based tool management in LangGraph. This page demonstrates how to manage large numbers of tools by using vector search to dynamically select relevant tools based on user queries, implementing tool selection nodes in LangGraph, and handling tool selection errors with retry mechanisms.
[How to create map-reduce branches for parallel execution](https://langchain-ai.github.io/langgraph/how-tos/map-reduce/): LLM should read this page when learning to implement parallel execution in LangGraph, creating map-reduce operations, or handling dynamic task decomposition. This guide explains how to use LangGraph's Send API to create map-reduce workflows, breaking tasks into parallel sub-tasks and recombining results, with examples showing joke generation across multiple subjects.
[How to add summary of the conversation history](https://langchain-ai.github.io/langgraph/how-tos/memory/add-summary-conversation-history/): LLM should read this page when implementing conversation summarization, managing context windows, or building chatbots with memory management. This page demonstrates how to add summary functionality to conversation history using LangGraph, including checking conversation length, creating summaries, and removing old messages while maintaining context.
[How to delete messages](https://langchain-ai.github.io/langgraph/how-tos/memory/delete-messages): LLM should read this page when attempting to manage message history in LangGraph, needing to delete specific messages from conversational state, or implementing memory management in LLM applications. This page explains how to delete messages from a LangGraph application using RemoveMessage modifiers, covering both manual deletion with message IDs and programmatic deletion within graph logic to maintain conversation history limits.
[How to manage conversation history](https://langchain-ai.github.io/langgraph/how-tos/memory/manage-conversation-history/): LLM should read this page when managing conversation history in LangGraph, preventing context window issues, or implementing custom message filtering. This page explains how to manage conversation history in LangGraph to prevent context window overflow by implementing message filtering functions that control which messages are sent to the LLM.
[How to add semantic search to your agent's memory](https://langchain-ai.github.io/langgraph/how-tos/memory/semantic-search/): LLM should read this page when implementing semantic search in agent memory, enabling memory-aware AI assistants, or configuring advanced memory retrieval systems. This page demonstrates how to add semantic search to LangGraph agent memory stores, covering basic setup with embeddings, storing memories, searching by semantic similarity, integrating memory in agents and ReAct agents, and advanced usage like multi-vector indexing and selective memory indexing.
[How to add multi-turn conversation in a multi-agent application](https://langchain-ai.github.io/langgraph/how-tos/multi-agent-multi-turn-convo/): LLM should read this page when implementing multi-turn conversations between agents, creating interactive agent systems with human input, or learning about langgraph interrupts and agent handoffs. This page demonstrates how to build a multi-agent system with multi-turn conversations, including human-in-the-loop interactions, agent handoffs, and state management using LangGraph, Command objects, and interrupts.
[How to add multi-turn conversation in a multi-agent application (functional API)](https://langchain-ai.github.io/langgraph/how-tos/multi-agent-multi-turn-convo-functional/): LLM should read this page when building multi-turn conversational agents, implementing agent-to-agent handoffs, or using interrupts to collect user input in LangGraph. This guide demonstrates how to create a multi-agent system with multi-turn conversations using LangGraph's functional API, featuring agent handoffs, interrupt mechanics for user input, and a complete example of travel and hotel advisor agents that can transfer control between each other.
[How to build a multi-agent network](https://langchain-ai.github.io/langgraph/how-tos/multi-agent-network/): LLM should read this page when implementing multi-agent networks, setting up agent communication via handoffs, or building travel assistance agents. This page explains how to create a fully-connected multi-agent network with LangGraph where agents can communicate with each other via handoffs, including custom agent implementation and using prebuilt ReAct agents with tools.
[How to build a multi-agent network (functional API)](https://langchain-ai.github.io/langgraph/how-tos/multi-agent-network-functional/): LLM should read this page when building multi-agent systems, implementing agent handoffs between specialists, or creating fully-connected agent networks. This guide demonstrates how to create a multi-agent network using LangGraph's functional API, with tasks for individual agents and entrypoint functions to manage agent handoffs based on tool calls.
[How to add node retry policies](https://langchain-ai.github.io/langgraph/how-tos/node-retries/): LLM should read this page when implementing error handling in LangGraph nodes, configuring API retry mechanisms, or troubleshooting node failures in graph workflows. Shows how to add custom retry policies to LangGraph nodes, including specifying which exceptions to retry on, setting max attempts, intervals, backoff factors, and implementing different retry behaviors for different node types.
[How to pass config to tools](https://langchain-ai.github.io/langgraph/how-tos/pass-config-to-tools/): LLM should read this page when implementing secure tool configuration in LangChain, passing user-specific parameters to tools, or configuring tools with runtime values. This page explains how to pass configuration to LangChain tools using RunnableConfig, allowing application-controlled values (like user IDs) to be securely passed to tools without LLM control, with examples of implementing tools that access user-specific data.
[How to pass private state between nodes](https://langchain-ai.github.io/langgraph/how-tos/pass_private_state/): LLM should read this page when implementing data sharing between specific nodes in LangGraph, handling private state in graph workflows, or designing multi-node sequential processes with selective data visibility. This page demonstrates how to pass private data between specific nodes in a LangGraph without making it part of the main schema, using typed dictionaries to define both public and private states, and showing a three-node example where private data flows only between the first two nodes.
[How to add thread-level persistence to your graph](https://langchain-ai.github.io/langgraph/how-tos/persistence/): LLM should read this page when implementing persistence in LangGraph, needing to preserve context across user interactions, or learning about thread-level state management. This page explains how to add thread-level persistence to LangGraph applications using MemorySaver, including code examples for creating stateful conversations where context is maintained across multiple interactions.
[How to add thread-level persistence (functional API)](https://langchain-ai.github.io/langgraph/how-tos/persistence-functional/): LLM should read this page when implementing thread-level persistence in LangGraph, creating conversational agents with memory, or using functional API with state management. This page explains how to add thread-level persistence to LangGraph functional API workflows using checkpointers, including code examples for creating a simple chatbot with memory across conversation turns.
[How to use MongoDB checkpointer for persistence](https://langchain-ai.github.io/langgraph/how-tos/persistence_mongodb/): LLM should read this page when implementing persistence in LangGraph agents, setting up MongoDB for state checkpointing, or working with MongoDB connections in LangGraph applications. This page explains how to use the MongoDB checkpointer for LangGraph persistence, covering connection methods (direct, client-based, async), basic setup requirements, and practical examples of saving and retrieving agent state between interactions.
[How to use Postgres checkpointer for persistence](https://langchain-ai.github.io/langgraph/how-tos/persistence_postgres/): LLM should read this page when setting up persistence for LangGraph agents, implementing PostgreSQL as a checkpoint storage backend, or working with either synchronous or asynchronous database connections. This page details how to use PostgreSQL for persisting LangGraph agent state, covering setup and configuration of PostgresSaver and AsyncPostgresSaver with different connection methods (pool, direct connection, connection string).
[How to create a custom checkpointer using Redis](https://langchain-ai.github.io/langgraph/how-tos/persistence_redis/): LLM should read this page when implementing persistence in LangGraph applications, creating custom checkpoint mechanisms for agents, or working with Redis as a storage backend. This page demonstrates how to create custom checkpointers for LangGraph agents using Redis, including implementations for both synchronous and asynchronous interfaces that save and retrieve agent state.
[How to create a ReAct agent from scratch](https://langchain-ai.github.io/langgraph/how-tos/react-agent-from-scratch/): LLM should read this page when needing to create a custom ReAct agent, wanting more control than prebuilt agents, or implementing ReAct from scratch with LangGraph. This guide shows how to build a custom ReAct agent using LangGraph, covering state definition, model/tool setup, node/edge configuration, graph creation, and testing the implementation with a weather query example.
[How to create a ReAct agent from scratch (Functional API)](https://langchain-ai.github.io/langgraph/how-tos/react-agent-from-scratch-functional): LLM should read this page when creating a ReAct agent using LangGraph's Functional API, implementing tool-calling workflows, or building conversational agents with thread persistence. This page explains how to build a ReAct agent from scratch using LangGraph's Functional API, including model and tool setup, defining tasks for model/tool calling, creating an entrypoint for orchestration, and adding thread-level persistence for conversational experiences.
[How to force tool-calling agent to structure output](https://langchain-ai.github.io/langgraph/how-tos/react-agent-structured-output): LLM should read this page when needing to force tool-calling agents to produce structured output, implementing consistent output formats for downstream software, or choosing between single-LLM vs two-LLM structured output approaches. The page explains two methods for implementing structured output with tool-calling agents: binding output as a tool (single LLM approach) and using two LLMs with structured output conversion, with code examples for both approaches using LangGraph.
[How to create and control loops](https://langchain-ai.github.io/langgraph/how-tos/recursion-limit/): LLM should read this page when building loops in computational graphs, needing to implement termination conditions, or handling recursion limits in LangGraph. The page explains how to create graphs with loops using conditional edges for termination, set recursion limits, handle GraphRecursionError, and implement complex loops with branches.
[How to review tool calls (Functional API)](https://langchain-ai.github.io/langgraph/how-tos/review-tool-calls-functional/): LLM should read this page when implementing human review of tool calls, creating ReAct agents with Functional API, or adding human-in-the-loop workflows. This page demonstrates how to review tool calls before execution in a ReAct agent using LangGraph's Functional API, including accepting, revising, or generating custom tool messages with the interrupt function.
[How to pass custom run ID or set tags and metadata for graph runs in LangSmith](https://langchain-ai.github.io/langgraph/how-tos/run-id-langsmith/): LLM should read this page when needing to customize trace information in LangSmith for LangGraph runs or when debugging graph runs with custom identifiers. The page explains how to pass custom run_id, set tags, add metadata, and customize run names for LangGraph traces in LangSmith using RunnableConfig, with examples showing implementation with a ReAct agent.
[How to create a sequence of steps](https://langchain-ai.github.io/langgraph/how-tos/sequence/): LLM should read this page when implementing sequential workflows in LangGraph, creating multi-step processes in applications, or learning about state management in graph-based systems. This page explains how to create sequences in LangGraph, covering methods for building sequential graphs using .add_node/.add_edge or the shorthand .add_sequence, defining state with TypedDict, creating nodes as functions that update state, and compiling/invoking graphs with examples.
[How to use Pydantic model as graph state](https://langchain-ai.github.io/langgraph/how-tos/state-model): LLM should read this page when implementing Pydantic models for state validation in LangGraph, handling complex state schema definitions, or troubleshooting validation errors in graph nodes. This guide explains how to use Pydantic BaseModel as a state schema in LangGraph for runtime validation, covering basic implementation, limitations, validation behavior across multiple nodes, serialization patterns, type coercion, and working with message models.
[How to update graph state from nodes](https://langchain-ai.github.io/langgraph/how-tos/state-reducers/): LLM should read this page when needing to update state in LangGraph, designing graphs with nodes that modify state, or implementing reducers for state management. This page explains how to define state schemas in LangGraph using TypedDict, how nodes can update state, and how to use reducers to control state updates, with specific examples using message handling.
[How to stream](https://langchain-ai.github.io/langgraph/how-tos/streaming/): LLM should read this page when needing to implement streaming in LangGraph applications, understanding different streaming modes, or troubleshooting LLM response delivery. This page explains how to stream LLM outputs using LangGraph, covering different streaming modes (values, updates, custom, messages, debug), with code examples for each mode and how to combine multiple streaming modes.
[How to stream data from within a tool](https://langchain-ai.github.io/langgraph/how-tos/streaming-events-from-within-tools/): LLM should read this page when implementing streaming functionality in tools, integrating LLM outputs with custom data streams, or developing LangGraph applications with real-time feedback. This page explains how to stream data from within tools using LangGraph, covering custom data streaming with stream_mode="custom", LLM token streaming with stream_mode="messages", and implementation approaches both with and without LangChain.
[How to stream LLM tokens from specific nodes](https://langchain-ai.github.io/langgraph/how-tos/streaming-specific-nodes/): LLM should read this page when needing to filter token streaming from specific nodes in LangGraph, implementing selective streaming in multi-node workflows, or controlling which node outputs are displayed. Guide explains how to stream LLM tokens from specific nodes using stream_mode="messages" and filtering by the langgraph_node metadata field, with complete code examples for implementing this in StateGraph applications.
[How to stream from subgraphs](https://langchain-ai.github.io/langgraph/how-tos/streaming-subgraphs/): LLM should read this page when needing to stream outputs from subgraphs in LangGraph, implementing nested graph streaming, or debugging hierarchical graph execution. This page explains how to stream outputs from subgraphs in LangGraph by using the subgraphs=True parameter in the parent graph's stream() method, with a complete code example showing the difference between regular streaming and subgraph streaming.
[How to stream LLM tokens from your graph](https://langchain-ai.github.io/langgraph/how-tos/streaming-tokens): LLM should read this page when needing to stream LLM tokens from a LangGraph application, implementing custom token streaming, or filtering streamed outputs. This page explains how to stream individual LLM tokens from LangGraph nodes using graph.stream() with different stream_mode options, including examples with and without LangChain, async implementations, and how to filter streamed tokens using metadata.
[How to use subgraphs](https://langchain-ai.github.io/langgraph/how-tos/subgraph/): LLM should read this page when building complex systems with subgraphs, implementing multi-agent systems, or needing to share state between parent graphs and subgraphs. The page explains two methods for using subgraphs: adding compiled subgraphs when schemas share keys, and invoking subgraphs via node functions when schemas differ, with code examples for both approaches.
[How to add thread-level persistence to a subgraph](https://langchain-ai.github.io/langgraph/how-tos/subgraph-persistence/): LLM should read this page when implementing persistence in nested LangGraph architectures, adding thread-level storage to subgraphs, or debugging state propagation in LangGraph applications. This guide demonstrates how to add thread-level persistence to subgraphs by passing a checkpointer only to the parent graph during compilation, accessing persisted states from both parent and child graphs, and retrieving subgraph state using the proper configuration parameters.
[How to transform inputs and outputs of a subgraph](https://langchain-ai.github.io/langgraph/how-tos/subgraph-transform-state/): LLM should read this page when needing to work with nested subgraphs, transforming state between parent and child graphs, or integrating independent state components in LangGraph. This page demonstrates how to transform inputs and outputs between parent graphs and subgraphs with different state structures, showing implementation of three nested graphs (parent, child, grandchild) with separate state dictionaries and transformation functions.
[How to view and update state in subgraphs](https://langchain-ai.github.io/langgraph/how-tos/subgraphs-manage-state/): LLM should read this page when working with state management in nested subgraphs, implementing human-in-the-loop patterns, or debugging complex graph flows. This guide covers viewing and updating state in LangGraph subgraphs, including how to resume execution from breakpoints, modify subgraph state, act as specific nodes, and work with multi-level nested subgraphs.
[How to call tools using ToolNode](https://langchain-ai.github.io/langgraph/how-tos/tool-calling/): LLM should read this page when learning how to implement tool calling with LangGraph, when working with the ToolNode component, or when building ReAct agents. This page covers using LangGraph's ToolNode for tool calling, including setup, manual invocation, working with chat models, building a ReAct agent, handling single and parallel tool calls, and error handling.
[How to handle tool calling errors](https://langchain-ai.github.io/langgraph/how-tos/tool-calling-errors/): LLM should read this page when handling tool call errors, implementing error handling for LLM-tool interactions, or creating fallback strategies for failed tool calls. This page covers strategies for handling tool calling errors in LangGraph, including using the prebuilt ToolNode with built-in error handling, implementing custom error handling patterns, and fallback mechanisms with model upgrades when tools fail.
[How to update graph state from tools](https://langchain-ai.github.io/langgraph/how-tos/update-state-from-tools/): LLM should read this page when needing to update graph state from tools in LangGraph, implementing personalized responses based on tool updates, or using Command objects to modify state. This page details how to update graph state from tools using Command objects, creating personalized agents with state tracking, and implementing dynamic prompt construction based on updated state values.
[How to interact with the deployment using RemoteGraph](https://langchain-ai.github.io/langgraph/how-tos/use-remote-graph/): LLM should read this page when needing to interact with LangGraph Platform deployments remotely, when implementing RemoteGraph interfaces, or when using deployed graphs as subgraphs. This page explains how to use RemoteGraph to interact with LangGraph Platform deployments, covering initialization methods (URL-based or client-based), synchronous/asynchronous invocation, thread-level persistence, and using RemoteGraph as a subgraph in larger applications.
[How to visualize your graph](https://langchain-ai.github.io/langgraph/how-tos/visualization): LLM should read this page when needing to visualize LangGraph graphs, looking for graph visualization methods, or working with graph visualization in Python. Comprehensive guide for visualizing graphs in LangGraph with multiple methods: Mermaid syntax, Mermaid.ink API for PNG rendering, Pyppeteer-based visualization, and Graphviz, with customization options for colors, styles, and layout.
[How to wait for user input (Functional API)](https://langchain-ai.github.io/langgraph/how-tos/wait-user-input-functional/): LLM should read this page when implementing human-in-the-loop workflows, integrating user input into agent systems, or adding interruption capabilities to LangGraph applications. The page explains how to use the `interrupt()` function in LangGraph's Functional API to pause execution for human input, with examples for both simple workflows and ReAct agents, including code implementations with checkpointing.
# Guides
- [index.md (index.md)](https://langchain-ai.github.io/langgraph/index/)
- [Quickstart (Get started > Quickstart)](https://langchain-ai.github.io/langgraph/agents/agents/)
- [concepts/why-langgraph.md (Get started > LangGraph basics > concepts/why-langgraph.md)](https://langchain-ai.github.io/langgraph/concepts/why-langgraph/)
- [Build a basic chatbot (Get started > LangGraph basics > Build a basic chatbot)](https://langchain-ai.github.io/langgraph/tutorials/get-started/1-build-basic-chatbot/)
- [tutorials/get-started/2-add-tools.md (Get started > LangGraph basics > tutorials/get-started/2-add-tools.md)](https://langchain-ai.github.io/langgraph/tutorials/get-started/2-add-tools/)
- [tutorials/get-started/3-add-memory.md (Get started > LangGraph basics > tutorials/get-started/3-add-memory.md)](https://langchain-ai.github.io/langgraph/tutorials/get-started/3-add-memory/)
- [Add human-in-the-loop (Get started > LangGraph basics > Add human-in-the-loop)](https://langchain-ai.github.io/langgraph/tutorials/get-started/4-human-in-the-loop/)
- [tutorials/get-started/5-customize-state.md (Get started > LangGraph basics > tutorials/get-started/5-customize-state.md)](https://langchain-ai.github.io/langgraph/tutorials/get-started/5-customize-state/)
- [tutorials/get-started/6-time-travel.md (Get started > LangGraph basics > tutorials/get-started/6-time-travel.md)](https://langchain-ai.github.io/langgraph/tutorials/get-started/6-time-travel/)
- [Deployment (Get started > Deployment)](https://langchain-ai.github.io/langgraph/tutorials/deployment/)
- [Overview (Prebuilt agents > Overview)](https://langchain-ai.github.io/langgraph/agents/overview/)
- [agents/run_agents.md (Prebuilt agents > agents/run_agents.md)](https://langchain-ai.github.io/langgraph/agents/run_agents/)
- [agents/streaming.md (Prebuilt agents > agents/streaming.md)](https://langchain-ai.github.io/langgraph/agents/streaming/)
- [agents/models.md (Prebuilt agents > agents/models.md)](https://langchain-ai.github.io/langgraph/agents/models/)
- [agents/tools.md (Prebuilt agents > agents/tools.md)](https://langchain-ai.github.io/langgraph/agents/tools/)
- [agents/mcp.md (Prebuilt agents > agents/mcp.md)](https://langchain-ai.github.io/langgraph/agents/mcp/)
- [agents/context.md (Prebuilt agents > agents/context.md)](https://langchain-ai.github.io/langgraph/agents/context/)
- [agents/memory.md (Prebuilt agents > agents/memory.md)](https://langchain-ai.github.io/langgraph/agents/memory/)
- [agents/human-in-the-loop.md (Prebuilt agents > agents/human-in-the-loop.md)](https://langchain-ai.github.io/langgraph/agents/human-in-the-loop/)
- [agents/multi-agent.md (Prebuilt agents > agents/multi-agent.md)](https://langchain-ai.github.io/langgraph/agents/multi-agent/)
- [agents/evals.md (Prebuilt agents > agents/evals.md)](https://langchain-ai.github.io/langgraph/agents/evals/)
- [agents/deployment.md (Prebuilt agents > agents/deployment.md)](https://langchain-ai.github.io/langgraph/agents/deployment/)
- [agents/ui.md (Prebuilt agents > agents/ui.md)](https://langchain-ai.github.io/langgraph/agents/ui/)
- [Overview (LangGraph framework > Agent architectures > Overview)](https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/)
- [Workflows & agents (LangGraph framework > Agent architectures > Workflows & agents)](https://langchain-ai.github.io/langgraph/tutorials/workflows/)
- [Overview (LangGraph framework > Graphs > Overview)](https://langchain-ai.github.io/langgraph/concepts/low_level/)
- [Runtime overview (LangGraph framework > Graphs > Runtime overview)](https://langchain-ai.github.io/langgraph/concepts/pregel/)
- [Use the Graph API (LangGraph framework > Graphs > Use the Graph API)](https://langchain-ai.github.io/langgraph/how-tos/graph-api/)
- [Overview (LangGraph framework > Streaming > Overview)](https://langchain-ai.github.io/langgraph/concepts/streaming/)
- [Stream outputs (LangGraph framework > Streaming > Stream outputs)](https://langchain-ai.github.io/langgraph/how-tos/streaming/)
- [Overview (LangGraph framework > Persistence > Overview)](https://langchain-ai.github.io/langgraph/concepts/persistence/)
- [concepts/durable_execution.md (LangGraph framework > Persistence > concepts/durable_execution.md)](https://langchain-ai.github.io/langgraph/concepts/durable_execution/)
- [how-tos/persistence.ipynb (LangGraph framework > Persistence > how-tos/persistence.ipynb)](https://langchain-ai.github.io/langgraph/how-tos/persistence/)
- [Overview (LangGraph framework > Memory > Overview)](https://langchain-ai.github.io/langgraph/concepts/memory/)
- [Manage memory (LangGraph framework > Memory > Manage memory)](https://langchain-ai.github.io/langgraph/how-tos/memory/)
- [Overview (LangGraph framework > Human-in-the-loop > Overview)](https://langchain-ai.github.io/langgraph/concepts/human_in_the_loop/)
- [how-tos/human_in_the_loop/add-human-in-the-loop.md (LangGraph framework > Human-in-the-loop > how-tos/human_in_the_loop/add-human-in-the-loop.md)](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/add-human-in-the-loop/)
- [Overview (LangGraph framework > Breakpoints > Overview)](https://langchain-ai.github.io/langgraph/concepts/breakpoints/)
- [how-tos/human_in_the_loop/breakpoints.ipynb (LangGraph framework > Breakpoints > how-tos/human_in_the_loop/breakpoints.ipynb)](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/breakpoints/)
- [Overview (LangGraph framework > Time travel > Overview)](https://langchain-ai.github.io/langgraph/concepts/time-travel/)
- [how-tos/human_in_the_loop/time-travel.ipynb (LangGraph framework > Time travel > how-tos/human_in_the_loop/time-travel.ipynb)](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/time-travel/)
- [Overview (LangGraph framework > Tools > Overview)](https://langchain-ai.github.io/langgraph/concepts/tools/)
- [how-tos/tool-calling.ipynb (LangGraph framework > Tools > how-tos/tool-calling.ipynb)](https://langchain-ai.github.io/langgraph/how-tos/tool-calling/)
- [Overview (LangGraph framework > Subgraphs > Overview)](https://langchain-ai.github.io/langgraph/concepts/subgraphs/)
- [how-tos/subgraph.ipynb (LangGraph framework > Subgraphs > how-tos/subgraph.ipynb)](https://langchain-ai.github.io/langgraph/how-tos/subgraph/)
- [Overview (LangGraph framework > Multi-agent > Overview)](https://langchain-ai.github.io/langgraph/concepts/multi_agent/)
- [how-tos/multi_agent.ipynb (LangGraph framework > Multi-agent > how-tos/multi_agent.ipynb)](https://langchain-ai.github.io/langgraph/how-tos/multi_agent/)
- [Overview (LangGraph framework > Functional API > Overview)](https://langchain-ai.github.io/langgraph/concepts/functional_api/)
- [how-tos/use-functional-api.md (LangGraph framework > Functional API > how-tos/use-functional-api.md)](https://langchain-ai.github.io/langgraph/how-tos/use-functional-api/)
- [Overview (LangGraph Platform > Overview)](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/)
- [Quickstart (LangGraph Platform > Get started > Quickstart)](https://langchain-ai.github.io/langgraph/tutorials/langgraph-platform/local-server/)
- [Deployment quickstart (LangGraph Platform > Get started > Deployment quickstart)](https://langchain-ai.github.io/langgraph/cloud/quick_start/)
- [Overview (LangGraph Platform > Components > Overview)](https://langchain-ai.github.io/langgraph/concepts/langgraph_components/)
- [Overview (LangGraph Platform > Components > LangGraph Server > Overview)](https://langchain-ai.github.io/langgraph/concepts/langgraph_server/)
- [Overview (LangGraph Platform > Components > LangGraph Server > Application structure > Overview)](https://langchain-ai.github.io/langgraph/concepts/application_structure/)
- [cloud/deployment/setup.md (LangGraph Platform > Components > LangGraph Server > Application structure > cloud/deployment/setup.md)](https://langchain-ai.github.io/langgraph/cloud/deployment/setup/)
- [cloud/deployment/setup_pyproject.md (LangGraph Platform > Components > LangGraph Server > Application structure > cloud/deployment/setup_pyproject.md)](https://langchain-ai.github.io/langgraph/cloud/deployment/setup_pyproject/)
- [cloud/deployment/setup_javascript.md (LangGraph Platform > Components > LangGraph Server > Application structure > cloud/deployment/setup_javascript.md)](https://langchain-ai.github.io/langgraph/cloud/deployment/setup_javascript/)
- [cloud/deployment/custom_docker.md (LangGraph Platform > Components > LangGraph Server > Application structure > cloud/deployment/custom_docker.md)](https://langchain-ai.github.io/langgraph/cloud/deployment/custom_docker/)
- [LangGraph CLI (LangGraph Platform > Components > LangGraph CLI)](https://langchain-ai.github.io/langgraph/concepts/langgraph_cli/)
- [Overview (LangGraph Platform > Components > LangGraph Studio > Overview)](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/)
- [Quickstart (LangGraph Platform > Components > LangGraph Studio > Quickstart)](https://langchain-ai.github.io/langgraph/cloud/how-tos/studio/quick_start/)
- [cloud/how-tos/invoke_studio.md (LangGraph Platform > Components > LangGraph Studio > cloud/how-tos/invoke_studio.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/invoke_studio/)
- [cloud/how-tos/studio/manage_assistants.md (LangGraph Platform > Components > LangGraph Studio > cloud/how-tos/studio/manage_assistants.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/studio/manage_assistants/)
- [cloud/how-tos/threads_studio.md (LangGraph Platform > Components > LangGraph Studio > cloud/how-tos/threads_studio.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/threads_studio/)
- [cloud/how-tos/iterate_graph_studio.md (LangGraph Platform > Components > LangGraph Studio > cloud/how-tos/iterate_graph_studio.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/iterate_graph_studio/)
- [cloud/how-tos/clone_traces_studio.md (LangGraph Platform > Components > LangGraph Studio > cloud/how-tos/clone_traces_studio.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/clone_traces_studio/)
- [cloud/how-tos/datasets_studio.md (LangGraph Platform > Components > LangGraph Studio > cloud/how-tos/datasets_studio.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/datasets_studio/)
- [LangGraph SDK (LangGraph Platform > Components > LangGraph SDK)](https://langchain-ai.github.io/langgraph/concepts/sdk/)
- [Add semantic search (LangGraph Platform > Data management > Add semantic search)](https://langchain-ai.github.io/langgraph/cloud/deployment/semantic_search/)
- [Add TTLs (LangGraph Platform > Data management > Add TTLs)](https://langchain-ai.github.io/langgraph/how-tos/ttl/configure_ttl/)
- [Overview (LangGraph Platform > Authentication & access control > Overview)](https://langchain-ai.github.io/langgraph/concepts/auth/)
- [how-tos/auth/custom_auth.md (LangGraph Platform > Authentication & access control > how-tos/auth/custom_auth.md)](https://langchain-ai.github.io/langgraph/how-tos/auth/custom_auth/)
- [how-tos/auth/openapi_security.md (LangGraph Platform > Authentication & access control > how-tos/auth/openapi_security.md)](https://langchain-ai.github.io/langgraph/how-tos/auth/openapi_security/)
- [Overview (LangGraph Platform > Assistants > Overview)](https://langchain-ai.github.io/langgraph/concepts/assistants/)
- [cloud/how-tos/configuration_cloud.md (LangGraph Platform > Assistants > cloud/how-tos/configuration_cloud.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/configuration_cloud/)
- [Overview (LangGraph Platform > Threads > Overview)](https://langchain-ai.github.io/langgraph/cloud/concepts/threads/)
- [cloud/how-tos/use_threads.md (LangGraph Platform > Threads > cloud/how-tos/use_threads.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/use_threads/)
- [Overview (LangGraph Platform > Runs > Overview)](https://langchain-ai.github.io/langgraph/cloud/concepts/runs/)
- [cloud/how-tos/background_run.md (LangGraph Platform > Runs > cloud/how-tos/background_run.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/background_run/)
- [cloud/how-tos/same-thread.md (LangGraph Platform > Runs > cloud/how-tos/same-thread.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/same-thread/)
- [cloud/how-tos/cron_jobs.md (LangGraph Platform > Runs > cloud/how-tos/cron_jobs.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/cron_jobs/)
- [cloud/how-tos/stateless_runs.md (LangGraph Platform > Runs > cloud/how-tos/stateless_runs.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/stateless_runs/)
- [cloud/how-tos/configurable_headers.md (LangGraph Platform > Runs > cloud/how-tos/configurable_headers.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/configurable_headers/)
- [Overview (LangGraph Platform > Streaming > Overview)](https://langchain-ai.github.io/langgraph/cloud/concepts/streaming/)
- [cloud/how-tos/streaming.md (LangGraph Platform > Streaming > cloud/how-tos/streaming.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/streaming/)
- [Human-in-the-loop (LangGraph Platform > Human-in-the-loop)](https://langchain-ai.github.io/langgraph/cloud/how-tos/add-human-in-the-loop/)
- [Breakpoints (LangGraph Platform > Breakpoints)](https://langchain-ai.github.io/langgraph/cloud/how-tos/human_in_the_loop_breakpoint/)
- [Time travel (LangGraph Platform > Time travel)](https://langchain-ai.github.io/langgraph/cloud/how-tos/human_in_the_loop_time_travel/)
- [MCP (LangGraph Platform > MCP)](https://langchain-ai.github.io/langgraph/concepts/server-mcp/)
- [Overview (LangGraph Platform > Double-texting > Overview)](https://langchain-ai.github.io/langgraph/concepts/double_texting/)
- [cloud/how-tos/interrupt_concurrent.md (LangGraph Platform > Double-texting > cloud/how-tos/interrupt_concurrent.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/interrupt_concurrent/)
- [cloud/how-tos/rollback_concurrent.md (LangGraph Platform > Double-texting > cloud/how-tos/rollback_concurrent.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/rollback_concurrent/)
- [cloud/how-tos/reject_concurrent.md (LangGraph Platform > Double-texting > cloud/how-tos/reject_concurrent.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/reject_concurrent/)
- [cloud/how-tos/enqueue_concurrent.md (LangGraph Platform > Double-texting > cloud/how-tos/enqueue_concurrent.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/enqueue_concurrent/)
- [Overview (LangGraph Platform > Webhooks > Overview)](https://langchain-ai.github.io/langgraph/cloud/concepts/webhooks/)
- [cloud/how-tos/webhooks.md (LangGraph Platform > Webhooks > cloud/how-tos/webhooks.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/webhooks/)
- [Overview (LangGraph Platform > Cron jobs > Overview)](https://langchain-ai.github.io/langgraph/cloud/concepts/cron_jobs/)
- [cloud/how-tos/cron_jobs.md (LangGraph Platform > Cron jobs > cloud/how-tos/cron_jobs.md)](https://langchain-ai.github.io/langgraph/cloud/how-tos/cron_jobs/)
- [how-tos/http/custom_lifespan.md (LangGraph Platform > Server customization > how-tos/http/custom_lifespan.md)](https://langchain-ai.github.io/langgraph/how-tos/http/custom_lifespan/)
- [how-tos/http/custom_middleware.md (LangGraph Platform > Server customization > how-tos/http/custom_middleware.md)](https://langchain-ai.github.io/langgraph/how-tos/http/custom_middleware/)
- [how-tos/http/custom_routes.md (LangGraph Platform > Server customization > how-tos/http/custom_routes.md)](https://langchain-ai.github.io/langgraph/how-tos/http/custom_routes/)
- [Overview (LangGraph Platform > Deployment > Overview)](https://langchain-ai.github.io/langgraph/concepts/deployment_options/)
- [Data plane (LangGraph Platform > Deployment > Data plane)](https://langchain-ai.github.io/langgraph/concepts/langgraph_data_plane/)
- [Control plane (LangGraph Platform > Deployment > Control plane)](https://langchain-ai.github.io/langgraph/concepts/langgraph_control_plane/)
- [Overview (LangGraph Platform > Deployment > Deployment options > Cloud SaaS > Overview)](https://langchain-ai.github.io/langgraph/concepts/langgraph_cloud/)
- [Deploy Cloud SaaS (LangGraph Platform > Deployment > Deployment options > Cloud SaaS > Deploy Cloud SaaS)](https://langchain-ai.github.io/langgraph/cloud/deployment/cloud/)
- [Overview (LangGraph Platform > Deployment > Deployment options > Self-Hosted Data Plane > Overview)](https://langchain-ai.github.io/langgraph/concepts/langgraph_self_hosted_data_plane/)
- [Deploy Self-Hosted Data Plane (LangGraph Platform > Deployment > Deployment options > Self-Hosted Data Plane > Deploy Self-Hosted Data Plane)](https://langchain-ai.github.io/langgraph/cloud/deployment/self_hosted_data_plane/)
- [Overview (LangGraph Platform > Deployment > Deployment options > Self-Hosted Control Plane > Overview)](https://langchain-ai.github.io/langgraph/concepts/langgraph_self_hosted_control_plane/)
- [Deploy Self-Hosted Control Plane (LangGraph Platform > Deployment > Deployment options > Self-Hosted Control Plane > Deploy Self-Hosted Control Plane)](https://langchain-ai.github.io/langgraph/cloud/deployment/self_hosted_control_plane/)
- [Overview (LangGraph Platform > Deployment > Deployment options > Standalone Container > Overview)](https://langchain-ai.github.io/langgraph/concepts/langgraph_standalone_container/)
- [Deploy Standalone Container (LangGraph Platform > Deployment > Deployment options > Standalone Container > Deploy Standalone Container)](https://langchain-ai.github.io/langgraph/cloud/deployment/standalone_container/)
- [Scalability & resilience (LangGraph Platform > Deployment > Scalability & resilience)](https://langchain-ai.github.io/langgraph/concepts/scalability_and_resilience/)
- [Plans & pricing (LangGraph Platform > Deployment > Plans & pricing)](https://langchain-ai.github.io/langgraph/concepts/plans/)
# Examples
- [Agentic RAG (Agentic RAG)](https://langchain-ai.github.io/langgraph/tutorials/rag/langgraph_agentic_rag/)
- [Agent Supervisor (Agent Supervisor)](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/agent_supervisor/)
- [SQL agent (SQL agent)](https://langchain-ai.github.io/langgraph/tutorials/sql-agent/)
- [Graph runs in LangSmith (Graph runs in LangSmith)](https://langchain-ai.github.io/langgraph/how-tos/run-id-langsmith/)
- [tutorials/auth/getting_started.md (LangGraph Platform > Authentication > tutorials/auth/getting_started.md)](https://langchain-ai.github.io/langgraph/tutorials/auth/getting_started/)
- [tutorials/auth/resource_auth.md (LangGraph Platform > Authentication > tutorials/auth/resource_auth.md)](https://langchain-ai.github.io/langgraph/tutorials/auth/resource_auth/)
- [tutorials/auth/add_auth_server.md (LangGraph Platform > Authentication > tutorials/auth/add_auth_server.md)](https://langchain-ai.github.io/langgraph/tutorials/auth/add_auth_server/)
- [Rebuild graph at runtime (LangGraph Platform > Rebuild graph at runtime)](https://langchain-ai.github.io/langgraph/cloud/deployment/graph_rebuild/)
- [Use RemoteGraph (LangGraph Platform > Use RemoteGraph)](https://langchain-ai.github.io/langgraph/how-tos/use-remote-graph/)
- [Deploy CrewAI, AutoGen, and other frameworks (LangGraph Platform > Deploy CrewAI, AutoGen, and other frameworks)](https://langchain-ai.github.io/langgraph/how-tos/autogen-langgraph-platform/)
- [Integrate LangGraph into a React app (LangGraph Platform > Front-end and generative UI > Integrate LangGraph into a React app)](https://langchain-ai.github.io/langgraph/cloud/how-tos/use_stream_react/)
- [Implement generative UI with LangGraph (LangGraph Platform > Front-end and generative UI > Implement generative UI with LangGraph)](https://langchain-ai.github.io/langgraph/cloud/how-tos/generative_ui_react/)
# Resources
- [concepts/faq.md (concepts/faq.md)](https://langchain-ai.github.io/langgraph/concepts/faq/)
- [Template applications (Template applications)](https://langchain-ai.github.io/langgraph/concepts/template_applications/)
- [llms.txt (llms.txt)](https://langchain-ai.github.io/langgraph/llms-txt-overview/)
- [agents/prebuilt.md (agents/prebuilt.md)](https://langchain-ai.github.io/langgraph/agents/prebuilt/)
- [troubleshooting/errors/index.md (Troubleshooting > Errors > troubleshooting/errors/index.md)](https://langchain-ai.github.io/langgraph/troubleshooting/errors/index/)
- [troubleshooting/errors/GRAPH_RECURSION_LIMIT.md (Troubleshooting > Errors > troubleshooting/errors/GRAPH_RECURSION_LIMIT.md)](https://langchain-ai.github.io/langgraph/troubleshooting/errors/GRAPH_RECURSION_LIMIT/)
- [troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.md (Troubleshooting > Errors > troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.md)](https://langchain-ai.github.io/langgraph/troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE/)
- [troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.md (Troubleshooting > Errors > troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.md)](https://langchain-ai.github.io/langgraph/troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE/)
- [troubleshooting/errors/MULTIPLE_SUBGRAPHS.md (Troubleshooting > Errors > troubleshooting/errors/MULTIPLE_SUBGRAPHS.md)](https://langchain-ai.github.io/langgraph/troubleshooting/errors/MULTIPLE_SUBGRAPHS/)
- [troubleshooting/errors/INVALID_CHAT_HISTORY.md (Troubleshooting > Errors > troubleshooting/errors/INVALID_CHAT_HISTORY.md)](https://langchain-ai.github.io/langgraph/troubleshooting/errors/INVALID_CHAT_HISTORY/)
- [troubleshooting/errors/INVALID_LICENSE.md (Troubleshooting > Errors > troubleshooting/errors/INVALID_LICENSE.md)](https://langchain-ai.github.io/langgraph/troubleshooting/errors/INVALID_LICENSE/)
- [LangGraph Studio (Troubleshooting > LangGraph Studio)](https://langchain-ai.github.io/langgraph/troubleshooting/studio/)
- [LangGraph Academy course (Learn > LangGraph Academy course)](https://langchain-ai.github.io/langgraph/https://academy.langchain.com/courses/intro-to-langgraph/)
- [Case studies (Learn > Case studies)](https://langchain-ai.github.io/langgraph/adopters/)
+5
View File
@@ -0,0 +1,5 @@
## Caching
::: langgraph.cache.base
::: langgraph.cache.memory
::: langgraph.cache.sqlite
+1
View File
@@ -29,6 +29,7 @@ The core APIs for the LangGraph opens source library.
- [Pregel](pregel.md): Pregel-inspired computation model.
- [Checkpointing](checkpoints.md): Saving and restoring graph state.
- [Storage](store.md): Storage backends and options.
- [Caching](cache.md): Caching mechanisms for performance.
- [Types](types.md): Type definitions for graph components.
- [Config](config.md): Configuration options.
- [Errors](errors.md): Error types and handling.
@@ -6,7 +6,7 @@ There could be a few reasons you're seeing this error:
1. You manually passed a malformed list of messages when invoking the graph, e.g. `graph.invoke({'messages': [AIMessage(..., tool_calls=[...])]})`
2. The graph was interrupted before receiving updates from the `tools` node (i.e. a list of ToolMessages)
and you invoked it with a an input that is not None or a ToolMessage,
and you invoked it with an input that is not None or a ToolMessage,
e.g. `graph.invoke({'messages': [HumanMessage(...)]}, config)`.
This interrupt could have been triggered in one of the following ways:
- You manually set `interrupt_before = ['tools']` in `create_react_agent`
@@ -8,7 +8,7 @@ class State(TypedDict):
some_key: str
def bad_node(state: State):
# Should return an dict with a value for "some_key", not a list
# Should return a dict with a value for "some_key", not a list
return ["whoops"]
builder = StateGraph(State)
@@ -29,7 +29,7 @@ InvalidUpdateError: Expected dict, got ['whoops']
For troubleshooting, visit: https://python.langchain.com/docs/troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE
```
Nodes in your graph must return an dict containing one or more keys defined in your state.
Nodes in your graph must return a dict containing one or more keys defined in your state.
## Troubleshooting
+2 -2
View File
@@ -1,6 +1,6 @@
# Connect an authentication provider
In the [the last tutorial](resource_auth.md), you added [resource authorization](../../tutorials/auth/resource_auth.md) to give users private conversations. However, you are still using hard-coded tokens for authentication, which is not secure. Now you'll replace those tokens with real user accounts using [OAuth2](../auth/getting_started.md).
In [the last tutorial](resource_auth.md), you added [resource authorization](../../tutorials/auth/resource_auth.md) to give users private conversations. However, you are still using hard-coded tokens for authentication, which is not secure. Now you'll replace those tokens with real user accounts using [OAuth2](../auth/getting_started.md).
You'll keep the same [`Auth`](../../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth) object and [resource-level access control](../../concepts/auth.md#single-owner-resources), but upgrade authentication to use Supabase as your identity provider. While Supabase is used in this tutorial, the concepts apply to any OAuth2 provider. You'll learn how to:
@@ -190,7 +190,7 @@ await sign_up(email1, password)
await sign_up(email2, password)
```
⚠️ Before continuing: Check your email and click both confirmation links. Supabase will will reject `/login` requests until after you have confirmed your users' email.
⚠️ Before continuing: Check your email and click both confirmation links. Supabase will reject `/login` requests until after you have confirmed your users' email.
Now test that users can only see their own data. Make sure the server is running (run `langgraph dev`) before proceeding. The following snippet requires the "anon public" key that you copied from the Supabase dashboard while [setting up the auth provider](#setup-auth-provider) previously.
+1 -1
View File
@@ -181,6 +181,6 @@ Congratulations! You've built a chatbot that only lets "authenticated" users acc
Now that you can control who accesses your bot, you might want to:
1. Continue the tutorial by going to [Make cnversations private](resource_auth.md) to learn about resource authorization.
1. Continue the tutorial by going to [Make conversations private](resource_auth.md) to learn about resource authorization.
2. Read more about [authentication concepts](../../concepts/auth.md).
3. Check out the [API reference](../../cloud/reference/sdk/python_sdk_ref.md) for more authentication details.
@@ -21,7 +21,7 @@ Before you begin, ensure you have the following:
=== "Node server"
```shell
npx @langchain/langgraph-cl
npx @langchain/langgraph-cli
```
## 2. Create a LangGraph app 🌱
+1
View File
@@ -255,6 +255,7 @@ nav:
- Pregel: reference/pregel.md
- Checkpointing: reference/checkpoints.md
- Storage: reference/store.md
- Caching: reference/cache.md
- Types: reference/types.md
- Config: reference/config.md
- Errors: reference/errors.md
Generated
+70 -55
View File
@@ -291,16 +291,16 @@ name = "autogen"
version = "0.3.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "diskcache" },
{ name = "docker" },
{ name = "flaml" },
{ name = "numpy" },
{ name = "openai" },
{ name = "packaging" },
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "termcolor" },
{ name = "tiktoken" },
{ name = "diskcache", marker = "python_full_version < '3.13'" },
{ name = "docker", marker = "python_full_version < '3.13'" },
{ name = "flaml", marker = "python_full_version < '3.13'" },
{ name = "numpy", marker = "python_full_version < '3.13'" },
{ name = "openai", marker = "python_full_version < '3.13'" },
{ name = "packaging", marker = "python_full_version < '3.13'" },
{ name = "pydantic", marker = "python_full_version < '3.13'" },
{ name = "python-dotenv", marker = "python_full_version < '3.13'" },
{ name = "termcolor", marker = "python_full_version < '3.13'" },
{ name = "tiktoken", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7b/e8/33b7fb072fbcf63b8a1b5bbba15570e4e8c86d6374da398889b92fc420c8/autogen-0.3.2.tar.gz", hash = "sha256:9f8a1170ac2e5a1fc9efc3cfa6e23261dd014db97b17c8c416f97ee14951bc7b", size = 306281 }
wheels = [
@@ -1008,9 +1008,9 @@ name = "docker"
version = "7.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pywin32", marker = "sys_platform == 'win32'" },
{ name = "requests" },
{ name = "urllib3" },
{ name = "pywin32", marker = "python_full_version < '3.13' and sys_platform == 'win32'" },
{ name = "requests", marker = "python_full_version < '3.13'" },
{ name = "urllib3", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834 }
wheels = [
@@ -1052,7 +1052,7 @@ name = "exceptiongroup"
version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.12.4'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 }
wheels = [
@@ -1153,7 +1153,7 @@ name = "flaml"
version = "2.3.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "numpy", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/20/a8/17322311b77f3012194f92c47c81455463f99c48d358c463fa45bd3c8541/flaml-2.3.4.tar.gz", hash = "sha256:308c3e769976d8a0272f2fd7d98258d7d4a4fd2e4525ba540d1ba149ae266c54", size = 284728 }
wheels = [
@@ -2590,7 +2590,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "0.4.3"
version = "0.4.5"
source = { editable = "../libs/langgraph" }
dependencies = [
{ name = "langchain-core" },
@@ -2641,7 +2641,7 @@ dev = [
[[package]]
name = "langgraph-checkpoint"
version = "2.0.25"
version = "2.0.26"
source = { editable = "../libs/checkpoint" }
dependencies = [
{ name = "langchain-core" },
@@ -2715,17 +2715,19 @@ dev = [
[[package]]
name = "langgraph-checkpoint-sqlite"
version = "2.0.7"
version = "2.0.10"
source = { editable = "../libs/checkpoint-sqlite" }
dependencies = [
{ name = "aiosqlite" },
{ name = "langgraph-checkpoint" },
{ name = "sqlite-vec" },
]
[package.metadata]
requires-dist = [
{ name = "aiosqlite", specifier = ">=0.20" },
{ name = "langgraph-checkpoint", editable = "../libs/checkpoint" },
{ name = "sqlite-vec", specifier = ">=0.1.6" },
]
[package.metadata.requires-dev]
@@ -2736,6 +2738,7 @@ dev = [
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-mock" },
{ name = "pytest-retry", specifier = ">=1.7.0" },
{ name = "pytest-watcher" },
{ name = "ruff" },
]
@@ -2825,11 +2828,11 @@ requires-dist = [
[package.metadata.requires-dev]
docs = [
{ name = "click", specifier = ">=8.1.7,<9" },
{ name = "jupyter", specifier = ">=1.1.1,<2" },
{ name = "langchain-cohere", specifier = ">=0.4.2,<0.5" },
{ name = "click" },
{ name = "jupyter" },
{ name = "langchain-cohere" },
{ name = "langchain-mcp-adapters", git = "https://github.com/langchain-ai/langchain-mcp-adapters" },
{ name = "langchain-ollama", specifier = ">=0.2.3,<0.3" },
{ name = "langchain-ollama" },
{ name = "langgraph", editable = "../libs/langgraph" },
{ name = "langgraph-checkpoint", editable = "../libs/checkpoint" },
{ name = "langgraph-checkpoint-postgres", editable = "../libs/checkpoint-postgres" },
@@ -2849,41 +2852,41 @@ docs = [
{ name = "mkdocs-rss-plugin" },
{ name = "mkdocstrings" },
{ name = "mkdocstrings-python" },
{ name = "psycopg", extras = ["binary"], specifier = ">=3.2.0,<4" },
{ name = "psycopg-pool", specifier = ">=3.2.0,<4" },
{ name = "pygments-ansi-color", specifier = ">=0.3" },
{ name = "ruff", specifier = ">=0.6.8,<0.7" },
{ name = "vcrpy", specifier = ">=6.0.1,<7" },
{ name = "psycopg", extras = ["binary"] },
{ name = "psycopg-pool" },
{ name = "pygments-ansi-color" },
{ name = "ruff" },
{ name = "vcrpy" },
]
test = [
{ name = "autogen", marker = "python_full_version >= '3.8' and python_full_version < '3.13'", specifier = ">=0.3.0,<0.4" },
{ name = "chromadb", specifier = ">=0.5.5,<0.6" },
{ name = "gpt4all", specifier = ">=2.8.2,<3" },
{ name = "grandalf", specifier = ">=0.8,<0.9" },
{ name = "langchain", specifier = ">=0.3.8,<0.4" },
{ name = "langchain-anthropic", specifier = ">=0.3.8,<0.4" },
{ name = "langchain-community", specifier = ">=0.3.0,<0.4" },
{ name = "langchain-core", specifier = ">=0.3.54,<0.4" },
{ name = "langchain-experimental", specifier = ">=0.3.2,<0.4" },
{ name = "langchain-fireworks", specifier = ">=0.2.0,<0.3" },
{ name = "langchain-mistralai", specifier = ">=0.2.6,<0.3" },
{ name = "langchain-nomic", specifier = ">=0.1.3,<0.2" },
{ name = "langchain-openai", specifier = ">=0.3.7,<0.4" },
{ name = "langchain-tavily", specifier = ">=0.1.5,<0.2" },
{ name = "langgraph-checkpoint-mongodb", specifier = ">=0.1.0,<0.2" },
{ name = "langmem", specifier = ">=0.0.19,<0.0.20" },
{ name = "langsmith", specifier = ">=0.3.0,<0.4" },
{ name = "matplotlib", specifier = ">=3.9.2,<4" },
{ name = "motor", specifier = ">=3.5.1,<4" },
{ name = "networkx", specifier = "~=3.3" },
{ name = "numexpr", specifier = ">=2.10.1,<3" },
{ name = "numpy", specifier = ">=1.26.4,<2" },
{ name = "pymongo", specifier = ">=4.8.0,<5" },
{ name = "pyppeteer", specifier = ">=2.0.0,<3" },
{ name = "pytest", specifier = ">=8.3.5,<9" },
{ name = "pytest-check-links", specifier = ">=0.10.1,<0.11" },
{ name = "redis", specifier = ">=5.0.8,<6" },
{ name = "scikit-learn", specifier = ">=1.5.2,<2" },
{ name = "autogen", marker = "python_full_version >= '3.8' and python_full_version < '3.13'" },
{ name = "chromadb" },
{ name = "gpt4all" },
{ name = "grandalf" },
{ name = "langchain" },
{ name = "langchain-anthropic" },
{ name = "langchain-community" },
{ name = "langchain-core" },
{ name = "langchain-experimental" },
{ name = "langchain-fireworks" },
{ name = "langchain-mistralai" },
{ name = "langchain-nomic" },
{ name = "langchain-openai" },
{ name = "langchain-tavily" },
{ name = "langgraph-checkpoint-mongodb" },
{ name = "langmem" },
{ name = "langsmith" },
{ name = "matplotlib" },
{ name = "motor" },
{ name = "networkx" },
{ name = "numexpr" },
{ name = "numpy" },
{ name = "pymongo" },
{ name = "pyppeteer" },
{ name = "pytest" },
{ name = "pytest-check-links" },
{ name = "redis" },
{ name = "scikit-learn" },
]
[[package]]
@@ -5769,6 +5772,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224 },
]
[[package]]
name = "sqlite-vec"
version = "0.1.6"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/ed/aabc328f29ee6814033d008ec43e44f2c595447d9cccd5f2aabe60df2933/sqlite_vec-0.1.6-py3-none-macosx_10_6_x86_64.whl", hash = "sha256:77491bcaa6d496f2acb5cc0d0ff0b8964434f141523c121e313f9a7d8088dee3", size = 164075 },
{ url = "https://files.pythonhosted.org/packages/a7/57/05604e509a129b22e303758bfa062c19afb020557d5e19b008c64016704e/sqlite_vec-0.1.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fdca35f7ee3243668a055255d4dee4dea7eed5a06da8cad409f89facf4595361", size = 165242 },
{ url = "https://files.pythonhosted.org/packages/f2/48/dbb2cc4e5bad88c89c7bb296e2d0a8df58aab9edc75853728c361eefc24f/sqlite_vec-0.1.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b0519d9cd96164cd2e08e8eed225197f9cd2f0be82cb04567692a0a4be02da3", size = 103704 },
{ url = "https://files.pythonhosted.org/packages/80/76/97f33b1a2446f6ae55e59b33869bed4eafaf59b7f4c662c8d9491b6a714a/sqlite_vec-0.1.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux1_x86_64.whl", hash = "sha256:823b0493add80d7fe82ab0fe25df7c0703f4752941aee1c7b2b02cec9656cb24", size = 151556 },
{ url = "https://files.pythonhosted.org/packages/6a/98/e8bc58b178266eae2fcf4c9c7a8303a8d41164d781b32d71097924a6bebe/sqlite_vec-0.1.6-py3-none-win_amd64.whl", hash = "sha256:c65bcfd90fa2f41f9000052bcb8bb75d38240b2dae49225389eca6c3136d3f0c", size = 281540 },
]
[[package]]
name = "sse-starlette"
version = "2.3.5"
@@ -511,12 +511,10 @@ class AsyncSqliteStore(AsyncBatchedBaseStore, BaseSqliteStore):
# Setup dot_product function if it doesn't exist
if embedding_requests and self.embeddings:
# Generate embeddings for search queries
vectors = await self.embeddings.aembed_documents(
[query for _, query in embedding_requests]
)
# Replace placeholders with actual embeddings
for (idx, _), embedding in zip(embedding_requests, vectors):
_params_list: list = queries[idx][1]
for i, param in enumerate(_params_list):
@@ -527,7 +525,7 @@ class AsyncSqliteStore(AsyncBatchedBaseStore, BaseSqliteStore):
await cur.execute(query, params)
rows = await cur.fetchall()
if "score" in query: # Vector search query
if "score" in query:
items = [
_row_to_search_item(
_decode_ns_text(row[0]),
@@ -579,5 +577,6 @@ class AsyncSqliteStore(AsyncBatchedBaseStore, BaseSqliteStore):
queries = self._get_batch_list_namespaces_queries(list_ops)
for (query, params), (idx, _) in zip(queries, list_ops):
await cur.execute(query, params)
rows = await cur.fetchall()
results[idx] = [_decode_ns_text(row[0]) for row in rows]
@@ -410,11 +410,12 @@ class BaseSqliteStore:
"json_extract(value, '$." + key + "') = " + str(value)
)
else:
# For complex objects, use param binding with JSON serialization
# Complex objects (list, dict, …) – compare JSON text
filter_conditions.append(
"json_extract(value, '$." + key + "') = ?"
)
filter_params.append(orjson.dumps(value))
# orjson.dumps returns bytes → decode to str so SQLite sees TEXT
filter_params.append(orjson.dumps(value).decode())
# Vector search branch
if op.query and self.index_config:
@@ -530,63 +531,95 @@ class BaseSqliteStore:
return queries, embedding_requests
def _get_batch_list_namespaces_queries(
self, list_ops: Sequence[tuple[int, ListNamespacesOp]]
self,
list_ops: Sequence[tuple[int, ListNamespacesOp]],
) -> list[tuple[str, Sequence]]:
queries: list[tuple[str, Sequence]] = []
for _, op in list_ops:
# In SQLite, we need to use a different approach for namespace segmentation
# since there's no direct equivalent to PostgreSQL's string aggregation
if op.max_depth is not None:
# SQLite doesn't have a built-in function for string splitting/joining with depth limit
# We'll use a more basic approach
query = """
WITH RECURSIVE split_prefix(prefix, remainder, depth) AS (
SELECT '', prefix || '.', 0 FROM (SELECT DISTINCT prefix FROM store)
UNION ALL
SELECT
CASE WHEN instr(remainder, '.') > 0
THEN prefix || CASE WHEN prefix = '' THEN '' ELSE '.' END || substr(remainder, 1, instr(remainder, '.') - 1)
ELSE prefix || CASE WHEN prefix = '' THEN '' ELSE '.' END || remainder
END,
CASE WHEN instr(remainder, '.') > 0
THEN substr(remainder, instr(remainder, '.') + 1)
ELSE ''
END,
depth + 1
FROM split_prefix
WHERE remainder != '' AND depth < ?
)
SELECT DISTINCT prefix FROM split_prefix WHERE depth > 0
"""
params: list[Any] = [op.max_depth]
else:
# If no max_depth is specified, we can just use a simpler query
query = "SELECT DISTINCT prefix FROM store"
params = []
conditions = []
for _, op in list_ops:
where_clauses: list[str] = []
params: list[Any] = []
if op.match_conditions:
for condition in op.match_conditions:
if condition.match_type == "prefix":
conditions.append("prefix LIKE ?")
for cond in op.match_conditions:
if cond.match_type == "prefix":
where_clauses.append("prefix LIKE ?")
params.append(
f"{_namespace_to_text(condition.path, handle_wildcards=True)}%"
f"{_namespace_to_text(cond.path, handle_wildcards=True)}%"
)
elif condition.match_type == "suffix":
conditions.append("prefix LIKE ?")
elif cond.match_type == "suffix":
where_clauses.append("prefix LIKE ?")
params.append(
f"%{_namespace_to_text(condition.path, handle_wildcards=True)}"
f"%{_namespace_to_text(cond.path, handle_wildcards=True)}"
)
else:
logger.warning(
f"Unknown match_type in list_namespaces: {condition.match_type}"
"Unknown match_type in list_namespaces: %s", cond.match_type
)
if conditions:
query += " WHERE " + " AND ".join(conditions)
where_sql = f"WHERE {' AND '.join(where_clauses)}" if where_clauses else ""
if op.max_depth is not None:
query = f"""
WITH RECURSIVE split(original, truncated, remainder, depth) AS (
SELECT
prefix AS original,
'' AS truncated,
prefix AS remainder,
0 AS depth
FROM (SELECT DISTINCT prefix FROM store {where_sql})
UNION ALL
SELECT
original,
CASE
WHEN depth = 0
THEN substr(remainder,
1,
CASE
WHEN instr(remainder, '.') > 0
THEN instr(remainder, '.') - 1
ELSE length(remainder)
END)
ELSE
truncated || '.' ||
substr(remainder,
1,
CASE
WHEN instr(remainder, '.') > 0
THEN instr(remainder, '.') - 1
ELSE length(remainder)
END)
END AS truncated,
CASE
WHEN instr(remainder, '.') > 0
THEN substr(remainder, instr(remainder, '.') + 1)
ELSE ''
END AS remainder,
depth + 1 AS depth
FROM split
WHERE remainder <> ''
AND depth < ?
)
SELECT DISTINCT truncated AS prefix
FROM split
WHERE depth = ? OR remainder = ''
ORDER BY prefix
LIMIT ? OFFSET ?
"""
params.extend([op.max_depth, op.max_depth, op.limit, op.offset])
else:
query = f"""
SELECT DISTINCT prefix
FROM store
{where_sql}
ORDER BY prefix
LIMIT ? OFFSET ?
"""
params.extend([op.limit, op.offset])
query += " ORDER BY prefix LIMIT ? OFFSET ?"
params.extend([op.limit, op.offset])
queries.append((query, tuple(params)))
return queries
@@ -828,342 +861,6 @@ class SqliteStore(BaseSqliteStore, BaseStore):
return results
def _prepare_batch_PUT_queries(
self, put_ops: Sequence[tuple[int, PutOp]]
) -> tuple[
list[tuple[str, Sequence]],
Optional[tuple[str, Sequence[tuple[str, str, str, str]]]],
]:
# Last-write wins
dedupped_ops: dict[tuple[tuple[str, ...], str], PutOp] = {}
for _, op in put_ops:
dedupped_ops[(op.namespace, op.key)] = op
inserts: list[PutOp] = []
deletes: list[PutOp] = []
for op in dedupped_ops.values():
if op.value is None:
deletes.append(op)
else:
inserts.append(op)
queries: list[tuple[str, Sequence]] = []
if deletes:
namespace_groups: dict[tuple[str, ...], list[str]] = defaultdict(list)
for op in deletes:
namespace_groups[op.namespace].append(op.key)
for namespace, keys in namespace_groups.items():
placeholders = ",".join(["?" for _ in keys])
query = (
f"DELETE FROM store WHERE prefix = ? AND key IN ({placeholders})"
)
params = (_namespace_to_text(namespace), *keys)
queries.append((query, params))
embedding_request: Optional[tuple[str, Sequence[tuple[str, str, str, str]]]] = (
None
)
if inserts:
values = []
insertion_params = []
vector_values = []
embedding_request_params = []
now = datetime.datetime.now(datetime.timezone.utc)
# First handle main store insertions
for op in inserts:
if op.ttl is None:
expires_at = None
else:
expires_at = now + datetime.timedelta(minutes=op.ttl)
values.append("(?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ?, ?)")
insertion_params.extend(
[
_namespace_to_text(op.namespace),
op.key,
orjson.dumps(cast(dict, op.value)),
expires_at,
op.ttl,
]
)
# Then handle embeddings if configured
if self.index_config:
for op in inserts:
if op.index is False:
continue
value = op.value
ns = _namespace_to_text(op.namespace)
k = op.key
if op.index is None:
paths = self.index_config["__tokenized_fields"]
else:
paths = [(ix, tokenize_path(ix)) for ix in op.index]
for path, tokenized_path in paths:
texts = get_text_at_path(value, tokenized_path)
for i, text in enumerate(texts):
pathname = f"{path}.{i}" if len(texts) > 1 else path
vector_values.append(
"(?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)"
)
embedding_request_params.append((ns, k, pathname, text))
values_str = ",".join(values)
query = f"""
INSERT OR REPLACE INTO store (prefix, key, value, created_at, updated_at, expires_at, ttl_minutes)
VALUES {values_str}
"""
queries.append((query, insertion_params))
if vector_values:
values_str = ",".join(vector_values)
query = f"""
INSERT OR REPLACE INTO store_vectors (prefix, key, field_name, embedding, created_at, updated_at)
VALUES {values_str}
"""
embedding_request = (query, embedding_request_params)
return queries, embedding_request
def _prepare_batch_search_queries(
self, search_ops: Sequence[tuple[int, SearchOp]]
) -> tuple[
list[tuple[str, list[Union[None, str, list[float]]]]], # queries, params
list[tuple[int, str]], # idx, query_text pairs to embed
]:
"""
Build per-SearchOp SQL queries (with optional TTL refresh) plus embedding requests.
Returns:
- queries: list of (SQL, param_list)
- embedding_requests: list of (original_index_in_search_ops, text_query)
"""
queries = []
embedding_requests = []
for idx, (_, op) in enumerate(search_ops):
# Build filter conditions first
filter_params = []
filter_conditions = []
if op.filter:
for key, value in op.filter.items():
if isinstance(value, dict):
for op_name, val in value.items():
condition, filter_params_ = self._get_filter_condition(
key, op_name, val
)
filter_conditions.append(condition)
filter_params.extend(filter_params_)
else:
# SQLite json_extract returns unquoted string values
if isinstance(value, str):
filter_conditions.append(
"json_extract(value, '$."
+ key
+ "') = '"
+ value.replace("'", "''")
+ "'"
)
elif value is None:
filter_conditions.append(
"json_extract(value, '$." + key + "') IS NULL"
)
elif isinstance(value, bool):
# SQLite JSON stores booleans as integers
filter_conditions.append(
"json_extract(value, '$."
+ key
+ "') = "
+ ("1" if value else "0")
)
elif isinstance(value, (int, float)):
filter_conditions.append(
"json_extract(value, '$." + key + "') = " + str(value)
)
else:
# For complex objects, use param binding with JSON serialization
filter_conditions.append(
"json_extract(value, '$." + key + "') = ?"
)
filter_params.append(orjson.dumps(value))
# Vector search branch
if op.query and self.index_config:
embedding_requests.append((idx, op.query))
# Choose the similarity function and score expression based on distance type
distance_type = self.index_config.get("distance_type", "cosine")
if distance_type == "cosine":
score_expr = "1.0 - vec_distance_cosine(sv.embedding, ?)"
elif distance_type == "l2":
score_expr = "vec_distance_L2(sv.embedding, ?)"
elif distance_type == "inner_product":
# For inner product, we want higher values to be better, so negate the result
# since inner product similarity is higher when vectors are more similar
score_expr = "-1 * vec_distance_L1(sv.embedding, ?)"
else:
# Default to cosine similarity
score_expr = "1.0 - vec_distance_cosine(sv.embedding, ?)"
filter_str = (
""
if not filter_conditions
else " AND " + " AND ".join(filter_conditions)
)
if op.namespace_prefix:
prefix_filter_str = f"WHERE s.prefix LIKE ? {filter_str} "
ns_args: Sequence = (f"{_namespace_to_text(op.namespace_prefix)}%",)
else:
ns_args = ()
if filter_str:
prefix_filter_str = f"WHERE {filter_str[5:]} "
else:
prefix_filter_str = ""
# We use a CTE to compute scores, with a SQLite-compatible approach for distinct results
base_query = f"""
WITH scored AS (
SELECT s.prefix, s.key, s.value, s.created_at, s.updated_at, s.expires_at, s.ttl_minutes,
{score_expr} AS score
FROM store s
JOIN store_vectors sv ON s.prefix = sv.prefix AND s.key = sv.key
{prefix_filter_str}
ORDER BY score DESC
LIMIT ?
),
ranked AS (
SELECT prefix, key, value, created_at, updated_at, expires_at, ttl_minutes, score,
ROW_NUMBER() OVER (PARTITION BY prefix, key ORDER BY score DESC) as rn
FROM scored
)
SELECT prefix, key, value, created_at, updated_at, expires_at, ttl_minutes, score
FROM ranked
WHERE rn = 1
ORDER BY score DESC
LIMIT ?
OFFSET ?
"""
params = [
_PLACEHOLDER, # Vector placeholder
*ns_args,
*filter_params,
op.limit * 2, # Expanded limit for better results
op.limit,
op.offset,
]
# Regular search branch (no vector search)
else:
base_query = """
SELECT prefix, key, value, created_at, updated_at, expires_at, ttl_minutes, NULL as score
FROM store
WHERE prefix LIKE ?
"""
params = [f"{_namespace_to_text(op.namespace_prefix)}%"]
if filter_conditions:
params.extend(filter_params)
base_query += " AND " + " AND ".join(filter_conditions)
base_query += " ORDER BY updated_at DESC"
base_query += " LIMIT ? OFFSET ?"
params.extend([op.limit, op.offset])
# Debug the query
logger.debug(f"Search query: {base_query}")
logger.debug(f"Search params: {params}")
# Handle TTL refresh if requested
if (
op.refresh_ttl
and self.ttl_config
and self.ttl_config.get("refresh_on_read", False)
):
final_sql = f"""
WITH search_results AS (
{base_query}
),
updated AS (
UPDATE store
SET expires_at = DATETIME(CURRENT_TIMESTAMP, '+' || ttl_minutes || ' minutes')
WHERE (prefix, key) IN (SELECT prefix, key FROM search_results)
AND ttl_minutes IS NOT NULL
)
SELECT * FROM search_results
"""
final_params = params[:] # copy params
else:
final_sql = base_query
final_params = params
queries.append((final_sql, final_params))
return queries, embedding_requests
def _get_batch_list_namespaces_queries(
self, list_ops: Sequence[tuple[int, ListNamespacesOp]]
) -> list[tuple[str, Sequence]]:
queries: list[tuple[str, Sequence]] = []
for _, op in list_ops:
# In SQLite, we need to use a different approach for namespace segmentation
# since there's no direct equivalent to PostgreSQL's string aggregation
if op.max_depth is not None:
# SQLite doesn't have a built-in function for string splitting/joining with depth limit
# We'll use a more basic approach
query = """
WITH RECURSIVE split_prefix(prefix, remainder, depth) AS (
SELECT '', prefix || '.', 0 FROM (SELECT DISTINCT prefix FROM store)
UNION ALL
SELECT
CASE WHEN instr(remainder, '.') > 0
THEN prefix || CASE WHEN prefix = '' THEN '' ELSE '.' END || substr(remainder, 1, instr(remainder, '.') - 1)
ELSE prefix || CASE WHEN prefix = '' THEN '' ELSE '.' END || remainder
END,
CASE WHEN instr(remainder, '.') > 0
THEN substr(remainder, instr(remainder, '.') + 1)
ELSE ''
END,
depth + 1
FROM split_prefix
WHERE remainder != '' AND depth < ?
)
SELECT DISTINCT prefix FROM split_prefix WHERE depth > 0
"""
params: list[Any] = [op.max_depth]
else:
# If no max_depth is specified, we can just use a simpler query
query = "SELECT DISTINCT prefix FROM store"
params = []
conditions = []
if op.match_conditions:
for condition in op.match_conditions:
if condition.match_type == "prefix":
conditions.append("prefix LIKE ?")
params.append(
f"{_namespace_to_text(condition.path, handle_wildcards=True)}%"
)
elif condition.match_type == "suffix":
conditions.append("prefix LIKE ?")
params.append(
f"%{_namespace_to_text(condition.path, handle_wildcards=True)}"
)
else:
logger.warning(
f"Unknown match_type in list_namespaces: {condition.match_type}"
)
if conditions:
query += " WHERE " + " AND ".join(conditions)
query += " ORDER BY prefix LIMIT ? OFFSET ?"
params.extend([op.limit, op.offset])
queries.append((query, tuple(params)))
return queries
def _get_filter_condition(self, key: str, op: str, value: Any) -> tuple[str, list]:
"""Helper to generate filter conditions."""
# We need to properly format values for SQLite JSON extraction comparison
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph-checkpoint-sqlite"
version = "2.0.8"
version = "2.0.10"
description = "Library with a SQLite implementation of LangGraph checkpoint saver."
authors = []
requires-python = ">=3.9"
@@ -2,6 +2,7 @@
import asyncio
import os
import tempfile
import uuid
from collections.abc import AsyncIterator, Generator, Iterable
from contextlib import asynccontextmanager
from typing import Optional, Union, cast
@@ -492,3 +493,227 @@ async def test_embed_with_path(
assert len(results) == 2
assert results[0].score < 0.9
assert results[1].score < 0.9
async def test_basic_store_ops(
fake_embeddings: CharacterEmbeddings,
) -> None:
"""Test vector search with specific text fields in SQLite store."""
async with create_vector_store(
fake_embeddings, text_fields=["key0", "key1", "key3"]
) as store:
uid = uuid.uuid4().hex
namespace = (uid, "test", "documents")
item_id = "doc1"
item_value = {"title": "Test Document", "content": "Hello, World!"}
results = await store.asearch((uid,))
assert len(results) == 0
await store.aput(namespace, item_id, item_value)
item = await store.aget(namespace, item_id)
assert item is not None
assert item.namespace == namespace
assert item.key == item_id
assert item.value == item_value
assert item.created_at is not None
assert item.updated_at is not None
updated_value = {
"title": "Updated Test Document",
"content": "Hello, LangGraph!",
}
await asyncio.sleep(1.01)
await store.aput(namespace, item_id, updated_value)
updated_item = await store.aget(namespace, item_id)
assert updated_item is not None
assert updated_item.value == updated_value
assert updated_item.updated_at > item.updated_at
different_namespace = (uid, "test", "other_documents")
item_in_different_namespace = await store.aget(different_namespace, item_id)
assert item_in_different_namespace is None
new_item_id = "doc2"
new_item_value = {"title": "Another Document", "content": "Greetings!"}
await store.aput(namespace, new_item_id, new_item_value)
items = await store.asearch((uid, "test"), limit=10)
assert len(items) == 2
assert any(item.key == item_id for item in items)
assert any(item.key == new_item_id for item in items)
namespaces = await store.alist_namespaces(prefix=(uid, "test"))
assert (uid, "test", "documents") in namespaces
await store.adelete(namespace, item_id)
await store.adelete(namespace, new_item_id)
deleted_item = await store.aget(namespace, item_id)
assert deleted_item is None
deleted_item = await store.aget(namespace, new_item_id)
assert deleted_item is None
empty_search_results = await store.asearch((uid, "test"), limit=10)
assert len(empty_search_results) == 0
async def test_list_namespaces(
fake_embeddings: CharacterEmbeddings,
) -> None:
"""Test list namespaces functionality with various filters."""
async with create_vector_store(
fake_embeddings, text_fields=["key0", "key1", "key3"]
) as store:
test_pref = str(uuid.uuid4())
test_namespaces = [
(test_pref, "test", "documents", "public", test_pref),
(test_pref, "test", "documents", "private", test_pref),
(test_pref, "test", "images", "public", test_pref),
(test_pref, "test", "images", "private", test_pref),
(test_pref, "prod", "documents", "public", test_pref),
(test_pref, "prod", "documents", "some", "nesting", "public", test_pref),
(test_pref, "prod", "documents", "private", test_pref),
]
# Add test data
for namespace in test_namespaces:
await store.aput(namespace, "dummy", {"content": "dummy"})
# Test prefix filtering
prefix_result = await store.alist_namespaces(prefix=(test_pref, "test"))
assert len(prefix_result) == 4
assert all(ns[1] == "test" for ns in prefix_result)
# Test specific prefix
specific_prefix_result = await store.alist_namespaces(
prefix=(test_pref, "test", "documents")
)
assert len(specific_prefix_result) == 2
assert all(ns[1:3] == ("test", "documents") for ns in specific_prefix_result)
# Test suffix filtering
suffix_result = await store.alist_namespaces(suffix=("public", test_pref))
assert len(suffix_result) == 4
assert all(ns[-2] == "public" for ns in suffix_result)
# Test combined prefix and suffix
prefix_suffix_result = await store.alist_namespaces(
prefix=(test_pref, "test"), suffix=("public", test_pref)
)
assert len(prefix_suffix_result) == 2
assert all(
ns[1] == "test" and ns[-2] == "public" for ns in prefix_suffix_result
)
# Test wildcard in prefix
wildcard_prefix_result = await store.alist_namespaces(
prefix=(test_pref, "*", "documents")
)
assert len(wildcard_prefix_result) == 5
assert all(ns[2] == "documents" for ns in wildcard_prefix_result)
# Test wildcard in suffix
wildcard_suffix_result = await store.alist_namespaces(
suffix=("*", "public", test_pref)
)
assert len(wildcard_suffix_result) == 4
assert all(ns[-2] == "public" for ns in wildcard_suffix_result)
wildcard_single = await store.alist_namespaces(
suffix=("some", "*", "public", test_pref)
)
assert len(wildcard_single) == 1
assert wildcard_single[0] == (
test_pref,
"prod",
"documents",
"some",
"nesting",
"public",
test_pref,
)
# Test max depth
max_depth_result = await store.alist_namespaces(max_depth=3)
assert all(len(ns) <= 3 for ns in max_depth_result)
max_depth_result = await store.alist_namespaces(
max_depth=4, prefix=(test_pref, "*", "documents")
)
assert len(set(res for res in max_depth_result)) == len(max_depth_result) == 5
# Test pagination
limit_result = await store.alist_namespaces(prefix=(test_pref,), limit=3)
assert len(limit_result) == 3
offset_result = await store.alist_namespaces(prefix=(test_pref,), offset=3)
assert len(offset_result) == len(test_namespaces) - 3
empty_prefix_result = await store.alist_namespaces(prefix=(test_pref,))
assert len(empty_prefix_result) == len(test_namespaces)
assert set(empty_prefix_result) == set(test_namespaces)
# Clean up
for namespace in test_namespaces:
await store.adelete(namespace, "dummy")
async def test_search_items(
fake_embeddings: CharacterEmbeddings,
) -> None:
"""Test search_items functionality by calling store methods directly."""
base = "test_search_items"
test_namespaces = [
(base, "documents", "user1"),
(base, "documents", "user2"),
(base, "reports", "department1"),
(base, "reports", "department2"),
]
test_items = [
{"title": "Doc 1", "author": "John Doe", "tags": ["important"]},
{"title": "Doc 2", "author": "Jane Smith", "tags": ["draft"]},
{"title": "Report A", "author": "John Doe", "tags": ["final"]},
{"title": "Report B", "author": "Alice Johnson", "tags": ["draft"]},
]
async with create_vector_store(
fake_embeddings, text_fields=["key0", "key1", "key3"]
) as store:
# Insert test data
for ns, item in zip(test_namespaces, test_items):
key = f"item_{ns[-1]}"
await store.aput(ns, key, item)
# 1. Search documents
docs = await store.asearch((base, "documents"))
assert len(docs) == 2
assert all(item.namespace[1] == "documents" for item in docs)
# 2. Search reports
reports = await store.asearch((base, "reports"))
assert len(reports) == 2
assert all(item.namespace[1] == "reports" for item in reports)
# 3. Pagination
first_page = await store.asearch((base,), limit=2, offset=0)
second_page = await store.asearch((base,), limit=2, offset=2)
assert len(first_page) == 2
assert len(second_page) == 2
keys_page1 = {item.key for item in first_page}
keys_page2 = {item.key for item in second_page}
assert keys_page1.isdisjoint(keys_page2)
all_items = await store.asearch((base,))
assert len(all_items) == 4
john_items = await store.asearch((base,), filter={"author": "John Doe"})
assert len(john_items) == 2
assert all(item.value["author"] == "John Doe" for item in john_items)
draft_items = await store.asearch((base,), filter={"tags": ["draft"]})
assert len(draft_items) == 2
assert all("draft" in item.value["tags"] for item in draft_items)
for ns in test_namespaces:
key = f"item_{ns[-1]}"
await store.adelete(ns, key)
+225
View File
@@ -2,6 +2,7 @@
import os
import re
import tempfile
import uuid
from collections.abc import Generator, Iterable
from contextlib import contextmanager
from typing import Any, Literal, Optional, Union, cast
@@ -822,3 +823,227 @@ def test_nonnull_migrations() -> None:
for migration in SqliteStore.MIGRATIONS:
statement = _leading_comment_remover.sub("", migration).split()[0]
assert statement.strip(), f"Empty migration statement found: {migration}"
def test_basic_store_operations(
fake_embeddings: CharacterEmbeddings,
) -> None:
"""Test basic store operations with SQLite store."""
with create_vector_store(
fake_embeddings, text_fields=["key0", "key1", "key3"]
) as store:
uid = uuid.uuid4().hex
namespace = (uid, "test", "documents")
item_id = "doc1"
item_value = {"title": "Test Document", "content": "Hello, World!"}
results = store.search((uid,))
assert len(results) == 0
store.put(namespace, item_id, item_value)
item = store.get(namespace, item_id)
assert item is not None
assert item.namespace == namespace
assert item.key == item_id
assert item.value == item_value
assert item.created_at is not None
assert item.updated_at is not None
updated_value = {
"title": "Updated Test Document",
"content": "Hello, LangGraph!",
}
store.put(namespace, item_id, updated_value)
updated_item = store.get(namespace, item_id)
assert updated_item is not None
assert updated_item.value == updated_value
assert updated_item.updated_at >= item.updated_at
different_namespace = (uid, "test", "other_documents")
item_in_different_namespace = store.get(different_namespace, item_id)
assert item_in_different_namespace is None
new_item_id = "doc2"
new_item_value = {"title": "Another Document", "content": "Greetings!"}
store.put(namespace, new_item_id, new_item_value)
items = store.search((uid, "test"), limit=10)
assert len(items) == 2
assert any(item.key == item_id for item in items)
assert any(item.key == new_item_id for item in items)
namespaces = store.list_namespaces(prefix=(uid, "test"))
assert (uid, "test", "documents") in namespaces
store.delete(namespace, item_id)
store.delete(namespace, new_item_id)
deleted_item = store.get(namespace, item_id)
assert deleted_item is None
deleted_item = store.get(namespace, new_item_id)
assert deleted_item is None
empty_search_results = store.search((uid, "test"), limit=10)
assert len(empty_search_results) == 0
def test_list_namespaces_operations(
fake_embeddings: CharacterEmbeddings,
) -> None:
"""Test list namespaces functionality with various filters."""
with create_vector_store(
fake_embeddings, text_fields=["key0", "key1", "key3"]
) as store:
test_pref = str(uuid.uuid4())
test_namespaces = [
(test_pref, "test", "documents", "public", test_pref),
(test_pref, "test", "documents", "private", test_pref),
(test_pref, "test", "images", "public", test_pref),
(test_pref, "test", "images", "private", test_pref),
(test_pref, "prod", "documents", "public", test_pref),
(test_pref, "prod", "documents", "some", "nesting", "public", test_pref),
(test_pref, "prod", "documents", "private", test_pref),
]
# Add test data
for namespace in test_namespaces:
store.put(namespace, "dummy", {"content": "dummy"})
# Test prefix filtering
prefix_result = store.list_namespaces(prefix=(test_pref, "test"))
assert len(prefix_result) == 4
assert all(ns[1] == "test" for ns in prefix_result)
# Test specific prefix
specific_prefix_result = store.list_namespaces(
prefix=(test_pref, "test", "documents")
)
assert len(specific_prefix_result) == 2
assert all(ns[1:3] == ("test", "documents") for ns in specific_prefix_result)
# Test suffix filtering
suffix_result = store.list_namespaces(suffix=("public", test_pref))
assert len(suffix_result) == 4
assert all(ns[-2] == "public" for ns in suffix_result)
# Test combined prefix and suffix
prefix_suffix_result = store.list_namespaces(
prefix=(test_pref, "test"), suffix=("public", test_pref)
)
assert len(prefix_suffix_result) == 2
assert all(
ns[1] == "test" and ns[-2] == "public" for ns in prefix_suffix_result
)
# Test wildcard in prefix
wildcard_prefix_result = store.list_namespaces(
prefix=(test_pref, "*", "documents")
)
assert len(wildcard_prefix_result) == 5
assert all(ns[2] == "documents" for ns in wildcard_prefix_result)
# Test wildcard in suffix
wildcard_suffix_result = store.list_namespaces(
suffix=("*", "public", test_pref)
)
assert len(wildcard_suffix_result) == 4
assert all(ns[-2] == "public" for ns in wildcard_suffix_result)
wildcard_single = store.list_namespaces(
suffix=("some", "*", "public", test_pref)
)
assert len(wildcard_single) == 1
assert wildcard_single[0] == (
test_pref,
"prod",
"documents",
"some",
"nesting",
"public",
test_pref,
)
# Test max depth
max_depth_result = store.list_namespaces(max_depth=3)
assert all(len(ns) <= 3 for ns in max_depth_result)
max_depth_result = store.list_namespaces(
max_depth=4, prefix=(test_pref, "*", "documents")
)
assert len(set(res for res in max_depth_result)) == len(max_depth_result) == 5
# Test pagination
limit_result = store.list_namespaces(prefix=(test_pref,), limit=3)
assert len(limit_result) == 3
offset_result = store.list_namespaces(prefix=(test_pref,), offset=3)
assert len(offset_result) == len(test_namespaces) - 3
empty_prefix_result = store.list_namespaces(prefix=(test_pref,))
assert len(empty_prefix_result) == len(test_namespaces)
assert set(empty_prefix_result) == set(test_namespaces)
# Clean up
for namespace in test_namespaces:
store.delete(namespace, "dummy")
def test_search_items(
fake_embeddings: CharacterEmbeddings,
) -> None:
"""Test search_items functionality by calling store methods directly."""
base = "test_search_items"
test_namespaces = [
(base, "documents", "user1"),
(base, "documents", "user2"),
(base, "reports", "department1"),
(base, "reports", "department2"),
]
test_items = [
{"title": "Doc 1", "author": "John Doe", "tags": ["important"]},
{"title": "Doc 2", "author": "Jane Smith", "tags": ["draft"]},
{"title": "Report A", "author": "John Doe", "tags": ["final"]},
{"title": "Report B", "author": "Alice Johnson", "tags": ["draft"]},
]
with create_vector_store(
fake_embeddings, text_fields=["key0", "key1", "key3"]
) as store:
# Insert test data
for ns, item in zip(test_namespaces, test_items):
key = f"item_{ns[-1]}"
store.put(ns, key, item)
# 1. Search documents
docs = store.search((base, "documents"))
assert len(docs) == 2
assert all(item.namespace[1] == "documents" for item in docs)
# 2. Search reports
reports = store.search((base, "reports"))
assert len(reports) == 2
assert all(item.namespace[1] == "reports" for item in reports)
# 3. Pagination
first_page = store.search((base,), limit=2, offset=0)
second_page = store.search((base,), limit=2, offset=2)
assert len(first_page) == 2
assert len(second_page) == 2
keys_page1 = {item.key for item in first_page}
keys_page2 = {item.key for item in second_page}
assert keys_page1.isdisjoint(keys_page2)
all_items = store.search((base,))
assert len(all_items) == 4
john_items = store.search((base,), filter={"author": "John Doe"})
assert len(john_items) == 2
assert all(item.value["author"] == "John Doe" for item in john_items)
draft_items = store.search((base,), filter={"tags": ["draft"]})
assert len(draft_items) == 2
assert all("draft" in item.value["tags"] for item in draft_items)
for ns in test_namespaces:
key = f"item_{ns[-1]}"
store.delete(ns, key)
+1 -1
View File
@@ -346,7 +346,7 @@ dev = [
[[package]]
name = "langgraph-checkpoint-sqlite"
version = "2.0.8"
version = "2.0.10"
source = { editable = "." }
dependencies = [
{ name = "aiosqlite" },
+1
View File
@@ -304,6 +304,7 @@ class StateGraph(Graph):
If a string is provided, it will be used as the node name, and action will be used as the function or runnable.
action: The action associated with the node. (default: None)
Will be used as the node function or runnable if `node` is a string (node name).
defer: Whether to defer the execution of the node until the run is about to end.
metadata: The metadata associated with the node. (default: None)
input: The input schema for the node. (default: the graph's input schema)
retry: The policy for retrying the node. (default: None)
+3 -12
View File
@@ -85,7 +85,6 @@ from langgraph.pregel.algo import (
PregelTaskWrites,
apply_writes,
local_read,
local_write,
prepare_next_tasks,
)
from langgraph.pregel.call import identifier
@@ -412,7 +411,7 @@ class Pregel(PregelProtocol):
def reducer(current, update):
if current:
return current + " | " + "update"
return current + " | " + update
else:
return update
@@ -1684,11 +1683,7 @@ class Pregel(PregelProtocol):
run_name=self.name + "UpdateState",
configurable={
# deque.extend is thread-safe
CONFIG_KEY_SEND: partial(
local_write,
writes.extend,
self.nodes.keys(),
),
CONFIG_KEY_SEND: writes.extend,
CONFIG_KEY_READ: partial(
local_read,
channels,
@@ -2111,11 +2106,7 @@ class Pregel(PregelProtocol):
run_name=self.name + "UpdateState",
configurable={
# deque.extend is thread-safe
CONFIG_KEY_SEND: partial(
local_write,
writes.extend,
self.nodes.keys(),
),
CONFIG_KEY_SEND: writes.extend,
CONFIG_KEY_READ: partial(
local_read,
channels,
+3 -32
View File
@@ -64,7 +64,6 @@ from langgraph.constants import (
TASKS,
Send,
)
from langgraph.errors import InvalidUpdateError
from langgraph.managed.base import ManagedValueMapping
from langgraph.pregel.call import get_runnable_for_task, identifier
from langgraph.pregel.io import read_channels
@@ -212,22 +211,6 @@ def local_read(
return values
def local_write(
commit: Callable[[Sequence[tuple[str, Any]]], None],
process_keys: Iterable[str],
writes: Sequence[tuple[str, Any]],
) -> None:
"""Function injected under CONFIG_KEY_SEND in task config, to write to channels.
Validates writes and forwards them to `commit` function."""
for chan, value in writes:
if chan in (PUSH, TASKS) and value is not None:
if not isinstance(value, Send):
raise InvalidUpdateError(f"Expected Send, got {value}")
if value.node not in process_keys:
raise InvalidUpdateError(f"Invalid node name {value.node} in packet")
commit(writes)
def increment(current: Optional[int], channel: BaseChannel) -> int:
"""Default channel versioning function, increments the current int version."""
return current + 1 if current is not None else 1
@@ -626,11 +609,7 @@ def prepare_single_task(
configurable={
CONFIG_KEY_TASK_ID: task_id,
# deque.extend is thread-safe
CONFIG_KEY_SEND: partial(
local_write,
writes.extend,
processes.keys(),
),
CONFIG_KEY_SEND: writes.extend,
CONFIG_KEY_READ: partial(
local_read,
channels,
@@ -750,11 +729,7 @@ def prepare_single_task(
configurable={
CONFIG_KEY_TASK_ID: task_id,
# deque.extend is thread-safe
CONFIG_KEY_SEND: partial(
local_write,
writes.extend,
processes.keys(),
),
CONFIG_KEY_SEND: writes.extend,
CONFIG_KEY_READ: partial(
local_read,
channels,
@@ -888,11 +863,7 @@ def prepare_single_task(
configurable={
CONFIG_KEY_TASK_ID: task_id,
# deque.extend is thread-safe
CONFIG_KEY_SEND: partial(
local_write,
writes.extend,
tuple(processes.keys()),
),
CONFIG_KEY_SEND: writes.extend,
CONFIG_KEY_READ: partial(
local_read,
channels,
+4 -2
View File
@@ -1083,7 +1083,8 @@ class SyncPregelLoop(PregelLoop, AbstractContextManager):
self, task: PregelExecutableTask, write_idx: int, call: Optional[Call] = None
) -> Optional[PregelExecutableTask]:
if pushed := super().accept_push(task, write_idx, call):
self.match_cached_writes()
for task in self.match_cached_writes():
self.output_writes(task.id, task.writes, cached=True)
return pushed
def put_writes(self, task_id: str, writes: WritesT) -> None:
@@ -1279,7 +1280,8 @@ class AsyncPregelLoop(PregelLoop, AbstractAsyncContextManager):
self, task: PregelExecutableTask, write_idx: int, call: Optional[Call] = None
) -> Optional[PregelExecutableTask]:
if pushed := super().accept_push(task, write_idx, call):
await self.amatch_cached_writes()
for task in await self.amatch_cached_writes():
self.output_writes(task.id, task.writes, cached=True)
return pushed
def put_writes(self, task_id: str, writes: WritesT) -> None:
+3 -1
View File
@@ -1,4 +1,5 @@
version = 1
revision = 1
requires-python = ">=3.9"
resolution-markers = [
"python_full_version >= '3.13' and python_full_version < '4.0'",
@@ -1365,7 +1366,7 @@ dev = [
[[package]]
name = "langgraph-checkpoint-sqlite"
version = "2.0.8"
version = "2.0.10"
source = { editable = "../checkpoint-sqlite" }
dependencies = [
{ name = "aiosqlite" },
@@ -1441,6 +1442,7 @@ dev = [
{ name = "pytest-mock" },
{ name = "pytest-watcher" },
{ name = "ruff" },
{ name = "syrupy" },
]
[[package]]
@@ -247,6 +247,7 @@ def create_react_agent(
Union[StructuredResponseSchema, tuple[str, StructuredResponseSchema]]
] = None,
pre_model_hook: Optional[RunnableLike] = None,
post_model_hook: Optional[RunnableLike] = None,
state_schema: Optional[StateSchemaType] = None,
config_schema: Optional[Type[Any]] = None,
checkpointer: Optional[Checkpointer] = None,
@@ -321,6 +322,12 @@ def create_react_agent(
...
}
```
post_model_hook: An optional node to add after the `agent` node (i.e., the node that calls the LLM).
Useful for implementing human-in-the-loop, guardrails, validation, or other post-processing.
Post-model hook must be a callable or a runnable that takes in current graph state and returns a state update.
!!! Note
Only available with `version="v2"`.
state_schema: An optional state schema that defines graph state.
Must have `messages` and `remaining_steps` keys.
Defaults to `AgentState` that defines those two keys.
@@ -591,6 +598,10 @@ def create_react_agent(
workflow.set_entry_point(entrypoint)
if post_model_hook is not None:
workflow.add_node("post_model_hook", post_model_hook)
workflow.add_edge("agent", "post_model_hook")
if response_format is not None:
workflow.add_node(
"generate_structured_response",
@@ -598,7 +609,10 @@ def create_react_agent(
generate_structured_response, agenerate_structured_response
),
)
workflow.add_edge("agent", "generate_structured_response")
if post_model_hook is not None:
workflow.add_edge("post_model_hook", "generate_structured_response")
else:
workflow.add_edge("agent", "generate_structured_response")
return workflow.compile(
checkpointer=checkpointer,
@@ -610,17 +624,24 @@ def create_react_agent(
)
# Define the function that determines whether to continue or not
def should_continue(state: StateSchema) -> Union[str, list]:
def should_continue(state: StateSchema) -> Union[str, list[Send]]:
messages = _get_state_value(state, "messages")
last_message = messages[-1]
# If there is no function call, then we finish
if not isinstance(last_message, AIMessage) or not last_message.tool_calls:
return END if response_format is None else "generate_structured_response"
if post_model_hook is not None:
return "post_model_hook"
elif response_format is not None:
return "generate_structured_response"
else:
return END
# Otherwise if there is, we continue
else:
if version == "v1":
return "tools"
elif version == "v2":
if post_model_hook is not None:
return "post_model_hook"
tool_calls = [
tool_node.inject_tool_args(call, state, store) # type: ignore[arg-type]
for call in last_message.tool_calls
@@ -649,6 +670,17 @@ def create_react_agent(
# This means that this node is the first one called
workflow.set_entry_point(entrypoint)
agent_paths = []
post_model_hook_paths = [entrypoint, "tools"]
# Add a post model hook node if post_model_hook is provided
if post_model_hook is not None:
workflow.add_node("post_model_hook", post_model_hook)
agent_paths.append("post_model_hook")
workflow.add_edge("agent", "post_model_hook")
else:
agent_paths.append("tools")
# Add a structured output node if response_format is provided
if response_format is not None:
workflow.add_node(
@@ -657,19 +689,57 @@ def create_react_agent(
generate_structured_response, agenerate_structured_response
),
)
workflow.add_edge("generate_structured_response", END)
should_continue_destinations = ["tools", "generate_structured_response"]
if post_model_hook is not None:
post_model_hook_paths.append("generate_structured_response")
else:
agent_paths.append("generate_structured_response")
else:
should_continue_destinations = ["tools", END]
if post_model_hook is not None:
post_model_hook_paths.append(END)
else:
agent_paths.append(END)
if post_model_hook is not None:
def post_model_hook_router(state: StateSchema) -> Union[str, list[Send]]:
"""Route to the next node after post_model_hook.
Routes to one of:
* "tools": if there are pending tool calls without a corresponding message.
* "generate_structured_response": if no pending tool calls exist and response_format is specified.
* END: if no pending tool calls exist and no response_format is specified.
"""
messages = _get_state_value(state, "messages")
tool_messages = [
m.tool_call_id for m in messages if isinstance(m, ToolMessage)
]
last_ai_message = next(
m for m in reversed(messages) if isinstance(m, AIMessage)
)
pending_tool_calls = [
c for c in last_ai_message.tool_calls if c["id"] not in tool_messages
]
if pending_tool_calls:
return [Send("tools", [tool_call]) for tool_call in pending_tool_calls]
elif isinstance(messages[-1], ToolMessage):
return entrypoint
elif response_format is not None:
return "generate_structured_response"
else:
return END
workflow.add_conditional_edges(
"post_model_hook",
post_model_hook_router, # type: ignore[arg-type]
path_map=post_model_hook_paths,
)
# We now add a conditional edge
workflow.add_conditional_edges(
# First, we define the start node. We use `agent`.
# This means these are the edges taken after the `agent` node is called.
"agent",
# Next, we pass in the function that will determine which node is called next.
should_continue,
path_map=should_continue_destinations,
should_continue, # type: ignore[arg-type]
path_map=agent_paths,
)
def route_tool_responses(state: StateSchema) -> str:
+162 -5
View File
@@ -1,11 +1,12 @@
from typing import (
Literal,
Optional,
Union,
)
from copy import deepcopy
from typing import Any, Literal, Optional, Union, cast
from langchain_core.messages import ToolCall, ToolMessage
from typing_extensions import TypedDict
from langgraph.types import Command, interrupt
from langgraph.utils.runnable import RunnableCallable
class HumanInterruptConfig(TypedDict):
"""Configuration that defines what actions are allowed for a human interrupt.
@@ -92,3 +93,159 @@ class HumanResponse(TypedDict):
type: Literal["accept", "ignore", "response", "edit"]
args: Union[None, str, ActionRequest]
class InterruptToolNode(RunnableCallable):
"""Prebuilt post model hook node used to enable common patterns for tool interrupts.
For any tools with specified policies, an interrupt will be raised when the LLM returns
a tool call for said tool. The interrupt policy will be used to determine what sort of resume logic is allowed.
Any of the following resume patterns are supported:
* accept: the tool call is executed as planned
* edit: the args for the tool call are edited and then the tool call is executed
* response: text response/feedback is fed back into the LLM
* ignore: the current tool call is ignored / skipped
Args:
**interrupt_policy: a mapping of tool names to [`HumanInterruptConfig`][prebuilt.interrupt.HumanInterruptConfig] dictionaries
specifying which interrupt patterns to enable for said tool.
Example:
```python
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.prebuilt.interrupt import HumanInterruptConfig, InterruptToolNode
from langgraph.types import Command
def book_hotel(hotel_name: str) -> str:
'''Book a room at the provided hotel.'''
# Some hotel API calls, a sensitive / expensive operation
return f"Booked a hotel at {hotel_name}."
agent = create_react_agent(
"openai:gpt-4.1",
tools=[book_hotel],
prompt="You are a hotel booking assistant.",
post_model_hook=InterruptToolNode(
book_hotel=HumanInterruptConfig(
allow_accept=True,
allow_edit=True,
allow_ignore=True,
allow_respond=True,
)
),
checkpointer=InMemorySaver(),
)
config = {"configurable": {"thread_id": 1}}
response = agent.invoke(
{"messages": [{"role": "user", "content": "please book a hotel at the hilton inn in boston."}]},
config=config,
)
response = agent.invoke(Command(resume={"type": "accept"}), config=config)
```
"""
def __init__(self, **interrupt_policy: HumanInterruptConfig):
super().__init__(self._func, self._afunc)
self.interrupt_policy = interrupt_policy
def _interrupt(
self,
tool_call: ToolCall,
interrupt_config: HumanInterruptConfig,
) -> Union[ToolCall, ToolMessage]:
"""Interrupt before a tool call and ask for human input."""
call_id = tool_call["id"]
tool_name = tool_call["name"]
request = HumanInterrupt(
action_request=ActionRequest(
action=tool_name,
args=tool_call["args"],
),
config=interrupt_config,
description=f"Please review tool call for `{tool_name}` before execution.",
)
response = interrupt([request])
# resume provided by agent inbox as a list
response = response[0] if isinstance(response, list) else response
try:
response_type = response.get("type")
except AttributeError:
raise TypeError(
f"Unexpected resume value: {response}."
f"Expected a dict with `'type'` key."
)
if response_type == "accept" and interrupt_config["allow_accept"]:
return tool_call
elif response_type == "edit" and interrupt_config["allow_edit"]:
return ToolCall(
args=cast(ActionRequest, response)["args"]["args"],
name=tool_name,
id=call_id,
type="tool_call",
)
elif response_type == "response" and interrupt_config["allow_respond"]:
return ToolMessage(
content=cast(str, response["args"]),
name=tool_name,
tool_call_id=call_id,
status="error",
)
elif response_type == "ignore" and interrupt_config["allow_ignore"]:
return ToolMessage(
content=f"User ignored the tool call for `{tool_name}` with id {call_id}",
name=tool_name,
tool_call_id=call_id,
status="success",
)
allowed_types = [
type_name
for type_name, is_allowed in {
"accept": interrupt_config["allow_accept"],
"edit": interrupt_config["allow_edit"],
"response": interrupt_config["allow_respond"],
"ignore": interrupt_config["allow_ignore"],
}.items()
if is_allowed
]
raise ValueError(
f"Unexpected human response: {response}. "
f"Expected one with `'type'` in {allowed_types} based on {tool_name}'s interrupt configuration."
)
def _func(self, input: dict[str, Any]) -> Command:
ai_msg = input["messages"][-1]
tool_calls: list[ToolCall] = deepcopy(ai_msg.tool_calls) or []
tool_messages: list[ToolMessage] = []
for idx, tool_call in enumerate(tool_calls):
if interrupt_config := self.interrupt_policy.get(tool_call["name"]):
interrupt_result = self._interrupt(
tool_call=tool_call, interrupt_config=interrupt_config
)
if isinstance(interrupt_result, ToolMessage):
tool_messages.append(interrupt_result)
else:
tool_calls[idx] = interrupt_result
updated_ai_msg = ai_msg.copy(update={"tool_calls": tool_calls})
# conditional routing logic for post_model_hook will direct to the tools node
# or agent node depending on if there are pending tool calls
return {"messages": [updated_ai_msg, *tool_messages]}
async def _afunc(self, input: dict[str, Any]) -> Command:
return self._func(input)
+10 -7
View File
@@ -431,22 +431,25 @@ class ToolNode(RunnableCallable):
return tool_calls, input_type
else:
input_type = "list"
message: AnyMessage = input[-1]
messages = input
elif isinstance(input, dict) and (messages := input.get(self.messages_key, [])):
input_type = "dict"
message = messages[-1]
elif messages := getattr(input, self.messages_key, None):
elif messages := getattr(input, self.messages_key, []):
# Assume dataclass-like state that can coerce from dict
input_type = "dict"
message = messages[-1]
else:
raise ValueError("No message found in input")
if not isinstance(message, AIMessage):
raise ValueError("Last message is not an AIMessage")
try:
latest_ai_message = next(
m for m in reversed(messages) if isinstance(m, AIMessage)
)
except StopIteration:
raise ValueError("No AIMessage found in input")
tool_calls = [
self.inject_tool_args(call, input, store) for call in message.tool_calls
self.inject_tool_args(call, input, store)
for call in latest_ai_message.tool_calls
]
return tool_calls, input_type
+1
View File
@@ -32,6 +32,7 @@ dev = [
"langgraph-checkpoint",
"langgraph-checkpoint-sqlite",
"langgraph-checkpoint-postgres",
"syrupy",
]
[tool.uv]
@@ -0,0 +1,173 @@
# serializer version: 1
# name: test_react_agent_graph_structure[None-None-None-tools0]
'''
graph TD;
__start__ --> agent;
agent --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[None-None-None-tools1]
'''
graph TD;
__start__ --> agent;
agent -.-> __end__;
agent -.-> tools;
tools --> agent;
'''
# ---
# name: test_react_agent_graph_structure[None-None-pre_model_hook-tools0]
'''
graph TD;
__start__ --> pre_model_hook;
pre_model_hook --> agent;
agent --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[None-None-pre_model_hook-tools1]
'''
graph TD;
__start__ --> pre_model_hook;
agent -.-> __end__;
agent -.-> tools;
pre_model_hook --> agent;
tools --> pre_model_hook;
'''
# ---
# name: test_react_agent_graph_structure[None-post_model_hook-None-tools0]
'''
graph TD;
__start__ --> agent;
agent --> post_model_hook;
post_model_hook --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[None-post_model_hook-None-tools1]
'''
graph TD;
__start__ --> agent;
agent --> post_model_hook;
post_model_hook -.-> __end__;
post_model_hook -.-> agent;
post_model_hook -.-> tools;
tools --> agent;
'''
# ---
# name: test_react_agent_graph_structure[None-post_model_hook-pre_model_hook-tools0]
'''
graph TD;
__start__ --> pre_model_hook;
agent --> post_model_hook;
pre_model_hook --> agent;
post_model_hook --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[None-post_model_hook-pre_model_hook-tools1]
'''
graph TD;
__start__ --> pre_model_hook;
agent --> post_model_hook;
post_model_hook -.-> __end__;
post_model_hook -.-> pre_model_hook;
post_model_hook -.-> tools;
pre_model_hook --> agent;
tools --> pre_model_hook;
'''
# ---
# name: test_react_agent_graph_structure[ResponseFormat-None-None-tools0]
'''
graph TD;
__start__ --> agent;
agent --> generate_structured_response;
generate_structured_response --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[ResponseFormat-None-None-tools1]
'''
graph TD;
__start__ --> agent;
agent -.-> generate_structured_response;
agent -.-> tools;
tools --> agent;
generate_structured_response --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[ResponseFormat-None-pre_model_hook-tools0]
'''
graph TD;
__start__ --> pre_model_hook;
agent --> generate_structured_response;
pre_model_hook --> agent;
generate_structured_response --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[ResponseFormat-None-pre_model_hook-tools1]
'''
graph TD;
__start__ --> pre_model_hook;
agent -.-> generate_structured_response;
agent -.-> tools;
pre_model_hook --> agent;
tools --> pre_model_hook;
generate_structured_response --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[ResponseFormat-post_model_hook-None-tools0]
'''
graph TD;
__start__ --> agent;
agent --> post_model_hook;
post_model_hook --> generate_structured_response;
generate_structured_response --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[ResponseFormat-post_model_hook-None-tools1]
'''
graph TD;
__start__ --> agent;
agent --> post_model_hook;
post_model_hook -.-> agent;
post_model_hook -.-> generate_structured_response;
post_model_hook -.-> tools;
tools --> agent;
generate_structured_response --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[ResponseFormat-post_model_hook-pre_model_hook-tools0]
'''
graph TD;
__start__ --> pre_model_hook;
agent --> post_model_hook;
post_model_hook --> generate_structured_response;
pre_model_hook --> agent;
generate_structured_response --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[ResponseFormat-post_model_hook-pre_model_hook-tools1]
'''
graph TD;
__start__ --> pre_model_hook;
agent --> post_model_hook;
post_model_hook -.-> generate_structured_response;
post_model_hook -.-> pre_model_hook;
post_model_hook -.-> tools;
pre_model_hook --> agent;
tools --> pre_model_hook;
generate_structured_response --> __end__;
'''
# ---
@@ -0,0 +1,191 @@
import pytest
from langchain_core.messages import ToolMessage
from langchain_core.runnables import RunnableConfig
from langgraph.checkpoint.base import BaseCheckpointSaver
from langgraph.prebuilt import create_react_agent
from langgraph.prebuilt.interrupt import HumanInterruptConfig, InterruptToolNode
from langgraph.types import Command
from tests.model import FakeToolCallingModel
def hello_tool(name: str) -> str:
"""Return a greeting for the provided person."""
return f"Hello, {name}!"
post_model_hook = InterruptToolNode(
hello_tool=HumanInterruptConfig(
allow_accept=True,
allow_edit=True,
allow_ignore=True,
allow_respond=True,
)
)
default_model = FakeToolCallingModel(
tool_calls=[
[
{
"name": "hello_tool",
"args": {"name": "lady gaga"},
"id": "some-random-id",
}
]
]
)
def test_interrupt_surfaced(
request: pytest.FixtureRequest,
sync_checkpointer: BaseCheckpointSaver,
) -> None:
agent = create_react_agent(
default_model,
[hello_tool],
checkpointer=sync_checkpointer,
post_model_hook=post_model_hook,
)
config: RunnableConfig = {"configurable": {"thread_id": "1"}}
result = agent.invoke({"messages": [("user", "Say hi to lady gaga!")]}, config)
interrupt_data = result["__interrupt__"]
assert interrupt_data[0].value == [
{
"action_request": {"action": "hello_tool", "args": {"name": "lady gaga"}},
"config": {
"allow_accept": True,
"allow_edit": True,
"allow_ignore": True,
"allow_respond": True,
},
"description": "Please review tool call for `hello_tool` before execution.",
}
]
response = agent.invoke(Command(resume={"type": "accept"}), config=config)
tool_message: ToolMessage = response["messages"][-2]
assert tool_message.content == "Hello, lady gaga!"
assert tool_message.name == "hello_tool"
@pytest.mark.parametrize(
"resume, expected_content",
[
({"type": "accept"}, "Hello, lady gaga!"),
(
{"type": "ignore"},
"User ignored the tool call for `hello_tool` with id some-random-id",
),
(
{
"type": "edit",
"args": {"action": "hello_tool", "args": {"name": "bruno mars"}},
},
"Hello, bruno mars!",
),
],
)
def test_interrupt_resume_variants(
request: pytest.FixtureRequest,
sync_checkpointer: BaseCheckpointSaver,
resume: dict,
expected_content: str,
) -> None:
agent = create_react_agent(
default_model,
[hello_tool],
checkpointer=sync_checkpointer,
post_model_hook=post_model_hook,
)
config: RunnableConfig = {"configurable": {"thread_id": "1"}}
agent.invoke({"messages": [("user", "Say hi to lady gaga!")]}, config)
response = agent.invoke(Command(resume=resume), config=config)
tool_message: ToolMessage = response["messages"][-2]
assert tool_message.name == "hello_tool"
assert tool_message.content == expected_content
if resume["type"] == "edit":
ai_msg = response["messages"][-1]
assert ai_msg.tool_calls == [
{
"name": "hello_tool",
"args": {"name": "lady gaga"},
"id": "some-random-id",
"type": "tool_call",
}
]
def test_resume_with_response(
request: pytest.FixtureRequest,
sync_checkpointer: BaseCheckpointSaver,
) -> None:
model = FakeToolCallingModel(
tool_calls=[
[
{
"name": "hello_tool",
"args": {"name": "lady gaga"},
"id": "some-random-id",
}
],
[
{
"name": "hello_tool",
"args": {"name": "bruno mars"},
"id": "some-random-id-2",
}
],
]
)
agent = create_react_agent(
model,
[hello_tool],
checkpointer=sync_checkpointer,
post_model_hook=post_model_hook,
)
config: RunnableConfig = {"configurable": {"thread_id": "1"}}
agent.invoke({"messages": [("user", "Say hi to lady gaga!")]}, config)
# Provide user response
agent.invoke(
Command(
resume={
"type": "response",
"args": "actually, please say hello to bruno mars",
}
),
config=config,
)
# Accept the updated call
response = agent.invoke(Command(resume={"type": "accept"}), config=config)
assert len(response["messages"]) == 6
tool_message: ToolMessage = response["messages"][-2]
assert tool_message.name == "hello_tool"
assert tool_message.content == "Hello, bruno mars!"
def test_resume_with_type_not_allowed(sync_checkpointer: BaseCheckpointSaver) -> None:
agent = create_react_agent(
default_model,
[hello_tool],
checkpointer=sync_checkpointer,
post_model_hook=post_model_hook,
)
config: RunnableConfig = {"configurable": {"thread_id": "1"}}
agent.invoke({"messages": [("user", "Say hi to lady gaga!")]}, config)
with pytest.raises(ValueError) as exc_info:
agent.invoke(Command(resume={"type": "not-allowed"}), config=config)
assert (
str(exc_info.value)
== "Unexpected human response: {'type': 'not-allowed'}. Expected one with `'type'` in ['accept', 'edit', 'response', 'ignore'] based on hello_tool's interrupt configuration."
)
+141
View File
@@ -1399,3 +1399,144 @@ def test_pre_model_hook() -> None:
AIMessage(content="Hello!", id="1"),
]
}
def test_post_model_hook() -> None:
class FlagState(AgentState):
flag: bool
model = FakeToolCallingModel(tool_calls=[])
def post_model_hook(state: FlagState) -> dict[str, bool]:
return {"flag": True}
pmh_agent = create_react_agent(
model, [], post_model_hook=post_model_hook, state_schema=FlagState
)
assert "post_model_hook" in pmh_agent.nodes
result = pmh_agent.invoke({"messages": [HumanMessage("hi?")], "flag": False})
assert result["flag"] is True
events = list(pmh_agent.stream({"messages": [HumanMessage("hi?")], "flag": False}))
assert events == [
{
"agent": {
"messages": [
AIMessage(
content="hi?",
additional_kwargs={},
response_metadata={},
id="1",
)
]
}
},
{"post_model_hook": {"flag": True}},
]
def test_post_model_hook_with_structured_output() -> None:
class WeatherResponse(BaseModel):
temperature: float = Field(description="The temperature in fahrenheit")
tool_calls = [[{"args": {}, "id": "1", "name": "get_weather"}]]
def get_weather():
"""Get the weather"""
return "The weather is sunny and 75°F."
expected_structured_response = WeatherResponse(temperature=75)
model = FakeToolCallingModel(
tool_calls=tool_calls, structured_response=expected_structured_response
)
class State(AgentState):
flag: bool
structured_response: WeatherResponse
def post_model_hook(state: State) -> Union[dict[str, bool], Command]:
return {"flag": True}
agent = create_react_agent(
model,
[get_weather],
response_format=WeatherResponse,
post_model_hook=post_model_hook,
state_schema=State,
)
assert "post_model_hook" in agent.nodes
assert "generate_structured_response" in agent.nodes
response = agent.invoke(
{"messages": [HumanMessage("What's the weather?")], "flag": False}
)
assert response["flag"] is True
assert response["structured_response"] == expected_structured_response
events = list(
agent.stream({"messages": [HumanMessage("What's the weather?")], "flag": False})
)
assert "generate_structured_response" in events[-1]
assert events == [
{
"agent": {
"messages": [
AIMessage(
content="What's the weather?",
additional_kwargs={},
response_metadata={},
id="2",
tool_calls=[
{
"name": "get_weather",
"args": {},
"id": "1",
"type": "tool_call",
}
],
)
]
}
},
{"post_model_hook": {"flag": True}},
{
"tools": {
"messages": [
_AnyIdToolMessage(
content="The weather is sunny and 75°F.",
name="get_weather",
tool_call_id="1",
),
]
}
},
{
"agent": {
"messages": [
AIMessage(
content="What's the weather?-What's the weather?-The weather is sunny and 75°F.",
additional_kwargs={},
response_metadata={},
id="3",
tool_calls=[
{
"name": "get_weather",
"args": {},
"id": "1",
"type": "tool_call",
}
],
)
]
}
},
{"post_model_hook": {"flag": True}},
{
"generate_structured_response": {
"structured_response": WeatherResponse(temperature=75.0)
}
},
]
@@ -0,0 +1,52 @@
from typing import Callable, Union
import pytest
from pydantic import BaseModel
from syrupy import SnapshotAssertion
from langgraph.prebuilt import create_react_agent
from tests.model import FakeToolCallingModel
model = FakeToolCallingModel()
def tool() -> None:
"""Testing tool."""
...
def pre_model_hook() -> None:
"""Pre-model hook."""
...
def post_model_hook() -> None:
"""Post-model hook."""
...
class ResponseFormat(BaseModel):
"""Response format for the agent."""
result: str
@pytest.mark.parametrize("tools", [[], [tool]])
@pytest.mark.parametrize("pre_model_hook", [None, pre_model_hook])
@pytest.mark.parametrize("post_model_hook", [None, post_model_hook])
@pytest.mark.parametrize("response_format", [None, ResponseFormat])
def test_react_agent_graph_structure(
snapshot: SnapshotAssertion,
tools: list[Callable],
pre_model_hook: Union[Callable, None],
post_model_hook: Union[Callable, None],
response_format: Union[type[BaseModel], None],
) -> None:
agent = create_react_agent(
model,
tools=tools,
pre_model_hook=pre_model_hook,
post_model_hook=post_model_hook,
response_format=response_format,
)
assert agent.get_graph().draw_mermaid(with_styles=False) == snapshot
+16 -1
View File
@@ -1,4 +1,5 @@
version = 1
revision = 1
requires-python = ">=3.9"
resolution-markers = [
"python_full_version >= '3.12.4'",
@@ -430,7 +431,7 @@ dev = [
[[package]]
name = "langgraph-checkpoint-sqlite"
version = "2.0.8"
version = "2.0.10"
source = { editable = "../checkpoint-sqlite" }
dependencies = [
{ name = "aiosqlite" },
@@ -480,6 +481,7 @@ dev = [
{ name = "pytest-mock" },
{ name = "pytest-watcher" },
{ name = "ruff" },
{ name = "syrupy" },
]
[package.metadata]
@@ -501,6 +503,7 @@ dev = [
{ name = "pytest-mock" },
{ name = "pytest-watcher" },
{ name = "ruff" },
{ name = "syrupy" },
]
[[package]]
@@ -1084,6 +1087,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/6a/98/e8bc58b178266eae2fcf4c9c7a8303a8d41164d781b32d71097924a6bebe/sqlite_vec-0.1.6-py3-none-win_amd64.whl", hash = "sha256:c65bcfd90fa2f41f9000052bcb8bb75d38240b2dae49225389eca6c3136d3f0c", size = 281540 },
]
[[package]]
name = "syrupy"
version = "4.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8c/f8/022d8704a3314f3e96dbd6bbd16ebe119ce30e35f41aabfa92345652fceb/syrupy-4.9.1.tar.gz", hash = "sha256:b7d0fcadad80a7d2f6c4c71917918e8ebe2483e8c703dfc8d49cdbb01081f9a4", size = 52492 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/9d/aef9ec5fd5a4ee2f6a96032c4eda5888c5c7cec65cef6b28c4fc37671d88/syrupy-4.9.1-py3-none-any.whl", hash = "sha256:b94cc12ed0e5e75b448255430af642516842a2374a46936dd2650cfb6dd20eda", size = 52214 },
]
[[package]]
name = "tenacity"
version = "9.1.2"
+1
View File
@@ -584,6 +584,7 @@ dev = [
{ name = "pytest-mock" },
{ name = "pytest-watcher" },
{ name = "ruff" },
{ name = "syrupy" },
]
[[package]]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@langchain/langgraph-sdk",
"version": "0.0.76",
"version": "0.0.77",
"description": "Client library for interacting with the LangGraph API",
"type": "module",
"packageManager": "yarn@1.22.19",
+2
View File
@@ -894,6 +894,7 @@ export class RunsClient<
metadata: payload?.metadata,
stream_mode: payload?.streamMode,
stream_subgraphs: payload?.streamSubgraphs,
stream_resumable: payload?.streamResumable,
feedback_keys: payload?.feedbackKeys,
assistant_id: assistantId,
interrupt_before: payload?.interruptBefore,
@@ -953,6 +954,7 @@ export class RunsClient<
metadata: payload?.metadata,
stream_mode: payload?.streamMode,
stream_subgraphs: payload?.streamSubgraphs,
stream_resumable: payload?.streamResumable,
assistant_id: assistantId,
interrupt_before: payload?.interruptBefore,
interrupt_after: payload?.interruptAfter,
+12
View File
@@ -156,6 +156,12 @@ export interface RunsStreamPayload<
*/
streamSubgraphs?: TSubgraphs;
/**
* Whether the stream is considered resumable.
* If true, the stream can be resumed and replayed in its entirety even after disconnection.
*/
streamResumable?: boolean;
/**
* Pass one or more feedbackKeys if you want to request short-lived signed URLs
* for submitting feedback to LangSmith with this key for this run.
@@ -173,6 +179,12 @@ export interface RunsCreatePayload extends RunsInvokePayload {
* Stream output from subgraphs. By default, streams only the top graph.
*/
streamSubgraphs?: boolean;
/**
* Whether the stream is considered resumable.
* If true, the stream can be resumed and replayed in its entirety even after disconnection.
*/
streamResumable?: boolean;
}
export interface CronsCreatePayload extends RunsCreatePayload {
+10
View File
@@ -0,0 +1,10 @@
{
"dockerfile_lines": [],
"graphs": {
"langgraph101": "src/graph.py:agent"
},
"python_version": "3.11",
"dependencies": [
"."
]
}
+54
View File
@@ -0,0 +1,54 @@
[project]
name = "test_studio"
version = "0.1.0"
description = "Test Studio"
requires-python = ">=3.11"
dependencies = [
"langchain>=0.3.9",
"langchain-core>=0.3.59",
"langchain-openai",
# Use local langgraph build
"langgraph @ file:///Users/rlm/Desktop/Code/langgraph/libs/langgraph",
"langgraph-prebuilt @ file:///Users/rlm/Desktop/Code/langgraph/libs/prebuilt",
"langsmith[pytest]>=0.3.4",
]
[project.optional-dependencies]
dev = ["mypy>=1.11.1", "ruff>=0.6.1"]
[build-system]
requires = ["setuptools>=73.0.0", "wheel"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
packages = ["agent"]
[tool.setuptools.package-dir]
"agent" = "src/graph"
[tool.setuptools.package-data]
"*" = ["py.typed"]
[tool.ruff]
lint.select = [
"E", # pycodestyle
"F", # pyflakes
"I", # isort
"D", # pydocstyle
"D401", # First line should be in imperative mood
"T201",
"UP",
]
lint.ignore = [
"UP006",
"UP007",
"UP035",
"D417",
"E501",
]
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["D", "UP"]
[tool.ruff.lint.pydocstyle]
convention = "google"
+27
View File
@@ -0,0 +1,27 @@
from langchain.tools import tool
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.prebuilt.interrupt import HumanInterruptConfig, InterruptToolNode
from langchain.chat_models import init_chat_model
llm = init_chat_model("openai:gpt-4.1", temperature=0)
@tool
def write_email(to: str, subject: str, content: str) -> str:
"""Write and send an email."""
# Placeholder response - in real app would send email
return f"Email sent to {to} with subject '{subject}' and content: {content}"
agent = create_react_agent(
llm,
tools=[write_email],
prompt="You are a helpful email assistant.",
post_model_hook=InterruptToolNode(
write_email=HumanInterruptConfig(
allow_accept=True,
allow_edit=True,
allow_ignore=True,
allow_respond=True,
)
),
)
File diff suppressed because one or more lines are too long