logo
05

RAG 系统入门

⏱️ 60分钟

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) 小练习

  1. 用 3 篇文章构建一个本地向量库(Chroma),实现检索问答,输出带引用编号。
  2. 加入 rerank(如 bge-reranker 或 Cohere Rerank),对比回答质量。
  3. 为查询增加“无答案”判定逻辑,避免误答,并记录检索得分与延迟。

📚 相关资源