Skip to content
围炉聊科技微信公众号二维码关注公众号,获取新文章推送
添加我为微信朋友扫描二维码,添加我为朋友

长期免费的Embedding向量化API——零成本基建系列

做知识库、语义搜索或 RAG 时,Embedding 往往是第一笔容易被忽略的成本。把文本切块以后,每一块都要变成向量;文章多一点、重新建一次索,调用次数很快就上去了。

这次只留下现在还能反复调用免费档、并且实际跑通的服务。注册送一笔 token 的试用不算进来,没测过的也不拿来凑表。测试用了同一组中英文语义检索样本:给“退款”问题找对应的退款政策,再和一段无关的服务器日志比较余弦相似度。

text
中文查询:我买了东西,想退货怎么办?
中文相关文档:退款政策:购买后30天内可申请全额退款。
中文无关文档:服务器日志保留七天,超过期限自动清理。

English query: I bought something and want a refund. How do I do that?
Relevant document: Refund policy: purchases can be fully refunded within 30 days.
Unrelated document: Server logs are retained for seven days and then removed.
服务这次实测模型免费规则中英文检索结果
硅基流动BAAI/bge-m3当前价格页标为免费两种语言都把退款政策排在日志前面
Cloudflare Workers AI@cf/baai/bge-m3@cf/qwen/qwen3-embedding-0.6b每天 10,000 Neurons两个模型的中英文检索都通过
Gemini Developer APIgemini-embedding-001gemini-embedding-2Free Tier 可调用两个模型的中英文检索都通过

这一轮不是基准测试,分数也不能跨模型横向比较。它只验证一件事:对同一模型来说,退款问题和退款政策的相似度高于和服务器日志的相似度。结果够用来排除“接口能返回向量但检索方向明显不对”的情况。

硅基流动:BGE-M3

硅基流动的价格页现在仍把 BAAI/bge-m3 标为免费,价格页。接口是 OpenAI 兼容风格,地址用 .cnhttps://api.siliconflow.cn/v1/embeddingsbge-m3 单条输入上限是 8,192 token;Qwen3-Embedding 系列虽然能用更长上下文和自定义维度,但价格页没有明确标成免费,就不把它算进零成本方案。Embedding 接口文档

测试返回的向量长度是 1024。中文里,相关文档的余弦相似度是 0.6978,无关文档是 0.5045;英文是 0.69330.4575。差距不算夸张,但排序是对的。

js
const response = await fetch('https://api.siliconflow.cn/v1/embeddings', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SILICONFLOW_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    model: 'BAAI/bge-m3',
    input: '退款政策:购买后30天内可申请全额退款。',
    encoding_format: 'float'
  })
});

const { data } = await response.json();
const vector = data[0].embedding;
console.log(vector.length); // 1024

国内项目如果只想给网站或小工具补一个中文语义搜索,我会先用它。之前测语音接口时已经踩过一次域名问题:旧示例里的 .com 和现在接口文档指定的 .cn 不能混用,这里也一样。

Cloudflare Workers AI:两个模型都能用

Cloudflare Workers AI 的免费计划每天给 10,000 Neurons,UTC 零点重置。@cf/baai/bge-m3@cf/qwen/qwen3-embedding-0.6b 目前都是每百万输入 token 消耗 1,075 Neurons。官方定价页

这次两个模型都返回 1024 维向量。BGE-M3 的中文、英文结果和硅基流动非常接近,毕竟跑的是同一个模型;Qwen3 的两组相关/无关差距更大一些:中文 0.5890 / 0.2353,英文 0.6228 / 0.1573。这不代表 Qwen3 一定更好,只说明在这四段很短的测试文本里,它把无关日志拉得更远。

Workers 里直接绑定 AI 后,调用不需要绕 REST API:

ts
export interface Env {
  AI: Ai;
}

export default {
  async fetch(request, env) {
    const result = await env.AI.run('@cf/qwen/qwen3-embedding-0.6b', {
      text: [
        '我买了东西,想退货怎么办?',
        '退款政策:购买后30天内可申请全额退款。'
      ]
    });
    return Response.json(result);
  }
} satisfies ExportedHandler<Env>;

如果要从本地脚本或别的服务器调用,换成 Cloudflare 的 REST API 即可。@cf/baai/bge-m3 是多语言模型,Cloudflare 的模型页给出的上下文窗口是 60,000 token;Qwen3 这次按接口返回的 1024 维数据使用即可,不要把不同模型生成的向量混到同一个索引里。BGE-M3 模型页

