# UnderStory：深入文本的轻小说阅读器 · Owlcarp

UnderStory：把正文阅读、作品 Wiki 与 AI 阅读伙伴连接起来。体验界面与功能演示，了解产品思考及长文本 Harness 设计。

作者：Owlcarp

原文：https://owlcarp.me/understory/

![UnderStory 绿色书籍树苗标志](https://owlcarp.me/articles/understory/app-icon.svg)

# UnderStory

## 栽一棵智能的树，  
为阅读而生长。

一款AI原生的轻小说阅读器

[项目仓库](https://github.com/GVD20/UnderStory)[开发杂记](https://owlcarp.me/understory/#article)

## 阅读之外，故事之内。

让阅读器主动理解故事情节，营造资料主动环抱的阅读体验。

### 让 UnderStory 读懂作品，  
再陪你看下去。

导入EPUB后，分析流程会逐卷读完整本书，整理成内部知识结构，再主动整理成你可以直接阅读的Wiki、概览等内容。

你可以配置任意自己喜爱的模型，包括文本嵌入和语言模型。

*Demo 视频的经过模拟和加速处理。

### 完整的作品维基站，  
就在阅读器里。

作品分析完成后，AI会利用作品内容撰写完整的 Wiki 页面结构。人物、世界观、地点……或者任意一种某个作品独有的概念，都清晰呈现。

专题页面更可以提供词条以外的全作洞察，助你发掘更深层次的原作体验。

### 只了解此刻  
应该知道的事。

维护阅读体验，同时尊重原著设计。Wiki文本会主动屏蔽剧透内容，并随着阅读进度适时解锁。辅助阅读，不意味着破坏原著体验。

剧透屏蔽模提供多种档位选择，适应不同需求。对话遇到重大剧透，则会先征求读者的选择。

### 问一个问题，  
得到可核对的回答。

阅读伙伴不仅仅是一个 RAG Agent。原文、分析产物、Wiki都能成为背后的知识库，因而回答非常准确。富文本回答、各种引用卡片都会出现在回答里，帮助读者判断答案从何而来。

它不只解释情节，也能在应用中修改阅读设置、创建读者关注的专题，让对话成为与整套阅读系统交互的入口。

### 一个陌生的名字，  
不必打断阅读。

选中原文，与之匹配的人物、事物和概念便会自然呈现。点即可进入对应 Wiki，不用查询，甚至无需离开阅读页面，让查资料成为阅读中的一个轻动作。

UnderStory

# 深入文本的轻小说阅读器

> 阅读提醒：文中的界面和测试示例取自《86－不存在的战区－》《关于我转生变成史莱姆这档事》与《猫的地球仪》。其中可能出现少量人物、设定与情节信息，介意剧透的读者可以酌情跳过相关图片。

记得很早以前用 Kindle 读书时，我就对 X-Ray 印象深刻。长按一个人名或术语，便能看到它在书中的介绍与出场信息。它有点像一本随书附赠的小型百科：人物、地点、组织、物品、事件，以及那些读到一半已经记不太清的专有名词，都能从这里找回来。

我一直觉得，这类功能尤其适合轻小说。许多作品有着层层展开的世界观，几十卷、十几年的连载并不稀奇。读到后面，某个曾经登场的人物突然回来，或一段很早之前埋下的设定重新变得重要，读者往往需要先回忆好一阵子，才能接上故事。

有人喜欢一口气读到底，不愿意被任何辅助信息打扰。我自己则属于另一种读者：读懵了、隔了很久再续读，偶尔甚至跳着看时，我很乐意停下来翻翻资料。以前用 X-Ray，后来查萌娘百科和各种作品 Wiki。想知道的常常只是“这个人原来是什么身份”“几个组织之间是什么关系”，倒不是想提前打听结局。恰恰相反，弄明白这些背景，通常能让我更有兴趣接着往下读。

但现有资料并不总能找到。社区 Wiki 依赖爱好者长期维护，热门角色可能有完整的生平，次要人物却仍在施工，冷门作品甚至没有像样的词条。X-Ray 也不是每本书都有，内容完整度很不一致。

到了今天，很自然会想到让大语言模型来帮忙。最容易实现的办法，是把小说切成片段，做成 RAG 检索库，再给阅读器加一个聊天侧栏。许多“阅读器接入 AI”的想法大概都会从这里起步。我也考虑过，只是很快发现，它与我真正想要的体验还有一点距离。

检索擅长把相关段落找出来，却不等于真正把一个故事从头到尾读过。假如问的是某段台词出现在哪里，它很好用；但如果想理解一个人物跨越数卷的变化、两件相隔很久的事情为何相关，答案往往藏在许多看似不直接相关的段落里。单靠找出几块相似文本再拼成回答，容易遗漏那些需要通读才能串起来的关系。

而且，就算回答足够准确，我也不想每了解一件事都先组织一道问题。浏览 Wiki 的乐趣，恰恰是有时根本不知道自己要找什么：从一个人物跳到某个组织，又顺着一个名词发现另一段故事。这种漫无目的的探索，很难靠一问一答替代。聊天机器人也不可能因为我问了一句话，就自然地把整部作品的百科呈现在面前。

所以我想换一种思路：**先让模型尽可能完整地“读”过作品，再把读到的内容整理成一个可以自由探索的知识空间。** 对话只是进入这个空间的途径之一。

在动手之前，我先估算了这件事的规模。以 DeepSeek V4-Flash 的 Tokenizer 为例，我把《魔女之旅》前 24 卷中文译本 EPUB 的全文读入统计，结果是 **1,298,480 Token**，比 100 万上下文窗口多约 29.8%。比预想中乐观：一些更短的作品，或许真能一次放进长上下文。但碰到四十卷的小说、上下文较小的模型，或者输入 Token 与长上下文价格较高的服务，这个办法立刻就不那么从容了。即使放得下，也未必适合每次提问都重新发送一整部书。

我需要的仍然是一套能够分段阅读、记住重要内容、再把信息交给不同任务使用的工作流，也就是后面会提到的模型运行架构，即 **Harness**。

![UnderStory 标志：从书中生长的树](https://owlcarp.me/articles/understory/app-icon.svg)

**UnderStory 的 Logo：书页是根，逐渐生长的树象征由原文生成的知识与阅读辅助。**

于是有了 **UnderStory**。在林业中，*understory* 指的是森林树冠下方的植被层。我选择这个名字，因为它恰好契合了我对这款阅读器的想象：一切从故事本身出发，也围绕故事生长。它首先是一款可以安静地阅读轻小说的阅读器；而在需要的时候，AI 能从原文中梳理人物、事件与世界观，让那些散落在章节之间、读过却未必记得住的细节变得清晰。

Logo 也延续了这个想法：一棵小树从摊开的书中生长出来，根系扎进书页，枝叶向上舒展。由 AI 生成的 Wiki、人物关系和其他辅助内容，就像这棵树逐渐长出的枝叶，从整部作品中汲取养分，又始终与原文相连。读者可以沿着枝叶探索故事的更多细节，也可以只是坐在树下，继续读手里的书。

## 第一部分：产品设计

### 基础板块

UnderStory 的产品形态并不是先写好一份需求文档，再照着逐条实现的。我一边 Vibe Coding，一边把自己放到实际阅读的场景中，不断调整界面与交互。最后逐渐收敛成三个相对独立、又能够彼此衔接的板块：阅读、Wiki 和对话。

#### 阅读

阅读首先得是一件舒服的事。所以最基础的仍然是一个正常的 EPUB 阅读器：左边是按卷和章节展开的目录，像文件管理器一样方便定位；中间是一整块可以连续滚动的正文；右边留给辅助信息；底部则有阅读进度。字体、字号、行距、插图与部分高级排版都能按需调整。我希望一个人即使完全不使用 AI，也能够坐下来好好读完一本书。

![UnderStory 的阅读页面](https://owlcarp.me/images/responsive/articles/understory/01-480.webp)

**阅读板块。以《猫的地球仪》为例，正文居中，卷章目录和阅读辅助侧栏分居两侧。**

#### Wiki

相比之下，Wiki 才是最初真正让我想做这个项目的东西。每导入一部作品，UnderStory 都可以先分析原文，为它生成独立的 Wiki：有首页、目录、分类、专题和具体词条。读者进入这里，不需要先提出问题，只要循着自己感兴趣的地方一路点下去即可。

我沿用了人们熟悉的 Wiki 阅读方式：左侧的大纲，右侧的词条内容；页面上有信息表、结构化正文、相关词条、作品导航，以及能够跳回原文的引用。不过底层并没有嵌入 MediaWiki 之类完整的维基系统。它们为社区协作和内容编辑准备了庞大的功能，而我更需要一个能承载 AI 生成结构、供读者浏览的轻量空间。于是索性自己实现了一套“看起来、用起来都像 Wiki”的界面。

![UnderStory 的 Wiki 主页](https://owlcarp.me/images/responsive/articles/understory/02-480.webp)

**《猫的地球仪》的作品 Wiki 主页。即使没有明确要查的内容，也可以从这里开始探索。**

#### 对话

第三个板块是对话。如果我已经知道自己想问什么，聊天当然最直接：Wiki 中没有解释清楚的关系、某段情节的疑问，都能拿来问。只不过这里的对话并非独立悬浮在阅读器之外的助手。它可以调用原文引用、Wiki 词条卡片和跳转链接，也能请求创建专题、补充词条，甚至直接修改阅读器设置。

![UnderStory 的独立对话板块](https://owlcarp.me/images/responsive/articles/understory/03-480.webp)

**对话板块。它既用于了解作品，也承担一部分阅读器操作入口的职责。**

对话界面本身也经历过一次改动。最初我采用类似短信应用的回合式气泡：我问一句，模型回一条。但用下来发现，读者往往是在认真阅读模型给出的一段内容，而不是像聊天软件那样快速互发消息。于是我改用接近 ChatGPT 的全宽回答排版，让标题、段落、粗体、列表和引用成为内容本身的组成部分。

等待过程也做了一些处理。Agent 调查问题、调用工具时，哪怕模型已经很快，读者仍可能等上数十秒。现在它会先给出一条简短的即时回应，再逐步显示工具调用和计时，让人知道这段等待正在发生什么。回答中还可以按需出现 Wiki 卡片、章节跳转、原文引用块、表格或叙事轨迹，点击即可回到相应内容。

![对话的结构化回答组件](https://owlcarp.me/images/responsive/articles/understory/04-480.webp)

**一份特意密集展示各种组件的回答示例。平时由模型按需要选择，不必每次都出现全部组件。**

![阅读、对话与 Wiki 的导航入口](https://owlcarp.me/images/responsive/articles/understory/05-474.webp)

**顶部三个并列入口，对应三种不同的使用方式。**

三个板块之间的关系很清晰和简洁：想读书时就读书，想随便逛逛就打开 Wiki，遇到明确的问题才去对话。不会全部挤在同一个界面里，也不必一同争夺读者的注意力。

---

### 阅读侧栏

当然，真正读书时最常见的情况，是正读到某句话，突然想确认一个名字或设定。这时专门切到 Wiki，再找到词条，又回到原文，还是有点麻烦。

于是阅读页面右侧保留了一个多功能侧栏，里面有四种视图：

- **轻览**会为每章生成一段尽量不剧透的前情提示和内容引入，并列出本章相关的词条快捷卡片。

- **Wiki** 和 **对话**则把对应板块直接带到正文旁，方便一边读一边查。

- **搜索**整合原文 RAG、Wiki 词条与章节检索。

不管我是在回忆上一章的情节，还是想问正在读的这一段，都不必离开阅读页面。

![阅读侧栏的四种视图](https://owlcarp.me/images/responsive/articles/understory/06-480.webp)

**从左到右：章节轻览、Wiki、阅读伙伴与搜索。辅助信息始终与正文相邻。**

---

### 原文选中与快速查询

我还做了一个更顺手的动作：直接选中文字。比如一段对话中突然提到了早已忘记的角色，我只要选中人名，弹窗便会找出可能对应的词条，点一下就能查看；如果我关心的是这句话本身，还能把选中的段落附给阅读伙伴，让它围绕这段文字回答。

![正文选中与快捷查询](https://owlcarp.me/images/responsive/articles/understory/07-480.webp)

**选中文字后，词条入口和对话操作就出现在附近；右侧同时展示围绕选中内容的讨论。**

这里并不只是简单地把选中文字与词条名逐字比较。小说里的称呼很复杂：人物可能有全名、姓氏、绰号与称号，《86》中的“修雷·诺赞”“诺赞”“死神”就可能指向同一个角色。仅凭标题完全匹配，很多读者真正选中的词反而会漏掉。

所以弹窗会同时提供准确匹配的词条和相关词条推荐，并在别名匹配时标明它对应的正式名称。文本里可识别的关键词也会相应高亮。支撑这件事的别名信息，并不是为弹窗单独写的一套规则，而是后面作品分析阶段积累的“公共要点表”中的数据。

![精确匹配、别名与相关词条推荐](https://owlcarp.me/images/responsive/articles/understory/08-480.webp)

**绿色标签对应直接匹配的词条，其他标签提供相关建议；别名匹配可以帮助辨认角色的不同称呼。**

![公共要点表中的别名记录](https://owlcarp.me/images/responsive/articles/understory/09-480.webp)

**内部调试界面。一个对象可以拥有多个别名，也会保留在不同卷中逐步补充的记录；读者通常不会直接看到这张表。**

### 阅读进度条

小说本身是一条不断向前的时间线。为了让读者在这条线上有方位感，我还在阅读区域底部加了一条进度栏，同时显示**全书**与**当前卷**两个尺度。动辄二十多卷的作品，如果只在一条横线上排满章节，很难看清自己究竟到了哪里；把全书与单卷分开，既能知道整体还剩多远，也能知道这一章大概读到了什么位置。

它还会和 Wiki 的叙事轨迹联动：当侧栏打开某个词条，进度栏会标出这个对象出现过的章节。于是“这个人物后面还有戏份吗”“某个概念究竟贯穿了多少卷”，不仅能在百科里读到，也能直接在阅读进度上看到分布。

![阅读进度条与 Wiki 定位联动](https://owlcarp.me/images/responsive/articles/understory/25-480.webp)

**全书与本卷的双尺度进度，以及词条出场章节在进度栏中的标记和跳转。**

---

### 反剧透

做到这里，一个很微妙的问题出现了：UnderStory 的 Wiki 正是因为提前分析了整部作品，才有机会把人物和故事讲清楚。但对尚未读完的读者来说，这些知识也可能毁掉阅读体验。

我不想让 Wiki 变成一本把结局写在第一页的答案册。章节轻览从生成时就被要求“只引导，不剧透”；Wiki 与对话则分别设计了不同的防剧透交互。

#### Wiki 的剧透屏蔽

Wiki 中最常见的情况，是某个词条前半部分可以放心阅读，后半部分却涉及人物未来的命运。我参考了萌娘百科等站点常见的遮罩方式，用色块盖住可能剧透的段落。读者知道这里还有内容，但不会在无意间直接看到。

![Wiki 中的剧透遮罩](https://owlcarp.me/images/responsive/articles/understory/10-480.webp)

**人物词条里的部分内容被绿色遮罩覆盖，未涉及剧透的部分仍能正常阅读。**

这些遮罩不是前端靠关键词猜出来的。词条撰写 Agent（Wiki Composer）生成内容时，会在结构化输出中标记哪些片段属于剧透，并尽可能附上 `unlock_after_chapter_id`，也就是读到哪一章以后，这段信息可以解锁。引用跳转、段落定位和剧透规则都包含在同一份 Wiki 数据结构里。

![Wiki 的剧透与引用结构](https://owlcarp.me/images/responsive/articles/understory/11-480.webp)

**Wiki 生成时的原始结构示例，包含引用位置、剧透片段及章节解锁标记。**

有了章节条件，遮罩就不必永远停在原地。系统可以跟踪读者的实际进度：当我已经读过对应情节，这段文字对我而言就不再算剧透，可以自动显示出来。

当然这是可调整的，取决于读者个人的接受能力：

- **关：**始终显示所有剧透。

- **自动：**阅读达到指定章节后自动解锁指定剧透屏蔽。

- **严格：**始终屏蔽任何剧透内容。同时提供更多严格的屏蔽特性，有可能影响 Wiki 使用体验。

对于单段屏蔽内容，也允许读者主动点击临时解锁。

![剧透设置与手动解锁](https://owlcarp.me/images/responsive/articles/understory/12-480.webp)

**三种屏蔽策略与单段手动解锁。是否知道后续情节，最终仍由读者决定。**

还有一种更棘手的词条：事件的标题或简介本身就是剧透。此时只遮住正文已经来不及了，因此 Wiki Composer 也可以把整个词条标记为剧透。阅读器会锁定整页、模糊页面内容，并遮住目录中的词条名称，尽量避免读者仅仅扫过导航就提前知道了某件事。

![整页词条与目录标题的屏蔽](https://owlcarp.me/images/responsive/articles/understory/13-480.webp)

**当词条标题本身可能泄露情节时，标题与内容会一并被隐藏。**

#### 对话的剧透确认

对话的处理方式则不同。读者可能主动问“这个人最后怎么样了”，也可能只想问“他来自哪里”。同样访问过全文的 Agent，没法仅凭一个问题就断定我是否愿意接受剧透。于是它在调查到相关信息、准备回答前，可以调用一个确认工具，先中断生成，然后询问读者意见。

![对话中的剧透确认](https://owlcarp.me/images/responsive/articles/understory/14-480.webp)

**Agent 识别到可能严重剧透的问题后，先询问读者，再决定如何继续。**

阻断工具会提供三种选择：

- **忽略警告，提供完整回答：**认为读者接受剧透。模型会根据真实作品信息完整回答。

- **回答，但尽量减少剧透：**将“尽量减少剧透”要求注入提示词中，模型会被要求修改回答方式，减少剧透。

- **取消并停止回答：**停止当前回答。

我希望这些机制最终让阅读器既有“知道全书”的能力，也懂得在读者尚未走到某个地方时，先把答案留在那里。

---

### 阅读体验与设置

UnderStory 的大部分设计都围绕 Wiki 和 AI 展开，但我平时真正面对它的时间，仍然是在读一行行文字。阅读体验如果不舒服，再丰富的百科也只会变成一个偶尔打开的功能。

阅读设置因此覆盖了字号、行距、EPUB 样式、插图显示、浅色与深色主题，以及前面提到的剧透策略。设置以模态窗口弹出；因为弹窗会暂时遮住正文，我在窗口里加了一块实时预览，让调整字号或行距时不用反复关闭设置再看效果。

![阅读设置与实时预览](https://owlcarp.me/images/responsive/articles/understory/15-480.webp)

**设置弹窗。修改排版参数时，旁边的预览会同步变化。**

#### EPUB 排版

其中我花了不少心思的是 EPUB 样式。EPUB 本质上使用网页相关技术组织内容，但不同书籍的排版质量差别很大。有些制作精良，包含精细的段落、字号与图片布局；有些却带着凌乱的 CSS，换一个阅读器就会变得难看。也有人只是喜欢所有书都维持简单、统一的排版。

我提供了三个显示档位。**关闭**时只保留基础换行，不沿用原书样式；**基础**保留粗体、斜体、下划线、上下标、引用等明确的语义格式；**高级**则进一步应用 EPUB 内嵌与外部 CSS，包括布局、颜色和局部字体大小等。书籍导入时会先解析、清洗样式，阅读时再根据选项决定采用到什么程度。

![EPUB 三种排版策略的比较](https://owlcarp.me/images/responsive/articles/understory/16-480.webp)

**同一段文本分别以关闭、基础、高级三种方式呈现。保留多少原书样式，可以交给读者选择。**

#### 插画显示

插画则有另一个细节。我的浅色阅读界面偏暖白，普通轻小说的黑白插画却往往带着纯白底色，嵌进页面就会像贴了一块亮白纸。于是我做了“沉浸式插画”选项：将图片以正片叠底的方式与页面融合。对黑白插图来说，这通常能保留线条的可读性，同时减少画面与纸张背景之间突兀的色差。

![沉浸式插画开启前后](https://owlcarp.me/images/responsive/articles/understory/17-480.webp)

**同一幅插图的两种显示方式。开启后，白底不再与暖白的阅读页面明显分离。**

#### 自然语言调整设置

还有一个来自“对话即系统”想法的小功能：我可以直接对阅读伙伴说“把字号调大一点”“行距再松一些”“改变插画显示方式”。Agent 通过读取设置和更新设置两个工具，修改字号、行距、图片模式、EPUB 特性或主题，并把改动整理成一张可操作的卡片。

这张卡片上有撤回入口。我觉得这个细节很重要：某些排版习惯是在读了很久以后才慢慢调到合适的，一句自然语言虽然方便，但不该让人忘记原来的数字、又费力找回去。修改应当看得见，也应当退得回。

![通过对话改变阅读设置](https://owlcarp.me/images/responsive/articles/understory/18-480.webp)

**自然语言请求、设置工具调用和可撤回的更新卡片。**

#### 阅读主题

至于主题，阅读器提供了浅色与深色两套界面。它们并没有不同的功能，只是让白天、夜晚或不同的阅读偏好都能得到合适的呈现。

![浅色与深色主题](https://owlcarp.me/images/responsive/articles/understory/19-480.webp)

**同一个阅读布局下的两种主题。**

---

### Wiki 页面设计

如果说 UnderStory 是“为了 Wiki 这碟醋包的饺子”，那么这部分就是那碟醋本身。

我最初的不满很具体：打开一个冷门作品的 Wiki，感兴趣的人物没有词条；换一本热门长篇，次要设定又散落在多个页面里。社区 Wiki 自有它们经过人工验证、长期维护的价值，但我想要的阅读辅助应该可以跟着自己导入的作品一起出现，不必先等有足够多的爱好者来填坑。

因此，在 UnderStory 中，用户配置好模型、选择“分析作品”，系统就会尝试从原文中生成属于这部作品的 Wiki。它不依赖网上有没有现成百科；小说被拆解成可索引的内容，多个分工明确的 Agent 再据此规划和撰写页面。实际分析规模可以达到数百万字，流程需要一定时间，原稿中的测试场景约半小时完成一次完整分析。具体怎么完成，下一部分再讲。

界面参考了 Wikipedia、萌娘百科、Wikidot 和 Fandom，但我没有直接使用一个真正的开源维基项目。UnderStory 是单机阅读器，主要是读者浏览，而非一群编辑共同维护；AI 生成的词条也有自己的引用、剧透、进度与叙事结构。比起移植庞大的社区系统，从头实现一套轻量但体验完整的 Wiki，反而更适合这里。

#### 首页与类目

它当然需要一个首页。读者有时只是想随便看看，不妨从一段作品介绍出发，点开一个专题推荐、人物卡片，或者某个分类。Wiki Architect 会为作品规划主页内容与导航，除了 Markdown 正文，还可以使用专题推荐、词条、类目与卷的链接卡片，让探索有可以随手抓住的线索。

![Wiki 主页中的推荐与导航](https://owlcarp.me/images/responsive/articles/understory/20-480.webp)

**主页并不只是词条索引，还提供简介、专题推荐、词条推荐和类目入口。**

从首页继续向下，则有针对人物、组织、地点、事件等对象的类目页。类目由模型依据作品自行组织，不强求每本书都使用同一套分类。类目页本身不急着详细介绍谁是谁，更多是在某一主题下展示可供继续阅读的词条，像图书馆里某一排书架。

![从分类进入 Wiki 类目页](https://owlcarp.me/images/responsive/articles/understory/21-480.webp)

**地点类目示例。它负责把同类词条摆到一起，让读者沿着类别继续浏览。**

#### 词条页

再往里才是具体词条。词条本身由并行的 Wiki Worker 撰写。根据词条的重要程度，留给 Wiki Worker 的思考和工具调用预算也不同。一份词条页面由标题/简介、Markdown正文、信息表、相关词条表、叙事轨迹和作品导航板块组成。

![Wiki 词条页的各个部分](https://owlcarp.me/images/responsive/articles/understory/22-480.webp)

**从正文、引用、信息表，到关联词条、剧透遮罩和导航模块，一篇 Wiki 词条所包含的内容。**

- 标题与简介：词条名称与介绍。

- 信息表：模仿常见Wiki，提供一个快速查询词条属性的结构化表格。

- 正文：标准Markdown+自定义格式（引用角标、链接、屏蔽片段等）

- 相关词条卡片：快速跳转到与当前词条相关的其他词条。

- 叙事轨迹：该词条在全书出现的标记。

- 作品导航：对整个Wiki提供结构化的概览，也可以用于跳转至其他词条。

#### 叙事轨迹

其中有个我特别喜欢的模块，叫作**叙事轨迹**。

Kindle X-Ray 可以指出一个词或人物在书中哪些地方出现过。我想再往前走一步：不仅知道“在哪里出现”，还可以一眼看清这个对象在整个故事里何时登场、何时消失、在哪里重新进入主线。它看上去有点像编曲软件中的音符轨道——一段人物弧线何时响起、停顿了多久、又在哪一卷被接续，仿佛有了可以观察的形状。

节点有三种类型：AI精选、正文引用和出场章节：

- **AI精选：**Wiki Composer主动填写的字段，描述该词条在全书中的主要事件、人物弧线等。但AI主动填写未必特别详细。

- **正文引用：**该词条Wiki引用出现的具体位置。

- **出场章节：**基于要点表和轻览反推的出场章节聚合。

由于AI在标注要点表的证据、填写精选表时，都会附加note字段，因此鼠标点击某个节点时，多数不仅显示卷/章位置，也能显示这个节点到底发生了什么。

![叙事轨迹的展开效果](https://owlcarp.me/images/responsive/articles/understory/23-480.webp)

**横跨多卷的出场记录被组织成时间轨迹；展开节点可以查看对应事件。**

#### 作品导航

每篇词条底部还有**作品导航**。它借鉴了主题 Wiki 常见的导航模板，把一组相关人物或概念整理成二维表格。与侧边目录不同，作品导航出现在词条末尾：读完一个角色以后，顺势看到同一阵营、同一组织里的其他角色，或这一类设定的全貌。传统 Wiki 的模板里常有“查·论·编”，这里既没有讨论页，也不打算让用户像编辑站点一样逐页编修，于是就把它留作纯粹的浏览地图。

![词条页末尾的作品导航](https://owlcarp.me/images/responsive/articles/understory/24-480.webp)

**用二维关系表把相关角色、组织或概念放在一起。读完当前词条，也就知道下一站可以去哪里。**

## 第二部分：模型运行架构

### 作品分析流程

前面介绍的是读者能够看到的部分。接下来的内容我会按照我的真实开发顺序梳理：初期如何设计架构，如何发现问题、后期如何修正、简化、收敛、优化。Token消耗量怎样从约40M/10卷降低到20M/10卷。

单次超长上下文虽然越来越常见，却不意味着上下文里的每个位置都同样容易被利用。更大的输入带来更高的成本，某些模型在长上下文中提取细节也不稳定；有的模型宣传的窗口很大，真正处理跨章节关系时的有效能力却未必跟得上。另一方面，纯 RAG 又容易只找回最相似的片段。对跨卷伏笔、人物转变与隐含关系来说，检索之外还需要某种长期保存的叙事记忆。关于层级式检索的 Storyline Trees 研究，也给过我这方面的启发：层级树式的检索明细比朴素RAG能获得更多信息，而更多的 chunks 边际效益非常低。

与其用 RAG 无限增加相似片段，不如让更多上下文用于保留层次与故事结构。

我最终把原文、检索索引与预先整理的结构化资料放到同一套系统中。Agent 既可以搜索原文、取回带出处的证据，也可以查询已经读过的卷章报告和公共要点。下游的 Wiki 写作不必每次从零开始，对话也不需要只盯着几段检索结果。

![原文、检索、结构化知识与 Agent 的关系](https://owlcarp.me/images/responsive/articles/understory/26-480.webp)

**原文切片和向量索引负责找证据；章节、卷摘要与公共要点提供已经整理过的知识；Agent 将两边结合起来。**

新作品导入后，需要从“分析作品”入口启动这些预处理工作。最终界面提供**质量分析**和**快速分析**两种模式，区别在于模型可使用的工作预算，方便根据时间和成本选择。

![分析作品的设置窗口](https://owlcarp.me/images/responsive/articles/understory/27-480.webp)

**对整部作品发起分析的入口，可以选择处理策略并观察进度。**

#### V1：逐章分析

这套工作流并不是第一版就长成现在这样。我最初的想法非常直观：既然想让模型认真读书，那就让它**一章一章读、先做笔记，再由更高一级的 Agent 汇总**。

V1 因此采用了“章节 Worker → 卷报告 Agent → Wiki Architect → 词条撰写 Worker”的流水线。

- **章总结Worker：**读取每一章完整内容，然后将本章内容写成章节报告。章报告是一个非常固定化的JSON结构，包含章节摘要、顺序剧情大纲、场景、实体（人物等）、事件、局部关系、设定片段、未决问题、关键细节与重要对白、叙事形式观察几种结构。

- **卷报告Agent：**每一章的报告会进入卷报告Agent。卷报告Agent有更多的调用预算，可以多轮调用，之后生成逐卷的卷总结。而卷总结也包含四个硬编码的概览、人物、事件和知识四个部分。

- **Wiki Architect：**完成所有卷总结后，Wiki Architect开始构建Wiki的骨架：首页、词条模板和词条列表。但Wiki Architect不会撰写具体的Wiki词条，而是在有限次的多轮调用中，持续调查-提交-修改，最终形成合适的Wiki骨架。

- **词条撰写Worker：**词条撰写Worker用于并行填充Wiki骨架中的具体词条。词条按照重点/标准/简要分类，并给予不同等级的工具调用预算。重点/标准词条可以以不同的轮次预算主动调取资料；简要词条则只能使用预提供的资料。

- **轻览：**由章总结Worker的摘要字段生成，为进入每一章时提供无剧透的引入。

![第一版作品分析架构](https://owlcarp.me/images/responsive/articles/understory/28-480.webp)

**V1：逐章生成报告，再按卷汇总，最后规划并撰写 Wiki。每一层都在重新阅读上游的整理结果。**

问题也正出在这里。为了让章节理解有足够的前后文，我给各章 Worker 提供了整卷内容。单章任务于是反复消耗同一卷输入，到了卷总结和词条撰写阶段，又要再次阅读大量报告。流程虽然工整，却显得很臃肿。

我用《魔女之旅》前四卷试跑过 V1。测试记录中，章节 Worker 占端到端 Token 用量的 **16.3%**；当时的成本统计还包含每百万 Token 覆盖约 5807 字、每千字约 172 Token 的记录。这套统计本身后来还值得回头统一口径，但最直观的问题已经很清楚：大量预算花在重复传送和重读相同材料上。

#### V2：逐卷分析

因此在 V2，我干脆去掉了独立的逐章 Worker，让**每一卷由一个 Agent 负责完整分析**。这个 Agent 可以直接读整卷、整理章节与卷信息，并在必要时查阅前面几卷已经留下的记忆。原先章报告与卷报告之间那一层昂贵的接力，就这样被合并了。

![第二版作品分析架构](https://owlcarp.me/images/responsive/articles/understory/29-480.webp)

**V2：以卷 Agent 为中心，借助公共要点表共享跨卷记忆，再把成果交给 Wiki 规划和撰写流程。**

V2 的卷 Agent 是一个可以多轮调查的完整工作单元。按照质量或快速档位，每卷最多可循环**4~9轮**，有多种工具可调用，且每轮鼓励批量调用工具：

- `get_knowledge_points`：批量读取相关既有要点的完整更新历史。仅第二卷及以后提供。

- `create_knowledge_points`：批量创建不存在的新要点。

- `append_knowledge_points`：为已读取的既有要点追加本卷内容，也可追加别名。

- `add_point_aliases`：批量补充要点别名。

- `search_original_text`：RAG检索原文。用于查找准确章节、段落定位和引用。

- `search_wiki`：对于已有过一次完整分析的作品，在分析新增卷时，按需检索现有 Wiki。

- `submit_volume_summary`：提交卷总结、逐章总结及非正文参考章节 ID，是最终提交工具，会终止当前工作流。

同时，我不再把作品信息强行限制在“概览、人物、事件、知识”四种栏目里。不同小说的叙事重点完全不同，奇幻作品可能有庞大的能力与阵营体系，另一部作品可能更值得追踪人物关系和阶段性事件；分类应该让模型根据作品自行确定。

词条 Worker 也不必再把大段原文和报告从头翻一遍。它先拿到与当前词条相关的公共要点，只有重要词条需要更细的证据时，才进一步回到 Wiki 或原文调查。按开发记录中的单位原文处理成本统计，V2 相比 V1 **下降了 93.9%**，相同 Token 预算可以覆盖约 **16 倍**的原文。另一次以十卷为规模的流程估算，也从原先约 **4000 万 Token** 收敛到约 **2000 万 Token**。不同测试口径下数字并不完全对应，但方向是一致的：减少重复阅读，把预算交给真正需要推理和核查的地方。

---

### 公共要点表

V2 最关键的变化，其实不是少了一个 Worker，而是多出了一张“公共要点表”。

最早我只是想用它节省 Token。后来发现，如果要让不同的 Agent 共享对一部小说的理解，它可能比几十份摘要都更重要。

想象一下，把二十卷小说压缩成二十篇卷摘要。这当然比原文短得多，却仍然是二十篇长文章。要写“希尔”的词条，模型需要重新找出所有与她有关的段落；下一次有人问“智慧之王拉斐尔”，另一个 Agent 又得在差不多的材料里搜索一遍。摘要已经浓缩了内容，却没有把内容整理成便于复用的对象。

所以我把值得长期跟踪的人物、组织、地点、道具、能力、事件，以及作品独有的概念，都视作一个个独立的“要点”。第一次遇到它的卷 Agent 负责创建；后面几卷如果这个对象再次出现，就只补充这一卷新增了什么。模型可以查阅这个要点过去的记录，再决定追加哪些信息，而不是把所有历史要点无差别塞进每次调用的上下文。

![公共要点表的跨卷增量维护](https://owlcarp.me/articles/understory/30.svg)

**两个卷 Agent 如何围绕同一个要点创建、读取和追加信息。旧记录被保留下来，新一卷只接着往后写。**

例如《转生史莱姆》的真实生成记录里，“希尔”作为人物要点被建立，此后持续接收其他卷的新增内容。每次追加都可以写明本卷变化、原文证据的位置和补充说明。这样查看一个对象时，就能沿着一串结构化的记录回看它的发展。

![公共要点表中的真实记录](https://owlcarp.me/images/responsive/articles/understory/31-480.webp)

**《转生史莱姆》的内部要点调试界面。展开一个人物，就能看到按卷追加的记录。**

这些表格不是给读者直接看的。它更像整部作品被分析后留下的中间表示：比原文短得多，比卷摘要更容易精确查询，同时又没有丢掉事件的发展顺序与原文证据。

别名也是这套记忆里重要的一部分。像“修雷·诺赞”“诺赞”“死神”这样的称呼，如果没有统一的对应关系，模型可能把同一个人物当成几个独立对象。卷 Agent 因此不仅要判断是否创建新要点，也要判断能不能沿用已有要点，或只为它补充一个新别名。有了这一层共识，阅读时选中别名能跳到正确词条，Wiki Worker 也能找到同一角色的跨卷经历；对话 Agent 则可以先查结构化记忆，再回到原文验证具体事实。

这张表从节约成本的小工具，逐渐变成了整个 UnderStory 的信息中枢。

---

### Wiki 生成架构

当每一卷的信息有了自己的去处，下一步就是：怎样把它们组织成一套真正可读的 Wiki？

现实里的大型 Wiki 不是一个人从头到尾写出来的。有人规划栏目，有人写词条，有人负责分类、模板和维护。UnderStory 也需要类似的分工。只安排一个 Agent 写完整个 Wiki，工作会又长又重，上下文很快被塞满；但直接让许多 Agent 各写各的，页面会缺乏一致结构，冷门条目也可能写得比主角还长。

我最后把 Wiki 生成拆成两个阶段：**集中规划，并行撰写**。

先由 Wiki Architect 负责整部作品的蓝图。它会查看卷报告和公共要点索引，再通过多轮检索与调查，决定首页应该展示什么、需要哪些类目与专题、哪些对象值得单独成为词条、各类词条使用什么模板，以及底部的作品导航如何分组。它还会为待写词条附上关联要点，提交完整的 Wiki 蓝图；程序会检查蓝图里的必要数据是否齐全，才让流程继续往下走。

然后才轮到 Wiki Composer，也就是一组并行工作的词条 Worker。每个 Worker 只负责一个页面，获得 Architect 为它准备的词条模板和相关要点。它可以在权限范围内查找原文或已有 Wiki，却不能随意修改别人的词条。这样既能并行加快生成速度，又能让成百上千段独立文字维持相近的页面结构和视觉语言。

![Wiki Architect 与词条 Worker 的分工](https://owlcarp.me/images/responsive/articles/understory/32-480.webp)

**Architect 先确定作品结构，再把各词条的资料和模板交给并行写作的 Worker。**

---

### 词条分级与撰写

词条的数量不能太少，也不能无限多，在Wiki Architect的提示词里，规则会根据公共要点表的数量估算作品规模，然后将预期Wiki词条数目限制在20到160个不等。

不同词条需要的资料量也不相同：主角可能横跨二十几卷，拥有复杂的人物弧线；一个只在某个单元故事中出现的地点，只需要简单的资料和少量字数的词条。如果让所有词条撰写Worker获得完全相同的上下文、轮次和工具，不仅成本很高，也会诱导模型把边缘内容写得过度膨胀。

因此 Wiki Architect 在规划词条时，也会为词条判断级别。目前分为三档：

- **核心词条（Core）：**作品的核心人物、关键能力、长期事件等。Worker拥有较高的轮次预算，可以主动读取多个公共要点、检索已有Wiki，或者用RAG回到原文寻找准确依据。

- **重要词条（Supporting）：**重要配角、主要地点、阶段性事件等。Worker仍然可以调查，不过工具和轮次比核心词条更克制。

- **局部词条（Local）：**只在较小范围内出现的人物、地点或概念。系统直接准备好相关要点与上下文，让模型在一次调用中完成写作。

早期的 Wiki 词条撰写是整个分析流程中最大的成本来源。V1 中，48个词条消耗了超过 52M Token，占端到端 Token 的近八成，平均每个词条超过一百万 Token。经过公共要点复用和分级后，最终版本生成139个词条，平均每个词条约10.7万 Token。词条数量变多了，单个词条的上下文反倒只有原来的十分之一。

---

### 对话 Agent

既然阅读器已经拥有预先整理的 Wiki、章节报告和公共要点表，对话 Agent 便不必回到最初那种“用户问题 + 几段 RAG 搜索结果”的简单模式。

我把它可用的资料分成三个层次：

- 最上层是已经组织成阅读内容的 Wiki。

- 中间是卷章摘要和公共要点这些结构化记忆。

- 最底层仍然是完整原文，用来复核事实、确定段落位置，或者在已有总结不够细时重新调查。

它可以按照问题的性质决定先访问哪一层，而不是每次都翻整本书。这些能力打包成对话Agent可调用的工具集：

- `search_wiki`：搜索Wiki词条的标题、摘要和正文。

- `get_wiki_entry`：读取指定Wiki词条的完整内容。

- `search_knowledge_points`：批量查询和读取公共要点表中的项目。

- `get_content_summaries`：批量读取卷总结或指定章节总结。

- `get_chapter_lists`：获取指定卷的章节目录、字数和正文标记。

- `search_chapter_titles`：根据标题查找章节。

- `search_original_text`：使用词法、语义或混合模式RAG检索带定位的原文切片。

- `get_full_chapter`：在确有必要时读取一个章节的完整正文。

- `get_reading_settings`：读取当前主题、字号、行距等阅读设置。

- `update_reading_settings`：按照读者的要求修改系统阅读设置选项。

- `confirm_serious_spoiler`：回答可能包含剧透时阻断并征求读者选择。

- `submit_answer`：提交最终回答。

侧栏里的阅读伙伴还会收到读者当前在第几卷、第几章的信息，以及作品目录、Wiki 词条索引和阅读设置。这一点很重要：当我指着正在读的那一段问“他为什么这样说”，它至少知道此刻的“这里”指向哪里，也能参考我设定的剧透策略来决定调查和回答的范围。

我请 Codex 对真实测试中的两类问题做过流程分析。一个问题与人物的进化、能力变化有关，Agent 先查 Wiki 和公共要点，再扩展检索相关情节，回原文核验后提交带证据的答案。另一个问题涉及跨卷人物关系，Agent 则需要先找到事件，再整理不同卷里的信息，最后回到具体章节确认。这些流程图最有意思的地方，不是模型调用了多少工具，而是不同问题让它选择了不同的查证路径。

![对话 Agent 调查人物进化问题](https://owlcarp.me/images/responsive/articles/understory/33-480.webp)

**一次实际测试：拆解问题、读取结构化资料、核对原文，再提交带引用的回答。**

![对话 Agent 调查跨卷人物关系](https://owlcarp.me/images/responsive/articles/understory/34-480.webp)

**另一种问题触发了不同的调查顺序：跨卷查找、证据归纳与最终回答。**

---

### RAG 检索与混合方案

#### 词法检索

至于最底层的原文检索，我自己也走过一段取舍过程。最开始只有词法匹配：速度快、结果很容易解释，查询独特人名、称号或一句台词时尤其好用。它一开始的表现就不错，但毕竟只理解文字表面。换成“某个人获得某种能力的那段经历”这种自然语言问题，明明原文里有答案，查询词却未必正好出现。

#### 语义检索

后来我加入了真正的语义检索。导入作品时，程序把各章切成连续的 RAG 片段，通过 Embedding 模型生成向量并写入本地 SQLite。测试时使用独立运行的 **Qwen3-Embedding-0.6B** 服务，也允许用户改用在线 Embedding API；如果嵌入服务不可用，就退回词法模式。查询的相似度计算由 Python 完成，索引直接跟着作品保存在本地，不需要为了单机阅读器再搭一套独立的向量数据库。

#### 混合检索与结果排序

最后又把两者结合起来，做成默认的**混合检索（hybrid）**。词法搜索对准确名称、特定台词很敏感；向量搜索更容易接住自然语言里的语义。两者并没有绝对的胜负。我让 Codex 用不同问法做过对比，结果很能说明这一点：

| 查询词 | 词法匹配 lexical | 语义检索 semantic | 更适合 |
| --- | --- | --- | --- |
| 希尔老师 | 前 8 条基本都准确 | 能找到相关章节，但混入泛化结果 | lexical |
| 大贤者进化成智慧之王 | 只找到第九卷的一句回顾 | 第 1 名准确找到第五卷进化现场 | semantic/hybrid |
| 大贤者 智慧之王拉斐尔 | 前几条非常准确，直接命中进化原文 | 更偏向人物关系、命名等相关场景 | lexical/hybrid |
| 就叫你希尔 指导 诞生了神智核 | 核心原文相关度第 1，但混入大量普通“指导” | 第 1 名正确找到命名场景，后部有噪声 | hybrid |
| 自然语言整句 | 0 条 | 第 1 名准确找到命名与伙伴场景 | semantic |
| 利姆路/智慧之王拉斐尔/神智核希尔 | 能找到共同出现，但不一定回答“羁绊” | 命名场景相关度第 1 | hybrid |

所以我没有简单地用向量检索替换掉原先的方法，而是允许 Agent 自己选择词法、语义或混合检索，默认采用混合方式。很多时候，换一种更准确的查询词，就比盲目增加检索片段更有效。

还有一个针对小说的小处理：系统先按相关性挑选原文片段，随后再把结果按照**卷、章、段落**的原始顺序排回来，同时保留相关性名次。一般知识问答最关注“哪个结果最像问题”，而小说是有先后顺序的。同一人物在第一卷与第十卷的状态可能完全不同；按故事发生的顺序看证据，更容易理解一件事情究竟是怎么发展到那里的。

## 后记

“有几个情节我读不懂，百科里也查不到。”

“那就问问 AI？”

“什么叫AI给你生成了一个百科？”

这是我最初的愿景。

在这个时代，知识和内容都来得太轻松了。只要打开一个聊天框，似乎任何问题都能迅速得到一段像模像样的回答。但信息仍然是从聊天框中涌出的：我问一句，那边答一段。查找、探索、链接，这些逛 Wiki 曾经带给我的乐趣，却很难在“对话”这样的形式中展开。

UnderStory 试着让辅助信息回到阅读过程本身：它可以是进入章节前的一小段轻览，是选中名字时浮现的词条，也可以是一条从第一卷延伸到最后一卷的叙事轨迹。只有读者真的想问点什么，才打开对话窗口。小说仍然在最中间，其他东西则像散落在故事周围的路径，需要的时候走进去看看。

UnderStory 已经基本实现了产品闭环，不过它目前还远没有达到一个商业产品或正经开源项目的级别。它承载了我对AI阅读、动态Wiki和长文本Harness的一整套思考，诠释了目前我对“AI原生产品”的理解。

[完整的项目仓库](https://github.com/GVD20/UnderStory)已经公开，你可以拉取、配置API后亲自体验，希望你能喜欢。

至少对我来说，它让当年翻开 X-Ray 时的那个念头，终于有了一种可以亲手使用的答案。

## 素材署名

示例作品：[《86－不存在的战区－》](https://dengekibunko.jp/product/86/321611000284.html) · 安里アサト；[《关于我转生变成史莱姆这档事》](https://gcnovels.jp/book/2) · 伏瀬；[《猫的地球仪》](https://dengekibunko.jp/product/neko-earth/) · 秋山瑞人。

背景摄影：[Jonathan Cooper](https://www.pexels.com/@theshuttervision/)；[Pexels 原图](https://www.pexels.com/photo/photo-of-a-green-leaves-12568124/)；[Pexels License](https://www.pexels.com/legal-pages/license/)。
