• 欢迎访问少将全栈,学会感恩,乐于付出,珍惜缘份,成就彼此、推荐使用最新版火狐浏览器和Chrome浏览器访问本网站。
  • 吐槽,投稿,删稿,交个朋友
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏少将全栈吧

Cloudflare Vectorize 从零搭建语义搜索:完整代码教程

AI Coding 实测 admin 57分钟前 6次浏览 已收录 扫描二维码

Cloudflare Vectorize 从零搭建语义搜索:完整代码教程

做AI应用经常遇到语义搜索的需求:用户输入”如何部署模型”,你要返回跟部署相关的文档,哪怕文档标题里没有”部署”两个字。关键词搜索搞不定这个,得靠向量检索。Cloudflare Vectorize是Cloudflare自家托管的向量数据库,跟Workers无缝集成,不用自己管服务器。这篇教程从创建索引到跑通查询,给出完整代码。

为什么选 Vectorize 而不是自建

自己搭pgvector或者Qdrant当然可以,但得管数据库运维、备份、扩容。Vectorize直接跑在Cloudflare边缘网络上,查询延迟低,按维度计费而不是按实例。根据Cloudflare官方定价页面,Workers Paid计划每月包含5000万查询维度和1000万存储维度,超出后查询维度每百万$0.01,存储维度每1亿$0.05。

实际算一下:1万条768维向量,每月查询3万次,总费用约$0.59/月。这个成本对独立开发者来说基本可以忽略。注意Vectorize存储和查询的是Float32格式,如果你用Float64传入会自动转换。

第一步:创建 Vectorize 索引

先装好wrangler CLI,在你的项目目录下执行:

npx wrangler vectorize create semantic-search-index --dimensions=768 --metric=cosine

这里两个参数很关键。dimensions必须跟你用的Embedding模型输出维度一致。Cloudflare Workers AI的@cf/baai/bge-base-en-v1.5模型输出768维,OpenAI的text-embedding-3-small输出1536维。如果维度对不上,插入和查询都会报错。

metric选cosine适合大多数文本语义搜索场景。另外两个选项是euclidean和dot-product,文本检索一般用cosine。索引创建后这两个参数不能改,要换得重建索引。

第二步:配置 wrangler.jsonc 绑定

在项目的wrangler.jsonc里添加Vectorize绑定:

{
  "name": "semantic-search",
  "main": "src/index.ts",
  "compatibility_date": "2026-10-01",
  "vectorize": [
    {
      "binding": "SEARCH_INDEX",
      "index_name": "semantic-search-index"
    }
  ],
  "ai": {
    "binding": "AI"
  }
}

同时绑定了AI(Workers AI),这样在Worker里可以直接调用Embedding模型,不用额外调OpenAI API。

第三步:写入向量数据

下面是完整的插入向量代码。把文本通过Workers AI生成embedding,然后upsert到Vectorize索引:

export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    if (url.pathname === "/insert" && request.method === "POST") {
      const body = await request.json();
      const documents = body.documents; // [{ id: "1", text: "内容..." }, ...]

      const embeddings = await env.AI.run("@cf/baai/bge-base-en-v1.5", {
        text: documents.map(d => d.text),
      });

      const vectors = documents.map((doc, i) => ({
        id: doc.id,
        values: embeddings.data[i],
        metadata: {
          text: doc.text,
          source: doc.source || "unknown",
          created: Date.now(),
        },
      }));

      const result = await env.SEARCH_INDEX.upsert(vectors);
      return Response.json({ inserted: vectors.length, mutationId: result });
    }
  },
};

用upsert而不是insert。区别在于:insert遇到相同ID会跳过,upsert会覆盖。大多数场景你应该用upsert,这样重新跑数据导入不会出问题。

插入是异步操作,调用返回后向量不会立即可查。官方文档说通常几秒内生效。如果立即查询返回空结果,等几秒再试。

第四步:实现语义查询

