openai-go

GitHub
3.1k 290 简单 5 次阅读 昨天Apache-2.0语言模型插件
AI 解读 由 AI 自动生成,仅供参考

openai-go 是 OpenAI 官方推出的 Go 语言 SDK,让开发者能轻松调用 OpenAI 的强大 AI 能力,比如生成文本、对话交互、多轮问答等。它封装了复杂的 HTTP 请求和数据结构,让你只需几行代码就能接入 GPT 等模型,省去手动拼接 API 请求的麻烦。

它主要解决的是 Go 开发者在项目中集成 AI 功能时“从零造轮子”的痛点——无需自己处理认证、参数序列化或错误重试,直接使用清晰的接口即可完成复杂任务。无论是构建聊天机器人、智能客服,还是自动化内容生成,都能快速落地。

适合熟悉 Go 语言的后端工程师、全栈开发者或技术型创业者使用,尤其推荐正在用 Go 构建服务并希望加入 AI 能力的团队。普通用户或非技术人员不建议直接使用。

技术亮点包括:支持流式响应(实时获取生成结果)、会话状态管理(自动维护上下文)、多轮对话追踪,以及类型安全的参数构造。要求 Go 1.22 或更高版本,安装简单,文档齐全,是目前 Go 生态中最权威的 OpenAI 接入方案。

使用场景

一家跨境电商公司的后端团队正在用 Go 语言开发智能客服系统,需要在用户咨询时实时调用 OpenAI 的 GPT 模型生成自然语言回复。

没有 openai-go 时

  • 团队必须手动封装 HTTP 请求,处理认证头、JSON 序列化和错误码映射,代码冗长且容易出错。
  • 每次模型升级或 API 变更都要重新调整请求结构,缺乏类型安全,调试成本高。
  • 多轮对话需自行维护上下文 ID 和会话状态,逻辑分散在多个服务中,难以统一管理。
  • 流式响应需从零实现 SSE(Server-Sent Events)解析,增加网络层复杂度和内存开销。
  • 缺乏官方支持的 SDK,遇到问题只能查阅原始 API 文档,排查效率低,新人上手慢。

使用 openai-go 后

  • 直接调用 client.Responses.New() 方法即可发起请求,内置 API Key 管理和结构体参数校验,代码简洁可靠。
  • 所有参数和返回值均有强类型定义,配合 Go 1.22+ 的泛型支持,编译期就能发现接口不匹配问题。
  • 通过 PreviousResponseIDConversation.ID 轻松维持对话状态,会话历史自动关联,业务逻辑清晰集中。
  • 使用内置流式响应支持,一行代码开启 Stream: true 即可逐字返回,降低延迟并提升用户体验。
  • 官方维护的库持续同步最新 API,附带完整示例和 changelog,团队能快速适配新功能,减少技术负债。

openai-go 让 Go 开发者以最小成本接入 OpenAI 能力,把精力从底层通信转移到真正的业务创新上。

运行环境要求

GPU

未说明

内存

未说明

依赖
notes需要 Go 1.22 或更高版本,通过 go get 安装,依赖环境变量 OPENAI_API_KEY 或手动传入 API 密钥
python未说明
openai-go hero image

快速开始

OpenAI Go API 库

Go 参考文档

OpenAI Go 库为使用 Go 语言编写的应用程序提供了便捷访问 OpenAI REST API 的能力。

[!WARNING] 此包的最新版本包含少量且有限的破坏性变更。 详情请参阅 变更日志

安装

import (
	"github.com/openai/openai-go/v3" // 导入后别名为 openai
)

或指定固定版本:

go get -u 'github.com/openai/openai-go/v3@v3.30.0'

要求

本库要求 Go 1.22 或更高版本。

使用方法

本库完整的 API 文档请参见 api.md

