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