跳转至

构建多智能代理系统

单个智能代理如果需要在多个领域进行专业化处理或管理大量工具时可能会遇到困难。为了解决这个问题,你可以将智能代理拆分为更小的独立智能代理,并将它们组合成一个多智能代理系统

在多智能代理系统中,智能代理之间需要相互通信。它们通过交接来实现这一点——这是一个原语,用于描述将控制权交给哪个智能代理以及发送给该智能代理的有效负载。

本指南涵盖以下内容:

要开始构建多智能代理系统,请查看 LangGraph 预构建实现中两种最流行的多智能代理架构——supervisorswarm

交接

要在多智能代理系统中设置智能代理之间的通信,你可以使用交接——一种模式,其中一个智能代理将控制权*交接*给另一个智能代理。交接允许你指定:

  • destination(目标):要导航到的目标智能代理(例如,要跳转到的 LangGraph 节点名称)
  • payload(有效负载):要传递给该智能代理的信息(例如,状态更新)

创建交接

要实现交接,你可以从智能代理节点或工具中返回 Command 对象:

from typing import Annotated
from langchain_core.tools import tool, InjectedToolCallId
from langgraph.prebuilt import create_react_agent, InjectedState
from langgraph.graph import StateGraph, START, MessagesState
from langgraph.types import Command

def create_handoff_tool(*, agent_name: str, description: str | None = None):
    name = f"transfer_to_{agent_name}"
    description = description or f"Transfer to {agent_name}"

    @tool(name, description=description)
    def handoff_tool(
        # highlight-next-line
        state: Annotated[MessagesState, InjectedState], # (1)!
        # highlight-next-line
        tool_call_id: Annotated[str, InjectedToolCallId],
    ) -> Command:
        tool_message = {
            "role": "tool",
            "content": f"Successfully transferred to {agent_name}",
            "name": name,
            "tool_call_id": tool_call_id,
        }
        return Command(  # (2)!
            # highlight-next-line
            goto=agent_name,  # (3)!
            # highlight-next-line
            update={"messages": state["messages"] + [tool_message]},  # (4)!
            # highlight-next-line
            graph=Command.PARENT,  # (5)!
        )
    return handoff_tool
  1. 使用 @[InjectedState] 注解访问调用交接工具的智能代理的状态
  2. Command 原语允许将状态更新和节点转换指定为单个操作,使其非常适合实现交接。
  3. 要交接到的智能代理或节点的名称。
  4. 获取智能代理的消息并将它们**添加**到父级的**状态**中作为交接的一部分。下一个智能代理将看到父级状态。
  5. 向 LangGraph 指示我们需要导航到**父级**多智能代理图中的智能代理节点。

Tip

如果你想使用返回 Command 的工具,可以使用预构建的 @[create_react_agent][] / @[ToolNode][] 组件,或者实现自己的工具执行节点来收集工具返回的 Command 对象并返回它们的列表,例如:

def call_tools(state):
    ...
    commands = [tools_by_name[tool_call["name"]].invoke(tool_call) for tool_call in tool_calls]
    return commands
import { tool } from "@langchain/core/tools";
import { Command, MessagesZodState } from "@langchain/langgraph";
import { z } from "zod";

function createHandoffTool({
  agentName,
  description,
}: {
  agentName: string;
  description?: string;
}) {
  const name = `transfer_to_${agentName}`;
  const toolDescription = description || `Transfer to ${agentName}`;

  return tool(
    async (_, config) => {
      // (1)!
      const state = config.state;
      const toolCallId = config.toolCall.id;

      const toolMessage = {
        role: "tool" as const,
        content: `Successfully transferred to ${agentName}`,
        name: name,
        tool_call_id: toolCallId,
      };

      return new Command({
        // (3)!
        goto: agentName,
        // (4)!
        update: { messages: [...state.messages, toolMessage] },
        // (5)!
        graph: Command.PARENT,
      });
    },
    {
      name,
      description: toolDescription,
      schema: z.object({}),
    }
  );
}
  1. 通过 config 参数访问调用交接工具的智能代理的状态
  2. Command 原语允许将状态更新和节点转换指定为单个操作,使其非常适合实现交接。
  3. 要交接到的智能代理或节点的名称。
  4. 获取智能代理的消息并将它们**添加**到父级的**状态**中作为交接的一部分。下一个智能代理将看到父级状态。
  5. 向 LangGraph 指示我们需要导航到**父级**多智能代理图中的智能代理节点。

Tip

如果你想使用返回 Command 的工具,可以使用预构建的 @[create_react_agent][] / @[ToolNode][] 组件,或者实现自己的工具执行节点来收集工具返回的 Command 对象并返回它们的列表,例如:

