{"record":{"id":"7683e29ec6d6e8c7","repo":"yikart/AiToEarn","slug":"aicallfailed","errorCode":"AiCallFailed","errorMessage":"Missing usage metadata","messagePattern":"Missing usage metadata","errorType":"error_code","errorClass":"AppException","httpStatus":null,"severity":"warning","filePath":"project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/chat/chat.service.ts","lineNumber":143,"sourceCode":"  }\n\n  async chatCompletion(request: ChatCompletionDto, userId: string) {\n    const { messages, model, ...params } = request\n\n    const langchainMessages: BaseMessage[] = messages.map((message) => {\n      return new ChatMessage(message)\n    })\n\n    const result = await this.openaiService.createChatCompletion({\n      model,\n      messages: langchainMessages,\n      ...params,\n      modalities: params.modalities as OpenAIClient.Chat.ChatCompletionModality[],\n    })\n\n    const usage = result.usage_metadata\n    if (!usage) {\n      throw new AppException(ResponseCode.AiCallFailed, { error: 'Missing usage metadata' })\n    }\n\n    // 处理返回的 content 中的 base64 图片\n    result.content = await this.processAIMessageChunkContent(result.content, model, userId) as typeof result.content\n\n    return {\n      model,\n      usage,\n      ...result,\n    }\n  }\n\n  private async handleCompletion(\n    params: ChatCompletionDto,\n    userId: string,\n    userType: UserType,\n    modelConfig: { name: string, channel: AiLogChannel },\n    startedAt: Date,","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/chat/chat.service.ts#L125-L161","documentation":"postComment validates TikTokCommentDto fields accountId, videoId and text; any missing/empty field throws BadRequestException('accountId, videoId和text是必须的'). All three are needed to resolve the token, target video, and comment content.","triggerScenarios":"POST the comment route with a TikTokCommentDto body where accountId, videoId or text is absent or an empty string (e.g. submitting the comment box without typing text).","commonSituations":"User submits an empty comment and the frontend doesn't block it, comment payload built from a context missing the account, or DTO field name mismatch (comment vs text) leaving text undefined.","solutions":["Send { accountId, videoId, text } with non-empty text in the JSON body.","Disable the submit button until text.trim().length > 0 client-side.","Verify the DTO key is exactly 'text' matching TikTokCommentDto."],"exampleFix":"// before\nawait api.post('/plat/tiktok/video/comment', { accountId, videoId, text });\n// after\nif (!accountId || !videoId || !text?.trim()) return;\nawait api.post('/plat/tiktok/video/comment', { accountId, videoId, text: text.trim() });","handlingStrategy":"validation","validationCode":"if (!commentDto.accountId || !commentDto.videoId || !commentDto.text?.trim()) throw new Error('accountId, videoId和text是必须的');","typeGuard":"function isCommentDto(v): v is { accountId: string; videoId: string; text: string } {\n  const d = v as any;\n  return typeof d?.accountId === 'string' && d.accountId.length > 0 && typeof d?.videoId === 'string' && d.videoId.length > 0 && typeof d?.text === 'string' && d.text.trim().length > 0;\n}","tryCatchPattern":"try {\n  return await api.post('/plat/tiktok/video/comment', commentDto);\n} catch (e) {\n  if (e.response?.status === 400) console.error('评论请求缺少 accountId/videoId/text');\n  throw e;\n}","preventionTips":["Disable the comment submit button until text is non-empty","Trim text before sending and validate the DTO key is 'text'","Validate all three fields client-side before issuing the request"],"tags":["validation","missing-parameter","request-body"],"backgroundTag":"missing-required-parameter","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}