向量化(向量数据库)

访问向量数据库,在 Nuxt 中构建全栈 AI 驱动的应用程序。
了解 Cloudflare 文档中的向量数据库。
向量化仅在使用 远程存储 时,在本地开发中可用。

入门

向量化索引在您的 NuxtHub 项目中,在 nuxt.config.ts 文件中的 hub.vectorize 对象中管理。可以使用单独的键创建多个索引。

nuxt.config.ts
export default defineNuxtConfig({
  hub: {
    vectorize: {
      <index-name>: {
        dimensions: <number>,
        metric: "dot-product" | "cosine" | "euclidean",
        metadataIndexes: {
          <property>: "string" | "number" | "boolean"
        }
      }
    }
  }
})

然后,确保 部署您的项目 以创建索引。

部署后,您可以使用 hubVectorize() 服务器组合,使用 远程存储 与向量数据库交互,使用 npx nuxt dev --remote 命令。

Cloudflare 向量化索引配置在创建后是不可变的。修改索引的维度大小或距离度量会将修改后的现有索引与您的已部署项目断开连接,并创建一个新的索引。现有数据不会迁移。在创建之前仔细考虑您的索引配置,以避免数据丢失和重新连接问题。

创建索引

创建索引需要三个输入

  • 一个名称,例如 prod-search-index 或 recommendations-idx-dev。
  • 每个向量的(固定)维度大小,例如 384 或 1536。
  • 用于计算向量相似性的(固定)距离度量
索引名称只能包含小写字符、连字符 (-),并且限制为 51 个字符。
Cloudflare 向量化 索引将在您 部署它 时为您项目创建。创建的向量化索引包含一个唯一的 4 个字符后缀。

使用现有索引

  1. 在 Cloudflare 仪表板 → Workers & Pages → 您的 Pages 项目
  2. 转到设置 → 绑定 → 添加
  3. 选择向量化数据库
    • 将变量名称设置为 VECTORIZE_<NAME>。整个变量名称应大写。
    • 选择现有的向量化索引
  4. 将索引配置添加到 nuxt.config.ts 中的 hub.vectorize。名称应与变量名称中使用的 <name> 相匹配,并且应为小写。

创建元数据索引

向量化允许您在索引中为每个向量添加最多 10 KiB 的元数据,并且还提供了在查询向量时对该元数据进行过滤的功能。为此,您需要将元数据字段指定为您的向量化索引的“元数据索引”。

了解有关 创建元数据索引 的更多信息。

hubVectorize()

服务器组合,返回一个 向量化索引

const index = hubVectorize("<index>")

IntelliSense 将根据 hub.vectorize 中配置的索引建议 <index>

insert()

将向量插入索引。

const vectorsToInsert = [
  { id: "123", values: [32.4, 6.5, 11.2, 10.3, 87.9] },
  { id: "456", values: [2.5, 7.8, 9.1, 76.9, 8.5] },
];
const inserted = await index.insert(vectorsToInsert);

查看 向量对象 上的所有可用属性。

如果具有相同向量 ID 的向量已存在于索引中,则只会插入具有新 ID 的向量。

向量化插入是异步的,插入操作返回该操作的唯一突变标识符。插入的向量通常需要几秒钟才能在索引中可用以查询。

如果您需要更新现有向量,请使用 upsert 操作。

插入与更新
  • 如果在向量化索引中两次插入相同的向量 ID,则索引将反映第一个添加的向量。
  • 如果在向量化索引中两次更新相同的向量 ID,则索引将反映第二个添加的向量。
  • 如果您想覆盖已存在于索引中的向量 ID 的向量值,请使用更新操作。

返回值

返回 VectorizeAsyncMutation

upsert()

将向量更新插入索引。

const vectorsToUpsert = [
  { id: "123", values: [32.4, 6.5, 11.2, 10.3, 87.9] },
  { id: "456", values: [2.5, 7.8, 9.1, 76.9, 8.5] },
  { id: "768", values: [29.1, 5.7, 12.9, 15.4, 1.1] },
];
const upserted = await index.upsert(vectorsToUpsert);

更新插入操作将在向量 ID 不存在的情况下将向量插入索引,并覆盖具有相同 ID 的向量。

查看 向量对象 上的所有可用属性。

