From 149a7c3a29f1cc3c18f49a4a98e6e0323ec5c9d4 Mon Sep 17 00:00:00 2001 From: hobokenchicken Date: Thu, 5 Mar 2026 20:51:43 +0000 Subject: [PATCH] fix(deepseek): even stricter R1 history compliance and error logging - Ensure ALL assistant messages in history have reasoning_content and string content. - Use a single space as a professional minimal placeholder for missing reasoning. - Log full offending request bodies at ERROR level for detailed debugging. --- src/providers/deepseek.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/providers/deepseek.rs b/src/providers/deepseek.rs index a643a11d..315dcc84 100644 --- a/src/providers/deepseek.rs +++ b/src/providers/deepseek.rs @@ -72,15 +72,16 @@ impl super::Provider for DeepSeekProvider { obj.remove("logprobs"); obj.remove("top_logprobs"); - // ENSURE: assistant messages with tool_calls must have reasoning_content + // ENSURE: EVERY assistant message must have reasoning_content and valid content if let Some(messages) = obj.get_mut("messages").and_then(|m| m.as_array_mut()) { for m in messages { - if m["role"] == "assistant" && m.get("tool_calls").is_some() { + if m["role"].as_str() == Some("assistant") { + // DeepSeek R1 requires reasoning_content for consistency in history. if m.get("reasoning_content").is_none() || m["reasoning_content"].is_null() { - m["reasoning_content"] = serde_json::json!("Thinking..."); + m["reasoning_content"] = serde_json::json!(" "); } - // DeepSeek R1 often requires content to be a string, not null - if m.get("content").is_none() || m["content"].is_null() { + // DeepSeek R1 often requires content to be a string, not null/array + if m.get("content").is_none() || m["content"].is_null() || m["content"].is_array() { m["content"] = serde_json::json!(""); } } @@ -102,7 +103,7 @@ impl super::Provider for DeepSeekProvider { let status = response.status(); let error_text = response.text().await.unwrap_or_default(); tracing::error!("DeepSeek API error ({}): {}", status, error_text); - tracing::debug!("DeepSeek Request Body: {}", serde_json::to_string(&body).unwrap_or_default()); + tracing::error!("Offending DeepSeek Request Body: {}", serde_json::to_string(&body).unwrap_or_default()); return Err(AppError::ProviderError(format!("DeepSeek API error ({}): {}", status, error_text))); } @@ -160,15 +161,16 @@ impl super::Provider for DeepSeekProvider { obj.remove("logprobs"); obj.remove("top_logprobs"); - // ENSURE: assistant messages with tool_calls must have reasoning_content + // ENSURE: EVERY assistant message must have reasoning_content and valid content if let Some(messages) = obj.get_mut("messages").and_then(|m| m.as_array_mut()) { for m in messages { - if m["role"] == "assistant" && m.get("tool_calls").is_some() { + if m["role"].as_str() == Some("assistant") { + // DeepSeek R1 requires reasoning_content for consistency in history. if m.get("reasoning_content").is_none() || m["reasoning_content"].is_null() { - m["reasoning_content"] = serde_json::json!("Thinking..."); + m["reasoning_content"] = serde_json::json!(" "); } - // DeepSeek R1 often requires content to be a string, not null - if m.get("content").is_none() || m["content"].is_null() { + // DeepSeek R1 often requires content to be a string, not null/array + if m.get("content").is_none() || m["content"].is_null() || m["content"].is_array() { m["content"] = serde_json::json!(""); } } @@ -227,8 +229,8 @@ impl super::Provider for DeepSeekProvider { let status = r.status(); let error_body = r.text().await.unwrap_or_default(); tracing::error!("DeepSeek Stream Error Probe ({}): {}", status, error_body); - // Log the offending request body at debug level (it's large) - tracing::debug!("Offending DeepSeek Request Body: {}", serde_json::to_string(&probe_body).unwrap_or_default()); + // Log the offending request body at ERROR level so it shows up in standard logs + tracing::error!("Offending DeepSeek Request Body: {}", serde_json::to_string(&probe_body).unwrap_or_default()); Err(AppError::ProviderError(format!("DeepSeek API error ({}): {}", status, error_body)))?; } Ok(_) => {