与 OpenAI 模型交互的主要接口是 Responses API。你可以使用以下代码从模型生成文本。

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"), // 默认从 os.LookupEnv("OPENAI_API_KEY") 获取
	)

	question := "写一首关于计算机的俳句"

	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())
}
多轮对话响应
response, err := client.Responses.New(ctx, responses.ResponseNewParams{
	Model: openai.ChatModelGPT5_2,
	Input: responses.ResponseNewParamsInputUnion{
		OfString: openai.String("法国的首都是哪里?"),
	},
})
if err != nil {
	panic(err)
}
fmt.Println("第一轮响应:", response.OutputText())

// 使用 PreviousResponseID 继续对话
response, err = client.Responses.New(ctx, responses.ResponseNewParams{
	Model:              openai.ChatModelGPT5_2,
	PreviousResponseID: openai.String(response.ID),
	Input: responses.ResponseNewParamsInputUnion{
		OfString: openai.String("那座城市的人口是多少?"),
	},
})
if err != nil {
	panic(err)
}
fmt.Println("第二轮响应:", response.OutputText())
会话(Conversations)
conv, err := client.Conversations.New(ctx, conversations.ConversationNewParams{})
if err != nil {
	panic(err)
}
fmt.Println("已创建会话:", conv.ID)

response, err := client.Responses.New(ctx, responses.ResponseNewParams{
	Model: openai.ChatModelGPT5_2,
	Input: responses.ResponseNewParamsInputUnion{
		OfString: openai.String("你好!记住我最喜欢的颜色是蓝色。"),
	},
	Conversation: responses.ResponseNewParamsConversationUnion{
		OfConversationObject: &responses.ResponseConversationParam{
			ID: conv.ID,
		},
	},
})
if err != nil {
	panic(err)
}
fmt.Println("第一轮响应:", response.OutputText())

// 继续会话
response, err = client.Responses.New(ctx, responses.ResponseNewParams{
	Model: openai.ChatModelGPT5_2,
	Input: responses.ResponseNewParamsInputUnion{
		OfString: openai.String("我最喜欢的颜色是什么?"),
	},
	Conversation: responses.ResponseNewParamsConversationUnion{
		OfConversationObject: &responses.ResponseConversationParam{
			ID: conv.ID,
		},
	},
})
if err != nil {
	panic(err)
}
fmt.Println("第二轮响应:", response.OutputText())

items, err := client.Conversations.Items.List(ctx, conv.ID, conversations.ItemListParams{})
if err != nil {
	panic(err)
}
fmt.Println("会话包含", len(items.Data), "条记录")
流式响应
ctx := context.Background()

stream := client.Responses.NewStreaming(ctx, responses.ResponseNewParams{
	Model: openai.ChatModelGPT5_2,
	Input: responses.ResponseNewParamsInputUnion{
		OfString: openai.String("写一首关于编程的俳句"),
	},
})

for stream.Next() {
	event := stream.Current()
	print(event.Delta)
}

if stream.Err() != nil {
	panic(stream.Err())
}

查看完整流式示例

工具调用(Tool calling)
ctx := context.Background()

