LangGraph中流式输出技术深度剖析与代码实践
流式输出核心概念与价值
对比非流式输出 :非流式输出在Agent内部处理完成后一次性输出结果,而流式输出能实时捕捉并输出任务处理过程中的状态变化,以块的形式传输最终输出,用户在大模型生成响应时就能看到部分结果,提供即时反馈 。提升用户体验 :在构建以大模型为基础的应用场景,如聊天机器人时,大模型生成完整响应通常需几秒钟,远超应用程序对最终用户200 - 300毫秒的响应阈值。流式输出通过显示中间进度,可显著提升用户体验,减少用户等待时的焦虑感 。开发阶段优势 :在开发过程中,流式输出功能有助于准确追踪事件的具体执行阶段,捕获相关数据,为接入不同逻辑的数据处理和决策流程提供支持,是应用开发中不可或缺的关键技术 。 LangGraph流式输出的实现基石
基于LangChain构建 :LangGraph底层依托LangChain进行构建,其流式输出直接复用了LangChain的回调系统。若开发者熟悉LangChain的流式输出机制,理解和应用LangGraph的流式输出将更为轻松 。异步流式输出示例 :以调用ChatOpenAI模型为例,使用astream
方法实现异步流式输出。在代码实现中,通过async for
循环迭代处理每个输出块,将其存储在列表中。每个块是AIMessageChunk
对象,代表AIMessage
的一部分,这些消息块在设计上可叠加,方便开发者获取和处理不同阶段的输出内容 。
import getpass
import os
from langchain_openai import ChatOpenAI
if not os. environ. get( "OPENAI_API_KEY" ) :
os. environ[ "OPENAI_API_KEY" ] = getpass. getpass( "Enter your OpenAI API key: " )
llm = ChatOpenAI( model= "gpt-40" )
chunks = [ ]
async for chunk in llm. astream( "你好,请你详细的介绍一下你自己。" ) :
chunks. append( chunk)
print ( chunk. content, end= "|" , flush= True )
图结构下的多元流式输出模式
模式名称 功能描述 应用场景 values
在图中的每个步骤之后,流式传输状态的完整值 适用于需要获取整个流程中各步骤完整状态信息的场景,如全面监控业务流程的执行情况,了解每一步处理后整体状态的全貌 updates
在图中的每个步骤之后,将更新流式传输到状态,若同一步骤有多个更新(如运行多个节点 ),更新将单独流式传输 用于关注流程中各步骤具体更新内容的场景,像追踪数据在每个处理节点的变化情况,精准定位数据更新的来源和内容 debug
在整个图的执行过程中,流式传输尽可能多的信息,主要用于调试程序 在开发和调试阶段,当开发者需要深入了解图结构执行过程中的细节,排查问题、分析执行逻辑时使用 messages
记录每个messages
中的增量token 在涉及大模型交互及工具调用的场景中很有用,如分析大模型回复的生成过程,追踪工具调用参数的产生步骤 custom
可通过LangGraph的StreamWriter
方法进行自定义流 当有特殊业务需求,上述标准模式无法满足时,开发者可根据自身需求定制流式输出内容和形式
values
模式:完整状态追踪 :在该模式下,图中每个步骤之后会流式传输状态的完整值。例如,消息列表会随着节点的执行不断追加新内容,完整呈现每个步骤的状态变化,便于开发者获取整个处理过程的详细信息 。
from langgraph import Graph
from langchain_core. messages import HumanMessage
graph = Graph( )
def print_stream ( stream) :
for sub_stream in stream:
message = sub_stream[ "messages" ] [ - 1 ]
message. pretty_print( )
input_message = { "messages" : [ HumanMessage( content= "你好,南京现在的天气怎么样?" ) ] }
print_stream( graph. stream( input_message, stream_mode= "values" ) )
updates
模式:精准更新呈现 :此模式下,每个步骤之后仅将更新流式传输到状态,只返回当前步骤更新后的状态,没有追加过程。在复杂工具应用中,可精准关注每个步骤的实时变化,了解系统在每个环节的具体更新情况 。
def print_stream_updates ( stream) :
for sub_stream in stream:
print ( sub_stream)
input_message = { "messages" : [ HumanMessage( content= "你好,天津、内蒙现在的天气怎么样?" ) ] }
print_stream_updates( graph. stream( input_message, stream_mode= "updates" ) )
debug
模式:深度调试支持 :主要用于调试程序,在整个图的执行过程中流式传输尽可能多的信息。它能提供丰富的中间状态细节,包括时间戳、步骤信息、函数调用等,帮助开发者深入理解中间过程,快速定位和解决问题 。
def print_stream_debug ( stream) :
for sub_stream in stream:
print ( sub_stream)
input_message = { "messages" : [ HumanMessage( content= "你好,天津、内蒙现在的天气怎么样?" ) ] }
print_stream_debug( graph. stream( input_message, stream_mode= "debug" ) )
messages
模式:增量token获取 :可记录每个messages
中的增量token,不仅能获取大模型正常响应的增量内容,还能捕获工具调用产生的token。在涉及工具调用的复杂应用中,能清晰追踪工具调用的参数生成及响应过程 。
from langchain_core. messages import AIMessageChunk, HumanMessage
def print_stream_messages ( ) :
inputs = [ HumanMessage( content= "what is the weather in sf" ) ]
first = True
async for msg, metadata in graph. astream( { "messages" : [ "你好,帮我查询一下数据库中Beijing的天气数据" ] } , stream_mode= "messages" ) :
if msg. content and not isinstance ( msg, HumanMessage) :
print ( msg. content, end= "|" , flush= True )
if isinstance ( msg, AIMessageChunk) :
if first:
gathered = msg
first = False
else :
gathered = gathered + msg
if msg. tool_call_chunks:
print ( gathered. tool_calls)
print_stream_messages( )
custom
模式:灵活扩展定制 :通过LangGraph的Streamwriter
方法,开发者可根据具体需求自定义流输出的内容和形式,增强流式功能的可用性和灵活性,满足特定业务场景的个性化需求 。
from langgraph import Streamwriter
def custom_stream ( ) :
def custom_writer ( ) :
pass
graph. stream( input_message, stream_mode= "custom" , custom_stream_writer= custom_writer)
custom_stream( )
同步与异步流式输出的应用实践
同步stream
方法 :返回迭代器,在生成输出块时同步生成。方法包含多个参数,input
用于从图状态中取值,config
可进行自定义可运行配置,stream_mode
用于指定流式输出模式。使用for
循环迭代处理每个块,不同组件类型的块处理方式不同,如大模型返回的块类型通常为AIMessageChunk
。在实际应用中,可通过该方法实现对图结构中数据的同步流式处理 。
graph = Graph( )
input_data = { "input_key" : "input_value" }
for result in graph. stream( input_data, stream_mode= "values" ) :
print ( result)
代码提取
def stream (
self,
input : Union[ dict [ str , Any] , Any] ,
config: Optional[ RunnableConfig] = None ,
* ,
stream_mode: Optional[ Union[ StreamMode, list [ StreamMode] ] ] = None ,
output_keys: Optional[ Union[ str , Sequence[ str ] ] ] = None ,
interrupt_before: Optional[ Union[ All, Sequence[ str ] ] ] = None ,
interrupt_after: Optional[ Union[ All, Sequence[ str ] ] ] = None ,
debug: Optional[ bool ] = False ,
subgraphs: bool = False
) - > Iterator[ Union[ dict [ str , Any] , Any] ] :
pass
函数定义 def stream
:定义了一个名为stream
的函数,它可能是某个类中的方法,因为第一个参数是self
,在类方法中self
指向类实例本身,用于访问实例的属性和其他方法。参数解释
input
:类型标注为Union[dict[str, Any], Any]
,意味着这个参数可以是一个字典(字典的键是字符串类型,值可以是任意类型 ),也可以是任意其他类型的数据。注释说明它是图中的输入,从状态中取值,是进行流式输出操作时的输入数据来源。config
:类型是Optional[RunnableConfig]
,Optional
表示该参数是可选的,RunnableConfig
应该是自定义的一个配置类或类型,用于配置与可运行对象相关的参数,默认值为None
。stream_mode
:类型为Optional[Union[StreamMode, list[StreamMode]]]
,也是可选参数。它可以是一个StreamMode
类型(推测是自定义的表示流式输出模式的类型 ),也可以是StreamMode
类型的列表,用于指定流式输出的模式,默认值为None
。output_keys
:类型是Optional[Union[str, Sequence[str]]]
,可选参数。可以是一个字符串,也可以是字符串序列,它代表流输出的键,注释说明默认是所有非下文源,也就是用于确定输出结果中包含哪些特定的键值对。interrupt_before
:类型为Optional[Union[All, Sequence[str]]]
,可选参数。All
可能是自定义的一个特殊标识(推测表示全部 ),也可以是字符串序列,用于指定在哪些节点之前中断流式输出操作,默认是图中的所有节点。interrupt_after
:类型是Optional[Union[All, Sequence[str]]]
,可选参数。功能类似interrupt_before
,用于指定在哪些节点之后中断流式输出操作,默认也是图中的所有节点。debug
:类型为Optional[bool]
,可选的布尔类型参数,用于控制在执行过程中是否打印调试信息,默认值为False
,即不打印调试信息。subgraphs
:类型是bool
,用于判断是否对流式传输子图,是或否的二选一情况,默认值为False
,即不流式传输子图。 返回值类型 -> Iterator[Union[dict[str, Any], Any]]:
:函数的返回值类型是一个迭代器(Iterator
),迭代器中的元素可以是字典(字典的键是字符串类型,值可以是任意类型 ),也可以是任意其他类型的数据。表明该函数执行后会返回一个可迭代对象,通过迭代它能获取流式输出的相关数据。函数体 pass
:函数体目前为空,意味着实际的函数逻辑还未编写,后续需要补充代码来实现流式输出相关的具体操作,如根据传入参数进行数据处理、按照指定模式生成并返回流式输出结果等。
异步astream
方法 :专为异步开发环境设计,避免阻塞IO,常用于部署应用程序时考虑并发情况。同样可指定流式输出模式,通过异步函数async for
循环处理输出结果。开发者既可以获取异步流式输出的中间结果,也可只关注最终结果。还能通过调整打印方式查看每个步骤的更新状态,或通过特定代码实现流式传输每个过程中的Tokens
,满足不同开发需求 。
async def async_stream_demo ( ) :
async for result in graph. astream( input_data, stream_mode= "updates" ) :
print ( result)
import asyncio
asyncio. run( async_stream_demo( ) )
from langchain_core. messages import AIMessageChunk, HumanMessage
first = True
async for msg, metadata in graph. astream( { "messages" : [ "你好,帮我查询一下数据库中Beijing的天气数据" ] } , stream_mode= "messages" ) :
if msg. content and not isinstance ( msg, HumanMessage) :
print ( msg. content, end= "|" , flush= True )
if isinstance ( msg, AIMessageChunk) :
if first:
gathered = msg
first = False
else :
gathered = gathered + msg
if msg. tool_call_chunks:
print ( gathered. tool_calls)