Files
doctate/docs/ionos-llm-api.md
T
Brummel cbb072d0cc feat: Show LLM failure banner and retry button
When an LLM analysis fails, a `.analysis_failed.json` marker is created.
If no document exists yet and the auto-trigger is blocked by this
marker,
the case page must display a banner. This banner provides the failure
reason and a button to retry the analysis, ensuring users are not left
in a dead-end state.

The `read_failure_marker` function is made public to allow the web layer
to access this failure information. The `CasePageTemplate` is updated to
include `analysis_failed` data, which conditionally renders the new
`.failure-banner` HTML.

This change prevents cases from becoming unrecoverable due to transient
LLM errors.
2026-05-03 13:53:55 +02:00

11 KiB
Raw Blame History

Ionos LLM API: Quirks und Workarounds

Stand: 2026-05-03 — empirisch ermittelt gegen https://openai.inference.de-txl.ionos.com/v1/chat/completions. Verhalten kann sich aendern; Datum oben mit dem aktuellen Test-Befund abgleichen, bevor man auf diese Notizen baut.

TL;DR

  • Die Marketing-Doku unter docs.ionos.com/cloud/ai/... ist unvollstaendig. Die dort gezeigten Beispiele fuer Llama 3.1 405B FP8 laufen in der Praxis in einen Gateway-Timeout, weil sie wesentliche Parameter weglassen.
  • Die echte aktuelle API-Spezifikation liegt unter https://api.ionos.com/docs/inference-openai/v1/. Das ist eine Redoc-Seite mit eingebetteter OpenAPI-3.0.3-Spec — die einzige verlaessliche Quelle fuer unterstuetzte Parameter, Defaults und Limits.
  • Llama 3.1 405B FP8 hat einen vLLM-Tokenizer-Bug: das End-of-Turn-Token <|eot_id|> wird als Klartext-String assistant\n\n ausgegeben statt als Special-Token. Folge: der offizielle stop-Parameter mit <|eot_id|> feuert nie, das Modell laeuft bis max_tokens voll.
  • Empfohlene Loesung fuer Doctate: response_format: json_schema. Schema-erzwungenes Decoding stoppt natuerlich am schliessenden }, funktioniert modell-agnostisch und braucht keine Provider-spezifischen Workarounds.

So kommt man an die richtige API-Info

Verfuegbare Modelle abfragen

Die Marketing-Doku listet Modelle, ist aber nicht immer synchron mit dem Backend. Authoritative Liste:

curl -s -H "Authorization: Bearer $TOKEN" \
  https://openai.inference.de-txl.ionos.com/v1/models | jq .

Aktuell (2026-05-03) verfuegbar:

Modell Kategorie Anmerkung
meta-llama/Meta-Llama-3.1-8B-Instruct Chat schnell, klein
meta-llama/Meta-Llama-3.1-405B-Instruct-FP8 Chat siehe Quirks unten
meta-llama/Llama-3.3-70B-Instruct Chat stoppt sauber, <1s
mistralai/Mistral-Nemo-Instruct-2407 Chat ungetestet
mistralai/Mistral-Small-24B-Instruct Chat siehe Doctate-Memory
openai/gpt-oss-120b Reasoning stoppt sauber, vorherige Doctate-Wahl
BAAI/bge-m3, bge-large-en-v1.5 Embedding
sentence-transformers/paraphrase-multilingual-mpnet-base-v2 Embedding
meta-llama/CodeLlama-13b-Instruct-hf Code
Qwen/Qwen3-Coder-Next Code
lightonai/LightOnOCR-2-1B OCR
black-forest-labs/FLUX.1-schnell Bild

Aktuelle OpenAPI-Spec extrahieren

Die Redoc-Seite rendert die Spec clientseitig — openapi.json ist nicht unter einer stabilen URL erreichbar, sondern liegt eingebettet im HTML als JavaScript-Variable __redoc_state. Extraktion mit Brace-Balancing, weil ein Regex auf </script> zu viel mitnimmt:

import json, re
import urllib.request

html = urllib.request.urlopen(
    "https://api.ionos.com/docs/inference-openai/v1/").read().decode()
start = html.find('{"menu":{"activeItemIdx":-1}')
depth, in_str, esc = 0, False, False
for i, c in enumerate(html[start:], start):
    if in_str:
        if esc: esc = False
        elif c == '\\': esc = True
        elif c == '"': in_str = False
    else:
        if c == '"': in_str = True
        elif c == '{': depth += 1
        elif c == '}':
            depth -= 1
            if depth == 0:
                end = i + 1; break

