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.
11 KiB
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-Stringassistant\n\nausgegeben statt als Special-Token. Folge: der offiziellestop-Parameter mit<|eot_id|>feuert nie, das Modell laeuft bismax_tokensvoll. - 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:
- Die Modellliste sich geaendert hat (
/v1/models). <|eot_id|>mittlerweile im Output sauber als Token statt alsassistant\n\nankommt — dann ist Quirk A behoben und die String-Hacks koennen weg.- Die OpenAPI-Spec neue/geaenderte Parameter zeigt.
Datum oben aktualisieren, wenn man verifiziert hat, dass diese Dokumentation noch dem realen Backend-Verhalten entspricht.