用 Gemini 4 Argon 一夜优化 20 万行 Python 代码库
英文标题:Optimizing a 200,000-line Python codebase overnight with Gemini 4 Argon
谷歌工程团队让 Gemini 4 Argon 在无人值守的情况下运行 11 小时,优化开源 Python Agent Development Kit(ADK)的跨文件性能瓶颈。团队通过 .agents/skills 提供维护规范,并使用 /goal 模式保持任务目标,最终从模型提出的六项优化中审核并合并了两项。合并后的改动使长会话中的内存会话准备开销降低约 40%,冷启动包导入速度提升 21%,158 个内部轮次的完整运行时间从 65.6 秒缩短至 56.1 秒。实践表明,编码 Agent 可以自主探索复杂代码库,但维护者的代码审查仍是保障兼容性和代码质量的关键。
1002.7 KB
221.6 KB
632.4 KB
762.2 KB
82.2 KB
333.6 KB
327.4 KB
41.3 KB
397.8 KB
80.3 KB
46.8 KB
中文

新模型发布时,大多数演示都展示从空文件夹开始构建的全新应用。这些演示很有趣,但对于实际工作中的软件工程师来说,它们并不能充分说明模型处理现有代码库的能力。
在 Gemini 4 Argon 发布之际,我们希望测试它能否在无人值守的情况下,用一夜时间完成一个难度高得多的任务:查找并修复 google/adk-python 中跨文件的性能瓶颈。Agent Development Kit(ADK)是谷歌用于构建和部署企业级 Agent 的开源框架。
这个 Python 代码库包含近 800 个源文件,约 20.5 万行代码,并有近 1.7 万个单元测试提供支持。
性能优化对模型来说是一项艰巨的考验,因为你不能只打开一个文件就找出速度变慢的原因。你需要跳出症状出现的位置,从其他角度进行排查,并在长时间运行过程中掌握整个代码库各部分如何组合在一起。
本文将介绍这次 11 小时夜间运行所使用的提示词、.agents/skills 规则文件,以及人工代码审查时的决策。这次运行使 ADK 在长会话中的内存会话准备时间减少约 40%,冷启动时的包导入速度提升 21%。你将看到最终上线的两项优化、审查中被否决的四项优化,以及如何在自己的代码库中搭建同样的夜间工作流。

图 1:分别经过 50 和 150 个轮次后,ADK 在首次模型请求之前的开销。Argon 的改动在两个测量点都将开销降低了约 40%,因此会话越长,节省的时间越多。
用 .agents/skills 和 /goal 模式引导运行
使用编码 Agent 进行性能优化时,大多数工作仍处于紧密的人机协作循环中:工程师运行性能分析器,告诉模型某个热点函数的位置,并指定修复方法。我们希望看看,如果只告诉 Argon 团队关心的结果,它会怎么做。
第一轮测试中,我们没有提供任何自定义代码库指令,只给了它下面这段提示词:
让 ADK 不给任何 Agent 带来明显开销:
无论是在启动时、长会话中,还是在多 Agent 和
工作流场景下。保持模型收到的内容和
公共 API 不变,并为每项改动提供能够捕获回归的测试。
第一轮运行了约两个小时。Argon 在第 7 分钟编写了一个基准测试脚本,在修改任何 ADK 源代码之前,花了 70 分钟进行测量和阅读代码,并找到了会话复制和包导入中的实际瓶颈。第一轮的部分代码进入了我们最终发布的提交。
第一轮的不足在于没有遵循维护者规范。由于 Argon 不知道 ADK 团队如何审查代码,它给 ADK 的认证模型加上了一个自定义钩子,导致其公共 JSON Schema 发生了细微变化;它还遗漏了自定义复制器中涉及模拟事件的边界情况,仅运行了附近的 216 个单元测试,并且没有在拉取请求的描述中给出基准测试数据。
第一轮证明确实存在提升性能的机会,因此我们做了两项调整,启动第二轮。首先,我们将团队的维护者指南放进代码库的 .agents/skills 目录,就像为新工程师准备入职指南一样。每项 skill 都以简短的 SKILL.md 索引开头,按需指向 references/ 中的专题文件,这样就不会在每个轮次都塞满 Agent 的上下文窗口。Argon 在晚上 9:11 开始运行时(图 2),在修改任何代码之前,先阅读了介绍私有 _-prefixed 模块规则的 visibility.md,以及介绍延迟加载检查的 imports.md。

