README.md

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