const callTools = async (state) => {
  // ...
  const commands = await Promise.all(
    toolCalls.map(toolCall => toolsByName[toolCall.name].invoke(toolCall))
  );
  return commands;
};

Important

此交接实现假设:

  • 每个智能代理接收多智能代理系统中的整体消息历史(跨所有智能代理)作为其输入。如果你想对智能代理输入有更多控制,请参阅此部分
  • 每个智能代理将其内部消息历史输出到多智能代理系统的整体消息历史中。如果你想对**智能代理输出的添加方式有更多控制**,请将智能代理包装在单独的节点函数中:

=== "Python"

def call_hotel_assistant(state):
    # 返回智能代理的最终响应,
    # 排除内部独白
    response = hotel_assistant.invoke(state)
    # highlight-next-line
    return {"messages": response["messages"][-1]}

=== "JavaScript"

const callHotelAssistant = async (state) => {
  // 返回智能代理的最终响应,
  // 排除内部独白
  const response = await hotelAssistant.invoke(state);
  // highlight-next-line
  return { messages: [response.messages.at(-1)] };
};

控制智能代理输入

你可以使用 @[Send()][Send] 原语在交接期间直接向工作智能代理发送数据。例如,你可以请求调用智能代理为下一个智能代理填充任务描述:

from typing import Annotated
from langchain_core.tools import tool, InjectedToolCallId
from langgraph.prebuilt import InjectedState
from langgraph.graph import StateGraph, START, MessagesState
# highlight-next-line
from langgraph.types import Command, Send

def create_task_description_handoff_tool(
    *, agent_name: str, description: str | None = None
):
    name = f"transfer_to_{agent_name}"
    description = description or f"Ask {agent_name} for help."

    @tool(name, description=description)
    def handoff_tool(
        # 这由调用智能代理填充
        task_description: Annotated[
            str,
            "Description of what the next agent should do, including all of the relevant context.",
        ],
        # 这些参数被 LLM 忽略
        state: Annotated[MessagesState, InjectedState],
    ) -> Command:
        task_description_message = {"role": "user", "content": task_description}
        agent_input = {**state, "messages": [task_description_message]}
        return Command(
            # highlight-next-line
            goto=[Send(agent_name, agent_input)],
            graph=Command.PARENT,
        )

    return handoff_tool

你可以使用 @[Send()][Send] 原语在交接期间直接向工作智能代理发送数据。例如,你可以请求调用智能代理为下一个智能代理填充任务描述:

import { tool } from "@langchain/core/tools";
import { Command, Send, MessagesZodState } from "@langchain/langgraph";
import { z } from "zod";

function createTaskDescriptionHandoffTool({
  agentName,
  description,
}: {
  agentName: string;
  description?: string;
}) {
  const name = `transfer_to_${agentName}`;
  const toolDescription = description || `Ask ${agentName} for help.`;

  return tool(
    async (
      { taskDescription },
      config
    ) => {
      const state = config.state;

      const taskDescriptionMessage = {
        role: "user" as const,
        content: taskDescription,
      };
      const agentInput = {
        ...state,
        messages: [taskDescriptionMessage],
      };

      return new Command({
        // highlight-next-line
        goto: [new Send(agentName, agentInput)],
        graph: Command.PARENT,
      });
    },
    {
      name,
      description: toolDescription,
      schema: z.object({
        taskDescription: z
          .string()
          .describe(
            "Description of what the next agent should do, including all of the relevant context."
          ),
      }),
    }
  );
}

请参阅多智能代理 supervisor 示例,了解在交接中使用 @[Send()][Send] 的完整示例。

构建多智能代理系统

你可以在使用 LangGraph 构建的任何智能代理中使用交接。我们建议使用预构建的智能代理ToolNode,因为它们原生支持返回 Command 的交接工具。以下是如何使用交接实现旅行预订多智能代理系统的示例:

from langgraph.prebuilt import create_react_agent
from langgraph.graph import StateGraph, START, MessagesState

def create_handoff_tool(*, agent_name: str, description: str | None = None):
    # 与上面相同的实现
    ...
    return Command(...)

# 交接
transfer_to_hotel_assistant = create_handoff_tool(agent_name="hotel_assistant")
transfer_to_flight_assistant = create_handoff_tool(agent_name="flight_assistant")

# 定义智能代理
flight_assistant = create_react_agent(
    model="anthropic:claude-3-5-sonnet-latest",
    # highlight-next-line
    tools=[..., transfer_to_hotel_assistant],
    # highlight-next-line
    name="flight_assistant"
)
hotel_assistant = create_react_agent(
    model="anthropic:claude-3-5-sonnet-latest",
    # highlight-next-line
    tools=[..., transfer_to_flight_assistant],
    # highlight-next-line
    name="hotel_assistant"
)

