菜鸟AI - 让提示词生成更简单! 全站导航 全站导航
AI工具安装 新手教程 进阶教程 辅助资源 AI提示词 热点资讯 技术资讯 产业资讯 内容生成 模型技术 AI信息库

已有账号?

首页 > AI教程 > 我的2025年阅读器零幻觉问答实现指南:深度原理、对比测评与推荐排行榜
进阶教程 深度原理

我的2025年阅读器零幻觉问答实现指南:深度原理、对比测评与推荐排行榜

2026-06-01
阅读 0
热度 0
作者 菜鸟AI编辑部
摘要

摘要

基于书籍原文的零幻觉问答通过三个阶段实现:全文直塞、LLM提取关键句、片段索引结合工

令狐兄(Foxycape)是一款 AI 阅读器。本文拆解其 **零幻觉问答** 的工程实现:回答严格锚定当前书籍原文,关键结论可 **一键溯源** 到具体段落。如果你也在做 AI 阅读、文档 QA 或 RAG 类应用,希望这三次迭代的经验与最终架构能提供可落地的参考。


## 一、实战复盘:三个阶段的迭代 零幻觉问答并非一开始就设计得滴水不漏。它是在**成本、延迟和准确率**这三项硬指标的反复挤压下,逐步演化成现在的形态。按时间线回顾这三个阶段,你就能理解为什么最终架构是这样了。 ```mermaid flowchart LR P1[阶段一:全文直塞] --> P2[阶段二:LLM 提取关键句] P2 --> P3[阶段三:片段索引 + Tool 检索] P1 -.->|慢、贵、长书不准| X1[淘汰] P2 -.->|丢细节、仍偏慢| X2[淘汰] P3 -->|当前方案| OK[零幻觉 + 可溯源] ``` ### 阶段一:全文直塞 Context(最简单,但最先暴露短板) **做法**:用户打开一本书提问题,直接把提取出的**全部正文**塞进 System Prompt 或 User 消息里,让模型自行作答。如果全书超过大约 **40 万字符**,则**硬截断**——只保留前面一段,后面的章节模型就看不到了。 **优点**: - 实现成本极低,几乎不需要预处理; - 对于短书、结构简单的文档效果尚可——模型确实「看到了整本书」; - 交互简单:问就答,没有那种「正在分析」的尴尬等待。 **缺点**(很快暴露,且越来越致命): - **响应慢**:每次提问都要将海量文本喂给模型,首 Token 延迟和总耗时随书长线性增长; - **Token 成本高**:同一本书,每问一次就得重复支付全书的输入费用; - **长书严重失真**:超过 40 万字符就被截断,后半本、附录、结论章节等于不存在,且 UI 上往往**没有明确告知**用户内容被截断; - **检索粒度为零**:模型需在几十万字里「大海捞针」,细节极易遗漏,更容易产生**看似合理、实则无据**的概括——这在阅读场景里是最致命的幻觉。 阶段一适合验证 MVP,但作为产品级方案,显然扛不住。 ### 阶段二:用轻量 LLM 提取关键句(压缩了,但拧得太干) **做法**:在提问前(或首次打开书时),用一个**成本更低的模型**对正文做一轮预处理:按 Spine 分章(或整书分段),抽取**关键句**,输出时带上 `[f文件-起始-结束]` 这样的位置标记,再将摘录拼成一段较短的文本,作为后续问答的 Context。 典型链路是 **Extract → Cache → Chat**:先离线或按需跑一遍提取并落库,之后每次提问都复用这份「关键句合集」。这和许多文档 QA 原型中「先压缩文档、再拿压缩结果做 QA」的思路一致,也是我们阶段二实际采用的路线。 **优点**: - 每次提问送入模型的文本**明显缩短**,单次 Token 消耗比阶段一显著下降; - 预处理结果可以缓存,同一本书不必每次提问都重新提取; - 已引入位置标记,为后续溯源打下基础。 **缺点**(在长书场景下依然扛不住): - **细节大量丢失**:「关键句」是模型主观筛选的,论证链上的限定条件、反例等容易被丢掉,答案容易看着「正确但片面」; - **长书 Context 仍然偏大**:大部头作品即便只留关键句,拼接后的输入依然可观,**延迟和成本只是缓解了,并未根治**; - **双重 LLM 误差**:提取阶段可能漏选,问答阶段又可能误读摘录,错误会**叠加**; - **静态 Context**:不论用户问的是某一章的细节还是全书的框架,送进模型的都是**同一份预提取文本**,无法根据问题动态收窄范围。 这一阶段给我们的教训很明确:**问题不在于「有没有压缩」,而在于「压缩是不是按需的,以及能不能回溯到原文」**。 ### 阶段三:片段索引 + Tool 按需检索 + 原文回传(当前方案) **做法**:基本思路参考了 PageIndex,相对于阶段二,核心变化有三点: 1. **预处理产物是结构化索引**(目录级摘要 + 精确字符 span),而非直接把摘录当成问答 Context; 2. **每次提问由模型通过 Tool Calling 按需检索**,再**拉取带位置标记的原文**来作答; 3. **System Prompt 与前端联动**,约束引用格式,并支持点击角标跳转、高亮原文。 **三阶段对比**: | 维度 | 阶段一(全文直塞) | 阶段二(关键句提取) | 阶段三(当前) | |------|-------------------|---------------------|----------------| | 单次提问 Context | 全书(或截断后的前半本) | 预提取关键句合集 | 仅与问题相关的少量 **原文** 片段 | | 长书准确性 | 超 40 万字符后严重下降 | 依赖提取质量,易丢细节 | 按目录/span 检索,不受全书长度硬截断 | | 响应速度 | 慢 | 略好,长书仍慢 | 检索 + 短 Context,明显更快 | | Token 成本 | 极高 | 中等偏高 | 预处理摊销 + 按需付费 | | 溯源能力 | 弱(难标注出处) | 有位置标记,但内容已是二次筛选 | 角标对应 **真实原文 span** | | 工程复杂度 | 低 | 中 | 高 | **为什么停在阶段三**:阅读场景下的零幻觉,关键不在于「让模型看过尽量多的字」,而在于**「作答前必须拿到与问题相关的原文证据」**。阶段一、二都是在 Context **体积**上做文章;阶段三把链路拆成**「索引(预处理)→ 检索(Tool)→ 取证(原文)→ 作答(约束生成)」**,才同时兼顾了准确率、成本与可溯源性。 下文展开 **阶段三** 的实现细节。 --- ## 二、问题定义:阅读场景下,幻觉比普通 Chat 更致命 普通 ChatBot 偶尔犯个错,用户往往还能忍。但在**书籍 QA** 里,幻觉的代价要高得多: - 用户问的是**这本书**说了什么,不是问模型的 parametric memory; - 一句似是而非的「书中观点」,可能误导笔记、引用甚至二次传播; - 没有出处,用户没法核实,产品的信任就很难建立起来。 所以,「零幻觉」在工程上落地为三条**可执行**的规则: 1. **书内问题必须先查书**:任何可能与当前书籍相关的问题,模型必须先走检索(Tool),再组织答案; 2. **答案必须可溯源**:关键结论附带原文位置标记,前端可以解析并跳转高亮; 3. **查不到就说查不到**:书中没有的内容要明确告知,而不是用通用知识来冒充「书中观点」。 下文按 **阶段三** 的数据流,具体说明这些规则是如何落地的。 --- ## 三、整体架构:预处理 → 工具检索 → 约束生成 → 可点击溯源 ```mermaid flowchart TB subgraph prep [离线/首次预处理] A[按目录或长度切分全书] --> B[LLM 生成片段摘要] B --> C[本地持久化 Segment 缓存] end subgraph ask [用户提问] D[用户输入问题] --> E{已有 Segment 缓存?} E -->|否| F[提取全文 / 询问是否预处理] F --> prep E -->|是| G[注册 Tool Calling] end subgraph retrieve [工具检索] G --> H{问题类型} H -->|全书概览/书评| I[get_full_book_segment_summaries] H -->|具体事实/人物/章节| J[get_related_segment_summaries] J --> K[LLM 从摘要目录中选相关片段 ID] K --> L[按 span 拉取原文 + 位置标记] I --> M[拼接全书片段摘要] end subgraph answer [生成与展示] L --> N[Tool 结果回传模型] M --> N N --> O[System Prompt 约束引用格式] O --> P[流式输出答案 + 位置角标] P --> Q[渲染可点击引用角标] Q --> R[点击 → 预览原文 → 跳转高亮] end ``` 核心思路可以概括为:**不让模型「凭记忆答题」,而是让它「先取证、再作答、并标注出处」**。 ---

四、预处理:把整本书变成可检索的「片段索引」

如果每次提问还沿用 **阶段一** 的全文 Context,长书必然会爆 Token,检索粒度也过粗。阶段三的解法是:用户第一次对某本书发起 AI 对话时,后台异步跑一个**片段摘要任务**,按**目录结构**或**文本长度**将全书切成若干 `Segment`,为每个片段生成摘要,并持久化到本地 IndexedDB 里。 每个 `Segment` 在数据结构上包含摘要与**正文物理位置**: | 字段 | 含义 | |------|------| | `startFileIndex` / `endFileIndex` | Spine 文件索引(PDF 则每页一个文件) | | `startOffset` / `endOffset` | 字符级起止偏移 | | `sequence` | 线性阅读顺序 | | `title` | 对应目录标题 | 切分策略上要兼顾精确度与处理成本:单目录正文不超过约 20KB 时只总结该节点;同级目录会合并成一批(15KB~20KB)再调用 LLM;没有目录的大块正文则按 3~4 万字符区间来切段。 摘要生成时的 System Prompt 会要求**保留原文位置标记**(格式 `[f数字-数字-数字]`),这样后续 Tool 回传原文时,位置信息才能与 spine 字符偏移保持一致。核心约束如下: ```text 如果总结内容与原文某段相关,须保留段末位置信息,格式 [f数字-数字-数字](如 [f1-90-109])。 位置标记是整体,禁止修改、合并或省略其中的任何字符或数值。 ``` 预处理完成后,问答就不再依赖「整书 Context」了,而是依赖**结构化片段索引**——这是长书场景下实现零幻觉的工程前提。 ---

五、位置标记体系:把「出处」编码进文本

零幻觉不仅要求内容来自原文,还要求**出处可以被机器解析、可以在 UI 中跳转**。我们采用内联位置标记: ``` [f{fileIndex}-{startChar}-{endChar}] ``` 例如 `[f5-123-165]` 表示:第 5 个 Spine 文件(从 0 起算)中,字符偏移 123~165 的文本区间。 ### 5.1 标记如何写入正文 正文提取层在输出片段时,为每个小段在段末写入 `[f{fileIndex}-{start}-{end}]`。示意: ```typescript const position = `[f${fileIndex}-${absOffset}-${absOffset + segment.length}]`; fileLines.push(segment.text.trim() + position); ``` 无论是预处理摘要还是 Tool 回传的原文摘录,位置信息都必须与**Spine 字符偏移**对齐,而不是让模型去「估算页码」。 ### 5.2 对模型输出的约束 在组装 System Prompt 时,我们单独约定了 **[Position Citation Rules]**,核心五条: 1. **标准格式**:必须使用 `[f_fileIndex-startChar-endChar]`,三段数字缺一不可; 2. **只引用当前来源**:角标必须**原样复制**自本轮 System/User 消息或 Tool 返回文本中的标记; 3. **禁止伪造**:不得自行计算、修改或编造位置; 4. **宁缺毋滥**:当前上下文没有合法标记时,正常作答即可,**不要输出任何位置标记**; 5. **紧跟论述**:标记要紧跟相关句段,禁止在文末堆砌引用清单。 前端展示前还会过滤掉模型偶尔输出的**两段位**非法标记(如 `[f1-293]`),避免无效角标进入 UI。 ![引用溯源弹窗](https://developer.qcloudimg.com/http-sa ve/yehe-3853432/8799c4aa19ad1e1e17a1d56fcce2a8f5.png) ---

六、Tool Calling:先检索,再回答