spec = json.loads(html[start:end])['spec']['data']
print(json.dumps(spec['paths']['/v1/chat/completions']['post'], indent=2))

Parameter-Schema fuer chat/completions (Stand 2026-05-03)

Wichtige Defaults und Limits aus der Spec (Auszug, fett = abweichend von OpenAI):

Parameter Typ Default Anmerkung
model string required
messages array required
temperature number 1
top_p number -1 Sentinel fuer "deaktiviert"
n int 1
stream bool false
stop array max 4 Sequenzen
max_tokens int 16 deprecated zugunsten max_completion_tokens
max_completion_tokens int 16
presence_penalty number 0
frequency_penalty number 0
logit_bias object Token-ID -> Bias
response_format object text / json_object / json_schema
tools, tool_choice Function Calling

Nicht exposed (wuerde es mit anderen vLLM-Backends geben): repetition_penalty, min_p, top_k, skip_special_tokens, ignore_eos, stop_token_ids. Wer diese vLLM-Backdoors braucht, ist auf Ionos falsch.

Warum die Marketing-Doku nicht reicht

https://docs.ionos.com/cloud/ai/ai-model-hub/models/llms/meta-llama-3-1-405b zeigt das Beispiel:

{ "model": "...405B-FP8", "messages": [...],
  "temperature": 0.7, "max_tokens": 100 }

Das funktioniert nur deshalb, weil max_tokens: 100 zufaellig klein genug ist, um vor Quirk B (siehe unten) zurueckzukommen. Sobald man realistische Ausgabelaengen will (z.B. 2k+ Tokens fuer einen Arztbrief), laeuft die Combo in den Gateway-Timeout, weil die Doku den Tokenizer-Bug nicht erwaehnt und die Empfehlungen nicht praxistauglich validiert sind.

Llama 3.1 405B FP8: Drei Quirks

Quirk A — <|eot_id|> wird als ASCII geleakt

Das Llama-3.1-Chat-Template nutzt <|eot_id|> (End-of-Turn) als Stop-Marker. Auf Ionos's vLLM-Deployment ist dieses Token nicht korrekt im Tokenizer als Special-Token registriert. Folge: das Modell emittiert es als die einzelnen ASCII-Zeichen a s s i s t a n t \n \n und labert weiter. Beobachtbar an Outputs der Form:

Hallo! Wie kann ich helfen?assistant\n\n
Oder moechtest du einfach plaudern?assistant\n\n
Entschuldigung, ich habe mich wiederholt...assistant\n\n

Konsequenz: der stop-Parameter mit ["<|eot_id|>"] greift nie, weil diese String-Sequenz nicht im Output auftaucht. Workarounds:

Workaround Funktioniert Sauber?
stop: ["assistant"] zusaetzlich ja, 0.79 s fuer "Sag hallo." unsauber, Provider-Bug-Patch
System-Prompt instruiert eigenen End-Marker, stop matcht ihn ja, 0.89 s mittel, Instruction-Drift moeglich
response_format: json_schema ja, 0.84 s sauber, offizielles Feature

Quirk B — Ohne max_tokens laeuft das Modell bis Context-Ceiling

Da das Modell selbst keinen Stop-Token emittiert (Quirk A) und max_tokens Default = 16 nur theoretisch greift, generiert FP8-405B auf vielen Pfaden bis zum Kontext-Limit (128k Tokens) weiter. Bei ~20 tok/s Generierungsrate sind das ~107 Minuten — laenger als jeder Gateway-Timeout. Beobachtung: HTTP 504 "stream timeout" nach ~3 Minuten, oder schon HTTP 000 clientseitig.

Konsequenz: immer max_tokens (oder max_completion_tokens) explizit setzen. Realistische Werte fuer Doctate-Arztbriefe: 2048-4096.

Quirk C — Generierungsrate ~20-23 tok/s

Empirisch ueber 6 Runs ermittelt (siehe Tabelle unten). Das ist deutlich langsamer als Llama-3.3-70B-Instruct (~1 s fuer 10 Tokens) und gpt-oss-120b (~0.5 s fuer 10 Tokens) auf demselben Endpoint. Konsequenz: praktischer Hardlimit bei ~3000 Output-Tokens, sonst Gateway-Timeout.

Empirische Datentabelle

Alle Tests mit meta-llama/Meta-Llama-3.1-405B-Instruct-FP8, Input "Sag hallo." (ausser T22 = realer Doctate-Body, ~1.5k Prompt-Tokens):

