你的编码代理正在通过搜索代码库浪费 Token。这里有一个命令即可解决。

Dev.to AI 2026-06-28T22:19:46.126837

graphlens-mcp 为 Claude Code、Cursor 以及兼容的客户端提供代码的类型化图,因此它们可以问"谁调用了 create_order?"并得到一个小的答案,而不是读取半个代码库。下文:引擎的工作原理、一个 936 次运行的基准测试关于它何时真正有回报、以及五分钟的安装。我一直在公开构建 graphlens。完整的故事在 Habr 上有三篇文章——引擎、基准测试、产品——但这个版本自成一体;我把重要的内容都整合进来了。文末有链接。

大家都知道的循环

想象一个大型项目。几十万行代码,后端是 Python,前端是 TypeScript,还有一个没人想碰的遗留角落。你用一个编码代理指向它,问一个普通问题:"这里的认证是如何工作的?"或者"如果我改变这个方法的签名,会破坏什么?"代理无法一次性看到整个代码库。所以它只能做它唯一能做的事:搜索一个名字,打开一个文件,读取它,跟随一个导入,再次搜索。它读取十几个文件,每一个都进入上下文窗口,每一个都在下一轮被重新计费。

这不是假设性的开销。Anthropic 自己的工程博客指出,工具定义和中间结果可以消耗"50,000+ Token,在代理读取请求之前"——窗口在代理甚至还没开始处理你的问题之前就填满了。

代码图正好针对这个问题。代理不是"读取文件并目测",而是问一个精确的问题——谁调用了 create_order——并返回一个小型结构化答案:已解析的边,而不是文本搜索和祈祷。

这就是核心卖点。这篇文章的其余部分是关于它是否站得住脚,以及让它变得可用需要什么。

引擎实际做了什么

"代码 → 图"的难点不是画框。而是边。大多数轻量级工具通过名称解析引用:它们看到对 save() 的调用,就画一条边到所有名为 save 的东西。快,但错误——一个真实的代码库有十几个 save

graphlens,MCP 服务器下的引擎,将工作分为两部分:

这些解析器与你的 IDE 运行的相同:Python 使用 ty(Astral 的 Rust 类型检查器),TS 使用 TypeScript Compiler API,Go 使用 gopls,Rust 使用 rust-analyzer。

因此,CALLS 边指向实际函数,HAS_TYPE 指向实际类,INHERITS_FROM 指向实际基类。这是"可能相关"和"相关"的区别。引擎知道 services.py 中的 process_order 是从 api.py 调用的那个,而不是 tests/ 中的同名函数。这就处理了第一道墙——名称歧义。

第二道墙是大多数代码智能工具都是单语言的。它们能优美地理解 Python,但在 TypeScript 前端调用 FastAPI 路由的那一刻就失效了。真实系统是多语言的;但围绕它们的工具通常不是。

graphlens 为服务暴露或消费的接口发出语言中立的 BOUNDARY 节点:HTTP 路由、队列主题、gRPC 方法。边界 ID 不携带项目或语言,HTTP 路径被规范化,因此 /users/1/users/{user_id}(FastAPI)、<int:id>(Flask)和 :id(Express)都坍缩为相同的键。

因此,一个 FastAPI 路由和对该端点的 TypeScript fetch 会产生相同的边界 ID。合并两个图,链接它们,你就得到了跨越语言边界的边——这让代理能够回答"哪些前端调用命中这个端点?",这是一个单语言工具甚至无法表述的问题。

另外两个选择对可信度很重要。ID 是确定性的:节点的 ID 是 project::kind::qualified_name 的 SHA-256 哈希,因此相同的扫描在任何机器上都会产生相同的 ID,这正是使得差异比较和增量更新成为可能的原因。并且图永远不会撒谎说它是完整的:如果缺少工具链或文件类型检查失败,解析器会记录状态(ok / degraded / unavailable),而不是悄悄返回一个半解析的图。在 CI 中,任何不是 ok 的状态都会导致构建失败。

它真的划算吗?936 次运行

这是大多数"我的工具更快"的文章跳过的部分。我写了第一篇,声称代理在搜索时浪费 Token,但没有给出任何数字支持。所以我建立了一个基准测试来找出答案,结果让我惊讶。

