4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Production Ready RAG】PDF・Office・テキストなど17形式で、原文の引用箇所をハイライトしてみた

4
Last updated at Posted at 2026-11-12

初めに

RAGの回答に文書名が付いていると、少し安心します。

ただ、実際に確認しようとすると、次のところで手が止まります。

  • PDFの何ページの、どの段落なのか。
  • Excelのどのsheet、どの行なのか。
  • Wordを変換した場合、元のどの段落に対応するのか。
  • TXTやMarkdownのようにページを持たないファイルは、どう開けばよいのか。
  • 同じ文章が2回あるとき、どちらを根拠にしたのか。

そこで、自作のProduction Ready Suiteで、解析した要素から原文へ戻る操作と、RAG Chatの根拠から原文へ戻る操作を整理しました。この記事では、その実装と、合成資料を使って確認した画面を紹介します。

文書・質問・数値・ファイル名は検証用の合成データです。スクリーンショットはデスクトップの1600×1000で撮影し、アカウント、内部ID、接続情報をマスクしています。確認日は2026年11月12日です。

先に結論

ファイルに合わせて、位置の持ち方を3つに分けています。

表示するもの 保存する位置 画面での見せ方
PDF、PDFへ変換したPowerPoint、画像 ページ番号、bbox、座標単位、ページサイズ ページ画像の該当領域を強調
TXT、Markdown、JSON、HTML 要素ID、文字範囲、原本との対応表 該当する文字や原要素を強調
CSV、構造化JSONLへ変換したExcel・Word record ID、sheet・行・セル・段落の位置 構造化した原文の記録を強調

重要なのは、解析時点で位置を残し、引用時点の版へ戻ることです。

ハイライトを表示するためだけに、LLMへ原文をもう一度探させる処理は入れていません。保存した対応関係を使って、サーバーとブラウザで位置を計算します。

一方、位置の確認と、回答内容の正しさは別です。正しい位置を示せても、生成回答には誤りや不足があり得ます。原文を開く機能は、利用者が回答を確かめるためのものです。

1. 今回確認した全17形式

現在のアップロード受付の定義と、合成ファイルの拡張子を照合しました。対象は次の17個です。

形式 拡張子 今回の解析・変換経路 今回のハイライト
PDF .pdf PDFをDoclingで解析 ページ上のbbox
Word .docx Word → 構造化JSONL 段落・表などの記録
旧Word .doc 変換してからWord → 構造化JSONL 変換後文書の段落・記録
PowerPoint .pptx Office → PDF → 解析 変換後PDFのbbox
旧PowerPoint .ppt Office → PDF → 解析 変換後PDFのbbox
Excel .xlsx Excel → 構造化JSONL sheet・行・セル範囲を持つ記録
旧Excel .xls 変換してからExcel → 構造化JSONL sheet・行・セル範囲を持つ記録
CSV .csv CSV → 構造化JSONL 元CSVの行に対応する記録
テキスト .txt Unstructured 原本文字範囲・引用元段落
Markdown .md Unstructured 原本文字範囲・引用元段落
Markdown別拡張子 .markdown Unstructured 原本文字範囲・引用元段落
JSON .json 通常JSONとして解析 本文の引用範囲、原JSONの対応箇所
HTML .html Unstructured 本文の引用範囲、原HTMLの対応箇所
HTML別拡張子 .htm Unstructured 本文の引用範囲、原HTMLの対応箇所
PNG .png 画像をDoclingで解析 画像上のbbox
JPEG .jpg 画像をDoclingで解析 画像上のbbox
JPEG別拡張子 .jpeg 画像をDoclingで解析 画像上のbbox

.jsonには、通常のJSONに加えて、XMLの要素・属性・子ノードをJSONで表した「XMLツリーJSON」の専用解析方式もあります。これはJSONの解析方式の違いです。今回の17個に、.xmlというアップロード拡張子を追加して数えているわけではありません。

同じWordでもPDFへ変換して扱う経路はあります。本稿の形式別検証では、表に書いた文書レシピを1つずつ確認しています。全parserと全文書レシピの組合せを網羅した試験ではありません。

2. 解析と回答を、同じ位置情報でつなぐ

処理の流れは次のとおりです。

解析結果には本文だけでなく、次のような情報を持たせます。

{
  "element_id": "element-2",
  "text": "説明用の合成本文",
  "page_number": 2,
  "bbox": [72, 120, 400, 180],
  "section_path": ["章A", "節B"]
}

これは説明用の例です。実際には、解析方式に応じた座標単位や原本対応、chunkの版なども使います。

解析画面で使うとき

右側で構造化要素やChunkを選び、その要素IDと解析成果の版を左側のプレビューへ渡します。画像ならページとbbox、テキストなら保存した対応表、構造化文書ならrecordを使います。

RAG Chatで使うとき

回答の根拠は、文書IDだけでなく、recipe、chunk set、元要素への対応を持ちます。そのため、回答したときの根拠から、同じ解析版の原文を読み直せます。

文書を再処理して古い引用の版が使えなくなった場合は、版が変わったことを表示します。新しい文書の似た文章へ、自動で付け替えることはしません。

3. PDF・PowerPoint・画像:bboxで示す

ここで解決したいこと

ページ番号だけでは、利用者はページ内を探し直す必要があります。そこで、抽出した要素のbboxをページ画像へ重ねます。

bboxには、少なくとも次の違いがあります。

  • 左上・右下を表すxyxyか、左上・幅・高さを表すxywhか。
  • ページ上の絶対座標か、0〜1の比率か、百分率か。
  • どのサイズ・向きのページに対する座標か。

共有UIのnormalizeBboxForPreviewがこれらを扱い、表示用の百分率へ変換します。たとえば、左上原点の絶対座標を持つxyxyなら、基本の計算は次です。

const leftPercent = x0 / pageWidth * 100;
const topPercent = y0 / pageHeight * 100;
const widthPercent = (x1 - x0) / pageWidth * 100;
const heightPercent = (y1 - y0) / pageHeight * 100;

実装では範囲の検証、ページの回転、表示対象ページの選択も行います。計算した矩形を、表示中のページ画像と同じ枠の中へ配置します。

PDFの解析画面

選択した要素と、左側のページ上の領域を対応させています。

PDFの解析結果とbbox

RAG ChatからPDFの根拠を開く

同じページviewerを使い、回答へ渡した根拠の場所を確認できます。

RAG ChatのPDF根拠

PowerPointは、変換後PDFの位置を使う

.pptxと.pptは、今回の文書レシピではPDFへ変換してから解析しています。強調しているのは引用時点の変換後PDFの領域です。

解析画面では「処理後」を選ぶと、このPDFを確認できます。元のPowerPointの編集画面上に図形を重ねている機能ではありません。

PowerPointの処理後PDFを確認

PNG、JPG、JPEGもページ画像と同様に領域を重ねます。スキャンや画像の認識品質、解析器が出したbboxの精度には依存します。

画像を根拠として開く

4. TXT・Markdown:文字の位置で示す

TXTやMarkdownでは、原本文字列上の位置を保存します。ページのbboxを作る必要はありません。

保存する対応表の概念例です。

{
  "coordinate_unit": "unicode_codepoint",
  "sha256": "<原本bytesのSHA-256>",
  "source_length": 1200,
  "elements": {
    "element-2": [
      {"source_start": 100, "source_end": 140}
    ]
  }
}

実際の対応表には形式・版などの情報も含みます。表示するときは原本のhash、対応表の形式、範囲を検証します。

PythonとJavaScriptの「文字数」を混ぜない

Python側の位置はUnicode codepointです。JavaScriptの通常の文字列indexはUTF-16なので、絵文字などで数がずれます。

共有のCodepointHighlightでは、文字列をcodepointの配列へ変換してから半開区間[start, end)を切り出します。

