所有 benchmark 测的都是"改一个 bug"或"补一个函数"。我测了一个更真实的场景:给 Agent 一份完整的功能需求文档,让它从零开始实现,中间不做任何干预。5 个功能,3 个翻车。翻车的规律非常一致。

这个实验想回答什么

Claude Code Routines 上线了,Codex 也在推自主执行模式。社区的方向很明确:让 AI 越来越独立地写代码,减少人类干预。

但我有个疑问:让 Agent 独立写一个完整功能——不是修一个 bug,不是补一段逻辑,而是从需求文档到可运行的代码——它到底能做到什么程度?

SWE-bench 测的是给你一个 issue、一个 repo,让你改对。现实中开发一个新功能要做的事远不止这些:理解需求、设计数据结构、规划文件结构、处理边界情况、和现有代码风格保持一致。这些 benchmark 覆盖不到。

实验设计

我选了自己的一个 side project(Node.js + TypeScript + SQLite),给 Claude Code 5 个不同复杂度的功能需求,每个需求用一份简短的 PRD 描述。规则:

  • 给完 PRD 之后不做任何干预
  • Agent 自主决定文件结构、数据模型、API 设计
  • 用 Claude Code 的 /code 命令执行,开启 auto-accept
  • 最终评估:能不能跑起来、代码质量、和现有代码的一致性

模型用 Opus 4.7(xhigh effort),通过 API 网关调用。

5 个功能需求

编号 功能 复杂度 涉及文件数
F1 给用户表加一个"最后活跃时间"字段,登录时自动更新 2-3
F2 实现一个简单的 API Key 管理模块(CRUD + 哈希存储) 中低 3-5
F3 添加操作日志功能:记录所有 API 调用,支持按时间范围查询 5-7
F4 实现 Webhook 通知系统:事件触发、重试机制、签名验证 中高 8-12
F5 实现多租户权限隔离:租户→成员→角色→资源权限的完整 RBAC 12-18

结果总览

功能 能跑起来? 代码质量 关键问题
F1 最后活跃时间
F2 API Key 管理 哈希算法选择偏保守
F3 操作日志 (有 bug) 时区处理有问题,缺索引
F4 Webhook 系统 勉强能 第 7 步开始架构偏离
F5 多租户 RBAC 不能 第 5 步开始偏,越走越远

低复杂度(F1-F2)完美通过。中等复杂度(F3)基本可用但有细节 bug。中高以上(F4-F5)都翻车了。

F1-F2:Agent 的舒适区

F1 只涉及 2 个文件:migration 加字段 + 登录接口更新。Agent 的执行步骤:

  1. 读取现有 schema
  2. 生成 migration SQL
  3. 修改登录函数,加 UPDATE users SET last_active_at = datetime('now')
  4. 加了一个查询接口

4 步,每步都合理,代码风格和项目一致。

F2 涉及 4 个文件:model、service、router、migration。同样干净利落。唯一小问题:它选了 bcrypt 做 API Key 哈希——能用但偏慢。实际场景下 SHA-256 + salt 更合适,因为 API Key 不需要 bcrypt 那种故意慢的设计。但这不算错,只是经验判断不同。

结论:3-5 个文件以内的功能,Agent 完全可以独立完成。

F3:开始露出问题

操作日志功能,Agent 的执行步骤:

  1. 创建 audit_logs 表(合理)
  2. 写中间件拦截所有 API 请求(合理)
  3. 记录请求和响应体(合理)
  4. 实现按时间范围查询的 API(合理)
  5. 添加分页(合理)
  6. ——但时间范围查询用了 JavaScript 的 new Date() 做比较,没有处理时区

第 6 步不是"错了",是遗漏了一个边界情况。数据库里存的是 UTC,查询参数传的是本地时间,没有转换。在开发环境可能看不出来,部署到服务器(UTC 时区)后查询结果会偏移 8 小时。

另一个问题:audit_logs 表没有加索引。前几千条数据看不出来,数据量上去之后按时间范围查询会很慢。

这两个问题的特点是:Agent 不会主动去想"生产环境会怎样"。 它解决了你明确提出的需求,但不会替你操心部署环境的差异和数据量增长。

F4:第 7 步开始偏离——最有价值的观察

Webhook 系统是这次实验最有意思的案例。我来逐步拆解 Agent 的 14 步执行过程。

步骤 1-6(正确):

  1. 创建 webhooks 表:URL、事件类型、密钥 ✓
  2. 创建 webhook_deliveries 表:记录每次发送 ✓
  3. Webhook CRUD API ✓
  4. 事件触发函数:当特定事件发生时向所有订阅者发送 HTTP POST ✓
  5. 请求签名:用 HMAC-SHA256 对 payload 签名 ✓
  6. 基本的失败重试:发送失败后延迟重试 ✓

到这里都没问题,代码清晰,和项目风格一致。

步骤 7(偏离开始):

Agent 决定实现一个"高级重试策略"——指数退避 + 死信队列。这本身不是坏主意,但它的实现方式出了问题:

// Agent 写的代码(简化版)
class WebhookRetryQueue {
  private queue: Map<string, RetryJob> = new Map();
  private timers: Map<string, NodeJS.Timer> = new Map();
  
  schedule(deliveryId: string, attempt: number) {
    const delay = Math.pow(2, attempt) * 1000; // 指数退避
    const timer = setTimeout(() => this.execute(deliveryId), delay);
    this.timers.set(deliveryId, timer);
  }
  // ...
}

问题:它把重试队列放在了内存里。 setTimeout + Map 是纯内存方案。进程重启后所有待重试的任务全部丢失。

对于一个 demo 来说没问题。对于一个声称支持"可靠重试"的 Webhook 系统来说,这是架构级别的缺陷。正确做法是把重试任务持久化到数据库,用定时轮询来驱动。

步骤 8-14(在错误基础上继续建设):

Agent 没有意识到步骤 7 的问题,继续在内存队列的基础上搭建:

  • 步骤 8:加了队列状态查询 API(查的是内存里的 Map)
  • 步骤 9:加了死信队列(也在内存里)
  • 步骤 10:加了并发控制(限制同时发送的请求数)
  • 步骤 11:加了统计面板(重试成功率、失败率)

每一步的局部逻辑都没错。但因为地基(步骤 7)选错了方案,后面所有的工作都建在一个不可靠的基础上。

这就是我说的"第 7 步偏离"——不是语法错误,不是逻辑 bug,是一个合理但不合适的架构决策。这种错误 linter 检测不到,测试覆盖不了,只有有经验的工程师在 code review 时才能发现。

F5:偏离得更早,偏离得更远

多租户 RBAC 是最复杂的功能。Agent 在第 5 步就开始偏离了。

它设计的权限模型:

// Agent 的设计
interface Permission {
  userId: string;
  resource: string;     // "project", "document", "api_key"
  action: string;       // "read", "write", "delete"
  tenantId: string;
}

问题:它把权限绑定到了 userId 而不是 roleId。这意味着每个用户的权限要单独分配,没有"角色"这个抽象层。我的 PRD 里明确写了"租户→成员→角色→资源权限"四层结构。

Agent 跳过了"角色"这一层。为什么?我猜是因为直接绑定用户更简单,它走了阻力最小的路径。

后面 10 几步都在这个错误模型上继续开发——权限检查、管理 API、中间件。全部返工。

提炼出的规律

特征 成功的任务 (F1-F2) 失败的任务 (F4-F5)
涉及文件数 2-5 8-18
需要架构决策 不需要 需要 2-3 个关键决策
偏离发生的步骤 第 5-7 步
偏离的类型 合理但不合适的架构选择
能否通过测试发现 不能(逻辑正确,方案错误)

核心规律:Agent 在"局部正确"上很强,在"全局合适"上很弱。

它能写出正确的代码、通过的测试、没有 bug 的逻辑。但当面临需要权衡多个因素的架构决策时(内存 vs 持久化、直接绑定 vs 间接引用、简单 vs 可扩展),它倾向于选择最直接、最简单的方案。

这些"简单方案"在小规模下完全能用——这也是为什么 benchmark 测不出来,因为 benchmark 的测试用例规模都不大。

我的工作流因此改了

实验之后我调整了和 Agent 协作的方式:

之前: 给完整 PRD,让 Agent 从头做到尾。

现在: 把工作拆成两层。

人类负责:
├── 选择技术方案(内存 vs 持久化、同步 vs 异步)
├── 定义核心接口和数据模型
├── 决定文件结构
└── 指定哪些边界情况必须处理

Agent 负责:
├── 实现每个接口的具体逻辑
├── 写测试
├── 处理错误情况
└── 写文档

用一个实际的例子:Webhook 系统如果重新来过,我会这样给 Agent:

## Webhook 系统实现要求

### 数据模型(你必须遵循这个设计)
- webhooks 表:id, url, secret, events, tenant_id, created_at
- webhook_deliveries 表:id, webhook_id, event, payload, status, attempts, next_retry_at

### 重试策略(架构决策已确定)
- 用 **数据库** 存储待重试任务,不要用内存
- 用 cron 定时轮询 next_retry_at < NOW() 的记录
- 指数退避:1min, 5min, 30min, 2h, 12h
- 最多重试 5 次,之后标记 failed

### 你负责实现
- Webhook CRUD API
- 事件触发函数
- HMAC-SHA256 签名
- 重试轮询逻辑
- 发送结果记录

关键区别:架构决策是我做的,实现细节是 Agent 做的。 这样 Agent 的工作范围限制在 3-5 个文件的"舒适区"内,每个文件它都能高质量完成。

用这个新流程重新做了 F4(Webhook 系统),Agent 一次通过,代码质量很好,而且持久化重试在进程重启后也能恢复。

这跟模型选择有关系吗

我用 DeepSeek V4 重跑了 F4,偏离点从第 7 步提前到了第 5 步。用 Opus 4.7 的 max effort,偏离点推迟到了第 9 步——但最终还是偏了(选了 Redis 队列而不是数据库持久化,对于这个项目来说是过度设计)。

模型 F4 偏离步骤 偏离严重程度
DeepSeek V4 第 5 步 高(缺少重试机制)
Opus 4.7 xhigh 第 7 步 中(内存队列)
Opus 4.7 max 第 9 步 低(Redis 过度设计)

模型越强,偏离得越晚、越轻微。但所有模型最终都会偏。这不是模型的能力问题,是任务结构的问题——跨文件的架构一致性不在 Agent 的训练信号里。

如果你的 Agent 要处理不同复杂度的任务,可以按复杂度路由到不同模型——简单实现走便宜的,涉及架构决策的走 Opus。通过 API 网关按任务类型自动路由,成本和质量的平衡最优。


TheRouter — 多模型 API 网关,一个 Key 调 30+ 模型。按任务复杂度自动路由:简单实现走轻量模型,架构级任务走 Opus,成本降 60%。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