简介:AI小说创作助手是一套面向小说作者与写作爱好者的智能创作生产力工具,基于人工智能与提示词技术,帮助解决灵感枯竭、框架搭建困难、文本润色耗时等痛点。资源包共52个文件,约3.48MB,以Python脚本与JavaScript模块为主,前者对应DeepSeek、豆包、通义千问、Claude、ChatGPT、Ollama、Gemini、文心一言等多模型接入实现,后者承载提示词编辑、拆书、思维导图、主题切换等前端交互逻辑,另含PNG界面截图、HTML页面、Markdown教程与说明文档,便于理解整体架构与运行方式。目前已有52人学习下载。通过该资源,读者可获得智能拆书、书名与简介生成、正文润色、错别字与语法修正等完整功能实现,并参考提示词优化与小说编辑器教程,快速搭建属于自己的AI写作工作流,适合希望提升创作效率、研究提示词工程与多模型集成的初中级开发者与写作者。
1. 从一份 Shi.zip 说起:AI 小说创作助手到底在解决什么
很多人第一次听到「AI 小说创作助手」,脑子里浮现的是「输入一句话,吐出一本百万字长篇」的玄学画面。真上手做过一段时间就会发现,纯靠一个大模型裸写,三章之后人物名字就开始漂移,第五章世界观自相矛盾,第十章连主角是男是女都记不清。真正卡住创作者的从来不是「写不出字」,而是「记不住设定、接不上伏笔、改不动稿子」。我拿到一份名为 Shi.zip 的工程包时,第一反应不是去看它界面多花哨,而是翻它的提示词管理模块和拆书逻辑——这两块才是这类工具能不能长期用的分水岭。
这篇笔记面向三类人:想自己搭一套 AI 小说工作流的独立作者、需要批量产出网文初稿的工作室、以及想把提示词工程落到具体产品里的开发者。核心讲清楚四件事:智能拆书怎么把一本参考书变成可复用的结构模板、书名与简介怎么用约束生成而不是抽卡、正文润色怎么在不改人设的前提下动刀、以及提示词管理这套东西为什么决定了你三个月后还能不能维护这个项目。Shi.zip 在这里更像一个具体的落地样本,我会顺着它的功能脉络,把每一步的参数、命令和翻车点讲透,你照着改就能跑自己的版本。
2. 智能拆书:把一本参考小说拆成可复用的结构模板
2.1 拆书到底拆的是什么,为什么不能直接喂全文
新手最容易犯的错,是把一本 80 万字的小说整本塞进上下文,然后让模型「学习它的风格」。先不说 token 成本和窗口限制,单说效果就很不稳定:模型会记住大量无关的对话细节,却抓不住真正决定节奏的东西——章节钩子、冲突升级曲线、伏笔回收点。智能拆书的本质是结构化抽取,把一本参考书压缩成几个可枚举的维度:人物关系网、章节功能标签、爽点/转折分布、叙事视角切换规律。
我一般会把拆书拆成三层。第一层是章节级摘要,每章压到 150 字以内,只保留「谁做了什么、结果如何、留下什么悬念」。第二层是结构标签,给每章打上功能标签,比如「铺垫」「冲突爆发」「信息揭露」「情绪释放」。第三层是可复用模板,把标签序列抽象成节奏曲线,比如「三章铺垫 + 一章爆发」这种模式。这三层做完,你得到的不是一本参考书,而是一套能套到新故事上的骨架。
提示:拆书阶段不要追求摘要「文采好」,要追求「信息密度高且可机读」。摘要里出现形容词堆砌,后面做结构匹配时全是噪声。
2.2 用 Python 跑通章节摘要与结构标签的最小流程
下面这段代码是拆书流程的核心骨架,输入是分好章的文本列表,输出是带标签的结构化 JSON。我把它写成可替换模型接口的形式,你换成自己的 API 或本地模型都能跑。
import json import re from typing import List, Dict # 假设你已有一个调用大模型的函数,返回纯文本 from llm_client import call_llm CHAPTER_SUMMARY_PROMPT = """你是一名小说结构分析师。请阅读下面这一章的内容,输出严格的 JSON,不要任何解释文字。 字段要求: - summary: 150字以内的情节摘要,只写事实,不写评价 - characters: 本章出场的主要人物名列表 - function_tag: 从["铺垫","冲突爆发","信息揭露","情绪释放","过渡"]中选一个最贴切的 - hook: 本章结尾留下的悬念,若无则填"无" 章节内容: {chapter_text} """ def summarize_chapter(chapter_text: str) -> Dict: prompt = CHAPTER_SUMMARY_PROMPT.format(chapter_text=chapter_text[:6000]) raw = call_llm(prompt, temperature=0.2) # 模型偶尔会包 ```json ```,这里做一次清洗 raw = re.sub(r"^```json|```$", "", raw.strip(), flags=re.MULTILINE) try: return json.loads(raw) except json.JSONDecodeError: # 解析失败时返回兜底结构,避免整个流程中断 return {"summary": "", "characters": [], "function_tag": "过渡", "hook": "无"} def split_book(chapters: List[str]) -> List[Dict]: result = [] for idx, ch in enumerate(chapters): info = summarize_chapter(ch) info["chapter_index"] = idx + 1 result.append(info) return result if __name__ == "__main__": with open("reference_novel_chapters.json", "r", encoding="utf-8") as f: chapters = json.load(f) structured = split_book(chapters) with open("book_structure.json", "w", encoding="utf-8") as f: json.dump(structured, f, ensure_ascii=False, indent=2)逻辑说明:summarize_chapter里把章节文本截断到 6000 字符,是因为大多数模型在长上下文里对中段信息的注意力会衰减,宁可分段多次调用也不要一次塞满。temperature=0.2是为了让结构标签稳定,拆书阶段不需要创造力。解析失败时返回兜底结构而不是抛异常,是因为一本 300 章的书里总有几章格式跑偏,不能因为一章失败就重跑全部。
参数说明:function_tag的枚举值你可以按自己的题材扩展,比如玄幻加「打脸」「夺宝」,言情加「误会」「和解」。枚举越贴合你的品类,后面做节奏匹配时越准。hook字段是后面生成新书章节钩子的直接素材来源,别省。
2.3 从结构标签到节奏曲线:怎么判断一本参考书值不值得学
拆完一本书,你会得到一张标签序列表。这时候别急着用,先做一次节奏密度统计:统计每 10 章里「冲突爆发」和「情绪释放」的出现次数。如果一本 200 章的书里这两类标签加起来不到 15 次,说明它节奏偏慢,适合学文笔和人物塑造,不适合学爽点节奏。反过来,如果每 10 章有 4 次以上爆发,那它的结构可以直接拿来当模板。
我一般会把这个统计做成一个简单的脚本,输出一张对比表,方便同时拆好几本书做横向比较。表格字段包括:总章数、爆发密度、平均铺垫长度、钩子覆盖率。钩子覆盖率指的是结尾有明确悬念的章节占比,这个指标低于 60% 的书,学它的结构容易让读者中途弃书。
注意:拆书结果要落盘成 JSON 而不是留在内存里。你后面做书名生成、正文润色时都要反复查这份结构,落盘一次省无数次重复调用。
3. 书名与简介生成:用约束把抽卡变成可控产出
3.1 为什么直接让模型「起个好书名」十次有九次不能用
裸提示词生成书名的典型失败模式有三种:一是同质化,十次里八次带「重生」「逆袭」「系统」;二是与正文脱节,书名承诺的爽点和实际内容对不上;三是长度失控,网文平台的书名一般有字数区间,模型经常给你整出一句诗。根因在于模型在做无约束采样,它只能往训练数据里最高频的模式靠。
解决办法是把生成拆成「候选池 + 打分 + 筛选」三步。候选池阶段放宽约束,让模型多出一些方向;打分阶段用明确的规则给每个候选打标签,比如「是否含核心冲突词」「字数是否在 6 到 12 之间」「是否与主角设定相关」;筛选阶段按分数排序取前几个。这样你得到的不是「模型觉得好」的书名,而是「符合你设定规则」的书名。
3.2 书名生成的三段式提示词与打分脚本
import re from typing import List, Dict TITLE_CANDIDATE_PROMPT = """你是一名网文编辑。根据下面的故事设定,生成 20 个候选书名。 要求: 1. 每个书名 6 到 12 个汉字 2. 至少 5 个包含核心冲突词(如"复仇""归来""封神") 3. 至少 5 个走悬念路线,不直接点破结局 4. 只输出书名,每行一个,不要编号 故事设定: {setting} """ def generate_candidates(setting: str) -> List[str]: raw = call_llm(TITLE_CANDIDATE_PROMPT.format(setting=setting), temperature=0.9) titles = [t.strip() for t in raw.split("\n") if t.strip()] # 过滤掉明显超长或带标点的 titles = [t for t in titles if 4 <= len(t) <= 14 and not re.search(r"[,。!?]", t)] return titles def score_title(title: str, core_words: List[str], protagonist: str) -> Dict: score = 0 reasons = [] if 6 <= len(title) <= 12: score += 2 reasons.append("长度合规") if any(w in title for w in core_words): score += 3 reasons.append("含核心冲突词") if protagonist and protagonist in title: score += 1 reasons.append("含主角名") if re.search(r"[??]", title): score += 1 reasons.append("悬念句式") return {"title": title, "score": score, "reasons": reasons} def rank_titles(titles: List[str], core_words: List[str], protagonist: str) -> List[Dict]: scored = [score_title(t, core_words, protagonist) for t in titles] return sorted(scored, key=lambda x: x["score"], reverse=True)逻辑说明:候选生成用temperature=0.9是为了拉开多样性,打分阶段完全不用模型,纯规则,这样结果可复现、可解释。core_words是你从拆书结构里提取的高频冲突词,比如拆了十本复仇流小说,「复仇」「归来」「清算」就会自然浮现。protagonist传主角名,是为了让书名和正文强绑定,避免出现「书名主角叫阿尘,正文主角叫林渊」这种低级错误。
参数说明:长度区间 6 到 12 是主流网文平台的常见区间,你做短篇或出版向可以改成 4 到 8。score的权重可以调,如果你特别看重悬念感,把悬念句式的加分从 1 提到 3。
3.3 简介生成:把「三段式」写进提示词模板
简介比书名更需要结构。我一般用「钩子 + 冲突 + 悬念」三段式:第一句抛出一个反常事实,第二句写主角面临的核心矛盾,第三句留一个不点破的悬念。提示词里把这三段写成明确的槽位,模型就不容易写成流水账。
BLURB_PROMPT = """根据以下设定写一段小说简介,严格按三段结构,总字数 120 到 180 字。 第一段(钩子):用一个反常事实或强烈画面开场,不超过 40 字。 第二段(冲突):写主角的核心困境和对手,不超过 80 字。 第三段(悬念):留一个不点破结局的问题,不超过 40 字。 不要出现"本书讲述了"这类套话。 设定: {setting} """ def generate_blurb(setting: str) -> str: return call_llm(BLURB_PROMPT.format(setting=setting), temperature=0.7).strip()逻辑说明:把字数上限写进每一段的约束里,比只写总字数有效得多,因为模型对分段约束的遵守度明显高于总量约束。temperature=0.7是简介生成的经验值,太低会写得干巴,太高会跑偏设定。
提示:简介生成后一定要人工过一遍,重点检查第二段的「对手」是否和正文设定一致。模型很容易在这里自己发明一个反派,后面正文对不上。
4. 正文润色:不改人设的前提下动刀
4.1 润色不是重写,先分清哪三类问题该动
很多人把润色理解成「让模型把这段重写一遍」,结果改完人物说话方式全变了,前一章的冷面杀手这一章开始讲冷笑话。正文润色要先把问题分类:语言层(重复用词、句式单调、标点混乱)、逻辑层(前后矛盾、时间线错乱、道具消失)、节奏层(该快的地方拖沓、该慢的地方一笔带过)。语言层可以放心交给模型,逻辑层必须带着上下文一起改,节奏层最好人工判断后给模型明确指令。
我的做法是分两轮:第一轮只做语言层润色,提示词里明确「不得改变人物性格、不得增删情节」;第二轮做逻辑层检查,把前后三章一起喂进去,让模型只输出「矛盾点列表」而不是直接改。节奏层不交给模型,自己看拆书得到的节奏曲线,手动调整章节顺序或增删段落。
4.2 语言层润色的提示词与批量处理脚本
POLISH_PROMPT = """你是一名文字编辑。请润色下面这段小说正文,遵守以下硬性规则: 1. 不得改变任何人物名、地名、专有名词 2. 不得增删任何情节动作 3. 不得改变人物的说话语气和用词习惯 4. 只允许:替换重复用词、调整句式长短、修正标点 5. 输出润色后的正文,不要任何说明 原文: {text} """ def polish_text(text: str, chunk_size: int = 800) -> str: # 按段落切块,避免一次处理太长导致规则遗忘 paragraphs = text.split("\n") chunks, current = [], "" for p in paragraphs: if len(current) + len(p) > chunk_size: chunks.append(current) current = p else: current += "\n" + p if current: chunks.append(current) polished = [call_llm(POLISH_PROMPT.format(text=c), temperature=0.3) for c in chunks] return "\n".join(polished)逻辑说明:chunk_size=800是经验值,超过这个长度模型对「不得改变人物语气」这条规则的遵守度会下降。按段落切而不是按字符硬切,是为了避免把一句话劈成两半。temperature=0.3保证润色稳定,润色不需要创造力。
参数说明:如果你的文本里有大量对话,可以把chunk_size降到 500,因为对话对语气一致性更敏感。规则里的第 3 条是血泪经验,不加这条,模型会把所有角色的说话方式统一成它自己的风格。
4.3 逻辑层检查:让模型只报告不修改
逻辑层最忌讳让模型直接改,因为它改的时候会顺手编新设定。正确做法是让它输出一份「矛盾清单」,你人工确认后再决定怎么改。
CONSISTENCY_PROMPT = """阅读下面连续三章的内容,找出其中的逻辑矛盾。 只输出 JSON 列表,每项包含: - location: 矛盾出现的章节和大致位置 - issue: 矛盾描述 - severity: "高"或"低" 不要提出修改建议,只报告问题。 内容: {text} """ def check_consistency(three_chapters: str) -> list: raw = call_llm(CONSISTENCY_PROMPT.format(text=three_chapters[:12000]), temperature=0.1) raw = re.sub(r"^```json|```$", "", raw.strip(), flags=re.MULTILINE) try: return json.loads(raw) except json.JSONDecodeError: return []逻辑说明:temperature=0.1让模型尽量保守,只报告它高度确信的矛盾。severity字段帮你排优先级,先处理「高」的。明确写「不要提出修改建议」,是因为一旦让它建议,它就会开始编新设定来「圆」矛盾,反而引入更多问题。
注意:三章一起喂是下限,涉及长线伏笔的检查要拉到十章以上。但上下文越长,模型漏报越多,所以长线检查建议按「卷」为单位,一卷查一次。
5. 提示词管理:决定这个项目三个月后还能不能维护
5.1 提示词散落在代码里,是这类工具最大的技术债
我见过太多 AI 写作工具,第一版能跑,第二个月想调一下书名生成的风格,结果发现提示词硬编码在五个不同的文件里,改了一处忘了另一处,生成结果开始精神分裂。提示词管理的核心不是「存起来」,而是版本化 + 可回滚 + 可对比。每一条提示词都应该有唯一 ID、版本号、适用场景标签,以及一份历史记录。
Shi.zip 这类工程包里,提示词管理模块往往是决定它能不能长期用的关键。我自己的做法是把提示词抽成独立的 YAML 或 JSON 文件,代码里只引用 ID,不写死文本。这样调提示词不用改代码,改完直接热加载。
5.2 用 YAML 管理提示词并做版本对比
# prompts/title_generation.yaml - id: title_candidate_v3 scene: title_generation version: 3 updated_at: "2025-01-15" temperature: 0.9 template: | 你是一名网文编辑。根据下面的故事设定,生成 20 个候选书名。 要求: 1. 每个书名 6 到 12 个汉字 ... changelog: "v3 增加了悬念路线的最低数量要求,v2 版本悬念书名太少"import yaml class PromptManager: def __init__(self, path: str): with open(path, "r", encoding="utf-8") as f: self.prompts = {p["id"]: p for p in yaml.safe_load(f)} def get(self, prompt_id: str) -> dict: if prompt_id not in self.prompts: raise KeyError(f"提示词 {prompt_id} 不存在,检查 ID 是否拼错") return self.prompts[prompt_id] def render(self, prompt_id: str, **kwargs) -> str: p = self.get(prompt_id) return p["template"].format(**kwargs) manager = PromptManager("prompts/title_generation.yaml") text = manager.render("title_candidate_v3", setting="...")逻辑说明:changelog字段是后悔药,每次改提示词都写清楚改了什么、为什么改。三个月后你回头看,能立刻知道哪次改动导致了效果波动。render方法把模板和参数分离,调用方不需要知道提示词长什么样。
参数说明:temperature存在提示词配置里而不是代码里,是因为同一个场景的不同版本可能需要不同温度,放在一起管理更直观。scene字段用于按场景批量加载,比如启动时只加载title_generation相关的提示词。
5.3 提示词 A/B 对比:怎么知道新版本真的更好
改完提示词,怎么判断是变好了还是变差了?靠感觉不靠谱。我一般会准备一组固定的测试输入,比如 10 个不同的故事设定,然后用新旧两版提示词各跑一遍,人工或规则打分对比。这个流程不需要多复杂,一个脚本加一张表就够。
| 对比项 | 旧版本 v2 | 新版本 v3 |
|---|---|---|
| 候选书名数量 | 18 | 20 |
| 长度合规率 | 72% | 95% |
| 含核心冲突词比例 | 40% | 55% |
| 人工可用率 | 3/10 | 6/10 |
这张表跑一次大概十几分钟,但能帮你避免「改了半天其实变差了」这种翻车。人工可用率是最重要的指标,规则打分再高,人看不上就是白搭。
提示:测试输入要固定下来存成文件,不要每次临时想。固定输入才能做纵向对比,临时输入只能看单次效果。
6. 避坑与排查:拆书、生成、润色里最容易翻车的五件事
现象一:拆书摘要越写越长,最后每章摘要 500 字,结构标签全是「过渡」。原因:提示词里只写了「150 字以内」,但模型对上限的遵守度在长文本上会衰减,加上章节本身信息多,它就倾向于多写。解决:把摘要拆成「一句话核心 + 三个关键动作」两个字段,强制结构化,比单纯限字数有效。另外把temperature降到 0.1,减少发挥。
现象二:书名生成每次跑出来都差不多,换十个设定还是那几个词。原因:temperature设得不够高,或者候选数量太少,模型在低多样性采样下反复命中高频模式。解决:把temperature提到 0.9 以上,候选数量从 10 提到 20 到 30,并且在提示词里明确要求「至少 N 个走不同路线」。如果还不行,就在提示词里给几个反向示例,告诉它「不要出现以下风格」。
现象三:润色后人物说话方式全变了,冷面角色开始讲段子。原因:提示词里没有明确禁止改变语气,模型默认把「润色」理解成「优化表达」,而它认为的优化就是更流畅、更口语。解决:在提示词里加硬性规则「不得改变人物说话语气和用词习惯」,并且把chunk_size降到 500 以下。如果某个角色语气特别重要,单独把它的对话摘出来,用专门的提示词处理。
现象四:逻辑检查报告了一堆「矛盾」,人工一看全是误报。原因:temperature太高,或者喂进去的上下文不完整,模型在信息缺失时倾向于脑补矛盾。解决:temperature降到 0.1,上下文至少给连续三章,并且在提示词里写「只报告你高度确信的矛盾,不确定的不要报」。误报率还是高的话,加一个「置信度」字段,只处理高置信度的。
现象五:提示词改了之后效果变差,但不知道是哪次改动导致的。原因:没有版本记录,改一次覆盖一次,出问题无法回滚。解决:每次改提示词都新建版本号而不是覆盖,changelog写清楚改动内容。跑一次固定测试集做对比,确认新版本确实更好再切换默认版本。这个习惯看起来麻烦,但能省下大量「改坏了找不回来」的时间。
7. 把拆书结构反哺回生成:一个让续写不跑偏的技巧
前面拆书得到的book_structure.json,大多数人只用它来分析参考书,用完就扔。其实这份结构可以直接反哺到你自己的续写流程里,做法是:在生成新章节之前,先查一下当前章节在节奏曲线上的位置,然后把「本章应该承担的功能标签」作为约束写进提示词。这样模型不会在铺垫章里突然爆发,也不会在爆发章里写一堆日常。
具体操作是维护一个「当前进度指针」,记录你写到第几章、下一个功能标签是什么。每次生成前,从结构模板里取出接下来三章的标签序列,作为上下文传给模型。
def build_chapter_prompt(chapter_index: int, structure: list, setting: str) -> str: # 取接下来三章的标签作为节奏约束 upcoming = structure[chapter_index: chapter_index + 3] tags = [s["function_tag"] for s in upcoming] hooks = [s["hook"] for s in upcoming if s["hook"] != "无"] prompt = f"""根据以下设定写第 {chapter_index + 1} 章正文。 本章的功能定位是:{tags[0] if tags else '过渡'} 接下来两章的功能依次是:{tags[1:]} 本章结尾需要留下一个悬念,参考方向:{hooks[0] if hooks else '自行设计'} 设定: {setting} 要求: 1. 本章不得提前释放后续章节的功能(比如铺垫章不要写爆发) 2. 结尾悬念要与下一章的功能衔接 3. 字数 2000 到 3000 字 """ return prompt逻辑说明:upcoming取三章而不是一章,是为了让模型有前瞻性,知道当前章节要为后面做什么铺垫。tags[0]是硬约束,tags[1:]是软约束,提示词里明确区分。hooks从拆书结果里取,相当于借用了参考书的钩子设计思路,但具体内容还是你自己的故事。
参数说明:chapter_index从 0 开始,和结构 JSON 里的chapter_index对齐时注意减一。字数区间按你的平台要求调,短篇平台可以改成 1500 到 2000。如果发现模型还是提前爆发,就在提示词里把「不得提前释放」这条加粗重复一遍,模型对重复出现的约束遵守度更高。
这个技巧我用了大半年,最大的感受是:AI 写作的瓶颈从来不在模型能力,而在你有没有把「结构」这件事从模型脑子里拿出来,变成你自己可控的数据。拆书拆出来的结构、提示词管理管起来的版本、润色时守住的人设边界,这三样东西加起来,才让一个 AI 小说创作助手从「能写」变成「能一直写下去」。希望帮到你。
本文还有配套的精品资源,点击获取