package parsers import ( "testing" "github.com/google/go-cmp/cmp" "github.com/ollama/ollama/api" ) func TestOlmo3Parser(t *testing.T) { tests := []struct { name string input string expectedContent string expectedThinking string expectedCalls []api.ToolCall }{ { name: "simple content", input: "Hello, how can I help you?", expectedContent: "Hello, how can I help you?", }, { name: "simple tool call", input: `get_weather(location="San Francisco")`, expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "get_weather", Arguments: map[string]any{"location": "San Francisco"}, }, }, }, }, { name: "content then tool call", input: `Let me check the weather.get_weather(location="NYC")`, expectedContent: "Let me check the weather.", expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "get_weather", Arguments: map[string]any{"location": "NYC"}, }, }, }, }, { name: "tool call with multiple arguments", input: `book_flight(from="SFO", to="NYC", date="2024-01-15")`, expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "book_flight", Arguments: map[string]any{ "from": "SFO", "to": "NYC", "date": "2024-01-15", }, }, }, }, }, { name: "multiple tool calls", input: `get_weather(location="San Francisco") get_weather(location="New York")`, expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "get_weather", Arguments: map[string]any{"location": "San Francisco"}, }, }, { Function: api.ToolCallFunction{ Name: "get_weather", Arguments: map[string]any{"location": "New York"}, }, }, }, }, { name: "tool call with numeric argument", input: `set_temperature(value=72)`, expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "set_temperature", Arguments: map[string]any{"value": int64(72)}, }, }, }, }, { name: "tool call with float argument", input: `set_price(amount=19.99)`, expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "set_price", Arguments: map[string]any{"amount": 19.99}, }, }, }, }, { name: "tool call with boolean argument", input: `toggle_setting(enabled=true)`, expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "toggle_setting", Arguments: map[string]any{"enabled": true}, }, }, }, }, { name: "tool call with null argument", input: `clear_value(field=null)`, expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "clear_value", Arguments: map[string]any{"field": nil}, }, }, }, }, { name: "tool call with array argument", input: `process_items(items=["apple", "banana", "cherry"])`, expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "process_items", Arguments: map[string]any{"items": []any{"apple", "banana", "cherry"}}, }, }, }, }, { name: "tool call with dict argument", input: `update_config(settings={"theme": "dark", "fontSize": 14})`, expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "update_config", Arguments: map[string]any{ "settings": map[string]any{ "theme": "dark", "fontSize": int64(14), }, }, }, }, }, }, { name: "tool call with nested dict", input: `create_request(data={"user": {"name": "John", "age": 30}, "active": true})`, expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "create_request", Arguments: map[string]any{ "data": map[string]any{ "user": map[string]any{ "name": "John", "age": int64(30), }, "active": true, }, }, }, }, }, }, { name: "tool call with no arguments", input: `get_current_time()`, expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "get_current_time", Arguments: map[string]any{}, }, }, }, }, { name: "tool call with single quotes", input: `search(query='hello world')`, expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "search", Arguments: map[string]any{"query": "hello world"}, }, }, }, }, { name: "tool call with escaped quotes", input: `search(query="say \"hello\"")`, expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "search", Arguments: map[string]any{"query": `say "hello"`}, }, }, }, }, { name: "tool call with mixed argument types", input: `create_user(name="John", age=30, active=true)`, expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "create_user", Arguments: map[string]any{ "name": "John", "age": int64(30), "active": true, }, }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := &Olmo3Parser{} p.Init(nil, nil, nil) content, thinking, calls, err := p.Add(tt.input, false) if err != nil { t.Fatalf("unexpected error: %v", err) } // Drain remaining content finalContent, finalThinking, finalCalls, err := p.Add("", true) if err != nil { t.Fatalf("unexpected error on done: %v", err) } content += finalContent thinking += finalThinking calls = append(calls, finalCalls...) if diff := cmp.Diff(content, tt.expectedContent); diff != "" { t.Errorf("content mismatch (-got +want):\n%s", diff) } if diff := cmp.Diff(thinking, tt.expectedThinking); diff != "" { t.Errorf("thinking mismatch (-got +want):\n%s", diff) } if diff := cmp.Diff(calls, tt.expectedCalls); diff != "" { t.Errorf("calls mismatch (-got +want):\n%s", diff) } }) } } func TestOlmo3Parser_Streaming(t *testing.T) { tests := []struct { name string chunks []string expectedContent string expectedCalls []api.ToolCall }{ { name: "streaming content", chunks: []string{"Hello, ", "how ", "can I help?"}, expectedContent: "Hello, how can I help?", }, { name: "streaming tool call", chunks: []string{"get_weather", "(location=\"SF\")", ""}, expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "get_weather", Arguments: map[string]any{"location": "SF"}, }, }, }, }, { name: "streaming content then tool call", chunks: []string{"Let me check.", "", "get_weather(location=\"NYC\")", ""}, expectedContent: "Let me check.", expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "get_weather", Arguments: map[string]any{"location": "NYC"}, }, }, }, }, { name: "tool call tag split across chunks", chunks: []string{"test()"}, expectedCalls: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "test", Arguments: map[string]any{}, }, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := &Olmo3Parser{} p.Init(nil, nil, nil) var allContent string var allCalls []api.ToolCall for _, chunk := range tt.chunks { content, _, calls, err := p.Add(chunk, false) if err != nil { t.Fatalf("unexpected error: %v", err) } allContent += content allCalls = append(allCalls, calls...) } // Drain content, _, calls, err := p.Add("", true) if err != nil { t.Fatalf("unexpected error on done: %v", err) } allContent += content allCalls = append(allCalls, calls...) if diff := cmp.Diff(allContent, tt.expectedContent); diff != "" { t.Errorf("content mismatch (-got +want):\n%s", diff) } if diff := cmp.Diff(allCalls, tt.expectedCalls); diff != "" { t.Errorf("calls mismatch (-got +want):\n%s", diff) } }) } } func TestOlmo3Parser_HasToolSupport(t *testing.T) { p := &Olmo3Parser{} if !p.HasToolSupport() { t.Error("expected HasToolSupport to return true") } } func TestOlmo3Parser_HasThinkingSupport(t *testing.T) { p := &Olmo3Parser{} if p.HasThinkingSupport() { t.Error("expected HasThinkingSupport to return false") } } func TestParseOlmo3FunctionCalls(t *testing.T) { tests := []struct { name string input string expected []api.ToolCall wantErr bool }{ { name: "simple call", input: `get_weather(location="SF")`, expected: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "get_weather", Arguments: map[string]any{"location": "SF"}, }, }, }, }, { name: "multiple args", input: `send_email(to="user@example.com", subject="Hello", body="Test message")`, expected: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "send_email", Arguments: map[string]any{ "to": "user@example.com", "subject": "Hello", "body": "Test message", }, }, }, }, }, { name: "multiple calls with newlines", input: `get_weather(location="SF") get_time(timezone="PST")`, expected: []api.ToolCall{ { Function: api.ToolCallFunction{ Name: "get_weather", Arguments: map[string]any{"location": "SF"}, }, }, { Function: api.ToolCallFunction{ Name: "get_time", Arguments: map[string]any{"timezone": "PST"}, }, }, }, }, { name: "empty input", input: "", expected: nil, }, { name: "whitespace only", input: " \n ", expected: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { calls, err := parseOlmo3FunctionCalls(tt.input) if (err != nil) != tt.wantErr { t.Errorf("parseOlmo3FunctionCalls() error = %v, wantErr %v", err, tt.wantErr) return } if diff := cmp.Diff(calls, tt.expected); diff != "" { t.Errorf("calls mismatch (-got +want):\n%s", diff) } }) } } func TestParseOlmo3Value(t *testing.T) { tests := []struct { name string input string expected any }{ {"string double quotes", `"hello"`, "hello"}, {"string single quotes", `'hello'`, "hello"}, {"integer", "42", int64(42)}, {"negative integer", "-10", int64(-10)}, {"float", "3.14", 3.14}, {"boolean true", "true", true}, {"boolean True", "True", true}, {"boolean false", "false", false}, {"null", "null", nil}, {"None", "None", nil}, {"empty array", "[]", []any{}}, {"array with strings", `["a", "b"]`, []any{"a", "b"}}, {"array with numbers", "[1, 2, 3]", []any{int64(1), int64(2), int64(3)}}, {"empty object", "{}", map[string]any{}}, {"simple object", `{"name": "John"}`, map[string]any{"name": "John"}}, {"object with number", `{"age": 30}`, map[string]any{"age": int64(30)}}, {"object with multiple keys", `{"a": 1, "b": 2}`, map[string]any{"a": int64(1), "b": int64(2)}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := parseOlmo3Value(tt.input) if err != nil { t.Fatalf("unexpected error: %v", err) } if diff := cmp.Diff(result, tt.expected); diff != "" { t.Errorf("value mismatch (-got +want):\n%s", diff) } }) } }