# 向量搜索

Typesense 具备索引嵌入向量的能力,这些向量可由任何机器学习模型生成,并支持对这些数据进行最近邻(KNN)搜索。

# 应用场景

以下是可以基于向量搜索构建的典型应用场景:

  1. 语义搜索
  2. 推荐系统
  3. 混合搜索 (关键词搜索 + 语义搜索 + 过滤)
  4. 视觉图像搜索
  5. 与LLM集成 (opens new window),让大语言模型能够基于您的数据集回答问题(RAG)

您还可以将上述功能与过滤、分面、排序、分组等特性结合,打造更友好的搜索体验。

# 什么是嵌入向量?

JSON文档的嵌入向量是一个浮点数数组(例如:[0.4422, 0.49292, 0.1245, ...]),它是文档的另一种数值表示形式。

这些嵌入向量由机器学习模型生成,其特点是"相似"文档(根据所用模型的不同相似度定义)的向量在空间中"更接近"(余弦相似度)。

以下是常用的文档嵌入向量生成模型:

  • Sentence-BERT
  • E-5
  • CLIP
  • OpenAI的文本嵌入模型
  • Google的PaLM API
  • Google的Vertex API

您可以将这些模型生成的嵌入向量导入Typesense的特殊向量字段,然后进行最近邻搜索——输入另一组向量或文档ID,即可获取最相似(余弦相似度)的文档。

Typesense还支持通过OpenAI、PaLM API或内置ML模型 (opens new window)自动生成这些嵌入向量。

# 实时演示

这里展示向量搜索的一个实际应用案例(众多可能性之一)——电商商店中的"查找相似商品"功能:ecommerce-store.typesense.org (opens new window)(点击每个商品下方的Find Similar按钮)。

# 延伸阅读

以下两篇文章更详细地讨论了嵌入(embeddings)技术:

现在我们来讨论如何在Typesense中索引和搜索嵌入向量。

# 索引嵌入向量

# 方案A:将外部生成的嵌入向量导入Typesense

如果您已经在Typesense外部使用自己的模型生成了嵌入向量,可以将其导入到Typesense中。

TIP

这里 (opens new window)提供了一个快速示例,展示如何在Typesense外部使用Sentence-BERT模型生成嵌入向量。

当您的文档嵌入向量准备就绪后,需要创建一个包含float[]类型字段的集合,并设置num_dim属性来索引这些向量。num_dim属性指定了嵌入向量的维度数(浮点数组的长度)。

让我们创建一个名为docs的集合,其中包含一个名为embedding的向量字段,该字段只有4个维度。

TIP

示例中我们创建了4维向量以保持代码片段简洁易读。

实际应用中,根据所用模型的不同,通常需要创建至少256维的向量字段才能获得良好效果。

现在我们来索引一个包含向量的文档。

# 选项 B:Typesense 内自动生成嵌入向量

为了简化嵌入向量生成流程,Typesense 可以自动使用您的 JSON 数据,并通过 OpenAI API、PaLM API 或 此处列出的 (opens new window) 任意内置嵌入模型来生成和存储嵌入向量。

当您对这种自动生成的向量字段进行搜索查询时,Typesense 会使用该字段对应的相同模型将您的搜索查询向量化,从而实现语义搜索,或结合关键词与语义搜索进行混合搜索。

嵌入向量更新

仅当在 embed.from 配置中指定的一个或多个字段发生更新时,才会重新生成嵌入向量。这可以避免文档中其他字段被修改时产生不必要的嵌入向量重建和 API 调用。

# 创建自动嵌入字段

要创建一个能自动嵌入其他字符串或字符串数组字段的字段,您需要设置该字段的 embed 属性。

示例如下:

在这个示例中,embedding 向量字段会在索引文档时自动生成,使用 product_namecategories 字段的拼接值(以空格分隔)。

# 使用内置模型

这些模型由 Typesense 官方支持,存储在 Typesense 的 Hugging Face 仓库中此处 (opens new window)

您可以通过在模型名称前添加 ts 命名空间来指定它们。当您在创建集合后索引文档时,Typesense 会自动下载这些模型并使其可供使用。

当您使用上述模式创建集合时,all-MiniLM-L12-v2 模型将被下载,您的文档将自动通过该模型进行嵌入,并存储在 embedding 字段中。

