返回 AIPA 笔记
AIPA Day 41

agentic 自适应检索 — 查询改写、多跳与防重复

agentic 自适应检索 — 查询改写、多跳与防重复

2026-07-25
agentic-ragadaptive-retrievalmulti-hop

日期: 2026-07-25 阶段: Phase 2 - AI-native 参考架构 标签: #agentic-rag #adaptive-retrieval #multi-hop

核心问题

Day 40 把 JIT 检索接进了 orchestrator:不预载、按需取、命中才进窗口。但 hybridSearch.ts 现在是单次、被动的——给一个 query,BM25+vector 融合返回 top-k,结束。这对「一句话能命中答案」的简单问题够用,对多跳问题(multi-hop)和措辞与文档不匹配的问题会塌方:

  • 多跳:「比较 Mysticeti 和 AptosBFT v4 的延迟取舍」需要先取 A 笔记、读懂、再据此取 B 笔记——单次检索拿不全证据链。
  • 措辞错配:用户问「钱包资产看板怎么算成本」,笔记里写的是「cost basis / FIFO 会计」——向量相似度被措辞拉低(正是 Day 40 Chroma 说的「needle-问题相似度低 → 退化更快」)。

今天回答:怎么把单次检索升级为 agentic 自适应检索——agent 自己改写查询、多跳迭代、判断证据是否够、不够再取、并避免重复检索,且这个循环要有界(不能无限取)。这是 orchestrator 从「会调检索工具」到「会用检索工具思考」的关键一跳。

关键内容

A. 三个核心机制:查询改写 / 多跳迭代 / 充分性判定

agentic RAG(2025-2026 主线,FutureAGI《Agentic RAG: Developer Guide》2026 口径)的核心是把「检索」从单步变成带反馈的循环。三个机制缺一不可:

1) 查询改写(query rewriting / transformation)——检索前先把用户原话改成「更适合检索」的形式:

  • 分解:2-hop 问题拆成 2 个子查询(「A 和 B 比较」→「A 的延迟」+「B 的延迟」)。
  • 词汇对齐:把口语措辞改写成文档术语(「怎么算成本」→「cost basis FIFO 会计方法」),直接攻击 Day 40 的「相似度低退化更快」问题。
  • 实体扩展:补全缩写/同义词(「AA 钱包」→「ERC-4337 account abstraction」)。

2) 多跳迭代(iterative multi-hop)——retrieve → read → 判断够不够 → 不够则据当前证据改写下一跳 query → 再 retrieve,直到够或撞预算。IRCoT(固定间隔交错检索)是早期范式;2025 的 FAIR-RAG 升级为充分性驱动而非固定间隔。

3) 充分性判定(sufficiency assessment)——决定「停还是继续」的闸门。FAIR-RAG(arXiv 2510.22344, 2025-10)的 SEA 模块给了最清晰的实现:

「first deconstruct the user's query into a checklist of discrete, required informational components」,然后「systematically audits the collected evidence against this checklist, confirming which findings are supported and identifying which remain as explicit 'intelligence gaps'.」

即:把问题拆成一张必填信息清单,每跳检索后逐项核对——已满足的打勾、未满足的标记为「intelligence gap」,下一跳只针对 gap 改写查询。停机条件:if is_sufficient == Yes then break,且硬上限「a maximum of three iterations to ensure a balance between comprehensiveness and latency」。

B. 自适应检索循环状态机 + 多跳算法

