{"record":{"id":"ccf6f8d98130276a","repo":"yikart/AiToEarn","slug":"aicallfailed-ccf6f8","errorCode":"AiCallFailed","errorMessage":"任务结果为空","messagePattern":"任务结果为空","errorType":"error_code","errorClass":"AppException","httpStatus":400,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/libs/volcengine/services/direct-edit.service.ts","lineNumber":80,"sourceCode":"    this.checkApiResponseError(response, 'Submit direct edit task', requestData)\n\n    return response.Result\n  }\n\n  /**\n   * 处理剪辑任务查询结果\n   * @param result API 返回的原始结果\n   * @param reqId 请求的任务 ID\n   * @returns 处理后的任务结果\n   */\n  private processDirectEditResult(\n    result: GetDirectEditResultResponse | null | undefined,\n    reqId: string,\n  ): GetDirectEditResultItem {\n    // 处理空结果\n    if (!result) {\n      this.logger.error({ reqId }, '[ProcessDirectEditResult] Result 为空')\n      throw new AppException(ResponseCode.AiCallFailed, '任务结果为空')\n    }\n\n    // 如果 Result 是数组\n    if (Array.isArray(result)) {\n      if (result.length === 0) {\n        // 任务刚提交，火山引擎还没准备好数据，返回处理中状态\n        this.logger.debug({ reqId }, '[ProcessDirectEditResult] 任务结果为空数组，任务可能刚提交还在准备中')\n        return {\n          TaskId: reqId,\n          ReqId: reqId,\n          Application: 'VideoTrackToB',\n          Status: 'Processing',\n        }\n      }\n\n      return result[0]\n    }\n","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/libs/volcengine/services/direct-edit.service.ts#L62-L98","documentation":"Volcengine DirectEdit 的 getDirectEditResult 调用 GetDirectEditResult 后，processDirectEditResult 收到的 response.Result 为 null/undefined。库把『结果整体缺失』视为 AI 调用失败并抛出 AiCallFailed，而空数组被视为『任务处理中』返回 Processing 状态。也就是说，结果缺失（而不是结果未准备好）被认为是真正的异常。","triggerScenarios":"调用 getDirectEditResult({ ReqIds: [...] }) 时，火山引擎 VOD API 返回了无 Error 的响应但 Result 字段为 null/undefined：例如 ReqId 无效或已过期、任务在火山侧被删除、响应被网关截断，或 Result 缺少 Result 字段。","commonSituations":"在任务提交后过早且只查了一次就假设有结果；ReqId 拼写错误或来自另一个 SpaceName；火山引擎返回了 200 但 body 结构与 SDK 类型不符（API 版本变更）；代理/网关剥掉了字段。","solutions":["打印 reqId 和完整响应体，确认 Result 确实缺失而不是类型断言错误。","核对 ReqId 是否来自同一次 SubmitDirectEditTaskAsync 的返回且 SpaceName 一致。","对『任务刚提交』的场景重试查询：空数组会返回 Processing，但 null 不行，需要调用方自行重试。","若持续为空，登录火山 VOD 控制台按 ReqId/TaskId 查任务是否仍存在。"],"exampleFix":"// before\nconst item = await directEditService.getDirectEditResult({ ReqIds: [reqId] })\n// after\nlet item: GetDirectEditResultItem | null = null\nfor (let i = 0; i < 5; i++) {\n  try {\n    item = await directEditService.getDirectEditResult({ ReqIds: [reqId] })\n    break\n  } catch (e) {\n    if (isAiCallFailed(e) && e.message === '任务结果为空') {\n      await sleep(3000)\n      continue\n    }\n    throw e\n  }\n}\nif (!item) throw new Error(`Direct edit result unavailable for ${reqId}`)","handlingStrategy":"retry","validationCode":"if (!reqId || typeof reqId !== 'string') throw new Error('Valid ReqId required before polling GetDirectEditResult')","typeGuard":"function hasDirectEditResult(r: unknown): r is { Result: unknown[] | object } {\n  return r != null && typeof r === 'object' && 'Result' in r && r.Result != null\n}","tryCatchPattern":"try {\n  const item = await getDirectEditResult({ ReqIds: [reqId] })\n  if (item.Status === 'Processing') return scheduleRetry(reqId)\n} catch (e) {\n  if (isAiCallFailed(e) && e.message === '任务结果为空') return scheduleRetry(reqId)\n  throw e\n}","preventionTips":["提交任务后等待数秒再首次查询，并对空结果做有限次退避重试","保存并核对 SubmitDirectEditTaskAsync 返回的 ReqId 与查询用 ID 一致","为轮询设置最大尝试次数和超时，避免无限轮询","监控该错误日志中的 reqId，定位是无效 ID 还是服务端延迟"],"tags":["volcengine","ai-task","null-result","async-polling"],"backgroundTag":"ai-task-result-missing","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}