基于 LangChain + Chroma + DeepSeek 的检索增强生成(RAG)应用 —— 纯 Python 实现,开箱即用。
痛点
说明
LLM 幻觉
大模型会"编造"不存在的事实,在专业场景下尤其危险
知识时效性
通用大模型的知识截止日期固定,无法获取最新信息
领域知识不足
通用模型在垂直领域(如企业内部文档、产品手册)表现不佳
回答不可溯源
传统 LLM 回答没有引用来源,无法验证真伪
RAG(Retrieval-Augmented Generation) 在 LLM 生成回答之前,先从本地知识库中检索相关文档片段,再将文档片段作为"参考材料"一起发给 LLM,确保回答有据可依、可溯源 。
传统 LLM: 用户问题 ──→ LLM ──→ 回答(可能幻觉)
RAG: 用户问题 ──→ 检索相关文档 ──→ LLM + 文档 ──→ 回答 + 引用来源 ✅
本项目的定位是 RAG 的最小可行产品(MVP) :用最简洁的代码(单文件 ~900 行)展示 RAG 的完整链路,同时具备生产可用的 UI 交互体验。
🔍 语义检索 — 基于 BAAI/bge-small-zh-v1.5 中文嵌入模型,精准匹配文档语义
🤖 DeepSeek 驱动 — 接入 DeepSeek API,中文理解力强、性价比极高
💾 本地向量库 — Chroma 本地持久化,无需外部数据库,数据完全私有
🎨 类 DeepSeek 风格 UI — 初始居中搜索框,对话式交互,侧边栏管理
💬 多对话管理 — 支持多轮对话、对话持久化、重命名、删除
📎 可溯源回答 — 每次回答附带参考来源,可对照验证
📂 文档管理 — 侧边栏支持上传、预览、删除文件,支持 .txt / .md / .csv / .json / .docx / .pdf
⚡ 离线嵌入 — 嵌入模型完全本地运行(预下载后),零 API 费用、零延迟
🔑 API Key 热配置 — 侧边栏底部直接修改 API Key,无需重启
┌──────────────────────────────────────────────────────────────────────┐
│ Streamlit 前端层 │
│ │
│ ┌──────────────────────┐ ┌──────────────────────────┐ │
│ │ 侧边栏 (管理) │ │ 主区域 (对话) │ │
│ │ │ │ │ │
│ │ • API Key 配置 │ │ • 居中搜索框(初始态) │ │
│ │ • 知识库构建/加载 │ │ • 聊天气泡(对话态) │ │
│ │ • 文档上传/管理 │ │ • 参考来源折叠面板 │ │
│ │ • 多对话切换 │ │ • 流式思考提示 │ │
│ │ • 文件预览 │ │ │ │
│ └──────────────────────┘ └──────────────────────────┘ │
│ │
└────────────────────────────────┬─────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ LangChain 编排层 │
│ │
│ ┌──────────────┐ ┌───────────────┐ ┌──────────────────────┐ │
│ │ Document │ │ Text │ │ RAG Chain (LCEL) │ │
│ │ Loader │──▶│ Splitter │ │ │ │
│ │ 文档加载器 │ │ 文本分割器 │ │ retriever │ │
│ └──────────────┘ └───────────────┘ │ │ format_docs │ │
│ │ ▼ │ │
│ ┌──────────────────────────────┐ │ prompt_template │ │
│ │ LangChain Hub / Callbacks │ │ │ │ │
│ │ (可扩展监控、缓存、日志) │ │ ▼ │ │
│ └──────────────────────────────┘ │ LLM (ChatOpenAI) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ StrOutputParser() │ │
│ └──────────────────────┘ │
│ │
└───────┬──────────────────────────────────┬───────────────────────────┘
│ │
▼ ▼
┌───────────────────────┐ ┌──────────────────────────────────┐
│ 检索层 (Retrieval) │ │ 生成层 (Generation) │
│ │ │ │
│ ┌─────────────────┐ │ │ ┌────────────────────────────┐ │
│ │ Chroma │ │ │ │ DeepSeek API │ │
│ │ 向量数据库 │ │ │ │ model: deepseek-chat │ │
│ │ (本地持久化) │ │ │ │ base_url: api.deepseek.com│ │
│ └────────┬────────┘ │ │ │ temperature: 0.3 │ │
│ │ │ │ │ max_tokens: 1024 │ │
│ ┌────────▼────────┐ │ │ └────────────────────────────┘ │
│ │ HuggingFace │ │ │ │
│ │ Embeddings │ │ └──────────────────────────────────┘
│ │ bge-small-zh │ │
│ │ 512 维向量 │ │
│ │ (本地 CPU 推理) │ │
│ └─────────────────┘ │
│ │
└───────────────────────┘
data/*.txt (原始文档)
│
▼
┌──────────────────────────────┐
│ ① TextLoader │ 读取 UTF-8 文本文件
│ 生成 Document 对象 │ (page_content + metadata)
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ ② RecursiveCharacterTextSplitter │
│ chunk_size=500 │ 按中文标点递归切分
│ chunk_overlap=50 │ 保留上下文连贯性
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ ③ HuggingFaceEmbeddings │
│ BAAI/bge-small-zh-v1.5 │ 文本 → 512 维归一化向量
│ device=cpu, local only │
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ ④ Chroma.from_documents() │
│ persist_directory= │ 向量 + 原文持久化到磁盘
│ chroma_db/ │
└──────────────────────────────┘
用户问题 "什么是RAG?"
│
▼
┌──────────────────────────────┐
│ ① 问题向量化 │ 同一嵌入模型 → 512 维向量
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ ② ANN 相似度检索 (Chroma) │ 余弦相似度 Top-4 文档块
│ retriever.invoke(q) │
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ ③ 格式化上下文 │ [来源 1]\n内容\n---\n[来源 2]...
│ format_docs(docs) │
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ ④ 组装 Prompt │ System Prompt (角色+规则+上下文)
│ ChatPromptTemplate │ + Human Prompt (用户问题)
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ ⑤ LLM 生成 (DeepSeek) │ temperature=0.3 确定性生成
│ ChatOpenAI.invoke() │
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐
│ ⑥ 结果展示 + 来源追溯 │ 回答气泡 + 📎 参考来源面板
└──────────────────────────────┘
层级
技术
版本
选型理由
UI 框架
Streamlit
1.x
纯 Python 写 Web UI,零前端代码,适合快速原型
编排框架
LangChain
latest
标准化 RAG 工作流,LCEL 链式语法简洁可读
文档加载
LangChain Community
—
TextLoader 开箱即用
文本分割
LangChain Text Splitters
—
RecursiveCharacterTextSplitter 按中文标点递归切分
向量数据库
Chroma
latest
开源、轻量、本地文件持久化,零配置
嵌入模型
BAAI/bge-small-zh-v1.5
v1.5
中文优化、512维轻量、本地运行、免费
LLM
DeepSeek (deepseek-chat)
—
中文能力强、API 性价比极高、OpenAI 兼容协议
LLM 适配
LangChain OpenAI
—
ChatOpenAI 直接适配 DeepSeek(OpenAI 兼容)
模型下载
ModelScope
—
国内 CDN,解决 HuggingFace 下载慢的问题
环境管理
python-dotenv
—
.env 管理 API Key,不入版本控制
嵌入模型:BAAI/bge-small-zh-v1.5 vs 其他
模型
维度
大小
中文效果
运行方式
费用
bge-small-zh-v1.5 ✅
512
~100MB
⭐⭐⭐⭐
本地 CPU
免费
bge-large-zh-v1.5
1024
~400MB
⭐⭐⭐⭐⭐
本地 CPU/GPU
免费
text-embedding-ada-002 (OpenAI)
1536
—
⭐⭐⭐
API 调用
付费
m3e-base
768
~400MB
⭐⭐⭐⭐
本地 CPU
免费
选择 small 而非 large :MVP 阶段追求快速启动,100MB 模型 10 秒内下载完成,CPU 推理速度足够。
数据库
部署方式
持久化
适用场景
选型理由
Chroma ✅
嵌入式
本地文件
原型/小规模
零配置,pip install 即用
FAISS
嵌入式
手动
原型/大规模
纯向量检索,无元数据管理
Milvus
Docker/K8s
服务端
生产/海量
功能强大但部署复杂
Pinecone
SaaS
云端
生产/无运维
需注册、付费、网络依赖
选择 Chroma :项目定位是本地 MVP,Chroma 的 persist_directory 一行代码搞定持久化,完全满足需求。
模型
中文能力
价格 (1M tokens)
协议兼容
选型理由
DeepSeek (deepseek-chat) ✅
⭐⭐⭐⭐⭐
¥1 (输入) / ¥2 (输出)
OpenAI 兼容
性价比之王
GPT-4o
⭐⭐⭐⭐
$2.5 / $10
OpenAI 原生
最强但贵
Qwen-Max
⭐⭐⭐⭐⭐
¥2 / ¥6
OpenAI 兼容
中文最强,成本可控
GLM-4
⭐⭐⭐⭐
¥0.1 / ¥0.1
自有协议
超低价但协议不兼容
选择 DeepSeek :中文理解力一流,API 价格是 GPT-4o 的 1/50,协议完全兼容 OpenAI SDK,一行代码切换。
本项目的演进经历了多个版本迭代,以下是核心优化点:
版本
方式
首启时间
问题
v1
HuggingFace 在线下载
15+ 分钟 (国内超时)
严重卡点,新手劝退
v2
ModelScope 预下载 + 环境变量 local_files_only=True
< 2 秒
✅ 一次下载,永久使用
优化效果 :首启时间从 900+ 秒 → 2 秒 ,快了 450 倍 。
# v1: 每次启动尝试从 HuggingFace 下载(国内极慢)
embeddings = HuggingFaceEmbeddings (model_name = "BAAI/bge-small-zh-v1.5" )
# v2: 设置国内镜像 + 仅用本地文件
os .environ ["HF_ENDPOINT" ] = "https://hf-mirror.com"
# modelscope 预下载到 ./models/
embeddings = HuggingFaceEmbeddings (
model_name = "./models/.../BAAI--bge-small-zh-v1.5/.../master" ,
model_kwargs = {"device" : "cpu" , "local_files_only" : True },
)
2. 界面体验:从基础 Streamlit → DeepSeek 风格 UI
版本
UI 特点
问题
v1
Streamlit 默认布局,标题+侧边栏
平淡,不像对话应用
v2
自定义 CSS ~300 行
DeepSeek 风格居中搜索框、现代侧边栏
优化效果 :初始态仿 DeepSeek 居中搜索,有对话后自动切换为标准对话布局,用户体验显著提升。
/* 核心技巧:用 body:has(.initial-mode) 隐藏默认 chat_input */
body : has (.initial-mode ) .stChatInput {
display : none !important ;
}
版本
方式
问题
v1
st.session_state.chat_history 列表
内存存储,刷新即丢
v2
conversations.json 文件持久化 + 多对话切换
✅ 刷新不丢,支持重命名/删除
优化效果 :从 刷新丢失 → 永久保存 ,支持多主题对话隔离。
版本
System Prompt
效果
v1
简单指令:"请基于上下文回答"
AI 可能因上下文不足而乱答
v2
分级指令:足够→准确回答 / 不足→诚实告知并标注来源
✅ 区分知识库内容 vs AI 补充
# v2 System Prompt 核心设计
1. 上下文足够 → 基于上下文准确完整回答
2. 上下文不足 → 诚实告知 + 标注哪些来自知识库、哪些来自你的知识
3. 使用中文,条理清晰
4. 适当引用上下文具体内容
版本
支持格式
限制
v1
仅 .txt
需手动转换格式
v2
.txt .md .csv .json .doc .docx .pdf
✅ 6 种格式覆盖常见文档
版本
方式
问题
v1
每次构建/加载都 HuggingFaceEmbeddings() 新建
重复加载模型,浪费内存和时间
v2
st.session_state.embeddings 单例缓存
✅ 模型只加载一次
# v2: 复用嵌入模型实例
if st .session_state .embeddings is None :
st .session_state .embeddings = HuggingFaceEmbeddings (...)
embeddings = st .session_state .embeddings # 复用
版本
方式
问题
v1
每次启动需手动点"加载"
多余操作
v2
启动时检测 chroma_db/ 存在则自动加载
✅ 如已有向量库,启动即就绪
维度
优化前 (v1)
优化后 (v2/v3)
提升
首次启动时间
15+ 分钟
< 3 秒
300x+
嵌入模型加载
每次请求 HuggingFace
本地离线 + session cache
∞
对话持久化
❌ 刷新丢失
✅ JSON 文件持久化
—
多对话支持
❌ 单会话
✅ 多对话 + 重命名 + 删除
—
UI 体验
默认 Streamlit
DeepSeek 风格居中搜索
—
文档格式
仅 .txt
6 种格式 (.txt .md .csv .json .doc .docx .pdf)
6x
文档管理
手动放文件
侧边栏上传/预览/删除
—
Prompt 鲁棒性
可能乱答
分级指令,诚实标注
—
错误提示
技术报错
用户友好提示
—
启动恢复
手动加载
自动检测恢复
—
RAG Demo(LangChain + Chroma +API)/
├── README.md # 本文档
└── my_local_rag/
├── app.py # 主程序(单文件 ~900 行)
├── requirements.txt # Python 依赖清单
├── .env # 环境变量(API Key + HF 镜像)
├── conversations.json # 对话持久化文件
├── .streamlit/
│ └── config.toml # Streamlit 配置(无统计、无头模式)
├── data/
│ ├── .gitkeep # 保持目录存在
│ └── my_knowledge.txt # 示例知识库文档
├── chroma_db/ # 向量数据库(构建后自动生成)
│ ├── chroma.sqlite3 # 元数据/索引
│ └── <collection-id>/ # 向量二进制数据
├── models/
│ └── models/
│ └── BAAI--bge-small-zh-v1.5/ # 嵌入模型缓存
│ └── snapshots/master/ # 模型权重文件
├── 启动指南.md # 环境搭建与启动方法
├── 使用文档.md # 操作步骤说明
└── 说明文档.md # 技术架构详解
git clone https://github.com/LoongzF/RAG-Demo-LangChain-Chroma-API-.git
cd RAG-Demo-LangChain-Chroma-API-/my_local_rag
pip install -r requirements.txt
编辑 .env 文件:
DEEPSEEK_API_KEY=sk-your-api-key-here
HF_ENDPOINT=https://hf-mirror.com
嵌入模型约 100MB,从 HuggingFace 直接下载在国内会极慢。通过 ModelScope 国内 CDN 10 秒内完成。
pip install modelscope
python -c "
from modelscope import snapshot_download
snapshot_download('BAAI/bge-small-zh-v1.5', cache_dir='./models')
"
浏览器自动打开 http://localhost:8501
左侧边栏确认 API Key 已自动填充
点击 🔨 构建 按钮
等待 2 秒,看到 "✅ 构建完成"
在居中搜索框输入问题,如 "什么是 RAG?"
💡 之后启动会自动检测 chroma_db/ 并恢复知识库,无需重复构建。
┌──────────────────────┬──────────────────────────────────┐
│ 侧边栏 (280px) │ 主区域 │
│ │ │
│ 📚 品牌标识 │ 初始态:居中标题 + 居中搜索框 │
│ ➕ 新对话 │ 对话态:聊天气泡 + 底部输入框 │
│ 历史对话列表 │ │
│ ───────────── │ ┌────────────────────────────┐ │
│ 知识库状态 / 构建 │ │ 回答内容 │ │
│ 文档导入/删除 │ │ 📎 参考来源 (可折叠) │ │
│ 文档列表/预览 │ └────────────────────────────┘ │
│ ───────────── │ │
│ ⚙️ API 设置 │ │
└──────────────────────┴──────────────────────────────────┘
操作
位置
说明
新建对话
侧边栏 ➕ 新对话
创建独立对话,互不干扰
切换/重命名/删除对话
侧边栏历史对话列表
点击切换,r 重命名,x 删除
构建知识库
侧边栏 🔨 构建
读取 data/ 所有文件 → 向量化 → 存入 Chroma
加载知识库
侧边栏 📂 加载
从 chroma_db/ 直接加载已有向量库
导入文件
侧边栏 📥 导入
上传文件到 data/ 目录
删除文件
侧边栏 🗑️ 删除
从 data/ 删除文件
预览文件
侧边栏展开 📁 文档列表
点击文件名预览前 100 字符
配置 API Key
侧边栏底部 ⚙️ API 设置
实时生效,无需重启
提问
主区域输入框
Enter 发送
查看来源
回答下方 📎 参考来源
显示检索到的文档碎片
清空对话
底部 🗑️ 清空对话
仅清空消息,不影响知识库
将文档(.txt / .md / .csv / .json 等)放入 data/ 目录或通过侧边栏上传,然后点击 🔨 构建 重建知识库即可。
data/*.txt → TextLoader → RecursiveCharacterTextSplitter(chunk=500,overlap=50)
→ HuggingFaceEmbeddings(bge-small-zh, cpu) → 512维向量
→ Chroma.from_documents(persist=chroma_db/) → 持久化到磁盘
rag_chain = (
{
"context" : retriever | format_docs ,
"question" : RunnablePassthrough (),
}
| prompt_template
| llm
| StrOutputParser ()
)
response = rag_chain .invoke (user_question )
参数
默认值
说明
调优建议
chunk_size
500
文本块大小
增大 → 上下文更完整;减小 → 检索更精准
chunk_overlap
50
块间重叠
防止关键信息落在切分边界
k (检索数量)
4
返回文档块数
增多 → 信息更全但 Prompt 更长
temperature
0.3
LLM 随机性
RAG 建议 0~0.3,追求准确性
max_tokens
1024
回答长度上限
根据场景调整
separators
\n\n \n 。 ! ? ,
切分优先级
中文场景按标点切分效果更好
问题
原因
解决
构建知识库报错
API Key 未配置
侧边栏 ⚙️ API 设置 填入有效 Key
构建极慢 (>10分钟)
嵌入模型从 HuggingFace 下载
按上文第四步通过 ModelScope 预下载
问答返回错误
API Key 无效/余额不足/网络问题
检查 Key 有效性,确认能访问 api.deepseek.com
AI 回答与文档不一致
System Prompt 允许 AI 补充
查看 📎 参考来源 对照验证
知识库状态显示"未构建"
chroma_db/ 不存在或为空
点击 🔨 构建
添加文档后问答不生效
未重建向量库
点击 🔨 构建(不是加载)
方向
说明
涉及技术
📄 更多文档格式
PDF 直接解析
PyPDFLoader
💬 多轮对话记忆
结合历史上下文追问
ConversationBufferMemory / RunnableWithMessageHistory
🔀 混合检索
关键词 BM25 + 向量融合
EnsembleRetriever
🌊 流式输出
逐字符实时显示
llm.stream() + Streamlit write_stream()
📊 更大嵌入模型
提升检索精度
bge-large-zh-v1.5 (1024 维)
🗂️ 多知识库
不同场景独立知识库
多个 Chroma Collection
📝 对话导出
导出问答为 Markdown/CSV
Python 文件 IO
🔐 用户认证
多用户隔离
Streamlit Authenticator
🧠 重排序
对检索结果二次精排
CohereRerank / bge-reranker
📈 监控追踪
调试 RAG 链各环节
LangSmith / LangFuse
MIT License
🎉 祝你使用愉快! 如有问题或建议,欢迎提 Issue 或 PR。