| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- /**
- * 智能容错层 - 为书籍生成提供稳定性保障
- *
- * 核心功能:
- * 1. AI调用重试机制(3次重试 + 指数退避)
- * 2. 节点级超时控制(超时自动跳过或降级)
- * 3. 进度监控和长时间无响应告警
- * 4. 失败自动恢复(智能重试策略)
- */
- import { bookStore } from './book-generator.store';
- import { callLLMWithMessages, ChatMessage } from '../../services/llm';
- // ============ 配置 ============
- export const FAULT_TOLERANCE_CONFIG = {
- // AI调用重试
- aiRetry: {
- maxRetries: 3, // 最大重试次数
- initialDelayMs: 2000, // 初始延迟 2秒
- maxDelayMs: 30000, // 最大延迟 30秒
- backoffMultiplier: 2, // 指数退避倍数
- },
-
- // 节点超时
- nodeTimeout: {
- deep_plan: 3 * 60 * 1000, // 深度规划:3分钟
- generate_outline: 5 * 60 * 1000, // 大纲生成:5分钟
- rich_outline: 8 * 60 * 1000, // 富信息大纲:8分钟
- generate_sections: 10 * 60 * 1000, // 节生成:10分钟
- generate_subsections: 15 * 60 * 1000, // 小节生成:15分钟
- write_chapters: 30 * 60 * 1000, // 内容生成:30分钟
- parallel_content: 45 * 60 * 1000, // 并行内容生成:45分钟(8路并发)
- continuity_edit: 10 * 60 * 1000, // 连贯性编辑:10分钟
- quality_check: 10 * 60 * 1000, // 质量校验:10分钟
- rewrite: 15 * 60 * 1000, // 改写优化:15分钟
- write_foreword: 5 * 60 * 1000, // 前言:5分钟
- write_afterword: 5 * 60 * 1000, // 后记:5分钟
- },
-
- // 进度监控
- progressMonitor: {
- maxIdleTimeMs: 10 * 60 * 1000, // 最大空闲时间 10分钟
- checkIntervalMs: 60 * 1000, // 检查间隔 1分钟
- },
-
- // 自动恢复
- autoRecovery: {
- maxRecoveryAttempts: 2, // 最大恢复尝试次数
- recoveryDelayMs: 5 * 60 * 1000, // 恢复延迟 5分钟
- },
- };
- // ============ 类型定义 ============
- export interface FaultToleranceContext {
- bookId: string;
- nodeId: string;
- attempt: number;
- maxAttempts: number;
- }
- // ============ AI调用重试包装器 ============
- /**
- * 带重试机制的AI调用
- * 失败后自动重试,使用指数退避策略
- */
- export async function callLLMWithRetry(
- messages: ChatMessage[],
- modelId: string | undefined,
- context: FaultToleranceContext,
- maxTokens?: number
- ): Promise<string> {
- const { bookId, nodeId, attempt, maxAttempts } = context;
- const config = FAULT_TOLERANCE_CONFIG.aiRetry;
-
- let lastError: Error | null = null;
-
- for (let i = 0; i <= maxAttempts; i++) {
- try {
- // 第一次不显示重试日志
- if (i > 0) {
- const delay = Math.min(
- config.initialDelayMs * Math.pow(config.backoffMultiplier, i - 1),
- config.maxDelayMs
- );
-
- console.log(`[容错] AI调用重试 ${i}/${maxAttempts},等待 ${delay/1000}秒后重试...`);
- await sleep(delay);
-
- // 通知用户正在重试
- await notifyUser(bookId, {
- type: 'ai_retry',
- nodeId,
- attempt: i,
- maxAttempts,
- message: `AI调用失败,正在重试 (${i}/${maxAttempts})...`,
- });
- }
-
- return await callLLMWithMessages(messages, modelId, maxTokens);
- } catch (error: any) {
- lastError = error;
- console.error(`[容错] AI调用失败 (attempt ${i + 1}/${maxAttempts + 1}):`, error.message);
-
- // 记录失败到数据库
- await logAIFailure(bookId, nodeId, error.message, i + 1);
- }
- }
-
- // 所有重试都失败了
- const finalError = new Error(`AI调用失败,已重试${maxAttempts}次: ${lastError?.message}`);
- console.error(`[容错] ❌ AI调用最终失败:`, finalError.message);
-
- // 通知用户AI调用失败
- await notifyUser(bookId, {
- type: 'ai_failed',
- nodeId,
- message: `AI调用失败,已重试${maxAttempts}次。系统将尝试自动恢复。`,
- error: lastError?.message,
- });
-
- throw finalError;
- }
- // ============ 节点超时控制 ============
- /**
- * 带超时控制的节点执行
- * 超时后根据策略处理(跳过/降级/标记失败)
- */
- export async function executeNodeWithTimeout<T>(
- bookId: string,
- nodeId: string,
- nodeFn: () => Promise<T>,
- timeoutMs?: number
- ): Promise<T> {
- const timeout = timeoutMs || FAULT_TOLERANCE_CONFIG.nodeTimeout[nodeId] || 10 * 60 * 1000;
-
- console.log(`[容错] 执行节点 ${nodeId},超时时间: ${timeout/1000}秒`);
-
- // 通知用户节点开始执行
- await notifyUser(bookId, {
- type: 'node_start',
- nodeId,
- message: `正在执行: ${getNodeDisplayName(nodeId)}`,
- });
-
- return new Promise<T>((resolve, reject) => {
- const timeoutId = setTimeout(async () => {
- console.error(`[容错] ⚠️ 节点 ${nodeId} 执行超时 (${timeout/1000}秒)`);
-
- // 通知用户超时
- await notifyUser(bookId, {
- type: 'node_timeout',
- nodeId,
- message: `节点执行超时 (${timeout/1000}秒),系统将尝试恢复...`,
- });
-
- reject(new Error(`节点 ${nodeId} 执行超时`));
- }, timeout);
-
- nodeFn()
- .then((result) => {
- clearTimeout(timeoutId);
-
- // 通知用户节点完成
- notifyUser(bookId, {
- type: 'node_complete',
- nodeId,
- message: `节点完成: ${getNodeDisplayName(nodeId)}`,
- }).catch(err => console.error('[容错] 通知用户失败:', err));
-
- resolve(result);
- })
- .catch((error) => {
- clearTimeout(timeoutId);
- reject(error);
- });
- });
- }
- // ============ 进度监控 ============
- /**
- * 启动进度监控器
- * 检测长时间无响应的任务
- */
- export function startProgressMonitor(bookId: string) {
- const config = FAULT_TOLERANCE_CONFIG.progressMonitor;
- let lastProgressTime = Date.now();
- let lastProgress = 0;
- let idleWarnings = 0;
-
- const monitorInterval = setInterval(async () => {
- try {
- // 从数据库获取最新进度
- const book = await bookStore.getById(bookId);
- if (!book) {
- clearInterval(monitorInterval);
- return;
- }
-
- const currentProgress = book.progress || 0;
-
- // 如果进度有更新,重置计数器
- if (currentProgress > lastProgress) {
- lastProgress = currentProgress;
- lastProgressTime = Date.now();
- idleWarnings = 0;
- return;
- }
-
- // 检查是否超时
- const idleTime = Date.now() - lastProgressTime;
- if (idleTime > config.maxIdleTimeMs) {
- idleWarnings++;
-
- console.warn(`[容错] ⚠️ 任务长时间无响应: bookId=${bookId}, 空闲时间=${idleTime/1000}秒`);
-
- // 第一次警告
- if (idleWarnings === 1) {
- await notifyUser(bookId, {
- type: 'progress_warning',
- message: '生成进度长时间未更新,系统正在监控中...',
- idleTime: Math.round(idleTime / 1000 / 60), // 分钟
- });
- }
-
- // 第二次警告,建议用户手动干预
- if (idleWarnings === 2) {
- await notifyUser(bookId, {
- type: 'progress_critical',
- message: '生成任务可能已卡住,建议刷新页面或重新生成',
- idleTime: Math.round(idleTime / 1000 / 60),
- });
- }
-
- // 第三次,尝试自动恢复
- if (idleWarnings >= 3) {
- console.error(`[容错] ❌ 任务长时间无响应,尝试自动恢复...`);
- clearInterval(monitorInterval);
-
- await notifyUser(bookId, {
- type: 'auto_recovery',
- message: '系统检测到任务异常,正在尝试自动恢复...',
- });
-
- await attemptAutoRecovery(bookId);
- }
- }
- } catch (error) {
- console.error('[容错] 进度监控失败:', error);
- }
- }, config.checkIntervalMs);
-
- // 返回停止函数
- return () => {
- clearInterval(monitorInterval);
- console.log(`[容错] 进度监控已停止: bookId=${bookId}`);
- };
- }
- // ============ 自动恢复机制 ============
- /**
- * 尝试自动恢复失败的任务
- */
- export async function attemptAutoRecovery(bookId: string) {
- const config = FAULT_TOLERANCE_CONFIG.autoRecovery;
-
- try {
- // 获取书籍当前状态
- const book: any = await bookStore.getById(bookId);
- if (!book) return;
-
- // 检查是否已经超过最大恢复次数(从errorMsg中判断)
- const errorCount = (book.errorMsg || '').match(/自动恢复/g)?.length || 0;
- if (errorCount >= config.maxRecoveryAttempts) {
- console.error(`[容错] ❌ 已达到最大恢复次数 (${config.maxRecoveryAttempts}),放弃恢复`);
-
- await notifyUser(bookId, {
- type: 'recovery_failed',
- message: `自动恢复失败(已尝试${config.maxRecoveryAttempts}次),请手动重新生成`,
- });
-
- // 标记为最终失败
- await bookStore.update(bookId, {
- genStage: 'failed',
- errorMsg: `生成失败,已尝试自动恢复${config.maxRecoveryAttempts}次`,
- });
-
- return;
- }
-
- console.log(`[容错] 🔄 尝试自动恢复 (第${errorCount + 1}次)...`);
-
- // 等待一段时间后重试
- await sleep(config.recoveryDelayMs);
-
- // 直接重新生成(跳过队列,数据库驱动更可靠)
- const { langGraphGenerator } = await import('./index.js');
- langGraphGenerator.generate(
- bookId,
- book.description || book.title,
- book.bookScale || '1000'
- ).then(() => {
- console.log(`[容错] ✅ 自动恢复成功: bookId=${bookId}`);
- }).catch(err => {
- console.error(`[容错] ❌ 自动恢复失败: bookId=${bookId}`, err);
- });
-
- await notifyUser(bookId, {
- type: 'recovery_success',
- message: '系统已自动恢复生成任务,请耐心等待...',
- });
- } catch (error) {
- console.error('[容错] 自动恢复失败:', error);
-
- await notifyUser(bookId, {
- type: 'recovery_error',
- message: '自动恢复失败,请手动重新生成',
- error: error instanceof Error ? error.message : '未知错误',
- });
- }
- }
- // ============ 用户通知 ============
- export interface UserNotification {
- type: 'ai_retry' | 'ai_failed' | 'node_start' | 'node_complete' | 'node_timeout' |
- 'progress_warning' | 'progress_critical' | 'auto_recovery' |
- 'recovery_success' | 'recovery_failed' | 'recovery_error';
- nodeId?: string;
- attempt?: number;
- maxAttempts?: number;
- message: string;
- error?: string;
- idleTime?: number;
- jobId?: string;
- }
- /**
- * 记录用户通知(客户端通过轮询获取状态)
- */
- async function notifyUser(bookId: string, notification: UserNotification) {
- console.log(`[容错] 📢 通知: ${notification.type} - ${notification.message} (bookId: ${bookId})`);
- }
- // ============ 失败日志 ============
- /**
- * 记录AI调用失败到数据库
- */
- async function logAIFailure(bookId: string, nodeId: string, errorMessage: string, attempt: number) {
- try {
- // 目前先记录到errorMsg字段
- const book: any = await bookStore.getById(bookId);
- const existingErrors = book.errorMsg ? `${book.errorMsg}\n` : '';
-
- await bookStore.update(bookId, {
- errorMsg: `${existingErrors}[${new Date().toISOString()}] ${nodeId} 失败 (尝试${attempt}次): ${errorMessage}`,
- } as any);
- } catch (error) {
- console.error('[容错] 记录失败日志失败:', error);
- }
- }
- // ============ 辅助函数 ============
- function sleep(ms: number): Promise<void> {
- return new Promise(resolve => setTimeout(resolve, ms));
- }
- function getNodeDisplayName(nodeId: string): string {
- const names: Record<string, string> = {
- deep_plan: '深度规划',
- generate_outline: '生成章大纲',
- rich_outline: '生成富信息大纲',
- generate_sections: '生成节大纲',
- generate_subsections: '生成小节大纲',
- write_chapters: '生成章节内容',
- parallel_content: '并行生成章节内容',
- continuity_edit: '连贯性编辑',
- quality_check: '质量校验',
- rewrite: '改写优化',
- write_foreword: '生成前言',
- write_afterword: '生成后记',
- };
- return names[nodeId] || nodeId;
- }
|