迁移经验检索的工程细节:混合检索、重排与拒答

📅
1 分钟阅读
·

本文是「Agent 开发实践与思考」系列第 6 篇。系列目录:

纯向量检索的限制

第一类是组件名和接口字段。Agent 正在迁移 LegacyTable,查询经验库“表格的 reload 方法怎么处理”时,向量检索召回了多条表格刷新策略,但其中多条对应其他表格资产,未召回 LegacyTable 的条目。embedding 模型通常将文本编码为连续向量,其目标是使语义相近的文本接近,不能保证组件名被精确区分。LegacyTableLegacyList 在向量空间里可能相近,但二者在业务上的契约不同。prop 名、事件名和 ref 方法名也有相同限制:语义相近的名称仍需要精确匹配。

第二类是项目内部的命名和缩写。Vue 转 React 过程中,团队将某类弹窗称为“审批流弹窗”,将某个状态模块称为“北极星”。通用 embedding 模型的训练数据可能未包含这些项目术语,因此“北极星”可能被编码为星座含义,或在向量表示中权重不足,导致检索无法匹配对应的项目概念。

这两类问题常被归因于 embedding 模型能力不足。我们试过更换模型,组件名类 case 的改善有限。语义空间不会稳定区分语义相近但标识不同的字符串:LegacyTableLegacyList 在业务上应被区分,但这种要求与语义相近的编码目标不一致,因此仅更换模型无法解决。

embedding 测评文章讨论了模型选择:评测要同时覆盖标识符、处理模式和不适用条目,不能只看平均分。纯向量检索仍无法保证精确标识符命中,也无法判断主题相近的经验是否适用。以下说明对应的工程处理方式。

BM25 与向量检索的融合

候选排序不能只依据文本相关度。先按项目范围、分支、框架和组件库版本、经验状态过滤候选:已经标为 review_required 的条目不作为默认方案,candidate 只能提供检查项,当前任务不适用的条目直接排除。通过资格检查后,再进行两路召回。BM25 根据词频、逆文档频率和文档长度计算得分。它不处理语义,但查询 LegacyTable 时会将包含 LegacyTable 的条目排在前面,适合精确匹配。向量检索处理语义等价的查询,例如“表格插槽怎么迁移”可匹配“列渲染模式转换方案”。两路各召回 top 50,再融合成一个列表。

工程接入使用了搜索服务内置的 BM25 和已有的向量库。两路可以并行执行,因此检索阶段的主要延迟通常接近较慢的一路,但仍要计入网络、调度、融合、超时和重试开销。索引侧还需要处理以下细节:组件名、prop 名和事件名这类标识符不应只依赖中文分词。可以为它们建立 keyword 字段、标识符专用 analyzer 或自定义词表,使 LegacyTable 作为完整词项入库;查询侧要使用与索引侧一致的分析方式。

RRF 按召回名次融合

加权求和要求两路分数具有可比尺度,但它们并不满足这个条件。BM25 的分数没有上界,12.7 和 3.2 都常见;向量相似度在 0 到 1 之间。直接加权得先做归一化,归一化又引入新的超参,数据分布一变就要重调。

RRF(Reciprocal Rank Fusion)不使用原始分数,而是依据每一路的名次进行融合。一个条目在某一路召回里排第 r 名,就得 1/(k+r) 分,几路得分相加后排序。它处理的是融合阶段的分数尺度问题,不表示不同查询的 RRF 总分可以直接比较。伪代码如下:

function rrf(rankings: string[][], k = 60): string[] {
  const scores = new Map<string, number>();
  for (const ranking of rankings) {
    // 每路召回的有序文档列表
    ranking.forEach((docId, index) => {
      const rank = index + 1;
      scores.set(docId, (scores.get(docId) ?? 0) + 1 / (k + rank));
    });
  }
  return [...scores.entries()]
    .sort((a, b) => b[1] - a[1])
    .map(([docId]) => docId);
}

经典实现常使用 k=60 作为默认值。该值控制不同名次的分数差:第 1 名得 1/61,第 10 名得 1/70;第 50 名得 1/110,对总分的贡献较小。例如,条目 X 在 BM25 排第 2、向量排第 30,得分约 1/62 + 1/90 ≈ 0.0272;条目 Y 两路都排第 8,得分 2/68 ≈ 0.0294,因此 Y 排在前面。该排序降低了单一路检索排名对结果的影响,使两路排名都较高的条目优先。

k 仍应结合召回深度、检索器数量和离线评测验证。如果某一路明显更可靠,RRF 也可以给每路乘权重。我们试过给向量那路加权,提升不明显,就保持了等权;这只是当时的资产和查询分布下的选择。

