"""Phase 1 HA: rich-outline 节点加降级路径""" PATH = "/data/ai/audio/server/src/modules/book-generator/nodes/rich-outline.node.ts" with open(PATH, 'r', encoding='utf-8') as f: src = f.read() # 加 import AllProvidersFailedError old_import = "import { callLLMWithMessages, ChatMessage } from '../../../services/llm';" new_import = "import { callLLMWithMessages, ChatMessage, AllProvidersFailedError } from '../../../services/llm';" assert old_import in src, "old import not found" src = src.replace(old_import, new_import, 1) # 改 catch 块:识别 AllProvidersFailedError 走降级路径 old_catch = """ } catch (error) { console.error('[RichOutline] 生成失败:', error); await bookStore.update(state.bookId, { genStage: 'failed', failedStage: 'outlining', errorMsg: error instanceof Error ? error.message : '富信息大纲生成失败', }); return { error: error instanceof Error ? error.message : '失败', finished: true }; } }""" new_catch = """ } catch (error: any) { // ============ HA 降级:所有供应商都挂了 ============ if (error?.name === 'AllProvidersFailedError' || error instanceof AllProvidersFailedError) { console.error('[RichOutline] ⚠️ 所有 LLM 供应商均不可用,触发降级:', error.message); // 降级策略 1: 尝试复用已有的简单大纲(如果有) try { const existingBook: any = await bookStore.getById(state.bookId); const existingOutline = existingBook?.outlineJson ? (typeof existingBook.outlineJson === 'string' ? JSON.parse(existingBook.outlineJson) : existingBook.outlineJson) : null; if (existingOutline?.chapters?.length) { console.log('[RichOutline] 降级路径 1: 复用已有大纲'); await bookStore.update(state.bookId, { errorMsg: `[降级-复用] 所有 LLM 供应商失败 (${error.attempts?.length || 0}个), 已复用已有大纲`, progress: PROGRESS.OUTLINE_DONE, failedStage: null, }); return { progress: PROGRESS.OUTLINE_DONE } as any; } } catch (e) { console.warn('[RichOutline] 降级路径 1 失败:', e); } // 降级策略 2: 标记为 partial_outline,让后续节点继续 // 不写 failedStage,让 processMonitor 有机会后续恢复 console.log('[RichOutline] 降级路径 2: 标记 partial_outline, 等待后续重试'); await bookStore.update(state.bookId, { genStage: 'partial_outline', errorMsg: `[降级] 所有 LLM 供应商失败 (${error.attempts?.length || 0}个): ${error.message?.substring(0, 500)}`, failedStage: null, // 关键:不标记 failed,让 processMonitor 后续重试 }); return { progress: PROGRESS.OUTLINE_DONE } as any; } // 真正不可恢复的错误(业务错误、解析错误等) console.error('[RichOutline] 生成失败:', error); await bookStore.update(state.bookId, { genStage: 'failed', failedStage: 'outlining', errorMsg: error instanceof Error ? error.message : '富信息大纲生成失败', }); return { error: error instanceof Error ? error.message : '失败', finished: true }; } }""" assert old_catch in src, "old catch not found" src = src.replace(old_catch, new_catch) with open(PATH, 'w', encoding='utf-8') as f: f.write(src) print("OK: rich-outline.node.ts patched")