eval-harness 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1046 -0
- package/README.zh-CN.md +597 -0
- package/bin/eval-harness.js +65 -0
- package/config/eval.example.json +70 -0
- package/docs/agent-runner-compatibility.md +47 -0
- package/docs/runners.md +82 -0
- package/package.json +43 -0
- package/prompts/judge.md +27 -0
- package/pyproject.toml +48 -0
- package/schemas/batch-comparison.schema.json +64 -0
- package/schemas/batch-summary.schema.json +85 -0
- package/schemas/evaluation.schema.json +81 -0
- package/schemas/experiment.schema.json +63 -0
- package/schemas/history-index.schema.json +37 -0
- package/schemas/history-record.schema.json +56 -0
- package/schemas/model-report.schema.json +38 -0
- package/schemas/next-action.schema.json +62 -0
- package/schemas/skill-improvement-input.schema.json +65 -0
- package/src/eval/__init__.py +3 -0
- package/src/eval/__main__.py +5 -0
- package/src/eval/cli.py +11150 -0
- package/src/eval/runners/__init__.py +0 -0
- package/src/eval/runners/base.py +197 -0
- package/src/eval/runners/claude.py +516 -0
- package/src/eval/runners/codex.py +412 -0
- package/src/eval/runners/mimocode.py +676 -0
- package/src/eval/runners/opencode.py +554 -0
- package/src/eval/schema.py +151 -0
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
# Eval Harness
|
|
2
|
+
|
|
3
|
+
[English documentation](README.md)
|
|
4
|
+
|
|
5
|
+
Release: `v0.2.18`
|
|
6
|
+
|
|
7
|
+
可配置的 runner `work/` 包评测框架。
|
|
8
|
+
|
|
9
|
+
Eval Harness 不绑定 OpenCode。它是面向 CodeX/Codex CLI、OpenCode、MiMo Code、Claude Code,以及后续更多 runner adapter 的评测平台。runner 抽象和扩展方式见 [Runner Adapters](docs/runners.md)。
|
|
10
|
+
|
|
11
|
+
默认配置是通用模板,不绑定具体赛题。你可以通过 `config/eval.local.json`、
|
|
12
|
+
Jenkins 参数,或者生成的 `eval.effective.json`,把它指向任意题目 Git 仓库
|
|
13
|
+
和任意 submitted work Git 仓库。
|
|
14
|
+
|
|
15
|
+
Jenkins 配置入口:
|
|
16
|
+
|
|
17
|
+
- [Jenkins 使用指南](docs/jenkins.md)
|
|
18
|
+
- [Jenkins 参数对比](docs/jenkins-parameter-comparison.md)
|
|
19
|
+
|
|
20
|
+
这个项目把一次性的 runner 运行,变成可复现、可比较、可持续优化的证据闭环。它不只看模型在控制台里“看起来聪明不聪明”,而是检查整个 Agent 系统是否能稳定完成以下工作:
|
|
21
|
+
|
|
22
|
+
- 理解动态题目目录和 `work/INSTRUCTION.md`
|
|
23
|
+
- 修改题目工程中的代码
|
|
24
|
+
- 输出 `work/result`、`work/reports`、`work/logs/trace` 等交付物
|
|
25
|
+
- 运行验证并记录证据
|
|
26
|
+
- 汇总评分、失败原因和下一轮 Skill/Agent/Instruction 优化建议
|
|
27
|
+
|
|
28
|
+
## 工作流
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
准备干净题目工程
|
|
32
|
+
-> 使用 submitted work/INSTRUCTION.md 启动选中的 runner
|
|
33
|
+
-> 收集 result、reports、trace、验证日志、代码 diff
|
|
34
|
+
-> 确定性评分
|
|
35
|
+
-> 给出 ACCEPT / INVESTIGATE / REJECT 决策
|
|
36
|
+
-> 生成 recommendations 和 next-action
|
|
37
|
+
-> CodeX/Codex 或人工据此优化一个小的 Skill/Agent/Instruction
|
|
38
|
+
-> 重新运行并比较
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
当前仓库提供确定性 runner/evaluator 和 Jenkins pipeline。它会给出下一轮优化输入,但不会自动修改 Skills。
|
|
42
|
+
|
|
43
|
+
## 你需要配置什么
|
|
44
|
+
|
|
45
|
+
框架把评测拆成三个独立输入,方便分享给其他团队或同学:
|
|
46
|
+
|
|
47
|
+
| 配置项 | Jenkins 参数 | JSON 配置字段 | 说明 |
|
|
48
|
+
| --- | --- | --- | --- |
|
|
49
|
+
| 题目仓库 | `TASK_REPO_*` | `source_repo` | 原始题目、工程或待修复项目。每个 worker 会复制为 `workspace/project`。 |
|
|
50
|
+
| work 仓库 | `WORK_REPO_*` | `submission_repo` | 参赛或实验用的 `work` 包,包含 `INSTRUCTION.md`、Skills、Agents、helper scripts。 |
|
|
51
|
+
| 执行矩阵 | `MODEL_MATRIX`、`CONCURRENCY`、`TIMEOUT_MINUTES` 等 | `execution` | 控制模型、并发、超时、provider 容错、验证、容器等。 |
|
|
52
|
+
|
|
53
|
+
每次 Jenkins 构建都会生成实际生效配置:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
<external-runs>/eval.effective.json
|
|
57
|
+
runs/eval.effective.json
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
这个文件记录本轮使用的 task/work 仓库 URL、SSH URL、ref、work 目录和 fallback URL,方便复现。
|
|
61
|
+
|
|
62
|
+
## 输入契约
|
|
63
|
+
|
|
64
|
+
为了换题后仍然可用,建议输入满足下面的契约:
|
|
65
|
+
|
|
66
|
+
| 输入 | 要求 | 说明 |
|
|
67
|
+
| --- | --- | --- |
|
|
68
|
+
| Task repository | 一个 Git 仓库,包含需要被 agent 修复的题目工程。 | Git 元数据会尽量保留,用于生成代码 diff;没有 `.git` 时会 fallback 到文件树比较。 |
|
|
69
|
+
| Work repository | 一个 Git 仓库,里面有提交的 work 包。 | `submission_repo.work_dir` 或 Jenkins `WORK_REPO_WORK_DIR` 必须指向包含 `INSTRUCTION.md` 的目录。 |
|
|
70
|
+
| Work assets | Skills、Agents、helper scripts、结果和日志约定。 | 默认支持 `work/work/skills/*` 和 `work/skills/*` 两种布局。 |
|
|
71
|
+
| Result artifacts | `work/result/output.md`、`work/reports/FINAL_RESULT.json`、`work/reports/FINAL_RESULT.md`、`work/reports/final-consistency-report.md`、`work/logs/trace/**`。 | 缺失会被当成证据缺口评分,而不是被控制台日志掩盖。 |
|
|
72
|
+
|
|
73
|
+
本地调试时也可以不用 Git 仓库,直接传:
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
PROJECT_SOURCE=<本地题目目录>
|
|
77
|
+
WORK_SOURCE=<本地 work 目录>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
共享 Jenkins job 时更推荐使用 Git URL 参数。
|
|
81
|
+
|
|
82
|
+
## 当前默认值
|
|
83
|
+
|
|
84
|
+
- 题目仓库:`config/eval.example.json` 中的占位 URL
|
|
85
|
+
- work 仓库:`config/eval.example.json` 中的占位 URL
|
|
86
|
+
- work 目录:`work`
|
|
87
|
+
- 默认模型:不写死;使用所选 runner 当前默认,或通过配置/Jenkins `MODEL_MATRIX` 指定
|
|
88
|
+
- 默认 reasoning variant:`high`
|
|
89
|
+
- 默认 worker 超时:`90` 分钟
|
|
90
|
+
- 默认执行契约:runner 会通过 prompt/env 告知 worker `AGENT_EVAL_TIMEOUT_MINUTES`、预留 `AGENT_EVAL_FINALIZE_BEFORE_MINUTES=5` 分钟收尾,并要求即使失败或部分完成也写出 `work/result/output.md`、`work/reports/FINAL_RESULT.json`、`work/reports/FINAL_RESULT.md`、`work/reports/final-consistency-report.md`
|
|
91
|
+
- 默认 idle timeout:`10` 分钟无 agent 输出或 artifact 活动
|
|
92
|
+
- 默认 fetch timeout:`15` 分钟
|
|
93
|
+
- Jenkins 并发:默认 `1`,可配置 `1~5`
|
|
94
|
+
- Jenkins runner matrix:逗号或换行分隔的 runner adapter,按 worker 顺序分配;留空则所有 worker 使用 config 中的 `agent.type`
|
|
95
|
+
- Jenkins 配置文件:`EVAL_CONFIG` 可留空;会优先用本地 `OEVAL_CONFIG`,再 fallback 到 `config/eval.example.json`
|
|
96
|
+
- Jenkins 定时运行:仓库默认不注册 cron;需要时在 Jenkins 环境中自行配置 timer
|
|
97
|
+
- work 版本检查:默认 `REQUIRE_WORK_VERSION=true`,简单接入时可设为 `false`
|
|
98
|
+
|
|
99
|
+
这些只是默认值,不是框架限制。
|
|
100
|
+
|
|
101
|
+
## 快速开始
|
|
102
|
+
|
|
103
|
+
平台无关。推荐 Python 3.10+,真实评分环境已经按 Python 3.12 兼容。外部工具需要在 `PATH` 或 Jenkins `EXTRA_PATHS` / `OEVAL_EXTRA_PATHS` 中可用:
|
|
104
|
+
|
|
105
|
+
- `git`
|
|
106
|
+
- `node`
|
|
107
|
+
- 选中的 agent CLI:`opencode`、`mimo`、`codex` 或 `claude`
|
|
108
|
+
- `agent-inspector`
|
|
109
|
+
- `mvn` 和 Java,启用验证时需要
|
|
110
|
+
|
|
111
|
+
Ubuntu 评测基线示例:
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
Ubuntu 24.04.4
|
|
115
|
+
Python 3.12
|
|
116
|
+
Node v24.13.0
|
|
117
|
+
OpenJDK 21
|
|
118
|
+
Maven 3.9.11
|
|
119
|
+
Maven home: /usr/local/maven3
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Linux/macOS:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
python3 -m venv .venv
|
|
126
|
+
. .venv/bin/activate
|
|
127
|
+
python -m pip install -e .
|
|
128
|
+
eval-harness --help
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Windows PowerShell:
|
|
132
|
+
|
|
133
|
+
```powershell
|
|
134
|
+
python -m venv .venv
|
|
135
|
+
.\.venv\Scripts\python -m pip install -e .
|
|
136
|
+
.\.venv\Scripts\eval-harness --help
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
NPM 包装入口:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npx eval-harness --help
|
|
143
|
+
npx eval-harness runner-presets
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
npm 包是一个很薄的 Node.js 启动器,会调用包内自带的 Python 源码。
|
|
147
|
+
运行机器仍然需要 `PATH` 中有 Python 3.10+,也可以通过
|
|
148
|
+
`EVAL_HARNESS_PYTHON` 指定 Python 可执行文件。从仓库外运行时,它会
|
|
149
|
+
fallback 到 npm 包内置的 `config/eval.example.json` 和 `schemas/`。
|
|
150
|
+
|
|
151
|
+
不安装包也可以运行:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
PYTHONPATH=src python -m eval --help
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```powershell
|
|
158
|
+
$env:PYTHONPATH = "src"
|
|
159
|
+
python -m eval --help
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## 配置自己的题目仓库和 work 仓库
|
|
163
|
+
|
|
164
|
+
复制配置文件:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
cp config/eval.example.json config/eval.local.json
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```powershell
|
|
171
|
+
Copy-Item config/eval.example.json config/eval.local.json
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
修改 `config/eval.local.json`:
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"source_repo": {
|
|
179
|
+
"url": "https://example.com/course/task.git",
|
|
180
|
+
"ssh_url": "git@example.com:course/task.git",
|
|
181
|
+
"ref": "main",
|
|
182
|
+
"fallback_urls": []
|
|
183
|
+
},
|
|
184
|
+
"submission_repo": {
|
|
185
|
+
"url": "https://example.com/team/work.git",
|
|
186
|
+
"ssh_url": "git@example.com:team/work.git",
|
|
187
|
+
"ref": "main",
|
|
188
|
+
"work_dir": "work",
|
|
189
|
+
"fallback_urls": []
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
也可以不修改文件,临时生成 effective config:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
PYTHONPATH=src python -m eval write-effective-config \
|
|
198
|
+
--config config/eval.example.json \
|
|
199
|
+
--output /tmp/oeval/eval.effective.json \
|
|
200
|
+
--task-repo-url https://example.com/course/task.git \
|
|
201
|
+
--task-repo-ref main \
|
|
202
|
+
--work-repo-url https://example.com/team/work.git \
|
|
203
|
+
--work-repo-ref main \
|
|
204
|
+
--work-repo-work-dir work
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
生成后按普通配置使用:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
PYTHONPATH=src python -m eval fetch-task --config /tmp/oeval/eval.effective.json --output /tmp/oeval/inputs/task --replace-existing
|
|
211
|
+
PYTHONPATH=src python -m eval fetch-work --config /tmp/oeval/eval.effective.json --output /tmp/oeval/inputs/work --replace-existing
|
|
212
|
+
PYTHONPATH=src python -m eval prepare --config /tmp/oeval/eval.effective.json --run-id demo-01 --project-source /tmp/oeval/inputs/task --work-source /tmp/oeval/inputs/work --replace-existing
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## 本地运行
|
|
216
|
+
|
|
217
|
+
准备:
|
|
218
|
+
|
|
219
|
+
```powershell
|
|
220
|
+
python -m eval prepare --config config/eval.local.json
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
执行配置的 agent runner:
|
|
224
|
+
|
|
225
|
+
```powershell
|
|
226
|
+
python -m eval execute --run runs/<run-id>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
评分:
|
|
230
|
+
|
|
231
|
+
```powershell
|
|
232
|
+
python -m eval score --run runs/<run-id>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
一条命令跑完整生命周期:
|
|
236
|
+
|
|
237
|
+
```powershell
|
|
238
|
+
python -m eval run --config config/eval.local.json
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
并发运行:
|
|
242
|
+
|
|
243
|
+
```powershell
|
|
244
|
+
python -m eval batch --config config/eval.local.json --count 5 --parallel 5
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
本地一次性运行 Jenkins 类似流程。该入口会生成有效配置、拉取 task/work 输入、并行执行 worker、汇总批次、导出产物,并可选校验导出的 JSON:
|
|
248
|
+
|
|
249
|
+
```powershell
|
|
250
|
+
python -m eval run-once `
|
|
251
|
+
--config config/eval.local.json `
|
|
252
|
+
--batch-id local-three-model `
|
|
253
|
+
--count 3 `
|
|
254
|
+
--parallel 3 `
|
|
255
|
+
--prefer-ssh `
|
|
256
|
+
--model agent-inspector/MiniMax-M3 `
|
|
257
|
+
--model agent-inspector/deepseek-v4-pro `
|
|
258
|
+
--model agent-inspector/glm-5.2 `
|
|
259
|
+
--timeout-minutes 90 `
|
|
260
|
+
--idle-timeout-minutes deepseek-v4-pro=30,default=10 `
|
|
261
|
+
--variant high `
|
|
262
|
+
--replace-existing `
|
|
263
|
+
--validate-artifacts
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
可以用 `--task-repo-url`、`--task-repo-ref`、`--work-repo-url`、`--work-repo-ref`、`--work-repo-work-dir` 覆盖本轮 task/work 仓库参数,私有配置不用提交到仓库。首次验证流程时可加 `--dry-run`。
|
|
267
|
+
|
|
268
|
+
短链路真实调度可以使用 `--smoke`。它会强制 `DRY_RUN=false`、`timeout=5`、`idle_timeout=2`,并关闭 validation、work version 和发布门禁,适合先确认 runner、模型路由和 Agent Inspector 链路:
|
|
269
|
+
|
|
270
|
+
```powershell
|
|
271
|
+
python -m eval run-once `
|
|
272
|
+
--config config/eval.local.json `
|
|
273
|
+
--smoke `
|
|
274
|
+
--count 3 `
|
|
275
|
+
--parallel 3 `
|
|
276
|
+
--model agent-inspector/MiniMax-M3 `
|
|
277
|
+
--model agent-inspector/deepseek-v4-pro `
|
|
278
|
+
--model agent-inspector/glm-5.2
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Agent Runner
|
|
282
|
+
|
|
283
|
+
当前支持的 `agent.type`:
|
|
284
|
+
|
|
285
|
+
- `codex` 或 `codex-cli`:CodeX/Codex CLI runner
|
|
286
|
+
- `opencode`:OpenCode CLI runner
|
|
287
|
+
- `mimocode` 或 `mimo`:MiMo Code runner
|
|
288
|
+
- `claude` 或 `claude-code`:Claude Code runner
|
|
289
|
+
|
|
290
|
+
每个 runner 都有自己的默认命令、日志目录、隔离运行时 profile 和 work asset 安装目录。需要覆盖命令时,可使用 `execution.agent.command`、`execution.<runner>.command` 或 `execution.<runner>_command`。
|
|
291
|
+
|
|
292
|
+
查看 runner 默认参数:
|
|
293
|
+
|
|
294
|
+
```powershell
|
|
295
|
+
python -m eval runner-presets
|
|
296
|
+
python -m eval runner-presets --format json
|
|
297
|
+
python -m eval runner-version-check --config config/eval.local.json --markdown-output runs/preflight/runner-version-check.md
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
CodeX/Codex CLI 示例:
|
|
301
|
+
|
|
302
|
+
```json
|
|
303
|
+
{
|
|
304
|
+
"agent": {
|
|
305
|
+
"type": "codex"
|
|
306
|
+
},
|
|
307
|
+
"execution": {
|
|
308
|
+
"codex_command": [
|
|
309
|
+
"{agent_executable}",
|
|
310
|
+
"exec",
|
|
311
|
+
"--json",
|
|
312
|
+
"--skip-git-repo-check",
|
|
313
|
+
"--dangerously-bypass-approvals-and-sandbox",
|
|
314
|
+
"--cd",
|
|
315
|
+
"{workspace}",
|
|
316
|
+
"Read and execute work/INSTRUCTION.md. Work autonomously, do not ask for human input, and write all required result artifacts."
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Windows Jenkins 服务账号下,Codex CLI 会先使用 `agent.executable`、`codex.executable`、`execution.codex_executable`、`execution.codex.executable` 或 `OEVAL_CODEX_EXECUTABLE`,然后自动发现当前用户的 `%LOCALAPPDATA%\OpenAI\Codex\bin\*\codex.exe`,最后才回退到 `PATH` 中的 `codex`。本机路径建议放在未提交的 local config 或 Jenkins 全局环境变量中,不要写入共享配置。
|
|
323
|
+
|
|
324
|
+
CodeX/Codex CLI 同样支持通用 `--model` 覆盖:
|
|
325
|
+
|
|
326
|
+
```powershell
|
|
327
|
+
python -m eval execute --run runs/<run-id> --model gpt-5
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Claude Code 示例:
|
|
331
|
+
|
|
332
|
+
```json
|
|
333
|
+
{
|
|
334
|
+
"agent": {
|
|
335
|
+
"type": "claude-code",
|
|
336
|
+
"executable": "claude"
|
|
337
|
+
},
|
|
338
|
+
"execution": {
|
|
339
|
+
"claude_command": [
|
|
340
|
+
"{agent_executable}",
|
|
341
|
+
"-p",
|
|
342
|
+
"--output-format",
|
|
343
|
+
"stream-json",
|
|
344
|
+
"--verbose",
|
|
345
|
+
"--dangerously-skip-permissions",
|
|
346
|
+
"Read and execute work/INSTRUCTION.md. Work autonomously, do not ask for human input, and write all required result artifacts."
|
|
347
|
+
]
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
使用 Agent Inspector 路由时,`agent-inspector/MiniMax-M3` 会传给 Claude Code
|
|
353
|
+
为 `--model MiniMax-M3`;runner 会在隔离 worker 环境中注入
|
|
354
|
+
`ANTHROPIC_BASE_URL` 和 `ANTHROPIC_API_KEY`。
|
|
355
|
+
|
|
356
|
+
Codex CLI 也会去掉 `agent-inspector/` 前缀,并会把 `agent-inspector/MiniMax M3` 这类显示名规范化成 `--model MiniMax-M3`。
|
|
357
|
+
|
|
358
|
+
使用 `agent-inspector/*` 模型路由时,先确认 Agent Inspector 已经启动:
|
|
359
|
+
|
|
360
|
+
```powershell
|
|
361
|
+
agent-inspector
|
|
362
|
+
Invoke-RestMethod http://127.0.0.1:25947/api/health
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
健康检查期望返回 `{"status":"ok"}`。`http://127.0.0.1:25947/proxy` 是注入给 OpenCode/MiMo 类 runner 的 `LLM_BASE_URL`,也是注入给 Claude Code 的 `ANTHROPIC_BASE_URL`,不是浏览器页面;浏览器 UI 使用 `http://127.0.0.1:25947/`。真实调度成功后,可以在选中 runner 的日志目录里查看,例如 `runs/<run-id>/<agent-log-dir>/opencode.log`。
|
|
366
|
+
|
|
367
|
+
选中的 agent CLI 不可用时,可以先 dry-run 验证调度:
|
|
368
|
+
|
|
369
|
+
```powershell
|
|
370
|
+
python -m eval batch --config config/eval.local.json --count 5 --parallel 5 --dry-run
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Jenkins 使用
|
|
374
|
+
|
|
375
|
+
配置参考:
|
|
376
|
+
|
|
377
|
+
- [Jenkins 使用指南](docs/jenkins.md)
|
|
378
|
+
- [Jenkins 参数对比](docs/jenkins-parameter-comparison.md)
|
|
379
|
+
|
|
380
|
+
Jenkinsfile 暴露了可配置参数。核心参数:
|
|
381
|
+
|
|
382
|
+
```text
|
|
383
|
+
EVAL_CONFIG=
|
|
384
|
+
CONCURRENCY=1
|
|
385
|
+
TIMEOUT_MINUTES=90
|
|
386
|
+
IDLE_TIMEOUT_MINUTES=10
|
|
387
|
+
DRY_RUN=false
|
|
388
|
+
SMOKE_MODE=false
|
|
389
|
+
ISOLATED_ENV=true
|
|
390
|
+
STREAM_OUTPUT=true
|
|
391
|
+
CONSOLE_LOG_MODE=important
|
|
392
|
+
RUNNER_MATRIX=
|
|
393
|
+
MODEL_MATRIX=
|
|
394
|
+
REASONING_VARIANT=high
|
|
395
|
+
PROVIDER_API_FAILURE_GRACE=5
|
|
396
|
+
REQUIRE_WORK_VERSION=true
|
|
397
|
+
TASK_REPO_URL=<题目仓库 HTTPS URL>
|
|
398
|
+
TASK_REPO_SSH_URL=<题目仓库 SSH URL,可选>
|
|
399
|
+
TASK_REPO_REF=main
|
|
400
|
+
WORK_REPO_URL=<work 仓库 HTTPS URL>
|
|
401
|
+
WORK_REPO_SSH_URL=<work 仓库 SSH URL,可选>
|
|
402
|
+
WORK_REPO_REF=main
|
|
403
|
+
WORK_REPO_WORK_DIR=work
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
DeepSeek 这类模型在子代理或子进程阶段可能长时间没有输出,建议只给 DeepSeek worker 放宽 idle timeout,其他模型仍保持默认 10 分钟:
|
|
407
|
+
|
|
408
|
+
```text
|
|
409
|
+
IDLE_TIMEOUT_MINUTES=deepseek-v4-pro=30,default=10
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
一个 Jenkins 任务也可以同时跑多个 runner adapter。`RUNNER_MATRIX` 控制每个 worker 使用哪个 runner,`MODEL_MATRIX` 控制每个 worker 使用哪个模型,二者都按 worker 顺序对齐,数量不足时复用最后一个值:
|
|
413
|
+
|
|
414
|
+
```text
|
|
415
|
+
CONCURRENCY=4
|
|
416
|
+
RUNNER_MATRIX=codex,opencode,mimocode,claude
|
|
417
|
+
MODEL_MATRIX=agent-inspector/MiniMax-M3,agent-inspector/deepseek-v4-pro,agent-inspector/glm-5.2,agent-inspector/MiniMax-M3
|
|
418
|
+
DRY_RUN=false
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
混合 runner 模式下,task/work 输入仍然只拉取一次;每个 worker 会在 `<external-runs>/configs/` 下生成自己的 runner-specific effective config,再进入 prepare/execute/score。
|
|
422
|
+
|
|
423
|
+
Jenkins 执行时会先生成本轮有效配置:
|
|
424
|
+
|
|
425
|
+
```powershell
|
|
426
|
+
python -m eval write-effective-config --config <EVAL_CONFIG> --output <external-runs>/eval.effective.json [repo overrides]
|
|
427
|
+
python -m eval fetch-task --config <external-runs>/eval.effective.json --output <external-runs>/inputs/task --replace-existing
|
|
428
|
+
python -m eval fetch-work --config <external-runs>/eval.effective.json --output <external-runs>/inputs/work --replace-existing
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
然后每个 worker 进入独立空间:
|
|
432
|
+
|
|
433
|
+
```text
|
|
434
|
+
<external-runs>/<run-id>/
|
|
435
|
+
inputs/task/
|
|
436
|
+
inputs/work/
|
|
437
|
+
workspace/project/
|
|
438
|
+
workspace/work/
|
|
439
|
+
runtime/home/
|
|
440
|
+
runtime/tmp/
|
|
441
|
+
runtime/cache/
|
|
442
|
+
runtime/config/
|
|
443
|
+
runtime/m2-repository/
|
|
444
|
+
control/timeout.json
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
每个 worker 仍然作为 Jenkins `parallel` 分支运行,分支名称包含 worker 序号、runner 和模型短名,例如 `01 codex MiniMax-M3`。分支内部的 config、准备、Inspector create/attach/complete、资产安装、执行和打分会作为日志段打印,不再拆成一堆 Jenkins 子节点:
|
|
448
|
+
|
|
449
|
+
```text
|
|
450
|
+
worker 01 codex MiniMax-M3: effective config
|
|
451
|
+
worker 01 codex MiniMax-M3: prepare
|
|
452
|
+
worker 01 codex MiniMax-M3: inspector create
|
|
453
|
+
worker 01 codex MiniMax-M3: install agent assets
|
|
454
|
+
worker 01 codex MiniMax-M3: execute
|
|
455
|
+
worker 01 codex MiniMax-M3: inspector attach
|
|
456
|
+
worker 01 codex MiniMax-M3: score
|
|
457
|
+
worker 01 codex MiniMax-M3: inspector complete
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
## Agent Asset 安装
|
|
461
|
+
|
|
462
|
+
默认安装规则由 runner adapter 决定。OpenCode 的默认安装规则示例:
|
|
463
|
+
|
|
464
|
+
```text
|
|
465
|
+
work/work/skills/* -> .opencode/skills
|
|
466
|
+
work/work/skills/*.md -> .opencode/agents
|
|
467
|
+
work/work/skills/versions.json -> .opencode/skills
|
|
468
|
+
work/skills/* -> .opencode/skills
|
|
469
|
+
work/skills/*.md -> .opencode/agents
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
这样可以兼容嵌套布局和扁平布局,且不依赖用户机器上的全局 runner 配置目录。其他 runner 会使用各自的默认目录,例如 MiMo Code 使用 `.mimocode/*`,Codex CLI 使用 `.codex/*`,Claude Code 使用 `.claude/*`。
|
|
473
|
+
|
|
474
|
+
如果需要额外安装目录,可以使用 Jenkins `AGENT_ASSET_INSTALLS`,目标目录需要匹配当前 runner:
|
|
475
|
+
|
|
476
|
+
```text
|
|
477
|
+
work/commands=>.opencode/command
|
|
478
|
+
work/references=>.opencode/references
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## 评分与产物
|
|
482
|
+
|
|
483
|
+
确定性评分总分 100:
|
|
484
|
+
|
|
485
|
+
- `artifacts` `/30`:必需结果文件、trace、final marker、报告产物
|
|
486
|
+
- `boundary` `/20`:保护路径没有被修改
|
|
487
|
+
- `validation` `/30`:验证摘要和 pass 证据
|
|
488
|
+
- `final_status` `/20`:最终状态和 finality marker
|
|
489
|
+
|
|
490
|
+
验证摘要支持两种 helper schema:命令维度的 `results[]`,以及阶段维度的 `phases[]` 加顶层 `status`。如果 `phases[]` 中所有阶段都是 `PASS`,harness 会把它当作正式的机器可读验证证据;只写在 `work/result/output.md` 里的成功描述仍然只是弱证据,不能替代 `work/logs/trace/validation/**/summary.json`。
|
|
491
|
+
|
|
492
|
+
最终报告也会被检查。`work/reports/final-consistency-report.md` 如果仍然是 helper 自动创建的 placeholder,score 会记录 final-artifact finding,并在 `evaluation.json` / `judge-input.md` 中输出 `final_report_evidence`,不会因为一个 `PASS` marker 就把它当成完整交付。
|
|
493
|
+
|
|
494
|
+
每个 run 会输出:
|
|
495
|
+
|
|
496
|
+
```text
|
|
497
|
+
runs/<run-id>/evaluation/evaluation.json
|
|
498
|
+
runs/<run-id>/evaluation/changed-files.md
|
|
499
|
+
runs/<run-id>/evaluation/judge-input.md
|
|
500
|
+
runs/<run-id>/evaluation/recommendations.md
|
|
501
|
+
runs/<run-id>/evaluation/next-action.json
|
|
502
|
+
runs/<run-id>/evaluation/skill-improvement-input.json
|
|
503
|
+
runs/<run-id>/<agent-log-dir>/opencode.log
|
|
504
|
+
runs/<run-id>/workspace/work/result/output.md
|
|
505
|
+
runs/<run-id>/workspace/work/reports/**
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
`skill-improvement-input.json` is the machine-readable input for the next Skill/Agent/helper/Instruction optimization. It includes the primary failure category, failure taxonomy, target candidates, evidence paths, final report evidence, and recommended change scope.
|
|
509
|
+
|
|
510
|
+
`<agent-log-dir>` 由当前 runner 决定,例如 `opencode`、`mimocode`、`codex` 或 `claude`。每个 run 会写入 `<agent-log-dir>/sessions.json` 和 `.md`;启用 Agent Inspector 时,如果 agent 日志没有解析出足够 session 信息,平台会尝试从 Inspector `/api/logs` 回填 evidence 链接。批次 `summary.md` 和 HTML 报告会包含 `Root Cause Digest`,集中展示失败原因、诊断文件、Inspector 证据和下一步建议。
|
|
511
|
+
|
|
512
|
+
批次汇总会输出:
|
|
513
|
+
|
|
514
|
+
```text
|
|
515
|
+
runs/batches/<batch-id>/summary.json
|
|
516
|
+
runs/batches/<batch-id>/summary.md
|
|
517
|
+
runs/batches/<batch-id>/model-report.json
|
|
518
|
+
runs/batches/<batch-id>/model-report.md
|
|
519
|
+
runs/batches/<batch-id>/experiment.json
|
|
520
|
+
runs/batches/<batch-id>/comparison.json
|
|
521
|
+
runs/batches/<batch-id>/comparison.md
|
|
522
|
+
runs/history/**
|
|
523
|
+
runs/eval.effective.json
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
校验归档 JSON:
|
|
527
|
+
|
|
528
|
+
```powershell
|
|
529
|
+
python -m eval validate-artifacts --root runs --batch-id <batch-id> --count <count>
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
常用运维命令:
|
|
533
|
+
|
|
534
|
+
```powershell
|
|
535
|
+
python -m eval runner-presets
|
|
536
|
+
python -m eval runner-version-check --config config/eval.local.json --markdown-output runs/preflight/runner-version-check.md
|
|
537
|
+
python -m eval clean-runs --config config/eval.local.json --keep 10 --dry-run
|
|
538
|
+
python -m eval release-notes --from-ref <previous-tag> --to-ref HEAD --output docs/release-notes-draft.md
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
## Provider 与超时容错
|
|
542
|
+
|
|
543
|
+
框架会尽早识别并分类以下问题:
|
|
544
|
+
|
|
545
|
+
- provider/model 配置错误
|
|
546
|
+
- provider 不可达
|
|
547
|
+
- invalid credentials
|
|
548
|
+
- rate limit
|
|
549
|
+
- provider API stream error
|
|
550
|
+
- agent idle timeout
|
|
551
|
+
- worker 总超时
|
|
552
|
+
|
|
553
|
+
`PROVIDER_API_FAILURE_GRACE` 默认是 `5`,表示普通 provider API stream error 可以容忍 5 次再终止。rate limit、鉴权错误、未知模型等 fatal 问题会提前终止,并保留部分产物供分析。
|
|
554
|
+
|
|
555
|
+
Provider failure 检测现在按 runner 做结构化识别。对于 JSON/JSONL 日志,harness 只扫描明确的错误载体,例如顶层 `error`、`request/response/session/turn failed`、Claude `result.is_error=true` 或 error subtype、`system` API error、`level=ERROR/FATAL/CRITICAL`、以及明确的 runtime error payload。assistant/model 分析、user tool result、tool-use 事件、Codex `command_execution` 输出和 agent message 会被忽略,所以业务域里的 `401`、`403`、`FORBIDDEN`、`UNAUTHORIZED`、`/api/v1`、`errorCode` 等内容会被当作任务证据,而不会误判为 provider outage。普通 stderr/stdout 仍会扫描真实 provider token,例如连接失败、无效凭据、rate limit、未知模型和 retry exhaustion。
|
|
556
|
+
|
|
557
|
+
当 worker 因 idle timeout、总 timeout、provider fail-fast 或手动中断被清理时,runner 会先写入 `<agent-log-dir>/process-diagnostics-*.json` 和 `.md`。诊断文件包含进程树、命令行、elapsed/idle 时间、监听的 artifact 路径、最近日志尾部等信息;Jenkins 归档 `runs/**` 后可以直接查看,用来定位 DeepSeek 这类子代理/子进程静默时到底卡在哪个进程或阶段。
|
|
558
|
+
|
|
559
|
+
如果 worker 在写完验证日志或修改代码后超时,但没有写完最终产物,score 阶段会先尝试 `execution.final_result_recovery_helpers` 中的 work helper,并兼容旧版 helper 参数;如果 helper 不存在或不兼容,harness 会内置生成 `PARTIAL` 的 `output.md`、`FINAL_RESULT.json`、`FINAL_RESULT.md`,同时保留失败原因和原始输出备份。这样 Jenkins 仍能 aggregate/export Evidence,但不会把失败验证掩盖成成功。
|
|
560
|
+
|
|
561
|
+
## 测试
|
|
562
|
+
|
|
563
|
+
```powershell
|
|
564
|
+
python -m unittest
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
可选开发工具:
|
|
568
|
+
|
|
569
|
+
```powershell
|
|
570
|
+
python -m pip install -e ".[dev]"
|
|
571
|
+
python -m pytest
|
|
572
|
+
python -m coverage run -m unittest
|
|
573
|
+
python -m ruff check .
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
测试覆盖:
|
|
577
|
+
|
|
578
|
+
- clone URL 顺序和 fallback
|
|
579
|
+
- task/work 输入物化
|
|
580
|
+
- agent worker 执行包装
|
|
581
|
+
- provider failure 分类
|
|
582
|
+
- timeout control
|
|
583
|
+
- artifact export
|
|
584
|
+
- schema validation
|
|
585
|
+
- model report 和 batch summary
|
|
586
|
+
|
|
587
|
+
## 演进策略
|
|
588
|
+
|
|
589
|
+
推荐的优化粒度:
|
|
590
|
+
|
|
591
|
+
1. 看 `runs/batches/<batch-id>/summary.md` 和 `model-report.md`
|
|
592
|
+
2. 找到最小的 Skill、Agent、知识包、helper 或 Instruction 问题
|
|
593
|
+
3. 修改并做版本控制
|
|
594
|
+
4. 重新运行同一 task/work 输入
|
|
595
|
+
5. 通过 `comparison.md` 和 history 判断是否接受
|
|
596
|
+
|
|
597
|
+
这个仓库的目标不是让一次模型运行偶然成功,而是让 Agent 系统可以持续进化、可审计、可复盘。
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require("child_process");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
|
|
8
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
9
|
+
const srcDir = path.join(packageRoot, "src");
|
|
10
|
+
const defaultConfig = path.join(packageRoot, "config", "eval.example.json");
|
|
11
|
+
const schemaDir = path.join(packageRoot, "schemas");
|
|
12
|
+
|
|
13
|
+
const env = { ...process.env };
|
|
14
|
+
env.PYTHONPATH = [srcDir, env.PYTHONPATH].filter(Boolean).join(path.delimiter);
|
|
15
|
+
if (!env.EVAL_HARNESS_DEFAULT_CONFIG && fs.existsSync(defaultConfig)) {
|
|
16
|
+
env.EVAL_HARNESS_DEFAULT_CONFIG = defaultConfig;
|
|
17
|
+
}
|
|
18
|
+
if (!env.EVAL_HARNESS_SCHEMA_DIR && fs.existsSync(schemaDir)) {
|
|
19
|
+
env.EVAL_HARNESS_SCHEMA_DIR = schemaDir;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function pythonCandidates() {
|
|
23
|
+
const candidates = [];
|
|
24
|
+
if (env.EVAL_HARNESS_PYTHON) {
|
|
25
|
+
candidates.push({ command: env.EVAL_HARNESS_PYTHON, args: [] });
|
|
26
|
+
}
|
|
27
|
+
if (env.PYTHON) {
|
|
28
|
+
candidates.push({ command: env.PYTHON, args: [] });
|
|
29
|
+
}
|
|
30
|
+
if (process.platform === "win32") {
|
|
31
|
+
candidates.push({ command: "py", args: ["-3"] });
|
|
32
|
+
}
|
|
33
|
+
candidates.push({ command: "python3", args: [] });
|
|
34
|
+
candidates.push({ command: "python", args: [] });
|
|
35
|
+
return candidates;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const cliArgs = process.argv.slice(2);
|
|
39
|
+
let lastError = null;
|
|
40
|
+
for (const candidate of pythonCandidates()) {
|
|
41
|
+
const result = spawnSync(
|
|
42
|
+
candidate.command,
|
|
43
|
+
[...candidate.args, "-m", "eval", ...cliArgs],
|
|
44
|
+
{
|
|
45
|
+
cwd: process.cwd(),
|
|
46
|
+
env,
|
|
47
|
+
stdio: "inherit"
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
if (result.error && result.error.code === "ENOENT") {
|
|
51
|
+
lastError = result.error;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (result.error) {
|
|
55
|
+
console.error(`eval-harness failed to start Python: ${result.error.message}`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.error("eval-harness requires Python 3.10+ on PATH.");
|
|
62
|
+
if (lastError) {
|
|
63
|
+
console.error(lastError.message);
|
|
64
|
+
}
|
|
65
|
+
process.exit(1);
|