package renderers import ( "testing" "github.com/google/go-cmp/cmp" "github.com/ollama/ollama/api" ) func TestQwen3CoderRenderer(t *testing.T) { tests := []struct { name string msgs []api.Message tools []api.Tool expected string }{ { name: "basic", msgs: []api.Message{ {Role: "system", Content: "You are a helpful assistant."}, {Role: "user", Content: "Hello, how are you?"}, }, expected: `<|im_start|>system You are a helpful assistant.<|im_end|> <|im_start|>user Hello, how are you?<|im_end|> <|im_start|>assistant `, }, { name: "with tools and response", msgs: []api.Message{ {Role: "system", Content: "You are a helpful assistant with access to tools."}, {Role: "user", Content: "What is the weather like in San Francisco?"}, { Role: "assistant", Content: "I'll check the weather in San Francisco for you.", ToolCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "get_weather", Arguments: map[string]any{ "unit": "fahrenheit", }, }, }, }, }, {Role: "tool", Content: "{\"location\": \"San Francisco, CA\", \"temperature\": 68, \"condition\": \"partly cloudy\", \"humidity\": 65, \"wind_speed\": 12}", ToolName: "get_weather"}, {Role: "user", Content: "That sounds nice! What about New York?"}, }, tools: []api.Tool{ {Function: api.ToolFunction{ Name: "get_weather", Description: "Get the current weather in a given location", Parameters: api.ToolFunctionParameters{ Required: []string{"unit"}, Properties: map[string]api.ToolProperty{ "unit": {Type: api.PropertyType{"string"}, Enum: []any{"celsius", "fahrenheit"}, Description: "The unit of temperature"}, // TODO(drifkin): add multiple params back once we have predictable // order via some sort of ordered map type (see // ) /* "location": {Type: api.PropertyType{"string"}, Description: "The city and state, e.g. San Francisco, CA"}, */ }, }, }}, }, expected: `<|im_start|>system You are a helpful assistant with access to tools. # Tools You have access to the following functions: get_weather Get the current weather in a given location unit string The unit of temperature ["celsius","fahrenheit"] ["unit"] If you choose to call a function ONLY reply in the following format with NO suffix: value_1 This is the value for the second parameter that can span multiple lines Reminder: - Function calls MUST follow the specified format: an inner block must be nested within XML tags - Required parameters MUST be specified - You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after - If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls <|im_end|> <|im_start|>user What is the weather like in San Francisco?<|im_end|> <|im_start|>assistant I'll check the weather in San Francisco for you. fahrenheit <|im_end|> <|im_start|>user {"location": "San Francisco, CA", "temperature": 68, "condition": "partly cloudy", "humidity": 65, "wind_speed": 12} <|im_end|> <|im_start|>user That sounds nice! What about New York?<|im_end|> <|im_start|>assistant `, }, { name: "parallel tool calls", msgs: []api.Message{ {Role: "system", Content: "You are a helpful assistant with access to tools."}, {Role: "user", Content: "call double(1) and triple(2)"}, {Role: "assistant", Content: "I'll call double(1) and triple(2) for you.", ToolCalls: []api.ToolCall{ {Function: api.ToolCallFunction{Name: "double", Arguments: map[string]any{"number": "1"}}}, {Function: api.ToolCallFunction{Name: "triple", Arguments: map[string]any{"number": "2"}}}, }}, {Role: "tool", Content: "{\"number\": 2}", ToolName: "double"}, {Role: "tool", Content: "{\"number\": 6}", ToolName: "triple"}, }, tools: []api.Tool{ {Function: api.ToolFunction{Name: "double", Description: "Double a number", Parameters: api.ToolFunctionParameters{Properties: map[string]api.ToolProperty{ "number": {Type: api.PropertyType{"string"}, Description: "The number to double"}, }}}}, {Function: api.ToolFunction{Name: "triple", Description: "Triple a number", Parameters: api.ToolFunctionParameters{Properties: map[string]api.ToolProperty{ "number": {Type: api.PropertyType{"string"}, Description: "The number to triple"}, }}}}, }, expected: `<|im_start|>system You are a helpful assistant with access to tools. # Tools You have access to the following functions: double Double a number number string The number to double triple Triple a number number string The number to triple If you choose to call a function ONLY reply in the following format with NO suffix: value_1 This is the value for the second parameter that can span multiple lines Reminder: - Function calls MUST follow the specified format: an inner block must be nested within XML tags - Required parameters MUST be specified - You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after - If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls <|im_end|> <|im_start|>user call double(1) and triple(2)<|im_end|> <|im_start|>assistant I'll call double(1) and triple(2) for you. 1 2 <|im_end|> <|im_start|>user {"number": 2} {"number": 6} <|im_end|> <|im_start|>assistant `, }, { name: "prefill", msgs: []api.Message{ {Role: "system", Content: "You are a helpful assistant."}, {Role: "user", Content: "Tell me something interesting."}, {Role: "assistant", Content: "I'll tell you something interesting about cats"}, }, expected: `<|im_start|>system You are a helpful assistant.<|im_end|> <|im_start|>user Tell me something interesting.<|im_end|> <|im_start|>assistant I'll tell you something interesting about cats`, }, { name: "complex tool call arguments should remain json encoded", msgs: []api.Message{ {Role: "user", Content: "call tool"}, {Role: "assistant", ToolCalls: []api.ToolCall{ {Function: api.ToolCallFunction{ Name: "echo", Arguments: map[string]any{ "payload": map[string]any{"foo": "bar"}, }, }}, }}, {Role: "tool", Content: "{\"payload\": {\"foo\": \"bar\"}}", ToolName: "echo"}, }, expected: `<|im_start|>user call tool<|im_end|> <|im_start|>assistant {"foo":"bar"} <|im_end|> <|im_start|>user {"payload": {"foo": "bar"}} <|im_end|> <|im_start|>assistant `, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rendered, err := Qwen3CoderRenderer(tt.msgs, tt.tools, nil) if err != nil { t.Fatal(err) } if diff := cmp.Diff(rendered, tt.expected); diff != "" { t.Errorf("mismatch (-got +want):\n%s", diff) } }) } } func TestFormatToolCallArgument(t *testing.T) { tests := []struct { name string arg any expected string }{ { name: "string", arg: "foo", // notice no quotes around the string expected: "foo", }, { name: "map", arg: map[string]any{"foo": "bar"}, expected: "{\"foo\":\"bar\"}", }, { name: "number", arg: 1, expected: "1", }, { name: "boolean", arg: true, expected: "true", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := formatToolCallArgument(tt.arg) if got != tt.expected { t.Errorf("formatToolCallArgument(%v) = %v, want %v", tt.arg, got, tt.expected) } }) } }