图 2:Argon 围绕同一目标进行的两小时首次运行与 11 小时夜间运行对比。第一轮自行找到了实际的性能提升机会;第二轮则遵循了代码库规范,运行了全部 16,892 个单元测试,并提交了一个可供审查的拉取请求。
其次,我们在 Google Antigravity 中启用了 /goal 模式。该模式会在上下文压缩后保留目标,让 Agent 持续运行直至任务完成。我们还增加了两句话,要求它提交一个不合并的拉取请求,并附上改动前后的数据:
/goal 让 ADK 不给任何 Agent 带来明显开销:
无论是在启动时、长会话中,还是在多 Agent 和工作流场景下。
保持模型收到的内容和公共 API 不变,并为每项改动提供能够捕获回归的测试。
针对 main 分支提交一个 ADK 维护者会批准的拉取请求,在描述中附上改动前后的数据。不要合并它。
代码库中配置好 .agents/skills 并启用 /goal 模式后,Argon 在夜间无人值守运行了 11 小时(图 3)。

图 3:Argon 在 Google Antigravity 中夜间运行 ADK 任务。/goal 提示词固定在窗口顶部和底部,已完成的工作则折叠成摘要块,因此在数小时的编辑、基准测试和测试运行过程中,最初的规则始终可见。(截图在公开的 Antigravity 界面中重新播放,以移除内部工作区细节。)
6 项提案经过代码审查,最终上线 2 项改动
第二天早上查看夜间运行日志时,有几个行为立刻引起了我们的注意。在修改源代码之前,Argon 阅读了 12 个指南文件,并针对四种测试工作负载运行了基线基准测试:包启动、200 步对话、20 个子 Agent 相互交接任务的测试应用,以及一个包含 12 个节点的工作流;测试运行了 30 次。甚至在 11 小时运行结束前的三分钟,Argon 仍在审查自己的代码差异,发现了自己编写的多 Agent 缓存中的一个 bug,并重新运行了全部 16,892 个单元测试,随后自行停止。
更重要的是,Argon 完成了性能分析器无法替你完成的跨模块排查。性能分析器会告诉你复制会话和导入包耗费了时间,但它不会追踪分布在 40 个文件中的每个调用方、模拟目标和共享引用,以确保加速这些路径不会破坏现有应用。
Argon 的拉取请求总共针对 32 个文件提出了六项优化。其中两项干净利落地解决了 ADK 的核心瓶颈,并已上线:
1. 在复制前筛选内存中的会话历史
ADK 运行的每一步都从加载对话历史开始。此前,ADK 的内存会话服务在每次读取时,都会先深度复制整个已存储的对话,再筛选出调用方请求的事件。即使只列出会话而不需要其事件,也会先复制所有事件。Argon 更新了该服务,使其在复制之前先筛选事件,并用一个快速、能安全处理循环引用的复制器替换了 Pydantic 对会话和事件的通用深度复制路径,同时保留共享状态字典和测试模拟对象。
2. 仅在需要时加载重量级包
此前,运行 import google.adk 时,即便只是一个基础文本 Agent,也会加载实时音视频流、OpenTelemetry 追踪和 Web 服务器(FastAPI 和 Starlette)相关模块。直接删除框架中的顶层导入,会破坏下游那些直接模拟模块属性的测试。当 Argon 在第二轮的拉取请求中从 llm_agent.py 移除了三个内部流程导入时,全部 16,892 个开源测试都通过了,但谷歌内部有 25 个测试目标失败,因为它们恰好对这些属性进行了替换。最终上线的修复使用延迟模块访问器,让重量级包仅在首次使用时加载,同时确保所有公共属性仍可在测试预期的位置被替换。
Argon 提出的另外四项优化又如何呢?
我们发现,模型会提出一些我们原本不会考虑的创造性解决方案。但其中一些创意会触碰框架规则,而破坏兼容性的改动是不可接受的。

