如何使用ES的同义词API自动化同义词生成和上传

为了提供高效的用户体验,提升搜索结果的质量至关重要。优化搜索的一种方法是通过同义词自动扩展查询词。这种方式可以更广泛地解释查询,涵盖语言变化,从而提高结果匹配的准确性。

本文探讨了如何使用大语言模型(LLM)自动识别和生成同义词,并将这些词以编程方式加载到Elasticsearch的同义词API中。

什么时候使用同义词?

使用同义词可以成为比向量搜索更快捷和经济高效的解决方案。其实现更为简单,因为不需要深入了解嵌入或复杂的向量摄取过程。此外,资源消耗也较低,因为向量搜索需要更大的存储容量和内存来进行嵌入索引和检索。

另一个重要方面是搜索的区域化。通过同义词,可以根据当地语言和习惯调整术语。这在嵌入可能无法匹配区域表达或特定国家术语的情况下非常有用。例如,一些词或缩写可能在不同地区有不同的含义,但对当地用户来说自然被视为同义词。在巴西,这种情况很常见。"Abacaxi" 和 "ananás" 都是菠萝,但第二个词在东北的一些地区更常使用。同样,东南地区著名的“pão francês”在东北可能被称为“pão careca”。

如何使用LLM生成同义词?

为了自动获取同义词,我们可以使用LLM,这些模型通过分析术语的上下文来建议合适的变体。这种方法允许动态扩展同义词,确保更广泛和准确的搜索,而不依赖固定词典。

在这个演示中,我们将使用LLM为电商产品生成同义词。许多搜索由于查询词的变化而返回很少甚至没有结果。通过同义词,我们可以解决这个问题。例如,搜索“智能手机”可以涵盖不同型号的手机,确保用户找到他们想要的产品。

预备条件

开始之前,我们需要设置环境并定义所需的依赖项。我们将使用Elastic提供的解决方案在Docker中本地运行Elasticsearch和Kibana。代码将用Python v3.9.6编写,并需以下依赖:

代码语言:bash复制
pip install openai==1.59.8 elasticsearch==8.15.1

创建产品索引

最初,我们将创建一个不支持同义词的产品索引。这将允许我们验证查询,然后与包含同义词的索引进行比较。

在Kibana DevTools中使用以下命令批量加载产品数据集来创建索引:

代码语言:json复制
POST _bulk
{"index": {"_index": "products", "_id": 10001}}
{"category": "Electronics", "name": "iPhone 14 Pro"}
{"index": {"_index": "products", "_id": 10007}}
{"category": "Electronics", "name": "MacBook Pro 16-inch"}
{"index": {"_index": "products", "_id": 10013}}
{"category": "Electronics", "name": "Samsung Galaxy Tab S8"}
...

使用LLM生成同义词

在这一步中,我们将使用LLM动态生成同义词。为此,我们将集成OpenAI API,定义合适的模型和提示。LLM将接收产品的类别和名称,确保同义词在上下文中相关。

代码语言:python代码运行次数:0运行复制
import json
import logging
from openai import OpenAI

def call_gpt(prompt, model):
    try:
        logging.info("通过LLM生成同义词...")
        response = client.chatpletions.create(
            model=model,
            messages=[{"role": "user", "content": prompt}],
            temperature=0.7,
            max_tokens=1000
        )
        content = response.choices[0].message.content.strip()
        return content
    except Exception as e:
        logging.error(f"使用模型失败: {e}")
        return None

def generate_synonyms(category, products):
    synonyms = {}

    for product in products:
        prompt = (
            f"你是产品同义词生成专家。根据提供的类别和产品名称生成同义词或相关术语。请遵循以下规则:\n"
            f"1. **格式**:第一个词应为主要项目(产品名称的一部分,排除品牌),后跟最多3个用逗号分隔的同义词。\n"
            f"2. **排除品牌**:同义词中不要包含品牌名称。\n"
            f"3. **同义词数量**:每个产品最多生成3个同义词。\n\n"
            f"类别为:**{category}**,产品为:**{product}**。只返回请求格式的同义词,不要附加解释。"
        )

        response = call_gpt(prompt, "gpt-4o")
        synonyms[product] = response

    return synonyms