params := responses.ResponseNewParams{
	Model: openai.ChatModelGPT5_2,
	Input: responses.ResponseNewParamsInputUnion{
		OfString: openai.String("纽约市的天气如何?"),
	},
	Tools: []responses.ToolUnionParam{{
		OfFunction: &responses.FunctionToolParam{
			Name:        "get_weather",
			Description: openai.String("获取指定位置的天气"),
			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)

// 检查响应输出中的函数调用
for _, item := range response.Output {
	if item.Type == "function_call" {
		toolCall := item.AsFunctionCall()
		if toolCall.Name == "get_weather" {
			// 提取参数并调用你的函数
			var args map[string]any
			json.Unmarshal([]byte(toolCall.Arguments), &args)
			location := args["location"].(string)

			// 模拟获取天气数据
			weatherData := getWeather(location)
			fmt.Printf("地点 %s 的天气:%s\n", location, weatherData)

			// 使用函数结果继续对话
			response, _ = client.Responses.New(ctx, responses.ResponseNewParams{
				Model:              openai.ChatModelGPT5_2,
				PreviousResponseID: openai.String(response.ID),
				Input: responses.ResponseNewParamsInputUnion{
					OfInputItemList: []responses.ResponseInputItemUnionParam{{
						OfFunctionCallOutput: &responses.ResponseInputItemFunctionCallOutputParam{
							CallID: toolCall.CallID,
							Output: responses.ResponseInputItemFunctionCallOutputOutputUnionParam{
								OfString: openai.String(weatherData),
							},
						},
					}},
				},
			})
		}
	}
}
结构化输出(Structured outputs)
import (
	"encoding/json"
	"github.com/invopop/jsonschema"
	// ...
)

```go
// 一个将被转换为结构化输出(Structured Outputs)响应模式的结构体
type HistoricalComputer struct {
	Origin       Origin   `json:"origin" jsonschema_description:"计算机的起源"`
	Name         string   `json:"full_name" jsonschema_description:"设备型号的名称"`
	Legacy       string   `json:"legacy" jsonschema:"enum=positive,enum=neutral,enum=negative" jsonschema_description:"其对计算领域的影响"`
	NotableFacts []string `json:"notable_facts" jsonschema_description:"关于该计算机的一些关键事实"`
}

type Origin struct {
	YearBuilt    int64  `json:"year_of_construction" jsonschema_description:"制造年份"`
	Organization string `json:"organization" jsonschema_description:"负责其开发的组织"`
}

// 结构化输出使用 JSON 模式的一个子集
// 这些标志是符合该子集所必需的
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
}

// 在初始化时生成 JSON 模式
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("什么计算机运行了第一个神经网络?"),
		},
		Text: responses.ResponseTextConfigParam{
			Format: responses.ResponseFormatTextConfigParamOfJSONSchema(
				"historical_computer",
				HistoricalComputerSchema,
			),
		},
	})
	if err != nil {
		panic(err)
	}

	// 提取到一个类型安全的结构体中
	var historicalComputer HistoricalComputer
	_ = json.Unmarshal([]byte(response.OutputText()), &historicalComputer)

	historicalComputer.Name
	historicalComputer.Origin.YearBuilt
	historicalComputer.Origin.Organization
	for i, fact := range historicalComputer.NotableFacts {
		// ...
	}
}

查看 完整的结构化输出示例

聊天补全 API

此前的标准(将无限期支持)文本生成接口是 聊天补全 API。你可以使用以下代码通过该 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("你是一个说话像海盗的编程助手。"),
			openai.UserMessage("我如何在 Go 中检查切片是否为空?"),
		},
		Model: openai.ChatModelGPT5_2,
	})
	if err != nil {
		panic(err)
	}

	println(chatCompletion.Choices[0].Message.Content)
}

请求字段

openai 库对请求字段使用 Go 1.24+ encoding/json 发布版中的 omitzero 语义。

必需的基本类型字段(int64string 等)带有标签 `json:"...,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",             // 必需属性
	Name: openai.String("..."), // 可选属性

	Point: openai.Point{
		X: 0,             // 必需字段,序列化为 0
		Y: openai.Int(1), // 可选字段,序列化为 1
		// ... 省略的非必需字段不会被序列化
	},

	Origin: openai.Origin{}, // [Origin] 的零值被视为省略
}

若要发送 null 而非 param.Opt[T],请使用 param.Null[T]()。 若要发送 null 而非结构体 T,请使用 param.NullStruct[T]()

p.Name = param.Null[string]()       // 发送 'null' 而非字符串
p.Point = param.NullStruct[Point]() // 发送 'null' 而非结构体

param.IsNull(p.Name)  // true
param.IsNull(p.Point) // true

请求结构体包含 .SetExtraFields(map[string]any) 方法,可用于在请求体中发送不符合规范的字段。额外字段会覆盖具有相同键的结构体字段。出于安全考虑,请仅对可信数据使用 SetExtraFields

若要发送自定义值而非结构体,请使用 param.Override[T](value)

// 当 API 指定某种类型,但你想发送其他内容时,使用 [SetExtraFields]:
p.SetExtraFields(map[string]any{
	"x": 0.01, // 将 "x" 作为浮点数发送,而非整数
})

// 发送数字而非对象
custom := param.Override[openai.FooParams](12)

请求联合类型(Unions)

联合类型表示为一个结构体,其每个变体字段前缀为 "Of", 仅允许一个字段非零。非零字段将被序列化。

联合类型的子属性可通过联合结构体上的方法访问。 这些方法返回指向底层数据的可变指针(如果存在)。

// 仅允许一个字段非零,使用 param.IsOmitted() 检查字段是否已设置
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},
		},
	},
}

