1. RAG 是什么,为什么需要它
最近大模型相关的应用越来越多,很多人都在尝试做“知识库问答”这类功能。无论是企业内部的规章制度查询、产品文档问答,还是个人知识库整理,背后都离不开一个核心方向:让大模型学会回答它原本不知道的内容。
这里就涉及一个基础问题:大模型的训练数据是有截止时间的,它并不了解你公司的内部资料、某个私有系统的数据结构,或者一份刚发布的政策文件。如果直接让大模型去回答这些内容,最常见的现象就是“一本正经地胡说八道”,也就是所谓的幻觉。
RAG 全称是 Retrieval-Augmented Generation,中文翻译为“检索增强生成”。它的核心思路很简单:在让大模型回答问题之前,先从外部知识库中检索出相关内容,把检索到的内容作为参考材料,连同用户的问题一起交给大模型,让它基于这些材料来生成回答。
用通俗的话解释就是:大模型从“闭卷考试”变成了“开卷考试”。它不需要把知识背诵下来,只需要学会读懂你递给它的参考资料。
RAG 的技术架构一般包含三个核心环节:
- 知识库构建:把文档切块、向量化,存储到向量数据库中。
- 召回:根据用户的问题,从向量库中找出最相关的知识片段。
- 生成:把用户问题和召回结果组装成 Prompt,让大模型生成最终答案。
这套流程不要求你训练模型,也不需要微调大模型的参数,所以上手门槛比预训练和微调低很多,尤其适合新手。
从应用场景来看,RAG 已经覆盖了智能客服、企业知识库问答、法律政策咨询、医疗助手、教育辅导、代码检索问答等众多方向。可以这样说,只要存在“私有数据 + 问答需求”的场景,RAG 就是当前性价比最高的技术方案之一。
本文会从最基础的概念讲起,逐步带你完成一个可以运行的 RAG 项目,并深入分析召回优化、向量库选型、工程落地中的关键问题。即使你之前完全没接触过大模型开发,也可以按照文章内容一步步跑通流程。
2. 环境准备与版本说明
在开始写代码之前,先来梳理一下环境依赖。RAG 涉及的内容比较多,如果不提前把环境理清楚,后面很容易出现各种版本冲突问题。
2.1 推荐运行环境
本文的示例代码基于 Python 编写,推荐使用以下环境:
| 组件 | 推荐版本/方案 | 说明 |
|---|---|---|
| 操作系统 | Windows 10/11、macOS、Linux 均可 | 本文演示基于 Python 虚拟环境 |
| Python | 3.9 及以上 | 建议使用 3.10 或 3.11 |
| 向量数据库 | Chroma / FAISS / Milvus | 新手建议从 Chroma 开始 |
| 大模型接入方案 | OpenAI 兼容 API / 本地模型 | 本文示例使用 OpenAI 兼容接口配置 |
| Embedding 模型 | text-embedding-3-small / BGE 系列 | 决定向量检索的质量 |
| 开发工具 | VS Code / PyCharm / Jupyter Notebook | 都可以,本文用命令行演示 |
需要注意,这里给出的版本只是常用参考,你的项目实际使用什么版本,需要根据自身环境来确定。尤其是 Python 版本和各个依赖库的版本,在安装前最好确认一下兼容性。
2.2 创建项目目录和虚拟环境
先创建一个项目目录,并建立虚拟环境:
mkdir rag-demo cd rag-demo python -m venv venv激活虚拟环境:
Windows 下:
venv\Scripts\activatemacOS / Linux 下:
source venv/bin/activate2.3 安装依赖库
本文的核心依赖如下:
- langchain:用于组装检索和生成流程。
- langchain-community:提供向量库等第三方集成。
- chromadb:本地向量数据库。
- openai:调用大模型接口。
- python-dotenv:读取环境变量。
- pypdf:解析 PDF 文档。
安装命令如下:
pip install langchain langchain-community chromadb openai python-dotenv pypdf如果你已经有 Python 环境,且不想破坏系统环境,请务必先创建虚拟环境再安装。版本冲突是 RAG 新手最常见的问题之一,虚拟环境可以有效规避。
安装完成后,可以用下面命令验证:
python -c "import langchain; print(langchain.__version__)"如果能够正常输出版本号,说明基础环境已经就绪。
3. RAG 核心原理拆解
在编写完整代码之前,先把 RAG 的每个核心环节拆开来看。只有理解了每个环节在做什么,后面调优时才知道应该改哪里。
3.1 文档加载与解析
RAG 的第一步是让系统“读”文档。这里的文档可能是 PDF、Word、Markdown、TXT,甚至数据库中的文本字段。
原始文件是不能直接进入模型的,需要先把它们解析成纯文本。以 PDF 为例,常见的做法是用 pypdf 或 PyMuPDF 读取每一页的内容。
示例代码如下:
from pypdf import PdfReader reader = PdfReader("docs/产品手册.pdf") for page in reader.pages: print(page.extract_text())这里需要注意,PDF 的排版往往比较复杂,直接提取出的文本可能会出现段落断裂、乱码、表格错乱等问题。如果文档质量较差,建议先通过 OCR 工具把扫描版 PDF 转为文本,再做后续处理。
3.2 文本切块
大模型和向量模型都有输入长度限制,我们不可能把整本手册一次性塞给模型。所以需要把长文本切成多个片段,每个片段成为一个“chunk”。
切块策略会直接影响召回效果。切得太小,单个片段包含的信息量不足,语义不完整;切得太大,向量表示会出现噪音,而且超出模型输入窗口时还得二次截断。
常见的切块方式有两种:
基于固定大小切块:
from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, ) chunks = text_splitter.split_text(long_text)基于语义结构切块:
如果原始文档已经按章节、段落组织,可以优先按标题和段落边界切分,这样每个片段都能保持完整语义。
实践中需要结合文档类型灵活选择策略。简单文档用固定大小切块即可,结构复杂的技术文档建议先识别标题层级,再做分段。
3.3 Embedding 向量化
文本切块之后,接下来要把每一段文本转换成向量。Embedding 模型的作用就是完成这个转换:它把一段文字映射成一个高维向量,语义相近的两段文字,它们的向量在空间中也应该距离较近。
OpenAI 的 text-embedding-3-small 是目前比较常用的 Embedding 模型之一,成本低,效果稳定。调用方式如下:
from openai import OpenAI client = OpenAI() response = client.embeddings.create( model="text-embedding-3-small", input="这是一段测试文本" ) vector = response.data[0].embedding print(len(vector))输出的向量维度通常是 1536。如果不想调用外部 API,也可以使用本地模型,例如 BGE 系列或 M3E 系列。自行部署本地 Embedding 模型的优点是数据不出内网,适合对数据安全要求较高的场景。
3.4 向量库存储与检索
向量化之后,需要把向量保存到一个专门用于相似度检索的数据库中,这就是向量数据库。
向量数据库区别于传统数据库的核心能力是向量检索。它支持我们输入一条查询向量,然后返回库中与这条向量最相似的若干条记录。
Chroma 是一个轻量级向量数据库,零配置、本地运行,非常适合新手入门。
初始化 Chroma 的代码:
import chromadb client = chromadb.PersistentClient(path="./chroma_db") collection = client.get_or_create_collection(name="knowledge_base")写入向量和文本:
collection.add( ids=["1", "2", "3"], embeddings=[vec1, vec2, vec3], documents=["片段1", "片段2", "片段3"], metadatas=[{"source": "a.pdf"}, {"source": "b.pdf"}, {"source": "c.pdf"}] )检索时,只需要传入查询向量和 top_k 参数:
results = collection.query( query_embeddings=[query_vector], n_results=3 ) print(results["documents"])这里的 n_results 就是返回候选片段的数量。数量太少会漏掉正确答案,太多则会混入无关内容,通常设置为 3 到 5 比较合适。
3.5 Prompt 组装与答案生成
向量库检索出候选片段之后,把这些片段和用户问题拼接成 Prompt,交给大模型生成答案。
让大模型“只基于参考文档回答”,并且“当参考文档中没有答案时如实说明”,这两个约束是控制幻觉的关键。
组装 Prompt 的代码思路如下:
def build_prompt(question, context_docs): context = "\n\n---\n\n".join(context_docs) prompt = f""" 你是企业内部智能助手。请严格按照"参考资料"中的内容回答用户问题。 参考资料: {context} 用户问题: {question} 要求: 1. 答案必须基于参考资料,不要编造。 2. 如果参考资料中没有相关答案,请明确回答"资料库中未找到相关信息"。 3. 回答时直接给出结果,不要复述参考资料原文。 """ return prompt大模型生成回答之后,可以把参考资料和答案一起返回给前端展示,这样用户也能判断回答是否可信。这个是很多生产级 RAG 系统中的常见做法。
4. 零基础搭建 RAG 知识库问答项目
前面把概念和原理梳理完了,接下来进入完整实战环节。我们会从零开始搭建一个小型 RAG 问答项目,处理一份本地文档,然后通过命令行进行提问。
4.1 项目结构设计
先创建好项目目录结构:
rag-demo/ ├── docs/ │ └── 企业员工手册.txt ├── src/ │ ├── config.py │ ├── ingest.py │ ├── query.py │ └── rag.py ├── .env └── requirements.txt- docs 目录:存放需要导入知识库的文档。
- src/config.py:统一读取环境变量和配置。
- src/ingest.py:处理文档、向量化、写入向量库。
- src/query.py:检索 + 生成回答。
- src/rag.py:封装完整的 RAG 类。
- .env:存放 API Key 等敏感信息。
4.2 编写配置文件
在项目根目录创建 .env 文件:
OPENAI_API_KEY=你的API_Key OPENAI_BASE_URL=https://api.openai.com/v1 OPENAI_MODEL=gpt-4o-mini EMBEDDING_MODEL=text-embedding-3-small如果你的网络环境需要配置代理,请自行配置,这里不做展开。注意,.env 文件不要提交到 Git 仓库中,防止密钥泄露。
编写 src/config.py:
import os from dotenv import load_dotenv load_dotenv() class Config: OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL") OPENAI_MODEL = os.getenv("OPENAI_MODEL") EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL") CHROMA_DB_PATH = "./chroma_db" COLLECTION_NAME = "employee_handbook"这里把所有配置集中到 Config 类中,后续代码可以直接引用,避免各个模块都去解析环境变量。
4.3 编写文档导入脚本
创建 src/ingest.py:
from langchain.text_splitter import RecursiveCharacterTextSplitter from openai import OpenAI import chromadb from src.config import Config def load_document(file_path: str) -> str: """读取本地文本文件,返回完整文本内容""" with open(file_path, "r", encoding="utf-8") as f: return f.read() def split_text(text: str): """将长文本切块,返回文本块列表""" splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""] ) return splitter.split_text(text) def embed_texts(client: OpenAI, texts: list[str], model: str): """批量对文本块进行向量化""" resp = client.embeddings.create(input=texts, model=model) return [item.embedding for item in resp.data] def ingest_document(file_path: str): """完整导入流程:读取 -> 切块 -> 向量化 -> 写入向量库""" client = OpenAI( api_key=Config.OPENAI_API_KEY, base_url=Config.OPENAI_BASE_URL ) ch_client = chromadb.PersistentClient(path=Config.CHROMA_DB_PATH) collection = ch_client.get_or_create_collection(name=Config.COLLECTION_NAME) text = load_document(file_path) chunks = split_text(text) vectors = embed_texts(client, chunks, Config.EMBEDDING_MODEL) ids = [f"chunk_{i}" for i in range(len(chunks))] sources = [file_path] * len(chunks) collection.add( ids=ids, embeddings=vectors, documents=chunks, metadatas=[{"source": src} for src in sources] ) print(f"成功导入 {len(chunks)} 个文本块到向量库")这里有两个细节需要注意:
RecursiveCharacterTextSplitter 的分隔符顺序有讲究。它优先按段落切,其次按句号、问号等语义边界切,最后才按空格和字符切。这样做可以在尽量保持语义完整的前提下控制块大小。
写入向量库时,我们同时保存了原文和来源文件路径。保存原文是为了后续生成答案时能直接拼接到 Prompt 中;保存来源则是为了做引用溯源。
4.4 编写问答查询脚本
创建 src/query.py:
from openai import OpenAI import chromadb from src.config import Config def search_relevant_docs(question: str, collection, top_k: int = 4): """对问题向量化并检索最相关的文本块""" client = OpenAI( api_key=Config.OPENAI_API_KEY, base_url=Config.OPENAI_BASE_URL ) query_vec = client.embeddings.create( input=question, model=Config.EMBEDDING_MODEL ).data[0].embedding results = collection.query( query_embeddings=[query_vec], n_results=top_k ) return results["documents"][0], results["metadatas"][0] def build_prompt(question: str, context_docs: list[str]) -> str: """组装大模型生成回答所需的 Prompt""" context = "\n\n---\n\n".join(context_docs) prompt = f""" 你是企业内部智能助手。请严格按照"参考资料"中的内容回答用户问题。 参考资料: {context} 用户问题: {question} 要求: 1. 答案必须基于参考资料,不要编造。 2. 如果参考资料中没有相关答案,请明确回答"资料库中未找到相关信息"。 3. 回答时直接给出结果,不要复述参考资料原文。 """ return prompt def ask_question(question: str, top_k: int = 4): """完整问答流程:检索 -> 组装 Prompt -> 生成回答""" ch_client = chromadb.PersistentClient(path=Config.CHROMA_DB_PATH) collection = ch_client.get_collection(name=Config.COLLECTION_NAME) context_docs, metadatas = search_relevant_docs(question, collection, top_k) prompt = build_prompt(question, context_docs) client = OpenAI( api_key=Config.OPENAI_API_KEY, base_url=Config.OPENAI_BASE_URL ) resp = client.chat.completions.create( model=Config.OPENAI_MODEL, messages=[{"role": "user", "content": prompt}] ) answer = resp.choices[0].message.content return answer, context_docs, metadatas这个脚本的核心逻辑分为三部分:
- search_relevant_docs 负责召回,它先对用户问题做向量化,然后从向量库中检索相关片段。
- build_prompt 负责组装上下文。
- ask_question 负责调用大模型生成最终答案。
4.5 编写统一入口
为了方便测试,再创建一个 src/rag.py:
from src.ingest import ingest_document from src.query import ask_question def main(): print("========== RAG 知识库问答系统 ==========") while True: print("\n请输入指令:") print("1: 导入文档") print("2: 发起提问") print("0: 退出") cmd = input(">>> ").strip() if cmd == "1": path = input("请输入文档路径:").strip() ingest_document(path) elif cmd == "2": question = input("请输入你的问题:").strip() answer, context_docs, metadatas = ask_question(question) print("\n【回答】") print(answer) print("\n【参考材料】") for i, doc in enumerate(context_docs): print(f"[片段{i+1}] {doc[:100]}...") elif cmd == "0": break else: print("无效指令,请重新输入") if __name__ == "__main__": main()4.6 准备测试文档
在 docs 目录下创建一份测试文档企业员工手册.txt,内容可参考以下模板:
# 企业员工手册 ## 第一章 入职与考勤 新员工入职后需在三个工作日内完成入职信息登记,并领取办公设备。 公司实行弹性工作制,核心工作时间为上午 10:00 至下午 4:00。 员工每日上下班需通过企业微信进行打卡,漏打卡需在当日提交补卡申请。 ## 第二章 休假制度 公司员工每年享有 15 天带薪年假,年假有效期为自然年。 病假需提供医院开具的证明,事假需提前两个工作日向直属主管申请。 产假和陪产假按照国家和当地最新政策执行。 ## 第三章 报销制度 差旅费用报销需在出差结束后的 10 个工作日内完成申请。 报销金额在 5000 元以上的,需要额外提交部门负责人审批。 发票需为增值税普通发票或电子发票,发票抬头需填写公司全称。实际项目中请将测试文档替换为你的私有知识库内容。
4.7 运行项目
首先导入文档:
python -m src.rag在交互菜单中选择 1,输入文档路径:
docs/企业员工手册.txt正常输出:
成功导入 12 个文本块到向量库接下来选择 2,输入问题:
公司的年假有多少天?系统会输出类似下面的回答和参考材料:
【回答】 根据《企业员工手册》,公司员工每年享有 15 天带薪年假,年假有效期为自然年。 【参考材料】 [片段1] 公司员工每年享有 15 天带薪年假,年假有效期为自然年。...到这里,一个最简单可运行的 RAG 项目就已经搭建完成了。
5. 向量数据库对比与选型建议
Chroma 适合学习和原型开发,但在生产环境中,还需要结合数据量、并发量、是否支持持久化、运维成本等维度综合考虑。
下面给出几个常见向量数据库的对比:
| 数据库 | 部署方式 | 适用规模 | 优点 | 不足 |
|---|---|---|---|---|
| Chroma | 嵌入式 | 小型项目 | 零配置、易上手 | 高并发能力有限 |
| FAISS | 库 | 中等规模 | 检索速度快 | 不做持久化管理较麻烦 |
| Milvus | 服务端 | 大规模 | 分布式、功能完善 | 部署运维成本较高 |
| Qdrant | 服务端 | 中等/大规模 | 能力完善、可嵌入可服务 | 需额外运维 |
| Elasticsearch + 向量插件 | 服务端 | 大量文本检索 | 关键词检索与向量检索结合 | 配置复杂 |
选择建议:
- 学习阶段:优先用 Chroma,可以和本文示例无缝衔接。
- 个人知识库或内部工具:FAISS 或 Chroma 都够用。
- 企业级项目:建议考虑 Milvus 或 Qdrant,有条件的话可以自建,也可以使用云厂商提供的向量数据库服务。
补充一点:如果项目不仅需要语义检索,还需要做高频关键词精确匹配,那么混合检索方案会更合适。先用关键词检索保证精确命中,再用向量检索补充语义相关结果,最后用加权融合的方式合并排序结果。这里的细节会在下一节展开。
6. 召回优化:从能跑到好用
RAG 项目跑通后,你会发现一个问题:简单场景下效果不错,但遇到复杂问题时,召回结果往往不够准确。这一节专门来聊召回优化。
6.1 切块参数调优
chunk_size 和 chunk_overlap 是最先应该调整的参数。
如果问答场景偏短问短答,比如“系统密码重置流程是什么”,切块粒度在 300 到 500 字之间即可;如果问答场景需要大段上下文,比如“请总结这份合同中的违约责任条款”,建议把 chunk_size 提高到 800 到 1500。
chunk_overlap 的作用是让相邻片段之间有部分重叠,弥补切块时截断导致的语义断层。一般设置为 chunk_size 的 10% 到 20%。
调优建议:
- 先切小块测试,记录漏召回。
- 逐步增大 chunk_size,观察答案完整性。
- 最终将两者结合起来,以一份量化评测集为准。
6.2 搜索类型选择
在某些向量库和检索框架中,可以配置检索类型:
- similarity:余弦相似度,返回相似度最高的若干片段。
- mmr:最大边际相关,可以在保证相关性的同时提高多样性。
MMR 适合防止多个召回结果高度重叠的情况。比如用户问一个涉及多个条款的问题,如果三个片段都在讲同一句话,答案会缺少全面性;MMR 会让系统不仅看相关性,还会惩罚与已有结果相似的候选。
6.3 引入混合检索
纯向量检索最大的问题,是它对专有名词和精确编号不敏感。比如文档中明确写着“流程编号 A-1024”,用户问“A-1024 是什么”,向量检索可能把这一段排在很后面,而关键词“A-1024”直接精确命中才是最好的结果。
混合检索的常见做法是:
- 用 BM25 进行关键词召回。
- 用向量检索进行语义召回。
- 将两部分结果做加权合并。
如果你使用的是 LangChain,可以通过 EnsembleRetriever 来组合两种召回器。
6.4 使用重排序模型
向量检索得到的 top_k 结果,通常只是“粗排”。候选片段可能包含噪音,直接让大模型处理会增加幻觉和错误率。
重排序模型(Reranker)可以用来对候选结果做二次精排。它会同时读入用户问题和候选片段,输出一个相关性分数,比分更高的片段排在前面。
常见方案有:
- bge-reranker
- cohere-rerank
- 通过 API 自建的交叉编码器排序服务
引入重排后的流程如下:
用户问题 -> 向量召回 top 20 -> 重排 -> 取 top 5 -> 组装 Prompt -> 大模型生成先扩大召回范围,再用重排精筛,效果通常优于直接取 top 5。
6.5 元数据过滤
如果知识库中包含多个来源文档,可以在检索时按来源过滤。例如用户只希望查询安全相关制度,就可以在向量查询条件中增加 metadata 过滤条件。
Chroma 中过滤示例:
collection.query( query_embeddings=[query_vec], n_results=5, where={"department": "安全部"} )元数据过滤能明显缩小检索范围,减少无关文档引入的干扰,同时提升检索速度。
7. Prompt 设计对答案质量的影响
召回结果再好,如果 Prompt 写得粗糙,大模型依然可能输出错误答案。下面是几个 Prompt 设计的原则。
7.1 明确角色和任务
不要直接用这样简单的写法:
根据以下内容回答问题:...更好的写法是给模型一个明确角色定位,例如“你是企业知识库智能助手”,并明确任务边界:“你的任务是基于指定参考资料回答问题,不要使用外部知识”。
7.2 设置防幻觉约束
在 Prompt 中明确告诉模型:“如果参考资料没有相关内容,直接回答‘未找到相关资料’,不要编造”。
同时可以补充输出格式要求,例如只输出回答内容、不要列出思考过程、不要复述原文等。
7.3 多轮对话中的上下文策略
如果是多轮问答,不要简单地把历史对话全部拼接进 Prompt,因为这样会稀释当前问题的相关性。更推荐的做法是:
- 当前问题先从向量库检索。
- 历史对话只保留最近 2 到 3 轮。
- 把历史对话摘要后再拼入上下文。
有条件的话,可以增加一层“对话改写”模块,把“它的使用流程是什么”这类代词问题改写成完整的独立问题,再去做检索。这种思路对多轮 RAG 效果提升非常明显。
7.4 提供答案格式要求
企业级场景中,往往需要答案输出为结构化内容,例如分点说明、表格形式、JSON 等。在 Prompt 中明确格式,可以让输出更可控。
8. 常见问题与排查思路
在 RAG 开发和调试过程中,以下问题出现的频率最高。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 导入文档时报错 | 依赖库版本冲突 | 检查 requirements.txt,统一依赖版本,重建虚拟环境 |
| 向量检索结果为空 | 集合名称不一致 | 确认写入和查询使用的 collection name 相同 |
| 回答内容不是基于文档 | Prompt 缺少约束 | 在 Prompt 中加入“严格基于参考资料回答” |
| 回答中混入无关内容 | 召回数量过多或切块跨主题 | 降低 top_k,优化切块策略 |
| 检索速度慢 | 向量库中数据量过大 | 增加元数据过滤,使用索引参数调优 |
| 同一个问题多次回答不稳定 | 大模型温度参数过高 | 将 temperature 调到 0.2 以下 |
| PDF 解析后乱码 | 扫描件或特殊编码 | 使用 OCR 预处理,或换解析工具 |
一个非常实用的排查思路是:先分步检查 RAG 管道的每一环。把“召回结果”打印出来,看看检索到的片段是否包含答案;把“Prompt 内容”打印出来,看看模型收到的上下文是否完整;最后再看“生成结果”。
在实际调试中,大多数问题都出在检索阶段,而不是生成阶段。
9. 最佳实践与工程落地建议
RAG 项目要想从 demo 走向生产,还有很多工程细节需要注意。以下是我认为最重要的几条建议。
9.1 数据质量优先
RAG 的效果上限取决于知识库的质量。如果原始文档本身存在错误、过时、歧义,无论检索和生成做得多好,最终答案都可能不正确。
建议先清洗文档,删除与主题无关的广告、页眉页脚、乱码,统一术语名称。对文本中的表格和复杂排版,优先转换成 Markdown 格式或纯文本再入库。
9.2 建立效果评测集
不要凭感觉判断“效果好了一点”,要建立固定的评测集。
评测集至少包含 30 到 100 条高质量问答对,覆盖常见问题、生僻问题和易混淆问题。每次修改参数后,在相同评测集上对比,才能客观评估调优效果。
指标可以直接看命中率、准确率和端到端回答正确率,也可以借助一些自动评测框架来做。
9.3 设计可观测性
生产环境必须能够追踪到每条回答的依据来源。前端展示答案时,把召回的相关文档片段一并展示,操作者可以自行判断答案来源是否有说服力。
同时做好日志记录,包括用户问题、召回结果、重排分数、最终答案、耗时。这些日志不仅是排查问题的依据,也是后续优化推荐策略的数据来源。
9.4 控制敏感信息边界
知识库问答系统涉及敏感信息时必须谨慎。注意以下几点:
- 对不同角色设置不同知识库权限,普通员工不应检索到薪酬、机密文件等受限内容。
- 对 API Key、数据库连接串、内部系统地址等敏感信息做好脱敏处理。
- 向量库中的数据仍然可以被检索,不要将明文密码等高敏数据放入。
- 涉及生产环境数据变更时,先在测试库验证,再上线。
9.5 成本与性能平衡
每次提问都可能涉及两次模型调用(向量化一次 + 生成一次),高频场景下成本不容忽视。
常用优化手法:
- 对用户问题结果做缓存,相同或相似问题直接命中缓存。
- 使用更小的 Embedding 模型完成召回。
- 用本地小模型处理简单问题,复杂问题再调用大模型。
- 对向量库数据定期监控,控制无效数据增长。
9.6 版本管理与更新机制
知识库不是静态的,文档会持续更新。要明确数据更新的触发方式和生效机制:
- 定期重建向量库。
- 支持单文档的增删改,删掉旧版本对应的向量。
- 对向量库版本做记录,保证答案能回溯到当时的文档快照。
10. 总结与下一阶段建议
到这里,你已经完成了一个从零到一的 RAG 项目:梳理了 RAG 的基本原理,搭建了本地向量库,实现了从文档导入到向量检索再到问答生成的全流程,同时还了解了召回优化和工程化的核心思路。
接下来的学习方向,可以从这几条线继续深入:
- 检索质量:系统学习 BM25、向量检索、重排序、混合检索的原理与实现。
- 工程框架:深入 LangChain、LlamaIndex 等框架的源码和设计思想。
- 评测体系:建立自己的 RAG 评测集,控制端到端回答质量。
- 架构演进:了解 GraphRAG、Agent + RAG、多路召回等更复杂的架构形态。
- 部署落地:学习 Docker 部署、模型服务化、知识库权限管理。
在实际项目中,最先要识别清楚的风险点依次是:数据质量、检索结果准确性、大模型幻觉控制、敏感信息安全和成本控制。不要一上来就想做太复杂的架构,先把小闭环跑通,再逐步扩展。
最后整理几个最简单的动手建议:拿一份你工作或学习中高频使用的文档,导入到本文的 demo 项目中;准备 5 到 10 个问题,测试召回效果;调整一次切块参数,对比答案的前后差异。通过这个流程,你会慢慢把 RAG 的每个环节都变成自己能掌控的部分。