Files
NianAIGC/backend/internal/providers/failure_diagnostics.go
T
2026-10-02 19:56:32 +08:00

151 lines
7.0 KiB
Go

package providers
import (
"encoding/json"
"net/http"
"regexp"
"strings"
)
var providerRequestIDInMessage = regexp.MustCompile(`(?i)\brequest[ _-]?id\s*[:=]\s*([A-Za-z0-9_.:-]{3,128})\b`)
// SanitizeFailureDetail retains a short provider explanation while removing
// submitted text, media links, credentials, and unrecognized quoted values.
// It is safe to call again after the original request has been discarded.
func SanitizeFailureDetail(message string, requestBody []byte, secrets ...string) string {
if strings.TrimSpace(message) == "" {
return ""
}
privateValues := append([]string(nil), secrets...)
var request any
if json.Unmarshal(requestBody, &request) == nil {
collectPrivateRequestValues(request, "", &privateValues)
}
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 {
return string(runes[:512]) + "...[truncated]"
}
return message
}
func collectPrivateRequestValues(value any, key string, values *[]string) {
switch typed := value.(type) {
case map[string]any:
for childKey, childValue := range typed {
collectPrivateRequestValues(childValue, strings.ToLower(childKey), values)
}
case []any:
for _, child := range typed {
collectPrivateRequestValues(child, key, values)
}
case string:
if strings.Contains(key, "prompt") || strings.Contains(key, "text") || strings.Contains(key, "url") || strings.Contains(key, "image") || strings.Contains(key, "video") || strings.Contains(key, "audio") || strings.Contains(key, "token") || strings.Contains(key, "secret") || strings.Contains(key, "key") || strings.Contains(key, "signature") || strings.Contains(key, "password") || strings.Contains(key, "credential") {
*values = append(*values, typed)
}
}
}
func requestIDFromMessage(message string) string {
match := providerRequestIDInMessage.FindStringSubmatch(message)
if len(match) < 2 {
return ""
}
return safeHTTPProviderDiagnosticToken(match[1], 128)
}
func safeProviderResponseDetail(raw, requestBody []byte, secrets ...string) string {
root := record(raw)
providerError := object(first(root["error"], root["Error"]))
output := object(root["output"])
data := object(root["data"])
task := object(first(root["task"], data["task"]))
nestedError := object(first(data["error"], task["error"], output["error"]))
metadataError := object(first(object(first(root["ResponseMetadata"], root["response_metadata"]))["Error"], object(first(root["ResponseMetadata"], root["response_metadata"]))["error"]))
message := stringValue(providerError["message"], providerError["Message"], nestedError["message"], nestedError["Message"], task["message"], output["message"], data["message"], metadataError["Message"], metadataError["message"], root["message"], root["Message"])
return SanitizeFailureDetail(message, requestBody, secrets...)
}
func successBusinessCode(code string) bool {
switch strings.ToLower(strings.TrimSpace(code)) {
case "", "0", "200", "ok", "success", "10000":
return true
}
return false
}
// Only explicit top-level or output error indicators count as a business
// rejection. A successful task can carry a normal informational message.
func inspectProviderBusinessRejection(raw []byte, headers http.Header, requestBody []byte, secrets ...string) (code, requestID, detail string, rejected bool) {
root := record(raw)
providerError := object(first(root["error"], root["Error"]))
output := object(root["output"])
data := object(root["data"])
task := object(first(root["task"], data["task"]))
nestedError := object(first(data["error"], task["error"], output["error"]))
code, requestID, _ = inspectHTTPProviderFailure(raw, headers)
message := stringValue(providerError["message"], providerError["Message"], nestedError["message"], nestedError["Message"], output["message"], data["message"], root["message"])
if (len(providerError) > 0 || len(nestedError) > 0) && (code != "" || message != "") {
rejected = true
} else if rootCode := safeHTTPProviderDiagnosticToken(first(root["code"], root["Code"]), 64); rootCode != "" && !successBusinessCode(rootCode) {
rejected = true
} else if outputCode := safeHTTPProviderDiagnosticToken(first(output["code"], output["Code"]), 64); outputCode != "" && !successBusinessCode(outputCode) {
rejected = true
} else if strings.EqualFold(stringValue(root["type"]), "error") && message != "" {
rejected = true
}
if !rejected {
return "", "", "", false
}
return code, requestID, SanitizeFailureDetail(message, requestBody, secrets...), true
}
func annotateTerminalFailure(result Result, raw []byte, headers http.Header, requestBody []byte, secrets ...string) Result {
if result.Status != StatusFailed && result.Status != StatusExpired && result.Status != StatusCancelled {
return result
}
root := record(raw)
business := object(first(root["Result"], root["result"]))
data := object(root["data"])
businessData := object(first(business["data"], business["Data"]))
output := object(root["output"])
task := object(root["task"])
providerError := object(first(task["error"], data["error"], businessData["error"], businessData["Error"], business["error"], business["Error"], output["error"], root["error"], root["Error"]))
code, requestID, _ := inspectHTTPProviderFailure(raw, headers)
if nestedCode := firstSafeHTTPProviderDiagnosticToken(64, providerError["code"], providerError["Code"], task["error_code"], data["error_code"], businessData["error_code"], business["error_code"]); nestedCode != "" {
code = nestedCode
}
if nestedID := firstSafeHTTPProviderDiagnosticToken(128, providerError["request_id"], providerError["requestId"], providerError["RequestId"], task["request_id"], data["request_id"], businessData["request_id"], business["request_id"]); nestedID != "" {
requestID = nestedID
}
message := stringValue(providerError["message"], providerError["Message"], task["error_message"], task["message"], output["message"], data["error_message"], data["message"], businessData["error_message"], businessData["message"], business["message"], root["message"], result.ErrorMessage)
if requestID == "" {
requestID = requestIDFromMessage(message)
}
result.ErrorCode = code
result.RequestID = requestID
result.ErrorDetail = SanitizeFailureDetail(message, requestBody, secrets...)
result.ErrorMessage = SanitizeFailureDetail(result.ErrorMessage, requestBody, secrets...)
return result
}