openai/openai-go

GitHub: openai/openai-go

OpenAI Go 官方 SDK,用于在 Go 应用程序中通过 REST API 访问 GPT 模型,支持对话、流式响应及函数调用等最新功能。

Stars: 3141 | Forks: 302

# OpenAI Go API 库 Go Reference OpenAI Go 库提供了便捷的方式来从 Go 编写的应用程序中访问 [OpenAI REST API](https://platform.openai.com/docs)。 ## 安装 ``` import ( "github.com/openai/openai-go/v3" // imported as openai ) ``` 或者固定版本: ``` go get -u 'github.com/openai/openai-go/v3@v3.31.0' ``` ## 环境要求 此库需要 Go 1.22 或更高版本。 ## 用法 此库的完整 API 可以在 [api.md](api.md) 中找到。 与 OpenAI 模型交互的主要 API 是 [Responses API](https://platform.openai.com/docs/api-reference/responses)。你可以使用下面的代码从模型生成文本。 ``` 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()) } ```
多轮 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()) ```
对话 ``` 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") ```
流式响应 ``` 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()) } ```
工具调用 ``` 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), }, }, }}, }, }) } } } ```
结构化输出 ``` 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 { // ... } } ```
### Chat Completions API 用于生成文本的先前标准(无限期支持)是 [Chat Completions API](https://platform.openai.com/docs/api-reference/chat)。你可以使用该 API 通过下面的代码从模型生成文本。 ``` 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) } ``` ### 请求字段 openai 库对请求字段使用 Go 1.24+ `encoding/json` 版本中的 [`omitzero`](https://tip.golang.org/doc/go1.24#encodingjsonpkgencodingjson) 语义。 必需的原始字段(`int64`、`string` 等)带有标签 \`api:"required"\`。这些字段总是会被序列化,即使是它们的零值。 可选的原始类型被包装在 `param.Opt[T]` 中。这些字段可以使用提供的构造函数设置,例如 `openai.String(string)`、`openai.Int(int64)` 等。 任何 `param.Opt[T]`、map、slice、struct 或字符串枚举都使用标签 \`json:"...,omitzero"\`。其零值被视为省略。 `param.IsOmitted(any)` 函数可以确认任何 `omitzero` 字段是否存在。 ``` 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 } ``` 要发送 `null` 而不是 `param.Opt[T]`,请使用 `param.Null[T]()`。 要发送 `null` 而不是结构体 `T`,请使用 `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 ``` 请求结构体包含一个 `.SetExtraFields(map[string]any)` 方法,可以在请求体中发送不符合规范的字段。额外字段会覆盖任何具有匹配键的结构体字段。出于安全原因,请仅对受信任的数据使用 `SetExtraFields`。 要发送自定义值而不是结构体,请使用 `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) ``` ### 请求联合 联合 表示为一个结构体,其每个变体的字段都以 "Of" 为前缀,只有一个字段可以是非零值。非零字段将被序列化。 联合的子属性可以通过联合结构体上的方法访问。 这些方法返回指向底层数据的可变指针(如果存在)。 ``` // 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 } ``` ### 响应对象 响应结构体中的所有字段都是普通的值类型(不是指针或包装器)。 响应结构体还包括一个特殊的 `JSON` 字段,其中包含关于每个属性的元数据。 ``` 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:"-"` } ``` 要处理可选数据,请使用 JSON 字段上的 `.Valid()` 方法。 如果字段不为 `null`、不存在或无法反序列化,`.Valid()` 将返回 true。 如果 `.Valid()` 为 false,相应的字段将仅仅是其零值。 ``` 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 ``` 这些 `.JSON` 结构体还包括一个 `ExtraFields` map,其中包含 json 响应中未在结构体中指定的任何属性。这对于 SDK 中尚未出现的 API 功能非常有用。 ``` body := res.JSON.ExtraFields["my_unexpected_field"].Raw() ``` ### 响应联合 在响应中,联合 由一个扁平的结构体表示,其中包含来自对象变体的每个可能字段。 要将其转换为变体,请使用 `.AsFooVariant()` 方法,如果存在,也可以使用 `.AsAny()` 方法。 如果响应值联合包含原始值,原始字段将与属性并列,但以 `Of` 为前缀并带有标签 `json:"...,inline"`。 ``` type AnimalUnion struct { // From variants [Dog], [Cat] Owner Person `json:"owner"` // From variant [Dog] DogBreed string `json:"dog_breed"` // From variant [Cat] CatBreed string `json:"cat_breed"` // ... JSON struct { Owner respjson.Field // ... } `json:"-"` } // If animal variant if animal.Owner.Address.ZipCode == "" { panic("missing zip code") } // Switch on the variant switch variant := animal.AsAny().(type) { case Dog: case Cat: default: panic("unexpected type") } ``` ### RequestOptions 此库使用函数式选项模式。在 `option` 包中定义的函数返回一个 `RequestOption`,它是一个改变 `RequestConfig` 的闭包。这些选项可以提供给客户端或单个请求。例如: ``` client := openai.NewClient( // Adds a header to every request made by the client option.WithHeader("X-Some-Header", "custom_header_info"), ) client.Responses.New(context.TODO(), responses.ResponseNewParams{...}, // Override the header option.WithHeader("X-Some-Header", "some_other_custom_header_info"), // Add an undocumented field to the request body, using sjson syntax option.WithJSONSet("some.json.path", map[string]string{"my": "object"}), ) ``` 在调试时,请求选项 `option.WithDebugLog(nil)` 可能会有所帮助。 请参阅[请求选项的完整列表](https://pkg.go.dev/github.com/openai/openai-go/option)。 ### 分页 此库为处理分页列表端点 提供了一些便利。 你可以使用 `.ListAutoPaging()` 方法跨所有页面遍历项目: ``` iter := client.FineTuning.Jobs.ListAutoPaging(context.TODO(), openai.FineTuningJobListParams{ Limit: openai.Int(20), }) // Automatically fetches more pages as needed. for iter.Next() { fineTuningJob := iter.Current() fmt.Printf("%+v\n", fineTuningJob) } if err := iter.Err(); err != nil { panic(err.Error()) } ``` 或者你可以使用简单的 `.List()` 方法来获取单个页面并接收一个标准响应对象,其中包含额外的辅助方法,如 `.GetNextPage()`,例如: ``` page, err := client.FineTuning.Jobs.List(context.TODO(), openai.FineTuningJobListParams{ Limit: openai.Int(20), }) for page != nil { for _, job := range page.Data { fmt.Printf("%+v\n", job) } page, err = page.GetNextPage() } if err != nil { panic(err.Error()) } ``` ### 错误 当 API 返回非成功状态代码时,我们返回类型为 `*openai.Error` 的错误。这包含请求的 `StatusCode`、`*http.Request` 和 `*http.Response` 值,以及错误正文的 JSON(就像 SDK 中的其他响应对象一样)。 要处理错误,我们建议你使用 `errors.As` 模式: ``` _, err := client.FineTuning.Jobs.New(context.TODO(), openai.FineTuningJobNewParams{ Model: openai.FineTuningJobNewParamsModel("gpt-4o"), TrainingFile: "file-abc123", }) if err != nil { var apierr *openai.Error if errors.As(err, &apierr) { println(string(apierr.DumpRequest(true))) // Prints the serialized HTTP request println(string(apierr.DumpResponse(true))) // Prints the serialized HTTP response } panic(err.Error()) // GET "/fine_tuning/jobs": 400 Bad Request { ... } } ``` 当发生其他错误时,它们将被原样返回;例如,如果 HTTP 传输失败,你可能会收到包装了 `*net.OpError` 的 `*url.Error`。 ### 超时 请求默认不超时;使用 context 为请求生命周期配置超时。 请注意,如果请求被[重试](#retries),context 超时不会重新开始。 要设置每次重试的超时时间,请使用 `option.WithRequestTimeout()`。 ``` // This sets the timeout for the request, including all the retries. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() client.Responses.New( ctx, responses.ResponseNewParams{ Model: openai.ChatModelGPT5_2, Input: responses.ResponseNewParamsInputUnion{ OfString: openai.String("How can I list all files in a directory using Python?"), }, }, // This sets the per-retry timeout option.WithRequestTimeout(20*time.Second), ) ``` ### 文件上传 对应于 multipart 请求中的文件上传的请求参数类型为 `io.Reader`。`io.Reader` 的内容默认将作为 multipart 表单部分发送,文件名为 "anonymous_file",内容类型为 "application/octet-stream"。 可以通过在 `io.Reader` 的运行时类型上实现 `Name() string` 或 `ContentType() string` 来自定义文件名和内容类型。请注意,`os.File` 实现了 `Name() string`,因此由 `os.Open` 返回的文件将使用磁盘上的文件名发送。 我们还提供了一个辅助函数 `openai.File(reader io.Reader, filename string, contentType string)`,它可以用适当的文件名和内容类型包装任何 `io.Reader`。 ``` // A file from the file system file, err := os.Open("input.jsonl") openai.FileNewParams{ File: file, Purpose: openai.FilePurposeFineTune, } // A file from a string openai.FileNewParams{ File: strings.NewReader("my file contents"), Purpose: openai.FilePurposeFineTune, } // With a custom filename and contentType openai.FileNewParams{ File: openai.File(strings.NewReader(`{"hello": "foo"}`), "file.go", "application/json"), Purpose: openai.FilePurposeFineTune, } ``` ## Webhook 验证 验证 webhook 签名是_可选的,但建议使用_。 有关 webhooks 的更多信息,请参阅 [API 文档](https://platform.openai.com/docs/guides/webhooks)。 ### 解析 webhook 载荷 对于大多数用例,你可能希望同时验证 webhook 并解析载荷。为此,我们提供了 `client.Webhooks.Unwrap()` 方法,该方法解析 webhook 请求并验证它是否由 OpenAI 发送。如果签名无效,此方法将返回错误。 请注意,`body` 参数应该是从服务器发送的原始 JSON 字节(不要先解析它)。`Unwrap()` 方法将在验证 webhook 是由 OpenAI 发送后,为你将此 JSON 解析为事件对象。 ``` package main import ( "io" "log" "net/http" "os" "github.com/gin-gonic/gin" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/option" "github.com/openai/openai-go/v3/webhooks" ) func main() { client := openai.NewClient( option.WithWebhookSecret(os.Getenv("OPENAI_WEBHOOK_SECRET")), // env var used by default; explicit here. ) r := gin.Default() r.POST("/webhook", func(c *gin.Context) { body, err := io.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Error reading request body"}) return } defer c.Request.Body.Close() webhookEvent, err := client.Webhooks.Unwrap(body, c.Request.Header) if err != nil { log.Printf("Invalid webhook signature: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid signature"}) return } switch event := webhookEvent.AsAny().(type) { case webhooks.ResponseCompletedWebhookEvent: log.Printf("Response completed: %+v", event.Data) case webhooks.ResponseFailedWebhookEvent: log.Printf("Response failed: %+v", event.Data) default: log.Printf("Unhandled event type: %T", event) } c.JSON(http.StatusOK, gin.H{"message": "ok"}) }) r.Run(":8000") } ``` ### 直接验证 webhook 载荷 在某些情况下,你可能希望单独于解析载荷来验证 webhook。如果你希望单独处理这些步骤,我们提供了 `client.Webhooks.VerifySignature()` 方法来_仅验证_ webhook 请求的签名。与 `Unwrap()` 一样,如果签名无效,此方法将返回错误。 请注意,`body` 参数应该是从服务器发送的原始 JSON 字节(不要先解析它)。然后,你需要在验证签名后解析正文。 ``` package main import ( "encoding/json" "io" "log" "net/http" "os" "github.com/gin-gonic/gin" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/option" ) func main() { client := openai.NewClient( option.WithWebhookSecret(os.Getenv("OPENAI_WEBHOOK_SECRET")), // env var used by default; explicit here. ) r := gin.Default() r.POST("/webhook", func(c *gin.Context) { body, err := io.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Error reading request body"}) return } defer c.Request.Body.Close() err = client.Webhooks.VerifySignature(body, c.Request.Header) if err != nil { log.Printf("Invalid webhook signature: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid signature"}) return } c.JSON(http.StatusOK, gin.H{"message": "ok"}) }) r.Run(":8000") } ``` ### 重试 某些错误默认将自动重试 2 次,具有短暂的指数退避。 我们默认重试所有连接错误、408 Request Timeout、409 Conflict、429 Rate Limit 和 >=500 Internal 错误。 你可以使用 `WithMaxRetries` 选项来配置或禁用此功能: ``` // Configure the default for all requests: client := openai.NewClient( option.WithMaxRetries(0), // default is 2 ) // Override per-request: client.Responses.New( context.TODO(), responses.ResponseNewParams{ Model: openai.ChatModelGPT5_2, Input: responses.ResponseNewParamsInputUnion{ OfString: openai.String("How can I get the name of the current day in JavaScript?"), }, }, option.WithMaxRetries(5), ) ``` ### 访问原始响应数据(例如响应头) 你可以使用 `option.WithResponseInto()` 请求选项访问原始 HTTP 响应数据。当你需要检查响应头、状态码或其他详细信息时,这非常有用。 ``` // Create a variable to store the HTTP response var httpResp *http.Response response, err := client.Responses.New( context.TODO(), responses.ResponseNewParams{ Model: openai.ChatModelGPT5_2, Input: responses.ResponseNewParamsInputUnion{ OfString: openai.String("Say this is a test"), }, }, option.WithResponseInto(&httpResp), ) if err != nil { // handle error } fmt.Printf("%+v\n", response) fmt.Printf("Status Code: %d\n", httpResp.StatusCode) fmt.Printf("Headers: %+#v\n", httpResp.Header) ``` ### 发起自定义/未记录的请求 此库已进行类型化,以便方便地访问已记录的 API。如果你需要访问未记录的端点、参数或响应属性,仍然可以使用此库。 #### 未记录的端点 要向未记录的端点发起请求,你可以使用 `client.Get`、`client.Post` 和其他 HTTP 动词。客户端上的 `RequestOptions`(例如重试)在发起这些请求时将被遵守。 ``` var ( // params can be an io.Reader, a []byte, an encoding/json serializable object, // or a "…Params" struct defined in this library. params map[string]any // result can be an []byte, *http.Response, a encoding/json deserializable object, // or a model defined in this library. result *http.Response ) err := client.Post(context.Background(), "/unspecified", params, &result) if err != nil { … } ``` #### 未记录的请求参数 要使用未记录的参数发起请求,你可以使用 `option.WithQuerySet()` 或 `option.WithJSONSet()` 方法。 ``` params := FooNewParams{ ID: "id_xxxx", Data: FooNewParamsData{ FirstName: openai.String("John"), }, } client.Foo.New(context.Background(), params, option.WithJSONSet("data.last_name", "Doe")) ``` #### 未记录的响应属性 要访问未记录的响应属性,你可以使用 `result.JSON.RawJSON()` 以字符串形式访问响应的原始 JSON,或者使用 `result.JSON.Foo.Raw()` 获取结果上特定字段的原始 JSON。 任何响应结构体上不存在的字段都将被保存,并且可以通过 `result.JSON.ExtraFields()` 访问,该方法返回一个 `map[string]Field` 类型的额外字段。 ### 中间件 我们提供了 `option.WithMiddleware`,它将给定的中间件应用于请求。 ``` func Logger(req *http.Request, next option.MiddlewareNext) (res *http.Response, err error) { // Before the request start := time.Now() LogReq(req) // Forward the request to the next handler res, err = next(req) // Handle stuff after the request end := time.Now() LogRes(res, err, start - end) return res, err } client := openai.NewClient( option.WithMiddleware(Logger), ) ``` 当提供多个中间件作为可变参数时,中间件将按从左到右的顺序应用。如果多次给定 `option.WithMiddleware`,例如先在客户端中然后在方法中,则客户端中的中间件将先运行,方法中提供的中间件将在其后运行。 你也可以使用 `option.WithHTTPClient(client)` 替换默认的 `http.Client`。仅接受一个 http 客户端(这将覆盖任何先前的客户端),并在应用任何中间件后接收请求。 ## 工作负载身份认证 对于云工作负载(Kubernetes、Azure、Google Cloud Platform),你可以使用工作负载身份认证而不是 API 密钥。这提供自动刷新的短期令牌。 ### Kubernetes ``` import ( "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/auth" "github.com/openai/openai-go/v3/option" ) client := openai.NewClient( option.WithWorkloadIdentity(auth.WorkloadIdentity{ ClientID: "your-client-id", IdentityProviderID: "idp-123", ServiceAccountID: "sa-456", Provider: auth.K8sServiceAccountTokenProvider(""), }), ) ``` ### Azure 托管标识 ``` client := openai.NewClient( option.WithWorkloadIdentity(auth.WorkloadIdentity{ ClientID: "your-client-id", IdentityProviderID: "idp-123", ServiceAccountID: "sa-456", Provider: auth.AzureManagedIdentityTokenProvider(nil), }), ) ``` ### Google Cloud Compute Engine ``` client := openai.NewClient( option.WithWorkloadIdentity(auth.WorkloadIdentity{ ClientID: "your-client-id", IdentityProviderID: "idp-123", ServiceAccountID: "sa-456", Provider: auth.GCPIDTokenProvider(nil), }), ) ``` ### 自定义 Subject Token 提供者 你可以实现自己的 subject token 提供者: ``` import ( "context" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/auth" "github.com/openai/openai-go/v3/option" ) type customTokenProvider struct{} func (p *customTokenProvider) TokenType() auth.SubjectTokenType { return auth.SubjectTokenTypeJWT } func (p *customTokenProvider) GetToken(ctx context.Context, httpClient auth.HTTPDoer) (string, error) { return "your-token", nil } client := openai.NewClient( option.WithWorkloadIdentity(auth.WorkloadIdentity{ ClientID: "your-client-id", IdentityProviderID: "idp-123", ServiceAccountID: "sa-456", Provider: &customTokenProvider{}, }), ) ``` ### 自定义刷新缓冲区 默认情况下,令牌会在过期前 20 分钟(1200 秒)刷新。你可以自定义此设置: ``` client := openai.NewClient( option.WithWorkloadIdentity(auth.WorkloadIdentity{ ClientID: "your-client-id", IdentityProviderID: "idp-123", ServiceAccountID: "sa-456", Provider: auth.K8sServiceAccountTokenProvider(""), RefreshBufferSeconds: 600, }), ) ``` ## Microsoft Azure OpenAI 要将此库与 [Azure OpenAI](https://learn.microsoft.com/azure/ai-services/openai/overview) 配合使用,请使用 `azure` 包中的 option.RequestOption 函数。 ``` package main import ( "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/openai/openai-go/v3" "github.com/openai/openai-go/v3/azure" ) func main() { const azureOpenAIEndpoint = "https://.openai.azure.com" // The latest API versions, including previews, can be found here: // https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versionng const azureOpenAIAPIVersion = "2024-06-01" tokenCredential, err := azidentity.NewDefaultAzureCredential(nil) if err != nil { fmt.Printf("Failed to create the DefaultAzureCredential: %s", err) os.Exit(1) } client := openai.NewClient( azure.WithEndpoint(azureOpenAIEndpoint, azureOpenAIAPIVersion), // Choose between authenticating using a TokenCredential or an API Key azure.WithTokenCredential(tokenCredential), // or azure.WithAPIKey(azureOpenAIAPIKey), ) } ``` ## 语义化版本控制 此包通常遵循 [SemVer](https://semver.org/spec/v2.0.0.html) 约定,尽管某些不兼容的更改可能会作为次要版本发布: 1. 对库内部的更改,这些内部内容在技术上是公开的,但不打算或未记录在外部使用。_(如果你依赖此类内部内容,请打开一个 GitHub issue 告诉我们。)_ 2. 我们预计实际上不会影响绝大多数用户的更改。 我们非常重视向后兼容性,并努力确保你可以依赖流畅的升级体验。 我们期待你的反馈;如有问题、错误或建议,请打开 [issue](https://www.github.com/openai/openai-go/issues)。 ## 贡献 请参阅[贡献文档](./CONTRIBUTING.md)。
标签:Apex, API客户端, ChatGPT, DLL 劫持, Golang, Go语言, GPT-5, OpenAI, Promptflow, Responses API, REST API, 人工智能, 内存规避, 大语言模型, 威胁情报, 安全编程, 官方库, 开发者工具, 日志审计, 机器学习, 生成式AI, 用户模式Hook绕过, 程序破解, 索引, 编程接口