langGraph多智能体官方文档链接: https://langgraph.com.cn/agents/multi-agent.1.html#supervisor

langGraph框架平台部分: https://langgraph.com.cn/how-tos/multi_agent/index.html

可能报错: ValueError: Found AIMessages with tool_calls that do not have a corresponding ToolMessage. Here are the first few of those tool calls: [{'name': 'book_flight', 'args': {'from_airport': '北京', 'to_airport': '上海'}, 'id': 'call_e5vxi6z87ms59jbuxwrmuqko', 'type': 'tool_call'}].

Every tool call (LLM requesting to call a tool) in the message history MUST have a corresponding ToolMessage (result of a tool invocation to return to the LLM) - this is required by most LLM providers.

1. 背景介绍:

单个智能体可能难以应对需要专门处理多个领域或管理多种工具的情况。为了解决这个问题,您可以将智能体分解为更小、独立的智能体,并将它们组合成一个多智能体系统

在多智能体系统中,智能体之间需要进行通信。它们通过移交来实现这一点——这是一种描述将控制权移交给哪个智能体以及发送给该智能体的数据负载的原始操作。

两种最受欢迎的多智能体架构是:

  • 主管——单个智能体由一个中央主管智能体协调。主管控制所有通信流和任务委派,根据当前上下文和任务要求决定调用哪个智能体。
  • 群组——智能体根据其专业性动态地相互移交控制权。系统会记住哪个智能体上次处于活动状态,确保在后续交互中,对话会与该智能体恢复。

摘自: https://langgraph.com.cn/how-tos/multi_agent/index.html

2. 官方案例

2.1 多智能体群组方式

这里我将英文替换了中文,其主题逻辑是一样的

from langgraph.prebuilt import create_react_agent
from langgraph_swarm import create_swarm, create_handoff_tool


# 定义模型(可以替换为自己的模型) ==============================================
import os
from langchain_openai            import ChatOpenAI
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
load_dotenv('.env')
base_ark_url = os.getenv("api_ark_url")
api_ark_key =  os.getenv("api_ark_key")
model_llm = ChatOpenAI(
    model_name  = "deepseek-v3-250324",
    base_url    = base_ark_url,
    api_key     = api_ark_key,
)

# -------------------------------
# 需要的工具方法实现如下
import time
from loguru import logger
def book_hotel(hotel_name: str):
    """预定酒店, 必须提供酒店名称。"""
    logger.error(   f"{time.time()}---------------------------------------------预定酒店------------------------------------------")
    time.sleep(2)
    logger.warning( f"{time.time()}---------------------------------------------预定酒店结束------------------------------------------")
    return f"预定成功 双人间 {hotel_name}."

def book_flight(from_airport: str, to_airport: str):
    """预定航班,提供航班信息"""
    logger.error(  f"{time.time()}--------------------------------------------预定航班--------------------------------------------")
    time.sleep(2)
    logger.warning(f"{time.time()}--------------------------------------------预定航班结束--------------------------------------------2")
    return f"已成功预订从 {from_airport}{to_airport}的航班."

# --------------------------------------------------------------------------------

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.",
)

flight_assistant = create_react_agent(
    model=model_llm,
    tools=[book_flight, transfer_to_hotel_assistant],
    prompt="You are a flight booking assistant",
    name="flight_assistant"
)
hotel_assistant = create_react_agent(
    model=model_llm,
    tools=[book_hotel, transfer_to_flight_assistant],
    prompt="You are a hotel booking assistant",
    name="hotel_assistant"
)

swarm = create_swarm(
    agents=[flight_assistant, hotel_assistant],
    default_active_agent="flight_assistant"
).compile()

for chunk in swarm.stream(
    {
        "messages": [
            {
                "role": "user",
                "content": "预定北京到上海的航班,并预定一个上海的希尔顿酒店"
            }
        ]
    }
):
    print(chunk)
    print("\n")

会出现如下报错

ValueError: Found AIMessages with tool_calls that do not have a corresponding ToolMessage. Here are the first few of those tool calls: [{'name': 'book_flight', 'args': {'from_airport': '北京', 'to_airport': '上海'}, 'id': 'call_weskplp7uj4miiw7wmywmlce', 'type': 'tool_call'}].

Every tool call (LLM requesting to call a tool) in the message history MUST have a corresponding ToolMessage (result of a tool invocation to return to the LLM) - this is required by most LLM providers.
For troubleshooting, visit: https://python.langchain.com/docs/troubleshooting/errors/INVALID_CHAT_HISTORY
During task with name 'agent' and id 'aa637476-eef1-6948-7f79-ccf050ba8535'
During task with name 'hotel_assistant' and id 'ba6993b3-090b-c247-c7c2-d6224579ef08'

