[fix] convert message types in toolshim mode (#2730)

Co-authored-by: Alice Hau <ahau@squareup.com>
Co-authored-by: Alice Hau <alice.a.hau@gmail.com>
This commit is contained in:
Alice Hau
2025-05-30 13:23:25 -04:00
committed by GitHub
parent 7e9f959661
commit 3d277177db
2 changed files with 87 additions and 4 deletions
+12 -2
View File
@@ -8,7 +8,8 @@ use crate::message::{Message, MessageContent, ToolRequest};
use crate::providers::base::{Provider, ProviderUsage};
use crate::providers::errors::ProviderError;
use crate::providers::toolshim::{
augment_message_with_tool_calls, modify_system_prompt_for_tool_json, OllamaInterpreter,
augment_message_with_tool_calls, convert_tool_messages_to_text,
modify_system_prompt_for_tool_json, OllamaInterpreter,
};
use crate::session;
use mcp_core::tool::Tool;
@@ -110,8 +111,17 @@ impl Agent {
) -> Result<(Message, ProviderUsage), ProviderError> {
let config = provider.get_model_config();
// Convert tool messages to text if toolshim is enabled
let messages_for_provider = if config.toolshim {
convert_tool_messages_to_text(messages)
} else {
messages.to_vec()
};
// Call the provider to get a response
let (mut response, usage) = provider.complete(system_prompt, messages, tools).await?;
let (mut response, usage) = provider
.complete(system_prompt, &messages_for_provider, tools)
.await?;
// Store the model information in the global store
crate::providers::base::set_current_model(&usage.model);
+75 -2
View File
@@ -38,6 +38,7 @@ use crate::model::ModelConfig;
use crate::providers::formats::openai::create_request;
use anyhow::Result;
use mcp_core::tool::{Tool, ToolCall};
use mcp_core::Content;
use reqwest::Client;
use serde_json::{json, Value};
use std::time::Duration;
@@ -164,7 +165,10 @@ impl OllamaInterpreter {
payload["stream"] = json!(false); // needed for the /api/chat endpoint to work
payload["format"] = format_schema;
// tracing::warn!("payload: {}", serde_json::to_string_pretty(&payload).unwrap_or_default());
tracing::info!(
"Tool interpreter payload: {}",
serde_json::to_string_pretty(&payload).unwrap_or_default()
);
let response = self.client.post(&url).json(&payload).send().await?;
@@ -193,7 +197,10 @@ impl OllamaInterpreter {
fn process_interpreter_response(response: &Value) -> Result<Vec<ToolCall>, ProviderError> {
let mut tool_calls = Vec::new();
tracing::info!(
"Tool interpreter response is {}",
serde_json::to_string_pretty(&response).unwrap_or_default()
);
// Extract tool_calls array from the response
if response.get("message").is_some() && response["message"].get("content").is_some() {
let content = response["message"]["content"].as_str().unwrap_or_default();
@@ -298,6 +305,72 @@ pub fn format_tool_info(tools: &[Tool]) -> String {
tool_info
}
/// Convert messages containing ToolRequest/ToolResponse to text messages for toolshim mode
/// This is necessary because some providers (like Bedrock) validate that tool_use/tool_result
/// blocks can only exist when tools are defined, but in toolshim mode we pass empty tools
pub fn convert_tool_messages_to_text(messages: &[Message]) -> Vec<Message> {
messages
.iter()
.map(|message| {
let mut new_content = Vec::new();
let mut has_tool_content = false;
for content in &message.content {
match content {
MessageContent::ToolRequest(req) => {
has_tool_content = true;
// Convert tool request to text format
let text = if let Ok(tool_call) = &req.tool_call {
format!(
"Using tool: {}\n{{\n \"name\": \"{}\",\n \"arguments\": {}\n}}",
tool_call.name,
tool_call.name,
serde_json::to_string_pretty(&tool_call.arguments)
.unwrap_or_default()
)
} else {
"Tool request failed".to_string()
};
new_content.push(MessageContent::text(text));
}
MessageContent::ToolResponse(res) => {
has_tool_content = true;
// Convert tool response to text format
let text = match &res.tool_result {
Ok(contents) => {
let text_contents: Vec<String> = contents
.iter()
.filter_map(|c| match c {
Content::Text(t) => Some(t.text.clone()),
_ => None,
})
.collect();
format!("Tool result:\n{}", text_contents.join("\n"))
}
Err(e) => format!("Tool error: {}", e),
};
new_content.push(MessageContent::text(text));
}
_ => {
// Keep other content types as-is
new_content.push(content.clone());
}
}
}
if has_tool_content {
Message {
role: message.role.clone(),
content: new_content,
created: message.created,
}
} else {
message.clone()
}
})
.collect()
}
/// Modifies the system prompt to include tool usage instructions when tool interpretation is enabled
pub fn modify_system_prompt_for_tool_json(system_prompt: &str, tools: &[Tool]) -> String {
let tool_info = format_tool_info(tools);