把上述三机制合成一个有界状态机(这是今天要落进代码的核心结构):

        ┌─────────────────────────────────────────────┐
        │ [REWRITE] 把 userQuery 拆成必填清单 checklist │
        │   + 生成首跳 query                            │
        └───────────────────┬─────────────────────────┘
                            ▼
        ┌─────────────────────────────────────────────┐
   ┌───►│ [RETRIEVE] hybridSearch(query) → 命中片段     │
   │    │   去重:命中 id ∈ seen 则丢弃(防重复检索)   │
   │    └───────────────────┬─────────────────────────┘
   │                        ▼
   │    ┌─────────────────────────────────────────────┐
   │    │ [ASSESS] 逐项核对 checklist:               │
   │    │   confirmed[] / gaps[]                        │
   │    └───────────────────┬─────────────────────────┘
   │            gaps 为空? ──┴── 或 hops ≥ MAX_HOPS(3)?
   │           ┌────────────┴────────────┐
   │          否(还有gap且未到上限)       是
   │           ▼                          ▼
   │  ┌──────────────────────┐   ┌──────────────────┐
   └──┤[REFINE] 据 gaps+      │   │ [SYNTHESIZE]     │
      │ confirmed 改写下一跳   │   │ 用 confirmed 证据 │
      │ query(只攻未满足项)  │   │ 合成答案 + 引用   │
      └──────────────────────┘   └──────────────────┘

多跳 + 去重算法(伪代码,落点 hybridSearch.ts 之上的 orchestration 层):

def agentic_retrieve(userQuery, MAX_HOPS=3):
    checklist = decompose(userQuery)        # SEA: 拆成必填信息清单
    seen_ids  = set()                       # 去重:已见过的 chunk id
    evidence  = []
    query     = rewrite(userQuery, checklist)   # 首跳查询改写
    for hop in range(MAX_HOPS):
        hits = hybridSearch(query, k=K).hits    # 复用 RRF 融合
        new_hits = [h for h in hits if h.id not in seen_ids]   # 防重复
        if not new_hits:                        # 本跳没拿到新东西 → 早停
            break
        seen_ids.update(h.id for h in new_hits)
        evidence += new_hits
        confirmed, gaps = assess(checklist, evidence)   # 充分性判定
        if not gaps:                            # 证据齐 → 停
            break
        query = refine(gaps, confirmed)         # 只针对未满足项改写
    return synthesize(evidence, confirmed)

三个关键不变量:

  • 有界MAX_HOPS=3(对齐 FAIR-RAG「最多三次迭代」),且每跳消耗一次 Budget.assertCanToolCall,撞预算即停。
  • 单调推进if not new_hits: break——本跳没拿到任何新 chunk 就早停,杜绝原地打转。
  • gap 制导refine(gaps, confirmed) 只查未满足项,FAIR-RAG 原话「By leveraging the confirmed findings, the agent makes the new queries more precise and avoids repeating previous searches」——这同时是查询改写防重复机制。

反直觉洞察①(防重复检索的关键不在「id 去重」,在「gap 制导改写」):朴素直觉是「记下见过的 chunk id,去重就行」。但真正的重复浪费发生在查询层——若每跳都拿原 query 的变体去检索,会反复命中同一批高相关 chunk(只是排序微调),id 去重后 new_hits 为空,循环空转到撞上限。FAIR-RAG 的洞察是:用 confirmed findings 把下一跳 query 推向「还没覆盖的信息空间」,让每跳检索的是「问题的不同侧面」而非「同一侧面的不同措辞」。去重是兜底,gap 制导才是治本。

C. 衔接 hybridSearch RRF:自适应层与融合层的分工

关键设计决策:自适应检索是 hybridSearch 之上的编排层,不改 hybridSearch 内部。 两层各管一件事:

职责文件不变
融合层(已有)单次 query 的 BM25+vector RRF 融合、降级处理hybridSearch.ts纯函数、无状态、静态导出兼容
自适应层(新增)查询改写、多跳循环、充分性判定、去重、预算新建 agenticRetrieve.tshybridSearch N 次

为什么不把多跳塞进 hybridSearch?因为 hybridSearch.ts 当前是纯函数式、可静态导出、向量缺失自动降级 BM25(见其注释「名实相符,不再是二选一」)——这是它能离线跑、能进 Agent Lab 教学模式的前提。多跳需要 LLM 做 rewrite/assess(有状态、有成本、可能失败),混进去会污染纯函数性质。分层后:融合层保持确定性可测,自适应层承接非确定性 LLM 调用。