# 定义多智能代理图
multi_agent_graph = (
    StateGraph(MessagesState)
    # highlight-next-line
    .add_node(flight_assistant)
    # highlight-next-line
    .add_node(hotel_assistant)
    .add_edge(START, "flight_assistant")
    .compile()
)
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { StateGraph, START, MessagesZodState } from "@langchain/langgraph";
import { z } from "zod";

function createHandoffTool({
  agentName,
  description,
}: {
  agentName: string;
  description?: string;
}) {
  // 与上面相同的实现
  // ...
  return new Command(/* ... */);
}

// 交接
const transferToHotelAssistant = createHandoffTool({
  agentName: "hotel_assistant",
});
const transferToFlightAssistant = createHandoffTool({
  agentName: "flight_assistant",
});

// 定义智能代理
const flightAssistant = createReactAgent({
  llm: model,
  // highlight-next-line
  tools: [/* ... */, transferToHotelAssistant],
  // highlight-next-line
  name: "flight_assistant",
});

const hotelAssistant = createReactAgent({
  llm: model,
  // highlight-next-line
  tools: [/* ... */, transferToFlightAssistant],
  // highlight-next-line
  name: "hotel_assistant",
});

// 定义多智能代理图
const multiAgentGraph = new StateGraph(MessagesZodState)
  // highlight-next-line
  .addNode("flight_assistant", flightAssistant)
  // highlight-next-line
  .addNode("hotel_assistant", hotelAssistant)
  .addEdge(START, "flight_assistant")
  .compile();
完整示例:旅行预订多智能代理系统
from typing import Annotated
from langchain_core.messages import convert_to_messages
from langchain_core.tools import tool, InjectedToolCallId
from langgraph.prebuilt import create_react_agent, InjectedState
from langgraph.graph import StateGraph, START, MessagesState
from langgraph.types import Command

# 我们将使用 `pretty_print_messages` 辅助函数来美化渲染流式智能代理输出

def pretty_print_message(message, indent=False):
    pretty_message = message.pretty_repr(html=True)
    if not indent:
        print(pretty_message)
        return

    indented = "\n".join("\t" + c for c in pretty_message.split("\n"))
    print(indented)


def pretty_print_messages(update, last_message=False):
    is_subgraph = False
    if isinstance(update, tuple):
        ns, update = update
        # 在打印输出中跳过父图更新
        if len(ns) == 0:
            return

        graph_id = ns[-1].split(":")[0]
        print(f"Update from subgraph {graph_id}:")
        print("\n")
        is_subgraph = True

    for node_name, node_update in update.items():
        update_label = f"Update from node {node_name}:"
        if is_subgraph:
            update_label = "\t" + update_label

        print(update_label)
        print("\n")

        messages = convert_to_messages(node_update["messages"])
        if last_message:
            messages = messages[-1:]

        for m in messages:
            pretty_print_message(m, indent=is_subgraph)
        print("\n")


def create_handoff_tool(*, agent_name: str, description: str | None = None):
    name = f"transfer_to_{agent_name}"
    description = description or f"Transfer to {agent_name}"

    @tool(name, description=description)
    def handoff_tool(
        # highlight-next-line
        state: Annotated[MessagesState, InjectedState], # (1)!
        # highlight-next-line
        tool_call_id: Annotated[str, InjectedToolCallId],
    ) -> Command:
        tool_message = {
            "role": "tool",
            "content": f"Successfully transferred to {agent_name}",
            "name": name,
            "tool_call_id": tool_call_id,
        }
        return Command(  # (2)!
            # highlight-next-line
            goto=agent_name,  # (3)!
            # highlight-next-line
            update={"messages": state["messages"] + [tool_message]},  # (4)!
            # highlight-next-line
            graph=Command.PARENT,  # (5)!
        )
    return handoff_tool

# 交接
transfer_to_hotel_assistant = create_handoff_tool(
    agent_name="hotel_assistant",
    description="Transfer user to the hotel-booking assistant.",
)
transfer_to_flight_assistant = create_handoff_tool(
    agent_name="flight_assistant",
    description="Transfer user to the flight-booking assistant.",
)

# 简单的智能代理工具
def book_hotel(hotel_name: str):
    """预订酒店"""
    return f"Successfully booked a stay at {hotel_name}."

def book_flight(from_airport: str, to_airport: str):
    """预订航班"""
    return f"Successfully booked a flight from {from_airport} to {to_airport}."

# 定义智能代理
flight_assistant = create_react_agent(
    model="anthropic:claude-3-5-sonnet-latest",
    # highlight-next-line
    tools=[book_flight, transfer_to_hotel_assistant],
    prompt="You are a flight booking assistant",
    # highlight-next-line
    name="flight_assistant"
)
hotel_assistant = create_react_agent(
    model="anthropic:claude-3-5-sonnet-latest",
    # highlight-next-line
    tools=[book_hotel, transfer_to_flight_assistant],
    prompt="You are a hotel booking assistant",
    # highlight-next-line
    name="hotel_assistant"
)

