摘要:Agent 写对了每一行代码,系统还是出了事故。我们用 Django、CPython、Kafka 三个仓库出题、跑了 480 个 run,想验证代码图能不能让 Agent 更懂代码,结果没有得到一个简单的「能」——它真正改善的是探索效率,而会话本身是另一个被低估的变量。

产品要做一个「用户活跃度周报」。Agent 接到需求后的表现,挑不出毛病:复用了项目里现成的 getActiveUsers(),直接读取 email/phone,接入了现成的 RateLimiter.check(),测试全绿。

两周后,线上出了三件事。

已删除用户混进了周报,活跃度被虚假拉高。低权限角色拿到了明文手机号。真实用户登录被限流——周报的批量轮询,把和登录模块共用的那个限流配额池耗光了。

三段代码单拎出来都算合格:复用的方法别的模块一直在用,工具是现成的,测试也通过了。问题都不在这些代码本身,而在它们周围——在那些没写在函数体里的事实里。

第一次接这个需求,Agent 用了 37 次 grep、读了 21 个文件、花了 12 分钟,最后靠人把坑一个个揪出来。一周后第二个类似需求,新会话从零开始:34 次 grep、11 分钟,同样的坑再踩一遍。

局部代码正确,不等于系统行为正确。这个道理不难懂。真正让我在意的是另一个问题:代码里明明已经包含了这么多信息,为什么 Agent 每次进入仓库,都处于冷启动状态,从零探索一遍?

问题拆解:代码理解的四个子问题

模型窗口从 8K 涨到 200K,再涨到百万级,Agent 进入仓库的冷启动状态却没有改变。原因并不神秘:上下文长度和代码理解本来就是两个层面的事——前者解决容量,后者要求组织。「让 AI 理解代码」从来不是一个问题,而是四个:

  • 表示:信息以什么形式保存;
  • 检索:需要的信息怎么找到;
  • 证据:结论能不能回溯到代码行;
  • 更新:代码变了,已有知识什么时候失效。

长上下文只回答了「能放多少」,管不了哪些信息值得看、怎么找到、怎么证明、什么时候失效。这个区分不是在说「长上下文没用」——它是容量,而容量解决不了组织问题。

按这四个协议去对照,现有工具其实各站一层:

层级 回答的问题 代表项目
L1 文本与符号骨架 有哪些文件、类、函数、签名? Aider
L2 符号语义导航 定义、引用在哪里? Serena
L3 仓库关系 谁调用谁?改动影响哪里? RepoGraph · CBMM
L4 意图与约束 为什么这样设计?什么不能一起做? CodeWiki

图1|四层代码理解模型

Aider 的 repo map 站在 L1,Serena 的 LSP 导航站在 L2,RepoGraph 和 CBMM 这类属性图站在 L3,CodeWiki 这类知识库站在 L4。名字不重要,方向很清楚:从看见符号,到发现关系,再到记录约束。

所以这篇文章分两部分,回答两个不同的问题:上篇用实验回答 L3——代码图到底给 Agent 带来了什么;下篇单独回答 L4——那些「为什么不能这么连」的知识,应该放在哪里。两个问题不同,工具不同,答案也不同。


上篇:代码图的对照实验

实验对象与总体设计

实验对象选了 codebase-memory-mcp(下文简称 CBMM):把源码解析成 SQLite 属性图,通过 MCP typed tools 暴露查询,支持 158 种语言,图查询亚毫秒级;文件变了只重建变化节点,记忆可以跨会话存活。选它没有特别深的理由,同类工具里它工程上最省心。

图2|CBMM 架构

仓库 Django(Python,~156K LOC)· CPython(C,~528K LOC)· Kafka(Java,~537K LOC)
题目 每仓库 40 题,共 120 道;方向:基础 / 影响分析 / Code Review / 重构
条件 baseline(仅 grep/read 等通用工具)vs hybrid(额外可用 CBMM 图工具)
模式 单题模式 vs 会话模式(见下文)
模型 执行:DeepSeek V4 Flash;独立评审:GLM-5.2 与 GPT-5.6 Sol
规模 480 个 run(有效 479),约 1.6 亿 token

