Fable 5 降级计费
降级模型的计费机制
🌐 查看英文原文 | 源码 Notebook
分类器降级与计费
Claude Fable 5 在网络安全、生物学和化学等领域的先进能力带来了真实的滥用风险:使其有用的技能同样可能帮助恶意行为者构建网络攻击或危险武器。因此,Claude Fable 5 内置了限制其在这些特定领域表现的防护措施,并且每个请求都会运行自动安全检查。这些检查会在以下三个领域阻止请求:
- 攻击性网络安全技术——构建漏洞利用、恶意软件或攻击工具
- 生物学与生命科学——实验方法或分子机制
- 提取模型的摘要思维
这些防护措施有意设计得比较保守。它们首先为稳健性而调优,这意味着良性的技术工作有时也会触发它们。我们发布 Fable 5 时在所有与生物学和网络安全相关的主题上启用了到 Opus 4.8 的降级,以此作为更快地将 Fable 的 Mythos 级能力带给你在其他所有领域的方式。我们将在发布后继续降低 Fable 5 的误报率。
API 客户应配置从 Claude Fable 5 到 Opus 4.8 的降级——可以使用内置的服务端降级功能(在原生 Claude API 和 AWS 上的 Claude Platform 上可用),也可以使用基于 Anthropic SDK 辅助工具构建的客户端降级逻辑。
我们还进行了计费变更,使客户在大多数 Fable 5 降级情况下不会产生 token 费用。当你不使用服务端降级功能时,需要采取行动来应用这些变更——参见下文。
本指南涵盖的内容
%%capture
%pip install -U "anthropic>=0.108.0"import os
from dotenv import load_dotenv
load_dotenv()
PRIMARY_MODEL = "claude-fable-5"
FALLBACK_MODEL = "claude-opus-4-8"
SERVER_SIDE_FALLBACK_BETA = "server-side-fallback-2026-06-01"
FALLBACK_CREDIT_BETA = "fallback-credit-2026-06-01"
# Anthropic() reads ANTHROPIC_API_KEY from the environment. Add it to a .env
# file (loaded above) or export it in your shell before running the live examples.
if not os.environ.get("ANTHROPIC_API_KEY"):
print(
"ANTHROPIC_API_KEY is not set - add it to .env or export it."
)1. 分类器阻止是什么样的
分类器阻止是 API 在请求似乎违反我们的防护措施时返回的内容。API 返回 200,带有 stop_reason: "refusal" 和描述类别的 stop_details 对象:
{
"stop_reason": "refusal",
"stop_details": {
"type": "refusal",
"category": "cyber",
"explanation": "This request triggered restrictions on violative cyber content and was blocked under Anthropic's Usage Policy..."
},
"content": [...]
}根据 stop_reason 来分支你的逻辑,而不是根据 content 或 stop_details。stop_details 是信息性的,可以为 null,此时你应将其视为通用拒绝(不特指以下类别)。
当存在时,category 是 "cyber"、"bio" 或 "reasoning_extraction" 之一。这可以帮助你优化降级选择:
| 类别 | 触发条件 |
|---|---|
cyber |
攻击性网络安全内容(漏洞利用、恶意软件、攻击工具) |
bio |
生物学/生命科学内容(实验方法、分子机制) |
reasoning_extraction |
试图提取模型摘要思维的请求 |
分类器阻止与模型拒绝(模型本身因其他策略原因而拒绝)不同。两者都表现为 stop_reason: "refusal",但 stop_details.category 可以告诉你被哪个分类器阻止了。
注意: 当你不使用服务端降级功能时,
stop_details还包含一个fallback_credit_token,你可以用它将降级模型请求计费为缓存读取——参见计费变更。
2. 服务端降级(推荐)
Messages API 可以为你执行降级。传入 fallbacks 为 [{"model": "claude-opus-4-8"}] 和 server-side-fallback-2026-06-01 beta header。如果 Fable 的分类器阻止了该轮次,API 会自动使用 Opus 4.8 重试——并添加注释以便你知道发生了什么。
自动降级功能目前在 Claude API 和 AWS 上的 Claude Platform 上受支持。目前它仅支持从 Fable 5 降级到 Opus 4.8;我们预计后续会扩展。
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: server-side-fallback-2026-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-fable-5",
"max_tokens": 1024,
"fallbacks": [
{ "model": "claude-opus-4-8" }
],
"messages": [
{ "role": "user", "content": "Hello, world" }
]
}'当降级无法执行时
当你已配置 fallbacks 但 API 无法访问降级模型时——其速率限制已耗尽或已过载——该轮次仍会返回为拒绝,stop_details.recommended_model 会指定可直接重试的标准模型 ID:
{
"stop_reason": "refusal",
"stop_details": {
"type": "refusal",
"category": "cyber",
"recommended_model": "claude-opus-4-8"
}
}recommended_model 仅在此情况下被填充(已配置降级且降级无法执行)。在未配置降级的普通阻止中,它不会出现——这就是为什么第 1 节的基本示例中没有它的原因。
# Fable applies extra safety filters. With a fallback chain configured, the API
# retries blocked turns on the next model server-side. A stop_reason of "refusal"
# means the whole chain refused.
from anthropic import Anthropic
client = Anthropic()
def chat_turn(messages, max_tokens=1024):
"""One API call; the server handles the fallback."""
return client.beta.messages.create(
model=PRIMARY_MODEL,
max_tokens=max_tokens,
messages=messages,
betas=[SERVER_SIDE_FALLBACK_BETA],
fallbacks=[{"model": FALLBACK_MODEL}],
)检测降级(非流式)
降级响应在每个切换点携带一个 {"type": "fallback"} 内容块,usage.iterations 记录每个模型的使用量。请注意,粘性服务的轮次——因为对话中较早的轮次发生了降级而直接路由到降级模型的轮次——不携带 fallback 块,因为请求是直接路由的,content 中没有可标记的边界。usage.iterations 是判断降级模型是否服务了该轮次的可靠方法。
def fallback_hops(response):
"""(from_model, to_model) for each hop that ran and blocked this turn."""
hops = []
for b in response.content:
if getattr(b, "type", None) == "fallback":
d = b.model_dump() if hasattr(b, "model_dump") else dict(b)
hops.append((d["from"]["model"], d["to"]["model"]))
return hops
def served_by_fallback(response):
"""True whenever a fallback model served the response, INCLUDING a
sticky-served turn (which carries no fallback block). usage.iterations is
the best way to check whether a turn was served by a fallback model."""
iters = getattr(response.usage, "iterations", None) or []
return any(
(i.get("type") if isinstance(i, dict) else getattr(i, "type", None))
== "fallback_message"
for i in iters
)
response = chat_turn([{"role": "user", "content": "Hello, world"}])
hops = fallback_hops(response)
for from_model, to_model in hops:
print(f"[{from_model} blocked \u2014 continued on {to_model}]")
if not hops and served_by_fallback(response):
print(f"[sticky: served directly by {response.model}]")在流式传输中检测降级
留意 content_block_start 事件中 block 为 {"type": "fallback"} 的情况——这标记了流内切换点。但要获得确定的每轮答案,请检查最终消息上的 usage.iterations,与非流式响应完全相同。该检查在每种情况下都是可靠的,包括由降级模型直接服务的流,因此请将其作为事实来源,而不要仅依赖流内事件。
with client.beta.messages.stream(
model=PRIMARY_MODEL,
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, world"}],
betas=[SERVER_SIDE_FALLBACK_BETA],
fallbacks=[{"model": FALLBACK_MODEL}],
) as stream:
for event in stream:
if (
getattr(event, "type", None) == "content_block_start"
and getattr(event.content_block, "type", None) == "fallback"
):
fb = event.content_block
fb = fb.model_dump() if hasattr(fb, "model_dump") else dict(fb)
print(f"[switching: {fb['from']['model']} -> {fb['to']['model']}]")
final = stream.get_final_message()
# Definitive per-turn check, same as non-streaming: usage.iterations also
# catches a stream served directly by the fallback model (no in-stream event).
if served_by_fallback(final):
print(f"[fallback model served this stream: {final.model}]")降级响应的结构
降级响应包含 message.model(最终回答的模型)、标记每个切换点的 {"type": "fallback"} 内容块,以及 usage.iterations 中的每次尝试使用量:
{
"id": "msg_01Ab...",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-8",
"content": [
{ "type": "fallback", "from": { "model": "claude-fable-5" }, "to": { "model": "claude-opus-4-8" } },
{ "type": "text", "text": "..." }
],
"stop_reason": "end_turn",
"stop_details": null,
"usage": {
"input_tokens": 412, "output_tokens": 264,
"cache_read_input_tokens": 0, "cache_creation_input_tokens": 0,
"iterations": [
{ "type": "message", "model": "claude-fable-5", "input_tokens": 408, "output_tokens": 0,
"cache_read_input_tokens": 0, "cache_creation_input_tokens": 0 },
{ "type": "fallback_message", "model": "claude-opus-4-8", "input_tokens": 412, "output_tokens": 264,
"cache_read_input_tokens": 0, "cache_creation_input_tokens": 0 }
]
}
}每次尝试的覆盖。 每个降级条目可以仅覆盖该次尝试的 max_tokens、thinking、output_config 和 speed(output_config 和 speed 还需要与相应的顶级字段相同的 beta headers)。合并了条目覆盖设置的请求必须是该条目模型的正确格式化的直接请求。
{
"model": "claude-fable-5",
"max_tokens": 1024,
"fallbacks": [
{ "model": "claude-opus-4-8", "max_tokens": 8192, "thinking": {"type": "disabled"}, "speed": "fast" }
],
"messages": [
{ "role": "user", "content": "Hello, world" }
]
}计费。 usage.input_tokens 对该轮次只计费一次。usage.output_tokens 反映的是回答。如果你需要精确的按模型归属,请使用 usage.iterations。
3. 流式传输
在流式传输中,降级设计为自动工作。如果分类器在任何输出到达你之前就阻止了请求,流将以降级模型的响应开始。此重试是不可见的,不会发出降级 SSE 事件。
如果分类器在流中间阻止了请求,重试也会在同一流上进行:部分输出会被保留,{"type": "fallback"} 内容块标记边界,降级模型从部分输出继续。已流式传输的内容永远不会被丢弃。
def stream_turn(messages, max_tokens=1024):
with client.beta.messages.stream(
model=PRIMARY_MODEL,
max_tokens=max_tokens,
messages=messages,
betas=[SERVER_SIDE_FALLBACK_BETA],
fallbacks=[{"model": FALLBACK_MODEL}],
) as stream:
# Nothing streamed is ever discarded: after a mid-stream block, the
# final message is partial + fallback block + continuation.
final = stream.get_final_message()
if final.stop_reason == "refusal":
return final # the whole chain refused
text = "".join(b.text for b in final.content if b.type == "text")
print(f"{final.model}: {text}")
return final4. 计费变更
我们进行了计费变更,以最小化降级的成本影响。当你使用降级和 Anthropic SDK 辅助工具时,这些变更会自动应用。只有在采用缓存未命中计费变更时才需要采取行动,且仅当你不使用服务端降级时。
1. 直接分类器阻止不收取输入 token 费用(即请求在任何输出 token 返回之前被阻止时)。无需采取行动——这已自动应用于所有生产模型,包括 Fable 5。
2. Fable 5 → Opus 4.8 降级的输入 token 按缓存命中计费。 通常,切换到另一个模型按缓存写入计费,比基础输入 token 成本高 1.25 倍(5 分钟 TTL)或 2 倍(60 分钟 TTL)。而我们将这些 Opus token 按已经缓存来计费——即按缓存读取计费,为基础输入 token 价格的 10%。
- 使用服务端降级功能: 此计费变更自动应用。
- 不使用服务端降级功能: 请参见下方的信用令牌流程。
兑现降级信用令牌(仅限客户端降级)
被安全分类器阻止的 Fable 请求会在 stop_details 中包含一个 fallback_credit_token。该令牌仅在被阻止的请求有可计费的缓存前缀时才存在,否则为 null。
要兑现它:
- 发送后续的 Opus 4.8 请求时带上
anthropic-beta: fallback-credit-2026-06-01header。 - 将令牌作为顶级
fallback_credit_token参数传入。 - 保持 prompt 构建字段与被阻止的请求完全相同——完全一致的
system、messages和tools。
然后,在 Fable 请求上缓存的前缀将按缓存读取费率而不是缓存写入费率计费。切换成本将被退还,因此重试的费用与对话一直在 Opus 上进行时一样。
有效性: 该令牌仅在被阻止的 Fable 5 请求后 5 分钟内发起且来自相同组织和 workspace 的 Opus 4.8 请求上有效。
流中阻止。 如果 Fable 5 请求在流式输出 token 中途被阻止,stop_details 还会在信用令牌旁边包含 fallback_has_prefill_claim: true。这意味着在后续的 Opus 4.8 请求中,你可以将该部分输出作为 assistant prefill 追加,并从 Fable 停止的地方继续——这在 Opus 4.8 请求中通常是不允许的。
def redeem_credit_after_block(blocked_response, messages, max_tokens=1024):
"""Retry a classifier-blocked Fable turn on Opus 4.8, redeeming the
fallback credit token so the cached prefix is billed at the cache-read
rate. Use this only when you are NOT using server-side fallback."""
details = blocked_response.stop_details
credit = getattr(details, "fallback_credit_token", None) if details else None
extra = {}
betas = []
if credit is not None: # present only when the blocked request had a cached prefix
betas.append(FALLBACK_CREDIT_BETA)
extra["fallback_credit_token"] = credit
# The system, messages, and tools must be IDENTICAL to the blocked request.
return client.beta.messages.create(
model=FALLBACK_MODEL,
max_tokens=max_tokens,
messages=messages,
betas=betas or None,
extra_body=extra or None,
)5. 使用 SDK 的客户端降级
服务端降级在原生 Claude API 和 AWS 上的 Claude Platform 上可用,但目前不在 Amazon Bedrock、Vertex AI、Microsoft Foundry 或 Message Batches API 上可用。对于这些平台,或者任何时候你想在客户端中实现降级逻辑,Anthropic SDK(Python、TypeScript、Go、Java、C#)都内置了拒绝降级中间件。
在一个客户端上配置一次你的降级模型列表和 BetaFallbackState,然后像往常一样调用 client.beta.messages。该中间件会:
- 在列表中的下一个模型上重试
stop_reason: "refusal"的轮次(如果降级也拒绝,则继续沿链向下;如果每个条目都拒绝,则返回原始拒绝而不是抛出异常); - 在每次请求时自动发送
fallback-credit-2026-06-01beta header,这样你就可以获得第 4 节中的缓存读取计费变更,而无需自己管理令牌; - 为你管理对话历史中的
fallback内容块; - 将接受的模型记录在
BetaFallbackState中,以便后续轮次保持绑定到该模型。
它与服务端的 fallbacks 参数互斥;使用其中一个即可。(要从安装了中间件的应用发送服务端 fallbacks 请求,请使用不带中间件的单独客户端实例。)
from anthropic import Anthropic, BetaFallbackState, BetaRefusalFallbackMiddleware
# Install the middleware once, with your fallback chain. No per-request betas needed.
client = Anthropic(
middleware=[BetaRefusalFallbackMiddleware([{"model": FALLBACK_MODEL}])],
)
state = BetaFallbackState() # reuse across turns to pin follow-ups to the accepting model
# Non-streaming: a refused Fable turn is retried on Opus 4.8 transparently.
with state:
message = client.beta.messages.create(
model=PRIMARY_MODEL,
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
)
print(f"served by: {message.model}")
# Streaming: on a refusal the middleware splices the fallback model's events
# onto the same open stream.
with (
state,
client.beta.messages.stream(
model=PRIMARY_MODEL,
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
) as stream,
):
for event in stream:
if event.type == "text":
print(event.text, end="", flush=True)
final = stream.get_final_message()
print(f"\nserved by: {final.model}")6. 常见反模式
在每次请求上设置降级,而不是每个账户设置一次。 没有账户级别或会话级别的开关来启用 Opus 4.8 降级。每个 API 调用都必须包含降级配置。未启用降级的调用会返回拒绝,而不是静默在降级模型上重试。
审查每个构建请求的代码路径。 重试按钮、消息重新生成和工具使用延续等功能通常会构建自己的请求,每个都可能静默忽略降级配置。在每个入口点显式设置降级。(参见迁移指南和 claude-api skill 以快速在整个代码库中添加降级。)
在子 agent 调用中包含降级,并预期每个 agent 的独立行为。 如果你的应用在一个会话中运行多个 agent,每个 agent 的调用都需要降级配置。当发生拒绝时,只有收到拒绝的 agent 会切换到降级模型——其他 agent 继续使用 Fable。不要假设一个 agent 的降级适用于整个会话。(Claude Code 中的子 agent 也是如此:只有遇到拒绝的子 agent 会降级到 Opus;会话的其余部分继续使用 Fable 5。)
在拒绝后重新提交相同的请求只会再次被拒绝。 被拒绝的内容仍在对话历史中,因此向同一模型重新提交会再次触发阻止。在 stop_reason: "refusal" 时,在降级模型上重试,并设置一个单独的指示器,以便你的路由器知道在对话的剩余部分保持在降级模型上。
根据 usage.iterations 而不是你请求的模型来构建服务模型分析。 响应的 model 字段是实际回答的模型,因此降级服务的轮次会报告 Opus 4.8。每当使用降级时,针对请求的模型记录的分析就会出错。可靠的每轮检查是最终使用记录中的 usage.iterations。
谨慎处理流式截断。 当你在流中途遇到分类器阻止时,忽略降级之前出现的任何 thinking、tool_use 或其他块。截断的 tool_use 块是无法解析的 JSON,另一个模型的 thinking 块会破坏下一次调用。如果你在不使用服务端降级的情况下继续,请使用 stop_details 中的 fallback_has_prefill_claim 授权,而不是将部分响应粘贴到已完成的 assistant 轮次中。
中文读者提示:本章节的代码和输出为英文原文。如需理解具体实现细节,可参考上方中文导读和代码注释。如有疑问,欢迎在 GitHub Issues 讨论。