Dev

Opus 5.5 ist günstiger, und die 400er sind der einfache Teil

Ein Request, der 400 zurückgibt, hat dir schon gesagt, was du reparieren musst. Die Änderungen, die dich bei diesem Upgrade etwas kosten, kommen als 200 zurück: eine Antwort, die nicht mehr mit Text beginnt, ein Effort-Level, das eine Stufe tiefer liegt, weil du es nie gesetzt hast, und ein Alias, der dich ohne Deploy auf ein neues Modell umgestellt hat.

Claude Opus 5.5 ist am 22. September erschienen, für $4 Input und $20 Output pro Million Tokens, nach $5 und $25 bei Opus 5. Am selben Tag hat Claude Code v2.1.280 es zum Standard-Opus-Modell gemacht. Anthropics eigene Liste dessen, was für bereits auf Opus 5 laufenden Code bricht, hat vier Punkte. Alle vier enden in einem 400, drei davon schon beim allerersten Request.

Ein 400 ist die gute Art von Bruch. Der Request wird sofort abgelehnt, und die Meldung nennt die Lösung. Die Änderungen, für die sich bei diesem Upgrade ein Nachmittag lohnt, sind die, die als 200 zurückkommen.

Welche Requests geben jetzt 400 zurück?

Vier Arten, wenn du von Opus 5 kommst: Thinking abschalten, einen Tool-Call erzwingen, das alte Computer-Use-Tool auf der Claude API und Google Cloud verwenden, und einen Thinking-Block erneut mitschicken, nachdem du etwas davor geändert hast. Die ersten drei kommen mit Fehlermeldungen, die spezifisch genug sind, um in deinen Logs danach zu greppen. Die Computer-Use-Meldung listet danach zusätzlich die Tool-Typen auf, die das Modell akzeptiert:

text
"thinking.type.disabled" is not supported for this model. Use "thinking.type.adaptive" and "output_config.effort" to control thinking behavior.
tool_choice: type "tool" and "any" are not supported for this model.
'claude-opus-5-5' does not support tool types: computer_20251124.

Zwei der Lösungen sind Löschungen. Entferne das Feld thinking (Adaptive Thinking ist immer an, und thinking: {"type": "adaptive"} ist gleichbedeutend damit, es wegzulassen) und steuere die Tiefe über output_config.effort. Die Prüfung auf erzwungene Tools gilt auch für den Token-Counting-Endpoint, also scheitert ein Kostenschätzer, der deinen Produktions-Request spiegelt, ebenfalls. Computer Use ist keine Löschung. Der Request deklariert computer_toolset_20260801 ohne Beta-Header, und die Agent-Schleife muss sich mitändern: Die Aktion kommt jetzt als Name des tool_use-Blocks statt als input.action, mehrere können in einem Turn kommen, und jedes Ergebnis gibt toolset_name zurück. Auf Amazon Bedrock funktioniert das alte Tool computer_20251124 weiter.

Der Replay-Fall wird leicht übersehen, weil er vom Alter deines Accounts abhängt. Für Accounts, die am oder nach dem 31. August 2026, 00:00 UTC, angelegt wurden, gibt das erneute Mitschicken eines Thinking-Blocks, nachdem du den system-Prompt, die tools oder eine frühere Nachricht geändert hast, standardmäßig 400 zurück. Eine Konversation, an die nur angehängt wird, trifft das nie. Code, der den Verlauf an Ort und Stelle umschreibt, um Tokens zu sparen, schon, es sei denn, du aktivierst das Verwerfen der veralteten Blöcke (thinking.block_binding.prefix_mismatch_behavior: "drop_block" hinter dem Beta-Header thinking-binding-controls-2026-08-01).

Wer von weiter hinten kommt, bekommt ältere Ablehnungen dazu, die 5.5 beibehält: ein manuelles Thinking-Budget über budget_tokens und nicht standardmäßige Werte für temperature, top_p oder top_k (beides abgelehnt seit Opus 4.7) sowie ein vorausgefüllter Assistant-Turn (seit 4.6).

Was ersetzt erzwungene Tool-Nutzung, und ist es dieselbe Garantie?

Nicht ganz. Der Migrationsleitfaden rät, tool_choice: {"type": "auto"} mit strict: true am Tool zu verwenden und im Prompt zu sagen, wann das Tool greift. Strict Tool Use garantiert, dass ein Call, wenn er passiert, zu deinem Schema passt. Es garantiert nicht, dass der Call passiert. Das Erzwingen hat das garantiert. (Strict Mode akzeptiert außerdem nur einen Teil von JSON Schema und braucht additionalProperties: false an jedem Objekt, prüf also jedes input_schema, bevor du ihn einschaltest.)

Schau dir also an, warum du den Call erzwungen hast. Ging es nur darum, JSON zurückzubekommen, verschieb das Schema in Structured Outputs (output_config.format), das die Antwort selbst einschränkt. Eine Ablehnung oder ein Abbruch bei max_tokens kann trotzdem als 200 ohne gültiges JSON zurückkommen, prüf also stop_reason, bevor du parst. Muss das Modell wirklich handeln, kann der Request das nicht mehr versprechen. Dein Code muss bemerken, wenn es nicht passiert ist:

python
calls = [b for b in resp.content if b.type == "tool_use" and b.name == "get_weather"]
if not calls:
    raise RuntimeError(f"no get_weather call (stop_reason={resp.stop_reason})")

Der Prompt macht den Call wahrscheinlich. Die Prüfung sorgt nicht dafür, dass er passiert. Sie macht aus einem fehlenden Call einen expliziten Fehler statt einer stillen Textantwort. Das ist die Unterscheidung zwischen Gewichten und Binden aus ein Prompt ist keine Invariante, eine Ebene tiefer.

Was bricht ohne Fehlermeldung?

Die Form der Antwort. Thinking läuft bei jedem Opus-5.5-Request, also kann eine Antwort mit einem oder mehreren thinking-Blöcken vor dem ersten text-Block beginnen, und beim Standard display: "omitted" kommen diese Blöcke mit leerem thinking-Feld. Alles, was die Antwort nach Position liest, bekommt einen Thinking-Block, wo es Text erwartet hat.

Ich habe dieses Muster zweimal ausgeliefert. Im März habe ich den Anthropic-Zweig von Beetroots Multi-Provider-Integration veröffentlicht, mit „Response shape: data.content[0].text“ als einem der vier Unterschiede zu OpenAI. Mein Paket agent-recall macht dasselbe in seinem API-Backend: resp.content[0].text, max_tokens=4096, kein thinking-Feld, eingepackt in ein breites except, das den Fehler loggt und None zurückgibt. Im jetzigen Zustand ist keins von beiden von dieser Änderung betroffen. Der API-Alias von agent-recall ist auf Opus 4.6 gepinnt, das ohne Thinking läuft, solange man nicht danach fragt, und das Beetroot-Mapping ist älter als 5.5. Richte eins davon auf claude-opus-5-5, und der Request kann erfolgreich sein und abgerechnet werden, während das Parsen in deinem eigenen Code scheitert.

Eine synthetische Antwort in der Form, die der Migrationsleitfaden beschreibt, reproduziert das mit dem anthropic Python SDK 1.8.0, und die Lösung ist eine Zeile:

python
from anthropic.types import Message
 
resp = Message.model_validate({
    "id": "msg_x", "type": "message", "role": "assistant",
    "model": "claude-opus-5-5", "stop_reason": "end_turn", "stop_sequence": None,
    "usage": {"input_tokens": 10, "output_tokens": 50},
    "content": [
        {"type": "thinking", "thinking": "", "signature": "sig"},
        {"type": "text", "text": "the answer"},
    ],
})
 
resp.content[0].text
# AttributeError: 'ThinkingBlock' object has no attribute 'text'
 
"".join(b.text for b in resp.content if b.type == "text")
# 'the answer'

Drei weitere Änderungen kommen ohne Fehler an.

Effort ist eine Stufe gefallen. Ein Request ohne effort läuft jetzt auf medium, wo Opus 5 auf high lief. Nichts schlägt fehl. Der Standard ist nur eine Stufe nach unten gerutscht, und auf jeder Stufe denkt das Modell pro Turn tendenziell mehr als Opus 5. Wenn du Effort nie gesetzt hast, läufst du jetzt auf einer Einstellung, die du nicht gewählt hast. Setz sie explizit und lass die Evals noch mal laufen, die dir gesagt haben, dass die alte die richtige war.

