Agentic RAG: Turning Retrieval Into a Tool So the Model Decides Whether to Search, How Many Times, and Whether to Start Over
A single fixed retrieval pass can't handle multi-hop questions or retrieval failures. Today wrap retrieval as a tool inside an agent loop, implement self-reflection and result correction, handle multi-hop queries, and set a call-count and budget cap on the loop so it doesn't keep searching forever.
今日目标
- 能把检索包装成工具接进 Agent 循环,并设计好工具描述让模型知道什么时候该用它
- 能实现检索结果的自我评估与纠错:发现材料不相关时改写查询重来,并设置重试上限
- 能说清 Agentic 检索相对固定流程多花了多少延迟与调用,以及什么场景下这笔钱不该花
前十一天你造的是一条流水线:问题进去,检索、重排、组装、生成,出来一个答案。今天把这条直线掰成一个圈。读完回到顶部把三条目标勾掉。
小白版讲解
查资料的人,和查资料的流程
图书馆里有两种找资料的方式。
第一种是流程:你把题目交给前台,前台按目录检索,取回五本书放在你桌上,转身走了。这五本对不对、够不够,流程不管。
第二种是人:一个熟练的研究员接过题目,先按最直白的关键词查一轮,翻开发现讲的是隔壁那个话题,于是换个说法再查;再翻,书里写着「此项须经平台组组长批准」,而题目问的是「那个组长叫什么名字」——书里没有名字,但给了线索,于是他拿着「平台组组长」五个字回到目录前,查第二轮。
前十一天你造的是第一种。它在大多数问题上表现得很好,因为大多数问题一次就能查对。但它有一个结构性缺陷:它没有「再试一次」这个动作。查错了它不知道,查不全它也不知道。
Agentic 检索补的就是这个缺口。名字唬人,剥开只有一句话:把检索从「流水线上的一道工序」变成「模型可以反复调用的一个工具」,再让模型每次调用之后自己评一句「够了吗」。够了就停,不够就换个查法再来一轮。
代价也直白:研究员比前台贵,多查一轮就多一次检索、多一次模型调用、多一份延迟。所以今天难的不是把循环写出来——那部分代码不到一百行——而是三个问题:说明书怎么写,模型才不会该查的时候不查、不该查的时候乱查?「够了吗」这一句怎么判才不是模型在自我感觉良好?以及最要紧的:怎么保证它不会一直查下去?
把检索做成工具:说明书写给模型看,不是写给同事看
工具定义有两部分:参数结构(模型要填什么)和自然语言描述(模型据此决定要不要调用)。前者是接口签名,后者是提示词——而绝大多数人只把前者当回事。
无数项目里的写法是:description 写成「在知识库里搜索」。六个字,语法没错,模型也确实能调起来,然后带来两类固定事故——该用的时候不用(用户问「年假能结转几天」,模型当常识编了个数字)、不该用的时候乱用(用户说「你好」也查一次库)。
一段合格的描述要回答四件事,缺一件对应一类错误行为:
| 写清楚什么 | 不写会怎样 |
|---|---|
| 库里有什么、没有什么 | 模型拿它当搜索引擎,问天气也去查 |
| 什么时候必须用 | 涉及公司制度的问题被模型凭记忆编答案 |
| 什么时候不要用 | 闲聊、翻译、算术都触发一次无谓检索 |
| 查询串写成什么形状 | 整句问话直接当查询,疑问词把检索带偏 |
最后一条最容易漏也最值钱。模型默认把用户原话原样塞进 query,于是「这个人叫什么名字」里的「什么名字」也进了检索,纯粹是噪声。在描述里写一句「写成关键词短语,不要带疑问词」,比在检索侧做十种查询清洗都管用。
// 工具描述是写给模型看的提示词,不是写给同事看的注释
export const SEARCH_TOOL = {
name: 'search_knowledge_base',
description: [
'在云梯科技的内部知识库里检索片段。库里有 30 篇内部文档,涵盖产品功能说明、',
'人事制度、工程规范与客服工单规则;不含公开互联网内容,不含实时数据。',
'',
'什么时候用:问题涉及本公司的制度、流程、配置项、职责分工、具体数值时,',
'必须先检索,不要凭记忆回答。',
'什么时候不用:闲聊、翻译、写代码、做算术,以及用户已经在对话里给出全部',
'事实的追问——这些直接回答,检索只会引入噪声。',
'',
'query 写成检索用的关键词短语(例如「主备切换 审批」),不要整句照抄',
'用户的问话,也不要带疑问词。',
'一次只查一件事:需要两个事实时分两次调用,用第一次的结果决定第二次查什么。',
'department 只在你明确知道答案属于哪个部门时才填,填错会把正确答案挡在外面。',
].join('\n'),
input_schema: {
type: 'object',
properties: {
query: { type: 'string', description: '检索关键词短语,2 到 8 个词' },
department: {
type: 'string',
enum: ['product', 'hr', 'eng', 'support'],
description: '可选。只在确定答案属于该部门时才填',
},
},
required: ['query'],
},
}# 工具描述是写给模型看的提示词,不是写给同事看的注释
SEARCH_TOOL = {
"name": "search_knowledge_base",
"description": "\n".join(
[
"在云梯科技的内部知识库里检索片段。库里有 30 篇内部文档,涵盖产品功能说明、",
"人事制度、工程规范与客服工单规则;不含公开互联网内容,不含实时数据。",
"",
"什么时候用:问题涉及本公司的制度、流程、配置项、职责分工、具体数值时,",
"必须先检索,不要凭记忆回答。",
"什么时候不用:闲聊、翻译、写代码、做算术,以及用户已经在对话里给出全部",
"事实的追问——这些直接回答,检索只会引入噪声。",
"",
"query 写成检索用的关键词短语(例如「主备切换 审批」),不要整句照抄",
"用户的问话,也不要带疑问词。",
"一次只查一件事:需要两个事实时分两次调用,用第一次的结果决定第二次查什么。",
"department 只在你明确知道答案属于哪个部门时才填,填错会把正确答案挡在外面。",
]
),
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "检索关键词短语,2 到 8 个词"},
"department": {
"type": "string",
"enum": ["product", "hr", "eng", "support"],
"description": "可选。只在确定答案属于该部门时才填",
},
},
"required": ["query"],
},
}工具本身还是 D9 那套混合检索:两路召回、倒数排名融合、重排前二十条。今天一个参数都不改——检索器一改,两组数字就不可比了。
自我反思:材料到手先评一句「够了吗」
循环的第二个零件是自评,它只回答一个问题:手上这堆材料,够不够回答用户的问题?
判据不能是「模型觉得够了」。让模型自由打「相关性 0 到 10 分」,你会得到一串 7 分和 8 分,没有决策价值。要让它可用,得拆成两个能分别判定的子问题:
第一问:问题里的要素,材料覆盖了几成? 把问题分词,去掉疑问词和「文件」「工作区」这类高频词,剩下的就是这一问的要素。覆盖率低于六成,说明这一轮压根没查对方向。
第二问:答案的「形状」对不对? 用户问「谁审批」,材料写着「须由平台组组长审批」——要素全命中,覆盖率接近满分,可它根本没回答问题:问题要一个人名,材料给的是一个职位。问「提前几个工作日」而材料里没有「数字 + 时间单位」,也属于这一类。
这类判断我叫它答案槽位检查:问题带什么疑问词,就要求材料里出现对应形状的东西。它和覆盖率互补——覆盖率抓「查错了方向」,槽位抓「只查到半路」,后一半正是多跳的入口。
自评一轮跑一次,所以必须便宜。实验里用 claude-haiku-4-5-20251001,生成仍用 claude-sonnet-5:判断「这堆材料够不够、下一句查什么」,小模型完全够用,用贵模型等于给每一轮都加一次贵调用。
纠错:放宽、追问、认输,三条路
自评说「不够」之后该怎么补救,取决于是哪一种不够。检索失败有两类,长得很像,修法完全相反:
第一类:根本没捞到。 答案文档在候选池里一次都没出现过,q06 的 doc-014 就是这样。这一类放宽多少都没用——只有拿新的查询词重查(下一节的多跳)才有救。
第二类:捞到了,却被自己的过滤器扔了。 答案就排在第二名,被准入门槛挡在装上下文之前。把门槛收紧成「只认向量路」(余弦 0.2 以上,不看关键词分)就能复现:q17 的 doc-021 排第 2、余弦 0.187,差一点点被挡掉,固定流程当场未命中。这一类跳多少跳都没用,该做的是降级放宽:门槛调松重判一遍准入,顺带把每路召回数调大。实验里这一手救回两道题。
判据就是上一节那两问:覆盖率低说明方向错了,走放宽;覆盖率高但槽位空说明只查到半路,走多跳。分类判错,容错就用反了——实测里 q07 被自评判成「查到半路」,于是去跳了一个跳不通的下一跳,而它真正需要的是放宽。
两条都走不通,认输。 这一条最容易被忽略,但它是刹车的一部分:找不到可以继续追的线索时,正确动作是停下来,把「材料不足」交给生成侧,而不是随便造个查询再试一轮。实验里这条路记成 give-up,报表上必须和「查到了」分开统计——混在一起,你就看不见系统在多大比例的问题上其实是放弃了。
多跳:前一步的答案,决定后一步查什么
今天的主演示。语料里有这么一对:
doc-019(值班与故障响应流程)写着:生产数据库主备切换必须由平台组组长书面审批。doc-014(组织架构与职责分工)写着:平台组组长是周敏。
标准答案集里的 q06 问:「生产数据库主备切换必须由谁书面审批?这个人叫什么名字?」
一次检索永远答不了这一问:doc-019 稳稳排第一,doc-014 连前二十都进不去——它和问题里任何一个词都对不上,「周敏」在问题里根本没出现过。这不是检索器不行,是答案分布在两篇文档里,而第二篇的检索线索藏在第一篇的正文里。
所以第二跳的查询只能从第一跳的材料里来。判据我用桥接短语:最贴题那一句里,问题没提过、且别的文档里也出现过的最长具体名词。两个条件缺一不可——前者保证是新信息,后者保证真有下一跳可跳。q06 里最贴题那句是「生产数据库主备切换:必须由平台组组长书面审批后执行」,选出来的就是「平台组组长」。
// 桥接短语:最贴题那一句里,问题没提过、且别的文档里也有的最长具体名词
export function bridgePhrase(question, context, stats) {
const sentence = bestSentence(question, context)
return [...new Set(chinesePhrases(sentence))]
.filter((p) => !question.includes(p) && (stats.docFreq.get(p) ?? 0) >= 2)
// 长的更具体;一样长时选出现在更少文档里的那个,指向性更强
.sort((a, b) => b.length - a.length || stats.docFreq.get(a) - stats.docFreq.get(b))[0] ?? ''
}
// 答案槽位:问题在要一个什么形状的东西。覆盖率满分也可能槽位是空的
const SLOTS = [
{ name: 'identity', ask: /谁|哪位|叫什么|名字/u, filled: /(?:组长|负责人|总裁)是[一-鿿]{2,3}/u },
{ name: 'duration', ask: /多久|几天|几个工作日|提前几/u, filled: /\d+\s*(?:个工作日|天|小时)/u },
]
export function unfilledSlot(question, contextText) {
return SLOTS.find((s) => s.ask.test(question) && !s.filled.test(contextText))
}import re
# 答案槽位:问题在要一个什么形状的东西。覆盖率满分也可能槽位是空的
SLOTS = [
("identity", re.compile(r"谁|哪位|叫什么|名字"), re.compile(r"(?:组长|负责人|总裁)是[一-鿿]{2,3}")),
("duration", re.compile(r"多久|几天|几个工作日|提前几"), re.compile(r"\d+\s*(?:个工作日|天|小时)")),
]
def bridge_phrase(question: str, context: list[str], doc_freq: dict[str, int]) -> str:
"""最贴题那一句里,问题没提过、且别的文档里也有的最长具体名词"""
sentence = best_sentence(question, context)
candidates = [
p
for p in dict.fromkeys(chinese_phrases(sentence))
if p not in question and doc_freq.get(p, 0) >= 2
]
# 长的更具体;一样长时选出现在更少文档里的那个,指向性更强
candidates.sort(key=lambda p: (-len(p), doc_freq[p]))
return candidates[0] if candidates else ""
def unfilled_slot(question: str, context_text: str):
return next(
((name, ask, filled) for name, ask, filled in SLOTS if ask.search(question) and not filled.search(context_text)),
None,
)实验跑出来的轨迹长这样(MOCK=1,你自己跑一遍会看到同样的输出):
第 1 轮|查询「生产数据库主备切换必须由谁书面审批?这个人叫什么名字?」
命中文档 doc-019、doc-029、doc-017、doc-010、doc-013|读入 598 token
自评 follow-up:材料命中了问题,但没给出「identity」这一项,
只留下「平台组组长」这个指代,顺着它再查一跳
第 2 轮|查询「平台组组长」
命中文档 doc-014、doc-019、doc-029、doc-026|新增 doc-014、doc-026
自评 sufficient:要素覆盖 73%,答案槽位已填上,够了
停止原因 sufficient|上下文文档 doc-019、doc-014、doc-029|584 token循环的刹车:三道闸,一道都不能少
到这里循环能跑了,也具备了永远跑下去的能力。无答案的问题就是反例:语料里根本没有「网页端支持哪些浏览器」,自评永远不会说「够了」,于是它一直改写、一直查。
三道闸,各拦一类失控,缺一道就有一类场景漏出去:
- 最大轮数。 最好懂,也最容易被当成唯一一道。实验里设 4,拦的是「每轮都在推进但永远推进不完」。
- 累计 token 预算。 拦的是「每轮都不超标但加起来爆掉」——四轮各读 600 token 没有一轮超标,可送进模型的材料已是单轮的四倍,轮数闸看不见这件事。
- 重复查询检测。 拦的是原地打转:改写出的查询和上一轮一样,同样的材料回来,自评又说不够。这一道要放在检索之前,否则要白花一次调用才发现在转圈;判重要对查询归一化(只看词的集合),不然「主备切换 审批」和「审批 主备切换」会被当成两个查询,圈照转不误。
The messages array (the whole thing gets resent every round)
实验里 q19(语料中没有答案)就是被第三道闸拦下来的:第 1 轮覆盖率 9%,放宽重查;第 2 轮改写出的查询和第 3 轮撞上,循环在第 3 轮开头停住,记成 duplicate-query。而默认那道 3000 token 的预算闸在这份语料上四轮跑满也踩不响——每轮只读约 600 token。这本身就是个教训:闸门装了不等于验过。 把预算压到 400 token 再跑一次,停止原因才变成 token-budget。
什么时候不要上 Agentic
把账摊开。实验在 D8 那份 20 题上用同一个检索器跑了两组:固定流程查一次结束,Agentic 带自评和循环。三笔账一起报:
| 账目 | 固定流程 | Agentic | 差 |
|---|---|---|---|
| 召回率(可答题) | 93.8% | 100.0% | 加 6.3 个点 |
| 召回率(single) | 100.0% | 100.0% | 持平 |
| 召回率(multi) | 75.0% | 100.0% | 加 25 个点 |
| 无答案题闭嘴率 | 0% | 100% | 见下 |
| 平均检索调用 | 1.00 | 1.75 | 1.75 倍 |
| 平均自评调用 | 0 | 1.75 | 从零开始 |
| 平均上下文 token | 533.6 | 536.2 | 几乎不变 |
(检索单位是块,与 D6、D8 一致;D1 的基线是文档级,绝对数字不能直接比。固定流程臂用的是 D9 的混合检索加重排,多跳基线 75%,比 D8 那份加权融合的基线报告里的 50% 高——换了检索器就不是同一条基线。表里没有平均倒数排名这一行,是因为它在 D8 已经顶格 1.0000:题目从语料反向出、字面重合度高,第一名永远是答案文档。指标饱和不是系统满分,更不能当改进的证据,所以这里只用召回率。最后一行的闭嘴率两侧口径不同:固定流程量的是「有没有候选过门槛」,Agentic 量的是「循环结束时自评有没有说够了」,比较时必须连口径一起报。)
读这张表要读结构:收益集中在四道多跳题里的一道,single 一个点都没涨,代价却是每一问平均多 0.75 次检索加 0.75 次模型调用。换句话说,你为 100% 的问题付了钱,只有 5% 的问题拿到好处。
三类场景明确不要上:
- 延迟敏感。 每多一轮就是一次检索加一次模型往返,首字延迟拉长一到两倍。
- 问题模式固定。 线上九成是单文档可答的,收益接近零、成本照付。判据不是感觉,是跑一遍评估看 multi 那一档占多大比例。
- 成本吃紧。 1.75 倍不是上限。真实模型不像规则替身那样老实,可能连查四轮,成本方差比均值更难受——按均值做的容量规划会在长尾上被打穿。
务实的做法是分流:先用一次便宜的判断看这一问像不像多跳,像才进循环。九成走一次检索、一成走循环,账完全不一样。这和 D8 那节 Agent Loop 是同一个立场——循环是能力,不是默认值。
源码导读
动手实验
starter/ 挖了 5 个练习点:工具描述、桥接短语、多跳的上下文分配、两道刹车、降级放宽。原样跑第七段有 5 项是 ❌,每做完一个就变 ✅——这是今天的进度条。语料和 20 题标准答案集是前面几天定稿的,一个字都不要改。
- 先原样跑一次
MOCK=1 pnpm start,看第三段:无答案的问题连着三轮改写出同一个查询,这就是没有刹车时的样子。 - 把
SEARCH_TOOL.description按「范围 / 该用 / 不该用 / 查询怎么写」四段补全,第一项验收变 ✅。 - 在
reflect.ts里实现桥接短语,重跑,看第二段的第 1 轮自评从give-up变成follow-up,并点出「平台组组长」。 - 把
packHops从「全部混排」改成按跳轮转,重跑,看q06的上下文里终于同时出现doc-019和doc-014。 - 补上重复查询检测与 token 预算两道闸,重跑,确认第三段的停止原因从
max-rounds变成duplicate-query,压到 400 token 时变成token-budget。 - 让「方向错了」这一支在重查时把
relaxGate传给检索,重跑,看第四段严格门槛下被救回来的题数从 0 变成 2。
面试题
今天 4 道题在下方题库区,侧重检索工具的描述设计、自反思循环的收敛、多跳的错误传播,以及什么时候该拒绝把 RAG 做成 Agentic。展开后先看"分析过程"再看要点——照着推导练,比背要点管用。标注"国内高频 / 海外高频"方便按目标市场取舍。
检查清单与明日预告
- 能把检索包装成工具接进 Agent 循环,并设计好工具描述让模型知道什么时候该用它
- 能实现检索结果的自我评估与纠错:发现材料不相关时改写查询重来,并设置重试上限
- 能说清 Agentic 检索相对固定流程多花了多少延迟与调用,以及什么场景下这笔钱不该花
- 能说出检索侧的相关性自评与 D6 生成侧拒答的分工,以及两者的先后顺序
- 能分辨「根本没捞到」与「捞到了被门槛扔了」两类失败,并说出各自的修法
- 能解释为什么多跳的收益要在上下文预算分配这一步才兑现
- 实验的 5 条验收标准全部通过,手里有一份固定流程与 Agentic 的三笔账
- 4 道面试题不看要点也能答出至少 3 道
明天(D13)把这套东西推上生产:增量同步与去重、按权限过滤、缓存分层、链路追踪与成本延迟账。顺序是有意的——今天这个循环把调用次数变成了会随问题波动的变量,而变量一旦上线就必须能被观测和限流。先有循环,才谈得上给循环装仪表盘。
Interview questions
You are exposing retrieval to a model as a tool. How do you write the tool description, and what concrete failure modes appear when you write it badly?把检索包成一个工具交给模型,这个工具的描述该怎么写?写不好会导致哪些具体的错误行为?
Common in ChinaCommon overseasBasic#tool-design#agentic-rag#promptingHow to reason about it · think before answering
- The discriminator is whether you can name concrete failure modes. Reciting 'the description should be clear' signals you have never shipped one.
- Give the structure first: a usable description answers four things - what is and is not in the corpus, when the tool must be called, when it must not be called, and what shape the query string should take.
- Attach a failure to each: no scope and the model treats it as a web search; no 'must call' and it answers policy questions from memory, convincingly; no 'must not call' and greetings or translations each burn a retrieval; no query shape and the model pastes the raw user sentence in, dragging interrogative words into the index.
- The query-shape line is the cheapest win: one sentence saying 'keyword phrase, no question words' beats ten heuristics for query cleaning on the retrieval side.
- Production angle: optional filter parameters such as department need an explicit 'only set this when you are certain'. Models like to fill optional fields, and a wrong filter hides the correct answer while the logs only show 'no results'.
- Expected follow-up: how do you verify the description works? Run a negative suite - small talk, translation, arithmetic, follow-ups already answered in the conversation - and assert the tool was not called. That regression is automatable.
分析过程 · 先想清楚再作答
- 这题的题眼是「具体的错误行为」。只会背「描述要写清楚工具的用途」的,一句话就暴露了没上过线——面试官想听的是描述里少一句话,线上就多一类工单。
- 先给结构:一段合格的工具描述要回答四件事——库里有什么和没有什么、什么时候必须用、什么时候不要用、查询串写成什么形状。四条各对应一类事故,逐条挂钩着说最有说服力。
- 逐条挂钩:不写范围,模型拿它当搜索引擎,问天气也去查;不写「必须用」,涉及公司制度的问题被模型凭记忆编答案,而且编得非常像真的;不写「不要用」,闲聊和翻译都触发一次无谓检索,成本和延迟白涨;不写查询形状,模型把用户整句问话塞进 query,「叫什么名字」这种疑问词进了检索,纯噪声。
- 最后一条最值钱也最容易漏:在描述里加一句「写成关键词短语,不要带疑问词」,比在检索侧做十种查询清洗都管用——问题在源头,就在源头修。
- 补一个生产视角:参数里的过滤字段(比如部门)要写明「只在确定时才填」。模型倾向于把可选参数填满,填错一个部门就把正确答案挡在库外,而这种错误在日志里看不出来,表现是「检索没结果」。
- 可预期的追问是「怎么验证描述写对了」。答案是拿一批负样本跑:闲聊、翻译、算术、以及答案已在对话里的追问,看模型有没有多调一次工具;这类回归是能自动化的。
Key points
- The description is a prompt for the model, not a code comment: scope, when to call, when not to call, query shape.
- Missing scope turns it into a web search; missing 'must call' produces confident answers from memory.
- Missing 'do not call' makes small talk trigger retrieval, paying cost and latency for nothing.
- Stating 'keyword phrase, no question words' fixes query pollution at the source.
- Optional filters need 'only set when certain' - a wrong filter silently hides the right answer.
- Regression-test with a negative suite and assert the tool was not invoked.
答题要点
- 描述是写给模型看的提示词,不是注释;四段式:范围、什么时候用、什么时候不用、查询写成什么形状。
- 不写范围会被当成搜索引擎;不写「必须用」会导致凭记忆编答案。
- 不写「不要用」会让闲聊也触发检索,成本和延迟白涨。
- 写明查询要用关键词短语、不带疑问词,比在检索侧清洗查询更根本。
- 可选过滤参数要写「只在确定时才填」,填错会静默地把正确答案挡在外面。
- 用一批负样本(闲聊、翻译、算术)做回归,断言工具没有被调用。
Self-reflective retrieval rewrites the query and retries. How do you guarantee it terminates instead of spinning on the same query forever?自反思式检索会反复改写查询重试。你怎么保证它一定会停下来,而不是在同一个查询上原地打转?
Common in ChinaCommon overseasIntermediate#agentic-rag#self-reflection#reliabilityHow to reason about it · think before answering
- This checks whether you have actually run such a loop. 'Set a max iteration count' is half an answer: it stops one failure mode and lets two others through.
- Split runaway behaviour into three shapes and give each its own brake. Progress that never completes is capped by max rounds. Per-round budgets that pass individually but blow up in aggregate need a cumulative token budget - four rounds of 600 tokens each never trips a per-round check yet quadruples what reaches the model. Spinning in place needs duplicate-query detection.
- Two implementation details prove you have written it: the duplicate check belongs before the retrieval call, otherwise you pay for a call to learn you are looping; and queries must be normalized to a set of terms, or 'failover approval' and 'approval failover' count as two distinct queries and the loop keeps turning.
- Say what happens after it stops: stop reasons must be recorded as distinct categories - satisfied, gave up, hit round cap, hit token budget, duplicate query. Collapsing them into 'loop finished' hides how often the system simply surrendered.
- An easy miss: installing a brake is not testing it. If the default token budget sits far above real usage it never fires, which is the same as not having one. Every brake needs a case that trips it.
- Expected follow-up: what if the model says 'not enough' when it actually is? Make the assessment structured - which elements are covered, which are missing - and treat an empty missing list as sufficient, so the decision is auditable rather than a bare boolean.
分析过程 · 先想清楚再作答
- 这题在考「有没有真让循环跑过」。只答「设一个最大轮数」的能拿一半分,因为最大轮数只拦住了一类失控,剩下两类照样漏出去。
- 怎么拆:把失控分成三种形态,每种配一道闸。一是「每轮都在推进但永远推进不完」,用最大轮数拦;二是「每轮都不超标但累计爆掉」,用累计 token 预算拦——四轮各读 600 token 没有一轮超标,可送进模型的材料已经是单轮的四倍;三是「原地打转」,用重复查询检测拦。
- 重复查询检测有两个实现细节,答出来就说明真写过:一是要放在检索之前,否则要白花一次调用才发现自己在转圈;二是判重要对查询做归一化,只看词的集合,否则「主备切换 审批」和「审批 主备切换」会被当成两个不同的查询,圈照转不误。
- 还要说清停下来之后怎么办:停止原因必须分类记录,「查够了」「主动认输」「撞到轮数」「撞到预算」「原地打转」是五种不同的结局。把它们混成一个「循环结束」,你就永远看不见系统在多大比例的问题上其实是放弃了。
- 一个容易被忽略的点:闸门装了不等于验过。默认预算如果比实际用量高一大截,跑多少遍都踩不响它,等于没装。每一道闸都要构造一个用例把它踩响,这是验收的一部分。
- 可预期的追问是「模型自己说不够,但其实已经够了怎么办」。答案是自评要给结构化输出(覆盖了哪些要素、缺哪些),缺失项为空却仍判不够时按「够了」处理——让判断可审计,而不是信一个布尔值。
Key points
- Three brakes, none optional: max rounds, cumulative token budget, duplicate-query detection.
- The cumulative budget catches rounds that each pass but blow up together - the round cap cannot see that.
- Check for duplicates before retrieving, and normalize the query to a term set before comparing.
- Record stop reasons as distinct categories rather than one 'finished' bucket.
- Every brake needs a case that actually trips it; an untested brake is no brake.
- Have the assessor emit covered and missing elements so 'not enough' is auditable.
答题要点
- 三道闸缺一不可:最大轮数、累计 token 预算、重复查询检测。
- 累计预算拦的是「每轮都不超但加起来爆掉」,轮数闸看不见这件事。
- 重复查询检测要放在检索之前,且查询要归一化成词的集合再判重。
- 停止原因分类记录:查够了、主动认输、撞轮数、撞预算、原地打转是五种结局。
- 每一道闸都要构造用例踩响,装了没验过等于没装。
- 自评输出结构化的覆盖与缺失项,让「不够」这个判断可审计。
In multi-hop retrieval a wrong first hop poisons every hop after it. How would you design for that?多跳检索里第一跳查错了,后面全跟着错。你会怎么设计容错?
Common in ChinaCommon overseasDeep dive#multi-hop#error-propagation#agentic-ragHow to reason about it · think before answering
- This is about error propagation. 'Add a retry' is not enough - retries help when one path fails, but the multi-hop problem is walking confidently down the wrong path.
- Separate two failure kinds first, because the fixes are opposite. Either the answer document never entered the candidate pool - no amount of loosening helps, only a new query term does, which is the multi-hop path - or it was retrieved and then dropped by your own admission threshold, where extra hops are useless and only relaxing the gate recovers it. Coverage plus the answer slot tells them apart: low coverage means wrong direction (broaden), high coverage with an empty slot means halfway there (hop).
- Then give the mechanism. Do not let the model freestyle the next query: pick a bridge phrase from the sentence that best matches the question - a concrete noun the question never mentioned that also appears in another document. 'Not in the question' makes it new information; 'appears elsewhere' guarantees there is somewhere to hop to.
- Fault tolerance has three layers: keep the earlier hop's material, so a bad second hop does not destroy the evidence you already had; trace each hop separately so you can locate where it went wrong; and surrender explicitly when there is no lead left, handing 'insufficient evidence' to the generation-side refusal.
- The overlooked trap is worth points: the hop succeeds but the metric does not move. The second hop really did retrieve the target document, yet if you merge both hops' candidates and pack by score, the first hop's higher lexical overlap fills the budget and the target never enters the context. Allocate the context budget round-robin across hops - that is where multi-hop gains are actually realized.
- Expected follow-up: how do you know the first hop was wrong? From the structured self-assessment, not from the final answer. By the time the answer is wrong the chain is three hops deep and much more expensive to debug.
分析过程 · 先想清楚再作答
- 这题考的是错误传播意识。只答「加个重试」是不够的——重试只在「同一条路走不通」时有用,而多跳的问题是走上了错误的路还越走越远。
- 先把两类失败分开,这是整题的骨架:一类是根本没捞到(答案文档在候选池里一次都没出现,放宽门槛毫无用处,只能靠新的查询词重查,也就是多跳),一类是捞到了却被自己的过滤器扔了(排在第二名但没过准入门槛,这一类跳多少跳都没用,只能降级放宽门槛重判)。判据是要素覆盖率加答案槽位:覆盖率低是方向错了走放宽,覆盖率高但槽位空是只查到半路走多跳。判错类型,容错就完全用反了。
- 然后给具体机制。判断下一跳查什么,不能凭模型自由发挥,要有可解释的判据:从最贴题的那一句里挑出问题没提过、且在别的文档里也出现过的具体名词当作桥接短语。「问题没提过」保证它是新信息,「别的文档里也有」保证真的有下一跳可跳——只在这一篇里出现的短语,查了只会把同一篇再捞回来。
- 结论层面,容错有三层:不要丢掉上一跳的材料(第二跳查错了,第一跳的证据还在);每一跳独立记录轨迹,事后能定位是哪一跳歪的;追不动时主动认输,把「材料不足」交给生成侧的拒答,而不是硬凑一个答案。
- 有一个非常容易被忽略的坑,说出来会加分:跳成功了,命中却没变。第二跳确实把目标文档检索回来了,但如果把两跳的候选混在一起按名次装上下文,第一跳的材料字面重合度更高,会把预算占满,目标文档根本挤不进去。上下文预算必须按跳轮转分配——多跳的收益是在这一步兑现的,不是在检索那一步。
- 可预期的追问是「怎么知道第一跳错了」。答案是靠自评的结构化输出,而不是靠最终答案对不对;等到答案错了再回头找,链路已经断了三跳,定位成本高得多。
Key points
- Classify first: never retrieved needs a new query term (a hop); retrieved-then-filtered needs a relaxed gate. The fixes are opposite.
- Derive the next query from a bridge phrase - a concrete noun absent from the question that also appears in another document.
- Keep the previous hop's material so a failed hop does not discard existing evidence.
- Trace every hop separately so you can pinpoint which one drifted.
- Allocate context budget round-robin across hops, or a successful hop still fails to change the metric.
- Surrender explicitly when no lead remains and hand it to the generation-side refusal.
答题要点
- 先分类:根本没捞到只能靠多跳换查询词,捞到了被门槛扔了只能靠降级放宽,两者修法相反。
- 下一跳的查询用桥接短语:问题没提过、且别的文档里也出现过的具体名词。
- 保留上一跳的材料,第二跳失败时第一跳的证据仍在。
- 每一跳独立记轨迹,能定位是哪一跳歪的。
- 上下文预算按跳轮转分配,否则跳成功了命中也不会变。
- 追不动时主动认输,把材料不足交给生成侧拒答,不硬凑答案。
When would you refuse to make a RAG system agentic, and what data would you use to convince your team?什么情况下你会拒绝把一个 RAG 系统做成 Agentic 的?拿什么数据说服你的团队?
Common in ChinaCommon overseasIntermediate#agentic-rag#cost#engineering-judgementHow to reason about it · think before answering
- This tests engineering judgement and whether you can do arithmetic. Anyone who says 'agentic is more advanced so we should ship it' is out. The interviewer wants you to name the cost and draw the boundary with numbers.
- Decompose it: identify which question types actually benefit, then check how much of your traffic they represent. Agentic gains concentrate in multi-hop questions and retrieval retries; single-document questions are answered by one lookup and every extra round is waste.
- So the criterion is the evaluation set, not intuition. On a 20-item set we measured multi-hop recall going from 75% to 100% while overall answerable recall moved only from 93.8% to 100%, at the cost of average retrieval calls going from 1 to 1.75 plus the same number of assessment calls - you pay for 100% of traffic so that 5% of it improves.
- Three clear refusals: latency-sensitive surfaces, where each round adds a retrieval plus a model round trip and roughly doubles time to first token; fixed question patterns, where nine in ten questions are single-document and the gain is near zero; and tight cost budgets, where a real model is less disciplined than an offline stand-in and the variance, not the mean, is what breaks your capacity plan.
- Finish with the alternative: route. Use one cheap check to decide whether a question looks multi-hop, and only then enter the loop. Nine tenths take a single retrieval, one tenth loops, and the economics change completely. Looping is a capability, not a default.
- Expected follow-up: how do you know which questions look multi-hop? Mine the eval set and production logs for patterns - two facts requested in one sentence, or a question about the person behind a role - start with rules, and reach for a small classifier only when rules stop working.
分析过程 · 先想清楚再作答
- 这题在考工程判断力,也在考你会不会算账。凡是答「Agentic 更先进所以要上」的,直接出局;面试官想听的是你能主动说出它的代价,并且用数字划出适用边界。
- 怎么拆:先承认收益来自哪一类问题,再看这类问题在你的流量里占多大比例。Agentic 的收益几乎全部集中在多跳和检索失败重试上,单文档可答的问题一次检索就够了,多查一轮纯属浪费。
- 所以判据不是感觉,是评估集:跑一遍,看 multi 那一档占多少题、涨了多少个点,再对照总调用次数涨了多少倍。在一份 20 题的集合上,我们量到的是多跳召回从 75% 涨到 100%,可答题整体只从 93.8% 涨到 100%,代价是平均检索调用从 1 次涨到 1.75 次、外加同样次数的自评调用——为 100% 的问题付钱,只有 5% 的问题拿到好处。
- 三类明确不上:延迟敏感(每多一轮就是一次检索加一次模型往返,首字延迟拉长一到两倍);问题模式固定(九成是单文档可答,收益接近零);成本吃紧(真实模型不像离线替身那样老实,成本方差比均值更难受,按均值做的容量规划会在长尾上被打穿)。
- 给出替代方案才算完整:分流。先用一次便宜的判断看这一问像不像多跳,像才进循环,不像走固定流程。九成走一次检索、一成走循环,账完全不一样。这也说明循环是一种能力,不是默认值。
- 可预期的追问是「那你怎么知道哪些问题像多跳」。答案是从评估集和线上日志里找模式(问句里同时问了两个事实、问的是某个角色背后的人),先用规则跑,跑不动再上小模型分类——顺序不要反。
Key points
- Gains concentrate in multi-hop and retry cases; single-document questions gain almost nothing.
- Settle it with the evaluation set: multi-hop delta against the multiplier on total calls.
- One measured set: multi-hop recall 75% to 100%, overall 93.8% to 100%, retrieval calls 1 to 1.75 plus the same number of assessment calls.
- Refuse when latency-sensitive, when question patterns are fixed, or when cost is tight - variance hurts more than the mean.
- Route instead: a cheap check up front, and only multi-hop-looking questions enter the loop.
- Looping is a capability, not a default.
答题要点
- 收益集中在多跳与检索失败重试,单文档可答的问题上收益接近零。
- 用评估集算账:multi 档涨了多少点,对照总调用次数涨了多少倍。
- 实测过的一组数字:多跳召回 75% 到 100%,整体 93.8% 到 100%,检索调用 1 次到 1.75 次外加等量自评调用。
- 三类不上:延迟敏感、问题模式固定、成本吃紧(方差比均值更难受)。
- 替代方案是分流:便宜的判断先过滤,像多跳才进循环。
- 循环是一种能力,不是默认值。
Comments
Sign in to join the discussion
No comments yet — be the first.