Compare commits

..
Author SHA1 Message Date
Hunter Lovell f0d537ba05 wip: put up translated doc 2025-07-16 15:43:01 -07:00
+535 -3
View File
@@ -9,6 +9,7 @@ search:
At its core, LangGraph models agent workflows as graphs. You define the behavior of your agents using three key components:
:::python
1. [`State`](#state): A shared data structure that represents the current snapshot of your application. It can be any Python type, but is typically a `TypedDict` or Pydantic `BaseModel`.
2. [`Nodes`](#nodes): Python functions that encode the logic of your agents. They receive the current `State` as input, perform some computation or side-effect, and return an updated `State`.
@@ -16,6 +17,17 @@ At its core, LangGraph models agent workflows as graphs. You define the behavior
3. [`Edges`](#edges): Python functions that determine which `Node` to execute next based on the current `State`. They can be conditional branches or fixed transitions.
By composing `Nodes` and `Edges`, you can create complex, looping workflows that evolve the `State` over time. The real power, though, comes from how LangGraph manages that `State`. To emphasize: `Nodes` and `Edges` are nothing more than Python functions - they can contain an LLM or just good ol' Python code.
:::
:::js
1. [`State`](#state): A shared data structure that represents the current snapshot of your application. It can be any TypeScript type, but is typically a Zod schema or TypeScript interface.
2. [`Nodes`](#nodes): TypeScript functions that encode the logic of your agents. They receive the current `State` as input, perform some computation or side-effect, and return an updated `State`.
3. [`Edges`](#edges): TypeScript functions that determine which `Node` to execute next based on the current `State`. They can be conditional branches or fixed transitions.
By composing `Nodes` and `Edges`, you can create complex, looping workflows that evolve the `State` over time. The real power, though, comes from how LangGraph manages that `State`. To emphasize: `Nodes` and `Edges` are nothing more than TypeScript functions - they can contain an LLM or just good ol' TypeScript code.
:::
In short: _nodes do the work, edges tell what to do next_.
@@ -31,21 +43,51 @@ The `StateGraph` class is the main graph class to use. This is parameterized by
To build your graph, you first define the [state](#state), you then add [nodes](#nodes) and [edges](#edges), and then you compile it. What exactly is compiling your graph and why is it needed?
Compiling is a pretty simple step. It provides a few basic checks on the structure of your graph (no orphaned nodes, etc). It is also where you can specify runtime args like [checkpointers](./persistence.md) and breakpoints. You compile your graph by just calling the `.compile` method:
Compiling is a pretty simple step. It provides a few basic checks on the structure of your graph (no orphaned nodes, etc). It is also where you can specify runtime args like [checkpointers](./persistence.md) and breakpoints.
:::python
You compile your graph by just calling the `.compile` method:
```python
graph = graph_builder.compile(...)
```
:::
:::js
You compile your graph by just calling the `.compile()` method:
```typescript
const graph = new StateGraph(State)
.addNode("node1", node1)
.addNode("node2", node2)
.addEdge(START, "node1")
.addEdge("node1", "node2")
.addEdge("node2", END)
.compile();
```
:::
You **MUST** compile your graph before you can use it.
## State
:::python
The first thing you do when you define a graph is define the `State` of the graph. The `State` consists of the [schema of the graph](#schema) as well as [`reducer` functions](#reducers) which specify how to apply updates to the state. The schema of the `State` will be the input schema to all `Nodes` and `Edges` in the graph, and can be either a `TypedDict` or a `Pydantic` model. All `Nodes` will emit updates to the `State` which are then applied using the specified `reducer` function.
:::
:::js
The first thing you do when you define a graph is define the `State` of the graph. The `State` consists of the [schema of the graph](#schema) as well as [`reducer` functions](#reducers) which specify how to apply updates to the state. The schema of the `State` will be the input schema to all `Nodes` and `Edges` in the graph, and can be either a Zod schema or a TypeScript interface. All `Nodes` will emit updates to the `State` which are then applied using the specified `reducer` function.
:::
### Schema
:::python
The main documented way to specify the schema of a graph is by using `TypedDict`. However, we also support [using a Pydantic BaseModel](../how-tos/graph-api.ipynb#use-pydantic-models-for-graph-state) as your graph state to add **default values** and additional data validation.
:::
:::js
The main documented way to specify the schema of a graph is by using Zod schemas. However, we also support [using TypeScript interfaces](../how-tos/graph-api.ipynb#use-typescript-interfaces-for-graph-state) as your graph state to add **default values** and additional data validation.
:::
By default, the graph will have the same input and output schemas. If you want to change this, you can also specify explicit input and output schemas directly. This is useful when you have a lot of keys, and some are explicitly for input and others for output. See the [guide here](../how-tos/graph-api.ipynb#define-input-and-output-schemas) for how to use.
@@ -62,6 +104,7 @@ It is also possible to define explicit input and output schemas for a graph. In
Let's look at an example:
:::python
```python
class InputState(TypedDict):
user_input: str
@@ -102,12 +145,77 @@ graph = builder.compile()
graph.invoke({"user_input":"My"})
{'graph_output': 'My name is Lance'}
```
:::
:::js
```typescript
import { z } from "zod";
const InputState = z.object({
userInput: z.string(),
});
const OutputState = z.object({
graphOutput: z.string(),
});
const OverallState = z.object({
foo: z.string(),
userInput: z.string(),
graphOutput: z.string(),
});
const PrivateState = z.object({
bar: z.string(),
});
const node1 = (state: z.infer<typeof InputState>): Partial<z.infer<typeof OverallState>> => {
// Write to OverallState
return { foo: state.userInput + " name" };
};
const node2 = (state: z.infer<typeof OverallState>): Partial<z.infer<typeof PrivateState>> => {
// Read from OverallState, write to PrivateState
return { bar: state.foo + " is" };
};
const node3 = (state: z.infer<typeof PrivateState>): Partial<z.infer<typeof OutputState>> => {
// Read from PrivateState, write to OutputState
return { graphOutput: state.bar + " Lance" };
};
const graph = new StateGraph({
state: OverallState,
input: InputState,
output: OutputState,
})
.addNode("node1", node1)
.addNode("node2", node2)
.addNode("node3", node3)
.addEdge(START, "node1")
.addEdge("node1", "node2")
.addEdge("node2", "node3")
.addEdge("node3", END)
.compile();
await graph.invoke({ userInput: "My" });
// { graphOutput: 'My name is Lance' }
```
:::
There are two subtle and important points to note here:
:::python
1. We pass `state: InputState` as the input schema to `node_1`. But, we write out to `foo`, a channel in `OverallState`. How can we write out to a state channel that is not included in the input schema? This is because a node _can write to any state channel in the graph state._ The graph state is the union of the state channels defined at initialization, which includes `OverallState` and the filters `InputState` and `OutputState`.
2. We initialize the graph with `StateGraph(OverallState,input_schema=InputState,output_schema=OutputState)`. So, how can we write to `PrivateState` in `node_2`? How does the graph gain access to this schema if it was not passed in the `StateGraph` initialization? We can do this because _nodes can also declare additional state channels_ as long as the state schema definition exists. In this case, the `PrivateState` schema is defined, so we can add `bar` as a new state channel in the graph and write to it.
:::
:::js
1. We pass `state: z.infer<typeof InputState>` as the input schema to `node1`. But, we write out to `foo`, a channel in `OverallState`. How can we write out to a state channel that is not included in the input schema? This is because a node _can write to any state channel in the graph state._ The graph state is the union of the state channels defined at initialization, which includes `OverallState` and the filters `InputState` and `OutputState`.
2. We initialize the graph with `new StateGraph({ state: OverallState, input: InputState, output: OutputState })`. So, how can we write to `PrivateState` in `node2`? How does the graph gain access to this schema if it was not passed in the `StateGraph` initialization? We can do this because _nodes can also declare additional state channels_ as long as the state schema definition exists. In this case, the `PrivateState` schema is defined, so we can add `bar` as a new state channel in the graph and write to it.
:::
### Reducers
@@ -119,6 +227,7 @@ These two examples show how to use the default reducer:
**Example A:**
:::python
```python
from typing_extensions import TypedDict
@@ -128,9 +237,24 @@ class State(TypedDict):
```
In this example, no reducer functions are specified for any key. Let's assume the input to the graph is `{"foo": 1, "bar": ["hi"]}`. Let's then assume the first `Node` returns `{"foo": 2}`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{"foo": 2, "bar": ["hi"]}`. If the second node returns `{"bar": ["bye"]}` then the `State` would then be `{"foo": 2, "bar": ["bye"]}`
:::
:::js
```typescript
import { z } from "zod";
const State = z.object({
foo: z.number(),
bar: z.array(z.string()),
});
```
In this example, no reducer functions are specified for any key. Let's assume the input to the graph is `{ foo: 1, bar: ["hi"] }`. Let's then assume the first `Node` returns `{ foo: 2 }`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{ foo: 2, bar: ["hi"] }`. If the second node returns `{ bar: ["bye"] }` then the `State` would then be `{ foo: 2, bar: ["bye"] }`
:::
**Example B:**
:::python
```python
from typing import Annotated
from typing_extensions import TypedDict
@@ -142,21 +266,51 @@ class State(TypedDict):
```
In this example, we've used the `Annotated` type to specify a reducer function (`operator.add`) for the second key (`bar`). Note that the first key remains unchanged. Let's assume the input to the graph is `{"foo": 1, "bar": ["hi"]}`. Let's then assume the first `Node` returns `{"foo": 2}`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{"foo": 2, "bar": ["hi"]}`. If the second node returns `{"bar": ["bye"]}` then the `State` would then be `{"foo": 2, "bar": ["hi", "bye"]}`. Notice here that the `bar` key is updated by adding the two lists together.
:::
:::js
```typescript
import { z } from "zod";
import "@langchain/langgraph/zod";
const State = z.object({
foo: z.number(),
bar: z.array(z.string()).langgraph.reducer((x, y) => x.concat(y)),
});
```
In this example, we've used the `.langgraph.reducer()` method to specify a reducer function for the second key (`bar`). Note that the first key remains unchanged. Let's assume the input to the graph is `{ foo: 1, bar: ["hi"] }`. Let's then assume the first `Node` returns `{ foo: 2 }`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{ foo: 2, bar: ["hi"] }`. If the second node returns `{ bar: ["bye"] }` then the `State` would then be `{ foo: 2, bar: ["hi", "bye"] }`. Notice here that the `bar` key is updated by concatenating the two arrays together.
:::
### Working with Messages in Graph State
#### Why use messages?
:::python
Most modern LLM providers have a chat model interface that accepts a list of messages as input. LangChain's [`ChatModel`](https://python.langchain.com/docs/concepts/#chat-models) in particular accepts a list of `Message` objects as inputs. These messages come in a variety of forms such as `HumanMessage` (user input) or `AIMessage` (LLM response). To read more about what message objects are, please refer to [this](https://python.langchain.com/docs/concepts/#messages) conceptual guide.
:::
:::js
Most modern LLM providers have a chat model interface that accepts a list of messages as input. LangChain's [`ChatModel`](https://js.langchain.com/docs/concepts/#chat-models) in particular accepts a list of `Message` objects as inputs. These messages come in a variety of forms such as `HumanMessage` (user input) or `AIMessage` (LLM response). To read more about what message objects are, please refer to [this](https://js.langchain.com/docs/concepts/#messages) conceptual guide.
:::
#### Using Messages in your Graph
:::python
In many cases, it is helpful to store prior conversation history as a list of messages in your graph state. To do so, we can add a key (channel) to the graph state that stores a list of `Message` objects and annotate it with a reducer function (see `messages` key in the example below). The reducer function is vital to telling the graph how to update the list of `Message` objects in the state with each state update (for example, when a node sends an update). If you don't specify a reducer, every state update will overwrite the list of messages with the most recently provided value. If you wanted to simply append messages to the existing list, you could use `operator.add` as a reducer.
However, you might also want to manually update messages in your graph state (e.g. human-in-the-loop). If you were to use `operator.add`, the manual state updates you send to the graph would be appended to the existing list of messages, instead of updating existing messages. To avoid that, you need a reducer that can keep track of message IDs and overwrite existing messages, if updated. To achieve this, you can use the prebuilt `add_messages` function. For brand new messages, it will simply append to existing list, but it will also handle the updates for existing messages correctly.
:::
:::js
In many cases, it is helpful to store prior conversation history as a list of messages in your graph state. To do so, we can add a key (channel) to the graph state that stores a list of `Message` objects and annotate it with a reducer function (see `messages` key in the example below). The reducer function is vital to telling the graph how to update the list of `Message` objects in the state with each state update (for example, when a node sends an update). If you don't specify a reducer, every state update will overwrite the list of messages with the most recently provided value. If you wanted to simply append messages to the existing list, you could use `(x, y) => x.concat(y)` as a reducer.
However, you might also want to manually update messages in your graph state (e.g. human-in-the-loop). If you were to use `(x, y) => x.concat(y)`, the manual state updates you send to the graph would be appended to the existing list of messages, instead of updating existing messages. To avoid that, you need a reducer that can keep track of message IDs and overwrite existing messages, if updated. To achieve this, you can use the prebuilt `messagesStateReducer` function. For brand new messages, it will simply append to existing list, but it will also handle the updates for existing messages correctly.
:::
#### Serialization
:::python
In addition to keeping track of message IDs, the `add_messages` function will also try to deserialize messages into LangChain `Message` objects whenever a state update is received on the `messages` channel. See more information on LangChain serialization/deserialization [here](https://python.langchain.com/docs/how_to/serialization/). This allows sending graph inputs / state updates in the following format:
```python
@@ -178,9 +332,36 @@ from typing_extensions import TypedDict
class GraphState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
```
:::
:::js
In addition to keeping track of message IDs, the `messagesStateReducer` function will also try to deserialize messages into LangChain `Message` objects whenever a state update is received on the `messages` channel. See more information on LangChain serialization/deserialization [here](https://js.langchain.com/docs/how_to/serialization/). This allows sending graph inputs / state updates in the following format:
```typescript
// this is supported
{ messages: [new HumanMessage("message")] }
// and this is also supported
{ messages: [{ role: "human", content: "message" }] }
```
Since the state updates are always deserialized into LangChain `Messages` when using `messagesStateReducer`, you should use dot notation to access message attributes, like `state.messages[state.messages.length - 1].content`. Below is an example of a graph that uses `messagesStateReducer` as its reducer function.
```typescript
import { BaseMessage } from "@langchain/core/messages";
import { messagesStateReducer } from "@langchain/langgraph";
import { z } from "zod";
import "@langchain/langgraph/zod";
const GraphState = z.object({
messages: z.array(z.any()).langgraph.reducer(messagesStateReducer),
});
```
:::
#### MessagesState
:::python
Since having a list of messages in your state is so common, there exists a prebuilt state called `MessagesState` which makes it easy to use messages. `MessagesState` is defined with a single `messages` key which is a list of `AnyMessage` objects and uses the `add_messages` reducer. Typically, there is more state to track than just messages, so we see people subclass this state and add more fields, like:
```python
@@ -189,9 +370,25 @@ from langgraph.graph import MessagesState
class State(MessagesState):
documents: list[str]
```
:::
:::js
Since having a list of messages in your state is so common, there exists a prebuilt state called `MessagesZodState` which makes it easy to use messages. `MessagesZodState` is defined with a single `messages` key which is a list of `BaseMessage` objects and uses the `messagesStateReducer` reducer. Typically, there is more state to track than just messages, so we see people merge this state with other schemas, like:
```typescript
import { MessagesZodState } from "@langchain/langgraph";
import { z } from "zod";
const State = z.object({
messages: MessagesZodState.shape.messages,
documents: z.array(z.string()),
});
```
:::
## Nodes
:::python
In LangGraph, nodes are typically python functions (sync or async) where the **first** positional argument is the [state](#state), and (optionally), the **second** positional argument is a "config", containing optional [configurable parameters](#configuration) (such as a `thread_id`).
Similar to `NetworkX`, you add these nodes to a graph using the [add_node][langgraph.graph.StateGraph.add_node] method:
@@ -232,26 +429,88 @@ If you add a node to a graph without specifying a name, it will be given a defau
builder.add_node(my_node)
# You can then create edges to/from this node by referencing it as `"my_node"`
```
:::
:::js
In LangGraph, nodes are typically TypeScript functions (sync or async) where the **first** positional argument is the [state](#state), and (optionally), the **second** positional argument is a "config", containing optional [configurable parameters](#configuration) (such as a `thread_id`).
Similar to `NetworkX`, you add these nodes to a graph using the `addNode` method:
```typescript
import { z } from "zod";
import { RunnableConfig } from "@langchain/core/runnables";
import { StateGraph } from "@langchain/langgraph";
const State = z.object({
input: z.string(),
results: z.string(),
});
const myNode = (state: z.infer<typeof State>, config: RunnableConfig) => {
console.log("In node: ", config?.configurable?.user_id);
return { results: `Hello, ${state.input}!` };
};
// The second argument is optional
const myOtherNode = (state: z.infer<typeof State>) => {
return state;
};
const graph = new StateGraph(State)
.addNode("myNode", myNode)
.addNode("otherNode", myOtherNode)
// ...
.compile();
```
Behind the scenes, functions are converted to [RunnableLambda](https://js.langchain.com/docs/concepts/#runnable-lambda)s, which add batch and async support to your function, along with native tracing and debugging.
:::
### `START` Node
The `START` Node is a special node that represents the node that sends user input to the graph. The main purpose for referencing this node is to determine which nodes should be called first.
:::python
```python
from langgraph.graph import START
graph.add_edge(START, "node_a")
```
:::
:::js
```typescript
import { START } from "@langchain/langgraph";
const graph = new StateGraph(State)
.addNode("nodeA", nodeA)
.addEdge(START, "nodeA")
.compile();
```
:::
### `END` Node
The `END` Node is a special node that represents a terminal node. This node is referenced when you want to denote which edges have no actions after they are done.
:::python
```
from langgraph.graph import END
graph.add_edge("node_a", END)
```
:::
:::js
```typescript
import { END } from "@langchain/langgraph";
const graph = new StateGraph(State)
.addNode("nodeA", nodeA)
.addEdge("nodeA", END)
.compile();
```
:::
### Node Caching
@@ -264,6 +523,7 @@ LangGraph supports caching of tasks/nodes based on the input to the node. To use
For example:
:::python
```py
import time
from typing_extensions import TypedDict
@@ -300,6 +560,40 @@ print(graph.invoke({"x": 5}, stream_mode='updates')) # (2)!
1. First run takes the full second to run (due to mocked expensive computation).
2. Second run utilizes cache and returns quickly.
:::
:::js
```typescript
import { z } from "zod";
import { StateGraph } from "@langchain/langgraph";
import { InMemoryCache } from "@langchain/langgraph";
const State = z.object({
x: z.number(),
result: z.number(),
});
const expensiveNode = (state: z.infer<typeof State>) => {
// expensive computation
return { result: state.x * 2 };
};
const graph = new StateGraph(State)
.addNode("expensiveNode", expensiveNode, {
cachePolicy: { ttl: 3 }
})
.addEdge("__start__", "expensiveNode")
.compile({ cache: new InMemoryCache() });
console.log(await graph.invoke({ x: 5 }, { streamMode: "updates" })); // (1)!
// [{ expensiveNode: { result: 10 } }]
console.log(await graph.invoke({ x: 5 }, { streamMode: "updates" })); // (2)!
// [{ expensiveNode: { result: 10 }, __metadata__: { cached: true } }]
```
1. First run takes the full computation time.
2. Second run utilizes cache and returns quickly.
:::
## Edges
@@ -314,14 +608,29 @@ A node can have MULTIPLE outgoing edges. If a node has multiple out-going edges,
### Normal Edges
:::python
If you **always** want to go from node A to node B, you can use the [add_edge][langgraph.graph.StateGraph.add_edge] method directly.
```python
graph.add_edge("node_a", "node_b")
```
:::
:::js
If you **always** want to go from node A to node B, you can use the `addEdge` method directly.
```typescript
const graph = new StateGraph(State)
.addNode("nodeA", nodeA)
.addNode("nodeB", nodeB)
.addEdge("nodeA", "nodeB")
.compile();
```
:::
### Conditional Edges
:::python
If you want to **optionally** route to 1 or more edges (or optionally terminate), you can use the [add_conditional_edges][langgraph.graph.StateGraph.add_conditional_edges] method. This method accepts the name of a node and a "routing function" to call after that node is executed:
```python
@@ -337,12 +646,45 @@ You can optionally provide a dictionary that maps the `routing_function`'s outpu
```python
graph.add_conditional_edges("node_a", routing_function, {True: "node_b", False: "node_c"})
```
:::
:::js
If you want to **optionally** route to 1 or more edges (or optionally terminate), you can use the `addConditionalEdges` method. This method accepts the name of a node and a "routing function" to call after that node is executed:
```typescript
const graph = new StateGraph(State)
.addNode("nodeA", nodeA)
.addNode("nodeB", nodeB)
.addNode("nodeC", nodeC)
.addConditionalEdges("nodeA", routingFunction)
.compile();
```
Similar to nodes, the `routingFunction` accepts the current `state` of the graph and returns a value.
By default, the return value `routingFunction` is used as the name of the node (or list of nodes) to send the state to next. All those nodes will be run in parallel as a part of the next superstep.
You can optionally provide a dictionary that maps the `routingFunction`'s output to the name of the next node.
```typescript
const graph = new StateGraph(State)
.addNode("nodeA", nodeA)
.addNode("nodeB", nodeB)
.addNode("nodeC", nodeC)
.addConditionalEdges("nodeA", routingFunction, {
true: "nodeB",
false: "nodeC",
})
.compile();
```
:::
!!! tip
Use [`Command`](#command) instead of conditional edges if you want to combine state updates and routing in a single function.
### Entry Point
:::python
The entry point is the first node(s) that are run when the graph starts. You can use the [`add_edge`][langgraph.graph.StateGraph.add_edge] method from the virtual [`START`][langgraph.constants.START] node to the first node to execute to specify where to enter the graph.
```python
@@ -350,9 +692,24 @@ from langgraph.graph import START
graph.add_edge(START, "node_a")
```
:::
:::js
The entry point is the first node(s) that are run when the graph starts. You can use the `addEdge` method from the virtual `START` node to the first node to execute to specify where to enter the graph.
```typescript
import { START } from "@langchain/langgraph";
const graph = new StateGraph(State)
.addNode("nodeA", nodeA)
.addEdge(START, "nodeA")
.compile();
```
:::
### Conditional Entry Point
:::python
A conditional entry point lets you start at different nodes depending on custom logic. You can use [`add_conditional_edges`][langgraph.graph.StateGraph.add_conditional_edges] from the virtual [`START`][langgraph.constants.START] node to accomplish this.
```python
@@ -366,11 +723,42 @@ You can optionally provide a dictionary that maps the `routing_function`'s outpu
```python
graph.add_conditional_edges(START, routing_function, {True: "node_b", False: "node_c"})
```
:::
:::js
A conditional entry point lets you start at different nodes depending on custom logic. You can use `addConditionalEdges` from the virtual `START` node to accomplish this.
```typescript
import { START } from "@langchain/langgraph";
const graph = new StateGraph(State)
.addNode("nodeB", nodeB)
.addNode("nodeC", nodeC)
.addConditionalEdges(START, routingFunction)
.compile();
```
You can optionally provide a dictionary that maps the `routingFunction`'s output to the name of the next node.
```typescript
import { START } from "@langchain/langgraph";
const graph = new StateGraph(State)
.addNode("nodeB", nodeB)
.addNode("nodeC", nodeC)
.addConditionalEdges(START, routingFunction, {
true: "nodeB",
false: "nodeC",
})
.compile();
```
:::
## `Send`
By default, `Nodes` and `Edges` are defined ahead of time and operate on the same shared state. However, there can be cases where the exact edges are not known ahead of time and/or you may want different versions of `State` to exist at the same time. A common example of this is with [map-reduce](https://langchain-ai.github.io/langgraph/how-tos/map-reduce/) design patterns. In this design pattern, a first node may generate a list of objects, and you may want to apply some other node to all those objects. The number of objects may be unknown ahead of time (meaning the number of edges may not be known) and the input `State` to the downstream `Node` should be different (one for each generated object).
:::python
To support this design pattern, LangGraph supports returning [`Send`][langgraph.types.Send] objects from conditional edges. `Send` takes two arguments: first is the name of the node, and second is the state to pass to that node.
```python
@@ -379,11 +767,31 @@ def continue_to_jokes(state: OverallState):
graph.add_conditional_edges("node_a", continue_to_jokes)
```
:::
:::js
To support this design pattern, LangGraph supports returning `Send` objects from conditional edges. `Send` takes two arguments: first is the name of the node, and second is the state to pass to that node.
```typescript
import { Send } from "@langchain/langgraph";
const continueToJokes = (state: OverallState) => {
return state.subjects.map((subject) => new Send("generateJoke", { subject }));
};
const graph = new StateGraph(State)
.addNode("nodeA", nodeA)
.addNode("generateJoke", generateJoke)
.addConditionalEdges("nodeA", continueToJokes)
.compile();
```
:::
## `Command`
It can be useful to combine control flow (edges) and state updates (nodes). For example, you might want to BOTH perform state updates AND decide which node to go to next in the SAME node. LangGraph provides a way to do so by returning a [`Command`][langgraph.types.Command] object from node functions:
:::python
```python
def my_node(state: State) -> Command[Literal["my_other_node"]]:
return Command(
@@ -393,18 +801,69 @@ def my_node(state: State) -> Command[Literal["my_other_node"]]:
goto="my_other_node"
)
```
:::
:::js
```typescript
import { Command } from "@langchain/langgraph";
const myNode = (state: State): Command => {
return new Command({
// state update
update: { foo: "bar" },
// control flow
goto: "myOtherNode",
});
};
```
:::
With `Command` you can also achieve dynamic control flow behavior (identical to [conditional edges](#conditional-edges)):
:::python
```python
def my_node(state: State) -> Command[Literal["my_other_node"]]:
if state["foo"] == "bar":
return Command(update={"foo": "baz"}, goto="my_other_node")
```
:::
:::js
```typescript
import { Command } from "@langchain/langgraph";
const myNode = (state: State): Command => {
if (state.foo === "bar") {
return new Command({
update: { foo: "baz" },
goto: "myOtherNode",
});
}
return new Command({
update: { foo: "qux" },
goto: "myThirdNode",
});
};
```
:::
!!! important
:::python
When returning `Command` in your node functions, you must add return type annotations with the list of node names the node is routing to, e.g. `Command[Literal["my_other_node"]]`. This is necessary for the graph rendering and tells LangGraph that `my_node` can navigate to `my_other_node`.
:::
:::js
When returning `Command` in your node functions, you must specify the `ends` option when adding the node to ensure proper graph rendering and tell LangGraph which nodes this node can navigate to.
```typescript
const graph = new StateGraph(State)
.addNode("myNode", myNode, {
ends: ["myOtherNode", "myThirdNode"],
})
.compile();
```
:::
Check out this [how-to guide](../how-tos/graph-api.ipynb#combine-control-flow-and-state-updates-with-command) for an end-to-end example of how to use `Command`.
@@ -418,6 +877,7 @@ Use [conditional edges](#conditional-edges) to route between nodes conditionally
If you are using [subgraphs](./subgraphs.md), you might want to navigate from a node within a subgraph to a different subgraph (i.e. a different node in the parent graph). To do so, you can specify `graph=Command.PARENT` in `Command`:
:::python
```python
def my_node(state: State) -> Command[Literal["other_subgraph"]]:
return Command(
@@ -426,6 +886,21 @@ def my_node(state: State) -> Command[Literal["other_subgraph"]]:
graph=Command.PARENT
)
```
:::
:::js
```typescript
import { Command } from "@langchain/langgraph";
const myNode = (state: State): Command => {
return new Command({
update: { foo: "bar" },
goto: "otherSubgraph", // where `otherSubgraph` is a node in the parent graph
graph: Command.PARENT,
});
};
```
:::
!!! note
@@ -447,7 +922,13 @@ Refer to [this guide](../how-tos/graph-api.ipynb#use-inside-tools) for detail.
### Human-in-the-loop
:::python
`Command` is an important part of human-in-the-loop workflows: when using `interrupt()` to collect user input, `Command` is then used to supply the input and resume execution via `Command(resume="User input")`. Check out [this conceptual guide](./human_in_the_loop.md) for more information.
:::
:::js
`Command` is an important part of human-in-the-loop workflows: when using `interrupt()` to collect user input, `Command` is then used to supply the input and resume execution via `new Command({ resume: "User input" })`. Check out [this conceptual guide](./human_in_the_loop.md) for more information.
:::
## Graph Migrations
@@ -463,6 +944,7 @@ LangGraph can easily handle migrations of graph definitions (nodes, edges, and s
When creating a graph, you can also mark that certain parts of the graph are configurable. This is commonly done to enable easily switching between models or system prompts. This allows you to create a single "cognitive architecture" (the graph) but have multiple different instance of it.
:::python
You can optionally specify a `config_schema` when creating a graph.
```python
@@ -488,19 +970,69 @@ def node_a(state, config):
llm = get_llm(llm_type)
...
```
:::
:::js
You can optionally specify a `configSchema` when creating a graph.
```typescript
import { z } from "zod";
const ConfigSchema = z.object({
llm: z.string(),
});
const graph = new StateGraph(State, ConfigSchema)
.addNode("nodeA", nodeA)
.compile();
```
You can then pass this configuration into the graph using the `configurable` config field.
```typescript
const config = { configurable: { llm: "anthropic" } };
await graph.invoke(inputs, config);
```
You can then access and use this configuration inside a node or conditional edge:
```typescript
const nodeA = (state: State, config: RunnableConfig) => {
const llmType = config?.configurable?.llm || "openai";
const llm = getLlm(llmType);
// ...
};
```
:::
See [this guide](../how-tos/graph-api.ipynb#add-runtime-configuration) for a full breakdown on configuration.
### Recursion Limit
The recursion limit sets the maximum number of [super-steps](#graphs) the graph can execute during a single execution. Once the limit is reached, LangGraph will raise `GraphRecursionError`. By default this value is set to 25 steps. The recursion limit can be set on any graph at runtime, and is passed to `.invoke`/`.stream` via the config dictionary. Importantly, `recursion_limit` is a standalone `config` key and should not be passed inside the `configurable` key as all other user-defined configuration. See the example below:
The recursion limit sets the maximum number of [super-steps](#graphs) the graph can execute during a single execution. Once the limit is reached, LangGraph will raise `GraphRecursionError`. By default this value is set to 25 steps.
:::python
The recursion limit can be set on any graph at runtime, and is passed to `.invoke`/`.stream` via the config dictionary. Importantly, `recursion_limit` is a standalone `config` key and should not be passed inside the `configurable` key as all other user-defined configuration. See the example below:
```python
graph.invoke(inputs, config={"recursion_limit": 5, "configurable":{"llm": "anthropic"}})
```
:::
:::js
The recursion limit can be set on any graph at runtime, and is passed to `.invoke`/`.stream` via the config dictionary. Importantly, `recursionLimit` is a standalone `config` key and should not be passed inside the `configurable` key as all other user-defined configuration. See the example below:
```typescript
await graph.invoke(inputs, {
recursionLimit: 5,
configurable: { llm: "anthropic" },
});
```
:::
Read [this how-to](https://langchain-ai.github.io/langgraph/how-tos/recursion-limit/) to learn more about how the recursion limit works.
## Visualization
It's often nice to be able to visualize graphs, especially as they get more complex. LangGraph comes with several built-in ways to visualize graphs. See [this how-to guide](../how-tos/graph-api.ipynb#visualize-your-graph) for more info.
It's often nice to be able to visualize graphs, especially as they get more complex. LangGraph comes with several built-in ways to visualize graphs. See [this how-to guide](../how-tos/graph-api.ipynb#visualize-your-graph) for more info.