做 AI 浏览器别只会接大模型:这套降本方案和代码能省不少钱
AI浏览器 如何降低成本|附源码
当“AI能力”开始进入浏览器,很多产品的第一反应是:接入一个大模型 API,做一个侧边栏助手、网页总结、划词解释、自动写作。
但上线后很快会遇到一个现实问题:AI浏览器的成本并不低。
用户每打开一个网页、每点击一次总结、每进行一次对话,背后都可能产生 Token 消耗、接口调用费用、向量检索费用、服务器带宽费用,甚至还有模型并发带来的基础设施成本。
本文将系统讲解:AI浏览器如何降低成本,并给出一个可运行的简化版源码示例,帮助你理解如何通过缓存、内容裁剪、模型分级、流式输出、请求合并等方式,把 AI 浏览器的调用成本降下来。
一、AI浏览器为什么成本高?
AI浏览器看起来只是“浏览器 + AI助手”,但实际成本来源非常复杂。
常见的 AI 浏览器功能包括:
- 网页内容总结
- 网页问答
- 划词翻译
- 划词解释
- 自动生成回复
- 表单填写辅助
- 多标签页内容分析
- 长文档阅读
- 网页内容结构化提取
- 基于浏览记录的个性化助手
这些功能背后往往都要调用大模型。
大模型计费通常基于:
- 输入 Token 数
- 输出 Token 数
- 模型类型
- 请求次数
- 并发量
- 上下文长度
- 是否使用向量数据库
- 是否启用搜索或工具调用
其中,网页内容总结和网页问答尤其容易造成高成本。
因为网页正文往往很长,一篇文章可能有几千到几万字,如果每次都把完整页面内容发送给大模型,成本会非常高。
例如,一个网页有 15000 个中文字符,粗略换算可能接近 8000~12000 tokens。
如果用户每天总结 100 个页面,那么输入 token 就可能达到百万级别。
如果用户量上升到 1 万,每日调用成本会迅速放大。
所以 AI 浏览器的核心问题不是“能不能接入大模型”,而是:
如何在保证体验的前提下,尽可能减少不必要的大模型调用。
二、AI浏览器降本的核心思路
AI浏览器降本可以从以下几个方向入手:
| 方向 | 核心目标 |
|---|---|
| 内容裁剪 | 少传无用内容 |
| 缓存复用 | 相同内容不重复调用 |
| 模型分级 | 简单任务用便宜模型 |
| 请求合并 | 减少频繁调用 |
| 本地预处理 | 浏览器端先做清洗 |
| 向量检索 | 只把相关片段发给模型 |
| 流式输出 | 降低等待感,减少重复请求 |
| 用户行为控制 | 避免自动无限触发 |
| Prompt 优化 | 减少输入输出 token |
| 结果复用 | 摘要、翻译、问答可共享上下文 |
下面分别展开。
三、策略一:网页正文提取,避免传整页 HTML
很多初级实现会直接把网页的 document.body.innerText 发送给模型。
这样虽然简单,但会把大量无关内容也传进去,例如:
- 顶部导航
- 底部版权
- 推荐文章
- 评论区
- 广告文本
- 侧边栏
- 菜单项
- 重复按钮文字
这会显著增加 token 成本。
更合理的方式是:
- 在浏览器端提取正文
- 删除脚本、样式、导航、页脚
- 保留标题、正文、发布时间、作者等关键信息
- 对内容做长度限制
- 对重复段落去重
例如:
function extractMainText() {
const clone = document.body.cloneNode(true);
const removeSelectors = [
'script',
'style',
'nav',
'footer',
'header',
'aside',
'iframe',
'noscript',
'.advertisement',
'.ads',
'.comment',
'.comments',
'.recommend',
'.related'
];
removeSelectors.forEach(selector => {
clone.querySelectorAll(selector).forEach(el => el.remove());
});
let text = clone.innerText || '';
text = text
.replace(/\n{3,}/g, '\n\n')
.replace(/[ \t]{2,}/g, ' ')
.trim();
return text;
}
这一步通常可以减少 30%~70% 的输入内容。
如果再结合正文识别算法,效果更明显。
四、策略二:内容指纹 + 缓存,避免重复总结
很多网页的内容是固定的。
如果多个用户访问同一个页面,或者同一个用户多次点击“总结”,其实没有必要反复调用大模型。
可以为网页内容生成一个 hash,作为缓存 key。
缓存维度可以包括:
- 页面 URL
- 页面标题
- 正文 hash
- 用户语言
- 任务类型
- Prompt 版本
- 模型版本
例如:
summary:v1:gpt-4o-mini:zh:contentHash
这样只要正文没有变化,就可以复用之前生成的摘要。
缓存可以放在:
- 浏览器本地
localStorage - IndexedDB
- 后端 Redis
- 数据库
- CDN KV 存储
建议采用多级缓存:
浏览器本地缓存 → 后端 Redis 缓存 → 数据库缓存 → 大模型调用
命中缓存后,成本几乎为零。
五、策略三:模型分级,不要所有任务都用最贵模型
AI浏览器里的任务难度并不一样。
例如:
| 功能 | 推荐模型 |
|---|---|
| 划词翻译 | 小模型 |
| 词语解释 | 小模型 |
| 网页摘要 | 中等模型 |
| 复杂问答 | 中高模型 |
| 多页面分析 | 高级模型 |
| 代码理解 | 中高模型 |
| 法律/医疗类严肃推理 | 更强模型 + 风控 |
很多场景没有必要调用最强模型。
比如“把这段英文翻译成中文”,使用便宜模型即可。
“总结这篇新闻文章”,也可以先用轻量模型。
只有在用户继续追问,或者涉及复杂推理时,才升级到更强模型。
这叫做:
模型路由,Model Routing。
示例逻辑:
function selectModel(taskType, textLength) {
if (taskType === 'translate') {
return 'cheap-model';
}
if (taskType === 'explain') {
return 'cheap-model';
}
if (taskType === 'summary' && textLength < 6000) {
return 'cheap-model';
}
if (taskType === 'summary' && textLength >= 6000) {
return 'middle-model';
}
if (taskType === 'deep_qa') {
return 'strong-model';
}
return 'cheap-model';
}
模型分级是降低成本最直接的方式之一。
六、策略四:长网页先分块,再压缩
如果一个网页特别长,不建议直接把全文扔给模型。
更合理的方式是:
- 将网页切分成多个片段
- 对每个片段做局部摘要
- 再对局部摘要做总摘要
这叫做 Map-Reduce Summarization。
示例:
原始长文
↓
分块 chunk1, chunk2, chunk3...
↓
分别摘要 summary1, summary2, summary3...
↓
合并摘要 final summary
虽然看起来调用次数变多,但每次上下文更短,可控性更强。
如果使用便宜模型做第一阶段摘要,再用中等模型合并,整体成本往往比一次性调用长上下文模型更低。
此外,对于问答场景,更推荐使用检索式流程:
- 用户提出问题
- 将网页切块
- 计算问题和片段的相似度
- 只取最相关的 3~5 个片段
- 发给模型回答
这样不需要每次都发送完整网页。
七、策略五:Prompt 要短,不要写成小作文
很多 AI 应用的 Prompt 非常长:
你是一个非常专业、非常严谨、非常有耐心、非常擅长分析网页内容的人工智能助手……
这类 Prompt 如果每次请求都发送,会带来额外 token 成本。
AI浏览器的 Prompt 应该尽量短,但约束清晰。
例如网页总结:
请用中文总结以下网页内容,输出:
1. 一句话总结
2. 三个要点
3. 适合谁阅读
相比冗长 Prompt,这已经足够。
输出格式也要限制,否则模型可能生成过长内容。
例如:
每个要点不超过 40 字,总字数不超过 300 字。
控制输出长度同样重要,因为输出 token 也收费。
八、策略六:前端不要自动疯狂调用
AI浏览器常见的成本失控原因是:
页面打开后自动总结、自动分析、自动生成推荐问题。
这对用户体验不一定有帮助,却会造成大量无效调用。
更合理的交互方式是:
- 默认不自动调用
- 用户点击按钮后再总结
- 对同一页面设置调用冷却时间
- 页面内容变化后延迟触发
- 滚动阅读到一定深度后再推荐总结
- 免费用户限制每日调用次数
- 对低价值页面不启用 AI
例如:
let lastCallTime = 0;
function canCallAI() {
const now = Date.now();
const cooldown = 10 * 1000;
if (now - lastCallTime < cooldown) {
return false;
}
lastCallTime = now;
return true;
}
产品层面的控制,往往比技术优化更重要。
九、简化版 AI浏览器插件架构
下面给出一个简化架构:
Chrome Extension
├── manifest.json
├── content.js // 提取网页内容
├── popup.html // 插件界面
├── popup.js // 用户交互
└── background.js // 请求后端
Backend
├── server.js // Express API
├── cache.js // 简单内存缓存
└── ai.js // 调用大模型
实际生产环境中,后端还应增加:
- 用户鉴权
- 用量统计
- Redis 缓存
- 日志系统
- 限流系统
- 计费系统
- 风控策略
- Prompt 版本管理
十、前端源码:Chrome 插件
下面是一个简化版插件源码,实现:
- 点击按钮提取当前网页正文
- 生成内容 hash
- 请求后端总结
- 显示摘要结果
1. manifest.json
{
"manifest_version": 3,
"name": "Cost Saving AI Browser Demo",
"version": "1.0.0",
"description": "一个演示如何降低AI浏览器成本的浏览器插件",
"permissions": [
"activeTab",
"scripting"
],
"host_permissions": [
"http://localhost:3000/*"
],
"action": {
"default_popup": "popup.html",
"default_title": "AI网页总结"
}
}
2. popup.html
AI网页总结
优先使用缓存,减少重复调用大模型。
3. popup.js
const btn = document.getElementById('summaryBtn');
const result = document.getElementById('result');
btn.addEventListener('click', async () => {
btn.disabled = true;
result.textContent = '正在提取网页内容...';
try {
const [tab] = await chrome.tabs.query({
active: true,
currentWindow: true
});
const [{ result: pageData }] = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: extractPageContent
});
if (!pageData.text || pageData.text.length < 100) {
result.textContent = '当前页面可总结内容太少。';
btn.disabled = false;
return;
}
result.textContent = '正在请求AI总结...';
const response = await fetch('http://localhost:3000/api/summary', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: pageData.url,
title: pageData.title,
text: pageData.text,
lang: 'zh'
})
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || '请求失败');
}
result.textContent = data.cached
? `[缓存命中]\n\n${data.summary}`
: `[AI生成]\n\n${data.summary}`;
} catch (err) {
result.textContent = `出错了:${err.message}`;
} finally {
btn.disabled = false;
}
});
function extractPageContent() {
const clone = document.body.cloneNode(true);
const removeSelectors = [
'script',
'style',
'nav',
'footer',
'header',
'aside',
'iframe',
'noscript',
'svg',
'canvas',
'.advertisement',
'.ads',
'.comment',
'.comments',
'.recommend',
'.related',
'[role="navigation"]',
'[role="banner"]',
'[role="contentinfo"]'
];
removeSelectors.forEach(selector => {
clone.querySelectorAll(selector).forEach(el => el.remove());
});
let text = clone.innerText || '';
text = text
.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0)
.filter((line, index, arr) => arr.indexOf(line) === index)
.join('\n');
// 控制最大长度,避免把超长网页完整发送到后端
const maxChars = 12000;
if (text.length > maxChars) {
text = text.slice(0, maxChars);
}
return {
url: location.href,
title: document.title,
text
};
}
十一、后端源码:Node.js + Express
下面是一个简化版后端,实现:
- 内容 hash
- 内存缓存
- 文本裁剪
- 模型选择
- Prompt 控制
- 调用大模型接口
为了便于演示,这里使用 OpenAI 风格接口。你可以替换成其他模型服务商。
1. 初始化项目
mkdir ai-browser-cost-demo
cd ai-browser-cost-demo
npm init -y
npm install express cors crypto-js dotenv
2. .env
OPENAI_API_KEY=你的_API_KEY
OPENAI_BASE_URL=https://api.openai.com/v1
PORT=3000
3. server.js
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const CryptoJS = require('crypto-js');
const app = express();
app.use(cors());
app.use(express.json({ limit: '2mb' }));
const PORT = process.env.PORT || 3000;
// 简单内存缓存,生产环境建议换成 Redis
const summaryCache = new Map();
// 简单限流,生产环境建议按用户、IP、设备ID做更细粒度控制
const rateLimitMap = new Map();
function checkRateLimit(ip) {
const now = Date.now();
const windowMs = 60 * 1000;
const maxRequests = 20;
const record = rateLimitMap.get(ip) || {
count: 0,
startTime: now
};
if (now - record.startTime > windowMs) {
record.count = 1;
record.startTime = now;
rateLimitMap.set(ip, record);
return true;
}
if (record.count >= maxRequests) {
return false;
}
record.count++;
rateLimitMap.set(ip, record);
return true;
}
function normalizeText(text) {
return String(text || '')
.replace(/\s+/g, ' ')
.trim();
}
function createContentHash({ title, text, lang }) {
const normalized = normalizeText(`${title}\n${text}\n${lang}`);
return CryptoJS.SHA256(normalized).toString();
}
function trimText(text, maxChars = 10000) {
if (!text) return '';
if (text.length <= maxChars) {
return text;
}
// 保留开头和结尾,避免只截取开头导致遗漏结论
const head = text.slice(0, Math.floor(maxChars * 0.7));
const tail = text.slice(-Math.floor(maxChars * 0.3));
return `${head}\n\n……中间内容已省略……\n\n${tail}`;
}
function selectModel(taskType, textLength) {
// 这里使用示例模型名,实际请替换成你的模型
if (taskType === 'summary' && textLength < 5000) {
return 'gpt-4o-mini';
}
if (taskType === 'summary' && textLength >= 5000) {
return 'gpt-4o-mini';
}
return 'gpt-4o-mini';
}
function buildSummaryPrompt({ title, text }) {
return [
{
role: 'system',
content: '你是一个简洁、准确的网页阅读助手。'
},
{
role: 'user',
content: `请用中文总结以下网页内容。
要求:
1. 输出“一句话总结”
2. 输出“核心要点”,最多5条
3. 输出“适合人群”
4. 总字数不超过400字
5. 不要编造原文不存在的信息
网页标题:
${title}
网页正文:
${text}`
}
];
}
async function callOpenAI({ model, messages }) {
const baseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';
const response = await fetch(`${baseUrl}/chat/completions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model,
messages,
temperature: 0.2,
max_tokens: 600
})
});
if (!response.ok) {
const text = await response.text();
throw new Error(`AI接口调用失败:${text}`);
}
const data = await response.json();
return data.choices?.[0]?.message?.content || '';
}
app.post('/api/summary', async (req, res) => {
try {
const ip = req.ip;
if (!checkRateLimit(ip)) {
return res.status(429).json({
message: '请求过于频繁,请稍后再试'
});
}
const { url, title, text, lang = 'zh' } = req.body;
if (!text || text.length < 100) {
return res.status(400).json({
message: '网页正文内容太少'
});
}
const cleanedText = normalizeText(text);
const contentHash = createContentHash({
title,
text: cleanedText,
lang
});
const promptVersion = 'summary_v1';
const model = selectModel('summary', cleanedText.length);
const cacheKey = [
promptVersion,
model,
lang,
contentHash
].join(':');
if (summaryCache.has(cacheKey)) {
return res.json({
cached: true,
summary: summaryCache.get(cacheKey)
});
}
const finalText = trimText(cleanedText, 10000);
const messages = buildSummaryPrompt({
title: title || url || '未命名网页',
text: finalText
});
const summary = await callOpenAI({
model,
messages
});
summaryCache.set(cacheKey, summary);
return res.json({
cached: false,
summary
});
} catch (err) {
console.error(err);
return res.status(500).json({
message: err.message || '服务器错误'
});
}
});
app.listen(PORT, () => {
console.log(`AI Browser Cost Demo Server running on http://localhost:${PORT}`);
});
4. 启动后端
node server.js
然后在 Chrome 浏览器中加载插件:
- 打开
chrome://extensions/ - 开启“开发者模式”
- 点击“加载已解压的扩展程序”
- 选择插件目录
- 打开任意文章页面
- 点击插件按钮“总结当前网页”
十二、这份源码里做了哪些降本设计?
虽然这是一个 Demo,但已经包含了几个关键降本点。
1. 前端正文清洗
插件没有直接传完整 HTML,而是去掉了:
- script
- style
- nav
- footer
- header
- aside
- iframe
- 广告区域
- 评论区域
- 推荐区域
这可以减少大量无效 token。
2. 前端最大长度限制
const maxChars = 12000;
前端限制最多传 12000 字符,防止超长页面直接打爆成本。
生产环境中还可以根据用户等级调整:
| 用户类型 | 最大字符数 |
|---|---|
| 免费用户 | 5000 |
| 普通会员 | 12000 |
| 高级会员 | 30000 |
3. 后端二次裁剪
const finalText = trimText(cleanedText, 10000);
即使前端被篡改,后端仍然会做二次限制。
这是非常重要的安全和成本控制措施。
4. 内容 hash 缓存
const contentHash = createContentHash({
title,
text: cleanedText,
lang
});
相同页面、相同内容、相同语言、相同模型、相同 Prompt 版本会复用缓存。
这样当用户重复总结同一页面时,不会再次调用大模型。
5. Prompt 版本纳入缓存 Key
const promptVersion = 'summary_v1';
这很关键。
如果你修改了 Prompt,但缓存 key 没变,就可能返回旧格式内容。
所以缓存 key 应该包含 Prompt 版本。
6. 输出长度限制
max_tokens: 600
模型输出也会产生费用。
限制输出长度可以避免模型生成过长内容。
7. 简单限流
const maxRequests = 20;
防止短时间内大量请求。
生产环境应该按用户 ID、IP、设备指纹、套餐等级等维度做限流。
十三、进一步优化:Redis 缓存
Demo 使用内存缓存:
const summaryCache = new Map();
缺点是:
- 服务重启后缓存丢失
- 多实例之间无法共享
- 内存不可控
生产环境推荐使用 Redis。
缓存结构可以设计为:
ai:summary:v1:gpt-4o-mini:zh:{hash}
并设置过期时间:
TTL = 7天 / 30天 / 永久
对于公开网页摘要,可以缓存更久。
对于用户私有页面,应谨慎缓存,或只做用户级缓存。
十四、进一步优化:问答场景使用切块检索
网页问答比网页摘要更容易浪费 token。
错误做法:
每次用户提问,都把整篇网页发送给模型。
正确做法:
网页切块 → 用户问题匹配相关片段 → 只发送相关片段
一个简单的非向量版本可以用关键词匹配:
function splitText(text, chunkSize = 800) {
const chunks = [];
for (let i = 0; i < text.length; i += chunkSize) {
chunks.push(text.slice(i, i + chunkSize));
}
return chunks;
}
function simpleSearch(question, chunks, topK = 3) {
const keywords = question
.toLowerCase()
.split(/\s+/)
.filter(Boolean);
return chunks
.map(chunk => {
const lower = chunk.toLowerCase();
const score = keywords.reduce((sum, kw) => {
return sum + (lower.includes(kw) ? 1 : 0);
}, 0);
return {
chunk,
score
};
})
.sort((a, b) => b.score - a.score)
.slice(0, topK)
.map(item => item.chunk);
}
如果要效果更好,可以使用 embedding 模型和向量数据库。
但要注意,向量化本身也有成本。对于短网页,关键词检索可能已经够用。
十五、进一步优化:本地小模型
随着 WebGPU、WASM、本地推理技术发展,一些轻量任务可以放在本地完成。
适合本地处理的任务包括:
- 简单文本清洗
- 语言识别
- 页面分类
- 敏感信息检测
- 短文本翻译
- 关键词提取
- 阅读时间估计
- 是否值得总结的判断
本地模型虽然会增加客户端资源消耗,但可以减少服务端调用成本。
不过要注意:
- 浏览器兼容性
- 模型体积
- 首次加载速度
- 用户设备性能差异
- 隐私与权限说明
对于商业产品,通常可以采用混合方案:
本地规则 / 小模型 → 后端轻量模型 → 后端强模型
十六、成本估算方法
要真正降低成本,必须建立成本监控。
至少要记录:
- 用户 ID
- 请求类型
- 输入字符数
- 估算输入 tokens
- 输出 tokens
- 模型名称
- 是否命中缓存
- 请求耗时
- 错误率
- 页面 URL hash
- Prompt 版本
可以建立一个简单公式:
单次成本 = 输入 tokens × 输入单价 + 输出 tokens × 输出单价
然后按功能统计:
| 功能 | 请求量 | 缓存命中率 | 平均输入 tokens | 平均输出 tokens | 日成本 |
|---|---|---|---|---|---|
| 网页总结 | 10000 | 45% | 3000 | 400 | xxx |
| 划词翻译 | 50000 | 20% | 80 | 100 | xxx |
| 网页问答 | 20000 | 15% | 1200 | 300 | xxx |
你会发现,真正烧钱的功能通常不是调用次数最多的,而是长上下文输入最多的功能。
十七、推荐的生产级降本组合
如果你要做一个真正可上线的 AI 浏览器,推荐使用以下组合:
免费用户
- 不自动总结
- 每日次数限制
- 最大输入 5000 字符
- 只用便宜模型
- 强缓存
- 输出限制 300 字以内
付费用户
- 最大输入 20000 字符
- 可使用更强模型
- 支持网页问答
- 支持历史记录总结
- Redis 缓存
- 向量检索
高级用户
- 多标签页分析
- 长文档处理
- 高级模型路由
- 私有知识库
- 更高限流额度
- 更长上下文
十八、总结
AI浏览器降低成本的关键,不是简单地换一个更便宜的模型,而是建立完整的成本控制体系。
核心原则可以总结为:
- 能不调用模型,就不调用模型
- 能用缓存,就用缓存
- 能少传内容,就少传内容
- 能用小模型,就不用大模型
- 能本地处理,就不走云端
- 能限制输出,就不要放任生成
- 能按需触发,就不要自动触发
- 能检索片段,就不要发送全文
本文给出的源码只是一个最小可用 Demo,但它已经包含 AI 浏览器降本的基本思想:
- 正文提取
- 内容裁剪
- 缓存复用
- 模型选择
- Prompt 控制
- 限流保护
在真实业务中,只要继续加入 Redis、用户体系、用量统计、向量检索、模型路由和套餐策略,就可以逐步演化为一个成本可控的 AI 浏览器系统。
AI浏览器的竞争,不仅是模型能力的竞争,也是工程效率、成本控制和产品体验的竞争。谁能用更低的成本提供更稳定的体验,谁就更有机会做出长期可持续的 AI 产品。