From 89eb795293f353d575ab52eb9252f73e0269819c Mon Sep 17 00:00:00 2001 From: Parth Sareen Date: Mon, 15 Dec 2025 18:55:17 -0800 Subject: [PATCH] parsers/renderers: use think from user for nemotron (#13492) --- model/parsers/nemotron3nano.go | 11 +++++----- model/parsers/nemotron3nano_test.go | 29 +++++++++------------------ model/parsers/parsers.go | 4 +--- model/renderers/nemotron3nano.go | 8 +++----- model/renderers/nemotron3nano_test.go | 26 +++++------------------- model/renderers/renderer.go | 6 +----- 6 files changed, 25 insertions(+), 59 deletions(-) diff --git a/model/parsers/nemotron3nano.go b/model/parsers/nemotron3nano.go index c230b4e7..6e662fba 100644 --- a/model/parsers/nemotron3nano.go +++ b/model/parsers/nemotron3nano.go @@ -24,19 +24,18 @@ const ( ) type Nemotron3NanoParser struct { - state Nemotron3NanoParserState - buffer strings.Builder - tools []api.Tool - HasThinking bool + state Nemotron3NanoParserState + buffer strings.Builder + tools []api.Tool } func (p *Nemotron3NanoParser) HasToolSupport() bool { return true } -func (p *Nemotron3NanoParser) HasThinkingSupport() bool { return p.HasThinking } +func (p *Nemotron3NanoParser) HasThinkingSupport() bool { return true } func (p *Nemotron3NanoParser) Init(tools []api.Tool, lastMessage *api.Message, thinkValue *api.ThinkValue) []api.Tool { p.tools = tools - // Check both model capability AND request preference + // thinking is enabled if user requests it thinkingEnabled := thinkValue != nil && thinkValue.Bool() prefill := lastMessage != nil && lastMessage.Role == "assistant" diff --git a/model/parsers/nemotron3nano_test.go b/model/parsers/nemotron3nano_test.go index cfe74a31..a4517fc4 100644 --- a/model/parsers/nemotron3nano_test.go +++ b/model/parsers/nemotron3nano_test.go @@ -203,7 +203,7 @@ func TestNemotron3NanoParser(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - p := &Nemotron3NanoParser{HasThinking: tt.thinkValue != nil && tt.thinkValue.Bool()} + p := &Nemotron3NanoParser{} p.Init(nil, nil, tt.thinkValue) content, thinking, calls, err := p.Add(tt.input, false) @@ -441,7 +441,7 @@ func TestNemotron3NanoParser_Streaming(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - p := &Nemotron3NanoParser{HasThinking: tt.thinkValue != nil && tt.thinkValue.Bool()} + p := &Nemotron3NanoParser{} p.Init(nil, nil, tt.thinkValue) var allContent string @@ -488,24 +488,15 @@ func TestNemotron3NanoParser_HasToolSupport(t *testing.T) { } func TestNemotron3NanoParser_HasThinkingSupport(t *testing.T) { - t.Run("with thinking enabled", func(t *testing.T) { - p := &Nemotron3NanoParser{HasThinking: true} - if !p.HasThinkingSupport() { - t.Error("expected HasThinkingSupport to return true") - } - }) - - t.Run("with thinking disabled", func(t *testing.T) { - p := &Nemotron3NanoParser{HasThinking: false} - if p.HasThinkingSupport() { - t.Error("expected HasThinkingSupport to return false") - } - }) + p := &Nemotron3NanoParser{} + if !p.HasThinkingSupport() { + t.Error("expected HasThinkingSupport to return true") + } } func TestNemotron3NanoParser_Init(t *testing.T) { t.Run("starts in thinking state when enabled", func(t *testing.T) { - p := &Nemotron3NanoParser{HasThinking: true} + p := &Nemotron3NanoParser{} p.Init(nil, nil, &api.ThinkValue{Value: true}) if p.state != Nemotron3NanoCollectingThinking { t.Errorf("expected state Nemotron3NanoCollectingThinking, got %v", p.state) @@ -513,7 +504,7 @@ func TestNemotron3NanoParser_Init(t *testing.T) { }) t.Run("starts in content state when thinking disabled", func(t *testing.T) { - p := &Nemotron3NanoParser{HasThinking: true} + p := &Nemotron3NanoParser{} p.Init(nil, nil, &api.ThinkValue{Value: false}) if p.state != Nemotron3NanoCollectingContent { t.Errorf("expected state Nemotron3NanoCollectingContent, got %v", p.state) @@ -521,7 +512,7 @@ func TestNemotron3NanoParser_Init(t *testing.T) { }) t.Run("starts in content state when nil thinkValue", func(t *testing.T) { - p := &Nemotron3NanoParser{HasThinking: true} + p := &Nemotron3NanoParser{} p.Init(nil, nil, nil) if p.state != Nemotron3NanoCollectingContent { t.Errorf("expected state Nemotron3NanoCollectingContent, got %v", p.state) @@ -529,7 +520,7 @@ func TestNemotron3NanoParser_Init(t *testing.T) { }) t.Run("starts in content state with assistant prefill", func(t *testing.T) { - p := &Nemotron3NanoParser{HasThinking: true} + p := &Nemotron3NanoParser{} prefill := &api.Message{Role: "assistant", Content: "Starting..."} p.Init(nil, prefill, &api.ThinkValue{Value: true}) if p.state != Nemotron3NanoCollectingContent { diff --git a/model/parsers/parsers.go b/model/parsers/parsers.go index 417261b1..4c7656f9 100644 --- a/model/parsers/parsers.go +++ b/model/parsers/parsers.go @@ -63,9 +63,7 @@ func ParserForName(name string) Parser { case "olmo3-think": return &Olmo3ThinkParser{} case "nemotron-3-nano": - return &Nemotron3NanoParser{HasThinking: false} - case "nemotron-3-nano-thinking": - return &Nemotron3NanoParser{HasThinking: true} + return &Nemotron3NanoParser{} default: return nil } diff --git a/model/renderers/nemotron3nano.go b/model/renderers/nemotron3nano.go index 08074d18..478a59bd 100644 --- a/model/renderers/nemotron3nano.go +++ b/model/renderers/nemotron3nano.go @@ -8,15 +8,13 @@ import ( "github.com/ollama/ollama/api" ) -type Nemotron3NanoRenderer struct { - IsThinking bool -} +type Nemotron3NanoRenderer struct{} func (r *Nemotron3NanoRenderer) Render(messages []api.Message, tools []api.Tool, thinkValue *api.ThinkValue) (string, error) { var sb strings.Builder - // thinking is enabled: model must support it AND user must request it - enableThinking := r.IsThinking && (thinkValue != nil && thinkValue.Bool()) + // thinking is enabled if user requests it + enableThinking := thinkValue != nil && thinkValue.Bool() // Extract system message if present var systemMessage string diff --git a/model/renderers/nemotron3nano_test.go b/model/renderers/nemotron3nano_test.go index dad528cc..ca1feb93 100644 --- a/model/renderers/nemotron3nano_test.go +++ b/model/renderers/nemotron3nano_test.go @@ -14,7 +14,6 @@ func TestNemotron3NanoRenderer(t *testing.T) { msgs []api.Message tools []api.Tool thinkValue *api.ThinkValue - isThinking bool expected string }{ { @@ -22,7 +21,6 @@ func TestNemotron3NanoRenderer(t *testing.T) { msgs: []api.Message{ {Role: "user", Content: "Hello!"}, }, - isThinking: true, thinkValue: &api.ThinkValue{Value: true}, expected: "<|im_start|>system\n<|im_end|>\n" + "<|im_start|>user\nHello!<|im_end|>\n" + @@ -33,7 +31,7 @@ func TestNemotron3NanoRenderer(t *testing.T) { msgs: []api.Message{ {Role: "user", Content: "Hello!"}, }, - isThinking: false, + thinkValue: nil, expected: "<|im_start|>system\n<|im_end|>\n" + "<|im_start|>user\nHello!<|im_end|>\n" + "<|im_start|>assistant\n", @@ -44,7 +42,6 @@ func TestNemotron3NanoRenderer(t *testing.T) { {Role: "system", Content: "You are a helpful assistant."}, {Role: "user", Content: "Hello!"}, }, - isThinking: true, thinkValue: &api.ThinkValue{Value: true}, expected: "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n" + "<|im_start|>user\nHello!<|im_end|>\n" + @@ -57,7 +54,6 @@ func TestNemotron3NanoRenderer(t *testing.T) { {Role: "assistant", Content: "Hello! How can I help?"}, {Role: "user", Content: "Tell me a joke"}, }, - isThinking: true, thinkValue: &api.ThinkValue{Value: true}, expected: "<|im_start|>system\n<|im_end|>\n" + "<|im_start|>user\nHi<|im_end|>\n" + @@ -86,7 +82,6 @@ func TestNemotron3NanoRenderer(t *testing.T) { }, }, }, - isThinking: true, thinkValue: &api.ThinkValue{Value: true}, expected: "<|im_start|>system\n" + "# Tools\n\nYou have access to the following functions:\n\n\n" + @@ -141,7 +136,6 @@ func TestNemotron3NanoRenderer(t *testing.T) { }, }, }, - isThinking: true, thinkValue: &api.ThinkValue{Value: true}, expected: "<|im_start|>system\n" + "# Tools\n\nYou have access to the following functions:\n\n\n" + @@ -198,7 +192,6 @@ func TestNemotron3NanoRenderer(t *testing.T) { }, }, }, - isThinking: true, thinkValue: &api.ThinkValue{Value: true}, expected: "<|im_start|>system\n" + "# Tools\n\nYou have access to the following functions:\n\n\n" + @@ -228,7 +221,6 @@ func TestNemotron3NanoRenderer(t *testing.T) { {Role: "assistant", Content: "Hello!", Thinking: "Let me think about this..."}, {Role: "user", Content: "How are you?"}, }, - isThinking: true, thinkValue: &api.ThinkValue{Value: true}, expected: "<|im_start|>system\n<|im_end|>\n" + "<|im_start|>user\nHi<|im_end|>\n" + @@ -274,7 +266,6 @@ func TestNemotron3NanoRenderer(t *testing.T) { }, }, }, - isThinking: true, thinkValue: &api.ThinkValue{Value: true}, expected: "<|im_start|>system\n" + "# Tools\n\nYou have access to the following functions:\n\n\n" + @@ -299,12 +290,11 @@ func TestNemotron3NanoRenderer(t *testing.T) { "<|im_start|>assistant\n\n", }, { - name: "thinking disabled even when model supports it", + name: "thinking disabled when user doesn't request it", msgs: []api.Message{ {Role: "user", Content: "Hello!"}, }, - isThinking: true, // model supports thinking - thinkValue: nil, // but user didn't request it + thinkValue: nil, expected: "<|im_start|>system\n<|im_end|>\n" + "<|im_start|>user\nHello!<|im_end|>\n" + "<|im_start|>assistant\n", @@ -351,7 +341,6 @@ func TestNemotron3NanoRenderer(t *testing.T) { }, }, }, - isThinking: true, thinkValue: &api.ThinkValue{Value: true}, expected: "<|im_start|>system\n" + "# Tools\n\nYou have access to the following functions:\n\n\n" + @@ -390,7 +379,7 @@ func TestNemotron3NanoRenderer(t *testing.T) { { name: "empty messages list", msgs: []api.Message{}, - isThinking: false, + thinkValue: nil, expected: "<|im_start|>system\n<|im_end|>\n<|im_start|>assistant\n", }, { @@ -417,7 +406,6 @@ func TestNemotron3NanoRenderer(t *testing.T) { }, }, }, - isThinking: true, thinkValue: &api.ThinkValue{Value: true}, expected: "<|im_start|>system\n" + "# Tools\n\nYou have access to the following functions:\n\n\n" + @@ -446,7 +434,6 @@ func TestNemotron3NanoRenderer(t *testing.T) { {Role: "assistant", Thinking: "Deep thoughts here...", Content: ""}, {Role: "user", Content: "What did you think?"}, }, - isThinking: true, thinkValue: &api.ThinkValue{Value: true}, expected: "<|im_start|>system\n<|im_end|>\n" + "<|im_start|>user\nThink about this<|im_end|>\n" + @@ -483,7 +470,6 @@ func TestNemotron3NanoRenderer(t *testing.T) { }, }, }, - isThinking: true, thinkValue: &api.ThinkValue{Value: true}, expected: "<|im_start|>system\n" + "# Tools\n\nYou have access to the following functions:\n\n\n" + @@ -512,7 +498,6 @@ func TestNemotron3NanoRenderer(t *testing.T) { {Role: "assistant", Content: "To call a tool, use tags with inside."}, {Role: "user", Content: "Thanks!"}, }, - isThinking: true, thinkValue: &api.ThinkValue{Value: true}, expected: "<|im_start|>system\n<|im_end|>\n" + "<|im_start|>user\nHow do I format a tool call?<|im_end|>\n" + @@ -546,7 +531,6 @@ func TestNemotron3NanoRenderer(t *testing.T) { }, }, }, - isThinking: true, thinkValue: &api.ThinkValue{Value: true}, expected: "<|im_start|>system\n" + "# Tools\n\nYou have access to the following functions:\n\n\n" + @@ -572,7 +556,7 @@ func TestNemotron3NanoRenderer(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - renderer := &Nemotron3NanoRenderer{IsThinking: tt.isThinking} + renderer := &Nemotron3NanoRenderer{} rendered, err := renderer.Render(tt.msgs, tt.tools, tt.thinkValue) if err != nil { t.Fatal(err) diff --git a/model/renderers/renderer.go b/model/renderers/renderer.go index 7c03cf93..657c75a5 100644 --- a/model/renderers/renderer.go +++ b/model/renderers/renderer.go @@ -77,11 +77,7 @@ func rendererForName(name string) Renderer { renderer := &Olmo3ThinkRenderer{Variant: Olmo3Think32B} return renderer case "nemotron-3-nano": - renderer := &Nemotron3NanoRenderer{IsThinking: false} - return renderer - case "nemotron-3-nano-thinking": - renderer := &Nemotron3NanoRenderer{IsThinking: true} - return renderer + return &Nemotron3NanoRenderer{} default: return nil }