<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[混合搜索 - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[混合搜索 - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/cn/search-labs/blog/category/hybrid-search</link>
    </image>
    <link>https://www.elastic.co/cn/search-labs/blog/category/hybrid-search</link>
    <atom:link href="https://www.elastic.co/cn/search-labs/rss/category/hybrid-search.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[cn]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 20:39:29 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Elasticsearch 向量数据库：数分钟内即可完成部署，能以超高性价比扩充至千亿规模]]></title>
    <description><![CDATA[混合检索的难点部分已经解决，配备优化的默认设置、第三方和原生 Jina AI 模型以及开箱即用的托管型 GPU 推理功能。您可以构建快速、可扩展的 AI 应用，无需构建基础架构。]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch 是全球部署最广泛的向量工作负载平台之一，为 GitHub、Docusign、Seismic 等众多公司的语义搜索、检索增强生成 (RAG) 以及推荐功能提供支持。今天，我们正式发布 Elasticsearch 向量数据库，这是一款专为基于向量的应用进行优化的全新无服务器产品。您只需提供文档和查询指令，我们来负责处理嵌入、索引调优以及基础架构。此外，该产品还兼具低成本和可扩展的优势。 </p><p>对于新用户而言，这是运行高质量向量搜索的最快方式。如果您已在使用 Elasticsearch，这款新产品能够在您的数据所在平台上实现向量搜索，您无需引入任何新系统。Elasticsearch 向量数据库支持多种应用场景，例如为大型语言模型 (LLM) 提供事实依据，为 AI 智能体赋予检索与记忆能力，以及处理数千亿个向量等。<a href="https://cloud.elastic.co/registration?onboarding_token=vector">立即创建新项目</a>，只需几分钟即可开始使用。</p><h2>一个引擎，满足所有向量应用场景</h2><p>Elasticsearch 向量数据库专为使用向量构建应用程序的用户而设计：</p><ul><li><p><strong>RAG：</strong>通过密集和稀疏向量检索为您的 LLM 检索适当的上下文，或者采用结合向量检索和词汇检索的混合搜索。您的生成质量会随着检索质量的提升而提高。</p></li><li><p><strong>AI 智能体：</strong>为智能体提供针对文档和对话记忆的快速筛选检索，满足多步智能体循环所需的低延迟要求。</p></li><li><p><strong>语义搜索：</strong>根据含义而非关键字进行匹配，只需一种字段类型且无需任何管道代码。</p></li><li><p><strong>推荐和相似度：</strong>针对产品、图像或任何内容大规模查找最近邻。</p></li></ul><h2>您的向量工作负载所需的一切，开箱即用且经过优化</h2><p>构建基于向量的应用程序意味着将多个独立部分连接起来：设置并托管嵌入模型，通过这些模型为您的文档编制索引，高效存储向量，将嵌入模型应用于每个查询，与向量存储进行匹配，以及最后检索匹配项背后的文档。Elasticsearch 向量数据库可为您处理所有这些操作，无需进行额外配置或设置。</p><h3>使用 vectordb_document 索引模式进行向量索引</h3><p><a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-vectordb-document-mode">vectordb_document</a> 索引模式是一种专为向量优先型工作负载构建的新索引配置，默认启用，因此您可以获得专家会选择的设置。以下是它会启用的功能：</p><ul><li><p><strong>默认使用 bfloat16：</strong>向量的存储大小仅为 float32 的一半，对召回率的影响可忽略不计，甚至在考虑量化之前，就能将您的磁盘占用空间大致减半。</p></li><li><p><strong>排除源向量：</strong>在 Elasticsearch 中，您的嵌入向量已存在于用于搜索的索引结构中；在 _source 中保留第二份原始副本只会增加存储占用并降低获取结果的速度。我们排除了重复项，以便更快获得响应并减少存储空间。</p></li><li><p><strong>将正确的文件预加载到缓存中：</strong>向量查询最先访问的数据结构会提前预热到内存中，从而确保无论是第一次查询还是第一千次查询，速度都快如闪电。</p></li><li><p><strong>并行合并：</strong>合并可将分段整合为组织更有序的向量结构，从而同时提升召回率并改善延迟，而以多线程方式运行这些合并则可以更快达成这一目标。</p></li></ul><h3>向量存储、压缩和自动调优</h3><ul><li><p>您的向量会自动压缩。<a href="https://www.elastic.co/cn/search-labs/blog/better-binary-quantization-lucene-elasticsearch">更好的二进制量化 (BBQ)</a> 可在保持召回率的同时将向量的内存占用最多减少 32 倍，而 DiskBBQ 则能针对大规模工作负载进一步降低内存需求。<a href="https://www.elastic.co/cn/search-labs/blog/vector-quantization-auto-calibration-diskbbq"> </a></p></li><li><p>选择启用<a href="https://www.elastic.co/cn/search-labs/blog/vector-quantization-auto-calibration-diskbbq">自动校准</a>，该功能会根据您的数据调整每个分段的量化参数，并在数据发生漂移时在每次合并时重新调整。在 18 个数据集上的测试结果显示，每秒查询数 (QPS) 平均提升了 16.7%，且大多数数据集的召回率也有所提高。</p></li></ul><h3>托管 GPU 推理上的嵌入</h3><ul><li><p>通过原生 <a href="https://www.elastic.co/cn/jina-search-models">Jina AI 嵌入和重排序模型</a>生成嵌入向量，或引入第三方模型；所有模型均在 <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service (EIS)</a> 提供的托管 GPU 上运行，无需运维模型服务器。如果您愿意，您也可以选择自行托管。</p></li><li><p><a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"><strong>semantic_text</strong></a> 字段类型可自动处理分块和嵌入以及查询，这是市面上实现语义搜索的最简单途径。 </p></li></ul><h3>混合搜索和过滤向量搜索</h3><ul><li><p><a href="https://www.elastic.co/cn/elasticsearch/hybrid-search">混合搜索</a>是内置功能，可在单个查询中结合全文检索和向量检索。您可以使用倒数排序融合 (RRF) 或您所需的任何其他融合机制来融合结果。在混合搜索中，向量搜索部分的配置往往最具挑战性；而借助 Elasticsearch 向量数据库，这一难题迎刃而解，您的整个混合技术栈也会变得更加出色。 </p></li><li><p>借助<a href="https://www.elastic.co/cn/search-labs/blog/filtered-hnsw-knn-search">带过滤功能的向量搜索</a>，将元数据过滤作为向量检索过程本身的一部分来实施，而不是将其作为事后补救措施，从而避免损害召回率。</p></li></ul><h3>从第一天起即面向企业</h3><p>您还可以获得基于角色的访问控制 (RBAC)、审计日志，以及纯向量数据库通常缺乏的合规性认证。</p><h2>扩展时经济实惠且具有可预测性</h2><p>Elasticsearch 向量数据库旨在让您随着业务增长仍可负担：BBQ 和 DiskBBQ 压缩可使存储线性增长并保持较低内存占用，这意味着即使扩展到数千亿个向量，费用也不会大幅攀升。您实际支付的费用由您已知的数字决定：存储的数据量、索引的数据量，以及所需的搜索容量。估算您的文档数量、向量维度和查询负载，您便可在创建项目之前算出所需费用。您还可以在月底逐项了解账单明细。没有不透明的计算单元，也不会因后台操作产生意外费用。</p><h2>如何开始使用 Elasticsearch 向量数据库</h2><h3>创建无服务器向量数据库项目</h3><p>创建新的 <a href="https://cloud.elastic.co/registration?onboarding_token=vector">Elastic Cloud 无服务器向量数据库项目</a>。将数据指向终端，即可开始编入索引。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt556cbdfba551f248/6aa10b4332b53038406d321a/image1.png" alt="Elastic Cloud Serverless project types: Elasticsearch, Vector Database, Observability and Security" /><h3>使用 semantic_text 创建索引</h3><p>向量索引模式负责处理向量配置。使用 semantic_text 意味着系统会在托管 GPU 推理上为您管理嵌入和分块设置以及索引设置，无需构建嵌入管道。</p>PUT my-vectors
{
"mappings": {
"properties": {
"description": { "type": "semantic_text" }
    }
  }
}<h3>采集文档</h3><p>索引文本，系统便会为您生成嵌入。</p>POST /my-vectors/_doc
{
  "id": "park_rocky-mountain",
  "title": "Rocky Mountain",
  "description": "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra."
}<h3>运行语义搜索查询</h3><p>查询您刚刚创建的相同语义字段：</p>GET /my-vectors/_search
{
  "query": {
    "semantic": {
      "field": "description",
      "query": "a mountain range in the middle of north america"
    }
  }
}<p>然后您会获得返回的结果：</p>{
  "took": 80,
  "hits": {
    "max_score": 0.7792325,
    "hits": [
      {
        "_index": "my-vectors",
        "_score": 0.7792325,
        "_source": {
          "id": "park_rocky-mountain",
          "title": "Rocky Mountain",
          "description": "Bisected north to south by the Continental Divide, ..."
        }
      }
    ]
  }
}<p>语义搜索只是开始。运行纯文本查询，或将两者结合为混合查询。您甚至可以构建自己的向量查询，实现完全控制。请参阅文档中的<a href="https://www.elastic.co/docs/solutions/vector-database/vector-full-text-search">语义搜索快速入门</a>，获取完整说明。</p><h2>Elasticsearch 中向量搜索的未来展望</h2><p>我们已经在着手进行后续改进：</p><ul><li><p><strong>更出色的多租户处理：</strong>如果您的数据需要按租户保持隔离，我们将为您提供一种速度更快、代码量更少的方法来实现这一目标。</p></li><li><p><strong>自动索引优化：</strong>从“全新索引”到“完全优化”，无需过多人工干预。</p></li><li><p><strong>持续的基础架构改进：</strong>对向量数据库的设置和基础架构进行持续调优，确保您始终获得最佳吞吐量和最快响应。</p></li></ul><h2>在 Elastic Cloud Serverless 上试用 Elasticsearch 向量数据库</h2><p>仅需数分钟，您即可从零开始构建支持混合检索与过滤功能的向量查询应用，并利用生产级默认配置自动完成性能调优。您可以构建快速、可扩展的 AI 应用，无需构建基础架构。</p><p>开始使用 <a href="https://cloud.elastic.co/registration?onboarding_token=vector">Elastic Cloud Serverless</a>，或深入了解<a href="https://www.elastic.co/docs/solutions/vector-database">完整文档</a>和 <a href="https://www.elastic.co/docs/api/doc/elastic-cloud-serverless/group/endpoint-vectordb-projects">API 参考。</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-database-rag-serverless</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-database-rag-serverless</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[Elastic Cloud Serverless]]></category>
    <category><![CDATA[混合搜索]]></category>
    <dc:creator><![CDATA[Dustin Coates]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4def84aae6aff861/6aa10ab1ee57e53d9b05253c/cover.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[如何衡量和提升 Elasticsearch 搜索召回率：通过混合搜索将召回率从 0.43 提升至 0.75]]></title>
    <description><![CDATA[了解如何通过将 BM25 词汇搜索与 Jina AI 向量嵌入相结合来测量和提高 Elasticsearch 中的搜索召回率，并使用 rank_eval API 以实际数据验证改进效果。]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/docs/solutions/search/full-text">词汇搜索</a>使用 <a href="https://www.elastic.co/blog/practical-bm25-part-1-how-shards-affect-relevance-scoring-in-elasticsearch">BM25 排序算法</a>，对于各种查询来说成本低、速度快且非常有效。但它有一个盲点：无法处理与文档没有共同标记的查询。在本文中，您将准确衡量 BM25 的不足之处。我们将使用 Elasticsearch 的<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval">排名评估 API</a> (<code>rank_eval</code>)，并通过添加 <a href="https://www.elastic.co/search-labs/es/blog/jina-embeddings-v3-elastic-inference-service">Jina AI 嵌入</a>，通过 <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic 推理服务</a> (EIS) 来缩小这一差距。您会看到召回分数从 <code>0.43</code> 提升到 <code>0.75</code>，并理解其原因。</p><h2>什么是召回？</h2><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-recall">召回率</a> 以 <code>0</code> 到 <code>1</code> 的范围来衡量用户真正想要的文档有多少出现在搜索结果中。如果某个查询应显示三个产品，而您的搜索结果仅有两个进入前 10 名，则该查询的得分为 <code>recall@10 = 0.67</code>。这是一个基于集合的指标：它并不关心相关文档在这 <em>k</em> 个结果中的位置。位置 10 的相关文档与位置 1 的相关文档具有同等效力。高召回率意味着您不会丢失相关结果。</p><p>
</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ffd147b13705680/6a170a6fe8fbce11a539fc22/b13af2a5d0ca055535d8bfe3dfe4b3d1093ee6da-1457x796.png" alt="维恩图展示了如何计算 Recall@10，通过显示所有相关文档与 BM25 检索出的前 10 个结果的重叠情况，得出 Recall@10 得分为 0.40。" /><p>该图表显示了两组文档：所有相关文档（左侧）和 BM25 实际检索到的文档（前 10 个，右侧）。只有交集部分才计入召回率，找到了 <code>prod_1</code> 和 <code>prod_2</code>，而 <code>prod_3</code>、<code>prod_4</code> 和 <code>prod_6</code> 则完全遗漏。结果：<code>Recall@10 = 2/5 = </code><strong><code>0.40</code></strong>。</p><h2>准备工作</h2><p>让我们言归正传，更好地了解召回的工作原理。本演示使用 Python。您可以在配套笔记本 (<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/relevance-tuning-improving-recall-adding-vectors/notebook.ipynb">notebook.ipynb</a>) 中跟着操作，其中每个代码块都是一个可直接运行的单元。</p><p>提供的代码使用以下内容：</p><ul><li><p>Elasticsearch 9.3+</p></li><li><p>Python 3.10+</p></li></ul>pip install elasticsearch pandas plotly python-dotenv<ul><li><p>包含 Elasticsearch 凭据的 <code>.env</code> 文件</p></li></ul>ELASTICSEARCH_URL=https://your-cluster-url
ELASTICSEARCH_API_KEY=your-api-key<h2>该数据集</h2><p>我们将使用包含 1,000 种产品的产品目录，涵盖鞋类、电子产品、工具等多个类别。</p><p>每份文档有四个字段：</p><p>字段</p><p>类型</p><p>“标题”</p><p>文本</p><p>“描述”</p><p>文本</p><p>“品牌”</p><p>关键字</p><p>`类别`</p><p>关键字</p><p>该数据集加载自 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/relevance-tuning-improving-recall-adding-vectors/dataset.csv"><code>dataset.csv</code></a>。</p><h2>词汇搜索的支持和局限性</h2><p>BM25 是 Elasticsearch 和大多数搜索引擎的默认排名算法。它根据查询词在文档中的出现频率对其进行评分，并根据文档长度和这些词在整个索引中的出现频率进行调整。在此基础上，您还可以获得<a href="https://www.elastic.co/docs/reference/text-analysis/analyzer-reference">分析器</a>：小写规范化、词干提取和停用词消除。查询“跑步鞋”将匹配“跑步鞋”，也可能匹配“跑步”。</p><p>这对很多查询都很有效：</p><ul><li><p>“跑鞋”会立即匹配标题中包含这些确切标记的产品。</p></li><li><p>“蓝牙扬声器”会显示便携式音频产品，因为这些词语是逐字匹配的。</p></li></ul><p>搜索结果具有确定性和可解释性：文档排名靠前，是因为查询词出现在其中。调试相关性很简单。</p><h3>出现问题的地方</h3><p>现在，让我们针对同一目录尝试这些查询：</p><ul><li><p><strong>“护肤流程”：</strong>在任何产品标题中都没有出现“流程”这个词。BM25 能够部分匹配“护肤”这一词，但面部精华液、身体精油和保湿霜等产品是用“维生素 C”、“视黄醇”或“提亮”等术语来描述的，这些术语与查询词都没有重叠。构成完整护肤流程的产品分散在索引中，没有任何共同的令牌将其关联起来。</p></li></ul>ID: B06XX6DS3P, Score: 9.0552, Title: Replenix Retinol Smooth + Tighten Body Lotion - Collagen-Boosting, Regenerating Anti-Aging Body Cream, Reduces Appearance of Stretch Marks, 6.7 oz.

  ID: B08XMPKJ1L, Score: 5.2699, Title: Bio-Oil Skincare Body Oil (Natural) Serum for Scars and Stretchmarks, Face and Body Moisturizer Hydrates Skin, with Organic Jojoba Oil and Vitamin E, For All Skin Types, 6.7 oz

  ID: B01CY764KQ, Score: 5.0057, Title: Nike Up Or Down Men Deodorant - Pack of 2 | Long-Lasting Fragrance, Body Spray Combo for Men | Deodorant for Active Living | Nike Men's Deo Set | Ultimate Odor Protection | Grooming Essentials | Signature Nike Scent | High-Performance Men's Deodorant<ul><li><p><strong>“宠物旅行配件”：</strong>这是一个用例分组，而非产品类别。宠物狗背带、宠物汽车座椅和旅行笼都与此相关，但它们的描述侧重于便携性、安全性和舒适性，而非“旅行配件”。BM25 与“宠物”大致匹配，但无法区分旅行专用产品与宠物目录中的其他产品。</p></li></ul>ID: B0BVV7BKTW, Score: 7.4371, Title: Large Foldable Travel Duffel Bag with Shoes Compartment

ID: B07TNPHYNV, Score: 6.6455, Title: 40 Pieces Christmas Bronze Jingle Bells Craft Small Bells

ID: B08R8FRW53, Score: 6.6335, Title: CUBY Dog and Cat Sling Carrier
ID: B08QMCQYGM, Score: 6.5259, Title: YTFGGY Whiteboard Pinstripe Tape 6 Rolls 1/8"
ID: B0CP3LQSWM, Score: 6.2994, Title: Portable Dog Water Bottle 32 Oz<p>这是一个<strong>召回问题</strong>。相关文档已存在于您的索引中。BM25 无法找到它们，因为用户的用词和文档中的词语匹配度不够高。</p><p>添加同义词有助于处理已知情况。但您无法枚举用户表达某种意图的所有方式。这就是向量发挥作用的地方。</p><h2>为何要测量召回率</h2><p>在解决问题之前，需要先对问题进行量化。</p><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-recall"><strong>Recall@k</strong></a> 衡量有多少用户真正想要的文档出现在搜索结果中。正式来说：</p>Recall@k = (relevant documents found in top k) / (total relevant documents)<p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-precision"><strong>Precision@k</strong></a> 衡量前 k 个结果，以及其中有多少是实际相关的：</p>Precision@k = (relevant documents in top k) / k<p>高精度意味着您返回的结果质量较高。在电子商务领域，缺少相关产品（召回率低）通常比显示稍有瑕疵的结果（精度较低）更糟糕，因为隐藏的产品意味着销售损失。</p><p>Elasticsearch 的 <code>rank_eval</code> API 允许您系统地测量两者。您提供一系列查询，每个查询都有一组已评分的文档，Elasticsearch 会为您计算所有查询的指标。</p><h2>设置评估</h2><p><code>rank_eval</code> API 需要一个<strong>评级数据集</strong>：查询与每个查询相关的文档之间的映射，以及相关性等级（0＝不相关，1＝相关，2＝高度相关）。</p><p>在笔记本中，这是<a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr#learning-to-rank-judgement-list">判断列表</a>：</p>judgments = [
    # Query 1: "running shoes" BM25 handles well (tokens appear in product titles) 
    {"query_id": "q1", "doc_id": "B09NQJFRW6", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B08JMD4LMM", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B08VRJ6F2Q", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B07S8NRRWR", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B01HD620I8", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B07DX86321", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B0968YVLQ8", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B093QJ39ZS", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B096FGSC39", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B01GVQWVV2", "grade": 1, "query": "running shoes"},

    # Query 2: "skincare routine" intent-based, "routine" never appears in product titles
    {"query_id": "q2", "doc_id": "B08XMPKJ1L", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B0BN3WQB92", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B0BT7B7P5T", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B00NPA2WEY", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B06XX6DS3P", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B07PDRD1KT", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B074J7869B", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B08JV31QW4", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B00K3TVJMQ", "grade": 1, "query": "skincare routine"},

    # Query 3: "study desk setup" intent-based, products are desks/stands/organizers
    {"query_id": "q3", "doc_id": "B08CS35J2T", "grade": 2, "query": "study desk setup"},
    {"query_id": "q3", "doc_id": "B09B3LFDXJ", "grade": 2, "query": "study desk setup"},
    {"query_id": "q3", "doc_id": "B07W58LMND", "grade": 1, "query": "study desk setup"},
    {"query_id": "q3", "doc_id": "B0CHYDX91L", "grade": 1, "query": "study desk setup"},

    # Query 4: "pet travel accessories" use-case grouping, products are carriers/crates/seats
    {"query_id": "q4", "doc_id": "B08R8FRW53", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B01MYUYX33", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B003C5RKE4", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B09GF8GBF6", "grade": 1, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B0CP3LQSWM", "grade": 1, "query": "pet travel accessories"},
]<p>这种混合是有意为之：<code>q1</code> 是 BM25 可以很好处理的查询（产品标题中的精确标记），而 <code>q2</code>、<code>q3</code> 和 <code>q4</code> 是基于意图的查询，用户的意图是以概念而非具体产品关键词来表达的。</p><h2>测量 BM25 基线召回率</h2><p>首先，设置 Elasticsearch 客户端，并对原始文本数据建立索引：</p>import os
import json
import pandas as pd
import plotly.graph_objects as go
from elasticsearch import Elasticsearch, helpers
from dotenv import load_dotenv

load_dotenv()

es = Elasticsearch(
    os.getenv("ELASTICSEARCH_URL"),
    api_key=os.getenv("ELASTICSEARCH_API_KEY")
)

INDEX_NAME = "ecommerce-products"<p>现在为 BM25 构建 <code>rank_eval</code> 请求。列表中的每个请求都将会查询及其评分结合起来：</p>judgments_df = pd.DataFrame(judgments)

bm25_requests = []
for query_id, query_text in (
    judgments_df[["query_id", "query"]].drop_duplicates().values
):
    relevant_docs = judgments_df[judgments_df["query_id"] == query_id]
    ratings = [
        {"_index": INDEX_NAME, "_id": row["doc_id"], "rating": row["grade"]}
        for _, row in relevant_docs.iterrows()
    ]

    bm25_requests.append({
        "id": query_id,
        "request": {
            "query": {
                "multi_match": {
                    "query": query_text,
                    "fields": ["title", "description"]
                }
            }
        },
        "ratings": ratings,
    })

bm25_eval = {
    "requests": bm25_requests,
    "metric": {"recall": {"k": 10, "relevant_rating_threshold": 1}},
}

bm25_result = es.rank_eval(index=INDEX_NAME, body=bm25_eval)
print("BM25 Recall@10:", bm25_result.body["metric_score"])<p>结果：</p>BM25 Recall@10: 0.43<p><code>0.43</code> 这意味着在所有四个查询中，BM25 只找到了它应该找到的文档的 43%。这种不足集中体现在基于意图的查询中：“护肤流程”漏掉了面部精华液和身体精油，因为“流程”一词从未出现在产品标题中；而“宠物旅行配件”则检索出了一些不相关的宠物产品，却遗漏了那些以便携性和安全性而非“旅行配件”来描述的宠物笼和宠物箱。</p><p>这就是我们的基准。现在我们有了一个要超越的数字。</p><h2>使用 Jina 嵌入添加向量搜索</h2><p><a href="https://www.elastic.co/docs/solutions/search/vector"><code>Vector search</code></a> 将文档和查询编码为高维向量，这是一种由数百甚至数千个数值组成的向量，每个数值都对它所代表的数据的特定特征进行编码。意义相似的文档最终会在向量空间中靠近，即使它们没有共同的词汇。“健身器材”和“哑铃套装”会放在一起，因为这两个概念是相关的。我选择 Elasticsearch 作为我的向量数据库，是因为它支持混合搜索，让我既能理解语义，又能精确查找关键字。</p><p><a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">EIS</a> 包括通过其<a href="https://www.elastic.co/docs/api/doc/elasticsearch/group/endpoint-inference">推理 API</a> 嵌入模型的开箱即用支持。</p><h3>步骤 1：使用 Jina 嵌入 v5 作为推理终端</h3>INFERENCE_ENDPOINT_ID = ".jina-embeddings-v5-text-small"<p>如果您的集群具有 GPU 资源（在 Elastic Cloud 和 Elasticsearch 9.3+ 中可用），嵌入将在 GPU 上生成，这比 CPU 推理快得多，并消除了历史上使向量在扩展时变得昂贵的性能权衡。</p><p>为什么要特别选用 Jina 嵌入？<a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text">jina-embeddings-v5-text</a> 是一种多语言模型（支持 119 种以上语言），具有 32,000 个标记的上下文窗口，并支持特定任务的<a href="https://arxiv.org/abs/2106.09685">低秩自适应 (LoRA) 适配器</a>。它适用于开箱即用的简短产品描述。<a href="https://huggingface.co/jinaai/jina-embeddings-v5-text-small">点击此处</a>了解有关 <code>jina-embeddings-v5-text</code> 模型的更多信息。</p><h3>步骤 2：创建具有语义字段的索引</h3>index_mappings = {
    "mappings": {
        "properties": {
            "title": {"type": "text", "copy_to": "semantic_field"},
            "description": {"type": "text", "copy_to": "semantic_field"},
            "brand": {"type": "keyword"},
            "category": {"type": "keyword"},
            "semantic_field": {
                "type": "semantic_text",
                "inference_id": INFERENCE_ENDPOINT_ID,
            },
        }
    }
}

if not es.indices.exists(index=INDEX_NAME):
    es.indices.create(index=INDEX_NAME, body=index_mappings)
    print(f"Created index: {INDEX_NAME}")<p>这里的关键在于 <a href="https://www.elastic.co/docs/solutions/search/semantic-search/semantic-search-semantic-text"><code>semantic_text</code></a> 字段类型。这是对 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector"><code>dense_vector</code></a> 的更高级别的抽象：您将其指向一个推理终端，Elasticsearch 会自动生成嵌入。</p><p><code>title</code> 和<code>description</code> 上的 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/copy-to"><code>copy_to</code></a> 属性意味着这两个字段的内容都会流入 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"><code>semantic_field</code></a> 进行嵌入，因此单个向量就能捕获完整的产品表示。</p><h3>步骤 3：为产品编制索引</h3>def bulk_index(products, index_name):
    actions = []
    for product in products:
        doc_id = product.get("_id")
        source = {k: v for k, v in product.items() if k != "_id"}
        action = {"_index": index_name, "_source": source}
        if doc_id:
            action["_id"] = doc_id
        actions.append(action)

    success, failed = helpers.bulk(es, actions, raise_on_error=False)
    if failed:
        for error in failed:
            print(f"Error: {error}")
    else:
        print(f"Successfully indexed {success} documents")

bulk_index(products, INDEX_NAME)<p>索引时，Elasticsearch 会调用每个文档的推理端点，并将生成的嵌入存储在 <code>semantic_field</code> 中。您无需编写任何额外代码。</p><h2>混合搜索：将 BM25 与向量结合并采用 RRF</h2><p>添加向量可以提高召回率，但仅使用向量可能会在精确匹配查询中失去精度；“跑鞋”仍应将逐字匹配的结果排在首位。混合搜索则保留词汇成分，以保持这种精确性。</p><p>使用<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">倒数排序融合</a> (RRF) 的混合搜索可以保持两者的优点：</p><ul><li><p>BM25 可以高精度处理精确和近似精确的查询。</p></li><li><p>语义搜索能以高召回率处理基于意图和多语言的查询。</p></li><li><p>RRF 将两份排名表合并为一份排名表。</p></li></ul><p>RRF 公式根据每个文档在每个结果列表中的排名，为每个文档分配分数：</p>score = sum(1 / (rank_constant + rank))<p>在两个列表中均排名靠前的文档将获得更高的综合得分。<code>rank_constant</code>用于控制排名较低的文档获得的权重大小。</p>hybrid_requests = []

for query_id, query_text in (
    judgments_df[["query_id", "query"]].drop_duplicates().values
):
    relevant_docs = judgments_df[judgments_df["query_id"] == query_id]
    ratings = [
        {"_index": INDEX_NAME, "_id": row["doc_id"], "rating": row["grade"]}
        for _, row in relevant_docs.iterrows()
    ]

    hybrid_requests.append({
        "id": query_id,
        "request": {
            "retriever": {
                "rrf": {
                    "retrievers": [
                        {
                            "standard": {
                                "query": {
                                    "multi_match": {
                                        "query": query_text,
                                        "fields": ["title", "description"],
                                    }
                                }
                            }
                        },
                        {
                            "standard": {
                                "query": {
                                    "match": {
                                        "semantic_field": {"query": query_text}
                                    }
                                }
                            }
                        },
                    ],
                    "rank_window_size": 50,
                    "rank_constant": 5,
                }
            }
        },
        "ratings": ratings,
    })

hybrid_eval = {
    "requests": hybrid_requests,
    "metric": {"recall": {"k": 10, "relevant_rating_threshold": 1}},
}

hybrid_result = es.rank_eval(index=INDEX_NAME, body=hybrid_eval)
print("Hybrid Recall@10:", hybrid_result.body["metric_score"])<p>结果：</p>Hybrid Recall@10: 0.75<p>混合搜索在 BM25 (<code>0.43</code>) 的基础上有了显著提升，并为“跑鞋”等精确匹配查询保留了精确度。</p><h2>结果：前后结果对比</h2><p>以下是所有三种方法的完整对比：</p>methods = {
    "BM25 (Lexical)": bm25_requests,
    "Hybrid (BM25 + Vectors)": hybrid_requests,
}

recall_metric = {"recall": {"k": 10, "relevant_rating_threshold": 1}}

comparison_data = []
for method_name, requests in methods.items():
    result = es.rank_eval(
        index=INDEX_NAME,
        body={"requests": requests, "metric": recall_metric}
    )
    comparison_data.append({
        "method": method_name,
        "recall@10": result.body["metric_score"]
    })

comparison_df = pd.DataFrame(comparison_data)
print(comparison_df.to_string(index=False))<p>结果：</p><p>方法</p><p>Recall@10</p><p>BM25（词法）</p><p>0.43</p><p>混合型（BM25 + 向量）</p><p>0.75</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5a1d72b57056fe64/6a170a71c1e8a56c58f882ab/e49f6c10516b0a48a0ad75962c6590ee07311407-700x500.png" alt="条形图比较了 BM25 词汇搜索和 BM25 与向量相结合的混合搜索的 Recall@10，结果显示混合搜索的召回率明显更高。" /><p>按查询细分：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt871347f754c866d0/6a170a73839dfa40abdcfeb4/40e36dcb7b34cbf4649c512bcb60cef60f1778a6-700x500.png" alt="分组条形图比较了四个产品查询中 BM25 词法搜索和混合搜索的 Recall@10，显示混合搜索在每个查询中始终优于词法搜索。" /><h2>结论</h2><p>在这篇文章中，我们看到，当用户键入精确的查询时，BM25 词汇搜索是可靠的，但当他们根据意图而非关键词进行搜索时，其召回率就会下降。借助 <code>rank_eval</code>，我们建立了一个可重复的基线，用真实数据来衡量这一差距。在此基础上，我们添加了一个由 Jina 嵌入提供支持的 <code>semantic_text</code> 字段，并再次运行了评估。结果：混合搜索将召回率从 <code>0.43</code> 提高到 <code>0.75</code>，同时保留了精确匹配查询的精确度，但实际幅度取决于您的查询组合。</p><p>该模式可扩展至本示例之外：从用户的实际查询中收集判断，以 <code>rank_eval</code> 作为基准运行，添加 <code>semantic_text</code>，然后再次进行测量。您将确切了解改进了哪些方面以及改进了多少。</p><h2>后续步骤</h2><ul><li><p>深入了解召回与向量搜索：《<a href="https://www.elastic.co/search-labs/blog/recall-vector-search-quantization">召回与向量搜索量化</a>》，作者：Jeff Vestal</p></li><li><p>添加重排序功能，以进一步提升前几条结果的精准度</p></li><li><p>探索 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">Elasticsearch 混合搜索文档</a></p></li><li><p>阅读有关 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-rank-eval.html"><code>rank_eval</code></a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-rank-eval.html">API</a> 的更多信息</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-relevance-tuning-improve-recall</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-relevance-tuning-improve-recall</guid>
    <category><![CDATA[混合搜索]]></category>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt37c9d2971b5a2db3/6a170a75cf4f254223b2d149/492c9b5432a2b9e40cebb3b60f0df019a8c7bf6d-1280x720.png" length="0" type="image/png"/>
    <pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 Elasticsearch 解决实体问题，第 4 部分：终极挑战]]></title>
    <description><![CDATA[在专为防止走捷径而设计的高度多样化的“终极挑战”数据集上，解决并评估实体解析挑战。]]></description>
    <content:encoded><![CDATA[<p>我们现在已经看到智能实体解析通过两种方式实现。两种方法都以相同的方式开始：实体准备和提取，然后使用 Elasticsearch 检索候选对象。然后，我们通过基于提示的 JSON 生成或函数调用，使用大语言模型 (LLM) 对这些候选对象进行评估，并要求该模型对其判断做出透明的解释。</p><p>正如我们在<a href="https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-function-calling">之前的帖子</a>中看到的，函数调用提供的一致性不仅仅是一种不错的优化手段，它还是至关重要的。一旦我们从评估循环中消除了结构性错误，标准场景（例如第 4 层数据集中的场景）的结果便有了显著提升。</p><p>然而，有一个显而易见的问题需要回答：</p><p><em>当情况变得真正复杂时，这种方法还管用吗？</em></p><p>现实中的实体解析很少因简单情况而失败。当名称跨越语言、文化、书写系统、时间段和组织边界时，它就会失效。当人们用头衔而非名字称呼，公司更改名称，音译不一致，且尝试仅凭上下文（而非拼写）提及现实实体时，这种方法就失败了。</p><p>因此，在本系列的最后一篇文章中，我们对该系统进行了所谓的<strong>终极挑战</strong>。</p><h2>是什么让这成为终极挑战？</h2><p>在之前的评估中，我们使用越来越复杂的数据集对该系统进行了测试。当我们达到上一篇帖子中所讨论的第 4 层时，我们已经要应对昵称、头衔、多语言名称以及语义引用的混合情况了。这些测试表明，架构本身是可靠的，但可靠性问题，特别是不规范的 JSON 格式抑制了召回率。</p><p>有了函数调用，我们终于有了稳定的基础。这让我们有机会提出一个更有趣的问题：</p><p><em>一个统一的管道能否同时处理</em><em><strong>多种不同类型</strong></em><em>的实体解析问题？</em></p><p>终极挑战数据集的设计正是为了推动这一层面的发展。</p><p>该数据集并未专注于单一难点（如昵称或音译），而是结合了 <strong>50 多种不同的挑战类型</strong>，包括：</p><ul><li><p>文化命名惯例。</p></li><li><p>基于标题的引用。</p></li><li><p>业务关系和历史名称变更。</p></li><li><p>多语言和跨脚本提及。</p></li><li><p>复合挑战融合了上述多种元素。</p></li></ul><p>关键是，这并不是针对某个狭窄的用例进行优化。这是要测试当规则从一个实体变更到另一个实体时，<em>设计模式</em>是否成立。</p><h2>数据集概览</h2><p>终极挑战数据集包含：</p><ul><li><p><strong>50个实体</strong>，涵盖个人、组织和机构。</p></li><li><p><strong>约 60 篇文章</strong>，结构和语言复杂程度各不相同。</p></li><li><p><strong>51 个不同的挑战类别</strong>，大致分为以下几类：</p><ul><li><p>文化命名惯例。</p></li><li><p>标题和专业背景。</p></li><li><p>商业关系与组织关系。</p></li><li><p>多语言和音译挑战。</p></li><li><p>综合场景和边缘情况场景。</p></li></ul></li></ul><p>在本系列的前几篇文章中，我们看到使用生成式 AI (GenAI) 创建数据集可谓喜忧参半。如果没有它，要收集足够多、足够多样化的测试数据将极其困难。但如果不加以控制，这种模式往往会让事情变得过于简单。</p><p>例如，在早期的一次生成过程中，我们发现模型将“俄罗斯总统”等短语作为弗拉基米尔·普京 (Vladimir Putin) 的明确别名。这在今天看来可能是合理的，但却违背了测试上下文解析的目的。如果文章讨论的是 20 世纪 90 年代的俄罗斯，会发生什么情况？系统应根据上下文推断出正确的实体，而不是依赖于硬编码的别名。</p><p>因此，我们特意设计了这个数据集，以<strong>避免使用捷径</strong>。当系统能够推断出含义时，则不明确列出别名。描述性短语没有预先链接到实体。正确的匹配通常取决于文章层面的上下文，而不仅仅是局部文本。</p><p><strong>重要说明：</strong>尽管我们展示了系统在各种场景下的能力，但这仍是一个具有教育意义的原型。处理真实世界受制裁实体监控的生产系统需要额外的验证、合规性检查、审计跟踪，以及针对敏感用例的专门处理。</p><h2>为什么这些场景很难应对</h2><p>在本系列的第一篇文章中，我们介绍了一个简单但含义模糊的示例：“新的 Swift 更新来了！”挑战在于，“Swift”可以根据上下文解析为现实世界中的多个实体。这个示例反映了一个更广泛的事实：自然语言本质上是模棱两可的。</p><p>因此，实体解析不仅仅是字符串匹配的问题。人们经常依赖共享知识、文化规范和情境背景来解析引用，我们甚至很少注意到我们正在这样做。</p><p>考虑以下几个常见案例：</p><ul><li><p>没有地缘政治和时间背景，“总统”这样的头衔就毫无意义。</p></li><li><p>公司名称可能指母公司、子公司或之前的品牌，具体取决于文章的撰写时间。</p></li><li><p>一个人的名字可能会以不同的顺序、书写系统或音译方式出现，这取决于语言和文化。</p></li><li><p>同一个短语在不同的语境中可以合法地指代不同的实体，系统必须能够像接受匹配短语一样自信地<em>拒绝</em>匹配短语。</p></li></ul><p>没有单一的规则集可以高效地处理所有这些情况。这就是为什么这款原型如此激进地将关注点分开：</p><ul><li><p>Elasticsearch 高效而透明地缩小了候选空间。</p></li><li><p>LLM 仅在需要判断且必须自行解释的情况下使用。</p></li><li><p>检索和推理仍然是两个不同步骤。</p></li></ul><p>随着挑战类型多样性的增加，这种区分变得更加重要。</p><h2>系统如何在无特殊处理的情况下应对多样性</h2><p>这次评估最有趣的结果之一是<em>没有</em>改变的内容：</p><ul><li><p>我们<strong>没有</strong>针对日语名字添加特殊逻辑。</p></li><li><p>我们<strong>没有</strong>为阿拉伯语父名添加自定义规则。</p></li><li><p>我们<strong>没有</strong>添加历史公司名称的硬编码映射。</p></li></ul><p>相反，该系统依赖于系列早期引入的相同核心要素：</p><ul><li><p>为语义搜索编制索引的上下文丰富实体。</p></li><li><p>Elasticsearch 中的混合检索（精确检索、别名检索和语义检索）。</p></li><li><p>一组数量少且定义明确的候选匹配项。</p></li><li><p>受函数调用和最小模式约束的 LLM 判断。</p></li></ul><p>这表明系统的灵活性来自<strong>表征和架构</strong>，而非不断增长的规则集合。</p><p>当系统成功时，是因为检索到了正确的候选对象，且 LLM 有足够的上下文来解释为什么某个引用会（或不会）映射到某个特定实体。</p><h2>结果：它的表现如何？</h2><p>在最终挑战数据集上，系统得出了以下总体结果：</p><ul><li><p><strong>精度：</strong>约 91%</p></li><li><p><strong>召回：</strong> ~86%</p></li><li><p><strong>F1 分数：</strong>约 89%</p></li><li><p><strong>LLM 接受率：</strong>约 72%</p></li></ul><h3>在各类挑战中的表现</h3><p>按挑战类型细分结果可以揭示优势和局限性：</p><p>在以下领域的<strong>表现最为突出（100% F1 分数）</strong>：</p><ul><li><p>跨脚本匹配（西里尔字母、韩文、中文企业实体）。</p></li><li><p>希伯来语场景（父名、职业头衔、宗教头衔、音译）。</p></li><li><p>企业层级（航空航天、多元化制造、多部门公司）。</p></li><li><p>专业头衔（学术、军事、政治、宗教）。</p></li><li><p>涉及多种书写系统的综合日语场景。</p></li></ul><p><strong>表现优异（80–99% F1 分数）</strong>包括：</p><ul><li><p>国际政治人物（98%）。</p></li><li><p>历史名称变更 (90%)。</p></li><li><p>复杂的业务层次结构（89%）。</p></li><li><p>日本公司名称（93%）。</p></li><li><p>跨脚本音译（86%）。</p></li><li><p>阿拉伯语父名（86%）。</p></li></ul><p><strong>更具挑战性的领域</strong>包括：</p><ul><li><p>高级音译（中文、韩文）：0% F1。</p></li><li><p>某些日本场景（敬语、姓名顺序、书写系统变化）：~67% F1。</p></li><li><p>一些阿拉伯语场景（公司名称、机构引用）：约 40% F1。</p></li></ul><p>这里重要的是<em>为什么</em>系统在这些情况下会遇到困难。这些失败并非由于整体方法的崩溃，而是由于特定组件的局限性，尤其是在某些多语言场景中用于语义搜索的密集向量模型。</p><p>由于检索和判断是完全分离的，因此提高性能无需重写系统。更换功能更强大的多语言嵌入模型、丰富实体上下文或改进检索策略，都能在不改变核心架构的情况下改善这些类别的结果。</p><p>从架构的角度来看，这才是真正的成功指标。</p><h2>这告诉我们关于设计的启示</h2><p>回顾整个系列，有几个模式尤为突出：</p><ul><li><p><strong>准备工作比巧妙搭配更重要。</strong>预先为实体添加上下文信息可以显著减少以后可能出现的歧义。</p></li><li><p><strong>LLM 作为评判者最有价值，而非检索者。</strong>让他们解释<em>为什么</em>某个匹配是有意义的，比要求他们进行搜索要有效得多。</p></li><li><p><strong>可靠性确保准确性。</strong>函数调用不仅清理了 JSON，还释放了检索步骤中已存在的召回能力。</p></li><li><p><strong>通用性胜过专业化。</strong>少量经过精心挑选的抽象概念无需自定义逻辑即可处理数十种挑战类型。</p></li></ul><p>这就是为什么原型有意采用 Elasticsearch 原生架构，并在 LLM 的使用上有意采取保守策略的原因。目标不是取代搜索；而是在意义至关重要的情况下，使搜索变得可解释。</p><h2>总结</h2><p>最终的挑战并非追求完美的指标，而是回答一个更根本的问题：</p><p><em>一个透明、搜索优先、LLM 辅助的架构能否处理现实世界中的实体歧义，而不陷入规则或黑箱的情况？</em></p><p>对于这个具有教育意义的原型，答案是肯定的，但需要明确注意生产环境的强化、合规性、监控以及数据质量等方面的问题。如果您正在构建的系统需要说明<em>为什么</em>要进行实体匹配，那么这种模式值得认真考虑。我希望这个系列能告诉人们，实体解析其实并不神秘。只要合理地进行关注点分离，就可以对问题进行推理、评估和优化。</p><p>这项工作还提出了一种更广泛的架构模式。由此出现了经典检索增强生成 (RAG) 的一次细微但重要的演变。我们没有让检索直接为生成提供信息，而是引入了一个明确的评估步骤。首先使用 LLM 对检索到的候选结果进行判断和合理性检查，只有通过审核的结果才允许用于增强生成。您可以将其视为“生成增强型检索增强生成与评估”，或者简称为“GARAGE”，毕竟谁不喜欢一个好听的缩写词呢。</p><p>还有哪些其他用例可以从这种模式中受益？需要信任、透明和可辩护推理的系统是当然的候选者。未来在这一领域的工作应该会像我们在这里看到的结果一样引人注目，我很期待看到社区接下来会有什么新的发展。</p><h2>下一步：亲自试用</h2><p>想看看终极挑战的实际操作吗？请查看<a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/notebooks#:~:text=5%20minutes%20ago-,05_ultimate_challenge_v3.ipynb,-Initial%20public%20lab"><strong>终极挑战笔记本</strong></a>，它通过实际实现、详细解释和动手示例，提供了完整的实践指南。</p><p>完整的实体解析管道展示了生产使用所需的核心概念和架构。您可以将其用作构建系统的基础，这些系统可以监测新闻文章、跟踪实体提及情况，并回答有关哪些实体出现在哪些文章中的问题，同时还能保持透明度和可解释性。
</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/entity-resolution-elasticsearch-llm-challenges</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/entity-resolution-elasticsearch-llm-challenges</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[混合搜索]]></category>
    <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc58be329ffebcd60/6a17043e47d49c0bc62d88ab/70fb0ff949f6db9ac9b8a28ecb4329ab915ebf46-720x420.png" length="0" type="image/png"/>
    <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 Elasticsearch 与 LLM 进行实体解析，第 2 部分：通过 LLM 判断和语义搜索匹配实体]]></title>
    <description><![CDATA[在 Elasticsearch 中使用语义搜索和透明 LLM 判断进行实体解析。]]></description>
    <content:encoded><![CDATA[<p>在<a href="https://www.elastic.co/search-labs/blog/entity-resolution-llm-elasticsearch"> 第 1 部分</a>中，我们准备了观察清单并提取了实体提及。现在我们准备回答那个难题：一个提及实际指的是哪个实体？让我们回到本系列第一篇博客中的例子，该例子说明了我们为何需要实体解析：“Swift 更新来了！”想象一下，这个标题伴随着一些上下文：</p><ol><li><p>新的 Swift 更新来了！开发人员迫不及待地想要尝试新功能。</p></li><li><p>新的 Swift 更新来了！新专辑将于下个月发布。</p></li></ol><p>有了这些增加的上下文，我们应该能够将名称“Swift”解析到正确的实体。</p><p>在<a href="https://www.elastic.co/search-labs/blog/entity-resolution-llm-elasticsearch">上一篇文章</a>中，我们设置了观察清单，并用额外的上下文丰富了实体信息。看上面的例子，我们需要在清单中至少包含以下两个实体：Taylor Swift 和 Swift 编程语言。我们还介绍了如何从文本中提取实体提及。这两个例子都能提取“Swift”。有了这些要素——丰富的观察清单和提取出的实体——我们终于可以介绍本次的主角了：实体匹配。</p><p><strong>请记住：</strong>这是一个教育原型，旨在教授实体匹配概念。生产系统可能会使用不同的大型语言模型 (LLM)、自定义匹配规则、专门的判断管道或结合多种匹配策略的集成方法。</p><h2>问题：为何匹配如此困难</h2><p>人类语言是一种非凡的事物。它最有趣的特性之一是其无限的创造力。我们可以生成并理解无数的新句子。那么，在实体解析中完全精确的匹配极为罕见，这还奇怪吗？作者们在可能的情况下都力求创新。如果每次提到某个实体时，我们都必须书写和阅读完整名称，那将会变得非常乏味。因此，尽管精确匹配很简单，但现实情况是我们需要一种更复杂的方法来进行实体解析：这种方法必须足够强大，以处理人类作者无限创造力中的至少一部分挑战。这就是我们将问题分解为两个步骤的原因：使用 Elasticsearch 大规模检索可能的候选实体，然后使用 LLM 来判断这些候选实体是否真正指向同一个现实世界中的实体。</p><h2>解决方案：三步匹配与透明的 LLM 判断</h2><p>我们正处于使用计算机方式的范式转变之中。正如互联网的兴起将我们从本地计算带入全球互联网络一样，生成式 AI (GenAI) 正在从根本上改变内容、代码和信息的创建方式。事实上，伴随本系列的教育型原型几乎完全是作者通过精心设计提示词，使用 LLM “vibe coded” 出来的。这并不是说 LLM 已经或将要达到人类语言所固有的那种生产力，但这确实意味着我们现在拥有一个强大的资源来帮助进行实体解析。</p><p>我们在使用生成式 AI 时的一个常见模式是检索增强生成 (RAG)。在这里，<em>检索</em>检索意味着检索实体候选（而不是生成答案），LLM 严格用于匹配评估和解释。虽然我们<em>可以</em>要求 LLM 帮助我们进行端到端的实体解析，但这在时间和金钱上都是一种成本高昂的方法。RAG 通过使用更高效的方式为 LLM 提供上下文，从而帮助 LLM 完成工作，进而使 LLM 能够有效地协助实体解析。</p><p>对于 RAG 中的检索部分，我们再次求助于 Elasticsearch。我们首先使用精确匹配、别名匹配以及结合了关键词和语义搜索的混合搜索来寻找潜在的匹配项。一旦找到这些潜在匹配项，我们就将它们发送给 LLM 进行判断。LLM 充当最终的匹配评估器。我们还让 LLM 解释其推理过程，这是与其他实体解析系统的一个重要区别。没有这些解释，实体解析就是一个黑匣子；有了它们，我们可以亲眼看到为什么某个匹配是合理的。</p><h2>关键概念：三步匹配、混合搜索和透明 LLM 判断</h2><p><strong>什么是三步匹配？</strong>在项目开始时，我们假设语义搜索将是系统的一个关键部分，但并非每个匹配都需要如此复杂的搜索。为了有效地找到匹配项，我们采用了渐进式的方法。首先，我们使用关键词搜索检查完全精确的匹配。如果找到这样的匹配，我们的工作就完成了，可以继续下一个。如果精确匹配失败，我们转向别名匹配。为简化起见，在原型中，别名匹配也是使用关键词进行精确匹配完成的。在生产环境中，您可能会通过标准化、音译规则、模糊匹配或精心管理的别名表来扩展这一步。如果在前两步之后仍未找到潜在的匹配项，那么是时候通过 Elasticsearch 的混合搜索（结合了倒数排序融合）来引入语义搜索了。</p><p><strong>什么是混合搜索？</strong>在 Elasticsearch 中，我们可以使用语义搜索来找到将上下文考虑在内的有意义的匹配。Elasticsearch 广泛用于向量搜索和混合检索。语义相似性对于理解含义非常强大，但它不能替代结构化过滤（例如，按时间范围、位置或标识符过滤），并且在存在精确匹配时通常是不必要的。Elasticsearch 以其词汇搜索而闻名，这在不适合语义搜索的任务中表现出色。为了充分利用这两种方法，我们在单个混合查询中将词汇搜索与语义搜索结合使用。然后，我们使用 RRF 合并结果，以找到最可能的匹配项。在原型中，排名前两位的结果成为可以发送给 LLM 进行判断的潜在匹配项。</p><p><strong>为什么需要 LLM 判断？</strong>LLM 的判断和解释使得我们的系统能够透明地处理歧义和上下文。这对于像“the president”这样可能根据上下文指代多个实体的情况至关重要，但它也使昵称和文化差异等情况在系统中能够很好地处理。最后，当我们考虑关键任务，例如识别制裁名单中的实体时，我们需要知道匹配被接受的原因，才能信任该系统。至关重要的是，LLM 并不搜索整个语料库；它只评估 Elasticsearch 返回的那一小部分候选集。</p><h2>实际结果：通过 LLM 推理进行匹配</h2><p>任何自然语言处理任务的一个主要挑战是创建一份黄金文档，一份告诉我们预期结果是什么的“答案”。没有它，几乎不可能判断一个系统在某个任务上表现如何，但创建这样一份文档可能是一个费力且费时的过程。对于实体解析原型，我们再次求助于生成式 AI 来帮助建立我们可以用来测试的数据。</p><p>我们首先定义了几种挑战类型，例如昵称和音译，然后要求 LLM 创建一个分层的数据集集合，这些数据集将逐渐变大，对系统来说也更具挑战性。数据集的创建并不像人们希望的那样简单。LLM 有一种强烈的“作弊”倾向，使得获取正确答案变得过于容易。例如，其中一种挑战类型侧重于语义上下文。这种类型包括将“Russian author”解读为“Leo Tolstoy”。LLM 错误地将“Russian author”作为“Leo Tolstoy”的一个别名，这就没有必要通过混合搜索来寻找匹配项了。</p><p>在进行了几次重构以修复此类问题后，我们有了五个可供使用的数据集层级。第 1-4 层规模逐渐增大，包含的挑战类型也更多。第 5 层是“终极挑战”数据集，由所有挑战类型中最棘手的例子组成。所有测试数据都可以在<a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/comprehensive_evaluation">全面评估目录</a>中找到。</p><p>为了评估我们基于提示的实体解析方法，我们将注意力集中在第 4 层数据集上。一个重要的说明是，评估是作为受控实验进行的，这样我们可以专注于实体匹配的质量。观察清单数据预先丰富了上下文，并且实体是提前从文章中提取出来的。这确保了评估的重点是匹配而非提取的准确性。这将匹配质量孤立出来；端到端的性能还将额外取决于提取的召回率和丰富数据的质量。</p><h3>评估数据集</h3><p>第 4 层评估数据集对系统的能力提供了一个全面的测试：[1]</p><ul><li><p><strong>观察清单实体：</strong>跨不同类型（人物、组织、地点）的 66 个实体。</p></li><li><p><strong>测试文章：</strong>69 篇涵盖现实世界实体解析场景的文章。</p></li><li><p><strong>预期匹配：</strong>所有文章中预期的 206 个实体匹配。</p></li><li><p><strong>挑战类型：</strong>测试实体解析各个方面的 15 种不同挑战类型。</p></li></ul><p>数据集中包含的挑战类型有：</p><ul><li><p><strong>昵称：</strong>“Bob Smith” → “Robert Smith”（七篇文章）。</p></li><li><p><strong>头衔和尊称：</strong> “Dr. Sarah Williams” → “Sarah Williams”（五篇文章）。</p></li><li><p><strong>语义上下文：</strong>“Russian author” → “Leo Tolstoy”（八篇文章）。</p></li><li><p><strong>多语言名字：</strong>处理不同书写系统中的名称（六篇文章）。</p></li><li><p><strong>商业实体：</strong> 公司名称变体（七篇文章）。</p></li><li><p><strong>高管引用：</strong>“Microsoft CEO”→“Satya Nadella”（五篇文章）。</p></li><li><p><strong>政治领导人：</strong>基于头衔的引用（五篇文章）。</p></li><li><p><strong>名称首字母：</strong>“J.Smith” → “John Smith”（三篇文章）。</p></li><li><p><strong>名称顺序变体：</strong>不同的名称顺序惯例（三篇文章）。</p></li><li><p><strong>名称截断：</strong>部分名称匹配（三篇文章）。</p></li><li><p><strong>名称拆分：</strong>名称在文本中拆分（三篇文章）。</p></li><li><p><strong>缺少空格/连字符：</strong>格式变体（两篇文章）。</p></li><li><p><strong>音译：</strong>跨书写系统的名称匹配（两篇文章）。</p></li><li><p><strong>组合挑战：</strong>一篇文章中包含多个挑战（共六篇文章）。</p></li><li><p><strong>复杂商业关系：</strong>分层商业关系（五篇文章）。</p></li></ul><p>让我们看看基于提示的实体解析表现如何。</p><h3>整体性能</h3><p>结果显示，由 LLM 驱动的匹配评估前景广阔，但也揭示了一个显著的可靠性问题。因为每个候选对都必须由 LLM 进行评估，结构化输出的失败可能会抑制接受率和召回率，即使检索环节工作正常。</p><p>指标</p><p>值</p><p>精确率</p><p>83.8%</p><p>召回</p><p>62.6%</p><p>F1 分数</p><p>71.7%</p><p>找到的总匹配数</p><p>344</p><p>LLM 接受率</p><p>44.8%</p><p>错误率</p><p>30.2%</p><h3>错误率问题</h3><p>回顾一下，我们在原型中采取的第一步是使用 Elasticsearch 创建潜在的匹配对。每个这样的潜在匹配都需要由 LLM 进行评估。为了高效地处理所有这些匹配项，我们将 LLM 调用批量组合在一起。这降低了 API 成本和延迟，但也增加了在输出中得到格式错误 JSON 的风险。随着批量大小的增加，JSON 变得更长、更复杂，使得 LLM 更有可能生成无效的 JSON。这就是 30% 错误率的来源。在评估中，我们每个请求使用 5 个匹配项的批量大小。即使采用这个保守的批量大小，我们仍然遇到 JSON 解析失败的情况，这显著地影响了评估结果。</p><h2>下一步：优化 LLM 集成</h2><p>现在，我们已经使用语义搜索和 LLM 判断匹配了实体，我们拥有了一个完整的实体解析管道。然而，这种方法引入了一种新的故障模式：当模型的判断正确，但其输出却不可用时。我们可以优化 LLM 集成以获得更好的可靠性和成本效益。在下一篇文章中，我们将探讨如何使用函数调用来实现结构化输出，这可以在减少错误和成本的同时，提供有保障的结构和类型安全。</p><h2>亲自试用</h2><p>想亲眼看看实体匹配是如何运作的吗？请查看<a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/notebooks#:~:text=5%20minutes%20ago-,03_entity_matching_v3.ipynb,-Initial%20public%20lab">实体匹配笔记本</a>，它通过实际实现、详细解释和动手示例，提供了完整的实践指南。该笔记本精确地向您展示了如何使用三步搜索、带有 RRF 的混合搜索以及由 LLM 驱动的带推理的判断来匹配实体。</p><p><strong>请记住：</strong>这是一个教育原型，旨在教授这些概念。在构建生产系统时，需要考虑额外的因素，如模型选择、成本优化、延迟要求、质量验证、错误处理和监控等，而这些在本学习重点的原型中并未涵盖。</p><h2>备注</h2><ol><li><p>这些数据集是合成的，专为教育目的设计；它们模拟了真实的挑战，但不代表任何单一的生产环境领域。</p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-semantic-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-semantic-search</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[混合搜索]]></category>
    <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltefc59243d9990405/6a17056ab339d5778f769ebf/473ca4357c7d60f690edbd2a844acda169aca9c3-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 26 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[以最低得分阈值确保语义精确性]]></title>
    <description><![CDATA[通过设定最低得分阈值提升语义精确性。本文提供语义搜索与混合搜索的实操案例。 ]]></description>
    <content:encoded><![CDATA[<p>语义搜索为检索相关性开辟了全新可能。以 ELSER、E5、Jina Embedding v4 为代表的高质量稀疏-稠密混合模型，通过解析词义而非简单关键词匹配返回相关结果。然而，这类模型在处理长尾查询或索引缺乏相关内容时，可能返回无关结果，这种特性既可能导致用户困惑，也会造成大型语言模型 (LLM) 的算力资源浪费。</p><p>本文将介绍如何使用最低得分参数来提高语义搜索结果的精确度。如想测试本博客文章中提供的示例，请访问 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/ensuring-semantic-precision-with-minimum-score/ensuring_semantic_precision_with_minimum_score.ipynb">相关 Jupyter 笔记本</a>。</p><h2>背景：精确率和召回率</h2><p>在搜索相关性中，<em>精确率</em>和<em>召回率</em>是关键概念。强烈建议尚不熟悉这些内容的读者查阅相关资料。以下是摘要。</p><ul><li><p><strong>精确率：</strong>返回的搜索结果中与用户相关的比例。</p></li><li><p><strong>召回率：</strong>搜索结果集中包含的语料库中所有相关文档的百分比。</p></li></ul><p>或者换句话说，精确率<strong>只</strong>返回相关结果，而召回率则返回<strong>所有</strong>相关结果。可以想象，这些需求经常相互冲突。语义搜索往往具有很高的召回率，但在精确率方面可能会不理想。继续阅读，了解如何避免这种情况。</p><h2>推出最低得分参数</h2><p>min_score参数通过设定最低得分阈值提升检索精度，系统将自动过滤得分低于该阈值的匹配结果，从而精简结果集。以下是一个简单的示例：</p>GET search-movies/_search
{
  "retriever": {
    "linear": {
      "min_score": 4,
      "retrievers": [
        ...
      ]
    }
  }
}<h2>得分归一化</h2><p>设置最低得分阈值固然可行，但并非所有语义模型都能返回适用于静态阈值的分值。以 ELSER 为例，它所返回的分值是无界得分。<a href="https://huggingface.co/intfloat/e5-small#faq">有些</a>密集模型得分是密集聚类的，只有在特定查询的背景下才有意义。</p><p>对于大多数语义搜索情况，我们建议在应用“min_score”之前使用归一化方法。归一化确保文档得分在规定区间内。Elasticsearch 检索器提供了两种此类<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/linear-retriever#linear-retriever-normalizers">归一化器</a>，即“l2_norm”和“minmax”。最常用的是“minmax”，因为它简单易懂，在很多情况下都很有效。“minmax”的主要属性包括：</p><ul><li><p>文档分数分布在 0 到 1 之间。</p></li><li><p>得分最高的文件总是记为 1 分。</p></li><li><p>得分最低的文件总是记为 0 分。</p><ul><li><p>这可能会使其不太适合关键字搜索。更多讨论请参见“混合搜索”部分。</p></li></ul></li></ul><p>以下是一个包含<code>min_score</code>规范化语义查询的示例。排名窗口参数已增加到 500，使系统能够返回从第 100 条开始的更长结果列表。</p>GET search-movies/_search
{
  "size": 100,
  "_source": [
    "title", "overview"
  ],
  "retriever": {
    "linear": {
      "rank_window_size": 500,
      "min_score": 0.25,
      "retrievers": [
        {
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "overview_vector",
                  "query": "superhero movie"
                }
              }
            }
          }
        }
      ]
    }
  }
}<p>当前参数已设置为高于生产环境的常规值，以便我们全面检验搜索结果质量并针对性优化输出。</p><h2>使用线性检索器的混合搜索</h2><p>对于混合搜索，最简单的方法是归一化所有分数，分配权重，并应用最低得分。请注意，通过选择总和为 1 的权重，可以将总分控制在 0-1 的范围内。这样使得最终得分易于解读，且便于调整 <code>min_score</code>。以下是一个示例：</p>GET search-movies/_search
{
  "size": 100,
  "_source": ["title", "overview","keywords"],
  "retriever": {
    "linear": {
      "rank_window_size": 500,
      "min_score": 0.25,
      "retrievers": [
        {
          "weight": 0.6,
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "overview_vector",
                  "query": "superhero movie"
                }
              }
            }
          }
        },
        {
          "weight": 0.4,
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "multi_match": {
                  "query": "superhero movie",
                  "fields": ["overview","keywords", "title"],
                  "type": "cross_fields",
                  "minimum_should_match": "2"
                }
              }
            }
          }
        }
      ]
    }
  }
}<h2>使用 RRF 的混合搜索</h2><p>使用 BM25 时，我们通常通过其他方式控制精度，例如使用<code>AND</code> 操作符或<code>minimum_should_match</code> 。此外，由单个、精确和罕见术语组成的查询自然会导致搜索结果较少，而且往往都是高度相关的结果。这就可能导致：</p><ul><li><p>在 BM25 检索器中，排名靠后的结果即使绝对 BM25 分值接近头部结果，仍会被赋予较低的归一化分数。</p></li><li><p>将极低的 BM25 分值与语义分值相加，总分即可近似为语义分值。</p></li><li><p>缺少 BM25 分值参考可能导致 <code>min_score threshold</code> 丢弃该文档。</p></li></ul><p>作为解决方案，我们可以改用倒数排序融合 (RRF) 来结合 BM25 和语义结果。该方法通过关注各结果集中的文档位置而非原始分值，巧妙规避了不同检索算法评分体系难以直接比较的技术难题。在这种情况下，<code>min_score</code> 仅应用于语义检索器。</p>GET search-movies/_search
{
  "_source": ["title", "overview","keywords"],
  "retriever": {
    "rrf": {
      "rank_window_size": 500,
      "retrievers": [
        {
          "linear": {
            "rank_window_size": 500,
            "min_score": 0.25,
            "retrievers": [
              {
                "normalizer": "minmax",
                "retriever": {
                  "standard": {
                    "query": {
                      "semantic": {
                        "field": "overview_vector",
                        "query": "superhero movie"
                      }
                    }
                  }
                }
              }
            ]
          }
        },
        {
          "standard": {
            "query": {
              "multi_match": {
                "query": "superhero movie",
                "fields": ["overview", "keywords","title"],
                "type": "cross_fields",
                "minimum_should_match": "2"
              }
            }
          }
        }
      ]
    }
  }
}<h2>结论</h2><p>通过采用 <code>min_score</code>，我们已验证可有效降低语义检索算法因高召回率导致的结果集中误报数量。要了解有关检索器的更多信息，请参阅本<a href="https://www.elastic.co/search-labs/blog/elasticsearch-retrievers">博文</a>和<a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">Elasticsearch 文档</a>。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/semantic-precision-minimum-score</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/semantic-precision-minimum-score</guid>
    <category><![CDATA[相关性]]></category>
    <category><![CDATA[混合搜索]]></category>
    <dc:creator><![CDATA[Mattias Brunnert]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4a3fba607900049/6a170e0fcdacbf8fe17d2a7a/8b3b5910abfe16d48d309341a0027008b16c4340-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 Elasticsearch 构建 ChatGPT 连接器以查询 GitHub 问题]]></title>
    <description><![CDATA[学习如何构建自定义 ChatGPT 连接器并部署使用混合搜索查询内部 GitHub 问题的 Elasticsearch MCP 服务器。]]></description>
    <content:encoded><![CDATA[<p>最近，OpenAI 宣布为专业版/商务版/企业版和教育版 ChatGPT 提供<a href="https://help.openai.com/en/articles/11487775-connectors-in-chatgpt">自定义连接器</a>功能。除了提供开箱即用的连接器来获取 Gmail、GitHub、Dropbox 等平台上的数据。还可以使用 MCP 服务器创建自定义连接器。</p><p>定制连接器使您能够将现有的 ChatGPT 连接器与其他数据源（如 Elasticsearch）结合，以获得全面的答案。</p><p>在本文中，我们将构建一个 <a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP</a> 服务器，将 ChatGPT 连接到包含内部 GitHub 问题和拉取请求信息的 Elasticsearch 索引。这样就可以使用 Elasticsearch 数据回答自然语言查询。</p><p>我们将在 Google Colab 上使用 <a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a> 和 ngrok 部署 MCP 服务器，以获取 ChatGPT 可以连接的公共 URL，从而省去复杂的基础架构设置。</p><p>有关 MCP 及其生态系统的全面概述，请参阅《<a href="https://www.elastic.co/search-labs/blog/mcp-current-state">MCP 的现状</a>》。</p><h2>准备工作</h2><p>在开始之前，您需要：</p><ul><li><p>Elasticsearch 集群（8.X 或更高版本）</p></li><li><p>Elasticsearch API密钥，具有对您的索引的读取访问权限</p></li><li><p>Google 账户（用于 Google Colab）</p></li><li><p>Ngrok账户 （免费套餐可用）</p></li><li><p>拥有专业版/企业版/商务版或教育版套餐的 ChatGPT 账户</p></li></ul><h2>了解 ChatGPT MCP 连接器的要求</h2><p>ChatGPT MCP 连接器需要实现两个工具：<code>search</code> 和 <code>fetch</code>。有关更多详情，请参阅 <a href="https://platform.openai.com/docs/mcp#create-an-mcp-server">OpenAI 文档</a>。</p><h3><a href="https://platform.openai.com/docs/mcp#search-tool">搜索工具</a></h3><p>根据用户查询，从 Elasticsearch 索引中返回相关结果列表。</p><h4>接收的内容：</h4><ul><li><p>一个单一的字符串，包含用户的自然语言查询。</p></li><li><p>示例：“查找与 Elasticsearch 迁移相关的问题。”</p></li></ul><h4>返回的内容：</h4><ul><li><p>一个对象，其<code>result</code> 关键字包含一个结果对象数组。每个结果包括：</p><ul><li><p><code>id</code> - 唯一文档标识符</p></li><li><p><code>title</code> - 问题或拉取请求标题</p></li><li><p><code>url</code> - 链接到问题或 PR</p></li></ul></li></ul><h4>在我们的实现中：</h4>return {
    "results": [
        {
            "id": "PR-612",
            "title": "Fix memory leak in WebSocket notification service",
            "url": "https://internal-git.techcorp.com/pulls/612"
        },
        # ... more results
    ]
}<h3><a href="https://platform.openai.com/docs/mcp#fetch-tool">获取工具</a></h3><p>获取指定文档的完整内容。</p><h4>接收的内容：</h4><ul><li><p>搜索结果中包含 Elasticsearch 文档 ID 的单个字符串</p></li><li><p>示例：“获取 PR-578 的详细信息。”</p></li></ul><h4>它返回的内容：</h4><ul><li><p>一个完整的文档对象，包含：</p><ul><li><p><code>id</code> - 唯一文档标识符</p></li><li><p><code>title</code> - 问题或拉取请求标题</p></li><li><p><code>text</code> - 完整的问题/PR描述和详细信息</p></li><li><p><code>url</code> - 链接到问题或 PR</p></li><li><p><code>type</code> - 文档类型（问题、pull_request）</p></li><li><p><code>status</code> - 当前状态（打开、进行中、已解决）</p></li><li><p><code>priority</code> - 优先级别（低、中、高、关键）</p></li><li><p><code>assignee</code> - 负责此问题/PR 的人员</p></li><li><p><code>created_date</code> - 何时创建</p></li><li><p><code>resolved_date</code> - 何时解决（如适用）</p></li><li><p><code>labels</code> - 与文件相关的标签</p></li><li><p><code>related_pr</code> － 相关拉取请求 ID</p></li></ul></li></ul>return {
    "id": "PR-578",
    "title": "Security hotfix: Patch SQL injection vulnerabilities",
    "text": "Description: CRITICAL SECURITY FIX for ISSUE-1889. Patches SQL...",
    "url": "https://internal-git.techcorp.com/pulls/578",
    "type": "pull_request",
    "status": "closed",
    "priority": "critical",
    "assignee": "sarah_dev",
    "created_date": "2025-09-19",
    "resolved_date": "2025-09-19",
    "labels": "security, hotfix, sql",
    "related_pr": null
}<p><strong>注意</strong>：本示例使用扁平结构，其中所有字段都位于根级别。OpenAI 的要求非常灵活，还支持嵌套的元数据对象。</p><h2>GitHub 问题和 PR 数据集</h2><p>在本教程中，我们将使用包含问题和拉取请求的内部 GitHub 数据集。这代表了一个您希望通过 ChatGPT 查询私有、内部数据的场景。</p><p>数据集可以在<a href="https://gist.github.com/TomasMurua/4e7bbdf7a7ebbdffaa663c43578d934a">此处</a>找到。我们将使用<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk">批量 API</a> 更新数据索引。</p><p>这个数据集包含：</p><ul><li><p>有关描述、状态、优先级和分配人员的问题</p></li><li><p>包含代码更改、审查和部署信息的拉取请求</p></li><li><p>问题与 PR 之间的关系（例如，PR-578 修复了 ISSUE-1889）</p></li><li><p>标签、日期和其他元数据</p></li></ul><h3>索引映射</h3><p>该索引使用以下<a href="https://www.elastic.co/docs/manage-data/data-store/mapping">映射</a>来支持使用 <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-elser">ELSER</a> 的混合搜索。<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">text_semantic</a> 用于语义搜索，而其他字段用于关键字搜索。</p>{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "title": {
        "type": "text"
      },
      "text": {
        "type": "text"
      },
      "text_semantic": {
        "type": "semantic_text",
        "inference_id": ".elser-2-elasticsearch"
      },
      "url": {
        "type": "keyword"
      },
      "type": {
        "type": "keyword"
      },
      "status": {
        "type": "keyword"
      },
      "priority": {
        "type": "keyword"
      },
      "assignee": {
        "type": "keyword"
      },
      "created_date": {
        "type": "date",
        "format": "iso8601"
      },
      "resolved_date": {
        "type": "date",
        "format": "iso8601"
      },
      "labels": {
        "type": "keyword"
      },
      "related_pr": {
        "type": "keyword"
      }
    }
  }
}<h2>构建MCP服务器</h2><p>我们的 MCP 服务器按照 OpenAI 规范实现了两个工具，使用混合搜索将语义和文本匹配相结合，以获得更好的结果。</p><h3>搜索工具</h3><p>利用 <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">RRF</a>（倒数排序融合）进行混合搜索，将语义搜索与文本匹配相结合：</p>@mcp.tool()
    async def search(query: str) -&gt; Dict[str, List[Dict[str, Any]]]:
        """
        Search for internal issues and PRs using hybrid search (semantic + text with RRF).
        Returns list with id, title, and url per OpenAI spec.
        """
        if not query or not query.strip():
            return {"results": []}

        logger.info(f"Searching for: '{query}'")

        try:
            # Hybrid search with RRF (Reciprocal Rank Fusion)
            response = es_client.search(
                index=ELASTICSEARCH_INDEX,
                size=10,
                source=["id", "title", "url", "type", "priority"],
                retriever={
                    "rrf": {
                        "retrievers": [
                            {
                                # Semantic search with ELSER
                                "standard": {
                                    "query": {
                                        "semantic": {
                                            "field": "text_semantic",
                                            "query": query
                                        }
                                    }
                                }
                            },
                            {
                                # Text search (BM25) for keyword matching
                                "standard": {
                                    "query": {
                                        "multi_match": {
                                            "query": query,
                                            "fields": [
                                                "title^3",
                                                "text^2",
                                                "assignee^2",
                                                "type",
                                                "labels",
                                                "priority"
                                            ],
                                            "type": "best_fields",
                                            "fuzziness": "AUTO"
                                        }
                                    }
                                }
                            }
                        ],
                        "rank_window_size": 50,
                        "rank_constant": 60
                    }
                }
            )

            results = []
            if response and 'hits' in response:
                for hit in response['hits']['hits']:
                    source = hit['_source']
                    results.append({
                        "id": source.get('id', hit['_id']),
                        "title": source.get('title', 'Unknown'),
                        "url": source.get('url', '')
                    })

            logger.info(f"Found {len(results)} results")
            return {"results": results}

        except Exception as e:
            logger.error(f"Search error: {e}")
            raise ValueError(f"Search failed: {str(e)}")<h3>要点：</h3><ul><li><p><strong>使用 RRF 的混合搜索：</strong>结合语义搜索 (ELSER) 和文本搜索 (BM25)，以获得更好的结果。</p></li><li><p><strong>多匹配查询：</strong><a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-multi-match-query">在多个字段中进行搜索</a>，并使用增强功能（标题^3、文本^2、分配人员^2）。插入符号 (^) 会乘以相关性分数，优先考虑标题中的匹配项而非内容中的匹配项。</p></li><li><p><strong>模糊匹配：</strong><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/common-options#fuzziness"><code>fuzziness: AUTO</code></a> 通过允许近似匹配来处理错别字和拼写错误。</p></li><li><p><strong>RRF 参数调整：</strong></p><ul><li><p><code>rank_window_size: 50</code> - 指定在合并之前从每个检索器（语义和文本）中考虑最靠前结果的数量。</p></li><li><p><code>rank_constant: 60</code> - 该值决定了单个结果集中的文档对最终排序结果的影响程度。</p></li></ul></li><li><p><strong>仅返回必填字段：</strong>根据 OpenAI 规范返回 <code>id</code>、<code>title</code>、<code>url</code>，避免不必要地暴露其他字段。</p></li></ul><h3>获取工具</h3><p>按文档 ID（如果存在）检索文档详细信息：</p>@mcp.tool()
    async def fetch(id: str) -&gt; Dict[str, Any]:
        """
        Retrieve complete issue/PR details by ID.
        Returns id, title, text, url.
        """
        if not id:
            raise ValueError("ID is required")

        logger.info(f"Fetching: {id}")

        try:
            # Search by the 'id' field (not _id) since IDs are stored as a field
            response = es_client.search(
                index=ELASTICSEARCH_INDEX,
                body={
                    "query": {
                        "term": {
                            "id": id  # Search by your custom 'id' field
                        }
                    },
                    "size": 1
                }
            )

            if not response or not response['hits']['hits']:
                raise ValueError(f"Document with id '{id}' not found")

            hit = response['hits']['hits'][0]
            source = hit['_source']

            result = {
                "id": source.get('id', id),
                "title": source.get('title', 'Unknown'),
                "text": source.get('text', ''),
                "url": source.get('url', ''),
                "type": source.get('type', ''),
                "status": source.get('status', ''),
                "priority": source.get('priority', ''),
                "assignee": source.get('assignee', ''),
                "created_date": source.get('created_date', ''),
                "resolved_date": source.get('resolved_date', ''),
                "labels": source.get('labels', ''),
                "related_pr": source.get('related_pr', '')
            }

            logger.info(f"Fetched: {result['title']}")
            return result

        except Exception as e:
            logger.error(f"Fetch error: {e}")
            raise ValueError(f"Failed to fetch '{id}': {str(e)}")<h3>要点：</h3><ul><li><p><strong>按文档 ID 字段进行搜索：</strong>使用自定义 <code>id</code> 字段上的术语查询</p></li><li><p><strong>返回完整文档：</strong>包含完整的 <code>text</code> 字段及其所有内容</p></li><li><p><strong>扁平结构：</strong>所有字段均位于根级别，与 Elasticsearch 的文档结构相匹配。</p></li></ul><h2>在 Google Colab 上部署</h2><p>我们将使用 Google Colab 来运行 MCP 服务器，并使用 ngrok 将其公开，以便 ChatGPT 可以连接到它。</p><h3>步骤 1：打开 Google Colab 笔记本</h3><p>访问我们预配置的笔记本<a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/elasticsearch-chatgpt-connector">适用于 ChatGPT 的 Elasticsearch MCP</a>。</p><h3>步骤 2：配置您的凭据</h3><p>您需要三项信息：</p><ul><li><p><strong>Elasticsearch URL：</strong>您的 <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-enterprise/connect-elasticsearch">Elasticsearch 集群 URL</a>。</p></li><li><p><strong>Elasticsearch API 密钥：</strong>具有索引读取权限的 <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">API 密钥</a>。</p></li><li><p><strong>Ngrok 身份验证令牌：来自 </strong><a href="https://ngrok.com/">ngrok</a> 的免费令牌。我们将使用 ngrok 将 MCP URL 公开到互联网，以便 ChatGPT 可以连接到它。</p></li></ul><h4>获取 ngrok 令牌</h4><ol><li><p>在 <a href="https://ngrok.com/">ngrok</a> 注册免费账户</p></li><li><p>前往您的 <a href="https://dashboard.ngrok.com/">ngrok 仪表板</a></p></li><li><p>复制您的身份验证令牌</p></li></ol><h4>为 Google Colab 添加机密</h4><p>在 Google Colab 笔记本中：</p><ol><li><p>点击左侧边栏中的“<strong>密钥图标</strong>”以打开“<strong>机密</strong>”。</p></li><li><p>添加这三个秘密：</p></li></ol>ELASTICSEARCH_URL=https://your-cluster.elastic.com:443
