openai/openai-go
GitHub: openai/openai-go
OpenAI Go 官方 SDK,用于在 Go 应用程序中通过 REST API 访问 GPT 模型,支持对话、流式响应及函数调用等最新功能。
Stars: 3141 | Forks: 302
# OpenAI Go API 库
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())
}
```
### 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` 等)带有标签 .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)。
多轮 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 { // ... } } ```\`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://标签:Apex, API客户端, ChatGPT, DLL 劫持, Golang, Go语言, GPT-5, OpenAI, Promptflow, Responses API, REST API, 人工智能, 内存规避, 大语言模型, 威胁情报, 安全编程, 官方库, 开发者工具, 日志审计, 机器学习, 生成式AI, 用户模式Hook绕过, 程序破解, 索引, 编程接口