mirror of
https://github.com/ollama/ollama.git
synced 2025-05-11 02:16:36 +02:00
- Allows specifying whether thinking mode should be on or not - Templates get passed a new option so, e.g., qwen3's template can put `/think` or `/no_think` in the system prompt depending on the value of the setting - Add parsing for thinking blocks in both streaming/non-streaming mode - Update the CLI to make use of these changes TODO: - [ ] Don't parse thinking blocks when the user doesn't explicitly set the option, to maintain backwards compatibility - [ ] Warning on CLI when using a non-thinking/older version of a model (with an old template) - [ ] Wire up capabilities fully - [x] Unify parsing for streaming/non-streaming - [ ] Update templates - [ ] Update python/js libraries - [ ] How to handle differences in models wrt defaults and whether or not the thinking ability can even be controlled. If not specified by the user, should there be a default or should the template be able to check if it was explicitly set?
161 lines
3.6 KiB
Go
161 lines
3.6 KiB
Go
package server
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestExtractThinking(t *testing.T) {
|
|
tests := []struct {
|
|
in, wantContent, wantThink string
|
|
}{
|
|
{
|
|
in: "<think> internal </think> world",
|
|
wantThink: "internal ",
|
|
wantContent: "world",
|
|
},
|
|
{
|
|
in: "<think>a</think><think>b</think>c",
|
|
wantThink: "a",
|
|
wantContent: "<think>b</think>c",
|
|
},
|
|
{
|
|
in: "no think",
|
|
wantThink: "",
|
|
wantContent: "no think",
|
|
},
|
|
}
|
|
for i, tt := range tests {
|
|
gotThinking, gotContent := extractThinking(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)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestThinkingStreaming(t *testing.T) {
|
|
|
|
type step struct {
|
|
input string
|
|
wantThinking string
|
|
wantContent string
|
|
wantStateAfter thinkingParseState
|
|
}
|
|
|
|
cases := []struct {
|
|
desc string
|
|
skip bool
|
|
steps []step
|
|
}{
|
|
{
|
|
desc: "content without a thinking tag",
|
|
steps: []step{
|
|
{
|
|
input: " abc",
|
|
wantThinking: "",
|
|
wantContent: " abc",
|
|
wantStateAfter: thinkingParseState_ThinkingDone,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "content before a thinking tag nerfs the thinking tag",
|
|
steps: []step{
|
|
{
|
|
input: " abc <think>def</think> ghi",
|
|
wantThinking: "",
|
|
wantContent: " abc <think>def</think> ghi",
|
|
wantStateAfter: thinkingParseState_ThinkingDone,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "building up a thinking tag partially",
|
|
// skip: true,
|
|
steps: []step{
|
|
{
|
|
input: " <th",
|
|
wantThinking: "",
|
|
wantContent: "",
|
|
wantStateAfter: thinkingParseState_LookingForOpening,
|
|
},
|
|
{
|
|
input: "in",
|
|
wantThinking: "",
|
|
wantContent: "",
|
|
wantStateAfter: thinkingParseState_LookingForOpening,
|
|
},
|
|
{
|
|
input: "k>a",
|
|
wantThinking: "a",
|
|
wantContent: "",
|
|
wantStateAfter: thinkingParseState_Thinking,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "partial closing tag",
|
|
steps: []step{
|
|
{
|
|
input: "<think>abc</th",
|
|
wantThinking: "abc",
|
|
wantContent: "",
|
|
wantStateAfter: thinkingParseState_Thinking,
|
|
},
|
|
{
|
|
input: "ink>def",
|
|
wantThinking: "",
|
|
wantContent: "def",
|
|
wantStateAfter: thinkingParseState_ThinkingDone,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
desc: "partial closing tag fakeout",
|
|
steps: []step{
|
|
{
|
|
input: "<think>abc</th",
|
|
wantThinking: "abc",
|
|
wantContent: "",
|
|
wantStateAfter: thinkingParseState_Thinking,
|
|
},
|
|
{
|
|
input: "ing>def",
|
|
wantThinking: "</thing>def",
|
|
wantContent: "",
|
|
wantStateAfter: thinkingParseState_Thinking,
|
|
},
|
|
{
|
|
input: "ghi</thi",
|
|
wantThinking: "ghi",
|
|
wantContent: "",
|
|
wantStateAfter: thinkingParseState_Thinking,
|
|
},
|
|
{
|
|
input: "nk>jkl",
|
|
wantThinking: "",
|
|
wantContent: "jkl",
|
|
wantStateAfter: thinkingParseState_ThinkingDone,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
parser := thinkingParser{
|
|
openingTag: "<think>",
|
|
closingTag: "</think>",
|
|
}
|
|
if c.skip {
|
|
continue
|
|
}
|
|
for i, step := range c.steps {
|
|
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)
|
|
}
|
|
if parser.state != step.wantStateAfter {
|
|
t.Errorf("case %q (step %d): got state %s, want %s", c.desc, i, parser.state.String(), step.wantStateAfter.String())
|
|
}
|
|
}
|
|
}
|
|
}
|