Alchemy 2.0.0-beta.46 发布解读:将 Cloudflare Vectorize 向量数据库作为 Effect 原生资源接入 Worker
2026/9/13 2:36:07 网站建设 项目流程

Alchemy 2.0.0-beta.46 发布解读:将 Cloudflare Vectorize 向量数据库作为 Effect 原生资源接入 Worker

【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code

本篇文章基于 Alchemy(Effect 生态的声明式云资源框架)官方发布说明,系统解读v2.0.0-beta.46的两项核心更新:一是将Cloudflare Vectorize封装为可绑定(bindable)的 Effect 原生资源——向量索引(VectorizeIndex)、元数据索引(VectorizeMetadataIndex)及运行时客户端;二是修复 Cloudflare state store 引导(bootstrap)时静默轮换加密密钥与认证令牌的严重缺陷。读者读完可掌握如何在 Worker 中声明、绑定并查询向量索引,以及为什么必须升级修复了密钥轮换问题的版本。

版本概览:Vectorize 成为一等公民资源

v2.0.0-beta.46的核心主题是让Cloudflare Vectorize(一个全球分布式向量数据库索引)在 Alchemy 中成为"可绑定资源"——你可以像声明 D1 数据库、KV Namespace、R2 Bucket 一样,用纯 TypeScript 声明一个向量索引,将其绑定到 Worker,并获得一个类型完备、可直接yield*的 Effect 运行时客户端。

本次发布同时包含:

  • Cloudflare.VectorizeIndex:向量索引资源,用于存储与查询向量嵌入(embeddings);
  • Cloudflare.VectorizeMetadataIndex:元数据索引资源,让query支持按元数据属性过滤;
  • 一个重要的 state store 引导修复:不再在每次更新时轮换加密密钥与 auth token。

Cloudflare.VectorizeIndex:声明一个全局分布式向量索引

VectorizeIndex是 Vectorize 在 Alchemy 中的资源抽象。声明方式与声明其他 Cloudflare 资源一致:把它作为一个 resourceyield*出来即可。最基础的用法是显式指定向量维度与距离度量:

const index = yield* Cloudflare.VectorizeIndex("docs", { dimensions: 768, metric: "cosine", });

这里"docs"是索引的逻辑名称(在 Alchemy 语义中即资源的id)。两个配置项的含义:

  • dimensions:索引中每个向量存储的维度数。Vectorize 要求同一索引内的向量维度一致;
  • metric:相似度搜索使用的距离度量。

从 VectorizeIndex.ts 的源码看,DistanceMetric的合法取值是"cosine" | "euclidean" | "dot-product",默认值为"cosine"

使用托管嵌入模型的preset预设

如果你不希望手工维护dimensionsmetric与嵌入模型的匹配关系,可以直接指定preset,让二者随托管模型固定下来:

const index = yield* Cloudflare.VectorizeIndex("docs", { preset: "@cf/baai/bge-base-en-v1.5", });

源码中Preset联合类型收录了官方预设,例如@cf/baai/bge-small-en-v1.5@cf/baai/bge-base-en-v1.5@cf/baai/bge-large-en-v1.5openai/text-embedding-ada-002cohere/embed-multilingual-v2.0等(VectorizeIndex.ts)。值得注意的细节是:该联合类型被刻意设计为开放联合| (string & {}),注释明确说明这样做的目的是"保持联合开放,避免因类型过时而被新的 Cloudflare 预设阻塞"——即未来 Cloudflare 新增预设时,无需等待框架发版即可直接使用。presetdimensions/metric互斥,同时提供会构成配置冲突。

测试用例印证了preset与显式配置在行为上等价:在 VectorizeIndex.test.ts 中,以@cf/baai/bge-base-en-v1.5预设部署后,实测索引的config.dimensions解析为 768(bge-base 的固定维度),并验证了description会被持久化。

索引不可变:改配置即触发替换

VectorizeIndex不可变性是理解其生命周期模型的关键:dimensionsmetricpresetdescription都在创建时固定,Vectorize 本身没有更新 API,因此修改其中任何一项都会触发资源的替换(replacement),而不是原地更新。

这一行为在 Provider 的diff逻辑中有精确实现(VectorizeIndex.ts):当namepresetdimensionsmetric(默认值兜底为cosine)或description任一发生变化时,diff 返回{ action: "replace" }。同时indexNameaccountId被标记为稳定属性(stables),作为跨替换识别同一逻辑资源的锚点。

资源模型还内建了健壮性处理:reconcile阶段先按名称观测线上索引,NotFound/Gone时回落到创建路径;创建遇到IndexAlreadyExists(409 冲突)时容忍竞态,改为重新读取已有索引(VectorizeIndex.ts)。

将索引绑定到 Worker:获得 Effect 原生客户端

仅声明资源还不够,真正的使用方式是把它**绑定(bind)**到 Worker。Cloudflare.VectorizeIndex.bind(index)完成绑定,并交还一个客户端,其方法upsertqueryqueryByIdinsertdeleteByIdsgetByIdsdescribe全部是可直接yield*的 Effect:

export default class Worker extends Cloudflare.Worker<Worker>()( "Worker", { main: import.meta.filename }, Effect.gen(function* () { const docs = yield* Cloudflare.VectorizeIndex.bind(index); return { fetch: Effect.gen(function* () { yield* docs.upsert([ { id: "1", values: [0.1, 0.2, 0.3], metadata: { kind: "doc" } }, ]); const matches = yield* docs.query([0.1, 0.2, 0.3], { topK: 5 }); return yield* HttpServerResponse.json({ count: matches.count }); }), }; }).pipe(Effect.provide(Cloudflare.VectorizeIndexBindingLive)), ) {}

几个要点:

  1. bind传入的是已声明的索引资源,而不是字符串名称——类型系统保证你绑定的一定是一个合法的VectorizeIndex
  2. upsert接受{ id, values, metadata }数组,values是长度与索引dimensions一致的数值向量,metadata是随向量存储、可供后续过滤的键值对;
  3. query接受查询向量与选项对象(如{ topK: 5 }),返回相似度最高的匹配;
  4. VectorizeIndexBindingLive是框架提供的运行时实现层,通过Effect.provide注入,让客户端在 Worker 运行时环境内真正可执行。

从底层实现看,绑定最终会落到 Cloudflare runtime 的远端绑定机制:makeRemoteBinding会生成一个type: "vectorize"的绑定,并包装为cloudflare-internal:vectorize-api模块,注入fetcher服务、indexId(索引名)与indexVersion: "v2"(Vectorize.ts)。这意味着绑定不止是编译期类型,而是 Worker 实际运行时可以执行的协议级对接。

Cloudflare.VectorizeMetadataIndex:让查询支持元数据过滤

默认情况下,Vectorize 不允许在query中按元数据属性进行filter——属性必须先建索引才能被过滤。VectorizeMetadataIndex就是用来声明这种元数据索引的资源。它的配置指向父索引,命名属性并给出类型:

const index = yield* Cloudflare.VectorizeIndex("docs", { dimensions: 768, metric: "cosine", }); yield* Cloudflare.VectorizeMetadataIndex("kind-index", { indexName: index.indexName, propertyName: "kind", indexType: "string", });

三个配置项的含义(VectorizeMetadataIndex.ts):

  • indexName:父 Vectorize 索引的名称。最佳实践是直接传index.indexName(而非重新拼字符串),这样框架可以自动追踪资源间依赖关系;更换父索引会触发替换;
  • propertyName:要建索引的元数据属性名,query的 filter 表达式将使用这个名字;
  • indexType:元数据值的类型,合法取值为"string" | "number" | "boolean"

元数据索引与向量索引一样不可变——修改属性名、类型或父索引都会触发替换。

关键使用约束是时序:元数据索引必须在向量插入之前创建。在此前提下,query即可携带 filter:

const matches = yield* docs.query([0.1, 0.2, 0.3], { topK: 3, filter: { kind: { $eq: "second" } }, });

filter采用类似 MongoDB 的操作符语法,{ property: { $eq: value } }表示对该属性做等值匹配,与topK配合即可实现"在某类文档中检索最近邻"的典型检索增强生成(RAG)场景。

修复:state store 引导不再轮换你的密钥

本次发布的另一半是必须关注的缺陷修复,涉及 Cloudflare state store(Alchemy 用于持久化部署状态的服务)。

⚠️ 升级提醒:如果你使用了 Cloudflare state store,请务必升级。此前的版本在每次 bootstrap/更新时都会重新生成 state store 的加密密钥与认证令牌,因为引导路径总是从全新的本地状态出发。轮换加密密钥意味着之前持久化的状态将无法再解密

缺陷成因

问题根因在于引导(bootstrap)的初始数据来源:更新 state store 栈时总是使用一份全新的本地状态(fresh local state)作为起点,于是认证令牌(auth token)——更严重的是加密密钥(encryption key)——在每次运行时都被重新生成。

从 State.ts 的引导逻辑可以印证:初始化时会检查本地栈localStage是否还存在,若存在则走deployWithLocalState完成引导(State.ts)。密钥与令牌在Token.ts中以EncryptionKeySecretNameAuthTokenSecretName两个 secret 形式管理——一旦被重新生成,旧状态便不可解密。

修复方式与验证命令

beta.46将引导改为从远端状态(remote state)读取——远端状态中已经保存了 auth token 与加密密钥,因此两者都不会再被重新生成。当你确实需要强制更新所有内容、但又不希望轮换密钥时,可以运行:

bun alchemy cloudflare bootstrap --force

该命令会基于真实远端状态强制刷新一切,同时保持密钥不变。

顺带加固的三个相邻故障模式

同一改动还加固了几个相邻的失败路径:

  • 解密失败优雅降级:解密失败现在返回undefined而不是抛异常;
  • 无效令牌自愈:检测到无效的 auth token 时自动刷新;
  • 存储缺失自动恢复:如果 profile 认为 state store 存在、但实际上已被删除,会被自动重建。

这三条让 state store 在异常场景下具备自恢复能力,避免因单次解密失败或令牌过期导致整个部署流程中断。

下一步

  • Vectorize 的完整能力与模型预设,可查阅 Cloudflare 官方 Vectorize 文档(发布说明中已给出链接);
  • 完整变更记录见仓库根目录CHANGELOG.mdv2.0.0-beta.46条目);
  • 版本间差异可对照v2.0.0-beta.45 → v2.0.0-beta.46的 diff 查看。

感谢社区贡献者 David J. Felix 与 John Royal 对本功能的贡献(PR #407),以及 state store 修复(PR #477)。

【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询