Benchmarks and quality control
🔵 agent-os bench runs agents through end-to-end scenarios and writes a report — this way
agent quality is checked automatically, not "by eye".
16.1 Running
# против работающего сервера
agent-os bench --url https://HOST --max 5
# поднять собственный локальный сервер (нужен DEEPSEEK_API_KEY в .env)
agent-os bench --port 3001 --max 3
# конкретный агент
agent-os bench --url https://HOST --agent drom --max 5 --parallel 3
# сценарии по имени (подстрока; `a|b` = OR)
agent-os bench --url https://HOST --name "Camry|BMW" --max 10
# с LLM-судьёй качества и кодом возврата для CI
agent-os bench --url https://HOST --agent drom --judge --fail-on-error --out report.json
# 3 полных прогона с усреднением (борьба с шумом LLM/сети)
agent-os bench --url https://HOST --agent drom --repeats 3 --judge
Flags: --url, --port (3001), --agent, --name, --max (0=all),
--parallel (4), --timeout (90), --config (config root), --out
(report path), --repeats (1), --judge, --judge-model, --fail-on-error.
Format — --flag value.
--repeats N runs the entire set of scenarios N times and averages: a turn is considered
passed only if it passed in all N runs (pass^N), while tokens/cost/
time/judge are averaged. This eliminates the "swings" of single runs; the report has
a field repeats.
The result — bench_report_<timestamp>.json (or --out), with turn counters
passed/failed, scenario counters scenarios_*, per-turn failures and
the full response text in the field response. --fail-on-error returns exit code 1
if there was at least one failure.
16.2 Scenario format
Scenarios in config/agents/<agent>/bench/scenarios.yaml:
name: "Drom Agent"
model: "drom-agent"
scenarios:
- name: "Поиск Camry"
turns:
- user: "Найди Toyota Camry"
assert:
contains: [camry]
min_links: 2
valid_urls: true
Multi-turn scenarios
- name: "Multi-turn: уточнение"
turns:
- user: "Первый вопрос"
assert: { contains: [Слово], min_links: 2 }
- user: "Уточнение"
assert: { min_links: 1 }
- user: "Финальное уточнение"
assert: { valid_urls: true }
Check types
| Check | Meaning |
|---|---|
contains: [w1, w2] |
the response must contain substrings (case-insensitive) |
not_contains: [w1, w2] |
these substrings must not be present |
regex: ["..."] |
the response must match each regex |
min_links: N |
at least N markdown links [text](url) |
min_urls: N |
at least N URLs (markdown links + bare http(s)://) |
valid_urls: true |
all URLs start with http:// or https:// |
min_chars: N |
minimum response length |
max_elapsed_ms: N |
turn time ceiling (speed) |
max_completion_tokens: N |
response token ceiling (savings) |
max_cost_usd: X |
turn cost ceiling |
Set speed and token savings with explicit ceilings
(max_elapsed_ms/max_completion_tokens/max_cost_usd), not just the judge.
16.3 Analyzing the result
- Look at the console:
✓(pass) /✗(fail). - Read
bench_report_*.json: the fieldsfailuresanderror. - Compare two reports (diff) to find regressions.
16.4 Run storage
Runs are written to logs/bench.db and are visible in the admin API:
| Endpoint | Description |
|---|---|
POST /v1/agents/:name/bench/run |
run a template benchmark |
GET /v1/agents/:name/bench/runs |
run history for a template |
GET /v1/agents/:name/bench/runs/:id |
a single run + its turns (while running — compact status; ?full=1 — full) |
GET /v1/bench/runs |
latest runs across all templates |
GET /v1/bench/catalog |
templates with configured scenarios |
Run body (all fields optional):
{"timeout_secs": 90, "parallel": 4, "judge": false, "judge_model": ""}.
parallel sets the concurrently executing scenarios, judge enables
LLM evaluation of each successful turn (average score — avg_judge_score).
In the admin UI these parameters are available on the "Benchmarks" tab.
While a run is in progress (status: running), GET .../runs/:id by default returns a
compact response (run, progress {done,total}, hint) without the list of turns —
so that an agent waiting for the result doesn't pull the entire list on every poll.
The full list is returned when status stops being running, or via
?full=1. The agent doesn't need to poll more often than once every ~30 s — for this there is
the tool wait_tool.
If the server was stopped/restarted during a run, such an "orphaned" run
is marked failed on the next start (finished_at + error: interrupted: server stopped before the run finished), а не висит в running.
16.5 Practices
- Each scenario focuses on one filter/flow.
- Queries in Russian (drom/hg/ngu), English (rss).
- Multi-turn scenarios check context preservation between turns.
- Heavy scenarios — combined filters and edge cases.
min_links: 1for edge cases (rare brands, narrow filters).- Always
valid_urls: true— catches broken link formatting.