先交代总账:120 道题 × 2 种工具条件 × 2 种模式 = 480 个 run。其中 1 个 run(Django 的 RP04 题,baseline 条件,会话模式)未在题库预算的 240 秒内完成,记为超时,有效 479。完整配对:单题模式 120 组,会话模式 119 组。后文所有对比都来自这批数据。

评审怎么判分:两套评审均为独立逐题打分,并对证据锚点做源码回查。GLM-5.2 从正确性、覆盖度、证据质量、表达与格式、效率五个维度各打 0–5 分,满分 25,总分差大于 1 分才记胜负;GPT-5.6 Sol 的质量均分只取前四个维度(满分 5 分),均分差低于 0.25 判平,效率单独记录、不计入质量分。

题目构成:四个方向与难度梯度

120 道题按四个方向出,每个仓库每个方向各 10 道:

  • 基础(30 道):定位与描述——公共 API、类型层级、符号定义、反向引用、模块职责,以及跨模块的数据流与契约;
  • 影响分析(30 道):给定真实或假设的变更、故障场景,考查影响面分析、错误链、回归计划与最小修复方案——产出带证据的书面分析,不产出代码;
  • Code Review(30 道):指定模块、指定风险维度的定向审查——十道题各对应一类风险点:边界校验、错误契约、资源生命周期、并发状态、API 兼容……;
  • 重构(30 道):重构方案的规划与安全评估——拆分、迁移、特性移除,强调兼容性约束、回滚点与测试缝隙,同样落到书面方案。

每个方向内部难度从 L1 到 L3 递增。难度是题库逐题标注的字段,没有更细的成文定义;从标注分布看大致是:L1 单文件可答(每仓库只有 2 道),L2 单模块内追踪,L3 跨模块、跨文件。

图3|题目构成:四个方向与难度梯度

光列题型还是抽象。真题题面为英文,下面每个方向转述一道:

基础|Django F01(L1):在固定提交的 django/template/__init__.py 里,精确列出 django.template.__all__ 中的名字,给出每个名字的定义模块与职责;再找出那些仍可属性访问、但不在 __all__ 里的导入名。题面甚至专门提醒:不要把 # NOQA isort:skip 当成 API 稳定的证据,不要把内部子模块当成公共 API。

影响分析|Kafka I01(L3):审计「bootstrap.servers 不带显式端口会被拒绝」这条既有行为——题面明确要求不要把它当成新变更;追踪 CommonClientConfigs 里的声明、ProducerConfig/ConsumerConfig 里的注册、ClientUtils.parseAndValidateAddresses 里 null host/port 抛 ConfigException 的校验,并覆盖五个直接生产调用点(KafkaProducer、ClassicKafkaConsumer、AsyncKafkaConsumer、ShareConsumerImpl、AdminBootstrapAddresses)。

Code Review|CPython CR01:圈定 Python/getargs.c 的一条具体解析路径、列出四个审查点,考 O& converter 失败时到底抛什么异常——后文会看到,就是这道题,在两种模式下得到了完全不同的答案。

重构|Kafka RP09(L3):为一次「混合模式支持结束后的主版本移除」做盘点:生产代码中的 kafka.zk 导入、broker 启动参数、指定的 ZK 迁移测试;把最后一个回滚点定义为源码删除前的那个版本,排除线上混合集群回滚。

两种实验模式:单题模式与会话模式

工具条件之外,还有第二个变量:上下文连不连续。

单题模式(per-question):每道题都开一个全新会话,做完立刻清空上下文。Agent 做第 5 题时,不记得第 4 题探索过什么——它衡量的是零记忆状态下的裸性能。

会话模式(session):同一方向的 10 道题在同一个连续对话里依次完成,前面翻过的代码、犯过的错,后面都记得——它衡量的是记忆累积之后的表现。

单题模式是断开的十个点,会话模式是连成线的十个点。这个设计后面会反复用到:它让我们能同时看清两个变量的效果——开不开图,以及上下文连不连续。

单题模式结果:质量持平,工具错误减少 46%