查询接口的代码:

if (url.pathname === "/search" && request.method === "POST") {
  const body = await request.json();
  const query = body.query;

  // 把查询文本转成向量
  const queryEmbedding = await env.AI.run("@cf/baai/bge-base-en-v1.5", {
    text: [query],
  });

  // 查询Vectorize索引
  const matches = await env.SEARCH_INDEX.query(
    queryEmbedding.data[0],
    {
      topK: 5,
      returnMetadata: "all",
    }
  );

  return Response.json({
    query,
    results: matches.matches.map(m => ({
      score: m.score,
      id: m.id,
      text: m.metadata?.text,
      source: m.metadata?.source,
    })),
  });
}

topK设5返回最相似的5条结果。returnMetadata设”all”会把插入时存的metadata一起返回,这样不用再查一次数据库。如果只要部分metadata字段,可以用”indexed”选项,延迟更低但长文本字段可能被截断。

第五步:加 Metadata 过滤

Vectorize支持查询时按metadata过滤。比如只搜某个来源的文档:

const matches = await env.SEARCH_INDEX.query(
  queryEmbedding.data[0],
  {
    topK: 10,
    returnMetadata: "all",
    filter: { source: "docs" },
  }
);

filter支持$eq(等于)操作。官方文档建议:高基数字段(比如UUID、时间戳)适合做$eq过滤,因为筛选性强。低基数字段(比如只有两三个值的分类)过滤效果有限。

metadata的key有些限制:不能为空、不能含点号(.)、不能含双引号、不能以$开头。踩过这个坑,调试半天发现key写成了user.id,改成userId就好了。

部署和测试

部署到Cloudflare:

npx wrangler deploy

测试插入数据:

curl -X POST https://semantic-search.your-subdomain.workers.dev/insert \
  -H "Content-Type: application/json" \
  -d '{"documents":[{"id":"1","text":"如何部署机器学习模型到生产环境","source":"docs"},{"id":"2","text":"向量数据库选型对比","source":"docs"},{"id":"3","text":"RAG系统架构设计","source":"blog"}]}'

测试查询:

curl -X POST https://semantic-search.your-subdomain.workers.dev/search \
  -H "Content-Type: application/json" \
  -d '{"query":"模型上线流程"}'

查询”模型上线流程”应该返回id为1的文档(关于部署机器学习模型),虽然两个表述完全不同,但语义接近,cosine相似度会比较高。

成本控制建议

Vectorize按查询维度计费,不是按查询次数。每次查询消耗的维度数等于(索引中向量数 + 1) × 每个向量维度。所以索引越大,每次查询越贵。

控制成本的方法:选维度小的Embedding模型。bge-base-en-v1.5是768维,如果用OpenAI的text-embedding-3-small是1536维,同样数据量成本翻倍。中文场景可以试试bge-base-zh-v1.5,也是768维,中文效果更好。

另一个方法是控制索引规模。只存需要检索的内容,别把整篇文档的embedding都塞进去。把长文档分段,每段生成一个向量,metadata里记录原文位置。

常见踩坑

第一,维度不匹配。创建索引时dimensions设了768,但用的Embedding模型输出1536维,插入直接报错。创建索引前先确认模型输出维度。

第二,插入后立即查询返回空。Vectorize的插入是异步的,官方说几秒内生效。如果批量插入后马上测试,等5秒再查。

第三,metadata key包含点号。JSON里写"user.id"作为key会被拒绝。用下划线或驼峰命名。

第四,topK和returnMetadata的限制。设returnValues为true或returnMetadata为”all”时,topK最大50。不需要返回向量值的话,保持returnValues为false,topK可以到100。

喜欢 (0)赏
[🍬谢谢你请我吃糖果🍬🍬~]
分享 (0)
关于作者:
少将,关注Web全栈开发、项目管理,持续不断的学习、努力成为一个更棒的开发,做最好的自己,让世界因你不同。