很多人第一次搭 RAG,卡的不是原理不懂,而是动手时不知道先做哪一步:资料怎么洗、切多大、向量存哪、检索怎么拼进提示词、上线后超时和并发怎么办。这篇按工程顺序给你一条能照着走的路径,每一步都给出判断标准和验收动作,并标出哪些环节容易返工。
第一步:先把范围和数据进料定死
很多人一上手就急着选向量库、调 embedding,结果做到一半发现答非所问。问题往往不在技术栈,而在于开始前没人把「这套系统到底要回答什么」写清楚。范围和数据进料这两件事定不死,后面每一轮调优都是在猜。
- 回答边界
- 明确哪些问题该进这套系统、哪些直接拒答,以及拒答时统一说什么话
- 数据进料
- 原始文档进库前的清洗与格式统一,决定后续检索的上限
- 切片粒度
- 一份文档按什么单位切成小段,直接影响召回的相关性
- 最小验收集
- 一批带标准出处的人工标注问题,用来判断系统是否真的可用
回答边界先写成一句话规则
- 能答的:只覆盖你实际喂进去的文档范围,比如内部制度、产品手册、常见故障处理
- 该拒答的:询问未收录主题、要求预测未来、涉及个人隐私或超范围承诺
- 拒答话术:统一为「这部分资料里没有,建议联系 XX」,别让模型自由发挥编一个
- 边界写完后回头核对:每条边界都能对应到「有文档」或「没文档」两种状态
边界不清最典型的后果,是模型把「文档里没写」当成「可以推理」,输出看着通顺但和事实对不上。所以边界不是写完就锁死,每次新增一类文档,都要重新判断它是否扩大了能答的范围,以及原来那条拒答话术还成不成立。什么情况下不适用:如果你的系统只是做文档检索、不生成答案,那拒答话术可以简化,但「哪些能查、哪些查不到」的界定仍然要写。
进料清洗:脏数据进了库,后面全白搭
- 去页眉页脚与水印把每页重复出现的公司名、页码、密级标记删掉,否则这些词会大量出现在切片里,干扰检索权重
- 还原多栏排版双栏 PDF 直接复制文本常串行,先确认阅读顺序是否正常,串了就按栏切分再合并
- 扫描件先做识别图片型 PDF 必须先过 OCR,识别后抽查几页错字率,错得多的那批宁可人工校一遍也别直接入库
- 统一格式再入库表格、清单、纯文本段落分开处理,别指望一套解析逻辑通吃,格式混乱会让切片质量断崖式下跌
| 内容形态 | 切片单位 | 理由 |
|---|---|---|
| 条款、制度、规范 | 按条目切 | 一条一个完整意思,拆开就丢前提 |
| 说明、教程、叙述 | 按自然段落切 | 段落本身就是作者划好的语义单元 |
| 代码、配置、脚本 | 按函数或配置块切 | 半截代码检索出来没法用 |
| 表格、参数清单 | 整表或按行切 | 行列关系断了,数字就对不上 |
切片粒度没有万能值,固定字数一刀切是新手最常见坑。条款类拆成半条,回答就会漏掉限定条件;说明类切太碎,上下文接不上;代码切到函数中间,模型看到的就是语法错误的片段。判断标准很简单:随机抽几段切片单独读一遍,如果你自己都看不出它在说什么,那检索出来也没用。不适用的情况是,如果你只想做关键词级别的全文检索,粒度可以粗一些,但只要涉及语义匹配就绕不开这件事。
给自己定一个最小验收动作
- 准备一批真实问题数量不用多,先把日常被问得最多的那些整理出来,覆盖不同文档来源,别全挑同一份文件里的
- 人工标注出处每条问题写下答案应该出自哪份文档、大概哪一段,这一步是后面判断检索对不对的唯一依据
- 标出该拒答的样本故意放几条资料里没有的问题,看系统会不会硬答,这类样本比能答对的更能暴露问题
- 跑通后逐条比对看检索命中的文档是否和标注一致,不一致的先查切片和清洗,别急着动模型参数
最后提醒一句顺序:先把边界和进料这两件事做完,再去碰检索和生成环节。顺序反了,你会在错误的数据上反复优化一个本来就不该问的问题。想对照不同中转站在文档解析和调用稳定性上的表现,可以到 中转站导航(/zhongzhuan) 看当时的实测记录,但记住那只是参考,你自己的进料质量才是决定性的。
第二步:入库和检索跑通一条链路
这一步的目标不是把系统做漂亮,而是让「文档进去、问题出来、结果能看见」这条链路完整跑通一次。判断标准很简单:给一条查询,能把召回的原文片段打印在终端里,你能逐条判断「这条相关、那条不相关」。做不到这一点,后面调什么都是在猜。
- 先定向量库别急着比性能,先看三件事:能不能本地一条命令起起来、有没有现成的写入和删除接口、换个库迁移成本高不高。本地起不来就得先解决网络和账号问题,会拖慢整条链路。具体功能差异到 /stacks/vector-dbs 翻目录对照,按你的部署环境筛。
- 把嵌入模型固定下来嵌入这一环只关心「召不召得回」。先用一个能本地跑、你团队能维护的嵌入模型,把文档切块后写进库,跑几十条查询看召回片段在不在前面。别在这一步同时换切块策略和嵌入模型,否则出了问题分不清是谁的锅。
- 只跑纯向量召回第一版就用纯向量相似度检索,不做关键词、不做重排、不做多路融合。链路越短越容易定位问题。等纯向量召回的相关性稳定了,再评估要不要叠关键词召回做混合。
- 把结果打印出来人工看写一个最小脚本,输入问题,输出前几条召回片段的原文和来源。逐条标注相关与不相关,记下不相关的原因是切块太碎、还是问法跟原文差太远。这一步花十分钟,比调一堆参数更有用。
| 环节 | 决定什么 | 怎么验证 |
|---|---|---|
| 向量库 | 数据怎么存、怎么增删改 | 本地能起、能插能删、能按 id 取回 |
| 嵌入模型 | 召回的片段相不相关 | 人工看前几条召回是否命中 |
| 生成模型 | 回答的表达和结构好不好 | 拿同一批召回片段,换模型对比输出 |
| 检索策略 | 先纯向量还是加混合 | 纯向量稳定后再叠加,逐项验证 |
- 向量库选型优先看本地可起、接口齐全、迁移成本,功能对比去 /stacks/vector-dbs 翻目录
- 嵌入模型决定召回,生成模型决定表达,分开评估、分开换
- 第一版只跑纯向量召回,混合检索等链路稳定后再加
- 召回结果必须能打印原文和来源,人工看一眼是最直接的判断
- 每次只改一个变量,改完重新跑同一批查询做对比
第三步:把检索结果拼进提示词并约束输出
检索做完,最后一步就是把这些片段塞进提示词交给模型。这一步看着简单,坑却最多:顺序乱、来源丢、片段贪多,都会让模型答得又飘又贵。下面按能直接照抄的方式讲一遍。
- 固定拼装顺序按「系统约束 → 检索片段 → 用户问题」三段拼。系统约束放最前,写清角色和回答边界;中间是检索片段,每段前面带一个来源标识,比如方括号里写文档名或链接;用户问题放最后,紧挨着让模型回答的位置。顺序固定下来,排查问题时才容易定位是约束没写清还是片段没找对。
- 给片段加分隔和来源片段之间用统一的分隔符隔开,别让两段内容粘成一坨。每段开头标来源,结尾可以标一句这条片段在说什么。这样模型引用时有东西可指,你回查时也能按来源直接翻原文。
- 约束输出行为在系统约束里明确两句话:只依据给定片段回答;片段里找不到就直说找不到,不要编。再要求它在答案里带上来源标记,比如用了哪条片段就标哪条。这个约束不是万能的,遇到模型上下文被撑满或者片段本身互相矛盾时,它还是会答偏,所以别把它当成质量保证。
| 片段档位 | 适用问题类型 | 给几条 |
|---|---|---|
| 少 | 事实型、单点查询 | 按站内 指南中心(/guides) 里当前的推荐档位取偏小值 |
| 中 | 解释型、需要两三条材料对照 | 取中间档位 |
| 多 | 对比型、跨文档汇总 | 取偏大值,但仍要设上限 |
| 不适用 | 问题本身没有对应片段 | 不硬塞,直接走「答不出来」这条路 |
- 片段数量别贪多,塞太多既费 token 又稀释重点,模型反而抓不住主线
- 来源标识要统一格式,别一半写文件名一半写链接,回查时会乱
- 用户问题放最后,别夹在片段中间,容易被前面的材料带跑
- 每次改动提示词都记一笔版本,不然出了偏差没法回溯
- 改完别凭感觉说变好了,用那 20 条问题重跑一遍再对比
常见问题
没有标注数据,怎么判断 RAG 搭得好不好?
先攒一份人为挑的真实问题清单,几十条就够,每条人工写出「应该引用哪份文档」。跑完看两件事:该召回的有没有召回、回答里引用的来源对不对。不用追求指标漂亮,能稳定发现退化就够用。
切片大小到底设多少?
没有通用答案,取决于你的文档形态。做法是先按段落或条目切,然后抽十条问题看召回效果:召回片段太碎、上下文不够就调大,片段里塞了太多无关内容就调小。定完记得写进配置并记录版本,别随手改。
检索和生成哪个更值得先优化?
优先看检索。生成模型再强,如果召回的片段本身不相关,回答也只会一本正经地跑偏。先把召回的前几条人工看一遍,确认相关再回头调提示词和模型,返工成本低很多。