🧩 Agent 执行的核心方式
以下是Agent的主要执行方式对比:
| 执行方式 | 方法名 | 类型 | 核心特点与适用场景 |
|---|---|---|---|
| 同步调用 | invoke() | 同步 | 最基础的调用方式。传入单个输入,程序会阻塞直到Agent完成所有步骤并返回最终结果。适用于对实时性要求不高的后台任务或简单脚本。 |
| 异步调用 | ainvoke() | 异步 | invoke的异步版本。不会阻塞主线程,适合在 async 函数中调用,或在Web应用等并发环境下使用。 |
| 同步流式 | stream() | 同步 | 逐步返回结果。可以实时获取Agent执行过程中产生的数据块(如LLM的token)。适合构建命令行工具,让用户即时看到反馈。 |
| 异步流式 | astream() | 异步 | stream的异步版本。在异步环境中逐步获取输出,是构建高性能流式应用的基础。 |
| 同步批处理 | batch() | 同步 | 高效处理多个输入。默认使用线程池并行执行 invoke(),显著提升吞吐量。适合离线批量数据处理。 |
| 异步批处理 | abatch() | 异步 | batch的异步版本。在异步环境中并发处理多个输入,是构建高并发异步批处理服务的理想选择。 |
| 事件流式 | astream_events() | 异步 | 最强大的流式接口。不仅能流式输出最终结果,还能流式输出Agent内部执行过程的所有事件。是实现复杂UI反馈、日志记录、调试等高级功能的首选。 |
接下来我们主要针对astream和astream_events举例: 代码如下:
js
async def generate_label_analysis_stream_response_v2(tips: str):
system_prompt = """
你现在是一个标签分析师,请根据以下信息输出标签的分析报告:
1、标签元数据信息{label_detail_info_dict}
2、标签的维度值分布{label_dimension_distribute}
3、标签关联的人群包信息{crowd_label_relation}
要求:
1、标签的维度值分布结果需要附加饼状图(饼状图已html的格式输出,并能支持在浏览器打开),并显示各维值的占比
2、输出的分析结果以markdown的格式输出
"""
tools = [query_label_info, query_label_distribution, get_crowd_by_label]
agent = create_agent(model=llm, tools=tools, system_prompt=system_prompt)
current_node = None
"""
方案一:agent.astream({"messages": [{"role": "user", "content": tips}]}, stream_mode="messages")
astream 搭配 stream_mode="messages" 只能获得最终回复的文本流,无法看到完整的推理链条。
"""
# async for chunk in agent.astream({"messages": [{"role": "user", "content": tips}]}, stream_mode="messages"):
# print(chunk) # messages模式返回是的元组
# if chunk:
# msg, metadata = chunk
# node_name = metadata.get("langgraph_node", "")
# model = metadata.get("ls_model_name", "")
# if hasattr(msg, 'content') and msg.content:
# if current_node and current_node != node_name:
# yield f"data: {json.dumps({'content': '', 'node': current_node, 'status': 'node_completed', 'finished': True, 'model': model, 'timestamp': '2026-07-27 13:48:00'}, ensure_ascii=False)}\n\n"
# current_node = node_name
# data = {
# "content": msg.content,
# "node": node_name,
# "status": "streaming",
# "finished": False,
# "model": model,
# "timestamp": "2026-07-27 13:48:00",
# }
# yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n"
# # await asyncio.sleep(0.05)
# if current_node:
# yield f"data: {json.dumps({'content': '', 'node': current_node, 'status': 'node_completed', 'finished': False}, ensure_ascii=False)}\n\n"
# yield f"data: {json.dumps({'content': '', 'node': '', 'status': 'completed', 'finished': True}, ensure_ascii=False)}\n\n"
"""
方案二、使用 stream_events 获取推理过程。
stream_events(..., version="v2") 是来获取结构化的流式事件, 输出Agent内部执行过程的所有事件执行顺序和工具调用(tool calls) 细节, 获取Agent推理的全过程。
"""
# 4. 使用stream_events迭代流式事件
inputs = {"messages": [("user", tips)]}
async for event in agent.astream_events(inputs, version="v2"):
kind = event["event"]
# 处理不同类型的事件
if kind == "on_chain_start":
# 当开始执行一个链/节点时触发 此处可能是model,tool,langgraph的开始执行
print(f"🔄 开始执行节点: {event.get('name')}")
data = {
"content": f"<br>🔄 开始执行节点: {event.get('name')}",
"finished": False,
"model": "llm",
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
}
yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n"
elif kind == "on_chat_model_stream":
# **最常用**: 当LLM生成新token时触发,用于实现打字机效果
chunk = event["data"]["chunk"]
if chunk.content:
print(chunk.content, end="", flush=True)
data = {
"content": chunk.content,
"finished": False,
"model": "llm",
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
}
yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n"
elif kind == "on_tool_start":
# 当开始调用一个工具时触发
print(f"\n🔧 开始调用工具: {event['name']}, 参数: {event['data'].get('input')}")
data = {
"content": f"<br>🔧 开始调用工具: {event['name']}, 参数: {event['data'].get('input')}",
"finished": False,
"model": "llm",
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
}
yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n"
elif kind == "on_tool_end":
# 当工具执行完毕时触发
print(f"\n✅ 工具执行完成: {event['name']}, 结果: {event['data'].get('output')}")
data = {
"content": f"<br>✅ 工具执行完成: {event['name']}, 结果: {event['data'].get('output')}",
"finished": True,
"model": "llm",
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
}
yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n"
elif kind == "on_chain_end":
# 当整个链/节点执行完毕时触发
print(f"\n🏁 节点执行完成: {event.get('name')}")
data = {
"content": f"<br>🏁 节点执行完成: {event.get('name')}",
"finished": True,
"model": "llm",
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
}
yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n"
# 推送数据时暂停0.05秒
# await asyncio.sleep(0.05)
@router.post("/analysis")
async def label_analysis(user_message: str):
"""
标签分析智能体(Agent)
:param user_message: 用户输入的意图
:return:
"""
return StreamingResponse(
generate_label_analysis_stream_response_v2(user_message),
media_type="text/plain",
headers={
"Cache-Control": "no-cache", # 禁用缓存
"Connection": "keep-alive", # 保持连接
"Content-Type": "text/event-stream", # SSE内容类型
}
)
注意:要使用此功能,Agent实例必须是一个
Runnable对象。通过create_agent创建的Agent满足此要求。
💎 astream_events不同版本的差异
astream_events() 目前主要有三个版本:v1、v2 和 v3。简单来说,v2 是 v1 的升级替代品,而 v3 则引入了一种全新的调用模式。
📜 v1 vs v2:迭代模式的演进
v1 是早期版本,目前已被弃用 ,并计划在 LangChain 0.4.0 版本中移除。v2 是对 v1 的完全重写,旨在解决其存在的问题。
主要区别体现在以下几个方面:
-
性能与效率 :
v2版本更高效,内部实现减少了不必要的抽象和开销。 -
事件结构一致性 :
v1中,同一事件(如on_chat_model_end)在不同上下文(根级或链内)的输出结构不一致。v2对此进行了统一,使事件结构更简洁、一致。 -
事件内容丰富度:
-
移除冗余事件 :
v2移除了v1中遗留的on_retriever_stream和on_tool_stream事件,相关信息已整合到对应的_end事件中。 -
修复错误事件 :
v2修复了v1中RunnableRetry在重试时可能产生错误on_chain_end事件的问题。
🆕 v3:全新的 Awaitable 模式
v3 是比 v2 更新的版本,它不是 v2 的简单升级,而是提供了一种完全不同的调用方式。
与 v1/v2 的可迭代(Iterable) 模式不同,v3 是一个可等待(Awaitable) 对象。
-
v1 / v2 (迭代模式) :返回一个异步迭代器,使用
async for来逐个处理StreamEvent字典。python
csharpasync for event in agent.astream_events(inputs, version="v2"): # 处理 event 字典 -
v3 (等待模式) :返回一个可等待对象,需要用
await来获取一个更高级的流对象,然后可以从中提取不同部分的数据。python
csharp# 先 await 获取流对象 stream = await agent.astream_events(inputs, version="v3") # 然后可以分别迭代不同部分,例如文本和工具调用 async for chunk in stream.text: # 处理文本块 async for tool_call in stream.tool_calls(): # 处理工具调用这种设计让开发者能更有结构地处理复杂的输出流。
💎 总结与建议
对于绝大多数场景,最稳妥、最推荐的做法是坚持使用默认的 version="v2" 。它成熟稳定,功能强大,能满足绝大部分流式处理需求。
v3 则代表了未来的方向,提供了更结构化的数据消费模式。如果你需要更精细地控制复杂的输出流,并且愿意尝试最新特性,可以关注 v3 的后续发展。
至于选择哪种执行方式,取决于你的具体需求:
- 追求简单:用
invoke()或ainvoke()。 - 需要实时输出:用
stream()或astream()。 - 需要批量处理:用
batch()或abatch()。 - 需要细粒度监控和复杂交互:用
astream_events()。
astream_events 提供了最强大的控制能力,是构建生产级、高交互性AI应用的关键工具。