// 修改字段
if address := animal.GetOwner().GetAddress(); address != nil {
	address.ZipCode = 94304
}

响应对象(Response objects)

响应结构体中的所有字段均为普通值类型(非指针或包装器)。
响应结构体还包含一个特殊的 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() 方法。
.Valid() 在字段不为 null、不存在或无法被序列化时返回 true。

.Valid() 为 false,则对应字段将为其零值。

raw := `{"owners": 1, "name": null}`

var res Animal
json.Unmarshal([]byte(raw), &res)

// 访问常规字段

res.Owners // 1
res.Name   // ""
res.Age    // 0

// 可选字段检查

res.JSON.Owners.Valid() // true
res.JSON.Name.Valid()   // false
res.JSON.Age.Valid()    // false

// 原始 JSON 值

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 映射,其中包含 JSON 响应中未在结构体中定义的任何属性。
这对于 SDK 尚未支持的 API 功能非常有用。

body := res.JSON.ExtraFields["my_unexpected_field"].Raw()

响应联合类型(Response Unions)

在响应中,联合类型(union)由一个扁平化的结构体表示,该结构体包含每个对象变体的所有可能字段。
要将其转换为具体变体,请使用 .AsFooVariant() 方法;如果存在,也可使用 .AsAny() 方法。

如果响应值联合类型包含基本类型(primitive values),则基本类型字段将与属性并列,但会以 Of 为前缀,并带有标签 json:"...,inline"

type AnimalUnion struct {
	// 来自变体 [Dog], [Cat]
	Owner Person `json:"owner"`
	// 来自变体 [Dog]
	DogBreed string `json:"dog_breed"`
	// 来自变体 [Cat]
	CatBreed string `json:"cat_breed"`
	// ...

	JSON struct {
		Owner respjson.Field
		// ...
	} `json:"-"`
}

// 如果是 animal 变体
if animal.Owner.Address.ZipCode == "" {
	panic("missing zip code")
}

// 根据变体进行切换
switch variant := animal.AsAny().(type) {
case Dog:
case Cat:
default:
	panic("unexpected type")
}

请求选项(RequestOptions)

本库使用函数式选项模式(functional options pattern)。
option 包中定义的函数返回一个 RequestOption,它是一个修改 RequestConfig 的闭包。
这些选项可以提供给客户端,也可以在单个请求中指定。例如:

client := openai.NewClient(
	// 为客户端发起的每个请求添加一个头部
	option.WithHeader("X-Some-Header", "custom_header_info"),
)

client.Responses.New(context.TODO(), responses.ResponseNewParams{...},
	// 覆盖头部
	option.WithHeader("X-Some-Header", "some_other_custom_header_info"),
	// 使用 sjson 语法向请求体添加未文档化的字段
	option.WithJSONSet("some.json.path", map[string]string{"my": "object"}),
)

调试时,请求选项 option.WithDebugLog(nil) 可能会有帮助。

请参阅完整的请求选项列表

分页(Pagination)

本库为处理分页列表端点提供了一些便利功能。

你可以使用 .ListAutoPaging() 方法遍历所有页面中的项目:

iter := client.FineTuning.Jobs.ListAutoPaging(context.TODO(), openai.FineTuningJobListParams{
	Limit: openai.Int(20),
})
// 根据需要自动获取更多页面。
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())
}

