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|>`, }, { name: "thinking enabled after tool call - should render thinking", messages: []api.Message{ {Role: "user", Content: "What's the weather in Paris?"}, { Role: "assistant", ToolCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "get_weather", Arguments: api.ToolCallFunctionArguments{ "location": "Paris", }, }, }, }, }, {Role: "tool", Content: "Temperature: 22°C, Sunny"}, {Role: "assistant", Content: "Based on the weather data, it's sunny in Paris."}, {Role: "user", Content: "Now tell me about London weather too."}, }, thinkValue: &api.ThinkValue{Value: true}, expected: `<|begin▁of▁sentence|><|User|>What's the weather in Paris?<|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|>Based on the weather data, it's sunny in Paris.<|end▁of▁sentence|><|User|>Now tell me about London weather too.<|Assistant|>`, }, { name: "thinking disabled after tool call - should not render thinking", messages: []api.Message{ {Role: "user", Content: "What's the weather in Paris?"}, { Role: "assistant", ToolCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "get_weather", Arguments: api.ToolCallFunctionArguments{ "location": "Paris", }, }, }, }, }, {Role: "tool", Content: "Temperature: 22°C, Sunny"}, {Role: "assistant", Content: "Based on the weather data, it's sunny in Paris."}, {Role: "user", Content: "Now tell me about London weather too."}, }, thinkValue: &api.ThinkValue{Value: false}, expected: `<|begin▁of▁sentence|><|User|>What's the weather in Paris?<|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|>Based on the weather data, it's sunny in Paris.<|end▁of▁sentence|><|User|>Now tell me about London weather too.<|Assistant|>`, }, { name: "thinking enabled but messages without thinking content", messages: []api.Message{ {Role: "user", Content: "First question about cats"}, {Role: "assistant", Content: "Cats are wonderful pets."}, {Role: "user", Content: "What about dogs?"}, {Role: "assistant", Content: "Dogs are loyal companions."}, {Role: "user", Content: "Final question about birds"}, }, thinkValue: &api.ThinkValue{Value: true}, expected: `<|begin▁of▁sentence|><|User|>First question about cats<|Assistant|>Cats are wonderful pets.<|end▁of▁sentence|><|User|>What about dogs?<|Assistant|>Dogs are loyal companions.<|end▁of▁sentence|><|User|>Final question about birds<|Assistant|>`, }, { name: "thinking disabled for all assistant responses", messages: []api.Message{ {Role: "user", Content: "First question about cats"}, {Role: "assistant", Content: "Cats are wonderful pets."}, {Role: "user", Content: "What about dogs?"}, {Role: "assistant", Content: "Dogs are loyal companions."}, {Role: "user", Content: "Final question about birds"}, }, thinkValue: &api.ThinkValue{Value: false}, expected: `<|begin▁of▁sentence|><|User|>First question about cats<|Assistant|>Cats are wonderful pets.<|end▁of▁sentence|><|User|>What about dogs?<|Assistant|>Dogs are loyal companions.<|end▁of▁sentence|><|User|>Final question about birds<|Assistant|>`, }, { name: "complex conversation with tool calls and thinking enabled", messages: []api.Message{ {Role: "user", Content: "Tell me about the weather"}, {Role: "assistant", Content: "I'll check the weather for you."}, {Role: "user", Content: "Actually, get Paris weather specifically"}, { Role: "assistant", ToolCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "get_weather", Arguments: api.ToolCallFunctionArguments{ "location": "Paris", }, }, }, }, }, {Role: "tool", Content: "Paris: 22°C, Sunny"}, {Role: "assistant", Content: "The weather in Paris is great!"}, {Role: "user", Content: "What about the forecast for tomorrow?"}, }, thinkValue: &api.ThinkValue{Value: true}, expected: `<|begin▁of▁sentence|><|User|>Tell me about the weather<|Assistant|>I'll check the weather for you.<|end▁of▁sentence|><|User|>Actually, get Paris weather specifically<|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|>Paris: 22°C, Sunny<|tool▁output▁end|>The weather in Paris is great!<|end▁of▁sentence|><|User|>What about the forecast for tomorrow?<|Assistant|>`, }, { name: "tool call without subsequent user message - no thinking", messages: []api.Message{ {Role: "user", Content: "Get the weather"}, { Role: "assistant", ToolCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "get_weather", Arguments: api.ToolCallFunctionArguments{ "location": "Paris", }, }, }, }, }, {Role: "tool", Content: "22°C, Sunny"}, }, thinkValue: &api.ThinkValue{Value: true}, expected: `<|begin▁of▁sentence|><|User|>Get 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|>22°C, Sunny<|tool▁output▁end|>`, }, { name: "messages with thinking content, no thinking in render", messages: []api.Message{ {Role: "user", Content: "Solve this math problem: 15 * 23"}, { Role: "assistant", Content: "The answer is 345.", Thinking: "Let me calculate 15 * 23. I can break this down: 15 * 20 = 300, and 15 * 3 = 45, so 300 + 45 = 345.", }, {Role: "user", Content: "What about 12 * 34?"}, }, thinkValue: &api.ThinkValue{Value: false}, expected: `<|begin▁of▁sentence|><|User|>Solve this math problem: 15 * 23<|Assistant|>The answer is 345.<|end▁of▁sentence|><|User|>What about 12 * 34?<|Assistant|>`, }, { name: "conversation with mix of thinking and no thinking", messages: []api.Message{ {Role: "user", Content: "Explain quantum physics"}, { Role: "assistant", Content: "Quantum physics is the study of matter and energy at the smallest scales.", Thinking: "This is a complex topic. I should start with basic concepts and avoid overwhelming technical details.", }, {Role: "user", Content: "What about photons?"}, { Role: "assistant", Content: "Photons are particles of light with no mass.", }, {Role: "user", Content: "How do they interact with matter?"}, }, thinkValue: &api.ThinkValue{Value: true}, expected: `<|begin▁of▁sentence|><|User|>Explain quantum physics<|Assistant|>Quantum physics is the study of matter and energy at the smallest scales.<|end▁of▁sentence|><|User|>What about photons?<|Assistant|>Photons are particles of light with no mass.<|end▁of▁sentence|><|User|>How do they interact with matter?<|Assistant|>`, }, { name: "tool call with thinking content in response", messages: []api.Message{ {Role: "user", Content: "What's the weather in Tokyo and New York?"}, { Role: "assistant", Content: "I'll check the weather for both cities.", Thinking: "I need to call the weather API for two different cities. Let me make parallel calls.", ToolCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "get_weather", Arguments: api.ToolCallFunctionArguments{ "location": "Tokyo", }, }, }, { Function: api.ToolCallFunction{ Name: "get_weather", Arguments: api.ToolCallFunctionArguments{ "location": "New York", }, }, }, }, }, {Role: "tool", Content: "Tokyo: 18°C, Cloudy"}, {Role: "tool", Content: "New York: 22°C, Sunny"}, { Role: "assistant", Content: "Based on the weather data: Tokyo is cloudy at 18°C, while New York is sunny at 22°C.", Thinking: "The data shows a nice contrast between the two cities. Tokyo is cooler and overcast while NYC has better weather.", }, }, thinkValue: &api.ThinkValue{Value: true}, expected: `<|begin▁of▁sentence|><|User|>What's the weather in Tokyo and New York?<|Assistant|>I'll check the weather for both cities.<|tool▁calls▁begin|><|tool▁call▁begin|>get_weather<|tool▁sep|>{"location":"Tokyo"}<|tool▁call▁end|><|tool▁call▁begin|>get_weather<|tool▁sep|>{"location":"New York"}<|tool▁call▁end|><|tool▁calls▁end|><|end▁of▁sentence|><|tool▁output▁begin|>Tokyo: 18°C, Cloudy<|tool▁output▁end|><|tool▁output▁begin|>New York: 22°C, Sunny<|tool▁output▁end|>Based on the weather data: Tokyo is cloudy at 18°C, while New York is sunny at 22°C.<|end▁of▁sentence|>`, }, { name: "empty thinking field", messages: []api.Message{ {Role: "user", Content: "Simple question"}, { Role: "assistant", Content: "Simple answer.", Thinking: "", // Empty thinking content }, }, thinkValue: &api.ThinkValue{Value: true}, expected: `<|begin▁of▁sentence|><|User|>Simple question<|Assistant|>Simple answer.<|end▁of▁sentence|>`, }, } renderer := &DeepSeek3Renderer{IsThinking: true, Variant: Deepseek31} 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) } }) } }