feat(ai): add Claude Fable 5 metadata

This commit is contained in:
Armin Ronacher
2026-06-09 22:42:48 +02:00
Unverified
parent a0c2465d47
commit 5a9d72ea02
7 changed files with 60 additions and 6 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Added
- Added Claude Fable 5 to Anthropic and Amazon Bedrock model metadata, with adaptive thinking and `xhigh` effort support.
### Fixed
- Fixed Amazon Bedrock inference profile ARN region resolution to prefer the ARN's embedded region over `AWS_REGION` ([#5527](https://github.com/earendil-works/pi/pull/5527) by [@AJM10565](https://github.com/AJM10565)).
+8 -1
View File
@@ -232,7 +232,8 @@ function isAnthropicAdaptiveThinkingModel(modelId: string): boolean {
modelId.includes("opus-4-8") ||
modelId.includes("opus-4.8") ||
modelId.includes("sonnet-4-6") ||
modelId.includes("sonnet-4.6")
modelId.includes("sonnet-4.6") ||
modelId.includes("fable-5")
);
}
@@ -294,6 +295,12 @@ function applyThinkingLevelMetadata(model: Model<any>): void {
) {
mergeThinkingLevelMap(model, { xhigh: "xhigh" });
}
if (
(model.api === "anthropic-messages" || model.api === "bedrock-converse-stream") &&
model.id.includes("fable-5")
) {
mergeThinkingLevelMap(model, { xhigh: "xhigh" });
}
if (model.api === "anthropic-messages" && isAnthropicAdaptiveThinkingModel(model.id)) {
mergeAnthropicMessagesCompat(model, { forceAdaptiveThinking: true });
}
+7 -2
View File
@@ -528,13 +528,18 @@ function getModelMatchCandidates(modelId: string, modelName?: string): string[]
function supportsAdaptiveThinking(modelId: string, modelName?: string): boolean {
const candidates = getModelMatchCandidates(modelId, modelName);
return candidates.some(
(s) => s.includes("opus-4-6") || s.includes("opus-4-7") || s.includes("opus-4-8") || s.includes("sonnet-4-6"),
(s) =>
s.includes("opus-4-6") ||
s.includes("opus-4-7") ||
s.includes("opus-4-8") ||
s.includes("sonnet-4-6") ||
s.includes("fable-5"),
);
}
function supportsNativeXhighEffort(model: Model<"bedrock-converse-stream">): boolean {
const candidates = getModelMatchCandidates(model.id, model.name);
return candidates.some((s) => s.includes("opus-4-7") || s.includes("opus-4-8"));
return candidates.some((s) => s.includes("opus-4-7") || s.includes("opus-4-8") || s.includes("fable-5"));
}
function mapThinkingLevelToEffort(
+2 -2
View File
@@ -200,7 +200,7 @@ export interface AnthropicOptions extends StreamOptions {
* Effort level for adaptive thinking models.
* Controls how much thinking Claude allocates:
* - "max": Always thinks with no constraints (Opus 4.6 only)
* - "xhigh": Highest reasoning level (Opus 4.7)
* - "xhigh": Highest reasoning level (Opus 4.7+, Fable 5)
* - "high": Always thinks, deep reasoning
* - "medium": Moderate thinking, may skip for simple queries
* - "low": Minimal thinking, skips for simple tasks
@@ -711,7 +711,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti
/**
* Map ThinkingLevel to Anthropic effort levels for adaptive thinking.
* Note: effort "max" is only valid on Opus 4.6, while Opus 4.7 supports "xhigh".
* Note: effort "max" is only valid on Opus 4.6, while Opus 4.7+ and Fable 5 support "xhigh".
*/
function mapThinkingLevelToEffort(
model: Model<"anthropic-messages">,
@@ -83,6 +83,13 @@ describe("Anthropic forceAdaptiveThinking compat override", () => {
expect(payload.output_config).toEqual({ effort: "medium" });
});
it("uses adaptive thinking with native xhigh effort for Claude Fable 5", async () => {
const payload = await capturePayload(getModel("anthropic", "claude-fable-5"), { reasoning: "xhigh" });
expect(payload.thinking).toEqual({ type: "adaptive", display: "summarized" });
expect(payload.output_config).toEqual({ effort: "xhigh" });
});
it("allows built-in adaptive models to opt out with compat.forceAdaptiveThinking false", async () => {
const model: Model<"anthropic-messages"> = {
...getModel("anthropic", "claude-opus-4-8"),
@@ -83,6 +83,25 @@ describe("Bedrock thinking payload", () => {
expect(payload.additionalModelRequestFields?.anthropic_beta).toBeUndefined();
});
it("uses adaptive thinking for Claude Fable 5 when reasoning is enabled", async () => {
const model = getModel("amazon-bedrock", "global.anthropic.claude-fable-5");
const payload = await capturePayload(model);
expect(payload.additionalModelRequestFields?.thinking).toEqual({ type: "adaptive", display: "summarized" });
expect(payload.additionalModelRequestFields?.output_config).toEqual({ effort: "high" });
expect(payload.additionalModelRequestFields?.anthropic_beta).toBeUndefined();
});
it("maps xhigh reasoning to effort=xhigh for Claude Fable 5", async () => {
const model = getModel("amazon-bedrock", "global.anthropic.claude-fable-5");
const payload = await capturePayload(model, { reasoning: "xhigh" });
expect(payload.additionalModelRequestFields?.thinking).toEqual({ type: "adaptive", display: "summarized" });
expect(payload.additionalModelRequestFields?.output_config).toEqual({ effort: "xhigh" });
});
it("omits display for GovCloud model ids on non-adaptive Claude thinking", async () => {
const baseModel = getModel("amazon-bedrock", "us.anthropic.claude-sonnet-4-5-20250929-v1:0");
const model: Model<"bedrock-converse-stream"> = {
+13 -1
View File
@@ -20,7 +20,13 @@ describe("getSupportedThinkingLevels", () => {
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
});
it("does not include xhigh for non-Opus Anthropic models", () => {
it("includes xhigh for Anthropic Claude Fable 5 on anthropic-messages API", () => {
const model = getModel("anthropic", "claude-fable-5");
expect(model).toBeDefined();
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
});
it("does not include xhigh for Claude Sonnet 4.5", () => {
const model = getModel("anthropic", "claude-sonnet-4-5");
expect(model).toBeDefined();
expect(getSupportedThinkingLevels(model!)).not.toContain("xhigh");
@@ -79,4 +85,10 @@ describe("getSupportedThinkingLevels", () => {
expect(model).toBeDefined();
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
});
it("includes xhigh for Bedrock Claude Fable 5", () => {
const model = getModel("amazon-bedrock", "global.anthropic.claude-fable-5");
expect(model).toBeDefined();
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
});
});