本文以我的个人项目flashNovel(https://github.com/CuSO41108/flashnovel)为例记录一次从“单次 Prompt 生成小说”到“章节级 Agent Runtime”的工程化尝试。项目当前还不是完整的 multi-agent 系统更准确地说它是一个基于 LangGraph 的多阶段 LLM 工作流把长篇小说创作拆成 context、plan、draft、extract、check、review、rewrite、commit、checkpoint 等节点并通过结构化记忆维持章节之间的连续性。参考了GitHub上的LoreSmith项目1. LangGraph 是什么LangGraph 是 LangChain 生态里的一个图编排框架适合构建有状态、可循环、可分支的 LLM workflow 或 agent runtime。如果只写一个简单的 ChatGPT 调用普通函数就够了用户输入 - LLM - 输出但真实的 Agent 应用往往不是一次调用能完成的。它可能需要保存中间状态根据模型输出决定下一步多轮检查、修正、重试中间暂停等待人工确认在多个工具或多个角色之间切换出错后从 checkpoint 恢复。这类流程更接近状态机而不是一条直线。LangGraph 的核心价值就是把这些节点、状态和分支显式表达出来。官方文档中比较关键的几个能力包括StateGraph把应用建模成状态图每个节点读取和更新 state。Conditional edges根据 state 决定下一步走向。Persistence / checkpointer通过 checkpoint 保存图状态支持恢复、人工介入、时间旅行等能力。Interrupts在图执行中暂停等待用户输入后继续。Multi-agent patterns可以用 supervisor、handoff、orchestrator-worker 等模式组织多个 agent。LangSmith / Studio用于 trace、调试和观察 graph 执行过程。参考资料LangGraph Workflows and AgentsLangGraph PersistenceLangGraph InterruptsLangGraph Multi-agent2. 为什么小说生成适合 graph最开始我对“AI 写小说”的想象很简单给一个故事设定让模型直接生成下一章。但真正尝试长篇连续创作时会很快遇到问题前几章发生过什么人物关系有没有变化某个伏笔是否已经埋下或回收时间线有没有冲突本章是否完成了章节目标如果质量不够是直接重写还是只局部润色什么时候把这一章写入长期记忆如果每次都把全部历史正文塞进 Prompttoken 成本会越来越高而且模型也不一定能稳定抓住重点。更合理的方式是把章节生成拆成一个工作流load_context - plan - draft - extract - check - review - rewrite - commit - checkpoint在这个流程里每一步都有相对清晰的职责节点作用load_context读取本章需要的上下文包plan生成章节计划draft根据计划和上下文写正文extract从草稿中抽取候选记忆check检查连续性和设定冲突review像主编一样判断是否通过rewrite根据 review 意见重写commit提交最终章节并把候选记忆写入长期记忆checkpoint判断是否继续下一章或暂停等待人工确认这样做以后小说生成就不再是一次 Prompt而是一个可观察、可恢复、可迭代的章节级状态机。3. flashNovel 当前怎么用 LangGraph在flashNovel里LangGraph 的入口是backend/app/graph/workflow.py。核心代码大致如下from langgraph.graph import END, START, StateGraph graph StateGraph(GraphState) graph.add_node(load_context, load_context_node(self.runtime)) graph.add_node(plan, plan_node(self.runtime)) graph.add_node(draft, draft_node(self.runtime)) graph.add_node(extract, extract_node(self.runtime)) graph.add_node(check, check_node(self.runtime)) graph.add_node(review, review_node(self.runtime)) graph.add_node(rewrite, rewrite_node(self.runtime)) graph.add_node(commit, commit_node(self.runtime)) graph.add_node(checkpoint, checkpoint_node(self.runtime)) graph.add_node(finish, finish_node(self.runtime)) graph.add_edge(START, load_context) graph.add_edge(load_context, plan) graph.add_edge(plan, draft) graph.add_edge(draft, extract) graph.add_edge(extract, check) graph.add_edge(check, review) graph.add_conditional_edges( review, route_after_review, {rewrite: rewrite, commit: commit}, ) graph.add_edge(rewrite, extract) graph.add_edge(commit, checkpoint) graph.add_conditional_edges( checkpoint, route_after_checkpoint, {load_context: load_context, finish: finish}, ) graph.add_edge(finish, END) self._compiled graph.compile()这里最重要的是两个条件边。第一个条件边发生在review之后review - rewrite review - commit如果审稿节点认为当前章节需要修改就进入rewrite如果通过就进入commit。第二个条件边发生在checkpoint之后checkpoint - load_context checkpoint - finish如果本批次还没写够指定章节数就继续下一章如果达到上限就暂停等待人工确认。3.1 GraphState工作流共享状态项目里的 state 定义在backend/app/graph/state.pyclass GraphState(TypedDict, totalFalse): run_id: str story_id: str seed_prompt: str chapter: int chapters_done_in_batch: int max_chapters: int context: dict[str, Any] plan: dict[str, Any] draft: str candidate_memory: dict[str, Any] consistency: dict[str, Any] review: dict[str, Any] rewrite_count: int status: str next_action: str error: str每个节点都读取并更新这个 state。比如draft节点会写入state[draft]review节点会写入state[review]和state[next_action]。这比散落在各个函数里的局部变量更清晰因为整个章节生成过程的状态都被集中建模了。3.2 rewrite loop每章最多迭代修改项目里当前设置了MAX_REWRITES 2review 节点会根据模型返回的 verdict 决定下一步if verdict in {rewrite, polish} and rewrite_count MAX_REWRITES: state[next_action] rewrite else: state[next_action] commitrewrite 完成后并不是直接提交而是回到rewrite - extract - check - review这样做的原因是重写后的正文可能引入新的设定变化或连续性问题所以需要重新抽取记忆、重新检查、重新 review。3.3 checkpoint每批章节后等待人工确认项目默认每批最多生成 5 章也可以在前端调整max_chapters。当本批次章节数达到上限时checkpoint节点会把 run 状态改成awaiting_confirmation这时前端可以显示“等待确认”用户确认后再继续跑下一批章节。这已经具备 Human-in-the-loop 的雏形但它还不是 LangGraph 官方interrupt()机制而是项目自己在 Runtime 层实现的暂停、确认和恢复。4. 上下文工程不是塞全文而是结构化记忆加 token budget长篇小说最难的地方不是生成一章而是保持几十章、几百章之间的连续性。如果每一章都把前文全文塞给模型会有几个问题token 成本随章节数线性甚至指数增长模型容易被大量无关文本干扰很多信息其实不需要全文只需要摘要、状态变化和约束草稿被重写前不应该污染长期记忆。所以flashNovel使用了结构化记忆。当前记忆大致分为四类层级内容Artifact草稿、最终章节、prompt 快照、原始输出Canon角色、世界规则、地点等稳定设定Episodic章节摘要、时间线事件Continuity人物关系、伏笔、状态变化、review issue生成下一章时系统会调用get_writer_context()组装出一个上下文包context { story: ..., workspace: ..., chapter: chapter, chapter_plan: ..., characters: ..., world_rules: ..., locations: ..., recent_summaries: ..., timeline: ..., relationships: ..., foreshadows: ..., state_changes: ..., review_reports: ..., review_issues: ..., }这里的关键点是系统读取的是“对下一章有用的信息”而不是机械塞入全部正文。同时项目还实现了简单的 token budget 裁剪def apply_context_budget(context: dict[str, Any], token_budget: int) - dict[str, Any]: if token_budget 0: return context list_keys [ recent_summaries, review_issues, foreshadows, relationships, state_changes, timeline, review_reports, ]在预算有限时系统会优先保留 story、characters、world_rules 等核心设定然后裁剪列表型上下文。4.1 什么是候选记忆项目里有一个概念叫candidate_memory可以理解为“候选记忆”。它来自extract节点模型读完当前草稿后抽取出可能影响后续章节的信息例如本章摘要关键事件时间线变化人物关系变化伏笔埋设或回收角色状态变化。但这些信息不会立刻进入长期记忆。原因很简单当前草稿可能会被 review 打回重写。如果一个被废弃的草稿已经污染了长期记忆后续章节就可能继承错误设定。所以项目采用了这个策略draft - extract candidate_memory - check/review/rewrite - commit - persist_candidate_memory也就是先暂存为候选记忆只有最终章节被 commit 后才把候选记忆写入 SQLite 的章节摘要、时间线、人物关系、伏笔、状态变化和 review issue 表。这是我觉得小说类 Agent 很需要的一层保护。5. 和真正 multi-agent 的差距虽然项目里有很多“角色”章节规划助手小说写作助手记忆抽取器连续性审校器小说主编改稿助手。但当前它们只是不同的 prompt/template 和 tool并不是独立 agent。当前项目更准确的定义是单 Runtime 多节点 多角色 Prompt 工具链真正的 multi-agent 通常至少会有以下特征每个 agent 有自己的职责、状态和工具集合agent 之间有消息传递或 handoff有 supervisor/router 决定任务分配可能存在并行 agent不同 agent 可以使用不同模型、不同 memory、不同评估策略每个 agent 的结果可以被其他 agent 审核、引用或合并。对比一下flashNovel当前的工具链是由同一个RuntimeHost调度同一个 LLM client 发起调用同一个 store 持久化数据。这不是坏事。对 V1 来说先把 workflow、memory、checkpoint、SSE、前端工作台跑通比一开始就拆成多个 agent 更重要。什么时候需要拆成不同 agent我觉得有几个信号角色需要独立工具例如 ContinuityAgent 需要专门查记忆库WriterAgent 只负责写正文。角色需要不同模型例如写作使用强模型审稿使用便宜快速模型记忆抽取使用结构化输出能力更稳的模型。角色需要独立记忆例如 EditorAgent 保存全局创作策略MemoryAgent 保存长期事实。角色之间需要协商或仲裁例如 WriterAgent 和 ReviewerAgent 对一章是否通过产生冲突需要 EditorAgent 裁决。部分任务可以并行例如连续性检查、节奏检查、人物动机检查可以并行跑然后汇总成 review report。到了这些阶段再拆 multi-agent 就比较自然。6. 当前不足6.1 同步串行吞吐有限当前项目对用户接口来说是异步的创建 run 后后端 worker 在后台执行。但 Agent Runtime 内部还是同步串行graph.invoke(state)是同步执行工具层使用complete_sync/stream_sync每章的 plan、draft、extract、check、review 串行执行rewrite 后又会重新串行执行 extract、check、review。如果一章没有重写大约需要 5 次 LLM 调用。如果最多重写 2 次最坏可能达到plan draft extract check review 2 * (rewrite extract check review) 13 次 LLM 调用所以主要瓶颈是 LLM 延迟而不是 SQLite 或文件写入。6.2 没有接 LangGraph 原生 checkpointer项目现在有自己的 run/checkpoint/event 持久化但还没有接 LangGraph 官方 checkpointer。也就是说当前状态恢复是项目 Runtime 层自己管理的而不是通过 LangGraph persistence 统一管理 graph state。6.3 Human-in-the-loop 还不是 interrupt项目目前的人工确认点是每批章节结束 - awaiting_confirmation - 用户确认 - resume这已经能满足“批次确认”但还不是 LangGraph 官方interrupt()。更理想的方式是在关键节点中断例如plan 后让用户确认本章方向review 后让用户选择 accept/rewrite/manual editcommit 前让用户确认是否写入长期记忆。6.4 没有接 LangSmith项目目前通过 SSE 和数据库 events 观察运行过程但没有 LangSmith trace。如果接入 LangSmith可以更方便地看到每个节点耗时每次 LLM 输入输出条件边为什么走到 rewrite每章 rewrite 了几次哪些 prompt 成本最高失败发生在哪个节点。6.5 没有生产级任务队列当前 worker 是进程内 daemon thread适合本地 MVP。如果要支持多用户或高并发需要引入生产级队列和 worker例如Redis Queue / Celery / DramatiqKafka / RabbitMQ独立 Python Agent Runtime workerJava 平台层负责用户、权限、租户、审计、任务调度和限流。7. 下一步优化方向7.1 async runtime第一步可以把 Runtime 从同步调用改成异步graph.invoke改为graph.ainvokeLLM client 使用 async HTTP client工具层提供 async execute可并行的检查任务用asyncio.gatherworker 支持并发执行多个 run。这能改善吞吐但要注意小说章节之间强依赖不能盲目并行章节。比较适合并行的是章节内部的检查项例如draft - continuity_check - style_check - character_motivation_check - pacing_check - aggregate_review7.2 接入 LangGraph checkpointer把项目自建 checkpoint 和 LangGraph persistence 对齐每个 run 对应 LangGraph threadgraph state 通过 checkpointer 保存恢复时从 checkpoint 继续项目自己的 run/event 表保留为业务视图。这样可以减少“业务 checkpoint”和“graph state checkpoint”之间的不一致。7.3 使用 interrupt 做更细的 Human-in-the-loop下一步可以把人工确认点放得更细章节计划生成后暂停让用户确认方向review 结果为 rewrite 时暂停让用户选择自动重写还是手动修改commit 前展示候选记忆让用户确认哪些写入长期记忆。这会比“每 5 章确认一次”更适合真实创作。7.4 接入 LangSmith trace接入 LangSmith 后可以把项目从“能跑”推进到“可调试、可评估”。尤其是 rewrite loop一旦出现质量不稳定就需要观察review prompt 是否太严格check prompt 是否误判rewrite 是否真的修复了问题token budget 裁剪是否丢掉了关键设定某个模型是否不适合做结构化抽取。7.5 拆分真正的 Agent当前 V1 不急着拆但后续可以考虑Agent职责WriterAgent写章节正文ContinuityAgent检查设定、时间线、人物状态MemoryAgent抽取、筛选、合并长期记忆EditorAgent判断章节质量和重写方向PublisherAgent导出章节、生成阅读视图、整理发布格式这时 LangGraph 的 multi-agent 模式会更有价值。总结我对这个项目目前的定位是不是完整 multi-agent 系统 而是一个以 LangGraph 为核心的章节级 Agent Runtime MVP。它已经具备几个 Agent 应用开发里很重要的工程要素状态机式 LLM workflow结构化上下文工程长期记忆与候选记忆review/rewrite 质量循环人工确认 checkpoint事件流和运行可观察性前后端工作台闭环。但距离生产级 Agent 平台还有明显差距运行时仍是同步串行未接 LangGraph 原生 checkpointerHuman-in-the-loop 还不是 interrupt未接 LangSmith trace没有生产级任务队列还不是真正的 multi-agent 协作。这也是下一阶段最值得继续做的地方先把 Runtime 做稳再把角色拆成 agent先把记忆和评估跑通再谈更复杂的协作模式。
从 LangGraph 到小说 Agent Runtime:用 flashNovel 实现章节级工作流、上下文记忆与人工确认
发布时间:2026/6/3 1:05:29
本文以我的个人项目flashNovel(https://github.com/CuSO41108/flashnovel)为例记录一次从“单次 Prompt 生成小说”到“章节级 Agent Runtime”的工程化尝试。项目当前还不是完整的 multi-agent 系统更准确地说它是一个基于 LangGraph 的多阶段 LLM 工作流把长篇小说创作拆成 context、plan、draft、extract、check、review、rewrite、commit、checkpoint 等节点并通过结构化记忆维持章节之间的连续性。参考了GitHub上的LoreSmith项目1. LangGraph 是什么LangGraph 是 LangChain 生态里的一个图编排框架适合构建有状态、可循环、可分支的 LLM workflow 或 agent runtime。如果只写一个简单的 ChatGPT 调用普通函数就够了用户输入 - LLM - 输出但真实的 Agent 应用往往不是一次调用能完成的。它可能需要保存中间状态根据模型输出决定下一步多轮检查、修正、重试中间暂停等待人工确认在多个工具或多个角色之间切换出错后从 checkpoint 恢复。这类流程更接近状态机而不是一条直线。LangGraph 的核心价值就是把这些节点、状态和分支显式表达出来。官方文档中比较关键的几个能力包括StateGraph把应用建模成状态图每个节点读取和更新 state。Conditional edges根据 state 决定下一步走向。Persistence / checkpointer通过 checkpoint 保存图状态支持恢复、人工介入、时间旅行等能力。Interrupts在图执行中暂停等待用户输入后继续。Multi-agent patterns可以用 supervisor、handoff、orchestrator-worker 等模式组织多个 agent。LangSmith / Studio用于 trace、调试和观察 graph 执行过程。参考资料LangGraph Workflows and AgentsLangGraph PersistenceLangGraph InterruptsLangGraph Multi-agent2. 为什么小说生成适合 graph最开始我对“AI 写小说”的想象很简单给一个故事设定让模型直接生成下一章。但真正尝试长篇连续创作时会很快遇到问题前几章发生过什么人物关系有没有变化某个伏笔是否已经埋下或回收时间线有没有冲突本章是否完成了章节目标如果质量不够是直接重写还是只局部润色什么时候把这一章写入长期记忆如果每次都把全部历史正文塞进 Prompttoken 成本会越来越高而且模型也不一定能稳定抓住重点。更合理的方式是把章节生成拆成一个工作流load_context - plan - draft - extract - check - review - rewrite - commit - checkpoint在这个流程里每一步都有相对清晰的职责节点作用load_context读取本章需要的上下文包plan生成章节计划draft根据计划和上下文写正文extract从草稿中抽取候选记忆check检查连续性和设定冲突review像主编一样判断是否通过rewrite根据 review 意见重写commit提交最终章节并把候选记忆写入长期记忆checkpoint判断是否继续下一章或暂停等待人工确认这样做以后小说生成就不再是一次 Prompt而是一个可观察、可恢复、可迭代的章节级状态机。3. flashNovel 当前怎么用 LangGraph在flashNovel里LangGraph 的入口是backend/app/graph/workflow.py。核心代码大致如下from langgraph.graph import END, START, StateGraph graph StateGraph(GraphState) graph.add_node(load_context, load_context_node(self.runtime)) graph.add_node(plan, plan_node(self.runtime)) graph.add_node(draft, draft_node(self.runtime)) graph.add_node(extract, extract_node(self.runtime)) graph.add_node(check, check_node(self.runtime)) graph.add_node(review, review_node(self.runtime)) graph.add_node(rewrite, rewrite_node(self.runtime)) graph.add_node(commit, commit_node(self.runtime)) graph.add_node(checkpoint, checkpoint_node(self.runtime)) graph.add_node(finish, finish_node(self.runtime)) graph.add_edge(START, load_context) graph.add_edge(load_context, plan) graph.add_edge(plan, draft) graph.add_edge(draft, extract) graph.add_edge(extract, check) graph.add_edge(check, review) graph.add_conditional_edges( review, route_after_review, {rewrite: rewrite, commit: commit}, ) graph.add_edge(rewrite, extract) graph.add_edge(commit, checkpoint) graph.add_conditional_edges( checkpoint, route_after_checkpoint, {load_context: load_context, finish: finish}, ) graph.add_edge(finish, END) self._compiled graph.compile()这里最重要的是两个条件边。第一个条件边发生在review之后review - rewrite review - commit如果审稿节点认为当前章节需要修改就进入rewrite如果通过就进入commit。第二个条件边发生在checkpoint之后checkpoint - load_context checkpoint - finish如果本批次还没写够指定章节数就继续下一章如果达到上限就暂停等待人工确认。3.1 GraphState工作流共享状态项目里的 state 定义在backend/app/graph/state.pyclass GraphState(TypedDict, totalFalse): run_id: str story_id: str seed_prompt: str chapter: int chapters_done_in_batch: int max_chapters: int context: dict[str, Any] plan: dict[str, Any] draft: str candidate_memory: dict[str, Any] consistency: dict[str, Any] review: dict[str, Any] rewrite_count: int status: str next_action: str error: str每个节点都读取并更新这个 state。比如draft节点会写入state[draft]review节点会写入state[review]和state[next_action]。这比散落在各个函数里的局部变量更清晰因为整个章节生成过程的状态都被集中建模了。3.2 rewrite loop每章最多迭代修改项目里当前设置了MAX_REWRITES 2review 节点会根据模型返回的 verdict 决定下一步if verdict in {rewrite, polish} and rewrite_count MAX_REWRITES: state[next_action] rewrite else: state[next_action] commitrewrite 完成后并不是直接提交而是回到rewrite - extract - check - review这样做的原因是重写后的正文可能引入新的设定变化或连续性问题所以需要重新抽取记忆、重新检查、重新 review。3.3 checkpoint每批章节后等待人工确认项目默认每批最多生成 5 章也可以在前端调整max_chapters。当本批次章节数达到上限时checkpoint节点会把 run 状态改成awaiting_confirmation这时前端可以显示“等待确认”用户确认后再继续跑下一批章节。这已经具备 Human-in-the-loop 的雏形但它还不是 LangGraph 官方interrupt()机制而是项目自己在 Runtime 层实现的暂停、确认和恢复。4. 上下文工程不是塞全文而是结构化记忆加 token budget长篇小说最难的地方不是生成一章而是保持几十章、几百章之间的连续性。如果每一章都把前文全文塞给模型会有几个问题token 成本随章节数线性甚至指数增长模型容易被大量无关文本干扰很多信息其实不需要全文只需要摘要、状态变化和约束草稿被重写前不应该污染长期记忆。所以flashNovel使用了结构化记忆。当前记忆大致分为四类层级内容Artifact草稿、最终章节、prompt 快照、原始输出Canon角色、世界规则、地点等稳定设定Episodic章节摘要、时间线事件Continuity人物关系、伏笔、状态变化、review issue生成下一章时系统会调用get_writer_context()组装出一个上下文包context { story: ..., workspace: ..., chapter: chapter, chapter_plan: ..., characters: ..., world_rules: ..., locations: ..., recent_summaries: ..., timeline: ..., relationships: ..., foreshadows: ..., state_changes: ..., review_reports: ..., review_issues: ..., }这里的关键点是系统读取的是“对下一章有用的信息”而不是机械塞入全部正文。同时项目还实现了简单的 token budget 裁剪def apply_context_budget(context: dict[str, Any], token_budget: int) - dict[str, Any]: if token_budget 0: return context list_keys [ recent_summaries, review_issues, foreshadows, relationships, state_changes, timeline, review_reports, ]在预算有限时系统会优先保留 story、characters、world_rules 等核心设定然后裁剪列表型上下文。4.1 什么是候选记忆项目里有一个概念叫candidate_memory可以理解为“候选记忆”。它来自extract节点模型读完当前草稿后抽取出可能影响后续章节的信息例如本章摘要关键事件时间线变化人物关系变化伏笔埋设或回收角色状态变化。但这些信息不会立刻进入长期记忆。原因很简单当前草稿可能会被 review 打回重写。如果一个被废弃的草稿已经污染了长期记忆后续章节就可能继承错误设定。所以项目采用了这个策略draft - extract candidate_memory - check/review/rewrite - commit - persist_candidate_memory也就是先暂存为候选记忆只有最终章节被 commit 后才把候选记忆写入 SQLite 的章节摘要、时间线、人物关系、伏笔、状态变化和 review issue 表。这是我觉得小说类 Agent 很需要的一层保护。5. 和真正 multi-agent 的差距虽然项目里有很多“角色”章节规划助手小说写作助手记忆抽取器连续性审校器小说主编改稿助手。但当前它们只是不同的 prompt/template 和 tool并不是独立 agent。当前项目更准确的定义是单 Runtime 多节点 多角色 Prompt 工具链真正的 multi-agent 通常至少会有以下特征每个 agent 有自己的职责、状态和工具集合agent 之间有消息传递或 handoff有 supervisor/router 决定任务分配可能存在并行 agent不同 agent 可以使用不同模型、不同 memory、不同评估策略每个 agent 的结果可以被其他 agent 审核、引用或合并。对比一下flashNovel当前的工具链是由同一个RuntimeHost调度同一个 LLM client 发起调用同一个 store 持久化数据。这不是坏事。对 V1 来说先把 workflow、memory、checkpoint、SSE、前端工作台跑通比一开始就拆成多个 agent 更重要。什么时候需要拆成不同 agent我觉得有几个信号角色需要独立工具例如 ContinuityAgent 需要专门查记忆库WriterAgent 只负责写正文。角色需要不同模型例如写作使用强模型审稿使用便宜快速模型记忆抽取使用结构化输出能力更稳的模型。角色需要独立记忆例如 EditorAgent 保存全局创作策略MemoryAgent 保存长期事实。角色之间需要协商或仲裁例如 WriterAgent 和 ReviewerAgent 对一章是否通过产生冲突需要 EditorAgent 裁决。部分任务可以并行例如连续性检查、节奏检查、人物动机检查可以并行跑然后汇总成 review report。到了这些阶段再拆 multi-agent 就比较自然。6. 当前不足6.1 同步串行吞吐有限当前项目对用户接口来说是异步的创建 run 后后端 worker 在后台执行。但 Agent Runtime 内部还是同步串行graph.invoke(state)是同步执行工具层使用complete_sync/stream_sync每章的 plan、draft、extract、check、review 串行执行rewrite 后又会重新串行执行 extract、check、review。如果一章没有重写大约需要 5 次 LLM 调用。如果最多重写 2 次最坏可能达到plan draft extract check review 2 * (rewrite extract check review) 13 次 LLM 调用所以主要瓶颈是 LLM 延迟而不是 SQLite 或文件写入。6.2 没有接 LangGraph 原生 checkpointer项目现在有自己的 run/checkpoint/event 持久化但还没有接 LangGraph 官方 checkpointer。也就是说当前状态恢复是项目 Runtime 层自己管理的而不是通过 LangGraph persistence 统一管理 graph state。6.3 Human-in-the-loop 还不是 interrupt项目目前的人工确认点是每批章节结束 - awaiting_confirmation - 用户确认 - resume这已经能满足“批次确认”但还不是 LangGraph 官方interrupt()。更理想的方式是在关键节点中断例如plan 后让用户确认本章方向review 后让用户选择 accept/rewrite/manual editcommit 前让用户确认是否写入长期记忆。6.4 没有接 LangSmith项目目前通过 SSE 和数据库 events 观察运行过程但没有 LangSmith trace。如果接入 LangSmith可以更方便地看到每个节点耗时每次 LLM 输入输出条件边为什么走到 rewrite每章 rewrite 了几次哪些 prompt 成本最高失败发生在哪个节点。6.5 没有生产级任务队列当前 worker 是进程内 daemon thread适合本地 MVP。如果要支持多用户或高并发需要引入生产级队列和 worker例如Redis Queue / Celery / DramatiqKafka / RabbitMQ独立 Python Agent Runtime workerJava 平台层负责用户、权限、租户、审计、任务调度和限流。7. 下一步优化方向7.1 async runtime第一步可以把 Runtime 从同步调用改成异步graph.invoke改为graph.ainvokeLLM client 使用 async HTTP client工具层提供 async execute可并行的检查任务用asyncio.gatherworker 支持并发执行多个 run。这能改善吞吐但要注意小说章节之间强依赖不能盲目并行章节。比较适合并行的是章节内部的检查项例如draft - continuity_check - style_check - character_motivation_check - pacing_check - aggregate_review7.2 接入 LangGraph checkpointer把项目自建 checkpoint 和 LangGraph persistence 对齐每个 run 对应 LangGraph threadgraph state 通过 checkpointer 保存恢复时从 checkpoint 继续项目自己的 run/event 表保留为业务视图。这样可以减少“业务 checkpoint”和“graph state checkpoint”之间的不一致。7.3 使用 interrupt 做更细的 Human-in-the-loop下一步可以把人工确认点放得更细章节计划生成后暂停让用户确认方向review 结果为 rewrite 时暂停让用户选择自动重写还是手动修改commit 前展示候选记忆让用户确认哪些写入长期记忆。这会比“每 5 章确认一次”更适合真实创作。7.4 接入 LangSmith trace接入 LangSmith 后可以把项目从“能跑”推进到“可调试、可评估”。尤其是 rewrite loop一旦出现质量不稳定就需要观察review prompt 是否太严格check prompt 是否误判rewrite 是否真的修复了问题token budget 裁剪是否丢掉了关键设定某个模型是否不适合做结构化抽取。7.5 拆分真正的 Agent当前 V1 不急着拆但后续可以考虑Agent职责WriterAgent写章节正文ContinuityAgent检查设定、时间线、人物状态MemoryAgent抽取、筛选、合并长期记忆EditorAgent判断章节质量和重写方向PublisherAgent导出章节、生成阅读视图、整理发布格式这时 LangGraph 的 multi-agent 模式会更有价值。总结我对这个项目目前的定位是不是完整 multi-agent 系统 而是一个以 LangGraph 为核心的章节级 Agent Runtime MVP。它已经具备几个 Agent 应用开发里很重要的工程要素状态机式 LLM workflow结构化上下文工程长期记忆与候选记忆review/rewrite 质量循环人工确认 checkpoint事件流和运行可观察性前后端工作台闭环。但距离生产级 Agent 平台还有明显差距运行时仍是同步串行未接 LangGraph 原生 checkpointerHuman-in-the-loop 还不是 interrupt未接 LangSmith trace没有生产级任务队列还不是真正的 multi-agent 协作。这也是下一阶段最值得继续做的地方先把 Runtime 做稳再把角色拆成 agent先把记忆和评估跑通再谈更复杂的协作模式。