Skip to content

fix(llm): model_map params erreichen die Wire-Ebene + Dream-Degenerations-Fixes - #39

Closed
W4-NERF wants to merge 8 commits into
GottZ:rootfrom
W4-NERF:fix/model-params-wire-passthrough
Closed

fix(llm): model_map params erreichen die Wire-Ebene + Dream-Degenerations-Fixes#39
W4-NERF wants to merge 8 commits into
GottZ:rootfrom
W4-NERF:fix/model-params-wire-passthrough

Conversation

@W4-NERF

@W4-NERF W4-NERF commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

fix(llm): model_map params erreichen die Wire-Ebene + Dream-Degenerations-Fixes (v5.1.0-Basis)

Problem (v5.0.0-Regression + Nemotron-Degeneration, prod-beobachtet 2026-08-22/24)

Seit der v5.0.0-Backend-Pool-Migration schlugen dream-eval und query-temporal
(translate-Rolle) mit unexpected end of JSON input fehl. Zusätzlich degenerierte
Nemotron 3.5 Lightning (vLLM/LiteLLM) auf dichten technischen Blöcken. Symptome:

  • dream: response hit the output cap — truncated JSON mit num_predict: 600,
    completion_tokens: 12000links_parsed: 0 (stiller Fehlschlag, kein WARN)
  • temporal LLM fallback failed, no temporal expansion available
    temporal: invalid JSON: unexpected end of JSON input
  • Nemotron-Degeneration: keywords too few (1) / no valid strings found auf
    Backslash-lastigem Content (BRADES-Migrations-Blöcke), 9× completion_tokens=1200,
    Endlos-Retry alle ~6 Min (workers=1 blockierte den Zyklus)
  • Eval-Cap-Hits: 21–28 Kandidaten ließen 1200er-Cap reißen (parse links: unexpected end)
  • Vor der Migration (Settings-basiert, dream.think=false): Ø ~4.700 Tokens/Eval,
    keine Fehler. Nach Migration: Ø ~12.000 Tokens, fast jeder Eval am Limit.

Root Cause

applyModelParams (llm/chain.go) verarbeitet aus model_map.<role>.params nur die
festen Options-Keys
(temperature, top_p, num_predict/max_tokens, think, …) und
verschluckt alle unbekannten Keys stillschweigend — insbesondere
chat_template_kwargs, das bei vLLM/LiteLLM-servierten Modellen (Nemotron 3.5
Lightning) der einzig wirksame Thinking-Schalter ist.

chatOpenAI (llm/client.go) übersetzt think:false stattdessen in das
OpenRouter-spezifische Feld reasoning:{enabled:false,exclude:true}. Das
ignorieren nicht-OpenRouter-Provider (vLLM/LiteLLM) → das Modell denkt weiter,
der Output läuft in den Token-Cap, strukturiertes JSON kommt abgeschnitten an.

Der dokumentierte Vertrag — "per-backend tuning belongs in the serving row's
model_map params … which override this default at dispatch" (Kommentar in
dream/evaluate.go, DreamOptions) — galt damit nur für die festen Keys.

Fix

