<?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[Florian Bernd - 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[Florian Bernd - 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/florian-bernd</link>
    </image>
    <link>https://www.elastic.co/cn/search-labs/author/florian-bernd</link>
    <atom:link href="https://www.elastic.co/cn/search-labs/rss/author/florian-bernd.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[cn]]></language>
    <lastBuildDate>Sat, 17 Oct 2026 00:13:18 GMT</lastBuildDate>
  <item>
    <title><![CDATA[LINQ to Elasticsearch ES|QL：编写 C# 代码，查询 Elasticsearch]]></title>
    <description><![CDATA[探索 Elasticsearch .NET 客户端中全新的 LINQ to Elasticsearch ES|QL 提供程序。借助该程序，您可以编写会自动转换为 ES|QL 查询的 C# 代码。]]></description>
    <content:encoded><![CDATA[<p>从 <strong>v9.3.4</strong> 和 <strong>v8.19.18</strong> 开始，Elasticsearch .NET 客户端包含一个<a href="https://learn.microsoft.com/en-us/dotnet/csharp/linq/">语言集成查询 (LINQ) </a>提供程序，可在运行时将 C# LINQ 表达式转换为 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">Elasticsearch 查询语言 (ES|QL)</a> 查询。您可以使用<code>Where</code>、<code>Select</code>、<code>OrderBy</code>、<code>GroupBy</code> 和其他标准操作符来编写查询，而无需手工编写 ES|QL 字符串。提供程序负责转换、参数化和结果反序列化，包括按行流式传输，无论结果集大小如何，都能保持稳定的内存使用量。</p><h2>您的第一个查询</h2><p>首先定义一个映射到 Elasticsearch 索引的普通旧 CLR 对象 (POCO)。属性名称通过标准<code>System.Text.Json</code> 属性（如<code>[JsonPropertyName]</code>）或配置的<code>JsonNamingPolicy</code> 解析为 ES|QL 列名。适用于客户端其他部分的<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization">源序列化</a>规则在这里也同样适用。</p>using System.Text.Json.Serialization;

public class Product
{
    [JsonPropertyName("product_id")]
    public string Id { get; set; }

    public string Name { get; set; }

    public string Brand { get; set; }

    [JsonPropertyName("price_usd")]
    public double Price { get; set; }

    [JsonPropertyName("in_stock")]
    public bool InStock { get; set; }
}<p>类型设置完成后，查询语句如下所示：</p>var minPrice = 100.0;
var brand = "TechCorp";

await foreach (var product in client.Esql.QueryAsync&lt;Product&gt;(q =&gt; q
    .From("products")
    .Where(p =&gt; p.InStock &amp;&amp; p.Price &gt;= minPrice &amp;&amp; p.Brand == brand)
    .OrderByDescending(p =&gt; p.Price)
    .Take(10)))
{
    Console.WriteLine($"{product.Name}: ${product.Price}");
}<p>该提供程序将此转换为以下 ES|QL：</p><p>需要注意的一些细节：</p><ul><li><p><strong>属性名称解析：</strong>由于 <code>[JsonPropertyName]</code> 属性，<code>p.Price</code> 变成了 <code>price_usd</code>，根据默认 camelCase 命名策略，<code>p.Brand</code> 变成 <code>brand</code>。</p></li><li><p><strong>参数捕获：</strong>C# 变量 <code>minPrice</code> 和 <code>brand</code> 被捕获为命名参数 (<code>?minPrice</code>，<code>?brand</code>)。它们与 JSON 有效负载中的查询字符串分开发送，这样可以防止注入，并实现服务器端查询计划缓存。</p></li><li><p><strong>流式传输：</strong><code>QueryAsync&lt;T&gt;</code> 返回 <code>IAsyncEnumerable&lt;T&gt;</code>。从 Elasticsearch 返回数据时，数据会逐行具体化。</p></li></ul><p>您还可以在不执行的情况下检查生成的查询及其参数：</p>var query = client.Esql.CreateQuery&lt;Product&gt;()
    .Where(p =&gt; p.InStock &amp;&amp; p.Price &gt;= minPrice &amp;&amp; p.Brand == brand)
    .OrderByDescending(p =&gt; p.Price)
    .Take(10);

Console.WriteLine(query.ToEsqlString());
// FROM products | WHERE (in_stock == true AND price_usd &gt;= 100) | SORT price_usd DESC | LIMIT 10

Console.WriteLine(query.ToEsqlString(inlineParameters: false));
// FROM products | WHERE (in_stock == true AND price_usd &gt;= ?minPrice AND brand == ?brand) | SORT price_usd DESC | LIMIT 10

var parameters = query.GetParameters();
// { "minPrice": 100.0, "brand": "TechCorp" }<h2>这如何运作？快速回顾一下 LINQ</h2><p>使 LINQ 提供程序成为可能的机制是 <code>IEnumerable&lt;T&gt;</code> 和 <code>IQueryable&lt;T&gt;</code> 之间的区别。</p><p>在 <code>IEnumerable&lt;T&gt;</code> 上调用 <code>.Where(p =&gt; p.Price &gt; 100)</code> 时，lambda 会编译为 <code>Func&lt;Product, bool&gt;</code>，即一个由运行时在进程内执行的常规委托。这就是 LINQ-to-Objects。</p><p>当您在<code>IQueryable&lt;T&gt;</code> 上调用相同的方法时，C# 编译器会将 lambda 封装在<code>Expression&lt;Func&lt;Product, bool&gt;&gt;</code> 中。这是一种数据结构，表示代码的<em>结构</em>，而不是代码的可执行形式。在运行时，该表达式树可被检查、分析，并转换为另一种语言。</p>// IEnumerable: the lambda is a compiled delegate
IEnumerable&lt;Product&gt; local = products.Where(p =&gt; p.Price &gt; 100);

// IQueryable: the lambda is an expression tree, a data structure
IQueryable&lt;Product&gt; remote = queryable.Where(p =&gt; p.Price &gt; 100);<p><code>IQueryProvider</code> 接口是扩展点。任何提供程序均可通过实现 <code>CreateQuery&lt;T&gt;</code> 和 <code>Execute&lt;T&gt;</code>，将这些表达式树转换为目标语言。实体框架就是利用此机制生成 SQL 语句。LINQ to ES|QL 提供程序使用它来生成 ES|QL 查询。</p><p>上述查询的表达式树如下所示：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt521838e8b9c36649/6a1705b1839dfa5f40dcfdfe/f864cd18a390831f8d28503a29b5835efb1842f7-1000x720.png" alt="示例查询的表达式树。" /><p><em>示例查询的表达式树。</em></p><p>此表达式树由内而外嵌套：<code>Take</code> 包裹着 <code>OrderByDescending</code>，它又包裹着 <code>Where</code>，而后者再包裹着 <code>From</code>，而最内层是根节点 <code>EsqlQueryable&lt;Product&gt;</code> 常量。对于 <code>&amp;&amp;</code>、<code>&gt;=</code> 和 <code>==</code> 这几种操作符而言，<code>Where</code> 谓词本身是一个由 <code>BinaryExpression</code> 个节点构成的子树，其中包含 <code>MemberExpression</code> 个叶子节点，这些叶子节点用于属性访问，以及对 <code>minPrice</code> 和 <code>brand</code> 变量的闭包捕获。提供程序会遍历这一数据结构，从而生成最终的 ES|QL 查询。</p><h2>深入了解：转换管道</h2><p>从 LINQ 表达式到查询结果的路径遵循六阶段管道：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt930670a505dd61ea/6a1705b3b339d58a54769ecf/2a2c772b63d720f61fc9a28b2f85668fa2db8d38-1999x1036.png" alt="转换管道概述。" /><p><em>转换管道概述。</em></p><h3>1. 表达式树捕获</h3><p>当在一个 <code>IQueryable&lt;T&gt;</code> 对象上串联使用 <code>.Where()</code>、<code>.OrderBy()</code>、<code>.Take()</code> 及其他操作符时，标准的 LINQ 基础架构会构建一个表达式树。<code>EsqlQueryable&lt;T&gt;</code> 实现了 <code>IQueryable&lt;T&gt;</code> 接口，并将处理委托给 <code>EsqlQueryProvider</code>。</p><h3>2. 翻译</h3><p>当查询被执行 (通过枚举、调用 <code>ToList()</code>，或使用 <code>await foreach)</code> 时)，<code>EsqlExpressionVisitor</code>自内而外遍历表达式树。它会将每个 LINQ 方法调用分派给一个专门的访问器进行处理：</p><p>访客</p><p>翻译</p><p>进入</p><p>WhereClauseVisitor</p><p>.Where(predicate)</p><p>WHERE 条件</p><p>SelectProjectionVisitor</p><p>.Select(selector)</p><p>评估 + 保留 + 重命名</p><p>GroupByVisitor</p><p>.GroupBy().Select()</p><p>统计信息 ... 依据</p><p>OrderByVisitor</p><p>.OrderBy() / .ThenBy()</p><p>SORT 字段 [ASC\|DESC]</p><p>EsqlFunctionTranslator</p><p>EsqlFunctions.*、Math.*、字符串方法</p><p>80+ ES|QL 函数</p><p>在翻译过程中，表达式中引用的 C# 变量被捕获为命名参数。</p><h3>3. 查询模型</h3><p>访问器不会直接生成字符串。相反，它们会产生 <code>QueryCommand</code> 对象，一个不可变的中间表征。一个 <code>FromCommand</code>、一个 <code>WhereCommand</code>、一个 <code>SortCommand</code> 和一个 <code>LimitCommand</code>，各代表一条 ES|QL 处理命令。这些数据被收集到<code>EsqlQuery</code> 模型中。</p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt788c9936976f2f62/6a1705b50e2e4910da419ff0/2adc349b6cf655b96b7b3e826a134e8a17fe42fd-1999x1036.png" alt="查询模型和命令模式。" /><p><em>查询模型和命令模式。</em></p><p>该中间模型与表达式树和输出格式均解耦。它可以被检查、拦截（通过 <code>IEsqlQueryInterceptor</code>）或在格式化前进行修改。</p><h3>4. 格式化</h3><p><code>EsqlFormatter</code> 依次访问每个<code>QueryCommand</code> ，并生成最终的 ES|QL 字符串。每条命令占一行，通过 ES|QL 中用于串联处理命令的管道 (|) 运算符分隔。若标识符包含特殊字符，系统会自动用反引号进行转义处理。</p><h3>5. 执行</h3><p>格式化后的 ES|QL 查询字符串及捕获的参数会以 JSON 数据载荷的形式发送至 Elasticsearch 的 <code>/_query</code> 终端。而 <code>IEsqlQueryExecutor</code> 接口则对传输层进行了抽象封装，这正是分层包架构发挥作用的关键环节。</p><h3>6. 实现</h3><p><code>EsqlResponseReader</code> 流式传输JSON响应，但不会将整个结果集缓冲到内存中。以流式方式传输 JSON 响应数据，无需将整个结果集缓存至内存。针对每次查询预先计算生成的 <code>ColumnLayout</code> 树结构，会将扁平化的 ES|QL 列名（如 <code>address.street</code>、<code>address.city</code>）映射到嵌套的 POCO 属性。每行数据会被组装为 <code>T</code> 实例，并通过 <code>IEnumerable&lt;T&gt;</code> 或 <code>IAsyncEnumerable&lt;T&gt;</code> 逐个返回。</p><h2>分层架构</h2><p>LINQ to ES|QL 功能分为三个软件包：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt662bd0dd8861b6b6/6a1705b7a929cf7086ae08a2/41b8aae860ecdc2480edcb1c1d4cc9b03cfb78c9-1999x1036.png" alt="软件包架构。" /><p><em>软件包架构。</em><a href="https://www.nuget.org/packages/Elastic.Esql"><strong><code>Elastic.Esql</code></strong></a> 是纯转换引擎。该组件完全不依赖 HTTP 协议栈，集成了表达式访问器、查询模型、格式化器及响应解析器等核心模块。您可独立使用它来构建和检查 ES|QL 查询（无需连接 Elasticsearch），这在测试验证、查询日志记录或自定义执行层开发等场景中极具实用价值。翻译要点解析：</p>// Translation-only: no Elasticsearch connection needed
var provider = new EsqlQueryProvider();
var query = new EsqlQueryable&lt;Product&gt;(provider)
    .From("products")
    .Where(p =&gt; p.InStock)
    .OrderByDescending(p =&gt; p.Price);

Console.WriteLine(query.ToEsqlString());
// FROM products | WHERE in_stock == true | SORT price_usd DESC<p><a href="https://www.nuget.org/packages/Elastic.Clients.Esql"><strong><code>Elastic.Clients.Esql</code></strong></a> 是一款轻量级的独立 ES|QL 客户端。该组件通过 <code>Elastic.Transport</code> 在 <code>Elastic.Esql</code> 之上扩展了 HTTP 协议执行能力。如果您的应用程序仅需使用 ES|QL 而无需其他 Elasticsearch API，此方案可实现最小化依赖集成。</p><p><a href="https://www.nuget.org/packages/Elastic.Clients.Elasticsearch"><strong><code>Elastic.Clients.Elasticsearch</code></strong></a> 是完整的 Elasticsearch.NET 客户端。它还建立在<code>Elastic.Esql</code> 的基础上，并通过<code>client.Esql</code> 命名空间公开 LINQ 提供程序接口。这是大多数应用程序的推荐入口点。</p><p>两个执行层组件包均提供了针对 <code>IEsqlQueryExecutor</code> 接口的独立实现。该策略接口作为转换与传输层的桥梁。</p><p>当与源码生成的 <code>JsonSerializerContext</code> 配合使用时，这三个组件包均支持原生 AOT 编译。如需完整客户端集成方案，请参阅<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization#native-aot">原生 AOT 文档</a>。</p><h2>不只使用基础功能</h2><p>上面的例子涵盖了筛选、排序和分页。该提供程序支持更广泛的操作范围。</p><h3>聚合</h3><p><code>GroupBy</code>结合 <code>Select</code> 中的聚合函数，转换为 ES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/stats-by"><code>STATS ... BY</code></a>：</p>var stats = client.Esql.Query&lt;Product, object&gt;(q =&gt; q
    .GroupBy(p =&gt; p.Brand)
    .Select(g =&gt; new
    {
        Brand = g.Key,
        Count = g.Count(),
        AvgPrice = g.Average(p =&gt; p.Price),
        MaxPrice = g.Max(p =&gt; p.Price)
    }));

// -&gt; FROM products | STATS COUNT(*), AVG(price_usd), MAX(price_usd) BY brand<h3>投影</h3><p><code>Select</code>，使用匿名类型生成 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval"><code>EVAL</code></a>、<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/keep"><code>KEEP</code></a> 和 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/rename"><code>RENAME</code></a> 命令：</p>var query = client.Esql.CreateQuery&lt;Product&gt;()
    .Select(p =&gt; new { ProductName = p.Name, p.Price, p.InStock });

// -&gt; FROM products | KEEP name, price_usd, in_stock | RENAME name AS ProductName<h3>丰富的函数库</h3><p>通过 <code>EsqlFunctions</code> 类，可以使用超过 80 个 ES|QL 函数，涵盖日期/时间、字符串、数学、IP、模式匹配和评分。标准 <code>Math.*</code> 和 <code>string.*</code> 方法也已转换：</p>.Where(p =&gt; p.Name.Contains("Pro"))       // -&gt; WHERE name LIKE "*Pro*"
.Where(p =&gt; EsqlFunctions.CidrMatch(      // -&gt; WHERE CIDR_MATCH(ip, "10.0.0.0/8")
    p.IpAddress, "10.0.0.0/8"))<h3>查找连接</h3><p>跨索引查找转换为 ES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join"><code>LOOKUP JOIN</code></a>：</p>var enriched = client.Esql.Query&lt;Product, object&gt;(q =&gt; q
    .LookupJoin&lt;Product, CategoryLookup, string, object&gt;(
        "category-lookup-index",
        product =&gt; product.Id,
        category =&gt; category.CategoryId,
        (product, category) =&gt; new { product.Name, category!.CategoryLabel }));<h3>原生 ES|QL 直通接口</h3><p>对于 LINQ 提供程序尚未涵盖的 ES|QL 功能，您可以添加原始片段：</p>var results = client.Esql.Query&lt;Product&gt;(q =&gt; q
    .Where(p =&gt; p.InStock)
    .RawEsql("| EVAL discounted = price_usd * 0.9"));<h3>服务器端异步查询</h3><p>对于长时间运行的查询，可将其提交给服务器进行后台处理：</p>await using var asyncQuery = await client.Esql.SubmitAsyncQueryAsync&lt;Product&gt;(
    q =&gt; q.Where(p =&gt; p.InStock),
    asyncQueryOptions: new EsqlAsyncQueryOptions
    {
        WaitForCompletionTimeout = TimeSpan.FromSeconds(5),
        KeepAlive = TimeSpan.FromMinutes(10)
    });

await asyncQuery.WaitForCompletionAsync();
await foreach (var product in asyncQuery.AsAsyncEnumerable())
    Console.WriteLine(product.Name);<p>服务器端异步查询对于长时间运行的分析型查询/大规模数据集处理尤其有用，这类操作可能会超出常规的超时阈值；在存在负载均衡器、API 网关或代理（这些组件会强制执行严格的 HTTP 超时设置）的超时敏感环境中，异步查询同样优势显著。异步查询通过将查询提交与结果获取解耦，避免了连接中断的情况。</p><h2>开始使用</h2><p>LINQ to ES|QL 自以下版本起可用：</p><ul><li><p><strong>Elastic.Clients.Elasticsearch v9.3.4</strong>（9.x 分支）</p></li><li><p><strong>Elastic.Clients.Elasticsearch v8.19.18</strong>（8.x 分支）</p></li></ul><p>从 NuGet 安装：</p><p><code>dotnet add package Elastic.Clients.Elasticsearch</code></p><p>入口点位于<code>client.Esql</code>上：</p><p>方法</p><p>返回值</p><p>用例</p><p>查询&lt;T&gt;(...)</p><p>IEnumerable&lt;T&gt;</p><p>同步执行</p><p>QueryAsync&lt;T&gt;(...)</p><p>IAsyncEnumerable&lt;T&gt;</p><p>异步流式处理</p><p>CreateQuery&lt;T&gt;()</p><p>IEsqlQueryable&lt;T&gt;</p><p>高级结构分析和检测</p><p>SubmitAsyncQueryAsync&lt;T&gt;(...)</p><p>EsqlAsyncQuery&lt;T&gt;</p><p>长时间运行的服务器端查询</p><p>有关完整的功能参考，包括查询选项、多字段访问、嵌套对象和多值字段处理，请参阅<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/linq-to-esql">LINQ to ES|QL 文档</a>。</p><h2>结论</h2><p>LINQ 转 ES|QL 将 C# LINQ 的强大表达能力引入到 Elasticsearch 的 ES|QL 查询语言中，让您无需手工编写查询字符串，就能生成强类型、可组合的查询。它具备自动参数捕获、流式物化功能，还拥有分层式的软件包架构，既能满足独立转换需求，也能适配完整的 Elasticsearch 客户端，可自然融入任意规模的 .NET 应用程序。安装最新客户端，将 LINQ 表达式指向索引，剩下的就交给该提供程序来处理。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/linq-esql-c-elasticsearch-net-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/linq-esql-c-elasticsearch-net-client</guid>
    <category><![CDATA[ES|QL]]></category>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Florian Bernd,Martijn Laarman]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa35fbcbbf4959f/6a1705b9dc55de19a4e00d07/e54132e915217063e9ed0ec45059c6cfc38e31dd-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[如何使用用于微软语义内核（Microsoft Semantic Kernel）的Elasticsearch矢量存储连接器进行人工智能代理开发]]></title>
    <description><![CDATA[微软语义内核（Microsoft Semantic Kernel）是一款轻量级开源开发工具包，可让您轻松构建人工智能代理，并将最新的人工智能模型集成到您的 C#、Python 或 Java 代码库中。随着Semantic Kernel Elasticsearch向量存储连接器（Elasticsearch Vector Store Connector）的发布，使用Semantic Kernel构建人工智能代理的开发人员现在可以将Elasticsearch作为可扩展的企业级向量存储插件，同时继续使用Semantic Kernel抽象。]]></description>
    <content:encoded><![CDATA[<p>我们与<a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/"> 微软语义内核</a> （ Microsoft<a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/"> Semantic Kernel ）团队合作，宣布面向 微软语义内核</a> （.NET）用户推出<a href="https://github.com/elastic/semantic-kernel-net/"> Semantic Kernel Elasticsearch矢量存储连接器（Vector Store Connector ）。</a>语义内核（Semantic Kernel）简化了企业级人工智能代理的构建过程，包括利用来自矢量存储库（Vector Store）的更多相关数据驱动响应来增强大型语言模型（LLM）的能力。语义内核（Semantic Kernel）为与Elasticsearch等矢量存储进行交互提供了一个无缝的抽象层，可提供创建、列出和删除记录集合以及上传、检索和删除单条记录等基本功能。</p><p><a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/elasticsearch-connector?pivots=programming-language-csharp">开箱即用的Semantic Kernel Elasticsearch向量存储连接器（Vector Store Connector</a>）支持Semantic Kernel<a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/?pivots=programming-language-csharp#the-vector-store-abstraction">向量存储抽象</a>，这使得开发人员在构建人工智能代理时能够非常容易地将Elasticsearch作为向量存储插件。</p><p>Elasticsearch 在开源社区拥有坚实的基础，最近采用了<a href="https://www.elastic.co/blog/elasticsearch-is-open-source-again">AGPL 许可证</a>。这些工具与开源的微软语义内核（Microsoft Semantic Kernel）相结合，可提供强大的企业级解决方案。您可以通过运行此命令<code>curl -fsSL https://elastic.co/start-local | sh </code> ，在几分钟内启动 Elasticsearch（详情请参考<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html">start-local</a>），然后在生产人工智能代理的同时，迁移到<a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;utm_source=semantickernel&amp;utm_content=documentation">云托管</a>或<a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.16/install-elasticsearch.html">自托管</a>版本。</p><p>在本篇博客中，我们将探讨在使用Semantic Kernel（语义内核）时，如何使用<a href="https://github.com/elastic/semantic-kernel-net/">Semantic Kernel Elasticsearch向量存储连接器</a>。该连接器的 Python 版本将在未来推出。</p><h2>高级应用场景：利用 Semantic Kernel&amp; Elasticsearch 构建 RAG 应用程序</h2><p>下面我们将举例说明。在高层次上，我们正在构建一个 RAG（检索增强生成）应用程序，它将用户的问题作为输入，并返回一个答案。我们将使用 Azure OpenAI （ 也可使用<a href="https://devblogs.microsoft.com/semantic-kernel/introducing-new-ollama-connector-for-local-models/"> 本地 LLM</a> ）作为 LLM，Elasticsearch 作为向量存储，Semantic Kernel (.net) 作为将所有组件连接在一起的框架。</p><p>如果您不熟悉 RAG 架构，可以通过以下文章快速了解<a href="https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag">： https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag。</a></p><p>答案由 LLM 生成，LLM 从 Elasticsearch 向量存储中获取与问题相关的上下文。答复还包括法律硕士用作背景的资料来源。</p><h3>RAG 示例</h3><p>在这个具体例子中，我们创建了一个应用程序，允许用户就内部酒店数据库中存储的酒店提出问题。例如，用户可以根据不同标准搜索特定酒店，或要求提供酒店列表。</p><p>在示例数据库中，我们生成了一个包含 100 个条目的<a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">酒店列表</a>。为了让您尽可能轻松地试用连接器演示，我们特意设置了较小的样本量。在实际应用中，Elasticsearch 连接器将显示出其优于其他选项（如 "InMemory "向量存储实现）的优势，尤其是在处理超大数据量时。</p><p>完整的演示应用程序可在 Elasticsearch 向量存储连接器存储<a href="https://github.com/elastic/semantic-kernel-net/tree/main/Elastic.SemanticKernel.Playground">库中</a>找到。</p><p>让我们先将所需的 NuGet 软件包和指令添加到项目中：</p>dotnet add package "Elastic.Clients.Elasticsearch" -v 8.16.2
dotnet add package "Elastic.SemanticKernel.Connectors.Elasticsearch" -v 0.1.2
dotnet add package "Microsoft.Extensions.Hosting" -v 9.0.0
dotnet add package "Microsoft.SemanticKernel.Connectors.AzureOpenAI" -v 1.30.0
dotnet add package "Microsoft.SemanticKernel.PromptTemplates.Handlebars" -v 1.30.0using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using Elastic.Clients.Elasticsearch;
using Elastic.Transport;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Data;
using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;<p>现在，我们可以创建我们的数据模型，并为其提供语义内核（Semantic Kernel）的特定属性，以定义存储模型模式和文本搜索的一些提示：</p>/// &lt;summary&gt;
/// Data model for storing a "hotel" with a name, a description, a  description embedding and an optional reference link.
/// &lt;/summary&gt;
public sealed record Hotel
{
	[VectorStoreRecordKey]
	public required string HotelId { get; set; }

	[TextSearchResultName]
	[VectorStoreRecordData(IsFilterable = true)]
	public required string HotelName { get; set; }

	[TextSearchResultValue]
	[VectorStoreRecordData(IsFullTextSearchable = true)]
	public required string Description { get; set; }

	[VectorStoreRecordVector(Dimensions: 1536, DistanceFunction.CosineSimilarity, IndexKind.Hnsw)]
	public ReadOnlyMemory&lt;float&gt;? DescriptionEmbedding { get; set; }

	[TextSearchResultLink]
	[VectorStoreRecordData]
	public string? ReferenceLink { get; set; }
}<p>存储模型模式属性（`VectorStore*`）与 Elasticsearch 向量存储连接器的实际使用最为相关，即</p><p></p><ul><li><p><code>VectorStoreRecordKey</code> 来标记记录类上的一个属性，作为记录存储在向量存储中的键。</p></li><li><p><code>VectorStoreRecordData</code> 将记录类的一个属性标记为 "数据"。</p></li><li><p><code>VectorStoreRecordVector</code> 将记录类的一个属性标记为矢量。</p></li></ul><p>所有这些属性都接受各种可选参数，可用于进一步定制存储模型。以<code>VectorStoreRecordKey </code> 为例，可以指定不同的距离函数或不同的索引类型。</p><p>文本搜索属性 (<code>TextSearch*</code>) 在本示例的最后一步中非常重要。我们稍后再谈。</p><p>下一步，我们将初始化语义内核引擎，并获取核心服务的引用。在实际应用中，应使用<a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection">依赖注入</a>而不是直接访问服务集合。同样的道理也适用于硬编码的配置和秘密，它们应该使用<a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration">配置提供程序</a>来读取：</p>var builder = Host.CreateApplicationBuilder(args);

// Register AI services.
var kernelBuilder = builder.Services.AddKernel();

kernelBuilder.AddAzureOpenAIChatCompletion("gpt-4o", "https://my-service.openai.azure.com", "my_token");

kernelBuilder.AddAzureOpenAITextEmbeddingGeneration("ada-002", "https://my-service.openai.azure.com", "my_token");

// Register text search service.
kernelBuilder.AddVectorStoreTextSearch&lt;Hotel&gt;();

// Register Elasticsearch vector store.
var elasticsearchClientSettings = new ElasticsearchClientSettings(new Uri("https://my-elasticsearch-instance.cloud"))
    .Authentication(new BasicAuthentication("elastic", "my_password"));

kernelBuilder.AddElasticsearchVectorStoreRecordCollection&lt;string, Hotel&gt;("skhotels", elasticsearchClientSettings);

// Build the host.
using var host = builder.Build();

// For demo purposes, we access the services directly without using a DI context.

var kernel = host.Services.GetService&lt;Kernel&gt;()!;
var embeddings = host.Services.GetService&lt;ITextEmbeddingGenerationService&gt;()!;
var vectorStoreCollection = host.Services.GetService&lt;IVectorStoreRecordCollection&lt;string, Hotel&gt;&gt;()!;

// Register search plugin.
var textSearch = host.Services.GetService&lt;VectorStoreTextSearch&lt;Hotel&gt;&gt;()!;
kernel.Plugins.Add(textSearch.CreateWithGetTextSearchResults("SearchPlugin"));<p>现在可以使用<code>vectorStoreCollection</code> 服务创建数据集，并摄取一些<a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">演示记录</a>：</p>await vectorStoreCollection.CreateCollectionIfNotExistsAsync();

// CSV format: ID;Hotel Name;Description;Reference Link
var hotels = (await File.ReadAllLinesAsync("hotels.csv"))
    .Select(x =&gt; x.Split(';'));

foreach (var chunk in hotels.Chunk(25))
{
    var descriptionEmbeddings = await embeddings.GenerateEmbeddingsAsync(chunk.Select(x =&gt; x[2]).ToArray());
    
    for (var i = 0; i &lt; chunk.Length; ++i)
    {
        var hotel = chunk[i];
        await vectorStoreCollection.UpsertAsync(new Hotel
        {
            HotelId = hotel[0],
            HotelName = hotel[1],
            Description = hotel[2],
            DescriptionEmbedding = descriptionEmbeddings[i],
            ReferenceLink = hotel[3]
        });
    }
}<p>由此可见，语义内核（Semantic Kernel）是如何将向量存储的使用及其复杂性简化为几个简单的方法调用的。</p><p>在 Elasticsearch 中创建一个新索引，并创建所有必要的属性映射。然后，我们的数据集会完全透明地映射到存储模型中，并最终存储到索引中。下面是映射在 Elasticsearch 中的显示方式。</p>{
  "mappings": {
    "properties": {
      "descriptionEmbedding": {
        "dims": 1536,
        "index": true,
        "index_options": {
          "type": "hnsw"
        },
        "similarity": "cosine",
        "type": "dense_vector"
      },
      "hotelName": {
        "type": "keyword"
      },
      "description": {
        "type": "text"
      }
    }
  }
}<p><code>embeddings.GenerateEmbeddingsAsync()</code> 会透明地调用已配置的 Azure AI 嵌入生成服务。</p><p>在这个演示的最后一个步骤中，我们还可以看到更多的神奇之处。</p><p>当用户就数据提问时，只需调用<code>InvokePromptAsync</code> ，就能执行以下所有操作：</p><p>1.为用户的问题生成嵌入代码</p><p>2.在矢量存储器中搜索相关条目</p><p>3.将查询结果插入提示模板</p><p>4.最终提示形式的实际查询将发送到人工智能聊天完成服务</p>// Invoke the LLM with a template that uses the search plugin to
// 1. get related information to the user query from the vector store
// 2. add the information to the LLM prompt.
var response = await kernel.InvokePromptAsync(
    promptTemplate: """
                    Please use this information to answer the question:
                    {{#with (SearchPlugin-GetTextSearchResults question)}}
                      {{#each this}}
                        Name: {{Name}}
                        Value: {{Value}}
                        Source: {{Link}}
                        -----------------
                      {{/each}}
                    {{/with}}
                    
                    Include the source of relevant information in the response.

                    Question: {{question}}
                    """,
    arguments: new KernelArguments
    {
        { "question", "Please show me all hotels that have a rooftop bar." },
    },
    templateFormat: "handlebars",
    promptTemplateFactory: new HandlebarsPromptTemplateFactory());<p>还记得我们之前在数据模型上定义的<code>TextSearch*</code> 属性吗？有了这些属性，我们就能在提示模板中使用相应的占位符，这些占位符会根据向量存储中的条目信息自动填充。</p><p>对于我们的问题"，请告诉我所有拥有屋顶酒吧的酒店。" ，最终答复如下：</p>Console.WriteLine(response.ToString());

// &gt; The hotel that has a rooftop bar is Skyline Suites. You can find more information about this hotel [here](https://example.com/yz567).<p>正确答案是指 hotels.csv 中的以下条目</p>9;
Skyline Suites;
Offering panoramic city views from every suite, this hotel is perfect for those who love the urban landscape. Enjoy luxurious amenities, a rooftop bar, and close proximity to attractions. Luxurious and contemporary.;
https://example.com/yz567<p>这个例子很好地说明了微软语义内核的使用是如何通过其深思熟虑的抽象功能大大降低复杂性，并实现高度灵活性的。例如，只需修改一行代码，就可以更换向量存储或所使用的人工智能服务，而无需重构代码的任何其他部分。</p><p>同时，该框架还提供了大量高级功能，如 "InvokePrompt "函数或模板或搜索插件系统。</p><p>完整的演示应用程序可在 Elasticsearch 向量存储连接器存储库中找到。</p><h2>Elasticsearch 还能做什么</h2><ul><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Elasticsearch 新语义文本映射：简化语义搜索</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-reranking-with-retrievers">利用检索器在 Elasticsearch 中进行语义重排</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1">高级 RAG 技术第 1 部分：数据处理</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">高级 RAG 技术第 2 部分：查询和测试</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-rag-with-llama3-opensource-and-elastic">使用 Llama 3 开放源代码和 Elastic 构建 RAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/local-rag-agent-elasticsearch-langgraph-llama3">使用 LangGraph、LLaMA3 和 Elasticsearch 向量存储从零开始构建本地代理的教程</a></p></li></ul><h2>Elasticsearch&amp; Semantic Kernel（语义内核）：下一步是什么？</h2><ul><li><p>我们展示了在.NET中构建GenAI应用时，如何将Elasticsearch向量存储轻松插入Semantic Kernel。敬请期待下一步的 Python 集成。</p></li><li><p>由于Semantic Kernel（语义内核）为<a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">混合</a>搜索等高级搜索功能建立了抽象，Elasticsearch连接将使.NET开发人员能够在使用Semantic Kernel（语义内核）的同时轻松实现这些功能。</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-connector-microsoft-semantic-kernel</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-connector-microsoft-semantic-kernel</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[.NET]]></category>
    <category><![CDATA[向量数据库]]></category>
    <dc:creator><![CDATA[Florian Bernd,Srikanth Manvi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d8725035e86f8a8/6a17fe447f6f1564f8c09d74/0564fe794e4c66d0507317822d7aa71826183d20-1311x762.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 06 Dec 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch .NET 客户端的演变：从 NEST 到 Elastic.Clients.Elasticsearch]]></title>
    <description><![CDATA[了解 Elasticsearch .NET 客户端的演变以及从 NEST 到 Elastic.Clients.Elasticsearch 的过渡。]]></description>
    <content:encoded><![CDATA[<h2>.NET 客户端和 NEST 简介</h2><p>在.NET世界中，与Elasticsearch的集成长期以来一直是通过<code>NEST</code> 库来实现的，该库是开发人员与Elasticsearch强大的搜索和分析功能进行交互的强大接口。<code>NEST</code>Elasticsearch 的本地 .NET 客户端的需求而诞生的，因其丰富的功能集和无缝集成能力而迅速受到开发人员的青睐。</p><p>近<a href="https://github.com/elastic/elasticsearch-net/commit/724f932ba598915c8c3c35a19827fdfa4f782c1d">14 年来</a>，在<a href="https://github.com/elastic/elasticsearch/commit/ec72ca8b7a115f9b2eea3c76c518062b99a1d015">Elasticsearch 首次提交</a>后仅 8 个月，NEST 就一直忠实地跟踪 Elasticsearch 的发布情况。</p><h2>从 NEST 过渡到 Elastic.Clients.Elasticsearch</h2><p>随着 Elasticsearch 的发展，维护<code>NEST</code> 的复杂代码库变得越来越困难。我们认识到需要一种更可持续的客户端开发方法，并开始了从头开始重新设计 .NET 客户端的旅程。我们花了将近一年的时间才发布了第一个测试版，又花了一年的时间才接近支持所有服务器端点。最困难的决定之一是缩小图书馆的范围，以便优先考虑可维护性。</p><p>鉴于目前 Elasticsearch API 的规模，手动维护 450 多个端点和近 3000 种类型（请求、响应、查询、聚合等）已不再现实。为了确保语言客户端和 Elasticsearch 之间一致、准确和及时的对齐，8.x 客户端和许多相关类型现在都是根据<a href="https://github.com/elastic/elasticsearch-specification">共享规范</a>自动生成代码的。这是在 SDK 和库（如 Azure、AWS 和 Google 云平台的 SDK 和库）中保持客户端和服务器之间一致性的常见解决方案。</p><p>Elasticsearch 规范是 8 年前通过从<code>NEST</code> 导出类型映射创建的，通过客户团队的辛勤工作，我们现在可以使用相同的规范创建新的 .NET 客户端（以及 Java、Go 等其他多种语言的客户端）。</p><p>随着 8.13 版的发布，<code>NEST</code> 正式宣布弃用。随着 Elasticsearch 过渡到<code>Elastic.Clients.Elasticsearch</code> ，<code>NEST</code> 将逐步淘汰，并在今年年底达到报废期。我们强烈建议开发人员尽早开始迁移工作，以确保平稳过渡并减少任何潜在的中断。采用<code>Elastic.Clients.Elasticsearch</code> 不仅能确保与最新服务器功能的兼容性，还能使应用程序免受未来功能过时的影响。</p><h2>Elastic.Clients.Elasticsearch：功能和变更概述</h2><p>切换到 v8 客户端<code>Elastic.Clients.Elasticsearch</code> 可以访问 Elasticsearch 8 的所有新功能，还为库本身带来了大量现代化功能，但也意味着与前代产品相比，便利功能有所减少。一些新的核心功能包括查询语言<code>ES|QL</code> 、现代机器学习（ML）功能以及以兼容 OpenTelemetry 活动的形式改进的诊断功能。从 8.13 版开始，<code>Elastic.Clients.Elasticsearch</code> 支持 Elasticsearch 8 的几乎所有服务器功能。</p><p>例如，一个重要的突破性变化与聚合有关。在<code>NEST</code> 中，Fluent API 的用法如下：</p>s =&gt; s
.Aggregations(aggs =&gt; aggs
    .Children&lt;CommitActivity&gt;("name_of_child_agg", child =&gt; child
        .Aggregations(childAggs =&gt; childAggs
            .Average("average_per_child", avg =&gt; avg.Field(p =&gt; p.ConfidenceFactor))
            .Max("max_per_child", max =&gt; max.Field(p =&gt; p.ConfidenceFactor))
            .Min("min_per_child", min =&gt; min.Field(p =&gt; p.ConfidenceFactor))
        )
    )
)
<p>而 v8 客户端需要使用以下语法：</p>s =&gt; s
.Aggregations(aggs =&gt; aggs
	.Add("name_of_child_agg", agg =&gt; agg
		.Children(_ =&gt; {})
		.Aggregations(childAggs =&gt; childAggs
			.Add("average_per_child", agg =&gt; agg.Avg(avg =&gt; avg.Field(p =&gt; p.ConfidenceFactor)))
			.Add("max_per_child", agg =&gt; agg.Max(max =&gt; max.Field(p =&gt; p.ConfidenceFactor)))
			.Add("min_per_child", agg =&gt; agg.Min(min =&gt; min.Field(p =&gt; p.ConfidenceFactor)))
		)
	)
)
<h2>从 NEST v7 迁移到 .NET 客户端 v8</h2><p>这里有一份全面的迁移指南：<a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/8.18/migration-guide.html">迁移指南：从 NEST v7 到 .NET Client v8</a>。</p><h2>其他资源</h2><ul><li><p><a href="https://github.com/elastic/elasticsearch-net">GitHub 上的 Elastic.Clients.Elasticsearch v8 客户端</a></p></li><li><p><a href="https://www.nuget.org/packages/Elastic.Clients.Elasticsearch">NuGet 上的 Elastic.Clients.Elasticsearch v8 客户端</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/net-client-evolution</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/net-client-evolution</guid>
    <category><![CDATA[.NET]]></category>
    <dc:creator><![CDATA[Florian Bernd]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltac0adcdaa85d703f/6a17f5816df731db170a1079/d09f7f5cb468d5e84f7f4636d92b3476e6604e11-1024x1024.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 16 Apr 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>