0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Jevで恋愛ADVを作った。LLMだけでは作れないギャルゲーを。

0
Last updated at Posted at 2026-09-27

今話題のTypeSafe AIの判断特化モデルJevを、LLM(Claude)と音声合成(Gemini)の前段に置いて、恋愛ADVを1本作りました。この記事は「Jevをどこに使うといいか、どこでは意味がないか」を、実際に作ったものをお見せしながら、実体験で書きます。Jevそのものの説明は公式ドキュメントと先行記事に譲ります。

とりあえず遊んでみる

誰でも遊べるようにしました!
GitHub Pagesから、Google Cloud Runに飛びます。
https://ozaki-taisuke.github.io/jev-kano/

3行でまとめるなら

  • Jevが「本音」を0.2秒で決め、その本音で顔と第一声が先に出る。言葉(口に出す文)はClaudeがあとから書き、声はGemini 3.8 Flash TTSが本音を加味して音にする
  • Jevの強みは「脊髄反射的な反応」「確率で強さを決める」「小さな判定を何度もできる」
  • LLMでも判定は作れる。ただし、LLMだけで「人間っぽい順番」を出すのは難しく、Jevを前段に置くと出せる(例: 人は熱湯に触れた時、考えてから「熱っ!」とは言わない)

何を作ったか

タイトル画面

学園祭前日の夜、部室で二人きり。相手は地味でおとなしい、箱入りの読書好き(軽音部だけど)。
話しかけるか、絵の上の頬やおでこに触れることも……?
仲良くなると振る舞いが変わり、彼女の方から問いかけてくるようになります。

反応直後

反応直後の画面です。
画面下の帯にJev反射0.40秒・Claude言葉3.62秒・Gemini声5.47秒。
UI上、上部と中央にゲームの要素、下部の帯に(本来は隠すべき)技術要素を分けてあります。
今回は検証として表示、ということで。

「練習、ずっと見てたよ。まひろのベース、好きだよ」と言うと……

① 0.40秒で表情が変わり、「……あ、はい」(青字部分)が出ます。
TypeSafe AIがJevを「System One」と呼ぶのは、この「考える前の反応」を意味するようです。

② 約3.6秒後に「ありがとう、ございます。指が届かなくて、低い音のところだけ、毎晩弾いてました。明日は……たぶん、大丈夫です」という言葉が返ってきて、その約2秒後に声が続きます。

役割分担

image.png

話しかける or 触れる(髪・おでこ・頬・肩・襟元)
   ├─ 反射 Jev … 本音 / 好感度の動き / 嫌がる / 話題 を1リクエストで → 表情変化・一言 (0.2〜0.5秒)
   │             (彼女からの問いを流したかは、問いと返事だけの別の小さな呼び出し。反射と並列)
   ├─ 一言 … 場面×本音×強さ(Jevの確率で3段階)で固定の一言。音声は同梱し、即出力
   ├─ 言葉 … Claude Opus 5 … 本音と一言を渡して、その続きの台詞を書く(数秒)
   └─ 声 … Gemini 3.8 Flash TTS … 本音×場面×強さ×親しさの声色で読む(ストリーミング) → 言葉(表示)から1〜2秒

※ Claude Opus 5はEffort: Lowにして使用
※ Geminiの音声は、上限対策として軽量版のFlash Lite TTSを予備に(Flashが弾かれたら自動でLiteで読み直す)


ゲームとしては、片付けが終わる10手のうちに親しさ70で「明日、一番前にいてほしい」と言わせれば達成。
嫌がることを続けると帰られます。
本や料理の話で話題が開く、弱音のあとの肩は「心配」として受け取られる、彼女からの問いを軽く流すと傷つく、といった仕掛けは全部Jevの判定で動いています。
当然ですが、人物も場面もすべて架空です。

Jevに何を聞いているか

Jevは文章を返しません。stateとquestionsを渡すと、選択(Choice)・段階(Score)・真偽(Noul)を確率つきで返します。1ターンに1リクエスト、4問です(彼女からの問いがあった手だけ、別に1問)。

