RAG知识库问答系统实战:从零搭建并用Deepseek驱动
2026/9/8 10:40:05 网站建设 项目流程

在业务中接入大模型时,很多同学都会走到同一步:希望大模型能回答自己企业内部文档里的问题。但直接提问会发现,模型既不了解你的业务,也容易一本正经地编造答案。要解决这个问题,RAG 是目前最实用、成本最低的方案。本文将基于一套完整可运行的代码,带你从零搭建一个RAG知识库问答系统,并把 Deepseek 作为最终生成模型接入其中,从原理到落地,一次性讲透。

先说一下本文适合谁:对大模型感兴趣但还没系统接触过 RAG 的开发者、正在做企业知识库需求的后端工程师、想在自己电脑上跑通一套完整问答系统的学习者。读完本文,你会理解 RAG 的完整处理链路,掌握文档加载、文本切分、向量化、向量检索、Prompt 组装、大模型生成这几个关键环节,并拿到一套可以扩展成真实项目的基础源码。

1. RAG 到底是什么,为什么大模型需要它

1.1 从大模型的“知识缺陷”说起

大模型虽然能写文章、写代码、做翻译,但有一个天然缺陷:它的知识来自训练数据,存在明显的“知识截止时间”。如果你的问题涉及企业最新制度、某个产品的使用手册、某个系统的内部操作说明,大模型很可能会回答一个看似合理、实则错误的内容,这就是常说的“幻觉”问题。

举个例子,你问“公司请休假制度是什么”,模型可能会编造一套看似标准的制度,而它根本没有见过你公司的文档。此时你不可能为了这个问题重新训练一个大模型,成本太高,也不现实。

RAG 的解决思路很直接:与其让模型“背”下所有知识,不如在它回答问题之前,先帮它找到相关资料。这就好比考试时允许翻书,模型不用死记硬背,只需要根据“查到的那几页”来组织答案。

1.2 RAG 的完整定义与核心流程

RAG,全称 Retrieval-Augmented Generation,检索增强生成。它是一种将信息检索与文本生成相结合的架构:先从外部知识库中检索出与用户问题相关的文档片段,再将片段与问题一起交给大模型,让模型基于这些片段生成回答。

标准 RAG 的处理流程可以拆成八个环节:

  1. 文档加载:读取内部文档,支持 txt、PDF、Word、Markdown 等格式。
  2. 文本切分:长文档不能整体向量化,需要按一定策略切分成 chunk(文本块)。
  3. 向量化:使用 embedding 模型将每个 chunk 转换为向量,向量可以理解为一段文本的语义坐标。
  4. 向量存储:将向量和原文一起存入向量数据库,例如 Chroma、FAISS、Milvus。
  5. 问题向量化:用户提问时,将问题也转换为向量。
  6. 相似度检索:计算问题向量与知识库向量的相似度,返回最相关的 Top K 个片段。
  7. Prompt 组装:将系统提示词、检索到的片段、用户问题拼接成一个完整的 Prompt。
  8. 生成回答:将 Prompt 发送给大模型,模型基于片段内容生成最终答案。

1.3 RAG 与模型微调的区别

很多初学者会把 RAG 和微调搞混。简单来说,微调是修改模型的“记忆”,RAG 是给模型“查资料”。两者各有适用场景:

对比维度RAG微调
成本较低,无需训练显卡较高,需要训练资源和数据准备
知识更新替换文档即可,实时生效需要重新训练,周期长
可解释性回答可溯源到具体文档片段相对难以解释模型参考了什么
幻觉控制通过限定参考片段,效果明显降低有限,模型仍可能生成非训练知识
适用场景企业知识库、文档问答、实时信息改变模型语气风格、专业术语理解、特定格式输出

真实项目中,RAG 和微调也经常配合使用。如果模型本身对你所在行业的术语理解较弱,可以先用部分数据微调模型,再叠加 RAG 让模型获取最新知识。但对大多数知识库问答需求来说,RAG 是首选中低成本方案。

2. 系统方案设计与技术选型

2.1 方案目标

