回答の根拠を保つ方法
ドキュメント用のアシスタントは、作り話をしない場合にのみ価値があります。自信に満ちた口調での誤った回答は、アシスタントがまったくない場合よりも大きな損失をもたらします。読者はその回答に基づいて行動し、その結果が翌日にはサポートキューに返ってくるからです。
したがって重要なのは、「AIを使っているか」ではなく、モデルが何を根拠に回答することを許されているのか、そして正しい記述が目の前にないときに何が起こるのかという点です。このページでは、実装を読めば分かるレベルの詳細で、その答えを説明します。
得られるもの#
読者が目にするすべての回答は、その質問に対して、そのリクエスト内でDocsbookが取得したページのテキストをもとに書かれています。読者はその過程を確認できます。ウィジェットはFound N resultsを出力し、開かれたページごとに、そのページ自体へのリンクであるReading <page>の行を1つ出力します。回答の下には引用元の一覧が表示されますが、引用元として残るのは、モデルが自身の回答内でそのページのパスをインラインで引用した場合、またはサーバーがこの質問のために実際にそのページを取得した場合だけです。モデルが作り出したものの、回答内で引用しなかったパスが引用元になることはありません。
関連する情報が何も見つからない場合、モデルはもっともらしい内容を構成するのではなく、ドキュメントでは扱われていないと答えるよう指示されています。その回答拒否は記録され、破棄されることはありません。Unanswered questionsレポートの行となり、chat.no_answer webhookになります。これは、次にどのページを書くべきかを知らせるシグナルです。
回答はどのように生成されますか?#
この順序で7つの段階があります。以下の各段階は図ではなく、リクエスト内の実際の分岐です。
1. ドキュメントは単位に分割されてから埋め込まれる#
自動インデックス作成は見出しの粒度で実行されます。つまり、ページの各セクションにつき1つの単位です。埋め込みモデルに送信されるテキストは、セクションの見出しのパンくずに続くセクション本文です — Billing > Refunds は返金テキストの前に置かれます — なぜなら、「Limits」という名前のセクションは「Webhooks」の下にある場合と「AI chat」の下にある場合とでは意味が異なり、ベクトルがその違いを保持する必要があるからです。各単位の上限は6,000文字です。インデクサーにはページ単位や行単位の粒度もありますが、自動パスでは見出しが使用されます。
単位の識別子は、ページパスと見出しアンカーを組み合わせたものです。アンカーはページ内で重複することがあります — 変更履歴には ### Fixed という見出しが40個含まれる場合があります — そのため、重複するアンカーには序数のサフィックスが付加されます。そうしなければ、それらのセクションはすべて同じ単位になり、書き込みが衝突します。
2. ユニットはベクトルになり、変更のないユニットにはコストがかからない#
埋め込みは openai/text-embedding-3-small、1,536 次元で、OpenRouter 経由で1 回の呼び出しにつき 96 ユニットずつ要求されます。ストレージ列は vector(1536) であり、異なる幅を返すモデルは、書き込まれた後で不一致が気づかれないままになるのではなく、即座に拒否されます。
各ユニットには sha256(model + NUL + text) のコンテンツハッシュが付与されます。再インデックス時には、ハッシュがすでに保存されているユニットはスキップされるため、1 ページを編集した場合にかかる埋め込みのコストはコーパス全体ではなく、1 ページ分だけです。実行前に表示される見積もりには、正確なユニット数(分割処理は決定論的で、すでに実行済みです)と、characters ÷ 4 の概算トークン数が含まれます。この数値は ±30% とみなしてください。そのため、価格ではなく見積もりとして提示されています。
3. 再インデックス作成はコミットに追随する#
ドキュメントへの各コミットによって、再インデックス作成がキューに追加されます。キューに入った実行は、すでに送信済みのレスポンスに紐付いたコールバックではなく、2分ごとにバックグラウンドランナーによって処理されます。コールバックこそが、以前は処理の途中で停止し、スタンプだけが付いた空のインデックスを残していました。5分以内に連続して行われたコミットは、1回の実行にまとめられます。公開フローでは、コンテンツ、ナビゲーション、ブランディングの順にコミットされるためです。
セマンティック検索が使用されるかどうかは、常にベクトルの存在によって決まり、「最終インデックス作成日時」によって決まることはありません。タイムスタンプは主張にすぎません。行が証拠です。そして、この違いこそが「セマンティック検索が有効になっている」ことと「セマンティック検索が機能している」ことの隔たり全体なのです。
4. 検索は毎回、両方の検索機構を実行する#
| 検索機構 | 実行されるタイミング | 提供するもの |
|---|---|---|
読者が @-言及したページ |
存在する場合は常に | リストの先頭に強制配置 |
| ベクトル検索 | ワークスペースにベクトルがあり、所有者のトグルがオンの場合は常に | 最大3個の異なるページ |
| Postgres全文検索 | ベクトル検索と並行して常に実行 | 少なくとも2枠。ベクトル検索で見つかった数が少ない場合はそれ以上 |
| ドキュメントグラフの語彙検索 | 上記2つがどちらも何も返さなかった場合のみ | 最大4ページ |
| エージェント型検索ループ | 上記のすべてが何も返さなかった場合のみ | 最大2ページ |
ベクトル検索はコサイン距離が最も近い6行を取得し、それらを 1 − distance の類似度に変換して、類似度の下限である0.25未満を除外し、3件に絞り込む前にページ単位で重複排除します(6件のヒットが1ページの6セクションであることが多いためです)。また、最終リスト内での優先順位も維持します。
ここで全文検索はフォールバックではありません。すべての質問に対して実行され、そのページが統合されます。ただし、ベクトル検索と語彙検索を合わせて5ページを超えないよう上限が設定されます。その理由は独自のインデックスで測定されています。質問「Docsbookはドキュメントサイトの提供にどのようなURLパターンを使用しますか?」に対して、正しいページの最も一致するチャンクは、コサイン類似度では1,341チャンク中48位でした。18ページがそれより高いスコアになりました。一方、語彙検索ではそのページがトップヒットになりました。ページに質問の語句が文字どおり含まれていたためです。top-k の値ではこの問題は解決できません。その質問に対してランキング自体が誤っていたのです。空ではないものの誤ったベクトル検索結果こそが、この統合によって修正される失敗であり、「ベクトル検索が空の場合のみ語彙検索を実行する」というルールでは検出できません。
表の最後の2行は、インデックスがまったくないコーパス向けです。エージェント型ループはモデルに search_docs ツールを渡し、最初の推測が外れた後にコードベースをgrepする場合と同じように、温度0で最大4往復まで異なる表現で再検索できるようにします。その後、最大2つのページパスに絞り込む必要があります。
5. ページは取得され、末尾から切り詰められます#
選択された各ページは、リポジトリのデフォルトブランチから取得され、パスとタイトルを記載したヘッダー行とともにプロンプトに配置されます。12,000文字を超えるページは先頭から切り詰められるのではなく、最初の9,000文字と最後の3,000文字が保持され、その間の省略部分が示されます。返金ポリシー、「関連」、トラブルシューティングのセクションはページの末尾にあるため、先頭からの切り詰めでは、質問が最も関係している可能性の高い部分がまさに削除されてしまいます。閲覧者が現在表示しているページは別途含められ、8,000文字に切り詰められます。
6. モデルは書き始める前に制約される#
リーダーチャットのデフォルトモデルはopenai/gpt-4o-miniです。OpenAIのモデルリファレンスによると、コンテキストウィンドウは128,000トークン、出力上限は16,384トークンです。プロジェクトごとに別のモデルを選択できます。AIチャットを参照してください。
システムメッセージは短く、置き換えることができます。グラウンディングのルールは、コンテンツとともに届く指示ブロックに記載されています。各ルールが具体的なのは、それぞれ実際に発生した失敗に対応しているためです。
- 提供されたコンテンツだけを根拠にする。 ドキュメントで説明されていない用語を定義したり、抜けている情報を補ったりするために、事前学習済みの知識に頼ってはいけません。
- 事実が存在する場合に、存在しないと主張しない。 何かが欠けていると言う前に、提供されたページを読み直してください。これには、その事実が有料またはオプション機能も扱っているページに記載されている場合も含まれます。
- 無料版と有料版の動作を区別する。 有料ページの詳細をデフォルト版の説明に混在させないでください。
- ページは人間による精査ではなく、検索によって見つかった。 各ページを、単語の一致ではなく、質問された具体的な内容に照らして確認してください。ロケールのサブディレクトリに関するページには「URLパターン」という語が含まれていますが、デフォルトURLに関する質問への答えにはなりません。
- より限定された質問に注意する。 ページが質問の条件付きのバージョン(言語、プラン、アドオンなど)に答えていて、質問にそのような条件が付いていない場合、そのページは別のことに答えています。
- セットアップに関する質問には、正確な文が必要。 「Xをどのようにセットアップしますか」という質問の場合、Xの具体的なメニューパス、ボタン、または手順を示す文を探してください。そのような文がない場合は、ドキュメントに組み込みのX統合についての説明はないと答えてください。別の箇所でのXへの言及や、理論上はXに接続できる汎用的な仕組みは、セットアップ手順ではありません。
- 前提条件は必須であり、2回明記されている。 必要なプラン、ロール、事前の手順、バージョン、割り当て量がないか、ページ全体を確認してください。ドキュメントでは、こうした情報がページ上部の短い太字の行に記載されているため、番号付き手順に進む途中で見落としやすくなっています。生成直前に最終確認を行い、すべてのページの冒頭を読み直します。これは、ページの導入部にだけ記載された前提条件が、モデルが該当するサブセクションまで読み進めた時点では、近くにある情報と競合しなくなるためです。
回答は、厳密なJSON(マークダウン本文とrefs配列)として、温度0.3でストリーミング配信されます。
7. 引用はサーバーによって付加され、モデルを信頼したものではない#
これは、引用が意味を持つかどうかを決めるステップです。
| モデルが提供するもの | サーバーが行うこと |
|---|---|
pagePath |
そのパスが回答自体の [ref:…] マーカー内でインライン引用されている場合のみ、またはサーバーが実際に取得したページである場合のみ、ref を保持します。モデルが読んでも引用してもいないパスは、読者に表示される前に削除されます。 |
pageTitle |
リンクラベルとして使用されます |
headingText、ページから逐語的にコピーされたもの |
レンダラーが使用するものと同じスラッグ化処理で、アンカー自体を再計算します |
| (何もなし) | アンカー ID がモデルに要求されることも、モデルから受け入れられることもありません |
アンカーのルールは、細かいことにこだわっているわけではありません。手書きのスラッグ規則(「小文字化し、スペースをダッシュに変換し、特殊文字を削除する」)は、単純な ASCII 句読点でレンダラーと一致しません。Edge cases & errors はページ上では edge-cases--errors になり、手作業で作成した規則では edge-cases-errors になります。また、非ラテン文字の見出しをダッシュだけに変えてしまいます。1つの担当箇所がその文字列を計算し、それ以外はすべてそこに問い合わせます。
関連する情報が見つからない場合にどうなるか#
空白を埋めるために何かが捏造されることはありません。すべてのリトリーバーが空の結果を返した場合、ページは添付されず、Found N results の行も表示されません。モデルに残される指示は、提供されたコンテンツでは質問に答えられないことを率直に伝えることです。
その拒否応答は、データとして扱われます。
- 回答テキストが回答なしのパターンに対してスキャンされ、一致すると通常の
chat.question_askedイベントとともにchat.no_answerwebhook がディスパッチされます。 - 質問は未回答の質問に表示されます。これは、そのチェックに失敗した、記録されたすべてのチャット質問をフィルタリングして表示するビューです。
- 読者はウィジェットで回答に低評価を付けることができ、その低評価は分析でページごとの低評価数として記録されます。
ドキュメントの欠落は、捏造された段落よりもレポートの1行として価値があります。このページ全体は、そのトレードオフを軸に構築されています。
なぜこれが正しい方法なのか(根拠)#
| Docsbookのルール | 効果がある理由 | 出典 |
|---|---|---|
| 取得したページに基づいて回答し、モデルの記憶に頼らない | 検索拡張モデルは「最先端のパラメトリックのみのseq2seqベースラインよりも、より具体的で多様かつ事実に基づく言語を生成する」 | Lewis et al., 2020 — 知識集約型NLPタスクのための検索拡張生成(NeurIPS) |
| 引用は、実際に読んだページを示している場合にのみ有効である | 4つの生成検索エンジンで測定したところ、「生成された文の完全な裏付けが引用によって得られるのはわずか51.5%」であり、「引用が対応する文を裏付けているのはわずか74.5%」だった — システムが検証していない引用は根拠にならない | Liu, Zhang & Liang, 2023 — 生成検索エンジンにおける検証可能性の評価 |
| フォールバックとしてではなく、すべての質問で語彙検索を実行する | 18を超える検索データセットにおいて、「BM25はゼロショット設定で堅牢なベースライン」である一方、密な検索器は「しばしば性能が下回る…その一般化能力には大きな改善の余地があることを示している」 — あなたのコーパスは、どの埋め込みモデルにとってもドメイン外である | Thakur et al., 2021 — BEIR |
| 1,536次元のベクトル、6,000文字の単位 | text-embedding-3-small は1,536次元を出力し、8,192入力トークンを受け付ける。単位を6,000文字に制限すれば、パンくずリスト用の余裕を残してこの上限内に収まる |
OpenAI — 埋め込みガイド |
| プロンプトを5ページに制限し、長いページは末尾から切り取る | モデルの性能は「関連情報が入力コンテキストの先頭または末尾にある場合に最高になることが多く、長いコンテキストの中央にある関連情報へモデルがアクセスする必要がある場合には大幅に低下する」 — ページ数を増やしても精度は上がらない | Liu et al., 2023 — 中央で失われる |
| 拒否を明示的に指示し、その記録を残す | 通常の指示チューニングでは「モデルが知識を持っているかどうかにかかわらず、モデルに文を完成させるよう強制する」。拒否するよう求める必要がある | Zhang et al., 2023 — R-Tuning:LLMに「わかりません」と言うよう指示する(NAACL 2024) |
| グラウンディングはハルシネーションを減らすが、なくすわけではない | 約18,000件のRAG応答に注釈を付けた結果、検索を行った場合でも、「LLMは検索された内容によって裏付けられていない、または矛盾する主張を提示する可能性がある」ことが判明した | Niu et al., 2024 — RAGTruth |
測定するもの — 公開しないもの#
Docsbook AIチャットの正答率は公開していません。顧客のコーパスを対象にラベル付きベンチマークを実施したことはなく、私たち自身のドキュメントで算出した数値は、あなたのドキュメントについて何も示しません。数値を示せば、このドキュメント全体が従っている原則に反することになります。
その代わりに存在するもの:
| 測定項目 | 内容 | 表示場所 |
|---|---|---|
| 回答評価ジャッジ | LLMが完成した会話のトランスクリプト(最大8,000文字)を温度0で読み取り、厳密な {answered, reasoning} 判定を返します |
ChatタブのAnswered列 |
| 一度だけ保存し、再判定しない | トランスクリプトは変化しないため、判定も変わりません — それぞれ一度だけ書き込まれ、その後読み出されます | — |
| リクエストごとの上限 | ページの読み込みごとに判定される新しい会話は最大6件です。そのため、評価未実施のスレッドが1,000件あるワークスペースでも、誰かが初めてタブを開いたときに1,000回分の呼び出し料金が発生することはありません | — |
| 回答なしの検出 | 回答テキストに対するパターンマッチにより、chat.no_answer をディスパッチし、「未回答の質問」に送ります |
Webhooks、「未回答の質問」 |
| 回答ごとの評価 | その回答に対する読者自身の判定を、ページごとに集計します | アナリティクス |
| 検索ラベル | すべての回答のストリームには、どの検索システムがページを生成したかが示されます — semantic、fulltext、semantic+fulltext、doc_graph、agentic または mentions |
レスポンスストリーム。1回のHTTP呼び出しで検証可能 |
最後の行は意図的なものです。「セマンティック検索が有効になっている」という主張は外部から反証できません。まさにそのため、かつてスタンプだけが付いて中身のないインデックスが、何か月も動作しているものとして通用していました。すべての回答で検索システムの名前を示すことで、あなたを含む誰もがその主張を検証できるようになります。
Docsbookでは、さらに2つの内部テストスイートも実行しています — 温度0でモデルが最初にどのツールを選ぶかを採点する40ケースのゴールデンセットと、決定論的なチェックに加えて限定的なLLMジャッジを使用するライブシナリオスイートです。どちらも読者向けチャットではなく、ダッシュボード内の管理者アシスタントを対象としています。このページの主題に対する品質の主張として、テストの成功を示す緑色のチェックマークが受け取られることがないよう、その点を明記しています。
制限#
- 公開された精度の数値はありません。 上記を参照してください。ベンダーが示す単一の精度数値は、それが当社のものであっても、コーパス、質問セット、評価者が公開されるまでは使用できないものとして扱ってください。
- 検索は、自信を持って誤ることがあります。 上記の1,341件中48番目のケースは、当社独自のコーパスで発生したものです。字句検索を統合することで、この問題の大部分は修正できますが、完全になくなるわけではありません。検索は、まさに最も重要な場面で最も信頼性が低くなります。実測では、モデルが記憶に頼るものを何も持たない、あまり知られていない事実に対して、検索が最も有効であることが示されています(Mallen et al., 2023)。
- 類似度の下限値0.25は固定値であり、コーパスごとに調整されるものではありません。語彙に特殊な特徴があるコーパスでは、異なる下限値が必要になる場合がありますが、現在、プロジェクト単位でこれを制御する方法はありません。
- 引用フィルターはANDではなくORです。 パスが取得されたまたはモデルが本文中で引用した場合、参照は残ります。両方を必須にすると、ほぼすべての回答で
refsが空になりました。モデルは配列を埋めてマーカーを省略するためです。その結果、モデルが創作したパスを本文中でも引用した場合、そのパスはフィルターを通過してしまいます。構造上、根拠があるのは取得された側だけです。本文中の引用部分もコーパスと照合するまで、これは要検証です。 - グラウンディングは忠実性の証明ではありません。 サーバーは、引用されたページが読まれたことは保証できますが、回答のすべての文がそのページから導かれていることまでは保証できません。この残余の問題を測定するのがRAGTruthであり、これは実在します。
- 回答なし検出器は英語のパターンマッチです。 別の言語で書かれた拒否応答は認識されないため、
chat.no_answerと「未回答の質問」レポートでは、英語以外のサイトの件数が過少にカウントされます。測定済みの代替手段を公開するまで、これは要検証です。 - 非常に長いページでは中央部分が失われます。 12,000文字を超えるページは、中央部分が省略されたことを示すマーカー付きで、先頭部分と末尾部分のみがモデルに渡されます。非常に長いページの中央部分にしか存在しない事実は、見落とされる可能性があります。そのページを分割することが解決策であり、人間にとってもページが読みやすくなります。
- モデルの挙動はバージョンに依存します。 デフォルトのリーダーモデル、そのコンテキストウィンドウ、拒否動作は、プロバイダーが変更できるものです。このページの仕組みは当社のものですが、モデルがそれに従うかどうかは当社が制御するものではありません。
- セマンティック検索にはベクトルが必要です。 インデックスの実行が完了するまで、検索は全文検索とドキュメントグラフにフォールバックします。これは動作するチャットであり、壊れているわけではありません。ただし、ステージ4で説明されているものとは異なります。
関連#
- AIチャット — 契約: アシスタントができることとできないこと。
- 検索 — このパイプラインが共有する語彙インデックス。
- ソース — アシスタントがあなたのページ以外に読み取ることを許可されているもの。
- チャットフック — 質問をブロックしたり、あなたのシステムだけが知っている事実をモデルに渡したりします。
- Docsbookが主張を証明する方法 — このページが従って書かれているルール。