fix(provider): generalize Claude adaptive thinking (#38757)
This commit is contained in:
@@ -635,24 +635,23 @@ function openaiCompatibleReasoningEfforts(id: string) {
|
||||
return gpt5CodexReasoningEfforts(apiId) ?? versionedGpt5ReasoningEfforts(apiId) ?? OPENAI_EFFORTS
|
||||
}
|
||||
|
||||
function anthropicOpus47OrLater(apiId: string) {
|
||||
// Matches "opus-4.7" (Anthropic/Bedrock/Vertex) and "claude-4.7-opus" (SAP AI Core inverted).
|
||||
// Greedy \d+ correctly extends to multi-digit majors (e.g. "claude-10.0-opus") for forward compatibility.
|
||||
const version = /opus-(\d+)[.-](\d+)(?:[.@-]|$)|claude-(\d+)[.-](\d+)-opus(?:[.@-]|$)/i.exec(apiId)
|
||||
if (!version) return false
|
||||
const major = Number(version[1] ?? version[3])
|
||||
const minor = Number(version[2] ?? version[4])
|
||||
function anthropicUsesModernAdaptiveThinking(apiId: string) {
|
||||
if (!apiId.toLowerCase().includes("claude-")) return false
|
||||
// Covers family-first IDs such as claude-opus-4.7 and version-first IDs such as claude-4.7-opus.
|
||||
// Limit minors to two digits so release dates in IDs such as claude-opus-4-20250514 are not versions.
|
||||
const version = /claude-(?:[a-z]+-)?(\d+)(?:[.-](\d{1,2}))?(?:[.@-]|$)/i.exec(apiId)
|
||||
if (!version) return true
|
||||
const major = Number(version[1])
|
||||
const minor = Number(version[2] ?? 0)
|
||||
return major > 4 || (major === 4 && minor >= 7)
|
||||
}
|
||||
|
||||
function anthropicSonnet5OrLater(apiId: string) {
|
||||
const version = /sonnet-(\d+)(?:[.@-]|$)|claude-(\d+)-sonnet(?:[.@-]|$)/i.exec(apiId)
|
||||
if (!version) return false
|
||||
return Number(version[1] ?? version[2]) >= 5
|
||||
function anthropicOpus45(apiId: string) {
|
||||
return ["opus-4-5", "opus-4.5"].some((value) => apiId.includes(value))
|
||||
}
|
||||
|
||||
function anthropicAdaptiveEfforts(apiId: string): string[] | null {
|
||||
if (anthropicOpus47OrLater(apiId) || anthropicSonnet5OrLater(apiId) || apiId.includes("fable-5")) {
|
||||
if (anthropicUsesModernAdaptiveThinking(apiId)) {
|
||||
return ["low", "medium", "high", "xhigh", "max"]
|
||||
}
|
||||
if (
|
||||
@@ -666,7 +665,7 @@ function anthropicAdaptiveEfforts(apiId: string): string[] | null {
|
||||
}
|
||||
|
||||
function anthropicOmitsThinking(apiId: string) {
|
||||
return anthropicOpus47OrLater(apiId) || anthropicSonnet5OrLater(apiId) || apiId.includes("fable-5")
|
||||
return anthropicUsesModernAdaptiveThinking(apiId)
|
||||
}
|
||||
|
||||
function googleThinkingLevelEfforts(apiId: string) {
|
||||
@@ -990,8 +989,10 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
||||
)
|
||||
}
|
||||
|
||||
if (["opus-4-5", "opus-4.5"].some((v) => model.api.id.includes(v))) {
|
||||
return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { effort }]))
|
||||
if (anthropicOpus45(model.api.id)) {
|
||||
return Object.fromEntries(
|
||||
WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, anthropicOpus45Effort(model, effort)]),
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -1103,7 +1104,7 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
|
||||
if (id.includes("anthropic")) {
|
||||
if (adaptiveEfforts) {
|
||||
// Bedrock adaptive splits `effort` out into `output_config` (vs Anthropic
|
||||
// native which inlines it). Opus 4.7+ flipped `display` default to "omitted".
|
||||
// native which inlines it). Claude 4.7+ defaults `display` to "omitted".
|
||||
return wrapInSapModelParams(
|
||||
Object.fromEntries(
|
||||
adaptiveEfforts.map((effort) => [
|
||||
@@ -1711,6 +1712,14 @@ function reasoningEffort(model: Provider.Model, effort: string) {
|
||||
...(anthropicOmitsThinking(model.api.id) ? { display: "summarized" } : {}),
|
||||
},
|
||||
}
|
||||
if (anthropicOpus45(model.api.id))
|
||||
return {
|
||||
reasoningConfig: {
|
||||
type: "enabled",
|
||||
budgetTokens: Math.min(16_000, Math.floor(model.limit.output / 2 - 1)),
|
||||
maxReasoningEffort: effort,
|
||||
},
|
||||
}
|
||||
if (model.api.id.includes("anthropic")) return
|
||||
return { reasoningConfig: { type: "enabled", maxReasoningEffort: effort } }
|
||||
case "@ai-sdk/gateway":
|
||||
@@ -1751,7 +1760,7 @@ function reasoningEffort(model: Provider.Model, effort: string) {
|
||||
}
|
||||
|
||||
function anthropicEffort(model: Provider.Model, effort: string) {
|
||||
if (["opus-4-5", "opus-4.5"].some((value) => model.api.id.includes(value))) return { effort }
|
||||
if (anthropicOpus45(model.api.id)) return anthropicOpus45Effort(model, effort)
|
||||
// Kimi defaults to omitting adaptive thinking text unless summarized display is requested.
|
||||
if (isKimiFamily(model)) return { thinking: { type: "adaptive", display: "summarized" }, effort }
|
||||
if (!anthropicAdaptiveEfforts(model.api.id)) return
|
||||
@@ -1764,6 +1773,16 @@ function anthropicEffort(model: Provider.Model, effort: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function anthropicOpus45Effort(model: Provider.Model, effort: string) {
|
||||
return {
|
||||
thinking: {
|
||||
type: "enabled",
|
||||
budgetTokens: Math.min(16_000, Math.floor(model.limit.output / 2 - 1)),
|
||||
},
|
||||
effort,
|
||||
}
|
||||
}
|
||||
|
||||
function reasoningBudget(model: Provider.Model, budget: number) {
|
||||
switch (model.api.npm) {
|
||||
case "@openrouter/ai-sdk-provider":
|
||||
|
||||
Reference in New Issue
Block a user