docs(anthropic): document opus 5 runtime contracts

This commit is contained in:
Vincent Koc
2026-07-25 18:09:10 +08:00
parent 3764d6739e
commit b4348fca42
2 changed files with 88 additions and 87 deletions

View File

@@ -342,72 +342,62 @@ Related Anthropic docs:
</Note>
## Safety refusal fallback (Claude Fable 5)
## Safety refusal fallback (Claude Opus 5 and Fable 5)
<Warning>
Using Claude Fable 5 means also using Claude Opus 4.8. Fable 5 ships with
safety classifiers that can decline a request, and Anthropic's sanctioned
recovery is to have `claude-opus-4-8` serve that turn. OpenClaw opts into this
automatically for direct API-key requests, so some Fable turns are answered
and billed as Claude Opus 4.8. If your policy or budget cannot accept
Opus-served turns, do not select `anthropic/claude-fable-5`.
Claude Opus 5 and Fable 5 can route a safety-classifier refusal to another
Claude model. OpenClaw opts into Anthropic's recommended per-category routing
for direct API-key requests. A fallback-served turn is billed at the model
that answered. If your policy requires every turn to stay on the requested
model, do not use these models through the automatic fallback path.
</Warning>
### Why this exists
Fable 5 classifiers return `stop_reason: "refusal"` on requests in restricted
domains, and they also false-positive on benign-adjacent work (security
tooling, life sciences, or even asking the model to reproduce its raw
reasoning). Without a fallback, the turn dies with an error even though
another Claude model would happily serve it - Anthropic's own refusal message
tells API integrators to configure a fallback model.
Opus 5 and Fable 5 classifiers return `stop_reason: "refusal"` on requests in
restricted domains. Without a fallback, the turn ends with an error even when
Anthropic has a recommended model for that refusal category.
### How it works
1. For every direct API-key request to `anthropic/claude-fable-5`, OpenClaw
sends Anthropic's server-side fallback opt-in: the
`server-side-fallback-2026-06-01` beta header plus
`fallbacks: [{"model": "claude-opus-4-8"}]`. Claude Opus 4.8 is the only
fallback target Anthropic permits for Fable 5.
1. For every direct API-key request to `anthropic/claude-opus-5` or
`anthropic/claude-fable-5`, OpenClaw sends the
`server-side-fallback-2026-07-01` beta header plus
`fallbacks: "default"`. Anthropic selects the recommended model for the
reported refusal category.
2. Only a safety-classifier decline triggers the fallback. Rate limits,
overloads, and server errors behave exactly as before and go through
OpenClaw's normal [model failover](/concepts/model-failover).
3. The rescue happens inside the same call. A decline before any output is
invisible apart from latency; the whole answer comes from Opus 4.8. On a
invisible apart from latency; the whole answer comes from the serving
model. On a
mid-stream decline the partial text is kept as the prefix the fallback
model continues from, while the declined model's reasoning and tool calls
are discarded per Anthropic's replay rules (they must not be echoed back or
executed).
4. If Claude Opus 4.8 declines as well, the turn surfaces the refusal as an
error, exactly like before this feature.
4. If the recommended model declines as well, the turn surfaces the refusal
as an error.
The fallback happens at the Anthropic API level, so `claude-opus-4-8` does not
need to be in your configured model list or fallback chain - a Fable-capable
API key can always serve Opus.
The fallback happens at the Anthropic API level, so the serving model does not
need to be in your configured OpenClaw fallback chain.
### Observability and billing
- A fallback-served turn records a `provider_fallback` diagnostic on the
assistant message naming `fromModel` and `toModel`, and the message's
`responseModel` reports `claude-opus-4-8`.
- Anthropic bills per attempt: a decline before output is free, and the rescue
bills at Claude Opus 4.8 rates (currently half of Fable 5 rates). OpenClaw's
per-turn cost estimate prices fallback-served turns at Opus rates to match.
- A mid-stream decline additionally bills the already-streamed Fable partial
`responseModel` reports the model that answered.
- Anthropic bills the fallback attempt at the serving model's rates. OpenClaw
prices known Opus 4.8 fallback-served turns at Opus 4.8 rates.
- A mid-stream decline additionally bills the already-streamed primary-model partial
on Anthropic's side; that portion is reported in the API's per-attempt
usage but not folded into OpenClaw's per-turn estimate.
### Scope
Applies to `anthropic/claude-fable-5` with API-key auth against
`api.anthropic.com`. OAuth (Claude CLI subscription reuse), proxy base URLs,
Bedrock, Vertex, and Foundry requests are unchanged and still surface
refusals as errors there.
Verified live: a benign prompt asking Fable 5 to reproduce its raw chain of
thought is declined with `category: "reasoning_extraction"` when sent without
fallbacks, and the same prompt through OpenClaw returns a normal Opus-served
answer with the `provider_fallback` diagnostic attached.
Applies to `anthropic/claude-opus-5` and `anthropic/claude-fable-5` with
API-key auth against `api.anthropic.com`. OAuth (including Claude CLI
subscription reuse), proxy base URLs, Bedrock, Vertex, and Foundry requests
are unchanged and still surface refusals as errors there.
See Anthropic's [refusals and fallback
guide](https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback)
@@ -481,19 +471,20 @@ OpenClaw supports Anthropic's prompt caching feature for API-key auth.
<AccordionGroup>
<Accordion title="Fast mode">
OpenClaw's shared `/fast` toggle sets Anthropic's `service_tier` field for direct API-key traffic to `api.anthropic.com`.
For Claude Opus 5 and Opus 4.8, OpenClaw's shared `/fast` toggle uses
Anthropic's native fast mode for direct API-key traffic to `api.anthropic.com`.
| Command | Maps to |
|---------|---------|
| `/fast on` | `service_tier: "auto"` |
| `/fast off` | `service_tier: "standard_only"` |
| --- | --- |
| `/fast on` | `speed: "fast"` plus `fast-mode-2026-02-01` |
| `/fast off` | Standard speed; no `speed` field |
```json5
{
agents: {
defaults: {
models: {
"anthropic/claude-sonnet-4-6": {
"anthropic/claude-opus-5": {
params: { fastMode: true },
},
},
@@ -503,10 +494,12 @@ OpenClaw supports Anthropic's prompt caching feature for API-key auth.
```
<Note>
- Only applies to direct `api.anthropic.com` requests made with an API key. OAuth/subscription-token requests and proxy routes never get a `service_tier` field.
- Native fast mode is a research preview for Claude Opus 5 and Opus 4.8. It can deliver up to 2.5x higher output-token throughput and is billed at `$10/$50` per million input/output tokens. OpenClaw applies the same 2x multiplier to cache pricing in its cost estimate.
- Native fast mode only applies to direct `api.anthropic.com` requests made with an API key. OAuth/subscription-token requests, Claude CLI, proxies, Bedrock, Vertex, and Foundry never receive the beta or `speed` field.
- Accounts need fast-mode access and a non-zero fast-mode rate limit. Anthropic returns a fast-specific `429` when the separate fast quota is exhausted or zero.
- For other direct Anthropic models, `/fast` retains the existing Priority Tier mapping: on uses `service_tier: "auto"` and off uses `service_tier: "standard_only"`.
- Explicit `serviceTier` or `service_tier` params override `/fast` when both are set.
- Claude Opus 5 and Sonnet 5 do not support Priority Tier, so OpenClaw omits `service_tier` for those models.
- On accounts without Priority Tier capacity, `service_tier: "auto"` may resolve to `standard`.
- Claude Sonnet 5 supports neither native fast mode nor Priority Tier, so OpenClaw omits both fields.
</Note>

