{"record":{"id":"6da136a887739e2b","repo":"yikart/AiToEarn","slug":"aicallfailed-6da136","errorCode":"AiCallFailed","errorMessage":"AiCallFailed","messagePattern":"AiCallFailed","errorType":"error_code","errorClass":"AppException","httpStatus":400,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/libs/volcengine/volcengine.service.ts","lineNumber":352,"sourceCode":"      serviceName: 'vod',\n    })\n  }\n\n  /**\n   * 检查 API 响应中的错误并抛出异常（内部方法）\n   */\n  private checkApiResponseError<T>(\n    response: OpenApiResponse<T>,\n    operation: string,\n    requestData?: unknown,\n  ): asserts response is OpenApiResponse<T> & { Result: T } {\n    if (response.ResponseMetadata?.Error) {\n      const error = response.ResponseMetadata.Error\n      this.logger.error(\n        { error, requestData },\n        `${operation} failed: ${error.Code || 'Unknown'} - ${error.Message}`,\n      )\n      throw new AppException(ResponseCode.AiCallFailed, {\n        code: error.Code || 'Unknown',\n        message: error.Message,\n      })\n    }\n\n    if (!response.Result) {\n      this.logger.error({ requestData }, `${operation} returned no result`)\n      throw new AppException(ResponseCode.AiCallFailed, {\n        message: typeof response === 'string' ? response : 'No result returned',\n      })\n    }\n  }\n}\n","sourceCodeStart":334,"sourceCodeEnd":366,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/libs/volcengine/volcengine.service.ts#L334-L366","documentation":"volcengine.service.ts 的通用 checkApiResponseError 会检查所有 VOD API 响应：当 ResponseMetadata.Error 存在时，把火山原始错误 Code/Message 包装为 AiCallFailed 抛出。这是所有火山 OpenAPI 业务级错误在库内的统一出口。","triggerScenarios":"任何经 volcengineService（submitDirectEditTaskAsync、getDirectEditResult、uploadMediaByUrl、uploadMaterial、getUploadTaskInfo 等）的调用返回带 ResponseMetadata.Error 的响应：签名错误、权限不足、参数非法、限流、空间不存在、内部错误等。","commonSituations":"AK/SK 配错或轮换后未更新；未开通 VOD/智能剪辑服务；SpaceName 不存在；请求参数不符合 API 版本 2018-01-01 规范；火山侧限流或故障。","solutions":["解析异常中的 code/message（原始火山错误码），按码定位：SignatureDoesNotMatch→检查密钥、InvalidParameter→修参数、RequestLimitExceeded→退避重试。","核对 VolcengineConfig 注入的 accessKeyId/secretAccessKey/spaceName 是否为当前有效值。","确认对应火山服务已在控制台开通且地域正确。","对可重试错误码实现指数退避重试。"],"exampleFix":"// before\nconst res = await volcengineService.getUploadTaskInfo(req)\n// after\ntry {\n  const res = await volcengineService.getUploadTaskInfo(req)\n} catch (e) {\n  const { code, message } = (e as any).data ?? {}\n  logger.error({ code, message }, 'Volcengine API failed')\n  if (RETRYABLE_CODES.includes(code)) return retryWithBackoff(() => volcengineService.getUploadTaskInfo(req))\n  throw e\n}","handlingStrategy":"try-catch","validationCode":"for (const k of ['VOLC_ACCESS_KEY_ID', 'VOLC_SECRET_ACCESS_KEY', 'VOLCSPACE_NAME']) {\n  if (!process.env[k]) throw new Error(`Missing Volcengine env var: ${k}`)\n}","typeGuard":"function isVolcengineApiError(e: unknown): e is AppException & { data: { code: string, message: string } } {\n  return e instanceof AppException\n    && (e as any).code === ResponseCode.AiCallFailed\n    && typeof (e as any).data?.code === 'string'\n}","tryCatchPattern":"try {\n  return await volcengineService.someOperation(req)\n} catch (e) {\n  if (isVolcengineApiError(e)) {\n    const { code, message } = e.data\n    if (RETRYABLE.includes(code)) return retryWithBackoff(() => volcengineService.someOperation(req))\n    throw new Error(`Volcengine ${code}: ${message}`)\n  }\n  throw e\n}","preventionTips":["维护火山错误码→处理策略（重试/失败/告警）映射表","密钥轮换后立即更新配置并做签名连通性自检","确认服务与空间在火山控制台已开通且地域一致","对所有可重试错误码实现指数退避"],"tags":["volcengine","api-error","ai-call","upstream"],"backgroundTag":"upstream-api-error","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}