LLMモデル当てクイズを作ったよ
さくらのAIを使って、「LLM当てクイズwithさくらのAI」というブラウザゲームを作りました!
(しばらくの間公開しておくので、実際に遊べると思います。
まだまだゆとりがありますが、上限まで使い切ったら、補充しておきます。)
これは、3体のAIに、自由入力で3つの質問をして、回答から「どれがどのモデルか」を当てるゲームです。
国産のllm-jp-3.1、中国生まれのKimi-K2.6、アメリカ生まれでChatGPTのなかま、gpt-oss-120bです。
ゲームの仕組み
- 質問は最大10文字の自由入力。「[ ]でスープを作るとしたら?」みたいな穴埋めテンプレートに好きな言葉を入れます(これで絞って、変な入力を避けてます)
- 同じ質問を3モデルへ並列に投げます
- さいごに「この中で◯◯はどの子?」というクイズが出るので、答えましょう
各モデルの使用料金が出ていますが、さくらのAIの枠内なら無料です!!
LLMを組み込んだゲームも、請求がぜんぜん怖くない!!
たいへんにありがたいことですね。
つくったきっかけ&技術的な話
「中国のAIもなかなか比肩するできでいいよ」というので、ライトな形で出力を試してみたいなと思ってたんですが、ちょうどさくらのAIキャンペーンがあったので便乗しました。
割と3体のAIは個性的な応答をしてくれた
作る前の懸念は「3体の答えが似ていて、ゲームにならないんじゃないか」というものでした。やってみたら、割と個性的だったのでほっとしています。
| モデル | 平均応答 | 傾向 |
|---|---|---|
| Kimi-K2.6 | 6〜7秒(たまに30秒超) | 返す答えは短くて詩的 |
| llm-jp-3.1-8x13b | 1〜4秒 | 丁寧。放っておくと「以下にステップバイステップで回答します」 |
| gpt-oss-120b | 1秒前後 | 最速。迷いなく即答 |
特に、面白いのがKimiでした。
「もし喋れたら話したいのは冷蔵庫?」→ 冷蔵庫。誰が何をどれだけ愛しているか、中身で全部知っているから。
「よふかしを郵便で送るとしたら?」→ 丸めた夜を段ボールに詰める。(41.6秒)
「ラスボスでスープを作るとしたら?」→ 世界を煮詰めた憎悪スープ。
さすがは漢詩の国である。
マジレスというよりも、ちょっとシャレっけのある応答をしてくれます。
まあ、Kimiはちょっといいモデルなので、実はちょっとずるいんですが(Qwenもあるんだけど……)Kimiを触りたかったのであえて入れました。
ちょっと簡単すぎるきらいもあるんですが、まあ、当たる方が楽しいでしょうということで、OKとしております。
ただし、Kimiだけ、続けて呼ぶと429(rate limit)が返ってきます。賢いモデルであるだけに、応答がたいへんなんでしょう。
429が返ったら5秒待って起こし、まだ寝てたら10秒待ってもう一度。それでも駄目なら「……zzz(返事がありませんでした)」と吹き出しに出して、それも個性として扱うことにしました。
10秒たっても返ってこない子には「考えてるよ!ちょっと待ってね!」と出しています。
「短く答えて」と言うと、Kimiは考えすぎてしまったようである
システムプロンプトを「短く、ひとことで。40文字以内」と強めたら、Kimiが思考トークンだけでmax_tokensの2000を食い切って、本文が空のまま切れるようになりました。そこで、この子だけ思考の天井を4000に上げて解決しました。
ちなみに「短く」指示は料金の節約にはなりません。見た目の吹き出しは短くなるのに、思考型のモデルは制約を守ろうと余計に考えるので、トークン消費はむしろ増えました。見た目と裏側が逆に動くの、おもしろいですね。
llm-jpのおしゃべりは、few-shotとtemperatureで抑制
llm-jpは「短く」という指示をわりと無視してきます。
実測で比較しました(6問の平均出力トークン)。
| やり方 | 平均出力トークン |
|---|---|
| 指示を強める | 272 |
| お手本を2問見せる(few-shot) | 106 |
| お手本2問+temperature 0.3 | 28 |
指示では動かないのに、お手本(「好きな色は?」→「青。空の色だから。」)を見せて温度パラメータを下げたらなんとか10分の1になりました。ただ、それでもよくしゃべります。以下略で切られてるのはこいつです。
無料の範囲で試しているうちにブレーキをかけておくことにする
一応、従量課金に切り替わった後のことも考えて、以下のように工夫をしています。
- 自由入力はサーバー側で10文字に切り詰め(画面のmaxlengthは飾り)
- NGワードは入口で弾いて、APIを呼ばない(弾いた回数も記録しています)
- 総プレイ数に上限をかけておく
- 荒らし用に、サイト全体で毎分30プレイ・1人毎分5プレイの柵(人間には当たらない値)
プレイごとの記録(入力・3体の応答全文・トークン数・実費・応答秒数・当ての正誤)は全部サーバーに残しているので、この記事の数字はそこから出しています。
並列処理って、大変だ
各モデルの応答を待ち受ける必要があるのですが、そこの仕組みを作るのに苦労しました。
// 3プロセスが同時にここを通る
$all = json_decode(@file_get_contents($path), true) ?: [];
$all[$model] = $answer;
file_put_contents($path, json_encode($all));
読んだ時点のスナップショットに自分の分だけ足して書き戻すので、いちばん遅いプロセス(だいたいKimi)が保存するときに、先に保存された2体分が消えてしまっていました。php -S はリクエストを1本ずつ処理するため手元では再現せず、本番環境でのみ出ました。
対策:ロックを取ってから読み直して合流させます。
$fp = fopen($path, 'c+');
flock($fp, LOCK_EX); // ここから自分だけ
$all = json_decode(stream_get_contents($fp), true) ?: [];
$all[$model] = $answer; // ロックの中で読み直してから足す
ftruncate($fp, 0);
rewind($fp);
fwrite($fp, json_encode($all, JSON_UNESCAPED_UNICODE));
fflush($fp);
flock($fp, LOCK_UN);
fclose($fp);
テストもプロセスごと並べて回すようにしました。
for i in $(seq 3); do php tests/answer_one.php "$QID" & done
wait
php tests/assert_three_answers.php "$QID"
送信ボタンの連打で、同じ質問が何度も課金されてしまう
反応が悪いときに送信を連打すると、同じモデルが延々と回答し続けてしまっていました。
原因は画面側とサーバー側の両方でした。まず送信ボタンに連打よけがなく、押した回数だけ質問が立って、押した回数だけ3体に投げていました。さらに吹き出しの組がHTMLに二重になってしまいました。
document.getElementById('answer-kimi').textContent = text;
これは先に見つかった一組目しか返さないので、二組目はどうやっても埋まりません!!
サーバー側は、確認してから呼ぶ形になっていました。
if (!answer_exists($qid, $model)) {
$answer = call_api($qid, $model); // 同時に届いた分が全部ここへ来る
save_answer($qid, $model, $answer);
}
answer_exists() が見ているのはリクエストが届いた瞬間の状態です。連打で同じ質問が同時に届くと全部が「まだ無い」と答えて、全部がKimiを起こしに行きます。応答が遅いモデルほどこの隙間が広くなります。
対策は2つです。画面側は送信中を落とします。
let sending = false;
btn.addEventListener('click', async () => {
if (sending) return;
sending = true;
btn.disabled = true;
input.disabled = true;
try {
await send(input.value);
} finally {
sending = false;
btn.disabled = false;
input.disabled = false;
}
});
重複していたテンプレートは消して、書き込み先は組を明示して取ります。
const pane = panes[round];
pane.querySelector('.answer-kimi').textContent = text;
サーバー側は、APIを呼びに行く前にロックの中で札を立てます。
$fp = fopen($lockPath, 'c');
flock($fp, LOCK_EX);
$state = load_state($qid, $model); // null | 'pending' | 'done'
$mine = ($state === null);
if ($mine) {
save_state($qid, $model, 'pending'); // 「いま取りに行っています」
}
flock($fp, LOCK_UN);
fclose($fp);
if ($mine) {
$answer = call_api($qid, $model);
save_answer($qid, $model, $answer); // pending -> done
return $answer;
}
return wait_for_answer($qid, $model, 15); // 札を見た側は保存された答えを待つ
修正後、本番に同じ質問を6本同時に投げて確認しました。
for i in $(seq 6); do
curl -s -X POST "$ENDPOINT" -d "qid=$QID" &
done
wait
3本が答えを返し、3本が「いま取りに行っています」で返ります。1プレイの消費も9リクエストちょうどに戻りました。この競合も php -S では再現しないので、確認はプロセスを並べて行っています。
ほかにつくったLLMのゲーム:世界創造Q
こちらも、LLMを組み込んで作ったゲームです。
目の前の神様に願うと、世界に何かが生まれます。「光がほしい」と打てば光が生まれ、「火を消して」と打てば火が消える。世界はページを開いた全員で共有されていて、あなたが生んだものは次に来た人の目の前にあるよ、というようなゲームです。
LLMを組み込んだ公開ゲーム、ずっと憧れだったんですよ!!
ただ、無尽蔵の課金がおっかなくて今まで作れていませんでした。
やろうと思えばできるんだよ。できるだけど、ミスると、ねえ???
アラートが飛んで来ようとも、止まってくれないと、怖いものは怖い。
これを書いている時点で、8月にこのゲームが使ったのは549リクエストです。これは、チャットと埋め込みを合わせた数です(無料枠はチャットが月3,000回、埋め込みは別枠でさらに10,000回)。
14日間で、いま18日目の世界が動いていますね。
ほか、Discordの報告を集約するBotもつくりました
ホントはこっち(Discord転記Bot)とかのが実用的だと思っている私であった。
これはDiscordの任意の報告チャンネルにあがってきた報告をスプレッドシートにまとめるBotです。
ゲームよりもだいぶ実用的ですし、一人で使う分には3000回はあまりにも十分すぎる回数なので、身の回りのツールのAPI部分を置き換えて快適ギーク生活を送ると良いかなと思っております(要:クレジットカードの登録)。
で、何に何回使ってどのくらいなのよ?
8/14日現在の使用量はこのような形です。
| 回数 | 使っているもの | |
|---|---|---|
| LLM当てクイズ | 1,163 | Anthropic互換 /v1/messages(3モデル) |
| 世界創造Q | 549 | 同上 + /v1/embeddings
|
| Discord→スプレッドシート転記bot | 約28 | OpenAI互換 /v1/chat/completions
|
| 合計 | 約1,740 |
そして、0円!
転記botの28回は、およそ報告75件ぶんです(10件ずつまとめているので、報告の数より桁が小さい)。
クイズのほうの内訳は、144プレイ、画面に出た回答が987本、429で寝かせ直したのが6本、その他という感じです。
世界創造Qというのは、自由に入れた単語でものを生み出してもらうというテキストベースのゲームなのですが、それが549ですね。
ちなみに、クイズのぶんを従量課金の単価で計算すると93.38円。無償プランなので、請求は0円です。 そのうち7割がKimiです(単価が入力60円/出力300円と、他の4倍。しかも長考ぶんの思考トークンにも料金がかかる)。
せっかく3000回呼び出せるのでその分をゲームにして人に使ってもらおうという企画なのですが、ほぼほぼ身内で動いていて、バズってないので、ゆとりを持って動いてくれております。
まとめ:みんなも気軽にLLMを組み込んだゲームを作ろう!
さくらのAIがお安いから……。
さくらのAIで作ったもの3つ
紹介記事