查看我们的 Hugging Face 仓库 (opens new window)获取所有官方支持的模型列表。 如果您需要支持其他公开可用的模型,欢迎将模型转换为 ONNX 格式并向我们的 Hugging Face 模型仓库 (opens new window)提交 PR。

# 使用 GPU(可选)

运行嵌入模型需要大量计算资源。因此,当使用内置模型时,您可能需要考虑在配备 GPU 的服务器上运行 Typesense,以提高嵌入生成的性能,特别是对于大型数据集。

TIP

GPU 仅用于 生成 嵌入 - 即在索引文档和为搜索词生成嵌入时使用。实际的最近邻向量搜索操作不会使用 GPU。

# 在 Typesense Cloud 上:

对于特定的 RAM/CPU 配置 (opens new window),当您部署新集群或在集群配置 > 修改中(适用于 Typesense 0.25.0 及以上版本),可以找到启用 "GPU 加速" 的选项。

# 自托管时:

请遵循安装指南

# 使用 OpenAI API

你也可以让 Typesense 将 JSON 数据中的特定字段发送到 OpenAI 的 API 来生成文本嵌入向量。

你可以使用 这里 (opens new window) 列出的任何 OpenAI 模型。

当你创建上述集合时,每次索引文档时,我们会调用 OpenAI API 从 product_name 字段生成嵌入向量,并将其存储在 embedding 字段中。

要使用此功能,你必须在 model_config 中提供有效的 OpenAI API 密钥。

# 使用 OpenAI 兼容的 API

你也可以使用像 Azure 这样的 OpenAI-API 兼容 API 提供商,通过在 model_config 中自定义基础 URL 来实现:

当你创建上述集合时,Typesense 会调用运行在 https://your-custom-openai-compatible-api.domain.com 后面的 OpenAI-API 兼容服务器,每次索引文档时从 product_name 字段创建嵌入向量并存储在 embedding 字段中。

model_config 中的 model_name 必须openai 开头。

指定的 URL 后面的自定义 API 服务器应提供以下端点:

端点:

POST /v1/embeddings

请求体:

参数 类型 描述
model string 模型名称
input string 或 string[] 输入字符串或字符串数组

响应体:

{
    "data": [
        {
            "embedding": [
                       ....
            ]
        }
    ]
}

响应体可能包含额外数据,但嵌入向量必须以上述格式返回。

错误响应体:

{
  "error": {
    "message": "错误信息",
    "type": "错误类型",
    "param": null,
    "code": "错误代码"
  }
}

# 使用 Google PaLM API

该 API 由 Google MakerSuite (opens new window) 提供,用于生成嵌入向量。

注意: 目前唯一支持的模型是 embedding-gecko-001

# 更新远程模型API密钥

您可以在不重新创建集合的情况下,更新用于远程嵌入模型(如OpenAI)的API密钥:

WARNING

注意:更新请求中必须包含所有字段参数(nameembed.frommodel_config 参数)。

# 使用 GCP Vertex AI API

该 API 同样由 Google 在 Google Cloud Platform (GCP) 平台下提供。

使用此方法需要以下认证信息:

  • GCP 访问令牌(创建字段时必须有效)
  • GCP 刷新令牌
  • GCP 应用客户端 ID
  • GCP 应用客户端密钥
  • GCP 项目 ID

有关如何获取这些值的更多信息,请参阅 Vertex AI 文档。

# 远程嵌入 API 参数

您可以使用以下任意参数来微调对远程嵌入服务的 API 调用:

# 搜索期间

参数 描述 默认值
remote_embedding_timeout_ms 在搜索过程中,调用远程嵌入服务API的超时等待时间(毫秒) 30秒
remote_embedding_num_tries 在搜索过程中,调用远程嵌入服务API失败时的重试次数 2

# 索引期间

参数 描述 默认值
remote_embedding_batch_size 批量导入文档时每次发送到远程 API 的最大批次大小。使用较小的值可降低超时风险,但会增加请求次数。 200
remote_embedding_timeout_ms 索引期间调用远程嵌入服务 API 的超时等待时间(毫秒)。 60000
remote_embedding_num_tries 索引期间远程嵌入服务 API 调用失败时的重试次数。 2

# 使用自定义模型

您也可以在 Typesense 内部使用自己的模型来生成嵌入向量。这些模型必须采用 ONNX 文件格式。

