Blog

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

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.

En colaboración con el equipo de Microsoft Semantic Kernel , anunciamos la disponibilidad del Semantic Kernel Elasticsearch Vector Store Connector, para usuarios de Microsoft Semantic Kernel (.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.

El conector de almacenamiento vectorial de Semantic Kernel Elasticsearch, ya de fábrica , soporta las abstracciones de almacenamiento vectorial Semantic Kernel, lo que facilita mucho a los desarrolladores integrar Elasticsearch como un almacén vectorial mientras crean agentes de IA.

Elasticsearch tiene una estable base en la comunidad de código abierto y recientemente adoptó la licencia AGPL. 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 curl -fsSL https://elastic.co/start-local | sh de comandos (consulta start-local para más detalles) y pasar a versiones alojadas en la nube o autoalojadas mientras produces tus agentes de IA.

En este blog analizamos cómo usar Semantic Kernel Elasticsearch Vector Store Connector al usar Semantic Kernel. En el futuro se pondrá a disposición una versión en Python del conector.

Escenario de alto nivel: Construcción de una aplicación RAG con Kernel Semántico y Elasticsearch

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 un LLM local ) como LLM, Elasticsearch como almacén vectorial y Semantic Kernel (.net) como marco para unir todos los componentes.

Si no estás familiarizado con las arquitecturas RAG, puedes hacer una breve introducción con este artículo: https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag.

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.

Ejemplo RAG

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.

Para la base de datos de ejemplo, generamos una lista de hoteles 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.

La aplicación de demostración completa se puede encontrar en el repositorio de conectores de almacenamiento vectorial de Elasticsearch.

Empecemos agregando los paquetes NuGet necesarios y aplicando directivas a nuestro proyecto:

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.0
using 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;

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:

/// <summary>
/// Data model for storing a "hotel" with a name, a description, a  description embedding and an optional reference link.
/// </summary>
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<float>? DescriptionEmbedding { get; set; }

	[TextSearchResultLink]
	[VectorStoreRecordData]
	public string? ReferenceLink { get; set; }
}

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:

  • VectorStoreRecordKey para marcar una propiedad en una clase de registro como la clave bajo la cual el registro se almacena en un almacén vectorial.

  • VectorStoreRecordData para marcar una propiedad en una clase de registro como 'datos'.

  • VectorStoreRecordVector para marcar una propiedad en una clase de registro como vector.

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 VectorStoreRecordKey , por ejemplo, es posible especificar una función de distancia diferente o un tipo de índice distinto.

Los atributos de búsqueda de texto (TextSearch*) serán importantes en el último paso de este ejemplo. Volveremos a ellos más tarde.

En el siguiente paso, inicializamos el motor Semantic Kernel y obtenemos referencias a los servicios principales. En una aplicación real, se debe usar inyección de dependencias 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 proveedor de configuración en su lugar:

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<Hotel>();

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

kernelBuilder.AddElasticsearchVectorStoreRecordCollection<string, Hotel>("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<Kernel>()!;
var embeddings = host.Services.GetService<ITextEmbeddingGenerationService>()!;
var vectorStoreCollection = host.Services.GetService<IVectorStoreRecordCollection<string, Hotel>>()!;

// Register search plugin.
var textSearch = host.Services.GetService<VectorStoreTextSearch<Hotel>>()!;
kernel.Plugins.Add(textSearch.CreateWithGetTextSearchResults("SearchPlugin"));

El servicio de vectorStoreCollection ahora puede usar para crear la colección e incorporar algunos discos demo:

await vectorStoreCollection.CreateCollectionIfNotExistsAsync();

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

foreach (var chunk in hotels.Chunk(25))
{
    var descriptionEmbeddings = await embeddings.GenerateEmbeddingsAsync(chunk.Select(x => x[2]).ToArray());
    
    for (var i = 0; i < 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]
        });
    }
}

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.

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.

{
  "mappings": {
    "properties": {
      "descriptionEmbedding": {
        "dims": 1536,
        "index": true,
        "index_options": {
          "type": "hnsw"
        },
        "similarity": "cosine",
        "type": "dense_vector"
      },
      "hotelName": {
        "type": "keyword"
      },
      "description": {
        "type": "text"
      }
    }
  }
}

El embeddings.GenerateEmbeddingsAsync() llama de forma transparente al servicio configurado Azure AI Embeddings Generation.

Se puede observar aún más magia en el último paso de esta demo.

Con una sola llamada a InvokePromptAsync, se realizan todas las siguientes operaciones cuando el usuario hace una pregunta sobre los datos:

1. Se genera una incrustación para la pregunta del usuario

2. Se busca en el almacén vectorial las entradas relevantes

3. Los resultados de la consulta se insertan en una plantilla de prompt

4. La consulta real en forma de prompt final se envía al servicio de finalización de chat con IA

// 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());

¿Recuerdas los atributos TextSearch* 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.

La respuesta final a nuestra pregunta "Por favor, muéstrame todos los hoteles que tengan un bar en la azotea" es la siguiente:

Console.WriteLine(response.ToString());

// > The hotel that has a rooftop bar is Skyline Suites. You can find more information about this hotel [here](https://example.com/yz567).

La respuesta correcta se refiere a la siguiente entrada de nuestro hotels.csv

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

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.

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.

La aplicación de demostración completa se puede encontrar en el repositorio de conectores de almacenamiento vectorial de Elasticsearch.

¿Qué más es posible con Elasticsearch?

Elasticsearch & Semantic Kernel: ¿Qué sigue?

  • 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.

  • A medida que Semantic Kernel construye abstracciones para funciones avanzadas de búsqueda como la búsqueda híbrida, el Elasticsearch Connect permitirá a los desarrolladores .NET implementarlas fácilmente usando Semantic Kernel.

Preguntas frecuentes

¿Qué es el Kernel Semántico de Microsoft?

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.

Contenido relacionado

Técnicas avanzadas de RAG parte 2: Consultas y pruebas

Han Xiang Choong

Resolución de entidades con Elasticsearch, parte 4: el desafío final

Jessica Moszkowicz

Automatización del análisis de logs en Streams con ML.

Nastia Havriushenko

Construir un agente de IA para RRHH con Elastic Agent Builder y GPT-OSS

Tomás Murúa

Técnicas avanzadas de RAG parte 1: Procesamiento de datos

Han Xiang Choong

¿Estás listo para crear experiencias de búsqueda de última generación?

No se logra una búsqueda suficientemente avanzada con los esfuerzos de uno. Elasticsearch está impulsado por científicos de datos, operaciones de ML, ingenieros y muchos más que son tan apasionados por la búsqueda como tú. Conectemos y trabajemos juntos para crear la experiencia mágica de búsqueda que te dará los resultados que deseas.

Pruébalo tú mismo