Files
NianAIGC/backend/internal/providers/minimax_diagnostics.go
T
2026-09-11 15:33:18 +08:00

115 lines
4.1 KiB
Go

package providers
import (
"encoding/json"
"errors"
"log"
"net/url"
"regexp"
"strconv"
"strings"
"unicode/utf8"
)
var (
minimaxDiagnosticURL = regexp.MustCompile(`(?i)(?:https?://|data:)[^\s"'<>]+`)
minimaxDiagnosticCredential = regexp.MustCompile(`(?i)(?:bearer\s+|(?:authorization|api[_-]?key|access[_-]?key|secret|token|password|signature|credential)\s*["']?\s*[:=]\s*["']?(?:bearer\s+)?)[^\s,"';}]+`)
minimaxDiagnosticQuoted = regexp.MustCompile("\"(?:[^\"\\\\]|\\\\.)*\"|'[^']*'|`[^`]*`")
minimaxDiagnosticLongToken = regexp.MustCompile(`[A-Za-z0-9_+/=.-]{32,}`)
)
// Preserve the upstream validation explanation, but remove request content,
// credentials, URLs and arbitrary quoted values before it reaches logs or DB.
func minimaxSafeFailureDetail(responseBody, requestBody []byte, apiKey string) string {
root := record(responseBody)
message := stringValue(object(root["error"])["message"], root["message"])
if message == "" {
return ""
}
privateValues := []string{apiKey}
request := record(requestBody)
content, _ := request["content"].([]any)
for _, item := range content {
entry := object(item)
privateValues = append(privateValues, stringValue(entry["text"]))
for _, kind := range []string{"image_url", "video_url", "audio_url"} {
privateValues = append(privateValues, stringValue(object(entry[kind])["url"]))
}
}
for _, value := range privateValues {
if value == "" {
continue
}
message = strings.ReplaceAll(message, value, "[redacted]")
encoded, _ := json.Marshal(value)
if len(encoded) > 2 {
message = strings.ReplaceAll(message, string(encoded[1:len(encoded)-1]), "[redacted]")
}
}
message = minimaxDiagnosticURL.ReplaceAllString(message, "[redacted]")
message = minimaxDiagnosticCredential.ReplaceAllString(message, "[redacted]")
message = minimaxDiagnosticQuoted.ReplaceAllStringFunc(message, func(quoted string) string {
value := quoted[1 : len(quoted)-1]
if minimaxDiagnosticValue(value) != "unrecognized" {
return quoted
}
return "[redacted]"
})
message = minimaxDiagnosticLongToken.ReplaceAllString(message, "[redacted]")
message = strings.Join(strings.Fields(message), " ")
runes := []rune(message)
if len(runes) > 512 {
message = string(runes[:512]) + "...[truncated]"
}
return message
}
func logMinimaxFailedRequest(request Request, config Config, err error) {
var failure *ProviderError
if !errors.As(err, &failure) {
return
}
// All strings except the request ID are fixed enums. In particular, never
// log custom endpoints, prompts or even the hosts of reference image URLs.
endpointClass := "custom"
basePathClass := "custom"
if endpoint, parseErr := url.Parse(config.BaseURL); parseErr == nil {
if endpoint.Hostname() == "api.minimax.cn" {
endpointClass = "official-cn"
}
switch strings.Trim(endpoint.Path, "/") {
case "":
basePathClass = "root"
case "v1", "v2":
basePathClass = "version-prefix"
}
}
imageItems := 0
if len(request.Materials) > 0 || len(request.InputURLs) > 0 {
imageItems = 1
}
duration := "invalid"
if encoded, encodeErr := json.Marshal(request.Settings["duration"]); encodeErr == nil {
if value, parseErr := strconv.Atoi(string(encoded)); parseErr == nil && value >= 4 && value <= 15 {
duration = strconv.Itoa(value)
}
}
log.Printf(
"zhinian-api MiniMax submit request summary requestId=%q model=%q resolution=%q duration=%s ratio=%q textItems=1 promptChars=%d imageItems=%d endpointClass=%s basePathClass=%s",
failure.RequestID, minimaxDiagnosticValue(requestModel(request, config.Model)),
minimaxDiagnosticValue(stringValue(request.Settings["resolution"])), duration,
minimaxDiagnosticValue(stringValue(request.Settings["ratio"])), utf8.RuneCountInString(request.Prompt),
imageItems, endpointClass, basePathClass,
)
}
func minimaxDiagnosticValue(value string) string {
switch value {
case MinimaxH3Model, "MiniMax-H3-Max", "768P", "2K", "480P", "adaptive", "21:9", "16:9", "4:3", "1:1", "3:4", "9:16",
"model", "content", "text", "prompt", "resolution", "duration", "ratio", "image_url", "video_url", "audio_url", "role", "first_frame", "last_frame", "aigc_watermark":
return value
default:
return "unrecognized"
}
}