# 定义多智能代理图
multi_agent_graph = (
    StateGraph(MessagesState)
    .add_node(flight_assistant)
    .add_node(hotel_assistant)
    .add_edge(START, "flight_assistant")
    .compile()
)

# 运行多智能代理图
for chunk in multi_agent_graph.stream(
    {
        "messages": [
            {
                "role": "user",
                "content": "book a flight from BOS to JFK and a stay at McKittrick Hotel"
            }
        ]
    },
    # highlight-next-line
    subgraphs=True
):
    pretty_print_messages(chunk)
  1. 访问智能代理的状态
  2. Command 原语允许将状态更新和节点转换指定为单个操作,使其非常适合实现交接。
  3. 要交接到的智能代理或节点的名称。
  4. 获取智能代理的消息并将它们**添加**到父级的**状态**中作为交接的一部分。下一个智能代理将看到父级状态。
  5. 向 LangGraph 指示我们需要导航到**父级**多智能代理图中的智能代理节点。
import { tool } from "@langchain/core/tools";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { StateGraph, START, MessagesZodState, Command } from "@langchain/langgraph";
import { ChatAnthropic } from "@langchain/anthropic";
import { isBaseMessage } from "@langchain/core/messages";
import { z } from "zod";

// 我们将使用辅助函数来美化渲染流式智能代理输出
const prettyPrintMessages = (update: Record<string, any>) => {
  // 处理带命名空间的元组情况
  if (Array.isArray(update)) {
    const [ns, updateData] = update;
    // 在打印输出中跳过父图更新
    if (ns.length === 0) {
      return;
    }

    const graphId = ns[ns.length - 1].split(":")[0];
    console.log(`Update from subgraph ${graphId}:\n`);
    update = updateData;
  }

  for (const [nodeName, updateValue] of Object.entries(update)) {
    console.log(`Update from node ${nodeName}:\n`);

    const messages = updateValue.messages || [];
    for (const message of messages) {
      if (isBaseMessage(message)) {
        const textContent =
          typeof message.content === "string"
            ? message.content
            : JSON.stringify(message.content);
        console.log(`${message.getType()}: ${textContent}`);
      }
    }
    console.log("\n");
  }
};

function createHandoffTool({
  agentName,
  description,
}: {
  agentName: string;
  description?: string;
}) {
  const name = `transfer_to_${agentName}`;
  const toolDescription = description || `Transfer to ${agentName}`;

  return tool(
    async (_, config) => {
      // highlight-next-line
      const state = config.state; // (1)!
      const toolCallId = config.toolCall.id;

      const toolMessage = {
        role: "tool" as const,
        content: `Successfully transferred to ${agentName}`,
        name: name,
        tool_call_id: toolCallId,
      };

      return new Command({
        // highlight-next-line
        goto: agentName, // (3)!
        // highlight-next-line
        update: { messages: [...state.messages, toolMessage] }, // (4)!
        // highlight-next-line
        graph: Command.PARENT, // (5)!
      });
    },
    {
      name,
      description: toolDescription,
      schema: z.object({}),
    }
  );
}

// 交接
const transferToHotelAssistant = createHandoffTool({
  agentName: "hotel_assistant",
  description: "Transfer user to the hotel-booking assistant.",
});

const transferToFlightAssistant = createHandoffTool({
  agentName: "flight_assistant",
  description: "Transfer user to the flight-booking assistant.",
});

// 简单的智能代理工具
const bookHotel = tool(
  async ({ hotelName }) => {
    return `Successfully booked a stay at ${hotelName}.`;
  },
  {
    name: "book_hotel",
    description: "Book a hotel",
    schema: z.object({
      hotelName: z.string(),
    }),
  }
);

const bookFlight = tool(
  async ({ fromAirport, toAirport }) => {
    return `Successfully booked a flight from ${fromAirport} to ${toAirport}.`;
  },
  {
    name: "book_flight",
    description: "Book a flight",
    schema: z.object({
      fromAirport: z.string(),
      toAirport: z.string(),
    }),
  }
);

const model = new ChatAnthropic({
  model: "claude-3-5-sonnet-latest",
});

// 定义智能代理
const flightAssistant = createReactAgent({
  llm: model,
  // highlight-next-line
  tools: [bookFlight, transferToHotelAssistant],
  prompt: "You are a flight booking assistant",
  // highlight-next-line
  name: "flight_assistant",
});

const hotelAssistant = createReactAgent({
  llm: model,
  // highlight-next-line
  tools: [bookHotel, transferToFlightAssistant],
  prompt: "You are a hotel booking assistant",
  // highlight-next-line
  name: "hotel_assistant",
});

