add registries for parsers/renderers

This commit is contained in:
Devon Rifkin
2025-10-14 01:13:54 -07:00
parent 05982a95cb
commit ddaca643d0
5 changed files with 222 additions and 4 deletions

View File

@@ -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{}

View 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")
}
}

View File

@@ -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{}

View 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")
}
}

View File

@@ -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
} }