返回文章列表

我是如何實現閱讀器「零幻覺」問答的

分享 AI 閱讀器零幻覺問答的工程實作:回答嚴格基於當前書籍原文,關鍵論述可一鍵溯源到具體段落。

封面:零幻覺問答

本文分享 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 提取關鍵句(壓縮 Context,但壓得太狠)

做法: 在提問前(或首次開啟書時),用 成本更低的模型 對正文做一輪預處理:依 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 / endFileIndexSpine 檔案索引(PDF 則每頁一個檔案)
startOffset / endOffset字元級起迄偏移
sequence線性閱讀順序
title對應目錄標題

切分策略兼顧精度與成本:單一目錄正文不超過約 20KB 時只摘要該節點;同級目錄會合併成批(15KB~20KB)再呼叫 LLM;無目錄的大塊正文則依 3~4 萬字元區間切段。

摘要生成時的 System Prompt 會要求 保留原文位置標記(格式 [f數字-數字-數字]),以便後續 Tool 回傳原文時,位置資訊與 spine 字元偏移一致。核心約束如下:

若摘要內容與原文某段相關,須保留段末位置資訊,格式 [f數字-數字-數字](如 [f1-90-109])。
位置標記是整體,禁止修改、合併或省略其中的任何字元或數值。

預處理完成後,問答不再依賴「整書 Context」,而是依賴 結構化片段索引——這是長書場景下零幻覺的工程前提。


五、位置標記體系:把「出處」編碼進文字

零幻覺不僅要求內容來自原文,還要求 出處可機器解析、可在 UI 中跳轉。我們採用內嵌位置標記:

[f{fileIndex}-{startChar}-{endChar}]

例如 [f5-123-165] 表示:第 5 個 Spine 檔案(從 0 起算)中,字元偏移 123~165 的文字區間。

5.1 標記如何寫入正文

正文提取層在輸出片段時,為每個小段在段末寫入 [f{fileIndex}-{start}-{end}]。示意:

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。

引用溯源彈窗


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

當對話綁定某本書(存在 resourceId,且 chatType === 'chat')時,每次生成前會向模型註冊兩個 Tool,並掛載對應的 executor。整體遵循 OpenAI 相容的 function calling 迴圈

適用於:概念、人物、情節、章節細節等 有明確檢索意圖 的問題。

流程簡述:

  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 允許脫離書籍,用通用知識回答
匯出對話可將角標轉為閱讀器深連結,便於分享或歸檔

對話匯出


九、設計取捨:為什麼不用「向量 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 服務商


原因二: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 按需檢索 這條演進路徑,以及 內嵌位置標記 + 原文回傳 的做法,能作為可參考的一種實作。

以上是我們在開發 令狐兄(Foxycape)AI 閱讀器實踐心得,僅供參考。文末可前往 下載頁面 體驗閱讀器。