Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20bd71e289 | ||
|
|
283485753f | ||
|
|
ba7f9975fa | ||
|
|
8c4904bee9 | ||
|
|
6bb06b8702 | ||
|
|
7a16e33833 | ||
|
|
3da5c73a04 | ||
|
|
e818f83a92 | ||
|
|
e478a8deb9 | ||
|
|
4bbdfbf381 | ||
|
|
db1fbe74cc | ||
|
|
01ce86ad9b | ||
|
|
d5f73fe37b | ||
|
|
5a200cd89e | ||
|
|
6082bcf8d3 | ||
|
|
18a9ae45f3 | ||
|
|
83bf004ad7 | ||
|
|
72114c6c33 | ||
|
|
88b57df15b | ||
|
|
abfb88e5d7 | ||
|
|
49f063e076 | ||
|
|
30f9bcd8de |
@@ -19,7 +19,7 @@ build-prebuilt:
|
||||
poetry run python -m _scripts.third_party_page.get_download_stats --fake stats.yml; \
|
||||
set +x; \
|
||||
fi
|
||||
poetry run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/prebuilt.md --language python
|
||||
poetry run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/agents/prebuilt.md --language python
|
||||
|
||||
build-docs: build-typedoc build-prebuilt
|
||||
poetry run python -m mkdocs build --clean -f mkdocs.yml --strict
|
||||
|
||||
@@ -45,6 +45,8 @@ MANUAL_API_REFERENCES_LANGGRAPH = [
|
||||
(["langgraph.constants"], "langgraph.types", "Interrupt", "types"),
|
||||
(["langgraph.constants"], "langgraph.types", "interrupt", "types"),
|
||||
(["langgraph.constants"], "langgraph.types", "Command", "types"),
|
||||
(["langgraph.config"], "langgraph.config", "get_stream_writer", "config"),
|
||||
(["langgraph.config"], "langgraph.config", "get_store", "config"),
|
||||
(["langgraph.func"], "langgraph.func", "entrypoint", "func"),
|
||||
(["langgraph.func"], "langgraph.func", "task", "func"),
|
||||
(["langgraph.types"], "langgraph.types", "RetryPolicy", "types"),
|
||||
@@ -56,6 +58,7 @@ MANUAL_API_REFERENCES_LANGGRAPH = [
|
||||
([], "langgraph.checkpoint.base", "SerializerProtocol", "checkpoints"),
|
||||
([], "langgraph.checkpoint.serde.jsonplus", "JsonPlusSerializer", "checkpoints"),
|
||||
([], "langgraph.checkpoint.memory", "MemorySaver", "checkpoints"),
|
||||
([], "langgraph.checkpoint.memory", "InMemorySaver", "checkpoints"),
|
||||
([], "langgraph.checkpoint.sqlite.aio", "AsyncSqliteSaver", "checkpoints"),
|
||||
([], "langgraph.checkpoint.sqlite", "SqliteSaver", "checkpoints"),
|
||||
([], "langgraph.checkpoint.postgres.aio", "AsyncPostgresSaver", "checkpoints"),
|
||||
@@ -214,7 +217,7 @@ def update_markdown_with_imports(markdown: str, path: str) -> str:
|
||||
path: The path of the file where the markdown content originated.
|
||||
|
||||
Returns:
|
||||
Updated markdown with API reference links appended to Python code blocks.
|
||||
Updated markdown with API reference links prepended to Python code blocks.
|
||||
|
||||
Example:
|
||||
Given a markdown with a Python code block:
|
||||
@@ -237,7 +240,7 @@ def update_markdown_with_imports(markdown: str, path: str) -> str:
|
||||
match (re.Match): The regex match object containing the code block.
|
||||
|
||||
Returns:
|
||||
str: The modified code block with API reference links appended if applicable.
|
||||
str: The modified code block with API reference links prepended if applicable.
|
||||
"""
|
||||
indent = match.group("indent")
|
||||
code_block = match.group("code")
|
||||
@@ -253,8 +256,8 @@ def update_markdown_with_imports(markdown: str, path: str) -> str:
|
||||
api_links = " | ".join(
|
||||
f'<a href="{imp["docs"]}">{imp["imported"]}</a>' for imp in imports
|
||||
)
|
||||
# Return the code block with appended API reference links
|
||||
return f"{original_code_block}\n\n{indent}API Reference: {api_links}"
|
||||
# Return the code block with prepended API reference links
|
||||
return f"{indent}API Reference: {api_links}\n\n{original_code_block}"
|
||||
|
||||
# Apply the replace_code_block function to all matches in the markdown
|
||||
updated_markdown = code_block_pattern.sub(replace_code_block, markdown)
|
||||
|
||||
@@ -31,6 +31,8 @@ REDIRECT_MAP = {
|
||||
"cloud/concepts/api.md": "concepts/langgraph_server.md",
|
||||
"cloud/concepts/cloud.md": "concepts/langgraph_cloud.md",
|
||||
"cloud/faq/studio.md": "concepts/langgraph_studio.md#studio-faqs",
|
||||
# misc
|
||||
"prebuilt.md": "agents/prebuilt.md"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ BLOCKLIST_COMMANDS = (
|
||||
|
||||
NOTEBOOKS_NO_CASSETTES = (
|
||||
"docs/how-tos/visualization.ipynb",
|
||||
"docs/how-tos/many-tools.ipynb"
|
||||
)
|
||||
|
||||
NOTEBOOKS_NO_EXECUTION = [
|
||||
@@ -49,7 +48,10 @@ NOTEBOOKS_NO_EXECUTION = [
|
||||
"docs/how-tos/map-reduce.ipynb", # flakiness from structured output, only when running with VCR
|
||||
"docs/tutorials/tot/tot.ipynb",
|
||||
"docs/how-tos/visualization.ipynb",
|
||||
"docs/tutorials/llm-compiler/LLMCompiler.ipynb"
|
||||
"docs/how-tos/streaming-specific-nodes.ipynb",
|
||||
"docs/tutorials/llm-compiler/LLMCompiler.ipynb",
|
||||
"docs/tutorials/customer-support/customer-support.ipynb", # relies on openai embeddings, doesn't play well w/ VCR
|
||||
"docs/how-tos/many-tools.ipynb", # relies on openai embeddings, doesn't play well w/ VCR
|
||||
]
|
||||
|
||||
|
||||
@@ -86,6 +88,12 @@ def has_blocklisted_command(code: str, metadata: dict) -> bool:
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_mermaid_retries(code: str) -> str:
|
||||
return code.replace(
|
||||
"draw_mermaid_png()",
|
||||
"draw_mermaid_png(max_retries=10, retry_delay=2.0)"
|
||||
)
|
||||
|
||||
|
||||
def add_vcr_to_notebook(
|
||||
notebook: nbformat.NotebookNode, cassette_prefix: str
|
||||
@@ -180,6 +188,15 @@ def add_vcr_to_notebook(
|
||||
return notebook
|
||||
|
||||
|
||||
def add_mermaid_retries_to_notebook(notebook: nbformat.NotebookNode) -> nbformat.NotebookNode:
|
||||
for cell in notebook.cells:
|
||||
if cell.cell_type != "code":
|
||||
continue
|
||||
|
||||
cell.source = add_mermaid_retries(cell.source)
|
||||
return notebook
|
||||
|
||||
|
||||
def process_notebooks(should_comment_install_cells: bool) -> None:
|
||||
for directory in NOTEBOOK_DIRS:
|
||||
for root, _, files in os.walk(directory):
|
||||
@@ -201,6 +218,8 @@ def process_notebooks(should_comment_install_cells: bool) -> None:
|
||||
notebook, cassette_prefix=cassette_prefix
|
||||
)
|
||||
|
||||
notebook = add_mermaid_retries_to_notebook(notebook)
|
||||
|
||||
if notebook_path in NOTEBOOKS_NO_EXECUTION:
|
||||
# Add a cell at the beginning to indicate that this notebook should not be executed
|
||||
warning_cell = nbformat.v4.new_markdown_cell(
|
||||
|
||||
@@ -9,10 +9,7 @@ import yaml
|
||||
|
||||
MARKDOWN = """\
|
||||
[//]: # (This file is automatically generated using a script in docs/_scripts. Do not edit this file directly!)
|
||||
# 🚀 Prebuilt Agents
|
||||
|
||||
LangGraph includes a prebuilt React agent. For more information on how to use it,
|
||||
check out our [how-to guides](https://langchain-ai.github.io/langgraph/how-tos/#prebuilt-react-agent).
|
||||
# Community Agents
|
||||
|
||||
If you’re looking for other prebuilt libraries, explore the community-built options
|
||||
below. These libraries can extend LangGraph's functionality in various ways.
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
# Agents
|
||||
|
||||
## What is an agent?
|
||||
|
||||
An *agent* consists of three components: a **large language model (LLM)**, a set of **tools** it can use, and a **prompt** that provides instructions.
|
||||
|
||||
The LLM operates in a loop. In each iteration, it selects a tool to invoke, provides input, receives the result (an observation), and uses that observation to inform the next action. The loop continues until a stopping condition is met — typically when the agent has gathered enough information to respond to the user.
|
||||
|
||||
<figure markdown="1">
|
||||
{: style="max-height:400px"}
|
||||
<figcaption>Agent loop: the LLM selects tools and uses their outputs to fulfill a user request.</figcaption>
|
||||
</figure>
|
||||
|
||||
## Basic configuration
|
||||
|
||||
Use [`create_react_agent`](https://python.langchain.com/docs/api_reference/langgraph.prebuilt.chat_agent_executor/#create-react-agent) to instantiate an agent:
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
def get_weather(city: str) -> str: # (1)!
|
||||
"""Get weather for a given city."""
|
||||
return f"It's always sunny in {city}!"
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest", # (2)!
|
||||
tools=[get_weather], # (3)!
|
||||
prompt="You are a helpful assistant" # (4)!
|
||||
)
|
||||
|
||||
# Run the agent
|
||||
agent.invoke({"messages": "what is the weather in sf"})
|
||||
```
|
||||
|
||||
1. Define a tool for the agent to use. Tools can be defined as vanilla Python functions. For more advanced tool usage and customization, check the [tools](./tools.md) page.
|
||||
2. Provide a language model for the agent to use. To learn more about configuring language models for the agents, check the [models](./models.md) page.
|
||||
3. Provide a list of tools for the model to use.
|
||||
4. Provide a system prompt (instructions) to the language model used by the agent.
|
||||
|
||||
## LLM configuration
|
||||
|
||||
Use [init_chat_model](https://python.langchain.com/api_reference/langchain/chat_models/langchain.chat_models.base.init_chat_model.html) to configure an LLM with specific parameters,
|
||||
such as temperature:
|
||||
|
||||
```python
|
||||
from langchain.chat_models import init_chat_model
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
# highlight-next-line
|
||||
model = init_chat_model(
|
||||
"anthropic:claude-3-7-sonnet-latest",
|
||||
# highlight-next-line
|
||||
temperature=0
|
||||
)
|
||||
|
||||
agent = create_react_agent(
|
||||
# highlight-next-line
|
||||
model=model,
|
||||
tools=[get_weather],
|
||||
)
|
||||
```
|
||||
|
||||
See the [models](./models.md) page for more information on how to configure LLMs.
|
||||
|
||||
## Custom Prompts
|
||||
|
||||
Prompts instruct the LLM how to behave. They can be:
|
||||
|
||||
* **Static**: A string is interpreted as a **system message**
|
||||
* **Dynamic**: a list of messages generated at **runtime** based on input or configuration
|
||||
|
||||
### Static prompts
|
||||
|
||||
Define a fixed prompt string or list of messages.
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
# A static prompt that never changes
|
||||
# highlight-next-line
|
||||
prompt="Never answer questions about the weather."
|
||||
)
|
||||
|
||||
agent.invoke(
|
||||
{"messages": "what is the weather in sf"},
|
||||
)
|
||||
```
|
||||
|
||||
### Dynamic prompts
|
||||
|
||||
Define a function that returns a message list based on the agent's state and configuration:
|
||||
|
||||
```python
|
||||
from langchain_core.messages import AnyMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.prebuilt.chat_agent_executor import AgentState
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
# highlight-next-line
|
||||
def prompt(state: AgentState, config: RunnableConfig) -> list[AnyMessage]: # (1)!
|
||||
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"]
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
# highlight-next-line
|
||||
prompt=prompt
|
||||
)
|
||||
|
||||
agent.invoke(
|
||||
{"messages": "what is the weather in sf"},
|
||||
# highlight-next-line
|
||||
config={"configurable": {"user_name": "John Smith"}}
|
||||
)
|
||||
```
|
||||
|
||||
1. Dynamic prompts allow including non-message [context](./context.md) when constructing an input to the LLM, such as:
|
||||
|
||||
- Information passed at runtime, like a `user_id` or API credentials (using `config`).
|
||||
- Internal agent state updated during a multi-step reasoning process (using `state`).
|
||||
|
||||
Dynamic prompts can be defined as functions that take `state` and `config` and return a list of messages to send to the LLM.
|
||||
|
||||
See the [context](./context.md) page for more information.
|
||||
|
||||
## Memory
|
||||
|
||||
To allow multi-turn conversations with an agent, you need to enable [persistence](../concepts/persistence.md) by providing a `checkpointer` when creating an agent. At runtime you need to provide a config containing `thread_id` — a unique identifier for the conversation (session):
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
# highlight-next-line
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
# highlight-next-line
|
||||
checkpointer=checkpointer # (1)!
|
||||
)
|
||||
|
||||
# Run the agent
|
||||
# highlight-next-line
|
||||
config = {"configurable": {"thread_id": "1"}}
|
||||
sf_response = agent.invoke(
|
||||
{"messages": "what is the weather in sf"},
|
||||
# highlight-next-line
|
||||
config # (2)!
|
||||
)
|
||||
ny_response = agent.invoke(
|
||||
{"messages": "what about new york?"},
|
||||
# highlight-next-line
|
||||
config
|
||||
)
|
||||
```
|
||||
|
||||
1. `checkpointer` allows the agent to store its state at every step in the tool calling loop. This enables [short-term memory](./memory.md#short-term-memory) and [human-in-the-loop](./human-in-the-loop.md) capabilities.
|
||||
2. Pass configuration with `thread_id` to be able to resume the same conversation on future agent invocations.
|
||||
|
||||
When you enable the checkpointer, it stores agent state at every step in the provided checkpointer database (or in memory, if using `InMemorySaver`).
|
||||
|
||||
Note that in the above example, when the agent is invoked the second time with the same `thread_id`, the original message history from the first conversation is automatically included, together with the new user input.
|
||||
|
||||
Please see the [memory guide](./memory.md) for more details on how to work with memory.
|
||||
|
||||
|
||||
## Structured output
|
||||
|
||||
To produce structured responses conforming to a schema, use the `response_format` parameter. The schema can be defined with a `Pydantic` model or `TypedDict`. The result will be accessible via the `structured_response` field.
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
class WeatherResponse(BaseModel):
|
||||
conditions: str
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
# highlight-next-line
|
||||
response_format=WeatherResponse # (1)!
|
||||
)
|
||||
|
||||
response = agent.invoke({"messages": "what is the weather in sf"})
|
||||
|
||||
# highlight-next-line
|
||||
response["structured_response"]
|
||||
```
|
||||
|
||||
1. When `response_format` is provided, a separate step is added at the end of the agent loop: agent message history is passed to an LLM with structured output to generate a structured response.
|
||||
|
||||
To provide a system prompt to this LLM, use a tuple `(prompt, schema)`, e.g., `response_format=(prompt, WeatherResponse)`.
|
||||
|
||||
!!! Note "LLM post-processing"
|
||||
|
||||
Structured output requires an additional call to the LLM to format the response according to the schema.
|
||||
|
||||
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 3.2 MiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 65 KiB |
@@ -0,0 +1,287 @@
|
||||
# Context
|
||||
|
||||
Agents often require more than a list of messages to function effectively. They need **context**.
|
||||
|
||||
Context includes *any* data outside the message list that can shape agent behavior or tool execution. This can be:
|
||||
|
||||
- Information passed at runtime, like a `user_id` or API credentials.
|
||||
- Internal state updated during a multi-step reasoning process.
|
||||
- Persistent memory or facts from previous interactions.
|
||||
|
||||
LangGraph provides **three** primary ways to supply context:
|
||||
|
||||
| Type | Description | Mutable? | Lifetime |
|
||||
|------------------------------------------------------------------------------|-----------------------------------------------|----------|-------------------------|
|
||||
| [**Config**](#config-static-context) | data passed at the start of a run | ❌ | per run |
|
||||
| [**State**](#state-mutable-context) | dynamic data that can change during execution | ✅ | per run or conversation |
|
||||
| [**Long-term Memory (Store)**](#long-term-memory-cross-conversation-context) | data that can be shared between conversations | ✅ | across conversations |
|
||||
|
||||
You can use context to:
|
||||
|
||||
- Adjust the system prompt the model sees
|
||||
- Feed tools with necessary inputs
|
||||
- Track facts during an ongoing conversation
|
||||
|
||||
## Providing Runtime Context
|
||||
|
||||
Use this when you need to inject data into an agent at runtime.
|
||||
|
||||
### Config (static context)
|
||||
|
||||
Config is for immutable data like user metadata or API keys. Use
|
||||
when you have values that don't change mid-run.
|
||||
|
||||
Specify configuration using a key called **"configurable"** which is reserved
|
||||
for this purpose:
|
||||
|
||||
```python
|
||||
agent.invoke(
|
||||
{"messages": "hi!"},
|
||||
# highlight-next-line
|
||||
config={"configurable": {"user_id": "user_123"}}
|
||||
)
|
||||
```
|
||||
|
||||
### State (mutable context)
|
||||
|
||||
State acts as short-term memory during a run. It holds dynamic data that can evolve during execution, such as values derived from tools or LLM outputs.
|
||||
|
||||
```python
|
||||
class CustomState(AgentState):
|
||||
# highlight-next-line
|
||||
user_name: str
|
||||
|
||||
agent = create_react_agent(
|
||||
# Other agent parameters...
|
||||
# highlight-next-line
|
||||
state_schema=CustomState,
|
||||
)
|
||||
|
||||
agent.invoke({
|
||||
"messages": "hi!",
|
||||
"user_name": "Jane"
|
||||
})
|
||||
```
|
||||
|
||||
!!! tip "Turning on memory"
|
||||
|
||||
Please see the [memory guide](./memory.md) for more details on how to enable memory. This is a powerful feature that allows you to persist the agent's state across multiple invocations.
|
||||
Otherwise, the state is scoped only to a single agent run.
|
||||
|
||||
|
||||
|
||||
### Long-Term Memory (cross-conversation context)
|
||||
|
||||
For context that spans *across* conversations or sessions, LangGraph allows access to **long-term memory** via a `store`. This can be used to read or update persistent facts (e.g., user profiles, preferences, prior interactions). For more, see the [Memory guide](./memory.md).
|
||||
|
||||
## Customizing Prompts with Context
|
||||
|
||||
Prompts define how the agent behaves. To incorporate runtime context, you can dynamically generate prompts based on the agent's state or config.
|
||||
|
||||
Common use cases:
|
||||
|
||||
- Personalization
|
||||
- Role or goal customization
|
||||
- Conditional behavior (e.g., user is admin)
|
||||
|
||||
=== "Using config"
|
||||
|
||||
```python
|
||||
from langchain_core.messages import AnyMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langgraph.prebuilt.chat_agent_executor import AgentState
|
||||
|
||||
def prompt(
|
||||
state: AgentState,
|
||||
# highlight-next-line
|
||||
config: RunnableConfig,
|
||||
) -> list[AnyMessage]:
|
||||
# highlight-next-line
|
||||
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"]
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
# highlight-next-line
|
||||
prompt=prompt
|
||||
)
|
||||
|
||||
agent.invoke(
|
||||
...,
|
||||
# highlight-next-line
|
||||
config={"configurable": {"user_name": "John Smith"}}
|
||||
)
|
||||
```
|
||||
|
||||
=== "Using state"
|
||||
|
||||
```python
|
||||
from langchain_core.messages import AnyMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langgraph.prebuilt.chat_agent_executor import AgentState
|
||||
|
||||
class CustomState(AgentState):
|
||||
# highlight-next-line
|
||||
user_name: str
|
||||
|
||||
def prompt(
|
||||
# highlight-next-line
|
||||
state: CustomState
|
||||
) -> list[AnyMessage]:
|
||||
# highlight-next-line
|
||||
user_name = state["user_name"]
|
||||
system_msg = f"You are a helpful assistant. User's name is {user_name}"
|
||||
return [{"role": "system", "content": system_msg}] + state["messages"]
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[...],
|
||||
# highlight-next-line
|
||||
state_schema=CustomState,
|
||||
# highlight-next-line
|
||||
prompt=prompt
|
||||
)
|
||||
|
||||
agent.invoke({
|
||||
"messages": "hi!",
|
||||
# highlight-next-line
|
||||
"user_name": "John Smith"
|
||||
})
|
||||
```
|
||||
|
||||
## Tools
|
||||
|
||||
Tools can access context through special parameter **annotations**.
|
||||
|
||||
* Use `RunnableConfig` for config access
|
||||
* Use `Annotated[StateSchema, InjectedState]` for agent state
|
||||
|
||||
|
||||
!!! tip
|
||||
|
||||
These annotations prevent LLMs from attempting to fill in the values. These parameters will be **hidden** from the LLM.
|
||||
|
||||
=== "Using config"
|
||||
|
||||
```python
|
||||
def get_user_info(
|
||||
# highlight-next-line
|
||||
config: RunnableConfig,
|
||||
) -> str:
|
||||
"""Look up user info."""
|
||||
# highlight-next-line
|
||||
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(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_user_info],
|
||||
)
|
||||
|
||||
agent.invoke(
|
||||
{"messages": "look up user information"},
|
||||
# highlight-next-line
|
||||
config={"configurable": {"user_id": "user_123"}}
|
||||
)
|
||||
```
|
||||
|
||||
=== "Using State"
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from langgraph.prebuilt import InjectedState
|
||||
|
||||
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"
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
## 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": "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).
|
||||
@@ -0,0 +1,83 @@
|
||||
# Deployment
|
||||
|
||||
To deploy your LangGraph agent, create and configure a LangGraph app. This setup supports both local development and production deployments.
|
||||
|
||||
Features:
|
||||
|
||||
* 🖥️ Local server for development
|
||||
* 🧩 Studio Web UI for visual debugging
|
||||
* ☁️ Cloud and 🔧 self-hosted deployment options
|
||||
* 📊 LangSmith integration for tracing and observability
|
||||
|
||||
!!! info "Requirements"
|
||||
|
||||
- ✅ You **must** have a [LangSmith account](https://www.langchain.com/langsmith). You can sign up for **free** and get started with the free tier.
|
||||
|
||||
## Create a LangGraph app
|
||||
|
||||
```bash
|
||||
pip install -U "langgraph-cli[inmem]"
|
||||
langgraph new path/to/your/app --template new-langgraph-project-python
|
||||
```
|
||||
|
||||
This will create an empty LangGraph project. You can modify it by replacing the code in `src/agent/graph.py` with your agent code. For example:
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get weather for a given city."""
|
||||
return f"It's always sunny in {city}!"
|
||||
|
||||
graph = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
prompt="You are a helpful assistant"
|
||||
)
|
||||
```
|
||||
|
||||
### Install dependencies
|
||||
|
||||
In the root of your new LangGraph app, install the dependencies in `edit` mode so your local changes are used by the server:
|
||||
|
||||
```shell
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
### Create an `.env` file
|
||||
|
||||
You will find a `.env.example` in the root of your new LangGraph app. Create
|
||||
a `.env` file in the root of your new LangGraph app and copy the contents of the `.env.example` file into it, filling in the necessary API keys:
|
||||
|
||||
```bash
|
||||
LANGSMITH_API_KEY=lsv2...
|
||||
ANTHROPIC_API_KEY=sk-
|
||||
```
|
||||
|
||||
## Launch LangGraph server locally
|
||||
|
||||
```shell
|
||||
langgraph dev
|
||||
```
|
||||
|
||||
This will start up the LangGraph API server locally. If this runs successfully, you should see something like:
|
||||
|
||||
> Ready!
|
||||
>
|
||||
> - API: [http://localhost:2024](http://localhost:2024/)
|
||||
>
|
||||
> - Docs: http://localhost:2024/docs
|
||||
>
|
||||
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
|
||||
|
||||
See this [tutorial](https://langchain-ai.github.io/langgraph/tutorials/langgraph-platform/local-server/) to learn more about running LangGraph app locally.
|
||||
|
||||
## LangGraph Studio Web UI
|
||||
|
||||
LangGraph Studio Web is a specialized UI that you can connect to LangGraph API server to enable visualization, interaction, and debugging of your application locally. Test your graph in the LangGraph Studio Web UI by visiting the URL provided in the output of the `langgraph dev` command.
|
||||
|
||||
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
|
||||
|
||||
## Deployment
|
||||
|
||||
Once your LangGraph app is running locally, you can deploy it using LangGraph Cloud or self-hosted options. Refer to the [deployment options guide](../tutorials/deployment.md) for detailed instructions on all supported deployment models.
|
||||
@@ -0,0 +1,119 @@
|
||||
# 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:
|
||||
|
||||
```python
|
||||
def evaluator(*, outputs: dict, reference_outputs: dict):
|
||||
# compare agent outputs against reference outputs
|
||||
output_messages = outputs["messages"]
|
||||
reference_messages = reference["messages"]
|
||||
score = compare_messages(output_messages, reference_messages)
|
||||
return {"key": "evaluator_score", "score": score}
|
||||
```
|
||||
|
||||
To get started, you can use prebuilt evaluators from `AgentEvals` package:
|
||||
|
||||
```bash
|
||||
pip install -U agentevals
|
||||
```
|
||||
|
||||
## Create evaluator
|
||||
|
||||
A common way to evaluate agent performance is by comparing its trajectory (the order in which it calls its tools) against a reference trajectory:
|
||||
|
||||
```python
|
||||
import json
|
||||
# highlight-next-line
|
||||
from agentevals.trajectory.match import create_trajectory_match_evaluator
|
||||
|
||||
outputs = [
|
||||
{
|
||||
"role": "assistant",
|
||||
"tool_calls": [
|
||||
{
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"arguments": json.dumps({"city": "san francisco"}),
|
||||
}
|
||||
},
|
||||
{
|
||||
"function": {
|
||||
"name": "get_directions",
|
||||
"arguments": json.dumps({"destination": "presidio"}),
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
reference_outputs = [
|
||||
{
|
||||
"role": "assistant",
|
||||
"tool_calls": [
|
||||
{
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"arguments": json.dumps({"city": "san francisco"}),
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
# Create the evaluator
|
||||
evaluator = create_trajectory_match_evaluator(
|
||||
# highlight-next-line
|
||||
trajectory_match_mode="superset", # (1)!
|
||||
)
|
||||
|
||||
# Run the evaluator
|
||||
result = evaluator(
|
||||
outputs=outputs, reference_outputs=reference_outputs
|
||||
)
|
||||
```
|
||||
|
||||
1. Specify how the trajectories will be compared. `superset` will accept output trajectory as valid if it's a superset of the reference one. Other options include: [strict](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#strict-match), [unordered](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#unordered-match) and [subset](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#subset-and-superset-match)
|
||||
|
||||
|
||||
As a next step, learn more about how to [customize trajectory match evaluator](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#agent-trajectory-match).
|
||||
|
||||
### LLM-as-a-judge
|
||||
|
||||
You can use LLM-as-a-judge evaluator that uses an LLM to compare the trajectory against the reference outputs and output a score:
|
||||
|
||||
```python
|
||||
import json
|
||||
from agentevals.trajectory.llm import (
|
||||
# highlight-next-line
|
||||
create_trajectory_llm_as_judge,
|
||||
TRAJECTORY_ACCURACY_PROMPT_WITH_REFERENCE
|
||||
)
|
||||
|
||||
evaluator = create_trajectory_llm_as_judge(
|
||||
prompt=TRAJECTORY_ACCURACY_PROMPT_WITH_REFERENCE,
|
||||
model="openai:o3-mini"
|
||||
)
|
||||
```
|
||||
|
||||
## Run evaluator
|
||||
|
||||
To run an evaluator, you will first need to create a [LangSmith dataset](https://docs.smith.langchain.com/evaluation/concepts#datasets). To use the prebuilt AgentEvals evaluators, you will need a dataset with the following schema:
|
||||
|
||||
- **input**: `{"messages": [...]}` input messages to call the agent with.
|
||||
- **output**: `{"messages": [...]}` expected message history in the agent output. For trajectory evaluation, you can choose to keep only assistant messages.
|
||||
|
||||
```python
|
||||
from langsmith import Client
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from agentevals.trajectory.match import create_trajectory_match_evaluator
|
||||
|
||||
client = Client()
|
||||
agent = create_react_agent(...)
|
||||
evaluator = create_trajectory_match_evaluator(...)
|
||||
|
||||
experiment_results = client.evaluate(
|
||||
lambda inputs: agent.invoke(inputs),
|
||||
# replace with your dataset name
|
||||
data="<Name of your dataset>",
|
||||
evaluators=[evaluator]
|
||||
)
|
||||
```
|
||||
@@ -0,0 +1,227 @@
|
||||
# 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](../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.
|
||||
|
||||
This is possible because the agent state is **checkpointed into a database**, which allows the system to persist execution context and later resume the workflow, continuing from where it left off.
|
||||
|
||||
For a deeper dive into the **human-in-the-loop** concept, see the [concept guide](../concepts/human_in_the_loop.md).
|
||||
|
||||
<figure markdown="1">
|
||||
{: style="max-height:400px"}
|
||||
<figcaption>
|
||||
A human can review and edit the output from the agent before proceeding. This is particularly critical in applications where the tool calls requested may be sensitive or require human oversight.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
## Review tool calls
|
||||
|
||||
To add a human approval step to a tool:
|
||||
|
||||
1. Use `interrupt()` in the tool to pause execution.
|
||||
2. Resume with a `Command(resume=...)` to continue based on human input.
|
||||
|
||||
```python
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.types import interrupt
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
# An example of a sensitive tool that requires human review / approval
|
||||
def book_hotel(hotel_name: str):
|
||||
"""Book a hotel"""
|
||||
# highlight-next-line
|
||||
response = interrupt( # (1)!
|
||||
f"Trying to call `book_hotel` with args {{'hotel_name': {hotel_name}}}. "
|
||||
"Please approve or suggest edits."
|
||||
)
|
||||
if response["type"] == "accept":
|
||||
pass
|
||||
elif response["type"] == "edit":
|
||||
hotel_name = response["args"]["hotel_name"]
|
||||
else:
|
||||
raise ValueError(f"Unknown response type: {response['type']}")
|
||||
return f"Successfully booked a stay at {hotel_name}."
|
||||
|
||||
# highlight-next-line
|
||||
checkpointer = InMemorySaver() # (2)!
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-5-sonnet-latest",
|
||||
tools=[book_hotel],
|
||||
# highlight-next-line
|
||||
checkpointer=checkpointer, # (3)!
|
||||
)
|
||||
```
|
||||
|
||||
1. The [`interrupt` function][langgraph.types.interrupt] pauses the agent graph at a specific node. In this case, we call `interrupt()` at the beginning of the tool function, which pauses the graph at the node that executes the tool. The information inside `interrupt()` (e.g., tool calls) can be presented to a human, and the graph can be resumed with the user input (tool call approval, edit or feedback).
|
||||
2. The `InMemorySaver` is used to store the agent state at every step in the tool calling loop. This enables [short-term memory](./memory.md#short-term-memory) and [human-in-the-loop](./human-in-the-loop.md) capabilities. In this example, we use `InMemorySaver` to store the agent state in memory. In a production application, the agent state will be stored in a database.
|
||||
3. Initialize the agent with the `checkpointer`.
|
||||
|
||||
Run the agent with the `stream()` method, passing the `config` object to specify the thread ID. This allows the agent to resume the same conversation on future invocations.
|
||||
|
||||
```python
|
||||
config = {
|
||||
"configurable": {
|
||||
# highlight-next-line
|
||||
"thread_id": "1"
|
||||
}
|
||||
}
|
||||
|
||||
for chunk in agent.stream(
|
||||
{"messages": "book a stay at McKittrick hotel"},
|
||||
# highlight-next-line
|
||||
config
|
||||
):
|
||||
print(chunk)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
> You should see that the agent runs until it reaches the `interrupt()` call, at which point it pauses and waits for human input.
|
||||
|
||||
Resume the agent with a `Command(resume=...)` to continue based on human input.
|
||||
|
||||
```python
|
||||
from langgraph.types import Command
|
||||
|
||||
for chunk in agent.stream(
|
||||
# highlight-next-line
|
||||
Command(resume={"type": "accept"}), # (1)!
|
||||
# Command(resume={"type": "edit", "args": {"hotel_name": "McKittrick Hotel"}}),
|
||||
config
|
||||
):
|
||||
print(chunk)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
1. The [`interrupt` function][langgraph.types.interrupt] is used in conjunction with the [`Command`](../reference/types.md#langgraph.types.Command) object to resume the graph with a value provided by the human.
|
||||
|
||||
## Using with Agent Inbox
|
||||
|
||||
You can create a wrapper to add interrupts to *any* tool.
|
||||
|
||||
The example below provides a reference implementation compatible with [Agent Inbox UI](https://github.com/langchain-ai/agent-inbox) and [Agent Chat UI](https://github.com/langchain-ai/agent-chat-ui).
|
||||
|
||||
```python title="Wrapper that adds human-in-the-loop to any tool"
|
||||
from typing import Callable
|
||||
from langchain_core.tools import BaseTool, tool as create_tool
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.types import interrupt
|
||||
from langgraph.prebuilt.interrupt import HumanInterruptConfig, HumanInterrupt
|
||||
|
||||
def add_human_in_the_loop(
|
||||
tool: Callable | BaseTool,
|
||||
*,
|
||||
interrupt_config: HumanInterruptConfig = None,
|
||||
) -> BaseTool:
|
||||
"""Wrap a tool to support human-in-the-loop review."""
|
||||
if not isinstance(tool, BaseTool):
|
||||
tool = create_tool(tool)
|
||||
|
||||
if interrupt_config is None:
|
||||
interrupt_config = {
|
||||
"allow_accept": True,
|
||||
"allow_edit": True,
|
||||
"allow_respond": True,
|
||||
}
|
||||
|
||||
@create_tool( # (1)!
|
||||
tool.name,
|
||||
description=tool.description,
|
||||
args_schema=tool.args_schema
|
||||
)
|
||||
def call_tool_with_interrupt(config: RunnableConfig, **tool_input):
|
||||
request: HumanInterrupt = {
|
||||
"action_request": {
|
||||
"action": tool.name,
|
||||
"args": tool_input
|
||||
},
|
||||
"config": interrupt_config,
|
||||
"description": "Please review the tool call"
|
||||
}
|
||||
# highlight-next-line
|
||||
response = interrupt([request])[0] # (2)!
|
||||
# approve the tool call
|
||||
if response["type"] == "accept":
|
||||
tool_response = tool.invoke(tool_input, config)
|
||||
# update tool call args
|
||||
elif response["type"] == "edit":
|
||||
tool_input = response["args"]["args"]
|
||||
tool_response = tool.invoke(tool_input, config)
|
||||
# respond to the LLM with user feedback
|
||||
elif response["type"] == "response":
|
||||
user_feedback = response["args"]
|
||||
tool_response = user_feedback
|
||||
else:
|
||||
raise ValueError(f"Unsupported interrupt response type: {response['type']}")
|
||||
|
||||
return tool_response
|
||||
|
||||
return call_tool_with_interrupt
|
||||
```
|
||||
|
||||
1. This wrapper creates a new tool that calls `interrupt()` **before** executing the wrapped tool.
|
||||
2. `interrupt()` is using special input and output format that's expected by [Agent Inbox UI](https://github.com/langchain-ai/agent-inbox):
|
||||
- a list of [`HumanInterrupt`][langgraph.prebuilt.interrupt.HumanInterrupt] objects is sent to `AgentInbox` render interrupt information to the end user
|
||||
- resume value is provided by `AgentInbox` as a list (i.e., `Command(resume=[...])`)
|
||||
|
||||
You can use the `add_human_in_the_loop` wrapper to add `interrupt()` to any tool without having to add it *inside* the tool:
|
||||
|
||||
```python
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
# highlight-next-line
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
def book_hotel(hotel_name: str):
|
||||
"""Book a hotel"""
|
||||
return f"Successfully booked a stay at {hotel_name}."
|
||||
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-5-sonnet-latest",
|
||||
tools=[
|
||||
# highlight-next-line
|
||||
add_human_in_the_loop(book_hotel), # (1)!
|
||||
],
|
||||
# highlight-next-line
|
||||
checkpointer=checkpointer,
|
||||
)
|
||||
|
||||
config = {"configurable": {"thread_id": "1"}}
|
||||
|
||||
# Run the agent
|
||||
for chunk in agent.stream(
|
||||
{"messages": "book a stay at McKittrick hotel"},
|
||||
# highlight-next-line
|
||||
config
|
||||
):
|
||||
print(chunk)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
1. The `add_human_in_the_loop` wrapper is used to add `interrupt()` to the tool. This allows the agent to pause execution and wait for human input before proceeding with the tool call.
|
||||
|
||||
> You should see that the agent runs until it reaches the `interrupt()` call,
|
||||
> at which point it pauses and waits for human input.
|
||||
|
||||
Resume the agent with a `Command(resume=...)` to continue based on human input.
|
||||
|
||||
```python
|
||||
from langgraph.types import Command
|
||||
|
||||
for chunk in agent.stream(
|
||||
# highlight-next-line
|
||||
Command(resume=[{"type": "accept"}]),
|
||||
# Command(resume=[{"type": "edit", "args": {"args": {"hotel_name": "McKittrick Hotel"}}}]),
|
||||
config
|
||||
):
|
||||
print(chunk)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
## Additional resources
|
||||
|
||||
* [Human-in-the-loop in LangGraph](../concepts/human_in_the_loop.md)
|
||||
@@ -0,0 +1,94 @@
|
||||
# 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.
|
||||
|
||||

|
||||
|
||||
Install the `langchain-mcp-adapters` library to use MCP tools in LangGraph:
|
||||
|
||||
```bash
|
||||
pip install langchain-mcp-adapters
|
||||
```
|
||||
|
||||
## Use MCP tools
|
||||
|
||||
The `langchain-mcp-adapters` package enables agents to use tools defined across one or more MCP servers.
|
||||
|
||||
```python title="Agent using tools defined on MCP servers"
|
||||
# highlight-next-line
|
||||
from langchain_mcp_adapters.client import MultiServerMCPClient
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
# highlight-next-line
|
||||
async with MultiServerMCPClient(
|
||||
{
|
||||
"math": {
|
||||
"command": "python",
|
||||
# Replace with absolute path to your math_server.py file
|
||||
"args": ["/path/to/math_server.py"],
|
||||
"transport": "stdio",
|
||||
},
|
||||
"weather": {
|
||||
# Ensure your start your weather server on port 8000
|
||||
"url": "http://localhost:8000/sse",
|
||||
"transport": "sse",
|
||||
}
|
||||
}
|
||||
) as client:
|
||||
agent = create_react_agent(
|
||||
"anthropic:claude-3-7-sonnet-latest",
|
||||
# highlight-next-line
|
||||
client.get_tools()
|
||||
)
|
||||
math_response = await agent.ainvoke({"messages": "what's (3 + 5) x 12?"})
|
||||
weather_response = await agent.ainvoke({"messages": "what is the weather in nyc?"})
|
||||
```
|
||||
|
||||
## Custom MCP servers
|
||||
|
||||
To create your own MCP servers, you can use the `mcp` library. This library provides a simple way to define tools and run them as servers.
|
||||
|
||||
Install the MCP library:
|
||||
|
||||
```bash
|
||||
pip install mcp
|
||||
```
|
||||
Use the following reference implementations to test your agent with MCP tool servers.
|
||||
|
||||
```python title="Example Math Server (stdio transport)"
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Math")
|
||||
|
||||
@mcp.tool()
|
||||
def add(a: int, b: int) -> int:
|
||||
"""Add two numbers"""
|
||||
return a + b
|
||||
|
||||
@mcp.tool()
|
||||
def multiply(a: int, b: int) -> int:
|
||||
"""Multiply two numbers"""
|
||||
return a * b
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="stdio")
|
||||
```
|
||||
|
||||
```python title="Example Weather Server (SSE transport)"
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Weather")
|
||||
|
||||
@mcp.tool()
|
||||
async def get_weather(location: str) -> str:
|
||||
"""Get weather for location."""
|
||||
return "It's always sunny in New York"
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="sse")
|
||||
```
|
||||
|
||||
## Additional resources
|
||||
|
||||
- [MCP documentation](https://modelcontextprotocol.io/introduction)
|
||||
- [MCP Transport documentation](https://modelcontextprotocol.io/docs/concepts/transports)
|
||||
@@ -0,0 +1,262 @@
|
||||
# Memory
|
||||
|
||||
LangGraph supports two types of memory essential for building conversational agents:
|
||||
|
||||
- **[Short-term memory](#short-term-memory)**: Tracks the ongoing conversation by maintaining message history within a session.
|
||||
- **[Long-term memory](#long-term-memory)**: Stores user-specific or application-level data across sessions.
|
||||
|
||||
This guide demonstrates how to use both memory types with agents in LangGraph. For a deeper
|
||||
understanding of memory concepts, refer to the [LangGraph memory documentation](../concepts/memory.md).
|
||||
|
||||
<figure markdown="1">
|
||||
{: style="max-height:400px"}
|
||||
<figcaption>Both <strong>short-term</strong> and <strong>long-term</strong> memory require persistent storage to maintain continuity across LLM interactions. In production environments, this data is typically stored in a database.</figcaption>
|
||||
</figure>
|
||||
|
||||
!!! note "Terminology"
|
||||
|
||||
In LangGraph:
|
||||
|
||||
- *Short-term memory* is also referred to as **thread-level memory**.
|
||||
- *Long-term memory* is also called **cross-thread memory**.
|
||||
|
||||
A [thread](../concepts/persistence.md#threads) represents a sequence of related runs
|
||||
grouped by the same `thread_id`.
|
||||
|
||||
## Short-term memory
|
||||
|
||||
Short-term memory enables agents to track multi-turn conversations. To use it, you must:
|
||||
|
||||
1. Provide a `checkpointer` when creating the agent. The `checkpointer` enables [persistence](../concepts/persistence.md) of the agent's state.
|
||||
2. Supply a `thread_id` in the config when running the agent. The `thread_id` is a unique identifier for the conversation session.
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
# highlight-next-line
|
||||
checkpointer = InMemorySaver() # (1)!
|
||||
|
||||
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get weather for a given city."""
|
||||
return f"It's always sunny in {city}!"
|
||||
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
# highlight-next-line
|
||||
checkpointer=checkpointer # (2)!
|
||||
)
|
||||
|
||||
# Run the agent
|
||||
config = {
|
||||
"configurable": {
|
||||
# highlight-next-line
|
||||
"thread_id": "1" # (3)!
|
||||
}
|
||||
}
|
||||
|
||||
sf_response = agent.invoke(
|
||||
{"messages": "what is the weather in sf"},
|
||||
# highlight-next-line
|
||||
config
|
||||
)
|
||||
|
||||
# Continue the conversation using the same thread_id
|
||||
ny_response = agent.invoke(
|
||||
{"messages": "what about new york?"},
|
||||
# highlight-next-line
|
||||
config # (4)!
|
||||
)
|
||||
```
|
||||
|
||||
1. The `InMemorySaver` is a checkpointer that stores the agent's state in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [checkpointer documentation](../reference/checkpoints.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready checkpointer for you.
|
||||
2. The `checkpointer` is passed to the agent. This enables the agent to persist its state across invocations. Please note that
|
||||
3. A unique `thread_id` is provided in the config. This ID is used to identify the conversation session. The value is controlled by the user and can be any string.
|
||||
4. The agent will continue the conversation using the same `thread_id`. This will allow the agent to infer that the user is asking specifically about the **weather** in New York.
|
||||
|
||||
When the agent is invoked the second time with the same `thread_id`, the original message history from the first conversation is automatically included, allowing the agent to infer that the user is asking specifically about the **weather** in New York.
|
||||
|
||||
!!! Note "LangGraph Platform providers a production-ready checkpointer"
|
||||
|
||||
If you're using [LangGraph Platform](./deployment.md), during deployment your checkpointer will be automatically configured to use a production-ready database.
|
||||
|
||||
### Message history summarization
|
||||
|
||||
<figure markdown="1">
|
||||
{: style="max-height:400px"}
|
||||
<figcaption>Message history can grow quickly and exceed the LLM's context window. A common solution is to maintain a running summary of the conversation. This allows the agent to keep track of the conversation without exceeding the LLM's context window.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
Long conversations can exceed the LLM's context window. To handle this, you can summarize older messages by specifying a [`pre_model_hook`][langgraph.prebuilt.chat_agent_executor.create_react_agent], such as the prebuilt [`SummarizationNode`](https://langchain-ai.github.io/langmem/reference/short_term/#langmem.short_term.SummarizationNode):
|
||||
|
||||
```python
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langmem.short_term import SummarizationNode
|
||||
from langchain_core.messages.utils import count_tokens_approximately
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langgraph.prebuilt.chat_agent_executor import AgentState
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from typing import Any
|
||||
|
||||
model = ChatAnthropic(model="claude-3-7-sonnet-latest")
|
||||
|
||||
summarization_node = SummarizationNode( # (1)!
|
||||
token_counter=count_tokens_approximately,
|
||||
model=model,
|
||||
max_tokens=384,
|
||||
max_summary_tokens=128,
|
||||
output_messages_key="llm_input_messages",
|
||||
)
|
||||
|
||||
class State(AgentState):
|
||||
# NOTE: we're adding this key to keep track of previous summary information
|
||||
# to make sure we're not summarizing on every LLM call
|
||||
# highlight-next-line
|
||||
context: dict[str, Any] # (2)!
|
||||
|
||||
|
||||
checkpointer = InMemorySaver() # (3)!
|
||||
|
||||
agent = create_react_agent(
|
||||
model=model,
|
||||
tools=tools,
|
||||
# highlight-next-line
|
||||
pre_model_hook=summarization_node, # (4)!
|
||||
# highlight-next-line
|
||||
state_schema=State, # (5)!
|
||||
checkpointer=checkpointer,
|
||||
)
|
||||
```
|
||||
|
||||
1. The `InMemorySaver` is a checkpointer that stores the agent's state in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [checkpointer documentation](../reference/checkpoints.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready checkpointer for you.
|
||||
2. The `context` key is added to the agent's state. The key contains book-keeping information for the summarization node. It is used to keep track of the last summary information and ensure that the agent doesn't summarize on every LLM call, which can be inefficient.
|
||||
3. The `checkpointer` is passed to the agent. This enables the agent to persist its state across invocations.
|
||||
4. The `pre_model_hook` is set to the `SummarizationNode`. This node will summarize the message history before sending it to the LLM. The summarization node will automatically handle the summarization process and update the agent's state with the new summary. You can replace this with a custom implementation if you prefer. Please see the [create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent] API reference for more details.
|
||||
5. The `state_schema` is set to the `State` class, which is the custom state that contains an extra `context` key.
|
||||
|
||||
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)
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
### Reading
|
||||
|
||||
```python title="A tool the agent can use to look up user information"
|
||||
from langgraph.config import get_store
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langgraph.store.memory import InMemoryStore
|
||||
|
||||
# highlight-next-line
|
||||
store = InMemoryStore() # (1)!
|
||||
|
||||
# highlight-next-line
|
||||
store.put( # (2)!
|
||||
("users",), # (3)!
|
||||
"user_123", # (4)!
|
||||
{
|
||||
"name": "John Smith",
|
||||
"language": "English",
|
||||
} # (5)!
|
||||
)
|
||||
|
||||
def get_user_info(config: RunnableConfig) -> str:
|
||||
"""Look up user info."""
|
||||
# Same as that provided to `create_react_agent`
|
||||
# highlight-next-line
|
||||
store = get_store() # (6)!
|
||||
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"
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_user_info],
|
||||
# highlight-next-line
|
||||
store=store # (8)!
|
||||
)
|
||||
|
||||
# Run the agent
|
||||
agent.invoke(
|
||||
{"messages": "look up user information"},
|
||||
# highlight-next-line
|
||||
config={"configurable": {"user_id": "user_123"}}
|
||||
)
|
||||
```
|
||||
|
||||
1. The `InMemoryStore` is a store that stores data in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [store documentation](../reference/stores.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready store for you.
|
||||
2. For this example, we write some sample data to the store using the `put` method. Please see the [BaseStore.put][langgraph.store.base.BaseStore.put] API reference for more details.
|
||||
3. The first argument is the namespace. This is used to group related data together. In this case, we are using the `users` namespace to group user data.
|
||||
4. A key within the namespace. This example uses a user ID for the key.
|
||||
5. The data that we want to store for the given user.
|
||||
6. The `get_store` function is used to access the store. You can call it from anywhere in your code, including tools and prompts. This function returns the store that was passed to the agent when it was created.
|
||||
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.
|
||||
|
||||
### Writing
|
||||
|
||||
```python title="Example of a tool that updates user information"
|
||||
from typing import TypedDict
|
||||
|
||||
from langgraph.config import get_store
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
from langgraph.store.memory import InMemoryStore
|
||||
|
||||
store = InMemoryStore() # (1)!
|
||||
|
||||
class UserInfo(TypedDict): # (2)!
|
||||
name: str
|
||||
|
||||
def save_user_info(user_info: UserInfo, config: RunnableConfig) -> str: # (3)!
|
||||
"""Save user info."""
|
||||
# Same as that provided to `create_react_agent`
|
||||
# highlight-next-line
|
||||
store = get_store() # (4)!
|
||||
user_id = config.get("configurable", {}).get("user_id")
|
||||
# highlight-next-line
|
||||
store.put(("users",), user_id, user_info) # (5)!
|
||||
return "Successfully saved user info."
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[save_user_info],
|
||||
# highlight-next-line
|
||||
store=store
|
||||
)
|
||||
|
||||
# Run the agent
|
||||
agent.invoke(
|
||||
{"messages": "My name is John Smith"},
|
||||
# highlight-next-line
|
||||
config={"configurable": {"user_id": "user_123"}} # (6)!
|
||||
)
|
||||
|
||||
# You can access the store directly to get the value
|
||||
store.get(("users",), "user_123").value
|
||||
```
|
||||
|
||||
1. The `InMemoryStore` is a store that stores data in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [store documentation](../reference/stores.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready store for you.
|
||||
2. The `UserInfo` class is a `TypedDict` that defines the structure of the user information. The LLM will use this to format the response according to the schema.
|
||||
3. The `save_user_info` function is a tool that allows an agent to update user information. This could be useful for a chat application where the user wants to update their profile information.
|
||||
4. The `get_store` function is used to access the store. You can call it from anywhere in your code, including tools and prompts. This function returns the store that was passed to the agent when it was created.
|
||||
5. The `put` method is used to store data in the store. The first argument is the namespace, and the second argument is the key. This will store the user information in the store.
|
||||
6. The `user_id` is passed in the config. This is used to identify the user whose information is being updated.
|
||||
|
||||
### Prebuilt memory tools
|
||||
|
||||
**LangMem** is a LangChain-maintained library that offers tools for managing long-term memories in your agent. See the [LangMem documentation](https://langchain-ai.github.io/langmem/) for usage examples.
|
||||
|
||||
|
||||
## Additional resources
|
||||
|
||||
* [Memory in LangGraph](../concepts/memory.md)
|
||||
@@ -0,0 +1,69 @@
|
||||
# Models
|
||||
|
||||
This page describes how to configure the chat model used by an agent.
|
||||
|
||||
## Tool calling support
|
||||
|
||||
To enable tool-calling agents, the underlying LLM must support [tool calling](https://python.langchain.com/docs/concepts/tool_calling/).
|
||||
|
||||
Compatible models can be found in the [LangChain integrations directory](https://python.langchain.com/docs/integrations/chat/).
|
||||
|
||||
## Specifying a model by name
|
||||
|
||||
You can configure an agent with a model name string:
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
agent = create_react_agent(
|
||||
# highlight-next-line
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
# other parameters
|
||||
)
|
||||
```
|
||||
|
||||
## Using `init_chat_model`
|
||||
|
||||
The [`init_chat_model`](https://python.langchain.com/docs/how_to/chat_models_universal_init/) utility simplifies model initialization with configurable parameters:
|
||||
|
||||
```python
|
||||
from langchain.chat_models import init_chat_model
|
||||
|
||||
model = init_chat_model(
|
||||
"anthropic:claude-3-7-sonnet-latest",
|
||||
temperature=0,
|
||||
max_tokens=2048
|
||||
)
|
||||
```
|
||||
|
||||
Refer to the [API reference](https://python.langchain.com/api_reference/langchain/chat_models/langchain.chat_models.base.init_chat_model.html) for advanced options.
|
||||
|
||||
## Using provider-specific LLMs
|
||||
|
||||
If a model provider is not available via `init_chat_model`, you can instantiate the provider's model class directly. The model must implement the [BaseChatModel interface](https://python.langchain.com/api_reference/core/language_models/langchain_core.language_models.chat_models.BaseChatModel.html) and support tool calling:
|
||||
|
||||
```python
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
model = ChatAnthropic(
|
||||
model="claude-3-7-sonnet-latest",
|
||||
temperature=0,
|
||||
max_tokens=2048
|
||||
)
|
||||
|
||||
agent = create_react_agent(
|
||||
# highlight-next-line
|
||||
model=model,
|
||||
# other parameters
|
||||
)
|
||||
```
|
||||
|
||||
!!! note "Illustrative example"
|
||||
|
||||
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.
|
||||
|
||||
## 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/)
|
||||
@@ -0,0 +1,275 @@
|
||||
# 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).
|
||||
|
||||
In multi-agent systems, agents need to communicate between each other. They do so via [handoffs](#handoffs) — a primitive that describes which agent to hand control to and the payload to send to that agent.
|
||||
|
||||
Two of the most popular multi-agent architectures are:
|
||||
|
||||
- [supervisor](#supervisor) — individual agents are coordinated by a central supervisor agent. The supervisor controls all communication flow and task delegation, making decisions about which agent to invoke based on the current context and task requirements.
|
||||
- [swarm](#swarm) — agents dynamically hand off control to one another based on their specializations. The system remembers which agent was last active, ensuring that on subsequent interactions, the conversation resumes with that agent.
|
||||
|
||||
## Supervisor
|
||||
|
||||

|
||||
|
||||
Use [`langgraph-supervisor`](https://github.com/langchain-ai/langgraph-supervisor-py) library to create a supervisor multi-agent system:
|
||||
|
||||
```bash
|
||||
pip install langgraph-supervisor
|
||||
```
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
# highlight-next-line
|
||||
from langgraph_supervisor import create_supervisor
|
||||
|
||||
def book_hotel(hotel_name: str):
|
||||
"""Book a hotel"""
|
||||
return f"Successfully booked a stay at {hotel_name}."
|
||||
|
||||
def book_flight(from_airport: str, to_airport: str):
|
||||
"""Book a flight"""
|
||||
return f"Successfully booked a flight from {from_airport} to {to_airport}."
|
||||
|
||||
flight_assistant = create_react_agent(
|
||||
model="openai:gpt-4o",
|
||||
tools=[book_flight],
|
||||
prompt="You are a flight booking assistant",
|
||||
# highlight-next-line
|
||||
name="flight_assistant"
|
||||
)
|
||||
|
||||
hotel_assistant = create_react_agent(
|
||||
model="openai:gpt-4o",
|
||||
tools=[book_hotel],
|
||||
prompt="You are a hotel booking assistant",
|
||||
# highlight-next-line
|
||||
name="hotel_assistant"
|
||||
)
|
||||
|
||||
# highlight-next-line
|
||||
supervisor = create_supervisor(
|
||||
agents=[flight_assistant, hotel_assistant],
|
||||
model=ChatOpenAI(model="gpt-4o"),
|
||||
prompt="You manage a hotel booking assistant and a flight booking assistant. Assign work to them."
|
||||
).compile()
|
||||
|
||||
for chunk in supervisor.stream({
|
||||
"messages": "book a flight from BOS to JFK and a stay at McKittrick Hotel"
|
||||
}):
|
||||
print(chunk)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
## Swarm
|
||||
|
||||

|
||||
|
||||
Use [`langgraph-swarm`](https://github.com/langchain-ai/langgraph-swarm-py) library to create a swarm multi-agent system:
|
||||
|
||||
```bash
|
||||
pip install langgraph-swarm
|
||||
```
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
# highlight-next-line
|
||||
from langgraph_swarm import create_swarm, create_handoff_tool
|
||||
|
||||
transfer_to_hotel_assistant = create_handoff_tool(
|
||||
agent_name="hotel_assistant",
|
||||
description="Transfer user to the hotel-booking assistant.",
|
||||
)
|
||||
transfer_to_flight_assistant = create_handoff_tool(
|
||||
agent_name="flight_assistant",
|
||||
description="Transfer user to the flight-booking assistant.",
|
||||
)
|
||||
|
||||
flight_assistant = create_react_agent(
|
||||
model="anthropic:claude-3-5-sonnet-latest",
|
||||
# highlight-next-line
|
||||
tools=[book_flight, transfer_to_hotel_assistant],
|
||||
prompt="You are a flight booking assistant",
|
||||
# highlight-next-line
|
||||
name="flight_assistant"
|
||||
)
|
||||
hotel_assistant = create_react_agent(
|
||||
model="anthropic:claude-3-5-sonnet-latest",
|
||||
# highlight-next-line
|
||||
tools=[book_hotel, transfer_to_flight_assistant],
|
||||
prompt="You are a hotel booking assistant",
|
||||
# highlight-next-line
|
||||
name="hotel_assistant"
|
||||
)
|
||||
|
||||
# highlight-next-line
|
||||
swarm = create_swarm(
|
||||
agents=[flight_assistant, hotel_assistant],
|
||||
default_active_agent="flight_assistant"
|
||||
).compile()
|
||||
|
||||
for chunk in supervisor.stream({
|
||||
"messages": "book a flight from BOS to JFK and a stay at McKittrick Hotel"
|
||||
}):
|
||||
print(chunk)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
## Handoffs
|
||||
|
||||
A common pattern in multi-agent interactions is **handoffs**, where one agent *hands off* control to another. Handoffs allow you to specify:
|
||||
|
||||
- **destination**: target agent to navigate to
|
||||
- **payload**: information to pass to that agent
|
||||
|
||||
This is used both by `langgraph-supervisor` (supervisor hands off to individual agents) and `langgraph-swarm` (an individual agent can hand off to other agents).
|
||||
|
||||
To implement handoffs with `create_react_agent`, you need to:
|
||||
|
||||
1. Create a special tool that can transfer control to a different agent
|
||||
|
||||
```python
|
||||
def transfer_to_bob():
|
||||
"""Transfer to bob."""
|
||||
return Command(
|
||||
# name of the agent (node) to go to
|
||||
# highlight-next-line
|
||||
goto="bob",
|
||||
# data to send to the agent
|
||||
# highlight-next-line
|
||||
update={"messages": [...]},
|
||||
# indicate to LangGraph that we need to navigate to
|
||||
# agent node in a parent graph
|
||||
# highlight-next-line
|
||||
graph=Command.PARENT,
|
||||
)
|
||||
```
|
||||
|
||||
1. Create individual agents that have access to handoff tools:
|
||||
|
||||
```python
|
||||
flight_assistant = create_react_agent(
|
||||
..., tools=[book_flight, transfer_to_hotel_assistant]
|
||||
)
|
||||
hotel_assistant = create_react_agent(
|
||||
..., tools=[book_hotel, transfer_to_flight_assistant]
|
||||
)
|
||||
```
|
||||
|
||||
1. Define a parent graph that contains individual agents as nodes:
|
||||
|
||||
```python
|
||||
from langgraph.graph import StateGraph, MessagesState
|
||||
multi_agent_graph = (
|
||||
StateGraph(MessagesState)
|
||||
.add_node(flight_assistant)
|
||||
.add_node(hotel_assistant)
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
Putting this together, here is how you can implement a simple multi-agent system with two agents — a flight booking assistant and a hotel booking assistant:
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from langchain_core.tools import tool, InjectedToolCallId
|
||||
from langgraph.prebuilt import create_react_agent, InjectedState
|
||||
from langgraph.graph import StateGraph, START, MessagesState
|
||||
from langgraph.types import Command
|
||||
|
||||
def create_handoff_tool(*, agent_name: str, description: str | None = None):
|
||||
name = f"transfer_to_{agent_name}"
|
||||
description = description or f"Transfer to {agent_name}"
|
||||
|
||||
@tool(name, description=description)
|
||||
def handoff_tool(
|
||||
# highlight-next-line
|
||||
state: Annotated[MessagesState, InjectedState], # (1)!
|
||||
# highlight-next-line
|
||||
tool_call_id: Annotated[str, InjectedToolCallId],
|
||||
) -> Command:
|
||||
tool_message = {
|
||||
"role": "tool",
|
||||
"content": f"Successfully transferred to {agent_name}",
|
||||
"name": name,
|
||||
"tool_call_id": tool_call_id,
|
||||
}
|
||||
return Command( # (2)!
|
||||
# highlight-next-line
|
||||
goto=agent_name, # (3)!
|
||||
# highlight-next-line
|
||||
update={"messages": state["messages"] + [tool_message]}, # (4)!
|
||||
# highlight-next-line
|
||||
graph=Command.PARENT, # (5)!
|
||||
)
|
||||
return handoff_tool
|
||||
|
||||
# Handoffs
|
||||
transfer_to_hotel_assistant = create_handoff_tool(
|
||||
agent_name="hotel_assistant",
|
||||
description="Transfer user to the hotel-booking assistant.",
|
||||
)
|
||||
transfer_to_flight_assistant = create_handoff_tool(
|
||||
agent_name="flight_assistant",
|
||||
description="Transfer user to the flight-booking assistant.",
|
||||
)
|
||||
|
||||
# Simple agent tools
|
||||
def book_hotel(hotel_name: str):
|
||||
"""Book a hotel"""
|
||||
return f"Successfully booked a stay at {hotel_name}."
|
||||
|
||||
def book_flight(from_airport: str, to_airport: str):
|
||||
"""Book a flight"""
|
||||
return f"Successfully booked a flight from {from_airport} to {to_airport}."
|
||||
|
||||
# Define agents
|
||||
flight_assistant = create_react_agent(
|
||||
model="anthropic:claude-3-5-sonnet-latest",
|
||||
# highlight-next-line
|
||||
tools=[book_flight, transfer_to_hotel_assistant],
|
||||
prompt="You are a flight booking assistant",
|
||||
# highlight-next-line
|
||||
name="flight_assistant"
|
||||
)
|
||||
hotel_assistant = create_react_agent(
|
||||
model="anthropic:claude-3-5-sonnet-latest",
|
||||
# highlight-next-line
|
||||
tools=[book_hotel, transfer_to_flight_assistant],
|
||||
prompt="You are a hotel booking assistant",
|
||||
# highlight-next-line
|
||||
name="hotel_assistant"
|
||||
)
|
||||
|
||||
# Define multi-agent graph
|
||||
multi_agent_graph = (
|
||||
StateGraph(MessagesState)
|
||||
.add_node(flight_assistant)
|
||||
.add_node(hotel_assistant)
|
||||
.add_edge(START, "flight_assistant")
|
||||
.compile()
|
||||
)
|
||||
|
||||
# Run the multi-agent graph
|
||||
for chunk in multi_agent_graph.stream({
|
||||
"messages": "book a flight from BOS to JFK and a stay at McKittrick Hotel"
|
||||
}):
|
||||
print(chunk)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
1. Access agent's state
|
||||
2. The `Command` primitive allows specifying a state update and a node transition as a single operation, making it useful for implementing handoffs.
|
||||
3. Name of the agent or node to hand off to.
|
||||
4. Take the agent's messages and **add** them to the parent's **state** as part of the handoff. The next agent will see the parent state.
|
||||
5. Indicate to LangGraph that we need to navigate to agent node in a **parent** multi-agent graph.
|
||||
|
||||
!!! Note
|
||||
This handoff implementation assumes that:
|
||||
|
||||
- each agent receives overall message history (across all agents) in the multi-agent system as its input
|
||||
- each agent outputs its internal messages history to the overall message history of the multi-agent system
|
||||
|
||||
Check out LangGraph [supervisor](https://github.com/langchain-ai/langgraph-supervisor-py#customizing-handoff-tools) and [swarm](https://github.com/langchain-ai/langgraph-swarm-py#customizing-handoff-tools) documentation to learn how to customize handoffs.
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: Overview
|
||||
---
|
||||
|
||||
# Agent development with LangGraph
|
||||
|
||||
**LangGraph** provides both low-level primitives and high-level prebuilt components for building agent-based applications. This section focuses on the **prebuilt**, **reusable** components designed to help you construct agentic systems quickly and reliably—without the need to implement orchestration, memory, or human feedback handling from scratch.
|
||||
|
||||
## Key features
|
||||
|
||||
LangGraph includes several capabilities essential for building robust, production-ready agentic systems:
|
||||
|
||||
- [**Memory integration**](./memory.md): Native support for *short-term* (session-based) and *long-term* (persistent across sessions) memory, enabling stateful behaviors in chatbots and assistants.
|
||||
- [**Human-in-the-loop control**](./human-in-the-loop.md): Execution can pause *indefinitely* to await human feedback—unlike websocket-based solutions limited to real-time interaction. This enables asynchronous approval, correction, or intervention at any point in the workflow.
|
||||
- [**Streaming support**](./streaming.md): Real-time streaming of agent state, model tokens, tool outputs, or combined streams.
|
||||
- [**Deployment tooling**](./deployment.md): Includes infrastructure-free deployment tools. [**LangGraph Platform**](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/) supports testing, debugging, and deployment.
|
||||
- **[Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/)**: A visual IDE for inspecting and debugging workflows.
|
||||
- Supports multiple [**deployment options**](https://langchain-ai.github.io/langgraph/tutorials/deployment/) for production.
|
||||
|
||||
## High-level building blocks
|
||||
|
||||
LangGraph comes with a set of prebuilt components that implement common agent behaviors and workflows. These abstractions are built on top of the LangGraph framework, offering a faster path to production while remaining flexible for advanced customization.
|
||||
|
||||
Using LangGraph for agent development allows you to focus on your application's logic and behavior, instead of building and maintaining the supporting infrastructure for state, memory, and human feedback.
|
||||
|
||||
## Package ecosystem
|
||||
|
||||
The high-level components are organized into several packages, each with a specific focus.
|
||||
|
||||
| Package | Description | Installation |
|
||||
|--------------------------------------------|-----------------------------------------------------------------------------|-----------------------------------------|
|
||||
| `langgraph-prebuilt` (part of `langgraph`) | Prebuilt components to [**create agents**](./agents.md) | `pip install -U langgraph langchain` |
|
||||
| `langgraph-supervisor` | Tools for building [**supervisor**](./multi-agent.md#supervisor) agents | `pip install -U langgraph-supervisor` |
|
||||
| `langgraph-swarm` | Tools for building a [**swarm**](./multi-agent.md#swarm) multi-agent system | `pip install -U langgraph-swarm` |
|
||||
| `langchain-mcp-adapters` | Interfaces to [**MCP servers**](./mcp.md) for tool and resource integration | `pip install -U langchain-mcp-adapters` |
|
||||
| `langmem` | Agent memory management: [**short-term and long-term**](./memory.md) | `pip install -U langmem` |
|
||||
| `agentevals` | Utilities to [**evaluate agent performance**](./evals.md) | `pip install -U agentevals` |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 🚀 Prebuilt Agents
|
||||
# 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.
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
# Running agents
|
||||
|
||||
|
||||
Agents support both synchronous and asynchronous execution using either `.invoke()` / `await .invoke()` for full responses, or `.stream()` / `.astream()` for **incremental** [streaming](#streaming) output. This section explains how to provide input, interpret output, enable streaming, and control execution limits.
|
||||
|
||||
|
||||
## Basic usage
|
||||
|
||||
Agents can be executed in two primary modes:
|
||||
|
||||
- **Synchronous** using `.invoke()` or `.stream()`
|
||||
- **Asynchronous** using `await .invoke()` or `async for` with `.astream()`
|
||||
|
||||
=== "Sync invocation"
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
agent = create_react_agent(...)
|
||||
|
||||
# highlight-next-line
|
||||
response = agent.invoke({"messages": "what is the weather in sf"})
|
||||
```
|
||||
|
||||
=== "Async invocation"
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
agent = create_react_agent(...)
|
||||
# highlight-next-line
|
||||
response = await agent.ainvoke({"messages": "what is the weather in sf"})
|
||||
```
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
Agents use a language model that expects a list of `messages` as an input. Therefore, agent inputs and outputs are stored as a list of `messages` under the `messages` key in the agent [state](../concepts/low_level.md#working-with-messages-in-graph-state).
|
||||
|
||||
## Input format
|
||||
|
||||
Agent input must be a dictionary with a `messages` key. Supported formats are:
|
||||
|
||||
| Format | Example |
|
||||
|--------------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| String | `{"messages": "Hello"}` — Interpreted as a [HumanMessage](https://python.langchain.com/docs/concepts/messages/#humanmessage) |
|
||||
| Message dictionary | `{"messages": {"role": "user", "content": "Hello"}}` |
|
||||
| List of messages | `{"messages": [{"role": "user", "content": "Hello"}]}` |
|
||||
| With custom state | `{"messages": [{"role": "user", "content": "Hello"}], "user_name": "Alice"}` — If using a custom `state_schema` |
|
||||
|
||||
Messages are automatically converted into LangChain's internal message format. You can read
|
||||
more about [LangChain messages](https://python.langchain.com/docs/concepts/messages/#langchain-messages) in the LangChain documentation.
|
||||
|
||||
!!! tip "Using custom agent state"
|
||||
|
||||
You can provide additional fields defined in your agent’s state schema directly in the input dictionary. This allows dynamic behavior based on runtime data or prior tool outputs.
|
||||
See the [context guide](./context.md) for full details.
|
||||
|
||||
!!! note
|
||||
|
||||
A string input for `messages` is converted to a [HumanMessage](https://python.langchain.com/docs/concepts/messages/#humanmessage). This behavior differs from the `prompt` parameter in `create_react_agent`, which is interpreted as a [SystemMessage](https://python.langchain.com/docs/concepts/messages/#systemmessage) when passed as a string.
|
||||
|
||||
|
||||
## Output format
|
||||
|
||||
Agent output is a dictionary containing:
|
||||
|
||||
- `messages`: A list of all messages exchanged during execution (user input, assistant replies, tool invocations).
|
||||
- Optionally, `structured_response` if [structured output](./agents.md#structured-output) is configured.
|
||||
- If using a custom `state_schema`, additional keys corresponding to your defined fields may also be present in the output. These can hold updated state values from tool execution or prompt logic.
|
||||
|
||||
See the [context guide](./context.md) for more details on working with custom state schemas and accessing context.
|
||||
|
||||
## Streaming output
|
||||
|
||||
Agents support streaming responses for more responsive applications. This includes:
|
||||
|
||||
- **Progress updates** after each step
|
||||
- **LLM tokens** as they're generated
|
||||
- **Custom tool messages** during execution
|
||||
|
||||
Streaming is available in both sync and async modes:
|
||||
|
||||
=== "Sync streaming"
|
||||
|
||||
```python
|
||||
for chunk in agent.stream(
|
||||
{"messages": "what is the weather in sf"},
|
||||
stream_mode="updates"
|
||||
):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
=== "Async streaming"
|
||||
|
||||
```python
|
||||
async for chunk in agent.astream(
|
||||
{"messages": "what is the weather in sf"},
|
||||
stream_mode="updates"
|
||||
):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
!!! tip
|
||||
|
||||
For full details, see the [streaming guide](./streaming.md).
|
||||
|
||||
## Max iterations
|
||||
|
||||
To control agent execution and avoid infinite loops, set a recursion limit. This defines the maximum number of steps the agent can take before raising a `GraphRecursionError`. You can configure `recursion_limit` at runtime or when defining agent via `.with_config()`:
|
||||
|
||||
=== "Runtime"
|
||||
|
||||
```python
|
||||
from langgraph.errors import GraphRecursionError
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
max_iterations = 3
|
||||
# highlight-next-line
|
||||
recursion_limit = 2 * max_iterations + 1
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-5-haiku-latest",
|
||||
tools=[get_weather]
|
||||
)
|
||||
|
||||
try:
|
||||
response = agent.invoke(
|
||||
{"messages": "what's the weather in sf"},
|
||||
# highlight-next-line
|
||||
{"recursion_limit": recursion_limit},
|
||||
)
|
||||
except GraphRecursionError:
|
||||
print("Agent stopped due to max iterations.")
|
||||
```
|
||||
|
||||
=== "`.with_config()`"
|
||||
|
||||
```python
|
||||
from langgraph.errors import GraphRecursionError
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
max_iterations = 3
|
||||
# highlight-next-line
|
||||
recursion_limit = 2 * max_iterations + 1
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-5-haiku-latest",
|
||||
tools=[get_weather]
|
||||
)
|
||||
# highlight-next-line
|
||||
agent_with_recursion_limit = agent.with_config(recursion_limit=recursion_limit)
|
||||
|
||||
try:
|
||||
response = agent_with_recursion_limit.invoke(
|
||||
{"messages": "what's the weather in sf"},
|
||||
)
|
||||
except GraphRecursionError:
|
||||
print("Agent stopped due to max iterations.")
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
* [Async programming in LangChain](https://python.langchain.com/docs/concepts/async)
|
||||
@@ -0,0 +1,208 @@
|
||||
# Streaming
|
||||
|
||||
Streaming is key to building responsive applications. There are a few types of data you’ll want to stream:
|
||||
|
||||
1. [**Agent progress**](#agent-progress) — get updates after each node in the agent graph is executed.
|
||||
2. [**LLM tokens**](#llm-tokens) — stream tokens as they are generated by the language model.
|
||||
3. [**Custom updates**](#tool-updates) — emit custom data from tools during execution (e.g., "Fetched 10/100 records")
|
||||
|
||||
You can stream [more than one type of data](#stream-multiple-modes) at a time.
|
||||
|
||||
|
||||
<figure markdown="1">
|
||||
{: style="max-height:300px"}
|
||||
<figcaption>
|
||||
Waiting is for pigeons.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
## Agent progress
|
||||
|
||||
To stream agent progress, use the [`stream()`][langgraph.graph.state.CompiledStateGraph.stream] or [`astream()`][langgraph.graph.state.CompiledStateGraph.astream] methods with [`stream_mode="updates"`](https://langchain-ai.github.io/langgraph/how-tos/streaming/#updates). This emits an event after every agent step.
|
||||
|
||||
For example, if you have an agent that calls a tool once, you should see the following updates:
|
||||
|
||||
* **LLM node**: AI message with tool call requests
|
||||
* **Tool node**: Tool message with execution result
|
||||
* **LLM node**: Final AI response
|
||||
|
||||
=== "Sync"
|
||||
|
||||
```python
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
)
|
||||
# highlight-next-line
|
||||
for chunk in agent.stream(
|
||||
{"messages": "what is the weather in sf"},
|
||||
# highlight-next-line
|
||||
stream_mode="updates"
|
||||
):
|
||||
print(chunk)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
=== "Async"
|
||||
|
||||
```python
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
)
|
||||
# highlight-next-line
|
||||
async for chunk in agent.astream(
|
||||
{"messages": "what is the weather in sf"},
|
||||
# highlight-next-line
|
||||
stream_mode="updates"
|
||||
):
|
||||
print(chunk)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
## LLM tokens
|
||||
|
||||
To stream tokens as they are produced by the LLM, use `stream_mode="messages"`:
|
||||
|
||||
=== "Sync"
|
||||
|
||||
```python
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
)
|
||||
# highlight-next-line
|
||||
for token, metadata in agent.stream(
|
||||
{"messages": "what is the weather in sf"},
|
||||
# highlight-next-line
|
||||
stream_mode="messages"
|
||||
):
|
||||
print("Token", token)
|
||||
print("Metadata", metadata)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
=== "Async"
|
||||
|
||||
```python
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
)
|
||||
# highlight-next-line
|
||||
async for token, metadata in agent.astream(
|
||||
{"messages": "what is the weather in sf"},
|
||||
# highlight-next-line
|
||||
stream_mode="messages"
|
||||
):
|
||||
print("Token", token)
|
||||
print("Metadata", metadata)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
## Tool updates
|
||||
|
||||
To stream updates from tools as they are executed, you can use [get_stream_writer][langgraph.config.get_stream_writer].
|
||||
|
||||
=== "Sync"
|
||||
|
||||
```python
|
||||
# highlight-next-line
|
||||
from langgraph.config import get_stream_writer
|
||||
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get weather for a given city."""
|
||||
# highlight-next-line
|
||||
writer = get_stream_writer()
|
||||
# stream any arbitrary data
|
||||
# highlight-next-line
|
||||
writer(f"Looking up data for city: {city}")
|
||||
return f"It's always sunny in {city}!"
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
)
|
||||
|
||||
for chunk in agent.stream(
|
||||
{"messages": "what is the weather in sf"},
|
||||
# highlight-next-line
|
||||
stream_mode="custom"
|
||||
):
|
||||
print(chunk)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
=== "Async"
|
||||
|
||||
```python
|
||||
# highlight-next-line
|
||||
from langgraph.config import get_stream_writer
|
||||
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get weather for a given city."""
|
||||
# highlight-next-line
|
||||
writer = get_stream_writer()
|
||||
# stream any arbitrary data
|
||||
# highlight-next-line
|
||||
writer(f"Looking up data for city: {city}")
|
||||
return f"It's always sunny in {city}!"
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
)
|
||||
|
||||
async for chunk in agent.astream(
|
||||
{"messages": "what is the weather in sf"},
|
||||
# highlight-next-line
|
||||
stream_mode="custom"
|
||||
):
|
||||
print(chunk)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
!!! Note
|
||||
If you add `get_stream_writer` inside your tool, you won't be able to invoke the tool outside of a LangGraph execution context.
|
||||
|
||||
## Stream multiple modes
|
||||
|
||||
You can specify multiple streaming modes by passing stream mode as a list: `stream_mode=["updates", "messages", "custom"]`:
|
||||
|
||||
=== "Sync"
|
||||
|
||||
```python
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
)
|
||||
|
||||
for stream_mode, chunk in agent.stream(
|
||||
{"messages": "what is the weather in sf"},
|
||||
# highlight-next-line
|
||||
stream_mode=["updates", "messages", "custom"]
|
||||
):
|
||||
print(chunk)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
=== "Async"
|
||||
|
||||
```python
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[get_weather],
|
||||
)
|
||||
|
||||
async for stream_mode, chunk in agent.astream(
|
||||
{"messages": "what is the weather in sf"},
|
||||
# highlight-next-line
|
||||
stream_mode=["updates", "messages", "custom"]
|
||||
):
|
||||
print(chunk)
|
||||
print("\n")
|
||||
```
|
||||
|
||||
## Additional resources
|
||||
|
||||
* [Streaming in LangGraph](https://langchain-ai.github.io/langgraph/how-tos/streaming)
|
||||
@@ -0,0 +1,268 @@
|
||||
# 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.
|
||||
|
||||
You can either [define your own tools](#define-simple-tools) or use [prebuilt integrations](#prebuilt-tools) that LangChain provides.
|
||||
|
||||
## Define simple tools
|
||||
|
||||
You can pass a vanilla function to `create_react_agent` to use as a tool:
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
def multiply(a: int, b: int) -> int:
|
||||
"""Multiply two numbers."""
|
||||
return a * b
|
||||
|
||||
create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet",
|
||||
tools=[multiply]
|
||||
)
|
||||
```
|
||||
|
||||
`create_react_agent` automatically converts vanilla functions to [LangChain tools](https://python.langchain.com/docs/concepts/tools/#tool-interface).
|
||||
|
||||
## Customize tools
|
||||
|
||||
For more control over tool behavior, use the `@tool` decorator:
|
||||
|
||||
```python
|
||||
# highlight-next-line
|
||||
from langchain_core.tools import tool
|
||||
|
||||
# highlight-next-line
|
||||
@tool("multiply_tool", parse_docstring=True)
|
||||
def multiply(a: int, b: int) -> int:
|
||||
"""Multiply two numbers.
|
||||
|
||||
Args:
|
||||
a: First operand
|
||||
b: Second operand
|
||||
"""
|
||||
return a * b
|
||||
```
|
||||
|
||||
You can also define a custom input schema using Pydantic:
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class MultiplyInputSchema(BaseModel):
|
||||
"""Multiply two numbers"""
|
||||
a: int = Field(description="First operand")
|
||||
b: int = Field(description="Second operand")
|
||||
|
||||
# highlight-next-line
|
||||
@tool("multiply_tool", args_schema=MultiplyInputSchema)
|
||||
def multiply(a: int, b: int) -> int:
|
||||
return a * b
|
||||
```
|
||||
|
||||
For additional customization, refer to the [custom tools guide](https://python.langchain.com/docs/how_to/custom_tools/).
|
||||
|
||||
## Hide arguments from the model
|
||||
|
||||
Some tools require runtime-only arguments (e.g., user ID or session context) that should not be controllable by the model.
|
||||
|
||||
You can put these arguments in the `state` or `config` of the agent, and access
|
||||
this information inside the tool:
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import InjectedState
|
||||
from langgraph.prebuilt.chat_agent_executor import AgentState
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
|
||||
def my_tool(
|
||||
# This will be populated by an LLM
|
||||
tool_arg: str,
|
||||
# access information that's dynamically updated inside the agent
|
||||
# highlight-next-line
|
||||
state: Annotated[AgentState, InjectedState],
|
||||
# access static data that is passed at agent invocation
|
||||
# highlight-next-line
|
||||
config: RunnableConfig,
|
||||
) -> str:
|
||||
"""My tool."""
|
||||
do_something_with_state(state["messages"])
|
||||
do_something_with_config(config)
|
||||
...
|
||||
```
|
||||
|
||||
## Disable parallel tool calling
|
||||
|
||||
Some model providers support executing multiple tools in parallel, but
|
||||
allow users to disable this feature.
|
||||
|
||||
For supported providers, you can disable parallel tool calling by setting `parallel_tool_calls=False` via the `model.bind_tools()` method:
|
||||
|
||||
```python
|
||||
from langchain.chat_models import init_chat_model
|
||||
|
||||
def add(a: int, b: int) -> int:
|
||||
"""Add two numbers"""
|
||||
return a + b
|
||||
|
||||
def multiply(a: int, b: int) -> int:
|
||||
"""Multiply two numbers."""
|
||||
return a * b
|
||||
|
||||
model = init_chat_model("anthropic:claude-3-5-sonnet-latest", temperature=0)
|
||||
tools = [add, multiply]
|
||||
agent = create_react_agent(
|
||||
# disable parallel tool calls
|
||||
# highlight-next-line
|
||||
model=model.bind_tools(tools, parallel_tool_calls=False),
|
||||
tools=tools
|
||||
)
|
||||
|
||||
agent.invoke({"messages": "what's 3 + 5 and 4 * 7? make both calculations in parallel"})
|
||||
```
|
||||
|
||||
## Return tool results directly
|
||||
|
||||
Use `return_direct=True` to return tool results immediately and stop the agent loop:
|
||||
|
||||
```python
|
||||
from langchain_core.tools import tool
|
||||
|
||||
# highlight-next-line
|
||||
@tool(return_direct=True)
|
||||
def add(a: int, b: int) -> int:
|
||||
"""Add two numbers"""
|
||||
return a + b
|
||||
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[add]
|
||||
)
|
||||
|
||||
agent.invoke({"messages": "what's 3 + 5?"})
|
||||
```
|
||||
|
||||
## Force tool use
|
||||
|
||||
To force the agent to use specific tools, you can set the `tool_choice` option in `model.bind_tools()`:
|
||||
|
||||
```python
|
||||
from langchain_core.tools import tool
|
||||
|
||||
# highlight-next-line
|
||||
@tool(return_direct=True)
|
||||
def greet(user_name: str) -> int:
|
||||
"""Greet user."""
|
||||
return f"Hello {user_name}!"
|
||||
|
||||
tools = [greet]
|
||||
|
||||
agent = create_react_agent(
|
||||
# highlight-next-line
|
||||
model=model.bind_tools(tools, tool_choice={"type": "tool", "name": "greet"}),
|
||||
tools=tools
|
||||
)
|
||||
|
||||
agent.invoke({"messages": "Hi, I am Bob"})
|
||||
```
|
||||
|
||||
!!! Warning "Avoid infinite loops"
|
||||
|
||||
Forcing tool usage without stopping conditions can create infinite loops. Use one of the following safeguards:
|
||||
|
||||
- Mark the tool with [`return_direct=True`](#return-tool-results-directly) to end the loop after execution.
|
||||
- Set [`recursion_limit`](../concepts/low_level.md#recursion-limit) to restrict the number of execution steps.
|
||||
|
||||
## Handle tool errors
|
||||
|
||||
By default, the agent will catch all exceptions raised during tool calls and will pass those as tool messages to the LLM. To control how the errors are handled, you can use the prebuilt [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode] — the node that executes tools inside `create_react_agent` — via its `handle_tool_errors` parameter:
|
||||
|
||||
=== "Enable error handling (default)"
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
def multiply(a: int, b: int) -> int:
|
||||
"""Multiply two numbers."""
|
||||
if a == 42:
|
||||
raise ValueError("The ultimate error")
|
||||
return a * b
|
||||
|
||||
# Run with error handling (default)
|
||||
agent = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=[multiply]
|
||||
)
|
||||
agent.invoke({"messages": "what's 42 x 7?"})
|
||||
```
|
||||
|
||||
=== "Disable error handling"
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent, ToolNode
|
||||
|
||||
def multiply(a: int, b: int) -> int:
|
||||
"""Multiply two numbers."""
|
||||
if a == 42:
|
||||
raise ValueError("The ultimate error")
|
||||
return a * b
|
||||
|
||||
# highlight-next-line
|
||||
tool_node = ToolNode(
|
||||
[multiply],
|
||||
# highlight-next-line
|
||||
handle_tool_errors=False # (1)!
|
||||
)
|
||||
agent_no_error_handling = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=tool_node
|
||||
)
|
||||
agent_no_error_handling.invoke({"messages": "what's 42 x 7?"})
|
||||
```
|
||||
|
||||
1. This disables error handling (enabled by default). See all available strategies in the [API reference][langgraph.prebuilt.tool_node.ToolNode].
|
||||
|
||||
=== "Custom error handling"
|
||||
|
||||
```python
|
||||
from langgraph.prebuilt import create_react_agent, ToolNode
|
||||
|
||||
def multiply(a: int, b: int) -> int:
|
||||
"""Multiply two numbers."""
|
||||
if a == 42:
|
||||
raise ValueError("The ultimate error")
|
||||
return a * b
|
||||
|
||||
# highlight-next-line
|
||||
tool_node = ToolNode(
|
||||
[multiply],
|
||||
# highlight-next-line
|
||||
handle_tool_errors=(
|
||||
"Can't use 42 as a first operand, you must switch operands!" # (1)!
|
||||
)
|
||||
)
|
||||
agent_custom_error_handling = create_react_agent(
|
||||
model="anthropic:claude-3-7-sonnet-latest",
|
||||
tools=tool_node
|
||||
)
|
||||
agent_custom_error_handling.invoke({"messages": "what's 42 x 7?"})
|
||||
```
|
||||
|
||||
1. This provides a custom message to send to the LLM in case of an exception. See all available strategies in the [API reference][langgraph.prebuilt.tool_node.ToolNode].
|
||||
|
||||
See [API reference][langgraph.prebuilt.tool_node.ToolNode] for more information on different tool error handling options.
|
||||
|
||||
## 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.
|
||||
|
||||
You can browse the full list of available integrations in the [LangChain integrations directory](https://python.langchain.com/docs/integrations/tools/).
|
||||
|
||||
Some commonly used tool categories include:
|
||||
|
||||
- **Search**: Bing, SerpAPI, Tavily
|
||||
- **Code interpreters**: Python REPL, Node.js REPL
|
||||
- **Databases**: SQL, MongoDB, Redis
|
||||
- **Web data**: Web scraping and browsing
|
||||
- **APIs**: OpenWeatherMap, NewsAPI, and others
|
||||
|
||||
These integrations can be configured and added to your agents using the same `tools` parameter shown in the examples above.
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# 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.
|
||||
|
||||
## Run agent in UI
|
||||
|
||||
First, set up LangGraph API server [locally](./deployment.md#launch-langgraph-server-locally) or deploy your agent on [LangGraph Cloud](https://langchain-ai.github.io/langgraph/cloud/quick_start/).
|
||||
|
||||
Then, navigate to [Agent Chat UI](https://agentchat.vercel.app), or clone the repository and [run the dev server locally](https://github.com/langchain-ai/agent-chat-ui?tab=readme-ov-file#setup):
|
||||
|
||||
<video controls src="../assets/base-chat-ui.mp4" type="video/mp4"></video>
|
||||
|
||||
!!! Tip
|
||||
|
||||
UI has out-of-box support for rendering tool calls, and tool result messages. To customize what messages are shown, see the [Hiding Messages in the Chat](https://github.com/langchain-ai/agent-chat-ui?tab=readme-ov-file#hiding-messages-in-the-chat) section in the Agent Chat UI documentation.
|
||||
|
||||
## Add human-in-the-loop
|
||||
|
||||
Agent Chat UI has full support for [human-in-the-loop](../concepts/human_in_the_loop.md) workflows. To try it out, replace the agent code in `src/agent/graph.py` (from the [deployment](./deployment.md) guide) with this [agent implementation](./human-in-the-loop.md#using-with-agent-inbox):
|
||||
|
||||
<video controls src="../assets/interrupt-chat-ui.mp4" type="video/mp4"></video>
|
||||
|
||||
!!! Important
|
||||
|
||||
Agent Chat UI works best if your LangGraph agent interrupts using the [`HumanInterrupt` schema][langgraph.prebuilt.interrupt.HumanInterrupt]. If you do not use that schema, the Agent Chat UI will be able to render the input passed to the `interrupt` function, but it will not have full support for resuming your graph.
|
||||
|
||||
## Generative UI
|
||||
|
||||
You can also use generative UI in the Agent Chat UI.
|
||||
|
||||
Generative UI allows you to define [React](https://react.dev/) components, and push them to the UI from the LangGraph server. For more documentation on building generative UI LangGraph agents, read [these docs](https://langchain-ai.github.io/langgraph/cloud/how-tos/generative_ui_react/).
|
||||
@@ -54,7 +54,7 @@ pip install -U "langgraph-cli[inmem]"
|
||||
|
||||
### `up`
|
||||
|
||||
The `langgraph up` command starts an instance of the [LangGraph API server](./langgraph_server.md) locally in a docker container. This requires thedocker server to be running locally. It also requires a LangSmith API key for local development or a license key for production use.
|
||||
The `langgraph up` command starts an instance of the [LangGraph API server](./langgraph_server.md) locally in a docker container. This requires the docker server to be running locally. It also requires a LangSmith API key for local development or a license key for production use.
|
||||
|
||||
The server includes all API endpoints for your graph's runs, threads, assistants, etc. as well as the other services required to run your agent, including a managed database for checkpointing and storage.
|
||||
|
||||
|
||||
@@ -17,3 +17,12 @@
|
||||
options:
|
||||
members:
|
||||
- ValidationNode
|
||||
|
||||
|
||||
::: langgraph.prebuilt.interrupt
|
||||
options:
|
||||
members:
|
||||
- HumanInterruptConfig
|
||||
- ActionRequest
|
||||
- HumanInterrupt
|
||||
- HumanResponse
|
||||
@@ -741,13 +741,7 @@
|
||||
"from IPython.display import Image, display\n",
|
||||
"from langchain_core.runnables.graph import MermaidDrawMethod\n",
|
||||
"\n",
|
||||
"display(\n",
|
||||
" Image(\n",
|
||||
" app.get_graph().draw_mermaid_png(\n",
|
||||
" draw_method=MermaidDrawMethod.API,\n",
|
||||
" )\n",
|
||||
" )\n",
|
||||
")"
|
||||
"display(Image(app.get_graph().draw_mermaid_png()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -91,7 +91,7 @@ plugins:
|
||||
- "!^_"
|
||||
|
||||
nav:
|
||||
- Home:
|
||||
- LangGraph:
|
||||
- index.md
|
||||
- Get started:
|
||||
- Learn the basics: tutorials/introduction.ipynb
|
||||
@@ -388,8 +388,6 @@ nav:
|
||||
- tutorials/auth/resource_auth.md
|
||||
- tutorials/auth/add_auth_server.md
|
||||
- Resources:
|
||||
# NOTE: prebuilt.md is auto-generated by `make build-prebuilt`
|
||||
- Prebuilt Agents: prebuilt.md
|
||||
- Companies using LangGraph: adopters.md
|
||||
- LLMS-txt: llms-txt-overview.md
|
||||
- FAQ: concepts/faq.md
|
||||
@@ -404,6 +402,26 @@ nav:
|
||||
- troubleshooting/errors/INVALID_LICENSE.md
|
||||
- LangGraph Academy Course: https://academy.langchain.com/courses/intro-to-langgraph
|
||||
|
||||
- Agents:
|
||||
- agents/overview.md
|
||||
- Get started:
|
||||
- agents/agents.md
|
||||
- Documentation:
|
||||
- agents/run_agents.md
|
||||
- agents/streaming.md
|
||||
- agents/models.md
|
||||
- agents/tools.md
|
||||
- agents/mcp.md
|
||||
- agents/context.md
|
||||
- agents/memory.md
|
||||
- agents/human-in-the-loop.md
|
||||
- agents/multi-agent.md
|
||||
- agents/evals.md
|
||||
- agents/deployment.md
|
||||
- agents/ui.md
|
||||
- Resources:
|
||||
# NOTE: prebuilt.md is auto-generated by `make build-prebuilt`
|
||||
- agents/prebuilt.md
|
||||
- API reference:
|
||||
- reference/index.md
|
||||
- Library:
|
||||
|
||||
@@ -3387,14 +3387,14 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10"
|
||||
|
||||
[[package]]
|
||||
name = "langchain-core"
|
||||
version = "0.3.52"
|
||||
version = "0.3.54"
|
||||
description = "Building applications with LLMs through composability"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
groups = ["docs", "test"]
|
||||
files = [
|
||||
{file = "langchain_core-0.3.52-py3-none-any.whl", hash = "sha256:cd137109c1e3d04f5a582c2cae9539b2cd5e4b795f486b58969dbc3d0387fe7c"},
|
||||
{file = "langchain_core-0.3.52.tar.gz", hash = "sha256:f1981ec9efa4fceb11ff5ca57f5f9c8e22859cea3a94f8a044e6de8815afbd57"},
|
||||
{file = "langchain_core-0.3.54-py3-none-any.whl", hash = "sha256:cd42155d9089e2fd4695ee02a4b2bc6daf55b9d4e1a37639647cf2455ed4fa04"},
|
||||
{file = "langchain_core-0.3.54.tar.gz", hash = "sha256:55ce38939038e19b1271f36f512335462d7f64057b531598b3651d2b403e1b42"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -3530,7 +3530,7 @@ langchain-core = ">=0.3.45,<1.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
description = "Building stateful, multi-actor applications with LLMs"
|
||||
optional = false
|
||||
python-versions = ">=3.9.0,<4.0"
|
||||
@@ -3541,7 +3541,7 @@ develop = true
|
||||
[package.dependencies]
|
||||
langchain-core = ">=0.1,<0.4"
|
||||
langgraph-checkpoint = "^2.0.10"
|
||||
langgraph-prebuilt = ">=0.1.1,<0.2"
|
||||
langgraph-prebuilt = ">=0.1.8,<0.2"
|
||||
langgraph-sdk = "^0.1.42"
|
||||
xxhash = "^3.5.0"
|
||||
|
||||
@@ -5987,7 +5987,6 @@ optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["test"]
|
||||
files = [
|
||||
{file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"},
|
||||
{file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"},
|
||||
]
|
||||
|
||||
@@ -5999,7 +5998,6 @@ optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["test"]
|
||||
files = [
|
||||
{file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"},
|
||||
{file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"},
|
||||
]
|
||||
|
||||
@@ -8902,4 +8900,4 @@ cffi = ["cffi (>=1.11)"]
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "45bbc644a3b878063f5cbb75eed56540423315784f8dd42cfd3937c910dfc9c5"
|
||||
content-hash = "36d7e4c4eba50d5e4dfb2e99964d7b51fe17d36238a912765cca8fc360216079"
|
||||
|
||||
@@ -43,6 +43,7 @@ langchain-cohere = "^0.4.2"
|
||||
|
||||
[tool.poetry.group.test.dependencies]
|
||||
langchain = "^0.3.8"
|
||||
langchain-core = "^0.3.54"
|
||||
langchain-openai = "^0.3.7"
|
||||
langchain-anthropic = "^0.3.8"
|
||||
langchain-nomic = "^0.1.3"
|
||||
|
||||
@@ -14,11 +14,11 @@ from langgraph.types import interrupt
|
||||
"""
|
||||
|
||||
EXPECTED_MARKDOWN = """\
|
||||
API Reference: <a href="https://langchain-ai.github.io/langgraph/reference/types/#langgraph.types.interrupt">interrupt</a>
|
||||
|
||||
```python
|
||||
from langgraph.types import interrupt
|
||||
```
|
||||
|
||||
API Reference: <a href="https://langchain-ai.github.io/langgraph/reference/types/#langgraph.types.interrupt">interrupt</a>
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -357,6 +357,29 @@ class PostgresSaver(BasePostgresSaver):
|
||||
),
|
||||
)
|
||||
|
||||
def delete_thread(self, thread_id: str) -> None:
|
||||
"""Delete all checkpoints and writes associated with a thread ID.
|
||||
|
||||
Args:
|
||||
thread_id (str): The thread ID to delete.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
with self._cursor(pipeline=True) as cur:
|
||||
cur.execute(
|
||||
"DELETE FROM checkpoints WHERE thread_id = %s",
|
||||
(str(thread_id),),
|
||||
)
|
||||
cur.execute(
|
||||
"DELETE FROM checkpoint_blobs WHERE thread_id = %s",
|
||||
(str(thread_id),),
|
||||
)
|
||||
cur.execute(
|
||||
"DELETE FROM checkpoint_writes WHERE thread_id = %s",
|
||||
(str(thread_id),),
|
||||
)
|
||||
|
||||
@contextmanager
|
||||
def _cursor(self, *, pipeline: bool = False) -> Iterator[Cursor[DictRow]]:
|
||||
"""Create a database cursor as a context manager.
|
||||
|
||||
@@ -314,6 +314,29 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
async with self._cursor(pipeline=True) as cur:
|
||||
await cur.executemany(query, params)
|
||||
|
||||
async def adelete_thread(self, thread_id: str) -> None:
|
||||
"""Delete all checkpoints and writes associated with a thread ID.
|
||||
|
||||
Args:
|
||||
thread_id (str): The thread ID to delete.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
async with self._cursor(pipeline=True) as cur:
|
||||
await cur.execute(
|
||||
"DELETE FROM checkpoints WHERE thread_id = %s",
|
||||
(str(thread_id),),
|
||||
)
|
||||
await cur.execute(
|
||||
"DELETE FROM checkpoint_blobs WHERE thread_id = %s",
|
||||
(str(thread_id),),
|
||||
)
|
||||
await cur.execute(
|
||||
"DELETE FROM checkpoint_writes WHERE thread_id = %s",
|
||||
(str(thread_id),),
|
||||
)
|
||||
|
||||
@asynccontextmanager
|
||||
async def _cursor(
|
||||
self, *, pipeline: bool = False
|
||||
@@ -481,5 +504,30 @@ class AsyncPostgresSaver(BasePostgresSaver):
|
||||
self.aput_writes(config, writes, task_id, task_path), self.loop
|
||||
).result()
|
||||
|
||||
def delete_thread(self, thread_id: str) -> None:
|
||||
"""Delete all checkpoints and writes associated with a thread ID.
|
||||
|
||||
Args:
|
||||
thread_id (str): The thread ID to delete.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
try:
|
||||
# check if we are in the main thread, only bg threads can block
|
||||
# we don't check in other methods to avoid the overhead
|
||||
if asyncio.get_running_loop() is self.loop:
|
||||
raise asyncio.InvalidStateError(
|
||||
"Synchronous calls to AsyncPostgresSaver are only allowed from a "
|
||||
"different thread. From the main thread, use the async interface. "
|
||||
"For example, use `await checkpointer.aget_tuple(...)` or `await "
|
||||
"graph.ainvoke(...)`."
|
||||
)
|
||||
except RuntimeError:
|
||||
pass
|
||||
return asyncio.run_coroutine_threadsafe(
|
||||
self.adelete_thread(thread_id), self.loop
|
||||
).result()
|
||||
|
||||
|
||||
__all__ = ["AsyncPostgresSaver", "AsyncShallowPostgresSaver", "Conn"]
|
||||
|
||||
@@ -1320,7 +1320,7 @@ def _ensure_index_config(
|
||||
index_config = index_config.copy()
|
||||
tokenized: list[tuple[str, Union[Literal["$"], list[str]]]] = []
|
||||
tot = 0
|
||||
text_fields = index_config.get("text_fields") or ["$"]
|
||||
text_fields = index_config.get("fields") or ["$"]
|
||||
if isinstance(text_fields, str):
|
||||
text_fields = [text_fields]
|
||||
if not isinstance(text_fields, list):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph-checkpoint-postgres"
|
||||
version = "2.0.19"
|
||||
version = "2.0.21"
|
||||
description = "Library with a Postgres implementation of LangGraph checkpoint saver."
|
||||
authors = []
|
||||
license = "MIT"
|
||||
|
||||
@@ -377,7 +377,7 @@ async def _create_vector_store(
|
||||
"vector_type": vector_type,
|
||||
},
|
||||
"distance_type": distance_type,
|
||||
"text_fields": text_fields,
|
||||
"fields": text_fields,
|
||||
}
|
||||
|
||||
async with await AsyncConnection.connect(
|
||||
|
||||
@@ -401,7 +401,7 @@ def _create_vector_store(
|
||||
"vector_type": vector_type,
|
||||
},
|
||||
"distance_type": distance_type,
|
||||
"text_fields": text_fields,
|
||||
"fields": text_fields,
|
||||
}
|
||||
|
||||
with Connection.connect(admin_conn_string, autocommit=True) as conn:
|
||||
|
||||
@@ -464,6 +464,25 @@ class SqliteSaver(BaseCheckpointSaver[str]):
|
||||
],
|
||||
)
|
||||
|
||||
def delete_thread(self, thread_id: str) -> None:
|
||||
"""Delete all checkpoints and writes associated with a thread ID.
|
||||
|
||||
Args:
|
||||
thread_id (str): The thread ID to delete.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
with self.cursor() as cur:
|
||||
cur.execute(
|
||||
"DELETE FROM checkpoints WHERE thread_id = ?",
|
||||
(str(thread_id),),
|
||||
)
|
||||
cur.execute(
|
||||
"DELETE FROM writes WHERE thread_id = ?",
|
||||
(str(thread_id),),
|
||||
)
|
||||
|
||||
async def aget_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
|
||||
"""Get a checkpoint tuple from the database asynchronously.
|
||||
|
||||
|
||||
@@ -244,6 +244,31 @@ class AsyncSqliteSaver(BaseCheckpointSaver[str]):
|
||||
self.aput_writes(config, writes, task_id, task_path), self.loop
|
||||
).result()
|
||||
|
||||
def delete_thread(self, thread_id: str) -> None:
|
||||
"""Delete all checkpoints and writes associated with a thread ID.
|
||||
|
||||
Args:
|
||||
thread_id (str): The thread ID to delete.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
try:
|
||||
# check if we are in the main thread, only bg threads can block
|
||||
# we don't check in other methods to avoid the overhead
|
||||
if asyncio.get_running_loop() is self.loop:
|
||||
raise asyncio.InvalidStateError(
|
||||
"Synchronous calls to AsyncSqliteSaver are only allowed from a "
|
||||
"different thread. From the main thread, use the async interface. "
|
||||
"For example, use `checkpointer.alist(...)` or `await "
|
||||
"graph.ainvoke(...)`."
|
||||
)
|
||||
except RuntimeError:
|
||||
pass
|
||||
return asyncio.run_coroutine_threadsafe(
|
||||
self.adelete_thread(thread_id), self.loop
|
||||
).result()
|
||||
|
||||
async def setup(self) -> None:
|
||||
"""Set up the checkpoint database asynchronously.
|
||||
|
||||
@@ -535,6 +560,26 @@ class AsyncSqliteSaver(BaseCheckpointSaver[str]):
|
||||
)
|
||||
await self.conn.commit()
|
||||
|
||||
async def adelete_thread(self, thread_id: str) -> None:
|
||||
"""Delete all checkpoints and writes associated with a thread ID.
|
||||
|
||||
Args:
|
||||
thread_id (str): The thread ID to delete.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
async with self.lock, self.conn.cursor() as cur:
|
||||
await cur.execute(
|
||||
"DELETE FROM checkpoints WHERE thread_id = ?",
|
||||
(str(thread_id),),
|
||||
)
|
||||
await cur.execute(
|
||||
"DELETE FROM writes WHERE thread_id = ?",
|
||||
(str(thread_id),),
|
||||
)
|
||||
await self.conn.commit()
|
||||
|
||||
def get_next_version(self, current: Optional[str], channel: ChannelProtocol) -> str:
|
||||
"""Generate the next version ID for a channel.
|
||||
|
||||
|
||||
@@ -321,6 +321,17 @@ class BaseCheckpointSaver(Generic[V]):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def delete_thread(
|
||||
self,
|
||||
thread_id: str,
|
||||
) -> None:
|
||||
"""Delete all checkpoints and writes associated with a specific thread ID.
|
||||
|
||||
Args:
|
||||
thread_id (str): The thread ID whose checkpoints should be deleted.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def aget(self, config: RunnableConfig) -> Optional[Checkpoint]:
|
||||
"""Asynchronously fetch a checkpoint using the given configuration.
|
||||
|
||||
@@ -415,6 +426,17 @@ class BaseCheckpointSaver(Generic[V]):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def adelete_thread(
|
||||
self,
|
||||
thread_id: str,
|
||||
) -> None:
|
||||
"""Delete all checkpoints and writes associated with a specific thread ID.
|
||||
|
||||
Args:
|
||||
thread_id (str): The thread ID whose checkpoints should be deleted.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_next_version(self, current: Optional[V], channel: ChannelProtocol) -> V:
|
||||
"""Generate the next version ID for a channel.
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ class InMemorySaver(
|
||||
],
|
||||
]
|
||||
writes: defaultdict[
|
||||
tuple[str, str, str],
|
||||
tuple[str, str, str], # thread ID, checkpoint NS, checkpoint ID
|
||||
dict[tuple[str, int], tuple[str, str, tuple[str, bytes], str]],
|
||||
]
|
||||
blobs: dict[
|
||||
@@ -451,6 +451,24 @@ class InMemorySaver(
|
||||
task_path,
|
||||
)
|
||||
|
||||
def delete_thread(self, thread_id: str) -> None:
|
||||
"""Delete all checkpoints and writes associated with a thread ID.
|
||||
|
||||
Args:
|
||||
thread_id (str): The thread ID to delete.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if thread_id in self.storage:
|
||||
del self.storage[thread_id]
|
||||
for k in list(self.writes.keys()):
|
||||
if k[0] == thread_id:
|
||||
del self.writes[k]
|
||||
for k in list(self.blobs.keys()):
|
||||
if k[0] == thread_id:
|
||||
del self.blobs[k]
|
||||
|
||||
async def aget_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:
|
||||
"""Asynchronous version of get_tuple.
|
||||
|
||||
@@ -530,6 +548,17 @@ class InMemorySaver(
|
||||
"""
|
||||
return self.put_writes(config, writes, task_id, task_path)
|
||||
|
||||
async def adelete_thread(self, thread_id: str) -> None:
|
||||
"""Delete all checkpoints and writes associated with a thread ID.
|
||||
|
||||
Args:
|
||||
thread_id (str): The thread ID to delete.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
return self.delete_thread(thread_id)
|
||||
|
||||
def get_next_version(self, current: Optional[str], channel: ChannelProtocol) -> str:
|
||||
if current is None:
|
||||
current_v = 0
|
||||
@@ -615,4 +644,4 @@ class PersistentDict(defaultdict):
|
||||
except Exception:
|
||||
logging.error(f"Failed to load file: {fileobj.name}")
|
||||
raise
|
||||
raise ValueError("File not in a supported f ormat")
|
||||
raise ValueError("File not in a supported format")
|
||||
|
||||
@@ -326,6 +326,10 @@ class Config(TypedDict, total=False):
|
||||
Must be >= 20 if provided.
|
||||
"""
|
||||
|
||||
_INTERNAL_docker_tag: Optional[str]
|
||||
"""Optional. Internal use only.
|
||||
"""
|
||||
|
||||
pip_config_file: Optional[str]
|
||||
"""Optional. Path to a pip config file (e.g., "/etc/pip.conf" or "pip.ini") for controlling
|
||||
package installation (custom indices, credentials, etc.).
|
||||
@@ -480,6 +484,7 @@ def validate_config(config: Config) -> Config:
|
||||
"node_version": node_version,
|
||||
"python_version": python_version,
|
||||
"pip_config_file": config.get("pip_config_file"),
|
||||
"_INTERNAL_docker_tag": config.get("_INTERNAL_docker_tag"),
|
||||
"dependencies": config.get("dependencies", []),
|
||||
"dockerfile_lines": config.get("dockerfile_lines", []),
|
||||
"graphs": config.get("graphs", {}),
|
||||
@@ -1025,7 +1030,9 @@ def _get_node_pm_install_cmd(config_path: pathlib.Path, config: Config) -> str:
|
||||
|
||||
|
||||
def python_config_to_docker(
|
||||
config_path: pathlib.Path, config: Config, base_image: str
|
||||
config_path: pathlib.Path,
|
||||
config: Config,
|
||||
base_image: str,
|
||||
) -> tuple[str, dict[str, str]]:
|
||||
"""Generate a Dockerfile from the configuration."""
|
||||
# configure pip
|
||||
@@ -1040,6 +1047,8 @@ def python_config_to_docker(
|
||||
else ""
|
||||
)
|
||||
|
||||
docker_tag = config.get("_INTERNAL_docker_tag") or config["python_version"]
|
||||
|
||||
# collect dependencies
|
||||
pypi_deps = [dep for dep in config["dependencies"] if not dep.startswith(".")]
|
||||
local_deps = _assemble_local_deps(config_path, config)
|
||||
@@ -1160,7 +1169,7 @@ ADD {relpath} /deps/{name}
|
||||
)
|
||||
|
||||
docker_file_contents = [
|
||||
f"FROM {base_image}:{config['python_version']}",
|
||||
f"FROM {base_image}:{docker_tag}",
|
||||
"",
|
||||
os.linesep.join(config["dockerfile_lines"]),
|
||||
"",
|
||||
@@ -1192,10 +1201,13 @@ ADD {relpath} /deps/{name}
|
||||
|
||||
|
||||
def node_config_to_docker(
|
||||
config_path: pathlib.Path, config: Config, base_image: str
|
||||
config_path: pathlib.Path,
|
||||
config: Config,
|
||||
base_image: str,
|
||||
) -> tuple[str, dict[str, str]]:
|
||||
faux_path = f"/deps/{config_path.parent.name}"
|
||||
install_cmd = _get_node_pm_install_cmd(config_path, config)
|
||||
docker_tag = config.get("_INTERNAL_docker_tag") or config["node_version"]
|
||||
|
||||
env_vars: list[str] = []
|
||||
|
||||
@@ -1222,7 +1234,7 @@ def node_config_to_docker(
|
||||
env_vars.append(f"ENV LANGSERVE_GRAPHS='{json.dumps(config['graphs'])}'")
|
||||
|
||||
docker_file_contents = [
|
||||
f"FROM {base_image}:{config['node_version']}",
|
||||
f"FROM {base_image}:{docker_tag}",
|
||||
"",
|
||||
os.linesep.join(config["dockerfile_lines"]),
|
||||
"",
|
||||
@@ -1246,8 +1258,13 @@ def default_base_image(config: Config) -> str:
|
||||
return "langchain/langgraph-api"
|
||||
|
||||
|
||||
def docker_tag(config: Config, base_image: Optional[str] = None) -> str:
|
||||
def docker_tag(
|
||||
config: Config,
|
||||
base_image: Optional[str] = None,
|
||||
) -> str:
|
||||
base_image = base_image or default_base_image(config)
|
||||
if config.get("_INTERNAL_docker_tag"):
|
||||
return f"{base_image}:{config['_INTERNAL_docker_tag']}"
|
||||
|
||||
if config.get("node_version") and not config.get("python_version"):
|
||||
return f"{base_image}:{config['node_version']}"
|
||||
@@ -1255,7 +1272,9 @@ def docker_tag(config: Config, base_image: Optional[str] = None) -> str:
|
||||
|
||||
|
||||
def config_to_docker(
|
||||
config_path: pathlib.Path, config: Config, base_image: Optional[str] = None
|
||||
config_path: pathlib.Path,
|
||||
config: Config,
|
||||
base_image: Optional[str] = None,
|
||||
) -> tuple[str, dict[str, str]]:
|
||||
base_image = base_image or default_base_image(config)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph-cli"
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
description = "CLI for interacting with LangGraph API"
|
||||
authors = []
|
||||
license = "MIT"
|
||||
|
||||
@@ -29,6 +29,17 @@
|
||||
],
|
||||
"description": "Optional. Path to a pip config file (e.g., \"/etc/pip.conf\" or \"pip.ini\") for controlling\npackage installation (custom indices, credentials, etc.).\n\nOnly relevant if Python dependencies are installed via pip. If omitted, default pip settings are used.\n"
|
||||
},
|
||||
"_INTERNAL_docker_tag": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional. Internal use only.\n"
|
||||
},
|
||||
"auth": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -145,6 +156,17 @@
|
||||
],
|
||||
"description": "Optional. Node.js version as a major version (e.g. '20'), if your deployment needs Node.\nMust be >= 20 if provided.\n"
|
||||
},
|
||||
"_INTERNAL_docker_tag": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional. Internal use only.\n"
|
||||
},
|
||||
"auth": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -29,6 +29,17 @@
|
||||
],
|
||||
"description": "Optional. Path to a pip config file (e.g., \"/etc/pip.conf\" or \"pip.ini\") for controlling\npackage installation (custom indices, credentials, etc.).\n\nOnly relevant if Python dependencies are installed via pip. If omitted, default pip settings are used.\n"
|
||||
},
|
||||
"_INTERNAL_docker_tag": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional. Internal use only.\n"
|
||||
},
|
||||
"auth": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -145,6 +156,17 @@
|
||||
],
|
||||
"description": "Optional. Node.js version as a major version (e.g. '20'), if your deployment needs Node.\nMust be >= 20 if provided.\n"
|
||||
},
|
||||
"_INTERNAL_docker_tag": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Optional. Internal use only.\n"
|
||||
},
|
||||
"auth": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -29,6 +29,7 @@ def test_validate_config():
|
||||
}
|
||||
actual_config = validate_config(expected_config)
|
||||
expected_config = {
|
||||
"_INTERNAL_docker_tag": None,
|
||||
"python_version": "3.11",
|
||||
"node_version": None,
|
||||
"pip_config_file": None,
|
||||
@@ -47,6 +48,7 @@ def test_validate_config():
|
||||
# full config
|
||||
env = ".env"
|
||||
expected_config = {
|
||||
"_INTERNAL_docker_tag": None,
|
||||
"python_version": "3.12",
|
||||
"node_version": None,
|
||||
"pip_config_file": "pipconfig.txt",
|
||||
@@ -567,6 +569,39 @@ RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not foun
|
||||
assert additional_contexts == {}
|
||||
|
||||
|
||||
def test_config_to_docker_nodejs_internal_docker_tag():
|
||||
graphs = {"agent": "./graphs/agent.js:graph"}
|
||||
actual_docker_stdin, additional_contexts = config_to_docker(
|
||||
PATH_TO_CONFIG,
|
||||
validate_config(
|
||||
{
|
||||
"node_version": "20",
|
||||
"graphs": graphs,
|
||||
"dockerfile_lines": ["ARG meow", "ARG foo"],
|
||||
"auth": {"path": "./graphs/auth.mts:auth"},
|
||||
"ui": {"agent": "./graphs/agent.ui.jsx"},
|
||||
"ui_config": {"shared": ["nuqs"]},
|
||||
"_INTERNAL_docker_tag": "my-tag",
|
||||
}
|
||||
),
|
||||
"langchain/langgraphjs-api",
|
||||
)
|
||||
expected_docker_stdin = """FROM langchain/langgraphjs-api:my-tag
|
||||
ARG meow
|
||||
ARG foo
|
||||
ADD . /deps/unit_tests
|
||||
RUN cd /deps/unit_tests && npm i
|
||||
ENV LANGGRAPH_AUTH='{"path": "./graphs/auth.mts:auth"}'
|
||||
ENV LANGGRAPH_UI='{"agent": "./graphs/agent.ui.jsx"}'
|
||||
ENV LANGGRAPH_UI_CONFIG='{"shared": ["nuqs"]}'
|
||||
ENV LANGSERVE_GRAPHS='{"agent": "./graphs/agent.js:graph"}'
|
||||
WORKDIR /deps/unit_tests
|
||||
RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts"""
|
||||
|
||||
assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin
|
||||
assert additional_contexts == {}
|
||||
|
||||
|
||||
def test_config_to_docker_gen_ui_python():
|
||||
graphs = {"agent": "./agent.py:graph"}
|
||||
actual_docker_stdin, additional_contexts = config_to_docker(
|
||||
|
||||
@@ -62,7 +62,7 @@ MAXFAIL_ARGS := $(if $(MAXFAIL),--maxfail $(MAXFAIL),)
|
||||
XDIST_ARGS := $(if $(WORKERS),-x $(XDIST_ARGS),)
|
||||
|
||||
test_watch:
|
||||
make start-postgres && poetry run ptw . -- --ff -vv $(XDIST_ARGS) $(MAXFAIL_ARGS) --snapshot-update --tb short $(TEST); \
|
||||
make start-postgres && poetry run ptw . -- --ff -vv $(XDIST_ARGS) $(MAXFAIL_ARGS) $(TEST); \
|
||||
EXIT_CODE=$$?; \
|
||||
make stop-postgres; \
|
||||
exit $$EXIT_CODE
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiosqlite"
|
||||
@@ -1348,7 +1348,7 @@ typing-extensions = ">=4.7"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-checkpoint"
|
||||
version = "2.0.21"
|
||||
version = "2.0.24"
|
||||
description = "Library with base interfaces for LangGraph checkpoint savers."
|
||||
optional = false
|
||||
python-versions = "^3.9.0,<4.0"
|
||||
@@ -1404,7 +1404,7 @@ url = "../checkpoint-sqlite"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-prebuilt"
|
||||
version = "0.1.4"
|
||||
version = "0.1.8"
|
||||
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
|
||||
optional = false
|
||||
python-versions = "^3.9.0,<4.0"
|
||||
@@ -1422,7 +1422,7 @@ url = "../prebuilt"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-sdk"
|
||||
version = "0.1.58"
|
||||
version = "0.1.61"
|
||||
description = "SDK for interacting with LangGraph API"
|
||||
optional = false
|
||||
python-versions = "^3.9.0,<4.0"
|
||||
@@ -3646,4 +3646,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.9.0,<4.0"
|
||||
content-hash = "b03760d1062e13e4df0b4052a194bedb8abb3baf80da0c036b39d2ebe26b0b5c"
|
||||
content-hash = "1a6454eb63ce88ddfddd0d49530a6fef0f02ac6b0b52be5d6710af8e47b1de24"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph"
|
||||
version = "0.3.30"
|
||||
version = "0.3.31"
|
||||
description = "Building stateful, multi-actor applications with LLMs"
|
||||
authors = []
|
||||
license = "MIT"
|
||||
@@ -12,7 +12,7 @@ python = ">=3.9.0,<4.0"
|
||||
langchain-core = ">=0.1,<0.4"
|
||||
langgraph-checkpoint = "^2.0.10"
|
||||
langgraph-sdk = "^0.1.42"
|
||||
langgraph-prebuilt = ">=0.1.1,<0.2"
|
||||
langgraph-prebuilt = ">=0.1.8,<0.2"
|
||||
xxhash = "^3.5.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
|
||||
@@ -4601,7 +4601,7 @@ def test_checkpoint_metadata() -> None:
|
||||
assert chkpnt_tuple.metadata["test_config_4"] == "bar"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC)
|
||||
@pytest.mark.parametrize("checkpointer_name", REGULAR_CHECKPOINTERS_SYNC)
|
||||
def test_remove_message_via_state_update(
|
||||
request: pytest.FixtureRequest, checkpointer_name: str
|
||||
) -> None:
|
||||
@@ -4631,6 +4631,12 @@ def test_remove_message_via_state_update(
|
||||
assert len(updated_state.values) == 1
|
||||
assert updated_state.values[-1].content == "Hi"
|
||||
|
||||
app.checkpointer.delete_thread(config["configurable"]["thread_id"])
|
||||
|
||||
# Verify that the message was removed from the checkpointer
|
||||
assert app.checkpointer.get_tuple(config) is None
|
||||
assert [*app.get_state_history(config)] == []
|
||||
|
||||
|
||||
def test_remove_message_from_node():
|
||||
from langchain_core.messages import AIMessage, HumanMessage, RemoveMessage
|
||||
@@ -6034,7 +6040,7 @@ def test_concurrent_execution_thread_safety():
|
||||
assert result["counter"] == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC)
|
||||
@pytest.mark.parametrize("checkpointer_name", REGULAR_CHECKPOINTERS_SYNC)
|
||||
def test_checkpoint_recovery(request: pytest.FixtureRequest, checkpointer_name: str):
|
||||
"""Test recovery from checkpoints after failures."""
|
||||
checkpointer = request.getfixturevalue(f"checkpointer_{checkpointer_name}")
|
||||
@@ -6087,6 +6093,11 @@ def test_checkpoint_recovery(request: pytest.FixtureRequest, checkpointer_name:
|
||||
failed_checkpoint = next(c for c in history if c.tasks and c.tasks[0].error)
|
||||
assert "RuntimeError('Simulated failure')" in failed_checkpoint.tasks[0].error
|
||||
|
||||
# Verify delete leaves it empty
|
||||
graph.checkpointer.delete_thread(config["configurable"]["thread_id"])
|
||||
assert graph.checkpointer.get_tuple(config) is None
|
||||
assert [*graph.get_state_history(config)] == []
|
||||
|
||||
|
||||
def test_multiple_updates_root() -> None:
|
||||
def node_a(state):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@langchain/langgraph-sdk",
|
||||
"version": "0.0.67",
|
||||
"version": "0.0.68",
|
||||
"description": "Client library for interacting with the LangGraph API",
|
||||
"type": "module",
|
||||
"packageManager": "yarn@1.22.19",
|
||||
|
||||
@@ -175,7 +175,11 @@ export function LoadExternalComponent({
|
||||
}, [uiClient, uiNamespace, message.name, shadowRootId, hasClientComponent]);
|
||||
|
||||
if (hasClientComponent) {
|
||||
return React.createElement(clientComponent, message.props);
|
||||
return (
|
||||
<UseStreamContext.Provider value={{ stream, meta }}>
|
||||
{React.createElement(clientComponent, message.props)}
|
||||
</UseStreamContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -125,7 +125,7 @@ class Checkpoint(TypedDict):
|
||||
thread_id: str
|
||||
"""Unique identifier for the thread associated with this checkpoint."""
|
||||
checkpoint_ns: str
|
||||
"""Namespace for the checkpoint, used for organization and retrieval."""
|
||||
"""Namespace for the checkpoint; used internally to manage subgraph state."""
|
||||
checkpoint_id: Optional[str]
|
||||
"""Optional unique identifier for the checkpoint itself."""
|
||||
checkpoint_map: Optional[dict[str, Any]]
|
||||
|
||||