我们要搭建的这套系统需要满足以下需求:

  • 支持把本地文档导入知识库。
  • 文档必须支持持续追加和更新,不需要重训练模型。
  • 用户提问后,系统能检索到相关文档片段,并在回答中给出依据。
  • 最终生成模型使用 Deepseek API,保证中文效果且调用简单。
  • 整个系统可以本地运行,不依赖大型 GPU 环境。

2.2 总体架构

根据需求,系统的整体架构可以这样设计:

用户提问(Web界面 / 命令行) ↓ 中文问题向量化(本地Embedding模型) ↓ Chroma向量库相似度检索 ↓ 返回 Top-K 相关文档片段 ↓ 组装 Prompt:系统提示词 + 参考片段 + 用户问题 ↓ 调用 Deepseek Chat API ↓ 生成最终回答并返回给用户

在这个架构中,文档离线处理链路把原始知识库转换为向量索引,在线问答链路负责检索与生成。两条链路合起来就是一套完整的 RAG 系统。

2.3 技术栈说明

本方案涉及的主要技术组件如下:

模块技术选型选择原因
文档加载Python 内置文件读取轻量,零依赖,适合 txt 文档入门
文本切分LangChain Text Splitters成熟的切分策略,支持重叠窗口
向量化模型sentence-transformers + bge-small-zh-v1.5中文效果好,本地运行,免费
向量数据库Chroma轻量级,无需独立服务,适合快速开发
生成模型Deepseek Chat API中文能力强,兼容 OpenAI 协议,接入成本低
Web 展示层Streamlit用 Python 快速搭建交互界面

为什么使用 Deepseek 作为生成模型,这是很多读者关心的问题。Deepseek 的 API 调用方式与 OpenAI 协议兼容,只需要安装 openai SDK 并修改 base_url 和 api_key 就能完成接入,非常省事。同时,Deepseek 在中文理解与生成上的表现足够优秀,适合中文知识库问答场景。相比本地部署一个十几B或几十B的大模型,API 方式对电脑性能要求极低,个人开发者和中小规模应用都能低成本使用。

为什么 embedding 模型选用本地部署,原因也很实际。如果每次写文档和提问都调用外部 embedding 接口,会产生持续费用,同时还会把文档内容送到第三方服务。使用本地 bge-small-zh-v1.5 模型,只需要首次运行下载模型文件,之后便可以在完全离线的状态下完成向量化,成本低、隐私性也更好。

3. 环境准备与项目结构

3.1 安装 Python 与依赖

本文示例以 Python 3.9 及以上版本为例。老规矩,先创建一个独立的虚拟环境,避免依赖污染系统环境:

python -m venv rag-demo-env source rag-demo-env/bin/activate # Windows 下为 rag-demo-env\Scripts\activate

然后安装依赖包:

pip install openai sentence-transformers chromadb langchain-text-splitters streamlit python-dotenv

如果你的环境安装速度较慢,可以更换为国内 pip 镜像源:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple openai sentence-transformers chromadb langchain-text-splitters streamlit python-dotenv

这里不锁定具体版本号,因为这些库的迭代速度较快,建议使用当前最新稳定版即可。如果后续遇到某个库升级导致接口变动,可以回到对应官方文档确认最新用法。

3.2 项目目录结构

完整源码建议按照下面的目录结构保存:

rag-demo/ ├── .env # API Key 等敏感配置(不提交到仓库) ├── requirements.txt # 依赖清单 ├── config.py # 全局配置 ├── ingest.py # 文档加载与向量库构建 ├── retriever.py # 检索模块 ├── rag.py # RAG 问答主流程 ├── app.py # Streamlit Web 界面 └── data/ # 存放私有文档 ├── 公司介绍.txt └── product_faq.txt

requirements.txt 文件内容如下:

openai>=1.0.0 sentence-transformers>=2.2.0 chromadb>=0.4.0 langchain-text-splitters>=0.0.1 streamlit>=1.30.0 python-dotenv>=1.0.0

4. 核心代码实现:从文档到知识库

4.1 全局配置 config.py

代码的第一步是写一个配置模块,把路径、模型名称、检索参数等集中管理起来。这样后续修改参数时不需要翻遍每个文件。

# 文件路径:rag-demo/config.py import os from dotenv import load_dotenv # 加载 .env 文件中的环境变量 load_dotenv() # 知识库文档目录 KNOWLEDGE_BASE_DIR = "data" # Chroma 向量库持久化目录 CHROMA_DIR = "./chroma_db" # 向量库集合名称 COLLECTION_NAME = "rag_demo" # 本地 Embedding 模型名称 EMBEDDING_MODEL_NAME = "BAAI/bge-small-zh-v1.5" # 检索返回的片段数量 TOP_K = 3 # 文档切分参数 CHUNK_SIZE = 500 CHUNK_OVERLAP = 50 # Deepseek API 配置 DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY", "") DEEPSEEK_BASE_URL = os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com") DEEPSEEK_MODEL = os.getenv("DEEPSEEK_MODEL", "deepseek-chat")

有一处要重点说明:DEEPSEEK_API_KEY 不建议硬编码在代码文件中,应该写在项目根目录的 .env 文件里,并确保 .env 被加入 .gitignore。这样可以避免 API Key 意外提交到公开仓库,造成安全风险。.env 文件的格式如下:

DEEPSEEK_API_KEY=你的Deepseek_API_Key

关于 Deepseek API 的使用,请前往 Deepseek 官方开放平台注册并创建 API Key。使用 API 会产生少量费用,具体计费标准以官方页面为准,建议先小额充值并查看接口文档中的价格说明。

4.2 文档加载与向量化入库 ingest.py

ingest.py 的作用是读取 data 目录下的文档,进行切分、向量化,然后写入向量数据库。这是整个 RAG 系统的离线构建阶段。

# 文件路径:rag-demo/ingest.py import os from typing import List from langchain_text_splitters import CharacterTextSplitter from sentence_transformers import SentenceTransformer import chromadb import config def load_documents(data_dir: str) -> List[str]: """读取 data 目录下的所有 txt 文件,返回文档内容列表。""" docs = [] for filename in os.listdir(data_dir): if filename.endswith(".txt"): filepath = os.path.join(data_dir, filename) with open(filepath, "r", encoding="utf-8") as f: content = f.read() if content.strip(): docs.append(content) print(f"已加载文档: {filename}") return docs def split_text(documents: List[str]) -> List[str]: """将长文档切分成指定大小的文本块 chunk。""" splitter = CharacterTextSplitter( separator="\n", chunk_size=config.CHUNK_SIZE, chunk_overlap=config.CHUNK_OVERLAP, length_function=len, ) chunks = [] for doc in documents: chunks.extend(splitter.split_text(doc)) return chunks def build_vector_store(chunks: List[str]): """将文本块向量化并写入 Chroma 向量库。""" # 加载本地 embedding 模型,首次运行会自动下载 print("正在加载 embedding 模型...") embedder = SentenceTransformer(config.EMBEDDING_MODEL_NAME) # 生成向量 print("正在生成向量...") embeddings = embedder.encode(chunks, show_progress_bar=True).tolist() # 创建 Chroma 客户端,持久化到本地目录 client = chromadb.PersistentClient(path=config.CHROMA_DIR) # 如果集合已存在,先删除,避免重复插入 existing_collections = client.list_collections() for col in existing_collections: if col.name == config.COLLECTION_NAME: client.delete_collection(config.COLLECTION_NAME) print("已删除旧的向量库集合") collection = client.create_collection( name=config.COLLECTION_NAME, metadata={"hnsw:space": "cosine"}, # 使用余弦相似度 ) # 写入向量库,id 使用序号,方便管理 ids = [str(i) for i in range(len(chunks))] # 这里直接把原始文本和向量一起存入 collection.add( ids=ids, embeddings=embeddings, documents=chunks, ) print(f"向量库构建完成,共写入 {len(chunks)} 个文本块") if __name__ == "__main__": all_docs = load_documents(config.KNOWLEDGE_BASE_DIR) if not all_docs: raise ValueError(f"data 目录下没有找到可用文档,请检查目录: {config.KNOWLEDGE_BASE_DIR}") all_chunks = split_text(all_docs) print(f"文档切分完成,共得到 {len(all_chunks)} 个文本块") build_vector_store(all_chunks)