当对话绑定某本书(存在 `resourceId`,且 `chatType === 'chat'`)时,每次生成前会向模型注册两个 Tool,并挂载对应的 executor。整体遵循 OpenAI 兼容的 **function calling 循环**。 ### 6.1 `get_related_segment_summaries` —— 针对具体问题查片段 适用于:概念、人物、情节、章节细节等**有明确检索意图**的问题。 流程简述: 1. 模型将用户的自然语言**改写为书中可能出现的术语**(System Prompt 中的「Optimize Search Queries」); 2. 调用 Tool,传入 `question`; 3. 将所有片段摘要按 Token 预算**分批**(单批约 3 万 Token,最多 5 批); 4. 每批发起一次**独立的 LLM 请求**,从 `{ id, title, summary }` 列表中选出相关片段 ID(最多 5 个),返回 JSON,形如 `{"Thinking":"...","answer":["1","3"]}`; 5. 根据选中的 Segment 的 span,从 Spine**拉取带位置标记的原文**(不是摘要),作为 Tool 结果回传。 **关键设计:Tool 回传原文,而不是摘要。** 模型作答时看到的是真实段落 + 内联 `[f…]`,避免了「摘要 → 再概括」带来的信息漂移。 ### 6.2 `get_full_book_segment_summaries` —— 全书概览类问题 适用于:「总结全书」「点评这本书」「整体结构/主题」等**需要全局视野**的问题。 按阅读顺序拼接所有片段的 `summary` 回传,避免逐段相关度筛选遗漏关键章节。 ### 6.3 System Prompt:书优先、工具优先 绑定书籍时,System Prompt 会注入 **[Core Principles for Reading Assistant]**,核心三条: ``` 1. Book First, Tool First - 任何可能与书籍相关的问题,必须先调用工具检索; - 答案必须主要依据检索结果,禁止不检索就编造「书中内容」。 2. General Knowledge as Fallback Only - 仅当:纯闲聊 / 用户明确要求不用书 / 工具无结果时,才可使用通用知识; - 若书中没有,必须先声明「书中未提及此内容」,再补充通用知识。 3. Direct Style - 直入主题,禁止「根据提供的材料…」「综上所述…」等套话。 ``` 生成层实现的是标准的 Tool 循环:`tool_calls` → 执行 executor → 追加 `role: tool` → 继续请求,直到输出最终文本。启用 tools 时关闭 thinking 通道,避免与 function call 协议冲突。 ---

七、前端溯源:从角标到原文高亮

模型输出的 `[f5-123-165]` 不会直接展示,在渲染层会转为可点击的引用。 ### 7.1 角标渲染 展示前将位置标记规范化为 Markdown 链接,例如 `[1]([f5-123-165])`,再渲染为序号角标;同一位置多次出现时可以去重,避免 UI 上堆叠。 ### 7.2 点击交互 1. **首次点击**:解析 `[f…]` → 取 fileIndex 与字符偏移 → 从 Spine 原文提取文本 → 弹出预览(可带目录标题); 2. **再次点击同一角标**:关闭弹窗; 3. **确认跳转**:打开阅读视图,按字符区间高亮。 从模型复制的标记到用户看到的原文,中间**不经过 LLM 二次加工**,溯源链路全程**确定、可复现**。 ---

八、边界情况与诚实降级

