Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ for deployment examples.
| MiniMax | [`configs/model-families/minimax.toml`](configs/model-families/minimax.toml) | `minimax-m2.5`, `minimax-m2.7`, `minimax-m3` |
| Moonshot AI | [`configs/model-families/moonshot-ai.toml`](configs/model-families/moonshot-ai.toml) | `kimi-k2`, `kimi-k2-0905`, `kimi-k2.5`, `kimi-k2.6`, `kimi-k2.6-code`, `kimi-k2.7-code`, `kimi-k2.7-code-highspeed`, `kimi-for-coding` |
| Alibaba Cloud | [`configs/model-families/qwen.toml`](configs/model-families/qwen.toml) | `qwen3.6-35b-a3b`; conservative broad defaults for `qwen3.6*` and `qwen3.7*` |
| xAI | [`configs/model-families/x-ai.toml`](configs/model-families/x-ai.toml) | `grok-4.3`, `grok-build-0.1` |
| xAI | [`configs/model-families/x-ai.toml`](configs/model-families/x-ai.toml) | `grok-4.3`, `grok-4.5`, `grok-build-0.1` |
| Xiaomi | [`configs/model-families/xiaomi.toml`](configs/model-families/xiaomi.toml) | `mimo-v2.5`, `mimo-v2.5-pro` |
| Z.ai | [`configs/model-families/z-ai.toml`](configs/model-families/z-ai.toml) | `glm-5`, `glm-5.1`, `glm-5.2` |

Expand Down
14 changes: 13 additions & 1 deletion configs/model-families/deepseek.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,26 @@ patterns = [
]

[model_families.deepseek_v4_flash.model_metadata]
context_window = 256000
context_window = 1000000
default_reasoning_level = "medium"
supported_reasoning_levels = ["low", "medium", "high"]
supports_parallel_tool_calls = false
auto_review_model_override = "deepseek-v4-flash"

[model_families.deepseek_v4_flash.transform]
force_parallel_tool_calls = false
preserve_reasoning_content_history = true

[[model_families.deepseek_v4_flash.transform.remove_chat_request_morphs]]
from = "reasoning.effort"
to = "reasoning_effort"
kind = "rename"

[[model_families.deepseek_v4_flash.transform.append_chat_request_morphs]]
from = "reasoning.effort"
to = "reasoning_effort"
kind = "rename"

[[model_families.deepseek_v4_flash.transform.append_chat_request_morphs]]
from = "reasoning.effort"
to = "thinking.type"
Expand All @@ -100,12 +106,18 @@ auto_review_model_override = "deepseek-v4-flash"

[model_families.deepseek_v4_pro.transform]
force_parallel_tool_calls = false
preserve_reasoning_content_history = true

[[model_families.deepseek_v4_pro.transform.remove_chat_request_morphs]]
from = "reasoning.effort"
to = "reasoning_effort"
kind = "rename"

[[model_families.deepseek_v4_pro.transform.append_chat_request_morphs]]
from = "reasoning.effort"
to = "reasoning_effort"
kind = "rename"

[[model_families.deepseek_v4_pro.transform.append_chat_request_morphs]]
from = "reasoning.effort"
to = "thinking.type"
Expand Down
27 changes: 27 additions & 0 deletions configs/model-families/x-ai.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,30 @@ supports_search_tool = true

[model_families.x_ai_grok_build_0_1.transform]
force_parallel_tool_calls = false

[model_families.x_ai_grok_4_5]
priority = 10
patterns = [
"grok-4.5",
"grok_4.5",
"grok-4.5-latest",
"xai/grok-4.5",
"x-ai/grok-4.5",
]

[model_families.x_ai_grok_4_5.model_metadata]
context_window = 500000
default_reasoning_level = "high"
supported_reasoning_levels = ["low", "medium", "high"]
input_modalities = ["text", "image"]
supports_parallel_tool_calls = true
supports_search_tool = true

[model_families.x_ai_grok_4_5.transform]
force_parallel_tool_calls = false
reasoning_effort_none_value = "low"

[[model_families.x_ai_grok_4_5.transform.append_chat_request_morphs]]
from = "reasoning.effort"
to = "reasoning_effort"
kind = "rename"
5 changes: 5 additions & 0 deletions configs/model-families/z-ai.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ from = "reasoning.effort"
to = "thinking.type"
kind = "thinking_type"

[[model_families.z_ai_glm_5.transform.append_chat_request_morphs]]
from = "reasoning.effort"
to = "reasoning_effort"
kind = "rename"

