Langchain 1.0后astream_events事件类型及生命周期简析
本文为博客园用户“孤舟晓月”原创,发布于博客园,备份与B站。若你在其他站点看到,说明它被盗了......
前置知识
langchain使用流式输出通常采用stream(同步)和astream(异步)两种模式,类似与下面的代码段:
print("开始流式输出...")
# 流式输出
for chunk in graph.stream(initial_state, config=my_config):
print(f"流式块: {chunk}")
print("流式输出测试完成!")
chunk就是大模型返回的信息。通常只包含少量的必需字段。也就存在很多问题:
比如无法取得token的细分使用量,部分模型(比如deepseek-reasoner)的封装无法在流式输出中实时呈现推理内容等等。
一、astream_event产生的事件类型
当然,langchain在1.0中也给出了解法,即使用astream_events方法。该方法会返回一系列的关键事件,以便咱们精准检测整个智能体的运行情况乃至修改相关数据。
langchain官方的参考地址为:astream_events
1. 官方文档
放个截图,方便后面的内容展开

其中我遇到过的事件类型部分摘录如下:
on_chat_model_starton_chat_model_streamon_chat_model_endon_chain_starton_chain_streamon_chain_endon_tool_starton_tool_end
.......
2. 所有事件的共性字段
上面的文档翻译一下,就是:astream_event 会迭代返回多个StreamEvent对象,所有的StreamEvent都具有下列共有字段:
| 字段名 | 类型 | 描述与作用 |
|---|---|---|
event |
string |
事件类型的唯一标识,如 ”on_chain_start”。 |
name |
string |
产生事件的组件或对象的名称,例如 ”LangGraph”、”model”、”tools”、”ChatDeepSeekCustomized”、”weather_tool”。 |
run_id |
string |
当前事件运行的唯一ID。用于标识一个特定的执行实例。 |
parent_ids |
array |
父级运行的ID列表。清晰展示了执行的层级和调用关系。例如,ChatDeepSeekCustomized 事件的 parent_ids 会包含其所属的 model 链和顶级 LangGraph 的 run_id。 |
tags |
array |
标签列表,用于分类或标记运行。常见标签如 ”graph: step: 1″、”seq: step: 1″。 |
metadata |
object |
元数据字典,包含执行的上下文信息。不同事件类型的元数据丰富程度不同,但以下LangGraph相关字段非常常见: • thread_id: 执行线程ID。 • langgraph_node: 当前所在的图节点名。 • langgraph_step: 执行步骤。 • langgraph_checkpoint_ns: 检查点命名空间。 |
| data | object |
与该事件相关的数据。该字段的内容 这取决于活动的类型。!!!重要!!! |
在官方文档中,密密麻麻列举了N多事件,遗憾的是截止2026年2月9日,官方文档并没有给出这些事件的生命周期。 于是就有了本篇文章。
接下来,我会先给出测试的DEMO和框架的输出,当然,输出已经被转换为标准的JSON格式。
坐稳,这就出发!
二、测试DEMO
"""
使用LangGraph create_agent方法创建智能体,ChatDeepSeekCustomized与多个工具的协作
该测试模拟一个复杂的任务场景,需要智能体调用多个工具才能完成
ChatDeepSeekCustomized为我基于langchain框架定制的deepseek封装,支持使用reasoner模型发起的tool-call和推理思维链透传。
如果你想运行这个测试,请:
1. 切换为langchain-deepseek库提供的ChatDeepseek封装。
2. 自行注册deepseek的API
"""
import asyncio
import os
import sys
# 添加项目根目录到Python路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from langchain_core.tools import tool
from langchain.agents import create_agent
from langgraph.checkpoint.memory import MemorySaver
from extends.custom_deepseek_chat import ChatDeepSeekCustomized
@tool
def calculator_tool(operation: str) -> str:
"""计算器工具,执行基本数学运算"""
try:
# 解析操作
result = eval(operation)
return f"计算结果: {result}"
except Exception as e:
return f"计算错误: {str(e)}"
@tool
def search_tool(query: str) -> str:
"""搜索工具,模拟信息检索"""
return f"搜索结果: 关于'{query}'的信息是模拟的,实际应用中应连接真实搜索引擎"
@tool
def analysis_tool(data: str) -> str:
"""分析工具,对数据进行分析"""
return f"分析结果: '{data}'的数据分析已完成"
@tool
def weather_tool(city: str) -> str:
"""天气工具,模拟查询天气"""
return f"天气预报: {city}的天气是晴朗的,温度约22°C"
@tool
def database_query_tool(table: str, condition: str) -> str:
"""数据库查询工具,模拟数据库查询"""
return f"数据库查询结果: 从表'{table}'中找到满足条件'{condition}'的记录共5条"
def create_complex_task_agent():
"""创建一个需要多次工具调用才能完成复杂任务的智能体"""
print("创建使用ChatDeepSeekCustomed的LangGraph智能体...")
# 设置API密钥
# !!!----------填入自己的API KEY-----------!!!
DEEPSEEK_API_KEY = "sk-你的API KEY"
# 为了获得比较清晰的events,直接使用ChatDeepSeekCustomized,启用reasoning
model = ChatDeepSeekCustomized(
model="deepseek-reasoner",
api_key=DEEPSEEK_API_KEY,
temperature=0.1,
include_reasoning_content=True # 启用推理内容输出
)
# 定义工具列表
tools = [calculator_tool, search_tool, analysis_tool, weather_tool, database_query_tool]
# 创建系统提示 - 字符串格式
system_prompt_str = """
你是一个高级AI助手,能够使用多种工具解决问题。你需要合理规划工具调用顺序,完成复杂任务。
在每次调用工具后,你会收到结果,然后根据结果决定下一步行动。 请使用推理来决定如何最好地解决问题。 你可以进行多步思考,并在必要时调用工具。
"""
# 定义checkpoint存储类型
my_cp_memory = MemorySaver()
# 创建Agent - 使用正确的参数名
graph = create_agent(
tools=tools,
model=model, # 使用我们的自定义模型
system_prompt=system_prompt_str,
checkpointer=my_cp_memory
)
return graph, tools
async def test_streaming():
"""测试流式输出,观察推理内容是否正常输出"""
print("\n开始测试流式输出...")
print("开始测试复杂任务...")
try:
from langchain.agents import AgentState
from langchain_core.runnables import RunnableConfig
# 创建智能体
graph, tools = create_complex_task_agent()
# 定义一个复杂任务,需要多次工具调用
complex_task = (
"我需要规划一次去北京的旅行。请帮我:\n"
"1. 查询北京的当前天气\n"
"2. 计算从上海到北京的距离(假设直线距离约为1000公里)\n"
"3. 基于距离计算旅行所需的油费(假设每公里油耗费用为0.8元)\n"
"4. 分析旅游预算的合理性\n"
"5. 最后给我一个完整的旅行建议"
)
print(f"任务: {complex_task}")
print("\n开始执行任务...")
class StateInAgent(AgentState):
pass
initial_state = StateInAgent(messages=[HumanMessage(content=complex_task)])
# 配置
my_config = RunnableConfig()
my_config["configurable"] = {
"thread_id": "test_streaming",
}
print("开始流式输出...")
events = []
# 测试流式输出
async for event in graph.astream_events(initial_state, config=my_config):
if event['event'] == 'on_chat_model_stream':
if not should_stop_append_on_chat_model_stream(events):
events.append(event)
print(event)
else:
events.append(event)
print(event)
print("流式输出测试完成!")
return True
except Exception as e:
print(f"流式输出测试失败: {e}")
import traceback
traceback.print_exc()
return False
def should_stop_append_on_chat_model_stream(event_list, max_count=10):
"""
因为大模型一次简单的推理就可能产生数百个on_chat_model_stream事件,这里做一个
定制化判断,如果事件队列末尾有连续10个on_chat_model_stream事件了,就停止入队列。
"""
stop_flag = False
# 判断最近的12个事件中,有多少个on_chat_model_stream事件,如果超过10个,则返回true
last_max_events = event_list[-max_count:]
for event in last_max_events:
if event['event'] == 'on_chat_model_stream':
stop_flag = True
else:
stop_flag = False
return stop_flag
async def main():
"""主函数"""
print("🧪 测试使用ChatDeepSeekCustomed的LangGraph智能体")
print("=" * 70)
await test_streaming()
print("=" * 70)
print(f"测试结束")
if __name__ == "__main__":
asyncio.run(main())
astream_events的输出
运行上述DEMO,并在print("流式输出测试完成!") 处断点,可以发现整个任务输出了上千个StreamEvent
(嘉靖帝咆哮:那些都是朕的Token啊,是朕的钱!!)
为了方便,我对事件内容进行了简化,去掉了很多跟本文无关的字段,并保留第一轮思考和调用结果;但即便如此,json长度还是很可怕,因此我把它放到了文章末尾,方便各位看官查看对照。
好了,现在我们对出现的事件进行分析,并请出deepseek帮我们绘制更加直观的流程图。
1. 事件类型、数量及出现顺序
在根据DEMO生成的json中,共出现了8种不同的事件类型。基本模式为:链/组件启动 -> 内部执行(模型流式生成/工具调用) -> 链/组件结束并流式输出。
以下是各类事件的数量、占比及简要描述:
| 序号 | 事件类型 (event) | 出现次数 | 描述 |
|---|---|---|---|
| 1 | on_chain_start |
13 | 一个“链”(Chain)或组件(如LangGraph、model、tools)开始执行。 |
| 2 | on_chain_stream |
31 | “链”在执行过程中产生中间结果(chunk),进行流式输出。 |
| 3 | on_chain_end |
13 | 一个“链”或组件执行结束,包含完整的输入和输出。 |
| 4 | on_chat_model_start |
6 | 聊天模型(如ChatDeepSeekCustomized)开始调用。 |
| 5 | on_chat_model_stream |
6 | 聊天模型产生流式输出块(chunk)。 |
| 6 | on_chat_model_end |
6 | 聊天模型调用结束,包含完整的输入和输出。 |
| 7 | on_tool_start |
6 | 工具(如weather_tool、calculator_tool)开始执行。 |
| 8 | on_tool_end |
6 | 工具执行结束,包含输入和结果。 |
2. 各类事件的特有字段(data 内容)
所有事件的核心差异体现在 data 字段的内容上,它承载了事件的具体信息。
-
on_chain_start/on_chain_end/on_chain_streamdata.input: 链开始执行时的输入。data.output(on_chain_end独有): 链执行完成后的完整输出。data.chunk(on_chain_stream独有): 链在流式输出过程中产生的中间数据块。其内部结构根据流出的节点不同而异(例如chunk.model或chunk.tools)。- 说明:这三者共同描述“链”的生命周期,
input和output在start和end中成对出现,chunk在stream中出现。
-
on_chat_model_start/on_chat_model_end/on_chat_model_streamdata.input: 模型调用时的输入,通常是格式化的消息列表。data.output(on_chat_model_end独有): 模型调用结束后的完整输出(AIMessage),包含content、tool_calls、usage_metadata等。data.chunk(on_chat_model_stream独有): 模型流式生成过程中的一个数据块(AIMessageChunk),可能只包含部分内容。- _说明:
- (1) 这三者共同描述“聊天模型”的生命周期。_
- (2)
usage_metadata中包含了模型的输入、输出等token调用,如果要进行限流,那么就应该实时检测这个字段中的内容!
-
on_tool_start/on_tool_enddata.input: 工具调用时的输入参数。data.output(on_tool_end独有): 工具执行完成后的结果(ToolMessage),包含content、name、tool_call_id等。- 说明:这两者共同描述“工具”的执行生命周期。
三、总结
1. 各个事件执行顺序的生命周期流程图
2. 关键执行路径说明:
🔵 正常执行路径(完成任务):
-
步骤1-7:AI模型接收输入并进行推理
-
步骤14:模型直接给出最终答案,工作流结束
-
总步骤:7步完成
🟡 工具调用路径(需多次循环):
-
步骤1-7:AI模型推理后决定调用工具
-
步骤8-13:执行具体工具并返回结果
-
步骤N:返回步骤2开始新一轮推理
-
可能多次循环:直到模型不再需要工具调用
-
最终步骤:从Decision节点跳转到步骤14结束
循环特点总结:
-
📊 事件成对出现:每个组件都有
start和end事件 -
🌀 嵌套结构:Model/Tools节点嵌套在最外层链中
-
🔄 可重复循环:Tools节点执行后结果回流到Model节点
-
🎯 决策点关键:Decision节点决定工作流走向
实际案例中的循环次数:
在本文完整的JSON数据中,这个循环重复执行了4次:
-
第一轮:调用
weather_tool(查询天气) -
第二轮:调用
calculator_tool(计算油费) -
第三轮:调用
search_tool(搜索旅游花费) -
第四轮:调用
analysis_tool(分析预算合理性)
每次循环都完整经历了上述流程图中的所有步骤(除最后的结束步骤外),直到第5轮模型不再调用工具,直接输出最终旅行建议,工作流结束。
附件
简化版的事件列表JSON
[
{
"event": "on_chain_start",
"data": {
"input": {
"messages": [
{
"content": "我需要规划一次去北京的旅行。请帮我:\n1.查询北京的当前天气\n2.计算从上海到北京的距离(假设直线距离约为1000公里)\n3.基于距离计算旅行所需的油费(假设每公里油耗费用为0.8元)\n4.分析旅游预算的合理性\n5.最后给我一个完整的旅行建议",
"additional_kwargs": {},
"response_metadata": {},
"_constructed_type": "HumanMessage"
}
]
}
},
"metadata": {
"thread_id": "test_streaming"
}
},
{
"event": "on_chain_start",
"data": {
"input": {
"messages": [
{
"content": "我需要规划一次去北京的旅行。请帮我:\n1.查询北京的当前天气\n2.计算从上海到北京的距离(假设直线距离约为1000公里)\n3.基于距离计算旅行所需的油费(假设每公里油耗费用为0.8元)\n4.分析旅游预算的合理性\n5.最后给我一个完整的旅行建议",
"additional_kwargs": {},
"response_metadata": {},
"_constructed_type": "HumanMessage"
}
]
}
}
},
{
"event": "on_chat_model_start",
"data": {
"input": {
"messages": [
[
{
"content": "\n你是一个高级AI助手,能够使用多种工具解决问题。你需要合理规划工具调用顺序,完成复杂任务。\n在每次调用工具后,你会收到结果,然后根据结果决定下一步行动。\n请使用推理来决定如何最好地解决问题。\n你可以进行多步思考,并在必要时调用工具。\n",
"additional_kwargs": {},
"response_metadata": {},
"_constructed_type": "SystemMessage"
},
{
"content": "我需要规划一次去北京的旅行。请帮我:\n1.查询北京的当前天气\n2.计算从上海到北京的距离(假设直线距离约为1000公里)\n3.基于距离计算旅行所需的油费(假设每公里油耗费用为0.8元)\n4.分析旅游预算的合理性\n5.最后给我一个完整的旅行建议",
"additional_kwargs": {},
"response_metadata": {},
"_constructed_type": "HumanMessage"
}
]
]
}
}
},
{
"event": "on_chat_model_stream",
"data": {
"chunk": {
"content": "",
"additional_kwargs": {
"reasoning_content": ""
},
"tool_calls": [],
"invalid_tool_calls": [],
"tool_call_chunks": [],
"_constructed_type": "AIMessageChunk"
}
}
},
{
"event": "on_chat_model_end",
"data": {
"input": {
"messages": [
[
{
"content": "\n你是一个高级AI助手,能够使用多种工具解决问题。你需要合理规划工具调用顺序,完成复杂任务。\n在每次调用工具后,你会收到结果,然后根据结果决定下一步行动。\n请使用推理来决定如何最好地解决问题。\n你可以进行多步思考,并在必要时调用工具。\n",
"_constructed_type": "SystemMessage"
},
{
"content": "我需要规划一次去北京的旅行。请帮我:\n1.查询北京的当前天气\n2.计算从上海到北京的距离(假设直线距离约为1000公里)\n3.基于距离计算旅行所需的油费(假设每公里油耗费用为0.8元)\n4.分析旅游预算的合理性\n5.最后给我一个完整的旅行建议",
"_constructed_type": "HumanMessage"
}
]
]
},
"output": {
"content": "",
"additional_kwargs": {
"reasoning_content": "我来规划这次去北京的旅行。首先,我需要查询北京的当前天气。然后计算距离和油费,再分析预算,最后给出完整的旅行建议。\n\n让我从查询北京天气开始。"
},
"tool_calls": [
{
"name": "weather_tool",
"args": {
"city": "北京"
},
"type": "tool_call"
}
],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 648,
"output_tokens": 84,
"total_tokens": 732,
"input_token_details": {
"cache_read": 640
},
"output_token_details": {
"reasoning": 39
}
},
"_constructed_type": "AIMessage"
}
}
},
{
"event": "on_chain_stream",
"data": {
"chunk": {
"messages": [
{
"content": "",
"additional_kwargs": {
"reasoning_content": "我来规划这次去北京的旅行。首先,我需要查询北京的当前天气。然后计算距离和油费,再分析预算,最后给出完整的旅行建议。\n\n让我从查询北京天气开始。"
},
"response_metadata": {
"finish_reason": "tool_calls"
},
"tool_calls": [
{
"name": "weather_tool",
"args": {
"city": "北京"
},
"type": "tool_call"
}
],
"_constructed_type": "AIMessage"
}
]
}
}
},
{
"event": "on_chain_end",
"data": {
"input": {
"messages": [
{
"content": "我需要规划一次去北京的旅行。请帮我:\n1.查询北京的当前天气\n2.计算从上海到北京的距离(假设直线距离约为1000公里)\n3.基于距离计算旅行所需的油费(假设每公里油耗费用为0.8元)\n4.分析旅游预算的合理性\n5.最后给我一个完整的旅行建议",
"additional_kwargs": {},
"response_metadata": {},
"_constructed_type": "HumanMessage"
}
]
},
"output": {
"messages": [
{
"content": "",
"additional_kwargs": {
"reasoning_content": "我来规划这次去北京的旅行。首先,我需要查询北京的当前天气。然后计算距离和油费,再分析预算,最后给出完整的旅行建议。\n\n让我从查询北京天气开始。
},
"tool_calls": [
{
"name": "weather_tool",
"args": {
"city": "北京"
},
"type": "tool_call"
}
],
"_constructed_type": "AIMessage"
}
]
}
}
},
{
"event": "on_chain_stream",
"data": {
"chunk": {
"model": {
"messages": [
{
"content": "",
"additional_kwargs": {
"reasoning_content": "我来规划这次去北京的旅行。首先,我需要查询北京的当前天气。然后计算距离和油费,再分析预算,最后给出完整的旅行建议。\n\n让我从查询北京天气开始。"
},
"response_metadata": {
"finish_reason": "tool_calls"
},
"tool_calls": [
{
"name": "weather_tool",
"args": {
"city": "北京"
},
"type": "tool_call"
}
],
"_constructed_type": "AIMessage"
}
]
}
}
}
},
{
"event": "on_chain_start",
"data": {
"input": {
"__type": "tool_call_with_context",
"state": {
"messages": [
{
"content": "我需要规划一次去北京的旅行。请帮我:\n1.查询北京的当前天气\n2.计算从上海到北京的距离(假设直线距离约为1000公里)\n3.基于距离计算旅行所需的油费(假设每公里油耗费用为0.8元)\n4.分析旅游预算的合理性\n5.最后给我一个完整的旅行建议",
"_constructed_type": "HumanMessage"
},
{
"content": "",
"additional_kwargs": {
"reasoning_content": "我来规划这次去北京的旅行。首先,我需要查询北京的当前天气。然后计算距离和油费,再分析预算,最后给出完整的旅行建议。\n\n让我从查询北京天气开始。"
},
"tool_calls": [
{
"name": "weather_tool",
"args": {
"city": "北京"
},
"type": "tool_call"
}
],
"_constructed_type": "AIMessage"
}
]
},
"tool_call": {
"args": {
"city": "北京"
},
"name": "weather_tool",
"type": "tool_call"
}
}
},
"name": "tools",
},
{
"event": "on_tool_start",
"data": {
"input": {
"city": "北京"
}
},
"name": "weather_tool"
},
{
"event": "on_tool_end",
"data": {
"input": {
"city": "北京"
},
"output": {
"content": "天气预报: 北京的天气是晴朗的,温度约22°C",
"name": "weather_tool",
"_constructed_type": "ToolMessage"
}
},
"name": "weather_tool",
},
{
"event": "on_chain_stream",
"data": {
"chunk": {
"messages": [
{
"content": "天气预报: 北京的天气是晴朗的,温度约22°C",
"name": "weather_tool",
"_constructed_type": "ToolMessage"
}
]
}
},
"name": "tools"
},
{
"event": "on_chain_end",
"data": {
"input": {
"__type": "tool_call_with_context",
"state": {
"messages": [
{
"content": "我需要规划一次去北京的旅行。请帮我:\n1.查询北京的当前天气\n2.计算从上海到北京的距离(假设直线距离约为1000公里)\n3.基于距离计算旅行所需的油费(假设每公里油耗费用为0.8元)\n4.分析旅游预算的合理性\n5.最后给我一个完整的旅行建议",
"_constructed_type": "HumanMessage"
},
{
"content": "",
"additional_kwargs": {
"reasoning_content": "我来规划这次去北京的旅行。首先,我需要查询北京的当前天气。然后计算距离和油费,再分析预算,最后给出完整的旅行建议。\n\n让我从查询北京天气开始。"
},
"tool_calls": [
{
"name": "weather_tool",
"args": {
"city": "北京"
},
"type": "tool_call"
}
],
"_constructed_type": "AIMessage"
}
]
},
"tool_call": {
"args": {
"city": "北京"
},
"name": "weather_tool",
"type": "tool_call"
}
},
"output": {
"messages": [
{
"content": "天气预报: 北京的天气是晴朗的,温度约22°C",
"name": "weather_tool",
"_constructed_type": "ToolMessage"
}
]
}
},
"name": "tools",
}
]

浙公网安备 33010602011771号