向量工作流与复合检索

向量工作流与复合检索 #

本文完整介绍在 Easysearch 中使用向量检索的全流程——从索引设计、向量写入到复合查询。

以下 mapping 和查询默认使用 Easysearch 2.4.0 原生 HNSW,不需要安装 k-NN 插件。旧插件索引的字段与查询语法不同, 参考 旧插件向量搜索指南

相关指南(先读这些) #

索引设计 #

创建向量索引 #

PUT knowledge_base
{
  "settings": {
    "number_of_shards": 2,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart"
      },
      "content": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "content_vector": {
        "type": "dense_vector",
        "dims": 768,
        "index": true,
        "similarity": "cosine",
        "index_options": {
          "type": "hnsw",
          "m": 16,
          "ef_construction": 100
        }
      },
      "category": { "type": "keyword" },
      "created_at": { "type": "date" }
    }
  }
}

向量字段参数 #

参数说明
dims向量维度,必须与 Embedding 模型输出维度一致
index2.4.0 原生 dense_vector 必须显式设置为 true
similaritycosinedot_productl2_normmax_inner_product
index_options.type2.4.0 必须显式设置为 hnsw
mHNSW 图中每个节点保留的最大连接数
ef_construction构图候选队列大小

向量写入 #

方式一:Ingest Pipeline 自动向量化 #

该方式需要安装 AI 插件。配置 Ingest Pipeline 在写入时自动调用 Embedding 服务;dims 必须与目标 dense_vector mapping 和模型 输出一致:

PUT _ingest/pipeline/vectorize
{
  "processors": [
        {
          "text_embedding": {
            "url": "https://api.openai.com/v1/embeddings",
            "vendor": "openai",
            "api_key": "<api_key>",
            "text_field": "content",
            "vector_field": "content_vector",
            "model_id": "text-embedding-3-small",
            "dims": 768,
            "ignore_missing": false,
            "ignore_failure": false
          }
    }
  ]
}

写入时指定 pipeline:

POST knowledge_base/_doc?pipeline=vectorize
{
  "title": "Easysearch 集群配置",
  "content": "本文介绍 Easysearch 集群的配置方法和最佳实践...",
  "category": "tutorial"
}

方式二:应用侧预计算 #

在应用中先调用 Embedding 服务获取向量,再连同向量一起写入:

POST knowledge_base/_doc
{
  "title": "Easysearch 集群配置",
  "content": "本文介绍 Easysearch 集群的配置方法和最佳实践...",
  "content_vector": [0.12, -0.34, 0.56, ...],
  "category": "tutorial"
}

批量回填历史数据 #

对于已有的大量文本数据,可以用批处理脚本分批向量化并写入:

from elasticsearch import Elasticsearch, helpers

es = Elasticsearch(["https://localhost:9200"], basic_auth=("admin", "pwd"), verify_certs=False)

# 分批读取现有文档
docs = helpers.scan(es, index="knowledge_base", query={"query": {"match_all": {}}})

actions = []
for doc in docs:
    vector = embedding_model.encode(doc["_source"]["content"])
    actions.append({
        "_op_type": "update",
        "_index": "knowledge_base",
        "_id": doc["_id"],
        "doc": {"content_vector": vector.tolist()}
    })
    if len(actions) >= 100:
        helpers.bulk(es, actions)
        actions = []

复合查询策略 #

query-level knn 可以与 match 放进同一个 bool 查询,兼顾精确匹配和语义理解。这是复合查询,不是 混合搜索。文档中的 Hybrid 只指搜索管道 RRF。

基础复合查询 #

POST knowledge_base/_search
{
  "size": 10,
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "content_vector",
            "query_vector": [0.12, -0.34, ...],
            "k": 10,
            "num_candidates": 50
          }
        }
      ],
      "should": [
        {
          "match": {
            "content": {
              "query": "集群健康检查",
              "boost": 0.3
            }
          }
        }
      ]
    }
  }
}

权重调优 #

可以分别设置 knnmatch 子句的 boost,调整两类分数对最终排序的相对影响。BM25 与向量分数不在同一分布,boost 不是 百分比权重。应使用一组标注好的查询-文档对评估 Precision@K、Recall@K、NDCG 等指标,再确定业务所需的参数。