diff --git a/README.md b/README.md
index 0d840b6..e6dd418 100644
--- a/README.md
+++ b/README.md
@@ -34,16 +34,13 @@ As shown in the example below, by adding extra metadata to each struct field (vi
> For more information on the `jsonschema` tags available, see the [`jsonschema` godoc](https://pkg.go.dev/github.com/invopop/jsonschema?utm_source=godoc).
-
-Running
+Running
```bash
export OPENAI_API_KEY=
go run examples/user/main.go
```
-
-
```go
package main
@@ -105,16 +102,13 @@ Age: %d
Function Calling with OpenAI
-
-Running
+Running
```bash
export OPENAI_API_KEY=
go run examples/function_calling/main.go
```
-
-
```go
package main
@@ -195,16 +189,13 @@ func main() {
Text Classification with Anthropic
-
-Running
+Running
```bash
export ANTHROPIC_API_KEY=
go run examples/classification/main.go
```
-
-
```go
package main
@@ -298,18 +289,15 @@ func assert(condition bool, message string) {
Images with OpenAI
-![List of books](https://raw.githubusercontent.com/instructor-ai/instructor-go/main/examples/images/openai/books.png)
+![List of books](https://raw.githubusercontent.com/instructor-ai/instructor-go/main/examples/vision/openai/books.png)
-
-Running
+Running
```bash
export OPENAI_API_KEY=
-go run examples/images/openai/main.go
+go run examples/vision/openai/main.go
```
-
-
```go
package main
@@ -437,18 +425,15 @@ func main() {
Images with Anthropic
-![List of movies](https://raw.githubusercontent.com/instructor-ai/instructor-go/main/examples/images/anthropic/movies.png)
+![List of movies](https://raw.githubusercontent.com/instructor-ai/instructor-go/main/examples/vision/anthropic/movies.png)
-
-Running
+Running
```bash
export ANTHROPIC_API_KEY=
-go run examples/images/anthropic/main.go
+go run examples/vision/anthropic/main.go
```
-
-
```go
package main
@@ -608,16 +593,13 @@ func urlToBase64(url string) (string, error) {
Streaming with OpenAI
-
-Running
+Running
```bash
export OPENAI_API_KEY=
go run examples/streaming/openai/main.go
```
-
-
```go
package main
@@ -754,16 +736,13 @@ Product list:
Document Segmentation with Cohere
-
-Running
+Running
```bash
export COHERE_API_KEY=
go run examples/document_segmentation/main.go
```
-
-
```go
package main
@@ -915,16 +894,13 @@ func getSectionsText(structuredDoc *StructuredDocument, line2text map[int]string
Streaming with Cohere
-
-Running
+Running
```bash
export COHERE_API_KEY=
go run examples/streaming/cohere/main.go
```
-
-
```go
package main
@@ -1008,15 +984,12 @@ func toPtr[T any](val T) *T {
Local, Self-Hosted Models with Ollama (via OpenAI API Support)
-
-Running
+Running
```bash
go run examples/ollama/main.go
```
-
-
```go
package main
@@ -1091,6 +1064,7 @@ func main() {
+
Receipt Item Extraction from Image (using OpenAI GPT-4o)
@@ -1098,15 +1072,12 @@ func main() {
-
-Running
+Running
```bash
go run examples/vision/receipt/main.go
```
-
-
```go
package main
@@ -1267,6 +1238,192 @@ Total: $98.21
+
+
+Task Ticket Creator from Transcript - OpenAI Structured Outputs (Strict JSON Mode)
+
+
+
+Running
+
+```bash
+export OPENAI_API_KEY=
+go run examples/auto_ticketer/main.go
+```
+
+```go
+```
+
+
+
+Task Ticket Creator from Transcript - OpenAI Structured Outputs (Strict JSON Mode)
+
+Running
+
+```bash
+export OPENAI_API_KEY=
+go run examples/auto_ticketer/main.go
+```
+
+```go
+/*
+ * Original example in Python: https://github.com/jxnl/instructor/blob/11125a7c831a26e2a4deaef4129f2b4845a7e079/examples/auto-ticketer/run.py
+ */
+
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/instructor-ai/instructor-go/pkg/instructor"
+ openai "github.com/sashabaranov/go-openai"
+)
+
+type PriorityEnum string
+
+const (
+ High PriorityEnum = "High"
+ Medium PriorityEnum = "Medium"
+ Low PriorityEnum = "Low"
+)
+
+type Subtask struct {
+ ID int `json:"id" jsonschema:"title=unique identifier for the subtask,description=Unique identifier for the subtask"`
+ Name string `json:"name" jsonschema:"title=name of the subtask,description=Informative title of the subtask"`
+}
+
+type Ticket struct {
+ ID int `json:"id" jsonschema:"title=unique identifier for the ticket,description=Unique identifier for the ticket"`
+ Name string `json:"name" jsonschema:"title=name of the task,description=Title of the task"`
+ Description string `json:"description" jsonschema:"title=description of the task,description=Detailed description of the task"`
+ Priority PriorityEnum `json:"priority" jsonschema:"title=priority level,description=Priority level"`
+ Assignees []string `json:"assignees" jsonschema:"title=list of users assigned to the task,description=List of users assigned to the task"`
+ Subtasks []Subtask `json:"subtasks" jsonschema:"title=list of subtasks associated with the main task,description=List of subtasks associated with the main task"`
+ Dependencies []int `json:"dependencies" jsonschema:"title=list of ticket IDs that this ticket depends on,description=List of ticket IDs that this ticket depends on"`
+}
+
+type ActionItems struct {
+ Tickets []Ticket `json:"tickets"`
+}
+
+func (ai ActionItems) String() string {
+ var sb strings.Builder
+
+ for _, ticket := range ai.Tickets {
+ sb.WriteString(fmt.Sprintf("Ticket ID: %d\n", ticket.ID))
+ sb.WriteString(fmt.Sprintf(" Name: %s\n", ticket.Name))
+ sb.WriteString(fmt.Sprintf(" Description: %s\n", ticket.Description))
+ sb.WriteString(fmt.Sprintf(" Priority: %s\n", ticket.Priority))
+ sb.WriteString(fmt.Sprintf(" Assignees: %s\n", strings.Join(ticket.Assignees, ", ")))
+
+ if len(ticket.Subtasks) > 0 {
+ sb.WriteString(" Subtasks:\n")
+ for _, subtask := range ticket.Subtasks {
+ sb.WriteString(fmt.Sprintf(" - Subtask ID: %d, Name: %s\n", subtask.ID, subtask.Name))
+ }
+ }
+
+ if len(ticket.Dependencies) > 0 {
+ sb.WriteString(fmt.Sprintf(" Dependencies: %v\n", ticket.Dependencies))
+ }
+
+ sb.WriteString("\n")
+ }
+
+ return sb.String()
+}
+
+func main() {
+ ctx := context.Background()
+
+ client := instructor.FromOpenAI(
+ openai.NewClient(os.Getenv("OPENAI_API_KEY")),
+ instructor.WithMode(instructor.ModeJSONStrict),
+ instructor.WithMaxRetries(0),
+ )
+
+ transcript := `
+Alice: Hey team, we have several critical tasks we need to tackle for the upcoming release. First, we need to work on improving the authentication system. It's a top priority.
+
+Bob: Got it, Alice. I can take the lead on the authentication improvements. Are there any specific areas you want me to focus on?
+
+Alice: Good question, Bob. We need both a front-end revamp and back-end optimization. So basically, two sub-tasks.
+
+Carol: I can help with the front-end part of the authentication system.
+
+Bob: Great, Carol. I'll handle the back-end optimization then.
+
+Alice: Perfect. Now, after the authentication system is improved, we have to integrate it with our new billing system. That's a medium priority task.
+
+Carol: Is the new billing system already in place?
+
+Alice: No, it's actually another task. So it's a dependency for the integration task. Bob, can you also handle the billing system?
+
+Bob: Sure, but I'll need to complete the back-end optimization of the authentication system first, so it's dependent on that.
+
+Alice: Understood. Lastly, we also need to update our user documentation to reflect all these changes. It's a low-priority task but still important.
+
+Carol: I can take that on once the front-end changes for the authentication system are done. So, it would be dependent on that.
+
+Alice: Sounds like a plan. Let's get these tasks modeled out and get started.
+`
+
+ var actionItems ActionItems
+ _, err := client.CreateChatCompletion(
+ ctx,
+ openai.ChatCompletionRequest{
+ Model: openai.GPT4oMini20240718,
+ Temperature: .2,
+ Messages: []openai.ChatCompletionMessage{
+ {
+ Role: openai.ChatMessageRoleSystem,
+ Content: "The following is a transcript of a meeting between a manager and their team. The manager is assigning tasks to their team members and creating action items for them to complete.",
+ },
+ {
+ Role: openai.ChatMessageRoleUser,
+ Content: fmt.Sprintf("Create the action items for the following transcript: %s", transcript),
+ },
+ },
+ },
+ &actionItems,
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ println(actionItems.String())
+ /*
+ Ticket ID: 1
+ Name: Improve Authentication System
+ Description: Revamp the front-end and optimize the back-end of the authentication system.
+ Priority: high
+ Assignees: Bob, Carol
+ Subtasks:
+ - Subtask ID: 1, Name: Front-end Revamp
+ - Subtask ID: 2, Name: Back-end Optimization
+
+ Ticket ID: 2
+ Name: Integrate Authentication with New Billing System
+ Description: Integrate the improved authentication system with the new billing system.
+ Priority: medium
+ Assignees: Bob
+ Dependencies: [1]
+
+ Ticket ID: 3
+ Name: Update User Documentation
+ Description: Update the user documentation to reflect changes made to the authentication system.
+ Priority: low
+ Assignees: Carol
+ Dependencies: [1]
+ */
+}
+```
+
+
+
## Providers
Instructor Go supports the following LLM provider APIs:
diff --git a/examples/auto_ticketer/main.go b/examples/auto_ticketer/main.go
new file mode 100644
index 0000000..4393e38
--- /dev/null
+++ b/examples/auto_ticketer/main.go
@@ -0,0 +1,154 @@
+/*
+ * Original example in Python: https://github.com/jxnl/instructor/blob/11125a7c831a26e2a4deaef4129f2b4845a7e079/examples/auto-ticketer/run.py
+ */
+
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/instructor-ai/instructor-go/pkg/instructor"
+ openai "github.com/sashabaranov/go-openai"
+)
+
+type PriorityEnum string
+
+const (
+ High PriorityEnum = "High"
+ Medium PriorityEnum = "Medium"
+ Low PriorityEnum = "Low"
+)
+
+type Subtask struct {
+ ID int `json:"id" jsonschema:"title=unique identifier for the subtask,description=Unique identifier for the subtask"`
+ Name string `json:"name" jsonschema:"title=name of the subtask,description=Informative title of the subtask"`
+}
+
+type Ticket struct {
+ ID int `json:"id" jsonschema:"title=unique identifier for the ticket,description=Unique identifier for the ticket"`
+ Name string `json:"name" jsonschema:"title=name of the task,description=Title of the task"`
+ Description string `json:"description" jsonschema:"title=description of the task,description=Detailed description of the task"`
+ Priority PriorityEnum `json:"priority" jsonschema:"title=priority level,description=Priority level"`
+ Assignees []string `json:"assignees" jsonschema:"title=list of users assigned to the task,description=List of users assigned to the task"`
+ Subtasks []Subtask `json:"subtasks" jsonschema:"title=list of subtasks associated with the main task,description=List of subtasks associated with the main task"`
+ Dependencies []int `json:"dependencies" jsonschema:"title=list of ticket IDs that this ticket depends on,description=List of ticket IDs that this ticket depends on"`
+}
+
+type ActionItems struct {
+ Tickets []Ticket `json:"tickets"`
+}
+
+func (ai ActionItems) String() string {
+ var sb strings.Builder
+
+ for _, ticket := range ai.Tickets {
+ sb.WriteString(fmt.Sprintf("Ticket ID: %d\n", ticket.ID))
+ sb.WriteString(fmt.Sprintf(" Name: %s\n", ticket.Name))
+ sb.WriteString(fmt.Sprintf(" Description: %s\n", ticket.Description))
+ sb.WriteString(fmt.Sprintf(" Priority: %s\n", ticket.Priority))
+ sb.WriteString(fmt.Sprintf(" Assignees: %s\n", strings.Join(ticket.Assignees, ", ")))
+
+ if len(ticket.Subtasks) > 0 {
+ sb.WriteString(" Subtasks:\n")
+ for _, subtask := range ticket.Subtasks {
+ sb.WriteString(fmt.Sprintf(" - Subtask ID: %d, Name: %s\n", subtask.ID, subtask.Name))
+ }
+ }
+
+ if len(ticket.Dependencies) > 0 {
+ sb.WriteString(fmt.Sprintf(" Dependencies: %v\n", ticket.Dependencies))
+ }
+
+ sb.WriteString("\n")
+ }
+
+ return sb.String()
+}
+
+func main() {
+ ctx := context.Background()
+
+ client := instructor.FromOpenAI(
+ openai.NewClient(os.Getenv("OPENAI_API_KEY")),
+ instructor.WithMode(instructor.ModeJSONStrict),
+ instructor.WithMaxRetries(0),
+ )
+
+ transcript := `
+Alice: Hey team, we have several critical tasks we need to tackle for the upcoming release. First, we need to work on improving the authentication system. It's a top priority.
+
+Bob: Got it, Alice. I can take the lead on the authentication improvements. Are there any specific areas you want me to focus on?
+
+Alice: Good question, Bob. We need both a front-end revamp and back-end optimization. So basically, two sub-tasks.
+
+Carol: I can help with the front-end part of the authentication system.
+
+Bob: Great, Carol. I'll handle the back-end optimization then.
+
+Alice: Perfect. Now, after the authentication system is improved, we have to integrate it with our new billing system. That's a medium priority task.
+
+Carol: Is the new billing system already in place?
+
+Alice: No, it's actually another task. So it's a dependency for the integration task. Bob, can you also handle the billing system?
+
+Bob: Sure, but I'll need to complete the back-end optimization of the authentication system first, so it's dependent on that.
+
+Alice: Understood. Lastly, we also need to update our user documentation to reflect all these changes. It's a low-priority task but still important.
+
+Carol: I can take that on once the front-end changes for the authentication system are done. So, it would be dependent on that.
+
+Alice: Sounds like a plan. Let's get these tasks modeled out and get started.
+`
+
+ var actionItems ActionItems
+ _, err := client.CreateChatCompletion(
+ ctx,
+ openai.ChatCompletionRequest{
+ Model: openai.GPT4oMini20240718,
+ Temperature: .2,
+ Messages: []openai.ChatCompletionMessage{
+ {
+ Role: openai.ChatMessageRoleSystem,
+ Content: "The following is a transcript of a meeting between a manager and their team. The manager is assigning tasks to their team members and creating action items for them to complete.",
+ },
+ {
+ Role: openai.ChatMessageRoleUser,
+ Content: fmt.Sprintf("Create the action items for the following transcript: %s", transcript),
+ },
+ },
+ },
+ &actionItems,
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ println(actionItems.String())
+ /*
+ Ticket ID: 1
+ Name: Improve Authentication System
+ Description: Revamp the front-end and optimize the back-end of the authentication system.
+ Priority: high
+ Assignees: Bob, Carol
+ Subtasks:
+ - Subtask ID: 1, Name: Front-end Revamp
+ - Subtask ID: 2, Name: Back-end Optimization
+
+ Ticket ID: 2
+ Name: Integrate Authentication with New Billing System
+ Description: Integrate the improved authentication system with the new billing system.
+ Priority: medium
+ Assignees: Bob
+ Dependencies: [1]
+
+ Ticket ID: 3
+ Name: Update User Documentation
+ Description: Update the user documentation to reflect changes made to the authentication system.
+ Priority: low
+ Assignees: Carol
+ Dependencies: [1]
+ */
+}
diff --git a/examples/vision/receipt/main.go b/examples/vision/receipt/main.go
index daa79e7..2413a22 100644
--- a/examples/vision/receipt/main.go
+++ b/examples/vision/receipt/main.go
@@ -97,7 +97,7 @@ func main() {
urls := []string{
// source: https://templates.mediamodifier.com/645124ff36ed2f5227cbf871/supermarket-receipt-template.jpg
- "https://raw.githubusercontent.com/instructor-ai/instructor-go/main/examples/vision/receipt/supermarket-receipt-template.jpg",
+ "https://raw.githubusercontent.com/instructor-ai/instructor-go/main/examples/vision/receipt/supermarket-receipt-template.jpg",
// source: https://ocr.space/Content/Images/receipt-ocr-original.jpg
"https://raw.githubusercontent.com/instructor-ai/instructor-go/main/examples/vision/receipt/receipt-ocr-original.jpg",
}
diff --git a/go.mod b/go.mod
index a3fb0c9..45c16c5 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
github.com/go-playground/validator/v10 v10.21.0
github.com/invopop/jsonschema v0.12.0
github.com/liushuangls/go-anthropic/v2 v2.1.0
- github.com/sashabaranov/go-openai v1.24.1
+ github.com/sashabaranov/go-openai v1.29.0
)
require (
diff --git a/go.sum b/go.sum
index 818bbbb..4c8050f 100644
--- a/go.sum
+++ b/go.sum
@@ -29,8 +29,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/sashabaranov/go-openai v1.24.1 h1:DWK95XViNb+agQtuzsn+FyHhn3HQJ7Va8z04DQDJ1MI=
-github.com/sashabaranov/go-openai v1.24.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
+github.com/sashabaranov/go-openai v1.29.0 h1:eBH6LSjtX4md5ImDCX8hNhHQvaRf22zujiERoQpsvLo=
+github.com/sashabaranov/go-openai v1.29.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
diff --git a/pkg/instructor/chat.go b/pkg/instructor/chat.go
index 4a6ec41..c7cc5d3 100644
--- a/pkg/instructor/chat.go
+++ b/pkg/instructor/chat.go
@@ -29,7 +29,7 @@ func chatHandler(i Instructor, ctx context.Context, request interface{}, respons
// keep a running total of usage
usage := &UsageSum{}
- for attempt := 0; attempt < i.MaxRetries(); attempt++ {
+ for attempt := 0; attempt <= i.MaxRetries(); attempt++ {
text, resp, err := i.chat(ctx, request, schema)
if err != nil {
diff --git a/pkg/instructor/mode_enum.go b/pkg/instructor/mode_enum.go
index ad55484..1a776ea 100644
--- a/pkg/instructor/mode_enum.go
+++ b/pkg/instructor/mode_enum.go
@@ -3,9 +3,11 @@ package instructor
type Mode = string
const (
- ModeToolCall Mode = "tool_call"
- ModeJSON Mode = "json_mode"
- ModeJSONSchema Mode = "json_schema_mode"
- ModeMarkdownJSON Mode = "markdown_json_mode"
- ModeDefault Mode = ModeJSONSchema
+ ModeToolCall Mode = "tool_call_mode"
+ ModeToolCallStrict Mode = "tool_call_strict_mode"
+ ModeJSON Mode = "json_mode"
+ ModeJSONStrict Mode = "json_strict_mode"
+ ModeJSONSchema Mode = "json_schema_mode"
+ ModeMarkdownJSON Mode = "markdown_json_mode"
+ ModeDefault Mode = ModeJSONSchema
)
diff --git a/pkg/instructor/openai_chat.go b/pkg/instructor/openai_chat.go
index b6f73a7..ab9b515 100644
--- a/pkg/instructor/openai_chat.go
+++ b/pkg/instructor/openai_chat.go
@@ -6,9 +6,18 @@ import (
"errors"
"fmt"
+ "github.com/invopop/jsonschema"
openai "github.com/sashabaranov/go-openai"
)
+type ResponseFormatSchemaWrapper struct {
+ Type string `json:"type"`
+ Required []string `json:"required"`
+ AdditionalProperties bool `json:"additionalProperties"`
+ Properties *jsonschema.Definitions `json:"properties"`
+ Definitions *jsonschema.Definitions `json:"$defs"`
+}
+
func (i *InstructorOpenAI) CreateChatCompletion(
ctx context.Context,
request openai.ChatCompletionRequest,
@@ -41,9 +50,13 @@ func (i *InstructorOpenAI) chat(ctx context.Context, request interface{}, schema
switch i.Mode() {
case ModeToolCall:
- return i.chatToolCall(ctx, &req, schema)
+ return i.chatToolCall(ctx, &req, schema, false)
+ case ModeToolCallStrict:
+ return i.chatToolCall(ctx, &req, schema, true)
case ModeJSON:
- return i.chatJSON(ctx, &req, schema)
+ return i.chatJSON(ctx, &req, schema, false)
+ case ModeJSONStrict:
+ return i.chatJSON(ctx, &req, schema, true)
case ModeJSONSchema:
return i.chatJSONSchema(ctx, &req, schema)
default:
@@ -51,9 +64,9 @@ func (i *InstructorOpenAI) chat(ctx context.Context, request interface{}, schema
}
}
-func (i *InstructorOpenAI) chatToolCall(ctx context.Context, request *openai.ChatCompletionRequest, schema *Schema) (string, *openai.ChatCompletionResponse, error) {
+func (i *InstructorOpenAI) chatToolCall(ctx context.Context, request *openai.ChatCompletionRequest, schema *Schema, strict bool) (string, *openai.ChatCompletionResponse, error) {
- request.Tools = createOpenAITools(schema)
+ request.Tools = createOpenAITools(schema, strict)
resp, err := i.Client.CreateChatCompletion(ctx, *request)
if err != nil {
@@ -100,12 +113,40 @@ func (i *InstructorOpenAI) chatToolCall(ctx context.Context, request *openai.Cha
return string(resultJSON), &resp, nil
}
-func (i *InstructorOpenAI) chatJSON(ctx context.Context, request *openai.ChatCompletionRequest, schema *Schema) (string, *openai.ChatCompletionResponse, error) {
+func (i *InstructorOpenAI) chatJSON(ctx context.Context, request *openai.ChatCompletionRequest, schema *Schema, strict bool) (string, *openai.ChatCompletionResponse, error) {
+
+ structName := schema.NameFromRef()
request.Messages = prepend(request.Messages, *createJSONMessage(schema))
- // Set JSON mode
- request.ResponseFormat = &openai.ChatCompletionResponseFormat{Type: openai.ChatCompletionResponseFormatTypeJSONObject}
+ if strict {
+ schemaWrapper := ResponseFormatSchemaWrapper{
+ Type: "object",
+ Required: []string{structName},
+ Definitions: &schema.Schema.Definitions,
+ Properties: &jsonschema.Definitions{
+ structName: schema.Definitions[structName],
+ },
+ AdditionalProperties: false,
+ }
+
+ schemaJSON, _ := json.Marshal(schemaWrapper)
+ schemaRaw := json.RawMessage(schemaJSON)
+
+ request.ResponseFormat = &openai.ChatCompletionResponseFormat{
+ Type: openai.ChatCompletionResponseFormatTypeJSONSchema,
+ JSONSchema: &openai.ChatCompletionResponseFormatJSONSchema{
+ Name: structName,
+ Description: schema.Description,
+ Schema: schemaRaw,
+ Strict: true,
+ },
+ }
+ } else {
+ request.ResponseFormat = &openai.ChatCompletionResponseFormat{
+ Type: openai.ChatCompletionResponseFormatTypeJSONObject,
+ }
+ }
resp, err := i.Client.CreateChatCompletion(ctx, *request)
if err != nil {
@@ -114,6 +155,14 @@ func (i *InstructorOpenAI) chatJSON(ctx context.Context, request *openai.ChatCom
text := resp.Choices[0].Message.Content
+ if strict {
+ resMap := make(map[string]any)
+ _ = json.Unmarshal([]byte(text), &resMap)
+
+ cleanedText, _ := json.Marshal(resMap[structName])
+ text = string(cleanedText)
+ }
+
return text, &resp, nil
}
@@ -195,6 +244,24 @@ Make sure to return an instance of the JSON, not the schema itself
return msg
}
+func createOpenAITools(schema *Schema, strict bool) []openai.Tool {
+ tools := make([]openai.Tool, 0, len(schema.Functions))
+ for _, function := range schema.Functions {
+ f := openai.FunctionDefinition{
+ Name: function.Name,
+ Description: function.Description,
+ Parameters: function.Parameters,
+ Strict: strict,
+ }
+ t := openai.Tool{
+ Type: "function",
+ Function: &f,
+ }
+ tools = append(tools, t)
+ }
+ return tools
+}
+
func nilOpenaiRespWithUsage(resp *openai.ChatCompletionResponse) *openai.ChatCompletionResponse {
if resp == nil {
return nil
diff --git a/pkg/instructor/openai_chat_stream.go b/pkg/instructor/openai_chat_stream.go
index 8a46c0e..72120cf 100644
--- a/pkg/instructor/openai_chat_stream.go
+++ b/pkg/instructor/openai_chat_stream.go
@@ -36,7 +36,9 @@ func (i *InstructorOpenAI) chatStream(ctx context.Context, request interface{},
switch i.Mode() {
case ModeToolCall:
- return i.chatToolCallStream(ctx, &req, schema)
+ return i.chatToolCallStream(ctx, &req, schema, false)
+ case ModeToolCallStrict:
+ return i.chatToolCallStream(ctx, &req, schema, true)
case ModeJSON:
return i.chatJSONStream(ctx, &req, schema)
case ModeJSONSchema:
@@ -46,8 +48,8 @@ func (i *InstructorOpenAI) chatStream(ctx context.Context, request interface{},
}
}
-func (i *InstructorOpenAI) chatToolCallStream(ctx context.Context, request *openai.ChatCompletionRequest, schema *Schema) (<-chan string, error) {
- request.Tools = createOpenAITools(schema)
+func (i *InstructorOpenAI) chatToolCallStream(ctx context.Context, request *openai.ChatCompletionRequest, schema *Schema, strict bool) (<-chan string, error) {
+ request.Tools = createOpenAITools(schema, strict)
return i.createStream(ctx, request)
}
@@ -63,23 +65,6 @@ func (i *InstructorOpenAI) chatJSONSchemaStream(ctx context.Context, request *op
return i.createStream(ctx, request)
}
-func createOpenAITools(schema *Schema) []openai.Tool {
- tools := make([]openai.Tool, 0, len(schema.Functions))
- for _, function := range schema.Functions {
- f := openai.FunctionDefinition{
- Name: function.Name,
- Description: function.Description,
- Parameters: function.Parameters,
- }
- t := openai.Tool{
- Type: "function",
- Function: &f,
- }
- tools = append(tools, t)
- }
- return tools
-}
-
func createJSONMessageStream(schema *Schema) *openai.ChatCompletionMessage {
message := fmt.Sprintf(`
Please respond with a JSON array where the elements following JSON schema:
diff --git a/pkg/instructor/schema.go b/pkg/instructor/schema.go
index c5868ef..8b9605a 100644
--- a/pkg/instructor/schema.go
+++ b/pkg/instructor/schema.go
@@ -3,6 +3,7 @@ package instructor
import (
"encoding/json"
"reflect"
+ "strings"
"github.com/invopop/jsonschema"
)
@@ -69,3 +70,7 @@ func ToFunctionSchema(tType reflect.Type, tSchema *jsonschema.Schema) []Function
return fds
}
+
+func (s *Schema) NameFromRef() string {
+ return strings.Split(s.Ref, "/")[2] // ex: '#/$defs/MyStruct'
+}