上一篇 下一篇 分享链接 返回 返回顶部

做 AI 浏览器别急着接大模型,先把这几处成本坑填上|源码示例

发布人:慈云数据-客服中心 发布时间:11小时前 阅读量:0

AI浏览器 如何降低成本|附源码

在大模型应用快速落地的今天,“AI浏览器”已经成为一个非常典型的产品形态:用户打开网页后,可以让 AI 总结页面、翻译内容、提取重点、生成待办、对网页内容进行问答,甚至自动填写表单、辅助搜索资料。

但真正做过 AI 浏览器的人会发现:成本很容易失控

因为浏览器场景天然有几个特点:

  1. 网页内容很长;
  2. 用户操作频繁;
  3. 同一个页面可能被反复分析;
  4. 多标签页、多任务并发;
  5. 用户希望实时响应;
  6. AI 请求一多,Token 成本和延迟都会上升。

所以,AI浏览器不是简单地“把网页内容丢给大模型”就可以了。想要长期稳定运行,必须从架构层面设计成本优化方案。

本文会从产品设计、技术架构、Token 优化、缓存策略、模型选择、流式响应、前后端实现等角度,系统讲解如何降低 AI 浏览器的成本,并附上一个简化版源码示例,帮助你快速理解实现思路。


一、AI浏览器为什么容易烧钱?

AI浏览器的核心能力通常包括:

  • 网页摘要;
  • 页面问答;
  • 网页翻译;
  • 关键词提取;
  • 文章改写;
  • 自动搜索;
  • 自动阅读多个网页;
  • 自动执行浏览器操作;
  • 长网页内容理解;
  • 多轮上下文对话。

这些能力背后都依赖大模型推理,而大模型计费通常和 Token 数量相关。

简单来说:

成本 ≈ 输入 Token 成本 + 输出 Token 成本 + 请求次数成本 + 向量检索成本 + 服务器成本

如果用户打开一个网页,这个网页有 2 万字,你每次提问都把全文传给大模型,那么成本会非常高。

例如:

网页正文:20,000 中文字符
转成 Token:约 10,000 ~ 15,000 tokens
用户问 10 个问题
如果每次都传全文:约 100,000 ~ 150,000 input tokens

如果用户很多,这个成本会迅速放大。

因此,AI浏览器降本的第一个原则是:

不要每次都把完整网页内容传给大模型。


二、AI浏览器降本的核心思路

想降低成本,可以从以下几个方向入手:

优化方向 说明
内容清洗 去掉广告、导航、脚本、无关文本
文本切片 将长网页拆成多个片段
向量检索 用户提问时只取相关片段
摘要缓存 同一个网页只总结一次
问答缓存 相同问题直接返回缓存
分级模型 简单任务用便宜模型,复杂任务用强模型
流式输出 提升体验,降低用户重复点击
限制上下文 控制最大输入 Token
本地预处理 在浏览器端完成内容提取
请求合并 避免重复请求模型
增量处理 只处理变化部分
用户额度 防止恶意或无意识高频调用

其中最关键的三个技术是:

  1. 网页正文提取
  2. RAG 检索增强生成
  3. 缓存机制

这三个做好之后,AI浏览器的成本通常可以下降 50% 到 90%。


三、推荐架构设计

一个成本友好的 AI 浏览器架构可以设计成下面这样:

浏览器插件 / WebView / 客户端
        │
        │ 提取网页正文
        ▼
内容清洗模块
        │
        │ 生成 pageHash
        ▼
缓存层 Redis / SQLite
        │
        ├── 已有摘要:直接返回
        │
        └── 未命中:继续处理
        ▼
文本切片 Chunking
        │
        ▼
向量化 Embedding
        │
        ▼
向量数据库
        │
        ▼
用户提问
        │
        ▼
检索相关片段
        │
        ▼
构造 Prompt
        │
        ▼
调用大模型
        │
        ▼
结果缓存并返回

这个架构的关键点是:
页面只处理一次,问题只使用相关内容,不重复消耗 Token。


四、第一步:在浏览器端提取正文

AI浏览器降本的第一步,是不要把整个 HTML 发给后端。

