> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fim.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# LLM-Anbieter-Kompatibilität

> Wie FIM One LLM-Aufrufe weiterleitet, die tool_choice-Architektur und anbieter-spezifische Fallstricke — besonders Anthropic Thinking + AWS Bedrock.

## Anbieter-Erkennung

FIM One verwendet LiteLLM als universellen Adapter. Die Funktion `_resolve_litellm_model()` in `core/model/openai_compatible.py` ordnet die `LLM_BASE_URL` + `LLM_MODEL` des Benutzers einem LiteLLM-Modellbezeichner mit einem Anbieter-Präfix zu. Das Präfix bestimmt, wie LiteLLM die Anfrage weiterleitet — natives API-Protokoll (Anthropic Messages API, Gemini, etc.) oder generisches OpenAI-kompatibles `/v1/chat/completions`.

Auflösungsreihenfolge:

1. **Expliziter Anbieter** (aus DB-Feld `ModelConfig.provider`) — höchste Priorität. Wenn der Anbieter einer bekannten Domäne in der URL entspricht, wird keine `api_base` zurückgegeben (LiteLLM leitet nativ weiter). Andernfalls wird `api_base` auf die Relay-URL gesetzt.
2. **Domänen-Abgleich** gegen `KNOWN_DOMAINS` — offizielle API-Endpunkte werden anhand des Hostnamens erkannt.
3. **URL-Pfad-Hinweis** gegen `PATH_PROVIDER_HINTS` — häufig auf Relay-Plattformen wie UniAPI, wo `/claude` oder `/anthropic` im Pfad das Upstream-Protokoll anzeigt.
4. **Fallback** — `openai/`-Präfix (generisches OpenAI-kompatibles).

| Domäne / Pfad                            | Anbieter-Präfix | Protokoll                           |
| ---------------------------------------- | --------------- | ----------------------------------- |
| `api.openai.com`                         | `openai/`       | OpenAI Chat Completions             |
| `anthropic.com`                          | `anthropic/`    | Anthropic Messages API              |
| `generativelanguage.googleapis.com`      | `gemini/`       | Google Gemini                       |
| `api.deepseek.com`                       | `deepseek/`     | DeepSeek (OpenAI-kompatibel)        |
| `api.mistral.ai`                         | `mistral/`      | Mistral                             |
| Pfad enthält `/claude` oder `/anthropic` | `anthropic/`    | Anthropic Messages API (über Relay) |
| Pfad enthält `/gemini`                   | `gemini/`       | Google Gemini (über Relay)          |
| Alles andere                             | `openai/`       | Generisches OpenAI-kompatibles      |

Wenn das Anbieter-Präfix ein natives Protokoll ist (anthropic, gemini, etc.) und die URL nicht der offizielle Endpunkt ist, verwendet LiteLLM das native Protokoll, sendet Anfragen aber an die `api_base` des Relays. Dies bedeutet, dass anbieter-spezifische Verhaltensweisen — einschließlich des unten beschriebenen Bedrock-Prefill-Problems — gelten, unabhängig davon, ob die Anfrage zur offiziellen API oder über ein Relay geht.

<Warning>
  Wenn Ihre Relay-URL `/claude` im Pfad enthält, leitet FIM One automatisch über das native Anthropic-Protokoll weiter. Dies ist normalerweise korrekt (besseres Streaming, Thinking-Unterstützung), bedeutet aber, dass anbieter-spezifische Verhaltensweisen gelten — einschließlich des unten beschriebenen Bedrock-Prefill-Problems.
</Warning>

## tool\_choice — die vier Modi

Der Parameter `tool_choice` ist über das OpenAI-Format standardisiert. LiteLLM übersetzt ihn vor dem Senden der Anfrage in das native Protokoll jedes Anbieters.

| Modus                                         | Bedeutung                                                                     | Anbieterunterstützung                                              |
| --------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `"auto"`                                      | Modell entscheidet, ob ein Werkzeug aufgerufen oder mit Text geantwortet wird | Alle Anbieter                                                      |
| `"required"`                                  | Muss ein Werkzeug aufrufen, aber Modell wählt welches                         | Die meisten Anbieter                                               |
| `{"type":"function","function":{"name":"X"}}` | Muss Funktion X spezifisch aufrufen                                           | Die meisten Anbieter — **nicht kompatibel mit Anthropic Thinking** |
| `"none"`                                      | Kann keine Werkzeuge verwenden, nur Text                                      | Alle Anbieter                                                      |

Die Unterscheidung zwischen `"auto"` und erzwungen (`{"type":"function",...}`) ist der Kern jedes Kompatibilitätsproblems in FIM One. Diese beiden Modi werden von völlig unterschiedlichen Subsystemen mit unterschiedlichen Anforderungen verwendet.

