From e3731fb1605dd0949766b9d209427c62fe588bf8 Mon Sep 17 00:00:00 2001 From: Parth Sareen Date: Mon, 15 Dec 2025 11:26:43 -0800 Subject: [PATCH] renderers: add olmo3.1 and olmo3 fixes (#13447) --- model/renderers/olmo3.go | 19 ++-- model/renderers/olmo3_test.go | 16 +-- model/renderers/olmo3_think.go | 110 ++++++-------------- model/renderers/olmo3_think_test.go | 153 ++++++++++------------------ model/renderers/renderer.go | 12 ++- 5 files changed, 119 insertions(+), 191 deletions(-) diff --git a/model/renderers/olmo3.go b/model/renderers/olmo3.go index 24ade20dc..c6cdaa722 100644 --- a/model/renderers/olmo3.go +++ b/model/renderers/olmo3.go @@ -10,12 +10,15 @@ import ( ) const ( - olmo3DefaultSystemMessage = "You are a helpful function-calling AI assistant. " - olmo3NoFunctionsMessage = "You do not currently have access to any functions. " - olmo3WithFunctionsMessage = "You are provided with function signatures within XML tags. You may call one or more functions to assist with the user query. Output any function calls within XML tags. Do not make assumptions about what values to plug into functions." + olmo3DefaultSystemMessage = "You are a helpful function-calling AI assistant. " + olmo31DefaultSystemMessage = "You are Olmo, a helpful AI assistant built by Ai2. Your date cutoff is December 2024, and your model weights are available at https://huggingface.co/allenai. " + olmo3NoFunctionsMessage = "You do not currently have access to any functions. " + olmo3WithFunctionsMessage = "You are provided with function signatures within XML tags. You may call one or more functions to assist with the user query. Output any function calls within XML tags. Do not make assumptions about what values to plug into functions." ) -type Olmo3Renderer struct{} +type Olmo3Renderer struct { + UseExtendedSystemMessage bool +} func (r *Olmo3Renderer) Render(messages []api.Message, tools []api.Tool, _ *api.ThinkValue) (string, error) { var sb strings.Builder @@ -51,7 +54,11 @@ func (r *Olmo3Renderer) Render(messages []api.Message, tools []api.Tool, _ *api. } else { // Default system message - single newline after "system" sb.WriteString("<|im_start|>system\n") - sb.WriteString(olmo3DefaultSystemMessage) + if r.UseExtendedSystemMessage { + sb.WriteString(olmo31DefaultSystemMessage) + } else { + sb.WriteString(olmo3DefaultSystemMessage) + } if len(tools) > 0 { functionsJSON, err := marshalWithSpaces(tools) @@ -140,7 +147,7 @@ func (r *Olmo3Renderer) Render(messages []api.Message, tools []api.Tool, _ *api. } if needsGenerationPrompt { - sb.WriteString("<|im_start|>assistant\n\n") + sb.WriteString("<|im_start|>assistant\n") } return sb.String(), nil diff --git a/model/renderers/olmo3_test.go b/model/renderers/olmo3_test.go index 56c79a23d..be9c4eac2 100644 --- a/model/renderers/olmo3_test.go +++ b/model/renderers/olmo3_test.go @@ -24,7 +24,7 @@ func TestOlmo3Renderer(t *testing.T) { "You are a helpful function-calling AI assistant. You do not currently have access to any functions. <|im_end|>\n" + "<|im_start|>user\n" + "Hello!<|im_end|>\n" + - "<|im_start|>assistant\n\n", + "<|im_start|>assistant\n", }, { name: "with system message no tools", @@ -36,7 +36,7 @@ func TestOlmo3Renderer(t *testing.T) { "You are a helpful assistant.<|im_end|>\n" + "<|im_start|>user\n" + "Hello!<|im_end|>\n" + - "<|im_start|>assistant\n\n", + "<|im_start|>assistant\n", }, { name: "with system message and tools", @@ -64,7 +64,7 @@ func TestOlmo3Renderer(t *testing.T) { `You are a helpful assistant.[{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather", "parameters": {"type": "object", "required": ["location"], "properties": {"location": {"type": "string", "description": "The city"}}}}}]<|im_end|>` + "\n" + "<|im_start|>user\n" + "What is the weather?<|im_end|>\n" + - "<|im_start|>assistant\n\n", + "<|im_start|>assistant\n", }, { name: "default system with tools - includes function instruction", @@ -93,7 +93,7 @@ func TestOlmo3Renderer(t *testing.T) { `[{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather", "parameters": {"type": "object", "required": ["location"], "properties": {"location": {"type": "string", "description": "The city"}}}}}]<|im_end|>` + "\n" + "<|im_start|>user\n" + "What is the weather?<|im_end|>\n" + - "<|im_start|>assistant\n\n", + "<|im_start|>assistant\n", }, { name: "assistant with tool calls - function call syntax", @@ -141,7 +141,7 @@ func TestOlmo3Renderer(t *testing.T) { `Let me check the weather.get_weather(location="San Francisco")<|im_end|>` + "\n" + "<|im_start|>environment\n" + `{"temperature": 68}<|im_end|>` + "\n" + - "<|im_start|>assistant\n\n", + "<|im_start|>assistant\n", }, { name: "multi-turn conversation", @@ -159,7 +159,7 @@ func TestOlmo3Renderer(t *testing.T) { "Hi there!<|im_end|>\n" + "<|im_start|>user\n" + "How are you?<|im_end|>\n" + - "<|im_start|>assistant\n\n", + "<|im_start|>assistant\n", }, { name: "parallel tool calls - newline separated", @@ -214,7 +214,7 @@ func TestOlmo3Renderer(t *testing.T) { `{"temperature": 68}<|im_end|>` + "\n" + "<|im_start|>environment\n" + `{"temperature": 55}<|im_end|>` + "\n" + - "<|im_start|>assistant\n\n", + "<|im_start|>assistant\n", }, { name: "tool call with multiple arguments", @@ -259,7 +259,7 @@ func TestOlmo3Renderer(t *testing.T) { "Book a flight<|im_end|>\n" + "<|im_start|>assistant\n" + `book_flight(from="SFO", to="NYC")<|im_end|>` + "\n" + - "<|im_start|>assistant\n\n", + "<|im_start|>assistant\n", }, { name: "assistant prefill - no generation prompt", diff --git a/model/renderers/olmo3_think.go b/model/renderers/olmo3_think.go index b327d0441..6fe621102 100644 --- a/model/renderers/olmo3_think.go +++ b/model/renderers/olmo3_think.go @@ -1,31 +1,31 @@ package renderers import ( - "encoding/json" "strings" "github.com/ollama/ollama/api" ) +type Olmo3ThinkVariant int + const ( - olmo3ThinkDefaultSystemMessage = "You are OLMo, a helpful function-calling AI assistant built by Ai2. Your date cutoff is November 2024, and your model weights are available at https://huggingface.co/allenai." - olmo3ThinkNoFunctionsMessage = " You do not currently have access to any functions." + // Olmo3Think32B is for allenai/Olmo-3-32B-Think + Olmo3Think32B Olmo3ThinkVariant = iota + // Olmo31Think is for allenai/Olmo-3-7B-Think and allenai/Olmo-3.1-32B-Think (includes model info) + Olmo31Think ) -type Olmo3ThinkRenderer struct{} +const ( + olmo3ThinkFunctionsSuffix = " You do not currently have access to any functions. " + olmo3Think32BSystemMessage = "You are a helpful AI assistant." + olmo31ThinkSystemMessage = "You are Olmo, a helpful AI assistant built by Ai2. Your date cutoff is December 2024, and your model weights are available at https://huggingface.co/allenai." +) -type olmo3ThinkToolCall struct { - ID string `json:"id,omitempty"` - Type string `json:"type,omitempty"` - Function olmo3ThinkToolCallFunc `json:"function"` +type Olmo3ThinkRenderer struct { + Variant Olmo3ThinkVariant } -type olmo3ThinkToolCallFunc struct { - Name string `json:"name"` - Arguments string `json:"arguments"` -} - -func (r *Olmo3ThinkRenderer) Render(messages []api.Message, tools []api.Tool, _ *api.ThinkValue) (string, error) { +func (r *Olmo3ThinkRenderer) Render(messages []api.Message, _ []api.Tool, _ *api.ThinkValue) (string, error) { var sb strings.Builder var systemMessage *api.Message @@ -37,34 +37,31 @@ func (r *Olmo3ThinkRenderer) Render(messages []api.Message, tools []api.Tool, _ } continue } + // Skip tool messages - Think models don't support tools + if message.Role == "tool" { + continue + } filteredMessages = append(filteredMessages, message) } - systemContent := olmo3ThinkDefaultSystemMessage - if systemMessage != nil { - systemContent = systemMessage.Content - } - sb.WriteString("<|im_start|>system\n") - sb.WriteString(systemContent) - if len(tools) > 0 { - functionsJSON, err := marshalWithSpaces(tools) - if err != nil { - return "", err - } - sb.WriteString(" ") - sb.WriteString(string(functionsJSON)) - sb.WriteString("") + if systemMessage != nil { + sb.WriteString(systemMessage.Content) + sb.WriteString(olmo3ThinkFunctionsSuffix) } else { - sb.WriteString(olmo3ThinkNoFunctionsMessage) - sb.WriteString(" ") + // Default system message varies by variant + switch r.Variant { + case Olmo3Think32B: + sb.WriteString(olmo3Think32BSystemMessage) + default: // Olmo3Think7B, Olmo31Think use same template - diverges from HF but confirmed difference from team + sb.WriteString(olmo31ThinkSystemMessage) + } } + sb.WriteString("<|im_end|>\n") - for i, message := range filteredMessages { - lastMessage := i == len(filteredMessages)-1 - + for _, message := range filteredMessages { switch message.Role { case "user": sb.WriteString("<|im_start|>user\n") @@ -73,58 +70,15 @@ func (r *Olmo3ThinkRenderer) Render(messages []api.Message, tools []api.Tool, _ case "assistant": sb.WriteString("<|im_start|>assistant\n") - if message.Content != "" { sb.WriteString(message.Content) } - - if len(message.ToolCalls) > 0 { - toolCalls := make([]olmo3ThinkToolCall, len(message.ToolCalls)) - for j, tc := range message.ToolCalls { - argsJSON, err := json.Marshal(tc.Function.Arguments) - if err != nil { - return "", err - } - toolCalls[j] = olmo3ThinkToolCall{ - ID: tc.ID, - Type: "function", - Function: olmo3ThinkToolCallFunc{ - Name: tc.Function.Name, - Arguments: string(argsJSON), - }, - } - } - toolCallsJSON, err := marshalWithSpaces(toolCalls) - if err != nil { - return "", err - } - sb.WriteString("") - sb.WriteString(string(toolCallsJSON)) - sb.WriteString("") - } - - if !lastMessage { - sb.WriteString("<|im_end|>\n") - } - - case "tool": - sb.WriteString("<|im_start|>environment\n") - sb.WriteString(message.Content) sb.WriteString("<|im_end|>\n") } } - needsGenerationPrompt := true - if len(filteredMessages) > 0 { - lastMsg := filteredMessages[len(filteredMessages)-1] - if lastMsg.Role == "assistant" && len(lastMsg.ToolCalls) == 0 && lastMsg.Content != "" { - needsGenerationPrompt = false - } - } - - if needsGenerationPrompt { - sb.WriteString("<|im_start|>assistant\n") - } + // Always add generation prompt with tag for thinking models + sb.WriteString("<|im_start|>assistant\n") return sb.String(), nil } diff --git a/model/renderers/olmo3_think_test.go b/model/renderers/olmo3_think_test.go index 21e333e34..8bfd5fdce 100644 --- a/model/renderers/olmo3_think_test.go +++ b/model/renderers/olmo3_think_test.go @@ -11,24 +11,27 @@ import ( func TestOlmo3ThinkRenderer(t *testing.T) { tests := []struct { name string + variant Olmo3ThinkVariant msgs []api.Message tools []api.Tool expected string }{ { - name: "basic without system - adds default system", + name: "7b_basic_without_system", + variant: Olmo31Think, msgs: []api.Message{ {Role: "user", Content: "Hello!"}, }, expected: "<|im_start|>system\n" + - "You are OLMo, a helpful function-calling AI assistant built by Ai2. Your date cutoff is November 2024, and your model weights are available at https://huggingface.co/allenai. You do not currently have access to any functions. <|im_end|>\n" + + "You are Olmo, a helpful AI assistant built by Ai2. Your date cutoff is December 2024, and your model weights are available at https://huggingface.co/allenai.<|im_end|>\n" + "<|im_start|>user\n" + "Hello!<|im_end|>\n" + "<|im_start|>assistant\n" + "", }, { - name: "with system message no tools", + name: "7b_with_custom_system", + variant: Olmo31Think, msgs: []api.Message{ {Role: "system", Content: "You are a helpful assistant."}, {Role: "user", Content: "Hello!"}, @@ -41,9 +44,9 @@ func TestOlmo3ThinkRenderer(t *testing.T) { "", }, { - name: "with system message and tools", + name: "7b_tools_ignored", + variant: Olmo31Think, msgs: []api.Message{ - {Role: "system", Content: "You are a helpful assistant."}, {Role: "user", Content: "What is the weather?"}, }, tools: []api.Tool{ @@ -52,27 +55,20 @@ func TestOlmo3ThinkRenderer(t *testing.T) { Function: api.ToolFunction{ Name: "get_weather", Description: "Get the current weather", - Parameters: api.ToolFunctionParameters{ - Type: "object", - Required: []string{"location"}, - Properties: map[string]api.ToolProperty{ - "location": {Type: api.PropertyType{"string"}, Description: "The city"}, - }, - }, }, }, }, expected: "<|im_start|>system\n" + - `You are a helpful assistant. [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather", "parameters": {"type": "object", "required": ["location"], "properties": {"location": {"type": "string", "description": "The city"}}}}}]<|im_end|>` + "\n" + + "You are Olmo, a helpful AI assistant built by Ai2. Your date cutoff is December 2024, and your model weights are available at https://huggingface.co/allenai.<|im_end|>\n" + "<|im_start|>user\n" + "What is the weather?<|im_end|>\n" + "<|im_start|>assistant\n" + "", }, { - name: "assistant with tool calls", + name: "7b_tool_calls_and_tool_messages_ignored", + variant: Olmo31Think, msgs: []api.Message{ - {Role: "system", Content: "You are a helpful assistant."}, {Role: "user", Content: "What is the weather in SF?"}, { Role: "assistant", @@ -81,53 +77,33 @@ func TestOlmo3ThinkRenderer(t *testing.T) { { ID: "call_1", Function: api.ToolCallFunction{ - Name: "get_weather", - Arguments: map[string]any{ - "location": "San Francisco", - }, - }, - }, - }, - }, - {Role: "tool", Content: `{"temperature": 68}`, ToolName: "get_weather"}, - }, - tools: []api.Tool{ - { - Type: "function", - Function: api.ToolFunction{ - Name: "get_weather", - Description: "Get the current weather", - Parameters: api.ToolFunctionParameters{ - Type: "object", - Required: []string{"location"}, - Properties: map[string]api.ToolProperty{ - "location": {Type: api.PropertyType{"string"}, Description: "The city"}, + Name: "get_weather", + Arguments: map[string]any{"location": "San Francisco"}, }, }, }, }, + {Role: "tool", Content: `{"temperature": 68}`}, }, expected: "<|im_start|>system\n" + - `You are a helpful assistant. [{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather", "parameters": {"type": "object", "required": ["location"], "properties": {"location": {"type": "string", "description": "The city"}}}}}]<|im_end|>` + "\n" + + "You are Olmo, a helpful AI assistant built by Ai2. Your date cutoff is December 2024, and your model weights are available at https://huggingface.co/allenai.<|im_end|>\n" + "<|im_start|>user\n" + "What is the weather in SF?<|im_end|>\n" + "<|im_start|>assistant\n" + - `Let me check the weather.[{"id": "call_1", "type": "function", "function": {"name": "get_weather", "arguments": "{\"location\":\"San Francisco\"}"}}]<|im_end|>` + "\n" + - "<|im_start|>environment\n" + - `{"temperature": 68}<|im_end|>` + "\n" + + "Let me check the weather.<|im_end|>\n" + "<|im_start|>assistant\n" + "", }, { - name: "multi-turn conversation", + name: "7b_multi_turn_conversation", + variant: Olmo31Think, msgs: []api.Message{ - {Role: "system", Content: "You are a helpful assistant."}, {Role: "user", Content: "Hello"}, {Role: "assistant", Content: "Hi there!"}, {Role: "user", Content: "How are you?"}, }, expected: "<|im_start|>system\n" + - "You are a helpful assistant. You do not currently have access to any functions. <|im_end|>\n" + + "You are Olmo, a helpful AI assistant built by Ai2. Your date cutoff is December 2024, and your model weights are available at https://huggingface.co/allenai.<|im_end|>\n" + "<|im_start|>user\n" + "Hello<|im_end|>\n" + "<|im_start|>assistant\n" + @@ -138,73 +114,56 @@ func TestOlmo3ThinkRenderer(t *testing.T) { "", }, { - name: "parallel tool calls", + name: "32b_basic_without_system", + variant: Olmo3Think32B, msgs: []api.Message{ - {Role: "user", Content: "Get weather in SF and NYC"}, - { - Role: "assistant", - ToolCalls: []api.ToolCall{ - { - ID: "call_1", - Function: api.ToolCallFunction{ - Name: "get_weather", - Arguments: map[string]any{"location": "San Francisco"}, - }, - }, - { - ID: "call_2", - Function: api.ToolCallFunction{ - Name: "get_weather", - Arguments: map[string]any{"location": "New York"}, - }, - }, - }, - }, - {Role: "tool", Content: `{"temperature": 68}`, ToolName: "get_weather"}, - {Role: "tool", Content: `{"temperature": 55}`, ToolName: "get_weather"}, - }, - tools: []api.Tool{ - { - Type: "function", - Function: api.ToolFunction{ - Name: "get_weather", - Parameters: api.ToolFunctionParameters{ - Type: "object", - Properties: map[string]api.ToolProperty{ - "location": {Type: api.PropertyType{"string"}}, - }, - }, - }, - }, + {Role: "user", Content: "Hello!"}, }, expected: "<|im_start|>system\n" + - `You are OLMo, a helpful function-calling AI assistant built by Ai2. Your date cutoff is November 2024, and your model weights are available at https://huggingface.co/allenai. [{"type": "function", "function": {"name": "get_weather", "parameters": {"type": "object", "properties": {"location": {"type": "string"}}}}}]<|im_end|>` + "\n" + + "You are a helpful AI assistant.<|im_end|>\n" + "<|im_start|>user\n" + - "Get weather in SF and NYC<|im_end|>\n" + - "<|im_start|>assistant\n" + - `[{"id": "call_1", "type": "function", "function": {"name": "get_weather", "arguments": "{\"location\":\"San Francisco\"}"}}, {"id": "call_2", "type": "function", "function": {"name": "get_weather", "arguments": "{\"location\":\"New York\"}"}}]<|im_end|>` + "\n" + - "<|im_start|>environment\n" + - `{"temperature": 68}<|im_end|>` + "\n" + - "<|im_start|>environment\n" + - `{"temperature": 55}<|im_end|>` + "\n" + + "Hello!<|im_end|>\n" + "<|im_start|>assistant\n" + "", }, { - name: "assistant message only content no tool calls", + name: "32b_with_custom_system_gets_suffix", + variant: Olmo3Think32B, msgs: []api.Message{ - {Role: "user", Content: "Tell me a joke"}, - {Role: "assistant", Content: "Why did the chicken cross the road?"}, - {Role: "user", Content: "I don't know, why?"}, + {Role: "system", Content: "You are a helpful assistant."}, + {Role: "user", Content: "Hello!"}, }, expected: "<|im_start|>system\n" + - "You are OLMo, a helpful function-calling AI assistant built by Ai2. Your date cutoff is November 2024, and your model weights are available at https://huggingface.co/allenai. You do not currently have access to any functions. <|im_end|>\n" + + "You are a helpful assistant. You do not currently have access to any functions. <|im_end|>\n" + "<|im_start|>user\n" + - "Tell me a joke<|im_end|>\n" + + "Hello!<|im_end|>\n" + "<|im_start|>assistant\n" + - "Why did the chicken cross the road?<|im_end|>\n" + + "", + }, + { + name: "31_basic_without_system", + variant: Olmo31Think, + msgs: []api.Message{ + {Role: "user", Content: "Hello!"}, + }, + expected: "<|im_start|>system\n" + + "You are Olmo, a helpful AI assistant built by Ai2. Your date cutoff is December 2024, and your model weights are available at https://huggingface.co/allenai.<|im_end|>\n" + "<|im_start|>user\n" + - "I don't know, why?<|im_end|>\n" + + "Hello!<|im_end|>\n" + + "<|im_start|>assistant\n" + + "", + }, + { + name: "31_with_custom_system_gets_suffix", + variant: Olmo31Think, + msgs: []api.Message{ + {Role: "system", Content: "You are a helpful assistant."}, + {Role: "user", Content: "Hello!"}, + }, + expected: "<|im_start|>system\n" + + "You are a helpful assistant. You do not currently have access to any functions. <|im_end|>\n" + + "<|im_start|>user\n" + + "Hello!<|im_end|>\n" + "<|im_start|>assistant\n" + "", }, @@ -212,7 +171,7 @@ func TestOlmo3ThinkRenderer(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - rendered, err := (&Olmo3ThinkRenderer{}).Render(tt.msgs, tt.tools, nil) + rendered, err := (&Olmo3ThinkRenderer{Variant: tt.variant}).Render(tt.msgs, tt.tools, nil) if err != nil { t.Fatal(err) } diff --git a/model/renderers/renderer.go b/model/renderers/renderer.go index 66c2f8de6..e3d797a62 100644 --- a/model/renderers/renderer.go +++ b/model/renderers/renderer.go @@ -60,10 +60,18 @@ func rendererForName(name string) Renderer { renderer := &CogitoRenderer{isThinking: true} return renderer case "olmo3": - renderer := &Olmo3Renderer{} + renderer := &Olmo3Renderer{UseExtendedSystemMessage: false} + return renderer + case "olmo3.1": + renderer := &Olmo3Renderer{UseExtendedSystemMessage: true} return renderer case "olmo3-think": - renderer := &Olmo3ThinkRenderer{} + // Used for Olmo-3-7B-Think and Olmo-3.1-32B-Think (same template) + renderer := &Olmo3ThinkRenderer{Variant: Olmo31Think} + return renderer + case "olmo3-32b-think": + // Used for Olmo-3-32B-Think + renderer := &Olmo3ThinkRenderer{Variant: Olmo3Think32B} return renderer default: return nil