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。在林业中,understory 指的是森林树冠下方的植被层。我选择这个名字,因为它恰好契合了我对这款阅读器的想象:一切从故事本身出发,也围绕故事生长。它首先是一款可以安静地阅读轻小说的阅读器;而在需要的时候,AI 能从原文中梳理人物、事件与世界观,让那些散落在章节之间、读过却未必记得住的细节变得清晰。
Logo 也延续了这个想法:一棵小树从摊开的书中生长出来,根系扎进书页,枝叶向上舒展。由 AI 生成的 Wiki、人物关系和其他辅助内容,就像这棵树逐渐长出的枝叶,从整部作品中汲取养分,又始终与原文相连。读者可以沿着枝叶探索故事的更多细节,也可以只是坐在树下,继续读手里的书。
第一部分:产品设计
基础板块
UnderStory 的产品形态并不是先写好一份需求文档,再照着逐条实现的。我一边 Vibe Coding,一边把自己放到实际阅读的场景中,不断调整界面与交互。最后逐渐收敛成三个相对独立、又能够彼此衔接的板块:阅读、Wiki 和对话。
阅读
阅读首先得是一件舒服的事。所以最基础的仍然是一个正常的 EPUB 阅读器:左边是按卷和章节展开的目录,像文件管理器一样方便定位;中间是一整块可以连续滚动的正文;右边留给辅助信息;底部则有阅读进度。字体、字号、行距、插图与部分高级排版都能按需调整。我希望一个人即使完全不使用 AI,也能够坐下来好好读完一本书。
Wiki
相比之下,Wiki 才是最初真正让我想做这个项目的东西。每导入一部作品,UnderStory 都可以先分析原文,为它生成独立的 Wiki:有首页、目录、分类、专题和具体词条。读者进入这里,不需要先提出问题,只要循着自己感兴趣的地方一路点下去即可。
我沿用了人们熟悉的 Wiki 阅读方式:左侧的大纲,右侧的词条内容;页面上有信息表、结构化正文、相关词条、作品导航,以及能够跳回原文的引用。不过底层并没有嵌入 MediaWiki 之类完整的维基系统。它们为社区协作和内容编辑准备了庞大的功能,而我更需要一个能承载 AI 生成结构、供读者浏览的轻量空间。于是索性自己实现了一套“看起来、用起来都像 Wiki”的界面。
对话
第三个板块是对话。如果我已经知道自己想问什么,聊天当然最直接:Wiki 中没有解释清楚的关系、某段情节的疑问,都能拿来问。只不过这里的对话并非独立悬浮在阅读器之外的助手。它可以调用原文引用、Wiki 词条卡片和跳转链接,也能请求创建专题、补充词条,甚至直接修改阅读器设置。
对话界面本身也经历过一次改动。最初我采用类似短信应用的回合式气泡:我问一句,模型回一条。但用下来发现,读者往往是在认真阅读模型给出的一段内容,而不是像聊天软件那样快速互发消息。于是我改用接近 ChatGPT 的全宽回答排版,让标题、段落、粗体、列表和引用成为内容本身的组成部分。
等待过程也做了一些处理。Agent 调查问题、调用工具时,哪怕模型已经很快,读者仍可能等上数十秒。现在它会先给出一条简短的即时回应,再逐步显示工具调用和计时,让人知道这段等待正在发生什么。回答中还可以按需出现 Wiki 卡片、章节跳转、原文引用块、表格或叙事轨迹,点击即可回到相应内容。
三个板块之间的关系很清晰和简洁:想读书时就读书,想随便逛逛就打开 Wiki,遇到明确的问题才去对话。不会全部挤在同一个界面里,也不必一同争夺读者的注意力。
阅读侧栏
当然,真正读书时最常见的情况,是正读到某句话,突然想确认一个名字或设定。这时专门切到 Wiki,再找到词条,又回到原文,还是有点麻烦。
于是阅读页面右侧保留了一个多功能侧栏,里面有四种视图:
- 轻览会为每章生成一段尽量不剧透的前情提示和内容引入,并列出本章相关的词条快捷卡片。
- Wiki 和 对话则把对应板块直接带到正文旁,方便一边读一边查。
- 搜索整合原文 RAG、Wiki 词条与章节检索。
不管我是在回忆上一章的情节,还是想问正在读的这一段,都不必离开阅读页面。
原文选中与快速查询
我还做了一个更顺手的动作:直接选中文字。比如一段对话中突然提到了早已忘记的角色,我只要选中人名,弹窗便会找出可能对应的词条,点一下就能查看;如果我关心的是这句话本身,还能把选中的段落附给阅读伙伴,让它围绕这段文字回答。
这里并不只是简单地把选中文字与词条名逐字比较。小说里的称呼很复杂:人物可能有全名、姓氏、绰号与称号,《86》中的“修雷·诺赞”“诺赞”“死神”就可能指向同一个角色。仅凭标题完全匹配,很多读者真正选中的词反而会漏掉。
所以弹窗会同时提供准确匹配的词条和相关词条推荐,并在别名匹配时标明它对应的正式名称。文本里可识别的关键词也会相应高亮。支撑这件事的别名信息,并不是为弹窗单独写的一套规则,而是后面作品分析阶段积累的“公共要点表”中的数据。
阅读进度条
小说本身是一条不断向前的时间线。为了让读者在这条线上有方位感,我还在阅读区域底部加了一条进度栏,同时显示全书与当前卷两个尺度。动辄二十多卷的作品,如果只在一条横线上排满章节,很难看清自己究竟到了哪里;把全书与单卷分开,既能知道整体还剩多远,也能知道这一章大概读到了什么位置。
它还会和 Wiki 的叙事轨迹联动:当侧栏打开某个词条,进度栏会标出这个对象出现过的章节。于是“这个人物后面还有戏份吗”“某个概念究竟贯穿了多少卷”,不仅能在百科里读到,也能直接在阅读进度上看到分布。
反剧透
做到这里,一个很微妙的问题出现了:UnderStory 的 Wiki 正是因为提前分析了整部作品,才有机会把人物和故事讲清楚。但对尚未读完的读者来说,这些知识也可能毁掉阅读体验。
我不想让 Wiki 变成一本把结局写在第一页的答案册。章节轻览从生成时就被要求“只引导,不剧透”;Wiki 与对话则分别设计了不同的防剧透交互。
Wiki 的剧透屏蔽
Wiki 中最常见的情况,是某个词条前半部分可以放心阅读,后半部分却涉及人物未来的命运。我参考了萌娘百科等站点常见的遮罩方式,用色块盖住可能剧透的段落。读者知道这里还有内容,但不会在无意间直接看到。
这些遮罩不是前端靠关键词猜出来的。词条撰写 Agent(Wiki Composer)生成内容时,会在结构化输出中标记哪些片段属于剧透,并尽可能附上 unlock_after_chapter_id,也就是读到哪一章以后,这段信息可以解锁。引用跳转、段落定位和剧透规则都包含在同一份 Wiki 数据结构里。
有了章节条件,遮罩就不必永远停在原地。系统可以跟踪读者的实际进度:当我已经读过对应情节,这段文字对我而言就不再算剧透,可以自动显示出来。
当然这是可调整的,取决于读者个人的接受能力:
- 关:始终显示所有剧透。
- 自动:阅读达到指定章节后自动解锁指定剧透屏蔽。
- 严格:始终屏蔽任何剧透内容。同时提供更多严格的屏蔽特性,有可能影响 Wiki 使用体验。
对于单段屏蔽内容,也允许读者主动点击临时解锁。
还有一种更棘手的词条:事件的标题或简介本身就是剧透。此时只遮住正文已经来不及了,因此 Wiki Composer 也可以把整个词条标记为剧透。阅读器会锁定整页、模糊页面内容,并遮住目录中的词条名称,尽量避免读者仅仅扫过导航就提前知道了某件事。
对话的剧透确认
对话的处理方式则不同。读者可能主动问“这个人最后怎么样了”,也可能只想问“他来自哪里”。同样访问过全文的 Agent,没法仅凭一个问题就断定我是否愿意接受剧透。于是它在调查到相关信息、准备回答前,可以调用一个确认工具,先中断生成,然后询问读者意见。
阻断工具会提供三种选择:
- 忽略警告,提供完整回答:认为读者接受剧透。模型会根据真实作品信息完整回答。
- 回答,但尽量减少剧透:将“尽量减少剧透”要求注入提示词中,模型会被要求修改回答方式,减少剧透。
- 取消并停止回答:停止当前回答。
我希望这些机制最终让阅读器既有“知道全书”的能力,也懂得在读者尚未走到某个地方时,先把答案留在那里。
阅读体验与设置
UnderStory 的大部分设计都围绕 Wiki 和 AI 展开,但我平时真正面对它的时间,仍然是在读一行行文字。阅读体验如果不舒服,再丰富的百科也只会变成一个偶尔打开的功能。
阅读设置因此覆盖了字号、行距、EPUB 样式、插图显示、浅色与深色主题,以及前面提到的剧透策略。设置以模态窗口弹出;因为弹窗会暂时遮住正文,我在窗口里加了一块实时预览,让调整字号或行距时不用反复关闭设置再看效果。
EPUB 排版
其中我花了不少心思的是 EPUB 样式。EPUB 本质上使用网页相关技术组织内容,但不同书籍的排版质量差别很大。有些制作精良,包含精细的段落、字号与图片布局;有些却带着凌乱的 CSS,换一个阅读器就会变得难看。也有人只是喜欢所有书都维持简单、统一的排版。
我提供了三个显示档位。关闭时只保留基础换行,不沿用原书样式;基础保留粗体、斜体、下划线、上下标、引用等明确的语义格式;高级则进一步应用 EPUB 内嵌与外部 CSS,包括布局、颜色和局部字体大小等。书籍导入时会先解析、清洗样式,阅读时再根据选项决定采用到什么程度。
插画显示
插画则有另一个细节。我的浅色阅读界面偏暖白,普通轻小说的黑白插画却往往带着纯白底色,嵌进页面就会像贴了一块亮白纸。于是我做了“沉浸式插画”选项:将图片以正片叠底的方式与页面融合。对黑白插图来说,这通常能保留线条的可读性,同时减少画面与纸张背景之间突兀的色差。
自然语言调整设置
还有一个来自“对话即系统”想法的小功能:我可以直接对阅读伙伴说“把字号调大一点”“行距再松一些”“改变插画显示方式”。Agent 通过读取设置和更新设置两个工具,修改字号、行距、图片模式、EPUB 特性或主题,并把改动整理成一张可操作的卡片。
这张卡片上有撤回入口。我觉得这个细节很重要:某些排版习惯是在读了很久以后才慢慢调到合适的,一句自然语言虽然方便,但不该让人忘记原来的数字、又费力找回去。修改应当看得见,也应当退得回。
阅读主题
至于主题,阅读器提供了浅色与深色两套界面。它们并没有不同的功能,只是让白天、夜晚或不同的阅读偏好都能得到合适的呈现。
Wiki 页面设计
如果说 UnderStory 是“为了 Wiki 这碟醋包的饺子”,那么这部分就是那碟醋本身。
我最初的不满很具体:打开一个冷门作品的 Wiki,感兴趣的人物没有词条;换一本热门长篇,次要设定又散落在多个页面里。社区 Wiki 自有它们经过人工验证、长期维护的价值,但我想要的阅读辅助应该可以跟着自己导入的作品一起出现,不必先等有足够多的爱好者来填坑。
因此,在 UnderStory 中,用户配置好模型、选择“分析作品”,系统就会尝试从原文中生成属于这部作品的 Wiki。它不依赖网上有没有现成百科;小说被拆解成可索引的内容,多个分工明确的 Agent 再据此规划和撰写页面。实际分析规模可以达到数百万字,流程需要一定时间,原稿中的测试场景约半小时完成一次完整分析。具体怎么完成,下一部分再讲。
界面参考了 Wikipedia、萌娘百科、Wikidot 和 Fandom,但我没有直接使用一个真正的开源维基项目。UnderStory 是单机阅读器,主要是读者浏览,而非一群编辑共同维护;AI 生成的词条也有自己的引用、剧透、进度与叙事结构。比起移植庞大的社区系统,从头实现一套轻量但体验完整的 Wiki,反而更适合这里。
首页与类目
它当然需要一个首页。读者有时只是想随便看看,不妨从一段作品介绍出发,点开一个专题推荐、人物卡片,或者某个分类。Wiki Architect 会为作品规划主页内容与导航,除了 Markdown 正文,还可以使用专题推荐、词条、类目与卷的链接卡片,让探索有可以随手抓住的线索。
从首页继续向下,则有针对人物、组织、地点、事件等对象的类目页。类目由模型依据作品自行组织,不强求每本书都使用同一套分类。类目页本身不急着详细介绍谁是谁,更多是在某一主题下展示可供继续阅读的词条,像图书馆里某一排书架。
词条页
再往里才是具体词条。词条本身由并行的 Wiki Worker 撰写。根据词条的重要程度,留给 Wiki Worker 的思考和工具调用预算也不同。一份词条页面由标题/简介、Markdown正文、信息表、相关词条表、叙事轨迹和作品导航板块组成。
- 标题与简介:词条名称与介绍。
- 信息表:模仿常见Wiki,提供一个快速查询词条属性的结构化表格。
- 正文:标准Markdown+自定义格式(引用角标、链接、屏蔽片段等)
- 相关词条卡片:快速跳转到与当前词条相关的其他词条。
- 叙事轨迹:该词条在全书出现的标记。
- 作品导航:对整个Wiki提供结构化的概览,也可以用于跳转至其他词条。
叙事轨迹
其中有个我特别喜欢的模块,叫作叙事轨迹。
Kindle X-Ray 可以指出一个词或人物在书中哪些地方出现过。我想再往前走一步:不仅知道“在哪里出现”,还可以一眼看清这个对象在整个故事里何时登场、何时消失、在哪里重新进入主线。它看上去有点像编曲软件中的音符轨道——一段人物弧线何时响起、停顿了多久、又在哪一卷被接续,仿佛有了可以观察的形状。
节点有三种类型:AI精选、正文引用和出场章节:
- AI精选:Wiki Composer主动填写的字段,描述该词条在全书中的主要事件、人物弧线等。但AI主动填写未必特别详细。
- 正文引用:该词条Wiki引用出现的具体位置。
- 出场章节:基于要点表和轻览反推的出场章节聚合。
由于AI在标注要点表的证据、填写精选表时,都会附加note字段,因此鼠标点击某个节点时,多数不仅显示卷/章位置,也能显示这个节点到底发生了什么。
作品导航
每篇词条底部还有作品导航。它借鉴了主题 Wiki 常见的导航模板,把一组相关人物或概念整理成二维表格。与侧边目录不同,作品导航出现在词条末尾:读完一个角色以后,顺势看到同一阵营、同一组织里的其他角色,或这一类设定的全貌。传统 Wiki 的模板里常有“查·论·编”,这里既没有讨论页,也不打算让用户像编辑站点一样逐页编修,于是就把它留作纯粹的浏览地图。
第二部分:模型运行架构
作品分析流程
前面介绍的是读者能够看到的部分。接下来的内容我会按照我的真实开发顺序梳理:初期如何设计架构,如何发现问题、后期如何修正、简化、收敛、优化。Token消耗量怎样从约40M/10卷降低到20M/10卷。
单次超长上下文虽然越来越常见,却不意味着上下文里的每个位置都同样容易被利用。更大的输入带来更高的成本,某些模型在长上下文中提取细节也不稳定;有的模型宣传的窗口很大,真正处理跨章节关系时的有效能力却未必跟得上。另一方面,纯 RAG 又容易只找回最相似的片段。对跨卷伏笔、人物转变与隐含关系来说,检索之外还需要某种长期保存的叙事记忆。关于层级式检索的 Storyline Trees 研究,也给过我这方面的启发:层级树式的检索明细比朴素RAG能获得更多信息,而更多的 chunks 边际效益非常低。
与其用 RAG 无限增加相似片段,不如让更多上下文用于保留层次与故事结构。
我最终把原文、检索索引与预先整理的结构化资料放到同一套系统中。Agent 既可以搜索原文、取回带出处的证据,也可以查询已经读过的卷章报告和公共要点。下游的 Wiki 写作不必每次从零开始,对话也不需要只盯着几段检索结果。
新作品导入后,需要从“分析作品”入口启动这些预处理工作。最终界面提供质量分析和快速分析两种模式,区别在于模型可使用的工作预算,方便根据时间和成本选择。
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的摘要字段生成,为进入每一章时提供无剧透的引入。
问题也正出在这里。为了让章节理解有足够的前后文,我给各章 Worker 提供了整卷内容。单章任务于是反复消耗同一卷输入,到了卷总结和词条撰写阶段,又要再次阅读大量报告。流程虽然工整,却显得很臃肿。
我用《魔女之旅》前四卷试跑过 V1。测试记录中,章节 Worker 占端到端 Token 用量的 16.3%;当时的成本统计还包含每百万 Token 覆盖约 5807 字、每千字约 172 Token 的记录。这套统计本身后来还值得回头统一口径,但最直观的问题已经很清楚:大量预算花在重复传送和重读相同材料上。
V2:逐卷分析
因此在 V2,我干脆去掉了独立的逐章 Worker,让每一卷由一个 Agent 负责完整分析。这个 Agent 可以直接读整卷、整理章节与卷信息,并在必要时查阅前面几卷已经留下的记忆。原先章报告与卷报告之间那一层昂贵的接力,就这样被合并了。
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 负责创建;后面几卷如果这个对象再次出现,就只补充这一卷新增了什么。模型可以查阅这个要点过去的记录,再决定追加哪些信息,而不是把所有历史要点无差别塞进每次调用的上下文。
例如《转生史莱姆》的真实生成记录里,“希尔”作为人物要点被建立,此后持续接收其他卷的新增内容。每次追加都可以写明本卷变化、原文证据的位置和补充说明。这样查看一个对象时,就能沿着一串结构化的记录回看它的发展。
这些表格不是给读者直接看的。它更像整部作品被分析后留下的中间表示:比原文短得多,比卷摘要更容易精确查询,同时又没有丢掉事件的发展顺序与原文证据。
别名也是这套记忆里重要的一部分。像“修雷·诺赞”“诺赞”“死神”这样的称呼,如果没有统一的对应关系,模型可能把同一个人物当成几个独立对象。卷 Agent 因此不仅要判断是否创建新要点,也要判断能不能沿用已有要点,或只为它补充一个新别名。有了这一层共识,阅读时选中别名能跳到正确词条,Wiki Worker 也能找到同一角色的跨卷经历;对话 Agent 则可以先查结构化记忆,再回到原文验证具体事实。
这张表从节约成本的小工具,逐渐变成了整个 UnderStory 的信息中枢。
Wiki 生成架构
当每一卷的信息有了自己的去处,下一步就是:怎样把它们组织成一套真正可读的 Wiki?
现实里的大型 Wiki 不是一个人从头到尾写出来的。有人规划栏目,有人写词条,有人负责分类、模板和维护。UnderStory 也需要类似的分工。只安排一个 Agent 写完整个 Wiki,工作会又长又重,上下文很快被塞满;但直接让许多 Agent 各写各的,页面会缺乏一致结构,冷门条目也可能写得比主角还长。
我最后把 Wiki 生成拆成两个阶段:集中规划,并行撰写。
先由 Wiki Architect 负责整部作品的蓝图。它会查看卷报告和公共要点索引,再通过多轮检索与调查,决定首页应该展示什么、需要哪些类目与专题、哪些对象值得单独成为词条、各类词条使用什么模板,以及底部的作品导航如何分组。它还会为待写词条附上关联要点,提交完整的 Wiki 蓝图;程序会检查蓝图里的必要数据是否齐全,才让流程继续往下走。
然后才轮到 Wiki Composer,也就是一组并行工作的词条 Worker。每个 Worker 只负责一个页面,获得 Architect 为它准备的词条模板和相关要点。它可以在权限范围内查找原文或已有 Wiki,却不能随意修改别人的词条。这样既能并行加快生成速度,又能让成百上千段独立文字维持相近的页面结构和视觉语言。
词条分级与撰写
词条的数量不能太少,也不能无限多,在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 则需要先找到事件,再整理不同卷里的信息,最后回到具体章节确认。这些流程图最有意思的地方,不是模型调用了多少工具,而是不同问题让它选择了不同的查证路径。
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原生产品”的理解。
完整的项目仓库已经公开,你可以拉取、配置API后亲自体验,希望你能喜欢。
至少对我来说,它让当年翻开 X-Ray 时的那个念头,终于有了一种可以亲手使用的答案。