Test Parameter Ergebnis Tok/s
T1 temp=0, max_tokens=8192 Timeout 25 s
T3 temp=0, max_tokens=100 5.1 s, finish=length, 100 Tokens 19.6
T8 temp=0, max_tokens=500 23 s, finish=length, 500 Tokens 21.7
T9 temp=0, max_tokens=1500 114 s, finish=length, 1500 Tokens 13.2
T10 temp=0, max_tokens=3000 183 s, finish=length, 3000 Tokens 16.4
T11 temp=0, max_tokens=4500 Gateway-Timeout (curl 200 s)
T17 Doku-Combo + stop:["...","assistant"] 0.79 s, finish=stop, 11 Tokens
T19 Doku-Combo + System-Marker `< 12345 >`
T21 response_format: json_schema (nur document:string) 0.84 s, finish=stop, 8 Tokens
T22 T21 + realer Doctate-Body (1539 Prompt-Tokens) 22.77 s, finish=stop, 482 Tokens 22.7

Konsistenz: finish_reason: "length" heisst Modell wollte weiter — kein sauberer Stop. Nur die letzten vier Loesungswege bekommen sauberes "stop".

Loesungswege im Vergleich

Loesung Speed Sauber Modell-agnostisch Kommentar
stop: ["assistant"] sehr schnell nein nein Patch fuer Provider-Bug; bricht im englischen Text
Eigener End-Marker via System-Prompt sehr schnell mittel ja Modell kann Marker bei langem Output vergessen
response_format: json_schema schnell ja ja Server erzwingt Stop ueber Grammar-Decoding
Modellwechsel auf gpt-oss-120b / Llama-3.3-70B sehr schnell ja Quirks A+B betreffen diese Modelle nicht

Empfehlung: response_format: json_schema als Default-Pfad fuer Doctate, weil derselbe Code-Pfad auf gpt-oss-120b und Llama 3.3 70B unveraendert weiter funktioniert. Wenn das Schema-Decoding bei zukuenftigen, viel groesseren Outputs Latenzprobleme macht, wechselt man auf einen schnelleren Modell-Endpoint, nicht zurueck auf String-Hacks.

Entscheidungsbaum

graph TD
    A[Neuer Aufruf gegen Ionos] --> B{Brauche ich strukturierten Output?}
    B -->|Ja oder unsicher| C[response_format: json_schema setzen]
    B -->|Nein, freier Text| D{Modell?}
    D -->|gpt-oss-120b oder Llama 3.3 70B| E[Standard-Aufruf reicht]
    D -->|Llama 3.1 405B FP8| F[max_tokens MUSS gesetzt sein]
    F --> G{Wie soll gestoppt werden?}
    G -->|Sauber via Schema| C
    G -->|String-Hack ok| H["stop: array enthaelt 'assistant'"]
    G -->|Eigener Marker| I[System-Prompt instruiert + stop matcht]
    C --> J[Output ist JSON, server-seitig parsen]
    E --> K[Output direkt nutzen]
    H --> K
    I --> K

Quellen und Verweise

  • API-Endpoint: https://openai.inference.de-txl.ionos.com/v1/chat/completions
  • Authoritative API-Doku (Redoc): https://api.ionos.com/docs/inference-openai/v1/
  • Marketing-Doku (oft veraltet, mit Vorsicht geniessen): https://docs.ionos.com/cloud/ai/ai-model-hub
  • vLLM-Tokenizer-Hintergrund (warum <|eot_id|> als ASCII leakt): Llama-3.1-Chat-Template + add_special_tokens=False-Detokenization-Pfad.
  • Verwandte Doctate-Memories: project_canary_ollama_vram_conflict, project_mistral_24b_silent_deletion, feedback_no_inline_llm_in_handlers.

Wartungshinweis

Diese Datei dokumentiert Verhalten zu einem Zeitpunkt. Bevor man bei einem neuen Bug auf diese Workarounds setzt, immer zuerst pruefen, ob:

  1. Die Modellliste sich geaendert hat (/v1/models).
  2. <|eot_id|> mittlerweile im Output sauber als Token statt als assistant\n\n ankommt — dann ist Quirk A behoben und die String-Hacks koennen weg.
  3. Die OpenAPI-Spec neue/geaenderte Parameter zeigt.

Datum oben aktualisieren, wenn man verifiziert hat, dass diese Dokumentation noch dem realen Backend-Verhalten entspricht.