🏁 Chapter 0: 導入・環境チェック

所要時間: 10 分 / 必修
この章のゴール: スターターコードを取得してローカルで起動し、全 API の共通パターンを理解し、環境が使えることを確認する

📚 Chrome Built-in AI とは?

Chrome 内蔵の Gemini Nano モデルを使い、サーバー通信なしにブラウザ内で LLM 推論を実行できる仕組みです。プライバシー・遅延・コストの面で強力な利点があります。

  • 🔒 プライバシー — 入力したテキストや画像はブラウザ内で完結して処理され、外部サーバーへ一切送信されません。機密文書、社内資料、個人情報、下書き段階のアイデアなど「他人に見られたくないデータ」を安全に AI 処理にかけられます。GDPR や日本の個人情報保護法への対応もシンプルになり、「AI を使いたいけどデータ流出が怖い」という組織の壁を大きく下げます。
  • 遅延 — クラウド LLM ではリクエストごとにネットワークを往復し、地理的距離やサーバー混雑の影響を受けます。オンデバイス推論はネットワークのラウンドトリップがゼロなので、体感速度が段違いに速く、応答も安定します。ストリーミング出力(Chapter 3 で扱います)と組み合わせれば、ユーザーが待たされない滑らかな UX を実現できます。オフラインや電波の悪い場所でも動作する点も強みです。
  • 💰 コスト — クラウド LLM API の従量課金がかかりません。モデルは初回ダウンロード後は端末に常駐し、何回呼び出しても追加コストはゼロ。無料/フリーミアム型サービス、教育アプリ、月間何百万リクエストが飛ぶ大規模サービスでも、AI 呼び出しコストを心配せずに機能を組み込めます。運用時のコスト予測も立てやすくなります。

今回扱う 4 つの API(Language Detector / Translator / Summarizer / Prompt API)は、いずれも Chrome 148 Stable から標準搭載されており、フラグ設定なしで使えます。Chrome を最新版に更新するだけで OK です 🎉

🎯 5 つの API

  • 🔤 Language Detector — テキストの言語を判定
  • 🌐 Translator — 多言語翻訳
  • 📝 Summarizer — 長文の要約
  • 💬 Prompt API(テキスト) — 汎用 LLM(構造化出力対応)
  • 📷 Prompt API(画像) — マルチモーダル入力

🚀 スターターを取得しよう

コードを書き始める前に、スターターキット(HTML/CSS/JS の骨格+TODO コメント付き)をローカルに用意しましょう。

ZIP をダウンロードして展開

以下のリンクから ZIP を取得し、任意の場所に展開してください:

📥 スターター ZIP をダウンロード

展開すると starter/ フォルダが出てきます 🎁

💡 git clone は使わないでください。全ファイル(模範解答含む)が落ちてくるので、答えが見えてしまいます 🙈

Chrome で開く

ローカルサーバは不要です!Chrome 148 以降starter/index.html をダブルクリック(または右クリック → 「このアプリケーションで開く」→ Chrome)して開くだけ 🎉

  • macOS: Finder で starter/index.html を右クリック → Chrome で開く
  • Windows: エクスプローラーで右クリック → 「プログラムから開く」→ Chrome
  • Linux: xdg-open starter/index.html もしくはファイラーからダブルクリック

ヘッダにある 5 つの環境バッジが すべて ✅ available になっていれば準備完了です!

💡 バッジに ⬇ downloadable が残っている場合は、まだ Gemini Nano のモデルダウンロードが済んでいません。事前アナウンスの動作確認スニペットを再度実行してダウンロードを完了させてください。

🧭 共通の 3 ステップパターン

全 API は同じ 3 ステップで呼び出せます:

// ① 使えるか判定(4 状態を返す)
const state = await Api.availability();
// 'unavailable' | 'downloadable' | 'downloading' | 'available'

// ② セッション/インスタンスを作成
const session = await Api.create({ ...options });

// ③ 実行メソッド
const result = await session.someMethod(input);

🚦 availability の 4 状態

  • unavailable — 環境が対応していない(Chrome バージョン不足・OS/ハードウェア要件不足など)
  • downloadable — 使えるが、モデルのダウンロードがまだ
  • downloading — 現在ダウンロード中
  • available — すぐ使える状態 ✅
💡 downloadable のときに create() を呼ぶと自動的にダウンロードが開始されます。monitor オプションで進捗イベントを購読できます(Chapter 2 で扱います)。
✅ Checkpoint
  1. スターター ZIP をダウンロードして任意の場所に展開した
  2. starter/index.html を Chrome 148 以降でダブルクリックして開いた
  3. ヘッダの環境バッジが すべて ✅ available になっている
  4. 共通の 3 ステップパターン(availabilitycreate → 実行)と、availability の 4 状態を理解した

🚀 次のステップ

環境が整ったら、左サイドバーから好きな章を選んでください。順序は自由です。

迷ったら難易度⭐️の少ない Chapter 1(Language Detector) からがおすすめです。

🔤 Chapter 1: Language Detector

所要時間: 7 分 / 難易度: ⭐️
この章のご褒美: たった 7 分でオンデバイス AI を初めて動かす成功体験。多言語文字列の言語を自動判定する小さなツールが手に入ります

🎯 ゴール

入力文の言語を判定し、トップ 3 候補を信頼度バー付きで表示する。

📚 API ざっくり紹介

Language Detector は、入力テキストがどの言語かを推定します。複数の候補と信頼度を返すため、単に「英語らしい」だけでなく「英語の可能性が 98%、ドイツ語の可能性が 1%…」といった分布として結果を扱えます。

グローバルオブジェクトとして LanguageDetector がブラウザに露出されており、そこから静的メソッドで使い始めます。

公式ドキュメント: Language Detection API

📖 API リファレンス

LanguageDetector.availability(options?)

この環境で Language Detector が使えるかを判定する静的メソッド。ページ読込直後の環境チェックで最初に呼ぶ関数です。

  • 引数: 省略可。特定言語だけを判定対象にしたい場合は { expectedInputLanguages: ['en', 'ja', ...] } を渡すことで、その言語ペアが使えるかを判定できます。
  • 戻り値: Promise<string>。以下 4 状態のいずれかが返ります。
    • 'unavailable' — 環境非対応(Chrome バージョン不足・OS 要件不足など)
    • 'downloadable' — 使えるが、モデル未ダウンロード
    • 'downloading' — 現在ダウンロード中
    • 'available' — すぐ使える ✅

LanguageDetector.create(options?)

実際に判定を実行するためのインスタンスを作成する静的メソッド。availability がまだ 'available' でない場合、この呼び出しがトリガーになってモデルダウンロードが開始されます

  • 引数: 省略可。オプションオブジェクト。
    • expectedInputLanguages?: string[] — 判定候補を絞る言語コード配列(['en', 'ja', 'fr'] など)。指定すると精度と速度が向上します。
    • monitor?: (m: EventTarget) => void — モデル DL 進捗を購読するためのコールバック。m.addEventListener('downloadprogress', e => ...)e.loaded(0〜1)を受け取れます。
  • 戻り値: Promise<LanguageDetector>(インスタンス)。以下のメソッドが呼べます。

detector.detect(text)

作成したインスタンスに対して、実際にテキストの言語を判定させるメソッド。

  • 引数: text: string — 判定対象のテキスト。
  • 戻り値: Promise<Array<{ detectedLanguage: string, confidence: number }>>
    • detectedLanguage: BCP-47 言語コード('en', 'ja', 'fr' など)
    • confidence: 0〜1 の信頼度。高いほど確からしい。
    • 配列は信頼度降順で並んでいます。最初の要素が最も確からしい候補。

戻り値の例:

[
  { detectedLanguage: 'fr', confidence: 0.98 },
  { detectedLanguage: 'de', confidence: 0.008 },
  { detectedLanguage: 'en', confidence: 0.004 },
  // ... 低信頼度の候補が続く
]

detector.destroy()

インスタンスが持つリソースを解放します。頻繁に作り直すアプリでは呼ぶと良いですが、この Codelab では省略しても問題ありません。

💡 ライフサイクル: 一度 create() したインスタンスは何度でも detect() できます。判定のたびに create() するのは無駄なので、可能なら再利用しましょう。

💻 やってみよう!

  1. 📝 コードを書くstarter/main.jshandleLanguageDetector() 関数内、// TODO N: コメントの下にコードを追記する
  2. 🔄 リロード — Chrome で開いている starter/index.html のページを再読込(Cmd+R / Ctrl+R)
  3. 🖱️ UI 操作 — スターターアプリ上部の 🔤 言語判定 タブを開き、テキストエリアに文を入力して「判定する」ボタンを押す。以下 2 つは必ず両方試して、返される言語コードと信頼度の違いを観察してみてください:
    • フランス語: Bonjour, comment allez-vous ? → 結果配列のトップが fr、高い信頼度
    • 日本語: こんにちは、今日はいい天気ですね。 → 結果配列のトップが ja、高い信頼度
    余裕があれば Hi のような短い文字列も試すと、信頼度がぐっと下がる様子が観察できます
  4. ✅ 動作確認 — 出力エリアに [{ detectedLanguage: 'fr', confidence: 0.98 }, ...] のような配列が表示されれば成功!

🌱 STEP 1: 最小動作(3 分)

やること: 入力文の言語を判定するだけ。

どこに書く: main.jshandleLanguageDetector() 内、// TODO 1// TODO 3

// TODO 1: セッション作成
const detector = await LanguageDetector.create();

// TODO 2: 判定実行
const results = await detector.detect(text);

// TODO 3: 全結果を表示
renderOutput('chapter-1', results);

期待される画面: 出力エリアに [{ detectedLanguage: 'fr', confidence: 0.98 }, ...] のような配列が表示される。

🌿 STEP 2: トップ 3 に絞る(2 分)

やること: 結果配列を上位 3 件に絞って表示。

// TODO 3 を書き換え
renderOutput('chapter-1', results.slice(0, 3));

Language Detector は多数の候補(低信頼度のものも含む)を返します。トップ 3 で十分実用的です。

🌳 STEP 3: 候補言語を絞る(2 分)

やること: UI の「候補言語を英・日・仏に絞る」チェックボックスが ON のときだけ expectedInputLanguages を渡す。

// TODO 1 を書き換え
const detector = await LanguageDetector.create(
  document.getElementById('ld-restrict').checked
    ? { expectedInputLanguages: ['en', 'ja', 'fr'] }
    : undefined
);
💡 候補を絞ると精度と速度が向上します。用途があらかじめ限定的なら積極的に指定すべきオプション。

🎨 Extend this!

  • 入力中にリアルタイムで判定(input イベント + デバウンス)
  • 信頼度をカラフルなバーで可視化
  • Translator と組み合わせて自動翻訳(Ch2 と連携)
  • 絵文字フラッグを言語コードから引く 🇫🇷🇯🇵🇺🇸
✅ Checkpoint
  1. 英語・日本語・仏語の文が正しく判定される
  2. トップ 3 の信頼度が出力に表示される
  3. 候補絞り込みチェックの ON/OFF で結果配列の中身が変わる

🌐 Chapter 2: Translator

所要時間: 10 分 / 難易度: ⭐️⭐️
この章のご褒美: Gemini Nano のモデルが目の前でダウンロードされる瞬間を目撃できる。完成後は多言語を自前で翻訳できるオフライン翻訳ツールが動きます

🎯 ゴール

source/target 言語を UI で選んで翻訳し、初回のモデルダウンロード進捗をリアルタイム表示する。

📚 API ざっくり紹介

Translator は、「source 言語」から「target 言語」への 1 方向翻訳を行います。逆方向に翻訳したければ source と target を入れ替えた別のインスタンスを作ります。

言語ペアごとに専用モデルを持つのが特徴で、たとえば en→ja のモデルと ja→en のモデルは別々にダウンロードされます。初回だけ数十MB〜数百MBのダウンロードが発生し、2 回目以降はキャッシュから即座に起動します。

公式ドキュメント: Translator API

📖 API リファレンス

Translator.availability(options)

指定した言語ペアで翻訳が使えるかを判定する静的メソッド。Language Detector と違って source/target の指定が必須な点に注意。

  • 引数: { sourceLanguage: string, targetLanguage: string }(必須)。BCP-47 言語コード('en', 'ja', 'fr' など)。
  • 戻り値: Promise<string>'unavailable' | 'downloadable' | 'downloading' | 'available' のいずれか。

Translator.create(options)

翻訳を実行するためのインスタンスを作成する静的メソッド。availability'downloadable' の状態でこれを呼ぶと、指定した言語ペアのモデルダウンロードが始まります。

  • 引数: オプションオブジェクト(必須)。
    • sourceLanguage: string(必須)— 翻訳元の言語コード
    • targetLanguage: string(必須)— 翻訳先の言語コード
    • monitor?: (m: EventTarget) => void — DL 進捗コールバック。m.addEventListener('downloadprogress', e => ...)
    • signal?: AbortSignal — 作成処理をキャンセルする場合に使用
  • 戻り値: Promise<Translator>(インスタンス)。

translator.translate(text)

作成したインスタンスに対して、実際に翻訳を実行するメソッド。

  • 引数: text: string — 翻訳したい原文。
  • 戻り値: Promise<string> — 翻訳された文字列(1 つの完成した文字列)。

戻り値の例:

await translator.translate('Hello, how are you?');
// → 'こんにちは、お元気ですか?'

translator.translateStreaming(text)

翻訳結果をチャンクごとに逐次受け取るためのメソッド。長文翻訳で体感速度を上げたいときに有効です。

  • 引数: text: string
  • 戻り値: ReadableStream<string>(async iterable)。for await (const chunk of stream) { ... } で逐次消費できます。

📡 downloadprogress イベント

monitor コールバック内で購読するイベント。モデルダウンロード中に繰り返し発火します。

  • イベントプロパティ:
    • e.loaded: number — 進捗率(0〜1)。0.5 なら 50%。
  • ダウンロードが完了するとイベント発火は止まり、create() の Promise が解決します。
💡 ペアごとにインスタンスを持つ: en→ja と ja→en は別々の Translator インスタンスです。両方向翻訳したいアプリでは、2 つのインスタンスを作って管理します。

💻 やってみよう!

  1. 📝 コードを書くstarter/main.jshandleTranslate() 関数内、// TODO N: コメントの下にコードを追記する
  2. 🔄 リロード — Chrome で開いている starter/index.html のページを再読込(Cmd+R / Ctrl+R)
  3. 🖱️ UI 操作 — スターターアプリ上部の 🌐 翻訳 タブを開き、翻訳元/先の言語を選び、テキストエリアに文を入力して「翻訳する」ボタンを押す。以下 2 つは必ず両方試して、言語ペアごとにモデルが分かれている挙動を確認してください:
    • ja → en で「今日は晴れていて気持ちがいいですね。」→ 英訳が返る(初回はモデル DL のため進捗バーが動く)
    • en → fr で I love machine learning. → 仏訳が返る(別ペアなので、これも初回は再び進捗バーが動く。ペアごとに専用モデルがある証拠です)
    同じペアで 2 回目に実行すると、進捗バーは出ずに即翻訳されます(キャッシュされた証拠)
  4. ✅ 動作確認 — 出力エリアに翻訳結果が表示されれば成功!(初めての言語ペアはモデル DL のため進捗バーが動き、2 回目以降は即座に翻訳されます)

🌱 STEP 1: 最小動作(4 分)

やること: source/target を指定して翻訳する。

// TODO 2: セッション作成
const translator = await Translator.create({ sourceLanguage, targetLanguage });

// TODO 3: 翻訳実行
const translated = await translator.translate(text);
renderOutput('chapter-2', translated);

期待: 初回は数秒〜数十秒(モデル DL)、2 回目以降は即座に翻訳完了。

🌿 STEP 2: ダウンロード進捗を可視化(3 分)

やること: monitor オプションで downloadprogress イベントを購読し、進捗バーを更新。

// TODO 1: monitor を定義(downloadprogress を購読)
const monitor = (m) => {
  m.addEventListener('downloadprogress', (e) => {
    showProgress('chapter-2', e.loaded * 100);
  });
};

// TODO 2: create に monitor を渡す
const translator = await Translator.create({
  sourceLanguage,
  targetLanguage,
  monitor,
});
💡 e.loaded は 0〜1 の進捗率。0.5 なら 50%。e.loaded * 100 でパーセント表示できます。

🌳 STEP 3: 複数言語ペアを試す(3 分)

UI の source/target セレクトを切り替えて、複数のペアで動作を確認:

  • en → ja / ja → en
  • fr → ja / ja → fr
  • en → de

気づき: 新しい言語ペアを初めて使うたびにモデル DL が発生します。同じペアの 2 回目以降は即座。

🎨 Extend this!

  • 逆翻訳で品質チェック(en→ja→en して原文と比較)
  • Language Detector と連携して source を自動判定
  • 翻訳結果の音声合成(speechSynthesis
  • translateStreaming() で長文翻訳を逐次出力
✅ Checkpoint
  1. en → ja で翻訳結果が出る
  2. 初めての言語ペアで進捗バーが動く
  3. 同じ言語ペアの 2 回目は進捗バーが出ない(キャッシュ済み)

📝 Chapter 3: Summarizer

所要時間: 10 分 / 難易度: ⭐️⭐️
この章のご褒美: タイプライター風に流れるストリーミング出力を体験できる。長文が数秒で TL;DR・箇条書き・見出しへと形を変える要約ツールが完成します

🎯 ゴール

長文を要約し、type/length オプションと streaming 出力を切り替えて挙動の違いを体感する。

📚 API ざっくり紹介

Summarizer は、入力テキストを短くまとめるための専用 API です。汎用の Prompt API でも要約はできますが、Summarizer は「要約に特化して最適化されている」ので、より高速で安定した結果が得られます。

出力の形式は 2 つの軸で制御できます:

  • type(要約の型):箇条書き、TL;DR、見出し風など 4 種類
  • length(長さ):short / medium / long の 3 段階

また、実行時は一括で結果を受け取る方法(summarize())と、チャンクごとに逐次受け取る方法(summarizeStreaming())があります。

公式ドキュメント: Summarizer API

📖 API リファレンス

Summarizer.availability(options?)

Summarizer が使えるかを判定する静的メソッド。

  • 引数: 省略可。{ type, length, format } を指定してその設定で使えるかを問い合わせることも可能。
  • 戻り値: Promise<string>(4 状態のいずれか)。

Summarizer.create(options?)

要約セッションを作成します。作成時にオプションを固定するのがポイントで、type や length を切り替えたい場合はインスタンスを作り直します。

  • 引数: オプションオブジェクト(省略可)。
    • type?: 'tl;dr' | 'teaser' | 'key-points' | 'headline' — 要約の型。デフォルトは 'key-points'
      • 'tl;dr' — 超短い一言サマリ
      • 'teaser' — 続きを読みたくなる導入文
      • 'key-points' — 箇条書きの要点
      • 'headline' — ニュース見出し風の一行
    • length?: 'short' | 'medium' | 'long' — 長さのめやす。デフォルトは 'short'
    • format?: 'plain-text' | 'markdown' — 出力形式。デフォルトは 'markdown'。key-points は Markdown 箇条書きで返る。
    • sharedContext?: string — 全ての summarize() 呼び出しに共通する追加文脈(例: 「これらは技術ブログ記事です」)。
    • monitor?: (m) => void — DL 進捗コールバック(Translator と同じ形式)。
  • 戻り値: Promise<Summarizer>

summarizer.summarize(text, options?)

作成したセッションで実際に要約を実行するメソッド。

  • 引数:
    • text: string(必須)— 要約したい長文。
    • options?: { context?: string, signal?: AbortSignal } — この呼び出し限定の追加文脈やキャンセルシグナル。
  • 戻り値: Promise<string> — 要約結果の文字列。format'markdown' のときは Markdown 記法が含まれます。

戻り値の例(type: 'key-points', length: 'medium'):

* Chrome Built-in AI は Gemini Nano をオンデバイスで実行する仕組み
* サーバー通信不要でプライバシー・遅延・コストに強い
* Language Detector / Translator / Summarizer / Prompt の 4 API を提供

summarizer.summarizeStreaming(text, options?)

要約結果をチャンクごとに逐次受け取る版。ユーザーに「動いている感」を早く伝えたいときに使います。

  • 引数: summarize() と同じ。
  • 戻り値: ReadableStream<string>for await (const chunk of stream) { ... } で消費。

典型的な使い方:

const stream = summarizer.summarizeStreaming(text);
let acc = '';
for await (const chunk of stream) {
  acc += chunk;
  renderOutput('chapter-3', acc); // 途中経過をどんどん描画
}
💡 チャンクの粒度: chunk は数文字〜数十文字ずつ届きます。累積して表示していくことで「タイプライター風」の演出になります。

💻 やってみよう!

  1. 📝 コードを書くstarter/main.jshandleSummarize() 関数内、// TODO N: コメントの下にコードを追記する
  2. 🔄 リロード — Chrome で開いている starter/index.html のページを再読込(Cmd+R / Ctrl+R)
  3. 🖱️ UI 操作 — スターターアプリ上部の 📝 要約 タブを開き、200 文字以上の長文を入力して「要約する」ボタンを押す。以下は 洋一郎さんのブログ記事からの引用です(もちろんご自身で用意した文章でも OK です):
    僕は、ウェブブラウザを「今日の OS」だと考えています。多くのアプリケーションが、ネイティブアプリではなくウェブアプリとして動いている現状を見れば、これは事実上 OS と呼んで差し支えないのかなと思っています。そして、今後のアプリケーションの形態は、これまでのような「CPU が駆動させる」ものから「LLM が駆動させる」ものへとシフトしていくのではないか、と僕は予想しています。
    上の文章を貼り付けて、同じ入力文で以下 2 パターンのオプションを切り替えて実行し、出力の違いを観察してください:
    • type=key-points, length=medium, Streaming ON → 要点が箇条書きで、文字が徐々に流れて表示される
    • type=headline, length=short, Streaming OFF → 一行の見出し風の要約が一気に返る
    type/length で「要約の性格」がガラッと変わることを体感できます
  4. ✅ 動作確認 — 出力エリアに要約が表示されれば成功!(Streaming ON なら文字が徐々に埋まっていく様子を確認できます)

🌱 STEP 1: 最小動作(4 分)

// TODO 1: セッション作成
const summarizer = await Summarizer.create({ type, length });

// TODO 2: 要約実行
const result = await summarizer.summarize(text);
renderOutput('chapter-3', result);

ヒント: 入力テキストは 200〜1000 文字くらいが試しやすいです。

🌿 STEP 2: type / length を UI と連動(3 分)

type の選択肢:

  • tl;dr — 超短くまとめ
  • teaser — 読みたくさせる導入
  • key-points — 箇条書きの要点
  • headline — 見出し風の一言

length: short / medium / long

UI の select と type/length を連動させ、いくつかの組み合わせを試してみましょう。

🌳 STEP 3: Streaming で逐次表示(3 分)

// TODO 2 を書き換え
const stream = summarizer.summarizeStreaming(text);
let acc = '';
for await (const chunk of stream) {
  acc += chunk;
  renderOutput('chapter-3', acc);
}
💡 体感: 非 streaming だと最後まで待たされますが、streaming は文字が徐々に埋まっていくので体感速度が劇的に上がります。同じ考え方は Chapter 4 の Prompt API でも使えます(promptStreaming())。

🎨 Extend this!

  • URL 入力 → fetch で本文取得 → 要約
  • 要約結果を Translator で他言語に翻訳
  • 複数記事を並列に要約して見出し一覧を作る
  • sharedContext オプションで文脈を与える(技術ブログ用など)
✅ Checkpoint
  1. type / length の切替で出力が変わる
  2. streaming モードで文字が徐々に表示される

💬 Chapter 4: Prompt API テキスト — 感情分析+タグ抽出

所要時間: 13 分 / 難易度: ⭐️⭐️⭐️
この章のご褒美: レビュー文を投げれば毎回同じ形の JSON が返ってくる、LLM を関数のように使える感動を体験できる。文章→データ変換の土台が身につきます

🎯 ゴール

レビュー文を入力すると、{ sentiment, score, tags, summary } という決まった形の JSON で返す。

📚 API ざっくり紹介

Prompt API(グローバル名は LanguageModel)は、他の 3 つの API と違って「これに特化した機能」がなく、汎用的な LLM 呼び出しインターフェイスです。プロンプトを書いて自由な出力を得たり、JSON Schema で出力形式を縛ったりできます。

Chrome Built-in AI の中で最も柔軟で、他 3 API では対応できないタスク(分類・抽出・生成・変換など)はすべてこの API で実現します。

主な機能:

  • チャット形式のセッション管理(system / user / assistant ロール、会話履歴保持)
  • structured outputresponseConstraint で JSON 形式を強制)
  • マルチモーダル入力(画像・音声も投入可能、Chapter 5 で扱う)
  • ストリーミングpromptStreaming()
  • パラメータ制御temperature, topK

公式ドキュメント: Prompt API

📖 API リファレンス

LanguageModel.availability(options?)

Prompt API が使えるかを判定する静的メソッド。マルチモーダル対応の可否も含めて確認できます。

  • 引数: 省略可。{ expectedInputs?: [{ type: 'image' | 'audio' }] } を渡すことで、マルチモーダル入力が使える環境かを判定できます(Chapter 5 で使用)。
  • 戻り値: Promise<string>(4 状態のいずれか)。

LanguageModel.params()

モデルのデフォルトパラメータや上限を取得します。UI で temperature スライダの上限を動的に決めたいときなどに便利。

  • 引数: なし
  • 戻り値: Promise<{ defaultTopK, maxTopK, defaultTemperature, maxTemperature }>

LanguageModel.create(options?)

会話セッションを作成します。「セッション」という概念が重要で、同じセッションに複数回 prompt() すると、過去のやりとりを踏まえた応答になります(マルチターン会話)。

  • 引数: オプションオブジェクト(省略可)。
    • initialPrompts?: Array<{ role: 'system' | 'user' | 'assistant', content: string }> — セッション開始時のプロンプト群。role: 'system' でモデルの役割を定義したり、user/assistant ペアで few-shot 例を仕込んだりします。
    • temperature?: number — 0(保守的・決定論的)〜 上限値(創造的・多様)。デフォルトは LanguageModel.params()defaultTemperature
    • topK?: number — サンプリングの多様性制御。小さいほど確定的、大きいほど多様。
    • expectedInputs?: [{ type: 'image' | 'audio' }] — マルチモーダル入力を使うときに宣言(Chapter 5)。
    • monitor?: (m) => void — DL 進捗コールバック。
    • signal?: AbortSignal — 作成キャンセル用。
  • 戻り値: Promise<LanguageModel>(セッションインスタンス)。

session.prompt(input, options?)

セッションにプロンプトを投げて応答を得る、最も基本のメソッド。

  • 引数:
    • input: 2 通りの形式が使えます。
      • 単純な文字列: await session.prompt('こんにちは')
      • メッセージ配列: await session.prompt([{ role: 'user', content: [...] }]) — 複雑な入力(マルチモーダル含む)のとき使用
    • options?:
      • responseConstraint?: objectJSON Schema オブジェクト。出力を指定形式に強制する(この章の目玉機能)。
      • signal?: AbortSignal — 呼び出しキャンセル用。
  • 戻り値: Promise<string>常に文字列responseConstraint を使った場合も文字列で返るので、JSON.parse(output) でオブジェクトに変換する必要があります。

戻り値の例(responseConstraint あり):

// prompt() が返す文字列(JSON として parse できる形)
'{"sentiment":"positive","score":0.85,"tags":["軽量","持ち運び"],"summary":"軽くて持ち運びに便利なカメラ"}'

// JSON.parse すると:
{
  sentiment: 'positive',
  score: 0.85,
  tags: ['軽量', '持ち運び'],
  summary: '軽くて持ち運びに便利なカメラ',
}

session.promptStreaming(input, options?)

応答を逐次受け取るストリーミング版。長い応答を待たせずに表示したいときに使用。

  • 引数: prompt() と同じ。
  • 戻り値: ReadableStream<string>for await で消費。

session.clone(options?)

現在のセッション状態を保ったままコピーを作成します。「同じ初期プロンプトから複数の分岐会話を試したい」ときに便利。

session.destroy()

セッションを破棄してリソースを解放します。

📊 セッションの状態プロパティ

  • session.inputUsage — 現時点で使用したトークン数
  • session.inputQuota — セッションの入力トークン上限
  • session.temperature, session.topK — 作成時のパラメータ値

上限が近くなったら destroy() して新しいセッションを作り直す、といった管理に使えます。

💡 session と prompt の関係: 1 セッションに複数 prompt を投げると履歴が積み上がっていく。単発の分析タスクなら都度セッションを作って捨てるのが安全。
チャット UI では 1 セッションを保持し続けます(履歴の恩恵を活かす)。

📋 使う JSON Schema

lib/schema.js に既に定義済みです。SENTIMENT_SCHEMAmain.js の先頭で import 済み:

import { SENTIMENT_SCHEMA, BUSINESS_CARD_SCHEMA } from './lib/schema.js';

// SENTIMENT_SCHEMA の中身(抜粋):
// {
//   type: 'object',
//   properties: {
//     sentiment: { type: 'string', enum: ['positive', 'neutral', 'negative'] },
//     score: { type: 'number', minimum: -1, maximum: 1 },
//     tags: { type: 'array', items: { type: 'string' }, maxItems: 5 },
//     summary: { type: 'string' },
//   },
//   required: ['sentiment', 'score', 'tags', 'summary'],
// }

💻 やってみよう!

  1. 📝 コードを書くstarter/main.jshandlePromptText() 関数内、// TODO N: コメントの下にコードを追記する
  2. 🔄 リロード — Chrome で開いている starter/index.html のページを再読込(Cmd+R / Ctrl+R)
  3. 🖱️ UI 操作 — スターターアプリ上部の 💬 感情分析 タブを開き、レビュー文をテキストエリアに入力して「分析する」ボタンを押す。以下 2 つは必ず両方試して、感情スコアが真逆に振れることを確認してください:
    • ポジティブ:「このカメラは最高です!画質もバッテリー持ちも素晴らしくて、旅行に持っていく相棒として大満足。」 → sentiment: "positive", score+1 近く
    • ネガティブ:「値段が高すぎる上に、1 週間で壊れました。カスタマーサポートも遅くて最悪。返品したい。」 → sentiment: "negative", score-1 近く
    さらに Temperature スライダを 01.5 の両極で試すと、tagssummary の揺らぎ方の違いも観察できます
  4. ✅ 動作確認 — 出力エリアに { "sentiment": "...", "score": ..., "tags": [...], "summary": "..." } という JSON が表示されれば成功!

🌱 STEP 1: 自由応答で会話(4 分)

// TODO 1: セッション作成(system prompt 付き)
const session = await LanguageModel.create({
  initialPrompts: [{ role: 'system', content: systemPrompt }],
});

// TODO 2 & 3: 実行して表示
const output = await session.prompt(reviewText);
renderOutput('chapter-4', output);

気づき: このままだと出力形式が毎回バラつきます。文章の場合もあれば、Markdown 混じりの場合も。次の STEP で「関数化」します。

🌿 STEP 2: JSON Schema で強制(5 分)— この章の目玉 ✨

// TODO 2 を書き換え
const output = await session.prompt(reviewText, {
  responseConstraint: SENTIMENT_SCHEMA,
});

// TODO 3: JSON.parse して構造化表示
renderOutput('chapter-4', JSON.parse(output));
💡 体験: 何度実行しても常に同じキーの JSON が返ります。バリデーションのプリミティブとして超便利。
この機能があるおかげで、LLM の出力を確定的にプログラムから扱えます。

🌳 STEP 3: temperature / topK でチューニング(4 分)

