claude adaptive thinking (#7944)

Signed-off-by: Angie Jones <angie@Angies-MBP.lan>
Co-authored-by: Angie Jones <angie@Angies-MacBook-Pro.local>
Co-authored-by: Angie Jones <angie@Angies-MBP.lan>
This commit is contained in:
Angie Jones
2026-03-16 23:00:53 -05:00
committed by GitHub
parent 4604354bc2
commit 28cab714b6
3 changed files with 108 additions and 45 deletions
@@ -1,5 +1,6 @@
use crate::conversation::message::{Message, MessageContent};
use crate::model::ModelConfig;
use crate::providers::formats::anthropic::{thinking_effort, thinking_type, ThinkingType};
use crate::providers::utils::{
convert_image, detect_image_path, is_valid_function_name, load_image_file, safely_parse_json,
sanitize_function_name, ImageFormat,
@@ -231,6 +232,50 @@ fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<Data
result
}
fn apply_claude_thinking_config(payload: &mut Value, model_config: &ModelConfig) {
let obj = payload.as_object_mut().unwrap();
match thinking_type(model_config) {
ThinkingType::Adaptive => {
obj.insert("thinking".to_string(), json!({ "type": "adaptive" }));
obj.insert(
"output_config".to_string(),
json!({ "effort": thinking_effort(model_config).to_string() }),
);
obj.insert(
"max_completion_tokens".to_string(),
json!(model_config.max_output_tokens()),
);
}
ThinkingType::Enabled => {
let budget_tokens = model_config
.get_config_param::<i32>("budget_tokens", "CLAUDE_THINKING_BUDGET")
.unwrap_or(16000)
.max(1024);
let max_tokens = model_config.max_output_tokens() + budget_tokens;
obj.insert("max_tokens".to_string(), json!(max_tokens));
obj.insert(
"thinking".to_string(),
json!({
"type": "enabled",
"budget_tokens": budget_tokens
}),
);
obj.insert("temperature".to_string(), json!(2));
}
ThinkingType::Disabled => {
if let Some(temp) = model_config.temperature {
obj.insert("temperature".to_string(), json!(temp));
}
obj.insert(
"max_completion_tokens".to_string(),
json!(model_config.max_output_tokens()),
);
}
}
}
pub fn format_tools(tools: &[Tool], model_name: &str) -> anyhow::Result<Vec<Value>> {
let mut tool_names = std::collections::HashSet::new();
let mut result = Vec::new();
@@ -540,11 +585,7 @@ pub fn create_request(
));
}
let model_name = model_config.model_name.to_string();
let is_openai_reasoning_model = model_config.is_openai_reasoning_model();
let is_claude_sonnet =
model_name.contains("claude-3-7-sonnet") || model_name.contains("claude-4-sonnet"); // can be goose- or databricks-
let (model_name, reasoning_effort) = if is_openai_reasoning_model {
let parts: Vec<&str> = model_config.model_name.split('-').collect();
let last_part = parts.last().unwrap();
@@ -602,34 +643,8 @@ pub fn create_request(
.insert("tools".to_string(), json!(tools_spec));
}
let is_thinking_enabled = std::env::var("CLAUDE_THINKING_ENABLED").is_ok();
if is_claude_sonnet && is_thinking_enabled {
// Anthropic requires budget_tokens >= 1024
const DEFAULT_THINKING_BUDGET: i32 = 16000;
let budget_tokens: i32 = std::env::var("CLAUDE_THINKING_BUDGET")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(DEFAULT_THINKING_BUDGET);
// With thinking enabled, max_tokens must include both output and thinking budget
let max_tokens = model_config.max_output_tokens() + budget_tokens;
payload
.as_object_mut()
.unwrap()
.insert("max_tokens".to_string(), json!(max_tokens));
payload.as_object_mut().unwrap().insert(
"thinking".to_string(),
json!({
"type": "enabled",
"budget_tokens": budget_tokens
}),
);
payload
.as_object_mut()
.unwrap()
.insert("temperature".to_string(), json!(2));
if is_claude_model(&model_config.model_name) {
apply_claude_thinking_config(&mut payload, model_config);
} else {
// open ai reasoning models currently don't support temperature
if !is_openai_reasoning_model {
@@ -1062,6 +1077,51 @@ mod tests {
Ok(())
}
#[test]
fn test_create_request_adaptive_thinking_for_46_models() -> anyhow::Result<()> {
let _guard = env_lock::lock_env([
("CLAUDE_THINKING_TYPE", Some("adaptive")),
("CLAUDE_THINKING_EFFORT", Some("low")),
("CLAUDE_THINKING_ENABLED", None::<&str>),
("CLAUDE_THINKING_BUDGET", None::<&str>),
]);
let mut model_config = ModelConfig::new_or_fail("databricks-claude-opus-4-6");
model_config.max_tokens = Some(4096);
let request = create_request(&model_config, "system", &[], &[], &ImageFormat::OpenAi)?;
assert_eq!(request["thinking"]["type"], "adaptive");
assert_eq!(request["output_config"]["effort"], "low");
assert!(request.get("temperature").is_none());
assert_eq!(request["max_completion_tokens"], 4096);
assert!(request.get("max_tokens").is_none());
Ok(())
}
#[test]
fn test_create_request_enabled_thinking_with_budget() -> anyhow::Result<()> {
let _guard = env_lock::lock_env([
("CLAUDE_THINKING_TYPE", None::<&str>),
("CLAUDE_THINKING_ENABLED", Some("1")),
("CLAUDE_THINKING_BUDGET", Some("10000")),
]);
let mut model_config = ModelConfig::new_or_fail("databricks-claude-3-7-sonnet");
model_config.max_tokens = Some(4096);
let request = create_request(&model_config, "system", &[], &[], &ImageFormat::OpenAi)?;
assert_eq!(request["thinking"]["type"], "enabled");
assert_eq!(request["thinking"]["budget_tokens"], 10000);
assert_eq!(request["max_tokens"], 14096);
assert_eq!(request["temperature"], 2);
assert!(request.get("max_completion_tokens").is_none());
Ok(())
}
#[test]
fn test_response_to_message_claude_thinking() -> anyhow::Result<()> {
let response = json!({
@@ -1262,7 +1262,7 @@ Some models expose their internal reasoning or "chain of thought" as part of the
| **DeepSeek-R1** (via OpenAI, Ollama, OpenRouter, OVHcloud, etc.) | Reasoning captured from the `reasoning_content` field in the API response |
| **Kimi** (via Groq or other OpenAI-compatible endpoints) | Reasoning captured from the `reasoning_content` field in the API response |
| **Gemini CLI** (Google Gemini models with thinking enabled) | Thinking blocks captured from the streaming response |
| **Claude** (Anthropic, with [extended thinking](/docs/guides/environment-variables#claude-extended-thinking) enabled) | Thinking blocks captured from the API response |
| **Claude** (Anthropic, with [Claude thinking](/docs/guides/environment-variables#claude-thinking-configuration) enabled) | Thinking blocks captured from the API response |
<Tabs groupId="interface">
<TabItem value="ui" label="goose Desktop" default>
@@ -164,28 +164,32 @@ export GOOSE_LEAD_FAILURE_THRESHOLD=3
export GOOSE_LEAD_FALLBACK_TURNS=2
```
### Claude Extended Thinking
### Claude Thinking Configuration
These variables control Claude's [extended thinking](https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking) feature, which allows the model to reason through complex problems before generating a response. Supported on Anthropic and Databricks providers.
These variables control Claude's reasoning behavior. Supported on Anthropic and Databricks providers.
| Variable | Purpose | Values | Default |
|----------|---------|---------|---------|
| `CLAUDE_THINKING_ENABLED` | Enables extended thinking for Claude models | Set to any value to enable | Disabled |
| `CLAUDE_THINKING_BUDGET` | Maximum tokens allocated for Claude's internal reasoning process | Positive integer (minimum 1024) | 16000 |
| `CLAUDE_THINKING_TYPE` | Controls Claude reasoning mode | `adaptive`, `enabled`, `disabled` | `adaptive` for Claude 4.6+ models, otherwise `disabled` |
| `CLAUDE_THINKING_BUDGET` | Maximum tokens allocated for Claude's internal reasoning process when `CLAUDE_THINKING_TYPE=enabled` | Positive integer (minimum 1024) | 16000 |
**Examples**
```bash
# Enable extended thinking with default budget (16000 tokens)
export CLAUDE_THINKING_ENABLED=1
# Claude 4.6 adaptive thinking
export GOOSE_PROVIDER=anthropic
export GOOSE_MODEL=claude-sonnet-4-6
export CLAUDE_THINKING_TYPE=adaptive
# Enable with a larger budget for complex tasks
export CLAUDE_THINKING_ENABLED=1
# Explicit extended thinking with the default budget
export CLAUDE_THINKING_TYPE=enabled
# Explicit extended thinking with a larger budget for complex tasks
export CLAUDE_THINKING_TYPE=enabled
export CLAUDE_THINKING_BUDGET=32000
# Enable with a smaller budget for faster responses
export CLAUDE_THINKING_ENABLED=1
export CLAUDE_THINKING_BUDGET=8000
# Disable Claude thinking entirely
export CLAUDE_THINKING_TYPE=disabled
```
:::tip Viewing Thinking Output
@@ -734,4 +738,3 @@ When deploying goose in enterprise environments, administrators might need to co
- For security-sensitive variables (like API keys), consider using the system keyring instead of environment variables.
- Some variables may require restarting goose to take effect.
- When using the planning mode, if planner-specific variables are not set, goose will fall back to the main model configuration.