const characters = Array.from(text);
const quoted = characters.slice(start, end).join("");

実装は、不正な範囲を除き、重なる範囲をまとめてからmarkを描画します。

Markdownの解析画面で原本を強調

同じ文章が2回あるとき

文書全体を文字検索して、最初に見つかった文章を塗る方式にはしていません。保存した元要素に対象を限定し、その範囲内で文字の対応を調べます。

引用の文字範囲が一意ならexact、要素まで確認できる場合はelementとして扱います。元要素を確認できなければunresolvedとなり、精密な位置として見せません。

テキストの根拠を原順序で読む

5. CSV・Excel・Word:構造化した記録を示す

この3種類では、今回の文書レシピで構造化JSONLを経由しています。

CSVの行、Excelのsheetと行、Wordの段落や表を、recordとして保存します。表示にも、そのrecordの位置と構造を使います。

CSV

CSVでは、記録の番号と原本の物理行を区別します。セル内改行があると、1記録が原本の複数行に対応するからです。変換器が記録した位置を引き継ぎ、読取時に原本対応を推測し直さないようにしています。

CSVの解析画面と記録の強調

Excel

sheet名、行、セル範囲を表示し、該当する記録を表で読めるようにしています。

Excelの根拠をRAG Chatから開く

Word

見出し、段落、表の位置を保持します。.docを変換して扱う場合は、画面にも変換後文書の位置であることを示します。

Wordの根拠をRAG Chatから開く

共有のSourceRecordViewが表示する精度は、記録単位です。Excelの元アプリの見た目や、Wordの元ページの組版を再現したbbox表示ではありません。表の場合は該当recordの値を強調します。

ここで、変換後JSONLのhashと、record ID・本文の一致も検証します。別の版や別の記録を、原文に対応しているように見せないためです。

元ファイルのダウンロードは別操作です。ExcelをJSONLへ変換していても、元のXLSXやXLSを取得できます。

6. JSON・HTML:表示本文と原本の対応も保持する

JSONやHTMLでは、画面で読む本文と、保存された原本の文字列が一致するとは限りません。

JSONならescapeやノードの位置、HTMLならタグやentityがあります。そこで、引用本文の強調に加えて、「原本の対応箇所」を別に開けるようにしています。

通常JSONとXMLツリーJSON

通常JSONの対応表と、XMLツリーJSONのjson_source_mapを、それぞれの保存形式に合わせて読みます。XMLツリーJSONではJSON Pointerと文字範囲を保持するため、同じ本文を持つ別ノードも区別できます。

通常JSONの解析と原本文字の強調

HTMLとHTM

HTMLは、本文の要素と、原HTMLのタグ片の対応を読みます。原コードは文字列として表示し、scriptや外部resourceを実行しません。

本文上の引用範囲と、原コードの対応範囲では精度が違う場合があります。原要素の対応だけが確認できたときは、その旨も表示します。

HTMLの引用箇所を確認

表の中の引用

解析器が表を縦線形式で保存する場合もあります。その場合は元文字列上の各セルの範囲を保持し、共有のDataTableへ渡します。

HTMLの表と縦線形式の表の両方について、引用範囲と交差するセルを強調します。同じ値が複数セルにあっても、値の文字検索で全部を塗る方式ではありません。

行途中で切れた表など、構造が確かめられない断片は、原文字列の範囲表示を残します。

7. 根拠を開いた後、前後の文脈も読めるようにする

引用した短い範囲だけでは、条件や前提を読み取りにくいことがあります。

そのため、原文readerには次の操作を用意しています。

操作 読み込むもの
引用位置に戻る 保存された元の引用要素と範囲
本節を読む 引用が属する節の冒頭
親章節を読む 親の章節の冒頭
目次 保存された章節の読み取り位置
前文・後文を読む 同じ解析版の原要素を原順序で読み進める
原本をダウンロード 元ファイル