{
  "model": "jev-latest",
  "state": {
    "ヒロイン": { "名前": "日向まひろ", "性格": "とてもおとなしく地味で……男性と話すことに慣れていない。優しくされる・褒められる・体に触れられると、まず照れて固まる……", "話し方": "……" },
    "場面": "学園祭前日の夜、部室。二人で機材の片付けをしている。明日はまひろが初めてステージでベースを弾く。",
    "現在の好感度": 50,
    "開いている話題": [],
    "これまでのやりとり": [{ "話者": "ヒロイン", "発言": "あっ……すみません、ケーブル、絡まって……" }],
    "プレイヤーの直前の発言": "練習、ずっと見てたよ。まひろのベース、好きだよ"
  },
  "questions": {
    "mood":  { "type": "choice", "instructions": "プレイヤーの直前の発言を受けて、ヒロインが考える前にとっさに抱く本音の感情(表情に出るもの)はどれか。……心が動かない発言なら平静(calm)を選ぶ。",
               "criteria": { "joy": "嬉しい・弾む", "shy": "照れる・どぎまぎする", "puzzled": "困る・戸惑う・引く", "upset": "不機嫌・傷つく・怒る", "calm": "平静・特に心が動かない" } },
    "delta": { "type": "score", "instructions": "この発言で、ヒロインのプレイヤーへの好感度はどう動くか。", "criteria": ["0: かなり下がる(-2)", "1: 少し下がる(-1)", "2: 変わらない(0)", "3: 少し上がる(+1)", "4: かなり上がる(+2)"] },
    "hurt":  { "type": "noul", "instructions": "この発言は、ヒロインを傷つける(失礼・見下し・無神経)か。", "criteria": { "true": "傷つける", "false": "傷つけない" } },
    "topic": { "type": "choice", "instructions": "この発言は次のどの話題に触れているか。", "criteria": { "book": "本・読書", "cook": "料理・食べ物", "stage": "明日のステージ・演奏・緊張", "circle": "サークル・部室・機材・仲間", "other": "どれでもない" } }
  }
}

返ってくるのはこれだけです(抜粋)。

{ "answers": {
    "mood":  { "choice": "shy", "probabilities": { "shy": 0.91, "joy": 0.07, "calm": 0.01, "puzzled": 0.01, "upset": 0.00 }, "confidence": 0.93 },
    "delta": { "score": 3, "probabilities": { "3": 0.62, "4": 0.31, "2": 0.07 } },
    "hurt":  { "noul": 0.02 },
    "topic": { "choice": "stage", "probabilities": { "stage": 0.78, "other": 0.15 } } },
  "usage": { "input_tokens": 1180 } }

これを受けて画面がやることは単純です。moodで表情、probabilities[mood]で一言と表情の強さ(0.6未満=弱、0.85未満=中、それ以上=強)、deltaの期待値で親しさ、hurtが0.8以上なら「嫌がった」、topicが0.6以上なら話題が開く。ここまで0.2秒前後、入力1,180トークンで**$0.00005**(入力100万トークン$0.042・出力無料)。だから、打っている途中の書きかけの文も0.35秒ごとにJevに読ませて、顔だけ黙って動かせます。
※追記:「0.35秒ごとに顔だけ動かす」は、その後OFFにしました。心読まれてるっぽくて不気味なので。

そのあとClaudeには「本音はshy、一言は『……あ、はい』。その続きを書く」と渡します。Claudeは判定をしません。判定はJevが済ませていて、Claudeの仕事は「決まった本音の中で言葉を選ぶ」ことだけです。

なぜLLMでは不充分だったのか

「LLMでも判定はできるだろ?」というお声もありましょう。その通りです。
実際、このゲームにはLLMだけで動くモードがあり、そちらでも遊べます。
台詞30本を同じ基準文でJevとClaudeに3回ずつ判定させましたので、数字でご覧ください(bench.mjs)。

Jev(2回の実測) Claude Haiku 4.5 Claude Opus 5
応答時間p50 179〜183 ms 1,078 ms 1,934 ms
応答時間p95 264〜271 ms 1,287 ms 2,930 ms
同じ入力3回で感情が全て同じ 28〜29/30 26/30 25/30
90回分の費用(参考) $0.008 $0.18 $0.29

ここからわかることを文章にすると、主に4つです。

1.速さ。顔は言葉を待てない

人は会話する時、言葉よりも先に顔や視線が動くことに慣れています。1秒止まると、それはもう「考えている」に見える。
台詞をしゃべる(≒画面に描画する)時間は台詞の長さで決まり、判定の時間はそれと無関係です。判定と生成を別の仕組みに分けることでしか、顔は言葉より先に動きません。

2.確率が使える。「どれを切るか」と「どの強さで切るか」

今回用意した「一言」は「場面×本音×強さ」からなる22通りのカードです(うち3つは「……」だけの無音)。Jevは本音を選ぶだけでなく確率を返すので、照れ55%なら弱い「……あ」、99%なら強い「あ、っ……!」。表情も強さで2種類を切り替えます(強い照れは本で口元を隠す)。
校正された確率をそのまま演出の強さに使う、が判断特化モデルの一番の使い所でした。

正直に書くと、固定の一言を22枚から選ぶだけなら小さな分類器でも足ります。Jevが違うのは、性格・好感度・直前のやりとり・同じ行為の繰り返し回数といった文脈つきの選択を、学習なしで日本語の基準文だけで0.2秒でやれることです。

3.揺れは、思ったほど差がなかった

感情のラベルでは、Jev 28〜29/30、Claude 25〜26/30。JSONスキーマで縛ったLLMは思ったより安定します。「Jevだから安定・不安定」とは言えません。

4.感情のラベルは半分ずれ、方向のある判定は揃う

JevとClaudeを独立に判定させると、感情の一致は14〜17/30。割れるのは「照れる」対「嬉しい」と、「うん」「そっか」のような短い相づちに対する「照れる」対「平静」で、どちらも間違いではありません。一方、好感度の上下(±1以内で27〜28/30)や「傷つけたか」(23〜27/30)は揃います。

最初は「独立に判定させて、ずれを見せる」設計でしたが、作ってみるとずれは面白いより気持ち悪い。自然に見えたのは、顔・一言・言葉・声が、時間差はあっても同じ本音でそろっているときでした。だから反射(Jev)で決まった本音をClaudeにも渡し、Claudeは「その本音の続き」だけを書きます。

Jevの使い所(この作品で分かった範囲)

効いた

  • 考える前の反応を、言葉と切り離して先に出す。表情・効果音・好感度バー・BGMの切り替えのような「言葉を待たずに動いてよいもの」
  • 確率を演出の強さに使う。Choiceの確率で弱・中・強、Noulの値で「嫌がった」のタグ
  • 小さな判定を何度も。打鍵中の先読み(1回$0.00005)、「彼女の問いを流したか」だけの別呼び出し(189ms)
  • 状況で意味が変わる規則を基準文の一文で入れる(「弱音のあとの肩は心配」)。学習もプロンプトの試行錯誤もなく、その一文で判定が変わりました

効かなかった・向かない

  • 台詞。ご存知かもしれませんが、Jevは文章を返しません。言葉はLLMの仕事です
  • 設定の偏りを文脈で薄めること。LLMは「気になっている相手」でも雑談には平静に返しますが、Jevはstateに書いたとおり、全部に照れます(後述)
  • 大きなstateに多くの判定を混ぜること。関係ない文脈が増えるほど、個々の判定が鈍ります(これも後述)

踏んじゃったミス

設定文がそのまま判定を引っ張る

初期版で、ヒロインをツンデレにしたら、Jevの本音がほぼ全部「デレ」になりました。「はいはい」でも照れ68%。設定文に「相手のことが気になっている」と書いた時点で、何を言われても照れる人になっていた。照れる条件を「褒められた・優しくされた・触れられた・気持ちを向けられたとき」に絞り、基準に「心が動かない発言なら平静」と足したら、「はいはい」は平静58%に分かれました。Jevはstateに書いた設定をそのまま信じます。LLMなら文脈で薄めてくれる偏りが、そのまま出ます。

ちなみに、この傾向は今のキャラクターにも残っています。「男性と話すことに慣れていない。優しくされると、まず照れて固まる」と書いた彼女は、「うん」「そっか」「……」にもJevでは照れ(好感度は動かない)、Claudeでは平静。ゲームでは弱い照れ=表情だけ・一言は無音なので、この差はそのままにしました。

大きなstateに混ぜると薄まる。小さな別呼び出しにする

仲良くなると彼女の方から問いかけてきます(「初めて人前で弾いたとき、怖くなかったですか?」)。これに「さあね。どうでもいいけど」と返したら傷つく、という地雷を作りました。最初は設定・場面・履歴・5問をまとめた大きなstateに「問いを流したか」のNoulを混ぜたら、0.26(流していない)。同じ返事を、問いと返事だけの小さなstateで聞くと0.91〜1.00。公式が認める弱点「不要な文脈で精度が落ちる」の実例です。

// 問いと返事だけを渡す小さな呼び出し(反射と並列に走らせる)
const body = { model: 'jev-latest',
  state: { ヒロインの問い: question, プレイヤーの返事: reply },
  questions: { how: { type: 'choice', instructions: 'ヒロインの問いに対する、プレイヤーの返事の態度はどれか。',
    criteria: { sincere: '問いに向き合って、自分のことを正直に答えている', deflect: 'はぐらかす・どうでもよさそう・話題を変える',
                tease: '茶化す・からかう', flatter: '上辺だけ褒めて答えていない' } } } };

教訓: Jevには「その判定に要るものだけ」を渡す。1リクエストにまとめて安くするより、小さく分けて並列に投げる方が正しく、費用も誤差だと思います(個人の感想)。

状況で意味が変わる触れ方は、基準文に一文足すだけ