检索质量 eval:自适应前后对比(用多跳基准的口径,FAIR-RAG 2025-10 数据为锚)。 多跳数据集上自适应迭代 vs 单次/固定间隔的增益是实测的:

数据集方法F1对比基线绝对增益
HotpotQA(2-hop)FAIR-RAG (3 iter)0.447Iter-Retgen 0.370+7.7 pt
2WikiMultiHopQAFAIR-RAG (3 iter)0.305Self-RAG 0.251+5.4 pt
MuSiQue(多跳难)FAIR-RAG (3 iter)0.267Iter-Retgen 0.190+7.7 pt

跳数越多增益越大的趋势在另一项 2026 工作(ReaLM-Retrieve vs IRCoT)更明显:+3.2% F1(2-hop)/ +5.8%(3-hop)/ +8.4%(4-hop)——「step-level uncertainty detection enables precisely-timed retrieval where knowledge gaps occur」。这条曲线证明:问题越复杂(跳数越多),自适应迭代相对单次检索的优势越大

反直觉洞察②(自适应检索对简单问题是净亏损):上面的增益数字会诱使人「全量切自适应」。但 2-hop 增益(+3.2%)远小于 4-hop(+8.4%),而自适应每跳要多付一次 LLM rewrite/assess 的延迟和成本。对「一句话能命中」的单跳问题,多跳循环只是把单次检索的延迟和成本翻几倍换来微乎其微的召回提升。所以入口要有「复杂度路由」(adaptive routing):简单问题走单次 hybridSearch,复杂/多跳问题才进自适应循环——这正是 Adaptive RAG「route queries to the right pipeline based on complexity」的本意。盲目全量自适应=给 64% 不需要它的查询交税(呼应 Princeton「多数任务单 agent 即够」的同源直觉)。

设计要点/决策表

要点决策理由
分层自适应层独立于 hybridSearch,调用而非改写保护融合层纯函数/可静态导出/可降级性质
跳数上限MAX_HOPS=3对齐 FAIR-RAG,平衡覆盖度与延迟
停机条件gaps 空 OR 撞 MAX_HOPS OR 本跳无新 chunk OR 撞 Budget多重早停,杜绝空转
防重复id 去重(兜底)+ gap 制导改写(治本)重复浪费在查询层,gap 制导让每跳查不同侧面
复杂度路由简单/单跳走单次检索,复杂/多跳才进循环自适应对简单问题是净亏损,按需启用
充分性判定拆 checklist 逐项核对(SEA 式)比「LLM 拍脑袋说够了」可解释、可调试

对本项目的落地

  • 新建 src/agent/rag/agenticRetrieve.ts:导出 agenticRetrieve({ model, query, hybrid, budget, maxHops }),实现 B 节状态机——decompose/rewrite/assess/refine 四个 LLM 步用 model 调(小模型即可,结构化输出),检索复用 hybridSearch(opts)。每跳前 budget.assertCanToolCall(),与 orchestratorAgent.ts 的预算体系一致。
  • 复用而非改写 hybridSearch.tsagenticRetrieve 把每跳的 query 喂给现有 hybridSearch,拿回 HybridResult.hits,对 hit.id 做去重(hybridSearch 已返回稳定 id,去重零成本)。hybridSearchsource: 'hybrid'|'bm25' 字段照常透传,自适应层不关心融合内部。
  • 接进 Knowledge 子 agentorchestratorAgent.tsrunKnowledgeAgent 是 RAG 子 agent——把它的检索从「单次 hybridSearch」换成「复杂度路由 → 简单走单次、多跳走 agenticRetrieve」。路由可先用启发式(query 含「比较/对比/和...的区别」或多个实体 → 判为多跳),W7 再考虑用 LLM 分类。
  • 检索质量 eval 前后对比:在 src/agent/eval/retrievalGolden.ts(已有内嵌 golden 模式)补一组多跳 golden(如「Mysticeti vs AptosBFT 延迟」需命中两篇笔记),断言 agenticRetrieve 的命中召回 > 单次 hybridSearch 的召回。这把 C 节的「自适应前后对比」变成 CI 可验证的数字,而非口头声称。
  • AML 落点:SAR 多跳核查(如「该地址的资金来源是否触及已知混币器」需先查地址→查关联地址→查混币器标签)正是多跳场景,agenticRetrieve 的 gap 制导直接复用——src/aml/typology.ts 的 typology 可作为 checklist 的来源(每个 typology 维度 = 一个必填项)。
  • 诚实标注agenticRetrieve.ts、复杂度路由、多跳 golden 为 W6 设计与实现动作;AML typology→checklist 映射为 P3 接入,本周落函数与单测骨架,不谎称已端到端跑通多跳 SAR 核查。

