fix(#5626 #5832): handle multiple content chunks & images better (#5839)

Co-authored-by: Pradeepta Dash <pradeepta@exceeds.ai>
This commit is contained in:
Alex Hancock
2025-11-24 10:52:01 -05:00
committed by GitHub
parent 4cc95d4202
commit c82a0dd8fc
3 changed files with 65 additions and 22 deletions
@@ -2,6 +2,7 @@ use crate::conversation::message::{Message, MessageContent};
use crate::model::ModelConfig;
use crate::providers::base::Usage;
use crate::providers::errors::ProviderError;
use crate::providers::utils::{convert_image, ImageFormat};
use anyhow::{anyhow, Result};
use rmcp::model::{object, CallToolRequestParam, ErrorCode, ErrorData, JsonObject, Role, Tool};
use rmcp::object as json_object;
@@ -106,7 +107,9 @@ pub fn format_messages(messages: &[Message]) -> Vec<Value> {
DATA_FIELD: redacted.data
}));
}
MessageContent::Image(_) => continue, // Anthropic doesn't support image content yet
MessageContent::Image(image) => {
content.push(convert_image(image, &ImageFormat::Anthropic));
}
MessageContent::FrontendToolRequest(tool_request) => {
if let Ok(tool_call) = &tool_request.tool_call {
content.push(json!({
+31 -11
View File
@@ -63,25 +63,22 @@ 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();
for content in &message.content {
match content {
MessageContent::Text(text) => {
if !text.text.is_empty() {
// Check for image paths in the text
if let Some(image_path) = detect_image_path(&text.text) {
// Try to load and convert the image
if let Ok(image) = load_image_file(image_path) {
converted["content"] = json!([
{"type": "text", "text": text.text},
convert_image(&image, image_format)
]);
content_array.push(json!({"type": "text", "text": text.text}));
content_array.push(convert_image(&image, image_format));
} else {
// If image loading fails, just use the text
converted["content"] = json!(text.text);
text_array.push(text.text.clone());
}
} else {
converted["content"] = json!(text.text);
text_array.push(text.text.clone());
}
}
}
@@ -205,8 +202,7 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<
// Skip tool confirmation requests
}
MessageContent::Image(image) => {
// Handle direct image content
converted["content"] = json!([convert_image(image, image_format)]);
content_array.push(convert_image(image, image_format));
}
MessageContent::FrontendToolRequest(request) => match &request.tool_call {
Ok(tool_call) => {
@@ -244,9 +240,16 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<
}
}
if !content_array.is_empty() {
converted["content"] = json!(content_array);
} else if !text_array.is_empty() {
converted["content"] = json!(text_array.join("\n"));
}
if converted.get("content").is_some() || converted.get("tool_calls").is_some() {
output.insert(0, converted);
}
messages_spec.extend(output);
}
@@ -1230,6 +1233,23 @@ mod tests {
Ok(())
}
#[test]
fn test_format_messages_multiple_text_blocks() -> anyhow::Result<()> {
let message = Message::user()
.with_text("--- Resource: file:///test.md ---\n# Test\n\n---\n")
.with_text(" What is in the file?");
let spec = format_messages(&[message], &ImageFormat::OpenAi);
assert_eq!(spec.len(), 1);
assert_eq!(spec[0]["role"], "user");
assert_eq!(
spec[0]["content"],
"--- Resource: file:///test.md ---\n# Test\n\n---\n\n What is in the file?"
);
Ok(())
}
#[test]
fn test_create_request_gpt_4o() -> anyhow::Result<()> {
// Test default medium reasoning effort for O3 model