Skip to main content

2 posts tagged with "ai4se"

View All Tags

迈向真实世界的软件智能体:如何将语义高亮功能融入智能体编程?

· 26 min read
ayanami

引言

我们训练了一个代码语义高亮模型SWE-Pruner并开源了配套的Agentic接入框架,在真实的Coding Agent多轮任务(SWE-bench, SWE-QA)上,在保持性能不变甚至提升的情况下提供多轮场景下30%甚至更多的token开销减少。该模型基于语义理解,自动识别并高亮检索到的文档中语义相关的句子。而框架作为agentic的接入层,可以轻松接入claude code,openhands等SOTA agent系统

模型发布

在本文中,我们将分享我们的技术方案。

问题:编码代理的上下文膨胀

在先前的RAG工作中,上下文的粗粒度膨胀已经是一个很严重的问题 https://huggingface.co/blog/zilliz/zilliz-semantic-highlight-model

在生产环境中的 RAG 系统中,一个典型的查询会检索 10 个文档,每个文档包含数千个词元,每次查询消耗数万个词元。问题在于: 只有几十个句子真正包含相关信息 ,其余的都是噪声,这会增加成本并降低答案质量。

然而,对于真实环境下的软件工程任务,问题甚至更加严重:一个典型的查询可能涉及上百个代码文件,而一些大型项目中,巨型文件的代码token包含的远不止数千个,现代的编程agent却仍然在广泛使用关键词匹配的方法,即grep,来对大型代码仓库进行探索,匹配项的过分庞大使得现代agent中都配备了相当多的上下文硬截断逻辑

image.png

在人工智能代理场景中,这个问题会变得更加严重,因为查询是经过推理和分解后的复杂指令。传统的高亮显示方式只是机械地标记匹配的词语,却会遗漏真正有价值的分析结论。

这就迫切需要一种有针对性的高亮模型,该模型仅保留上下文相关的代码,并高亮显示它们,同时剪掉所有无关的噪声内容——这种技术也被广泛称为上下文剪枝。类似的纯文本任务也被Provence为代表的一系列工作较为完善地解决,但对于究竟如何在代码任务上引入语义高亮模型,社区尚未给出一个答案。

现有模型的困境

我们调研了现有的解决方案,但发现它们并不完全符合我们的需求。

Provence/XProvence,OpenProvence,zilliz/semantic-highlight-bilingual-v1沿用Provence的思路继续优化Provence的上下文长度和多语言性能。然而,它们并没有指出在代码等非纯文本场景下,如何构造合理的粗细粒度,如何满足额外的AST约束等。

arxiv-provence-paper

另一个困境是,现有的模型沿用BGE的类BERT管道,但大部分现代代码嵌入模型都是LLM架构,decoder-only的模型如何设计裁剪头来处理特征的交互和输出的合理性仍是一个待解决的问题——BERT自然双向注意,但decoder模型需要某些“代偿”因果掩码单向注意的方案。

我们的选择

既然市面上没有这样的代码剪枝模型,那我们自己训练一个

第一个问题是在代码中,如何将纯文本里面的细粒度“句子”转换成代码的对应概念?

我们提出两套方案,基于代码行和基于AST statements,鉴于数据的生产难度,我们选择了以行取代句子作为细粒度

第二个问题是,选择什么样的模型?

BGE-m3是非常好的模型,但它没有针对代码数据进行大规模预训练;BAAI的另一个优秀模型是BGE-Code-v1,但社区对它的适配相对较少

我们注意到了社区的另一个工作modernbert,然而出于数据分布或是模型表达能力等原因,我们并没有在这个模型上训练成功

我们的猜想是——代码任务对基础模型的能力提出了可能超越大部分旧BERT模型的要求

最后我们采用的是Qwen的新模型,Qwen/Qwen3-Reranker-0.6B, 它的海量预训练数据和世界知识产生了相当好的代码嵌入,0.6B的大小又和传统bert类模型开销接近,LLM模型作为基底的另一个好处是自然完成了多语言query的适配。

类似Qwen这种LLM based reranker依然采用Casual Mask,这对token打分实际造成了一些影响,添加一个Full self-attention模块能让模型在测试集上的F1上升接近0.1

