热闻岛
返回全网热点

《LangChain 系列》Streaming:为什么 AI 应用一定要做流式输出?

6月16日50 阅读
《LangChain 系列》Streaming:为什么 AI 应用一定要做流式输出?配图
在普通接口里,后端必须等模型、工具、RAG、Agent 全部跑完,才能一次性返回。用户看到的是空白。 在流式接口里,模型 token、工具进度、节点状态、人工中断、最终结果,都可以一段一段推给前端。用户知道系统还活着,也知道系统正在做什么。

《LangChain 系列》Streaming:为什么 AI 应用一定要做流式输出?配图

在普通接口里,后端必须等模型、工具、RAG、Agent 全部跑完,才能一次性返回。用户看到的是空白。

在流式接口里,模型 token、工具进度、节点状态、人工中断、最终结果,都可以一段一段推给前端。用户知道系统还活着,也知道系统正在做什么。

一句话:Streaming 不是模型能力,而是 AI 产品的实时反馈系统。

1. 为什么 AI 应用必须做流式输出?

传统接口适合短请求。比如查订单、查余额、查配置,几十毫秒到几百毫秒就能返回。

但大模型应用不一样。它可能要先理解问题,再查知识库,再调用工具,再让模型生成长答案。一次请求可能 5 秒、15 秒,甚至更久。

如果这段时间前端没有任何变化,用户会以为页面卡死。

《LangChain 系列》Streaming:为什么 AI 应用一定要做流式输出?配图

等待式回答和流式回答的用户体感差异

流式输出解决的不是“能不能返回”,而是“用户愿不愿意等”。

用户可以接受复杂任务慢一点,但不能接受什么都看不到。

AI 应用的第一体验,不是答案多聪明,而是有没有及时反馈。

2. Streaming 到底在流什么?

很多人以为 Streaming 就是“模型一个字一个字输出”。这是最窄的理解。

在 LangChain / LangGraph 里,流式输出可以流很多东西:模型 token、Agent 节点进度、完整状态、自定义工具进度、checkpoint、任务生命周期、debug 信息。

《LangChain 系列》Streaming:为什么 AI 应用一定要做流式输出?配图

不同 stream mode 的用途

最常用的组合是 messages + updates。

messages 负责模型文本。updates 负责 Agent 进度。一个给用户看答案,一个给用户看系统正在干什么。

如果工具执行时间长,比如查数据库、跑计算、调第三方接口,就加 custom,让工具内部主动发出“正在查询”“已经拿到结果”“正在整理答案”。

不要只做 token streaming。真正的 Agent 应用,要做多路 streaming。

3. LangChain 官方推荐的两套流式方式

当前官方文档里有两套思路。

第一套是 stream / astream + stream_mode。它更贴近 LangGraph 运行时,能直接拿到 updates、messages、custom、debug 等模式。

第二套是 stream_events。它更适合前端应用。它把底层事件拆成 typed projections,比如 messages、tool_calls、values、output、subgraphs。前端不用再硬解析一堆 tuple 或 chunk。

《LangChain 系列》Streaming:为什么 AI 应用一定要做流式输出?配图

stream_events 把底层事件拆成前端更好消费的投影视角

简单判断:

后端调试、掌控图运行时,用 stream_mode。

前端产品化、聊天界面、工具卡片、子 Agent 展示,用 event streaming。

官方文档也说明,LangChain agents 构建在 LangGraph 之上,因此支持同一套 streaming stack;对大多数应用和前端场景,推荐使用 Event Streaming,通过 typed projections 独立消费消息、工具调用、状态和自定义更新。

4. stream_mode 的核心逻辑

stream_mode 可以理解成“我要看哪一种运行过程”。

比如你只想看模型生成,就用 messages。

你想看每个节点执行后的状态变化,就用 updates。

你想让工具内部主动报进度,就用 custom。

你想调试整个图,就用 debug。

LangGraph v2 流格式把每个 chunk 统一成一个 StreamPart。核心字段是 type、ns、data。type 表示流模式,ns 表示命名空间,data 是真正的数据。

StreamPart = {
"type": "updates" | "messages" | "custom" | "debug" | ... ,
"ns": (),
"data": ...
}

这点很关键。因为前端和网关不应该靠猜字符串来判断事件类型。应该看 type。

例如:type=messages,就渲染到聊天气泡;type=updates,就刷新进度条;type=custom,就显示工具内部状态。

5. 源码链路:stream 为什么能边跑边吐?

从源码视角看,Streaming 不是模型一个人在输出。它是图运行时把每一步执行结果都作为事件往外吐。

《LangChain 系列》Streaming:为什么 AI 应用一定要做流式输出?配图

Streaming 源码链路压缩图

链路可以压缩成这样:

用户调用 agent.stream()。

agent 本质是 CompiledStateGraph。

CompiledStateGraph 进入 LangGraph 图运行时。

图运行时按节点执行:model 节点、tools 节点、middleware 节点、子图节点。

每个节点执行时,都可能产生输出:模型 token、状态更新、工具结果、自定义进度。

运行时根据 stream_mode 把这些输出包装成 StreamPart。

外部消费者持续迭代这些 chunk,再通过 SSE 或 WebSocket 推给前端。

源码级理解:stream 是图执行器的事件出口,不是简单的 print。

6. messages:模型 token 是怎么流出来的?

messages 模式关注模型输出。

模型开始生成时,不是等完整 AIMessage 出来才返回,而是不断产生 message chunk。每个 chunk 可能是一小段文本,也可能带 metadata。

这些 metadata 很重要。它能告诉你 token 来自哪个节点、哪次模型调用、哪个子图。