零幻觉不等于「永远有答案」,而是**没有证据时不瞎编**: | 场景 | 行为 | |------|------| | 片段摘要尚未生成 | 先提取全文做摘要 | | Tool 检索无结果 | 返回 `(No relevant segment excerpts found…)`,模型应声明书中未提及 | | 模型输出了非法两段位标记 | 前端过滤,不展示无效角标 | | 用户纯闲聊 | System Prompt 允许脱离书籍,用通用知识回答 | | 导出对话 | 可将角标转为阅读器深链接,便于分享或归档 | ![对话导出](https://developer.qcloudimg.com/http-sa ve/yehe-3853432/bb7a8853e4e2dac022d408bbbe2e09be.png) ---

九、设计取舍:为什么不用「向量 RAG」?

做文档 QA 的同行常会问:既然要做检索增强,为什么不走 **Embedding + 向量库 Top-K** 这条标准路线? 实际上**我们也在做 RAG**——每次回答前都会先查书、再生成。差别在于:社区语境里的 RAG 往往默认包含**向量化与相似度检索**;当前的方案是**「片段索引 + Tool 按需拉原文」**(阶段三),**刻意不引入向量层**。下面从**架构约束**来说明取舍,并非否定向量 RAG 的价值。 ### 界定范围:不是不用检索,而是不用「向量检索」 - **广义 RAG**:检索相关材料 → 再生成 → **我们在做**。 - **向量 RAG**:召回依赖 Embedding 相似度 → **当前版本不做**。 全书预处理为**片段摘要索引**;提问时模型通过 Tool 选段,再**回传原文**。检索增强确实存在,但不依赖单独的 embedding 模型与向量索引维护。 --- ### 原因一:支持自定义 LLM Provider,配置链路要尽量短 产品允许用户自由接入**自有 API Key**、自定义 Base URL,或使用**本地 Ollama**——对话模型由用户自选,成本和数据路径都可控。这对很多自托管、多模型对比的场景来说是硬需求。 叠加典型向量 RAG 后,集成面会明显变宽: - 除了**Chat 模型**之外,通常还需要**Embedding 模型**(另一个 model name,有时还是另一个 endpoint); - 像 Ollama 这种本地部署方案,还得单独拉 embedding 模型,并处理维度、接口兼容问题; - 故障域会变复杂:Chat 正常但**检索为空**时,可能是 embedding、索引或维度不一致,排查成本远高于「单 Provider 全链路」。 在当前方案里,**选段与作答共用同一套 Provider 配置**,避免了「Chat 用 A、建索引用 B」这种割裂。如果你在做**可插拔 LLM** 的应用,这一点往往比多几个点的召回率更重要。 ![自定义 AI 服务商](https://developer.qcloudimg.com/http-sa ve/yehe-3853432/1be6f13489d480dd623cc8d46eec67a7.png) --- ### 原因二:Embedding 与索引强绑定,切换 Provider 成本高 向量 RAG 里常被低估的一点:**向量不是通用中间格式,而是某个 embedding 模型下的坐标。** 建库用模型 A、查询用模型 B 时,相似度通常**不可比**——换模型往往意味着**全书重新向量化**,而且不同模型的**向量维度**(768 / 1024 / 1536 …)会绑死存储 schema。 阶段三持久化的是**结构化摘要 + 字符 span**,不存向量;切换 Chat 模型时**无需重建索引**,证据链(原文位置)不变。这与「用户随时对比不同 LLM」的目标更加一致。 --- ### 原因三:有目录的长文档,结构化路由往往已经够用 电子书、PDF 通常有**章节结构**;预处理已经产出**段标题 + 摘要**。对于「某一章讲了什么」「书中如何定义某概念」这类问题,在摘要目录上选段再**拉回原文**,实践中效果已经很稳定;而且 Tool 回传的是**带 `[f…]` 的原文**,零幻觉的锚点始终在字符 span 上。 向量检索在语义模糊、跨语言、长段落字面匹配等场景下仍有优势;但在**有 TOC、可预处理、要强溯源**的阅读器场景里,优先把复杂度放在**Tool + 原文回传 + 引用约束**上,ROI 通常更高。 --- ### 后续方向:混合召回,而非推倒重来 不排除将来增加**向量粗召回**(例如 embedding 只用来筛 Top-N 候选章节),最终仍然走**选段 → 原文回传 → 可点击溯源**这条路线,零幻觉的规则会保持不变。如果引入,会尽量满足:Embedding **可选**、换模型时**显式提示重建索引**,避免 silent wrong retrieval。 在此之前,优先保证:**任意 OpenAI 兼容的 Chat API 即可工作,换 Chat 模型不必重建本地索引**。 ---

十、小结

| 环节 | 手段 | 作用 | |------|------|------| | 预处理 | 按目录/长度切分 + 片段摘要缓存 | 长书可检索、可定位 | | 位置标记 | `[f文件-起始-结束]` 写入原文 | 出处可机器解析 | | Tool 检索 | 按问题查片段/全书摘要,回传 **原文** | 作答前强制取证 | | System Prompt | 书优先、禁止伪造角标、查不到要说 | 约束生成行为 | | 前端溯源 | 角标 → 预览 → 跳转高亮 | 用户可核验证据 | | 不用向量检索 | 单 Provider、换 Chat 模型无需重建索引 | 降低集成与迁移成本 | 「零幻觉」不是指望模型从不犯错,而是**用工程结构把输出锁在证据链上**:没有检索结果就不应冒充书中内容;有检索结果则应给出可核验的原文位置。 若你也在做 AI 阅读或文档 QA,希望**全文直塞 → 关键句提取 → Tool-First 按需检索**这条演进路径,以及**内联位置标记 + 原文回传**的做法,能作为可参考的一种实现。

来源:互联网

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

同类文章推荐

相关文章推荐

更多