我们实验了不同的架构设计和特征trick,发现几个训练技巧对这个任务比较好用:

  • 冻结大部分层:我们发现只解冻最后两层和全参数微调差距极小,而冻结之后更能避免过拟合,训练速度也更快
  • 特征融合:对于LLM不同层数的特征作用,在先前已经有了相当多的工作进行讨论,一般认为浅层会有更多的语法信息,而深层则更重语义,将浅层、中间层和深层做简单的concat接入下游即可显著提升token打分头性能(我们使用的是7, 14和28层)
  • Sample Loss Norm: 代码片段的长短有着很大的差别,更关键的是,与文本领域大部分的信息都是稀疏不同,保留的代码行既有稀疏的片段(大部分行被删除)、又有稠密的片段(大部分行被保留),且长度上也有比较大的区别。如果简单在token level计算,模型训练相对不稳定,将每一个样本的loss使用样本的长度归一化能更好的平衡长短样本
  • CRF Layer:我们特殊设计了Token打分头的模型结构,使用CRF层进行序列建模,取代BERT风格的mlp层,实验证明对于改进模型的稳定性有一定帮助

模型架构&训练

我们设计了一个如下图的模型架构,它在思路上借鉴了Provence,但细节做了大量改动

image.png

此外,我们使用了和Provence相同的做法保留了基础模型的原始rerank头, 即图中的Path B,这种Rerank-While-Prune的方式进一步降低了在RAG管道等场景接入模型的成本。

最终,我们使用8 * H100 训练了3个epoch,用时大约是4个小时。

我们的具体训练超参数可以在论文中找到。

推理过程:

推理过程非常直接:

  1. 将输入拼接为 指令 + 查询 + 代码
  2. 对上下文中的每个 token 进行打分(范围在 0 到 1 之间)
  3. 对每行内所有 token 的得分取平均,得到该行的得分
  4. 根据阈值保留得分高的行,同时移除得分低的行

一个有趣的细节是阈值的选择,在Provence的开源模型之中,他们推荐将threshold设置为0.1来保守裁剪;而我们阈值调优实验发现,和训练保持一致的0.5阈值就能在下游任务中达到最佳性能,而0.1似乎过于保守——0.3已经会保留非常多的部分,这也可能是代码数据的分布特点和文本不同所导致的,在下文中会详细说明。

训练数据:如何构造“部分相关”的查询-代码对?

我们使用Qwen-Coder-30B-A3B-Instruct,从github 2025年的新仓库上自行构建数据(再次感谢qwen团队的优秀工作!),在来自6k个仓库、19w个文件的20w条代码片段上,使用了9个种子任务构建数据,并使用Qwen3-Next-80B-A3B-Thinking进行质量筛选,最后得到了约5万条高质量“部分关联query code对”

为什么是“部分相关”?

与文本领域不同的是,代码领域的QA数据集相对较少,缺少类似ms macro这样规模的query,code对;另一个问题是,由于缺少搜索引擎商开源的数据,现有的code query数据集的query往往分布偏窄,常是“这个函数的功能是什么?”“这段代码的运行结果是什么?”这种与整段代码相关的查询

但我们想要在代码之中引入semantic highlight功能,需要的是对“部分代码”的查询,例如,“这一条if分支会产出什么结果?” “这个类的属性A的作用是什么?”,这样我们才能划分出highlight的部分

也就是说,我们最后的数据集格式会是类似 <查询,原始代码,highlight部分> 的三元组

如何保证数据质量

数据质量直接决定了最后模型的性能,乃至训练是否能够成功。我们采用了以下几种方法来提升数据质量和解决实际遇到的问题。

  • 问题:模型产出的query同质化比较严重
    • 解法:定义代码重构、代码debug、代码解释等9个种子任务,构建时随机抽取,让模型根据种子任务写短/中/长不同长度,不同相关程度的查询
  • 问题:代码片段太短,较难从中捕捉不同的执行流分支;代码片段太长,行数会变得过多(相比于Provence大部分段落在5-10个句子的情况,我们的代码段取的大约是100-150行)
    • 解法:让模型使用range格式表示最终的“相关片段”,即1,4-10,16-28这样的区间,来避免模型陷入不断输出下一个数字的坏情况
  • 问题:模型会认为大部分代码行都和query “有点关系”,相关度难以量化
    • 解法:借鉴Provence的prompt设计,让模型仅根据提供的代码片段回答query,并要求引用,仅将回答中引用到的片段视为相关部分
  • 问题:由于行数过多还是会出现一些bad case
    • 解法:将构建的数据集使用Qwen3-Next-80B-A3B-Thinking筛选,判断其在代码结构保留程度,query质量和相关性质量上的表现。代码结构的筛选也是我们的方法在裁剪后AST正确率上领先其他方法的重要基础,在编程语言这种高度结构化的文本中,缺少一个 :} 都会导致整个语法树崩溃。

      压缩方法AST 语法正确率 (%)
      无压缩 (Full Context)98.5
      Token 级压缩 (如 LLMLingua2)0.29
      Function RAG92.3
      Pruner + Function RAG87.3

