内置 AI API:正确做法和错误做法

发布时间:2026 年 4 月 30 日

借助内置 AI,您的网站或 Web 应用可以执行 AI 赋能的任务,而无需部署、管理或自行托管模型。您可能会发现,从演示版功能过渡到可用于生产用途的功能是一项艰巨的任务。本文档涵盖了技术和用户体验方面的注意事项,可帮助您避免陷入常见误区。

尽早准备模型

适用范围:所有 API,例如 Summarizer、Translator 和 Writer。

建议:在识别出用户意图后立即初始化会话。由于需要用户激活才能初始化会话,因此您可以使用任何互动,例如点击提供 AI 赋能功能的页面上的任意位置。这样可以在用户与界面互动时准备模型和运行时。在相关情况下,一旦开始渲染结果,就立即启动下一个最有可能的 AI 任务。

不要:等到用户点击“生成”后才初始化会话。这会导致冷启动延迟数秒,因为模型必须先加载到内存中并准备其执行流水线。

在创建期间设置初始提示

适用对象:Prompt API。

建议:在会话初始化期间提供系统指令,以提高第一个提示的速度。

请勿:从空会话开始,并在首次 prompt() 调用中发送系统指令。这会增加延迟时间,因为这会迫使模型在最后一刻处理这些指令。

// ✅ DO: Create the session as early as possible (tip on warming up the model early) and use initialPrompts for system instructions in the create call
const session = await LanguageModel.create({
  initialPrompts: [
    { role: 'system', content: 'You are a helpful assistant specialized in code reviews.' }
  ]
});

// A few moments later, when the user triggers the AI feature
const review = await session.prompt(`Review the following code:\n\n${code}`);

// ❌ DON'T: Send instructions using prompt() after creation
// const slowerSession = await LanguageModel.create();
// await slowerSession.prompt(`You are a helpful assistant specialized in code reviews.\n\nReview the following code:\n\n${code}`); // Higher latency

克隆会话以执行重复性任务

适用对象:Prompt API。

对于 Prompt API,每个会话都会跟踪对话的上下文,并将所有之前的互动纳入考虑范围。由于克隆会继承父会话中的所有内容,包括初始提示和克隆时的所有互动历史记录,因此请合理安排使用方式,以便仅继承所需内容。

正确做法

  • 创建基本会话:为了高效处理不相关的任务,请创建一个仅包含系统指令且不包含任何先前对话上下文的基本会话。
  • 克隆基准会话:对该基准会话使用 clone() 来执行新任务,以节省重新解析繁重的系统指令所带来的开销。这样,您就可以创建并行对话,或将任务重置为基准状态。

错误做法

  • 请勿将同一会话重复用于不相关的任务,并避免克隆任何已包含不必要互动历史记录的会话。这两种模式都可能导致无关的先前上下文干扰当前任务。
  • 请勿使用相同的繁重系统指令重复调用 create()。 请改用克隆模式来优化性能。
// ✅ DO: Create a baseline session and clone it for each new task
const baseSession = await LanguageModel.create({
  initialPrompts: [{
    role: 'system',
    content: 'You are a technical editor...',
  }],
});

// Clone the base session once for the first task
const task1 = await baseSession.clone();
const response1 = await task1.prompt("Review this first draft...");
// ... Repeat the cloning pattern for subsequent independent tasks
// Each task starts fresh from the baseline system instructions

// ❌ DON'T:
// Bad performance pattern: repeated create() calls for identical tasks.
// This forces the model to re-parse instructions every time, increasing latency.
// const sessionA = await LanguageModel.create({ initialPrompts: [...] });
// await sessionA.prompt("Task 1...");
// const sessionB = await LanguageModel.create({ initialPrompts: [...] });
// await sessionB.prompt("Task 2...");
// Bad quality pattern: reusing the same session for unrelated tasks.
// const session = await LanguageModel.create();
// await session.prompt("Analyze this financial report...");
// Unrelated task in the same session:
// await session.prompt("Now write a children's story...");

销毁未使用的会话

适用范围:所有 API。

:在不再需要的会话上显式调用 destroy(),以便在不再使用某项功能时释放内存。如果您使用克隆模式,请保留基本会话,并销毁不再需要的克隆。