指标 baseline hybrid Δ
质量评分(GLM-5.2,/25) 23.48 23.46 持平
质量评分(GPT-5.6 Sol,/5) 4.62 4.59 −0.03
平均耗时 205s 202s −2%
平均 token 530,578 586,636 +10.6%
工具错误(次/题) 0.57 0.31 −46%

质量基本没动,token 却多了 10.6%。真正稳定变化的是工具错误率,从 0.57 降到 0.31——Agent 少走了弯路,但走对路之后,答案没有变好。

每道题都清空上下文,图工具很容易退化成一个更高级的 grep:帮你更快找到位置,仅此而已。

这不算孤例。RepoGraph 的论文(ICLR 2025)里有一个更刺眼的对照:SWE-bench Lite 上,基线解决率 27.33%,接入 RepoGraph 后升到 29.7%;把同一张图的 2-hop 子图直接展平塞进 prompt,反而掉到 26.0%,低于什么图都不给的基线。

图4|RepoGraph 三条件对比

同一张图,调用方式不同,结果相差悬殊。图的价值不在信息多,而在按需可查。

这一结果与图工具的定位一致:它本来就不是为单题搜索设计的。因此,更关键的检验在会话模式。

会话模式结果:耗时下降 24%,质量基本持平

指标 baseline hybrid Δ
质量(GLM-5.2,/25) 23.4 23.8 +0.4(胜/平/负 37/67/15)
质量(GPT-5.6 Sol,/5) 4.61 4.61 持平
平均耗时 123s 94s −24%
工具错误(次/题) 0.34 0.22 −35%
单题 token 117,016 127,435 +9%

质量方面的变化有限:GLM 评分 +0.4(约 1.6%),GPT-5.6 Sol 整体持平;分项里有亮点——CPython 仓库 4.55→4.69(10 胜/29 平/1 负),code review 方向 4.47→4.70(11 胜/19 平/0 负)——但这个幅度的差异,与两个评审模型之间的给分差在同一量级,不足以称为稳定收益。

耗时方面的变化更大:123 秒对 94 秒,而且越到后段差距越大。

图5|会话模式轮次耗时曲线

token 随轮次近乎线性增长(末题约为首题的 5–6 倍,因为携带全部历史),耗时却一路下降:第 1 题 114 秒,第 10 题 71 秒。前几道题探索过的代码区域,后面直接复用了。同条件对比,会话模式比单题模式少了 40–65% 的耗时、60–78% 的单题 token。

