chore: refactor format_messages in databricks.rs to reduce line count (#6275)
This commit is contained in:
@@ -24,6 +24,88 @@ struct DatabricksMessage {
|
|||||||
tool_call_id: Option<String>,
|
tool_call_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_text_content(text: &str, image_format: &ImageFormat) -> (Vec<Value>, bool) {
|
||||||
|
let mut items = vec![json!({"type": "text", "text": text})];
|
||||||
|
let has_image = if let Some(path) = detect_image_path(text) {
|
||||||
|
if let Ok(image) = load_image_file(path) {
|
||||||
|
items.push(convert_image(&image, image_format));
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
(items, has_image)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_tool_response(
|
||||||
|
response: &crate::conversation::message::ToolResponse,
|
||||||
|
image_format: &ImageFormat,
|
||||||
|
) -> Vec<DatabricksMessage> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
match &response.tool_result {
|
||||||
|
Ok(call_result) => {
|
||||||
|
let abridged: Vec<_> = call_result
|
||||||
|
.content
|
||||||
|
.iter()
|
||||||
|
.filter(|c| c.audience().is_none_or(|a| a.contains(&Role::Assistant)))
|
||||||
|
.map(|c| c.raw.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut tool_content = Vec::new();
|
||||||
|
let mut image_messages = Vec::new();
|
||||||
|
|
||||||
|
for content in abridged {
|
||||||
|
match content {
|
||||||
|
RawContent::Image(image) => {
|
||||||
|
tool_content.push(Content::text(
|
||||||
|
"This tool result included an image that is uploaded in the next message.",
|
||||||
|
));
|
||||||
|
image_messages.push(DatabricksMessage {
|
||||||
|
role: "user".to_string(),
|
||||||
|
content: [convert_image(&image.no_annotation(), image_format)].into(),
|
||||||
|
tool_calls: None,
|
||||||
|
tool_call_id: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
RawContent::Resource(resource) => {
|
||||||
|
let text = match &resource.resource {
|
||||||
|
ResourceContents::TextResourceContents { text, .. } => text.clone(),
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
tool_content.push(Content::text(text));
|
||||||
|
}
|
||||||
|
_ => tool_content.push(content.no_annotation()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tool_response_content: Value = json!(tool_content
|
||||||
|
.iter()
|
||||||
|
.filter_map(|c| c.as_text().map(|t| t.text.clone()))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(" "));
|
||||||
|
|
||||||
|
result.push(DatabricksMessage {
|
||||||
|
content: tool_response_content,
|
||||||
|
role: "tool".to_string(),
|
||||||
|
tool_call_id: Some(response.id.clone()),
|
||||||
|
tool_calls: None,
|
||||||
|
});
|
||||||
|
result.extend(image_messages);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
result.push(DatabricksMessage {
|
||||||
|
role: "tool".to_string(),
|
||||||
|
content: format!("The tool call returned the following error:\n{}", e).into(),
|
||||||
|
tool_call_id: Some(response.id.clone()),
|
||||||
|
tool_calls: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert internal Message format to Databricks' API message specification
|
/// Convert internal Message format to Databricks' API message specification
|
||||||
/// Databricks is mostly OpenAI compatible, but has some differences (reasoning type, etc)
|
/// Databricks is mostly OpenAI compatible, but has some differences (reasoning type, etc)
|
||||||
/// some openai compatible endpoints use the anthropic image spec at the content level
|
/// some openai compatible endpoints use the anthropic image spec at the content level
|
||||||
@@ -49,53 +131,27 @@ fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<Data
|
|||||||
match content {
|
match content {
|
||||||
MessageContent::Text(text) => {
|
MessageContent::Text(text) => {
|
||||||
if !text.text.is_empty() {
|
if !text.text.is_empty() {
|
||||||
// Check for image paths in the text
|
let (items, multi) = format_text_content(&text.text, image_format);
|
||||||
if let Some(image_path) = detect_image_path(&text.text) {
|
content_array.extend(items);
|
||||||
has_multiple_content = true;
|
has_multiple_content |= multi;
|
||||||
// Try to load and convert the image
|
|
||||||
if let Ok(image) = load_image_file(image_path) {
|
|
||||||
content_array.push(json!({
|
|
||||||
"type": "text",
|
|
||||||
"text": text.text
|
|
||||||
}));
|
|
||||||
content_array.push(convert_image(&image, image_format));
|
|
||||||
} else {
|
|
||||||
content_array.push(json!({
|
|
||||||
"type": "text",
|
|
||||||
"text": text.text
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
content_array.push(json!({
|
|
||||||
"type": "text",
|
|
||||||
"text": text.text
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MessageContent::Thinking(content) => {
|
MessageContent::Thinking(content) => {
|
||||||
has_multiple_content = true;
|
has_multiple_content = true;
|
||||||
content_array.push(json!({
|
content_array.push(json!({
|
||||||
"type": "reasoning",
|
"type": "reasoning",
|
||||||
"summary": [
|
"summary": [{
|
||||||
{
|
"type": "summary_text",
|
||||||
"type": "summary_text",
|
"text": content.thinking,
|
||||||
"text": content.thinking,
|
"signature": content.signature
|
||||||
"signature": content.signature
|
}]
|
||||||
}
|
|
||||||
]
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
MessageContent::RedactedThinking(content) => {
|
MessageContent::RedactedThinking(content) => {
|
||||||
has_multiple_content = true;
|
has_multiple_content = true;
|
||||||
content_array.push(json!({
|
content_array.push(json!({
|
||||||
"type": "reasoning",
|
"type": "reasoning",
|
||||||
"summary": [
|
"summary": [{"type": "summary_encrypted_text", "data": content.data}]
|
||||||
{
|
|
||||||
"type": "summary_encrypted_text",
|
|
||||||
"data": content.data
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
MessageContent::ToolRequest(request) => {
|
MessageContent::ToolRequest(request) => {
|
||||||
@@ -103,152 +159,58 @@ fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<Data
|
|||||||
match &request.tool_call {
|
match &request.tool_call {
|
||||||
Ok(tool_call) => {
|
Ok(tool_call) => {
|
||||||
let sanitized_name = sanitize_function_name(&tool_call.name);
|
let sanitized_name = sanitize_function_name(&tool_call.name);
|
||||||
let arguments_str = match &tool_call.arguments {
|
let arguments_str = tool_call
|
||||||
Some(args) => {
|
.arguments
|
||||||
|
.as_ref()
|
||||||
|
.map(|args| {
|
||||||
serde_json::to_string(args).unwrap_or_else(|_| "{}".to_string())
|
serde_json::to_string(args).unwrap_or_else(|_| "{}".to_string())
|
||||||
}
|
})
|
||||||
None => "{}".to_string(),
|
.unwrap_or_else(|| "{}".to_string());
|
||||||
};
|
|
||||||
|
|
||||||
let tool_calls = converted.tool_calls.get_or_insert_default();
|
converted.tool_calls.get_or_insert_default().push(json!({
|
||||||
tool_calls.push(json!({
|
|
||||||
"id": request.id,
|
"id": request.id,
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {"name": sanitized_name, "arguments": arguments_str}
|
||||||
"name": sanitized_name,
|
|
||||||
"arguments": arguments_str,
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
content_array.push(json!({
|
content_array
|
||||||
"type": "text",
|
.push(json!({"type": "text", "text": format!("Error: {}", e)}));
|
||||||
"text": format!("Error: {}", e)
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MessageContent::SystemNotification(_) => {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
MessageContent::ToolResponse(response) => {
|
MessageContent::ToolResponse(response) => {
|
||||||
match &response.tool_result {
|
result.extend(format_tool_response(response, image_format));
|
||||||
Ok(call_result) => {
|
|
||||||
// Send only contents with no audience or with Assistant in the audience
|
|
||||||
let abridged: Vec<_> = call_result
|
|
||||||
.content
|
|
||||||
.iter()
|
|
||||||
.filter(|content| {
|
|
||||||
content
|
|
||||||
.audience()
|
|
||||||
.is_none_or(|audience| audience.contains(&Role::Assistant))
|
|
||||||
})
|
|
||||||
.map(|content| content.raw.clone())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Process all content, replacing images with placeholder text
|
|
||||||
let mut tool_content = Vec::new();
|
|
||||||
let mut image_messages = Vec::new();
|
|
||||||
|
|
||||||
for content in abridged {
|
|
||||||
match content {
|
|
||||||
RawContent::Image(image) => {
|
|
||||||
tool_content.push(Content::text("This tool result included an image that is uploaded in the next message."));
|
|
||||||
image_messages.push(DatabricksMessage {
|
|
||||||
role: "user".to_string(),
|
|
||||||
content: [convert_image(
|
|
||||||
&image.no_annotation(),
|
|
||||||
image_format,
|
|
||||||
)]
|
|
||||||
.into(),
|
|
||||||
tool_calls: None,
|
|
||||||
tool_call_id: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
RawContent::Resource(resource) => {
|
|
||||||
let text = match &resource.resource {
|
|
||||||
ResourceContents::TextResourceContents {
|
|
||||||
text, ..
|
|
||||||
} => text.clone(),
|
|
||||||
_ => String::new(),
|
|
||||||
};
|
|
||||||
tool_content.push(Content::text(text));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
tool_content.push(content.no_annotation());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let tool_response_content: Value = json!(tool_content
|
|
||||||
.iter()
|
|
||||||
.filter_map(|content| content.as_text().map(|t| t.text.clone()))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(" "));
|
|
||||||
|
|
||||||
result.push(DatabricksMessage {
|
|
||||||
content: tool_response_content,
|
|
||||||
role: "tool".to_string(),
|
|
||||||
tool_call_id: Some(response.id.clone()),
|
|
||||||
tool_calls: None,
|
|
||||||
});
|
|
||||||
// Then add any image messages that need to follow
|
|
||||||
result.extend(image_messages);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
// A tool result error is shown as output so the model can interpret the error message
|
|
||||||
result.push(DatabricksMessage {
|
|
||||||
role: "tool".to_string(),
|
|
||||||
content: format!(
|
|
||||||
"The tool call returned the following error:\n{}",
|
|
||||||
e
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
tool_call_id: Some(response.id.clone()),
|
|
||||||
tool_calls: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
MessageContent::ToolConfirmationRequest(_) => {}
|
|
||||||
MessageContent::ActionRequired(_) => {}
|
|
||||||
MessageContent::Image(image) => {
|
MessageContent::Image(image) => {
|
||||||
content_array.push(convert_image(image, image_format));
|
content_array.push(convert_image(image, image_format));
|
||||||
}
|
}
|
||||||
MessageContent::FrontendToolRequest(req) => {
|
MessageContent::FrontendToolRequest(req) => {
|
||||||
// Frontend tool requests are converted to text messages
|
let text = match &req.tool_call {
|
||||||
if let Ok(tool_call) = &req.tool_call {
|
Ok(tool_call) => format!(
|
||||||
content_array.push(json!({
|
"Frontend tool request: {} ({})",
|
||||||
"type": "text",
|
tool_call.name,
|
||||||
"text": format!(
|
serde_json::to_string_pretty(&tool_call.arguments).unwrap()
|
||||||
"Frontend tool request: {} ({})",
|
),
|
||||||
tool_call.name,
|
Err(e) => format!("Frontend tool request error: {}", e),
|
||||||
serde_json::to_string_pretty(&tool_call.arguments).unwrap()
|
};
|
||||||
)
|
content_array.push(json!({"type": "text", "text": text}));
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
content_array.push(json!({
|
|
||||||
"type": "text",
|
|
||||||
"text": format!(
|
|
||||||
"Frontend tool request error: {}",
|
|
||||||
req.tool_call.as_ref().unwrap_err()
|
|
||||||
)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
MessageContent::SystemNotification(_)
|
||||||
|
| MessageContent::ToolConfirmationRequest(_)
|
||||||
|
| MessageContent::ActionRequired(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !content_array.is_empty() {
|
if !content_array.is_empty() {
|
||||||
// If we only have a single text content and no other special content,
|
converted.content = if content_array.len() == 1
|
||||||
// use the simple string format
|
|
||||||
if content_array.len() == 1
|
|
||||||
&& !has_multiple_content
|
&& !has_multiple_content
|
||||||
&& content_array[0]["type"] == "text"
|
&& content_array[0]["type"] == "text"
|
||||||
{
|
{
|
||||||
converted.content = json!(content_array[0]["text"]);
|
json!(content_array[0]["text"])
|
||||||
} else {
|
} else {
|
||||||
converted.content = json!(content_array);
|
json!(content_array)
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if !content_array.is_empty() || has_tool_calls {
|
if !content_array.is_empty() || has_tool_calls {
|
||||||
|
|||||||
Reference in New Issue
Block a user