Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
ollama
Commits
0683efa6
Commit
0683efa6
authored
Jun 05, 2025
by
Devon Rifkin
Browse files
export ThinkingParser
parent
09430011
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
35 additions
and
35 deletions
+35
-35
server/routes.go
server/routes.go
+14
-14
server/thinking.go
server/thinking.go
+13
-13
server/thinking_test.go
server/thinking_test.go
+8
-8
No files found.
server/routes.go
View file @
0683efa6
...
...
@@ -282,12 +282,12 @@ func (s *Server) GenerateHandler(c *gin.Context) {
prompt
=
b
.
String
()
}
var
thinkingState
*
t
hinkingParser
var
thinkingState
*
T
hinkingParser
openingTag
,
closingTag
:=
inferThinkingTags
(
m
.
Template
.
Template
)
if
req
.
Think
!=
nil
&&
*
req
.
Think
&&
openingTag
!=
""
&&
closingTag
!=
""
{
thinkingState
=
&
t
hinkingParser
{
o
peningTag
:
openingTag
,
c
losingTag
:
closingTag
,
thinkingState
=
&
T
hinkingParser
{
O
peningTag
:
openingTag
,
C
losingTag
:
closingTag
,
}
}
...
...
@@ -316,7 +316,7 @@ func (s *Server) GenerateHandler(c *gin.Context) {
}
if
thinkingState
!=
nil
{
thinking
,
content
:=
thinkingState
.
a
ddContent
(
cr
.
Content
)
thinking
,
content
:=
thinkingState
.
A
ddContent
(
cr
.
Content
)
res
.
Thinking
=
thinking
res
.
Response
=
content
}
...
...
@@ -1522,12 +1522,12 @@ func (s *Server) ChatHandler(c *gin.Context) {
return
}
var
thinkingState
*
t
hinkingParser
var
thinkingState
*
T
hinkingParser
openingTag
,
closingTag
:=
inferThinkingTags
(
m
.
Template
.
Template
)
if
req
.
Think
!=
nil
&&
*
req
.
Think
&&
openingTag
!=
""
&&
closingTag
!=
""
{
thinkingState
=
&
t
hinkingParser
{
o
peningTag
:
openingTag
,
c
losingTag
:
closingTag
,
thinkingState
=
&
T
hinkingParser
{
O
peningTag
:
openingTag
,
C
losingTag
:
closingTag
,
}
}
...
...
@@ -1565,7 +1565,7 @@ func (s *Server) ChatHandler(c *gin.Context) {
}
if
thinkingState
!=
nil
{
thinkingContent
,
remainingContent
:=
thinkingState
.
a
ddContent
(
res
.
Message
.
Content
)
thinkingContent
,
remainingContent
:=
thinkingState
.
A
ddContent
(
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
:=
&
t
hinkingParser
{
o
peningTag
:
"<think>"
,
c
losingTag
:
"</think>"
,
thinkingState
:=
&
T
hinkingParser
{
O
peningTag
:
"<think>"
,
C
losingTag
:
"</think>"
,
}
_
,
content
:=
thinkingState
.
a
ddContent
(
msg
.
Content
)
_
,
content
:=
thinkingState
.
A
ddContent
(
msg
.
Content
)
msgs
[
i
]
.
Content
=
content
}
}
...
...
server/thinking.go
View file @
0683efa6
...
...
@@ -46,17 +46,17 @@ func (s thinkingState) String() string {
}
}
type
t
hinkingParser
struct
{
type
T
hinkingParser
struct
{
state
thinkingState
o
peningTag
string
c
losingTag
string
O
peningTag
string
C
losingTag
string
acc
strings
.
Builder
}
//
a
ddContent returns the thinking content and the non-thinking content that
//
A
ddContent 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
*
t
hinkingParser
)
a
ddContent
(
content
string
)
(
string
,
string
)
{
func
(
s
*
T
hinkingParser
)
A
ddContent
(
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
*
t
hinkingParser
)
(
string
,
string
,
bool
)
{
func
eat
(
s
*
T
hinkingParser
)
(
string
,
string
,
bool
)
{
switch
s
.
state
{
case
thinkingState_LookingForOpening
:
trimmed
:=
strings
.
TrimLeftFunc
(
s
.
acc
.
String
(),
unicode
.
IsSpace
)
if
strings
.
HasPrefix
(
trimmed
,
s
.
o
peningTag
)
{
after
:=
strings
.
Join
(
strings
.
Split
(
trimmed
,
s
.
o
peningTag
)[
1
:
],
s
.
o
peningTag
)
if
strings
.
HasPrefix
(
trimmed
,
s
.
O
peningTag
)
{
after
:=
strings
.
Join
(
strings
.
Split
(
trimmed
,
s
.
O
peningTag
)[
1
:
],
s
.
O
peningTag
)
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
.
o
peningTag
,
trimmed
)
{
}
else
if
strings
.
HasPrefix
(
s
.
O
peningTag
,
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
.
c
losingTag
)
{
split
:=
strings
.
Split
(
acc
,
s
.
c
losingTag
)
if
strings
.
Contains
(
acc
,
s
.
C
losingTag
)
{
split
:=
strings
.
Split
(
acc
,
s
.
C
losingTag
)
thinking
:=
split
[
0
]
remaining
:=
strings
.
Join
(
split
[
1
:
],
s
.
c
losingTag
)
remaining
:=
strings
.
Join
(
split
[
1
:
],
s
.
C
losingTag
)
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
.
c
losingTag
);
overlapLen
>
0
{
}
else
if
overlapLen
:=
overlap
(
acc
,
s
.
C
losingTag
);
overlapLen
>
0
{
thinking
:=
acc
[
:
len
(
acc
)
-
overlapLen
]
remaining
:=
acc
[
len
(
acc
)
-
overlapLen
:
]
s
.
acc
.
Reset
()
...
...
server/thinking_test.go
View file @
0683efa6
...
...
@@ -26,11 +26,11 @@ func TestExtractThinking(t *testing.T) {
},
}
for
i
,
tt
:=
range
tests
{
parser
:=
t
hinkingParser
{
o
peningTag
:
"<think>"
,
c
losingTag
:
"</think>"
,
parser
:=
T
hinkingParser
{
O
peningTag
:
"<think>"
,
C
losingTag
:
"</think>"
,
}
gotThinking
,
gotContent
:=
parser
.
a
ddContent
(
tt
.
in
)
gotThinking
,
gotContent
:=
parser
.
A
ddContent
(
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
:=
t
hinkingParser
{
o
peningTag
:
"<think>"
,
c
losingTag
:
"</think>"
,
parser
:=
T
hinkingParser
{
O
peningTag
:
"<think>"
,
C
losingTag
:
"</think>"
,
}
if
c
.
skip
{
continue
}
for
i
,
step
:=
range
c
.
steps
{
thinking
,
content
:=
parser
.
a
ddContent
(
step
.
input
)
thinking
,
content
:=
parser
.
A
ddContent
(
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
)
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment