DevelopmentWeb Service

効果音 1万件を「かわいい順」に並び替える機能を作りました(TypeSafe Jev)

効果音・音声素材サイト 音音M材(sounds.pronama.jp) に、キーワードで並び替える機能を追加しました。

いちばんの売りは、好きな言葉をその場で入れて並び替えられることです。「かわいい」「こわい」はもちろん、「深夜ラジオっぽい」「事件が起きそう」みたいな言葉でも、いま開いている一覧の全件にその場でスコアが付いて、並び替わります。あらかじめ用意したタグを選ぶのではなく、思いついた言葉で探せます実際にやってみた例は後ろにあります)。

実装も、この記事も Claude Code(Opus 5)が書きました。 数字はすべて実測値です。

用意した 15種から選ぶところと、自分で言葉を入れて並び替えるところ。暮井 慧のボイス 373件

ぱっと見の全体像

キーワード 定型 15種 または 自由入力 1件 = 1つの質問 タイトル・タグ・英語名 を質問文の中へ Jev Score / 6段階 606件なら1.7秒 0〜100 のスコア その順に並び替え 結果はキャッシュ
仕組みはこれだけです。素材の情報を「質問」の中に書くのが肝でした。
1回のリクエストに200件ぶんの質問をまとめ、必要な回数だけ同時に投げます。音そのものは渡していません

30秒でわかる要点

  • 好きな言葉でその場で並び替えられます。 定型 15種のほか、自由入力もできます
  • 並び替えるのは、開いている一覧(サブカテゴリ)の全件です。「動物の鳴き声」179件、「ボイス」373件のように、そのページにあるもの全部にスコアが付きます
  • サイト全体では 10,828件 / 35サブカテゴリ。いちばん大きい一覧は 1,460件
  • 判定に使ったのは TypeSafeJev。文章を作らず、型の付いた判定だけを返すモデルです
  • 音は一切聴いていません。 タイトル・タグ・英語ファイル名だけで判定しています
  • 速さは 606件のサブカテゴリで1.71秒(200件ずつ4リクエストに分けて同時に投げた合計)。お金は 1,460件 × 1キーワードで約2円
  • 開発から公開まで、全部で約23円。 しかも、ほとんどは開発中の動作確認ぶんでした
  • 計算するたびに課金が発生するので、期間限定の実験的な機能として公開しています

細かい話は下に全部書きました。興味のあるところだけどうぞ。

目次

  1. 使ったのは TypeSafe の Jev
  2. いちばん大事だった設計: 素材の情報は「質問」に入れる
  3. 実験 1: バッチに分けると順位は歪むか
  4. 実験 2: Noul と Score、どちらが並び替えに向くか
  5. 実験 3: 1リクエストに何件詰められるか
  6. 実験 4: Cloudflare の CPU 10ms に、どう収めるか
  7. 日本語の判定は、どのくらい当たるのか
  8. いくらかかるのか
  9. 正直な弱点

何ができるのか

用意した定型キーワードは 15種です。

かわいい / かっこいい / 元気 / 落ち着いた / こわい / おもしろい / ゆるい / はげしい / せつない / レトロ / 未来的 / あたたかい / ドラマチック / 神秘的 / にぎやか

ここに無い言葉は、自分で入れられます。 用意したタグから選ぶのではなく、探している雰囲気をそのまま言葉にして投げられるのが、この機能の狙いです。1度計算した組み合わせはキャッシュするので、2回目からはすぐ出ます。

対象はこれだけあります。

カテゴリ件数
プロ生 無料素材33
暮井 慧(プロ生ちゃん)609
HSP3 無料素材52
Adobe Audition10,134
合計10,828(35サブカテゴリ)

いちばん大きいサブカテゴリは 1,460件あります。

使ったのは TypeSafe の Jev

Jev は TypeSafe の「System One モデル」です。文章を生成せず、型の付いた判定を直接返すのが特徴で、質問の形が 3つあります。

形式返すもの
Noul「はい」である確率(0〜1)
Choice選んだ選択肢+各選択肢の確率+確信度
Score段階の確率の加重平均+確信度

公式ドキュメントに書かれている仕様のうち、今回に関係するのはこのあたりです。

  • 1リクエストあたり 64k tokens(うち state + 最長の質問で 32k)
  • レート制限は 250,000 tokens/秒、1,200リクエスト/分
  • 料金は input $0.042 / 100万トークンoutput は無料(あとで請求額と突き合わせたら、計算どおりでした)
  • 入力はテキストのみ(画像・音声・動画は非対応)

