Hotdry.
ai-systems

用可检索上下文注入让编码 Agent 实现跨文件精准补全与重构

将代码上下文外包给检索系统,按需注入 Agent,兼顾 token 成本与跨文件准确率,给出三段阈值与回滚策略。

当编码 Agent 面对百万行级仓库时,再长的上下文窗口也会迅速被 “淹没”。常见后果是:补全出来的函数在 A 文件写得漂亮,却忘了 B 文件早已改名;重构建议看似优雅,实则把接口契约撕得粉碎。Nia 的做法是把 “记忆” 从模型体内搬到外部索引,只在需要时把最小必要信息塞回 —— 也就是 “可检索的代码上下文注入”。下面给出一条可直接落地的流水线与参数。

一、问题拆解:为什么跨文件总 “幻觉”

  1. 上下文窗口有限,32 k token 看似宽裕,实际换成 UTF-8 中文注释后只能容纳约 1/4 的百万行仓库。
  2. 模型自有的 “模糊记忆” 会张冠李戴 —— 把去年删掉的类当成现成接口。
  3. 重构场景需要 “引用链” 与 “接口契约” 同时出现,否则极易产生破坏型变更。

二、核心思路:先索引、再召回、后注入

  1. 离线索引阶段
    用 LSP 把全仓库切成符号级片段(函数、类、接口),保留定义位置、引用位置、类型签名。再对片段做向量编码,建成双路索引:

    • 倒排符号 → 精确匹配
    • 向量语义 → 模糊匹配
  2. 在线召回阶段
    当用户光标停在 foo.ts:42,Agent 先抽 AST 路径与当前符号,跑两轮召回:

    • 精确路:符号完全命中,取 Top 5
    • 语义路:向量相似度 >0.82,取 Top 10 合并后按 “与当前编辑距离” 二次排序,保留最多 2 k token。
  3. 动态注入阶段
    把召回片段塞进 System Prompt 尾部,用 [Context-Begin]/[Context-End] 包裹,模型输出时自带 “出处行号”,前端高亮方便回跳。

三、三段阈值与回滚策略

阶段 关键阈值 作用
召回置信度 语义相似度 ≥0.82 低于此值直接走纯局部补全,避免噪声
Token 预算 ≤2 k 超过则按 “引用链优先” 裁剪,保证接口契约完整
缓存 TTL 5 min 同文件再次请求直接读缓存,降低 30% 延迟

回滚开关:当监控发现 “补全接受率” 连续 10 分钟低于 55%,自动降级为仅本地上下文,同时告警。

四、重构场景的额外注入

  1. 引用链注入:把 “实现 - 调用 - 导出” 三层节点全拉进来,让模型知道动一处会震三地。
  2. 接口契约注入:把 TypeScript 的 interface 与 Go 的 struct tag 一并召回,防止字段类型漂移。
  3. 改动影响面提示:在 Prompt 尾部追加一句 “若修改此接口,请同时更新以下 N 处引用”,N 由静态分析实时算出。

五、监控面板必看三指标

  • 注入命中率 = 有召回注入的请求 / 总请求
    目标 ≥70%,过低说明索引粒度太粗。
  • token 节省率 = 1 − 实际注入 token / 全文件拼接 token
    目标 ≥60%,低于预期就调小 “最大片段数”。
  • 补全接受率 = 用户采纳的补全 / 总返回
    目标 ≥55%,连续下跌即触发回滚。

六、常见坑与快速修复

  1. 同名符号冲突
    多语言仓库里 User 类可能同时出现在 java/kotlin/。给符号加 “命名空间哈希” 再建索引,冲突率从 12% 降到 2%。

  2. 向量噪声放大
    语义召回偶尔拉回无关注释。加入 “代码行数权重”—— 定义段 > 调用段 > 注释段,向量得分再乘 1.5/1.0/0.7,可让 Top 10 精准度提升 18%。

  3. 大文件边缘效应
    超过 3 k 行的文件,LSP 切片耗时长。预先把大文件按 “类” 级拆片并缓存,召回时直接读片,平均延迟从 800 ms 降到 220 ms。

七、小结

把上下文 “外包” 给检索系统,Agent 只拿 “当下需要的记忆”,是兼顾成本与准确率的最务实路线。只要守住三段阈值、盯紧三大指标,就能把跨文件补全的幻觉率压到可接受范围,同时让重构建议不再 “纸上谈兵”。


参考资料
[1] LSP 官方规范:符号索引与引用解析实践
[2] 向量检索在代码搜索中的实验报告,GitHub 工程博客,2025

查看归档