Recotem 2.0.0のリリース

レコメンドシステム構築ツール Recotem の 2.0.0 をリリースしました。

2.0 は 1.x からの完全な作り直しです。これまでの Django / Vue / Celery とデータベース・メッセージブローカーで構成された Web アプリケーションを廃止し、pip でインストールできる単一の Python パッケージと 1 つの Docker イメージにまとめました。レシピファイル(YAML)を 1 つ書き、コマンドを実行するだけでレコメンドモデルを作成でき、作成したモデルは Recotem が提供する Docker でそのままレコメンド API として立ち上げられます。

Recotem 2.0 で変わったこと

  • レシピ駆動のワークフロー: 1 つの YAML レシピがそのまま 1 モデル・1 エンドポイントに対応します(1 recipe = 1 model = 1 endpoint)。
  • 2 つの CLI コマンド: recotem train <recipe.yaml> で学習し、recotem serve --recipes <dir> で配信します。
  • FastAPI による配信: /v1/recipes/{name}:recommend などのエンドポイントでレコメンドを返します。アーティファクトの更新を検知してホットスワップします。
  • 署名付きアーティファクト: 学習と配信は HMAC 署名付きのアーティファクトファイルのみでやり取りするため、別々のマシンで動かせます。共有データベースもメッセージブローカーも不要です。
  • 多様なデータソース: csv / parquet / bigquery / sql(PostgreSQL / MySQL / SQLite)に対応しています。
  • Optuna によるハイパーパラメータ探索: irspack のアルゴリズムに対して自動でチューニングします。

なお Python 3.12 以上が必要です。1.x との互換性はなく、モデルは再学習が必要です。

インストール

pip install recotem

または Docker イメージを利用します。

docker pull ghcr.io/codelibs/recotem:2.0.0

レシピファイルでモデルを定義する

モデルは 1 つの YAML レシピで定義します。データソース、カラムの対応、前処理、学習アルゴリズム、出力先をまとめて記述します。以下はチュートリアル用のレシピ例です。

name: purchase_log

source:
  # csv, parquet, bigquery(またはプラグイン名)を選択
  type: csv
  path: https://raw.githubusercontent.com/codelibs/recotem/refs/tags/v1.0.0/frontend/e2e/test_data/purchase_log.csv
  # ネットワーク経由のパスでは sha256 が必須
  sha256: 945fc769205a5976d38c5783500ae473afbb04608043b703951a699993c8f8be
  dtype:
    user_id: str
    item_id: str

schema:
  user_column: user_id
  item_column: item_id

cleansing:
  drop_null_ids: true
  dedup: keep_last
  min_rows: 100
  min_users: 10
  min_items: 10

training:
  # IALS, CosineKNN, TopPop, RP3beta, DenseSLIM, TruncatedSVD, BPRFM から選択し Optuna が最適化
  algorithms: [IALS, TopPop]
  # ndcg, map, recall, hit から選択
  metric: ndcg
  cutoff: 10
  n_trials: 10
  parallelism: 1
  split:
    scheme: random
    heldout_ratio: 0.2
    seed: 42

output:
  path: ./artifacts/purchase_log.recotem
  versioning: append_sha

training.algorithms に複数のアルゴリズムを指定すると、Optuna が n_trials の範囲で組み合わせを探索し、metric で最良のモデルを選びます。

コマンドでモデルを作成する

まず署名用と API 用のキーを生成します。

recotem keygen --type signing --kid dev
recotem keygen --type api --kid dev

生成したキーを環境変数に設定します。

export RECOTEM_SIGNING_KEYS="dev:<signing から得た平文>"
export RECOTEM_API_KEYS="dev:sha256:<api から得たハッシュ>"
export RECOTEM_API_PLAINTEXT="<api から得た平文>"

レシピの内容を検証してから学習を実行します。

recotem validate examples/tutorial-purchase-log/recipe.yaml
recotem train examples/tutorial-purchase-log/recipe.yaml