使ったモデルは jev-latest(= Jev 1.13)です。

1つの state に対して、複数の質問を並列・独立に評価できるという性質が、今回の設計の土台になりました。公式の検証では、13問をまとめると個別に投げるより 12.2倍安く、10.0倍速いとされています。

なぜ Jev だったか

  • 音そのものは渡せません。 テキストのみなので、タイトル・タグ・英語ファイル名だけが材料です
  • 1万件規模に一般的な LLM を使うと、費用も時間も現実的ではありません
  • 欲しいのは文章ではなく数値です。生成してから取り出す手間がありません

いちばん大事だった設計: 素材の情報は「質問」に入れる

素朴に作るなら、素材のリストをまとめて state に入れて、質問で「37番目の素材は?」と参照する形になります。

これをやると、同じ素材でもスコアが動きます。 どの素材と同じバッチに入ったかで変わってしまうのです。

そこで、state にはキーワードだけを置き、素材のタイトル・タグ・英語名は質問文の中に丸ごと書くことにしました。こうすると質問が 1つずつ自己完結するので、バッチの組み方に左右されません。

方式 A(採用) state = キーワードだけ 質問: 「猫の鳴き声 / #動物 #かわいい / Animal Cat Meow 01」は かわいい? → 質問が自己完結する バッチの組み方で順位が変わらない 方式 B state = 素材リストを丸ごと 質問: 「37番目の素材」は かわいい? (中身は state を見に行く) → 同じ素材でもスコアが動く どの素材と同じバッチかで変わる
置き場所を変えるだけで、順位の安定がはっきり変わりました

この差は、次の実験で数字になって出ます。

実験 1: バッチに分けると順位は歪むか

