この記事は CyberAgent Developers Advent Calendar 2024 23日目の記事です。
こんにちは!サイバーエージェント Developer Experts の原 (@herablog) です。本記事では、今年初めて開催された Web AI summit と、そこで発表した Ameba でのクライアントサイド AI 活用事例についてご紹介します。
Ameba の発表は YouTube で公開されていますので、ぜひご覧ください。
目次
- Web AI summit とは
- Amebaブログでクライアントサイド AI 活用のプロトタイプ作成
- なぜクライアントサイド AI に取り組んだのか
- クライアントサイド AI 活用の工夫ポイント
- クライアントサイド AI ( Prompt API ) を使った実装概要
- クライアントサイド AI の課題と可能性
- まとめ
NOTE: 本記事の内容は 2024 年 12月 時点の情報に基づいています。記載されている内容は古くなっている可能性がありますので、最新の情報は公式サイト等をご確認ください。
Web AI summit とは
Web AI summit は、初めて公開で開催された Web AI に関連するサミットで 2024年10月 にアメリカのサニーベールで開催されました。そこでは Google、Hugging Face、Intel、Microsoft、MLC、LangChain などのトップランナーたちが、Web AI の最新の進展について発表しました。
主催者の Jason Mayes によると、サミットには 22 カ国の 1,000 人を超える参加者が集い、インターナショナルなものとなりました。公開されている写真や動画でも感じられますが、会場の熱気は素晴らしく、参加者はこの新しい分野の学びの機会にとても興奮しているようでした。サミットを通じて、各チームがクライアントサイドの機械学習を活用して次世代のウェブアプリをどのように作成しているかを学ぶ絶好の機会となりました。
Amebaブログでクライアントサイド AI 活用のプロトタイプ作成
Amebaブログではクライアントサイド AI として Gemini Nano in Chrome を利用するプロトタイプを作成しました。
作成したプロトタイプはブログ記事のタイトルを生成するものです。ブログ記事の下書きページで本文を書いた後に「タイトル生成」ボタンを押すとタイトルの候補を生成します。さらにもう一度作り直したり、「より丁寧に」「よりカジュアルに」「似たタイトルを生成」といった操作ができるのも特徴です。
このプロトタイプはクライアントサイド AI を活用した例として、Google I/O 2024 でも紹介されました。
なぜクライアントサイド AI に取り組んだのか
Amebaブログでクライアントサイド AI である Gemini Nano in Chrome を使ったプロトタイプを作成した理由は、アプリケーション開発者とサービス利用者それぞれにメリットがあると考え、それらを検証したかったからです。
クライアントサイド AI の利点として、大きくセキュリティ、コスト、応答速度が挙げられます。クライアントサイドの AI では、デバイスに事前にインストールされたモデルを実行するため、ネットワークを介してデータを送受信する必要がありません。これは、ブログの下書きのような公開を前提としていないユースケースで特に役立ちます。また、応答速度が安定しており、コストもかからないため、何度でも実行できます。
開発者にとってはセットアップが簡単であることが利点です。事前にブラウザにインストールされた大規模言語モデル (以下 LLM ) の API が用意されているため、複雑なセットアップや LLM の管理が不要で、他のブラウザ API と同様に JavaScript を通じてすぐに使い始めることができます。
Amebaブログの利用者にとっても同様に利点があります。下書きには機密情報や意図しない表現が含まれている可能性があり、サーバーに送信されないことはセキュリティを高め、プライバシー保護に役立ちます。さらに AI 機能の利用に追加の費用がかからないこと、ネットワーク環境に依存せず安定して結果を得られること、そして何度でも利用できることが大きな利点です。
NOTE: クライアントサイド AI については「State of client side machine learning」、Gemini Nano in Chrome に関しては「Overview of Chrome built-in AI」セッションで紹介されています。
クライアントサイド AI 活用の工夫ポイント
こうしたクライアントサイド AI の特性を踏まえ、ブログ作成機能のサポートとして AI を活用するとブロガーさんにとって便利になり、記事の質を向上させることができるのではないかと考えました。例えば、ブログ記事のタイトル作成はブロガーさんにとって難しい場面のひとつです。より多くの人に読んでもらうためには、タイトルはキャッチーでありながら、記事の内容を適切に表現している必要があります。しかし、そのようなタイトルを考えるのは難易度が高く、時には多くの時間を費やしてしまいます。
ここで、LLM の力を活用できます。LLM は一般的に、与えられた内容を要約することが得意です。この特性を活かせば、タイトル作成に役立つのではないかと考えました。ただし、利用者の期待に応える完璧なタイトルを1回で導き出すことは難しいと考え、いくつかの候補を生成しそれらをカスタマイズできるような機能にしました。
さらに、既存のブログ作成機能に統合する際には、ユーザーインターフェース (以下 UI ) の工夫も重要でした。既存の多くの UI で見慣れている「ボタン」を中心に設計し、タスクをクリックで行えるようにしました。こうすることで、何度も気軽に実行できるクライアントサイド AI の特性をフルに活用できます。
タスクベースのボタン UI を採用することにより、利用者はプロンプトを書く必要がなくなりました。その結果、AI に慣れていない人でも簡単に使えるようになりました。もちろん、これでは細かいチューニングはできないため使い所には注意が必要ですが、完璧を求めずに何度も実行するタイトル生成機能にはマッチしています。
このようなクライアントサイド AI の特性を活かしたアプローチは、今後さらに多くの場面で応用できる可能性があると思います。
クライアントサイド AI ( Prompt API ) を使った実装概要
Amebaブログのタイトル作成機能では、Prompt API を利用しています。Prompt API は最も一般的な用途のAPI であり、プロンプトを通じて様々なタスクを実行できます。Prompt APIを使って機能を実装する際には、いくつか注意しなければならない点があります。
まず一つ目は、レスポンスが必ずしも指示通りに返ってこない可能性があることです。他の LLM と同様に、同じ指示に対して一致するテキストが返ってくるとは限りません。実装時には、指示通りのレスポンスが得られないことを想定して処理を設計する必要があります。例えば、レスポンスが Markdown 形式で返ってきたり、返ってこなかったり、さらには無効な形式の JSON が返ってきたりすることがありました。今回のプロトタイプではこれらに対して、いくつかワークアラウンドな処理を実装しました。
二つ目の注意点は、トークン数の制限があることです。Prompt API では maxTokens
、tokensSoFar
、countPromptTokens()
といったプロパティ、メソッドが提供されているため、これらをうまく活用することが重要です。また、セッションをうまく活用することも有用です。セッションを利用することで、過去のやり取りを学習した状態でタスクを行うことができ、トークンの節約にもつながります。今回のプロトタイプでは、タイトルを調整する際にはセッションを再利用し、少ないトークン消費でタスクを行えるようにしました。
三つ目は、使用しているモデルが比較的小さいことです。大きなモデルに比べると語彙が少ないように感じられました。現状の Prompt API で機能を実装するにはトークン数の制限がある中でも、プロンプト内でできる限り情報を提供することが重要そうです。そうすると、そのような条件下でも要約的なタスクを行う上では十分な性能を発揮します。
以下は簡易的ですが、Prompt API を使ってブログ記事タイトル生成機能を実装するサンプルコードです。
NOTE: このコードは説明のために簡略化されたサンプルです。実プロダクトで利用する際は書き換える必要があります。
(async () => { if (!window.ai || !window.ai.languageModel) { // 非対応表示をする return; } // アプリケーション内の TopK, temperature デフォルト値を定義 const DEFAULT_TOP_K = 3; const DEFAULT_TEMPERATURE = 1; let session = null; async function createAISession({ systemPrompt, topK, temperature } = {}) { const { available, defaultTopK, maxTopK, defaultTemperature } = await ai.languageModel.capabilities(); // readily, after-download or no if (available === "no") { return Promise.reject(new Error('AI not available')); } const params = { monitor(monitor) { monitor.addEventListener('downloadprogress', event => { console.log(`Downloaded: ${event.loaded} of ${event.total} bytes.`); }); }, systemPrompt: systemPrompt || '', topK: topK || defaultTopK, temperature: temperature || defaultTemperature, }; const session = await ai.languageModel.create(params); return session; } async function updateSession({ systemPrompt, topK, temperature } = { topK: DEFAULT_TOP_K, temperature: DEFAULT_TEMPERATURE, }) { if (session) { session.destroy(); session = null; } session = await createAISession({ systemPrompt, topK, temperature, }); } // トークン数が超過する場合は短くする (エラー返すパターンもあり) async function truncatePrompt(prompt) { const tokenLength = await session.countPromptTokens(prompt); const tokensLeft = session.maxTokens - session.tokensSoFar; if (numTokens > tokensLeft) { // Truncate prompt string ... return truncatedPrompt; } return prompt; } // 不正な配列形式の JSON が返却されることがあるので修正する処理 // プロトタイプ作成時には "] で終了していないものが多かった function fixJSON(jsonString) { if (!jsonString.trim().endsWith('"]')) { return jsonString + '"]'; } // 他の処理があれば追加 … return jsonString; } async function generateTitle() { // 初回作成時にはセッションを初期化する await updateSession({ systemPrompt: 'ブログ記事の内容に合う128文字以内のタイトルを3つ作成し、JSONの配列形式で返答してください。', }); const prompt = `以下のブログ記事からタイトルを作成してください。${textareaEl.textContent}`; const truncatedPrompt = truncatePrompt(prompt); const result = await session.prompt(truncatedPrompt); // Markdown 形式で返却されるのでパース、テキストだけ取得する const el = document.createElement('div'); el.innerHTML = DOMPurify.sanitize(marked.parse(result)); try { const fixedJson = fixJSON(el.textContent); const json = JSON.parse(fixedJson); // display result } catch (error) { // display error } } async function generateMoreFormalTitle() { // 再生成時はセッションを再利用するため updateSession を実行しない const prompt = 'よりフォーマルなタイトルを生成してください。'; const result = await session.prompt(prompt); ... } ... })();
順次、用途別の API も公開されているので、今後はより適した API を利用した方が良いかもしれません。最新情報は「Buil-in AI」に記載されています。
NOTE: Chrome のビルトイン API を利用するうえでの工夫ポイントは 「Lessons learned from being customer zero of Chrome’s built-in APIs」で触れられています。
クライアントサイド AI の課題と可能性
現時点でのクライアントサイド AI (Gemini Nano in Chrome) を実プロダクトで試してみて感じた課題は、まず全てのブラウザで利用できない点が挙げられます。特にモバイルブラウザでは利用者が多いにもかかわらず、対応が難しいという問題があります。また、小さいモデルを使用するため、機能は限定的です。より正確なレスポンスを期待するにはプロンプトで情報を渡す必要があるものの、トークン数の制限があるため限界があります。
一方でクライアントサイド AI の利点としては、特定のタスクにおいては十分な性能を発揮する点が挙げられます。さらに、何度も試行錯誤できるため、使い所によっては非常に有用です。サーバーサイド、クライアントサイドどちらで AI を実行するにしても、完璧なレスポンスは難しく妥協点を見つける必要があります。用途によっては小さいモデルでも十分である可能性があり、この点についても考慮すべきです。
NOTE: MLC のセッション「WebLLM: A high-performance in-browser LLM Inference engine」では、小さく、特化されたモデルの活用についても触れられていました。
まとめ
生成 AI、特にクライアントサイド AI の活用はまだとても早い段階で、全ての人が使いたい時に使えるまでには時間が必要だと考えられます。このような初期段階では新たな取り組みに挑戦し、その結果得られたナレッジを共有し合う開発コミュニティの力が重要だと思います。
また現在多くの生成 AI 活用の中で、サーバーサイドやプロンプト利用が注目されています。さらにクライアントサイド AI との併用で、それぞれの特性を活かしながら AI を活用し、プロダクトを改善できる可能性があると感じています。
サイバーエージェントでは引き続き AI 活用に注力していきます。
NOTE: Web AI summit 2024 の全ての動画はプレイリストにまとめられています。どのセッションも情報が充実していますのでお時間がある時にぜひご覧ください。