请勿:保持多个大型会话处于活跃状态。每个会话都会消耗内存,从而造成不必要的资源使用,并可能成为问题。会话将由垃圾回收器自然清理,但调用 destroy() 可更快地释放内存。

// ✅ DO: Use the clone and destroy it immediately after
const clone = await baseSession.clone();
const response = await clone.prompt("Quick task...");
// Free memory right away: destry the clone, keep the baseSession
clone.destroy();

安全高效地呈现流式回答

适用对象:所有支持流式传输的 API(Prompt、Summarizer、Writer、Rewriter 和 Translator)。

:将所有 LLM 输出视为不受信任的内容。对整个组合输出(而不仅仅是块)进行清理,因为恶意代码可能会在更新中拆分。 在渲染之前,请在支持的情况下使用 Sanitizer API。为避免性能下降,请使用流式 Markdown 解析器,例如 streaming-markdown

请勿:在每次更新块时直接设置 innerHTML。这种方法速度较慢,尤其是在使用语法突出显示等复杂格式时,并且容易受到注入攻击。

import * as smd from "streaming-markdown";
// Set up virtual buffer and Sanitizer API
const sanitizer = new Sanitizer({
  allowElements: ['figure', 'figcaption', 'p', 'br', 'strong', 'em', 'img', 'a'],
  allowAttributes: {
    'loading': ['img'], 'decoding': ['img'], 'src': ['img'], 'href': ['a']
  }
});

// Create an off-screen fragment so the parser doesn't cause flicker
// or trigger XSS in the live DOM during the building process.
const buffer = new DocumentFragment();
const parser = smd.parser_new(buffer);

// Use sanitizer as a gatekeeper / cleaner function so we can combine it with the streaming Markdown parser
function syncSanitized(target, sourceFragment) {
  // .sanitize() returns a fresh, clean DocumentFragment
  const cleanFragment = sanitizer.sanitize(sourceFragment);
  // replaceChildren is the modern high-performance way to swap DOM content
  target.replaceChildren(cleanFragment);
}

// Streaming Logic
// `chunks` keeps track of the raw string (useful for logs/debug)
chunks += chunk;
// Let the parser build the DOM incrementally in the buffer.
// This is high-performance because the buffer is not live
smd.parser_write(parser, chunk);
// Use the Sanitizer API to port the content safely to the container.
syncSanitized(container, buffer);

优化输入以提高速度

适用范围:所有 API。

:仅将绝对必要的信息传递给模型。去除与当前任务无关的所有内容。对于大型数据集,请提供简短的概览和少量相关项目。

请勿:向 API 发送未经处理的原始文本、不必要的元数据、HTML 标记或未经过滤的大型列表。延迟时间会随着输入大小的增加而显著增加,这可能会导致 AI 功能在许多设备上看起来无法正常运行。

// ✅ DO: Send only relevant text
const cleanText = document.querySelector('#article').innerText;
const summary = await Summarizer.summarize(cleanText);

// ❌ DON'T: Send the entire DOM structure
// const dirtyText = document.querySelector('#article').innerHTML;

使用结构化输出获得可预测的结果

适用对象:Prompt API。

正确做法:如果您需要模型以特定格式返回数据,请通过提供 responseConstraint 字段来提供 JSON 架构,从而使用结构化输出。这样可确保输出可预测,并避免您需要进行复杂的后处理或手动解析。

错误做法:仅依赖自然语言指令(例如“仅输出 JSON”)。模型可能包含会破坏解析器的对话填充内容。

// ✅ DO: Use a JSON Schema for predictable results
const schema = {
  type: "object",
  properties: {
    isTopicCats: { type: "boolean" }
  }
};

const result = await session.prompt(`Is this post about cats?\n\n${post}`, {
  responseConstraint: schema,
});
console.log(JSON.parse(result).isTopicCats);

将生成与长度限制解耦

适用对象:Prompt API,因为它是唯一支持结构化输出架构的 API。

建议:让模型自然生成回答,然后使用客户端逻辑截断文本,使其适合您的界面。

错误做法:使用结构化输出架构强制执行严格的字符数限制(例如 maxLength: 125)。如果模型的回答长度超过您设置的限制,模型可能会切换到高密度 token(例如外语或表情符号)来压缩含义,从而导致输出内容毫无意义。

