mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3
1671 字
5 分钟
AI Agent 实践 Demo:LangGraph × RAG × 流式对话的企业知识库问答
2026-09-02

这是我在学习企业级 AI Agent + RAG 时做的一个实践 Demo:上传文档构建个人/企业知识库,再用自然语言提问,Agent 会先判断是否需要检索知识库,最终以「流式打字机」的方式给出带完整推理过程的回答。本文主要记录它的技术栈、实现难点与心得

🧱 项目概览#

Demo 采用前后端分离的 Monorepo 结构,两个应用独立运行:

  • Enterprise-server/ — Python 3.12 + FastAPI 后端:LangChain/LangGraph Agent 负责对话编排,RAG 检索落到 Milvus,Embedding 由本地 Ollama 提供
  • Enterprise-web/ — Vue 3 + Vite + TypeScript 前端:聊天页 + 知识库上传页,通过 SSE 实时渲染流式回答

核心数据流如下:

用户提问
│ fetch POST /chat/stream(SSE)
FastAPI 路由 ──► LangGraph Agent(DeepSeek)
│ │ 判断是否调用工具
│ ▼
│ RagQuery 工具 ──► 问题向量化(Ollama nomic-embed-text)
│ ▼
│ Milvus 相似度检索(top_k 片段)
│ │
◄── SSE 事件流 start / reasoning / tool_call / text / complete / done
Vue3 前端逐段渲染(markstream)+ 思考过程面板 + 工具调用标签

🛠️ 技术栈#

后端(Enterprise-server)#

分类选型说明
语言 / Web 框架Python 3.12 + FastAPI + uvicorn异步路由,StreamingResponse 输出 text/event-stream
Agent 编排LangChain 1.2 + LangGraph 1.1create_agent 构建带工具 + 中文系统提示词的 Agent;stream_events(..., version="v3") 拿流式事件
大模型DeepSeek(deepseek-v4-flash通过 init_chat_model(model_provider="deepseek") 接入
EmbeddingOllama 本地服务 nomic-embed-text768 维向量,本地跑、免费、不依赖外网
向量数据库Milvus(pymilvus + langchain-milvus集合 KnowledgeBase,COSINE 相似度、string 主键、dimension=768
文档处理LangChain Loaders(Text / PyPDF / Docx2txt / Markdown)+ RecursiveCharacterTextSplitter按扩展名自动选 loader,中文标点感知的分块

前端(Enterprise-web)#

分类选型说明
框架Vue 3.5 + Vite + TypeScript<script setup lang="ts">vue-tsc 类型检查
状态管理Piniachatstore(对话流)/ filestore(文件生命周期)
流式渲染markstream-vueMarkdown「打字机」式逐段输出
SSE 解析fetch + ReadableStream + TextDecoder手动按 \n\n 切帧解析 data: JSON
代理Vite dev-proxy/chat/file 代理到 :8000 后端

文档摄入管线#

前端拖拽上传(支持 .pdf / .docx / .md)→ 后端落盘 → 按扩展名选择 Loader 加载 → 中文分块 → 向量化 → 逐 chunk upsert 到 Milvus(每行携带 content 文本与 source_file / chunk_index 元数据)。前端为每个文件维护 uploading → uploaded → vectorizing → vectorized 的完整生命周期状态。

🚧 遇到的困难#

1. 把 Agent 的「内部过程」翻译成前端可渲染的事件协议#

LangGraph stream_events 输出的是增量式的多类型事件:思考内容(reasoning)、工具调用(tool_call)、最终回复(text)交织出现,且同一个 tool_call 可能跨多条增量消息重复发送

解法:后端自定义一套 SSE 事件协议 start / reasoning / tool_call / text / complete / done,在生成器里统一归类转发;前端 applyEventtype 分支处理,并对 tool_call 按工具名去重,避免重复渲染。难点在于这套协议由两端共同维护——后端 generator 和前端 store 任何一边改动,另一边都要同步。

2. Vue 流式渲染的「响应式陷阱」#

把「正在流式生成」的 assistant 消息 push 进 store 时,如果只把它当成 ref 里的普通对象,闭包内逐字符地 content += delta不会触发视图更新的,markstream 也就不会动。

解法:必须用 reactive() 包裹该消息对象后再 push,让每次增量写入都经过代理,即时触发模板重渲染。这个细节代码注释里专门标了「关键」二字,属于踩过才懂的坑。

3. 中文文档切分不是「默认就能用」#

RecursiveCharacterTextSplitter 的默认分隔符是面向英文的,直接切中文容易在句子中间下刀,把语义切断,直接影响检索召回质量。

解法:自定义分隔符优先级列表,把中文标点放进去:\n\n → \n → 。→ !→ ?→ ;→ ,→ 空格,再配合 chunk_size=500chunk_overlap=75 反复调参。RAG 效果的天花板往往由切分质量决定,而不是模型。

4. 向量库维度与配置的一致性#

Milvus 集合创建时的 dimension 必须和 Embedding 模型输出维度严格一致(这里都是 768),metric_type=COSINEid_type=string。维度对不上时写入或检索会直接失败。

解法:把 MILVUS_DIMENSIONCHUNK_SIZECHUNK_OVERLAPTOP_K=6 等全部收敛到 config.py 单一配置源,换 Embedding 模型时必须同步核对维度。

5. 摄入链路长、失败点分散#

上传 → 校验扩展名 → 落盘 → 选 Loader → 切分 → Embedding → 逐块 upsert,是一条很长的链路,还涉及路径遍历防护、文件名冲突处理、文件不存在等边界情况。

解法:后端统一返回 { code, message, data } 信封,前端以 code === 200 判定成功,据此维护每个文件的生命周期与错误提示,把「哪一步挂了」清晰暴露给用户。

6. 返回结果不正确/找不到#

如果用户在系统输入的内容过于模糊,过少等情感,检索返回的答案会出现不匹配、找不到的情况,这时就需要通过增加tok_k参数以以提高命中率。后续考虑使用更加细致的langgraph节点编排提高系统的可用性

💡 心得#

  1. Agent 的关键不是「调 API」,而是编排与工具设计。 一个 @tool(RagQuery)+ create_agent + 一句系统提示词就能成型;真正有门槛的是把 Agent 的推理链路设计得清晰、可解释。

  2. 流式的本质是「事件协议」而非 HTTP 传输。 只要把事件 type 定义清楚、前端增量拼接足够,就能把后端生成器与前端 UI 彻底解耦。但这要求协议双端维护、最好有注释或文档作为。

  3. 让思考过程可见是很好的 Agent 产品体验。 可折叠的「推理过程」面板 + 工具调用标签,让用户能看懂模型「为什么这么答」,既是调试手段,也让 Demo 更有说服力、更适合现场演示。

  4. 原型阶段优先用「本地 + 低成本」组件。 Ollama 本地免费做 Embedding、DeepSeek API 成本低、Milvus 起在 localhost 就能把链路跑通;等链路验证完,再把向量库 / Embedding 按需迁到云端或集群。

  5. Monorepo 里后端生成器与前端 store 是天然的一对。 聊到 SSE,后端是「生成方」、前端是「消费方」,两边的状态机(streaming / complete / error)最好保持一致语义,否则会出现「后端已 done、前端还卡在 buffering」之类的错位。


有问题? 欢迎在评论区交流讨论!

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

AI Agent 实践 Demo:LangGraph × RAG × 流式对话的企业知识库问答
https://basyc.cloud/posts/ai-agent-rag-demo/
作者
北汐-Basyc
发布于
2026-09-02
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录