Merge PR #3046 from Hmbown: add Moonshot/Kimi to reasoning-content provider and model gates
fix(reasoning): add Moonshot/Kimi to reasoning-content provider and model support
This commit is contained in:
@@ -1996,6 +1996,7 @@ fn provider_accepts_reasoning_content(provider: ApiProvider) -> bool {
|
||||
| ApiProvider::Volcengine
|
||||
| ApiProvider::Arcee
|
||||
| ApiProvider::Sglang
|
||||
| ApiProvider::Moonshot // #3016: Kimi thinking traces use reasoning_content
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2745,6 +2746,76 @@ mod stream_decoder_tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decoder_streams_moonshot_multi_chunk_reasoning_as_thinking() {
|
||||
// #3016: recorded shape from Moonshot's native endpoint — kimi-k2.6
|
||||
// streams `reasoning_content` deltas before the answer text. The
|
||||
// thinking deltas must accumulate into ONE thinking block and the
|
||||
// answer must arrive as text, not be glued into the trace.
|
||||
let chunks = [
|
||||
r#"{"id":"cmpl-kimi","model":"kimi-k2.6","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"Let me check"}}]}"#,
|
||||
r#"{"id":"cmpl-kimi","model":"kimi-k2.6","choices":[{"index":0,"delta":{"reasoning_content":" the config."}}]}"#,
|
||||
r#"{"id":"cmpl-kimi","model":"kimi-k2.6","choices":[{"index":0,"delta":{"content":"The answer is 42."}}]}"#,
|
||||
];
|
||||
|
||||
let is_reasoning =
|
||||
is_reasoning_model_for_stream(crate::config::ApiProvider::Moonshot, "kimi-k2.6");
|
||||
let mut content_index = 0u32;
|
||||
let mut text_started = false;
|
||||
let mut thinking_started = false;
|
||||
let mut tool_indices = std::collections::HashMap::new();
|
||||
let mut events = Vec::new();
|
||||
for chunk in chunks {
|
||||
let value: Value = serde_json::from_str(chunk).expect("valid SSE JSON");
|
||||
events.extend(parse_sse_chunk(
|
||||
&value,
|
||||
&mut content_index,
|
||||
&mut text_started,
|
||||
&mut thinking_started,
|
||||
&mut tool_indices,
|
||||
is_reasoning,
|
||||
));
|
||||
}
|
||||
|
||||
let thinking: String = events
|
||||
.iter()
|
||||
.filter_map(|event| match event {
|
||||
StreamEvent::ContentBlockDelta {
|
||||
delta: Delta::ThinkingDelta { thinking },
|
||||
..
|
||||
} => Some(thinking.as_str()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(thinking, "Let me check the config.");
|
||||
|
||||
let thinking_starts = events
|
||||
.iter()
|
||||
.filter(|event| {
|
||||
matches!(
|
||||
event,
|
||||
StreamEvent::ContentBlockStart {
|
||||
content_block: ContentBlockStart::Thinking { .. },
|
||||
..
|
||||
}
|
||||
)
|
||||
})
|
||||
.count();
|
||||
assert_eq!(thinking_starts, 1, "one thinking block: {events:?}");
|
||||
|
||||
let text: String = events
|
||||
.iter()
|
||||
.filter_map(|event| match event {
|
||||
StreamEvent::ContentBlockDelta {
|
||||
delta: Delta::TextDelta { text },
|
||||
..
|
||||
} => Some(text.as_str()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(text, "The answer is 42.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decoder_accepts_openrouter_reasoning_delta_with_extra_fields() {
|
||||
let events = decode_chunk(
|
||||
@@ -3649,6 +3720,22 @@ mod alias_thinking_detection_tests {
|
||||
assert!(provider_accepts_reasoning_content(ApiProvider::NvidiaNim));
|
||||
assert!(provider_accepts_reasoning_content(ApiProvider::XiaomiMimo));
|
||||
assert!(provider_accepts_reasoning_content(ApiProvider::Arcee));
|
||||
// #3016: Moonshot's native endpoint streams Kimi thinking as
|
||||
// reasoning_content.
|
||||
assert!(provider_accepts_reasoning_content(ApiProvider::Moonshot));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stream_classifies_moonshot_kimi_as_reasoning() {
|
||||
// #3016: without this, kimi-k2.6 thinking leaked into answer text.
|
||||
assert!(is_reasoning_model_for_stream(
|
||||
ApiProvider::Moonshot,
|
||||
"kimi-k2.6"
|
||||
));
|
||||
assert!(
|
||||
!is_reasoning_model_for_stream(ApiProvider::Moonshot, "kimi-for-coding"),
|
||||
"kimi-for-coding is Moonshot's documented non-thinking model"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -312,6 +312,12 @@ pub fn model_supports_reasoning(model: &str) -> bool {
|
||||
if lower.contains("deepseek") && lower.contains("v4") {
|
||||
return true;
|
||||
}
|
||||
// #3016: Moonshot-native Kimi IDs also emit reasoning_content.
|
||||
// `kimi-for-coding` is Moonshot's documented non-thinking model — it
|
||||
// must not be classified as reasoning-capable by the prefix rule.
|
||||
if lower.starts_with("kimi-") && lower != "kimi-for-coding" {
|
||||
return true;
|
||||
}
|
||||
matches!(
|
||||
lower.as_str(),
|
||||
"arcee-ai/trinity-large-thinking"
|
||||
@@ -542,6 +548,15 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moonshot_native_kimi_ids_support_reasoning_except_for_coding() {
|
||||
// #3016: bare Moonshot ids (no moonshotai/ prefix) emit
|
||||
// reasoning_content; kimi-for-coding is the non-thinking exception.
|
||||
assert!(model_supports_reasoning("kimi-k2.6"));
|
||||
assert!(model_supports_reasoning("kimi-k2.5"));
|
||||
assert!(!model_supports_reasoning("kimi-for-coding"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arcee_direct_models_have_static_windows_without_reasoning_flag() {
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user