// 定义多智能代理图
const multiAgentGraph = new StateGraph(MessagesZodState)
  .addNode("flight_assistant", flightAssistant)
  .addNode("hotel_assistant", hotelAssistant)
  .addEdge(START, "flight_assistant")
  .compile();

// 运行多智能代理图
const stream = await multiAgentGraph.stream(
  {
    messages: [
      {
        role: "user",
        content: "book a flight from BOS to JFK and a stay at McKittrick Hotel",
      },
    ],
  },
  // highlight-next-line
  { subgraphs: true }
);

for await (const chunk of stream) {
  prettyPrintMessages(chunk);
}
  1. 访问智能代理的状态
  2. Command 原语允许将状态更新和节点转换指定为单个操作,使其非常适合实现交接。
  3. 要交接到的智能代理或节点的名称。
  4. 获取智能代理的消息并将它们**添加**到父级的**状态**中作为交接的一部分。下一个智能代理将看到父级状态。
  5. 向 LangGraph 指示我们需要导航到**父级**多智能代理图中的智能代理节点。

多轮对话

用户可能希望与一个或多个智能代理进行*多轮对话*。要构建一个能够处理此场景的系统,你可以创建一个使用 @interrupt 来收集用户输入并路由回**活动**智能代理的节点。

然后,智能代理可以作为图中的节点来实现,执行智能代理步骤并确定下一个操作:

  1. **等待用户输入**以继续对话,或
  2. 通过交接路由到另一个智能代理(或返回自身,例如在循环中)
def human(state) -> Command[Literal["agent", "another_agent"]]:
    """用于收集用户输入的节点。"""
    user_input = interrupt(value="Ready for user input.")

    # 确定活动智能代理。
    active_agent = ...

    ...
    return Command(
        update={
            "messages": [{
                "role": "human",
                "content": user_input,
            }]
        },
        goto=active_agent
    )

def agent(state) -> Command[Literal["agent", "another_agent", "human"]]:
    # 路由/停止的条件可以是任何东西,例如 LLM 工具调用/结构化输出等。
    goto = get_next_agent(...)  # 'agent' / 'another_agent'
    if goto:
        return Command(goto=goto, update={"my_state_key": "my_state_value"})
    else:
        return Command(goto="human") # 跳转到 human 节点
import { interrupt, Command } from "@langchain/langgraph";

function human(state: MessagesState): Command {
  const userInput: string = interrupt("Ready for user input.");

  // 确定活动智能代理
  const activeAgent = /* ... */;

  return new Command({
    update: {
      messages: [{
        role: "human",
        content: userInput,
      }]
    },
    goto: activeAgent,
  });
}

function agent(state: MessagesState): Command {
  // 路由/停止的条件可以是任何东西,例如 LLM 工具调用/结构化输出等。
  const goto = getNextAgent(/* ... */); // 'agent' / 'anotherAgent'

  if (goto) {
    return new Command({
      goto,
      update: { myStateKey: "myStateValue" }
    });
  }

  return new Command({ goto: "human" });
}
完整示例:旅行推荐多智能代理系统

在此示例中,我们将构建一个可以通过交接相互通信的旅行助手智能代理团队。

我们将创建 2 个智能代理:

  • travel_advisor:可以帮助推荐旅行目的地。可以向 hotel_advisor 寻求帮助。
  • hotel_advisor:可以帮助推荐酒店。可以向 travel_advisor 寻求帮助。
from langchain_anthropic import ChatAnthropic
from langgraph.graph import MessagesState, StateGraph, START
from langgraph.prebuilt import create_react_agent, InjectedState
from langgraph.types import Command, interrupt
from langgraph.checkpoint.memory import InMemorySaver


model = ChatAnthropic(model="claude-3-5-sonnet-latest")

class MultiAgentState(MessagesState):
    last_active_agent: str


# 定义旅行顾问工具和 ReAct 智能代理
travel_advisor_tools = [
    get_travel_recommendations,
    make_handoff_tool(agent_name="hotel_advisor"),
]
travel_advisor = create_react_agent(
    model,
    travel_advisor_tools,
    prompt=(
        "You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). "
        "If you need hotel recommendations, ask 'hotel_advisor' for help. "
        "You MUST include human-readable response before transferring to another agent."
    ),
)


def call_travel_advisor(
    state: MultiAgentState,
) -> Command[Literal["hotel_advisor", "human"]]:
    # 你还可以添加额外的逻辑,例如更改智能代理的输入/输出等。
    # 注意:我们使用状态中的完整消息历史来调用 ReAct 智能代理
    response = travel_advisor.invoke(state)
    update = {**response, "last_active_agent": "travel_advisor"}
    return Command(update=update, goto="human")