这段代码的重点有几个。

文本切分参数值得专门调试。CHUNK_SIZE 表示每个文本块的最大字符数,CHUNK_OVERLAP 表示相邻块之间重叠的字符数。重叠的部分可以让切块边界处的语义不丢失,尤其是当问题和答案分别跨越两个块边界时,重叠能明显提升召回效果。实际项目中,500 到 800 字是比较常见的块大小,过小会导致检索上下文不足,过大则会让单块包含太多无关信息,降低检索精度。

Chroma 的持久化方式在代码中使用了 PersistentClient,向量库数据会保存到 ./chroma_db 目录。这样下次启动系统时不需要重新构建向量库,直接读取即可。如果知识库文档发生了更新,重新运行一次 ingest.py 即可,不必重启任何服务。

4.3 检索模块 retriever.py

retriever.py 负责接收用户问题,将其向量化后在 Chroma 中检索最相关的 Top K 个片段。

# 文件路径:rag-demo/retriever.py import chromadb from sentence_transformers import SentenceTransformer import config def get_embedder(): """获取共享的 embedding 模型实例,避免多次重复加载。""" return SentenceTransformer(config.EMBEDDING_MODEL_NAME) def search(query: str, top_k: int = None): """根据用户问题检索知识库,返回相关片段列表。""" if top_k is None: top_k = config.TOP_K # 加载向量库 client = chromadb.PersistentClient(path=config.CHROMA_DIR) collection = client.get_collection(config.COLLECTION_NAME) # 对用户问题进行向量化 embedder = get_embedder() query_embedding = embedder.encode([query]).tolist() # 执行相似度检索 results = collection.query( query_embeddings=query_embedding, n_results=top_k, ) documents = results.get("documents", [[]])[0] distances = results.get("distances", [[]])[0] return documents, distances

这里需要留意一个问题:每次调用 get_embedder 都会重新加载一次模型。在 Streamlit 这种 Web 服务中,频繁加载模型会拖慢速度。实际开发时可以把 embedder 设计成全局单例,或者放到内存缓存中。我在这里保留简单写法,是为了让流程更清晰,后面工程优化部分再讨论如何改进。

Chroma 的 query 接口返回结果中,documents 是对应的原始文本内容,distances 是余弦距离。距离越小表示相似度越高,后续可以把距离信息一并展示,帮助用户判断答案的可信度。

4.4 RAG 问答主流程 rag.py

rag.py 是系统的核心,负责把检索结果和用户问题组装成 Prompt,然后调用 Deepseek 生成回答。

# 文件路径:rag-demo/rag.py from openai import OpenAI import config from retriever import search # 初始化 Deepseek 客户端,兼容 OpenAI SDK client = OpenAI( api_key=config.DEEPSEEK_API_KEY, base_url=config.DEEPSEEK_BASE_URL, ) def build_prompt(query: str, context_docs: list) -> str: """组装发送给大模型的 Prompt。""" system_prompt = ( "你是一个专业的知识库问答助手。" "请根据提供的参考资料回答用户问题。" "必须优先参考参考资料中的内容,不要臆造答案。" "如果参考资料无法回答该问题,请直接说明知识库中暂未找到相关信息。" ) context = "\n\n".join(context_docs) user_prompt = f"""请根据以下参考资料回答用户问题。 【参考资料】 {context} 【用户问题】 {query} 请用中文回答,如果引用了参考资料,可以说明信息来源于哪个部分。""" return system_prompt, user_prompt def ask(query: str) -> dict: """Retrieve + Generate 完整流程。""" # 检索相关片段 docs, distances = search(query) # 组装 Prompt system_prompt, user_prompt = build_prompt(query, docs) # 调用 Deepseek 生成回答 response = client.chat.completions.create( model=config.DEEPSEEK_MODEL, temperature=0.3, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}, ], ) answer = response.choices[0].message.content return { "answer": answer, "source_documents": docs, "distances": distances, } if __name__ == "__main__": question = input("请输入你的问题:") result = ask(question) print("\n==== 回答 ====") print(result["answer"]) print("\n==== 引用片段 ====") for i, doc in enumerate(result["source_documents"]): print(f"\n[{i + 1}] {doc[:200]}")