js
const url = `https://api.cloudflare.com/client/v4/accounts/${process.env.CLOUDFLARE_ACCOUNT_ID}/ai/run/@cf/baai/bge-m3`;
const response = await fetch(url, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ text: '服务器日志保留七天,超过期限自动清理。' })
});
const { result } = await response.json();
console.log(result.data[0]);

我自己的网站已经在 Cloudflare Pages 上,因为之前接过 Worker、D1,所以比较顺手。

Gemini:001 和 Embedding 2 不是同一种写法

Gemini Developer API 的 Free Tier 现在可以调用 gemini-embedding-001gemini-embedding-2。001 只做文本;Embedding 2 可以把文本、图片、音频、视频和 PDF 放进同一个向量空间。Gemini 定价页注明免费层提交的内容可能被用于改进产品,业务数据不适合直接拿去跑免费层。

我把输出维度固定为 768。001 的中文相关/无关分数是 0.7327 / 0.5867,英文是 0.7332 / 0.5480;Embedding 2 分别是 0.7450 / 0.59820.7837 / 0.5831。它们同样把相关文档排在前面。

这里有一个兼容性坑。gemini-embedding-001 可以用 taskType 区分 RETRIEVAL_QUERYRETRIEVAL_DOCUMENT;Embedding 2 不接受这个参数,官方建议把查询角色写进文本。两个模型的向量空间也不兼容,已经入库的数据不能只改模型名就继续用,必须重新向量化。Embedding 文档

js
// gemini-embedding-001:查询和文档分别声明用途
const response = await fetch(
  `https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-001:embedContent?key=${process.env.GEMINI_API_KEY}`,
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      content: { parts: [{ text: '我买了东西,想退货怎么办?' }] },
      taskType: 'RETRIEVAL_QUERY',
      outputDimensionality: 768
    })
  }
);
const { embedding } = await response.json();
console.log(embedding.values.length); // 768

Embedding 2 的文本检索请求改成下面这样,查询和文档用不同前缀:

js
const response = await fetch(
  `https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-2:embedContent?key=${process.env.GEMINI_API_KEY}`,
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      content: {
        parts: [{ text: 'task: search result | query: 我买了东西,想退货怎么办?' }]
      },
      outputDimensionality: 768
    })
  }
);

Gemini 2 的多模态也实际跑了一次。我用了前面文生图文章中生成的窗边橘猫 PNG,直接把图片的 base64 放进请求;再用中文和英文的“窗边橘猫”查询去匹配它,并和无关的服务器日志文本比较。中文查询对图片的相似度是 0.7086,对无关文本是 0.5772;英文是 0.74190.5525。两次都把图片排在了无关文本前面。

js
import { readFile } from 'node:fs/promises';

const image = await readFile('orange-cat.png');
const response = await fetch(
  `https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-2:embedContent?key=${process.env.GEMINI_API_KEY}`,
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      content: {
        parts: [{
          inline_data: {
            mime_type: 'image/png',
            data: image.toString('base64')
          }
        }]
      },
      outputDimensionality: 768
    })
  }
);
const { embedding } = await response.json();
console.log(embedding.values.length); // 768

这只验证了一张 PNG 和两条描述。把图片、PDF 和文本混在一起检索时,切分方式、图片质量和向量库的过滤条件都会影响结果;因此可不能这么简单的用在自己的知识库里哦。

API测试方式

测试脚本没有引入向量数据库,直接计算余弦相似度。做一轮很小的冒烟测试,这样最省事:

js
function cosine(a, b) {
  let dot = 0;
  let aa = 0;
  let bb = 0;
  for (let i = 0; i < a.length; i += 1) {
    dot += a[i] * b[i];
    aa += a[i] * a[i];
    bb += b[i] * b[i];
  }
  return dot / Math.sqrt(aa * bb);
}

console.log(cosine(queryVector, refundVector));
console.log(cosine(queryVector, logVector));

真正做知识库时,别只拿一条退款政策验收。应该把自己的标题、缩写、产品名、错别字和跨段提问都放进去,再看召回结果。

怎么选

中文网站或国内小工具,硅基流动的 BGE-M3 最省事。已经在 Cloudflare 上开发的,直接用 Workers AI,少维护一个供应商的 Key。需要多模态检索,或者后面想把图片、PDF 一起接进来,可以从 Gemini Embedding 2 开始试。