fix(cloud-agent): 工具失败归类与可行动字段
工具失败此前只有一句自然语言(tool_failed + text),无法区分"模型自己出的错"和真实的 权限/状态/上游问题:模型拿不到可自纠的反馈,排查时分不清该改提示词、改权限还是等上游。 - 新增 backend/internal/app/cloud_agent_tool_error_class.go:把失败归成 invalid_model_output(幻觉工具名;本轮契约之外、只被 contextScope 挡掉的调用)、 schema_error(缺必填字段/类型错/未知字段/非法 JSON)、state_conflict(画布在读取之后被改动)、 permission_violation(只读轮发起写入等真实授权边界)、upstream_failure(上游 4xx/5xx、 超时、媒体上游拒绝),其余归 tool_error。 - 每类附 retryable 与稳定的 requiredAction(fix_arguments / reread_canvas / use_advertised_tools / ask_user / report_to_user),写进 tool_failed 的回执与载荷顶层。 只加标注:没有任何放行或拒绝逻辑读它。 - 缺必填字段的通用判定:各工具的校验点抛的多是普通 AppError,从错误类型看不出"模型漏了字段"。 现在用本轮实际暴露的 schema 的 required 与参数比对,命中即按参数契约错误处理(回传 schema 与 guidance 供模型自纠);只在业务侧确实按参数问题拒绝(400/422)时套用,已分类的参数错误不套壳。 - 幻觉工具名的拒绝文案更具体:过去一律"工具未获本轮权限授权",现在指向"不存在的工具 + 只使用 本轮工具表里列出的工具"。 - 前端工具卡失败时显示归类标签,比统一的"执行失败"更能说明下一步该做什么。
This commit is contained in:
@@ -4,21 +4,25 @@ export const AGENT_TOOL_METADATA: Record<string, { summary: string | ((context:
|
||||
canvas_list_node_types: { summary: "已读取可用节点类型", failureMessage: "获取可用节点类型失败" },
|
||||
canvas_get_state: { summary: "已读取当前画布", failureMessage: "获取画布内容失败" },
|
||||
task_get: { summary: "已查询任务状态", failureMessage: "查询任务状态失败" },
|
||||
canvas_apply_ops: { summary: ({ pending }) => pending ? "准备更新画布内容" : "画布内容已保存至服务端", failureMessage: "更新画布内容失败" },
|
||||
canvas_apply_ops: { summary: ({ pending }) => (pending ? "准备更新画布内容" : "画布内容已保存至服务端"), failureMessage: "更新画布内容失败" },
|
||||
model_list: { summary: "已获取可用模型", failureMessage: "获取可用模型失败" },
|
||||
generate_media: { summary: ({ pending, detail }) => pending ? "准备创建媒体节点并生成" : field(detail, "eventType") === "tool_completed" ? "生成结果已回写画布节点" : "媒体节点已创建,生成任务已提交", failureMessage: "媒体生成未完成" },
|
||||
generate_media: { summary: ({ pending, detail }) => (pending ? "准备创建媒体节点并生成" : field(detail, "eventType") === "tool_completed" ? "生成结果已回写画布节点" : "媒体节点已创建,生成任务已提交"), failureMessage: "媒体生成未完成" },
|
||||
};
|
||||
|
||||
export type AgentToolCategory = "read" | "create" | "operate";
|
||||
|
||||
function record(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : {};
|
||||
}
|
||||
|
||||
function toolArguments(detail?: unknown) {
|
||||
const value = record(detail).arguments;
|
||||
if (typeof value !== "string") return record(value);
|
||||
try { return record(JSON.parse(value)); } catch { return {}; }
|
||||
try {
|
||||
return record(JSON.parse(value));
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,3 +92,27 @@ export function friendlyAgentToolSummary(toolName: string, text: string, detail?
|
||||
const failure = metadata?.failureMessage;
|
||||
return failed ? failure || "操作未完成" : summary || (pending ? "准备执行操作" : "操作已完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具失败的稳定归类标签(后端 `errorClass`,见 handoff 工作项 B 的第一步)。
|
||||
* 界面上把"模型自己出的错"和"真实的权限/状态/上游问题"分开显示,用户与排查都不用猜。
|
||||
*/
|
||||
export const AGENT_TOOL_ERROR_CLASS_LABELS: Record<string, string> = {
|
||||
invalid_model_output: "模型输出问题",
|
||||
schema_error: "参数不符合契约",
|
||||
state_conflict: "画布状态已变化",
|
||||
permission_violation: "超出本轮权限",
|
||||
upstream_failure: "上游故障",
|
||||
tool_error: "工具执行失败",
|
||||
};
|
||||
|
||||
/** 从工具事件载荷里取归类标签:优先用后端给的 label,其次查本地映射。 */
|
||||
export function agentToolErrorClassLabel(detail?: unknown): string | undefined {
|
||||
const payload = record(detail);
|
||||
const nested = record(payload.result);
|
||||
const label = payload.errorClassLabel ?? nested.errorClassLabel;
|
||||
if (typeof label === "string" && label) return label;
|
||||
const errorClass = payload.errorClass ?? nested.errorClass;
|
||||
if (typeof errorClass !== "string" || !errorClass) return undefined;
|
||||
return AGENT_TOOL_ERROR_CLASS_LABELS[errorClass] ?? "工具执行失败";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user