更新插入不会将现有向量的值或元数据与更新插入的向量合并或组合:更新插入的向量会完全替换现有向量。
向量化更新插入是异步的,更新插入操作返回该操作的唯一突变标识符。更新插入的向量通常需要几秒钟才能在索引中可用以查询。

返回值

返回 VectorizeAsyncMutation

query()

使用提供的向量查询索引,返回基于配置的距离度量的最近向量的分数。

const queryVector = [32.4, 6.55, 11.2, 10.3, 87.9];
const matches = await index.query(queryVector);

console.log(matches)
/*
{
    "count": 5,
    "matches": [
        { "score": 0.999909486, "id": "5" },
        { "score": 0.789848214, "id": "4" },
        { "score": 0.720476967, "id": "1234" },
        { "score": 0.463884663, "id": "6" },
        { "score": 0.378282232, "id": "1" }
    ]
}
*/

查询索引或向量搜索使您能够通过提供一个输入向量来搜索索引,并返回基于 配置的距离度量 的最近向量。

可选地,您可以应用 元数据过滤器命名空间 来缩小向量搜索空间。

参数

vector必需
数组

将用于驱动相似性搜索的输入向量。

options
对象

查询选项。

const matches = await index.query(queryVector, {
  topK: 5,
  returnValues: true,
  returnMetadata: "all",
});

返回值

返回 VectorizeMatches

对评分精度和查询精度的控制

在查询向量时,您可以指定使用高精度评分,从而提高查询匹配分数的精度以及查询结果的准确性,或者使用近似评分来加快响应时间。使用近似评分,返回的分数将是您的查询与返回的向量之间实际距离/相似性的近似值。

通过在查询中设置 returnValues: true 来启用高精度评分;这告诉向量化获取并使用匹配项的原始向量值,这使得能够计算匹配项的精确分数,从而提高结果的准确性。

getByIds()

通过其 ID 检索指定的向量,包括值和元数据。

const ids = ["11", "22", "33", "44"];
const vectors = await index.getByIds(ids);

参数

ids必需
字符串[]

应返回的向量 ID 列表。

返回值

返回 VectorizeVector

deleteByIds()

从当前索引中删除提供的向量 ID。

const idsToDelete = ["11", "22", "33", "44"];
const deleted = await index.deleteByIds(idsToDelete);
向量化删除是异步的,删除操作返回该操作的唯一突变标识符。向量通常需要几秒钟才能从向量化索引中删除。

参数

ids必需
字符串[]

应删除的向量 ID 列表。

返回值

返回 VectorizeAsyncMutation

describe()

直接检索给定索引的配置,包括其配置的 dimensions 和距离 metric

const details = await index.describe();

返回值

返回 VectorizeIndexDetails

向量

向量对象

向量表示机器学习模型的向量嵌入输出。

id必需
字符串

在索引中标识向量的唯一 string。这应映射回生成向量值的文档、对象或数据库标识符的 ID。

namespace
对象

索引中的可选分区键。操作在每个命名空间执行,因此这可用于在较大索引中创建隔离段。

values必需
number[] | Float32Array | Float64Array

作为向量嵌入本身的 numberFloat32ArrayFloat64Array 数组。这必须是一个密集数组,并且此数组的长度必须与索引上配置的 dimensions 相匹配。

metadata
Record<string, 'string' | 'number' | 'boolean'>

一组可选的键值对,可用于与向量一起存储附加元数据。

const vectorExample = {
    id: "12345",
    values: [32.4, 6.55, 11.2, 10.3, 87.9],
    metadata: {
        key: "value",
        hello: "world",
        url: "r2://bucket/some/object.json",
    },
};

维度

维度由用于生成它们的机器学习 (ML) 模型的输出大小决定,并且是模型如何将特征编码和描述为向量嵌入的函数。

输出维度的数量可以决定向量搜索精度、搜索性能(延迟)以及索引的总体大小。较小的输出维度可能更快地搜索,这对于面向用户的应用程序很有用。较大的输出维度可以提供更准确的搜索,尤其是在更大的数据集或输入非常相似的数据集上。

创建索引的维度数不可更改。索引期望接收具有相同维度数的密集向量。

下表重点介绍了一些示例嵌入模型及其输出维度