错误(Errors)

当 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)))  // 打印序列化的 HTTP 请求
		println(string(apierr.DumpResponse(true))) // 打印序列化的 HTTP 响应
	}
	panic(err.Error()) // GET "/fine_tuning/jobs": 400 Bad Request { ... }
}

当发生其他错误时,它们将以未包装形式返回;例如,
如果 HTTP 传输失败,你可能会收到一个包装了 *net.OpError*url.Error

超时(Timeouts)

默认情况下,请求不会超时;请使用 context 配置请求生命周期的超时时间。

注意:如果请求被重试,context 超时不会重新计时。
要设置每次重试的超时时间,请使用 option.WithRequestTimeout()

// 此处设置的是整个请求(包括所有重试)的超时时间。
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?"),
		},
	},
	// 此处设置的是每次重试的超时时间
	option.WithRequestTimeout(20*time.Second),
)

文件上传

在 multipart 请求中,对应文件上传的请求参数类型为 io.Reader。默认情况下,io.Reader 的内容将以文件名为 "anonymous_file"、内容类型(content-type)为 "application/octet-stream" 的 multipart 表单部分发送。

可以通过在 io.Reader 的运行时类型上实现 Name() stringContentType() string 方法来自定义文件名和内容类型。注意:os.File 已实现 Name() string,因此通过 os.Open 返回的文件将使用磁盘上的实际文件名发送。

我们还提供了一个辅助函数 openai.File(reader io.Reader, filename string, contentType string),可用于为任意 io.Reader 包装指定的文件名和内容类型。

// 来自文件系统的文件
file, err := os.Open("input.jsonl")
openai.FileNewParams{
	File:    file,
	Purpose: openai.FilePurposeFineTune,
}

// 来自字符串的文件
openai.FileNewParams{
	File:    strings.NewReader("my file contents"),
	Purpose: openai.FilePurposeFineTune,
}

// 自定义文件名和 contentType
openai.FileNewParams{
	File:    openai.File(strings.NewReader(`{"hello": "foo"}`), "file.go", "application/json"),
	Purpose: openai.FilePurposeFineTune,
}

Webhook 验证

验证 webhook 签名是_可选但推荐的_。

有关 webhook 的更多信息,请参阅 API 文档

解析 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")), // 默认使用环境变量;此处显式指定。
	)

	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": "读取请求体失败"})
			return
		}
		defer c.Request.Body.Close()

		webhookEvent, err := client.Webhooks.Unwrap(body, c.Request.Header)
		if err != nil {
			log.Printf("无效的 webhook 签名: %v", err)
			c.JSON(http.StatusBadRequest, gin.H{"error": "签名无效"})
			return
		}

		switch event := webhookEvent.AsAny().(type) {
		case webhooks.ResponseCompletedWebhookEvent:
			log.Printf("响应完成: %+v", event.Data)
		case webhooks.ResponseFailedWebhookEvent:
			log.Printf("响应失败: %+v", event.Data)
		default:
			log.Printf("未处理的事件类型: %T", event)
		}

		c.JSON(http.StatusOK, gin.H{"message": "ok"})
	})

	r.Run(":8000")
}

直接验证 webhook 负载

在某些情况下,你可能希望在解析负载之前单独验证 webhook。如果你倾向于分别处理这两个步骤,我们提供了 client.Webhooks.VerifySignature() 方法,仅用于验证 webhook 请求的签名。与 Unwrap() 类似,如果签名无效,此方法将返回错误。

请注意,body 参数应为服务器发送的原始 JSON 字节(不要先解析它)。你需要在验证签名后再自行解析 body。

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")), // 默认使用环境变量;此处显式指定。
	)

	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": "读取请求体失败"})
			return
		}
		defer c.Request.Body.Close()

		err = client.Webhooks.VerifySignature(body, c.Request.Header)
		if err != nil {
			log.Printf("无效的 webhook 签名: %v", err)
			c.JSON(http.StatusBadRequest, gin.H{"error": "签名无效"})
			return
		}

		c.JSON(http.StatusOK, gin.H{"message": "ok"})
	})

	r.Run(":8000")
}

