主题
多工具调用结果统一回传
当模型一次返回多个 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].id | tool_call_id |
tool_calls[0].name | name |
| 工具执行返回值 | 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、顺序执行与并行执行
多个工具调用之间没有依赖关系时,可以并行执行来减少等待时间;如果后一个工具需要使用前一个工具的结果,则必须按照依赖关系顺序执行。
无论采用顺序执行还是并行执行,都必须遵守两个规则:
- 每个工具调用都必须生成一条对应的
ToolMessage。 - 所有工具结果准备完成后,才能再次调用模型。
并行执行时,工具完成的先后顺序可能与模型返回的顺序不同。因此不能依赖列表下标配对,必须始终使用 tool_call_id 建立关联。
7、调用流程总结
多个 tool_calls 的处理可以记忆为以下四步:
- 模型返回一个包含多个工具调用的
AIMessage。 - 程序遍历所有
tool_calls,根据工具名称和参数执行工具。 - 每个工具结果生成一条
ToolMessage,并使用对应的tool_call_id。 - 将全部
ToolMessage一次性追加到messages,再调用模型生成最终回答。
一句话总结:多个 tool_calls 对应多个 ToolMessage,全部执行完后一起回传模型。