<?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/es/search-labs/author/florian-bernd</link>
    </image>
    <link>https://www.elastic.co/es/search-labs/author/florian-bernd</link>
    <atom:link href="https://www.elastic.co/es/search-labs/rss/author/florian-bernd.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[es]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 05:13:18 GMT</lastBuildDate>
  <item>
    <title><![CDATA[LINQ a Elasticsearch ES|QL: escribir en C#, buscar en Elasticsearch]]></title>
    <description><![CDATA[Explorar el nuevo proveedor de LINQ a Elasticsearch ES|QL en el cliente .NET de Elasticsearch, que te permite escribir código en C# que se traduce automáticamente en búsquedas ES|QL.]]></description>
    <content:encoded><![CDATA[<p>A partir de <strong>v9.3.4</strong> y <strong>v8.19.18</strong>, el cliente de Elasticsearch para .NET incluye un <a href="https://learn.microsoft.com/en-us/dotnet/csharp/linq/">proveedor de Language Integrated Query (LINQ) </a>que traduce las expresiones LINQ de C# a búsquedas del <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">lenguaje de búsqueda de Elasticsearch (ES|QL)</a> en tiempo de ejecución. En lugar de escribir textos de ES|QL manualmente, compones búsquedas con <code>Where</code>, <code>Select</code>, <code>OrderBy</code>, <code>GroupBy</code> y otros operadores estándar. El proveedor se encarga de la traducción, la parametrización y la deserialización de los resultados, incluido el streaming fila por fila, lo que mantiene el uso de memoria constante independientemente del tamaño del conjunto de resultados.</p><h2>Tu primera búsqueda</h2><p>Comienza por definir un objeto CLR (POCO) simple que se mapea a tu índice de Elasticsearch. Los nombres de las propiedades se resuelven a nombres de columnas ES|QL a través de atributos <code>System.Text.Json</code> estándar, como <code>[JsonPropertyName]</code>, o a través de un <code>JsonNamingPolicy</code> configurado. Las mismas reglas de <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization">serialización de origen</a> que se aplican en el resto del cliente también se aplican aquí.</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>Con el tipo ya definido, una consulta se ve así:</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>El proveedor traduce esto al siguiente ES|QL:</p><p>Algunos detalles a tener en cuenta:</p><ul><li><p><strong>Resolución de nombres de propiedades:</strong> <code>p.Price</code> se vuelve <code>price_usd</code> debido al atributo <code>[JsonPropertyName]</code>, y <code>p.Brand</code> se convierte en <code>brand</code> siguiendo la política predeterminada de nombres camelCase.</p></li><li><p><strong>Captura de parámetros:</strong> Las variables C# <code>minPrice</code> y <code>brand</code> se capturan como parámetros nombrados (<code>?minPrice</code>, <code>?brand</code>). Se envían por separado del texto de búsqueda en la carga útil JSON, lo que previene la inyección y habilita el almacenamiento en caché del plan de búsqueda del lado del servidor.</p></li><li><p><strong>Streaming:</strong> <code>QueryAsync&lt;T&gt;</code> devuelve <code>IAsyncEnumerable&lt;T&gt;</code>. Las filas se materializan una a la vez a medida que llegan desde Elasticsearch.</p></li></ul><p>También puedes inspeccionar la búsqueda generada y sus parámetros sin ejecutarla:</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>¿Cómo funciona esto? Un repaso rápido de LINQ</h2><p>El mecanismo que hace posibles los proveedores LINQ es la distinción entre <code>IEnumerable&lt;T&gt;</code> y <code>IQueryable&lt;T&gt;</code>.</p><p>Cuando llamas a <code>.Where(p =&gt; p.Price &gt; 100)</code> en un <code>IEnumerable&lt;T&gt;</code>, la lambda se compila en un <code>Func&lt;Product, bool&gt;</code>, un delegado común que el runtime ejecuta en proceso. Esto es LINQ a objetos.</p><p>Cuando llamas al mismo método en un <code>IQueryable&lt;T&gt;</code>, el compilador de C# encapsula la expresión lambda en un <code>Expression&lt;Func&lt;Product, bool&gt;&gt;</code> en su lugar. Esta es una estructura de datos que representa la <em>estructura</em> del código en lugar de su forma ejecutable. El árbol de expresión puede inspeccionarse, analizarse y traducirse a otro idioma en tiempo de ejecución.</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>La interfaz <code>IQueryProvider</code> es el punto de extensión. Cualquier proveedor puede implementar <code>CreateQuery&lt;T&gt;</code> y <code>Execute&lt;T&gt;</code> para traducir estos árboles de expresiones a un idioma destino. Entity Framework usa esto para emitir SQL. El proveedor de LINQ a ES|QL lo usa para emitir ES|QL.</p><p>El árbol de expresión para la búsqueda anterior se ve así:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt521838e8b9c36649/6a1705b1839dfa5f40dcfdfe/f864cd18a390831f8d28503a29b5835efb1842f7-1000x720.png" alt="Árbol de expresiones para la búsqueda de ejemplo." /><p><em>Árbol de expresiones para la búsqueda de ejemplo.</em></p><p>El árbol está anidado al revés: <code>Take</code> envuelve <code>OrderByDescending</code>, que envuelve <code>Where</code>, que envuelve <code>From</code>, que envuelve la constante raíz <code>EsqlQueryable&lt;Product&gt;</code>. El predicado <code>Where</code> es en sí mismo un subárbol de <code>BinaryExpression</code> nodos para los operadores <code>&amp;&amp;</code>, <code>&gt;=</code> y <code>==</code>, con <code>MemberExpression</code> hojas para accesos a propiedades y capturas de cierre para las variables <code>minPrice</code> y <code>brand</code>. Esta es la estructura de datos que el proveedor recorre para producir el ES|QL final.</p><h2>En detalle: el pipeline de traducción</h2><p>La ruta de una expresión LINQ a los resultados de la búsqueda sigue un pipeline de seis etapas:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt930670a505dd61ea/6a1705b3b339d58a54769ecf/2a2c772b63d720f61fc9a28b2f85668fa2db8d38-1999x1036.png" alt="Visión general del pipeline de traducción." /><p><em>Visión general del pipeline de traducción.</em></p><h3>1. Captura del árbol de expresiones</h3><p>Cuando se encadenan <code>.Where()</code>, <code>.OrderBy()</code>, <code>.Take()</code> y otros operadores en un <code>IQueryable&lt;T&gt;</code>, la infraestructura LINQ estándar crea un árbol de expresiones. <code>EsqlQueryable&lt;T&gt;</code> implementa <code>IQueryable&lt;T&gt;</code> y delega a <code>EsqlQueryProvider</code>.</p><h3>2. Traducción</h3><p>Cuando se ejecuta la búsqueda (al enumerar, llamar a <code>ToList()</code> o usar <code>await foreach)</code>, <code>EsqlExpressionVisitor</code> recorre el árbol de expresiones de adentro hacia afuera. Envía cada llamada al método LINQ a un visitante especializado:</p><p>Visitante</p><p>Traduce</p><p>En</p><p>whereClauseVisitor</p><p>.Where(predicado)</p><p>Condición WHERE</p><p>SelectProjectionVisitor</p><p>.Select(selector)</p><p>EVAL + KEEP + RENAME</p><p>GroupByVisitor</p><p>.GroupBy().Select()</p><p>STATS ... BY</p><p>OrderByVisitor</p><p>.OrderBy() / .ThenBy()</p><p>Campo SORT [ASC\|DESC]</p><p>EsqlFunctionTranslator</p><p>EsqlFunctions.*, Math.*, métodos de texto</p><p>Más de 80 funciones ES|QL</p><p>Durante la traducción, las variables de C# a las que se hace referencia en las expresiones se capturan como parámetros con nombre.</p><h3>3. Modelo de búsqueda</h3><p>Los visitantes no producen textos directamente. En cambio, producen objetos <code>QueryCommand</code>, una representación intermedia inmutable. Un <code>FromCommand</code>, un <code>WhereCommand</code>, un <code>SortCommand</code> y un <code>LimitCommand</code>, cada uno representa un comando de procesamiento de ES|QL. Estos se recopilan en un modelo <code>EsqlQuery</code>.</p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt788c9936976f2f62/6a1705b50e2e4910da419ff0/2adc349b6cf655b96b7b3e826a134e8a17fe42fd-1999x1036.png" alt="Modelo de búsqueda y patrón de comandos." /><p><em>Modelo de búsqueda y patrón de comandos.</em></p><p>Este modelo intermedio está desacoplado tanto del árbol de expresiones como del formato de salida. Se puede inspeccionar, interceptar (vía <code>IEsqlQueryInterceptor</code>) o modificar antes de dar formato.</p><h3>4. Formato</h3><p><code>EsqlFormatter</code> visita cada <code>QueryCommand</code> en orden y produce el texto final de ES|QL. Cada comando se convierte en una línea, separada por el operador de barra vertical (|) que ES|QL usa para encadenar comandos de procesamiento. Los identificadores que contienen caracteres especiales se escapan automáticamente con comillas invertidas.</p><h3>5. Ejecución</h3><p>El texto ES|QL formateado y los parámetros capturados se envían al endpoint <code>/_query</code> de Elasticsearch como carga útil JSON. La interfaz <code>IEsqlQueryExecutor</code> abstrae la capa de transporte, que es donde entra en juego la arquitectura de paquetes en capas.</p><h3>6. Materialización</h3><p><code>EsqlResponseReader</code> transmite la respuesta JSON sin almacenar en memoria todo el conjunto de resultados. Un árbol <code>ColumnLayout</code>, precomputado una vez por búsqueda, mapea nombres de columnas planas de ES|QL (como <code>address.street</code>, <code>address.city</code>) a propiedades anidadas de POCO. Cada fila se ensambla en una instancia <code>T</code> y se genera una a la vez a través de <code>IEnumerable&lt;T&gt;</code> o <code>IAsyncEnumerable&lt;T&gt;</code>.</p><h2>La arquitectura en capas</h2><p>La funcionalidad de LINQ a ES|QL se divide en tres paquetes:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt662bd0dd8861b6b6/6a1705b7a929cf7086ae08a2/41b8aae860ecdc2480edcb1c1d4cc9b03cfb78c9-1999x1036.png" alt="Arquitectura de paquetes." /><p><em>Arquitectura de paquetes.</em>
<a href="https://www.nuget.org/packages/Elastic.Esql"><strong><code>Elastic.Esql</code></strong></a> es el motor puro de traducción. No tiene dependencias HTTP y contiene los visitantes de expresiones, el modelo de búsqueda, el formateador y el lector de respuestas. Puedes usarlo de forma independiente para crear e inspeccionar búsquedas de ES|QL sin una conexión de Elasticsearch, lo que es útil para pruebas, logging de búsquedas o para crear tu propia capa de ejecución.</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 un cliente ES|QL ligero e independiente. Añade ejecución HTTP sobre <code>Elastic.Esql</code> a través de <code>Elastic.Transport</code>. Si tu aplicación solo necesita ES|QL y ninguna de las otras API de Elasticsearch, esta es la opción de dependencia mínima.</p><p><a href="https://www.nuget.org/packages/Elastic.Clients.Elasticsearch"><strong><code>Elastic.Clients.Elasticsearch</code></strong></a> es el cliente completo de Elasticsearch .NET. También se basa en <code>Elastic.Esql</code> y expone al proveedor LINQ a través del espacio de nombres <code>client.Esql</code>. Este es el punto de entrada recomendado para la mayoría de las aplicaciones.</p><p>Ambos paquetes de capa de ejecución proporcionan su propia implementación de <code>IEsqlQueryExecutor</code>, la interfaz estratégica que une la traducción y el transporte.</p><p>Los tres paquetes son compatibles con Native AOT cuando se usan con un <code>JsonSerializerContext</code> generado por el código fuente. Para el cliente completo, consulta la <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization#native-aot">documentación de Native AOT</a>.</p><h2>Mas allá de los conceptos básicos</h2><p>El ejemplo anterior cubrió el filtrado, la clasificación y la paginación. El proveedor admite un conjunto más amplio de operaciones.</p><h3>Agregaciones</h3><p><code>GroupBy</code>, combinado con funciones agregadas en <code>Select</code>, se traduce a 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>Proyecciones</h3><p><code>Select</code>, con tipos anónimos genera comandos <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>, y <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>Biblioteca de funciones enriquecida</h3><p>Hay más de 80 funciones ES|QL disponibles a través de la clase <code>EsqlFunctions</code>, que abarcan fecha/hora, texto, matemáticas, IP, coincidencia de patrones y puntuación. También se traducen los métodos estándar <code>Math.*</code> y <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>LOOKUP JOIN</h3><p>Las consultas cruzadas de índices se traducen a 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>Acceso directo a ES|QL sin procesar</h3><p>Para las características de ES|QL que aún no están cubiertas por el proveedor de LINQ, puedes anexar fragmentos sin procesar:</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>Búsquedas asíncronas del lado del servidor</h3><p>Para búsquedas de ejecución prolongada, envíalas para procesamiento en segundo plano en el servidor:</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>Las búsquedas asíncronas del lado del servidor son especialmente útiles para búsquedas analíticas de larga duración/procesamiento de grandes sets de datos que pueden superar los umbrales típicos de tiempo de espera, o en entornos sensibles al tiempo de espera con balanceadores de carga, gateways API o proxies que imponen tiempos de espera HTTP estrictos. Las búsquedas asíncronas evitan las caídas de conexión al separar el envío de la solicitud de la recuperación de los resultados.</p><h2>Primeros pasos</h2><p>LINQ a ES|QL está disponible a partir de:</p><ul><li><p><strong>Elastic.Clients.Elasticsearch v9.3.4</strong> (rama 9.x)</p></li><li><p><strong>Elastic.Clients.Elasticsearch v8.19.18</strong> (rama 8.x)</p></li></ul><p>Instalar desde NuGet:</p><p><code>dotnet add package Elastic.Clients.Elasticsearch</code></p><p>Los puntos de entrada están en <code>client.Esql</code>:</p><p>Método</p><p>Devuelve</p><p>Caso de uso</p><p>Query&lt;T&gt;(...)</p><p>IEnumerable&lt;T&gt;</p><p>Ejecución sincrónica</p><p>QueryAsync&lt;T&gt;(...)</p><p>IAsyncEnumerable&lt;T&gt;</p><p>Transmisión asíncrona</p><p>CreateQuery&lt;T&gt;()</p><p>IEsqlQueryable&lt;T&gt;</p><p>Composición e inspección avanzadas</p><p>SubmitAsyncQueryAsync&lt;T&gt;(...)</p><p>EsqlAsyncQuery&lt;T&gt;</p><p>Búsquedas de larga ejecución del lado del servidor</p><p>Para consultar la referencia completa de características, incluidas las opciones de búsqueda, el acceso a múltiples campos, los objetos anidados y el manejo de campos de valores múltiples, consulta la <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/linq-to-esql">documentación de LINQ a ES|QL</a>.</p><h2>Conclusión</h2><p>LINQ a ES|QL aporta toda la expresividad de LINQ de C# al lenguaje de búsqueda ES|QL de Elasticsearch, lo que te permite realizar búsquedas con tipado fuerte y combinables sin crear manualmente cadenas de texto. Con captura automática de parámetros, materialización de streaming y una arquitectura de paquetes en capas que escala desde el paquete de traducción autónomo hasta el cliente completo de Elasticsearch, se adapta naturalmente a aplicaciones .NET de cualquier tamaño. Instala el cliente más reciente, dirige tus expresiones LINQ a un índice y deja que el proveedor se encargue del resto.</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[Base de datos vectorial]]></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[Cómo usar el Conector de Almacenamiento de Vectores de Elasticsearch para el Kernel Semántico de Microsoft para el desarrollo de agentes de IA]]></title>
    <description><![CDATA[Microsoft Semantic Kernel es un kit de desarrollo ligero y de código abierto que te permite construir agentes de IA con facilidad e integrar los últimos modelos de IA en tu base de código en C#, Python o Java. Con el lanzamiento del Conector de Almacenamiento Vectorial de Semantic Kernel Elasticsearch, los desarrolladores que emplean Semantic Kernel para construir agentes de IA pueden ahora integrar Elasticsearch como un almacén vectorial escalable de nivel empresarial, mientras continúan empleando abstracciones de Semantic Kernel.]]></description>
    <content:encoded><![CDATA[<p>En colaboración con el equipo <a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/">de Microsoft Semantic Kernel</a> , anunciamos la disponibilidad del <a href="https://github.com/elastic/semantic-kernel-net/">Semantic Kernel Elasticsearch Vector Store Connector</a>, para usuarios <a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/">de Microsoft Semantic Kernel</a> (.NET). Semantic Kernel simplifica la construcción de agentes de IA de nivel empresarial, incluyendo la capacidad de mejorar grandes modelos de lenguaje (LLMs) con respuestas más relevantes y basadas en datos desde una Vector Store. Semantic Kernel proporciona una capa de abstracción fluida para interactuar con Vector Stores como Elasticsearch, ofreciendo funciones esenciales como crear, listar y eliminar colecciones de registros, así como subir, recuperar y eliminar registros individuales.</p><p>El conector de <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">almacenamiento vectorial de Semantic Kernel Elasticsearch, ya de fábrica</a> , soporta las <a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/?pivots=programming-language-csharp#the-vector-store-abstraction">abstracciones de almacenamiento vectorial</a> Semantic Kernel, lo que facilita mucho a los desarrolladores integrar Elasticsearch como un almacén vectorial mientras crean agentes de IA.</p><p>Elasticsearch tiene una estable base en la comunidad de código abierto y recientemente adoptó la <a href="https://www.elastic.co/blog/elasticsearch-is-open-source-again">licencia AGPL</a>. Combinadas con el kernel semántico de código abierto de Microsoft, estas herramientas ofrecen una solución poderosa y preparada para compañías. Puedes empezar localmente activando Elasticsearch en unos minutos ejecutando este <code>curl -fsSL https://elastic.co/start-local | sh </code>de comandos (consulta <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html">start-local</a> para más detalles) y pasar a versiones <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;utm_source=semantickernel&amp;utm_content=documentation">alojadas en la nube</a> o <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.16/install-elasticsearch.html">autoalojadas</a> mientras produces tus agentes de IA.</p><p>En este blog analizamos cómo usar <a href="https://github.com/elastic/semantic-kernel-net/">Semantic Kernel Elasticsearch Vector Store Connector</a> al usar Semantic Kernel. En el futuro se pondrá a disposición una versión en Python del conector.</p><h2>Escenario de alto nivel: Construcción de una aplicación RAG con Kernel Semántico y Elasticsearch</h2><p>En la siguiente sección repasamos un ejemplo. A un nivel general, estamos construyendo una aplicación RAG (Recuperación Generación Aumentada) que toma la pregunta del usuario como entrada y devuelve una respuesta. Usaremos Azure OpenAI (también se puede usar <a href="https://devblogs.microsoft.com/semantic-kernel/introducing-new-ollama-connector-for-local-models/">un LLM local</a> ) como LLM, Elasticsearch como almacén vectorial y Semantic Kernel (.net) como marco para unir todos los componentes.</p><p>Si no estás familiarizado con las arquitecturas RAG, puedes hacer una breve introducción con este artículo: <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>La respuesta la genera el LLM, que se alimenta con el contexto relevante para la pregunta, recuperado de Elasticsearch vectorstore. La respuesta también incluye la fuente que el LLM empleó como contexto.</p><h3>Ejemplo RAG</h3><p>En este ejemplo concreto, construimos una aplicación que permite a los usuarios hacer preguntas sobre hoteles almacenados en una base de datos interna de hoteles. El usuario podría, por ejemplo, Busca un hotel específico, según diferentes criterios, o pide una lista de hoteles.</p><p>Para la base de datos de ejemplo, generamos una <a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">lista de hoteles</a> con 100 entradas. El tamaño de la muestra es intencionadamente pequeño para que puedas probar la demo del conector lo más fácilmente posible. En una aplicación real, el conector Elasticsearch mostraría sus beneficios sobre otras opciones, como la implementación de almacenamiento vectorial 'InMemory', especialmente cuando se trabaja con cantidades extremadamente grandes de datos.</p><p>La aplicación de demostración completa se puede encontrar en el <a href="https://github.com/elastic/semantic-kernel-net/tree/main/Elastic.SemanticKernel.Playground">repositorio</a> de conectores de almacenamiento vectorial de Elasticsearch.</p><p>Empecemos agregando los paquetes NuGet necesarios y aplicando directivas a nuestro proyecto:</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>Ahora podemos crear nuestro modelo de datos y proporcionarle atributos específicos del Núcleo Semántico para definir el esquema del modelo de almacenamiento y algunas pistas para la búsqueda de texto:</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>Los atributos del Esquema del Modelo de Almacenamiento ('VectorStore*') son los más relevantes para el uso real del Conector de Almacenamiento Vectorial Elasticsearch, a saber:</p><p></p><ul><li><p><code>VectorStoreRecordKey</code> para marcar una propiedad en una clase de registro como la clave bajo la cual el registro se almacena en un almacén vectorial.</p></li><li><p><code>VectorStoreRecordData</code> para marcar una propiedad en una clase de registro como 'datos'.</p></li><li><p><code>VectorStoreRecordVector</code> para marcar una propiedad en una clase de registro como vector.</p></li></ul><p>Todos estos atributos aceptan varios parámetros opcionales que pueden emplear para personalizar aún más el modelo de almacenamiento. En el caso de <code>VectorStoreRecordKey </code>, por ejemplo, es posible especificar una función de distancia diferente o un tipo de índice distinto.</p><p>Los atributos de búsqueda de texto (<code>TextSearch*</code>) serán importantes en el último paso de este ejemplo. Volveremos a ellos más tarde.</p><p>En el siguiente paso, inicializamos el motor Semantic Kernel y obtenemos referencias a los servicios principales. En una aplicación real, se debe usar <a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection">inyección de dependencias</a> en lugar de acceder directamente a la colección de servicios. Lo mismo se aplica a la configuración y secretos codificados en fija, que deberían leer usando un <a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration">proveedor de configuración</a> en su lugar:</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>El servicio de <code>vectorStoreCollection</code> ahora puede usar para crear la colección e incorporar algunos <a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">discos demo</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>Esto muestra cómo Semantic Kernel reduce el uso de un almacén vectorial con toda su complejidad a unas pocas llamadas a métodos simples.</p><p>En el fondo, se crea un nuevo índice en Elasticsearch y se generan todas las asignaciones de propiedades necesarias. Nuestro conjunto de datos se mapea de forma completamente transparente en el modelo de almacenamiento y finalmente se almacena en el índice. A continuación se muestra cómo se ven los mapeos en 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>El <code>embeddings.GenerateEmbeddingsAsync()</code> llama de forma transparente al servicio configurado Azure AI Embeddings Generation.</p><p>Se puede observar aún más magia en el último paso de esta demo.</p><p>Con una sola llamada a <code>InvokePromptAsync</code>, se realizan todas las siguientes operaciones cuando el usuario hace una pregunta sobre los datos:</p><p>1. Se genera una incrustación para la pregunta del usuario</p><p>2. Se busca en el almacén vectorial las entradas relevantes</p><p>3. Los resultados de la consulta se insertan en una plantilla de prompt</p><p>4. La consulta real en forma de prompt final se envía al servicio de finalización de chat con IA</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>¿Recuerdas los atributos <code>TextSearch*</code> que definimos previamente en nuestro modelo de datos? Estos atributos nos permiten usar marcadores de posición correspondientes en nuestra plantilla de prompts, que se llenan automáticamente con la información de nuestras entradas en el almacén vectorial.</p><p>La respuesta final a nuestra pregunta "Por favor, muéstrame todos los hoteles que tengan un bar en la azotea" es la siguiente:</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>La respuesta correcta se refiere a la siguiente entrada de nuestro 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>Este ejemplo muestra muy bien cómo el uso de Microsoft Semantic Kernel logra una reducción significativa de la complejidad gracias a sus abstracciones bien pensadas, además de permitir un nivel muy alto de flexibilidad. Por ejemplo, al cambiar una sola línea de código, el almacén vectorial o los servicios de IA empleados pueden ser reemplazados sin necesidad de refactorizar ninguna otra parte del código.</p><p>Al mismo tiempo, el framework proporciona un enorme conjunto de funcionalidades de alto nivel, como la función 'InvokePrompt', o el sistema de plantillas o plugins de búsqueda.</p><p>La aplicación de demostración completa se puede encontrar en el repositorio de conectores de almacenamiento vectorial de Elasticsearch.</p><h2>¿Qué más es posible con Elasticsearch?</h2><ul><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Mapeado de nuevas semantic_text de Elasticsearch: Simplificación de la búsqueda semántica</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-reranking-with-retrievers">Reclasificación semántica en Elasticsearch con los retrievers</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1">Técnicas avanzadas de RAG parte 1: Procesamiento de datos</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">Técnicas avanzadas de RAG parte 2: Consultas y pruebas</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-rag-with-llama3-opensource-and-elastic">Construyendo RAG con Llama 3 de código abierto y Elastic</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/local-rag-agent-elasticsearch-langgraph-llama3">Un tutorial para construir agentes locales usando LangGraph, LLaMA3 y almacenamiento vectorial de Elasticsearch desde cero</a></p></li></ul><h2>Elasticsearch &amp; Semantic Kernel: ¿Qué sigue?</h2><ul><li><p>Mostramos cómo la tienda vectorial de Elasticsearch puede conectarse fácilmente a Semantic Kernel mientras se construyen aplicaciones GenAI en .NET. Estad atentos para la próxima integración de Python.</p></li><li><p>A medida que Semantic Kernel construye abstracciones para funciones avanzadas de búsqueda como <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">la búsqueda híbrida</a>, el Elasticsearch Connect permitirá a los desarrolladores .NET implementarlas fácilmente usando 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[Base de datos vectorial]]></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[Evolución del cliente Elasticsearch .NET: De NEST a Elas&shy;tic.Cli&shy;ents.Elastic&shy;search]]></title>
    <description><![CDATA[Descubre la evolución del cliente .NET de Elasticsearch y la transición de NEST a Elastic.Clients.Elasticsearch.]]></description>
    <content:encoded><![CDATA[<h2>Introducción al cliente .NET y NEST</h2><p>En el mundo .NET, la integración con Elasticsearch fue facilitada durante mucho tiempo por la biblioteca <code>NEST</code> , que sirve como una interfaz robusta para que los desarrolladores interactúen con las poderosas capacidades de búsqueda y análisis de Elasticsearch. <code>NEST</code>, nacida de la necesidad de un cliente .NET nativo para Elasticsearch, ganó rápidamente popularidad entre los desarrolladores por su amplio conjunto de funciones y sus integraciones fluidas.</p><p>Durante casi <a href="https://github.com/elastic/elasticsearch-net/commit/724f932ba598915c8c3c35a19827fdfa4f782c1d">14</a> años y solo 8 meses después <a href="https://github.com/elastic/elasticsearch/commit/ec72ca8b7a115f9b2eea3c76c518062b99a1d015">del primer commit de Elasticsearch</a> , NEST estuvo siguiendo fielmente las versiones de Elasticsearch.</p><h2>Transición de NEST a Elastic.Clients.Elasticsearch</h2><p>A medida que Elasticsearch evolucionó, mantener la compleja base de código de <code>NEST</code>se volvió cada vez más difícil. Reconocimos la necesidad de un enfoque más sostenible en el desarrollo de clientes y emprendimos un camino para rediseñar el cliente .NET desde cero. Nos llevó casi un año lanzar la primera versión beta y otro año más acercarnos a dar soporte a todos y cada uno de los endpoints de servidor. Una de las decisiones más difíciles fue reducir el alcance de la biblioteca para priorizar la mantenibilidad.</p><p>Dado el tamaño actual de la superficie de la API de Elasticsearch, ya no es práctico mantener manualmente más de 450 endpoints y casi 3000 tipos (solicitudes, respuestas, consultas, agregaciones, etc.). Para cerciorar una alineación consistente, precisa y oportuna entre los clientes del lenguaje y Elasticsearch, los clientes 8.x y muchos de los tipos asociados ahora se generan automáticamente por código a partir de una <a href="https://github.com/elastic/elasticsearch-specification">especificación compartida</a>. Esta es una solución común para mantener la alineación entre cliente y servidor entre SDKs y librerías, como los de Azure, AWS y la Google Cloud Platform.</p><p>La especificación Elasticsearch se creó hace más de 8 años exportando los mapeos de tipos de <code>NEST</code> y, gracias al arduo trabajo del equipo de clientes, ahora podemos usar la misma especificación para crear un nuevo cliente .NET (y clientes para varios otros lenguajes como Java, Go, etc.).</p><p>Con el lanzamiento de la versión 8.13, se anunció oficialmente la desaparición de <code>NEST</code> . A medida que Elasticsearch se transforma en <code>Elastic.Clients.Elasticsearch</code>, <code>NEST</code> irá desapareciendo gradualmente, alcanzando su fin de vida útil al final del año. Se anima encarecidamente a los promotores a iniciar los esfuerzos de migración con antelación para garantizar una transición fluida y mitigar posibles interrupciones. Adoptar <code>Elastic.Clients.Elasticsearch</code> no solo garantiza compatibilidad con las últimas funciones del servidor, sino que también protege a futuro las aplicaciones contra funcionalidades obsoletas.</p><h2>Elastic.Clients.Elasticsearch: resumen de características y cambios</h2><p>Cambiar al cliente v8 <code>Elastic.Clients.Elasticsearch</code> permite acceder a todas las nuevas funciones de Elasticsearch 8 y también aporta numerosas modernizaciones a la propia biblioteca, pero implica una reducción en las características de comodidad en comparación con su predecesor. Algunas de las nuevas características principales incluyen el lenguaje de consultas <code>ES|QL</code>, capacidades modernas de aprendizaje automático (ML) y diagnósticos mejorados en forma de actividades compatibles con OpenTelemetry. A partir de la versión 8.13, <code>Elastic.Clients.Elasticsearch</code> soporta casi todas las funciones de servidor de Elasticsearch 8.</p><p>Un cambio importante de ruptura, por ejemplo, está relacionado con las agregaciones. En <code>NEST</code>, el uso fluido de la API es así:</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>mientras que el cliente v8 requiere la siguiente sintaxis:</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>Migración de NEST v7 a .NET client v8</h2><p>Aquí está disponible una guía completa de migración: <a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/8.18/migration-guide.html">Guía de migración: De NEST v7 a .NET Client v8</a>.</p><h2>Recursos adicionales</h2><ul><li><p><a href="https://github.com/elastic/elasticsearch-net">Cliente Elastic.Clients.Elasticsearch v8 en GitHub</a></p></li><li><p><a href="https://www.nuget.org/packages/Elastic.Clients.Elasticsearch">Cliente Elastic.Clients.Elasticsearch v8 en NuGet</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>