平时用 AI 编程助手改代码时,我经常陷入一种很尴尬的流程:Agent 改到一半,我会忍不住自己上手微调几行逻辑,比如改个参数默认值、补一个边界判断,或者把函数签名调整一下。结果再让 Agent 继续干活时,它就像短暂失忆了一样,要么重复生成已经被我改掉的代码,要么在我刚改过的地方继续“叠床架屋”。
这种“用户中途触摸代码”的协作场景,在真实开发里非常常见,但在主流 Coding Agent 评测体系里却长期缺位。SWE-Touch 这个基准测试(Benchmark)正是冲着这个问题来的:当用户已经亲手改过代码之后,编码智能体还能不能保持正确的理解和判断?
本文会围绕 SWE-Touch 的评测思想展开,先讲清楚它和传统编码智能体评测的差异,再拆解真实协作中的挑战,最后用一个最小可运行的 Python 示例,演示一套简化版评测闭环。适合正在做 Coding Agent 应用开发、或者准备给团队接入 AI 编程助手的后端开发者阅读。
1. 背景与核心概念:从 SWE-bench 到 SWE-Touch
1.1 编码智能体评测的主流范式
先来回顾一下 Coding Agent 评测的主流做法。以大家熟悉的 SWE-bench 类基准测试为例,它的评测流程大致是这样的:给定一个真实 GitHub Issue,再给一个完整代码仓库的固定快照,让编码智能体独立完成问题定位、代码修复、生成补丁,最后用仓库自带的测试集验证补丁是否让相关用例通过。
这种“Issue → Patch → Test”的闭环设计,优点是清晰、可复现、可横向比较。不同 Agent 面对同样的任务,最后看通过率就能分出高下,因此它很快成为编码智能体领域最常用的评测范式。
但它隐含了一个很强的前提假设:Agent 是在一份“静止的、无人打扰的代码”上工作的。任务一开始,仓库状态就是最终状态,Agent 只需要从零开始理解这份代码,然后独立完成修改,不需要考虑别人是不是已经动过代码。
这个假设在大规模评测时是合理的,因为它保证了所有 Agent 面对完全相同的起点。但放到真实开发环境里,这个假设经常不成立。
1.2 用户触摸代码:真实协作中被低估的变量
现实中的 AI 辅助编程,从来不是“Agent 独自完成全部工作”的孤岛模式,而是一种高度频繁的人机协作。用户会根据自己对业务逻辑的理解,随时插入到代码修改过程中。
常见的有三种情况:
- 用户先改,Agent 后做:用户已经手动实现了一部分功能,剩下的交给 Agent 补齐。
- Agent 做到一半,用户插进来:Agent 正在生成代码或补丁的过程中,用户自己改了相关文件,Agent 需要基于新的状态继续。
- Agent 改完,用户 reivew 后又改:Agent 产出了修复补丁,用户 review 时手动调整了实现方式或 API 名称,后续 Agent 还要继续维护。
这三种情况里,代码状态已经不是“初始快照”,而是被用户动过的“中间状态”。用户对代码的每一次修改,都可能是新增约束、调整接口、修复一部分 bug,也可能无意中留下新的问题。
SWE-Touch 这个名字里,“Touch”指的就是用户亲手触摸、修改代码这个动作。它把这类场景纳入了基准测试的核心设计,考察 Agent 在用户已改过代码之后,是否还能正确感知变更、理解用户意图,并在此基础上继续高质量地完成编码任务。
1.3 SWE-Touch 的核心评测视角
从评测方法论上看,SWE-Touch 与传统基准测试最大的区别在于:它不再只关注“Agent 能否独立从零生成正确补丁”,而是关注“Agent 能否在一个已经被用户修改过的代码状态下,继续完成剩余工作”。
这听起来只是一点任务构造差异,实际上会引出一系列更复杂的评测维度:
- 变更感知:Agent 能不能主动发现用户改过哪些文件,而不是两眼一抹黑地基于旧理解输出代码。
- 意图对齐:用户手动改代码往往带有明确的业务意图,Agent 能不能从改动中反推这些意图,并保持对齐。
- 冲突处理:Agent 要补充的实现,会不会和用户已经写好的部分产生冲突,会不会把用户刚写的代码“反向改回去”。
- 回归判断:用户改过之后,原本能通过的测试是否仍然安全,Agent 有没有能力在不破坏用户修改的前提下完成剩余工作。
换句话说,SWE-Touch 想评测的不是“Agent 的代码能力”,而是“Agent 在真实协作场景中的可持续工作能力”。
2. 现实场景中的协作模式与挑战
2.1 三种典型的用户触摸模式
为了把问题讲清楚,我们可以把用户触摸代码的行为归纳成三种典型模式。
| 模式 | 发生时机 | 用户行为 | Agent 面临的典型任务 |
|---|---|---|---|
| 先手触摸 | Agent 开始工作之前 | 用户已手动改好一部分代码,留下 TODO 或半成品 | 理解已有改动,补齐剩余逻辑 |
| 中途插入 | Agent 工作过程中 | 用户直接修改了 Agent 正在处理的文件 | 重新读取最新状态,避免重复或冲突 |
| 后置修改 | Agent 产出补丁之后 | 用户 review 并手动调整了 Agent 的改动 | 基于新改动继续迭代,不能回退用户调整 |
在传统评测里,这三种模式基本没有体现。任务一开始仓库就是“干净”的,Agent 不会意识到中途会有人插手,也不需要理解“用户意图”这种偏主观的变量。
2.2 用户修改给 Agent 带来的四类困难
当用户真的摸过代码之后,Agent 拿到的不再是一个单纯的技术问题,而是一个混合了“代码理解问题”和“协作对齐问题”的复杂任务。
第一类是信息过时。真实项目里用户可能直接编辑文件,也可能通过 IDE 重构、批量替换等方式改了多个文件。Agent 如果只依赖刚才上下文里的代码片段,而不是重新读取当前文件内容,就会基于过时信息生成代码。结果是代码合并后出现重复定义、接口错位甚至语法错误。
第二类是隐含约束。用户手动改代码往往不是随便改的,背后通常有业务约定。比如把某个参数默认值从False改成True,可能意味着后续所有调用方都希望走四舍五入逻辑。Agent 如果看不见这个改动背后的意图,就很难写出符合预期的实现。
第三类是目标漂移。用户中途插入修改,可能让原始任务的边界发生变化。比如原本只是让 Agent 修一个计算 bug,用户顺手把函数签名改了,Agent 再继续“修 bug”时,必须先把签名调整考虑进去,否则所有修法都是白做。
第四类是变更回退风险。最糟糕的不是 Agent 不会改,而是 Agent 生成的补丁把用户刚写好的代码又覆盖回去了。这种问题在自动化评测里很难自动发现,因为测试可能照样通过,但用户实际体验是灾难性的。
2.3 为什么传统 benchmark 无法覆盖这些情况
传统基准测试之所以没有覆盖这些情况,根因在于它把“任务”定义得太线性了。任务只有一个起点、一个终点,Agent 从头走到尾即可。
真实开发中,任务更像是“状态流”:初始仓库状态 → 用户手动修改 → Agent 继续修改 → 用户 review 修改 → Agent 再次修改。每一步都可能产生新的状态,Agent 必须随时响应状态变化。
此外,传统评测的判定标准通常只有“测试是否通过”,它不太关心 Agent 是否破坏了用户已有的修改。SWE-Touch 这类基准测试的出现,本质上是在推动评测标准从“结果正确性”转向“协作正确性”,让 Benchmarking 更贴近真实工程实践。
3. 环境准备与最小评测框架搭建
理解了背景之后,我们来动手搭一个最小评测框架。这个框架会模拟 SWE-Touch 的核心思路:仓库处于“用户触摸后”的中间状态,Agent 需要感知这个状态,并完成剩余开发任务。
3.1 运行环境建议
本文的示例以常见的 Python 环境演示,重点讲解评测闭环的设计思路,不依赖特定云服务或复杂基础设施。
需要注意,具体版本需要根据你的项目实际情况调整。建议准备:
- Python 3.10 或更高版本;
- pytest 测试框架;
- git 命令行工具;
- 一个终端或 IDE 内置终端。
3.2 项目目录结构
我们先创建一个名为swetouch-demo的目录,模拟一个最小仓库。
mkdir -p swetouch-demo/app swetouch-demo/tests cd swetouch-demo python -m venv .venv source .venv/bin/activate pip install pytest git initWindows 环境下,虚拟环境激活命令稍有不同,需要改为:
.venv\Scripts\activate最终目录结构如下:
swetouch-demo/ ├── app/ │ ├── __init__.py │ └── pricing.py ├── tests/ │ └── test_pricing.py └── eval_runner.py3.3 准备一个最小的示例仓库
为了让示例足够聚焦,我们模拟一个电商场景中的折扣计算函数。业务目标很简单:给定商品原价和折扣比例,计算折后价,并支持一个“是否四舍五入到分”的开关。
先创建app/__init__.py,内容可以留空,只是为了把app变成可导入的 Python 包:
touch app/__init__.py现在准备“用户触摸之后”的代码状态。
4. 核心原理:如何构造 SWE-Touch 风格评测任务
在写代码之前,先理解评测任务是怎么构造的。SWE-Touch 风格的评测任务,本质上是一个带“中间状态”的状态机。
4.1 任务状态流
我们可以把一次评测任务拆成四个阶段:
初始仓库(Start) → 用户手动修改(User Touch) → Agent 生成补丁(Agent Patch) → 运行测试验证(Test Result)第一阶段是初始仓库,对应传统基准里的原始代码状态。第二阶段会模拟用户对代码的手动修改,这是 SWE-Touch 任务最核心的部分。第三阶段让 Agent 基于用户修改后的状态继续工作。第四阶段用测试集验证最终结果。
需要特别强调的是,用户手动修改不是简单地在代码里“埋一个 bug”,而是模拟真实协作中的部分实现、接口调整或约束新增。用户的改动本身可能是有意义的开发进度,Agent 不能无视它。
4.2 评测指标设计
在 SWE-Touch 风格的评测里,指标设计比传统测试通过率更丰富一些。通常可以关注以下四类指标:
第一,测试通过率。这是最基础的指标,决定 Agent 的补丁是否让任务功能上正确。
第二,用户变更保持率。Agent 生成的补丁是否保留用户手动修改的部分,有没有出现“把用户代码改回去”的情况。
第三,迭代效率。Agent 是花了一步就理解了用户改动,还是反复生成大量无效补丁后才摸清状态。
第四,变更定位能力。Agent 能否准确识别用户改过的文件,而不是在无关文件里做无用功。
在本文的最小示例中,我们主要以测试通过率作为可验证指标,其余指标可以在真实评测平台中扩展实现。
4.3 构造任务时的注意事项
构造这类评测任务时,有几个细节容易踩坑。
一是用户修改不能太随意。用户的改动要有合理性,比如补充了部分参数校验、预留了接口,而不是毫无逻辑地删代码。
二是测试用例要区分“原有约束”和“用户新增约束”。最好用注释或显式标记说明哪些测试是用户触摸之后新增的,这样 Agent 能更清楚用户改了什么。
三是必须提供显式的“当前状态”。真实开发中用户改了代码一般会留下工作区变更,Agent 可以通过git diff感知差异。评测框架也应该把这种感知能力纳入考量。
5. 模拟实战:用 Python 实现一个简化版 SWE-Touch
接下来进入完整实战环节。我们会手动构建一个“用户触摸后”的代码状态,并写一个评测脚本,模拟 Agent 从当前状态继续完成任务的完整流程。
5.1 定义“用户已触摸”的代码状态
先创建app/pricing.py。这个文件是用户手动修改后的版本,也是 Agent 开始工作前看到的起点。
# 文件路径:swetouch-demo/app/pricing.py # 当前状态:用户手动修改后的版本(Agent 看到的起点) def calculate_discount(price: float, discount: float, round_result: bool = True) -> float: """计算折后价格。 参数说明: price : 商品原价,必须大于等于 0 discount : 折扣比例,范围 0 ~ 100 round_result : 是否将最终结果四舍五入到两位小数 """ if price < 0: raise ValueError("price 不能为负数") if discount < 0 or discount > 100: raise ValueError("discount 必须在 [0, 100] 区间") result = price * (1 - discount / 100) # TODO(agent): 请根据 round_result 参数决定是否对结果四舍五入 # 用户已经完成了参数校验和基础折扣计算,剩余逻辑由 Agent 补充。 return result注意,这个代码是“半成品”。用户已经完成了参数校验和基础折扣计算,但round_result参数还没有生效。Agent 的任务就是读取这个函数,理解用户已经完成的部分,然后补齐剩余逻辑。
这里面没有埋所谓的低质量 bug,反而更贴近真实协作:用户做了一部分,剩下的交给 Agent。
5.2 加入用户补充的测试用例
接着创建测试文件tests/test_pricing.py。其中前两个用例是原有约束,后两个用例是用户触摸后新增的约束。
# 文件路径:swetouch-demo/tests/test_pricing.py import pytest from app.pricing import calculate_discount def test_normal_discount(): """基础折扣场景。""" assert calculate_discount(100, 20) == 80.0 assert calculate_discount(100, 0) == 100.0 def test_full_discount(): """100% 折扣时价格应为 0。""" assert calculate_discount(100, 100) == 0.0 def test_round_result_true(): """用户补充用例:默认对结果四舍五入到分。""" assert calculate_discount(99.99, 10, round_result=True) == 89.99 def test_round_result_false(): """用户补充用例:关闭四舍五入时保留原始精度。""" assert calculate_discount(99.99, 10, round_result=False) == 89.991这里有两个非常关键的细节。
第一,test_round_result_true和test_round_result_false是用户触摸之后新增的用例,它们明确了用户对round_result参数的约束。
第二,如果 Agent 没有感知到用户已经新增了这些约束,直接把函数按旧逻辑写死,就无法通过全部测试。
5.3 编写评测脚本
接下来创建eval_runner.py,这是整个评测闭环的入口。它会模拟一个 benchmark 系统:应用 Agent 提交的补丁,然后运行测试集,根据结果判定任务是否成功。
# 文件路径:swetouch-demo/eval_runner.py """简化版 SWE-Touch 评测脚本。 它演示了 benchmark 的核心闭环: 1. 仓库处于“用户触摸后”的中间状态; 2. Agent 提交一个补丁文件; 3. 评测系统应用补丁并运行测试; 4. 根据测试结果判定任务是否成功。 """ import argparse import subprocess from pathlib import Path REPO_DIR = Path(__file__).resolve().parent TEST_CMD = ["python", "-m", "pytest", "tests/", "-q"] def apply_patch(patch_path: Path) -> None: """把 Agent 输出的 patch 应用到仓库。""" result = subprocess.run( ["git", "apply", str(patch_path)], cwd=REPO_DIR, capture_output=True, text=True, ) if result.returncode != 0: raise RuntimeError( f"应用 patch 失败,stderr:\n{result.stderr}" ) print(f"==> 已应用 patch: {patch_path.name}") def run_tests() -> bool: """运行单元测试,返回是否全部通过。""" result = subprocess.run( TEST_CMD, cwd=REPO_DIR, capture_output=True, text=True, ) print(result.stdout) if result.stderr: print(result.stderr) return result.returncode == 0 def main() -> None: parser = argparse.ArgumentParser(description="SWE-Touch 简化评测脚本") parser.add_argument( "--patch", type=Path, default=Path("agent.patch"), help="Agent 生成的 patch 文件,默认使用当前目录下的 agent.patch" ) args = parser.parse_args() if not args.patch.exists(): raise FileNotFoundError(f"找不到 patch 文件: {args.patch}") print("==> 步骤 1: 应用 Agent patch") apply_patch(args.patch) print("==> 步骤 2: 运行测试集") passed = run_tests() print(f"==> 任务结果: {'PASSED' if passed else 'FAILED'}") if not passed: raise SystemExit(1) if __name__ == "__main__": main()这个脚本虽然简单,但已经具备了一个 benchmark 评测器的基本骨架:接收补丁、应用补丁、执行验证、输出结果。真实评测平台会在此基础上增加沙箱隔离、超时控制、多任务批量执行等能力。
5.4 运行与验证
先不应用任何补丁,直接运行测试,能够看到当前“用户触摸后”的状态并不能通过全部用例。
cd swetouch-demo python -m pytest tests/ -q预期输出大概是这样:
..FF ================================== FAILURES ================================== ___________ test_round_result_true ____________ ... ___________ test_round_result_false ____________ ... 2 failed, 2 passed in 0.05s两个原有用例通过,两个用户新增用例失败。这说明 Agent 必须继续工作,补齐round_result的处理逻辑。
现在模拟 Agent 的工作成果。创建一个补丁文件agent.patch:
diff --git a/app/pricing.py b/app/pricing.py --- a/app/pricing.py +++ b/app/pricing.py @@ -13,6 +13,8 @@ def calculate_discount(price: float, discount: float, round_result: bool = True) result = price * (1 - discount / 100) - # TODO(agent): 请根据 round_result 参数决定是否对结果四舍五入 - # 用户已经完成了参数校验和基础折扣计算,剩余逻辑由 Agent 补充。 - return result + if round_result: + result = round(result, 2) + + return result然后运行评测脚本:
python eval_runner.py --patch agent.patch预期输出:
==> 步骤 1: 应用 Agent patch ==> 已应用 patch: agent.patch ==> 步骤 2: 运行测试集 .... [100%] 4 passed in 0.02s ==> 任务结果: PASSED到这里,一个简化版的 SWE-Touch 评测闭环就跑通了。
5.5 从模拟到真实 Benchmark
需要提醒的是,上面这个示例是教学级模拟,目的是帮助你理解 SWE-Touch 的评测思想。真实的 SWE-Touch 基准测试会复杂得多,主要体现在几个方面。
任务规模更大。真实基准会准备大量真实仓库、真实 Issue 和真实用户修改,形成完整的任务集,而不是单独一个函数。
用户修改更贴近真实历史。真实用户修改可能涉及多个文件、重构、注释调整、测试补充等,Agent 需要依赖工具感知变更,而不只是读一个文件。
评测流程更工程化。包括 Docker 沙箱、资源限制、超时控制、防止 Agent 作弊、统一补丁校验、多轮交互日志记录等。
不过,把最小闭环跑通之后,再去阅读真实基准的评测代码和任务构造逻辑,会轻松很多。
6. 常见问题与排查思路
在搭建和运行这类评测闭环时,比较容易遇到一些共性问题。这里整理成一份排查清单。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
应用 patch 失败,报patch does not apply | 仓库状态和 patch 基准不一致,或 Agent 基于旧状态生成补丁 | 确认仓库处于用户触摸后的状态;要求 Agent 先查看git diff再生成补丁 |
| 测试结果不稳定,同一补丁时而过时而不通过 | 测试用例存在随机性或依赖外部服务 | 在评测逻辑中固定随机种子、隔离网络、使用沙箱环境 |
| Agent 生成的补丁把用户修改覆盖了 | Agent 没有感知用户改动,基于旧代码上下文输出 | 在提示词中强制 Agent 先读取当前文件,再输出补丁;评测时增加“用户变更保持率”指标 |
| 评测时仓库状态被污染 | 上一次测试的补丁没有回滚 | 在评测脚本执行前重置仓库,回到“用户触摸后”的状态 |
| 不同 Agent 结果无法横向对比 | 评测基准不统一、测试集不一致 | 固定同一份 task 集和测试集,统一超时和资源限制 |
| Agent 反复尝试但一直不通过 | 用户新增约束没有被 Agent 理解 | 检查用户改动是否足够清晰;真实场景中可要求 Agent 先输出对用户改动的理解 |
在这类问题里,最容易被忽视的是“仓库状态被污染”。尤其是本地手工实验时,上一次git apply的补丁如果没回滚,下一次评测结果就会失真。建议在评测脚本里增加一个 reset 步骤,每次都从干净的“用户触摸状态”出发。
7. 最佳实践与工程建议
7.1 对 Coding Agent 开发者的建议
如果你在开发自己的 Coding Agent,SWE-Touch 的核心思想可以直接落到产品设计里。
让 Agent 具备“先读差异再动手”的习惯。当 Agent 进入一个仓库时,首先应该执行git diff、git status之类的能力,明确当前工作区有哪些用户改动,再基于最新状态规划修改方案。
给 Agent 提供“用户改动摘要”的接口。对于已有的代码改动,可以通过工具或者预处理的变更摘要,把用户修改的文件、函数、约束条件注入到上下文中。这能显著降低 Agent 对用户意图的误解。
在评测链路中加入“用户改动保持检查”。简单做法是在评测脚本中,先保存用户改动文件的内容摘要,Agent 输出补丁后,检查这些文件的核心改动是否仍然存在。如果被 Agent 反向回退,即便测试通过,也应判定为失败或降权。
7.2 对 Benchmark 使用者的建议
如果你正在用 SWE-Touch 或类似基准评估编码智能体,建议不要只看整体通过率,重点关注 Agent 在“用户触摸后”的稳定性。
具体来说,可以按维度拆分统计:用户先手修改场景下的通过率、Agent 中途被插入修改时的通过率、Agent 修改后用户再 review 的通过率。三种场景的难度和风险完全不同,混在一起看容易掩盖问题。
同时,尽量让 Agent 在评测中拥有与真实开发一致的工具权限。如果 Agent 没有读取git diff的通道,那么在 SWE-Touch 任务里天然处于劣势,这种劣势不能完全代表模型能力。
7.3 对将 Agent 落地到真实项目的团队
对真实项目团队来说,SWE-Touch 提醒我们的其实是工程协作设计问题。团队在引入 AI 编程助手时,应该为“人机协作改码”设定清晰的秩序。
一方面,尽量让 AI 和用户的改动范围解耦。比如约定 AI 优先生成新文件或新增函数,用户手工维护核心业务逻辑,减少同一区域的直接冲突。
另一方面,建立代码变更的强制 review 机制。Agent 产生的 diff,必须经过git diff检视后再入库,不要让 Agent 直接提交。这样即使 Agent 基于过时信息输出了补丁,也能在合并之前被拦截。
在配置 Agent 的权限时,遵循最小权限原则也是必要的。不要让 Agent 直接改动 git 历史、强制推送或批量替换文件,所有高风险操作都应该限制在测试环境或者经过人工确认后执行。
8. 总结与下一步学习方向
这篇文章从一个真实协作痛点出发,介绍了 SWE-Touch 这种基准测试的核心思想:编码智能体不应该只会在干净仓库里完成 Issue 修复,更应该能在用户已经触摸过代码之后继续稳定工作。
我们搭建了一个最小可运行的评测闭环,模拟了“用户手动修改代码 → Agent 继续补齐逻辑 → 测试验证结果”的完整流程。虽然示例规模很小,但评测框架的四个核心阶段——初始状态、用户触摸、Agent 补丁、测试验证——已经完整呈现。
接下来如果你对这个方向感兴趣,可以沿着两条线继续深入。一条是在真实任务集上跑 SWE-Touch,观察不同 Coding Agent 在用户修改场景下的稳定性差异;另一条是改进自己的 Agent 产品,让它具备“感知变更、尊重用户改动、规避冲突”的协作能力。
最后留一个实践题目给你:在这个示例仓库里,如果用户触摸后的代码不是留下 TODO,而是把round_result的处理逻辑写反了,Agent 应该怎样才能在不破坏用户意图的前提下完成任务?建议自己动手跑一遍,会很有收获。
如果你的工作流里也经常出现“AI 改到一半、你又手动改了几行”的情况,欢迎在评论区分享你的场景和踩坑经历。