学習が完了すると、output.path に署名付きのアーティファクトファイルが出力されます。これがそのままレコメンドモデルの実体になります。

Docker でレコメンドAPIを立ち上げる

作成したモデルは、Recotem が提供する Docker でレコメンド API として配信できます。レシピを置いたディレクトリを指定して serve するだけです。

recotem serve --recipes examples/tutorial-purchase-log/

付属の compose.yaml を使えば、学習と配信を Docker だけで完結できます。

docker compose run --rm train
docker compose up -d serve

立ち上がった API には HTTP でリクエストします。ユーザーへのレコメンドは次のように取得します。

curl -sX POST http://localhost:8080/v1/recipes/purchase_log:recommend \
  -H "X-API-Key: $RECOTEM_API_PLAINTEXT" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "1", "limit": 5}' | jq .

アイテムに関連するアイテムのレコメンドも取得できます。

curl -sX POST http://localhost:8080/v1/recipes/purchase_log:recommend-related \
  -H "X-API-Key: $RECOTEM_API_PLAINTEXT" \
  -H "Content-Type: application/json" \
  -d '{"seed_items": ["<item_id>"], "limit": 5}' | jq .

このほか :recommend-batch によるバッチ推論や、Prometheus 向けの /metrics エンドポイントにも対応しています。Helm チャートや Kubernetes 用のマニフェストも同梱しているため、コンテナ環境へそのままデプロイできます。

Claude Code でレコメンドモデルを作成する

2.0 ではレシピが単なる YAML ファイルになり、学習も配信もコマンド 1 つで実行できるようになりました。この設計により、Claude Code のような AI コーディングエージェントを使ったモデル作成も可能になります。

例えば、手元のデータのカラム構成を Claude Code に伝えれば、適切な sourceschema を持つレシピファイルを生成してもらえます。そのまま recotem validaterecotem train を実行してモデルを作成し、評価結果を見ながらアルゴリズムや n_trials を調整する、といった一連の流れをエージェントに任せられます。レコメンドモデルの構築に必要な作業が「ファイルを書く」「コマンドを実行する」に集約されたことで、AI エージェントとの相性が大きく向上しました。

1.x からの移行

2.0 へは自動移行のパスがありません。次の点に注意してください。

  1. モデルは再学習する: 1.x のモデルは 2.0 の署名付きアーティファクト形式と互換性がありません。
  2. データベース・メッセージブローカーは不要: 2.0 はステートレスで、永続化されるのは署名付きアーティファクトファイルだけです。
  3. API クライアントを更新する: /predict/{name} は廃止され、POST /v1/recipes/{name}:recommend に変わりました。
  4. キーを生成する: recotem keygen で署名用・API 用のキーを生成し、RECOTEM_SIGNING_KEYS / RECOTEM_API_KEYS に設定します。

まとめ

Recotem 2.0.0 は、レシピファイルとコマンドでレコメンドモデルを手軽に作成し、Docker でそのまま API として配信できるツールに生まれ変わりました。重厚な Web アプリケーションから、軽量で AI エージェントとも組み合わせやすいレコメンド基盤へと進化しています。ぜひお試しください。

関連リンク

Recotem に SQL / GA4 Data API データソースを追加

推薦システム構築ツール Recotem のレシピ駆動パイプラインに、2 つのファーストパーティデータソースを追加しました。SQLAlchemy 2 経由の汎用 SQL ソース (sql) と、Google Analytics 4 Data API の直接読み込みソース (ga4) です。これまで標準対応していた CSV / Parquet / BigQuery に加え、運用中の RDB や GA4 から直接学習データを取り込めるようになっています。

追加した 2 つのデータソース

sql — 汎用 SQL データソース

SQLAlchemy 2 を使った汎用的な SQL データソースで、PostgreSQL / MySQL / MariaDB / SQLite に対応します。

