Files
ducoterra f221b40fce feat(agent): add CSV benchmark recorder + summary/embedding test scripts and skills
New files:
- scripts/model_benchmark.py — shared CSV recorder for all model tests
- scripts/test_summary_model.py — summary model quality benchmark (coherence, coverage, brevity, hallucination)
- scripts/test_embed_model.py — embedding model benchmark (dimension, cosine accuracy, speed)
- .agents/skills/test-summary-model/SKILL.md — skill for testing summary models
- .agents/skills/test-embed-model/SKILL.md — skill for testing embedding models
- benchmarks/README.md — schema documentation

Updated:
- .agents/skills/test-chat-model/SKILL.md — now also records to CSV

All three scripts write to benchmarks/model_benchmarks.csv with one row
per run per check. The CSV accumulates results across runs for comparison.
2026-09-06 21:58:35 -04:00

151 lines
6.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: test-chat-model
description: Adds a new chat model to the app (BOR_LLM_CHAT_MODEL in .env) and tests its tool-calling functionality with the controlled methodology from TOOL_CALLING_TESTING.md — fixture battery gate, locked derived gate, per-turn and total wall timing — then records the verdicts in TOOL_CALLING_TESTING.md with a single docs commit. Use when the user asks to add or test a new chat model, benchmark a model on tool calling, or compare models (e.g. "test model turbo", "switch the chat model to X and test it").
---
# Test a Chat Model (controlled tool-calling battery)
Switch `BOR_LLM_CHAT_MODEL` to the target model and run the controlled
tool-calling battery from `TOOL_CALLING_TESTING.md` (repo root). The
methodology is fixed: 8 hand-written fixture docs with unguessable
specifics, snapshotted to `tests/fixtures/test_kb.dump.sql` and restored
in ~0.03 s — **no repo imports, no re-embedding, no app code changes**.
The user supplies the model name (e.g. `turbo`). If they don't, ask.
## Rules (non-negotiable)
- **Do not touch** the battery questions, the thresholds, or the fixture
documents — changing any of those is a *methodology* change: flag it to
the user first.
- **Do not edit app code** (`app/`, `tests/`). This skill tests a model,
not the app. If the model exposes an app defect, report it — don't fix
it here.
- `.env` is gitignored and is **not committed** — the model switch stays
a live dev setting, and the summary must say which model `.env` is left
on (default: the tested model — "add a new model" implies keeping it).
## Procedure
All commands run from the repo root with `uv run`.
### 1. Preconditions (fast fail)
```bash
podman compose up -d db
ls tests/fixtures/test_kb.dump.sql # must exist
grep -E "BOR_LLM_(BASE_URL|API_KEY|EMBED_MODEL)" .env
```
If the dump is missing, rebuild it once (real embeddings, ~2 s):
```bash
uv run python -m scripts.load_test_kb
```
A gate exit code of **2** means a precondition failed (DB down, schema
not applied, dump missing) — each prints the actionable fix; fix it and
re-run. Do not interpret an exit-2 run.
### 2. Switch the model
Edit the single line in `.env` (leave `BOR_LLM_SUMMARY_MODEL` and
`BOR_LLM_EMBED_MODEL` alone):
```
BOR_LLM_CHAT_MODEL=<model>
```
### 3. Run the battery (3 runs)
```bash
# a. sanity micro-loop — ~12 s for a fast model, scales with the model
uv run python -m scripts.agent_realmodel_check --restore --mode fixture --turns 3
# b. full fixture gate, twice (variance matters — see the 92/100/100 spread for lite)
uv run python -m scripts.agent_realmodel_check --restore --mode fixture
uv run python -m scripts.agent_realmodel_check --restore --mode fixture
# c. locked derived battery (phase-72 gate — bare-path traps, executed >= 90% bar)
uv run python -m scripts.agent_realmodel_check --restore
```
Grep `^gate:` / `^turn ` from each run. Total wall: ~2–8 min depending on
model speed. Per-turn seconds are on the `turn` lines — capture them,
they are the latency signal. (No `--concurrency` — the endpoint
serializes; measured, no gain.)
### 4. Read the verdicts
Each `gate:` line: `PASS|FAIL turns=10 answered=N caps=N tool-turns=N
calls X/Y executed (E%) contract C/D (K%) DATE (wall Ts)`. A `FAIL`
prints the `MISS` lines naming the condition. Interpret:
- **contract < 100 %** — real incident-class errors (bad scopes, bare
paths, hallucinated identities). Correlate with the per-run log lines
`agent tool=… args=… round=…/…` and the refusal classes in
`app/rag/agent.py` to name the exact misuse.
- **executed < contract** — the gap is `ALREADY_IN_CONTEXT` re-reads of
already-seeded documents: a *model-specific habit* (reference rates:
`lite` ~100 % of seeded-target turns, `turbo` ~12 %). Report the
re-read rate, not a verdict.
- **FAIL on only the ≥6/10 tool-turns usage floor** — the model answered
seeded questions from `<documents>` context instead of making the
(refusable) read call the trap design expects. That is the *ideal*
grounded behavior; report it as a test artifact, accuracy unaffected.
- **caps > 0** — the phase-72 incident signature; a real regression,
say so explicitly.
### 5. Record to CSV + commit
**a. CSV record** — append to `benchmarks/model_benchmarks.csv` via
`scripts/model_benchmark.bench_write`. For each of the 3 runs, call:
```python
from scripts.model_benchmark import bench_write
bench_write(
script="chat", model="<model>", mode="fixture",
gate_status="PASS", turns=10, answered=10, caps=0,
tool_turns=<N>, emitted=<E>, executed=<X>,
contract=<C>, wall_s=<wall>,
contract_denom=<C_denom>, executed_denom=<E_denom>,
)
```
For the derived run, use `mode="derived"`.
**b. Append to TOOL_CALLING_TESTING.md** — keep the `gate:` lines
**byte-exact verbatim**, plus 2–4 sentences of interpretation against
the reference rates above and the wall-time baseline (lite ~40–55 s,
turbo ~97–135 s per full loop).
Commit — docs only, house style:
```bash
git add TOOL_CALLING_TESTING.md benchmarks/model_benchmarks.csv
git commit --no-gpg-sign \
-m "docs(agent): record the <model> comparison on the controlled fixture battery" \
-m "<one-paragraph body: the numbers, the re-read rate, the wall time, any MISS nuance>"
```
### 6. Summary
Table of contract / executed / caps / tool-turns / wall per run (vs the
already-recorded models), the one-line conclusion (more or less
disciplined than the others, faster or slower), and a note that `.env`
is now on `<model>`.
## Troubleshooting
- **endpoint down / all turns error** — check
`curl -s $BOR_LLM_BASE_URL/models` with the key; the gate will exit 1
with `answered<10`. Report, don't retry-loop.
- **slow runs** — per-turn seconds on the `turn` lines show it; the LLM
is ~95 % of the cost, DB work is milliseconds.
- **a turn deflects (defl=yes)** — the honesty gate found no grounded
retrieval; that breaks the battery's design contract. Re-run once; if
it repeats, the fixture KB or the embed model changed — run
`uv run python -m scripts.load_test_kb` and check its retrieval report
(all 10 questions must be grounded).