
说明:本文不含任何具体产品的评分、排名、耗时或准确率数字。这类数字只有在你自己的仓库、网络、账号下跑出来才有意义。文中的目录结构、YAML 和 Python 脚本可以直接照着搭,示例里的数值只是格式占位,不代表任何产品表现。
最近在给团队选辅助编码工具,发现最容易得出的结论是"我用着挺顺"。这个结论通常经不起复现,原因有三:
所以要建的不是排行榜,而是一套别人拿去也能跑出同样结果的考卷。
说"评一个 AI 产品"其实含糊。能操作编辑器的工具至少是四层叠加:
层与层不隔离,结论换台机器就翻转。实践做法是先固定后面三层只比较一层;或者干脆绕开插件,统一走同一个 API 入口、自己拼上下文,这样"模型层"的对比才干净。下面这套脚本走的是后一条路。
不要按"难度"分类,按"能力边界"分类,每类至少三个任务,每个任务必须带可自动判定的断言:
任务定义用一个 YAML:
id: fix-001
category: 单文件缺陷修复
fixture: fixtures/repo-a
allow_paths:
- src/rate_limit.py
prompt: |
src/rate_limit.py 的 allow() 在窗口边界会把计数算错。
请只修改这个文件,让 test_rate_limit.py 全部通过,不要改测试文件。
assertions:
- name: 单测通过
type: exit0
cmd: python -m pytest -q test_rate_limit.py
- name: 未改测试文件
type: exit0
cmd: test -z "$(git diff --name-only | grep test_)"关键约束:prompt 里不能出现期望代码,断言里不能出现答案,任务集目录不要放在被测仓库内部,否则会被插件自动索引读进上下文,等于泄露考卷。
依赖只有 pyyaml。被测工具用环境变量注入,脚本本身不绑定任何产品:
#!/usr/bin/env python3
import hashlib, json, os, shutil, subprocess, time
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parent
TASKS, FIXTURES, OUT = ROOT / "tasks", ROOT / "fixtures", ROOT / "runs"
def tree_sha(path):
h = hashlib.sha256()
for p in sorted(path.rglob("*")):
if p.is_file():
h.update(str(p.relative_to(path)).encode())
h.update(p.read_bytes())
return h.hexdigest()[:16]
def prepare(task, run_dir):
work = run_dir / task["id"]
shutil.copytree(FIXTURES / task["fixture"], work)
for cmd in (["git", "init", "-q"],
["git", "add", "."],
["git", "-c", "user.email=e@local", "-c",
"user.name=eval", "commit", "-qm", "baseline"]):
subprocess.run(cmd, cwd=work, check=True, capture_output=True)
return work
def invoke(work, prompt_file):
cmd = os.environ["AGENT_CLI"].format(prompt=str(prompt_file))
t0 = time.monotonic()
p = subprocess.run(cmd, shell=True, cwd=work, capture_output=True,
text=True, timeout=int(os.environ.get("AGENT_TIMEOUT", "300")))
return {"elapsed_s": round(time.monotonic() - t0, 2),
"exit_code": p.returncode,
"stderr_tail": p.stderr[-1000:]}
def diff_stat(work):
r = subprocess.run(["git", "diff", "--numstat"], cwd=work,
capture_output=True, text=True, check=True)
files = added = deleted = 0
for line in r.stdout.splitlines():
parts = line.split("\t")
a, d = parts[0], parts[1]
files += 1
added += 0 if a == "-" else int(a)
deleted += 0 if d == "-" else int(d)
return {"files": files, "added": added, "deleted": deleted}
def run_assertions(work, assertions):
out = []
for a in assertions:
p = subprocess.run(a["cmd"], shell=True, cwd=work,
capture_output=True, text=True)
ok = p.returncode == 0 if a["type"] == "exit0" else a["expect"] in p.stdout
out.append({"name": a["name"], "pass": ok,
"detail": (p.stdout + p.stderr)[-300:]})
return out
def main():
run_id = time.strftime("%Y%m%d-%H%M%S")
run_dir = OUT / run_id
run_dir.mkdir(parents=True, exist_ok=True)
with (run_dir / "results.jsonl").open("w") as f:
for path in sorted(TASKS.glob("*.yaml")):
task = yaml.safe_load(path.read_text())
work = prepare(task, run_dir)
prompt = work / ".eval-prompt.md"
prompt.write_text(task["prompt"])
inv = invoke(work, prompt)
asserts = run_assertions(work, task["assertions"]) if inv["exit_code"] == 0 else []
rec = {
"run_id": run_id,
"task_id": task["id"],
"category": task["category"],
"fixture_sha": tree_sha(FIXTURES / task["fixture"]),
"agent_build": os.environ.get("AGENT_BUILD", "unset"),
"elapsed_s": inv["elapsed_s"],
"diff": diff_stat(work),
"assertions": asserts,
"pass_rate": sum(1 for a in asserts if a["pass"]) / max(len(asserts), 1),
}
f.write(json.dumps(rec, ensure_ascii=False) + "\n")
print(task["id"], rec["pass_rate"])
print("results:", run_dir / "results.jsonl")
if __name__ == "__main__":
main()跑法:
export AGENT_CLI='your-agent-cli --non-interactive --prompt-file {prompt}'
export AGENT_BUILD='记录模型版本与客户端版本,自行填写'
python3 eval.py有个顺序细节值得说明:git add . 在 baseline 提交之前执行,此时 .eval-prompt.md 还没写入,所以提示词文件是未跟踪文件,不会出现在 git diff --numstat 里,也就不会污染改动面积的统计。
git diff --name-only 和 YAML 里的 allow_paths 比对,越界直接标失败。这些字段都进 JSONL,之后用任何表格工具都能做透视,不需要专门搭平台。
第一周只做四到六个任务、把脚本跑通,目标是 fixture 可复现而不是分数好看;第二周补齐四类任务和越界检查,加入第三个工具做对比;第三周把任务集纳入版本管理,每次客户端或模型版本变更就跑一次回归,把 JSONL 存档。
到这里,"哪个工具更好"会变成一个可以指着某次 run_id 讨论的问题,而不是会议室里的感觉之争。评测结果请自行在自己的环境里产生,本文不提供也不暗示任何产品的分数。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。