5.9 式输出:实时查看 LangGraph Agent 思考过程

LangGraph 提供了 ****** **stream()** ** 方法,支持逐块返回执行进度,实现类似 ChatGPT 的逐字输出效果。下面从为什么需要流式输出、stream 与三种 stream_mode,到三个递进脚本,把流式调用从概念落到代码。


为什么需要流式输出

用户体验:消除等待焦虑

传统 invoke 方式:

text 复制代码
用户:请帮我写一篇关于 AI 的文章

[等待 30 秒......]

Agent:这是一篇关于 AI 的文章......

用户全程无反馈,体验较差。

流式输出则逐块返回中间结果,让用户知道 Agent 还在工作。

适用场景

| 场景 | 说明 |

| --- | --- |

| 长时间任务 | 数据分析、多步推理,用户能实时看到进度 |

| 调试与可观测性 | 查看每个节点的执行结果,快速定位问题节点 |

| 聊天应用 | 逐字返回 LLM 回复,类似 ChatGPT 体验 |

流式输出让用户看到完整的推理过程,而不只是最终结果,建立透明度和信任感。


invoke vs stream

invoke:一次性返回

python 复制代码
result = app.invoke({"messages": [HumanMessage(content="广州天气如何?")]})

print(result)

特点:全部节点执行完成后,一次性返回完整 Result。适合后台任务、不需要实时反馈的场景。

stream:逐块返回

python 复制代码
for chunk in app.stream(

    {"messages": [HumanMessage(content="广州天气如何?")]},

    stream_mode="values",

):

    print(chunk)

特点:参数与 invoke 基本相同,但返回生成器 ,用 for 循环逐块处理每个切片。每个节点执行后立即返回一块数据,无需等待整图跑完。


三种 stream_mode

调用 stream() 时通过 stream_mode 选择输出粒度:

| 模式 | 返回内容 | 数据量 | 适用场景 |

| --- | --- | --- | --- |

| values | 节点执行后的完整 State | 最大 | 查看完整状态变化 |

| updates | 每个节点本次更新的字段 | 中等 | 调试、节点级日志 |

| messages | 消息增量(含 LLM token 流) | 最小 | 聊天应用逐字输出 |

values 模式

每次返回当前完整 State。新 chunk 会累积之前的所有消息,再加一条新内容:

  • 第 1 块:只有用户问题

  • 第 2 块:用户问题 + AIMessage(含 tool_calls)

  • 第 3 块:+ ToolMessage(工具结果)

  • 第 4 块:+ 最终 AIMessage(整理后的回答)

适合需要观察 State 全貌变化的场景。

updates 模式

每次只返回本次节点更新的内容 ,格式为 {节点名: 更新字典}

python 复制代码
for chunk in app.stream(input, stream_mode="updates"):

    for node_name, node_update in chunk.items():

        print(f"节点 [{node_name}] 输出: {node_update}")
  • llm 节点 → 返回 AIMessage

  • tools 节点 → 返回 ToolMessage

  • 下一个 llm 节点 → 返回最终回答

每次 chunk 都是增量,不包含历史重复内容,适合调试和结构化日志。

messages 模式

只返回消息的增量内容,数据量最小。配合 LLM 的 token 流,每个 token 作为一个 chunk 输出,实现真正的逐字打印:

python 复制代码
for chunk in app.stream(input, stream_mode="messages"):

    if chunk.content:

        print(chunk.content, end="", flush=True)

messages 模式会继承大模型自身的流式输出能力,LangGraph 自动处理 LLM 的 stream,每个 token 作为一个 chunk 返回。


流式输出工作原理

text 复制代码
app.stream() 调用

       │

       ▼

  START 节点执行 → 生成 chunk 1 → 立即返回

       │

       ▼

  节点 2 执行 → 生成 chunk 2 → 立即返回

       │

       ▼

  节点 3 执行 → 生成 chunk 3 → 立即返回

       │

       ▼

     ......

