Repository: ollama/ollama
Pull request: types: add any type and validation for ToolFunction enum

Closes: https://github.com/ollama/ollama/issues/10164

Given the spec: https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-6.1.2

We should allow for any enum type as long as they are consistent and unique

Changed files:
filename: api/types.go
patch: @@ -217,7 +217,7 @@ type ToolFunction struct {
 		Properties map[string]struct {
 			Type        PropertyType `json:"type"`
 			Description string       `json:"description"`
-			Enum        []string     `json:"enum,omitempty"`
+			Enum        []any        `json:"enum,omitempty"`
 		} `json:"properties"`
 	} `json:"parameters"`
 }

filename: api/types_test.go
patch: @@ -232,6 +232,67 @@ func TestMessage_UnmarshalJSON(t *testing.T) {
 	}
 }
 
+func TestToolFunction_UnmarshalJSON(t *testing.T) {
+	tests := []struct {
+		name    string
+		input   string
+		wantErr string
+	}{
+		{
+			name: "valid enum with same types",
+			input: `{
+				"name": "test",
+				"description": "test function",
+				"parameters": {
+					"type": "object",
+					"required": ["test"],
+					"properties": {
+						"test": {
+							"type": "string",
+							"description": "test prop",
+							"enum": ["a", "b", "c"]
+						}
+					}
+				}
+			}`,
+			wantErr: "",
+		},
+		{
+			name: "empty enum array",
+			input: `{
+				"name": "test",
+				"description": "test function",
+				"parameters": {
+					"type": "object",
+					"required": ["test"],
+					"properties": {
+						"test": {
+							"type": "string",
+							"description": "test prop",
+							"enum": []
+						}
+					}
+				}
+			}`,
+			wantErr: "",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var tf ToolFunction
+			err := json.Unmarshal([]byte(tt.input), &tf)
+
+			if tt.wantErr != "" {
+				require.Error(t, err)
+				assert.Contains(t, err.Error(), tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
 func TestPropertyType_UnmarshalJSON(t *testing.T) {
 	tests := []struct {
 		name     string

filename: openai/openai_test.go
patch: @@ -285,23 +285,23 @@ func TestChatMiddleware(t *testing.T) {
 								Properties map[string]struct {
 									Type        api.PropertyType `json:"type"`
 									Description string           `json:"description"`
-									Enum        []string         `json:"enum,omitempty"`
+									Enum        []any            `json:"enum,omitempty"`
 								} `json:"properties"`
 							}{
 								Type:     "object",
 								Required: []string{"location"},
 								Properties: map[string]struct {
 									Type        api.PropertyType `json:"type"`
 									Description string           `json:"description"`
-									Enum        []string         `json:"enum,omitempty"`
+									Enum        []any            `json:"enum,omitempty"`
 								}{
 									"location": {
 										Type:        api.PropertyType{"string"},
 										Description: "The city and state",
 									},
 									"unit": {
 										Type: api.PropertyType{"string"},
-										Enum: []string{"celsius", "fahrenheit"},
+										Enum: []any{"celsius", "fahrenheit"},
 									},
 								},
 							},

filename: server/routes_generate_test.go
patch: @@ -374,23 +374,23 @@ func TestGenerateChat(t *testing.T) {
 						Properties map[string]struct {
 							Type        api.PropertyType `json:"type"`
 							Description string           `json:"description"`
-							Enum        []string         `json:"enum,omitempty"`
+							Enum        []any            `json:"enum,omitempty"`
 						} `json:"properties"`
 					}{
 						Type:     "object",
 						Required: []string{"location"},
 						Properties: map[string]struct {
 							Type        api.PropertyType `json:"type"`
 							Description string           `json:"description"`
-							Enum        []string         `json:"enum,omitempty"`
+							Enum        []any            `json:"enum,omitempty"`
 						}{
 							"location": {
 								Type:        api.PropertyType{"string"},
 								Description: "The city and state",
 							},
 							"unit": {
 								Type: api.PropertyType{"string"},
-								Enum: []string{"celsius", "fahrenheit"},
+								Enum: []any{"celsius", "fahrenheit"},
 							},
 						},
 					},
@@ -471,23 +471,23 @@ func TestGenerateChat(t *testing.T) {
 						Properties map[string]struct {
 							Type        api.PropertyType `json:"type"`
 							Description string           `json:"description"`
-							Enum        []string         `json:"enum,omitempty"`
+							Enum        []any            `json:"enum,omitempty"`
 						} `json:"properties"`
 					}{
 						Type:     "object",
 						Required: []string{"location"},
 						Properties: map[string]struct {
 							Type        api.PropertyType `json:"type"`
 							Description string           `json:"description"`
-							Enum        []string         `json:"enum,omitempty"`
+							Enum        []any            `json:"enum,omitempty"`
 						}{
 							"location": {
 								Type:        api.PropertyType{"string"},
 								Description: "The city and state",
 							},
 							"unit": {
 								Type: api.PropertyType{"string"},
-								Enum: []string{"celsius", "fahrenheit"},
+								Enum: []any{"celsius", "fahrenheit"},
 							},
 						},
 					},