Generic Passthrough statt Config-Workaround:

  1. Options bekommt ein Extra map[string]any Feld (json:"-" — Ollamas
    /api/chat options-Block bekommt bewusst keine fremden Keys).
  2. applyModelParams sammelt unbekannte Params in Options.Extra, statt sie
    zu droppen. chat_template_kwargs wird explizit als bekannter Passthrough-Key
    geführt (dokumentiert, dass er nur auf dem OpenAI-Wire wirkt).
  3. chatOpenAI merged Options.Extra nach dem Marshal in den Request-Body —
    vor applyOpenAIBodyExtras, sodass die Backend-extra_body bei
    Key-Kollision weiter Vorrang behält (last write wins, "extra_body only
    tightens").

Damit funktioniert die bestehende model_map-Konfiguration wie dokumentiert:

{
  "dream": {
    "model": "DGX_nemotron_3_5_lightning",
    "params": {
      "max_tokens": 600,
      "think": false,
      "chat_template_kwargs": { "enable_thinking": false }
    }
  }
}

Dream-Budget: dream.num_predict (v5.1 hot-Setting) statt Code-Konstante

Zweites, unabhängiges Problem: Nemotron 3.5 Lightning ignoriert die
"Maximum 5 entries"-Instruktion und listet alle Kandidaten (~40 Tokens pro
pretty-printed Array-Eintrag). Bei 10+ Kandidaten riss der 600er-Cap das JSON
mitten im Array ab (prod: Block 01a0120d, 2× "output cap — truncated JSON"),
bei 21–28 Kandidaten auch der 1200er-Cap (parse links: unexpected end of JSON input, prod 2026-08-24). v5.1.0 bringt dream.num_predict als hot-Setting
dieser PR dokumentiert die Prod-Erkenntnisse im Code-Kommentar und empfiehlt
dream.num_predict=1600 für Nemotron-Installationen (~30 Einträge, weit unter
der 12000-Degenerations-Schwelle). Modelle, die die 5-Einträge-Instruktion
befolgen, bleiben weit darunter (gemessen 61–315 Tokens).

Keyword-Phase: Cap hart (CapLocked) + 12h-Cooldown bei Fehlschlag

Drittes, wieder anderes Problem (prod 2026-08-22, Block 01a02b24 "Migration
BRADES-SERVER"): Die Keyword-Phase nutzt dieselbe Rolle RoleDream wie die
Eval-Phase
— das model_map dream.max_tokens-Override (1200, für Eval
gedacht) blähte dadurch auch die Keyword-Extraktion auf (Code-Default 200).
Bei einem Block mit vielen Backslash-Pfaden (E:\brades-export\ etc.)
degenerierte Nemotron und generierte 9× deterministisch bis zum Cap
(completion_tokens: 1200, "keywords too few (1)"). Da der Fehlschlag nur
einen 5-Min-Cooldown auslöste, wurde der Block alle ~6 Min neu gepickt —
ein Endlos-Retry, der den Dream-Zyklus (workers=1) blockierte.

Zwei Fixes:

  1. Options.CapLocked: markiert eine Phase mit hartem Budget. Die
    Keyword-Phase setzt es (keywordOptions: NumPredict 200, CapLocked) —
    applyModelParams wendet num_predict/max_tokens-Overrides dann nicht
    an. Eine Extraktions-Phase behält damit immer ihr Budget, egal was die
    Serving-Row für andere Phasen derselben Rolle konfiguriert.
  2. CooldownKeywordFailMinutes = 12h: nach 3 fehlgeschlagenen
    Keyword-Versuchen parkt der Block 12h statt 5 Min. Transiente Fehler
    (Preempt, GPU-Outage) bleiben beim 5-Min-Pfad.

Deterministischer Keyword-Fallback + Tokenize-Fix

Viertes Problem (prod 2026-08-23): Auch mit CapLocked-400 degenerierte Nemotron
auf dichten technischen Blöcken (Windows-Pfade) bis zum Cap → no valid strings found. Fixes:

  1. GenerateKeywords fällt nach KeywordsMaxRetries auf den deterministischen
    ExtractKeywords-Tokenizer zurück
    — der Block wird trotzdem gesucht/evaluiert
    statt geparkt; der 12h-Cooldown greift nur, wenn selbst der Tokenizer nichts findet.
  2. tokenize spaltet an allen Nicht-Buchstaben-/Ziffern-Zeichen (außer
    Bindestrich): C:\Users\Brades\.ssh\authorized_keys wird zu suchbaren
    Begriffen statt einem Riesen-Token. Verifiziert: BRADES-Content liefert
    [authorizedkeysfile streaming-pc admin-user-falle pipeline-server ...].

Tests: TestCapLockedResistsModelMapOverride pinnt den CapLocked-Vertrag,
TestGenerateKeywords_FallbackAfterRetries den Fallback, TestExtractKeywords_BradesContent
den Tokenize-Fix auf Prod-Content.

⚠️ Wichtig: Diese Konfiguration ist NUR für Modelle nötig, die standardmäßig denken

Die think:false/chat_template_kwargs.enable_thinking:false-Konfiguration ist
nur erforderlich, wenn das Serving-Modell per Default Reasoning/Thinking
emittiert
— konkret nicht für Qwen 3.6/3.8 (27b/35b), die ohne Thinking
antworten. Betroffen war Nemotron 3.5 Lightning (30B A3B NVFP4) über
vLLM/LiteLLM, das standardmäßig Chain-of-Thought produziert.

Für Qwen-Modelle kann die Konfiguration entfallen bzw. wäre nur eine
Optimierung; für Nemotron und andere Default-Thinking-Modelle ist sie
zwingend, sonst fressen Reasoning-Tokens das Output-Budget und JSON-Antworten
werden abgeschnitten.

Warum KEIN neues Settings-System (bewusste Entscheidung)

Die v5.0.0-Migration (133) hat die alten Tuple-Settings (dream.think,
chat.* etc.) bewusst retired — die Architektur ist jetzt Backend-zentriert
(context_backends mit model_map/extra_body). Diese Konfiguration als
globale Settings wieder einzuführen wäre ein Schritt zurück und würde die
Per-Rolle-/Per-Backend-Differenzierung (chat vs. dream vs. translate) verlieren.
Der Passthrough macht das vorhandene model_map-Params-System vollständig
funktionsfähig — der richtige Ort für Backend-spezifische Knöpfe.

Tests

  • TestModelMapExtraPassthrough (neu, llm/wire_test.go): pinnt den Vertrag —
    chat_template_kwargs landet in Options.Extra, geht auf die Wire-Ebene,
    Backend-extra_body gewinnt bei Kollision, think:false wird gesetzt.
  • TestCapLockedResistsModelMapOverride (neu): CapLocked-Phase behält ihr
    Budget trotz model_map-Override; Kontrolle ohne Lock übersteuerbar.
  • TestGenerateKeywords_FallbackAfterRetries (neu): 3 LLM-Calls, dann
    deterministischer Fallback, >= MinKeywords.
  • TestExtractKeywords_BradesContent (neu): Tokenize-Fix auf Prod-Content.
  • TestEvaluateRelationships_CapHitStillErrors (dream): Cap-hit-Pfad bleibt
    Observability-only.
  • Bestehende Wire-Tests (Temporal/Synthesize/ChatChain) + dream-Tests grün
    (auf v5.1.0-Basis rebased).
  • go vet + golangci-lint: 0 Issues.

Verifikation (prod, 2026-08-22/24, v5.1.0 + Fixes)

  • Dream: Zyklen links_evaluated/links_written 5/5, 5/5, 6/6 — keine stillen
    0-Fehlschläge. Tokens 12.000 → 162–865.
  • Keyword-Loop gestoppt: BRADES-Block (vorher 9×1200-Token-Degeneration) im
    12h-Cooldown; kein Endlos-Retry mehr. 24h: 790/835 eval-Erfolg (94,6%),
    keyword-Fehler auf ~6 Blöcke begrenzt (Retry-Versuche, Fallback greift).
  • Cap-Hits (21–28 Kandidaten) durch dream.num_predict=1600 adressiert.
  • Temporal: Live-Query mit Zeitbezug liefert saubere temporal-Auflösung,
    kein Fallback-Fehler mehr.
  • v5.1.0: rebased, deployed, erster Zyklus 5/5 Links, Health ok.

Beobachtungszeitraum

Erneuter Beobachtungszeitraum nach Merge dieses PRs: 48 h (mindestens
10 Dream-Zyklen + mehrere temporal-Queries), danach Abschluss-Urteil.

applyModelParams verschluckte unbekannte Params aus model_map.<role>.params
(chat_template_kwargs etc.), chatOpenAI sendete stattdessen das
OpenRouter-only reasoning-Feld. Nicht-OpenRouter-Provider (vLLM/LiteLLM,
Nemotron 3.5 Lightning) denken dadurch weiter -> Output laeuft in den
Token-Cap, strukturiertes JSON kommt abgeschnitten an (dream-eval
links_parsed:0, query-temporal invalid JSON).

Fix: Options.Extra (json:"-", Ollama-options bleiben sauber), Sammeln in
applyModelParams, Merge in chatOpenAI VOR applyOpenAIBodyExtras (Backend-
extra_body behaelt Vorrang). TestModelMapExtraPassthrough pinnt den Vertrag.
…-Anpassung

Nemotron 3.5 Lightning ignoriert 'Maximum 5 entries' und listet alle
Kandidaten (~40 Tokens/Eintrag). Bei 10+ Kandidaten riss der 600er-Cap das
JSON mitten im Array (prod: Block 01a0120d, 2x output cap). 1200 deckt ~25
Eintraege ab, weit unter den 12000 der Reasoning-Poison-Aera; model_map kann
weiter uebersteuern.
…rd-Fehlschlag

Zwei strukturelle Fixes gegen den BRADES-Loop (prod 2026-08-22):
1. CapLocked: dream-keywords nutzt RoleDream, daher ueberschrieb das
   model_map dream max_tokens (1200, fuer Eval) auch die Keyword-Phase
   (Default 200). Nemotron degenerierte bei Backslash-lastigem Content
   bis zum Cap (9x completion_tokens=1200, 'keywords too few (1)') und
   blockierte den Zyklus (workers=1). CapLocked haelt das Extraktions-
   Budget hart; applyModelParams respektiert es bei num_predict/max_tokens.
2. CooldownKeywordFailMinutes=12h: nach 3 fehlgeschlagenen Keyword-
   Versuchen parkt der Block 12h statt 5 Min, statt alle ~6 Min neu
   gepickt zu werden (Endlos-Retry). Transient-Pfad (Preempt etc.)
   bleibt bei 5 Min.
Tests: TestCapLockedResistsModelMapOverride pinnt den Vertrag.
…eywords + Escaping)

Prod 2026-08-23: CapLocked 200 war zu knapp. 38/49 (78%) der
dream-keywords-Extraktionen liefen in den 200er-Cap (signatur:
completion_tokens=200, response abgeschnitten wie {"rclone serve sftp"),
-> 'keywords too few (1)'. Nemotron 3.5 Lightning benoetigt bei langem
Content + Windows-Pfad-Escaping >200 Tokens fuer das 5-8er-JSON.
400 deckt ~8 Keywords inkl. Escaping ab, bleibt weit unter der
1200-Degenerations-Schwelle. CapLocked bleibt aktiv (kein model_map
Override).
Nemotron 3.5 Lightning degeneriert bei dichten technischen Blaettern
(Windows-Pfade, lange Schemata) unabhaengig vom Budget: 200 UND 400
Token-Caps laufen leer ab ('no valid strings found', prod 2026-08-23,
Block 01a02d3e Transfer-Dashboard). Weiteres Budget-Anheben wuerde die
alte 1200-Degenerations-Schwelle zurueckbringen.

Fix: Nach KeywordsMaxRetries fehlgeschlagenen LLM-Versuchen faellt
GenerateKeywords auf den deterministischen ExtractKeywords-Tokenizer
zurueck (Stopwort-Filter, laengste/rarest Terms, Titel-Bonus). Der Block
wird dann trotzdem in diesem Zyklus gesucht/evaluiert statt geparkt, und
der 12h-Cooldown greift nur noch, wenn selbst der Tokenizer nichts findet.

Test: TestGenerateKeywords_FallbackAfterRetries pinnt den Vertrag (3
LLM-Calls, dann Fallback, >= MinKeywords).
…uchbar)

Der deterministische Fallback (d4ba54e) lieferte fuer dichte technische
Bloecke weiterhin unbrauchbare Keywords: tokenize behandelte
'C:\Users\Brades\.ssh\authorized_keys' als EINEN Riesen-Token
('authorizedkeysfile=c:\users\brades...'), und 'server->streaming-pc'
blieb ein Token. Damit fand die RRF-Suche nichts, der Block wurde
trotzdem geparkt (prod 2026-08-23, Bloecke 01a03062/01a02b24/01a03040).

Fix: tokenize spaltet jetzt an ALLEN Nicht-Buchstaben-/Ziffern-Zeichen
(ausser Binde-strich): Pfade, key=value, user@host, -> werden saubere
Suchbegriffe. Verifiziert: BRADES-Content liefert jetzt
[authorizedkeysfile streaming-pc admin-user-falle pipeline-server
doppel-session] statt Pfad-Muell.

Test: TestExtractKeywords_BradesContent pinnt den Fallback auf dem
Prod-Content.
Prod-Audit 2026-08-24: 3 Cap-Hits in 24h auf dream-eval mit 21-28
Kandidaten (prompt_tokens 5719-7256) - 1200 truncatete das JSON
mitten im Array ('parse links: unexpected end of JSON input'). 1600
deckt ~30 Eintraege ab (~40 Tokens/Eintrag + Struktur), bleibt weit
unter der 12000-Degenerations-Schwelle. Bloecke mit >30 Kandidaten
bleiben selten; die bekommen Cooldown + Retry.
Prod 2026-08-25: Ornith 1.5 35B antwortet auf die Array-Anforderung
konsistent mit einem JSON-OBJEKT ({"Phrase":"description",...})
statt eines Arrays. parseKeywords scheiterte (Erfolgsquote ~16%,
5/30). Fix:
1. Prompt expliziter: 'NO object, NO keys', Antwort muss mit [ starten
   und mit ] enden.
2. Parser: Objekt-Drift erkennen ({...}) und die WERTE als Keywords
   nehmen (die Schluessel sind oft lange Satzfragmente; die Werte die
   Konzepte). Fallback bleibt fuer kaputte Escapes.

Tests: TestParseKeywords_ObjectDrift (Werte statt Schluessel),
TestParseKeywords_ArrayStillWorks (Array-Pfad unberuehrt).
GottZ pushed a commit that referenced this pull request Aug 25, 2026
…ions-Fixes (#39)

Squash der PR-Strecke fix/model-params-wire-passthrough (8 Commits, W4-NERF).

llm: applyModelParams verarbeitete aus model_map.<role>.params nur die
festen Options-Keys und verschluckte alle unbekannten stillschweigend —
insbesondere chat_template_kwargs, bei vLLM/LiteLLM-served Modellen
(Nemotron 3.5 Lightning) der einzig wirksame Thinking-Schalter; chatOpenAI
übersetzt think:false nur in das OpenRouter-spezifische reasoning-Feld.
Fix: Options.Extra (json:"-") sammelt unbekannte Params; chatOpenAI merged
sie nach dem Marshal und VOR applyOpenAIBodyExtras in den Body — die
Backend-extra_body behält bei Kollision Vorrang, die zdr-Enforcement
bleibt das letzte Wort. Ollamas options-Block bekommt bewusst keine
fremden Keys (Extra wird auf dem Ollama-Pfad nie gelesen).

llm: Options.CapLocked markiert Phasen mit hartem Budget —
num_predict/max_tokens-Overrides aus der Serving-Row greifen dort nicht.

dream: Keyword-Phase nutzt CapLocked (Budget 200→400, von der Eval-Row
nicht mehr aufblähbar); nach erschöpften LLM-Retries deterministischer
ExtractKeywords-Fallback statt Parken; erst wenn auch der nichts findet,
12h-Cooldown (CooldownKeywordFailMinutes) statt 5-min-Spin (prod: BRADES-
Block blockierte den Zyklus alle ~6 min bei workers=1). tokenize trennt
Pfad-/Symbol-Cluster (\ / . = @ -> :), damit dichte Windows-Pfad-Blöcke
suchbare Terme liefern; Objekt-Drift-Parser nimmt bei {"k":"v"}-Antworten
die Values als Konzepte (Ornith 1.5); Keyword-Systemprompt verschärft
(Array-Pflicht). Eval: DefaultNumPredict-Kommentar dokumentiert die
Nemotron-Empfehlung dream.num_predict=1600; Cap-Hit bleibt reine
Observability (Parse-Fehler + Cooldown unverändert, Test gepinnt).

Prod-Evidenz 2026-08-22/24: dream-eval + query-temporal "unexpected end
of JSON input" seit v5.0.0-Pool-Migration; 9× completion_tokens=1200
"keywords too few (1)"; Ø 12.000 statt 4.700 Tokens/Eval.
GottZ added a commit that referenced this pull request Aug 25, 2026
Der ModelMapEditor zeigte params nur als passives "+params (edit via
CLI)"-Badge — seit dem generischen Wire-Passthrough (#39) ist jeder
params-Key bedeutungstragend (chat_template_kwargs, think, max_tokens,
Provider-Knobs), also editiert das Web sie jetzt vollständig: pro Row ein
params-Toggle mit JSON-Objekt-Textarea (Mono, Platzhalter zeigt die
Nemotron-Empfehlung aus #39). Nur valides JSON-Objekt erreicht onchange —
invalide Drafts bleiben in der Textarea stehen (roter Rand + Hinweis),
der letzte valide Stand gilt; leer/{} entfernt die params der Row. Die
Projektion lebt als exportiertes parseParamsDraft im module-script
(mount() im Test-Env nicht verfügbar — VaultForm-Muster: pure Funktion +
Server-Render-Assertion). Datenkette (rowsToModelMap, ModelSpec.params,
Server map[string]any ohne Key-Restriktion) trug params bereits.

Doku: development.md Backends-Sub-Route-Beschreibung erweitert.
FE-Gates: vitest 978/978, stylelint+inline-gate+svelte-check+tsc clean.
GottZ added a commit that referenced this pull request Aug 25, 2026
@GottZ

GottZ commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Merged in v5.3.0 — danke für die saubere Analyse-Strecke, die Prod-Evidenz im PR-Body war Gold wert.

Deine 8 Commits sind als ein Squash-Commit mit deiner Autorschaft gelandet: 621b0cd (fix(llm): model_map params erreichen die Wire-Ebene + Dream-Degenerations-Fixes (#39)). Obendrauf drei Härtungs-Commits aus dem Review plus ein Folge-Feature:

  • 6176100 — der BRADES-Test las /tmp/block_content.txt (in CI stiller Skip, lokal beliebiger Fremdinhalt, und die echte Fixture wäre privater Block-Content im Repo) → synthetische Inline-Fixture mit derselben Fehlerform.
  • a8f9aa7 — der Objekt-Drift-Salvage nahm die Map-Values in Go-Zufallsreihenfolge und ungekappt (empirisch: 20 Paare → 20 Keywords → 20 RRF-Suchen) → sortierte Key-Reihenfolge, Kappung bei MaxObjectDriftKeywords=8 (die Obergrenze, die der Array-Prompt selbst fordert).
  • 5584aa9 — das Warn-Log sagte noch „transient cooldown", der Code parkt seit deinem Umbau 12h → Text + cooldown_minutes-Feld sagen jetzt den echten Zustand.
  • b281a7c — Folge deines Passthroughs: der Web-ModelMapEditor editiert model_map-Params jetzt pro Rolle als freies JSON-Objekt (das alte „+params (edit via CLI)"-Badge ist raus).

Credit steht in CONTRIBUTORS.md (f3acfde). Verifikation vor dem Merge: volle go-short-Suite, Integration dream+llm, vitest 978/978, e2e-visual 447/447.

Closing als merged — der Squash taucht für GitHub nicht als Branch-Merge auf, die Commits sind seit dem v5.3.0-Push auf root.

@GottZ GottZ closed this Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants