前面很多内容都是从大方面了解langgraph和智能体,这样我们已经有了一个很好的全局意识了,现在需要深入掌握一下langgraph的基础功能和开发细节,以此应对后续开发负责的智能体,我们第一个需要学习的就是stream,为什么要学习其他,因为这个和调试智能体有很大的关系,前面我么写了那么多代码出问题都不知道怎么调试,因此本节就是要学会怎么调试。
我们一直在使用 workflow.invoke(inputs)
方法执行工作流,invoke 方法会把整个流的最终执行结果一次性的返回给调用端,如果工作流执行时间很长,用户需要等待的时间就会很长,体验会很差,目前主流的大模型都会提供SSE流式输出接口,LangChain 本身对于流式输出做了很好的封装,本文我们来讨论一下在LangGraph 中如何使用流式输出。
from langgraph.graph import add_messages, StateGraph, START, END
from langchain_core.messages import ToolMessage, HumanMessage, AIMessage, BaseMessage
from typing import Type, TypedDict, Annotated, List
from langchain_openai import ChatOpenAI
# 初始化 DeepSeek 大模型客户端
llm = ChatDeepSeek(
model="deepseek-chat", # 指定 DeepSeek 的模型名称
api_key="" # 替换为您自己的 DeepSeek API 密钥
)
class State(TypedDict):
messages: Annotated[List[BaseMessage], add_messages]
class TestNode:
def __init__(self, msg: str):
self.msg = msg
def __call__(self, state: State):
return {"messages": self.msg}
def chatbot(state: State):
return {"messages": [llm.invoke(state["messages"])]}
graph_builder = StateGraph(State)
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_node("n2", TestNode("很高兴认识你"))
graph_builder.add_edge(START, "chatbot")
graph_builder.add_edge("chatbot", "n2")
graph_builder.add_edge("n2", END)
app = graph_builder.compile()
app.get_graph().draw_mermaid_png(output_file_path="graph_tool.png")
inputs = {"messages": [HumanMessage(content="你好,你是谁?")]}
result = app.invoke(inputs)
for i in result.get("messages"):
i.pretty_print()
代码使用 result = app.invoke(inputs)
,result 为整个工作流最终的结果,也就是上面定义的 State
对象。
State 对象有个 messages
属性,我们使用for 循环来遍历 messages 中的元素,得到的输出为
================================ Human Message =================================
你好,你是谁?
================================== Ai Message ==================================
你好!我是DeepSeek Chat,由深度求索公司创造的智能助手。我可以帮你解答各种问题、提供信息、聊天交流,还能处理上传的文档。如果有任何问题或需要帮助,尽管问我吧!
================================ Human Message =================================
很高兴认识你
pretty_print()
为langchain 中封装的方法,可以将大模型中的消息打印的相对好看一些。
通过上面代码结果,可以看到,最终的结果需要所有的节点都执行完毕才返回。接下来我们看一看如何使用流式输出,一步一步的将每个节点的输出依次打印。
inputs = {"messages":[HumanMessage(content="你好,你是谁?")]}
for i in app.stream(inputs):
print(i)
将上面的 result = app.invoke(inputs)
修改为 app.stream(inputs)
,这个方法会返回一个迭代器,Iterator[Union[dict[str, Any], Any]]
, 我们可以遍历这个迭代器来分别打印每个节点的结果输出。
{'chatbot': {'messages': [AIMessage(content='你好!我是DeepSeek Chat,一个由深度求索公司(DeepSeek)开发的智能AI助手。我可以帮你解答问题、提供建议、聊天交流,或者协助处理各种文本相关的任务。如果有任何问题或需要帮助,随时问我哦!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 56, 'prompt_tokens': 7, 'total_tokens': 63, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 7}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': '17c0c01b-b696-4a42-babf-5577446c5186', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--c87af3f9-bcd5-4dba-91c2-0819840237b1-0', usage_metadata={'input_tokens': 7, 'output_tokens': 56, 'total_tokens': 63, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}})]}}
{'n2': {'messages': '很高兴认识你'}}
通过返回结果,我们可以看出,返回的迭代对象,每个元素为一个字典对象,字典的key 为节点的 name, 值为该节点返回的内容。注意,这里只会返回 State 中定义的属性,如果节点中返回了额外的值,这里是不会返回的,如
class TestNode:
def __init__(self, msg: str):
self.msg = msg
def __call__(self, state: State):
return {"messages": self.msg, "timestamp": time.time()}
假如 TestNode 中返回了 timestamp
,app.stream(inputs)
的返回值中也不会包含 timestamp 值的。
stream 方法还有一个 stream_mode
参数,该参数有以下几个值可选
write: StreamWriter
kwarg of each node.我们来分别看看各值的效果
values 为当每个节点执行结束以后,State 对象更新以后的值,如上面的State定义
inputs = {"messages":[HumanMessage(content="你好,你是谁?")]}
for i in app.stream(inputs,stream_mode="values"):
print(i)
{'messages': [HumanMessage(content='你好,你是谁?', additional_kwargs={}, response_metadata={}, id='2f766f47-62f5-4cdd-b052-d3e701722b1c')]}
{'messages': [HumanMessage(content='你好,你是谁?', additional_kwargs={}, response_metadata={}, id='2f766f47-62f5-4cdd-b052-d3e701722b1c'), AIMessage(content='你好!我是DeepSeek Chat,一个由深度求索公司(DeepSeek)开发的智能AI助手。我可以帮你解答问题、提供建议、处理文本、分析数据,甚至陪你聊天! \n\n有什么我可以帮你的吗?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 51, 'prompt_tokens': 7, 'total_tokens': 58, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 7}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': 'f5c91f17-35b9-450a-96f1-02d58297a2be', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--49296890-29a6-412e-95b0-8ffd56f28acd-0', usage_metadata={'input_tokens': 7, 'output_tokens': 51, 'total_tokens': 58, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}})]}
{'messages': [HumanMessage(content='你好,你是谁?', additional_kwargs={}, response_metadata={}, id='2f766f47-62f5-4cdd-b052-d3e701722b1c'), AIMessage(content='你好!我是DeepSeek Chat,一个由深度求索公司(DeepSeek)开发的智能AI助手。我可以帮你解答问题、提供建议、处理文本、分析数据,甚至陪你聊天! \n\n有什么我可以帮你的吗?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 51, 'prompt_tokens': 7, 'total_tokens': 58, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 7}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': 'f5c91f17-35b9-450a-96f1-02d58297a2be', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--49296890-29a6-412e-95b0-8ffd56f28acd-0', usage_metadata={'input_tokens': 7, 'output_tokens': 51, 'total_tokens': 58, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}), HumanMessage(content='很高兴认识你', additional_kwargs={}, response_metadata={}, id='f928a0aa-2fd3-4661-8774-48cf4174a572')]}
values 为当每个节点执行结束以后,State 对象更新以后的值,如上面的State定义
1 2 |
|
State 中有个messages 属性,这个属性的更新方法为 add_messages
, 当__START__
节点执行结束以后,__START__
节点可以理解为调用 app.stream(inputs)
以后,当首次传入 inputs 以后,此时的State 对象经过 add_messages 方法更新以后的对象。此时State对象为
1 |
|
之后执行 chatbot 节点,该节点调用大模型,
1 2 |
|
返回大模型的回复,经过 add_messages 以后,将大模型回复的内容追加到 State 对象的 messages 中,此时的State对象为:(此处打印进行了省略和格式化)
updates 为更新,这种模式下,会显示哪个节点更新了哪些内容,但是并不会将State所有的值返回,只返回更新的内容。
inputs = {"messages":[HumanMessage(content="你好")]}
for i in app.stream(inputs,stream_mode="updates"):
print(i)
1 2 3 4 |
|
第一次经过 __START__
节点时,由于此时并没有更新操作,所以执行完 __START__
节点以后, 不会有什么输出。
执行完 chatbot 节点以后,return {"messages": [llm.invoke(state["messages"])]}
, 返回了大模型的输出,此时是需要更新State 中的 messages 的,更新的内容为 {'messages': [AIMessage(content='你好!很高兴为你提供帮助。'...)]}
, 也就是chatbot 节点返回的内容。
同理执行完 n2 节点以后,返回的是 {'messages': '很高兴认识你'}
。
updates 模式下,可以理解为返回的是需要更新State的内容或者节点本身返回的内容。
实际应用时主要用到的是 values 和 updates ,还有几个模式,我们简单看一下
debug 模式,会将每个节点执行时的输入和输出返回,以及一些运行时的信息,方便我们进行调试
inputs = {"messages":[HumanMessage(content="你好")]}
for i in app.stream(inputs,stream_mode="debug"):
print(i)
{'type': 'task', 'timestamp': '2025-06-21T12:05:42.663064+00:00', 'step': 1, 'payload': {'id': '374a5e83-a2dc-8fbd-b123-ef85f02f217b', 'name': 'chatbot', 'input': {'messages': [HumanMessage(content='你好', additional_kwargs={}, response_metadata={}, id='7a04d4a8-1291-4460-987c-a7d853a4a0d6')]}, 'triggers': ('branch:to:chatbot',)}}
{'type': 'task_result', 'timestamp': '2025-06-21T12:05:45.717225+00:00', 'step': 1, 'payload': {'id': '374a5e83-a2dc-8fbd-b123-ef85f02f217b', 'name': 'chatbot', 'error': None, 'result': [('messages', [AIMessage(content='你好! 很高兴见到你~有什么我可以帮你的吗?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 4, 'total_tokens': 19, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 4}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': '966c5c46-bf12-4342-bdb1-5702edd08c3b', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--5a9f71c7-fb28-4b94-86ff-dd678fca1951-0', usage_metadata={'input_tokens': 4, 'output_tokens': 15, 'total_tokens': 19, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}})])], 'interrupts': []}}
{'type': 'task', 'timestamp': '2025-06-21T12:05:45.717225+00:00', 'step': 2, 'payload': {'id': 'd23c2392-eaaf-4aa7-881d-826ff91158b9', 'name': 'n2', 'input': {'messages': [HumanMessage(content='你好', additional_kwargs={}, response_metadata={}, id='7a04d4a8-1291-4460-987c-a7d853a4a0d6'), AIMessage(content='你好! 很高兴见到你~有什么我可以帮你的吗?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 4, 'total_tokens': 19, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 4}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': '966c5c46-bf12-4342-bdb1-5702edd08c3b', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--5a9f71c7-fb28-4b94-86ff-dd678fca1951-0', usage_metadata={'input_tokens': 4, 'output_tokens': 15, 'total_tokens': 19, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}})]}, 'triggers': ('branch:to:n2',)}}
{'type': 'task_result', 'timestamp': '2025-06-21T12:05:45.719815+00:00', 'step': 2, 'payload': {'id': 'd23c2392-eaaf-4aa7-881d-826ff91158b9', 'name': 'n2', 'error': None, 'result': [('messages', '很高兴认识你')], 'interrupts': []}}
messages 模式下,只会将大模型进行token_by_token流式返回
inputs = {"messages":[HumanMessage(content="你好")]}
for i in app.stream(inputs,stream_mode="messages"):
print(i)
(AIMessageChunk(content='', additional_kwargs={}, response_metadata={}, id='run--666c41f4-e2b8-4239-ad8f-fa7f69d68b57'), {'langgraph_step': 1, 'langgraph_node': 'chatbot', 'langgraph_triggers': ('branch:to:chatbot',), 'langgraph_path': ('__pregel_pull', 'chatbot'), 'langgraph_checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'ls_provider': 'openai', 'ls_model_name': 'deepseek-chat', 'ls_model_type': 'chat', 'ls_temperature': None})
(AIMessageChunk(content='你好', additional_kwargs={}, response_metadata={}, id='run--666c41f4-e2b8-4239-ad8f-fa7f69d68b57'), {'langgraph_step': 1, 'langgraph_node': 'chatbot', 'langgraph_triggers': ('branch:to:chatbot',), 'langgraph_path': ('__pregel_pull', 'chatbot'), 'langgraph_checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'ls_provider': 'openai', 'ls_model_name': 'deepseek-chat', 'ls_model_type': 'chat', 'ls_temperature': None})
(AIMessageChunk(content='!', additional_kwargs={}, response_metadata={}, id='run--666c41f4-e2b8-4239-ad8f-fa7f69d68b57'), {'langgraph_step': 1, 'langgraph_node': 'chatbot', 'langgraph_triggers': ('branch:to:chatbot',), 'langgraph_path': ('__pregel_pull', 'chatbot'), 'langgraph_checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'ls_provider': 'openai', 'ls_model_name': 'deepseek-chat', 'ls_model_type': 'chat', 'ls_temperature': None})
(AIMessageChunk(content='', additional_kwargs={}, response_metadata={}, id='run--666c41f4-e2b8-4239-ad8f-fa7f69d68b57'), {'langgraph_step': 1, 'langgraph_node': 'chatbot', 'langgraph_triggers': ('branch:to:chatbot',), 'langgraph_path': ('__pregel_pull', 'chatbot'), 'langgraph_checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'ls_provider': 'openai', 'ls_model_name': 'deepseek-chat', 'ls_model_type': 'chat', 'ls_temperature': None})
(AIMessageChunk(content=' ', additional_kwargs={}, response_metadata={}, id='run--666c41f4-e2b8-4239-ad8f-fa7f69d68b57'), {'langgraph_step': 1, 'langgraph_node': 'chatbot', 'langgraph_triggers': ('branch:to:chatbot',), 'langgraph_path': ('__pregel_pull', 'chatbot'), 'langgraph_checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'ls_provider': 'openai', 'ls_model_name': 'deepseek-chat', 'ls_model_type': 'chat', 'ls_temperature': None})
(AIMessageChunk(content='很高兴', additional_kwargs={}, response_metadata={}, id='run--666c41f4-e2b8-4239-ad8f-fa7f69d68b57'), {'langgraph_step': 1, 'langgraph_node': 'chatbot', 'langgraph_triggers': ('branch:to:chatbot',), 'langgraph_path': ('__pregel_pull', 'chatbot'), 'langgraph_checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'ls_provider': 'openai', 'ls_model_name': 'deepseek-chat', 'ls_model_type': 'chat', 'ls_temperature': None})
(AIMessageChunk(content='见到', additional_kwargs={}, response_metadata={}, id='run--666c41f4-e2b8-4239-ad8f-fa7f69d68b57'), {'langgraph_step': 1, 'langgraph_node': 'chatbot', 'langgraph_triggers': ('branch:to:chatbot',), 'langgraph_path': ('__pregel_pull', 'chatbot'), 'langgraph_checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'ls_provider': 'openai', 'ls_model_name': 'deepseek-chat', 'ls_model_type': 'chat', 'ls_temperature': None})
(AIMessageChunk(content='你', additional_kwargs={}, response_metadata={}, id='run--666c41f4-e2b8-4239-ad8f-fa7f69d68b57'), {'langgraph_step': 1, 'langgraph_node': 'chatbot', 'langgraph_triggers': ('branch:to:chatbot',), 'langgraph_path': ('__pregel_pull', 'chatbot'), 'langgraph_checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'ls_provider': 'openai', 'ls_model_name': 'deepseek-chat', 'ls_model_type': 'chat', 'ls_temperature': None})
(AIMessageChunk(content='~', additional_kwargs={}, response_metadata={}, id='run--666c41f4-e2b8-4239-ad8f-fa7f69d68b57'), {'langgraph_step': 1, 'langgraph_node': 'chatbot', 'langgraph_triggers': ('branch:to:chatbot',), 'langgraph_path': ('__pregel_pull', 'chatbot'), 'langgraph_checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'ls_provider': 'openai', 'ls_model_name': 'deepseek-chat', 'ls_model_type': 'chat', 'ls_temperature': None})
(AIMessageChunk(content='有什么', additional_kwargs={}, response_metadata={}, id='run--666c41f4-e2b8-4239-ad8f-fa7f69d68b57'), {'langgraph_step': 1, 'langgraph_node': 'chatbot', 'langgraph_triggers': ('branch:to:chatbot',), 'langgraph_path': ('__pregel_pull', 'chatbot'), 'langgraph_checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'ls_provider': 'openai', 'ls_model_name': 'deepseek-chat', 'ls_model_type': 'chat', 'ls_temperature': None})
(AIMessageChunk(content='我可以', additional_kwargs={}, response_metadata={}, id='run--666c41f4-e2b8-4239-ad8f-fa7f69d68b57'), {'langgraph_step': 1, 'langgraph_node': 'chatbot', 'langgraph_triggers': ('branch:to:chatbot',), 'langgraph_path': ('__pregel_pull', 'chatbot'), 'langgraph_checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'ls_provider': 'openai', 'ls_model_name': 'deepseek-chat', 'ls_model_type': 'chat', 'ls_temperature': None})
(AIMessageChunk(content='帮', additional_kwargs={}, response_metadata={}, id='run--666c41f4-e2b8-4239-ad8f-fa7f69d68b57'), {'langgraph_step': 1, 'langgraph_node': 'chatbot', 'langgraph_triggers': ('branch:to:chatbot',), 'langgraph_path': ('__pregel_pull', 'chatbot'), 'langgraph_checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'ls_provider': 'openai', 'ls_model_name': 'deepseek-chat', 'ls_model_type': 'chat', 'ls_temperature': None})
(AIMessageChunk(content='你的', additional_kwargs={}, response_metadata={}, id='run--666c41f4-e2b8-4239-ad8f-fa7f69d68b57'), {'langgraph_step': 1, 'langgraph_node': 'chatbot', 'langgraph_triggers': ('branch:to:chatbot',), 'langgraph_path': ('__pregel_pull', 'chatbot'), 'langgraph_checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'ls_provider': 'openai', 'ls_model_name': 'deepseek-chat', 'ls_model_type': 'chat', 'ls_temperature': None})
(AIMessageChunk(content='吗', additional_kwargs={}, response_metadata={}, id='run--666c41f4-e2b8-4239-ad8f-fa7f69d68b57'), {'langgraph_step': 1, 'langgraph_node': 'chatbot', 'langgraph_triggers': ('branch:to:chatbot',), 'langgraph_path': ('__pregel_pull', 'chatbot'), 'langgraph_checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'ls_provider': 'openai', 'ls_model_name': 'deepseek-chat', 'ls_model_type': 'chat', 'ls_temperature': None})
(AIMessageChunk(content='?', additional_kwargs={}, response_metadata={}, id='run--666c41f4-e2b8-4239-ad8f-fa7f69d68b57'), {'langgraph_step': 1, 'langgraph_node': 'chatbot', 'langgraph_triggers': ('branch:to:chatbot',), 'langgraph_path': ('__pregel_pull', 'chatbot'), 'langgraph_checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'ls_provider': 'openai', 'ls_model_name': 'deepseek-chat', 'ls_model_type': 'chat', 'ls_temperature': None})
(AIMessageChunk(content='', additional_kwargs={}, response_metadata={'finish_reason': 'stop', 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8'}, id='run--666c41f4-e2b8-4239-ad8f-fa7f69d68b57', usage_metadata={'input_tokens': 4, 'output_tokens': 15, 'total_tokens': 19, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}), {'langgraph_step': 1, 'langgraph_node': 'chatbot', 'langgraph_triggers': ('branch:to:chatbot',), 'langgraph_path': ('__pregel_pull', 'chatbot'), 'langgraph_checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'checkpoint_ns': 'chatbot:cce34092-774a-d68d-0fac-d62a1fdb09d9', 'ls_provider': 'openai', 'ls_model_name': 'deepseek-chat', 'ls_model_type': 'chat', 'ls_temperature': None})
常用的模式为 updates和values, 如果我们只关心每个节点执行结束以后State 的值,那么需要使用 values 模式,如果想要获取每个节点的返回值,则需要使用 updates。
另外,stream_mode 也可以是列表形式,比如你既关心每个节点的返回,也关心State 更新以后的值,
inputs = {"messages":[HumanMessage(content="你好")]}
for i in app.stream(inputs,stream_mode=["updates", "values"]):
print(i,"\n\n")
('values', {'messages': [HumanMessage(content='你好', additional_kwargs={}, response_metadata={}, id='e9d5259b-0399-451f-98aa-65824f67a2de')]})
('updates', {'chatbot': {'messages': [AIMessage(content='你好! 很高兴见到你~有什么我可以帮你的吗?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 4, 'total_tokens': 19, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 4}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': '0ac5f76f-93b0-4207-87a4-a3528e007d37', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--701789b3-16f5-4e48-8151-f5fc91a01fdf-0', usage_metadata={'input_tokens': 4, 'output_tokens': 15, 'total_tokens': 19, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}})]}})
('values', {'messages': [HumanMessage(content='你好', additional_kwargs={}, response_metadata={}, id='e9d5259b-0399-451f-98aa-65824f67a2de'), AIMessage(content='你好! 很高兴见到你~有什么我可以帮你的吗?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 4, 'total_tokens': 19, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 4}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': '0ac5f76f-93b0-4207-87a4-a3528e007d37', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--701789b3-16f5-4e48-8151-f5fc91a01fdf-0', usage_metadata={'input_tokens': 4, 'output_tokens': 15, 'total_tokens': 19, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}})]})
('updates', {'n2': {'messages': '很高兴认识你'}})
('values', {'messages': [HumanMessage(content='你好', additional_kwargs={}, response_metadata={}, id='e9d5259b-0399-451f-98aa-65824f67a2de'), AIMessage(content='你好! 很高兴见到你~有什么我可以帮你的吗?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 4, 'total_tokens': 19, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 4}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': '0ac5f76f-93b0-4207-87a4-a3528e007d37', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--701789b3-16f5-4e48-8151-f5fc91a01fdf-0', usage_metadata={'input_tokens': 4, 'output_tokens': 15, 'total_tokens': 19, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}), HumanMessage(content='很高兴认识你', additional_kwargs={}, response_metadata={}, id='6cdc480a-1fb4-44bd-bd84-e8c59bc8cf16')]})
这种模式下,每个流式数据为一个tuple 1. 第一个元素为事件(模式)名称,如上面的 updates 或者 values 2. 第二个元素为事件的数据,和上面介绍的数据一样的。
stream_mode 默认为 updates
, 虽然查看源代码,注释中有提到说默认是 values, 但是从代码的执行结果来看,默认的行为是 updates。
上面介绍了使用 stream 方法来进行节点流式输出,并且通过 stream_mode 来控制流式输出的模式,LangGraph 作为一个大模型应用开发框架,我们会使用它开发很多基于LLM 的应用,这些应用不免会使用到大模型,为了提升用户体验,一般大模型会提供流式的输出。
通过上面的章节,stream_mode 为 messages 时,虽然可以将大模型进行 token_by_token 的输出,但是却不能同时输出节点的更新或者State 的values, 当然, 我们可以使用stream_mode=["updates", "messages"]
的方式来同时输出。
inputs = {"messages": [HumanMessage(content="你好")]}
for event, data in app.stream(inputs, stream_mode=["updates", "messages"]):
if event == "messages":
message_data, graph_data = data
print(event, {graph_data.get("langgraph_node"): message_data.to_json()})
else:
print(event, data)
print("\n")
messages {'chatbot': {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'content': '', 'type': 'AIMessageChunk', 'id': 'run--745049bd-226f-4160-b160-a4150b8623c0', 'tool_calls': [], 'invalid_tool_calls': []}}}
messages {'chatbot': {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'content': '你好', 'type': 'AIMessageChunk', 'id': 'run--745049bd-226f-4160-b160-a4150b8623c0', 'tool_calls': [], 'invalid_tool_calls': []}}}
messages {'chatbot': {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'content': '!', 'type': 'AIMessageChunk', 'id': 'run--745049bd-226f-4160-b160-a4150b8623c0', 'tool_calls': [], 'invalid_tool_calls': []}}}
messages {'chatbot': {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'content': '', 'type': 'AIMessageChunk', 'id': 'run--745049bd-226f-4160-b160-a4150b8623c0', 'tool_calls': [], 'invalid_tool_calls': []}}}
messages {'chatbot': {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'content': ' ', 'type': 'AIMessageChunk', 'id': 'run--745049bd-226f-4160-b160-a4150b8623c0', 'tool_calls': [], 'invalid_tool_calls': []}}}
messages {'chatbot': {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'content': '很高兴', 'type': 'AIMessageChunk', 'id': 'run--745049bd-226f-4160-b160-a4150b8623c0', 'tool_calls': [], 'invalid_tool_calls': []}}}
messages {'chatbot': {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'content': '见到', 'type': 'AIMessageChunk', 'id': 'run--745049bd-226f-4160-b160-a4150b8623c0', 'tool_calls': [], 'invalid_tool_calls': []}}}
messages {'chatbot': {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'content': '你', 'type': 'AIMessageChunk', 'id': 'run--745049bd-226f-4160-b160-a4150b8623c0', 'tool_calls': [], 'invalid_tool_calls': []}}}
messages {'chatbot': {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'content': '~', 'type': 'AIMessageChunk', 'id': 'run--745049bd-226f-4160-b160-a4150b8623c0', 'tool_calls': [], 'invalid_tool_calls': []}}}
messages {'chatbot': {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'content': '有什么', 'type': 'AIMessageChunk', 'id': 'run--745049bd-226f-4160-b160-a4150b8623c0', 'tool_calls': [], 'invalid_tool_calls': []}}}
messages {'chatbot': {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'content': '我可以', 'type': 'AIMessageChunk', 'id': 'run--745049bd-226f-4160-b160-a4150b8623c0', 'tool_calls': [], 'invalid_tool_calls': []}}}
messages {'chatbot': {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'content': '帮', 'type': 'AIMessageChunk', 'id': 'run--745049bd-226f-4160-b160-a4150b8623c0', 'tool_calls': [], 'invalid_tool_calls': []}}}
messages {'chatbot': {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'content': '你的', 'type': 'AIMessageChunk', 'id': 'run--745049bd-226f-4160-b160-a4150b8623c0', 'tool_calls': [], 'invalid_tool_calls': []}}}
messages {'chatbot': {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'content': '吗', 'type': 'AIMessageChunk', 'id': 'run--745049bd-226f-4160-b160-a4150b8623c0', 'tool_calls': [], 'invalid_tool_calls': []}}}
messages {'chatbot': {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'content': '?', 'type': 'AIMessageChunk', 'id': 'run--745049bd-226f-4160-b160-a4150b8623c0', 'tool_calls': [], 'invalid_tool_calls': []}}}
messages {'chatbot': {'lc': 1, 'type': 'constructor', 'id': ['langchain', 'schema', 'messages', 'AIMessageChunk'], 'kwargs': {'content': '', 'response_metadata': {'finish_reason': 'stop', 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8'}, 'type': 'AIMessageChunk', 'id': 'run--745049bd-226f-4160-b160-a4150b8623c0', 'usage_metadata': {'input_tokens': 4, 'output_tokens': 15, 'total_tokens': 19, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}, 'tool_calls': [], 'invalid_tool_calls': []}}}
updates {'chatbot': {'messages': [AIMessage(content='你好! 很高兴见到你~有什么我可以帮你的吗?', additional_kwargs={}, response_metadata={'finish_reason': 'stop', 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8'}, id='run--745049bd-226f-4160-b160-a4150b8623c0', usage_metadata={'input_tokens': 4, 'output_tokens': 15, 'total_tokens': 19, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}})]}}
updates {'n2': {'messages': '很高兴认识你'}}
使用 stream_mode 为 ["updates", "messages"]
可以同时返回大模型的token_by_token 输出以及节点执行结束以后需要updates的输出。此方式虽然可以暂时解决同时输出大模型流式和节点流式,但是局限性也很明显,这里推荐使用另外一个方法,astream_events
, 很奇怪的是,这个方法是异步的,但是 LangChain 却没有定义 stream_events
方法,一般 LangChain 会同时定义同步和异步方法,如 invoke
与 ainvoke
, stream
与 astream
, 但是这个方法只有astream_events
。且官方文档中介绍,在LangGraph 中其实没有必要使用的,
但是我觉得这个方法还是很灵活的,所以这里也简单的介绍一下它的使用方法吧。
我们可以将State 的更新与大模型的执行等操作理解为“事件” events, 每一种操作都是一种事件,如当节点开始执行,节点执行结束,收到大模型的流式输出,调用工具等等,这些可以理解为事件,LangChain 内部定义了很多事件,详情可参考langchain官方文档 https://python.langchain.com/docs/how_to/streaming/#event-reference
当使用 astream_events
,会得到很多输出,这里应该只关注我们想要的事件,比如节点执行结束,和大模型流式输出。修改一下原来的代码,改为异步调用
async def main():
inputs = {"messages": [HumanMessage(content="你好")]}
async for event in app.astream_events(inputs, version="v2"):
kind = event.get("event")
data = event.get("data")
name = event.get("name")
if name == "_write":
continue
if kind == "on_chain_end":
ydata = {
"kind": kind,
"name": name,
"data": data
}
elif kind == "on_chat_model_stream":
ydata = {
"kind": kind,
"name": name,
"node": event.get("metadata").get("langgraph_node"),
"data": event["data"]["chunk"].content
}
else:
continue
print(ydata)
# 确保异步执行
await main() # 在 Jupyter Notebook 里直接运行
{'kind': 'on_chat_model_stream', 'name': 'ChatDeepSeek', 'node': 'chatbot', 'data': ''}
{'kind': 'on_chat_model_stream', 'name': 'ChatDeepSeek', 'node': 'chatbot', 'data': '你好'}
{'kind': 'on_chat_model_stream', 'name': 'ChatDeepSeek', 'node': 'chatbot', 'data': '!'}
{'kind': 'on_chat_model_stream', 'name': 'ChatDeepSeek', 'node': 'chatbot', 'data': ''}
{'kind': 'on_chat_model_stream', 'name': 'ChatDeepSeek', 'node': 'chatbot', 'data': ' '}
{'kind': 'on_chat_model_stream', 'name': 'ChatDeepSeek', 'node': 'chatbot', 'data': '很高兴'}
{'kind': 'on_chat_model_stream', 'name': 'ChatDeepSeek', 'node': 'chatbot', 'data': '见到'}
{'kind': 'on_chat_model_stream', 'name': 'ChatDeepSeek', 'node': 'chatbot', 'data': '你'}
{'kind': 'on_chat_model_stream', 'name': 'ChatDeepSeek', 'node': 'chatbot', 'data': '~'}
{'kind': 'on_chat_model_stream', 'name': 'ChatDeepSeek', 'node': 'chatbot', 'data': '有什么'}
{'kind': 'on_chat_model_stream', 'name': 'ChatDeepSeek', 'node': 'chatbot', 'data': '我可以'}
{'kind': 'on_chat_model_stream', 'name': 'ChatDeepSeek', 'node': 'chatbot', 'data': '帮'}
{'kind': 'on_chat_model_stream', 'name': 'ChatDeepSeek', 'node': 'chatbot', 'data': '你的'}
{'kind': 'on_chat_model_stream', 'name': 'ChatDeepSeek', 'node': 'chatbot', 'data': '吗'}
{'kind': 'on_chat_model_stream', 'name': 'ChatDeepSeek', 'node': 'chatbot', 'data': '?'}
{'kind': 'on_chat_model_stream', 'name': 'ChatDeepSeek', 'node': 'chatbot', 'data': ''}
{'kind': 'on_chain_end', 'name': 'chatbot', 'data': {'output': {'messages': [AIMessage(content='你好! 很高兴见到你~有什么我可以帮你的吗?', additional_kwargs={}, response_metadata={'finish_reason': 'stop', 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8'}, id='run--02507d9a-e444-4b60-88dc-c2f78acb2e5c', usage_metadata={'input_tokens': 4, 'output_tokens': 15, 'total_tokens': 19, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}})]}, 'input': {'messages': [HumanMessage(content='你好', additional_kwargs={}, response_metadata={}, id='1548ecc6-61ac-41d9-8520-dbccd8b687f9')]}}}
{'kind': 'on_chain_end', 'name': 'n2', 'data': {'output': {'messages': '很高兴认识你'}, 'input': {'messages': [HumanMessage(content='你好', additional_kwargs={}, response_metadata={}, id='1548ecc6-61ac-41d9-8520-dbccd8b687f9'), AIMessage(content='你好! 很高兴见到你~有什么我可以帮你的吗?', additional_kwargs={}, response_metadata={'finish_reason': 'stop', 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8'}, id='run--02507d9a-e444-4b60-88dc-c2f78acb2e5c', usage_metadata={'input_tokens': 4, 'output_tokens': 15, 'total_tokens': 19, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}})]}}}
{'kind': 'on_chain_end', 'name': 'LangGraph', 'data': {'output': {'messages': [HumanMessage(content='你好', additional_kwargs={}, response_metadata={}, id='1548ecc6-61ac-41d9-8520-dbccd8b687f9'), AIMessage(content='你好! 很高兴见到你~有什么我可以帮你的吗?', additional_kwargs={}, response_metadata={'finish_reason': 'stop', 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8'}, id='run--02507d9a-e444-4b60-88dc-c2f78acb2e5c', usage_metadata={'input_tokens': 4, 'output_tokens': 15, 'total_tokens': 19, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}), HumanMessage(content='很高兴认识你', additional_kwargs={}, response_metadata={}, id='97101224-d3d7-4f66-a37d-13407114503d')]}}}
可能还有其他用法,后续实际应用再深入研究了