mirror of
https://github.com/ollama/ollama.git
synced 2025-11-11 15:57:04 +01:00
add registries for parsers/renderers
This commit is contained in:
@@ -16,7 +16,28 @@ type Parser interface {
|
|||||||
HasThinkingSupport() bool
|
HasThinkingSupport() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
func ParserForName(name string) Parser {
|
func ParserForName(name string) Parser {
|
||||||
|
if parser, ok := registry.constructors[name]; ok {
|
||||||
|
return parser()
|
||||||
|
}
|
||||||
switch name {
|
switch name {
|
||||||
case "qwen3-coder":
|
case "qwen3-coder":
|
||||||
parser := &Qwen3CoderParser{}
|
parser := &Qwen3CoderParser{}
|
||||||
|
|||||||
97
model/parsers/parsers_test.go
Normal file
97
model/parsers/parsers_test.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package parsers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ollama/ollama/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockParser struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockParser) Init(tools []api.Tool, lastMessage *api.Message) []api.Tool {
|
||||||
|
return tools
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockParser) Add(s string, done bool) (content string, thinking string, calls []api.ToolCall, err error) {
|
||||||
|
return "mock:" + s, "", nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockParser) HasToolSupport() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockParser) HasThinkingSupport() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterCustomParser(t *testing.T) {
|
||||||
|
// Register a custom parser
|
||||||
|
Register("custom-parser", func() Parser {
|
||||||
|
return &mockParser{name: "custom"}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Retrieve it
|
||||||
|
parser := ParserForName("custom-parser")
|
||||||
|
if parser == nil {
|
||||||
|
t.Fatal("expected parser to be registered")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test it works
|
||||||
|
content, _, _, err := parser.Add("test", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if content != "mock:test" {
|
||||||
|
t.Errorf("expected 'mock:test', got %q", content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuiltInParsersStillWork(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{"passthrough"},
|
||||||
|
{"qwen3-coder"},
|
||||||
|
{"harmony"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
parser := ParserForName(tt.name)
|
||||||
|
if parser == nil {
|
||||||
|
t.Fatalf("expected built-in parser %q to exist", tt.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverrideBuiltInParser(t *testing.T) {
|
||||||
|
// Override a built-in parser
|
||||||
|
Register("passthrough", func() Parser {
|
||||||
|
return &mockParser{name: "override"}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Should get the override
|
||||||
|
parser := ParserForName("passthrough")
|
||||||
|
if parser == nil {
|
||||||
|
t.Fatal("expected parser to exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test it's the override
|
||||||
|
content, _, _, err := parser.Add("test", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if content != "mock:test" {
|
||||||
|
t.Errorf("expected 'mock:test' from override, got %q", content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnknownParserReturnsNil(t *testing.T) {
|
||||||
|
parser := ParserForName("nonexistent-parser")
|
||||||
|
if parser != nil {
|
||||||
|
t.Error("expected nil for unknown parser")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,46 @@
|
|||||||
package renderers
|
package renderers
|
||||||
|
|
||||||
import "github.com/ollama/ollama/api"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ollama/ollama/api"
|
||||||
|
)
|
||||||
|
|
||||||
type Renderer interface {
|
type Renderer interface {
|
||||||
Render(messages []api.Message, tools []api.Tool, think *api.ThinkValue) (string, error)
|
Render(messages []api.Message, tools []api.Tool, think *api.ThinkValue) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RendererForName(name string) Renderer {
|
type (
|
||||||
|
RendererConstructor func() Renderer
|
||||||
|
RendererRegistry struct {
|
||||||
|
renderers map[string]RendererConstructor
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *RendererRegistry) Register(name string, renderer RendererConstructor) {
|
||||||
|
r.renderers[name] = renderer
|
||||||
|
}
|
||||||
|
|
||||||
|
var registry = RendererRegistry{
|
||||||
|
renderers: make(map[string]RendererConstructor),
|
||||||
|
}
|
||||||
|
|
||||||
|
func Register(name string, renderer RendererConstructor) {
|
||||||
|
registry.Register(name, renderer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderWithRenderer(name string, msgs []api.Message, tools []api.Tool, think *api.ThinkValue) (string, error) {
|
||||||
|
renderer := rendererForName(name)
|
||||||
|
if renderer == nil {
|
||||||
|
return "", fmt.Errorf("unknown renderer %q", name)
|
||||||
|
}
|
||||||
|
return renderer.Render(msgs, tools, think)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rendererForName(name string) Renderer {
|
||||||
|
if constructor, ok := registry.renderers[name]; ok {
|
||||||
|
return constructor()
|
||||||
|
}
|
||||||
switch name {
|
switch name {
|
||||||
case "qwen3-coder":
|
case "qwen3-coder":
|
||||||
renderer := &Qwen3CoderRenderer{}
|
renderer := &Qwen3CoderRenderer{}
|
||||||
|
|||||||
67
model/renderers/renderer_test.go
Normal file
67
model/renderers/renderer_test.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package renderers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ollama/ollama/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockRenderer struct{}
|
||||||
|
|
||||||
|
func (m *mockRenderer) Render(msgs []api.Message, tools []api.Tool, think *api.ThinkValue) (string, error) {
|
||||||
|
return "mock-output", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterCustomRenderer(t *testing.T) {
|
||||||
|
// Register a custom renderer
|
||||||
|
Register("custom-renderer", func() Renderer {
|
||||||
|
return &mockRenderer{}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Retrieve and use it
|
||||||
|
result, err := RenderWithRenderer("custom-renderer", nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if result != "mock-output" {
|
||||||
|
t.Errorf("expected 'mock-output', got %q", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuiltInRendererStillWorks(t *testing.T) {
|
||||||
|
// Test that qwen3-coder still works
|
||||||
|
messages := []api.Message{
|
||||||
|
{Role: "user", Content: "Hello"},
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := RenderWithRenderer("qwen3-coder", messages, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if result == "" {
|
||||||
|
t.Error("expected non-empty result from qwen3-coder renderer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverrideBuiltInRenderer(t *testing.T) {
|
||||||
|
// Override the built-in renderer
|
||||||
|
Register("qwen3-coder", func() Renderer {
|
||||||
|
return &mockRenderer{}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Should get the override
|
||||||
|
result, err := RenderWithRenderer("qwen3-coder", nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if result != "mock-output" {
|
||||||
|
t.Errorf("expected 'mock-output' from override, got %q", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnknownRendererReturnsError(t *testing.T) {
|
||||||
|
_, err := RenderWithRenderer("nonexistent-renderer", nil, nil, nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error for unknown renderer")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -106,8 +106,7 @@ func chatPrompt(ctx context.Context, m *Model, tokenize tokenizeFunc, opts *api.
|
|||||||
|
|
||||||
func renderPrompt(m *Model, msgs []api.Message, tools []api.Tool, think *api.ThinkValue) (string, error) {
|
func renderPrompt(m *Model, msgs []api.Message, tools []api.Tool, think *api.ThinkValue) (string, error) {
|
||||||
if m.Config.Renderer != "" {
|
if m.Config.Renderer != "" {
|
||||||
renderer := renderers.RendererForName(m.Config.Renderer)
|
rendered, err := renderers.RenderWithRenderer(m.Config.Renderer, msgs, tools, think)
|
||||||
rendered, err := renderer.Render(msgs, tools, think)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user