data:
  source: sql
  dsn_env: RECOTEM_PURCHASE_DSN
  query: |
    SELECT user_id, item_id, purchased_at
    FROM purchases
    WHERE purchased_at >= :since
  params:
    since: '2025-01-01'
  user_column: user_id
  item_column: item_id

DSN はレシピに直接書かず、必ず環境変数経由で渡す設計です。pandas.read_sql を 100k 行単位でチャンク読み込みし、RECOTEM_MAX_SQL_ROWS で行数の上限をハードキャップします。サーバーサイドカーソル (stream_results=True) を使って、メモリに乗り切らないサイズのテーブルを安全に扱えます。

ga4 — GA4 Data API データソース

Google Analytics 4 の Data API を直接叩いて、イベントログを学習データとして取り込みます。

data:
  source: ga4
  property_id: '123456789'
  date_range:
    start_date: '2025-01-01'
    end_date: '2025-12-31'
  event_names: ['purchase', 'add_to_cart']
  user_column: clientId
  item_column: itemId

認証は Application Default Credentials (ADC) のみ対応で、レシピに credentials を埋め込めない設計です。runReport のページングは RECOTEM_GA4_MAX_PAGES で上限を設けています。ResourceExhausted / ServiceUnavailable に対しては 3 × api_timeout_seconds の予算ベースのリトライを行い、各 API コール後に壁時計のデッドラインを再チェックします。

SQL ソースのセキュリティ設計

SQL ソースは「ユーザーが任意の接続先 DSN を指定できる」性質上、SSRF を含む攻撃面が広くなります。以下のような多層防御を組み込んでいます。

読み取り専用トランザクションの強制

ダイアレクトごとに読み取り専用ヒントを発行します。

DB適用するヒント
PostgreSQLSET TRANSACTION READ ONLY
MySQLSET SESSION TRANSACTION READ ONLY
MariaDBSET SESSION TRANSACTION READ ONLY + max_statement_time
SQLitePRAGMA query_only=ON

特に SQLite は以前は silent no-op になっていたものを fail-closed に変更し、ステートメントタイムアウトが適用できない場合は構造化警告 (sql_statement_timeout_not_applied) を出すようにしました。

SSRF ガード

デフォルトで RFC1918 / loopback / link-local の宛先を拒否します。プライベートネットワーク内の RDB を読みたい場合は RECOTEM_SQL_ALLOW_PRIVATE=1 で明示的に opt-in します。

ガードは DSN の netloc だけでなく、各ドライバが認識するルーティング指定を全て検査します。

  • ?host= / ?hostaddr= (libpq)
  • ?service= / ?unix_socket= (拒否)
  • 絶対パスの ?host= (拒否)
  • ホスト情報を持たないネットワーク DSN (拒否)

これは SQLAlchemy の make_url が netloc しか url.host に格納しない一方で、libpq や PyMySQL は上記のクエリパラメータも参照するため、netloc だけ見ていると簡単にバイパスできてしまうからです。

DNS リバインディング対策

プローブとフェッチのタイミングで getaddrinfo を使って IPv4 / IPv6 双方を再解決し、解決された IP 集合をピン留めします。gethostbyname_ex 時代の IPv4 限定実装をやめたことで、デュアルスタックや IPv6 専用ホストでの誤検知も解消されました。

ログからの DSN ユーザー情報の除去

src/recotem/log_redaction.py に DSN userinfo スクラバーを追加し、構造化ログのチェーン先頭に置いています。空ユーザー名・URL エンコードされたパスワード・スキーム別の判定も組み込みで、s3:// / gs:// / az:// / abfs(s):// のようなオブジェクトストア URI は誤検知しないよう除外しています。

また、DataSourceError.code"datasource_error" に固定することで、pipeline.py が exc_info=False でロギングするようになり、psycopg / PyMySQL の例外チェーン経由で DSN userinfo が流出するのを防いでいます。

TLS 設定の Advisory

PostgreSQL / MySQL の DSN で sslmode= / ssl= が指定されていない場合、初期化時に sql_dsn_tls_not_configured を警告として記録します。