Die Zwischentexte sind verstummt. Die kurzen Notizen, die das Modell zwischen Tool-Calls schreibt, kommen jetzt als thinking-Blöcke zurück, beim Standard-Display leer. Ein Produkt, das sie Nutzern als Fortschrittsmeldungen gestreamt hat, hört mitten in der Aufgabe auf, sich zu aktualisieren. Setz thinking: {"type": "adaptive", "display": "updates"} mit dem Beta-Header thinking-display-updates-2026-08-18, um die Fortschrittsnotizen zurückzubekommen, während das Reasoning verborgen bleibt, oder "summarized" für beides gemischt, und rendere dann die nicht leeren Blöcke vor dem Tool-Call, dem sie vorausgehen.

Thinking teilt sich jetzt dein Output-Budget. Bei Opus 4.8 und früher lief ein Request ohne thinking-Feld ohne Thinking. Bei 5.5 denkt das Modell, das Thinking zählt gegen dasselbe max_tokens, das früher allein deiner Antwort gehörte, und es wird als Output abgerechnet, auch wenn du den Text nie siehst. Der Preis pro Token ist gesunken. Für einen Workload, der früher ohne Thinking lief, können die Kosten pro Request in die andere Richtung gehen. Miss pro Request.

Warum zählt der Wechsel des Standardmodells mehr als der Preis?

Weil ein Alias dich ohne Deploy umzieht. Claude Code v2.1.280 hat Opus 5.5 zum Standard-Opus gemacht, also bekommt alles, was claude -p --model opus aufruft, das neue Modell an dem Tag, an dem Claude Code aktualisiert wird, ohne Änderung auf deiner Seite, sofern du den Alias nicht überschrieben hast.

agent-recall zeigt beide Hälften in einem Paket. Sein API-Backend löst den Alias opus auf ein gepinntes claude-opus-4-6 auf, ist also sicher vor der Änderung der Antwortform und gleichzeitig blind dafür. Sein CLI-Backend ruft claude -p --model opus auf, folgt also dem Alias, sobald Claude Code aktualisiert wird. Claude Code parst die Antwortform selbst und gibt reinen Text zurück, dieser Pfad bricht also nicht beim Parsen. Was er nicht abfängt, ist die Änderung am Modell selbst: der Effort, die Länge, das Verhalten. Derselbe Config-Key, zwei Modelle.

Anfang des Monats habe ich argumentiert, dass ein Modell eine Abhängigkeit ist, die nicht stillhält: Pinne die Version, damit dir der Moment gehört, in dem sie sich ändert. Der Wechsel ist die andere Seite davon. Ein Pin schützt den API-Pfad vor einem Bruch und versteckt den Bruch gleichzeitig vor dir, bis zu dem Tag, an dem du den Pin verschiebst. Ein Alias gibt dir das neue Modell nach dem Zeitplan des Anbieters und keinen 400, der dir sagt, dass es passiert ist.

Wie fängst du das ab, bevor dich ein Wechsel des Standardmodells erreicht?

Halte einen Canary: einen realistisch geformten Request für jedes Modell, von dem du abhängst, und für das nächste, noch bevor du wechselst. Er liest die Antwort nach Blocktyp und scheitert laut, wenn die Antwort nicht da ist.

python
import sys
import anthropic
 
client = anthropic.Anthropic()
MODELS = ["claude-opus-5", "claude-opus-5-5"]
failures = []
 
for model in MODELS:
    try:
        resp = client.messages.create(
            model=model,
            max_tokens=2048,
            output_config={"effort": "medium"},
            messages=[{"role": "user", "content": "Reply with the word ok."}],
        )
    except anthropic.BadRequestError as e:
        failures.append(f"{model}: 400 {e.message}")
        continue
    kinds = [b.type for b in resp.content]
    text = "".join(b.text for b in resp.content if b.type == "text")
    print(f"{model}: blocks={kinds} stop={resp.stop_reason} out={resp.usage.output_tokens}")
    if resp.stop_reason != "end_turn" or "ok" not in text.lower():
        failures.append(f"{model}: stop={resp.stop_reason} text={text!r}")
 
if failures:
    sys.exit("\n".join(failures))

Das ist das Gerüst. Setz deinen echten Request ein: deine Tools, dein tool_choice, dein max_tokens und deinen Produktions-Parser statt des Joins. Wenn du Tool-Schleifen fährst, füg einen zweiten Turn mit einem Tool-Ergebnis hinzu, denn die Replay-Regeln zeigen sich nur dort. Es geht darum, dem neuen Modell genau das zu schicken, was die Produktion schickt, und zuzusehen, wie es bricht, solange es noch ein Skript ist.

Lass ihn laufen, wenn ein Changelog erscheint. Anthropics Release Notes und die Release-Seite von Claude Code listen Modelländerungen noch am selben Tag. Für alles, was über einen Alias aufgerufen wird, lass den Canary gegen das Modell laufen, auf das der Alias gleich zeigen wird, nicht gegen das, auf das er jetzt zeigt. Der Migrationsleitfaden liefert auch einen automatisierten Weg mit, /claude-api migrate in Claude Code, der die Parameter umschreibt und dir eine Checkliste zum manuellen Prüfen gibt. Das repariert die Requests. Ob die Antworten noch geparst werden, ist die Aufgabe des Canary.

Was solltest du diese Woche ändern?

Sortiert danach, wie leise es jeweils scheitert:

  1. Lies Content-Blöcke überall nach type und gib thinking-Blöcke in Tool-Schleifen unverändert zurück. Die API gibt hier 200 zurück, und der Fehler zeigt sich in deinem eigenen Code, wenn überhaupt.
  2. Setz effort bei jedem Request explizit, damit eine Änderung des Standards ihn nicht für dich wählt.
  3. Prüf auf stop_reason: "max_tokens" bei allem, was früher ohne Thinking lief, erhöh dann das Limit oder senk den Effort, und miss die Kosten pro Request neu.
  4. Ersetz erzwungene Tool-Nutzung: Structured Outputs, wo du JSON wolltest, auto plus eine Prüfung auf fehlende Calls, wo du eine Aktion wolltest, und strict an den Tools, deren Schemas das unterstützen.
  5. Prüf thinking.display, wenn Nutzer dem Agenten bei der Arbeit zusehen.
  6. Entferne thinking: disabled und manuelle Budgets, und stell Computer Use samt Agent-Schleife auf das Toolset um (Claude API und Google Cloud). Die 400er erinnern dich sowieso daran.

Die Preissenkung ist echt, und sie kommt so an wie die 400er: angekündigt. Alles andere auf dieser Liste kommt als 200 zurück und wartet darauf, dass du es bemerkst.

Diskussion

Hier gibt es keine Kommentarspalte. Diskussionen laufen auf X.

Max Nardit

Max Nardit

@mnardit

Weitere Artikel

Ein Preis, den man nicht ausrechnen kann

Die Rechnung steigt um $96 im Jahr, das ist nichts. Darunter hat sich etwas anderes verändert: Die Zahl lässt sich nicht mehr herleiten. Das im Plan enthaltene Kontingent hat keine veröffentlichte Größe, das Gratis-Kontingent an Credits auch nicht, und der Jahres-Credit kostet mehr als der Monats-Credit. Ein Preis, den du nicht aus veröffentlichten Zahlen ausrechnen kannst, ist eine Schätzung, und eine Schätzung ist eine andere Art von Abhängigkeit.

Claude Code liest jetzt AGENTS.md, und der Standard ist ein Fallback

Anthropic hat wieder das Laden gelöst und die Rangfolge gelassen, wo sie war. Welche Projektdatei der Standard lädt, hängt davon ab, was zufällig auf der Platte liegt. Die geladene Datei fehlt in den Übersichten, mit denen du sie prüfen würdest, und der alte Einzeiler-Import bleibt das Setup, das sich über Provider und Versionen hinweg gleich verhält.

Das Tool, das du installierst, hat deine Reichweite

Ein installiertes Tool hält jede Autorität, die du ohnehin schon hast, und sie herzugeben ist die eine Sicherheitsentscheidung, die keinen zweiten Blick bekommt: einmal getroffen, im unbedachtesten Moment der ganzen Installation, danach nie wieder angerührt. Du kannst eingrenzen, was es erreichen kann, oder lesen, was sein Code wirklich anfasst; dem Namen zu vertrauen tut keines von beidem.