0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Jevで文書の階層構造を探索する!DecisionRAGを実装してみた

0
Last updated at Posted at 2026-11-10

はじめに

前回は、Jevを使った「Self-RAG」について学び、生成した回答が検索文書の内容に基づいているかを評価する仕組みを実装しました。今回は、文書の階層構造を使って必要な情報を探す DecisionRAG(ディシジョンRAG) について学んでいこうと思います。

これまでのRAGでは、Embeddingによる類似度検索を基本としてきました。一方、DecisionRAGでは、文書の階層構造をたどりながら、Jevによって次に探索すべき文書を判断します。

今回は、これまで使ってきたTaskFlowのマニュアルを階層化し、Naive RAGと比較して、検索精度や処理時間がどのように変化するのかを実験していきます。

これまでの記事・実装

DecisionRAGとは?

DecisionRAGとは、文書の階層構造をたどりながら、意思決定モデルを使って関連性の高い文書を段階的に探すRAGの検索手法です。

2026年10月に公開されたRetriever-Free Retrieval: Navigating Documents with Decision Modelsで提案されています。

Naive RAGでは、Embeddingによって質問と文書の類似度を計算し、上位の文書を取得します。一方、DecisionRAGでは、カテゴリや文書の見出しを評価し、関連性の高い階層を順番に探索します。

比較 Naive RAG DecisionRAG
文書の探し方 Embedding類似度 Jevによる関連性判断
文書の構造 フラットなチャンク 階層構造
検索範囲 全チャンクから検索 有望な階層に絞って探索
Jevの役割 なし 探索する文書を選択
特徴 類似度で高速に検索 文書の意味と構造を利用

DecisionRAGの特徴は、質問に似た文章を探すのではなく、質問の答えが存在しそうな場所を判断して探索することです。

例えば、「Standardプランを月の途中で解約したら、日割りになりますか?」という質問を考えます。

Naive RAGでは「日割り」という単語に引っ張られ、「プラン変更時の日割り」などの文書が検索されることがあります。

DecisionRAGでは、まず「料金プラン」「請求」「退会」「セキュリティ」を評価します。関連度が2以上のカテゴリを残し、その中のグループ、さらにその中の文書と、3段階で進みます。

「退会」が残れば「退会後」を見て、「退会後の請求」まで探索できます。一方、カテゴリの段階で除外された枝は、その下を探索しません。

DecisionRAGとは.png

文書の階層構造

今回は、既存のTaskFlowマニュアルを次のようなツリー構造で扱います。

TaskFlowマニュアル
│
├── 料金プラン
│   └── 各プラン
│       ├── Freeプラン
│       ├── Standardプラン
│       └── Proプラン
│
├── 請求
│   ├── 毎月の請求
│   │   ├── 請求日
│   │   └── 領収書
│   ├── 支払い
│   │   └── 支払い失敗
│   ├── 日割り
│   │   ├── プラン変更時の日割り
│   │   └── 日割り請求のルール
│   └── 契約更新
│       └── 契約更新のルール
│
├── 退会
│   ├── 手続き
│   │   └── 退会できる人
│   └── 退会後
│       ├── 退会後の請求
│       └── 退会後のデータ削除
│
└── セキュリティ
    ├── データ保護
    │   └── 通信と保管
    └── 利用権限
        ├── 権限
        └── ログイン

葉となる文書は15件です。Naive RAGでは、一番下の節をフラットなチャンクとして検索します。DecisionRAGでは、カテゴリ・グループ・文書の親子関係を残して探索します。

今回は既存マニュアルをもとに、3段階のツリーを手動で定義しました。階層構造そのものを自動生成する処理は対象外とします。

例えば「退会後の請求」を取得したい場合は、以下のように進みます。

  1. Jevで4つのカテゴリをまとめて採点し、関連度2以上を残す。
  2. 残ったカテゴリのグループをまとめて採点し、関連度2以上を残す。
  3. 残ったグループの文書を本文ごと採点し、点数の高い順に最大3件を選ぶ。
  4. 選んだ文書をLLMに渡す。