View File

@@ -5,7 +5,6 @@ import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { applyExtraParamsToAgent } from "./embedded-agent-runner/extra-params.js";
import { isLiveTestEnabled } from "./live-test-helpers.js";
import { isLiveAuthDrift, isLiveBillingDrift } from "./live-test-provider-drift.test-support.js";
const OPENAI_KEY = process.env.OPENAI_API_KEY ?? "";
const ANTHROPIC_KEY = process.env.ANTHROPIC_API_KEY ?? "";
@@ -119,56 +118,65 @@ describeLive("embedded agent extra params (live)", () => {
});
describeAnthropicLive("embedded agent extra params (anthropic live)", () => {
it("verifies Anthropic fast-mode service_tier semantics against the live API", async () => {
it("verifies Claude Opus 5 default fallback against the live API", async () => {
const headers = {
"content-type": "application/json",
"x-api-key": ANTHROPIC_KEY,
"anthropic-version": "2023-06-01",
"anthropic-beta": "server-side-fallback-2026-07-01",
};
const runProbe = async (serviceTier: "auto" | "standard_only") => {
const res = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers,
body: JSON.stringify({
model: "claude-sonnet-4-6",
max_tokens: 32,
service_tier: serviceTier,
messages: [{ role: "user", content: "Reply with OK." }],
}),
});
const json = (await res.json()) as {
error?: { message?: string };
stop_reason?: string;
usage?: { service_tier?: string };
};
const errorMessage = json.error?.message ?? `HTTP ${res.status}`;
if (!res.ok) {
if (isLiveBillingDrift(errorMessage)) {
console.warn(`[anthropic:live] skip service_tier ${serviceTier}: billing drift`);
return null;
}
if (isLiveAuthDrift(errorMessage)) {
console.warn(`[anthropic:live] skip service_tier ${serviceTier}: auth drift`);
return null;
}
}
expect(res.ok, errorMessage).toBe(true);
return json;
const res = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers,
body: JSON.stringify({
model: "claude-opus-5",
max_tokens: 32,
fallbacks: "default",
messages: [{ role: "user", content: "Reply with OK." }],
}),
});
const json = (await res.json()) as {
error?: { message?: string };
model?: string;
stop_reason?: string;
};
const standard = await runProbe("standard_only");
if (!standard) {
return;
}
expect(standard.usage?.service_tier).toBe("standard");
expect(standard.stop_reason).toBe("end_turn");
expect(res.ok, json.error?.message ?? `HTTP ${res.status}`).toBe(true);
expect(json.model).toBe("claude-opus-5");
expect(json.stop_reason).toBe("end_turn");
}, 45_000);
const fast = await runProbe("auto");
if (!fast) {
it("verifies Claude Opus 5 native fast-mode contract against the live API", async () => {
const res = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"content-type": "application/json",
"x-api-key": ANTHROPIC_KEY,
"anthropic-version": "2023-06-01",
"anthropic-beta": "fast-mode-2026-02-01",
},
body: JSON.stringify({
model: "claude-opus-5",
max_tokens: 32,
speed: "fast",
messages: [{ role: "user", content: "Reply with OK." }],
}),
});
const json = (await res.json()) as {
error?: { message?: string; type?: string };
stop_reason?: string;
usage?: { speed?: string };
};
if (!res.ok) {
expect(res.status).toBe(429);
expect(json.error?.type).toBe("rate_limit_error");
expect(json.error?.message).toContain("fast mode");
return;
}
expect(["standard", "priority"]).toContain(fast.usage?.service_tier);
expect(fast.stop_reason).toBe("end_turn");
expect(json.usage?.speed).toBe("fast");
expect(json.stop_reason).toBe("end_turn");
}, 45_000);
});