用 Go 打开大模型的正确姿势:OpenAI、字节、百度、阿里、MiniMax 全系 HTTPS 调用与流式实践
通过一套“通用 HTTPS + Go 骨架”,可以较为平滑地接入 OpenAI、字节(火山方舟/豆包)、百度(文心千帆/ERNIE)、阿里(通义千问/DashScope)、MiniMax 等多家大模型服务,覆盖纯文本、流式、文生图、图生问、TTS 与多模态输入的主流场景。工程上关键在于抽象:把“非流式 POST JSON”“SSE 流式读取”“data URL 安全传图”“Access Token
用 Go 打开大模型的正确姿势:OpenAI、字节、百度、阿里、MiniMax 全系 HTTPS 调用与流式实践
“模型越来越多,接口五花八门,工程上到底该怎么统一调用、稳定落地?”
这是一篇从工程实战出发的长文:用一套 Go 与 HTTPS 的通用骨架,串起纯文本大模型、文生图、图生问(图像理解问答)、TTS 语音合成与多模态输入的主流调用姿势,覆盖 OpenAI、字节(火山方舟/豆包)、百度(文心千帆/ERNIE)、阿里(通义千问/DashScope)、MiniMax 等服务商。文末还有常见坑与优化建议,欢迎留言交流经验。
目录
- 为什么用 Go + HTTPS 打通全家桶
- 核心通用件(SSE 流式读取、非流式 POST、Base64 工具)
- OpenAI:文本、流式、文生图、图生问、TTS 全示例
- 字节(火山方舟/豆包):OpenAI 风格的 Chat 与图片理解
- 阿里(通义千问/DashScope):兼容模式快速接入与流式
- 百度(文心千帆/ERNIE):获取 Access Token、非流式与流式
- MiniMax:Chat 基本调用与流式
- 多模态输入模式要点(图生问的“data URL”安全做法)
- 稳定性与工程化:超时、重试、限流、灰度与观测
- 常见坑与排错清单
- 结语与互动
为什么用 Go + HTTPS 打通全家桶
- Go 自带高性能网络库,SSE/长连接实现轻量、稳定、便携。
- 各家平台虽参数命名不同,但基础都是 HTTPS + JSON(流式多为 SSE 或行分隔 JSON),完全可以抽象出一套“通用骨架 + 少量适配”的模式。
- 使用 OpenAI 兼容模式的服务(如部分厂商提供的 compatible-mode/OpenAI-style 接口),可直接复用一套调用逻辑,显著降低接入成本。
实战经验表明:
- 非流式(一次性返回):最稳且易于重试,但对长回答/进度反馈不友好。
- 流式(SSE):用户体验最好,可边收边显,但需要注意超时、断线重试与增量拼接。
核心通用件(SSE、HTTP、Base64)
以下是后文所有示例都会用到的 Go 辅助。默认依赖标准库,便于理解与迁移。
package llmcommon
import (
"bufio"
"context"
"crypto/tls"
"encoding/base64"
"encoding/json"
"errors"
"io"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"time"
)
// 创建一个适合流式的 HTTP Client:连接复用、TLS、合理超时
func NewHTTPClient() *http.Client {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 60 * time.Second,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 200,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
}
return &http.Client{
Transport: transport,
// 流式不要设置固定 Timeout,交给 context 控制
Timeout: 0,
}
}
// 通用:非流式 POST JSON,返回原始响应体字节
func PostJSON(ctx context.Context, client *http.Client, url string, headers map[string]string, payload any) ([]byte, *http.Response, error) {
b, err := json.Marshal(payload)
if err != nil {
return nil, nil, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(string(b)))
if err != nil {
return nil, nil, err
}
req.Header.Set("Content-Type", "application/json")
for k, v := range headers {
req.Header.Set(k, v)
}
resp, err := client.Do(req)
if err != nil {
return nil, resp, err
}
defer resp.Body.Close()
if resp.StatusCode/100 != 2 {
body, _ := io.ReadAll(resp.Body)
return nil, resp, errors.New(resp.Status + ": " + string(body))
}
body, err := io.ReadAll(resp.Body)
return body, resp, err
}
// 通用:SSE/按行流式读取,兼容 "data: xxx" 与纯 JSON 行
func ReadSSE(ctx context.Context, resp *http.Response, onEvent func(jsonLine []byte) error) error {
defer resp.Body.Close()
reader := bufio.NewReader(resp.Body)
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
line, err := reader.ReadBytes('\n')
if err != nil {
if err == io.EOF {
return nil
}
return err
}
s := strings.TrimSpace(string(line))
if s == "" {
continue
}
// 兼容标准 SSE 与纯 JSON 行
if strings.HasPrefix(s, "data:") {
s = strings.TrimSpace(strings.TrimPrefix(s, "data:"))
}
if s == "[DONE]" {
return nil
}
if err := onEvent([]byte(s)); err != nil {
return err
}
}
}
// 工具:读取本地文件并转成 data URL(用于图生问、避免外链)
func FileToDataURL(path string, mime string) (string, error) {
b, err := os.ReadFile(path)
if err != nil {
return "", err
}
ext := strings.ToLower(filepath.Ext(path))
if mime == "" {
// 简单猜测 MIME(必要时手动传入更稳妥)
switch ext {
case ".png":
mime = "image/png"
case ".jpg", ".jpeg":
mime = "image/jpeg"
case ".webp":
mime = "image/webp"
default:
mime = "application/octet-stream"
}
}
return "data:" + mime + ";base64," + base64.StdEncoding.EncodeToString(b), nil
}
OpenAI:文本、流式、文生图、图生问、TTS
OpenAI 是事实标准,很多厂商提供兼容风格接口。以下用 Chat Completions 与经典 Images/TTS 端点,示例以环境变量 OPENAI_API_KEY 取密钥。
- 基础 URL:
https://api.openai.com/v1
- 认证:
Authorization: Bearer ${OPENAI_API_KEY}
1) 纯文本大模型:非流式与流式
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
"your/module/llmcommon"
)
type OAChatMessageText struct {
Role string `json:"role"`
Content string `json:"content"`
}
type OAChatRequest struct {
Model string `json:"model"`
Messages []OAChatMessageText `json:"messages"`
Temperature float32 `json:"temperature,omitempty"`
Stream bool `json:"stream,omitempty"`
}
type OAChatResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Choices []struct {
Index int `json:"index"`
FinishReason string `json:"finish_reason"`
Message struct {
Role string `json:"role"`
Content string `json:"content"`
} `json:"message,omitempty"`
// 流式增量
Delta struct {
Role string `json:"role,omitempty"`
Content string `json:"content,omitempty"`
} `json:"delta,omitempty"`
} `json:"choices"`
}
func openaiNonStream() error {
client := llmcommon.NewHTTPClient()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
url := "https://api.openai.com/v1/chat/completions"
headers := map[string]string{
"Authorization": "Bearer " + os.Getenv("OPENAI_API_KEY"),
}
req := OAChatRequest{
Model: "gpt-4o-mini", // 文本与多模态通吃;仅文本问题也可用
Messages: []OAChatMessageText{
{Role: "system", Content: "You are a concise assistant."},
{Role: "user", Content: "用三句话解释什么是幂等性。"},
},
Temperature: 0.7,
}
body, _, err := llmcommon.PostJSON(ctx, client, url, headers, req)
if err != nil {
return err
}
var resp OAChatResponse
if err := json.Unmarshal(body, &resp); err != nil {
return err
}
if len(resp.Choices) > 0 {
fmt.Println("非流式回答:", resp.Choices[0].Message.Content)
}
return nil
}
func openaiStream() error {
client := llmcommon.NewHTTPClient()
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
url := "https://api.openai.com/v1/chat/completions"
headers := map[string]string{
"Authorization": "Bearer " + os.Getenv("OPENAI_API_KEY"),
"Content-Type": "application/json",
}
req := OAChatRequest{
Model: "gpt-4o-mini",
Messages: []OAChatMessageText{
{Role: "user", Content: "写一首四行现代诗,每行不超过8个字。"},
},
Stream: true,
}
// 手工发起请求(为了拿到流式响应体)
b, _ := json.Marshal(req)
httpReq, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(string(b)))
for k, v := range headers {
httpReq.Header.Set(k, v)
}
resp, err := client.Do(httpReq)
if err != nil {
return err
}
if resp.StatusCode/100 != 2 {
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
return fmt.Errorf("status=%s, body=%s", resp.Status, string(data))
}
fmt.Print("流式回答:")
return llmcommon.ReadSSE(ctx, resp, func(line []byte) error {
var chunk OAChatResponse
if err := json.Unmarshal(line, &chunk); err != nil {
return nil // 有些实现会混入心跳或非 JSON 行,忽略
}
if len(chunk.Choices) > 0 {
fmt.Print(chunk.Choices[0].Delta.Content)
}
return nil
})
}
func main() {
_ = openaiNonStream()
_ = openaiStream()
}
2) 图生问(图像理解问答)
安全起见,避免外网图片 URL,建议使用 data URL。OpenAI 的 chat/completions 支持 content 为数组,混合文本与图像。
// 省略 import 与客户端创建
type ContentText struct {
Type string `json:"type"`
Text string `json:"text"`
}
type ContentImageURL struct {
Type string `json:"type"`
ImageURL struct {
URL string `json:"url"`
} `json:"image_url"`
}
type OAMessageVision struct {
Role string `json:"role"`
Content []interface{} `json:"content"`
}
type OAChatVisionReq struct {
Model string `json:"model"`
Messages []OAMessageVision `json:"messages"`
Stream bool `json:"stream,omitempty"`
}
func openaiVisionFromFile() error {
client := llmcommon.NewHTTPClient()
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
dataURL, err := llmcommon.FileToDataURL("local-image.png", "image/png")
if err != nil {
return err
}
msg := OAMessageVision{
Role: "user",
Content: []interface{}{
ContentText{Type: "text", Text: "这张图主要包含什么元素?简要回答。"},
ContentImageURL{Type: "image_url", ImageURL: struct{ URL string `json:"url"` }{URL: dataURL}},
},
}
req := OAChatVisionReq{
Model: "gpt-4o-mini",
Messages: []OAMessageVision{msg},
}
url := "https://api.openai.com/v1/chat/completions"
headers := map[string]string{"Authorization": "Bearer " + os.Getenv("OPENAI_API_KEY")}
body, _, err := llmcommon.PostJSON(ctx, client, url, headers, req)
if err != nil {
return err
}
var resp OAChatResponse
if err := json.Unmarshal(body, &resp); err != nil {
return err
}
fmt.Println("图生问回答:", resp.Choices[0].Message.Content)
return nil
}
3) 文生图(图像生成)
type OAImageGenReq struct {
Model string `json:"model"` // 建议 gpt-image-1
Prompt string `json:"prompt"`
Size string `json:"size,omitempty"` // 例如 "1024x1024"
ResponseFormat string `json:"response_format,omitempty"` // "b64_json"
}
type OAImageGenResp struct {
Created int64 `json:"created"`
Data []struct {
B64JSON string `json:"b64_json"`
} `json:"data"`
}
func openaiImageGen() error {
client := llmcommon.NewHTTPClient()
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer cancel()
url := "https://api.openai.com/v1/images/generations"
headers := map[string]string{
"Authorization": "Bearer " + os.Getenv("OPENAI_API_KEY"),
}
req := OAImageGenReq{
Model: "gpt-image-1",
Prompt: "一只在月球上喝茶的猫,赛博朋克,霓虹色调",
Size: "1024x1024",
ResponseFormat: "b64_json",
}
body, _, err := llmcommon.PostJSON(ctx, client, url, headers, req)
if err != nil {
return err
}
var resp OAImageGenResp
if err := json.Unmarshal(body, &resp); err != nil {
return err
}
if len(resp.Data) > 0 {
imgBytes, _ := base64.StdEncoding.DecodeString(resp.Data[0].B64JSON)
return os.WriteFile("gen.png", imgBytes, 0644)
}
return nil
}
4) TTS(文本转语音)
type OATTSReq struct {
Model string `json:"model"` // 如 gpt-4o-mini-tts
Input string `json:"input"`
Voice string `json:"voice"` // 如 "alloy"
Format string `json:"format"` // "mp3" | "wav"
}
func openaiTTS() error {
client := llmcommon.NewHTTPClient()
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
url := "https://api.openai.com/v1/audio/speech"
headers := map[string]string{
"Authorization": "Bearer " + os.Getenv("OPENAI_API_KEY"),
"Content-Type": "application/json",
}
req := OATTSReq{
Model: "gpt-4o-mini-tts",
Input: "欢迎来到 Go 与大模型的世界。",
Voice: "alloy",
Format: "mp3",
}
b, _ := json.Marshal(req)
httpReq, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(string(b)))
for k, v := range headers {
httpReq.Header.Set(k, v)
}
resp, err := client.Do(httpReq)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode/100 != 2 {
data, _ := io.ReadAll(resp.Body)
return fmt.Errorf("status=%s, body=%s", resp.Status, string(data))
}
audio, _ := io.ReadAll(resp.Body)
return os.WriteFile("speech.mp3", audio, 0644)
}
字节(火山方舟/豆包):OpenAI 风格 Chat 与图片理解
火山方舟(Ark)的大模型服务接口在风格上与 OpenAI 高度一致(路径与字段名基本相同),上手门槛较低。以下示例以 ARK_API_KEY
为密钥环境变量。
- 基础 URL(Chat):
https://ark.cn-beijing.volces.com/api/v3/chat/completions
- 认证:
Authorization: Bearer ${ARK_API_KEY}
- 模型名:控制台里可见(如开通的 Doubao Chat 模型或自定义 Endpoint ID)
1) 纯文本 Chat:非流式与流式
// 复用上文 OAChatRequest/OAChatResponse 结构
func arkChatNonStream() error {
client := llmcommon.NewHTTPClient()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
url := "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
headers := map[string]string{
"Authorization": "Bearer " + os.Getenv("ARK_API_KEY"),
}
req := OAChatRequest{
Model: "ep-xxxxxxxxxxxxxxxx", // 方舟“Endpoint ID”或官方模型名
Messages: []OAChatMessageText{
{Role: "user", Content: "豆包你好,用一句话介绍你自己。"},
},
}
body, _, err := llmcommon.PostJSON(ctx, client, url, headers, req)
if err != nil {
return err
}
var resp OAChatResponse
_ = json.Unmarshal(body, &resp)
fmt.Println("Ark 非流式:", resp.Choices[0].Message.Content)
return nil
}
func arkChatStream() error {
client := llmcommon.NewHTTPClient()
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
url := "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
req := OAChatRequest{
Model: "ep-xxxxxxxxxxxxxxxx",
Messages: []OAChatMessageText{
{Role: "user", Content: "流式输出 3 条旅行建议。"},
},
Stream: true,
}
b, _ := json.Marshal(req)
httpReq, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(string(b)))
httpReq.Header.Set("Authorization", "Bearer "+os.Getenv("ARK_API_KEY"))
httpReq.Header.Set("Content-Type", "application/json")
resp, err := client.Do(httpReq)
if err != nil {
return err
}
return llmcommon.ReadSSE(ctx, resp, func(line []byte) error {
var chunk OAChatResponse
if json.Unmarshal(line, &chunk) == nil && len(chunk.Choices) > 0 {
fmt.Print(chunk.Choices[0].Delta.Content)
}
return nil
})
}
2) 图生问(图片理解)
Ark 的多模态 Chat 支持 OpenAI 风格的 content 数组,直接复用上文的 vision 模式。把 model
换成 Ark 的模型或 Endpoint 即可:
func arkVisionFromFile() error {
client := llmcommon.NewHTTPClient()
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
url := "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
dataURL, _ := llmcommon.FileToDataURL("image.jpg", "image/jpeg")
msg := OAMessageVision{
Role: "user",
Content: []interface{}{
ContentText{Type: "text", Text: "找出图中品牌 logo,并简述场景。"},
ContentImageURL{Type: "image_url", ImageURL: struct{ URL string `json:"url"` }{URL: dataURL}},
},
}
req := OAChatVisionReq{
Model: "ep-xxxxxxxxxxxxxxxx",
Messages: []OAMessageVision{msg},
}
headers := map[string]string{"Authorization": "Bearer " + os.Getenv("ARK_API_KEY")}
body, _, err := llmcommon.PostJSON(ctx, client, url, headers, req)
if err != nil {
return err
}
var resp OAChatResponse
_ = json.Unmarshal(body, &resp)
fmt.Println(resp.Choices[0].Message.Content)
return nil
}
阿里(通义千问/DashScope):兼容模式快速接入与流式
DashScope 提供“OpenAI 兼容模式”入口,能直接用 chat/completions 语义;这对统一接入非常友好。以下以 DASHSCOPE_API_KEY
为密钥。
- 基础 URL(兼容):
https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
- 认证:
Authorization: Bearer ${DASHSCOPE_API_KEY}
- 模型名:如
qwen-plus
、qwen2.5-72b-instruct
等,以控制台为准
func dashscopeCompatChatStream() error {
client := llmcommon.NewHTTPClient()
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
url := "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
req := OAChatRequest{
Model: "qwen-plus",
Messages: []OAChatMessageText{
{Role: "system", Content: "回答请简洁。"},
{Role: "user", Content: "用要点列出 Rust 的三大优势。"},
},
Stream: true,
}
b, _ := json.Marshal(req)
httpReq, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(string(b)))
httpReq.Header.Set("Authorization", "Bearer "+os.Getenv("DASHSCOPE_API_KEY"))
httpReq.Header.Set("Content-Type", "application/json")
resp, err := client.Do(httpReq)
if err != nil {
return err
}
return llmcommon.ReadSSE(ctx, resp, func(line []byte) error {
var chunk OAChatResponse
if json.Unmarshal(line, &chunk) == nil && len(chunk.Choices) > 0 {
fmt.Print(chunk.Choices[0].Delta.Content)
}
return nil
})
}
说明:
- 文生图与 TTS 在 DashScope 有原生接口(如“通义万相”等),路径与参数不同;接入时按官方文档确定 endpoint 与字段。工程上可沿用本文的“非流式/流式骨架”,只需替换 URL 与请求结构。
百度(文心千帆/ERNIE):获取 Access Token、非流式与流式
与其他家不同,百度多数接口采用先获取 access_token
再调用 API 的模式。
- 获取 Token:
https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${API_KEY}&client_secret=${SECRET_KEY}
- Chat(非流式/流式参数):
https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions?access_token=...
- 认证:在 URL 上携带
access_token
type BaiduTokenResp struct {
AccessToken string `json:"access_token"`
ExpiresIn int64 `json:"expires_in"`
}
func baiduGetToken(ctx context.Context, client *http.Client, ak, sk string) (string, error) {
url := fmt.Sprintf("https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s", ak, sk)
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
var tr BaiduTokenResp
if err := json.NewDecoder(resp.Body).Decode(&tr); err != nil {
return "", err
}
return tr.AccessToken, nil
}
type BaiduMsg struct {
Role string `json:"role"`
Content string `json:"content"`
}
type BaiduChatReq struct {
Messages []BaiduMsg `json:"messages"`
Stream bool `json:"stream,omitempty"`
}
type BaiduChatResp struct {
Result string `json:"result"`
// 流式时,多次返回,含 is_end 等字段
ID string `json:"id,omitempty"`
IsEnd bool `json:"is_end,omitempty"`
}
func baiduChatNonStream() error {
client := llmcommon.NewHTTPClient()
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
token, err := baiduGetToken(ctx, client, os.Getenv("BAIDU_AK"), os.Getenv("BAIDU_SK"))
if err != nil {
return err
}
url := "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions?access_token=" + token
req := BaiduChatReq{
Messages: []BaiduMsg{{Role: "user", Content: "请用 3 条要点介绍边缘计算。"}},
Stream: false,
}
body, _, err := llmcommon.PostJSON(ctx, client, url, nil, req)
if err != nil {
return err
}
var resp BaiduChatResp
_ = json.Unmarshal(body, &resp)
fmt.Println("百度非流式:", resp.Result)
return nil
}
func baiduChatStream() error {
client := llmcommon.NewHTTPClient()
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
token, _ := baiduGetToken(ctx, client, os.Getenv("BAIDU_AK"), os.Getenv("BAIDU_SK"))
url := "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions?access_token=" + token
req := BaiduChatReq{
Messages: []BaiduMsg{{Role: "user", Content: "分步骤讲讲如何做需求澄清。"}},
Stream: true,
}
b, _ := json.Marshal(req)
httpReq, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(string(b)))
httpReq.Header.Set("Content-Type", "application/json")
resp, err := client.Do(httpReq)
if err != nil {
return err
}
// 百度流式常见为按行 JSON(不一定带 data: 前缀),用兼容读取
return llmcommon.ReadSSE(ctx, resp, func(line []byte) error {
var chunk BaiduChatResp
if json.Unmarshal(line, &chunk) == nil {
fmt.Print(chunk.Result)
}
return nil
})
}
说明:
- 百度的多模态与图像生成有单独产品与字段(常见为传
image
的 base64 或 URL)。工程上仍沿用“非流式/流式骨架”,按文档调整字段名即可。 - 在“千帆”控制台也可找到 OpenAI 兼容入口(如果已开通),那就可以用统一的 OpenAI 风格调用。
MiniMax:Chat 基本调用与流式
MiniMax 的 Chat 接口与 OpenAI 风格接近,另有 GroupId
或组织信息要求(按控制台为准)。以下示例展示典型用法。密钥环境变量 MINIMAX_API_KEY
,假设使用 v1/v2 Chat 端点之一。
- 典型基础 URL:
https://api.minimax.chat/v1/text/chatcompletion_v2
(或以官方文档为准) - 认证:
Authorization: Bearer ${MINIMAX_API_KEY}
- 组织:可能需要在查询参数或头部携带
GroupId
(务必以控制台信息为准) - 模型:如
abab6.5-chat
type MiniMaxChatReq struct {
Model string `json:"model"`
Messages []OAChatMessageText `json:"messages"` // 多数场景兼容 role/content
Stream bool `json:"stream,omitempty"`
// 可能还需额外字段,按官方文档补充
}
type MiniMaxChatResp struct {
Choices []struct {
Message struct {
Role string `json:"role"`
Content string `json:"content"`
} `json:"message,omitempty"`
Delta struct {
Content string `json:"content,omitempty"`
} `json:"delta,omitempty"`
} `json:"choices"`
}
func minimaxChat(groupID string) error {
client := llmcommon.NewHTTPClient()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
base := "https://api.minimax.chat/v1/text/chatcompletion_v2"
url := base + "?GroupId=" + groupID
req := MiniMaxChatReq{
Model: "abab6.5-chat",
Messages: []OAChatMessageText{
{Role: "user", Content: "给我三条效率提升建议。"},
},
Stream: false,
}
headers := map[string]string{
"Authorization": "Bearer " + os.Getenv("MINIMAX_API_KEY"),
}
body, _, err := llmcommon.PostJSON(ctx, client, url, headers, req)
if err != nil {
return err
}
var resp MiniMaxChatResp
_ = json.Unmarshal(body, &resp)
fmt.Println("MiniMax 非流式:", resp.Choices[0].Message.Content)
return nil
}
func minimaxChatStream(groupID string) error {
client := llmcommon.NewHTTPClient()
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
url := "https://api.minimax.chat/v1/text/chatcompletion_v2?GroupId=" + groupID
req := MiniMaxChatReq{
Model: "abab6.5-chat",
Messages: []OAChatMessageText{
{Role: "user", Content: "流式输出 5 个面试小技巧。"},
},
Stream: true,
}
b, _ := json.Marshal(req)
httpReq, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(string(b)))
httpReq.Header.Set("Authorization", "Bearer "+os.Getenv("MINIMAX_API_KEY"))
httpReq.Header.Set("Content-Type", "application/json")
resp, err := client.Do(httpReq)
if err != nil {
return err
}
return llmcommon.ReadSSE(ctx, resp, func(line []byte) error {
var chunk MiniMaxChatResp
if json.Unmarshal(line, &chunk) == nil && len(chunk.Choices) > 0 {
fmt.Print(chunk.Choices[0].Delta.Content)
}
return nil
})
}
说明:
- MiniMax 的字段细节、是否需要
use_standard_sse
等参数,以最新文档为准;但工程上 SSE/非流式骨架完全相同。 - TTS、图像生成等也有服务端点,调用方式同理(替换 URL/请求结构、二进制/JSON 返回处理)。
多模态输入文本模型要点(图生问)
- 内容数组:大多采用 OpenAI 风格的
messages[].content = [ text_part, image_part, ... ]
。 - 图片推荐用 data URL:避免公网可访问 URL 的权限与可用性问题。用上文
FileToDataURL
转换本地文件最稳妥。 - 尺寸/页数控制:有的服务支持传
image
额外参数(如缩放、采样策略)。图片过大可先本地缩放再发送,降低时延与成本。 - 注意计费维度:多模态通常按 tokens + 图片分辨率/面积计费。
稳定性与工程化:超时、重试、限流、观测
- 超时策略:流式由
context
控制总体超时;非流式可在http.Client
上设定请求超时。 - 幂等与重试:429/5xx 时指数退避,流式中断可在上层做“上下文回放 + 断点续推”(视模型与业务而定)。
- 速率限制:读取
Retry-After
或平台自定义限流头,按租户/模型维度做滑动窗口限速。 - 观测:打点记录“请求耗时、tokens 消耗、错误率、断线率、SSE 事件数”,便于容量规划与回归定位。
- 安全:密钥走 KMS/密管,运行时注入到环境变量或凭据服务,严禁入库或写死在代码里。
一个简单的 429/5xx 重试示例(可封装在 PostJSON 外层):
func WithRetry(do func() error) error {
backoff := []time.Duration{300 * time.Millisecond, 800 * time.Millisecond, 1500 * time.Millisecond}
var last error
for i := 0; i < len(backoff); i++ {
if err := do(); err != nil {
last = err
time.Sleep(backoff[i])
continue
}
return nil
}
return last
}
常见坑与排错清单
- 流式读不到数据:检查是否真的返回
text/event-stream
;有些平台需要专用头(如启用 SSE)或stream=true
。 - 非标准行:SSE 中可能混入心跳或注释行,解析前先
TrimSpace
并过滤非 JSON。 - 超时过短:图片/语音处理较慢,务必放宽服务器端等待与客户端超时。
- 证书/代理:公司网络下的 TLS 代理或自签证书需显式信任或跳过(不建议跳过,建议导入根证书)。
- Base64 太大:多图或大图时,建议压缩/裁剪;或传文件地址给同云侧对象存储(按官方支持)。
- 模型名不匹配/无权限:不同租户可见模型不同,先在控制台确认配额、模型名、地域。
- Content-Type 错:几乎都是
application/json
;上传文件的接口再改为 multipart/form-data。
结语与互动
通过一套“通用 HTTPS + Go 骨架”,可以较为平滑地接入 OpenAI、字节(火山方舟/豆包)、百度(文心千帆/ERNIE)、阿里(通义千问/DashScope)、MiniMax 等多家大模型服务,覆盖纯文本、流式、文生图、图生问、TTS 与多模态输入的主流场景。工程上关键在于抽象:把“非流式 POST JSON”“SSE 流式读取”“data URL 安全传图”“Access Token 管理”做成可复用组件,其余只是“换 URL 与字段”。
你已经在生产用哪几家?流式体验、稳定性与性价比如何?欢迎在评论区分享踩坑与最佳实践,也可以补充更多厂商的兼容/差异点,一起完善这份调用指南。
更多推荐
所有评论(0)