## Wo tool\_choice verwendet wird

Zwei Subsysteme verwenden `tool_choice`, und sie verwenden es auf grundlegend unterschiedliche Weise.

### ReAct-Engine — tool\_choice="auto"

Die ReAct-Schleife erfordert, dass das Modell in jeder Iteration entscheidet: ein Werkzeug aufrufen oder eine endgültige Antwort geben. Nur `"auto"` macht hier Sinn — das Modell wählt frei zwischen der Erzeugung von `tool_calls` oder Textinhalten. Dies ist mit allen Anbietern, allen Modellen und allen Modi kompatibel, einschließlich erweitertem Denken.

```mermaid theme={null}
flowchart TD
    A["ReAct-Iteration"] --> B{"Modell entscheidet<br/>(tool_choice=auto)"}
    B -->|"tool_calls vorhanden"| C["Werkzeug ausführen → nächste Iteration"]
    B -->|"nur Textinhalt"| D["Endgültige Antwort"]
```

Die ReAct-Engine verwendet natives Function Calling (`_run_native`), wenn `abilities["tool_call"] = True`, und fällt andernfalls auf den JSON-im-Inhalt-Modus (`_run_json`) zurück. Beide Modi verwenden `"auto"` — der Unterschied besteht darin, ob Werkzeuge über den `tools`-Parameter übergeben oder in der Systemaufforderung beschrieben werden. Weitere Informationen finden Sie unter [ReAct-Engine — Dual-Mode-Ausführung](/architecture/react-engine#dual-mode-execution).

### structured\_llm\_call — tool\_choice=forced

Einmalige strukturierte Extraktion (Schema-Annotation, DAG-Planung, Plan-Analyse). Erzwingt, dass das Modell eine bestimmte virtuelle Funktion aufruft und garantiert strukturierte JSON-Ausgabe. Dies ist die Aufrufstelle, die anbieterspezifische Fehler auslöst.

`structured_llm_call` implementiert eine 3-stufige Degradationskette:

```mermaid theme={null}
flowchart TD
    START["structured_llm_call()"] --> L1{"abilities.tool_call<br/>AND tool_choice?"}
    L1 -->|Both true| FC["Level 1: native_fc<br/>forced tool_choice"]
    L1 -->|Either false| L2
    FC -->|"Success"| DONE["Return StructuredCallResult"]
    FC -->|"Fail (catch Exception)"| L2{"abilities.json_mode?"}
    L2 -->|Yes| JM["Level 2: json_mode<br/>response_format=json_object"]
    L2 -->|No| PT["Level 3: plain_text<br/>extract JSON from free text"]
    JM -->|"Success"| DONE
    JM -->|"Fail → retry once"| PT
    PT -->|"Success"| DONE
    PT -->|"Fail → retry once"| ERR["StructuredOutputError<br/>or default_value"]
```

Der kritische Designunterschied: Das Fallback von `structured_llm_call` ist **Laufzeit** — es versucht dynamisch jede Stufe und fängt Ausnahmen ab, um durchzufallen. Die Modusauswahl der ReAct-Engine ist **Build-Zeit** — sie prüft `_native_mode_active` einmal am Anfang und verpflichtet sich auf einen Modus für die gesamte Schleife. Das bedeutet, dass `structured_llm_call` sich transparent von anbieterspezifischen 400-Fehlern erholen kann, während ReAct darauf angewiesen ist, dass der Modus von Anfang an korrekt gewählt wird.

## Die Bedrock-Prefill-Falle

Wenn `response_format={"type":"json_object"}` für ein Modell übergeben wird, das mit dem `anthropic/`-Präfix aufgelöst wird, injiziert LiteLLM intern eine Assistant-Prefill-Nachricht, um den JSON-Modus zu simulieren. Die Anthropic Messages API hat keinen nativen `response_format`-Parameter, daher approximiert LiteLLM dies, indem eine öffnende Klammer als Assistant-Inhalt vorangestellt wird:

```json theme={null}
{"role": "assistant", "content": "{"}
```

Dies funktioniert auf Anthropics direkter API. Jedoch lehnen neuere AWS-Bedrock-Modellversionen jede Konversation ab, deren letzte Nachricht `role: "assistant"` hat — sie nennen dies „Assistant-Message-Prefill" und werfen:

```
ValidationException: This model does not support assistant message prefill.
The conversation must end with a user message.
```

Dieser Fehler tritt nur auf, wenn **alle drei Bedingungen** gleichzeitig erfüllt sind:

1. Das Modell wird mit dem `anthropic/`-Präfix aufgelöst (über Domain-Matching oder URL-Pfad-Hinweis).
2. `response_format={"type":"json_object"}` wird übergeben (der json\_mode-Code-Pfad in `structured_llm_call`).
3. Das eigentliche Backend ist AWS Bedrock (das Prefill ablehnt).

<Tip>
  **Bedrock über OpenAI-kompatiblen Endpoint?** Wenn Ihr Bedrock-Relay einen OpenAI-kompatiblen `/v1/chat/completions`-Endpoint bereitstellt (entweder AWS's eigenes OpenAI-kompatibles Gateway oder einen Drittanbieter-Proxy), und der URL-Pfad enthält **nicht** `/claude` oder `/anthropic`, löst FIM One es mit dem `openai/`-Präfix auf. LiteLLM behandelt das Backend dann als einen Standard-OpenAI-kompatiblen Server, übergibt `response_format` direkt ohne Prefill-Injektion, und der Server handhabt JSON-Constraining nativ. **Die Prefill-Falle gilt nicht** — Sie müssen `json_mode_enabled=false` nicht setzen.
</Tip>

<Warning>
  Dies betrifft NICHT natives Tool-Calling (`tool_choice="auto"` mit `tools=`-Parameter). Die Prefill-Injektion erfolgt nur für `response_format`. Die ReAct-Agent-Ausführung ist völlig unbeeinträchtigt.
</Warning>

Wenn sowohl Level 1 (native\_fc) als auch Level 2 (json\_mode) auf Bedrock fehlschlagen, stellt sich das System auf Level 3 (plain\_text) wieder her. Das unten beschriebene Flag `json_mode_enabled` eliminiert den verschwendeten Level-2-Aufruf.

### Die Lösung: json\_mode\_enabled

Ein pro-Modell-Flag `json_mode_enabled` steuert, ob Level 2 (json\_mode) jemals versucht wird:

* **DB-konfigurierte Modelle**: Umschalter in Admin → Models → Advanced settings. Das Flag wird auf `ModelProviderModel.json_mode_enabled` gespeichert (Standard `TRUE`).
* **ENV-konfigurierte Modelle**: setzen Sie `LLM_JSON_MODE_ENABLED=false` in Ihrer Umgebung.
* **Auswirkung**: wenn deaktiviert, gibt `abilities["json_mode"]` `False` zurück → `response_format` wird nie übergeben → keine Prefill → Bedrock funktioniert. Die Degradationskette wird zu `native_fc → plain_text`, wobei der fehlgeschlagene json\_mode-Aufruf vollständig übersprungen wird.
* **Kein Qualitätsverlust**: das Modell gibt weiterhin gültiges JSON zurück, da das System-Prompt es anweist. Die plain\_text-Ebene verwendet `extract_json()` zum Parsen von JSON aus Freitext-Inhalten, was bei modernen Modellen zuverlässig funktioniert.

## Thinking-Modelle + erzwungene tool\_choice

Einige Modelle haben erweitertes Denken (Chain-of-Thought) dauerhaft aktiviert. Ihre APIs lehnen erzwungene `tool_choice` ab, da das Erzwingen eines bestimmten Funktionsaufrufs der Freiheit des Modells zum ersten Nachdenken widerspricht:

```
tool_choice 'specified' is incompatible with thinking enabled
```

Anthropic erzwingt diese Einschränkung auf Protokollebene, und einige andere Anbieter (z. B. Moonshot AI / Kimi K2.5) folgen demselben Muster.

Für Anthropic-Modelle verwaltet `structured_llm_call` dies automatisch, indem `reasoning_effort=None` beim Aufrufen von native\_fc übergeben wird, wodurch erweitertes Denken für diesen spezifischen Aufruf deaktiviert wird. Strukturierte Ausgabeaufrufe benötigen **Schemakonformität**, nicht tiefes Denken – das Deaktivieren des Denkens hier ist sowohl korrekt als auch vorteilhaft (niedrigere Latenz, niedrigere Kosten).

Jedoch haben einige Modelle (z. B. Kimi K2.5) Denken dauerhaft aktiviert, ohne dass es extern deaktiviert werden kann. Für diese Modelle schlägt native\_fc immer mit einem 400-Fehler fehl, was vor der Degradationskette etwa 10 Sekunden verschwendete Latenz pro strukturiertem Aufruf hinzufügt, bevor sie zu json\_mode fällt.

### Die Lösung: tool\_choice\_enabled

Ein Pro-Modell-Flag `tool_choice_enabled` steuert, ob Level 1 (native\_fc) jemals versucht wird:

* **DB-konfigurierte Modelle**: Umschalter in Admin → Models → Advanced → "Native Function Calling". Das Flag wird auf `ModelProviderModel.tool_choice_enabled` gespeichert (Standard `TRUE`).
* **ENV-konfigurierte Modelle**: setzen Sie `LLM_TOOL_CHOICE_ENABLED=false` in Ihrer Umgebung.
* **Auswirkung**: wenn deaktiviert, gibt `abilities["tool_choice"]` `False` zurück → die Degradationskette beginnt bei Level 2 (json\_mode) oder Level 3 (plain\_text) und überspringt native\_fc vollständig. Dies eliminiert die \~10s Strafe pro strukturiertem Aufruf für inkompatible Modelle.
* **ReAct-Agent nicht betroffen**: `tool_choice_enabled` steuert nur die erzwungene Werkzeugauswahl in `structured_llm_call`. Die ReAct-Engine verwendet `tool_choice="auto"` (Modell entscheidet frei), was mit allen Modellen unabhängig von dieser Einstellung funktioniert.

<Note>
  `tool_choice_enabled` und `tool_call` sind separate Ability-Flags. `tool_call` (immer `True` für `OpenAICompatibleLLM`) steuert, ob Werkzeuge dem Modell überhaupt übergeben werden — das Deaktivieren würde den ReAct-Agent beschädigen. `tool_choice` steuert nur, ob **erzwungene** Werkzeugauswahl für die Strukturierte-Ausgabe-Extraktion versucht wird.
</Note>

`tool_choice="auto"` wird durch den Thinking-Modus nicht beeinflusst. Die ReAct-Engine verwendet ausschließlich `"auto"`, daher funktioniert die Agent-Ausführung mit aktiviertem Thinking.

<Warning>
  Setzen Sie NICHT `abilities["tool_call"] = False`, um diese Einschränkung zu vermeiden. Das würde ReActs `_run_native`-Modus deaktivieren (der `tool_choice="auto"` verwendet und mit Thinking gut funktioniert) und würde es in den weniger zuverlässigen `_run_json`-Modus zwingen.
</Warning>

<Note>
  **Hinweis zur Provider-Migration:** Einige Drittanbieter-Relays löschen stillschweigend nicht unterstützte Parameter wie `reasoning_effort` (`drop_params=True`), daher wird Thinking nie aktiviert, auch wenn es konfiguriert ist. Bei der Migration zu einem Provider, der Thinking ordnungsgemäß unterstützt (Bedrock, direkte Anthropic API), stellt `reasoning_effort=None` in native\_fc konsistentes Verhalten sicher. Es ist keine Benutzeraktion erforderlich — strukturierte Ausgabe funktioniert identisch über alle Provider hinweg.
</Note>

## Quick reference: what works where

| Scenario                               | ReAct mode    | structured\_llm\_call path                   | Notes                                                              |
| -------------------------------------- | ------------- | -------------------------------------------- | ------------------------------------------------------------------ |
| OpenAI (any model)                     | `_run_native` | native\_fc                                   | Vollständige Unterstützung                                         |
| Anthropic (no thinking)                | `_run_native` | native\_fc                                   | Vollständige Unterstützung                                         |
| Anthropic + thinking                   | `_run_native` | native\_fc (thinking auto-disabled)          | Thinking nur für strukturierte Ausgabe deaktiviert                 |
| Bedrock relay (no thinking)            | `_run_native` | native\_fc                                   | Vollständige Unterstützung                                         |
| Bedrock relay + thinking               | `_run_native` | native\_fc (thinking auto-disabled)          | Thinking nur für strukturierte Ausgabe deaktiviert                 |
| Bedrock via OpenAI-compatible endpoint | `_run_native` | native\_fc                                   | Vollständige Unterstützung — kein Prefill-Problem; siehe Tipp oben |
| Gemini                                 | `_run_native` | native\_fc                                   | Vollständige Unterstützung                                         |
| DeepSeek (non-thinking)                | `_run_native` | native\_fc                                   | Vollständige Unterstützung                                         |
| DeepSeek R1 (thinking)                 | `_run_native` | json\_mode (set `tool_choice_enabled=false`) | Thinking immer aktiviert; native\_fc überspringen                  |
| Kimi K2 (non-thinking)                 | `_run_native` | native\_fc                                   | Vollständige Unterstützung                                         |
| Kimi K2.5 (thinking)                   | `_run_native` | json\_mode (set `tool_choice_enabled=false`) | Thinking immer aktiviert; native\_fc überspringen                  |
| Generic OpenAI-compatible              | `_run_native` | native\_fc                                   | Vollständige Unterstützung                                         |
| Any model with `tool_call=false`       | `_run_json`   | json\_mode or plain\_text                    | Fallback für Modelle ohne Tool-Call-Unterstützung                  |

## Empfohlene Pro-Modell-Konfiguration

Sowohl `tool_choice_enabled` als auch `json_mode_enabled` können pro Modell in Admin → Models → Advanced settings umgeschaltet werden. Die Standardwerte (beide `TRUE`) funktionieren für die meisten Anbieter. Passen Sie sie nur an, wenn Sie auf Fehler oder unnötige Latenz stoßen.

| Modelltyp                  | Native FC | JSON Mode | Grund                                                   |
| -------------------------- | --------- | --------- | ------------------------------------------------------- |
| OpenAI GPT-Serie           | ON        | ON        | Vollständige Unterstützung — Standardwerte sind korrekt |
| Anthropic Claude           | ON        | ON        | Thinking wird für native\_fc automatisch deaktiviert    |
| Google Gemini              | ON        | ON        | Vollständige Unterstützung                              |
| DeepSeek V3 / Coder        | ON        | ON        | Vollständige Unterstützung                              |
| **DeepSeek R1 (thinking)** | **OFF**   | ON        | Thinking immer aktiviert; native\_fc abgelehnt          |
| **Kimi K2.5 (thinking)**   | **OFF**   | ON        | Thinking immer aktiviert; native\_fc abgelehnt          |
| Kimi K2 (non-thinking)     | ON        | ON        | Vollständige Unterstützung                              |
| **AWS Bedrock Relay**      | ON        | **OFF**   | Bedrock lehnt Assistant-Prefill in json\_mode ab        |
| Schwache / kleine Modelle  | OFF       | OFF       | Direkt zur plain\_text-Extraktion                       |

<Tip>
  **Wann ändern:** Wenn Sie in Ihren Logs `structured_llm_call: native_fc call raised` Warnungen gefolgt von erfolgreicher json\_mode-Extraktion sehen, profitiert das Modell nicht von native\_fc. Deaktivieren Sie "Native Function Calling" für dieses Modell, um den verschwendeten API-Aufruf zu eliminieren (\~10s pro strukturierter Ausgabeanfrage).
</Tip>

**ENV-level Overrides** gelten für alle Modelle, die über Umgebungsvariablen konfiguriert werden (nicht Admin UI):

```bash theme={null}
# Disable native_fc globally (for thinking-model-only deployments)
LLM_TOOL_CHOICE_ENABLED=false

# Disable json_mode globally (for Bedrock relay deployments)
LLM_JSON_MODE_ENABLED=false
```

## Reasoning-Aufwand und Thinking-Konfiguration

FIM One stellt zwei Umgebungsvariablen zur Steuerung von erweitertem Thinking / Reasoning zur Verfügung:

| Variable                      | Werte                   | Effekt                                                                                                                                                                                   |
| ----------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LLM_REASONING_EFFORT`        | `low`, `medium`, `high` | Wird als `reasoning_effort` an LiteLLM übergeben. Anthropic: auf `thinking`-Parameter abgebildet. OpenAI o-Serie: durchgeleitet. Andere: stillschweigend verworfen (`drop_params=True`). |
| `LLM_REASONING_BUDGET_TOKENS` | Integer (z. B. `10000`) | Nur Anthropic: setzt eine explizite `thinking.budget_tokens`-Obergrenze und umgeht LiteLLMs automatische Abbildung. Nützlich zur Kostenkontrolle bei Claude-Modellen.                    |

Wenn `reasoning_effort` gesetzt ist und das Modell als `anthropic/` aufgelöst wird, gelten zwei zusätzliche Verhaltensweisen:

1. **Temperatur wird auf 1,0 erzwungen.** Bedrock lehnt `temperature != 1.0` ab, wenn Thinking aktiviert ist. FIM One handhabt dies automatisch — keine Benutzeraktion erforderlich.
2. **GPT-5.x mit Tools**: `reasoning_effort` wird stillschweigend verworfen, wenn `tools` vorhanden sind, da der GPT-5-Endpunkt `/v1/chat/completions` die Kombination ablehnt. Dies betrifft nur die ReAct-Tool-Schleife; `structured_llm_call`-Aufrufe ohne `tools`-Parameter sind nicht betroffen.

## Defensive Parsing für strukturierte Ausgabe

Auch wenn native\_fc korrekt funktioniert, enthält die Pipeline für strukturierte Ausgabe eine defensive Parsing-Schicht, um Grenzfälle von beliebigen Anbietern oder Kompatibilitätsschichten zu behandeln.

Der `_dict_to_steps`-Parser des DAG-Planers behandelt drei häufige Grenzfälle:

1. **Einzelnes Objekt statt Array.** Einige Modelle geben `{"steps": {"id": "1", "task": "..."}}` (ein einzelnes Step-Objekt) statt `{"steps": [{"id": "1", "task": "..."}]}` (ein Array) zurück. Der Parser erkennt dies, indem er auf `id`- oder `task`-Schlüssel prüft und das Objekt in eine Liste einbindet.

2. **Doppelt kodierter JSON-String.** Wenn strukturierte Ausgabe auf json\_mode zurückfällt (das keine Schema-Erzwingung hat), geben einige Anbieter den `steps`-Wert als JSON-String statt als natives Array zurück – z. B. `{"steps": "[{\"id\": \"1\", ...}]"}`. Dieser String kann auch Zeilenumbrüche enthalten (aus der Formatierung des Modells), die Standard-`json.loads` unterbrechen. Der Parser verwendet `extract_json_value()` (das `_repair_json_strings` enthält), um folgende Fälle zu behandeln:
   * Literale Zeilenumbrüche innerhalb von JSON-Stringwerten
   * Ungültige Escape-Sequenzen (häufig bei LaTeX- oder Code-Inhalten)
   * Andere Serialisierungsbesonderheiten von Kompatibilitätsschichten

3. **Fehlender `steps`-Wrapper.** Das Modell kann einen einzelnen Step als Top-Level-Objekt ohne den `steps`-Wrapper-Schlüssel zurückgeben. Der Parser erkennt `id` und `task` auf der Root-Ebene und bindet sie entsprechend ein.

<Note>
  Unter normalen Bedingungen gibt native\_fc korrekt strukturierte Tool-Call-Argumente zurück und diese Grenzfälle treten nicht auf. Die defensiven Parser existieren als Sicherheitsnetz für benutzerdefinierte `BaseLLM`-Unterklassen, ungewöhnliche Anbieterverhalten oder Fallback-Szenarien, in denen strukturierte Ausgabe zu json\_mode oder plain\_text degradiert.
</Note>

## Prompt-Caching (anbieterübergreifend)

FIM One implementiert Anthropics explizites Prompt-Caching über `cache_control`-Breakpoints und profitiert gleichzeitig vom automatischen Prefix-Caching aller anderen Anbieter durch die **Prompt Section Registry**. Das Ziel ist ein einziger Prompt-Assembly-Pfad, der über alle Anbieter hinweg funktioniert, ohne dass sich die Prompt-Form bei jedem Aufruf unterscheidet.

### Architektur

Das Modul `fim_one.core.prompt` stellt drei Primitive bereit:

* **`PromptSection`** — ein benanntes Fragment mit entweder statischem `content: str` oder dynamischem `content: Callable`
* **`PromptRegistry`** — ein memoized Store (statische Abschnitte werden einmal gerendert, dynamische Abschnitte werden pro Aufruf neu gerendert)
* **`DYNAMIC_BOUNDARY`** — ein Sentinel-Marker, den die Registry zwischen dem letzten statischen Abschnitt und dem ersten dynamischen einfügt, damit Aufrufer den gerenderten Prompt am Cache-Breakpoint aufteilen können

System-Prompts für ReAct (JSON-Modus, nativer Function-Calling-Modus, Synthese) werden aufgeteilt in:

* **Statisches Präfix** (\~95% des Prompts) — Identität, Kernrichtlinien, Tool-Beschreibungen
* **Dynamisches Suffix** — aktuelles Datum/Uhrzeit, sprachspezifische Direktive pro Anfrage, Handoff-Kontext

### Funktionserkennung

`fim_one.core.prompt.caching.is_cache_capable(model_id)` gibt `True` zurück, wenn die Modell-ID eines der folgenden Elemente enthält: `claude`, `anthropic`, `bedrock/anthropic`, `vertex_ai/claude`. Diese Provider erhalten **zwei** `role="system"`-Nachrichten mit `cache_control: {"type": "ephemeral"}` in der ersten (statischen) Nachricht.

Alle anderen Provider erhalten eine **einzelne** verkettete Systemnachricht ohne `cache_control`-Feld — notwendig, da Nicht-Anthropic-Endpunkte das Feld entweder ablehnen oder stillschweigend ignorieren, und das Senden über einige Relays führt zu `400 unknown parameter`-Fehlern.

### Cross-Provider-Abdeckung

| Provider                         | Mechanismus                        | Read-Rabatt | Unsere Handhabung                                                                 |
| -------------------------------- | ---------------------------------- | ----------- | --------------------------------------------------------------------------------- |
| **Anthropic Claude** (3, 3.5, 4) | Explizit `cache_control`           | 0.10×       | Zwei Systemmeldungen mit ephemerem Breakpoint                                     |
| **AWS Bedrock Anthropic**        | Leitet Anthropic-Cache durch       | 0.10×       | Gleich wie Anthropic                                                              |
| **GCP Vertex AI Claude**         | Leitet Anthropic-Cache durch       | 0.10×       | Gleich wie Anthropic                                                              |
| **OpenAI GPT / o-series**        | Auto-Präfix-Hash (≥1024 tokens)    | 0.50×       | Byte-stabiles Präfix über Section Registry → automatischer Hit                    |
| **DeepSeek (v3 / R1)**           | Auto disk-gestützter Präfix-Cache  | 0.10×       | Gleich wie OpenAI                                                                 |
| **Moonshot Kimi (K1/K2)**        | Auto-Präfix-Cache                  | 0.10×/0.50× | Gleich                                                                            |
| **ZhipuAI GLM-4.5+**             | Auto Long-Context-Cache            | 0.20×       | Gleich                                                                            |
| **Grok (xAI)**                   | Auto-Präfix-Cache                  | 0.25×       | Gleich                                                                            |
| **Google Gemini**                | Separate `createCachedContent` API | 0.25×       | **Noch nicht implementiert** — auf v0.9-Roadmap als `GeminiCacheAdapter` verfolgt |
| **Mistral / Cohere**             | Kein nativer Cache                 | N/A         | N/A                                                                               |

Die `PromptRegistry` bietet jedem Provider automatisches Präfix-Caching „kostenlos" — durch Beibehaltung des statischen Teils byte-identisch über Aufrufe hinweg (aktuelle Uhrzeit befindet sich im dynamischen Suffix, nicht im Präfix), stimmt der Hash jedes Auto-Caching-Providers überein und erzielt einen Cache-Hit. Dies ist der Grund, warum die Registry bereits vor Berücksichtigung des Anthropic-spezifischen `cache_control` ein grundlegender modellloser Gewinn ist.

### Observability

Every `chat/*` response's `done_payload` now includes:

```json theme={null}
"cache": {
  "read_tokens": 1067,
  "creation_tokens": 0
}
```

`TurnProfiler` emits a structured log line per turn: `turn_cache summary | model=claude-sonnet-4-6 | read_tokens=1067 | create_tokens=0 | saved_input_tokens=961 (~90%)`. This also functions as a **relay honesty probe** — if you route through an API relay, compare actual billed input vs `read_tokens` to detect whether the relay strips `cache_control` or keeps the 0.10× discount.

No dollar estimate is returned at the LLM layer — pricing and relay markup are applied above, so the LLM layer only returns objective token counts.

### Multi-turn Cache ROI

Gemessen auf Claude 4 ReAct Turns mit dem Standard-Agent-Prompt:

| Mode                      | Static prefix tokens | Dynamic suffix tokens | Cache ratio |
| ------------------------- | -------------------- | --------------------- | ----------- |
| JSON mode, no tools       | \~753                | \~46                  | 94.2%       |
| JSON mode with \~10 tools | \~1067               | \~46                  | 95.9%       |
| Native function-calling   | \~523                | \~46                  | 91.9%       |

Ein 10-Iterations-ReAct-Lauf mit 10 Tools spart \~8.640 Input-Tokens pro Turn nach dem ersten (9 Cache-Treffer × 1067 Tokens × 90%). Anthropic berechnet 1,25× für Cache-Schreibvorgänge beim ersten Aufruf, daher ist die Gewinnschwelle beim **zweiten** Aufruf — Single-Shot-Abfragen profitieren nicht davon.

## Reasoning-Replay-Richtlinie (modelllose Korrektheit)

Extended Thinking / Reasoning Blöcke verhalten sich über verschiedene Provider hinweg unterschiedlich. Eine einheitliche Serialisierungsrichtlinie verstößt gegen Protokollverträge und automatische Prefix-Caches. `fim_one.core.prompt.reasoning.reasoning_replay_policy(model_id)` gibt einen von drei Werten zurück und steuert `ChatMessage.to_openai_dict(replay_policy=...)` in `OpenAICompatibleLLM._build_request_kwargs()`.

### Drei Richtlinien

* **`anthropic_thinking`** — Claude-Familie (einschließlich `anthropic/`, `bedrock/anthropic`, `vertex_ai/claude`). Thinking-Blöcke MÜSSEN mit `signature` replayed werden; Anthropic lehnt nachfolgende Turns ab, wenn die Signatur fehlt oder geändert wird.
* **`informational_only`** — Modelle, die CoT ausgeben, aber KEINE Replay erwarten: DeepSeek R1 / R1-Distill, Qwen QwQ, Gemini 2.x thinking, OpenAI o1 / o3 / o4. Ihre Dokumentation besagt explizit „senden Sie `reasoning_content` nicht in der Nachrichtenhistorie zurück". Es trotzdem zu senden:
  * Verstößt gegen den Provider-Vertrag (kann in zukünftigen Versionen Ablehnungen verursachen)
  * **Invalidiert automatisch ihren Prefix-Cache stillschweigend** — Nachrichtenbytes mutieren bei jedem Turn und brechen den Hash
* **`unsupported`** — Modelle ohne Reasoning-Fähigkeit (GPT-4o, GPT-4 Turbo, Gemini 1.5, Mistral, Llama). Kein CoT zum Replay; Feld sollte niemals erscheinen. Diese Richtlinie ist auch die sichere Standardeinstellung für unbekannte Modell-IDs.

### Durchsetzung

Die gesamte Richtlinienbewertung erfolgt an einer Stelle (`_build_request_kwargs`). `ChatMessage.to_openai_dict(replay_policy=None)` behält die A3-permissive Standardeinstellung bei, damit unkoordinierte Aufrufer nicht zurückgehen. Die Cross-Provider-Testmatrix befindet sich in `tests/test_reasoning_replay_policy.py` mit **umgekehrten Assertions**, die beweisen, dass Nicht-Anthropic-Anfragen `reasoning_content` NICHT durchsickern lassen.

### Für Benutzer

Sowohl Feature- als auch Bug-Verhalten ist automatisch — Sie müssen nichts konfigurieren. Workflow-Implikationen:

* **Wenn Sie Agenten zwischen Claude und DeepSeek in derselben Konversation wechseln**, wird der Verlauf mit Thinking Blocks intakt gespeichert; beim nächsten Turn passt sich die ausgehende Nachrichtenform an das aktuelle Modell an.
* **Wenn Sie einen Proxy / eine benutzerdefinierte `BaseLLM`-Unterklasse verwenden**, stellen Sie sicher, dass seine Modell-ID erkennbar ist (enthält eines der Fragmente) oder die Standard-`unsupported`-Richtlinie wird angewendet — was sicher ist, aber bedeutet, dass Claude hinter einem ungewöhnlichen Proxy möglicherweise die Thinking-Wiedergabe verliert. Fügen Sie das Modell-ID-Fragment zu `_CACHE_CAPABLE_MODEL_FRAGMENTS` (in `core/prompt/caching.py`) und/oder zur Reasoning-Richtlinien-Suche hinzu.

## Fehlerbehebung

**"This model does not support assistant message prefill"**
Bedrock + json\_mode. Zwei Lösungen: (1) setzen Sie `LLM_JSON_MODE_ENABLED=false` oder deaktivieren Sie JSON Mode in den Admin-Modelleinstellungen; oder (2) wenn Ihr Bedrock-Anbieter einen OpenAI-kompatiblen `/v1/chat/completions`-Endpunkt anbietet, wechseln Sie dorthin — FIM One löst ihn als `openai/` auf und die Prefill-Injektion tritt nie auf.

**"Thinking may not be enabled when tool\_choice forces tool use"** / **"tool\_choice 'specified' is incompatible with thinking enabled"**
Bei Anthropic-Modellen deaktiviert `structured_llm_call` das Denken für native\_fc-Aufrufe automatisch. Für andere Anbieter mit immer aktiviertem Denken (z. B. Kimi K2.5) deaktivieren Sie "Native Function Calling" in den erweiterten Einstellungen des Modells, oder setzen Sie `LLM_TOOL_CHOICE_ENABLED=false` global. Die Degradationskette überspringt native\_fc und extrahiert strukturierte Ausgaben stattdessen über json\_mode oder plain\_text.

**"DAG pipeline failed: LLM 'steps' is not an array"**
Das LLM hat das Feld `steps` als String oder einzelnes Objekt statt als Array zurückgegeben. Dies bedeutet typischerweise, dass die strukturierte Ausgabe zu json\_mode durchgefallen ist (das keine Schemaerzwingung hat). Überprüfen Sie das Protokoll auf `structured_llm_call: level=xxx` — wenn es `json_mode` statt `native_fc` anzeigt, schlägt native\_fc stillschweigend fehl. Wenn Sie eine benutzerdefinierte `BaseLLM`-Unterklasse verwenden, überprüfen Sie, ob sie das Kwarg `reasoning_effort` akzeptiert.

**ReAct fällt unerwartet zu JSON-Modus zurück**
Überprüfen Sie, dass `abilities["tool_call"]` des Modells `True` ist. Dies ist immer `True` für `OpenAICompatibleLLM`, aber eine benutzerdefinierte `BaseLLM`-Unterklasse könnte es überschreiben. Überprüfen Sie mit dem Modelldetail-Endpunkt in der Admin-API.

**structured\_llm\_call erschöpft alle Ebenen und wirft StructuredOutputError**
Das Modell konnte auf keiner Ebene gültiges JSON erzeugen. Dies ist selten bei modernen Modellen. Überprüfen Sie: (1) das Schema ist gültiges JSON Schema, (2) das Modell hat genug `max_tokens`, um die vollständige Antwort zu erzeugen, (3) die Systemaufforderung widerspricht nicht den Schemaanweisungen. Der DAG-Planer und Analyzer bieten beide `default_value`-Fallbacks, daher wird dieser Fehler nur von Aufrufstellen weitergegeben, die Standardwerte explizit auslassen.
