Skip to content

多工具调用结果统一回传 ​

当模型一次返回多个 tool_calls 时,表示模型希望开发者把这些工具调用都执行完成,再将所有工具结果一次性传回模型。

例如,用户同时询问杭州天气和当天新闻,模型可能一次返回如下两个工具调用:

text
tool_calls = [
    {
        "name": "get_weather",
        "args": {"city": "杭州"},
        "id": "call_weather_001"
    },
    {
        "name": "get_news",
        "args": {},
        "id": "call_news_001"
    }
]

此时,程序需要先执行 get_weather 和 get_news,分别构造两条 ToolMessage,然后将两条消息一起追加到消息列表,最后再次调用模型。

1、完整执行顺序 ​

完整流程如下:

text
模型返回多个 tool_calls
        ↓
遍历并执行所有工具调用
        ↓
为每个工具结果构造对应的 ToolMessage
        ↓
一次性将所有 ToolMessage 追加到 messages
        ↓
将完整消息列表再次传给模型
        ↓
模型生成最终回答

关键点是:模型返回几个 tool_calls,程序就需要准备几条对应的 ToolMessage。不能执行完第一个工具后马上调用模型,因为此时消息列表中缺少其他工具调用的结果。

2、每个工具消息必须正确配对 ​

AIMessage 中每个工具调用都有自己的唯一 id。构造 ToolMessage 时,必须把这个 id 原样赋值给 tool_call_id。

python
ToolMessage(
    content=str(result),
    tool_call_id=tool_call["id"],
    name=tool_call["name"]
)

对应关系如下:

AIMessage中的字段ToolMessage中的字段
tool_calls[0].idtool_call_id
tool_calls[0].namename
工具执行返回值content

如果 tool_call_id 配错,模型无法判断某条工具结果对应哪一次工具调用,后续对话可能出现工具结果错配、消息校验失败或模型无法继续生成等问题。

3、顺序执行多个工具 ​

多个工具可以依次执行。虽然工具执行顺序不影响消息的配对关系,但所有工具都执行完之前,不能再次调用模型。

python
from langchain.messages import HumanMessage, ToolMessage


def get_weather(city: str):
    """获取天气信息"""
    return f"{city}天气晴朗"


def get_news():
    """获取新闻信息"""
    return "今天暂无重要新闻"


tool_map = {
    "get_weather": get_weather,
    "get_news": get_news,
}

messages = [
    HumanMessage("请告诉我杭州的天气,并查询今天的新闻")
]

response = model_with_tools.invoke(messages)

messages.append(response)

tool_messages = []

for tool_call in response.tool_calls:
    name = tool_call["name"]
    args = tool_call["args"]
    call_id = tool_call["id"]

    result = tool_map[name](**args)

    tool_messages.append(
        ToolMessage(
            content=str(result),
            tool_call_id=call_id,
            name=name
        )
    )

messages.extend(tool_messages)

final_response = model_with_tools.invoke(messages)
print(final_response.content)

这段代码中,messages.append(response) 保存模型返回的 AIMessage,tool_messages 暂存每个工具的执行结果,messages.extend(tool_messages) 则一次性追加所有 ToolMessage。

4、使用工具对象执行 ​

如果函数使用 @tool 装饰器定义,可以通过工具对象的 invoke() 方法执行工具调用。此时,LangChain 会根据传入的工具调用信息生成对应的 ToolMessage。

python
from langchain_core.tools import tool


@tool
def get_weather(city: str):
    """获取天气信息"""
    return f"{city}天气晴朗"


@tool
def get_news():
    """获取新闻信息"""
    return "今天暂无重要新闻"


tool_map = {
    "get_weather": get_weather,
    "get_news": get_news,
}

response = model_with_tools.invoke(messages)
messages.append(response)

tool_messages = []
for tool_call in response.tool_calls:
    tool = tool_map[tool_call["name"]]
    tool_messages.append(tool.invoke(tool_call))

messages.extend(tool_messages)
final_response = model_with_tools.invoke(messages)

使用 @tool 时,tool.invoke(tool_call) 接收的参数中包含工具名称、参数和工具调用 ID,因此返回的 ToolMessage 会携带正确的关联信息。

5、不能执行一个就调用模型 ​

下面的写法存在问题:

python
for tool_call in response.tool_calls:
    result = tool_map[tool_call["name"]](**tool_call["args"])
    messages.append(
        ToolMessage(
            content=str(result),
            tool_call_id=tool_call["id"],
            name=tool_call["name"]
        )
    )

    final_response = model_with_tools.invoke(messages)

模型返回多个工具调用时,它期待下一轮消息中包含全部工具结果。如果只追加第一条 ToolMessage 就调用模型,消息序列是不完整的,可能导致以下问题:

  • 其他工具调用没有执行,用户问题只能得到部分答案。
  • 模型无法同时看到所有工具结果,最终回复内容不完整。
  • 某些模型或接口会因为缺少对应的工具结果而返回消息校验错误。

正确的方式是把模型调用放在循环之后:

python
for tool_call in response.tool_calls:
    result = tool_map[tool_call["name"]](**tool_call["args"])
    tool_messages.append(
        ToolMessage(
            content=str(result),
            tool_call_id=tool_call["id"],
            name=tool_call["name"]
        )
    )

messages.extend(tool_messages)
final_response = model_with_tools.invoke(messages)

6、顺序执行与并行执行 ​

多个工具调用之间没有依赖关系时,可以并行执行来减少等待时间;如果后一个工具需要使用前一个工具的结果,则必须按照依赖关系顺序执行。

无论采用顺序执行还是并行执行,都必须遵守两个规则:

  1. 每个工具调用都必须生成一条对应的 ToolMessage。
  2. 所有工具结果准备完成后,才能再次调用模型。

并行执行时,工具完成的先后顺序可能与模型返回的顺序不同。因此不能依赖列表下标配对,必须始终使用 tool_call_id 建立关联。

7、调用流程总结 ​

多个 tool_calls 的处理可以记忆为以下四步:

  1. 模型返回一个包含多个工具调用的 AIMessage。
  2. 程序遍历所有 tool_calls,根据工具名称和参数执行工具。
  3. 每个工具结果生成一条 ToolMessage,并使用对应的 tool_call_id。
  4. 将全部 ToolMessage 一次性追加到 messages,再调用模型生成最终回答。

一句话总结:多个 tool_calls 对应多个 ToolMessage,全部执行完后一起回传模型。