mirror of https://github.com/ollama/ollama.git
371 lines
9.8 KiB
Go
371 lines
9.8 KiB
Go
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
|
|
// <https://github.com/ollama/ollama/issues/12244>)
|
|
/*
|
|
"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:
|
|
|
|
<tools>
|
|
<function>
|
|
<name>get_weather</name>
|
|
<description>Get the current weather in a given location</description>
|
|
<parameters>
|
|
<parameter>
|
|
<name>unit</name>
|
|
<type>string</type>
|
|
<description>The unit of temperature</description>
|
|
<enum>["celsius","fahrenheit"]</enum>
|
|
</parameter>
|
|
<required>["unit"]</required>
|
|
</parameters>
|
|
</function>
|
|
</tools>
|
|
|
|
If you choose to call a function ONLY reply in the following format with NO suffix:
|
|
|
|
<tool_call>
|
|
<function=example_function_name>
|
|
<parameter=example_parameter_1>
|
|
value_1
|
|
</parameter>
|
|
<parameter=example_parameter_2>
|
|
This is the value for the second parameter
|
|
that can span
|
|
multiple lines
|
|
</parameter>
|
|
</function>
|
|
</tool_call>
|
|
|
|
<IMPORTANT>
|
|
Reminder:
|
|
- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> 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
|
|
</IMPORTANT><|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.
|
|
|
|
<tool_call>
|
|
<function=get_weather>
|
|
<parameter=unit>
|
|
fahrenheit
|
|
</parameter>
|
|
</function>
|
|
</tool_call><|im_end|>
|
|
<|im_start|>user
|
|
<tool_response>
|
|
{"location": "San Francisco, CA", "temperature": 68, "condition": "partly cloudy", "humidity": 65, "wind_speed": 12}
|
|
</tool_response>
|
|
<|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:
|
|
|
|
<tools>
|
|
<function>
|
|
<name>double</name>
|
|
<description>Double a number</description>
|
|
<parameters>
|
|
<parameter>
|
|
<name>number</name>
|
|
<type>string</type>
|
|
<description>The number to double</description>
|
|
</parameter>
|
|
</parameters>
|
|
</function>
|
|
<function>
|
|
<name>triple</name>
|
|
<description>Triple a number</description>
|
|
<parameters>
|
|
<parameter>
|
|
<name>number</name>
|
|
<type>string</type>
|
|
<description>The number to triple</description>
|
|
</parameter>
|
|
</parameters>
|
|
</function>
|
|
</tools>
|
|
|
|
If you choose to call a function ONLY reply in the following format with NO suffix:
|
|
|
|
<tool_call>
|
|
<function=example_function_name>
|
|
<parameter=example_parameter_1>
|
|
value_1
|
|
</parameter>
|
|
<parameter=example_parameter_2>
|
|
This is the value for the second parameter
|
|
that can span
|
|
multiple lines
|
|
</parameter>
|
|
</function>
|
|
</tool_call>
|
|
|
|
<IMPORTANT>
|
|
Reminder:
|
|
- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> 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
|
|
</IMPORTANT><|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.
|
|
|
|
<tool_call>
|
|
<function=double>
|
|
<parameter=number>
|
|
1
|
|
</parameter>
|
|
</function>
|
|
</tool_call>
|
|
<tool_call>
|
|
<function=triple>
|
|
<parameter=number>
|
|
2
|
|
</parameter>
|
|
</function>
|
|
</tool_call><|im_end|>
|
|
<|im_start|>user
|
|
<tool_response>
|
|
{"number": 2}
|
|
</tool_response>
|
|
<tool_response>
|
|
{"number": 6}
|
|
</tool_response>
|
|
<|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
|
|
|
|
<tool_call>
|
|
<function=echo>
|
|
<parameter=payload>
|
|
{"foo":"bar"}
|
|
</parameter>
|
|
</function>
|
|
</tool_call><|im_end|>
|
|
<|im_start|>user
|
|
<tool_response>
|
|
{"payload": {"foo": "bar"}}
|
|
</tool_response>
|
|
<|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)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestQwen3ToolDefinitionTypes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
propertyType api.PropertyType
|
|
expected string
|
|
}{
|
|
{
|
|
name: "simple",
|
|
propertyType: api.PropertyType{"string"},
|
|
expected: "string",
|
|
},
|
|
{
|
|
name: "multiple",
|
|
propertyType: api.PropertyType{"string", "number"},
|
|
expected: "[\"string\",\"number\"]",
|
|
},
|
|
{
|
|
name: "empty",
|
|
propertyType: api.PropertyType{},
|
|
expected: "[]",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := formatToolDefinitionType(tt.propertyType)
|
|
if got != tt.expected {
|
|
t.Errorf("formatToolDefinitionType() = %v, want %v", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|