
本記事では、AI リーダーにおける ゼロ幻覚 Q&A のエンジニアリング実装を共有します。回答は現在開いている書籍の原文に厳密に基づき、主要な論述は ワンクリックで 具体的な段落に遡及できます。AI 読書、ドキュメント Q&A、RAG 系アプリを開発している方に、3 回の反復から得た経験と最終アーキテクチャが参考になれば幸いです。
一、実践の経緯:3 段階の進化
ゼロ幻覚 Q&A は最初から完璧に設計されたわけではありません。コスト、レイテンシ、正確性 のトレードオフの中で段階的に進化してきました。以下、時系列で 3 段階を振り返り、現在のアーキテクチャがこの形になった理由を理解しやすくします。
段階一:全文を Context に投入(最もシンプル、最初に問題が露呈)
やり方: ユーザーが書籍を開いて質問するとき、抽出した 全文 を System Prompt または User メッセージに入れ、対話モデルに回答させる。書籍が約 40 万文字 を超える場合は ハード切り捨て — 前半のみ保持し、後続の章はモデルから見えない。
メリット:
- 実装コストが極めて低く、ほぼ前処理不要;
- 短編や構造が単純なドキュメントでは効果がまずまず — モデルは確かに「本全体を見た」;
- インタラクションがシンプル:質問すれば答えが返る、「分析をお待ちください」の待機状態がない。
デメリット(すぐに許容できなくなる):
- 応答が遅い:毎回の質問で大量テキストをモデルに送るため、初 Token レイテンシと総時間が書籍の長さに比例して悪化;
- Token コストが高い:同じ書籍について質問するたびに全文の入力料金を繰り返し支払う;
- 長編で深刻な歪み:40 万文字超で切り捨て後、後半、付録、結論章は存在しないも同然。UI もしばしば 切り捨てを明示しない;
- 検索粒度ゼロ:モデルは数十万文字の中で「干し草の中の針」を探す必要があり、詳細を見落としやすく、もっともらしいが根拠のない 要約を生成しやすい — 読書シーンで最も避けたい幻覚。
段階一は MVP 検証には向くが、プロダクト級の方案には不向き。
段階二:軽量 LLM で重要文を抽出(Context 圧縮、しかし圧縮しすぎ)
やり方: 質問前(または初回オープン時)に、コストの低いモデル で本文を前処理:Spine で章分割(または書籍全体を分割)し、重要文 を抽出。出力時に [fファイル-開始-終了] 形式の位置マーカーを保持し、抜粋を短いテキストに連結して後続 Q&A の Context とする。
典型的なチェーンは Extract → Cache → Chat:先にオフラインまたはオンデマンドで抽出を実行して DB に保存し、以降の質問ごとに同じ「重要文コレクション」を再利用。多くのドキュメント Q&A プロトタイプの「先に圧縮、圧縮結果で QA」という思路と同じで、段階二で実際に採用したルートでもある。
メリット:
- 各質問でモデルに送るテキストが 明らかに短縮 され、1 回あたりの Token 消費が段階一より大幅に低下;
- 前処理結果をキャッシュ可能、同じ書籍で毎回抽出し直す必要がない;
- 位置マーカーを導入済み、後続の遡及の基盤となる。
デメリット(長編シーンでは依然耐えられない):
- 詳細が大量に失われる:「重要文」はモデルが主観的に選別。論証チェーン上の限定条件、反例などが捨てられやすく、回答が「正しいが偏った」ものになりやすい;
- 長編では Context が依然大きい:大部作品でも重要文のみ残しても、連結後の入力は依然として大きく、レイテンシとコストは緩和されただけで根治されていない;
- 二重 LLM 誤差:抽出段階で見落とし、Q&A 段階で抜粋を誤読 — エラーが 累積 する;
- 静的 Context:ユーザーが特定章の詳細を聞いても書籍全体の構造を聞いても、モデルに送られるのは常に 同じ事前抽出テキスト で、質問に応じた動的な範囲絞り込みができない。
この段階の教訓は明確:問題は「圧縮するかどうか」ではなく、「圧縮がオンデマンドか、原文に戻れるか」。
段階三:セグメント索引 + Tool オンデマンド検索 + 原文返却(現行方案)
やり方: 基本方針は PageIndex を参考。段階二と比べ、コアの変化は 3 点:
- 前処理の成果物は構造化索引(目次レベルの要約 + 正確な文字 span)であり、抜粋を直接 Q&A Context として使わない;
- 各質問でモデルが Tool Calling によりオンデマンド検索 し、位置マーカー付き原文 を取得して回答;
- System Prompt とフロントエンド連動 で引用形式を制約し、クリックで脚注ジャンプ、原文ハイライトをサポート。
3 段階の比較:
| 次元 | 段階一(全文投入) | 段階二(重要文抽出) | 段階三(現行) |
|---|---|---|---|
| 1 回の質問の Context | 書籍全体(または切り捨て後の前半) | 事前抽出の重要文コレクション | 質問に関連する少量の 原文 フラグメントのみ |
| 長編の正確性 | 40 万文字超で深刻に低下 | 抽出品質に依存、詳細欠落しやすい | 目次/span で検索、書籍全体長のハード切り捨てなし |
| 応答速度 | 遅い | やや改善、長編は依然遅い | 検索 + 短い Context、明らかに高速 |
| Token コスト | 極めて高い | 中〜高 | 前処理償却 + オンデマンド課金 |
| 遡及能力 | 弱い(出典付けが困難) | 位置マーカーあり、内容は二次選別済み | 脚注が 真の原文 span に対応 |
| エンジニアリング複雑度 | 低 | 中 | 高 |
段階三で止めた理由: 読書シーンのゼロ幻覚で重要なのは「モデルにできるだけ多くの文字を見せる」ことではなく、「回答前に質問に関連する原文の証拠を取得すること」。段階一・二は Context のサイズ で工夫;段階三はチェーンを 「索引(前処理)→ 検索(Tool)→ 証拠取得(原文)→ 回答(制約付き生成)」 に分割し、正確性、コスト、遡及性を同時に両立。
以下、段階三 の実装詳細を展開。
二、問題定義:読書シーンでは、幻覚は通常 Chat より致命的
通常の ChatBot の偶発エラーはユーザーが許容しがち。しかし 書籍 Q&A では幻覚の代償が高い:
- ユーザーが聞いているのは この本 が何と言っているかであり、モデルの parametric memory ではない;
- もっともらしい「書中の見解」一句が、ノート、引用、さらには二次伝播を誤導する可能性;
- 出典がなければユーザーは検証できず、プロダクトの信頼構築が困難。
したがって「ゼロ幻覚」はエンジニアリング上、3 つの 実行可能な ルールとして具体化される:
- 書内の質問は先に書籍を検索:現在の書籍に関連しうる質問は、モデルが先に検索(Tool)を実行し、その後回答を構成;
- 回答は遡及可能でなければならない:主要結論に原文位置マーカーを付与し、フロントエンドが解析してジャンプ・ハイライト可能;
- 見つからなければ見つからないと言う:書中にない内容は明確に告知し、汎用知識で「書中の見解」を装わない。
以下、段階三 のデータフローに沿って、上記ルールの実装方法を説明。
三、全体アーキテクチャ:前処理 → Tool 検索 → 制約付き生成 → クリック可能な遡及
核心思路は一言で:モデルに「記憶で答えさせない」—「先に証拠を取り、答え、出典を付与する」 こと。
四、前処理:書籍全体を検索可能な「セグメント索引」に変換
毎回の質問で 段階一 の全文 Context を使うと、長編では必ず Token が爆発し、検索粒度も粗すぎる。段階三の解法:ユーザーが特定の書籍で初めて AI 対話を開始すると、バックグラウンドで セグメント要約タスク を非同期実行。目次構造 または テキスト長 で書籍を複数の Segment に分割し、各フラグメントの要約を生成、ローカル IndexedDB に永続化。
各 Segment のデータ構造には要約と 本文の物理位置 が含まれる:
| フィールド | 意味 |
|---|---|
startFileIndex / endFileIndex | Spine ファイル索引(PDF は各ページ 1 ファイル) |
startOffset / endOffset | 文字レベルの開始/終了オフセット |
sequence | 線形読書順序 |
title | 対応する目次タイトル |
分割戦略は精度とコストのバランス:単一目次ノードの本文が約 20KB 以下ならそのノードのみ要約;同レベルの目次は 15KB〜20KB のバッチにマージしてから LLM 呼び出し;目次のない大きな本文ブロックは 3〜4 万文字区間で分割。
要約生成時の System Prompt では 原文位置マーカーを保持(形式 [f数字-数字-数字])を要求し、後続の Tool 原文返却時に位置情報が spine 文字オフセットと一致。核心制約:
要約内容が原文の某段落に関連する場合、段落末尾の位置情報 [f数字-数字-数字](例:[f1-90-109])を保持すること。
位置マーカーは一体のものであり、文字や数値を変更・マージ・省略してはならない。
前処理完了後、Q&A は「書籍全体 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 を個別に約定。コア 5 項目:
- 標準形式:必ず
[f_fileIndex-startChar-endChar]を使用、3 段の数字はすべて必須; - 現在のソースのみ引用:脚注は今回の System/User メッセージまたは Tool 返却テキストのマーカーを そのままコピー;
- 偽造禁止:位置を自ら計算・変更・捏造してはならない;
- 寧可省略:現在のコンテキストに合法マーカーがなければ通常通り回答、位置マーカーを出力しない;
- 論述に密接:マーカーは関連文の直後に付け、文末に引用リストを蓄積しない。
フロントエンド表示前に、モデルが偶発出力する 2 段 の不正マーカー(例:[f1-293])もフィルタし、無効脚注が UI に入らないようにする。

