mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-06-16 13:34:04 +08:00
fix: backfill placeholder reasoning_content for bare tool-call messages
Thinking models like kimi/Moonshot and DeepSeek reject any assistant message carrying tool_calls without a non-empty reasoning_content. When cross-turn history recovery misses (proxy restart drops the in-memory cache, ambiguous call_id, or a turn produced no reasoning upstream), the converted assistant message has none and the whole request fails with 'reasoning_content is missing in assistant tool call message'. Add a final-stage backfill that runs after all input items are processed, so genuine trailing reasoning items still attach first and a placeholder is only injected when none remains. Mirrors the placeholder behavior in transform::anthropic_to_openai_with_reasoning_content.
This commit is contained in:
@@ -198,6 +198,7 @@ fn append_responses_input_as_chat_messages(
|
||||
&mut pending_reasoning,
|
||||
&mut last_assistant_index,
|
||||
);
|
||||
backfill_tool_call_reasoning_placeholders(messages);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -404,6 +405,45 @@ fn attach_pending_reasoning_to_assistant(
|
||||
}
|
||||
}
|
||||
|
||||
/// 在所有 input 处理完毕后,对仍缺 `reasoning_content` 的 assistant tool-call 消息补占位。
|
||||
/// 必须作为管线末端的最终兜底执行:真实 reasoning 可能以尾随 `reasoning` item 的形式经
|
||||
/// `attach_reasoning_to_last_assistant` 回填,过早注入占位会被 `append_reasoning_content`
|
||||
/// 追加而污染真实思考。
|
||||
fn backfill_tool_call_reasoning_placeholders(messages: &mut [Value]) {
|
||||
for message in messages.iter_mut() {
|
||||
let is_assistant_tool_call = message.get("role").and_then(|value| value.as_str())
|
||||
== Some("assistant")
|
||||
&& message
|
||||
.get("tool_calls")
|
||||
.and_then(|value| value.as_array())
|
||||
.is_some_and(|calls| !calls.is_empty());
|
||||
if is_assistant_tool_call {
|
||||
ensure_tool_call_reasoning_content(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// kimi/Moonshot、DeepSeek 等 thinking 模型要求每条带 `tool_calls` 的 assistant
|
||||
/// 消息都必须携带非空 `reasoning_content`。跨轮历史恢复 miss(如代理重启丢失内存缓存、
|
||||
/// call_id 歧义无法恢复、上游某轮未产出思考)时,这里补一个占位,避免上游返回
|
||||
/// `reasoning_content is missing in assistant tool call message`。
|
||||
/// 与 `transform::anthropic_to_openai_with_reasoning_content` 的占位行为保持对称。
|
||||
fn ensure_tool_call_reasoning_content(message: &mut Value) {
|
||||
let Some(obj) = message.as_object_mut() else {
|
||||
return;
|
||||
};
|
||||
let has_reasoning = obj
|
||||
.get("reasoning_content")
|
||||
.and_then(|value| value.as_str())
|
||||
.is_some_and(|text| !text.trim().is_empty());
|
||||
if !has_reasoning {
|
||||
obj.insert(
|
||||
"reasoning_content".to_string(),
|
||||
Value::String("tool call".to_string()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn attach_reasoning_to_last_assistant(
|
||||
messages: &mut [Value],
|
||||
last_assistant_index: Option<usize>,
|
||||
@@ -1258,6 +1298,36 @@ mod tests {
|
||||
assert_eq!(messages[1]["role"], "tool");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn responses_request_to_chat_injects_placeholder_reasoning_for_bare_tool_call() {
|
||||
// 历史恢复 miss 时,带 tool_calls 的 assistant 消息没有任何可用 reasoning,
|
||||
// 必须补占位,否则 kimi/Moonshot thinking 模型会拒绝整个请求。
|
||||
let input = json!({
|
||||
"model": "kimi-k2-thinking",
|
||||
"input": [
|
||||
{
|
||||
"type": "function_call",
|
||||
"call_id": "call_1",
|
||||
"name": "read_file",
|
||||
"arguments": "{\"path\":\"README.md\"}"
|
||||
},
|
||||
{
|
||||
"type": "function_call_output",
|
||||
"call_id": "call_1",
|
||||
"output": "Readme content"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
let result = responses_to_chat_completions(input).unwrap();
|
||||
let messages = result["messages"].as_array().unwrap();
|
||||
|
||||
assert_eq!(messages[0]["role"], "assistant");
|
||||
assert_eq!(messages[0]["tool_calls"][0]["id"], "call_1");
|
||||
assert_eq!(messages[0]["reasoning_content"], "tool call");
|
||||
assert_eq!(messages[1]["role"], "tool");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn responses_request_to_chat_attaches_trailing_reasoning_to_tool_call_message() {
|
||||
let input = json!({
|
||||
|
||||
Reference in New Issue
Block a user