前端如果只拿文本,就只能做打字机效果。

前端如果同时拿 metadata,就能做到更高级的展示:不同 Agent 的输出分区、不同节点的输出分层、工具调用前后的答案区分。

在复杂 Agent 里,可能不止一次模型调用。第一次模型调用可能只是决定调用工具。第二次模型调用才是真正总结答案。messages 模式会把这些模型调用过程都流出来。

所以产品上要做过滤:哪些 token 给用户看,哪些只进入调试面板。

7. updates:Agent 进度是怎么流出来的?

updates 模式关注图状态变化。

节点执行完,会返回一段 state update。比如 model 节点追加了一条 AIMessage,tools 节点追加了一条 ToolMessage,router 节点更新了 intent。

updates 不适合直接当最终答案展示。它适合显示“Agent 跑到了哪一步”。

例如:

模型节点完成。

工具节点开始。

RAG 检索完成。

人工审批触发。

最终答案生成。

这一类信息对企业应用很重要。因为用户不只想看到答案,还想知道系统有没有查资料、有没有调用工具、有没有卡在审批。

8. custom:工具内部如何主动发进度?

custom 模式解决一个痛点:工具执行时间太长,用户不知道它在干什么。

比如一个工具要查三张表、调两个接口、跑一次重排。整个工具可能耗时 8 秒。

如果只等工具最终返回,前端还是会空等。

这时可以在工具内部通过 get_stream_writer 写出自定义事件。官方示例里,工具可以写出类似“正在查询城市数据”“已经获取城市数据”的自定义进度。

产品上可以这样显示:

正在查询知识库。

召回 20 条文档。

正在重排 Top5。

正在生成最终答案。

这类信息不一定进入最终回答,但应该进入前端进度区。

custom 流的价值:让长工具不再像黑盒。

9. Event Streaming:更适合前端的方式

stream_mode 更底层,适合后端控制。

stream_events 更产品化,适合前端消费。

因为它把事件拆成 projection。你可以只消费 stream.messages,也可以消费 stream.tool_calls,还可以拿 stream.output 做最终状态持久化。

这对前端很友好。

聊天气泡只关心 messages。

工具卡片只关心 tool_calls。

状态面板只关心 values。

最终入库只关心 output。

这比所有事件混在一个 for 循环里更清晰。

前端不要直接理解 Agent 内核。前端只需要消费适合 UI 的投影。

10. 企业级 Streaming 架构怎么做?

《LangChain 系列》Streaming:为什么 AI 应用一定要做流式输出?配图

企业级 Streaming 架构

生产系统里,Streaming 不是 Python 服务直接连浏览器就完事。

推荐架构是:

Python AI 服务负责 LangChain / LangGraph 执行,产出 stream。

Java 主服务负责鉴权、会话、限流、审计、用户态、日志、SSE 或 WebSocket 转发。

前端负责把不同事件渲染成不同 UI:聊天文本、进度条、工具卡片、审批卡片、错误提示。

观测系统负责记录每一段事件的 traceId、node、tool、耗时、token 和异常。

这样做的好处是:AI 服务专注推理,Java 服务专注工程治理,前端专注体验。

11. SSE 还是 WebSocket?

大多数问答场景,SSE 就够。

SSE 是服务端单向推送,简单、稳定、适合聊天流、进度流、日志流。

WebSocket 更适合双向实时交互。比如一边生成,一边允许用户打断、补充参数、审批工具、控制子 Agent。

简单选择:

普通聊天问答:SSE。

Agent 进度展示:SSE。

需要实时打断和交互控制:WebSocket。

移动端弱网环境:要加心跳、重连、事件 ID、断点续传。

无论用哪种,后端都要把事件统一成协议。不要把 LangChain 原始对象直接丢给前端。

12. 上线最容易踩的坑

《LangChain 系列》Streaming:为什么 AI 应用一定要做流式输出?配图

Streaming 上线常见问题

第一,不要只做 token 流。Agent 应用至少要有 messages 和 updates。

第二,不要把内部推理、敏感参数、数据库结果原样流给用户。流式输出也要做脱敏。

第三,不要忽略异常。如果中途工具失败,前端必须收到错误事件,而不是连接突然断掉。

第四,不要忘记 traceId。流式请求更难排查,因为它不是一次返回,而是一串事件。

第五,不要让前端直接适配十几种后端原始 chunk。应该在网关层统一事件协议。

第六,不要没有心跳。很多代理、网关、浏览器都会因为长时间无数据而断开连接。

13. 生产环境推荐事件协议

建议把后端事件统一包装成自己的业务协议。

{
"event_id": "evt_001",
"trace_id": "req_123",
"type": "message.delta | tool.start | tool.end | state.update | error | done",
"node": "model | tools | router",
"data": {},
"timestamp": 1710000000000
}

这层协议很重要。

因为你以后可能从 LangChain 换到别的框架,也可能同时接入多个 Agent 服务。前端不应该被底层框架绑死。

LangChain 的 chunk 是内部执行视角。业务事件协议是产品视角。

底层可以变,前端协议要稳定。

14. 总结

真正成熟的 AI 应用,不是等答案出来才开始响应用户,而是从第一秒就让用户看见系统正在认真工作。

要点速读

在普通接口里,后端必须等模型、工具、RAG、Agent 全部跑完,才能一次性返回。用户看到的是空白。 在流式接口里,模型

  • 在普通接口里,后端必须等模型、工具、RAG、Agent 全部跑完,才能一次性返回
  • 用户看到的是空白
  • 在流式接口里,模型