在上一篇文章中,我们介绍了 create_agent() 函数及其各个参数,以及如何配置 Model。

create_agent() 函数主要负责创建 Agent,Agent 创建完成后,下一步自然就是:怎样真正把它运行起来?

最简单的写法只有一行:

代码片段Python
result = agent.invoke({
    "messages": [
        {"role": "user", "content": "北京今天天气怎么样?"}
    ]
})

但实际开发会遇到更多问题:messages 为什么是一个列表?Agent 返回的为什么还是 messagesHumanMessageAIMessageToolMessage 分别是什么?什么时候使用 invoke(),什么时候应该使用 stream()

这些问题看起来分散,实际上都围绕同一件事:Agent 执行过程中,消息如何进入 State,又如何在 Model、Tool 和最终响应之间不断流转。

一、Agent 如何执行

要理解 Agent 如何执行,我们需要理解通过 create_agent() 所创建的 Agent 的本质。

LangChain 的 create_agent() 底层运行在 LangGraph Runtime 上。Model、Tools、Middleware 等能力参与执行图的不同环节,Agent 在这些环节之间运行,直到模型给出最终答案,或者满足其他停止条件。

因此:

代码片段Python
agent.invoke(...)

表面上是在“调用 Agent”,实际启动的是一次完整的 Graph Runtime 执行。

以一个天气 Agent 为例:

代码片段Python
from langchain.agents import create_agent
 
 
def get_weather(city: str) -> str:
    """获取指定城市的天气。"""
    return f"{city}今天晴,最高气温 28℃"
 
 
agent = create_agent(
    model="openai:gpt-5.5",
    tools=[get_weather],
    system_prompt="你是一名天气助手。",
)

执行:

代码片段Python
result = agent.invoke({
    "messages": [
        {"role": "user", "content": "北京今天天气怎么样?"}
    ]
})

内部执行过程大体是:

代码片段Text
HumanMessage

Model

AIMessage:请求调用 get_weather

Tool

ToolMessage:北京今天晴,最高气温 28℃

Model

AIMessage:生成最终回答

这也是理解 Agent 执行最重要的一条线索:Agent 的运行过程,很大程度上可以看成 Message 不断产生并加入 State 的过程。

二、理解 Message

在 LangChain 中,Message 是模型上下文的基本单位。

一条 Message 不只有一段文字,它通常还包含角色、内容、ID、Token 使用量、工具调用信息以及模型响应元数据等内容。LangChain 通过统一的 Message 抽象,屏蔽不同模型厂商在消息格式上的部分差异。

最常见的四种消息如下:

Message对应角色主要作用常见产生位置
SystemMessagesystem定义模型行为和系统规则开发者提供
HumanMessageuser表示用户输入用户请求
AIMessageassistant表示模型输出Model 节点
ToolMessagetool保存工具执行结果Tool 节点

这四种 Message 都继承自 BaseMessageBaseMessage 定义在 langchain_core.messages 中,它是所有消息类型的抽象基类。

其中前三种 Message 比较容易理解。

例如:

代码片段Python
from langchain.messages import (
    SystemMessage,
    HumanMessage,
    AIMessage,
)
 
messages = [
    SystemMessage("你是一名 Python 助手。"),
    HumanMessage("什么是装饰器?"),
    AIMessage("装饰器用于在不修改原函数代码的情况下扩展函数行为。"),
]

也可以直接使用字典:

代码片段Python
messages = [
    {"role": "system", "content": "你是一名 Python 助手。"},
    {"role": "user", "content": "什么是装饰器?"},
]

LangChain 官方同时支持 Message 对象和常见的字典形式。

对于简单调用,字典写起来更短;需要访问 tool_callsusage_metadatacontent_blocks 等属性时,Message 对象通常更方便。

三、ToolMessage 如何连接工具调用

四种常见 Message 中,ToolMessage 最容易让初学者困惑。

