Skip to content

Commit 623074c

Browse files
authored
feat: Support Structured Outputs (#813)
* feat: Support Structured Outputs * feat: Support Structured Outputs * update imports * add integration test * update JSON schema comments
1 parent dbe726c commit 623074c

File tree

3 files changed

+80
-2
lines changed

3 files changed

+80
-2
lines changed

api_integration_test.go

+61
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package openai_test
44

55
import (
66
"context"
7+
"encoding/json"
78
"errors"
89
"io"
910
"os"
@@ -178,3 +179,63 @@ func TestAPIError(t *testing.T) {
178179
t.Fatal("Empty error message occurred")
179180
}
180181
}
182+
183+
func TestChatCompletionResponseFormat_JSONSchema(t *testing.T) {
184+
apiToken := os.Getenv("OPENAI_TOKEN")
185+
if apiToken == "" {
186+
t.Skip("Skipping testing against production OpenAI API. Set OPENAI_TOKEN environment variable to enable it.")
187+
}
188+
189+
var err error
190+
c := openai.NewClient(apiToken)
191+
ctx := context.Background()
192+
193+
resp, err := c.CreateChatCompletion(
194+
ctx,
195+
openai.ChatCompletionRequest{
196+
Model: openai.GPT4oMini,
197+
Messages: []openai.ChatCompletionMessage{
198+
{
199+
Role: openai.ChatMessageRoleSystem,
200+
Content: "Please enter a string, and we will convert it into the following naming conventions:" +
201+
"1. PascalCase: Each word starts with an uppercase letter, with no spaces or separators." +
202+
"2. CamelCase: The first word starts with a lowercase letter, " +
203+
"and subsequent words start with an uppercase letter, with no spaces or separators." +
204+
"3. KebabCase: All letters are lowercase, with words separated by hyphens `-`." +
205+
"4. SnakeCase: All letters are lowercase, with words separated by underscores `_`.",
206+
},
207+
{
208+
Role: openai.ChatMessageRoleUser,
209+
Content: "Hello World",
210+
},
211+
},
212+
ResponseFormat: &openai.ChatCompletionResponseFormat{
213+
Type: openai.ChatCompletionResponseFormatTypeJSONSchema,
214+
JSONSchema: openai.ChatCompletionResponseFormatJSONSchema{
215+
Name: "cases",
216+
Schema: jsonschema.Definition{
217+
Type: jsonschema.Object,
218+
Properties: map[string]jsonschema.Definition{
219+
"PascalCase": jsonschema.Definition{Type: jsonschema.String},
220+
"CamelCase": jsonschema.Definition{Type: jsonschema.String},
221+
"KebabCase": jsonschema.Definition{Type: jsonschema.String},
222+
"SnakeCase": jsonschema.Definition{Type: jsonschema.String},
223+
},
224+
Required: []string{"PascalCase", "CamelCase", "KebabCase", "SnakeCase"},
225+
AdditionalProperties: false,
226+
},
227+
Strict: true,
228+
},
229+
},
230+
},
231+
)
232+
checks.NoError(t, err, "CreateChatCompletion (use json_schema response) returned error")
233+
var result = make(map[string]string)
234+
err = json.Unmarshal([]byte(resp.Choices[0].Message.Content), &result)
235+
checks.NoError(t, err, "CreateChatCompletion (use json_schema response) unmarshal error")
236+
for _, key := range []string{"PascalCase", "CamelCase", "KebabCase", "SnakeCase"} {
237+
if _, ok := result[key]; !ok {
238+
t.Errorf("key:%s does not exist.", key)
239+
}
240+
}
241+
}

chat.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"encoding/json"
66
"errors"
77
"net/http"
8+
9+
"github.com/sashabaranov/go-openai/jsonschema"
810
)
911

1012
// Chat message role defined by the OpenAI API.
@@ -175,11 +177,20 @@ type ChatCompletionResponseFormatType string
175177

176178
const (
177179
ChatCompletionResponseFormatTypeJSONObject ChatCompletionResponseFormatType = "json_object"
180+
ChatCompletionResponseFormatTypeJSONSchema ChatCompletionResponseFormatType = "json_schema"
178181
ChatCompletionResponseFormatTypeText ChatCompletionResponseFormatType = "text"
179182
)
180183

181184
type ChatCompletionResponseFormat struct {
182-
Type ChatCompletionResponseFormatType `json:"type,omitempty"`
185+
Type ChatCompletionResponseFormatType `json:"type,omitempty"`
186+
JSONSchema ChatCompletionResponseFormatJSONSchema `json:"json_schema,omitempty"`
187+
}
188+
189+
type ChatCompletionResponseFormatJSONSchema struct {
190+
Name string `json:"name"`
191+
Description string `json:"description,omitempty"`
192+
Schema jsonschema.Definition `json:"schema"`
193+
Strict bool `json:"strict"`
183194
}
184195

185196
// ChatCompletionRequest represents a request structure for chat completion API.

jsonschema/json.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,17 @@ type Definition struct {
2929
// one element, where each element is unique. You will probably only use this with strings.
3030
Enum []string `json:"enum,omitempty"`
3131
// Properties describes the properties of an object, if the schema type is Object.
32-
Properties map[string]Definition `json:"properties"`
32+
Properties map[string]Definition `json:"properties,omitempty"`
3333
// Required specifies which properties are required, if the schema type is Object.
3434
Required []string `json:"required,omitempty"`
3535
// Items specifies which data type an array contains, if the schema type is Array.
3636
Items *Definition `json:"items,omitempty"`
37+
// AdditionalProperties is used to control the handling of properties in an object
38+
// that are not explicitly defined in the properties section of the schema. example:
39+
// additionalProperties: true
40+
// additionalProperties: false
41+
// additionalProperties: jsonschema.Definition{Type: jsonschema.String}
42+
AdditionalProperties any `json:"additionalProperties,omitempty"`
3743
}
3844

3945
func (d Definition) MarshalJSON() ([]byte, error) {

0 commit comments

Comments
 (0)