从零搭建多文档 RAG 问答系统:LangChain + 通义千问全链路实践
前言
最近我在实践性的学习AI 工程能力,花了一周时间从零搭建了一个多文档 RAG(检索增强生成)智能问答系统。它支持上传 PDF、Word、TXT 文档,自动构建向量库,用户可以用自然语言提问,系统会基于文档内容流式回答,并标注引用来源。
这篇文章记录了我的完整实现过程、遇到的关键问题和分块策略的实验结论。项目代码已开源,文末有 GitHub 链接和演示 GIF。
项目概述
项目名称:MultiDoc-RAG
一句话介绍:用 LangChain + ChromaDB + 通义千问,从零搭建一个支持多格式文档上传、语义检索、流式对话的完整 RAG 系统,带 Web 界面。
技术栈:
- 编排框架:LangChain(LCEL)
- 大模型:通义千问(qwen-turbo),通过 OpenAI 兼容接口调用
- Embedding 模型:BGE 中文小模型(bge-small-zh-v1.5),本地运行
- 向量数据库:ChromaDB(持久化存储)
- 前端:Streamlit
为什么做这个项目
RAG 是当前大模型应用中最成熟的技术范式之一。虽然网上有很多 RAG 教程,但大多数要么停留在"调一个 API 就结束了",要么直接用高度封装好的脚手架,很难看清全貌。
我的目标是:把 RAG 的全链路自己走一遍,从文档加载、文本分块、向量化存储、检索到对话生成,每个环节都亲自实现,过程中遇到的所有坑都记录下来。
系统架构
整个系统的数据流如下:
用户上传文档 → 多格式加载(PDF/Word/TXT)
→ 文本分块(chunk_size=800, overlap=50)
→ 向量化(BGE 本地模型)
→ 存入 ChromaDB(持久化)
→ 用户提问 → 语义检索 Top-K 相关块
→ 拼接提示词 → 通义千问生成回答 → 流式输出 + 引用来源
关键实现细节
1. 多格式文档加载
我用 LangChain 的 Document Loader 封装了一个统一入口函数 load_document(),根据文件后缀自动选择 PyPDFLoader、Docx2txtLoader 或 TextLoader。这样上层调用者不需要关心具体文件类型。
2. 文本分块策略实验
文本分块是 RAG 系统中最容易被忽视的关键环节。我用 RecursiveCharacterTextSplitter 做了三组分块实验(chunk_size=500/800/1000),用同一个问题"他在哪些公司工作过?"测试检索效果。
实验发现:
- chunk_size=500 时,公司名和职责被切到不同块,导致检索遗漏
- chunk_size=800 恰好让完整的"公司+职位+职责"段落保持在一起,检索相关性最佳
- chunk_size=1000 时噪声增多,相关性下降
最终我将默认 chunk_size 定为 800,并保留参数可调。
3. Embedding 的选型波折
我最初计划调用通义千问的 Embedding API,但实际接入时遇到了兼容接口格式不匹配的问题,LangChain 的 OpenAIEmbeddings 把参数包装成了千问不认识的格式。为了不卡进度,我果断切换备选方案——用本地模型 sentence-transformers 的 BGE 中文小模型,零费用、零网络依赖,向量化一条文本只需 0.1 秒。
4. 流式输出与对话记忆
通过 LangChain 的 RunnableWithMessageHistory,我实现了多轮对话记忆。系统会记住用户之前的提问,当追问"他在这些公司担任什么职位?"时,能正确理解"他"指代的是前文提到的某人。
流式输出让回答像打字机一样逐字显示,用户体验比干等几秒后一次性返回好很多。
5. 引用溯源
我要求模型在回答末尾列出"参考来源",标注出具体的文档块编号和来源文件。这让回答有据可查,也方便用户二次验证。
我踩过的坑
- API 地址写错:通义千问的兼容接口地址是
/compatible-mode/v1,我最初写成了/completions,导致调用成功但无返回。事后才意识到调试时应该先加 API Key 检查。 - LangChain 拆包问题:LangChain 把 text_splitter、HuggingFace、Chroma 都拆分成了独立包,开发过程中先后遇到了
ModuleNotFoundError: langchain.text_splitter、langchain_huggingface、langchain_chroma,每次都要补装依赖。 - Streamlit 与检索器的生命周期不一致:Streamlit 热重载时,向量库对象和检索器对象可能不同步,导致用户提问时报
Collection not found。解决方案是把检索器改成延迟加载,每次调用时动态获取最新向量库。
成果展示
- 🖥️ Web 界面:左侧上传文档,右侧流式对话,支持多轮记忆和引用溯源
-
🎬 演示 GIF:https://github.com/Chris2ai/multidoc-rag/blob/main/demo.gif?raw=true
- 📂 GitHub 仓库:
https://github.com/Chris2ai/multidoc-rag
后续优化方向
- 支持扫描型 PDF(OCR 识别)
- 云端部署(Streamlit Cloud / Hugging Face Spaces)
- 引入重排序模型(Rerank)提升检索精度
- 增加 Embedding 模型和 chunk_size 的前端可调面板
写在最后
这一周从第一行 pip install 到一个完整的 RAG 系统,中间踩了十几个坑,但每一次排查和解决都让我对 RAG 全链路有了更深的理解。如果你也在自学 AI 工程,强烈建议亲手做一遍——过程虽繁琐,收获却不小。
欢迎 Star ⭐ 我的项目,一起交流!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)