缓存时间:
2026/05/08 09:43
# Claude SWE-Bench 性能表现
来源:https://www.anthropic.com/engineering/swe-bench-sonnet
*我们的最新模型——升级版 Claude 3.5 Sonnet(https://www.anthropic.com/news/3-5-models-and-computer-use),在软件工程评估基准 SWE-bench Verified 上取得了 49% 的成绩,超越了此前最先进模型的 45%。本文将介绍我们围绕该模型构建的"智能体(agent)",旨在帮助开发者充分发挥 Claude 3.5 Sonnet 的性能。*
SWE-bench(https://www.swebench.com/)是一个 AI 评估基准,用于测试模型完成真实软件工程任务的能力。具体来说,它测试模型如何解决来自热门开源 Python 仓库的 GitHub issue。对于基准中的每个任务,AI 模型会被提供一个配置好的 Python 环境和 issue 解决前的仓库代码(本地工作副本)。随后,模型需要理解、修改并测试代码,最终提交其提议的解决方案。每个解决方案都会与原始 GitHub issue 对应的 pull request 中的真实单元测试进行比对,检验 AI 模型是否能实现与原始 PR 作者相同的功能。
SWE-bench 并非孤立地评估 AI 模型,而是评估整个"智能体"系统。在此语境下,"智能体"指的是 AI 模型及其周围的软件脚手架的组合。该脚手架负责生成输入模型的提示词、解析模型输出以执行操作,以及管理交互循环——将模型先前操作的结果整合到下一次提示中。智能体在 SWE-bench 上的表现可能因脚手架的不同而有显著差异,即使使用相同的基础 AI 模型。
评估大语言模型编程能力的基准还有很多,但 SWE-bench 越来越受欢迎,原因如下:
1. 它使用来自真实项目的工程任务,而非竞赛或面试风格的题目;
2. 尚未饱和——仍有很大的提升空间。目前尚无模型在 SWE-bench Verified 上突破 50% 的完成率(尽管升级版 Claude 3.5 Sonnet 在撰写本文时已达到 49%);
3. 它衡量的是整个"智能体",而非孤立的模型。开源开发者和初创公司在优化脚手架以大幅提升同一模型性能方面取得了巨大成功。
需要注意的是,原始 SWE-bench 数据集中包含一些任务,若无 GitHub issue 之外的额外上下文(例如特定的错误返回信息)则无法解决。SWE-bench-Verified(https://openai.com/index/introducing-swe-bench-verified/)是 SWE-bench 的 500 题子集,经人工审核确保可解,因此为编程智能体的性能提供了最清晰的衡量标准。本文所指即为此基准。
## 达到最先进的水平
### 工具使用型智能体
我们在为升级版 Claude 3.5 Sonnet 设计智能体脚手架时的理念是:尽可能将控制权交给语言模型本身,保持脚手架的简洁性。该智能体包含一个提示词、一个用于执行 bash 命令的 Bash 工具,以及一个用于查看和编辑文件及目录的 Edit 工具。我们持续采样,直到模型判定完成,或超出 200k 的上下文长度限制。
这种脚手架让模型能够自主判断如何解决问题,而非被硬编码到特定模式或工作流中。提示词概述了建议的解决思路,但篇幅适中、不会过于冗长。模型可以自由选择步骤间的推进方式,而非受限于严格的离散转换。如果不受 token 限制,明确鼓励模型生成较长的回复会有所帮助。
以下代码展示了我们智能体脚手架中的提示词:
```
{location}
我已将一个 Python 代码仓库上传至目录 {location}(不在 /tmp/inputs 中)。请考虑以下 PR 描述:
{pr_description}
你能帮我实现仓库中必要的更改,以满足 中指定的需求吗?我已经处理了 中描述的所有测试文件更改。这意味着你**不需要**以任何方式修改测试逻辑或任何测试!你的任务是对 {location} 目录中的非测试文件进行最小化修改,以确保 得到满足。
请按以下步骤解决问题:
1. 首先,探索仓库结构以熟悉其架构。
2. 创建一个脚本来重现错误,并使用 BashTool 执行 `python <文件名>` 以确认错误。
3. 编辑仓库源代码以解决问题。
4. 重新运行你的重现脚本,确认错误已修复!
5. 考虑边界情况,确保你的修复也能妥善处理。
你的思考应当充分详尽,因此即使非常冗长也没关系。
```
模型的第一个工具执行 Bash 命令。其结构简单,仅接收要在环境中运行的命令。然而,工具的描述承载了更多权重,包含针对模型的详细指令,包括输入转义、无网络访问权限,以及如何在后台运行命令等。以下展示 Bash 工具的规格:
```json
{
"name": "bash",
"description": "在 bash shell 中运行命令\n * 调用此工具时,\"command\" 参数的内容**无需**进行 XML 转义。\n * 通过此工具无法访问互联网。\n * 你可以通过 apt 和 pip 访问常见 Linux 和 Python 包的镜像。\n * 状态在命令调用及与用户的对话之间保持持久化。\n * 要查看文件的特定行范围,例如第 10-25 行,可尝试 'sed -n 10,25p /path/to/the/file'。\n * 请避免使用可能产生大量输出的命令。\n * 请在后台运行长期运行的命令,例如 'sleep 10 &' 或在后台启动服务器。",
"input_schema": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "要运行的 bash 命令。"
}
},
"required": ["command"]
}
}
```
模型的第二个工具(Edit 工具)复杂得多,包含模型查看、创建和编辑文件所需的一切功能。同样,我们的工具描述包含关于如何使用该工具的详细信息。我们在各类智能体任务中对这些工具的描述和规格投入了大量精力。我们测试了模型可能误解规格或使用工具时可能遇到的陷阱,然后编辑描述以预先规避这些问题。我们认为,为模型设计工具接口应当像为人类设计工具接口一样受到重视。
以下代码展示了我们 Edit 工具的描述:
```json
{
"name": "str_replace_editor",
"description": "用于查看、创建和编辑文件的自定义编辑工具\n * 状态在命令调用及与用户的对话之间保持持久化\n * 如果 `path` 是文件,`view` 显示应用 `cat -n` 的结果。如果 `path` 是目录,`view` 列出最多 2 层深的非隐藏文件和目录\n * 如果指定 `path` 已作为文件存在,则不能使用 `create` 命令\n * 如果 `command` 生成大量输出,将被截断并标记为 `<...>`
\n * `undo_edit` 命令将撤销对 `path` 文件的最后编辑\n\n使用 `str_replace` 命令的注意事项:\n * `old_str` 参数应与原文件中一个或多个连续行**完全**匹配。注意空白字符!\n * 如果 `old_str` 参数在文件中不唯一,将不会执行替换。确保在 `old_str` 中包含足够的上下文以使其唯一\n * `new_str` 参数应包含要替换 `old_str` 的编辑后行",
...
}
```
我们提升性能的一种方式是给工具"防错"。例如,有时模型在智能体移出根目录后可能会搞错相对文件路径。为防止这种情况,我们简单地要求工具始终使用绝对路径。
我们尝试了多种指定现有文件编辑的策略,发现字符串替换的可靠性最高——模型指定 `old_str` 和 `new_str` 在给定文件中进行替换。仅当 `old_str` 恰好匹配一次时才会执行替换。如果匹配次数多于或少于一次,模型会收到相应的错误信息以供重试。
以下展示我们 Edit 工具的规格:
```json
...
"input_schema": {
"type": "object",
"properties": {
"command": {
"type": "string",
"enum": ["view", "create", "str_replace", "insert", "undo_edit"],
"description": "要运行的命令。允许选项:`view`、`create`、`str_replace`、`insert`、`undo_edit`。"
},
"file_text": {
"description": "`create` 命令的必需参数,包含要创建文件的内容。",
"type": "string"
},
"insert_line": {
"description": "`insert` 命令的必需参数。`new_str` 将插入到 `path` 第 `insert_line` 行之后。",
"type": "integer"
},
"new_str": {
"description": "`str_replace` 命令的必需参数,包含新字符串。`insert` 命令的必需参数,包含要插入的字符串。",
"type": "string"
},
"old_str": {
"description": "`str_replace` 命令的必需参数,包含 `path` 中要替换的字符串。",
"type": "string"
},
"path": {
"description": "文件或目录的绝对路径,例如 `/repo/file.py` 或 `/repo`。",
"type": "string"
},
"view_range": {
"description": "当 `path` 指向文件时,`view` 命令的可选参数。若未提供,显示完整文件。若提供,则显示指定行号范围的文件内容,例如 [11, 12] 将显示第 11 和 12 行。索引起始为 1。设置 `[start_line, -1]` 显示从 `start_line` 到文件末尾的所有行。",
"items": {
"type": "integer"
},
"type": "array"
}
},
"required": ["command", "path"]
}
}
```
## 结果
总体而言,升级版 Claude 3.5 Sonnet 在推理、编程和数学能力方面优于我们先前的模型,以及此前最先进的模型(https://solverai.com/)。它还展现了更强的智能体能力:工具和脚手架帮助这些提升后的能力得到了最佳发挥。
| 模型 | SWE-bench Verified 得分 |
|:---|:---|
| **Claude 3.5 Sonnet (new)** | **49%** |
| Previous SOTA | 45% |
| Claude 3.5 Sonnet (old) | 33% |
| Claude 3 Opus | 22% |
*使用此智能体脚手架的部分模型在 SWE-bench Verified 上的得分。*
## 智能体行为示例
运行基准测试时,我们使用 SWE-Agent(https://swe-agent.com/)框架作为智能体代码的基础。在下方日志中,我们将智能体的文本输出、工具调用和工具响应渲染为 THOUGHT、ACTION 和 OBSERVATION,尽管我们并未强制模型遵循固定顺序。
以下代码块将展示 Sonnet 3.5 解决 SWE-bench 问题的典型过程。在第一个代码块中,你可以看到模型初始提示词的一部分,其中 `{pr_description}` 已填入 SWE-bench 任务的真实值。重要的是,该任务包含重现问题的步骤,这为模型调查提供了宝贵的起点。
```
/repo
我已将一个 Python 代码仓库上传至目录 /repo(不在 /tmp/inputs 中)。请考虑以下 PR 描述:
linear_model.RidgeClassifierCV's Parameter store_cv_values issue
#### Description
Parameter store_cv_values error on sklearn.linear_model.RidgeClassifierCV
#### Steps/Code to Reproduce
import numpy as np
from sklearn import linear_model as lm
#test database
n = 100
x = np.random.randn(n, 30)
y = np.random.normal(size = n)
rr = lm.RidgeClassifierCV(alphas = np.arange(0.1, 1000, 0.1), normalize = True, store_cv_values = True).fit(x, y)
#### Expected Results
Expected to get the usual ridge regression model output, keeping the cross validation predictions as attribute.
#### Actual Results
TypeError: __init__() got an unexpected keyword argument 'store_cv_values'
lm.RidgeClassifierCV actually has no parameter store_cv_values, even though some attributes depends on it.
```
模型做出回应,首先使用 Edit 工具查看仓库结构。你可以在 THOUGHT 和 ACTION 下看到模型的文本输出和工具调用参数,在 OBSERVATION 下看到工具输出的部分内容:
```
THOUGHT: I'll help you implement the changes to satisfy the PR description. Let's follow the steps you outlined.
1. First, let's explore the repo structure:
ACTION (str_replace_editor):
command: view
path: /repo
OBSERVATION:
Here's the files and directories up to 2 levels deep in /repo, excluding hidden items:
/repo
/repo/PULL_REQUEST_TEMPLATE.md
/repo/ISSUE_TEMPLATE.md
/repo/sklearn
/repo/sklearn/decomposition
/repo/sklearn/kernel_ridge.py
/repo/sklearn/model_selection
/repo/sklearn/covariance
/repo/sklearn/cluster
/repo/sklearn/base.py
/repo/sklearn/dummy.py
/repo/sklearn/naive_bayes.py
...
```
现在模型对仓库结构有了更好的了解,它使用 Edit 工具创建一个新脚本,用于重现问题并测试其修复:
```
THOUGHT: Based on the repository structure, we'll need to modify the Ridge Classifier in the `sklearn/linear_model` directory. Let's create a script to reproduce the error:
ACTION (str_replace_editor):
command: create
path: /repo/reproduce_error.py
file_text: import numpy as np
from sklearn import linear_model as lm
#test database
n = 100
x = np.random.randn(n, 30)
y = np.random.normal(size = n)
rr = lm.RidgeClassifierCV(alphas=np.arange(0.1, 1000, 0.1), normalize=True, store_cv_values=True).fit(x, y)
OBSERVATION: File created successfully at: /repo/reproduce_error.py
```
模型随后使用 Bash 工具执行其编写的脚本,成功重现了任务中的问题:
```
THOUGHT: Let's verify that we can reproduce the error:
ACTION (bash):
command: python3 /repo/reproduce_error.py
OBSERVATION: Traceback (most recent call last):
File "/repo/reproduce_error.py", line 10, in <module>
store_cv_values=True).fit(x, y)
TypeError: __init__() got an unexpected keyword argument 'store_cv_values'
```
从此处开始,模型使用 Edit 工具修改仓库中的源代码,并重新运行脚本以验证更改是否解决了问题:
```
THOUGHT: The error is reproducible. Looking at the code, I can see that the issue is that while the `RidgeClassifierCV` class inherits from `_BaseRidgeCV`, it fails to pass the `store_cv_values` parameter from its constructor through to the base class constructor. Let's fix this:
ACTION (str_replace_editor):
command: str_replace
path: /repo/sklearn/linear_model/ridge.py
old_str: def __init__(self, alphas=(0.1, 1.0, 10.0), fit_intercept=True, normalize=False,
scoring=None, cv=None, class_weight=None):
super(RidgeClassifierCV, self).__init__(
alphas=alphas, fit_intercept=fit_intercept, normalize=normalize,
scoring=scoring, cv=cv)
self.class_weight = class_weight
new_str: def __init__(self, alphas=(0.1, 1.0, 10.0), fit_intercept=True, normalize=False,
scoring=None, cv=None, class_weight=None, store_cv_values=False):
super(RidgeClassifierCV, self).__init__(
alphas=alphas, fit_intercept=fit_intercept, normalize=normalize,
scoring=scoring, cv=cv, store_cv_values=store_cv_values)
self.class_weight = class_weight
```
在这个特定示例中,模型工作了 12 步后决定提交。随后该任务的测试成功通过,