LangGraph--基础学习(stream 流式调用和使用)

        前面很多内容都是从大方面了解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()

LangGraph--基础学习(stream 流式调用和使用)_第1张图片

代码使用 result = app.invoke(inputs) ,result 为整个工作流最终的结果,也就是上面定义的 State 对象。

State 对象有个 messages 属性,我们使用for 循环来遍历 messages 中的元素,得到的输出为

================================ Human Message =================================

你好,你是谁?
================================== Ai Message ==================================

你好!我是DeepSeek Chat,由深度求索公司创造的智能助手。我可以帮你解答各种问题、提供信息、聊天交流,还能处理上传的文档。如果有任何问题或需要帮助,尽管问我吧!
================================ Human Message =================================

很高兴认识你

pretty_print() 为langchain 中封装的方法,可以将大模型中的消息打印的相对好看一些。

通过上面代码结果,可以看到,最终的结果需要所有的节点都执行完毕才返回。接下来我们看一看如何使用流式输出,一步一步的将每个节点的输出依次打印。

1.1 使用stream进行流式调用

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 值的。

1.2 StreamMode 不同参数的含义¶

stream 方法还有一个 stream_mode 参数,该参数有以下几个值可选

  • 'values': Emit all values of the state for each step.
  • 'updates': Emit only the node name(s) and updates
    that were returned by the node(s) after each step.- 'debug': Emit debug events for each step.
  • 'messages': Emit LLM messages token-by-token.
  • 'custom': Emit custom output write: StreamWriter kwarg of each node.

我们来分别看看各值的效果

values¶

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
class State(TypedDict):  
    messages: Annotated[List[BaseMessage], add_messages]

State 中有个messages 属性,这个属性的更新方法为 add_messages, 当__START__ 节点执行结束以后,__START__ 节点可以理解为调用 app.stream(inputs) 以后,当首次传入 inputs 以后,此时的State 对象经过 add_messages 方法更新以后的对象。此时State对象为

1
{'messages': [HumanMessage(content='你好,你是谁?', additional_kwargs={}, response_metadata={}, id='20122092-434c-42c5-a127-145a2c0540a6')]}

之后执行 chatbot 节点,该节点调用大模型,

1
2
def chatbot(state: State):  
    return {"messages": [llm.invoke(state["messages"])]}

返回大模型的回复,经过 add_messages 以后,将大模型回复的内容追加到 State 对象的 messages 中,此时的State对象为:(此处打印进行了省略和格式化)

updates¶

updates 为更新,这种模式下,会显示哪个节点更新了哪些内容,但是并不会将State所有的值返回,只返回更新的内容。

inputs = {"messages":[HumanMessage(content="你好")]}
for i in app.stream(inputs,stream_mode="updates"):
    print(i)
1
2
3
4
{'chatbot': {'messages': [AIMessage(content='你好!很高兴为你提供帮助。'...)]}}


{'n2': {'messages': '很高兴认识你'}}

第一次经过 __START__ 节点时,由于此时并没有更新操作,所以执行完 __START__ 节点以后, 不会有什么输出。

执行完 chatbot 节点以后,return {"messages": [llm.invoke(state["messages"])]} , 返回了大模型的输出,此时是需要更新State 中的 messages 的,更新的内容为 {'messages': [AIMessage(content='你好!很高兴为你提供帮助。'...)]}, 也就是chatbot 节点返回的内容。

同理执行完 n2 节点以后,返回的是 {'messages': '很高兴认识你'}

updates 模式下,可以理解为返回的是需要更新State的内容或者节点本身返回的内容。

实际应用时主要用到的是 values 和 updates ,还有几个模式,我们简单看一下

debug¶

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¶

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})
stream_mode 总结¶
  1. values 为每个节点执行完毕以后,State 对象更新以后的值
  2. updates 为每个节点执行完毕以后,需要更新的内容或者可以理解为节点返回的内容,但是只包含State 中定义的属性。
  3. debug 模式会将节点执行的输入和输出返回
  4. messages 模式只会返回大模型的流式输出

常用的模式为 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 与 ainvokestream 与 astream, 但是这个方法只有astream_events。且官方文档中介绍,在LangGraph 中其实没有必要使用的,

但是我觉得这个方法还是很灵活的,所以这里也简单的介绍一下它的使用方法吧。

astream_events¶

我们可以将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')]}}}

可能还有其他用法,后续实际应用再深入研究了

你可能感兴趣的:(学习)