今年在做企业知识库项目时,团队遇到一个非常典型的问题:资料文件堆积了几十个 GB,业务人员想找一份历史合同的关键条款,得翻半天共享盘;想问“去年 Q3 的客诉处理周期是多少”,运维、研发、销售各说各话。传统的关键词搜索只能做字面匹配,真正理解语义、结合上下文给出答案这件事,普通检索引擎完全做不到。
后来我们把 RAG(Retrieval-Augmented Generation,检索增强生成)落地到了知识库场景里,才算是把“让大模型学会回答自己企业内部问题”这条链路真正走通。现在整理这篇系统教程,从 RAG 的基本原理开始,到环境准备、核心代码、完整实战项目,再到评估方法和常见坑点,一次性覆盖从入门到实战的完整流程。如果你正准备搭建企业级知识库,或者刚接触 AI 大模型应用开发,这篇内容应该能帮你省掉一大段摸索时间。
1. RAG 与知识库基础概念
1.1 什么是 RAG
RAG 的全称是 Retrieval-Augmented Generation,翻译过来就是“检索增强生成”。它的思路并不复杂:在大模型回答用户问题之前,先从外部知识源里检索出与问题相关的文档片段,把这些片段拼进提示词,再让大模型基于这些片段生成答案。
用户提问 → 检索相关文档 → 拼装 Prompt → 大模型生成答案为什么要这么做?因为大模型本身的知识截止到它的训练数据,企业内部文档、私有资料、最新政策、数据库里的业务数据,模型一概不知道。直接问它“某某项目的验收标准是什么”,它大概率会编造一个答案。RAG 的核心价值就是给大模型外挂一个“可检索的知识库”,让它在回答时有所依据,减少幻觉。
1.2 RAG 解决什么问题
在企业场景里,RAG 主要解决三类问题:
- 私有知识问答:企业内部制度、产品文档、FAQ、合同条款、客服话术等,属于敏感且非公开的内容,不能直接拿去训练模型。
- 动态知识更新:大模型训练成本极高,但企业资料每周都在变。RAG 不需要重新训练模型,只需要更新向量数据库里的文档即可。
- 答案可溯源:RAG 可以把命中的原文片段返回给用户,回答“为什么这么说”,比纯模型生成的答案更有说服力。
1.3 RAG 与微调的区别
很多刚接触 AI 应用的同学会问:为什么不直接对大模型做微调?
微调适合改变模型的行为方式、输出格式、专业术语表达;RAG 适合注入实时、私有、频繁更新的知识。实际项目中,两者经常组合使用:用微调调教“说话方式”,用 RAG 解决“知识来源”。
| 维度 | RAG | 微调 |
|---|---|---|
| 知识更新 | 更新向量库即可,成本低 | 需要重新训练,成本高 |
| 可解释性 | 可返回原文出处 | 难以追溯知识来源 |
| 硬件成本 | 推荐配置 GPU,CPU 也可运行部分环节 | 训练需要较高算力 |
| 适用场景 | 私有知识、实时信息、高频更新 | 风格迁移、领域术语固化 |
2. 企业级 RAG 知识库的技术选型
2.1 基座模型选择
基座模型负责最终生成答案。选型时主要考虑三点:生成质量、上下文长度、部署成本。
市面上常见的开源基座模型包括 Qwen(通义千问)、ChatGLM、DeepSeek、Llama 等,闭源 API 则可以选择国内主流大模型平台的接口。如果企业数据不能出内网,优先考虑本地部署开源模型;如果允许使用云端 API,开发效率会更高。本文示例以 API 调用方式为主,本地化部署的思路是通用的。
2.2 Embedding 模型选择
Embedding 模型负责把文本转换为向量,是知识库检索质量的基础。选型时重点看两个指标:
- 语义理解能力:能否理解同义词、指代、上下文。
- 向量维度:维度越高通常表达越细,但存储和计算成本也越高。
常见的开源 Embedding 模型有 BGE(BAAI General Embedding)、M3E、text2vec 等,国内也有很多商业化 API 可选。需要注意:Embedding 模型必须与检索链路里的其他组件兼容,比如向量数据库支持的向量维度、距离算法等。
2.3 向量数据库选择
向量数据库用于存储文档向量并执行相似度检索。主流选择包括:
- Milvus:功能全面,支持分布式,适合生产环境。
- Weaviate:部署简单,适合中小团队。
- Qdrant:Rust 编写,性能较好,API 友好。
- FAISS:不是独立数据库,而是向量检索库,适合单机原型验证。
- pgvector:PostgreSQL 的扩展,适合已有 PostgreSQL 体系的企业。
选型建议:原型验证阶段用 FAISS 或 Chroma 起步最快;生产阶段视数据量选择 Milvus 或 Qdrant。如果企业已经有 PostgreSQL,可以考虑 pgvector 减少组件数量。
2.4 RAG 工具链选型:自研还是低代码
如果只是想验证想法,可以先用 Dify、FastGPT、AnythingLLM 这类开源平台快速搭建。它们已经封装好了文档解析、切片、向量化、检索、Prompt 编排等流程,有的甚至支持可视化工作流编排,适合业务人员直接使用。 如果要做深度定制,比如修改检索策略、接入企业内部权限系统、自定义重排逻辑,建议基于 LangChain 或 LlamaIndex 自研核心链路。本文后面的实战部分以 LangChain 为例,讲解自研方式。
3. 环境准备与项目结构
3.1 运行环境说明
本文示例以 Python 3.10+ 为基础环境,操作系统可以是 Windows、macOS 或 Linux。示例环境如下,读者可根据自己项目实际情况调整:
- Python 3.10+
- LangChain(以当前主流 0.2.x / 0.3.x 版本为例)
- FAISS 或任意向量数据库
- OpenAI 兼容 API 或本地部署模型接口
如果使用云端 API,需要提前申请 API Key;如果本地部署模型,建议使用支持 OpenAI 接口格式的推理服务。
3.2 安装依赖
pip install langchain langchain-openai langchain-community faiss-cpu pypdf unstructured openpyxl如果使用国产模型 API,可以再安装对应的 SDK 包,比如:
pip install openai dashscope然后通过环境变量配置 API Key:
export OPENAI_API_KEY="你的_KEY"或者使用.env文件管理配置:
OPENAI_API_KEY=你的_KEY3.3 建议的项目目录结构
rag_knowledge_base/ ├── data/ # 存放待入库的原始文档 │ ├── 企业制度.pdf │ ├── 产品手册.md │ └── 常见问题.xlsx ├── vector_store/ # 向量数据库文件(本地模式) ├── src/ │ ├── document_loader.py # 文档加载 │ ├── text_splitter.py # 文本切分 │ ├── embedding.py # 向量化 │ ├── retriever.py # 检索 │ └── chat.py # 问答应用 ├── app.py # 入口文件 ├── requirements.txt └── .env4. RAG 核心原理与代码拆解
4.1 文档加载
文档加载做的事情是:把 PDF、Word、Markdown、Excel 等不同格式的文件解析成纯文本。不同类型文件需要不同的加载器,LangChain 提供了统一接口:
# 文件路径:src/document_loader.py from langchain_community.document_loaders import PyPDFLoader, UnstructuredMarkdownLoader from langchain_community.document_loaders import Docx2txtLoader from langchain_community.document_loaders import UnstructuredExcelLoader def load_document(file_path: str): if file_path.endswith(".pdf"): loader = PyPDFLoader(file_path) elif file_path.endswith(".md"): loader = UnstructuredMarkdownLoader(file_path) elif file_path.endswith(".docx"): loader = Docx2txtLoader(file_path) elif file_path.endswith(".xlsx"): loader = UnstructuredExcelLoader(file_path) else: raise ValueError(f"不支持的文件类型: {file_path}") return loader.load()这里有一个需要注意的地方:PDF 解析质量直接决定后续检索效果。扫描版 PDF(图片型 PDF)需要走 OCR 流程,否则抽出来的是一堆空字符串或乱码。
4.2 文本切分策略
加载出来的文档可能是一整篇长文本,直接向量化会有两个问题:
- 超出 Embedding 模型的最大输入长度。
- 整篇文档只生成一个向量,检索时无法定位到具体段落。
因此需要把文档切成小块,常见的切分方式有三种:
| 方式 | 特点 | 适用场景 |
|---|---|---|
| 固定长度切分 | 按字符数切,简单粗暴 | 快速原型 |
| 递归字符切分 | 按段落、句子、标点逐级切分 | 大部分通用场景 |
| 结构感知切分 | 按 Markdown 标题、HTML 标签、代码块切分 | 文档结构明显的场景 |
LangChain 中推荐使用RecursiveCharacterTextSplitter:
# 文件路径:src/text_splitter.py from langchain_text_splitters import RecursiveCharacterTextSplitter def split_documents(documents): text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", ".", "!", "?", ";", ";", " ", ""] ) return text_splitter.split_documents(documents)参数说明:
chunk_size:每个块的最大字符数。设置太小会导致语义不完整,太大会导致检索粒度粗糙。chunk_overlap:相邻块之间的重叠字符数。目的是避免句子被拦腰截断导致语义缺失。separators:切分优先级,先尝试按段落切,再按句子切,最后按字符切。
实际项目中,切分参数要根据文档类型反复调试。代码、表格、对话类文档的切片策略完全不同。
4.3 向量化与入库
文本切分完毕后,需要调用 Embedding 模型为每个块生成向量,然后写入向量数据库。
# 文件路径:src/embedding.py from langchain_openai import OpenAIEmbeddings def get_embedding_model(api_key: str, base_url: str = None): return OpenAIEmbeddings( model="text-embedding-ada-002", api_key=api_key, base_url=base_url )如果你使用的是国产模型的 OpenAI 兼容接口,只需要把base_url换成对应服务的地址即可。
向量入库 Demo:
from langchain_community.vectorstores import FAISS def build_vector_store(docs, embedding_model, save_path="./vector_store"): vector_store = FAISS.from_documents(docs, embedding_model) vector_store.save_local(save_path) return vector_store这里的核心知识是:向量入库后,数据库存储了两类信息——原始文本内容和文本对应的向量。检索时,系统把你的问题转成向量,然后在库里找“角度最接近”的向量,返回对应的原始文本。
4.4 检索策略
最基础的检索方式是向量相似度检索。但实际业务中,关键词精确匹配同样重要。比如产品型号“A100”,向量可能把它理解为“A 级 100 分”,这时候需要 BM25 这类稀疏检索来兜底。
混合检索是生产级 RAG 的常用方案:
# 文件路径:src/retriever.py from langchain.retrievers import EnsembleRetriever from langchain_community.retrievers import BM25Retriever from langchain_community.vectorstores import FAISS def build_hybrid_retriever(vector_store, docs): # 稠密检索:向量召回 vector_retriever = vector_store.as_retriever( search_type="similarity", search_kwargs={"k": 8} ) # 稀疏检索:关键词召回 keyword_retriever = BM25Retriever.from_documents(docs) keyword_retriever.k = 8 # 集成检索,按比例加权融合结果 ensemble_retriever = EnsembleRetriever( retrievers=[vector_retriever, keyword_retriever], weights=[0.7, 0.3] ) return ensemble_retrieverweights表示两种检索结果的融合权重。语义理解能力强的文档可以调高向量权重;代码、型号、编号类内容较多的场景,关键词权重需要加大。
4.5 重排(Rerank)
检索阶段拿到的 TopK 结果,相关性并不是严格排序的。这时候可以加一层重排模型(Rerank),对候选文档做更精细的相关性打分,重新排序。
# 以调用重排 API 为例,思路如下: from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import CrossEncoderReranker from langchain_community.cross_encoders import HuggingFaceCrossEncoder reranker = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-v2-m3") compressor = CrossEncoderReranker(model=reranker, top_n=4) compression_retriever = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=base_retriever )注意:top_n是重排后保留的文档数。重排模型会把召回的相关片段重新打分,去掉排在后面但不太相关的噪声。重排能够明显提升 RAG 答案质量,是生产环境里比较推荐的一环。
4.6 生成答案
检索到相关文档后,最关键的一步是拼装 Prompt。Prompt 写得好不好,直接决定答案是“照着文档念”还是“东拼西凑瞎编”。
# 文件路径:src/chat.py from langchain_openai import ChatOpenAI from langchain.prompts import ChatPromptTemplate SYSTEM_TEMPLATE = """你是一个企业知识库助手。 请根据以下检索到的文档片段,回答用户的问题。 要求: 1. 如果检索片段中没有相关信息,请明确回答“知识库中未找到相关信息”,不要编造。 2. 回答时尽量引用原文内容,保持客观准确。 3. 如果涉及多个片段,需要综合判断,注明信息来源。 检索片段: {context} 用户问题:{question} """ prompt = ChatPromptTemplate.from_template(SYSTEM_TEMPLATE) def create_answer_chain(retriever, model): chain = ( { "context": retriever, "question": lambda x: x["question"] } | prompt | model ) return chain生成阶段的重点是“约束”:告诉模型只根据检索片段回答,不要自由发挥;不知道就说不知道。这样可以大幅降低幻觉。
5. 完整实战:搭建一个企业知识库问答系统
下面我们把上面的模块组合起来,完成一个最小可运行的 RAG 知识库问答系统。
5.1 初始化参数与目录
# 文件路径:config.py import os from dotenv import load_dotenv load_dotenv() OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL", "") EMBEDDING_MODEL = "text-embedding-ada-002" # 按实际模型调整 CHAT_MODEL = "gpt-4o-mini" # 按实际模型调整如果你的 API Key 或模型名称与示例不同,直接在.env里修改即可。
5.2 创建向量库
# 文件路径:create_vector_store.py from src.document_loader import load_document from src.text_splitter import split_documents from src.embedding import get_embedding_model from langchain_community.vectorstores import FAISS from config import OPENAI_API_KEY, OPENAI_BASE_URL def create_vector_store(doc_paths: list, save_path: str): embedding_model = get_embedding_model( api_key=OPENAI_API_KEY, base_url=OPENAI_BASE_URL ) all_docs = [] for doc_path in doc_paths: docs = load_document(doc_path) all_docs.extend(docs) split_docs = split_documents(all_docs) vector_store = FAISS.from_documents(split_docs, embedding_model) vector_store.save_local(save_path) print(f"向量库已创建,共 {len(split_docs)} 个片段,保存至 {save_path}") if __name__ == "__main__": docs = ["./data/企业制度.pdf", "./data/产品手册.md"] create_vector_store(docs, "./vector_store")5.3 加载已有向量库并检索问答
# 文件路径:app.py from src.embedding import get_embedding_model from src.retriever import build_hybrid_retriever from src.chat import create_answer_chain from langchain_community.vectorstores import FAISS from langchain_openai import ChatOpenAI from config import OPENAI_API_KEY, OPENAI_BASE_URL, CHAT_MODEL def main(): embedding_model = get_embedding_model( api_key=OPENAI_API_KEY, base_url=OPENAI_BASE_URL ) vector_store = FAISS.load_local( "./vector_store", embedding_model, allow_dangerous_deserialization=True ) retriever = build_hybrid_retriever(vector_store, docs=[]) llm = ChatOpenAI( model=CHAT_MODEL, api_key=OPENAI_API_KEY, base_url=OPENAI_BASE_URL, temperature=0.2 ) chain = create_answer_chain(retriever, llm) while True: question = input("请输入问题(输入 exit 退出):") if question.lower() == "exit": break result = chain.invoke({"question": question}) print("\n=== 回答 ===\n") print(result.content) print("\n") if __name__ == "__main__": main()关于allow_dangerous_deserialization=True这里需要强调一下:这个参数只建议在加载自己生成的本地向量库时使用,因为 FAISS 的本地文件反序列化存在安全风险。生产环境更推荐使用 Milvus、Qdrant 这类专业向量数据库。
5.4 运行与验证
python create_vector_store.py python app.py预期效果如下:
请输入问题(输入 exit 退出):公司的年假制度是怎样的? === 回答 === 根据《企业制度.pdf》中的规定,员工累计工作满1年不满10年的,年休假为5天;满10年不满20年的,年休假为10天;满20年的,年休假为15天。5.5 结果说明
至此,一个最小的企业知识库问答系统已经跑通了。它实现了以下能力:
- 把 PDF、Markdown 等文档切分并向量化。
- 用户提问时,混合检索召回相关片段。
- 结合检索片段和用户问题,由大模型生成有依据的答案。
6. 知识库质量评估:RAG 测评怎么做
“看起来能用”和“真正能用”之间差了评估。RAG 上线之前,必须有一套测评标准。
6.1 核心评估指标
| 指标 | 含义 | 评估方式 |
|---|---|---|
| 召回率 | 相关文档是否被检索出来 | 人工标注 + 计算命中率 |
| 精确率 | 检索出的文档是否都相关 | 人工标注 + 计算准确率 |
| 忠实度 | 答案是否忠于检索内容,不编造 | 人工比对或 LLM 打分 |
| 答案相关性 | 答案是否对应用户问题 | 人工比对或 LLM 打分 |
| 端到端成功率 | 用户问题能否得到满意答案 | 人工验收 |
6.2 最小可行评估脚本
# 文件路径:evaluate.py # 简单的人工评估框架:准备测试集,批量跑问答,记录答案与检索片段 test_cases = [ {"question": "公司的年假制度是怎样的?", "expected_keyword": "年假"}, {"question": "产品支持哪些部署方式?", "expected_keyword": "部署"}, ] def evaluate(chain, test_cases): for case in test_cases: result = chain.invoke({"question": case["question"]}) answer = result.content passed = case["expected_keyword"] in answer print(f"问题:{case['question']}") print(f"回答:{answer[:100]}") print(f"包含期望关键词:{'是' if passed else '否'}") print("-" * 50) if __name__ == "__main__": # 使用上一节构建的 chain 作为参数传入 pass评估样本的构建是个长期工程。建议从真实用户问题中抽样,按业务模块分类,每个模块准备 20 到 50 条测试问法,覆盖不同表达方式。后续每次优化检索策略或更换模型,都跑一遍评估集,用数据说话,而不是凭感觉。
7. 常见问题与排查思路
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 回答内容与知识库无关 | 检索没有召回正确内容 | 检查切片大小、Embedding 模型、检索 TopK 数量 |
| 回答总是说“没有相关信息” | 切片后语义不完整 | 加大 chunk_size,或改用结构感知切分 |
| 同一个问题不同文档互相矛盾 | 知识库内容冲突 | 先做文档清洗,明确优先级规则 |
| PDF 导入后检索效果差 | PDF 是扫描版或者表格复杂 | 接 OCR 流程,或转成文本后检查 |
| 回答内容重复、啰嗦 | Prompt 约束不足 | 在 Prompt 中限制答案长度和格式 |
| 检索速度慢 | 向量数据库没建索引 | 使用 HNSW、IVF 等近似最近邻索引 |
| 本地部署模型显存不足 | 基座模型过大 | 换小参数模型,或使用量化版本 |
| 拼接的 Prompt 太长 | 召回的文档片段过多 | 降低 TopK,或增加重排过滤 |
另外需要专门提一个常见问题:Dify 等平台升级后无法保存知识库,或修改知识库时报 Internal Server Error。这个问题很多时候是向量数据库表结构变更、或者 API Key 权限变化导致的。排查时先看后端日志,确认是数据库异常还是鉴权异常;如果是数据库异常,优先检查向量化服务的版本兼容性。
8. 最佳实践与工程建议
8.1 文档切片不是随便切
生产环境里,切片策略直接影响检索效果。比较好的做法是:先按文档结构切,比如一级标题、二级标题、表格、列表,然后再在结构块内部调用RecursiveCharacterTextSplitter。这样可以保留章节上下文,又不会让单个片段过长。代码、JSON、表格类内容,建议单独走专用切分逻辑。
8.2 检索质量比模型能力更重要
很多团队花大量时间调 Prompt,忽视检索链路,结果答案总是“一本正经地胡说八道”。检索不到正确的上下文,再强的模型也答不对。建议按优先顺序投入:
- 数据清洗:去重、去噪、纠错、统一格式。
- 切片与检索:调参数、加混合检索、加重排。
- 基座模型:选更强模型或优化 Prompt。
- 微调:最后才考虑。
8.3 生产环境必须控制权限和数据边界
企业知识库涉及大量内部资料。在把 RAG 系统放到生产环境之前,至少要确认:
- 文档入库前做权限标记,不同角色的用户只能检索自己有权限访问的文档。
- 向量数据库和模型服务均属于核心资产,建议放在内网或私有云。
- 对大模型 API 的调用需要做审计,记录每个问题的检索片段和模型输出,方便排查问题。
- 涉及删除、重建向量库等操作,先在测试环境验证。
8.4 从 RAG 走向 Agentic RAG
基础 RAG 的流程是固定的:用户问 → 查一次 → 生成。但真实业务经常会遇到复杂问题,比如“对比 A 产品和 B 产品的售后政策差异”,这时候单次检索很难覆盖所有维度。Agentic RAG(智能体 RAG)把检索动作交给 Agent 编排,模型可以自主决定是否需要多次检索、调用工具、拆分任务。它是 RAG 演进的重要方向,建议基础 RAG 跑通后再深入研究。
9. 总结与学习路线
到这里,我们已经完成了一个企业级 RAG 知识库的最小闭环:文档加载、文本切分、向量化、向量存储、混合检索、重排、Prompt 生成、结果评估。核心要点可以概括为三句话:
- 知识库的质量决定 RAG 的上限,数据清洗和切片要花足够时间。
- 检索链路(Embedding、混合检索、重排)的优化优先级高于模型调参。
- 评估不是上线之后才做的事,而是一个持续迭代的指标系统。
接下来可以按这个顺序继续进阶:
- 把 FAISS 替换成 Milvus 或 Qdrant,体验生产级向量检索。
- 给知识库增加在线文档更新机制,实现增量入库。
- 接入真实的权限系统,按用户角色过滤检索结果。
- 研究 Agentic RAG,把简单问答升级成多步任务处理。
实操是最好的学习方式。建议先拿十几份自己的业务文档跑通上面的代码,再逐步增加数据量和复杂度。等你把检索链路调顺、评估体系搭好,企业知识库才真正变成一个可靠的基础设施。