parsers.go 2.63 KB
Newer Older
Devon Rifkin's avatar
Devon Rifkin committed
1
2
3
package parsers

import (
4
5
6
	"strings"
	"unicode"

Devon Rifkin's avatar
Devon Rifkin committed
7
	"github.com/ollama/ollama/api"
8
	"github.com/ollama/ollama/harmony"
Devon Rifkin's avatar
Devon Rifkin committed
9
10
)

Devon Rifkin's avatar
Devon Rifkin committed
11
type Parser interface {
Grace's avatar
Grace committed
12
	// Init initializes the parser with tools, optional last message for chat prefill, and think value
13
	// Returns processed tools if the parser needs to modify them (e.g., harmony renames them)
Grace's avatar
Grace committed
14
	Init(tools []api.Tool, lastMessage *api.Message, thinkValue *api.ThinkValue) []api.Tool
15
16
17
	// Add processes streamed content and returns parsed content, thinking, and tool calls
	// The done flag indicates if this is the last chunk (used for draining accumulators)
	Add(s string, done bool) (content string, thinking string, calls []api.ToolCall, err error)
Devon Rifkin's avatar
Devon Rifkin committed
18
19
20
21
	HasToolSupport() bool
	HasThinkingSupport() bool
}

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
type ParserConstructor func() Parser

type ParserRegistry struct {
	constructors map[string]ParserConstructor
}

func (r *ParserRegistry) Register(name string, constructor ParserConstructor) {
	r.constructors[name] = constructor
}

var registry = ParserRegistry{
	constructors: make(map[string]ParserConstructor),
}

func Register(name string, constructor ParserConstructor) {
	registry.Register(name, constructor)
}

Devon Rifkin's avatar
Devon Rifkin committed
40
func ParserForName(name string) Parser {
41
42
43
	if parser, ok := registry.constructors[name]; ok {
		return parser()
	}
44
45
	var p Parser

Devon Rifkin's avatar
Devon Rifkin committed
46
47
	switch name {
	case "qwen3-coder":
48
		p = &Qwen3CoderParser{}
49
	case "qwen3-vl-instruct":
50
		p = &Qwen3VLParser{hasThinkingSupport: false}
Grace's avatar
Grace committed
51
	case "qwen3-vl-thinking":
52
53
54
		p = &Qwen3VLParser{hasThinkingSupport: true}
	case "ministral":
		p = &MinistralParser{hasThinkingSupport: false}
Devon Rifkin's avatar
Devon Rifkin committed
55
56
	case "passthrough":
		return &PassthroughParser{}
57
58
	case "harmony":
		return harmony.NewHarmonyMessageHandler()
Grace's avatar
Grace committed
59
60
	case "cogito":
		return &CogitoParser{}
Devon Rifkin's avatar
Devon Rifkin committed
61
62
63
	default:
		return nil
	}
64
	return p
Devon Rifkin's avatar
Devon Rifkin committed
65
66
67
68
}

type PassthroughParser struct{}

Grace's avatar
Grace committed
69
func (p *PassthroughParser) Init(tools []api.Tool, lastMessage *api.Message, thinkValue *api.ThinkValue) []api.Tool {
70
71
72
73
	return tools // passthrough doesn't modify tools
}

func (p *PassthroughParser) Add(s string, done bool) (content string, thinking string, calls []api.ToolCall, err error) {
Devon Rifkin's avatar
Devon Rifkin committed
74
75
76
77
78
79
80
81
82
83
	return s, "", nil, nil
}

func (p *PassthroughParser) HasToolSupport() bool {
	return false
}

func (p *PassthroughParser) HasThinkingSupport() bool {
	return false
}
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

func splitAtTag(sb *strings.Builder, tag string, trimAfter bool) (string, string) {
	split := strings.SplitN(sb.String(), tag, 2)
	if len(split) == 1 {
		sb.Reset()
		return split[0], ""
	}
	before := split[0]
	before = strings.TrimRightFunc(before, unicode.IsSpace)
	after := split[1]
	if trimAfter {
		after = strings.TrimLeftFunc(after, unicode.IsSpace)
	}
	sb.Reset()
	sb.WriteString(after)
	return before, after // return events
}