fix: handle reasoning_content for Kimi/thinking models (#7252)
Signed-off-by: clayarnoldg2m <carnold@g2m.ai> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -82,7 +82,7 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<
|
||||
let mut output = Vec::new();
|
||||
let mut content_array = Vec::new();
|
||||
let mut text_array = Vec::new();
|
||||
let mut reasoning_text: Option<String> = None;
|
||||
let mut reasoning_text = String::new();
|
||||
|
||||
for content in &message.content {
|
||||
match content {
|
||||
@@ -116,7 +116,7 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<
|
||||
continue;
|
||||
}
|
||||
MessageContent::Reasoning(r) => {
|
||||
reasoning_text = Some(r.text.clone());
|
||||
reasoning_text.push_str(&r.text);
|
||||
}
|
||||
MessageContent::ToolRequest(request) => match &request.tool_call {
|
||||
Ok(tool_call) => {
|
||||
@@ -278,15 +278,11 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<
|
||||
converted["content"] = json!(null);
|
||||
}
|
||||
|
||||
// DeepSeek requires reasoning_content field when tool_calls are present
|
||||
// Set it to the captured reasoning text, or empty string if not present
|
||||
if converted.get("tool_calls").is_some() {
|
||||
let reasoning = reasoning_text.unwrap_or_default();
|
||||
converted["reasoning_content"] = json!(reasoning);
|
||||
} else if let Some(reasoning) = reasoning_text {
|
||||
if !reasoning.is_empty() {
|
||||
converted["reasoning_content"] = json!(reasoning);
|
||||
}
|
||||
// Include reasoning_content only when non-empty.
|
||||
// Kimi rejects empty reasoning_content (""), so we must omit it entirely
|
||||
// when there's no reasoning to send.
|
||||
if !reasoning_text.is_empty() {
|
||||
converted["reasoning_content"] = json!(reasoning_text);
|
||||
}
|
||||
|
||||
if converted.get("content").is_some() || converted.get("tool_calls").is_some() {
|
||||
@@ -542,6 +538,7 @@ where
|
||||
use futures::StreamExt;
|
||||
|
||||
let mut accumulated_reasoning: Vec<Value> = Vec::new();
|
||||
let mut accumulated_reasoning_content = String::new();
|
||||
|
||||
'outer: while let Some(response) = stream.next().await {
|
||||
if response.as_ref().is_ok_and(|s| s == "data: [DONE]") {
|
||||
@@ -562,6 +559,9 @@ where
|
||||
if let Some(details) = &chunk.choices[0].delta.reasoning_details {
|
||||
accumulated_reasoning.extend(details.iter().cloned());
|
||||
}
|
||||
if let Some(rc) = &chunk.choices[0].delta.reasoning_content {
|
||||
accumulated_reasoning_content.push_str(rc);
|
||||
}
|
||||
}
|
||||
|
||||
let mut usage = extract_usage_with_output_tokens(&chunk);
|
||||
@@ -602,6 +602,9 @@ where
|
||||
if let Some(details) = &tool_chunk.choices[0].delta.reasoning_details {
|
||||
accumulated_reasoning.extend(details.iter().cloned());
|
||||
}
|
||||
if let Some(rc) = &tool_chunk.choices[0].delta.reasoning_content {
|
||||
accumulated_reasoning_content.push_str(rc);
|
||||
}
|
||||
if let Some(delta_tool_calls) = &tool_chunk.choices[0].delta.tool_calls {
|
||||
for delta_call in delta_tool_calls {
|
||||
if let Some(index) = delta_call.index {
|
||||
@@ -642,6 +645,10 @@ where
|
||||
};
|
||||
|
||||
let mut contents = Vec::new();
|
||||
if !accumulated_reasoning_content.is_empty() {
|
||||
contents.push(MessageContent::reasoning(&accumulated_reasoning_content));
|
||||
accumulated_reasoning_content.clear();
|
||||
}
|
||||
let mut sorted_indices: Vec<_> = tool_call_data.keys().cloned().collect();
|
||||
sorted_indices.sort();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user