olmo3_test.go 10.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package renderers

import (
	"testing"

	"github.com/google/go-cmp/cmp"

	"github.com/ollama/ollama/api"
)

func TestOlmo3Renderer(t *testing.T) {
	tests := []struct {
		name     string
		msgs     []api.Message
		tools    []api.Tool
		expected string
	}{
		{
			name: "basic without system - adds default system",
			msgs: []api.Message{
				{Role: "user", Content: "Hello!"},
			},
			expected: "<|im_start|>system\n" +
				"You are a helpful function-calling AI assistant. You do not currently have access to any functions. <functions></functions><|im_end|>\n" +
				"<|im_start|>user\n" +
				"Hello!<|im_end|>\n" +
27
				"<|im_start|>assistant\n",
28
29
30
31
32
33
34
35
36
37
38
		},
		{
			name: "with system message no tools",
			msgs: []api.Message{
				{Role: "system", Content: "You are a helpful assistant."},
				{Role: "user", Content: "Hello!"},
			},
			expected: "<|im_start|>system\n" +
				"You are a helpful assistant.<|im_end|>\n" +
				"<|im_start|>user\n" +
				"Hello!<|im_end|>\n" +
39
				"<|im_start|>assistant\n",
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
		},
		{
			name: "with system message and tools",
			msgs: []api.Message{
				{Role: "system", Content: "You are a helpful assistant."},
				{Role: "user", Content: "What is the 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"},
56
							Properties: testPropsMap(map[string]api.ToolProperty{
57
								"location": {Type: api.PropertyType{"string"}, Description: "The city"},
58
							}),
59
60
61
62
63
64
65
66
						},
					},
				},
			},
			expected: "<|im_start|>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><|im_end|>` + "\n" +
				"<|im_start|>user\n" +
				"What is the weather?<|im_end|>\n" +
67
				"<|im_start|>assistant\n",
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
		},
		{
			name: "default system with tools - includes function instruction",
			msgs: []api.Message{
				{Role: "user", Content: "What is the 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"},
83
							Properties: testPropsMap(map[string]api.ToolProperty{
84
								"location": {Type: api.PropertyType{"string"}, Description: "The city"},
85
							}),
86
87
88
89
90
91
92
93
94
95
						},
					},
				},
			},
			expected: "<|im_start|>system\n" +
				"You are a helpful function-calling AI assistant. " +
				"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." +
				`<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><|im_end|>` + "\n" +
				"<|im_start|>user\n" +
				"What is the weather?<|im_end|>\n" +
96
				"<|im_start|>assistant\n",
97
98
99
100
101
102
103
104
105
106
107
108
109
110
		},
		{
			name: "assistant with tool calls - function call syntax",
			msgs: []api.Message{
				{Role: "system", Content: "You are a helpful assistant."},
				{Role: "user", Content: "What is the weather in SF?"},
				{
					Role:    "assistant",
					Content: "Let me check the weather.",
					ToolCalls: []api.ToolCall{
						{
							ID: "call_1",
							Function: api.ToolCallFunction{
								Name: "get_weather",
111
								Arguments: testArgs(map[string]any{
112
									"location": "San Francisco",
113
								}),
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
							},
						},
					},
				},
				{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"},
129
							Properties: testPropsMap(map[string]api.ToolProperty{
130
								"location": {Type: api.PropertyType{"string"}, Description: "The city"},
131
							}),
132
133
134
135
136
137
138
139
140
141
142
143
						},
					},
				},
			},
			expected: "<|im_start|>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><|im_end|>` + "\n" +
				"<|im_start|>user\n" +
				"What is the weather in SF?<|im_end|>\n" +
				"<|im_start|>assistant\n" +
				`Let me check the weather.<function_calls>get_weather(location="San Francisco")</function_calls><|im_end|>` + "\n" +
				"<|im_start|>environment\n" +
				`{"temperature": 68}<|im_end|>` + "\n" +