参考资料

  1. FAIR-RAG — Faithful Adaptive Iterative Refinement for Retrieval-Augmented Generation(arXiv 2510.22344):SEA 充分性判定(拆 checklist + intelligence gaps)、最多 3 迭代、gap 制导改写防重复、HotpotQA 0.447/2Wiki 0.305/MuSiQue 0.267 F1 (2025-10)
  2. FutureAGI — Agentic RAG: Developer Guide to Smarter Retrieval (2026):查询改写/多跳迭代/自适应路由/Self-RAG 反思 token 全景 (2026)
  3. Self-RAG — Learning to Retrieve, Generate and Critique through Self-Reflection(arXiv 2310.11511):反思 token、按需检索、可控推理 (经典基线,2025 仍为对照锚点)
  4. When to Retrieve During Reasoning: Adaptive Retrieval for Large Reasoning Models(arXiv 2604.26649):ReaLM-Retrieve vs IRCoT,+3.2%/+5.8%/+8.4% F1 随跳数递增、step-level uncertainty 精准定时检索 (2026)
  5. Agent-Orchestrated Adaptive RAG(arXiv 2606.05658):动态查询分解+迭代检索+有界自反思评估循环 (2026)
  6. 本仓库 src/agent/rag/hybridSearch.ts(RRF 融合层,被自适应层复用)、src/agent/orchestrator/orchestratorAgent.ts(Knowledge 子 agent + Budget)、src/agent/eval/retrievalGolden.ts(多跳 golden 落点)(2026-06)

SOTA 检查 (2026-06-11)

  • agentic 自适应检索是 2026-06 RAG 的主线,单次检索已是「baseline」:FutureAGI 2026 指南、多篇 2026 arXiv(A-RAG/Agent-Orchestrated Adaptive RAG)一致把「查询改写+多跳+自反思」当默认;本日 WebSearch 未见推翻「自适应迭代在多跳上稳定优于单次」的结论。
  • 充分性驱动 > 固定间隔:FAIR-RAG(充分性判定)相对 IRCoT(固定间隔交错检索)的优势在 2025-10 已确立——「按 gap 制导」是当前更优范式;本笔记状态机采此口径。
  • 复杂度路由仍是关键防线:2026 多项工作(Adaptive RAG / Stop-RAG 的 value-based 停机)都强调「不是所有 query 都该多跳」,与本笔记反直觉洞察②一致——盲目全量自适应是净亏损。Stop-RAG(OpenReview 2026)用 value function 学停机时机,比固定 MAX_HOPS 更细,是 P3 可评估的升级。
  • 过时认知警示:「检索就是单次 top-k 向量搜索」是 2023-2024 口径,2025-2026 已被自适应迭代取代;但反过来「全部上多跳」同样过时——SOTA 是「按复杂度路由」。
  • 待跟踪:GraphSearch(图检索的 agentic deep search,arXiv 2509)把多跳推到知识图谱上——若本项目 AML 资金溯源做成地址关联图,应评估图上多跳检索(与 Zep 的 Graphiti 时间知识图谱可能协同)。