Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ jobs:
with:
python-version: "3.11"
- run: pip install -r requirements-dev.txt
# Только офлайн self-test (без сети/денег/ключей). Тяжёлый прогон — вручную (RUN_BENCH=1).
- run: pytest -q tests/test_fixtures.py
# Импорт ВСЕХ модулей (в т.ч. engines/judges/runner/report) — синтаксис/импорты
# ловятся до тестов; раньше эти модули CI не видел (REVIEW.md R37).
- run: python -c "import llmbench.core, llmbench.fixtures, llmbench.mcp, llmbench.scoring, llmbench.cases, llmbench.engines, llmbench.judges, llmbench.report, llmbench.runner"
# Офлайн self-test (без сети/денег/ключей). Тяжёлый прогон — вручную (RUN_BENCH=1).
- run: pytest -q
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ node_modules/
*.log
.pytest_cache/
.DS_Store

# Сырые артефакты прогонов и авто-сгенерированный отчёт (курируемые .ru/.en.md — под VCS).
results/runs-*.jsonl
results/model-comparison-grid.generated.md
results/model-comparison-grid.partial.md
84 changes: 50 additions & 34 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,41 @@ Two modes:
The engine and MCP client are **decoupled from askads** (extracted into `llmbench/`), so the
repository is self-contained.

> Full review of the harness and the list of fixed issues — see [`REVIEW.md`](REVIEW.md).

## What is scored, and by whom

| Dimension | By | Where |
|---|---|---|
| **Tool-Use** (right tools / order / call cap) | code | `scoring.score_tooluse` |
| **Numeric-Accuracy** (number correctness, "don't invent CPA") | code | `scoring.score_numeric` |
| **Tool-Use** (right tools succeed / order / call cap) | code | `scoring.score_tooluse` |
| **Numeric-Accuracy** (number correctness + entity anchoring, "don't invent CPA") | code | `scoring.score_numeric` |
| Interpretation / Russian / Edge handling | judge panel | `judges.py` |

Key metrics are the backbone of the decision; judges are strictly secondary.
Key metrics are the backbone of the comparison; judges are strictly secondary.

## Layout

```
llmbench/
core.py # allowlists, tool-schema converters, truncation, rates, system prompt, retry
fixtures.py # frozen account + FIXTURE_VERSION
mcp.py # live stdio client + server registry + fake session + open_session(mode)
engines.py # agentic loops: run_anthropic (Claude/GLM), run_openai (GPT)
scoring.py # numeric + toolcheck + cost + decision rule
judges.py # panel {Claude, GPT, GLM, (opt.) Gemini} + neutrality
cases.py # cases (question + trace spec + golden_facts + rubric)
runner.py # variant grid x cases x repeat, report, verdicts
tests/test_fixtures.py # offline self-test (CI)
results/ # report + raw per-run log of the latest run
core.py # allowlists, tool-schema converters, truncation, rates, system prompt, retry, timeouts
fixtures.py # frozen account + FIXTURE_VERSION (single source of golden facts)
mcp.py # live stdio client + server registry + fake session + preflight_live
engines.py # agentic loops: run_anthropic (Claude/GLM), run_openai (GPT); retries, timeouts
scoring.py # numeric (entity anchoring) + toolcheck (is_error aware) + cost
judges.py # panel {Claude, GPT, GLM, (opt.) Gemini}, neutrality, determinism
cases.py # cases (question + trace spec + golden_facts with entity + rubric)
report.py # record aggregation, Stability/Score/Pareto, markdown build (CI-tested)
runner.py # variant grid x cases x repeat, JSONL persistence, report
tests/ # offline self-test: scoring + aggregation + runner pipeline (all in CI)
results/ # curated reports (.ru/.en.md) + generated report and runs-*.jsonl (gitignored)
```

## Running

Offline self-test (no network/money; CI):
```bash
pip install -r requirements-dev.txt
pytest -q tests/test_fixtures.py
pytest -q
```

Deterministic model benchmark (needs model keys):
Expand All @@ -64,38 +67,51 @@ RUN_BENCH=1 ANTHROPIC_API_KEY=… YANDEX_DIRECT_TOKEN=… \
```
Account-token env: `YANDEX_DIRECT_TOKEN` (+ optional `YANDEX_DIRECT_LOGIN`),
`YANDEX_METRIKA_TOKEN`, `VK_ADS_TOKEN`. Override a server path with
`MCP_PATH_YANDEX_DIRECT=/path/to/dist/index.js`.
`MCP_PATH_YANDEX_DIRECT=/path/to/dist/index.js`. Live mode preflights (tokens + server
presence) BEFORE the first paid call.

Flags: `--variants`, `--cases` (a typo in the filter is an error, not a silent full grid),
`--repeat`, `--judges panel|neutral|off`, `--concurrency N` (parallel runs within a variant),
`--dry-run` (shows the estimate without keys), `--out`, `--report-from <jsonl>`. The variant
list (model x thinking/effort/reasoning) lives in `llmbench/runner.py`; adding a model is one
line (don't forget the rate in `core.MODEL_RATES`, or the runner warns).

## Artifacts and re-scoring

Flags: `--variants`, `--cases`, `--repeat`, `--judges panel|neutral|off`, `--dry-run`,
`--out`. The variant list (model x thinking/effort/reasoning) lives in `llmbench/runner.py`;
adding a model is one line.
Every run writes **`results/runs-<ts>.jsonl`** — one record per run (answer, tool trace,
usage, all scores, errors). This is the source of truth: the report is rebuilt from it for
free, with no repeat model calls —
```bash
python -m llmbench.runner --report-from results/runs-20260703-120000.jsonl
```
The runner writes the generated report to `results/model-comparison-grid.generated.md`
(gitignored) so it never clobbers the hand-curated `model-comparison-grid.ru.md` / `…en.md`
(top-3, prose, bilingual) — those are edited by hand from the generated one.

## Decision rule
## How Score is computed

`scoring.DECISION_RULE` (numeric ≥ 4.5, tool ≥ 4.5, edge ≥ 4.0, score/$ ≥ baseline) is fixed
BEFORE the run; the runner prints PASS/FAIL per variant vs the baseline (current prod —
`Sonnet disabled/high`). Both axes (quality and score/$) are measured on the shippable config.
A run's `Score` is the mean of the **available** components: Tools Use (always), Accuracy (if
the case has golden facts), Edge Cases and Lang quality (if judges ran). The set depends on
the case, so Score is comparable across variants (everyone runs the same cases) but is NOT
equal to the mean of the four report columns. Failed runs (API errors, token-limit
truncation) are excluded from metrics and shown in a separate `Err` column.
`Stability = 5 − mean spread of Score between repeats of the same case` (not across cases of
differing difficulty).

## Latest run results

`results/model-comparison-grid.en.md` (+ `…ru.md`) — summary + Pareto frontier;
`results/run-log.txt` — raw per-run log (aggregates are recomputable from it).

In short (fixed, repeat 2, 16 variants): **GLM-4.6 without thinking** — parity with Claude on
the key metrics (Numeric/Tool 5.0), the most stable non-Claude variant (σ 0.36), and **~11×
cheaper** than prod (score/$ 1630 vs 115). The only SWITCH on the Pareto frontier. Surprises:
thinking HURTS GLM-4.6 (Numeric 5.0→4.0); GLM-5 is worse than 4.6; GPT fails the key metrics
(gpt-4.1 omits CPA → Numeric 4.0; gpt-5 reaches for tools on a "change the bid" request).
`results/model-comparison-grid.ru.md` (+ English `…en.md`) — curated summary + Pareto
frontier. ⚠️ Their current numbers are from the 2026-06-29 run, BEFORE the scoring/fixture
fixes (see `REVIEW.md`); regenerate with a fresh run.

## Known limitations

- **Case ceiling:** top models max out at 5.0 on Tool/Numeric → "parity" here means "both ace
THESE tasks". Harder cases are needed to truly separate quality.
- **Judges are secondary:** without a neutral vendor, the primary soft score is the panel mean
(advisory, self-preference possible). The decision rests on the key metrics.
- gpt/gemini/glm rates and cache multipliers are estimates; verify against billing.
(advisory, self-preference possible). The comparison rests on the key metrics.
- Model rates and cache multipliers are from price lists; verify against billing.
- `glm-5`/`gpt-5`: availability ≠ identity of the expected model — verify.
- `--repeat` is a coarse noise flag, not a comparison count; "wrong number == missing" in
numeric is a simplification.
- `--repeat` is a coarse noise flag; "wrong number == missing" in numeric is a simplification.
- `fixed` mode does not catch robustness on messy API output (use `--mode live` for that).
- The `system prompt` in `core.py` is domain-specific (a Direct analyst); swap it for your case.
83 changes: 49 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,41 @@ VK Ads / Метрика) и **бенчить их между собой** на

Движок и MCP-клиент **развязаны от askads** (вынесены в `llmbench/`), репозиторий автономен.

> Полное ревью харнесса и список исправленных проблем — в [`REVIEW.md`](REVIEW.md).

## Что и кто считает

| Измерение | Кто | Где |
|---|---|---|
| **Tool-Use** (нужные тулы/порядок/кап) | код | `scoring.score_tooluse` |
| **Numeric-Accuracy** (точность чисел, «не выдумывать CPA») | код | `scoring.score_numeric` |
| **Tool-Use** (нужные тулы успешно/порядок/кап) | код | `scoring.score_tooluse` |
| **Numeric-Accuracy** (точность чисел + entity-анкоринг, «не выдумывать CPA») | код | `scoring.score_numeric` |
| Интерпретация / Русский / Краевое | панель судей | `judges.py` |

Ключевые метрики — костяк решения; судьи строго вторичны.
Ключевые метрики — костяк сравнения; судьи строго вторичны.

## Структура

```
llmbench/
core.py # allowlists, конвертеры тулов, обрезка, ставки, system-prompt, retry
fixtures.py # замороженный кабинет + FIXTURE_VERSION
mcp.py # live stdio-клиент + реестр серверов + fake-сессия + open_session(mode)
engines.py # агентные loop'ы: run_anthropic (Claude/GLM), run_openai (GPT)
scoring.py # numeric + toolcheck + cost + decision rule
judges.py # панель {Claude, GPT, GLM, (опц.) Gemini} + нейтральность
cases.py # кейсы (вопрос + trace-спека + golden_facts + рубрика)
runner.py # сетка вариантов × кейсы × repeat, отчёт, вердикты
tests/test_fixtures.py # офлайн self-test (CI)
results/ # отчёт + сырой per-run лог последнего прогона
core.py # allowlists, конвертеры тулов, обрезка, ставки, system-prompt, retry, таймауты
fixtures.py # замороженный кабинет + FIXTURE_VERSION (единый источник golden-фактов)
mcp.py # live stdio-клиент + реестр серверов + fake-сессия + preflight_live
engines.py # агентные loop'ы: run_anthropic (Claude/GLM), run_openai (GPT); ретраи, таймауты
scoring.py # numeric (entity-анкоринг) + toolcheck (учёт is_error) + cost
judges.py # панель {Claude, GPT, GLM, (опц.) Gemini}, нейтральность, детерминизм
cases.py # кейсы (вопрос + trace-спека + golden_facts с entity + рубрика)
report.py # агрегация записей, Stability/Score/Pareto, сборка markdown (тестируется в CI)
runner.py # сетка вариантов × кейсы × repeat, JSONL-персист, отчёт
tests/ # офлайн self-test: скоринг + агрегация + конвейер ранера (всё в CI)
results/ # курируемые отчёты (.ru/.en.md) + авто-отчёт и runs-*.jsonl (в .gitignore)
```

## Запуск

Офлайн self-test (без сети/денег; CI):
```bash
pip install -r requirements-dev.txt
pytest -q tests/test_fixtures.py
pytest -q
```

Детерминированный model-бенч (нужны ключи моделей):
Expand All @@ -63,38 +66,50 @@ RUN_BENCH=1 ANTHROPIC_API_KEY=… YANDEX_DIRECT_TOKEN=… \
```
Env токенов кабинета: `YANDEX_DIRECT_TOKEN` (+ опц. `YANDEX_DIRECT_LOGIN`),
`YANDEX_METRIKA_TOKEN`, `VK_ADS_TOKEN`. Путь к серверу можно переопределить
`MCP_PATH_YANDEX_DIRECT=/path/to/dist/index.js`.
`MCP_PATH_YANDEX_DIRECT=/path/to/dist/index.js`. Live-режим делает префлайт (токены +
наличие серверов) ДО первого платного вызова.

