Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion docs/content/reference/llm.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,60 @@ fmt.Println(myThread)

LinGoose allows you to bind a function describing its scope and input's schema. The function will be called by the OpenAI LLM automatically depending on the user's input. Here we force the tool choice to be "auto" to let OpenAI decide which tool to use. If, after an LLM generation, the last message is a tool call, you can enrich the thread with a new LLM generation based on the tool call result.

## OpenAI structured outputs

LinGoose supports structured outputs to ensure responses adhere to a JSON schema. You can define the response format type as `json_object` or `json_schema` to control output format. Here is examples of how to use structured outputs feature:

### JSON mode (json_object)

```go
openaillm := openai.New().WithModel(openai.GPT4o).WithResponseFormat(openai.ResponseFormatJSONObject).WithMaxTokens(1000)

t := thread.New().AddMessage(
thread.NewUserMessage().AddContent(
thread.NewTextContent("Give me a JSON object that describes a person"),
),
)

err := openaillm.Generate(context.Background(), t)
```

### Structured outputs (json_schema)

```go
import "github.com/sashabaranov/go-openai/jsonschema"


type Result struct {
FirstName string
LastName string
Age int
Job string
}
var result Result
schema, err := jsonschema.GenerateSchemaForType(result)
if err != nil {
panic(err)
}

openaillm := openai.New().
WithModel("gpt-4.1-nano").
WithResponseFormat(openai.ResponseFormatTypeJSONSchema).
WithResponseFormatJSONSchema(&openai.ResponseFormatJSONSchema{
Name: "Person",
Schema: schema,
Strict: true,
}).
WithMaxTokens(1000)

t := thread.New().AddMessage(
thread.NewUserMessage().AddContent(
thread.NewTextContent("Give me a JSON object that describes a person"),
),
)

err = openaillm.Generate(context.Background(), t)
```

## Private LLMs
If you want to run your model or use a private LLM provider, you have many options.
Expand Down Expand Up @@ -147,4 +201,4 @@ if err != nil {
}

fmt.Println(myThread)
```
```
47 changes: 47 additions & 0 deletions examples/llm/openai/response_format/structured-outputs/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package main

import (
"context"
"fmt"

"github.com/henomis/lingoose/llm/openai"
"github.com/henomis/lingoose/thread"
"github.com/sashabaranov/go-openai/jsonschema"
)

