说实话,能调是能调,但官方并没有给 Go 提供现成的 SDK,得自己动手拼 HTTP 请求。直接用 net/http 或者 go-resty 就行,关键就三步:token 获取、请求构造、流式响应处理,少一步就卡住。
如何正确获取并刷新 access_token
文心一言的 API 并不接受直接传入 API_KEY 和 SECRET_KEY,所有请求都必须携带一个有效的 access_token。这个 token 的有效期只有 30 分钟,所以既不能写死,也不能每次请求都去重新获取。
access_token需要通过https://aip.baidubce.com/oauth/2.0/token发起 GET 请求获取,参数是grant_type=client_credentials、client_id(即API_KEY)、client_secret(即SECRET_KEY)- 返回的是 JSON 格式,用
gjson.Get(resp, "access_token").String()或json.Unmarshal提取出来就行,如果拿到空值,直接报错退出 - 别在每次调用 API 前都去请求 token——更好的做法是加一个内存缓存,比如用
sync.Map存 token 和过期时间戳,调用前先检查是否还剩超过 60 秒;快过期时用 goroutine 异步刷新,这样就不会阻塞主流程
chat/completions 和 chat/eb-instant 两个 endpoint 怎么选
前者是通用的 chat 接口(对应 ERNIE-Bot-4 等模型),后者是 turbo 专用的流式接口(ERNIE-Bot-turbo)。不是所有模型都支持后者,选错了会返回 404 或 model not found。
/chat/completions:适用于非流式场景,需要完整响应后再处理,请求体里stream: false(默认值),响应结构固定,答案在choices[0].message.content里/chat/eb-instant:仅限 turbo 模型使用,必须设置stream: true,响应是 SSE 格式,每行以data:开头,需要手动按行解析、跳过空行和event:行,最后合并delta.content- 注意,URL 中的 model 名称并不参与路径,而是放在请求体的
model字段里(比如"ernie-bot-turbo"),但/eb-instant实际会忽略该字段,只认 turbo
流式响应解析容易崩在哪儿
这里有个常见的坑:Go 的 http.Response.Body 是 io.ReadCloser,但 SSE 流绝对不能直接用 json.Unmarshal 整体去读,否则要么卡死,要么丢数据。必须边读边解析,同时还要能容忍服务端偶尔发来的空行、注释行(比如 : ping)。
- 用
bufio.Scanner按行扫描,scanner.Split(bufio.ScanLines),每行 trim 后判断是否以data:开头 - 遇到
data: [DONE]就终止循环;遇到data: {"delta":{"content":"..."}}才解析 JSON,提取delta.content并追加到结果字符串 - 千万别用
ioutil.ReadAll或io.ReadAll试图一次性读完——流没结束前会一直阻塞,超时或 context cancel 后连接可能已经断了,但 goroutine 还卡在读操作上 - 务必设置
http.Client.Timeout和context.WithTimeout,否则网络抖动时 goroutine 泄露的风险极高
并发调用时 token 和 client 复用要注意什么
token 是全局共享的,但 http.Client 可以复用,http.Transport 的连接池能大幅降低 TLS 握手开销。不过百度 API 有 QPS 限制(千帆后台可以查到配额),硬并发上去只会触发 429 Too Many Requests。
http.Client应该定义为包级变量,复用底层 TCP 连接;Transport.MaxIdleConns和MaxIdleConnsPerHost建议设为 100+,避免频繁建连- 千万不要为每个请求都新建一个
http.Client,否则文件描述符耗尽、TLS 缓存失效、DNS 缓存丢失这些问题会接踵而至 - 真实业务中建议加一层限流:用
golang.org/x/time/rate.Limiter控制每秒请求数,比靠 429 重试更可控 - token 刷新过程要加
sync.Once或sync.RWMutex,防止多个 goroutine 同时发起 token 请求,造成配额浪费或响应冲突
token 有效期短、流式协议不标准、错误码含义模糊(比如 500 可能是鉴权失败,也可能是模型内部错误),这些地方如果不提前兜底,上线后第一波流量就会暴露问题。