# 定义酒店顾问工具和 ReAct 智能代理
hotel_advisor_tools = [
    get_hotel_recommendations,
    make_handoff_tool(agent_name="travel_advisor"),
]
hotel_advisor = create_react_agent(
    model,
    hotel_advisor_tools,
    prompt=(
        "You are a hotel expert that can provide hotel recommendations for a given destination. "
        "If you need help picking travel destinations, ask 'travel_advisor' for help."
        "You MUST include human-readable response before transferring to another agent."
    ),
)


def call_hotel_advisor(
    state: MultiAgentState,
) -> Command[Literal["travel_advisor", "human"]]:
    response = hotel_advisor.invoke(state)
    update = {**response, "last_active_agent": "hotel_advisor"}
    return Command(update=update, goto="human")


def human_node(
    state: MultiAgentState, config
) -> Command[Literal["hotel_advisor", "travel_advisor", "human"]]:
    """用于收集用户输入的节点。"""

    user_input = interrupt(value="Ready for user input.")
    active_agent = state["last_active_agent"]

    return Command(
        update={
            "messages": [
                {
                    "role": "human",
                    "content": user_input,
                }
            ]
        },
        goto=active_agent,
    )


builder = StateGraph(MultiAgentState)
builder.add_node("travel_advisor", call_travel_advisor)
builder.add_node("hotel_advisor", call_hotel_advisor)

# 这添加了一个收集用户输入的节点,
# 它将路由回活动智能代理。
builder.add_node("human", human_node)

# 我们将始终从通用旅行顾问开始。
builder.add_edge(START, "travel_advisor")


checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)

让我们用这个应用程序测试多轮对话。

import uuid

thread_config = {"configurable": {"thread_id": str(uuid.uuid4())}}

inputs = [
    # 第 1 轮对话,
    {
        "messages": [
            {"role": "user", "content": "i wanna go somewhere warm in the caribbean"}
        ]
    },
    # 由于我们使用的是 `interrupt`,我们需要使用 Command 原语来恢复。
    # 第 2 轮对话,
    Command(
        resume="could you recommend a nice hotel in one of the areas and tell me which area it is."
    ),
    # 第 3 轮对话,
    Command(
        resume="i like the first one. could you recommend something to do near the hotel?"
    ),
]

for idx, user_input in enumerate(inputs):
    print()
    print(f"--- Conversation Turn {idx + 1} ---")
    print()
    print(f"User: {user_input}")
    print()
    for update in graph.stream(
        user_input,
        config=thread_config,
        stream_mode="updates",
    ):
        for node_id, value in update.items():
            if isinstance(value, dict) and value.get("messages", []):
                last_message = value["messages"][-1]
                if isinstance(last_message, dict) or last_message.type != "ai":
                    continue
                print(f"{node_id}: {last_message.content}")
--- Conversation Turn 1 ---

User: {'messages': [{'role': 'user', 'content': 'i wanna go somewhere warm in the caribbean'}]}

travel_advisor: Based on the recommendations, Aruba would be an excellent choice for your Caribbean getaway! Aruba is known as "One Happy Island" and offers:
- Year-round warm weather with consistent temperatures around 82°F (28°C)
- Beautiful white sand beaches like Eagle Beach and Palm Beach
- Clear turquoise waters perfect for swimming and snorkeling
- Minimal rainfall and location outside the hurricane belt
- A blend of Caribbean and Dutch culture
- Great dining options and nightlife
- Various water sports and activities

Would you like me to get some specific hotel recommendations in Aruba for your stay? I can transfer you to our hotel advisor who can help with accommodations.

--- Conversation Turn 2 ---

User: Command(resume='could you recommend a nice hotel in one of the areas and tell me which area it is.')

hotel_advisor: Based on the recommendations, I can suggest two excellent options:

1. The Ritz-Carlton, Aruba - Located in Palm Beach
- This luxury resort is situated in the vibrant Palm Beach area
- Known for its exceptional service and amenities
- Perfect if you want to be close to dining, shopping, and entertainment
- Features multiple restaurants, a casino, and a world-class spa
- Located on a pristine stretch of Palm Beach

2. Bucuti & Tara Beach Resort - Located in Eagle Beach
- An adults-only boutique resort on Eagle Beach
- Known for being more intimate and peaceful
- Award-winning for its sustainability practices
- Perfect for a romantic getaway or peaceful vacation
- Located on one of the most beautiful beaches in the Caribbean

Would you like more specific information about either of these properties or their locations?

--- Conversation Turn 3 ---

User: Command(resume='i like the first one. could you recommend something to do near the hotel?')

travel_advisor: Near the Ritz-Carlton in Palm Beach, here are some highly recommended activities:

