真要动手写代码时,卡住的地方往往不是「RAG 是什么」,而是切多长、用哪个嵌入模型、检索回来怎么塞进上下文、答歪了从哪一步查。这篇按实际搭建顺序走一遍,每一步给判断标准和排查方向,顺带说清什么情况下这套做法不适用。
真要动手写代码时,卡住人的地方往往不是「RAG 是什么」。那玩意儿文档看两页就懂了。麻烦在于切多长、用哪个嵌入模型、检索回来的内容怎么塞进上下文、答歪了从哪一步开始查。这篇按实际搭建顺序走一遍,每一步给判断标准和排查方向。默认你已经有 API key、会调 HTTP、能起个本地服务,纯新手先补基础。
先说清楚不适用的情况:如果你的文档总量很小,几十页以内,而且问题基本是「原文里找句子」,那直接全文塞进上下文可能比搭 RAG 更省事,检索环节反而是多余的一层。RAG 值得做的场景是文档会持续增长、问题需要跨多份材料拼答案、或者上下文放不下。另外这套流程默认你接受「检索质量决定上限」这个前提,指望换个生成模型解决答不准,方向就错了。
先定分块,再谈模型
很多人一上来就挑模型、挑向量库,代码写到一半发现召回全是半截句子,返工。分块是地基,先干这个。切法按文档形态走:说明书按标题层级切,代码按函数或类切,表格整块别拆,尤其是带表头的那种,拆开之后模型看到的是一堆没有列名的数字。切完拿几篇人眼读一遍,重点看段尾是不是断在句子中间、标题和正文有没有被拆散。
长度和重叠没有通用值,别去抄别人的参数。正确做法是拿几十条真实问题跑一轮:把每条问题检索到的块打出来,看里面够不够回答问题。不够就说明块太短或切得太碎,答案被切到隔壁块了;反过来块太长、检索回来一大段只有一句有用,就往下压。重叠部分的作用是防止跨块信息丢失,通常给块长的一小截就够,具体比例自己试。
- 元数据里存原文位置和标题路径,后面拼上下文、标出处、跳转原文全靠它
- 每块带一个稳定 ID,调试时能直接定位到哪一段被召回
- 保留原始文档的层级信息,别拍平成纯文本再切
- 表格、代码块单独标记类型,拼上下文时可以区别对待
- 切完抽样人读,别只看块数量和长度分布就收工
嵌入模型和向量库怎么定
嵌入模型决定检索的上限,而且换模型等于整个索引重建,所有文档得重新算一遍向量。所以这事儿别留到上线后再想。小规模阶段先用本地跑得动的嵌入模型把链路验证通,跑通之后再评估要不要换托管服务,到时候你有真实问题集,对比起来才有依据。选型时看的是它在你这类文档上的召回表现,不是看谁的介绍写得漂亮。
向量库反而不用纠结。优先选你已经会运维的那个,数据量不大的时候,链路跑通比挑「最强库」重要得多。数据结构本来就简单:一条记录一个向量加一份元数据。上生产前把重建索引的流程写成脚本跑一遍,确认换嵌入模型时能整体重算而不是手工补数据——这个动作能省掉后面最难受的一次返工。
| 环节 | 容易踩的坑 | 判断信号 |
|---|---|---|
| 嵌入模型 | 上线后才想换 | 索引要整体重建,成本被低估 |
| 向量库 | 追新库、忽略运维 | 出问题不会排查,恢复慢 |
| 索引更新 | 增量更新没设计 | 文档改了检索还是旧内容 |
| 元数据 | 只存向量不存出处 | 答对了也说不清依据在哪 |
顺带提一句成本:嵌入调用和生成调用的费用结构不一样,具体单价到 /price-watch 核对当时的数字,别凭印象做预算。文档量大的时候,嵌入那部分的调用次数往往是倍数的,先算清楚再决定要不要上托管。
检索回来的东西怎么塞给 DeepSeek
这一步是把检索结果拼成提示词。两个动作最关键。一是给每段材料标出处,编号或者带标题路径都行,然后明确要求模型只依据材料回答,材料里没有就说没有。这句话不写,模型会拿自己的先验知识补,答得像模像样但全是编的。二是控制塞进去的段数,宁可少而准。塞太多段,模型的注意力被稀释,反而抓不住重点,还推高 token 消耗。
问题本身最好做一次改写或补全,尤其是多轮对话里指代不明的。用户说「那它支持吗」,直接拿去检索基本查不到东西。让模型结合上一轮把问题补成完整句子再检索,召回质量会明显不一样。这个改写步骤很轻,一次小调用就够,但对多轮场景的提升比调块长还直接。
- 组装上下文按相关度排序取前若干块,每块加编号和出处,块之间用清晰分隔符,别让模型分不清哪段是哪段
- 写约束明确「只依据材料回答」「材料没有就说没有」「引用时标出编号」,三条缺一不可
- 验证拿材料里没有答案的问题测一遍,看模型是老实说没有还是硬编,这一条最能暴露提示词漏洞
答得不对,按这个顺序查
答歪了别急着改提示词。先看召回:把检索到的原文整段打出来,自己读一遍,材料里到底有没有答案。没有,那就是检索或分块的问题,改提示词没用,白费功夫。这一步很多人跳过,直接调生成参数,越调越乱。
材料有答案但答错,分两种。材料对但被截断、顺序乱、或者块之间矛盾,那是上下文组装逻辑的问题,回去看拼接代码。材料清楚、提示词也写了约束,模型还答歪,这才轮到调生成侧:收紧输出格式、加few-shot示例、把约束写得更硬。排查顺序固定下来,每次出问题按这个走,比凭感觉试快得多。
召回没有的东西,生成端怎么调都变不出来。
最后补一句:这套流程跑通之后,建议把每类问题的排查结论记下来,形成自己文档的「病历」。同一份语料反复出的问题往往集中在一两个环节,记两次就能看出规律。要不要上更复杂的检索策略,比如混合检索或者重排,等基础链路稳住、且有明确瓶颈之后再评估,一上来就堆组件反而不好定位问题。
下一步打算选中转站跑嵌入或生成调用的话,先去站内做一轮可信度检测,再对照价格页面算清楚调用量下的实际开销,别只看单次单价。→常见问题
一定要用向量数据库吗?
数据量小的时候,内存里算相似度完全够跑通流程,先验证效果再上库。等文档量、并发或者需要过滤条件到一定程度,再换成专门的向量库,迁移成本比一开始就纠结选哪个库低得多。
DeepSeek 官方接口和本地部署的模型,RAG 写法一样吗?
检索这一段基本一致,差别在生成侧:接口调用是发请求,本地部署要自己管服务和并发。所以先把检索链路做成可替换的模块,换生成端时只改一处。
检索不准和模型能力差怎么区分?
把检索到的原文单独打印出来人眼判断。材料里有正确答案但模型答错,是生成侧问题;材料里压根没有,那是分块或检索的问题,换再强的模型也没用。