func main() {
type Result struct {
FirstName string
LastName string
Age int
Job string
}
var result Result
schema, err := jsonschema.GenerateSchemaForType(result)
if err != nil {
panic(err)
}

openaillm := openai.New().
WithModel("gpt-4.1-nano").
WithResponseFormat(openai.ResponseFormatTypeJSONSchema).
WithResponseFormatJSONSchema(&openai.ResponseFormatJSONSchema{
Name: "Person",
Schema: schema,
Strict: true,
}).
WithMaxTokens(1000)

t := thread.New().AddMessage(
thread.NewUserMessage().AddContent(
thread.NewTextContent("Give me a JSON object that describes a person"),
),
)

err = openaillm.Generate(context.Background(), t)
if err != nil {
panic(err)
}

fmt.Println(t)
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ require (
github.com/henomis/qdrant-go v1.1.0
github.com/henomis/restclientgo v1.2.0
github.com/invopop/jsonschema v0.7.0
github.com/sashabaranov/go-openai v1.24.0
github.com/sashabaranov/go-openai v1.38.2
golang.org/x/net v0.25.0
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
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.0 h1:4H4Pg8Bl2RH/YSnU8DYumZbuHnnkfioor/dtNlB20D4=
github.com/sashabaranov/go-openai v1.24.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/sashabaranov/go-openai v1.38.2 h1:akrssjj+6DY3lWuDwHv6cBvJ8Z+FZDM9XEaaYFt0Auo=
github.com/sashabaranov/go-openai v1.38.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
Expand Down
7 changes: 5 additions & 2 deletions llm/openai/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ type StreamCallback func(string)
type ResponseFormat = openai.ChatCompletionResponseFormatType

const (
ResponseFormatJSONObject ResponseFormat = openai.ChatCompletionResponseFormatTypeJSONObject
ResponseFormatText ResponseFormat = openai.ChatCompletionResponseFormatTypeText
ResponseFormatTypeJSONSchema ResponseFormat = openai.ChatCompletionResponseFormatTypeJSONSchema
ResponseFormatJSONObject ResponseFormat = openai.ChatCompletionResponseFormatTypeJSONObject
ResponseFormatText ResponseFormat = openai.ChatCompletionResponseFormatTypeText
)

type ResponseFormatJSONSchema = openai.ChatCompletionResponseFormatJSONSchema
33 changes: 20 additions & 13 deletions llm/openai/openai.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,19 @@ var threadRoleToOpenAIRole = map[thread.Role]string{
}

type OpenAI struct {
openAIClient *openai.Client
model Model
temperature float32
maxTokens int
stop []string
usageCallback UsageCallback
functions map[string]Function
streamCallbackFn StreamCallback
responseFormat *ResponseFormat
toolChoice *string
cache *cache.Cache
Name string
openAIClient *openai.Client
model Model
temperature float32
maxTokens int
stop []string
usageCallback UsageCallback
functions map[string]Function
streamCallbackFn StreamCallback
responseFormat *ResponseFormat
responseFormatJSONSchema *ResponseFormatJSONSchema
toolChoice *string
cache *cache.Cache
Name string
}

// WithModel sets the model to use for the OpenAI instance.
Expand Down Expand Up @@ -105,6 +106,11 @@ func (o *OpenAI) WithResponseFormat(responseFormat ResponseFormat) *OpenAI {
return o
}

func (o *OpenAI) WithResponseFormatJSONSchema(jsonSchema *ResponseFormatJSONSchema) *OpenAI {
o.responseFormatJSONSchema = jsonSchema
return o
}

// SetStop sets the stop sequences for the completion.
func (o *OpenAI) SetStop(stop []string) {
o.stop = stop
Expand Down Expand Up @@ -358,7 +364,8 @@ func (o *OpenAI) buildChatCompletionRequest(t *thread.Thread) openai.ChatComplet
var responseFormat *openai.ChatCompletionResponseFormat
if o.responseFormat != nil {
responseFormat = &openai.ChatCompletionResponseFormat{
Type: *o.responseFormat,
Type: *o.responseFormat,
JSONSchema: o.responseFormatJSONSchema,
}
}

Expand Down
47 changes: 47 additions & 0 deletions llm/openai/openai_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package openai

import (
"reflect"
"testing"

"github.com/henomis/lingoose/thread"
"github.com/sashabaranov/go-openai"
)

func TestWithResponseFormatJSONSchema(t *testing.T) {
tests := []struct {
name string
jsonSchema *ResponseFormatJSONSchema
want *ResponseFormatJSONSchema
}{
{
name: "should set JSON schema",
jsonSchema: &ResponseFormatJSONSchema{
Name: "FirstName",
Description: "First of your name",
Strict: true,
},
},
{
name: "should set nil JSON schema",
jsonSchema: nil,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := New().
WithResponseFormatJSONSchema(tt.jsonSchema).
WithResponseFormat(openai.ChatCompletionResponseFormatTypeJSONSchema)
if !reflect.DeepEqual(tt.jsonSchema, o.responseFormatJSONSchema) {
t.Fatalf("New OpenAI WithResponseFormatJSONSchema doesn't set value correctly")
}

myThread := thread.New()
request := o.buildChatCompletionRequest(myThread)
if !reflect.DeepEqual(tt.jsonSchema, request.ResponseFormat.JSONSchema) {
t.Fatalf("New OpenAI WithResponseFormatJSONSchema doesn't generate correct chat request correctly")
}
})
}
}