模型 / 嵌入 API输出维度用例
Workers AI - @cf/baai/bge-base-en-v1.5768文本
OpenAI - ada-0021536文本
Cohere - embed-multilingual-v2.0768文本
Google Cloud - multimodalembedding1408多模态(文本、图像)
请参阅 Workers AI 文档,了解其内置嵌入模型。

距离度量

距离度量是用来判断向量之间距离的函数。向量化索引支持以下距离度量

度量详情
余弦距离从 -1(最不相似)到 1(完全相同)测量。 0 表示正交向量。
欧几里得欧几里得 (L2) 距离。 0 表示相同向量。正数越大,向量之间的距离越远。
点积负点积。较大的负值 *或* 较小的正值表示更相似的向量。分数 -1000-500 更相似,分数 1550 更相似。

确定向量之间的相似性可能是主观的,取决于用于表示结果向量嵌入中特征的机器学习模型。例如,使用 cosine 度量时,分数 0.8511 表示两个向量距离很近,但它们所代表的数据是否 *相似* 是模型能够代表原始内容的程度的函数。

查询向量时,您可以指定 Vectorize 使用以下任一项

  • 高精度评分,这将提高查询匹配分数的精度以及查询结果的准确性。
  • 为了更快地响应时间,使用近似评分。使用近似评分,返回的分数将是您的查询与返回向量之间真实距离/相似度的近似值。请参考 控制评分精度和查询准确性

距离度量不能在索引创建后更改,并且每个度量都有不同的评分函数。

支持的向量格式

Vectorize 支持三种格式的向量

在大多数情况下,在处理其他 API 时,number[] 数组是最简单的,也是大多数机器学习 API 的返回类型。

元数据 & 过滤

元数据是一组可选的键值对,可以在插入或更新时附加到向量,并允许您嵌入或共置有关向量本身的数据。

