总结:本文记录当前的整体架构与流程,并附一次实际写作的产物链路。把「收集、设计、精修、审校」交给需人工确认的智能体,把「写作」做成一条确定性的执行流水线;核心思路是知识库先行、阶段分离、人在环中。
结论:当前系统输出的单元剧文章(目标平台:Lofter,类型:动画同人文),远没达到能够发布平台接受检验的水平。AI写小说,是否能够做到:AI为主+人工为辅,还是只能做到:人工为主+AI为辅,目前博主正探索中。
文章目录
-
- [一、前言:AI 写长篇,到底难在哪](#一、前言:AI 写长篇,到底难在哪)
- 二、项目速览
- 三、总体架构:知识库-设计-写作-审核
- 四、五段式流程
- 五、几个关键设计
- 六、实战案例
- 七、资源与结语
-
- [7.1 资源](#7.1 资源)
- [7.2 结论](#7.2 结论)
- [7.3 教训](#7.3 教训)
- 附录:AI小说示例(单场景,质量很低,不建议欣赏)
一、前言:AI 写长篇,到底难在哪
让大模型写一段几百字的场景并不难,难的是把它写「长」还写「住」。实践中遇到的问题主要在四处:
| 问题 | 表现 | 原因分析 | 解决思路 |
|---|---|---|---|
| 人设崩 | 角色像换了个人,说话方式、决策逻辑前后不一致 | 缺少稳定的人设约束,模型在不同段落中"重写"角色 | 统一角色卡、固定说话习惯、动作逻辑和情绪基线 |
| 文风漂 | 开头是一种语气,中段像换了个模型 | 生成过程没有统一文风召回和约束,长文本容易漂移 | 通过 RAG 检索风格示范,分段锁定语气与句法 |
| 场景不连贯 | 上一幕的动作、时间、道具,下一幕对不上 | 缺少连续性上下文和节拍约束,场景切换时丢失状态 | 用 beat 结构、前情梗概和场景状态跟踪维护连续性 |
二、项目速览
一套基于 Qdrant 向量检索 + LLM 的小说写作系统,技术栈为 Python + Qdrant + LangChain + Git多仓,配合 opencode 的 subagent 编排。一次创作拆成五段:
收集 → 设计 → 写作 → 精修 → 审校
其中「收集」是写作前的知识库构建,「写作」是不调用分析/设计能力的纯执行段,其余三段以「subagent + 人工确认」的形式进行。
三、总体架构:知识库-设计-写作-审核
系统的第一层不是写作,而是写作前的知识库预处理。四个独立子仓各管一段,构成一条从「抓原文」到「产资产」的流程:
┌──────────────────────────────────────────────┐
│ ① 预处理 / 知识库层(写作前动作 · 独立子仓) │
└──────────────────────────────────────────────┘
┌───────────────┐ ┌────────────────────────┐
│ lofter_fetch │────────►│ 短篇原文(Markdown) │
│ 抓取同人短篇 │ └───────────┬────────────┘
└───────────────┘ │
┌──────────────────────┼───────────────────────┐
▼ ▼ ▼
┌────────────────────┐ ┌──────────────────┐ ┌────────────────────┐
│short-story-analysis│ │ style-corpus-rag │ │ csp │
│ 多维度分析 │ │ 分块→标注→向量化 │ │ 检索→蒸馏→质检角色卡│
└─────────┬──────────┘ └────────┬─────────┘ └─────────┬──────────┘
▼ ▼ ▼
内核/框架/开篇 三报告 文风向量库(Qdrant) 角色卡(人设规范)
└──────────────┬───────┴──────────────────────┘
▼
(三类写作前知识资产)
┌──────────────────────────────────────────────────────────────────────┐
│ ② 编排层 opencode subagent(人在环中,阶段分离) │
│ scene-designer → scene-refiner → scene-polisher → scene-wordsmith │
│ → scene-reviewer(可选) │
└───────────────────────────────┬──────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────────────┐
│ ③ 执行层 src/writing/(纯确定性流水线,不内置分析 / 设计 / 精修) │
│ scene_pipeline → beat_parser / beat_retriever / writing_guide / │
│ screen_writer / scene_summarizer │
└───────────────────────────────┬──────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────────────┐
│ ④ 配置层 config/* ⑤ 输出层 output/*(带时间戳,可追溯) │
└──────────────────────────────────────────────────────────────────────┘
四个子仓的分工如下:
- lofter_fetch(采集):从 LOFTER 按热度/标签抓取同人短篇,落盘为带元数据的 Markdown。
- short-story-analysis(分析) :对抓取到的文章做多维度分析,产出「底层内核 / 叙事框架 / 开篇拆解」三份方法论报告------这是后续「设计」的原料。
- csp · Character Card Producer(角色卡) :根据网络公开资料(萌娘百科 API 等)做检索 → 交叉验证 → 行为蒸馏 → 质量检查,生成可追溯、可更新的写作用角色卡,记录研究日期与资料边界。
- style-corpus-rag(文风库) :把文风示范语料分块 → 多标签标注(写作手法 / 情绪 / 场景)→ 向量化入库,建成可检索的文风知识库(Qdrant),供写作阶段召回示范段落。
这四步都在写作之前,分别回答「写什么题材」「怎么组织故事」「角色是谁」「用什么文风」。三类资产各归其位------三报告用于创意参考、角色卡进设定上下文、文风库进写作检索。
四、五段式流程
#mermaid-svg-3ldNzBiP5J1NcfLN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3ldNzBiP5J1NcfLN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3ldNzBiP5J1NcfLN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3ldNzBiP5J1NcfLN .error-icon{fill:#552222;}#mermaid-svg-3ldNzBiP5J1NcfLN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3ldNzBiP5J1NcfLN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3ldNzBiP5J1NcfLN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3ldNzBiP5J1NcfLN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3ldNzBiP5J1NcfLN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3ldNzBiP5J1NcfLN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3ldNzBiP5J1NcfLN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3ldNzBiP5J1NcfLN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3ldNzBiP5J1NcfLN .marker.cross{stroke:#333333;}#mermaid-svg-3ldNzBiP5J1NcfLN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3ldNzBiP5J1NcfLN p{margin:0;}#mermaid-svg-3ldNzBiP5J1NcfLN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3ldNzBiP5J1NcfLN .cluster-label text{fill:#333;}#mermaid-svg-3ldNzBiP5J1NcfLN .cluster-label span{color:#333;}#mermaid-svg-3ldNzBiP5J1NcfLN .cluster-label span p{background-color:transparent;}#mermaid-svg-3ldNzBiP5J1NcfLN .label text,#mermaid-svg-3ldNzBiP5J1NcfLN span{fill:#333;color:#333;}#mermaid-svg-3ldNzBiP5J1NcfLN .node rect,#mermaid-svg-3ldNzBiP5J1NcfLN .node circle,#mermaid-svg-3ldNzBiP5J1NcfLN .node ellipse,#mermaid-svg-3ldNzBiP5J1NcfLN .node polygon,#mermaid-svg-3ldNzBiP5J1NcfLN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3ldNzBiP5J1NcfLN .rough-node .label text,#mermaid-svg-3ldNzBiP5J1NcfLN .node .label text,#mermaid-svg-3ldNzBiP5J1NcfLN .image-shape .label,#mermaid-svg-3ldNzBiP5J1NcfLN .icon-shape .label{text-anchor:middle;}#mermaid-svg-3ldNzBiP5J1NcfLN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3ldNzBiP5J1NcfLN .rough-node .label,#mermaid-svg-3ldNzBiP5J1NcfLN .node .label,#mermaid-svg-3ldNzBiP5J1NcfLN .image-shape .label,#mermaid-svg-3ldNzBiP5J1NcfLN .icon-shape .label{text-align:center;}#mermaid-svg-3ldNzBiP5J1NcfLN .node.clickable{cursor:pointer;}#mermaid-svg-3ldNzBiP5J1NcfLN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3ldNzBiP5J1NcfLN .arrowheadPath{fill:#333333;}#mermaid-svg-3ldNzBiP5J1NcfLN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3ldNzBiP5J1NcfLN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3ldNzBiP5J1NcfLN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3ldNzBiP5J1NcfLN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3ldNzBiP5J1NcfLN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3ldNzBiP5J1NcfLN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3ldNzBiP5J1NcfLN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3ldNzBiP5J1NcfLN .cluster text{fill:#333;}#mermaid-svg-3ldNzBiP5J1NcfLN .cluster span{color:#333;}#mermaid-svg-3ldNzBiP5J1NcfLN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3ldNzBiP5J1NcfLN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3ldNzBiP5J1NcfLN rect.text{fill:none;stroke-width:0;}#mermaid-svg-3ldNzBiP5J1NcfLN .icon-shape,#mermaid-svg-3ldNzBiP5J1NcfLN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3ldNzBiP5J1NcfLN .icon-shape p,#mermaid-svg-3ldNzBiP5J1NcfLN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3ldNzBiP5J1NcfLN .icon-shape rect,#mermaid-svg-3ldNzBiP5J1NcfLN .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3ldNzBiP5J1NcfLN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3ldNzBiP5J1NcfLN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3ldNzBiP5J1NcfLN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 候选包 + 推荐
选定
确认
打回修订
draft_bundle
final
通过
反馈
① 收集 · 子仓预处理
三报告 / 文风库 / 角色卡
② 设计 · scene-designer(agent)
结构 → 场景分析 → beat
用户选案
② 展开多场景剧本(agent)
用户确认剧本
③ 写作 · scene_pipeline(执行)
解析 → 按 beat 检索 → 文风 → 初稿
④ 精修(agent)
refiner → polisher → wordsmith
用户审阅 / 反馈
⑤ 审校 · scene-reviewer(agent,可选)
菱形为人工决策点,其余为脚本或 agent 节点;agent(设计 / 精修 / 审校)以 subagent 形式经 opencode 编排,写作段为纯脚本执行。
- 收集:四个子仓把网络原文转成写作前的知识资产(三报告 / 文风库 / 角色卡)。
- 设计:scene-designer 产出多场景剧本,经「选案 + 剧本确认」两道人工门。
- 写作 :scene_pipeline 纯执行到初稿;多场景用
--context注入上一场景梗概,保持动作连续。 - 精修:refiner → polisher → wordsmith 依次处理表达逻辑、文风、字词。
- 审校(可选):scene-reviewer 做多维审校并闭环修改。
全程落盘、可追溯:设计文件、初稿、精修输入包、审校包、最终正文都带时间戳,互不覆盖。
五、几个关键设计
- 阶段分离 + 人工双确认门:设计、精修、审校各有各的空间;候选要用户选案、剧本要用户确认,才轮到写作。
- 参考机制迁移卡:借鉴参考作品时,区分「可迁移的因果机制」与「不可搬用的表层」,只借机制与读感。
- 设定与文风解耦:角色卡与官方设定负责「像谁」,风格 RAG 只提供语感,结构由剧本设计、写法由文风规范各自负责。
- 按 beat 分配检索:把检索粒度对齐叙事节拍,避免整段召回导致文风串味。
- 子仓化与可重建:知识库与向量库拆成独立子仓,风格库可由语料全量重建,主仓只留一层薄转发。
六、实战案例
以最近完成的《梦限大MewType》同人单元剧《客座第一键》(藤都子 × 薇欧拉 恋爱喜剧)为例,整条链路的产物是:
- 收集:从短篇知识库取到一份参考作品的三报告,配套角色卡与文风库;
- 设计:产出参考机制迁移卡与三份候选,用户选定「客座第一键」后展开为三场景剧本(每场景 5--6 个行动 beat);
- 写作:逐场景生成初稿,场景之间注入上一场景梗概;
- 精修:三场景分别过表达/逻辑精修、文风精修、炼字;
- 成稿:合成最终正文,附录给出其中一个场景。
这里比较能体现设计意图的一点,是「工作在明、情在暗」:一条不署名的键盘轨,把两个角色不愿直说的话交给工作和道具承载。
七、资源与结语
7.1 资源
- 主仓:
https://gitee.com/zhuowoodbird/novel_rag_mewtype - 子仓(Gitee 同组织):
lofter_fetch、short-story-analysis、style-corpus-rag - 角色卡生成器 csp:
https://github.com/Jacob-Zhuo/Character_Skill_Producer
7.2 结论
整体上,这套系统处理的是长文本的「可控性」:把设计、精修、审校这些主观环节拆成可人工确认的步骤,把执行环节做成确定性流水线。它不保证文本质量,只是让改动的影响范围和结果更可追溯。
就现状而言,它仍有明显不足:多场景衔接、文风稳定性、审校的有效性都还不理想,距离满意还有距离。后续会继续在检索重排、审校自动化和多作品风格库上推进。
7.3 教训
背景:个人项目,从 9 月起断续开发,编码主要由 DeepSeek 协助完成。系统改了两版,初版以gitee分支的形式保存。
现状反思 :系统目前需要大改。例如设计阶段取自 short-story-analysis 的三份报告多为「作品复述」而非「可迁移机制」,抽象规律与具体素材混在一起、又未标适用边界,即便提示词写明「不要照搬」,模型仍容易被牵引------因此要从知识库这一层改起。类似问题还有方案可行性与行文缺少人味。
开发:
- 先 Plan 再 Build,需求问清再动手,减少返工。
- 能用 Agent 驱动就不把流程写死在脚本里,逐步从「脚本自动化」转向「智能调用 + 脚本自动化」。
- 测试时让 Agent 实时汇报进度、统计 token;脚本执行要可中断、可恢复,而非从头重跑。
方法:AI 开发的前提是对目标系统「成竹在胸」,但这依赖很高的判断力,难以一步到位。更现实的做法是先搭出初步的模块化系统,再依据整体输出决定取舍------过早深挖单个模块往往白费,因为模块的上下文与是否该重构,都取决于全系统的测试结果。
整体开发规律呈现一个「总---分---总」、螺旋上升的过程。前期开发完系统,后期以点带面优化。我目前进入以点带面优化的过程了,打算深入研究某个模块,对比效果。
附录:AI小说示例(单场景,质量很低,不建议欣赏)
摘自单元剧《客座第一键》,三场景中的第一场景《懒得写间奏》。
懒得写间奏
凌晨一点十七分。
野乃花把一条键盘轨丢进合轨文件夹,紧跟着补上一行字。句尾缀着笑脸和三个感叹号,不用看署名,隔着屏幕都能闻出那股「拜托了都子酱!!」的热血味。
留言写着:「这轨明天彩排前必须有!匿名投稿箱收的,我原样塞进来了,别问,问就是直觉告诉我能用!」
藤都子盯着屏幕看了两秒。
直觉。
她把手机往旁边一推,转向电脑,摸出耳机戴上。管它什么直觉,先听再说。
点开文件。她原本只打算听两小节,判断能不能用。
第一小节。
握鼠标的手停住了。
是那双手触键的方式。旋律一概让给主唱,自己只在缝里垫一下;副歌前那一拍,换别人早该顶上去,那双手却先退半步。该推的时候不推,该亮的时候让开,像早就习惯站在后面,等别人先走。
耳机里循环着同一个小节。都子把进度条拖回去,再听一遍。搭在空格键上的手指顿了半拍。
这种弹法,谁都能写。
她在心里重复了一遍。舌尖抵着上颚,像在念咒。
然后她把文件拖进废件夹。
删。
下一秒又拖了回来。理由:没时间重写。
再删。这种投稿不能用------手却比脑子先动,把文件拖了回来。
再删。这回的理由是格式好像没问题。拖回的时候,她自己都听得出这借口有多牵强。
再删。间奏太薄,不够用------理由还没想完,指尖已经先动了。
删。拖回。
文件第五次落回轨道的时候,她盯着屏幕,把它改名为「K-07」。一个编号。冷冰冰的,不留任何痕迹。
窗外,救护车的鸣笛掠过,红蓝光扫过天花板一角,又消失。
她抱着丸君,把脸埋进手臂。
丸君的戏偶脸被挤得歪了一点,绒毛蹭着下巴。
「......反正又不是没人能写。」她对着绒毛说,声音闷在胳膊里,「这种轨,谁都能......」
后半句咽了回去。
合轨。
她新建了一个巡演版合轨工程,把K-07拖进去。主唱轨叠上来,吉他跟进来,鼓点卡着拍。她自己的键盘轨早就录好,此刻垫在K-07底下。手指在键盘模拟器上虚敲几个音,没按录制,只是比着轨的位置在心里数拍。
间奏。
到间奏,她的手停了。
两轨叠在一起。键盘垫在底下,主唱在上面走,合起来的声音是对的,是完整的,是彩排能用的。
可她听着听着,眉头皱了起来。
多。
多了一个音。
不是K-07多,是她自己的声部多了。两条轨叠在一块,间奏那段陡然显得挤,像两个人同时开口,谁也听不清谁。都子把音量调大,又调小,再调大。那一段被翻来覆去地听,耳机捂得发烫。
她选中自己的键盘轨,在间奏段按了删除。
波形从中间坍下去一块,黑的,像分镜稿被撕掉一页。间奏只剩K-07孤零零垫在底下;主唱换气的空隙里,它只垫上半口气,像在等一句接不上的话。
她把空出来的这一段单独导出,另存为新文件。
文件名:「空白参考.mp3」。
凌晨一点四十三分。屏幕一角,野乃花那句「必须有」还亮着。都子把新文件放进草稿文件夹,让它和那些「最后没用」「太冒险」「存着吧」的项目躺在一起。
她盯着文件夹看了很久。
然后翻出野乃花转来的那个匿名投稿箱地址。
附件,拖进去。不写称呼,不写正文,只落一个文件名:「空白参考.mp3」。
按下发送。
进度条走完。发送成功。时间戳停在凌晨两点零七分。
连载的原稿还摊在手边,铅笔压着空白格,截稿日用红笔圈了三遍。她把手机按在稿纸角落,屏幕朝下,像盖住什么不该被看见的东西。
丸君歪在桌角,戏偶的眼睛在屏幕光里闪了一下。
「......我只是懒得写间奏。」她对着丸君说,声音很轻,像怕被谁听见。
丸君没有回答。绒毛脸歪着,表情固定在某个似笑非笑的弧度上。
都子盯着那个弧度看了几秒,伸手把丸君的帽子往下拉了拉,盖住半张脸。
「反正又不是没人能写。」
她没把「吧」字说出口。