本文目录
一句话:RAG 听起来高大上,核心其实就三步——检索相关资料 → 拼进 prompt → 让模型照着答。把这三步亲手跑通,你就懂了"开卷考试"的本质;想做深,站内有完整 RAG 教程接住。
为什么需要 RAG(一句话回顾)
仅靠模型参数不能可靠覆盖最新或私有知识,生成答案也可能出错。RAG 就是在提问前,先去外部知识库捞出相关资料,塞进上下文,让模型"照着资料答"——把闭卷考试变成开卷考试。
三步拆解
用户问题
↓ ① 检索:把问题和知识库都变成向量,找最相似的几段
相关片段
↓ ② 增强:把片段拼进 prompt("根据以下资料回答…")
拼好的 prompt
↓ ③ 生成:让 LLM 基于资料作答
带依据的回答最小实现(可跑,不依赖向量库)
知识库使用几段演示文本,向量化与聊天分别连接支持相应能力的服务。不能因为一个服务兼容 Chat Completions,就假设它也提供 OpenAI 的 embedding 模型。
先完成第 2 篇的虚拟环境与聊天凭据准备,再安装 numpy,并准备 OpenAI embedding 端点对应的 key:
python -m pip install numpy
export OPENAI_API_KEY="替换为支持该embedding模型的OpenAI API key"下面的 embedding 使用 OpenAI 官方 text-embedding-3-small,聊天使用 LLM_BASE_URL 与 LLM_MODEL。两类请求需要各自有效的服务、模型与凭据;也可以整体替换成经过验证的本地实现。
import os
import numpy as np
from openai import OpenAI
embedding_client = OpenAI(api_key=os.environ["OPENAI_API_KEY"], timeout=30.0, max_retries=2)
chat_client = OpenAI(base_url=os.environ["LLM_BASE_URL"], api_key=os.environ["LLM_API_KEY"], timeout=30.0, max_retries=2)
MODEL = os.environ["LLM_MODEL"]
# 0) 一个迷你知识库
docs = [
"本公司年假:入职满 1 年 5 天,满 3 年 10 天。",
"报销流程:登录 OA 提交发票,主管审批后 3 个工作日到账。",
"考勤:弹性工作,核心时段 10:00-16:00 需在岗。",
]
def embed(texts):
r = embedding_client.embeddings.create(model="text-embedding-3-small", input=texts)
return np.array([d.embedding for d in sorted(r.data, key=lambda item: item.index)])
doc_vecs = embed(docs) # ① 离线:知识库向量化(只算一次)
def cosine(a, b):
denominator = np.linalg.norm(a) * np.linalg.norm(b)
return float(a @ b / denominator) if denominator else 0.0
def answer(question, k=2):
q_vec = embed([question])[0] # ① 在线:问题向量化
sims = [cosine(q_vec, dv) for dv in doc_vecs]
top = np.argsort(sims)[::-1][:k] # 取最相似的 k 段
context = "\n".join(docs[i] for i in top)
prompt = f"根据以下资料回答问题,资料没提到就说不知道。\n\n资料:\n{context}\n\n问题:{question}" # ②
resp = chat_client.chat.completions.create( # ③ 生成
model=MODEL,
messages=[{"role": "user", "content": prompt}],
)
return resp.choices[0].message.content
print(answer("入职两年有几天年假?")) # → 5 天(满 1 年 5 天,满 3 年才 10 天)示例希望模型依据年假条款回答“5 天”。接入 RAG 不保证答案正确;若结果不符,应分别检查召回片段、上下文和生成结果,并保留失败样本。
从"能跑"到"能用",难在哪
这个 30 行版能讲清原理,但真实知识库有 5000+ 文档时,每一步都会暴露问题:
- 切分:文档怎样分成片段?可以用含否定条件的教学样本检查语义边界,避免主句和例外条件被切散。
- 检索:向量与关键词可能在专名、语义相近但条件不同的查询上各有短板。是否混合检索,应在固定题集上对照验证,不能预设某种方案一定更好。
- 重排:召回一堆,怎么把最相关的排到前面 → Cross-Encoder 精排。
- 评估:怎么量化"答得准不准" → 召回率 / QA 准确率。
👉 想做深,直接看站内 RAG 完整项目教程:沿着文档处理、检索、重排、引用与评测完成项目,并查看配套 GitHub 代码;效果数字要由自己的评测得到。
常见坑
- 资料没提到也硬答:prompt 里必须加"没提到就说不知道",否则照样幻觉。
- k 取太大:塞太多片段 → 噪声多 + 烧 token + 注意力稀释。
- 每次都重算知识库向量:知识库向量离线算一次存好,在线只算问题向量。
- 不做切分:整篇文档当一个片段,要么超长、要么粒度太粗,检索不准。
关联面试题
- 「RAG 的完整流程是什么?检索质量怎么优化?」
- 「为什么需要混合检索?向量和 BM25 各自的短板是什么?」
👉 去 题库 搜 RAG / 混合检索 / 重排 / 切分。
训练营延伸
课程项目继续练习多格式入库、检索评测、权限与部署的工程闭环。可以先阅读公开项目教程,再根据自己的基础了解具体课程安排。