在上一篇文章中,我们介绍了 create_agent() 函数及其各个参数,以及如何配置 Model。
create_agent() 函数主要负责创建 Agent,Agent 创建完成后,下一步自然就是:怎样真正把它运行起来?
最简单的写法只有一行:
result = agent.invoke({
"messages": [
{"role": "user", "content": "北京今天天气怎么样?"}
]
})
但实际开发会遇到更多问题:messages 为什么是一个列表?Agent 返回的为什么还是 messages?HumanMessage、AIMessage、ToolMessage 分别是什么?什么时候使用 invoke(),什么时候应该使用 stream()?
这些问题看起来分散,实际上都围绕同一件事:Agent 执行过程中,消息如何进入 State,又如何在 Model、Tool 和最终响应之间不断流转。
一、Agent 如何执行
要理解 Agent 如何执行,我们需要理解通过 create_agent() 所创建的 Agent 的本质。
LangChain 的 create_agent() 底层运行在 LangGraph Runtime 上。Model、Tools、Middleware 等能力参与执行图的不同环节,Agent 在这些环节之间运行,直到模型给出最终答案,或者满足其他停止条件。
因此:
agent.invoke(...)
表面上是在“调用 Agent”,实际启动的是一次完整的 Graph Runtime 执行。
以一个天气 Agent 为例:
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="你是一名天气助手。",
)
执行:
result = agent.invoke({
"messages": [
{"role": "user", "content": "北京今天天气怎么样?"}
]
})
内部执行过程大体是:
HumanMessage
↓
Model
↓
AIMessage:请求调用 get_weather
↓
Tool
↓
ToolMessage:北京今天晴,最高气温 28℃
↓
Model
↓
AIMessage:生成最终回答
这也是理解 Agent 执行最重要的一条线索:Agent 的运行过程,很大程度上可以看成 Message 不断产生并加入 State 的过程。
二、理解 Message
在 LangChain 中,Message 是模型上下文的基本单位。
一条 Message 不只有一段文字,它通常还包含角色、内容、ID、Token 使用量、工具调用信息以及模型响应元数据等内容。LangChain 通过统一的 Message 抽象,屏蔽不同模型厂商在消息格式上的部分差异。
最常见的四种消息如下:
| Message | 对应角色 | 主要作用 | 常见产生位置 |
|---|---|---|---|
SystemMessage | system | 定义模型行为和系统规则 | 开发者提供 |
HumanMessage | user | 表示用户输入 | 用户请求 |
AIMessage | assistant | 表示模型输出 | Model 节点 |
ToolMessage | tool | 保存工具执行结果 | Tool 节点 |
这四种 Message 都继承自 BaseMessage。BaseMessage 定义在 langchain_core.messages 中,它是所有消息类型的抽象基类。
其中前三种 Message 比较容易理解。
例如:
from langchain.messages import (
SystemMessage,
HumanMessage,
AIMessage,
)
messages = [
SystemMessage("你是一名 Python 助手。"),
HumanMessage("什么是装饰器?"),
AIMessage("装饰器用于在不修改原函数代码的情况下扩展函数行为。"),
]
也可以直接使用字典:
messages = [
{"role": "system", "content": "你是一名 Python 助手。"},
{"role": "user", "content": "什么是装饰器?"},
]
LangChain 官方同时支持 Message 对象和常见的字典形式。
对于简单调用,字典写起来更短;需要访问 tool_calls、usage_metadata、content_blocks 等属性时,Message 对象通常更方便。
三、ToolMessage 如何连接工具调用
四种常见 Message 中,ToolMessage 最容易让初学者困惑。
假设用户问:
北京今天天气怎么样?
模型本身并不会直接执行 get_weather()。
如果模型决定调用工具,它首先产生一个 AIMessage,其中保存工具调用请求:
AIMessage
tool_calls = [
{
"name": "get_weather",
"args": {"city": "北京"},
"id": "call_123"
}
]
Agent Runtime 根据这个请求执行工具,当执行完成后,然后产生对应的:
ToolMessage
content = "北京今天晴,最高气温 28℃"
tool_call_id = "call_123"
这里的 tool_call_id 非常重要,它用于说明:这个工具结果究竟是在回答哪个工具调用。 注意:ToolMessage.tool_call_id 必须与对应 AIMessage (也即请求执行工具调用的AIMesssage)中 Tool Call 的 ID 匹配。
之后,Model 再读取 ToolMessage,并生成最终的 AIMessage。
所以工具调用不是:
用户 → 工具 → 用户
而是:
用户
↓
模型决定调用什么工具
↓
工具执行
↓
结果重新交给模型
↓
模型组织最终答案
理解这个过程之后,再查看 Agent 返回的 Message 列表就会清晰很多。
四、使用 invoke 获取结果
invoke() 适合一次性执行完整 Agent,并在运行结束后取得最终 State。
最基本的调用方式是:
result = agent.invoke({
"messages": [
{"role": "user", "content": "北京今天天气怎么样?"}
]
})
Agent执行完成后,通过以下方式可以获取模型最终的输出,也即列表中的最后一条 Message:
last_message = result["messages"][-1]
print(last_message.content)
示例输出:
北京今天晴,最高气温约 28℃。
如果想观察整个过程,可以遍历消息:
for message in result["messages"]:
message.pretty_print()
上述代码输出:
================================ 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 调用通常不需要一开始就处理这些选项。
需要特别区分 context 和 messages。
messages 属于会随着 Agent 执行不断变化的 State;context 更适合保存一次运行期间固定不变的信息,例如当前用户 ID、数据库连接或者其他依赖。
五、使用 stream 查看执行过程
invoke() 的特点是:等整个 Agent 执行完成,再一次性返回结果。
如果 Agent 只调用一次模型,等待时间可能并不明显。但如果一次请求需要调用搜索、数据库、多个工具,再执行数轮模型推理,前端长时间没有任何输出,体验就会比较差。
此时可以使用:
agent.stream(...)
LangChain 当前的 Streaming 可以输出 Agent 执行进度、模型生成的消息片段以及自定义事件等内容。常用 Stream Mode 包括 updates、messages 和 custom。
stream_mode | 输出内容 | 适合场景 |
|---|---|---|
updates | 每个 Agent Step 的状态更新 | 观察 Agent 执行过程 |
messages | LLM 生成的 Token 和元数据 | 打字机式输出 |
custom | 自己定义的进度信息 | 业务进度展示 |
先看 updates:
for chunk in agent.stream(
{
"messages": [
{"role": "user", "content": "北京今天天气怎么样?"}
]
},
stream_mode="updates",
version="v2",
):
print(chunk["type"], chunk["data"])
示例输出
updates {'model': {'messages': [AIMessage(...tool call...)]}}
updates {'tools': {'messages': [ToolMessage(...)]}}
updates {'model': {'messages': [AIMessage(...final answer...)]}}
从结果可以看到,这里流式输出的不是单纯的文字,而是 Agent 每一步执行完成后的状态变化。
也就是说,我们可以知道 Agent 当前是在调用模型、执行工具,还是已经生成最终答案。
六、流式输出模型内容
如果目标是实现 ChatGPT 类似的逐字输出,更常用的是:
stream_mode="messages"
例如:
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)
示例输出
Python 装饰器是一种在不修改原函数代码的情况下,
为函数增加额外行为的机制……
这里与 invoke() 有一个明显区别。
invoke() 最终通常得到完整的 AIMessage,而模型流式生成过程中得到的是 AIMessageChunk。这些 Chunk 可以逐步处理,也可以组合成完整 Message。
因此:
invoke
→ 等待完整执行
→ 得到完整结果
而:
stream
→ Agent 一边执行
→ 程序一边接收事件或消息片段
实际项目中,如果只是后台任务、批处理或需要最终结果再继续处理,invoke() 通常更简单。
如果是聊天界面、Agent 操作进度、长时间工具调用或需要用户实时看到模型输出,则更适合 stream()。
七、理解 Message 中的数据
实际项目中,不要只关注:
message.content
AIMessage 中还有很多有价值的信息。官方当前常用属性包括 text、content、content_blocks、tool_calls、id、usage_metadata 和 response_metadata。
例如:
message = result["messages"][-1]
print(message.text)
print(message.content_blocks)
print(message.usage_metadata)
print(message.response_metadata)
其中:
| 属性 | 含义 |
|---|---|
text | Message 中的文本内容 |
content | 原始消息内容 |
content_blocks | LangChain 标准化后的内容块 |
tool_calls | 模型请求执行的工具 |
usage_metadata | Token 使用情况 |
response_metadata | 模型、Provider 等响应信息 |
id | Message 唯一标识 |
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,就会更为容易理解与掌握。
社区讨论
参与讨论
有问题或想法?欢迎继续讨论。