为什么选择这些模型/数据集?

我们认为,github的原始代码最能够保证和真实用户数据的分布接近,同时具备足够的、真实场景的多样性

使用Qwen-Coder-30B-A3B-Instruct有以下几个原因:

  • 性能稳定,我们抽取了部分样例人工校验模型认为的“相关代码”是否符合人类的观点,它的符合程度相当高
  • 速度非常快——我们使用vllm的offline推理模式在24h左右的时间,8 * H100机器上完成了20万条数据的构建和筛选;而对应的GLM-Z1-32B等dense模型则需要一周

而使用Qwen3-Next-80B-A3B-Thinking则是在含有思维链情况下平衡速度和质量的一个优解

评估1:单轮任务

我们在多个数据集上对比了不同模型的性能,包括:

  • LongCodeQA
  • LongCodeCompletion

评估的方法包括 我们的模型, RAG, LLMlingua2, selective context, LongCodeZip 等, 实验中swe-pruner的压缩率向下取整,其他方法的压缩率向上取整(实在难调平)

image.png

主要发现:

  • 上下文噪声的去除不会影响QA的性能,甚至可能因为噪声的去除有所提升
  • 我们的模型在压缩率上要远远超出其他方法 —— 事实上为了调整不同方法的压缩率到近似可比,我们花了非常大的力气调整超参数
  • 我们的模型取得了所有压缩率下的SOTA

还有一个有趣的发现是,以Qwen3-Reranker-0.6B这种LLM based reranker作为基底,它的自定义instruction能力让下游任务的泛化更加轻松了。例如代码补全任务的query是上一行代码行而不是自然语言文字,这对于大部分模型其实是难以理解的,但我们可以在instruction中描述补全任务,使得模型在训练相对少的补全任务上性能也相当不错。

智能体服务:不止于 RAG

在构建了这样的模型之后,我们回到了我们的初始想法,如何将其融合到现代的Code Agent呢?虽然CodeRAG也是很广泛的范式,但也有相当多Agent是使用Grep作为搜索的工具,比如我们用得最多的Claude Code

更进一步的,会令上下文爆炸的,其实很多时候是ReadFile而不是Grep,我们想要削减上下文的开销的话,应该也对ReadFile处理好冗余上下文的问题

所以,我们设计了一个最小侵入的highlight模型接入方式,它不仅能服务于CodeRAG,还能服务于Grep, ReadFile乃至任何阅读工具

具体的做法是,假设有一个工具A,会产生一些输出,现在在工具A的原始参数基础上,增加一个参数context focus question,SWE-Pruner就可以根据这个question对A的输出进行裁剪 —— 而A可以是cat, grep, rag search, 甚至像是我们论文之中更暴力的bash

image.png

image.png

Agent模型负责根据当前运行的需要产生question, 甚至不产生question(留空则不进行裁剪),让整个系统留下了更多的灵活性和随基础模型成长的可能——在我们的实验之中,我们确实看到了顶尖的模型会评估自己当前的需求、预估命令输出的长度,从而灵活地选择简单或是复杂的 question,又或是不使用question来保证关键命令的输出完整性

这种仅对工具做包装的方法保留了最大的兼容性,我们实现了claude code sdk和openhands sdk等框架的兼容示例,可以在 https://github.com/Ayanami1314/swe-pruner/blob/public/examples/README.md 中找到

评估2:多轮任务

我们使用SWE Bench Verified和SWE-QA两个benchmark来评估这套方案的agentic能力,并在claude-sonnet-4.5和glm-4.6上做了实现,接入mini-swe-agent进行测试,结果也是比较喜人的——不仅明显地节约了token的开销,并且在正确率上能打平甚至微升(sweqa, sonnet)

