Files
nanobot-runtime/results/2026-09-20_hodnoceni-beziciho-modelu-nanobot.md
2026-09-20 14:15:09 +02:00

252 lines
18 KiB
Markdown
Raw 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.
# Jak hodnotit běžící LLM model v nanobotu
Deep research, 2026-09-20. Otázka: jak navrhnout hodnocení modelu, který **už běží** jako
agent v nanobotu — ne offline benchmark kandidáta před nasazením.
## Shrnutí
Běžící model se hodnotí z **protokolů, které po sobě zanechal**, ne z re-runu — a musí se
měřit dvěma známkami, ne jednou: úspěch výsledku (outcome) a kvalita trajektorie, protože
ty dvě se rozcházejí [1]. Hlavní metodologický problém v nanobotu ale není statistika ani
judge — je to, že **nanobot sám je harness**, a ten se mění každý den (skilly, MEMORY.md,
SOUL.md, tool schémata). Změna skóre mezi dvěma okny je proto ve skutečnosti nejčastěji
změna scaffoldu, ne modelu — harness umí posunout výsledek o 734 procentních bodů při
identických vahách [2][3]. Praktický závěr: neporovnávat skóre v čase, ale **párovat na
stejných úlohách**, drift hlídat zvlášť jako signál, a hodnotit **pass^k**, ne pass@1
model se 90 % pass@1 má při osmi opakováních ~43 % [4].
## Zjištění
### 1. Co „běžící model" mění
Tři strukturální rozdíly proti offline evalu:
- **Nemůžeš opakovat vstup.** Produkční snímek (co uživatel poslal) se nedá znovu
vyvolat. Zbývá replay ze záznamu — a ten je věrný jen do chvíle, kdy se změní nástroje
nebo stav okolí [5].
- **Váhy nejsou zmrazené.** Vendor mění checkpoint pod stejným ID; pinning ID zamrazí
jen část chování, serving stack, batching a decoding se mění bez čísla verze [6][7].
Detekce: canary prompt s hashí, output-shape metriky [8][9].
- **Hodnotíš i sebe.** Model, který dělá evaluaci, je tentýž model, který je hodnocený —
a LLM judge favorizuje vlastní styl výstupu (self-preference bias, mechanismus =
perplexity familiarita, ne vědomé rozpoznání) [10]. To se týká přímo mě: každý eval,
který spustím v turnu na `deepseek`, je self-eval.
### 2. Jaké důkazní zdroje už v nanobotu jsou
Tohle je konkrétní inventura téhle instance (ne obecná rada):
| Zdroj | Co v něm je | Použitelné pro |
|---|---|---|
| session JSONL (`<config-dir>/sessions/<workspace-id>/`) | plná historie tahů včetně `tool_calls`, `reasoning_content` [11] | trajectory eval, failure clustering |
| `memory/history.jsonl` | denormalizované distilly tahů | levná klasifikace témat |
| `db/ollama_usage.sqlite` (`samples`) | **přesné per-model request counts** + % kvóty | cost/usage scorecard |
| `.nanobot/tool-results/<session>/call_*.txt` | raw tool výstupy (cap 16 000 znaků) | forenzika selhání tool callu |
| `tasks/done/`, `tasks/archive/` | frontmatter s `model:` a `duration_seconds` | **attribuce výsledku na model** |
| `results/` | reporty včetně deep-research výstupů | outcome grading |
| `reflect/findings.jsonl` | už existující store klasifikovaných vzorců chyb | failure clustering, hotový |
| `log/reflect_cron.log` | běhy nočního scriptu | provozní telemetrie |
| webui `token-usage.json` | tokeny + requesty po source (user/api/cron/dream/system) | rozpad spotřeby kvóty |
**Co v nanobotu chybí:** žádná OpenTelemetry/GenAI instrumentace — grep na `gen_ai`,
`otlp`, `opentelemetry` v `nanobot/` nic nenajde. Standard pro agentní traces přitom
existuje: spany `invoke_agent``chat``execute_tool`, atributy `gen_ai.request.model`,
`gen_ai.usage.input_tokens`/`output_tokens`, metrika `gen_ai.client.operation.duration`
[12][13]. Je to stále **pre-stable, bez 1.0** [12] — ale tvar je dobrý a je to jediná
cesta, jak dostat trajectory eval bez ad-hoc parsování JSONL. Nanobot má naštěstí
`AgentHook` s `after_iteration` a `usage` [14] — což je přesně to místo, kam OTel span
patří, aniž by se musel sáhnout do core loopu.
### 3. Co měřit: dvě známky, ne jedna
Outcome-only hodnocení **řadí modely ve špatném pořadí** [1]. Konkrétně:
- Správná odpověď po rozbitém plánu, který trefil výsledek náhodou (lucky pass).
- Špatná odpověď ze správného plánu, kterému poslední tool vrátil 503 (unlucky fail).
- Policy/konvenční porušení, které výsledek nezmění — τ-bench zavedl policy adherence
jako first-class metriku právě proto: agent, který vrátí peníze bez ověření identity,
má outcome pass a policy fail [15].
Trajektorie se skóruje po vrstvách: kvalita plánu, volba nástroje (včetně **správného
nerozhodnutí volat**), správnost argumentů, recovery po chybě, finální odpověď [1].
U nás je „správné nerozhodnutí volat" přesně to, co testuje scénář S6 v
`projects/ai/artifacts/ollama-toolcall-test.py` — a co vyřadilo `deepseek-v3` [16].
**Klíčová metrika je pass^k, ne pass@1.** τ-bench ji zavedl jako reliability measure:
podíl úloh, kde uspěje **všech** k opakování. Neklesá ke středu, ale dolů, protože
přesně odhaluje nekonzistenci [15][4]. Čísla: GPT-4o v retailu pod 50 % na pass^1, ale
pod 25 % na pass^8 [15]; model s 90% pass@1 padá na ~43 % při pass^8 a ~19 % při pass^16
(při nezávislých pokusech) [4]. Model, který získá pass@1 a ztratí pass^4, je pro provoz
regrese, i když se leaderboard pohnul nahoru [4].
### 4. Statistika: velikost vzorku a co je ještě šum
Tohle je nejvíc podceňovaná část. Konkrétní čísla z runnable receptu [17]:
- **Wilson interval**, ne Wald. Wald na 7/10 hlásí [0,42; 0,98], Wilson [0,40; 0,89] —
Wald slibuje jistotu, kterou data neunesou [17][18].
- **Clusterované SE.** Když se skóre měří na víc otázkách z jednoho tématu/ session,
naive SE podstřeluje; v příkladu vyšlo clusterované 1,45× naive. Čím větší cluster,
tím větší nafouknutí — v některých případech přes 3× [17]. U nás jsou clustery
přirozeně **session a reminder/note kategorie**.
- **Párový test (McNemar).** Vzorek 200 párových úloh detekoval +9,5 pb rozdíl na
p=0,032; nezávislý design by na totéž potřeboval **388 úloh na rameno** [17].
Párování je tedy 2× úspora vzorku — a to je přesně argument pro replay stejných úloh
na obou modelech místo porovnávání dvou náhodných řezů provozem.
- **„27 z 30" je statisticky téměř ticho** [19]. Eval sety o 50200 příkladech, které
hlásí signifikanci, ji většinou nemají [20].
Praktický důsledek pro nás: náš reálný objem je **desítky** relevantních tahů za den, ne
tisíce. To stačí na detekci hrubých rozdílů (S6 fail, CJK drift, 2× latence), ale **ne**
na rozlišení 23 pb rozdílu ve kvalitě. Cokoli jemnějšího je u nás měření šumu.
### 5. Jak to nasadit bez re-runu
Tři režimy a co který umí [21]:
| Režim | Co dostaneš | Co nedostaneš |
|---|---|---|
| **Replay ze záznamu** | chování na reálné distribuci vstupů, nulové riziko | žádný signál o uživatelském výsledku |
| **Shadow** | totéž, ale živě, paralelně s produkcí | pořád žádný user-outcome signál — nikdo jeho odpověď nevidí |
| **Canary / A/B** | skutečný outcome uživatele | riziko na řezu provozu |
Pro nanobot je **canary triviálně dostupný a to je jeho největší výhoda**: `detach
--model <preset>` pustí úlohu izolovaně na jiném presetu, cron joby mají vlastní preset,
`/model` přepíná za běhu. Assignace varianty se ale musí dělat **hashem stabilního ID
(session/chat), ne per-request náhodou** — jinak se model v rámci jedné konverzace
přepne uprostřed a test se kontaminuje crossoverem [21]. A promote/rollback se gatuje
na **deltě proti připnutému baseline s tolerancí**, ne na absolutním čísle; je to otázka
signifikance, ne verdiktu [21].
Pozor na slepou uličku: standardní canary controller (error rate, p99) na LLM regresi
**nikdy nezabliká**, protože ta nemá error code — horší model vrátí HTTP 200, v limitu, s
plynulou a sebevědomou chybnou odpovědí. Signál se musí **vyrobit** (LLM judge na 110 %
provozu) a teprve ten skóre je rollback trigger [21].
### 6. Drift: dvě různé věci se stejným jménem
Rozlišovat **input drift / output drift / judge-score drift** [8]:
- *Input drift* = uživatelé začali posílat něco, na co systém nebyl testovaný. Bývá první
signál reálného failure clusteru [8].
- *Output drift* = odpovědi změnily tvar. **Odpojený od input driftu je to nejhlasitější
signál „model se nám změnil pod rukama"** — typicky tiché přetrénování u providera [8].
U nás konkrétně měřitelné: podíl odpovědí s CJK znaky, refusal rate, tool-call rate,
platnost struktury, medián délky odpovědi, poměr thinking:response tokenů.
- *Judge-score drift* = nejblíž kvalitnímu canary, ale je downstream od stability
judge — judge se musí pinovat zvlášť [8].
**Referenční okno je verzovaný artefakt**, ne „včera" nebo „minulý týden": je to okno, proti
kterému byl aktuální model validovaný, content-hashed a svázané s releasem [8]. Bez toho
se párové srovnání rozpadá.
### 7. Pasti specifické pro tenhle případ
Tohle je jádro odpovědi — čtyři věci, které se u nás pokazí, i kdyby metodika byla jinak
správná:
1. **Harness churn je u nás dominantní, ne okrajový.** `git log` v workspace má záznam
skoro každý den (naposledy `974b37a` 2026-09-20 12:21) a mění se přitom skilly,
MEMORY.md, SOUL.md, AGENTS.md — tedy systémový prompt, instrukce i tool popisy.
Harness sensitivity je 734 pb při identických vahách [2][3]; prompt a tool popisy
samy o sobě 515 pb [3]. **Porovnávat skóre napříč týdny je proto neplatné.** Párování
musí být na úrovni úlohy, ne období.
2. **Self-eval.** Vyhodnocovací smyčka běží na stejném modelu, který hodnotí. Mitigace
jsou známé a levné [10]: cross-model judging (jiná rodina než contestant), double-swap
s filtrem konzistence (swap consistency je u judge modelů jen 70,577,3 %, tj. hrubě
každý čtvrtý verdikt se překlopí jen podle pořadí [10]), length-controlled scoring
(všechny testované LLM mají silný length bias, +17 pb nad lidskými hodnotiteli [10]).
Prakticky: hodnotit `deepseek` judge modelem z jiné rodiny (`glm`), a každou dvojici
proběhnout dvakrát s prohozeným pořadím.
3. **Kvóta je součástí měření.** Ollama Cloud má max 3 paralelní dotazy a usage tiers
(Medium u `deepseek-v4.1-flash` vs High u `glm`); 4. souběžný dotaz čeká ve frontě a
to čekání spadne do wall-clocku [16][22]. Naměřená latence z paralelního běhu je proto
nadhodnocená a musí se měřit izolovaně.
4. **Sandbox brání čtení záznamů.** Session JSONL jsou mimo workspace
(`<config-dir>/sessions/<workspace-id>/`, migrace ve v0.3.5 [11]) a `restrict_to_workspace`
mi blokuje exec na `~/.nanobot/...` — ověřeno dnes, guard to odmítl. Scripty spouštěné
cronem (jako `reflect_auto.py`) přístup mají. **Eval harness tedy musí být script pod
cronem nebo `detach`, ne pár tool callů v mém turnu.**
## Rozpory a nejistoty
- **Harness sensitivity: čísla se rozcházejí.** „734 pb" [2] vs „1020 pb" [3] vs
„1020 pb, scaffold gap přes 28 pb" [23]. Rozsah je konzistentní, přesná hodnota
závisí na benchmarku a definici scaffoldu. Pro nás je podstatné, že efekt je **větší
než typická mezera mezi modely**, ne konkrétní číslo.
- **τ-bench je zmrazený.** Oficiální board je zamrzlý na modelech z konce 2024
(Claude 3.5 Sonnet 69,2 % retail / 46,0 % airline); každé číslo z let 202526 na
„τ-bench" je vendor self-report nebo successor τ2/τ3 [15]. Konkrétní čísla tedy
nepoužívat jako srovnávací bod — použitelná je **metodika** (pass^k, policy adherence,
user simulator), ne hodnoty.
- **OTel GenAI konvence jsou pre-stable**, bez 1.0, GenAI repo nemá tagovaný release,
každý agent span má status Development [12]. Doporučení „instrumentovat proti
`gen_ai.*`" je tedy investice do pohyblivého cíle. Alternativa OpenInference existuje
paralelně a trace z jedné konvence nepoplní atributy druhé [12].
- **Nemám ověřené, že session JSONL na téhle instanci skutečně obsahují `usage` per tah.**
Session manager ukládá `tool_calls`, `reasoning_content`, `thinking_blocks` [24];
token usage jde do separátního `token-usage.json` v webui dir [25]. Per-tah token
rozpad na thinking vs response, který bych chtěl pro verbositu, může být potřeba
dopočítat nebo vzít z response objektu providera — to jsem neověřil.
- **Sierra má komerční zájem na τ-bench** [15] — metodika je dobrá, ale je to benchmark
postavený tak, aby vyhovoval jejich typu agenta. Nekopírovat slepě.
- **„LLM judge ≈ lidský hodnotitel" je slabší tvrzení, než se cituje.** Kappa se často
reportuje bez konfidenčního intervalu [26]; agregátní korelace mohou maskovat rozptyl
[27]. Judge je pro nás nástroj na hrubé filtrování, ne ground truth.
## Konkrétní návrh pro nanobot
Kdybych to stavěl, minimální funkční varianta:
1. **Scorecard per tah, ne per model.** Skript (cron) přečte nové session JSONL a pro
každý tah zapíše do `db/eval.sqlite`: preset, tool cally, tokeny in/out,
thinking:response poměr, wall-clock izolovaně, stop_reason, počet iterací.
Bez tohohle je jakákoli další úvaha dohad.
2. **Připnutá párová sada ~50 úloh**, ne jednorázové testy: remind CRUD, note lookup,
wiki search, krátký multi-step edit, deep-research. Každá úloha má deterministický
check na stavu disku plus rubrici pro trajektorii. Spouští se **na obou presetech
z téhož commitu workspace** — jinak se měří scaffold.
3. **pass^k = 4** jako primární metrika, Wilson interval a McNemar na párových výsledcích.
Clusterovat na session, ne na tah.
4. **Deterministické checky první**, agent-as-judge až na to, co deterministicky ověřit
nelze. Judge z jiné modelové rodiny, double-swap, length-controlled.
5. **Drift watchdog** jako samostatný hlídač: input drift (délka promptu, jazyk, téma) a
output drift (CJK podíl, refusal rate, tool-call rate, poměr thinking:response) proti
připnutému referenčnímu oknu, s prahy nastavenými na toleranci false positives [8].
6. **Vlastní harness fingerpint.** Hash (systémový prompt + seznam skillů + tool schéma)
při každém běhu. Když se změní, scorecard se označí jako nepárovatelný s předchozím.
Tohle je u nás ta jediná věc, bez které je zbytek měření neplatný.
Krok 1 a 6 jsou přitom levné a dají se udělat hned; kroky 24 vyžadují tu párovou sadu,
což je ta skutečná práce.
## Zdroje
[1] Trajectory-level evaluation (outcome vs trajectory, 2×2 matice) — https://www.aievals.co/learn/agentic-evals/trajectory-vs-outcome
[2] Agent Harness Benchmark Score: The Point-Swing Data (734 pb) — https://alatirok.com/agent-harness-benchmark-score/
[3] AI Agent Benchmarks: Why Harness Sensitivity Matters — https://halmob.com/blog/ai-agent-benchmarks-harness-sensitivity-guide
[4] Pass^k: the metric that catches inconsistent agents — https://www.aievals.co/learn/agentic-evals/pass-k-and-consistency
[5] A/B Testing and Shadow Deployments for Agents (eval/regression boundary) — https://www.skillveris.com/learn/agent-evaluation-observability/a-b-testing-and-shadow-deployments-for-agents
[6] Your Model ID Is Not a Lockfile: Silent Updates and Reproducibility — https://www.zustis.com/blog/model-id-not-a-lockfile.html
[7] How to Handle AI Model Version Changes in Production (2026) — https://www.aimadetools.com/blog/ai-model-version-changes-production/
[8] Drift detection for production AI (input/output/judge-score, referenční okno) — https://www.aievals.co/learn/production/drift-detection
[9] How to Catch a Silent Model Upgrade: Version Pinning, Canary Prompts — https://dreaming.press/posts/how-to-catch-a-silent-model-upgrade-hosted-endpoint-drift.html
[10] LLM Judge Biases: Position, Verbosity, Self-Preference (swap consistency 70,577,3 %, length bias +17 pb, perplexity mechanismus) — https://ai-tldr.dev/learn/evaluation-safety/llm-as-judge/llm-judge-biases/
[11] nanobot-internals.md (sessions storage, v0.3.5 migrace) — `workspace/knowledge/nanobot-internals.md`
[12] OpenTelemetry GenAI Semantic Conventions: Tracing AI Agents in Production (invoke_agent/chat/execute_tool, pre-stable bez 1.0, OpenInference paralelně) — https://veraexmachina.com/tech/opentelemetry-genai-agent-observability-production/
[13] Inside the LLM Call: GenAI Observability with OpenTelemetry — https://opentelemetry.io/blog/2026/genai-observability/
[14] nanobot `nanobot/agent/hook.py` (AgentHook, `after_iteration`, `usage`) — `tmp/nanobot`
[15] Tau-Bench 2026: Customer-Service Agent Eval, Methodology, Tiers (policy adherence, pass^k, zamrzlý board, vendor interest) — https://benchmarkingagents.com/tau-bench/
[16] projects/ai/memory.md (tool-call testy, S6 over-eager calling u deepseek-v3) a develop/knowledge.md (max 3 paralelní dotazy, usage tiers) — `workspace/`
[17] Error Bars for LLM Evals: Wilson, clustered SE, párový McNemar, power (200 párových vs 388 na rameno) — https://www.aievals.co/cookbook/adding-error-bars
[18] LLM Eval Sample Size Confidence Intervals (Wilson math, „27 z 30 je téměř ticho") — https://qaskills.sh/blog/llm-eval-sample-size-confidence-intervals
[19] How Many Eval Examples Do You Need? Sample Size for LLM Tests — https://ai-tldr.dev/learn/evaluation-safety/evaluation-basics/llm-eval-sample-size/
[20] Your LLM Eval Is Lying to You: The Statistical Power Problem — https://tianpan.co/blog/2026/04/15/statistical-power-llm-evals
[21] How to Roll Out a New LLM in Production: Shadow vs Canary vs A/B (200 bez error code, hash stabilního ID, delta vs baseline) — https://dreaming.press/posts/how-to-roll-out-a-new-llm-shadow-vs-canary-vs-ab.html
[22] ollama.com/library/deepseek-v4.1-flash/tags (Medium Usage, 1M ctx, vision+tools+thinking) — https://ollama.com/library/deepseek-v4.1-flash/tags
[23] LLM Benchmark Methodology 2026: Reading Leaderboards (contamination, harness multiplier, governance types) — https://www.digitalapplied.com/blog/llm-benchmark-methodology-2026-contamination-leaderboard-guide
[24] nanobot `nanobot/session/manager.py` (ukládané klíče zpráv) — `tmp/nanobot`
[25] nanobot `nanobot/webui/token_usage.py` (workspace-scoped token telemetrie) — `tmp/nanobot`
[26] Your LLM-as-judge eval set is too small. Here is the math (kappa bez CI) — https://medium.com/@maya.andersson/your-llm-judge-eval-set-is-too-small-here-is-the-math-da21cdde24f1
[27] Beyond correlation: The impact of human uncertainty in measuring LLM-as-Judge — https://openreview.net/forum?id=E8gYIrbP00