质量维度上还有一个值得单独说的案例。CPython 的 CR01 题,考 O& converter 失败时抛什么异常——源码 Python/getargs.c:474 有个隐藏分支:错误消息以 ( 开头时抛 SystemError,而不是大多数人以为的 TypeError。单题模式下,hybrid 被图工具带到了附近,读了源码仍然判错 TypeError(78 vs 基线的 98/100);会话模式下,同一个模型借着前几道题对 seterror()/converterr() 错误处理的讨论,判对了(94 vs 84/100)。

图6|CR01 四象限对比

这是全部 120 道题中唯一的关键事实错误案例,样本量为 1,说明不了统计意义上的任何事情。但它直观地展示了一种机制:图负责发现关系,累积的上下文负责验证和修正关系——两者碰上,才有「知识修正」这回事。

像 CR01 这样题目级、经源码核验的「前题修正后题」案例,只有这一个;方向级的证据更粗,但同向。Kafka 的 code review 方向,GLM 评审记录里写道:「连续对话中前置 CR 题已建立的模块认知,加上图工具的精准索引,让后续题几乎『秒答』」——这个方向 baseline 平均每题 319 秒,hybrid 108 秒。评审还专门抽查了反向风险——前题的错误结论被后题继承的「上下文污染」——没有发现明显案例,但样本有限。

可靠性方面:240 条会话模式答案做了全量源码核验,没有发现伪造的符号或路径,缺陷集中在个别行号 off-by-one;evidence 锚点命中率 78.3%,两种条件持平;JSON 畸形 1 例。

归因分析:会话与图工具的贡献拆分

−24% 不能直接归因于图工具。轮次耗时曲线显示,baseline 自己也在提速:第 1 题 138 秒,第 10 题 94 秒,降了 32%;hybrid 从 114 降到 71,降了 38%。两条线的降幅相差不大——图工具的边际贡献可能只有几个百分点,代价却是 token 多了 9–11%。

更合理的拆法是:会话提供了工作记忆,让前置探索的成果可以被复用;图工具降低了单次探索和定位的成本;两个变量叠加,才有效率收益。会话化本来就是一个值得做的工程改造,如果把它当成基线的一部分,图工具的净贡献比合并数字给人的直觉小得多。

仓库维度的数据支持这种拆法:

仓库(图规模) baseline → hybrid 耗时 质量(GLM 胜/负)
Django(51k 节点) 61s → 65s(↑7%) 4 胜/6 负
CPython(110k 节点) 98s → 82s(↓16%) 13 胜/6 负
Kafka(163k 节点) 210s → 136s(↓35%) 20 胜/3 负

图7|仓库规模效应

Django 上图反而更慢,Kafka 上省了三分之一的时间。任务类型同样:Code Review 方向整体降幅最大(−39%,161s→98s),但拆开看,这份收益全部由 Kafka 贡献——CPython 和 Django 的 CR 方向,hybrid 反而更慢(+18% 和 +8%)。基础查找类的降幅最小。什么任务最依赖跨模块追踪,什么任务收益就最大——图省的是探索成本,探索不构成瓶颈的地方,它就是纯开销。

上篇小结:代码图的价值边界

到这里,可以给这次实验一个结论:代码图目前最可信的价值是效率,不是已经被充分证明的理解能力。它让 Agent 更少 grep、更少调用错工具、更快定位跨模块关系,但没有稳定地让最终答案的质量上一个台阶。这个观察和外部研究是一个方向——Lost in the Middle 的 U 形曲线、Chroma 的 Context Rot、SWE-agent 把收益归因于接口设计、Anthropic 披露 Claude Code 刻意只用 glob/grep——不同团队从不同角度得到同一个判断:更多上下文,不等于更多有效利用。

但我不想把「图不是理解引擎」说成「图没价值」。更贴近数据的话是:它降低的是理解之前的探索成本,而探索成本是真实存在的成本。


下篇:知识库与知识回写机制

图的边界:显式结构之外的约束

上篇的结论是图省「探索」,但开头那三个事故,一个都不是探索不够造成的。我把它们重新过了一遍。

deleted_at 的过滤藏在 middleware——图可以告诉你 getActiveUsers() 被谁调用、依赖哪些配置,但「返回结果已经过中间件过滤,Service 层不能再依赖这个事实」不在图的任何一条边上。脱敏同理——图会如实告诉你 Model 和 Serializer 之间有依赖,但不会告诉你绕过它取数是危险的。限流配额更远——「周报和登录不能共享这个 Redis 前缀」是两个模块之间的系统级约束,局部代码不会说,调用关系也不会说。

图知道怎么连,但通常不知道为什么不能这么连。

这不是图的缺陷,是它的边界:它表达的是代码里显式存在的结构。而真正危险的约束,常常在代码关系之外——历史事故里,设计决策里,或者某个老员工的脑子里。把这些约束记下来、管起来,是另一类工具要回答的问题。

CodeWiki 的两层知识模型

在公开资料里,高德技术团队的 CodeWiki 是这个问题上相对完整的一条工程路径。它把知识分成两层,并且明确两层不能用同一种方式维护:cross-ref 记录代码显式做了什么——入参校验、条件分支、异常路径、调用与配置,用 tree-sitter 加 LLM 自动生成,随源码更新;domain-knowledge 记录代码没写的东西——为什么这样设计、什么不能一起做、历史事故——由研发 [suggest-wiki] 标注触发,人审通过才能归档,LLM 不能自己决定什么值得被固化成团队知识。

图8|CodeWiki 两层知识模型

高德团队报告过一个数据点:一个「投放时段智能调控」需求的 9 个约束检查点,纯 OpenSpec 覆盖 0.5,加 cross-ref 后 4.5,再加 domain-knowledge 后到 7.5;功能互斥、Tair 幂等、投放节奏冻结、状态生命周期这 4 项,只有 domain-knowledge 能覆盖,代码里根本找不到。这是团队自己的工程数据,单一需求样本,我不会把它当成统计证明——但它和开头三个事故完全同构:会出事的约束,大多在 domain-knowledge 那一侧。

知识回写闭环与人审闸门

知识库容易让人想到「先建个大库」。CodeWiki 实践里更要紧的是一条闭环:任务探索中发现约束 → 研发附证据标注 → 人审 → 归档 → 后续任务复用。

图9|知识回写闭环

人审这一环省不掉。一条错误知识一旦进入长期记忆,会被后续所有任务反复复用——LLM 的一次幻觉,会被固化成「团队共识」,误导每一个人。归档动作可以自动化,领域规则的最终判断不能。

支撑这条闭环的是几个朴素的机制,共同目标只有一个——知识可追溯、可更新、可审核:每条断言附 field / type / quote / reason 四要素代码证据(含文件与行号),confidence 低于 0.8 强制标记 candidate 回源验证;Merkle Tree 三层哈希做增量检测,改一个方法只重算所属子树,日常变更只触发个位数 LLM 调用。它们回答了知识库最常见的两种死法:全量跑 LLM 太贵,以及没人知道库里哪句是真的。


结论:三种记忆的组合

完整地看,Agent 要摆脱每次进入仓库都从零开始的冷启动状态,需要继承三种记忆:

结构记忆——代码怎么连接。图、LSP、索引提供的是这一种,上篇实验检验的就是它。工作记忆——这个会话已经探索过什么。连续上下文承担,实验显示它的贡献可能比图工具的边际贡献还大。经验记忆——团队踩过什么坑、哪些设计有隐含约束。知识库承担,目前还没有严格对照实验,只有工程实践在支撑。

这是从这批实验和别人的工程实践里长出来的工作模型,不是被验证过的定律。

所以回到标题的问题:代码图和知识库,能不能帮助 AI 理解代码?我的答案是克制的版本。代码图是一种仓库探索效率基础设施——仓库够大、对话够长、任务跨模块时,它实打实省时间、减少工具错误,这次实验能支撑的到这里为止。知识库覆盖图够不着的隐式约束,方向合理,但「知识回写提升后续任务质量」这条因果链,还没有被任何严格实验验证过,包括我们这次。

所以我不会把代码图叫做「代码理解引擎」。至于「让 Agent 对仓库的认知可以跨会话沉淀」这个目标——它需要的不是更长的上下文,也不是更大的一张图,而是三种记忆的组合。

回到开头那个周报需求:现在的 Agent 会从图上查到 getActiveUsers() 的调用链,会话里积累的探索会帮它快很多——但「这个方法不能绕过中间件单独用」这条知识,依然要等某个人踩坑之后,把它标注进团队的记忆里。

那一天还没到,但路径比实验开始前清楚了。


引用与出处

论文与技术报告:

  • Nelson F. Liu, Kevin Lin, John Hewitt, Ashwin Paranjape, Michele Bevilacqua, Fabio Petroni, Percy Liang. Lost in the Middle: How Language Models Use Long Contexts. TACL 2024, 12:157–173. doi:10.1162/tacl_a_00638(arXiv:2307.03172)
  • Kelly Hong, Anton Troynikov, Jeff Huber. Context Rot: How Increasing Input Tokens Impacts LLM Performance. Chroma Technical Report, 2025-07-14. trychroma.com/research/context-rot
  • John Yang, Carlos E. Jimenez, Alexander Wettig, Kilian Lieret, Shunyu Yao, Karthik R. Narasimhan, Ofir Press. SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering. NeurIPS 2024(arXiv:2405.15793)
  • RepoGraph: Enhancing AI Software Engineering with Repository-level Code Graph. ICLR 2025

工程实践:

  • Anthropic. Effective Context Engineering for AI Agents. 2025-09-29
  • codebase-memory-mcp:github.com/DeusData/codebase-memory-mcp
  • CodeWiki:高德技术公众号《CodeWiki:为 LLM 自动生成代码知识库的工程实践》,2026-07-28
  • Aider:github.com/Aider-AI/aider · aider.chat/docs/repomap.html
  • Serena:github.com/oraios/serena