fix: strip thinking tags from OpenAI-compatible provider responses
Remove leaked thinking/reasoning tags from assistant content to prevent internal reasoning text from being exposed to end users. Fixes #1235
This commit is contained in:
parent
cdbd09bf2f
commit
84a2dc291d
1 changed files with 44 additions and 3 deletions
|
|
@ -350,9 +350,16 @@ func parseResponse(body io.Reader) (*LLMResponse, error) {
|
||||||
toolCalls = append(toolCalls, toolCall)
|
toolCalls = append(toolCalls, toolCall)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract thinking content from content if present (for models like MiniMax)
|
||||||
|
content := choice.Message.Content
|
||||||
|
reasoningContent := choice.Message.ReasoningContent
|
||||||
|
if reasoningContent == "" {
|
||||||
|
content, reasoningContent = extractThinkingTags(content)
|
||||||
|
}
|
||||||
|
|
||||||
return &LLMResponse{
|
return &LLMResponse{
|
||||||
Content: choice.Message.Content,
|
Content: content,
|
||||||
ReasoningContent: choice.Message.ReasoningContent,
|
ReasoningContent: reasoningContent,
|
||||||
Reasoning: choice.Message.Reasoning,
|
Reasoning: choice.Message.Reasoning,
|
||||||
ReasoningDetails: choice.Message.ReasoningDetails,
|
ReasoningDetails: choice.Message.ReasoningDetails,
|
||||||
ToolCalls: toolCalls,
|
ToolCalls: toolCalls,
|
||||||
|
|
@ -360,8 +367,42 @@ func parseResponse(body io.Reader) (*LLMResponse, error) {
|
||||||
Usage: apiResponse.Usage,
|
Usage: apiResponse.Usage,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
// extractThinkingTags extracts content between
|
||||||
|
// and from the content string.
|
||||||
|
// Returns the content with thinking tags removed and the extracted thinking content.
|
||||||
|
func extractThinkingTags(content string) (string, string) {
|
||||||
|
const thinkOpen = "<think>"
|
||||||
|
const thinkClose = "</think>"
|
||||||
|
|
||||||
|
// Check if content contains thinking tags
|
||||||
|
if !strings.Contains(content, thinkOpen) {
|
||||||
|
return content, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract thinking content
|
||||||
|
start := strings.Index(content, thinkOpen)
|
||||||
|
end := strings.Index(content, thinkClose)
|
||||||
|
if end <= start {
|
||||||
|
return content, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
thinkingContent := content[start+len(thinkOpen) : end]
|
||||||
|
|
||||||
|
// Remove thinking tags from content
|
||||||
|
var result strings.Builder
|
||||||
|
result.Grow(len(content))
|
||||||
|
|
||||||
|
// Add content before thinking tags
|
||||||
|
result.WriteString(content[:start])
|
||||||
|
|
||||||
|
// Add content after thinking tags
|
||||||
|
if end+len(thinkClose) < len(content) {
|
||||||
|
result.WriteString(content[end+len(thinkClose):])
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.String(), thinkingContent
|
||||||
|
}
|
||||||
|
|
||||||
// openaiMessage is the wire-format message for OpenAI-compatible APIs.
|
|
||||||
// It mirrors protocoltypes.Message but omits SystemParts, which is an
|
// It mirrors protocoltypes.Message but omits SystemParts, which is an
|
||||||
// internal field that would be unknown to third-party endpoints.
|
// internal field that would be unknown to third-party endpoints.
|
||||||
type openaiMessage struct {
|
type openaiMessage struct {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue