LangGraph中如何使用流式输出

LangGraph中流式输出技术深度剖析与代码实践

  1. 流式输出核心概念与价值
    • 对比非流式输出:非流式输出在Agent内部处理完成后一次性输出结果,而流式输出能实时捕捉并输出任务处理过程中的状态变化,以块的形式传输最终输出,用户在大模型生成响应时就能看到部分结果,提供即时反馈 。
    • 提升用户体验:在构建以大模型为基础的应用场景,如聊天机器人时,大模型生成完整响应通常需几秒钟,远超应用程序对最终用户200 - 300毫秒的响应阈值。流式输出通过显示中间进度,可显著提升用户体验,减少用户等待时的焦虑感 。
    • 开发阶段优势:在开发过程中,流式输出功能有助于准确追踪事件的具体执行阶段,捕获相关数据,为接入不同逻辑的数据处理和决策流程提供支持,是应用开发中不可或缺的关键技术 。
  2. 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)

在这里插入图片描述

  1. 图结构下的多元流式输出模式
模式名称功能描述应用场景
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方法,开发者可根据具体需求自定义流输出的内容和形式,增强流式功能的可用性和灵活性,满足特定业务场景的个性化需求 。
# 假设已经定义好自定义的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()
  1. 同步与异步流式输出的应用实践
    • 同步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,  # 执行过程中是否打印调试信息,默认为False
    subgraphs: bool = False  # 是否流式传输子图
) -> Iterator[Union[dict[str, Any], Any]]:
    pass
  1. 函数定义
    def stream:定义了一个名为stream的函数,它可能是某个类中的方法,因为第一个参数是self ,在类方法中self指向类实例本身,用于访问实例的属性和其他方法。
  2. 参数解释
    • 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 ,即不流式传输子图。
  3. 返回值类型
    -> Iterator[Union[dict[str, Any], Any]]::函数的返回值类型是一个迭代器(Iterator ),迭代器中的元素可以是字典(字典的键是字符串类型,值可以是任意类型 ),也可以是任意其他类型的数据。表明该函数执行后会返回一个可迭代对象,通过迭代它能获取流式输出的相关数据。
  4. 函数体
    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
# 从langchain_core.messages模块导入AIMessageChunk和HumanMessage类。
# AIMessageChunk用于表示大模型生成消息的增量块,HumanMessage用于表示用户输入的消息

first = True
# 初始化一个布尔变量first为True,用于标记是否是首次处理AIMessageChunk类型的消息块

async for msg, metadata in graph.astream({"messages": ["你好,帮我查询一下数据库中Beijing的天气数据"]}, stream_mode="messages"):
    # 通过async for异步循环遍历graph.astream方法的返回结果
    # 调用astream方法进行异步流式输出,传入用户消息“你好,帮我查询一下数据库中Beijing的天气数据”
    # 并指定stream_mode为“messages”,即按记录每个messages中的增量token模式进行流式输出
    # msg代表每次获取到的消息对象,metadata代表消息相关的元数据(此处未对metadata进行处理)

    if msg.content and not isinstance(msg, HumanMessage):
        # 如果消息对象msg有content属性(即消息有实际内容),并且msg不是HumanMessage类型(即不是用户输入消息)
        print(msg.content, end="|", flush=True)
        # 则打印消息内容,end="|"表示打印后不换行,以竖线|结尾,flush=True表示立即刷新输出缓冲区

    if isinstance(msg, AIMessageChunk):
        # 如果消息对象msg是AIMessageChunk类型(即大模型生成的消息增量块)
        if first:
            # 如果first为True,说明是首次遇到AIMessageChunk类型消息
            gathered = msg
            # 将msg赋值给gathered变量
            first = False
            # 并将first设为False
        else:
            # 如果first为False,说明不是首次
            gathered = gathered + msg
            # 将当前的msg与gathered进行合并(基于AIMessageChunk类定义的拼接逻辑)

    if msg.tool_call_chunks:
        # 如果消息对象msg有tool_call_chunks属性(即存在工具调用相关的增量信息)
        print(gathered.tool_calls)
        # 则打印gathered变量中存储的工具调用信息(tool_calls)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值