Sync deployment documentation to the final successful SAE state and clear pending deployment checklist items. Include backend/frontend/R hardening and diagnostics improvements required for stable production behavior. Made-with: Cursor
210 lines
7.4 KiB
R
210 lines
7.4 KiB
R
# utils/error_codes.R
|
||
# 结构化错误码,便于 LLM 自愈
|
||
|
||
ERROR_CODES <- list(
|
||
# 业务错误(可被 Planner 修复)
|
||
E001_COLUMN_NOT_FOUND = list(
|
||
code = "E001",
|
||
type = "business",
|
||
message_template = "列名 '{col}' 在数据中不存在",
|
||
user_hint = "请检查变量名是否拼写正确"
|
||
),
|
||
E002_TYPE_MISMATCH = list(
|
||
code = "E002",
|
||
type = "business",
|
||
message_template = "列 '{col}' 类型应为 {expected},实际为 {actual}",
|
||
user_hint = "该列包含非数值数据,请检查数据格式"
|
||
),
|
||
E003_INSUFFICIENT_GROUPS = list(
|
||
code = "E003",
|
||
type = "business",
|
||
message_template = "分组变量 '{col}' 应有 {expected} 个水平,实际有 {actual} 个",
|
||
user_hint = "分组变量的取值个数不符合要求"
|
||
),
|
||
E004_SAMPLE_TOO_SMALL = list(
|
||
code = "E004",
|
||
type = "business",
|
||
message_template = "样本量 {n} 不足,至少需要 {min_required}",
|
||
user_hint = "数据量太少,无法进行统计分析"
|
||
),
|
||
|
||
# 统计计算错误(用户友好映射)
|
||
E005_SINGULAR_MATRIX = list(
|
||
code = "E005",
|
||
type = "business",
|
||
message_template = "矩阵计算异常: {details}",
|
||
user_hint = "变量之间可能存在多重共线性,建议移除高度相关的变量"
|
||
),
|
||
E006_CONVERGENCE_FAILED = list(
|
||
code = "E006",
|
||
type = "business",
|
||
message_template = "模型未能收敛: {details}",
|
||
user_hint = "算法未能找到稳定解,可能需要调整参数或检查数据"
|
||
),
|
||
E007_VARIANCE_ZERO = list(
|
||
code = "E007",
|
||
type = "business",
|
||
message_template = "变量 '{col}' 方差为零",
|
||
user_hint = "该列的所有值都相同,无法进行比较"
|
||
),
|
||
|
||
# 系统错误(需人工介入)
|
||
E100_INTERNAL_ERROR = list(
|
||
code = "E100",
|
||
type = "system",
|
||
message_template = "内部错误: {details}",
|
||
user_hint = "系统繁忙,请稍后重试"
|
||
),
|
||
E101_PACKAGE_MISSING = list(
|
||
code = "E101",
|
||
type = "system",
|
||
message_template = "缺少依赖包: {package}",
|
||
user_hint = "请联系管理员"
|
||
),
|
||
E102_FUNCTION_NOT_FOUND = list(
|
||
code = "E102",
|
||
type = "business",
|
||
message_template = "找不到函数: {func}",
|
||
user_hint = "请检查函数名是否正确,或确认已加载相关包"
|
||
)
|
||
)
|
||
|
||
# R 原始错误到错误码的映射字典
|
||
R_ERROR_MAPPING <- list(
|
||
"system is computationally singular" = "E005_SINGULAR_MATRIX",
|
||
"did not converge" = "E006_CONVERGENCE_FAILED",
|
||
"constant" = "E007_VARIANCE_ZERO",
|
||
"object '.*' not found" = "E001_COLUMN_NOT_FOUND",
|
||
"undefined columns" = "E001_COLUMN_NOT_FOUND",
|
||
"subscript out of bounds" = "E100_INTERNAL_ERROR",
|
||
"cannot coerce" = "E002_TYPE_MISMATCH",
|
||
"non-numeric argument" = "E002_TYPE_MISMATCH",
|
||
"not meaningful for factors" = "E002_TYPE_MISMATCH",
|
||
"missing value where TRUE/FALSE needed" = "E100_INTERNAL_ERROR",
|
||
"replacement has" = "E100_INTERNAL_ERROR",
|
||
"could not find function" = "E102_FUNCTION_NOT_FOUND",
|
||
"there is no package called" = "E101_PACKAGE_MISSING",
|
||
"cannot open the connection" = "E100_INTERNAL_ERROR",
|
||
"singular gradient" = "E005_SINGULAR_MATRIX",
|
||
"rank deficien" = "E005_SINGULAR_MATRIX",
|
||
"contrasts can be applied only to factors" = "E002_TYPE_MISMATCH",
|
||
"need at least 2 observations" = "E004_SAMPLE_TOO_SMALL",
|
||
"not enough observations" = "E004_SAMPLE_TOO_SMALL",
|
||
"sample size must be" = "E004_SAMPLE_TOO_SMALL",
|
||
"groups with fewer than" = "E004_SAMPLE_TOO_SMALL"
|
||
)
|
||
|
||
# 构造错误响应(含用户友好提示)
|
||
make_error <- function(error_def, ...) {
|
||
params <- list(...)
|
||
msg <- error_def$message_template
|
||
for (name in names(params)) {
|
||
msg <- gsub(paste0("\\{", name, "\\}"), as.character(params[[name]]), msg)
|
||
}
|
||
return(list(
|
||
status = "error",
|
||
error_code = error_def$code,
|
||
error_type = error_def$type,
|
||
message = msg,
|
||
user_hint = error_def$user_hint
|
||
))
|
||
}
|
||
|
||
#' Agent 通道专用:结构化错误格式
|
||
#' 提取行号、错误分类、修复建议,方便 LLM 理解并修复
|
||
format_agent_error <- function(e, code, warnings = list(), messages = character(0)) {
|
||
raw_msg <- conditionMessage(e)
|
||
|
||
error_line <- NULL
|
||
line_match <- regmatches(raw_msg, regexpr("(line \\d+|at \\d+:\\d+|:( *)\\d+)", raw_msg))
|
||
if (length(line_match) > 0 && nchar(line_match[1]) > 0) {
|
||
nums <- regmatches(line_match[1], gregexpr("\\d+", line_match[1]))[[1]]
|
||
error_line <- as.integer(nums[1])
|
||
}
|
||
|
||
error_context <- NULL
|
||
if (!is.null(error_line) && !is.null(code)) {
|
||
code_lines <- strsplit(code, "\n")[[1]]
|
||
start <- max(1, error_line - 3)
|
||
end <- min(length(code_lines), error_line + 3)
|
||
context_lines <- paste0(
|
||
ifelse(start:end == error_line, ">>> ", " "),
|
||
sprintf("%3d| ", start:end),
|
||
code_lines[start:end]
|
||
)
|
||
error_context <- paste(context_lines, collapse = "\n")
|
||
}
|
||
|
||
error_code <- "E_EXEC"
|
||
error_type <- "runtime"
|
||
user_hint <- raw_msg
|
||
|
||
for (pattern in names(R_ERROR_MAPPING)) {
|
||
if (grepl(pattern, raw_msg, ignore.case = TRUE)) {
|
||
key <- R_ERROR_MAPPING[[pattern]]
|
||
info <- ERROR_CODES[[key]]
|
||
error_code <- info$code
|
||
error_type <- info$type
|
||
user_hint <- paste0(info$user_hint, " | ", raw_msg)
|
||
break
|
||
}
|
||
}
|
||
|
||
friendly_msg <- paste0(
|
||
"[", error_code, "] ", raw_msg,
|
||
if (!is.null(error_line)) paste0(" (约第 ", error_line, " 行)") else "",
|
||
if (length(warnings) > 0) paste0("\n[Warnings] ", paste(utils::head(warnings, 5), collapse = "; ")) else ""
|
||
)
|
||
|
||
list(
|
||
message = friendly_msg,
|
||
raw_error = raw_msg,
|
||
error_code = error_code,
|
||
error_type = error_type,
|
||
error_line = error_line,
|
||
error_context = error_context,
|
||
user_hint = user_hint,
|
||
warnings = if (length(warnings) > 0) warnings else NULL
|
||
)
|
||
}
|
||
|
||
# 尝试将 R 原始错误映射为友好错误码
|
||
map_r_error <- function(raw_error_msg) {
|
||
for (pattern in names(R_ERROR_MAPPING)) {
|
||
if (grepl(pattern, raw_error_msg, ignore.case = TRUE)) {
|
||
error_key <- R_ERROR_MAPPING[[pattern]]
|
||
|
||
# E101: 提取缺失包名(there is no package called 'xxx')
|
||
if (error_key == "E101_PACKAGE_MISSING") {
|
||
pkg <- "unknown"
|
||
m <- regexec("there is no package called ['\"]([^'\"]+)['\"]", raw_error_msg, ignore.case = TRUE)
|
||
mm <- regmatches(raw_error_msg, m)[[1]]
|
||
if (length(mm) >= 2) pkg <- mm[2]
|
||
return(make_error(ERROR_CODES[[error_key]], package = pkg))
|
||
}
|
||
|
||
# E102: 提取找不到的函数名(could not find function "xxx")
|
||
if (error_key == "E102_FUNCTION_NOT_FOUND") {
|
||
func <- "unknown"
|
||
m <- regexec("could not find function ['\"]([^'\"]+)['\"]", raw_error_msg, ignore.case = TRUE)
|
||
mm <- regmatches(raw_error_msg, m)[[1]]
|
||
if (length(mm) >= 2) func <- mm[2]
|
||
return(make_error(ERROR_CODES[[error_key]], func = func))
|
||
}
|
||
|
||
# E001: 尝试提取缺失对象名(object 'xxx' not found)
|
||
if (error_key == "E001_COLUMN_NOT_FOUND") {
|
||
col <- "unknown"
|
||
m <- regexec("object ['\"]([^'\"]+)['\"] not found", raw_error_msg, ignore.case = TRUE)
|
||
mm <- regmatches(raw_error_msg, m)[[1]]
|
||
if (length(mm) >= 2) col <- mm[2]
|
||
return(make_error(ERROR_CODES[[error_key]], col = col))
|
||
}
|
||
|
||
return(make_error(ERROR_CODES[[error_key]], details = raw_error_msg))
|
||
}
|
||
}
|
||
# 无法映射,返回通用内部错误
|
||
return(make_error(ERROR_CODES$E100_INTERNAL_ERROR, details = raw_error_msg))
|
||
}
|