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,现在都没人维护了),现在正式发布的实现可没那么“优雅”。总之,下面是目前的配置方法。

  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 到 60 亿参数、做了 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 团队不同,我恰好是个认为函数调用 / 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}

坑点

它的指令遵循能力不太好,所以必填字段其实并不会被严格遵守:

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 默认是有状态的,如果忘了这点会很坑。所以一个无状态的版本大概长这样:

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);

正是因为这些坑,你大概会想自己手写一个小的封装库,或者参考 https://github.com/kstonekuan/simple-chromium-ai

最后,给不熟悉 JS 的同学一个小技巧:如何在浏览器环境中(也就是不用 npm install 或打包步骤)用 ESM 语法引入这些封装库(可能需要 <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 进行翻译

评论