Gemini Nano in Chrome 137: notes for AI Engineers

Shawn Wang

Chrome 137 中的 Gemini Nano:給 AI 工程師的筆記

原文由 Shawn Wang 發布,訂閱此部落格

終於,Gemini Nano 幾乎要向所有 Chrome 使用者開放了(我原本被誤導以為是在 Chrome 138——但我自己查證後發現,從 Chrome 137 起,在部分情境下已經開始不需開啟 flag 就釋出了)。我是被這篇 HN 貼文提醒的。我預期到今年年底會全面預設啟用。

我不太喜歡 Google 寫文件的方式,所以這篇部落格文章基本上就是我用符合自己腦袋的方式,把他們的文件重寫一遍。

他們提供了幾個針對常見使用情境的 API,不過說真的,身為工程師,你真正在乎的主要就是Prompt API,這是最靈活、開放性最高的一個。

圖片

設定

跟當初過度承諾的 window.ai(還催生了一堆像 xanderchromeai 這樣的仿製 shim,現在都沒人在維護了)不同,現在實際釋出的實作就沒那麼「乾淨」了。總之,以下是目前的設定方式。

  1. 確認你已經安裝 Chrome 137 以上版本
  2. 前往 chrome://flags/#prompt-api-for-gemini-nano 並把它開啟(很可惜,你得重新啟動 Chrome)
  3. 然後第一次呼叫 LangaugeModel.create() 來下載模型——在家用 Wi-Fi 下大概要花幾分鐘。Gemini 說「下載大小約在 1.5 GB 到 2.4 GB 之間。」所以姑且可以說這是個 4-6B 參數、以 4-8 位元量化後的模型。
const session = await LanguageModel.create({
monitor(m) {
  m.addEventListener("downloadprogress", (e) => {
    console.log(`Downloaded ${e.loaded * 100}%`);
  });
},
// // uncomment if want multimodal input https://developer.chrome.com/docs/ai/prompt-api#multimodal_capabilities 
// expectedInputs: [
//  { type: "audio" },
//  { type: "image" }
//  ]
})

基本重點

載入後的模型有 6k token 的上下文長度(只要在沒有任何 initialPrompts 的情況下去要 inputQuota 就能看到):

session.inputQuota
// 6144

跟 Gemini Nano 團隊不同,我剛好是個覺得 function calling/JSON 輸出非常重要的人,所以來看看在 Gemini Nano 上怎麼把它跑起來,prompt 範例是從 Hamel 和 Jason 那邊偷來的

const JSONschema = `<schema>
{
    "description": "Correctly extracted \`UserDetail\` with all the required parameters with correct types",
    "name": "UserDetail",
    "parameters": {
        "properties": {
            "age": {
                "title": "Age",
                "type": "integer"
            },
            "name": {
                "title": "Name",
                "type": "string"
            }
        },
        "required": [
            "age",
            "name"
        ],
        "type": "object"
    }
}
</schema>`
const JSONsession = await LanguageModel.create({
  initialPrompts: [
    { role: 'system', content: 'You are a helpful LLM that only responds in valid JSON fitting a schema: ' + JSONschema },
    { role: 'user', content: "Extract Jason is 35 years old" },
    { role: 'assistant', content: '{age: 35, name: Jason}'},
  ]
});

const result1 = await JSONsession.prompt("Extract sarah is 22 years old");
console.log(result1);
// {age: 22, name: Sarah}

陷阱

它在指令遵循(instruction following)方面表現不太好,所以 required 欄位其實不太會被遵守:

const result1 = await JSONsession.prompt("its been a year since vibhu's birthday, he was 28 last year, guess how old he is now");
console.log(result1);
// { "age": 29 }

另一點是,session 預設是有狀態(stateful)的,如果你忘了這件事會有點麻煩。所以一個無狀態(stateless)的版本看起來會像這樣:

const baseSession = await LanguageModel.create({
  initialPrompts: // blah blah, as above
})

// you can also implement this as a class if you want to force users to use`new` keyword to make super clear it is stateless
const statelessSession = {  
    async prompt(str) {
        const clonedSession = await session.clone()
        return clonedSession.prompt(str)
    }
}

// these are all stateless calls now! yay repeatability and predictability!
const result1 = await statelessSession.prompt("Extract sarah is 22 years old");
console.log(result1);
const result2 = await statelessSession.prompt("Extract tanisha is 30 years old");
console.log(result2);

就是因為有這些坑,你大概會想自己手刻一些小型的 wrapper 函式庫,或是參考 https://github.com/kstonekuan/simple-chromium-ai

最後給非 JS 專業人士的一個小技巧,是如何在瀏覽器環境中(也就是不用 npm install 或建置步驟)用 ESM 語法來引入這些 wrapper 函式庫(可能會需要 <script type="module">——請在 localhost 或 CSP 比較寬鬆的網站上執行):

// alternatively use https://cdn.jsdelivr.net/npm/[email protected]/dist/simple-chromium-ai.mjs
const ChromiumAI = await import('https://unpkg.com/[email protected]/dist/simple-chromium-ai.mjs');

const ai = await ChromiumAI.initialize("You are a friendly assistant");
const response = await ChromiumAI.prompt(ai, "Tell me a joke");
console.log(response);
const ChromiumAI = await import('https://unpkg.com/[email protected]/dist/simple-chromium-ai.mjs');

const ai = await ChromiumAI.initialize("You are a friendly assistant");
const response = await ChromiumAI.prompt(ai, "Tell me a joke");
console.log(response);
// Why don't scientists trust atoms?  Because they make up everything! 

// and of course... the structured output implementation now works:
const schema = {
  type: "object",
  properties: {
    sentiment: {
      type: "string",
      enum: ["positive", "negative", "neutral"]
    },
    confidence: {
      type: "number",
      minimum: 0,
      maximum: 1
    },
    keywords: {
      type: "array",
      items: { type: "string" },
      maxItems: 5
    }
  },
  required: ["sentiment", "confidence", "keywords"]
};

// Create session with response constraint
const response = await ChromiumAI.prompt(
  ai, 
  "Analyze the sentiment of this text: 'I love this new feature!'",
  undefined, // no timeout
  { responseConstraint: schema }
);

// Response will be valid JSON matching the schema
const result = JSON.parse(response);
console.log(result);

本文章由 muse-spark-1.2-contributor 進行翻譯

留言