CLI flag symmetry: --judge-api-url split; mock-boxed hyphen names

- --judge now accepts a bare model name paired with --judge-api-url,
  mirroring --model/--api-url; full legacy specs keep working
  (_compose_judge_spec, verified: name+url -> spec, spec passthrough,
  empty -> None; end-to-end on simple_qa with a live judge endpoint)
- mock adapter spellings: mock-boxed / mock-oracle / mock-fc preferred,
  colon forms still accepted; bare 'mock' stays echo
- fix mock adapter singleton mode pollution: resolve_adapter memoizes
  one instance, so mock-boxed then mock in one process leaked the
  boxed mode into the echo run -- each mock spec now builds a fresh
  instance
- README: mock-boxed in examples, judge flags row updated

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sora 2026-09-10 06:43:27 +00:00
parent ea93602dfa
commit 08605d9ab2
3 changed files with 39 additions and 9 deletions

View File

@ -42,7 +42,7 @@ pip install .
离线验证安装(不需要模型、不联网):
```bash
evalharness eval run gsm8k --model mock:boxed --limit 8
evalharness eval run gsm8k --model mock-boxed --limit 8
# acc 100% —— mock 适配器直接输出金标答案,证明
# 数据 → prompt → 生成 → 判分 → 报告 全链路可用
```
@ -68,7 +68,7 @@ evalharness eval run gsm8k \
| `--api-url URL` | OpenAI 兼容端点;与 `--model` 搭配使用(不用手拼 spec 字符串) |
| `--model NAME` | 服务端模型名(配合 `--api-url`);或直接给完整 spec`openai/http://h:8000/v1?qwen3-8b` |
| `--provider {openai-chat,openai-pool}` | 协议/提供方,默认 `openai-chat`;端点池用 `openai-pool` |
| `--judge SPEC` | LLM-judge 模型 spechle / simple_qa / imo 等 judge 类 recipe 需要) |
| `--judge NAME` + `--judge-api-url URL` | LLM-judge 模型hle / simple_qa / imo 需要);也可单给完整 spec `--judge openai/http://...?m` |
| `--profile NAME` | 命名生成参数集(内置 `dp4-nothink``qwen3-es-parity``t1-short`,或任意 `@register_gen_profile` 名);优先级:插件默认 < profile 默认 < profile bench 覆盖 < 显式参数 |
| `--disable-thinking` | 发送 `enable_thinking=false`Qwen3 类思考模型推荐;带 tools 的请求自动退回模板安全的软开关) |
| `--textools` | 工具以文本形式随 prompt 下发,而非原生 tool_calls |
@ -157,7 +157,7 @@ evalharness viz show report.json --style errors # 失败样本下钻
| `openai/<base_url>?<model_id>` | 单端点vLLM、SGLang、lmdeploy、ollama、云 API |
| `openai-pool/<base{8000..8007}/v1,...>?<model_id>` | 端点池:轮询 + 自适应并发 + failover |
| `deploy:<engine>/<model>` | 经 Deployer 解析(钉版本的推理环境) |
| `mock` / `mock:boxed` / `mock:fc` | 离线适配器(管线自检 |
| `mock` / `mock-boxed` / `mock-fc` | 离线适配器(管线自检:回声 / 回放金标 / 回放工具调用 |
| `!nothink` `!perf` `!textools` 后缀 | spec 内联开关(与 CLI 参数等价) |
API key 按端点从环境变量读取(`OPENAI_API_KEY``ANTHROPIC_API_KEY` …)。
@ -180,7 +180,7 @@ evalharness eval list # 全部判分 recipe
各族注意事项:
- **LLM-judge 类**`hle``simple_qa``imo_answerbench`):传 `--judge <spec>`judge 走官方协议
- **LLM-judge 类**`hle``simple_qa``imo_answerbench`):传 `--judge <模型名> --judge-api-url <端点>`(与主模型同款分离参数风格)judge 走官方协议
(如 SimpleQA 的分级正确性 + NOT_ATTEMPTED 兜底)。
- **代码执行类**:模型生成的代码在硬隔离 Docker 中运行(`--network none`、cgroup 上限、只读 rootfs
swe 的逐实例 `sweb.eval.*` 镜像用 `evalharness sandbox prefetch swe_bench_verified` 预取。

View File

@ -228,6 +228,26 @@ def _compose_model_spec(args):
return _model_with_flags(model, args)
def _compose_judge_spec(args):
"""--judge accepts a bare model name (with --judge-api-url) or a full
legacy spec; keep both working like the main model flags."""
judge = getattr(args, 'judge', '') or ''
url = getattr(args, 'judge_api_url', '') or ''
if url and judge and '/' not in judge:
judge = f'openai/{url.rstrip("/")}?{judge}'
return judge or None
def _compose_judge_spec(args):
"""--judge accepts a bare model name (with --judge-api-url) or a full
legacy spec; both keep working, mirroring the main model flags."""
judge = getattr(args, 'judge', '') or ''
url = getattr(args, 'judge_api_url', '') or ''
if url and judge and '/' not in judge:
judge = f'openai/{url.rstrip("/")}?{judge}'
return judge or None
def _cmd_eval_run(args) -> int:
import asyncio
import time as _time
@ -283,7 +303,7 @@ def _cmd_eval_run(args) -> int:
ds, model_spec, concurrency=args.concurrency, limit=args.limit,
limit_per_task=args.limit_per_task,
checkpoint=args.resume,
judge_spec=args.judge, env=args.env,
judge_spec=_compose_judge_spec(args), env=args.env,
gen_profile=getattr(args, 'profile', ''),
progress_reporter=progress_reporter,
status_callback=status_callback))
@ -496,7 +516,10 @@ def build_parser() -> argparse.ArgumentParser:
p.add_argument('--provider', default='openai-chat',
choices=('openai-chat', 'openai-pool'),
help='API protocol/provider (default: openai-chat)')
p.add_argument('--judge', default='', help='judge model spec for llm_judge recipes')
p.add_argument('--judge', default='',
help='judge model name with --judge-api-url, or full spec')
p.add_argument('--judge-api-url', default='',
help='judge API base URL when --judge is only the model name')
p.add_argument('--profile', default='',
help='named gen-params profile (dp4-nothink | qwen3-es-parity | t1-short '
'or any @register_gen_profile name); layers: plugin default < '

View File

@ -12,6 +12,7 @@ raw strings to the sync evaluate().
import asyncio
import os
import re
import time
from typing import Any, Dict, List, Optional, Union
@ -743,9 +744,15 @@ def _make_adapter(spec: str) -> ModelAdapter:
elif seg:
specs.append(f'openai/{seg}?{model}')
adapter = pooled(specs)
elif spec.partition(':')[0] == 'mock' and ':' in spec and '/' not in spec.partition(':')[0]:
adapter = resolve_adapter('mock')
adapter.extra['mode'] = spec.partition(':')[2] or 'echo'
elif re.fullmatch(r'mock[-:](boxed|oracle|fc|tool|echo|const)?', spec):
# mock-boxed (preferred) == legacy mock:boxed; bare 'mock' == echo.
# NEVER reuse the cached singleton: resolve_adapter memoizes and a
# shared instance would leak this run's mode into the next one
mode = re.fullmatch(r'mock[-:]?(.*)', spec).group(1) or 'echo'
from .adapter import ADAPTER_REGISTRY
adapter = ADAPTER_REGISTRY.get('mock')(model='mock', api_base='')
adapter.extra['mode'] = mode
return adapter
else:
adapter = resolve_adapter(spec)