Add canonical thinking modes (#9743)
Signed-off-by: jh-block <jhugo@block.xyz>
This commit is contained in:
@@ -3,7 +3,7 @@ mod model;
|
||||
mod name_builder;
|
||||
mod registry;
|
||||
|
||||
pub use model::{CanonicalModel, Limit, Modalities, Modality, Pricing};
|
||||
pub use model::{CanonicalModel, Limit, Modalities, Modality, Pricing, ThinkingMode};
|
||||
pub use name_builder::{
|
||||
canonical_name, map_provider_name, map_to_canonical_model, strip_version_suffix,
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -229,7 +229,7 @@
|
||||
"env": [
|
||||
"CORTECS_API_KEY"
|
||||
],
|
||||
"model_count": 50
|
||||
"model_count": 51
|
||||
},
|
||||
{
|
||||
"id": "crof",
|
||||
@@ -318,7 +318,7 @@
|
||||
"env": [
|
||||
"FASTROUTER_API_KEY"
|
||||
],
|
||||
"model_count": 15
|
||||
"model_count": 47
|
||||
},
|
||||
{
|
||||
"id": "firepass",
|
||||
@@ -351,7 +351,7 @@
|
||||
"env": [
|
||||
"FREEMODEL_API_KEY"
|
||||
],
|
||||
"model_count": 9
|
||||
"model_count": 10
|
||||
},
|
||||
{
|
||||
"id": "friendli",
|
||||
@@ -551,17 +551,6 @@
|
||||
],
|
||||
"model_count": 4
|
||||
},
|
||||
{
|
||||
"id": "meta-llama",
|
||||
"display_name": "Llama",
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"api": "https://api.llama.com/compat/v1/",
|
||||
"doc": "https://llama.developer.meta.com/docs/models",
|
||||
"env": [
|
||||
"LLAMA_API_KEY"
|
||||
],
|
||||
"model_count": 7
|
||||
},
|
||||
{
|
||||
"id": "llmgateway",
|
||||
"display_name": "LLM Gateway",
|
||||
@@ -573,6 +562,17 @@
|
||||
],
|
||||
"model_count": 191
|
||||
},
|
||||
{
|
||||
"id": "llmtr",
|
||||
"display_name": "LLMTR",
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"api": "https://llmtr.com/v1",
|
||||
"doc": "https://llmtr.com/docs",
|
||||
"env": [
|
||||
"LLMTR_API_KEY"
|
||||
],
|
||||
"model_count": 6
|
||||
},
|
||||
{
|
||||
"id": "lmstudio",
|
||||
"display_name": "LMStudio",
|
||||
@@ -606,6 +606,17 @@
|
||||
],
|
||||
"model_count": 19
|
||||
},
|
||||
{
|
||||
"id": "meta-llama",
|
||||
"display_name": "Llama",
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"api": "https://api.llama.com/compat/v1/",
|
||||
"doc": "https://llama.developer.meta.com/docs/models",
|
||||
"env": [
|
||||
"LLAMA_API_KEY"
|
||||
],
|
||||
"model_count": 7
|
||||
},
|
||||
{
|
||||
"id": "minimax",
|
||||
"display_name": "MiniMax (minimax.io)",
|
||||
@@ -791,7 +802,7 @@
|
||||
"env": [
|
||||
"NVIDIA_API_KEY"
|
||||
],
|
||||
"model_count": 94
|
||||
"model_count": 83
|
||||
},
|
||||
{
|
||||
"id": "ollama-cloud",
|
||||
@@ -813,7 +824,7 @@
|
||||
"env": [
|
||||
"OPENCODE_API_KEY"
|
||||
],
|
||||
"model_count": 69
|
||||
"model_count": 70
|
||||
},
|
||||
{
|
||||
"id": "opencode-go",
|
||||
@@ -1167,7 +1178,7 @@
|
||||
"env": [
|
||||
"XIAOMI_API_KEY"
|
||||
],
|
||||
"model_count": 5
|
||||
"model_count": 6
|
||||
},
|
||||
{
|
||||
"id": "xiaomi-token-plan-ams",
|
||||
@@ -1244,7 +1255,7 @@
|
||||
"env": [
|
||||
"ZENMUX_API_KEY"
|
||||
],
|
||||
"model_count": 107
|
||||
"model_count": 108
|
||||
},
|
||||
{
|
||||
"id": "zhipuai",
|
||||
|
||||
@@ -62,6 +62,14 @@ pub struct Limit {
|
||||
pub output: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ThinkingMode {
|
||||
Enabled,
|
||||
Adaptive,
|
||||
AlwaysOnAdaptive,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CanonicalModel {
|
||||
/// Model identifier (e.g., "anthropic/claude-3-5-sonnet")
|
||||
@@ -82,6 +90,10 @@ pub struct CanonicalModel {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub reasoning: Option<bool>,
|
||||
|
||||
/// Request shape to use when enabling thinking/reasoning.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thinking_mode: Option<ThinkingMode>,
|
||||
|
||||
/// Whether the model supports tool calling
|
||||
#[serde(default)]
|
||||
pub tool_call: bool,
|
||||
|
||||
@@ -12,7 +12,7 @@ use clap::Parser;
|
||||
use goose::providers::create_with_named_model;
|
||||
use goose_providers::canonical::{
|
||||
canonical_name, CanonicalModel, CanonicalModelRegistry, Limit, Modalities, Modality,
|
||||
ModelMapping, Pricing,
|
||||
ModelMapping, Pricing, ThinkingMode,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
@@ -346,6 +346,25 @@ fn get_string(value: &Value, field: &str) -> Option<String> {
|
||||
value.get(field).and_then(|v| v.as_str()).map(String::from)
|
||||
}
|
||||
|
||||
fn get_thinking_mode(canonical_id: &str, value: &Value) -> Option<ThinkingMode> {
|
||||
value
|
||||
.get("thinking_mode")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|mode| serde_json::from_value(Value::String(mode.to_string())).ok())
|
||||
.or_else(|| inferred_thinking_mode(canonical_id))
|
||||
}
|
||||
|
||||
fn inferred_thinking_mode(canonical_id: &str) -> Option<ThinkingMode> {
|
||||
match canonical_id {
|
||||
"anthropic/claude-fable-5" => Some(ThinkingMode::AlwaysOnAdaptive),
|
||||
"anthropic/claude-opus-4.6" => Some(ThinkingMode::Adaptive),
|
||||
"anthropic/claude-opus-4.7" => Some(ThinkingMode::Adaptive),
|
||||
"anthropic/claude-opus-4.8" => Some(ThinkingMode::Adaptive),
|
||||
"anthropic/claude-sonnet-4.6" => Some(ThinkingMode::Adaptive),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_modalities(model_data: &Value, field: &str) -> Vec<Modality> {
|
||||
model_data
|
||||
.get("modalities")
|
||||
@@ -412,6 +431,7 @@ fn process_model(
|
||||
family: get_string(model_data, "family"),
|
||||
attachment: model_data.get("attachment").and_then(|v| v.as_bool()),
|
||||
reasoning: model_data.get("reasoning").and_then(|v| v.as_bool()),
|
||||
thinking_mode: get_thinking_mode(&canonical_id, model_data),
|
||||
tool_call: model_data
|
||||
.get("tool_call")
|
||||
.and_then(|v| v.as_bool())
|
||||
@@ -497,6 +517,7 @@ fn collect_provider_metadata(
|
||||
println!(" Added {} ({}) - {} models", provider_id, npm, model_count);
|
||||
}
|
||||
|
||||
metadata_list.sort_by(|a, b| a.id.cmp(&b.id));
|
||||
metadata_list
|
||||
}
|
||||
|
||||
@@ -722,6 +743,7 @@ mod tests {
|
||||
family: None,
|
||||
attachment: None,
|
||||
reasoning: None,
|
||||
thinking_mode: None,
|
||||
tool_call: false,
|
||||
temperature: None,
|
||||
knowledge: None,
|
||||
|
||||
@@ -12,8 +12,8 @@ use tokio_util::io::StreamReader;
|
||||
use super::api_client::{ApiClient, AuthMethod};
|
||||
use super::base::{ConfigKey, MessageStream, ModelInfo, Provider, ProviderDef, ProviderMetadata};
|
||||
use super::formats::anthropic::{
|
||||
create_request_with_options, response_to_streaming_message, thinking_type,
|
||||
AnthropicFormatOptions, ThinkingType,
|
||||
create_request_with_options_for_provider, response_to_streaming_message, thinking_type,
|
||||
AnthropicFormatOptions, ThinkingType, ANTHROPIC_PROVIDER_NAME,
|
||||
};
|
||||
use super::inventory::{config_secret_value, serialize_string_map, InventoryIdentityInput};
|
||||
use super::openai_compatible::handle_status;
|
||||
@@ -26,7 +26,6 @@ use crate::providers::utils::RequestLog;
|
||||
use futures::future::BoxFuture;
|
||||
use rmcp::model::Tool;
|
||||
|
||||
const ANTHROPIC_PROVIDER_NAME: &str = "anthropic";
|
||||
pub const ANTHROPIC_DEFAULT_MODEL: &str = "claude-sonnet-4-5";
|
||||
const ANTHROPIC_DEFAULT_FAST_MODEL: &str = "claude-haiku-4-5";
|
||||
const ANTHROPIC_KNOWN_MODELS: &[&str] = &[
|
||||
@@ -341,7 +340,8 @@ impl Provider for AnthropicProvider {
|
||||
messages: &[Message],
|
||||
tools: &[Tool],
|
||||
) -> Result<MessageStream, ProviderError> {
|
||||
let mut payload = create_request_with_options(
|
||||
let mut payload = create_request_with_options_for_provider(
|
||||
ANTHROPIC_PROVIDER_NAME,
|
||||
model_config,
|
||||
system,
|
||||
messages,
|
||||
|
||||
@@ -18,7 +18,7 @@ use super::base::{
|
||||
};
|
||||
use super::databricks_auth::{DatabricksAuth, DatabricksAuthProvider};
|
||||
use super::embedding::EmbeddingCapable;
|
||||
use super::formats::databricks::create_request;
|
||||
use super::formats::databricks::{create_request_for_provider, DATABRICKS_PROVIDER_NAME};
|
||||
use super::formats::openai_responses::create_responses_request;
|
||||
use super::openai_compatible::{
|
||||
handle_response_openai_compat, handle_status, map_http_error_to_provider_error, sanitize_url,
|
||||
@@ -58,7 +58,6 @@ struct CachedDatabricksEndpointInfo {
|
||||
fetched_at: Instant,
|
||||
}
|
||||
|
||||
const DATABRICKS_PROVIDER_NAME: &str = "databricks";
|
||||
const DATABRICKS_ENDPOINT_METADATA_TTL_SECS: u64 = 60;
|
||||
static DATABRICKS_ENDPOINT_INFO_CACHE: LazyLock<
|
||||
Mutex<std::collections::HashMap<String, CachedDatabricksEndpointInfo>>,
|
||||
@@ -670,7 +669,8 @@ impl Provider for DatabricksProvider {
|
||||
model_config
|
||||
};
|
||||
|
||||
let mut payload = create_request(
|
||||
let mut payload = create_request_for_provider(
|
||||
DATABRICKS_PROVIDER_NAME,
|
||||
request_model_config,
|
||||
system,
|
||||
messages,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::conversation::message::{Message, MessageContent};
|
||||
use crate::mcp_utils::extract_text_from_resource;
|
||||
use crate::model::ModelConfig;
|
||||
use crate::providers::canonical::maybe_get_canonical_model;
|
||||
use anyhow::{anyhow, Result};
|
||||
use goose_providers::canonical::ThinkingMode;
|
||||
use goose_providers::conversation::token_usage::{ProviderUsage, Usage};
|
||||
use goose_providers::errors::ProviderError;
|
||||
use goose_providers::images::{convert_image, ImageFormat};
|
||||
@@ -14,6 +16,8 @@ use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) const ANTHROPIC_PROVIDER_NAME: &str = "anthropic";
|
||||
|
||||
macro_rules! string_enum {
|
||||
($name:ident { $($variant:ident => $str:literal),+ $(,)? }) => {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -68,27 +72,51 @@ 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")
|
||||
fn canonical_thinking_mode(provider_name: &str, model_name: &str) -> Option<ThinkingMode> {
|
||||
maybe_get_canonical_model(provider_name, model_name).and_then(|model| model.thinking_mode)
|
||||
}
|
||||
|
||||
fn canonical_reasoning(provider_name: &str, model_config: &ModelConfig) -> Option<bool> {
|
||||
maybe_get_canonical_model(provider_name, &model_config.model_name)
|
||||
.and_then(|model| model.reasoning)
|
||||
}
|
||||
|
||||
pub fn model_supports_temperature(provider_name: &str, model_config: &ModelConfig) -> bool {
|
||||
maybe_get_canonical_model(provider_name, &model_config.model_name)
|
||||
.and_then(|model| model.temperature)
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub fn thinking_type(model_config: &ModelConfig) -> ThinkingType {
|
||||
let model_lower = model_config.model_name.to_lowercase();
|
||||
if !model_lower.contains("claude") {
|
||||
thinking_type_for_provider(ANTHROPIC_PROVIDER_NAME, model_config)
|
||||
}
|
||||
|
||||
pub fn thinking_type_for_provider(provider_name: &str, model_config: &ModelConfig) -> ThinkingType {
|
||||
let mode = canonical_thinking_mode(provider_name, &model_config.model_name);
|
||||
let reasoning = model_config
|
||||
.reasoning
|
||||
.or_else(|| canonical_reasoning(provider_name, model_config));
|
||||
|
||||
if reasoning != Some(true) {
|
||||
return ThinkingType::Disabled;
|
||||
}
|
||||
|
||||
let is_adaptive_model = supports_adaptive_thinking(&model_config.model_name);
|
||||
if mode == Some(ThinkingMode::AlwaysOnAdaptive) {
|
||||
return ThinkingType::Adaptive;
|
||||
}
|
||||
|
||||
let effort = model_config.thinking_effort();
|
||||
|
||||
if effort.is_none() && legacy_thinking_budget_tokens().is_some() {
|
||||
return ThinkingType::Enabled;
|
||||
return match mode {
|
||||
Some(ThinkingMode::Adaptive) => ThinkingType::Adaptive,
|
||||
_ => ThinkingType::Enabled,
|
||||
};
|
||||
}
|
||||
|
||||
match effort.unwrap_or(ThinkingEffort::Off) {
|
||||
ThinkingEffort::Off => ThinkingType::Disabled,
|
||||
_ if is_adaptive_model => ThinkingType::Adaptive,
|
||||
_ if mode == Some(ThinkingMode::Adaptive) => ThinkingType::Adaptive,
|
||||
_ => ThinkingType::Enabled,
|
||||
}
|
||||
}
|
||||
@@ -515,6 +543,13 @@ pub fn thinking_effort(model_config: &ModelConfig) -> ThinkingEffort {
|
||||
.unwrap_or(ThinkingEffort::High)
|
||||
}
|
||||
|
||||
pub fn adaptive_output_effort(model_config: &ModelConfig) -> ThinkingEffort {
|
||||
match thinking_effort(model_config) {
|
||||
ThinkingEffort::Off => ThinkingEffort::High,
|
||||
effort => effort,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn thinking_budget_tokens(model_config: &ModelConfig) -> i32 {
|
||||
if let Some(request_param) = model_config
|
||||
.request_params
|
||||
@@ -553,15 +588,16 @@ fn legacy_thinking_budget_tokens() -> Option<i32> {
|
||||
|
||||
fn apply_thinking_config(
|
||||
payload: &mut Value,
|
||||
provider_name: &str,
|
||||
model_config: &ModelConfig,
|
||||
max_tokens: i32,
|
||||
options: AnthropicFormatOptions,
|
||||
) {
|
||||
let obj = payload.as_object_mut().unwrap();
|
||||
match thinking_type(model_config) {
|
||||
match thinking_type_for_provider(provider_name, model_config) {
|
||||
ThinkingType::Adaptive => {
|
||||
obj.insert("thinking".to_string(), json!({"type": "adaptive"}));
|
||||
let effort = thinking_effort(model_config).to_string();
|
||||
let effort = adaptive_output_effort(model_config).to_string();
|
||||
obj.insert("output_config".to_string(), json!({"effort": effort}));
|
||||
}
|
||||
ThinkingType::Enabled => {
|
||||
@@ -620,6 +656,24 @@ pub fn create_request_with_options(
|
||||
messages: &[Message],
|
||||
tools: &[Tool],
|
||||
options: AnthropicFormatOptions,
|
||||
) -> Result<Value> {
|
||||
create_request_with_options_for_provider(
|
||||
ANTHROPIC_PROVIDER_NAME,
|
||||
model_config,
|
||||
system,
|
||||
messages,
|
||||
tools,
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create_request_with_options_for_provider(
|
||||
provider_name: &str,
|
||||
model_config: &ModelConfig,
|
||||
system: &str,
|
||||
messages: &[Message],
|
||||
tools: &[Tool],
|
||||
options: AnthropicFormatOptions,
|
||||
) -> Result<Value> {
|
||||
let options = options.for_model(model_config);
|
||||
let anthropic_messages = format_messages_with_options(messages, options);
|
||||
@@ -651,14 +705,22 @@ pub fn create_request_with_options(
|
||||
.insert("tools".to_string(), json!(tool_specs));
|
||||
}
|
||||
|
||||
if let Some(temp) = model_config.temperature {
|
||||
payload
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("temperature".to_string(), json!(temp));
|
||||
if model_supports_temperature(provider_name, model_config) {
|
||||
if let Some(temp) = model_config.temperature {
|
||||
payload
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("temperature".to_string(), json!(temp));
|
||||
}
|
||||
}
|
||||
|
||||
apply_thinking_config(&mut payload, model_config, max_tokens, options);
|
||||
apply_thinking_config(
|
||||
&mut payload,
|
||||
provider_name,
|
||||
model_config,
|
||||
max_tokens,
|
||||
options,
|
||||
);
|
||||
|
||||
Ok(payload)
|
||||
}
|
||||
@@ -1544,6 +1606,14 @@ mod tests {
|
||||
thinking_type(&cfg_with_effort("claude-opus-4-6", "high")),
|
||||
ThinkingType::Adaptive
|
||||
);
|
||||
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")),
|
||||
@@ -1561,6 +1631,41 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_thinking_type_always_on_adaptive() {
|
||||
let _guard = env_lock::lock_env([("GOOSE_THINKING_EFFORT", None::<&str>)]);
|
||||
|
||||
assert_eq!(
|
||||
thinking_type(&cfg("claude-fable-5")),
|
||||
ThinkingType::Adaptive
|
||||
);
|
||||
assert_eq!(
|
||||
thinking_type(&cfg_with_effort("claude-fable-5", "off")),
|
||||
ThinkingType::Adaptive
|
||||
);
|
||||
assert_eq!(
|
||||
thinking_type(&cfg_with_effort("claude-fable-5", "high")),
|
||||
ThinkingType::Adaptive
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_request_fable_5_omits_temperature() -> Result<()> {
|
||||
let _guard = env_lock::lock_env([("GOOSE_THINKING_EFFORT", None::<&str>)]);
|
||||
let mut config = cfg("claude-fable-5");
|
||||
config.max_tokens = Some(4096);
|
||||
config.temperature = Some(0.7);
|
||||
let messages = vec![Message::user().with_text("Hello")];
|
||||
|
||||
let payload = create_request(&config, "system", &messages, &[])?;
|
||||
|
||||
assert_eq!(payload["thinking"]["type"], "adaptive");
|
||||
assert!(payload.get("temperature").is_none());
|
||||
assert_eq!(payload["output_config"]["effort"], "high");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_thinking_budget_uses_legacy_env() {
|
||||
let _guard = env_lock::lock_env([
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::conversation::message::{Message, MessageContent};
|
||||
use crate::model::ModelConfig;
|
||||
use crate::providers::formats::anthropic::{
|
||||
thinking_budget_tokens, thinking_effort, thinking_type, ThinkingType,
|
||||
adaptive_output_effort, model_supports_temperature, thinking_budget_tokens,
|
||||
thinking_type_for_provider, ThinkingType,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Error};
|
||||
@@ -19,6 +20,8 @@ use serde::Serialize;
|
||||
use serde_json::{json, Value};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub(crate) const DATABRICKS_PROVIDER_NAME: &str = "databricks";
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct DatabricksMessage {
|
||||
content: Value,
|
||||
@@ -243,15 +246,19 @@ fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<Data
|
||||
result
|
||||
}
|
||||
|
||||
fn apply_claude_thinking_config(payload: &mut Value, model_config: &ModelConfig) {
|
||||
fn apply_claude_thinking_config(
|
||||
payload: &mut Value,
|
||||
provider_name: &str,
|
||||
model_config: &ModelConfig,
|
||||
) {
|
||||
let obj = payload.as_object_mut().unwrap();
|
||||
|
||||
match thinking_type(model_config) {
|
||||
match thinking_type_for_provider(provider_name, 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() }),
|
||||
json!({ "effort": adaptive_output_effort(model_config).to_string() }),
|
||||
);
|
||||
obj.insert(
|
||||
"max_completion_tokens".to_string(),
|
||||
@@ -272,8 +279,10 @@ fn apply_claude_thinking_config(payload: &mut Value, model_config: &ModelConfig)
|
||||
obj.insert("temperature".to_string(), json!(2));
|
||||
}
|
||||
ThinkingType::Disabled => {
|
||||
if let Some(temp) = model_config.temperature {
|
||||
obj.insert("temperature".to_string(), json!(temp));
|
||||
if model_supports_temperature(provider_name, model_config) {
|
||||
if let Some(temp) = model_config.temperature {
|
||||
obj.insert("temperature".to_string(), json!(temp));
|
||||
}
|
||||
}
|
||||
obj.insert(
|
||||
"max_completion_tokens".to_string(),
|
||||
@@ -585,6 +594,24 @@ pub fn create_request(
|
||||
messages: &[Message],
|
||||
tools: &[Tool],
|
||||
image_format: &ImageFormat,
|
||||
) -> anyhow::Result<Value, Error> {
|
||||
create_request_for_provider(
|
||||
DATABRICKS_PROVIDER_NAME,
|
||||
model_config,
|
||||
system,
|
||||
messages,
|
||||
tools,
|
||||
image_format,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create_request_for_provider(
|
||||
provider_name: &str,
|
||||
model_config: &ModelConfig,
|
||||
system: &str,
|
||||
messages: &[Message],
|
||||
tools: &[Tool],
|
||||
image_format: &ImageFormat,
|
||||
) -> anyhow::Result<Value, Error> {
|
||||
if model_config.model_name.starts_with("o1-mini") {
|
||||
return Err(anyhow!(
|
||||
@@ -644,10 +671,10 @@ pub fn create_request(
|
||||
}
|
||||
|
||||
if is_claude_model(&model_config.model_name) {
|
||||
apply_claude_thinking_config(&mut payload, model_config);
|
||||
apply_claude_thinking_config(&mut payload, provider_name, model_config);
|
||||
} else {
|
||||
// open ai reasoning models currently don't support temperature
|
||||
if !is_openai_reasoning_model {
|
||||
if !is_openai_reasoning_model && model_supports_temperature(provider_name, model_config) {
|
||||
if let Some(temp) = model_config.temperature {
|
||||
payload
|
||||
.as_object_mut()
|
||||
@@ -1200,6 +1227,51 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_request_adaptive_thinking_for_new_anthropic_models() -> anyhow::Result<()> {
|
||||
let _guard = env_lock::lock_env([("GOOSE_THINKING_EFFORT", None::<&str>)]);
|
||||
|
||||
for name in [
|
||||
"databricks-claude-opus-4-7",
|
||||
"databricks-claude-opus-4-8",
|
||||
"databricks-claude-fable-5",
|
||||
"global.anthropic.claude-fable-5",
|
||||
] {
|
||||
let mut model_config = ModelConfig::new_or_fail(name);
|
||||
model_config.max_tokens = Some(4096);
|
||||
let mut params = std::collections::HashMap::new();
|
||||
params.insert("thinking_effort".to_string(), serde_json::json!("high"));
|
||||
model_config.request_params = Some(params);
|
||||
|
||||
let request = create_request(&model_config, "system", &[], &[], &ImageFormat::OpenAi)?;
|
||||
|
||||
assert_eq!(request["thinking"]["type"], "adaptive", "{name}");
|
||||
assert!(request.get("temperature").is_none(), "{name}");
|
||||
assert_eq!(request["max_completion_tokens"], 4096, "{name}");
|
||||
assert!(request.get("max_tokens").is_none(), "{name}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_request_always_on_adaptive_off_effort_falls_back_to_high() -> anyhow::Result<()>
|
||||
{
|
||||
let _guard = env_lock::lock_env([("GOOSE_THINKING_EFFORT", None::<&str>)]);
|
||||
let mut model_config = ModelConfig::new_or_fail("databricks-claude-fable-5");
|
||||
model_config.max_tokens = Some(4096);
|
||||
let mut params = std::collections::HashMap::new();
|
||||
params.insert("thinking_effort".to_string(), serde_json::json!("off"));
|
||||
model_config.request_params = Some(params);
|
||||
|
||||
let request = create_request(&model_config, "system", &[], &[], &ImageFormat::OpenAi)?;
|
||||
|
||||
assert_eq!(request["thinking"]["type"], "adaptive");
|
||||
assert_eq!(request["output_config"]["effort"], "high");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_request_enabled_thinking_with_budget() -> anyhow::Result<()> {
|
||||
let mut model_config = ModelConfig::new_or_fail("databricks-claude-3-7-sonnet");
|
||||
|
||||
Reference in New Issue
Block a user