从零搭建AI智能小说创作助手:设定库、大纲引擎与一致性校验实战

发布时间:2026/10/10 21:53:25
从零搭建AI智能小说创作助手:设定库、大纲引擎与一致性校验实战
简介这是一款基于大语言模型的多功能小说创作助手面向希望高效产出逻辑严谨、设定统一的长篇故事的写作者与AI应用开发者。它通过小说设定工坊完成世界观架构、角色设定与剧情蓝图借助智能章节生成的多阶段流程保障剧情连贯并以状态追踪系统记录角色发展轨迹、管理伏笔配合语义检索引擎维护长程上下文一致性。资源包共163个文件约4.02MB以77个Python后端逻辑、33个tsx与8个ts前端界面、15个json配置为主另含sql建表脚本、md说明文档及模型权重文件构成前后端分离的完整工程。知识库集成支持本地文档参考自动审校机制可检测剧情矛盾与逻辑冲突可视化工作台实现配置、生成、审校一体化操作。已有99人学习适合想研究AI写作工具架构或直接部署使用的读者参考借鉴。1. 从一份 zip 说起AI 智能小说创作助手到底在解决什么你手里可能也躺着类似的东西一个叫「AI 智能小说创作助手」的压缩包解压出来一堆文件README 写得含糊跑起来要么报错要么生成的东西像流水账。问题不在于 AI 能不能写小说而在于大多数人把「让大模型写一段文字」和「搭一个能持续产出可用章节的创作系统」混为一谈了。前者是一次 API 调用后者要处理设定一致性、人物弧光、伏笔回收、章节节奏还要让作者随时能介入修改。这个标题真正指向的是一套把大模型能力约束在长篇叙事结构里的工程方案而不是一个对话框套壳。它适合两类人想自己动手搭写作工具的开发者以及愿意花一个周末把环境跑起来、之后长期用来辅助长篇创作的人。下面我按自己实际搭过一版的路径把选型、代码、参数和翻车点讲清楚。2. 拆解创作助手的四个核心模块与选型逻辑2.1 为什么不能只靠一个提示词硬写长篇单次调用大模型写三千字前一千字人物还叫「林昭」后一千字就变成「林招」这不是模型笨是上下文窗口里没有强制约束。长篇创作的核心矛盾是模型每次生成只看得到有限上下文而故事要求跨章节保持一致。常见做法是把创作拆成四个模块——设定库、大纲引擎、章节生成器、一致性校验器。设定库存人物卡、世界观规则、已发生事件大纲引擎把一句话梗概展开成卷-章-场景三级结构章节生成器按场景逐段写校验器在每章生成后回查设定库发现冲突就标记。这四个模块不一定要四个服务但逻辑上必须分开否则你调参时根本不知道是哪一层出了问题。选型上我一般会这样定设定库用 SQLite 加 JSON 字段够轻单机跑不折腾大纲和章节生成走同一个大模型接口但用不同的系统提示词和温度参数校验器优先用规则匹配人名、地名、时间线规则覆盖不到的再调一次模型做语义比对。为什么不全部交给模型因为规则校验零成本、零延迟、结果确定而模型校验每次都要花钱花时间还可能出现「校验器自己幻觉」的玄学问题。把确定性的活交给代码把模糊判断留给模型这是整个系统稳定的前提。2.2 设定库的表结构设计与初始化代码设定库是整个系统的地基表结构没设计好后面每加一个功能都要改表。我踩过的坑是早期把人物属性全塞进一个 text 字段结果想按「阵营」筛选人物时只能全表扫描加字符串匹配。下面是我现在用的最小可用表结构三张表覆盖人物、事件、世界观规则。-- 人物表核心属性拆列扩展属性放 JSON CREATE TABLE characters ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, -- 人物主名用于一致性校验 aliases TEXT DEFAULT [], -- 别名/称呼JSON 数组 role TEXT, -- 主角/配角/反派 faction TEXT, -- 阵营用于筛选 status TEXT DEFAULT alive, -- alive/dead/missing profile JSON, -- 性格、外貌、背景等扩展字段 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 事件表按章节顺序记录已发生事件供校验器回查 CREATE TABLE events ( id INTEGER PRIMARY KEY AUTOINCREMENT, chapter_no INTEGER NOT NULL, scene_no INTEGER, summary TEXT NOT NULL, -- 事件摘要一句话 involved TEXT DEFAULT [], -- 涉及人物 id 列表JSON 数组 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 世界观规则表硬性设定生成时作为约束注入 CREATE TABLE world_rules ( id INTEGER PRIMARY KEY AUTOINCREMENT, category TEXT, -- 地理/魔法体系/科技水平等 rule_text TEXT NOT NULL, is_hard BOOLEAN DEFAULT 1 -- 硬规则不可违背软规则可酌情 );建表之后要写一个初始化脚本把人物卡和规则灌进去。注意aliases和involved用 JSON 数组存SQLite 从 3.38 起支持 JSON 函数查询时可以用json_each展开。参数上is_hard这个字段很关键硬规则在生成时直接拼进系统提示词软规则只在校验时提示不强制拦截。我一般把「魔法体系上限」「地理距离」设为硬规则把「人物口头禅」设为软规则因为后者偶尔变化反而更自然。2.3 大纲引擎从一句话梗概到三级结构的展开策略大纲引擎的任务是把「一个少年在废土世界寻找失踪的妹妹」这种一句话展开成可执行的章节列表。直接让模型一次生成全部大纲它会偷懒后面章节越写越简略。我的做法是分层展开先让模型生成卷级梗概3 到 5 卷再对每一卷单独调用生成章级梗概每卷 10 到 20 章最后对每章生成场景列表3 到 5 个场景。每次调用的上下文只包含上一层的结果和设定库摘要不塞全文这样既省 token 又避免模型被无关信息干扰。import json from openai import OpenAI client OpenAI(base_urlhttp://localhost:8000/v1, api_keynot-needed) def expand_outline(premise, level, parent_context): 分层展开大纲level 为 volume / chapter / scene prompts { volume: f基于以下梗概生成3到5卷的卷级大纲每卷包含标题和一句话概要\n{premise}, chapter: f基于以下卷级概要生成10到20章的章级大纲每章包含标题和两句话概要\n{parent_context}, scene: f基于以下章级概要生成3到5个场景每个场景包含地点、出场人物、核心冲突\n{parent_context} } resp client.chat.completions.create( modelqwen2.5-14b-instruct, # 本地部署的模型换成你实际用的 messages[ {role: system, content: 你是小说大纲策划输出严格 JSON不要额外解释。}, {role: user, content: prompts[level]} ], temperature0.7, # 大纲需要一定创造性0.7 比 0.2 更合适 max_tokens2000, response_format{type: json_object} ) return json.loads(resp.choices[0].message.content)这段代码的关键参数是temperature和response_format。大纲阶段温度设 0.7让模型有发挥空间章节正文生成时我会降到 0.5 左右减少跑偏。response_format强制 JSON 输出省去解析自然语言的麻烦但要注意不是所有本地模型都支持这个参数跑之前先用一条简单请求测一下不支持就退回提示词里强调「只输出 JSON」。另外max_tokens给 2000 是经验值卷级大纲通常几百 token 就够章级大纲可能接近上限给少了会被截断给多了浪费显存。3. 章节生成器的实现提示词组装与参数调优3.1 系统提示词的分层组装方法章节生成的质量八成取决于提示词怎么组装。我见过有人把设定库全部内容一股脑塞进 system prompt结果模型注意力被稀释写出来的东西反而更差。正确做法是分层第一层是固定角色定义「你是一位擅长废土题材的小说作者」第二层是本章相关的硬规则只取和本章场景有关的 3 到 5 条第三层是出场人物卡只取本章涉及的人物第四层是前情摘要上一章的事件摘要不是全文。这四层加起来控制在 1500 字以内留足空间给模型生成正文。def build_chapter_prompt(chapter_outline, scene_list, characters, prev_summary, hard_rules): 组装章节生成的 system prompt控制总长度 system_parts [ 你是一位小说作者严格按照给定设定和大纲写作不添加未提及的设定。, 【硬性规则】\n \n.join(f- {r} for r in hard_rules[:5]), 【出场人物】\n \n.join( f- {c[name]}{c[role]}{c[profile].get(brief, )} for c in characters ), 【前情摘要】\n (prev_summary or 这是第一章无前情。), ] system_prompt \n\n.join(system_parts) user_prompt ( f请写出第{chapter_outline[no]}章《{chapter_outline[title]}》。\n f本章概要{chapter_outline[summary]}\n f场景列表{json.dumps(scene_list, ensure_asciiFalse)}\n f要求每个场景至少 800 字对话符合人物性格结尾留钩子。 ) return system_prompt, user_prompt逻辑说明hard_rules[:5]做了截断防止规则太多挤占上下文人物卡只取brief字段而不是完整 profile也是为了控制长度。参数上prev_summary我一般用上一章生成后让模型自己总结的 200 字摘要而不是原文这样既保留关键信息又省 token。如果你发现生成内容总是重复前章情节优先检查prev_summary是不是太详细了模型会倾向于「续写」而不是「推进」。3.2 温度、重复惩罚与流式输出的取舍正文生成的参数和大纲阶段完全不同。温度我设 0.5太低会呆板太高会跑偏frequency_penalty设 0.3 到 0.5抑制重复用词但别超过 0.8否则会出现「为了不重复而用生僻词」的怪现象presence_penalty保持 0 或 0.1这个参数对中文小说影响不大。流式输出建议开启一是用户等待体验好二是你可以在流式过程中做实时校验发现人名写错立刻中断重生成省得整章写完才发现问题。def generate_chapter(system_prompt, user_prompt, streamTrue): 生成章节正文支持流式输出和实时校验 stream_resp client.chat.completions.create( modelqwen2.5-14b-instruct, messages[ {role: system, content: system_prompt}, {role: user, content: user_prompt} ], temperature0.5, frequency_penalty0.4, presence_penalty0.1, max_tokens4000, streamstream ) full_text for chunk in stream_resp: delta chunk.choices[0].delta.content or full_text delta # 实时校验发现未登记人名立即标记 if len(full_text) % 200 0: check_unknown_names(full_text) return full_textmax_tokens4000对应大约 2500 到 3000 中文字够一个场景用。如果你的模型上下文窗口小可以把一章拆成多次调用每次生成一个场景然后用事件表串起来。check_unknown_names是我自己写的函数用设定库里的人名列表做匹配发现疑似新人名就记录到日志不中断生成等整章写完再人工确认。这个「先记录后处理」的策略比实时中断更实用因为模型偶尔会写出合理的临时角色名直接中断反而打断思路。4. 一致性校验规则匹配与模型复核怎么配合4.1 三类冲突的检测优先级一致性校验不是把所有东西都丢给模型判断那样又慢又贵。我把冲突分三类按优先级处理第一类是人名、地名、时间线的硬冲突用规则匹配零成本第二类是人物状态冲突比如已死亡角色又出场查事件表和人物表就能发现第三类是语义冲突比如角色性格前后矛盾这类才需要模型复核。实际跑下来前两类能覆盖八成以上的问题模型复核只用在关键章节。def check_consistency(chapter_text, db_conn): 三层校验规则匹配 - 状态查询 - 模型复核 issues [] # 第一层人名匹配 known_names {row[0] for row in db_conn.execute(SELECT name FROM characters)} for alias_row in db_conn.execute(SELECT aliases FROM characters): known_names.update(json.loads(alias_row[0])) # 简单分词后比对实际可用 jieba 等分词库 for token in simple_tokenize(chapter_text): if looks_like_name(token) and token not in known_names: issues.append({type: unknown_name, token: token, level: warn}) # 第二层死亡角色出场检测 dead {row[0] for row in db_conn.execute( SELECT name FROM characters WHERE statusdead)} for name in dead: if name in chapter_text: issues.append({type: dead_character_appear, token: name, level: error}) # 第三层语义冲突交给模型只对 error 级别以上的章节触发 if any(i[level] error for i in issues): issues.extend(model_semantic_check(chapter_text)) return issueslooks_like_name是个启发式函数判断 token 是否符合人名模式两到四个汉字、非常见词这一步会有误报所以标记为 warn 而不是 error。第二层的死亡角色检测是硬性的一旦命中就是 error必须人工处理。第三层只在有 error 时才触发避免每章都调模型增加成本。这套分层策略跑下来单章校验耗时从全模型方案的十几秒降到一两秒。4.2 校验结果怎么反馈给生成环节校验出问题后不能只打印日志就完事要把问题反馈到下一轮生成。我的做法是维护一个「修正队列」error 级别的问题必须解决才能发布章节warn 级别的记录到待办列表作者有空时批量处理。解决方式有两种一是手动改文本二是把问题拼进提示词让模型重写相关段落。后者适合问题集中在一小段的情况整章重写成本太高。def revise_paragraph(original_para, issue, context): 针对单个问题段落做定向重写 prompt ( f以下段落存在设定冲突{issue[type]}涉及「{issue[token]}」。\n f已知设定{context}\n f原文{original_para}\n f请重写这一段消除冲突保持文风和情节连贯。只输出重写后的段落。 ) resp client.chat.completions.create( modelqwen2.5-14b-instruct, messages[{role: user, content: prompt}], temperature0.3, # 修正阶段要保守低温度减少二次跑偏 max_tokens800 ) return resp.choices[0].message.contenttemperature0.3是修正场景的保守值目的是「改对」而不是「改好」。context参数传入相关设定比如涉及人物时传该人物卡涉及地点时传地理规则。定向重写比重写整章快得多但要注意如果问题跨越多个段落定向重写可能造成前后文风不一致这时候还是老实重写整章。我一般把连续三个段落以内的问题用定向重写超过就整章重来。5. 避坑与排查搭这套系统时最容易翻车的五件事5.1 现象生成的人名前后不一致同一角色出现三种写法原因模型在长上下文里对专有名词的注意力衰减尤其是名字相近时。解决在系统提示词里用「人物名必须严格使用以下写法」的强约束句式同时在生成后用规则匹配做一次全文替换把别名统一成主名。别指望模型自己记住要靠代码兜底。5.2 现象章节越写越短后面几章像在赶进度原因大纲引擎一次生成太多章节模型对后面的章节缺乏细节规划。解决把大纲生成拆成多轮每轮只生成 5 到 8 章生成下一轮时把上一轮的实际生成结果作为上下文传进去让模型基于已写内容继续规划。这样虽然多几次调用但章节质量稳定得多。5.3 现象校验器频繁误报把正常词汇当人名原因looks_like_name的启发式规则太宽中文里很多两字词都符合人名模式。解决维护一个停用词表把「但是」「于是」「忽然」这类高频非人名词排除同时把误报的词加入白名单下次不再报警。这个白名单会随着使用越来越准是典型的「越用越好用」的组件。5.4 现象本地模型生成速度慢一章要等好几分钟原因模型太大或量化精度太高显存不够导致频繁换页。解决优先用量化版本如 4bit 量化14B 模型在 16G 显存上跑 4bit 量化通常能到每秒 20 到 30 token如果还是慢把章节拆成场景级生成每次生成 800 字左右用户等待感知会好很多。别硬上 70B 模型写作任务 14B 到 32B 足够。5.5 现象生成内容涉及敏感或不当描写原因模型对提示词里的情节要求理解过宽。解决在系统提示词里加明确的边界约束同时在生成后做关键词过滤。过滤规则要可配置不同题材的边界不同硬编码一套规则迟早不够用。这一条没有一劳永逸的方案只能持续维护规则库。6. 让助手越用越顺手的两个进阶技巧第一个技巧是「风格锚定」。如果你希望整本书保持统一的叙事风格可以在设定库里存三到五段「风格样本」每次生成时把样本作为 few-shot 示例放进提示词。样本不用长每段 200 字左右选最能代表你想要的节奏和语感的段落。我实测下来加了三段风格样本后模型输出的句式重复率明显下降章节之间的文风也更统一。代价是每次调用多几百 token但换来的是少改稿值。第二个技巧是「版本快照」。每次生成章节后把设定库、大纲、正文一起打一个快照存下来用章节号加时间戳命名。这样当你改到第十版发现还不如第三版时能直接回滚。我吃过这个亏有一次调参数后连续生成了五章越看越不对想回到之前的版本结果设定库已经被覆盖了只能从头再来。现在我的习惯是任何参数调整前先打快照调整后生成的前三章如果不如预期立刻回滚不纠结。# 快照脚本示例打包设定库和当前大纲 SNAPSHOT_DIR./snapshots/$(date %Y%m%d_%H%M%S) mkdir -p $SNAPSHOT_DIR cp novel.db $SNAPSHOT_DIR/ cp outline.json $SNAPSHOT_DIR/ cp -r chapters/ $SNAPSHOT_DIR/ echo 快照已保存到 $SNAPSHOT_DIR这个脚本简单到没什么技术含量但它是整套系统里我最离不开的东西。参数调优是玄学快照是后悔药。希望帮到你。本文还有配套的精品资源点击获取