【langgraph 从入门到精通graphApi 篇】流式输出与 Streaming

第 10 章:流式输出与 Streaming

文章目录

    • [第 10 章:流式输出与 Streaming](#第 10 章:流式输出与 Streaming)
      • [10.1 本章目标](#10.1 本章目标)
      • [10.2 核心概念](#10.2 核心概念)
        • [stream_mode 七种模式对比](#stream_mode 七种模式对比)
        • [v2 vs v3 流式 API](#v2 vs v3 流式 API)
      • [10.3 实战](#10.3 实战)
        • [实战 1:Token 级打字机效果(messages 模式)](#实战 1:Token 级打字机效果(messages 模式))
        • [实战 2:多模式组合流式(v2 统一格式)](#实战 2:多模式组合流式(v2 统一格式))
        • [实战 3:自定义流式事件(custom 模式)](#实战 3:自定义流式事件(custom 模式))
        • [实战 4:v3 事件流式 API(推荐)](#实战 4:v3 事件流式 API(推荐))
      • [10.4 API 速查](#10.4 API 速查)
      • [10.5 错误与避坑指南](#10.5 错误与避坑指南)
        • [坑 1:同步 stream() 在 async 函数中调用](#坑 1:同步 stream() 在 async 函数中调用)
        • [坑 2:messages 模式未过滤不需要的模型输出](#坑 2:messages 模式未过滤不需要的模型输出)
        • [坑 3:custom 模式在节点外调用 get_stream_writer()](#坑 3:custom 模式在节点外调用 get_stream_writer())
        • [坑 4:v2 格式未判断 chunk 类型](#坑 4:v2 格式未判断 chunk 类型)
      • [10.6 最佳实践总结](#10.6 最佳实践总结)

10.1 本章目标

学完本章你将能够:

  1. 理解 stream()astream() 的区别和使用场景
  2. 掌握所有 stream_mode 模式(values/updates/debug/messages/custom/checkpoints/tasks)
  3. 学会 v2 统一格式和 v3 事件流式 API
  4. 实现 Token 级打字机效果和自定义流式事件

10.2 核心概念

stream_mode 七种模式对比
模式 输出内容 典型用途
values 每步后完整 State 快照 查看完整状态变化
updates 每步增量更新(含节点名) 了解哪个节点改了什么
messages LLM Token 流式输出 (token, metadata) 打字机效果
custom 通过 get_stream_writer() 的自定义数据 进度条、状态通知
debug 尽可能多的调试信息 排查问题
checkpoints checkpoint 创建事件 监控持久化
tasks 任务启动/完成事件 监控并行任务
v2 vs v3 流式 API
特性 v2 v3(推荐)
API stream(mode=[...], version="v2") stream_events(version="v3")
输出格式 统一 StreamPart 格式 类型化投影
中断检测 需手动处理 stream.interrupts 属性
最终状态 需手动收集 stream.output 属性

10.3 实战

实战 1:Token 级打字机效果(messages 模式)
python 复制代码
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END, add_messages
from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI


class State(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]


llm = ChatOpenAI(model="gpt-4o", temperature=0.7, streaming=True)


def chat_node(state: State) -> dict:
    response = llm.invoke(
        [SystemMessage(content="你是一个友好的助手")] + list(state["messages"])
    )
    return {"messages": [response]}


builder = StateGraph(State)
builder.add_node("chat", chat_node)
builder.add_edge(START, "chat")
builder.add_edge("chat", END)
graph = builder.compile()


# ============================================
# 流式输出:Token 级打字机效果
# ============================================
print("AI: ", end="", flush=True)

for chunk in graph.stream(
    {"messages": [HumanMessage(content="写一首关于编程的短诗")]},
    stream_mode="messages",
):
    if isinstance(chunk, tuple):
        msg_chunk, metadata = chunk
        if hasattr(msg_chunk, "content") and msg_chunk.content:
            print(msg_chunk.content, end="", flush=True)
实战 2:多模式组合流式(v2 统一格式)
python 复制代码
# 同时使用多种 stream_mode
for chunk in graph.stream(
    {"messages": [HumanMessage(content="写一首诗")]},
    stream_mode=["values", "updates", "messages"],
    version="v2",
):
    chunk_type = chunk.get("type")
    
    if chunk_type == "values":
        print(f"[values] 消息数: {len(chunk['data'].get('messages', []))}")
    
    elif chunk_type == "updates":
        for node_name, update in chunk["data"].items():
            print(f"[updates] 节点 '{node_name}' 更新了 State")
    
    elif chunk_type == "messages":
        msg_chunk, metadata = chunk["data"]
        if hasattr(msg_chunk, "content") and msg_chunk.content:
            print(msg_chunk.content, end="", flush=True)
实战 3:自定义流式事件(custom 模式)
python 复制代码
from langgraph.config import get_stream_writer
import time


def long_running_node(state: State) -> dict:
    """模拟长时间运行的节点,通过 custom 模式发送进度通知。"""
    writer = get_stream_writer()  # 获取流式写入器
    
    writer({"progress": 0, "status": "开始处理..."})
    time.sleep(0.5)
    
    writer({"progress": 30, "status": "正在分析数据..."})
    time.sleep(0.5)
    
    writer({"progress": 60, "status": "正在生成结果..."})
    time.sleep(0.5)
    
    writer({"progress": 100, "status": "处理完成!"})
    
    return {"messages": [AIMessage(content="处理完成!")]}


# 接收自定义事件
for chunk in graph.stream(
    {"messages": [HumanMessage(content="开始处理")]},
    stream_mode=["custom", "messages"],
    version="v2",
):
    if chunk["type"] == "custom":
        event = chunk["data"]
        print(f"\n  [{event['progress']}%] {event['status']}", end="")
    elif chunk["type"] == "messages":
        msg_chunk, _ = chunk["data"]
        if hasattr(msg_chunk, "content") and msg_chunk.content:
            print(f"\nAI: {msg_chunk.content}", end="")
实战 4:v3 事件流式 API(推荐)
python 复制代码
stream = graph.stream_events(
    {"messages": [HumanMessage(content="写一首诗")]},
    version="v3",
)

# 类型化投影:直接遍历 LLM 消息 token
for msg in stream.messages:
    for token in msg.text:
        print(token, end="", flush=True)

# 获取最终状态
final = stream.output
print(f"\n\n最终消息数: {len(final['messages'])}")

# 检查中断
if stream.interrupted:
    print(f"图被中断: {stream.interrupts}")

10.4 API 速查

API 完整签名 入参说明 返回值 说明
.stream(input, stream_mode) stream(input, stream_mode, version) input: 初始 State; stream_mode: 模式或模式列表; version: "v2" Iterator[dict] 同步流式
.astream(input, stream_mode) astream(input, stream_mode, version) 同 stream AsyncIterator[dict] 异步流式
stream_mode="values" 模式名 完整 State 快照 每步后完整状态
stream_mode="updates" 模式名 增量更新 每个节点的变更
stream_mode="messages" 模式名 (token, metadata) LLM Token 流
stream_mode="custom" 模式名 自定义数据 通过 get_stream_writer
stream_mode="debug" 模式名 调试信息 节点执行详情
get_stream_writer() get_stream_writer() Writer 函数 在节点中发送自定义事件
.stream_events(input, v3) stream_events(input, version="v3") input: 初始 State; version: "v3" EventsStream v3 事件流式 API(推荐)

10.5 错误与避坑指南

坑 1:同步 stream() 在 async 函数中调用
python 复制代码
# ❌ 错误写法
async def bad_handler():
    for chunk in graph.stream(...):  # 同步 stream 在 async 中阻塞事件循环
        print(chunk)

# ✅ 正确写法
async def good_handler():
    async for chunk in graph.astream(...):  # 使用 astream
        print(chunk)
坑 2:messages 模式未过滤不需要的模型输出
python 复制代码
# ❌ 错误:所有 LLM 输出都混在一起
for chunk in graph.stream(..., stream_mode="messages"):
    print(chunk[0].content)  # 可能包含内部模型的输出

# ✅ 正确:使用 tags 过滤
model = ChatOpenAI(model="gpt-4o").with_config({"tags": ["user_facing"]})

for chunk in graph.stream(..., stream_mode="messages"):
    msg_chunk, metadata = chunk
    if "user_facing" in metadata.get("tags", []):
        print(msg_chunk.content)
坑 3:custom 模式在节点外调用 get_stream_writer()
python 复制代码
# ❌ 错误写法
writer = get_stream_writer()  # 在节点外调用 → 报错

# ✅ 正确写法
def my_node(state: State) -> dict:
    writer = get_stream_writer()  # 在节点内调用
    writer({"status": "ok"})
坑 4:v2 格式未判断 chunk 类型
python 复制代码
# ❌ 错误:假设所有 chunk 都是 messages
for chunk in graph.stream(..., stream_mode=["values", "messages"], version="v2"):
    print(chunk["data"][0].content)  # values 类型的 chunk 没有 .content!

# ✅ 正确:先判断类型
for chunk in graph.stream(..., stream_mode=["values", "messages"], version="v2"):
    if chunk["type"] == "messages":
        msg_chunk, _ = chunk["data"]
        print(msg_chunk.content)

10.6 最佳实践总结

  1. 生产环境使用 astream() 异步流式:不阻塞事件循环,支持更多并发
  2. Token 流式用 messages 模式,调试用 debug 模式:各取所需
  3. 使用 v2 统一格式处理多模式组合version="v2" 统一返回 StreamPart
  4. 新项目优先使用 v3 事件流式 API:类型化投影更安全、更易用
  5. 自定义进度通知用 custom 模式 :配合 get_stream_writer() 实现实时进度
相关推荐
科技林总19 小时前
旋转位置编码(RoPE)
人工智能
综合资讯19 小时前
半导体上游ETF的配置标尺
人工智能·科技·物联网·半导体·科创
lhxcc_fly19 小时前
LangGraph 项目部署知识点总结
ai·langchain·项目部署·langgraph
东方佑19 小时前
BitsFusion + Precision Diffusion — UV 实验报告
人工智能
大模型码小白19 小时前
向量化引擎与 AI 排障:当 SIMD 遇到异常检测,存储诊断的范式转移
java·大数据·数据库·人工智能·python
遥感知识服务19 小时前
水库是在缓解干旱,还是把干旱重新分配?
人工智能
在水一缸19 小时前
当 AI 拥有了“核按钮”:深入解析 MCP 服务器与命令执行护栏
运维·服务器·人工智能·命令执行·智能体·ai安全·mcp
weixin_4462608519 小时前
ARM++:面向深度伪造检测器的多域智能体协同可迁移对抗攻击框架
arm开发·人工智能
tinygone19 小时前
在Windows上部署Unlimited-ocr并提供给大模型使用
人工智能·windows·经验分享