SpIDER: Spatially Informed Dense Embedding Retrieval for Software Issue Localization
Shravan Chaudhari, Rahul Thomas Jacob, Jiajun Cao, Shihab Rashid, Mononito Goswami, Christian Bock
EMNLP 2026 camera-ready
cs.SE, cs.LG
2025-12-18
SpIDER把检索名额锁在20个函数,沿代码图把稠密检索漏掉的近邻换进来。覆盖Python到TypeScript四门语言,Recall@20相对至少提高13%,接上调用边后至少提到27%。
编码 agent 改仓库之前,先要定位该改的函数。位置错了,补丁不完整,后面的 token 也白烧。BM25 靠词面重合。稠密检索把 issue 和函数嵌进同一向量空间,按余弦相似度排序,通常明显强于 BM25。两条路都不看结构:函数之间谁包含谁、谁调用谁。
正确函数常常就在高分命中旁边。SWE-PolyBench 上,单函数实例的正确函数落进 top-100 的比例,Python、Java、JavaScript 分别是 67%、77%、75%;多函数实例掉到 18%、15%、41%。即便相距不超过 2 跳(包含边),top-100 能一次收齐的也只有 34%、30%、53%。种子找得到,近邻排不进 Top-K。
函数级比文件级和类级更难,公开评测又大多停在 Python。SpIDER 把检索名额 K 锁死,用代码图把漏掉的函数换进来。
图在会话开始时按仓库现建。Python 用标准库 ast,Java、JavaScript、TypeScript 用 Tree-sitter。节点是目录、文件、类和函数,边有包含(contains)、调用(invokes)、导入(imports)、继承(inherits)。只解析主语言。图式与 LocAgent 相同,这里不做多轮游走,只做一次名额不变的交换。
Recall 会不会涨,只看一笔交换。准入精度 η 高于被挤掉榜尾的命中率 τ 才算赚。对照的是第 K 名稠密命中,不是仓库里的随机函数。近邻高于随机基线仍可能低于 τ,筛选器要负责把 η 顶过去。
超参只在 SWE-PolyBench 的 Python 划分上搜过,之后全部锁死。一次查询 5 次 LLM 调用,每次输入大约 3K 到 6K token。
主表只走包含边。底座 SweRankEmbed-Small 在约 3300 个 Python 仓库上训练,零样本使用。K=20 时,四种语言、三套基准的 Recall@20 全部更高,相对至少 13%,绝对 +0.05 到 +0.12。
| 设置 | 指标 | 稠密检索 | +SpIDER |
| Verified, Python | Recall@20 | 0.54 | 0.61 |
| PolyBench, Python | Recall@20 / Acc@20 | 0.42 / 0.35 | 0.49 / 0.40 |
| PolyBench, Java | Recall@20 / Acc@20 | 0.31 / 0.24 | 0.36 / 0.27 |
| PolyBench, JavaScript | Recall@20 / Acc@20 | 0.52 / 0.45 | 0.60 / 0.52 |
| PolyBench, TypeScript | Recall@20 / Acc@20 | 0.29 / 0.21 | 0.35 / 0.28 |
| Multi-SWE, Java / JS / TS | Recall@20 | 0.32 / 0.31 / 0.40 | 0.37 / 0.39 / 0.52 |
Acc@20 要求应改函数全部进 Top-K,论文给出的相对下限是 14%。套到 CodeSAGE 和 BM25 上同样为正,绝对分更低。零样本 SweRank 换到非 Python 后仍高于这两条基线。
LLM 重排从 20 个里再选 3 个,优势还在。Python PolyBench 上 Recall@3 从 0.38 到 0.44,Acc@3 从 0.31 到 0.36。
调用边消融只在 PolyBench 上做。深度 2 的调用边每门语言都高于包含边,两者合用最好,相对下限从 13% 升到 27%:Python 0.42 到 0.60,Java 0.31 到 0.42,JavaScript 0.52 到 0.66,TypeScript 0.29 到 0.48。绝对 +0.11 到 +0.19。一万次配对 bootstrap 下,四门语言 Recall@20 的 p 值都小于 0.01。只走包含边时,Java 的 Acc@20 是唯一不显著的格子。导入边、继承边不加分,继承边连不到函数节点。
token 大约差一个数量级。包含边每条 7.8K 到 14.9K,调用边 55.5K 到 109.9K。主表把包含边当默认。
mini-swe-agent 被绑在检索结果上,不能自由 grep。Verified 子集里,每个 K 的更优变体解决率都更高。最大差距在 K=10,包含边 0.681 对稠密检索 0.611,多 7.0 个百分点、18 条。这组用 81.5K token,超过 K=20 稠密检索的 0.650 和 185.3K token,用量是 44%。
改动在检索层,编码器不用重训。图用语法树现建,对着开发者当前这份仓库就行,不必先做离线全库索引。换入的函数带着种子和边类型,审阅时能看到它为什么进名单。
包含边只多 5 次调用,比迭代式图搜索 agent 大约便宜两个数量级,只比纯向量多一轮。K=10 的解决率可以超过 K=20 的稠密检索,token 更少。调用边更准,token 大约多一个数量级,不该当默认。
余量看语言。JavaScript 的稠密检索已经盖住不少实例。Java 的坑多在跨文件:调用连通度 0.63,四门语言里最高,稠密 top-100 覆盖只有 29%,又是最低。包含边走不到这些跨文件函数。多函数、跨文件的缺陷,才是这层交换吃得上的。
初始排序定了天花板。探索只在 Top-500 里进行。至少有一个正确函数排在 500 名之外的实例,Verified 占 38%,PolyBench 的 Python、Java、JavaScript 占 51%、62%、22%。这些函数图也够不着。
筛选器再丢一层。把 LLM 关掉、邻域候选全收进来,那种召回 SpIDER 只保住 79% 到 89%。两种错法附录里都有:种子擦边,真要改的邻居被否掉;筛选过松,邻居涌进来,把榜上原有的正确函数挤出去。
27% 不能挪到主表上用。调用边只在 PolyBench 测过,Verified 和 Multi-SWE-bench 的主结果仍是包含边。仓库只解析主语言,跨语言改动不在图里。函数粒度上没有继承边。深度从 4 增到 8,Python 输入 token 从 3.6K 涨到 29.7K,约 8.2 倍。d=4 是预算选择。相邻深度的差值约 0.01,盖不住筛选器抖动。
表 4 关掉了自由 grep,涨幅可以记在检索上。agent 还能自己搜时这 7 个百分点剩多少,不能从这张表外推。MRR@20 有几格反而下降:第一个正确函数若是后补进来的,会排在种子下面,首位变差。覆盖上去了,第一名不一定更靠前。