diff --git a/docs/docs/how-tos/state-reducers.md b/docs/docs/how-tos/state-reducers.md
index 3853f7c2c..fc94cf75e 100644
--- a/docs/docs/how-tos/state-reducers.md
+++ b/docs/docs/how-tos/state-reducers.md
@@ -12,10 +12,14 @@ We will use [messages](../concepts/low_level.md/#messagesstate) in our examples.
First, let's install langgraph:
-```python
-%%capture --no-stderr
-%pip install -U langgraph
-```
+=== "Python"
+ ```shell
+ pip install -U langgraph
+ ```
+=== "TypeScript"
+ ```shell
+ npm install @langchain/langgraph
+ ```
Set up LangSmith for better debugging
@@ -34,15 +38,28 @@ By default, graphs will have the same input and output schema, and the state det
Let's consider a simple example:
-```python exec="on" source="above" session="1"
-from langchain_core.messages import AnyMessage
-from typing_extensions import TypedDict
+=== "Python"
+ ```python exec="on" source="above" session="1"
+ from langchain_core.messages import AnyMessage
+ from typing_extensions import TypedDict
+
+
+ class State(TypedDict):
+ messages: list[AnyMessage]
+ extra_field: int
+ ```
+=== "TypeScript"
+ ```typescript exec="on" source="above" session="1"
+ import { Annotation } from "@langchain/langgraph";
+ import { BaseMessage } from "@langchain/core/messages";
-
-class State(TypedDict):
- messages: list[AnyMessage]
- extra_field: int
-```
+ const StateAnnotation = Annotation.Root({
+ messages: Annotation
({
+ reducer: (x, y) => x.concat(y),
+ }),
+ extra_field: Annotation(),
+ });
+ ```
This state tracks a list of [message](https://python.langchain.com/docs/concepts/messages/) objects, as well as an extra integer field.
@@ -50,16 +67,31 @@ This state tracks a list of [message](https://python.langchain.com/docs/concepts
Let's build an example graph with a single node. Our [node](../concepts/low_level.md#nodes) is just a Python function that reads our graph's state and makes updates to it. The first argument to this function will always be the state:
-```python exec="on" source="above" session="1"
-from langchain_core.messages import AIMessage
+=== "Python"
+ ```python exec="on" source="above" session="1"
+ from langchain_core.messages import AIMessage
+
+
+ def node(state: State):
+ messages = state["messages"]
+ new_message = AIMessage("Hello!")
+
+ return {"messages": messages + [new_message], "extra_field": 10}
+ ```
+=== "TypeScript"
+ ```typescript exec="on" source="above" session="1"
+ import { AIMessage } from "@langchain/core/messages";
+ const node = (state: typeof StateAnnotation.State) => {
+ const { messages } = state;
+ const newMessage = new AIMessage("Hello!");
-def node(state: State):
- messages = state["messages"]
- new_message = AIMessage("Hello!")
-
- return {"messages": messages + [new_message], "extra_field": 10}
-```
+ return {
+ messages: [newMessage],
+ extra_field: 10,
+ };
+ };
+ ```
This node simply appends a message to our message list, and populates an extra field.
@@ -70,23 +102,38 @@ This node simply appends a message to our message list, and populates an extra f
Let's next define a simple graph containing this node. We use [StateGraph](../concepts/low_level.md#stategraph) to define a graph that operates on this state. We then use [add_node](../concepts/low_level.md#messagesstate) populate our graph.
-```python exec="on" source="above" session="1"
-from langgraph.graph import StateGraph
+=== "Python"
+ ```python exec="on" source="above" session="1"
+ from langgraph.graph import StateGraph
+
+ graph_builder = StateGraph(State)
+ graph_builder.add_node(node)
+ graph_builder.set_entry_point("node")
+ graph = graph_builder.compile()
+ ```
+=== "TypeScript"
+ ```typescript exec="on" source="above" session="1"
+ import { StateGraph, START } from "@langchain/langgraph";
-graph_builder = StateGraph(State)
-graph_builder.add_node(node)
-graph_builder.set_entry_point("node")
-graph = graph_builder.compile()
-```
+ const graphBuilder = new StateGraph(StateAnnotation)
+ .addNode("node", node)
+ .addEdge(START, "node");
+
+ const graph = graphBuilder.compile();
+ ```
LangGraph provides built-in utilities for visualizing your graph. Let's inspect our graph. See [this guide](../how-tos/visualization.ipynb) for detail on visualization.
-```python
-from IPython.display import Image, display
-
-display(Image(graph.get_graph().draw_mermaid_png()))
-```
+=== "Python"
+ ```python
+ image = graph.get_graph().draw_mermaid_png()
+ ```
+=== "TypeScript"
+ ```typescript
+ const drawableGraph = graph.getGraph();
+ const image = await drawableGraph.drawMermaidPng();
+ ```

@@ -97,12 +144,24 @@ In this case, our graph just executes a single node.
Let's proceed with a simple invocation:
-```python exec="on" source="above" session="1" result="ansi"
-from langchain_core.messages import HumanMessage
+=== "Python"
+ ```python exec="on" source="above" session="1" result="ansi"
+ from langchain_core.messages import HumanMessage
+
+ result = graph.invoke({"messages": [HumanMessage("Hi")]})
+ result
+ ```
+=== "TypeScript"
+ ```typescript exec="on" source="above" session="1" result="ansi"
+ import { HumanMessage } from "@langchain/core/messages";
-result = graph.invoke({"messages": [HumanMessage("Hi")]})
-result
-```
+ const result = await graph.invoke({
+ messages: [new HumanMessage("Hi")],
+ extra_field: 0,
+ });
+
+ console.log(result);
+ ```
Note that:
@@ -112,10 +171,17 @@ Note that:
For convenience, we frequently inspect the content of [message objects](https://python.langchain.com/docs/concepts/messages/) via pretty-print:
-```python exec="on" source="above" session="1" result="ansi"
-for message in result["messages"]:
- message.pretty_print()
-```
+=== "Python"
+ ```python exec="on" source="above" session="1" result="ansi"
+ for message in result["messages"]:
+ message.pretty_print()
+ ```
+=== "TypeScript"
+ ```typescript exec="on" source="above" session="1" result="ansi"
+ result.messages.forEach(message => {
+ console.log(`${message._getType()}: ${message.content}`);
+ });
+ ```
## Process state updates with reducers
@@ -126,43 +192,88 @@ For `TypedDict` state schemas, we can define reducers by annotating the correspo
In the earlier example, our node updated the `"messages"` key in the state by appending a message to it. Below, we add a reducer to this key, such that updates are automatically appended:
-```python exec="on" source="above" session="1"
-from typing_extensions import Annotated
+=== "Python"
+ ```python exec="on" source="above" session="2"
+ from typing_extensions import Annotated
+
+
+ def add(left, right):
+ """Can also import `add` from the `operator` built-in."""
+ return left + right
+
+
+ class State(TypedDict):
+ # highlight-next-line
+ messages: Annotated[list[AnyMessage], add]
+ extra_field: int
+ ```
+=== "TypeScript"
+ ```typescript exec="on" source="above" session="2"
+ import { Annotation } from "@langchain/langgraph";
+ import { BaseMessage } from "@langchain/core/messages";
-
-def add(left, right):
- """Can also import `add` from the `operator` built-in."""
- return left + right
-
-
-class State(TypedDict):
- # highlight-next-line
- messages: Annotated[list[AnyMessage], add]
- extra_field: int
-```
+ const StateAnnotation = Annotation.Root({
+ messages: Annotation({
+ reducer: (x, y) => x.concat(y),
+ }),
+ extra_field: Annotation(),
+ });
+ ```
Now our node can be simplified:
-```python exec="on" source="above" session="1"
-def node(state: State):
- new_message = AIMessage("Hello!")
- # highlight-next-line
- return {"messages": [new_message], "extra_field": 10}
-```
+=== "Python"
+ ```python exec="on" source="above" session="2"
+ def node(state: State):
+ new_message = AIMessage("Hello!")
+ # highlight-next-line
+ return {"messages": [new_message], "extra_field": 10}
+ ```
+=== "TypeScript"
+ ```typescript exec="on" source="above" session="2"
+ const node = (state: typeof StateAnnotation.State) => {
+ const newMessage = new AIMessage("Hello!");
+
+ return {
+ messages: [newMessage],
+ extra_field: 10,
+ };
+ };
+ ```
-```python exec="on" source="above" session="1" result="ansi"
-from langgraph.graph import START
+===! "Python"
+ ```python exec="on" source="above" session="2" result="ansi"
+ from langgraph.graph import START
+
+
+ graph = StateGraph(State).add_node(node).add_edge(START, "node").compile()
+
+ result = graph.invoke({"messages": [HumanMessage("Hi")]})
+
+ for message in result["messages"]:
+ message.pretty_print()
+ ```
+=== "TypeScript"
+ ```typescript exec="on" source="above" session="2" result="ansi"
+ import { StateGraph, START } from "@langchain/langgraph";
+ import { HumanMessage } from "@langchain/core/messages";
+ const graph = new StateGraph(StateAnnotation)
+ .addNode("node", node)
+ .addEdge(START, "node")
+ .compile();
-graph = StateGraph(State).add_node(node).add_edge(START, "node").compile()
+ const result = await graph.invoke({
+ messages: [new HumanMessage("Hi")],
+ extra_field: 0,
+ });
-result = graph.invoke({"messages": [HumanMessage("Hi")]})
-
-for message in result["messages"]:
- message.pretty_print()
-```
+ result.messages.forEach(message => {
+ console.log(`${message._getType()}: ${message.content}`);
+ });
+ ```
### MessagesState
@@ -174,45 +285,96 @@ In practice, there are additional considerations for updating lists of messages:
LangGraph includes a built-in reducer `add_messages` that handles these considerations:
-```python exec="on" source="above" session="1"
-from langgraph.graph.message import add_messages
+=== "Python"
+ ```python exec="on" source="above" session="3"
+ from langgraph.graph.message import add_messages
+
+
+ class State(TypedDict):
+ # highlight-next-line
+ messages: Annotated[list[AnyMessage], add_messages]
+ extra_field: int
+
+
+ def node(state: State):
+ new_message = AIMessage("Hello!")
+ return {"messages": [new_message], "extra_field": 10}
+
+
+ graph = StateGraph(State).add_node(node).set_entry_point("node").compile()
+ ```
+=== "TypeScript"
+ ```typescript exec="on" source="above" session="3"
+ import { Annotation, messagesStateReducer } from "@langchain/langgraph";
+ import { BaseMessage, AIMessage } from "@langchain/core/messages";
+
+ const StateAnnotation = Annotation.Root({
+ messages: Annotation({
+ reducer: messagesStateReducer,
+ }),
+ extra_field: Annotation(),
+ });
+
+ const node = (state: typeof StateAnnotation.State) => {
+ const newMessage = new AIMessage("Hello!");
+ return {
+ messages: [newMessage],
+ extra_field: 10,
+ };
+ };
+
+ const graphBuilder = new StateGraph(StateAnnotation)
+ .addNode("node", node)
+ .addEdge(START, "node");
+
+ const graph = graphBuilder.compile();
+ ```
-class State(TypedDict):
+===! "Python"
+ ```python exec="on" source="above" session="3" result="ansi"
# highlight-next-line
- messages: Annotated[list[AnyMessage], add_messages]
- extra_field: int
+ input_message = {"role": "user", "content": "Hi"}
+
+ result = graph.invoke({"messages": [input_message]})
+
+ for message in result["messages"]:
+ message.pretty_print()
+ ```
+=== "TypeScript"
+ ```typescript exec="on" source="above" session="3" result="ansi"
+ const inputMessage = { role: "user", content: "Hi" };
+ const result = await graph.invoke({
+ messages: [inputMessage],
+ extra_field: 0,
+ });
-def node(state: State):
- new_message = AIMessage("Hello!")
- return {"messages": [new_message], "extra_field": 10}
-
-
-graph = StateGraph(State).add_node(node).set_entry_point("node").compile()
-```
-
-
-```python exec="on" source="above" session="1" result="ansi"
-# highlight-next-line
-input_message = {"role": "user", "content": "Hi"}
-
-result = graph.invoke({"messages": [input_message]})
-
-for message in result["messages"]:
- message.pretty_print()
-```
+ result.messages.forEach(message => {
+ console.log(`${message._getType()}: ${message.content}`);
+ });
+ ```
This is a versatile representation of state for applications involving [chat models](https://python.langchain.com/docs/concepts/chat_models/). LangGraph includes a pre-built `MessagesState` for convenience, so that we can have:
-```python exec="on" source="above" session="1"
-from langgraph.graph import MessagesState
+=== "Python"
+ ```python exec="on" source="above" session="4"
+ from langgraph.graph import MessagesState
+
+
+ class State(MessagesState):
+ extra_field: int
+ ```
+=== "TypeScript"
+ ```typescript exec="on" source="above" session="4"
+ import { Annotation, MessagesAnnotation } from "@langchain/langgraph";
-
-class State(MessagesState):
- extra_field: int
-```
+ const StateAnnotation = Annotation.Root({
+ ...MessagesAnnotation.spec,
+ extra_field: Annotation(),
+ });
+ ```
## Next steps