ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用LLM构建小说创作工作流:从设定到一致性自检

用LLM构建小说创作工作流:从设定到一致性自检 在很长一段时间里我写小说面临的最大问题不是“没灵感”而是“灵感停留在脑子里落不到稿子上”。大纲写了两章就开始崩人物性格写着写着就飘了场景之间的过渡生硬得像是幻灯片。接触大语言模型之后我开始把它当成创作流程中的搭档而不是资料查询工具整套写作进度反而被盘活了。这篇文章不是讨论“AI 能不能写出伟大文学”而是分享一套可复用的 LLM 辅助小说创作流程从环境准备、接口封装、人物设定表、大纲生成、章节扩写到一致性自检和常见坑点排查。无论你是写网文、短篇还是做互动叙事下面这套思路都能直接迁移使用。1. 背景与核心概念LLM 辅助创作到底在解决什么问题1.1 “LLMs Set My Fiction Free” 该怎么理解这个标题可以拆成两层意思。第一层是字面的大语言模型把小说创作者从繁琐的“码字劳动”中解放出来。以前写一个章节可能要用两小时从头到尾打字现在借助 LLM 辅助先把章节的冲突、场景、视角、节奏想清楚再交给模型生成初稿最后做删改润色效率确实提升很多。第二层是更关键的LLM 降低了长篇幅创作的“维持成本”。很多作者写着写着断更不是因为编不出剧情而是因为忘记了自己写过的设定。人物关系、伏笔位置、时间线、道具细节都在脑子里乱成一团。LLM 擅长处理和检索大量文本只要我们把设定文档和章节摘要喂给它它就能在生成新章节时保留前面的信息。从这个角度看LLM 解放的不是“手”而是“脑”它让创作者不用再背着几十万字的记忆负担可以专心做判断和创意决策。1.2 LLM 能承担小说创作中的哪些环节根据我自己的实践下面这几个环节是 LLM 参与度最高、收益最明显的世界观设定整理。把零散的想法写成结构化设定比如地理、势力、能力体系、经济系统。剧情大纲生成。输入故事概念输出三幕结构或章节级大纲。章节初稿扩写。根据大纲摘要和人物设定生成一版可修改的正文初稿。对话润色与文风统一。保持人物说话习惯一致减少“作者腔”。一致性自检。模型自己读一遍新章节对照设定表找出矛盾点。灵感发散与卡文处理。给模型几个关键词让它提供剧情转折方向用于打破思路僵局。这里要注意LLM 的能力边界也很明显。它没有“长期记忆”如果一次对话里塞不下整本小说的内容它就会遗忘前文。它也没有稳定的审美判断经常会生成“看似通顺但缺乏张力”的文本。它更不理解版权边界如果我们不主动约束它可能生成与已有作品高度相似的表达。所以正确的使用方式是把 LLM 当成“协作伙伴”而不是“自动写稿机”。1.3 创作者的角色转换从打字者变成编辑引入 LLM 之后我的创作角色从一个“纯打字者”变成了“编辑加导演”。我需要做的是决定故事方向和冲突类型。审核模型输出是否符合人物逻辑。删掉模型生成的套话和冗余描写。把模型生成的多条候选路线合并成一条主线。这种转换刚开始会有点不适应尤其会担心“AI 写出来的文字还是我的作品吗”。我的经验是最终成稿里重要的剧情决策、人物弧光、情感爆发点都来自人工编辑LLM 提供的是更快的“文本生成能力”和“信息组织能力”。只要人工审校介入充分成品就能体现作者自己的创作意志。2. 环境准备与模型选择2.1 模型选择API 还是本地部署做 LLM 辅助创作第一步是选择模型调用方式。目前市面上能提供大语言模型推理能力的渠道主要分为两类。云端 API调用方把文本请求发给服务端服务端返回生成结果。优点是使用简单、不需要本地 GPU 资源适合快速开发和原型验证。本地部署把开源模型下载到自己的机器上通过推理框架加载运行。优点是数据隐私好、没有按 token 计费的压力适合长篇幅创作和敏感题材但需要较好的显卡、显存和内存环境。对大多数小说写作者来说我建议先尝试云端 API确认工作流能跑通再根据成本、隐私和效果决定是否迁移到本地部署。实际操作中无论选择哪家平台只要模型服务兼容OpenAI Chat Completions这种 JSON 接口格式就可以复用同一套代码逻辑差别只在base_url、api_key和model参数。2.2 开发环境准备本文的示例代码使用 Python 编写因为 Python 在处理 JSON 配置、文本文件、API 请求方面都很方便。你需要准备以下环境Python 3.9 或更高版本。requests库用于发起 HTTP 请求。一个可用的 LLM API 密钥以及对应的接口地址和模型名称。本地目录里准备一个config.json存放 API 配置避免把密钥硬编码在代码中。安装依赖只需要一条命令pip install requests不建议为了这个项目一开始就安装大型深度学习依赖等需要本地部署时再引入相应推理框架即可。2.3 配置文件示例新建config.json内容结构如下{ api_key: your-api-key, base_url: https://your-endpoint.example.com, model: your-model-name, temperature: 0.7, max_tokens: 3000, timeout: 120 }这里需要注意base_url、model都必须以你实际使用的服务为准不同平台的字段要求会有差异。代码中我会统一从该配置读取参数这样后续切换模型或平台时只需要改配置文件不需要改代码。3. 核心原理LLM 为什么能辅助长篇创作3.1 从“文字接龙”到“创作外脑”大语言模型的基本原理可以简化理解为根据上文内容预测下一个最可能出现的 token。这种能力经过大规模训练后展现出惊人的文本连贯性和知识覆盖度。但在创作领域它本质上依然是对海量文本中的写作模式进行“重组”和“续写”而不是从真实体验出发进行原创。理解这一点很重要。当我们让 LLM 写“雨夜里的追车戏”时它给出的场景可能基于无数影视和小说里的经典桥段。它能给我们提供很好的“初稿素材”但如果我们不做修改和组合故事很容易显得套路化。所以我会把模型生成的内容称为“素材草稿”而不是“成品稿件”。3.2 上下文窗口与记忆管理每个 LLM 都有上下文长度限制也就是一次请求里能容纳的 token 总数。超过限制后要么接口报错要么模型会忽略最前面的内容。对小说创作来说这是最需要工程设计的问题。比如一本 30 万字的小说不可能一次全放进上下文。即便能放进模型也很难在生成当前章节时精准引用 20 章之前埋下的伏笔。我的方案是做“分层记忆”第一层全局设定文档包括世界观、人物设定、主线大纲通常控制在 3000 到 5000 字符以内。第二层近期摘要把最近几章的剧情浓缩成一段话每次生成新章节时带在请求里。第三层当前章节正文重点保证这一章内部的叙事连贯。每次调用模型时我们不需要让它记住所有内容只要把“本次生成所需要的上下文”准备充分即可。这种思路在工程上叫检索增强式生成在小说创作中就是给模型提供一份“现场编剧手册”。3.3 Prompt 稳定性为什么模型生成结果忽好忽坏小说创作类的 Prompt 与问答类 Prompt 不一样。问答只需要给出明确指令创作为了保持风格一致需要更稳定的系统提示词。我们可以把系统提示词当作一部作品的“作者须知”里面写清楚文风、视角、人称、节奏偏好、禁止事项等。如果发现同一个 Prompt 生成的结果时好时坏通常原因有三个模型对风格描述理解不稳定需要更具体的正反示例。上下文里出现了无关信息干扰了模型对主线的关注。温度参数过高导致随机性过大。写大纲时建议temperature调低到 0.4 到 0.6扩写正文可以调到 0.7 到 0.9。在实际项目中我倾向于为不同任务准备不同的 Prompt 模板并且把模板保存为单独文件这样既方便调试也方便复用。4. 完整实战搭建一个小说创作辅助工作流下面我们用 Python 实现一个可运行的小说创作辅助工具功能包括加载配置、调用模型接口、维护人物设定表、生成大纲、扩写章节、执行一致性自检。代码文件较多建议按项目结构存放。4.1 项目结构设计fiction-assistant/ ├── config.json ├── main.py ├── requirements.txt ├── data/ │ ├── characters.json │ └── outline.json └── output/ ├── chapter_001.md └── chapter_002.mdconfig.jsonAPI 配置。main.py主程序包含接口封装与创作助手类。data/characters.json人物设定表。data/outline.json生成后的大纲文件。output/章节输出目录。4.2 编写模型调用封装层在main.py中先实现一个通用的模型调用类。为了减少对特定 SDK 的依赖直接使用requests请求兼容接口。import json import requests class ChatClient: 兼容 OpenAI Chat Completions 接口的通用请求封装 def __init__(self, api_key, base_url, model, timeout120): self.api_key api_key self.base_url base_url.rstrip(/) self.model model self.timeout timeout def complete(self, messages, temperature0.7, max_tokens2000): url self.base_url /chat/completions headers { Authorization: fBearer {self.api_key}, Content-Type: application/json, } payload { model: self.model, messages: messages, temperature: temperature, max_tokens: max_tokens, } resp requests.post(url, headersheaders, jsonpayload, timeoutself.timeout) resp.raise_for_status() data resp.json() return data[choices][0][message][content].strip()如果使用 OpenAI 官方接口base_url填官方地址即可如果使用第三方兼容平台则需要填入平台提供的base_url。整个项目只需要依赖这个封装类后面所有功能模块都复用complete方法。4.3 人物设定表的设计人物设定表是保持长篇一致性的核心资产。我建议使用 JSON 保存因为 JSON 结构清晰、便于程序读取和注入 Prompt。下面是一份简化示例保存到data/characters.json{ 角色: { 林晚: { 身份: 前考古队员通晓古文字, 性格: 冷静、谨慎、不善表达情感, 外貌: 黑色短发左眉有一道陈旧疤痕, 口头禅: 先看证据再下结论, 核心动机: 寻找失踪导师的下落, 成长弧光: 从独来独往到学会信任同伴 }, 周野: { 身份: 地下市集的文物贩子, 性格: 圆滑、重义气、表面玩世不恭, 外貌: 高个子笑起来有虎牙, 口头禅: 这行就是灰色地带, 核心动机: 替死去的妹妹讨回公道, 成长弧光: 从只信利益到愿意为同伴冒险 } }, 主要关系: 林晚与周野因同一件文物交易相识两人互相戒备又不得不合作。, 禁止事项: 不要让林晚在非紧张情境中哭泣不要安排周野主动坦白过去。 }在实际项目中这个文件可以写得很长包含每个角色的背景故事、人际关系、当前状态等。关键在于每次生成新章节前都把这份设定表的关键部分注入到 Prompt 中。4.4 实现小说创作助手类接下来实现FictionAssistant类包含人物设定加载、大纲生成、章节扩写、一致性自检四个方法。class FictionAssistant: def __init__(self, client: ChatClient): self.client client self.character_sheet None self.outline None def load_characters(self, pathdata/characters.json): with open(path, r, encodingutf-8) as f: self.character_sheet json.load(f) def load_outline(self, pathdata/outline.json): with open(path, r, encodingutf-8) as f: self.outline json.load(f) def save_json(self, data, path): with open(path, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) def generate_outline(self, concept, style简洁网文风): system_prompt ( 你是一名资深小说策划编辑擅长把创意概念转化为结构清晰、冲突明确的长篇大纲。 输出 JSON 格式包含 story_name、logline、characters、chapters 四个字段。 chapters 是列表每个元素包含 chapter_title 和 chapter_brief。 不要输出 JSON 之外的任何文字。 ) user_prompt f故事概念{concept}\n目标文风{style} result self.client.complete( [ {role: system, content: system_prompt}, {role: user, content: user_prompt}, ], temperature0.5, max_tokens2500, ) outline self._parse_json(result) self.outline outline return outline def expand_chapter(self, chapter_no, chapter_title, chapter_brief): if self.character_sheet is None: raise ValueError(请先加载人物设定表load_characters()) if self.outline is None: raise ValueError(请先加载或生成大纲load_outline() / generate_outline()) system_prompt ( 你是小说续写助手。你只负责根据给定大纲、人物设定和本章摘要求输出正文。\n 要求\n 1. 保持人物性格、行为逻辑和说话习惯一致。\n 2. 避免重复用词和空泛描写。\n 3. 不要擅自引入尚未出现的世界观设定。\n 4. 叙事视角、人称以现有正文为准。\n 5. 直接输出正文不要解释写作思路。 ) user_prompt f请扩写小说章节。 人物设定 {json.dumps(self.character_sheet, ensure_asciiFalse, indent2)} 章节信息 - 章节序号{chapter_no} - 章节标题{chapter_title} - 本章摘要{chapter_brief} 请输出正文 return self.client.complete( [ {role: system, content: system_prompt}, {role: user, content: user_prompt}, ], temperature0.8, max_tokens3000, ) def consistency_check(self, chapter_text): if self.character_sheet is None: raise ValueError(请先加载人物设定表) system_prompt ( 你是一名小说编辑质检助手。请对照人物设定检查章节正文 找出人物性格、外貌、关系、行为逻辑不一致的地方 并用简洁的清单形式输出问题。如果没有问题返回一致性良好。 ) user_prompt f人物设定\n{json.dumps(self.character_sheet, ensure_asciiFalse, indent2)}\n\n章节正文\n{chapter_text} return self.client.complete( [ {role: system, content: system_prompt}, {role: user, content: user_prompt}, ], temperature0.2, max_tokens1200, ) staticmethod def _parse_json(text): start text.find({) end text.rfind(}) if start -1 or end -1: raise ValueError(模型输出中没有找到 JSON 对象) return json.loads(text[start:end 1])_parse_json的作用是容错。有些模型可能返回带前后说明文字的 JSON直接用json.loads会失败所以我们先裁剪出最外层{...}再解析。如果仍然失败可以把原始输出打印出来查看。4.5 编写主程序入口在main.py中追加main函数用来串联整个流程。def load_config(pathconfig.json): with open(path, r, encodingutf-8) as f: return json.load(f) def main(): config load_config() client ChatClient( api_keyconfig[api_key], base_urlconfig[base_url], modelconfig[model], timeoutconfig.get(timeout, 120), ) assistant FictionAssistant(client) assistant.load_characters() concept ( 都市奇幻题材考古队员林晚在一次文物鉴定中偶然发现一件能映照使用者内心的古镜。 这件古镜被地下文物贩子周野盯上两人被迫合作去追查古镜背后隐藏的失落古城。 ) outline assistant.generate_outline(conceptconcept, style悬疑冒险风格) assistant.save_json(outline, data/outline.json) print(大纲生成完毕, outline[story_name]) for idx, chap in enumerate(outline[chapters][:2], start1): text assistant.expand_chapter( chapter_noidx, chapter_titlechap[chapter_title], chapter_briefchap[chapter_brief], ) report assistant.consistency_check(text) print(f第 {idx} 章一致性检查) print(report) with open(foutput/chapter_{idx:03d}.md, w, encodingutf-8) as f: f.write(f# 第 {idx} 章{chap[chapter_title]}\n\n{text}\n) if __name__ __main__: main()4.6 运行与验证运行前请先创建output目录并确保data/characters.json存在。python main.py如果 API 配置正确会看到类似下面的输出大纲生成完毕古镜迷城 第 1 章一致性检查 一致性良好。 第 2 章一致性检查 1. 第 2 章中林晚说话时语气比设定更热情建议检查是否符合前期克制风格。 2. “周野的虎牙”描写出现次数偏多可以考虑适当减少。这里要说明实际输出内容由模型决定不同模型、不同 Prompt 的结果会有差异。这个流程的目的是把创作过程中的“生成、检查、保存”变成可重复执行的操作而不是追求某一次的具体输出。4.7 结果说明与文件归档章节生成后会保存到output/chapter_001.md、output/chapter_002.md。每个文件包含章节标题和正文。一致性检查结果目前只是打印在终端实际项目中可以把检查结果也保存到文件例如output/check_report.md方便统一处理。建议每次批量生成后把人物设定表和最新章节一起提交到 Git 仓库。这样一旦发现某一次生成导致剧情走向失控可以快速回退到之前的版本。5. 常见问题与排查思路在使用 LLM 辅助写小说的过程中最常遇到的问题并不是模型“不会写”而是“写得前后不一致”。下面整理一份高频问题清单。问题现象可能原因排查与解决思路人物性格前后不一致林晚突然变得爱哭上下文过长后模型遗忘了早期设定在每次生成前注入人物设定表并将设定表放在 Prompt 靠前位置剧情走向频繁偏离大纲大纲没有在每次请求中都携带生成章节时把当前主线目标、本章任务加入 Prompt文风漂移一会像网文一会像翻译腔系统提示词中的风格描述不够具体加入正反风格示例明确禁止使用的句式模型输出大量套话和空泛描写温度过低导致模型选择最常见表达适当调高 temperature并在 Prompt 中要求“避免陈词滥调”返回内容无法解析为 JSON模型在 JSON 前后添加了说明文字使用正则裁剪 JSON 片段并用json.loads解析后再做类型检查一次请求内容太长导致报错上下文超出模型窗口限制把旧章节摘要化只保留关键伏笔和状态模型生成了不合规或不合适的内容系统提示词没有定义底线在系统提示词中明确禁止事项例如“不要生成血腥暴力内容”接口偶尔超时生成 token 数过多或服务端繁忙设置超时时间、拆分长章节、增加重试机制排查这类问题我建议先看日志。最简单的方式是在ChatClient.complete里打印请求和响应的 token 数量便于判断是不是上下文超限。也可以把每次 Prompt 保存到文件里检查看看是不是某个字段拼接错误导致模型理解偏了。6. 工程化与创作管理建议6.1 建立分层记忆体系长篇小说创作的核心难点在于信息管理。我建议把项目信息分为三个层级来管理世界设定层世界观、势力、地理、科技水平、魔法规则等不经常变动。故事线层主线大纲、每卷目标、章节摘要随创作进度滚动更新。细节档案层每个角色的完整背景、伏笔记录、时间线事件表。每次调用模型时先决定本次写作需要哪一层的信息然后组合成 Prompt。不需要把所有档案都塞进去信息越多反而可能干扰模型对当前章节重点的把握。6.2 使用版本管理保存创作过程对于长篇写作强烈建议用 Git 管理文稿和设定文档。具体做法是每个章节一个 Markdown 文件。人物设定表作为一个独立 JSON 文件每次修改后提交一次。大纲的每次调整单独提交保留变更历史。这样做的价值在于你可以随时知道“剧情是在哪一版发生了转折”也可以对比不同模型的输出效果甚至在 AI 生成内容出现问题时回滚到安全版本。版本管理不只是开发者的习惯也是内容创作者保持作品可控性的重要手段。6.3 版权与内容合规提醒使用 LLM 生成小说内容时需要注意几个问题不同平台对 AI 生成内容的版权归属有不同的规定发布前务必阅读并遵守相关平台条款。不要在 Prompt 中要求模型模仿在世作家的风格也不要让模型直接续写别人的作品避免侵权风险。AI 生成内容不代表可以免去人工审核。涉及事实性信息、出版规范、敏感题材时创作者必须自行把关。如果作品计划用于商业出版建议保留人工修改和创作的记录并咨询专业法律意见。这些都只能在“合法授权、测试环境验证、资料备份”的框架下进行。创作过程越透明、越有据可查后续潜在的争议就越少。6.4 控制 API 成本与调用频率生成小说需要大量 token成本控制不能忽视。常用手段包括使用模型返回的usage字段统计 token 数。对章节生成结果做缓存避免重复请求。把“大纲生成”和“章节扩写”拆开分别使用不同的模型和参数而不是所有任务都用最强模型。设置单次批处理的最大章节数一次性任务不要无限制跑下去。示例中ChatClient目前没有打印 token 统计如果你需要可以在接口返回中读取data[usage]然后写入日志文件。这些数据能帮你判断哪类请求消耗最大。# 在 ChatClient.complete 中追加一个可选日志 if data.get(usage): print(prompt_tokens:, data[usage].get(prompt_tokens), completion_tokens:, data[usage].get(completion_tokens))6.5 把创作流程拆成可复用的流水线当项目变大后可以把流程进一步模块化。比如从FictionAssistant中拆出OutlineGenerator、ChapterWriter、ConsistencyChecker三个类各自只负责一个任务。这样便于针对不同模型做 A/B 测试也便于以后接入新的模型能力。一条典型的流水线可以长这样创作者撰写或确认故事概念。大纲生成器产出粗纲。人工调整大纲确认每章目标。章节扩写器逐章生成初稿。一致性检查器发现问题。人工修改后在设定的“禁止事项”中加入新规则。定期把新状态写回人物设定表和主线大纲。这个循环跑上一段时间后设定文件会越来越完善模型输出的质量也会因为 Prompt 更精确而明显提升。7. 总结与下一步学习这篇文章围绕“用 LLM 解放小说创作”这条主线介绍了一套从环境准备、提示词设计、代码封装到长篇小说信息管理的完整方案。你可以看到LLM 在这里承担的角色不是“作者”而是“外脑加初稿生成器”。真正让故事有价值、有情感、有风格的仍然是作者对人物和世界的主观判断。下一步你可以从三个方向继续深入一是优化 Prompt 模板例如加入“一句话概括本章目标”“本章必须出现的伏笔”等结构化字段让生成结果更可控二是研究适合本地部署的开源模型在隐私和成本之间找到平衡三是尝试做多角色互动式创作比如用不同模型分别扮演不同人物让对话更自然。如果你打算把这套流程真正用在一个长篇项目里我的建议是先开一本短篇练手。目标不是写出惊天大作而是把“设定表维护、章节生成、一致性自检、人工修改”这套循环跑熟。等这种节奏变成肌肉记忆之后再回到更复杂的长篇创作你会明显感觉到LLM 确实把写作从“一字一字苦熬”变成了“从多个可能中做选择”而选择权始终在你手里。
返回列表