RAG(Retrieval-Augmented Generation)では、モデルの性能よりも「投入するテキストの構造」が結果を大きく左右します。
前回の記事ではMarkdown形式をRAG向けに扱いやすくするフォーマットを解説しましたが、今回は具体的なテキストデータを使い、実際に前処理を行ってみます。
実際の例:青空文庫の利用
青空文庫は、日本の著作権が消滅した文学作品を無料で公開しているオンラインライブラリです。 https://www.aozora.gr.jp/
作品は多数公開されていますが、今回は 柳田国男『遠野物語』 を題材に、RAGで扱いやすい形に変換する前処理を具体例付きで説明します。
- 青空文庫で全文公開されており著作権がクリア
- オムニバス形式の短編で、話数・題目が明確なため構造化しやすい
- 民俗・怪異・地名など固有名詞が多く、検索クエリと相性が良い
AIに民話の「語り部」を演じてもらうなど、創作性との相性も期待できます。
RAG利用を想定したフォーマット
本記事では、前回定義したフォーマットを基に、更に目的を絞った 各チャンクに「話番号」「題目」「行番号」「現代語訳」を付与するフォーマット を定義します。
【<話番号>:<題目>:L<行番号>】<現代語文> | 原文: <原文>
これにより、RAG実装時に、検索にヒットしたチャンクの「前後」を識別してLLMに渡せるようになります。
青空文庫テキストそのままではRAGには適さない
青空文庫では、PDFやテキストの形でダウンロードが可能です。 テキストファイルは原著の紙媒体に忠実な構成で、「読む」には適していますが、このままでは「検索」には扱いづらい点があります。
「遠野物語」をRAGで利用する場合の主な問題点は下記のとおりです。
- 題目・注記・奥付が混在
- 原文と注釈が連続している
- 古い表現の日本語が使われている
- 図が含まれている
LLMは単純なキーワードで検索を行うため、この状態でそのままEmbeddingすると、ノイズが増え回答精度が落ちます。 図については、今回はシンプルにするため無視します。
チャンク分割
「遠野物語」の場合、テキストファイル中では日本語の読点「。」までを行とし、改行が付与されており、1行=1意味単位になっています。
RAGでは、1チャンクあたり数百トークン程度に収まるよう設計するケースが多く、日本語では結果として数百〜数千文字程度が一般的です。
本文はこのままでも十分適正なサイズですが、一般には段落を一行単位にするとちょうどよいでしょう。
文字コードと正規化
青空文庫のテキストでShiftJISが使われており、環境によって変換が必要です。 文字コードは混在しやすいため、前処理で統一します。
- Python等、処理系と親和性の良いUTF-8へ統一
- 記号や空白の揺れは最小限に整える
全角スペースや機種依存文字が使われている場合もあり、前処理で正規化しておくことを推奨します。 LLMが扱いやすいよう、今回はルビに使われている《》を()に変換し、漢数字はローマ数字に変換しています。
話の番号と題目の付与
『遠野物語』では各話には通し番号が振られており、題目との対応示されています。
巻頭に「地勢」「山女」などの題目があり、話の番号との対応が示されています。 1つの話が複数の題目を持つケースもあり、分類やグルーピングにも近いやや独特な構造です。
話番号は、話の前後を判断するインデックスとなります。行番号でも可能ですが、題目レベルでの絞り込みに便利です。 序文や題目の部分にも、便宜上の「序文」「題目」という名前を定義し、フォーマットを統一しています。
奥付・注記の扱い
奥付は今回の目的では不要なため、検索対象から外すために本文から切り離します。 人間の管理上は、Markdown形式のFrontmatterとしてYAML形式でまとめておくのもよいでしょう。
現代語文(補助)の考え方
原文だけでなく、意味が取れる程度の現代語訳を付与することで、検索精度を向上させます。 正確性よりも「意味が取れる」こと、検索でヒットしやすいこと、LLMが解釈できることを重視します。 英語等の外国語の文献でも、同様に原文+要約的な補助文を付与することで検索精度が向上します。 現代文もLLMで生成可能ですが、精度が不安定な場合は人手で補正することを推奨します。
整形後の出力例
ChatGPTに元のテキストファイルを与えて、プロンプトを工夫することで直接、テキストを処理させることも可能です。
今回はAIの力を借りて、Pythonコードで整形処理を記述しました。将来的な再利用や仕様の統一を目的としています。 実際は本によって構造が変わるため、直接的なコードの再利用は難しいですが、基本的な構造などは仕様としてコード化にしておくとLLMの理解にも役立ち便利です。
出力例:
【1:地勢:L65】遠野郷は今の陸中上閉伊郡の西半分に位置し、山々に取り囲まれた平地である。 | 原文: 遠野郷(とおのごう)は今の陸中上閉伊(かみへい)郡の西の半分、山々にて取り囲(かこ)まれたる平地なり。
【1:地勢:L66】新町村には、遠野・土淵・附馬牛・松崎・青笹・上郷・小友・綾織・鱒沢・宮守・達曾部という十の村が一町十ヶ村に分かれている。 | 原文: 新町村(しんちょうそん)にては、遠野、土淵(つちぶち)、附馬牛(つくもうし)、松崎、青笹(あおざさ)、上郷(かみごう)、小友(おとも)、綾織(あやおり)、鱒沢(ますざわ)、宮守(みやもり)、達曾部(たっそべ)の一町十ヶ村に分かつ。
【1:地勢:L67】近代または西閉伊郡とも呼ばれ、中古では遠野保とも呼ばれた。 | 原文: 近代或いは西閉伊郡とも称し、中古にはまた遠野保(とおのほ)とも呼べり。
まとめ
この記事では、青空文庫の『遠野物語』を題材に、RAGで扱いやすいテキスト構造へ前処理する方法を解説しました。
この形にしておくと、例えば次のような会話が安定します。 • 山女に関する話を要約して • 狼が登場する逸話を探して • 地勢に関する記述だけ抜き出して
文学作品を、そのまま検索可能な知識ベースにできます。
- 青空文庫TXTは「読む」前提のため、そのままEmbeddingするとノイズが増えやすい
- 1行=1意味単位、参照ラベル付与、ノイズ要素の分離が効く
- Chroma DBでは、本文+メタデータ設計が検索精度を左右する
RAGでは「何を入れるか」より「どう入れるか」が重要です。
次は、このテキストの構造を活かしてRAGを実装する方法を考えます。



