发布时间:2026 年 4 月 30 日
借助内置 AI,您的网站或 Web 应用可以执行 AI 赋能的任务,而无需部署、管理或自行托管模型。您可能会发现,从演示版过渡到可用于生产用途的功能并非易事。本文档介绍了技术和用户体验方面的注意事项,可帮助您避免常见陷阱。
大多数指南适用于所有内置 AI API。例外情况也会加以说明。
在合理的时间准备模型
建议:在明确用户使用 AI 功能的意图后,立即初始化会话。例如,当用户进入相关的 AI 工具界面、将鼠标悬停在 AI 工作区上或与该功能周围的界面互动时。预热会话可让模型在用户设置任务时在后台静默加载到内存中,从而消除可避免的冷启动延迟。 尝试提前一步,在开始渲染当前结果时立即启动下一个最可能的 AI 任务,例如,如果该功能专为迭代使用而设计。
不建议:除非必要,否则请勿等待用户点击“生成”才初始化会话。这会导致冷启动延迟,因为模型必须先加载到内存中并准备好执行流水线。
在创建期间设置初始提示
以下指南适用于 Prompt API。
建议:在会话初始化期间提供系统指令,以提高第一个提示的速度。
不建议:从空会话开始,并在首次 prompt() 调用中发送系统指令。这会增加延迟时间,因为模型必须在最后一刻处理这些指令。
// ✅ DO: Create the session as early as possible (warm 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.'
}
]
});
// Once 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...");
销毁未使用的会话
建议:在不再需要会话时,显式调用 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 发送未经处理的原始文本、不必要的元数据、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。
建议:让模型自然地生成回答,然后使用客户端逻辑截断文本,使其适合您的界面。
不建议:使用结构化输出架构强制执行严格的字符数限制(例如 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 输出的预期长度。例如:
- 流式传输长内容:对于摘要或聊天,流式传输默认会创建逐个词元的打字机效果。这会让人感觉自然,并提供即时反馈。
- 非流式传输,适用于简短任务(或长时间异步任务):对于简短输出(例如替代文本),非流式传输可以打造更精致的界面。它还提供了时间,以便在当前 AI 任务渲染时,推测性地准备下一个 AI 任务。此方法也适用于较长的异步或后台任务。如果用户没有被输出卡住,可以继续其流程,则无需在输出生成时立即显示。在界面中发出信号,表明该过程正在进行中。
- 更新的视觉过渡:翻译或重写文本时,请使用动画,例如文字变形。
不建议:在没有视觉提示的情况下更新界面。
契合用户对时间和工作的心理模型
建议:如果回答几乎是即时生成的,请考虑人为延迟一两秒。矛盾的是,当用户认为生成过程与他们认为的任务难度相符时,他们可能会认为结果更值得信赖。使用动画来表示 AI 处理已完成。
不建议:瞬间替换界面,让用户猝不及防。
允许用户快速浏览和撤消 AI 编辑
建议:为界面配备步进器或导航历史记录,让用户能够放心地探索不同的结果,并快速撤消 AI 编辑。这可确保不同版本仍然随时可用。
不建议:覆盖用户之前的草稿或他们可能喜欢的 AI 结果,而不提供返回、还原或比较版本的方法。
赋予用户控制和覆盖权限
建议:让用户成为所有生成内容的最终编辑者。提供直观的覆盖选项,让用户对最终输出拥有完全所有权。这些 API 可能会产生不正确的结果。
不建议:强制将 AI 生成的结果作为唯一选项。
缓存重复性任务的结果
建议:针对重复的输入或查询,实现本地结果缓存(例如,使用 sessionStorage 或 IndexedDB)。通过剪裁空格和转换为小写来规范化输入,以提高缓存命中率。对于大型输入(例如图片),请生成一个哈希值作为缓存键。为缓存设置保守的存留时间 (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;
}