Deepseek 的接入方式与 OpenAI 几乎一致,核心代码只有这么几行。这里给出的 model 名称是 deepseek-chat,对应 Deepseek 的对话模型。temperature 设置为 0.3,会让回答更加稳定、更贴近参考资料内容。如果你想获得更有创造性的回答,可以调高到 0.7 左右,但在知识库问答场景下,推荐保持低 temperature,以减少胡说八道的概率。

Prompt 的设计值得仔细思考。系统提示词明确要求“必须优先参考参考资料中的内容,不要臆造答案”,这一步是抑制幻觉的关键。如果直接把问题和检索片段丢给模型,而不加任何约束,模型仍然可能倾向使用自己的知识回答。所以在 RAG 系统中,Prompt 不是随便拼一段文字,而是需要反复打磨的工程质量项。

4.5 Streamlit Web 界面 app.py

最后写一个简单的 Web 界面,让系统可以被同事或用户直接使用。

# 文件路径:rag-demo/app.py import streamlit as st import config from rag import ask st.set_page_config(page_title="RAG 知识库问答系统", page_icon="📚", layout="wide") st.title("📚 RAG 知识库问答系统") st.caption("基于 Deepseek + Chroma 构建的私有知识库问答系统") query = st.text_area("请输入你的问题", height=100) if st.button("获取答案", type="primary"): if not query.strip(): st.warning("请输入问题内容") elif not config.DEEPSEEK_API_KEY: st.error("未检测到 DEEPSEEK_API_KEY,请检查 .env 文件配置") else: with st.spinner("正在检索并生成回答..."): result = ask(query.strip()) st.subheader("回答") st.write(result["answer"]) with st.expander("查看参考文档片段"): for i, (doc, dist) in enumerate(zip(result["source_documents"], result["distances"])): st.markdown(f"**片段 {i + 1}**(距离:{dist:.4f})") st.text(doc[:500])

这个界面就是典型的“输入框 + 按钮 + 结果展示”结构,没有复杂的前端依赖。st.expander 折叠展示参考片段,让用户既能看答案,也能核对答案的出处,符合 RAG 可溯源的优势。

5. 运行系统与效果验证

5.1 准备示例文档

在 data 目录下创建两个测试文档,这里以一个虚构的公司场景为例:

# 文件路径:rag-demo/data/公司介绍.txt 某某科技有限公司成立于2018年,总部位于上海,主营业务是企业级人工智能解决方案定制服务。 公司核心技术团队来自国内一线互联网公司与高校实验室,在自然语言处理、知识图谱、大模型应用方向有丰富经验。 公司的主要产品包括智能客服系统、知识库问答平台、数据中台建设服务。 截至2025年,公司已服务超过200家中小企业客户,覆盖零售、金融、教育等行业。
# 文件路径:rag-demo/data/product_faq.txt 问:知识库问答平台支持哪些文档格式? 答:平台支持 txt、PDF、Word、Markdown 等常见格式,推荐使用 Markdown 或 txt 以获得最佳解析效果。 问:平台的数据是否安全? 答:平台支持私有化部署,文档向量化和检索过程可以在企业内部网络独立完成,只有最终生成回答时会调用大模型 API。 问:知识库更新需要重新训练模型吗? 答:不需要。知识库问答系统采用 RAG 架构,只需要更新向量库,即可让模型基于最新文档回答问题。

5.2 初始化知识库

在项目根目录执行:

python ingest.py

正常情况下会看到类似输出:

已加载文档: 公司介绍.txt 已加载文档: product_faq.txt 文档切分完成,共得到 6 个文本块 正在加载 embedding 模型... 正在生成向量... 向量库构建完成,共写入 6 个文本块