GA4 ソースの堅牢化

エラーの正しい分類

GA4 レスポンスの eventCount が非整数だった場合、以前は ValueError として exit code 1 (_EXIT_UNKNOWN) で落ちていましたが、DataSourceError (exit 3 / _EXIT_DATASOURCE) に変更しました。データソース起因の障害が運用上きちんと分類されるようになります。

Prometheus メトリクスの部分初期化耐性

_metrics_ga4 は atomic init とロールバックを実装しており、いずれかのカウンタ登録が失敗するとモジュール全体を no-op フォールバックにラッチします。catch する例外も第三者レジストリ起因の KeyError / ImportError まで広げて、本体の動作を阻害しないようにしています。

バリデーション強化

  • event_names は正規表現と長さ上限でバウンドチェック
  • weight_column が dimensions と衝突する場合はバリデーション時に拒否
  • ローリング XOR バグ修正済みの日付範囲バリデーション

新規追加された環境変数

変数デフォルト用途
RECOTEM_MAX_SQL_ROWS50,000,000SQL の行数ハードキャップ ([1,000, 500,000,000] にクランプ)
RECOTEM_SQL_ALLOW_PRIVATE(空)SQL ソースでプライベート / loopback DSN を許可
RECOTEM_GA4_MAX_PAGES500GA4 のページネーション上限 ([1, 10,000] にクランプ)

エントリーポイント登録とインストールヒント

新規データソースは [project.entry-points."recotem.datasources"] で登録しつつ、optional extras がインストールされていない wheel でもロード自体は通るように、フォールバック辞書と _BUILTIN_INSTALL_HINTS を用意しました。エラー時には適切な extras (postgres / mysql / sqlite / ga4 / all) をインストールするよう案内されます。

pip install 'recotem[postgres]'   # PostgreSQL
pip install 'recotem[mysql]'      # MySQL / MariaDB
pip install 'recotem[sqlite]'     # SQLite
pip install 'recotem[ga4]'        # GA4 Data API
pip install 'recotem[all]'        # 全部入り

テスト

ユニット / 統合 / fuzz の 3 階層で 1,674 ケース をカバーしました。

  • ユニット: 各ソースの設定バリデーション、SSRF の全ルーティング形式のパラメトライズ、IPv4 / IPv6 / デュアルスタックでの DNS リバインディング、PostgreSQL / MySQL / MariaDB / SQLite の読み取り専用 + ステートメントタイムアウトの SQL 完全一致検査、GA4 のリトライ判定 (ResourceExhausted / ServiceUnavailable / PermissionDenied)、weight_column 衝突拒否、非整数 eventCount 拒否、ログリダクションのオブジェクトストア URI 保持
  • 統合: SQLite を使った SQL の train + serve エンドツーエンド
  • Fuzz: 両ソースのレシピ形状に対する hypothesis のバイトミューテーション
  • レジストリ: 正常系とフォールバック経路の両方を検証、autouse フィクスチャでテスト間の LRU キャッシュ漏れを防止

まとめ

Recotem のデータソースに sqlga4 を追加し、運用中の RDB や GA4 のイベントログをそのまま学習データとして取り込めるようになりました。SQL ソースは SSRF / DNS リバインディング / DSN ログ流出対策を全ルーティング形式に対して入れ、読み取り専用も fail-closed に修正しています。GA4 ソースは ADC のみの認証・予算ベースのリトライ・部分初期化耐性のメトリクスといった、運用観点での堅牢化を行いました。既存の csv / parquet / bigquery レシピは無変更で、Recipe.source 判別子への追加だけの後方互換変更です。

Recotem 2.0: 設定ファイル駆動のCLI/ライブラリへ全面刷新

推薦システム構築ツールRecotemを 2.0 として全面的に書き直しました。これまでの Django + Vue + Celery + PostgreSQL + Redis という7サービス構成の Web アプリケーションから、YAML レシピを書いて recotem train / recotem serve を実行するだけのシングルパッケージ Python CLI/ライブラリに刷新しています。