从创建的产品索引中,我们将检索“Electronics”类别下的所有商品,并将其名称发送给LLM。预期输出可能类似于:

代码语言:json复制
{
  "iPhone 14 Pro": ["iPhone", "smartphone", "mobile", "handset"],
  "MacBook Pro 16-inch": ["MacBook", "Laptop", "Notebook", "Ultrabook"],
  ...
}

借助生成的同义词,我们可以使用Synonyms API将它们注册到Elasticsearch中。

使用Synonyms API管理同义词

Synonyms API提供了一种在系统内直接管理同义词集合的高效方式。每个同义词集合包含同义词规则,其中一组词在搜索中被视为等效。

创建同义词集示例

代码语言:json复制
PUT _synonyms/my-synonyms-set
{
  "synonyms_set": [
    {
      "id": "rule-1",
      "synonyms": "hello, hi"
    },
    {
      "synonyms": "bye, goodbye"
    }
  ]
}

这创建了一个名为“my-synonyms-set”的集合,其中“hello”和“hi”被视为等效词,“bye”和“goodbye”也是如此。

为产品目录实现同义词创建

以下是构建同义词集并将其插入到Elasticsearch中的方法。根据LLM建议的同义词映射生成同义词规则。每个规则都有一个ID,对应于产品名称的slug格式,以及LLM计算的同义词列表。

代码语言:python代码运行次数:0运行复制
import json
import logging
from elasticsearch import Elasticsearch
from slugify import slugify

es = Elasticsearch(
    "http://localhost:9200",
    api_key="your_api_key"
)

def mount_synonyms(results):
    synonyms_set = [{"id": slugify(product), "synonyms": synonyms} for product, synonyms in results.items()]

    try:
        response = es.synonyms.put_synonym(id="products-synonyms-set", synonyms_set=synonyms_set)
        logging.info(json.dumps(response.body, indent=4))
        return response.body
    except Exception as e:
        logging.error(f"创建同义词时出错: {str(e)}")
        return None

以下是创建同义词集的请求负载:

代码语言:json复制
{
   "synonyms_set":[
      {
         "id": "iphone-14-pro",
         "synonyms": "iPhone, smartphone, mobile, handset"
      },
      {
         "id": "macbook-pro-16-inch",
         "synonyms": "MacBook, Laptop, Notebook, Computer"
      },
      {
         "id": "samsung-galaxy-tab-s8",
         "synonyms": "Tablet, Slate, Pad, Device"
      },
      {
         "id": "garmin-forerunner-945",
         "synonyms": "Forerunner, smartwatch, fitness watch, GPS watch"
      },
      {
         "id": "bose-quietcomfort-35-headphones",
         "synonyms": "Headphones, Earphones, Headset, Cans"
      }
   ]
}

在集群中创建同义词集后,我们可以进入下一步,即使用定义的同义词集创建一个支持同义词的新索引。

完整的Python代码,包括LLM生成的同义词和Synonyms API定义的同义词集创建如下:

代码语言:python代码运行次数:0运行复制
import json
import logging
from elasticsearch import Elasticsearch
from openai import OpenAI
from slugify import slugify

logging.basicConfig(level=logging.INFO)
client = OpenAI(api_key="your-key")
es = Elasticsearch("http://localhost:9200", api_key="your_api_key")

def call_gpt(prompt, model):
    try:
        logging.info("通过LLM生成同义词...")
        response = client.chatpletions.create(
            model=model,
            messages=[{"role": "user", "content": prompt}],
            temperature=0.7,
            max_tokens=1000
        )
        content = response.choices[0].message.content.strip()
        return content
    except Exception as e:
        logging.error(f"使用模型失败: {e}")
        return None

def generate_synonyms(category, products):
    synonyms = {}
    for product in products:
        prompt = (
            f"你是产品同义词生成专家。根据提供的类别和产品名称生成同义词或相关术语。请遵循以下规则:\n"
            f"1. **格式**:第一个词应为主要项目(产品名称的一部分,排除品牌),后跟最多3个用逗号分隔的同义词。\n"
            f"2. **排除品牌**:同义词中不要包含品牌名称。\n"
            f"3. **同义词数量**:每个产品最多生成3个同义词。\n\n"
            f"类别为:**{category}**,产品为:**{product}**。只返回请求格式的同义词,不要附加解释。"
        )
        response = call_gpt(prompt, "gpt-4o")
        synonyms[product] = response
    return synonyms

