很多人照着示例把「检索—判断—再检索」的循环跑通了,一接真数据和真流量就出问题:循环停不下来、检索节点拖垮整体延迟、换模型或换向量库后效果变了却说不清哪里变了。这篇不讲概念,只讲在 LangGraph 里把 agentic RAG 做成能上线的工程活:循环边界、超时与降级、并发限流、日志字段、以及一套小样本评测的搭法。
很多人照着示例把「检索—判断—再检索」的循环跑通了,一接真数据和真流量就出问题:循环停不下来、检索节点拖垮整体延迟、换模型或换向量库之后效果变了却说不清哪里变了。这篇文章不讲概念,只讲在 LangGraph 里把 agentic RAG 做成能上线的工程活。
核心就四件事:图的结构和循环边界怎么写死、节点超时和降级顺序怎么排、并发和成本护栏放在哪一层、日志字段和小样本评测怎么搭。下面按顺序拆,每一条都给出判断标准和可执行动作。
先把图搭对:状态、节点与循环边界
状态是整张图的记忆,不是提示词的垃圾桶。凡是节点之间要传递、又不想重复计算的东西,都放进状态;塞进提示词里绕一圈的结果是每轮都要重新解析,还容易在多轮之后被截断或丢掉。
- 问题原文:用户最初的问题,任何一轮改写都不能覆盖它,否则最后一轮容易跑偏
- 已改写查询列表:每轮改写了什么,方便回看是哪一轮开始偏的
- 已检索片段:片段正文加来源标识,别只存 id,否则生成阶段还要回头查一次
- 已尝试次数与轮数:判断节点和循环边界都靠它,必须显式存放
- 最终答案与引用来源:答案和来源分开存,方便出问题时区分是没召回还是没用上
节点建议拆成五个:查询改写、检索、相关性判断、生成、是否再检索的判断。判断节点只输出结构化结论,不做生成,这样才能单独调、单独测、单独换模型。把判断和生成揉在一个节点里,是最常见的返工来源。
循环必须有硬边界。最大检索轮数、最大总步数、单次执行时间上限,三个里任何一个先到就跳出到生成节点,用兜底提示词收尾,而不是抛异常。什么时候不适用:如果问题本身就是单跳事实查询,别硬套多轮循环,直接一次检索加生成,图更简单也更好排查。
节点级超时、重试与降级顺序
检索超时和生成超时根本不是一个量级,别用一个全局值糊弄过去。先量自己的延迟基线,到 /tools/speed-test 跑一遍,按量出来的分布给两类节点分别设阈值,检索给短一些,生成给长一些。
| 节点类型 | 超时策略 | 重试策略 | 失败后动作 |
|---|---|---|---|
| 查询改写 | 短超时 | 可重试一次 | 沿用原问题继续 |
| 检索 | 短超时 | 可重试一次 | 走降级链 |
| 相关性判断 | 短超时 | 可重试一次 | 默认判定为需要再检索或直接收尾,按业务定 |
| 生成 | 长超时 | 谨慎重试 | 返回已有上下文生成的部分答案并标注缺失 |
重试只对幂等且失败率高的节点做。查询改写、判断节点重试一次通常划算;生成节点重试要谨慎,用户可能因此多等一倍时间,体验上的代价往往比答案质量收益更大。
什么时候不该这么降级:检索源本身不稳定又没有备用源时,重试只会放大超时。这时应该尽早返回部分结果,并在答案里明确标注来源缺失,而不是硬撑到用户自己关掉页面。
并发、额度和成本护栏
一个用户请求内部可能并发多个检索或拆出多个子问题。要设每请求的子任务并发上限,否则一次提问就打出一堆并行调用,把上游额度瞬间抽干,还让同一用户的延迟互相打架。
多用户并发时按上游额度做排队,并且把检索和生成分开限流:检索类通常便宜但延迟敏感,生成类慢且贵,混在同一个队列里会互相拖累,检索被生成堵住是最冤的一种慢。
- 每请求子任务并发上限:按检索源能承受的 QPS 反推,不要拍脑袋定
- 检索与生成分队列:两类资源分开排队,各自设阈值
- 循环轮数上限:这是最直接的成本闸门,每加一轮,token 和调用次数都可能翻倍
- 最坏情况估算:上线前按最大轮数估一遍用量,成本口径自己去 /tools/model-cost-calculator 算当时的数字
- 调试与线上配置分离:两份配置,别直接改线上那份
什么时候可以不设这么严:内部工具、低峰期、单用户调试时可以把上限放宽,但配置要分成「调试」和「线上」两份。最怕的是顺手改了线上那份,第二天高峰才发现循环多跑了两轮。
日志字段与小样本评测
日志不是打完给以后看的,是出问题时唯一能复盘的东西。字段不够,你连「是检索没召回到,还是召回了但生成没用上」都分不清。下面是建议的最低配置。
- 请求 id
- 贯穿一次完整执行,串起所有节点日志
- 轮数与每轮改写查询
- 看是哪一轮开始偏的,判断循环是否在空转
- 每轮命中的片段 id
- 召回情况的直接证据,和最终答案分开留档
- 降级路径
- 走了哪几档降级,触发率高了说明基础检索源在退化
- 各节点耗时
- 定位瓶颈在检索还是生成,不能只看总耗时
- token 用量
- 成本归因,配合轮数看哪一轮最贵
特别强调:把「检索到的东西」和「最终答案」分开留档。否则答案不对时,你无法区分是召回阶段没找到,还是生成阶段没用上。这两类问题的修法完全不同,一个改检索,一个改提示词和上下文组织。
- 搭小评测集准备 20 条左右的问题,覆盖单跳问题、需要两跳的问题、检索不到的问题、诱导幻觉的问题这四类,每条配一个期望的要点答案。
- 固定变量跑基线先不改任何东西跑一遍,记下命中率、轮数分布、降级触发率、平均耗时,作为后续对比的基线。
- 每次改动都重跑改提示词、改轮数上限、换嵌入模型、换向量库之后都跑一遍,对比指标差异,而不是凭肉眼感觉。
- 看轮数分布别只看最终答案对不对,轮数从两轮涨到五轮往往说明检索质量在退化,只是答案勉强还过得去。
常见问题
LangGraph 里做 agentic RAG,循环到底设几轮合适?
没有通用答案,先按你的问题类型定:多数业务问题两到三轮就够,超过之后新增轮次的召回收益通常很低,但成本和延迟是实打实涨的。做法是先设一个偏小的上限,把每轮命中片段记下来,看第几轮开始不再有新内容,再把上限收到那个位置。这个数字要用自己的数据测,别照抄示例里的值。
检索节点超时了,应该直接报错还是继续生成?
优先继续生成,但要在回答里标注来源不足。原因是用户更在意拿到一个能用的回答,而不是一个干净的报错。前提是你要有降级链路:向量检索失败退到关键词检索,再失败就只用已有上下文生成。只有当所有检索路径都不可用、且问题必须依赖私有资料时,才适合明确回绝,并说明是资料侧的问题。
怎么判断是检索不行还是生成不行?
把两者分开看。先看命中的片段里有没有正确答案:有答案但最终回答没提,那是生成或拼装的问题;片段里压根没有答案,那是检索或切分的问题。所以日志里必须同时留「召回片段」和「最终答案」,只留一个就没法定位。每次只改一层,改完跑同一套小评测集,才能知道是这层变好了还是整体被别的因素带偏了。