なぜ書き直したのか

旧 Recotem は Web UI を中心に据えていたため、利用者は管理画面から学習ジョブを登録し、結果を確認し、モデルを配信する仕組みでした。便利な一方で、「ちょっと推薦モデルを動かしたい」「既存のデータパイプラインに組み込みたい」というケースでは複雑な構成です。PostgreSQL / Redis / Channels / Daphne / nginx といったサービスを立ち上げる必要があり、コンテナイメージも複数に分かれていました。

Recotem 2.0 では発想を逆転させ、「Web UI をやめて、設定ファイル(YAML レシピ)で完結させる」方針にしました。1 つの YAML が 1 つのモデル、1 つの /predict/{name} エンドポイントに対応します。インストールは pip install recotem の一発、配布物も単一の Docker イメージです。

レシピ駆動の利用フロー

1. レシピを書く

データソース、アルゴリズム、チューニング設定を 1 つの YAML にまとめます。

name: purchase-recommender
data:
  source: csv
  path: s3://my-bucket/purchases.csv
  user_column: user_id
  item_column: item_id
training:
  algorithms: [IALS, BPR, RP3beta]
  trials: 30
  timeout_per_trial: 300

2. 学習

recotem train recipe.yaml

irspack + Optuna でアルゴリズム選択とハイパーパラメータ探索を行い、HMAC 署名付きのバイナリアーティファクトを出力します。アルゴリズム別の試行回数や試行ごとのタイムアウトを指定でき、Optuna のストレージは in-memory / SQLite / PostgreSQL から選択して並列・再開可能なチューニングが実行できます。

3. サービング

recotem serve --recipes ./recipes/

ディレクトリ内のレシピを監視し、アーティファクトが更新されたら無停止でホットスワップします。FastAPI ベースで、/predict/{name} エンドポイントが自動的に生やされる仕組みです。

廃止したコンポーネント

シンプル化のために、旧バージョンで使っていた以下のスタックは全て削除しました。

  • バックエンド: Django, Django REST Framework, Django Channels, Daphne, Celery
  • データストア: PostgreSQL, Redis
  • フロントエンド: Vue, Vite, PrimeVue, Tailwind
  • インフラ: nginx プロキシ, 推論専用サブサービス

代わりに recotem パッケージひとつで完結します。配布は PyPI と単一 Docker イメージ、Helm チャートはサービング専用に学習用 CronJob オプション付きという構成です。

データソースのプラグイン化

