rag-stream-intermediate-events-tutorial
通过 LlamaIndex Instrumentation + Server-Sent Events,将 RAG 管道中间步骤实时推送到前端,解决 AI 问答的"死屏"体验问题
加载项目详情…
本应用为开源项目,仅供学习研究,请遵守其开源协议。
通过 LlamaIndex Instrumentation + Server-Sent Events,将 RAG 管道中间步骤实时推送到前端,解决 AI 问答的"死屏"体验问题
加载项目详情…
本应用为开源项目,仅供学习研究,请遵守其开源协议。
想象这样一个场景:你在一个 AI 问答网站上提问,系统却像死机了一样——光标在输入框里转了整整 15 秒,界面没有任何反应,然后答案突然一下子全部跳出来。这种体验糟透了,用户完全不知道系统到底在做什么:是还在思考?正在检索文档?还是已经卡死了?
GitHub 项目 rag-stream-intermediate-events-tutorial 正是为解决这一问题而生的。这是一份详细的教程,作者 Rohan 展示了如何在 RAG 管道中,将 LlamaIndex 的中间处理步骤——文档检索、Embedding 生成、LLM token 生成——实时推送到前端界面,让用户从"黑盒等待"变为"透明可见"。LlamaIndex 官方甚至在 LinkedIn 官方账号上专门推荐了这个项目,称其为"见过的最完整的 RAG 流式事件教程"。
作者 Rohan 的 GitHub 头像
一个典型的 RAG 问答流程包含多个串行阶段:用户提问 → 检索相关文档块(Retrieval)→ 将上下文送入 LLM 生成答案(Generation)。在每个阶段内部还有更细粒度的操作,比如 Embedding 模型对查询进行向量化、从向量数据库中寻找 Top-K 相似块等。
传统实现方式是将整个流程同步执行,后端等待所有步骤完成后再一次性返回完整答案。对于一个包含 10 个检索块的 RAG 查询,仅文档检索阶段就可能耗时 2-3 秒,LLM 生成阶段再耗时 5-10 秒。这意味着用户至少要面对 7-13 秒的"死屏"——既不知道系统在检索,也不知道系统在生成,体验极其割裂。
问题的根源在于:大多数 RAG 实现只做了LLM token 流(streaming),即 LLM 每生成一个 token 就实时推送一个。但这只是最后一公里——用户仍然不知道在此之前,系统已经在后端完成了多么繁重的检索工作。
LlamaIndex 从 0.10.x 版本开始引入了一个强大的调试观测框架——Instrumentation 模块。它的设计理念类似 Web 前端的 EventEmitter(事件发射器):在 RAG 管道的关键节点上"埋点",每当特定事件发生时,自动通知所有已注册的事件处理器。
这套架构的精髓在于 get_dispatcher() 函数——LlamaIndex 内部维护一个全局事件调度器,开发者可以向其注册自定义事件处理器 BaseEventHandler。当管道运行时,以下事件会被自动触发:
RetrievalStartEvent:开始从向量数据库检索相关节点EmbeddingEndEvent:Embedding 模型处理完查询向量RetrievalEndEvent:检索阶段结束,返回 Top-K 结果StreamChatDeltaReceivedEvent:LLM 流式输出每个 tokenStreamChatEndEvent:LLM 生成完成LLMChatEndEvent:聊天引擎会话结束项目中定义了一个 CustomEventHandler,它继承自 BaseEventHandler,通过 handle() 方法识别事件类型,构造 EventToSend 消息并推入 Python 队列:
class CustomEventHandler(BaseEventHandler):
def handle(self, event: BaseEvent) -> None:
if isinstance(event, RetrievalStartEvent):
event_q.put(EventToSend(
message="Retrieving relevant nodes..."
))
elif isinstance(event, EmbeddingEndEvent):
event_q.put(EventToSend(
status="done",
message=f"Done embedding {len(event.chunks)} query chunks."
))
# ... 更多事件类型
后端 FastAPI 使用 Python 标准库中的 Queue 作为生产者-消费者队列。LlamaIndex Instrumentation 自动触发的事件被写入队列,后端轮询队列内容,通过 FastAPI 的 StreamingResponse 以 Server-Sent Events(SSE) 协议推送至客户端:
async def event_generator():
while True:
if not event_q.empty():
event = event_q.get()
yield f"data: {json.dumps(event)}\n\n"
await asyncio.sleep(0.01)
return StreamingResponse(event_generator(), media_type="text/event-stream")
前端则利用 Vercel AI SDK(ai 包)的 useChat Hook,接收 SSE 流并将中间事件嵌入到消息 UI 中。insertDataIntoMessages 函数负责将原始消息数组与带外事件数据合并,最终渲染出带有进度状态栏的聊天界面——用户可以清晰地看到"正在检索文档" → "完成检索,找到 5 个相关块" → "AI 正在生成答案" → 答案流式输出。
本项目采用前后端完全分离的架构:
| 层级 | 技术选型 | 说明 |
|---|---|---|
| 后端 | FastAPI + Uvicorn | Python 异步 Web 框架,处理 SSE 流 |
| AI 框架 | LlamaIndex 0.10.x | 文档索引、检索、聊天引擎 |
| LLM/Embedding | OpenAI (GPT-3.5/GPT-4) | 通过 OpenAI API 调用,需配置 KEY |
| 前端 | Next.js 14 (App Router) | React 全栈框架 |
| 流式通信 | Vercel AI SDK 2.2.x | 封装 SSE/WebSocket 客户端 |
| 样式 | Tailwind CSS + Radix UI | 原子化 CSS + 无头组件库 |
| 包管理 | Poetry (后端) + npm (前端) | 各自独立管理依赖 |
| 文档格式 | docx2txt | 将 .docx/.doc 文件转为纯文本供索引 |
值得注意的是,项目前端使用了 create-llama 脚手架工具(LlamaIndex 官方提供的 Next.js 集成方案),这意味着整个模板已经内置了 LlamaIndex TypeScript SDK(llamaindex)的集成逻辑,包括流式聊天的基础设施。
部署本项目需要两台"服务器"——后端(端口 8000)和前端(端口 3000)各自独立运行。
第一步:后端配置
cd backend
cp .env.example .env # 复制环境变量模板
# 编辑 .env,填入 OPENAI_API_KEY
poetry install # 安装 Python 依赖
poetry run python app/engine/generate.py # 生成向量索引(首次运行)
poetry run python main.py # 启动后端服务
第二步:前端配置
cd frontend
cp .env.example .env
npm install
npm run dev
访问 http://localhost:3000 即可体验带中间事件流的前端界面。初次使用时,后端会自动将 backend/data/101.pdf(一个教学 PDF)向量化并存储在 backend/storage/ 目录下,这个过程约需 10-20 秒。
这个项目并非没有缺点,在生产使用时需要注意以下问题:
1. 缺乏容器化支持:项目没有提供 Dockerfile 或 docker-compose.yml,前后端部署完全依赖手动安装 Poetry 和 Node.js 环境。在服务器上复现时,环境配置的复杂度不低,尤其是 Python 3.11 版本约束较严格。
2. 仅支持 OpenAI:虽然 LlamaIndex 本身支持多种 LLM 和 Embedding 提供商(Anthropic、Azure、HuggingFace、本地模型等),但本教程仅演示了 OpenAI 配置。若要接入其他模型,需要自行修改 backend/app/settings.py 中的配置逻辑。
3. SSE vs WebSocket 的取舍:项目选择了 SSE 而非 WebSocket,这在大多数场景下是合理的(SSE 更轻量,由 HTTP 驱动,天然支持重试),但 SSE 是单向通道,无法从客户端向服务端推送控制命令(如取消正在进行的 RAG 请求)。如果需要真正的双向通信,需要改用 WebSocket。
4. 中间事件的格式约定:前后端通过一个硬编码的 EventToSend 消息格式通信,这在教程范围内没有问题,但如果要将这套模式泛化到其他 RAG 管道,势必要定义一套更通用的跨端事件协议。
本项目在 GitHub 上获得了 197 颗星(Stars),24 次 Fork。LlamaIndex 官方在 LinkedIn 上对其进行专题推荐,表明该项目在社区中具有一定的标杆意义——它代表了 RAG 开发从"能跑就行"到"体验精细化"的演进方向。
从技术趋势看,流式中间事件正在成为 AI 应用开发的新标准。OpenAI 的 Assistants API 已支持类似的事件流机制,Anthropic 的 SDK 也在跟进。随着用户对 AI 应用"感知延迟"的要求越来越高,会有更多开发者需要掌握如何在 RAG 管道中精细化地暴露内部状态——这份教程恰好填补了这一技术空白。
作者 Rohan 还提供了另一个相关教程 llamaindex-workflow-streaming-tutorial,展示了在 LlamaIndex Workflows(事件驱动架构)中如何实现流式事件,进一步拓展了这一技术的应用边界。