返回 AiToEarn
ai-generation-retry.util.ts
根目录 / project / aitoearn-backend / apps / aitoearn-ai / src / core / ai / ai-generation-retry.util.ts
1 import { HttpException } from '@nestjs/common'
2 import { AppException, getErrorMessage, ResponseCode } from '@yikart/common'
3 import { AxiosError } from 'axios'
4
5 export function isNonRetryableAiRequestError(error: unknown): boolean {
6 const message = getErrorMessage(error)
7 if (isContentSafetyMessage(message)) {
8 return false
9 }
10
11 if (error instanceof AppException) {
12 return [
13 ResponseCode.InvalidModel,
14 ResponseCode.InvalidAiTaskId,
15 ResponseCode.VideoUploadInvalidInput,
16 ResponseCode.AiLogNotFound,
17 ].includes(error.code)
18 }
19
20 const status = getHttpStatus(error)
21 if (status != null && [400, 401, 403, 404, 422].includes(status)) {
22 return true
23 }
24
25 const lowerMessage = message.toLowerCase()
26 return [
27 'invalid parameter',
28 'missing required',
29 'bad request',
30 'unauthorized',
31 'forbidden',
32 'not found',
33 'insufficient balance',
34 'unsupported',
35 'model not found',
36 'image size is not supported',
37 'invalid image',
38 'invalid input',
39 ].some(pattern => lowerMessage.includes(pattern))
40 }
41
42 export async function runWithAiGenerationRetry<T>(
43 run: () => Promise<T>,
44 retry: number | undefined,
45 onRetry?: (error: unknown, attempt: number, maxAttempts: number) => void,
46 ): Promise<T> {
47 const maxAttempts = 1 + Math.max(0, retry ?? 0)
48 let lastError: unknown
49
50 for (let attempt = 1; attempt <= maxAttempts; attempt++) {
51 try {
52 return await run()
53 }
54 catch (error) {
55 lastError = error
56 if (attempt >= maxAttempts || isNonRetryableAiRequestError(error)) {
57 throw error
58 }
59 onRetry?.(error, attempt, maxAttempts)
60 }
61 }
62
63 throw lastError
64 }
65
66 function getHttpStatus(error: unknown): number | undefined {
67 if (error instanceof HttpException) {
68 return error.getStatus()
69 }
70 if (error instanceof AxiosError || looksLikeAxiosError(error)) {
71 return (error as AxiosError).response?.status
72 }
73 return undefined
74 }
75
76 function looksLikeAxiosError(error: unknown): boolean {
77 return typeof error === 'object' && error != null && 'isAxiosError' in error
78 }
79
80 function isContentSafetyMessage(message: string): boolean {
81 const lowerMessage = message.toLowerCase()
82 return [
83 'safety',
84 'policy',
85 'content filter',
86 'risk',
87 'violation',
88 'sensitive',
89 ].some(pattern => lowerMessage.includes(pattern))
90 || ['违规', '风控', '内容安全'].some(pattern => message.includes(pattern))
91 }
92
92 lines TYPESCRIPT