作者:来自 Elastic Justin Castilla
通常,找到你想要的东西需要找到正确的问题。你使用的短语正确吗?你说出的关键词是否正确,从而引发了正确的反应?如果你有一些有限的、数字的或空间的限制,可以帮助你定义你最终想要的是什么,该怎么办?
虽然这些关于问题的问题可能很快就会变得乏味,但幸运的是,今天我们可以只关注一个探究领域 —— 向量搜索。最重要的是,我们将学习如何更轻松地找到我们想要的东西。通过在提取数据进行向量搜索的过程中插入一个快速而轻松的步骤,我们将能够从用户提出细致入微的定性查询中提供更好的结果。
背景
在 Han Xiang Choong 之前发表的搜索实验室文章中,我们了解到,创建一个假设文档( hypothetical document),该文档能够以足够的真实性回答查询,这将使我们非常接近找到正确的结果。然后,我们嵌入这个假设的答案,并使用它来查找真实且真正回答问题的类似文档。假设文档与答案的相似性更高,从而确保更准确的搜索结果。
此方法还可用于从条目中提取多个字段(而不仅仅是指定用于嵌入的一个特定字段),并创建一个假设查询,其中包含数字、布尔值、关键字和富文本作为搜索目标。
玩具并不都一样,但它们是
例如,想象一个特定的玩具,它可能是更大的收藏品或宇宙的一部分,它的名字可能出现在许多地方,它的派系也可能相当大。以我成长过程中最喜欢的玩具为例:宇宙巨人卡通片中的战斗装甲希曼 (He-Man) 动作人物。那些在 20 世纪 80 年代长大的人可能还记得这部动画片,里面正义与邪恶对抗,有许多令人难忘的角色,既有恶棍也有英雄。
我有很多宇宙巨人的动作人物,但我最喜欢的是战斗盔甲希曼,因为他的胸甲在按下时会从完美闪亮的盔甲变成破裂和磨损。这个简单的机械升级使这个玩具成为我玩具箱中最令人向往的玩具。我保证,随着你继续阅读,这将变得更重要。
在我 40 多岁的时候,找到一个替代的动作人物来重温那种怀旧之情并不容易。可以想象,有超过 200 个单独的玩具,还有两个单独的系列重启。作为主角,希曼是制作最多的,有无数的迭代和版本来适应不同发布时间的市场。
因此,我需要找到一个特定发布日期范围内的动作人物,一个特定的修改器来解释战斗盔甲,当然,还有一个我愿意出价的价格范围。就像我在网上分享的大多数东西一样,可以将其表示为 json 对象。下面是一个我愿意购买的例子,尽管它没有原包装:
he_man = {
"product_name": "Battle Armor He-Man",
"product_type": "Action Figure",
"franchise": "Masters of the Universe",
"year_released": 1983,
"condition": "excellent",
"price": 132.99,
"original_packaging": False,
"complete_set": False,
"product_description": "This Battle Armor He-Man figure is an icon
from the original Masters of the Universe line, showcasing the hero in his
legendary battle gear. The rotating chest plate reveals different levels of
damage, making every skirmish more intense. It’s a nostalgic piece that
captures He-Man’s indomitable spirit and looks amazing on display.",
"included_accessories": [
"Battle Axe",
"Battle Sword",
"Weapon Sheath",
"Battle-Damage Chest Piece"
],
}
马泰尔的困境
尽管我可以仅通过产品名称(关键字搜索)在现代玩具网站上进行搜索,但我不可避免地会收到来自三个不同时代的结果,导致结果中充斥着许多技术上正确但不够准确的选项。
我可以尝试使用几个短语搜索产品说明来描述我的具体选择(向量搜索),但最终我只能听天由命,取决于说明文本中包含的内容。如果它只是充满了激进的销售文案,而没有我正在寻找的任何特定属性怎么办?我还可以向搜索添加一些过滤功能(混合搜索),但同样,这只是希望和微调;此时我感到沮丧并准备放弃。
我有力量!
从搜索产品的用户那里俘获人心、思想并最终实现商业的一种方法是预测关于特定产品可能被问到的绝对最佳问题。这个问题将解决每一个相关属性,决定他们愿意支付的价格,并包括一些额外的好东西,真正让他们的节日送礼变得完美。
我们不能指望我们的用户创建这些完美的问题并操纵我们的过滤功能来找到完美的产品,但我们可以自己创建这个完美的问题并将其用作 vector_embedding 来针对他们的查询。
通过让 LLM 使用产品的所有相关属性创建查询,然后将这个假设问题嵌入为向量,我们可以捕获可能具有或可能不具有产品所有属性的类似问题。 这使我们能够跳过过滤并依赖可能未充分包含特定产品属性的嵌入文本表示。
这是一个相对简单的过程,将 LLM 生成的文本插入到嵌入并用作主要搜索向量的条目中。 结果清晰、准确且一致。
阅读代码 - 创建完美的问题
让我们使用 Open AI 生成我们的 HyDE(Hypothetical Document Embedding)文档。 这是一个简单的 Python 代码片段,用于联系 Open AI 并请求完成:
from openai import OpenAI
ai_client = OpenAI()
def get_ai_completion(prompt):
completion = ai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": prompt},
],
)
return completion
这很简单 - 真正的力量来自提示。当我们为新玩具创建条目时,我们会创建一个提示,其中包含文本中包含的所有必要属性。这会创建一个假设问题,该问题满足我们对这个特定玩具的所有必需搜索参数。
这是一个生成查询的简单提示:
prompt = f'Create a detailed query profile written from a customers
perspective describing what they are looking for when shopping for this
exact item. Include key characteristics like type, features, use cases,
quality aspects, materials, specific price range, an appropriate age for
the user, and target user. Focus on aspects a shopper would naturally
mention in their search query.
Format: descriptive text without bullet points or sections.
Example: "Looking for a high-end lightweight carbon fiber road bike for
competitive racing with electronic gear shifting and aerodynamic frame
design suitable for experienced cyclists who value performance and
speed."
Describe this product in natural language that matches how real
customers would search for it. Make sure to mention the target age and a
close approximation of the price to a whole number in the output text
somewhere.
Here are the product details:
Product Name: "{product_name}"
About Product: "{product_description}"
Price: "{price}"
Target Age: "{target_age}"'
这里有一个辅助函数可以轻松创建这些提示(prompt):
def generate_prompt(toy):
prompt = f'Create a detailed query profile written from a customers perspective describing what they are looking for when shopping for this exact item. Include key characteristics like type, features, use cases, quality aspects, materials, specific price range, an appropriate age for the user, and target user. Focus on aspects a shopper would naturally mention in their search query. \nFormat: descriptive text without bullet points or sections. \nExample: "Looking for a high-end lightweight carbon fiber road bike for competitive racing with electronic gear shifting and aerodynamic frame design suitable for experienced cyclists who value performance and speed." Describe this product in natural language that matches how real customers would search for it. Make sure to mention the target age and a close approximation of the price to a whole number in the output text somewhere. Here are the product details: \nProduct Name: {toy["product_name"]} \nAbout Product: {toy["product_description"]} \nPrice: {toy["price"]} \nTarget Age: {toy["target_age"]}'
return prompt
让我们从上面的提示和 he-man 对象读取由我们的 LLM 生成的查询:
"I'm on the hunt for a Battle Armor He-Man action figure that really
captures the essence of the original Masters of the Universe line. I'm
ideally looking for a version that showcases He-Man in his legendary battle
gear because I want something that stands out on display and evokes
nostalgia. One of the key features I'm interested in is the rotating chest
plate that reveals different levels of damage; that not only adds a fun
interactive element but also enhances the play experience, making every
skirmish more thrilling for kids who love creative storytelling. \n\nSince
this will be a gift aimed at children aged 7 and up, I'm also considering the
quality aspects of the figure. It should be well-made, durable enough to
withstand rough play, and feature vibrant colors and intricate details that
truly reflect the iconic character's spirit. I want to ensure it's something
that will not just sit on a shelf but will also be played with, sparking
imagination and adventure.\n\nIn terms of price, I’m looking for something
within the $130 range, around $133, as I want to ensure it’s a quality
collector’s item while still being accessible for a young fan. Overall, I want
to find that perfect Battle Armor He-Man figure that balances nostalgia for
older collectors with robustness for kids to enjoy."
大型语言模型(LLM)无法更好地描述我玩儿时玩具时的纯粹快乐!
在为新玩具建立索引时,创建查询的这一步是在数据进入 Elasticsearch 之前完成的。
阅读代码 - 创建索引
使用 HyDE 创建索引与使用向量搜索的任何其他标准索引没有什么不同。我们将包括原始查询配置文件文本以及 query_profile(嵌入向量)进行检查和微调,以确保我们的问题符合我们在开发中想要的方式。我们将使用 Elastic 的 ELSER v2 模型进行嵌入。
def create_index():
mappings = {
"mappings": {
"properties": {
"product_name": {"type": "text"},
"product_description": {"type": "text"},
"price": {"type": "float"},
"target_age": {"type": "integer"},
"query_profile": {"type": "sparse_vector"},
"raw_query_profile": {"type": "text"},
}
}
}
# Check if the index already exists
if not client.indices.exists(index=index_name):
client.indices.create(index=index_name, body=mappings)
print(f"Index '{index_name}' created successfully.")
else:
print(f"Index '{index_name}' already exists.")
create_index()
阅读代码 - 创建提取管道
此管道将嵌入 raw_query_profile 文本,并在将其添加到索引后立即将其存储在 query_profile 中。请注意,这也可以使用其他嵌入模型来完成。
def create_ingest_pipeline():
resp = client.ingest.put_pipeline(
id="elser-v2",
processors=[
{
"inference": {
"model_id": "elser_model_2",
"input_output": [
{
"input_field": "raw_query_profile",
"output_field": "query_profile",
}
],
}
}
],
)
print(resp)
create_ingest_pipeline()
阅读代码 - 部署嵌入模型
我们需要创建一个推理端点,以便在嵌入 query_profile 以及传入的用户查询时指向该端点。这将创建一个稳定的命名目标,我们稍后可以根据性能和速度对其进行微调。
def create_inference_endpoint():
resp = client.inference.put(
task_type="sparse_embedding",
inference_id="elser_model_2",
inference_config={
"service": "elser",
"service_settings": {"num_allocations": 1, "num_threads": 1},
},
)
print(resp)
create_inference_endpoint()
阅读代码 - 添加条目
现在我们添加一个条目。请注意,我们将生成一个包含此玩具具体信息的提示。然后,我们将该提示发送给我们的 LLM 以生成我们的假设查询文档。然后,我们将在存储时将其嵌入 Elastic。
def add_one_toy(toy):
prompt = generate_prompt(toy)
ai_response = get_ai_completion(prompt)
toy["raw_query_profile"] = ai_response.choices[0].message.content
try:
resp = client.index(index=index_name, body=toy, pipeline="elser-v2")
print(resp)
except IndexError as e:
print(e)
add_toy(he_man)
阅读代码 - 查询索引
现在让我们使用 query_profile 向量作为搜索基础来查询我们的索引。
toy_query = "I'm looking for the classic He-man action figure toy from the
80s with a muscular build and a sword. He had a neat trick where his chest
would get battle damage if it was pressed down upon. I'm willing to pay
around $100 to $150 dollars. It's uhh... for my 7 year old's birthday.
Sure. That's it."
resp = client.search(
index=index_name,
query={
"sparse_vector": {
"field": "query_profile",
"inference_id": "elser_model_2",
"query": toy_query,
}
},
)
print(resp)
""" TOP THREE RESULTS:
{
"_score": 35.444317,
"_source": {
"product_name": "Battle Armor He-Man",
"target_age": 7,
"price": 132.99
}
},
{
"_score": 33.13795,
"_source": {
"target_age": "7",
"price": "11.99",
"product_name": "Captain Thunder Action Figure"
}
},
{
"_score": 22.948023,
"_source": {
"target_age": "2",
"price": "14.99",
"product_name": "My First Doll"
}
}
"""
这是一个非常简单、非常简略的例子,说明将 HyDE 添加到你的查询流程中如何极大地改善你的搜索结果。由于我们中的许多人都在寻找这个假期的完美礼物,因此从 HyDE 先生(或 Vector Search 的小帮手)那里获得一点帮助可能会使整个过程更快,并且不会那么盲目。
感谢大家阅读今年的编程挑战 Advent of Code,祝愿每一位朋友在这个假日季节中收获美好时光,遇到最少的故障,享受最多的休闲时刻!
存储库:GitHub - justincastilla/santas-little-helper: A demonstration of HyDE enhanced vector search