1. Visit the Palm Beach Plaza Mall - Just a short walk from the hotel, featuring shopping, dining, and entertainment
2. Try your luck at the Stellaris Casino - It's right in the Ritz-Carlton
3. Take a sunset sailing cruise - Many depart from the nearby pier
4. Visit the California Lighthouse - A scenic landmark just north of Palm Beach
5. Enjoy water sports at Palm Beach:
   - Jet skiing
   - Parasailing
   - Snorkeling
   - Stand-up paddleboarding

Would you like more specific information about any of these activities or would you like to know about other options in the area?
import { ChatAnthropic } from "@langchain/anthropic";
import { StateGraph, START, MessagesZodState, Command, interrupt, MemorySaver } from "@langchain/langgraph";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { tool } from "@langchain/core/tools";
import { z } from "zod";

const model = new ChatAnthropic({ model: "claude-3-5-sonnet-latest" });

const MultiAgentState = MessagesZodState.extend({
  lastActiveAgent: z.string().optional(),
});

// 定义旅行顾问工具
const getTravelRecommendations = tool(
  async () => {
    // 占位符实现
    return "Based on current trends, I recommend visiting Japan, Portugal, or New Zealand.";
  },
  {
    name: "get_travel_recommendations",
    description: "Get current travel destination recommendations",
    schema: z.object({}),
  }
);

const makeHandoffTool = (agentName: string) => {
  return tool(
    async (_, config) => {
      const state = config.state;
      const toolCallId = config.toolCall.id;

      const toolMessage = {
        role: "tool" as const,
        content: `Successfully transferred to ${agentName}`,
        name: `transfer_to_${agentName}`,
        tool_call_id: toolCallId,
      };

      return new Command({
        goto: agentName,
        update: { messages: [...state.messages, toolMessage] },
        graph: Command.PARENT,
      });
    },
    {
      name: `transfer_to_${agentName}`,
      description: `Transfer to ${agentName}`,
      schema: z.object({}),
    }
  );
};

const travelAdvisorTools = [
  getTravelRecommendations,
  makeHandoffTool("hotel_advisor"),
];

const travelAdvisor = createReactAgent({
  llm: model,
  tools: travelAdvisorTools,
  prompt: [
    "You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). ",
    "If you need hotel recommendations, ask 'hotel_advisor' for help. ",
    "You MUST include human-readable response before transferring to another agent."
  ].join("")
});

const callTravelAdvisor = async (
  state: z.infer<typeof MultiAgentState>
): Promise<Command> => {
  const response = await travelAdvisor.invoke(state);
  const update = { ...response, lastActiveAgent: "travel_advisor" };
  return new Command({ update, goto: "human" });
};

// 定义酒店顾问工具
const getHotelRecommendations = tool(
  async () => {
    // 占位符实现
    return "I recommend the Ritz-Carlton for luxury stays or boutique hotels for unique experiences.";
  },
  {
    name: "get_hotel_recommendations",
    description: "Get hotel recommendations for destinations",
    schema: z.object({}),
  }
);

const hotelAdvisorTools = [
  getHotelRecommendations,
  makeHandoffTool("travel_advisor"),
];

const hotelAdvisor = createReactAgent({
  llm: model,
  tools: hotelAdvisorTools,
  prompt: [
    "You are a hotel expert that can provide hotel recommendations for a given destination. ",
    "If you need help picking travel destinations, ask 'travel_advisor' for help.",
    "You MUST include human-readable response before transferring to another agent."
  ].join("")
});

const callHotelAdvisor = async (
  state: z.infer<typeof MultiAgentState>
): Promise<Command> => {
  const response = await hotelAdvisor.invoke(state);
  const update = { ...response, lastActiveAgent: "hotel_advisor" };
  return new Command({ update, goto: "human" });
};

const humanNode = async (
  state: z.infer<typeof MultiAgentState>
): Promise<Command> => {
  const userInput: string = interrupt("Ready for user input.");
  const activeAgent = state.lastActiveAgent || "travel_advisor";

  return new Command({
    update: {
      messages: [
        {
          role: "human",
          content: userInput,
        }
      ]
    },
    goto: activeAgent,
  });
};

const builder = new StateGraph(MultiAgentState)
  .addNode("travel_advisor", callTravelAdvisor)
  .addNode("hotel_advisor", callHotelAdvisor)
  .addNode("human", humanNode)
  .addEdge(START, "travel_advisor");

const checkpointer = new MemorySaver();
const graph = builder.compile({ checkpointer });

让我们用这个应用程序测试多轮对话。

import { v4 as uuidv4 } from "uuid";
import { Command } from "@langchain/langgraph";

const threadConfig = { configurable: { thread_id: uuidv4() } };

