Response-Quality-Assessment / docs /context /07_huggingface_spaces_deployment.md
Ryoya Awano
deploy: fix MedLFQA Marginal mode sample matching
19fc84f

A newer version of the Streamlit SDK is available: 1.56.0

Upgrade

Hugging Face Spaces デプロイ設計

1. 目標とスコープ

デモアプリ(demo/app.py)を Hugging Face Spaces(公開)で動かす。

スコープ: サンプルクエリ専用モード

  • demo/data/samples.json に事前計算済みのクエリのみ動作する
  • 「推論実行」ボタンは FAISS・Wikipedia DB を使わずに事前計算済み結果を即時表示
  • 「回答を生成」ボタン(LLM 再統合)は OpenAI API を呼ぶため、API キーが必要
  • ライブ推論(任意クエリ入力)は行わない

Spaces の制約

  • Singularity は使えない(Docker / Python 環境)
  • リポジトリに大きなバイナリファイル(FAISS インデックス、SQLite DB)は置けない
  • 環境変数(Secrets)で API キーを設定できる

2. 現行コードの移植可否分析

処理 Spaces での動作 対応方針
samples.json / thresholds.csv の読み込み ○ そのまま動く(リポジトリ内) 変更不要
「推論実行」→ 事前計算済み結果の表示 ○ API 呼び出しなし 変更不要
「回答を生成」(reintegrate_subclaims ○ OpenAI API のみ Secrets に OPENAI_API_KEY を設定
build_faiss_manager() / build_scorer() △ ファイルが存在しないためエラー ライブ推論パスに到達しなければ呼ばれない(後述)
@st.cache_resource でのリソース初期化 △ 呼ばれた時点でエラー 環境フラグで無効化
.env の読み込み △ Spaces では .env ファイルは使わない Spaces Secrets → 環境変数として自動注入

ライブ推論パスが呼ばれる条件

現在のコードでは、build_faiss_manager() / build_scorer() はライブ推論パス内でしか呼ばれない。

# app.py 内の live inference ブランチ(sample_map にないクエリのみ)
faiss_manager = get_faiss_manager(dataset)   ← ここでエラーになる
scorer = get_scorer(dataset)

サンプルクエリのプルダウンには samples.json 内のクエリしか表示されないため、 通常操作では このパスには到達しない

ただし、予期しないエラー時のフォールバックや将来の機能追加を考慮し、 環境変数フラグ SPACES_DEMO=1 でライブ推論パスを明示的に無効化する。


3. 必要な変更

3-1. app.py の変更(最小限)

SPACES_DEMO=1 が設定されている場合、ライブ推論パスをブロックする。

import os

SPACES_DEMO = os.getenv("SPACES_DEMO", "0") == "1"

「推論実行」ボタン処理内:

if run_btn and query_input:
    precomputed = sample_map.get(query_input)
    if precomputed is not None and ...:
        # 事前計算済み → そのまま表示(変更なし)
        ...
    elif SPACES_DEMO:
        st.error("このデモではサンプルクエリのみ対応しています。")
    else:
        # ライブ推論(ローカル環境のみ)
        ...

3-2. Spaces 用設定ファイル

Spaces は リポジトリの README.md(frontmatter) で設定を宣言する。

---
title: Response Quality Assessment Demo
emoji: 📊
colorFrom: blue
colorTo: green
sdk: streamlit
sdk_version: 1.43.2
app_file: demo/app.py
pinned: false
---

app_filedemo/app.py を指定することでリポジトリ構造を変えずに済む。

3-3. requirements.txt の整理(実装済み)

Spaces はリポジトリルートの requirements.txt を自動で pip install する。 リポジトリでは以下の構成を採用している:

ファイル 用途
requirements.txt HF Spaces 向け最小セット。Spaces はこのファイルを自動で読む
requirements-dev.txt ローカル・Singularity 向け全依存。-r requirements.txt で共通部分を継承

requirements.txt(Spaces 向け最小セット、§4-1 調査結果に基づく):

openai>=2.0
python-dotenv>=1.0
numpy>=1.24
pandas>=2.0
pyyaml>=6.0
streamlit>=1.43

faiss-cpu / torch / transformers / sentence-transformers / langchain-core / scikit-learn はいずれも不要(遅延 import により Spaces モードでは読み込まれない)。

ローカル開発時は pip install -r requirements-dev.txt を使う。

3-4. Secrets の設定

Spaces の Settings > Variables and secrets に以下を追加する:

キー 用途
OPENAI_API_KEY sk-... 「回答を生成」ボタン(reintegrate_subclaims
SPACES_DEMO 1 ライブ推論パスを無効化

.env ファイルは Spaces 環境では使わない(python-dotenvload_dotenv() は 環境変数が既にセットされていれば上書きしないため、ローカルとの互換性は保たれる)。


4. 事前調査結果

4-1. src/ の import チェーンの影響調査(済)

sys.modules を import 前後で比較し、重い依存パッケージの混入を確認した。

原因: src.common.faiss_managersrc.common.file_managerlangchain_text_splitterstorch / transformers / sentence_transformers 等を連鎖的に引き込む。 src.subclaim_processor.scorer.subclaim_scorer も同様。

対処: FAISSIndexManager / SubclaimScorer の import を build_faiss_manager() / build_scorer() 関数内に移動(遅延 import)。型ヒントは TYPE_CHECKING ガードで維持。 これにより、サンプルクエリ専用モードでは import 時に重い依存が読み込まれない。

Spaces 向け最小パッケージセットbuild_*() が呼ばれない前提):

openai
python-dotenv
numpy
pandas
pyyaml
langchain-core
streamlit>=1.43

faiss-cpu / torch / transformers / sentence-transformers / scipy / scikit-learn は不要。

4-2. DATA_ROOT / config パスの解決(済)

_load_main_config() / _load_dataset_config() は関数呼び出し時に初めて実行される (モジュールレベルでは実行されない)ため、DATA_ROOT 未設定でも import は成功する。 サンプルクエリ専用モードではこれらの関数は呼ばれないため問題なし。


5. デプロイ手順

  1. HF Space の作成

    • huggingface.co/spaces/<username>/<space-name> を新規作成(SDK: Streamlit)
  2. spaces remote を追加

    git remote add spaces https://huggingface.co/spaces/EQUES/Response-Quality-Assessment
    

    認証はリモート URL にトークンを埋め込む方法を使う(.git/config.gitignore 対象外だが git の管理ファイルであり GitHub にはプッシュされない):

    git remote set-url spaces https://<username>:<hf_token>@huggingface.co/spaces/EQUES/Response-Quality-Assessment
    
  3. Orphan ブランチで push

    HF Spaces はプッシュ時に 全コミット履歴 をスキャンし、10 MiB 超のファイルを拒否する。 feature/hf-spaces の祖先コミットに大きなデータファイル(data/out/Medlfqav2/ 等)が含まれるため、 履歴なしの Orphan ブランチを一時作成してから push する:

    git checkout --orphan spaces-deploy
    git add -A
    git rm --cached data/out/ data/raw/ -r --ignore-unmatch
    git commit -m "deploy: initial push to HF Spaces"
    git push --force spaces spaces-deploy:main
    git checkout feature/hf-spaces
    git branch -D spaces-deploy
    

    なぜ Orphan か: 通常の push では過去コミットごと送られる。Orphan ブランチは親コミットが 存在しない「起点」なので、HF Spaces には現在のスナップショット 1 コミットだけが届く。 ローカルと GitHub の履歴には一切影響しない。

  4. Secrets の設定

    • Space の Settings > Variables and secrets に以下を追加:
    キー
    OPENAI_API_KEY sk-...
    SPACES_DEMO 1
  5. 動作確認

    • サンプルクエリの表示・スライダー操作
    • 「回答を生成」ボタン(OpenAI API 呼び出し)

再デプロイ(コード変更後)

コードを変更したら同じ Orphan 手順を繰り返す。spaces remote は設定済みのため手順 2 は不要。


6. 既知の問題・注意点

MedLFQA Marginal モードのサンプル一致

precompute.py は grouped データセット(medlf_qa)のサンプルを全て mode="conditional" で 生成するため、samples.jsonmode="marginal" エントリが存在しない。

app.py では Marginal モード選択時にモード不一致でサンプルが見つからない問題を回避するため、 Marginal モードではサンプルの mode/group フィールドを無視してマッチさせる:

if (
    precomputed is not None
    and (
        mode == "marginal"
        or (precomputed["mode"] == mode and precomputed["group"] == group)
    )
):

サブクレームスコア自体はモードに依存しないため、閾値(_lookup_q_hat が marginal 用 q_hat を 参照)との比較は正しく行われる。


7. 開発ステップ

ステップ 状態 担当ファイル
① import 影響調査 完了
② requirements 整理 完了 requirements.txt, requirements-dev.txt
app.py にフラグ追加 完了 demo/app.py
README.md の作成 完了 README.md
⑤ Spaces へ push 完了
⑥ Marginal モードのバグ修正 完了 demo/app.py