mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 08:18:19 +00:00
fix bug can not create image with minimax provider (#630)
This commit is contained in:
1 parent
4cd7b0a8d4
commit
b5c4d1de2a
2 files changed
+174
-30
No files matched your search
@@ -13,42 +13,46 @@ import (
|
||||
"github.com/nextlevelbuilder/goclaw/internal/providers"
|
||||
)
|
||||
|
||||
// callMinimaxImageGen calls the MiniMax image generation API.
|
||||
// Endpoint: POST {apiBase}/image_generation
|
||||
// Response: base64 image data in data.image_list[0].base64_image
|
||||
// aspectRatioToMinimaxSize converts aspect_ratio (e.g. "16:9") to MiniMax size format.
|
||||
// Falls back to explicit "size" param if set, otherwise uses aspect_ratio mapping.
|
||||
func aspectRatioToMinimaxSize(params map[string]any) string {
|
||||
// Explicit size param takes priority (from UI provider settings)
|
||||
// minimaxImageAspectRatio returns the aspect_ratio string for MiniMax image_generation.
|
||||
// See: https://platform.minimax.io/docs/guides/image-generation
|
||||
// Legacy chain settings may still pass "size" as "WIDTH*HEIGHT"; map those to ratios.
|
||||
func minimaxImageAspectRatio(params map[string]any) string {
|
||||
if s := GetParamString(params, "size", ""); s != "" {
|
||||
return s
|
||||
switch strings.ReplaceAll(s, " ", "") {
|
||||
case "1280*720":
|
||||
return "16:9"
|
||||
case "720*1280":
|
||||
return "9:16"
|
||||
case "1024*768":
|
||||
return "4:3"
|
||||
case "768*1024":
|
||||
return "3:4"
|
||||
case "1024*1024":
|
||||
return "1:1"
|
||||
}
|
||||
}
|
||||
ar := GetParamString(params, "aspect_ratio", "1:1")
|
||||
ar := GetParamString(params, "aspect_ratio", "")
|
||||
switch ar {
|
||||
case "16:9":
|
||||
return "1280*720"
|
||||
case "9:16":
|
||||
return "720*1280"
|
||||
case "4:3":
|
||||
return "1024*768"
|
||||
case "3:4":
|
||||
return "768*1024"
|
||||
case "1:1", "3:4", "4:3", "9:16", "16:9":
|
||||
return ar
|
||||
case "":
|
||||
return "1:1"
|
||||
default:
|
||||
return "1024*1024"
|
||||
return "1:1"
|
||||
}
|
||||
}
|
||||
|
||||
// callMinimaxImageGen calls the MiniMax image generation API.
|
||||
// Endpoint: POST {apiBase}/image_generation
|
||||
// Response: base64 strings in data.image_base64 (per official guide).
|
||||
func callMinimaxImageGen(ctx context.Context, apiKey, apiBase, model, prompt string, params map[string]any) ([]byte, *providers.Usage, error) {
|
||||
size := aspectRatioToMinimaxSize(params)
|
||||
promptOptimizer := GetParamBool(params, "prompt_optimizer", true)
|
||||
aspectRatio := minimaxImageAspectRatio(params)
|
||||
|
||||
body := map[string]any{
|
||||
"model": model,
|
||||
"prompt": prompt,
|
||||
"size": size,
|
||||
"num_images": 1,
|
||||
"enable_base64_output": true,
|
||||
"prompt_optimizer": promptOptimizer,
|
||||
"model": model,
|
||||
"prompt": prompt,
|
||||
"aspect_ratio": aspectRatio,
|
||||
"response_format": "base64",
|
||||
}
|
||||
|
||||
jsonBody, err := json.Marshal(body)
|
||||
@@ -81,7 +85,8 @@ func callMinimaxImageGen(ctx context.Context, apiKey, apiBase, model, prompt str
|
||||
|
||||
var minimaxResp struct {
|
||||
Data *struct {
|
||||
ImageList []struct {
|
||||
ImageBase64 []string `json:"image_base64"`
|
||||
ImageList []struct {
|
||||
Base64Image string `json:"base64_image"`
|
||||
} `json:"image_list"`
|
||||
} `json:"data"`
|
||||
@@ -99,13 +104,18 @@ func callMinimaxImageGen(ctx context.Context, apiKey, apiBase, model, prompt str
|
||||
minimaxResp.BaseResp.StatusCode, minimaxResp.BaseResp.StatusMsg)
|
||||
}
|
||||
|
||||
if minimaxResp.Data == nil || len(minimaxResp.Data.ImageList) == 0 {
|
||||
if minimaxResp.Data == nil {
|
||||
return nil, nil, fmt.Errorf("no image data in MiniMax response")
|
||||
}
|
||||
|
||||
b64 := minimaxResp.Data.ImageList[0].Base64Image
|
||||
var b64 string
|
||||
if len(minimaxResp.Data.ImageBase64) > 0 {
|
||||
b64 = minimaxResp.Data.ImageBase64[0]
|
||||
} else if len(minimaxResp.Data.ImageList) > 0 {
|
||||
b64 = minimaxResp.Data.ImageList[0].Base64Image
|
||||
}
|
||||
if b64 == "" {
|
||||
return nil, nil, fmt.Errorf("empty base64_image in MiniMax response")
|
||||
return nil, nil, fmt.Errorf("no image data in MiniMax response")
|
||||
}
|
||||
|
||||
imageBytes, err := base64.StdEncoding.DecodeString(b64)
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMinimaxImageAspectRatio(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
params map[string]any
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "explicit ratio",
|
||||
params: map[string]any{"aspect_ratio": "16:9"},
|
||||
want: "16:9",
|
||||
},
|
||||
{
|
||||
name: "default empty",
|
||||
params: map[string]any{},
|
||||
want: "1:1",
|
||||
},
|
||||
{
|
||||
name: "unknown ratio falls back to 1:1",
|
||||
params: map[string]any{"aspect_ratio": "21:9"},
|
||||
want: "1:1",
|
||||
},
|
||||
{
|
||||
name: "legacy size wins over aspect_ratio",
|
||||
params: map[string]any{"size": "1280*720", "aspect_ratio": "1:1"},
|
||||
want: "16:9",
|
||||
},
|
||||
{
|
||||
name: "legacy size 1024*1024",
|
||||
params: map[string]any{"size": "1024*1024"},
|
||||
want: "1:1",
|
||||
},
|
||||
{
|
||||
name: "unknown size ignored uses aspect_ratio",
|
||||
params: map[string]any{"size": "custom", "aspect_ratio": "9:16"},
|
||||
want: "9:16",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := minimaxImageAspectRatio(tt.params); got != tt.want {
|
||||
t.Fatalf("minimaxImageAspectRatio() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallMinimaxImageGen_ImageBase64Response(t *testing.T) {
|
||||
wantPNG := []byte{0x89, 0x50, 0x4e, 0x47}
|
||||
b64 := base64.StdEncoding.EncodeToString(wantPNG)
|
||||
|
||||
var gotAuth string
|
||||
var body map[string]any
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/v1/image_generation" {
|
||||
t.Errorf("path = %q, want /v1/image_generation", r.URL.Path)
|
||||
}
|
||||
gotAuth = r.Header.Get("Authorization")
|
||||
raw, _ := io.ReadAll(r.Body)
|
||||
if err := json.Unmarshal(raw, &body); err != nil {
|
||||
t.Errorf("request body: %v", err)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"data": map[string]any{"image_base64": []string{b64}},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
out, usage, err := callMinimaxImageGen(context.Background(), "test-key", srv.URL+"/v1", "image-01", "a prompt",
|
||||
map[string]any{"aspect_ratio": "16:9"})
|
||||
if err != nil {
|
||||
t.Fatalf("callMinimaxImageGen: %v", err)
|
||||
}
|
||||
if usage != nil {
|
||||
t.Fatalf("usage = %#v, want nil", usage)
|
||||
}
|
||||
if string(out) != string(wantPNG) {
|
||||
t.Fatalf("decoded bytes mismatch")
|
||||
}
|
||||
if gotAuth != "Bearer test-key" {
|
||||
t.Fatalf("Authorization = %q", gotAuth)
|
||||
}
|
||||
if body["model"] != "image-01" || body["prompt"] != "a prompt" ||
|
||||
body["aspect_ratio"] != "16:9" || body["response_format"] != "base64" {
|
||||
t.Fatalf("request fields = %#v", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallMinimaxImageGen_LegacyImageListFallback(t *testing.T) {
|
||||
want := []byte("x")
|
||||
b64 := base64.StdEncoding.EncodeToString(want)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"data": map[string]any{
|
||||
"image_list": []map[string]any{{"base64_image": b64}},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
out, _, err := callMinimaxImageGen(context.Background(), "k", srv.URL+"/v1", "m", "p", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(out) != string(want) {
|
||||
t.Fatalf("got %q, want %q", out, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallMinimaxImageGen_BaseRespError(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"base_resp": map[string]any{"status_code": 1004, "status_msg": "bad"},
|
||||
"data": map[string]any{"image_base64": []string{}},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
_, _, err := callMinimaxImageGen(context.Background(), "k", srv.URL+"/v1", "m", "p", nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user