カテゴリとグループで関連度2以上の候補が1つもない場合は、その段の最高点の候補を1つ残します。

候補を最初から1つに絞ることはしません。ただし、上位階層で除外された枝の下は探索しないという点が重要です。

処理の流れ

Naive RAGとの大きな違いは、Embedding検索を使わず、Jevによる階層ごとの判断で文書を取得することです。

処理の内容は以下のとおりです。

処理 役割
階層の取得 カテゴリ、グループ、文書を取得
カテゴリの評価 Jevで関連度2以上のカテゴリを残す
グループの評価 Jevで関連度2以上のグループを残す
文書の評価 Jevで本文を採点し、上位3件を選ぶ
Generation 選択した文書を使って回答を生成

元論文では、複数の候補を同じコンテキストに渡し、各候補について関連性を並列に評価しています。

今回はこの考え方を参考に、Jevで各階層の候補をまとめて評価し、有望な枝を順番に探索する構成を実装します。

成果物

今回も架空のSaaS「TaskFlow」のマニュアルを使い、Naive RAGとDecisionRAGの検索結果・回答品質を比較します。

検証する質問は次の2つです。

質問 検証する内容
Standardプランを月の途中で解約したら、日割りになりますか? 「退会後の請求」を取得できるか(正解:日割りなし・満額請求)
退会後、データはいつ削除されますか? 「退会後のデータ削除」を取得できるか(正解:30日後)

1問目では、類似した別の規定に引っ張られず正解文書に到達できるかを確認します。2問目では、単純な質問でも正しく検索できるかを確認します。

実装内容

各段で、候補を1つのstateにまとめてPOST https://api.typesafe.ai/v1/systemoneを1回送ります。モデルはjev-latestです。

候補ごとにscoreを取得し、カテゴリとグループでは関連度2以上を残し、文書では点数の高い順に最大3件を選択します。カテゴリとグループで採用候補が0件の場合は、最高点の候補を1件残します。

採点基準は4段階です。

点数 判断基準
0 関係がない
1 話題は近いが、答えはなさそう
2 答えの一部がありそう
3 質問が聞いている操作に直接答えられそう

指示には「似た言葉があるだけでは点を上げない」と入れています。

① Jevでカテゴリを評価する

1問目で送ったリクエストです。

q0からq3が、料金プラン・請求・退会・セキュリティです。判断基準は4つとも同じで、指示のカテゴリ名だけが違います。

以下のquestionsは一部抜粋で、q1だけを掲載しています。実際にはq0からq3まで送信しています。

{
  "state": "質問: Standardプランを月の途中で解約したら、日割りになりますか?\n\n候補:\n\nq0. 料金プラン\n各プラン\n\nq1. 請求\n毎月の請求、支払い、日割り、契約更新\n\nq2. 退会\n手続き、退会後\n\nq3. セキュリティ\nデータ保護、利用権限",
  "questions": {
    "q1": {
      "type": "score",
      "instructions": "質問「Standardプランを月の途中で解約したら、日割りになりますか?」に対して、このカテゴリ「請求」は回答の根拠になりそうか。似た言葉があるだけでは点を上げない。",
      "criteria": [
        "関係がない",
        "話題は近いが、答えはなさそう",
        "答えの一部がありそう",
        "質問が聞いている操作に直接答えられそう"
      ]
    }
  },
  "model": "jev-latest"
}

返ってきたレスポンスです。

{
  "model": "jev-1.14.0",
  "answers": {
    "q0": {
      "type": "score",
      "score": 1.06,
      "probabilities": {
        "0": 0.12,
        "1": 0.75,
        "2": 0.09,
        "3": 0.04
      }
    },
    "q1": {
      "type": "score",
      "score": 2.44,
      "probabilities": {
        "0": 0.01,
        "1": 0.13,
        "2": 0.27,
        "3": 0.59
      }
    },
    "q2": {
      "type": "score",
      "score": 2.01,
      "probabilities": {
        "0": 0.02,
        "1": 0.30,
        "2": 0.33,
        "3": 0.35
      }
    },
    "q3": {
      "type": "score",
      "score": 0.08,
      "probabilities": {
        "0": 0.94,
        "1": 0.06,
        "2": 0,
        "3": 0
      }
    }
  },
  "usage": {
    "input_tokens": 1007,
    "output_tokens": 60
  }
}

この呼び出しでは、請求が2.44、退会が2.01、料金プランが1.06、セキュリティが0.08でした。

ただし、比較実行では「退会」の関連度が1.94となり、閾値の2を下回ったため除外されています。

「退会」のprobabilitiesを見ると、1点が0.30、2点が0.33、3点が0.35となっており、期待値は2のすぐ近くです。

このように、関連度が閾値の近くにあると、実行ごとのスコアの違いによって探索する階層が変わることが分かります。

② Jevでグループを評価する

比較実行で「請求」だけが残った場合のリクエストです。

候補は「毎月の請求」「支払い」「日割り」「契約更新」の4つです。

こちらもquestionsは一部抜粋で、q2だけを掲載しています。実際にはq0からq3まで送信しています。

{
  "state": "質問: Standardプランを月の途中で解約したら、日割りになりますか?\n\n候補:\n\nq0. 請求 / 毎月の請求\n請求日、領収書\n\nq1. 請求 / 支払い\n支払い失敗\n\nq2. 請求 / 日割り\nプラン変更時の日割り、日割り請求のルール\n\nq3. 請求 / 契約更新\n契約更新のルール",
  "questions": {
    "q2": {
      "type": "score",
      "instructions": "質問「Standardプランを月の途中で解約したら、日割りになりますか?」に対して、このグループ「請求 / 日割り」は回答の根拠になりそうか。似た言葉があるだけでは点を上げない。",
      "criteria": [
        "関係がない",
        "話題は近いが、答えはなさそう",
        "答えの一部がありそう",
        "質問が聞いている操作に直接答えられそう"
      ]
    }
  },
  "model": "jev-latest"
}

このリクエストでは、「日割り」の関連度が2.69となり、ほかのグループは0.95、0.69、0.88でした。

比較実行でも「日割り」が2.70で採用され、残ったグループはこれだけでした。

③ Jevで文書を評価する

最後に、残ったグループの文書を本文ごと同じ形式で採点します。

1問目で探索したのは「請求 / 日割り」で、その配下にある文書は以下の2件です。

  • プラン変更時の日割り
  • 日割り請求のルール

「退会後の請求」は、最初のカテゴリ評価で「退会」が除外されたため、文書評価の候補に入りません。

この段階では、関連度が2未満でも文書を除外せず、点数の高い順に最大3件をLLMへ渡します。

image.png

実行結果

比較はnpm run decisionで実行します。Naive RAGは類似度の上位3件を渡し、DecisionRAGはJevによる階層探索で取得した文書を渡します。

① Standardプランを月の途中で解約したら、日割りになりますか?

Decision RAGの実行結果(解約の日割り)

Naive RAGの結果

検索上位3件は以下のとおりです。

文書 類似度
プラン変更時の日割り 0.7009
契約更新のルール 0.6097
日割り請求のルール 0.5787

必要な「退会後の請求」は含まれていません。

検索は1157 ms、回答生成LLMへの入力トークン数は303、回答生成は1202 msでした。

回答は「マニュアルには記載がないので、分からない」でした。

DecisionRAGの結果

まず、カテゴリの評価結果です。

カテゴリ 関連度 判定
請求 2.49 採用
退会 1.94 除外
料金プラン 1.07 除外
セキュリティ 0.06 除外

「請求」のみが残りました。

次に、「請求」カテゴリ内のグループを評価します。

グループ 関連度 判定
日割り 2.70 採用
毎月の請求 0.93 除外
契約更新 0.85 除外
支払い 0.66 除外

最後に、「日割り」グループ内の文書を評価します。

文書 関連度
日割り請求のルール 1.91
プラン変更時の日割り 1.21

最終的に、この2件をLLMへ渡しました。

探索は908 ms、回答生成LLMへの入力トークン数は240、回答生成は1034 msでした。

回答はNaive RAGと同じく「分からない」でした。

比較と考察