一个网页的 HTML 里可能包含大量无用内容:

  • 导航栏;
  • 页脚;
  • 推荐文章;
  • 广告;
  • 评论区;
  • JavaScript;
  • CSS;
  • 隐藏元素;
  • 侧边栏;
  • 统计代码。

如果直接把完整 HTML 发给大模型,会浪费大量 Token。

比较好的做法是在浏览器端用 Readability 算法提取正文。

例如可以使用 Mozilla 的 @mozilla/readability

浏览器端源码示例

// content-script.js
import { Readability } from '@mozilla/readability';

function extractPageContent() {
  const documentClone = document.cloneNode(true);
  const reader = new Readability(documentClone);
  const article = reader.parse();

  if (!article) {
    return {
      title: document.title,
      url: location.href,
      content: document.body.innerText.slice(0, 20000)
    };
  }

  return {
    title: article.title || document.title,
    url: location.href,
    content: article.textContent || '',
    excerpt: article.excerpt || ''
  };
}

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'EXTRACT_PAGE') {
    const data = extractPageContent();
    sendResponse(data);
  }
});

这样做的好处是:

  1. 只传正文;
  2. 减少无关噪声;
  3. 降低 Token 数量;
  4. 提升回答质量;
  5. 减少后端服务器压力。

在实际项目中,单是正文提取这一步,通常就能减少 30% 到 70% 的输入内容。


五、第二步:对网页内容生成唯一标识

同一个网页可能被多个用户打开,也可能被同一个用户多次打开。如果每次都重新总结,就会浪费成本。

我们可以对网页 URL 和正文内容生成一个 pageHash

// hash.js
import crypto from 'crypto';

export function createPageHash(url, content) {
  return crypto
    .createHash('sha256')
    .update(url + '\n' + content.slice(0, 5000))
    .digest('hex');
}

为什么不只用 URL?

因为很多页面 URL 不变但内容会变化,例如:

  • 新闻首页;
  • 商品详情页;
  • 文档页面;
  • 动态渲染网页;
  • 内部系统页面。

为什么不对全文做 hash?

可以,但长文本 hash 会略微增加计算成本。实际中可以根据业务选择:

  • 对 URL + title + 前 5000 字符做 hash;
  • 对完整正文做 hash;
  • 对正文分段后分别做 hash。

对于大多数 AI 浏览器场景,使用 url + content.slice(0, 5000) 已经足够。


六、第三步:文本切片,避免全文进入模型

长网页不能每次都完整送给大模型。正确做法是把正文拆成多个 chunk。

切片时需要注意:

  1. 每个 chunk 不要太短,否则语义不完整;
  2. 每个 chunk 不要太长,否则检索不精准;
  3. chunk 之间最好有 overlap,避免上下文断裂;
  4. 按段落切分优于按固定字符切分。

下面是一个简单的切片函数。

// chunk.js
export function splitText(text, options = {}) {
  const chunkSize = options.chunkSize || 1000;
  const overlap = options.overlap || 150;

  const paragraphs = text
    .split(/\n+/)
    .map(p => p.trim())
    .filter(Boolean);

  const chunks = [];
  let current = '';

  for (const paragraph of paragraphs) {
    if ((current + paragraph).length <= chunkSize) {
      current += paragraph + '\n';
    } else {
      if (current.trim()) {
        chunks.push(current.trim());
      }

      if (overlap > 0 && current.length > overlap) {
        current = current.slice(-overlap) + '\n' + paragraph + '\n';
      } else {
        current = paragraph + '\n';
      }
    }
  }

  if (current.trim()) {
    chunks.push(current.trim());
  }

  return chunks;
}

例如一篇 2 万字网页,可以切成 20 个片段。用户提问时,只检索最相关的 3 到 5 个片段,而不是把 2 万字全部传给模型。

这就是 AI浏览器降本的核心。


七、第四步:使用向量检索减少输入 Token

切片后,我们需要把每个 chunk 转成向量,然后存入向量数据库。

用户提问时,也把问题转成向量,然后查找最相关的 chunk。

这样构造 Prompt 时只需要包含:

  • 页面标题;
  • 用户问题;
  • 3 到 5 个相关片段。

而不是整篇文章。

Embedding 示例代码

下面是一个伪代码风格的 Node.js 示例,你可以替换成自己的模型服务。

