🌟 LangChain 30 天保姆级教程 · Day 15|异步 + 流式响应!让 AI 边思考边输出,告别“转圈等待”!

4 阅读3分钟

系列目标:30 天从 LangChain 入门到企业级部署
今日任务:掌握异步 Chain 调用 → 实现 Token 级流式输出 → 构建 FastAPI 流式聊天接口!


⚡ 一、为什么需要流式响应?

传统 AI 调用是“全有或全无”:

  • 用户提问 → 等待 5 秒 → 一次性返回全部答案

问题

  • 用户焦虑:“AI 是不是卡了?”
  • 长回答体验差(如生成 500 字文章)
  • 无法实现“打字机效果”

解决方案

✅ 流式响应(Streaming)  —— 模型每生成一个 token,就立即推送给前端!

✅ 异步(Async)  —— 不阻塞主线程,支持高并发!

💡 今天,我们就用 LangChain + Ollama + FastAPI,打造一个支持 SSE(Server-Sent Events)的流式聊天机器人


🧱 二、核心概念

表格

技术作用
astream()异步流式调用 Chain/Agent,返回 token 生成器
AsyncCallbackHandler异步回调(监听流式事件)
SSE (Server-Sent Events)前端接收流式数据的标准协议(比 WebSocket 简单)
FastAPI支持异步和流式响应的 Python Web 框架

🔔 注意:并非所有 LLM 都支持流式
✅ Ollama(含 Qwen)✅ 支持!
❌ 某些 API 模型可能不支持(需查文档)


🛠️ 三、动手实践 1:基础流式输出(控制台版)

步骤 1:启用 LLM 流式模式

# day15_streaming.py
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate

# 关键:设置 streaming=True
llm = ChatOllama(model="qwen:7b", temperature=0, streaming=True)

prompt = ChatPromptTemplate.from_template("请写一首关于{topic}的五言诗")
chain = prompt | llm

步骤 2:使用 astream() 异步流式调用

import asyncio

async def stream_poem():
    print("🤖 AI 正在作诗:", end="", flush=True)
    
    # astream() 返回异步生成器
    async for chunk in chain.astream({"topic": "春天"}):
        print(chunk.content, end="", flush=True)
    
    print("\n✅ 完成!")

# 运行
asyncio.run(stream_poem())

▶️ 输出效果(逐字打印):

🤖 AI 正在作诗:春风吹绿柳,
细雨润花红。
燕语穿林过,
莺歌入梦中。
✅ 完成!

✅ 实现了“打字机效果”!用户不再干等。


🛠️ 四、动手实践 2:自定义流式 Callback(记录首 token 时间)

from langchain_core.callbacks import AsyncCallbackHandler

class StreamingCallback(AsyncCallbackHandler):
    def __init__(self):
        self.first_token_time = None

    async def on_llm_start(self, serialized, prompts, **kwargs):
        print("⏳ LLM 开始生成...")

    async def on_llm_new_token(self, token, **kwargs):
        if self.first_token_time is None:
            self.first_token_time = asyncio.get_event_loop().time()
            print(f"\n⏱️ 首个 token 延迟: {self.first_token_time - start_time:.2f}s")
        print(token, end="", flush=True)

💡 可用于监控“首 token 延迟”——衡量系统响应速度的关键指标!


🌐 五、动手实践 3:FastAPI 流式聊天接口(生产级)

步骤 1:安装依赖

pip install fastapi uvicorn sse-starlette

步骤 2:创建流式 API

# day15_fastapi_streaming.py
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from sse_starlette.sse import EventSourceResponse
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
import asyncio

app = FastAPI()

# 全局 LLM(生产环境建议用连接池)
llm = ChatOllama(model="qwen:7b", temperature=0, streaming=True)
prompt = ChatPromptTemplate.from_template("{input}")
chain = prompt | llm

async def event_generator(user_input: str):
    """生成 SSE 事件流"""
    async for chunk in chain.astream({"input": user_input}):
        yield {"data": chunk.content}
    yield {"data": "[DONE]"}  # 结束标记

@app.get("/stream")
async def stream_chat(q: str):
    return EventSourceResponse(event_generator(q))

步骤 3:启动服务

uvicorn day15_fastapi_streaming:app --reload

步骤 4:前端测试(浏览器控制台)

// 打开浏览器控制台执行
const evtSource = new EventSource("http://localhost:8000/stream?q=你好");
evtSource.onmessage = (event) => {
  if (event.data === "[DONE]") {
    console.log("🔚 结束");
    evtSource.close();
  } else {
    process.stdout.write(event.data); // 实际前端应 append 到 DOM
  }
};

✅ 前端将实时收到每个 token,实现丝滑打字效果!


⚠️ 六、注意事项 & 最佳实践

表格

问题建议
模型不支持 streaming检查 LLM 是否实现 BaseChatModel.stream() 方法
中文 token 分割奇怪Ollama 对中文流式支持良好,Qwen 表现优秀
高并发下内存溢出限制最大并发;使用连接池管理 LLM 实例
需要中断生成?目前 LangChain 不支持“取消流式”,需自行管理超时
安全性流式接口同样需做输入校验 + 敏感词过滤(结合 Day 14 Callback)

💡 生产建议

  • 使用 uvicorn + gunicorn 部署
  • 添加请求限流(slowapi
  • 日志记录完整会话(非仅流)

📦 七、配套代码结构

langchain-30-days/
└── day15/
    ├── streaming_console.py      # 控制台流式输出
    └── fastapi_streaming_api.py  # FastAPI 流式聊天接口

📝 八、今日小结

  • ✅ 理解了流式响应对用户体验的价值
  • ✅ 学会了用 astream() 实现 Token 级流式输出
  • ✅ 掌握了 AsyncCallbackHandler 监听流式事件
  • ✅ 构建了基于 FastAPI + SSE 的生产级流式接口
  • ✅ 知道了流式在高并发下的优化方向

🎯 明日预告:Day 16 —— 文档加载器(Document Loaders)大合集!PDF、Word、网页、数据库一键读取!