内容提要
JetBrains 团队分享了构建语义代码搜索 RAG 管线的经验:使用语言解析器进行结构感知分块,避免固定切分破坏语义;向量化时保留全部维度并压缩为单比特,通过汉明距离检索以大幅节省存储;嵌入文件路径辅助定位;不存储源码、不用数据训练以保护隐私。
延伸解读
结构感知分块:为何固定切分不可取
文章指出,固定行数切分会将无关代码(如导入语句与函数体)混在一起,破坏语义完整性。JetBrains 利用语言解析器进行结构感知分块,保留文档、注解等前缀与声明一起,并移除无意义注解。这种基于语法节点的切分能确保每个块包含足够上下文,提升检索准确性。
向量压缩:维度比精度更重要
在存储预算有限时,文章建议保留全部维度但降低精度(如1比特),而非截断维度。因为每个维度代表一个语义问题,保留所有维度能维持对每个问题的粗略回答,而截断维度会丢失信息。测试表明,4096维1比特的检索效果优于128维32比特,尽管后者精度更高。
二值量化的局限:阈值判断失效
二值量化虽节省存储,但压缩了相似度分数的范围,导致相关与不相关向量的得分差距变小。这使得基于绝对阈值的功能(如主动推荐代码)难以设定合理 cutoff。因此,在需要绝对相关性判断的场景,文章仍保留16位浮点数,以维持可用的分数区分度。
隐私保护设计:不存源码、不用于训练
JetBrains Context 不存储源代码,仅保存文件路径、偏移量等坐标信息,检索结果在用户本地组装。所有嵌入由自托管开源模型完成,数据不出基础设施,也不用于训练。这些设计在保证隐私的同时,经内部基准测试,检索质量并未受损。
Q&A
为什么语义代码搜索对AI编码代理很重要?
传统的关键词搜索和grep要求代理预先知道确切的搜索文本,但代理需要按含义搜索代码(语义搜索),才能处理抽象领域。例如,代理寻找会话令牌刷新的位置时,不能依赖代码中包含“refresh”这个词。语义搜索通过RAG索引代码语义,允许代理用自由文本按需检索相关片段,从而发挥代理的优势。
在构建RAG管线时,为什么固定大小的分块方法效果不好?
固定大小分块(如按固定行数分割)会导致语义上不相关的代码片段被分到一起,例如导入语句和函数内容混合,从而在检索时产生错误。更好的方法是利用源代码的结构(如Java的导入、类定义、字段和方法),进行结构感知的分块,以保持语义完整性。
JetBrains的RAG管线如何处理代码分块?
JetBrains使用语言解析器进行结构感知分块。解析器将源文件分解为语法节点流,分块算法根据节点类型和大小决定分块范围,保留最大的语法单元,仅对过大的单元进行细分,并合并小单元以避免碎片化。同时进行规范化(修剪空白、删除空行、调整缩进),并将文件路径作为元数据与块一起嵌入。
为什么在向量化时选择保留所有维度并压缩为单比特,而不是减少维度?
保留所有维度并压缩为单比特(二进制量化)比减少维度但保持高精度检索效果更好。每个维度代表模型学到的一个小问题,保留所有维度可以保留对所有问题的粗略答案,而截断维度会丢失信息。实验表明,在相同存储预算下,全维度单比特比少维度全精度检索效果显著更好。
二进制量化后,相似度计算和存储有什么变化?
二进制量化将每个向量分量变为1比特(非负为1,负为0),存储减少32倍。相似度度量从余弦相似度变为汉明距离,即比较两个比特模式不同的位置数。计算时,4096位向量存储为64个64位字,通过异或和计数1来快速计算汉明距离,每次比较只需约一百条指令,远快于浮点余弦相似度。
JetBrains如何保护客户源代码的隐私?
JetBrains采取两项措施:1) 不存储源代码,块只包含集群引用、项目类型、文件路径、起止偏移量、向量引用和可选元数据,不保存代码内容;2) 不使用数据训练模型,所有索引使用自托管的开放权重嵌入模型,嵌入请求不离开基础设施,确保数据不用于训练。