fix(providers): handle tool messages in text_only message converter
messages_to_openai_json_text_only() was missing tool-calling support, causing DeepSeek 400 errors when conversations included tool turns. Now mirrors messages_to_openai_json() logic for tool-role messages (tool_call_id, name) and assistant tool_calls, with images replaced by "[Image]" text.
This commit is contained in:
@@ -84,11 +84,41 @@ pub async fn messages_to_openai_json(messages: &[UnifiedMessage]) -> Result<Vec<
|
|||||||
/// Convert messages to OpenAI-compatible JSON, but replace images with a
|
/// Convert messages to OpenAI-compatible JSON, but replace images with a
|
||||||
/// text placeholder "[Image]". Useful for providers that don't support
|
/// text placeholder "[Image]". Useful for providers that don't support
|
||||||
/// multimodal in streaming mode or at all.
|
/// multimodal in streaming mode or at all.
|
||||||
|
///
|
||||||
|
/// Handles tool-calling messages identically to `messages_to_openai_json`:
|
||||||
|
/// assistant messages with `tool_calls`, and tool-role messages with
|
||||||
|
/// `tool_call_id`/`name`.
|
||||||
pub async fn messages_to_openai_json_text_only(
|
pub async fn messages_to_openai_json_text_only(
|
||||||
messages: &[UnifiedMessage],
|
messages: &[UnifiedMessage],
|
||||||
) -> Result<Vec<serde_json::Value>, AppError> {
|
) -> Result<Vec<serde_json::Value>, AppError> {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
for m in messages {
|
for m in messages {
|
||||||
|
// Tool-role messages: { role: "tool", content: "...", tool_call_id: "...", name: "..." }
|
||||||
|
if m.role == "tool" {
|
||||||
|
let text_content = m
|
||||||
|
.content
|
||||||
|
.first()
|
||||||
|
.map(|p| match p {
|
||||||
|
ContentPart::Text { text } => text.clone(),
|
||||||
|
ContentPart::Image(_) => "[Image]".to_string(),
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let mut msg = serde_json::json!({
|
||||||
|
"role": "tool",
|
||||||
|
"content": text_content
|
||||||
|
});
|
||||||
|
if let Some(tool_call_id) = &m.tool_call_id {
|
||||||
|
msg["tool_call_id"] = serde_json::json!(tool_call_id);
|
||||||
|
}
|
||||||
|
if let Some(name) = &m.name {
|
||||||
|
msg["name"] = serde_json::json!(name);
|
||||||
|
}
|
||||||
|
result.push(msg);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build content parts for non-tool messages (images become "[Image]" text)
|
||||||
let mut parts = Vec::new();
|
let mut parts = Vec::new();
|
||||||
for p in &m.content {
|
for p in &m.content {
|
||||||
match p {
|
match p {
|
||||||
@@ -100,10 +130,26 @@ pub async fn messages_to_openai_json_text_only(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.push(serde_json::json!({
|
|
||||||
"role": m.role,
|
let mut msg = serde_json::json!({ "role": m.role });
|
||||||
"content": parts
|
|
||||||
}));
|
// For assistant messages with tool_calls, content can be null
|
||||||
|
if let Some(tool_calls) = &m.tool_calls {
|
||||||
|
if parts.is_empty() {
|
||||||
|
msg["content"] = serde_json::Value::Null;
|
||||||
|
} else {
|
||||||
|
msg["content"] = serde_json::json!(parts);
|
||||||
|
}
|
||||||
|
msg["tool_calls"] = serde_json::json!(tool_calls);
|
||||||
|
} else {
|
||||||
|
msg["content"] = serde_json::json!(parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(name) = &m.name {
|
||||||
|
msg["name"] = serde_json::json!(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(msg);
|
||||||
}
|
}
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user