diff --git a/core/internal/testutil/structured_outputs.go b/core/internal/testutil/structured_outputs.go index 0d9b12af7..8a21ec205 100644 --- a/core/internal/testutil/structured_outputs.go +++ b/core/internal/testutil/structured_outputs.go @@ -421,7 +421,7 @@ func RunStructuredOutputResponsesTest(t *testing.T, client *bifrost.Bifrost, ctx Type: &typeStr, Properties: &props, Required: structuredOutputSchema["required"].([]string), - AdditionalProperties: &additionalProps, + AdditionalProperties: &schemas.AdditionalProperties{BoolValue: &additionalProps}, }, }, }, @@ -535,7 +535,7 @@ func RunStructuredOutputResponsesStreamTest(t *testing.T, client *bifrost.Bifros Type: &typeStr, Properties: &props, Required: structuredOutputSchema["required"].([]string), - AdditionalProperties: &additionalProps, + AdditionalProperties: &schemas.AdditionalProperties{BoolValue: &additionalProps}, }, }, }, diff --git a/core/providers/anthropic/utils.go b/core/providers/anthropic/utils.go index 2199f83cf..4881dfda4 100644 --- a/core/providers/anthropic/utils.go +++ b/core/providers/anthropic/utils.go @@ -766,7 +766,9 @@ func convertAnthropicOutputFormatToResponsesTextConfig(outputFormat interface{}) } if additionalProps, ok := schemaMap["additionalProperties"].(bool); ok { - jsonSchema.AdditionalProperties = &additionalProps + jsonSchema.AdditionalProperties = &schemas.AdditionalProperties{BoolValue: &additionalProps} + } else if additionalProps, ok := schemaMap["additionalProperties"].(map[string]any); ok { + jsonSchema.AdditionalProperties = &schemas.AdditionalProperties{ObjectValue: &additionalProps} } format.JSONSchema = jsonSchema diff --git a/core/providers/cohere/utils.go b/core/providers/cohere/utils.go index 2241bfed1..f29aeb4d2 100644 --- a/core/providers/cohere/utils.go +++ b/core/providers/cohere/utils.go @@ -74,7 +74,9 @@ func convertInterfaceToToolFunctionParameters(params interface{}) *schemas.ToolF // Extract additionalProperties if addPropsVal, ok := paramsMap["additionalProperties"].(bool); ok { - result.AdditionalProperties = &addPropsVal + result.AdditionalProperties = &schemas.AdditionalProperties{BoolValue: &addPropsVal} + } else if addPropsVal, ok := paramsMap["additionalProperties"].(map[string]any); ok { + result.AdditionalProperties = &schemas.AdditionalProperties{ObjectValue: &addPropsVal} } return result diff --git a/core/providers/gemini/gemini_test.go b/core/providers/gemini/gemini_test.go index 2a8b12a39..eedfbb0af 100644 --- a/core/providers/gemini/gemini_test.go +++ b/core/providers/gemini/gemini_test.go @@ -556,7 +556,7 @@ func TestResponsesStructuredOutputConversion(t *testing.T) { }, }, Required: []string{"user_id", "status"}, - AdditionalProperties: schemas.Ptr(false), + AdditionalProperties: &schemas.AdditionalProperties{BoolValue: schemas.Ptr(false)}, }, }, }, diff --git a/core/providers/gemini/utils.go b/core/providers/gemini/utils.go index b50d0af3c..badedb7c5 100644 --- a/core/providers/gemini/utils.go +++ b/core/providers/gemini/utils.go @@ -1143,7 +1143,9 @@ func buildJSONSchemaFromMap(schemaMap map[string]interface{}) *schemas.Responses // Extract additionalProperties if additionalProps, ok := normalizedSchemaMap["additionalProperties"].(bool); ok { - jsonSchema.AdditionalProperties = schemas.Ptr(additionalProps) + jsonSchema.AdditionalProperties = &schemas.AdditionalProperties{BoolValue: &additionalProps} + } else if additionalProps, ok := normalizedSchemaMap["additionalProperties"].(map[string]any); ok { + jsonSchema.AdditionalProperties = &schemas.AdditionalProperties{ObjectValue: &additionalProps} } // Extract name/title diff --git a/core/schemas/chatcompletions.go b/core/schemas/chatcompletions.go index 5aae9d6d2..b9e2e96fb 100644 --- a/core/schemas/chatcompletions.go +++ b/core/schemas/chatcompletions.go @@ -273,12 +273,12 @@ type ChatToolFunction struct { // ToolFunctionParameters represents the parameters for a function definition. type ToolFunctionParameters struct { - Type string `json:"type"` // Type of the parameters - Description *string `json:"description,omitempty"` // Description of the parameters - Required []string `json:"required,omitempty"` // Required parameter names - Properties *OrderedMap `json:"properties,omitempty"` // Parameter properties - Enum []string `json:"enum,omitempty"` // Enum values for the parameters - AdditionalProperties *bool `json:"additionalProperties,omitempty"` // Whether to allow additional properties + Type string `json:"type"` // Type of the parameters + Description *string `json:"description,omitempty"` // Description of the parameters + Required []string `json:"required,omitempty"` // Required parameter names + Properties *OrderedMap `json:"properties,omitempty"` // Parameter properties + Enum []string `json:"enum,omitempty"` // Enum values for the parameters + AdditionalProperties *AdditionalProperties `json:"additionalProperties,omitempty"` // Whether to allow additional properties } // UnmarshalJSON implements custom JSON unmarshalling for ToolFunctionParameters. @@ -307,6 +307,45 @@ func (t *ToolFunctionParameters) UnmarshalJSON(data []byte) error { return nil } +// AdditionalProperties handle `additionalProperties` being either a bool value +// or an object, according to JSONSchema: +// https://json-schema.org/understanding-json-schema/reference/object#additionalproperties +type AdditionalProperties struct { + BoolValue *bool + ObjectValue *map[string]any +} + +// UnmarshalJSON implements custom JSON unmarshalling for AdditionalProperties. +// Handles both the value being either bool or a generic jsonschema object. +func (t *AdditionalProperties) UnmarshalJSON(data []byte) error { + // Try to unmarshal as a bool + var boolValue bool + if err := Unmarshal(data, &boolValue); err == nil { + t.BoolValue = &boolValue + return nil + } + + // Otherwise unmarshal as a generic object + var objectValue map[string]any + if err := Unmarshal(data, &objectValue); err != nil { + return err + } + t.ObjectValue = &objectValue + return nil +} + +// MarshalJSON implements custom JSON marshalling for AdditionalProperties. +// If object value exists then it take precedence, else bool value. +func (t *AdditionalProperties) MarshalJSON() ([]byte, error) { + if t.ObjectValue != nil { + return Marshal(t.ObjectValue) + } + if t.BoolValue != nil { + return Marshal(t.BoolValue) + } + return Marshal(nil) +} + type OrderedMap map[string]interface{} // normalizeOrderedMap recursively converts JSON-like data into a tree where diff --git a/core/schemas/responses.go b/core/schemas/responses.go index 566018970..373a5fe94 100644 --- a/core/schemas/responses.go +++ b/core/schemas/responses.go @@ -129,14 +129,14 @@ type ResponsesTextConfigFormat struct { // ResponsesTextConfigFormatJSONSchema represents a JSON schema specification type ResponsesTextConfigFormatJSONSchema struct { - Name *string `json:"name,omitempty"` - Schema *any `json:"schema,omitempty"` - Description *string `json:"description,omitempty"` - Strict *bool `json:"strict,omitempty"` - AdditionalProperties *bool `json:"additionalProperties,omitempty"` - Properties *map[string]any `json:"properties,omitempty"` - Required []string `json:"required,omitempty"` - Type *string `json:"type,omitempty"` + Name *string `json:"name,omitempty"` + Schema *any `json:"schema,omitempty"` + Description *string `json:"description,omitempty"` + Strict *bool `json:"strict,omitempty"` + AdditionalProperties *AdditionalProperties `json:"additionalProperties,omitempty"` + Properties *map[string]any `json:"properties,omitempty"` + Required []string `json:"required,omitempty"` + Type *string `json:"type,omitempty"` } type ResponsesResponseConversation struct {