|
|
2 月之前 | |
|---|---|---|
| .. | ||
| nodes | 2 月之前 | |
| parsers | 3 月之前 | |
| prompts | 2 月之前 | |
| strategies | 2 月之前 | |
| test | 3 月之前 | |
| utils | 3 月之前 | |
| FAULT_TOLERANCE.md | 4 月之前 | |
| README.md | 3 月之前 | |
| ai-generate-controller.ts | 4 月之前 | |
| album-controller.ts | 3 月之前 | |
| album-management.controller.ts | 4 月之前 | |
| audio-scanner.ts | 3 月之前 | |
| book-generator-optimizations.ts | 4 月之前 | |
| book-generator.controller.ts | 3 月之前 | |
| book-generator.service.ts | 3 月之前 | |
| book-generator.store.ts | 2 月之前 | |
| book-generator.types.ts | 2 月之前 | |
| book-queue.processor.ts | 3 月之前 | |
| book-type-config.ts | 3 月之前 | |
| fault-tolerance.ts | 3 月之前 | |
| graph.ts | 2 月之前 | |
| index.ts | 2 月之前 | |
| langgraph-controller.ts | 2 月之前 | |
| langgraph-generator.ts.bak | 4 月之前 | |
| langgraph-types.ts | 3 月之前 | |
| stage-manager.ts | 3 月之前 | |
| tts-queue.ts | 2 月之前 | |
| utils.ts | 3 月之前 | |
基于 LangGraph 的三级目录书籍生成模块。
章 (level=1, parentId=null)
└─ 节 (level=2, parentId=章ID)
└─ 小节 (level=3, parentId=节ID) ← 存储实际正文内容
| 层级 | level | parentId | content | 说明 |
|---|---|---|---|---|
| 章 | 1 | null | 不存储 | 只做目录 |
| 节 | 2 | 章ID | 不存储 | 只做目录 |
| 小节 | 3 | 节ID | 存储 | 唯一内容存储 |
设计原则:正文只存在于小节,章/节只做目录导航,无冗余数据。
model BookChapter {
id Int @id @default(autoincrement())
bookId Int
parentId Int? // 父节点ID(null = 章这一级)
level Int @default(1) // 1=章, 2=节, 3=小节
number Int // 同级排序序号
title String // 章节标题
summary String? @db.Text
keyPoints String? @db.Text // JSON数组
estimatedWords Int @default(1000)
content String? @db.LongText // 仅小节存储正文
wordCount Int @default(0)
status String @default("pending")
book Book @relation(fields: [bookId], references: [id], onDelete: Cascade)
parent BookChapter? @relation("ChapterChildren", fields: [parentId], references: [id])
children BookChapter[] @relation("ChapterChildren")
@@unique([bookId, level, number])
@@index([bookId])
@@index([bookId, parentId])
@@index([bookId, level])
}
| id | bookId | parentId | level | number | title |
|---|---|---|---|---|---|
| 1 | 1 | null | 1 | 1 | 第一章:人工智能概述 |
| 5 | 1 | 1 | 2 | 1 | 1.1 人工智能的历史 |
| 10 | 1 | 5 | 3 | 1 | 1.1.1 早期发展 |
| 11 | 1 | 5 | 3 | 2 | 1.1.2 符号主义与统计方法 |
1. generateOutlineNode
└─ 生成章标题 → 入库 level=1
2. generateFullOutlineNode
└─ 为每章生成节 → 入库 level=2
└─ 为每节生成小节 → 入库 level=3
3. writeChaptersNode
└─ 逐小节生成 content → 写入 level=3
└─ 不再合并到 level=1/2
4. writeForewordNode → 写入 Book.foreword
5. writeAfterwordNode → 写入 Book.afterword
draft → generating → planning → generating → completed
(创建) (大纲) (内容) (完成)
| 状态 | 说明 |
|---|---|
| draft | 草稿 |
| generating | 生成中(创建/内容阶段) |
| planning | 规划中(大纲生成阶段) |
| completed | 已完成 |
| failed | 失败 |
POST /api/book-generator/langgraph/books
GET /api/book-generator/langgraph/books/:id
返回章节数据示例:
{
"book": {
"id": "1",
"title": "人工智能入门",
"chapters": [
{
"id": "1",
"number": 1,
"title": "第一章:人工智能概述",
"level": 1,
"parentId": null,
"content": ""
},
{
"id": "5",
"number": 1,
"title": "1.1 人工智能的历史",
"level": 2,
"parentId": 1,
"content": ""
},
{
"id": "10",
"number": 1,
"title": "1.1.1 早期发展",
"level": 3,
"parentId": 5,
"content": "正文内容..."
}
]
}
}
function buildChapterTree(chapters: Chapter[]) {
const map = new Map<number | null, Chapter[]>();
chapters.forEach(c => {
const key = c.parentId;
if (!map.has(key)) map.set(key, []);
map.get(key)!.push(c);
});
return {
chapters: map.get(null) || [], // 章 level=1
getSections: (chapterId: number) => map.get(chapterId) || [], // 节 level=2
getSubsections: (sectionId: number) => map.get(sectionId) || [], // 小节 level=3
};
}
<view v-for="chapter in chapters.filter(c => c.level === 1)" :key="chapter.id">
<!-- 章 -->
<text class="chapter-title">{{ chapter.title }}</text>
<view v-for="section in chapters.filter(c => c.parentId === chapter.id)" :key="section.id">
<!-- 节 -->
<text class="section-title">{{ section.title }}</text>
<view v-for="sub in chapters.filter(c => c.parentId === section.id)" :key="sub.id">
<!-- 小节 -->
<text class="subsection-title">{{ sub.title }}</text>
<text v-if="sub.content">{{ sub.content }}</text>
</view>
</view>
</view>
| 文件 | 说明 |
|---|---|
langgraph-generator.ts |
LangGraph 工作流定义,5个节点 |
book-generator.store.ts |
数据库操作,BookStore 类 |
book-generator.types.ts |
类型定义 |
book-type-config.ts |
书籍类型配置 |
langgraph-controller.ts |
API 路由 |
| 节点 | 说明 |
|---|---|
| generateOutlineNode | 生成章大纲 |
| generateFullOutlineNode | 生成节和小节,存入 DB |
| writeChaptersNode | 生成小节 content,存入 DB |
| writeForewordNode | 生成前言 |
| writeAfterwordNode | 生成后记 |
{"chapters":[{"writingInstructions":{...}}]}richOutlineNode 中配置)mustNotRepeat 注明章号(如"不要重复第3章的XX")与 JSON 方案做过 4 章 / 65 章全量对比测试,Markdown 格式有以下优势:
| 维度 | JSON | Markdown |
|---|---|---|
| LLM 原生性 | 低(训练语料少) | 高(母语格式) |
| 格式容错 | 依赖 jsonrepair | 天生容错(单点故障只影响当前章节) |
| 文笔质感 | 学院派,精准 | 更自然,对话感强 |
| 跨章引用 | 依赖 prompt 约束 | 天然写"不要与第13章混淆" |
| 人可读性 | 需解析 | 直接阅读 |
| 下游兼容 | ✅ 无需改动 | ❌ 需改造 quality_check / rewrite 等节点 |
| 切换方式 | 默认 | process.env.OUTLINE_FORMAT='markdown' |
切换方法:
# 环境变量方式(整个服务生效)
OUTLINE_FORMAT=markdown npm run dev
# 代码方式(单次调用)
process.env.OUTLINE_FORMAT = 'markdown';
切换前提(暂未实施):
quality-check.node.ts:解析 writingInstructions 时改用 Markdown 正则rewrite.node.ts:同上content.node.ts:同上结论:JSON 当前是最佳选择(零成本适配下游),Markdown 已作为策略5兜底 + 未来切换方案就绪。