Compare commits

..
Author SHA1 Message Date
William Fu-Hinthorn 9d03ba660c format 2025-04-28 08:30:49 -07:00
William Fu-Hinthorn ed8c91c39a Add how-to 2025-04-28 08:24:27 -07:00
William Fu-Hinthorn 6ca126f94d Add schema updates for the configurable headers 2025-04-28 08:02:44 -07:00
129 changed files with 1762 additions and 2752 deletions
+1 -21
View File
@@ -22,12 +22,6 @@ MANUAL_API_REFERENCES_LANGGRAPH = [
"create_react_agent",
"prebuilt",
),
(
[],
"langgraph.prebuilt.chat_agent_executor",
"AgentState",
"prebuilt",
),
(["langgraph.prebuilt"], "langgraph.prebuilt.tool_node", "ToolNode", "prebuilt"),
(
["langgraph.prebuilt"],
@@ -69,18 +63,6 @@ MANUAL_API_REFERENCES_LANGGRAPH = [
([], "langgraph.checkpoint.sqlite", "SqliteSaver", "checkpoints"),
([], "langgraph.checkpoint.postgres.aio", "AsyncPostgresSaver", "checkpoints"),
([], "langgraph.checkpoint.postgres", "PostgresSaver", "checkpoints"),
# other prebuilts
(["langgraph_supervisor"], "langgraph_supervisor.supervisor", "create_supervisor", "supervisor"),
(["langgraph_supervisor"], "langgraph_supervisor.handoff", "create_handoff_tool", "supervisor"),
([], "langgraph_supervisor.handoff", "create_forward_message_tool", "supervisor"),
(["langgraph_swarm"], "langgraph_swarm.swarm", "create_swarm", "swarm"),
(["langgraph_swarm"], "langgraph_swarm.swarm", "add_active_agent_router", "swarm"),
(["langgraph_swarm"], "langgraph_swarm.swarm", "SwarmState", "swarm"),
(["langgraph_swarm"], "langgraph_swarm.handoff", "create_handoff_tool", "swarm"),
([], "langchain_mcp_adapters.client", "MultiServerMCPClient", "mcp"),
([], "langchain_mcp_adapters.tools", "load_mcp_tools", "mcp"),
([], "langchain_mcp_adapters.prompts", "load_mcp_prompt", "mcp"),
([], "langchain_mcp_adapters.resources", "load_mcp_resources", "mcp"),
]
WELL_KNOWN_LANGGRAPH_OBJECTS = {
@@ -162,9 +144,7 @@ def get_imports(code: str, path: str) -> List[ImportInformation]:
for found_import in found_imports:
module = found_import["source"]
if module.startswith("langchain_mcp_adapters"):
package_ecosystem = "langgraph"
elif module.startswith("langchain"):
if module.startswith("langchain"):
# Handles things like `langchain` or `langchain_anthropic`
package_ecosystem = "langchain"
elif module.startswith("langgraph"):
+14 -14
View File
@@ -1,6 +1,7 @@
import ast
import os
import re
from pathlib import Path
from typing import Literal
import nbformat
@@ -25,7 +26,7 @@ def _uses_input(source: str) -> bool:
def _rewrite_cell_magic(code: str) -> str:
"""Process a code block that uses cell magic.
"""Process a code block that uses cell magic.:w
- Lines starting with "%%capture" are ignored.
- Lines starting with "%pip" are rewritten by removing the leading "%" character.
@@ -51,14 +52,10 @@ def _rewrite_cell_magic(code: str) -> str:
if stripped.startswith("%%capture"):
continue
# Rewrite %pip lines by dropping the '%'
elif stripped.startswith("%") or stripped.startswith("!"):
# Drop the leading '%' character and then drop all leading whitespace
stripped = stripped.lstrip("%! \t")
# Check if the line starts with "pip"
if stripped.startswith("pip"):
rewritten_lines.append(stripped)
else:
raise NotImplementedError(f"Unhandled line: {line}")
elif stripped.startswith("%pip"):
# Drop the leading '%' character
rewritten_lines.append(stripped[1:])
# Anything else is not supported
else:
raise NotImplementedError(f"Unhandled line: {line}")
@@ -250,10 +247,13 @@ class EscapePreprocessor(Preprocessor):
)
cell.metadata["exec"] = is_exec
# For markdown exec migration we'll re-write cell magic as bash commands
if source.startswith("%%"):
cell.source = _rewrite_cell_magic(source)
cell.metadata["language"] = "shell"
if self.markdown_exec_migration:
# For markdown exec migration we'll re-write cell magic as bash commands
if source.startswith("%%"):
cell.source = _rewrite_cell_magic(source)
cell.metadata["language"] = "shell"
cell.metadata["has_output"] = _has_output(source)
# Remove noqa comments
cell.source = re.sub(r"#\s*noqa.*$", "", cell.source, flags=re.MULTILINE)
@@ -352,7 +352,7 @@ exporter = MarkdownExporter(
def convert_notebook(
notebook_path: str,
notebook_path: Path,
mode: Literal["markdown", "exec"] = "markdown",
) -> str:
with open(notebook_path) as f:
@@ -1,18 +1,5 @@
{% extends 'markdown/index.md.j2' %}
{% block input %}{# cell.metadata.language is an addition of our docs pipeline. #}
```{%- if 'language' in cell.metadata -%}
{{ cell.metadata.language }}
{%- elif 'magics_language' in cell.metadata -%}
{{ cell.metadata.magics_language }}
{%- elif 'name' in nb.metadata.get('language_info', {}) -%}
{{ nb.metadata.language_info.name }}
{%- endif %}
{{ cell.source }}
```
{% endblock input %}
{%- block traceback_line -%}
```output
{{ line.rstrip() | strip_ansi }}
@@ -21,13 +8,13 @@
{%- block stream -%}
```output
{{ output.text.rstrip() | strip_ansi }}
{{ output.text.rstrip() }}
```
{%- endblock stream -%}
{%- block data_text scoped -%}
```output
{{ output.data['text/plain'].rstrip() | strip_ansi }}
{{ output.data['text/plain'].rstrip() }}
```
{%- endblock data_text -%}
+1 -2
View File
@@ -32,8 +32,7 @@ REDIRECT_MAP = {
"cloud/concepts/cloud.md": "concepts/langgraph_cloud.md",
"cloud/faq/studio.md": "concepts/langgraph_studio.md#studio-faqs",
# misc
"prebuilt.md": "agents/prebuilt.md",
"reference/prebuilt.md": "reference/agents.md"
"prebuilt.md": "agents/prebuilt.md"
}
+1 -10
View File
@@ -1,12 +1,3 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Agents
## What is an agent?
@@ -112,7 +103,7 @@ from langgraph.prebuilt import create_react_agent
# highlight-next-line
def prompt(state: AgentState, config: RunnableConfig) -> list[AnyMessage]: # (1)!
user_name = config["configurable"].get("user_name")
user_name = config.get("configurable", {}).get("user_name")
system_msg = f"You are a helpful assistant. Address the user as {user_name}."
return [{"role": "system", "content": system_msg}] + state["messages"]
+65 -14
View File
@@ -1,12 +1,3 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Context
Agents often require more than a list of messages to function effectively. They need **context**.
@@ -107,7 +98,7 @@ Common use cases:
config: RunnableConfig,
) -> list[AnyMessage]:
# highlight-next-line
user_name = config["configurable"].get("user_name")
user_name = config.get("configurable", {}).get("user_name")
system_msg = f"You are a helpful assistant. User's name is {user_name}"
return [{"role": "system", "content": system_msg}] + state["messages"]
@@ -162,7 +153,7 @@ Common use cases:
})
```
## Accessing Context in Tools
## Tools
Tools can access context through special parameter **annotations**.
@@ -183,7 +174,7 @@ Tools can access context through special parameter **annotations**.
) -> str:
"""Look up user info."""
# highlight-next-line
user_id = config["configurable"].get("user_id")
user_id = config.get("configurable", {}).get("user_id")
return "User is John Smith" if user_id == "user_123" else "Unknown user"
agent = create_react_agent(
@@ -231,6 +222,66 @@ Tools can access context through special parameter **annotations**.
})
```
### Update Context from Tools
Tools can update agent's context (state and long-term memory) during execution. This is useful for persisting intermediate results or making information accessible to subsequent tools or prompts. See [Memory](./memory.md#read-short-term) guide for more information.
## Update context from tools
Tools can modify the agent's state during execution. This is useful for persisting intermediate results or making information accessible to subsequent tools or prompts.
```python
from typing import Annotated
from langchain_core.tools import InjectedToolCallId
from langchain_core.messages import ToolMessage
from langgraph.prebuilt import InjectedState
from langgraph.types import Command
class CustomState(AgentState):
# highlight-next-line
user_name: str
def get_user_info(
# highlight-next-line
tool_call_id: Annotated[str, InjectedToolCallId],
# highlight-next-line
config: RunnableConfig
) -> Command:
"""Look up user info."""
# highlight-next-line
user_id = config.get("configurable", {}).get("user_id")
name = "John Smith" if user_id == "user_123" else "Unknown user"
return Command(update={
# highlight-next-line
"user_name": name,
# update the message history
# highlight-next-line
"messages": [
ToolMessage(
"Successfully looked up user information",
# highlight-next-line
tool_call_id=tool_call_id
)
]
})
def greet(
# highlight-next-line
state: Annotated[CustomState, InjectedState]
) -> str:
"""Use this to greet the user once you found their info."""
user_name = state["user_name"]
return f"Hello {user_name}!"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_user_info, greet],
# highlight-next-line
state_schema=CustomState
)
agent.invoke(
{"messages": [{"role": "user", "content": "greet the user"}]},
# highlight-next-line
config={"configurable": {"user_id": "user_123"}}
)
```
For more details, see [how to update state from tools](../how-tos/update-state-from-tools.ipynb).
-9
View File
@@ -1,12 +1,3 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Deployment
To deploy your LangGraph agent, create and configure a LangGraph app. This setup supports both local development and production deployments.
-9
View File
@@ -1,12 +1,3 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Evals
To evaluate your agent's performance you can use `LangSmith` [evaluations](https://docs.smith.langchain.com/evaluation). You would need to first define an evaluator function to judge the results from an agent, such as final outputs or trajectory. Depending on your evaluation technique, this may or may not involve a reference output:
+1 -12
View File
@@ -1,17 +1,6 @@
---
search:
boost: 2
tags:
- human-in-the-loop
- hil
- agent
hide:
- tags
---
# Human-in-the-loop
To review, edit and approve tool calls in an agent you can use LangGraph's built-in [Human-In-the-Loop (HIL)](../concepts/human_in_the_loop.md) features, specifically the [`interrupt()`][langgraph.types.interrupt] primitive.
To review, edit and approve tool calls in an agent you can use LangGraph's built-in [human-in-the-loop](../concepts/human_in_the_loop.md) features, specifically the [`interrupt()`][langgraph.types.interrupt] primitive.
LangGraph allows you to pause execution **indefinitely** — for minutes, hours, or even days—until human input is received.
-9
View File
@@ -1,12 +1,3 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# MCP Integration
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol that standardizes how applications provide tools and context to language models. LangGraph agents can use tools defined on MCP servers through the `langchain-mcp-adapters` library.
+4 -112
View File
@@ -1,12 +1,3 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Memory
LangGraph supports two types of memory essential for building conversational agents:
@@ -149,104 +140,6 @@ agent = create_react_agent(
To learn more about using `pre_model_hook` for managing message history, see this [how-to guide](../how-tos/create-react-agent-manage-message-history.ipynb)
### Read in tools { #read-short-term }
LangGraph allows agent to access its short-term memory (state) inside the tools.
```python
from typing import Annotated
from langgraph.prebuilt import InjectedState, create_react_agent
class CustomState(AgentState):
# highlight-next-line
user_id: str
def get_user_info(
# highlight-next-line
state: Annotated[CustomState, InjectedState]
) -> str:
"""Look up user info."""
# highlight-next-line
user_id = state["user_id"]
return "User is John Smith" if user_id == "user_123" else "Unknown user"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_user_info],
# highlight-next-line
state_schema=CustomState,
)
agent.invoke({
"messages": "look up user information",
# highlight-next-line
"user_id": "user_123"
})
```
See the [Context](./context.md#__tabbed_2_2) guide for more information.
### Write from tools { #write-short-term }
To modify the agent's short-term memory (state) during execution, you can return state updates directly from the tools. This is useful for persisting intermediate results or making information accessible to subsequent tools or prompts.
```python
from typing import Annotated
from langchain_core.tools import InjectedToolCallId
from langchain_core.runnables import RunnableConfig
from langchain_core.messages import ToolMessage
from langgraph.prebuilt import InjectedState, create_react_agent
from langgraph.prebuilt.chat_agent_executor import AgentState
from langgraph.types import Command
class CustomState(AgentState):
# highlight-next-line
user_name: str
def update_user_info(
tool_call_id: Annotated[str, InjectedToolCallId],
config: RunnableConfig
) -> Command:
"""Look up and update user info."""
user_id = config["configurable"].get("user_id")
name = "John Smith" if user_id == "user_123" else "Unknown user"
# highlight-next-line
return Command(update={
# highlight-next-line
"user_name": name,
# update the message history
"messages": [
ToolMessage(
"Successfully looked up user information",
tool_call_id=tool_call_id
)
]
})
def greet(
# highlight-next-line
state: Annotated[CustomState, InjectedState]
) -> str:
"""Use this to greet the user once you found their info."""
user_name = state["user_name"]
return f"Hello {user_name}!"
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=[get_user_info, greet],
# highlight-next-line
state_schema=CustomState
)
agent.invoke(
{"messages": [{"role": "user", "content": "greet the user"}]},
# highlight-next-line
config={"configurable": {"user_id": "user_123"}}
)
```
For more details, see [how to update state from tools](../how-tos/update-state-from-tools.ipynb).
## Long-term memory
Use long-term memory to store user-specific or application-specific data across conversations. This is useful for applications like chatbots, where you want to remember user preferences or other information.
@@ -256,10 +149,9 @@ To use long-term memory, you need to:
1. [Configure a store](../how-tos/cross-thread-persistence.ipynb) to persist data across invocations.
2. Use the [`get_store`][langgraph.config.get_store] function to access the store from within tools or prompts.
### Read { #read-long-term }
### Reading
```python title="A tool the agent can use to look up user information"
from langchain_core.runnables import RunnableConfig
from langgraph.config import get_store
from langgraph.prebuilt import create_react_agent
from langgraph.store.memory import InMemoryStore
@@ -282,7 +174,7 @@ def get_user_info(config: RunnableConfig) -> str:
# Same as that provided to `create_react_agent`
# highlight-next-line
store = get_store() # (6)!
user_id = config["configurable"].get("user_id")
user_id = config.get("configurable", {}).get("user_id")
# highlight-next-line
user_info = store.get(("users",), user_id) # (7)!
return str(user_info.value) if user_info else "Unknown user"
@@ -311,7 +203,7 @@ agent.invoke(
7. The `get` method is used to retrieve data from the store. The first argument is the namespace, and the second argument is the key. This will return a `StoreValue` object, which contains the value and metadata about the value.
8. The `store` is passed to the agent. This enables the agent to access the store when running tools. You can also use the `get_store` function to access the store from anywhere in your code.
### Write { #write-long-term }
### Writing
```python title="Example of a tool that updates user information"
from typing_extensions import TypedDict
@@ -330,7 +222,7 @@ def save_user_info(user_info: UserInfo, config: RunnableConfig) -> str: # (3)!
# Same as that provided to `create_react_agent`
# highlight-next-line
store = get_store() # (4)!
user_id = config["configurable"].get("user_id")
user_id = config.get("configurable", {}).get("user_id")
# highlight-next-line
store.put(("users",), user_id, user_info) # (5)!
return "Successfully saved user info."
+1 -77
View File
@@ -1,14 +1,3 @@
---
search:
boost: 2
tags:
- anthropic
- openai
- agent
hide:
- tags
---
# Models
This page describes how to configure the chat model used by an agent.
@@ -74,72 +63,7 @@ agent = create_react_agent(
The example above uses `ChatAnthropic`, which is already supported by `init_chat_model`. This pattern is shown to illustrate how to manually instantiate a model not available through init_chat_model.
## Disable streaming
To disable streaming of the individual LLM tokens, set `disable_streaming=True` when initializing the model:
=== "`init_chat_model`"
```python
from langchain.chat_models import init_chat_model
model = init_chat_model(
"anthropic:claude-3-7-sonnet-latest",
# highlight-next-line
disable_streaming=True
)
```
=== "`ChatModel`"
```python
from langchain_anthropic import ChatAnthropic
model = ChatAnthropic(
model="claude-3-7-sonnet-latest",
# highlight-next-line
disable_streaming=True
)
```
Refer to the [API reference](https://python.langchain.com/api_reference/core/language_models/langchain_core.language_models.chat_models.BaseChatModel.html#langchain_core.language_models.chat_models.BaseChatModel.disable_streaming) for more information on `disable_streaming`
## Adding model fallbacks
You can add a fallback to a different model or a different LLM provider using `model.with_fallbacks([...])`:
=== "`init_chat_model`"
```python
from langchain.chat_models import init_chat_model
model_with_fallbacks = (
init_chat_model("anthropic:claude-3-5-haiku-latest")
# highlight-next-line
.with_fallbacks([
init_chat_model("openai:gpt-4.1-mini"),
])
)
```
=== "`ChatModel`"
```python
from langchain_anthropic import ChatAnthropic
from langchain_openai import ChatOpenAI
model_with_fallbacks = (
ChatAnthropic(model="claude-3-5-haiku-latest")
# highlight-next-line
.with_fallbacks([
ChatOpenAI(model="gpt-4.1-mini"),
])
)
```
See this [guide](https://python.langchain.com/docs/how_to/fallbacks/#fallback-to-better-model) for more information on model fallbacks.
## Additional resources
- [Model integration directory](https://python.langchain.com/docs/integrations/chat/)
- [Universal initialization with `init_chat_model`](https://python.langchain.com/docs/how_to/chat_models_universal_init/)
- [Universal initialization with `init_chat_model`](https://python.langchain.com/docs/how_to/chat_models_universal_init/)
-9
View File
@@ -1,12 +1,3 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Multi-agent
A single agent might struggle if it needs to specialize in multiple domains or manage many tools. To tackle this, you can break your agent into smaller, independent agents and composing them into a [multi-agent system](../concepts/multi_agent.md).
-6
View File
@@ -1,11 +1,5 @@
---
title: Overview
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Agent development with LangGraph
-7
View File
@@ -1,10 +1,3 @@
---
tags:
- agent
hide:
- tags
---
# Community Agents
To share your project, simply open a Pull Request adding an entry for your package in our [packages.yml](https://github.com/langchain-ai/langgraph/blob/main/docs/_scripts/third_party_page/packages.yml) file.
-9
View File
@@ -1,12 +1,3 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Running agents
-15
View File
@@ -1,12 +1,3 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Streaming
Streaming is key to building responsive applications. There are a few types of data youll want to stream:
@@ -212,12 +203,6 @@ You can specify multiple streaming modes by passing stream mode as a list: `stre
print("\n")
```
## Disable streaming
In some applications you might need to disable streaming of individual tokens for a given model. This is useful in [multi-agent](./multi-agent.md) systems to control which agents stream their output.
See the [Models](./models.md#disable-streaming) guide to learn how to disable streaming.
## Additional resources
* [Streaming in LangGraph](https://langchain-ai.github.io/langgraph/how-tos/streaming)
-16
View File
@@ -1,12 +1,3 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# Tools
[Tools](https://python.langchain.com/docs/concepts/tools/) are a way to encapsulate a function and its input schema in a way that can be passed to a chat model that supports tool calling. This allows the model to request the execution of this function with specific inputs.
@@ -271,13 +262,6 @@ By default, the agent will catch all exceptions raised during tool calls and wil
See [API reference][langgraph.prebuilt.tool_node.ToolNode] for more information on different tool error handling options.
## Working with memory
LangGraph allows access to short-term and long-term memory from tools. See [Memory](./memory.md) guide for more information on:
* how to [read](./memory.md#read-short-term) from and [write](./memory.md#write-short-term) to **short-term** memory
* how to [read](./memory.md#read-long-term) from and [write](./memory.md#write-long-term) to **long-term** memory
## Prebuilt tools
LangChain supports a wide range of prebuilt tool integrations for interacting with APIs, databases, file systems, web data, and more. These tools extend the functionality of agents and enable rapid development.
-9
View File
@@ -1,12 +1,3 @@
---
search:
boost: 2
tags:
- agent
hide:
- tags
---
# UI
You can use a prebuilt chat UI for interacting with any LangGraph agent through the [Agent Chat UI](https://github.com/langchain-ai/agent-chat-ui). Using the [deployed version](https://agentchat.vercel.app) is the quickest way to get started, and allows you to interact with both local and deployed graphs.
+10 -10
View File
@@ -107,13 +107,13 @@ After installing and authorizing LangChain's `hosted-langserve` GitHub app, repo
All traffic from `LangGraph Platform` deployments created after January 6th 2025 will come through a NAT gateway.
This NAT gateway will have several static ip addresses depending on the region you are deploying in. Refer to the table below for the list of IP addresses to whitelist:
| US | EU |
|----------------|-----------------|
| 35.197.29.146 | 34.90.213.236 |
| 34.145.102.123 | 34.13.244.114 |
| 34.169.45.153 | 34.32.180.189 |
| 34.82.222.17 | 34.34.69.108 |
| 35.227.171.135 | 34.32.145.240 |
| 34.169.88.30 | 34.90.157.44 |
| 34.19.93.202 | 34.141.242.180 |
| 34.19.34.50 | 34.32.141.108 |
| US | EU |
|----------------|----------------|
| 35.197.29.146 | 34.13.192.67 |
| 34.145.102.123 | 34.147.105.64 |
| 34.169.45.153 | 34.90.22.166 |
| 34.82.222.17 | 34.147.36.213 |
| 35.227.171.135 | 34.32.137.113 |
| 34.169.88.30 | 34.91.238.184 |
| 34.19.93.202 | 35.204.101.241 |
| 34.19.34.50 | 35.204.48.32 |
@@ -17,70 +17,4 @@ Here's how to customize the included and excluded headers:
}
```
The `include` and `exclude` lists accept exact header names or patterns using `*` to match any number of characters. For your security, no other regex patterns are supported.
## Using within your graph
You can access the included headers in your graph using the `config` argument of any node.
```python
def my_node(state, config):
organization_id = config["configurable"].get("x-organization-id")
...
```
Or by fetching from context (useful in tools and or within other nested functions).
```python
from langgraph.config import get_config
def search_everything(query: str):
organization_id = get_config()["configurable"].get("x-organization-id")
...
```
You can even use this to dynamically compile the graph.
```python
# my_graph.py.
import contextlib
@contextlib.asynccontextmanager
async def generate_agent(config):
organization_id = config["configurable"].get("x-organization-id")
if organization_id == "org1":
graph = ...
yield graph
else:
graph = ...
yield graph
```
```json
{
"graphs": {"agent": "my_grph.py:generate_agent"}
}
```
For more examples on how to use runtime configuration, check out the [configuration how-to](../../how-tos/configuration.ipynb).
### Opt-out of configurable headers
If you'd like to opt-out of configurable headers, you can simply set a wildcard pattern in the `exclude` list:
```json
{
"http": {
"configurable_headers": {
"exclude": ["*"]
}
}
}
```
This will exclude all headers from being added to your run's configuration.
Note that exclusions take precedence over inclusions.
+14 -170
View File
@@ -207,6 +207,18 @@ Behind the scenes, `LoadExternalComponent` will fetch the JS and CSS for the UI
## How-to guides
### Show loading UI when components are loading
You can provide a fallback UI to be rendered when the components are loading.
```tsx
<LoadExternalComponent
stream={thread}
message={ui}
fallback={<div>Loading...</div>}
/>
```
### Provide custom components on the client side
If you already have the components loaded in your client application, you can provide a map of such components to be rendered directly without fetching the UI code from LangGraph Platform.
@@ -223,18 +235,6 @@ const clientComponents = {
/>;
```
### Show loading UI when components are loading
You can provide a fallback UI to be rendered when the components are loading.
```tsx
<LoadExternalComponent
stream={thread}
message={ui}
fallback={<div>Loading...</div>}
/>
```
### Customise the namespace of UI components.
By default `LoadExternalComponent` will use the `assistantId` from `useStream()` hook to fetch the code for UI components. You can customise this by providing a `namespace` prop to the `LoadExternalComponent` component.
@@ -316,9 +316,9 @@ const WeatherComponent = (props: { city: string }) => {
};
```
### Streaming UI messages from the server
### Streaming UI updates before the node execution is finished
You can stream UI messages before the node execution is finished by using the `onCustomEvent` callback of the `useStream()` hook. This is especially useful when updating the UI component as the LLM is generating the response.
You can stream UI updates before the node execution is finished by using the `onCustomEvent` callback of the `useStream()` hook.
```tsx
import { uiMessageReducer } from "@langchain/langgraph-sdk/react-ui";
@@ -335,162 +335,6 @@ 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.
=== "Python"
```python
from typing import Annotated, Sequence, TypedDict
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessage
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages
from langgraph.graph.ui import AnyUIMessage, push_ui_message, ui_message_reducer
class AgentState(TypedDict): # noqa: D101
messages: Annotated[Sequence[BaseMessage], add_messages]
ui: Annotated[Sequence[AnyUIMessage], ui_message_reducer]
class CreateTextDocument(TypedDict):
"""Prepare a document heading for the user."""
title: str
async def writer_node(state: AgentState):
model = ChatAnthropic(model="claude-3-5-sonnet-latest")
message: AIMessage = await model.bind_tools(
tools=[CreateTextDocument],
tool_choice={"type": "tool", "name": "CreateTextDocument"},
).ainvoke(state["messages"])
tool_call = next(
(x["args"] for x in message.tool_calls if x["name"] == "CreateTextDocument"),
None,
)
if tool_call:
ui_message = push_ui_message("writer", tool_call, message=message)
ui_message_id = ui_message["id"]
# We're already streaming the LLM response to the client through UI messages
# so we don't need to stream it again to the `messages` stream mode.
content_stream = model.with_config({"tags": ["nostream"]}).astream(
f"Create a document with the title: {tool_call['title']}"
)
content: AIMessageChunk | None = None
async for chunk in content_stream:
content = content + chunk if content else chunk
push_ui_message(
"writer",
{"content": content.text()},
id=ui_message_id,
message=message,
# Use `merge=rue` to merge props with the existing UI message
merge=True,
)
return {"messages": [message]}
```
=== "JS"
```tsx
import {
Annotation,
MessagesAnnotation,
type LangGraphRunnableConfig,
} from "@langchain/langgraph";
import { z } from "zod";
import { ChatAnthropic } from "@langchain/anthropic";
import {
typedUi,
uiMessageReducer,
} from "@langchain/langgraph-sdk/react-ui/server";
import type { AIMessageChunk } from "@langchain/core/messages";
import type ComponentMap from "./ui";
const AgentState = Annotation.Root({
...MessagesAnnotation.spec,
ui: Annotation({ reducer: uiMessageReducer, default: () => [] }),
});
async function writerNode(
state: typeof AgentState.State,
config: LangGraphRunnableConfig
): Promise<typeof AgentState.Update> {
const ui = typedUi<typeof ComponentMap>(config);
const model = new ChatAnthropic({ model: "claude-3-5-sonnet-latest" });
const message = await model
.bindTools(
[
{
name: "create_text_document",
description: "Prepare a document heading for the user.",
schema: z.object({ title: z.string() }),
},
],
{ tool_choice: { type: "tool", name: "create_text_document" } }
)
.invoke(state.messages);
type ToolCall = { name: "create_text_document"; args: { title: string } };
const toolCall = message.tool_calls?.find(
(tool): tool is ToolCall => tool.name === "create_text_document"
);
if (toolCall) {
const { id, name } = ui.push(
{ name: "writer", props: { title: toolCall.args.title } },
{ message }
);
const contentStream = await model
// We're already streaming the LLM response to the client through UI messages
// so we don't need to stream it again to the `messages` stream mode.
.withConfig({ tags: ["nostream"] })
.stream(`Create a short poem with the topic: ${message.text}`);
let content: AIMessageChunk | undefined;
for await (const chunk of contentStream) {
content = content?.concat(chunk) ?? chunk;
ui.push(
{ id, name, props: { content: content?.text } },
// Use `merge: true` to merge props with the existing UI message
{ message, merge: true }
);
}
}
return { messages: [message] };
}
```
=== "`ui.tsx`"
```tsx
function WriterComponent(props: { title: string; content?: string }) {
return (
<article>
<h2>{props.title}</h2>
<p style={{ whiteSpace: "pre-wrap" }}>{props.content}</p>
</article>
);
}
export default {
weather: WriterComponent,
};
```
### Remove UI messages from state
Similar to how messages can be removed from the state by appending a RemoveMessage you can remove an UI message from the state by calling `remove_ui_message` / `ui.delete` with the ID of the UI message.
@@ -1,4 +1,4 @@
# How to use the interrupt option
# Interrupt
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md).
@@ -1,5 +1,4 @@
# How to use the Rollback option
# Rollback
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md).
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Agent architectures
Many LLM applications implement a particular control flow of steps before and / or after LLM calls. As an example, [RAG](https://github.com/langchain-ai/rag-from-scratch) performs retrieval of documents relevant to a user question, and passes those documents to an LLM in order to ground the model's response in the provided document context.
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Application Structure
!!! info "Prerequisites"
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Assistants
!!! info "Prerequisites"
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Authentication & Access Control
LangGraph Platform provides a flexible authentication and authorization system that can integrate with most authentication schemes.
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Breakpoints
Breakpoints pause graph execution at specific points and enable stepping through execution step by step. Breakpoints are powered by LangGraph's [**persistence layer**](./persistence.md), which saves the state after each graph step. Breakpoints can also be used to enable [**human-in-the-loop**](./human_in_the_loop.md) workflows, though we recommend using the [`interrupt` function](./human_in_the_loop.md#interrupt) for this purpose.
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Bring Your Own Cloud (BYOC)
!!! note Prerequisites
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Deployment Options
!!! info "Prerequisites"
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Double Texting
!!! info "Prerequisites"
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Durable Execution
**Durable execution** is a technique in which a process or workflow saves its progress at key points, allowing it to pause and later resume exactly where it left off. This is particularly useful in scenarios that require [human-in-the-loop](./human_in_the_loop.md), where users can inspect, validate, or modify the process before continuing, and in long-running tasks that might encounter interruptions or errors (e.g., calls to an LLM timing out). By preserving completed work, durable execution enables a process to resume without reprocessing previous steps -- even after a significant delay (e.g., a week later).
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# FAQ
Common questions and their answers!
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Functional API
## Overview
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Why LangGraph?
## LLM applications
-26
View File
@@ -1,13 +1,3 @@
---
search:
boost: 2
tags:
- human-in-the-loop
- hil
hide:
- tags
---
# Human-in-the-loop
!!! tip "This guide uses the new `interrupt` function."
@@ -450,22 +440,6 @@ Upon **resuming** the graph, the counter will be incremented a second time, resu
The value of counter is: 2
```
### Resuming multiple interrupts with one invocation
If you have multiple interrupts in the task queue, you can use `Command.resume` with a dictionary mapping
of interrupt ids to resume values to resume multiple interrupts with a single `invoke` / `stream` call.
For example, once your graph has been interrupted (multiple times, theoretically) and is stalled:
```python
resume_map = {
i.interrupt_id: f"human input for prompt {i.value}"
for i in parent.get_state(thread_config).interrupts
}
parent_graph.invoke(Command(resume=resume_map), config=thread_config)
```
## Common Pitfalls
### Side-effects
-2
View File
@@ -1,8 +1,6 @@
---
title: Concepts
description: Conceptual Guide for LangGraph
search:
boost: 0.5
---
# Conceptual Guide
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# LangGraph CLI
!!! info "Prerequisites"
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Cloud SaaS (Beta)
To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-to guide for [how to deploy to Cloud SaaS](../cloud/deployment/cloud.md).
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# LangGraph Control Plane
The term "control plane" is used broadly to refer to the Control Plane UI where users create and update [LangGraph Servers](./langgraph_server.md) (deployments) and the Control Plane APIs that support the UI experience.
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# LangGraph Data Plane
The term "data plane" is used broadly to refer to [LangGraph Servers](./langgraph_server.md) (deployments), the corresponding infrastructure for each server, and the "listener" application that continuously polls for updates from the [LangGraph Control Plane](./langgraph_control_plane.md).
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Self-Hosted Control Plane (Beta)
To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-to guide for [how to deploy the Self-Hosted Control Plane](../cloud/deployment/self_hosted_control_plane.md).
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Self-Hosted Data Plane (Beta)
To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-to guide for [how to deploy the Self-Hosted Data Plane](../cloud/deployment/self_hosted_data_plane.md).
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# LangGraph Server
!!! info "Prerequisites"
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Standalone Container
To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-to guide for [how to deploy a Standalone Container](../cloud/deployment/standalone_container.md).
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# LangGraph Studio
!!! info "Prerequisites"
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# LangGraph Glossary
## Graphs
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Memory
## What is Memory?
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Multi-agent Systems
An [agent](./agentic_concepts.md#agent-architectures) is _a system that uses an LLM to decide the control flow of an application_. As you develop these systems, they might grow more complex over time, making them harder to manage and scale. For example, you might run into the following problems:
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Persistence
LangGraph has a built-in persistence layer, implemented through checkpointers. When you compile graph with a checkpointer, the checkpointer saves a `checkpoint` of the graph state at every super-step. Those checkpoints are saved to a `thread`, which can be accessed after graph execution. Because `threads` allow access to graph's state after execution, several powerful capabilities including human-in-the-loop, memory, time travel, and fault-tolerance are all possible. See [this how-to guide](../how-tos/persistence.ipynb) for an end-to-end example on how to add and use checkpointers with your graph. Below, we'll discuss each of these concepts in more detail.
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# LangGraph Platform Plans
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# LangGraph Platform Architecture
![](img/langgraph_platform_deployment_architecture.png)
+1 -6
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# LangGraph's Runtime (Pregel)
[Pregel][langgraph.pregel.Pregel] implements LangGraph's runtime, managing the execution of LangGraph applications.
@@ -27,7 +22,7 @@ Repeat until no **actors** are selected for execution, or a maximum number of st
## Actors
An **actor** is a `PregelNode`. It subscribes to channels, reads data from them, and writes data to them. It can be thought of as an **actor** in the Pregel algorithm. `PregelNodes` implement LangChain's Runnable interface.
An **actor** is a [PregelNode][langgraph.pregel.read.PregelNode]. It subscribes to channels, reads data from them, and writes data to them. It can be thought of as an **actor** in the Pregel algorithm. [PregelNodes][langgraph.pregel.read.PregelNode] implement LangChain's Runnable interface.
## Channels
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# LangGraph Platform: Scalability & Resilience
LangGraph Platform is designed to scale horizontally with your workload. Each instance of the service is stateless, and keeps no resources in memory. The service is designed to gracefully handle new instances being added or removed, including hard shutdown cases.
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# LangGraph SDK
!!! info "Prerequisites"
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Self-Hosted
!!! note Prerequisites
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Streaming
Building a responsive app for end-users? Real-time updates are key to keeping users engaged as your app progresses.
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Template Applications
Templates are open source reference applications designed to help you get started quickly when building with LangGraph. They provide working examples of common agentic workflows that can be customized to your needs.
-5
View File
@@ -1,8 +1,3 @@
---
search:
boost: 2
---
# Time Travel ⏱️
!!! note "Prerequisites"
@@ -691,8 +691,7 @@
"\n",
"checkpointer = InMemorySaver()\n",
"graph = create_react_agent(\n",
" # limit the output size to ensure consistent behavior\n",
" model.bind(max_tokens=256),\n",
" model,\n",
" tools,\n",
" # highlight-next-line\n",
" pre_model_hook=summarization_node,\n",
+1 -3
View File
@@ -1,8 +1,6 @@
---
title: How-to Guides
description: How to accomplish common tasks in LangGraph
search:
boost: 0.5
---
# How-to Guides
@@ -153,7 +151,7 @@ See the below guide for how to integrate with other frameworks using the [Functi
### Prebuilt ReAct Agent
The LangGraph [prebuilt ReAct agent](../reference/agents.md#langgraph.prebuilt.chat_agent_executor.create_react_agent) is pre-built implementation of a [tool calling agent](../concepts/agentic_concepts.md#tool-calling-agent).
The LangGraph [prebuilt ReAct agent](../reference/prebuilt.md#langgraph.prebuilt.chat_agent_executor.create_react_agent) is pre-built implementation of a [tool calling agent](../concepts/agentic_concepts.md#tool-calling-agent).
One of the big benefits of LangGraph is that you can easily create your own agent architectures. So while it's fine to start here to build an agent quickly, we would strongly recommend learning how to build your own agent so that you can take full advantage of LangGraph.
-39
View File
@@ -1,39 +0,0 @@
# Agents
::: langgraph.prebuilt.chat_agent_executor
options:
members:
- AgentState
- create_react_agent
::: langgraph.prebuilt.tool_node.ToolNode
options:
show_if_no_docstring: true
show_root_heading: true
show_root_full_path: false
inherited_members: false
members:
- inject_tool_args
::: langgraph.prebuilt.tool_node
options:
members:
- InjectedState
- InjectedStore
- tools_condition
::: langgraph.prebuilt.tool_validator.ValidationNode
options:
show_if_no_docstring: true
show_root_heading: true
show_root_full_path: false
inherited_members: false
members: false
::: langgraph.prebuilt.interrupt
options:
members:
- HumanInterruptConfig
- ActionRequest
- HumanInterrupt
- HumanResponse
+1 -7
View File
@@ -25,11 +25,5 @@
::: langgraph.checkpoint.sqlite.aio
::: langgraph.checkpoint.postgres
options:
members:
- PostgresSaver
::: langgraph.checkpoint.postgres.aio
options:
members:
- AsyncPostgresSaver
::: langgraph.checkpoint.postgres.aio
+6 -65
View File
@@ -1,75 +1,16 @@
# Graph Definitions
::: langgraph.graph.state.StateGraph
::: langgraph.graph.graph
options:
show_if_no_docstring: true
show_root_heading: true
show_root_full_path: false
members:
- add_node
- add_edge
- add_conditional_edges
- add_sequence
- compile
- Graph
- CompiledGraph
::: langgraph.graph.state.CompiledStateGraph
::: langgraph.graph.state
options:
show_if_no_docstring: true
show_root_heading: true
show_root_full_path: false
members:
- stream
- astream
- invoke
- ainvoke
- get_state
- aget_state
- get_state_history
- aget_state_history
- update_state
- aupdate_state
- bulk_update_state
- abulk_update_state
- get_graph
- aget_graph
- get_subgraphs
- aget_subgraphs
- with_config
::: langgraph.graph.graph.Graph
options:
show_if_no_docstring: true
show_root_heading: true
show_root_full_path: false
members:
- add_node
- add_edge
- add_conditional_edges
- compile
::: langgraph.graph.graph.CompiledGraph
options:
show_if_no_docstring: true
show_root_heading: true
show_root_full_path: false
members:
- stream
- astream
- invoke
- ainvoke
- get_state
- aget_state
- get_state_history
- aget_state_history
- update_state
- aupdate_state
- bulk_update_state
- abulk_update_state
- get_graph
- aget_graph
- get_subgraphs
- aget_subgraphs
- with_config
- StateGraph
- CompiledStateGraph
::: langgraph.graph.message
options:
-2
View File
@@ -1,8 +1,6 @@
---
title: Reference
description: API reference for LangGraph
search:
boost: 0.5
---
<style>
-21
View File
@@ -1,21 +0,0 @@
# LangChain Model Context Protocol (MCP) Adapters
::: langchain_mcp_adapters.client
options:
members:
- MultiServerMCPClient
::: langchain_mcp_adapters.tools
options:
members:
- load_mcp_tools
::: langchain_mcp_adapters.prompts
options:
members:
- load_mcp_prompt
::: langchain_mcp_adapters.resources
options:
members:
- load_mcp_resources
+28
View File
@@ -0,0 +1,28 @@
# Prebuilt
::: langgraph.prebuilt.chat_agent_executor
options:
members:
- create_react_agent
::: langgraph.prebuilt.tool_node
options:
members:
- ToolNode
- InjectedState
- InjectedStore
- tools_condition
::: langgraph.prebuilt.tool_validator
options:
members:
- ValidationNode
::: langgraph.prebuilt.interrupt
options:
members:
- HumanInterruptConfig
- ActionRequest
- HumanInterrupt
- HumanResponse
+3 -21
View File
@@ -1,25 +1,7 @@
# Pregel
::: langgraph.pregel.Pregel
::: langgraph.pregel
options:
show_if_no_docstring: true
show_root_heading: true
show_root_full_path: false
members:
- stream
- astream
- invoke
- ainvoke
- get_state
- aget_state
- get_state_history
- aget_state_history
- update_state
- aupdate_state
- bulk_update_state
- abulk_update_state
- get_graph
- aget_graph
- get_subgraphs
- aget_subgraphs
- with_config
- Pregel
- PregelNode
-12
View File
@@ -1,12 +0,0 @@
# LangGraph Supervisor
::: langgraph_supervisor.supervisor
options:
members:
- create_supervisor
::: langgraph_supervisor.handoff
options:
members:
- create_handoff_tool
- create_forward_message_tool
-13
View File
@@ -1,13 +0,0 @@
# LangGraph Swarm
::: langgraph_swarm.swarm
options:
members:
- SwarmState
- create_swarm
- add_active_agent_router
::: langgraph_swarm.handoff
options:
members:
- create_handoff_tool
+1
View File
@@ -10,6 +10,7 @@
- CachePolicy
- Interrupt
- PregelTask
- PregelExecutableTask
- StateSnapshot
- Send
- Command
@@ -1,7 +1,3 @@
---
search:
boost: 0.5
---
# Error reference
This page contains guides around resolving common errors you may find while building with LangGraph.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

+6 -47
View File
@@ -2,17 +2,17 @@
## :fontawesome-brands-safari:{ .safari } Safari connection error with local dev server
Safari blocks plainHTTP traffic on localhost. If you start Studio with a vanilla `langgraph dev`, the page may report a "Failed to load assistants" error and the browser DevTools will show network errors.
Safari blocks plainHTTP traffic on localhost. If you start Studio with a vanilla
`langgraph dev`, the page may report a "Failed to load assistants" error (or something similar) and the browser DevTools will show network errors.
#### Quick fix — run Studio through a secure Cloudflare tunnel
=== "Python"
```shell
pip install -U langgraph-cli>=0.2.6
pip install -U langgraph-cli>=0.2.6 # Python
langgraph dev --tunnel
```
=== "JS"
```shell
@@ -25,18 +25,17 @@ The command prints a URL like:
```shell
https://smith.langchain.com/studio/?baseUrl=https://hamilton-praise-heart-costumes.trycloudflare.com
```
where
```shell
?baseUrl=https://hamilton-praise-heart-costumes.trycloudflare.com
```
indicates the endpoint where your agent server is exposed.
indicates the endpoint where your agent server is exposed. Open that URL in Safari and Studio should load immediately.
Open that URL in Safari and Studio should load immediately.
#### Alternative — use a Chromiumbased browser
Chrome and other Chromiumbased browsers allow HTTP on localhost, so a plain `langgraph dev` should work without extra steps.
Chrome, Edge, and Brave allow HTTP on localhost, so a plain `langgraph dev` should work without extra steps.
#### If its still not loading
@@ -44,43 +43,3 @@ Chrome and other Chromiumbased browsers allow HTTP on localhost, so a plain
2. Confirm your CLI version with `langgraph --version`.
No other configuration, certificates, or CORS tweaks are required.
## :fontawesome-brands-brave:{ .brave } Brave connection error with local dev server
By default, Brave blocks plainHTTP traffic on localhost if Brave Shields are enabled. If you start Studio with a vanilla `langgraph dev`, the page may report a "Failed to load assistants" error and the browser DevTools will show network errors.
#### Quick fix — disable Brave Shields for LangSmith
Click the Brave icon next to the URL bar and turn off the Brave Shields in the popover.
![Brave Shields](./img/brave-shields.png)
#### Alternative — run Studio through a secure Cloudflare tunnel
=== "Python"
```shell
pip install -U langgraph-cli>=0.2.6
langgraph dev --tunnel
```
=== "JS"
```shell
# Requires @langchain/langgraph-cli>=0.0.26
npx @langchain/langgraph-cli dev
```
The command prints a URL like:
```shell
https://smith.langchain.com/studio/?baseUrl=https://hamilton-praise-heart-costumes.trycloudflare.com
```
where
```shell
?baseUrl=https://hamilton-praise-heart-costumes.trycloudflare.com
```
indicates the endpoint where your agent server is exposed. Open that URL in Brave and Studio should load immediately.
-2
View File
@@ -1,7 +1,5 @@
---
title: Tutorials
search:
boost: 0.5
---
# Tutorials
-4
View File
@@ -1,7 +1,3 @@
---
search:
boost: 2
---
# Workflows and Agents
This guide reviews common patterns for agentic systems. In describing these systems, it can be useful to make a distinction between "workflows" and "agents". One way to think about this difference is nicely explained in [Anthropic's](https://python.langchain.com/docs/integrations/providers/anthropic/) `Building Effective Agents` blog post:
+7 -12
View File
@@ -56,7 +56,6 @@ plugins:
- search:
separator: '[\s\u200b\-,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;'
- autorefs
- tags
- mkdocstrings:
custom_templates: templates
handlers:
@@ -384,24 +383,20 @@ nav:
- Resources:
# NOTE: prebuilt.md is auto-generated by `make build-prebuilt`
- agents/prebuilt.md
- Reference:
- API reference:
- reference/index.md
- LangGraph:
- Library:
- Graphs: reference/graphs.md
- Checkpointing: reference/checkpoints.md
- Storage: reference/store.md
- Types: reference/types.md
- Config: reference/config.md
- Functional API: reference/func.md
- Prebuilt components: reference/prebuilt.md
- Channels: reference/channels.md
- Errors: reference/errors.md
- Types: reference/types.md
- Constants: reference/constants.md
- Pregel: reference/pregel.md
- Channels: reference/channels.md
- Prebuilt:
- Agents: reference/agents.md
- Supervisor: reference/supervisor.md
- Swarm: reference/swarm.md
- MCP Adapters: reference/mcp.md
- Config: reference/config.md
- Functional API: reference/func.md
- LangGraph Platform:
- Server API: "cloud/reference/api/api_ref.md"
- CLI: "cloud/reference/cli.md"
+399 -223
View File
File diff suppressed because it is too large Load Diff
+2 -7
View File
@@ -1,11 +1,10 @@
[tool.poetry]
name = "langgraph-docs"
name = "langgraph-monorepo"
version = "0.0.1"
description = "LangGraph docs"
description = "LangGraph monorepo"
authors = []
license = "MIT"
readme = "README.md"
package-mode = false
[tool.poetry.dependencies]
python = "^3.10"
@@ -21,10 +20,6 @@ langgraph-checkpoint = { path = "../libs/checkpoint/", develop = true }
langgraph-checkpoint-sqlite = { path = "../libs/checkpoint-sqlite", develop = true }
langgraph-checkpoint-postgres = { path = "../libs/checkpoint-postgres", develop = true }
langgraph-sdk = {path = "../libs/sdk-py", develop = true}
# TODO: switch these to published versions
langgraph-supervisor = { git = "https://github.com/langchain-ai/langgraph-supervisor-py" }
langgraph-swarm = { git = "https://github.com/langchain-ai/langgraph-swarm-py" }
langchain-mcp-adapters = { git = "https://github.com/langchain-ai/langchain-mcp-adapters" }
langchain-ollama = "^0.2.3"
mkdocs = "*"
mkdocs-autorefs = "*"
@@ -1,13 +1,8 @@
import os
import tempfile
import nbformat
import pytest
from _scripts.notebook_convert import (
_convert_links_in_markdown,
_has_output,
convert_notebook,
)
@@ -37,41 +32,3 @@ def test_has_output() -> None:
def test_link_conversion(source: str, expected: str) -> None:
"""Test logic to convert links in markdown cells."""
assert _convert_links_in_markdown(source) == expected
EXPECTED_OUTPUT = """\
```shell
pip install -U langgraph
```
```python
print('Hello')
```\
"""
def test_converting_cell_magic() -> None:
"""Test converting cell magic to code blocks."""
with tempfile.TemporaryDirectory() as tmpdir:
nb_path = os.path.join(tmpdir, "test_notebook.ipynb")
# Create a minimal notebook object
nb = nbformat.v4.new_notebook()
nb.cells = [
nbformat.v4.new_code_cell(
"%%capture --no-stderr\n"
"%pip install -U langgraph"
),
nbformat.v4.new_code_cell("print('Hello')"),
]
nb.metadata["language_info"] = {"name": "python"}
# Write to file
with open(nb_path, "w", encoding="utf-8") as f:
nbformat.write(nb, f)
# Run the conversion
converted = convert_notebook(nb_path)
assert converted == EXPECTED_OUTPUT
@@ -27,8 +27,6 @@ Conn = _internal.Conn # For backward compatibility
class PostgresSaver(BasePostgresSaver):
"""Checkpointer that stores checkpoints in a Postgres database."""
lock: threading.Lock
def __init__(
@@ -27,8 +27,6 @@ Conn = _ainternal.Conn # For backward compatibility
class AsyncPostgresSaver(BasePostgresSaver):
"""Asynchronous checkpointer that stores checkpoints in a Postgres database."""
lock: asyncio.Lock
def __init__(
+1 -1
View File
@@ -425,7 +425,7 @@ typing-extensions = ">=4.7"
[[package]]
name = "langgraph-checkpoint"
version = "2.0.25"
version = "2.0.24"
description = "Library with base interfaces for LangGraph checkpoint savers."
optional = false
python-versions = ">=3.9"
+1 -1
View File
@@ -357,7 +357,7 @@ typing-extensions = ">=4.7"
[[package]]
name = "langgraph-checkpoint"
version = "2.0.25"
version = "2.0.24"
description = "Library with base interfaces for LangGraph checkpoint savers."
optional = false
python-versions = ">=3.9"
@@ -34,8 +34,6 @@ EMPTY_BYTES = b""
class JsonPlusSerializer(SerializerProtocol):
"""Serializer that uses ormsgpack, with a fallback to extended JSON serializer."""
def __init__(
self, *, __unpack_ext_hook__: Optional[Callable[[int, bytes], Any]] = None
) -> None:
@@ -1,5 +1,3 @@
"""Utilities for batching operations in a background task."""
import asyncio
import functools
import weakref
@@ -13,8 +13,6 @@ C = TypeVar("C")
class BaseChannel(Generic[Value, Update, C], ABC):
"""Base class for all channels."""
__slots__ = ("key", "typ")
def __init__(self, typ: Any, key: str = "") -> None:
+1 -5
View File
@@ -14,10 +14,8 @@ EMPTY_SEQ: tuple[str, ...] = tuple()
MISSING = object()
# --- Public constants ---
TAG_NOSTREAM = sys.intern("nostream")
TAG_NOSTREAM = sys.intern("langsmith:nostream")
"""Tag to disable streaming for a chat model."""
TAG_NOSTREAM_ALT = sys.intern("langsmith:nostream")
"""Tag to disable streaming for a chat model. (Deprecated in favour of "nostream")"""
TAG_HIDDEN = sys.intern("langsmith:hidden")
"""Tag to hide a node/edge from certain tracing/streaming environments."""
START = sys.intern("__start__")
@@ -104,8 +102,6 @@ CONF = cast(Literal["configurable"], sys.intern("configurable"))
# key for the configurable dict in RunnableConfig
NULL_TASK_ID = sys.intern("00000000-0000-0000-0000-000000000000")
# the task_id to use for writes that are not associated with a task
CONFIG_KEY_RESUME_MAP = sys.intern("__pregel_resume_map")
# holds a mapping of task ns -> resume value for resuming tasks
RESERVED = {
TAG_HIDDEN,
-34
View File
@@ -76,15 +76,6 @@ class Graph:
*,
metadata: Optional[dict[str, Any]] = None,
) -> Self:
"""Add a new node to the graph.
Args:
node (Union[str, RunnableLike]): The function or runnable this node will run.
If a string is provided, it will be used as the node name, and action will be used as the function or runnable.
action (Optional[RunnableLike]): The action associated with the node. (default: None)
Will be used as the node function or runnable if `node` is a string (node name).
metadata (Optional[dict[str, Any]]): The metadata associated with the node. (default: None)
"""
if isinstance(node, str):
for character in (NS_SEP, NS_END):
if character in node:
@@ -119,12 +110,6 @@ class Graph:
return self
def add_edge(self, start_key: str, end_key: str) -> Self:
"""Add a directed edge from the start node to the end node.
Args:
start_key (str): The key of the start node of the edge.
end_key (str): The key of the end node of the edge.
"""
if self.compiled:
logger.warning(
"Adding an edge to a graph that has already been compiled. This will "
@@ -317,25 +302,6 @@ class Graph:
debug: bool = False,
name: Optional[str] = None,
) -> "CompiledGraph":
"""Compiles the graph into a `CompiledGraph` object.
The compiled graph implements the `Runnable` interface and can be invoked,
streamed, batched, and run asynchronously.
Args:
checkpointer (Optional[Union[Checkpointer, Literal[False]]]): A checkpoint saver object or flag.
If provided, this Checkpointer serves as a fully versioned "short-term memory" for the graph,
allowing it to be paused, resumed, and replayed from any point.
If None, it may inherit the parent graph's checkpointer when used as a subgraph.
If False, it will not use or inherit any checkpointer.
interrupt_before (Optional[Sequence[str]]): An optional list of node names to interrupt before.
interrupt_after (Optional[Sequence[str]]): An optional list of node names to interrupt after.
debug (bool): A flag indicating whether to enable debug mode.
name (Optional[str]): The name to use for the compiled graph.
Returns:
CompiledGraph: The compiled graph.
"""
# assign default values
interrupt_before = interrupt_before or []
interrupt_after = interrupt_after or []
+88 -19
View File
@@ -1,7 +1,6 @@
import functools
import logging
import weakref
from dataclasses import is_dataclass
from inspect import isclass
from typing import (
Annotated,
@@ -14,8 +13,8 @@ from typing import (
get_type_hints,
)
from pydantic import BaseModel, ConfigDict, TypeAdapter
from typing_extensions import is_typeddict
from pydantic import BaseModel
from pydantic.v1 import BaseModel as BaseModelV1
__all__ = ["SchemaCoercionMapper"]
@@ -46,7 +45,7 @@ class SchemaCoercionMapper:
def __init__(
self,
schema: type[BaseModel],
schema: type[Any],
type_hints: Optional[dict[str, Any]] = None,
*,
max_depth: int = 12,
@@ -64,12 +63,30 @@ class SchemaCoercionMapper:
else get_type_hints(schema, localns={schema.__name__: schema})
)
if issubclass(schema, BaseModel):
if issubclass(schema, BaseModelV1):
self._fields = {
n: self.type_hints.get(n, f.annotation)
for n, f in schema.__fields__.items()
}
self._construct = schema.construct
unhandled_attrs = (
"__pre_root_validators__",
"__post_root_validators__",
"__validators__",
)
if any(getattr(schema, c, None) for c in unhandled_attrs):
self.coerce: Callable[[Any, Any], Union[BaseModelV1, BaseModel]] = (
lambda v, _: schema(**v)
)
else:
self.coerce = self._coerce
elif issubclass(schema, BaseModel):
self._fields = {
n: self.type_hints.get(n, f.annotation)
for n, f in schema.model_fields.items()
}
self._construct: Callable[..., Any] = schema.model_construct
self._construct: Callable[..., Any] = schema.model_construct # type: ignore
unhandled_attrs = ("validators", "field_validators", "root_validators")
if (decorators := getattr(schema, "__pydantic_decorators__", None)) and any(
getattr(decorators, attr, None) for attr in unhandled_attrs
@@ -77,8 +94,9 @@ class SchemaCoercionMapper:
self.coerce = lambda v, _: schema.model_validate(v)
else:
self.coerce = self._coerce
else:
raise TypeError("Schema must be a Pydantic V2 model.")
raise TypeError("Schema is neither a Pydantic v1 nor v2 model.")
self._field_coercers: Optional[dict[str, Callable[[Any, int], Any]]] = None
@@ -120,12 +138,14 @@ class SchemaCoercionMapper:
if isclass(field_type):
# This is needed bcs. of issubclass issues on older versions of python
is_class_ = True
try:
is_bm_subclass = issubclass(field_type, BaseModel)
is_bm_v2 = issubclass(field_type, BaseModel)
except TypeError:
# python < 3.11 issue.
is_bm_subclass = False
if is_bm_subclass:
is_class_ = False
is_bm_v2 = False
if is_bm_v2 or (is_class_ and issubclass(field_type, BaseModelV1)):
mapper = SchemaCoercionMapper(field_type, max_depth=depth - 1)
return lambda v, d: mapper.coerce(v, d) if isinstance(v, dict) else v
@@ -245,18 +265,67 @@ _IDENTITY_TYPES: tuple[type[Any], ...] = (
type(None),
)
try:
# Pydantic v2.
from pydantic import TypeAdapter
@functools.lru_cache(maxsize=2048)
def _adapter_for(tp: Any) -> Callable[[Any], Any]: # noqa: D401
try:
config = (
None
if (issubclass(tp, BaseModel) or is_dataclass(tp) or is_typeddict(tp))
else ConfigDict(arbitrary_types_allowed=True)
import pydantic.v1.types as v1_types_
from pydantic.v1 import parse_obj_as
v1_types = tuple(
v for k, v in vars(v1_types_).items() if k in v1_types_.__all__
)
except TypeError:
config = None
return TypeAdapter(tp, config=config).validate_python
except ImportError:
v1_types = ()
def parse_obj_as(tp: Any, v: Any) -> Any: # type: ignore
return v
try:
from pydantic.v1 import parse_obj_as
from pydantic.v1.main import create_model
except ImportError:
create_model = None # type: ignore
def _get_v1_parser(tp: Any) -> Any:
if create_model is not None:
try:
parser = create_model(
f"ParsingModel[{tp}]",
__root__=(tp, ...),
)
return lambda v: parser(__root__=v).__root__ # type: ignore
except RuntimeError:
return lambda v: v
return lambda v: parse_obj_as(tp, v)
@functools.lru_cache(maxsize=2048)
def _adapter_for(tp: Any) -> Callable[[Any], Any]: # noqa: D401
if tp in v1_types:
return _get_v1_parser(tp)
try:
return TypeAdapter(
tp, config={"arbitrary_types_allowed": True}
).validate_python
except TypeError:
# Delayed classes like ConstrainedList
return _get_v1_parser(tp)
except ImportError:
# Pydantic V1
from pydantic.v1.main import create_model
@functools.lru_cache(maxsize=2048)
def _adapter_for(tp: Any) -> Callable[[Any], Any]: # noqa: D401
try:
parser = create_model(
f"ParsingModel[{tp}]",
__root__=(tp, ...),
)
return lambda v: parser(__root__=v).__root__ # type: ignore
except RuntimeError:
return lambda v: v
def _get_adapter(tp: Any) -> Callable[[Any], Any]:
+91 -80
View File
@@ -23,6 +23,7 @@ from typing import (
from langchain_core.runnables import Runnable, RunnableConfig
from pydantic import BaseModel
from pydantic.v1 import BaseModel as BaseModelV1
from typing_extensions import Self
from langgraph._api.deprecation import LangGraphDeprecationWarning
@@ -122,44 +123,42 @@ class StateGraph(Graph):
config_schema (Optional[Type[Any]]): The schema class that defines the configuration.
Use this to expose configurable parameters in your API.
Example:
```python
from langchain_core.runnables import RunnableConfig
from typing_extensions import Annotated, TypedDict
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph
def reducer(a: list, b: int | None) -> list:
if b is not None:
return a + [b]
return a
class State(TypedDict):
x: Annotated[list, reducer]
class ConfigSchema(TypedDict):
r: float
graph = StateGraph(State, config_schema=ConfigSchema)
def node(state: State, config: RunnableConfig) -> dict:
r = config["configurable"].get("r", 1.0)
x = state["x"][-1]
next_value = x * r * (1 - x)
return {"x": next_value}
graph.add_node("A", node)
graph.set_entry_point("A")
graph.set_finish_point("A")
compiled = graph.compile()
print(compiled.config_specs)
# [ConfigurableFieldSpec(id='r', annotation=<class 'float'>, name=None, description=None, default=None, is_shared=False, dependencies=None)]
step1 = compiled.invoke({"x": 0.5}, {"configurable": {"r": 3.0}})
# {'x': [0.5, 0.75]}
```
"""
Examples:
>>> from langchain_core.runnables import RunnableConfig
>>> from typing_extensions import Annotated, TypedDict
>>> from langgraph.checkpoint.memory import MemorySaver
>>> from langgraph.graph import StateGraph
>>>
>>> def reducer(a: list, b: int | None) -> list:
... if b is not None:
... return a + [b]
... return a
>>>
>>> class State(TypedDict):
... x: Annotated[list, reducer]
>>>
>>> class ConfigSchema(TypedDict):
... r: float
>>>
>>> graph = StateGraph(State, config_schema=ConfigSchema)
>>>
>>> def node(state: State, config: RunnableConfig) -> dict:
... r = config["configurable"].get("r", 1.0)
... x = state["x"][-1]
... next_value = x * r * (1 - x)
... return {"x": next_value}
>>>
>>> graph.add_node("A", node)
>>> graph.set_entry_point("A")
>>> graph.set_finish_point("A")
>>> compiled = graph.compile()
>>>
>>> print(compiled.config_specs)
[ConfigurableFieldSpec(id='r', annotation=<class 'float'>, name=None, description=None, default=None, is_shared=False, dependencies=None)]
>>>
>>> step1 = compiled.invoke({"x": 0.5}, {"configurable": {"r": 3.0}})
>>> print(step1)
{'x': [0.5, 0.75]}"""
nodes: dict[str, StateNodeSpec] # type: ignore[assignment]
channels: dict[str, BaseChannel]
@@ -252,8 +251,17 @@ class StateGraph(Graph):
retry: Optional[Union[RetryPolicy, Sequence[RetryPolicy]]] = None,
destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None,
) -> Self:
"""Add a new node to the state graph.
"""Adds a new node to the state graph.
Will take the name of the function/runnable as the node name.
Args:
node (RunnableLike): The function or runnable this node will run.
Raises:
ValueError: If the key is already being used as a state key.
Returns:
Self: The instance of the state graph, allowing for method chaining.
"""
...
@@ -268,7 +276,18 @@ class StateGraph(Graph):
retry: Optional[Union[RetryPolicy, Sequence[RetryPolicy]]] = None,
destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None,
) -> Self:
"""Add a new node to the state graph."""
"""Adds a new node to the state graph.
Args:
node (str): The key of the node.
action (RunnableLike): The action associated with the node.
Raises:
ValueError: If the key is already being used as a state key.
Returns:
Self: The instance of the state graph, allowing for method chaining.
"""
...
def add_node(
@@ -281,13 +300,13 @@ class StateGraph(Graph):
retry: Optional[Union[RetryPolicy, Sequence[RetryPolicy]]] = None,
destinations: Optional[Union[dict[str, str], tuple[str, ...]]] = None,
) -> Self:
"""Add a new node to the state graph.
"""Adds a new node to the state graph.
Will take the name of the function/runnable as the node name.
Args:
node (Union[str, RunnableLike]): The function or runnable this node will run.
If a string is provided, it will be used as the node name, and action will be used as the function or runnable.
action (Optional[RunnableLike]): The action associated with the node. (default: None)
Will be used as the node function or runnable if `node` is a string (node name).
metadata (Optional[dict[str, Any]]): The metadata associated with the node. (default: None)
input (Optional[Type[Any]]): The input schema for the node. (default: the graph's input schema)
retry (Optional[Union[RetryPolicy, Sequence[RetryPolicy]]]): The policy for retrying the node. (default: None)
@@ -300,29 +319,29 @@ class StateGraph(Graph):
Raises:
ValueError: If the key is already being used as a state key.
Example:
```python
from langgraph.graph import START, StateGraph
def my_node(state, config):
return {"x": state["x"] + 1}
builder = StateGraph(dict)
builder.add_node(my_node) # node name will be 'my_node'
builder.add_edge(START, "my_node")
graph = builder.compile()
graph.invoke({"x": 1})
# {'x': 2}
Examples:
```pycon
>>> from langgraph.graph import START, StateGraph
...
>>> def my_node(state, config):
... return {"x": state["x"] + 1}
...
>>> builder = StateGraph(dict)
>>> builder.add_node(my_node) # node name will be 'my_node'
>>> builder.add_edge(START, "my_node")
>>> graph = builder.compile()
>>> graph.invoke({"x": 1})
{'x': 2}
```
Customize the name:
Example: Customize the name:
```python
builder = StateGraph(dict)
builder.add_node("my_fair_node", my_node)
builder.add_edge(START, "my_fair_node")
graph = builder.compile()
graph.invoke({"x": 1})
# {'x': 2}
```pycon
>>> builder = StateGraph(dict)
>>> builder.add_node("my_fair_node", my_node)
>>> builder.add_edge(START, "my_fair_node")
>>> graph = builder.compile()
>>> graph.invoke({"x": 1})
{'x': 2}
```
Returns:
@@ -425,7 +444,7 @@ class StateGraph(Graph):
return self
def add_edge(self, start_key: Union[str, list[str]], end_key: str) -> Self:
"""Add a directed edge from the start node (or list of start nodes) to the end node.
"""Adds a directed edge from the start node (or list of start nodes) to the end node.
When a single start node is provided, the graph will wait for that node to complete
before executing the end node. When multiple start nodes are provided,
@@ -565,7 +584,7 @@ class StateGraph(Graph):
debug: bool = False,
name: Optional[str] = None,
) -> "CompiledStateGraph":
"""Compiles the state graph into a `CompiledStateGraph` object.
"""Compiles the state graph into a `CompiledGraph` object.
The compiled graph implements the `Runnable` interface and can be invoked,
streamed, batched, and run asynchronously.
@@ -579,7 +598,6 @@ class StateGraph(Graph):
interrupt_before (Optional[Sequence[str]]): An optional list of node names to interrupt before.
interrupt_after (Optional[Sequence[str]]): An optional list of node names to interrupt after.
debug (bool): A flag indicating whether to enable debug mode.
name (Optional[str]): The name to use for the compiled graph.
Returns:
CompiledStateGraph: The compiled state graph.
@@ -624,7 +642,7 @@ class StateGraph(Graph):
self.input
if len(self.channels) > 1
and isclass(self.input)
and issubclass(self.input, BaseModel)
and issubclass(self.input, (BaseModel, BaseModelV1))
else None
),
nodes={},
@@ -829,9 +847,7 @@ class CompiledStateGraph(CompiledGraph):
) -> Sequence[Union[ChannelWriteEntry, Send]]:
writes = [
(
ChannelWriteEntry(
p if p == END else CHANNEL_BRANCH_TO.format(p), None
)
ChannelWriteEntry(CHANNEL_BRANCH_TO.format(p), None)
if not isinstance(p, Send)
else p
)
@@ -1010,7 +1026,7 @@ def _pick_mapper(
if isclass(schema):
if issubclass(schema, dict):
return None
if issubclass(schema, BaseModel):
if issubclass(schema, (BaseModel, BaseModelV1)):
return SchemaCoercionMapper(schema, type_hints=type_hints)
return partial(_coerce_state, schema)
@@ -1051,14 +1067,9 @@ def _control_static(
ends: Union[tuple[str, ...], dict[str, str]],
) -> Sequence[tuple[str, Any, Optional[str]]]:
if isinstance(ends, dict):
return [
(k if k == END else CHANNEL_BRANCH_TO.format(k), None, label)
for k, label in ends.items()
]
return [(CHANNEL_BRANCH_TO.format(k), None, label) for k, label in ends.items()]
else:
return [
(e if e == END else CHANNEL_BRANCH_TO.format(e), None, None) for e in ends
]
return [(CHANNEL_BRANCH_TO.format(e), None, None) for e in ends]
def _get_root(input: Any) -> Optional[Sequence[tuple[str, Any]]]:
@@ -1193,7 +1204,7 @@ def _get_schema(
channels: dict,
name: str,
) -> type[BaseModel]:
if isclass(typ) and issubclass(typ, BaseModel):
if isclass(typ) and issubclass(typ, (BaseModel, BaseModelV1)):
return typ
else:
keys = list(schemas[typ].keys())
+3 -10
View File
@@ -1,4 +1,4 @@
from typing import Any, Literal, Optional, Union, cast
from typing import Any, Literal, Optional, Union
from uuid import uuid4
from langchain_core.messages import AnyMessage
@@ -55,7 +55,6 @@ def push_ui_message(
metadata: Optional[dict[str, Any]] = None,
message: Optional[AnyMessage] = None,
state_key: str = "ui",
merge: bool = False,
) -> UIMessage:
"""Push a new UI message to update the UI state.
@@ -101,10 +100,10 @@ def push_ui_message(
"name": name,
"props": props,
"metadata": {
"merge": merge,
"run_id": config.get("run_id", None),
**(config.get("metadata") or {}),
"tags": config.get("tags", None),
"name": config.get("run_name", None),
"run_id": config.get("run_id", None),
**(metadata or {}),
**({"message_id": message_id} if message_id else {}),
},
@@ -193,12 +192,6 @@ def ui_message_reducer(
ids_to_remove.add(msg_id)
else:
ids_to_remove.discard(msg_id)
if cast(UIMessage, msg).get("metadata", {}).get("merge", False):
prev_msg = merged[existing_idx]
msg = msg.copy()
msg["props"] = {**prev_msg["props"], **msg["props"]}
merged[existing_idx] = msg
else:
if msg.get("type") == "remove-ui":
+192 -218
View File
@@ -230,15 +230,15 @@ class Pregel(PregelProtocol):
## Actors
An **actor** is a `PregelNode`.
An **actor** is a [PregelNode][langgraph.pregel.read.PregelNode].
It subscribes to channels, reads data from them, and writes data to them.
It can be thought of as an **actor** in the Pregel algorithm.
`PregelNodes` implement LangChain's
[PregelNodes][langgraph.pregel.read.PregelNode] implement LangChain's
Runnable interface.
## Channels
Channels are used to communicate between actors (`PregelNodes`).
Channels are used to communicate between actors (PregelNodes).
Each channel has a value type, an update type, and an update function which
takes a sequence of updates and
modifies the stored value. Channels can be used to send data from one chain to
@@ -560,7 +560,7 @@ class Pregel(PregelProtocol):
def get_graph(
self, config: RunnableConfig | None = None, *, xray: int | bool = False
) -> Graph:
"""Return a drawable representation of the computation graph."""
"""Returns a drawable representation of the computation graph."""
# gather subgraphs
if xray:
subgraphs = {
@@ -588,7 +588,7 @@ class Pregel(PregelProtocol):
async def aget_graph(
self, config: RunnableConfig | None = None, *, xray: int | bool = False
) -> Graph:
"""Return a drawable representation of the computation graph."""
"""Returns a drawable representation of the computation graph."""
# gather subgraphs
if xray:
@@ -639,7 +639,6 @@ class Pregel(PregelProtocol):
return self.__class__(**attrs)
def with_config(self, config: RunnableConfig | None = None, **kwargs: Any) -> Self:
"""Create a copy of the Pregel object with an updated config."""
return self.copy(
{"config": merge_configs(self.config, config, cast(RunnableConfig, kwargs))}
)
@@ -802,16 +801,6 @@ class Pregel(PregelProtocol):
def get_subgraphs(
self, *, namespace: str | None = None, recurse: bool = False
) -> Iterator[tuple[str, PregelProtocol]]:
"""Get the subgraphs of the graph.
Args:
namespace (Optional[str]): The namespace to filter the subgraphs by.
recurse (bool): Whether to recurse into the subgraphs.
If False, only the immediate subgraphs will be returned.
Returns:
Iterator[tuple[str, PregelProtocol]]: An iterator of the (namespace, subgraph) pairs.
"""
for name, node in self.nodes.items():
# filter by prefix
if namespace is not None:
@@ -841,16 +830,6 @@ class Pregel(PregelProtocol):
async def aget_subgraphs(
self, *, namespace: str | None = None, recurse: bool = False
) -> AsyncIterator[tuple[str, PregelProtocol]]:
"""Get the subgraphs of the graph.
Args:
namespace (Optional[str]): The namespace to filter the subgraphs by.
recurse (bool): Whether to recurse into the subgraphs.
If False, only the immediate subgraphs will be returned.
Returns:
AsyncIterator[tuple[str, PregelProtocol]]: An iterator of the (namespace, subgraph) pairs.
"""
for name, node in self.get_subgraphs(namespace=namespace, recurse=recurse):
yield name, node
@@ -874,7 +853,6 @@ class Pregel(PregelProtocol):
created_at=None,
parent_config=None,
tasks=(),
interrupts=(),
)
# migrate checkpoint if needed
@@ -959,12 +937,6 @@ class Pregel(PregelProtocol):
next_tasks[tid].writes.append((k, v))
if tasks := [t for t in next_tasks.values() if t.writes]:
apply_writes(saved.checkpoint, channels, tasks, None)
tasks_with_writes = tasks_w_writes(
next_tasks.values(),
saved.pending_writes,
task_states,
self.stream_channels_asis,
)
# assemble the state snapshot
return StateSnapshot(
read_channels(channels, self.stream_channels_asis),
@@ -973,8 +945,12 @@ class Pregel(PregelProtocol):
saved.metadata,
saved.checkpoint["ts"],
patch_checkpoint_map(saved.parent_config, saved.metadata),
tasks_with_writes,
tuple([i for task in tasks_with_writes for i in task.interrupts]),
tasks_w_writes(
next_tasks.values(),
saved.pending_writes,
task_states,
self.stream_channels_asis,
),
)
async def _aprepare_state_snapshot(
@@ -993,7 +969,6 @@ class Pregel(PregelProtocol):
created_at=None,
parent_config=None,
tasks=(),
interrupts=(),
)
# migrate checkpoint if needed
@@ -1081,13 +1056,6 @@ class Pregel(PregelProtocol):
next_tasks[tid].writes.append((k, v))
if tasks := [t for t in next_tasks.values() if t.writes]:
apply_writes(saved.checkpoint, channels, tasks, None)
tasks_with_writes = tasks_w_writes(
next_tasks.values(),
saved.pending_writes,
task_states,
self.stream_channels_asis,
)
# assemble the state snapshot
return StateSnapshot(
read_channels(channels, self.stream_channels_asis),
@@ -1096,8 +1064,12 @@ class Pregel(PregelProtocol):
saved.metadata,
saved.checkpoint["ts"],
patch_checkpoint_map(saved.parent_config, saved.metadata),
tasks_with_writes,
tuple([i for task in tasks_with_writes for i in task.interrupts]),
tasks_w_writes(
next_tasks.values(),
saved.pending_writes,
task_states,
self.stream_channels_asis,
),
)
def get_state(
@@ -1192,8 +1164,8 @@ class Pregel(PregelProtocol):
before: RunnableConfig | None = None,
limit: int | None = None,
) -> Iterator[StateSnapshot]:
"""Get the history of the state of the graph."""
config = ensure_config(config)
"""Get the history of the state of the graph."""
checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get(
CONFIG_KEY_CHECKPOINTER, self.checkpointer
)
@@ -1243,8 +1215,8 @@ class Pregel(PregelProtocol):
before: RunnableConfig | None = None,
limit: int | None = None,
) -> AsyncIterator[StateSnapshot]:
"""Asynchronously get the history of the state of the graph."""
config = ensure_config(config)
"""Get the history of the state of the graph."""
checkpointer: BaseCheckpointSaver | None = ensure_config(config)[CONF].get(
CONFIG_KEY_CHECKPOINTER, self.checkpointer
)
@@ -1709,7 +1681,7 @@ class Pregel(PregelProtocol):
config: RunnableConfig,
supersteps: Sequence[Sequence[StateUpdate]],
) -> RunnableConfig:
"""Asynchronously apply updates to the graph state in bulk. Requires a checkpointer to be set.
"""Apply updates to the graph state in bulk. Requires a checkpointer to be set.
Args:
config: The config to apply the updates to.
@@ -2134,7 +2106,7 @@ class Pregel(PregelProtocol):
values: dict[str, Any] | Any,
as_node: str | None = None,
) -> RunnableConfig:
"""Asynchronously update the state of the graph with the given values, as if they came from
"""Update the state of the graph asynchronously with the given values, as if they came from
node `as_node`. If `as_node` is not provided, it will be set to the last node
that updated the state, if not ambiguous.
"""
@@ -2237,100 +2209,101 @@ class Pregel(PregelProtocol):
Yields:
The output of each step in the graph. The output shape depends on the stream_mode.
Example: Using stream_mode="values":
```python
import operator
from typing_extensions import Annotated, TypedDict
from langgraph.graph import StateGraph, START
Examples:
Using different stream modes with a graph:
```pycon
>>> import operator
>>> from typing_extensions import Annotated, TypedDict
>>> from langgraph.graph import StateGraph, START
...
>>> class State(TypedDict):
... alist: Annotated[list, operator.add]
... another_list: Annotated[list, operator.add]
...
>>> builder = StateGraph(State)
>>> builder.add_node("a", lambda _state: {"another_list": ["hi"]})
>>> builder.add_node("b", lambda _state: {"alist": ["there"]})
>>> builder.add_edge("a", "b")
>>> builder.add_edge(START, "a")
>>> graph = builder.compile()
```
With stream_mode="values":
class State(TypedDict):
alist: Annotated[list, operator.add]
another_list: Annotated[list, operator.add]
```pycon
>>> for event in graph.stream({"alist": ['Ex for stream_mode="values"']}, stream_mode="values"):
... print(event)
{'alist': ['Ex for stream_mode="values"'], 'another_list': []}
{'alist': ['Ex for stream_mode="values"'], 'another_list': ['hi']}
{'alist': ['Ex for stream_mode="values"', 'there'], 'another_list': ['hi']}
```
With stream_mode="updates":
builder = StateGraph(State)
builder.add_node("a", lambda _state: {"another_list": ["hi"]})
builder.add_node("b", lambda _state: {"alist": ["there"]})
builder.add_edge("a", "b")
builder.add_edge(START, "a")
graph = builder.compile()
```pycon
>>> for event in graph.stream({"alist": ['Ex for stream_mode="updates"']}, stream_mode="updates"):
... print(event)
{'a': {'another_list': ['hi']}}
{'b': {'alist': ['there']}}
```
With stream_mode="debug":
for event in graph.stream({"alist": ['Ex for stream_mode="values"']}, stream_mode="values"):
print(event)
# {'alist': ['Ex for stream_mode="values"'], 'another_list': []}
# {'alist': ['Ex for stream_mode="values"'], 'another_list': ['hi']}
# {'alist': ['Ex for stream_mode="values"', 'there'], 'another_list': ['hi']}
```pycon
>>> for event in graph.stream({"alist": ['Ex for stream_mode="debug"']}, stream_mode="debug"):
... print(event)
{'type': 'task', 'timestamp': '2024-06-23T...+00:00', 'step': 1, 'payload': {'id': '...', 'name': 'a', 'input': {'alist': ['Ex for stream_mode="debug"'], 'another_list': []}, 'triggers': ['start:a']}}
{'type': 'task_result', 'timestamp': '2024-06-23T...+00:00', 'step': 1, 'payload': {'id': '...', 'name': 'a', 'result': [('another_list', ['hi'])]}}
{'type': 'task', 'timestamp': '2024-06-23T...+00:00', 'step': 2, 'payload': {'id': '...', 'name': 'b', 'input': {'alist': ['Ex for stream_mode="debug"'], 'another_list': ['hi']}, 'triggers': ['a']}}
{'type': 'task_result', 'timestamp': '2024-06-23T...+00:00', 'step': 2, 'payload': {'id': '...', 'name': 'b', 'result': [('alist', ['there'])]}}
```
Example: Using stream_mode="updates":
```python
for event in graph.stream({"alist": ['Ex for stream_mode="updates"']}, stream_mode="updates"):
print(event)
With stream_mode="custom":
# {'a': {'another_list': ['hi']}}
# {'b': {'alist': ['there']}}
```pycon
>>> from langgraph.types import StreamWriter
...
>>> def node_a(state: State, writer: StreamWriter):
... writer({"custom_data": "foo"})
... return {"alist": ["hi"]}
...
>>> builder = StateGraph(State)
>>> builder.add_node("a", node_a)
>>> builder.add_edge(START, "a")
>>> graph = builder.compile()
...
>>> for event in graph.stream({"alist": ['Ex for stream_mode="custom"']}, stream_mode="custom"):
... print(event)
{'custom_data': 'foo'}
```
Example: Using stream_mode="debug":
```python
for event in graph.stream({"alist": ['Ex for stream_mode="debug"']}, stream_mode="debug"):
print(event)
With stream_mode="messages":
# {'type': 'task', 'timestamp': '2024-06-23T...+00:00', 'step': 1, 'payload': {'id': '...', 'name': 'a', 'input': {'alist': ['Ex for stream_mode="debug"'], 'another_list': []}, 'triggers': ['start:a']}}
# {'type': 'task_result', 'timestamp': '2024-06-23T...+00:00', 'step': 1, 'payload': {'id': '...', 'name': 'a', 'result': [('another_list', ['hi'])]}}
# {'type': 'task', 'timestamp': '2024-06-23T...+00:00', 'step': 2, 'payload': {'id': '...', 'name': 'b', 'input': {'alist': ['Ex for stream_mode="debug"'], 'another_list': ['hi']}, 'triggers': ['a']}}
# {'type': 'task_result', 'timestamp': '2024-06-23T...+00:00', 'step': 2, 'payload': {'id': '...', 'name': 'b', 'result': [('alist', ['there'])]}}
```
```pycon
>>> from typing_extensions import Annotated, TypedDict
>>> from langgraph.graph import StateGraph, START
>>> from langchain_openai import ChatOpenAI
...
>>> llm = ChatOpenAI(model="gpt-4o-mini")
...
>>> class State(TypedDict):
... question: str
... answer: str
...
>>> def node_a(state: State):
... response = llm.invoke(state["question"])
... return {"answer": response.content}
...
>>> builder = StateGraph(State)
>>> builder.add_node("a", node_a)
>>> builder.add_edge(START, "a")
>>> graph = builder.compile()
Example: Using stream_mode="custom":
```python
from langgraph.types import StreamWriter
def node_a(state: State, writer: StreamWriter):
writer({"custom_data": "foo"})
return {"alist": ["hi"]}
builder = StateGraph(State)
builder.add_node("a", node_a)
builder.add_edge(START, "a")
graph = builder.compile()
for event in graph.stream({"alist": ['Ex for stream_mode="custom"']}, stream_mode="custom"):
print(event)
# {'custom_data': 'foo'}
```
Example: Using stream_mode="messages":
```python
from typing_extensions import Annotated, TypedDict
from langgraph.graph import StateGraph, START
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
class State(TypedDict):
question: str
answer: str
def node_a(state: State):
response = llm.invoke(state["question"])
return {"answer": response.content}
builder = StateGraph(State)
builder.add_node("a", node_a)
builder.add_edge(START, "a")
graph = builder.compile()
for event in graph.stream({"question": "What is the capital of France?"}, stream_mode="messages"):
print(event)
# (AIMessageChunk(content='The', additional_kwargs={}, response_metadata={}, id='...'), {'langgraph_step': 1, 'langgraph_node': 'a', 'langgraph_triggers': ['start:a'], 'langgraph_path': ('__pregel_pull', 'a'), 'langgraph_checkpoint_ns': '...', 'checkpoint_ns': '...', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o-mini', 'ls_model_type': 'chat', 'ls_temperature': 0.7})
# (AIMessageChunk(content=' capital', additional_kwargs={}, response_metadata={}, id='...'), {'langgraph_step': 1, 'langgraph_node': 'a', 'langgraph_triggers': ['start:a'], ...})
# (AIMessageChunk(content=' of', additional_kwargs={}, response_metadata={}, id='...'), {...})
# (AIMessageChunk(content=' France', additional_kwargs={}, response_metadata={}, id='...'), {...})
# (AIMessageChunk(content=' is', additional_kwargs={}, response_metadata={}, id='...'), {...})
# (AIMessageChunk(content=' Paris', additional_kwargs={}, response_metadata={}, id='...'), {...})
>>> for event in graph.stream({"question": "What is the capital of France?"}, stream_mode="messages"):
... print(event)
(AIMessageChunk(content='The', additional_kwargs={}, response_metadata={}, id='...'), {'langgraph_step': 1, 'langgraph_node': 'a', 'langgraph_triggers': ['start:a'], 'langgraph_path': ('__pregel_pull', 'a'), 'langgraph_checkpoint_ns': '...', 'checkpoint_ns': '...', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o-mini', 'ls_model_type': 'chat', 'ls_temperature': 0.7})
(AIMessageChunk(content=' capital', additional_kwargs={}, response_metadata={}, id='...'), {'langgraph_step': 1, 'langgraph_node': 'a', 'langgraph_triggers': ['start:a'], ...})
(AIMessageChunk(content=' of', additional_kwargs={}, response_metadata={}, id='...'), {...})
(AIMessageChunk(content=' France', additional_kwargs={}, response_metadata={}, id='...'), {...})
(AIMessageChunk(content=' is', additional_kwargs={}, response_metadata={}, id='...'), {...})
(AIMessageChunk(content=' Paris', additional_kwargs={}, response_metadata={}, id='...'), {...})
```
"""
@@ -2498,7 +2471,7 @@ class Pregel(PregelProtocol):
debug: bool | None = None,
subgraphs: bool = False,
) -> AsyncIterator[dict[str, Any] | Any]:
"""Asynchronously stream graph steps for a single input.
"""Stream graph steps for a single input.
Args:
input: The input to the graph.
@@ -2523,100 +2496,101 @@ class Pregel(PregelProtocol):
Yields:
The output of each step in the graph. The output shape depends on the stream_mode.
Example: Using stream_mode="values":
```python
import operator
from typing_extensions import Annotated, TypedDict
from langgraph.graph import StateGraph, START
Examples:
Using different stream modes with a graph:
```pycon
>>> import operator
>>> from typing_extensions import Annotated, TypedDict
>>> from langgraph.graph import StateGraph, START
...
>>> class State(TypedDict):
... alist: Annotated[list, operator.add]
... another_list: Annotated[list, operator.add]
...
>>> builder = StateGraph(State)
>>> builder.add_node("a", lambda _state: {"another_list": ["hi"]})
>>> builder.add_node("b", lambda _state: {"alist": ["there"]})
>>> builder.add_edge("a", "b")
>>> builder.add_edge(START, "a")
>>> graph = builder.compile()
```
With stream_mode="values":
class State(TypedDict):
alist: Annotated[list, operator.add]
another_list: Annotated[list, operator.add]
```pycon
>>> async for event in graph.astream({"alist": ['Ex for stream_mode="values"']}, stream_mode="values"):
... print(event)
{'alist': ['Ex for stream_mode="values"'], 'another_list': []}
{'alist': ['Ex for stream_mode="values"'], 'another_list': ['hi']}
{'alist': ['Ex for stream_mode="values"', 'there'], 'another_list': ['hi']}
```
With stream_mode="updates":
builder = StateGraph(State)
builder.add_node("a", lambda _state: {"another_list": ["hi"]})
builder.add_node("b", lambda _state: {"alist": ["there"]})
builder.add_edge("a", "b")
builder.add_edge(START, "a")
graph = builder.compile()
```pycon
>>> async for event in graph.astream({"alist": ['Ex for stream_mode="updates"']}, stream_mode="updates"):
... print(event)
{'a': {'another_list': ['hi']}}
{'b': {'alist': ['there']}}
```
With stream_mode="debug":
async for event in graph.astream({"alist": ['Ex for stream_mode="values"']}, stream_mode="values"):
print(event)
# {'alist': ['Ex for stream_mode="values"'], 'another_list': []}
# {'alist': ['Ex for stream_mode="values"'], 'another_list': ['hi']}
# {'alist': ['Ex for stream_mode="values"', 'there'], 'another_list': ['hi']}
```pycon
>>> async for event in graph.astream({"alist": ['Ex for stream_mode="debug"']}, stream_mode="debug"):
... print(event)
{'type': 'task', 'timestamp': '2024-06-23T...+00:00', 'step': 1, 'payload': {'id': '...', 'name': 'a', 'input': {'alist': ['Ex for stream_mode="debug"'], 'another_list': []}, 'triggers': ['start:a']}}
{'type': 'task_result', 'timestamp': '2024-06-23T...+00:00', 'step': 1, 'payload': {'id': '...', 'name': 'a', 'result': [('another_list', ['hi'])]}}
{'type': 'task', 'timestamp': '2024-06-23T...+00:00', 'step': 2, 'payload': {'id': '...', 'name': 'b', 'input': {'alist': ['Ex for stream_mode="debug"'], 'another_list': ['hi']}, 'triggers': ['a']}}
{'type': 'task_result', 'timestamp': '2024-06-23T...+00:00', 'step': 2, 'payload': {'id': '...', 'name': 'b', 'result': [('alist', ['there'])]}}
```
Example: Using stream_mode="updates":
```python
async for event in graph.astream({"alist": ['Ex for stream_mode="updates"']}, stream_mode="updates"):
print(event)
With stream_mode="custom":
# {'a': {'another_list': ['hi']}}
# {'b': {'alist': ['there']}}
```pycon
>>> from langgraph.types import StreamWriter
...
>>> async def node_a(state: State, writer: StreamWriter):
... writer({"custom_data": "foo"})
... return {"alist": ["hi"]}
...
>>> builder = StateGraph(State)
>>> builder.add_node("a", node_a)
>>> builder.add_edge(START, "a")
>>> graph = builder.compile()
...
>>> async for event in graph.astream({"alist": ['Ex for stream_mode="custom"']}, stream_mode="custom"):
... print(event)
{'custom_data': 'foo'}
```
Example: Using stream_mode="debug":
```python
async for event in graph.astream({"alist": ['Ex for stream_mode="debug"']}, stream_mode="debug"):
print(event)
With stream_mode="messages":
# {'type': 'task', 'timestamp': '2024-06-23T...+00:00', 'step': 1, 'payload': {'id': '...', 'name': 'a', 'input': {'alist': ['Ex for stream_mode="debug"'], 'another_list': []}, 'triggers': ['start:a']}}
# {'type': 'task_result', 'timestamp': '2024-06-23T...+00:00', 'step': 1, 'payload': {'id': '...', 'name': 'a', 'result': [('another_list', ['hi'])]}}
# {'type': 'task', 'timestamp': '2024-06-23T...+00:00', 'step': 2, 'payload': {'id': '...', 'name': 'b', 'input': {'alist': ['Ex for stream_mode="debug"'], 'another_list': ['hi']}, 'triggers': ['a']}}
# {'type': 'task_result', 'timestamp': '2024-06-23T...+00:00', 'step': 2, 'payload': {'id': '...', 'name': 'b', 'result': [('alist', ['there'])]}}
```
```pycon
>>> from typing_extensions import Annotated, TypedDict
>>> from langgraph.graph import StateGraph, START
>>> from langchain_openai import ChatOpenAI
...
>>> llm = ChatOpenAI(model="gpt-4o-mini")
...
>>> class State(TypedDict):
... question: str
... answer: str
...
>>> async def node_a(state: State):
... response = await llm.ainvoke(state["question"])
... return {"answer": response.content}
...
>>> builder = StateGraph(State)
>>> builder.add_node("a", node_a)
>>> builder.add_edge(START, "a")
>>> graph = builder.compile()
Example: Using stream_mode="custom":
```python
from langgraph.types import StreamWriter
async def node_a(state: State, writer: StreamWriter):
writer({"custom_data": "foo"})
return {"alist": ["hi"]}
builder = StateGraph(State)
builder.add_node("a", node_a)
builder.add_edge(START, "a")
graph = builder.compile()
async for event in graph.astream({"alist": ['Ex for stream_mode="custom"']}, stream_mode="custom"):
print(event)
# {'custom_data': 'foo'}
```
Example: Using stream_mode="messages":
```python
from typing_extensions import Annotated, TypedDict
from langgraph.graph import StateGraph, START
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
class State(TypedDict):
question: str
answer: str
async def node_a(state: State):
response = await llm.ainvoke(state["question"])
return {"answer": response.content}
builder = StateGraph(State)
builder.add_node("a", node_a)
builder.add_edge(START, "a")
graph = builder.compile()
async for event in graph.astream({"question": "What is the capital of France?"}, stream_mode="messages"):
print(event)
# (AIMessageChunk(content='The', additional_kwargs={}, response_metadata={}, id='...'), {'langgraph_step': 1, 'langgraph_node': 'a', 'langgraph_triggers': ['start:a'], 'langgraph_path': ('__pregel_pull', 'a'), 'langgraph_checkpoint_ns': '...', 'checkpoint_ns': '...', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o-mini', 'ls_model_type': 'chat', 'ls_temperature': 0.7})
# (AIMessageChunk(content=' capital', additional_kwargs={}, response_metadata={}, id='...'), {'langgraph_step': 1, 'langgraph_node': 'a', 'langgraph_triggers': ['start:a'], ...})
# (AIMessageChunk(content=' of', additional_kwargs={}, response_metadata={}, id='...'), {...})
# (AIMessageChunk(content=' France', additional_kwargs={}, response_metadata={}, id='...'), {...})
# (AIMessageChunk(content=' is', additional_kwargs={}, response_metadata={}, id='...'), {...})
# (AIMessageChunk(content=' Paris', additional_kwargs={}, response_metadata={}, id='...'), {...})
>>> for event in graph.stream({"question": "What is the capital of France?"}, stream_mode="messages"):
... print(event)
(AIMessageChunk(content='The', additional_kwargs={}, response_metadata={}, id='...'), {'langgraph_step': 1, 'langgraph_node': 'a', 'langgraph_triggers': ['start:a'], 'langgraph_path': ('__pregel_pull', 'a'), 'langgraph_checkpoint_ns': '...', 'checkpoint_ns': '...', 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o-mini', 'ls_model_type': 'chat', 'ls_temperature': 0.7})
(AIMessageChunk(content=' capital', additional_kwargs={}, response_metadata={}, id='...'), {'langgraph_step': 1, 'langgraph_node': 'a', 'langgraph_triggers': ['start:a'], ...})
(AIMessageChunk(content=' of', additional_kwargs={}, response_metadata={}, id='...'), {...})
(AIMessageChunk(content=' France', additional_kwargs={}, response_metadata={}, id='...'), {...})
(AIMessageChunk(content=' is', additional_kwargs={}, response_metadata={}, id='...'), {...})
(AIMessageChunk(content=' Paris', additional_kwargs={}, response_metadata={}, id='...'), {...})
```
"""
+1 -16
View File
@@ -40,7 +40,6 @@ from langgraph.constants import (
CONFIG_KEY_CHECKPOINTER,
CONFIG_KEY_PREVIOUS,
CONFIG_KEY_READ,
CONFIG_KEY_RESUME_MAP,
CONFIG_KEY_SCRATCHPAD,
CONFIG_KEY_SEND,
CONFIG_KEY_STORE,
@@ -595,8 +594,6 @@ def prepare_single_task(
config[CONF].get(CONFIG_KEY_SCRATCHPAD),
pending_writes,
task_id,
xxh3_128_hexdigest(task_checkpoint_ns.encode()),
config[CONF].get(CONFIG_KEY_RESUME_MAP),
),
},
),
@@ -707,8 +704,6 @@ def prepare_single_task(
config[CONF].get(CONFIG_KEY_SCRATCHPAD),
pending_writes,
task_id,
xxh3_128_hexdigest(task_checkpoint_ns.encode()),
config[CONF].get(CONFIG_KEY_RESUME_MAP),
),
CONFIG_KEY_PREVIOUS: checkpoint["channel_values"].get(
PREVIOUS, None
@@ -835,8 +830,6 @@ def prepare_single_task(
config[CONF].get(CONFIG_KEY_SCRATCHPAD),
pending_writes,
task_id,
xxh3_128_hexdigest(task_checkpoint_ns.encode()),
config[CONF].get(CONFIG_KEY_RESUME_MAP),
),
CONFIG_KEY_PREVIOUS: checkpoint["channel_values"].get(
PREVIOUS, None
@@ -888,8 +881,6 @@ def _scratchpad(
parent_scratchpad: Optional[PregelScratchpad],
pending_writes: list[PendingWrite],
task_id: str,
namespace_hash: str,
resume_map: Optional[dict[str, Any]],
) -> PregelScratchpad:
if len(pending_writes) > 0:
# find global resume value
@@ -901,7 +892,6 @@ def _scratchpad(
# None cannot be used as a resume value, because it would be difficult to
# distinguish from missing when used over http
null_resume_write = None
# find task-specific resume value
for w in pending_writes:
if w[0] == task_id and w[1] == RESUME:
@@ -911,13 +901,8 @@ def _scratchpad(
break
else:
task_resume_write = []
# clear var
del w
# find namespace and task-specific resume value
if resume_map and namespace_hash in resume_map:
mapped_resume_write = resume_map[namespace_hash]
task_resume_write.append(mapped_resume_write)
else:
null_resume_write = None
task_resume_write = []
+23 -2
View File
@@ -1,10 +1,12 @@
from collections import Counter
from collections.abc import Iterator, Mapping, Sequence
from typing import Any, Literal, Optional, TypeVar, Union
from uuid import UUID
from langchain_core.runnables.utils import AddableDict
from langgraph.channels.base import BaseChannel, EmptyChannelError
from langgraph.checkpoint.base import PendingWrite
from langgraph.constants import (
EMPTY_SEQ,
ERROR,
@@ -22,6 +24,15 @@ from langgraph.pregel.log import logger
from langgraph.types import Command, PregelExecutableTask, Send
def is_task_id(task_id: str) -> bool:
"""Check if a string is a valid task id."""
try:
UUID(task_id)
except Exception:
return False
return True
def read_channel(
channels: Mapping[str, BaseChannel],
chan: str,
@@ -55,7 +66,9 @@ def read_channels(
return values
def map_command(cmd: Command) -> Iterator[tuple[str, str, Any]]:
def map_command(
cmd: Command, pending_writes: list[PendingWrite]
) -> Iterator[tuple[str, str, Any]]:
"""Map input chunk to a sequence of pending writes in the form (channel, value)."""
if cmd.graph == Command.PARENT:
raise InvalidUpdateError("There is no parent graph")
@@ -74,7 +87,15 @@ def map_command(cmd: Command) -> Iterator[tuple[str, str, Any]]:
f"In Command.goto, expected Send/str, got {type(send).__name__}"
)
if cmd.resume is not None:
yield (NULL_TASK_ID, RESUME, cmd.resume)
if isinstance(cmd.resume, dict) and all(is_task_id(k) for k in cmd.resume):
for tid, resume in cmd.resume.items():
existing: list[Any] = next(
(w[2] for w in pending_writes if w[0] == tid and w[1] == RESUME), []
)
existing.append(resume)
yield (tid, RESUME, existing)
else:
yield (NULL_TASK_ID, RESUME, cmd.resume)
if cmd.update:
for k, v in cmd._update_as_tuples():
yield (NULL_TASK_ID, k, v)
+5 -13
View File
@@ -47,7 +47,6 @@ from langgraph.constants import (
CONFIG_KEY_DEDUPE_TASKS,
CONFIG_KEY_DELEGATE,
CONFIG_KEY_ENSURE_LATEST,
CONFIG_KEY_RESUME_MAP,
CONFIG_KEY_RESUMING,
CONFIG_KEY_SCRATCHPAD,
CONFIG_KEY_STREAM,
@@ -113,7 +112,7 @@ from langgraph.pregel.io import (
)
from langgraph.pregel.manager import AsyncChannelsManager, ChannelsManager
from langgraph.pregel.read import PregelNode
from langgraph.pregel.utils import get_new_channel_versions, is_xxh3_128_hexdigest
from langgraph.pregel.utils import get_new_channel_versions
from langgraph.store.base import BaseStore
from langgraph.types import (
All,
@@ -650,22 +649,15 @@ class PregelLoop(LoopProtocol):
# map command to writes
if isinstance(self.input, Command):
if resume_is_map := (
(resume := self.input.resume) is not None
and isinstance(resume, dict)
and all(is_xxh3_128_hexdigest(k) for k in resume)
):
self.config[CONF][CONFIG_KEY_RESUME_MAP] = self.input.resume
if resume is not None and not self.checkpointer:
if self.input.resume is not None and not self.checkpointer:
raise RuntimeError(
"Cannot use Command(resume=...) without checkpointer"
)
writes: defaultdict[str, list[tuple[str, Any]]] = defaultdict(list)
# group writes by task ID
for tid, c, v in map_command(cmd=self.input):
if not (c == RESUME and resume_is_map):
writes[tid].append((c, v))
if not writes and not resume_is_map:
for tid, c, v in map_command(self.input, self.checkpoint_pending_writes):
writes[tid].append((c, v))
if not writes:
raise EmptyInputError("Received empty Command input")
# save writes
for tid, ws in writes.items():
+2 -4
View File
@@ -13,7 +13,7 @@ from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.messages import BaseMessage
from langchain_core.outputs import ChatGenerationChunk, LLMResult
from langgraph.constants import NS_SEP, TAG_HIDDEN, TAG_NOSTREAM, TAG_NOSTREAM_ALT
from langgraph.constants import NS_SEP, TAG_HIDDEN, TAG_NOSTREAM
from langgraph.types import Command, StreamChunk
try:
@@ -93,9 +93,7 @@ class StreamMessagesHandler(BaseCallbackHandler, _StreamingCallbackHandler):
metadata: Optional[dict[str, Any]] = None,
**kwargs: Any,
) -> Any:
if metadata and (
not tags or (TAG_NOSTREAM not in tags and TAG_NOSTREAM_ALT not in tags)
):
if metadata and (not tags or TAG_NOSTREAM not in tags):
self.metadata[run_id] = (
tuple(cast(str, metadata["langgraph_checkpoint_ns"]).split(NS_SEP)),
metadata,
+1 -2
View File
@@ -241,7 +241,7 @@ class RemoteGraph(PregelProtocol):
)
def _create_state_snapshot(self, state: ThreadState) -> StateSnapshot:
tasks: list[PregelTask] = []
tasks = []
for task in state["tasks"]:
interrupts = []
for interrupt in task["interrupts"]:
@@ -289,7 +289,6 @@ class RemoteGraph(PregelProtocol):
if state["parent_checkpoint"]
else None,
tasks=tuple(tasks),
interrupts=tuple([i for task in tasks for i in task.interrupts]),
)
def _get_checkpoint(self, config: Optional[RunnableConfig]) -> Optional[Checkpoint]:
-6
View File
@@ -1,6 +1,5 @@
import ast
import inspect
import re
import textwrap
from typing import Any, Callable, Optional
@@ -208,8 +207,3 @@ class NonLocals(ast.NodeVisitor):
parent = parent.value
if isinstance(parent, ast.Name):
self.loads.add(parent.id + "." + attr_expr)
def is_xxh3_128_hexdigest(value: str) -> bool:
"""Check if the given string matches the format of xxh3_128_hexdigest."""
return bool(re.fullmatch(r"[0-9a-f]{32}", value))

Some files were not shown because too many files have changed in this diff Show More