Naive RAG DecisionRAG
探索範囲 全チャンク 請求 / 日割り
文書数 3 2
回答生成LLM入力トークン 303 240
検索・探索時間 1157 ms 908 ms
回答 分からない 分からない

DecisionRAGでも必要な「退会後の請求」を取得できませんでした。

原因は、最初のカテゴリ評価で「退会」の関連度が1.94となり、閾値の2を下回ったことです。

その結果、「退会」配下の「退会後の請求」は探索対象から除外されています。

回答生成LLMへの入力トークンは303から240に減りました。ただし、DecisionRAGでは各階層でJevを呼び出すため、システム全体のトークン数やAPIコストが削減されたわけではありません。

今回の結果から、上位階層の判断ミスが、その下にある正解文書の取りこぼしにつながることが分かりました。

② 退会後、データはいつ削除されますか?

Decision RAGの実行結果(データ削除)

Naive RAGの結果

検索結果の1位は「退会後のデータ削除」で、類似度は0.6478でした。

必要な文書を取得できており、回答は「退会完了から30日後に完全に削除される」でした。

検索は750 ms、回答生成LLMへの入力トークン数は343、回答生成は1788 msでした。

DecisionRAGの結果

まず、カテゴリ評価では「退会」が関連度2.61で採用されました。

次に「退会」配下のグループを評価します。

グループ 関連度 判定
退会後 2.58 採用
手続き 0.88 除外

最後に文書を評価し、「退会後のデータ削除」が関連度2.96で1位になりました。

**「退会 → 退会後 → 退会後のデータ削除」**と、必要な文書まで正しく探索できています。

探索は635 ms、回答生成LLMへの入力トークン数は272、回答生成は1797 msでした。

回答は「退会完了から30日後に削除される」でした。

比較と考察

Naive RAG DecisionRAG
探索範囲 全チャンク 退会 / 退会後
文書数 3 2
回答生成LLM入力トークン 343 272
検索・探索時間 750 ms 635 ms
回答 30日後に削除 30日後に削除

どちらも必要な文書を取得し、正しく回答できました。

DecisionRAGでは、関連する階層を段階的に絞り込むことで、回答に必要な文書を取得できています。

回答生成LLMへの入力トークンは343から272に減りました。ただし、Jevによる評価処理が別途発生するため、全体のトークン消費量とは区別する必要があります。

今回の1回の計測ではDecisionRAGの探索時間が635 ms、Naive RAGの検索時間が750 msでした。しかし、単発の計測だけではDecisionRAGの方が高速とは言えません。

まとめ

今回は、TaskFlowのマニュアルをカテゴリ・グループ・文書の3段階に整理し、Jevのscoreを使って階層構造を探索するDecisionRAGを実装しました。

Naive RAGと比較した結果は以下のとおりです。

質問 Naive RAG DecisionRAG
解約月の日割り 正解文書を取得できず 「退会」を除外して正解文書を取得できず
退会後のデータ削除 正しく回答 正しく回答

1問目では、「退会」の関連度が1.94となり、閾値の2をわずかに下回りました。そのため、配下の「退会後の請求」まで探索できませんでした。

一方、別の呼び出しでは「退会」の関連度が2.01でした。わずかなスコアの変化によって、探索できる文書が大きく変わることが分かります。

2問目では、「退会 → 退会後 → 退会後のデータ削除」と正しく探索でき、Naive RAGと同じ回答が得られました。

今回の学習で特に重要だったのは、DecisionRAGでは上位階層の判断が検索精度に大きく影響することです。

改善策としては、関連度の上位2カテゴリを必ず残す方法や、閾値付近の候補を追加で探索する方法が考えられます。

階層構造を使えば探索対象を絞り込めますが、早い段階で候補を除外しすぎると、必要な文書を見つけられなくなります。

また、今回の実験では回答生成LLMへの入力トークン数は減りましたが、Jevによる評価処理のコストが別途発生します。検索精度だけでなく、全体の処理時間やAPIコストも考慮する必要があります。

階層型検索では、どの文書を選択するかだけでなく、どの候補を探索対象として残すかが重要だと感じました。

参考資料

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?