2.2 多智能体移交

多智能体交互中一个常见的模式是移交(handoffs),即一个智能体将控制权移交给另一个智能体。移交允许您指定:

  • 目的地:要导航到的目标智能体
  • 负载:要传递给该智能体的信息
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

# 定义模型(可以替换为自己的模型) ==============================================
import os
from langchain_openai            import ChatOpenAI
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
load_dotenv('.env')
base_ark_url = os.getenv("api_ark_url")
api_ark_key =  os.getenv("api_ark_key")
model_llm = ChatOpenAI(
    model_name  = "deepseek-v3-250324",
    base_url    = base_ark_url,
    api_key     = api_ark_key,
)
# -----------------------------------------

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(
        state: Annotated[MessagesState, InjectedState], 
        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(  
            goto=agent_name,  
            update={"messages": state["messages"] + [tool_message]},  
            graph=Command.PARENT,  
        )
    return handoff_tool

# Handoffs
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.",
)

# Simple agent tools
def book_hotel(hotel_name: str):
    """Book a hotel"""
    return f"Successfully booked a stay at {hotel_name}."

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

# Define agents
flight_assistant = create_react_agent(
    model=model_llm,
    tools=[book_flight, transfer_to_hotel_assistant],
    prompt="You are a flight booking assistant",
    name="flight_assistant"
)
hotel_assistant = create_react_agent(
    model=model_llm,
    tools=[book_hotel, transfer_to_flight_assistant],
    prompt="You are a hotel booking assistant",
    name="hotel_assistant"
)

# Define multi-agent graph
multi_agent_graph = (
    StateGraph(MessagesState)
    .add_node(flight_assistant)
    .add_node(hotel_assistant)
    .add_edge(START, "flight_assistant")
    .compile()
)

# Run the multi-agent graph
for chunk in multi_agent_graph.stream(
    {
        "messages": [
            {
                "role": "user",
                "content": "预定北京到上海的航班,并预定一个上海的希尔顿酒店"
            }
        ]
    },
    stream_mode="values"        # 这个官方样例没有, 为了更好的显示
):
    for msg in chunk["messages"]:
        msg.pretty_print()

2.3 问题

2.3.1 运行官方样例可能会出现上述问题

ValueError: Found AIMessages with tool_calls that do not have a corresponding ToolMessage. Here are the first few of those tool calls: [{'name': 'book_flight', 'args': {'from_airport': '北京', 'to_airport': '上海'}, 'id': 'call_e5vxi6z87ms59jbuxwrmuqko', 'type': 'tool_call'}].

Every tool call (LLM requesting to call a tool) in the message history MUST have a corresponding ToolMessage (result of a tool invocation to return to the LLM) - this is required by most LLM providers.

ValueError: Found AIMessages with tool_calls that do not have a corresponding ToolMessage. Here are the first few of those tool calls: [{'name': 'book_flight', 'args': {'from_airport': '北京', 'to_airport': '上海'}, 'id': 'call_e5vxi6z87ms59jbuxwrmuqko', 'type': 'tool_call'}].

Every tool call (LLM requesting to call a tool) in the message history MUST have a corresponding ToolMessage (result of a tool invocation to return to the LLM) - this is required by most LLM providers.
For troubleshooting, visit: https://python.langchain.com/docs/troubleshooting/errors/INVALID_CHAT_HISTORY

2.3.2 问题原因分析

2.3.2.1 将报错信息进行翻译:

ValueError:发现带有tool_calls的AIMessages没有对应的ToolMessage。以下是这些tool calls的前几个:

消息历史中的每个工具调用(大型语言模型(LLM)请求调用工具)都必须有相应的ToolMessage(工具调用返回给LLM的结果)——这是大多数LLM提供商的要求。

2.3.2.2 debug分析

我们的query是

"预定北京到上海的航班,并预定一个上海的希尔顿酒店"

报错信息大致意思是说大模型中有tool_calls但是没有正确的ToolMessages与之对应

debug 到command处 发现此时AiMessages的tool_calls里面想要调用两个方法(book_flight, trans_to_hotel_assistant),messages里面只有两个消息,一个HumanMessage, 一个AIMessage, 发现此时此时book_flightToolMessage并没有在state状态中, comand在这一步只添加了一个移交的ToolMessages, 所以会移交之后的时候少一个ToolMessage,进行下一步执行的时候就会报错

相关讨论: https://github.com/langchain-ai/langgraph/discussions/4803

2.3.2.3 为什么会出现这个问题

查看入口节点flight_assistant的agent中有两个工具[book_flight, transfer_to_hotel_assistant]