データソースは recotem.datasources の entry point で拡張可能なプラグイン方式に変更しました。標準で以下に対応しています。

  • CSV / Parquet: ローカルファイルシステム, オブジェクトストア (s3://, gs://, az://, abfs://, abfss://), HTTPS
  • BigQuery: GA4 のBQ エクスポート構成を想定したパターン

HTTPS データソースを使う場合、sha256 による整合性ピンが必須で、RECOTEM_MAX_DOWNLOAD_BYTES(デフォルト 256 MiB)でダウンロードサイズの上限を設定できます。リダイレクト時のスキーム変更拒否や、プライベート IP への接続拒否(SSRF対策)も組み込まれており、安心して外部 URL を指定できます。

セキュリティ設計

CLI/ライブラリ化に伴い、アーティファクトの取り回しが運用上の中心になるため、署名と検証を強化しています。

  • HMAC 署名付きアーティファクト: マジックバイト + バージョン + リザーブ + kid + ヘッダ JSON + ペイロードという構造。複数 kid を持つ KeyRing でゼロダウンタイムの鍵ローテーションが可能
  • FQCN allow-list: 復元時に読み込めるクラスを手動で列挙し、numpy.* / scipy.sparse.* の限定的なモジュールプレフィックス許可と高リスクサブモジュール拒否を組み合わせる二重防御
  • API キー認証: X-API-Key ヘッダで認証。digest は hashlib.scrypt + ドメイン分離ソルト recotem.api-key.v1 で保存
  • TrustedHost / CORS デフォルト拒否: 余計なホスト/オリジンからのアクセスを排除
  • /health/health/details の分離: 匿名アクセス可能なヘルスチェックは件数のみ、kidbest_class といったメタデータは認証付きの /health/details のみで開示
  • OpenAPI のフェイルセキュア: /docs/openapi.jsonRECOTEM_ENVdevelopment / dev / test のときのみ有効
  • 構造化ログのリダクション: structlog のチェーン先頭にリダクションプロセッサを置き、シークレットや認証情報の混入を防止

--insecure-no-auth--dev-allow-unsigned といった開発用フラグは RECOTEM_ENV でガードされ、本番環境では使えない設計です。

オブザーバビリティ

/metrics エンドポイントは RECOTEM_METRICS_ENABLED=true のオプトイン方式で、12 種類の Prometheus メトリクスを出します。

  • 推論系: recotem_predict_total, recotem_predict_latency_seconds
  • モデル状態: recotem_model_loaded, recotem_active_recipes
  • 障害系: recotem_artifact_load_failures_total, recotem_artifact_stat_failures_total, recotem_watcher_unhandled_errors_total, recotem_metadata_lookup_errors_total, recotem_recipe_rescan_errors_total, recotem_recipes_dir_scan_failures_total
  • 運用系: recotem_swap_total, recotem_bigquery_storage_fallback_total

構造化ログには train_done / train_error / tuning_aborted といった正規化済みイベントを出力し、run_id / exit_code / artifact / best_class / best_score / trials / trained_at / kid などのフィールドを統一的に持たせています。

ホットスワップとフェイルセーフ

サービング側では、アーティファクトファイルの更新を検知して無停止で読み替えます。ここに以下の堅牢性が組み込まれています。

  • Read-once プロトコル: stat → read の TOCTOU を回避
  • Stale-on-swap-fail: ホットスワップ時の復元失敗で 503 にせず、直前のモデルを継続提供
  • Lenient startup: 壊れた YAML があっても起動を中断せず、/health/detailsloaded=false / last_load_error=... で開示
  • X-Recotem-Metadata-Degraded: 1 ヘッダ: メタデータ参照に失敗した推論レスポンスを明示的にマーク
  • OOM のフォールスルー: ファイル読み込みや学習パイプラインでの OOM は意図的にラップせず伝播

チュートリアル: 1 コマンドで動かす

examples/tutorial-purchase-log/ を新設し、HTTPS で取得できる小さな公開 CSV(sha256 ピン済み)を使ったゼロセットアップのクイックスタートを用意しました。compose.yaml の tutorial フローに組み込まれており、docker compose で学習からサービングまで通せます。

テスト

ユニット / 統合 / fuzz の 3 階層で 1,420 ケースをカバーしました。

  • hypothesis によるアーティファクトローダーのバイトミューテーション fuzz
  • レシピローダーの YAML ミューテーション fuzz
  • pytest-httpserver を使った HTTPS CSV ソースの統合テスト
  • 実ファイルウォッチャーでの並列ホットスワップテスト
  • 起動時の壊れた YAML 耐性テスト

CI では pytest / ruff lint / ruff format / シークレットログ検査 / Docker ビルド / e2e (train → serve → predict) / CodeQL / Trivy を回しています。

まとめ

Recotem 2.0 は、Web UI ベースの推薦システム管理ツールから、YAML レシピ駆動の CLI/ライブラリへと舵を切りました。pip install recotem でインストールして、recotem train recipe.yamlrecotem serve --recipes ./recipes/ を打つだけでモデル学習から API 配信まで完結します。Docker, Kubernetes (Helm), オブジェクトストア, BigQuery, Prometheus といったクラウドネイティブな環境とそのまま接続でき、HMAC 署名・scrypt API キー・SSRF 対策など運用に必要なセキュリティも揃っています。