run-chapter-workflow.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import type { LlmConfig } from "@/stores/wiki-store"
  2. import type { AiWorkflowMode } from "@/lib/agent/workflow-mode"
  3. import type {
  4. ChapterWorkflowEvent,
  5. DeepChapterGenerationCallbacks,
  6. DeepChapterGenerationDeps,
  7. DeepChapterGenerationInput,
  8. DeepChapterGenerationResult,
  9. } from "@/lib/novel/deep-chapter-generation"
  10. import type { AgentActivityEvent, AgentToolEvent, Tool, ToolExecutionContext } from "../types"
  11. export type RunDeepChapterGeneration = (
  12. input: DeepChapterGenerationInput,
  13. callbacks?: DeepChapterGenerationCallbacks,
  14. deps?: DeepChapterGenerationDeps,
  15. signal?: AbortSignal,
  16. ) => Promise<DeepChapterGenerationResult>
  17. export interface RunChapterWorkflowToolOptions {
  18. projectPath: string
  19. llmConfig: LlmConfig
  20. aiWorkflowMode: AiWorkflowMode
  21. runDeepChapterGeneration: RunDeepChapterGeneration
  22. onToolEvent?: (event: AgentToolEvent) => void
  23. onActivityEvent?: (event: AgentActivityEvent) => void
  24. /**
  25. * 当 AI 未在工具调用参数中携带 planBlueprint 时,从这里兜底取已确认的章节计划。
  26. * 保证用户确认的计划为强制约束,不依赖模型是否遵守自然语言提示。
  27. */
  28. getPlanBlueprint?: () => string | undefined
  29. }
  30. interface RunChapterWorkflowParams {
  31. intent?: string
  32. userRequest?: string
  33. chapterNumber?: number
  34. workflowMode?: AiWorkflowMode
  35. planBlueprint?: string
  36. }
  37. function toNumber(value: unknown): number | undefined {
  38. if (typeof value === "number" && Number.isFinite(value)) return value
  39. if (typeof value === "string" && value.trim()) {
  40. const parsed = Number(value)
  41. return Number.isFinite(parsed) ? parsed : undefined
  42. }
  43. return undefined
  44. }
  45. function normalizeParams(params: Record<string, unknown>): RunChapterWorkflowParams {
  46. return {
  47. intent: typeof params.intent === "string" ? params.intent : undefined,
  48. userRequest: typeof params.userRequest === "string" ? params.userRequest : undefined,
  49. chapterNumber: toNumber(params.chapterNumber),
  50. workflowMode:
  51. params.workflowMode === "fast" || params.workflowMode === "standard" || params.workflowMode === "strict"
  52. ? params.workflowMode
  53. : undefined,
  54. planBlueprint: typeof params.planBlueprint === "string" ? params.planBlueprint : undefined,
  55. }
  56. }
  57. function workflowEventToToolEvent(parentCallId: string, event: ChapterWorkflowEvent): AgentToolEvent {
  58. return {
  59. type: event.type === "started" ? "call_started" : event.type === "error" ? "error" : "result",
  60. callId: `${parentCallId}:${event.name}`,
  61. parentCallId,
  62. name: event.name,
  63. params: {
  64. title: event.title,
  65. ...(event.detail ? { detail: event.detail } : {}),
  66. ...(event.params ?? {}),
  67. },
  68. result: event.result ?? event.detail ?? event.title,
  69. timestamp: event.timestamp,
  70. }
  71. }
  72. export function createRunChapterWorkflowTool(options: RunChapterWorkflowToolOptions): Tool {
  73. return {
  74. name: "run_chapter_workflow",
  75. description: [
  76. "运行小说章节写作工作流。用于生成、续写、改写或润色章节。",
  77. "调用后会读取项目上下文、生成写作任务书、生成正文,并按当前模式执行审稿、返修和去AI味。",
  78. "正文由本工具直接交付给用户,调用成功后本轮任务即结束:不要复述、改写或补充正文。",
  79. "一次调用只处理一章;保存到项目文件仍需要写入工具和用户确认。",
  80. ].join("\n"),
  81. category: "action",
  82. permission: "auto",
  83. executeTimeoutMs: 0,
  84. finalizesRun: true,
  85. parameters: {
  86. intent: {
  87. type: "string",
  88. description: "章节任务类型:write_chapter、continue_chapter、rewrite_chapter 或 polish_chapter。",
  89. enum: ["write_chapter", "continue_chapter", "rewrite_chapter", "polish_chapter"],
  90. },
  91. userRequest: {
  92. type: "string",
  93. description: "用户原始章节写作请求,必须完整保留。",
  94. required: true,
  95. },
  96. chapterNumber: {
  97. type: "integer",
  98. description: "目标章节号,无法确定时可以省略。",
  99. },
  100. workflowMode: {
  101. type: "string",
  102. description: "执行强度:fast、standard 或 strict。省略时使用当前 AI 会话模式。",
  103. enum: ["fast", "standard", "strict"],
  104. },
  105. planBlueprint: {
  106. type: "string",
  107. description:
  108. "用户在会话层已确认的章节计划原文。若存在,必须完整透传,作为写作任务书的权威依据;不得改写或省略。",
  109. },
  110. },
  111. async execute(rawParams, signal, context?: ToolExecutionContext) {
  112. const params = normalizeParams(rawParams)
  113. const userRequest = params.userRequest?.trim()
  114. if (!userRequest) {
  115. return "错误:缺少 userRequest,无法运行章节工作流。"
  116. }
  117. const parentCallId = context?.callId ?? `run_chapter_workflow:${Date.now()}`
  118. const emitToolEvent = context?.onToolEvent ?? options.onToolEvent
  119. const emitActivityEvent = context?.onActivityEvent ?? options.onActivityEvent
  120. // 兜底:AI 未在工具调用参数中携带 planBlueprint 时,从外部 getter 补上,
  121. // 保证用户确认的计划一定进入章节生成链路,不依赖模型是否遵守自然语言提示。
  122. const planBlueprint = params.planBlueprint?.trim() || options.getPlanBlueprint?.()?.trim() || undefined
  123. const result = await options.runDeepChapterGeneration(
  124. {
  125. projectPath: options.projectPath,
  126. userRequest,
  127. chapterNumber: params.chapterNumber,
  128. llmConfig: options.llmConfig,
  129. aiWorkflowMode: params.workflowMode ?? options.aiWorkflowMode,
  130. planBlueprint,
  131. },
  132. {
  133. onWorkflowEvent: (event) => {
  134. emitToolEvent?.(workflowEventToToolEvent(parentCallId, event))
  135. },
  136. onActivityEvent: (event) => {
  137. emitActivityEvent?.({
  138. ...event,
  139. toolCallId: event.toolCallId ?? parentCallId,
  140. })
  141. },
  142. // 终稿直接交付给会话,避免外层模型再复述一遍正文。
  143. // 履约修复会再次触发,后一次覆盖前一次。
  144. onFinalContent: (content) => {
  145. const body = content.trim()
  146. if (body) context?.onFinalContent?.(body)
  147. },
  148. onRequestTrace: context?.onRequestTrace,
  149. },
  150. undefined,
  151. signal,
  152. )
  153. return [
  154. "章节工作流完成。",
  155. `是否返修:${result.revised ? "是" : "否"}`,
  156. `任务书:${result.taskBrief}`,
  157. result.executionReport ? `执行报告:\n${result.executionReport}` : "",
  158. result.planCompliance ? `计划履约度:\n${result.planCompliance}` : "",
  159. "",
  160. "最终正文:",
  161. result.finalContent,
  162. ].filter((line) => line !== "").join("\n")
  163. },
  164. }
  165. }