图 4:Argon 在早晨提交的拉取请求中提出的六项优化,分为我们上线的两项和代码审查中删去的四项。我们保留了能够干净地减少实际开销的会话复制和延迟导入改动,舍弃了依赖脆弱状态、库的私有内部实现,或仅在合成测试中带来收益的四项改动。
代码审查期间,我们否决了另外四项改动:
- 多 Agent 事件缓存使一项合成基准测试加快了 17%,但它依赖一个弱内存引用:如果 Python 在垃圾回收后重用了某个内存地址,就可能跨会话返回过期数据。
- 一项遥测检查读取了 OpenTelemetry 追踪器实现中的私有属性,可能在库的小版本升级后悄然失效。
- 对 ADK 在每次模型调用前格式化对话历史的方式进行的重写,让核心代码路径更难阅读,却没有改善我们独立留出基准测试中的端到端延迟。
- 工作流事件重放的查找索引增加了状态跟踪的复杂度,而它只对合成重放测试有帮助,对日常 Agent 运行没有帮助。
这种分工正是该工作流奏效的原因:Agent 负责探索,维护者负责做减法。我们结合第一轮和第二轮的最佳成果,形成了一项涉及 22 个文件的改动,并新增 17 个测试函数作为保障。经过 ADK 团队审查后,该改动合并进 main,提交说明写着“这些改动由 Argon 生成”。
基准测试结果:ADK 中的“轮次”是什么,为什么 158 个轮次很重要
为了理解提交说明中的数据,先了解 ADK 所称的轮次是什么会有所帮助。
当工程师输入一个提示词并收到最终答案时,ADK 将其称为一次调用(invocation)。轮次则是底层的一个内部步骤,Agent 每执行一次操作就会产生一个轮次,例如调用工具、并行运行一批工具调用,或查询 MCP 服务器。
使用 Agent 框架的一个原因就是多轮工具调用:一次运行的底层可能发生几十甚至几百个内部轮次。
在两轮运行开始之前,我们编写了一个 Argon 从未见过的独立留出基准测试(bench_turn.py)。在 bench_turn.py 中,单个会话逐轮增长,模型和工具都即时响应,因此计时中的每一毫秒都是 ADK 自身的框架开销。每个轮次在返回答案前会执行三轮操作,每轮包含四个并行工具调用;测试分别在已有 0、50 和 150 个轮次之后,对接下来的 5 个轮次计时。下文每个数字都是运行 6 至 12 次的中位数,而且在每个轮次中,改动前后模型收到的请求在字节层面完全相同。
我们通过五项基准测试,将合并后的提交与之前的版本进行了对比(图 5):

图 5:在全部五项留出基准测试中,Argon 改动前后的 ADK 框架开销中位数。长会话的会话准备开销下降约 40%,包导入时间下降 21%;由于延迟加载的模块会在第一个轮次加载,新进程发出首次请求所需的时间基本持平。
如何结合自己的 Agent 理解这些数据:
import google.adk的速度提升 21%,能立即惠及每一位 ADK 用户,加快 CLI 命令、本地开发和测试发现。由于延迟加载的模块仍会在 Agent 运行第一个轮次时加载,从全新进程启动到首次模型请求的时间基本不变(从 3.07 秒变为 3.09 秒)。在生产服务器上,应在接收真实流量前发送一次预热请求,避免让第一个用户请求承担这一次性的导入成本。- 模型调用前开销降低 40% 的结果适用于
InMemorySessionService。它为InMemoryRunner、本地评估循环、CI 测试套件,以及未配置外部数据库 URI 时的默认 Cloud Run 或 GKE 部署提供支持。对于耗时数秒的单次 LLM 调用,人通常感觉不到 28 毫秒或 78 毫秒的差异。但在 158 个内部轮次的工具调用交互中,这些复制开销会不断累积:消除它们使完整的 158 轮运行时间从 65.6 秒缩短至 56.1 秒,减少了近 10 秒的纯框架开销。外部数据库、SQLite、Gemini 平台、Firestore 和 Redis 会话存储是从存储中反序列化数据,而不是在内存中进行深度复制,因此这次改动不会改变它们的读取时间。
试用 Gemini 4 Argon
企业开发者和构建者可以通过 Gemini platform 的 API 访问 Argon,模型 ID 为 gemini-4-argon;也可以在 Google AI Studio 和 Google Antigravity 中使用。
请查阅模型卡和开发者指南,了解完整技术规格、Token 限制和迁移说明。
试用优化后的 ADK
English / 原文

When a new model launches, most demos show greenfield apps built from an empty folder. They're fun to watch. They also tell a working software engineer very little about how the model handles an existing codebase.
For the launch of Gemini 4 Argon, we wanted to test whether it could run unattended overnight on a much harder problem: finding and fixing cross-file performance bottlenecks in google/adk-python. Agent Development Kit (ADK) is Google's open-source framework for building and deploying enterprise agents.
The Python repository spans roughly 205,000 lines of code across nearly 800 source files, backed by nearly 17,000 unit tests.
Performance work is a hard test for a model because you can't just open one file and spot the slowdown. You have to look orthogonally to where the symptom appears and hold a map of how the entire codebase fits together over a long run.
This post walks through the prompts, .agents/skills rulefiles, and human code-review decisions from the 11-hour overnight run that cut ADK's in-memory session prep time by about 40% in long sessions and sped up cold package imports by 21%. You'll see the two optimizations that shipped, the four we rejected in review, and how to set up the same overnight workflow on your own codebase.