Флаги: `--variants`, `--cases` (опечатка в фильтре — ошибка, а не молчаливый полный грид),
`--repeat`, `--judges panel|neutral|off`, `--concurrency N` (параллельные прогоны внутри
варианта), `--dry-run` (показывает смету без ключей), `--out`, `--report-from <jsonl>`.
Список вариантов (модель × thinking/effort/reasoning) — в `llmbench/runner.py`; добавить
модель = одна строка (не забудь тариф в `core.MODEL_RATES`, иначе ранер предупредит).

## Артефакты и пере-скоринг

Флаги: `--variants`, `--cases`, `--repeat`, `--judges panel|neutral|off`, `--dry-run`,
`--out`. Список вариантов (модель × thinking/effort/reasoning) — в `llmbench/runner.py`;
добавить модель = одна строка.
Каждый прогон пишет **`results/runs-<ts>.jsonl`** — по записи на прогон (ответ, tool-трейс,
usage, все оценки, ошибки). Это источник правды: отчёт пересобирается из него бесплатно,
без повторных вызовов моделей —
```bash
python -m llmbench.runner --report-from results/runs-20260703-120000.jsonl
```
Ранер пишет авто-отчёт в `results/model-comparison-grid.generated.md` (в `.gitignore`),
чтобы не затирать курируемые вручную `model-comparison-grid.ru.md` / `…en.md` (Топ-3, проза,
двуязычие) — их правят руками из сгенерированного.