// embedding.js
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
});

export async function createEmbedding(text) {
  const result = await client.embeddings.create({
    model: 'text-embedding-3-small',
    input: text
  });

  return result.data[0].embedding;
}

简单内存向量检索源码

为了方便理解,下面提供一个不依赖向量数据库的简化版本。生产环境可以替换为:

  • Milvus;
  • Qdrant;
  • Weaviate;
  • pgvector;
  • Elasticsearch Vector;
  • LanceDB。
// vector-store.js
function cosineSimilarity(a, b) {
  let dot = 0;
  let normA = 0;
  let normB = 0;

  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }

  return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}

const memoryStore = new Map();

export function saveChunks(pageHash, chunks) {
  memoryStore.set(pageHash, chunks);
}

export function searchChunks(pageHash, queryEmbedding, topK = 4) {
  const chunks = memoryStore.get(pageHash) || [];

  return chunks
    .map(item => ({
      ...item,
      score: cosineSimilarity(item.embedding, queryEmbedding)
    }))
    .sort((a, b) => b.score - a.score)
    .slice(0, topK);
}

这个简化版适合本地 Demo 或小规模测试。真实业务中建议持久化存储,否则服务重启后数据会丢失。


八、第五步:摘要缓存,避免重复调用

网页摘要是 AI浏览器里最常见的功能。如果用户每次点击“总结页面”都请求大模型,那么成本会很高。

正确做法是:

  1. 根据 pageHash 查询缓存;
  2. 如果命中缓存,直接返回摘要;
  3. 如果没有缓存,再调用模型;
  4. 生成后写入缓存。
// cache.js
const cache = new Map();

export function getCache(key) {
  const item = cache.get(key);
  if (!item) return null;

  if (item.expireAt && Date.now() > item.expireAt) {
    cache.delete(key);
    return null;
  }

  return item.value;
}

export function setCache(key, value, ttlSeconds = 86400) {
  cache.set(key, {
    value,
    expireAt: Date.now() + ttlSeconds * 1000
  });
}

缓存 key 可以这样设计:

summary:{pageHash}
qa:{pageHash}:{questionHash}
translate:{pageHash}:{targetLang}

这样不同能力之间互不影响,也方便后续统计成本。


九、第六步:分级模型,简单任务用便宜模型

AI浏览器并不是所有任务都需要最强模型。

例如:

任务 推荐模型策略
网页标题总结 小模型
关键词提取 小模型
文章摘要 中等模型
页面问答 中等模型 + RAG
多网页综合分析 强模型
自动操作浏览器 强模型
代码分析 强模型
法律、医疗、金融严肃场景 强模型 + 审核

也就是说,应该根据任务复杂度选择模型。

可以封装一个模型路由器:

// model-router.js
export function selectModel(taskType) {
  switch (taskType) {
    case 'keyword':
    case 'title':
    case 'simple_summary':
      return 'gpt-4o-mini';

    case 'page_qa':
    case 'translate':
    case 'summary':
      return 'gpt-4o-mini';

    case 'multi_page_research':
    case 'agent':
    case 'code_analysis':
      return 'gpt-4o';

    default:
      return 'gpt-4o-mini';
  }
}

生产环境中还可以增加:

  • 用户会员等级;
  • 当前系统负载;
  • 剩余额度;
  • 响应时延要求;
  • 模型失败后的降级策略。

例如免费用户默认使用便宜模型,付费用户可以使用更强模型。


十、第七步:构造更省 Token 的 Prompt

很多 AI 应用成本高,并不是模型贵,而是 Prompt 写得太啰嗦。

AI浏览器中的 Prompt 应该尽量结构化、简洁、明确。

不推荐的 Prompt

你是一个非常非常厉害的人工智能助手,你需要认真阅读下面这篇非常长的文章,
然后尽可能详细地回答用户问题。请注意你必须保持准确性,并且不要胡说。
下面是文章内容……

这种 Prompt 有大量无效词。

推荐的 Prompt

你是网页阅读助手。只根据给定资料回答。
如果资料不足,回答“页面中未找到相关信息”。

页面标题:{{title}}

相关资料:
{{context}}

用户问题:
{{question}}

