Spaces:
Sleeping
A newer version of the Streamlit SDK is available: 1.56.0
ResponseQualityAssessment リポジトリ解析
対象コード:
ResponseQualityAssessment/(conformal-rag-demo サブモジュール) 論文: "Response Quality Assessment for Retrieval-Augmented Generation via Conditional Conformal Factuality"(SIGIR 2025 想定)
1. システム概要
RAG(Retrieval-Augmented Generation)システムの回答品質を評価するパイプラインの実装。Conformal Prediction を用いて、LLM の回答をサブクレームに分解し、各サブクレームの事実性スコアを計算・校正することで、統計的保証付きの品質評価を実現する。
主な研究貢献
- Split Conformal Prediction: キャリブレーションデータから閾値を算出し、テストデータへ適用
- Group Conditional Conformal: グループ別(例: 医療カテゴリ)の閾値を独立に算出
- 複数のスコアリング手法(類似度・頻度・対数確率など)の横断比較
2. ディレクトリ構成
ResponseQualityAssessment/
├── conf/
│ ├── config.yaml # 実行全体の設定
│ ├── dataset_config.yaml # データセット別設定
│ └── path_config.yaml # ファイルパス設定
├── data/
│ ├── raw/ # 生データ(FactScore, HotpotQA, PopQA, MedLFQA, WikiDB)
│ ├── processed/ # 標準化済みクエリ・文書データ
│ ├── out/ # サブクレーム+スコア出力
│ └── result/ # 最終結果・可視化
├── index_store/ # FAISS インデックスとマッピング
├── logs/ # 実行ログ
├── src/
│ ├── calibration/ # 共形予測キャリブレーション
│ ├── common/ # 共通コンポーネント(設定・ファイル・LLM 管理)
│ ├── data_processor/ # データセット処理パイプライン
│ ├── dataloader/ # HuggingFace データローダー
│ ├── rag/ # RAG(SQLite ベース文書 DB)
│ ├── subclaim_processor/ # 回答品質評価の中核パイプライン
│ └── utils/ # 汎用ユーティリティ
├── main.py # エントリーポイント
└── requirements.txt # 依存ライブラリ
3. 使用ライブラリ
| ライブラリ | 用途 |
|---|---|
openai |
LLM API(GPT-4o-mini による応答生成・サブクレーム抽出・アノテーション)、埋め込みモデル |
faiss-cpu |
ベクトル近傍探索(FAISS IndexFlatIP、L2 正規化後の内積=コサイン類似度) |
sentence-transformers / transformers |
埋め込みモデル(補助的) |
datasets |
HuggingFace からの QA データセット取得 |
langchain |
LLM オーケストレーション |
numpy |
数値計算(共形予測の分位点計算、スコア演算) |
torch |
Transformer モデルのバックエンド |
PyPDF2 |
PDF 文書のテキスト抽出 |
matplotlib |
キャリブレーション結果の可視化 |
jsonschema |
各フェーズの出力データのスキーマ検証 |
python-dotenv |
.env からの API キー読み込み |
flask / flask-cors |
Web API(コードに存在するが現状未使用) |
Python バージョン: 3.11
4. アーキテクチャ設計
4.1. 設計パターン
| パターン | 適用箇所 |
|---|---|
| Strategy パターン | スコアリング戦略(ProductScoreStrategy)、集約戦略(MeanAggregation / MaxAggregation)、チャンキング戦略 |
| Template Method パターン | RawDataProcessor(抽象基底)← データセット別実装、ICalibration ← 共形予測実装 |
| Factory / Dispatcher パターン | QueryProcessor がデータセット名に基づき各プロセッサへ委譲 |
| Manager パターン | ConfigManager、FAISSIndexManager、FileManager、OpenAIManager による各リソースのライフサイクル管理 |
| Pipeline アーキテクチャ | main.py で各ステージを順次呼び出す直列パイプライン |
| Dependency Injection | コンストラクタ経由でコンポーネントを注入(faiss_manager、scorer など) |
4.2. コンポーネント相関図
main.py
├─ ConfigManager … YAML 読み込み・ログ設定
├─ DataLoader … HuggingFace / Wikipedia SQLite DB
├─ QueryProcessor … データセット別に標準化
├─ FAISSIndexManager … ベクトルインデックス作成・検索
│ └─ OpenAIManager … text-embedding-3-large で埋め込み生成
├─ process_subclaims() … サブクレーム処理オーケストレーター
│ └─ SubclaimProcessor
│ ├─ OpenAIRAGAgent … RAG 応答生成
│ ├─ OpenAIAtomicFactGenerator … サブクレーム抽出(logprobs 付き)
│ ├─ OpenAIClaimVerification … 事実性アノテーション(S/I/U/N)
│ └─ SubclaimScorer … 7 種スコア計算
└─ SplitConformalCalibration / GroupConditionalConformal
└─ calibration/utils … r_a スコア・分位点閾値計算
5. 処理パイプライン詳細
Step 1: データ読み込み・標準化
| 処理 | 実装クラス |
|---|---|
| HuggingFace からデータセット取得 | DataLoader.load_qa_data() |
| Wikipedia SQLite DB 構築 | DataLoader.create_wiki_db() |
| データセット別正規化 | FactScoreProcessor, HotpotQAProcessor, PopQAProcessor, MedLFQAProcessor |
データセット別 外部コーパス対応表
FAISS インデックスの構築・検索に用いる外部コーパスはデータセットによって異なる。
| データセット | 外部コーパス | 取得元 / 形式 |
|---|---|---|
| FActScore | Wikipedia(2023-04-01 ダンプ) | bz2 ダンプ → SQLite DB(enwiki-20230401.db) |
| HotpotQA | Wikipedia(2023-04-01 ダンプ) | 同上(HuggingFace kilt_tasks/hotpotqa と組み合わせ) |
| PopQA | Wikipedia(2023-04-01 ダンプ) | 同上(HuggingFace akariasai/PopQA と組み合わせ) |
| MedLFQA | MedLFQAv2 の QA データセット自体 | GitHub jjcherian/conformal-safety の JSONL ファイル群(healthsearch_qa, kqa_golden, kqa_silver_wogold, live_qa, medication_qa) |
FActScore・HotpotQA・PopQA は共通の Wikipedia SQLite DB(DocDB)から文書を取得する。MedLFQA のみ Wikipedia を使わず、各 JSONL ファイルに格納された retrieved_passages フィールドの文書をそのままコーパスとして使用する。
出力形式(JSON):
{"input": "クエリ文字列", "output": {"answer": "正解", "provenance": [...]}}
Step 2: FAISS インデックス構築
構築フロー
全データセット共通で、以下の 2 フェーズに分かれる。
フェーズ A: 文書収集(データセット別)
各プロセッサが「どこから文書を取ってくるか」が異なり、最終的に同一 JSON 形式で保存される。
| データセット | 文書取得元 | 1クエリあたり文書数 |
|---|---|---|
| FActScore | Wikipedia SQLite DB(provenance タイトル 1 件でタイトル引き) | 1 記事 |
| PopQA | Wikipedia SQLite DB(s_wiki_title 1 件、重複排除あり) |
1 記事 |
| HotpotQA | Wikipedia SQLite DB(provenance の複数タイトル、重複排除あり) | 2〜3 記事 |
| MedLFQA | JSONL の Free_form_answer を文単位に分割 + Nice_to_have リスト |
10〜20 文(短文) |
Wikipedia 3 データセットは Wikipedia 全文をそのまま FAISS に入れるのではなく、各クエリの provenance タイトルに対応する記事のみを SQLite から引き当てて保存する。MedLFQA は Wikipedia を使わず、JSONL ファイルに格納済みの文書を直接使用する。
フェーズ B: 埋め込み・インデックス化(全データセット共通、main.py:211-275)
- 文書を Fixed-Length チャンカー(デフォルト: 2000 語、25 語オーバーラップ)で分割
- OpenAI
text-embedding-3-large(次元数: 3072)で埋め込みベクトルを生成 - L2 正規化 →
IndexFlatIP(内積 ≒ コサイン類似度)に追加 - インデックスバイナリと
indice2fmマッピング(FAISS インデックス ID → ファイル位置)を保存
各データセット独立したインデックスとして index_store/{Dataset}/ に保存される。インデックスは .gitignore によりリポジトリ管理外であり、実行のたびに再構築が必要。
text-embedding-3-large の構築コスト見積もり(query_size: 500、$0.13/1M トークン)
| データセット | ユニーク文書数の目安 | チャンク/文書 | 総トークン数 | 推定コスト |
|---|---|---|---|---|
| FActScore | ~500 記事 | ~2 | ~2.7M | ~$0.35 |
| PopQA | ~500 記事 | ~2 | ~2.7M | ~$0.35 |
| HotpotQA | ~1,000 記事(2〜3 provenance × 重複排除) | ~2 | ~5.4M | ~$0.70 |
| MedLFQA | ~500 クエリ × ~15 文 | チャンク分割なし(短文) | ~0.2M | ~$0.03 |
| 合計(4 データセット) | ~11M | ~$1.4 |
これは1 回限りのインデックス構築コスト。推論時のクエリ埋め込み・サブクレーム埋め込みは数千トークン程度で無視できる。Wikipedia 記事長のばらつきにより±2 倍程度の誤差あり。
Step 3: 応答生成
SubclaimProcessor.generate_responses():
- クエリに対し FAISS から上位 k 件(デフォルト: 10)の文書を検索(閾値: 0.3 以上)
- 検索文書をコンテキストとして GPT-4o-mini に渡し、初期回答を生成
Step 4: サブクレーム抽出
OpenAIAtomicFactGenerator.get_facts_from_text():
- LLM に回答テキストを渡し、セミコロン区切りの原子的事実に分解
logprobs=True, top_logprobs=1でトークンごとの対数確率を取得- 各サブクレームの対数確率リストを保持
Step 5: サブクレームスコアリング
SubclaimProcessor.score_subclaim() で 7 種のスコアを計算:
| スコア名 | 計算方法 | 意味 |
|---|---|---|
relavance(relevance) |
FAISS 検索スコア × サブクレーム-文書コサイン類似度 の積、文書間で集約 | 検索文書との総合関連度 |
query_claim_cosine_similarity |
クエリ埋め込み ↔ サブクレーム埋め込み のコサイン類似度 | クエリとの意味的整合性 |
doc_claim_cosine_similarity |
全検索文書とサブクレームのコサイン類似度の最大値 | 文書との意味的整合性 |
frequency |
温度 1.0 で 5 回サンプリングし、同内容のサブクレームが出現した割合 | LLM 自体の一貫性(自己信頼度) |
min_log_prob |
サブクレーム中のトークン対数確率の最小値 | LLM の生成確信度 |
random |
Uniform(0, 1) | ベースライン |
ordinal |
i / サブクレーム数 |
応答内の出現順序(ベースライン) |
全スコアに N(0, 0.001) のガウスノイズを付加(安定化のため)
集約戦略(文書間集約):
MeanAggregation: 文書スコアの平均MaxAggregation: 文書スコアの最大値
スコアリング戦略:
ProductScoreStrategy: FAISS スコア × コサイン類似度 の積(現状唯一の実装)
Step 6: アノテーション
OpenAIClaimVerification.annotate():
- サブクレームを検索文書・正解と照合し、GPT-4o-mini が 4 段階ラベルを付与
| ラベル | 意味 |
|---|---|
S (Supported) |
事実として支持される |
I (Irrelevant) |
質問と無関係 |
U (Unverifiable) |
検証不可能 |
N (Nonfactual) |
事実に反する |
Step 7: 共形キャリブレーション
Split Conformal Prediction
For each alpha in [0.05, 0.10, ..., 0.40]:
Repeat 1000 runs:
1. データをシャッフルし 50/50 でキャリブ/テストに分割
2. キャリブデータで各サブクレームの r_a スコアを計算:
r_a(x) = "サブクレームを低スコア順に除去したとき、
残存サブクレームの正確率が初めて a 以上になる最小閾値"
3. 閾値 q̂ = ceil((n+1)*(1-alpha))/n 分位点(共形予測の保証付き計算)
4. テストデータで閾値 q̂ を適用し、除去率・正確率を計算
キャリブレーションのデータ量について
キャリブレーションに使うデータは query_size で処理したクエリ全体を 50/50 分割したものであり、専用のキャリブレーションセットは別途用意しない。デフォルト(query_size: 500)の場合、1 ランあたりキャリブレーション 250 件・テスト 250 件となる。各データセットの総件数(HotpotQA ~5,600 件、PopQA ~14,267 件など)に対して 500 件はサンプリング上限であり、query_size を増やすほどキャリブレーション精度は上がるが LLM 呼び出しコストも線形に増加する。1000 回のランダムシャッフルを繰り返すことで、少ないサンプル数でも統計的ばらつきを吸収している。
Group Conditional Conformal
GroupConditionalConformal: グループ(MedLFQA のカテゴリなど)ごとに独立してキャリブレーションを実行し、グループ別閾値を算出。
Step 8: 結果出力
- CSV: alpha × confidence_method ごとの除去率・正確率
- PNG プロット: 共形除去カーブ(除去率 vs. 事実性保証)、事実的正確率カーブ
- JSON: 全サブクレーム(スコア・アノテーション込み)
- config/: 再現性のため実行設定の YAML コピー
6. データフロー
生データ(HuggingFace / bz2 Wikipedia)
↓ DataLoader
SQLite Wikipedia DB + 標準化クエリ JSON
↓ FileManager + OpenAIManager
チャンク埋め込み行列(N × 3072)
↓ FAISSIndexManager
FAISS IndexFlatIP + indice2fm マッピング
↓ SubclaimProcessor.generate_responses()
{query, gld_ans, retrieved_docs, response, groups}
↓ OpenAIAtomicFactGenerator
{subclaim_text, log_prob_list}(サブクレーム + 対数確率)
↓ SubclaimScorer (7 種スコア)
{scores: {relavance, query_claim_cosine, ..., ordinal}}
↓ OpenAIClaimVerification
{annotations: {gpt: "S"|"I"|"U"|"N"}}
↓ SplitConformalCalibration / GroupConditionalConformal
除去率・正確率の統計(1000 runs × alpha × method)
↓
CSV / PNG / config YAML
7. 設定ファイル
conf/config.yaml(主要パラメータ)
dataset:
name: "pop_qa" # fact_score | hotpot_qa | pop_qa | medlf_qa
query_size: 500 # 処理クエリ数(-1 = 全件)
index:
embedding_model: "text-embedding-3-large"
truncation_config:
strategy: "fixed_length"
chunk_size: 2000 # チャンクあたり単語数
chunk_overlap: 25
rag:
retrival_topk: 10 # 検索上位件数
retrival_threshold: 0.3 # コサイン類似度の下限閾値
response_model: "gpt-4o-mini"
conformal_prediction:
aggregation_strategy: "mean" # mean | max
scoring_strategy: "product"
split_conformal: true
conformal_alphas:
start: 0.05
end: 0.45
step: 0.05
a_value: 1.0 # 目標正確率(0〜1)
conf/dataset_config.yaml
datasets:
fact_score:
is_grouped: false # グループ条件付き共形を無効
medlf_qa:
is_grouped: true # グループ条件付き共形を有効
8. 主要クラス・関数一覧
設定・管理系
| クラス / 関数 | 役割 |
|---|---|
ConfigManager |
YAML 設定の読み込み・保存・更新、ロギング設定 |
FileManager |
文書(PDF/テキスト)処理、チャンキング、埋め込みキャッシュ |
FAISSIndexManager |
FAISS インデックスの作成・読み込み・検索、ファイル位置マッピング |
OpenAIManager |
OpenAI API ラッパー(埋め込み・アシスタント・スレッド) |
データ処理系
| クラス / 関数 | 役割 |
|---|---|
DataLoader.load_qa_data() |
4 データセットを HuggingFace から取得 |
DataLoader.create_wiki_db() |
Wikipedia bz2 ダンプから SQLite DB 構築 |
QueryProcessor.get_queries() |
標準スキーマへの変換・サンプリング |
DocDB |
SQLite ベースの文書検索(FActScore 実装を参照) |
RAG・スコアリング系
| クラス / 関数 | 役割 |
|---|---|
OpenAIRAGAgent.answer() |
クエリ + 検索文書 → LLM 回答生成 |
OpenAIAtomicFactGenerator.get_facts_from_text() |
テキスト → 原子的事実リスト(logprobs 付き) |
OpenAIClaimVerification.annotate() |
サブクレームの S/I/U/N ラベル付け |
SubclaimScorer.score() |
コサイン類似度ベースの関連度スコア計算 |
SubclaimScorer.frequency_score() |
LLM 複数サンプリングによる一貫性スコア |
キャリブレーション系
| クラス / 関数 | 役割 |
|---|---|
get_r_score(entry, method, a) |
r_a スコア(閾値を下げたとき正確率が a を超える最小値)の計算 |
compute_threshold(alpha, data, a, method) |
共形予測の分位点閾値 q̂ の計算 |
SplitConformalCalibration.plot_conformal_removal() |
除去率 vs. 事実性保証のカーブ生成 |
GroupConditionalConformal |
グループ別閾値による条件付き共形予測 |
9. テスト・品質保証
専用のテストスイートは存在しない。品質保証は以下の方法で実施:
- JSON スキーマ検証:
jsonschemaによる各ステージ出力の構造検証(base_schema.json,wiki_schema.json,subclaims_schema.json) - インデックス整合性チェック:
FAISSIndexManager.is_indice_align()によるインデックスとマッピングの同期確認 - バッチ処理の冪等性: サブクレーム処理は既存の処理済みエントリをスキップ(再実行安全)
- 設定ログ: 実行時の設定 YAML を
config/ディレクトリにコピーして再現性を担保
10. 既知の制約・拡張ポイント
| 項目 | 現状 | 拡張の方向性 |
|---|---|---|
| スコアリング戦略 | ProductScoreStrategy のみ実装 |
Strategy パターンにより追加容易 |
| テキストチャンキング | Fixed-Length のみ(Recursive は未実装) | FixedLengthChunker と同インターフェースで追加可 |
| 共形予測の種別 | Split / Group Conditional の 2 種 | オンライン共形予測などへの拡張余地あり |
| 並列処理 | なし(完全逐次処理) | バッチ API や非同期処理で高速化可 |
| Web API | flask が依存関係に含まれるが未使用 |
デモ API サーバー化の際に活用可 |
| FAISS コーパス範囲 | 各クエリの provenance に対応する文書のみインデックス化(正解文書が事前判明している前提) | データセット外の任意クエリに対応するには Wikipedia 全文などコーパス全体をインデックス化する必要がある |
| 全コーパス検索時の精度 | IndexFlatIP は完全探索のため数学的な top-k 精度は落ちない。ただし埋め込み空間に無関係文書が増えることで真に関連する文書が top-k から押し出される可能性がある。FAISS スコアはスコアリングにも使われるため最終的な品質評価精度にも直結する |
ドメイン特化の fine-tuned 埋め込みモデルへの切り替えや BM25 との hybrid retrieval が対策として有効 |
11. 実行方法
# 基本実行
python main.py --config conf/config.yaml
# データセット・クエリ数を上書き指定
python main.py --dataset hotpot_qa --query_size 200
# カスタム実行 ID を付与
python main.py --run_id my_experiment_01
環境変数(.env):
OPENAI_API_KEY=sk-...