你的编码代理正在通过搜索代码库浪费 Token。这里有一个命令即可解决。
graphlens-mcp 为 Claude Code、Cursor 以及兼容的客户端提供代码的类型化图,因此它们可以问"谁调用了 create_order?"并得到一个小的答案,而不是读取半个代码库。下文:引擎的工作原理、一个 936 次运行的基准测试关于它何时真正有回报、以及五分钟的安装。我一直在公开构建 graphlens。完整的故事在 Habr 上有三篇文章——引擎、基准测试、产品——但这个版本自成一体;我把重要的内容都整合进来了。文末有链接。
大家都知道的循环
想象一个大型项目。几十万行代码,后端是 Python,前端是 TypeScript,还有一个没人想碰的遗留角落。你用一个编码代理指向它,问一个普通问题:"这里的认证是如何工作的?"或者"如果我改变这个方法的签名,会破坏什么?"代理无法一次性看到整个代码库。所以它只能做它唯一能做的事:搜索一个名字,打开一个文件,读取它,跟随一个导入,再次搜索。它读取十几个文件,每一个都进入上下文窗口,每一个都在下一轮被重新计费。
这不是假设性的开销。Anthropic 自己的工程博客指出,工具定义和中间结果可以消耗"50,000+ Token,在代理读取请求之前"——窗口在代理甚至还没开始处理你的问题之前就填满了。
代码图正好针对这个问题。代理不是"读取文件并目测",而是问一个精确的问题——谁调用了 create_order——并返回一个小型结构化答案:已解析的边,而不是文本搜索和祈祷。
这就是核心卖点。这篇文章的其余部分是关于它是否站得住脚,以及让它变得可用需要什么。
引擎实际做了什么
"代码 → 图"的难点不是画框。而是边。大多数轻量级工具通过名称解析引用:它们看到对 save() 的调用,就画一条边到所有名为 save 的东西。快,但错误——一个真实的代码库有十几个 save。
graphlens,MCP 服务器下的引擎,将工作分为两部分:
- Tree-sitter 将每个文件解析为具体语法树:精确结构,精确的基于 1 的跨距位置。每个使用点都被记录为一个具有角色(调用、读取、写入、注解、基类)的出现。
- 一个特定于语言的类型感知解析器,为每个出现回答
definition_at(file, line, col)。解析后的定义成为指向真实声明的真实边。
这些解析器与你的 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/队列) |
每个响应都携带一个图质量状态(ok 或 degraded),因此智能体永远不会把部分答案误认为完整的答案。列表有上限,并且标记为 truncated,而不是静默截断。
导航技能教导的模式:从 search_symbols 开始,用 get_callers / find_references 展开,只在真正需要源码的位置才拉取 get_node_info —— 而不是逐字阅读每个调用文件。
为什么在你编辑时图数据库不会过时
将一个产品与“引擎加脚本”区分开的是:图数据库能在你编辑时自行保持最新。
一个文件系统监视器随服务器一起启动。当磁盘上的文件发生变化时,服务器会一次性重新索引变更的连接集(变更的文件、导入它的文件以及它导入的文件),这样跨文件的边就能正确重建,而不是半断裂。删除文件会清除其符号并更新其导入者。
每个人都会忘记的场景——服务器关闭时所做的编辑——由启动时的一次性协调处理:扫描项目,索引新增内容,删除消失的内容,刷新变更的内容,然后将控制权交给监视器。
图数据库位于 .graphlens/graph.db(SQLite)中。它是一个可重新生成的缓存,可以安全删除;重新索引即可重建。将 .graphlens/ 添加到版本控制忽略列表中。
它刻意不是什么
状态还早期,我宁愿直言也不愿美化。导航核心功能是工作的;其余部分仍在进行中。
- 监视器会重新索引变更的关联集合,而不是整个项目——如果重构通过多层间接层扩散,可能需要完全重新索引才能获得完美准确的图。
- 跨语言的
COMMUNICATES_WITH边在完全重新索引时重建,在增量编辑时可能会退化。 - 除 Python 之外的语言需要其工具链存在(Python 开箱即用;ty 自带);没有它们,语言报告为
degraded——结构被解析,但调用和类型未完全解析——但init永远不会阻塞它,状态会准确告诉你缺少什么。 - 还有一个结构性边界,而不是路线图项目:graphlens-mcp 不做嵌入或语义“找类似的东西”搜索。这个图是结构化和类型感知的,而不是向量索引。如果你需要“找到概念上类似于速率限制的代码,不管它叫什么”,那是向量工具的工作。这个回答结构性问题:谁调用它,它依赖什么,什么会破坏。
试试它,告诉我哪里出了问题
如果你在大型多语言项目上运行 Claude Code、Cursor 或兼容的客户端,并且厌烦了看着智能体 grep 遍历仓库——特别是在重构之前进行影响分析(这正是图的用武之地)——那就安装它。
如果你的项目很小(grep 在几十个文件上瞬间完成)或者你主要需要语义搜索,那就算了。退出零障碍:一切都是本地的,没有任何东西离开你的机器,MIT 许可证,并且可以用一条命令卸载(graphlens-mcp remove --purge-db)。
把它指向你的主要项目,确认 MCP 服务器在你的智能体中处于活动状态,然后在同一个架构问题上比较有图和无图的工具调用次数。
我现在最需要的是在非 superset 的代码库上独立运行。问题、来自你项目的数据、对工具粒度的抱怨——都欢迎在仓库中提出。在不同代码上测得的数据越多,就越接近一个你可以直接采用的答案,而不是“在 superset 上有效”。
你的编码代理正在通过grep你的代码库消耗大量token。这里有一个单命令修复方案。
- graphlens-mcp: github.com/Neko1313/graphlens-mcp
- 引擎: github.com/Neko1313/graphlens
- 基准测试: github.com/Neko1313/agent-context-bench