mirror of
https://github.com/ollama/ollama.git
synced 2026-11-10 15:02:13 -06: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
150 lines
4.1 KiB
Go
150 lines
4.1 KiB
Go
package thinking
|
||
|
||
import (
|
||
"strings"
|
||
"text/template"
|
||
"text/template/parse"
|
||
)
|
||
|
||
// TemplateSupportsThinking reports whether a raw model chat template declares
|
||
// thinking output. It recognizes templates that emit paired think tags and
|
||
// templates that strip prior tagged reasoning; templates using a separate
|
||
// reasoning_content field are not treated as tagged-thinking templates.
|
||
func TemplateSupportsThinking(chatTemplate string) bool {
|
||
if strings.Contains(chatTemplate, "<think>") && strings.Contains(chatTemplate, "</think>") {
|
||
return true
|
||
}
|
||
|
||
return (strings.Contains(chatTemplate, "content.split('</think>')") ||
|
||
strings.Contains(chatTemplate, `content.split("</think>")`)) &&
|
||
!strings.Contains(chatTemplate, "reasoning_content") &&
|
||
!strings.Contains(chatTemplate, "<SPECIAL_12>")
|
||
}
|
||
|
||
func templateVisit(n parse.Node, enterFn func(parse.Node) bool, exitFn func(parse.Node)) {
|
||
if n == nil {
|
||
return
|
||
}
|
||
shouldContinue := enterFn(n)
|
||
if !shouldContinue {
|
||
return
|
||
}
|
||
switch x := n.(type) {
|
||
case *parse.ListNode:
|
||
for _, c := range x.Nodes {
|
||
templateVisit(c, enterFn, exitFn)
|
||
}
|
||
case *parse.BranchNode:
|
||
if x.Pipe != nil {
|
||
templateVisit(x.Pipe, enterFn, exitFn)
|
||
}
|
||
if x.List != nil {
|
||
templateVisit(x.List, enterFn, exitFn)
|
||
}
|
||
if x.ElseList != nil {
|
||
templateVisit(x.ElseList, enterFn, exitFn)
|
||
}
|
||
case *parse.ActionNode:
|
||
templateVisit(x.Pipe, enterFn, exitFn)
|
||
case *parse.WithNode:
|
||
templateVisit(&x.BranchNode, enterFn, exitFn)
|
||
case *parse.RangeNode:
|
||
templateVisit(&x.BranchNode, enterFn, exitFn)
|
||
case *parse.IfNode:
|
||
templateVisit(&x.BranchNode, enterFn, exitFn)
|
||
case *parse.TemplateNode:
|
||
templateVisit(x.Pipe, enterFn, exitFn)
|
||
case *parse.PipeNode:
|
||
for _, c := range x.Cmds {
|
||
templateVisit(c, enterFn, exitFn)
|
||
}
|
||
case *parse.CommandNode:
|
||
for _, a := range x.Args {
|
||
templateVisit(a, enterFn, exitFn)
|
||
}
|
||
// text, field, number, etc. are leaves – nothing to recurse into
|
||
}
|
||
if exitFn != nil {
|
||
exitFn(n)
|
||
}
|
||
}
|
||
|
||
// InferTags uses a heuristic to infer the tags that surround thinking traces:
|
||
// We look for a range node that iterates over "Messages" and then look for a
|
||
// reference to "Thinking" like `{{.Thinking}}`. We then go up to the nearest
|
||
// ListNode and take the first and last TextNodes as the opening and closing
|
||
// tags.
|
||
func InferTags(t *template.Template) (string, string) {
|
||
ancestors := []parse.Node{}
|
||
|
||
openingTag := ""
|
||
closingTag := ""
|
||
|
||
enterFn := func(n parse.Node) bool {
|
||
ancestors = append(ancestors, n)
|
||
|
||
switch x := n.(type) {
|
||
case *parse.FieldNode:
|
||
if len(x.Ident) > 0 && x.Ident[0] == "Thinking" {
|
||
var mostRecentRange *parse.RangeNode
|
||
for i := len(ancestors) - 1; i >= 0; i-- {
|
||
if r, ok := ancestors[i].(*parse.RangeNode); ok {
|
||
mostRecentRange = r
|
||
break
|
||
}
|
||
}
|
||
if mostRecentRange == nil || !rangeUsesField(mostRecentRange, "Messages") {
|
||
return true
|
||
}
|
||
|
||
// TODO(drifkin): to be more robust, check that it's in the action
|
||
// part, not the `if`'s pipeline part. We do match on the nearest list
|
||
// that starts and ends with text nodes, which makes this not strictly
|
||
// necessary for our heuristic
|
||
|
||
// go up to the nearest ancestor that is a *parse.ListNode
|
||
for i := len(ancestors) - 1; i >= 0; i-- {
|
||
if l, ok := ancestors[i].(*parse.ListNode); ok {
|
||
firstNode := l.Nodes[0]
|
||
if t, ok := firstNode.(*parse.TextNode); ok {
|
||
openingTag = strings.TrimSpace(t.String())
|
||
}
|
||
lastNode := l.Nodes[len(l.Nodes)-1]
|
||
if t, ok := lastNode.(*parse.TextNode); ok {
|
||
closingTag = strings.TrimSpace(t.String())
|
||
}
|
||
|
||
break
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
exitFn := func(n parse.Node) {
|
||
ancestors = ancestors[:len(ancestors)-1]
|
||
}
|
||
|
||
templateVisit(t.Root, enterFn, exitFn)
|
||
|
||
return openingTag, closingTag
|
||
}
|
||
|
||
// checks to see if the given field name is present in the pipeline of the given range node
|
||
func rangeUsesField(rangeNode *parse.RangeNode, field string) bool {
|
||
found := false
|
||
enterFn := func(n parse.Node) bool {
|
||
switch x := n.(type) {
|
||
case *parse.FieldNode:
|
||
if x.Ident[0] == field {
|
||
found = true
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
templateVisit(rangeNode.BranchNode.Pipe, enterFn, nil)
|
||
return found
|
||
}
|