const session = await LanguageModel.create({
  initialPrompts: [{ role: 'system', content: systemPrompt }],
  temperature,   // 0(保守的)〜 2(創造的)
  topK: 3,       // サンプリングの多様性
});

UI のスライダで temperature を変えながら、同じレビュー文を複数回実行してみましょう。tagssummary の揺らぎ方が変わります。

  • temperature=0 → 毎回ほぼ同じ結果(決定論的)
  • temperature=1.5 → 毎回違うタグが出てくる(多様性)

🎨 Extend this!

  • 複数レビューをまとめて分析(1 セッションで複数 prompt を投げる)
  • 過去の会話を保持したチャット UI に発展
  • tags を集計してタグクラウド表示
  • Schema をカスタマイズ(例: emojis フィールドを追加して感情絵文字を返させる)
✅ Checkpoint
  1. ポジティブなレビューで sentiment: positive, score が高くなる
  2. ネガティブなレビューで sentiment: negative, score が低くなる
  3. tags に短いキーワードが 3〜5 個含まれる
  4. 常に有効な JSON が返る(parse エラーなし)

📷 Chapter 5: Prompt API 画像 — 名刺 OCR

所要時間: 10 分 / 難易度: ⭐️⭐️⭐️
この章のご褒美: 名刺画像から数秒で連絡先データが JSON になって返ってくる、名刺スキャナが完成します。撮った画像を AI に "見せる" マルチモーダル技術が手に入ります

🎯 ゴール

名刺画像を入力すると、{ name, company, title, email, phone, address, website } の連絡先データを JSON で抽出する。

📚 API ざっくり紹介

Chapter 4 で扱った Prompt API(LanguageModel)と同じ API を、画像入力モードで使うのがこの章のポイントです。新しい API を覚える必要はなく、2 箇所の差分だけ理解すれば OK。

  1. LanguageModel.create() のときに expectedInputs: [{ type: 'image' }] を宣言する
  2. session.prompt() の入力に画像 Blob と指示テキストを配列で渡す

これだけで、モデルは画像を「見て」、その内容についてテキストで返答できるようになります。従来の OCR エンジンより高度で、単に文字を抜き出すだけでなく「この画像は名刺で、これが役職欄で…」といった意味的な理解が可能です。

📖 API リファレンス(Chapter 4 との差分)

マルチモーダル対応の availability 判定

await LanguageModel.availability({
  expectedInputs: [{ type: 'image' }],
});
// → 'available' | 'downloadable' | ...

expectedInputs を渡さない場合はテキスト用途としての可否を返します。画像対応の可否は明示的に問い合わせる必要があります。

LanguageModel.create({ expectedInputs: [...] })

マルチモーダルセッションを作る際は expectedInputs オプションでどの型の入力を受け付けるかを宣言します。

  • expectedInputs: Array<{ type: 'text' | 'image' | 'audio' }>
    • [{ type: 'image' }] — 画像入力のみ
    • [{ type: 'image' }, { type: 'audio' }] — 両方受け付け(環境が対応している場合)
    • 宣言しなかった型を prompt() に渡すとエラーになります。

マルチモーダルな session.prompt(input, options?)

入力は必ずメッセージ配列形式で、content を配列にして imagetext のパーツを並べます。

  • 引数の型:
    input: [{
      role: 'user',
      content: Array<
        | { type: 'text',  value: string }
        | { type: 'image', value: Blob | ImageBitmap | HTMLImageElement | HTMLCanvasElement | ... }
      >
    }]
  • image の value に渡せるもの:
    • Blob / File — file input や fetch から取得したもの(この章で使用)
    • ImageBitmapcreateImageBitmap() の結果
    • HTMLImageElement<img> 要素
    • HTMLCanvasElement, OffscreenCanvas — canvas 由来
    • HTMLVideoElement — video の現フレーム
  • content の順序: 画像を先、テキスト指示を後ろに置くのが推奨(画像を見せてから指示を出す形)。
  • 戻り値: Promise<string>(テキストと同じ)。responseConstraint と組み合わせれば JSON 強制も可能。

典型的な呼び出し:

const output = await session.prompt([{
  role: 'user',
  content: [
    { type: 'image', value: imageBlob },
    { type: 'text',  value: 'この画像から名刺情報を JSON で抽出してください。' },
  ],
}], {
  responseConstraint: BUSINESS_CARD_SCHEMA,
});

const contact = JSON.parse(output);
// { name: '山田太郎', company: 'サンプル株式会社', ... }
💡 画像入力のコツ
  • 解像度が低すぎる画像は読み取れない(推奨は幅 800px 以上)
  • 斜めや歪みが強いと精度が落ちる — 事前にトリミングしてから渡すと良い
  • 複数画像を一度に渡すこともできる(content 配列に image を 2 つ以上並べる)

📋 使う JSON Schema

lib/schema.jsBUSINESS_CARD_SCHEMA を使います(Chapter 4 と一緒に import 済み)。

// BUSINESS_CARD_SCHEMA の中身(抜粋):
// {
//   type: 'object',
//   properties: {
//     name:    { type: 'string' },
//     company: { type: 'string' },
//     title:   { type: 'string' },
//     email:   { type: 'string' },
//     phone:   { type: 'array', items: { type: 'string' } },
//     address: { type: 'string' },
//     website: { type: 'string' },
//   },
//   required: ['name', 'company'],
// }

