Compare commits

..
Author SHA1 Message Date
William Fu-Hinthorn e38bae6b3a mon9o 2025-10-31 13:58:17 -07:00
William Fu-Hinthorn c31ee23e2e exmpale 2025-10-31 13:49:04 -07:00
Kathryn MayandGitHub ae525fb74f docs: Update hosting page name redirect to platform setup (#6371) 2025-10-31 14:12:59 -04:00
Mason DaughertyandGitHub a6dde39be7 chore: style fixes for refs (#6365) 2025-10-30 17:59:40 -04:00
10 changed files with 163 additions and 244 deletions
+1 -1
View File
@@ -180,7 +180,7 @@ REDIRECT_MAP = {
"cloud/concepts/data_storage_and_privacy.md": "https://docs.langchain.com/langsmith/data-storage-and-privacy",
"cloud/deployment/semantic_search.md": "https://docs.langchain.com/langsmith/semantic-search",
"how-tos/ttl/configure_ttl.md": "https://docs.langchain.com/langsmith/configure-ttl",
"concepts/deployment_options.md": "https://docs.langchain.com/langsmith/hosting",
"concepts/deployment_options.md": "https://docs.langchain.com/langsmith/platform-setup",
"cloud/quick_start.md": "https://docs.langchain.com/langsmith/deployment-quickstart",
"cloud/deployment/setup.md": "https://docs.langchain.com/langsmith/setup-app-requirements-txt",
"cloud/deployment/setup_pyproject.md": "https://docs.langchain.com/langsmith/setup-pyproject",
@@ -5,5 +5,5 @@ description = "Test for prerelease stuff"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"langgraph==0.6.0"
"langgraph==0.6.11"
]
@@ -8,7 +8,7 @@ authors = [
license = { text = "MIT" }
requires-python = ">=3.11,<4.0"
dependencies = [
"langgraph>=0.6.0,<0.7.0",
"langgraph>=0.6.0,<2",
"langchain-core>=0.2.14",
]
+1 -2
View File
@@ -81,9 +81,8 @@ def push_ui_message(
metadata: Optional additional metadata about the UI message.
message: Optional message object to associate with the UI message.
state_key: Key in the graph state where the UI messages are stored.
Defaults to "ui".
merge: Whether to merge props with existing UI message (True) or replace
them (False). Defaults to False.
them (False).
Returns:
The created UI message.
+64 -54
View File
@@ -55,6 +55,7 @@ __all__ = (
"Command",
"Durability",
"interrupt",
"Overwrite",
)
Durability = Literal["sync", "async", "exit"]
@@ -283,26 +284,32 @@ class Send:
node (str): The name of the target node to send the message to.
arg (Any): The state or message to send to the target node.
Examples:
>>> from typing import Annotated
>>> import operator
>>> class OverallState(TypedDict):
... subjects: list[str]
... jokes: Annotated[list[str], operator.add]
>>> from langgraph.types import Send
>>> from langgraph.graph import END, START
>>> def continue_to_jokes(state: OverallState):
... return [Send("generate_joke", {"subject": s}) for s in state["subjects"]]
>>> from langgraph.graph import StateGraph
>>> builder = StateGraph(OverallState)
>>> builder.add_node("generate_joke", lambda state: {"jokes": [f"Joke about {state['subject']}"]})
>>> builder.add_conditional_edges(START, continue_to_jokes)
>>> builder.add_edge("generate_joke", END)
>>> graph = builder.compile()
>>>
>>> # Invoking with two subjects results in a generated joke for each
>>> graph.invoke({"subjects": ["cats", "dogs"]})
{'subjects': ['cats', 'dogs'], 'jokes': ['Joke about cats', 'Joke about dogs']}
!!! example
```python
from typing import Annotated
from langgraph.types import Send
from langgraph.graph import END, START
from langgraph.graph import StateGraph
import operator
class OverallState(TypedDict):
subjects: list[str]
jokes: Annotated[list[str], operator.add]
def continue_to_jokes(state: OverallState):
return [Send("generate_joke", {"subject": s}) for s in state["subjects"]]
builder = StateGraph(OverallState)
builder.add_node("generate_joke", lambda state: {"jokes": [f"Joke about {state['subject']}"]})
builder.add_conditional_edges(START, continue_to_jokes)
builder.add_edge("generate_joke", END)
graph = builder.compile()
# Invoking with two subjects results in a generated joke for each
graph.invoke({"subjects": ["cats", "dogs"]})
# {'subjects': ['cats', 'dogs'], 'jokes': ['Joke about cats', 'Joke about dogs']}
```
"""
__slots__ = ("node", "arg")
@@ -342,10 +349,8 @@ N = TypeVar("N", bound=Hashable)
class Command(Generic[N], ToolOutputMixin):
"""One or more commands to update the graph's state and send messages to nodes.
!!! version-added "Added in version 0.2.24"
Args:
graph: graph to send the command to. Supported values are:
graph: Graph to send the command to. Supported values are:
- `None`: the current graph
- `Command.PARENT`: closest parent graph
@@ -415,7 +420,8 @@ def interrupt(value: Any) -> Any:
To use an `interrupt`, you must enable a checkpointer, as the feature relies
on persisting the graph state.
Example:
!!! example
```python
import uuid
from typing import Optional
@@ -520,38 +526,42 @@ def interrupt(value: Any) -> Any:
@dataclass(slots=True)
class Overwrite:
"""Bypass a reducer and write the wrapped value directly to a BinaryOperatorAggregate channel.
"""Bypass a reducer and write the wrapped value directly to a `BinaryOperatorAggregate` channel.
Receiving multiple Overwrite values for the same channel in a single super-step will raise an InvalidUpdateError.
Receiving multiple `Overwrite` values for the same channel in a single super-step
will raise an `InvalidUpdateError`.
Example:
>>> from typing import Annotated
>>> import operator
>>> from langgraph.graph import StateGraph
>>> from langgraph.types import Overwrite
>>>
>>> class State(TypedDict):
... messages: Annotated[list, operator.add]
>>>
>>> def node_a(state: TypedDict):
... # Normal update: uses the reducer (operator.add)
... return {"messages": ["a"]}
>>>
>>> def node_b(state: State):
... # Overwrite: bypasses the reducer and replaces the entire value
... return {"messages": Overwrite(value=["b"])}
>>>
>>> builder = StateGraph(State)
>>> builder.add_node("node_a", node_a)
>>> builder.add_node("node_b", node_b)
>>> builder.set_entry_point("node_a")
>>> builder.add_edge("node_a", "node_b")
>>> graph = builder.compile()
>>>
>>> # Without Overwrite in node_b, messages would be ["START", "a", "b"]
>>> # With Overwrite, messages is just ["b"]
>>> result = graph.invoke({"messages": ["START"]})
>>> assert result == {"messages": ["b"]}
!!! example
```python
from typing import Annotated
import operator
from langgraph.graph import StateGraph
from langgraph.types import Overwrite
class State(TypedDict):
messages: Annotated[list, operator.add]
def node_a(state: TypedDict):
# Normal update: uses the reducer (operator.add)
return {"messages": ["a"]}
def node_b(state: State):
# Overwrite: bypasses the reducer and replaces the entire value
return {"messages": Overwrite(value=["b"])}
builder = StateGraph(State)
builder.add_node("node_a", node_a)
builder.add_node("node_b", node_b)
builder.set_entry_point("node_a")
builder.add_edge("node_a", "node_b")
graph = builder.compile()
# Without Overwrite in node_b, messages would be ["START", "a", "b"]
# With Overwrite, messages is just ["b"]
result = graph.invoke({"messages": ["START"]})
assert result == {"messages": ["b"]}
```
"""
value: Any
@@ -309,37 +309,40 @@ def create_react_agent(
model: The language model for the agent. Supports static and dynamic
model selection.
- **Static model**: A chat model instance (e.g., `ChatOpenAI()`) or
string identifier (e.g., `"openai:gpt-4"`)
- **Static model**: A chat model instance (e.g.,
[`ChatOpenAI`][langchain_openai.ChatOpenAI]) or string identifier (e.g.,
`"openai:gpt-4"`)
- **Dynamic model**: A callable with signature
`(state, runtime) -> BaseChatModel` that returns different models
based on runtime context
If the model has tools bound via `.bind_tools()` or other configurations,
the return type should be a Runnable[LanguageModelInput, BaseMessage]
Coroutines are also supported, allowing for asynchronous model selection.
`(state, runtime) -> BaseChatModel` that returns different models
based on runtime context
If the model has tools bound via `bind_tools` or other configurations,
the return type should be a `Runnable[LanguageModelInput, BaseMessage]`
Coroutines are also supported, allowing for asynchronous model selection.
Dynamic functions receive graph state and runtime, enabling
context-dependent model selection. Must return a `BaseChatModel`
instance. For tool calling, bind tools using `.bind_tools()`.
Bound tools must be a subset of the `tools` parameter.
Dynamic model example:
```python
from dataclasses import dataclass
!!! example "Dynamic model"
@dataclass
class ModelContext:
model_name: str = "gpt-3.5-turbo"
```python
from dataclasses import dataclass
# Instantiate models globally
gpt4_model = ChatOpenAI(model="gpt-4")
gpt35_model = ChatOpenAI(model="gpt-3.5-turbo")
@dataclass
class ModelContext:
model_name: str = "gpt-3.5-turbo"
def select_model(state: AgentState, runtime: Runtime[ModelContext]) -> ChatOpenAI:
model_name = runtime.context.model_name
model = gpt4_model if model_name == "gpt-4" else gpt35_model
return model.bind_tools(tools)
```
# Instantiate models globally
gpt4_model = ChatOpenAI(model="gpt-4")
gpt35_model = ChatOpenAI(model="gpt-3.5-turbo")
def select_model(state: AgentState, runtime: Runtime[ModelContext]) -> ChatOpenAI:
model_name = runtime.context.model_name
model = gpt4_model if model_name == "gpt-4" else gpt35_model
return model.bind_tools(tools)
```
!!! note "Dynamic Model Requirements"
@@ -351,23 +354,26 @@ def create_react_agent(
If an empty list is provided, the agent will consist of a single LLM node without tool calling.
prompt: An optional prompt for the LLM. Can take a few different forms:
- str: This is converted to a SystemMessage and added to the beginning of the list of messages in state["messages"].
- SystemMessage: this is added to the beginning of the list of messages in state["messages"].
- Callable: This function should take in full graph state and the output is then passed to the language model.
- Runnable: This runnable should take in full graph state and the output is then passed to the language model.
- `str`: This is converted to a `SystemMessage` and added to the beginning of the list of messages in `state["messages"]`.
- `SystemMessage`: this is added to the beginning of the list of messages in `state["messages"]`.
- `Callable`: This function should take in full graph state and the output is then passed to the language model.
- `Runnable`: This runnable should take in full graph state and the output is then passed to the language model.
response_format: An optional schema for the final agent output.
If provided, output will be formatted to match the given schema and returned in the 'structured_response' state key.
If not provided, `structured_response` will not be present in the output state.
Can be passed in as:
- an OpenAI function/tool schema,
- a JSON Schema,
- a TypedDict class,
- or a Pydantic class.
- a tuple (prompt, schema), where schema is one of the above.
The prompt will be used together with the model that is being used to generate the structured response.
- An OpenAI function/tool schema,
- A JSON Schema,
- A TypedDict class,
- A Pydantic class.
- A tuple `(prompt, schema)`, where schema is one of the above.
The prompt will be used together with the model that is being used to
generate the structured response.
!!! Important
`response_format` requires the model to support `.with_structured_output`
@@ -428,13 +434,16 @@ def create_react_agent(
store: An optional store object. This is used for persisting data
across multiple threads (e.g., multiple conversations / users).
interrupt_before: An optional list of node names to interrupt before.
Should be one of the following: "agent", "tools".
Should be one of the following: `"agent"`, `"tools"`.
This is useful if you want to add a user confirmation or other interrupt before taking an action.
interrupt_after: An optional list of node names to interrupt after.
Should be one of the following: "agent", "tools".
Should be one of the following: `"agent"`, `"tools"`.
This is useful if you want to return directly or run additional processing on an output.
debug: A flag indicating whether to enable debug mode.
version: Determines the version of the graph to create.
Can be one of:
- `"v1"`: The tool node processes a single message. All tool
@@ -443,7 +452,7 @@ def create_react_agent(
Tool calls are distributed across multiple instances of the tool
node using the [Send](https://langchain-ai.github.io/langgraph/concepts/low_level/#send)
API.
name: An optional name for the CompiledStateGraph.
name: An optional name for the `CompiledStateGraph`.
This name will be automatically used when adding ReAct agent graph to another graph as a subgraph node -
particularly useful for building multi-agent systems.
@@ -453,14 +462,14 @@ def create_react_agent(
Returns:
A compiled LangChain runnable that can be used for chat interactions.
A compiled LangChain `Runnable` that can be used for chat interactions.
The "agent" node calls the language model with the messages list (after applying the prompt).
If the resulting AIMessage contains `tool_calls`, the graph will then call the ["tools"][langgraph.prebuilt.tool_node.ToolNode].
The "tools" node executes the tools (1 tool per `tool_call`) and adds the responses to the messages list
as `ToolMessage` objects. The agent node then calls the language model again.
The process repeats until no more `tool_calls` are present in the response.
The agent then returns the full list of messages as a dictionary containing the key "messages".
The agent then returns the full list of messages as a dictionary containing the key `'messages'`.
``` mermaid
sequenceDiagram
+10 -8
View File
@@ -36,7 +36,7 @@ class ActionRequest(TypedDict):
Contains the action type and any associated arguments needed for the action.
Attributes:
action: The type or name of action being requested (e.g., "Approve XYZ action")
action: The type or name of action being requested (e.g., `"Approve XYZ action"`)
args: Key-value pairs of arguments needed for the action
"""
@@ -89,14 +89,16 @@ class HumanResponse(TypedDict):
Attributes:
type: The type of response:
- "accept": Approves the current state without changes
- "ignore": Skips/ignores the current step
- "response": Provides text feedback or instructions
- "edit": Modifies the current state/content
- `'accept'`: Approves the current state without changes
- `'ignore'`: Skips/ignores the current step
- `'response'`: Provides text feedback or instructions
- `'edit'`: Modifies the current state/content
args: The response payload:
- None: For ignore/accept actions
- str: For text responses
- ActionRequest: For edit actions with updated content
- `None`: For ignore/accept actions
- `str`: For text responses
- `ActionRequest`: For edit actions with updated content
"""
type: Literal["accept", "ignore", "response", "edit"]
+36 -32
View File
@@ -6,6 +6,7 @@ Tools are functions that models can call to interact with external systems,
APIs, databases, or perform computations.
The module implements design patterns for:
- Parallel execution of multiple tool calls for efficiency
- Robust error handling with customizable error messages
- State injection for tools that need access to graph state
@@ -13,11 +14,13 @@ The module implements design patterns for:
- Command-based state updates for advanced control flow
Key Components:
`ToolNode`: Main class for executing tools in LangGraph workflows
`InjectedState`: Annotation for injecting graph state into tools
`InjectedStore`: Annotation for injecting persistent store into tools
`ToolRuntime`: Runtime information for tools, bundling together state, context, config, stream_writer, tool_call_id, and store
`tools_condition`: Utility function for conditional routing based on tool calls
- `ToolNode`: Main class for executing tools in LangGraph workflows
- `InjectedState`: Annotation for injecting graph state into tools
- `InjectedStore`: Annotation for injecting persistent store into tools
- `ToolRuntime`: Runtime information for tools, bundling together `state`, `context`,
`config`, `stream_writer`, `tool_call_id`, and `store`
- `tools_condition`: Utility function for conditional routing based on tool calls
Typical Usage:
```python
@@ -552,44 +555,52 @@ class ToolNode(RunnableCallable):
Output format depends on input type and tool behavior:
**For Regular tools**:
- Dict input → `{"messages": [ToolMessage(...)]}`
- List input → `[ToolMessage(...)]`
**For Command tools**:
- Returns `[Command(...)]` or mixed list with regular tool outputs
- Commands can update state, trigger navigation, or send messages
- `Command` can update state, trigger navigation, or send messages
Args:
tools: A sequence of tools that can be invoked by this node. Supports:
tools: A sequence of tools that can be invoked by this node.
Supports:
- **BaseTool instances**: Tools with schemas and metadata
- **Plain functions**: Automatically converted to tools with inferred schemas
name: The name identifier for this node in the graph. Used for debugging
and visualization. Defaults to "tools".
and visualization.
tags: Optional metadata tags to associate with the node for filtering
and organization. Defaults to `None`.
and organization.
handle_tool_errors: Configuration for error handling during tool execution.
Supports multiple strategies:
- **True**: Catch all errors and return a ToolMessage with the default
- `True`: Catch all errors and return a `ToolMessage` with the default
error template containing the exception details.
- **str**: Catch all errors and return a ToolMessage with this custom
- `str`: Catch all errors and return a `ToolMessage` with this custom
error message string.
- **type[Exception]**: Only catch exceptions with the specified type and
- `type[Exception]`: Only catch exceptions with the specified type and
return the default error message for it.
- **tuple[type[Exception], ...]**: Only catch exceptions with the specified
- `tuple[type[Exception], ...]`: Only catch exceptions with the specified
types and return default error messages for them.
- **Callable[..., str]**: Catch exceptions matching the callable's signature
- `Callable[..., str]`: Catch exceptions matching the callable's signature
and return the string result of calling it with the exception.
- **False**: Disable error handling entirely, allowing exceptions to
- `False`: Disable error handling entirely, allowing exceptions to
propagate.
Defaults to a callable that:
- catches tool invocation errors (due to invalid arguments provided by the model) and returns a descriptive error message
- ignores tool execution errors (they will be re-raised)
- Catches tool invocation errors (due to invalid arguments provided by the
model) and returns a descriptive error message
- Ignores tool execution errors (they will be re-raised)
messages_key: The key in the state dictionary that contains the message list.
This same key will be used for the output `ToolMessage` objects.
Defaults to "messages".
Allows custom state schemas with different message field names.
Examples:
@@ -891,9 +902,6 @@ class ToolNode(RunnableCallable):
return self._validate_tool_command(response, request.tool_call, input_type)
if isinstance(response, ToolMessage):
response.content = cast("str | list", msg_content_output(response.content))
# Enrich ToolMessage with name if not set (e.g., from fallback handlers)
if response.name is None:
response.name = call["name"]
return response
msg = f"Tool {call['name']} returned unexpected type: {type(response)}"
@@ -1051,9 +1059,6 @@ class ToolNode(RunnableCallable):
return self._validate_tool_command(response, request.tool_call, input_type)
if isinstance(response, ToolMessage):
response.content = cast("str | list", msg_content_output(response.content))
# Enrich ToolMessage with name if not set (e.g., from fallback handlers)
if response.name is None:
response.name = call["name"]
return response
msg = f"Tool {call['name']} returned unexpected type: {type(response)}"
@@ -1399,7 +1404,7 @@ def tools_condition(
"""Conditional routing function for tool-calling workflows.
This utility function implements the standard conditional logic for ReAct-style
agents: if the last AI message contains tool calls, route to the tool execution
agents: if the last `AIMessage` contains tool calls, route to the tool execution
node; otherwise, end the workflow. This pattern is fundamental to most tool-calling
agent architectures.
@@ -1408,16 +1413,15 @@ def tools_condition(
Args:
state: The current graph state to examine for tool calls. Supported formats:
- Dictionary containing a messages key (for StateGraph)
- BaseModel instance with a messages attribute
- Dictionary containing a messages key (for `StateGraph`)
- `BaseModel` instance with a messages attribute
messages_key: The key or attribute name containing the message list in the state.
This allows customization for graphs using different state schemas.
Defaults to "messages".
Returns:
Either "tools" if tool calls are present in the last AI message, or "__end__"
to terminate the workflow. These are the standard routing destinations for
tool-calling conditional edges.
Either `'tools'` if tool calls are present in the last `AIMessage`, or `'__end__'`
to terminate the workflow. These are the standard routing destinations for
tool-calling conditional edges.
Raises:
ValueError: If no messages can be found in the provided state format.
@@ -1614,7 +1618,7 @@ class InjectedStore(InjectedToolArg):
This annotation enables tools to access LangGraph's persistent storage system
without exposing storage details to the language model. Tools annotated with
InjectedStore receive the store instance automatically during execution while
`InjectedStore` receive the store instance automatically during execution while
remaining invisible to the model's tool-calling interface.
The store provides persistent, cross-session data storage that tools can use
@@ -45,9 +45,9 @@ def _default_format_error(
category=LangGraphDeprecatedSinceV10,
)
class ValidationNode(RunnableCallable):
"""A node that validates all tools requests from the last AIMessage.
"""A node that validates all tools requests from the last `AIMessage`.
It can be used either in StateGraph with a "messages" key.
It can be used either in `StateGraph` with a `'messages'` key.
!!! note
@@ -57,7 +57,8 @@ class ValidationNode(RunnableCallable):
messages and tool IDs (for use in multi-turn conversations).
Returns:
(Union[Dict[str, List[ToolMessage]], Sequence[ToolMessage]]): A list of ToolMessages with the validated content or error messages.
(Union[Dict[str, List[ToolMessage]], Sequence[ToolMessage]]): A list of
`ToolMessage` objects with the validated content or error messages.
Example:
```python title="Example usage for re-prompting the model to generate a valid response:"
-106
View File
@@ -1,106 +0,0 @@
"""Test to reproduce the tool error fallback issue.
When using ToolNode(...).with_fallbacks(...) with an error handler that returns
ToolMessage objects, the messages are losing:
1. The `name` field (becomes `None` instead of the tool name)
2. The `status` field (becomes `success` instead of `error`)
"""
from unittest.mock import Mock
from langchain_core.messages import AIMessage, ToolMessage
from langchain_core.runnables import RunnableLambda
from langchain_core.runnables.config import RunnableConfig
from langchain_core.tools import tool
from langgraph.prebuilt import ToolNode
def _create_mock_runtime():
"""Create a mock Runtime object for testing ToolNode outside of graph context."""
mock_runtime = Mock()
mock_runtime.store = None
mock_runtime.context = None
mock_runtime.stream_writer = lambda *args, **kwargs: None
return mock_runtime
def _create_config_with_runtime() -> RunnableConfig:
"""Create a RunnableConfig with mock Runtime for testing ToolNode."""
return {"configurable": {"__pregel_runtime": _create_mock_runtime()}}
@tool
def failing_tool(x: int) -> str:
"""A tool that always fails."""
raise RuntimeError("This tool always fails!")
def handle_tool_error(state) -> dict:
"""Error handler that returns ToolMessages."""
print(f"handle_tool_error called with state: {state}")
print(f"State type: {type(state)}")
print(f"State keys: {state.keys() if isinstance(state, dict) else 'N/A'}")
error = state.get("error")
print(f"Error: {error}")
tool_calls = state["messages"][-1].tool_calls
print(f"Tool calls: {tool_calls}")
return {
"messages": [
ToolMessage(
content=f"Error: {repr(error)}\n please fix your mistakes.",
tool_call_id=tc["id"],
)
for tc in tool_calls
]
}
fallback_runnable = RunnableLambda(handle_tool_error)
def test_tool_error_with_fallbacks():
"""Test that ToolMessages from fallback handlers preserve name and status."""
# Create a ToolNode with a fallback
tool_node = ToolNode([failing_tool], handle_tool_errors=True).with_fallbacks(
[fallback_runnable],
)
# Create an AI message with a tool call
messages = [
AIMessage(
content="",
tool_calls=[
{
"name": "failing_tool",
"args": {"x": 1},
"id": "call_123",
"type": "tool_call",
}
],
)
]
# Invoke the tool node
result = tool_node.invoke({"messages": messages}, config=_create_config_with_runtime())
print("Result:", result)
print("Result type:", type(result))
print("Result keys:", result.keys() if isinstance(result, dict) else "N/A")
print("\nTool messages:")
for msg in result["messages"]:
if isinstance(msg, ToolMessage):
print(f" - content: {msg.content[:50]}...")
print(f" name: {msg.name}")
print(f" tool_call_id: {msg.tool_call_id}")
print(f" status: {msg.status}")
print()
# Check expectations
assert msg.name == "failing_tool", f"Expected name='failing_tool', got {msg.name}"
assert msg.status == "error", f"Expected status='error', got {msg.status}"
print("✓ Test passed!")
if __name__ == "__main__":
test_tool_error_with_fallbacks()