所有 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 的执行步骤:
- 读取现有 schema
- 生成 migration SQL
- 修改登录函数,加
UPDATE users SET last_active_at = datetime('now') - 加了一个查询接口
4 步,每步都合理,代码风格和项目一致。
F2 涉及 4 个文件:model、service、router、migration。同样干净利落。唯一小问题:它选了 bcrypt 做 API Key 哈希——能用但偏慢。实际场景下 SHA-256 + salt 更合适,因为 API Key 不需要 bcrypt 那种故意慢的设计。但这不算错,只是经验判断不同。
结论:3-5 个文件以内的功能,Agent 完全可以独立完成。
F3:开始露出问题
操作日志功能,Agent 的执行步骤:
- 创建
audit_logs表(合理) - 写中间件拦截所有 API 请求(合理)
- 记录请求和响应体(合理)
- 实现按时间范围查询的 API(合理)
- 添加分页(合理)
- ——但时间范围查询用了 JavaScript 的
new Date()做比较,没有处理时区
第 6 步不是"错了",是遗漏了一个边界情况。数据库里存的是 UTC,查询参数传的是本地时间,没有转换。在开发环境可能看不出来,部署到服务器(UTC 时区)后查询结果会偏移 8 小时。
另一个问题:audit_logs 表没有加索引。前几千条数据看不出来,数据量上去之后按时间范围查询会很慢。
这两个问题的特点是:Agent 不会主动去想"生产环境会怎样"。 它解决了你明确提出的需求,但不会替你操心部署环境的差异和数据量增长。
F4:第 7 步开始偏离——最有价值的观察
Webhook 系统是这次实验最有意思的案例。我来逐步拆解 Agent 的 14 步执行过程。
步骤 1-6(正确):
- 创建
webhooks表:URL、事件类型、密钥 ✓ - 创建
webhook_deliveries表:记录每次发送 ✓ - Webhook CRUD API ✓
- 事件触发函数:当特定事件发生时向所有订阅者发送 HTTP POST ✓
- 请求签名:用 HMAC-SHA256 对 payload 签名 ✓
- 基本的失败重试:发送失败后延迟重试 ✓
到这里都没问题,代码清晰,和项目风格一致。
步骤 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%。