要求:
1. 用中文回答;
2. 简洁准确;
3. 必要时列出要点。

这个 Prompt 更短、更稳定,也更容易控制输出。


十一、完整后端源码示例

下面给出一个简化版 Express 后端源码,包含:

  • 页面接收;
  • hash 生成;
  • 文本切片;
  • embedding;
  • 向量保存;
  • 摘要缓存;
  • 页面问答;
  • 模型调用。

安装依赖

npm init -y
npm install express cors openai dotenv

.env

OPENAI_API_KEY=你的API_KEY
PORT=3000

server.js

import express from 'express';
import cors from 'cors';
import crypto from 'crypto';
import dotenv from 'dotenv';
import OpenAI from 'openai';

dotenv.config();

const app = express();
app.use(cors());
app.use(express.json({ limit: '5mb' }));

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
});

const cache = new Map();
const vectorStore = new Map();

function hashText(text) {
  return crypto.createHash('sha256').update(text).digest('hex');
}

function createPageHash(url, content) {
  return hashText(url + '\n' + content.slice(0, 5000));
}

function getCache(key) {
  const item = cache.get(key);
  if (!item) return null;

  if (item.expireAt && Date.now() > item.expireAt) {
    cache.delete(key);
    return null;
  }

  return item.value;
}

function setCache(key, value, ttlSeconds = 86400) {
  cache.set(key, {
    value,
    expireAt: Date.now() + ttlSeconds * 1000
  });
}

function splitText(text, chunkSize = 1000, overlap = 150) {
  const paragraphs = text
    .split(/\n+/)
    .map(item => item.trim())
    .filter(Boolean);

  const chunks = [];
  let current = '';

  for (const paragraph of paragraphs) {
    if ((current + paragraph).length <= chunkSize) {
      current += paragraph + '\n';
    } else {
      if (current.trim()) chunks.push(current.trim());

      if (current.length > overlap) {
        current = current.slice(-overlap) + '\n' + paragraph + '\n';
      } else {
        current = paragraph + '\n';
      }
    }
  }

  if (current.trim()) chunks.push(current.trim());
  return chunks;
}

async function createEmbedding(text) {
  const result = await client.embeddings.create({
    model: 'text-embedding-3-small',
    input: text
  });

  return result.data[0].embedding;
}

function cosineSimilarity(a, b) {
  let dot = 0;
  let normA = 0;
  let normB = 0;

  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }

  return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}

function searchChunks(pageHash, queryEmbedding, topK = 4) {
  const chunks = vectorStore.get(pageHash) || [];

  return chunks
    .map(item => ({
      ...item,
      score: cosineSimilarity(item.embedding, queryEmbedding)
    }))
    .sort((a, b) => b.score - a.score)
    .slice(0, topK);
}

async function callLLM({ model, messages, temperature = 0.2 }) {
  const completion = await client.chat.completions.create({
    model,
    messages,
    temperature
  });

  return completion.choices[0].message.content;
}

app.post('/api/page/index', async (req, res) => {
  try {
    const { url, title, content } = req.body;

    if (!url || !content) {
      return res.status(400).json({ error: 'url 和 content 不能为空' });
    }

    const pageHash = createPageHash(url, content);

    if (vectorStore.has(pageHash)) {
      return res.json({
        pageHash,
        indexed: true,
        cached: true
      });
    }

    const chunks = splitText(content);
    const embeddedChunks = [];

    for (let i = 0; i < chunks.length; i++) {
      const embedding = await createEmbedding(chunks[i]);

      embeddedChunks.push({
        id: `${pageHash}_${i}`,
        pageHash,
        title,
        url,
        text: chunks[i],
        embedding
      });
    }

    vectorStore.set(pageHash, embeddedChunks);

    res.json({
      pageHash,
      indexed: true,
      cached: false,
      chunkCount: embeddedChunks.length
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: '页面索引失败' });
  }
});

