fix(deepseek): more aggressive R1 fix and detailed error logging
- Ensure assistant tool calls always have content: "" instead of null. - Add debug-level logging of the full request body when DeepSeek returns 400. - Improved R1 placeholder injection to include content sanitation.
This commit is contained in:
@@ -77,10 +77,12 @@ impl super::Provider for DeepSeekProvider {
|
|||||||
for m in messages {
|
for m in messages {
|
||||||
if m["role"] == "assistant" && m.get("tool_calls").is_some() {
|
if m["role"] == "assistant" && m.get("tool_calls").is_some() {
|
||||||
if m.get("reasoning_content").is_none() || m["reasoning_content"].is_null() {
|
if m.get("reasoning_content").is_none() || m["reasoning_content"].is_null() {
|
||||||
// DeepSeek R1 requires reasoning_content for tool calls in history.
|
|
||||||
// If missing (e.g. from client or other model), inject a placeholder.
|
|
||||||
m["reasoning_content"] = serde_json::json!("Thinking...");
|
m["reasoning_content"] = serde_json::json!("Thinking...");
|
||||||
}
|
}
|
||||||
|
// DeepSeek R1 often requires content to be a string, not null
|
||||||
|
if m.get("content").is_none() || m["content"].is_null() {
|
||||||
|
m["content"] = serde_json::json!("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,6 +102,7 @@ impl super::Provider for DeepSeekProvider {
|
|||||||
let status = response.status();
|
let status = response.status();
|
||||||
let error_text = response.text().await.unwrap_or_default();
|
let error_text = response.text().await.unwrap_or_default();
|
||||||
tracing::error!("DeepSeek API error ({}): {}", status, error_text);
|
tracing::error!("DeepSeek API error ({}): {}", status, error_text);
|
||||||
|
tracing::debug!("DeepSeek Request Body: {}", serde_json::to_string(&body).unwrap_or_default());
|
||||||
return Err(AppError::ProviderError(format!("DeepSeek API error ({}): {}", status, error_text)));
|
return Err(AppError::ProviderError(format!("DeepSeek API error ({}): {}", status, error_text)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +167,10 @@ impl super::Provider for DeepSeekProvider {
|
|||||||
if m.get("reasoning_content").is_none() || m["reasoning_content"].is_null() {
|
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!("Thinking...");
|
||||||
}
|
}
|
||||||
|
// DeepSeek R1 often requires content to be a string, not null
|
||||||
|
if m.get("content").is_none() || m["content"].is_null() {
|
||||||
|
m["content"] = serde_json::json!("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,9 +225,11 @@ impl super::Provider for DeepSeekProvider {
|
|||||||
match probe_resp {
|
match probe_resp {
|
||||||
Ok(r) if !r.status().is_success() => {
|
Ok(r) if !r.status().is_success() => {
|
||||||
let status = r.status();
|
let status = r.status();
|
||||||
let body = r.text().await.unwrap_or_default();
|
let error_body = r.text().await.unwrap_or_default();
|
||||||
tracing::error!("DeepSeek Stream Error Probe ({}): {}", status, body);
|
tracing::error!("DeepSeek Stream Error Probe ({}): {}", status, error_body);
|
||||||
Err(AppError::ProviderError(format!("DeepSeek API error ({}): {}", status, 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());
|
||||||
|
Err(AppError::ProviderError(format!("DeepSeek API error ({}): {}", status, error_body)))?;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
Err(AppError::ProviderError(format!("Stream error: {}", e)))?;
|
Err(AppError::ProviderError(format!("Stream error: {}", e)))?;
|
||||||
|
|||||||
@@ -65,10 +65,10 @@ pub async fn messages_to_openai_json(messages: &[UnifiedMessage]) -> Result<Vec<
|
|||||||
msg["reasoning_content"] = serde_json::json!(reasoning);
|
msg["reasoning_content"] = serde_json::json!(reasoning);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For assistant messages with tool_calls, content can be null
|
// For assistant messages with tool_calls, content can be empty string
|
||||||
if let Some(tool_calls) = &m.tool_calls {
|
if let Some(tool_calls) = &m.tool_calls {
|
||||||
if parts.is_empty() {
|
if parts.is_empty() {
|
||||||
msg["content"] = serde_json::Value::Null;
|
msg["content"] = serde_json::json!("");
|
||||||
} else {
|
} else {
|
||||||
msg["content"] = serde_json::json!(parts);
|
msg["content"] = serde_json::json!(parts);
|
||||||
}
|
}
|
||||||
@@ -143,10 +143,10 @@ pub async fn messages_to_openai_json_text_only(
|
|||||||
msg["reasoning_content"] = serde_json::json!(reasoning);
|
msg["reasoning_content"] = serde_json::json!(reasoning);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For assistant messages with tool_calls, content can be null
|
// For assistant messages with tool_calls, content can be empty string
|
||||||
if let Some(tool_calls) = &m.tool_calls {
|
if let Some(tool_calls) = &m.tool_calls {
|
||||||
if parts.is_empty() {
|
if parts.is_empty() {
|
||||||
msg["content"] = serde_json::Value::Null;
|
msg["content"] = serde_json::json!("");
|
||||||
} else {
|
} else {
|
||||||
msg["content"] = serde_json::json!(parts);
|
msg["content"] = serde_json::json!(parts);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user