#430 行代码,让本地大模型替我写小说
先说结论:这个项目只有 6 个文件,430 行 Python,依赖只有 2 个包。
它能做什么?三条命令,从零生成一本小说:
python write.py new "苍穹之上" --genre xuanhuan --theme 修仙 --chapters 100
python write.py outline # 世界观 + 角色 + 100章大纲
python write.py write # 写下一章,3000字
没有前端,没有 API 服务器,没有数据库,没有 Docker。一个终端窗口搞定一切。
为什么这么少?因为第一版我做复杂了——多智能体协作、浏览器自动化发布、看板拖拽管理、FastAPI 后端、定时调度器。该有的都有。
然后我发现:80% 的功能我从来没用过。
真正每天在用的就三件事:创建小说、生成大纲、写章节。于是砍到只剩这三件事。
##项目长什么样
novel-lite/
├── config.yaml # Prompt 模板 + 模型配置
├── config.py # 读配置(25行)
├── ai.py # 调 Ollama(25行)
├── files.py # 文件读写(179行)
├── core.py # 核心逻辑(131行)
├── cli.py # 命令行入口(62行)
└── write.py # 启动入口(6行)
click to copy
六个文件,每个只做一件事。
write.pyclick to copy 只有 6 行,是个门面:
"""极简本地小说创作系统 - 入口点"""
from cli import main
if __name__ == "__main__":
main()
ai.pyclick to copy 只有 25 行,封装了 Ollama 调用:
def generate(prompt: str, context: str = "", retries: int = 3) -> str:
"""调用 Ollama 生成文本,带重试"""
full_prompt = f"{context}\n\n{prompt}" if context else prompt
for attempt in range(retries):
try:
response = ollama.chat(
model=CONFIG["model"],
messages=[{"role": "user", "content": full_prompt}],
options={"num_ctx": 4096}
)
return response["message"]["content"]
except Exception as e:
if attempt == retries - 1:
raise RuntimeError(f"Ollama 调用失败: {e}")
time.sleep(2 ** attempt)
指数退避重试,没了。不搞什么流式输出、多轮对话、上下文缓存。够用就好。
##创作流水线
三条命令,背后是四个步骤:
new ──→ novel.md(元信息)+ context.md(进度追踪)
│
outline ──→ 世界观 → 角色 → 大纲 ──→ outline.md
│
write ──→ 读大纲 + 读上下文 → 生成章节 → chapters/001-xxx.md
│ │
│ 自动生成摘要 → 更新 context.md
│
└──→ 下次 write 时带上最近5章摘要
click to copy
###第一步:new——创建小说
python write.py new "苍穹之上" --genre xuanhuan --theme 修仙 --chapters 100
生成两个文件。novel.mdclick to copy 存元信息,用 YAML front matter:
---
title: 苍穹之上
genre: xuanhuan
theme: 修仙
target_chapters: 100
words_per_chapter: 3000
model: deepseek-r1:7b
created: 2026-04-06
---
context.mdclick to copy 存进度,一开始几乎是空的:
# 上下文摘要
## 当前进度
- 已完成:0章
###第二步:outline——世界观 + 角色 + 大纲
这一条命令干了三件事,依次调用 Ollama:
世界观 → 写入 novel.md 的 ## 世界观 段
↓
角色 → 写入 novel.md 的 ## 角色 段
↓
大纲 → 写入 outline.md
click to copy
Prompt 设计得挺讲究。生成世界观的 Prompt 长这样:
# config.yaml
world: |
请为一部{genre}类型的小说创建世界观设定。
主题:{theme}
要求:
1. 修炼/能力体系(3-5个等级)
2. 世界背景(势力分布、历史背景)
3. 特色元素(2-3个独特的设定)
4. 字数:500-800字
角色的 Prompt 会把刚生成的世界观喂进去,大纲的 Prompt 会把世界观和角色都喂进去。每一步都站在前一步的肩膀上。
最终 outline.mdclick to copy 长这样:
# 大纲
## 第一卷 初入仙途
- 001. 灵脉觉醒
- 002. 拜入宗门
- 003. 外门试炼
...
## 第二卷 风起云涌
- 021. 秘境开启
- 022. 古老传承
...
分卷规划,每卷 20-30 章。主线清晰,起承转合。
###第三步:write——写章节
这是核心。每写一章,系统做四件事:
1. 读大纲,拿到当前章节标题
chapter_title = get_chapter_outline(chapter_num)
if not chapter_title:
raise ValueError(f"未找到第 {chapter_num} 章的大纲")
2. 组装 Prompt,带上上下文
prompt = CONFIG["prompts"]["chapter"].format(
context=read_context(), # 最近5章摘要 + 角色状态 + 伏笔
num=chapter_num,
title=chapter_title,
words=3000,
style="玄幻类型,修仙主题"
)
3. 调 Ollama 生成,写入文件
content = generate(prompt)
# 章节文件带 YAML front matter
write_file(
CHAPTERS_DIR / f"{chapter_num:03d}-{chapter_title[:10]}.md",
build_yaml_front_matter(meta) + f"\n# 第{chapter_num}章 {chapter_title}\n\n{content}"
)
4. 生成摘要,更新上下文
# 让 AI 用 200 字概括这一章
summary = generate(f"请用200字概括以下章节的剧情:\n{content[:2000]}")
# 滑动窗口:只保留最近 5 章
ctx["recent_summaries"] = (ctx["recent_summaries"] + [f"第{num}章:{summary}"])[-5:]
ctx["current_chapter"] = chapter_num
write_context(ctx)
##上下文管理:滑动窗口
大模型有上下文长度限制,100 章的小说不可能全塞进去。
我的方案很粗暴但有效——滑动窗口 + 自动摘要:
写第 50 章时的 context.md:
┌─────────────────────────────────────┐
│ 世界观设定(始终存在) │ ← novel.md 里的固定内容
│ 角色设定(始终存在) │
├─────────────────────────────────────┤
│ 第 46 章摘要(200字) │ ← 滑动窗口,只留最近 5 章
│ 第 47 章摘要(200字) │
│ 第 48 章摘要(200字) │
│ 第 49 章摘要(200字) │
├─────────────────────────────────────┤
│ 第 50 章大纲标题 │ ← 当前任务
└─────────────────────────────────────┘
click to copy
世界观和角色始终在上下文里(它们不会变),剧情摘要只保留最近 5 章。每次写完一章,AI 自动生成 200 字摘要,老的被挤出窗口。
核心就一行代码:
ctx["recent_summaries"] = (summaries + [new_summary])[-5:]
加上角色状态追踪和伏笔管理,context.mdclick to copy 还能记录:
## 角色状态
- 林凡:筑基中期,获得上古传承
- 苏婉儿:金丹初期,暗中守护
## 待埋伏笔
- 神秘老者的身份未揭示
- 天道碑碎片还有三块未找到
这样 AI 写新章节时不会忘记前面挖的坑。
##文件写入:原子操作
files.pyclick to copy 占了 179 行,是最大的模块,但做的事情很简单——安全地读写 Markdown 文件。
一个容易忽略的细节:文件写入是原子的。
def write_file(path: Path, content: str) -> None:
"""原子写入文件,带备份"""
if path.exists():
shutil.copy(path, Path(str(path) + ".bak")) # 先备份
tmp = Path(str(path) + ".tmp")
tmp.write_text(content, encoding="utf-8") # 写临时文件
tmp.rename(path) # 原子重命名
万一写到一半断电了?没关系,.bakclick to copy 文件还在。万一 .tmpclick to copy 写了一半挂了?原文件没动过,稳如磐石。
数据格式也很讲究。novel.mdclick to copy 用 YAML front matter 存元信息,正文用 Markdown 标题分段:
---
title: 苍穹之上
genre: xuanhuan
---
# 世界观
(AI 生成的世界观内容)
# 角色
(AI 生成的角色内容)
读写分离:parse_yaml_front_matterclick to copy 拆开头和正文,split("# 世界观")click to copy 提取各段。不需要数据库,不需要 JSON,一个 Markdown 文件全搞定。
##配置:YAML + 环境变量
config.yamlclick to copy 存两样东西:模型参数和 Prompt 模板。
model: deepseek-r1:7b
timeout: 300
prompts:
world: |
请为一部{genre}类型的小说创建世界观设定...
character: |
基于以下世界观,创建小说角色:{world}...
outline: |
基于以下设定,生成小说大纲:{world} {characters}...
chapter: |
{context} 第{num}章:{title}...
想换模型?改一行 YAML,或者设个环境变量:
if os.getenv("OLLAMA_MODEL"):
config["model"] = os.getenv("OLLAMA_MODEL")
Prompt 模板用 Python 的 str.format()click to copy 占位符。不搞 Jinja2,不搞模板引擎。{genre}click to copy、{world}click to copy、{characters}click to copy,够清楚。
##为什么不用云端大模型?
三个原因,按重要性排序:
隐私第一。你的世界观、角色设定、剧情走向——这是你最有价值的东西。送进云端 API,就是送进别人的训练数据。本地模型,数据不出本机。
成本第二。100 章小说,每章 3000 字,生成、摘要、世界观、角色、大纲加起来要调几百次 API。按 token 计费,一本书几百块。本地模型跑在自己的 GPU 上,电费忽略不计。
可控第三。模型在你自己的电脑上。想停就停,想换就换,断网也能用。不用担心 API 限速、服务下线、价格翻倍。
| 模型 | 显存 | 质量 | 推荐场景 |
|---|---|---|---|
| deepseek-r1:7b | 8GB | 不错 | 日常创作,性价比之王 |
| qwen2.5:14b | 16GB | 很好 | 追求质量 |
| deepseek-r1:32b | 24GB | 优秀 | 显卡好的话闭眼选 |
##依赖只有两个
ollama>=0.1.0
pyyaml>=6.0
click to copy
ollamaclick to copy 调本地大模型,pyyamlclick to copy 解析配置和 front matter。
没有 FastAPI,没有 Playwright,没有 APScheduler,没有 React,没有 zustand,没有 dnd-kit。
第一版有 20 多个依赖。现在只需要 2 个。
##跑起来
# 1. 装 Ollama,拉模型
ollama pull deepseek-r1:7b
# 2. 装依赖
pip install ollama pyyaml
# 3. 开写
cd novel-lite
python write.py new "我的小说" --genre xuanhuan --theme 修仙
python write.py outline
python write.py write
python write.py write
python write.py write
...
没有 conda 环境要创建,没有 environment.ymlclick to copy,没有 npm installclick to copy。两个 pip 包,一个 Ollama,完事。
##最后
这个项目的第一版有前端看板、API 后端、浏览器自动化、定时调度——该有的都有。
然后我砍掉了 80%。
留下的 20% 是真正每天在用的。创建小说,生成大纲,写章节。没了。
config.yamlclick to copy 里的四个 Prompt 模板才是这个项目真正的核心。世界观怎么描述、角色怎么生成、大纲怎么规划、章节怎么写——这些决定了 AI 输出的质量。代码只是把这些 Prompt 按正确的顺序喂给 Ollama,然后把结果存成 Markdown 文件。
430 行代码做的事很简单:把人的创意变成结构化的 Prompt,把 AI 的输出变成结构化的文件。
复杂的事情发生在模型内部,不在代码里。
"Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away."
——Antoine de Saint-Exupery