Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | /** * 大纲生成节点(集成容错机制) */ import { GraphState } from '../graph'; import { bookStore } from '../book-generator.store'; import { callLLMWithMessages } from '../../../services/llm'; import { parseOutline } from '../parsers/outline.parser'; import { buildOutlineMessages, SCALE_CHAPTER_RANGE } from '../prompts/builder'; import { PROGRESS } from '../utils'; import { callLLMWithRetry, executeNodeWithTimeout, FAULT_TOLERANCE_CONFIG } from '../fault-tolerance'; import { getChapterRange } from '../book-type-config'; export async function generateOutlineNode(state: typeof GraphState.State): Promise<Partial<typeof GraphState.State>> { console.log('[LangGraph] 生成大纲, bookId:', state.bookId, 'scale:', state.bookScale); // 读取前序规划节点的分析结果,用于指导大纲生成 let bookPlan: any = null; if (state.bookPlan) { try { bookPlan = JSON.parse(state.bookPlan); console.log('[LangGraph] 使用规划结果指导大纲:', bookPlan.structureLogic, bookPlan.writingStyle); } catch { console.warn('[LangGraph] bookPlan 解析失败,忽略'); } } try { // 使用容错包装器执行AI调用 const response = await executeNodeWithTimeout( state.bookId, 'generate_outline', async () => { return callLLMWithRetry( buildOutlineMessages(state.topic, state.bookScale, state.description, bookPlan), undefined, { bookId: state.bookId, nodeId: 'generate_outline', attempt: 0, maxAttempts: FAULT_TOLERANCE_CONFIG.aiRetry.maxRetries, } ); }, FAULT_TOLERANCE_CONFIG.nodeTimeout.generate_outline ); const outline = parseOutline(response); if (!outline) throw new Error('大纲解析失败'); // 校验章节数:允许 ±20% 浮动,超出才截断/补充 const targetChapters = SCALE_CHAPTER_RANGE[state.bookScale as keyof typeof SCALE_CHAPTER_RANGE]; if (targetChapters) { const { min: minAllowed, max: maxAllowed } = getChapterRange(targetChapters); const actual = outline.chapters.length; if (actual > maxAllowed) { console.warn(`[LangGraph] AI 返回章节数 ${actual} 超出上限 ${maxAllowed},截断`); outline.chapters = outline.chapters.slice(0, maxAllowed); } else if (actual < minAllowed) { console.warn(`[LangGraph] AI 返回章节数 ${actual} 少于下限 ${minAllowed},使用话题相关的默认章节补充`); const topicHint = state.topic?.substring(0, 30) || ''; const perChapterWords = Math.round(parseInt(state.bookScale) / minAllowed); const gapTopics = [ `概述与背景`, `核心概念`, `深入探究`, `应用与实践`, `总结与展望`, ]; while (outline.chapters.length < minAllowed) { const idx = outline.chapters.length; const gapTitle = gapTopics[idx % gapTopics.length]; outline.chapters.push({ number: idx + 1, title: topicHint ? `第${idx + 1}章 ${topicHint} - ${gapTitle}` : `第${idx + 1}章 ${gapTitle}`, summary: `本章围绕"${topicHint || gapTitle}"展开,介绍${gapTitle}相关内容。`, keyPoints: [gapTitle, '关键知识点', '实践要点'], estimatedWords: perChapterWords, }); } } // 在范围内(±20%)不做任何处理,让 AI 自己决定 } // 短文类至少1个章节 if (outline.chapters.length === 0) { console.warn('[LangGraph] AI 返回章节数为0,使用话题创建默认章节'); const fallbackTitle = state.topic?.substring(0, 50) || '正文'; outline.chapters = [{ number: 1, title: fallbackTitle, summary: `关于"${fallbackTitle}"的全面介绍,涵盖核心概念、关键知识和实践要点。`, keyPoints: ['核心概念', '关键知识', '实践要点'], estimatedWords: parseInt(state.bookScale) || 800, }]; } const totalChapters = outline.chapters.length; await bookStore.update(state.bookId, { totalChapters, outlineJson: JSON.stringify(outline), progress: PROGRESS.OUTLINE_DONE }); await bookStore.createChapters(state.bookId, outline.chapters.map(c => ({ number: c.number, title: c.title, summary: c.summary, keyPoints: c.keyPoints, estimatedWords: c.estimatedWords, }))); console.log('[LangGraph] 大纲生成完成,章节数:', outline.chapters.length); return { progress: PROGRESS.OUTLINE_DONE }; } catch (error) { console.error('[LangGraph] 大纲生成失败:', 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 }; } } |