重试机制

默认情况下,某些错误会自动重试 2 次,并采用短指数退避策略。 默认重试所有连接错误、408 请求超时、409 冲突、429 速率限制以及 >=500 的内部错误。

你可以使用 WithMaxRetries 选项来配置或禁用此行为:

// 为所有请求配置默认值:
client := openai.NewClient(
	option.WithMaxRetries(0), // 默认值为 2
)

// 为单个请求覆盖设置:
client.Responses.New(
	context.TODO(),
	responses.ResponseNewParams{
		Model: openai.ChatModelGPT5_2,
		Input: responses.ResponseNewParamsInputUnion{
			OfString: openai.String("如何在 JavaScript 中获取当前星期几的名称?"),
		},
	},
	option.WithMaxRetries(5),
)

访问原始响应数据(如响应头)

你可以使用 option.WithResponseInto() 请求选项访问原始 HTTP 响应数据。当你需要检查响应头、状态码或其他详细信息时,这非常有用。

// 创建一个变量用于存储 HTTP 响应
var httpResp *http.Response
response, err := client.Responses.New(
	context.TODO(),
	responses.ResponseNewParams{
		Model: openai.ChatModelGPT5_2,
		Input: responses.ResponseNewParamsInputUnion{
			OfString: openai.String("说这是一次测试"),
		},
	},
	option.WithResponseInto(&httpResp),
)
if err != nil {
	// 处理错误
}
fmt.Printf("%+v\n", response)

fmt.Printf("状态码: %d\n", httpResp.StatusCode)
fmt.Printf("响应头: %+#v\n", httpResp.Header)

发起自定义/未文档化的请求

本库为方便访问已文档化的 API 而进行了类型标注。若您需要访问未文档化的端点(endpoints)、参数(params)或响应属性(response properties),仍可使用本库。

未文档化的端点

要向未文档化的端点发起请求,您可以使用 client.Getclient.Post 及其他 HTTP 动词方法。客户端上的 RequestOptions(如重试机制)在发起此类请求时仍将生效。

var (
    // params 可以是 io.Reader、[]byte、可被 encoding/json 序列化的对象,
    // 或本库中定义的“…Params”结构体。
    params map[string]any

    // result 可以是 []byte、*http.Response、可被 encoding/json 反序列化的对象,
    // 或本库中定义的模型。
    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 类型的额外字段集合。

中间件(Middleware)

我们提供 option.WithMiddleware,用于将指定的中间件应用于请求。

func Logger(req *http.Request, next option.MiddlewareNext) (res *http.Response, err error) {
	// 请求前处理
	start := time.Now()
	LogReq(req)

	// 将请求转发给下一个处理器
	res, err = next(req)

	// 请求后处理
	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 客户端(这会覆盖之前设置的客户端),且该客户端将在所有中间件处理完毕后接收请求。

Microsoft Azure OpenAI

要在 Azure OpenAI 上使用本库,请使用 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://<azure-openai-resource>.openai.azure.com"

	// 最新的 API 版本(包括预览版)可在此处找到:
	// 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),

		// 选择使用 TokenCredential 或 API Key 进行身份验证
		azure.WithTokenCredential(tokenCredential),
		// 或 azure.WithAPIKey(azureOpenAIAPIKey),
	)
}

语义化版本控制(Semantic versioning)

本包通常遵循 SemVer 规范,但某些不兼容的变更可能会作为次要版本发布:

  1. 对库内部实现的更改,这些部分虽技术上公开,但并非为外部使用而设计或文档化。(若您依赖此类内部实现,请提交 GitHub Issue 告知我们。)
  2. 我们认为在实践中不会影响绝大多数用户的变更。

我们高度重视向后兼容性,并努力确保您能获得顺畅的升级体验。

我们非常期待您的反馈;请通过 Issue 提交问题、错误或建议。

贡献指南