💻 やってみよう!

  1. 📝 コードを書くstarter/main.jshandlePromptImage() 関数内、// TODO N: コメントの下にコードを追記する
  2. 🔄 リロード — Chrome で開いている starter/index.html のページを再読込(Cmd+R / Ctrl+R)
  3. 🖱️ UI 操作 — スターターアプリ上部の 📷 名刺OCR タブを開き、画像を用意してから「連絡先を抽出する」ボタンを押す。以下 2 通りは必ず両方試して、実画像と理想画像での抽出精度の違いを体感してください:
    • サンプル画像を使う」ボタン → 同梱の名刺(田中 洋一郎 / Yoichiro Tanaka)が読み込まれ、氏名・会社・メール・電話などが期待通りに JSON へ抽出される
    • ファイルを選択」でご自身の名刺(もしくは会場で借りた別の名刺)をアップロード → 実物の名刺で、レイアウトやフォントによって抽出結果がどう変わるか観察
    画像の解像度・傾き・光量で結果が大きく変わることに気付けます
  4. ✅ 動作確認 — 出力エリアに { "name": "...", "company": "...", "email": "...", "phone": [...], ... } という JSON が表示されれば成功!

🌱 STEP 1: マルチモーダルセッションを作る(4 分)

// TODO 1: 画像入力対応のセッション作成
const session = await LanguageModel.create({
  expectedInputs: [{ type: 'image' }],
});
💡 expectedInputs を宣言しないと画像を渡してもエラーになります。必ず作成時に image を宣言してください。

🌿 STEP 2: 画像 + 指示を送る(3 分)

// TODO 2: 画像 Blob と指示テキストを prompt に渡す
const output = await session.prompt([{
  role: 'user',
  content: [
    { type: 'image', value: currentImageBlob },
    { type: 'text', value: 'この名刺から連絡先情報を JSON で抽出してください。' },
  ],
}]);

ポイント: content 配列に image と text を混ぜて渡します。順序は image → text が推奨(画像を先に見せてから指示を出す)。

🌳 STEP 3: Schema で強制 + 整形表示(3 分)

// prompt に responseConstraint を追加
const output = await session.prompt([{
  role: 'user',
  content: [
    { type: 'image', value: currentImageBlob },
    { type: 'text', value: 'この名刺から連絡先情報を JSON で抽出してください。' },
  ],
}], {
  responseConstraint: BUSINESS_CARD_SCHEMA,
});

// TODO 3: JSON.parse して表示
renderOutput('chapter-5', JSON.parse(output));

これで Chapter 4 と同じく、確実に JSON 形式で返ってきます。

🎨 Extend this!

  • レシート OCR に切替(金額・品目を抽出、Schema を差し替えるだけ)
  • 手書きメモの文字起こし
  • 抽出結果をカレンダー / 連絡先アプリと連携(Chrome 拡張機能化)
  • 複数画像を一度に送って比較する
✅ Checkpoint
  1. 同梱のサンプル画像で { name, company, ... } が返る
  2. 自分の名刺(もしくは別の名刺画像)でも試して結果が変わる
  3. JSON.parse エラーが出ない(常に有効な JSON)

🎨 Chapter 6: 拡張タイム&Q&A

所要時間: 5 分 / 目的: 自分のアイデアを乗せてみる

🌱 章ごとの拡張アイデア

  • 🔤 Language Detector: リアルタイム判定、絵文字フラッグ表示、Detector 結果を Translator に自動投入
  • 🌐 Translator: 逆翻訳品質チェック、音声合成、複数言語同時出力、translateStreaming() で逐次出力
  • 📝 Summarizer: URL 記事の要約、複数記事の比較要約、多言語要約
  • 💬 Prompt API テキスト: マルチターン会話、タグクラウド、複数レビューまとめ
  • 📷 Prompt API 画像: レシート版、手書きメモ版、画像 + テキスト混在資料の抽出

🚀 API 連携アイデア

順序依存にはしていませんが、章同士を連携すると強力なアプリになります:

  1. 多言語ブラウザ内リーダー: Detector → Translator → Summarizer → Prompt でフォローアップ Q&A
  2. 多言語カスタマーサポート下書き: Detector → Translator → Summarizer → Prompt で返信ドラフト → Translator で戻す
  3. スマホカメラで名刺 → 挨拶メール自動生成: Ch5 の抽出結果を Prompt に渡してテンプレメール生成 → Translator で英訳
  4. ブログ多言語化パイプライン: 日本語で書く → Summarizer で TL;DR 生成 → Translator で各言語に翻訳 → Prompt で SNS 投稿文を各言語で生成

🎁 持ち帰り課題

会場では時間が限られるので、以下は自宅で挑戦してみてください:

  • Chrome Extension 化(Content Script でページ内テキストを扱う)
  • Chapter 4 と 5 を組み合わせた「画像 + テキスト混在資料」の解析
  • 自分の業務で使えるツールとしてリファクタ
  • PWA 化してモバイルでも使えるように

💡 発表しよう!

拡張したものを SNS でシェアするときは、以下のハッシュタグをぜひ〜:

  • #ChromeBuiltInAI
  • #IOExtendedTokyo
  • #GeminiNano

📚 さらに学ぶ

🎤 質問タイム

疑問点は会場スタッフか、Slack / Discord チャンネル(会場でご案内)まで。

🎊 お疲れさまでした!
Chrome Built-in AI の 4 API を全部触った経験、めちゃくちゃ貴重です。 ここから先はあなたのアイデア次第。世界を驚かせる Web AI アプリを作ってください 🚀