今話題の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秒後に声が続きます。
役割分担
話しかける 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などに公開してお手軽にしたいのですが、時間の制約などがあり、現時点ではここまでにしたいと思います。
最後に
- 感想・ご意見など、なんでもコメントお待ちしております!
- まだ改善の余地や設定の矛盾がありまくるはずです。ご指摘いただけると助かります。
- 別のキャラクターやシチュエーションも作りたくなっています。演劇部とかどうですかね!


