Fix AI workflow retry loops and improve fallback handling
This commit is contained in:
@@ -25,6 +25,7 @@ type AiGenerateInput = {
|
||||
system?: string;
|
||||
prompt: string;
|
||||
temperature: number;
|
||||
maxRetries?: number;
|
||||
};
|
||||
|
||||
type AiGenerateOutput = {
|
||||
@@ -108,6 +109,83 @@ function asErrorMessage(error: unknown) {
|
||||
return String(error);
|
||||
}
|
||||
|
||||
function errorSearchText(error: unknown) {
|
||||
const chunks: string[] = [];
|
||||
const seen = new Set<unknown>();
|
||||
|
||||
const visit = (value: unknown) => {
|
||||
if (value === null || value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
const normalized = value.trim();
|
||||
if (normalized.length > 0) {
|
||||
chunks.push(normalized);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value !== 'object') {
|
||||
chunks.push(String(value));
|
||||
return;
|
||||
}
|
||||
|
||||
if (seen.has(value)) {
|
||||
return;
|
||||
}
|
||||
seen.add(value);
|
||||
|
||||
if (value instanceof Error) {
|
||||
if (value.message) {
|
||||
chunks.push(value.message);
|
||||
}
|
||||
|
||||
const withCause = value as Error & { cause?: unknown };
|
||||
if (withCause.cause !== undefined) {
|
||||
visit(withCause.cause);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const record = value as Record<string, unknown>;
|
||||
visit(record.message);
|
||||
visit(record.error);
|
||||
visit(record.reason);
|
||||
visit(record.detail);
|
||||
visit(record.details);
|
||||
visit(record.cause);
|
||||
};
|
||||
|
||||
visit(error);
|
||||
return chunks.join('\n');
|
||||
}
|
||||
|
||||
const REPORT_FALLBACK_ERROR_PATTERNS: RegExp[] = [
|
||||
/insufficient balance/i,
|
||||
/no resource package/i,
|
||||
/insufficient quota/i,
|
||||
/quota exceeded/i,
|
||||
/insufficient credit/i,
|
||||
/invalid api key/i,
|
||||
/authentication/i,
|
||||
/unauthorized/i,
|
||||
/forbidden/i,
|
||||
/payment required/i,
|
||||
/recharge/i,
|
||||
/unable to connect/i,
|
||||
/network/i,
|
||||
/timeout/i,
|
||||
/timed out/i,
|
||||
/econnrefused/i
|
||||
];
|
||||
|
||||
function shouldFallbackReportError(error: unknown) {
|
||||
const searchText = errorSearchText(error) || asErrorMessage(error);
|
||||
return REPORT_FALLBACK_ERROR_PATTERNS.some((pattern) => pattern.test(searchText));
|
||||
}
|
||||
|
||||
function defaultCreateModel(config: AiConfig) {
|
||||
if (config.provider === 'zhipu') {
|
||||
const zhipu = createZhipu({
|
||||
@@ -131,7 +209,8 @@ async function defaultGenerate(input: AiGenerateInput): Promise<AiGenerateOutput
|
||||
model: input.model as never,
|
||||
system: input.system,
|
||||
prompt: input.prompt,
|
||||
temperature: input.temperature
|
||||
temperature: input.temperature,
|
||||
maxRetries: input.maxRetries ?? 0
|
||||
});
|
||||
|
||||
return { text: result.text };
|
||||
@@ -196,7 +275,8 @@ export async function runAiAnalysis(prompt: string, systemPrompt?: string, optio
|
||||
model,
|
||||
system: systemPrompt,
|
||||
prompt,
|
||||
temperature: config.temperature
|
||||
temperature: config.temperature,
|
||||
maxRetries: 0
|
||||
});
|
||||
|
||||
const text = result.text.trim();
|
||||
@@ -218,6 +298,16 @@ export async function runAiAnalysis(prompt: string, systemPrompt?: string, optio
|
||||
text
|
||||
};
|
||||
} catch (error) {
|
||||
if (workload === 'report' && shouldFallbackReportError(error)) {
|
||||
warn(`[AI SDK] Report fallback activated: ${asErrorMessage(error)}`);
|
||||
|
||||
return {
|
||||
provider: 'local-fallback',
|
||||
model: config.model,
|
||||
text: fallbackResponse(prompt)
|
||||
};
|
||||
}
|
||||
|
||||
if (workload === 'extraction') {
|
||||
warn(`[AI SDK] Extraction fallback activated: ${asErrorMessage(error)}`);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user