详见 贡献文档

版本历史

v3.30.02026/03/25
v3.29.02026/03/17
v3.28.02026/03/14
v3.27.02026/03/13
v3.26.02026/03/05
v3.25.02026/03/05
v3.24.02026/02/24
v3.23.02026/02/24
v3.22.12026/02/23
v3.22.02026/02/14
v3.21.02026/02/10
v3.20.02026/02/10
v3.19.02026/02/09
v3.18.02026/02/05
v3.17.02026/01/27
v3.16.02026/01/09
v3.15.02025/12/19
v3.14.02025/12/16
v3.13.02025/12/15
v3.12.02025/12/11

常见问题

相似工具推荐

everything-claude-code

everything-claude-code 是一套专为 AI 编程助手(如 Claude Code、Codex、Cursor 等)打造的高性能优化系统。它不仅仅是一组配置文件,而是一个经过长期实战打磨的完整框架,旨在解决 AI 代理在实际开发中面临的效率低下、记忆丢失、安全隐患及缺乏持续学习能力等核心痛点。 通过引入技能模块化、直觉增强、记忆持久化机制以及内置的安全扫描功能,everything-claude-code 能显著提升 AI 在复杂任务中的表现,帮助开发者构建更稳定、更智能的生产级 AI 代理。其独特的“研究优先”开发理念和针对 Token 消耗的优化策略,使得模型响应更快、成本更低,同时有效防御潜在的攻击向量。 这套工具特别适合软件开发者、AI 研究人员以及希望深度定制 AI 工作流的技术团队使用。无论您是在构建大型代码库,还是需要 AI 协助进行安全审计与自动化测试,everything-claude-code 都能提供强大的底层支持。作为一个曾荣获 Anthropic 黑客大奖的开源项目,它融合了多语言支持与丰富的实战钩子(hooks),让 AI 真正成长为懂上

139k|★★☆☆☆|今天
开发框架Agent语言模型

NextChat

NextChat 是一款轻量且极速的 AI 助手,旨在为用户提供流畅、跨平台的大模型交互体验。它完美解决了用户在多设备间切换时难以保持对话连续性,以及面对众多 AI 模型不知如何统一管理的痛点。无论是日常办公、学习辅助还是创意激发,NextChat 都能让用户随时随地通过网页、iOS、Android、Windows、MacOS 或 Linux 端无缝接入智能服务。 这款工具非常适合普通用户、学生、职场人士以及需要私有化部署的企业团队使用。对于开发者而言,它也提供了便捷的自托管方案,支持一键部署到 Vercel 或 Zeabur 等平台。 NextChat 的核心亮点在于其广泛的模型兼容性,原生支持 Claude、DeepSeek、GPT-4 及 Gemini Pro 等主流大模型,让用户在一个界面即可自由切换不同 AI 能力。此外,它还率先支持 MCP(Model Context Protocol)协议,增强了上下文处理能力。针对企业用户,NextChat 提供专业版解决方案,具备品牌定制、细粒度权限控制、内部知识库整合及安全审计等功能,满足公司对数据隐私和个性化管理的高标准要求。

87.6k|★★☆☆☆|今天
开发框架语言模型

ML-For-Beginners

ML-For-Beginners 是由微软推出的一套系统化机器学习入门课程,旨在帮助零基础用户轻松掌握经典机器学习知识。这套课程将学习路径规划为 12 周,包含 26 节精炼课程和 52 道配套测验,内容涵盖从基础概念到实际应用的完整流程,有效解决了初学者面对庞大知识体系时无从下手、缺乏结构化指导的痛点。 无论是希望转型的开发者、需要补充算法背景的研究人员,还是对人工智能充满好奇的普通爱好者,都能从中受益。课程不仅提供了清晰的理论讲解,还强调动手实践,让用户在循序渐进中建立扎实的技能基础。其独特的亮点在于强大的多语言支持,通过自动化机制提供了包括简体中文在内的 50 多种语言版本,极大地降低了全球不同背景用户的学习门槛。此外,项目采用开源协作模式,社区活跃且内容持续更新,确保学习者能获取前沿且准确的技术资讯。如果你正寻找一条清晰、友好且专业的机器学习入门之路,ML-For-Beginners 将是理想的起点。

