RAGを構築してもうまくいかない時にまず調べること:それはチャンク戦略だ!
こんにちは。D2Cのデータサイエンティスト(AIエンジニア)の須田です。
「どのテーブルを使えばいいかわからない…」
社内データ活用の現場でよく聞くそんな声に応えるため、
自然言語で質問するだけで、社内データベースから必要な情報を探し、SQLまで生成してくれるAIツール「Kibidango」を開発・リリースしました。
機能の詳細については以下の記事をご覧ください👇
▶︎ 社内データ活用を加速する“Kibidango”の新機能!テーブルレコメンド×SQL生成で検索体験を革新
本記事ではRAG(Retrieval Augmented Generation)を活用する中で得た学びをお伝えします。
Kibidangoの課題と目標
初期のKibidangoは「テーブルを選んで自然言語で質問し、SQLを生成する」形式でした。
しかし実際に運用してみると「そもそもどのテーブルを選べば良いかわからない」「詳しい人に質問が集中してしまう」といった声があがってきました。
社内のデータ構造は複雑で、新しく入った人がキャッチアップするにも時間がかかります。
そこで「自然言語で、目的に合ったテーブルをレコメンドしてくれる機能があれば、業務の効率化に繋がるはず」と考え、開発に踏み切りました。
Kibidangoのアーキテクチャ
ここで一度、Kibidangoの構成を簡単に図にまとめてみました。
この構成では、ユーザーがUI(たとえばStreamlit)から自然言語で質問を入力すると、そのリクエストはAmazon ECS上のアプリを通じてClaude 3 Sonnetへ送られます。
Claudeは、OpenSearch上にインデックスされたテーブル情報をベースにレコメンド処理を行い、最終的にユーザーへ「このテーブルが良さそうです」と返します。
図の青い部分が今回新たに設計・追加された領域で、社内のS3バケットやOpenSearchと連携している点がポイントです。
試したこと
まずは既存のDDL(データ定義)を活用してできることを試しました。
- DDLに簡単なテーブル要約を追加
- タグ情報(用途やドメイン)を付加
- データレイクのカタログの情報を整形して取り込み
- LLMプロンプトを工夫して回答精度を上げる工夫
ただし、これらだけでは思うような結果は得られませんでした。
つまり「データが多ければ良い」という単純な話ではなかったのです。
ハルシネーションには2つ種類がある
1. LLM自体の問題
「ナレッジベースからの検索はうまくいっているが、LLMが適切な回答を生成できていない」というケースです。
この場合は、RAGの「生成」部分に問題がある可能性があります。
考えられる要因
- LLMの精度が不十分
- モデルがそもそも対象ドメインの知識に弱い、複雑な構文理解が苦手など
- プロンプトテンプレートが最適化されていない
- 情報がうまく渡せていない、出力形式の指示が曖昧、意図が伝わりにくい など
- 検索で得たチャンクが多すぎてLLMが混乱している
- 入力トークンが多くなりすぎて、文脈理解が難しくなっている可能性も
2. LLMにくわせる前、データベースから取得できていない問題
これは、RAGの「検索」部分でうまく情報が取り出せていないケースです。
つまり、ユーザーの質問に対して適切な情報がナレッジベースから引っ張れていない状態です。
原因の一つとしてよくあるのが、「チャンク戦略がうまく設計されていない」という問題です。
チャンクとは?
RAG(Retrieval Augmented Generation)では、元となるテキストデータをそのまま一括で使うのではなく、小さな意味のまとまり=「チャンク(chunk)」に分割して、ベクトル検索用のインデックスとして保存します。
このチャンクには以下のような目的があります:
-
検索の精度を高める
→ 小さい単位で意味が通るようにすることで、似た文脈がマッチしやすくなる -
回答生成時の文脈を維持する
→ 必要な情報が文ごとに残っていれば、ハルシネーションを減らせる -
しかし、このチャンクの切り方が不適切だと以下のような問題が起きます:
- 文の途中で切れてしまい意味が伝わらない
- テーブル名と説明が別チャンクに入り、結びつかない
- カラム情報や用途がばらばらになり、検索対象にならない
チャンク戦略の例
- テーブル単位でチャンク化する
- カラム単位でチャンク化する
- 説明文とテーブル名を1セットにしてチャンク化する
など、目的やデータ構造に応じたチャンクの工夫が、RAGの精度に直結します。
今回採用しているAWS Bedrockでは以下のチャンク戦略がデフォルトで設定されていました。
デフォルトの300トークン単位で切ると文脈が破壊される
テーブルごと/カラムごと/1行ごと に分けるなど、工夫が必要
チャンク関連の話はこちらのブログがとても参考になりました。
このように、「ハルシネーション」の原因が検索段階にある場合には、まずチャンクの粒度や構造を見直すのが効果的です。LLMが悪いのではなく、「そもそも必要な情報にたどり着けていない」ことがよくあるため、まずは「生成が悪いのか、検索が悪いのか?」を切り分けることが何よりも重要です。
そのためには以下のようなアプローチを取ることができます。(AWSのKnowladge baseを利用していることを前提としています)
チャンク戦略の確認方法(RAGの構造を調べる)
aws bedrock-agent-runtime retrieve
コマンドでナレッジベースの検索結果を確認
1. AWSのコンソールからCloudShellを開き、以下のコマンドを実行します。
aws bedrock-agent-runtime retrieve --knowledge-base-id <ナレッジベースのID> --retrieval-query '{"text": "デモグラデータを集計したい時はどのテーブルを使えば良い?"}' --retrieval-configuration '{"vectorSearchConfiguration": {"numberOfResults": 3}}'
次のようなアウトプットが出てくるはずです。
scoreから--retrieval-query '{"text": "デモグラデータを集計したい時はどのテーブルを使えば良い?"}'との検索一致度がわかります。
{
"retrievalResults": [
{
"content": {
"text": "検索結果の内容",
"type": "TEXT"
},
"location": {
"s3Location": {
"uri": "xxx"
},
"type": "xx"
},
"metadata": {
"xxx": "xxx"
},
"score": 0.xxx
}
2. OpenSearchの中身を確認する
OpenSearchを開き、[Serverless]ボタン→[Dashboard] ボタン → 指定のダッシュボードをクリック→左上サイドバーから[Dev Tools]ボタンをクリック。(自分が作成したダッシュボードしか表示されないことがあるため権限に注意)
上記画面になったら次のコマンドを入力し実行します。
GET bedrock-knowledge-base-default-index/_search
{
"_source": ["AMAZON_BEDROCK_TEXT"],
"query": { "match_all": {} }
}
以下のようなアウトプットが出てくるはずです。(ベクトルDBに入れている内容によってアウトプットの中身も変わります)
### エントリ 1 ###
{ "xxx": "xxx", "xxx": "xxx", "xxx": [ "xxx", "AdMaster", "xxx", "xxx", "xx", "xxx"]}
チャンク戦略について確認した後に考えられるアプローチ
チャンクの切り方や内容を確認した結果、「必要な情報がきちんと含まれていない」あるいは「含まれていても上位に出てこない」などの課題が見つかった場合、次にとるべきアプローチはいくつかあります。
1. チャンクの再構成(切り方の見直し)
- カラム単位では細かすぎる/文脈が失われている → テーブル単位や意味のまとまり単位で再構成
- 逆にテーブル単位では大きすぎる/検索精度が低下している → セマンティックチャンクや、説明文・用途文単位に分割
- チャンク内に含める情報の順序・フォーマットを揃える → LLMにとって読みやすい構造に変える(例:見出し付き、ラベル統一)
2. メタ情報の拡充
- descriptionやcommentが短い/抽象的すぎる → 用途や具体的な利用例、業務での使われ方を補足
- タグやドメイン、部門情報などを付与 → 質問の意図とのマッチング精度を高める
- ナレッジベースの内容にFAQ・サンプル質問と回答を追加 → LLMが意図を汲み取りやすくなる
LLMに問題があると特定された場合のアプローチ
RAGの構成上、検索結果(チャンク)は適切に取得できているのに、最終的な出力がおかしい/ハルシネーションが多いというケースでは、LLM自体の課題が疑われます。以下のような改善策を検討するのが効果的です。
1. プロンプトテンプレートの見直し
- 出力形式が曖昧/自由すぎる
→ 表形式や箇条書きなど、出力構造を明確に指定 - 条件分岐が不足している
→ 「該当するテーブルがなければこのように答えてください」など、想定外出力を防ぐ設計を強化 - 説明文が長すぎ/複雑すぎ
→ prompt内のシステムメッセージや指示を簡潔にして、意図が伝わりやすい内容に改善
2. モデルの変更・アップグレード
- Claude 3.5 → Claude 3.7 など、より最新・高性能なモデルを利用することで精度が大きく改善するケースあり
- タスクごとに異なるモデルを使い分ける
→ 例:検索タイプの判定に軽量モデル、最終回答生成に高精度モデルを使う構成
Kibidangoで実際に取り組んだ工夫
1. 答えてほしい内容について整理をすること
チームで話し合った結果、まずはどんな質問に答えられたらよいか?を明確にするために考えれる質問の分類と優先順位づけ、想定質問を考えることを行いました。
▼ 実際に作った表
質問のタイプ | 特徴 | 解決アプローチ |
---|---|---|
テーブルの詳細を知る | スキーマ情報が明示的に含まれている | DDLの整備 |
テーブルの用途を知る | 説明文が整備されていればヒットしやすい | descriptionの強化 |
関連するテーブルを知る | JOIN可能かどうか判断できれば対応可能 | リネージ情報の追加・補完 |
データの取得方法を知る | サンプルクエリがあれば提案しやすい | クエリ例をナレッジベースに追加 |
ID変換方法を知る | 明示的ルールがないと難しい | 対応表の整備・プロンプト制御で補完 |
→解決アプローチ的にDDLの整備とdescriptionの強化が一番手をつけやすそうということからテーブルの詳細を知ったり、用途を知る質問に答えられるようにしようとスコープを絞りました。
テーブルの詳細については既存の機能で聞ける内容でもあったため、テーブル用途を知ることをメインとして以下の想定質問を考えました。
- 2024年12月中にxxxをした人を分析できるテーブルを教えて
- xxxのxxxユーザーを抽出したいです。
この質問は実際にステークホルダーからどういう質問に答えてくれたら嬉しいか?を伺い、決定しました。
2. 想定質問に合わせたデータを用意する
社内のステークホルダーにこういう質問ができる機能を作りたい。と相談したところ
用途ごとに整備されたテーブル情報を社内の熟知者から提供いただくことになりました。
そのテーブル情報をナレッジベースに投入したところ、レコメンドの精度が劇的に改善しました。
もしいただいていなかったら、自分達で作成していたと思います。本当にありがとうございます!
まとめ:RAG活用のコツとKibidangoで得た学び
Kibidangoの開発を通じて、RAG(Retrieval Augmented Generation)を本番運用レベルまで持っていくには「設計・検証・整備」の地道な積み重ねが不可欠だと実感しました。
特に、以下の3つの視点はとても重要です:
1. 「検索」と「生成」の問題を切り分けて改善
- aws bedrock-agent-runtime retrieveやOpenSearchの中身を確認し、取得できていないのか、生成が間違っているのかを切り分ける
- LLMのプロンプト設計やモデル選定も合わせて検証する
2. “どの質問に答えられるようにしたいか?”の明確化
- 質問のタイプを整理し、AIに得意なパターンから対応していく
- スコープを広げすぎず、まずは用途に絞ってデータ整備を進める
3. 必要に応じてチャンク戦略を“データ構造に合わせて設計する”
- ただ分割するのではなく、文脈がつながるように構造を考慮
- テーブル・カラム単位か、用途単位かなど、最適な粒度を見つける
本記事が、RAGを活用した社内システムの構築や改善に挑戦する方にとって、少しでも参考になれば嬉しいです。

株式会社D2C d2c.co.jp のテックブログです。 D2Cは、NTTドコモと電通などの共同出資により設立されたデジタルマーケティング企業です。 ドコモの膨大なデータを活用した最適化を行える広告配信システムの開発をしています。
Discussion