ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

给大模型建代码索引,先解决两个 submit 方法互相覆盖的问题

给大模型建代码索引,先解决两个 submit 方法互相覆盖的问题

订单服务和退款服务都可能有一个submit方法。若索引直接用方法名作键,后注册的源码会覆盖前一份。后续检索即使命中了“正确名字”,模型拿到的也可能是另一条业务链路。这类问题比“模型上下文不够长”更早发生。

代码知识库需要先把“这个符号是谁”说明白,再讨论从哪个模型得到解释。封面展示的是检索和模型路由的概念关系,不表示某款模型已经理解全部企业代码。

语法节点不是完整的调用关系

Tree-sitter 的查询文档说明了如何按节点类型和字段匹配语法树。它能帮助我们可靠地找到方法声明,但从声明走到语义调用图,还涉及重载解析、依赖版本、动态分派和生成代码。

因此,用正则抓出方法名、向后截取固定 30 行,适合演示搜索过程,不能称作完整 AST 或代码属性图。源码里的括号、注释和内部类都会影响边界;跨文件依赖更不能用“函数体里出现过这个单词”代替。

图中两个submit进入不同的符号格。至少需要仓库修订、模块、相对文件路径、所属类型和完整签名。修订号参与索引身份,是为了避免回答时把新接口和旧实现拼在一起。

先把身份规则写成可检查的代码

下面只演示符号键和去重,不解析 Java 源码,也不构建调用图。每个键由字段列表组成,避免自己发明分隔符后遇到转义歧义。

import java.util.HashMap; import java.util.List; import java.util.Map; public class Demo { static List<String> key(String revision, String module, String path, String type, String signature) { return List.of(revision, module, path, type, signature); } static void put(Map<List<String>, String> index, List<String> key, String source) { if (index.putIfAbsent(key, source) != null) { throw new IllegalArgumentException("duplicate symbol identity"); } } public static void main(String[] args) { Map<List<String>, String> index = new HashMap<>(); List<String> order = key("r1", "order", "Order.java", "Order", "submit(long)"); List<String> refund = key("r1", "refund", "Refund.java", "Refund", "submit(long)"); put(index, order, "order-source"); put(index, refund, "refund-source"); if (index.size() != 2 || !index.get(order).equals("order-source")) { throw new AssertionError("same-name collision"); } boolean rejected = false; try { put(index, order, "replacement"); } catch (IllegalArgumentException expected) { rejected = true; } if (!rejected) throw new AssertionError("duplicate accepted"); System.out.println("two qualified symbols retained; duplicate rejected"); } }

保存为Demo.java,运行javac --release 11 Demo.java和java Demo。测试只验证限定键不会因同名方法而覆盖,以及重复登记被拒绝。它没有证明真实仓库检索召回率或回答正确率。

上下文预算必须有停止条件

符号身份确定后,再沿依赖边收集接口、直接调用和必要的数据类型。深度上限只是一个保护:一层依赖也可能展开几千个节点,因此还需要节点数、总字节数和真实 tokenizer 的 Token 预算。达到预算后输出“哪些依赖没有纳入”,比悄悄截断一个方法体更有用。

增量更新也要以修订为单位发布。先构建一份完整可查询的新快照,再切换索引引用;不要在同一张表里逐个覆盖,让读请求看见一半新一半旧的代码。删除的符号要有墓碑或重建机制,否则过期实现仍可能被检索出来。

最后,检索到源码不代表请求者有权限阅读它。仓库权限应在取片段之前检查,索引和缓存也需要租户隔离。本文没有运行 Tree-sitter、向量库或真实模型,跨语言解析、动态调用解析和权限集成都需要在实际代码库里验证。先让证据对应同一个修订,再让模型组织解释,回答才有可追溯的基础。

本地运行环境为 Java/javac 12.0.1,使用--release 11编译并执行了文中的检查;没有安装新依赖。该结果只覆盖示例列出的逻辑,不代表真实模型、浏览器或生产链路已经验证。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进