diff --git a/docs/docs/concepts/low_level.md b/docs/docs/concepts/low_level.md
index a6b6a21f..6165626d 100644
--- a/docs/docs/concepts/low_level.md
+++ b/docs/docs/concepts/low_level.md
@@ -406,59 +406,6 @@ const myNode = (state: typeof StateAnnotation.State) => {
};
```
-A `Command` has the following properties:
-
-| Property | Description |
-| --- | --- |
-| `graph` | Graph to send the command to. Supported values:
- `None`: the current graph (default)
- `Command.PARENT`: closest parent graph |
-| `update` | Update to apply to the graph's state. |
-| `resume` | Value to resume execution with. To be used together with [`interrupt()`](https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph.interrupt-1.html). |
-| `goto` | Can be one of the following:
- name of the node to navigate to next (any node that belongs to the specified `graph`)
- sequence of node names to navigate to next
- [`Send`](https://langchain-ai.github.io/langgraphjs/reference/classes/langgraph.Send.html) object (to execute a node with the input provided)
- sequence of `Send` objects
If `goto` is not specified and there are no other tasks left in the graph, the graph will halt after executing the current superstep. |
-
-Here's a complete example:
-
-```ts
-import { StateGraph, Annotation, Command } from "@langchain/langgraph";
-
-const StateAnnotation = Annotation.Root({
- foo: Annotation,
-});
-
-const myNode = async (state: typeof StateAnnotation.State) => {
- return new Command({
- // state update
- update: {
- foo: "bar",
- },
- // control flow
- goto: "myOtherNode",
- });
-};
-
-const myOtherNode = async (state: typeof StateAnnotation.State) => {
- return {
- foo: state.foo + "baz"
- };
-};
-
-const graph = new StateGraph(StateAnnotation)
- .addNode("myNode", myNode, {
- // For compiling and validating the graph
- ends: ["myOtherNode"],
- })
- .addNode("myOtherNode", myOtherNode)
- .addEdge("__start__", "myNode")
- .compile();
-
-await graph.invoke({
- foo: "",
-});
-```
-
-```ts
-{ foo: "barbaz" }
-```
-
With `Command` you can also achieve dynamic control flow behavior (identical to [conditional edges](#conditional-edges)):
```ts
@@ -477,7 +424,7 @@ const myNode = async (state: typeof StateAnnotation.State) => {
!!! important
- When returning `Command` in your node functions, you must also add an `ends` parameter with the list of node names the node is routing to, e.g. `.addNode("myNode", myNode, { ends: ["nodeA", "nodeB"] })`. This is necessary for graph compilation and validation, and indicates that `myNode` can navigate to `nodeA` and `nodeB`.
+ When returning `Command` in your node functions, you must also add an `ends` parameter with the list of node names the node is routing to, e.g. `.addNode("myNode", myNode, { ends: ["myOtherNode"] })`. This is necessary for graph compilation and validation, and indicates that `myNode` can navigate to `myOtherNode`.
Check out this [how-to guide](../how-tos/command.ipynb) for an end-to-end example of how to use `Command`.
diff --git a/docs/docs/concepts/multi_agent.md b/docs/docs/concepts/multi_agent.md
index 22125df0..9d175030 100644
--- a/docs/docs/concepts/multi_agent.md
+++ b/docs/docs/concepts/multi_agent.md
@@ -27,22 +27,77 @@ There are several ways to connect agents in a multi-agent system:
### Network
-In this architecture, agents are defined as graph nodes. Each agent can communicate with every other agent (many-to-many connections) and can decide which agent to call next. While very flexible, this architecture doesn't scale well as the number of agents grows:
+In this architecture, agents are defined as graph nodes. Each agent can communicate with every other agent (many-to-many connections) and can decide which agent to call next. This architecture is good for problems that do not have a clear hierarchy of agents or a specific sequence in which agents should be called.
-- hard to enforce which agent should be called next
-- hard to determine how much [information](#shared-message-list) should be passed between the agents
+```ts
+import {
+ StateGraph,
+ Annotation,
+ MessagesAnnotation,
+ Command
+} from "@langchain/langgraph";
+import { ChatOpenAI } from "@langchain/openai";
+
+const model = new ChatOpenAI({
+ model: "gpt-4o-mini",
+});
-We recommend avoiding this architecture in production and using one of the below architectures instead.
+const agent1 = async (state: typeof MessagesAnnotation.State) => {
+ // you can pass relevant parts of the state to the LLM (e.g., state.messages)
+ // to determine which agent to call next. a common pattern is to call the model
+ // with a structured output (e.g. force it to return an output with a "next_agent" field)
+ const response = await model.withStructuredOutput(...).invoke(...);
+ return new Command({
+ update: {
+ messages: [response.content],
+ },
+ goto: response.next_agent,
+ });
+};
+
+const agent2 = async (state: typeof MessagesAnnotation.State) => {
+ const response = await model.withStructuredOutput(...).invoke(...);
+ return new Command({
+ update: {
+ messages: [response.content],
+ },
+ goto: response.next_agent,
+ });
+};
+
+const agent3 = async (state: typeof MessagesAnnotation.State) => {
+ ...
+ return new Command({
+ update: {
+ messages: [response.content],
+ },
+ goto: response.next_agent,
+ });
+};
+
+const graph = new StateGraph(MessagesAnnotation)
+ .addNode("agent1", agent1, {
+ ends: ["agent2", "agent3" "__end__"],
+ })
+ .addNode("agent2", agent2, {
+ ends: ["agent1", "agent3", "__end__"],
+ })
+ .addNode("agent3", agent3, {
+ ends: ["agent1", "agent2", "__end__"],
+ })
+ .addEdge("__start__", "agent1")
+ .compile();
+```
### Supervisor
-In this architecture, we define agents as nodes and add a supervisor node (LLM) that decides which agent nodes should be called next. We use [conditional edges](./low_level.md#conditional-edges) to route execution to the appropriate agent node based on supervisor's decision. This architecture also lends itself well to running multiple agents in parallel or using [map-reduce](../how-tos/map-reduce.ipynb) pattern.
+In this architecture, we define agents as nodes and add a supervisor node (LLM) that decides which agent nodes should be called next. We use [`Command`](./low_level.md#command) to route execution to the appropriate agent node based on supervisor's decision. This architecture also lends itself well to running multiple agents in parallel or using [map-reduce](../how-tos/map-reduce.ipynb) pattern.
```ts
import {
StateGraph,
- Annotation,
MessagesAnnotation,
+ Command,
} from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
@@ -50,35 +105,51 @@ const model = new ChatOpenAI({
model: "gpt-4o-mini",
});
-const StateAnnotation = Annotation.Root({
- ...MessagesAnnotation.spec,
- next: Annotation<"agent1" | "agent2">,
-});
-
-const supervisor = async (state: typeof StateAnnotation.State) => {
+const supervisor = async (state: typeof MessagesAnnotation.State) => {
+ // you can pass relevant parts of the state to the LLM (e.g., state.messages)
+ // to determine which agent to call next. a common pattern is to call the model
+ // with a structured output (e.g. force it to return an output with a "next_agent" field)
const response = await model.withStructuredOutput(...).invoke(...);
- return { next: response.next_agent };
+ // route to one of the agents or exit based on the supervisor's decision
+ // if the supervisor returns "__end__", the graph will finish execution
+ return new Command({
+ goto: response.next_agent,
+ });
};
-const agent1 = async (state: typeof StateAnnotation.State) => {
+const agent1 = async (state: typeof MessagesAnnotation.State) => {
+ // you can pass relevant parts of the state to the LLM (e.g., state.messages)
+ // and add any additional logic (different models, custom prompts, structured output, etc.)
const response = await model.invoke(...);
- return { messages: [response] };
+ return new Command({
+ goto: "supervisor",
+ update: {
+ messages: [response],
+ },
+ });
};
-const agent2 = async (state: typeof StateAnnotation.State) => {
+const agent2 = async (state: typeof MessagesAnnotation.State) => {
const response = await model.invoke(...);
- return { messages: [response] };
+ return new Command({
+ goto: "supervisor",
+ update: {
+ messages: [response],
+ },
+ });
};
-const graph = new StateGraph(StateAnnotation)
- .addNode("supervisor", supervisor)
- .addNode("agent1", agent1)
- .addNode("agent2", agent2)
+const graph = new StateGraph(MessagesAnnotation)
+ .addNode("supervisor", supervisor, {
+ ends: ["agent1", "agent2", "__end__"],
+ })
+ .addNode("agent1", agent1, {
+ ends: ["supervisor"],
+ })
+ .addNode("agent2", agent2, {
+ ends: ["supervisor"],
+ })
.addEdge("__start__", "supervisor")
- // route to one of the agents or exit based on the supervisor's decisiion
- .addConditionalEdges("supervisor", async (state) => state.next)
- .addEdge("agent1", "supervisor")
- .addEdge("agent2", "supervisor")
.compile();
```
@@ -90,7 +161,7 @@ In this architecture we add individual agents as graph nodes and define the orde
- **Explicit control flow (normal edges)**: LangGraph allows you to explicitly define the control flow of your application (i.e. the sequence of how agents communicate) explicitly, via [normal graph edges](./low_level.md#normal-edges). This is the most deterministic variant of this architecture above — we always know which agent will be called next ahead of time.
-- **Dynamic control flow (conditional edges)**: in LangGraph you can allow LLMs to decide parts of your application control flow. This can be achieved by using [conditional edges](./low_level.md#conditional-edges).
+- **Dynamic control flow (conditional edges)**: in LangGraph you can allow LLMs to decide parts of your application control flow. This can be achieved by using [`Command`](./low_level.md#command).
```ts
import {