前後文を得るために、検索順位で前後のchunkを連結しているわけではありません。同じ版の解析結果から、有界な窓として本文を読みます。画面にも「追加の参考文脈」と「引用の元となる段落」を分けて表示します。

章節のない文書では章節操作を無効にし、続きがなければ前後文のボタンを無効にします。クリックのたびに回答を生成し直す処理はありません。

8. 回答の引用が未確定だったら、無理に塗らない

今回の実モデルによるRAG Chatでは、生成された根拠行の位置が未確定になるケースもありました。

この場合、案内を表示して、当該入口からの強調を抑止します。位置が未確定の状態を、成功したハイライトへ数えていません。

一方、AIへ渡した根拠一覧には、保存されたchunkと元要素の対応があります。その一覧から原文を開き、確認できた元要素を読む操作も検証しました。

本稿の形式別の「Chatの画面」は、回答の根拠行、またはこの取得済み根拠一覧から開いた画面です。入口の違いは検証記録へ残しています。17形式を確認したことは、任意の質問の生成回答や、全引用行の精密な定位を保証する意味ではありません。

9. RAG検索とAgent Chatでも使えるか

RAG検索

RAG検索とRAG Chatは、CitationSourceSheetを共有します。根拠を表示する部分は、同じ原文readerとページviewerへ進みます。

今回は17形式それぞれについて、独立した合成資料の範囲で実検索を行い、検索結果から原文を開きました。検索streamの置き換えは行っていません。

RAG検索から原文を確認

Agent Chat

Agentは、公開MCPの検索結果から根拠を保存します。画面で原文を開くときは、Agent backendが閲覧者として短期の署名URLを取得し、そのURLから本文やページ画像を読みます。

Agent Chat
  → Agent backend
  → RAGの公開MCP
  → 閲覧者と根拠・版に結び付いた署名URL
  → 共有の原文reader / ページviewer

本文を読むfetchはCookieとRefererを送らず、署名URLの読み取り側では利用者の現在の権限、対象範囲、引用版を確認します。

2026年11月12日、合成文書・保存済みAgent Runを使い、17形式すべての原文ハイライトを実環境で確認しました。 検証用Agentは下書き実行です。

PDF・PPTX・PPT・PNG・JPG・JPEGでは、元要素を持つ根拠からページ画像のbboxを表示します。

Agent ChatでPDFの引用箇所を確認・実環境

TXT・MD・Markdown別拡張子・HTML・HTM・通常JSONでは、共通の原文readerで文字範囲を表示します。

Agent ChatのMarkdown別拡張子・実環境

CSV・XLSX・XLS・DOCX・DOCでは、保存された構造化JSONLを引用版のreaderへ渡し、記録を表示します。Excelは行とセル範囲、Wordは段落と見出しを確認できます。

Agent ChatのExcel記録・実環境

Agent ChatのWord段落・実環境

この5形式では、署名URLから元ファイルをダウンロードし、合成原本とSHA-256が一致することも確認しました。表示用のJSONLへ変換していても、元ファイルのdownloadは置き換わりません。

構造化Officeは、保存された派生ファイルのMIMEに応じてreaderを選び、引用版のJSONLを読み込みます。表示時にもhash・record ID・本文の一致を確認します。

形式別の入口と確認範囲は、付録の検証記録にまとめました。これらは原文表示の確認であり、生成回答の内容や全引用行の精度を保証する意味ではありません。

10. 形式別の画面一覧

すべてデスクトップで撮影した合成資料です。サムネイルの代わりに、各形式の画像へ直接リンクしています。

形式 ファイル解析の実画面 RAG Chatの実画面
.pdf 解析と強調 Chatの根拠
.docx 解析と強調 Chatの根拠
.doc 解析と強調 Chatの根拠
.pptx 解析と強調 Chatの根拠
.ppt 解析と強調 Chatの根拠
.xlsx 解析と強調 Chatの根拠
.xls 解析と強調 Chatの根拠
.csv 解析と強調 Chatの根拠
.txt 解析と強調 Chatの根拠
.md 解析と強調 Chatの根拠
.markdown 解析と強調 Chatの根拠
.json 解析と強調 Chatの根拠
.html 解析と強調 Chatの根拠
.htm 解析と強調 Chatの根拠
.png 解析と強調 Chatの根拠
.jpg 解析と強調 Chatの根拠
.jpeg 解析と強調 Chatの根拠

