mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
fix: add kimi/moonshot to Anthropic tool thinking history normalizer (#3377)
* fix: add kimi/moonshot to Anthropic tool thinking history normalizer Kimi's Anthropic-compatible endpoint requires reasoning_content in assistant tool call messages when thinking is enabled, same as DeepSeek. The is_reasoning_content_compatible_identifier already included kimi/moonshot but is_anthropic_tool_thinking_history_identifier was missing them, causing 400 errors on multi-turn conversations with tool calls. Closes #3351 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: unify reasoning vendor hints into a single SSOT list Replace the two separately-maintained vendor identifier lists with one shared REASONING_VENDOR_HINTS constant and a single is_reasoning_vendor_identifier predicate. The Anthropic tool-thinking-history matcher and the openai_chat reasoning_content matcher previously kept independent lists, which is exactly how kimi/moonshot ended up in one but not the other (#3351). A single source of truth keeps the two from drifting apart again. Also add a Kimi regression test for the Anthropic tool-history normalization path. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Jason <farion1231@gmail.com>
This commit is contained in:
@@ -21,6 +21,8 @@ use serde_json::{json, Value};
|
||||
|
||||
const ANTHROPIC_THINKING_PLACEHOLDER: &str = "tool call";
|
||||
const ANTHROPIC_REDACTED_THINKING_PLACEHOLDER: &str = "[redacted thinking]";
|
||||
// Keep hints lowercase; matching lowercases only the input value.
|
||||
const REASONING_VENDOR_HINTS: &[&str] = &["moonshot", "kimi", "deepseek", "mimo", "xiaomimimo"];
|
||||
|
||||
/// 获取 Claude 供应商的 API 格式
|
||||
///
|
||||
@@ -86,18 +88,11 @@ pub fn claude_api_format_needs_transform(api_format: &str) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
fn is_reasoning_content_compatible_identifier(value: &str) -> bool {
|
||||
fn is_reasoning_vendor_identifier(value: &str) -> bool {
|
||||
let value = value.to_ascii_lowercase();
|
||||
value.contains("moonshot")
|
||||
|| value.contains("kimi")
|
||||
|| value.contains("deepseek")
|
||||
|| value.contains("mimo")
|
||||
|| value.contains("xiaomimimo")
|
||||
}
|
||||
|
||||
fn is_anthropic_tool_thinking_history_identifier(value: &str) -> bool {
|
||||
let value = value.to_ascii_lowercase();
|
||||
value.contains("deepseek") || value.contains("mimo") || value.contains("xiaomimimo")
|
||||
REASONING_VENDOR_HINTS
|
||||
.iter()
|
||||
.any(|hint| value.contains(hint))
|
||||
}
|
||||
|
||||
fn should_normalize_anthropic_tool_thinking_history(
|
||||
@@ -112,7 +107,7 @@ fn should_normalize_anthropic_tool_thinking_history(
|
||||
if body
|
||||
.get("model")
|
||||
.and_then(|m| m.as_str())
|
||||
.is_some_and(is_anthropic_tool_thinking_history_identifier)
|
||||
.is_some_and(is_reasoning_vendor_identifier)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -129,7 +124,7 @@ fn should_normalize_anthropic_tool_thinking_history(
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.any(is_anthropic_tool_thinking_history_identifier)
|
||||
.any(is_reasoning_vendor_identifier)
|
||||
}
|
||||
|
||||
/// DeepSeek's Anthropic-compatible endpoint requires thinking history to be
|
||||
@@ -224,7 +219,7 @@ fn should_preserve_reasoning_content_for_openai_chat(provider: &Provider, body:
|
||||
if body
|
||||
.get("model")
|
||||
.and_then(|m| m.as_str())
|
||||
.is_some_and(is_reasoning_content_compatible_identifier)
|
||||
.is_some_and(is_reasoning_vendor_identifier)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -243,7 +238,7 @@ fn should_preserve_reasoning_content_for_openai_chat(provider: &Provider, body:
|
||||
base_urls
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.any(is_reasoning_content_compatible_identifier)
|
||||
.any(is_reasoning_vendor_identifier)
|
||||
}
|
||||
|
||||
pub fn transform_claude_request_for_api_format(
|
||||
@@ -2000,6 +1995,37 @@ mod tests {
|
||||
assert_eq!(content[2]["type"], "tool_use");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kimi_anthropic_tool_history_injects_missing_thinking() {
|
||||
let provider = create_provider(json!({
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "https://api.kimi.com/coding",
|
||||
"ANTHROPIC_API_KEY": "test-key"
|
||||
}
|
||||
}));
|
||||
let mut body = json!({
|
||||
"model": "kimi-for-coding",
|
||||
"messages": [{
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{"type": "tool_use", "id": "call_123", "name": "read_file", "input": {"path": "README.md"}}
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
let changed = normalize_anthropic_tool_thinking_history_for_provider(
|
||||
&mut body,
|
||||
&provider,
|
||||
"anthropic",
|
||||
);
|
||||
|
||||
assert!(changed);
|
||||
let content = body["messages"][0]["content"].as_array().unwrap();
|
||||
assert_eq!(content[0]["type"], "thinking");
|
||||
assert_eq!(content[0]["thinking"], ANTHROPIC_THINKING_PLACEHOLDER);
|
||||
assert_eq!(content[1]["type"], "tool_use");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deepseek_anthropic_tool_history_rewrites_redacted_thinking() {
|
||||
let provider = create_provider(json!({
|
||||
|
||||
Reference in New Issue
Block a user