"use strict"; /** * LangGraph 书籍生成器 * 使用 @langchain/langgraph v1.2.8 API * 状态通过数据库传递,LangGraph 只负责流程控制 */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.langGraphGenerator = exports.LangGraphBookGenerator = void 0; const axios_1 = __importDefault(require("axios")); const config_1 = require("../../../config"); const book_generator_store_1 = require("../book-generator.store"); const langgraph_1 = require("@langchain/langgraph"); // ============ 定义 State ============ const GraphState = langgraph_1.Annotation.Root({ bookId: (langgraph_1.Annotation), topic: (langgraph_1.Annotation), bookScale: (langgraph_1.Annotation), // 纯数字字符串,如 '1000', '130000', '340000' currentChapter: (langgraph_1.Annotation), finished: (langgraph_1.Annotation), error: (langgraph_1.Annotation), progress: (langgraph_1.Annotation), }); // ============ LLM 配置 ============ const TEMPERATURE = 0.7; const MAX_RETRIES = 2; async function callLLM(prompt, currentModel) { const modelId = currentModel || config_1.config.models.textGeneration.defaultModel; const modelConfig = config_1.config.models.getModel(modelId); const apiKey = modelConfig?.apiKey; const baseUrl = modelConfig?.baseUrl; if (!apiKey || !baseUrl) throw new Error(`模型 ${modelId} 缺少 API 配置`); try { const response = await axios_1.default.post(`${baseUrl}/chat/completions`, { model: modelId, messages: [{ role: 'user', content: prompt }] }, { headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, timeout: 180000 }); const result = response.data?.choices?.[0]?.message?.content || ''; if (!result) throw new Error('API 返回内容为空'); return result; } catch (error) { const errorMessage = error.response?.data?.message || error.message || 'API 调用失败'; console.log(`[LLM] 模型 ${modelId} 调用失败: ${errorMessage}`); // 检查是否需要切换模型 if (config_1.config.models.shouldSwitchModel(errorMessage)) { const nextModel = config_1.config.models.getNextModel(modelId, 'text'); if (nextModel) { console.log(`[LLM] 自动切换到模型: ${nextModel}`); return callLLM(prompt, nextModel); } } throw new Error(errorMessage); } } function countWords(text) { return (text.match(/[\u4e00-\u9fa5]/g) || []).length; } function parseOutline(jsonStr) { try { const match = jsonStr.match(/\{[\s\S]*\}/); if (match) { const data = JSON.parse(match[0]); return { mainTheme: data.mainTheme || '主题待定', structureLogic: data.structureLogic || '由浅入深', chapters: (data.chapters || []).map((c, i) => ({ number: c.number || i + 1, title: c.title || `第${i + 1}章`, summary: c.summary || '', keyPoints: c.keyPoints || [], estimatedWords: c.estimatedWords || 1000, })), }; } } catch { console.error('解析大纲失败'); } return null; } // ============ LangGraph 节点(从数据库读取状态)============ // 规模对应的章节范围 const SCALE_CHAPTER_RANGE = { '800': { min: 1, max: 1, wordsPerChapter: 800 }, '2000': { min: 1, max: 1, wordsPerChapter: 2000 }, '5000': { min: 1, max: 1, wordsPerChapter: 5000 }, '2000': { min: 2, max: 8, wordsPerChapter: 3000 }, '130000': { min: 5, max: 15, wordsPerChapter: 2500 }, '340000': { min: 10, max: 20, wordsPerChapter: 2500 }, }; async function generateOutlineNode(state) { console.log('[LangGraph] 生成大纲, bookId:', state.bookId, 'scale:', state.bookScale); // 规模说明 const scaleDesc = { '800': '短文,约800字', '2000': '短文,约2000字', '5000': '短文,约5000字', }; const scaleLabel = scaleDesc[state.bookScale] || `书籍,约${state.bookScale}字`; const prompt = `请为书籍《${state.topic}》设计大纲。 书籍规模:${scaleDesc[state.bookScale] || '中篇'} 请分析这个主题的复杂程度,决定合适的章节数量(${SCALE_CHAPTER_RANGE[state.bookScale]?.min || 5}-${SCALE_CHAPTER_RANGE[state.bookScale]?.max || 10}章之间),并生成完整大纲。 返回JSON格式: { "mainTheme": "主题一句话描述", "structureLogic": "结构逻辑说明", "chapters": [ { "number": 1, "title": "章节标题", "summary": "章节摘要,50字左右", "keyPoints": ["要点1", "要点2", "要点3"], "estimatedWords": 预估字数 } ] } 请确保: 1. 章节数根据主题实际复杂度决定,不要固定用中间值 2. 章节之间有清晰的逻辑递进关系 3. 每章的预估字数要符合规模要求`; try { const response = await callLLM(prompt); const outline = parseOutline(response); if (!outline) throw new Error('大纲解析失败'); // 更新书籍的总章节数 const totalChapters = outline.chapters.length; // 保存大纲和创建章节到数据库 await book_generator_store_1.bookStore.update(state.bookId, { totalChapters, outlineJson: JSON.stringify(outline), status: 'planning', progress: 5, }); // 创建章节记录 await book_generator_store_1.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: 5 }; } catch (error) { console.error('[LangGraph] 大纲生成失败:', error); return { error: error instanceof Error ? error.message : '失败', finished: true }; } } async function writeChaptersNode(state) { console.log('[LangGraph] 生成章节, bookId:', state.bookId); // 从数据库读取书籍和章节 const book = await book_generator_store_1.bookStore.getById(state.bookId); if (!book || !book.outline) { console.log('[LangGraph] 无大纲或书籍,跳过章节生成'); return { finished: true, progress: 90 }; } const chapters = []; const failedChapters = []; for (const chapterOutline of book.outline.chapters) { console.log(`[LangGraph] 生成第${chapterOutline.number}章: ${chapterOutline.title}`); const prompt = `撰写《${state.topic}》第${chapterOutline.number}章。章节:${chapterOutline.title},概述:${chapterOutline.summary},知识点:${chapterOutline.keyPoints.join(', ')},长度${chapterOutline.estimatedWords}字左右。直接输出正文:`; try { const content = await callLLM(prompt); const wordCount = countWords(content); await book_generator_store_1.bookStore.updateChapter(state.bookId, chapterOutline.number, { content, wordCount, status: 'completed', }); chapters.push({ number: chapterOutline.number, title: chapterOutline.title, content, wordCount, status: 'completed', }); const progress = Math.round((chapters.length / book.outline.chapters.length) * 80) + 10; await book_generator_store_1.bookStore.update(state.bookId, { progress, status: 'generating' }); console.log(`[LangGraph] 第${chapterOutline.number}章完成,进度${progress}%`); } catch (error) { const errorMsg = error instanceof Error ? error.message : '失败'; await book_generator_store_1.bookStore.updateChapter(state.bookId, chapterOutline.number, { status: 'failed', errorMsg }); failedChapters.push(chapterOutline.number); console.error(`[LangGraph] 第${chapterOutline.number}章失败:`, errorMsg); } } return { currentChapter: book.outline.chapters.length, progress: 90, error: failedChapters.length > 0 ? `章节${failedChapters.join(',')}失败` : undefined, }; } async function writeForewordNode(state) { console.log('[LangGraph] 生成前言, bookId:', state.bookId); const prompt = `为《${state.topic}》写前言。主题:${state.topic},300-500字。直接输出:`; try { const foreword = await callLLM(prompt); await book_generator_store_1.bookStore.update(state.bookId, { foreword, progress: 95 }); return { progress: 95 }; } catch (error) { console.error('[LangGraph] 前言生成失败:', error); return { progress: 95 }; } } async function writeAfterwordNode(state) { console.log('[LangGraph] 生成后记, bookId:', state.bookId); const prompt = `为《${state.topic}》写后记。主题:${state.topic},300-500字。直接输出:`; try { const afterword = await callLLM(prompt); await book_generator_store_1.bookStore.update(state.bookId, { afterword }); return { finished: true, progress: 100 }; } catch (error) { console.error('[LangGraph] 后记生成失败:', error); return { finished: true, progress: 100 }; } } // ============ 创建工作流 ============ function createGraph() { const workflow = new langgraph_1.StateGraph(GraphState) .addNode('generate_outline', generateOutlineNode) .addNode('write_chapters', writeChaptersNode) .addNode('write_foreword', writeForewordNode) .addNode('write_afterword', writeAfterwordNode) .setEntryPoint('generate_outline') // 顺序流程 .addEdge('generate_outline', 'write_chapters') .addEdge('write_chapters', 'write_foreword') .addEdge('write_foreword', 'write_afterword') .addEdge('write_afterword', langgraph_1.END); return workflow.compile(); } // ============ 主类 ============ class LangGraphBookGenerator { graph = createGraph(); async generate(bookId, topic, bookScale = '130000') { const initialState = { bookId, topic, bookScale, currentChapter: 0, finished: false, error: undefined, progress: 0, }; await book_generator_store_1.bookStore.update(bookId, { status: 'generating', progress: 0 }); try { const stream = await this.graph.stream(initialState); for await (const step of stream) { console.log('[LangGraph] Step:', Object.keys(step)); } await book_generator_store_1.bookStore.update(bookId, { status: 'completed', progress: 100 }); await book_generator_store_1.bookStore.publishAlbum(bookId); // 发布专辑 console.log('[LangGraph] 书籍生成完成'); } catch (error) { console.error('[LangGraph] 生成失败:', error); await book_generator_store_1.bookStore.update(bookId, { status: 'failed', errorMsg: error instanceof Error ? error.message : '生成失败' }); } } } exports.LangGraphBookGenerator = LangGraphBookGenerator; exports.langGraphGenerator = new LangGraphBookGenerator();