app.post('/api/page/summary', async (req, res) => {
  try {
    const { pageHash, title } = req.body;

    if (!pageHash) {
      return res.status(400).json({ error: 'pageHash 不能为空' });
    }

    const cacheKey = `summary:${pageHash}`;
    const cached = getCache(cacheKey);

    if (cached) {
      return res.json({
        cached: true,
        summary: cached
      });
    }

    const chunks = vectorStore.get(pageHash) || [];
    const context = chunks
      .slice(0, 6)
      .map((item, index) => `片段${index + 1}:\n${item.text}`)
      .join('\n\n');

    const summary = await callLLM({
      model: 'gpt-4o-mini',
      messages: [
        {
          role: 'system',
          content: '你是网页摘要助手,请基于用户提供的网页内容生成准确、简洁的中文摘要。'
        },
        {
          role: 'user',
          content: `页面标题:${title || ''}

网页内容:
${context}

请输出:
1. 一句话概括;
2. 3-5个核心要点;
3. 适合继续追问的问题。`
        }
      ]
    });

    setCache(cacheKey, summary, 7 * 86400);

    res.json({
      cached: false,
      summary
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: '摘要生成失败' });
  }
});

app.post('/api/page/ask', async (req, res) => {
  try {
    const { pageHash, question, title } = req.body;

    if (!pageHash || !question) {
      return res.status(400).json({ error: 'pageHash 和 question 不能为空' });
    }

    const questionHash = hashText(question);
    const cacheKey = `qa:${pageHash}:${questionHash}`;
    const cached = getCache(cacheKey);

    if (cached) {
      return res.json({
        cached: true,
        answer: cached
      });
    }

    const queryEmbedding = await createEmbedding(question);
    const relatedChunks = searchChunks(pageHash, queryEmbedding, 4);

    const context = relatedChunks
      .map((item, index) => `资料${index + 1},相关度:${item.score.toFixed(3)}\n${item.text}`)
      .join('\n\n');

    const answer = await callLLM({
      model: 'gpt-4o-mini',
      messages: [
        {
          role: 'system',
          content: '你是网页问答助手。只能根据给定资料回答;如果资料不足,请说明页面中未找到相关信息。'
        },
        {
          role: 'user',
          content: `页面标题:${title || ''}

相关资料:
${context}

用户问题:
${question}

回答要求:
1. 使用中文;
2. 简洁准确;
3. 如果有依据,请引用资料中的关键信息;
4. 不要编造页面中没有的内容。`
        }
      ]
    });

    setCache(cacheKey, answer, 86400);

    res.json({
      cached: false,
      answer,
      usedChunks: relatedChunks.length
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: '问答失败' });
  }
});

const port = process.env.PORT || 3000;

app.listen(port, () => {
  console.log(`AI Browser backend running at http://localhost:${port}`);
});

启动服务:

node server.js

如果你使用的是 ES Module,需要在 package.json 中加上:

{
  "type": "module"
}

十二、前端调用示例

假设浏览器插件已经提取到页面内容,可以这样调用后端。

async function indexPage(page) {
  const response = await fetch('http://localhost:3000/api/page/index', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(page)
  });

  return response.json();
}

async function summarizePage(pageHash, title) {
  const response = await fetch('http://localhost:3000/api/page/summary', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      pageHash,
      title
    })
  });

  return response.json();
}

async function askPage(pageHash, title, question) {
  const response = await fetch('http://localhost:3000/api/page/ask', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      pageHash,
      title,
      question
    })
  });

  return response.json();
}

一个典型流程是:

const page = {
  url: location.href,
  title: document.title,
  content: document.body.innerText
};

const indexResult = await indexPage(page);

const summaryResult = await summarizePage(indexResult.pageHash, page.title);
console.log(summaryResult.summary);

const answerResult = await askPage(
  indexResult.pageHash,
  page.title,
  '这篇文章的核心观点是什么?'
);

console.log(answerResult.answer);

十三、进一步降低成本的高级策略

如果你的 AI浏览器已经有一定用户量,还可以继续做更深入的优化。

1. 本地摘要预处理

可以先在浏览器端对正文做简单压缩:

  • 删除连续空行;
  • 删除重复文本;
  • 删除菜单项;
  • 删除版权信息;
  • 删除短链接;
  • 删除无意义符号。
function cleanText(text) {
  return text
    .replace(/\s+/g, ' ')
    .replace(/相关推荐|相关文章|分享到|复制链接/g, '')
    .trim();
}

虽然这个函数很简单,但在网页场景中很有效。


