用 net/http 直接发 POST 请求最可控,因其轻量、无依赖、便于调试,且可精准控制 Content-Type、Authorization、JSON 请求体等关键点。

用 net/http 直接发 POST 请求最可控
OpenAI 的 Chat Completions API 本质就是个 REST 接口,net/http 足够轻量、无依赖、便于调试。很多项目盲目上 github.com/sashabaranov/go-openai,结果卡在 token 刷新、流式响应解析或自定义 header 上——其实多数需求只需要发一次 JSON 请求并读回 choices[0].message.content。
关键点:
-
Content-Type必须设为application/json,漏掉会返回400 Bad Request -
Authorizationheader 格式是Bearer sk-xxx,注意开头有空格 - 请求体必须是合法 JSON,
model字段不能拼错(比如写成modle) - Go 默认不自动解 gzip,但 OpenAI 响应通常未压缩,不用额外处理
示例片段:
reqBody := map[string]interface{}{
"model": "gpt-3.5-turbo",
"messages": []map[string]string{
{"role": "user", "content": "你好"},
},
}
data, _ := json.Marshal(reqBody)
resp, _ := http.Post("https://api.openai.com/v1/chat/completions", "application/json", bytes.NewBuffer(data))
用 go-openai 库时要注意 Client 初始化和超时
这个库封装得不错,但默认没设超时,生产环境容易卡死;而且它把 API key 写死在 Client 实例里,没法动态切换 key 或做租户隔离。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 初始化时显式传入
http.Client,自己控制Timeout和Transport - 不要复用全局
openai.Client实例来服务不同用户,key 是实例级的 - 流式调用(
CreateChatCompletionStream)需手动 closeresponse,否则 goroutine 泄漏 - 错误类型不是标准
error,要检查resp.Error.Message字段,而不是只看err != nil
遇到 429 Too Many Requests 不能只靠重试
OpenAI 的限流是按「每分钟请求数」和「每分钟 token 数」双重计算的,单纯 sleep + retry 容易失败。尤其当多个 goroutine 并发调用时,本地重试可能让问题更糟。
更稳的做法:
- 在请求前加简单令牌桶(如
golang.org/x/time/rate.Limiter),限制每秒最大请求数 - 从响应 header 里读
X-RateLimit-Remaining和X-RateLimit-Reset,动态调整等待时间 - 对高频小请求(如批量打标),合并成单次
messages数组,比串行调用省 token 也少触发限流 - 别在
defaulthttp.Transport 上调大MaxIdleConns,OpenAI 不支持长连接复用,反而占着 fd
中文输出乱码或截断?检查 json.RawMessage 和 UTF-8 编码
Go 的 json.Unmarshal 默认按 UTF-8 解析,但如果你用 io.ReadAll 读响应体后又手动转 string(),再塞进结构体,中间若误用了 []byte 截断或错误切片,会导致中文变 或提前终止。
安全做法:
- 直接对响应 body 做
json.Unmarshal,别中间转 string - 如果需要日志记录原始响应,用
fmt.Printf("%s", body),别用string(body[:n])随意截 - 结构体字段用
json.RawMessage接收不确定格式的字段(比如函数调用返回的function_call),避免因字段缺失 panic - 响应中
content字段值本身就是字符串,不需要额外url.QueryUnescape或base64.StdEncoding.DecodeString
真正容易被忽略的是:OpenAI 返回的 JSON 本身是严格 UTF-8,但你的终端、IDE 或日志系统如果没设 UTF-8 编码,看起来就是乱码——先确认是传输层问题,还是显示层问题。

















