langgraph.stream中不同模式的区别
langgraph.stream中不同模式的区别,给我写一个mock代码,举例说明
下面按 LangGraph graph.stream(..., stream_mode=...) 的常见模式来讲。官方文档里当前列出的 stream modes 包括:values、updates、messages、custom、checkpoints、tasks、debug;并且推荐在新代码里加 version="v2",因为 v2 输出统一为 {"type": ..., "ns": ..., "data": ...} 的结构。(LangChain 文档)
1. 各模式区别
| 模式 | 输出什么 | 适合场景 |
|---|---|---|
values |
每一步后的 完整 state | 想看当前全量状态,比如调试状态流转 |
updates |
每个节点返回的 增量更新 | UI 只关心“刚刚哪个节点改了什么” |
messages |
LLM token/message chunk + metadata | 做聊天打字机效果、流式输出模型回答 |
custom |
节点里手动 writer(...) 发出的自定义事件 |
进度条、日志、阶段提示 |
checkpoints |
checkpoint 事件,格式类似 get_state() |
需要持久化状态检查点;需要 checkpointer |
tasks |
task start/finish、结果、错误 | 看任务生命周期;需要 checkpointer |
debug |
尽可能多的运行时信息 | 深度调试,不建议直接给生产 UI 用 |
官方说明里:updates 是每步后的 state updates;values 是每步后的完整 state;messages 是 LLM 调用产生的 (LLM token, metadata);custom 是通过 get_stream_writer() 从节点里发出的自定义数据。(LangChain 文档)
2. 一个最小 mock graph
这个例子不依赖真实 LLM,方便你直接理解 values / updates / custom / debug 的差异。
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.config import get_stream_writer
class State(TypedDict):
user_input: str
cleaned_input: str
result: str
def preprocess(state: State):
writer = get_stream_writer()
writer({"stage": "preprocess", "msg": "开始清洗输入"})
return {
"cleaned_input": state["user_input"].strip().lower()
}
def generate_answer(state: State):
writer = get_stream_writer()
writer({"stage": "generate_answer", "msg": "开始生成答案"})
return {
"result": f"mock answer for: {state['cleaned_input']}"
}
builder = StateGraph(State)
builder.add_node("preprocess", preprocess)
builder.add_node("generate_answer", generate_answer)
builder.add_edge(START, "preprocess")
builder.add_edge("preprocess", "generate_answer")
builder.add_edge("generate_answer", END)
graph = builder.compile()
3. stream_mode="updates":只看每个节点改了什么
for chunk in graph.stream(
{"user_input": " Hello LangGraph "},
stream_mode="updates",
version="v2",
):
print(chunk)
可能输出:
{
"type": "updates",
"ns": (),
"data": {
"preprocess": {
"cleaned_input": "hello langgraph"
}
}
}
{
"type": "updates",
"ns": (),
"data": {
"generate_answer": {
"result": "mock answer for: hello langgraph"
}
}
}
可以理解为:节点返回什么,就流出什么。
适合前端显示:“preprocess 完成了”、“generate_answer 完成了”。
4. stream_mode="values":每一步都给完整 state
for chunk in graph.stream(
{"user_input": " Hello LangGraph "},
stream_mode="values",
version="v2",
):
print(chunk)
可能输出:
{
"type": "values",
"ns": (),
"data": {
"user_input": " Hello LangGraph "
}
}
{
"type": "values",
"ns": (),
"data": {
"user_input": " Hello LangGraph ",
"cleaned_input": "hello langgraph"
}
}
{
"type": "values",
"ns": (),
"data": {
"user_input": " Hello LangGraph ",
"cleaned_input": "hello langgraph",
"result": "mock answer for: hello langgraph"
}
}
可以理解为:每次都给你一张完整快照。
缺点是数据量更大;优点是消费端不用自己拼 state。
5. stream_mode="custom":节点里主动发事件
前面节点里有:
writer = get_stream_writer()
writer({"stage": "preprocess", "msg": "开始清洗输入"})
所以可以这样消费:
for chunk in graph.stream(
{"user_input": " Hello LangGraph "},
stream_mode="custom",
version="v2",
):
print(chunk)
可能输出:
{
"type": "custom",
"ns": (),
"data": {
"stage": "preprocess",
"msg": "开始清洗输入"
}
}
{
"type": "custom",
"ns": (),
"data": {
"stage": "generate_answer",
"msg": "开始生成答案"
}
}
这个模式很适合做:
正在检索文档...
正在调用工具...
正在生成摘要...
已完成 80%
6. 多模式一起用:["custom", "updates"]
这是实际项目里很常见的写法:
custom 给用户看进度,updates 给系统更新状态。
for chunk in graph.stream(
{"user_input": " Hello LangGraph "},
stream_mode=["custom", "updates"],
version="v2",
):
if chunk["type"] == "custom":
print("[progress]", chunk["data"])
elif chunk["type"] == "updates":
print("[state update]", chunk["data"])
可能输出:
[progress] {'stage': 'preprocess', 'msg': '开始清洗输入'}
[state update] {'preprocess': {'cleaned_input': 'hello langgraph'}}
[progress] {'stage': 'generate_answer', 'msg': '开始生成答案'}
[state update] {'generate_answer': {'result': 'mock answer for: hello langgraph'}}
在 v2 格式里,无论单模式还是多模式,都是通过 chunk["type"] 区分类型;旧 v1 默认格式在单模式、多模式、subgraph 情况下返回结构会变化。(LangChain 文档)
7. messages 模式:真实 LLM token 流
messages 需要节点里调用真实 LangChain ChatModel。它流出来的是:
(message_chunk, metadata)
官方文档说明 messages 会从图里的 LLM 调用中流式产出 token/message segment 和 metadata。(LangChain 文档)
mock 写法大概是:
from typing import TypedDict
from langchain.chat_models import init_chat_model
from langgraph.graph import StateGraph, START, END
class ChatState(TypedDict):
topic: str
answer: str
model = init_chat_model("gpt-4o-mini") # 示例,按你的 provider 改
def call_llm(state: ChatState):
response = model.invoke([
{
"role": "user",
"content": f"用一句话解释:{state['topic']}"
}
])
return {"answer": response.content}
builder = StateGraph(ChatState)
builder.add_node("call_llm", call_llm)
builder.add_edge(START, "call_llm")
builder.add_edge("call_llm", END)
graph = builder.compile()
for chunk in graph.stream(
{"topic": "LangGraph stream mode"},
stream_mode="messages",
version="v2",
):
if chunk["type"] == "messages":
message_chunk, metadata = chunk["data"]
# message_chunk.content 通常就是 token / partial text
if message_chunk.content:
print(message_chunk.content, end="", flush=True)
你还可以通过 metadata 过滤节点,例如只显示某个节点的 token:
for chunk in graph.stream(
{"topic": "LangGraph stream mode"},
stream_mode="messages",
version="v2",
):
if chunk["type"] == "messages":
message_chunk, metadata = chunk["data"]
if metadata.get("langgraph_node") == "call_llm":
print(message_chunk.content, end="", flush=True)
8. debug 模式:调试用
for chunk in graph.stream(
{"user_input": " Hello LangGraph "},
stream_mode="debug",
version="v2",
):
print(chunk)
debug 会包含非常详细的执行信息。官方文档描述它是 “all available info”,包含 checkpoints、tasks 以及额外 metadata。(LangChain 文档)
一般建议:
开发调试:debug
前端状态更新:updates
前端完整状态渲染:values
聊天 token 输出:messages
进度条/自定义日志:custom
9. 最实用的选择建议
日常开发里我会这样选:
# 做普通工作流 UI
stream_mode=["custom", "updates"]
# 做 ChatGPT 一样的打字机效果
stream_mode=["messages", "updates"]
# 调试 state 为什么不对
stream_mode="values"
# 深度排查 graph 执行过程
stream_mode="debug"
一句话总结:
values = 给我完整状态
updates = 只告诉我变了什么
messages = 给我 LLM token
custom = 给我业务自定义事件
debug = 全都给我,方便排查

浙公网安备 33010602011771号