85k|★★☆☆☆|今天
图像数据工具视频

ragflow

RAGFlow 是一款领先的开源检索增强生成(RAG)引擎,旨在为大语言模型构建更精准、可靠的上下文层。它巧妙地将前沿的 RAG 技术与智能体(Agent)能力相结合,不仅支持从各类文档中高效提取知识,还能让模型基于这些知识进行逻辑推理和任务执行。 在大模型应用中,幻觉问题和知识滞后是常见痛点。RAGFlow 通过深度解析复杂文档结构(如表格、图表及混合排版),显著提升了信息检索的准确度,从而有效减少模型“胡编乱造”的现象,确保回答既有据可依又具备时效性。其内置的智能体机制更进一步,使系统不仅能回答问题,还能自主规划步骤解决复杂问题。 这款工具特别适合开发者、企业技术团队以及 AI 研究人员使用。无论是希望快速搭建私有知识库问答系统,还是致力于探索大模型在垂直领域落地的创新者,都能从中受益。RAGFlow 提供了可视化的工作流编排界面和灵活的 API 接口,既降低了非算法背景用户的上手门槛,也满足了专业开发者对系统深度定制的需求。作为基于 Apache 2.0 协议开源的项目,它正成为连接通用大模型与行业专有知识之间的重要桥梁。

77.1k|★★★☆☆|昨天
Agent图像开发框架

PaddleOCR

PaddleOCR 是一款基于百度飞桨框架开发的高性能开源光学字符识别工具包。它的核心能力是将图片、PDF 等文档中的文字提取出来,转换成计算机可读取的结构化数据,让机器真正“看懂”图文内容。 面对海量纸质或电子文档,PaddleOCR 解决了人工录入效率低、数字化成本高的问题。尤其在人工智能领域,它扮演着连接图像与大型语言模型(LLM)的桥梁角色,能将视觉信息直接转化为文本输入,助力智能问答、文档分析等应用场景落地。 PaddleOCR 适合开发者、算法研究人员以及有文档自动化需求的普通用户。其技术优势十分明显:不仅支持全球 100 多种语言的识别,还能在 Windows、Linux、macOS 等多个系统上运行,并灵活适配 CPU、GPU、NPU 等各类硬件。作为一个轻量级且社区活跃的开源项目,PaddleOCR 既能满足快速集成的需求,也能支撑前沿的视觉语言研究,是处理文字识别任务的理想选择。

74.9k|★★★☆☆|今天
语言模型图像开发框架

OpenHands

OpenHands 是一个专注于 AI 驱动开发的开源平台,旨在让智能体(Agent)像人类开发者一样理解、编写和调试代码。它解决了传统编程中重复性劳动多、环境配置复杂以及人机协作效率低等痛点,通过自动化流程显著提升开发速度。 无论是希望提升编码效率的软件工程师、探索智能体技术的研究人员,还是需要快速原型验证的技术团队,都能从中受益。OpenHands 提供了灵活多样的使用方式:既可以通过命令行(CLI)或本地图形界面在个人电脑上轻松上手,体验类似 Devin 的流畅交互;也能利用其强大的 Python SDK 自定义智能体逻辑,甚至在云端大规模部署上千个智能体并行工作。 其核心技术亮点在于模块化的软件智能体 SDK,这不仅构成了平台的引擎,还支持高度可组合的开发模式。此外,OpenHands 在 SWE-bench 基准测试中取得了 77.6% 的优异成绩,证明了其解决真实世界软件工程问题的能力。平台还具备完善的企业级功能,支持与 Slack、Jira 等工具集成,并提供细粒度的权限管理,适合从个人开发者到大型企业的各类用户场景。

70.6k|★★★☆☆|今天
语言模型Agent开发框架