首次运行会下载 bge-small-zh-v1.5 模型,文件大小在 100MB 左右,需要保持网络畅通。下载完成后,模型会缓存在本地,之后运行不会再重复下载。

5.3 启动 Web 界面

streamlit run app.py

浏览器会自动打开 Streamlit 提供的本地地址,默认是 http://localhost:8501。可以看到一个简洁的问答界面,输入问题后点击“获取答案”。

尝试提问:“公司知识库平台支持哪些文档格式?”

预期回答会参考 product_faq.txt 中的内容,并明确指出支持 txt、PDF、Word、Markdown 等格式。如果提问“公司成立于哪一年”,系统应该从公司介绍文档中检索到对应信息并给出答案。

在“查看参考文档片段”区域,你会看到检索到的原始片段和对应的相似度距离,这能帮助你判断系统是否真正用上了知识库内容。

6. 常见问题与排查思路

在实际运行中,很多问题其实是共通的。下面把高频问题整理成表格,再逐个展开。

问题现象常见原因解决思路
首次运行下载模型失败网络不稳定或 HuggingFace 地址无法访问使用镜像地址或手动下载模型到本地
向量库查询报错 Collection not found未执行 ingest.py 或目录不一致执行 ingest.py 构建向量库;检查 CHROMA_DIR
检索结果与问题无关chunk 过大、top_k 过小、文档格式混乱调整 CHUNK_SIZE、增大 TOP_K,清洗文档
Deepseek API 返回 401 错误API Key 不正确或未加载成功检查 .env 文件,确认 load_dotenv 生效
回答没有引用知识库内容Prompt 约束不够或检索片段为空加强 system prompt,检查检索结果
Chroma old database version 报错向量库版本升级不兼容删除 chroma_db 目录后重新 ingest

6.1 首次运行模型下载失败

bge-small-zh-v1.5 模型默认从 HuggingFace 下载。如果你的网络环境无法稳定访问,可以设置镜像环境变量:

export HF_ENDPOINT=https://hf-mirror.com

然后在终端重新运行 ingest.py。设置镜像环境变量的目的是解决下载通道问题,属于开发过程中的常规操作。模型下载完成后,可以不再依赖外网。

另一种方式是从其他方式下载模型文件后,把模型目录放到本地,然后在 config.py 中将 EMBEDDING_MODEL_NAME 改为模型所在的本地路径。例如:

EMBEDDING_MODEL_NAME = "./models/bge-small-zh-v1.5"

6.2 检索结果不理想

检索结果差通常不是单一原因造成的,需要按照顺序排查几个关键参数。

先检查 TOP_K。如果设置为 1,结果可能只覆盖一个片段,信息量不足;如果设置为 5,又可能混入大量不相关内容。对于短文档,3 是合理的起点。

再检查 CHUNK_SIZE。如果文档本身是 FAQ 格式,每条问答很短,那么把 CHUNK_SIZE 设置成 500,会把多个问答切进同一个块,导致检索时指向内容不准确。对这种结构化文档,建议将 chunk_size 调小,比如 200 到 300,并去掉过大的 overlap。

最重要的还是文档清洗。如果原始文档包含大量页眉页脚、特殊符号、乱码,向量化效果会明显变差。在真实项目中,文档预处理往往占用整个 RAG 项目一半以上的工作量,这是很正常的现象。

6.3 Deepseek API 接入问题

如果调用时报 401,最直接的排查方式是打印 config.DEEPSEEK_API_KEY,确认 .env 是否被正确加载。.env 文件必须和运行命令所在目录一致,也就是项目根目录。

如果报连接超时,先确认网络环境能够访问 Deepseek API。同时,可以在代码中打印 DEEPSEEK_BASE_URL,确认没有因为之前的项目设置而覆盖成了其他地址。

6.4 向量库版本兼容问题

Chroma 版本迭代较快,不同版本生成的数据库文件可能不兼容,出现类似“old database version”的报错时,最简单的处理是备份并删除 chroma_db 目录,然后重新运行 ingest.py。这只是开发阶段的做法,生产环境中应该提前规划向量库的升级策略,例如使用独立的向量数据库服务。