我们也针对不同的方法再次进行了消融对比,即使不考虑速率的因素,我们的方法也比简单的大模型总结更加优秀——代码是一个相当难总结的形式。

真实场景案例研究:精准识别核心语句

除了基准分数之外,让我们来看几个更有趣的例子,以直观地展示我们的模型在实际应用中的性能。

模型在查看文件时,使用了 Focus on the MRO resolution logic in Inherit Docstring 作为context focus question,而SWE-Pruner在大量代码中精准定位到了高亮逻辑 —— Docstring的类定义,mro的部分代码逻辑

image.png

另一个例子来自SWE-bench的 django__django-10554 问题,Baseline 智能体和搭载了 Pruner 的智能体表现出了截然不同的行为模式:

配置方案运行步数读取操作搜索操作编辑操作Token 总消耗
Baseline1645939257,001,934
Pruner562010111,170,160
  • 传统的 Baseline 智能体倾向于采用“广度优先”的搜索策略。它会盲目执行类似 find . -name "*.py" | grep union 的命令,并不断使用 sed 命令在大堆文件中反复读取片段。这种方式虽然覆盖面广,但会导致大量无关紧要的代码进入上下文,最终造成上下文溢出,让智能体在冗余信息中迷失方向。
  • 相比之下,搭载了 Pruner 的智能体表现得像个经验丰富的架构师。它直接定位到核心文件 django/db/models/sql/query.py,通过带行号的 cat -n 进行读取,并迅速锁定了关键代码分支(if self.combinator: ...),避免了 Baseline 的无效试错

站在巨人的肩膀上

该模型的开发建立在大量前期工作的基础上,我们谨此感谢所有为我们的这项工作做出贡献的人:


  1. Provence的理论基础: 提出了一种有效的训练Rerank-While-Prune模型的方法,即句级打标与双头损失
  2. Qwen团队的优秀模型:Qwen3-Reranker, Qwen3-Coder-30B-A3B-Instruct, Qwen3-Next-80B-A3B-Thinking

在这些基础之上,我们提出了若干创新

  1. 将Provence双头设计首次迁移到Qwen3-Reranker这样的decoder-only LLM上
  2. 从github代码仓库中构建部分相关查询-代码对的数据集的完整管道与数据
  3. 模型结构和训练方法的优化
  4. 实际Agentic系统的接入方法创新,提出了context focus question这一工具包装机制并实验验证

我们诚挚感谢 Provence 团队和 Qwen 团队的奠基性工作。

Conclusion

在本文中,我们分享了我们从识别生产环境中 Coding Agent 中的上下文成本问题到构建最先进的代码语义高亮模型SWE-Pruner的历程。

SWE-Pruner 填补了粗粒度文件检索与细粒度 Token 压缩之间的空白, 并为代码、多轮的场景引入了一套新式的上下文压缩解决方案,比起对Agent的轨迹进行“事后”压缩,SWE-Pruner强调在“事前”过滤掉无关的代码,从根源上降低Agent在生产环境的token开销和注意力噪声。通过引入任务感知的行级剪枝,我们不仅显著降低了 AI 编码的经济成本,还提高了智能体在复杂代码库中的推理质量。

该框架具有极强的即插即用性,可作为标准中间件集成至 OpenHands,Claude Code 等主流 Agent 框架中,也可以与任意轨迹压缩方案集成,为构建更高效、更具扩展性的 AI 软件工程师提供了新的路径

Future Works & Limitations

  • 由于我们的算力有限,我们只进行了20万条数据、单语言环境下的迭代,多语言和更大规模数据量的scale或许是将来采用这项技术的团队的命题
  • 在Agent方案之中,模型产出的question质量对实验效果的影响较大,在将来社区如果能对基础模型进行后训练以专门适应包装形式的工具,预计效果仍会出现显著的提升
  • 生产环境的“屎山”代码仓库benchmark较为稀少,swebench等开源代码仓库还是不够“脏”,对于semantic highlight模型的发挥有一定劣势,希望在将来这个方法能被用于更冗长的代码库之中,并对这种极端情况下Agent应该如何工作做出一定参考意义

从现代Coding Agent视角回看代码搜索与嵌入