元数据键不能为空,不能包含点字符 (.),不能包含双引号字符 ("),也不能以美元字符 ($) 开头。

元数据可用于

  • 包含对象存储密钥、数据库 UUID 或其他标识符以查找向量嵌入所代表的内容。
  • 原始内容(最多 元数据限制),这可以让您跳过对较小内容的额外查找。
  • 日期、时间戳或其他描述向量嵌入生成时间或生成方式的元数据。

例如,表示图像的向量嵌入可以包含生成它的 blob 的路径、格式和类别查找

{ id: '1', values: [32.4, 74.1, 3.2, ...], metadata: { path: 'r2://bucket-name/path/to/image.png', format: 'png', category: 'profile_image' } }

元数据过滤

除了向查询提供输入向量之外,您还可以通过 与每个向量关联的向量元数据 进行过滤。查询结果仅包含与 filter 条件匹配的向量,这意味着 filter 首先应用,然后从过滤后的集合中获取 topK 结果。

通过使用元数据过滤来限制查询范围,您可以根据特定客户 ID、租户、产品类别或您与向量关联的任何其他元数据进行过滤。

Vectorize 要求在插入向量之前指定元数据索引以支持元数据过滤。支持 stringnumberboolean 元数据索引。请参考 创建元数据索引 以获取详细信息。

Vectorize 默认支持 命名空间 过滤。

创建元数据索引

元数据索引都在 nuxt.config.ts 中的索引配置中的 metadataIndexes 对象中进行管理。

nuxt.config.ts
export default defineNuxtConfig({
  hub: {
    vectorize: {
      tutorial: {
        dimensions: 32,
        metric: "cosine",
        metadataIndexes: {
          // <property>: "string" | "number" | "boolean"
          url: "string",
          "nested.property": "boolean"
        }
      }
    }
  }
})

支持的元数据索引属性类型是 stringnumberboolean 类型。

使用 .(点)定义嵌套属性。

Vectorize 目前每个 Vectorize 索引最多支持 10 个元数据索引。在 https://developers.cloudflare.com/vectorize/platform/limits/ 上了解更多信息。

对于类型为数字的元数据索引,索引的数字精度为 float64。

对于类型为字符串的元数据索引,每个向量都会索引字符串数据的头 64B,在 UTF-8 字符边界上截断,以该限制内的最长格式良好的 UTF-8 子字符串为准,因此向量可对每个索引属性的值的前 64B 进行过滤。

截至目前,需要在插入向量之前指定向量可用于过滤的元数据字段,建议在创建 Vectorize 索引后立即指定这些元数据字段。

支持的操作

query() 方法上的可选 filter 属性指定元数据过滤器

操作符描述
$eq等于
$ne不等于
  • filter 必须是非空对象,其紧凑 JSON 表示必须小于 2048 字节。
  • filter 对象键不能为空,不能包含 " | .(点用于嵌套),不能以 $ 开头,也不能超过 512 个字符。
  • filter 对象非嵌套值可以是 stringnumberbooleannull 值。

有效的 filter 示例

隐式 $eq 运算符
{ "streaming_platform": "netflix" }
显式运算符
{ "someKey": { "$ne": true } }
隐式逻辑 AND,带有多个键
{ "pandas.nice": 42, "someKey": { "$ne": true } }
键使用 .(点)定义嵌套
{ "pandas.nice": 42 }

// looks for { "pandas": { "nice": 42 } }

限制

您每个向量可以存储最多 10KiB 的元数据,每个 Vectorize 索引可以创建最多 10 个元数据索引。

对于类型为 number 的元数据索引,索引的数字精度为 float64。

对于类型为 string 的元数据索引,每个向量都会索引字符串数据的头 64B,在 UTF-8 字符边界上截断,以该限制内的最长格式良好的 UTF-8 子字符串为准,因此向量可对每个索引属性的值的前 64B 进行过滤。

有关限制的完整列表,请参见 Vectorize 限制

示例

添加元数据

使用以下索引定义

nuxt.config.ts
export default defineNuxtConfig({
  hub: {
    vectorize: {
      tutorial: {
        dimensions: 32,
        metric: "cosine",
        metadataIndexes: {
          streaming_platform: "string",
          "property.nested": "boolean"
        }
      }
    }
  }
})

可以在 插入或更新向量 时添加元数据。

const index = hubVectorize("tutorial")

const newMetadataVectors: Array<VectorizeVector> = [
    {
        id: "1",
        values: [32.4, 74.1, 3.2, ...],
        metadata: { url: "/products/sku/13913913", streaming_platform: "netflix" },
    },
    {
        id: "2",
        values: [15.1, 19.2, 15.8, ...],
        metadata: { url: "/products/sku/10148191", streaming_platform: "hbo" },
    },
    {
        id: "3",
        values: [0.16, 1.2, 3.8, ...],
        metadata: { url: "/products/sku/97913813", streaming_platform: "amazon" },
    },
    {
        id: "4",
        values: [75.1, 67.1, 29.9, ...],
        metadata: { url: "/products/sku/418313", streaming_platform: "netflix" },
    },
    {
        id: "5",
        values: [58.8, 6.7, 3.4, ...],
        metadata: { url: "/products/sku/55519183", streaming_platform: "hbo" },
    },
];

// Upsert vectors with added metadata, returning a count of the vectors upserted and their vector IDs
const upserted = await index.upsert(newMetadataVectors);

查询示例

使用 query() 方法

const queryVector: Array<number> = [54.8, 5.5, 3.1, ...];
const originalMatches = await index.query(queryVector, {
    topK: 3,
    returnValues: true,
    returnMetadata: 'all',
});

没有元数据过滤的结果

{
    "matches": [
        {
            "id": "5",
            "score": 0.999909486,
            "values": [58.79999923706055, 6.699999809265137, 3.4000000953674316],
            "metadata": {
                "url": "/products/sku/55519183",
                "streaming_platform": "hbo"
            }
        },
        {
            "id": "4",
            "score": 0.789848214,
            "values": [75.0999984741211, 67.0999984741211, 29.899999618530273],
            "metadata": {
                "url": "/products/sku/418313",
                "streaming_platform": "netflix"
            }
        },
        {
            "id": "2",
            "score": 0.611976262,
            "values": [15.100000381469727, 19.200000762939453, 15.800000190734863],
            "metadata": {
                "url": "/products/sku/10148191",
                "streaming_platform": "hbo"
            }
        }
    ]
}

具有 filter 属性的相同 query() 方法支持元数据过滤。

const queryVector: Array<number> = [54.8, 5.5, 3.1, ...];
const metadataMatches = await index.query(queryVector, {
    topK: 3,
    filter: { streaming_platform: "netflix" },
    returnValues: true,
    returnMetadata: 'all',
});

带有元数据过滤的结果

{
    "matches": [
        {
            "id": "4",
            "score": 0.789848214,
            "values": [75.0999984741211, 67.0999984741211, 29.899999618530273],
            "metadata": {
                "url": "/products/sku/418313",
                "streaming_platform": "netflix"
            }
        },
        {
            "id": "1",
            "score": 0.491185264,
            "values": [32.400001525878906, 74.0999984741211, 3.200000047683716],
            "metadata": {
                "url": "/products/sku/13913913",
                "streaming_platform": "netflix"
            }
        }
    ]
}

命名空间

命名空间提供了一种在索引中对向量进行分割的方法。例如,按客户、商家或商店 ID。

要将向量与命名空间关联,您可以在执行插入或更新操作时,选择性地提供 namespace: string 值。查询时,您可以将命名空间作为可选参数传递给查询,以在其中进行搜索。

命名空间的长度最多可达 64 个字符(字节),每个索引最多可以有 1000 个命名空间。有关更多详细信息,请参考 限制 文档。

在查询操作中指定命名空间后,只有该命名空间内的向量才会用于搜索。命名空间过滤在向量搜索之前应用,而不是之后。

插入带有命名空间的向量

// Mock vectors
// Vectors from a machine-learning model are typically ~100 to 1536 dimensions
// wide (or wider still).
const sampleVectors: Array<VectorizeVector> = [
    {
        id: "1",
        values: [32.4, 74.1, 3.2, ...],
        namespace: "text",
    },
    {
        id: "2",
        values: [15.1, 19.2, 15.8, ...],
        namespace: "images",
    },
    {
        id: "3",
        values: [0.16, 1.2, 3.8, ...],
        namespace: "pdfs",
    },
];

// Insert your vectors, returning a count of the vectors inserted and their vector IDs.
const inserted = await index.insert(sampleVectors);

在命名空间内查询向量

// Your queryVector will be searched against vectors within the namespace (only)
const matches = await index.query(queryVector, {
    namespace: "images",
});

命名空间与元数据过滤

两者 命名空间 和元数据过滤都可以缩小查询的向量搜索空间。在评估这两种过滤器类型时,请考虑以下事项

  • 命名空间过滤器在元数据过滤器之前应用。
  • 一个向量只能属于一个命名空间,其中包含记录在案的 限制。向量元数据可以包含多个键值对,最多可以达到 每个向量的元数据限制。元数据值支持不同的类型 (stringboolean 等),因此提供更高的灵活性。

限制

功能当前限制
每个帐户的索引数100 个索引
每个向量的最大维度1536 个维度
向量 ID 的最大长度51 个字节
每个向量的元数据10KiB
带有值或元数据的最大返回结果 (topK)20
没有值和元数据的最大返回结果 (topK)100
最大更新批次大小(每批次)1000
最大索引名称长度64 个字节
每个索引的最大向量数5,000,000
每个索引的最大命名空间数1000 个命名空间
最大命名空间名称长度64 个字节
最大向量上传大小100 MB
每个 Vectorize 索引的最大元数据索引数10
每个向量每个元数据索引的最大索引数据64 个字节

了解有关 Cloudflare Vectorize 限制 的更多信息。

类型

VectorizeVector

参见 向量对象

interface VectorizeVector {
  /** The ID for the vector. This can be user-defined, and must be unique. It should uniquely identify the object, and is best set based on the ID of what the vector represents. */
  id: string;
  /** The vector values */
  values: VectorFloatArray | number[];
  /** The namespace this vector belongs to. */
  namespace?: string;
  /** Metadata associated with the vector. Includes the values of other fields and potentially additional details. */
  metadata?: Record<string, VectorizeVectorMetadata>;
}

VectorizeMatches

特定查询的一组匹配 VectorizeMatch

interface VectorizeMatches {
  matches: VectorizeMatch[];
  count: number;
}

VectorizeMatch

表示查询的匹配向量,以及其分数和(如果指定)匹配向量信息。

type VectorizeMatch = Pick<Partial<VectorizeVector>, "values"> &
  Omit<VectorizeVector, "values"> & {
    /** The score or rank for similarity, when returned as a result */
    score: number;
  };

VectorizeAsyncMutation

结果类型,指示 Vectorize 索引上的变异。实际变异是异步处理的,其中 mutationId 是操作的唯一标识符。

interface VectorizeAsyncMutation {
  /** The unique identifier for the async mutation operation containing the changeset. */
  mutationId: string;
}

VectorizeIndexInfo

有关现有索引的元数据。

interface VectorizeIndexInfo {
  /** The number of records containing vectors within the index. */
  vectorsCount: number;
  /** Number of dimensions the index has been configured for. */
  dimensions: number;
  /** ISO 8601 datetime of the last processed mutation on in the index. All changes before this mutation will be reflected in the index state. */
  processedUpToDatetime: number;
  /** UUIDv4 of the last mutation processed by the index. All changes before this mutation will be reflected in the index state. */
  processedUpToMutation: number;
}

示例

在本例中

  1. 从搜索查询生成嵌入向量。
  2. 使用嵌入向量查询 Vectorize 索引。
  3. 然后通过查询数据库获取 Vectorize 返回的 ID 以检索原始源数据。

https://developers.cloudflare.com/vectorize/reference/what-is-a-vector-database/#vector-search 上了解更多信息

server/api/search.get.ts
import { z } from "zod";

interface EmbeddingResponse {
  shape: number[];
  data: number[][];
}

const Query = z.object({
  query: z.string().min(1).max(256),
  limit: z.coerce.number().int().min(1).max(20).default(10),
});

export default defineEventHandler(async (event) => {
  const { query, limit } = await getValidatedQuery(event, Query.parse);

  // 1. generate embeddings for search query
  const embeddings: EmbeddingResponse = await hubAI().run("@cf/baai/bge-base-en-v1.5", { text: [query] });
  const vectors = embeddings.data[0];

  // 2. query vectorize to find similar results
  const vectorize: VectorizeIndex = hubVectorize('jobs');
  const { matches } = await vectorize.query(vectors, { topK: limit });

  // 3. get details for matching items
  const jobMatches = await useDrizzle().query.jobs.findMany({
    where: (jobs, { inArray }) => inArray(jobs.id, matches.map((match) => match.id)),
    with: {
      department: true,
      subDepartment: true,
    },
  });

  // 4. add score to matches
  const jobMatchesWithScore = jobMatches.map((job) => {
    const match = matches.find((match) => match.id === job.id);
    return { ...job, score: match!.score };
  });

  // 5. sort by score
  return jobMatchesWithScore.sort((a, b) => b.score - a.score);
});

批量生成和导入

本例使用文本嵌入 AI 模型批量生成数据库表中所有数据的向量,使用 Nitro 任务。您可以通过 Nuxt DevTools 运行该任务。

server/tasks/generate-embeddings.ts
import { jobs } from "../database/schema";
import { asc, count } from "drizzle-orm";

export default defineTask({
  meta: {
    name: "vectorize:seed",
    description: "Generate text embeddings vectors",
  },
  async run() {
    console.log("Running Vectorize seed task...");
    const jobCount = (await useDrizzle().select({ count: count() }).from(tables.jobs))[0].count;

    // process in chunks of 100 as that's the maximum supported by workers ai
    const INCREMENT_AMOUNT = 100;

    const totalBatches = Math.ceil(jobCount / INCREMENT_AMOUNT);
    console.log(`Total items: ${jobCount} (${totalBatches} batches)`);

    for (let i = 0; i < jobCount; i += INCREMENT_AMOUNT) {
      console.log(`⏳ Processing items ${i} - ${i + INCREMENT_AMOUNT}...`);

      const jobsChunk = await useDrizzle()
        .select()
        .from(tables.jobs)
        .orderBy(asc(jobs.id))
        .limit(INCREMENT_AMOUNT)
        .offset(i);

      // generate embeddings for job titles
      const ai = hubAi();
      const embeddings = await ai.run(
        "@cf/baai/bge-base-en-v1.5",
        { text: jobsChunk.map((job) => job.jobTitle) },
        { gateway: { id: "new-role" } },
      );
      const vectors = embeddings.data;

      const formattedEmbeddings = jobsChunk.map(({ id, ...metadata }, index) => ({
        id,
        metadata: { ...metadata },
        values: vectors[index],
      }));

      // save vector embeddings to index
      const index = hubVectorize('jobs');
      await index.upsert(formattedEmbeddings);

      console.log(`✅ Processed items ${i} - ${i + INCREMENT_AMOUNT}...`);
    }

    console.log("Vectorize seed task completed!");
    return { result: "success" };
  },
});