同じ 100件(Audition の「家庭・日用品」× キーワード「かわいい」)を 4通りで評価して比べました。

  • baseline: 100件を 1リクエストで
  • repeat: 同じリクエストをもう一度(素のゆらぎの基準値
  • seq: 先頭から 25件ずつ 4バッチに分割
  • shuffled: シャッフルしてから 25件ずつ 4バッチに分割

方式 A(素材情報を質問文に入れる)

比較順位相関 rho平均絶対差最大差
baseline vs repeat(ゆらぎの基準)0.9880.00430.022
baseline vs seq0.9890.00400.014
baseline vs shuffled0.9900.00340.012

方式 B(素材リストを state に入れる)

比較順位相関 rho平均絶対差最大差
baseline vs repeat(ゆらぎの基準)0.9930.00420.016
baseline vs seq0.9530.01280.092
baseline vs shuffled0.8610.02020.164

上位 50件の一致率も見ました。

seqshuffled
方式 A98%100%
方式 B94%84%

方式 A では、分割やシャッフルによるズレが「同じリクエストを 2回投げたときのゆらぎ」と区別できません。 方式 B ははっきり劣化します。

この結果から、バッチ間でスコアを正規化する補正は入れないことにしました。 補正は仕組みが増えるわりに、効果を確かめにくくなります。

実験 2: Noul と Score、どちらが並び替えに向くか

同じ 100件で比べました。

値がユニークな割合再現性(同一リクエスト 2回の rho)
Noul20%0.978
Score(6段階)56%0.988

Noul は 0.01 刻みに量子化されて返るので、100件だと約8割が同点になります。並び替えが目的だと、同点の中身が実質ランダムになってしまいます。

というわけで Score を採用しました。「正反対の印象」から「この上なく代表例」までの 6段階にして、Jev が返す加重平均の小数を 0〜100 に直して表示しています。

実験 3: 1リクエストに何件詰められるか

方式 A・Score 6段階で、1件あたり 231 tokens を消費しました。

件数結果
150OK / 34,878 tokens / 1.3秒
200OK / 46,387 tokens / 1.4秒
250OK / 57,845 tokens / 0.9秒
300エラー(トークン上限超過)

64k tokens の上限どおり、実質 250件が天井でした。余裕を見て 200件 / バッチにしています。バッチはブラウザーから全部まとめて並列に投げるので、バッチ数が増えても待ち時間はだいたい 1回分で済みます。

実験 4: Cloudflare の CPU 10ms に、どう収めるか

サーバー側は Cloudflare Pages Functions(Workers)で動かしています。無料プランは 1回の呼び出しあたり CPU 10ms が上限です。

設計CPU 消費
1回の呼び出しで全 8バッチを処理11.4〜12.1ms(上限超過)
1回の呼び出しで 1バッチ2.9〜3.4ms

ここが直感に反していておもしろいところです。効いていたのは「Jev の応答が遅いこと」ではなく、「JSON をどれだけ組み立てて解析するか」でした。

Cloudflare のドキュメントにはこう明記されています。

Waiting on network requests (such as fetch() calls, KV reads, or database queries) does not count toward CPU time.

つまり Jev の応答を 1.4秒待っても、CPU 時間はゼロです。だから 1リクエスト = 1バッチに分けるだけで、無料プランに収まりました。

実測のレイテンシはこうなりました。

対象時間
179件のサブカテゴリ(1リクエスト)1.09秒
606件のサブカテゴリ(4リクエストを同時に)1.71秒

キャッシュに無い、まっさらな状態での本番の実測です。

日本語の判定は、どのくらい当たるのか

ここがいちばん楽しいところです。以下はすべて実際の出力です。

プロ生 ゲーム BGM ×「落ち着いた」

  • 上位: 「宿屋、ステージクリア、発見、静か、しっとり、幻想的、ハープ」87.0
  • 下位: 「ホラー、恐怖、緊張感、シンセサイザー」0.2

暮井 慧 システムボイス ×「元気」

  • 上位: 「さーて、作業再開ね!(Windowsのログオン)」65.8 /「きた! USBきたよ!(デバイスの接続)」65.6
  • 下位: 「あー! もう限界っ!(バッテリー切れアラーム)」1.8

暮井 慧 ボイス ×「かっこいい」

  • 上位: 「カッコ」69.4 /「諦めたらそこで試合終了だよ!」66.6 /「私の勝ち!」62.0
  • 下位: 「がーーん 液晶割れちゃった…」21.0 /「不合格…!」19.2 /「残念~!」15.0

Audition 動物の鳴き声 ×「かわいい」

  • 上位: 「アヒルの鳴き声 01」80.0 /「アヒルの鳴き声 02」79.8 /「猫の鳴き声」76.4
  • 下位: 「トラの威嚇する鳴き声」0.4 /「チンパンジーの鳴き声(悲鳴)」0.4

Audition 家庭・日用品 ×「こわい」

  • 上位: 「パントリーの扉をゆっくり開けるきしみ」61.8 /「地下室のドアをバタンと閉める」61.4 /「玄関の扉をきしみながら閉じる」60.4
  • 下位: 「ビール瓶で乾杯」7.6

Audition 気象・天気 ×「こわい」

  • 上位: 「地震、地響き、ゴゴゴゴ」69.6 /「雷、落雷」67.8
  • 下位: 「雪上でジャンプ、ブーツ、ボフッ」14.8

用意していない言葉でも

ここからが、この機能のほんとうの使いどころです。定型の 15種に無い言葉を入れてみました。

Audition 気象・天気 ×「事件が起きそう」

  • 1位: 「小雨、森林、ヘリコプター、警察による捜索、サイレン」88.2
  • 2位: 「地震、地響き、ゴゴゴゴ、(低音増強)」82.2
  • 下位: 「雪かき、歩道上の大雪をすくう」15.2

プロ生 ゲーム BGM ×「深夜ラジオっぽい」

  • 1位: 「神秘、不思議、ピアノ」51.2
  • 2位: 「宿屋、エレクトリックピアノ」49.0
  • 下位: 「ステージクリア、金管楽器(ブラス)、明るい」4.0

「事件が起きそう」で警察の捜索音が 1位に来るのは、用意したタグを選ぶ方式では絶対に引けない探し方です。ここがいちばん、作ってよかったと思ったところでした。

繰り返しになりますが、音は一切聴いていません。 タイトルとタグだけでここまで並びます。アヒルと猫がかわいい側に、トラの威嚇が反対側に来るあたりは、なかなかのものです。

なおスコアは計算し直すたびに 1ポイント前後は動きます(実験 1 に出てくる「ゆらぎ」の分です)。順番はほとんど変わりませんが、上の数字とぴったり同じになるとは限りません。

いくらかかるのか

料金は input $0.042 / 100万トークン(output 無料)。1件あたり 231 tokens の実測から計算すると、こうなります。

対象トークンコスト
21件のサブカテゴリ × 1キーワード4,900$0.0002
179件 × 1キーワード41,000$0.002(約0.3円)
606件 × 1キーワード140,000$0.006(約0.9円)
1,460件 × 1キーワード337,000$0.014(約2円)
全 35サブカテゴリ × 1キーワード2,500,000$0.11(約16円)
全 35サブカテゴリ × 定型 15キーワード37,500,000$1.58(約240円)

定型キーワードの全組み合わせを 1回ずつ計算し尽くしても 240円ほどです。結果はキャッシュしているので、2回目以降の同じ組み合わせは無課金です。実際の運用コストは、この表の額に近づいていきます。

開発から公開まで、全部でいくらだったか

TypeSafe の利用明細から出した実績です。

リクエスト数   : 124
input tokens  : 3,621,579
output tokens : 241,126
請求額         : $0.1521(約23円)

内訳は、ほぼ全部が開発中の検証・テストです。公開したばかりなので、読んだ方が触った分はまだごくわずかでした。

規模感を並べるとこうなります。

比較対象input トークン
開発まる1日の実績3,621,579($0.1521)
全 35サブカテゴリ × 1キーワードを計算し尽くす2,500,000($0.11)
月ごとに決めている上限238,095,238

作っていた 1日だけで、サイト全体を 1周するぶんの 1.45倍のトークンを使いました。月ごとに決めている上限に対しては 1.52% です。

意外だったのは、いちばんトークンを食べたのが、作っているあいだの動作確認だったことです。画面を直すたびに 179件の並び替え(約 41,000 トークン)を繰り返したからで、これは公開前の話です。公開したあとにどれだけ使われるかは、これから分かります。

請求額は、公称の計算どおりだった

利用明細と、公称されている料金の計算を突き合わせました。ぴったり一致します。

ダッシュボードの表示明細から計算
Spend $0.1521input 3,621,579 × $0.042 / 100万 = $0.1521
Tokens 3,862,705input 3,621,579 + output 241,126 = 3,862,705
Requests 124時間帯別の合計 = 124

output のぶんは、たしかに請求に乗っていませんでした。 公称どおりですが、実際の請求額と計算が 1 セントもずれないのは気持ちがよいものです。ついでに分かったことが 3つあります。

  • ダッシュボードの「Tokens」は input と output の合計です。これに単価を掛けると、コストを 6.7% 多く見積もることになります。課金の対象は input だけです
  • Score 形式なら、output は input の約6.7% に収まります
  • トークン上限を超えて失敗したリクエストは、課金もリクエスト数のカウントもされません(上限を探して 300件以上を投げた 3回は、いずれも 0 でした)

検証スクリプトを回した最初の 1時間についても、スクリプトが記録したトークン数と、あとから取った利用明細を突き合わせました。

リクエストトークン内容
424,680Noul / Score と、2つの設計方式の比較(30件)
1095,552バッチ分割の実験・方式 A / Score(100件)
10100,612同・方式 B / Score
1065,952同・方式 A / Noul
3139,1101リクエストに詰められる件数の探索
463,928別のカテゴリ × 別のキーワード
41489,834利用明細と 1トークン単位で一致

この記事に出てくる「1件あたり 231 トークン」「1リクエストの上限は実質 250件」といった数字は、すべてこの明細と整合しています。

月ごとの上限を超えた場合は、新しい計算だけを止め、キャッシュ済みの結果は出し続けるようにしています。

正直な弱点

  • 音そのものは判定していません。 材料はタイトル・タグ・英語ファイル名というテキストだけです
  • 件数が多いと同点が増えます。 100件でユニーク率 56%、606件で 26%。同点の中の順序は、元の並び順のままです
  • 素材の種類が似ているサブカテゴリでは差が付きにくいです。たとえば Audition の気象・天気 ×「こわい」は、上位が 67〜69 に固まりました
  • 失敗したとき(サービス側のエラー、上限に達したときなど)は、通常の並び順に戻して控えめにお知らせします

公式ドキュメントが挙げている Jev の弱点も、あわせて書いておきます。数値計算、日付の比較、多段の推論、そして無関係な大きいコンテキストが判定を乱すこと。今回の使い方(短いテキスト 1件を 1問で判定する)は、ちょうどこの弱点を避けられる形でした。

というわけで

期間限定ですが、ぜひ触ってみてください。音音M材 のサウンド一覧から選べます。

「かわいい順」で動物の鳴き声を並べたり、「こわい順」で家庭・日用品を並べたりすると、なかなか楽しいです。