このご時世どうかと思いつつ、身体に触れるアクションを入れています。
中でも特徴的なのが、「肩に触れる」というアクション。彼女には普段なら距離が近すぎて嫌がられます。しかし、会話の文脈によっては許容されることがあります。
それは、直前の会話で彼女が弱音を口にしていたときです(もう一つ、好感度が70以上のときも)。この時、彼女の肩に手を置くと「心配」として受け取られます。
基準文に「直前に彼女が弱音・不安を口にしていたときは、心配や親しさとして受け取る」と一文足すだけで、「明日のステージ、怖い?」→「指が震えて、二小節目でずれそうで」→肩に触れる→照れ85%・嫌がる0.28・「手……。でも、少しだけ、落ち着きました」になりました。

声と絵で踏んだミス

  • 声質は文章から作る(GeminiのVoice design)。「20代前半、低めで落ち着き、抑揚が少なく淡々。感情を声に出すのが苦手だが、ふとした瞬間に温かさがにじむ」でvoice_…のIDが返り、以後その声で読めます。かわいさの大半はここで決まり、プロンプトで直せるのは調子だけでした。「10代後半の少女」は安全ポリシーで弾かれました。そういうことじゃないんですけどね。
  • 指示に「小さな声」「間を置いて」と書くと、TTSは本当に小さく、本当に長い無音を入れます(冒頭の台詞16.7秒のうち12.7秒が無音)。「……」も長い間になるので、声に渡す文だけ読点に変えました。囁きがマジで聞こえん。
  • 絵は基準画1枚から、同じ人物のまま表情だけ編集。5回作り直しました。立ち絵にするための背景抜きは、Geminiの編集に「背景だけ一様な緑に、人物は同一」と頼むと人物が変わらずにグリーンバックになったので、画面側で透明化処理をしています。背景は1枚目にアンプの実在ブランド名とポスターの英字が出たので作り直しました。やめてくれ!
  • その他、過程はリポジトリ内の制作記録(MAKING.md)に、ボツごと残しています。

大切な「費用とTier」の話

GeminiのTTSは無料枠で1日10回、有料Tier 1でも1日100回(モデルごと・プロジェクトごと)。1ゲームで10〜15回呼ぶので、Tier 1の鍵で遊べるのは1日6〜8ゲームです。Tier 2(累計$100の支払い+最初の支払いから3日)でこの壁は消えます。私はこの記事を書いている日に前払いで累計$100を超え、同日中にTier 2になりました。

とはいえ遊ぶ人の多くは無料枠かTier 1なので、配布は「各自が自分の鍵で動かす」形にし、上限に当たっても黙って止まらないようにしました。

  • 一言(19本)と冒頭・結末(10本)の音声は「文・声・指示」のハッシュで一度だけ作り、リポジトリに同梱。鍵が無くても鳴る
  • 声はFlash→Lite→字幕モード。上限に当たった時刻をサーバーが覚え、戻るまで呼びに行かない
  • 言葉はClaudeが使えなければ、本音に合う予備の台詞

書いておくと、この記事を書いている日に、私の先読みの不具合(既定の声を落として同梱済みを「無い」と判定し、上限の知らせを読めずに再試行を連打)で、100回のうち70回を昼までに溶かしました。枠のあるAPIの先読みは「何本作るか」を先に出して、1本目の前に止まれるようにしておくべきでした。

考慮すべき点(念のため)

  • 30本×3回の小さな実測です。傾向までしか言えません。
  • Jevのレスポンスの例は、実測の値を丸めて整形したものです(構造は実物どおり)。
  • TypeSafe AIの入力データの保持・学習利用の方針は、公式ドキュメントに記載が見当たりません(2026-09時点)。このゲームは架空の台詞しか送りませんが、自分の言葉を入れるなら気に留めてください。
  • 「ハルシネーションゼロ」はスキーマ外の値が出ないという意味で、判定が正しい保証ではありません。

遊ぶ(再掲)

誰でも遊べるようにしました!
GitHub Pagesから、Google Cloud Runに飛びます。
https://ozaki-taisuke.github.io/jev-kano/

その他の遊び方(旧版/開発者向け)

公開リポジトリからcloneをお願いします。

npm install
cp .env.example .env   # TYPESAFE_API_KEY / ANTHROPIC_API_KEY / (任意) GEMINI_API_KEY
npm start              # http://127.0.0.1:8792/

Jevの鍵が無ければLLMだけモード、Geminiの鍵が無ければ字幕モード(一言と冒頭・結末は同梱の音声で鳴る)で動きます。反射・言葉・声の時間は画面下の帯に常に出ます。?debug=1にすると一言の時間と本音の内訳(判定名と確率の段階)も出ます。

本当はGitHub Pagesなどに公開してお手軽にしたいのですが、時間の制約などがあり、現時点ではここまでにしたいと思います。

最後に

  • 感想・ご意見など、なんでもコメントお待ちしております!
  • まだ改善の余地や設定の矛盾がありまくるはずです。ご指摘いただけると助かります。
  • 別のキャラクターやシチュエーションも作りたくなっています。演劇部とかどうですかね!
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?