アイドルマスターのファンに向けて、「好きなキャラ像」を自然文で書くだけで、意味的に近いキャラを近い順に返すWebツール iMAS Chara Match を作りました。前編ではプロダクトそのもの ―― 何を・どんな仕組みで作ったか ―― を紹介します。
後編はこちら 👉 【後編】AIに"設計通り"実装させる ― 仕様駆動開発(SDD)ハーネスをClaude Codeで組んだ(開発手法・ハーネス編)
↓リポジトリはこちら
この記事のゴール
- 自然文入力 × 埋め込み × コサイン類似度という推薦の仕組みを、図で理解する
- backend を Clean Architecture 4層でどう組んだかを知る
- 著作権に配慮した「仕組みだけ公開する」設計判断を知る
対象読者は、埋め込みモデルで簡単な意味検索・推薦を作ってみたい人です。
1. 何を作ったか
よくある相性診断は、あらかじめ用意された選択肢(Yes/No、4択など)に答えていく形式です。出てくる結果は設問設計者の分類であって、ユーザー自身の言葉ではありません。
このツールの価値の核は 「自然文が入力になること」 です。
「双子姉妹の姉で精神年齢が高い子」
——選択肢には無いこういう言い回しをそのまま入力でき、自分の言葉から発見が返ってきます。対象は765プロのアイドル13人+プロデューサー1人の計14人です。
実際の画面がこちらです。「双子姉妹の姉で精神年齢が高い子」と入力して送信すると、近い順に14人が並びます。
「双子姉妹」という手がかりに引かれて、双海真美・双海亜美の姉妹が1〜2位に来ています。プロデューサーだけ種別バッジが緑色で、アイドル(ピンク)と一目で区別できます(この入力では最下位の14位)。
2. しくみ ― 意味の近さで並べる
ルールベースではなく、テキストの意味の近さで推薦します。全体像はこうです。
処理を言葉にすると3ステップです。
- 事前準備: キャラのプロフィール文(性格・印象的なエピソード)を埋め込みモデルでベクトル化し、ファイルに保存しておく
- リクエスト時: ユーザーの好み入力も同じモデルでベクトル化する(推論するのはこの1本だけ)
- 並べる: 入力ベクトルと各キャラのベクトルのコサイン類似度を計算し、大きい順に14人を並べる
コサイン類似度とは
2つのベクトルの「向きの近さ」を測る指標です。値は -1〜1 で、1に近いほど意味が似ていると解釈します。
cos(θ) = (A・B) / (|A| |B|)
「近似最近傍探索(ANN)」のような高度な仕組みは今回不要でした。候補はたった14件なので、全件を素直に計算すれば十分です。
埋め込みモデル
日本語特化の文埋め込みモデル Ruri v3(cl-nagoya/ruri-v3-30m) を採用しました。
- 日本語のみのテキストには多言語モデルより適している見込み
- 素の BERT(東北大BERT系など)は文埋め込み用途に非最適。文埋め込み用に対照学習された派生モデルを選ぶ
- ファインチューニングは行わない。事前学習済みモデルをそのまま使う
- Ruri 系はクエリ側と文書側で異なる prefix(
"検索クエリ: "/"検索文書: ")を要求する。片方だけ付ける/両方同じにすると、テストで気づきにくい静かな精度劣化になるので、この prefix 定数はコードの1箇所に集約した
3. こだわった設計判断
類似度は「生値」を返し、UIでは順位だけを見せる
この種のモデルのコサイン類似度は、全ペアが狭い帯に集まりやすい(無関係な文でも 0.3〜0.5 が出たりする)性質があります。これを 0〜100 に引き伸ばすと、わずかな差が実態以上に大きく見えて誤解を招きます。
そこで API は正規化しない生値(-1〜1)を返し、UI は数値を出さず順位だけを見せます。表示上の配慮は UI 側の責任、という切り分けです。
プロデューサーも同列に、ただし種別を持たせる
プロデューサーもアイドルと同列に推薦候補へ入れますが、各項目に種別(idol / producer)を持たせ、呼び出し側が判別できるようにしています。
推薦項目のデータ構造
推薦一覧の各要素は、次の5つを持ちます。
| フィールド | 意味 |
|---|---|
character_id |
キャラクター識別子 |
display_name |
表示名 |
rank |
順位(類似度の降順で1から連番) |
similarity |
コサイン類似度の生値(-1〜1) |
character_kind |
種別(idol / producer) |
同点のときは character_id の昇順でタイブレークし、実行のたびに順位が入れ替わらないようにしています。
4. backend の構成 ― Clean Architecture 4層
backend は Clean Architecture に準拠し、依存は内向きのみ(外側は内側に依存してよいが逆は禁止)というルールで組みました。
ポイントは 依存性逆転です。application は「テキスト → ベクトル」の**抽象(Embedder ポート)**だけを定義し、実モデル(RuriEmbedder)は最外層に置いて、composition root で結線します。
これには実利があります。埋め込みモデル(torch などの重い依存)をポートの向こう側に隔離できるので、推薦ロジックのテストはフェイク実装で実モデル無しに回せるのです。「同点の並び順」「負の類似度」「ノルム0のベクトル」「次元不一致」といった壊れ方も、手で置いた小さなベクトルで決定論的にテストできます。
リクエストの流れ
埋め込みモデルのロードは起動時に1回だけ。リクエストごとに再ロードはしません。モデル識別子と次元が保存済みインデックスと一致しなければ、誤った推薦を返さず明示的に失敗します(同次元の別モデルは計算が通ってしまい結果だけ無意味になる、という最も危険な壊れ方を防ぐため)。
5. できたもの
| 項目 | 内容 |
|---|---|
| backend | Clean Architecture 4層、約1,700行 + テスト関数423本 |
| frontend | React + Vite + Atomic Design、約600行 |
| 対象キャラ | 14人(765プロ13人+プロデューサー1人) |
| 埋め込みモデル |
cl-nagoya/ruri-v3-30m(Ruri v3・ファインチューニングなし) |
ローカルでの起動はシンプルです。
# backend
cd backend && uv sync && uv run uvicorn app.main:app --reload
# frontend
cd frontend && npm install && npm run dev
起動直後(未入力)の初期状態はこうです。プレースホルダで「自由記述である」ことを示し、まだ推薦していないことが分かる空状態にしています。
6. 著作権 ― 「仕組みだけ公開する」という設計
アイマスには公式の二次創作ガイドラインが無く、黙認に近い運用です。そこで公開範囲をこう割り切りました。
| 項目 | 公開 |
|---|---|
| キャラのプロフィール原文 | ❌ しない |
| 計算済みの埋め込みベクトル | ❌ しない |
| テキスト取得の手順・スクリプト | ⭕ してよい |
| ベクトル化・類似度計算のコード | ⭕ してよい |
| 事前学習済みモデル(FTなし) | ⭕ 問題なし |
つまり 「生データと学習成果物は公開せず、仕組み(コード)だけ公開する」。プロフィール原文と埋め込みベクトルは .gitignore で追跡対象から除外しています。この方針を、コード側でも「プロフィール文の原文と埋め込みベクトルは、レスポンス・ログ・エラーメッセージのいずれにも出力しない」というルールとして守り、テストで検証しています。
おわりに
前編では「何を・どんな仕組みで作ったか」を見てきました。実はこのプロジェクトの一番の工夫は、このツールを"AIコーディングエージェントに設計通り実装させる仕組み"ごと組んだことにあります。
その「仕組み」=仕様駆動開発(SDD)ハーネスの話を、後編で詳しく書きます。
👉 【後編】AIに"設計通り"実装させる ― 仕様駆動開発(SDD)ハーネスをClaude Codeで組んだ
本ツールは非公式のファン制作物です。キャラクターのプロフィール原文・埋め込みベクトルは公開せず、仕組み(コード)のみを公開しています。
by NeoSoleil