2. 用户问题改写

用户的问题可能很长,也可能包含无关信息。可以先用小模型把问题改写成检索 query。

但要注意:如果每次都多调用一次模型,也会增加成本。更便宜的办法是先用规则处理:

function normalizeQuestion(question) {
  return question
    .replace(/请问|帮我看下|我想知道|能不能告诉我/g, '')
    .trim()
    .slice(0, 300);
}

这样可以减少 embedding 输入长度。


3. 控制 topK

RAG 并不是检索越多越好。
topK=10 看起来更安全,但成本也更高。

一般建议:

  • 简单问答:topK = 3;
  • 复杂问题:topK = 5;
  • 长文综合:topK = 6;
  • 多网页研究:分阶段汇总。

你可以根据问题类型动态调整:

function getTopK(question) {
  if (question.length < 20) return 3;
  if (/总结|分析|对比|原因|影响/.test(question)) return 5;
  return 4;
}

4. 限制最大输出长度

很多时候输出 Token 也很贵。
可以在 Prompt 中明确要求:

请在 300 字以内回答。

或者使用 API 参数控制最大输出长度。

const completion = await client.chat.completions.create({
  model: 'gpt-4o-mini',
  messages,
  temperature: 0.2,
  max_tokens: 500
});

这样可以防止模型输出过长。


5. 流式输出减少重复点击

用户等待时间太长时,容易重复点击按钮,导致重复请求。

流式输出虽然不一定直接减少单次 Token,但可以降低重复请求率,间接降低成本。


6. 额度系统

对于公开产品,必须设计额度系统,例如:

  • 每日免费摘要 10 次;
  • 每日免费问答 30 次;
  • 长网页分析消耗更多额度;
  • 多网页研究只对付费用户开放;
  • 超大页面需要用户确认。

额度不是单纯限制用户,而是保护系统成本。


十四、成本优化效果估算

假设一篇网页原始正文约 15,000 tokens,用户问 5 个问题。

不优化方案

每次都传全文:

15,000 tokens × 5 = 75,000 input tokens

RAG 优化方案

切片后每次只取 4 个 chunk,每个 chunk 约 500 tokens:

4 × 500 × 5 = 10,000 input tokens

再加上 embedding 和摘要缓存,整体成本通常可以下降:

约 60% ~ 90%

如果同一个网页被多个用户访问,缓存命中率越高,节省越明显。


十五、生产环境建议

上面的源码适合 Demo,但生产环境还需要增强:

  1. 用 Redis 替代内存缓存;
  2. 用 PostgreSQL + pgvector 或 Qdrant 存储向量;
  3. 增加用户鉴权;
  4. 增加调用频率限制;
  5. 增加日志和成本统计;
  6. 增加任务队列;
  7. 增加失败重试;
  8. 增加模型降级策略;
  9. 增加隐私保护;
  10. 对敏感网页内容做本地处理或加密存储。

尤其是隐私问题非常重要。AI浏览器可能读取用户当前页面内容,如果用户正在浏览公司后台、邮箱、财务系统、医疗报告等页面,必须明确告知用户,并尽量做到:

  • 用户主动触发才读取页面;
  • 不默认上传敏感内容;
  • 支持本地模式;
  • 支持删除历史记录;
  • 传输过程使用 HTTPS;
  • 服务端不长期保存原文;
  • 企业版支持私有化部署。

十六、总结

AI浏览器要想降低成本,核心不是“找一个更便宜的模型”这么简单,而是要从整体链路优化:

  • 浏览器端提取正文,减少无效内容;
  • 使用 pageHash 避免重复处理;
  • 对长文本进行合理切片;
  • 使用 embedding 和向量检索,只给模型相关片段;
  • 对摘要和问答结果做缓存;
  • 根据任务复杂度选择不同模型;
  • 控制 Prompt 长度和输出长度;
  • 使用额度系统防止滥用;
  • 在生产环境引入 Redis、向量数据库和日志统计。

一句话概括:

AI浏览器降本的本质,是把“每次全文推理”改造成“页面一次索引、问题按需检索、结果尽量复用”。

只要完成这套架构,即使用户量上升,成本也会更可控,响应速度也会更快,用户体验反而更好。

目录结构
全文