假设用户问:

代码片段Text
北京今天天气怎么样?

模型本身并不会直接执行 get_weather()

如果模型决定调用工具,它首先产生一个 AIMessage,其中保存工具调用请求:

代码片段Text
AIMessage
tool_calls = [
    {
        "name": "get_weather",
        "args": {"city": "北京"},
        "id": "call_123"
    }
]

Agent Runtime 根据这个请求执行工具,当执行完成后,然后产生对应的:

代码片段Text
ToolMessage
content = "北京今天晴,最高气温 28℃"
tool_call_id = "call_123"

这里的 tool_call_id 非常重要,它用于说明:这个工具结果究竟是在回答哪个工具调用。 注意:ToolMessage.tool_call_id 必须与对应 AIMessage (也即请求执行工具调用的AIMesssage)中 Tool Call 的 ID 匹配。

之后,Model 再读取 ToolMessage,并生成最终的 AIMessage。

所以工具调用不是:

代码片段Text
用户 → 工具 → 用户

而是:

代码片段Text
用户

模型决定调用什么工具

工具执行

结果重新交给模型

模型组织最终答案

理解这个过程之后,再查看 Agent 返回的 Message 列表就会清晰很多。

四、使用 invoke 获取结果

invoke() 适合一次性执行完整 Agent,并在运行结束后取得最终 State。

最基本的调用方式是:

代码片段Python
result = agent.invoke({
    "messages": [
        {"role": "user", "content": "北京今天天气怎么样?"}
    ]
})

Agent执行完成后,通过以下方式可以获取模型最终的输出,也即列表中的最后一条 Message:

代码片段Python
last_message = result["messages"][-1]
 
print(last_message.content)

示例输出:

代码片段Text
北京今天晴,最高气温约 28℃。

如果想观察整个过程,可以遍历消息:

代码片段Python
for message in result["messages"]:
    message.pretty_print()

上述代码输出:

代码片段Text
================================ Human Message =================================

北京今天天气怎么样?

================================== Ai Message ==================================

Tool Calls:
  get_weather
  Args:
    city: 北京

================================= Tool Message =================================

北京今天晴,最高气温 28℃

================================== Ai Message ==================================

北京今天晴,最高气温约 28℃。

pretty_print()BaseMessage 提供的方法,用于输出更适合人阅读的 Message 表示形式。调试 Agent 时,它往往比直接 print(message) 更直观。

日常使用 invoke() 时,常见参数还有:

参数作用常见用途
input本次执行输入传入 messages 等 State 数据
config本次运行配置thread_id、递归限制、Callbacks 等
context静态运行时上下文用户 ID、数据库连接等依赖
stream_mode控制执行结果的流模式高级 Graph 场景
version控制运行结果格式v1 或新的 v2 格式

完整的 Graph invoke() 还提供 interrupt、durability、output keys 等参数,但普通 Agent 调用通常不需要一开始就处理这些选项。

需要特别区分 contextmessages

messages 属于会随着 Agent 执行不断变化的 State;context 更适合保存一次运行期间固定不变的信息,例如当前用户 ID、数据库连接或者其他依赖。

五、使用 stream 查看执行过程

invoke() 的特点是:等整个 Agent 执行完成,再一次性返回结果。

如果 Agent 只调用一次模型,等待时间可能并不明显。但如果一次请求需要调用搜索、数据库、多个工具,再执行数轮模型推理,前端长时间没有任何输出,体验就会比较差。

此时可以使用:

代码片段Python
agent.stream(...)

LangChain 当前的 Streaming 可以输出 Agent 执行进度、模型生成的消息片段以及自定义事件等内容。常用 Stream Mode 包括 updatesmessagescustom

stream_mode输出内容适合场景
updates每个 Agent Step 的状态更新观察 Agent 执行过程
messagesLLM 生成的 Token 和元数据打字机式输出
custom自己定义的进度信息业务进度展示