你的编码代理正在浪费token来grep整个仓库。这里有一个命令搞定。

实验设置只有一个受控变量。相同的代理(Claude Code)、相同的提示词、相同的任务。唯一变化的是为代理提供上下文的MCP服务器。四种“工具”:filesystem(grep + 读取)、graphlens(结构图)、serena(LSP)和codegraph(一个竞争的图工具)。三个模型(Haiku、Sonnet、Opus),三个种子,针对apache/superset(约40万行代码,Python + TypeScript)的26个任务。共936次运行。

我锁定了一些条件,以确保数据有意义。内置的Claude Code工具(读取、Grep、Bash)被禁用——否则代理会忽略MCP服务器,测试毫无意义。参考答案是针对一个固定标签手动验证的,且关键一点是绝不通过被测试的任何工具生成。temperature=0并不能使这些模型完全确定,因此我使用三个种子并报告中位数,而不是均值。如果一次运行达到轮数上限仍未给出答案,则准确率记为0:“工具未在预算内完成”,而非“无数据”。

头条发现:排名完全取决于任务。对于简单的点查询——“类X在哪里定义”“它继承了什么”——所有四个工具在准确率上持平。唯一的区别是价格,大约差距3倍,graphlens居中表现平平。如果我仅仅测量了这些,我会写“图不值得,grep就够了”。那将是片面的真相。

在实际重要的任务上——爆炸半径问题、查找所有重写、解析歧义名称——工具之间差异巨大:

工具 准确率 Token数 工具调用次数 费用/任务
filesystem (grep) 0.71 12,596 27 $0.424
graphlens 0.84 748 1 $0.018
serena (LSP) 0.85 1,368 5 $0.065
codegraph 0.93 1,114 2 $0.036

grep崩溃了。准确率最低,而且只有83%的运行能得出答案——其余的都浪费在了50轮的上限中。那些确实完成的运行成本高出10-23倍,时间多10-18倍。当问题是“对此的所有调用”或“十个同名方法中的哪一个”时,文本搜索淹死在噪声中。

之前在点查询中看起来很平庸的graphlens,现在成了最便宜($0.018)且最快的选项,一次工具调用就能给出答案,而不是27次。在这些任务上,相比grep,约减少了94%的token。codegraph最准确(0.93);serena也表现出色。

还有一个我没有预料到的转折:最佳工具取决于你运行的模型。graphlens返回大量token的结果——图邻域、引用列表。在廉价模型上,这种冗余几乎免费,所以在Haiku上graphlens是四个工具中最便宜的。在Opus上,这些同样token的价格高得多,graphlens成了结构工具中最贵的(但仍然比grep便宜)。serena和codegraph返回紧凑、精确的结果,在任何模型上都保持低廉。

这引出了一个我敢赌钱的结论:廉价模型+结构工具胜过昂贵模型+grep。codegraph + Haiku(~$0.023,~0.99准确率)在每个指标上都同时击败了filesystem + Opus(~$0.087,0.93)。

我的一个预测彻底失败了,值得一报。我特意设置了跨语言任务(一个TypeScript调用需要解析到跨/api/v1/...边界的Python处理程序)作为压力测试,确信单语言工具会跌倒。但它们没有——所有工具,包括grep,都解决了。代理自己跨过了边界,无论给它提供什么上下文。

一个只证实你期望结果的基准测试不是真正的基准测试。诚实的细印:一个仓库、一个框架、26个任务(20个简单,6个困难)。成本差异在统计上可靠;困难任务上的准确率差距是一个强信号,但尚未在n=6时证明。cost_usd是API等价费用,不是你的订阅账单。这是一个在单一案例上的可复现测量,而非通用排名——如果你想在自己的代码上运行,整个框架加上原始数据都是开放的。

无人提及的差距:引擎不是产品

所以,在图应该有效的任务上,引擎确实有效。但我在两篇Habr文章中略过了一个漏洞:引擎并不是你可以直接交给代理使用的东西。

graphlens,按设计,止步于生成图。它不拥有数据库,不监视文件系统,不重新索引自身,也不启动一个长时间运行的服务。对于引擎来说,这是正确的选择——一个小核心很容易测试、缓存和组合。

但要真正将其接入代理,必须有人编写上层:图存储、失效(当文件变化时哪些需要重新索引)、文件系统监视器、一个带有代理可调用工具的MCP服务器、在每个客户端配置格式中的注册,以及一个导航技能,以便代理知道如何使用所有这些。这一层是每个人最终都得手动重新做的工作。

我写了一次,并将其打包为graphlens-mcp:一个轻量级运行时,运行在引擎之上,拥有存储、新鲜度模型以及代理所见的一切。

一个命令:

标题:你的编程智能体正在浪费 Token 来搜索仓库。用一条命令解决。

原文:

uv tool install graphlens-mcp
# 或:pipx install graphlens-mcp
cd your-project && graphlens-mcp init

init 会检测项目的语言,运行工具链“诊断”,将代码索引到本地图数据库中,把 MCP 服务器写入智能体的配置(它知道 Claude Code、Cursor、Windsurf、VS Code/Copilot、Codex CLI,并以幂等方式写入,不会破坏你的其他服务器),并安装导航技能。智能体从该配置启动服务器 —— 你永远不需要手动运行 serve

重启智能体,然后问它“如果我修改 create_order 的签名,会破坏什么?”

要求:Python ≥ 3.13(继承自引擎)。MIT 许可证。当前版本 0.1.2,还很早期——下面会详述。

智能体获得了什么

八个工具,每一个针对特定的代码查询问题:

工具 回答的内容
search_symbols 对符号名称进行全文搜索 —— 入口点
get_node_info 源码片段 + 签名 + 文档字符串 + 位置
get_file_structure 文件的符号大纲
get_callees 函数调用了什么(向外到 max_depth)
get_callers 谁调用了函数 —— 影响分析的核心
get_neighbors N 跳内的节点,任意方向
find_references 非调用关系:类型注解、赋值
get_cross_language_calls 跨越服务边界的链接(HTTP/gRPC/队列)

每个响应都携带一个图质量状态(okdegraded),因此智能体永远不会把部分答案误认为完整的答案。列表有上限,并且标记为 truncated,而不是静默截断。

导航技能教导的模式:从 search_symbols 开始,用 get_callers / find_references 展开,只在真正需要源码的位置才拉取 get_node_info —— 而不是逐字阅读每个调用文件。

为什么在你编辑时图数据库不会过时

将一个产品与“引擎加脚本”区分开的是:图数据库能在你编辑时自行保持最新。

一个文件系统监视器随服务器一起启动。当磁盘上的文件发生变化时,服务器会一次性重新索引变更的连接集(变更的文件、导入它的文件以及它导入的文件),这样跨文件的边就能正确重建,而不是半断裂。删除文件会清除其符号并更新其导入者。

每个人都会忘记的场景——服务器关闭时所做的编辑——由启动时的一次性协调处理:扫描项目,索引新增内容,删除消失的内容,刷新变更的内容,然后将控制权交给监视器。

图数据库位于 .graphlens/graph.db(SQLite)中。它是一个可重新生成的缓存,可以安全删除;重新索引即可重建。将 .graphlens/ 添加到版本控制忽略列表中。

它刻意不是什么

状态还早期,我宁愿直言也不愿美化。导航核心功能是工作的;其余部分仍在进行中。

试试它,告诉我哪里出了问题

如果你在大型多语言项目上运行 Claude Code、Cursor 或兼容的客户端,并且厌烦了看着智能体 grep 遍历仓库——特别是在重构之前进行影响分析(这正是图的用武之地)——那就安装它。

如果你的项目很小(grep 在几十个文件上瞬间完成)或者你主要需要语义搜索,那就算了。退出零障碍:一切都是本地的,没有任何东西离开你的机器,MIT 许可证,并且可以用一条命令卸载(graphlens-mcp remove --purge-db)。

把它指向你的主要项目,确认 MCP 服务器在你的智能体中处于活动状态,然后在同一个架构问题上比较有图和无图的工具调用次数。

我现在最需要的是在非 superset 的代码库上独立运行。问题、来自你项目的数据、对工具粒度的抱怨——都欢迎在仓库中提出。在不同代码上测得的数据越多,就越接近一个你可以直接采用的答案,而不是“在 superset 上有效”。

你的编码代理正在通过grep你的代码库消耗大量token。这里有一个单命令修复方案。

查看原文