登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

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 回传。只保存最终回答,会丢掉工具名、参数和调用标识,下一次模型就无法可靠地把结果对应回原调用。

HumanMessage、AIMessage、ToolMessage、消息历史与模型上下文的静态结构说明图
图1:消息历史结构说明图,区分模型发出的工具调用与工具返回的消息对象。

这条消息链应被视为一个整体: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 tool_calls 调用 id 与 ToolMessage tool_call_id、content、artifact 的字段关系说明图
图2:工具结果字段关系说明图,突出 tool_call_id 与工具调用 id 的对应边界。

如果一次 AIMessage 含有多个工具调用,就要为每个结果建立独立的 ToolMessage,逐一匹配各自的 ID。不能用“最后一次调用”或工具名称猜测对应关系。

兼容边界:content、artifact 与持久化

ToolMessage.content 是发送给模型的工具结果;若应用还需要保存检索文档编号、原始响应或渲染信息,可以把这类不必直接进入模型上下文的附加数据放入 artifact,并在业务层按需序列化。

因此可以按三层保存:第一层是完整 messages,用于继续调用;第二层是每条消息的类型、调用 ID 和内容,便于日志检索;第三层是 artifact 等应用侧数据,服务展示或审计。跨请求、跨线程的长期保留还需要接入自己的存储或 LangGraph 的持久化能力,不能把一次 invoke 返回值自动当成永久数据库。

最终采用建议很简单:展示回答时取末条消息,恢复上下文时保存完整消息列表,手动回填工具结果时严格复用调用 ID。这样既保留中间消息,也不会把工具结果错接到另一条调用上。

常见问题

只保存 AIMessage 可以吗?不建议。模型发出的调用和工具返回的结果是一对关系,缺少 ToolMessage 后,后续上下文不完整。

artifact 会自动发送给模型吗?不应这样假设。把需要模型理解的结果放在 content,应用侧附加数据再单独放进 artifact。

为什么工具结果必须带 tool_call_id?它用来对应 AIMessage 中具体的调用 ID;多个工具并行调用时尤其不能省略。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>