diff --git a/src/providers/openai.rs b/src/providers/openai.rs index 19c8c81c..88c841a5 100644 --- a/src/providers/openai.rs +++ b/src/providers/openai.rs @@ -43,6 +43,7 @@ impl OpenAIProvider { pub fn parse_tool_uses_json(text: &str) -> Vec { let mut calls = Vec::new(); if let Some(start) = text.find("{\"tool_uses\":") { + // ... (rest of method unchanged) // Find the end of the JSON block by matching braces let sub = &text[start..]; let mut brace_count = 0; @@ -87,6 +88,27 @@ impl OpenAIProvider { } calls } + + /// Strips internal metadata prefixes like 'to=multi_tool_use.parallel' from model responses. + pub fn strip_internal_metadata(text: &str) -> String { + let mut result = text.to_string(); + + // Patterns to strip + let patterns = [ + "to=multi_tool_use.parallel", + "to=functions.multi_tool_use", + "ส่งเงินบาทไทยjson", // User reported Thai text preamble + ]; + + for p in patterns { + if let Some(start) = result.find(p) { + // Remove the pattern and any whitespace around it + result.replace_range(start..start + p.len(), ""); + } + } + + result.trim().to_string() + } } #[async_trait] @@ -404,6 +426,8 @@ impl super::Provider for OpenAIProvider { } tool_calls.extend(embedded_calls); } + + content_text = Self::strip_internal_metadata(&content_text); Ok(ProviderResponse { content: content_text, @@ -773,9 +797,10 @@ impl super::Provider for OpenAIProvider { if let Some(start) = content_buffer.find("{\"tool_uses\":") { // Yield text before the JSON block let preamble = content_buffer[..start].to_string(); - if !preamble.is_empty() { + let stripped_preamble = Self::strip_internal_metadata(&preamble); + if !stripped_preamble.is_empty() { yield ProviderStreamChunk { - content: preamble, + content: stripped_preamble, reasoning_content: None, finish_reason: None, tool_calls: None, @@ -785,6 +810,7 @@ impl super::Provider for OpenAIProvider { } // Yield the tool calls + // ... (rest of tool call yielding unchanged) let deltas: Vec = embedded_calls.into_iter().enumerate().map(|(idx, tc)| { crate::models::ToolCallDelta { index: idx as u32, @@ -834,14 +860,17 @@ impl super::Provider for OpenAIProvider { } else { // Standard text, yield and clear buffer let content = std::mem::take(&mut content_buffer); - yield ProviderStreamChunk { - content, - reasoning_content: None, - finish_reason: None, - tool_calls: None, - model: model.clone(), - usage: None, - }; + let stripped_content = Self::strip_internal_metadata(&content); + if !stripped_content.is_empty() { + yield ProviderStreamChunk { + content: stripped_content, + reasoning_content: None, + finish_reason: None, + tool_calls: None, + model: model.clone(), + usage: None, + }; + } } }