brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
llm-eval-harnessTests/benchmarks a third-party LLM endpoint (OpenAI- or Anthropic-compatible): availability, fidelity, speed, concurrency, protocol compliance, quality regression. Use before hand-rolling a curl loop, when onboarding a provider, or debugging "system prompt 不生效" / a tok/s claim — 测评/压测一个模型或渠道. Not for TTS/voice-clone supplier eval (use the audio skill).
Independent GitHub snapshot · Automated classification · Original documentation governs
Give this skill an endpoint (base_url + model + an API key in an env var) and it
measures whether the endpoint actually works and whether the model is fast, stable,
protocol-correct, and good enough — instead of trusting the vendor's headline numbers.
Six dimensions, usually scattered across ad-hoc scripts that get rewritten (with the
same bugs) every time:
| Dimension | Script | Answers |
|---|---|---|
| Availability | scripts/availability_probe.py | which model IDs work here, with 3-state error classification |
| Request fidelity | scripts/fidelity_probe.py | do system prompt / tools / history actually REACH the model? |
| Speed | scripts/speed_probe.py | TTFT + sustained decode tok/s, thinking-aware |
| Concurrency / stability | scripts/concurrency_probe.py | success rate, p50/p90 latency, where it breaks |
| Protocol compliance | scripts/protocol_probe.py | does the Anthropic thinking block actually fire when requested, AND does the endpoint accept one already sitting in history on a later turn (N≥10, both are separate checks — see Dimension 3)? |
| Quality / use-case regression | scripts/usecase_runner.py + blind judges | does it pass your accumulated cases? |
Route from what the user is actually doing:
--check history-replay specifically — this symptom is the
generation check passing while history-replay silently doesn't; don't stop at generation--output, then follow
references/vendor_evidence_protocol.mdDon't run all six ritually — pick what answers the user's question.
Key handling (non-negotiable): every script takes the API key by env-var name
(--key-env MY_KEY), never the key value on the command line — so it stays out of ps,
shell history, and any saved report. Never hardcode a key into a use-case file or a
wrapper. Read references/evaluation_disciplines.md
for the full reasoning behind this and the other disciplines.
Your private data lives outside this bundle. Use-case libraries, model rosters, and
keys belong in ~/.llm-eval/ (or wherever you keep secrets), NOT in this skill directory —
the skill is generic and public; your test suite is yours. See "Use-case library" below.
Detect what you have, then run the dimensions that apply. For an OpenAI-compatible model:
export MY_KEY=sk-... # the key never appears in a command below
# Speed: real-task throughput + sustained decode ceiling
uv run --with openai python scripts/speed_probe.py \
--base-url https://api.example.com/v1 --model some-model --key-env MY_KEY --mode both
# Concurrency: ramp until it breaks
uv run --with aiohttp python scripts/concurrency_probe.py \
--url https://api.example.com/v1/chat/completions --model some-model --key-env MY_KEY \
--format openai --concurrency 10 20 40 60
If the endpoint is Anthropic-Messages-shaped (/v1/messages), also run the protocol probe
(below). Pick dimensions by what the user actually asked — don't run all six if they only
asked "is it fast?".
uv run --with aiohttp python scripts/availability_probe.py \
--base-url <base> --key-env <ENV> --format both \
--models model-a vendor/model-b @models.txt --output /tmp/avail.json
no-channel (no route for this
ID), upstream-error (route exists, upstream failing — retry later), empty-content
(usually a max_tokens artifact on reasoning models, NOT a broken model). The probe
keeps --max-tokens at 8192 by default precisely so thinking models don't read as
dead — do not "optimize" it downward.-preview, dated variants, tier names) decide routability. Full traps: disciplines §9.some-model[1m] are frequently a CLI client's own local convention (Claude
Code parses and strips [1m] before the request is ever built — see
claude-switch-models-setup's "Configuring Context Window Size" section for the full
mechanism) — they never appear on the wire. The probe warns when it sees one, but the
discipline is yours: probe the bare model ID the vendor actually documents, not a string
copied out of a Claude Code ANTHROPIC_MODEL env var./v1/models listing is one input, never the verdict — real gateways
route models the listing omits.uv run --with aiohttp python scripts/fidelity_probe.py \
--base-url <base> --model <model> --key-env <ENV> \
--format anthropic --check all --repeat 10 --output /tmp/fidelity.json
system (canary code planted in the system prompt — can the model echo
it back?), tools (full round-trip including returning a tool result and verifying
the final answer uses it), multiturn (plant facts across turns, recall them all),
auth (x-api-key vs Bearer on both protocol endpoints — gateways commonly accept
both on one path and only one on the other, and the wrong-header 401 reads exactly
like a dead key).--repeat (default 10) and a three-state verdict (delivered / intermittent k-of-N /
not-delivered). A clean single-window result is still just that window: re-sample in
another time slice before publishing a number (disciplines §12).uv run --with openai python scripts/speed_probe.py \
--base-url <…/v1> --model <model> --key-env <ENV> --mode both --output /tmp/speed.json
mixed runs representative tasks (what real usage feels like); decode forces one long
output to find the sustained ceiling (the number to compare against a vendor's claim);
both does both.reasoning_content field, but completion_tokens counts it. Collecting only content
while dividing by completion_tokens produces wildly inflated numbers — a real ~750 tok/s
model once measured as 4700 tok/s this way. The script captures both, takes TTFT as the
first token of either kind, and reports completion_tokens / (total − TTFT).uv run --with aiohttp python scripts/concurrency_probe.py \
--url <full endpoint URL> --model <model> --key-env <ENV> \
--format openai|anthropic --concurrency 10 20 40 60 --output /tmp/conc.json
--concurrency levels to ramp and find the ceiling — the level where success
rate drops or latency explodes. A model that's fast single-threaded can still collapse at
modest concurrency (real example: one provider held 50 concurrent at 0.4s while another
dropped requests at just 5 concurrent).trust_env=False) and disables keep-alive
pooling (force_close) — otherwise you measure the proxy's limit or one pinned upstream
replica, not the model. It prints a "concurrency proof" (overlapping request pairs) so you
can confirm requests really ran in parallel.uv run python scripts/protocol_probe.py \
--url <…/v1/messages> --model <model> --key-env <ENV> --check all --repeat 10 --output /tmp/proto.json
/v1/messages compatibility. --check all
(default) runs BOTH sub-checks — they test different code paths and a vendor can pass one
while hard-failing the other:
thinking: {type: enabled} actually produce thinking_delta /
signature_delta SSE events when you request it? (the original check)type: "thinking" block that's already
sitting in a prior assistant turn, when that history is replayed back on a later turn —
exactly what Claude Code and every other agentic client does on every continuation?
(--check history-replay to run just this one)preserve_thinking), which
looked like confirmation. Round 2 was STILL wrong: the probe never left that one reseller, so
it couldn't see that the vendor's own direct/native endpoint handled the "rejected" model fine
— real production traffic proved it. Read §19 before treating any single-reseller-confirmed
result as final; check the vendor's own endpoint or real traffic for the actual channel in
question before writing an "X doesn't support thinking" conclusion into anything.--repeat defaults to 10; generation's verdict has three states (fully-implemented,
intermittent (k/N), not-implemented), history-replay's has its own three-plus states
(accepts-thinking-in-history, rejects-thinking-in-history, inconsistent, or
inconclusive when errors look unrelated to thinking at all). Never conclude from a single
sample.Connection: close per request so a load balancer can't pin all samples to one
replica and hide the real distribution (a real probe saw 0/10 with keep-alive vs 17/90
with close on the same endpoint).This is two halves on purpose: collect, then judge independently.
Step 1 — collect the model's answers to your use-case library:
uv run --with openai python scripts/usecase_runner.py \
--base-url <…/v1> --model <model> --key-env <ENV> \
--usecases ~/.llm-eval/usecases.json --output-dir ~/.llm-eval/runs/<model>
Step 2 — judge with independent blind judges (orchestrate inline — do NOT let the model
grade itself). For each answer in the run directory, spawn 3 independent Task agents (or
fewer for a quick pass). Each judge gets ONLY: the prompt, the answer, and the case's
rubric — and is explicitly told it is judging in isolation, with no knowledge of other
judges' scores or any prior evaluation (this prevents anchoring). Then aggregate:
tags): a category where judges
systematically disagree with the rubric is a real weakness — on one real eval, a whole
category scored 12.5% precision and exposed a systematic misclassification that a single
grader would have missed.For the rubric-scoring mechanics (LLM-as-judge thresholds, llm-rubric), you can also
compose with the promptfoo-evaluation skill — point its providers at the same endpoint.
This harness's blind-judge method and promptfoo's rubric assertions are complementary: use
promptfoo for fast per-case pass/fail gating, blind judges for precision on a category you
suspect is weak. Full method: references/quality_blind_judge.md.
Keep it OUTSIDE this bundle (e.g. ~/.llm-eval/usecases.json) so it survives skill updates
and never lands in a public repo. It's a plain JSON list — version it in a private repo to
accumulate a regression suite over time:
[
{"id": "refund-window", "prompt": "A customer asks for a refund 20 days after purchase. Reply as support.",
"rubric": "1.0 if it correctly cites the 30-day refund window; 0.0 if it refuses or invents a different window.",
"tags": ["support", "policy"]},
{"id": "lru-cache", "prompt": "Implement an LRU cache in Python with O(1) get/put.",
"rubric": "1.0 if get and put are both O(1) via dict + doubly linked list and the self-test passes.",
"tags": ["code"]}
]
assets/example_usecases.json is a starter you can copy. Only id and prompt are required;
rubric, expected, and tags make judging sharper.
When the user says "evaluate / benchmark this model", the typical flow is:
/v1/chat/completions) or Anthropic-Messages
(/v1/messages)? Hit GET /v1/models or read the vendor docs; don't assume. This decides
which probes apply (protocol probe is Anthropic-only). Remember the listing is incomplete
evidence either way (disciplines §9).--output JSON to a run directory.For tests that will run repeatedly against a live system — deployment gates, resident canaries, fault-injection mocks, and the monitoring statistics that lie — see references/production_testing_patterns.md.
After a run, offer the natural follow-ups:
Evaluation complete for <model>.
Options:
A) Render an HTML dashboard of the results — compose with a visualization skill (Recommended if sharing)
B) Compare against another model — same probes, side-by-side
C) Add the failing cases to ~/.llm-eval/usecases.json as a permanent regression guard
D) Done — the numbers answer the question