mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-11 22:53:00 +00:00
Fix Sonnet 4.5 effort parameter error by converting to thinking API
claude-sonnet-4-5 doesn't support output_config.effort — it needs the older thinking API with budget_tokens. Add an `effort` feature flag to ModelFeatures and have the Anthropic adapter auto-convert reasoning_effort to a thinking config for models that lack effort support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f0849a796a
commit
3229e58a7e
3 changed files with 70 additions and 26 deletions
|
|
@ -523,6 +523,18 @@ fn extract_thinking_config(
|
|||
.cloned()
|
||||
}
|
||||
|
||||
/// Map a reasoning effort level to a thinking `budget_tokens` value for models
|
||||
/// that don't support the `output_config.effort` parameter (e.g. claude-sonnet-4-5).
|
||||
fn effort_to_budget_tokens(effort: &str, max_tokens: i64) -> i64 {
|
||||
let budget = match effort {
|
||||
"low" => max_tokens / 4,
|
||||
"high" => max_tokens * 3 / 4,
|
||||
_ => max_tokens / 2, // "medium" or unknown
|
||||
};
|
||||
// Anthropic requires budget_tokens >= 1024
|
||||
budget.max(1024)
|
||||
}
|
||||
|
||||
fn is_auto_cache_enabled(provider_options: Option<&serde_json::Value>) -> bool {
|
||||
provider_options
|
||||
.and_then(|opts| opts.get("anthropic"))
|
||||
|
|
@ -1072,22 +1084,48 @@ fn build_api_request(
|
|||
apply_cache_control_to_conversation_prefix(&mut api_messages);
|
||||
}
|
||||
|
||||
let thinking = extract_thinking_config(request.provider_options.as_ref());
|
||||
let explicit_thinking = extract_thinking_config(request.provider_options.as_ref());
|
||||
|
||||
let output_config = request
|
||||
.reasoning_effort
|
||||
.as_ref()
|
||||
.map(|effort| serde_json::json!({"effort": effort}));
|
||||
// Check whether this model supports the `output_config.effort` parameter.
|
||||
// Older reasoning models (e.g. claude-sonnet-4-5) need `thinking` with
|
||||
// `budget_tokens` instead.
|
||||
let model_info = fabro_model::get_model_info(&request.model);
|
||||
let supports_effort = model_info.as_ref().is_none_or(|m| m.features.effort);
|
||||
|
||||
let mut resolved_max_tokens = request
|
||||
.max_tokens
|
||||
.or_else(|| model_info.as_ref().and_then(|m| m.limits.max_output))
|
||||
.unwrap_or(65536);
|
||||
|
||||
let (thinking, output_config) = if let Some(effort) = &request.reasoning_effort {
|
||||
if supports_effort {
|
||||
(
|
||||
explicit_thinking,
|
||||
Some(serde_json::json!({"effort": effort})),
|
||||
)
|
||||
} else if explicit_thinking.is_none() {
|
||||
// Convert effort level to a thinking budget for models that don't
|
||||
// support the effort parameter (e.g. claude-sonnet-4-5).
|
||||
let budget = effort_to_budget_tokens(effort, resolved_max_tokens);
|
||||
if resolved_max_tokens <= budget {
|
||||
resolved_max_tokens = budget + 1024;
|
||||
}
|
||||
(
|
||||
Some(serde_json::json!({"type": "enabled", "budget_tokens": budget})),
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
// thinking already configured via provider_options; skip output_config
|
||||
(explicit_thinking, None)
|
||||
}
|
||||
} else {
|
||||
(explicit_thinking, None)
|
||||
};
|
||||
|
||||
let api_request = ApiRequest {
|
||||
model: request.model.clone(),
|
||||
messages: api_messages,
|
||||
max_tokens: request
|
||||
.max_tokens
|
||||
.or_else(|| {
|
||||
fabro_model::get_model_info(&request.model).and_then(|m| m.limits.max_output)
|
||||
})
|
||||
.unwrap_or(65536),
|
||||
max_tokens: resolved_max_tokens,
|
||||
system: system_value,
|
||||
temperature: request.temperature,
|
||||
top_p: request.top_p,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"display_name": "Claude Opus 4.6",
|
||||
"limits": { "context_window": 1000000, "max_output": 128000 },
|
||||
"training": "2025-08-01",
|
||||
"features": { "tools": true, "vision": true, "reasoning": true },
|
||||
"features": { "tools": true, "vision": true, "reasoning": true, "effort": true },
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 15.0,
|
||||
"output_cost_per_mtok": 75.0,
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
"display_name": "Claude Sonnet 4.6",
|
||||
"limits": { "context_window": 200000, "max_output": 64000 },
|
||||
"training": "2025-08-01",
|
||||
"features": { "tools": true, "vision": true, "reasoning": true },
|
||||
"features": { "tools": true, "vision": true, "reasoning": true, "effort": true },
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 3.0,
|
||||
"output_cost_per_mtok": 15.0,
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
"display_name": "GPT-5.2",
|
||||
"limits": { "context_window": 1047576, "max_output": 128000 },
|
||||
"training": "2025-08-31",
|
||||
"features": { "tools": true, "vision": true, "reasoning": true },
|
||||
"features": { "tools": true, "vision": true, "reasoning": true, "effort": true },
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 1.75,
|
||||
"output_cost_per_mtok": 14.0,
|
||||
|
|
@ -87,7 +87,7 @@
|
|||
"display_name": "GPT-5 Mini",
|
||||
"limits": { "context_window": 1047576, "max_output": 128000 },
|
||||
"training": "2025-08-31",
|
||||
"features": { "tools": true, "vision": true, "reasoning": true },
|
||||
"features": { "tools": true, "vision": true, "reasoning": true, "effort": true },
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 0.25,
|
||||
"output_cost_per_mtok": 2.0,
|
||||
|
|
@ -103,7 +103,7 @@
|
|||
"display_name": "GPT-5.2 Codex",
|
||||
"limits": { "context_window": 1047576, "max_output": 128000 },
|
||||
"training": "2025-08-31",
|
||||
"features": { "tools": true, "vision": true, "reasoning": true },
|
||||
"features": { "tools": true, "vision": true, "reasoning": true, "effort": true },
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 1.75,
|
||||
"output_cost_per_mtok": 14.0,
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
"display_name": "GPT-5.3 Codex",
|
||||
"limits": { "context_window": 1047576, "max_output": 128000 },
|
||||
"training": "2025-08-31",
|
||||
"features": { "tools": true, "vision": true, "reasoning": true },
|
||||
"features": { "tools": true, "vision": true, "reasoning": true, "effort": true },
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 1.75,
|
||||
"output_cost_per_mtok": 14.0,
|
||||
|
|
@ -135,7 +135,7 @@
|
|||
"display_name": "GPT-5.3 Codex Spark",
|
||||
"limits": { "context_window": 131072, "max_output": 128000 },
|
||||
"training": "2025-08-31",
|
||||
"features": { "tools": true, "vision": false, "reasoning": true },
|
||||
"features": { "tools": true, "vision": false, "reasoning": true, "effort": true },
|
||||
"costs": {
|
||||
"input_cost_per_mtok": null,
|
||||
"output_cost_per_mtok": null,
|
||||
|
|
@ -151,7 +151,7 @@
|
|||
"display_name": "GPT-5.4",
|
||||
"limits": { "context_window": 1047576, "max_output": 128000 },
|
||||
"training": "2025-08-31",
|
||||
"features": { "tools": true, "vision": true, "reasoning": true },
|
||||
"features": { "tools": true, "vision": true, "reasoning": true, "effort": true },
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 2.5,
|
||||
"output_cost_per_mtok": 15.0,
|
||||
|
|
@ -168,7 +168,7 @@
|
|||
"display_name": "GPT-5.4 Pro",
|
||||
"limits": { "context_window": 1047576, "max_output": 128000 },
|
||||
"training": "2025-08-31",
|
||||
"features": { "tools": true, "vision": true, "reasoning": true },
|
||||
"features": { "tools": true, "vision": true, "reasoning": true, "effort": true },
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 30.0,
|
||||
"output_cost_per_mtok": 180.0,
|
||||
|
|
@ -184,7 +184,7 @@
|
|||
"display_name": "GPT-5.4 Mini",
|
||||
"limits": { "context_window": 400000, "max_output": 128000 },
|
||||
"training": "2025-08-31",
|
||||
"features": { "tools": true, "vision": true, "reasoning": true },
|
||||
"features": { "tools": true, "vision": true, "reasoning": true, "effort": true },
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 0.75,
|
||||
"output_cost_per_mtok": 4.50,
|
||||
|
|
@ -200,7 +200,7 @@
|
|||
"display_name": "Gemini 3.1 Pro (Preview)",
|
||||
"limits": { "context_window": 1048576, "max_output": 65536 },
|
||||
"training": "2025-01-01",
|
||||
"features": { "tools": true, "vision": true, "reasoning": true },
|
||||
"features": { "tools": true, "vision": true, "reasoning": true, "effort": true },
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 2.0,
|
||||
"output_cost_per_mtok": 12.0,
|
||||
|
|
@ -217,7 +217,7 @@
|
|||
"display_name": "Gemini 3.1 Pro Custom Tools (Preview)",
|
||||
"limits": { "context_window": 1048576, "max_output": 65536 },
|
||||
"training": "2025-01-01",
|
||||
"features": { "tools": true, "vision": true, "reasoning": true },
|
||||
"features": { "tools": true, "vision": true, "reasoning": true, "effort": true },
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 2.0,
|
||||
"output_cost_per_mtok": 12.0,
|
||||
|
|
@ -233,7 +233,7 @@
|
|||
"display_name": "Gemini 3 Flash (Preview)",
|
||||
"limits": { "context_window": 1048576, "max_output": 65536 },
|
||||
"training": "2025-01-01",
|
||||
"features": { "tools": true, "vision": true, "reasoning": true },
|
||||
"features": { "tools": true, "vision": true, "reasoning": true, "effort": true },
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 0.5,
|
||||
"output_cost_per_mtok": 3.0,
|
||||
|
|
@ -249,7 +249,7 @@
|
|||
"display_name": "Gemini 3.1 Flash Lite (Preview)",
|
||||
"limits": { "context_window": 1048576, "max_output": 65536 },
|
||||
"training": "2025-01-01",
|
||||
"features": { "tools": true, "vision": true, "reasoning": true },
|
||||
"features": { "tools": true, "vision": true, "reasoning": true, "effort": true },
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 0.25,
|
||||
"output_cost_per_mtok": 1.5,
|
||||
|
|
@ -316,7 +316,7 @@
|
|||
"display_name": "Mercury 2",
|
||||
"limits": { "context_window": 131072, "max_output": 50000 },
|
||||
"training": null,
|
||||
"features": { "tools": true, "vision": false, "reasoning": true },
|
||||
"features": { "tools": true, "vision": false, "reasoning": true, "effort": true },
|
||||
"costs": {
|
||||
"input_cost_per_mtok": 0.25,
|
||||
"output_cost_per_mtok": 0.75,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ pub struct ModelFeatures {
|
|||
pub tools: bool,
|
||||
pub vision: bool,
|
||||
pub reasoning: bool,
|
||||
/// Whether the model supports the `reasoning_effort` / `effort` parameter
|
||||
/// directly (e.g. Anthropic `output_config.effort`, OpenAI `reasoning.effort`).
|
||||
/// Models with `reasoning=true` but `effort=false` (e.g. claude-sonnet-4-5)
|
||||
/// need the older `thinking` API with `budget_tokens` instead.
|
||||
#[serde(default)]
|
||||
pub effort: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue