
🚀 欢迎来到我的CSDN博客:Optimistic _ chen
✨ 一名热爱技术与分享的全栈开发者,在这里记录成长,专注分享编程技术与实战经验,助力你的技术成长之路,与你共同进步!
🚀我的专栏推荐:
| 专栏 | 内容特色 | 适合人群 |
|---|---|---|
| 🔥C语言从入门到精通 | 系统讲解基础语法、指针、内存管理、项目实战 | 零基础新手、考研党、复习 |
| 🔥Java基础语法 | 系统解释了基础语法、类与对象、继承 | Java初学者 |
| 🔥Java核心技术 | 面向对象、集合框架、多线程、网络编程、新特性解析 | 有一定语法基础的开发者 |
| 🔥Java EE 进阶实战 | Servlet、JSP、SpringBoot、MyBatis、项目案例拆解 | 想快速入门Java Web开发的同学 |
| 🔥Java数据结构与算法 | 图解数据结构、LeetCode刷题解析、大厂面试算法题 | 面试备战、算法爱好者、计算机专业学生 |
| 🔥Redis系列 | 从数据类型到核心特性解析 | 项目必备 |
🚀我的承诺:
✅ 文章配套代码:每篇技术文章都提供完整的可运行代码示例
✅ 持续更新:专栏内容定期更新,紧跟技术趋势
✅ 答疑交流:欢迎在文章评论区留言讨论,我会及时回复(支持互粉)
🚀 关注我,解锁更多技术干货!
⏳ 每天进步一点点,未来惊艳所有人!✍️ 持续更新中,记得⭐收藏关注⭐不迷路 ✨
📌 标签:#技术博客 #编程学习 #Java #C语言 #算法 #程序员
流式处理
流式处理(Streaming)是 LangChain 中非常核心的能力,尤其在构建聊天应用或需要实时反馈的场景中。它能让 LLM 的输出像打字机一样逐步呈现,而不必等整个响应生成完毕,大幅提升用户体验。
- 我们之前直接使⽤invoke的调⽤⽅式属于⾮流式传输,看到的现象是聊天模型直接返回全量内容,若模型思考时间较⻓,则我们等待的时间就越⻓
- 流式返回减少用户等待时间使系统与用户的交互性大大提高
stream()同步传输
LangChain 现在推荐使用 LCEL(LangChain Expression Language) 构建的 Runnable 体系(也就是链),其内置 .stream() 方法
from langchain.chat_models import init_chat_model
model = init_chat_model("deepseek-v4-pro", model_provider="deepseek")
chunks=[]
for chunk in model.stream("简单介绍LangChain流式输出"):
chunks.append(chunk)
print(chunk.content,end="|",flush=True)

.stream() 返回一个迭代器,每次产出的是一个 AIMessageChunk,其 .content 是增量文本。
astream()异步传输
import asyncio
from langchain.chat_models import init_chat_model
model = init_chat_model("deepseek-v4-pro", model_provider="deepseek")
async def async_stream():
print("=========异步调用======")
async for chunk in model.astream("简单解释LangChain流式传输"):
print(chunk.content,end="|",flush=True)
asyncio.run(async_stream())
之前定义聊天模型中提到,聊天模型、输出解析器等组件都实现Runnable接口,它们都是Runnable接口的实例:

所以.stream() 和.astream() ⽅法产⽣的块(chunk)类型取决于正在流式传输的组件。我们当前正在使⽤聊天模型的流式传输,返回的每个块都将是⼀个AIMessageChunk 。但是,对于其他组件,块类型可能不同。
输出解析器
接下俩我们使用LCEL构建一简单的链,结合模型和解析器来构建
#定义大模型
model = init_chat_model("deepseek-v4-pro", model_provider="deepseek")
#定义输出解析器
parser=StrOutputParser()
# 定义链
chain=model|parser
for chunk in chain.stream("写一首关于程序员的诗词,5句话"):
print(chunk,end="|",flush=True)
我们使⽤模型和解析器,StrOutputParser 来解析模型的输出,它从AIMessageChunk 中提取内容字段,因为model.stream(...) 每次产出的 chunk 是一个 AIMessageChunk 对象,它包含 .content(文本内容)以及可能的额外字段(如工具调用信息等)