六、Tool Calling:先に検索、後に回答
対話が書籍にバインドされている(resourceId あり、chatType === 'chat')場合、各生成前に 2 つの Tool をモデルに登録し、対応する executor をマウント。全体は OpenAI 互換の function calling ループ に従う。
6.1 get_related_segment_summaries — 具体問題向けセグメント検索
適用:概念、人物、プロット、章の詳細など 明確な検索意図 がある質問。
フロー概要:
- モデルがユーザーの口語を 書中に現れうる用語 に書き換え(System Prompt の「Optimize Search Queries」);
- Tool を呼び出し、
questionを渡す; - 全セグメント要約を Token 予算で バッチ分割(1 バッチ約 3 万 Token、最大 5 バッチ);
- 各バッチで 独立 LLM リクエスト を発起し、
{ id, title, summary }リストから関連セグメント ID(最大 5 個)を選択、JSON 返却、形式{"Thinking":"...","answer":["1","3"]}; - 選択された Segment の span に基づき、Spine から 位置マーカー付き原文(要約ではない)を取得し、Tool 結果として返却。
重要設計:Tool は要約ではなく原文を返却。 モデルは真の段落 + インライン [f…] を見て回答し、「要約 → 再要約」によるドリフトを回避。
6.2 get_full_book_segment_summaries — 書籍全体概観類の質問
適用:「書籍全体を要約」「この本を評価」「全体構造/テーマ」など 全体視野 が必要な質問。
読書順に全セグメントの summary を連結して返却し、セグメントごとの関連度選別で重要章を見落とすことを回避。
6.3 System Prompt:書籍優先、Tool 優先
書籍バインド時、System Prompt に Core Principles for Reading Assistant を注入。コア 3 項目:
1. Book First, Tool First
- 書籍に関連しうる質問は、必ず先に Tool で検索;
- 回答は主に検索結果に基づき、検索せずに「書中の内容」を捏造禁止。
2. General Knowledge as Fallback Only
- 雑談のみ / ユーザーが明示的に書籍を使わない / Tool 結果なし の場合のみ汎用知識使用可;
- 書中にない場合、先に「書中にこの内容は言及されていない」と宣言し、その後汎用知識を補足。
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 クリックインタラクション
- 初回クリック:
[f…]を解析 → fileIndex と文字オフセット取得 → Spine 原文からテキスト抽出 → プレビューポップアップ(目次タイトル付き可); - 同一脚注を再クリック:ポップアップを閉じる;
- ジャンプ確認:読書ビューを開き、文字区間をハイライト。
モデルがコピーしたマーカーからユーザーが見る原文まで、途中 LLM による二次加工なし、遡及チェーン全体が 確定的・再現可能。
八、境界ケースと正直なデグレード
ゼロ幻覚 ≠「常に答えがある」、証拠がなければでたらめを言わない:
| シーン | 動作 |
|---|---|
| セグメント要約未生成 | 先に全文抽出して要約 |
| Tool 検索結果なし | (No relevant segment excerpts found…) を返却、モデルは書中未言及と宣言すべき |
| モデルが不正 2 段マーカーを出力 | フロントエンドでフィルタ、無効脚注を表示しない |
| ユーザーが純粋な雑談 | System Prompt が書籍から離れ汎用知識で回答を許可 |
| 対話エクスポート | 脚注をリーダーディープリンクに変換可能、共有・アーカイブに便利 |