关键:每个节点执行后立即返回 chunk,用户可实时打印、存储或转发,无需等待整个图执行完毕。


脚本 1:invoke 与 stream 对比

*文件: **1**_***invoke和stream对比.py ****

构建带工具调用的天气 Agent,对比 invokestream(values) 的差异:

python 复制代码
import os

import time

from dotenv import load_dotenv

from langchain_openai import ChatOpenAI

from langchain_core.tools import tool

from langchain_core.messages import HumanMessage

from langgraph.graph import StateGraph, START, END, MessagesState

from langgraph.prebuilt import ToolNode, tools_condition

  


load_dotenv()

  


llm = ChatOpenAI(

    model="deepseek-ai/DeepSeek-V3.2",

    base_url="https://api.siliconflow.cn/v1",

    api_key=os.getenv("SILICONFLOW_API_KEY"),

    temperature=0,

)

  


WEATHER_DATA = {

    "北京": "晴天,气温 15-25°C,空气质量良",

    "上海": "多云,气温 18-28°C,空气质量优",

    "广州": "多云,气温 20-30°C,空气质量优",

    "深圳": "阵雨,气温 22-28°C,空气质量良",

}

  


@tool

def get_weather(city: str) -> str:

    """获取指定城市的天气信息"""

    time.sleep(1)  # 模拟延迟,便于观察流式差异

    return WEATHER_DATA.get(city, f"暂无 {city} 的天气信息")

  


tools = [get_weather]

llm_with_tools = llm.bind_tools(tools)

  


def llm_node(state: MessagesState):

    response = llm_with_tools.invoke(state["messages"])

    return {"messages": [response]}

  


graph = StateGraph(MessagesState)

graph.add_node("llm", llm_node)

graph.add_node("tools", ToolNode(tools))

graph.add_edge(START, "llm")

graph.add_conditional_edges("llm", tools_condition)

graph.add_edge("tools", "llm")

  


app = graph.compile()

input_data = {"messages": [HumanMessage(content="广州天气如何?")]}

  


# 普通调用:等全部完成后一次性输出

print("=== 普通调用 invoke ===")

result = app.invoke(input_data)

print(result["messages"][-1].content)

  


# 流式调用 values 模式:逐块输出

print("\n=== 流式调用 stream (values) ===")

for chunk in app.stream(input_data, stream_mode="values"):

    print("--- chunk ---")

    print(chunk)

**对比效果: **

  • invoke:打印「普通调用」后需等待较长时间,结果一次性出现

  • stream(values):几乎立即开始逐块输出,每块包含累积的完整 messages 列表

values 模式下,每个 chunk 的 messages 会逐步增长:用户消息 → +AIMessage(tool_calls) → +ToolMessage → +最终 AIMessage。


脚本 2:updates 模式

*文件: **2**_***updates模式.py ****

图结构与脚本 1 相同,改用 stream_mode="updates",只打印每个节点的增量更新:

python 复制代码
# 图构建同上,此处省略

  


print("=== 流式调用 stream (updates) ===")

for chunk in app.stream(input_data, stream_mode="updates"):

    for node_name, node_update in chunk.items():

        print(f"节点 [{node_name}]")

        for msg in node_update.get("messages", []):

            print(f"  {type(msg).__name__}: {msg.content or msg.tool_calls}")

        print()

**运行结果示例: **

text 复制代码
节点 [llm]

  AIMessage: [tool_calls: get_weather(city='广州')]

  


节点 [tools]

  ToolMessage: 多云,气温 20-30°C,空气质量优

  


节点 [llm]

  AIMessage: 广州当前天气为多云,气温 20-30°C,空气质量优。

values 的区别:每次 chunk 只包含本次节点新增的内容,不重复历史消息,更适合调试和节点级日志。


脚本 3:messages 模式

*文件: **3**_***messages模式.py ****

单 LLM 节点,用 messages 模式实现 ChatGPT 式逐字输出:

python 复制代码
import os

from dotenv import load_dotenv

from langchain_openai import ChatOpenAI

from langchain_core.messages import HumanMessage

from langgraph.graph import StateGraph, START, END, MessagesState

  


load_dotenv()

  


llm = ChatOpenAI(

    model="deepseek-ai/DeepSeek-V3.2",

    base_url="https://api.siliconflow.cn/v1",

    api_key=os.getenv("SILICONFLOW_API_KEY"),

    temperature=0.7,

    streaming=True,

)

  


def llm_node(state: MessagesState):

    response = llm.invoke(state["messages"])

    return {"messages": [response]}

  


graph = StateGraph(MessagesState)

graph.add_node("llm", llm_node)

graph.add_edge(START, "llm")

graph.add_edge("llm", END)

  


app = graph.compile()

input_data = {"messages": [HumanMessage(content="用一句话介绍什么是 LangGraph")]}

  


print("=== 流式调用 stream (messages) ===")

for chunk, metadata in app.stream(input_data, stream_mode="messages"):

    if chunk.content:

        print(chunk.content, end="", flush=True)

print()

**效果: **

文字像 ChatGPT 一样逐字打印,只返回消息内容本身,不含节点名、State 结构等额外信息。长文本时效果更明显------用户能实时看到生成进度。


三种模式对比(一图看懂)

text 复制代码
values 模式:

  chunk1: {messages: [HumanMessage]}

  chunk2: {messages: [HumanMessage, AIMessage(tool_call)]}      ← 累积

  chunk3: {messages: [HumanMessage, AIMessage, ToolMessage]}     ← 累积

  chunk4: {messages: [HumanMessage, AIMessage, ToolMessage, AIMessage]}  ← 累积

  


updates 模式:

  chunk1: {llm: {messages: [AIMessage(tool_call)]}}               ← 仅增量

  chunk2: {tools: {messages: [ToolMessage]}}                      ← 仅增量

  chunk3: {llm: {messages: [AIMessage]}}                          ← 仅增量

  


messages 模式:

  chunk1: "Lang"

  chunk2: "Graph"

  chunk3: " 是"

  chunk4: "..."

  ← 逐 token 输出,仅消息正文

小结

| 要点 | 内容 |

| --- | --- |

| invoke | 整图跑完后一次性返回,适合后台任务 |

| stream | 逐块返回生成器,适合实时反馈 |

| values | 每次返回完整 State,数据最全,消息累积 |

| updates | 每次只返回节点增量,适合调试日志 |

| messages | 只返回消息/token 增量,适合聊天逐字输出 |

| 选型 | 调试看 updates,聊天用 messages,全状态追踪用 values

相关推荐
魏祖潇1 小时前
SDD 完整指南——Spec 端打底、Story 端交付、留白区
人工智能·后端
Token炼金师1 小时前
从节点图到低秩矩阵:ComfyUI 推理引擎与 LoRA 适配机制拆解
人工智能·aigc
武子康1 小时前
调查研究-210 Netflix 用 AI 复刻 Gene Wilder 的声音:语音克隆的下半场,不是模型,而是权利
人工智能·aigc·openai
Quz1 小时前
在 Obsidian 中嵌入 Claude Code 的实践记录
人工智能·claude
雪隐2 小时前
个人电脑玩AI-10让5060 Ti给你打工——部署 Odysseus:终于有个能打的"AI管家"了
人工智能·后端
武子康2 小时前
调查研究-209 Apptronik Robot Park 深度解析:人形机器人竞争,开始拼“真实世界数据工厂“
人工智能·google·llm
IT_陈寒2 小时前
Vite打包时踩的坑:静态资源为啥突然404了?
前端·人工智能·后端
一点一木3 小时前
🚀 2026 年 6 月 GitHub 十大热门项目排行榜 🔥
人工智能·github
aneasystone本尊3 小时前
学习 turbovec 的 SIMD 搜索内核
人工智能