refactor(*): 平台架构 - 拆分后端领域模块与画布前端组件并补充测试
This commit is contained in:
82
backend/internal/app/cloud_agent_runtime_errors.go
Normal file
82
backend/internal/app/cloud_agent_runtime_errors.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// 画布 Agent 面向用户的错误文案净化。
|
||||
//
|
||||
// 模型、工具与上游媒体服务的原始错误可能包含密钥、内部地址或冗长堆栈;
|
||||
// 这里统一转换成可读、可展示的中文说明,原始错误只进诊断日志。
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"infinite-canvas/backend/internal/model"
|
||||
)
|
||||
|
||||
// Only expose known failure categories; raw provider errors can contain URLs and credentials.
|
||||
func cloudAgentModelFailure(task *model.Task) (string, string) {
|
||||
detail, reason := "模型任务未成功", "model_task_failed"
|
||||
if diagnostic := taskExecutionDiagnostic(task); diagnostic != nil && diagnostic.Code == string(ReasonUpstreamDNSFailed) {
|
||||
return "模型服务域名解析失败,请检查渠道域名和后端 DNS 配置;本轮已停止。请在任务中心检查模型任务 " + task.ID, string(ReasonUpstreamDNSFailed)
|
||||
}
|
||||
raw := strings.ToLower(task.Error)
|
||||
switch {
|
||||
case strings.Contains(task.Error, "没有返回内容"):
|
||||
// 思考模型的典型失败:整个输出预算被推理吃掉,正文与工具调用皆空。
|
||||
detail, reason = "上游连续返回空内容(通常是思考占满输出预算);已自动关思考并放大预算重试仍失败,建议换用非思考模型或调小上下文", "model_empty_output"
|
||||
case strings.Contains(task.Error, cloudAgentStepTimeoutError):
|
||||
detail, reason = "单步模型调用超过执行时限仍未返回(长思考或上下文过大时常见);已自动关思考重试仍超时,可在管理端调大 Agent 单步超时", "model_step_timeout"
|
||||
case strings.Contains(raw, "connection reset by peer"):
|
||||
detail, reason = "模型连接被对端或中间网络设备重置", "model_connection_reset"
|
||||
case strings.Contains(raw, "timeout"), strings.Contains(raw, "deadline exceeded"):
|
||||
detail, reason = "模型请求超时", "model_request_timeout"
|
||||
case strings.Contains(raw, "connection refused"):
|
||||
detail, reason = "无法连接模型服务(连接被拒绝)", "model_connection_refused"
|
||||
}
|
||||
return detail + ";本轮已停止。请在任务中心检查模型任务 " + task.ID, reason
|
||||
}
|
||||
|
||||
func cloudAgentSafeToolError(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
var readLoopErr *cloudAgentReadLoopError
|
||||
if errors.As(err, &readLoopErr) {
|
||||
return readLoopErr.Error()
|
||||
}
|
||||
var appErr *AppError
|
||||
if errors.As(err, &appErr) && appErr != nil {
|
||||
message := strings.TrimSpace(appErr.Message)
|
||||
if cloudAgentSafeUserMessage(message) {
|
||||
return message
|
||||
}
|
||||
}
|
||||
return "工具执行失败,请检查输入或稍后重试"
|
||||
}
|
||||
|
||||
func cloudAgentSafeUserMessage(message string) bool {
|
||||
if message == "" || !utf8.ValidString(message) || strings.ContainsAny(message, "\x00\r\n") || utf8.RuneCountInString(message) > 240 {
|
||||
return false
|
||||
}
|
||||
lower := strings.ToLower(message)
|
||||
for _, marker := range []string{
|
||||
"http://", "https://", "ftp://", "file://", "authorization", "cookie", "secret", "token", "api_key", "apikey", "x-api-key",
|
||||
"/var/", "/tmp/", "\\", "stack trace", "traceback", " at ", "sql:", "sqlite", "postgres",
|
||||
} {
|
||||
if strings.Contains(lower, marker) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// cloudAgentSafeMediaTaskError preserves a short, user-facing task diagnostic
|
||||
// while refusing provider details that commonly contain URLs, credentials, or
|
||||
// internal request metadata. Task.Error is not a safe presentation field.
|
||||
func cloudAgentSafeMediaTaskError(task *model.Task) string {
|
||||
if task == nil || strings.TrimSpace(task.Error) == "" {
|
||||
return "媒体任务未成功"
|
||||
}
|
||||
// 与画布节点展示同一套分类:网络/审核/存储/HTTP 状态归类,可读的供应商原因原样保留。
|
||||
return userFacingTaskError(task.Error)
|
||||
}
|
||||
Reference in New Issue
Block a user