powered by TechFeed
表示モード
Deep Dive

RAGシステムの隠れたコスト削減ポイント — 質問パース後の「チャンク戦略・モデル選択・アクティベーション・監査」をコードではなくデータで制御する

6月18日、Toward Data Scienceが「Dispatching the Parsed RAG Question: Chunk Strategy, Model Tier, Activations, Audit」と題した記事を公開した。この記事では、エンタープライズRAGシステムにおける質問パース後の意思決定フロー——チャンク戦略・モデル選択・アクティベーション制御・監査——を実装レベルで詳述している。

6月18日、Toward Data Scienceが「Dispatching the Parsed RAG Question: Chunk Strategy, Model Tier, Activations, Audit」と題した記事を公開した。この記事では、エンタープライズRAGシステムにおける質問パース後の意思決定フロー——チャンク戦略・モデル選択・アクティベーション制御・監査——を実装レベルで詳述している。


なぜ「質問パース後の処理」が重要なのか

RAGシステムで見落とされがちなのが、ユーザーの質問をパースした後のディスパッチ(dispatch)処理だ。「名前は何ですか?」という質問に対し、単純にキーワード name で全文検索しても、履歴書には「name」という単語は登場しない。人間であれば文書を一瞥して「これは履歴書だから、候補者名を聞いているのだ」と理解する。パーサーも同じ出発点が必要だ。

本記事は、エンタープライズRAGをパーシング・質問パース・検索・生成の4ブロックで構築するシリーズ「Enterprise Document Intelligence」の第6回cパートに当たる。シリーズの前回(第6回bパート)では、質問テキストから直接抽出できる情報——answer_shape(単一値か複数値か)、answer_type(金額・日付・フラグ等)、マッチしたコンセプト名——をパーサーが導出する手順を解説している。本稿(6cパート)では、それらの出力値を受け取り、文書プロファイルを参照してさらに決定する列(チャンク戦略・モデル選択・アクティベーション)を扱う。

※本記事はシリーズの一部として読むと理解が深まる。前回パート(6b)ではパーサーが質問から直接取り出す情報(形状・型・コンセプト)を扱っており、本稿の「3つのディスパッチ決定」はその出力を前提としている。


核心:3つのディスパッチ決定

パーサーは質問解析後に、以下の3つを決定する。すべて「コンセプト単位のオーバーライド → 形状/タイプのデフォルト → プロジェクト全体のフォールバック」という同一カスケードで解決される。

1. どれだけのコンテキストを読むか(answer_context)

StructuralHints 上の3フィールドがこれを制御する:

  • detection_context:正規表現による確認ゾーンの粒度。金額・日付なら "line"、叙述文なら "paragraph"
  • answer_context:生成器に渡す周辺テキストの範囲。"line" / "paragraph" / "page" / "section" / "chapter" / "document" から選択
  • needs_summary:答えが逐語引用に収まらない場合に True

「年間保険料はいくら?」は (single, amount) なので answer_context = "line"。「この契約の除外事項は?」は exclusions コンセプトに一致するため、concepts_df のオーバーライドで answer_context = "chapter" になる。

2. チャンクをどう処理するか(chunk_strategy)

ここが最もコスト効率に直結する部分だ。

  • 単一の事実(金額・日付・IBAN・yes/no)は sequential:検索順位に従い1チャンクずつLLMに投入し、answer_found=True になった時点で停止。k=3のとき、上位1チャンクに答えがあれば入力トークンを約⅔削減できる。
  • 複数箇所から合成が必要な答え(除外条項のリスト、定義と脚注の組み合わせ)は combined:k個のチャンクを1回のLLM呼び出しにまとめる。

この判断はパーサーが一度行い、検索と生成は実行するだけ。エンタープライズスケール(数百万文書 × top-kチャンク)では、この差がLLMコストの大半を占めると記事は指摘する。

解決ロジックのコードは以下のとおり:

def resolve_chunk_strategy(
    answer_shape: str,
    matched_concept: str | None,
    answer_shapes_df: pd.DataFrame,
    concepts_df: pd.DataFrame,
) -> Literal["combined", "sequential"]:
    """Concept-level override > answer-shape default > hard default."""
    if matched_concept is not None:
        row = concepts_df[concepts_df["concept"] == matched_concept]
        if not row.empty and pd.notna(row.iloc[0].get("default_chunk_strategy")):
            return row.iloc[0]["default_chunk_strategy"]
    row = answer_shapes_df[answer_shapes_df["shape"] == answer_shape]
    if not row.empty:
        return row.iloc[0]["default_chunk_strategy"]
    return "combined"

