/** * LangGraph 书籍生成器 - 主入口(策略门面) * * 使用策略模式,通过 strategies/selector.ts 配置切换不同生成方案 * * 当前支持策略: * 1. sequential - 串行(原始方案) * 2. one-step-outline - 一步大纲 + 并行内容 * 3. per-chapter - 逐章内聚 * 4. deep-plan-parallel - DeepPlan+RichOutline+Concurrent+Edit(当前推荐,默认策略) * * 大纲层级(genLevel): * 1 = 仅章(短文/小说/故事类) * 2 = 章→节(科普/商业/大众类) * 3 = 章→节→小节(教材/技术教程/专业类) * * 成本追踪:通过 AsyncLocalStorage 上下文,所有 LLM/TTS 调用自动携带 bookId/chapterId */ import { bookStore } from './book-generator.store'; import { getScaleConfig } from './book-type-config'; import { getCurrentStrategy, setCurrentStrategy, getAllStrategies, getCurrentStrategyName } from './strategies/selector'; import type { StrategyName } from './strategies/selector'; import { runWithContext } from '../../services/llm-context'; // ============ 书籍类型 → 大纲层级映射 ============ const BOOK_TYPE_GEN_LEVEL: Record = { '教材': 3, '技术教程': 3, '小说': 1, '传记': 1, '商业': 2, '科普': 2, '历史': 2, '学术': 3, '心理学': 2, }; /** * 将书籍类型映射为大纲层级 */ export function mapBookTypeToGenLevel(bookType: string): number { return BOOK_TYPE_GEN_LEVEL[bookType] ?? 2; } /** * 根据书籍规模和话题文本自动推荐大纲层级 * 增强版:如未指定 genLevel,会从话题文本推断书籍类型→层级 */ export function resolveGenLevel(bookScale: string, userGenLevel?: number | string, topic?: string): number { if (userGenLevel !== undefined && userGenLevel !== null) { const n = typeof userGenLevel === 'string' ? parseInt(userGenLevel) : userGenLevel; if ([1, 2, 3].includes(n)) return n; } const config = getScaleConfig(bookScale); if (!config) return 2; if (config.isShortArticle) return 1; // 从话题文本推断书籍类型,增强 genLevel 推荐准确度 if (topic) { const text = topic.toLowerCase(); if (/教材|教程|编程|开发|实战|教学|课程/i.test(text)) return 3; if (/小说|故事|传记|散文|诗歌/i.test(text)) return 1; } return 2; } // ============ 策略切换 API(外部可调用) ============ export { setCurrentStrategy, getCurrentStrategyName, getAllStrategies } from './strategies/selector'; export type { StrategyName } from './strategies/selector'; // ============ 主类(策略门面) ============ export class LangGraphBookGenerator { async generate( bookId: string, topic: string, bookScale: string = '1000', genLevel?: number, userId?: number, userSpecifiedVoiceSpeed?: boolean, ): Promise { const userSpecified = genLevel !== undefined; const level = genLevel ?? resolveGenLevel(bookScale, undefined, topic); const strategy = getCurrentStrategy(); console.log(`[LangGraph] 开始生成: bookId=${bookId}, userId=${userId ?? '-'}, scale=${bookScale}, genLevel=${level}, strategy=${strategy.name}, userSpecifiedVoiceSpeed=${userSpecifiedVoiceSpeed}`); // 设置异步上下文,后续所有 LLM/TTS 调用自动携带 userId / bookId await runWithContext({ userId, bookId: parseInt(bookId) }, async () => { await strategy.generate(bookId, topic, bookScale, level, userSpecified ? level : undefined, userSpecifiedVoiceSpeed); }); } } export const langGraphGenerator = new LangGraphBookGenerator(); // 重新导出 book-type-config 中的函数供外部使用 export { getScaleConfig } from './book-type-config'; // ============ 独立大纲生成函数(供 API 预览调用,不依赖完整策略) ============ import { callLLMWithMessages } from '../../services/llm'; import { buildOutlineMessages } from './prompts/builder'; import { parseOutline } from './parsers/outline.parser'; /** * 独立的大纲生成函数(供 API 预览调用,不依赖策略系统) * 用于让用户在正式开始生成前预览大纲结构 */ export async function generateOutline(bookId: string, topic: string, bookScale: string = '1000'): Promise { console.log('[generateOutline] 预览大纲, bookId:', bookId, 'scale:', bookScale); const messages = buildOutlineMessages(topic, bookScale); const response = await callLLMWithMessages(messages); const outline = parseOutline(response); if (!outline) { throw new Error('大纲解析失败'); } return outline; }