11. 実装で分かったこと

元要素への対応は、chunk化の前から残す

後から根拠の文字列だけを使って原文を探そうとすると、重複文、表、変換後の文書で位置が曖昧になります。要素ID、版、文字範囲、原本や派生物への対応を、解析から引き継いでおくと扱いやすくなります。

bboxと記録の位置は、それぞれの精度で見せる

画像上の領域、抽出要素内の文字範囲、Excelの行、Wordの段落は、同じ種類の座標ではありません。画面の説明も、それぞれに合わせています。

前後文と回答に使った引用を混ぜない

利用者が追加で開いた前後文は、読解を助けるための文脈です。回答生成時に使われた引用とは表示を分けます。

ハイライトのための再生成を増やさない

保存された対応関係で処理できる表示は、コードで計算します。対応が不足する旧記録や、位置が確認できない引用は、その状態を表示します。

12. 関連する実装と、手元で確認するコマンド

主な実装箇所です。

局所の回帰を確認する例です。repositoryの依存関係を準備してから実行します。これらは決定論的なテスト・画面のfixture回帰であり、実サービスを使った今回の確認とは分けます。

cd rag/backend
uv run pytest tests/test_source_reader.py tests/test_source_map_resolve.py tests/test_structured_source_reader.py
cd rag/frontend
npx playwright test e2e/citation-text-preview.spec.ts
cd agent/frontend
npx playwright test e2e/chat-answer-evidence.spec.ts

まとめ

Production Ready RAGでは、17形式の原文を、ページのbbox・原本文字範囲・構造化recordという3つの方法で表示しています。

解析結果とChatの根拠を同じ元要素・版へ結び付け、利用者が原文を開いて確認できるようにしました。原本対応が確認できないケースも、精度の違いを残して表示します。

RAGの回答を確認しやすくするには、回答の文章だけでなく、利用者が根拠へ戻る操作まで一緒に作っておくと役立ちます。

関連記事

付録:形式別検証記録

検証範囲・形式別の結果・制約を開く

確認日:2026年11月12日。desktop 1600×1000。文書・質問は合成データ。画像はアカウント欄・内部ID・接続情報をmaskした。

実行範囲

  • アップロード受付の17拡張子を実装定義と照合。全parser・全レシピの組合せを保証するものではない。
  • RAG:実配備の解析、実際に生成したChat回答、実際の検索streamで確認。検索応答の置換なし。
  • 各行で原文の読み込み完了と、文字markまたはbbox overlayが1件以上あることを確認。文書要約のカードを原文位置の証拠に使っていない。
  • Chatの生成引用行に位置不足の案内が出る場合は、取得済み根拠一覧の元要素から開いた。引用行が不明な状態を精密定位成功と数えない。
  • Agent:形式ごとの合成文書に限定した公開MCP検索を実行。全17 Runは完了。下書きAgentによる試験。
  • 実配備のfrontend・backendと保存済み合成Runを使用。原文応答の置換や回答再生成は行っていない。

RAG:17形式の結果とスクリーンショット

すべての画像は直接開けるPNG。Chatの入口も列で区別する。

