mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-26 19:45:00 +02:00
feat: add docs translations (#5552)
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com> Co-authored-by: Tat Dat Duong <david@duong.cz>
This commit is contained in:
co-authored by
Eugene Yurtsev
Tat Dat Duong
parent
72e418e4d0
commit
d59091672f
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Multi-agent systems
|
||||
|
||||
An [agent](./agentic_concepts.md#agent-architectures) is _a system that uses an LLM to decide the control flow of an application_. As you develop these systems, they might grow more complex over time, making them harder to manage and scale. For example, you might run into the following problems:
|
||||
@@ -25,21 +20,23 @@ The primary benefits of using multi-agent systems are:
|
||||
|
||||
There are several ways to connect agents in a multi-agent system:
|
||||
|
||||
- **Network**: each agent can communicate with [every other agent](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/multi-agent-collaboration/). Any agent can decide which other agent to call next.
|
||||
- **Supervisor**: each agent communicates with a single [supervisor](../tutorials/multi_agent/agent_supervisor.md) agent. Supervisor agent makes decisions on which agent should be called next.
|
||||
- **Network**: each agent can communicate with [every other agent](../tutorials/multi_agent/multi-agent-collaboration.ipynb/). Any agent can decide which other agent to call next.
|
||||
- **Supervisor**: each agent communicates with a single [supervisor](../tutorials/multi_agent/agent_supervisor.md/) agent. Supervisor agent makes decisions on which agent should be called next.
|
||||
- **Supervisor (tool-calling)**: this is a special case of supervisor architecture. Individual agents can be represented as tools. In this case, a supervisor agent uses a tool-calling LLM to decide which of the agent tools to call, as well as the arguments to pass to those agents.
|
||||
- **Hierarchical**: you can define a multi-agent system with [a supervisor of supervisors](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/hierarchical_agent_teams/). This is a generalization of the supervisor architecture and allows for more complex control flows.
|
||||
- **Hierarchical**: you can define a multi-agent system with [a supervisor of supervisors](../tutorials/multi_agent/hierarchical_agent_teams.ipynb/). This is a generalization of the supervisor architecture and allows for more complex control flows.
|
||||
- **Custom multi-agent workflow**: each agent communicates with only a subset of agents. Parts of the flow are deterministic, and only some agents can decide which other agents to call next.
|
||||
|
||||
### Handoffs
|
||||
|
||||
In multi-agent architectures, agents can be represented as graph nodes. Each agent node executes its step(s) and decides whether to finish execution or route to another agent, including potentially routing to itself (e.g., running in a loop). A common pattern in multi-agent interactions is **handoffs**, where one agent *hands off* control to another. Handoffs allow you to specify:
|
||||
In multi-agent architectures, agents can be represented as graph nodes. Each agent node executes its step(s) and decides whether to finish execution or route to another agent, including potentially routing to itself (e.g., running in a loop). 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 (e.g., name of the node to go to)
|
||||
- __payload__: [information to pass to that agent](#communication-and-state-management) (e.g., state update)
|
||||
- **destination**: target agent to navigate to (e.g., name of the node to go to)
|
||||
- **payload**: [information to pass to that agent](#communication-and-state-management) (e.g., state update)
|
||||
|
||||
To implement handoffs in LangGraph, agent nodes can return [`Command`](./low_level.md#command) object that allows you to combine both control flow and state updates:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
def agent(state) -> Command[Literal["agent", "another_agent"]]:
|
||||
# the condition for routing/halting can be anything, e.g. LLM tool call / structured output, etc.
|
||||
@@ -52,6 +49,26 @@ def agent(state) -> Command[Literal["agent", "another_agent"]]:
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
graph.addNode((state) => {
|
||||
// the condition for routing/halting can be anything, e.g. LLM tool call / structured output, etc.
|
||||
const goto = getNextAgent(...); // 'agent' / 'another_agent'
|
||||
return new Command({
|
||||
// Specify which agent to call next
|
||||
goto,
|
||||
// Update the graph state
|
||||
update: { myStateKey: "myStateValue" }
|
||||
});
|
||||
})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
In a more complex scenario where each agent node is itself a graph (i.e., a [subgraph](./subgraphs.md)), a node in one of the agent subgraphs might want to navigate to a different agent. For example, if you have two agents, `alice` and `bob` (subgraph nodes in a parent graph), and `alice` needs to navigate to `bob`, you can set `graph=Command.PARENT` in the `Command` object:
|
||||
|
||||
```python
|
||||
@@ -64,8 +81,30 @@ def some_node_inside_alice(state):
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
In a more complex scenario where each agent node is itself a graph (i.e., a [subgraph](./subgraphs.md)), a node in one of the agent subgraphs might want to navigate to a different agent. For example, if you have two agents, `alice` and `bob` (subgraph nodes in a parent graph), and `alice` needs to navigate to `bob`, you can set `graph: Command.PARNT` in the `Command` object:
|
||||
|
||||
```typescript
|
||||
alice.addNode((state) => {
|
||||
return new Command({
|
||||
goto: "bob",
|
||||
update: { myStateKey: "myStateValue" },
|
||||
// specify which graph to navigate to (defaults to the current graph)
|
||||
graph: Command.PARENT,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! note
|
||||
If you need to support visualization for subgraphs communicating using `Command(graph=Command.PARENT)` you would need to wrap them in a node function with `Command` annotation, e.g. instead of this:
|
||||
|
||||
:::python
|
||||
|
||||
If you need to support visualization for subgraphs communicating using `Command(graph=Command.PARENT)` you would need to wrap them in a node function with `Command` annotation:
|
||||
Instead of this:
|
||||
|
||||
```python
|
||||
builder.add_node(alice)
|
||||
@@ -80,9 +119,30 @@ def some_node_inside_alice(state):
|
||||
builder.add_node("alice", call_alice)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
If you need to support visualization for subgraphs communicating using/ `Command({ graph: Command.PARENT })` you would need to wrap them in a node function with `Command` annotation:
|
||||
|
||||
Instead of this:
|
||||
|
||||
```typescript
|
||||
builder.addNode("alice", alice);
|
||||
```
|
||||
|
||||
you would need to do this:
|
||||
|
||||
```typescript
|
||||
builder.addNode("alice", (state) => alice.invoke(state), { ends: ["bob"] });
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#### Handoffs as tools
|
||||
|
||||
One of the most common agent types is a [tool-calling agent](../agents/overview.md). For those types of agents, a common pattern is wrapping a handoff in a tool call, e.g.:
|
||||
One of the most common agent types is a [tool-calling agent](../agents/overview.md). For those types of agents, a common pattern is wrapping a handoff in a tool call:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langchain_core.tools import tool
|
||||
@@ -101,18 +161,65 @@ def transfer_to_bob():
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { Command } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const transferToBob = tool(
|
||||
async () => {
|
||||
return new Command({
|
||||
// name of the agent (node) to go to
|
||||
goto: "bob",
|
||||
// data to send to the agent
|
||||
update: { myStateKey: "myStateValue" },
|
||||
// indicate to LangGraph that we need to navigate to
|
||||
// agent node in a parent graph
|
||||
graph: Command.PARENT,
|
||||
});
|
||||
},
|
||||
{
|
||||
name: "transfer_to_bob",
|
||||
description: "Transfer to bob.",
|
||||
schema: z.object({}),
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
This is a special case of updating the graph state from tools where, in addition to the state update, the control flow is included as well.
|
||||
|
||||
!!! important
|
||||
|
||||
If you want to use tools that return `Command`, you can either use prebuilt [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent] / [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode] components, or implement your own tool-executing node that collects `Command` objects returned by the tools and returns a list of them, e.g.:
|
||||
|
||||
```python
|
||||
def call_tools(state):
|
||||
...
|
||||
commands = [tools_by_name[tool_call["name"]].invoke(tool_call) for tool_call in tool_calls]
|
||||
return commands
|
||||
```
|
||||
:::python
|
||||
If you want to use tools that return `Command`, you can use the prebuilt @[`create_react_agent`][create_react_agent] / @[`ToolNode`][ToolNode] components, or else implement your own logic:
|
||||
|
||||
```python
|
||||
def call_tools(state):
|
||||
...
|
||||
commands = [tools_by_name[tool_call["name"]].invoke(tool_call) for tool_call in tool_calls]
|
||||
return commands
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
If you want to use tools that return `Command`, you can use the prebuilt @[`createReactAgent`][create_react_agent] / @[ToolNode] components, or else implement your own logic:
|
||||
|
||||
```typescript
|
||||
graph.addNode("call_tools", async (state) => {
|
||||
// ... tool execution logic
|
||||
const commands = toolCalls.map((toolCall) =>
|
||||
toolsByName[toolCall.name].invoke(toolCall)
|
||||
);
|
||||
return commands;
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
Let's now take a closer look at the different multi-agent architectures.
|
||||
|
||||
@@ -120,6 +227,7 @@ Let's now take a closer look at the different multi-agent architectures.
|
||||
|
||||
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.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Literal
|
||||
@@ -164,10 +272,70 @@ builder.add_edge(START, "agent_1")
|
||||
network = builder.compile()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, START, END } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { Command } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
const agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// 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.invoke(...);
|
||||
// route to one of the agents or exit based on the LLM's decision
|
||||
// if the LLM returns "__end__", the graph will finish execution
|
||||
return new Command({
|
||||
goto: response.nextAgent,
|
||||
update: { messages: [response.content] },
|
||||
});
|
||||
};
|
||||
|
||||
const agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: response.nextAgent,
|
||||
update: { messages: [response.content] },
|
||||
});
|
||||
};
|
||||
|
||||
const agent3 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// ...
|
||||
return new Command({
|
||||
goto: response.nextAgent,
|
||||
update: { messages: [response.content] },
|
||||
});
|
||||
};
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
.addNode("agent1", agent1, {
|
||||
ends: ["agent2", "agent3", END]
|
||||
})
|
||||
.addNode("agent2", agent2, {
|
||||
ends: ["agent1", "agent3", END]
|
||||
})
|
||||
.addNode("agent3", agent3, {
|
||||
ends: ["agent1", "agent2", END]
|
||||
})
|
||||
.addEdge(START, "agent1");
|
||||
|
||||
const network = builder.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 [`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/graph-api.md#map-reduce-and-the-send-api) pattern.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Literal
|
||||
from langchain_openai import ChatOpenAI
|
||||
@@ -211,12 +379,124 @@ builder.add_edge(START, "supervisor")
|
||||
supervisor = builder.compile()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, Command, START, END } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
const supervisor = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// 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.invoke(...);
|
||||
// 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.nextAgent });
|
||||
};
|
||||
|
||||
const agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// 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 new Command({
|
||||
goto: "supervisor",
|
||||
update: { messages: [response] },
|
||||
});
|
||||
};
|
||||
|
||||
const agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: "supervisor",
|
||||
update: { messages: [response] },
|
||||
});
|
||||
};
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
.addNode("supervisor", supervisor, {
|
||||
ends: ["agent1", "agent2", END]
|
||||
})
|
||||
.addNode("agent1", agent1, {
|
||||
ends: ["supervisor"]
|
||||
})
|
||||
.addNode("agent2", agent2, {
|
||||
ends: ["supervisor"]
|
||||
})
|
||||
.addEdge(START, "supervisor");
|
||||
|
||||
const supervisorGraph = builder.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, Command, START, END } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
const supervisor = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// 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.invoke(...);
|
||||
// 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.nextAgent });
|
||||
};
|
||||
|
||||
const agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// 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 new Command({
|
||||
goto: "supervisor",
|
||||
update: { messages: [response] },
|
||||
});
|
||||
};
|
||||
|
||||
const agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: "supervisor",
|
||||
update: { messages: [response] },
|
||||
});
|
||||
};
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
.addNode("supervisor", supervisor, {
|
||||
ends: ["agent1", "agent2", END]
|
||||
})
|
||||
.addNode("agent1", agent1, {
|
||||
ends: ["supervisor"]
|
||||
})
|
||||
.addNode("agent2", agent2, {
|
||||
ends: ["supervisor"]
|
||||
})
|
||||
.addEdge(START, "supervisor");
|
||||
|
||||
const supervisorGraph = builder.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Check out this [tutorial](../tutorials/multi_agent/agent_supervisor.md) for an example of supervisor multi-agent architecture.
|
||||
|
||||
### Supervisor (tool-calling)
|
||||
|
||||
In this variant of the [supervisor](#supervisor) architecture, we define a supervisor [agent](./agentic_concepts.md#agent-architectures) which is responsible for calling sub-agents. The sub-agents are exposed to the supervisor as tools, and the supervisor agent decides which tool to call next. The supervisor agent follows a [standard implementation](./agentic_concepts.md#tool-calling-agent) as an LLM running in a while loop calling tools until it decides to stop.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from langchain_openai import ChatOpenAI
|
||||
@@ -245,12 +525,67 @@ tools = [agent_1, agent_2]
|
||||
supervisor = create_react_agent(model, tools)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
// this is the agent function that will be called as tool
|
||||
// notice that you can pass the state to the tool via config parameter
|
||||
const agent1 = tool(
|
||||
async (_, config) => {
|
||||
const state = config.configurable?.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 the LLM response as a string (expected tool response format)
|
||||
// this will be automatically turned to ToolMessage
|
||||
// by the prebuilt createReactAgent (supervisor)
|
||||
return response.content;
|
||||
},
|
||||
{
|
||||
name: "agent1",
|
||||
description: "Agent 1 description",
|
||||
schema: z.object({}),
|
||||
}
|
||||
);
|
||||
|
||||
const agent2 = tool(
|
||||
async (_, config) => {
|
||||
const state = config.configurable?.state;
|
||||
const response = await model.invoke(...);
|
||||
return response.content;
|
||||
},
|
||||
{
|
||||
name: "agent2",
|
||||
description: "Agent 2 description",
|
||||
schema: z.object({}),
|
||||
}
|
||||
);
|
||||
|
||||
const tools = [agent1, agent2];
|
||||
// the simplest way to build a supervisor w/ tool-calling is to use prebuilt ReAct agent graph
|
||||
// that consists of a tool-calling LLM node (i.e. supervisor) and a tool-executing node
|
||||
const supervisor = createReactAgent({ llm: model, tools });
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Hierarchical
|
||||
|
||||
As you add more agents to your system, it might become too hard for the supervisor to manage all of them. The supervisor might start making poor decisions about which agent to call next, or the context might become too complex for a single supervisor to keep track of. In other words, you end up with the same problems that motivated the multi-agent architecture in the first place.
|
||||
|
||||
To address this, you can design your system _hierarchically_. For example, you can create separate, specialized teams of agents managed by individual supervisors, and a top-level supervisor to manage the teams.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Literal
|
||||
from langchain_openai import ChatOpenAI
|
||||
@@ -319,6 +654,97 @@ builder.add_edge("team_2_graph", "top_level_supervisor")
|
||||
graph = builder.compile()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, Command, START, END } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
// define team 1 (same as the single supervisor example above)
|
||||
|
||||
const team1Supervisor = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({ goto: response.nextAgent });
|
||||
};
|
||||
|
||||
const team1Agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: "team1Supervisor",
|
||||
update: { messages: [response] }
|
||||
});
|
||||
};
|
||||
|
||||
const team1Agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: "team1Supervisor",
|
||||
update: { messages: [response] }
|
||||
});
|
||||
};
|
||||
|
||||
const team1Builder = new StateGraph(MessagesZodState)
|
||||
.addNode("team1Supervisor", team1Supervisor, {
|
||||
ends: ["team1Agent1", "team1Agent2", END]
|
||||
})
|
||||
.addNode("team1Agent1", team1Agent1, {
|
||||
ends: ["team1Supervisor"]
|
||||
})
|
||||
.addNode("team1Agent2", team1Agent2, {
|
||||
ends: ["team1Supervisor"]
|
||||
})
|
||||
.addEdge(START, "team1Supervisor");
|
||||
const team1Graph = team1Builder.compile();
|
||||
|
||||
// define team 2 (same as the single supervisor example above)
|
||||
const team2Supervisor = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// ...
|
||||
};
|
||||
|
||||
const team2Agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// ...
|
||||
};
|
||||
|
||||
const team2Agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// ...
|
||||
};
|
||||
|
||||
const team2Builder = new StateGraph(MessagesZodState);
|
||||
// ... build team2Graph
|
||||
const team2Graph = team2Builder.compile();
|
||||
|
||||
// define top-level supervisor
|
||||
|
||||
const topLevelSupervisor = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// you can pass relevant parts of the state to the LLM (e.g., state.messages)
|
||||
// to determine which team 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_team" field)
|
||||
const response = await model.invoke(...);
|
||||
// route to one of the teams or exit based on the supervisor's decision
|
||||
// if the supervisor returns "__end__", the graph will finish execution
|
||||
return new Command({ goto: response.nextTeam });
|
||||
};
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
.addNode("topLevelSupervisor", topLevelSupervisor, {
|
||||
ends: ["team1Graph", "team2Graph", END]
|
||||
})
|
||||
.addNode("team1Graph", team1Graph)
|
||||
.addNode("team2Graph", team2Graph)
|
||||
.addEdge(START, "topLevelSupervisor")
|
||||
.addEdge("team1Graph", "topLevelSupervisor")
|
||||
.addEdge("team2Graph", "topLevelSupervisor");
|
||||
|
||||
const graph = builder.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Custom multi-agent workflow
|
||||
|
||||
In this architecture we add individual agents as graph nodes and define the order in which agents are called ahead of time, in a custom workflow. In LangGraph the workflow can be defined in two ways:
|
||||
@@ -327,6 +753,8 @@ In this architecture we add individual agents as graph nodes and define the orde
|
||||
|
||||
- **Dynamic control flow (Command)**: 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). A special case of this is a [supervisor tool-calling](#supervisor-tool-calling) architecture. In that case, the tool-calling LLM powering the supervisor agent will make decisions about the order in which the tools (agents) are being called.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
@@ -349,6 +777,37 @@ builder.add_edge(START, "agent_1")
|
||||
builder.add_edge("agent_1", "agent_2")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
const agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return { messages: [response] };
|
||||
};
|
||||
|
||||
const agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return { messages: [response] };
|
||||
};
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
.addNode("agent1", agent1)
|
||||
.addNode("agent2", agent2)
|
||||
// define the flow explicitly
|
||||
.addEdge(START, "agent1")
|
||||
.addEdge("agent1", "agent2");
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Communication and state management
|
||||
|
||||
The most important thing when building multi-agent systems is figuring out how the agents communicate.
|
||||
@@ -390,12 +849,27 @@ It can be helpful to indicate which agent a particular AI message is from, espec
|
||||
|
||||
### Representing handoffs in message history
|
||||
|
||||
:::python
|
||||
Handoffs are typically done via the LLM calling a dedicated [handoff tool](#handoffs-as-tools). This is represented as an [AI message](https://python.langchain.com/docs/concepts/messages/#aimessage) with tool calls that is passed to the next agent (LLM). Most LLM providers don't support receiving AI messages with tool calls **without** corresponding tool messages.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Handoffs are typically done via the LLM calling a dedicated [handoff tool](#handoffs-as-tools). This is represented as an [AI message](https://js.langchain.com/docs/concepts/messages/#aimessage) with tool calls that is passed to the next agent (LLM). Most LLM providers don't support receiving AI messages with tool calls **without** corresponding tool messages.
|
||||
:::
|
||||
|
||||
You therefore have two options:
|
||||
|
||||
:::python
|
||||
|
||||
1. Add an extra [tool message](https://python.langchain.com/docs/concepts/messages/#toolmessage) to the message list, e.g., "Successfully transferred to agent X"
|
||||
2. Remove the AI message with the tool calls
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. Add an extra [tool message](https://js.langchain.com/docs/concepts/messages/#toolmessage) to the message list, e.g., "Successfully transferred to agent X"
|
||||
2. Remove the AI message with the tool calls
|
||||
:::
|
||||
|
||||
In practice, we see that most developers opt for option (1).
|
||||
|
||||
@@ -403,16 +877,25 @@ In practice, we see that most developers opt for option (1).
|
||||
|
||||
A common practice is to have multiple agents communicating on a shared message list, but only [adding their final messages to the list](#sharing-only-final-results). This means that any intermediate messages (e.g., tool calls) are not saved in this list.
|
||||
|
||||
What if you __do__ want to save these messages so that if this particular subagent is invoked in the future you can pass those back in?
|
||||
What if you **do** want to save these messages so that if this particular subagent is invoked in the future you can pass those back in?
|
||||
|
||||
There are two high-level approaches to achieve that:
|
||||
|
||||
:::python
|
||||
|
||||
1. Store these messages in the shared message list, but filter the list before passing it to the subagent LLM. For example, you can choose to filter out all tool calls from **other** agents.
|
||||
2. Store a separate message list for each agent (e.g., `alice_messages`) in the subagent's graph state. This would be their "view" of what the message history looks like.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. Store these messages in the shared message list, but filter the list before passing it to the subagent LLM. For example, you can choose to filter out all tool calls from **other** agents.
|
||||
2. Store a separate message list for each agent (e.g., `aliceMessages`) in the subagent's graph state. This would be their "view" of what the message history looks like.
|
||||
:::
|
||||
|
||||
### Using different state schemas
|
||||
|
||||
An agent might need to have a different state schema from the rest of the agents. For example, a search agent might only need to keep track of queries and retrieved documents. There are two ways to achieve this in LangGraph:
|
||||
|
||||
- Define [subgraph](./subgraphs.md) agents with a separate state schema. If there are no shared state keys (channels) between the subgraph and the parent graph, it’s important to [add input / output transformations](../how-tos/subgraph.md#different-state-schemas) so that the parent graph knows how to communicate with the subgraphs.
|
||||
- Define agent node functions with a [private input state schema](../how-tos/graph-api.md/#pass-private-state-between-nodes) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent.
|
||||
- Define [subgraph](./subgraphs.md) agents with a separate state schema. If there are no shared state keys (channels) between the subgraph and the parent graph, it's important to [add input / output transformations](../how-tos/subgraph.ipynb#different-state-schemas) so that the parent graph knows how to communicate with the subgraphs.
|
||||
- Define agent node functions with a [private input state schema](../how-tos/graph-api.ipynb#pass-private-state-between-nodes) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent.
|
||||
|
||||
Reference in New Issue
Block a user