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
2cf007c9
Unverified
Commit
2cf007c9
authored
Jun 05, 2025
by
Devon Rifkin
Committed by
GitHub
Jun 05, 2025
Browse files
Merge pull request #10987 from ollama/drifkin/export-thinking-parser
export ThinkingParser
parents
09430011
0683efa6
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 @
2cf007c9
...
...
@@ -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 @
2cf007c9
...
...
@@ -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 @
2cf007c9
...
...
@@ -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