大家好,我是工藤学编程 🦉 | 一个正在努力学习的小博主,期待你的关注 |
---|---|
实战代码系列最新文章😉 | C++实现图书管理系统(Qt C++ GUI界面版) |
SpringBoot实战系列🐷 | 【SpringBoot实战系列】SpringBoot3.X 整合 MinIO 存储原生方案 |
分库分表 | 分库分表之实战-sharding-JDBC分库分表执行流程原理剖析 |
消息队列 | 深入浅出 RabbitMQ-RabbitMQ消息确认机制(ACK) |
AI大模型 | 零基础学AI大模型之LangChain链 |
前情摘要:
1、零基础学AI大模型之读懂AI大模型
2、零基础学AI大模型之从0到1调用大模型API
3、零基础学AI大模型之SpringAI
4、零基础学AI大模型之AI大模型常见概念
5、零基础学AI大模型之大模型私有化部署全指南
6、零基础学AI大模型之AI大模型可视化界面
7、零基础学AI大模型之LangChain
8、零基础学AI大模型之LangChain六大核心模块与大模型IO交互链路
9、零基础学AI大模型之Prompt提示词工程
10、零基础学AI大模型之LangChain-PromptTemplate
11、零基础学AI大模型之ChatModel聊天模型与ChatPromptTemplate实战
12、零基础学AI大模型之LangChain链
本文章目录
- 零基础学AI大模型之Stream流式输出实战
-
- 前情摘要
- [1. 为什么需要流式输出?先搞懂"一次性输出"的痛点](#1. 为什么需要流式输出?先搞懂“一次性输出”的痛点)
- [2. 流式输出核心原理:什么是Stream?](#2. 流式输出核心原理:什么是Stream?)
- [3. 基础实战:用ChatOpenAI实现"故事小助手"流式输出](#3. 基础实战:用ChatOpenAI实现“故事小助手”流式输出)
-
- [3.1 环境准备](#3.1 环境准备)
- [3.2 完整代码:故事小助手(逐字生成故事)](#3.2 完整代码:故事小助手(逐字生成故事))
- [3.3 运行效果说明](#3.3 运行效果说明)
- [4. 进阶实战:用LCEL实现"科普助手"流式输出](#4. 进阶实战:用LCEL实现“科普助手”流式输出)
-
- [4.1 需求定义:科普助手](#4.1 需求定义:科普助手)
- [4.2 完整代码:LCEL流式串联](#4.2 完整代码:LCEL流式串联)
- [4.3 运行效果示例(输入"人工智能")](#4.3 运行效果示例(输入“人工智能”))
- [4.4 LCEL流式的优势](#4.4 LCEL流式的优势)
- [5. 流式输出的优势与限制:实战前必看](#5. 流式输出的优势与限制:实战前必看)
-
- [5.1 核心优势](#5.1 核心优势)
- [5.2 关键限制](#5.2 关键限制)
- [6. 实战注意事项:避坑指南](#6. 实战注意事项:避坑指南)
- [7. 总结](#7. 总结)
-
- [7.1 核心总结](#7.1 核心总结)
零基础学AI大模型之Stream流式输出实战
前情摘要
在之前的LangChain实战中,我们调用大模型时都是「一次性获取完整结果」------比如生成一篇文案、回答一个问题,需要等模型把所有内容生成完才返回。但在实际场景中(如ChatGPT聊天界面、长文本生成),这种方式会让用户面对"空白加载页"等待几秒甚至更久,体验大打折扣。
本文将聚焦LLM的Stream流式输出,从核心原理讲起,通过"故事小助手""科普助手"两个实战案例,带你掌握从基础调用到LCEL表达式的流式落地,最后分析流式输出的优劣势与实战注意事项。

1. 为什么需要流式输出?先搞懂"一次性输出"的痛点
在学习流式输出前,我们先明确:流式输出不是"让模型生成更快",而是"让用户感知更快"。
先看"一次性输出"的典型问题:
- 体验割裂:生成1000字的文章需要5秒,这5秒内用户看不到任何内容,容易误以为"程序卡住了";
- 内存压力大:如果生成超长文本(如万字报告),一次性加载完整结果会占用更多内存,甚至导致前端页面卡顿;
- 无法中断:一旦触发生成,必须等完整结果返回才能停止,若用户不想看了也无法中途取消。
而流式输出的解决思路很简单:模型生成一个字/一个短句,就立刻返回一个"片段(Chunk)",前端实时拼接展示------就像与人对话时"边说边听",而非"等对方说完一长段再回应"。
2. 流式输出核心原理:什么是Stream?
LLM的流式输出本质是基于HTTP流式传输(或WebSocket)实现的"增量返回"机制,核心逻辑可拆解为3步:
- 模型端"分段生成":大模型生成文本时,并非一次性计算所有内容,而是按"token片段"(可理解为"词语/短句单元")逐步生成------比如生成"讲一个翠花的故事",模型会先算"翠花是山村里的",再算"一个小姑娘,每天帮妈妈",以此类推;
- 实时推送片段:每生成一个token片段,模型就通过"流式接口"将片段推送给调用方(如我们的Python代码),而非等待全部生成;
- 调用方"实时拼接":我们的代码接收到每个片段后,立即打印、展示或存储,最终拼接成完整结果。
类比生活场景:就像外卖小哥送10份餐,"一次性输出"是等10份餐全做好再一起送;"流式输出"是做好1份送1份,你收到后可以先吃,不用等全部到齐。
3. 基础实战:用ChatOpenAI实现"故事小助手"流式输出
首先从最基础的「直接调用大模型流式接口」入手,实现一个"输入关键词,流式生成故事"的小工具。我们依然用DeepSeek 作为示例模型,你也可以替换为GPT-4、Llama 3等支持流式的模型。
3.1 环境准备
确保已安装最新版langchain-openai
(流式功能依赖较新的API封装):
bash
pip install --upgrade langchain-openai
3.2 完整代码:故事小助手(逐字生成故事)
python
# 1. 导入依赖:ChatOpenAI是大模型客户端,支持流式调用
from langchain_openai import ChatOpenAI
# 2. 初始化大模型:关键是确保模型支持流式(qwen-plus、gpt-4等均支持)
model = ChatOpenAI(
model_name='deepseek-r1:7b', # 本地模型名称,根据实际情况填写
base_url="http://127.0.0.1:11434/v1", # 本地模型API地址
api_key="none", # 本地模型通常不需要真实API密钥
temperature=0.7, # 可根据需要调整温度参数
streaming=True # 开启流式模式(核心参数,必须设为True)
)
# 3. 流式调用:用for循环迭代接收"片段(Chunk)"
print("=== 故事小助手(流式输出)===")
print("正在生成《翠花的山村故事》...\n")
# model.stream()返回的是"片段迭代器",每次循环获取一个生成片段
for chunk in model.stream("讲一个关于山村女孩翠花的短故事,500字以内,温暖治愈风格。"):
# chunk.content 是当前片段的文本内容(如"翠花""住在""太行山脚下")
# end="":取消默认的"换行",让片段连续拼接
# flush=True:强制实时刷新输出(避免片段在缓冲区堆积,导致"一次性打印")
print(chunk.content, end="", flush=True)
3.3 运行效果说明
代码执行后,你会看到控制台逐字/逐词输出故事,而非等待几秒后一次性显示:
=== 故事小助手(流式输出)===
正在生成《翠花的山村故事》...
<think>
好的,用户让我写一个关于山村女孩翠花的短故事,要求500字以内,温暖治愈风格。首先,我需要理解用户的需求。他们可能喜欢温情的故事,或许想表达一种宁静的生活氛围或者家庭关系中的温暖瞬间。
接下来,我要考虑故事的主题。翠花是一个名字,可能暗示她的性格温柔善良。山村环境适合营造宁静的氛围,这样更容易传达温暖的感觉。主角可能是一个孩子,这样容易让读者产生共鸣。
然后,我需要构思一个情节。考虑到是温暖治愈风格,可以围绕家庭团聚展开。比如,翠花和她的奶奶一起过生日,这样的场景既有情感又有温馨的感觉。奶奶作为长辈的形象,能够很好地体现家庭的温暖。
在故事结构上,开头介绍人物和背景,中间发展自然,结尾点出主题。比如,翠花照顾生病的奶奶,这个情节不仅展示了她的善良,也体现了逆转的希望,符合治愈的主题。
语言方面,要简洁明了,用细腻的描写来营造画面感,让读者感受到温暖和宁静的氛围。同时,注意节奏,避免过于复杂的情节,保持故事流畅自然。
最后,检查字数是否在500字以内,并确保故事传达出积极的情感,比如亲情的力量或者生活的美好瞬间。
</think>
## 《奶奶的病床》
我端着水碗站在厨房里,看着奶奶平日里气色红润的脸庞此刻变得苍白。她坐在角落的藤椅上,手里紧紧攥着那张泛黄的药方。
"小翠花来。"奶奶颤巍巍的声音像是从很远的地方传来。
我小心翼翼地走进房间,脚步声轻得像一片落叶。将药方放在桌上时,我不由得放慢了手速。红肿的眼睛里,我能看到奶奶年轻时在镜子里的倒影。
"这是医生说的药方。"我轻声说,"我先给你倒水吧。"
茶几上的杯子空空如也,奶奶指节分明的手指轻轻叩击着杯沿,似乎这样就能让药效更慢一些。我知道她又要犯那个习惯------等我看书看累了才来。
"小翠花,你多大了?"她的声音里带着不容置疑的关心。
我打量着手里的水杯,又看着这间熟悉的小屋。竹制的窗外已经有些开裂,但奶奶还是坚持每天午休时坐在那里晒太阳。那时的她总是笑着的,像是有说不尽的话题。
"十岁。"我回答得很快,"写作业的时候写累了就来吧。"
"不用了不用了..."奶奶摇摇头,水声中带着淡淡的苦涩,"你先去写作业吧,别站着太久了。"

关键注意点:
streaming=True
是开启流式的核心参数,若未设置,model.stream()
会报错;flush=True
必须加,否则Python会将片段缓存起来,导致"看似流式实则一次性输出"。
4. 进阶实战:用LCEL实现"科普助手"流式输出
在LangChain 0.3+中,推荐用LCEL表达式 (|
管道符)串联组件------流式输出也不例外。相比直接调用model.stream()
,LCEL能更灵活地结合"提示模板、输出解析器",适配复杂场景(如带格式的科普内容生成)。
4.1 需求定义:科普助手
实现功能:接收用户输入的"科普主题",按"【核心概念】→【生活案例】→【一句话总结】"的格式,流式生成科普内容。
4.2 完整代码:LCEL流式串联
python
# 1. 导入依赖:LCEL+流式所需组件
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate # 提示模板(支持多轮格式)
from langchain_core.output_parsers import StrOutputParser # 流式输出解析器
from langchain_core.runnables import RunnablePassthrough # 辅助传递变量
# 2. 初始化核心组件
# 2.1 大模型(开启流式)
model = ChatOpenAI(
model_name='deepseek-r1:7b', # 本地模型名称,根据实际情况填写
base_url="http://127.0.0.1:11434/v1", # 本地模型API地址
api_key="none", # 本地模型通常不需要真实API密钥
temperature=0.7, # 可根据需要调整温度参数
streaming=True # 开启流式模式(核心参数,必须设为True)
)
# 2.2 提示模板(定义科普内容的格式)
prompt = ChatPromptTemplate.from_messages([
("system", "你是专业科普助手,按以下格式生成内容:\n1. 【核心概念】:用30字内解释主题;\n2. 【生活案例】:举1个贴近生活的例子;\n3. 【一句话总结】:提炼核心价值。"),
("user", "科普主题:{topic}") # 动态变量:用户输入的科普主题
])
# 2.3 输出解析器(将流式片段转为纯文本,避免多余格式)
parser = StrOutputParser()
# 3. 用LCEL管道符串联组件:prompt → 变量传递 → model → parser
# RunnablePassthrough():确保"topic"变量能传递到prompt中
lcel_stream_chain = (
{"topic": RunnablePassthrough()} # 传递用户输入的"topic"
| prompt # 填充提示模板
| model # 流式调用模型
| parser # 解析流式片段
)
# 4. 流式调用科普助手
print("=== 科普助手(LCEL流式输出)===")
user_topic = input("请输入科普主题:") # 接收用户输入(如"人工智能""区块链")
print("\n正在生成科普内容...\n")
# 迭代接收流式片段并打印
for chunk in lcel_stream_chain.stream(user_topic):
print(chunk, end="", flush=True)
4.3 运行效果示例(输入"人工智能")
=== 科普助手(LCEL流式输出)===
请输入科普主题:人工智能
正在生成科普内容...
<think>
嗯,用户让我写一个关于人工智能的科普内容,并且要按照特定的格式来生成。首先,我得理解他们的要求是什么。
他们提到有三个部分:核心概念、生活案例和一句话总结。核心概念需要用30字以内解释,所以我需要简洁明了地点出AI的基本意思。可能用"模仿人类智能"这样的表达比较合适,因为大家都知道AI是模仿人类的。
接下来是生活案例,我得找一个贴近生活的例子。自动驾驶是个不错的选择,现在很多人用过这个功能,容易让人理解。同时,它展示了AI的实际应用,说明AI不仅能做复杂决策还能解决实际问题。
最后是句话总结,要提炼核心价值。AI的核心价值应该在于模拟人类智能,帮助人们更高效地完成任务,并推动科技发展。这样不仅点明了主题,还强调了其对社会的积极影响。
用户可能是科普教育者或者普通读者,他们希望通过简洁明了的内容快速理解人工智能的基本概念和实际应用。深层需求可能是在寻找一种易于理解的方式,展示AI对日常生活的影响,同时激励更多人关注科技的发展。
</think>
1. 【核心概念】:人工智能(AI)是模仿人类智能的系统技术。
2. 【生活案例】:自动驾驶汽车通过AI识别道路信号、规划路线,帮助司机做出复杂决策。
3. 【一句话总结】:人工智能的核心价值在于模拟人类智能,解决复杂问题并推动科技发展。

4.4 LCEL流式的优势
相比直接调用model.stream()
,LCEL的核心优势在于组件解耦与复用:
- 若想修改科普格式,只需改
prompt
,无需动模型和解析器; - 若想切换模型(如从通义千问换成GPT-3.5),只需替换
model
实例,流式逻辑不变; - 后续可轻松添加"输入验证""结果存储"等组件(如
输入验证 → prompt → model → parser → 数据库存储
),流式特性不受影响。
5. 流式输出的优势与限制:实战前必看
流式输出虽能提升体验,但并非适用于所有场景。我们需要客观看待其优劣势,避免盲目使用。
5.1 核心优势
优势点 | 具体场景说明 |
---|---|
提升用户感知体验 | 聊天机器人、实时问答场景中,用户看到"逐字输出",会觉得"响应很快",减少等待焦虑; |
降低内存占用 | 生成万字报告、小说章节时,无需一次性加载完整文本(可能占几十MB内存),而是逐段处理; |
支持中途中断 | 若用户看到部分内容后不想继续(如生成的故事不符合预期),可随时停止流式接收,节省资源; |
适配长文本生成 | 一次性输出长文本可能触发"超时错误"(如API限制单次返回时长),流式分段返回可规避此问题; |
5.2 关键限制
限制点 | 实际影响与应对方案 |
---|---|
完整结果总耗时未减少 | 流式只是"分段返回",模型生成完整内容的总时间基本不变(甚至略增,因多了分段传输开销); → 应对:仅在"用户需要实时感知"的场景用(如聊天),后台批量生成(如报表)仍用一次性输出; |
代码复杂度提升 | 需处理"片段拼接""中断逻辑""错误重试"(如某片段传输失败,需重新请求后续内容); → 应对:用LangChain LCEL封装,减少重复代码; |
部分模型/API不支持 | 并非所有LLM都支持流式输出(如部分轻量开源模型、旧版API); → 应对:调用前查看模型文档(如通义千问、GPT系列、Llama 3均明确支持流式); |
输出解析难度增加 | 一次性输出可直接用JSON解析器提取结构化数据(如"{"核心概念": "xxx", "案例": "xxx"}"),流式需逐段判断"是否解析完整"; → 应对:用LangChain的StreamingOutputParser (专为流式设计的解析器); |
6. 实战注意事项:避坑指南
- API密钥安全 :代码中的
api_key
不要硬编码到项目中,建议用环境变量(如os.getenv("QWEN_API_KEY")
)或配置文件存储,避免泄露; - 流式中断处理 :在用户主动取消(如关闭页面)或网络异常时,需调用
stream.close()
停止流式传输,避免模型继续生成浪费资源; - 不同模型的流式差异 :
- 通义千问、GPT系列的
stream()
返回的是"包含content
字段的Chunk对象"; - 部分开源模型(如本地部署的Llama 3)需通过
transformers
库的pipeline("text-generation", streaming=True)
实现流式,接口略有不同;
- 通义千问、GPT系列的
- 前端流式展示 :若需在Web端实现流式(如类似ChatGPT的界面),后端需用SSE(Server-Sent Events)或WebSocket传递流式片段,前端用
EventSource
接收并实时更新DOM;
7. 总结
7.1 核心总结
- 流式输出的核心价值:不是"加速生成",而是"提升用户实时感知体验",适合聊天、长文本实时生成等场景;
- 落地路径 :基础场景用
ChatOpenAI.stream()
,复杂场景用LCEL串联"模板+模型+解析器",减少代码复杂度; - 适用边界:优先在"用户可见"的交互场景用,后台批量任务用一次性输出,平衡体验与效率。
如果本文对你有帮助,欢迎点赞+评论,说说你在流式输出中遇到的问题或想实现的场景,一起交流进步!
