feat: add model directive management

This commit is contained in:
fawney19
2026-05-03 14:48:25 +08:00
parent fe27fb17fb
commit c4ea042eb4
53 changed files with 2655 additions and 182 deletions

View File

@@ -2503,7 +2503,8 @@ pub(crate) fn claude_output_effort_to_openai_reasoning_effort(value: &str) -> Op
"low" => Some("low"),
"medium" => Some("medium"),
"high" => Some("high"),
"max" | "xhigh" => Some("xhigh"),
"xhigh" => Some("xhigh"),
"max" => Some("max"),
_ => None,
}
}

View File

@@ -12,9 +12,12 @@ use crate::{
CanonicalRequest,
},
protocol::context::FormatContext,
request::openai::{
map_openai_reasoning_effort_to_claude_output,
map_openai_reasoning_effort_to_thinking_budget,
request::{
model_directives::claude_model_uses_adaptive_effort,
openai::{
map_openai_reasoning_effort_to_claude_output,
map_openai_reasoning_effort_to_thinking_budget,
},
},
};
@@ -155,14 +158,18 @@ pub fn to_raw(
let budget_tokens = thinking
.budget_tokens
.or_else(|| openai_effort.and_then(map_openai_reasoning_effort_to_thinking_budget));
let uses_adaptive = claude_model_uses_adaptive_effort(mapped_model)
|| claude_model_uses_adaptive_effort(canonical.model.as_str());
if thinking.enabled || budget_tokens.is_some() {
output.insert(
"thinking".to_string(),
let thinking_config = if uses_adaptive {
json!({"type": "adaptive"})
} else {
json!({
"type": "enabled",
"budget_tokens": budget_tokens.unwrap_or(1024),
}),
);
})
};
output.insert("thinking".to_string(), thinking_config);
}
if let Some(output_effort) =
openai_effort.and_then(map_openai_reasoning_effort_to_claude_output)

View File

@@ -15,7 +15,13 @@ use crate::{
OPENAI_RESPONSES_LEGACY_EXTENSION_NAMESPACE,
},
protocol::context::FormatContext,
request::openai::map_openai_reasoning_effort_to_gemini_budget,
request::{
model_directives::{gemini_model_uses_thinking_level, ReasoningEffort},
openai::{
map_openai_reasoning_effort_to_gemini_budget,
map_thinking_budget_to_openai_reasoning_effort,
},
},
};
pub fn from(body: &Value, ctx: &FormatContext) -> Option<CanonicalRequest> {
@@ -203,7 +209,8 @@ fn canonical_to_gemini_request_body(
if let Some(system_instruction) = canonical_system_instruction(canonical) {
output.insert("systemInstruction".to_string(), system_instruction);
}
if let Some(generation_config) = canonical_generation_config_to_gemini(canonical) {
if let Some(generation_config) = canonical_generation_config_to_gemini(canonical, mapped_model)
{
output.insert("generationConfig".to_string(), generation_config);
}
if let Some(tools) = canonical_tools_to_gemini(canonical) {
@@ -370,7 +377,10 @@ fn canonical_media_to_gemini_part(
})
}
fn canonical_generation_config_to_gemini(canonical: &CanonicalRequest) -> Option<Value> {
fn canonical_generation_config_to_gemini(
canonical: &CanonicalRequest,
mapped_model: &str,
) -> Option<Value> {
let mut generation_config = Map::new();
if let Some(value) = canonical.generation.max_tokens {
generation_config.insert("maxOutputTokens".to_string(), Value::from(value));
@@ -406,14 +416,8 @@ fn canonical_generation_config_to_gemini(canonical: &CanonicalRequest) -> Option
.and_then(|value| value.get("thinking_config"))
.cloned()
.or_else(|| {
let budget = thinking.budget_tokens.or_else(|| {
canonical_openai_reasoning_effort(thinking)
.and_then(map_openai_reasoning_effort_to_gemini_budget)
})?;
Some(json!({
"includeThoughts": true,
"thinkingBudget": budget,
}))
let effort = canonical_openai_reasoning_effort(thinking);
gemini_thinking_config_from_reasoning(mapped_model, effort, thinking.budget_tokens)
})
}) {
generation_config.insert("thinkingConfig".to_string(), thinking_config);
@@ -421,6 +425,34 @@ fn canonical_generation_config_to_gemini(canonical: &CanonicalRequest) -> Option
(!generation_config.is_empty()).then_some(Value::Object(generation_config))
}
fn gemini_thinking_config_from_reasoning(
mapped_model: &str,
effort: Option<&str>,
budget_tokens: Option<u64>,
) -> Option<Value> {
if gemini_model_uses_thinking_level(mapped_model) {
let level = effort
.and_then(ReasoningEffort::parse)
.or_else(|| {
budget_tokens
.map(map_thinking_budget_to_openai_reasoning_effort)
.and_then(ReasoningEffort::parse)
})
.map(ReasoningEffort::as_gemini_level_value)?;
return Some(json!({
"includeThoughts": true,
"thinkingLevel": level,
}));
}
let budget =
budget_tokens.or_else(|| effort.and_then(map_openai_reasoning_effort_to_gemini_budget))?;
Some(json!({
"includeThoughts": true,
"thinkingBudget": budget,
}))
}
fn apply_response_format_to_gemini_generation_config(
generation_config: &mut Map<String, Value>,
response_format: &CanonicalResponseFormat,

View File

@@ -397,7 +397,7 @@ fn reasoning_config_to_responses(thinking: &CanonicalThinkingConfig) -> Option<V
.and_then(Value::as_str)
.map(|effort| {
json!({
"effort": if effort == "xhigh" { "high" } else { effort },
"effort": openai_responses_reasoning_effort(effort),
})
})
})
@@ -410,6 +410,16 @@ fn reasoning_config_to_responses(thinking: &CanonicalThinkingConfig) -> Option<V
})
}
fn openai_responses_reasoning_effort(effort: &str) -> &str {
match effort.trim().to_ascii_lowercase().as_str() {
"xhigh" | "max" => "xhigh",
"low" => "low",
"medium" => "medium",
"high" => "high",
_ => effort,
}
}
fn canonical_text_config_to_responses(canonical: &CanonicalRequest) -> Option<Value> {
let mut text = Map::new();
if let Some(response_format) = &canonical.response_format {