7. 最佳实践与工程化建议

文章到这里,整套代码已经能跑通了。但如果你想把这个 Demo 变成真正可用的系统,还需要注意下面几个工程问题。

7.1 把 Prompt 单独管理

不要把 Prompt 字符串散落在业务代码里。随着项目迭代,Prompt 的调整频率很高,应该单独提取为一个配置文件或 Prompt 管理模块。某些团队还会为不同场景编写多套 Prompt,比如“摘要模式”“对比模式”“严格引用模式”,然后用模版变量动态切换。

7.2 增加检索结果的引用溯源

RAG 的核心优势之一就是可解释性。在 Web 界面和 API 返回中,都应该保留 source_documents 字段,最好把文档名、页码、片段位置一起返回。这样用户能够点击查看原文,运营人员也能快速发现错误片段并及时修正知识库。

7.3 完善文档更新机制

目前 ingest.py 每次都会删除旧集合重新写入。当知识库规模变大后,应该改成增量更新:根据文档的 hash 值或更新时间,只处理变更部分。向量库中也要增加元数据字段,比如来源文件名、更新时间、部门标签,方便按条件过滤。

7.4 引入重排(Rerank)提升精度

向量检索拿到的 Top K 片段里,可能顺序并不完全符合语义相关度。更成熟的方案是在向量检索之后增加一个 rerank 模型,对候选片段重新打分。比如本地部署 bge-reranker-base 模型,先用向量召回 20 个候选,再重排取前 3 个送给大模型。实践表明,加一层 rerank 后,回答准确率往往会有明显提升。

7.5 从 RAG 走向 Agentic RAG

目前实现的是标准 RAG,每次提问只做一次检索。当问题复杂时,例如“对比公司产品 A 和产品 B 的区别”,或者“找出去年所有投诉工单中的高频问题”,单次检索可能不够。

Agentic RAG 的核心思想是让大模型具备“工具调用”能力:它可以决定先搜索什么、搜索几次、是否改写问题、是否需要查看多个文档后再综合回答。这个方向可以作为下一步的进阶学习目标。本质上是把 RAG 的“搜索链路”从固定流程升级成由模型驱动的动态流程,对 Prompt 设计和工具调用的工程质量要求更高。

7.6 安全与合规注意事项

涉及到 API 调用、内部文档时,有几个安全底线需要坚持。第一,API Key 绝不硬编码在源码中,.env 文件必须加入 .gitignore;第二,生产环境建议通过独立的密钥管理服务接收 API Key,由后端服务统一调用,不要把密钥直接暴露给前端;第三,企业内部敏感文档如果涉及数据保密要求,需要确认是否允许调用外部大模型 API。如果严格禁止外部传输,则应该采用本地部署的模型替换 Deepseek API,把整个链路都收敛在内网环境中完成。

8. 从入门到落地,下一步可以做什么

至此,你已经亲手搭完了一套完整的 RAG 知识库问答系统:从文档加载、切分、向量化、检索,到 Prompt 组装和 Deepseek 生成回答,所有环节都是可运行、可扩展的。相比直接调用大模型接口“硬问”,这套方案让模型真正学会利用你的私有资料来回答问题。

如果你打算继续深入,有两条推荐路线。一条是优化检索质量,尝试引入 rerank、多路召回、元数据过滤,让召回结果更精准;另一条是走向 Agentic RAG,让模型能自主规划检索步骤,处理更复杂的多跳问答任务。两条路线都不需要重新训练模型,完全符合 RAG 低成本的核心理念。

构建知识库本身也是一门细活。文档清洗、文本切分、领域词表、Prompt 调优,每一个环节都会影响最终回答质量。不要指望运行一遍代码就得到完美效果,真正的项目落地就是在这些细节里不断打磨的过程。希望这份手把手教程能帮你迈出扎实的第一步,也欢迎把你在搭建过程中遇到的问题留在评论区,大家互相交流排错经验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询