mirror of
https://github.com/ollama/ollama.git
synced 2026-10-21 11:01:33 +00:00
* create: add server-side MLX imports and drop GGUF conversion Support safetensors imports through the MLX create pipeline both locally and on the server, including remote upload/staging, draft layer handling, cancellation propagation, transfer limits, and shared manifest/blob writing. Limit GGUF create to wrapping existing GGUF inputs into Ollama manifests. Remove the in-tree safetensors-to-GGUF converter, server quantization path, and converter-only dependencies so GGUF conversion and quantization stay in llama.cpp tooling. Keep the MLX path focused on supported safetensors model creation with validation before MLX work, and expose that flow without the --experimental CLI gate. * address comments * add client side gguf create fast path * address comments * rebase adjustments
153 lines
3.9 KiB
Go
153 lines
3.9 KiB
Go
package thinking
|
|
|
|
import (
|
|
"testing"
|
|
"text/template"
|
|
)
|
|
|
|
func TestTemplateSupportsThinking(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
tmpl string
|
|
want bool
|
|
}{
|
|
{name: "paired tags", tmpl: "<think>{{ reasoning }}</think>", want: true},
|
|
{name: "single quoted split", tmpl: "content.split('</think>')", want: true},
|
|
{name: "double quoted split", tmpl: `content.split("</think>")`, want: true},
|
|
{name: "separate reasoning field", tmpl: "content.split('</think>') reasoning_content", want: false},
|
|
{name: "special token reasoning", tmpl: "content.split('</think>') <SPECIAL_12>", want: false},
|
|
{name: "plain template", tmpl: "{{ content }}", want: false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := TemplateSupportsThinking(tt.tmpl); got != tt.want {
|
|
t.Fatalf("TemplateSupportsThinking() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInferThinkingTags(t *testing.T) {
|
|
cases := []struct {
|
|
desc string
|
|
tmplString string
|
|
wantOpeningTag string
|
|
wantClosingTag string
|
|
}{
|
|
{
|
|
desc: "basic",
|
|
tmplString: `
|
|
{{ if .Thinking}}
|
|
/think
|
|
{{ end }}
|
|
{{- range $i, $_ := .Messages }}
|
|
{{- $last := eq (len (slice $.Messages $i)) 1 -}}
|
|
{{ if and $last .Thinking }}
|
|
<think>{{ .Thinking }}</think>
|
|
{{ end }}
|
|
{{ end }}
|
|
`,
|
|
wantOpeningTag: "<think>",
|
|
wantClosingTag: "</think>",
|
|
},
|
|
{
|
|
desc: "doubly nested range",
|
|
tmplString: `
|
|
{{ if .Thinking}}
|
|
/think
|
|
{{ end }}
|
|
{{- range $i, $_ := .Messages }}
|
|
{{- range $j, $_ := .NotMessages }}
|
|
{{- $last := eq (len (slice $.Messages $i)) 1 -}}
|
|
{{ if and $last .Thinking }}
|
|
<think>{{ .Thinking }}</think>
|
|
{{ end }}
|
|
{{ end }}
|
|
{{ end }}
|
|
`,
|
|
wantOpeningTag: "",
|
|
wantClosingTag: "",
|
|
},
|
|
{
|
|
desc: "whitespace is trimmed",
|
|
tmplString: `
|
|
{{ if .Thinking}}
|
|
/think
|
|
{{ end }}
|
|
{{- range $i, $_ := .Messages }}
|
|
{{- $last := eq (len (slice $.Messages $i)) 1 -}}
|
|
{{ if and $last .Thinking }}
|
|
Some text before {{ .Thinking }} Some text after
|
|
{{ end }}
|
|
{{ end }}
|
|
`,
|
|
wantOpeningTag: "Some text before",
|
|
wantClosingTag: "Some text after",
|
|
},
|
|
{
|
|
desc: "qwen3",
|
|
tmplString: `
|
|
{{- if or .System .Tools .Thinking }}<|im_start|>system
|
|
{{- if .System }}
|
|
{{ .System }}
|
|
{{- end }}
|
|
{{- if .Tools }}
|
|
|
|
# Tools
|
|
|
|
You may call one or more functions to assist with the user query.
|
|
|
|
You are provided with function signatures within <tools></tools> XML tags:
|
|
<tools>
|
|
{{- range .Tools }}
|
|
{"type": "function", "function": {{ .Function }}}
|
|
{{- end }}
|
|
</tools>
|
|
|
|
For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:
|
|
<tool_call>
|
|
{"name": <function-name>, "arguments": <args-json-object>}
|
|
</tool_call>
|
|
{{- end }}
|
|
{{- if .Thinking }}
|
|
/think
|
|
{{- else }}
|
|
/no_think
|
|
{{- end }}<|im_end|>
|
|
{{ end }}
|
|
{{- range $i, $_ := .Messages }}
|
|
{{- $last := eq (len (slice $.Messages $i)) 1 -}}
|
|
{{- if eq .Role "user" }}<|im_start|>user
|
|
{{ .Content }}<|im_end|>
|
|
{{ else if eq .Role "assistant" }}<|im_start|>assistant
|
|
{{ if and $last .Thinking }}
|
|
<think>{{ .Thinking }}</think>
|
|
{{ end }}
|
|
{{ if .Content }}{{ .Content }}
|
|
{{- else if .ToolCalls }}<tool_call>
|
|
{{ range .ToolCalls }}{"name": "{{ .Function.Name }}", "arguments": {{ .Function.Arguments }}}
|
|
{{ end }}</tool_call>
|
|
{{- end }}{{ if not $last }}<|im_end|>
|
|
{{ end }}
|
|
{{- else if eq .Role "tool" }}<|im_start|>user
|
|
<tool_response>
|
|
{{ .Content }}
|
|
</tool_response><|im_end|>
|
|
{{ end }}
|
|
{{- if and (ne .Role "assistant") $last }}<|im_start|>assistant
|
|
{{ end }}
|
|
{{- end }}
|
|
`,
|
|
wantOpeningTag: "<think>",
|
|
wantClosingTag: "</think>",
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
tmpl := template.Must(template.New("test").Parse(c.tmplString))
|
|
openingTag, closingTag := InferTags(tmpl)
|
|
if openingTag != c.wantOpeningTag || closingTag != c.wantClosingTag {
|
|
t.Errorf("case %q: got (%q,%q), want (%q,%q)", c.desc, openingTag, closingTag, c.wantOpeningTag, c.wantClosingTag)
|
|
}
|
|
}
|
|
}
|