· 24 min read
ayanami

应该如何说起代码搜索呢,先说代码搜索的几个小的子流派吧,这方面可能略微和其他领域不同

代码的检索我认为是可以分解成以下几种的:

  • 搜索引擎式
  • grep传统搜
  • 向量embedding搜
  • 码仓index

每一种都是什么意思呢,举个例子

搜索引擎式是复用传统的搜索引擎,如elasticsearch, meilisearch等;也有一些变体,比如github的代码搜索,主要是弥补传统搜索引擎在代码搜索的不足

grep如其名字,基于linux grep或者riggrep,在agent内使用最广

向量embedding搜则有多个子任务,如NL2Code, Code2Code, Code2NL等,每个还有一些细微差别,这个很有意思,待会讲;

码仓index呢,则大致有两种:一种基于传统的AST分析,希望构建整个码仓的符号树或者CKG来辅助搜,另一种则寄希望于新型的生成式大模型,希望让LLM读了仓库之后能生成”索引“,典型的做法比如deepwiki。

搜索引擎式

传统的搜索引擎其实在代码搜上有很多问题,参见

https://github.blog/engineering/architecture-optimization/the-technology-behind-githubs-new-code-search/

最严重的问题甚至不是想象的NL2Code的问题,而是传统搜索引擎的部分优化不够、部分优化又不足,总之不与代码适配

  1. 索引的开销, 恐怖的内存消耗

当我们第一次部署 Elasticsearch 时,需要几个月的时间才能索引 GitHub 上的所有代码(当时大约有 800 万个存储库)。今天,这个数字已经超过 2 亿,而且代码不是静态的:它不断变化,这对搜索引擎来说是相当具有挑战性的。

  1. 可不可以不要索引?对大规模库高并发是不可能的:

首先,让我们探讨一下解决问题的蛮力方法。我们经常收到这样的问题:“你为什么不直接使用 grep?为了回答这个问题,让我们使用 ripgrep 对这 115 TB 的内容进行一些数学计算。在具有八核 Intel CPU 的机器上,ripgrep 可以在 2.769 秒内对缓存在内存中的 13 GB 文件运行详尽的正则表达式查询 ,即大约 0.6 GB/秒/内核。 我们很快就会发现,这对于我们拥有的大量数据来说确实不起作用。代码搜索在 64 个核心、32 个机器集群上运行。即使我们设法将 115 TB 的代码放入内存中并假设我们可以完美并行化工作,我们也会在 96 秒内使 2,048 个 CPU 内核饱和,以处理单个查询!只能运行一个查询。其他人都必须排队。结果是每秒 0.01 次查询

  1. 分词器不需要了:搜索引擎依赖分词来减少构建倒排的成本和作为基础搜索单元,但就和Tokenizer的引入一样,这个开销的减少从来不是免费的,接下来的问题就是:代码如何分词?另一个问题是:停用词全部没用了!无论是!@?/#$:{}()[]还是什么别的乱七八糟的符号,都是编程语言的最爱, 这使得传统的分词系统更加雪上加霜

    1. 如果提前跑一遍AST分析呢?我们的CPU要算爆炸了:), 并且你如何统一没有LSP小众语言,不同的语法版本(py2->3)... 工程量立刻爆炸了
    2. 能不能在char level倒排?太爆炸了索引,所以github是使用3-char的倒排的 argument -> arg、rgu、gum、...
    3. 代码里面还有注释,注释还有多语言,甚至单个仓库都很常见中英两种语言的注释...看起来朴素的分词器比如jieba要全面阵亡了...
  2. 基于git的增量更新:增量更新本身可以用merkel tree,这也是cursor在用的技术,但结合git版本?你发现事情变得复杂起来了,这是纯工程的复杂性。一次git commit涉及到十几个文件的几行变化,需要触发至少十几个chunk的embedding更新?如何处理多分支呢?我的每一个存储代码块是否还得加一个tag标识它的branch,然后在搜索引擎里面支持完备地按tag过滤,省的不同branch的代码在同一个搜索引擎中返回?(然后发现branch name作为tag简直太烂了,它是一个完全动态的无限的集合)

鉴于恐怖的存储代码数量,github采用的搜索引擎相当简陋,甚至是弱化版本的完全匹配:3-grams索引

