LangChain Agent 工作流程
上一节我们学习了 create_agent() 函数的参数及基本用法。
本章节将进一步了解 Agent 的内部执行机制:模型如何选择工具、工具结果如何返回模型、Agent 在什么条件下结束,以及如何通过流式接口观察整个执行过程。
在 LangChain 1.x 中,create_agent() 基于 LangGraph 构建 Agent 执行图,能够自动管理模型调用、工具执行和状态更新。
本教程继续使用 DeepSeek Flash 模型,并使用模拟天气、模拟时间和课程查询工具进行演示。
注意:本章使用 LangGraph 1.1 及以上版本支持的 version="v2" 流式事件格式。旧版 LangGraph 的流式返回结构可能不同。
一、Agent 的基本执行循环
Agent 的核心机制可以概括为:
调用模型 → 判断工具调用 → 执行工具 → 再次调用模型
当模型返回工具调用请求时,Agent 执行对应工具,并将结果加入消息历史。
随后,模型可以根据工具结果继续处理任务。
整个过程通常持续到模型不再请求工具,或者遇到其他停止、暂停或错误条件。
Agent 的基本流程如下:

Model 节点与 Tools 节点
一个典型的 Agent 可以抽象成两个核心节点:
| 节点 | 作用 |
|---|---|
model |
调用语言模型,生成回复或工具调用请求 |
tools |
执行模型请求的客户端工具,并返回工具结果 |
例如,用户提问:
杭州的模拟天气和模拟时间分别是什么?
模型可能生成两个工具调用:
get_weather(city="杭州") get_time(city="杭州")
工具执行完成后,结果会作为 ToolMessage 返回,模型再生成最终回答。
注意:模型可能一次请求多个工具,也可能分多轮调用工具。因此,执行图的节点顺序和次数并不固定。
二、安装与环境准备
本教程使用 LangChain 1.x,并采用 LangGraph v2 流式格式。
安装或更新依赖:
pip install -U langchain langgraph langchain-deepseek python-dotenv
使用 version="v2" 需要 LangGraph 1.1 或更新版本。
项目目录中的 .env 文件:
DEEPSEEK_API_KEY=your_deepseek_api_key
创建模型时使用:
model = init_chat_model(
"deepseek:deepseek-flash"
)
实际调用前需要确认已正确配置 API Key,并且对应模型支持工具调用。
三、追踪 Agent 的完整执行过程
接下来定义两个工具:
get_weather():返回模拟天气。get_time():返回模拟时间。
为了方便观察,两个工具都使用固定字典数据,不会调用真实的天气或时间接口。
实例:使用 updates 追踪执行节点
实例
from dotenv import load_dotenv
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
load_dotenv()
# 工具一:模拟天气
@tool
def get_weather(city: str) -> str:
"""查询指定城市的模拟天气。"""
weather_data = {
"杭州": "晴,25°C",
"北京": "多云,18°C"
}
return weather_data.get(
city,
f"未找到{city}的模拟天气数据"
)
# 工具二:模拟时间
@tool
def get_time(city: str) -> str:
"""查询指定城市的模拟时间。"""
time_data = {
"杭州": "14:30",
"北京": "14:30",
"纽约": "02:30"
}
return time_data.get(
city,
f"未找到{city}的模拟时间数据"
)
# 初始化模型
model = init_chat_model(
"deepseek:deepseek-flash"
)
# 创建 Agent
agent = create_agent(
model=model,
tools=[get_weather, get_time],
system_prompt=(
"你是一名中文助手。"
"天气和时间工具中的数据都是模拟数据。"
"用户查询这类数据时应使用工具。"
"不要把模拟数据当作实时信息。"
)
)
# 用户消息
inputs = {
"messages": [
{
"role": "user",
"content": (
"请分别查询杭州的模拟天气"
"和模拟时间。"
)
}
]
}
# 追踪节点更新
print("=== Agent 执行过程 ===")
event_count = 0
for event in agent.stream(
inputs,
stream_mode="updates",
version="v2"
):
if event["type"] != "updates":
continue
event_count += 1
print(f"\n--- 更新事件 {event_count} ---")
# v2 事件数据
updates = event["data"]
for node_name, update in updates.items():
print("节点:", node_name)
if not isinstance(update, dict):
print("节点数据:", update)
continue
messages = update.get("messages", [])
for message in messages:
if message.type == "ai" and message.tool_calls:
for call in message.tool_calls:
print(
"[请求工具]",
call["name"],
call["args"]
)
elif message.type == "tool":
print(
"[工具结果]",
message.name,
message.content
)
elif message.type == "ai":
print(
"[模型回复]",
message.text
)
可能的执行过程
如果模型在同一轮请求两个工具,日志可能类似:
=== Agent 执行过程 ===
--- 更新事件 1 ---
节点: model
[请求工具] get_weather {'city': '杭州'}
[请求工具] get_time {'city': '杭州'}
--- 更新事件 2 ---
节点: tools
[工具结果] get_weather 晴,25°C
[工具结果] get_time 14:30
--- 更新事件 3 ---
节点: model
[模型回复] 杭州的模拟天气为晴天、25°C,
模拟时间为14:30。
这里包含三次节点更新:
- Model:模型生成两个工具调用请求。
- Tools:工具执行,返回模拟天气和时间。
- Model:模型读取工具结果并生成回答。
但是,三次更新只是其中一种可能的执行路径。
模型也可能先调用天气工具,下一轮再调用时间工具,这样就会产生更多节点更新。
如果多个工具在同一轮执行,工具结果也不一定按照模型请求的顺序完成。
四、理解 Agent 的消息变化
除了观察节点,还可以查看执行过程中产生的消息。
一次典型的工具调用包含以下消息:
HumanMessage
↓
AIMessage(tool_calls=[...])
↓
ToolMessage
↓
AIMessage(最终回答)
如果模型一次请求两个工具,消息可能为:
HumanMessage
↓
AIMessage(两个 tool_calls)
↓
ToolMessage(天气)
ToolMessage(时间)
↓
AIMessage(最终回答)
其中:
| 消息类型 | 内容 |
|---|---|
HumanMessage |
用户输入 |
AIMessage |
模型回复或工具调用请求 |
ToolMessage |
工具执行结果 |
注意,包含工具调用的 AIMessage,其普通文本内容可能为空,但 tool_calls 中仍然有工具请求。
因此,观察 Agent 工作流程时,应同时检查:
message.content message.tool_calls message.type
不要简单根据 content 是否为空,判断模型是否调用了工具。
五、stream_mode 参数详解
agent.stream() 支持不同的流式模式,用于获取不同粒度的数据。
| 模式 | 返回内容 | 适用场景 |
|---|---|---|
updates |
节点执行后的状态更新 | 观察 Agent 执行步骤 |
values |
图运行过程中的完整状态快照 | 检查消息历史和状态字段 |
messages |
语言模型的消息片段及元数据 | 聊天流式输出 |
custom |
工具或图节点发送的自定义数据 | 进度提示和任务状态 |
debug |
图执行相关的调试信息 | 深入排查执行过程 |
updates、messages 和 custom 是 LangChain Agent 官方入门文档重点介绍的模式;values 和 debug 等也可参考 LangGraph 流式文档。
使用 version="v2" 时,流事件采用统一结构:
{
"type": "updates",
"ns": [],
"data": {
"model": {
"messages": [...]
}
}
}
其中:
type:当前流式模式。ns:事件所属的图或子图命名空间。data:具体事件内容。
不同 stream_mode 的 data 内容不同。
六、stream_mode="updates":查看节点更新
updates 用于观察 Agent 的执行步骤。
每当相关节点产生状态更新时,就可以收到相应事件。
实例
实例
{
"messages": [
{
"role": "user",
"content": "请查询杭州的模拟天气。"
}
]
},
stream_mode="updates",
version="v2"
):
if event["type"] != "updates":
continue
for node_name, update in event["data"].items():
print("节点:", node_name)
if isinstance(update, dict):
for message in update.get("messages", []):
print(
message.type,
message.content
)
如果模型调用天气工具,可能观察到:
节点: model ai 节点: tools tool 晴,25°C 节点: model ai 杭州的模拟天气为晴天,气温25°C。
这种模式适合调试 Agent 工具调用,以及在后台展示当前执行到哪个节点。
注意:一个节点更新事件不一定等于一次工具调用。一个 Tools 节点更新中可能包含多条工具结果。
七、stream_mode="values":查看完整状态
updates 主要显示节点产生的变化,而 values 用于观察运行过程中的完整状态。
例如,可以查看每次更新后的消息数量。
实例
实例
"messages": [
{
"role": "user",
"content": "查询杭州的模拟天气。"
}
]
}
snapshot_count = 0
for event in agent.stream(
inputs,
stream_mode="values",
version="v2"
):
if event["type"] != "values":
continue
snapshot_count += 1
state = event["data"]
messages = state.get("messages", [])
print(
f"\n状态快照 {snapshot_count}:"
f"{len(messages)} 条消息"
)
for message in messages:
print(
f"[{message.type}]",
str(message.content)[:80]
)
如果工具调用流程是:
用户 → 模型请求工具 → 工具返回 → 模型回答
那么观察到的消息数量可能依次增长:
状态快照 1:1 条消息 状态快照 2:2 条消息 状态快照 3:3 条消息 状态快照 4:4 条消息
但具体快照数量和消息数量会受到实际执行路径影响,不能将其视为固定结果。
注意:不要在尚未执行完成时,仅仅为了减少打印次数就随意 break 退出流式循环。
提前停止消费事件可能使执行过程、资源清理或后续结果处理变得不完整。教学示例建议消费完整个事件流。
八、stream_mode="messages":流式生成文本
messages 用于获取语言模型生成过程中的消息片段。
它适合制作类似 ChatGPT 的流式打字效果。
需要注意,流式返回的并不一定是一个完整 Token,也不一定只包含普通文本。
模型可能产生:
- 文本消息片段。
- 工具调用参数片段。
- 推理内容块。
- 响应元数据。
实例:逐步输出模型文本
实例
# 不注册工具,便于观察普通文本输出
text_agent = create_agent(
model="deepseek:deepseek-flash",
tools=[]
)
print("=== 流式输出 ===")
for event in text_agent.stream(
{
"messages": [
{
"role": "user",
"content": (
"用一句话介绍菜鸟教程 RUNOOB。"
)
}
]
},
stream_mode="messages",
version="v2"
):
if event["type"] != "messages":
continue
message_chunk, metadata = event["data"]
# 只显示模型节点输出的文本
if metadata.get("langgraph_node") == "model":
print(
message_chunk.text,
end="",
flush=True
)
print()
代码中的 message_chunk 通常是 AIMessageChunk。
metadata 中可以包含当前节点名称等信息。
由于不同模型的流式响应格式可能不同,message_chunk.text 可能为空。
如果模型返回工具调用、思考内容等非普通文本块,还需要根据对应的数据结构进行处理。
流式工具调用
如果 Agent 注册了工具,模型在流式输出期间还可能逐步返回工具调用参数。
例如:
工具名称:get_weather
参数片段:{"city":
参数片段:"杭州"
参数片段:}
这些片段不一定是完整的 JSON。
需要等待必要的消息片段合并后,才能将工具调用作为完整请求理解。
因此,在展示 Agent 过程时,通常建议:
- 使用
messages显示模型生成中的文本。 - 使用
updates显示已经完成的工具调用和工具结果。
九、stream_mode="custom":自定义进度事件
除了模型和图节点产生的事件,开发者还可以从工具内部发送自定义进度。
例如,一个需要处理大量文件的工具,可以持续报告当前处理数量。
LangChain 可以通过 get_stream_writer() 获取流式写入器。
实例:工具发送进度提示
实例
from langchain.agents import create_agent
from langgraph.config import get_stream_writer
@tool
def process_documents(task_name: str) -> str:
"""模拟处理文档并发送进度更新。"""
writer = get_stream_writer()
for i in range(1, 4):
writer({
"task": task_name,
"current": i,
"total": 3,
"message": f"已处理 {i}/3 个文档"
})
return "三个模拟文档处理完成"
agent = create_agent(
model="deepseek:deepseek-flash",
tools=[process_documents],
system_prompt=(
"你是一名文档处理助手。"
"用户要求执行模拟文档处理时,"
"调用 process_documents。"
)
)
for event in agent.stream(
{
"messages": [
{
"role": "user",
"content": (
"请使用工具模拟处理测试文档。"
)
}
]
},
stream_mode="custom",
version="v2"
):
if event["type"] == "custom":
print(event["data"])
如果模型调用工具,可以观察到类似进度:
{'task': '测试文档', 'current': 1, 'total': 3, 'message': '已处理 1/3 个文档'}
{'task': '测试文档', 'current': 2, 'total': 3, 'message': '已处理 2/3 个文档'}
{'task': '测试文档', 'current': 3, 'total': 3, 'message': '已处理 3/3 个文档'}
这里没有真正读取文件,只是通过循环模拟进度。
注意:get_stream_writer() 需要适当的 LangGraph 执行上下文。直接调用包含它的工具函数时,可能无法获得对应的流式写入器。
真实项目可以将自定义事件发送给前端,用于展示上传进度、数据处理进度或任务阶段。
十、同时使用多个 stream_mode
如果需要同时观察节点更新和模型文本,可以传入多个模式。
例如:
stream_mode=["messages", "updates"]
在 v2 格式中,不同事件都使用同样的外层结构,可以通过 event["type"] 区分。
实例:同时观察工具调用与文本输出
实例
"messages": [
{
"role": "user",
"content": "查询杭州的模拟天气。"
}
]
}
for event in agent.stream(
inputs,
stream_mode=[
"messages",
"updates"
],
version="v2"
):
mode = event["type"]
if mode == "messages":
message_chunk, metadata = event["data"]
if metadata.get("langgraph_node") == "model":
text = message_chunk.text
if text:
print(
text,
end="",
flush=True
)
elif mode == "updates":
updates = event["data"]
for node_name, update in updates.items():
if not isinstance(update, dict):
continue
for message in update.get("messages", []):
if message.type == "tool":
print(
f"\n[工具 {message.name}] "
f"{message.content}"
)
print()
此示例假设当前程序中的 agent 是前面定义的天气 Agent。
如果使用不包含天气工具的其他 Agent,应替换为对应的工具及输入。
另外,使用多个流式模式时,不要混淆事件数据与完整执行状态。
messages 提供模型生成过程中的片段,updates 提供节点完成后的更新。
十一、Agent 在什么情况下停止?
通常情况下,当模型不再返回工具调用请求时,Agent 会结束当前工具调用循环。
但这不是唯一的停止条件。
| 条件 | 行为 |
|---|---|
| 没有工具调用 | 模型生成最终回答,正常结束 |
return_direct |
满足直接返回条件时,在工具执行后结束 |
| 结构化输出完成 | 满足结构化输出策略的完成条件后结束 |
| 中间件结束执行 | 中间件可根据受支持的执行控制机制终止循环 |
| 达到图执行限制 | 可能触发相应异常或限制处理流程 |
| 执行中断 | 暂停并等待恢复,不等于正常完成 |
| 模型或工具错误 | 可能抛出异常或进入错误处理流程 |
1、模型不再调用工具
这是最常见的正常结束条件。
例如,模型收到问题后直接生成回答:
AIMessage(
content="Python 是一种通用编程语言。",
tool_calls=[]
)
由于没有工具调用请求,Agent 不需要再进入 Tools 节点。
2、return_direct=True
在支持该行为的 Agent 中,工具设置 return_direct=True 后,可以在满足直接返回条件时结束循环。
需要注意,当前 create_agent() 对同一批客户端工具调用会综合判断其直接返回设置。
不能简单认为一批工具里只要有一个设置了 return_direct=True,就一定立即结束。
此外,直接返回时最后一条消息可能是 ToolMessage。
3、结构化输出完成
如果 create_agent() 配置了 response_format,Agent 可以在满足对应结构化输出策略时结束。
结构化数据通常通过:
result["structured_response"]
获取。
不能认为只要某条模型回复看起来像 JSON,Agent 就一定已经完成结构化输出流程。
4、中间件控制结束
LangChain 中间件可以在相应的执行钩子中控制 Agent 后续流程。
例如,中间件可以根据执行状态决定结束当前运行。
但这需要使用当前 LangChain 版本支持的中间件状态更新或路由机制,不应通过修改普通变量实现。
5、执行限制
如果 Agent 一直请求工具,可能造成循环次数过多。
可以通过 recursion_limit 设置图的执行步数限制。
但达到限制时不代表任务正常完成,可能抛出 GraphRecursionError。
6、中断与暂停
Agent 还可以通过 LangGraph 的中断机制暂停执行。
例如,在工具执行前等待用户批准。
暂停并不等于结束。如果需要继续完成任务,还需要按照对应的恢复流程执行。
十二、实例:比较无工具与有工具的执行流程
下面通过两个 Agent 对比消息历史的变化。
实例
实例
from langchain.agents import create_agent
# 无工具 Agent
agent_without_tools = create_agent(
model="deepseek:deepseek-flash",
tools=[]
)
@tool
def search_course(keyword: str) -> str:
"""查询指定关键词的模拟课程。"""
data = {
"python": "Python 基础、Python 数据分析",
"java": "Java 基础、Java 面向对象"
}
return data.get(
keyword.lower(),
"没有找到相关模拟课程"
)
# 有工具 Agent
agent_with_tools = create_agent(
model="deepseek:deepseek-flash",
tools=[search_course],
system_prompt=(
"你是编程课程助手。"
"查询课程时使用工具。"
"工具结果是模拟数据。"
)
)
def show_messages(agent, question):
result = agent.invoke({
"messages": [
{
"role": "user",
"content": question
}
]
})
print("\n问题:", question)
print("消息总数:", len(result["messages"]))
for message in result["messages"]:
if message.type == "ai" and message.tool_calls:
print(
"[AI 工具调用]",
[
call["name"]
for call in message.tool_calls
]
)
elif message.type == "tool":
print(
"[工具结果]",
message.name,
message.content
)
else:
print(
f"[{message.type}]",
str(message.content)[:100]
)
# 不使用工具
show_messages(
agent_without_tools,
"用一句话介绍 Python。"
)
# 使用工具
show_messages(
agent_with_tools,
"请使用工具搜索 python 课程。"
)
无工具场景一般包含用户消息与模型回复。
有工具场景如果模型发起一次工具调用,通常会包含:
human ai(包含 tool_calls) tool ai(最终回答)
但是,如果模型调用多个工具或没有选择工具,消息数量也会发生变化。
因此,不能将消息总数作为判断工具执行是否成功的唯一标准。
更可靠的方式是检查对应的 tool_calls、ToolMessage 和最终状态。
十三、限制 Agent 的执行步数
Agent 可能连续调用多个工具,如果工具结果不满足任务需要,就可能不断循环。
LangGraph 提供 recursion_limit,用于限制图执行步数。
实例
实例
try:
result = agent.invoke(
{
"messages": [
{
"role": "user",
"content": (
"请查询杭州的模拟天气和模拟时间。"
)
}
]
},
config={
"recursion_limit": 15
}
)
print(result["messages"][-1].content)
except GraphRecursionError:
print(
"Agent 达到执行步数限制,"
"任务未正常完成。"
)
这里的 recursion_limit=15 表示图执行的步数预算,并不是严格限制最多调用 15 次工具。
实际应用中,还应根据业务情况限制:
- 模型调用次数。
- 工具调用次数。
- 请求超时时间。
- API Token 消耗。
- 最大任务执行时长。
如果任务达到了限制,应明确向用户说明没有完成,而不是将部分结果伪装为成功结果。
十四、invoke() 与 stream() 的区别
LangChain Agent 同时支持同步和异步调用,以及完整结果和流式输出。
| 方法 | 返回方式 | 主要场景 |
|---|---|---|
invoke() |
同步等待执行结果 | 普通脚本、简单后端接口 |
ainvoke() |
异步等待执行结果 | 异步 Web 服务 |
stream() |
同步迭代流式事件 | 执行追踪、实时输出 |
astream() |
异步迭代流式事件 | SSE、WebSocket、异步服务 |
选择哪一种方式,取决于应用是否需要实时展示执行过程,以及程序是否使用异步架构。
实例:异步流式调用
实例
from langchain.agents import create_agent
agent = create_agent(
model="deepseek:deepseek-flash",
tools=[]
)
async def main():
async for event in agent.astream(
{
"messages": [
{
"role": "user",
"content": "介绍 Python 的三个优点。"
}
]
},
stream_mode="messages",
version="v2"
):
if event["type"] != "messages":
continue
message_chunk, metadata = event["data"]
if metadata.get("langgraph_node") == "model":
print(
message_chunk.text,
end="",
flush=True
)
print()
if __name__ == "__main__":
asyncio.run(main())
这种方式适合与 FastAPI 等异步 Web 框架配合,实现流式聊天接口。
但 astream() 本身只是异步事件迭代器,并不会自动创建 SSE 或 WebSocket 服务。还需要由 Web 框架完成事件传输。
十五、thread_id:管理不同对话线程
如果希望 Agent 在多次调用之间保留对话历史,可以结合 Checkpointer 使用 thread_id。
thread_id 用于区分不同会话。
需要注意,**仅传入 thread_id 并不能自动保存对话历史**,还需要配置 Checkpointer。
实例:保存同一会话的历史
实例
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent(
model="deepseek:deepseek-flash",
tools=[],
checkpointer=InMemorySaver(),
system_prompt="你是一名编程学习助手。"
)
config = {
"configurable": {
"thread_id": "conversation-001"
}
}
# 第一轮对话
agent.invoke(
{
"messages": [
{
"role": "user",
"content": "我正在学习 Python。"
}
]
},
config=config
)
# 第二轮对话
result = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "我刚才说在学习什么?"
}
]
},
config=config
)
print(result["messages"][-1].content)
由于两次调用使用相同 thread_id,第二轮可以访问 Checkpointer 保存的此前状态。
InMemorySaver 只在当前 Python 进程中保存数据,程序退出后通常会丢失。
真实项目如果需要长期保存对话,应使用数据库支持的 Checkpointer。
stream() 同样支持 thread_id
实例
for event in agent.stream(
{
"messages": [
{
"role": "user",
"content": "再给我推荐一个 Python 学习方向。"
}
]
},
config=config,
stream_mode="updates",
version="v2"
):
if event["type"] != "updates":
continue
print(event["data"])
使用 invoke() 和 stream() 都可以传入 config。
如果希望预先绑定配置,也可以使用:
session_agent = agent.with_config(
configurable={
"thread_id": "conversation-001"
}
)
之后通过 session_agent.invoke() 或 session_agent.stream() 执行调用。
注意:在 Web 应用中,不同用户或会话必须使用经过应用层管理的线程标识,并实现权限隔离,避免一个用户读取另一个用户的聊天记录。
十六、完整实例:同时追踪工具与最终回答
下面将前面内容整合为一个完整的 Agent 工作流程观察程序。
文件名:agent_workflow_demo.py
完整实例
from dotenv import load_dotenv
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
load_dotenv()
# -------------------------
# 模拟工具
# -------------------------
@tool
def get_weather(city: str) -> str:
"""查询指定城市的模拟天气。"""
data = {
"杭州": "晴,25°C",
"北京": "多云,18°C"
}
return data.get(
city,
"没有该城市的模拟天气"
)
@tool
def get_time(city: str) -> str:
"""查询指定城市的模拟时间。"""
data = {
"杭州": "14:30",
"北京": "14:30"
}
return data.get(
city,
"没有该城市的模拟时间"
)
# -------------------------
# 创建 Agent
# -------------------------
model = init_chat_model(
"deepseek:deepseek-flash",
timeout=30,
max_retries=2
)
agent = create_agent(
model=model,
tools=[get_weather, get_time],
system_prompt=(
"你是一名中文助手。"
"工具提供的数据都是模拟数据。"
"遇到天气或时间查询时使用工具。"
"不要声称模拟数据是实时数据。"
)
)
# -------------------------
# 追踪执行过程
# -------------------------
def trace_agent(question: str):
inputs = {
"messages": [
{
"role": "user",
"content": question
}
]
}
final_ai_message = None
print("\n问题:", question)
print("=" * 50)
for event in agent.stream(
inputs,
stream_mode="updates",
version="v2",
config={
"recursion_limit": 15
}
):
if event["type"] != "updates":
continue
for node_name, update in event["data"].items():
print("\n执行节点:", node_name)
if not isinstance(update, dict):
continue
for message in update.get("messages", []):
if message.type == "ai":
if message.tool_calls:
for call in message.tool_calls:
print(
"[工具请求]",
call["name"],
call["args"]
)
else:
final_ai_message = message
elif message.type == "tool":
print(
"[工具结果]",
message.name,
message.content
)
print("\n=== 最终回答 ===")
if final_ai_message is not None:
print(final_ai_message.content)
else:
print(
"没有观察到完整的最终 AI 回复,"
"请检查执行过程。"
)
if __name__ == "__main__":
trace_agent(
"请查询杭州的模拟天气和模拟时间。"
)
运行:
python agent_workflow_demo.py
如果 Agent 依次完成模型工具请求、工具执行和最终回复,就能观察整个工作流程。
这类追踪方法适合:
- 检查 Agent 是否正确选择工具。
- 查看工具实际使用了什么参数。
- 观察工具是否返回预期数据。
- 判断是否发生重复调用。
- 分析 Agent 执行路径。
由于模型行为是动态的,工具调用顺序、事件数量和最终措辞可能有所不同。
十七、常见问题
1、Agent 一定会调用工具吗?
不一定。
即使注册了工具,模型也可能认为不需要使用工具,直接生成回答。
如果业务要求必须执行工具,应该在程序或工作流中明确控制。
2、为什么一次 Model 节点会请求多个工具?
因为部分支持 Tool Calling 的模型可以在同一轮响应中产生多个工具调用请求。
例如,同时查询两个城市的天气。
但是否使用多个工具调用,仍取决于模型和相关设置。
3、stream_mode="messages" 返回的是一个个 Token 吗?
不一定。
返回的是消息片段和元数据。一个片段可能包含一段文本、工具参数的一部分或其他结构化内容。
4、为什么 stream_mode="updates" 的事件数量不固定?
因为 Agent 的执行路径是动态的。
模型可能直接回复,也可能调用一个或多个工具,还可能进行多轮工具调用。
5、为什么 Agent 的最后一条消息不是 AIMessage?
可能是因为工具使用了 return_direct=True,Agent 在工具执行后直接结束。
也可能是因为图处于暂停或异常状态,尚未产生最终回复。
应根据具体执行状态进行判断。
6、为什么设置 thread_id 后没有记住对话?
因为 thread_id 只是会话标识。
还需要配置 Checkpointer,才能保存和恢复会话状态。
7、recursion_limit 是否等于最大工具调用次数?
不是。
它限制的是图执行步数,不是严格的模型请求次数或工具调用次数。
8、怎样查看完整 Agent 状态?
可以使用 stream_mode="values" 观察执行过程中的状态快照。
如果配置了 Checkpointer,还可以在执行后使用 get_state(config) 查看已保存状态。
9、如何在前端展示 Agent 执行进度?
可以使用 stream_mode="updates" 显示节点执行步骤,通过 messages 显示生成中的文本,通过 custom 显示业务进度。
然后使用 FastAPI 等后端框架将事件转换为 SSE 或 WebSocket 消息。
10、什么是新的 Event Streaming API?
LangChain v1.3 引入了新的 Event Streaming API,例如 stream_events(),提供不同类型事件的独立访问方式。
对于新开发的复杂流式应用,官方推荐考虑这种方式。
但本教程先以 stream() 和 astream() 为主,便于理解 Agent 的执行节点、消息片段和基本循环机制。
总结
LangChain Agent 的核心工作机制是:
模型 → 工具调用 → 工具结果 → 模型
使用 create_agent() 后,这个过程由 LangGraph 执行图负责管理。
本章节介绍了:
- Model 节点与 Tools 节点的执行关系。
stream_mode="updates":观察节点更新。stream_mode="values":观察完整状态。stream_mode="messages":流式接收模型消息。stream_mode="custom":发送自定义进度。- 使用多个流式模式组合观察 Agent。
- Agent 的正常结束、暂停和异常条件。
recursion_limit:限制图执行步数。thread_id与 Checkpointer:保存多轮对话。
对于初学者,建议首先掌握:
for event in agent.stream(
inputs,
stream_mode="updates",
version="v2"
):
print(event["data"])
通过观察模型节点与工具节点的更新,可以更直观地理解 Agent 每一步发生了什么。
最后需要记住:Agent 的执行路径并不是固定脚本。模型负责提出下一步操作,工具负责执行,而 LangGraph 则负责管理整个执行流程及其状态。