/*  DO: Handle overflow using CSS */
.result {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis; /* Displays '…' */
}
// ❌ DON'T: Force length in the prompt
const result = await session.prompt("Write a bio in exactly 50 characters.");

管理用户的耐心程度

适用范围:所有 API。

:使用动画和界面技术来管理用户的耐心程度。最佳方法取决于您的使用场景和 API 输出的预期长度。例如:

  • 长内容流式传输:对于摘要或聊天,流式传输默认会创建逐令牌打字机效果。这样可以感觉更自然,并提供即时反馈。
  • 非流式传输,适用于短任务(或长时间的异步任务):对于短输出(例如替代文本),非流式传输可以创建更精美的界面。它还可以在当前任务渲染时,提供时间来推测性地准备下一个 AI 任务。这种方法也适用于较长的异步或后台任务。如果用户在输出中未被阻止而无法继续其历程,则无需在输出发生时立即生成输出。指示界面中的进程正在进行。
  • 更新的视觉过渡效果:在翻译或重写文本时,使用动画(例如文字变形)。

不建议:在没有视觉提示的情况下更新界面。

符合用户对时间和工作的认知模式

适用范围:所有 API。

建议:如果响应几乎是即时返回的,请考虑添加一到两秒的人为延迟。矛盾的是,如果用户认为生成过程与他们认为的任务难度相符,他们可能会觉得结果更可信。使用动画来指示已发生 AI 进程。

请勿:突然替换界面,让用户感到意外。

允许用户快速浏览和撤消 AI 编辑

适用范围:所有 API。

建议:为界面配备步进器或导航历史记录,让用户能够放心地探索不同的结果,并快速撤消 AI 编辑。这样可确保不同版本仍可随时使用。

请勿:覆盖用户之前的草稿或他们可能喜欢的 AI 结果,并且不提供返回、恢复或比较版本的方式。

显示导航历史记录的步进器界面元素。
界面模式:拒绝 / 接受建议 (Google 文档)
Google Antigravity 界面中的“撤消所有代理编辑”按钮。
界面模式:撤消所有代理编辑内容 (Google Antigravity)
Google 文档中的“拒绝”或“接受”建议按钮。
界面模式:步进器(替代文本演示)

增强用户控制和替换功能

适用范围:所有 API。

:始终让用户有最终决定权。提供手动替换建议的方式。这些 API 可能会产生不正确的结果。

请勿:强制将 AI 生成的结果作为唯一选项。

缓存重复任务的结果

适用范围:所有 API。

建议:针对重复的输入或查询实现本地结果缓存(例如,使用 sessionStorageIndexedDB)。通过去除空格和转换为小写来对输入进行归一化,以提高缓存命中率。对于大量输入内容(例如图片),生成一个哈希用作缓存键。为缓存设置保守的存留时间 (TTL)(或在后台更新缓存结果的同时提供缓存结果)。如果结果不尽如人意,允许用户触发新的推理。

请勿:针对重复的搜索查询或相同的数据输入重新运行相同的推理,例如,当用户在搜索结果之间来回导航时。虽然从云费用方面来看,设备端推理是免费的,但从用户时间和电池续航时间方面来看,它非常昂贵。

// ✅ DO: Check a local cache before running inference
async function getAiResponse(userInput, forceRefresh = false) {
  // Normalize the query to increase cache hits
  const query = userInput.trim().toLowerCase();
  const cacheKey = `ai_results_${query}`;
  const TTL_MS = 3600000; // 1 hour conservative TTL

  if (!forceRefresh) {
    const itemStr = localStorage.getItem(cacheKey);
    if (itemStr) {
      const item = JSON.parse(itemStr);
      const now = Date.now();

      // Check if the item has expired
      if (now < item.expiry) {
        // Lightweight safety check before rendering
        if (isValid(item.value)) return item.value;
      } else {
        // Delete the stale entry if the TTL has passed
        localStorage.removeItem(cacheKey);
      }
    }
  }

  // Fallback: Run inference if no valid cache exists
  const session = await LanguageModel.create();
  const response = await session.prompt(userInput);

  // Store the result for future use (with an expiration)
  const cacheData = {
    value: response,
    expiry: Date.now() + TTL_MS
  };
  localStorage.setItem(cacheKey, JSON.stringify(cacheData));

  return response;
}