def get_products(category):
    query = {
        "size": 50,
        "_source": ["name"],
        "query": {
            "bool": {
                "filter": [
                    {
                        "term": {
                            "category.keyword": category
                        }
                    }
                ]
            }
        }
    }
    response = es.search(index="products", body=query)

    if response["hits"]["total"]["value"] > 0:
        product_names = [hit["_source"]["name"] for hit in response["hits"]["hits"]]
        return product_names
    else:
        return []

def mount_synonyms(results):
    synonyms_set = [{"id": slugify(product), "synonyms": synonyms} for product, synonyms in results.items()]

    try:
        es_client = get_client_es()
        response = es_client.synonyms.put_synonym(id="products-synonyms-set", synonyms_set=synonyms_set)
        logging.info(json.dumps(response.body, indent=4))
        return response.body
    except Exception as e:
        logging.error(f"更新同义词时出错: {str(e)}")
        return None

if __name__ == '__main__':
    category = "Electronics"
    products = get_products("Electronics")
    llm_synonyms = generate_synonyms(category, products)
    mount_synonyms(llm_synonyms)

创建支持同义词的索引

将创建一个新索引,所有products索引中的数据将被重新索引。这个索引将使用synonyms_filter,应用之前创建的products-synonyms-set

以下是配置为使用同义词的索引映射:

代码语言:json复制
PUT products_02
{
  "settings": {
    "analysis": {
      "filter": {
        "synonyms_filter": {
          "type": "synonym",
          "synonyms_set": "products-synonyms-set",
          "updateable": true
        }
      },
      "analyzer": {
        "synonyms_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "synonyms_filter"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "ID": {
        "type": "long"
      },
      "category": {
        "type": "keyword"
      },
      "name": {
        "type": "text",
        "analyzer": "standard",
        "search_analyzer": "synonyms_analyzer"
      }
    }
  }
}

重新索引products索引

现在,我们将使用Reindex API将数据从products索引迁移到包含同义词支持的新products_02索引。以下代码在Kibana DevTools中执行:

代码语言:json复制
POST _reindex
{
  "source": {
    "index": "products"
  },
  "dest": {
    "index": "products_02"
  }
}

迁移完成后,products_02索引将被填充并准备好使用配置的同义词集进行搜索验证。

验证同义词搜索

让我们比较两个索引之间的搜索结果。我们将在两个索引上执行相同的查询,并验证是否使用同义词来检索结果。

products索引中搜索(不支持同义词)

我们将使用Kibana执行搜索并分析结果。在Analytics > Discovery菜单中,我们将创建一个数据视图以查看我们创建的索引数据。

在Discovery中,点击数据视图并定义名称和索引模式。对于“products”索引,我们将使用“products”模式。然后,我们将重复这个过程为“products_02”索引创建一个新的数据视图,使用“products_02”模式。

配置数据视图后,我们可以返回到Analytics > Discovery并开始验证。

在这里,选择DataView产品并搜索“tablet”一词后,我们没有得到结果,尽管我们知道有诸如“Kindle Paperwhite”和“Apple iPad Air”这样的产品。

products_02索引中搜索(支持同义词)

在支持同义词的“products_synonyms”数据视图中执行相同查询时,产品成功检索。这表明配置的同义词集正常工作,确保搜索词的不同变体返回预期结果。

我们可以通过直接在Kibana DevTools中运行相同的查询来达到相同的效果。只需使用Elasticsearch Search API搜索products_02索引:

结论

在Elasticsearch中实现同义词提高了产品目录搜索的准确性和覆盖范围。关键的差异在于使用了LLM,它自动且有上下文地生成同义词,消除了预定义列表的需求。模型分析了产品名称和类别,确保电子商务相关的同义词。

此外,Synonyms API简化了词典管理,允许动态修改同义词集。通过这种方法,搜索变得更加灵活,能够适应不同的用户查询模式。

这个过程可以通过新的数据和模型调整不断改进,确保更高效的搜索体验。

参考文献

本地运行Elasticsearch

.html

Synonyms API

.html