Prompt API 说明
这是 Chrome 内置 AI 团队的一份早期设计草案,旨在描述以下问题并征求对提议解决方案的反馈。尚未批准在 Chrome 中发布。
浏览器和操作系统越来越被期望能够访问语言模型。(示例, 示例, 示例。) 语言模型以其多功能性而闻名。通过足够创造性的提示,它们可以帮助完成各种各样的任务,例如:
- 对任意文本进行分类、标记和关键词提取;
- 帮助用户撰写文本,如博客文章、评论或传记;
- 总结文章、用户评论或聊天记录;
- 根据文章内容生成标题或头条;
- 根据网页的非结构化内容回答问题;
- 语言之间的翻译;
- 校对
虽然 Chrome 内置 AI 团队正在为其中一些用例探索专用 API(例如翻译,未来可能还会有摘要和撰写),我们也在探索一个通用的"prompt API",允许 Web 开发者直接向语言模型发送提示。这让 Web 开发者可以访问更多功能,但代价是需要他们自己进行提示工程。
目前,希望使用语言模型的 Web 开发者必须调用云 API,或者自带模型并使用 WebAssembly 和 WebGPU 等技术运行。通过提供对浏览器或操作系统现有语言模型的访问,我们可以与云 API 相比提供以下优势:
- 本地处理敏感数据,例如允许网站将 AI 功能与端到端加密结合使用。
- 潜在更快的结果,因为不涉及服务器往返。
- 离线使用。
- 降低 Web 开发者的 API 成本。
- 允许混合方法,例如网站的免费用户使用设备上的 AI,而付费用户使用更强大的基于 API 的模型。
同样,与自带 AI 的方法相比,使用内置语言模型可以节省用户的带宽,可能受益于更多优化,并降低 Web 开发者的使用门槛。
比许多其他需要开启标志的 API 更甚,prompt API 是一个实验,旨在帮助我们了解 Web 开发者的用例,以制定专用 API 的路线图。 然而,我们希望发布一个说明文档,为实验进行期间提供文档和公开讨论的场所。
目标
我们的目标是:
- 为 Web 开发者提供一个统一的 JavaScript API,用于访问浏览器提供的语言模型。
- 尽可能抽象特定语言模型的细节,如分词、系统消息或控制令牌。
- 引导 Web 开发者优雅地处理失败情况,例如没有可用的浏览器提供的模型。
- 允许各种实现策略,包括设备上或基于云的模型,同时对开发者隐藏这些细节。
以下是明确的非目标:
- 我们不打算强制每个浏览器都提供或公开语言模型;特别是,并非所有设备都能够存储或运行语言模型。通过始终表示没有可用的语言模型来实现这个 API 是符合规范的,或者完全通过使用云服务而不是设备上的模型来实现这个 API。
- 我们不打算保证语言模型的质量、稳定性或浏览器之间的互操作性。特别是,我们不能保证这些 API 公开的模型在任何特定用例中都特别好。这些被视为实现质量问题,类似于形状检测 API。(另请参阅 W3C "AI 与 Web"文档中关于互操作性的讨论。)
以下是我们尚不确定的潜在目标:
- 允许 Web 开发者知道或控制语言模型交互是在设备上完成还是使用云服务。这将使他们能够保证他们输入到此 API 的任何用户数据不会离开设备,这对隐私目的很重要。同样,我们可能希望允许开发者请求仅限设备上的语言模型,以防浏览器提供两种类型。
- 允许 Web 开发者知道正在使用的语言模型的某个标识符,与浏览器版本分开。这将允许他们将特定模型列入白名单或黑名单以维持所需的质量水平,或将某些用例限制到特定模型。
这两个潜在目标都可能给互操作性带来挑战,因此我们想进一步调查这些功能对开发者有多重要,以找到合适的权衡。
示例
零样本提示
在这个例子中,使用单个字符串向 API 发送提示,假设这来自用户。返回的响应来自助手。
const session = await ai.assistant.create();
// 向模型发送提示并等待整个结果返回。
const result = await session.prompt("给我写一首诗。");
console.log(result);
// 向模型发送提示并流式传输结果:
const stream = await session.promptStreaming("给我写一首特别长的诗。");
for await (const chunk of stream) {
console.log(chunk);
}
系统提示
助手可以配置一个特殊的"系统提示",为未来的交互提供上下文:
const session = await ai.assistant.create({
systemPrompt: "假装你是一只能言善辩的仓鼠。"
});
console.log(await session.prompt("你最喜欢的食物是什么?"));
系统提示是特殊的,助手不会对其做出响应,即使由于对 prompt()
的过多调用导致上下文窗口溢出,它也会被保留。
如果系统提示太大(见下文),则 promise 将被拒绝,并抛出一个 "QuotaExceededError"
DOMException
。
N 样本提示
如果开发者想提供用户/助手交互的示例,他们可以使用 initialPrompts
数组。这与常见的"聊天完成 API"格式 { role, content }
对齐,包括一个 "system"
角色,可以用来代替上面显示的 systemPrompt
选项。
const session = await ai.assistant.create({
initialPrompts: [
{ role: "system", content: "预测最多 5 个表情符号作为对评论的回应。输出表情符号,用逗号分隔。" },
{ role: "user", content: "这太棒了!" },
{ role: "assistant", content: "❤️, ➕" },
{ role: "user", content: "看起来不错" },
{ role: "assistant", content: "👍, 🚢" }
]
});
// 为了效率,克隆现有会话,而不是每次都重新创建。
async function predictEmoji(comment) {
const freshSession = await session.clone();
return await freshSession.prompt(comment);
}
const result1 = await predictEmoji("重新开始");
const result2 = await predictEmoji("这段代码写得太好了,你应该得到晋升");
一些错误情况的详细信息:
- 同时使用
systemPrompt
和initialPrompts
中的{ role: "system" }
提示,或使用多个{ role: "system" }
提示,或将{ role: "system" }
提示放在initialPrompts
中的第 0 位以外的位置,将导致拒绝并抛出TypeError
。 - 如果所有初始提示的组合令牌长度(包括单独提供的
systemPrompt
,如果有的话)太大,则 promise 将被拒绝,并抛出一个"QuotaExceededError"
DOMException
。
每个会话选项的配置
除了上面显示的 systemPrompt
和 initialPrompts
选项外,目前可配置的选项还有 temperature 和 top-K。关于这些参数的值的更多信息可以在 下面 解释的 capabilities()
API 中找到。
const customSession = await ai.assistant.create({
temperature: 0.8,
topK: 10
});
const capabilities = await ai.assistant.capabilities();
const slightlyHighTemperatureSession = await ai.assistant.create({
temperature: Math.max(capabilities.defaultTemperature * 1.2, 1.0),
});
// capabilities 还包含 defaultTopK 和 maxTopK。
会话持久性和克隆
每个助手会话由与模型的一系列持久交互组成:
const session = await ai.assistant.create({
systemPrompt: "你是一个友好、乐于助人的助手,专门负责服装选择。"
});
const result = await session.prompt(`
我今天应该穿什么?天气晴朗,我在T恤和polo衫之间犹豫不决。
`);
console.log(result);
const result2 = await session.prompt(`
听起来不错,但是糟糕,实际上要下雨了!有新的建议吗??
`);
可以通过创建会话然后克隆它来设置同一提示的多个不相关的延续:
const session = await ai.assistant.create({
systemPrompt: "你是一个友好、乐于助人的助手,专门负责服装选择。"
});
const session2 = await session.clone();
会话销毁
助手会话可以通过以下方式销毁,要么使用传递给create()
方法调用的AbortSignal
:
const controller = new AbortController();
stopButton.onclick = () => controller.abort();
const session = await ai.assistant.create({ signal: controller.signal });
或者通过调用会话的destroy()
:
stopButton.onclick = () => session.destroy();
销毁会话将产生以下影响:
-
如果在
create()
返回的 promise 解决之前完成:-
停止为语言模型发送任何正在进行的下载进度信号。(浏览器可能会中止下载,也可能会继续。无论如何,不会再触发
downloadprogress
事件。) -
拒绝
create()
promise。
-
-
否则:
-
拒绝任何正在进行的
prompt()
调用。 -
使
promptStreaming()
返回的任何ReadableStream
出错。
-
-
最重要的是,销毁会话允许用户代理从内存中卸载语言模型,如果没有其他 API 或会话正在使用它。
在所有情况下,用于拒绝 promise 或使ReadableStream
出错的异常将是"AbortError"
DOMException
,或给定的中止原因。
手动销毁会话的能力允许应用程序在不等待垃圾收集的情况下释放内存,这很有用,因为语言模型可能相当大。
中止特定提示
可以通过传递AbortSignal
来中止对prompt()
或promptStreaming()
的特定调用:
const controller = new AbortController();
stopButton.onclick = () => controller.abort();
const result = await session.prompt("给我写一首诗", { signal: controller.signal });
请注意,由于会话是有状态的,并且提示可能会排队,中止特定提示略微复杂:
- 如果提示仍在会话中的其他提示后面排队,则它将从队列中移除。
- 如果模型当前正在处理提示,则它将被中止,并且提示/响应对将从对话历史中删除。
- 如果模型已经完全处理了提示,那么尝试中止提示将不会产生任何效果。
标记化、上下文窗口长度限制和溢出
给定的助手会话将有一个最大可处理的标记数。开发人员可以通过使用会话对象上的以下属性来检查他们当前的使用情况和接近该限制的进度:
console.log(`${session.tokensSoFar}/${session.maxTokens} (${session.tokensLeft} 剩余)`);
要知道一个字符串会消耗多少标记,而不实际处理它,开发人员可以使用countPromptTokens()
方法:
const numTokens = await session.countPromptTokens(promptString);
关于这个 API 的一些注意事项:
- 我们不向开发人员公开实际的标记化,因为这会使依赖模型特定细节变得太容易。
- 实现必须在其计数中包括处理提示所需的任何控制标记,例如指示输入开始或结束的标记。
- 计数过程可以通过传递
AbortSignal
来中止,即session.countPromptTokens(promptString, { signal })
。
可能会发送导致上下文窗口溢出的提示。也就是说,考虑在调用session.prompt(promptString)
之前session.countPromptTokens(promptString) > session.tokensLeft
的情况,然后 Web 开发人员还是调用了session.prompt(promptString)
。在这种情况下,将逐一删除与助手的对话的初始部分,每次删除一个提示/响应对,直到有足够的标记可用于处理新提示。例外是系统提示,它永远不会被删除。如果无法从对话历史中删除足够的标记来处理新提示,那么prompt()
或promptStreaming()
调用将失败并返回"QuotaExceededError"
DOMException
,并且不会删除任何内容。
可以通过在会话上监听"contextoverflow"
事件来检测此类溢出:
session.addEventListener("contextoverflow", () => {
console.log("上下文溢出!");
});
能力检测
在我们上面的所有示例中,我们调用ai.assistant.create()
并假设它总是会成功。
然而,有时语言模型需要下载后才能使用 API。在这种情况下,立即调用create()
将开始下载,这可能需要很长时间。能力 API 让您了解模型的下载状态:
const capabilities = await ai.assistant.capabilities();
console.log(capabilities.available);
capabilities.available
属性是一个可以取三个值之一的字符串:
"no"
,表示设备或浏览器完全不支持提示语言模型。"after-download"
,表示设备或浏览器支持提示语言模型,但需要下载后才能使用。"readily"
,表示设备或浏览器支持提示语言模型,并且可以立即使用,无需任何下载步骤。
在"after-download"
的情况下,开发人员可能希望在调用create()
开始下载之前让用户确认,因为这会使用大量带宽,用户可能不愿意在使用网站或功能之前等待大型下载。
请注意,无论available
的返回值如何,create()
也可能失败,如果下载失败或会话创建失败。
能力 API 还包含有关模型的其他信息:
defaultTemperature
、defaultTopK
和maxTopK
属性提供有关模型采样参数的信息。supportsLanguage(languageTag)
,返回"no"
、"after-download"
或"readily"
以指示模型是否支持用给定的人类语言进行对话。
下载进度
在需要作为创建的一部分下载模型的情况下,您可以使用以下代码监控下载进度(例如,为了向用户显示进度条):
const session = await ai.assistant.create({
monitor(m) {
m.addEventListener("downloadprogress", e => {
console.log(`已下载 ${e.loaded} / ${e.total} 字节。`);
});
}
});
如果下载失败,则将停止发出downloadprogress
事件,并且create()
返回的 promise 将被拒绝,并返回"NetworkError"
DOMException
。
这种模式是怎么回事?
这种模式有点复杂。已经考虑了几种替代方案。然而,在 Web 标准社区询问后,似乎这种方式最好,因为它允许使用标准事件处理程序和ProgressEvent
,并且还确保一旦 promise 解决,助手对象就完全可以使用。
它还可以通过向m
对象添加更多事件和属性来很好地扩展未来。
最后,请注意,(从未发布的)FetchObserver
设计中有一种类似的先例。
详细设计
完整的 Web IDL API 表面
// 共享 self.ai API
partial interface WindowOrWorkerGlobalScope {
[Replaceable] readonly attribute AI ai;
};
[Exposed=(Window,Worker)]
interface AI {
readonly attribute AIAssistantFactory assistant;
};
[Exposed=(Window,Worker)]
interface AICreateMonitor : EventTarget {
attribute EventHandler ondownloadprogress;
// 将来可能会添加更多内容,例如
// https://github.com/explainers-by-googlers/prompt-api/issues/4
};
callback AICreateMonitorCallback = undefined (AICreateMonitor monitor);
enum AICapabilityAvailability { "readily", "after-download", "no" };
// 助手
[Exposed=(Window,Worker)]
interface AIAssistantFactory {
Promise<AIAssistant> create(optional AIAssistantCreateOptions options = {});
Promise<AIAssistantCapabilities> capabilities();
};
[Exposed=(Window,Worker)]
interface AIAssistant : EventTarget {
Promise<DOMString> prompt(DOMString input, optional AIAssistantPromptOptions options = {});
ReadableStream promptStreaming(DOMString input, optional AIAssistantPromptOptions options = {});
Promise<unsigned long long> countPromptTokens(DOMString 输入, 可选 AIAssistantPromptOptions 选项 = {});
只读属性 unsigned long long maxTokens;
只读属性 unsigned long long tokensSoFar;
只读属性 unsigned long long tokensLeft;
只读属性 unsigned long topK;
只读属性 float temperature;
属性 EventHandler oncontextoverflow;
Promise<AIAssistant> clone();
undefined destroy();
};
[暴露=(Window,Worker)]
接口 AIAssistantCapabilities {
只读属性 AICapabilityAvailability available;
// 当available === "no"时始终为null
只读属性 unsigned long? defaultTopK;
只读属性 unsigned long? maxTopK;
只读属性 float? defaultTemperature;
AICapabilityAvailability supportsLanguage(DOMString languageTag);
};
字典 AIAssistantCreateOptions {
AbortSignal signal;
AICreateMonitorCallback monitor;
DOMString systemPrompt;
sequence<AIAssistantPrompt> initialPrompts;
[EnforceRange] unsigned long topK;
float temperature;
};
字典 AIAssistantPrompt {
AIAssistantPromptRole role;
DOMString content;
};
字典 AIAssistantPromptOptions {
AbortSignal signal;
};
枚举 AIAssistantPromptRole { "system", "user", "assistant" };
### 指令调优模型与基础模型
我们打算通过这个API来暴露指令调优模型。虽然我们无法强制要求特定水平的质量或遵循指令的能力,但我们认为设定这个基本期望可以帮助确保浏览器提供的内容与网页开发者的预期一致。
为了说明差异以及它如何影响网页开发者的期望:
* 在基础模型中,像"写一首关于树的诗。"这样的提示可能会被完成为"...写一篇关于你想成为的动物的文章。写一篇关于兄弟姐妹之间冲突的文章。"(等等)它直接完成文本序列中最可能的下一个标记。
* 而在指令调优模型中,模型通常会遵循"写一首关于树的诗。"这样的指令,并回应一首关于树的诗。
为确保API可以被多个实现中的网页开发者使用,所有浏览器都应确保他们的模型表现得像指令调优模型。
## 考虑过的和正在考虑的替代方案
### 获得响应需要多少个阶段?
要根据提示实际从模型获得响应,涉及以下可能的阶段:
1. 如有必要,下载模型。
2. 建立会话,包括配置[每个会话的选项](#configuration-of-per-session-options)。
3. 添加初始提示以建立上下文。(这不会生成响应。)
4. 执行提示并接收响应。
我们选择将这3-4个阶段在API中体现为两个方法,`ai.assistant.create()`和`session.prompt()`/`session.promptStreaming()`,并提供一些额外的功能来处理`ai.assistant.create()`可能包含下载步骤的事实。一些API将此简化为单个方法,而一些则将其分为三个(通常不是四个)。
### 无状态还是基于会话
我们的设计在这里使用了[会话](#session-persistence-and-cloning)。一些API中可以看到另一种设计,即要求开发者每次都将整个对话历史提供给模型,并跟踪结果。
这可能会稍微灵活一些;例如,它允许在将模型的回应重新输入上下文窗口之前手动纠正。
然而,我们理解基于会话的模型可以更高效地实现,至少对于具有设备内模型的浏览器来说是这样。(对于基于云的模型来说,实现它可能需要更多工作。)而且,开发者总是可以通过为每次交互使用新的会话来实现无状态模型。
## 隐私考虑
如果通过这个API暴露基于云的语言模型,那么将用户或网站数据暴露给相关云和模型提供商可能存在潜在的隐私问题。这不是特定于此API的问题,因为网站已经可以选择使用诸如`fetch()`之类的API将用户或网站数据暴露给其他源。然而,值得记住的是,正如我们在[目标](#goals)中讨论的那样,也许我们应该让网页开发者更容易知道是否正在使用基于云的模型,或者使用的是哪一个。
如果设备内语言模型与浏览器和操作系统版本分开更新,这个API可能会通过提供额外的识别位来增强网络的指纹识别服务。强制要求较旧的浏览器版本不接收更新或不能从太远的将来下载模型可能是一个可能的补救措施。
最后,我们打算(在规范中)禁止使用任何未通过API直接提供的用户特定信息。例如,不允许基于用户过去在浏览器中输入的信息对语言模型进行微调。
## 利益相关方反馈
* W3C TAG:尚未请求
* 浏览器引擎和浏览器:
* Chromium:正在标志后进行原型设计
* Gecko:尚未请求
* WebKit:尚未请求
* Edge:尚未请求
* Web开发者:积极([示例](https://x.com/mortenjust/status/1805190952358650251),[示例](https://tyingshoelaces.com/blog/chrome-ai-prompt-api),[示例](https://labs.thinktecture.com/local-small-language-models-in-the-browser-a-first-glance-at-chromes-built-in-ai-and-prompt-api-with-gemini-nano/))