144
				"<|im_start|>assistant\n",
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
		},
		{
			name: "multi-turn conversation",
			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: "<|im_start|>system\n" +
				"You are a helpful assistant.<|im_end|>\n" +
				"<|im_start|>user\n" +
				"Hello<|im_end|>\n" +
				"<|im_start|>assistant\n" +
				"Hi there!<|im_end|>\n" +
				"<|im_start|>user\n" +
				"How are you?<|im_end|>\n" +
162
				"<|im_start|>assistant\n",
163
164
165
166
167
168
169
170
171
172
173
174
		},
		{
			name: "parallel tool calls - newline separated",
			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",
175
								Arguments: testArgs(map[string]any{"location": "San Francisco"}),
176
177
178
179
180
181
							},
						},
						{
							ID: "call_2",
							Function: api.ToolCallFunction{
								Name:      "get_weather",
182
								Arguments: testArgs(map[string]any{"location": "New York"}),
183
184
185
186
187
188
189
190
191
192
193
194
195
196
							},
						},
					},
				},
				{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",
197
							Properties: testPropsMap(map[string]api.ToolProperty{
198
								"location": {Type: api.PropertyType{"string"}},
199
							}),
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
						},
					},
				},
			},
			expected: "<|im_start|>system\n" +
				"You are a helpful function-calling AI assistant. " +
				"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." +
				`<functions>[{"type": "function", "function": {"name": "get_weather", "parameters": {"type": "object", "properties": {"location": {"type": "string"}}}}}]</functions><|im_end|>` + "\n" +
				"<|im_start|>user\n" +
				"Get weather in SF and NYC<|im_end|>\n" +
				"<|im_start|>assistant\n" +
				`<function_calls>get_weather(location="San Francisco")` + "\n" +
				`get_weather(location="New York")</function_calls><|im_end|>` + "\n" +
				"<|im_start|>environment\n" +
				`{"temperature": 68}<|im_end|>` + "\n" +
				"<|im_start|>environment\n" +
				`{"temperature": 55}<|im_end|>` + "\n" +
217
				"<|im_start|>assistant\n",
218
219
220
221
222
223
224
225
226
227
228
229
		},
		{
			name: "tool call with multiple arguments",
			msgs: []api.Message{
				{Role: "user", Content: "Book a flight"},
				{
					Role: "assistant",
					ToolCalls: []api.ToolCall{
						{
							ID: "call_1",
							Function: api.ToolCallFunction{
								Name: "book_flight",
Devon Rifkin's avatar
Devon Rifkin committed
230
231
232
								Arguments: testArgsOrdered([]orderedArg{
									{"from", "SFO"},
									{"to", "NYC"},
233
								}),
234
235
236
237
238
239
240
241
242
243
244
245
							},
						},
					},
				},
			},
			tools: []api.Tool{
				{
					Type: "function",
					Function: api.ToolFunction{
						Name: "book_flight",
						Parameters: api.ToolFunctionParameters{
							Type: "object",
Devon Rifkin's avatar
Devon Rifkin committed
246
247
248
							Properties: testPropsOrdered([]orderedProp{
								{"from", api.ToolProperty{Type: api.PropertyType{"string"}}},
								{"to", api.ToolProperty{Type: api.PropertyType{"string"}}},
249
							}),
250
251
252
253
254
255
256
257
258
259
260
261
						},
					},
				},
			},
			expected: "<|im_start|>system\n" +
				"You are a helpful function-calling AI assistant. " +
				"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." +
				`<functions>[{"type": "function", "function": {"name": "book_flight", "parameters": {"type": "object", "properties": {"from": {"type": "string"}, "to": {"type": "string"}}}}}]</functions><|im_end|>` + "\n" +
				"<|im_start|>user\n" +
				"Book a flight<|im_end|>\n" +
				"<|im_start|>assistant\n" +
				`<function_calls>book_flight(from="SFO", to="NYC")</function_calls><|im_end|>` + "\n" +
262
				"<|im_start|>assistant\n",
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
		},
		{
			name: "assistant prefill - no generation prompt",
			msgs: []api.Message{
				{Role: "user", Content: "Hello"},
				{Role: "assistant", Content: "Hi there!"},
			},
			expected: "<|im_start|>system\n" +
				"You are a helpful function-calling AI assistant. You do not currently have access to any functions. <functions></functions><|im_end|>\n" +
				"<|im_start|>user\n" +
				"Hello<|im_end|>\n" +
				"<|im_start|>assistant\n" +
				"Hi there!",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			rendered, err := (&Olmo3Renderer{}).Render(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)
			}
		})
	}
}