RAG检索增强生成实战教程:用Python搭建知识库问答系统
大模型很强,但它有一个绕不开的硬伤:它不知道你公司的内部知识。问它“我们公司的报销流程是什么”,它会一本正经地编造一个答案;问它“2026年6月最新的产品定价”,它的训练数据里根本没有。这种“一本正经地胡说八道”就是著名的幻觉(Hallucination)问题。
解决这个问题的主流方案就是 RAG(Retrieval-Augmented Generation,检索增强生成)。它的核心思路非常朴素:在让模型回答之前,先从你的知识库里检索出相关文档,把这些文档塞进提示词,让模型“看着资料答题”。本文将从原理到代码,带你完整搭建一个可用的知识库问答系统。
一、为什么需要 RAG
让模型掌握私有知识,理论上只有两条路:一是把知识“训练”进模型权重(微调),二是把知识“喂”给模型上下文(RAG)。两条路对比下来,RAG 几乎在所有维度上都更适合知识库场景:
| 维度 | 微调 | RAG |
|---|---|---|
| 知识更新 | 需要重新训练,周期长 | 更新文档即可,实时生效 |
| 成本 | 训练 + 推理都很贵 | 只需推理 + 向量检索 |
| 可溯源 | 无法追溯答案来源 | 可标注引用文档 |
| 准确性 | 容易混淆新旧知识 | 基于检索结果,幻觉少 |
所以 2026 年绝大多数企业知识库、客服机器人、文档助手,底层都是 RAG 架构。
二、RAG 的完整流程
一个标准的 RAG 系统分为两个阶段:离线索引和在线问答。
离线索引阶段
- 文档加载:从 PDF、Word、网页、数据库等来源读取原始文本。
- 文档切分(Chunking):把长文档切成几百字的小块,便于检索。
- 向量化(Embedding):用嵌入模型把每个文本块转成高维向量。
- 存入向量库:把向量和原文一起存进向量数据库。
在线问答阶段
- 问题向量化:用同一个嵌入模型把用户问题也转成向量。
- 向量检索:在向量库中找出与问题最相似的 Top-K 个文本块。
- 构造提示词:把检索到的文本块 + 用户问题拼成提示词。
- 生成回答:调用 LLM 基于检索资料生成答案。
理解了这个流程,下面的代码就是把它逐行实现出来。
三、环境准备
# 安装依赖:OpenAI SDK + 向量库 + 文本切分
pip install openai faiss-cpu numpy
# 设置 EnlyAI 统一接入的密钥与地址
export OPENAI_API_KEY="sk-your-enlyai-key"
export OPENAI_BASE_URL="https://enlyai.com/v1"
提示:通过 EnlyAI 统一接入,一个 key 即可调用 GPT-5.5、Claude Opus 4.8、Gemini 3.5 Pro 以及各类 Embedding 模型,无需在多个平台分别注册。
四、文档切分:影响检索质量的关键
切分(Chunking)看似简单,却是 RAG 效果好坏的最大变量。切得太大会引入噪声、检索不准;切得太小会破坏语义完整性。推荐使用递归字符切分:优先按段落、换行、句号切,保证语义边界。
import re
def recursive_split(text, chunk_size=500, chunk_overlap=80):
"""按段落 -> 句子 -> 字符递归切分,保留重叠避免语义断裂"""
# 先按双换行切段落
paragraphs = [p.strip() for p in text.split("\n\n") if p.strip()]
chunks = []
for para in paragraphs:
# 段落过长再按句号切
if len(para) > chunk_size:
sentences = re.split(r'(?<=[。!?.!?])\s+', para)
buf = ""
for s in sentences:
if len(buf) + len(s) <= chunk_size:
buf += s
else:
if buf:
chunks.append(buf)
buf = s
if buf:
chunks.append(buf)
else:
chunks.append(para)
return chunks
docs = """
EnlyAI 是一个 LLM API 聚合平台,支持 OpenAI、Claude、Gemini 等数十种模型。
API base URL 是 https://enlyai.com/v1,完全兼容 OpenAI API 格式。
新用户注册即送免费额度,可在控制台查看用量与账单。
支持流式输出、Function Calling、多模态等高级能力。
"""
chunks = recursive_split(docs)
print(f"切分出 {len(chunks)} 个文本块")
经验值:chunk_size 设 300-500 字符,chunk_overlap 设 10%-20%,能在大多数中文场景下取得平衡。
五、向量化:调用 Embedding API
切分完成后,需要把每个文本块转成向量。这里用 OpenAI SDK 调用 EnlyAI 提供的 Embedding 接口。
from openai import OpenAI
import numpy as np
client = OpenAI(
api_key="sk-your-enlyai-key",
base_url="https://enlyai.com/v1"
)
def get_embeddings(texts, model="text-embedding-3-small"):
"""批量获取文本向量"""
resp = client.embeddings.create(model=model, input=texts)
return [d.embedding for d in resp.data]
vectors = get_embeddings(chunks)
print(f"得到 {len(vectors)} 个向量,维度 {len(vectors[0])}")
Embedding 模型输出的通常是 1536 维或更高维度的浮点向量,语义相近的文本在向量空间里距离也近。这就是后续“相似度检索”的基础。
六、向量检索:用 FAISS 建索引
FAISS 是 Facebook 开源的高效向量检索库,适合中小型知识库(百万级以下)。我们把向量建成索引,就能用余弦相似度快速找最相关的文本块。
import faiss
dim = len(vectors[0])
index = faiss.IndexFlatIP(dim) # 内积索引(向量归一化后等价于余弦相似度)
arr = np.array(vectors, dtype="float32")
faiss.normalize_L2(arr) # 归一化,让内积 = 余弦相似度
index.add(arr)
def retrieve(query, k=3):
"""检索与 query 最相关的 k 个文本块"""
q_vec = np.array(get_embeddings([query]), dtype="float32")
faiss.normalize_L2(q_vec)
scores, ids = index.search(q_vec, k)
return [(chunks[i], float(scores[0][j])) for j, i in enumerate(ids[0])]
# 测试检索
for chunk, score in retrieve("EnlyAI 的 API 地址是什么?"):
print(
总结
RAG 不是什么高深的技术,它的本质就是“检索 + 生成”两步走。但要把效果做到生产可用,关键在于细节:切分策略、Embedding 质量、检索召回率、提示词约束、重排序优化。本文给出的代码已经是一个可运行的最小闭环,你可以在此基础上替换真实文档、接入 Milvus、加入重排序,逐步演进到企业级系统。
而把 RAG 的模型调用统一交给 EnlyAI,你就能把精力集中在检索质量和业务逻辑上,不用在多平台密钥管理上浪费时间。
想快速搭建自己的知识库问答系统?
EnlyAI 提供 OpenAI 兼容的统一接口,一个 key 调用 GPT-5.5、Claude Opus 4.8、Gemini 3.5 Pro 及各类 Embedding 模型,注册即送免费额度,RAG 代码只需改一个 base_url 即可接入。
立即注册 EnlyAI →