ELASTICSEARCH_API_KEY=your-api-key
NGROK_TOKEN=your-ngrok-token<p>3. 为每个机密启用笔记本访问权限</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5acae97b386277f8/6a17f08f5ea30f74c964b6c2/d5dd6ac19fe816a562c6351fdb0f11369da0e877-609x321.jpg" alt="向 Google Collab 添加敏感信息" /><h3>步骤 3：运行 Notebook</h3><ol><li><p>点击“<strong>运行时</strong>”，然后点击“<strong>全部运行</strong>”，以执行所有单元格</p></li><li><p>等待服务器启动（约30秒）</p></li><li><p>查找显示您的公开 ngrok URL 的输出</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdd11aacf2deab67c/6a17f091e8fbce81f13a1a41/f185100e8869624bc9e1c7b2b4eb32785e2d89e7-1189x283.png" alt="" /><p>4. 该输出将显示如下内容：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8891d917fdbaaf48/6a17f092abe0f208c7dfeaf6/e02e625e91ed9136454e4401b184575fb03a336e-1052x465.jpg" alt="在 Google Collab 中运行笔记本的输出结果" /><h2>连接 ChatGPT</h2><p>现在我们将 MCP 服务器连接到您的 ChatGPT 账户。</p><ol><li><p>打开 ChatGPT，前往“<strong>设置</strong>”。</p></li><li><p>导航到<strong>“连接器”。</strong>如果您使用的是专业版账户，则需要在连接器中打开“<a href="https://platform.openai.com/docs/guides/developer-mode">开发者模式</a>”。</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt95efdcb2c39307e7/6a17f094abe0f24d8edfeafa/32c02192912fc0e7e5a52e9399077ba7ae3b4901-739x715.png" alt="将 MPC 服务器连接到 ChatGPT 账户" /><p><em>如果您使用的是 ChatGPT 企业版或商业版，您需要将连接器发布到您的工作场所。</em></p><p>3. 点击“<strong>创建</strong>”。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4c8fc8dd6033918/6a17f095631730de19585b7b/15c53e5ccc381108a9dc0052cca05bf0fc97679a-755x683.png" alt="向 ChatGPT 添加连接器" /><p><em><strong>注意</strong></em><em>：在商业版、企业版和教育版工作区中，只有工作区所有者、管理员和已启用相应设置（针对企业版/教育版）的用户才能添加自定义连接器。具有普通成员角色的用户无法自行添加自定义连接器。</em></p><p><em>一旦连接器被所有者或管理员用户添加并启用，工作区中的所有成员即可使用该连接器。</em></p><p>4. 输入所需信息和以 <code>/sse/</code> 结尾的 ngrok URL。请注意“sse”后面的“/”。没有它就无法正常工作：</p><ul><li><p><strong>名字：</strong> Elasticsearch MCP</p></li><li><p><strong>描述：</strong>用于搜索和获取 GitHub 内部信息的自定义 MCP。</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd716ad0beeeb1d35/6a17f09714d90c11cc79b6d7/162a85705cc8ac48a3f2f665551d513e0719f93d-479x684.png" alt="创建一个 Elastic MCP 连接器 " /><p>5. 按下“<strong>创建</strong>”保存自定义 MCP。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt857794237d7d3b5a/6a17f0983e03d729b74f2d54/97eb5fb0a32b86bfadfb35561f698616f217c049-913x629.png" alt="点击创建，保存自定义 MCP 连接器" /><p>如果您的服务器正在运行，则连接是即时的。无需额外的身份验证，因为 Elasticsearch API 密钥已在服务器中配置。</p><h2>测试 MCP 服务器</h2><p>在提问之前，您需要先选择 ChatGPT 应该使用的连接器。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1602c48878dc7a9/6a17f09a6df731cca90a0fff/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" alt="选择 ChatGPT 应使用的连接器" /><h3>提示 1: 搜索问题</h3><p>提问：“<strong>查找与 Elasticsearch 迁移相关的问题”</strong>并确认操作工具调用。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c204ceacf897f61/6a17f09c9da390fb1de4657d/cfd781acbff8cd7c8095bbe29224f8b26d581f77-650x375.png" alt="让 ChatGPT“查找与 Elasticsearch 迁移相关的问题&quot;并确认调用工具的操作。" /><p>ChatGPT 将调用<code>search</code> 工具处理您的查询。你可以看到它正在查找可用工具，并准备调用 Elasticsearch 工具，在对该工具执行任何操作之前与用户确认。</p><h4>工具调用请求：</h4>{
  "query": "Elasticsearch migration issues"
}<h4>工具响应：</h4>{
  "results": [
    {
      "id": "PR-598",
      "title": "Elasticsearch 8.x migration - Application code changes",
      "url": "https://internal-git.techcorp.com/pulls/598"
    },
    {
      "id": "ISSUE-1712",
      "title": "Migrate from Elasticsearch 7.x to 8.x",
      "url": "https://internal-git.techcorp.com/issues/1712"
    },
    {
      "id": "RFC-045",
      "title": "Design Proposal: Microservices Migration Architecture",
      "url": "https://internal-git.techcorp.com/rfcs/045"
    }
    // ... 7 more results
  ]
}<p>ChatGPT 会处理这些结果，并以自然对话的形式呈现。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1b4378e7d26b4ad0/6a17f09ddbb4ff4de1fb57bf/9d5b6cff85c7e54ccc2584b8ae96d45495fae8c1-923x1352.png" alt="ChatGPT 如何处理工具调用请求和工具调用响应的结果" /><h3>幕后</h3><h4>提示：“查找与 Elasticsearch 迁移相关的问题”</h4><p>1. ChatGPT 调用 <code>search(“Elasticsearch migration”)</code></p><p>2. Elasticsearch 执行混合搜索。</p><ul><li><p><strong>语义搜索</strong>能理解“升级”和“<em>版本兼容性</em>”等概念。</p></li><li><p><strong>文本搜索</strong>可查找与“<em>Elasticsearch</em>”和“迁移”完全匹配的内容。</p></li><li><p><strong>RRF</strong> 将两种方法的结果进行合并和排序</p></li></ul><p>3. 返回与 <code>id</code>、<code>title</code> 匹配度最高的 10 个事件。 <code>url</code></p><p>4. ChatGPT 将“<em>ISSUE-1712：从 Elasticsearch 7.x 迁移到 8.x</em>”作为最相关的结果</p><h3>提示 2：获取完整的详细信息</h3><p>问：<em><strong>“请提供有关 ISSUE-1889 的详细信息”</strong></em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8d1a53db8bfe8326/6a17f09f445de966104d021a/5c0db5245535ce67a36056e61e135bddc97ce496-934x629.png" alt="ChatGPT 识别到您需要有关特定问题的详细信息，并调用 fetch 工具，在对该工具采取任何行动前与用户确认。" /><p>ChatGPT 识别到您需要有关特定问题的详细信息，并调用 <code>fetch</code> 工具，在对该工具采取任何行动前与用户确认。</p><h4>工具调用请求：</h4>{
  "id": "ISSUE-1889"
}<h4>工具响应：</h4>{
  "id": "ISSUE-1889",
  "title": "SQL injection vulnerability in search endpoint",
  "text": "Description: Security audit identified SQL injection vulnerability in /api/v1/search endpoint. User input from query parameter is not properly sanitized before being used in raw SQL query. Severity: HIGH - Immediate action required Affected Code: - File: services/search/query_builder.py - Line: 145-152 - Issue: String concatenation used instead of parameterized queries Investigation: - @security_team_alice: Confirmed exploitable with UNION-based injection - @sarah_dev: Checking all other endpoints for similar patterns - @john_backend: Found 3 more instances in legacy codebase Remediation: - Rewrite using SQLAlchemy ORM or parameterized queries - Add input validation and sanitization - Implement WAF rules as additional layer - Security regression tests Comments: - @tech_lead_mike: Stop all other work, this is P0 - @sarah_dev: PR-578 ready with fixes for all 4 vulnerable endpoints - @alex_devops: Deployed hotfix to production 2025-09-19 at 14:30 UTC - @security_team_alice: Verified fix, conducting full pentest next week Resolution: All vulnerable endpoints patched. Added pre-commit hooks to catch raw SQL queries. Security training scheduled for team.",
  "url": "https://internal-git.techcorp.com/issues/1889",
  "type": "issue",
  "status": "closed",
  "priority": "critical",
  "assignee": "sarah_dev",
  "created_date": "2025-09-18",
  "resolved_date": "2025-09-19",
  "labels": "security, vulnerability, bug, sql",
  "related_pr": "PR-578"
}<p>ChatGPT 会整合信息并清晰呈现。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt560958fa3bd212d0/6a17f0a0faa91355ba93c974/410f19f213e94fc4e3c47eeef6e04b69e0c86159-602x462.png" alt="ChatGPT 如何综合信息并显示 " /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcccf35a584e8373b/6a17f0a2505ac3471cad8c2e/54d8ffa117628a1e3afc317c3ab75d4f7731d7ab-767x1600.png" alt="ChatGPT 如何呈现信息" /><h3>幕后</h3><h4>提示：“获取有关 ISSUE-1889 的详细信息”</h4><ol><li><p>ChatGPT 调用 <code>fetch(“ISSUE-1889”)</code></p></li><li><p>Elasticsearch 会检索完整文档</p></li><li><p>返回一个包含所有字段在根级别的完整文档</p></li><li><p>ChatGPT会综合信息并提供正确的引用。</p></li></ol><h2>结论</h2><p>在本文中，我们构建了一个自定义 MCP 服务器，使用专用的<strong>搜索</strong>和<strong>获取</strong> MCP 工具将 ChatGPT 连接到 Elasticsearch，从而实现对私有数据的自然语言查询。</p><p>这种 MCP 模式适用于任何您想通过自然语言查询的 Elasticsearch 索引、文档、产品、日志或其他数据。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/chatgpt-connector-mcp-server-github-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/chatgpt-connector-mcp-server-github-elasticsearch</guid>
    <category><![CDATA[智能体 AI]]></category>
    <category><![CDATA[混合搜索]]></category>
    <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1602c48878dc7a9/6a17f09a6df731cca90a0fff/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" length="0" type="image/gif"/>
    <pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[混合搜索不头疼：用检索器简化混合搜索]]></title>
    <description><![CDATA[探索如何利用线性和 RRF 检索器的多字段查询格式简化 Elasticsearch 中的混合搜索，并在不了解 Elasticsearch 索引的情况下创建查询。]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/what-is/hybrid-search">混合搜索</a>被公认为是一种强大的搜索方法，它将<a href="https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch#lexical-search---sparse-retrieval">词法搜索</a>的精确性和速度与<a href="https://www.elastic.co/what-is/semantic-search">语义搜索</a>的自然语言能力结合在一起。不过，在实际应用中可能会很棘手，往往需要对索引有深入的了解，并通过非繁琐的配置来构建冗长的查询。在本博客中，我们将探讨<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers#multi-field-query-format">线性和 RRF 检索器的多字段查询格式</a>如何使混合搜索变得更简单、更易用，从而消除常见的头痛问题，让您更轻松地充分利用其强大功能。我们还将回顾多字段查询格式如何使您在不了解索引的情况下执行混合搜索查询。</p><h2>分数范围问题</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3521a6558cd477ca/6a17f174445de94d3c4d0225/c8b49153c47d2cdc233c0d2e440db04711d48ca5-1600x1600.jpg" alt="" /><p>首先，让我们回顾一下混合搜索困难的主要原因之一：不同的分数范围。我们的老朋友<a href="https://www.elastic.co/elasticon/conf/2016/sf/improved-text-scoring-with-bm25">BM25</a>会产生无限制的分数。换句话说，BM25 可以生成从接近 0 到（理论上）无穷大的分数。与此相反，针对<code>dense_vector</code> 字段的查询会产生介于 0 和 1 之间的分数。由于<code>semantic_text</code> 混淆了用于索引嵌入的字段类型，因此除非您对索引和推理端点配置有详细了解，否则很难说清查询的分数范围。这在试图交错使用词汇和语义搜索结果时会带来问题，因为即使语义结果更相关，词汇结果也可能优先于语义结果。对于这个问题，普遍接受的解决方案是在交织结果之前对分数进行归一化处理。Elasticsearch 为此提供了两种工具：<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/linear-retriever">线性</a>检索器和<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/rrf-retriever">RRF</a>检索器。
</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt09ed6bf3d25066bd/6a17f1750b0bedbd70dd3686/264481268c8b6ac259e3c257b85431b513f16672-1077x586.png" alt="使用线性/rrf 与不使用线性/rrf 的搜索结果比较" /><p><strong>RRF</strong>检索器采用<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">RRF 算法</a>，将文档排名作为衡量相关性的标准，并舍弃分数。由于不考虑分数，因此分数范围不匹配不是问题。</p><p><strong>线性</strong>检索器使用线性组合来确定文档的最终得分。这包括获取文档中每个组件查询的得分，对其进行归一化处理，然后求和生成总分。在数学上，这一操作可以表示为</p>Total Score = 𝚺(N(Sx))<p>其中<code>N</code> 是归一化函数，SX 是查询 X 的得分。归一化功能在这里非常关键，因为它将每个查询的得分转换为使用相同的范围。您可以<a href="https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search">在这里</a>了解有关线性寻回猎犬的更多信息。</p><h2>分解</h2><p>用户可以利用这些工具实现有效的混合搜索，但需要对索引有一定的了解。让我们看一个使用线性检索器的示例，在这个示例中，我们将查询一个包含两个字段的索引：</p>PUT linear_retriever_example
{
  "mappings": {
    "properties": {
      "semantic_text_field": { &lt;1&gt;
        "type": "semantic_text",
        "inference_id": ".multilingual-e5-small-elasticsearch"
      },
      "text_field": { &lt;2&gt;
        "type": "text"
      }
    }
  }
}<p>1.<code>semantic_text_field</code> 是一个<code>semantic_text</code> 字段，使用文本嵌入模型<a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5">E5</a></p><p><code>text_field</code> 是一个标准的<code>text</code> 字段</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "standard": {
              "query": {
                "match": { &lt;1&gt;
                  "semantic_text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}<p>1.我们在 字段上使用 查询，我们<a href="https://www.elastic.co/search-labs/blog/semantic-search-match-knn-sparse-vector#we-made-match-happen-in-semantic-search!"> 在 Elasticsearch 8.18/9.0</a> 中添加了对<code>match</code> 该查询的 支持<code>semantic_text</code></p><p>
在构建查询时，我们需要牢记<code>semantic_text_field</code> 使用的是文本嵌入模型，因此对它的任何查询都会产生 0 到 1 之间的分数。我们还需要知道<code>text_field</code> 是一个标准的<code>text</code> 字段，因此对它的查询将产生一个无限制的分数。为了创建具有适当相关性的结果集，我们需要使用一种检索器，在合并查询得分之前将其归一化。在本例中，我们使用了<code>minmax</code> 归一化的线性检索器，它将每个查询的得分归一化为介于 0 和 1 之间的值。</p><p>本例中的查询结构相当简单，因为只涉及两个字段。然而，随着字段的增加和类型的变化，它很快就会变得复杂。这表明，要编写有效的混合搜索查询，往往需要对所查询的索引有更深入的了解，这样才能在组合之前对组件查询得分进行适当的归一化处理。这对混合搜索的广泛应用构成了障碍。</p><h3>查询分组</h3><p>让我们扩展一下示例：如果我们想查询一个<code>text</code> 字段和两个<code>semantic_text</code> 字段，该怎么办？我们可以构建这样一个查询：</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "semantic_text_field_1",
                  "query": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "semantic_text_field_2",
                  "query": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}<p>这表面上看起来不错，但也有潜在的问题。现在，<code>semantic_text</code> 场比赛占总分的⅔：</p>Total Score = N(semantic_text_field_1 score) + N(semantic_text_field_2 score) + N(text_field score)<p>这可能不是你想要的结果，因为这会造成分数不平衡。在只有 3 个字段的示例中，这种影响可能并不明显，但如果查询的字段较多，就会出现问题。例如，大多数索引包含的词法字段远远多于语义字段（即<code>dense_vector</code>,<code>sparse_vector</code>, 或<code>semantic_text</code>) 。如果我们使用上述模式查询一个包含 9 个词法字段和 1 个语义字段的索引呢？词性匹配将占得分的 90% ，从而削弱语义搜索的有效性。</p><p>解决这一问题的常用方法是将查询分为词汇和语义两个类别，并对两者进行平均加权。这就避免了任一类别在总分中占主导地位。</p><p>让我们付诸实践。在使用线性检索器时，本例中的分组查询方法会是怎样的？</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "linear": {
              "retrievers": [
                {
                  "retriever": {
                    "standard": {
                      "query": {
                        "semantic": {
                          "field": "semantic_text_field_1",
                          "query": "foo"
                        }
                      }
                    }
                  },
                  "normalizer": "minmax"
                },
                {
                  "retriever": {
                    "standard": {
                      "query": {
                        "semantic": {
                          "field": "semantic_text_field_2",
                          "query": "foo"
                        }
                      }
                    }
                  },
                  "normalizer": "minmax"
                }
              ]
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}<p>哇，真是啰嗦！您甚至可能需要上下滚动多次才能查看整个查询！在这里，我们使用两级标准化来创建查询组。数学上可以表示为</p>Total Score = N(N(semantic_text_field_1 score) + N(semantic_text_field_2 score)) + N(text_field score)<p>这第二级规范化可确保<code>semantic_text</code> 字段和<code>text</code> 字段的查询权重均匀。请注意，在本例中，我们省略了<code>text_field</code> 的二级规范化，因为只有一个词法字段，这样可以避免<em>更多的</em>繁琐。</p><p>这种查询结构已经很笨重了，而且我们只查询三个字段。随着查询字段的增多，即使是经验丰富的搜索从业人员也越来越难以驾驭。</p><h2>多字段查询格式</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5d9007d279f0da29/6a17f17714d90c39cf79b6f5/dd04e1686076a574b717c1460acfe4eb79299208-1600x1600.jpg" alt="" /><p>我们在 Elasticsearch 8.19、9.1 和 无服务器 中为线性和 RRF 检索器添加了<a href="https://www.elastic.co/cloud/serverless"> </a><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers#multi-field-query-format">多字段查询格式</a> ，以简化所有这些操作。现在，您只需使用"...... "即可执行与上述相同的查询：</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>这将查询从 55 行缩减到 9 行！Elasticsearch 自动将索引映射用于</p><ul><li><p>确定每个查询字段的类型</p></li><li><p>将每个字段归入一个词汇或语义类别</p></li><li><p>在最终得分中平均分配每个类别的权重</p></li></ul><p>这样，任何人都可以执行有效的混合搜索查询，而无需了解有关索引或所用推理端点的详细信息。</p><p>使用 RRF 时，可以省略<code>normalizer</code> ，因为排名是相关性的代表：</p>GET rrf_retriever_example/_search
{
  "retriever": {
    "rrf": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field" ],
      "query": "foo"
    }
  }
}<h2>每场增强</h2><p>在使用线性检索器时，您可以应用每个字段增强功能来调整某些字段中匹配的重要性。例如，假设您要查询四个字段：两个<code>semantic_text</code> 字段和两个<code>text</code> 字段：</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field_1", "text_field_2" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>默认情况下，每个字段在其组（词法或语义）中的权重相同。比分细目如下</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdc6e197e5ff7b68d/6a17f179505ac32834ad8c46/ba31c76189e3a1e5b1638437ccf0528aafec2598-1600x549.png" alt="查询组和领域得分的比较" /><p>换句话说，每个字段占总分的 25% 。</p><p>我们可以使用<code>field^boost</code> 语法为任何字段添加每个字段的提升。让我们将<code>semantic_text_field_1</code> 和<code>text_field_1</code> 提升 2：</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1^2", "semantic_text_field_2", "text_field_1^2", "text_field_2" ]
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>现在的比分是这样的</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltccc9ed7d6e865a5e/6a17f17abe6086a31d004882/de20e555d52f914bf483a048d056f54f4fece757-1600x549.png" alt="通过 ref 和混合搜索更改字段权重" /><p>每个查询组的权重仍然相同，但组内字段的权重发生了变化：</p><ul><li><p><code>semantic_text_field_1</code> 是语义查询组得分的 66% ，是总分的 33% </p></li><li><p><code>text_field_1</code> 是词法查询组得分的 66% ，是总分的 33% </p></li></ul><p>ℹ️ 请注意，在按字段提升时，总分范围不会改变。这是分数标准化的预期副作用，可确保词法和语义查询分数保持直接可比性。</p><p>ℹ️ 在 Elasticsearch 9.2+ 中，每个字段的提升也可与 RRF 检索器一起使用</p><h3>通配符分辨率</h3><p>您可以在<code>fields</code> 参数中使用<code>*</code> 通配符来匹配多个字段。继续上面的例子，这个查询在功能上等同于明确查询<code>emantic_text_field_1</code>,<code>semantic_text_field_2</code>, 和<code>text_field_1</code> ：</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_*", "*_field_1" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>值得注意的是，<code>*_field_1</code> 模式同时匹配<code>text_field_1</code> 和<code>semantic_text_field_1</code> 。查询将自动执行，就像明确查询每个字段一样。<code>semantic_text_field_1</code> 同时匹配两种模式也没有问题；在执行查询之前，所有匹配的字段名称都会被去除重复。</p><p>您可以通过多种方式使用通配符：</p><ul><li><p>前缀匹配（例如：<code>*_text_field</code>)</p></li><li><p>内联匹配 (ex:<code>semantic_*_field</code>)</p></li><li><p>后缀匹配（例如：<code>semantic_text_field_*</code>)</p></li></ul><p>您还可以使用多个通配符来应用上述组合，例如<code>*_text_field_*</code> 。</p><h3>默认查询字段</h3><p>多字段查询格式还允许您查询您一无所知的索引。如果省略<code>fields</code> 参数，它将查询由<a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules">index.query.default_field 索引设置</a>指定的所有字段：</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>默认情况下，<code>index.query.default_field</code> 设置为<code>*</code> 。该通配符将解析索引中支持术语查询的所有字段类型，其中大多数字段类型都支持术语查询。例外情况是</p><ul><li><p><code>dense_vector</code> 领域</p></li><li><p><code>rank_vector</code> 领域</p></li><li><p>几何领域：<code>geo_point</code>, <code>shape</code></p></li></ul><p>当你想在第三方提供的索引上执行混合搜索查询时，该功能尤其有用。多字段查询格式可让您以简单的方式执行适当的查询。只需排除<code>fields</code> 参数，就能查询所有适用字段。</p><h2>结论</h2><p>分数范围问题会让有效的混合搜索实施起来很头疼，尤其是在对所查询的索引或所使用的推理端点了解有限的情况下。线性和 RRF 检索器的多字段查询格式将基于查询分组的自动混合搜索方法打包到简单易用的应用程序接口中，从而减轻了这种痛苦。附加功能（如按字段增强、通配符解析和默认查询字段）扩展了功能，涵盖了多种使用情况。</p><h2>立即试用多字段查询格式</h2><p>您可以通过<a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/create-serverless-project"> 免费试用</a> ，在完全托管的 Elasticsearch<a href="https://www.elastic.co/cloud/serverless"> Serverless 项目中使用多字段查询格式检查线性检索器和</a> RRF 检索器。它还提供从 8.19&amp; 9.1 开始的堆栈版本。</p><p>只需一条命令，几分钟即可在本地环境中开始使用：</p>curl -fsSL https://elastic.co/start-local | sh<p></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hybrid-search-multi-field-query-retrievers-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hybrid-search-multi-field-query-retrievers-elasticsearch</guid>
    <category><![CDATA[混合搜索]]></category>
    <category><![CDATA[相关性]]></category>
    <dc:creator><![CDATA[Mike Pellegrini]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt89e066372c549595/6a17f17c3e9e457c38ba1583/4494f98ae3958bbdbc6171df9677fc4d65ec5640-1536x1024.png" length="0" type="image/png"/>
    <pubDate>Thu, 27 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[你懂的，语境--第三部分：混合搜索在语境工程中的威力]]></title>
    <description><![CDATA[了解如何利用上下文工程和混合搜索，通过聚合、RBAC 和非内容信号来提高人工智能输出的准确性。]]></description>
    <content:encoded><![CDATA[<p>我们已经讨论了混合搜索<a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">（第一部分</a>）和上下文工程<a href="https://www.elastic.co/search-labs/blog/context-engineering-llm-evolution-agentic-ai">（第二部分</a>）；现在，让我们深入探讨它们如何协同工作，为 RAG 和代理人工智能操作提供有针对性的上下文，以达到最佳效果。</p><h2>搜索并未消亡，只是转移了位置</h2><p>因此，我们已经从主要通过文本框搜索上下文，然后使用返回的信息（上下文）自己构建答案，转变为现在使用自然语言告诉代理我们想要什么，然后让它自动研究并为我们编译答案。科技界的许多人都指出了这一转变，并宣称 "搜索已死"（搜索引擎优化和广告词的世界<a href="https://www.pewresearch.org/short-reads/2025/07/22/google-users-are-less-likely-to-click-on-links-when-an-ai-summary-appears-in-the-results/"> 肯定 在 变化</a> ：<a href="https://www.wired.com/story/goodbye-seo-hello-geo-brandlight-openai/"> GEO</a> 谁知道？</p><p>以前，人类是主观相关性的主要仲裁者：每个用户都有自己进行搜索的理由，他们的个人经验会影响搜索结果的相对准确性。如果我们要相信代理能得出与我们相同（或更好）的结论，我们就必须确保代理能获得的上下文信息尽可能接近我们的主观意图。为了实现这一目标，我们必须设计为法律硕士提供的环境！</p><h2>利用混合搜索检索生成上下文</h2><p>在此提醒大家，Elastic 的混合搜索结合了传统基于关键字搜索的优势（语法灵活性、关键字精确度和相关性评分）和向量相似性搜索的语义理解，并提供了多种重排技术。这种协同作用（这个词从未有过如此真实的用法）这样就能获得高度相关的结果，查询内容的针对性也会更加细致。这不仅仅是说你可以将主观相关性作为检索阶段<em>之一</em>，而是说第一阶段检索可以同时包括相关性评分和所有其他模式。</p><h3>卓越的精度&amp; 效率</h3><p>使用可提供分布式搜索、检索和重新排序的数据平台作为主要的上下文检索引擎非常有意义。您可以使用高级查询语法来添加主观意图的缺失部分，并过滤掉可能干扰或混淆所返回的上下文信息价值的内容。您可以从任何可用的单独语法选项中进行选择，也可以将各种模式组合成一个单一的搜索，以其最能理解的方式针对每种类型的数据进行搜索，然后通过重新排序对其进行组合/重新排序。您可以对响应进行过滤，使其只包含您想要的字段/值，从而避免无关数据。在为代理提供服务时，这种目标定位的灵活性可让您构建的工具在检索上下文时极为准确。</p><h3>语境细化（聚合和非内容信号）</h3><p>聚合在塑造工具向上下文窗口提供的内容方面特别有用。聚合自然会提供有关返回的上下文数据形状的基于数字的事实，这使得 LLM 的推理更容易、更准确。由于聚合可以分层嵌套，因此很容易为 LLM 增加多层次的细节，从而产生更细致入微的理解。聚合还有助于管理上下文窗口的大小--您可以轻松地将 10 万个文档的查询结果减少到几百个聚合洞察的标记。</p><p>非内容信号是数据中的固有指标，它们能告诉你所查看内容的全貌；它们是结果的附加特征，如受欢迎程度、新鲜度、地理位置、类别、主机多样性或价格带。这些信息可以为代理如何权衡所接收到的上下文的重要性提供有用信息。一些简单的例子也许最能说明这一点：</p><ul><li><p><strong>提升近期发布的热门内容</strong>--想象一下，您有一个文章知识库。您希望找到与用户查询相关的文章，但同时也希望推广那些最近发表的、对其他用户有帮助的文章（例如，具有较高"likes" 数量的文章）。在这种情况下，我们可以使用混合搜索来查找相关文章，然后根据文章的发表日期和受欢迎程度对其进行排序。</p></li><li><p><strong>带有销售和库存调整功能的电子商务搜索</strong>- 在电子商务环境中，您希望向客户展示与其搜索词相匹配的产品，但同时也希望推广销售良好且有库存的产品。您可能还想把库存少的产品降级，以避免客户失望。</p></li><li><p><strong>在错误跟踪器中确定高严重性问题的优先级</strong>--对于软件开发团队来说，在搜索问题时，首先浮现高严重性、高优先级和最近更新的问题至关重要。您可以使用 "关键性 "和 "讨论最多 "等非信号来独立权衡不同的因素，确保最关键和讨论最活跃的问题排在最前面</p></li></ul><p>这些示例查询和更多内容可在随附的 Elasticsearch Labs<a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/you-know-for-context/">内容页面</a>中找到。</p><h3>安全执法</h3><p>利用 Elastic 等搜索驱动的速度层进行上下文工程的一个重要优势是其内置的安全框架。Elastic 的平台通过细粒度的基于角色的访问控制（RBAC）和基于属性的访问控制（ABAC），确保向代理和生成式人工智能操作提供的上下文尊重并保护敏感的私人信息。这意味着不仅能高效处理查询，还能根据代理或发起请求的用户的特定权限对结果进行过滤。</p><p>代理以认证用户的身份运行，因此通过平台内置的安全功能隐式地应用了安全功能：</p><ul><li><p><strong>细粒度权限：</strong>在文档、字段甚至术语级别定义访问权限，确保人工智能代理只接收他们有权查看的数据。</p></li><li><p><strong>基于角色的访问控制（RBAC）：</strong>为代理或用户分配角色，根据其定义的职责授予对特定数据集或功能的访问权限。</p></li><li><p><strong>基于属性的访问控制（ABAC）：</strong>根据数据、用户或环境的属性实施动态访问策略，从而实现高度适应性和上下文感知的安全性。</p></li><li><p><strong>文档级安全（DLS）和字段级安全（FLS）：</strong>这些功能可确保即使在检索的文档中，也只能看到授权部分，从而防止敏感信息外泄。</p></li><li><p><strong>与企业安全集成：</strong>与现有身份管理系统（如 LDAP、SAML、OIDC）无缝集成，在整个组织内执行一致的安全策略。</p></li></ul><p>通过将这些安全措施直接集成到上下文检索机制中，Elastic 成为了一个安全的看门人，确保人工智能代理在定义的数据边界内运行，防止未经授权的数据暴露，并维护数据隐私法规的合规性。这对于在处理机密或专有信息的人工智能代理系统中建立信任至关重要。</p><p>此外，通过在企业数据源上使用统一的数据速度层，还可以减轻代理工具在这些资源库上产生的意外临时查询负载。您只需在一个地方就能近乎实时地搜索所有内容，并在一个地方应用安全和治理控制。</p><h2>基于搜索的混合工具</h2><p>Elastic 平台的一些核心功能（<a href="https://www.elastic.co/blog/whats-new-elastic-9-2-0">更多</a>功能将陆续推出）能极大地促进情境工程的发展。这里最主要的是，该平台提供了多种实现方法，随着人工智能生态系统的发展，可以灵活地调整、改变和扩展方法。</p><h3>代理生成器介绍</h3><p>Elastic<a href="https://www.elastic.co/elasticsearch/agent-builder">Agent Builder</a>是我们在代理式人工智能工具领域的首次尝试，该工具可与您已存储在 Elastic 中的数据聊天。Agent Builder 提供了一个聊天界面，使用户能够在 Kibana 中创建和管理自己的代理和工具。它内置 MCP 和 A2A 服务器、编程 API 和一套预置系统工具，用于查询和探索 Elasticsearch 索引，以及从自然语言生成 ES|QL 查询。代理生成器允许您创建自定义工具，通过富有表现力的<a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a>查询语法，瞄准并雕琢返回给代理的上下文数据。</p><p>你会问，ES|QL 如何执行混合搜索？核心功能是通过结合<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"> semantic_text</a> 字段 类型和<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fork"> </a><a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fuse">FORK/FUSE</a> 命令来实现的（FUSE 默认使用<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion"> RRF</a> 来合并每个分叉的结果）。下面是一个虚构产品搜索的简单示例：</p>FROM products
| FORK
  (MATCH description "high performance gaming laptop" | EVAL search_type = "bm25"),
  (MATCH description_semantic "high performance gaming laptop" | EVAL search_type = "semantic")
| FUSE 
| LIMIT 20
| KEEP product_name, description, _score, search_type<p>在上面的示例中，每个 FORK 分支都包含了 EVAL 子句，但<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval"> EVAL 子句并不是绝对必要的；包含 EVAL 子句只是为了演示如何跟踪给定结果是从哪种搜索模式返回的。</a></p><h3>搜索模板</h3><p>假设您想将自己的外部代理工具指向 Elastic 部署。您希望使用多级检索器或重新使用已开发的现有 DSL 语法，而不是 ES|QL，还希望能够控制查询接受的输入、执行搜索时使用的语法以及输出中返回的字段。<a href="https://www.elastic.co/docs/solutions/search/search-templates">搜索模板</a>允许用户为常用搜索模式定义预定义结构，从而提高检索数据的效率和一致性。这对与搜索应用程序接口交互的代理工具尤其有利，因为它们有助于规范模板代码，加快搜索逻辑的迭代速度。如果您需要调整其中任何一个因素，只需更新搜索模板，就可以实现更改。如果您正在寻找搜索模板与代理工具配合使用的示例，可以看看 Elasticsearch 实验室的博客 "<a href="https://www.elastic.co/search-labs/blog/mcp-intelligent-search">MCP for intelligent</a>search"，它在来自外部 MCP 服务器的工具调用背后使用了搜索模板。</p><h3>集成工作流程（FTW!）</h3><p>在我们新的代理人工智能世界中，最难驾驭的事情之一就是半自主、自导自演的 "推理 "代理的非确定性。情境工程是代理人工智能的一门关键学科：这些技术有助于将我们的代理可能得出的结论缩小到我们所知道的基本事实。即使有了高度准确和相关的上下文窗口（当我们跳出数字事实的范畴时），我们仍然缺少一点保证，即代理的反应是完全可重复和可靠的。</p><p>当您多次向代理运行同一个请求时，得到的答案可能<em>基本相同</em>，<em>只是</em>在响应上有那么一点点差别。对于简单的查询来说，这通常没什么问题，也许几乎不会引起注意，我们可以尝试使用上下文工程技术来塑造输出。但是，随着我们要求代理完成的任务变得越来越复杂，一个或多个子任务就更有可能带来差异，从而稍微改变最终结果。随着我们开始更多地依赖代理与代理之间的通信，这种情况可能会变得更糟，而这些差异也会累积起来。这再次说明，与我们的代理互动的工具需要非常灵活，并可进行调整，以精确瞄准上下文数据，而且它们应该以预期的输出格式做出响应。这也表明，在许多使用案例中，我们需要指导代理和工具之间的交互--这就是工作流的作用所在！</p><p>Elastic 将很快在平台核心中内置完全可定制的工作流程。这些工作流程将能以双向方式与代理和工具一起运行，因此工作流程将能呼叫代理和工具，代理和工具也能呼叫工作流程。将这些功能完全集成到同一搜索人工智能平台中，您的所有数据都将在该平台中存活，这将是一场变革，工作流程的潜力令人无比振奋！很快，很快就会到来！</p><h3>作为统一记忆库的弹性</h3><p>Elastic 是一个分布式数据平台，专为近乎实时的搜索而设计，因此能自然而然地为代理型人工智能系统提供长期记忆功能。通过内置的 Agent Builder 聊天体验，我们还可以跟踪和管理短期记忆和聊天记录。由于整个平台以 API 为先，因此利用 Elastic 作为平台来持久化工具的上下文输出（并能在稍后参考）非常容易，而这些输出可能会淹没代理的上下文窗口；这种技术在上下文工程领域有时被称为 "<a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents#:~:text=Agents%20can%20assemble%20understanding%20layer%20by%20layer%2C%20maintaining%20only%20what%27s%20necessary%20in%20working%20memory%20and%20leveraging%20note%2Dtaking%20strategies%20for%20additional%20persistence">记笔记</a>"。</p><p>在同一个搜索平台上同时拥有短期和长期记忆会带来很多内在的好处：试想一下，我们可以将聊天记录和持久化的上下文回复作为语义影响因素的一部分，用于未来的聊天互动，或用于执行威胁分析，或用于创建从频繁重复的工具调用中自动生成的持久化数据产品......这种可能性是无穷无尽的！</p><h2>结论</h2><p>大型语言模型的出现改变了我们匹配内容的方式，也改变了我们查询数据的方法。在我们的世界里，人类正在迅速地从研究、背景考虑和逻辑推理来回答自己的问题，转变为这些步骤在很大程度上通过代理人工智能实现自动化。为了让我们相信所收到的生成答案，我们需要确保代理在生成答案时考虑了<em>所有</em> <em>最相关的</em>信息（包括主观相关性因素）。我们使代理人工智能值得信赖的主要方法是，通过 RAG 和上下文工程技术将检索额外上下文的工具落地，但这些工具如何进行<em>初始检索</em>对响应的准确性至关重要。</p><p>Elastic Search 人工智能平台提供了混合搜索的灵活性和优势，同时还提供了多项内置功能，有助于代理式人工智能的准确性、性能和可扩展性；换句话说，Elastic 是语境工程多个方面的绝佳平台！通过搜索平台将上下文检索标准化，我们在多个方面简化了代理工具的操作--与 "放慢速度才能更快 "的矛盾论类似，上下文生成层的简化意味着代理人工智能更快、更可信。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-agentic-ai-accuracy</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-agentic-ai-accuracy</guid>
    <category><![CDATA[混合搜索]]></category>
    <category><![CDATA[智能体 AI]]></category>
    <dc:creator><![CDATA[Woody Walton]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt42a203a316f0e22e/6a170932b339d58ebc769f5f/b82ff25242e4229cc20b218d9cc91c60cfd680bc-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 20 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[你懂的，语境--第一部分：混合搜索和语境工程的演变]]></title>
    <description><![CDATA[探索混合搜索和上下文工程如何从词汇基础发展到支持下一代代理人工智能工作流程。]]></description>
    <content:encoded><![CDATA[<h2>我们全新的人工智能代理世界</h2><p>和我们许多人一样，我发现自己对人工智能能力的发展速度既目瞪口呆又惊叹不已。我们首先看到大型语言模型（LLMs）和矢量搜索将我们带入语义革命，在这场革命中，我们不再需要用关键字来寻找事物。随后，法学硕士们向我们展示了与数据交互的新方法，他们使用聊天界面将自然语言请求转化为回复，将庞大的知识库提炼为易于使用的摘要。我们现在（已经）以 "代理人工智能"（agentic AI）工作流的形式出现的自动 LLM 驱动逻辑已经初具雏形，它可以从语义上理解接收到的请求，推理出需要采取的步骤，然后从可用的工具中选择迭代执行的行动来实现这些目标。</p><p>人工智能代理的前景正迫使我们从主要使用 "提示工程 "来塑造我们的人工智能生成交互，发展到关注我们如何帮助代理工具获得最相关、最有效的额外信息，以便 LLM 在生成其响应时加以考虑--"情境工程 "是下一个前沿领域。混合搜索是迄今为止最强大、最灵活的浮现相关上下文的手段，Elastic 的搜索人工智能平台开辟了一种全新的方式来利用数据为上下文工程服务。在本文中，我们将从两个角度讨论法律硕士如何改变了信息检索的世界，然后再讨论如何将它们结合起来以取得更大的成果。有相当多的地方需要覆盖...</p><h2>第 I 部分：法律硕士如何改变搜索方式</h2><p>让我们从法律硕士如何改变了我们获取和检索信息的方式这个角度出发。</p><h3>我们的词汇遗产</h3><p>长期以来，我们一直生活在有限的词库搜索世界中（尽我们所能，相当不错）。搜索是我们在研究或开始一个新项目时最先使用的工具，直到最近，我们还需要以词法搜索引擎能够理解的方式来描述我们的查询。词法搜索依赖于将某种形式的查询术语与文档语料库中的关键字进行匹配，无论内容是非结构化的还是结构化的。词法搜索要返回命中的文档，必须与该关键词相匹配（或者有同义词列表或词典等受控词汇来为我们建立概念联系）。</p>POST my-index/_search
{
  "size": 10,
  "query": {
    "semantic": {
      "query": "machine learning applications",
      "field": "semantic-content-field"
    }
  }
}<p><em>词法 </em><a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-multi-match-query"><em>多匹配</em></a><em> 查询</em>示例 </p><p>至少搜索引擎有能力返回相关性得分的点击率。搜索引擎提供丰富的查询语法选项，可有效定位索引数据，并提供内置相关性算法，根据用户查询语法的意图对结果进行评分。几十年来，搜索引擎在相关性排序算法方面取得了长足的进步，这使搜索引擎成为一个高效的数据检索平台，可以根据查询结果的相关性对结果进行评分和排序。使用 SQL 作为主要数据检索方法的数据库和其他系统在这方面处于劣势：数据库查询中没有相关性的概念；它们能做的最好的事情就是按字母或数字对结果进行排序。好消息是，您将获得这些关键词的所有点击率（召回率），但相对于您询问这些关键词的<em>原因</em>（精确度）而言，它们的顺序不一定有帮助。这一点很重要，我们很快就会看到...</p><h3>进入（语义）龙</h3><p>信息矢量表示法作为关键字搜索的替代方法，其潜力已被研究了<a href="https://www.elastic.co/search-labs/blog/introduction-to-vector-search">很长时间</a>。矢量让我们摆脱了只用关键词匹配内容的模式，因此前景十分广阔--由于矢量是术语和权重的数字表示，因此可以根据语言模型对术语在训练领域中相互关系的理解，在数学上接近概念。通用矢量搜索之所以拖延了很长时间，是因为模型大多局限于特定领域，它们根本不足以充分理解一个术语在不同语境中可能代表的许多不同概念。</p><p>直到几年前出现了大型语言模型（LLM），它们能够在更大的数据量上进行训练（使用<a href="https://en.wikipedia.org/wiki/Transformer_(deep_learning_architecture)">转换器</a>和<a href="https://en.wikipedia.org/wiki/Attention_(machine_learning)">注意力</a>），矢量搜索才变得实用起来--LLM 的大小和深度最终使矢量能够存储足够的细微差别，从而真正捕捉语义。理解深度的骤然增加使得 LLM 现在可以实现大量以前无法实现的自然语言处理（NLP）功能，其中影响最大的可能是根据序列中迄今为止的上下文推断序列中最有可能出现的下一个术语。推理过程赋予了生成式人工智能近乎人类的文本生成能力。人工智能生成的文本参考了 LLM 对训练数据中术语相关性的理解，并利用请求的措辞来区分术语可能出现的不同语境。</p><p>尽管生成式人工智能非常神奇，但 LLM 也<em>有</em>其局限性，会导致质量和准确性方面的误差，也就是通常所说的幻觉。当 LLM 无法获得信息（或没有正确的上下文引导）来根据事实回答问题时，就会产生幻觉，因此，为了帮助 LLM，它会产生一个自信满满、听起来似是而非的回答。部分原因在于，虽然 LLM 可以在包含各种信息的大型领域中学习语言的用法，但它们必须在某个时间点停止训练，因此它们的理解存在时效性因素--也就是说，模型只能知道在停止训练之前的准确性。造成幻觉的另一个因素是，模型通常不知道私人持有的数据（不能在公共互联网上获取的数据），当这些数据包含特定术语和名词时，这一点尤为重要。</p><h3>矢量数据库</h3><p>LLM 使用一种称为文本嵌入的技术将内容矢量化到其模型空间中，这种技术是指根据所接受的训练，将内容的语义<a href="https://www.elastic.co/search-labs/blog/hybrid-search-multiple-embeddings">嵌入</a>或映射到模型的世界观中。准备和处理嵌入内容需要几个步骤，包括<a href="https://www.elastic.co/search-labs/blog/chunking-strategies-elasticsearch">分块</a>和标记化（以及<a href="https://www.kaggle.com/code/danishmahdi/subword-tokenization-bpe-wordpiece-and-unigram">子词标记化</a>）。其结果通常是一组密集的向量，代表了模型在其向量空间内对该内容块含义的理解。分块是一个不精确的过程，目的是使内容符合模型生成嵌入的处理限制，同时还尝试使用语义结构（如句子和段落指示符）将相关文本归入一个分块。</p><p>由于单个分块与同一文档中的其他分块并不完全关联，因此分块的需要可能会在嵌入文档中造成一些语义损失。神经网络固有的不透明性会使这种损失变得更加严重--LLM 是一个真正的 "黑盒子"，训练过程中术语和概念之间的联系是非确定的，人类无法解释。这就导致了可解释性、可重复性、无意识偏见等问题，并可能失去信任和准确性。不过，从语义上将想法联系起来的能力，以及在查询时不受特定关键词束缚的能力还是非常强大的：</p>POST my-index/_search 
{
  "size": 10, 
  "query": {
    "semantic": {
      "query": "machine learning applications",
      "field": "semantic-content-field"
    }
  }
} <p><a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-semantic-query"><em>语义</em></a><em> 查询 示例</em></p><p>矢量数据库还有一个问题需要考虑：它们不是搜索引擎，而是数据库！在进行<a href="https://www.elastic.co/search-labs/blog/introduction-to-vector-search">矢量相似性搜索</a>时，会对查询词进行编码，以便在模型的矢量空间中找到一组（嵌入）坐标。然后将这些坐标作为靶心，找出与靶心 "近邻 "的文档--这意味着文档的排名（或在结果中的位置）是由计算出的文档坐标与查询坐标的相似度<em>距离</em>决定的。在可能的上下文中，哪个最接近用户的意图？我将其比喻为电影《<a href="https://www.youtube.com/watch?v=x3h7xz558EY&amp;start=3&amp;end=86">星际之门</a>》中的一个场景，我们有六个相交的坐标点来告诉我们目的地（靶心），但如果不知道 "第七个符号"--代表用户主观意图的起点坐标，我们就无法到达目的地。因此，通过表达式语法和相关性评分来考虑查询的主观意图，我们就能得到类似于主观相关性分级的<em>圆柱体</em>，而不是根据不断扩大和无差别的相似性来对向量进行相对排序。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb49ae330d3de1fc1/6a17053ee8fbce089639fb46/1ddfaae0c1496d08d7d30419e6d2aeaeacfc0ea2-1600x544.png" alt="主观相关性渐变的圆柱体。" /><p>LLM 的推理能力可能有助于确定<em>它</em>对查询所掌握的最有可能的上下文，但问题是，<em>在没有帮助的</em>情况下，输入查询的坐标<em>只能</em>根据模型最初的训练方式来确定。</p><p>在某些方面，你可以说矢量相似性走向了与严格的关键词匹配相反的极端--它的优势在于能够克服术语不匹配的问题，但<a href="https://medium.com/data-science/vector-embeddings-are-lossy-heres-what-to-do-about-it-4f9a8ee58bb7">几乎可以</a>说是无懈可击：LLM 倾向于统一相关概念，而不是区分它们。矢量相似性提高了我们从语义上匹配内容的能力，但并不能保证精确度，因为它可能会忽略精确的关键字和特定的细节，而这些细节在模型中并没有得到足够的消歧。矢量相似性搜索本身就很强大，但我们需要将从矢量数据库中获取的结果与其他检索方法的结果关联起来。</p><h3>重新排名技术</h3><p>现在是提及一种名为 "重排 "的通用技术的好时机。"重排 "是对结果集进行重新评分或归一化，使其达到统一的排名顺序。需要重新排序的原因可能是来自多个来源或检索方法的结果具有不同的排序/评分机制（或者根本没有，SQL！），或者重新排序可能是为了使来自非语义来源的结果与用户的查询在语义上保持一致。重新排序是第二阶段的操作，是指通过某种<em>初始检索</em>方法收集到的一组结果（即SQL、词法搜索、向量搜索），然后用不同的评分方法重新排序。</p><p>有几种可用的方法，包括<a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr">学习排名（Learning-To-Rank，</a>LTR）和<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">互惠排名融合（Reciprocal Rank Fusion，RRF</a>）--LTR 适用于捕捉搜索结果特征（喜欢、评分、点击等），并利用这些特征对搜索结果进行评分、提升或倾斜。RRF 非常适合合并不同查询模式返回的结果（如词法搜索和矢量数据库搜索）合并为一个结果列表。Elastic 还提供了使用<a href="https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search">线性重排</a>方法调整分数的灵活性。</p><p>不过，最有效的重排技术之一是<a href="https://www.elastic.co/docs/solutions/search/ranking/semantic-reranking">语义重排</a>，它利用 LLM 的语义理解能力来分析查询和结果的向量嵌入，然后应用相关性评分/重评分来确定最终顺序。当然，语义重排需要与重排模型建立连接，Elasticsearch提供了<a href="https://www.elastic.co/docs/api/doc/elasticsearch/group/endpoint-inference">推理API</a>，让您可以创建<strong>重排</strong>端点，利用内置模型<a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-rerank">（Elastic Rerank</a>）、<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/eland/machine-learning">导入的</a>第三方模型或外部托管服务（如<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-cohere">Cohere</a>或<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-googlevertexai">Google Vertex AI</a>）。然后就可以通过<a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">检索器</a>查询抽象语法执行重新排序：</p>POST my-index/_search 
{
  "size": 10,
  "retriever": {
    "text_similarity_reranker": {
      "retriever": {
        "rrf": {
          "retrievers": [
            {
              "standard": {
                "query": {
                  "multi_match": {
                    "query": "machine learning applications",
                    "fields": ["title", "content"]
                  }
                }
              }
            },
            {
              "knn": {
                "field": "semantic-content-field",
                "k": 10,
                "num_candidates": 100,
                "query_vector_builder": {
                  "text_embedding": {
                    "model_id": "my-text-embedding-model",
                    "model_text": "machine learning applications"
                  }
                }
              }
            }
          ],
          "rank_window_size": 50,
          "rank_constant": 20
        }
      }
    },
    "field": "content",
    "inference_id": "my-reranker",
    "inference_text": "machine learning applications",
    "rank_window_size": 20
  }
}<p><em>多级检索器重排操作示例</em></p><p>听起来不错吧？我们可以对来自不同来源的结果进行重新排序，从而接近对所有类型内容的语义理解......语义重新排序的计算成本和处理时间都很高，正因为如此，语义重新排序只能在数量有限的结果上进行，这意味着<em>如何</em>检索这些初始结果非常重要。</p><h3>语境检索方法很重要</h3><p>主观意图是确定结果准确性和评分相关性的一个重要因素。由于无法考虑用户执行查询的意图（如通过灵活的语法或第二阶段重排所表达的意图），我们只能从模型空间中已编码的现有上下文中进行选择。我们通常通过<a href="https://en.wikipedia.org/wiki/Retrieval-augmented_generation">检索增强生成（RAG）</a>等技术来解决这种缺乏上下文的问题。RAG 的工作原理是，它可以有效地转移查询的坐标，包括通过预查询返回的其他相关术语，以获取与上下文相关的数据。这就使得提供额外上下文的引擎及其执行检索<em>的</em>初始方法对上下文的准确性更加重要！</p><p>让我们回顾一下不同的上下文检索方法，以及它们对 RAG 操作的帮助或伤害：</p><ul><li><p><strong>没有搜索引擎的混合搜索检索仍然缺乏主观相关性。</strong>如果提供 RAG 的平台主要基于下面的 SQL（包括大多数 "数据湖 "平台），那么在初始检索阶段就缺乏相关性评分。许多数据湖平台提供自己版本的混合检索（而非搜索），通常在基于 SQL 的检索和矢量数据库结果上结合语义重排和 RRF 等重排技术。简单的排序显然不足以进行主观排序，但即使作为第二阶段语义重排操作的基础，SQL 作为第一阶段检索，在只对 "前 k 个 "点击进行语义重排时也会出现问题--如果不在检索时对结果进行某种评分，我们又如何保证<em>最好的</em>结果确实在最前面的结果中呢？</p></li><li><p><strong>对于 RAG 来说，仅有矢量相似性是不够的</strong>。这实际上是由一系列复杂问题造成的--这是嵌入的损失性，还有天真的分块方法、相似性的计算方法，以及主观意图这一至关重要的缺失部分。RAG 的主要目标之一是将生成式人工智能交互建立在客观事实的基础上，既能防止产生幻觉，又能让 LLM 了解它在训练过程中不知道的私人信息。我们可以利用 RAG 提供的额外语境来约束和引导 LLM，使其考虑我们所知道的对回答当前问题最重要的关联和细节。为此，我们需要<em>同时</em>使用语义和词汇方法。</p></li><li><p><strong>基于文件的 grep/regex RAG。</strong>在人工智能代理领域，有一些<a href="https://www.nicolasbustamante.com/p/the-rag-obituary-killed-by-agents">人</a>指出，应使用大幅放大的上下文窗口，通过 grep 和 regex 访问本地文件，以实现 RAG，而不是使用外部检索平台。我们的想法是，有了更大的上下文窗口，法律硕士就能在自己的思维空间内建立概念联系，而不是依赖分块的碎片和多种检索方法/平台来收集相关信息。虽然从理论上讲，拥有整个文档比拥有文档片段能提供更全面的信息，但这只适用于小数据域（或者，例如，在提供用于<a href="https://en.wikipedia.org/wiki/Vibe_coding">振动编码的</a>文件时），即使在这种情况下，初始检索方法也是扫描所有仅有关键字匹配的文档。</p></li></ul><p><strong>搜索不仅仅是检索</strong></p><p>搜索引擎的设计目的是使查询尽可能快速和灵活。在内部，它们利用专门的数据结构来存储和检索不同类型的数据，以满足这些数据类型的需要。Elasticsearch 可优化所有类型数据的存储和查询，包括非结构化/全文词法搜索（匹配、短语、近似、多重匹配）、快速关键字（精确匹配）匹配和过滤、数字范围、日期、IP 地址，而且存储文档结构的方式也非常灵活（例如，可通过"...嵌套或扁平化文档）。Elasticsearch 还是一个原生矢量数据库，可以存储和查询稀疏和密集矢量类型。我们将继续探索创新方法（例如，<a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">更好的二进制量化 (BBQ)</a> &amp; <a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction">DiskBBQ</a>），以保持搜索保真度，同时提高速度、可扩展性以及与矢量化内容相关的成本。Elasticsearch 平台还提供内置的数据弹性和高可用性，并包含数据生命周期管理功能，<a href="https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/searchable-snapshots">如可搜索快照</a>，让您可以在经济高效的对象存储上保留不常访问或长期保留的数据，但仍可完全搜索。</p><h3>混合搜索是最好的选择</h3><p><a href="https://www.elastic.co/what-is/hybrid-search">混合搜索</a>（不仅仅是混合检索）将传统词汇搜索的优势与 LLM 的语义理解和向量相似性搜索相结合。这种协同作用允许在<em>检索</em>阶段通过搜索引擎提供的任何灵活的查询语法选项：意图驱动语法选项和相关性评分、多模态数据检索、过滤、聚合和偏置来定位高度相关的结果。利用<a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a>等搜索语法和多级<a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">检索器</a>，我们可以在一个请求中灵活地将传统搜索与语义搜索、过滤器和多种重排技术结合起来。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6ee9512910856ee3/6a17053fcdacbf2ea97d291e/f25180cb430414b99ae553d3b8eb161dbccea4d4-1920x1080.png" alt="混合搜索的工作原理" /><p>混合搜索的最大优势之一是，您的查询可以同时针对多种不同的数据类型使用专门的语法。这些不同的查询语法不仅可用于<em>查找</em>结果，还可用作结果<em>的</em>筛选器或聚合器。例如，最常见的查询类型之一是<a href="https://www.elastic.co/docs/explore-analyze/geospatial-analysis">地理空间分析</a>，它经常与其他语法相结合。您可以查询地理坐标在某一点指定距离内的结果，或要求按地区对结果进行汇总，或进行汇总以跟踪进入/离开某个区域的移动情况并发出警报。使用混合搜索，您可以灵活地组合语法，以最准确的方式定位搜索结果，检索最贴近您的上下文的内容。</p><h2>中场休息</h2><p>第一部分讲述了矢量搜索如何改变了我们检索数据的方式，并为 LLM 给我们用来与数据交互的查询机制带来的变化做了铺垫。我们将假装不得不把这部分内容分成多个部分，以便 LLM 能够在不丢失上下文的情况下理解......;-)让我们在<a href="https://www.elastic.co/search-labs/blog/context-engineering-llm-evolution-agentic-ai">第二部分 "代理人工智能和上下文工程的必要性"中进一步了解 这一点的重要性 ，在第三部分中，我们将继续讨论混合搜索。</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai</guid>
    <category><![CDATA[混合搜索]]></category>
    <category><![CDATA[相关性]]></category>
    <dc:creator><![CDATA[Woody Walton]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc49e39872984c8eb/6a1705410e2e49e42c419fd5/7e59a0671aa9ea32d68188a693936a66ebf48625-1000x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 12 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[利用 Elasticsearch 和 SigLIP-2 对山峰进行多模式搜索 ]]></title>
    <description><![CDATA[了解如何使用 SigLIP-2 嵌入和 Elasticsearch kNN 向量搜索实现文本到图像和图像到图像的多模态搜索。项目重点：寻找珠峰徒步旅行中拍摄的阿玛达布拉姆峰照片。]]></description>
    <content:encoded><![CDATA[<p>您是否曾想过按含义搜索相册？试着询问 "给我看我穿着蓝色夹克坐在长椅上的照片"、"给我看珠穆朗玛峰的照片 "或 "清酒和寿司"。喝杯咖啡（或您最喜欢的饮料），继续阅读。在本博客中，我们将向您展示如何构建多模态混合搜索应用程序。多模态是指应用程序可以理解和搜索不同类型的输入（文本、图像和音频），而不仅仅是文字。混合式意味着它结合了关键词匹配、kNN 向量搜索和地理围栏等技术，以提供更清晰的结果。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa1ec1ccd450e94/6a17da751d1b8308ee93e344/0ec6bbb45013846b59ee00d2bf73ee2182ee7392-1920x1080.gif" alt="来自珠穆朗玛峰徒步旅行的不同山峰照片库。" /><p>为此，我们使用谷歌的 SigLIP-2 为图像和文本生成矢量嵌入，并将其存储在 Elasticsearch 矢量数据库中。在查询时，我们将搜索输入（文本或图像）转换为嵌入，并运行快速的 kNN 向量搜索来检索结果。这种设置可实现高效的文本到图像和图像到图像搜索。Streamlit 用户界面为我们提供了一个前端，不仅可以进行基于文本的搜索，从相册中查找并查看匹配的照片，还可以从上传的图片中识别山峰，并查看相册中该山峰的其他照片，从而使该项目栩栩如生。我们还介绍了为提高搜索准确性而采取的措施，以及实用技巧和窍门。为便于进一步探索，我们提供了<a href="https://github.com/navneet83/multimodal-mountain-peak-search">GitHub 存储库</a>和<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">Colab 笔记本</a>。</p><h2>如何开始</h2><p>这篇博文的灵感来自于一个 10 岁的孩子，他让我给他们看我在珠峰大本营徒步旅行时拍摄的阿玛达布拉姆山的所有照片。在翻阅相册时，我还被要求辨认其他几座山峰，其中一些我还叫不出名字。</p><p>这让我想到，这可以成为一个有趣的计算机视觉项目。我们的目标</p><ul><li><p>按名称查找山峰图片</p></li><li><p>从图片中猜测山峰名称，并在相册中找到类似的山峰</p></li><li><p>让概念查询发挥作用<em>（人</em>、<em>河流</em>、<em>祈祷旗</em> <em>等）</em></p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf82df9d7005fc3fe/6a17da78abe0f2e77bdfe8b9/e9d0d720a9b565d5b749bdc915068852d4f157ad-1200x1600.png" alt="阿玛-达布拉姆山 " /><h2>组建梦之队：SigLIP-2、Elasticsearch&amp; Streamlit</h2><p>很快我们就发现，要想实现这一目标，我们需要将文字（"阿玛达布拉姆"）和图像（我相册中的照片）都转化为可以进行有意义比较的矢量，即在同一个矢量空间中。一旦我们做到了这一点，搜索就只是 "寻找最近的邻居"。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f80b69a9d5bd28a/6a17da7a4b055ddd1243209e/20e6f8b7d4fa48414f407ec200adbe00ee28d517-1536x1024.png" alt="SigLIP-2、Elasticsearch&amp; Streamlit--梦之队。" /><p>为了生成图像嵌入，我们使用了多语言<a href="https://huggingface.co/blog/vlms-2025"> 视觉语言编码器</a>，因此山峰的照片和 "Ama Dablam "这样的短语会出现在同一个向量空间中。</p><p>谷歌最近发布的<a href="https://huggingface.co/blog/siglip2"><strong>SigLIP-2</strong></a> 在这方面非常适合。它可以在没有特定任务训练的情况下生成嵌入式（<strong>零镜头</strong>设置），并能很好地适用于我们的使用案例：未标记的照片和具有不同名称和语言的山峰。由于它是针对文本与图像匹配进行训练的，因此即使查询语言或拼写不同，徒步旅行中的山峰图片和简短的文字提示最终也能接近嵌入。</p><p>SigLIP-2 在质量与速度之间实现了很好的平衡，支持多种输入分辨率，并可在 CPU 和 GPU 上运行。SigLIP-2 在设计上比以前的型号（如最初的 CLIP）更适合户外拍摄。在我们的测试中，SigLIP-2 始终能生成可靠的结果。此外，它还得到了很好的支持，因此是本项目的不二之选。</p><p>接下来，我们需要一个向量数据库来存储嵌入和强力搜索。它不仅应支持对图像嵌入进行余弦 kNN 搜索，还应在单个查询中应用地理围栏和文本过滤器。Elasticsearch 在这方面非常适合：它能很好地处理向量（在 dense_vector 字段上使用 HNSW kNN），支持结合文本、向量和地理查询的混合搜索，并提供开箱即用的过滤和排序功能。它还可以横向扩展，因此很容易从少量照片扩展到数千张照片。<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/python"></a>最后，我们需要一个轻量级前端，以便输入搜索查询并查看结果。对于基于 Python 的快速演示，Streamlit 非常适合。它提供了我们所需的基本功能--文件上传、响应式图像网格以及用于排序和地理围栏的下拉菜单。它很容易克隆并在本地运行，也可以在 Colab 笔记本中使用。</p><h2>实施</h2><h3>Elasticsearch 索引设计和索引策略</h3><p>我们将在这个项目中使用两个索引：<code>peaks_catalog</code> 和<code>photos</code> 。</p><h4>峰值_目录索引</h4><p>该索引是珠峰大本营徒步旅行期间可看到的著名山峰的简明目录。该索引中的每份文件都对应一座山峰，如珠穆朗玛峰。对于每个山峰文档，我们都会存储名称/别名、可选的经纬度坐标以及由 SigLIP-2 文本提示（+ 可选的参考图片）混合而成的单一原型向量。</p><p><strong>索引映射：</strong></p><p>现场</p><p>类型</p><p>示例</p><p>目的/说明</p><p>矢量/索引</p><p>本我</p><p>关键词</p><p>阿玛-达布拉姆</p><p>稳定的弹头/ID</p><p>-</p><p>姓名</p><p>文本 + 关键字子字段</p><p>["Ama Dablam","Amadablam"]</p><p>别名/多语言名称；names.raw 用于精确筛选</p><p>-</p><p>纬纶</p><p>地理点</p><p>{"lat":27.8617,"lon":86.8614}</p><p>以经纬度组合形式显示的山顶 GPS 坐标（可选）</p><p>-</p><p>海拔_m</p><p>整数</p><p>6812</p><p>海拔（可选）</p><p>-</p><p>嵌入文本</p><p>dense_vector</p><p>768</p><p>该山峰的混合原型（提示和可选的 1-3 幅参考图片</p><p>index:true, similarity:"cosine", index_options：{type:"hnsw", m:16, ef_construction:128}</p><p>该索引主要用于图像到图像的搜索，例如从图像中识别山峰。我们还使用该索引来增强文本到图片的搜索结果。</p><p>总之，<code>peaks_catalog</code> 将问题""这是什么山？" "转化为一个重点突出的 "最近邻问题"，有效地将概念理解与图像数据的复杂性分离开来。</p><p><strong>peaks_catalog 索引的索引策略： </strong>首先，我们创建了一份在 EBC 徒步旅行中可见的最突出山峰的列表。对于每个山峰，我们都会在<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/data/peaks.yaml">yaml 文件</a>中存储其地理位置、名称、同义词和海拔高度。下一步是<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L351"> 生成</a> 每个峰值的 嵌入 值，并将其存储在<code>text_embed</code> 字段中。为了生成稳健的嵌入，我们使用了以下技术：</p><ul><li><p>创建文本原型：</p><ul><li><p>山峰名称</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L301">提示组合</a>（使用多个不同的提示来尝试回答同一个问题），例如</p><ul><li><p>"尼泊尔喜马拉雅山脉山峰的自然照片{name} "</p></li><li><p>"{name} 昆布地区的地标性山峰，高山景观"</p></li><li><p>"{name} 山顶，积雪，岩石山脊线"</p></li></ul></li><li><p>可选的<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L333">反概念</a>（告诉 SigLIP-2 什么不能匹配）：为 "绘画、插图、海报、地图、徽标 "减去一个小矢量，这样我们就偏向于真实照片。</p></li></ul></li><li><p>如果提供了峰值的参考图像，可选择<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L388C13-L388C29">创建图像原型</a>。</p></li></ul><p>然后，我们<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L392">混合文本和图像原型</a>，生成最终的嵌入。最后，文件将被<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L396">索引到</a>所有必填字段：</p>def l2norm(v: np.ndarray) -&gt; np.ndarray:
    return v / (np.linalg.norm(v) + 1e-12)
def compute_blended_peak_vec(
        emb: Siglip2,
        names: List[str],
        peak_id: str,
        peaks_images_root: str,
        alpha_text: float = 0.5,
        max_images: int = 3,
) -&gt; Tuple[np.ndarray, int, int, List[str]]:
    """
    Build blended vector for a single peak.

    Returns:
      vec           : np.ndarray (L2-normalized)
      found_count   : number of reference images discovered
      used_count    : number of references used (&lt;= max_images)
      used_filenames: list of filenames used (for logging)
    """
    # 1) TEXT vector
    tv = embed_text_blend(emb, names)

    # 2) IMAGE refs: prefer folder by id; fallback to slug of the primary name
    root = Path(peaks_images_root)
    candidates = [root / peak_id]
    if names:
        candidates.append(root / slugify(names[0]))

    all_refs: List[Path] = []
    for c in candidates:
        if c.exists() and c.is_dir():
            all_refs = list_ref_images(c)
            if all_refs:
                break

    found = len(all_refs)
    used_list = all_refs[:max_images] if (max_images and found &gt; max_images) else all_refs
    used = len(used_list)

    img_v = embed_image_mean(emb, used_list) if used_list else None

    # 3) Blend TEXT and IMAGE vectors, clamp alpha to [0,1]
    a = max(0.0, min(1.0, float(alpha_text)))
    vec = l2norm(tv if img_v is None else (a * tv + (1.0 - a) * img_v)).astype("float32")
    return vec, found, used, [p.name for p in used_list]<p><code>peaks_catalog</code> 索引中的文件样本：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1219f5d0e39b512c/6a17da7c57726263161bcace/bc05fbd0c4f8d721d5170c28a3884a9eda80bb7d-1210x1132.png" alt="来自 Elasticsearch 中 peaks_catalog 索引的示例文档。" /><h4>照片索引</h4><p>该主索引存储相册中所有照片的详细信息。每份文档代表一张照片，包含以下信息：</p><ul><li><p>相册中照片的相对路径。可用于查看匹配图像或在搜索用户界面中加载图像。</p></li><li><p>图片的 GPS 和时间信息。</p></li><li><p>SigLIP-2 生成的图像编码密集矢量。</p></li><li><p><code>predicted_peaks</code> 可让我们根据峰名进行筛选。<strong>索引映射</strong></p></li></ul><p>现场</p><p>类型</p><p>示例</p><p>目的/说明</p><p>矢量/索引</p><p>路径</p><p>关键词</p><p>data/images/IMG_1234.HEIC</p><p>用户界面如何打开缩略图/全图</p><p>-</p><p>剪贴图片</p><p>dense_vector</p><p>768</p><p>SigLIP-2 图像嵌入</p><p>index:true, similarity:"cosine", index_options：{type:"hnsw", m:16, ef_construction:128}</p><p>预测峰值</p><p>关键词</p><p>["ama-dablam","pumori"]</p><p>索引时的 Top-K 猜想（廉价用户体验过滤器/面）</p><p>-</p><p>全球定位系统</p><p>地理点</p><p>{"lat":27.96,"lon":86.83}</p><p>启用地理筛选器</p><p>-</p><p>拍摄时间</p><p>date</p><p>2023-10-18T09:41:00Z</p><p>捕捉时间：排序/过滤</p><p>-</p><p><strong>照片索引的索引策略： </strong>对于相册中的每张照片，我们会采取以下措施：
<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L526">从图像元数据中</a>提取图像<code>shot_time</code> 和<code>gps</code> 信息。</p><ul><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L511">SigLIP-2 图像嵌入</a>：通过模型传递图像并对向量进行 L2 归一化。将嵌入内容存储在<code>clip_image</code> 字段中。</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L519">预测峰值</a>并将其存储在<code>predicted_peaks</code> 字段中。为此，我们首先获取上一步生成的照片图像向量，然后针对<code>peaks_catalog</code> 索引中的 text_embed 字段快速运行 kNN 搜索。我们保留顶部的 3-4 个山峰，忽略其余的。</p></li><li><p>我们通过对图片名称和路径进行<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L509">散列</a>计算<code>_id</code> 字段。这可以确保我们在多次运行后不会出现重复。</p></li></ul><p>一旦我们确定了照片的所有字段，就会使用<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L530"> 批量 索引对照片文件进行</a> 批量 索引：</p>def bulk_index_photos(
        es: Elasticsearch,
        images_root: str,
        photos_index: str = "photos",
        peaks_index: str = "peaks_catalog",
        topk_predicted: int = 5,
        batch_size: int = 200,
        refresh: str = "false",
) -&gt; None:
    """Walk a folder of images, embed + enrich, and bulk index to Elasticsearch."""
    root = Path(images_root)
    if not root.exists():
        raise SystemExit(f"Images root not found: {images_root}")

    emb = Siglip2()
    batch: List[Dict[str, Any]] = []
    n_indexed = 0

    for p in iter_images(root):
        rel = relpath_within(root, p)
        _id = id_for_path(rel)

        # 1) Image embedding (and reuse it for predicted_peaks)
        try:
            with Image.open(p) as im:
                ivec = emb.image_vec(im.convert("RGB")).astype("float32")
        except (UnidentifiedImageError, OSError) as e:
            print(f"[skip] {rel} — cannot embed: {e}")
            continue

        # 2) Predict top-k peak names
        try:
            top_names = predict_peaks(es, ivec.tolist(), peaks_index=peaks_index, k=topk_predicted)
        except Exception as e:
            print(f"[warn] predict_peaks failed for {rel}: {e}")
            top_names = []

        # 3) EXIF enrichment (safe)
        gps = get_gps_decimal(str(p))
        shot = get_shot_time(str(p))

        # 4) Build doc and stage for bulk
        doc = {"path": rel, "clip_image": ivec.tolist(), "predicted_peaks": top_names}
        if gps:
            doc["gps"] = gps
        if shot:
            doc["shot_time"] = shot

        batch.append(
            {"_op_type": "index", "_index": photos_index, "_id": _id, "_source": doc}
        )

        # 5) Periodic flush
        if len(batch) &gt;= batch_size:
            helpers.bulk(es, batch, refresh=refresh)
            n_indexed += len(batch)
            print(f"[photos] indexed {n_indexed} (last: {rel})")
            batch.clear()

    # Final flush
    if batch:
        helpers.bulk(es, batch, refresh=refresh)
        n_indexed += len(batch)
        print(f"[photos] indexed {n_indexed} total.")

    print("[done] photos indexing")<p>照片索引中的样本文件：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt744b7e6326937cfc/6a17da7e6df731d3040a0da8/1dc1406ac2a97440b6804838795b3c2205c4c6b2-1080x1234.png" alt="来自 Elasticsearch 照片索引的样本文件。" /><p>总之，照片索引是相册中所有照片的快速、可过滤、kNN 就绪存储。它的映射结构非常简单，只需足够的结构就能快速检索、清晰显示，并按空间和时间对结果进行切分。该索引可同时满足这两种搜索用途。创建这两个索引的 Python 脚本可在<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/create_indices.py">此处</a>找到。</p><p>下面的 Kibana 地图可视化将相册中的文档显示为绿色圆点，将<code>peaks_catalog</code> 索引中的山峰显示为红色三角形，其中绿色圆点与珠峰大本营徒步路线非常吻合。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5bf016e8d9c3e84/6a17da80be608681f10045e6/1c75d0ed0ce53d28a94bf2f47a354e25581d2baf-1600x1402.png" alt="Kibana 地图可视化显示相册中的文件为绿色圆点，peaks_catalog 索引中的山峰为红色三角形，其中绿色圆点与珠峰大本营徒步路线非常吻合。" /><h2>搜索用例</h2><p><strong>按名称搜索（文本到图像）：</strong>该功能可让用户使用文本查询查找山峰照片（甚至是 "祈祷旗 "等抽象概念）。为此，使用 SigLIP-2 将文本输入<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L87C5-L87C20">转换为文本向量</a>。为了生成稳健的文本向量，我们采用了与在<code>peaks_catalog</code> 索引中创建文本嵌入相同的策略：<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104"> 将</a> 文本输入与小型 提示集合<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L100"> 相结合</a> ，减去次要的<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L103"> 反概念向量</a> ，并应用<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104"> L2 归一化 生成最终的查询向量。</a>然后在<code>photos.clip_image</code> 字段上执行 kNN<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L140">查询</a>，根据余弦相似度检索匹配度最高的峰值，从而找到最接近的图像。作为查询的一部分，还可选择应用地理和日期<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L152">筛选器</a>和/或<code>photos.predicted_peaks</code> 术语筛选器来提高搜索结果的相关性（见下文查询示例）。这有助于排除在徒步过程中看不到的相似山峰。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9bb9abf5ce64fcbb/6a17da81e8fbce20db3a17da/b5fac28ffdbedb820505365ca07df125cd01b939-946x370.png" alt="如何在 Elasticsearch 中通过名称（文本到图像）进行多模式搜索。" /><p><strong>带有地理过滤器的 Elasticsearch 查询：</strong></p>POST photos/_search
{
  "knn": {
    "field": "clip_image",
    "query_vector": [ ... ],
    "k": 60,
    "num_candidates": 2000
  },
  "query": {
    "bool": {
      "filter": [
        { "geo_bounding_box": { "gps": { "top_left": "...", "bottom_right": "..." } } }
      ]
    }
  },
  "_source": ["path","predicted_peaks","gps","shot_time"]
}

Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<p><strong>按图像搜索（图像到图像）：</strong>通过该功能，我们可以识别照片中的某座山，并在相册中查找该座山的其他图像。图像上传后，将由 SigLIP-2 图像编码器处理，生成<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L228">图像矢量</a>。然后在<code>peaks_catalog.text_embed</code> 字段上进行<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L234">kNN 搜索</a>，以确定最匹配的峰值名称。随后，根据这些匹配的山峰名称<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L257"> 生成</a> 一个 文本向量 ，并在照片索引中进行另一次<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L263"> kNN 搜索</a> ，以找到相应的照片。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab9d16333e2a9e69/6a17da827f6f155448c099cc/3a3d5635bee7a222b95529dd7f9fbee016381610-1226x550.png" alt="Elasticsearch 如何通过图像进行多模式搜索（图像到图像）。" /><p><strong>Elasticsearch 查询：</strong></p><p>第 1 步：找到匹配的山峰名称</p>GET peaks_catalog/_search
{
 "knn": {
   "field": "text_embed",
   "query_vector": [...image-vector... ],
   "k": 3,
   "num_candidates": 500
 },
 "_source": [
   "id",
   "names",
   "latlon",
   "text_embed"
 ]
}


Response (first two documents):
{
 "took": 2,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 3,
     "relation": "eq"
   },
   "max_score": 0.58039916,
   "hits": [
     {
       "_index": "peaks_catalog",
       "_id": "pumori",
       "_score": 0.58039916,
       "_source": {
         "id": "pumori",
         "names": [
           "Pumori",
           "Pumo Ri"
         ],
         "latlon": {
           "lat": 28.01472,
           "lon": 86.82806
         },
         "text_embed": [
                  ... embeddings...
         ]
       }
     },
     {
       "_index": "peaks_catalog",
       "_id": "kyajo-ri",
       "_score": 0.57942784,
       "_source": {
         "id": "kyajo-ri",
         "names": [
           "Kyajo Ri",
           "Kyazo Ri"
         ],
         "latlon": {
           "lat": 27.909167,
           "lon": 86.673611
         },
         "text_embed": [
           ... embeddings...
         ]
       }
     }
   ]
 }
}<p>第 2 步：在<code>photos</code> 索引上进行搜索，找到匹配的图片（与文本到图片搜索用例中的查询相同）：</p>POST photos/_search
{
 "knn": {
   "field": "clip_image",
   "query_vector": [ ...image-vector... ],
   "k": 30,
   "num_candidates": 2000
 },
 "_source": [
   "path",
   "gps",
   "shot_time",
   "predicted_peaks",
   "clip_image"
 ],
 "query": {
   "bool": {
     "filter": [
       {
         "term": {
           "predicted_peaks": "Pumori"
         }
       }
     ]
   }
 }
}


Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<h2>流光 UI</h2><p>为了将所有功能整合在一起，我们创建了一个简单的 Streamlit 用户界面，让我们可以同时执行两种搜索用例。左侧栏显示可滚动的峰值列表（从<code>photos.predicted_peaks</code> 中汇总），并带有复选框和小地图/地理过滤器。顶部有一个<strong>按姓名搜索</strong>框和一个<strong>从照片</strong>上传识别按钮。中心窗格采用响应式缩略图网格，显示 kNN 分数、预测峰值徽章和捕获时间。每张图片都有一个<strong>查看图片</strong>按钮，用于全分辨率预览。</p><p><strong>通过上传图片进行搜索：</strong>我们会预测峰值，并从相册中找到匹配的峰值。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1fb2b0304a310d2/6a17da8425daab7cda08a0fa/dca540cbf5279e6d6102c5a0c0351ddd4ac91cda-1600x1112.png" alt="这是一个简单的流光式用户界面，可通过文本到图像和图像到图像的多模态搜索方式搜索阿玛达布拉姆山峰。" /><p><strong>文本搜索</strong>从文本中查找相册中匹配的峰值</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt496c1ae8f7886320/6a17da86abe0f2da48dfe8bd/b1e8618db746cd49ea4962d3dc73031387b975dd-1600x1166.png" alt="如何在山峰库中通过文本搜索珠穆朗玛峰。" /><h2>结论</h2><p><em>我们能看看 </em><em><strong>阿玛-达布拉姆</strong></em><em> 的照片吗？</em>变成了一个可运行的小型<strong>多模态搜索</strong>系统。我们采集了原始的徒步旅行照片，将其转化为<strong>SigLIP-2 嵌入</strong>，并使用<strong>Elasticsearch</strong>对向量进行快速的<strong>kNN</strong>处理，再加上简单的地理/时间过滤器，根据<em>意义</em>浮现出正确的图像。在此过程中，我们将两个索引的关注点分开：一个是混合原型的小<code>peaks_catalog</code> （用于识别），另一个是图像向量和 EXIF 的可扩展<code>photos</code> 索引（用于检索）。它实用、可复制、易扩展。</p><p>如果您想对其进行调整，有几项设置可供使用：</p><ul><li><p><strong>查询时间设置：</strong> <code>k</code> （您希望返回多少个邻居）和<code>num_candidates</code> （最终评分前的搜索范围）。这些设置将在<a href="https://www.elastic.co/search-labs/blog/elasticsearch-knn-and-num-candidates-strategies">此处的</a>博客中讨论。</p></li><li><p><strong>索引时间设置：</strong> <code>m</code> （图形连接性）和<code>ef_construction</code> （构建时间精度与内存）。对于查询，也可以尝试使用<code>ef_search</code> --更高通常意味着更高的召回率，但需要权衡一定的延迟。有关这些设置的更多详情，请参阅<a href="https://www.elastic.co/search-labs/blog/hnsw-graph">本博客</a>。</p></li></ul><p>展望未来，用于<strong>多模态</strong>和<strong>多语言</strong>搜索的本地模型/路由器即将登陆<a href="https://ir.elastic.co/news/news-details/2025/Elastic-Completes-Acquisition-of-Jina-AI-a-Leader-in-Frontier-Models-for-Multimodal-and-Multilingual-Search/default.aspx?utm_source=chatgpt.com"> Elastic</a>生态系统，这将使图像/文本检索和混合排名功能更加强大。</p><p>如果你想亲自尝试一下：</p><ul><li><p><strong>GitHub 代码库</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search"><em>： https://github.com/navneet83/multimodal-mountain-peak-search</em></a></p></li><li><p><strong>Colab 快速入门</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">：https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb</a></p></li></ul><p>我们的旅程就此结束，是时候飞回去了。希望这对你有帮助，如果你改动（或改进）了它，我很乐意听听你的改动。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdce2fff1569d2a8b/6a17da894b055dd1f24320a2/d324d1e1472f1bfbd8f25747f57bdeeb9c7f16b2-1600x1200.png" alt="" />]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[混合搜索]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Navneet Kumar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltccb66279debb05f9/6a17da8b63baffe228741b15/ffcf93358a7c5dadcea82faf3de460bf060d003c-1600x1200.png" length="0" type="image/png"/>
    <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[为 Elasticsearch 改进代理人工智能工具的实验]]></title>
    <description><![CDATA[了解我们如何通过迭代实验，结合线性检索器、混合搜索和语义文本进行可扩展的 RAG 优化，从而改进 Elasticsearch 的人工智能代理工作流。]]></description>
    <content:encoded><![CDATA[<p>如今，在 Elastic，我们也像其他人一样，全力投入到聊天、代理和 RAG 中。在搜索部门，我们最近一直在开发代理生成器和工具注册表，目的都是为了简化在 Elasticsearch 中与数据 "聊天 "的过程。</p><p>请阅读<a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder"> " 利用 Elasticsearch 构建人工智能代理工作流 "博客</a> ，了解更多有关这项工作的 "全貌"，或阅读<a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch"> " 你的第一个弹性代理" 博客 ，了解更多有关这项工作的实用入门知识</a> ：从单个查询到人工智能驱动的聊天 》，了解更多实用入门知识。</p><p>不过，在本博客中，我们将放大一些，看看当您开始聊天时最先发生的事情之一，并向您介绍我们最近做出的一些改进。</p><h2>这里发生了什么？</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1331b1043612efe3/6a17f115505ac3dc41ad8c3c/25a24055a166d7d6ba81d80aa35cb97163662e23-1600x443.png" alt="" /><p>当您与 Elasticsearch 数据聊天时，我们默认的人工智能代理会执行此标准流程：</p><ol><li><p>检查提示。</p></li><li><p>确定哪个索引可能包含该提示的答案。</p></li><li><p>根据提示为该索引生成查询。</p></li><li><p>使用该查询搜索该索引。</p></li><li><p>综合结果。</p></li><li><p>结果能否解决提示问题？如果是，请回答。如果不行，就重复，但要尝试不同的方法。</p></li></ol><p>这看起来并不新奇--它只是检索增强一代（RAG）。正如您所期望的那样，回复的质量在很大程度上取决于初始搜索结果的相关性。因此，在我们努力提高响应质量的过程中，我们一直在密切关注在第 3 步中生成和在第 4 步中运行的查询。我们注意到一个有趣的模式。</p><p>通常情况下，当我们的首次响应 "糟糕 "时，并不是因为我们运行了一个糟糕的查询。这是因为<em>我们选错了</em>要查询的索引。第 3 步和第 4 步通常不是我们的问题，问题在于第 2 步。</p><h2>我们在做什么？</h2><p>我们最初的实施很简单。我们建立了一个工具（名为 index_explorer），它可以有效地进行<code>_cat/indices</code> ，列出我们可用的所有索引，然后要求 LLM 识别这些索引中哪个最符合用户的信息/问题/提示。您可以 在这里 看到<a href="https://github.com/elastic/kibana/blob/0cc78184957fcd12110dabae50353392ea937508/x-pack/platform/packages/shared/onechat/onechat-genai-utils/tools/index_explorer.ts#L98-L113"> 最初的实施方案</a> 。</p>You are an AI assistant for the Elasticsearch company.
based on a natural language query from the user, your task is to select up to ${limit} most relevant indices from a list of indices.

*The natural language query is:* ${nlQuery}

*List of indices:*
${indices.map((index) =&gt; `- ${index.index}`).join('\n')}

Based on those information, please return most relevant indices with your reasoning.
Remember, you should select at maximum ${limit} indices.<p>效果如何？我们不确定！我们有一些效果<em>不佳</em>的明显例子，但我们真正面临的第一个挑战是如何量化我们的现状。</p><h2>确定基线</h2><h3>从数据开始</h3><p>我们需要的是一个 "黄金数据集"，用于衡量工具在用户提示和已有索引集的情况下选择正确索引的效率。而我们手头并没有这样的数据集，所以我们生成了一个。</p><p>致谢：我们知道，这不是 "最佳做法"。但有时，前进总比骑自行车好。<a href="https://www.elastic.co/about/our-source-code#progress-perfection">进步，简单完美</a>。</p><p>我们利用<a href="https://gist.github.com/seanstory/a08db2e149897da656db3a1ca72e17ac">这一提示</a>为多个不同领域生成了种子指数。然后，对于每个生成的域，我们使用<a href="https://gist.github.com/seanstory/a280a85d067e61bfeb5911bf2654e6e2"> 该提示</a>又生成了几个索引（目的是用硬否定和难以分类的示例给 LLM 制造混乱）。接下来，我们手动编辑了每个生成的索引及其说明。最后，我们使用<a href="https://gist.github.com/seanstory/44291b666c05a383136f6e36bb9106fa">该提示</a>生成了测试查询：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1bd9cd78154195e3/6a17f117dbb4fff7b5fb57d2/9d96d87e286eddbc012402b1ecccd57419a99253-1600x782.png" alt="" /><p>和测试用例，如</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltadf30a0aeafd56ef/6a17f1192f4a5c160ffa89eb/4c2e9ad941d98d7e66033bbc08c9b8060ec19097-1600x797.png" alt="" /><h3>创建测试线束</h3><p>从这里开始的过程非常简单。脚本工具可以</p><ol><li><p>使用目标 Elasticsearch 集群建立一片净土。</p></li><li><p>创建目标数据集中定义的所有索引。</p></li><li><p>针对每个测试场景，执行 i<code>ndex_explorer</code> 工具（很方便，我们有一个<a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-post-agent-builder-tools-execute">执行工具 API</a>）。</p></li><li><p>将结果索引与预期索引进行比较，并捕捉结果。</p></li><li><p>完成所有测试方案后，将结果制成表格。</p></li></ol><h3>调查说...</h3><p>不出所料，最初的成果平平。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt73367741359e258d/6a17f11a505ac39749ad8c40/9c10679bcd6291edfa2a9ba42e7dd922aa483f0b-1216x806.png" alt="" /><p>总体而言，77.14% 能准确识别正确的索引。这是在 "最好的情况 "下，即所有指数都有好的、语义上有意义的名称。使用过 `PUT test2/_doc/foo{...}` 的人都知道，索引的名称并不总是有意义的。</p><p>因此，我们有了一个基准线，而且它显示出很大的改进空间。现在是时候来点科学知识了！🧪</p><h2>实验</h2><h3>假设 1：映射将有助于</h3><p>这样做的目的是确定一个索引，其中包含与原始提示相关的数据。而索引中最能描述其所含数据的部分就是索引的<em>映射</em>。即使不抓取索引内容的任何样本，只要知道该索引有一个 double 类型的价格字段，就意味着该数据代表了要出售的东西。文本类型的作者字段意味着一些非结构化语言数据。两者合在一起可能意味着数据是书籍/故事/诗歌。通过了解索引的属性，我们可以获得很多语义线索。因此，我在本地分支中调整了 `.index_explorer工具，将索引的完整映射（连同索引名称）发送给 LLM，由 LLM 做出决定。 </p><p>结果（来自 Kibana 日志）：</p>[2025-09-05T11:01:21.552-05:00][ERROR][plugins.onechat] Error: Error calling connector: event: error
data: {"error":{"code":"request_entity_too_large","message":"Received a content too large status code for request from inference entity id [.rainbow-sprinkles-elastic] status [413]","type":"error"}}


    at createInferenceProviderError (errors.ts:90:10)
    at convertUpstreamError (convert_upstream_error.ts:39:38)
    at handle_connector_response.ts:26:33
    at Observable.init [as _subscribe] (/Users/seanstory/Desktop/Dev/kibana/node_modules/rxjs/src/internal/observable/throwError.ts:123:68)...<p>该工具的最初作者已经预见到了这一点。虽然索引映射是一座信息金矿，但它也是一个相当冗长的 JSON 数据块。而在实际情况中，您需要比较众多指数（我们的评估数据集定义了 20 个指数），这些 JSON blob 会不断增加。因此，我们希望为 LLM 的决策提供更多的背景信息，而不仅仅是所有选项的索引名称，但又不至于提供每个选项的完整映射。</p><h3>假设 2："扁平化 "映射（字段列表）是一种折中方案</h3><p>我们首先假设索引创建者会使用有语义的索引名称。如果我们将这一假设扩展到字段名呢？我们之前的实验之所以失败，是因为 JSON 映射包含了大量繁琐的元数据和模板。</p>     "description_text": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          },
          "copy_to": [
            "description_semantic"
          ]
        },<p>例如，上面的代码块有 236 个字符，只定义了 Elasticsearch 映射中的一个字段。而字符串 "description_text "只有 16 个字符。字符数几乎增加了 15 倍，但在描述该字段对可用数据的含义方面却没有任何有意义的改进。如果我们要获取所有索引的映射，但在将其发送到 LLM 之前，将其 "扁平化 "为字段名列表，会怎么样？</p><p>我们试了一下。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta5eda7a79493ee81/6a17f11c9da390327fe46590/112c2f447c11f154b5082725cd49b51d0a3c8a65-1214x804.png" alt="" /><p>这太棒了！全面改进。但我们能做得更好吗？</p><h3>假设 3：映射 _meta 中的描述</h3><p>如果仅仅是字段名而没有额外的上下文就能带来如此大的跳跃，那么增加大量的上下文可能会更好！每个索引都附加描述并不一定是常规做法，但可以在映射的 _meta 对象中添加任何类型的索引级元数据。我们回到生成的索引，为数据集中的每个索引添加说明。只要描述不是太长，就应该比完整映射使用更少的标记，并能更好地说明索引中包含了哪些数据。我们的实验验证了这一假设。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt61b85cf40e0e6357/6a17f11dfbc5f82809491bbe/32d2692ad4479d0e52d8ee723dcc5710a6ec90f3-1208x806.png" alt="" /><p>稍有改进，我们现在的&gt;90% 准确度全面提高。</p><h3>假设 4：总和大于部分</h3><p>字段名增加了我们的成果。说明增加了我们的成果。因此，<em>同时 </em>使用描述和字段名称应该会得到更好的结果，对吗？</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte6297c6aaf7db802/6a17f11e14d90c1bd779b6e6/114cbb408ff16b136251d2265416bd5270380fe5-1208x794.png" alt="" /><p>数据显示 "否"（与上次实验相比没有变化）。这里的主要理论是，由于描述是从索引字段/映射开始生成的，这两个上下文之间没有足够的不同信息，因此在将它们组合在一起时无助于添加任何 "新 "信息。此外，我们为 20 个测试指数发送的有效载荷越来越大。我们迄今为止所遵循的思路是无法扩展的。事实上，我们有充分的理由相信，在有成百上千个索引可供选择的 Elasticsearch 集群上，我们迄今为止进行的所有实验都不会奏效。任何随着索引总数的增加而线性增加发送到 LLM 的信息量的方法，可能都不是通用的策略。</p><p>我们真正需要的是一种方法，它能帮助我们从众多候选人中筛选出最相关的选项...</p><p>这就是一个搜索问题。</p><h3>假设 5：通过语义搜索进行选择</h3><p>如果一个索引的名称具有语义意义，那么它就可以存储为一个向量，并进行语义搜索。</p><p>如果索引的字段名具有语义意义，那么就可以将其存储为向量，并进行语义搜索。</p><p>如果一个索引有一个具有语义意义的描述，那么它也可以存储为一个向量，并进行语义搜索。</p><p>如今，Elasticsearch 索引并不能搜索到这些信息（也许我们应该这样做！），但要想解决这个问题却非常容易<a href="https://github.com/elastic/connectors/pull/3638"> 。</a>利用 Elastic 的连接器框架，我构建了一个连接器，可以为集群中的每个索引输出文档。输出文件将类似于</p> doc = {
                "_id": index_name,
                "index_name": index_name,
			"meta_description”: description,
"field_descriptions" = field_descriptions,
                "mapping": json.dumps(mapping),  
                "source_cluster": self.es_client.configured_host,
            }<p>我将这些文件发送到一个新的索引，并在其中手动定义了映射：</p>{
   "mappings": {
       "properties": {
           "semantic_content": {
               "type": "semantic_text"
           },
           "index_name": {
               "type": "text",
               "copy_to": "semantic_content"
           },
           "mapping": {
               "type": "keyword",
               "copy_to": "semantic_content"
           },
           "source_cluster": {
               "type": "keyword"
           },
           "meta_description": {
               "type": "text",
               "copy_to": "semantic_content"
           },
           "field_descriptions": {
               "type": "text",
               "copy_to": "semantic_content"
           }
       }
   }
}<p>这样就创建了一个单一的 semantic_content 字段，其他所有具有语义意义的字段都会被分块并编入索引。搜索该索引变得非常简单，只需.....：</p>GET indexed-indices/_search
{
 "query": {
   "semantic": {
     "field": "semantic_content",
     "query": "$query"
   }
 }
}<p>修改后的<code>index_explorer</code> 工具现在速度<em>更快</em>，因为它不需要向 LLM 提出请求，而是可以为给定的查询请求单个嵌入，并执行高效的向量搜索操作。以最高点击率为选定索引，我们得到的结果是</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc27c302e6bef0b23/6a17f120577262d2f21bccdc/06ef5d78040d064d3444793f636d527d9e19a869-1214x800.png" alt="" /><p>这种方法具有可扩展性。这种方法效率很高。但这种方法比我们的基准线好不了多少。但这并不奇怪，因为这里的搜索方法太天真了。没有任何细微差别。不承认索引的名称和描述应比索引包含的任意字段名称更有分量。没有加权精确词性匹配而非同义匹配的功能。不过，要建立一个高度细致的查询，需要对手头的数据进行大量假设。到目前为止，我们已经对索引和字段名称的语义做了一些大的假设，但我们还需要更进一步，开始假设它们有<em>多大</em>的意义以及它们之间的关系。如果不这样做，我们可能无法可靠地将最佳匹配结果确定为我们的首要结果，但更有可能说最佳匹配结果就在前 N 个结果中的某个地方。我们需要的是一种能够在语义信息存在的语境中消费语义信息的东西，它可以与另一个可能以不同语义方式表示自己的实体进行比较，并在两者之间做出判断。比如法学硕士。</p><h3>假设 6：候选集减少</h3><p>还有很多实验我就不一一列举了，但关键的突破是放弃了纯粹从语义搜索中挑选最佳匹配项的愿望，转而利用语义搜索作为过滤器，从 LLM 的考虑范围中剔除不相关的索引。我们将线性检索、混合检索与 RRF 以及<code>semantic_text</code> 结合起来进行<a href="https://gist.github.com/seanstory/d704443120e20f6c844db10e30066860">检索</a>，将结果限制在匹配指数的前 5 位。</p><p>然后，对于每个匹配项，我们都将索引名称、描述和字段名称添加到 LLM 的信息中。结果非常好：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ac4cb8f7153fdf9/6a17f121af47b66d1dcde082/8fcabd78f591f90d6bc7c0e087d31317e4eef791-1206x804.png" alt="" /><p>这是迄今为止精度最高的实验！由于这种方法不会使信息大小与索引总数成正比，因此这种方法的可扩展性要好得多。</p><h2>成果</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8d66130d9fae6bea/6a17f123ddf97d7527910c19/04d630797213dbb8bf567da41d1cdd5c7b4586c9-1600x521.png" alt="" /><p>第一个明确的结果是，我们的基线<em>可以</em>改进。现在回想起来，这一点似乎显而易见，但在实验开始之前，我们曾认真讨论过是否应该完全放弃<code>index_explorer</code> 工具，而依靠用户的明确配置来限制搜索空间。虽然这仍然是一个可行且有效的选择，但这项研究表明，在无法获得此类用户输入的情况下，实现索引选择自动化的道路大有可为。</p><p>下一个明确的结果是，一味地增加描述性文字的数量，其回报率会越来越低。在这项研究之前，我们一直在讨论是否应该投资扩展 Elasticsearch 存储<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-field-meta">字段级元数据</a>的能力。如今，这些<code>meta</code> 值的上限是 50 个字符，而且有一种假设认为，我们需要增加这个值，以便能够从语义上理解我们的字段。但情况显然不是这样，法律硕士似乎只需填写字段名称就可以了。我们以后可能会进一步调查这个问题，但现在已经没有紧迫感了。</p><p>相反，这也清楚地证明了 "可搜索 "索引元数据的重要性。在这些实验中，我们破解了指数的索引。但是，我们可以研究将其直接构建到 Elasticsearch 中，构建应用程序接口来进行管理，或者至少围绕其建立一个惯例。我们将权衡各种选择并进行内部讨论，敬请期待。</p><p>最后，这项工作证实了我们花时间进行试验和做出数据驱动决策的价值。事实上，它帮助我们再次确认，我们的代理生成器产品需要一些强大的产品内评估功能。如果我们需要专门为一个选取指数的工具构建整个测试线束，那么我们的客户绝对需要在进行迭代调整时对其定制工具进行定性评估的方法。</p><p>我很期待看到我们的成果，希望你们也是！</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance</guid>
    <category><![CDATA[智能体 AI]]></category>
    <category><![CDATA[在 Elastic 内部]]></category>
    <category><![CDATA[混合搜索]]></category>
    <dc:creator><![CDATA[Sean Story]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68d11a4c7fd11d4c/6a17f1257b54f9b6598b39d4/42903c869e034674b30bb36013345aaa97f6608b-1184x864.png" length="0" type="image/png"/>
    <pubDate>Mon, 06 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[混合搜索重温：在 Elasticsearch 中引入线性检索器！]]></title>
    <description><![CDATA[了解线性检索器如何利用加权分数和 MinMax 归一化来增强混合搜索，从而获得更精确、更一致的排名，并学习如何使用它。]]></description>
    <content:encoded><![CDATA[<p>在<a href="https://www.elastic.co/cn/search-labs/blog/elasticsearch-retrievers-ga-8.16.0">上一篇博文</a>中，我们介绍了重新设计的 "从零开始 "检索器框架，它可以创建复杂的排名管道。我们还探讨了互易排名融合（RRF）检索器如何通过合并不同查询的结果来实现混合搜索。虽然 RRF 很容易实现，但它有一个明显的局限性：它只关注相对排名，而忽略了实际得分。这就给微调和优化带来了挑战。</p><h2>直线型寻回犬</h2><p>在本篇文章中，我们将介绍<a href="https://www.elastic.co/cn/docs/solutions/search/retrievers-overview#retrievers-overview-types"><code>linear</code></a> <a href="https://www.elastic.co/cn/docs/solutions/search/retrievers-overview#retrievers-overview-types">retriever</a>，它是我们支持混合搜索的最新成员！与<code>rrf</code> 不同，<code>linear</code> retriever 计算的是与文档匹配的所有查询的加权总和。这种方法既能保留结果集中每个文档的相对重要性，又能精确控制每个查询对最终得分的影响。因此，它为微调混合搜索提供了一种更直观、更灵活的方式。</p><p>定义一个线性检索器，其最终得分的计算公式为</p><p>就这么简单：</p>GET linear_retriever_blog/_search
{
   "retriever": {
       "linear": {
           "retrievers": [
               {
                   "retriever": {
                       "knn": {
                          ...
                        }
                    },
                   "weight": 5
               },
                  {
                   "retriever": {
                       "standard": {
                          ...
                        }
                    },
                   "weight": 1.5
               },


           ]
        }
     }
}<p>注意到它有多简单直观了吗？(与<code>rrf</code> 非常相似！）这种配置允许您精确控制每种查询类型对最终排名的贡献程度，这与<code>rrf</code> 不同，后者仅依赖于相对排名。</p><p>需要注意的是：<code>knn</code> 分数可能有严格的界限，这取决于所使用的相似性指标。例如，使用余弦相似度或单位归一化向量的点积，得分总是在<code>[0, 1]</code> 范围内。相比之下，<code>bm25</code> 分数的可预测性较差，而且没有明确的界限。</p><h2>评分缩放：KNN vs BM25</h2><p>混合搜索面临的一个挑战是，不同的检索器会产生不同的分数。例如，请考虑以下情况：</p><p>查询 A 得分：</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>文档4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>100</p><p>1.5</p><p>1</p><p>0.5</p><p>查询 B 得分：</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>文档4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>0.63</p><p>0.01</p><p>0.3</p><p>0.4</p><p>您可以从上面看到这种差异：<code>kNN</code> 分数介于 0 和 1 之间，而<code>bm25</code> 分数可能相差悬殊。这种差异使得设置静态最佳权重以合并结果变得非常棘手。</p><h2>归一化拯救：MinMax 归一化器</h2><p>为了解决这个问题，我们引入了一个可选的<code>minmax</code> 归一化器，该归一化器使用以下公式将每个查询的分数独立缩放至<code>[0, 1]</code> 范围：</p><p>这就保留了每个文档在查询结果集中的相对重要性，从而更容易合并来自不同检索器的得分。正常化后，分数变为</p><p>查询 A 得分：</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>文档4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>1.00</p><p>0.01</p><p>0.005</p><p>0.000</p><p>查询 B 得分：</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>文档4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>1.00</p><p>0.000</p><p>0.465</p><p>0.645</p><p>现在，所有得分都在<code>[0, 1]</code> 范围内，加权总和的优化也更加简单明了，因为我们现在捕捉的是结果的重要性（相对于查询而言），而不是绝对得分，并能在不同查询中保持一致。</p><h2>线性寻回器示例 </h2><p>现在，让我们通过一个例子来展示上述内容，以及<code>linear</code> Retriever 如何解决<code>rrf</code> 的一些不足之处。RRF 仅依靠相对排名，不考虑实际分数差异。例如，给出这些分数：</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>文档4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>100</p><p>1.5</p><p>1</p><p>0.5</p><p>rrf 分数</p><p>0.03226</p><p>0.03252</p><p>0.03200</p><p>0.03125</p><p>rrf 会将文件排序为</p><p>但是，doc1 的<code>bm25</code> 得分明显高于其他文件，而<code>rrf</code> 只查看相对排名，因此未能捕捉到这一点。<code>linear</code> Retriever 结合归一化处理，可以正确地考虑分数及其差异，从而得出更有意义的排名：</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>文档4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>1</p><p>0.01</p><p>0.005</p><p>0</p><p>如上图所示，doc1 的优秀排名和<code>score</code> 的<code>bm25</code> 都得到了适当的考虑，并反映在最终得分上。此外，所有分数现在都在<code>[0, 1]</code> 范围内，这样我们就能以更直观的方式对它们进行比较和组合（甚至建立离线优化流程）。</p><h2>将所有内容整合在一起</h2><p>要充分利用<code>linear</code> 检索器的正常化功能，搜索请求应如下所示：</p>GET linear_retriever_blog/_search
{
   "retriever": {
       "linear": {
           "retrievers": [
               {
                   "retriever": {
                       "knn": {
                          ...
                        }
                    },
                   "weight": 5
               },
                  {
                   "retriever": {
                       "standard": {
                          ...
                        }
                    },
                   "weight": 1.5,
                   "normalizer": "minmax"
               },


           ]
       }
   }
}<p>这种方法结合了两方面的优点：既保留了<code>linear</code> Retriever 的灵活性和直观评分，又通过 MinMax 归一化确保了一致的评分缩放。</p><p>与我们所有的检索工具一样，<code>linear</code> 检索工具可以集成到分层检索树的任何层级中，并支持可解释性、匹配高亮、字段折叠等功能。</p><h2>何时选择线性寻回犬，为什么会有区别</h2><p><code>linear</code> 猎犬：</p><ul><li><p>通过利用实际得分，而不仅仅是排名，来保留相对重要性。</p></li><li><p>允许利用不同查询的加权贡献进行微调。</p></li><li><p>利用规范化增强一致性，使混合搜索更稳健、更可预测。</p></li></ul><h2>结论</h2><p><code>linear</code> retriever 已经在 Elasticsearch Serverless 以及 8.18 和 9.0 版本中可用！更多示例和配置参数可参阅我们的文档。试用一下，看看它如何改善您的混合搜索体验--我们期待您的反馈。搜索愉快</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search</guid>
    <category><![CDATA[相关性]]></category>
    <category><![CDATA[混合搜索]]></category>
    <dc:creator><![CDATA[Panagiotis Bailis]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte58a751cbcc1153d/6a17e3c76317305c4f585a10/7a07e27e3095463ff93b4cb7f8a0cf3b8e44eab0-1777x1000.png" length="0" type="image/png"/>
    <pubDate>Wed, 28 May 2025 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>