因为book_flight工具和transfer_to_hotel_assistant移交代理工具是并行执行的

理想情况下:

  • case1: 先运行book_flight得到ToolMessage 在运行transfer_to_hotel_assistant 此时state里面会有预定航班的ToolMessages在加上移交的ToolMessages就能和AIMessage中的两个tool_calls对应起来
  • case2: 或者先运行移交代理,在运行航班助手结果也会有两个ToolMessage

2.4 解决方法

在command之前判断state的状态, 如果缺少相应的ToolMessage则调用对应方法补齐history_message

修改后代码如下

from typing import Annotated
from langchain_core.tools import tool, InjectedToolCallId
from langgraph.prebuilt import create_react_agent, InjectedState, get_model_input_state
from langgraph.graph import StateGraph, START, MessagesState
from langgraph.types import Command
from langchain_core.messages import ToolMessage, AIMessage
from typing import Annotated
from langchain_core.tools import InjectedToolCallId, tool
# 定义模型 ==============================================
import os
from langchain_openai            import ChatOpenAI
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
load_dotenv('.env')
base_ark_url = os.getenv("api_ark_url")
api_ark_key =  os.getenv("api_ark_key")
model_llm = ChatOpenAI(
    # model_name  = "Qwen3-32B",
    model_name  = "deepseek-v3-250324",
    base_url    = base_ark_url,
    api_key     = api_ark_key,
)

import time
# -------------------------------
# 移交方法 闭包函数
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(
        state: Annotated[MessagesState, InjectedState], 
        tool_call_id: Annotated[str, InjectedToolCallId],
    ) -> Command:
        tool_message_dic = {
            "role": "tool",
            "content": f"转接请求到 {agent_name}, {state['messages'][0].content}",
            "name": name,
            # "name": agent_name,
            "tool_call_id": tool_call_id,
        }

        logger.warning(f"{time.time()}移交1 消息长度{len(state['messages'])}-----------------------------------------handoff_tool {agent_name}-------------------------------------")
        # 添加的代码 ---------------------------------------------------------- 补充tool_messages
        tool_message = ToolMessage(**tool_message_dic)
        tool_calls = state["messages"][-1].tool_calls
        function_messages = []
        if tool_calls:
            for tool in tool_calls:
                if tool["name"] != name:
                    function_name = tool["name"]
                    function_args = tool["args"]
                    result = eval(function_name)(function_args)     # 调用函数对应的方法 TODO 这里可以铺货异常
                    print(tool["name"])
                    function_messages.append(ToolMessage(**{
                        "content": result,
                        "name": function_name,
                        "tool_call_id": tool["id"],
                        "call_id": tool["id"],
                    }))
        logger.warning(f"{time.time()}移交2 消息长度{len(state['messages'])}-----------------------------------------handoff_tool {agent_name}-------------------------------------")
        # 结束部分
        return Command(  
            goto=agent_name,  
            update={"messages": state["messages"] + function_messages + [tool_message]},  
            graph=Command.PARENT,       # 导航到父图中的节点
        )
    return handoff_tool

# 移交
transfer_to_hotel_assistant = create_handoff_tool(
    agent_name="hotel_assistant",
    description="将用户转接至酒店预订助手。",
)
transfer_to_flight_assistant = create_handoff_tool(
    agent_name="flight_assistant",
    description="将用户转接至航班预订助手。",
)
# 工具方法 ==================================================================
from langchain_core.tools import InjectedToolCallId, tool
from loguru import logger
@tool
def book_hotel(hotel_name: str):
    """预定酒店, 必须提供酒店名称。"""
    key_time = time.time()
    logger.error(  f"{key_time} 开始---------------------------------------------book hotel------------------------------------------")
    time.sleep(1)
    logger.warning(f"{key_time} 结束---------------------------------------------book hotel------------------------------------------")
    return f"预定成功 双人间 {hotel_name}."

@tool
def book_flight(from_airport: str, to_airport: str):
    """预定航班,提供航班信息"""
    key_time = time.time()
    logger.error(  f"{key_time}开始--------------------------------------------book_flight--------------------------------------------")
    time.sleep(1)
    logger.warning(f"{key_time}结束--------------------------------------------book_flight--------------------------------------------2")
    return f"已成功预订从 {from_airport}{to_airport}的航班."