3. どのモデルを呼ぶか(suggested_model)

1行の金額を抽出するのに gpt-4.1 を使うのは無駄だ。かといって3ページの法律文書を小さなモデルに投げると品質が落ちる。

設計では2層のサテライトテーブルを使う。サテライトテーブルとは、メインのロジックコードとは分離された参照用データテーブルのことで、ここではモデル情報をコードに埋め込まず外部データとして管理する手法を指す:

  • llm_model_tiers_df:nano / mini / standard / large の4段階の概念的グルーピング(ベンダー非依存)
  • llm_models_df:プロジェクトが呼び出せる具体的なモデルを1行1モデルで管理

デフォルトはティアではなく具体的なモデル名llm_models_df の外部キー)を指す。gpt-4.1 から gpt-4.5 への切り替えは、llm_models_df の1行更新と評価スイートの再実行だけで済み、コード変更は不要だ。


アクティベーション:文書プロファイルへの適応

文書が期待どおりの構造を持つとは限らない。

Word文書で「1ページ目に何が書いてありますか?」と聞かれた場合、PDFと違い「1ページ目」はフォントや画面幅・プリンタドライバによって変わる。extract_page_numbers=True をハードコードすると、高い確度で誤ったページ参照を返す

記事では DocumentProfile モデルで文書の実際の性質を把握し、アクティベーションを適切に降格させる設計を示している:

class DocumentProfile(BaseModel):
    format: Literal["pdf", "docx", "html", "txt", "xlsx"]
    has_toc: bool = False
    has_tables: bool = False
    n_pages: int | None = None      # フォーマットに実ページがない場合はNone
    languages: list[str] = Field(default_factory=list)
    is_scanned: bool = False        # OCR済み、スペルノイズに注意

def parse_question(question, doc_profile) -> ParsedQuestion:
    parsed = base_parse(question)
    if doc_profile.format == 'docx':
        parsed.activations.extract_page_numbers = False
    if not doc_profile.has_toc:
        parsed.activations.use_toc_navigation = False
    return parsed

よくある落とし穴: アクティベーションフラグをドキュメントタイプに関係なくデフォルト値でハードコードすること。常に extract_page_numbers=True を設定するパイプラインは、実際のページを持たない文書でもページ引用を生成してしまう。アクティベーションは文書の実際のプロパティから導出しなければならない。

parsing_notes フィールドには「パーサーが検知したが強制できなかった制限」が記録され、生成側の _meta ブロックに流れる。ユーザーは誤った確度の高い答えではなく、「このフォーマットではページ参照は近似値です」という注記付きの答えを受け取る。


監査(Audit):意思決定の追跡可能性

タイトルに掲げられた「Audit」は、ここまでの3つのディスパッチ決定すべてに横断する設計上の要件だ。

チャンク戦略・モデル選択・アクティベーションのいずれも、「なぜその値になったか」のトレースが ParsedQuestion オブジェクト内に記録される。どのサテライトテーブルのどの行が適用されたか、コンセプトオーバーライドが発動したかフォールバックで解決されたか——こうした決定の根拠がログとして残ることで、本番環境での挙動を事後に検証できる

エンタープライズ用途では、「なぜそのモデルが選ばれたか」「なぜページ参照が省略されたか」を説明できることが、システムの信頼性と運用保守性を大きく左右する。コードではなくデータで制御する設計は、この監査要件とも表裏一体だ。


設計思想:コードではなくデータで管理する

本記事全体を貫く方針は、意思決定をコードではなくサテライトテーブルに持たせることだ。モデルの追加、デフォルトの変更、コンセプト単位のオーバーライドはすべてSQLの更新で完結する。評価スイートさえ回せば、コードレビューなしにデプロイできる。

この設計が有効なのは、チャンク戦略・モデル選択・アクティベーション・監査の4要素がいずれも「運用中に頻繁に調整が必要になる」性質を持つからだ。新しいLLMモデルのリリース、新たな文書フォーマットへの対応、特定コンセプトの品質改善——こうした変更をコード修正なしに吸収できる構造は、エンタープライズRAGの長期運用において実質的なコスト削減につながる。

詳細はDispatching the Parsed RAG Question: Chunk Strategy, Model Tier, Activations, Auditを参照していただきたい。