## Правило решения
## Как считается Score

`scoring.DECISION_RULE` (numeric ≥ 4.5, tool ≥ 4.5, edge ≥ 4.0, score/$ ≥ baseline) фиксируется
ДО прогона; ранер печатает PASS/FAIL по каждому варианту vs baseline (текущий прод —
`Sonnet disabled/high`). Обе оси (качество и score/$) — на shippable-конфиге.
`Score` прогона = среднее **доступных** компонент: Tools Use (всегда), Accuracy (если у
кейса есть golden-факты), Edge Cases и Lang quality (если работали судьи). Состав зависит от
кейса, поэтому Score сравним между вариантами (кейсы у всех одни), но НЕ равен среднему
четырёх колонок отчёта. Упавшие прогоны (ошибки API, обрезка лимитом токенов) в метрики не
входят и видны отдельной колонкой `Err`. `Stability = 5 − средний разброс Score между
повторами одного кейса` (а не между кейсами разной сложности).

## Результаты последнего прогона

`results/model-comparison-grid.ru.md` (+ англ. `…en.md`) — сводка + Pareto-фронт;
`results/run-log.txt` — сырой per-run лог (по нему пересчитываются агрегаты).

Кратко (fixed, repeat 2, 16 вариантов): **GLM-4.6 без thinking** — паритет с Claude по кодовым
метрикам (Numeric/Tool 5.0), самый стабильный из не-Claude (σ 0.36), и **~11× дешевле** прода
(score/$ 1630 против 115). Единственный SWITCH на Pareto-фронте. Сюрпризы: thinking ВРЕДИТ
GLM-4.6 (Numeric 5.0→4.0); GLM-5 хуже 4.6; GPT не проходит ключевые метрики (gpt-4.1 не выводит
CPA → Numeric 4.0; gpt-5 лезет в тулы на «измени ставку»).
`results/model-comparison-grid.ru.md` (+ англ. `…en.md`) — курируемая сводка + Pareto-фронт.
⚠️ Текущие числа в них — от прогона 2026-06-29, ДО правок скоринга/фикстур (см. `REVIEW.md`);
их нужно перегенерировать свежим прогоном.

## Известные ограничения

- **Потолок кейсов:** топ-модели упираются в 5.0 по Tool/Numeric → «паритет» здесь = «оба
отлично решают ЭТИ задачи». Для строгого различения качества нужны более трудные кейсы.
- **Судьи вторичны:** без нейтрального вендора первичный мягкий балл = среднее панели
(advisory, возможна self-preference). Вес решения — на ключевых метриках.
- Ставки/кэш-множители gpt/gemini/glmоценки; сверить с биллингом.
(advisory, возможна self-preference). Вес сравнения — на ключевых метриках.
- Ставки/кэш-множители моделейпо прайс-листам; сверить с биллингом.
- `glm-5`/`gpt-5`: доступность ≠ идентичность ожидаемой модели — сверить.
- `--repeat` — грубый флаг шума, не число для сравнения; «неверное число == пропущенное» в
numeric — упрощение.
- `--repeat` — грубый флаг шума; «неверное число == пропущенное» в numeric — упрощение.
- `fixed`-режим не ловит robustness на грязном API-выводе (для этого `--mode live`).
- `system-prompt` в `core.py` — доменный (аналитик Директа); замени под свой кейс.
Loading
Loading