<data_dir>/models 目录下创建一个子目录,将您的 ONNX 模型文件、词汇表文件和模型配置 JSON 文件存放在其中。

注意: 您的模型文件必须命名为 model.onnx,配置文件必须命名为 config.json

# 模型配置文件

该文件将包含您要使用的模型类型信息。

JSON 文件必须包含 model_type(模型类型;目前我们支持 bertxlm_roberta)和 vocab_file_name 键。

目录结构:

<data_dir>/models/test_model/model.onnx
<data_dir>/models/test_model/vocab.txt
<data_dir>/models/test_model/config.json

config.json 文件内容:

{
    "model_type": "bert",
    "vocab_file_name": "vocab.txt"
}

使用目录名称作为 model_config 中的 model_name 创建 embedding 字段。

# 可选模型参数

这些是可选的模型参数,可能需要与您的自定义模型一起使用。

# 索引前缀与查询前缀

某些模型可能需要前缀来区分文本是查询语句还是待查询的实际文本(例如可以查看 intfloat/e5-small 模型)。

如果在 model_config 中设置此属性,当索引文档时,给定的 indexing_prefix 会被添加到用于生成嵌入向量的文本前;而在生成查询的嵌入向量前,query_prefix 会被添加到实际查询前。示例:

对于这个示例,当你索引文档时:

{
   "product_name": "ABCD"
}

embedding 字段生成嵌入向量时使用的文本将是 passage: ABCD 而非 ABCD。而当查询时,如果你的查询是 EFGH,它将被嵌入为 query: EFGH 而非 EFGH

# 最近邻向量搜索

当您将嵌入向量索引到向量字段后,现在可以搜索与给定查询向量"最接近"的文档。

要控制返回的文档数量,您可以使用 per_page 分页参数或向量查询中的 k 参数。

# 分页

要对向量搜索(或语义搜索/混合搜索)结果进行分页处理,可以使用 vector_search 中的 k 参数限制结果数量(例如 embedding:([], k: 200)),设置 per_page 控制每页结果数量,并使用 page 参数进行分页导航:











 
 
 




curl 'http://localhost:8108/multi_search' \
  -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
  -X POST \
  -d '{
    "searches": [
      {
        "q": "device to type things on",
        "query_by": "embedding",
        "collection": "products",
        "exclude_fields": "embedding",
        "vector_query": "embedding:([], k: 200)",
        "per_page": 10,
        "page": 1
      }
    ]
  }'

这种方式会先获取最接近的200个向量搜索结果,然后在该结果集中进行分页,每页返回10条结果。您可以通过递增 page 参数来获取后续页面的结果。

采用这种设计是因为在向量搜索中,数据集中的每个项目在技术上都是某种程度的"匹配"。因此我们需要通过 k 参数设置数量上限(如上所述)和/或使用 vector_querydistance_threshold 参数来限制相似度的上限。

# 查询相似文档

如果您有一个特定文档 id,想要查找与该文档"相似"的其他文档,可以直接通过引用该 id 进行向量查询。

curl 'http://localhost:8108/multi_search' \
  -X POST \
  -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
  -d '{
        "searches": [
          {
            "collection": "docs",
            "q": "*",
            "vector_query": "embedding:([], id: foobar)"
          }
        ]
      }'


# 请确保将 `embedding` 替换为您存储嵌入向量的字段名称。

通过指定一个空查询向量 [] 并传递 id 参数,该查询将返回所有 embedding 值与 foobar 文档的 embedding 值最接近的文档。

:::提示 查询结果中不会包含 foobar 文档本身。 :::

# 语义搜索

当使用自动嵌入功能时,你可以直接将 query_by 设置为自动嵌入字段,对该字段进行语义搜索。

Typesense 会使用生成该自动嵌入字段时相同的嵌入模型,为 q 参数生成向量,然后在内部执行最近邻搜索。

这将自动使用与 embedding 字段相同的模型对 chair 查询进行嵌入,并执行最近邻向量搜索。

当使用 自动嵌入 功能时,您可以将 query_by 设置为同时包含常规字段和自动嵌入字段的列表,从而在多个字段上进行混合搜索。

Typesense 会在所有常规字段上执行关键词搜索,在自动嵌入字段上执行语义搜索,然后使用 Rank Fusion(排名融合)算法合并结果,最终得出用于排序匹配项的 融合分数

