很多团队的 RAG 演示都能跑,一接真实流量就原形毕露:检索慢把整体拖到超时、并发一上来就限流、答案偶发跑偏却查不到是哪一环出的问题。这篇不讲原理,只给工程落地清单——每个环节该设什么、什么时候不适用、出了问题按什么顺序查。
很多团队的 RAG 演示都能跑通,一接真实流量就原形毕露:检索慢把整体拖到超时、并发一上来就限流、答案偶发跑偏却查不到是哪一环出的问题。原因通常不在模型,而在于链路没有被切段、超时没有分档、日志不足以还原一次请求。
这篇不讲原理,只给工程落地清单:每个环节该设什么、什么时候不适用、出了问题按什么顺序查。目标是让系统从「能演示」变成「敢放量」,同时保留随时把问题定位到具体环节的能力。想先确认模型本身的延迟水位,可以到 /tools/speed-test 核对当时的数字,把它当作对比基线。
先把链路切段,再谈优化
优化前先能看见。把一次问答拆成清洗入库、检索召回、重排、拼装生成四段,每段单独打点计时并落库。没有分段耗时,你只能知道「整体慢」,却不知道是向量库慢、embedding 接口慢还是模型慢,改起来就是碰运气。
| 链路分段 | 主要耗时来源 | 上线前该确认的事 |
|---|---|---|
| 清洗入库 | 解析、分块、embedding 批处理 | 是离线任务还是阻塞在线请求 |
| 检索召回 | 向量库查询、网络往返 | 超时值与返回条数上限是否明确 |
| 重排 | 重排模型或规则打分 | 失败时是否可跳过而不是直接报错 |
| 拼装生成 | 上下文长度、模型输出长度 | 上下文截断策略是否固定且可复现 |
- 先量基线:用 /tools/speed-test 把模型侧的延迟单独测出来,把「模型慢」和「检索慢」分清楚,避免误判。
- 给每段定超时预算:检索段一般不该比生成段还长,哪段先超预算就优先查哪段。
- 分段计时用一个统一的埋点字段收口,不要把耗时散落在各服务的日志里,否则复盘时要手动拼时间线。
- 不适用情况:单文档、低并发的小工具,全链路一个总超时值就够了,拆太细反而增加维护成本。
超时、并发与降级怎么设
超时不要只设一个总开关。总超时太短会把正常的长回答掐死,太长又会让请求卡住却不报错。正确做法是由内到外分三档逐层放宽:连接超时、单次检索超时、整体请求超时。只要有一档没设,就会出现「页面一直转圈但日志里没有错误」这种最难查的状态。
- 设定三档超时连接、单次检索、整体请求各给一个值,内层必须小于外层;上线后观察是否有请求正好卡在某一档,那就是该放宽或该优化的信号。
- 按下游最紧的一环定并发并发上限通常先卡在向量库或 embedding 接口上。用分批加队列的方式限流,不要一上来就堆协程,否则只是把压力原样转给下游。
- 写清降级触发条件召回为空、重排超时、生成超时这三种情况各返回什么要提前定。宁可显式返回「暂时答不了」,也不要让模型在没有依据的情况下硬编一段话。
- 验证降级路径主动把向量库或模型接口断开,确认前端拿到的是可解释的提示,而不是空白或超时报错页。
不适用情况:对延迟不敏感的内部工具可以不做复杂降级,但失败必须显式暴露,不能让接口静默返回空答案——那等于把问题推给了下一个排查的人。
日志要能还原一次请求
日志的目的不是记录系统跑了,而是出事时能复现那一次回答。判断标准很简单:给你一个请求 ID,能不能还原出当时检索了哪些文档、分数多少、各段耗时多久、最后是否走了降级。缺任何一项,复盘都会退化成猜测。
- 请求 ID
- 贯穿全链路,前端报错时能一把捞出所有相关日志
- 命中文档与分数
- 判断是没召回、召回了没排上,还是排上了没被用好
- 分段耗时
- 定位瓶颈落在检索还是生成,避免凭感觉换模型
- 降级标记
- 区分「模型答得差」和「系统根本没正常答」两类问题
- 检索结果与模型输出成对存下来,这是后续做评测集的原始素材。
- 日志做脱敏,不要整段原文入库;本地处理场景可以参考 /tools/card-batch 这类批量脱敏的思路。
- 排查顺序固定:先看有没有命中,再看分数排位,最后看提示词拼装,不要一上来就怀疑模型。
- 日志保留周期与知识库更新节奏对齐,否则旧日志对不上新文档,复盘会失真。
小规模评测,别靠肉眼
肉眼抽查最大的问题是不可比:同一批问题,今天觉得答得不错,明天换了提示词又觉得差不多,其实已经变差。做法是固定一小批问题当基准,覆盖「能答」「该说不知道」「需要多文档拼接」三类,每次变更都跑一遍。
| 评测项 | 看什么 | 什么时候要重跑 |
|---|---|---|
| 命中率 | 正确文档有没有被召回进上下文 | 换 embedding、改分块、调 top-k |
| 忠实度 | 回答有没有超出检索到的内容 | 改提示词、换生成模型 |
| 拒答表现 | 该说不知道的时候有没有说实话 | 知识库删改或新增之后 |
命中率和忠实度要分开看:命中率低是检索的问题,忠实度高但命中率低,说明模型没瞎编,只是没拿到对的材料。两个指标混在一起看,就分不清该调检索还是调生成。
- 换模型、换分块策略、调 top-k 都算变更,变更前后各跑一次并留档,不做对比等于没测。
- 评测集规模不用大,但要稳定;问题描述写具体,避免同一个问题每次理解都不一样。
- 不适用情况:知识库每周大改的项目,评测集要定期换血,否则分数会虚高,给你一种变好了的错觉。
- 拿不准某个环节是不是瓶颈时,先回到 /rankings 和 /price-watch 看当时可选的模型与接口水位,再决定是换件还是改架构。
常见问题
RAG 应用里检索和生成的超时该怎么分开设?
内层检索设短一点,外层生成设长一点,整体超时放在最外层兜底。检索一旦超预算就先返回空结果走降级,别让整条链路一起挂着。改完记得用真实并发压一次,单请求跑通不代表并发下也稳。
并发一提高就报限流,是加机器还是改调用方式?
先确认限在谁身上:向量库、embedding 接口还是生成模型。多数情况是下游按 QPS 或并发数卡的,加机器没用。做法是分批加队列、控制同时在飞的请求数,再配合指数退避重试,具体阈值以你实际套餐为准。
怎么判断 RAG 答错是检索的问题还是生成的问题?
看日志里命中的文档片段。命中了正确片段但答案跑偏,是拼装和提示词的问题;压根没命中,就是分块、embedding 或 top-k 的问题。两类改动方向完全不同,不分清容易白改。