book-analysis-parallel.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. /**
  2. * 拆书分析 Workflow - 并行处理章节提取角色
  3. */
  4. export const meta = {
  5. name: 'book-analysis-parallel',
  6. description: '并行分析多个章节提取角色信息',
  7. phases: [
  8. { title: '分析章节', detail: '并行提取每章角色信息' },
  9. { title: '合并去重', detail: '合并所有角色并去重' },
  10. { title: '生成Skills', detail: '为每个角色生成Skill文档' },
  11. ],
  12. }
  13. // 参数:
  14. // - chapters: Array<{id, title, content, order}>
  15. // - bookMetadata: {title, author, ...}
  16. // - llmConfig: LLM配置
  17. const chapters = args.chapters || []
  18. const bookMetadata = args.bookMetadata || {}
  19. const outputDir = args.outputDir || ''
  20. if (!chapters.length) {
  21. return { error: '没有要分析的章节' }
  22. }
  23. log(`开始并行分析 ${chapters.length} 个章节...`)
  24. // 第一阶段:并行分析每个章节
  25. phase('分析章节')
  26. const chapterSchema = {
  27. type: 'object',
  28. properties: {
  29. characters: {
  30. type: 'array',
  31. items: {
  32. type: 'object',
  33. properties: {
  34. name: { type: 'string' },
  35. aliases: { type: 'array', items: { type: 'string' } },
  36. category: { type: 'string', enum: ['protagonist', 'antagonist', 'supporting', 'minor'] },
  37. description: { type: 'string' },
  38. personality: { type: 'string' },
  39. speechStyle: { type: 'string' },
  40. relationships: {
  41. type: 'array',
  42. items: {
  43. type: 'object',
  44. properties: {
  45. target: { type: 'string' },
  46. relation: { type: 'string' },
  47. description: { type: 'string' },
  48. },
  49. },
  50. },
  51. importance: { type: 'number', minimum: 1, maximum: 10 },
  52. },
  53. required: ['name', 'category', 'importance'],
  54. },
  55. },
  56. },
  57. required: ['characters'],
  58. }
  59. // 使用 pipeline 并行处理所有章节(无阻塞)
  60. const chapterResults = await pipeline(
  61. chapters,
  62. (chapter) => agent(
  63. `分析以下章节中的所有角色:
  64. **章节标题**:${chapter.title}
  65. **章节序号**:第${chapter.order}章
  66. **章节内容**:
  67. ${chapter.content}
  68. ---
  69. 请提取所有出现的角色,包括:
  70. 1. 角色名称(主要名称和所有别名)
  71. 2. 角色类别(主角protagonist/反派antagonist/配角supporting/龙套minor)
  72. 3. 角色描述(外貌、身份、背景)
  73. 4. 性格特征
  74. 5. 说话方式
  75. 6. 与其他角色的关系
  76. 7. 重要性评分(1-10分)
  77. 注意:
  78. - 即使是只出现一次的龙套角色也要记录
  79. - 同一角色的不同称呼要记录在aliases中
  80. - 重要性根据角色在情节中的作用评分`,
  81. {
  82. label: `分析:${chapter.title}`,
  83. phase: '分析章节',
  84. schema: chapterSchema,
  85. }
  86. ),
  87. // 将chapter信息附加到结果中
  88. (result, chapter) => result ? { ...result, chapterId: chapter.id, chapterOrder: chapter.order } : null
  89. )
  90. const validResults = chapterResults.filter(Boolean)
  91. log(`成功分析 ${validResults.length}/${chapters.length} 个章节`)
  92. if (validResults.length === 0) {
  93. return { error: '没有成功分析任何章节' }
  94. }
  95. // 第二阶段:合并和去重
  96. phase('合并去重')
  97. const allCharacters = validResults.flatMap(r => r.characters.map(char => ({
  98. ...char,
  99. firstAppearance: r.chapterOrder,
  100. lastAppearance: r.chapterOrder,
  101. appearanceCount: 1,
  102. chapters: [r.chapterId],
  103. })))
  104. log(`收集到 ${allCharacters.length} 个角色实例,开始合并...`)
  105. const mergeSchema = {
  106. type: 'object',
  107. properties: {
  108. characters: {
  109. type: 'array',
  110. items: {
  111. type: 'object',
  112. properties: {
  113. id: { type: 'string' },
  114. name: { type: 'string' },
  115. aliases: { type: 'array', items: { type: 'string' } },
  116. category: { type: 'string' },
  117. description: { type: 'string' },
  118. personality: { type: 'string' },
  119. speechStyle: { type: 'string' },
  120. relationships: { type: 'array' },
  121. importance: { type: 'number' },
  122. firstAppearance: { type: 'number' },
  123. lastAppearance: { type: 'number' },
  124. appearanceCount: { type: 'number' },
  125. },
  126. required: ['id', 'name', 'category', 'importance'],
  127. },
  128. },
  129. },
  130. required: ['characters'],
  131. }
  132. const mergedResult = await agent(
  133. `请合并以下角色列表,识别同一角色的不同出现:
  134. ${JSON.stringify(allCharacters, null, 2)}
  135. 任务:
  136. 1. 识别哪些角色是同一人(考虑名字相似、别名、关系网等)
  137. 2. 合并同一角色的信息:
  138. - 综合所有描述
  139. - 合并所有别名
  140. - 统计出现次数
  141. - 记录首次和最后出现章节
  142. - 整合性格和说话方式
  143. - 合并关系网络
  144. 3. 为每个角色生成唯一ID(使用 name 的拼音或英文简写)
  145. 4. 按重要性排序
  146. 注意:
  147. - 宁可多列不同角色,也不要错误合并
  148. - 重要性应综合考虑所有出现`,
  149. {
  150. label: '合并角色',
  151. phase: '合并去重',
  152. schema: mergeSchema,
  153. }
  154. )
  155. const mergedCharacters = mergedResult?.characters || []
  156. log(`合并后共 ${mergedCharacters.length} 个不同角色`)
  157. // 第三阶段:生成 Skills
  158. phase('生成Skills')
  159. const skillSchema = {
  160. type: 'object',
  161. properties: {
  162. skillContent: { type: 'string' },
  163. },
  164. required: ['skillContent'],
  165. }
  166. const skills = await pipeline(
  167. mergedCharacters,
  168. (character) => agent(
  169. `为小说《${bookMetadata.title}》中的角色"${character.name}"生成一个完整的 Skill 文档。
  170. **角色信息**:
  171. ${JSON.stringify(character, null, 2)}
  172. **Skill 格式要求**:
  173. \`\`\`markdown
  174. ---
  175. 角色名: ${character.name}
  176. 来源: ${bookMetadata.title}
  177. 作者: ${bookMetadata.author || '未知'}
  178. ---
  179. # ${character.name}
  180. ## 基本信息
  181. - **别名**: [列出所有别名]
  182. - **类别**: [主角/反派/配角/龙套]
  183. - **重要性**: [X/10]
  184. ## 角色描述
  185. [详细的角色描述,包括外貌、身份、背景]
  186. ## 性格特征
  187. [详细的性格分析]
  188. ## 说话方式
  189. [说话风格和语言特点]
  190. ## 关系网络
  191. [与其他角色的关系]
  192. ## 出现信息
  193. - 首次出现:第 X 章
  194. - 最后出现:第 Y 章
  195. - 出现次数:Z 次
  196. ## 使用建议
  197. 在创作时,如果需要这种类型的角色,可以参考"${character.name}"的性格特征和说话方式。
  198. \`\`\`
  199. 请按照以上格式生成完整的 Skill 文档。`,
  200. {
  201. label: `生成:${character.name}`,
  202. phase: '生成Skills',
  203. schema: skillSchema,
  204. }
  205. ),
  206. (result, character) => result ? {
  207. id: character.id,
  208. characterName: character.name,
  209. sourceBook: bookMetadata.title,
  210. skillContent: result.skillContent,
  211. filePath: `${outputDir}/skills/${character.id}.md`,
  212. } : null
  213. )
  214. const validSkills = skills.filter(Boolean)
  215. log(`成功生成 ${validSkills.length}/${mergedCharacters.length} 个 Skills`)
  216. return {
  217. characters: mergedCharacters,
  218. skills: validSkills,
  219. summary: {
  220. totalChapters: chapters.length,
  221. successfulChapters: validResults.length,
  222. totalCharacters: mergedCharacters.length,
  223. totalSkills: validSkills.length,
  224. },
  225. }