parser/renderer: add Ornith 9B renderer/parser support (#16920)

This commit is contained in:
Parth Sareen 2026-06-25 23:18:47 -07:00 committed by GitHub
parent 2cb2c5381f
commit 2e474c98f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 101 additions and 4 deletions

View file

@ -54,6 +54,8 @@ func ParserForName(name string) Parser {
p = &Qwen3Parser{hasThinkingSupport: true, defaultThinking: true}
case "qwen3.5":
p = &Qwen35Parser{}
case "ornith":
p = &Qwen35Parser{}
case "qwen3-coder":
p = &Qwen3CoderParser{}
case "qwen3-vl-instruct":

View file

@ -65,6 +65,7 @@ func TestBuiltInParsersStillWork(t *testing.T) {
{"lfm2"},
{"lfm2-thinking"},
{"qwen3.5"},
{"ornith"},
{"harmony"},
}

16
model/renderers/ornith.go Normal file
View file

@ -0,0 +1,16 @@
package renderers
type OrnithRenderer struct {
Qwen35Renderer
}
func newOrnithRenderer() Renderer {
return &OrnithRenderer{
Qwen35Renderer: Qwen35Renderer{
isThinking: true,
alwaysRenderAssistantThinkBlock: true,
emitEmptyThinkOnNoThink: true,
useImgTags: RenderImgTags,
},
}
}

View file

@ -0,0 +1,74 @@
package renderers
import (
"testing"
"github.com/ollama/ollama/api"
)
func TestOrnithRendererMatchesAssistantHistoryThinkBlocks(t *testing.T) {
msgs := []api.Message{
{Role: "user", Content: "Say hello."},
{Role: "assistant", Content: "Hello."},
{Role: "user", Content: "Now say bye."},
}
got, err := RenderWithRenderer("ornith", msgs, nil, nil)
if err != nil {
t.Fatalf("render failed: %v", err)
}
want := `<|im_start|>user
Say hello.<|im_end|>
<|im_start|>assistant
<think>
</think>
Hello.<|im_end|>
<|im_start|>user
Now say bye.<|im_end|>
<|im_start|>assistant
<think>
`
if got != want {
t.Fatalf("unexpected Ornith render\n--- got ---\n%q\n--- want ---\n%q", got, want)
}
}
func TestOrnithRendererKeepsAssistantThinkBlocksWhenThinkingDisabled(t *testing.T) {
msgs := []api.Message{
{Role: "user", Content: "Say hello."},
{
Role: "assistant",
Thinking: "Keep it short.",
Content: "Hello.",
},
{Role: "user", Content: "Now say bye."},
}
got, err := RenderWithRenderer("ornith", msgs, nil, &api.ThinkValue{Value: false})
if err != nil {
t.Fatalf("render failed: %v", err)
}
want := `<|im_start|>user
Say hello.<|im_end|>
<|im_start|>assistant
<think>
Keep it short.
</think>
Hello.<|im_end|>
<|im_start|>user
Now say bye.<|im_end|>
<|im_start|>assistant
<think>
</think>
`
if got != want {
t.Fatalf("unexpected Ornith render with thinking disabled\n--- got ---\n%q\n--- want ---\n%q", got, want)
}
}

View file

@ -39,8 +39,9 @@ Reminder:
type Qwen35Renderer struct {
isThinking bool
emitEmptyThinkOnNoThink bool
useImgTags bool
alwaysRenderAssistantThinkBlock bool
emitEmptyThinkOnNoThink bool
useImgTags bool
}
func (r *Qwen35Renderer) LeadingBOS() string {
@ -140,9 +141,10 @@ func (r *Qwen35Renderer) Render(messages []api.Message, tools []api.Tool, think
if message.Role == "user" || (message.Role == "system" && i != 0) {
sb.WriteString(imStartTag + message.Role + "\n" + content + imEndTag + "\n")
} else if message.Role == "assistant" {
contentReasoning, content := splitQwen35ReasoningContent(content, message.Thinking, isThinking)
renderAssistantThinkBlock := r.alwaysRenderAssistantThinkBlock || (isThinking && i > lastQueryIndex)
contentReasoning, content := splitQwen35ReasoningContent(content, message.Thinking, renderAssistantThinkBlock)
if isThinking && i > lastQueryIndex {
if renderAssistantThinkBlock {
sb.WriteString(imStartTag + message.Role + "\n<think>\n" + contentReasoning + "\n</think>\n\n" + content)
} else {
sb.WriteString(imStartTag + message.Role + "\n" + content)

View file

@ -69,6 +69,8 @@ func rendererForName(name string) Renderer {
case "qwen3.5":
renderer := &Qwen35Renderer{isThinking: true, emitEmptyThinkOnNoThink: true, useImgTags: RenderImgTags}
return renderer
case "ornith":
return newOrnithRenderer()
case "cogito":
renderer := &CogitoRenderer{isThinking: true}
return renderer