对于代码搜索,我们需要一种特殊类型的倒排索引,称为 ngram 索引,它对于查找内容的子字符串很有用。ngram 是长度为 n 的字符序列。例如,如果我们选择 n=3,则构成内容“limits”的 ngram 是 limimimitits(二元组的选择性不够,四元组占用了太多空间)

这个索引显然是非常大无法放入内存的,所以github采用了一些传统数据库里面的懒加载和流式优化技术,使得可以仅读取一个小子集完成搜索

而关于构建索引本身,github还有很多特殊设计,但这其实属于system/后端任务了,不细讲:

  • 用Git blob object ID来分片,kafka分区

  • 用path, branch, repository + 元信息(owner, visibility, etc.) 来构建增量索引key

  • commit-level的一致性

  • Github相当多的blob是相同的,使用增量编码很有吸引力, 这里用到了概率上的近似数据结构和一些分布式图(近似)算法

    To determine the optimal ingest order, we need a way to tell how similar one repository is to another (similar in terms of their content), so we invented a new probabilistic data structure to do this in the same class of data structures as MinHash and HyperLogLog. This data structure, which we call a geometric filter, allows computing set similarity and the symmetric difference between sets with logarithmic space. In this case, the sets we’re comparing are the contents of each repository as represented by (path, blob_sha) tuples. Armed with that knowledge, we can construct a graph where the vertices are repositories and edges are weighted with this similarity metric. Calculating a minimum spanning tree of this graph (with similarity as cost) and then doing a level order traversal of the tree gives us an ingest order where we can make best use of delta encoding. Really though, this graph is enormous (millions of nodes, trillions of edges), so our MST algorithm computes an approximation that only takes a few minutes to calculate and provides 90% of the delta compression benefits we’re going for.


Grep

grep属实是在Coding Agent时代焕发了第二春,由于其系统级别自带+完美匹配Bash工具和Unix文本管道的特性,在现代的LLM之中都大量训练了如何写出各种米奇妙妙grep的数据

claude code这种经过更多优化的grep会更过分一点,它会有几个细节优化:

  • 使用更现代的rg(riggrep)代替原始的grep
  • 逆向cc源码可知,它的grep有七八个参数,分别对应grep里面的不同参数比如 -A -E -C , 除了一些呈现格式(比如带不带行号和文件名)之类的差别,主要的几个参数就是在匹配行前保留多少行、匹配行后保留多少行、和上下保留多少行
    • 如果读者熟悉coding agent的工作的话,其实早在swe-agent就已经探究过这个context window开多少的问题,原始论文的实验结论是50行
❯ tldr grep
grep
Find patterns in files using regular expressions.More information: https://www.gnu.org/software/grep/manual/grep.html.

- Search for a pattern within a file:
grep "{{search_pattern}}" {{path/to/file}}

- Search for an exact string (disables regular expressions):
grep {{[-F|--fixed-strings]}} "{{exact_string}}" {{path/to/file}}

- Search for a pattern in all files recursively in a directory, showing line numbers of matches, ignoring binary files:
grep {{[-r|--recursive]}} {{[-n|--line-number]}} --binary-files {{without-match}} "{{search_pattern}}" {{path/to/directory}}

- Use extended regular expressions (supports ?, +, {}, (), and |), in case-insensitive mode:
grep {{[-E|--extended-regexp]}} {{[-i|--ignore-case]}} "{{search_pattern}}" {{path/to/file}}

- Print 3 lines of [C]ontext around, [B]efore or [A]fter each match:
grep --{{context|before-context|after-context}} 3 "{{search_pattern}}" {{path/to/file}}

- Print file name and line number for each match with color output:
grep {{[-H|--with-filename]}} {{[-n|--line-number]}} --color=always "{{search_pattern}}" {{path/to/file}}

- Search for lines matching a pattern, printing only the matched text:
grep {{[-o|--only-matching]}} "{{search_pattern}}" {{path/to/file}}

- Search stdin for lines that do not match a pattern:
cat {{path/to/file}} | grep {{[-v|--invert-match]}} "{{search_pattern}}"

