初めに
RAGでPDFを扱う記事はかなり増えました。
一方、実際の業務では、次のようなOffice文書もよく使われます。
- 複数のsheetを持つExcel
- 一覧表と作業手順が混在するExcel
- 見出し、本文、箇条書き、表が混在するWord
- 数式、結合セル、非表示sheetを含むExcel
これらを単純にplain textへ変換してから1,000文字ごとに分割すると、見た目はテキストになっていても、質問に答えるための関係が壊れてしまいます。
そこで今回は、ExcelとWordをRAGへ取り込むときに、文書構造を残したまま「検索可能な証拠単位」へ変換する方法を整理します。
対象読者は、実際にOffice文書を使ったRAGを構築・改善したい方です。
単なる考え方だけではなく、失敗しやすいところ、データ構造、サンプル文書の生成コード、変換後のJSONL、chunk分割のコードまで紹介します。
この記事に登場するファイル名、商品、コマンド、データは、すべて説明用の架空データです。
結論
先に結論を書くと、Office文書のRAGでは、次の順番が安定します。
1. ファイル形式ごとに構造を読む
2. Excelはsheetごとに「表形式」と「手順形式」を判定する
3. Wordは見出し階層と本文・表の出現順を保持する
4. 出典情報付きのEvidence Blockへ統一する
5. Evidence Blockの境界でchunkを確定する
6. chunk確定後にEmbedding、検索、Rerankを行う
重要なのは、最初から文字数だけでchunkを作らないことです。
Excelの1行、作業手順の1ステップ、Wordの1節、Word表の1行など、「これ以上分けると意味が壊れる単位」を先に作ります。
1. なぜ単純なテキスト変換では足りないのか
ここで解決したいこと
よくある取り込み方法は、ExcelやWordから文字列を取り出し、すべて連結してから固定長で分割する方法です。
text = extract_all_text("document.xlsx")
chunks = [text[i:i + 1000] for i in range(0, len(text), 1000)]
実装は簡単ですが、業務文書では次の問題が起きます。
- sheet名が消え、同じ列名を持つ表を区別できない
- 表のヘッダーと値が別chunkになる
- 作業コマンドと確認方法が別chunkになる
- Wordの見出しと本文が離れる
- Word本文と、その直後にある表の順番が崩れる
- 数式、更新日時、行番号などの根拠情報が落ちる
- 回答が正しくても、元の場所へ戻れない
例えば、次の作業手順を考えます。
| No. | 作業項目 | コマンド/確認ポイント | 備考 |
|---|---|---|---|
| 1.1 | 事前確認 | samplectl service status |
runningを確認 |
samplectl disk check |
空き容量を確認 |
1行目と2行目を別々のchunkにすると、「何を実行するか」は検索できても、「何を確認するか」が検索結果に入らない可能性があります。
LLMへ渡す前に文書構造が壊れていると、EmbeddingやRerankのモデルを改善しても限界があります。
2. まず共通のEvidence Blockを決める
ExcelとWordでは読み方が違います。
ただし、後段の処理まで別々にすると、Embedding、検索、引用、評価の実装が複雑になります。
そこで、Preprocessの出口だけを共通化します。
{
"source_file": "sample_office_docs.xlsx",
"source_type": "excel",
"block_type": "table_row",
"sheet": "Products",
"section_path": ["商品一覧"],
"row_start": 2,
"row_end": 2,
"fields": {
"商品ID": "ITEM-001",
"商品名": "ノートPCスタンド",
"単価": 3200,
"在庫数": 12
}
}
最低限、次のmetadataを残します。
| 項目 | 目的 |
|---|---|
source_file |
元ファイルを特定する |
source_type |
Excel、Word、CSVなどを区別する |
block_type |
表の1行、手順、Word節、Word表の1行を区別する |
sheet |
Excelの出典sheetへ戻る |
section_path |
上位見出しを含めて意味を補う |
row_start / row_end
|
元の行、または文書内位置へ戻る |
content |
文章・手順向けの本文 |
fields |
表形式データの列と値の対応 |
空の項目は出力しません。
これにより、ExcelとWordの違いを残しながら、後段では同じJSONLとして扱えます。
3. マルチシートExcelはsheetごとに読む
ここで解決したいこと
Excelでは、workbook全体を1つの表として扱わないことが重要です。
sheetごとに役割、列構成、密度が違うからです。
実装では、次の順番で処理します。
workbook
├─ visible sheetを列挙
├─ 除外対象sheetを除く
├─ sheetごとにヘッダー候補を探す
├─ 表形式 / 手順形式を判定する
└─ sheet名付きEvidence Blockへ変換する
3.1 読み込むsheetを明示する
すべてのsheetを無条件に取り込むと、評価用の回答、計算用の中間sheet、古いバックアップ、非表示設定などが知識ベースへ混ざることがあります。
次の項目は設定可能にしておく方が安全です。
excluded_sheets = {"QA", "Answer", "Evaluation"}
include_hidden = False
除外名をコードの各所へ埋め込むのではなく、変換関数の引数または設定ファイルから渡します。
convert_excel_to_text_document(
file_path,
excluded_sheets=("QA", "Answer", "Evaluation"),
include_hidden=False,
mode="auto",
max_block_chars=3000,
)
ここで大事なのは、複数sheetを読めることだけではありません。
「どのsheetを、なぜ読まなかったか」をログへ残すと、後で検索漏れを調査しやすくなります。
3.2 .xlsxは数式と保存済み計算結果の両方を読む
openpyxlでは、同じworkbookを2回開きます。
from openpyxl import load_workbook
formula_book = load_workbook(path, data_only=False)
value_book = load_workbook(path, data_only=True)
-
data_only=False: 数式を取得する -
data_only=True: Excelが保存した計算結果を取得する
基本方針は次です。
value = cached_value if cached_value is not None else formula
保存済み計算結果があれば値を優先し、存在しなければ数式を残します。
なお、openpyxl自体はExcelの数式計算エンジンではありません。生成直後などで計算結果が保存されていない場合は、数式文字列が出力されます。
3.3 表形式と手順形式を分ける
同じExcelでも、次の2種類はchunkの作り方が違います。
表形式
商品ID | 商品名 | 単価 | 在庫数 | 更新日時
ITEM-001 | ノートPCスタンド | 3200 | 12 | ...
ITEM-002 | USBハブ | 4800 | 8 | ...
表形式では、1データ行を1つのEvidence Blockにします。
{
"block_type": "table_row",
"sheet": "Products",
"row_start": 2,
"row_end": 2,
"fields": {
"商品ID": "ITEM-001",
"商品名": "ノートPCスタンド",
"単価": 3200,
"在庫数": 12
}
}
これにより、商品ID、商品名、価格、在庫数の関係を同じchunkに保持できます。
手順形式
No. | 作業項目 | コマンド/確認ポイント
1 | 月次メンテナンス |
1.1 | 事前確認 | samplectl service status
| | samplectl disk check
1.2 | バックアップ | samplectl backup create --name monthly
| | samplectl backup verify --name monthly
手順形式では、1行を1chunkにしてはいけません。
「番号列と作業項目列の両方に値がある行」を次の手順の開始位置として、次の開始位置の直前までを同じEvidence Blockへまとめます。
{
"block_type": "procedure_step",
"sheet": "Maintenance",
"section_path": ["月次メンテナンス", "事前確認"],
"row_start": 3,
"row_end": 4,
"content": "コマンド: samplectl service status | 備考: runningを確認\nコマンド: samplectl disk check | 備考: 空き容量を確認"
}
上位の作業名も各子ステップへ繰り返して入れます。
そうすると、「月次メンテナンスの事前確認は何か」のような質問でも、検索対象のchunk単体で意味が通ります。
3.4 自動判定は「ヘッダー+密度+手順境界」で行う
自動判定の実装例です。
from statistics import median
def detect_sheet_mode(rows, header_index, active_columns):
if has_number_and_title_boundaries(
rows,
header_index,
active_columns,
):
return "procedure"
sample_rows = [
row
for row in rows[header_index + 1:]
if any(value not in (None, "") for value in row)
][:50]
if not sample_rows:
return "table"
fill_ratios = [
sum(
col < len(row) and row[col] not in (None, "")
for col in active_columns
) / len(active_columns)
for row in sample_rows
]
return "table" if median(fill_ratios) >= 0.60 else "procedure"
この例では、最大50件の非空行を調べ、利用列の中央値が60%以上埋まっていれば表形式とみなします。
60%は絶対的な正解ではありません。
実データで誤判定したsheetをログへ残し、sheet単位でtableまたはprocedureを上書きできるようにしておくのが現実的です。
3.5 Excelで落としやすい情報
実装時は、次も確認します。
| 対象 | 処理 |
|---|---|
| 空行 | chunkを作らない |
| 日付・時刻 | ISO 8601形式へ統一する |
| 重複ヘッダー | 列記号を付けて区別する |
| 空ヘッダー |
column_Cのような名前を付ける |
| 結合セル | 論理ブロック内だけ値を引き継ぐ |
| 非表示sheet | 初期値では読み込まない |
| 数式 | 保存済み値を優先し、なければ数式を残す |
.xls |
xlrdなど別readerを使う |
| CSV | UTF-8に加えてCP932も考慮する |
重複ヘッダーの例です。
担当 | 担当
このままdictへ変換すると、後の値で上書きされます。
担当
担当__H
のように列記号で区別すると、元の列位置も分かります。
4. Wordは見出しと出現順を残す
ここで解決したいこと
Wordでは、見出し、本文、箇条書き、表が交互に登場します。
見出し
本文
箇条書き
表
本文
次の見出し
python-docxのdocument.paragraphsとdocument.tablesを別々に処理すると、本文と表の出現順が分からなくなります。
そのため、文書bodyのXML要素を順番に走査します。
from docx.oxml.table import CT_Tbl
from docx.oxml.text.paragraph import CT_P
from docx.table import Table
from docx.text.paragraph import Paragraph
def iter_word_items(document):
for child in document.element.body.iterchildren():
if isinstance(child, CT_P):
yield Paragraph(child, document)
elif isinstance(child, CT_Tbl):
yield Table(child, document)
4.1 Heading 1〜3をsection pathとして持つ
例えば、次のWord文書があります。
運用ガイド Heading 1
バックアップ Heading 2
バックアップを作成します。 本文
実行前に空き容量を確認する。 箇条書き
障害時の確認 Heading 2
ログを確認します。 本文
バックアップを作成します。だけをchunkへ入れるのではなく、次のように見出し階層を付けます。
{
"source_file": "sample_operations_guide.docx",
"source_type": "word",
"block_type": "word_section",
"section_path": ["運用ガイド", "バックアップ"],
"content": "バックアップを作成します。\n- 実行前に空き容量を確認します。"
}
検索時には本文だけでなく、運用ガイド > バックアップもEmbedding対象へ含めます。
同じ「確認」という言葉が複数の章にあっても、意味を区別しやすくなります。
4.2 目次は本文として扱わない
Wordの目次には、本文と同じ見出し文字列が並びます。
TOC 1、TOC 2などのstyleを本文として取り込むと、検索結果に目次だけが出ることがあります。
if style_name.casefold().startswith("toc"):
continue
ただし、独自styleを使用している文書もあるため、style名はログで確認できるようにします。
4.3 Word表は1行ずつEvidence Blockにする
表は、先頭行をヘッダーとして、各データ行をfield mappingへ変換します。
{
"source_file": "sample_operations_guide.docx",
"source_type": "word",
"block_type": "word_table_row",
"section_path": ["運用ガイド", "バックアップ"],
"table_index": 1,
"row_start": 2,
"row_end": 2,
"fields": {
"項目": "保存先",
"確認方法": "空き容量が20%以上あること"
}
}
表の各行へ現在のsection_pathを継承させるところがポイントです。
表だけを見ても、その表が何について説明しているか分かるようになります。
5. ChunkingはEvidence Blockの境界を優先する
ここで解決したいこと
Preprocessで正しく構造化しても、後段で再び1,000文字ごとに切ると意味がありません。
そこで、Evidence Blockの末尾へ固定delimiterを付けます。
{"block_type":"table_row", ...} <FIXED_DELIMITER>
{"block_type":"procedure_step", ...} <FIXED_DELIMITER>
{"block_type":"word_section", ...} <FIXED_DELIMITER>
splitter側は、このdelimiterがある場合、通常のchunk_sizeとchunk_overlapよりもBlock境界を優先します。
FIXED_DELIMITER = "<FIXED_DELIMITER>"
def split_fixed_blocks(text: str) -> list[str]:
if FIXED_DELIMITER not in text:
return recursive_character_split(text)
return [
block.strip()
for block in text.split(FIXED_DELIMITER)
if block.strip()
]
これで、原則として次の関係になります。
1 Evidence Block = 1 Chunk
5.1 最大文字数はhard limitではなくsoft limitにする
1つのWord節や作業手順が極端に長い場合は、上限も必要です。
ただし、3,000文字になった瞬間に文字列を切るのではなく、次の境界だけで分割します。
- Wordの段落境界
- 箇条書きの項目境界
- Excel手順のコマンド/確認行の境界
- 表の行境界
表の1行が上限を超える場合でも、途中で分割すると列と値の対応が壊れます。
そのため、表の1行や単独コマンドはatomicな要素として扱い、soft limitを超えても保持する選択肢が必要です。
5.2 overlapを自動で付けない
固定長chunkでは、前後の文脈を補うためにoverlapを使います。
しかし、構造化Blockへ機械的にoverlapを付けると、別sheetや別手順のJSONが混ざる可能性があります。
Evidence Blockにsection_pathや上位タイトルを繰り返している場合、固定delimiterのchunkにはoverlapを付けない方が扱いやすくなります。
6. コピペして試せるサンプルOffice文書を作る
ここからは、架空のExcelとWordを生成します。
Excelには次のsheetを作ります。
| sheet | 内容 | 期待する処理 |
|---|---|---|
Products |
商品一覧 | 表形式として1行1Block |
Maintenance |
月次作業 | 手順形式として複数行を1Block |
QA |
評価用データ | 除外 |
HiddenConfig |
内部設定 | 非表示のため除外 |
Wordには、Heading、本文、箇条書き、表を順番に配置します。
6.1 必要なライブラリ
pip install openpyxl python-docx
6.2 サンプル生成コード
from datetime import datetime
from pathlib import Path
from docx import Document
from openpyxl import Workbook
output_dir = Path("sample_data")
output_dir.mkdir(exist_ok=True)
# ------------------------------------------------------------
# Excel
# ------------------------------------------------------------
workbook = Workbook()
products = workbook.active
products.title = "Products"
products.append([
"商品ID",
"商品名",
"単価",
"在庫数",
"更新日時",
"在庫金額",
"担当",
"担当",
])
products.append([
"ITEM-001",
"ノートPCスタンド",
3200,
12,
datetime(2026, 7, 1, 9, 0),
"=C2*D2",
"運用担当",
"購買担当",
])
products.append([
"ITEM-002",
"USBハブ",
4800,
8,
datetime(2026, 7, 2, 10, 30),
"=C3*D3",
"運用担当",
"購買担当",
])
maintenance = workbook.create_sheet("Maintenance")
maintenance.append([
None,
"作業項目",
"コマンド/確認ポイント",
"備考",
])
maintenance.append([
1,
"月次メンテナンス",
None,
None,
])
maintenance.append([
1.1,
"事前確認",
"samplectl service status",
"状態がrunningであること",
])
maintenance.append([
None,
None,
"samplectl disk check",
"空き容量が20%以上あること",
])
maintenance.append([
1.2,
"バックアップ",
"samplectl backup create --name monthly",
"作成完了を確認する",
])
maintenance.append([
None,
None,
"samplectl backup verify --name monthly",
"検証結果がsuccessであること",
])
maintenance.merge_cells("A3:A4")
maintenance.merge_cells("B3:B4")
maintenance.merge_cells("A5:A6")
maintenance.merge_cells("B5:B6")
qa = workbook.create_sheet("QA")
qa.append(["Question", "Expected Answer"])
qa.append([
"ITEM-001の在庫数は?",
"このsheetは評価専用なので知識ベースへ入れない",
])
hidden = workbook.create_sheet("HiddenConfig")
hidden.append(["Key", "Value"])
hidden.append(["internal_mode", "sample"])
hidden.sheet_state = "hidden"
excel_path = output_dir / "sample_office_docs.xlsx"
workbook.save(excel_path)
# ------------------------------------------------------------
# Word
# ------------------------------------------------------------
document = Document()
document.add_heading("運用ガイド", level=1)
document.add_paragraph(
"この文書はサンプルシステムの基本的な運用方法を説明します。"
)
document.add_heading("バックアップ", level=2)
document.add_paragraph(
"月次作業では、保存先を確認してからバックアップを作成します。"
)
document.add_paragraph(
"実行前に空き容量を確認します。",
style="List Bullet",
)
document.add_paragraph(
"作成後に検証結果を確認します。",
style="List Bullet",
)
table = document.add_table(rows=3, cols=2)
table.rows[0].cells[0].text = "項目"
table.rows[0].cells[1].text = "確認方法"
table.rows[1].cells[0].text = "保存先"
table.rows[1].cells[1].text = "空き容量が20%以上あること"
table.rows[2].cells[0].text = "検証結果"
table.rows[2].cells[1].text = "statusがsuccessであること"
document.add_heading("障害時の確認", level=2)
document.add_paragraph(
"エラーが発生した場合は、時刻とエラーコードを記録してログを確認します。"
)
word_path = output_dir / "sample_operations_guide.docx"
document.save(word_path)
print(excel_path)
print(word_path)
samplectlは説明用の架空コマンドです。
6.3 期待するExcel変換結果
Productsの2行目は、次のようなBlockになります。
{
"source_file": "sample_office_docs.xlsx",
"source_type": "excel",
"block_type": "table_row",
"sheet": "Products",
"row_start": 2,
"row_end": 2,
"fields": {
"商品ID": "ITEM-001",
"商品名": "ノートPCスタンド",
"単価": 3200,
"在庫数": 12,
"更新日時": "2026-07-01T09:00:00",
"在庫金額": "=C2*D2",
"担当": "運用担当",
"担当__H": "購買担当"
}
}
このファイルはPythonで生成した直後なので、Excelによる数式の再計算結果は保存されていません。
そのため、在庫金額には数式が残る想定です。
Maintenanceの3〜4行目は、1つの作業Blockになります。
{
"source_file": "sample_office_docs.xlsx",
"source_type": "excel",
"block_type": "procedure_step",
"sheet": "Maintenance",
"section_path": [
"月次メンテナンス",
"事前確認"
],
"row_start": 3,
"row_end": 4,
"content": "セクション: 月次メンテナンス\ncolumn_A: 1.1 | 作業項目: 事前確認 | コマンド/確認ポイント: samplectl service status | 備考: 状態がrunningであること\nコマンド/確認ポイント: samplectl disk check | 備考: 空き容量が20%以上あること"
}
実行コマンドと確認条件が同じchunkに入っています。
6.4 期待するWord変換結果
バックアップ節の本文と箇条書きです。
{
"source_file": "sample_operations_guide.docx",
"source_type": "word",
"block_type": "word_section",
"section_path": [
"運用ガイド",
"バックアップ"
],
"row_start": 4,
"row_end": 6,
"content": "月次作業では、保存先を確認してからバックアップを作成します。\n- 実行前に空き容量を確認します。\n- 作成後に検証結果を確認します。"
}
直後の表は、同じ見出し階層を継承します。
{
"source_file": "sample_operations_guide.docx",
"source_type": "word",
"block_type": "word_table_row",
"section_path": [
"運用ガイド",
"バックアップ"
],
"table_index": 1,
"row_start": 2,
"row_end": 2,
"body_index": 6,
"fields": {
"項目": "保存先",
"確認方法": "空き容量が20%以上あること"
}
}
7. JSONLへ出力して後段へ渡す
Evidence Blockを1行1JSONで保存します。
import json
from pathlib import Path
FIXED_DELIMITER = "<FIXED_DELIMITER>"
def write_evidence_document(blocks, output_path):
output = Path(output_path)
output.parent.mkdir(parents=True, exist_ok=True)
with output.open("w", encoding="utf-8") as stream:
for block in blocks:
json_line = json.dumps(
block,
ensure_ascii=False,
separators=(",", ":"),
)
stream.write(
f"{json_line} {FIXED_DELIMITER}\n"
)
出力イメージです。
{"source_file":"sample_office_docs.xlsx","source_type":"excel","block_type":"table_row","sheet":"Products","row_start":2,"row_end":2,"fields":{"商品ID":"ITEM-001","商品名":"ノートPCスタンド","単価":3200,"在庫数":12}} <FIXED_DELIMITER>
{"source_file":"sample_office_docs.xlsx","source_type":"excel","block_type":"procedure_step","sheet":"Maintenance","section_path":["月次メンテナンス","事前確認"],"row_start":3,"row_end":4,"content":"..."} <FIXED_DELIMITER>
{"source_file":"sample_operations_guide.docx","source_type":"word","block_type":"word_section","section_path":["運用ガイド","バックアップ"],"content":"..."} <FIXED_DELIMITER>
JSONLにしておくと、次の用途に使いやすくなります。
- chunk単位の再処理
- 出典metadataによるfilter
- 変換前後のdiff
- 検索失敗時の調査
- Embeddingだけの再作成
- Block単位の自動テスト
8. テストで確認すること
構造化Preprocessでは、変換できたかどうかだけでなく、「関係が壊れていないか」をテストします。
Excel
[ ] 複数のvisible sheetを読み込める
[ ] 除外sheetが知識ベースへ入らない
[ ] hidden sheetの初期値が除外になっている
[ ] 数式の保存値を優先し、なければ数式が残る
[ ] 日時がISO 8601になる
[ ] 重複ヘッダーが上書きされない
[ ] 結合セルを含む手順の継続行が同じBlockに入る
[ ] sheet名とrow_start / row_endが残る
Word
[ ] Heading 1〜3の階層を保持する
[ ] 目次styleを除外する
[ ] 本文、箇条書き、表の出現順を保持する
[ ] 表の各行が現在のsection_pathを継承する
[ ] 長文を段落境界で分割する
[ ] 表の1行を途中で分割しない
Chunk
最も重要なテストです。
chunks = split_fixed_blocks(evidence_text)
assert len(chunks) == len(evidence_blocks)
assert all(
FIXED_DELIMITER not in chunk
for chunk in chunks
)
さらに、次も確認します。
[ ] 1 Evidence Blockが1 chunkになる
[ ] 構造化Blockにoverlapが混入しない
[ ] すべてのchunkにsource_fileがある
[ ] Excel chunkにsheetと行番号がある
[ ] Word chunkにsection_pathがある
[ ] 評価用の正解データが混入していない
9. 実運用で失敗しやすいところ
9.1 回答データを知識ベースへ入れてしまう
検索精度が高く見えても、評価用の回答sheetを検索しているだけでは意味がありません。
評価データと知識データは、取り込み時点で分離します。
9.2 すべてのExcelを同じルールで読む
一覧表と作業手順では、意味の単位が違います。
workbook単位ではなく、sheet単位で判定します。
9.3 chunk_sizeだけを何度も調整する
800文字、1,000文字、1,500文字と変更しても、元の構造が壊れていれば改善しません。
まず、表の1行、手順の1ステップ、Wordの1節を正しく作ります。
9.4 元の場所へ戻れない
回答だけを表示できても、利用者が根拠を確認できないと業務では使いにくくなります。
source_file、sheet、section_path、row_start、row_endを最初から持たせます。
9.5 長いBlockを無条件に細かく切る
手順の実行内容と確認条件が分離すると、危険な回答になりやすくなります。
文字数上限より先に、意味の最小単位を決めます。
10. 実装時のチェックリスト
| 段階 | 確認すること |
|---|---|
| Input | 原本を変更せず、別outputへ保存する |
| Excel | 複数sheet、hidden、除外設定に対応する |
| Excel | 表形式と手順形式をsheetごとに分ける |
| Excel | 数式、日時、結合セル、重複列を扱う |
| Word | Heading、本文、箇条書き、表の順番を保持する |
| Schema | 出典metadata付きの共通Blockへ変換する |
| Split | Block境界を通常の文字数分割より優先する |
| Search | IDはText Search、意味検索はVector Searchも使う |
| Rerank | 最終回答へ渡す根拠を絞る |
| Generation | Contextにない内容は推測で補わない |
| Evaluation | 正解ではなく、根拠Blockの到達も評価する |
まとめ
Office文書をRAGで扱うとき、重要なのは「ExcelやWordをテキスト化できたか」ではありません。
重要なのは、質問に答えるための関係を壊さず、検索可能なEvidence Blockへ変換できたかです。
Excel:
sheetを残す
表は1行単位
手順は1ステップ単位
実行内容と確認条件をまとめる
Word:
見出し階層を残す
本文と表の出現順を残す
表へsection pathを引き継ぐ
Chunking:
文字数より構造境界を優先する
1 Evidence Block = 1 Chunk
出典metadataを落とさない
先にParserとChunkingを安定させると、Embedding、Hybrid Search、Rerank、LLM回答の改善を正しく評価できるようになります。
マルチシートExcelやWordをRAGへ入れるときは、まず1つの質問に必要な情報が「同じchunkに残っているか」を確認してみてください。
是非お試しください!