diff --git a/model/renderers/deepseek3.go b/model/renderers/deepseek3.go
new file mode 100644
index 000000000..c52b08049
--- /dev/null
+++ b/model/renderers/deepseek3.go
@@ -0,0 +1,115 @@
+package renderers
+
+import (
+ "encoding/json"
+ "strings"
+
+ "github.com/ollama/ollama/api"
+)
+
+type DeepSeekRenderer struct {
+ isThinking bool
+}
+
+func (r *DeepSeekRenderer) 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 (true)
+ enableThinking := r.isThinking && (thinkValue != nil && thinkValue.Bool())
+
+ var systemPrompt strings.Builder
+ var conversationMessages []api.Message
+ isFirstSystemPrompt := true
+
+ for _, message := range messages {
+ if message.Role == "system" {
+ if isFirstSystemPrompt {
+ systemPrompt.WriteString(message.Content)
+ isFirstSystemPrompt = false
+ } else {
+ systemPrompt.WriteString("\n\n" + message.Content)
+ }
+ } else {
+ conversationMessages = append(conversationMessages, message)
+ }
+ }
+
+ sb.WriteString("<|begin▁of▁sentence|>" + systemPrompt.String())
+
+ isLastUser := false
+ isToolContext := false
+
+ for _, message := range conversationMessages {
+ switch message.Role {
+ case "user":
+ isToolContext = false
+ isLastUser = true
+ sb.WriteString("<|User|>" + message.Content)
+
+ case "assistant":
+ if len(message.ToolCalls) > 0 {
+ if isLastUser {
+ sb.WriteString("<|Assistant|>")
+ }
+ isLastUser = false
+ isToolContext = false
+
+ if message.Content != "" {
+ sb.WriteString(message.Content)
+ }
+
+ sb.WriteString("<|tool▁calls▁begin|>")
+
+ for _, toolCall := range message.ToolCalls {
+ sb.WriteString("<|tool▁call▁begin|>" + toolCall.Function.Name + "<|tool▁sep|>")
+
+ argsJSON, _ := json.Marshal(toolCall.Function.Arguments)
+ sb.WriteString(string(argsJSON))
+ sb.WriteString("<|tool▁call▁end|>")
+ }
+
+ sb.WriteString("<|tool▁calls▁end|><|end▁of▁sentence|>")
+ } else {
+ if isLastUser {
+ sb.WriteString("<|Assistant|>")
+ if enableThinking {
+ sb.WriteString("")
+ } else {
+ sb.WriteString("")
+ }
+ }
+ isLastUser = false
+
+ content := message.Content
+ if isToolContext {
+ sb.WriteString(content + "<|end▁of▁sentence|>")
+ isToolContext = false
+ } else {
+ if strings.Contains(content, "") {
+ parts := strings.SplitN(content, "", 2)
+ if len(parts) > 1 {
+ content = parts[1]
+ }
+ }
+ sb.WriteString(content + "<|end▁of▁sentence|>")
+ }
+ }
+
+ case "tool":
+ isLastUser = false
+ isToolContext = true
+ sb.WriteString("<|tool▁output▁begin|>" + message.Content + "<|tool▁output▁end|>")
+ }
+ }
+
+ if isLastUser && !isToolContext {
+ sb.WriteString("<|Assistant|>")
+ if enableThinking {
+ sb.WriteString("")
+ } else {
+ sb.WriteString("")
+ }
+ }
+
+ return sb.String(), nil
+}
diff --git a/model/renderers/deepseek3_test.go b/model/renderers/deepseek3_test.go
new file mode 100644
index 000000000..f389881c7
--- /dev/null
+++ b/model/renderers/deepseek3_test.go
@@ -0,0 +1,288 @@
+package renderers
+
+import (
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+
+ "github.com/ollama/ollama/api"
+)
+
+func TestDeepSeekRenderer(t *testing.T) {
+ tests := []struct {
+ name string
+ messages []api.Message
+ tools []api.Tool
+ thinkValue *api.ThinkValue
+ expected string
+ }{
+ {
+ name: "basic user message",
+ messages: []api.Message{
+ {Role: "user", Content: "Hello, how are you?"},
+ },
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|><|User|>Hello, how are you?<|Assistant|>`,
+ },
+ {
+ name: "basic with system message",
+ messages: []api.Message{
+ {Role: "system", Content: "You are a helpful assistant."},
+ {Role: "user", Content: "Hello, how are you?"},
+ },
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|>You are a helpful assistant.<|User|>Hello, how are you?<|Assistant|>`,
+ },
+ {
+ name: "multiple system messages",
+ messages: []api.Message{
+ {Role: "system", Content: "First instruction"},
+ {Role: "system", Content: "Second instruction"},
+ {Role: "user", Content: "Hello"},
+ },
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|>First instruction
+
+Second instruction<|User|>Hello<|Assistant|>`,
+ },
+ {
+ name: "thinking enabled",
+ messages: []api.Message{
+ {Role: "user", Content: "Hello, how are you?"},
+ },
+ thinkValue: &api.ThinkValue{Value: true},
+ expected: `<|begin▁of▁sentence|><|User|>Hello, how are you?<|Assistant|>`,
+ },
+ {
+ name: "thinking enabled with system",
+ messages: []api.Message{
+ {Role: "system", Content: "You are a helpful assistant."},
+ {Role: "user", Content: "Hello, how are you?"},
+ },
+ thinkValue: &api.ThinkValue{Value: true},
+ expected: `<|begin▁of▁sentence|>You are a helpful assistant.<|User|>Hello, how are you?<|Assistant|>`,
+ },
+ {
+ name: "conversation with assistant response",
+ messages: []api.Message{
+ {Role: "user", Content: "What is the capital of France?"},
+ {Role: "assistant", Content: "The capital of France is Paris."},
+ {Role: "user", Content: "Fantastic!"},
+ },
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|><|User|>What is the capital of France?<|Assistant|>The capital of France is Paris.<|end▁of▁sentence|><|User|>Fantastic!<|Assistant|>`,
+ },
+ {
+ name: "assistant with tool calls",
+ messages: []api.Message{
+ {Role: "user", Content: "What's the weather?"},
+ {
+ Role: "assistant",
+ ToolCalls: []api.ToolCall{
+ {
+ Function: api.ToolCallFunction{
+ Name: "get_weather",
+ Arguments: api.ToolCallFunctionArguments{
+ "location": "Paris",
+ },
+ },
+ },
+ },
+ },
+ },
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|><|User|>What's the weather?<|Assistant|><|tool▁calls▁begin|><|tool▁call▁begin|>get_weather<|tool▁sep|>{"location":"Paris"}<|tool▁call▁end|><|tool▁calls▁end|><|end▁of▁sentence|>`,
+ },
+ {
+ name: "assistant with content and tool calls",
+ messages: []api.Message{
+ {Role: "user", Content: "What's the weather in Paris?"},
+ {
+ Role: "assistant",
+ Content: "I'll check the weather for you.",
+ ToolCalls: []api.ToolCall{
+ {
+ Function: api.ToolCallFunction{
+ Name: "get_weather",
+ Arguments: api.ToolCallFunctionArguments{
+ "location": "Paris",
+ },
+ },
+ },
+ },
+ },
+ },
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|><|User|>What's the weather in Paris?<|Assistant|>I'll check the weather for you.<|tool▁calls▁begin|><|tool▁call▁begin|>get_weather<|tool▁sep|>{"location":"Paris"}<|tool▁call▁end|><|tool▁calls▁end|><|end▁of▁sentence|>`,
+ },
+ {
+ name: "tool response",
+ messages: []api.Message{
+ {Role: "user", Content: "What's the weather?"},
+ {
+ Role: "assistant",
+ ToolCalls: []api.ToolCall{
+ {
+ Function: api.ToolCallFunction{
+ Name: "get_weather",
+ Arguments: api.ToolCallFunctionArguments{
+ "location": "Paris",
+ },
+ },
+ },
+ },
+ },
+ {Role: "tool", Content: "Temperature: 22°C, Sunny"},
+ },
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|><|User|>What's the weather?<|Assistant|><|tool▁calls▁begin|><|tool▁call▁begin|>get_weather<|tool▁sep|>{"location":"Paris"}<|tool▁call▁end|><|tool▁calls▁end|><|end▁of▁sentence|><|tool▁output▁begin|>Temperature: 22°C, Sunny<|tool▁output▁end|>`,
+ },
+ {
+ name: "multiple tool calls",
+ messages: []api.Message{
+ {Role: "user", Content: "Get weather for Paris and London"},
+ {
+ Role: "assistant",
+ ToolCalls: []api.ToolCall{
+ {
+ Function: api.ToolCallFunction{
+ Name: "get_weather",
+ Arguments: api.ToolCallFunctionArguments{
+ "location": "Paris",
+ },
+ },
+ },
+ {
+ Function: api.ToolCallFunction{
+ Name: "get_weather",
+ Arguments: api.ToolCallFunctionArguments{
+ "location": "London",
+ },
+ },
+ },
+ },
+ },
+ {Role: "tool", Content: "Paris: 22°C, Sunny"},
+ {Role: "tool", Content: "London: 18°C, Cloudy"},
+ },
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|><|User|>Get weather for Paris and London<|Assistant|><|tool▁calls▁begin|><|tool▁call▁begin|>get_weather<|tool▁sep|>{"location":"Paris"}<|tool▁call▁end|><|tool▁call▁begin|>get_weather<|tool▁sep|>{"location":"London"}<|tool▁call▁end|><|tool▁calls▁end|><|end▁of▁sentence|><|tool▁output▁begin|>Paris: 22°C, Sunny<|tool▁output▁end|><|tool▁output▁begin|>London: 18°C, Cloudy<|tool▁output▁end|>`,
+ },
+ {
+ name: "content with tag removal",
+ messages: []api.Message{
+ {Role: "user", Content: "Think about this"},
+ {Role: "assistant", Content: "I'm thinking about this.The answer is 42."},
+ },
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|><|User|>Think about this<|Assistant|>The answer is 42.<|end▁of▁sentence|>`,
+ },
+ {
+ name: "empty system message",
+ messages: []api.Message{
+ {Role: "system", Content: ""},
+ {Role: "user", Content: "Hello"},
+ },
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|><|User|>Hello<|Assistant|>`,
+ },
+ {
+ name: "empty assistant content",
+ messages: []api.Message{
+ {Role: "user", Content: "Hello"},
+ {Role: "assistant", Content: ""},
+ },
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|><|User|>Hello<|Assistant|><|end▁of▁sentence|>`,
+ },
+ {
+ name: "special characters",
+ messages: []api.Message{
+ {Role: "user", Content: "What about <|special|> tokens and \"quotes\"?"},
+ {Role: "assistant", Content: "They're handled normally."},
+ },
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|><|User|>What about <|special|> tokens and "quotes"?<|Assistant|>They're handled normally.<|end▁of▁sentence|>`,
+ },
+ {
+ name: "tool calls with null content",
+ messages: []api.Message{
+ {Role: "user", Content: "Get weather"},
+ {
+ Role: "assistant",
+ ToolCalls: []api.ToolCall{
+ {
+ Function: api.ToolCallFunction{
+ Name: "get_weather",
+ Arguments: api.ToolCallFunctionArguments{
+ "location": "Paris",
+ },
+ },
+ },
+ },
+ },
+ },
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|><|User|>Get weather<|Assistant|><|tool▁calls▁begin|><|tool▁call▁begin|>get_weather<|tool▁sep|>{"location":"Paris"}<|tool▁call▁end|><|tool▁calls▁end|><|end▁of▁sentence|>`,
+ },
+ {
+ name: "assistant after tool context",
+ messages: []api.Message{
+ {Role: "user", Content: "Process data"},
+ {
+ Role: "assistant",
+ ToolCalls: []api.ToolCall{
+ {
+ Function: api.ToolCallFunction{
+ Name: "process",
+ Arguments: api.ToolCallFunctionArguments{
+ "data": "test",
+ },
+ },
+ },
+ },
+ },
+ {Role: "tool", Content: "Success"},
+ {Role: "assistant", Content: "Done"},
+ },
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|><|User|>Process data<|Assistant|><|tool▁calls▁begin|><|tool▁call▁begin|>process<|tool▁sep|>{"data":"test"}<|tool▁call▁end|><|tool▁calls▁end|><|end▁of▁sentence|><|tool▁output▁begin|>Success<|tool▁output▁end|>Done<|end▁of▁sentence|>`,
+ },
+ {
+ name: "no messages",
+ messages: []api.Message{},
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|>`,
+ },
+ {
+ name: "only system messages",
+ messages: []api.Message{
+ {Role: "system", Content: "System instruction"},
+ },
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|>System instruction`,
+ },
+ {
+ name: "multiple think tags in content",
+ messages: []api.Message{
+ {Role: "user", Content: "Complex question"},
+ {Role: "assistant", Content: "First thoughtSecond thoughtFinal answer"},
+ },
+ thinkValue: &api.ThinkValue{Value: false},
+ expected: `<|begin▁of▁sentence|><|User|>Complex question<|Assistant|>Second thoughtFinal answer<|end▁of▁sentence|>`,
+ },
+ }
+
+ renderer := &DeepSeekRenderer{isThinking: true}
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ rendered, err := renderer.Render(tt.messages, tt.tools, tt.thinkValue)
+ if err != nil {
+ t.Fatalf("Render() error = %v", err)
+ }
+ if diff := cmp.Diff(tt.expected, rendered); diff != "" {
+ t.Errorf("Render() mismatch (-want +got):\n%s", diff)
+ }
+ })
+ }
+}
diff --git a/model/renderers/renderer.go b/model/renderers/renderer.go
index e3d797a62..e6fede7a5 100644
--- a/model/renderers/renderer.go
+++ b/model/renderers/renderer.go
@@ -59,6 +59,9 @@ func rendererForName(name string) Renderer {
case "cogito":
renderer := &CogitoRenderer{isThinking: true}
return renderer
+ case "deepseek":
+ renderer := &DeepSeekRenderer{isThinking: true}
+ return renderer
case "olmo3":
renderer := &Olmo3Renderer{UseExtendedSystemMessage: false}
return renderer