こんにちは、広野です。
RAG をつくるにはチャンキング戦略が大事!と当社若手エンジニアの野口さんに語られまして。w
ニーズが多いであろう、CSV データからの検索精度向上を目指してみました。本記事はアーキテクチャ編で、続編記事で実装編の公開を予定しています。
やりたいこと (前置き)
以下のような架空のヘルプデスク問い合わせ履歴データ (CSV) を用意しました。
ヘルプデスク担当者が、新たな問い合わせを受けたときに似たような過去の対応を引き当てられるようにしたい、というのが目的です。
参考記事
当社エンジニア野口さんの記事。本件の実現にあたり、相談をさせて頂きました。ありがとう!
以前、私が公開した Amazon Bedrock Knowledge Bases や Amazon S3 Vectors を使用した RAG 基盤の記事。今回はこの基盤のチャンキング戦略をカスタマイズして臨みました。
本記事の言及範囲
RAG そのものや、RAG 基盤については本記事では語りません。
以下のアーキテクチャ図の中の、赤枠の部分に着目します。ベクトルデータを格納するまでの、データソースのチャンキングをどう設計、実装するかです。
やりたいこと (再掲)
以下の CSV データを読み込ませて、似たような対応を引き当てたいと前置きで申し上げました。
もう少しやりたいことをブレークダウンします。
- LLM に、今届いた新しい問い合わせに対する回答案を提案させたい。
- 回答案を生成するために、自然言語で書かれた問い合わせ内容と回答内容から、意味的に近いデータを引き当てたい。
- カテゴリで検索対象をフィルタしたい。その方が精度が上がるケースがあると考えられる。
- LLM が回答案を提案するときには、参考にした過去対応履歴がどの問合せ番号のものか、提示させたい。その問合せ番号をキーに、生の対応履歴データを参照できるようにしたい。
以下の前提があります。
- データソースとなる CSV ファイルは 1つのみ。過去の対応履歴は 1 つの CSV ファイルに収まっているということ。
- つまり、データの1行が1件の問い合わせであり、その項目間には意味的なつながりがある。
まあ、ごくごく一般的なニーズではないかと思います。
アーキテクチャ
実現するために、どんなアーキテクチャにするべきか。
1行の中にある各項目は意味的なつながりがあり、これらをチャンク分けしてしまうと意味的な関連性が切れてしまいます。そのため、1行1チャンクにするのが適切だと考えられます。
チャンクを引き当てた後は、それに紐づく各データを参照したいので、メタデータを定義することが必要です。それがあれば、参考データとしてメタデータを持ってくることができます。また、メタデータを定義した項目については、その項目でフィルタすることができます。
なるべく安くてマネージドなサービスを使用したく、冒頭で紹介したアーキテクチャ図にあるように、Amazon Bedrock Knowledge Bases と Amazon S3 Vectors を使用します。
ここで課題が。
Amazon Bedrock Knowledge Bases のチャンキング戦略は組み込みでいくつか用意されているのですが、構造化データの「1行1チャンク」というパターンは存在しないのです。
そのため、カスタムのチャンキング戦略を作成するしかなく、そのための手段として AWS Lambda 関数で実装する機能が用意されています。
実際その仕組みを作ってみて理解したのですが、以下のようなアーキテクチャになります。
だいたい図の中の丸数字を見ていただければ理解いただけると思うのですが、一応説明します。前提として、ドキュメント用 S3 バケットに CSV データが入っています。
- Amazon Bedrock ナレッジベースの同期ボタンを押します。
- ナレッジベースは、ドキュメント用 S3 バケットにある CSV データを取得します。
- CSV データを、所定のフォーマットの JSON にして、中間成果物用 S3 バケットに保存します。JSON フォーマットは決まっていて、そこに取得した CSV データのテキストをそのまま格納する感じです。Amazon Bedrock ナレッジベースとしてのチャンク戦略は「なし」にする必要があります。チャンク分割せずデータまるごと、後続の Lambda 関数でチャンク分割する設計です。仕様ですが、中間成果物用 S3 バケットはドキュメント用 S3 バケットとは別にする必要があります。
- Bedrock ナレッジベースは、保存した JSON データ (と言っても実質 CSV データ部分しか使用されない) を処理するため、チャンク分割用カスタム Lambda 関数を呼び出します。
- Lambda 関数は、Bedrock ナレッジベースから渡された JSON データのバケットやキーを元に CSV 部分のデータを取得し、1行単位で1チャンクにし、さらにメタデータを付加した JSON データに加工して中間成果物用 S3 バケットに書き戻します。
- Bedrock ナレッジベースが、Lambda 関数が作成してくれた加工後 JSON データをもとにベクトル化し、S3 Vectors に書き込みます。
チャンク分割された後のデータ構造
Lambda 関数がチャンク分割した後のデータ構造 (上のアーキテクチャ図では 5番の処理によって作成されるもの) は、以下のようにします。
{
"fileContents": [
{
"contentBody": "問合せ番号: AB01234569\n商品番号: SH001-01BL\n\n問合せ内容:\n[問合せ内容の文章]\n\n回答内容:\n[回答内容の文章]",
"contentType": "TEXT",
"contentMetadata": {
"問合せ番号": "AB01234569",
"販売形態": "代理店",
"受付日時": "2026/2/23 12:59",
"完了日時": "2026/2/23 13:39",
"商品番号": "SH001-01BL",
"カテゴリ": "家庭用収納棚",
"ステータス": "完了"
}
},
{
"contentBody": "問合せ番号: AB01234573\n商品番号: TB19541\n\n問合せ内容:\n[問合せ内容の文章]\n\n回答内容:\n[回答内容の文章]",
"contentType": "TEXT",
"contentMetadata": {
"問合せ番号": "AB01234573",
"販売形態": "直販",
"受付日時": "2026/2/24 9:15",
"完了日時": "2026/2/24 14:30",
"商品番号": "TB19541",
"カテゴリ": "家庭用テーブル",
"ステータス": "完了"
}
}
]
}
- fileContents 配列の各要素が 1 チャンク(CSV の 1 行に相当)
- contentBody がベクトル化・検索対象にできるテキスト
- contentMetadata が引用表示やフィルタリングに使用されるメタデータ ※contentBody ももちろん引用可能
contentBody、contentMetadata にどの項目を含めるかはデータや設計次第で変わりますが、データソースの CSV データをこのフォーマットに落とし込めれば勝ちです。
以降は、実装編の記事で詳細を説明いたします。
続編記事
続編記事を公開でき次第、こちらに掲載します。
まとめ
いかがでしたでしょうか。
本記事が皆様のお役に立てれば幸いです。





