实测CodeNib论文中的MCP检索策略:33文件仓库MCP比grep多消耗4.1x token,而249文件时节省86%。相同策略,效果取决于仓库规模。
TL;DR:将 grep 替换为 MCP retrieval 工具,在 33 个文件的代码仓库上让我的编码智能体多消耗了 4.1 倍 token;而在 249 个文件的仓库上则节省了 86%。同一模型、同一任务,结论截然相反。
我花了一个周末复现了论文 CodeNib 中的智能体实验,设备是一台没有 GPU 的 Windows 笔记本,对抗两个我自己写的代码仓库。八道行为题、四个实验组、32 次智能体运行,每个 token 都直接从提供商响应中读取。
在写任何代码之前有两条规则。永远测量,不估计。如果结果与论文相悖,那才是有趣的结果,不要去粉饰它。
第二条规则保护了我不被论文误导。但它没能保护我不被自己误导——这就是本文最后三分之一要讲的内容。
CodeNib(arXiv:2607.25431)针对单次仓库提交构建了三个物化视图:词法索引、稠密嵌入索引和符号图。摘要中有这么一句话,是我阅读这篇论文的原因:使用其上下文策略后,你的编码智能体比配对的 grep/read 智能体少消耗 50% 到 87% 的轨迹 token。
我想知道在真实硬件上这一结论是否还成立。
纯 CPU 笔记本的墙上时钟时间无法与 H100 运行相比,所以我不会把它们放在一起报告。Token 计数与硬件无关,而这正是我测量的东西。
SalesRabbit 的 TS/JS 文件中有 6,032 个是 node_modules。Leadpipe 的 8,693 个"后端 Python 文件"中有 8,558 个是 backend/.venv。
这个差距原来是整篇文章的关键。在 33 个真实文件上,发现(discovery)很廉价,一个让发现更廉价的系统没有什么可卖给你的。
这是摘要没告诉你的东西。论文的三个实验组使用完全相同的工具。只有提示词历史不同。
grep/read:历史以 [S, Q] 开头。智能体自行发现一切。
eager:历史以 [S, Q, C10] 开头,其中 C10 是冻结的排名前 10 的嵌入排序的可调用块,在运行前计算一次。
eager + compact:起始相同,然后在首次成功读取后做一次确定性的历史重写。它保留去重后的读取路径、最新读取结果的完整内容,以及最新助手消息的前 600 个字符。其余全部丢弃,不运行任何摘要器。
所以"50% 到 87%"不是一个数字,也不是一个机制。它是每个模型的最优实验组。Gemma 4-12B 给出 87%。Claude Haiku 4.5 给出 50%,而对 Haiku 来说压缩实际上让情况更糟,达到 eager token 的 123.3%,这就是为什么论文自身的选择规则为 Haiku 选择了纯 eager。那个负面结果在正文里,不在摘要中。
我打算做的是不同的。固定提示词策略,替换工具集,用 codenib mcp 中的工具替换 grep 和 read_file。这是一个工程师真正会问的问题。但这也不是论文的实验,我在运行任何东西之前就把这个区别写到了一个文件里,这样之后我就没法自欺欺人。
测试框架以咖啡命名,因为我接下来两天都要盯着它,名字好歹要让自己开心一下。brew_kit.py 持有共享循环。filter_menu.py 是对照工具集,只有 grep 加上 read_file,没有别的。espresso_menu.py 通过 stdio 实时从 codenib mcp 拉取工具。cafe.py 运行整个框架。
循环对它持有的菜单一无所知。把一个换成另一个只会改变工具集,不会改变其他东西。同样的系统提示词、同样的 16 轮上限、同样的温度 0、同样的模型。
Token 计数是提供商报告的,按调用逐次累加,跨越整个轨迹,这就是论文自身的定义。完整转储以 JSON 形式每次运行输出一次,所以每个数字都是可审计的,而不是靠信任。这个决定值回了两次成本,你会在下面看到这两次。
33 文件仓库上的第一组配对结果。
两个智能体都在相同轮数找到了正确的文件。以下是差距的分解。
在第 1 轮,用户消息在各实验组间相同,治疗提示词仍然大 875 个 token。这是纯粹的 tool-schema 开销。九个 MCP 工具,每个都有长段落式的描述,而对照只有两个紧凑的工具描述,在任何代码被检索出来之前每一轮都要为此付费。16 轮下来大约是 14,000 个什么也没做的 token。
如果你正在把一个 MCP 服务器接入智能体循环,这个数字才是需要记住的。工具描述是提示词,你每轮都要为此付费。
论文的会计方式无法暴露这个问题,因为它的各实验组共用同一套工具集,schema 成本天然相互抵消。当工具集成为变量时,它就无法抵消了。
第二个成本更微妙。每个 search_semantic 结果都附带完整的函数体作为内联返回。确实是更好的证据,但它进入了历史,并在之后的每一轮被重新计费。对照智能体的 grep 只返回了单行 60 个字符的内容,然后只读取了恰好 6 行。
在小仓库的三项任务上,治疗组消耗了对照组的 410%(中位数),正确性同为 3/3。
然后我换到了 249 文件的仓库。
lp-dberror 问的是:违反数据库约束后,被转换成可读语句而不是原始 Postgres 错误字符串的地方在哪里。
对照智能体燃尽了全部 16 轮。14 次 grep、2 次读取,没有任何 FILES: 行。47,276 个输入 token,什么都没找到。每次失败的 grep 都留在历史中,并在每一轮后续被再次计费。
我不是为了产生这个结果而构建那道题的。我在运行任何东西之前就从源文件写出了全部八道题,用行为方式措辞,所以从不提及文件名或函数名。我还检查了每个目标符号都恰好定义在一个地方,所以正确答案没有歧义。智能体只是猜不到"约束违规的可读消息"藏在 db_errors.py 里,以 SQLSTATE 编码如 23514 的形式存在。
治疗智能体运行了一次语义搜索,两轮就答完了,只用了 6,569 个 token。这是对照组的 13.9%。同一模型家族的论文数字是 12.9%。
彼时我还没有运行论文的实验,所以我自己构建了。eager 和 compact 使用对照的两套工具,所以它们完全不付 schema 税。CodeNib 只离线出现,作为冻结候选的来源。
论文的机制可以复现。57% 的总体节省,没有正确性损失,应用论文自身的选择规则选出了 compact,正如论文为 Gemma 选择 compact 一样。
每行下面都是同一个检索引擎。证据如何到达模型比模型手里握着什么工具更重要。
然后我按仓库拆分了同一张表。
在更大的仓库上,compact 使用了 grep/read token 的 32.3%,在论文声称的范围内,正确性 5/5,而对照组是 4/5。
在小仓库上,每个实验组都输了,eager 最严重,达到 294%。
最后一行部分洗脱了我的工具交换结果。我对 4 倍惩罚的简洁解释是 875-token 的 schema 税。但论文的实验组完全不付 schema 税,它们在那里也输了,而且输得更多。所以主导变量既不是工具集也不是投递策略。
关键是对照智能体的 grep 本身能不能成功。在 33 个文件上它总是成功,三轮,大约 3.6k token。我测试的每种机制都是在为一种永远不会发生的螺旋买保险。
eager 和 compact 接收字节级相同的候选,所以它们的对比是唯一干净地隔离保留(retention)的方式。它让 compact 处于 eager 的 59.9%。论文报告 Gemma 4-12B 是 27.9%。方向相同,选中的实验组相同,规模大约是一半。用 8 个任务对他们 100 个快照,符号一致是我能声称的全部。
我刚写完一段自信的段落,讲治疗组是如何平坦且可预测的。下一道题把它摧毁了。在 lp-sms 上它用的轮数比对照组少,却仍然多消耗了 2.4 倍。
turn 1: 1,179 tokens search_semantic
turn 2: 4,980 search_bm25
turn 3: 9,137 search_regex
turn 4: 9,203 search_bm25
turn 5: 13,296 (answer)
四次搜索,跨越四种不同工具。广泛的工具表面改变了模型遵循的策略,不只是每次调用的成本。九个工具呈现九种合理的下一动作,而尚未有信心的模型会多试几个。两个工具强制收敛或死亡。
没有任何固定工具集的实验能观察到这一点,这就是为什么我认为这是运行中真正全新的东西。我很高兴这道题打破了我的泛化,因为我早已把它写下来了。
对 33 个文件的仓库建立索引,45 分钟后仍未完成。我在等待时阅读了向量存储的源代码,发现嵌入模型的序列长度上限默认为 8192 个 token,并断定这显然就是问题所在。
然后我没有修复,而是做了基准测试。
序列长度几乎无关紧要。分块大小约为 400 个 token,Transformer 会对批次中最长的项进行填充,所以 8192 的上限从未被触及。
真正的罪魁祸首是批次大小。sentence-transformers 默认为 32,并按长度从大到小排序,所以第 0 批会将 32 个最大的分块打包在一起。我观察到工作进程的 working set 在 15.3 GB 机器上峰值达到 6.9 GB,然后才开始发生内存抖动。将批次大小降至 4 后,索引时间从 45 分钟未完成变成了 212 秒。
我差点就提交了那个序列长度的修复。它本会让检索质量下降而不带来任何提速,而且我还会相信它确实有效——因为与此同时我正在做的批次大小修改才是真正在起作用的部分。
然后更大的仓库直接推翻了这个结论。它的 Python 分块触及了 300 行的分块上限,所以在那里序列上限确实占据了主导地位,正如我最初为那个错误的仓库所做的错误猜测一样。两个旋钮都很重要,哪个占主导取决于你的分块大小分布。
在 Windows 上安装 CodeNib 会遇到的错误
每一个都耗费了我大量时间,而这些在论文的 Linux 环境中都不会发生。
索引时出现段错误,退出码 -1073741819,即访问违规,在 248 秒时发生。torch 和 faiss 各自携带了自己的 OpenMP 运行时 DLL,两者加载到同一个进程中。设置 KMP_DUPLICATE_LIB_OK=TRUE 可以修复。
pip install codenib 只会给你三个视图中的一个。只有词法视图。没有 dense、没有 graph、没有 MCP。codenib doctor 可以立即捕获这一点,而且确实是一个设计良好的命令。如果我信任默认值,就会对一个 BM25 包装器做基准测试然后称之为 CodeNib。有了 extras,虚拟环境达到 863 MB。
结构化视图根本无法从发布的包构建。它需要外部 SCIP 二进制文件(scip-typescript、scip-python),而 graph extra 并没有提供它们。在两种语言上都失败了,所以九个 MCP 工具中有四个在每一轮中都占用着 schema token,却无法返回任何内容。这是我数据对 CodeNib 不公的最明显之处。
将 212 秒索引与永远不会完成的索引区分开的那个参数,无法从 CLI 到达。register_default_builders() 接受 embedding_batch_size 和 embedding_max_seq_length。但 codenib index 没有将它们穿透过去。两行 argparse 的事。
每次 POST 都返回 HTTP 403、Cloudflare error 1010,而 GET 正常工作。Cloudflare 会禁止 User-Agent: Python-urllib/3.12。
API key 正确却返回 HTTP 401,这是骗了我最久的一个。PowerShell 5.1 的 Set-Content -Encoding utf8 会写入字节顺序标记,所以 Python 读到 csk-... 前面有一个不可见字符,然后把它发送了出去。用 encoding="utf-8-sig" 读取文件。
一次运行失败,错误信息为空。[sr-hmac] espresso FAILED: 冒号后面什么都没有。空字符串就是线索:concurrent.futures.TimeoutError 会字符串化为 ""。在消息旁边打印 type(error).__name__,这样这种事就永远不会发生在你身上。
审计我自己的报告
最后一次会话没有运行新实验。我直接从收据重新计算了每个发布的数字,使用了一个与我的聚合器不共享代码的脚本,所以任何 bug 都无法隐藏自己。
每个发布的数字都站得住脚。有一个比我应得的更好。 我曾报告 schema 税为"约 875 个 token",而第一轮 delta 在所有八个任务上恰好是 875,因为 schema 是固定的,系统提示词是共享的。
然后我开始寻找那些文字在发挥作用而数据并不支持的地方。我找到了四处,而且没有一个是错误的数字。
一处截断我只披露了一个分支而没有披露另一个。我的结果文件标记了 MCP 观察的 12,000 字符上限,并将其称为使治疗分支受益的注意事项。很好。但我的候选构建器有 MAX_BLOCK_CHARACTERS = 2000,我从未写下来过。它在 sr-loop 的 10 个分块中绑定 5 个,lp-decision 中绑定 3 个,lp-city 中绑定 2 个。这缩小了 C10,从而降低了 eager 和 compact 的 token 消耗——正是产生我头条结果的两个分支。我披露了那个使负面结果看起来更糟的注意事项,却忘了那个使正面结果看起来更好的注意事项。
压缩通过消耗轮次来购买 token。我报告了平均轮次:eager 为 2.4,compact 为 2.6,然后就到此为止了。按任务来看,压缩在 8 个任务中的 4 个上提高了轮次,在一个上降低了轮次。改写会丢弃 C10,所以当第一次读取不是正确的文件时,智能体就会再次寻找。而在别处我论证过,在限速端点下,将模型调用减半会将挂钟时间减半。如果轮次在工具交换分支带来它们时是一种收益,那在压缩拿走它们时就是一种成本。
有一次运行,found 衡量的是检索行为,而不是智能体行为。在 lp-decision 上,eager 和 compact 都直接从 C10 中回答,零工具调用,产生了字节级相同的收据。根据论文的公式这是正确的,因为没有一次读取成功过,所以改写从未触发。这也意味着系统提示词中的"在检查代码之前不要发出 FILES"在 32 次运行中的 2 次中被违反了,这是对候选注入坐在论证它的那一节内这个事实的一个轻微的反对点。
一个悄然失败的复现步骤。tasting_notes.py results_*\receipts.json,我自己的 REPRODUCE.md 中的最终聚合命令,在 PowerShell 下写入一个空表并以 0 退出。PowerShell 不会对原生可执行文件的参数进行 glob 展开,模式作为字面字符串到达,is_file() 返回 False,而我的收集器有一个裸露的 continue。任何从文档端到端阅读的读者都会得到一个空结果文件,而没有任何出错的迹象。这是我发现的最糟糕的事情,因为这是读者实际运行的部分。
还发现了一个潜伏的 bug,它没有触及任何发布的内容。matches_expected 是一个无锚定的双向子字符串测试,所以 policy.py 会匹配 expected y.py。我 32 次运行的每一次都引用了精确的仓库相对路径,所以正确性列是可靠的,通过锚定版本重新评分所有 32 次产生 0 次翻转。在一个文件名不那么整洁的仓库上,它会悄无声息地膨胀。
这个模式值得命名。这些都不是错误的数字。它们是在对故事伤害最小的地方放置的真实陈述。审计错误数字不会发现任何东西。
当代码检索值得花费 token 时
小仓库、常规命名、grep 通常在两三轮内命中:这就让你花费了 token。在工具交换上测量为 143% 到 412%,即使在论文最佳分支上也达 129%。在这里停手。
大仓库、行为问题、出现在标识符中的领域词汇:它带来的回报是自身的多倍,并将失败转化为答案。在完全这样的任务上测量为对照组的 14%。
方差缩减才是真正的产品。对照组 token 范围从 3,531 到 47,276,跨度达 13.4 倍。你购买的是更紧的尾部,而不是更低的均值。
关于我自己的数据的一句实话。这些是每个条件下的单条轨迹。它们在 temperature 0 下可以精确复现,但这建立在一个重复任务上,而 8 个任务无法支持置信区间。我声称的是效应大小和方向,而不是显著性。
我下一步会做什么
将九工具 MCP 表面缩减到三个(search_semantic、search_bm25、get_manifest)然后重新运行。这削减了固定的 schema 税,收窄了动作空间,我预计小仓库惩罚的大部分会随着 lp-sms 内存抖动的消失而消失。
仓库有完整的构建日志,记录了我按遇到顺序的所有死胡同,所有 32 份收据和 transcript,以及一份 REPRODUCE.md,包含你在每个阶段应该看到的确切命令和检查点数字。提交原始收据,而不是汇总表。这是审计能够发现任何东西的唯一原因。
CoffeeAurCode / CodeNib-Replication
对 CodeNib: A Multi-View Data System for Serving Repository Context to Coding Agents (Yu et al., arXiv:2607.25431v1) 的部分、可测量的复现,在一台纯 CPU 的 Windows 笔记本电脑上针对两个真实的私有仓库运行。
头条:当发现成本高昂时,论文的 eager + compact 策略可复现——在 249 个文件的仓库上,grep/read token 仅为对照组的 32.3%,在论文声称的 50–87% 范围内,正确性 5/5 对比对照组的 4/5。在 33 个文件的仓库上,每个分支都输了。决定结果符号的变量不是工具集,也不是投递策略;而是对照组智能体的 grep 原本是否会成功。
如果你也做过智能体上下文实验,且基线 grep 智能体表现很强,我很想知道你的代码仓库规模。这个变量对我结果的影响超过了我构建的任何东西。
For further actions, you may consider blocking this person and/or reporting abuse