工具失败此前只有一句自然语言(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)时套用,已分类的参数错误不套壳。 - 幻觉工具名的拒绝文案更具体:过去一律"工具未获本轮权限授权",现在指向"不存在的工具 + 只使用 本轮工具表里列出的工具"。 - 前端工具卡失败时显示归类标签,比统一的"执行失败"更能说明下一步该做什么。
58 lines
4.8 KiB
TypeScript
58 lines
4.8 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import { agentToolCategory, agentToolCategoryLabel, agentToolErrorClassLabel, agentToolStatus, friendlyAgentToolSummary } from "@/lib/canvas/agent-tool-presentation";
|
|
|
|
describe("Agent tool presentation", () => {
|
|
it("separates read, create, and canvas operation activity", () => {
|
|
expect(agentToolCategory("canvas_get_state", { eventType: "tool_completed" })).toBe("read");
|
|
expect(agentToolCategoryLabel("canvas_get_state", "read")).toBe("读取节点");
|
|
expect(agentToolCategory("generate_media", { eventType: "generation_task_created" })).toBe("create");
|
|
expect(agentToolCategory("canvas_apply_ops", { eventType: "canvas_updated", actions: [{ action: "updated" }] })).toBe("operate");
|
|
expect(agentToolCategory("canvas_apply_ops", { eventType: "canvas_updated", actions: [{ action: "created" }] })).toBe("create");
|
|
});
|
|
|
|
it("distinguishes media submission from a completed canvas result", () => {
|
|
expect(friendlyAgentToolSummary("generate_media", "", { eventType: "generation_task_created" })).toBe("媒体节点已创建,生成任务已提交");
|
|
expect(friendlyAgentToolSummary("generate_media", "", { eventType: "tool_completed" })).toBe("生成结果已回写画布节点");
|
|
expect(friendlyAgentToolSummary("generate_media", "", { eventType: "tool_failed" })).toBe("媒体生成未完成");
|
|
expect(friendlyAgentToolSummary("generate_media", "", { eventType: "tool_failed", result: { phase: "admission", taskSubmitted: false } })).toBe("生成请求未提交");
|
|
expect(friendlyAgentToolSummary("generate_media", "", { eventType: "tool_failed", result: { phase: "completion", status: "succeeded" } })).toBe("生成成功,画布回写未完成");
|
|
});
|
|
it("uses the failure event even without error keywords", () => {
|
|
const text = "技能未在本轮启用,或参考文件未包含在固定快照中";
|
|
const detail = { eventType: "tool_failed", skillName: "剧本撰写", path: "references/workflow.md" };
|
|
expect(agentToolStatus("skill_read_file", text, detail)).toBe("failed");
|
|
expect(friendlyAgentToolSummary("skill_read_file", text, detail)).toBe("读取参考资料失败 · 剧本撰写 · references/workflow.md");
|
|
});
|
|
it("does not interpret successful content containing error words as failure", () => {
|
|
const detail = { eventType: "tool_completed", skillName: "剧本撰写", path: "error-handling.md" };
|
|
expect(agentToolStatus("skill_read_file", "错误处理资料", detail)).toBe("completed");
|
|
expect(friendlyAgentToolSummary("skill_read_file", "错误处理资料", detail)).toBe("已读取参考资料 · 剧本撰写 · error-handling.md");
|
|
});
|
|
it("distinguishes empty directories, populated directories and file reads", () => {
|
|
for (const path of ["", undefined]) {
|
|
const detail = { eventType: "tool_completed", skillName: "剧本撰写", path, result: { files: [] as string[] } };
|
|
expect(friendlyAgentToolSummary("skill_read_file", "工具执行成功", detail)).toContain("无可读参考文件");
|
|
detail.result.files = ["a.md"];
|
|
expect(friendlyAgentToolSummary("skill_read_file", "工具执行成功", detail)).toContain("可读参考文件 1 个");
|
|
}
|
|
});
|
|
it("preserves other tools, approval summaries and legacy status fallback", () => {
|
|
expect(friendlyAgentToolSummary("canvas_apply_ops", "", undefined, true)).toBe("准备更新画布内容");
|
|
expect(friendlyAgentToolSummary("skills_load", "已从用户技能库固定加载 3 个技能")).toBe("已加载 3 个 Skills 技能");
|
|
expect(agentToolStatus("canvas_get_state", "失败")).toBe("failed");
|
|
expect(friendlyAgentToolSummary("canvas_get_state", "无法访问", { eventType: "tool_failed" })).toBe("获取画布内容失败");
|
|
});
|
|
});
|
|
|
|
describe("Agent tool error classification", () => {
|
|
it("maps backend errorClass to a human label, with a local fallback", () => {
|
|
expect(agentToolErrorClassLabel({ errorClass: "schema_error" })).toBe("参数不符合契约");
|
|
expect(agentToolErrorClassLabel({ result: { errorClass: "state_conflict" } })).toBe("画布状态已变化");
|
|
expect(agentToolErrorClassLabel({ errorClass: "permission_violation", errorClassLabel: "超出本轮权限" })).toBe("超出本轮权限");
|
|
expect(agentToolErrorClassLabel({ result: { errorClass: "upstream_failure" } })).toBe("上游故障");
|
|
expect(agentToolErrorClassLabel({ errorClass: "invalid_model_output" })).toBe("模型输出问题");
|
|
expect(agentToolErrorClassLabel({ errorClass: "unknown_new_class" })).toBe("工具执行失败");
|
|
expect(agentToolErrorClassLabel({ text: "没有归类" })).toBeUndefined();
|
|
});
|
|
});
|