LangChain 怎样为工具调用保留中间消息
来源:17golang原创
时间:2026-10-29 02:00:40 450浏览 收藏
LangChain 的工具调用不是“模型输出一段文字、工具再返回一段文字”这么简单。要让下一次模型调用知道刚才调用了哪个工具,应用需要保留消息序列:用户消息、带有 tool_calls 的 AIMessage、带有对应 tool_call_id 的 ToolMessage,以及后续模型消息。
官方地址:https://docs.langchain.com/oss/python/langchain/messages
最实用的做法是把 agent 返回的 result["messages"] 当作中间状态保存,而不是只取最后一条 content。这样既能继续对话,也能在日志或持久化层中追踪工具调用的对应关系。
为什么中间消息不能只保存最终文本
LangChain 的消息对象同时承载角色、内容和元数据。模型决定调用工具时,工具调用信息位于 AIMessage.tool_calls;工具执行后的结果则用 ToolMessage 回传。只保存最终回答,会丢掉工具名、参数和调用标识,下一次模型就无法可靠地把结果对应回原调用。

这条消息链应被视为一个整体:AIMessage 说明模型提出了什么调用,ToolMessage 说明该调用得到什么结果。二者之间最关键的连接不是文本相似,而是调用 ID。
旧写法的问题:只取最后一条消息
下面这种写法适合只展示最终答案,却不适合保留中间状态:
# 只取最终文本,工具调用名称和参数会被丢掉 final_text = result["messages"][-1].content print(final_text)
它的问题不在于最后一条消息一定错误,而在于它把“展示结果”和“保存状态”混在一起。展示时可以读取最后一条;保存时应保留整个列表,并按消息类型提取需要的字段。
新规则:从 agent 返回值保留完整 messages
使用 create_agent 时,返回值包含消息状态。下面的示例只做消息收集,不把示例输出描述成已经在本机运行的结果:
from langchain.agents import create_agent
from langchain.tools import tool
from langchain.messages import AIMessage, ToolMessage
@tool
def lookup_weather(city: str) -> str:
"""返回一个供示例使用的天气结果。"""
# 工具返回值会进入 ToolMessage.content,保持结果可序列化
return f"{city}:晴,26℃"
agent = create_agent(
model="openai:gpt-5-nano",
tools=[lookup_weather],
system_prompt="你是一个会使用工具的助手",
)
result = agent.invoke({
"messages": [{"role": "user", "content": "查询上海天气"}]
})
# 保存完整消息列表,而不是只保存最后的回答文本
messages = result["messages"]
for message in messages:
if isinstance(message, AIMessage):
print("AI 调用:", message.tool_calls)
elif isinstance(message, ToolMessage):
print("工具结果:", message.content)
这里的核心只有两点:messages 是可继续交给模型的上下文,AIMessage 与 ToolMessage 则让应用可以分别记录模型调用和工具结果。若只需要展示最终答案,再从列表末尾读取即可。
手动续接工具结果时,调用标识必须对应
如果应用没有直接使用 agent 循环,而是自己执行工具并把结果交回模型,必须把 ToolMessage.tool_call_id 设置为 AIMessage.tool_calls 中对应调用的 id:
from langchain.messages import AIMessage, HumanMessage, ToolMessage
ai_message = AIMessage(
content=[],
tool_calls=[{
"name": "lookup_weather",
"args": {"city": "上海"},
"id": "call_weather_001",
}],
)
# 真实工具执行后,把同一个 id 写回结果消息
tool_message = ToolMessage(
content="上海:晴,26℃",
tool_call_id="call_weather_001",
)
messages = [
HumanMessage("查询上海天气"),
ai_message,
tool_message,
]
# 下一次模型调用接收的是完整的消息关系
response = model.invoke(messages)

如果一次 AIMessage 含有多个工具调用,就要为每个结果建立独立的 ToolMessage,逐一匹配各自的 ID。不能用“最后一次调用”或工具名称猜测对应关系。
兼容边界:content、artifact 与持久化
ToolMessage.content 是发送给模型的工具结果;若应用还需要保存检索文档编号、原始响应或渲染信息,可以把这类不必直接进入模型上下文的附加数据放入 artifact,并在业务层按需序列化。
因此可以按三层保存:第一层是完整 messages,用于继续调用;第二层是每条消息的类型、调用 ID 和内容,便于日志检索;第三层是 artifact 等应用侧数据,服务展示或审计。跨请求、跨线程的长期保留还需要接入自己的存储或 LangGraph 的持久化能力,不能把一次 invoke 返回值自动当成永久数据库。
最终采用建议很简单:展示回答时取末条消息,恢复上下文时保存完整消息列表,手动回填工具结果时严格复用调用 ID。这样既保留中间消息,也不会把工具结果错接到另一条调用上。
常见问题
只保存 AIMessage 可以吗?不建议。模型发出的调用和工具返回的结果是一对关系,缺少 ToolMessage 后,后续上下文不完整。
artifact 会自动发送给模型吗?不应这样假设。把需要模型理解的结果放在 content,应用侧附加数据再单独放进 artifact。
为什么工具结果必须带 tool_call_id?它用来对应 AIMessage 中具体的调用 ID;多个工具并行调用时尤其不能省略。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
326 收藏
-
313 收藏
-
178 收藏
-
334 收藏
-
311 收藏
-
174 收藏
-
205 收藏
-
415 收藏
-
127 收藏
-
228 收藏
-
459 收藏
-
189 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习