K = 文档在关键词搜索中的排名
S = 文档在语义搜索中的排名

rank_fusion_score = 0.7 * K + 0.3 * S

0.70.3 的权重值可以通过 alpha 参数 进行调整。

:::提示 在混合搜索中,sort_by 中的 _text_match 子句将指向综合的融合分数。 :::

如果您没有使用自动嵌入功能,而是通过外部方式填充嵌入字段,仍然可以通过 vector_query 参数手动传递查询字符串的嵌入向量来进行混合搜索。

curl 'http://localhost:8108/multi_search' \
    -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
    -X POST \
    -d '{
          "searches": [
            {
              "q": "chair",
              "query_by": "product_name,embedding",
              "vector_query": "embedding:([0.2, 0.4, 0.1])",
              "sort_by": "_text_match:desc"
            }
          ]
        }'

Typesense 会使用 q 参数执行关键词搜索,同时使用 vector_query 字段执行最近邻搜索,然后按照前面描述的排名融合算法将结果合并为一个排序后的结果集。

性能提示

如果您预期用户在混合搜索时会使用多词查询(例如在 对话式搜索 场景中常见),建议设置 drop_tokens_threshold: 0 作为额外的搜索参数,以避免冗余的内部关键词搜索和过高的 CPU 使用率。更多关于此参数的作用,请参阅 此表格 下的说明。

# 语义搜索与关键词搜索的权重分配

默认情况下,Typesense 为向量搜索排名分配 0.3 的权重,为关键词搜索排名分配 0.7 的权重。您可以通过 vector_query 参数的 alpha 选项来调整向量搜索排名的权重。

例如,要将向量搜索排名权重设为 0.8,可将 alpha 设为 0.8

curl 'http://localhost:8108/multi_search' \
    -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
    -X POST \
    -d '{
          "searches": [
            {
              "collection": "products",
              "query_by": "embedding,product_name",
              "q": "chair",
              "vector_query": "embedding:([], alpha: 0.8)",
              "exclude_fields": "embedding"
            }
          ]
        }'

:::提示 当同时查询嵌入字段和常规搜索字段时,某些参数如 query_by_weights 不会对 query_by 中提到的嵌入字段产生影响。但由于 query_by_weights 的长度必须与 query_by 的长度匹配,您可以使用占位值如 0。 :::

# 混合匹配结果的重排序

默认情况下,在混合搜索中:

  • 通过关键词搜索找到但未通过向量搜索找到的文档将只有文本匹配分数
  • 通过向量搜索找到但未通过关键词搜索找到的文档将只有向量距离分数

您可以通过在搜索参数中设置 rerank_hybrid_matches: true 来为所有匹配项计算这两个分数。启用后:

  • 仅通过关键词搜索找到的文档也会获得向量距离分数
  • 仅通过向量搜索找到的文档也会获得文本匹配分数

这样可以在额外的计算时间成本下,实现更全面的结果排序。

示例:

响应中的每个匹配项都将包含 text_match_infovector_distance 分数,无论它最初是通过关键词搜索还是向量搜索找到的。

# 距离阈值

您还可以为语义搜索和混合搜索的结果设置最大向量距离阈值。为此,您需要在 vector_query 参数中设置 distance_threshold

# 基于向量距离的混合匹配排序

若需同时获取关键词搜索和向量搜索的匹配结果,但仅按向量距离排序,可在 sort_by 参数中使用特殊排序关键字 _vector_distance

示例如下:

{
  "q": "chair",
  "query_by": "title,embedding",
  "sort_by": "popularity_score:desc,_vector_distance:asc"
}

此例中,我们同时在 title 文本字段和 embedding 向量字段上进行搜索,但最终结果会先按 popularity_score 降序排列,再按向量距离升序排列。

# 基于历史查询的搜索

通过 queries 参数发送搜索查询列表,可以让向量搜索从这些查询中计算加权查询嵌入向量。此功能可用于根据用户历史搜索查询个性化搜索结果。

以下示例中,向量搜索使用的嵌入向量是由查询 smart phoneapple ipad 的嵌入向量计算得出。

我们还可以使用可选的 query_weights 参数为查询分配权重。若不传递 query_weights 参数,所有查询将具有相同权重。

{
  "vector_query": "embedding:([], queries:[smart phone, apple ipad], query_weights:[0.9, 0.1])"
}

# 通过向量搜索重排关键词搜索结果

除了合并关键词搜索和向量搜索的评分,您还可以将向量搜索距离作为排序条件来重新排列关键词搜索结果。

下例中,我们将向量距离作为文本匹配分数之后的次要排序条件:

{
  "q": "shoes",
  "query_by": "title",
  "sort_by": "_text_match:desc,_vector_query(embedding:([])):asc"
}

# 暴力搜索(Brute-force searching)

默认情况下,Typesense 使用内置的 HNSW 索引进行近似最近邻向量搜索。这种方式对于大型数据集具有很好的扩展性。不过,如果您希望绕过 HNSW 索引,直接进行平面/暴力排序的向量搜索,可以通过 flat_search_cutoff 参数实现。

例如,当某个查询匹配到的文档数量少于 20 个时,如果您希望进行暴力向量搜索,发送 flat_search_cutoff=20 参数将在结果数量小于 20 时绕过 HNSW 索引。

以下示例中,我们对 category 字段进行过滤,并指定当过滤操作产生的结果数量少于 20 条时,使用直接的平面搜索方式进行向量搜索。

curl 'http://localhost:8108/multi_search' \
      -X POST \
      -H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
      -d '{
            "searches": [
              {
                "collection": "docs",
                "q": "*",
                "filter_by": "category:shoes",
                "vector_query": "embedding:([0.96826, 0.94, 0.39557, 0.306488], k:100, flat_search_cutoff: 20)"
              }
            ]
          }'


# 请确保将 `embedding` 替换为您存储嵌入向量的字段名称。

# 配置 HNSW 参数

# 索引参数

在创建集合时,您可以为向量和嵌入字段设置 ef_construction(默认值:200)和 M(默认值:16)参数。

{
  "name": "docs",
  "fields": [
    {
      "name": "vec",
      "type": "float[]",
      "num_dim": 768,
      "hnsw_params": {
        "ef_construction": 100,
        "M": 8
      }
    }
  ]
}

# 搜索参数

您可以通过 vector_query 参数设置自定义的 ef 值(默认值为 10)。

{
  "vector_query" : "vec:([], ef:100)"
}

# 距离度量标准

默认情况下,Typesense 使用余弦相似度(cosine similarity)作为向量搜索的距离度量标准。当使用 distance_threshold 参数时,余弦距离大于阈值的文档将会:

  • 在独立向量搜索中:被排除在结果之外
  • 当用于排序(sort_by)时:获得最大可能距离分数但仍保留在结果中

该参数可同时适用于余弦相似度和内积(inner product)距离度量标准。例如:

{
  "vector_query": "embedding:([], distance_threshold: 0.30)"
}

这有助于过滤掉相关性较低的结果,同时仍允许其他排序条件生效。

# 向量搜索参数

以下是您可以在 vector_query 搜索参数中使用的所有可能参数,这些参数我们在前面的各个章节中已经介绍过:

参数名称 描述 示例
k 返回的最近邻数量 k:100
id 用于查找相似文档的文档ID id:foobar
alpha 混合搜索中向量搜索排名的权重 alpha:0.8
distance_threshold 结果的最大向量距离阈值 distance_threshold:0.30
queries 用于计算加权查询嵌入的历史搜索查询列表 queries:[smart phone, apple ipad]
query_weights 对应查询的权重 query_weights:[0.9, 0.1]
flat_search_cutoff 绕过HNSW索引进行暴力搜索的阈值 [] flat_search_cutoff:20
ef 自定义HNSW搜索参数 ef:100

您可以像下面这个例子一样,在 vector_query 中使用上述每个参数:

{
  "vector_query": "vector_field_name:([], k: 100, alpha: 0.8, distance_threshold:0.30)"
}

# UI 示例

  • 这里 (opens new window) 是一个演示,展示如何使用 Typesense 内置的嵌入生成机制实现混合搜索(语义搜索 + 关键词搜索 + 过滤 + 分面)。

  • 这里 (opens new window) 是一个演示,展示如何在电商商店中使用向量搜索实现"查找相似商品"功能。

    点击每个商品图块下方的"Find Similar"查看实现说明。

  • 这里 (opens new window) 是一个演示,展示如何使用外部嵌入 API 和向量搜索实现语义搜索。