另一个有趣的事情是,现在的coding agent不约而同地使用了grep而不是rag作为其系统原生的工具,我觉得理由也是非常清晰的:

  1. grep的输出是标准可预测的,而rag的输出依赖于 {基础模型, 分块方法,召回topk,重排模型} 等多个配置参数,一个标准的输出带来的好处是 可强化学习, 如果对一个 code llm + rag 的系统做RL,最后的搜索策略一定会是拟合到和rag的embedding模型和具体策略相匹配,丧失了可迁移性
  2. 除此之外的好处也有很多,比如RL环境不需要embedding的额外开销(存储和计算上甚至编码成本上),整体轨迹可解释,精确匹配效果好,速度快...

rag的index开销其实相当大,学界不在乎这个,为了提升精度每个token一个embedding的方法也有,但一个embedding是一个1024维的向量,光存储开销就是4KB,对于百万行级别的代码仓库,其chunk可能在数万,达到了GB级别的存储开销

而工业项目有百万码仓,在TB级别的存储上进行高效地索引和查询着实压力很大,可以参考 美团和milvus/lancedb的相关文章 ,索引优化也有相当多的新实践,但这是做DB的人考虑的(雾

但Grep就是万灵药吗?并非如此

Grep提供了一个切面,能够让模型Agentic Search,根据搜索到的局部反馈调整搜索方法,从部分开始探索整个代码仓库——大部分需求的完成不需要对全仓的理解

——吗?

一个Grep的bad case是高阶语义的需求

  • 哪里导致了这个bug?
  • 某个模块的核心逻辑是什么?
  • 整体的代码结构?
  • ...

模型要么老实cat,要么就只能在log中见到它尝试“猜”你的变量名字,比如你问“...的实现”就会开始Grep impl,如果你把所有变量换成abcde,它立刻就GG了

比起失败的搜索浪费的上下文更糟地是浪费的交互轮数 —— 长达几百轮的agent轨迹是相当稀少的训练数据,如果再配合没那么好的历史压缩方法,或是没有精心设计的防止模型死循环的额外环境反馈,连续失败的grep会让agent的性能迅速地劣化

基于这方面的需求,在推理阶段Grep还是得配合别的工具,比如deepwiki,比如CKG(代码知识图谱,例如每个函数的caller和callee),

比如Code RAG

这方面也有一些新的探索,例如 https://cognition.ai/blog/swe-grep 的RL并行工具调用(关键不在速度,关键在减少交互轮数!),比如在工程侧融合Grep和RAG如https://github.com/daimh/mgrephttps://github.com/zilliztech/claude-context ,以及我们将要发的一篇文章(自吹自擂一下,关注主包后续的工作谢谢喵)


code embedding

主包主包,code embedding和文本的embedding有什么区别呢?为什么要强调code?

非常好问题,爱来自AI4SE。我认为code其实和图像比较像,某种意义上算是一种特殊的模态,不完全是文本——code某种意义上是“反语言常理”的,例如大部分语言的上下文有限,一句话很难和1000个字之前的某个东西形成强烈的联系,而这种长程交互在code之中非常常见——甚至有跨文件、跨模块、跨仓库的交互

而另一个很有趣的事情是,当我们在讲“某段代码的语义”的时候,这件事本身是模糊的,文本没有那么强的二义性,太阳就是太阳,月亮就是月亮,但一个递归斐波那契函数的语义到底是 “递归“ 还是 ”斐波那契“?这其实折射出了代码的某种特殊性,它同时具备字面义 ”斐波那契“ 和运行义 ”递归“(甚至”低效“、”算法“、“python”),而在一个代码仓库之中,代码还具备了上下游的属性:谁是我的caller,谁是我的callee?

这件事情为什么重要呢,因为传统的embedding向量相似度产出的是一个标量,它只能衡量一个维度的相似性!

  • 当你在说“查找与function A相关的代码”时,你想要的到底是什么?
    • function A的字面义相关的代码?
    • function A的运行效果相关的代码?
    • function A的caller/callee?
    • ...

然后你就发现从这个角度上来说,Code2Code的向量搜是很诡异的一件事情,至少传统的cos相似度无法干这件事——

  • 更悲伤(从学术研究的角度上来说或许是兴奋)的是,在现实需求中,我们真的不在意找到和一个function的字面意相关的function...假设你想要补全一段代码,你可能更需要关注谁会是它的caller,假设你需要优化一段代码,你可能搜索的方法是某种低效的pattern...而这些embedding相似度全部做不到
    • 据我所知,企业对这个接近摆烂了,只有MSRA有一个group还在研究,我之前溯源到的比较早的上下游建模技术是Order Embedding,感兴趣的或许可以试试做

直接结果是:我们只有NL2Code了

并且这个NL也只能关注一个方面...

什么样的NL才是真实会问会写的NL呢?Coding Agent的轨迹数据

没有轨迹数据怎么办?从大规模代码中挖掘注释作为NL

一个人写注释的方法和提问的方法不一样,这个语义空间的unmatch如何处理?各家自显神通

  • 例如2025年5月快手的OASIS: Order-Augmented Strategy for Improved Code Search,认为现有的code embedding往往关注的是代码的字面相似性,即只把代码认为是一种特殊的“语言”,而忽略了代码的非文字意义上的相似性

    • 对代码片段(结合其他静态分析信息),用LLM产生其作用的描述文本
    • 计算这个描述文本和其他代码片段的相似性,以此来挖掘难负样本
    • 因为这个文本描述的是相对High Level的函数作用,能够一定程度上避免变量名字等带来的影响,专注于实际作用
  • 24年12月的Nomic AI的cornstack

    • 强调<文档,代码>对的相关性重要性,并采用双重过滤:如果文档与代码间相似度低,或者并不在topK,只要有一个满足就筛掉
    • 动态硬负例挖掘策略:对于批内负样本挖掘,采用softmax概率采样,但是在训练过程中,逐步改变softmax的温度,前期温度高提高多样性,后期温度低,注重难负样本的区分
  • 25年5月的BAAI Towards A Generalist Code Embedding Model Based On Massive Data Synthesis

    • 强调退火训练,第一阶段纯文本,第二阶段全数据训练text-code能力,第三阶段纯代码

码仓Index - DeepWiki/CKG

这个说起来就比较简单了,deepwiki重要的始终是LLM的能力,而CKG则是静态分析的质量,开源的tree-sitter固然可用,企业也有一些统一各种语言的私有AST,静态分析已经日趋成熟,困难的是如何将这个信息给到LLM

很早在Google的博客中就有论述: 现在的vibe coding就像是把一个几千行的代码粘贴到记事本里面,然后让程序员来修改bug —— LLM看到的就是 “记事本”,而不是程序员的带有各种Lint和跳转的IDE界面

AST分析树如此庞大,除了摆烂式地提供一个获取caller/callee的mcp工具给agent之外,还可以做些什么?

先前在web领域有一个llms.txt的旨在LLM-friendly的格式,代码领域却暂时缺乏哪怕是新兴的统一处理格式

AI-friendly IDE可能对于Coding Agent的能力提升相当重要,这也是moonbit社区他们宣传的,不过我没有实际上手用过,也不是学PL的,就不瞎讲了,感兴趣的可以看张宏波的演讲

【AI时代下的基础软件 | 张宏波 刘子悦 蚂蚁&MoonBit Meetup杭州站回放】

https://www.bilibili.com/video/BV1wL8DzgEXZ/

除此之外,可能我们原先认为不能搜索或者没必要搜索的部分,现在也正在发挥着额外的作用

  • python的package,众所周知(可能并非),pip包只是一个特殊的压缩包,可以直接看到文本格式的原始代码,现代的claude-sonnet-4等模型在环境出错的时候会主动读/搜 pyproject.toml, .venv等特殊文件,遇到import的不了解api还会尝试进入package观看源码
  • 而某些binary风格或是一串神秘哈希引入的包可能对于LLM并不是很友好...

除此之外也有很多新兴的想法,例如注释本身可不可以作为一个天生的码仓Index? ...

CLI版本的Coding Agent好处是可以在各处方便的引用,尤其利于大规模并发采集数据

IDE版本的Coding Agent则会更加地“懂人”,原因是AI IDE在背后做了一大堆不仅仅是diff等格式渲染的工作,用户环境信息,用户系统信息,... 这些都被从后台塞入了Coding Agent的system prompt,使得你在Linux上运行Copilot的时候,模型不会让你执行 brew install命令

但文本本身依然有着局限,或许在某个未来,我们能看到真正的code native架构,不在绞尽脑汁地想把编译器的报错,AST的分析等等原本结构化的东西转成markdown再塞入永远不够的agent上下文...