StrOutputParser 在这里负责把模型输出的消息对象简化为纯文本,让流式循环中拿到的每一块内容直接就是可打印的字符串,并且保留了流式的分块效果。
如果想要更改解析器的输出方法,可以通过在链中使用生成器函数完成自定义解析器。
import re
from typing import Iterator
from langchain.chat_models import init_chat_model
from langchain_core.output_parsers import StrOutputParser
# 定义大模型
model = init_chat_model("deepseek-v4-pro", model_provider="deepseek")
# 定义输出解析器
parser = StrOutputParser()
# 定义链
chain = model | parser
def stream_sentences(chain, prompt: str) -> Iterator[str]:
"""
生成器函数:按句输出模型的流式结果
- chain: 已经组装好的 Runnable
- prompt: 用户输入
yield 完整的句子(字符串),每次一句
"""
# 用于暂存尚未形成句子的文本
buffer = ""
# 句子结束符(中文常见)
sentence_endings = re.compile(r"[。!?;]")
for chunk in chain.stream(prompt): # chunk 已经是字符串
buffer += chunk
# 查找最后一个句子结束符的位置
while True:
match = sentence_endings.search(buffer)
if not match:
break # 没有完整的句子,继续接收下一个 chunk
# 拿到结束符之前的内容(包含结束符,即一个完整句子)
end_pos = match.end()
sentence = buffer[:end_pos]
buffer = buffer[end_pos:] # 剩余部分继续保留
yield sentence # 生成一个完整的句子
# 所有 chunk 处理完后,如果 buffer 还有剩余内容,也作为最后一句输出
if buffer.strip():
yield buffer
# 调用生成器,一句一句打印
print("=== 一句一句流式输出 ===")
for sentence in stream_sentences(chain, "写一首关于程序员的诗词,5句话"):
print(sentence)