# 工具方法
flight_assistant = create_react_agent(
    model=model_llm,
    tools=[book_flight, transfer_to_hotel_assistant],
    prompt = (
        "你是一个专业的航班预订助手。\n"
        "当用户需要预订航班时,请调用 book_flight 工具。\n"
        "如果收到转交消息,请仔细阅读用户请求并立即采取行动。\n"
        "不要仅仅确认转交,必须完成预订。"
        "工具要一个一个调用 不要并行调用"
    ),
    name="flight_assistant",
    version="v2"
)
hotel_assistant = create_react_agent(
    model=model_llm,
    tools=[book_hotel, transfer_to_flight_assistant],
    prompt = (
        "你是一个专业的酒店预订助手。\n"
        "当用户需要预订酒店时,请调用 book_hotel 工具。\n"
        "如果收到转交消息,请仔细阅读用户请求并立即采取行动。\n"
        "不要仅仅确认转交,必须完成预订。"
    ),
    name="hotel_assistant"
)

from typing                      import Annotated
from typing_extensions           import TypedDict
from langgraph.graph             import StateGraph, START
from langgraph.graph.message     import add_messages
class State(TypedDict):
    messages: Annotated[list, add_messages]

multi_agent_graph = (
    StateGraph(MessagesState)
    .add_node(flight_assistant)
    .add_node(hotel_assistant)
    .add_edge(START, "flight_assistant")
    .compile()
)

thread = {"configurable": {"thread_id": "1"}}
async def main():
    for step in multi_agent_graph.stream({
        "messages": [
            {
                "role": "user",
                # "content": "今晚预定一个桔子晶酒店",
                # "content": "预定一个北京到上海的航班",
                "content": "现有两个需求,今晚预定一个桔子晶酒店, 预定要给北京到上海的航班,",
            }
        ],
    },
        stream_mode="values",  # 返回每一步的完整状态
        config=thread
    ):
        for msg in step["messages"]:
            msg.pretty_print()
            if hasattr(msg, 'tool_calls') and msg.tool_calls:
                print(f"tool_calls: {msg.content} {msg.tool_calls}")

    print("+-+" * 50)
import asyncio
if __name__ == '__main__': 
    asyncio.run(main())

看结果输出如下: 同一时间点中book_flihgt还没结束运行 就是执行移交操作导致state中没有缺少book_flightToolMessage,这就是之前报错的原因,

添加的核心代码如下

        # 添加的代码 ---------------------------------------------------------- 补充tool_messages
        tool_message = ToolMessage(**tool_message_dic)
        tool_calls = state["messages"][-1].tool_calls
        function_messages = []
        if tool_calls:
            for tool in tool_calls:
                if tool["name"] != name:
                    function_name = tool["name"]
                    function_args = tool["args"]
                    result = eval(function_name)(function_args)     # 调用函数对应的方法 TODO 这里可以铺货异常
                    print(tool["name"])
                    function_messages.append(ToolMessage(**{
                        "content": result,
                        "name": function_name,
                        "tool_call_id": tool["id"],
                        "call_id": tool["id"],
                    }))
        logger.warning(f"{time.time()}移交2 消息长度{len(state['messages'])}-----------------------------------------handoff_tool {agent_name}-------------------------------------")
        # 结束部分

3. 总结

3.1 群组和移交

以为官网案例移交是自己实现create_handoff_tool, 所以我们可以修改,针对群组案例我们是通过导入的,所以我们这里只讲关于移交的修正方案。

预购建智能提示,最好是避免同时使用普通工具移交工具同时调用的清醒,否则会报错。

3.2 其他

这个可能是目前langGraph的bug,我看网上的信息比较少,

  • 如果query不涉及同时调用移交工具和普通工具的情况下,程序是可以正常运行的,
    • 例如query如下, 一次调用book_flihgt, 下一次一次是调用trans_to_hotel_assistant 工具, 因为调用途中没有缺少ToolMessages,可以通过_validate_chat_history验证.
# "content": "今晚预定一个桔子晶酒店",
# "content": "预定一个北京到上海的航班",
  • 让两个工具book_flight串行执行,在agent中加入parallel_tool_calls=False, 但是我在代码中添加之后依然报错。
flight_assistant = create_react_agent(
    # model=model_llm,
    model=model_llm.bind_tools([book_flight, transfer_to_hotel_assistant], parallel_tool_calls=False),
    tools=[book_flight, transfer_to_hotel_assistant],
    # prompt="你是一个航班助理, 如果有用想要定航班你需要调用 航班工具,如果需要定酒店请移交到transfer_to_hotel_assistant智能体",
    prompt=(
        "你是一个专业的航班预订助手。\n"
        "当用户需要预订酒店时,请调用 book_flight 工具。\n"
        "如果收到转交消息,请仔细阅读用户请求并立即采取行动。\n"
        "不要仅仅确认转交,必须完成预订。"
    ),
    name="flight_assistant"
)

相关链接: https://github.com/langchain-ai/langgraph/discussions/4803

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