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,对比 invoke 与 stream(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