Repository: ollama/ollama
Pull request: renderers: add olmo3.1 and olmo3 fixes

renderer for olmo3.1 along with fixes for olmo3 renderer

Changed files:
filename: model/renderers/olmo3.go
patch: @@ -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 <functions></functions> XML tags. You may call one or more functions to assist with the user query. Output any function calls within <function_calls></function_calls> 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 <functions></functions> XML tags. You may call one or more functions to assist with the user query. Output any function calls within <function_calls></function_calls> 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("
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("
assistant\n\n")
+		sb.WriteString("
assistant\n")
 	}
 
 	return sb.String(), nil

filename: model/renderers/olmo3_test.go
patch: @@ -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. <functions></functions>
\n" +
 				"
user\n" +
 				"Hello!
\n" +
-				"
assistant\n\n",
+				"
assistant\n",
 		},
 		{
 			name: "with system message no tools",
@@ -36,7 +36,7 @@ func TestOlmo3Renderer(t *testing.T) {
 				"You are a helpful assistant.
\n" +
 				"
user\n" +
 				"Hello!
\n" +
-				"
assistant\n\n",
+				"
assistant\n",
 		},
 		{
 			name: "with system message and tools",
@@ -64,7 +64,7 @@ func TestOlmo3Renderer(t *testing.T) {
 				`You are a helpful assistant.<functions>[{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather", "parameters": {"type": "object", "required": ["location"], "properties": {"location": {"type": "string", "description": "The city"}}}}}]</functions>
` + "\n" +
 				"
user\n" +
 				"What is the weather?
\n" +
-				"
assistant\n\n",
+				"
assistant\n",
 		},
 		{
 			name: "default system with tools - includes function instruction",
@@ -93,7 +93,7 @@ func TestOlmo3Renderer(t *testing.T) {
 				`<functions>[{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather", "parameters": {"type": "object", "required": ["location"], "properties": {"location": {"type": "string", "description": "The city"}}}}}]</functions>
` + "\n" +
 				"
user\n" +
 				"What is the weather?
\n" +
-				"
assistant\n\n",
+				"
assistant\n",
 		},
 		{
 			name: "assistant with tool calls - function call syntax",
@@ -141,7 +141,7 @@ func TestOlmo3Renderer(t *testing.T) {
 				`Let me check the weather.<function_calls>get_weather(location="San Francisco")</function_calls>
` + "\n" +
 				"
environment\n" +
 				`{"temperature": 68}
` + "\n" +
-				"
assistant\n\n",
+				"
assistant\n",
 		},
 		{
 			name: "multi-turn conversation",
@@ -159,7 +159,7 @@ func TestOlmo3Renderer(t *testing.T) {
 				"Hi there!
\n" +
 				"
user\n" +
 				"How are you?
\n" +
-				"
assistant\n\n",
+				"
assistant\n",
 		},
 		{
 			name: "parallel tool calls - newline separated",
@@ -214,7 +214,7 @@ func TestOlmo3Renderer(t *testing.T) {
 				`{"temperature": 68}
` + "\n" +
 				"
environment\n" +
 				`{"temperature": 55}
` + "\n" +
-				"
assistant\n\n",
+				"
assistant\n",
 		},
 		{
 			name: "tool call with multiple arguments",
@@ -259,7 +259,7 @@ func TestOlmo3Renderer(t *testing.T) {
 				"Book a flight
\n" +
 				"
assistant\n" +
 				`<function_calls>book_flight(from="SFO", to="NYC")</function_calls>
` + "\n" +
-				"
assistant\n\n",
+				"
assistant\n",
 		},
 		{
 			name: "assistant prefill - no generation prompt",

filename: model/renderers/olmo3_think.go
patch: @@ -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{}
-
-type olmo3ThinkToolCall struct {
-	ID       string                 `json:"id,omitempty"`
-	Type     string                 `json:"type,omitempty"`
-	Function olmo3ThinkToolCallFunc `json:"function"`
-}
+const (
+	olmo3ThinkFunctionsSuffix  = " You do not currently have access to any functions. <functions></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 olmo3ThinkToolCallFunc struct {
-	Name      string `json:"name"`
-	Arguments string `json:"arguments"`
+type Olmo3ThinkRenderer struct {
+	Variant Olmo3ThinkVariant
 }
 
-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("
system\n")
-	sb.WriteString(systemContent)
 
-	if len(tools) > 0 {
-		functionsJSON, err := marshalWithSpaces(tools)
-		if err != nil {
-			return "", err
-		}
-		sb.WriteString(" <functions>")
-		sb.WriteString(string(functionsJSON))
-		sb.WriteString("</functions>")
+	if systemMessage != nil {
+		sb.WriteString(systemMessage.Content)
+		sb.WriteString(olmo3ThinkFunctionsSuffix)
 	} else {
-		sb.WriteString(olmo3ThinkNoFunctionsMessage)
-		sb.WriteString(" <functions></functions>")
+		// 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("
\n")
 
-	for i, message := range filteredMessages {
-		lastMessage := i == len(filteredMessages)-1
+	sb.WriteString("
\n")
 
+	for _, message := range filteredMessages {
 		switch message.Role {
 		case "user":
 			sb.WriteString("
user\n")
@@ -73,58 +70,15 @@ func (r *Olmo3ThinkRenderer) Render(messages []api.Message, tools []api.Tool, _
 
 		case "assistant":
 			sb.WriteString("
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("<function_calls>")
-				sb.WriteString(string(toolCallsJSON))
-				sb.WriteString("</function_calls>")
-			}
-
-			if !lastMessage {
-				sb.WriteString("
\n")
-			}
-
-		case "tool":
-			sb.WriteString("
environment\n")
-			sb.WriteString(message.Content)
 			sb.WriteString("
\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("
assistant\n<think>")
-	}
+	// Always add generation prompt with <think> tag for thinking models
+	sb.WriteString("
assistant\n<think>")
 
 	return sb.String(), nil
 }

filename: model/renderers/olmo3_think_test.go
patch: @@ -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: "
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. <functions></functions>
\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.
\n" +
 				"
user\n" +
 				"Hello!
\n" +
 				"
assistant\n" +
 				"<think>",
 		},
 		{
-			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) {
 				"<think>",
 		},
 		{
-			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: "
system\n" +
-				`You are a helpful assistant. <functions>[{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather", "parameters": {"type": "object", "required": ["location"], "properties": {"location": {"type": "string", "description": "The city"}}}}}]</functions>
` + "\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.
\n" +
 				"
user\n" +
 				"What is the weather?
\n" +
 				"
assistant\n" +
 				"<think>",
 		},
 		{
-			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: "
system\n" +
-				`You are a helpful assistant. <functions>[{"type": "function", "function": {"name": "get_weather", "description": "Get the current weather", "parameters": {"type": "object", "required": ["location"], "properties": {"location": {"type": "string", "description": "The city"}}}}}]</functions>
` + "\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.
\n" +
 				"
user\n" +
 				"What is the weather in SF?
\n" +
 				"
assistant\n" +
-				`Let me check the weather.<function_calls>[{"id": "call_1", "type": "function", "function": {"name": "get_weather", "arguments": "{\"location\":\"San Francisco\"}"}}]</function_calls>
` + "\n" +
-				"
environment\n" +
-				`{"temperature": 68}
` + "\n" +
+				"Let me check the weather.
\n" +
 				"
assistant\n" +
 				"<think>",
 		},
 		{
-			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: "
system\n" +
-				"You are a helpful assistant. You do not currently have access to any functions. <functions></functions>
\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.
\n" +
 				"
user\n" +
 				"Hello
\n" +
 				"
assistant\n" +
@@ -138,81 +114,64 @@ func TestOlmo3ThinkRenderer(t *testing.T) {
 				"<think>",
 		},
 		{
-			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: "
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. <functions>[{"type": "function", "function": {"name": "get_weather", "parameters": {"type": "object", "properties": {"location": {"type": "string"}}}}}]</functions>
` + "\n" +
+				"You are a helpful AI assistant.
\n" +
 				"
user\n" +
-				"Get weather in SF and NYC
\n" +
+				"Hello!
\n" +
 				"
assistant\n" +
-				`<function_calls>[{"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\"}"}}]</function_calls>
` + "\n" +
-				"
environment\n" +
-				`{"temperature": 68}
` + "\n" +
-				"
environment\n" +
-				`{"temperature": 55}
` + "\n" +
+				"<think>",
+		},
+		{
+			name:    "32b_with_custom_system_gets_suffix",
+			variant: Olmo3Think32B,
+			msgs: []api.Message{
+				{Role: "system", Content: "You are a helpful assistant."},
+				{Role: "user", Content: "Hello!"},
+			},
+			expected: "
system\n" +
+				"You are a helpful assistant. You do not currently have access to any functions. <functions></functions>
\n" +
+				"
user\n" +
+				"Hello!
\n" +
 				"
assistant\n" +
 				"<think>",
 		},
 		{
-			name: "assistant message only content no tool calls",
+			name:    "31_basic_without_system",
+			variant: Olmo31Think,
 			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: "user", Content: "Hello!"},
 			},
 			expected: "
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. <functions></functions>
\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.
\n" +
 				"
user\n" +
-				"Tell me a joke
\n" +
+				"Hello!
\n" +
 				"
assistant\n" +
-				"Why did the chicken cross the road?
\n" +
+				"<think>",
+		},
+		{
+			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: "
system\n" +
+				"You are a helpful assistant. You do not currently have access to any functions. <functions></functions>
\n" +
 				"
user\n" +
-				"I don't know, why?
\n" +
+				"Hello!
\n" +
 				"
assistant\n" +
 				"<think>",
 		},
 	}
 
 	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)
 			}

filename: model/renderers/renderer.go
patch: @@ -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