拡張子 解析 Chatの入口 Chat 検索
.pdf 成功 回答の引用行 成功 成功
.docx 成功 回答の引用行 成功 成功
.doc 成功 回答の引用行 成功 成功
.pptx 成功 取得済み根拠一覧 成功 成功
.ppt 成功 取得済み根拠一覧 成功 成功
.xlsx 成功 取得済み根拠一覧 成功 成功
.xls 成功 取得済み根拠一覧 成功 成功
.csv 成功 取得済み根拠一覧 成功 成功
.txt 成功 取得済み根拠一覧 成功 成功
.md 成功 取得済み根拠一覧 成功 成功
.markdown 成功 取得済み根拠一覧 成功 成功
.json 成功 回答の引用行 成功 成功
.html 成功 取得済み根拠一覧 成功 成功
.htm 成功 取得済み根拠一覧 成功 成功
.png 成功 取得済み根拠一覧 成功 成功
.jpg 成功 取得済み根拠一覧 成功 成功
.jpeg 成功 取得済み根拠一覧 成功 成功

Agent Chat:実環境の受入結果

2026年11月12日、17形式を実環境で検証し、全件成功。実配備のfrontend・backendを使用。隔離frontend・原文応答の置換・回答の再生成は行っていない。同じ保存済み合成Runの、元要素を持つ根拠から原文を開いた。

  • 17形式すべてで実際のmarkまたはbbox overlayの描画を確認。
  • CSV/XLSX/XLS/DOCX/DOCでは構造化recordと、その中のmarkも確認。
  • 同5形式の署名URLによる原本downloadはHTTP 200、合成原本とSHA-256一致。
  • テキストreaderのブラウザリクエストにCookieとRefererがないことを確認。
  • アカウント欄・内部IDをmaskした1600×1000のdesktop画像を再撮影。
拡張子 配備後の表示 構造化record 元ファイルdownload スクリーンショット
.pdf ページbbox・成功 対象外 今回の再検証は対象外 配備後の実画面
.docx 文字範囲・成功 確認済み 原本hash一致 配備後の実画面
.doc 文字範囲・成功 確認済み 原本hash一致 配備後の実画面
.pptx ページbbox・成功 対象外 今回の再検証は対象外 配備後の実画面
.ppt ページbbox・成功 対象外 今回の再検証は対象外 配備後の実画面
.xlsx 文字範囲・成功 確認済み 原本hash一致 配備後の実画面
.xls 文字範囲・成功 確認済み 原本hash一致 配備後の実画面
.csv 文字範囲・成功 確認済み 原本hash一致 配備後の実画面
.txt 文字範囲・成功 対象外 今回の再検証は対象外 配備後の実画面
.md 文字範囲・成功 対象外 今回の再検証は対象外 配備後の実画面
.markdown 文字範囲・成功 対象外 今回の再検証は対象外 配備後の実画面
.json 文字範囲・成功 対象外 今回の再検証は対象外 配備後の実画面
.html 文字範囲・成功 対象外 今回の再検証は対象外 配備後の実画面
.htm 文字範囲・成功 対象外 今回の再検証は対象外 配備後の実画面
.png ページbbox・成功 対象外 今回の再検証は対象外 配備後の実画面
.jpg ページbbox・成功 対象外 今回の再検証は対象外 配備後の実画面
.jpeg ページbbox・成功 対象外 今回の再検証は対象外 配備後の実画面

検証範囲と制約

  • URLと原文readerの関連画面spec:34成功。desktop/375pxを含む。lint・build・完全CI成功。
  • 原文プレビューと構造化readerの関連pytest:32成功。MIME判定、記録表示、元Office bytesの保持、派生hash不一致409を確認。ruff・mypy・完全CI成功。
  • 実環境では17形式の原文ハイライトと、CSV/Excel/Wordの原本downloadを確認した。
  • 本文の正しさやLLMの全引用行の精度を100%保証するテストではない。版・元要素・引用位置が確認できる範囲を表示する。

再確認で直した試験手順

  • 初回の一部画像は読み込み完了前を撮影したため採用せず、mark/bbox表示を待って撮り直した。
  • 要約カードにはページのbboxがない。元要素を持つ根拠を選び直した。要約の表示を、精密な原文位置として数えない。
  • PPTの解析画面では処理後PDFを選択。画面が表示中のページに元要素があることを確かめた。
  • HTMLの初回は再描画中のDOM参照が外れたため、再取得するlocatorで再実行した。
4
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?