Figure 1: ADK's overhead before the first model request, measured after 50 and 150 turns. Argon's change cuts it by about 40% at both points, so the time saved grows as the session gets longer.
Steering the run with .agents/skills and /goal mode
Most performance work with a coding agent still runs in a tight human loop: an engineer runs a profiler, points the model at a single hot function, and specifies the fix. We wanted to see what Argon would do when given only the outcome the team cared about.
In round one, we tested Argon with zero custom repository instructions. We gave it this single-paragraph prompt:
Make ADK add no noticeable overhead to any agent:
at startup, in long sessions, in multi-agent and
workflow setups. Keep what the model receives and
the public API unchanged, and back each change
with a test that would catch a regression.
Round one ran for about two hours. Argon wrote a benchmark script 7 minutes in, spent 70 minutes measuring and reading code before editing a single line of ADK source, and found real bottlenecks in session copying and package imports. Parts of round one's code made it into the final commit we shipped.
Where round one fell short was maintainer conventions. Because Argon didn't know how the ADK team reviews code, it attached a custom hook to ADK's authentication model that subtly altered its public JSON schema, missed mock event edge cases in its custom copier, ran only 216 nearby unit tests, and left benchmark numbers out of its pull request description.
Round one proved there were real speedups to be had, so we set up a second round with two adjustments. First, we put the team's maintainer guidelines into the repository's .agents/skills directory, like an onboarding guide for a new engineer. Each skill starts with a short SKILL.md index that points to focused references/ files on demand, so we don't flood the agent's context window on every turn. When Argon started its run at 9:11 PM (Figure 2), it read visibility.md for our private _-prefixed module rule and imports.md for our lazy-loading checks before touching a line of code.

Figure 2: Argon's two-hour first run compared with its 11-hour overnight run on the same goal. Round 1 found real speedups on its own, while Round 2 followed repo conventions, ran all 16,892 unit tests, and opened a review-ready pull request.
Second, we turned on /goal mode in Google Antigravity, which preserves the goal across context compactions so the agent keeps running until the task is finished. We also added two sentences asking for an unmerged pull request with before-and-after numbers:
/goal Make ADK add no noticeable overhead to any agent:
at startup, in long sessions, in multi-agent and workflow setups.
Keep what the model receives and the public API unchanged, and back each change with a test that would catch a regression.
Open one pull request against main that the ADK maintainers would approve, with before and after numbers in its description. Don't merge it.
With .agents/skills in the repo and /goal mode active, Argon ran unattended for 11 hours overnight (Figure 3).

Figure 3: Argon's overnight ADK run in Google Antigravity. The /goal prompt stays pinned at the top and bottom of the window while finished work collapses into summary blocks, so the original rules stay in view across hours of edits, benchmarks, and test runs. (Screenshot replayed in the public Antigravity interface to remove internal workspace details.)
6 proposals led to 2 shipped changes after code review
Looking at the overnight session log the next morning, a few behaviors stood out right away. Before touching source code, Argon read 12 guideline files and ran a baseline benchmark across four test workloads: package startup, a 200-step conversation, a test app where 20 sub-agents hand tasks to one another, and a 12-node workflow, run 30 times. Even three minutes before finishing its 11-hour run, Argon was still auditing its own diff, catching a bug in its own multi-agent cache and rerunning all 16,892 unit tests before stopping on its own.
More importantly, Argon did the cross-module legwork a profiler can't do for you. A profiler will tell you that copying sessions and importing packages take time. What it won't do is trace every caller, mock target, and shared reference across 40 files so that speeding up those paths doesn't break existing apps.
In total, Argon's pull request proposed six optimizations across 32 files. Two of those optimizations solved ADK's core bottlenecks cleanly and shipped to production:
1. Filtering in-memory session history before copying
Every step in an ADK run starts by loading the conversation history. Previously, ADK's in-memory session service deep-copied the entire stored conversation on every read before filtering down to the events a caller asked for. Even listing sessions without their events copied every event first. Argon updated the service to filter events before copying, and replaced Pydantic's generic deep-copy path on sessions and events with a fast, cycle-safe copier that preserves shared state dictionaries and test mocks.
2. Loading heavy packages only when needed
Running import google.adk previously pulled in modules for live audio and video streaming, OpenTelemetry tracing, and web servers (FastAPI and Starlette) even for a basic text agent. Simply deleting top-level imports in a framework breaks downstream tests that mock attributes directly on modules. When Argon's round-two pull request removed three internal flow imports from llm_agent.py, all 16,892 open-source tests passed, yet 25 internal Google test targets failed because they patched those exact attributes. The shipped fix uses lazy module accessors so heavy packages load only on first use while every public attribute stays patchable where tests expect it.
What about the other four optimizations Argon proposed?
We found that the model comes up with creative solutions we wouldn’t have considered otherwise. Some of those creative ideas run into framework rules where breaking changes are not acceptable.

