[LangChain] 学习笔记(04):手搓 ChatGPT 打字机——流式输出(SSE)与模型初始化选型

📌 核心结论(TL;DR)

  1. 体验革命:流式输出(Streaming)通过底层的 SSE(Server-Sent Events)协议保持 HTTP 长连接,服务器边生成边推送,彻底消除用户的等待焦虑。

  2. 同步与异步 :本地脚本调试可用 stream();在 Web 后端开发中,必须使用 astream() 异步迭代,避免阻塞服务器线程。注意在 Windows 下需配置特定的 EventLoopPolicy 以防报错。

  3. 模型动态配置 :通过 configurable_fields 和 config_prefix,可以在不修改代码和重启服务的情况下,在运行时动态修改模型参数(如 temperature)。

  4. 模型选型 :对接国内兼容 OpenAI 协议的模型(如 DeepSeek),推荐直接使用 ChatOpenAI + base_url,兼容性最强、依赖最少。

🔍 一、 核心原理与选型探讨

1. 为什么需要流式输出(SSE)?

传统 invoke 是阻塞式的,用户要等大模型把所有字生成完才能看到结果。而流式传输基于 SSE(Server-Sent Events) 协议,服务器会保持连接不断开,每生成一个字(一个 Token),就立刻推送给客户端。LangChain 底层接收到这些数据碎片后,会将其封装成 AIMessageChunk 对象。

2. 为什么不用 init_chat_model?

  • 依赖问题 :init_chat_model(model="...", model_provider="deepseek") 底层会去调用 langchain-deepseek 这个专属集成包。

  • 环境报错 :还记得你之前终端里报的错吗?No matching distribution found for langchain-deepseek。因为你的 Python 版本或者某些依赖,导致这个专属包装不上。如果你强用 init_chat_model,代码直接跑崩溃。

  • 通用性最强 :DeepSeek 的接口是 100% 兼容 OpenAI 协议的。用 ChatOpenAI + base_url 是业界公认的"万能插座" 。不管你是用 DeepSeek、Kimi、通义千问,还是用中转代理 API,只要改个 base_url 就能跑,不需要去苦苦等待各家厂商发布专属的 LangChain 集成包。

3. configurable_fields 与 config_prefix

在构建企业级应用时,我们经常需要在运行时(前端传参)动态调整大模型的参数。

  • configurable_fields:允许将参数(如 temperature)标记为可配置。

  • config_prefix:如果链里有两个以上模型 ,必须用它加上前缀(如 config_prefix="model_a"),否则运行时无法区分参数传给谁。

💻 二、 精选核心代码片段

1. 基础流式与 LCEL 结合(同步)

python 复制代码
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

load_dotenv()

# 使用 ChatOpenAI 连接 DeepSeek
model = ChatOpenAI(
    model="deepseek-chat",
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com",
)

chain = model | StrOutputParser()

# stream() 返回一个迭代器
for chunk in chain.stream("写一段关于春天的散文。"):
    # 关键:flush=True 强制刷新缓冲区,实现打字机效果
    print(chunk, end="", flush=True)

2. 企业级异步流式(FastAPI 后端必学)

在 Web 服务中,一个用户的长时间请求不能卡死其他用户,必须使用 astream。

python 复制代码
import asyncio
from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    model="deepseek-chat",
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com",
)

async def async_stream():
    # 使用 async for 接收异步流
    async for chunk in model.astream("写一首绝句。"):
        print(chunk.content, end="", flush=True)

if __name__ == "__main__":
    asyncio.run(async_stream())

报错 RuntimeError: Event loop is closed(进程退出代码为 0) :

这是 Windows 平台下 Python 异步库(asyncio)的经典遗留问题。程序功能正常,但底层网络连接回收时会触发此警告。

解决 :在代码最上方加入以下两行代码,将事件循环策略切换为兼容性更好的 WindowsSelectorEventLoopPolicy:

python 复制代码
import sys, asyncio
if sys.platform.startswith("win"):
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

3. 运行时动态修改参数(多模型隔离)

python 复制代码
from langchain.chat_models import init_chat_model

model_a = init_chat_model(
    model="deepseek-chat",
    model_provider="openai",
    api_key="...",
    base_url="https://api.deepseek.com",
    configurable_fields=("temperature", "max_tokens"),
    config_prefix="model_a",  # 正确位置
)

# 运行时使用带前缀的键名
result = model_a.invoke(
    "写首诗",
    config={"configurable": {"model_a_temperature": 1.5}}
)

`config_prefix` 是 `init_chat_model` 工厂函数的参数,用于为不同模型的配置字段添加命名空间前缀。错误写法会引发 `ValueError`, 当链中存在多个可配置模型时,通过 config_prefix 可以彻底隔离参数命名空间,避免 temperature 等通用字段冲突。

那个 LangChainBetaWarning 只是官方提醒你 init_chat_model 还在 Beta 测试阶段,完全不影响使用,可以忽略。

(完整的包含自定义生成器"按句输出"的代码已上传至 Gitee 仓库)

⚠️ 三、 踩坑排查排雷

  1. 控制台无打字机效果 :Python 的 print() 有系统缓冲区。排查 :必须加上 print(chunk.content, end="", flush=True),强制刷新。

  2. 报错 RuntimeError: Event loop is closed(Windows 专属) :程序正常退出(Exit code 0)但抛出此红字警告。这是 Windows 下 asyncio 底层网络连接的回收遗留问题。排查 :在代码最上方加入 asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) 即可完美解决。

  3. 报错 SyntaxError: 'async for' outside async function :异步迭代必须放在由 async def 声明的协程函数内部。

  4. 多模型动态参数覆盖失效 :链里有两个模型都配置了 temperature,但未加 config_prefix。排查 :声明时加上 config_prefix,传参时使用带前缀的 key(如 "model_a_temp")。

相关推荐
奶油话梅糖37 分钟前
2,615 个知识点、3,003 条先修边:6,800 Star 的开源知识图谱,能顺着错题从高中回溯到小学
人工智能·知识图谱
李帅朋1 小时前
INP-Former阅读笔记
笔记·深度学习·计算机视觉
seacracker1 小时前
不重造 S3 轮子:PowerFS 如何把 RustFS 变成自己的 S3 服务面
人工智能·kv存储·rustfs·powerfs·s3对象存储
IT_陈寒1 小时前
SpringBoot实战技巧总结
前端·人工智能·后端
叼烟扛炮1 小时前
Linux 学习笔记(六十一):网络编程毕业设计——Reactor 反应堆模式
linux·网络·笔记·学习·课程设计
lucas_AI1 小时前
Agent 基准也会烂:Terminal-Bench 三成任务有缺陷,修复后分数集体上涨 10 个点
人工智能·agent
AI源码1 小时前
基于深度学习的日常服饰分类与推荐系统设计
人工智能·深度学习·分类
科小怪1 小时前
Obsidian 下载安装:官网下太慢?笔记存自己电脑,不注册不联网
笔记·下载教程·obsidion
叼烟扛炮3 小时前
Linux 学习笔记(五十九):epoll 的生死抉择——LT 与 ET 模式
linux·运维·服务器·网络·笔记·学习·php