The OpenAI Go library provides convenient access to the OpenAI REST API from applications written in Go.
[!WARNING] The latest version of this package has small and limited breaking changes. See the changelog for details.
import (
"github.com/openai/openai-go/v3" // imported as openai
)
Or to pin the version:
go get -u 'github.com/openai/openai-go/v3@v3.41.0'
This library requires Go 1.22+.
The full API of this library can be found in api.md.
The primary API for interacting with OpenAI models is the Responses API. You can generate text from the model with the code below.
package main
import (
"context"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/option"
"github.com/openai/openai-go/v3/responses"
)
func main() {
ctx := context.Background()
client := openai.NewClient(
option.WithAPIKey("My API Key"), // defaults to os.LookupEnv("OPENAI_API_KEY")
)
question := "Write me a haiku about computers"
resp, err := client.Responses.New(ctx, responses.ResponseNewParams{
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String(question)},
Model: openai.ChatModelGPT5_2,
})
if err != nil {
panic(err)
}
println(resp.OutputText())
}
Multi-turn Responses
response, err := client.Responses.New(ctx, responses.ResponseNewParams{
Model: openai.ChatModelGPT5_2,
Input: responses.ResponseNewParamsInputUnion{
OfString: openai.String("What is the capital of France?"),
},
})
if err != nil {
panic(err)
}
fmt.Println("First response:", response.OutputText())
// Use PreviousResponseID to continue the conversation
response, err = client.Responses.New(ctx, responses.ResponseNewParams{
Model: openai.ChatModelGPT5_2,
PreviousResponseID: openai.String(response.ID),
Input: responses.ResponseNewParamsInputUnion{
OfString: openai.String("And what is the population of that city?"),
},
})
if err != nil {
panic(err)
}
fmt.Println("Second response:", response.OutputText())
Conversations
conv, err := client.Conversations.New(ctx, conversations.ConversationNewParams{})
if err != nil {
panic(err)
}
fmt.Println("Created conversation:", conv.ID)
response, err := client.Responses.New(ctx, responses.ResponseNewParams{
Model: openai.ChatModelGPT5_2,
Input: responses.ResponseNewParamsInputUnion{
OfString: openai.String("Hello! Remember that my favorite color is blue."),
},
Conversation: responses.ResponseNewParamsConversationUnion{
OfConversationObject: &responses.ResponseConversationParam{
ID: conv.ID,
},
},
})
if err != nil {
panic(err)
}
fmt.Println("First response:", response.OutputText())
// Continue the conversation
response, err = client.Responses.New(ctx, responses.ResponseNewParams{
Model: openai.ChatModelGPT5_2,
Input: responses.ResponseNewParamsInputUnion{
OfString: openai.String("What is my favorite color?"),
},
Conversation: responses.ResponseNewParamsConversationUnion{
OfConversationObject: &responses.ResponseConversationParam{
ID: conv.ID,
},
},
})
if err != nil {
panic(err)
}
fmt.Println("Second response:", response.OutputText())
items, err := client.Conversations.Items.List(ctx, conv.ID, conversations.ItemListParams{})
if err != nil {
panic(err)
}
fmt.Println("Conversation has", len(items.Data), "items")
Streaming responses
ctx := context.Background()
stream := client.Responses.NewStreaming(ctx, responses.ResponseNewParams{
Model: openai.ChatModelGPT5_2,
Input: responses.ResponseNewParamsInputUnion{
OfString: openai.String("Write a haiku about programming"),
},
})
for stream.Next() {
event := stream.Current()
print(event.Delta)
}
if stream.Err() != nil {
panic(stream.Err())
}
See the full streaming example
Tool calling
ctx := context.Background()
params := responses.ResponseNewParams{
Model: openai.ChatModelGPT5_2,
Input: responses.ResponseNewParamsInputUnion{
OfString: openai.String("What is the weather in New York City?"),
},
Tools: []responses.ToolUnionParam{{
OfFunction: &responses.FunctionToolParam{
Name: "get_weather",
Description: openai.String("Get weather at the given location"),
Parameters: map[string]any{
"type": "object",
"properties": map[string]any{
"location": map[string]string{
"type": "string",
},
},
"required": []string{"location"},
},
},
}},
}
response, _ := client.Responses.New(ctx, params)
// Check for function calls in the response output
for _, item := range response.Output {
if item.Type == "function_call" {
toolCall := item.AsFunctionCall()
if toolCall.Name == "get_weather" {
// Extract arguments and call your function
var args map[string]any
json.Unmarshal([]byte(toolCall.Arguments), &args)
location := args["location"].(string)
// Simulate getting weather data
weatherData := getWeather(location)
fmt.Printf("Weather in %s: %s\n", location, weatherData)
// Continue conversation with function result
response, _ = client.Responses.New(ctx, responses.ResponseNewParams{
Model: openai.ChatModelGPT5_2,
PreviousResponseID: openai.String(response.ID),
Input: responses.ResponseNewParamsInputUnion{
OfInputItemList: []responses.ResponseInputItemUnionParam{{
OfFunctionCallOutput: &responses.ResponseInputItemFunctionCallOutputParam{
CallID: toolCall.CallID,
Output: responses.ResponseInputItemFunctionCallOutputOutputUnionParam{
OfString: openai.String(weatherData),
},
},
}},
},
})
}
}
}
Structured outputs
import (
"encoding/json"
"github.com/invopop/jsonschema"
// ...
)
// A struct that will be converted to a Structured Outputs response schema
type HistoricalComputer struct {
Origin Origin `json:"origin" jsonschema_description:"The origin of the computer"`
Name string `json:"full_name" jsonschema_description:"The name of the device model"`
Legacy string `json:"legacy" jsonschema:"enum=positive,enum=neutral,enum=negative" jsonschema_description:"Its influence on the field of computing"`
NotableFacts []string `json:"notable_facts" jsonschema_description:"A few key facts about the computer"`
}
type Origin struct {
YearBuilt int64 `json:"year_of_construction" jsonschema_description:"The year it was made"`
Organization string `json:"organization" jsonschema_description:"The organization that was in charge of its development"`
}
// Structured Outputs uses a subset of JSON schema
// These flags are necessary to comply with the subset
func GenerateSchema[T any]() map[string]any {
reflector := jsonschema.Reflector{
AllowAdditionalProperties: false,
DoNotReference: true,
}
var v T
schema := reflector.Reflect(v)
data, _ := json.Marshal(schema)
var result map[string]any
json.Unmarshal(data, &result)
return result
}
// Generate the JSON schema at initialization time
var HistoricalComputerSchema = GenerateSchema[HistoricalComputer]()
func main() {
client := openai.NewClient()
ctx := context.Background()
response, err := client.Responses.New(ctx, responses.ResponseNewParams{
Model: openai.ChatModelGPT5_2,
Input: responses.ResponseNewParamsInputUnion{
OfString: openai.String("What computer ran the first neural network?"),
},
Text: responses.ResponseTextConfigParam{
Format: responses.ResponseFormatTextConfigParamOfJSONSchema(
"historical_computer",
HistoricalComputerSchema,
),
},
})
if err != nil {
panic(err)
}
// extract into a well-typed struct
var historicalComputer HistoricalComputer
_ = json.Unmarshal([]byte(response.OutputText()), &historicalComputer)
historicalComputer.Name
historicalComputer.Origin.YearBuilt
historicalComputer.Origin.Organization
for i, fact := range historicalComputer.NotableFacts {
// ...
}
}
See the full structured outputs example
The previous standard (supported indefinitely) for generating text is the Chat Completions API. You can use that API to generate text from the model with the code below.
package main
import (
"context"
"github.com/openai/openai-go/v3"
)
func main() {
client := openai.NewClient()
chatCompletion, err := client.Chat.Completions.New(context.TODO(), openai.ChatCompletionNewParams{
Messages: []openai.ChatCompletionMessageParamUnion{
openai.DeveloperMessage("You are a coding assistant that talks like a pirate."),
openai.UserMessage("How do I check if a slice is empty in Go?"),
},
Model: openai.ChatModelGPT5_2,
})
if err != nil {
panic(err)
}
println(chatCompletion.Choices[0].Message.Content)
}
The openai library uses the omitzero
semantics from the Go 1.24+ encoding/json release for request fields.
Required primitive fields (int64, string, etc.) feature the tag `api:"required"`. These
fields are always serialized, even their zero values.
Optional primitive types are wrapped in a param.Opt[T]. These fields can be set with the provided constructors, openai.String(string), openai.Int(int64), etc.
Any param.Opt[T], map, slice, struct or string enum uses the
tag `json:"...,omitzero"`. Its zero value is considered omitted.
The param.IsOmitted(any) function can confirm the presence of any omitzero field.
p := openai.ExampleParams{
ID: "id_xxx", // required property
Name: openai.String("..."), // optional property
Point: openai.Point{
X: 0, // required field will serialize as 0
Y: openai.Int(1), // optional field will serialize as 1
// ... omitted non-required fields will not be serialized
},
Origin: openai.Origin{}, // the zero value of [Origin] is considered omitted
}
To send null instead of a param.Opt[T], use param.Null[T]().
To send null instead of a struct T, use param.NullStruct[T]().
p.Name = param.Null[string]() // 'null' instead of string
p.Point = param.NullStruct[Point]() // 'null' instead of struct
param.IsNull(p.Name) // true
param.IsNull(p.Point) // true
Request structs contain a .SetExtraFields(map[string]any) method which can send non-conforming
fields in the request body. Extra fields overwrite any struct fields with a matching
key. For security reasons, only use SetExtraFields with trusted data.
To send a custom value instead of a struct, use param.Override[T](value).
// In cases where the API specifies a given type,
// but you want to send something else, use [SetExtraFields]:
p.SetExtraFields(map[string]any{
"x": 0.01, // send "x" as a float instead of int
})
// Send a number instead of an object
custom := param.Override[openai.FooParams](12)
Unions are represented as a struct with fields prefixed by "Of" for each of its variants, only one field can be non-zero. The non-zero field will be serialized.
Sub-properties of the union can be accessed via methods on the union struct. These methods return a mutable pointer to the underlying data, if present.
// Only one field can be non-zero, use param.IsOmitted() to check if a field is set
type AnimalUnionParam struct {
OfCat *Cat `json:",omitzero,inline`
OfDog *Dog `json:",omitzero,inline`
}
animal := AnimalUnionParam{
OfCat: &Cat{
Name: "Whiskers",
Owner: PersonParam{
Address: AddressParam{Street: "3333 Coyote Hill Rd", Zip: 0},
},
},
}
// Mutating a field
if address := animal.GetOwner().GetAddress(); address != nil {
address.ZipCode = 94304
}
All fields in response structs are ordinary value types (not pointers or wrappers).
Response structs also include a special JSON field containing metadata about
each property.
type Animal struct {
Name string `json:"name,nullable"`
Owners int `json:"owners"`
Age int `json:"age"`
JSON struct {
Name respjson.Field
Owner respjson.Field
Age respjson.Field
ExtraFields map[string]respjson.Field
} `json:"-"`
}
To handle optional data, use the .Valid() method on the JSON field.
.Valid() returns true if a field is not null, not present, or couldn't be marshaled.
If .Valid() is false, the corresponding field will simply be its zero value.
raw := `{"owners": 1, "name": null}`
var res Animal
json.Unmarshal([]byte(raw), &res)
// Accessing regular fields
res.Owners // 1
res.Name // ""
res.Age // 0
// Optional field checks
res.JSON.Owners.Valid() // true
res.JSON.Name.Valid() // false
res.JSON.Age.Valid() // false
// Raw JSON values
res.JSON.Owners.Raw() // "1"
res.JSON.Name.Raw() == "null" // true
res.JSON.Name.Raw() == respjson.Null // true
res.JSON.Age.Raw() == "" // true
res.JSON.Age.Raw() == respjson.Omitted // true
These .JSON structs also include an ExtraFields map containing
any properties in the json response that were not specified
in the struct. This can be useful for API features not yet
$ claude mcp add openai-go \
-- python -m otcore.mcp_server <graph>