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 ```bash
evalharness eval run gsm8k --model mock:boxed --limit 8 evalharness eval run gsm8k --model mock-boxed --limit 8
# acc 100% —— mock 适配器直接输出金标答案,证明 # acc 100% —— mock 适配器直接输出金标答案,证明
# 数据 → prompt → 生成 → 判分 → 报告 全链路可用 # 数据 → prompt → 生成 → 判分 → 报告 全链路可用
``` ```
@ -68,7 +68,7 @@ evalharness eval run gsm8k \
| `--api-url URL` | OpenAI 兼容端点;与 `--model` 搭配使用(不用手拼 spec 字符串) | | `--api-url URL` | OpenAI 兼容端点;与 `--model` 搭配使用(不用手拼 spec 字符串) |
| `--model NAME` | 服务端模型名(配合 `--api-url`);或直接给完整 spec`openai/http://h:8000/v1?qwen3-8b` | | `--model NAME` | 服务端模型名(配合 `--api-url`);或直接给完整 spec`openai/http://h:8000/v1?qwen3-8b` |
| `--provider {openai-chat,openai-pool}` | 协议/提供方,默认 `openai-chat`;端点池用 `openai-pool` | | `--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 覆盖 < 显式参数 | | `--profile NAME` | 命名生成参数集(内置 `dp4-nothink``qwen3-es-parity``t1-short`,或任意 `@register_gen_profile` 名);优先级:插件默认 < profile 默认 < profile bench 覆盖 < 显式参数 |
| `--disable-thinking` | 发送 `enable_thinking=false`Qwen3 类思考模型推荐;带 tools 的请求自动退回模板安全的软开关) | | `--disable-thinking` | 发送 `enable_thinking=false`Qwen3 类思考模型推荐;带 tools 的请求自动退回模板安全的软开关) |
| `--textools` | 工具以文本形式随 prompt 下发,而非原生 tool_calls | | `--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/<base_url>?<model_id>` | 单端点vLLM、SGLang、lmdeploy、ollama、云 API |
| `openai-pool/<base{8000..8007}/v1,...>?<model_id>` | 端点池:轮询 + 自适应并发 + failover | | `openai-pool/<base{8000..8007}/v1,...>?<model_id>` | 端点池:轮询 + 自适应并发 + failover |
| `deploy:<engine>/<model>` | 经 Deployer 解析(钉版本的推理环境) | | `deploy:<engine>/<model>` | 经 Deployer 解析(钉版本的推理环境) |
| `mock` / `mock:boxed` / `mock:fc` | 离线适配器(管线自检 | | `mock` / `mock-boxed` / `mock-fc` | 离线适配器(管线自检:回声 / 回放金标 / 回放工具调用 |
| `!nothink` `!perf` `!textools` 后缀 | spec 内联开关(与 CLI 参数等价) | | `!nothink` `!perf` `!textools` 后缀 | spec 内联开关(与 CLI 参数等价) |
API key 按端点从环境变量读取(`OPENAI_API_KEY``ANTHROPIC_API_KEY` …)。 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 兜底)。 (如 SimpleQA 的分级正确性 + NOT_ATTEMPTED 兜底)。
- **代码执行类**:模型生成的代码在硬隔离 Docker 中运行(`--network none`、cgroup 上限、只读 rootfs - **代码执行类**:模型生成的代码在硬隔离 Docker 中运行(`--network none`、cgroup 上限、只读 rootfs
swe 的逐实例 `sweb.eval.*` 镜像用 `evalharness sandbox prefetch swe_bench_verified` 预取。 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) 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: def _cmd_eval_run(args) -> int:
import asyncio import asyncio
import time as _time import time as _time
@ -283,7 +303,7 @@ def _cmd_eval_run(args) -> int:
ds, model_spec, concurrency=args.concurrency, limit=args.limit, ds, model_spec, concurrency=args.concurrency, limit=args.limit,
limit_per_task=args.limit_per_task, limit_per_task=args.limit_per_task,
checkpoint=args.resume, checkpoint=args.resume,
judge_spec=args.judge, env=args.env, judge_spec=_compose_judge_spec(args), env=args.env,
gen_profile=getattr(args, 'profile', ''), gen_profile=getattr(args, 'profile', ''),
progress_reporter=progress_reporter, progress_reporter=progress_reporter,
status_callback=status_callback)) status_callback=status_callback))
@ -496,7 +516,10 @@ def build_parser() -> argparse.ArgumentParser:
p.add_argument('--provider', default='openai-chat', p.add_argument('--provider', default='openai-chat',
choices=('openai-chat', 'openai-pool'), choices=('openai-chat', 'openai-pool'),
help='API protocol/provider (default: openai-chat)') 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='', p.add_argument('--profile', default='',
help='named gen-params profile (dp4-nothink | qwen3-es-parity | t1-short ' help='named gen-params profile (dp4-nothink | qwen3-es-parity | t1-short '
'or any @register_gen_profile name); layers: plugin default < ' 'or any @register_gen_profile name); layers: plugin default < '

View File

@ -12,6 +12,7 @@ raw strings to the sync evaluate().
import asyncio import asyncio
import os import os
import re
import time import time
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
@ -743,9 +744,15 @@ def _make_adapter(spec: str) -> ModelAdapter:
elif seg: elif seg:
specs.append(f'openai/{seg}?{model}') specs.append(f'openai/{seg}?{model}')
adapter = pooled(specs) adapter = pooled(specs)
elif spec.partition(':')[0] == 'mock' and ':' in spec and '/' not in spec.partition(':')[0]: elif re.fullmatch(r'mock[-:](boxed|oracle|fc|tool|echo|const)?', spec):
adapter = resolve_adapter('mock') # mock-boxed (preferred) == legacy mock:boxed; bare 'mock' == echo.
adapter.extra['mode'] = spec.partition(':')[2] or '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 return adapter
else: else:
adapter = resolve_adapter(spec) adapter = resolve_adapter(spec)