Skip to content

Commit

Permalink
Changes Definition.Type from DataType to []DataType
Browse files Browse the repository at this point in the history
Why?

* Allows for more fine-grained control of types
* Allows for pointers to be treated as nullable
* Fixes issue with empty properties object being omitted when marshaling
  • Loading branch information
blwsh committed Dec 26, 2024
1 parent 56a9acf commit 9ed028e
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 146 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -628,14 +628,14 @@ Using the `jsonschema` package, this schema could be created using structs as su
FunctionDefinition{
Name: "get_current_weather",
Parameters: jsonschema.Definition{
Type: jsonschema.Object,
Type: []jsonschema.DataType{jsonschema.Object},
Properties: map[string]jsonschema.Definition{
"location": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
Description: "The city and state, e.g. San Francisco, CA",
},
"unit": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
Enum: []string{"celsius", "fahrenheit"},
},
},
Expand Down
16 changes: 8 additions & 8 deletions api_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,14 @@ func TestAPI(t *testing.T) {
Functions: []openai.FunctionDefinition{{
Name: "get_current_weather",
Parameters: jsonschema.Definition{
Type: jsonschema.Object,
Type: []jsonschema.DataType{jsonschema.Object},
Properties: map[string]jsonschema.Definition{
"location": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
Description: "The city and state, e.g. San Francisco, CA",
},
"unit": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
Enum: []string{"celsius", "fahrenheit"},
},
},
Expand Down Expand Up @@ -273,19 +273,19 @@ func TestChatCompletionStructuredOutputsFunctionCalling(t *testing.T) {
Name: "display_cases",
Strict: true,
Parameters: &jsonschema.Definition{
Type: jsonschema.Object,
Type: []jsonschema.DataType{jsonschema.Object},
Properties: map[string]jsonschema.Definition{
"PascalCase": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
},
"CamelCase": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
},
"KebabCase": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
},
"SnakeCase": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
},
},
Required: []string{"PascalCase", "CamelCase", "KebabCase", "SnakeCase"},
Expand Down
20 changes: 10 additions & 10 deletions chat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,21 +428,21 @@ func TestChatCompletionsFunctions(t *testing.T) {
Functions: []openai.FunctionDefinition{{
Name: "test",
Parameters: &jsonschema.Definition{
Type: jsonschema.Object,
Type: []jsonschema.DataType{jsonschema.Object},
Properties: map[string]jsonschema.Definition{
"count": {
Type: jsonschema.Number,
Type: []jsonschema.DataType{jsonschema.Number},
Description: "total number of words in sentence",
},
"words": {
Type: jsonschema.Array,
Type: []jsonschema.DataType{jsonschema.Array},
Description: "list of words in sentence",
Items: &jsonschema.Definition{
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
},
},
"enumTest": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
Enum: []string{"hello", "world"},
},
},
Expand All @@ -465,21 +465,21 @@ func TestChatCompletionsFunctions(t *testing.T) {
Functions: []openai.FunctionDefine{{
Name: "test",
Parameters: &jsonschema.Definition{
Type: jsonschema.Object,
Type: []jsonschema.DataType{jsonschema.Object},
Properties: map[string]jsonschema.Definition{
"count": {
Type: jsonschema.Number,
Type: []jsonschema.DataType{jsonschema.Number},
Description: "total number of words in sentence",
},
"words": {
Type: jsonschema.Array,
Type: []jsonschema.DataType{jsonschema.Array},
Description: "list of words in sentence",
Items: &jsonschema.Definition{
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
},
},
"enumTest": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
Enum: []string{"hello", "world"},
},
},
Expand Down
6 changes: 3 additions & 3 deletions examples/completion-with-tool/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ func main() {

// describe the function & its inputs
params := jsonschema.Definition{
Type: jsonschema.Object,
Type: []jsonschema.DataType{jsonschema.Object},
Properties: map[string]jsonschema.Definition{
"location": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
Description: "The city and state, e.g. San Francisco, CA",
},
"unit": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
Enum: []string{"celsius", "fahrenheit"},
},
},
Expand Down
19 changes: 10 additions & 9 deletions jsonschema/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const (
// It is fairly limited, and you may have better luck using a third-party library.
type Definition struct {
// Type specifies the data type of the schema.
Type DataType `json:"type,omitempty"`
Type []DataType `json:"type,omitempty"`
// Description is the description of the schema.
Description string `json:"description,omitempty"`
// Enum is used to restrict a value to a fixed set of values. It must be an array with at least
Expand All @@ -44,7 +44,7 @@ type Definition struct {
// that are not explicitly defined in the properties section of the schema. example:
// additionalProperties: true
// additionalProperties: false
// additionalProperties: jsonschema.Definition{Type: jsonschema.String}
// additionalProperties: jsonschema.Definition{Type: []jsonschema.DataType{jsonschema.String}}
AdditionalProperties any `json:"additionalProperties,omitempty"`
}

Expand Down Expand Up @@ -72,23 +72,23 @@ func reflectSchema(t reflect.Type) (*Definition, error) {
var d Definition
switch t.Kind() {
case reflect.String:
d.Type = String
d.Type = []DataType{String}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
d.Type = Integer
d.Type = []DataType{Integer}
case reflect.Float32, reflect.Float64:
d.Type = Number
d.Type = []DataType{Number}
case reflect.Bool:
d.Type = Boolean
d.Type = []DataType{Boolean}
case reflect.Slice, reflect.Array:
d.Type = Array
d.Type = []DataType{Array}
items, err := reflectSchema(t.Elem())
if err != nil {
return nil, err
}
d.Items = items
case reflect.Struct:
d.Type = Object
d.Type = []DataType{Object}
d.AdditionalProperties = false
object, err := reflectSchemaObject(t)
if err != nil {
Expand All @@ -101,6 +101,7 @@ func reflectSchema(t reflect.Type) (*Definition, error) {
return nil, err
}
d = *definition
d.Type = append(d.Type, Null)
case reflect.Invalid, reflect.Uintptr, reflect.Complex64, reflect.Complex128,
reflect.Chan, reflect.Func, reflect.Interface, reflect.Map,
reflect.UnsafePointer:
Expand All @@ -112,7 +113,7 @@ func reflectSchema(t reflect.Type) (*Definition, error) {

func reflectSchemaObject(t reflect.Type) (*Definition, error) {
var d = Definition{
Type: Object,
Type: []DataType{Object},
AdditionalProperties: false,
}
properties := make(map[string]Definition)
Expand Down
88 changes: 40 additions & 48 deletions jsonschema/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,61 +17,58 @@ func TestDefinition_MarshalJSON(t *testing.T) {
{
name: "Test with empty Definition",
def: jsonschema.Definition{},
want: `{"properties":{}}`,
want: `{}`,
},
{
name: "Test with Definition properties set",
def: jsonschema.Definition{
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
Description: "A string type",
Properties: map[string]jsonschema.Definition{
"name": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
},
},
},
want: `{
"type":"string",
"type":["string"],
"description":"A string type",
"properties":{
"name":{
"type":"string",
"properties":{}
"type":["string"]
}
}
}`,
},
{
name: "Test with nested Definition properties",
def: jsonschema.Definition{
Type: jsonschema.Object,
Type: []jsonschema.DataType{jsonschema.Object},
Properties: map[string]jsonschema.Definition{
"user": {
Type: jsonschema.Object,
Type: []jsonschema.DataType{jsonschema.Object},
Properties: map[string]jsonschema.Definition{
"name": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
},
"age": {
Type: jsonschema.Integer,
Type: []jsonschema.DataType{jsonschema.Integer},
},
},
},
},
},
want: `{
"type":"object",
"type":["object"],
"properties":{
"user":{
"type":"object",
"type":["object"],
"properties":{
"name":{
"type":"string",
"properties":{}
"type":["string"]
},
"age":{
"type":"integer",
"properties":{}
"type":["integer"]
}
}
}
Expand All @@ -81,25 +78,25 @@ func TestDefinition_MarshalJSON(t *testing.T) {
{
name: "Test with complex nested Definition",
def: jsonschema.Definition{
Type: jsonschema.Object,
Type: []jsonschema.DataType{jsonschema.Object},
Properties: map[string]jsonschema.Definition{
"user": {
Type: jsonschema.Object,
Type: []jsonschema.DataType{jsonschema.Object},
Properties: map[string]jsonschema.Definition{
"name": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
},
"age": {
Type: jsonschema.Integer,
Type: []jsonschema.DataType{jsonschema.Integer},
},
"address": {
Type: jsonschema.Object,
Type: []jsonschema.DataType{jsonschema.Object},
Properties: map[string]jsonschema.Definition{
"city": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
},
"country": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
},
},
},
Expand All @@ -108,31 +105,27 @@ func TestDefinition_MarshalJSON(t *testing.T) {
},
},
want: `{
"type":"object",
"type":["object"],
"properties":{
"user":{
"type":"object",
"type":["object"],
"properties":{
"name":{
"type":"string",
"properties":{}
},
"age":{
"type":"integer",
"properties":{}
},
"address":{
"type":"object",
"type":["object"],
"properties":{
"city":{
"type":"string",
"properties":{}
"type":["string"]
},
"country":{
"type":"string",
"properties":{}
"type":["string"]
}
}
},
"name":{
"type":["string"]
},
"age":{
"type":["integer"]
}
}
}
Expand All @@ -142,28 +135,24 @@ func TestDefinition_MarshalJSON(t *testing.T) {
{
name: "Test with Array type Definition",
def: jsonschema.Definition{
Type: jsonschema.Array,
Type: []jsonschema.DataType{jsonschema.Array},
Items: &jsonschema.Definition{
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
},
Properties: map[string]jsonschema.Definition{
"name": {
Type: jsonschema.String,
Type: []jsonschema.DataType{jsonschema.String},
},
},
},
want: `{
"type":"array",
"type":["array"],
"items":{
"type":"string",
"properties":{
}
"type":["string"]
},
"properties":{
"name":{
"type":"string",
"properties":{}
"type":["string"]
}
}
}`,
Expand All @@ -183,6 +172,9 @@ func TestDefinition_MarshalJSON(t *testing.T) {
got := structToMap(t, tt.def)
gotPtr := structToMap(t, &tt.def)

gotBytes, err := json.Marshal(tt.def)

Check failure on line 175 in jsonschema/json_test.go

View workflow job for this annotation

GitHub Actions / Sanity check

ineffectual assignment to err (ineffassign)
_ = gotBytes

if !reflect.DeepEqual(got, want) {
t.Errorf("MarshalJSON() got = %v, want %v", got, want)
}
Expand Down
Loading

0 comments on commit 9ed028e

Please sign in to comment.