fix(ai): 创意生成工作流失败时不再把原始报文吐给用户
Dify 以 workflow_finished + data.status=failed + outputs={} 收场时,后端只搬运
字段、从不读 status,把一个失败结果包成 success 下发,且把整包报文塞进 raw;
前端 formatCreativeResult 取不到业务字段就落到 text || stringifyUnknown(payload),
整段原始 JSON(含 workflow_id / task_id / req_id 与上游错误栈)进了聊天气泡。
- 新增 workflowStatus / isWorkflowFailed:失败时只回 status + 可读 answer,
不下发 outputs / raw,不携带上游错误文本
- executeCreativeAssistant 的 workflow_finished 分支先判失败,命中则直接返回,
不再用流式片段或 taskId 覆盖失败文案
- 失败报文顶层 answer 是 workflow_run_id(UUID),故失败文案用固定字符串,
不取自 answer
- 新增 AiCreativeFailureResultTest:13 个用例,样本为 issue 原文的真实失败报文
Refs: .scratch/ai-assistant/issues/01-creative-assistant-raw-json-bubble.md
This commit is contained in:
@@ -53,6 +53,14 @@ public class AiController extends BaseController {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AiController.class);
|
||||
private static final long AI_REQUEST_TIMEOUT = 10 * 60 * 1000L;
|
||||
|
||||
/**
|
||||
* 创意生成工作流以失败收场时给用户看的文案。
|
||||
*
|
||||
* <p>上游的真实失败原因(如 {@code Model disabled})属于 Dify 侧配置问题,
|
||||
* 既不面向用户也不该泄露;这里统一给一句可读提示。
|
||||
*/
|
||||
private static final String CREATIVE_FAILED_MESSAGE = "生成失败,请稍后重试";
|
||||
|
||||
@Resource
|
||||
private WebSocketServer webSocketServer;
|
||||
@Resource
|
||||
@@ -847,6 +855,14 @@ public class AiController extends BaseController {
|
||||
}
|
||||
if ("message_end".equals(event) || "workflow_finished".equals(event)) {
|
||||
Map<String, Object> result = buildCreativeAssistantResult(data, json);
|
||||
if (isWorkflowFailed(stringValue(result.get("status")))) {
|
||||
// 失败态:buildCreativeAssistantResult 已给好可读文案,
|
||||
// 这里不得再用流式片段或 taskId 覆盖,也不下发 raw。
|
||||
clearActiveAiTask(loginUserId, taskId);
|
||||
logger.warn("creative assistant workflow failed, workflowRunId={}, taskId={}",
|
||||
workflowRunId, taskId);
|
||||
return success(result);
|
||||
}
|
||||
if (StrUtil.isBlank(stringValue(result.get("answer"))) && reply.length() > 0) {
|
||||
result.put("answer", reply.toString());
|
||||
}
|
||||
@@ -1301,6 +1317,17 @@ public class AiController extends BaseController {
|
||||
|
||||
private Map<String, Object> buildCreativeAssistantResult(Map<String, Object> responseData, String responseBody) {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
|
||||
String status = workflowStatus(responseData);
|
||||
if (isWorkflowFailed(status)) {
|
||||
// 工作流失败:只回可读文案与状态,不下发 outputs / raw / 上游错误栈。
|
||||
// raw 里含 workflow_id / task_id / req_id 与上游异常文本,端上没有任何消费者,
|
||||
// 一旦下发就会被前端兜底逻辑整段贴进聊天气泡(见 issue 01)。
|
||||
result.put("status", status);
|
||||
result.put("answer", CREATIVE_FAILED_MESSAGE);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (responseData == null) {
|
||||
result.put("answer", responseBody);
|
||||
return result;
|
||||
@@ -1326,7 +1353,8 @@ public class AiController extends BaseController {
|
||||
|
||||
Object data = responseData.get("data");
|
||||
if (data instanceof Map) {
|
||||
Object status = ((Map) data).get("status");
|
||||
// status 已在方法开头经 workflowStatus 统一取出,
|
||||
// 这里只负责下发(含顶层 status 的兜底情况)。
|
||||
if (status != null) {
|
||||
result.put("status", status);
|
||||
}
|
||||
@@ -1343,6 +1371,41 @@ public class AiController extends BaseController {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取 Dify 工作流事件里的执行状态。
|
||||
*
|
||||
* <p>{@code workflow_finished} 事件把状态放在 {@code data.status};
|
||||
* 少数事件会放在顶层,两处都看,取到即返回。
|
||||
*
|
||||
* @return 状态字符串;取不到时为 {@code null}
|
||||
*/
|
||||
private String workflowStatus(Map<String, Object> responseData) {
|
||||
if (responseData == null) {
|
||||
return null;
|
||||
}
|
||||
Object data = responseData.get("data");
|
||||
if (data instanceof Map) {
|
||||
Object status = ((Map) data).get("status");
|
||||
if (status != null && StrUtil.isNotBlank(status.toString())) {
|
||||
return status.toString();
|
||||
}
|
||||
}
|
||||
Object status = responseData.get("status");
|
||||
if (status != null && StrUtil.isNotBlank(status.toString())) {
|
||||
return status.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断工作流是否以失败收场。
|
||||
*
|
||||
* <p>只在拿到明确失败状态时为真;状态缺失视为未失败,避免误伤正常响应。
|
||||
*/
|
||||
private boolean isWorkflowFailed(String status) {
|
||||
return "failed".equalsIgnoreCase(StrUtil.trimToEmpty(status));
|
||||
}
|
||||
|
||||
private Map<String, Object> buildChatMessageResult(Map<String, Object> responseData, String responseBody) {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
if (responseData == null) {
|
||||
|
||||
Reference in New Issue
Block a user