九、設計上のトレードオフ:なぜ「ベクトル RAG」を使わないのか
ドキュメント Q&A を作る同業者からよく聞かれる:検索拡張生成をするなら、なぜ Embedding + ベクトル DB Top-K の標準ルートを取らないのか?
実際 RAG はやっている — 毎回回答前に書籍を検索してから生成。違いは:コミュニティ文脈の RAG はしばしば ベクトル化と類似度検索 を暗黙に含む;現行方案は 「セグメント索引 + Tool オンデマンド原文取得」(段階三)で、ベクトル層を意図的に導入しない。以下は アーキテクチャ制約 からのトレードオフであり、ベクトル RAG の価値を否定するものではない。
範囲の限定:検索を使わないのではなく「ベクトル検索」を使わない
- 広義 RAG:関連資料を検索 → 生成 → やっている。
- ベクトル RAG:Embedding 類似度に依存した recall → 現バージョンでは未実装。
書籍全体の前処理は セグメント要約索引;質問時はモデルが Tool でセグメントを選び、原文を返却。検索拡張は存在するが、独立した embedding モデルとベクトル索引のメンテナンスに依存しない。
理由一:カスタム LLM Provider 対応、設定チェーンをできるだけ短く
プロダクトはユーザーが 独自 API Key、カスタム Base URL、ローカル Ollama を自由に接続可能 — 対話モデルはユーザーが選択、コストとデータパスを制御可能。多くのセルフホスト、複数モデル比較シーンでは必須要件。
典型的なベクトル RAG を重ねると、統合面が明らかに広がる:
- Chat モデル 以外に、通常 Embedding モデル も必要(別 model name、場合によっては別 endpoint);
- Ollama 等のローカルデプロイでは embedding モデルを別途 pull し、次元・API 互換を処理;
- 障害ドメインが複雑化:Chat は正常だが 検索が空 — embedding、索引、次元不一致の可能性、単一 Provider 全チェーンより調査コストが高い。
現行方案では、セグメント選択と回答が同一 Provider 設定を共有 — 「Chat は A、索引構築は B」を回避。差し替え可能 LLM アプリでは、recall 数ポイントより重要なことが多い。

