go/index.md +0 −1100 deleted
File Deleted View Diff
1# OpenAI Go API Library
2
3<!-- x-release-please-start-version -->
4
5<a href="https://pkg.go.dev/github.com/openai/openai-go/v3"><img src="https://pkg.go.dev/badge/github.com/openai/openai-go.svg" alt="Go Reference"></a>
6
7<!-- x-release-please-end -->
8
9The OpenAI Go library provides convenient access to the [OpenAI REST API](https://platform.openai.com/docs)
10from applications written in Go.
11
12> [!WARNING]
13> The latest version of this package has small and limited breaking changes.
14> See the [changelog](CHANGELOG.md) for details.
15
16## Installation
17
18<!-- x-release-please-start-version -->
19
20```go
21import (
22 "github.com/openai/openai-go/v3" // imported as openai
23)
24```
25
26<!-- x-release-please-end -->
27
28Or to pin the version:
29
30<!-- x-release-please-start-version -->
31
32```sh
33go get -u 'github.com/openai/openai-go/v3@v3.36.0'
34```
35
36<!-- x-release-please-end -->
37
38## Requirements
39
40This library requires Go 1.22+.
41
42## Usage
43
44The full API of this library can be found in [api.md](api.md).
45
46The primary API for interacting with OpenAI models is the [Responses API](https://platform.openai.com/docs/api-reference/responses). You can generate text from the model with the code below.
47
48```go
49package main
50
51import (
52 "context"
53
54 "github.com/openai/openai-go/v3"
55 "github.com/openai/openai-go/v3/option"
56 "github.com/openai/openai-go/v3/responses"
57)
58
59func main() {
60 ctx := context.Background()
61 client := openai.NewClient(
62 option.WithAPIKey("My API Key"), // defaults to os.LookupEnv("OPENAI_API_KEY")
63 )
64
65 question := "Write me a haiku about computers"
66
67 resp, err := client.Responses.New(ctx, responses.ResponseNewParams{
68 Input: responses.ResponseNewParamsInputUnion{OfString: openai.String(question)},
69 Model: openai.ChatModelGPT5_2,
70 })
71
72 if err != nil {
73 panic(err)
74 }
75
76 println(resp.OutputText())
77}
78```
79
80<details>
81<summary>Multi-turn Responses</summary>
82
83```go
84response, err := client.Responses.New(ctx, responses.ResponseNewParams{
85 Model: openai.ChatModelGPT5_2,
86 Input: responses.ResponseNewParamsInputUnion{
87 OfString: openai.String("What is the capital of France?"),
88 },
89})
90if err != nil {
91 panic(err)
92}
93fmt.Println("First response:", response.OutputText())
94
95// Use PreviousResponseID to continue the conversation
96response, err = client.Responses.New(ctx, responses.ResponseNewParams{
97 Model: openai.ChatModelGPT5_2,
98 PreviousResponseID: openai.String(response.ID),
99 Input: responses.ResponseNewParamsInputUnion{
100 OfString: openai.String("And what is the population of that city?"),
101 },
102})
103if err != nil {
104 panic(err)
105}
106fmt.Println("Second response:", response.OutputText())
107```
108</details>
109
110<details>
111<summary>Conversations</summary>
112
113```go
114conv, err := client.Conversations.New(ctx, conversations.ConversationNewParams{})
115if err != nil {
116 panic(err)
117}
118fmt.Println("Created conversation:", conv.ID)
119
120response, err := client.Responses.New(ctx, responses.ResponseNewParams{
121 Model: openai.ChatModelGPT5_2,
122 Input: responses.ResponseNewParamsInputUnion{
123 OfString: openai.String("Hello! Remember that my favorite color is blue."),
124 },
125 Conversation: responses.ResponseNewParamsConversationUnion{
126 OfConversationObject: &responses.ResponseConversationParam{
127 ID: conv.ID,
128 },
129 },
130})
131if err != nil {
132 panic(err)
133}
134fmt.Println("First response:", response.OutputText())
135
136// Continue the conversation
137response, err = client.Responses.New(ctx, responses.ResponseNewParams{
138 Model: openai.ChatModelGPT5_2,
139 Input: responses.ResponseNewParamsInputUnion{
140 OfString: openai.String("What is my favorite color?"),
141 },
142 Conversation: responses.ResponseNewParamsConversationUnion{
143 OfConversationObject: &responses.ResponseConversationParam{
144 ID: conv.ID,
145 },
146 },
147})
148if err != nil {
149 panic(err)
150}
151fmt.Println("Second response:", response.OutputText())
152
153items, err := client.Conversations.Items.List(ctx, conv.ID, conversations.ItemListParams{})
154if err != nil {
155 panic(err)
156}
157fmt.Println("Conversation has", len(items.Data), "items")
158```
159
160</details>
161
162<details>
163<summary>Streaming responses</summary>
164
165```go
166ctx := context.Background()
167
168stream := client.Responses.NewStreaming(ctx, responses.ResponseNewParams{
169 Model: openai.ChatModelGPT5_2,
170 Input: responses.ResponseNewParamsInputUnion{
171 OfString: openai.String("Write a haiku about programming"),
172 },
173})
174
175for stream.Next() {
176 event := stream.Current()
177 print(event.Delta)
178}
179
180if stream.Err() != nil {
181 panic(stream.Err())
182}
183```
184
185> See the [full streaming example](./examples/responses-streaming/main.go)
186
187</details>
188
189<details>
190<summary>Tool calling</summary>
191
192```go
193ctx := context.Background()
194
195params := responses.ResponseNewParams{
196 Model: openai.ChatModelGPT5_2,
197 Input: responses.ResponseNewParamsInputUnion{
198 OfString: openai.String("What is the weather in New York City?"),
199 },
200 Tools: []responses.ToolUnionParam{{
201 OfFunction: &responses.FunctionToolParam{
202 Name: "get_weather",
203 Description: openai.String("Get weather at the given location"),
204 Parameters: map[string]any{
205 "type": "object",
206 "properties": map[string]any{
207 "location": map[string]string{
208 "type": "string",
209 },
210 },
211 "required": []string{"location"},
212 },
213 },
214 }},
215}
216
217response, _ := client.Responses.New(ctx, params)
218
219// Check for function calls in the response output
220for _, item := range response.Output {
221 if item.Type == "function_call" {
222 toolCall := item.AsFunctionCall()
223 if toolCall.Name == "get_weather" {
224 // Extract arguments and call your function
225 var args map[string]any
226 json.Unmarshal([]byte(toolCall.Arguments), &args)
227 location := args["location"].(string)
228
229 // Simulate getting weather data
230 weatherData := getWeather(location)
231 fmt.Printf("Weather in %s: %s\n", location, weatherData)
232
233 // Continue conversation with function result
234 response, _ = client.Responses.New(ctx, responses.ResponseNewParams{
235 Model: openai.ChatModelGPT5_2,
236 PreviousResponseID: openai.String(response.ID),
237 Input: responses.ResponseNewParamsInputUnion{
238 OfInputItemList: []responses.ResponseInputItemUnionParam{{
239 OfFunctionCallOutput: &responses.ResponseInputItemFunctionCallOutputParam{
240 CallID: toolCall.CallID,
241 Output: responses.ResponseInputItemFunctionCallOutputOutputUnionParam{
242 OfString: openai.String(weatherData),
243 },
244 },
245 }},
246 },
247 })
248 }
249 }
250}
251```
252
253</details>
254
255<details>
256<summary>Structured outputs</summary>
257
258```go
259import (
260 "encoding/json"
261 "github.com/invopop/jsonschema"
262 // ...
263)
264
265// A struct that will be converted to a Structured Outputs response schema
266type HistoricalComputer struct {
267 Origin Origin `json:"origin" jsonschema_description:"The origin of the computer"`
268 Name string `json:"full_name" jsonschema_description:"The name of the device model"`
269 Legacy string `json:"legacy" jsonschema:"enum=positive,enum=neutral,enum=negative" jsonschema_description:"Its influence on the field of computing"`
270 NotableFacts []string `json:"notable_facts" jsonschema_description:"A few key facts about the computer"`
271}
272
273type Origin struct {
274 YearBuilt int64 `json:"year_of_construction" jsonschema_description:"The year it was made"`
275 Organization string `json:"organization" jsonschema_description:"The organization that was in charge of its development"`
276}
277
278// Structured Outputs uses a subset of JSON schema
279// These flags are necessary to comply with the subset
280func GenerateSchema[T any]() map[string]any {
281 reflector := jsonschema.Reflector{
282 AllowAdditionalProperties: false,
283 DoNotReference: true,
284 }
285 var v T
286 schema := reflector.Reflect(v)
287
288 data, _ := json.Marshal(schema)
289 var result map[string]any
290 json.Unmarshal(data, &result)
291 return result
292}
293
294// Generate the JSON schema at initialization time
295var HistoricalComputerSchema = GenerateSchema[HistoricalComputer]()
296
297func main() {
298 client := openai.NewClient()
299 ctx := context.Background()
300
301 response, err := client.Responses.New(ctx, responses.ResponseNewParams{
302 Model: openai.ChatModelGPT5_2,
303 Input: responses.ResponseNewParamsInputUnion{
304 OfString: openai.String("What computer ran the first neural network?"),
305 },
306 Text: responses.ResponseTextConfigParam{
307 Format: responses.ResponseFormatTextConfigParamOfJSONSchema(
308 "historical_computer",
309 HistoricalComputerSchema,
310 ),
311 },
312 })
313 if err != nil {
314 panic(err)
315 }
316
317 // extract into a well-typed struct
318 var historicalComputer HistoricalComputer
319 _ = json.Unmarshal([]byte(response.OutputText()), &historicalComputer)
320
321 historicalComputer.Name
322 historicalComputer.Origin.YearBuilt
323 historicalComputer.Origin.Organization
324 for i, fact := range historicalComputer.NotableFacts {
325 // ...
326 }
327}
328```
329
330> See the [full structured outputs example](./examples/responses-structured-outputs/main.go)
331
332</details>
333
334### Chat Completions API
335
336The previous standard (supported indefinitely) for generating text is the [Chat Completions API](https://platform.openai.com/docs/api-reference/chat). You can use that API to generate text from the model with the code below.
337
338```go
339package main
340
341import (
342 "context"
343
344 "github.com/openai/openai-go/v3"
345)
346
347func main() {
348 client := openai.NewClient()
349
350 chatCompletion, err := client.Chat.Completions.New(context.TODO(), openai.ChatCompletionNewParams{
351 Messages: []openai.ChatCompletionMessageParamUnion{
352 openai.DeveloperMessage("You are a coding assistant that talks like a pirate."),
353 openai.UserMessage("How do I check if a slice is empty in Go?"),
354 },
355 Model: openai.ChatModelGPT5_2,
356 })
357 if err != nil {
358 panic(err)
359 }
360
361 println(chatCompletion.Choices[0].Message.Content)
362}
363```
364
365### Request fields
366
367The openai library uses the [`omitzero`](https://tip.golang.org/doc/go1.24#encodingjsonpkgencodingjson)
368semantics from the Go 1.24+ `encoding/json` release for request fields.
369
370Required primitive fields (`int64`, `string`, etc.) feature the tag <code>\`api:"required"\`</code>. These
371fields are always serialized, even their zero values.
372
373Optional 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.
374
375Any `param.Opt[T]`, map, slice, struct or string enum uses the
376tag <code>\`json:"...,omitzero"\`</code>. Its zero value is considered omitted.
377
378The `param.IsOmitted(any)` function can confirm the presence of any `omitzero` field.
379
380```go
381p := openai.ExampleParams{
382 ID: "id_xxx", // required property
383 Name: openai.String("..."), // optional property
384
385 Point: openai.Point{
386 X: 0, // required field will serialize as 0
387 Y: openai.Int(1), // optional field will serialize as 1
388 // ... omitted non-required fields will not be serialized
389 },
390
391 Origin: openai.Origin{}, // the zero value of [Origin] is considered omitted
392}
393```
394
395To send `null` instead of a `param.Opt[T]`, use `param.Null[T]()`.
396To send `null` instead of a struct `T`, use `param.NullStruct[T]()`.
397
398```go
399p.Name = param.Null[string]() // 'null' instead of string
400p.Point = param.NullStruct[Point]() // 'null' instead of struct
401
402param.IsNull(p.Name) // true
403param.IsNull(p.Point) // true
404```
405
406Request structs contain a `.SetExtraFields(map[string]any)` method which can send non-conforming
407fields in the request body. Extra fields overwrite any struct fields with a matching
408key. For security reasons, only use `SetExtraFields` with trusted data.
409
410To send a custom value instead of a struct, use `param.Override[T](value)`.
411
412```go
413// In cases where the API specifies a given type,
414// but you want to send something else, use [SetExtraFields]:
415p.SetExtraFields(map[string]any{
416 "x": 0.01, // send "x" as a float instead of int
417})
418
419// Send a number instead of an object
420custom := param.Override[openai.FooParams](12)
421```
422
423### Request unions
424
425Unions are represented as a struct with fields prefixed by "Of" for each of its variants,
426only one field can be non-zero. The non-zero field will be serialized.
427
428Sub-properties of the union can be accessed via methods on the union struct.
429These methods return a mutable pointer to the underlying data, if present.
430
431```go
432// Only one field can be non-zero, use param.IsOmitted() to check if a field is set
433type AnimalUnionParam struct {
434 OfCat *Cat `json:",omitzero,inline`
435 OfDog *Dog `json:",omitzero,inline`
436}
437
438animal := AnimalUnionParam{
439 OfCat: &Cat{
440 Name: "Whiskers",
441 Owner: PersonParam{
442 Address: AddressParam{Street: "3333 Coyote Hill Rd", Zip: 0},
443 },
444 },
445}
446
447// Mutating a field
448if address := animal.GetOwner().GetAddress(); address != nil {
449 address.ZipCode = 94304
450}
451```
452
453### Response objects
454
455All fields in response structs are ordinary value types (not pointers or wrappers).
456Response structs also include a special `JSON` field containing metadata about
457each property.
458
459```go
460type Animal struct {
461 Name string `json:"name,nullable"`
462 Owners int `json:"owners"`
463 Age int `json:"age"`
464 JSON struct {
465 Name respjson.Field
466 Owner respjson.Field
467 Age respjson.Field
468 ExtraFields map[string]respjson.Field
469 } `json:"-"`
470}
471```
472
473To handle optional data, use the `.Valid()` method on the JSON field.
474`.Valid()` returns true if a field is not `null`, not present, or couldn't be marshaled.
475
476If `.Valid()` is false, the corresponding field will simply be its zero value.
477
478```go
479raw := `{"owners": 1, "name": null}`
480
481var res Animal
482json.Unmarshal([]byte(raw), &res)
483
484// Accessing regular fields
485
486res.Owners // 1
487res.Name // ""
488res.Age // 0
489
490// Optional field checks
491
492res.JSON.Owners.Valid() // true
493res.JSON.Name.Valid() // false
494res.JSON.Age.Valid() // false
495
496// Raw JSON values
497
498res.JSON.Owners.Raw() // "1"
499res.JSON.Name.Raw() == "null" // true
500res.JSON.Name.Raw() == respjson.Null // true
501res.JSON.Age.Raw() == "" // true
502res.JSON.Age.Raw() == respjson.Omitted // true
503```
504
505These `.JSON` structs also include an `ExtraFields` map containing
506any properties in the json response that were not specified
507in the struct. This can be useful for API features not yet
508present in the SDK.
509
510```go
511body := res.JSON.ExtraFields["my_unexpected_field"].Raw()
512```
513
514### Response Unions
515
516In responses, unions are represented by a flattened struct containing all possible fields from each of the
517object variants.
518To convert it to a variant use the `.AsFooVariant()` method or the `.AsAny()` method if present.
519
520If a response value union contains primitive values, primitive fields will be alongside
521the properties but prefixed with `Of` and feature the tag `json:"...,inline"`.
522
523```go
524type AnimalUnion struct {
525 // From variants [Dog], [Cat]
526 Owner Person `json:"owner"`
527 // From variant [Dog]
528 DogBreed string `json:"dog_breed"`
529 // From variant [Cat]
530 CatBreed string `json:"cat_breed"`
531 // ...
532
533 JSON struct {
534 Owner respjson.Field
535 // ...
536 } `json:"-"`
537}
538
539// If animal variant
540if animal.Owner.Address.ZipCode == "" {
541 panic("missing zip code")
542}
543
544// Switch on the variant
545switch variant := animal.AsAny().(type) {
546case Dog:
547case Cat:
548default:
549 panic("unexpected type")
550}
551```
552
553### RequestOptions
554
555This library uses the functional options pattern. Functions defined in the
556`option` package return a `RequestOption`, which is a closure that mutates a
557`RequestConfig`. These options can be supplied to the client or at individual
558requests. For example:
559
560```go
561client := openai.NewClient(
562 // Adds a header to every request made by the client
563 option.WithHeader("X-Some-Header", "custom_header_info"),
564)
565
566client.Responses.New(context.TODO(), responses.ResponseNewParams{...},
567 // Override the header
568 option.WithHeader("X-Some-Header", "some_other_custom_header_info"),
569 // Add an undocumented field to the request body, using sjson syntax
570 option.WithJSONSet("some.json.path", map[string]string{"my": "object"}),
571)
572```
573
574The request option `option.WithDebugLog(nil)` may be helpful while debugging.
575
576See the [full list of request options](https://pkg.go.dev/github.com/openai/openai-go/option).
577
578### Pagination
579
580This library provides some conveniences for working with paginated list endpoints.
581
582You can use `.ListAutoPaging()` methods to iterate through items across all pages:
583
584```go
585iter := client.FineTuning.Jobs.ListAutoPaging(context.TODO(), openai.FineTuningJobListParams{
586 Limit: openai.Int(20),
587})
588// Automatically fetches more pages as needed.
589for iter.Next() {
590 fineTuningJob := iter.Current()
591 fmt.Printf("%+v\n", fineTuningJob)
592}
593if err := iter.Err(); err != nil {
594 panic(err.Error())
595}
596```
597
598Or you can use simple `.List()` methods to fetch a single page and receive a standard response object
599with additional helper methods like `.GetNextPage()`, e.g.:
600
601```go
602page, err := client.FineTuning.Jobs.List(context.TODO(), openai.FineTuningJobListParams{
603 Limit: openai.Int(20),
604})
605for page != nil {
606 for _, job := range page.Data {
607 fmt.Printf("%+v\n", job)
608 }
609 page, err = page.GetNextPage()
610}
611if err != nil {
612 panic(err.Error())
613}
614```
615
616### Errors
617
618When the API returns a non-success status code, we return an error with type
619`*openai.Error`. This contains the `StatusCode`, `*http.Request`, and
620`*http.Response` values of the request, as well as the JSON of the error body
621(much like other response objects in the SDK).
622
623To handle errors, we recommend that you use the `errors.As` pattern:
624
625```go
626_, err := client.FineTuning.Jobs.New(context.TODO(), openai.FineTuningJobNewParams{
627 Model: openai.FineTuningJobNewParamsModel("gpt-4o"),
628 TrainingFile: "file-abc123",
629})
630if err != nil {
631 var apierr *openai.Error
632 if errors.As(err, &apierr) {
633 println(string(apierr.DumpRequest(true))) // Prints the serialized HTTP request
634 println(string(apierr.DumpResponse(true))) // Prints the serialized HTTP response
635 }
636 panic(err.Error()) // GET "/fine_tuning/jobs": 400 Bad Request { ... }
637}
638```
639
640When other errors occur, they are returned unwrapped; for example,
641if HTTP transport fails, you might receive `*url.Error` wrapping `*net.OpError`.
642
643### Timeouts
644
645Requests do not time out by default; use context to configure a timeout for a request lifecycle.
646
647Note that if a request is [retried](#retries), the context timeout does not start over.
648To set a per-retry timeout, use `option.WithRequestTimeout()`.
649
650```go
651// This sets the timeout for the request, including all the retries.
652ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
653defer cancel()
654client.Responses.New(
655 ctx,
656 responses.ResponseNewParams{
657 Model: openai.ChatModelGPT5_2,
658 Input: responses.ResponseNewParamsInputUnion{
659 OfString: openai.String("How can I list all files in a directory using Python?"),
660 },
661 },
662 // This sets the per-retry timeout
663 option.WithRequestTimeout(20*time.Second),
664)
665```
666
667### File uploads
668
669Request parameters that correspond to file uploads in multipart requests are typed as
670`io.Reader`. The contents of the `io.Reader` will by default be sent as a multipart form
671part with the file name of "anonymous_file" and content-type of "application/octet-stream".
672
673The file name and content-type can be customized by implementing `Name() string` or `ContentType()
674string` on the run-time type of `io.Reader`. Note that `os.File` implements `Name() string`, so a
675file returned by `os.Open` will be sent with the file name on disk.
676
677We also provide a helper `openai.File(reader io.Reader, filename string, contentType string)`
678which can be used to wrap any `io.Reader` with the appropriate file name and content type.
679
680```go
681// A file from the file system
682file, err := os.Open("input.jsonl")
683openai.FileNewParams{
684 File: file,
685 Purpose: openai.FilePurposeFineTune,
686}
687
688// A file from a string
689openai.FileNewParams{
690 File: strings.NewReader("my file contents"),
691 Purpose: openai.FilePurposeFineTune,
692}
693
694// With a custom filename and contentType
695openai.FileNewParams{
696 File: openai.File(strings.NewReader(`{"hello": "foo"}`), "file.go", "application/json"),
697 Purpose: openai.FilePurposeFineTune,
698}
699```
700
701## Webhook Verification
702
703Verifying webhook signatures is _optional but encouraged_.
704
705For more information about webhooks, see [the API docs](https://platform.openai.com/docs/guides/webhooks).
706
707### Parsing webhook payloads
708
709For most use cases, you will likely want to verify the webhook and parse the payload at the same time. To achieve this, we provide the method `client.Webhooks.Unwrap()`, which parses a webhook request and verifies that it was sent by OpenAI. This method will return an error if the signature is invalid.
710
711Note that the `body` parameter should be the raw JSON bytes sent from the server (do not parse it first). The `Unwrap()` method will parse this JSON for you into an event object after verifying the webhook was sent from OpenAI.
712
713```go
714package main
715
716import (
717 "io"
718 "log"
719 "net/http"
720 "os"
721
722 "github.com/gin-gonic/gin"
723 "github.com/openai/openai-go/v3"
724 "github.com/openai/openai-go/v3/option"
725 "github.com/openai/openai-go/v3/webhooks"
726)
727
728func main() {
729 client := openai.NewClient(
730 option.WithWebhookSecret(os.Getenv("OPENAI_WEBHOOK_SECRET")), // env var used by default; explicit here.
731 )
732
733 r := gin.Default()
734
735 r.POST("/webhook", func(c *gin.Context) {
736 body, err := io.ReadAll(c.Request.Body)
737 if err != nil {
738 c.JSON(http.StatusInternalServerError, gin.H{"error": "Error reading request body"})
739 return
740 }
741 defer c.Request.Body.Close()
742
743 webhookEvent, err := client.Webhooks.Unwrap(body, c.Request.Header)
744 if err != nil {
745 log.Printf("Invalid webhook signature: %v", err)
746 c.JSON(http.StatusBadRequest, gin.H{"error": "invalid signature"})
747 return
748 }
749
750 switch event := webhookEvent.AsAny().(type) {
751 case webhooks.ResponseCompletedWebhookEvent:
752 log.Printf("Response completed: %+v", event.Data)
753 case webhooks.ResponseFailedWebhookEvent:
754 log.Printf("Response failed: %+v", event.Data)
755 default:
756 log.Printf("Unhandled event type: %T", event)
757 }
758
759 c.JSON(http.StatusOK, gin.H{"message": "ok"})
760 })
761
762 r.Run(":8000")
763}
764```
765
766### Verifying webhook payloads directly
767
768In some cases, you may want to verify the webhook separately from parsing the payload. If you prefer to handle these steps separately, we provide the method `client.Webhooks.VerifySignature()` to _only verify_ the signature of a webhook request. Like `Unwrap()`, this method will return an error if the signature is invalid.
769
770Note that the `body` parameter should be the raw JSON bytes sent from the server (do not parse it first). You will then need to parse the body after verifying the signature.
771
772```go
773package main
774
775import (
776 "encoding/json"
777 "io"
778 "log"
779 "net/http"
780 "os"
781
782 "github.com/gin-gonic/gin"
783 "github.com/openai/openai-go/v3"
784 "github.com/openai/openai-go/v3/option"
785)
786
787func main() {
788 client := openai.NewClient(
789 option.WithWebhookSecret(os.Getenv("OPENAI_WEBHOOK_SECRET")), // env var used by default; explicit here.
790 )
791
792 r := gin.Default()
793
794 r.POST("/webhook", func(c *gin.Context) {
795 body, err := io.ReadAll(c.Request.Body)
796 if err != nil {
797 c.JSON(http.StatusInternalServerError, gin.H{"error": "Error reading request body"})
798 return
799 }
800 defer c.Request.Body.Close()
801
802 err = client.Webhooks.VerifySignature(body, c.Request.Header)
803 if err != nil {
804 log.Printf("Invalid webhook signature: %v", err)
805 c.JSON(http.StatusBadRequest, gin.H{"error": "invalid signature"})
806 return
807 }
808
809 c.JSON(http.StatusOK, gin.H{"message": "ok"})
810 })
811
812 r.Run(":8000")
813}
814```
815
816### Retries
817
818Certain errors will be automatically retried 2 times by default, with a short exponential backoff.
819We retry by default all connection errors, 408 Request Timeout, 409 Conflict, 429 Rate Limit,
820and >=500 Internal errors.
821
822You can use the `WithMaxRetries` option to configure or disable this:
823
824```go
825// Configure the default for all requests:
826client := openai.NewClient(
827 option.WithMaxRetries(0), // default is 2
828)
829
830// Override per-request:
831client.Responses.New(
832 context.TODO(),
833 responses.ResponseNewParams{
834 Model: openai.ChatModelGPT5_2,
835 Input: responses.ResponseNewParamsInputUnion{
836 OfString: openai.String("How can I get the name of the current day in JavaScript?"),
837 },
838 },
839 option.WithMaxRetries(5),
840)
841```
842
843### Accessing raw response data (e.g. response headers)
844
845You can access the raw HTTP response data by using the `option.WithResponseInto()` request option. This is useful when
846you need to examine response headers, status codes, or other details.
847
848```go
849// Create a variable to store the HTTP response
850var httpResp *http.Response
851response, err := client.Responses.New(
852 context.TODO(),
853 responses.ResponseNewParams{
854 Model: openai.ChatModelGPT5_2,
855 Input: responses.ResponseNewParamsInputUnion{
856 OfString: openai.String("Say this is a test"),
857 },
858 },
859 option.WithResponseInto(&httpResp),
860)
861if err != nil {
862 // handle error
863}
864fmt.Printf("%+v\n", response)
865
866fmt.Printf("Status Code: %d\n", httpResp.StatusCode)
867fmt.Printf("Headers: %+#v\n", httpResp.Header)
868```
869
870### Making custom/undocumented requests
871
872This library is typed for convenient access to the documented API. If you need to access undocumented
873endpoints, params, or response properties, the library can still be used.
874
875#### Undocumented endpoints
876
877To make requests to undocumented endpoints, you can use `client.Get`, `client.Post`, and other HTTP verbs.
878`RequestOptions` on the client, such as retries, will be respected when making these requests.
879
880```go
881var (
882 // params can be an io.Reader, a []byte, an encoding/json serializable object,
883 // or a "…Params" struct defined in this library.
884 params map[string]any
885
886 // result can be an []byte, *http.Response, a encoding/json deserializable object,
887 // or a model defined in this library.
888 result *http.Response
889)
890err := client.Post(context.Background(), "/unspecified", params, &result)
891if err != nil {
892 …
893}
894```
895
896#### Undocumented request params
897
898To make requests using undocumented parameters, you may use either the `option.WithQuerySet()`
899or the `option.WithJSONSet()` methods.
900
901```go
902params := FooNewParams{
903 ID: "id_xxxx",
904 Data: FooNewParamsData{
905 FirstName: openai.String("John"),
906 },
907}
908client.Foo.New(context.Background(), params, option.WithJSONSet("data.last_name", "Doe"))
909```
910
911#### Undocumented response properties
912
913To access undocumented response properties, you may either access the raw JSON of the response as a string
914with `result.JSON.RawJSON()`, or get the raw JSON of a particular field on the result with
915`result.JSON.Foo.Raw()`.
916
917Any fields that are not present on the response struct will be saved and can be accessed by `result.JSON.ExtraFields()` which returns the extra fields as a `map[string]Field`.
918
919### Middleware
920
921We provide `option.WithMiddleware` which applies the given
922middleware to requests.
923
924```go
925func Logger(req *http.Request, next option.MiddlewareNext) (res *http.Response, err error) {
926 // Before the request
927 start := time.Now()
928 LogReq(req)
929
930 // Forward the request to the next handler
931 res, err = next(req)
932
933 // Handle stuff after the request
934 end := time.Now()
935 LogRes(res, err, start - end)
936
937 return res, err
938}
939
940client := openai.NewClient(
941 option.WithMiddleware(Logger),
942)
943```
944
945When multiple middlewares are provided as variadic arguments, the middlewares
946are applied left to right. If `option.WithMiddleware` is given
947multiple times, for example first in the client then the method, the
948middleware in the client will run first and the middleware given in the method
949will run next.
950
951You may also replace the default `http.Client` with
952`option.WithHTTPClient(client)`. Only one http client is
953accepted (this overwrites any previous client) and receives requests after any
954middleware has been applied.
955
956## Workload Identity Authentication
957
958For cloud workloads (Kubernetes, Azure, Google Cloud Platform), you can use workload identity authentication instead of API keys. This provides short-lived tokens that are automatically refreshed.
959
960### Kubernetes
961
962```go
963import (
964 "github.com/openai/openai-go/v3"
965 "github.com/openai/openai-go/v3/auth"
966 "github.com/openai/openai-go/v3/option"
967)
968
969client := openai.NewClient(
970 option.WithWorkloadIdentity(auth.WorkloadIdentity{
971 IdentityProviderID: "idp-123",
972 ServiceAccountID: "sa-456",
973 Provider: auth.K8sServiceAccountTokenProvider(""),
974 }),
975)
976```
977
978### Azure Managed Identity
979
980```go
981client := openai.NewClient(
982 option.WithWorkloadIdentity(auth.WorkloadIdentity{
983 IdentityProviderID: "idp-123",
984 ServiceAccountID: "sa-456",
985 Provider: auth.AzureManagedIdentityTokenProvider(nil),
986 }),
987)
988```
989
990### Google Cloud Compute Engine
991
992```go
993client := openai.NewClient(
994 option.WithWorkloadIdentity(auth.WorkloadIdentity{
995 IdentityProviderID: "idp-123",
996 ServiceAccountID: "sa-456",
997 Provider: auth.GCPIDTokenProvider(nil),
998 }),
999)
1000```
1001
1002### Custom Subject Token Provider
1003
1004You can implement your own subject token provider:
1005
1006```go
1007import (
1008 "context"
1009
1010 "github.com/openai/openai-go/v3"
1011 "github.com/openai/openai-go/v3/auth"
1012 "github.com/openai/openai-go/v3/option"
1013)
1014
1015type customTokenProvider struct{}
1016
1017func (p *customTokenProvider) TokenType() auth.SubjectTokenType {
1018 return auth.SubjectTokenTypeJWT
1019}
1020
1021func (p *customTokenProvider) GetToken(ctx context.Context, httpClient auth.HTTPDoer) (string, error) {
1022 return "your-token", nil
1023}
1024
1025client := openai.NewClient(
1026 option.WithWorkloadIdentity(auth.WorkloadIdentity{
1027 IdentityProviderID: "idp-123",
1028 ServiceAccountID: "sa-456",
1029 Provider: &customTokenProvider{},
1030 }),
1031)
1032```
1033
1034### Customizing Refresh Buffer
1035
1036By default, tokens are refreshed 20 minutes (1200 seconds) before expiry. You can customize this:
1037
1038```go
1039client := openai.NewClient(
1040 option.WithWorkloadIdentity(auth.WorkloadIdentity{
1041 IdentityProviderID: "idp-123",
1042 ServiceAccountID: "sa-456",
1043 Provider: auth.K8sServiceAccountTokenProvider(""),
1044 RefreshBufferSeconds: 600,
1045 }),
1046)
1047```
1048
1049## Microsoft Azure OpenAI
1050
1051To use this library with [Azure OpenAI]https://learn.microsoft.com/azure/ai-services/openai/overview),
1052use the option.RequestOption functions in the `azure` package.
1053
1054```go
1055package main
1056
1057import (
1058 "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
1059 "github.com/openai/openai-go/v3"
1060 "github.com/openai/openai-go/v3/azure"
1061)
1062
1063func main() {
1064 const azureOpenAIEndpoint = "https://<azure-openai-resource>.openai.azure.com"
1065
1066 // The latest API versions, including previews, can be found here:
1067 // https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versionng
1068 const azureOpenAIAPIVersion = "2024-06-01"
1069
1070 tokenCredential, err := azidentity.NewDefaultAzureCredential(nil)
1071
1072 if err != nil {
1073 fmt.Printf("Failed to create the DefaultAzureCredential: %s", err)
1074 os.Exit(1)
1075 }
1076
1077 client := openai.NewClient(
1078 azure.WithEndpoint(azureOpenAIEndpoint, azureOpenAIAPIVersion),
1079
1080 // Choose between authenticating using a TokenCredential or an API Key
1081 azure.WithTokenCredential(tokenCredential),
1082 // or azure.WithAPIKey(azureOpenAIAPIKey),
1083 )
1084}
1085```
1086
1087## Semantic versioning
1088
1089This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:
1090
10911. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_
10922. Changes that we do not expect to impact the vast majority of users in practice.
1093
1094We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.
1095
1096We are keen for your feedback; please open an [issue](https://www.github.com/openai/openai-go/issues) with questions, bugs, or suggestions.
1097
1098## Contributing
1099
1100See [the contributing documentation](./CONTRIBUTING.md).