在当时收集的组件名类迁移查询中,混合检索减少了精确标识符导致的漏召回。这个结论只说明该批索引和命名规范下的变化,不代表所有 embedding 模型或数据集都会得到相同结果。

对候选条目进行重排

融合后的 top 50 仍包含主题相关但不适用的条目。向量检索中,query 和文档分别编码为向量后再计算距离,编码阶段没有交互,因而难以判断细粒度相关性。BM25 只依据词项重合,也不判断上下文。

在迁移场景中,检索“表格 reload 怎么处理”时,混合检索可以召回包含 reload 的条目;向量检索也会召回所有涉及表格刷新的条目,其中可能包含仅提及“刷新”但机制不同的资产经验。BM25 会将所有包含 reload 的条目视为匹配,不区分其属于 LegacyTable 还是其他表格。

这里使用的是 cross-encoder 类 reranker:它将 query 与候选条目联合编码,逐对输出相关性分数。精度通常高于双塔召回,但每对都要完整计算,只适合放在几十个候选之后。所以流程拆成两段:混合检索召回 top 50,重排精排后取 top 5 注入 Agent 上下文。在当时的人工复核样本中,不适用但主题相近的经验被注入的情况有所减少;这不是对所有资产类型的通用比例。

召回数 K 和精排数 N 需要结合延迟约束确定。K 过小时,相关经验会在召回阶段被遗漏;K 过大时,重排逐对打分的线性开销会增加延迟。K=50、N=5 是这批迁移任务在当时延迟约束下的选择,不是通用参数。重排分数比向量相似度更适合在同一模型、同一版本和相近输入格式下做校准,但不能默认跨查询具有稳定的绝对可比性。不同迁移单元的候选难度、长度和分布都可能改变分数,阈值仍然要用业务评测集验证。

我们也测试过由大模型重排:将 50 段经验条目放入 prompt,让 LLM 逐个给出相关性分数。单次调用使用上万 token,延迟为好几秒,成本是重排模型的几十倍,效果没有拉开差距。迁移任务检索频率较高,因此未采用该方案。

用重排阈值限制经验注入

调参过程中,错误经验被直接采用对迁移质量的影响超过了召回率。经验库没有完全匹配当前资产的条目时,模型仍可能根据主题相关的片段生成迁移方案。基于错误经验的方案可能使 Agent 修改后产生不符合预期的行为。

做法是在重排分数上设置限制:分数不足以支持直接采用时,不将条目作为默认方案注入,Agent 改为依据原始代码和项目状态分析;必要时仍可保留条目标题、出处和适用条件,作为待验证的参考。提示词侧也要配合,在系统提示里明确”如果没有经过资格检查的匹配经验,从当前代码和项目记忆出发分析”。

阈值需要基于评测集确定。我的做法如下:从过去迁移任务的检索日志里攒一个评测集,按“可直接复用、只能参考、契约不适用、知识缺失”标注相关性。对每条查询跑完整的过滤、检索和重排,记录最高重排分,再从低到高扫描候选阈值,分别观察可用经验的保留率、错误经验的注入率和拒绝率。这里的阈值只是特定 reranker、版本、输入模板、候选生成策略和评测集下的操作点;不同资产类型的分布差异较大时,应分别校准或把版本、经验状态、精确标识符命中等条件纳入决策,而不是只依赖一个全局分数。

有两项限制。若采用重排分数做拒绝决策,阈值必须针对该 reranker 的输出校准;向量相似度和重排分的分布不同,不能混用。评测集也要持续补充:新资产类型会带来新的查询模式,半年前的曲线不能代表当前分布。

被拒绝直接采用的查询进入待补队列:人工确认确实缺少这类经验的,在积累足够验证证据后补充;确认已有经验但未检索到的,转回检索侧继续排查 bad case;确认候选主题相关但契约不适用的,补充反例或失效条件。这些查询也可作为评测集的新来源。

检索后的两个问题

检索负责找到匹配的经验条目,后续还有两类问题。

第一类是如何向模型组织条目,包括顺序、去重和上下文预算。目前我们只按分数排序,重复条目会挤占上下文。经验条目的上下文预算还需要与项目记忆、系统提示词和工具结果共同分配,不能将所有检索结果都加入上下文。

第二类是检索触发条件。目前检索是固定流程,每次迁移开始都执行一次,包括项目记忆已经覆盖的常规情况。后续需要评估由 Agent 决定是否检索及检索次数的方案。多轮迁移中的“上面那个表格的 reload 怎么处理的”这类指代不能直接用于检索,需要先补全为独立查询;这部分刚开始处理,尚无结论。

经验条目结构化仍未解决。目前条目由自然语言和元数据组成,检索时按文本处理。将经验抽取为条件、动作、约束后再检索的方案值得评估,但尚未完成工程验证。


441 字 · 39 段落
ximing

Follow onGitHub

相关文章