430 行代码,让本地大模型替我写小说
技术#AI#Ollama

430 行代码,让本地大模型替我写小说

>
~5 min read

#430 行代码,让本地大模型替我写小说

先说结论:这个项目只有 6 个文件,430 行 Python,依赖只有 2 个包。

它能做什么?三条命令,从零生成一本小说:

>hljs bash.0 lines
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 行,是个门面:

>hljs python.0 lines
"""极简本地小说创作系统 - 入口点"""
from cli import main

if __name__ == "__main__":
    main()

ai.pyclick to copy 只有 25 行,封装了 Ollama 调用:

>hljs python.0 lines
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——创建小说

>hljs bash.0 lines
python write.py new "苍穹之上" --genre xuanhuan --theme 修仙 --chapters 100

生成两个文件。novel.mdclick to copy 存元信息,用 YAML front matter:

>hljs yaml.0 lines
---
title: 苍穹之上
genre: xuanhuan
theme: 修仙
target_chapters: 100
words_per_chapter: 3000
model: deepseek-r1:7b
created: 2026-04-06
---

context.mdclick to copy 存进度,一开始几乎是空的:

>hljs markdown.0 lines
# 上下文摘要

## 当前进度
- 已完成:0章

###第二步:outline——世界观 + 角色 + 大纲

这一条命令干了三件事,依次调用 Ollama:

世界观 → 写入 novel.md 的 ## 世界观 段 ↓ 角色 → 写入 novel.md 的 ## 角色 段 ↓ 大纲 → 写入 outline.md click to copy

Prompt 设计得挺讲究。生成世界观的 Prompt 长这样:

>hljs yaml.0 lines
# config.yaml
world: |
  请为一部{genre}类型的小说创建世界观设定。
  主题:{theme}
  要求:
  1. 修炼/能力体系(3-5个等级)
  2. 世界背景(势力分布、历史背景)
  3. 特色元素(2-3个独特的设定)
  4. 字数:500-800字

角色的 Prompt 会把刚生成的世界观喂进去,大纲的 Prompt 会把世界观和角色都喂进去。每一步都站在前一步的肩膀上。

最终 outline.mdclick to copy 长这样:

>hljs markdown.0 lines
# 大纲

## 第一卷 初入仙途

- 001. 灵脉觉醒
- 002. 拜入宗门
- 003. 外门试炼
...

## 第二卷 风起云涌

- 021. 秘境开启
- 022. 古老传承
...

分卷规划,每卷 20-30 章。主线清晰,起承转合。

###第三步:write——写章节

这是核心。每写一章,系统做四件事:

1. 读大纲,拿到当前章节标题

>hljs python.0 lines
chapter_title = get_chapter_outline(chapter_num)
if not chapter_title:
    raise ValueError(f"未找到第 {chapter_num} 章的大纲")

2. 组装 Prompt,带上上下文

>hljs python.0 lines
prompt = CONFIG["prompts"]["chapter"].format(
    context=read_context(),        # 最近5章摘要 + 角色状态 + 伏笔
    num=chapter_num,
    title=chapter_title,
    words=3000,
    style="玄幻类型,修仙主题"
)

3. 调 Ollama 生成,写入文件

>hljs python.0 lines
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. 生成摘要,更新上下文

>hljs python.0 lines
# 让 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 字摘要,老的被挤出窗口。

核心就一行代码:

>hljs python.0 lines
ctx["recent_summaries"] = (summaries + [new_summary])[-5:]

加上角色状态追踪和伏笔管理,context.mdclick to copy 还能记录:

>hljs markdown.0 lines
## 角色状态
- 林凡:筑基中期,获得上古传承
- 苏婉儿:金丹初期,暗中守护

## 待埋伏笔
- 神秘老者的身份未揭示
- 天道碑碎片还有三块未找到

这样 AI 写新章节时不会忘记前面挖的坑。

##文件写入:原子操作

files.pyclick to copy 占了 179 行,是最大的模块,但做的事情很简单——安全地读写 Markdown 文件。

一个容易忽略的细节:文件写入是原子的。

>hljs python.0 lines
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 标题分段:

>hljs markdown.0 lines
---
title: 苍穹之上
genre: xuanhuan
---
# 世界观

(AI 生成的世界观内容)

# 角色

(AI 生成的角色内容)

读写分离:parse_yaml_front_matterclick to copy 拆开头和正文,split("# 世界观")click to copy 提取各段。不需要数据库,不需要 JSON,一个 Markdown 文件全搞定。

##配置:YAML + 环境变量

config.yamlclick to copy 存两样东西:模型参数和 Prompt 模板。

>hljs yaml.0 lines
model: deepseek-r1:7b
timeout: 300

prompts:
  world: |
    请为一部{genre}类型的小说创建世界观设定...
  character: |
    基于以下世界观,创建小说角色:{world}...
  outline: |
    基于以下设定,生成小说大纲:{world} {characters}...
  chapter: |
    {context} 第{num}章:{title}...

想换模型?改一行 YAML,或者设个环境变量:

>hljs python.0 lines
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:7b8GB不错日常创作,性价比之王
qwen2.5:14b16GB很好追求质量
deepseek-r1:32b24GB优秀显卡好的话闭眼选

##依赖只有两个

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 个。

##跑起来

>hljs bash.0 lines
# 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

> tags
#AI#Ollama#小说#Python#极简主义
> related_posts
>cd /blog_