[model_families.z_ai_glm_5_base]
priority = 10
patterns = [
Expand Down
9 changes: 5 additions & 4 deletions docs/model-family-catalogs.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Provider/gateway behavior belongs in
| MiniMax | [`minimax.toml`](../configs/model-families/minimax.toml) | `minimax-m2.5`, `minimax-m2.7`, `minimax-m3` |
| Moonshot AI | [`moonshot-ai.toml`](../configs/model-families/moonshot-ai.toml) | `kimi-k2`, `kimi-k2-0905`, `kimi-k2.5`, `kimi-k2.6`, `kimi-k2.6-code`, `kimi-k2.7-code`, `kimi-k2.7-code-highspeed`, `kimi-for-coding` |
| Alibaba Cloud | [`qwen.toml`](../configs/model-families/qwen.toml) | `qwen3.6-35b-a3b`; conservative broad defaults for `qwen3.6*` and `qwen3.7*` |
| xAI | [`x-ai.toml`](../configs/model-families/x-ai.toml) | `grok-4.3`, `grok-build-0.1` |
| xAI | [`x-ai.toml`](../configs/model-families/x-ai.toml) | `grok-4.3`, `grok-4.5`, `grok-build-0.1` |
| Xiaomi | [`xiaomi.toml`](../configs/model-families/xiaomi.toml) | `mimo-v2.5`, `mimo-v2.5-pro` |
| Z.ai | [`z-ai.toml`](../configs/model-families/z-ai.toml) | `glm-5`, `glm-5.1`, `glm-5.2` |

Expand All @@ -48,8 +48,8 @@ transform behavior.
| --- | --- | --- | --- | --- | --- | --- |
| DeepSeek | `deepseek-v3.2` | 128k | provider/default | low, medium, high; default medium | provider/default | Parallel tool metadata is true, but requests force `parallel_tool_calls = false`. |
| DeepSeek | `deepseek-v3.2-speciale` | 128k | provider/default | low, medium, high; default high | provider/default | Forces `parallel_tool_calls = false`. |
| DeepSeek | `deepseek-v4-flash` | 256k | provider/default | low, medium, high; default medium | provider/default | Converts `reasoning.effort` to `thinking.type`; forces `parallel_tool_calls = false`. |
| DeepSeek | `deepseek-v4-pro` | 1,000k | provider/default | low, medium, high; default high | provider/default | Converts `reasoning.effort` to `thinking.type`; forces `parallel_tool_calls = false`. |
| DeepSeek | `deepseek-v4-flash` | 1,000k | provider/default | low, medium, high; default medium | provider/default | Converts `reasoning.effort` to `thinking.type` and forwards `reasoning_effort`; preserves reasoning history (`preserve_reasoning_content_history`); forces `parallel_tool_calls = false`. |
| DeepSeek | `deepseek-v4-pro` | 1,000k | provider/default | low, medium, high; default high | provider/default | Converts `reasoning.effort` to `thinking.type` and forwards `reasoning_effort`; preserves reasoning history (`preserve_reasoning_content_history`); forces `parallel_tool_calls = false`. |
| MiniMax | `minimax-m2.5` | 192k | text | none | true | Non-reasoning variant; forces `parallel_tool_calls = false`. |
| MiniMax | `minimax-m2.7` | 200k | text | low, medium, high; default high | true | Converts `reasoning.effort` to `thinking.type`; forces `parallel_tool_calls = false`. |
| MiniMax | `minimax-m3` | 1,000k | text, image | low, medium, high; default high | false | Converts `reasoning.effort` to `thinking.type`; forces `parallel_tool_calls = false`. |
Expand All @@ -61,11 +61,12 @@ transform behavior.
| Moonshot AI | `kimi-k2.7-code`, `kimi-k2.7-code-highspeed`, `kimi-for-coding` | usable 220k, max 262,144 | text, image | high | provider/default | Uses a conservative Codex planning window below the provider 256K cap to leave output/tokenizer headroom; sets `thinking.type = enabled` and `thinking.keep = all`; drops K2.7-rejected sampling overrides; forces `parallel_tool_calls = false`. |
| Alibaba Cloud | `qwen3.6-35b-a3b` | 262k, max 1,010k | text, image, video | high | false | Removes inherited OpenAI `reasoning_effort`; broad `qwen3.6*` and `qwen3.7*` entries also drop it until a gateway documents support. |
| xAI | `grok-4.3` | 1,000k | text, image | low, medium, high; default medium | true | Uses `web_search`; parallel tool metadata is true, but requests force `parallel_tool_calls = false`. |
| xAI | `grok-4.5` | 500k | text, image | low, medium, high; default high | true | Uses `web_search`; reasoning cannot be disabled (`none` mapped to `low` via `reasoning_effort_none_value`); parallel tool metadata is true, but requests force `parallel_tool_calls = false`. |
| xAI | `grok-build-0.1` | 256k | text | low, medium, high; default medium | true | Uses `web_search`; parallel tool metadata is true, but requests force `parallel_tool_calls = false`. |
| Xiaomi | `mimo-v2.5` | 1,000k | text, image | low, medium, high; default medium | provider/default | Gateway profile does not carry model-specific overrides. |
| Xiaomi | `mimo-v2.5-pro` | 1,000k | text | low, medium, high; default medium | provider/default | Gateway profile does not carry model-specific overrides. |
| Z.ai | `glm-5`, `glm-5.1` family | 200k | text | low, medium, high; default medium | provider/default | Broad GLM-5 transform converts `reasoning.effort` to `thinking.type`; forces `parallel_tool_calls = false`. |
| Z.ai | `glm-5.2` | 1,000k | text | low, medium, high; default medium | provider/default | Inherits broad GLM-5 transform. |
| Z.ai | `glm-5.2` | 1,000k | text | low, medium, high; default medium | provider/default | Inherits broad GLM-5 transform; forwards `reasoning_effort` alongside `thinking.type` (GLM-5.2 honors it natively). |

## Matching And Priority

Expand Down
6 changes: 6 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ pub struct TransformConfig {
pub responses_request_morphs: Vec<RequestMorph>,
pub unsupported_tool_types: Vec<String>,
pub unsupported_tool_strategy: UnsupportedToolStrategy,
pub reasoning_effort_none_value: Option<String>,
pub drop_empty_tool_choice: bool,
pub force_parallel_tool_calls: Option<bool>,
pub request_stream_options_include_usage: bool,
Expand All @@ -357,6 +358,7 @@ impl Default for TransformConfig {
responses_request_morphs: Vec::new(),
unsupported_tool_types: vec!["custom".to_string()],
unsupported_tool_strategy: UnsupportedToolStrategy::AsFunction,
reasoning_effort_none_value: None,
drop_empty_tool_choice: true,
force_parallel_tool_calls: None,
request_stream_options_include_usage: false,
Expand All @@ -377,6 +379,7 @@ pub struct TransformConfigPatch {
pub append_responses_request_morphs: Vec<RequestMorph>,
pub unsupported_tool_types: Option<Vec<String>>,
pub unsupported_tool_strategy: Option<UnsupportedToolStrategy>,
pub reasoning_effort_none_value: Option<String>,
pub drop_empty_tool_choice: Option<bool>,
pub force_parallel_tool_calls: Option<bool>,
pub request_stream_options_include_usage: Option<bool>,
Expand Down Expand Up @@ -414,6 +417,9 @@ impl TransformConfigPatch {
if let Some(strategy) = self.unsupported_tool_strategy {
transform.unsupported_tool_strategy = strategy;
}
if let Some(value) = &self.reasoning_effort_none_value {
transform.reasoning_effort_none_value = Some(value.clone());
}
if let Some(drop_empty_tool_choice) = self.drop_empty_tool_choice {
transform.drop_empty_tool_choice = drop_empty_tool_choice;
}
Expand Down
87 changes: 87 additions & 0 deletions src/config_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ fn example_configs_parse_request_morphs() {
assert!(default_config.model_families.contains_key("mimo_v2_5_pro"));
assert!(default_config.model_families.contains_key("x_ai_grok"));
assert!(default_config.model_families.contains_key("x_ai_grok_4_3"));
assert!(default_config.model_families.contains_key("x_ai_grok_4_5"));
assert!(
default_config
.model_families
Expand Down Expand Up @@ -506,6 +507,92 @@ fn model_family_matches_are_sorted_by_priority() {
assert_eq!(matches[1].priority, 10);
}

#[test]
fn first_class_reasoning_and_tool_translation_for_target_models() {
let config = load_config_layers(&[]).expect("default config loads");

// grok-4.5: new model with correct metadata and `none` -> `low` mapping.
let grok45 = config
.model_families
.get("x_ai_grok_4_5")
.expect("grok-4.5 family exists");
assert_eq!(grok45.model_metadata.context_window, Some(500_000));
assert_eq!(
grok45.model_metadata.default_reasoning_level.as_deref(),
Some("high")
);
assert_eq!(
grok45.model_metadata.supported_reasoning_levels,
Some(vec![
"low".to_string(),
"medium".to_string(),
"high".to_string()
])
);
assert_eq!(
grok45.transform.reasoning_effort_none_value.as_deref(),
Some("low")
);
assert!(
grok45
.transform
.append_chat_request_morphs
.iter()
.any(|m| m.from == "reasoning.effort" && m.to.as_deref() == Some("reasoning_effort"))
);
assert!(
matching_model_families(&config, "grok-4.5")
.iter()
.any(|family| family.priority == 10)
);

// DeepSeek V4: 1M context + reasoning-history preservation + effort forwarding.
let ds_flash = config
.model_families
.get("deepseek_v4_flash")
.expect("deepseek-v4-flash family exists");
assert_eq!(ds_flash.model_metadata.context_window, Some(1_000_000));
assert_eq!(
ds_flash.transform.preserve_reasoning_content_history,
Some(true)
);
assert!(
ds_flash
.transform
.append_chat_request_morphs
.iter()
.any(|m| m.from == "reasoning.effort" && m.to.as_deref() == Some("reasoning_effort"))
);

let ds_pro = config
.model_families
.get("deepseek_v4_pro")
.expect("deepseek-v4-pro family exists");
assert_eq!(
ds_pro.transform.preserve_reasoning_content_history,
Some(true)
);
assert!(
ds_pro
.transform
.append_chat_request_morphs
.iter()
.any(|m| m.from == "reasoning.effort" && m.to.as_deref() == Some("reasoning_effort"))
);

// GLM-5.2: broad family forwards reasoning_effort alongside thinking.type.
let glm5 = config
.model_families
.get("z_ai_glm_5")
.expect("glm-5 broad family exists");
assert!(
glm5.transform
.append_chat_request_morphs
.iter()
.any(|m| m.from == "reasoning.effort" && m.to.as_deref() == Some("reasoning_effort"))
);
}

#[test]
fn transform_patch_can_remove_inherited_morphs_before_appending() {
let mut transform = TransformConfig::default();
Expand Down
2 changes: 1 addition & 1 deletion src/models_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ fn deepseek_family_metadata_is_variant_specific() {

assert_eq!(models[0]["context_window"], 128_000);
assert_eq!(models[0]["supports_parallel_tool_calls"], true);
assert_eq!(models[1]["context_window"], 256_000);
assert_eq!(models[1]["context_window"], 1_000_000);
assert_eq!(models[1]["supports_parallel_tool_calls"], false);
assert_eq!(models[2]["context_window"], 1_000_000);
assert_eq!(models[2]["default_reasoning_level"], "high");
Expand Down
4 changes: 3 additions & 1 deletion src/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::config::TransformConfig;
use crate::config::UnsupportedToolStrategy;
use crate::ids::generated_id;
use crate::transform_morph::apply_native_request_morphs;
use crate::transform_morph::apply_reasoning_effort_none_value;
use crate::transform_morph::apply_request_morphs;

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -138,7 +139,8 @@ pub fn responses_to_chat(request: Value, transform: &TransformConfig) -> ChatTra
}
apply_request_morphs(&request, &mut out, transform);

let body = Value::Object(out);
let mut body = Value::Object(out);
apply_reasoning_effort_none_value(&mut body, transform);
let diagnostics = transform_diagnostics(&request, &body, input_tools.len(), tool_diagnostics);

ChatTransform {
Expand Down
18 changes: 18 additions & 0 deletions src/transform_morph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,24 @@ pub(crate) fn apply_request_morphs(
}
}

pub(crate) fn apply_reasoning_effort_none_value(body: &mut Value, transform: &TransformConfig) {
let Some(none_value) = &transform.reasoning_effort_none_value else {
return;
};
if let Some(Value::String(effort)) = body.get_mut("reasoning_effort") {
if effort.eq_ignore_ascii_case("none") {
*effort = none_value.clone();
}
}
if let Some(reasoning) = body.get_mut("reasoning").and_then(Value::as_object_mut) {
if let Some(Value::String(effort)) = reasoning.get_mut("effort") {
if effort.eq_ignore_ascii_case("none") {
*effort = none_value.clone();
}
}
}
}

pub(crate) fn apply_native_request_morphs(request: &mut Value, transform: &TransformConfig) {
let original = request.clone();
for morph in &transform.responses_request_morphs {
Expand Down