fix(openai): strip internal metadata from gpt-5.4 responses
- Add strip_internal_metadata helper to remove prefixes like 'to=multi_tool_use.parallel' - Clean up Thai text preambles reported in the journal - Apply metadata stripping to both synchronous and streaming response paths - Improve visual quality of proxied model responses
This commit is contained in:
@@ -43,6 +43,7 @@ impl OpenAIProvider {
|
||||
pub fn parse_tool_uses_json(text: &str) -> Vec<crate::models::ToolCall> {
|
||||
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<crate::models::ToolCallDelta> = 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user