05
RAG 系统入门
RAG(Retrieval-Augmented Generation)让模型结合你的私有数据回答问题,是 AI 应用的关键组成。
1) 概念与适用场景
- 解决知识时效/私有数据问题:让模型“先检索,再回答”。
- 适用:企业知识库问答、文档助手、客服 FAQ、合规检索、代码库问答。
- 不适用:需要强推理但无资料支撑;实时多模态推理可需额外能力。
2) 端到端流程
用户问题 → 向量化 → 检索 → 构建上下文 → LLM 生成回答 → (可选) 返引用/信心分
核心组件:切分、Embedding、向量库、检索、重排、构建 Prompt、生成、反馈闭环。
3) 数据准备与切分
- 清洗:去除页眉脚、目录/水印、脚注;保持段落语义。
- 切分:递归字符切分(200-400 token 一块,overlap 10-20%),表格/代码可按语义块。
- 元数据:文档名、章节、页码、时间戳、来源类型,用于过滤与引用。
Python 示例(LangChain)
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=300,
chunk_overlap=60
)
chunks = splitter.split_text(text)
4) 向量化(Embedding)
- 选型:OpenAI text-embedding-3-*;兼顾成本与中文/英文效果。
- 维度:默认即可;保持同一模型以免空间不一致。
- 去重:哈希内容或 ID,避免重复写入。
Python 示例
from openai import OpenAI
client = OpenAI()
emb = client.embeddings.create(
model="text-embedding-3-small",
input=chunks
).data
5) 向量数据库选择
- 托管:Pinecone、Weaviate Cloud,省运维,适合生产。
- 自建:Chroma(轻量)、Weaviate、Milvus;注意持久化与备份。
- 选择要点:延迟、扩展性、过滤支持(metadata filter)、多租户隔离。
6) 检索策略
- 相似度检索(k=3-6)是基础。
- 过滤:按文档类型/时间/租户过滤,减少误检。
- 多路检索:BM25 + 向量混合;或 rerank 提升相关性(如 Cohere Rerank / bge-reranker)。
- 去重与重排:合并同源段落,避免冗余。
7) 构建上下文与提示
- 格式:列出检索到的段落,附带编号/来源,提示“仅依据上下文回答,未知则说未知”。
- 长度控制:上下文 tokens 控制在模型上限的 1/2-2/3,留出输出空间。
- 引用:要求回答时带上段落编号,便于追溯。
示例 Prompt 片段
你是知识库助手。仅根据“提供的段落”回答,不要编造。
提供的段落:
[1] 段落内容...
[2] 段落内容...
若未找到答案,回复“未在提供的段落中找到”。
回答请附上引用编号,如 [1][2]。
8) 生成阶段与防幻觉
- System 约束:只根据上下文,不要自创事实。
- JSON/结构化输出:便于前端或下游使用。
- 置信度:可返回检索得分/来源,低分可提示“低置信”。
- 拒答:未知时返回“未找到”,不要臆测。
9) 增量与更新
- 定期/实时同步:监听文件变更,增量更新向量库。
- 版本化:为文档打版本号,检索结果附版本,便于溯源。
- 过期策略:下线旧版本,清理无效向量。
10) 性能与成本
- 控制 chunk 大小与检索 k,减少上下文长度。
- 预摘要:对超长文档先摘要,再检索摘要。
- 缓存:热门问答缓存;对常见查询提前生成答案。
- 硬件/服务:向量库放近模型区域,降低延迟。
11) 评估与对齐
- 离线评估集:准备问答对,检查相关性、准确性、引用正确率。
- 线上反馈:thumbs up/down 收集误检,回填改进检索或切分。
- 统计:命中率、无答案率、平均引用数、P95 延迟、成本。
12) 安全与多租户
- 租户隔离:metadata 带 tenant_id,检索时强过滤。
- 访问控制:按用户/角色过滤可见文档。
- 数据敏感:敏感字段脱敏;日志不存原文。
13) 最小可行示例(简版)
# 1) 切分 + 嵌入 + 入库(示意)
chunks = splitter.split_text(text)
vectors = client.embeddings.create(
model="text-embedding-3-small",
input=chunks
).data
db.add(texts=chunks, embeddings=[v.embedding for v in vectors])
# 2) 查询
query = "退款流程是什么?"
q_emb = client.embeddings.create(model="text-embedding-3-small", input=query).data[0].embedding
docs = db.similarity_search_by_vector(q_emb, k=4)
# 3) 构建提示
context = "\\n".join([f"[{i+1}] {d.page_content}" for i, d in enumerate(docs)])
prompt = f"""你是客服助手,只根据提供的段落回答,不要编造。
提供的段落:
{context}
问题:{query}
如未找到答案,回复“未在提供的段落中找到”。回答附引用编号。"""
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}]
)
print(resp.choices[0].message.content)
14) 小练习
- 用 3 篇文章构建一个本地向量库(Chroma),实现检索问答,输出带引用编号。
- 加入 rerank(如 bge-reranker 或 Cohere Rerank),对比回答质量。
- 为查询增加“无答案”判定逻辑,避免误答,并记录检索得分与延迟。