diff --git a/server/routes.go b/server/routes.go index 924ba06ca7..d03ac2ece9 100644 --- a/server/routes.go +++ b/server/routes.go @@ -282,12 +282,12 @@ func (s *Server) GenerateHandler(c *gin.Context) { prompt = b.String() } - var thinkingState *thinkingParser + var thinkingState *ThinkingParser openingTag, closingTag := inferThinkingTags(m.Template.Template) if req.Think != nil && *req.Think && openingTag != "" && closingTag != "" { - thinkingState = &thinkingParser{ - openingTag: openingTag, - closingTag: closingTag, + thinkingState = &ThinkingParser{ + OpeningTag: openingTag, + ClosingTag: closingTag, } } @@ -316,7 +316,7 @@ func (s *Server) GenerateHandler(c *gin.Context) { } if thinkingState != nil { - thinking, content := thinkingState.addContent(cr.Content) + thinking, content := thinkingState.AddContent(cr.Content) res.Thinking = thinking res.Response = content } @@ -1522,12 +1522,12 @@ func (s *Server) ChatHandler(c *gin.Context) { return } - var thinkingState *thinkingParser + var thinkingState *ThinkingParser openingTag, closingTag := inferThinkingTags(m.Template.Template) if req.Think != nil && *req.Think && openingTag != "" && closingTag != "" { - thinkingState = &thinkingParser{ - openingTag: openingTag, - closingTag: closingTag, + thinkingState = &ThinkingParser{ + OpeningTag: openingTag, + ClosingTag: closingTag, } } @@ -1565,7 +1565,7 @@ func (s *Server) ChatHandler(c *gin.Context) { } if thinkingState != nil { - thinkingContent, remainingContent := thinkingState.addContent(res.Message.Content) + thinkingContent, remainingContent := thinkingState.AddContent(res.Message.Content) if thinkingContent == "" && remainingContent == "" && !r.Done { // need to accumulate more to decide what to send return @@ -1676,11 +1676,11 @@ func filterThinkTags(msgs []api.Message, m *Model) []api.Message { // change the user output), we should probably perform this filtering // for all thinking models (not just qwen3 & deepseek-r1) since it tends // to save tokens and improve quality. - thinkingState := &thinkingParser{ - openingTag: "", - closingTag: "", + thinkingState := &ThinkingParser{ + OpeningTag: "", + ClosingTag: "", } - _, content := thinkingState.addContent(msg.Content) + _, content := thinkingState.AddContent(msg.Content) msgs[i].Content = content } } diff --git a/server/thinking.go b/server/thinking.go index 2213b6b6e9..4ef3c1848a 100644 --- a/server/thinking.go +++ b/server/thinking.go @@ -46,17 +46,17 @@ func (s thinkingState) String() string { } } -type thinkingParser struct { +type ThinkingParser struct { state thinkingState - openingTag string - closingTag string + OpeningTag string + ClosingTag string acc strings.Builder } -// addContent returns the thinking content and the non-thinking content that +// AddContent returns the thinking content and the non-thinking content that // should be immediately sent to the user. It will internally buffer if it needs // to see more raw content to disambiguate -func (s *thinkingParser) addContent(content string) (string, string) { +func (s *ThinkingParser) AddContent(content string) (string, string) { s.acc.WriteString(content) var thinkingSb, remainingSb strings.Builder @@ -76,12 +76,12 @@ func (s *thinkingParser) addContent(content string) (string, string) { } // the additional bool return is true iff we should continue eating -func eat(s *thinkingParser) (string, string, bool) { +func eat(s *ThinkingParser) (string, string, bool) { switch s.state { case thinkingState_LookingForOpening: trimmed := strings.TrimLeftFunc(s.acc.String(), unicode.IsSpace) - if strings.HasPrefix(trimmed, s.openingTag) { - after := strings.Join(strings.Split(trimmed, s.openingTag)[1:], s.openingTag) + if strings.HasPrefix(trimmed, s.OpeningTag) { + after := strings.Join(strings.Split(trimmed, s.OpeningTag)[1:], s.OpeningTag) after = strings.TrimLeftFunc(after, unicode.IsSpace) // after might contain more than just thinking tokens, so we continue // parsing instead of returning it as thinking tokens here @@ -93,7 +93,7 @@ func eat(s *thinkingParser) (string, string, bool) { s.state = thinkingState_Thinking } return "", "", true - } else if strings.HasPrefix(s.openingTag, trimmed) { + } else if strings.HasPrefix(s.OpeningTag, trimmed) { // partial opening seen, so let's keep accumulating return "", "", false } else if trimmed == "" { @@ -119,10 +119,10 @@ func eat(s *thinkingParser) (string, string, bool) { } case thinkingState_Thinking: acc := s.acc.String() - if strings.Contains(acc, s.closingTag) { - split := strings.Split(acc, s.closingTag) + if strings.Contains(acc, s.ClosingTag) { + split := strings.Split(acc, s.ClosingTag) thinking := split[0] - remaining := strings.Join(split[1:], s.closingTag) + remaining := strings.Join(split[1:], s.ClosingTag) remaining = strings.TrimLeftFunc(remaining, unicode.IsSpace) s.acc.Reset() if remaining == "" { @@ -131,7 +131,7 @@ func eat(s *thinkingParser) (string, string, bool) { s.state = thinkingState_ThinkingDone } return thinking, remaining, false - } else if overlapLen := overlap(acc, s.closingTag); overlapLen > 0 { + } else if overlapLen := overlap(acc, s.ClosingTag); overlapLen > 0 { thinking := acc[:len(acc)-overlapLen] remaining := acc[len(acc)-overlapLen:] s.acc.Reset() diff --git a/server/thinking_test.go b/server/thinking_test.go index a2055635ee..90d3f961db 100644 --- a/server/thinking_test.go +++ b/server/thinking_test.go @@ -26,11 +26,11 @@ func TestExtractThinking(t *testing.T) { }, } for i, tt := range tests { - parser := thinkingParser{ - openingTag: "", - closingTag: "", + parser := ThinkingParser{ + OpeningTag: "", + ClosingTag: "", } - gotThinking, gotContent := parser.addContent(tt.in) + gotThinking, gotContent := parser.AddContent(tt.in) if gotContent != tt.wantContent || gotThinking != tt.wantThink { t.Errorf("case %d: got (%q,%q), want (%q,%q)", i, gotThinking, gotContent, tt.wantThink, tt.wantContent) } @@ -259,15 +259,15 @@ func TestThinkingStreaming(t *testing.T) { } for _, c := range cases { - parser := thinkingParser{ - openingTag: "", - closingTag: "", + parser := ThinkingParser{ + OpeningTag: "", + ClosingTag: "", } if c.skip { continue } for i, step := range c.steps { - thinking, content := parser.addContent(step.input) + thinking, content := parser.AddContent(step.input) if content != step.wantContent || thinking != step.wantThinking { t.Errorf("case %q (step %d): got (%q,%q), want (%q,%q)", c.desc, i, content, thinking, step.wantContent, step.wantThinking) }