理由二:Embedding と索引は強く結合、Provider 切り替えコストが高い
ベクトル RAG で過小評価されがちな点:ベクトルは汎用中間フォーマットではなく、特定 embedding モデル下の座標。 索引構築にモデル A、クエリにモデル B では類似度は通常 比較不可 — モデル変更は 書籍全体の再ベクトル化 を意味し、異なるモデルの ベクトル次元(768 / 1024 / 1536 …)がストレージ schema を縛る。
段階三が永続化するのは 構造化要約 + 文字 span でありベクトルではない;Chat モデル切り替え時 索引再構築不要、証拠チェーン(原文位置)は不変。「ユーザーがいつでも異なる LLM を比較」という目標とより一致。
理由三:目次付き長文書では、構造化ルーティングで十分なことが多い
電子書籍、PDF は通常 章構造 あり;前処理で セグメントタイトル + 要約 を生成。「ある章で何を述べているか」「書中で某概念をどう定義しているか」類の質問は、要約カタログ上でセグメントを選び 原文を引き戻す 方式で実践中安定;Tool 返却は [f…] 付き原文 で、ゼロ幻覚は依然文字 span にアンカー。
ベクトル検索は意味的曖昧さ、多言語、長段落の文字面不一致などで優位;目次あり、前処理可能、強い遡及 のリーダーでは、複雑度を Tool + 原文返却 + 引用制約 に優先する ROI が通常高い。
今後の方向:ハイブリッド recall、ゼロから作り直しではない
将来 ベクトル粗 recall(例:embedding で Top-N 候補章のみスクリーニング)を追加する可能性は排除しない。最終的には依然 セグメント選択 → 原文返却 → クリック可能な遡及、ゼロ幻覚ルール不変。導入時は:Embedding オプション、モデル変更時 索引再構築を明示的に通知、silent wrong retrieval を回避。
それまでは優先:任意の OpenAI 互換 Chat API で動作、Chat モデル変更時にローカル索引再構築不要。
十、まとめ
| 段階 | 手段 | 作用 |
|---|---|---|
| 前処理 | 目次/長さで分割 + セグメント要約キャッシュ | 長編を検索可能・位置特定可能に |
| 位置マーカー | 原文に [fファイル-開始-終了] を書き込み | 出典を機械解析可能に |
| Tool 検索 | 質問に応じてセグメント/書籍全体要約を検索、原文 を返却 | 回答前に証拠取得を強制 |
| System Prompt | 書籍優先、脚注偽造禁止、見つからなければ言う | 生成行動を制約 |
| フロントエンド遡及 | 脚注 → プレビュー → ジャンプハイライト | ユーザーが証拠を検証可能 |
| ベクトル検索不使用 | 単一 Provider、Chat モデル変更時に索引再構築不要 | 統合・移行コストを低減 |
「ゼロ幻覚」とはモデルが決して間違えないことを期待するのではなく、エンジニアリング構造で出力を証拠チェーンにロックする こと:検索結果がなければ書中内容を装うべきでない;検索結果があれば検証可能な原文位置を示すべき。
AI 読書やドキュメント Q&A を開発している方に、全文投入 → 重要文抽出 → Tool-First オンデマンド検索 という進化パス、および インライン位置マーカー + 原文返却 のアプローチが参考実装の一つになれば幸いです。
以上は Foxycape AI リーダー開発の実践心得であり、参考まで。文末の ダウンロードページ からリーダーをお試しください。