Figure 4: The six optimizations in Argon's morning pull request, split between the two we shipped and the four we cut in code review. We kept the session-copy and lazy-import changes that cut real overhead cleanly, and dropped the four that relied on fragile state, private library internals, or synthetic-only gains.
During code review, we rejected the other four changes:
- A multi-agent event cache sped up a synthetic benchmark by 17%, yet relied on a weak memory reference that could return stale data across sessions if Python reused a memory address after garbage collection.
- A telemetry check read a private attribute on OpenTelemetry's tracer implementation, which could break silently on a minor library upgrade.
- A rewrite of how ADK formats conversation history before each model call made a core code path harder to read without moving end-to-end latency on our independent holdout benchmark.
- A lookup index for workflow event replays added state-tracking complexity that only helped synthetic replay tests rather than everyday agent runs.
That division of labor is why the workflow worked: the agent explores, the maintainer subtracts. We combined the best parts of rounds one and two into a 22-file change backed by 17 new test functions. After review across the ADK team, it landed in main with the commit note "Argon generated these changes."
Benchmark results: what a "turn" is in ADK, and why 158 turns matters
To make sense of the numbers in the commit message, it helps to know what ADK calls a turn.
When an engineer types a prompt and gets a final answer back, ADK calls that an invocation. A turn is an internal step under the hood every time the agent acts: calling a tool, running a batch of parallel tool calls, or querying an MCP server.
The reason you use an agent framework is multi-turn tool calling, where dozens or hundreds of internal turns happen underneath a run.
Before either round ran we wrote an independent holdout benchmark (bench_turn.py) that Argon never saw. In bench_turn.py, a single session grows turn by turn against an instant model and instant tools, so every millisecond on the clock is ADK's own framework overhead. Each turn runs three rounds of four parallel tool calls before returning its answer, timing 5 turns after 0, 50, and 150 earlier turns. Each number below is the median of 6 to 12 runs, and on every turn the model receives byte-identical requests before and after the change.
We tested the merged commit against the previous release across five benchmarks (Figure 5):

Figure 5: Median ADK framework overhead before and after Argon's changes across all five holdout benchmarks. Session preparation drops by about 40% in long sessions and package import time drops by 21%, while new-process time to first request stays flat because deferred modules load on the first turn.
How to read these numbers for your own agents:
- The 21% faster import google.adk helps every ADK user right away, speeding up CLI commands, local development, and test discovery. Because deferred modules still load when an agent runs its first turn, time from a brand-new process to its first model request stays flat (3.07 s to 3.09 s). On a production server, send a warm-up request before taking live traffic so the first user call doesn't pay that one-time import cost.
- The 40% reduction in pre-model overhead applies to
InMemorySessionService, which powersInMemoryRunner, local evaluation loops, CI test suites, and default Cloud Run or GKE deployments when no external database URI is configured. On a single LLM call that takes seconds, a human won't feel 28 ms or 78 ms. Across 158 internal turns of tool calling interactions, however, those copies compound: removing them cuts the full 158-turn run from 65.6 seconds to 56.1 seconds (nearly 10 seconds of pure framework overhead). External database, SQLite, Gemini platform, Firestore, and Redis session stores deserialize from storage rather than deep-copying in memory, so their read times don't change here.
Try Gemini 4 Argon
Enterprise developers and builders can access Argon via API in Gemini platform under model ID gemini-4-argon, in Google AI Studio, and in Google Antigravity.
Review the model card and developer guide for complete technical specifications, token limits, and migration instructions.