SSE协议
在介绍 SSE(Server-Sent Events)协议之前,需要先理解 HTTP 协议的设计。HTTP 协议本身是一种无状态的请求-响应模式,这意味着服务器无法主动向客户端推送消息。然而,通过 Server-Sent Events(服务器发送事件)技术,我们可以实现流式传输,允许服务器主动、持续地向客户端推送数据流。动向客户端推送数据流。
也就是说,服务端向客户端声明,接下来要发送的是流消息,浏览器可以通过内置的EventSoucre API来接收并处理这些实时事件。
SSE特点:
- 基于HTTP协议:复用标准HTTP/HTTPS协议,无需额外端口或者协议,兼容性好且易于部署
- 单向通信机制:SSE仅⽀持服务器向客⼾端的单向数据推送
- 自动重连机制:支持短线重连,连接中断,浏览器会自动尝试重新连接(retry字段指定重连间隔)
- 自定义消息类型:客户端发起请求后,服务器保持连接开放,响应头设置
Content-Type:text/event-stream,标识为事件流格式,持续推送事件流
每一次发送的消息,由若干个message组成,每个message之间由\n\n分割,每个message内部由若干行组成,每一行都是如下格式:
[field]:value \n
field可以取值:
- data[必须]:数据内容
- event[必须]:标识自定义的事件类型,默认是message事件
- id[非必须]:数据标识符,相当于每一条数据的编号
- retry[非必须]:指定浏览器重新发起连接的时间间隔
LangChain流式传输
LangChain本身并没有网络协议,而是依赖于底层大模型提供商和自身服务所使用的Web框架的协议
因此对于LangChain的流式传输能⼒,本⾝是因为⼤模型供应商提供了流式传输能⼒,由LangChain进⾏调⽤后接收并处理成⼀个个的AIMessageChunk.
具体情况我们可以阅读源码探索整个传输流程。
def _stream(
self,
messages: list[BaseMessage], # 对话消息列表
stop: list[str] | None = None, # 停止词列表(触发停止的 token)
run_manager: CallbackManagerForLLMRun | None = None, # 回调管理器,用于 on_llm_new_token 等事件
*,
stream_usage: bool | None = None, # 是否在流式响应中包含 token 用量信息
**kwargs: Any,
) -> Iterator[ChatGenerationChunk]: # 返回值是一个生成器,逐块产出 ChatGenerationChunk
# ------------------------------------------------------------
# 1. 准备阶段:确保客户端可用,设置 stream 参数
# ------------------------------------------------------------
self._ensure_sync_client_available() # 确认 OpenAI 客户端已创建(同步)
kwargs["stream"] = True # 强制开启流式模式
# 决定是否需要在流中获取 usage 信息(如 token 消耗)
stream_usage = self._should_stream_usage(stream_usage, **kwargs)
if stream_usage:
# 当需要 usage 时,在请求中加入 stream_options
kwargs["stream_options"] = {"include_usage": stream_usage}
# 将消息和停止词等参数整合成最终的 API 请求体
payload = self._get_request_payload(messages, stop=stop, **kwargs)
# 记录当前使用的消息块类型(默认为 AIMessageChunk)
default_chunk_class: type[BaseMessageChunk] = AIMessageChunk
# 用于存储响应的元信息(例如 HTTP 头),初始为空
base_generation_info = {}
try:
# ------------------------------------------------------------
# 2. 根据是否使用结构化输出(response_format)分流处理
# ------------------------------------------------------------
if "response_format" in payload:
# ---- 分支 A:使用 JSON 等结构化输出 ----
# 这种情况下不能同时获取响应头(官方限制)
if self.include_response_headers:
warnings.warn(
"Cannot currently include response headers when "
"response_format is specified."
)
# 移除 stream 参数(因为 beta 接口下 stream 由上下文管理器控制)
payload.pop("stream")
# 使用 beta 客户端发起流式请求(专门针对 JSON 模式优化)
response_stream = self.root_client.beta.chat.completions.stream(
**payload
)
context_manager = response_stream # 后续统一用 with 打开
else:
# ---- 分支 B:普通文本输出 ----
# 根据是否需要包含响应头决定请求方式
if self.include_response_headers:
# 需要响应头时,先拿到原始响应对象(带 headers)
raw_response = self.client.with_raw_response.create(**payload)
# 解析出正常的响应体(这样才能被上下文管理器识别)
response = raw_response.parse()
# 把 headers 存入 base_generation_info,后续会附着在第一个 chunk 上
base_generation_info = {"headers": dict(raw_response.headers)}
else:
# 不需要响应头,直接调用普通 API
response = self.client.create(**payload)
context_manager = response # 统一用 with 打开
# ------------------------------------------------------------
# 3. 使用上下文管理器迭代响应块
# ------------------------------------------------------------
with context_manager as response:
is_first_chunk = True # 标记是否为第一个有效 chunk
for chunk in response: # 遍历 API 返回的每一个增量数据
# 确保 chunk 是字典格式(兼容不同 SDK 返回类型)
if not isinstance(chunk, dict):
chunk = chunk.model_dump()
# 核心转换:将原始 chunk 转成 LangChain 的 ChatGenerationChunk
generation_chunk = self._convert_chunk_to_generation_chunk(
chunk,
default_chunk_class,
# 只对第一个 chunk 注入 headers 等基础信息
base_generation_info if is_first_chunk else {},
)
# 有些 chunk 可能被过滤(返回 None),跳过
if generation_chunk is None:
continue
# 更新当前使用的消息块类型(因为后续 chunk 的类型可能变化,如 AIMessageChunk -> ToolMessageChunk)
default_chunk_class = generation_chunk.message.__class__
# 提取 logprobs(如果存在)
logprobs = (generation_chunk.generation_info or {}).get("logprobs")
# ----- 触发回调(关键!这是外部能收到逐 token 推送的地方)-----
if run_manager:
run_manager.on_llm_new_token(
generation_chunk.text, # 当前 token 文本
chunk=generation_chunk, # 完整 chunk 对象
logprobs=logprobs, # 对数概率信息
)
is_first_chunk = False # 第一个 chunk 已处理
yield generation_chunk # 向外产出 chunk,供上层 .stream() 迭代
except openai.BadRequestError as e:
# 处理 400 类错误(如参数错误)
_handle_openai_bad_request(e)
except openai.APIError as e:
# 处理其他 API 错误
_handle_openai_api_error(e)
# ------------------------------------------------------------
# 4. JSON 模式下的额外收尾:获取最终完成对象
# ------------------------------------------------------------
# 注意这段代码在 try-except 之外,仅在使用了 response_format 且 response 对象支持 get_final_completion 时执行
if hasattr(response, "get_final_completion") and "response_format" in payload:
final_completion = response.get_final_completion()
# 将最终补全转换为 GenerationChunk
generation_chunk = self._get_generation_chunk_from_completion(
final_completion
)
# 同样触发一次 token 回调(最终汇总或结束标记)
if run_manager:
run_manager.on_llm_new_token(
generation_chunk.text, chunk=generation_chunk
)
yield generation_chunk
- 整段代码就是一个生成器,从OpenAI API拿到原始数据流,每取到一小块就把它”打成“LangChain标准格式(ChatGenerationChunk),并交给回调系统,然后对外yield
- 分两条路径:普通聊天用常规 API,JSON 模式用 beta 接口,但最终都要转换成统一的 chunk 对象。这样就可以以统⼀的⽅式处理来⾃不同模型提供商(OpenAI,Anthropic等)的流式响应。
- 回调是同步嵌入的:每生成一个 chunk,立即调用
on_llm_new_token,所以你在外面能实时收到每个 token,而不是等全部结束 - langchain-openai包通过集成OpenAIPythonSDK,提供了⼀个HTTP客⼾端。
完结撒花!🎉

如果这篇博客对你有帮助,不妨点个赞支持一下吧!👍
你的鼓励是我创作的最大动力~
✨ 想获取更多干货? 欢迎关注我的专栏 → optimistic_chen
📌 收藏本文,下次需要时不迷路!
我们下期再见!💫 持续更新中……
悄悄说:点击主页有更多精彩内容哦~ 😊
436

被折叠的 条评论
为什么被折叠?