const inputs = [
  // 第 1 轮对话
  {
    messages: [
      { role: "user", content: "i wanna go somewhere warm in the caribbean" }
    ]
  },
  // 由于我们使用的是 `interrupt`,我们需要使用 Command 原语来恢复。
  // 第 2 轮对话
  new Command({
    resume: "could you recommend a nice hotel in one of the areas and tell me which area it is."
  }),
  // 第 3 轮对话
  new Command({
    resume: "i like the first one. could you recommend something to do near the hotel?"
  }),
];

for (const [idx, userInput] of inputs.entries()) {
  console.log();
  console.log(`--- Conversation Turn ${idx + 1} ---`);
  console.log();
  console.log(`User: ${JSON.stringify(userInput)}`);
  console.log();

  for await (const update of await graph.stream(
    userInput,
    { ...threadConfig, streamMode: "updates" }
  )) {
    for (const [nodeId, value] of Object.entries(update)) {
      if (value?.messages?.length) {
        const lastMessage = value.messages.at(-1);
        if (lastMessage?.getType?.() === "ai") {
          console.log(`${nodeId}: ${lastMessage.content}`);
        }
      }
    }
  }
}
--- Conversation Turn 1 ---

User: {"messages":[{"role":"user","content":"i wanna go somewhere warm in the caribbean"}]}

travel_advisor: Based on the recommendations, Aruba would be an excellent choice for your Caribbean getaway! Aruba is known as "One Happy Island" and offers:
- Year-round warm weather with consistent temperatures around 82°F (28°C)
- Beautiful white sand beaches like Eagle Beach and Palm Beach
- Clear turquoise waters perfect for swimming and snorkeling
- Minimal rainfall and location outside the hurricane belt
- A blend of Caribbean and Dutch culture
- Great dining options and nightlife
- Various water sports and activities

Would you like me to get some specific hotel recommendations in Aruba for your stay? I can transfer you to our hotel advisor who can help with accommodations.

--- Conversation Turn 2 ---

User: Command { resume: 'could you recommend a nice hotel in one of the areas and tell me which area it is.' }

hotel_advisor: Based on the recommendations, I can suggest two excellent options:

1. The Ritz-Carlton, Aruba - Located in Palm Beach
- This luxury resort is situated in the vibrant Palm Beach area
- Known for its exceptional service and amenities
- Perfect if you want to be close to dining, shopping, and entertainment
- Features multiple restaurants, a casino, and a world-class spa
- Located on a pristine stretch of Palm Beach

2. Bucuti & Tara Beach Resort - Located in Eagle Beach
- An adults-only boutique resort on Eagle Beach
- Known for being more intimate and peaceful
- Award-winning for its sustainability practices
- Perfect for a romantic getaway or peaceful vacation
- Located on one of the most beautiful beaches in the Caribbean

Would you like more specific information about either of these properties or their locations?

--- Conversation Turn 3 ---

User: Command { resume: 'i like the first one. could you recommend something to do near the hotel?' }

travel_advisor: Near the Ritz-Carlton in Palm Beach, here are some highly recommended activities:

1. Visit the Palm Beach Plaza Mall - Just a short walk from the hotel, featuring shopping, dining, and entertainment
2. Try your luck at the Stellaris Casino - It's right in the Ritz-Carlton
3. Take a sunset sailing cruise - Many depart from the nearby pier
4. Visit the California Lighthouse - A scenic landmark just north of Palm Beach
5. Enjoy water sports at Palm Beach:
   - Jet skiing
   - Parasailing
   - Snorkeling
   - Stand-up paddleboarding

Would you like more specific information about any of these activities or would you like to know about other options in the area?

预构建实现

LangGraph 提供了两种最流行的多智能代理架构的预构建实现:

  • supervisor — 各个智能代理由中央 supervisor 智能代理协调。supervisor 控制所有通信流和任务委派,根据当前上下文和任务需求决定调用哪个智能代理。你可以使用 langgraph-supervisor 库来创建 supervisor 多智能代理系统。
  • swarm — 智能代理根据其专长动态地将控制权交给彼此。系统会记住哪个智能代理最后处于活动状态,确保在后续交互中对话从该智能代理恢复。你可以使用 langgraph-swarm 库来创建 swarm 多智能代理系统。
  • supervisor — 各个智能代理由中央 supervisor 智能代理协调。supervisor 控制所有通信流和任务委派,根据当前上下文和任务需求决定调用哪个智能代理。你可以使用 langgraph-supervisor 库来创建 supervisor 多智能代理系统。
  • swarm — 智能代理根据其专长动态地将控制权交给彼此。系统会记住哪个智能代理最后处于活动状态,确保在后续交互中对话从该智能代理恢复。你可以使用 langgraph-swarm 库来创建 swarm 多智能代理系统。