From ee2026a97388b9c676ea2773c89fae1f0494bda2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 19 Feb 2026 02:59:50 +0800 Subject: [PATCH] fix(agent): tolerate non-json analyzer replies and cap graph step latency --- lib/ai/auto-agent-graph.ts | 40 +++++++++++++++++++++++++++++++++++++- lib/ai/smart-analyzer.ts | 34 ++++++++++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/lib/ai/auto-agent-graph.ts b/lib/ai/auto-agent-graph.ts index 7b5ae4986..470c412c9 100644 --- a/lib/ai/auto-agent-graph.ts +++ b/lib/ai/auto-agent-graph.ts @@ -85,6 +85,24 @@ function compactText(text: string, max = 48) { return `${normalized.slice(0, max)}...`; } +async function withTimeoutFallback( + promise: Promise, + timeoutMs: number, + fallbackValue: T, +): Promise { + let timeoutId: NodeJS.Timeout | undefined; + try { + return await Promise.race([ + promise, + new Promise((resolve) => { + timeoutId = setTimeout(() => resolve(fallbackValue), timeoutMs); + }), + ]); + } finally { + if (timeoutId) clearTimeout(timeoutId); + } +} + const AGENT_VISION_MAX_IMAGES = 4; const AGENT_VISION_MAX_SIDE = 500; const AGENT_VISION_FETCH_TIMEOUT_MS = 10_000; @@ -484,6 +502,7 @@ function buildStrategicFriendlyMessage( state: AutoAgentStateType, ): Promise { const fallbackQuestion = analysis.clarifyingQuestions?.[0]; + const existingReply = analysis.conversationalReply?.trim(); if (analysis.needsClarification) { return Promise.resolve( @@ -491,7 +510,21 @@ function buildStrategicFriendlyMessage( ); } - return generateAgentConversation(analysis, state); + if (existingReply) { + const isStructuredReply = + /###\s*需求理解/.test(existingReply) || + /###\s*图片分析/.test(existingReply) || + /###\s*执行计划/.test(existingReply); + if (isStructuredReply || existingReply.length >= 24) { + return Promise.resolve(existingReply); + } + } + + return withTimeoutFallback( + generateAgentConversation(analysis, state), + 12000, + existingReply || null, + ); } type EnabledModelWithProvider = Awaited< @@ -694,6 +727,7 @@ async function generateAgentConversation( ], temperature: 0.2, maxTokens: 360, + abortSignal: AbortSignal.timeout(12000), } as any); visualAnalysis = visionText.trim(); @@ -746,6 +780,7 @@ async function generateAgentConversation( prompt, temperature: 0.35, maxTokens: 520, + abortSignal: AbortSignal.timeout(12000), } as any); const cleaned = text.trim(); @@ -819,6 +854,7 @@ async function recommendImageParamsWithAgent( prompt: requestText, temperature: 0.15, maxTokens: 220, + abortSignal: AbortSignal.timeout(10000), } as any); const parsed = parseJsonBlock(text); @@ -903,6 +939,7 @@ async function recommendVideoPlanWithAgent( prompt, temperature: 0.2, maxTokens: 720, + abortSignal: AbortSignal.timeout(12000), } as any); const parsed = parseJsonBlock>(text); @@ -974,6 +1011,7 @@ async function refineRouteWithAgent( ${candidatesText}`, temperature: 0.1, maxTokens: 120, + abortSignal: AbortSignal.timeout(8000), } as any); const parsed = parseJsonBlock<{ modelId?: string; reason?: string }>(text); diff --git a/lib/ai/smart-analyzer.ts b/lib/ai/smart-analyzer.ts index d892cd77c..19133a96e 100644 --- a/lib/ai/smart-analyzer.ts +++ b/lib/ai/smart-analyzer.ts @@ -373,7 +373,21 @@ function parseAnalysisResultFromModelText( const jsonMatch = responseText.match(/\{[\s\S]*\}/); if (!jsonMatch) { console.warn("[Smart Analyzer] Model response missing JSON:", responseText.slice(0, 240)); - return null; + const fallback = fallbackKeywordAnalysis(userText, hasImage); + const conversationalReply = responseText.trim(); + if (!conversationalReply) { + return null; + } + return normalizeIntentForImageContext( + { + ...fallback, + confidence: Math.max(fallback.confidence, 0.82), + needsClarification: false, + conversationalReply, + }, + userText, + hasImage, + ); } try { @@ -386,7 +400,21 @@ function parseAnalysisResultFromModelText( "raw=", jsonMatch[0].slice(0, 240), ); - return null; + const fallback = fallbackKeywordAnalysis(userText, hasImage); + const conversationalReply = responseText.trim(); + if (!conversationalReply) { + return null; + } + return normalizeIntentForImageContext( + { + ...fallback, + confidence: Math.max(fallback.confidence, 0.8), + needsClarification: false, + conversationalReply, + }, + userText, + hasImage, + ); } } @@ -902,6 +930,7 @@ ${historyText} ], temperature: 0.1, maxTokens: 360, + abortSignal: AbortSignal.timeout(12000), } : { model, @@ -909,6 +938,7 @@ ${historyText} prompt: userMessage, temperature: 0.1, maxTokens: 300, + abortSignal: AbortSignal.timeout(10000), }; const { text: responseText } = await generateText(requestPayload as any);