DOCAI · 课程 · L3 · 40 分钟
搭一个最小可用的 RAG
三份文档、一次向量化、一次检索、一次回答:不装框架,用最少代码看清 RAG 的每一步在哪出错。
你将学会
- 能说清 RAG 的三个步骤各自负责什么
- 能用手头的文档跑通一次检索增强问答
- 能判断一次答错是检索没找到,还是找到了没用上
为什么先手写一遍
框架会把检索、拼接、调用包在一起。出了问题,你分不清是切分错了、检索没命中,还是模型没用上材料。
手写一遍只要几十行,但每一步都看得见。RAG 是什么先读一遍再动手。
三步
1. 切分与向量化
把文档切成 200–500 字的块,每块调用一次 embedding 接口,存成「向量 + 原文」。
chunks = split(docs, size=300, overlap=50)
vectors = [client.embeddings.create(model=EMB, input=c).data[0].embedding
for c in chunks]
块太大,检索不精准;太小,上下文被切碎。先按段落切,是最省事的起点。
2. 检索
把用户问题也向量化,算余弦相似度,取前 3–5 块。
q = client.embeddings.create(model=EMB, input=question).data[0].embedding
top = sorted(zip(vectors, chunks), key=lambda x: cosine(q, x[0]), reverse=True)[:4]
3. 生成
把命中的原文拼进提示,并要求只依据材料回答。
只依据下面的材料回答;材料里没有的,直接说「材料未提及」。
材料: {top chunks}
答错了,怎么定位
| 现象 | 问题在 | 怎么验证 |
|---|---|---|
| 材料里明明有,答「未提及」 | 检索没命中 | 打印 top 块,看是否包含答案 |
| 检索到了,答案仍不对 | 提示没约束住 | 换成「逐字引用原文再回答」 |
| 答案掺了材料外的内容 | 幻觉 | 见什么是幻觉 |
先看检索结果,再看生成。 九成的「RAG 跑不通」停在第一步。
什么时候该上框架
当你要处理成千上万份文档、需要增量更新和权限过滤时。三份文档的场景,手写更好维护。
自测
- 为什么「材料里有、模型说没有」通常不是模型的问题?
- 块大小调大,检索精度和上下文完整性各会怎么变?
- 你的检索结果里混进了不相关段落,最直接的两个调整是什么?