diff --git a/pkg/providers/openai_compat/provider.go b/pkg/providers/openai_compat/provider.go index f97bf3acd..f887202f3 100644 --- a/pkg/providers/openai_compat/provider.go +++ b/pkg/providers/openai_compat/provider.go @@ -10,6 +10,7 @@ import ( "log" "net/http" "net/url" + "regexp" "strings" "time" @@ -346,8 +347,11 @@ func parseResponse(body io.Reader) (*LLMResponse, error) { toolCalls = append(toolCalls, toolCall) } + // Strip any leaked thinking/reasoning tags from content + cleanedContent := stripThinkingTags(choice.Message.Content) + return &LLMResponse{ - Content: choice.Message.Content, + Content: cleanedContent, ReasoningContent: choice.Message.ReasoningContent, Reasoning: choice.Message.Reasoning, ReasoningDetails: choice.Message.ReasoningDetails, @@ -518,3 +522,53 @@ func supportsPromptCacheKey(apiBase string) bool { host := u.Hostname() return host == "api.openai.com" || strings.HasSuffix(host, ".openai.azure.com") } + +// stripThinkingTags removes chain-of-thought tags from content that may be +// leaked by some OpenAI-compatible providers (e.g., MiniMax-style responses). +// Handles both normal tags and escaped Unicode forms. +// Tags removed: , , , , +func stripThinkingTags(content string) string { + if content == "" { + return content + } + + // First, handle escaped forms like \u003cthink\u003e...\u003c/think\u003e + // These appear as literal backslash-u-sequences in the string + content = strings.ReplaceAll(content, `\u003c`, "<") + content = strings.ReplaceAll(content, `\u003e`, ">") + content = strings.ReplaceAll(content, `\\u003c`, "<") + content = strings.ReplaceAll(content, `\\u003e`, ">") + + // Pattern to match thinking/reasoning tags (with optional inner content) + // Matches: content, , + patterns := []string{ + `(?i)]*>.*?`, // ... + `(?i)]*>.*?`, // ... + `(?i)]*>.*?`, // ... + `(?i)]*>.*?`, // ... + `(?i)*>(?:/>|>.*?)`, // or ... (remove tag, keep content) + } + + for _, pattern := range patterns { + re := regexp.MustCompile(pattern) + if strings.HasPrefix(pattern, `(?i) tags, remove the tags but keep the inner content + content = re.ReplaceAllString(content, "$1") + } else { + // For thinking tags, remove both tags and content + content = re.ReplaceAllString(content, "") + } + } + + // Clean up any whitespace-only lines left after removing blocks + lines := strings.Split(content, "\n") + var cleaned []string + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if trimmed != "" { + cleaned = append(cleaned, line) + } + } + + return strings.Join(cleaned, "\n") +}