<?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[Carly Richmond - 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[Carly Richmond - 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/author/carly-richmond</link>
    </image>
    <link>https://www.elastic.co/cn/search-labs/author/carly-richmond</link>
    <atom:link href="https://www.elastic.co/cn/search-labs/rss/author/carly-richmond.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[cn]]></language>
    <lastBuildDate>Tue, 22 Sep 2026 08:35:16 GMT</lastBuildDate>
  <item>
    <title><![CDATA[如何在 Elasticsearch 中连接两个索引]]></title>
    <description><![CDATA[解释如何使用术语查询、Logstash elasticsearch 过滤器、浓缩处理器和 ES|QL 来连接 Elasticsearch 中的两个索引。]]></description>
    <content:encoded><![CDATA[<p>在 Elasticsearch 中，连接两个索引不像在传统 SQL 关系数据库中那么简单。不过，使用 Elasticsearch 提供的某些技术和功能也可以实现类似的结果。</p><p>历史上，许多人使用<a href="https://www.elastic.co/cn/docs/reference/elasticsearch/mapping-reference/nested"><code>nested</code></a><a href="https://www.elastic.co/cn/docs/reference/elasticsearch/mapping-reference/nested"> 字段类型</a>作为将不同索引连接在一起的机制。然而，由于 Kibana 的查询成本高昂且支持不完整，特别是镜头可视化功能，该功能受到了限制。</p><p>本文将深入探讨在 Elasticsearch 中连接两个索引的过程，重点介绍以下方法： </p><ol><li><p>使用<code>terms</code> 查询</p></li><li><p>在摄取流水线中使用<code>enrich</code> 处理器</p></li><li><p>Logstash<code>elasticsearch</code> 过滤器插件</p></li><li><p>ES|QL <code>ENRICH</code></p></li><li><p>ES|QL <code>LOOKUP JOIN</code></p></li></ol><h2>使用术语查询</h2><p><a href="https://www.elastic.co/cn/docs/reference/query-languages/query-dsl/query-dsl-terms-query">术语查询</a>是 Elasticsearch 中连接两个索引的最有效方法之一。该查询用于检索在特定字段中包含一个或多个精确术语的文档。下面我们讨论如何使用它来连接两个索引。</p><p>首先，您需要从第一个索引中获取所需的数据。这可以通过简单的 GET 请求和从<code>_source</code> 属性中提取值来实现。</p># Simple GET request
GET first_index/_search<p>获得第一个索引的数据后，就可以用它来查询第二个索引。这是通过<code>terms</code> 查询完成的，您可以在查询中指定要匹配的字段和值。</p><p>下面就是一个例子：</p>GET second_index/_search
{
  "query": {
    "terms": {
      "field_in_second_index": ["value1_from_first_index", "value2_from_first_index"]
    }
  }
}<p>
在本例中，<code>field_in_second_index</code> 是第二个索引中要与第一个索引中的值匹配的字段。<code>value1_from_first_index</code> 和<code>value2_from_first_index</code> 是第一个索引中要在第二个索引中匹配的值。</p><p>术语查询还支持使用<a href="https://www.elastic.co/cn/docs/reference/query-languages/query-dsl/query-dsl-terms-query#query-dsl-terms-lookup">术语查找</a>技术一次性完成上述两个步骤。Elasticsearch 会以透明方式从另一个索引中检索要匹配的值。例如，如果您有一个包含球员列表的球队索引：</p>PUT teams/_doc/team1
{
  "players":   ["john", "bill", "michael"]
}
PUT teams/_doc/team2
{
  "players":   ["aaron", "joe", "donald"]
}<p>如下图所示，可以通过人员索引查询在 team1 队中比赛的所有人员：</p>GET people/_search?pretty
{
  "query": {
    "terms": {
        "name" : {
            "index" : "teams",
            "id" : "team1",
            "path" : "players"
        }
    }
  }
}<p>在上面的示例中，Elasticsearch 会以透明方式从球队索引中 id 为 team1 的文档中检索球员姓名（即"john"、"bill "和 "michael"），并查找人物索引中所有在姓名字段中包含这些值的文档。</p><p>对于那些好奇的人来说，等价的 SQL 查询应该是这样的：</p><h2>使用浓缩处理器</h2><p><a href="https://www.elastic.co/cn/docs/reference/enrich-processor/enrich-processor"><code>enrich</code></a><a href="https://www.elastic.co/cn/docs/reference/enrich-processor/enrich-processor"> 处理器</a>是另一个可用于连接 Elasticsearch 中两个索引的强大工具。该处理器通过添加来自预定义丰富索引的数据来丰富输入文件的数据。</p><p>下面介绍如何使用浓缩处理器连接两个索引：</p><p>1.首先，您需要创建一个浓缩策略。该策略定义了使用哪个索引来丰富输入文档、匹配哪个字段以及使用哪个字段来丰富输入文档。</p><p>下面就是一个例子：</p>PUT _enrich/policy/my_enrich_policy
{
  "match": {
    "indices": "first_index",
    "match_field": "field_in_first_index",
    "enrich_fields": ["field_to_enrich"]
  }
}<p>2.创建策略后，需要执行该策略，以便根据新创建的策略创建 enrich 索引：</p>PUT _enrich/policy/my_enrich_policy/_execute<p>这将建立一个新的隐藏浓缩索引，在浓缩过程中使用。根据源索引的大小，这一操作可能需要一些时间。在进行下一步之前，请确保充实政策已完全制定。</p><p>3.建立丰富策略后，就可以在摄取管道中使用丰富处理器来丰富传入文档的数据：</p>PUT _ingest/pipeline/my_pipeline
{
  "processors": [
    {
      "enrich": {
        "policy_name": "my_enrich_policy",
        "field": "field_in_second_index",
        "target_field": "enriched_field"
      }
    }
  ]
}<p>在本例中，<code>field_in_second_index</code> 是第二个索引中需要与第一个索引中的<code>match_field</code> 匹配的字段。<code>enriched_field</code> 是第二个索引中的新字段，将包含第一个索引<code>enrich_fields</code> 中的丰富数据。</p><p>这种方法的一个缺点是，如果<code>first_index</code> 中的数据发生变化，则需要重新执行浓缩策略。丰富索引不会自动更新或同步源索引。但是，如果<code>first_index</code> 相对稳定，那么这种方法就很有效。</p><h2>Logstash elasticsearch 过滤器插件</h2><p>如果使用 Logstash，另一个与上述<code>enrich</code> 处理器类似的选项是使用<code>elasticsearch</code> 过滤器插件，根据指定的查询将相关字段添加到事件中。Logstash 管道的配置位于<code>.conf</code> 文件中，如<code>my-pipeline.conf</code> 。</p><p>假设我们的管道使用<a href="https://www.elastic.co/cn/docs/reference/logstash/plugins/plugins-inputs-elasticsearch"><code>elasticsearch</code></a><a href="https://www.elastic.co/cn/docs/reference/logstash/plugins/plugins-inputs-elasticsearch"> 输入插件</a>从 Elasticsearch 中提取日志，并通过查询缩小选择范围：</p>input {
  # Read all documents from Elasticsearch matching the given query
  elasticsearch {
    hosts =&gt; "localhost"
    query =&gt; '{ "query": { "match": { "statuscode": 200 } }, "sort": [ "_doc" ] }'
  }
}<p>如果我们想用给定索引的信息来丰富这些信息，可以使用<code>filter</code> 部分的<a href="https://www.elastic.co/cn/docs/reference/logstash/plugins/plugins-filters-elasticsearch"><code>elasticsearch</code></a><a href="https://www.elastic.co/cn/docs/reference/logstash/plugins/plugins-filters-elasticsearch"> 过滤器插件</a>来丰富我们的日志：</p>filter {
   elasticsearch {
      hosts =&gt; ["localhost"]
      index =&gt; "index_name"
      query =&gt; "type:start AND operation:%{[opid]}"
      fields =&gt; { "@timestamp" =&gt; "started" }
   }
}<p>上述代码将从索引<code>index_name</code> 中查找文件，其中<code>type</code> 为起始值，操作字段与指定的<code>opid</code> 匹配，然后将<code>@timestamp</code> 字段的值复制到名为<code>started</code> 的新字段中。</p><p>然后，丰富的文档将被发送到适当的输出源，在本例中是使用<a href="https://www.elastic.co/cn/docs/reference/logstash/plugins/plugins-outputs-elasticsearch"><code>elasticsearch</code></a><a href="https://www.elastic.co/cn/docs/reference/logstash/plugins/plugins-outputs-elasticsearch"> 输出插件</a>发送到 Elasticsearch：</p>output {
    elasticsearch {
        hosts =&gt; "localhost"
        data_stream =&gt; "true"
    }
}<p>如果您已经在使用 Logstash，该选项可能有助于将丰富逻辑整合到一个地方，并在新事件发生时进行处理。但是，如果您不这样做，就会增加解决方案的复杂性，而且您还需要运行和维护另一个组件。</p><h2>ES|QL ENRICH</h2><p>在 8.14 版本中引入的<a href="https://www.elastic.co/cn/docs/explore-analyze/query-filter/languages/esql">ES|QL</a> 是 Elasticsearch 支持的管道式查询语言，可用于过滤、转换和分析数据。使用 ENRICH 处理命令，我们就可以使用丰富策略从现有索引中添加数据。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbeb8992bde773461/6a17f6b663baff00c9741dd4/03aadddc08afffff3f6526c9c052999c97fa09dd-1600x989.png" alt="丰富 esql" /><p>以原浓缩处理器示例中的相同策略<code>my_enrich_policy</code> 为例，ES|QL 示例如下：</p><p>也可以覆盖匹配字段和丰富字段，在我们的例子中分别是<code>field_in_first_index</code> 和<code>field_to_enrich</code> ：</p><p>虽然 ES|QL 的明显限制是需要先指定丰富策略，但 ES|QL 确实提供了根据需要调整字段的灵活性。</p><h2>es|ql 查找连接</h2><p>Elasticsearch 8.18 引入了一种在 Elasticsearch 中连接索引的新方法，即<code>LOOKUP JOIN</code> 命令。该命令在连接的右侧使用新的<a href="https://www.elastic.co/cn/docs/reference/elasticsearch/index-settings/index-modules#index-mode-setting">查找索引模式</a>，以 SQL 风格的 LEFT OUTER JOIN 方式运行。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt783ffb3f9802f92d/6a17f6b8e9ea870608a9c788/1d73495979c4d6bb675c4c966ea86d9a72dc1c48-510x605.png" alt="es|ql 查找连接" /><p>再看我们之前的例子，新的查询如下，其中<code>match_field</code> 需要同时出现在<code>first_index</code> 和<code>second_index</code> 中：</p><p>与其他方法相比，LOOKUP JOIN 的优势在于它不需要任何<code>enrich</code> 策略，因此也不需要与设置策略相关的额外处理。与本文讨论的其他方法不同，它在处理经常变化的丰富数据时非常有用。</p><h2>结论</h2><p>总之，虽然 Elasticsearch 不支持传统的连接操作，但它提供了各种功能，可用于实现类似的结果。具体来说，我们介绍了如何使用连接操作：</p><ol><li><p><code>terms</code> 查询</p></li><li><p><code>enrich</code> 摄录流水线中的处理器</p></li><li><p>Logstash<code>elasticsearch</code> 过滤器插件</p></li><li><p>ES|QL <code>ENRICH</code></p></li><li><p>ES|QL <code>LOOKUP JOIN</code></p></li></ol><p>需要注意的是，这些方法都有其局限性，应根据具体要求和数据性质谨慎使用。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-join-two-indexes</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-join-two-indexes</guid>
    <category><![CDATA[基础功能]]></category>
    <dc:creator><![CDATA[Carly Richmond]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt74822b3b7cb2a41a/6a17f6b97f6f156288c09cc7/0d4736d10fa3e12e6233cd59993299c7bd48911b-680x450.png" length="0" type="image/png"/>
    <pubDate>Wed, 07 May 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 Elasticsearch 和 Go 进行混合搜索来打地鼠]]></title>
    <description><![CDATA[了解如何使用 Elasticsearch 和 Elasticsearch Go 客户端结合关键字和矢量搜索实现混合搜索。]]></description>
    <content:encoded><![CDATA[<p>本系列的前几部分演示了如何使用 Elasticsearch Go 客户端进行<a href="https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client">传统的关键字搜索</a>和<a href="https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client">矢量搜索</a>。第三部分涉及混合搜索。我们将分享如何使用<a href="https://www.elastic.co/elasticsearch/"> Elasticsearch</a> 和<a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch"> </a><a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Elasticsearch Go 客户端</a> 结合矢量搜索和关键字搜索的 示例 。</p><h2>准备工作</h2><p>与本系列的第一部分一样，本示例也需要以下前提条件：</p><ol><li><p>安装 Go 1.21 或更高版本</p></li><li><p>使用<a href="https://go.dev/doc/code">Go 文档</a>中推荐的结构和软件包管理创建自己的 Go 仓库</p></li><li><p>创建自己的 Elasticsearch 集群，其中包含一组<a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch#sources"> 基于啮齿类动物的页面</a> ，包括维基百科中与我们友好的<a href="https://en.wikipedia.org/wiki/Gopher"> Gopher</a> 的 页面 ：</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a436c42996e5172/6a1704cf0c48573d1c01a957/34fa81a9b4c292634c719b8303a9b6b7506d7920-1440x662.png" alt="维基百科 Gopher 网页" /><h2>连接到 Elasticsearch</h2><p>提醒一下，在我们的示例中，我们将使用 Go 客户端提供的<a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/typedapi.html">Typed API</a>。要为任何查询建立安全连接，都需要使用以下两种方法之一配置客户端：</p><ol><li><p>云 ID 和 API 密钥（如果使用弹性云</p></li><li><p>群集 URL、用户名、密码和证书</p></li></ol><p>连接到位于 Elastic Cloud 上的集群的过程如下：</p>func GetElasticsearchClient() (*elasticsearch.TypedClient, error) {
	var cloudID = os.Getenv("ELASTIC_CLOUD_ID")
	var apiKey = os.Getenv("ELASTIC_API_KEY")

	var es, err = elasticsearch.NewTypedClient(elasticsearch.Config{
		CloudID: cloudID,
		APIKey:  apiKey,
		Logger:  &amp;elastictransport.ColorLogger{os.Stdout, true, true},
	})

	if err != nil {
		return nil, fmt.Errorf("unable to connect: %w", err)
	}

	return es, nil
}
<p>然后，<code>client</code> 连接可用于搜索，这将在随后的章节中演示。</p><h2>手动增强混合搜索</h2><p>在组合任何一组搜索算法时，传统的方法是手动配置常量，以提高每种查询类型的效率。具体来说，为每个查询指定一个因子，然后将组合结果集与预期结果集进行比较，以确定查询的召回率。然后，我们对几组因子进行重复计算，选出最接近理想状态的因子。</p><p>例如，在两种查询类型中都指定<code>Boost</code> 字段，就可以将系数为<code>0.8</code> 的单一文本搜索查询与系数为<code>0.2</code> 的较低 knn 查询结合起来，如下例所示：</p>func HybridSearchWithBoost(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
	var k = 10
	var numCandidates = 10
	var knnBoost float32 = 0.2
	var queryBoost float32 = 0.8

	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnSearch{
			Field:         "text_embedding.predicted_value",
			Boost:         &amp;knnBoost,
			K:             &amp;k,
			NumCandidates: &amp;numCandidates,
			QueryVectorBuilder: &amp;types.QueryVectorBuilder{
				TextEmbedding: &amp;types.TextEmbedding{
					ModelId:   "sentence-transformers__msmarco-minilm-l-12-v3",
					ModelText: term,
				},
			}}).
		Query(&amp;types.Query{
			Match: map[string]types.MatchQuery{
				"title": {
					Query: term,
					Boost: &amp;queryBoost,
				},
			},
		}).
		Do(context.Background())

	if err != nil {
		return nil, err
	}

	return getRodents(res.Hits.Hits)
}
<p><code>Boost</code> 选项中为每个查询指定的因子会添加到文档得分中。通过将匹配查询的得分提高一个比 knn 查询更大的系数，关键词查询的结果将获得更高的权重。</p><p>手动提升的挑战在于，它需要进行调整，以找出能产生理想结果集的因素，这对不是搜索专家的人来说尤其如此。这只是一个尝试随机值的例子，看看什么值更接近你想要的结果集。</p><h2>混合搜索中的互惠排名融合&amp; Go 客户端</h2><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">互惠排名融合</a>（RRF）在 Elasticsearch 8.9 混合搜索技术预览版中发布。其目的是降低与调整相关的学习曲线，减少为优化结果集而试验各种因素的时间。</p><p>使用 RRF 时，文档得分通过以下算法混合得分重新计算：</p>score := 0.0
// q is a query in the set of queries (vector and keyword search)
for _, q := range queries {
    // result(q) is the results 
    if document in result(q) {
        // k is a ranking constant (default 60)
        // rank(result(q), d) is the document's rank within result(q) 
        // range from 1 to the window_size (default 100)
        score +=  1.0 / (k + rank(result(q), d))
    }
}

return score
<p>使用 RRF 的好处是，我们可以利用 Elasticsearch 中合理的默认值。排序常数<code>k</code> 默认为<code>60</code> 。在对大型数据集进行搜索时，为了在返回文档的相关性和查询性能之间进行权衡，每个考虑过的查询结果集的大小都限制在<code>window_size</code> 的值范围内，默认值为<code>100</code> ，如<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html#rrf-api">文档</a>中所述。</p><p><code>k</code> 和<code>windows_size</code> 也可以在 Go 客户端<code>Rank</code> 方法中的<code>Rrf</code> 配置中进行配置，如下例所示：</p>func HybridSearchWithRRF(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
	var k = 10
	var numCandidates = 10

	// Minimum required window size for the default result size of 10
	var windowSize int64 = 10
	var rankConstant int64 = 42

	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnSearch{
			Field:         "text_embedding.predicted_value",
			K:             &amp;k,
			NumCandidates: &amp;numCandidates,
			QueryVectorBuilder: &amp;types.QueryVectorBuilder{
				TextEmbedding: &amp;types.TextEmbedding{
					ModelId:   "sentence-transformers__msmarco-minilm-l-12-v3",
					ModelText: term,
				},
			}}).
		Query(&amp;types.Query{
			Match: map[string]types.MatchQuery{
				"title": {Query: term},
			},
		}).
		Rank(&amp;types.RankContainer{
			Rrf: &amp;types.RrfRank{
				WindowSize:   &amp;windowSize,
				RankConstant: &amp;rankConstant,
			},
		}).
		Do(context.Background())

	if err != nil {
		return nil, err
	}

	return getRodents(res.Hits.Hits)
}
<h2>结论</h2><p>在此，我们讨论了如何使用 Elasticsearch<a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html"> Go 客户端 在</a> Elasticsearch 中将向量搜索和关键字搜索结合起来。</p><p>查看<a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch">GitHub 仓库</a>中本系列的所有代码。如果您还没有阅读本系列的所有代码，请查看<a href="https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client">第 1 部分</a>和<a href="https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client">第 2 部分</a>。</p><p><em>祝您打地鼠愉快</em></p><h2>资源</h2><ol><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html">Elasticsearch 指南</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Elasticsearch Go 客户端</a></p></li><li><p><a href="https://www.elastic.co/what-is/vector-search">什么是矢量搜索？| 弹性</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">互惠等级融合</a></p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hybrid-search-with-the-elasticsearch-go-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hybrid-search-with-the-elasticsearch-go-client</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[Go]]></category>
    <dc:creator><![CDATA[Carly Richmond,Laurent Saint-Félix]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdcae959696d55a9b/6a1704d5ab7f085a56db9d7c/491ef9efbb30b253e1d9e3b7f816a9a23c8f5264-721x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 02 Nov 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 Elasticsearch Go 客户端在 Elasticsearch 中执行矢量搜索]]></title>
    <description><![CDATA[通过实际示例了解如何使用 Elasticsearch Go 客户端在 Elasticsearch 中执行矢量搜索。]]></description>
    <content:encoded><![CDATA[<p>使用包括 Go 语言在内的任何编程语言构建软件，都需要终身学习。在大学和工作生涯中，卡莉涉猎过多种编程语言和技术，包括最新、最先进的矢量搜索技术。但这还不够！所以最近 Carly 也开始玩围棋了。</p><p>就像动物、编程语言和您友好的作者一样，搜索也经历了不同实践的演变，您可能很难在自己的搜索用例中做出选择。在本博客中，我们将分享矢量搜索的概述，以及使用<a href="https://www.elastic.co/elasticsearch/"> Elasticsearch</a> 和<a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html"> Elasticsearch Go 客户端</a> 的每种方法的<a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch"> 示例</a> 。这些示例将向您展示如何使用 Elasticsearch 和 Go 中的矢量搜索查找地鼠并确定它们吃什么。</p><h2>准备工作</h2><p>要学习本示例，请确保满足以下前提条件：</p><ol><li><p>安装 Go 1.21 或更高版本</p></li><li><p>使用</p></li><li><p>创建您自己的 Elasticsearch 集群，其中包含一组基于啮齿动物的页面，包括维基百科中对我们友好的<a href="https://en.wikipedia.org/wiki/Gopher">Gopher</a> 的介绍：</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a436c42996e5172/6a1704cf0c48573d1c01a957/34fa81a9b4c292634c719b8303a9b6b7506d7920-1440x662.png" alt="维基百科 Gopher 网页" /><h2>连接到 Elasticsearch</h2><p>在我们的示例中，我们将使用 Go 客户端提供的<a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/typedapi.html">类型 API</a>。要为任何查询建立安全连接，都需要使用以下两种方法之一配置客户端：</p><ol><li><p>云 ID 和 API 密钥（如果使用弹性云）。</p></li><li><p>集群 URL、用户名、密码和证书。</p></li></ol><p>连接到位于 Elastic Cloud 上的集群的过程如下：</p>func GetElasticsearchClient() (*elasticsearch.TypedClient, error) {
	var cloudID = os.Getenv("ELASTIC_CLOUD_ID")
	var apiKey = os.Getenv("ELASTIC_API_KEY")

	var es, err = elasticsearch.NewTypedClient(elasticsearch.Config{
		CloudID: cloudID,
		APIKey:  apiKey,
		Logger:  &amp;elastictransport.ColorLogger{os.Stdout, true, true},
	})

	if err != nil {
		return nil, fmt.Errorf("unable to connect: %w", err)
	}

	return es, nil
}
<p>如后续章节所示，<code>client</code> 连接可用于矢量搜索。</p><h2>向量搜索</h2><p>矢量搜索试图通过将搜索问题转换为使用矢量的数学比较来解决这一问题。文件嵌入过程还有一个额外的阶段，即使用模型将文件转换成密集的向量表示，或者简单地转换成数字流。这种方法的优势在于，通过将非文本文档（如图像和音频）翻译成矢量并与查询一起进行搜索。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc4b3b9b2ad1e8f46/6a1704d06234e0800fdb1927/b113358093e367358d684f7aaf0a6684ebb2d0dd-1440x653.png" alt="矢量搜索图" /><p>简单地说，矢量搜索是一组矢量距离计算。在下图中，我们将查询<code>Go Gopher</code>的向量表示与向量空间中的文档进行比较，并返回最接近的结果（用常数<code>k</code> 表示）：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5c736ecfc49bb038/6a1704d2cf4f25bcb6b2d075/54a17a4b41029644e7c66f33d428e8d01a6f4ce3-1184x743.png" alt="Gopher 矢量空间示例" /><p>根据为文档生成嵌入式的方法，有两种不同的方法可以找出地鼠吃什么。</p><h3>方法 1：自带模型</h3><p>有了白金许可，就可以通过上传模型和使用推理 API 在 Elasticsearch 中生成嵌入。建立模型有六个步骤：</p><ol><li><p>从模型库中选择一个 PyTorch 模型上传。在本例中，我们使用 Hugging Face 的<a href="https://huggingface.co/sentence-transformers/msmarco-MiniLM-L-12-v3">句子转换器/msmarco-MiniLM-L-12-v3</a>来生成嵌入。</p></li><li><p>使用 Elasticsearch 集群的凭据和任务类型<code>text_embeddings</code> ，使用<a href="https://www.elastic.co/guide/en/elasticsearch/client/eland/current/overview.html">Python 版 Eland 机器学习客户端</a>将模型加载到 Elastic 中。如果没有安装 Eland，可以<a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-import-model.html#ml-nlp-import-docker">使用 Docker 运行导入步骤</a>，如下图所示：</p></li></ol>docker run -it --rm --network host \
    docker.elastic.co/eland/eland \
    eland_import_hub_model \
      --cloud-id $ELASTIC_CLOUD_ID \
      --es-api-key $ELASTIC_API_KEY \
      --hub-model-id sentence-transformers/msmarco-MiniLM-L-12-v3 \
      --task-type text_embedding
<ol><li><p>上传后，请使用样本文档快速测试模型<code>sentence-transformers__msmarco-minilm-l-12-v3</code> ，以确保生成的嵌入结果符合预期：</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a399e1f50ecfdaf/6a1704d4ab7f08935edb9d78/fbbdde621361a487eb07304bed029c228d0f7aa6-1440x789.png" alt="弹性测试训练模型示例" /><ol><li><p>创建包含推理处理器的摄取管道。这将允许使用上传的模型生成矢量表示：</p></li></ol>PUT _ingest/pipeline/search-rodents-vector-embedding-pipeline
{
  "processors": [
    {
      "inference": {
        "model_id": "sentence-transformers__msmarco-minilm-l-12-v3",
        "target_field": "text_embedding",
        "field_map": {
          "body_content": "text_field"
        }
      }
    }
  ]
}
<ol><li><p>创建一个新索引，其中包含类型为<code>dense_vector</code> 的字段<code>text_embedding.predicted_value</code> ，用于存储为每个文档生成的向量嵌入：</p></li></ol>PUT vector-search-rodents
{
  "mappings": {
    "properties": {
      "text_embedding.predicted_value": {
        "type": "dense_vector",
        "dims": 384,
        "index": true,
        "similarity": "cosine"
      },
      "text": {
        "type": "text"
      }
    }
  }
}
<ol><li><p>使用新创建的摄取管道重新索引文档，生成文本嵌入作为每个文档的附加字段<code>text_embedding.predicted_value</code> ：</p></li></ol>POST _reindex
{
  "source": {
    "index": "search-rodents"
  },
  "dest": {
    "index": "vector-search-rodents",
    "pipeline": "search-rodents-vector-embedding-pipeline"
  }
}
<p>现在，我们可以使用新的索引<code>vector-search-rodents</code> ，在同一个搜索 API 中使用<code>Knn</code> 选项，如下例所示：</p>func VectorSearch(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
  var k = 10
	var numCandidates = 10

	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnSearch{
      # Field in document containing vector
			Field:         "text_embedding.predicted_value",
      # Number of neighbors to return
			K:             &amp;k,
      # Number of candidates to evaluate in comparison
			NumCandidates: &amp;numCandidates,
      # Generate query vector using the same model used in the inference processor
			QueryVectorBuilder: &amp;types.QueryVectorBuilder{
				TextEmbedding: &amp;types.TextEmbedding{
					ModelId:   "sentence-transformers__msmarco-minilm-l-12-v3",
					ModelText: term,
				},
			}}).Do(context.Background())

	if err != nil {
		return nil, fmt.Errorf("error in rodents vector search: %w", err)
	}

	return getRodents(res.Hits.Hits)
}
<p>通过解分叉转换 JSON 结果对象的方法与关键字搜索示例完全相同。常量<code>K</code> 和<code>NumCandidates</code> 允许我们配置返回的邻接文件数和每个分区要考虑的候选文件数。需要注意的是，增加候选结果的数量会提高结果的准确性，但由于要进行更多的比较，查询的运行时间会更长。</p><p>当使用查询<code>What do Gophers eat?</code> 执行代码时，返回的结果与下图类似，突出显示 Gopher 文章包含所需的信息，与之前的关键字搜索不同：</p>[
  {ID:64f74ecd4acb3df024d91112 Title:Gopher - Wikipedia Url:https://en.wikipedia.org/wiki/Gopher} 
  {ID:64f74ed34acb3d71aed91fcd Title:Squirrel - Wikipedia Url:https://en.wikipedia.org/wiki/Squirrel} 
  //Other results omitted
]
<h3>方法 2：拥抱脸推理应用程序接口</h3><p>另一种方法是在 Elasticsearch 之外生成相同的嵌入，并将其作为文档的一部分进行摄取。由于该选项不使用 Elasticsearch 机器学习节点，因此可以在免费层上完成。</p><p>Hugging Face 公开了一个免费使用、有费率限制的<a href="https://huggingface.co/docs/api-inference/index">推理 API</a>，只要有账户和 API 令牌，就可以手动生成相同的嵌入式数据，用于实验和原型开发，帮助你开始工作。不建议用于生产。也可以使用类似的方法在本地调用自己的模型来生成嵌入，或使用付费 API。</p><p>在下面的函数<code>GetTextEmbeddingForQuery</code> 中，我们根据查询字符串使用推理 API 生成从<code>POST</code> 请求端点返回的向量：</p>// HuggingFace text embedding helper
func GetTextEmbeddingForQuery(term string) []float32 {
    // HTTP endpoint
    model := "sentence-transformers/msmarco-minilm-l-12-v3"
    posturl := fmt.Sprintf("https://api-inference.huggingface.co/pipeline/feature-extraction/%s", model)

    // JSON body
    body := []byte(fmt.Sprintf(`{
        "inputs": "%s",
        "options": {"wait_for_model":True}
    }`, term))

    // Create a HTTP post request
    r, err := http.NewRequest("POST", posturl, bytes.NewBuffer(body))

    if err != nil {
        log.Fatal(err)
        return nil
    }

    token := os.Getenv("HUGGING_FACE_TOKEN")
    r.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))

    client := &amp;http.Client{}
    res, err := client.Do(r)
    if err != nil {
        panic(err)
    }

    defer res.Body.Close()

    var post []float32
    derr := json.NewDecoder(res.Body).Decode(&amp;post)

    if derr != nil {
        log.Fatal(derr)
        return nil
    }

    return post
}
<p>生成的<code>[]float32</code> 类型的向量将作为<code>QueryVector</code> 传递，而不是使用<code>QueryVectorBuilder</code> 选项来利用之前上传到 Elastic 的模型。</p>func VectorSearchWithGeneratedQueryVector(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
	vector, err := GetTextEmbeddingForQuery(term)
	if err != nil {
		return nil, err
	}

	if vector == nil {
		return nil, fmt.Errorf("unable to generate vector: %w", err)
	}

  var k = 10
	var numCandidates = 10

	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnSearch{
      # Field in document containing vector
			Field:         "text_embedding.predicted_value",
      # Number of neighbors to return
			K:             &amp;k,
      # Number of candidates to evaluate in comparison
			NumCandidates: &amp;numCandidates,
      # Query vector returned from Hugging Face inference API
			QueryVector:   vector,
		}).
		Do(context.Background())

	if err != nil {
		return nil, err
	}

	return getRodents(res.Hits.Hits)
}
<p>请注意，无论使用哪两个选项，<code>K</code> 和<code>NumCandidates</code> 选项都是一样的，而且生成的结果也是一样的，因为我们使用的是同一个模型来生成嵌入结果</p><h2>结论</h2><p>在此，我们讨论了如何使用 Elasticsearch<a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html"> Go 客户端 在</a> Elasticsearch 中执行矢量搜索。查看<a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch">GitHub 仓库</a>中本系列的所有代码。请继续阅读<a href="https://www.elastic.co/search-labs/blog/hybrid-search-with-the-elasticsearch-go-client">第 3 部分</a>，了解如何将矢量搜索与 Go 语言<a href="https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client">第一部分</a>中的关键字搜索功能相结合。</p><p>在此之前，祝您打地鼠愉快！</p><h2>资源</h2><ol><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html">Elasticsearch 指南</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Elasticsearch Go 客户端</a></p></li><li><p><a href="https://www.elastic.co/what-is/vector-search">什么是矢量搜索？| 弹性</a></p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client</guid>
    <category><![CDATA[向量数据库]]></category>
    <category><![CDATA[Go]]></category>
    <dc:creator><![CDATA[Carly Richmond,Laurent Saint-Félix]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdcae959696d55a9b/6a1704d5ab7f085a56db9d7c/491ef9efbb30b253e1d9e3b7f816a9a23c8f5264-721x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 01 Nov 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[使用 Elasticsearch Go 客户端执行文本查询]]></title>
    <description><![CDATA[通过实际示例了解如何使用 Elasticsearch Go 客户端在 Elasticsearch 中执行传统文本查询。]]></description>
    <content:encoded><![CDATA[<p>使用包括 Go 语言在内的任何编程语言构建软件，都需要终身学习。在整个大学和工作生涯中，Carly 需要适应多语言环境，并涉猎多种编程语言，包括 Python、C、JavaScript、TypeScript 和 Java。但这还不够！所以最近她也开始玩围棋了！</p><p>就像动物、编程语言和您的一位友好作家一样，搜索也经历了不同实践的演变，您可能很难在自己的搜索用例中做出选择。在本博客中，我们将概述传统的关键字搜索，并以使用<a href="https://www.elastic.co/elasticsearch/"> Elasticsearch</a> 和<a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html"> Elasticsearch</a> Go<a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch"> 客户端 为例</a> 进行说明。</p><h2>准备工作</h2><p>要学习本示例，请确保满足以下前提条件：</p><ol><li><p>安装 Go 1.21 或更高版本</p></li><li><p>使用<a href="https://go.dev/doc/code">Go 文档</a>中推荐的结构和软件包管理创建自己的 Go 仓库</p></li><li><p>创建您自己的 Elasticsearch 集群，其中包含一组<a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch#sources"> 基于啮齿动物的页面</a> ，包括维基百科中与我们友好的<a href="https://en.wikipedia.org/wiki/Gopher"> Gopher</a> 的 页面 ：</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a436c42996e5172/6a1704cf0c48573d1c01a957/34fa81a9b4c292634c719b8303a9b6b7506d7920-1440x662.png" alt="维基百科 Gopher 网页" /><h2>连接到 Elasticsearch</h2><p>在我们的示例中，我们将使用 Go 客户端提供的<a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/typedapi.html">类型 API</a>。要为任何查询建立安全连接，都需要使用以下任一种方法配置客户端：</p><ol><li><p>云 ID 和 API 密钥（如果使用弹性云）。</p></li><li><p>集群 URL、用户名、密码和证书。</p></li></ol><p>连接到位于 Elastic Cloud 上的集群的过程如下：</p>func GetElasticsearchClient() (*elasticsearch.TypedClient, error) {
	var cloudID = os.Getenv("ELASTIC_CLOUD_ID")
	var apiKey = os.Getenv("ELASTIC_API_KEY")

	var es, err = elasticsearch.NewTypedClient(elasticsearch.Config{
		CloudID: cloudID,
		APIKey:  apiKey,
		Logger:  &amp;elastictransport.ColorLogger{os.Stdout, true, true},
	})

	if err != nil {
		return nil, fmt.Errorf("unable to connect: %w", err)
	}

	return es, nil
}
<p>如稍后所示，<code>client</code> 连接可用于搜索。</p><h2>关键词搜索</h2><p>自 1990 年第一个有文字记载的互联网搜索引擎<a href="https://en.wikipedia.org/wiki/Archie_(search_engine)">Archie</a> 诞生以来，关键词搜索就是我们所熟悉的基础搜索类型。</p><p>关键词搜索的核心部分是将文件翻译成倒排索引。与教科书后面的索引完全相同，倒排索引包含标记列表与其在每个文档中的位置之间的映射。下图显示了生成索引的关键阶段：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt366681a12027f14b/6a17e7aa1d1b831c2093e452/9cd7b2a60d9aa54facb37738b821bba6ca925830-1440x581.png" alt="反向索引生成" /><p>如上所述，Elasticsearch 中令牌的生成包括三个关键阶段：</p><ol><li><p>通过 0 个或多个<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-charfilters.html"><code>char_filters</code></a> 删除不必要的字符。在我们的示例中，我们通过<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-htmlstrip-charfilter.html"><code>html_strip</code></a> 过滤器去除<code>body_content</code> 字段中的 HTML 元素。</p></li><li><p>使用<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html"><code>standard</code></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html"> 令牌化器</a> 从内容中分割 令牌 ，令牌 化器 会根据间距和关键标点符号进行分割。</p></li><li><p>使用零个或多个<code>filter</code> 选项，如<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lowercase-tokenfilter.html"><code>lowercase</code></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lowercase-tokenfilter.html"> token filter</a> 或 stemmer<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-snowball-tokenfilter.html"><code>snowball</code></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-snowball-tokenfilter.html"> </a>等词干转换器，从令牌转换器的输出流中移除不需要的令牌或转换令牌，以便将令牌转换回其语言词根。</p></li></ol><h2>用 Go 在 Elasticsearch 中搜索</h2><p>使用 Go 客户端进行查询时，我们要指定要搜索的索引，并传递查询和其他选项，就像下面的示例一样：</p>func KeywordSearch(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
	res, err := client.Search().
		Index("search-rodents").
		Query(&amp;types.Query{
			Match: map[string]types.MatchQuery{
				"title": {Query: term},
			},
		}).
		From(0).
		Size(10).
		Do(context.Background())

	if err != nil {
		return nil, fmt.Errorf("could not search for rodents: %w", err)
	}

	return getRodents(res.Hits.Hits)
}
<p>在上面的示例中，我们执行了一个标准的<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html"><code>match</code></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html"> 查询</a>，以查找索引中包含传入函数的指定字符串的任何文档。请注意，我们通过<code>Do(context.Background())</code> 向搜索执行传递了一个<a href="https://pkg.go.dev/context#Background">新的空上下文</a>。此外，Elasticsearch 返回的任何错误都会输出到<code>err</code> 属性，以便记录日志和处理错误。</p><p>结果以<code>res.Hits.Hits</code> 的形式返回，其中<code>_Source</code> 属性包含 JSON 格式的文档本身。要将该源代码转换为 Go 友好的结构，我们需要使用 Go<a href="https://pkg.go.dev/encoding/json"> encoding/json</a><a href="https://pkg.go.dev/encoding/json#Unmarshal"> </a>包对 JSON 响应进行 解码 ，如下例所示：</p>func getRodents(hits []types.Hit) ([]Rodent, error) {
	var rodents []Rodent

	for _, hit := range hits {
		var currentRodent Rodent
		err := json.Unmarshal(hit.Source_, &amp;currentRodent)

		if err != nil {
			return nil, fmt.Errorf("an error occurred while unmarshaling rodent %s: %w", hit.Id_, err)
		}

		currentRodent.ID = hit.Id_
		rodents = append(rodents, currentRodent)
	}

	return rodents, nil
}
<p>搜索并取消查询<code>gopher</code> 将如期返回维基百科中有关 Gopher 的页面：</p>[
  {ID:64f74ecd4acb3df024d91112 Title:Gopher - Wikipedia Url:https://en.wikipedia.org/wiki/Gopher}
]
<p>然而，如果我们询问<code>What do Gophers eat?</code> ，就不会得到我们想要的结果：</p>[]
<p>通过简单的关键字搜索，就能以我们熟悉的方式将结果返回到 Go 应用程序中。它还能很好地进行精确的术语匹配，这与查找特定公司或术语等情况相关。</p><p>然而，正如我们在上文所看到的，由于词汇不匹配问题，它在识别上下文和语义方面存在困难。此外，对图像和音频等非文本文件格式的支持也具有挑战性。</p><h2>结论</h2><p>在这里，我们讨论了如何使用 Elasticsearch<a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html"> Go 客户端 在</a> Elasticsearch 中执行传统文本查询。鉴于 Go 被广泛用于基础设施脚本和构建网络服务器，因此了解如何用 Go 进行搜索非常有用。</p><p>查看<a href="https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client">GitHub 仓库</a>中本系列的所有代码。请继续阅读<a href="https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client">第 2 部分</a>，了解矢量搜索的概述以及如何在 Go 中执行矢量搜索。在此之前，祝您打地鼠愉快！</p><h2>资源</h2><ol><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html">Elasticsearch 指南</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Elasticsearch Go 客户端</a></p></li><li><p><a href="https://codingexplained.com/coding/elasticsearch/understanding-analysis-in-elasticsearch-analyzers">了解 Elasticsearch 中的分析（分析器），作者 Bo Andersen | #CodingExplained</a></p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client</guid>
    <category><![CDATA[Go]]></category>
    <dc:creator><![CDATA[Carly Richmond,Laurent Saint-Félix]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdcae959696d55a9b/6a1704d5ab7f085a56db9d7c/491ef9efbb30b253e1d9e3b7f816a9a23c8f5264-721x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 31 Oct 2023 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>