About

Chrome内蔵LLM(Prompt API)はタダのAPIではない

訪問者のGPUで動く、結果を信用できない計算 — だから補助にだけ使う

Chromeでこの2行が動く。

const session = await LanguageModel.create()
const answer = await session.prompt("このコメントは誹謗中傷?")

APIキーもサーバーもない。最初はタダのGPT APIに見える。結論から言うと違う。これは訪問者のGPUで動く、結果を信用できないクライアント側の計算だ。この記事を読めば、このAPIをどこに使ってよくてどこに使ってはいけないか、1つの基準で分けられるようになる。

きっかけは韓国のYouTubeチャンネル「コーディングアップル」の動画(Chromeがこっそり入れる2GBのファイル)だった。自分で確かめたくて、JSテスト用のアプリにセクションを1つ足した。

ダウンロードと利用は別々に動く

Chrome built-in AI: when things actually happenPage loadavailability()downloads nothingUser clickcreate()needs user activationUseprompt()runs on the visitor GPUTranslator: ko to ja goes through EnglishkoKoreanenpivotjaJapaneseen_ko 75MBen_ja 76MBMeasured 2026-09-19, Chrome 152 on macOS, no flags

availability() は何もダウンロードしない。ページ読み込み時に呼んでも安全だ。実際の準備は create() で始まり、取得が必要な場合はユーザーのクリック(user activation)の中で呼ぶ必要がある。セッションは会話履歴を持つので、判定のように毎回独立させたい処理は clone() で分けて使う。responseConstraint にJSON Schemaを渡せば出力形式を強制できる。

実際に測ってみた(2026-09-19、macOS・Chrome 152・フラグなし)

モデルは数か月前から入っていた。 プロフィールフォルダの OptGuideOnDeviceModel/ 配下に weights.bin が4.27GB、日付は2026-04-27。ボタンを押す5か月前だ。動画の「こっそり入るファイル」はこれだった。

downloadable は「ファイルがない」という意味ではなかった。 ページ読み込み時は downloadable だったのに、create() の後も weights.bin の日付は変わらなかった。何もダウンロードしていない。代わりに create() に18秒かかり、その瞬間に program_cache.bin ができた。ダウンロードではなくGPU向けの準備(シェーダーのコンパイル)に見える。

通常のChromeで出てきたのはGemini Nanoだった。 manifestのモデル名は v3Nano。動画で言っていたGemma 4(約2GB)は自分の環境では確認できなかった。なのでGemma 4の品質についてはこの記事では書かない。

翻訳は英語を経由する。 韓国語→日本語を頼んだら、取得されたのは en_ko(75MB)と en_ja(76MB)の2組だった。韓国語→英語→日本語の2段翻訳だ。言語N個に対してモデル2N個で済むのはいいが、敬語の段階のように英語にないニュアンスが途中で落ちうる。日韓翻訳には不利な作りだ。

誹謗中傷判定は理由と結論が食い違った。

{"toxic": true, "category": "insult",
 "reason": "特定の相手に外見について肯定的な表現を使っており、侮辱的な意味を含んでいる。"}

(韓国語の出力を訳したもの)「肯定的な表現」なのに「侮辱」。responseConstraint のおかげでJSONの形は守られたが、中身は信用できない。下の基準の実例だ。

サーバーAPIと比べると

サーバーのLLM API Chrome内蔵LLM
コスト 自分が払う(トークン課金) 自分は0円。代わりに訪問者の電気・GPU・ディスクが払う
入力データ サーバーに出ていく 端末の外に出ない
結果の信頼 自分のサーバーが出した値 DevToolsでいくらでも書き換えられる
使える人 ブラウザを問わない デスクトップのChromeで、スペック条件あり。Android・iOSは未対応
モデル 選べる ブラウザが用意したもの(自分の環境はGemini Nano)

スペック条件はかなり高い。公式ドキュメントでは、プロフィールのあるディスクに22GBの空き、VRAMは4GB超が必要とされている。訪問者全員が使えると思ってはいけない。

基準は1つ — クライアントが結果をごまかしても誰も損しないか

使い道 判定 理由
コメント送信前の「ちょっと強くない?」警告 使う ごまかしても本人が損するだけ。サーバー側の検証は別に置く
誹謗中傷のブロック(サーバー検証の代わり) 使わない JS 1行で回避される
ブログ画像のalt提案 使う 間違えても書き手が直す
読む人向けの翻訳・要約 使う 結果を見る人=計算した人
ポイント・ランキング・課金判定 使わない ごまかすと得をする
機能全体をこれに頼る 使わない Chrome以外のユーザーは何も使えない。フォールバック必須

一言でいえば、補助(assist)には使い、判定(enforce)には使わない。

判定が必要ならサーバー側でやる。サーバーで速く安く判定だけするモデルなら、Jev のような選択肢もある。

機能をブラウザ内で動かすかサーバーに送るかを決める段階なら、negabaroに相談してもらって構わない。こうした境界線を引く仕事を実際のサービスでやってきた。

キーコンセプト

1

ページ読み込み時は availability() だけ — 何もダウンロードしない

2

create() はユーザーのクリックの中で — downloadable ならここで準備が始まる

3

判定のように毎回独立させたいなら clone() でセッションを分ける

4

形式は responseConstraint で守り、中身は信用しない

5

Chrome以外のユーザー向けのフォールバックを必ず置く

メリット

  • ✓ 推論コスト0 — サーバー・APIキー・課金がない
  • ✓ 入力が端末の外に出ない

デメリット

  • ✗ 結果を信用できない — クライアントが書き換えられる
  • ✗ デスクトップのChromeと高いスペックでしか動かない
  • ✗ 翻訳が英語を経由し、敬語のようなニュアンスが落ちうる

ユースケース

コメント送信前の警告 — ブロックはサーバー、知らせるのはブラウザ 書き手の補助 — 画像alt提案、文章の手直し 読む人向けの翻訳・要約 — 入力がサーバーに出ない
← レビュアーは同じなのに結果が違う — AI実装の品質はレビューの手前で決まる Jev — 文章を書かないLLM、判断だけを速く →