先看 updates

代码片段Python
for chunk in agent.stream(
    {
        "messages": [
            {"role": "user", "content": "北京今天天气怎么样?"}
        ]
    },
    stream_mode="updates",
    version="v2",
):
    print(chunk["type"], chunk["data"])

示例输出

代码片段Text
updates {'model': {'messages': [AIMessage(...tool call...)]}}

updates {'tools': {'messages': [ToolMessage(...)]}}

updates {'model': {'messages': [AIMessage(...final answer...)]}}

从结果可以看到,这里流式输出的不是单纯的文字,而是 Agent 每一步执行完成后的状态变化

也就是说,我们可以知道 Agent 当前是在调用模型、执行工具,还是已经生成最终答案。

六、流式输出模型内容

如果目标是实现 ChatGPT 类似的逐字输出,更常用的是:

代码片段Python
stream_mode="messages"

例如:

代码片段Python
for chunk in agent.stream(
    {
        "messages": [
            {"role": "user", "content": "介绍一下 Python 装饰器。"}
        ]
    },
    stream_mode="messages",
    version="v2",
):
    if chunk["type"] != "messages":
        continue
 
    token, metadata = chunk["data"]
 
    if token.text:
        print(token.text, end="", flush=True)

示例输出

代码片段Text
Python 装饰器是一种在不修改原函数代码的情况下,
为函数增加额外行为的机制……

这里与 invoke() 有一个明显区别。

invoke() 最终通常得到完整的 AIMessage,而模型流式生成过程中得到的是 AIMessageChunk。这些 Chunk 可以逐步处理,也可以组合成完整 Message。

因此:

代码片段Text
invoke
→ 等待完整执行
→ 得到完整结果

而:

代码片段Text
stream
→ Agent 一边执行
→ 程序一边接收事件或消息片段

实际项目中,如果只是后台任务、批处理或需要最终结果再继续处理,invoke() 通常更简单。

如果是聊天界面、Agent 操作进度、长时间工具调用或需要用户实时看到模型输出,则更适合 stream()

七、理解 Message 中的数据

实际项目中,不要只关注:

代码片段Python
message.content

AIMessage 中还有很多有价值的信息。官方当前常用属性包括 textcontentcontent_blockstool_callsidusage_metadataresponse_metadata

例如:

代码片段Python
message = result["messages"][-1]
 
print(message.text)
print(message.content_blocks)
print(message.usage_metadata)
print(message.response_metadata)

其中:

属性含义
textMessage 中的文本内容
content原始消息内容
content_blocksLangChain 标准化后的内容块
tool_calls模型请求执行的工具
usage_metadataToken 使用情况
response_metadata模型、Provider 等响应信息
idMessage 唯一标识

content_blocks 尤其值得理解。

不同模型厂商可能使用不同结构表示文本、推理、多模态内容和工具调用。LangChain 提供标准化的 Content Block,把这些内容转换到更统一的结构中,因此在需要兼容多个模型 Provider 时,通常比直接解析 Provider 原始数据更方便。

这也说明 Message 并不只是“聊天记录”。

在 Agent 中,它同时承担了模型输入、模型输出、工具调用、工具结果以及执行元数据的载体

八、总结

上一篇文章解决的是 Agent 如何创建,本文继续解决 Agent 如何运行

invoke()stream() 并不是两套不同的 Agent 机制,它们只是观察同一次 Agent Runtime 执行的不同方式:前者更适合等待完整结果,后者更适合实时获取执行过程或模型输出。

而贯穿整个过程的就是 Message。用户输入形成 HumanMessage,模型产生 AIMessage,工具执行结果形成 ToolMessage,这些消息不断进入 Agent State,并成为下一轮 Model 决策的上下文。理解这条消息流之后,后面再学习 State、Memory、Context、Middleware 和多轮 Agent,就会更为容易理解与掌握。