mirror of
https://github.com/block/goose.git
synced 2026-06-02 06:19:33 +02:00
fix(anthropic): adaptive thinking + omit sampling params for Opus 4.7/4.8
Address codex review feedback on #9484: - P1: Extend supports_adaptive_thinking() to match Opus 4.7 and 4.8. These models accept adaptive thinking only and reject manual budget_tokens thinking with HTTP 400, so reasoning requests (GOOSE_THINKING_EFFORT / legacy CLAUDE_THINKING_TYPE=adaptive) now send the adaptive payload. - P2: Add rejects_sampling_params() and omit temperature for Opus 4.7/4.8, whose Messages API rejects non-default sampling params (temperature/top_p/top_k) with HTTP 400. Adds tests covering adaptive thinking and temperature suppression for the new models. Signed-off-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
@@ -69,7 +69,17 @@ impl AnthropicFormatOptions {
|
||||
|
||||
pub fn supports_adaptive_thinking(model_name: &str) -> bool {
|
||||
let lower = model_name.to_lowercase();
|
||||
lower.contains("claude-opus-4-6") || lower.contains("claude-sonnet-4-6")
|
||||
lower.contains("claude-opus-4-6")
|
||||
|| lower.contains("claude-sonnet-4-6")
|
||||
|| lower.contains("claude-opus-4-7")
|
||||
|| lower.contains("claude-opus-4-8")
|
||||
}
|
||||
|
||||
/// Anthropic's Opus 4.7+ Messages API rejects non-default sampling parameters
|
||||
/// (`temperature`, `top_p`, `top_k`) with HTTP 400, so they must be omitted.
|
||||
pub fn rejects_sampling_params(model_name: &str) -> bool {
|
||||
let lower = model_name.to_lowercase();
|
||||
lower.contains("claude-opus-4-7") || lower.contains("claude-opus-4-8")
|
||||
}
|
||||
|
||||
pub fn thinking_type(model_config: &ModelConfig) -> ThinkingType {
|
||||
@@ -649,10 +659,12 @@ pub fn create_request_with_options(
|
||||
}
|
||||
|
||||
if let Some(temp) = model_config.temperature {
|
||||
payload
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("temperature".to_string(), json!(temp));
|
||||
if !rejects_sampling_params(&model_config.model_name) {
|
||||
payload
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("temperature".to_string(), json!(temp));
|
||||
}
|
||||
}
|
||||
|
||||
apply_thinking_config(&mut payload, model_config, max_tokens, options);
|
||||
@@ -1206,6 +1218,54 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_request_adaptive_thinking_for_47_and_48_models() -> Result<()> {
|
||||
let _guard = env_lock::lock_env([("GOOSE_THINKING_EFFORT", None::<&str>)]);
|
||||
|
||||
for model in ["claude-opus-4-7", "claude-opus-4-8"] {
|
||||
let mut params = std::collections::HashMap::new();
|
||||
params.insert("thinking_effort".to_string(), json!("high"));
|
||||
|
||||
let mut config = cfg(model);
|
||||
config.max_tokens = Some(4096);
|
||||
config.request_params = Some(params);
|
||||
let messages = vec![Message::user().with_text("Hello")];
|
||||
let payload = create_request(&config, "system", &messages, &[])?;
|
||||
|
||||
assert_eq!(payload["thinking"]["type"], "adaptive", "model {model}");
|
||||
assert_eq!(payload["output_config"]["effort"], "high", "model {model}");
|
||||
assert!(payload.get("budget_tokens").is_none(), "model {model}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_request_omits_temperature_for_47_and_48_models() -> Result<()> {
|
||||
let _guard = env_lock::lock_env([("GOOSE_THINKING_EFFORT", None::<&str>)]);
|
||||
|
||||
for model in ["claude-opus-4-7", "claude-opus-4-8"] {
|
||||
let mut config = cfg(model);
|
||||
config.temperature = Some(0.7);
|
||||
let messages = vec![Message::user().with_text("Hello")];
|
||||
let payload = create_request(&config, "system", &messages, &[])?;
|
||||
|
||||
assert!(
|
||||
payload.get("temperature").is_none(),
|
||||
"model {model} should omit temperature"
|
||||
);
|
||||
}
|
||||
|
||||
// Older models still send temperature.
|
||||
let mut config = cfg("claude-opus-4-6");
|
||||
config.temperature = Some(0.7);
|
||||
let messages = vec![Message::user().with_text("Hello")];
|
||||
let payload = create_request(&config, "system", &messages, &[])?;
|
||||
assert!(payload.get("temperature").is_some());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_request_enabled_thinking_with_budget() -> Result<()> {
|
||||
let _guard = env_lock::lock_env([
|
||||
@@ -1519,6 +1579,15 @@ mod tests {
|
||||
thinking_type(&cfg_with_effort("claude-opus-4-6", "high")),
|
||||
ThinkingType::Adaptive
|
||||
);
|
||||
// Opus 4.7/4.8 are adaptive-only models
|
||||
assert_eq!(
|
||||
thinking_type(&cfg_with_effort("claude-opus-4-7", "high")),
|
||||
ThinkingType::Adaptive
|
||||
);
|
||||
assert_eq!(
|
||||
thinking_type(&cfg_with_effort("claude-opus-4-8", "high")),
|
||||
ThinkingType::Adaptive
|
||||
);
|
||||
// Adaptive model with off → disabled
|
||||
assert_eq!(
|
||||
thinking_type(&cfg_with_effort("claude-opus-4-6", "off")),
|
||||
|
||||
Reference in New Issue
Block a user