<?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[Base de datos vectorial - 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[Base de datos vectorial - 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/blog/category/vector-database</link>
    </image>
    <link>https://www.elastic.co/es/search-labs/blog/category/vector-database</link>
    <atom:link href="https://www.elastic.co/es/search-labs/rss/category/vector-database.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[es]]></language>
    <lastBuildDate>Tue, 29 Sep 2026 04:12:49 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Base de datos vectorial de Elasticsearch: envía en minutos, escala de manera accesible a cientos de miles de millones]]></title>
    <description><![CDATA[Las partes difíciles de la recuperación híbrida, ya resueltas, con valores predeterminados optimizados, modelos de terceros y nativos de Jina AI e inferencia de GPU gestionada, todo listo para usar. Crea apps de IA rápidas y escalables en lugar de infraestructura.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch es una de las plataformas más ampliamente desplegadas para cargas de trabajo vectoriales en el mundo, potenciando la búsqueda semántica, retrieval augmented generation (RAG) y recomendaciones para empresas como GitHub, Docusign, Seismic y muchas otras. Hoy anunciamos Elasticsearch Vector Database, una nueva oferta sin servidor optimizada para aplicaciones basadas en vectores. Traes tus documentos y tus búsquedas, y nosotros nos encargamos de las incrustaciones y el ajuste del índice, junto con la infraestructura. Además, lo mantenemos barato y escalable. </p><p>Para los nuevos usuarios, esta es la forma más rápida de poner en marcha la búsqueda de vectores de alta calidad. Si ya usas Elasticsearch, la nueva oferta es la búsqueda de vectores en la plataforma donde ya residen tus datos, sin necesidad de adoptar un sistema nuevo. Elasticsearch Vector Database admite una variedad de situaciones, desde fundamentación para un modelo de lenguaje grande (LLM) hasta dotar a un agente de IA de recuperación y memoria, y prestar servicio a cientos de miles de millones de vectores. <a href="https://cloud.elastic.co/registration?onboarding_token=vector">Crea un nuevo proyecto</a> y comienza en minutos.</p><h2>Un motor, todos los casos de uso vectoriales</h2><p>La base de datos vectorial de Elasticsearch está diseñada para cualquier persona que desarrolle aplicaciones con vectores:</p><ul><li><p><strong>RAG:</strong> recupera el contexto adecuado para tu LLM con la recuperación de vectores densos y dispersos, o elige la búsqueda híbrida combinando la recuperación vectorial y léxica. La calidad de tu generación mejora con la calidad de tu recuperación.</p></li><li><p><strong>Agentes de IA:</strong> brinda a los agentes una recuperación rápida y filtrada de documentos y memoria de conversación, con las bajas latencias que demandan los bucles de agentes de varios pasos.</p></li><li><p><strong>Búsqueda semántica:</strong> busca coincidencias según el significado, no por palabras clave, con un tipo de campo y cero código de pipeline.</p></li><li><p><strong>Recomendaciones y similitud:</strong> encuentra los vecinos más cercanos en productos, imágenes o cualquier contenido que tengas, a escala.</p></li></ul><h2>Todo lo que tu carga de trabajo vectorial necesita, optimizado y listo para usar</h2><p>Crear una aplicación basada en vectores implica conectar varias piezas independientes: configurar y alojar modelos de incrustación, indexar tus documentos a través de ellos, almacenar los vectores de forma eficiente, aplicar el modelo de incrustación a cada búsqueda, comparar con el almacenamiento vectorial y, por último, recuperar los documentos detrás de las coincidencias. La base de datos vectorial de Elasticsearch se encarga de todo por ti, sin configuración ni ajustes adicionales.</p><h3>Indexación de vectores con el modo de índice vectordb_document</h3><p>El modo de índice <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-vectordb-document-mode">vectordb_document</a>, una nueva configuración de índice diseñada especialmente para cargas de trabajo centradas en vectores, está activado de forma predeterminada, por lo que obtienes la configuración que elegirían los expertos. Esto es lo que activa:</p><ul><li><p><strong>bfloat16 de forma predeterminada:</strong> Los vectores se almacenan con la mitad del tamaño de float32 y un impacto insignificante en la recuperación, lo que reduce aproximadamente a la mitad el espacio en disco incluso antes de que entre en juego la cuantización.</p></li><li><p><strong>Vectores de origen excluidos:</strong> en Elasticsearch, tus incrustaciones ya se encuentran en las estructuras de índice que se usan para la búsqueda; mantener una segunda copia sin procesar in _source solo infla el almacenamiento y ralentiza la recuperación de resultados. Excluimos el duplicado para que las respuestas se devuelvan más rápido y almacenes menos.</p></li><li><p><strong>Los archivos adecuados precargados en caché:</strong> las estructuras de datos a las que primero acceden las búsquedas vectoriales se cargan en la memoria con anticipación, para que tu primera (y tu milésima) búsqueda sea ultrarrápida.</p></li><li><p><strong>Combinación paralela:</strong> La combinación consolida los segmentos en estructuras vectoriales mejor organizadas, lo que mejora tanto la recuperación como la latencia, y ejecutar esas combinaciones en varios hilos permite lograrlo más rápido.</p></li></ul><h3>Almacenamiento de vectores, compresión y ajuste automático</h3><ul><li><p>Tus vectores se comprimen automáticamente.<a href="https://www.elastic.co/es/search-labs/blog/better-binary-quantization-lucene-elasticsearch"> Better Binary Quantization (BBQ)</a> reduce la huella de memoria de los vectores hasta 32 veces mientras conserva la recuperación, y DiskBBQ reduce aún más los requisitos de memoria para cargas de trabajo a gran escala.<a href="https://www.elastic.co/es/search-labs/blog/vector-quantization-auto-calibration-diskbbq"> </a></p></li><li><p>Opta por la<a href="https://www.elastic.co/es/search-labs/blog/vector-quantization-auto-calibration-diskbbq"> autocalibración</a>, que ajusta la cuantización de cada segmento a tus datos y la vuelve a calibrar en cada fusión a medida que los datos varían. Al probarse en 18 sets de datos, las búsquedas por segundo (QPS) mejoraron un promedio de 16.7 %, con mejoras en la recuperación en la mayoría de ellos.</p></li></ul><h3>Incrustaciones en inferencia por GPU administrada</h3><ul><li><p>Genera incrustaciones con <a href="https://www.elastic.co/es/jina-search-models">modelos nativos de incrustación y reclasificación de Jina AI</a>, o incorpora modelos de terceros, todo en GPU administradas a través de <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service (EIS)</a> sin tener que operar servidores de modelos. O autohospeda, si prefieres el tuyo propio.</p></li><li><p>El tipo de campo <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"><strong>semantic_text</strong></a> maneja automáticamente la fragmentación y la generación de incrustaciones, junto con las consultas, el camino más simple hacia la búsqueda semántica en el mercado. </p></li></ul><h3>Búsqueda híbrida y búsqueda vectorial filtrada</h3><ul><li><p>La <a href="https://www.elastic.co/es/elasticsearch/hybrid-search">búsqueda híbrida</a> viene integrada, combinando la recuperación de texto completo y vectorial en una sola búsqueda. Combina los resultados con la fusión de rango recíproco (RRF) o cualquier otro mecanismo de combinación que quieras. La búsqueda vectorial suele ser la parte de la búsqueda híbrida más difícil de configurar bien. Con la base de datos vectorial de Elasticsearch, lo tienes resuelto y toda tu pila híbrida mejora. </p></li><li><p>Con la <a href="https://www.elastic.co/es/search-labs/blog/filtered-hnsw-knn-search">búsqueda de vectores filtrada</a>, aplica filtros de metadatos como parte de la propia recuperación de vectores y no como una idea de último momento que arruina la exhaustividad.</p></li></ul><h3>Empresarial desde el primer día</h3><p>También obtienes control de acceso basado en roles (RBAC), logs de auditoría y las certificaciones de cumplimiento de las que generalmente carecen las bases de datos vectoriales dedicadas.</p><h2>Accesible a escala y predecible</h2><p>La base de datos vectorial de Elasticsearch está diseñada para seguir siendo accesible a medida que creces: la compresión BBQ y DiskBBQ que mantiene el almacenamiento lineal y la memoria baja significa que el escalado a cientos de miles de millones de vectores no disparará tu factura. Y lo que pagas se calcula a partir de números que ya conoces: cuántos datos almacenas y cuántos indexas, junto con cuánta capacidad de búsqueda necesitas. Estima tu cantidad de documentos y dimensiones de vectores, además de tu carga de búsqueda, y podrás calcular lo que pagarás antes de crear el proyecto. También puedes entender todos los conceptos de tu factura al final del mes. No hay unidades de cómputo opacas ni cargos sorpresa por operaciones en segundo plano.</p><h2>Cómo comenzar con la base de datos vectorial de Elasticsearch</h2><h3>Crea un proyecto serverless de base de datos vectorial</h3><p>Crea un nuevo <a href="https://cloud.elastic.co/registration?onboarding_token=vector">proyecto sin servidor de base de datos vectorial en Elastic Cloud</a>. Apunta los datos al endpoint y todo estará listo para indexar.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt556cbdfba551f248/6aa10b4332b53038406d321a/image1.png" alt="Elastic Cloud Serverless project types: Elasticsearch, Vector Database, Observability and Security" /><h3>Crea un índice usando semantic_text</h3><p>El modo de índice de vectores gestiona la configuración de vectores. Usar semantic_text significa que la configuración de las incrustaciones y la fragmentación se gestiona por ti, al igual que la configuración del índice, en inferencia de GPU gestionada, sin necesidad de crear un pipeline de incrustaciones.</p>PUT my-vectors
{
"mappings": {
"properties": {
"description": { "type": "semantic_text" }
    }
  }
}<h3>Ingestar documentos</h3><p>Indexa texto y las incrustaciones se generan para ti.</p>POST /my-vectors/_doc
{
  "id": "park_rocky-mountain",
  "title": "Rocky Mountain",
  "description": "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra."
}<h3>Ejecuta una consulta de búsqueda semántica</h3><p>Consulta el mismo campo semántico que acabas de crear:</p>GET /my-vectors/_search
{
  "query": {
    "semantic": {
      "field": "description",
      "query": "a mountain range in the middle of north america"
    }
  }
}<p>Y recibes los resultados:</p>{
  "took": 80,
  "hits": {
    "max_score": 0.7792325,
    "hits": [
      {
        "_index": "my-vectors",
        "_score": 0.7792325,
        "_source": {
          "id": "park_rocky-mountain",
          "title": "Rocky Mountain",
          "description": "Bisected north to south by the Continental Divide, ..."
        }
      }
    ]
  }
}<p>La búsqueda semántica es solo el comienzo. Ejecuta búsquedas totalmente textuales o combina ambas en búsquedas híbridas. Incluso puedes crear tus propias búsquedas de vectores para tener control total. Sigue la <a href="https://www.elastic.co/docs/solutions/vector-database/vector-full-text-search">guía de inicio rápido de búsqueda semántica</a> en la documentación para obtener todas las instrucciones.</p><h2>Qué sigue para la búsqueda de vectores en Elasticsearch</h2><p>Ya estamos trabajando en las próximas mejoras:</p><ul><li><p><strong>Mejor manejo de varios inquilinos:</strong> si tus datos deben mantenerse separados por inquilino, te daremos una forma de hacerlo más rápido y con menos código.</p></li><li><p><strong>Optimización automática del índice:</strong> de "índice completamente nuevo" a "totalmente optimizado", con los menores ajustes posibles.</p></li><li><p><strong>Mejoras continuas de la infraestructura:</strong> Ajuste continuo de la configuración y la infraestructura de la base de datos vectorial para obtener siempre el mejor rendimiento y las respuestas más rápidas.</p></li></ul><h2>Prueba la base de datos vectorial de Elasticsearch en Elastic Cloud Serverless</h2><p>Pasa de un proyecto vacío a una búsqueda vectorial híbrida y filtrada en minutos, con valores predeterminados de nivel de producción que se encargan del ajuste por ti. Crea apps de IA rápidas y escalables en lugar de infraestructura.</p><p>Comienza con <a href="https://cloud.elastic.co/registration?onboarding_token=vector">Elastic Cloud Serverless</a> o explora la <a href="https://www.elastic.co/docs/solutions/vector-database">documentación completa </a>y la <a href="https://www.elastic.co/docs/api/doc/elastic-cloud-serverless/group/endpoint-vectordb-projects">referencia de la API.</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-database-rag-serverless</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-database-rag-serverless</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[Elastic Cloud Serverless]]></category>
    <category><![CDATA[Búsqueda híbrida]]></category>
    <dc:creator><![CDATA[Dustin Coates]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4def84aae6aff861/6aa10ab1ee57e53d9b05253c/cover.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Cómo medir y mejorar la recuperación de búsqueda de Elasticsearch: de 0,43 a 0,75 con búsqueda híbrida]]></title>
    <description><![CDATA[Aprende a medir y mejorar la recuperación de búsqueda en Elasticsearch combinando la búsqueda léxica BM25 con incrustaciones vectoriales de Jina AI, usando la API rank_eval para validar la mejora con cifras reales.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/docs/solutions/search/full-text">La búsqueda léxica</a> mediante el <a href="https://www.elastic.co/blog/practical-bm25-part-1-how-shards-affect-relevance-scoring-in-elasticsearch">algoritmo de clasificación BM25</a> es económica, rápida y muy eficaz para una amplia gama de consultas. Sin embargo, tiene un punto ciego: las consultas que no comparten tokens con tus documentos. En este artículo, mencionaremos con precisión en qué aspectos se queda corto el BM25. Emplearemos la <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval">API de evaluación de clasificación</a> (<code>rank_eval</code>) de Elasticsearch y cerraremos esa brecha agregando <a href="https://www.elastic.co/search-labs/es/blog/jina-embeddings-v3-elastic-inference-service">incrustaciones de Jina AI</a> mediante <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service</a> (EIS). Verás cómo la puntuación de recuperación pasa de <code>0.43</code> a <code>0.75</code> y entenderás por qué.</p><h2>¿Qué es la recuperación?</h2><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-recall">La recuperación</a> mide en una escala de <code>0</code> a <code>1</code> cuántos de los documentos que realmente quieren tus usuarios aparecen en algún lugar de los resultados de búsqueda. Si una consulta debe mostrar tres productos y la búsqueda solo arroja dos de ellos en los 10 principales, <code>recall@10 = 0.67</code> para esa consulta. Es una métrica basada en conjuntos: no le importa la posición de los documentos relevantes dentro de esos <em>k</em> resultados. Un documento relevante en la posición 10 cuenta igual que uno en la posición 1. Tener una recuperación alta significa que no estás perdiendo resultados relevantes.</p><p>
</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ffd147b13705680/6a170a6fe8fbce11a539fc22/b13af2a5d0ca055535d8bfe3dfe4b3d1093ee6da-1457x796.png" alt="Diagrama de Venn que ilustra cómo se calcula el Recall@10, mostrando la superposición entre todos los documentos relevantes y los 10 primeros resultados recuperados por BM25, lo que da como resultado una puntuación de Recall@10 de 0.40." /><p>El diagrama muestra dos conjuntos: todos los documentos relevantes (izquierda) y lo que BM25 realmente recuperó (10 principales, derecha). Solo la intersección cuenta para la recuperación, se encontraron <code>prod_1</code> y <code>prod_2</code>, mientras que <code>prod_3</code>, <code>prod_4</code> y <code>prod_6</code> se perdieron por completo. Resultado: <code>Recall@10 = 2/5 = </code><strong><code>0.40</code></strong>.</p><h2>Requisitos previos</h2><p>Pongamos manos a la obra para comprender mejor cómo funciona la recuperación. Esta demostración utiliza Python. Puedes seguirlo en el cuaderno complementario (<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/relevance-tuning-improving-recall-adding-vectors/notebook.ipynb">notebook.ipynb</a>), donde cada bloque de código es una celda lista para ejecutarse.</p><p>El código proporcionado utiliza lo siguiente:</p><ul><li><p>Elasticsearch 9.3+</p></li><li><p>Python 3.10+</p></li></ul>pip install elasticsearch pandas plotly python-dotenv<ul><li><p>Un archivo <code>.env</code> con tus credenciales de Elasticsearch</p></li></ul>ELASTICSEARCH_URL=https://your-cluster-url
ELASTICSEARCH_API_KEY=your-api-key<h2>El set de datos</h2><p>Emplearemos un catálogo con 1000 productos, que abarcan categorías como calzado, electrónica, herramientas y más.</p><p>Cada documento tiene cuatro campos:</p><p>Campo</p><p>Tipo</p><p>`título`</p><p>texto</p><p>'descripción'</p><p>texto</p><p>`marca`</p><p>palabra clave</p><p>'categoría'</p><p>palabra clave</p><p>El set de datos se carga desde <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/relevance-tuning-improving-recall-adding-vectors/dataset.csv"><code>dataset.csv</code></a>.</p><h2>El poder y los límites de la búsqueda léxica</h2><p>BM25 es el algoritmo de clasificación predeterminado en Elasticsearch y en la mayoría de los motores de búsqueda. Califica los documentos según la frecuencia con la que aparecen tus términos de consulta en ellos, ajustado por la longitud del documento y la frecuencia de esos términos en todo el índice. Los <a href="https://www.elastic.co/docs/reference/text-analysis/analyzer-reference">analizadores</a> aparecen en la parte superior: normalización de minúsculas, derivación y eliminación de palabras vacías. Una búsqueda de "zapatillas para correr" coincidirá con "Zapatillas para correr" y probablemente también con "correr".</p><p>Esto funciona bien para una amplia clase de consultas:</p><ul><li><p>"zapatillas de correr" muestra de inmediato los productos que contienen exactamente esas palabras en el título.</p></li><li><p>"parlante con bluetooth" muestra productos de audio portátiles porque los tokens aparecen textualmente.</p></li></ul><p>Los resultados son deterministas y explicables: un documento se clasifica alto porque los términos de la consulta aparecen en él. Depurar la relevancia es sencillo.</p><h3>Cuándo falla</h3><p>Ahora probemos estas búsquedas en el mismo catálogo:</p><ul><li><p><strong>"rutina de skincare":</strong> la palabra "rutina" no aparece en ningún título de producto. BM25 puede hacer una coincidencia parcial con "skincare", pero los sueros faciales, aceites corporales y humectantes se describen utilizando términos como "vitamina C", "retinol" o "aclarante", ninguno de los cuales se superpone con la consulta. Los productos que forman una rutina completa de skincare están dispersos en el índice sin ningún token compartido para anclarlos.</p></li></ul>ID: B06XX6DS3P, Score: 9.0552, Title: Replenix Retinol Smooth + Tighten Body Lotion - Collagen-Boosting, Regenerating Anti-Aging Body Cream, Reduces Appearance of Stretch Marks, 6.7 oz.

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

  ID: B01CY764KQ, Score: 5.0057, Title: Nike Up Or Down Men Deodorant - Pack of 2 | Long-Lasting Fragrance, Body Spray Combo for Men | Deodorant for Active Living | Nike Men's Deo Set | Ultimate Odor Protection | Grooming Essentials | Signature Nike Scent | High-Performance Men's Deodorant<ul><li><p><strong>"accesorios de viaje para mascotas":</strong> esta es una agrupación de casos de uso, no una categoría de producto. Un transportín para perros, un asiento para mascotas en el automóvil y una jaula de viaje son todos relevantes, pero sus descripciones hablan sobre portabilidad, seguridad y comodidad en vez de "accesorios de viaje". BM25 coincide con "mascota" de manera amplia, pero no tiene ninguna señal para distinguir los productos específicos de viaje del resto del catálogo de mascotas.</p></li></ul>ID: B0BVV7BKTW, Score: 7.4371, Title: Large Foldable Travel Duffel Bag with Shoes Compartment

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

ID: B08R8FRW53, Score: 6.6335, Title: CUBY Dog and Cat Sling Carrier
ID: B08QMCQYGM, Score: 6.5259, Title: YTFGGY Whiteboard Pinstripe Tape 6 Rolls 1/8"
ID: B0CP3LQSWM, Score: 6.2994, Title: Portable Dog Water Bottle 32 Oz<p>Este es un <strong>problema de recuperación</strong>. Los documentos relevantes están en tu índice. BM25 simplemente no puede encontrarlos porque las palabras del usuario y las del documento no coinciden lo suficiente.</p><p>Agregar sinónimos es útil en casos conocidos. Pero no se puede enumerar todas las formas en que un usuario podría expresar una intención. Ahí es donde entran los vectores.</p><h2>Por qué deberías medir el recall</h2><p>Antes de solucionar un problema, necesitas cuantificarlo.</p><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-recall"><strong>Recall@k</strong></a> mide cuántos de los documentos que realmente buscan tus usuarios aparecen en algún lugar de los resultados de búsqueda. Es decir:</p>Recall@k = (relevant documents found in top k) / (total relevant documents)<p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-precision"><strong>Precision@k</strong></a> mide los mejores k resultados y cuántos son realmente relevantes:</p>Precision@k = (relevant documents in top k) / k<p>La alta precisión significa que los resultados que se devuelven son buenos. En el comercio electrónico, no mostrar un producto relevante (baja recuperación) suele ser peor que mostrar un resultado ligeramente imperfecto (menor precisión), porque un producto oculto es una venta perdida.</p><p>La API <code>rank_eval</code> de Elasticsearch te permite medir ambos de forma sistemática. Proporcionas una lista de consultas, cada una con un conjunto de documentos calificados, y Elasticsearch calcula las métricas por ti en todas las consultas.</p><h2>Configuración de la evaluación</h2><p>La API <code>rank_eval</code> necesita un <strong>set de datos de calificaciones</strong>: un mapeo de consultas a los documentos que son relevantes para cada uno, junto con un grado de relevancia (0 = no relevante, 1 = relevante, 2 = altamente relevante).</p><p>En el cuaderno, esta es la <a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr#learning-to-rank-judgement-list">lista de evaluaciones</a>:</p>judgments = [
    # Query 1: "running shoes" BM25 handles well (tokens appear in product titles) 
    {"query_id": "q1", "doc_id": "B09NQJFRW6", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B08JMD4LMM", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B08VRJ6F2Q", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B07S8NRRWR", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B01HD620I8", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B07DX86321", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B0968YVLQ8", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B093QJ39ZS", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B096FGSC39", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B01GVQWVV2", "grade": 1, "query": "running shoes"},

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

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

    # Query 4: "pet travel accessories" use-case grouping, products are carriers/crates/seats
    {"query_id": "q4", "doc_id": "B08R8FRW53", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B01MYUYX33", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B003C5RKE4", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B09GF8GBF6", "grade": 1, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B0CP3LQSWM", "grade": 1, "query": "pet travel accessories"},
]<p>La combinación es intencionada: <code>q1</code> es una consulta que BM25 maneja bien (tokens exactos en los títulos de productos), mientras que <code>q2</code>, <code>q3</code> y <code>q4</code> son consultas basadas en la intención donde la intención del usuario se expresa como un concepto en lugar de palabras clave específicas de producto.</p><h2>Medición de la recuperación basal de BM25</h2><p>Primero, configura el cliente de Elasticsearch e indexa los datos de texto sin procesar:</p>import os
import json
import pandas as pd
import plotly.graph_objects as go
from elasticsearch import Elasticsearch, helpers
from dotenv import load_dotenv

load_dotenv()

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

INDEX_NAME = "ecommerce-products"<p>Ahora crea la solicitud <code>rank_eval</code> para BM25. Cada solicitud en la lista combina una consulta con sus calificaciones:</p>judgments_df = pd.DataFrame(judgments)

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

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

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

bm25_result = es.rank_eval(index=INDEX_NAME, body=bm25_eval)
print("BM25 Recall@10:", bm25_result.body["metric_score"])<p>Resultado:</p>BM25 Recall@10: 0.43<p><code>0.43</code> significa que en las cuatro consultas, BM25 encuentra solo el 43 % de los documentos que debería encontrar. La deficiencia se concentra en las consultas basadas en la intención: "rutina de skincare" no encuentra sueros faciales ni aceites corporales porque la palabra "rutina" nunca aparece en los títulos de los productos, y "accesorios de viaje para mascotas" recupera productos para mascotas que no tienen relación con el tema, mientras que no encuentra transportines ni jaulas descritos en términos de portabilidad y seguridad en vez de "accesorios de viaje".</p><p>Esta es nuestra referencia base. Ahora tenemos un número que superar.</p><h2>Agregar búsqueda vectorial con embeddings de Jina</h2><p><a href="https://www.elastic.co/docs/solutions/search/vector"><code>Vector search</code></a> Codifica documentos y consultas como vectores de alta dimensión, un tipo de vector compuesto por cientos o miles de valores numéricos, cada uno de los cuales codifica una característica específica de los datos que representa. Los documentos con significado similar terminan cerca unos de otros en el espacio vectorial, incluso si no comparten palabras. "Equipo de gimnasio" y "conjunto de mancuernas" estarán cerca porque los conceptos están relacionados. Elegí Elasticsearch como mi base de datos vectorial porque admite la búsqueda híbrida, lo que me brinda comprensión semántica y precisión de palabras clave de inmediato.</p><p><a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">EIS</a> incluye soporte integrado para la incorporación de modelos a través de su <a href="https://www.elastic.co/docs/api/doc/elasticsearch/group/endpoint-inference">API de inferencia</a>.</p><h3>Paso 1: usar las incrustaciones de Jina v5 como endpoint de inferencia</h3>INFERENCE_ENDPOINT_ID = ".jina-embeddings-v5-text-small"<p>Si tu clúster tiene recursos de GPU (disponibles en Elastic Cloud y Elasticsearch 9.3+), las incrustaciones se generan en GPU, lo cual es mucho más rápido que la inferencia de CPU y elimina el compromiso de rendimiento que históricamente hacía que los vectores fueran caros a gran escala.</p><p>¿Por qué las incrustaciones de Jina en particular? <a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text">jina-embeddings-v5-text</a> es un modelo multilingüe (más de 119 idiomas) con una ventana de contexto de 32 000 tokens y soporte para <a href="https://arxiv.org/abs/2106.09685">adaptadores de Adaptación de Rango Bajo (LoRA)</a> específicos para tareas. Funciona bien para descripciones cortas de productos que están listas para usar. Lee más sobre el modelo <code>jina-embeddings-v5-text</code> <a href="https://huggingface.co/jinaai/jina-embeddings-v5-text-small">aquí</a>.</p><h3>Paso 2: Crear el índice con un campo semántico</h3>index_mappings = {
    "mappings": {
        "properties": {
            "title": {"type": "text", "copy_to": "semantic_field"},
            "description": {"type": "text", "copy_to": "semantic_field"},
            "brand": {"type": "keyword"},
            "category": {"type": "keyword"},
            "semantic_field": {
                "type": "semantic_text",
                "inference_id": INFERENCE_ENDPOINT_ID,
            },
        }
    }
}

if not es.indices.exists(index=INDEX_NAME):
    es.indices.create(index=INDEX_NAME, body=index_mappings)
    print(f"Created index: {INDEX_NAME}")<p>El tipo de campo <a href="https://www.elastic.co/docs/solutions/search/semantic-search/semantic-search-semantic-text"><code>semantic_text</code></a> es la clave aquí. Es una abstracción de mayor nivel sobre <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector"><code>dense_vector</code></a>: la apuntas a un endpoint de inferencia, y Elasticsearch se encarga de generar incrustaciones de forma automática.</p><p>La propiedad <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/copy-to"><code>copy_to</code></a> en <code>title</code> y <code>description</code> significa que el contenido de ambos campos fluye hacia <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"><code>semantic_field</code></a> para su incrustación, por lo que un solo vector captura la representación completa del producto.</p><h3>Paso 3: Indexar los productos</h3>def bulk_index(products, index_name):
    actions = []
    for product in products:
        doc_id = product.get("_id")
        source = {k: v for k, v in product.items() if k != "_id"}
        action = {"_index": index_name, "_source": source}
        if doc_id:
            action["_id"] = doc_id
        actions.append(action)

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

bulk_index(products, INDEX_NAME)<p>En tiempo de indexación, Elasticsearch llama al endpoint de inferencia para cada documento y almacena la incrustación resultante en <code>semantic_field</code>. No necesitas agregar ningún código adicional.</p><h2>Búsqueda híbrida: combinación de BM25 y vectores con RRF</h2><p>Agregar vectores mejora la recuperación, pero usar solo vectores corre el riesgo de perder precisión en las consultas de coincidencia exacta; "zapatillas para correr" aún debe clasificar primero las coincidencias literales. La búsqueda híbrida retiene el componente léxico específicamente para preservar esa precisión.</p><p>La búsqueda híbrida con <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">Reciprocal Rank Fusion</a> (RRF) combina lo mejor de ambos:</p><ul><li><p>BM25 se ocupa de consultas exactas y casi exactas con alta precisión.</p></li><li><p>La búsqueda semántica gestiona consultas basadas en la intención y multilingües con alto nivel de recuperación.</p></li><li><p>RRF combina las dos listas clasificadas en una sola clasificación.</p></li></ul><p>La fórmula RRF asigna a cada documento una puntuación basada en su clasificación en cada lista de resultados:</p>score = sum(1 / (rank_constant + rank))<p>Un documento que se clasifica en una posición alta en ambas listas obtiene una puntuación combinada más alta. El <code>rank_constant</code> controla cuánto peso reciben los documentos de menor rango.</p>hybrid_requests = []

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

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

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

hybrid_result = es.rank_eval(index=INDEX_NAME, body=hybrid_eval)
print("Hybrid Recall@10:", hybrid_result.body["metric_score"])<p>Resultado:</p>Hybrid Recall@10: 0.75<p>La búsqueda híbrida mejora sustancialmente sobre BM25 (<code>0.43</code>) y preserva la precisión para consultas de coincidencia exacta como "zapatillas para correr".</p><h2>Resultados: Antes y después</h2><p>Aquí está la comparación completa entre los tres enfoques:</p>methods = {
    "BM25 (Lexical)": bm25_requests,
    "Hybrid (BM25 + Vectors)": hybrid_requests,
}

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

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

comparison_df = pd.DataFrame(comparison_data)
print(comparison_df.to_string(index=False))<p>Resultado:</p><p>Método</p><p>Recall@10</p><p>BM25 (Lexical)</p><p>0,43</p><p>Híbrido (BM25 + Vectores)</p><p>0,75</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5a1d72b57056fe64/6a170a71c1e8a56c58f882ab/e49f6c10516b0a48a0ad75962c6590ee07311407-700x500.png" alt="Gráfico de barras que compara Recall@10 entre la búsqueda léxica BM25 y la búsqueda híbrida que combina BM25 con vectores, lo que muestra que la búsqueda híbrida logra una recuperación mucho mayor." /><p>Desglose por consulta:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt871347f754c866d0/6a170a73839dfa40abdcfeb4/40e36dcb7b34cbf4649c512bcb60cef60f1778a6-700x500.png" alt="Gráfico de barras agrupado que compara Recall@10 entre la búsqueda léxica BM25 y la búsqueda híbrida en cuatro consultas de productos, mostrando que la búsqueda híbrida supera con constancia a la búsqueda léxica en cada consulta." /><h2>Conclusión</h2><p>A lo largo de esta publicación, vimos que la búsqueda léxica BM25 es confiable cuando los usuarios teclean consultas exactas, pero pierde recuperación cuando buscan por intención en lugar de palabras clave. Usando <code>rank_eval</code>, establecimos una línea de base reproducible para medir esa brecha con números reales. A partir de ahí, agregamos un <code>semantic_text</code> campo impulsado por incrustaciones de Jina y volvimos a ejecutar la evaluación. El resultado: la búsqueda híbrida mejoró la recuperación de <code>0.43</code> a <code>0.75</code> a la vez que conservó la precisión en las consultas de coincidencia exacta, aunque el margen real dependerá de tu combinación de consultas.</p><p>El patrón escala más allá de este ejemplo: recopila juicios de las consultas reales de tus usuarios, ejecuta <code>rank_eval</code> como línea de base, agrega <code>semantic_text</code> y vuelve a medir. Sabrás exactamente qué mejoró y en qué medida.</p><h2>Pasos siguientes</h2><ul><li><p>Aprende más sobre la recuperación y la búsqueda de vectores: <a href="https://www.elastic.co/search-labs/blog/recall-vector-search-quantization">Recuperación y cuantización de la búsqueda de vectores</a> por Jeff Vestal</p></li><li><p>Añade reclasificación para una precisión aún mejor en los resultados principales</p></li><li><p>Explora la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">documentación de búsqueda híbrida de Elasticsearch</a></p></li><li><p>Lee más sobre la<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-rank-eval.html"> API rank_eval</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-relevance-tuning-improve-recall</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-relevance-tuning-improve-recall</guid>
    <category><![CDATA[Búsqueda híbrida]]></category>
    <category><![CDATA[Base de datos vectorial]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt37c9d2971b5a2db3/6a170a75cf4f254223b2d149/492c9b5432a2b9e40cebb3b60f0df019a8c7bf6d-1280x720.png" length="0" type="image/png"/>
    <pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Cómo creamos Elasticsearch simdvec para hacer una de las búsquedas vectoriales más rápidas del mundo]]></title>
    <description><![CDATA[Cómo construimos Elasticsearch SIMDvec, la biblioteca del kernel SIMD ajustada a mano detrás de cada consulta de búsqueda vectorial en Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch simdvec es el motor detrás de cada cálculo de distancia vectorial en Elasticsearch. Proporciona kerneles AVX-512 y NEON ajustados manualmente para cada tipo de vector que admite Elasticsearch. Su arquitectura de scoring masivo oculta la latencia de memoria mediante la precarga de datos explícita en x86 y las cargas intercaladas en ARM, por lo que muestra un rendimiento hasta 4 veces mayor que bibliotecas como FAISS y jvector cuando los datos exceden la caché de la CPU. En esta publicación, explicamos por qué lo creamos, qué contiene y cómo hace que la búsqueda vectorial de Elasticsearch sea una de las más rápidas del mundo.</p><h2>Cómo construimos Elasticsearch SIMDvec</h2><p>Cada consulta de búsqueda vectorial en Elasticsearch, ya sea un recorrido de <a href="https://arxiv.org/abs/1603.09320">mundo pequeño jerárquico navegable (HNSW)</a>, un escaneo de archivo invertido (IVF) o una pasada de reclasificación, se reduce al mismo problema: calcular distancias entre vectores, millones de veces por búsqueda. Elasticsearch admite una amplia gama de tipos de datos y estrategias de cuantificación, desde float32 hasta int8, bfloat16, binario y Mejor cuantificación binaria (BBQ). Cada una tiene distintas compensaciones entre memoria, rendimiento y recuperación. Detrás de todo esto hay un único motor: simdvec.</p><p>Creamos simdvec para que cada cálculo de distancia sea tan rápido como el hardware lo permita. En esta publicación, explicamos por qué lo creamos, qué contiene y dónde produce el mayor impacto.</p><h3>Creado como un auto de carreras</h3><p>Como aficionados a la Fórmula 1, y dado que uno de nosotros trabajó anteriormente con el equipo Ferrari de Fórmula 1, vemos un claro paralelismo. Un auto de Fórmula 1 está diseñado con un solo propósito: lograr el mejor tiempo de vuelta. La potencia del motor, la aerodinámica y el diseño del chasis solo importan en la medida en que contribuyen a ese resultado. Lo mismo ocurre con una base de datos vectorial, donde el rendimiento de indexación, la latencia de búsqueda y la recuperación definen el éxito.</p><p>Aunque lo que importa es el resultado final, para alcanzar los máximos niveles de rendimiento es necesario que cada componente funcione a la perfección. No puede ser solo <em>lo suficientemente bueno</em>, tiene que ser <em>el mejor </em>en su categoría. Simdvec está construido con esa mentalidad, que se enfoca en una parte fundamental del sistema: el motor. Es una biblioteca de kernel optimizada para <a href="https://en.wikipedia.org/wiki/Single_instruction,_multiple_data">una sola instrucción y múltiples datos</a> (SIMD) diseñada específicamente, que proporciona funciones nativas de distancia en C++ ajustadas a mano, llamadas desde Java a través de la interfaz para funciones externas (FFI) de <a href="https://openjdk.org/projects/panama/">Panamá</a>. Admite evaluación masiva, precarga de líneas de caché y todos los tipos de vectores y diseños utilizados en Elasticsearch.</p><p>Ese es el motor que hay detrás de cada búsqueda.</p><h3>Por qué creamos nuestro propio motor</h3><p>Comenzamos en 2023 con la API Panama Vector en Apache Lucene. Funcionaba bien para productos punto float32, pero las necesidades de Elasticsearch superaron rápidamente lo que podía ofrecer. Elasticsearch admite una amplia gama de tipos de vectores cuantificados: int8, int4, bfloat16, de un solo bit y BBQ asimétrico. Cada uno tiene diferentes estrategias SIMD, diseños de empaquetado y requisitos de acumulador. Más allá de la cobertura de tipos, las rutas de puntuación de Elasticsearch exigen algo más que un rendimiento de un solo par: HNSW necesita puntuar varios vecinos del grafo en una sola pasada, IVF necesita puntuar en masa miles de candidatos con precarga y la puntuación basada en disco debe funcionar directamente en la memoria mapeada con mmap sin necesidad de copiar. Examinamos lo que estaba disponible y nada abarcaba el conjunto completo.</p><p>Así que construimos simdvec: kerneles C++ nativos ajustados a mano llamados desde Java a través de FFI, con puntuación masiva, precarga y soporte para cada tipo de vector que usa Elasticsearch. Al ser propietarios de la biblioteca, controlamos el stack completo. Cuando añadimos un nuevo tipo de cuantificación, como BBQ, se le asigna un kernel SIMD optimizado que se integra en todo el sistema. No esperamos a que una biblioteca upstream le de soporte y no comprometemos el rendimiento para ningún tipo. Cada consulta vectorial en Elasticsearch, ya sea HNSW, IVF, reranking o híbrida, se ejecuta en este motor, construido en torno a las operaciones y los tipos que realmente usamos.</p><p>Simdvec tiene bibliotecas nativas separadas para x86 y ARM, cada una con múltiples niveles de arquitectura de conjunto de instrucciones (ISA) seleccionados al inicio. La sobrecarga de llamadas desde Java vía FFI es muy baja, con <a href="https://github.com/ldematte/simsimd-benchmarks/blob/main/COMPARISON.md#ffm-downcall-overhead-measurements">nanosegundos de un solo dígito</a>.</p><h3>El panorama</h3><p>No somos los únicos que construimos kerneles de distancia vectorial optimizados para SIMD. El ecosistema es rico y queríamos comprender cómo se desempeña simdvec. No para clasificar proyectos, sino para proporcionar contexto y explicar dónde se encuentra el motor de Elasticsearch. Seleccionamos tres proyectos como puntos de referencia, cada uno representando un enfoque diferente:</p><ul><li><p><strong>jvector:</strong> Una biblioteca de Java de vecino más cercano aproximado (ANN) que emplea la API Panama Vector para el cálculo vectorizado de distancias, con aceleración nativa en C opcional en x86.</p></li><li><p><strong>FAISS:</strong> Un marco de trabajo de búsqueda vectorial de open source ampliamente desplegado, con kerneles AVX2/AVX-512 ajustados a mano.</p></li><li><p><strong>NumKong</strong> (anteriormente SimSIMD): un conjunto completo de más de 2 000 kerneles SIMD ajustados a mano que abarcan funciones de distancia, operaciones matrices y computación geoespacial.</p></li></ul><p>Cada proyecto cumple un propósito diferente y realiza diferentes concesiones. Incluimos números de referencia de ellos para dar contexto al rendimiento de simdvec en las operaciones específicas que Elasticsearch necesita.</p><h3>Cómo medimos</h3><p>Las evaluaciones de simdvec y <a href="https://github.com/ChrisHegarty/jvector-kernel-benchmarks">jvector</a> están escritos en Java con JMH, la microevaluación estándar de JVM, lo que incluye la sobrecarga de FFI. Para <a href="https://github.com/ldematte/simsimd-benchmarks">las evaluaciones de NumKong</a> y <a href="https://github.com/ChrisHegarty/faiss-kernel-benchmarks">las evaluaciones de FAISS</a>, escribimos pequeños marcos de prueba de C/C++ con Google Benchmark, que es el marco de trabajo estándar para microevaluaciones de C++. Ambos marcos de trabajo reportan nanosegundos por operación con calibración de calentamiento e iteración. Verificamos mediante contadores de rendimiento de hardware que todas las bibliotecas usan SIMD en ambas plataformas. Todo el código de evaluación está disponible públicamente en los repositorios enlazados de GitHub (y, en el caso de simdvec, en el repositorio <a href="https://github.com/elastic/elasticsearch">elasticsearch</a>).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt90a7d66553be3c9d/6a1701d166c4f942caf8bea5/aee116772df161cf86b7668f575ac34c733a23c5-1580x238.png" alt="Tabla en la que se enumeran dos plataformas: x86 con AMD EPYC Turin (Zen 5), AVX2 y AVX-512, AWS c8a.4xlarge; y ARM con Graviton 4 (Neoverse V2), NEON y SVE2, AWS c8g.4xlarge." /><p><strong>Software:</strong> JDK 25.0.2, JMH 1.37, GCC 14, Google Benchmark (última versión).</p><h2>Un vector a la vez</h2><p>La operación más básica en la búsqueda vectorial es calcular la distancia entre dos vectores. Cada evaluación de un vecino de HNSW, cada puntaje de un candidato a IVF, cada comparación de reclasificación se reduce a este bucle interno.</p><p>Medimos el rendimiento de un solo par en 1024 dimensiones en ambas plataformas, empezando por float32, el tipo base y el donde el ecosistema es más competitivo. Comparamos simdvec con FAISS y jvector; hemos excluido NumKong porque utiliza acumuladores float64 para float32, lo que lo hace entre 3,2 y 5,3 veces más lento (dependiendo de la plataforma), ya que prioriza la precisión numérica por encima del rendimiento. Para mantener la comparación de igual a igual, evaluamos NumKong en int8 en su lugar, donde utiliza la misma estrategia de acumulador que simdvec.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte3bc2ecaf3ad2b61/6a1701d2ab7f08039ddb9d0f/352cfa0fc18f123140f746d843404f80127fb1b7-1500x675.png" alt="Gráfico de barras horizontal titulado “Float32 Dot Product — AMD Turin” que compara cinco implementaciones: FAISS AVX‑512 a 23,2 ns/op, ES simdvec AVX‑512 a 28,3 ns/op, FAISS AVX2 a 36,4 ns/op, ES simdvec AVX2 a 38,9 ns/op y jvector a 43,9 ns/op." /><p>En la arquitectura x86, FAISS AVX-512 es el kernel de par único más rápido, con un tiempo de ejecución de 23 ns. A continuación, se ejecuta Simdvec AVX-512 a los 28 ns, un intervalo que refleja la sobrecarga de la llamada FFI. Ambos emplean FMA de 512 bits con desenrollado de múltiples acumuladores. A nivel AVX2, los dos valores son mucho más similares, 36 ns y 39 ns respectivamente, ambos limitados por el ancho de carga de memoria y registro de 256 bits. jvector tarda 44 ns usando la API de Java Panama Vector. Panamá genera buen código SIMD, pero las funciones intrínsecas de C++ ajustadas manualmente siguen teniendo ventaja.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcbbbe442631a575d/6a1701d42b835f81bcf4b083/95e44d9767f21ec4bccaed835e3c99aa180431ee-1500x495.png" alt="Gráfico de barras horizontal llamado “float32 Dot Product — Graviton 4 (ARM)” que muestra ES simdvec en 70.2 ns/op, jvector en 110.0 ns/op y FAISS en 155.6 ns/op." /><p>En ARM, simdvec lidera a 70 ns, muy por delante de jvector a 110 ns y FAISS a 156 ns. Simdvec tiene kernels NEON ajustados a mano para aarch64. Jvector no tiene código ARM nativo y depende de Panama. FAISS se basa en la auto-vectorización del compilador en lugar de en las intrínsecas NEON explícitas, lo que explica la mayor brecha. Esto refleja una ventaja práctica de poseer la biblioteca del kernel: cuando Elasticsearch se expandió a Graviton, agregamos kerneles NEON especialmente diseñados. Ni jvector ni FAISS priorizaron el código nativo de ARM en la misma medida.</p><p>Sin embargo, Elasticsearch no solo puntúa float32. La cuantificación <strong>Int8</strong> reduce la memoria en 4x, bfloat16 en 2x y BBQ en 32x. Cada tipo necesita su propia estrategia SIMD, y simdvec proporciona kernels nativos ajustados a mano para todos ellos.</p><p>De todas las bibliotecas que comparamos, solo NumKong tiene kernels comparables para int8. Medimos el producto escalar int8, la distancia euclidiana al cuadrado y el coseno en 1024 dimensiones.</p><p><strong>Puntuación de par único Int8 (1024 dimensiones, ns/vec op — cuanto más bajo, mejor)</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ac255d158e93504/6a1701d5a292997e48d00eb1/a0b852fd5f51d57bd2488886600472ee65ab64da-1594x378.png" alt="Tabla que compara el rendimiento de x86 y ARM en el producto punto, distancia euclidiana al cuadrado y similitud del coseno; muestra los valores de ES, NumKong y diff para cada operación en ambas arquitecturas." /><p>En ambas arquitecturas, NumKong es igual o más rápido en dimensiones pequeñas y medias, donde la diferencia se debe en gran parte a una menor sobrecarga de llamadas (llamada C directa vs FFI en Java). En dimensiones mayores, simdvec se pone al día, donde la implementación más eficiente del kernel (que emplea desenrollamiento en cascada) amortiza el costo de llamada: a medida que aumenta la dimensión, <a href="https://github.com/ldematte/simsimd-benchmarks/blob/main/COMPARISON.md#single-pair-i8-nsop-2">esta brecha se cierra y finalmente se revierte</a>. El cruce está en dimensiones entre 768 y 1536, dependiendo de la función y la arquitectura.</p><p>A pesar de la sobrecarga ligeramente mayor de Java FFI, simdvec está a la par con las bibliotecas altamente optimizadas de C/C++. No solo es la única librería con kernels optimizados tanto para float32 <em>como para </em> int8, sino que también lidera en ARM y solo está ligeramente por detrás de FAISS en x86 (para float32), y muy cerca de NumKong en ambas arquitecturas (para int8). Y en el caso de bfloat16, int4, binary y BBQ, aunque existen alternativas, simdvec se distingue por su SIMD ajustado manualmente y adaptado a la estructura de datos de cada tipo.</p><p>Pero un motor de búsqueda en producción no califica un vector a la vez; califica miles por consulta. La siguiente pregunta es qué sucede a esa escala.</p><h3>Miles a la vez</h3><p>El rendimiento de un solo par es solo una parte del panorama. Lo que importa en la práctica es cómo se comportan los sistemas bajo carga. Una sola consulta HNSW puede puntuar cientos de vecinos de grafos. Un escaneo de IVF puede puntuar miles de entradas en la lista de publicaciones. Un paso de reclasificación puede puntuar decenas de miles de candidatos. El rendimiento por par individual es importante, pero lo que más importa es la rapidez con la que puedes puntuar muchos vectores, y cómo se degrada el rendimiento de forma gradual a medida que el conjunto de trabajo se desborda de las cachés de la CPU.</p><p>Simdvec proporciona evaluación masiva para todos los tipos de datos. No son solo bucles sobre kernels de un solo par; usan bucles internos de varios acumuladores que cargan el vector de búsqueda una vez por paso de dimensión y lo comparten entre varios vectores de documentos, con precarga explícita de líneas de caché para el siguiente batch. Ni jvector ni FAISS ofrecen un equivalente (en el momento de redacción de esta publicación). Jvector no tiene una API de bulk, así que quien llama puntúa un par a la vez en un bucle. FAISS expone <code>fvec_inner_products_ny</code> que, en el momento de redacción de esta publicación, está implementado como un bucle sobre su función de distancia de un solo par, sin amortización de la búsqueda ni precarga.</p><p><strong>Float32.</strong> Para medir el impacto a nivel del kernel, puntuamos una sola consulta contra un número creciente de vectores de documentos float32 de 1024 dimensiones con patrones de acceso aleatorio que simulan búsquedas de vecinos de grafos dispersos similares a HNSW. Los tres tamaños de sets de datos, 32, 625 y 32 500 vectores, se eligen para que el conjunto de trabajo supere la caché L1, L2 y L3, respectivamente.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2df64ec77a1921ae/6a1701d714b270393ae3c4de/1d90267be1c63ac82b8ba588617ebedb0be0d1b6-1334x558.png" alt="Dos gráficos de barras que comparan los tiempos de puntuaciones en producto escalar de Float32 en masa para Elasticsearch simdvec, FAISS y jvector en AMD Turin (x86, AVX-512) y Graviton 4 (ARM, NEON) en tres tamaños: 32 vectores, 625 vectores y 32 500 vectores." /><p>Cuando los datos caben en la caché, simdvec es el más rápido en ambas plataformas, pero las diferencias son modestas, ya que predomina la aritmética del kernel. La separación real se nota cuando el conjunto de trabajo supera el tamaño de L3. En x86, simdvec alcanza los 95 ns por vector, mientras que FAISS necesita 165 ns y jvector 412 ns. En ARM, la tendencia es la misma: simdvec se mantiene en 162 ns, mientras que FAISS sube a 347 ns y jvector a 476 ns. La precarga y la amortización de búsquedas en simdvec mantienen la latencia de memoria oculta de una manera que un bucle simple sobre kerneles de par único no puede coincidir y el beneficio se amplía precisamente donde operan las cargas de búsqueda reales, en lo profundo de la memoria principal.</p><p><strong>Int8.</strong> Lo mismo ocurre con los tipos cuantificados. Medimos el rendimiento del cálculo del producto escalar int8 en 1024 dimensiones con sets de datos de un tamaño tal que superaran los límites de las cachés L1, L2 y L3, en comparación con el cálculo en bloque de simdvec con el cálculo de pares individuales de NumKong en un bucle.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcda85e10007188d0/6a1701d9cf4f25d722b2cff7/9ee97d98b40d13b19370b76b33e67ea66bfb3250-1580x338.png" alt=" Tabla llamada &quot;x86 — puntuación masiva, producto punto int8 (ns/op, menor es mejor)&quot; que compara ES simdvec y NumKong en tres tamaños de vector: 128, 2 500 y 130 000, con los valores correspondientes de ns/op y las proporciones de aceleración." /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt972c311b7d08ba73/6a1701dadc55dec02ee00c87/601f50a03fa3e6263cbac9700281dfe3e511de60-1580x338.png" alt="Tabla llamada “ARM — Puntuación masiva, producto punto int8 (ns/op, menor es mejor)” comparación de ES simdvec y NumKong a través de tres tamaños de vector—128, 2 500 y 130 000—con los valores correspondientes de ns/op y las ratios de aceleración" /><p>En x86, simdvec es entre 1,2 y 1,9 veces más rápido, impulsado por la combinación de precarga explícita y procesamiento por lotes. En ARM, simdvec gana de nuevo (1,7 a 1,9 veces más rápido) en todos los tamaños de sets de datos. La ventaja radica en el procesamiento por lotes de cuatro vectores a la vez, lo que proporciona paralelismo a nivel de memoria mediante un patrón de acceso intercalado. En ambos casos, el resultado más llamativo es lo que ocurre en el mayor tamaño de set de datos, donde más importa.</p><p>Los resultados para la distancia al cuadrado y el coseno muestran un patrón similar, con aceleraciones de 1,4 a 1,8 veces para ARM, y de 1,3 a 3,0 veces para x86 (detalles <a href="https://github.com/ldematte/simsimd-benchmarks/blob/main/COMPARISON.md">aquí</a>).</p><h3>Cuando la memoria importa</h3><p>Los índices de vectores de producción normalmente no caben en la caché de la CPU. Un índice de 10M-vectores int8 a 1024 dimensiones es de 10 GB. La evaluación de candidatos implica el procesamiento en flujo continuo de datos desde la DRAM y ahí es donde la arquitectura de evaluación masiva marca la diferencia.</p><p>Usamos contadores de rendimiento de hardware para medir lo que sucede dentro de la CPU durante la puntuación masiva y descubrimos que ocultar la latencia de la memoria requiere dos estrategias fundamentalmente diferentes, una por arquitectura.</p><p><strong>En x86, la precarga explícita elimina los fallos de caché. </strong>El kernel masivo procesa los vectores secuencialmente, uno completamente calculado antes del siguiente, mientras emite instrucciones de precarga para el siguiente batch. Los datos futuros se cargan en la L1 antes de que la CPU los necesite.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt368f29b5143d0f5a/6a1701dcc1e8a51780f8817b/a39548f8060c2a4a5154521a4047dd92d8cd77be-1580x309.png" alt="Tabla titulada «x86 (AMD Turín) — contadores de hardware por operación int8» comparando modos individuales y masivos para fallos de caché L1, IPC y fallos de dTLB, con los factores de mejora correspondientes." /><p>En ARM, el mismo enfoque secuencial tuvo un rendimiento deficiente, incluso con precarga. En cambio, <strong>el kernel masivo entrelaza las cargas</strong> de cuatro vectores en cada posición de zancada, lo que proporciona al motor fuera de orden cuatro flujos de memoria independientes. La CPU no recoge datos más rápido, sino que espera menos al tener siempre algo más que calcular mientras las solicitudes de memoria están en curso. Puedes encontrar un análisis detallado en <a href="https://github.com/elastic/elasticsearch/issues/145412">este ticket de GitHub</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8751ac5b20b75508/6a1701deab7f08db83db9d13/832de3bc0d556a493b7bf3f250196018acfc1585-1580x238.png" alt="Tabla llamada “ARM (Graviton 4): contadores de hardware por operación int8” que compara los modos individual y masivo para los errores de caché L1 y los bloqueos de backend, con las notas de mejora correspondientes" /><p>Los números cuentan dos historias diferentes:</p><ol><li><p>En x86, la precarga convierte 139K fallos de caché en 19K y las instrucciones por ciclo (IPC) se duplican más de dos veces. La ventaja de masa crece con el tamaño de los sets de datos, de 1,2 veces en L2 a 2,8 veces más allá de L3, porque la precarga oculta progresivamente viajes de ida y vuelta de DRAM más costosos.</p></li><li><p>En ARM, los fallos de caché apenas cambian. Lo que cambia es la utilización: las interrupciones del backend disminuyen un 40 % porque el patrón de acceso intercalado mantiene el pipeline alimentado. Esta ventaja se mantiene en un consistente 1,8 veces independientemente del tamaño del sets de datos, porque el paralelismo a nivel de memoria se aplica tanto si los datos provienen de la caché como de la DRAM.</p></li></ol><p>Dos arquitecturas, dos estrategias y un resultado: a escala de producción, simdvec mantiene la pipeline de la CPU ocupada incluso cuando los vectores están dispersos por la memoria principal.</p><h2>Qué significa esto para los usuarios de Elasticsearch</h2><p>Estas capacidades a nivel de kernel se potencian entre sí. Una única consulta de búsqueda vectorial puede calcular millones de operaciones de distancia: recorrido del grafo HNSW, puntuación de candidatos, reclasificación. En miles de búsquedas concurrentes, los nanosegundos por operación se traducen directamente en latencia de búsqueda y rendimiento del cluster. Tanto si usas float32, int8, bfloat16 o BBQ, tanto si tu índice está en la memoria como en el disco, simdvec es el motor subyacente, y cada una de esas operaciones pasa por el mismo motor, lo que optimiza hasta el último nanosegundo.</p><p>La conclusión clave es que, a escala de producción, el rendimiento de la búsqueda vectorial no está determinado principalmente por el rendimiento bruto de SIMD. Lo que marca la diferencia es la eficiencia con la que el sistema oculta la latencia de la memoria mientras mantiene el rendimiento computacional en millones de operaciones pequeñas.</p><p>Los kerneles de simdvec mejoran prácticamente con cada versión de Elasticsearch. Cuando surgen nuevos tipos de cuantización y plataformas de hardware, reciben kerneles ajustados desde el primer día. Y los tipos existentes siguen ganando velocidad a medida que perfeccionamos las implementaciones que ya están disponibles.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-vector-search-simdvec-engine</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-vector-search-simdvec-engine</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[Dentro de Elastic]]></category>
    <dc:creator><![CDATA[Chris Hegarty,Lorenzo Dematte,Simon Cooper]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta18088409621369b/6a1701dfdc55de7297e00c8b/df9646091bafbbf0a6dfd212ff8a6bd1e8589708-1280x720.png" length="0" type="image/png"/>
    <pubDate>Thu, 23 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Agrupación no supervisada de documentos con Elasticsearch + incrustaciones de Jina]]></title>
    <description><![CDATA[Un enfoque práctico y reproducible para la agrupación no supervisada de documentos con Elasticsearch y embeddings de Jina.]]></description>
    <content:encoded><![CDATA[<p>La búsqueda vectorial empieza con una consulta, pero ¿qué pasa si no tienes una?</p><p>Las organizaciones acumulan grandes colecciones de documentos, como tickets de soporte, presentaciones legales, feeds de noticias y trabajos de investigación, pero para poder hacer las preguntas correctas, primero necesitan entender lo que contienen. Sin etiquetas o datos de entrenamiento, revisar manualmente miles de documentos es poco práctico. La búsqueda tradicional no es útil cuando no sabes qué buscar.</p><p>Esta publicación describe un enfoque nativo de Elasticsearch para la agrupación no supervisada de documentos y el seguimiento temporal de temas que aborda este problema de descubrimiento. Después de leerla, podrás seguir la evolución de distintos temas a lo largo de varios días:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta8c243e0c5773440/6a17093fa6c2b98c86e7968c/100a60a7fb85da8ab3813fd071a82c93f2c3f318-1300x650.png" alt="Cadenas de temas temporales que evolucionan a lo largo de febrero de 2025: cada ruta coloreada es un tema que persiste a lo largo de los días, con un ancho de enlace que muestra la fuerza de superposición de kNN" /><p><strong>Lo que descubrirás:</strong></p><ul><li><p>Por qué <strong>las incrustaciones de agrupación</strong> (y no las incrustaciones de recuperación) son fundamentales para descubrir temas sin una consulta.</p></li><li><p>Cómo la clasificación de centroides con sonda de densidad agrupa documentos por tema usando Elasticsearch k vecinos más cercanos (kNN) y lotes de <code>msearch</code>.</p></li><li><p>¿Cómo puede <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significanttext-aggregation"><code>significant_text</code></a> autoetiquetar clústeres para que los temas sean legibles sin entrenar un modelo?</p></li><li><p>De qué forma las cadenas temporales de temas conectan los clústeres de datos diarios para mostrar la evolución de los temas día a día.</p></li></ul><p>El pipeline utiliza unos 8500 artículos de febrero de 2025 de BBC News y The Guardian como corpus de prueba. Si bien las noticias son convenientes porque tienen un comportamiento temporal claro, el patrón es aplicable si el descubrimiento de documentos es importante: revisión legal, monitoreo del cumplimiento normativo, síntesis de investigación, triage de atención al cliente.</p><p><strong>Stack:</strong></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text"><strong>Jina v5</strong></a> <strong>incrustaciones de agrupación:</strong> adaptadores específicos de Low-Rank Adaptation (LoRA) para la agrupación de temas. <a href="https://www.elastic.co/blog/elastic-jina-ai">Jina se unió a Elastic</a>, y sus modelos están disponibles de forma nativa a través del <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service (EIS)</a>.</p></li><li><p><strong>Elasticsearch:</strong> <a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN</a> escalable, etiquetado <code>significant_text</code> y almacenamiento de vectores.</p></li><li><p><a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction"><strong>DiskBBQ:</strong></a> un formato de índice vectorial basado en disco que combina <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/bbq">Better Binary Quantization (BBQ)</a> con una partición jerárquica k-medios para la aceleración aproximada de los vecinos más cercanos (ANN). Esta partición de índice es interna a la búsqueda vectorial y separada del algoritmo de agrupación con sonda de densidad utilizado en esta publicación. <code>bbq_disk</code> almacena vectores cuantificados en el disco y mantiene solo los metadatos de partición en el heap, lo que reduce drásticamente los requisitos de recursos en comparación con <code>bbq_hnsw</code>, mientras mantiene un alto nivel de recuperación.</p></li><li><p><strong>Agrupación global + vinculación temporal diaria:</strong> descubrimiento y evolución del tema.</p></li></ul><p><strong>Lo que necesitarás:</strong></p><ul><li><p>Un despliegue de Elasticsearch (Elastic Cloud, Elasticsearch Serverless o Elastic Self-Managed 8.18+/9.0+): <code>bbq_disk</code> requiere la versión 8.18 o posterior. La sección opcional de recuperación diversificada requiere 9.3+ o sin servidor.</p></li><li><p>Una <a href="https://jina.ai/embeddings/">clave de API de Jina</a>: la capa gratuita incluye 10 millones de tokens, lo que cubre la pipeline de agrupación del núcleo (~4.25 millones de tokens). La comparación opcional entre recuperación y agrupación usa una segunda pasada de incrustación.</p></li><li><p>Una <a href="https://bonobo.capi.gutools.co.uk/register/developer">clave API de Guardian</a> (gratis).</p></li></ul><h2>Configuración</h2><p>Instala los paquetes requeridos:</p>pip install elasticsearch pandas numpy plotly umap-learn python-dotenv pydantic-settings datasets requests<p>Opcional (solo si ejecutas los asistentes de raspado desde este repositorio):</p>pip install beautifulsoup4<p>Luego configura las claves API en un archivo <code>.env</code> en la raíz del proyecto:</p>ELASTIC_CLOUD_ID=your-cloud-id        # or ELASTIC_HOST=https://...
ELASTIC_API_KEY=your-api-key
JINA_API_KEY=your-jina-key
GUARDIAN_API_KEY=your-guardian-key<p>Este cuaderno llama a <code>load_dotenv(override=True)</code>, por lo que los valores locales <code>.env</code> tienen prioridad.</p>Connected to Elasticsearch<h2>Parte 1: agrupación de descubrimiento - ¿Por qué agrupar incrustaciones?</h2><p>La mayoría de las búsquedas vectoriales usan <strong>incrustaciones de recuperación</strong> entrenadas para hacer coincidir una <em>consulta</em> con <em>documentos</em> relevantes. Eso es ideal para la búsqueda, pero no para el descubrimiento. Cuando quieras encontrar qué temas existen en un corpus sin ninguna consulta, necesitas incrustaciones que agrupen documentos similares.</p><p>Jina v5 resuelve esto con <strong>adaptadores de Low-Rank Adaptation (LoRA) específicos para cada tarea</strong>. LoRA agrega pequeñas actualizaciones de bajo rango a las capas internas específicas mientras mantiene la mayoría de los pesos del modelo base congelados, por lo que el comportamiento del modelo se desplaza hacia una tarea específica sin repetir el entrenamiento completo. El mismo modelo base produce diferentes incrustaciones según el parámetro <code>task</code>:</p><p>Tarea</p><p>Capacitado para</p><p>Caso de uso</p><p>retrieval.passage</p><p>Coincidencia búsqueda-documento</p><p>Búsqueda, Retrieval-Augmented Generation (RAG)</p><p>agrupación</p><p>Agrupación de temas (optimizada para clústeres estrechos)</p><p>Descubrimiento, categorización</p><p>El adaptador de agrupación está entrenado para hacer que los documentos sobre el mismo tema estén <em>más cerca</em> en el espacio de incrustaciones y que los documentos sobre temas diferentes estén <em>más separados</em>. La comparación visual a continuación muestra la diferencia de forma concreta.</p><h3>Recuperación frente a agrupación: una comparación visual</h3><p>Para ver la diferencia, se incrusta una muestra de documentos con ambos tipos de tareas. La agrupación se realiza en el espacio de incrustación original de 1024 dimensiones; Uniform Manifold Approximation and Projection (UMAP) se usa solo para proyectar esas incrustaciones en 2D para su visualización. UMAP preserva la estructura de vecindad local, por lo cual es útil para comparar la separación entre clústeres.</p><p>A continuación, se muestra la misma muestra de 480 documentos con ambos tipos de tareas y proyectada en 2D con UMAP. Busca grupos de colores más compactos y mejor diferenciados en el panel de agrupación.</p>    Full dataset: 8,495 articles
    Sources: guardian: 5749, bbc: 2746
    Date range: 2025-02-01 to 2025-02-28


    Sample: 480 docs across 8 sections
    section
    Film              60
    World news        60
    Australia news    60
    Opinion           60
    Football          60
    US news           60
    Sport             60
    Business          60


    Clustering embeddings: 480
    Retrieval embeddings:  480


    UMAP projection complete<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4b3733dccad212b6/6a1709407d8d67aaeb70e6a4/9bcf7a744900560c1c6c63a2dc3af2f9bfd33e11-1100x500.png" alt="Comparación de UMAP entre incrustaciones de recuperación y de agrupación" /><p><em>Las incrustaciones de recuperación (izquierda) distribuyen los temas ampliamente; las incrustaciones de agrupación (derecha) producen grupos más ajustados y separados de los mismos documentos.</em></p><p>Las incrustaciones de agrupación producen grupos más compactos y visualmente más distintivos. Las incrustaciones de recuperación distribuyen los temas de manera más uniforme, ideales para la búsqueda (similitud de grano fino); pero para el descubrimiento, los clústeres temáticos compactos son lo que importa.</p><p>Esta es la razón por la que <code>task="clustering"</code> se usa para el resto de este recorrido.</p><h3>Carga de los sets de datos</h3><p>El corpus combina dos fuentes de noticias para febrero de 2025:</p><ul><li><p><strong>BBC News</strong> a través del <a href="https://huggingface.co/datasets/RealTimeData/bbc_news_alltime">set de datos RealTimeData/BBC_News_AllTime HuggingFace</a>.</p></li><li><p><strong>The Guardian</strong> a través de la <a href="https://open-platform.theguardian.com/">API de Guardian Open Platform</a>.</p></li></ul><p>Tener varias fuentes permite validar que la agrupación encuentra <em>temas</em> en lugar de <em>estilo específico de la fuente</em>.</p>    Total articles:  8,495
    
    Source breakdown:
    source
    guardian    5749
    bbc         2746
    
    Date range: 2025-02-01 → 2025-02-28
    Days covered: 28
    
    Sample article:
      Source:  guardian
      Title:   Carbon monoxide poisoning ruled out in death of Gene Hackman and wife, police sa
      Section: Film
      Text:    Authorities have ruled out that Gene Hackman and his wife, Betsy Arakawa, died from carbon monoxide poisoning earlier this week in their home in Santa Fe, New Mexico. The Santa Fe county sheriff, Adan...<h3>Incrustar con la tarea de agrupación</h3><p>La API de Jina v5 se llama con <code>task="clustering"</code> para todos los documentos. Las incrustaciones se almacenan en caché en disco, por lo que las ejecuciones posteriores se saltan la API por completo.</p><p>La llamada a la API es muy sencilla. El parámetro <code>task</code> es la diferencia clave con respecto al uso típico de incrustación:</p>payload = {
    "model": "jina-embeddings-v5-text-small",
    "input": texts,
    "task": "clustering",  # ← This selects the clustering LoRA adapter
}<p>El tiempo que se muestra a continuación refleja un acierto de caché. La primera ejecución contra la API lleva más tiempo, según el tamaño del corpus.</p>    Embeddings ready: 8,495 vectors of dimension 1024
    Time: 0.6s<h3>Indexar un índice único de Elasticsearch</h3><p>Para la agrupación por descubrimiento, el mes completo se destina a un índice (<code>docs-clustering-all</code>). La partición diaria se realiza más tarde para la vinculación temporal de temas.</p><p>El mapeo de índices emplea <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/bbq"><code>bbq_disk</code></a> para el campo vectorial:</p>{
  "embedding": {
    "type": "dense_vector",
    "dims": 1024,
    "index": true,
    "similarity": "cosine",
    "index_options": {
      "type": "bbq_disk"        // hierarchical k-means partitioning for ANN index lookup; separate from this post's clustering algorithm
    }
  }
}<p>Un vector float32 de 1024 dimensiones es de 4 KB. <a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction"><code>bbq_disk</code></a> usa k-medios jerárquicos para particionar vectores en pequeños clústeres, los cuantifica en binario y almacena los vectores de precisión completa en el disco para volver a guardarlos. Solo los metadatos de partición permanecen en el heap, por lo que los requisitos de memoria permanecen bajos incluso para corpus grandes. Para las cargas de trabajo que pueden permitirse más memoria dinámica, <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/bbq"><code>bbq_hnsw</code></a> crea un grafo HNSW (Hierarchical Navigable Small World) para agilizar las búsquedas, aunque a costa de un mayor consumo de recursos.</p><p>El tipo de campo <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector"><code>dense_vector</code></a> brinda soporte para múltiples estrategias de cuantización: <code>bbq_disk</code> y <code>bbq_hnsw</code> son las más adecuadas para embeddings de alta dimensión, como los vectores de 1024 dimensiones usados aquí.</p>    Indexed 8,495 documents into docs-clustering-all
    Time: 57.5s<h3>Agrupación: clasificación del centroide sondeada por densidad</h3><p>Los algoritmos de agrupación tradicionales como HDBSCAN asumen que puedes mantener la matriz de vectores N × d completa en memoria y ejecutar actualizaciones de pasada completa repetidas. Para 8495 documentos con 1024 dimensiones, esto es manejable (~35 MB), pero el enfoque no escala a millones de documentos sin infraestructura adicional.</p><p>Este algoritmo es conceptualmente similar a la inicialización de KMedios++ con asignación de Voronoi y un nivel de ruido, pero emplea <a href="https://www.elastic.co/docs/solutions/search/vector/knn">la búsqueda kNN</a> de Elasticsearch como primitiva de cálculo, manteniendo casi todo el trabajo en el servidor:</p><ol><li><p><strong>Muestrea el 5 % de los documentos</strong> como sondas de densidad (muestra aleatoria, mínimo 50).</p></li><li><p><strong>Densidad de sondas vía lotes de</strong> <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-msearch"><strong><code>msearch</code></strong></a> <strong>kNN</strong>. Cada sonda dispara una búsqueda kNN y registra la similitud media de sus vecinos. Alta similitud media = región densa del espacio de incrustación. <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-msearch"><code>msearch</code></a> envía múltiples solicitudes de búsqueda en una sola llamada HTTP, lo cual es fundamental en este caso: el sondeo de densidad genera cientos de consultas kNN, y agruparlas evita la sobrecarga por solicitud.</p></li><li><p><strong>Seleccione semillas de alta densidad con diversificación</strong>: los candidatos por encima de la densidad mediana se ordenan por densidad descendente y se aceptan de manera ávida solo cuando su similitud del coseno con cada semilla existente está por debajo de un umbral de separación. Este es el único cómputo del lado del cliente (~0.01s para 8k docs).</p></li><li><p><strong>Clasifica todos los documentos frente a los centroides mediante</strong> <strong><code>msearch</code></strong> <strong>kNN</strong>: cada semilla actúa como un centroide; una búsqueda kNN recupera documentos cercanos por encima de un umbral de similitud. Cada documento se asigna al centroide que lo devolvió con la puntuación más alta. Los clústeres pequeños se disuelven en ruido.</p></li></ol><p>Elasticsearch se encarga del trabajo pesado: <code>msearch</code> para sondas de densidad, <code>msearch</code> para clasificación y <code>significant_text</code> para etiquetado. Para este corpus (8495 documentos), la muestra del 5 % de sondeo de densidad lanza 425 consultas kNN de sondeo, que <code>msearch</code> agrupa en nueve llamadas HTTP (con lotes de 50), lo que evita la sobrecarga de una solicitud por sondeo. Combinado con la búsqueda ANN de <code>bbq_disk</code>, esto mantiene la etapa de agrupación rápida y escalable. Las consultas kNN usan un valor mínimo de <a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/approximate-knn-search"><code>num_candidates</code></a> para mayor velocidad durante la pasada de agrupación; las consultas de búsqueda de producción deben usar valores más altos de <code>num_candidates</code> para mejorar la recuperación a costa de la latencia.</p><p>Los clústeres tienen tamaños naturales determinados por la densidad del espacio de incrustación alrededor de cada centroide, no por un límite rígido de <code>k</code>. Las regiones temáticas densas producen clústeres más grandes; los temas de nicho producen clústeres más pequeños.</p><h4>¿Por qué no KMeans o HDBSCAN?</h4><p>KMedios asume clústeres esféricos y requiere la matriz N×d completa en memoria. Para corpus que caben en memoria, <a href="https://scikit-learn.org/stable/modules/generated/sklearn.cluster.HDBSCAN.html">HDBSCAN</a> es una alternativa estable. Maneja formas arbitrarias de clústeres y tiene una semántica de densidad bien entendida.</p><p>El enfoque de centroides con sondeo de densidad apunta a un nicho diferente: corpus con almacenamiento, recuperación y agrupación en un solo sistema, o en los que la escala hace que las operaciones matriciales del lado del cliente sean poco prácticas. Usa Elasticsearch kNN como primitiva de cómputo, maneja tamaños de clúster arbitrarios y mantiene casi todo el procesamiento del lado del servidor.</p>    Clustered global index in 31.6s
      Total clusters: 82
      Total noise:    2420 (28.5%)
      Density probes: 425 kNN queries via 9 _msearch HTTP calls<h4>Comprender la tasa de ruido</h4><p>La tasa de ruido de ~28 % es intencional, no un modo de falla. Los documentos que no encajan en ningún clúster denso en el <code>similarity_threshold</code> configurado quedan sin asignar en vez de ser forzados a una coincidencia deficiente. Esto actúa como un control de calidad: las columnas de opinión, los artículos cortos y las historias aisladas resisten naturalmente la acción de agrupar porque carecen de la densidad temática que define un grupo coherente.</p><p>El umbral es ajustable: reducir <code>similarity_threshold</code> produce una agrupación más agresiva (más documentos asignados, pero clústeres más dispersos), mientras que aumentarlo ajusta los clústeres e incrementa la fracción de ruido. Para este corpus de contenido de noticias mixtas, ~30 % de ruido es un punto de operación razonable. Los despliegues de producción deben ajustar el umbral según criterios de calidad específicos del dominio.</p><h3>Etiquetas automáticas con significant_text</h3><p>Ahora cada clúster necesita una etiqueta legible para humanos. La agregación <code>significant_text</code> de Elasticsearch encuentra términos que aparecen inusualmente a menudo en un conjunto en primer plano (el clúster) en comparación con un conjunto de fondo (el corpus completo).</p><p>En el fondo, utiliza una heurística estadística (puntuación JLH de forma predeterminada) que equilibra los cambios de frecuencia absoluta y relativa, sin machine learning, sin llamadas a modelos de lenguaje grandes (LLM). Un clúster sobre política del Reino Unido podría mostrar términos como <code>starmer</code>, <code>labour</code>, <code>downing</code> porque esos términos son desproporcionadamente comunes en ese clúster en comparación con el corpus de noticias general.</p><p>Para esta pasada global, las etiquetas se calculan directamente con respecto a <code>docs-clustering-all</code>, por lo que tanto el primer plano como el fondo se extraen del mes completo. En la parte 2, el etiquetado usa el patrón de índice diario (<code>docs-clustering-*</code>), un comodín que permite que las búsquedas abarquen todos los índices coincidentes simultáneamente, para darle a <code>significant_text</code> un fondo más amplio y lograr un mejor contraste.</p><p>Una forma mínima de consulta se ve así:</p>{
  "size": 0,
  "query": { "term": { "cluster_id": "72" } },
  "aggs": {
    "label_terms": {
      "significant_text": {
        "field": "text",
        "size": 5,
        "filter_duplicate_text": true
      }
    }
  }
}<p><code>significant_text</code> también sirve como control de calidad: los clústeres que no producen términos importantes no tienen vocabulario distintivo. Son agrupaciones incoherentes que deberían disolverse de nuevo en ruido en lugar de recibir una etiqueta engañosa.</p><p>Un paso de limpieza determinista y ligero elimina los términos de etiqueta ruidosos (tokens numéricos, palabras genéricas) y recurre a un titular representativo cuando es necesario. Esto mantiene las etiquetas nativas de Elasticsearch y, al mismo tiempo, mejora la legibilidad.</p>    Sample cluster labels:
      cluster   3  (200 docs)  arsenal | mikel | villa
      cluster   1  (198 docs)  volodymyr | ukrainian | kyiv
      cluster   0  (196 docs)  hostages | hamas | israeli
      cluster   4  (187 docs)  scrum | rugby | borthwick
      cluster  52  (185 docs)  fossil | renewable | renewables
      cluster  10  (156 docs)  labour | gwynne | mps
      cluster  40  (151 docs)  novel | novels | literary
      cluster  11  (149 docs)  mewis | sarina | wiegman
      cluster  44  (143 docs)  flooding | rainfall | rain
      cluster  13  (131 docs)  doge | musk | elon
      cluster  12  (128 docs)  murder | insp | knockholt
      cluster   5  (124 docs)  putin | backstop | starmer


    Reassigned 35 docs from incoherent clusters to noise
    Total docs: 8,495
    Clustered:  6,040 (71.1%)
    Noise:      2,455 (28.9%)<h3>Visualizar los clústeres</h3><p>Las visualizaciones a continuación muestran lo que descubrió la pasada global de agrupación: un desglose por fecha de los documentos agrupados frente a los documentos de ruido, una proyección UMAP del mes completo y un gráfico de combinación de fuentes que confirma que los clústeres reflejan temas en lugar de fuentes.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4ed087d8b6dac2a0/6a17094260084b44543c4501/99099f5adaa945ae4097c50b0d7151c7dd28872e-1000x400.png" alt="Distribución diaria de documentos agrupados en clústeres frente a documentos de ruido" /><p>Distribución diaria de documentos agrupados frente a documentos de ruido a lo largo de febrero de 2025.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf5ca320bc91131ab/6a17094366c4f95828f8bfbf/477c6c7177942955a942f85f5c881da50e517915-1100x700.png" alt="Proyección UMAP de todo el mes con todos los documentos" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f554a6bc2bc367b/6a170945a929cfa7a1ae0947/4f4302556c8974c416842452cf33bca06e90b966-1100x700.png" alt="Proyección UMAP que muestra solo documentos agrupados" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8e40c26f89ce5523/6a17094747d49c147d2d8974/327f96a79e382ef30614cb0570aa7fccd822b8f8-1100x700.png" alt="[Proyección UMAP que resalta un solo clúster" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf1dd2c19ae628f1d/6a1709481949f7a630e7a9a3/acfb1524a10e24d6ff2412e7c3ec0f2b3ac75193-900x600.png" alt="Mezcla de origen por cluster que muestra la agrupación por temas" /><p>Cada isla coloreada en el UMAP representa un clúster: un grupo de artículos sobre el mismo tema descubiertos únicamente a partir de la similitud de incrustación. Los puntos de ruido gris son artículos que no encajaban perfectamente en ningún clúster (a menudo artículos cortos, artículos de opinión o historias únicas).</p><p>El gráfico de desglose de fuentes confirma que los clústeres contienen artículos de <strong>ambas</strong> BBC News y The Guardian. La agrupación está encontrando <em>temas</em>, no <em>fuentes</em>: exactamente lo que el descubrimiento no supervisado debería arrojar.</p><h3>Explorar la amplitud del clúster con el recuperador diversificado</h3><p>El kNN simple devuelve los documentos más similares al centroide de un clúster (el núcleo compacto). Pero los clústeres reales cubren subtemas. El <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/diversify-retriever"><strong>recuperador diversificado</strong></a> utiliza Maximal Marginal Relevance (MMR) para mostrar documentos que son relevantes para el centroide pero también <em>diferentes entre sí</em>.</p><p>El parámetro clave es <strong>λ (lambda):</strong></p><ul><li><p>λ = 1.0 → pura relevancia (igual que kNN simple).</p></li><li><p>λ = 0.0 → diversidad pura (resultados de máxima distribución).</p></li><li><p>λ = 0.5 → equilibrado: es relevante para el tema, pero cubre diferentes ángulos.</p></li></ul><p>Una solicitud mínima de recuperador tiene el siguiente aspecto:</p>{
  "size": 8,
  "retriever": {
    "diversify": {
      "type": "mmr",
      "field": "embedding",
      "lambda": 0.5,
      "query_vector": "&lt;cluster-centroid-vector&gt;",
      "retriever": {
        "knn": {
          "field": "embedding",
          "query_vector": "&lt;cluster-centroid-vector&gt;",
          "k": 50,
          "num_candidates": 100
        }
      }
    }
  }
}<p>Los parámetros <code>type</code>, <code>field</code> y <code>query_vector</code> son necesarios a nivel de diversificación: <code>field</code> indica al MMR qué campo dense_vector usar para la similitud entre resultados, y <code>query_vector</code> proporciona el punto de referencia para el puntaje de relevancia.</p><p>Esto te permite responder: “¿Qué abarca realmente este clúster?” en vez de solo “¿Qué hay en su centro?”</p>    Exploring cluster 52 (185 docs)
    Label: fossil | renewable | renewables
    Centroid computed (dim=1024)


    ========================================================================
    Plain kNN (closest to centroid)
    ========================================================================
      1. [0.9738] Green campaigners fear ministers are poised to award billions of pounds in fresh subsidies to Drax power station, despite strong concerns...
      2. [0.9710] Thirteen more oil and gas licences could be cancelled as ministers decide new guidance for fossil fuel extraction after a landmark court...
      3. [0.9699] Experts have accused the fossil fuel industry of seeking special treatment after lobbyists argued greenhouse gas emissions from oilfields...
      4. [0.9681] Burning wood is a terrible way of producing electricity . Chopping down trees destroys habitats for wildlife, and growing new trees cannot...
      5. [0.9649] Keir Starmer will do huge damage to the global fight against climate change if he gives in to political pressure and allows the development...
      6. [0.9641] Labour will next week be confronted with stark policy choices that threaten to expose the fault lines between the Treasury and the...
      7. [0.9638] The Drax power station near Selby in north Yorkshire burns imported wood pellets  The government has agreed a new funding arrangement with...
      8. [0.9581] If you care about the world we are handing on to future generations, the news on Thursday morning was dramatic. This January was the...
    
    ========================================================================
    Diversify retriever (MMR, lambda=0.5)
    ========================================================================
      1. [0.9738] Green campaigners fear ministers are poised to award billions of pounds in fresh subsidies to Drax power station, despite strong concerns...
      2. [0.9434] Oil and gas interests have waged a coordinated campaign to kill pro-electrification policies that ban gas connections in new buildings ,...
      3. [0.9303] It was interesting to read that new licences for oil and gas production in the North Sea are being delayed by legal action ( Thirteen more...
      4. [0.9139] The US energy secretary, Chris Wright, has said he “would love to see Australia get in the game of supplying uranium and maybe going down...
      5. [0.9077] Rachel Reeves was facing criticism on Saturday night as it was confirmed that a report she cited as evidence that a third ­runway at...
      6. [0.8996] When Margaret Thatcher opened the Hadley Centre for Climate Change in 1990 journalists suggested she was attempting to appear to be doing...
      7. [0.8993] The vast majority of governments are likely to miss a looming deadline to file vital plans that will determine whether or not the world has...
      8. [0.8987] European imports of seaborne gas shipments fell by a fifth last year to their lowest level since the pandemic, according to a new report,...
    
    Overlap: 1/8 documents appear in both result sets
    
    Avg pairwise similarity (lower = more diverse):
      Plain kNN:          0.9057
      Diversify retriever: 0.6965<p>Los resultados simples de kNN se agrupan en torno a un ángulo del tema: los documentos más similares al centroide y entre sí. El recuperador diversificado muestra diferentes facetas del mismo clúster: subtemas, fuentes diferentes y perspectivas variadas.</p><p>La métrica de diversidad confirma esto cuantitativamente: la similitud promedio por pares es menor en los resultados del recuperador diversificado, lo que significa que los documentos arrojados tienen mayor alcance.</p><p>Esto es útil para:</p><ul><li><p><strong>Comprender el alcance real de un clúster</strong>: no solo su centro, sino también sus bordes.</p></li><li><p><strong>Generar resúmenes</strong>. Los documentos diversos y representativos le dan a un LLM mejor material.</p></li><li><p><strong>Encontrar ejemplos representativos</strong> para revisión humana o etiquetado posterior.</p></li><li><p><strong>Controles de calidad</strong>. Si los diversos resultados parecen incoherentes, es posible que el clúster deba dividirse.</p></li></ul><h2>Parte 2: cadenas temáticas temporales</h2><h3>Seguimiento de temas con el paso de los días</h3><p>La parte 1 agrupó todo el mes a nivel global para el descubrimiento de temas. Para el flujo temporal, la misma clasificación de centroides con sonda de densidad se ejecuta independientemente por día en los <strong>índices diarios</strong>, y luego los clústeres se vinculan en días adyacentes. Tenga en cuenta que los clústeres diarios son independientes de los clústeres globales de la parte 1; cada día produce sus propias asignaciones y etiquetas de clúster ajustadas al contenido de ese día.</p><h4><strong>El enfoque de enlazado: muestra y consulta</strong></h4><p>Para cada clúster el día A:</p><ol><li><p>Muestra algunos documentos representativos.</p></li><li><p>Ejecuta kNN contra el índice del día B.</p></li><li><p>Cuenta cuántas coincidencias se registran en cada clúster del día B.</p></li><li><p>Si la fracción de aciertos excede un umbral (fracción kNN ≥ 0.4), registra un enlace.</p></li></ol><p>Esto es rápido (solo se consultan unos pocos documentos por clúster, no todos) y usa el kNN nativo de Elasticsearch, sin necesidad de herramientas externas.</p>Preparing daily indices for temporal linkage...


Indexed 8,495 docs into 28 daily indices


Temporal links found: 808 in 145.4s

Strongest links:
  2025.02.01 'league | arsenal | premier' -&gt; 2025.02.02 'league | season | striker'  (100%)
  2025.02.03 'league | striker | loan' -&gt; 2025.02.04 'league | striker | season'  (100%)
  2025.02.03 'score | operator | gedling' -&gt; 2025.02.04 'league | striker | season'  (100%)
  2025.02.12 'playoff | leg | bayern' -&gt; 2025.02.13 'league | players | injury'  (100%)
  2025.02.14 'league | injury | football' -&gt; 2025.02.15 'league | premier | football'  (100%)
  2025.02.18 'russia | ukraine | talks' -&gt; 2025.02.19 'saudi | russia | arabia'  (100%)
  2025.02.18 'football | league | bayern' -&gt; 2025.02.19 'league | manchester | players'  (100%)
  2025.02.21 'league | premier | manchester' -&gt; 2025.02.22 'game | players | defeat'  (100%)
  2025.02.21 'rugby | calcutta | brilliant' -&gt; 2025.02.22 'game | players | defeat'  (100%)
  2025.02.26 'metals | kyiv | ukrainian' -&gt; 2025.02.27 'ukraine | russia | talks'  (100%)<p>Una fracción kNN del 100 % significa que cada documento muestreado del clúster de origen llegó al mismo clúster de destino, el vínculo entre días más fuerte posible. La mayoría de los vínculos anteriores están relacionados con el fútbol, lo que tiene sentido: la cobertura de la Premier League se ejecuta a diario con alta consistencia temática.</p><p>El enlace <code>score | operator | gedling</code> → <code>league | striker | season</code> es un ejemplo de un clúster de fútbol local de nicho (Gedling es un club de liga no profesional) que se integra en el clúster más amplio de la Premier League al día siguiente, una consecuencia natural de la reagrupación diaria a diferentes niveles de granularidad.</p><h3>Construyendo cadenas de historias</h3><p>Una cadena de temas es una secuencia de agrupaciones vinculadas en días consecutivos.</p><p>Los enlaces individuales por pares indican que el clúster "Política del Reino Unido" del lunes se conecta con el del martes. Las cadenas revelan la evolución completa: una historia que comienza el lunes, evoluciona a lo largo de la semana y se desvanece el viernes.</p><p>Las cadenas se construyen de forma voraz a partir de enlaces con una fracción kNN ≥ 0.4, lo que significa que al menos el 40 % de los documentos muestreados del clúster de origen terminaron en un único clúster de destino. Comenzando desde el clúster más antiguo, el algoritmo siempre sigue el enlace saliente más fuerte.
</p>    Strong links (kNN fraction &gt;= 0.4): 244
    Story chains spanning 3+ days: 18
      Chain 1: 'ukrainian | kyiv | eastern' (19 days: Feb 3 → Feb 21)
      Chain 2: 'playing | opposition' (19 days: Feb 10 → Feb 28)
      Chain 3: 'tadhg | maro | cadan' (10 days: Feb 1 → Feb 10)
      Chain 4: 'invade | china | putin' (8 days: Feb 21 → Feb 28)
      Chain 5: 'elected | labour | leader' (7 days: Feb 12 → Feb 18)
      Chain 6: 'film | swift | awards' (6 days: Feb 2 → Feb 7)
      Chain 7: 'amendment | termination | reporting' (6 days: Feb 12 → Feb 17)
      Chain 8: 'officers | scene | police' (5 days: Feb 1 → Feb 5)<p>La cadena más larga rastrea la cobertura Ucrania-Rusia durante 19 días consecutivos, lo cual no es sorprendente dado el sostenido nivel de intensidad geopolítica en febrero de 2025. El segundo más largo sigue al fútbol de la Premier League durante 19 días del mes. Las cadenas más cortas capturan la temporada de premios (cine/premios, seis días), el rugby de las Seis Naciones (10 días) y la cobertura del liderazgo político del Reino Unido (siete días). Cada cadena representa una evolución temática que el algoritmo descubrió puramente a partir de la similitud de incrustaciones a través de índices diarios.</p><h3>Sankey: visualizando el flujo de la historia</h3><p>Un diagrama de Sankey es una visualización de un flujo en la que el grosor de los enlaces indica la intensidad de la conexión. Aquí, cada banda vertical es un día, cada nodo es un clúster diario (dimensionado por el recuento de documentos) y cada ruta de color traza una cadena de temas a lo largo del tiempo. El ancho del enlace codifica la fuerza de superposición de kNN: los enlaces más gruesos significan que más documentos muestreados llegaron al clúster de destino. Los colores son consistentes por cadena, así que una sola ruta de color de izquierda a derecha se lee como la progresión de un tema.</p><p>Por ejemplo, la cadena Ucrania-Rusia (que se ve como una de las rutas más largas) fluye de manera continua desde principios de febrero hasta la tercera semana, con enlaces que se mantienen gruesos, lo que indica una fuerte continuidad temática a lo largo de los días.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta8c243e0c5773440/6a17093fa6c2b98c86e7968c/100a60a7fb85da8ab3813fd071a82c93f2c3f318-1300x650.png" alt="Cadenas temporales de temas que se desarrollan a lo largo de febrero de 2025" /><p><em>Cadenas temporales de temas que se desarrollan a lo largo de febrero de 2025. Cada camino coloreado es un tema que persiste a lo largo de los días; el ancho del enlace indica la fuerza de superposición kNN.</em></p><h2>Qué ofrece este enfoque</h2><p>Esta guía cubrió una pipeline completa de agrupación de documentos sin supervisión desarrollada sobre Elasticsearch:</p><ol><li><p><strong>Agrupar incrustaciones</strong>: los adaptadores específicos de la tarea de Jina v5 producen incrustaciones optimizadas para la agrupación por temas, no solo para la coincidencia entre consulta y documento.</p></li><li><p><strong>Agrupación de descubrimiento global</strong>: agrupar el mes completo en un índice maximiza el descubrimiento de temas entre días.</p></li><li><p><strong>Clasificación de centroides con densidad probada</strong>: muestrea el 5 %, sondea densidad vía <code>msearch</code> kNN, selecciona semillas diversas de alta densidad, clasifica todos los documentos contra los centroides. Elasticsearch maneja el cómputo pesado; solo la selección de semillas se ejecuta del lado del cliente (~0.01 s).</p></li><li><p><a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significanttext-aggregation"><strong><code>significant_text</code></strong></a> <strong>etiquetado</strong>: las pruebas de significancia producen etiquetas de clúster significativas sin ningún modelo de ML o anotación manual. Los clústeres que no producen términos significativos son incoherentes y se degradan a ruido: una barrera de calidad integrada.</p></li><li><p><strong>Vinculación temporal de temas</strong>: los índices diarios y el rastreo de muestras y consultas de kNN entre índices permiten rastrear cómo evolucionan los temas a lo largo del tiempo.</p></li></ol><p><strong>Conclusiones clave:</strong></p><ul><li><p>El tipo de tarea de incrustación es importante: las incrustaciones de agrupación producen grupos temáticos notablemente más compactos.</p></li><li><p>Elasticsearch puede funcionar tanto como capa de almacenamiento <em>como</em> motor de agrupación mediante <a href="https://www.elastic.co/docs/solutions/search/vector/knn">búsqueda kNN</a>.</p></li><li><p>La clasificación de centroides con sonda de densidad mantiene casi todos los datos del lado del servidor y produce clústeres con tamaños naturales determinados por la densidad de espacio de incrustaciones.</p></li><li><p><code>significant_text</code> es rápido, interpretable y efectivo tanto para el etiquetado automático como para el control de calidad.</p></li></ul><p><strong>Este enfoque es útil en los siguientes casos:</strong></p><ul><li><p>Tienes texto con fecha y quieres descubrir un tema sin datos de entrenamiento etiquetados.</p></li><li><p>Deseas una pila para almacenamiento, búsqueda de vectores, etiquetado y vinculación temporal.</p></li></ul><p><strong>Extensiones para explorar:</strong></p><ul><li><p>Agrupación de varios períodos (resúmenes semanales y mensuales).</p></li><li><p>Ingesta en tiempo real con asignación incremental de clúster.</p></li><li><p>Resúmenes de clústeres generados por LLM usando los términos de significant_text como semillas.</p></li><li><p>A mayor escala, los centroides de K-Medios obtenidos mediante muestreo pueden servir como semillas de inicio rápido para la agrupación basada en densidad, lo que reduce el costo de la fase de exploración.</p></li></ul><h2>Pruébalo tú mismo</h2><p>Sustituye el corpus de documentos con marcas de tiempo por el tuyo; cualquier colección de texto con fechas funciona con este pipeline. El cuaderno completo y el código de soporte están disponibles en el <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/unsupervised-document-clustering-elasticsearch-jina-embeddings">repositorio complementario</a>.</p><ul><li><p><a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs"><strong>Activa una prueba gratuita de Elastic Cloud</strong></a>: crea un clúster administrado con soporte para <code>bbq_disk</code> en cuestión de minutos.</p></li><li><p><a href="https://www.elastic.co/elasticsearch/serverless"><strong>Prueba Elasticsearch Serverless</strong></a>: sin gestión de clústeres, escala automáticamente y brinda soporte para todo en este tutorial.</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/unsupervised-document-clustering-elasticsearch-jina-embeddings</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/unsupervised-document-clustering-elasticsearch-jina-embeddings</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[Investigación en ML]]></category>
    <category><![CDATA[Jina AI]]></category>
    <dc:creator><![CDATA[Matthew Adams]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4bd7dd10a7cd6dc8/6a17094a14b270581de3c5b6/662c00694c3e0c2fb2128098bdb6813df9e86a72-1280x720.png" length="0" type="image/png"/>
    <pubDate>Fri, 10 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Cuando TSDS se une a ILM: diseñar flujos de datos temporales que no rechazan los datos tardíos]]></title>
    <description><![CDATA[Cómo los límites de tiempo de TSDS interactúan con las fases de ILM; y cómo diseñar políticas que toleren métricas tardías.]]></description>
    <content:encoded><![CDATA[<p>Recientemente, migré el clúster de métricas de un cliente de “todo en el nivel caliente” a una arquitectura de caliente/frío/congelado. Fue un cambio que había realizado docenas de veces antes. En cuestión de minutos, Logstash dejó de avanzar los datos por completo.</p><p>Elasticsearch rechazaba métricas que llegaban con retraso. Esos rechazos hicieron que el pipeline se retrasara, lo que dio como resultado más datos tardíos, lo que desencadenó aún más rechazos. Finalmente, el pipeline se detuvo por completo.</p><p>Tuvimos que restaurar desde un snapshot, reindexar los datos y rediseñar el pipeline de ingesta para recuperarnos.</p><p>La causa raíz no era la gestión del ciclo de vida de los índices (ILM) en sí. Eran los flujos de datos temporales (TSDS) y cómo imponen índices de respaldo acotados en el tiempo.</p><p>TSDS puede reducir las necesidades de almacenamiento para las métricas en un 40–70 %, pero los cambios de arquitectura que hacen que TSDS sea eficiente también alteran el comportamiento de los índices con el tiempo. Esos cambios importan al diseñar políticas de ILM o cuando tus Pipelines de ingesta pueden generar datos que llegan con retraso.</p><h2>TL;DR</h2><p>Al usar TSDS:</p><ul><li><p>Los índices de respaldo solo aceptan documentos dentro de una ventana de tiempo específica.</p></li><li><p>Si llegan datos tardíos después de que un índice pasa a frío o congelado, Elasticsearch rechaza esos documentos o los envía al almacén de fallas, si está configurado.</p></li></ul><p>Regla de diseño:</p>warm_min_age &gt; rollover_max_age + maximum_expected_lateness<h2>¿Qué es un flujo de datos temporales?</h2><p>Un<em> flujo de datos temporales</em> (TSDS) es un flujo de datos especializado optimizado para datos de métricas. Los datos se distribuyen de manera que los documentos relacionados se encuentren dentro de los mismos fragmentos, lo que optimiza su búsqueda y recuperación. Así es como lo hace Elasticsearch:</p><p>Cada documento contiene:</p><ul><li><p>Una marca de tiempo.</p></li><li><p>Campos de dimensión que identifican las series temporales.</p></li><li><p>Campos métricos que representan valores medidos.</p></li></ul><p>Entre los ejemplos, se incluyen los siguientes:</p><ul><li><p>Uso de CPU por host.</p></li><li><p>Latencia de las solicitudes por servicio.</p></li><li><p>Lecturas de temperatura por sensor.</p></li></ul><p><em>Las dimensiones identifican </em>lo que queremos medir, mientras que <em>las métricas representan </em>valores que cambian con el tiempo.</p><h3>Dimensiones</h3><p>Las dimensiones describen la entidad medida.</p><p>Ejemplos:</p>host.name
service.name
container.id<p>Los definimos en mapeos con:</p>time_series_dimension: true<h3>Métricas</h3><p>Las métricas representan valores numéricos y se definen mediante:</p>time_series_metric<p>Tipos comunes de métricas:</p><ul><li><p>Indicador: valores que suben y bajan.</p></li><li><p>Contador: valores que aumentan hasta que se reinician.</p></li></ul><p>Elastic Agent recopila principalmente métricas y datos de logs, por lo que, incluso si no has habilitado ningún índice TSDS manualmente, puedes tenerlos aún en tu clúster.</p><h3>El campo _tsid</h3><p>Elasticsearch genera internamente un valor <code>_tsid</code> a partir de campos dimensionales. Esto permite que los documentos con dimensiones idénticas se dirijan al mismo shard, lo que mejora:</p><ul><li><p>Compresión.</p></li><li><p>Localidad de búsqueda.</p></li><li><p>Rendimiento de agregación.</p></li></ul><h2>La diferencia clave: índices de respaldo limitados en el tiempo</h2><p>Los flujos de datos tradicionales siempre escriben en el índice de respaldo más reciente, llamado <em>índice de escritura</em>, pero TSDS se comporta de manera diferente.</p><p>Cada índice de respaldo TSDS tiene una ventana de tiempo definida y solo acepta documentos con <code>@timestamp</code> valores que se encuentren en esa ventana:</p>GET _data_stream/my-metrics-data-stream


     "index_mode": "time_series",
     "time_series": {
       "temporal_ranges": [
         {
           "start": "2026-01-15T14:35:50.000Z",
           "end": "2026-03-16T11:34:40.000Z"
         }
       ]
     }<p>Cuando se indexa un documento, Elasticsearch lo dirige al índice de respaldo responsable de esa marca de tiempo, lo que significa que, a diferencia de los índices tradicionales, un TSDS puede escribir en varios índices de respaldo al mismo tiempo.</p><p>Por ejemplo:</p><ul><li><p>Datos en tiempo real → índice más reciente.</p></li><li><p>Datos tardíos → índice anterior que abarca ese intervalo de tiempo.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b001853af30d5f8/6a17dc2cfaa9137a7d93c751/31af2bb3b3dc24db8342e791e1db77a44659ba7a-1589x502.png" alt="Cronología que muestra cómo un documento tardío se enruta a un índice más antiguo, mientras que un documento actual va al índice más reciente." /><h2>Diseño para datos con retraso</h2><p>Las pipelines de ingesta reales rara vez entregan métricas perfectamente a tiempo. Las métricas pueden retrasarse por interrupciones de la red, retrasos en el camino, ingesta por batch y pérdida de dispositivos periféricos, que se vuelven a conectar y comienzan a ponerse al día.</p><p>Los índices tradicionales absorben silenciosamente esos retrasos. TSDS no lo hace.</p><p>Si la marca de tiempo de un documento queda fuera del intervalo de índices de respaldo con capacidad de escritura, Elasticsearch lo rechaza, lo que significa que tu política de ILM debe tener en cuenta los datos que llegan con retraso.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6e8eae1b2ddad142/6a17dc2e1d1b8335a793e35c/32a103b95b20e31615c214271e27811a7ee315ae-1999x691.png" alt="Línea de tiempo del ciclo de vida del índice" /><h2>La restricción crítica</h2><p>Los índices de respaldo deben mantenerse con permisos de escritura el tiempo suficiente para aceptar datos tardíos.</p><p>En términos prácticos:</p>time_until_readonly &gt; maximum_expected_lateness<p>Debido a que ILM mide las antigüedades desde el desplazamiento, la regla operativa se convierte en:</p>warm_or_cold_min_age &gt; rollover_max_age + maximum_expected_lateness<p></p><p>Por ejemplo, si las métricas pueden llegar con hasta seis horas de retraso, los índices deben permanecer editables al menos seis horas después de la transferencia.</p><p></p><p>No tener en cuenta esta restricción fue exactamente lo que causó la falla de ingesta descrita anteriormente. Los datos que llegaban tarde se dirigieron a un índice anterior, que ya estaba en el nivel frío y, por tanto, bloqueaba la escritura.</p><p></p><h2>Gestión de documentos rechazados</h2><p>Cuando TSDS rechaza un documento, Elasticsearch devuelve un error, lo que indica que la marca de tiempo no está dentro del rango de índices de escritura. La forma en que tu pipeline de ingesta maneja ese error determina si pierdes datos o si la ingesta se detiene.</p><p>El mecanismo principal para manejar documentos rechazados es el almacén de fallas.</p><h3>Almacén de fallas (recomendado en Elasticsearch 9.1+)</h3><p>Elasticsearch 9.1 introdujo el almacén de fallas, que captura automáticamente los documentos rechazados. En lugar de devolver errores a los clientes, Elasticsearch escribe los documentos rechazados en un índice de fallas dedicado dentro del flujo de datos.</p><p>Puedes inspeccionar las fallas usando:</p>GET metrics-myapp::failures/_search<p>Usar el almacén de fallas evita que los pipelines de ingesta se bloqueen por errores de rechazo, a la vez que conserva los datos fallidos para analizarlos o <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/reindex-tsds">reindexarlos</a>.</p><h2>Monitoreo de problemas de rechazo</h2><p>Los problemas de retraso suelen aparecer primero como anomalías de ingesta. Puedes notarlos primero como:</p><ul><li><p>Caídas repentinas en la tasa de indexación.</p></li><li><p>Aumentos repentinos en el número de documentos rechazados.</p></li><li><p>Una cantidad cada vez mayor de entradas del almacén de fallas.</p></li><li><p>Discrepancias entre el número de entradas y salidas del pipeline.</p></li></ul><p>Alertar sobre estas señales permite a los operadores detectar problemas antes de que los pipelines se detengan. Se pueden utilizar flujos de trabajo, tareas de machine learning y otros mecanismos para automatizar la detección y la notificación.</p><h2>Lista de verificación de migración para TSDS + ILM</h2><p>Si estás migrando un cluster de métricas a TSDS, introduciendo la organización en niveles con ILM o actualizando a una versión de Elasticsearch en la que las métricas son TSDS por defecto, revisa primero estos elementos.</p><h3><strong>1. Mide la latencia de la ingesta</strong></h3><p>Antes de cambiar las políticas de ILM, determina:</p><ul><li><p>Retraso normal de ingesta.</p></li><li><p>Retraso en el peor de los casos durante incidentes.</p></li><li><p>Retrasos causados por pipelines de batch.</p></li></ul><p>Tu diseño de ILM debe contemplar el retraso máximo realista.</p><h3><strong>2. Verifica las ventanas de tiempo de indexación</strong></h3><p>Inspecciona tus índices de respaldo TSDS:</p>GET _data_stream/&lt;your-stream&gt;<p>Busca:</p><ul><li><p><code>time_series.start_time</code></p></li><li><p><code>time_series.end_time</code></p></li></ul><p>Estos límites determinan qué índices pueden aceptar documentos. Entender estas ventanas puede ayudarte a determinar cuán tarde pueden llegar los datos antes de que se rechacen.</p><h3><strong>3. Dimensiona el nivel caliente para llegadas tardías</strong></h3><p>Garantiza que los índices de respaldo permanezcan con permisos de escritura el tiempo suficiente para los datos retrasados.</p><p>Regla operativa:</p><ul><li><p><code>warm_min_age &gt; rollover_max_age + maximum_expected_lateness</code></p></li></ul><p>Recuerda, los índices deben permanecer en modo de escritura durante al menos seis horas si las métricas pueden llegar con seis horas de retraso.</p><h3><strong>4. Decide cómo manejar los documentos rechazados</strong></h3><p>Elige una estrategia antes de habilitar TSDS:</p><ul><li><p>Almacén de fallas (recomendado en Elasticsearch 9.1+).</p></li><li><p>Cola de mensajes no procesados de Logstash.</p></li><li><p>Índice de respaldo para retrasos.</p></li><li><p>Aceptar la pérdida de datos limitada.</p></li></ul><h3><strong>5. Monitorea el estado de la ingesta</strong></h3><p>Agrega alertas para:</p><ul><li><p>La tasa de indexación disminuye.</p></li><li><p>Documentos rechazados.</p></li><li><p>Crecimiento del almacén de fallas.</p></li><li><p>Desajustes de entrada/salida en la pipeline.</p></li></ul><p>Los problemas de datos tardíos a menudo aparecen primero como anomalías de ingesta.</p><h2>Resumen</h2><p>Los flujos de datos temporales ofrecen importantes mejoras de almacenamiento y rendimiento para las cargas de trabajo de métricas, pero introducen un cambio arquitectónico importante: los índices de respaldo están acotados en el tiempo, lo que afecta el comportamiento de ILM.</p><p>Al usar TSDS:</p><ul><li><p>Los índices deben mantenerse con permisos de escritura el tiempo suficiente para aceptar datos tardíos.</p></li><li><p>Los pipelines de ingesta deben gestionar los documentos rechazados de forma segura.</p></li></ul><p>La regla clave a recordar es:</p>warm_min_age &gt; rollover_max_age + maximum_expected_lateness<p>Si diseñas políticas de ILM teniendo en cuenta esa limitación, TSDS funciona muy bien para cargas de trabajo de métricas.</p><p>Sin embargo, si lo ignoras, tu pipeline de ingesta puede descubrir esos límites de tiempo de la manera difícil.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/tsds-ilm-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/tsds-ilm-elasticsearch</guid>
    <category><![CDATA[Datos de índice]]></category>
    <category><![CDATA[Base de datos vectorial]]></category>
    <dc:creator><![CDATA[Bret Wortman]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcacf154aeacb29d8/6a17dc30dbb4ffc7ddfb557f/e4c46e4a6f746d9c845857e80de036f5d51cd4e7-1280x720.png" length="0" type="image/png"/>
    <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <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[Rapidez vs. precisión: medición de recuperación de la búsqueda vectorial cuantificada]]></title>
    <description><![CDATA[Explicación de cómo medir la recuperación para la búsqueda vectorial en Elasticsearch con una configuración mínima.]]></description>
    <content:encoded><![CDATA[<p>Todos quieren que la búsqueda de vectores sea instantánea. Pero los vectores de alta dimensión son pesados. Un único vector float-32 de 1024 dimensiones ocupa una memoria significativa, y compararlo con millones de otros es computacionalmente costoso.</p><p>Para resolver esto, los motores de búsqueda como Elasticsearch usan dos estrategias principales de optimización:</p><ol><li><p><strong>Búsqueda aproximada (mundo pequeño jerárquico navegable [HNSW]):</strong> en lugar de examinar cada documento, construimos un grafo de navegación para saltar rápidamente al vecindario probable de la respuesta.</p></li><li><p><strong>Cuantización:</strong> Comprimimos los vectores (por ejemplo, de flotantes de 32 bits a enteros de 8 bits o incluso valores binarios de 1 bit) para reducir el uso de memoria y acelerar los cálculos.</p></li></ol><p>Pero la optimización a menudo tiene un precio: <strong>la precisión</strong>.</p><p>El miedo es válido: "Si comprimo mis datos y tomo atajos durante la búsqueda, ¿me perderé los mejores resultados?". "¿Esta optimización degrada la relevancia de mi motor de búsqueda?".</p><p>Para demostrar que la cuantificación de Elastic no degrada los resultados, construimos un marco de pruebas repetible mediante el conjunto de datos de <a href="https://huggingface.co/datasets/fancyzhx/dbpedia_14"><strong>DBPedia-14</strong></a><a href="https://huggingface.co/datasets/fancyzhx/dbpedia_14"></a> para calcular exactamente cuánta precisión intercambiamos (específicamente, la <strong>recuperación)</strong> por velocidad al usar las optimizaciones predeterminadas en Elasticsearch.</p><p>Resumen: es probable que sea mucho menos de lo que piensas. Echa un vistazo al <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/fast_vs_accurate_measuring_the_recall_of_quantized_vector_search/vector_recall_notebook.ipynb">cuaderno aquí</a> e inténtalo por tu cuenta</p><h2><strong>Las definiciones (para los no expertos)</strong></h2><p>Antes de ver el código, establezcamos algunos términos.</p><ul><li><p><strong>Relevancia versus recuperación:</strong> <strong>La relevancia</strong> es subjetiva (¿encontré cosas buenas?). <strong>La recuperación</strong> es matemática. Si hay 10 documentos en la base de datos que son las <em>coincidencias matemáticas perfectas</em> para tu consulta, y el motor de búsqueda encuentra nueve de ellos, tu recuperación es del 90 % (o 0,9).</p></li><li><p><strong>Búsqueda exacta (plana):</strong> A veces denominado el método de "fuerza bruta". El motor de búsqueda analiza cada uno de los documentos de un índice y calcula la distancia.</p><ul><li><p><em>Pros:</em> 100 % de recuperación perfecta.</p></li><li><p><em>Contras:</em> computacionalmente caro y lento en escala.</p></li></ul></li><li><p><strong>Búsqueda aproximada (HNSW):</strong> El método del "atajo". El motor de búsqueda crea un grafo <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">HNSW</a>. Recorre el grafo para encontrar a los vecinos más cercanos.</p><ul><li><p><em>Ventajas:</em> extremadamente rápido y escalable.</p></li><li><p><em>Desventajas:</em> podrías perderte algún vecino si el recorrido del grafo se detiene demasiado pronto.</p></li></ul></li></ul><h2><strong>El experimento: exactitud versus aproximación</strong></h2><p>Para probar la recuperación, usamos el conjunto de datos <strong>DBPedia-14</strong>, un gran set de datos de títulos y resúmenes de 14 clases de ontología que normalmente se utilizan para entrenar y evaluar modelos de categorización de texto. En concreto, nos centraremos en la categoría de "Cine". Queríamos comparar los ajustes de producción optimizados con una verdad de base matemáticamente perfecta.</p><p>Para este experimento, utilizamos el <a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text">modelo jina-embeddings-v5-text-small</a>, un modelo multilingüe de última generación que lidera los estándares de la industria para la representación de texto. Elegimos este modelo porque define el estándar actual para embeddings de alto rendimiento. Al combinar la precisión de élite de Jina v5 con la cuantización nativa de Elasticsearch, podemos demostrar una arquitectura de búsqueda que es tanto computacionalmente eficiente como intransigente en la calidad de la recuperación.</p><p>Configuramos un índice con un mapeo doble. Ingerimos el mismo texto en dos campos diferentes de forma simultánea:</p><ol><li><p><strong><code>content.raw</code></strong>con el tipo: <code>flat</code>. Esto obliga a Elasticsearch a realizar un escaneo por fuerza bruta de los vectores Float32 completos. Esto devuelve resultados de coincidencia exacta y se utilizará para nuestra línea de base.</p></li><li><p><strong><code>content</code></strong>con tipo <code>semantic_text</code>. Con valores predeterminados que utilizan HNSW + la mejor cuantificación binaria (BBQ). Esta es la configuración de producción estándar y optimizada para una coincidencia aproximada.</p></li></ol><h3><strong>La prueba de recall@10</strong></h3><p>Para nuestra métrica, usamos Recall@10.</p><p>Elegimos 50 películas aleatorias y ejecutamos la misma consulta en ambos campos.</p><ul><li><p>Si la búsqueda <strong>exacta (plana)</strong> indica que los 10 vecinos más cercanos son los ID [1, 2, 3... 10].</p></li><li><p>Y la búsqueda <strong>aproximada (HNSW)</strong> devuelve los ID [1, 2, 3... 9, 99].</p></li><li><p>Encontramos nueve de los 10 principales correctamente. La puntuación es <strong>0,9</strong>.</p></li></ul><p>Este es el mapeo que utilizamos:</p># The "Control Group": Forces exact brute-force scan
"raw": {
    "type": "semantic_text",
    "inference_id": ".jina-embeddings-v5-text-small",
    "index_options": {
        "dense_vector": {
            "type": "flat"
        }
    }
}<p><strong>Los resultados: la "línea plana" del éxito</strong></p><p>Realizamos una prueba de escala, recargando el conjunto de datos completo y probando con tamaños de índice de entre 1000 y 40 000 documentos.</p><p>Esto es lo que sucedió con la puntuación de recuperación:</p><p>Documentos</p><p>Puntuación de recall@10</p><p>1000</p><p>1000 (100 %)</p><p>5000</p><p>0,998 (100 %)</p><p>10,000</p><p>0,992 (99,4 %)</p><p>20 000</p><p>0,999 (99,0 %)</p><p>40 000</p><p>0,992 (98,8 %)</p><p>Los resultados fueron increíblemente estables. Incluso a medida que escalábamos, la búsqueda aproximada coincidió con la búsqueda exacta de fuerza bruta <strong>&gt;99 % del tiempo</strong>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8168a0a4946bade7/6a170e154a531b61b536a9eb/a4bfacb1d0cce6fdf6df0e1a9d4fc5d4007a66da-1999x1209.png" alt="Estabilidad de la búsqueda vectorial: recuperación versus tamaño del índice" /><h2><strong>¿Por qué funcionó tan bien?</strong></h2><p>Podrías esperar que comprimir vectores a valores binarios perjudicaría más la precisión. La razón por la que esto no ocurre está en la forma en que Elasticsearch gestiona la recuperación.</p><p>La mayoría de los modelos de incrustación actuales dan como salida vectores Float32, que son grandes. Para hacer la búsqueda eficiente, Elasticsearch emplea cuantización para vectores de alta dimensión. Concretamente, desde la versión 9.2, usa <a href="https://www.elastic.co/search-labs/blog/elasticsearch-9-1-bbq-acorn-vector-search">BBQ</a> por defecto.</p><p>BBQ usa un mecanismo de <strong>recalificación</strong>:</p><ol><li><p><strong>Recorrido:</strong> el motor de búsqueda utiliza los vectores comprimidos (cuantificados) para recorrer rápidamente el grafo HNSW. Como los vectores son pequeños, puedes realizar un sobremuestreo de manera eficiente, recopilando una lista más amplia de candidatos (por ejemplo, los 100 documentos más parecidos) sin que afecte al rendimiento.</p></li><li><p><strong>Recálculo de la puntuación:</strong> una vez que obtiene esos candidatos, recupera los valores de precisión completa solo para esos pocos documentos para calcular la clasificación final y precisa.</p></li></ol><p>Esto te brinda lo mejor de ambos mundos, la velocidad de la cuantización para el trabajo pesado y la precisión de los flotantes para la ordenación final.</p><h2><strong>¿Podemos hacerlo mejor?</strong></h2><p>Cabe destacar que los resultados que vemos aquí utilizan la configuración predeterminada y una muestra aleatoria de datos. Piensa en esto como un punto de partida de alto rendimiento. Aunque Jina v5 es una bestia, estas puntuaciones de recuperación no son una garantía de "talla única" para todos los conjuntos de datos. Cada conjunto de datos tiene sus propias peculiaridades, y aunque sin duda puedes seguir ajustando los parámetros para obtener un mejor rendimiento, siempre debes realizar pruebas con tus propios datos específicos para ver cuál es tu límite.</p><h2><strong>Conclusión</strong></h2><p>Esta es una prueba a muy pequeña escala. Pero el punto del ejercicio no es medir el modelo de incrustación o BBQ específicamente, sino demostrar cómo puedes medir fácilmente la recuperación de tu conjunto de datos con una configuración mínima.</p><p>Si quieres ejecutar esta prueba con tus propios datos, puedes consultar el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/fast_vs_accurate_measuring_the_recall_of_quantized_vector_search/vector_recall_notebook.ipynb">cuaderno aquí</a> e intentarlo por tu cuenta.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/recall-vector-search-quantization</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/recall-vector-search-quantization</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <dc:creator><![CDATA[Jeff Vestal]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt198c7085db96aa04/6a170e17cdacbfe88c7d2a86/09f03b9239d66c36763cdab3fafcdac207ff6d83-1280x720.png" length="0" type="image/png"/>
    <pubDate>Fri, 20 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Terminación temprana adaptativa para HNSW en Elasticsearch]]></title>
    <description><![CDATA[Presentamos una nueva estrategia adaptativa de terminación temprana para HNSW en Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch utiliza el algoritmo <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">Hierarchical Navigable Small World</a> (HNSW) para realizar búsquedas de vectores en un grafo de proximidad. Se sabe que HNSW ofrece un buen equilibrio entre la calidad de los resultados del método k vecino más cercano (KNN) y el costo asociado.</p><p>En HNSW, la búsqueda avanza expandiendo iterativamente los nodos candidatos en el grafo, lo que mantiene un conjunto acotado de vecinos más cercanos descubiertos hasta ahora. Cada expansión tiene un costo (operaciones vectoriales, búsquedas aleatorias en el disco y más), y el beneficio marginal de ese costo tiende a disminuir a medida que avanza la búsqueda.</p><p>Una forma de optimizar el recorrido de grafos de HNSW es dejar de buscar cuando la probabilidad marginal de encontrar nuevos vecinos verdaderos no aumenta. Por esta razón, en <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules#index-dense-vector-hnsw-early-termination">Elasticsearch 9.2</a>, introdujimos un nuevo <a href="https://www.elastic.co/search-labs/blog/hnsw-knn-search-early-termination">mecanismo de terminación temprana</a>. Esto detiene el proceso de búsqueda cuando visitar nodos del grafo no proporciona suficientes vecinos más cercanos nuevos, consecutivamente, para un número fijo de veces.</p><p>En este artículo, te orientamos sobre cómo mejoramos el mecanismo de terminación temprana mencionado en HNSW para hacerlo más adecuado para diferentes sets de datos y distribuciones de datos.</p><h2><strong>Terminación temprana en HNSW</strong></h2><p>En HNSW, la búsqueda avanza expandiendo iterativamente nodos candidatos en el grafo de proximidad, lo que mantiene un conjunto limitado de vecinos más cercanos descubiertos hasta el momento, hasta que haya visitado todo el grafo o cumpla con algunos criterios de terminación temprana.</p><p>Por lo tanto, la terminación temprana no siempre es necesariamente una optimización, sino que es <strong>parte del algoritmo de búsqueda en sí</strong>. El momento en que decidimos detenernos determina el equilibrio entre eficiencia y recuperación. En Elasticsearch, ya hay varias formas en que una consulta en HNSW puede terminar de forma temprana:</p><ul><li><p>Se visita una cantidad máxima fija de nodos.</p></li><li><p>Se alcanza un tiempo de espera fijo.</p></li></ul><p>Si bien son simples y previsibles, estas reglas son, en gran medida, <strong>independientes de lo que realmente está haciendo la búsqueda</strong>. También se emplean, principalmente, para garantizar que la búsqueda finalice en un tiempo razonable para el usuario final.</p><p>En una <a href="https://www.elastic.co/search-labs/blog/hnsw-knn-search-early-termination">entrada anterior del blog</a>, presentamos el concepto de redundancia en HNSW. En resumen, los cálculos redundantes ocurren cuando HNSW continúa evaluando nuevos nodos candidatos que no dan como resultado la búsqueda de más vecinos más cercanos.</p><h2><strong>Paciencia: medir el progreso en lugar del esfuerzo</strong></h2><p>La noción de <em>paciencia</em> replantea la terminación temprana en torno al <strong>progreso, en lugar del esfuerzo</strong>.</p><p>En lugar de preguntar:</p><p>“¿Cuántos pasos hemos dado?”</p><p>La nueva pregunta es:</p><p>“¿Qué cantidad de cómputo aceptamos desperdiciar hasta que perdemos la esperanza?”</p><p>Durante la búsqueda de HNSW, por lo general, la exploración temprana produce mejoras máximas en el conjunto de candidatos top-k. Durante los primeros pasos de la exploración de grafos de HNSW, el conjunto de vecinos se actualiza continuamente a medida que el algoritmo sigue descubriendo vecinos cada vez más cercanos al vector de búsqueda. Con el tiempo, estas mejoras se vuelven más raras a medida que la búsqueda converge. <a href="https://cs.uwaterloo.ca/~jimmylin/publications/Teofili_Lin_ECIR2025.pdf">La terminación basada en la paciencia</a> monitorea este patrón y finaliza la búsqueda una vez que las mejoras han cesado durante un período prolongado.</p><p>En la práctica, al visitar el grafo de HNSW, también calculamos la relación de saturación de la cola al saltar entre nodos candidatos. Esto mide el porcentaje de vecinos más cercanos que se dejaron sin cambios al visitar el nodo del grafo más reciente (o el inverso del número de nuevos vecinos introducidos durante la última iteración). Cuando esa proporción se vuelve demasiado grande para demasiadas iteraciones consecutivas, dejamos de visitar el grafo.</p><p>Conceptualmente, la paciencia trata la búsqueda de HNSW como un <strong>proceso de rendimientos decrecientes</strong>. Cuando los rendimientos se estabilizan, seguir analizando el grafo aporta pocos beneficios.</p><p>Esta estructura es poderosa porque vincula la terminación directamente a <em>resultados observables</em>, en lugar de a límites fijos arbitrarios.</p><p>El beneficio de usar esta técnica inteligente de terminación temprana es que las exploraciones del grafo de HNSW tienden a visitar un número menor de nodos del grafo, mientras mantienen una recuperación relativa casi perfecta.</p><p>Para visualizar esto, podemos graficar la cantidad de recuperación por nodo visitado que obtuvimos con la terminación temprana basada en la paciencia (etiquetada como <em><code>et=static</code></em>), en comparación con el comportamiento predeterminado de HNSW (etiquetado como <em><code>et=no</code></em>) en un par de sets de datos, FinancialQA y Quora, y modelos, JinaV3 y E5-small.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfd0d692b9beb476a/6a170ef4dc55debf0be00e97/a9d07c5153ea64a2426c82487c36846030692bb9-1600x945.png" alt="Terminación temprana adaptativa para HNSW " /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt93509c251a1b641e/6a170ef6dc55dea2b3e00e9b/dac56125c4b16d1b596c9876b6ca9ac7b2dc87fa-1600x944.png" alt="Terminación anticipada adaptativa para HNSW es" /><h2><strong>Umbrales estáticos y dinámicas de HNSW</strong></h2><p>En la práctica, en Elasticsearch esto se implementa utilizando <strong>umbrales estáticos</strong>. Un umbral se refiere al <strong>umbral de saturación</strong>, es decir, la proporción de saturación que consideramos subóptima. El otro umbral se refiere al número de nodos del grafo consecutivos que permitimos que se visiten sin dejar de tener una saturación de cola subóptima: es decir, el <strong>umbral de paciencia.</strong></p><p>Cuando introdujimos esta estrategia de terminación temprana en Elasticsearch 9.2, decidimos optar por valores predeterminados conservadores, para permitir la recuperación tanto como fuera posible, mientras se mejora la latencia y el consumo de memoria. Por esta razón, establecemos el umbral de saturación al 100 % y el umbral de paciencia se establece como un 30 % (limitado) del <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query#knn-query-top-level-parameters:~:text=search%20request%20size.-,num_candidates,-(Optional%2C%20integer)%20The"><em><code>num_candidates</code></em></a> en la búsqueda de KNN.</p><p>En muchas situaciones, estas configuraciones resultaron funcionar bien; sin embargo, dos búsquedas que solicitan el mismo número de vecinos podrían tener comportamientos de convergencia radicalmente diferentes. Algunas búsquedas encuentran vecindarios locales densos y se saturan rápidamente; otras deben atravesar caminos largos y dispersos antes de encontrar candidatos competitivos. Estas últimas resultaron ser las más difíciles de manejar con eficacia.</p><p>Como resultado, a veces notamos lo siguiente:</p><ul><li><p>Sobreexploración para búsquedas sencillas.</p></li><li><p>Terminación prematura para búsquedas difíciles.</p></li></ul><p>Por lo tanto, consideramos que los valores de umbral fijos codifican suposiciones globales sobre la convergencia, mientras que podríamos hacer que HNSW se adapte mejor a diferentes dinámicas.</p><h2><strong>Hacer que la terminación temprana de HNSW sea adaptativa</strong></h2><p>La terminación temprana adaptativa aborda este problema desde otro ángulo. En lugar de imponer umbrales de parada predefinidos, el algoritmo <strong>infiere cuándo detenerse de la propia dinámica de búsqueda</strong>.</p><p>Entonces, en lugar de comparar la proporción de saturación de cola entre dos candidatos consecutivos, decidimos introducir una tasa de descubrimiento suavizada instantánea  (cuántos vecinos nuevos se introdujeron para una búsqueda <em>q</em> en la <em>última visita i</em>) junto con el promedio móvil  y la desviación estándar  de tal tasa de descubrimiento durante la visita del grafo (usando el <a href="https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm">algoritmo de Welford</a>). Estas estadísticas sobre la tasa de descubrimiento se calculan por búsqueda, de modo que esta información pueda utilizarse para decidir diferentes grados de paciencia para cada búsqueda.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbfb1e123f1b026d/6a170ef7cf4f25d9bab2d216/1958be7ca4425ade66eaf621ada3533173183598-694x118.png" alt="" /><p>Los umbrales anteriormente estáticos se vuelven adaptativos a las estadísticas de tasa de descubrimiento: el umbral de saturación se convierte en el promedio móvil más la desviación estándar; mientras tanto, hacemos que la paciencia se adapte y escale inversamente con la desviación estándar.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d4d91f464a9dc9d/6a170ef8d7c0223420de656a/f7ee4a55c24853b657df26052b275e8bd76cf0f9-654x156.png" alt="" /><p>Las reglas de salida anticipada siguen siendo las mismas; la saturación se produce cuando la tasa de descubrimiento instantáneo es menor que el umbral de saturación adaptativa. La visita al grafo se detiene si la saturación persiste durante un número de visitas consecutivas de candidatos que es mayor que la paciencia adaptativa.</p><p>De este modo, obtenemos un comportamiento que no depende del parámetro <em><code>num_candidates</code></em> en la búsqueda de KNN (que puede estar siempre establecido o dejado como predeterminado, independientemente de si sale pronto) y que se adapta mejor dinámicamente a cada consulta y distribución vectorial.</p><p>La recuperación por nodo visitado en FinancialQA y Quora con la estrategia adaptativa (etiquetada como <em><code>et=adaptive</code></em>) reporta una mayor recuperación por nodo visitado, en comparación con la estrategia estática (<em><code>et=static</code></em>) y el comportamiento predeterminado de HNSW (<em><code>et=no</code></em>).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blteab7ba53ae14da0e/6a170ef9961e69e072c4cfd5/2a906997d9a25d74c7038bd9661bc97581e7258e-1600x938.png" alt=" Estrategia adaptativa y el comportamiento predeterminado de HNSW" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6fb9e672d3698200/6a170efb67045b7b2b45c2ab/3a114911e232c351dbb814cea20e8b0f1415a717-1600x925.png" alt="" /><p>La terminación temprana adaptativa está activada de manera predeterminada en Elasticsearch 9.3 para los campos vectoriales densos de HNSW (y, eventualmente, se puede desactivar a través de la <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules#index-dense-vector-hnsw-early-termination">misma configuración de nivel de índice</a>).</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hnsw-elasticsearch-adaptive-early-termination</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hnsw-elasticsearch-adaptive-early-termination</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[Dentro de Elastic]]></category>
    <dc:creator><![CDATA[Tommaso Teofili]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt27b746cc1995e6b7/6a170efda29299de8ad010c6/e6d3186f609dd56dc5ffe33d70fa9e5cfa05b51f-1280x720.png" length="0" type="image/png"/>
    <pubDate>Mon, 02 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[La búsqueda de vectores de Elasticsearch es hasta 8 veces más rápida que OpenSearch]]></title>
    <description><![CDATA[Exploramos los puntos de referencia de búsqueda vectorial con filtrado entre OpenSearch y Elasticsearch, y por qué el rendimiento de la búsqueda vectorial es crucial para los sistemas diseñados en función del contexto.]]></description>
    <content:encoded><![CDATA[<h2>¿Por qué es importante la velocidad de búsqueda para los agentes de IA y la ingeniería de contexto?</h2><p>Nuestros benchmarks en un corpus de documentos de 20M muestran que Elasticsearch ofrece hasta 8 veces más rendimiento que OpenSearch para búsqueda vectorial filtrada, además de lograr mayores Recall@100 en las configuraciones que probamos. La ingeniería de contexto depende de más que la rápida recuperación de vectores. Los equipos también necesitan fuertes controles de relevancia, como búsqueda y filtrado híbridos, simplicidad operativa y rendimiento predecible, a medida que los flujos de trabajo se repiten. Pero como los agentes suelen ejecutar bucles de recuperación y razonamiento muchas veces por cada solicitud, la latencia de la recuperación se convierte en un factor multiplicador, por lo que las mejoras en este aspecto se traducen directamente en una mejor capacidad de respuesta de extremo a extremo y en un menor costo.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9daec868eed84658/6a170bef6234e0c322db1a19/d5a52a07773f0942c2baa732dacfe782aac0f415-1600x683.png" alt="OpenSearch vs. Elasticsearch: prueba de rendimiento para la búsqueda vectorial filtrada" /><p>En la ingeniería de contexto, la recuperación no es un paso único. Los agentes y las aplicaciones ejecutan bucles repetidamente, como recuperar → razonar → recuperar, para refinar consultas, verificar hechos, reunir contexto fundamentado y completar tareas. Este patrón es común en los flujos de trabajo agénticos y en la Retrieval-Augmented Generation (RAG) iterativa. Como la recuperación puede invocarse muchas veces por cada consulta del usuario, agrega demora a la respuesta o aumenta los costos de infraestructura.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt718c72fb858e4c98/6a170bf00e2e496e7241a132/54ac476ff20a3cf93484298c9ae47612c12fc110-800x417.png" alt="La ingeniería de contexto convierte un gran grupo de contexto en una ventana de contexto limitada del LLM." /><h2>¿Por qué es crítico el rendimiento de la búsqueda de vectores?</h2><p></p><p>Imagina un asistente de compras respondiendo la pregunta: "Necesito una mochila de equipaje de mano de menos de $60 que quepa una laptop de 15 pulgadas, sea resistente al agua y pueda llegar para el viernes".</p><p>En producción, el asistente rara vez emite una consulta vectorial y se detiene ahí. Ejecuta un ciclo de recuperación para crear el contexto correcto, y cada paso suele estar limitado por filtros, como disponibilidad, región, promesa de envío, reglas de marca y elegibilidad de políticas.</p><p><strong>Paso 1: Interpretar la intención y traducirla a restricciones.</strong></p><p>El agente convierte la solicitud en filtros estructurados y una consulta semántica, tales como:</p><ul><li><p>Filtros: En stock, entregable al código postal del usuario, entrega antes del viernes, precio inferior a $60, listado válido</p></li><li><p>Consulta vectorial: "Mochila de equipaje de mano computadora portátil de 15 pulgadas resistente al agua"</p></li></ul><p><strong>Paso 2: Recuperar candidatos y luego refinar la selección.</strong></p><p>A menudo repite la recuperación con variaciones para evitar perder buenas coincidencias:</p><ul><li><p>"mochila de viaje de equipaje de mano con funda para computadora portátil"</p></li><li><p>"mochila de viaje resistente al agua de 15 pulgadas"</p></li><li><p>“mochila de cabina ligera”</p></li></ul><p>Cada consulta utiliza los mismos filtros de elegibilidad, porque recuperar elementos irrelevantes o no disponibles es un desperdicio de contexto.</p><p><strong>Paso 3: Expandir para confirmar detalles y reducir el riesgo.</strong></p><p>A continuación, el agente vuelve a consultar para verificar los atributos clave que influyen en la respuesta final:</p><ul><li><p>Palabras utilizadas para describir los materiales y la resistencia al agua</p></li><li><p>Dimensiones y ajuste del compartimento de la computadora portátil</p></li><li><p>Restricciones de la garantía o política de devolución</p></li><li><p>Opciones alternativas si hay poco inventario</p></li></ul><p>Esto es ingeniería de contexto en múltiples pasos: recuperar, razonar, recuperar, ensamblar.</p><h2>¿Por qué la latencia y la recuperación son importantes para la ingeniería de contexto?</h2><p>Estas interacciones pueden implicar decenas de llamadas de recuperación filtradas por sesión de usuario. Eso hace que la latencia por llamada sea un multiplicador directo en el tiempo de respuesta de extremo a extremo, y la baja recuperación obliga a reintentos adicionales o hace que el agente pierda elementos elegibles, lo que degrada la calidad de la respuesta.</p><p>Conclusión: En sistemas diseñados con contexto, los vecinos más cercanos aproximados (ANN, por su sigla en inglés) filtrados no son una sola consulta. Es una operación repetida bajo restricciones, por lo que el rendimiento de la búsqueda vectorial se nota enseguida en la latencia, la capacidad de procesamiento y el costo, incluso cuando el modelo de lenguaje grande (LLM) es el componente más visible.</p><h2>Evaluación comparativa</h2><h3>Resultados</h3><p>En el grafo 2, cada punto representa una configuración de prueba. Los mejores resultados aparecen hacia la parte superior izquierda, lo que significa una mayor recuperación con menor latencia. Los resultados de Elasticsearch se sitúan sistemáticamente más cerca de la esquina superior izquierda que los de OpenSearch, lo que indica una mayor velocidad y precisión con los mismos ajustes de carga de trabajo.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb562b30a600cc8e1/6a170bf2cf4f253582b2d1b2/c50d1df00968cac18149a2799e6242fbe49b66a0-1600x990.png" alt=" Grafo 2: Recuperación versus latencia promedio (recalificación 1)." /><h4>Algunas ideas clave</h4><ul><li><p><code>s_n_r_value</code>: La abreviatura de <code>size_numCandidates_rescoreOversample</code> (k y numCandidates iguales a numCandidates en estas pruebas), por ejemplo, <code>100_500_1</code> significa tamaño=100, numCandidates=500 y k=500, rescore oversample=1</p></li><li><p>Recuperación: Mide Recall@100 para esa configuración</p></li><li><p>Latencia promedio (ms): Latencia de extremo a extremo promedio por consulta</p></li><li><p>Rendimiento: Búsquedas por segundo</p></li><li><p>Recall %: Mejora relativa de recuperación de Elasticsearch frente a OpenSearch (Elasticsearch menos OpenSearch)/OpenSearch</p></li><li><p>Latencia Xs: Latencia promedio de OpenSearch dividida por la latencia media de Elasticsearch</p></li><li><p>Rendimiento Xs: rendimiento de Elasticsearch dividido por el rendimiento de OpenSearch</p></li></ul><p>Motor</p><p>'s_n_r_value'</p><p>Recuperación</p><p>Latencia promedio (ms)</p><p>Rendimiento</p><p>Porcentaje de recuperación</p><p>Latencia Xs</p><p>Rendimiento Xs</p><p>Elasticsearch</p><p>100_250_1</p><p>0.7704</p><p>25</p><p>534.75</p><p>9.70 %</p><p>2.28</p><p>1.91</p><p>OpenSearch</p><p>100_250_1</p><p>0.7023</p><p>57.08</p><p>279.58</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_500_1</p><p>0.8577</p><p>25.42</p><p>524.14</p><p>7.20 %</p><p>2.4</p><p>2</p><p>OpenSearch</p><p>100_500_1</p><p>0.8001</p><p>60.9</p><p>262.12</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_750_1</p><p>0.8947</p><p>29.67</p><p>528.09</p><p>5.72 %</p><p>2.25</p><p>2.21</p><p>OpenSearch</p><p>100_750_1</p><p>0.8463</p><p>66.76</p><p>239.11</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_1000_1</p><p>0.9156</p><p>29.65</p><p>534.5</p><p>4.66 %</p><p>2.46</p><p>2.44</p><p>OpenSearch</p><p>100_1000_1</p><p>0.8748</p><p>72.88</p><p>219.01</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_1500_1</p><p>0.9386</p><p>31.84</p><p>497.3</p><p>3.38 %</p><p>2.71</p><p>2.68</p><p>OpenSearch</p><p>100_1500_1</p><p>0.9079</p><p>86.16</p><p>185.4</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_2000_1</p><p>0,9507</p><p>34.69</p><p>457.2</p><p>2.57 %</p><p>2.98</p><p>2.96</p><p>OpenSearch</p><p>100_2000_1</p><p>0.9269</p><p>103.36</p><p>154.55</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_2500_1</p><p>0.9582</p><p>37.9</p><p>418.43</p><p>1.99 %</p><p>3.28</p><p>3.26</p><p>OpenSearch</p><p>100_2500_1</p><p>0.9395</p><p>124.29</p><p>128.53</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_3000_1</p><p>0.9636</p><p>41.86</p><p>379.4</p><p>1.62 %</p><p>3.46</p><p>3.44</p><p>OpenSearch</p><p>100_3000_1</p><p>0.9482</p><p>144.67</p><p>110.34</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_4000_1</p><p>0.9705</p><p>50.28</p><p>316.21</p><p>1,06%</p><p>3.87</p><p>3.85</p><p>OpenSearch</p><p>100_4000_1</p><p>0.9603</p><p>194.36</p><p>82.22</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_5000_1</p><p>0.9749</p><p>58.77</p><p>270.91</p><p>0.73 %</p><p>4.43</p><p>4.41</p><p>OpenSearch</p><p>100_5000_1</p><p>0.9678</p><p>260.33</p><p>61.38</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_6000_1</p><p>0.9781</p><p>66.75</p><p>238.59</p><p>0.52 %</p><p>4.91</p><p>4.89</p><p>OpenSearch</p><p>100_6000_1</p><p>0.973</p><p>327.44</p><p>48.81</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_7000_1</p><p>0.9804</p><p>74.64</p><p>213.49</p><p>0.38 %</p><p>5.28</p><p>5.27</p><p>OpenSearch</p><p>100_7000_1</p><p>0.9767</p><p>394.24</p><p>40.53</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_8000_1</p><p>0.9823</p><p>82.28</p><p>193.59</p><p>0.27 %</p><p>6.86</p><p>6.83</p><p>OpenSearch</p><p>100_8000_1</p><p>0.9797</p><p>564.14</p><p>28.33</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_9000_1</p><p>0.9837</p><p>90.08</p><p>176.96</p><p>0.16 %</p><p>7.63</p><p>7.61</p><p>OpenSearch</p><p>100_9000_1</p><p>0.9821</p><p>687.25</p><p>23.25</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_10000_1</p><p>0.9848</p><p>97.64</p><p>163.31</p><p>0.08 %</p><p>8.38</p><p>8.36</p><p>OpenSearch</p><p>100_10000_1</p><p>0.984</p><p>818.64</p><p>19.53</p><p></p><p></p><p></p><p>Por ejemplo, en <code>100_9000_1</code>, OpenSearch tiene un promedio de 687 milisegundos por recuperación frente a 90 milisegundos en Elasticsearch, y en un bucle de recuperación de 10 pasos eso equivale a aproximadamente 10 × (687 - 90) = seis segundos de tiempo de espera adicional. </p><p>Consulta los <a href="https://github.com/elastic/competitive-benchmarking-studies/tree/main/es-9.3-vs-os-3.5-vector-search/jingra/results/20260220">resultados completos</a>.</p><h3>Metodología</h3><p>Al usar Python para enviar las consultas y rastrear el tiempo de respuesta y otras estadísticas, enviamos las siguientes consultas a los motores. Ten en cuenta que el rendimiento de cualquier motor de búsqueda vectorial depende de cómo ajustes sus parámetros núcleo: cuántos candidatos considerar, cuán agresivamente volver a puntuar y cuánto contexto devolver. Estos ajustes afectan directamente tanto la exhaustividad (la probabilidad de encontrar la respuesta correcta) como la latencia (la rapidez con la que obtienes los resultados).</p><p>En nuestras pruebas comparativas, empleamos la misma configuración de candidatos, repuntuación y tamaño de resultados que normalmente ajustarías en un bucle de recuperación basado en agentes, y medimos el rendimiento de Elasticsearch bajo esa carga de trabajo. Luego ejecutamos OpenSearch con la misma configuración como referencia.</p><p>OpenSearch</p>GET &lt;INDEX_NAME&gt;/_search
{
  "query": {
    "knn": {
      "&lt;DENSE_VECTOR_FIELD_NAME&gt;": {
        "vector": [...],
        "k": &lt;NUMBER_OF_CANDIDATES&gt;,
        "method_parameters": {
          "ef_search": &lt;NUMBER_OF_CANDIDATES&gt;
        },
        "rescore": {
          "oversample_factor": &lt;OVERSAMPLE&gt;
        },
        "filter": {
          &lt;SOME_FILTER&gt;
        }
      }
    }
  },
  "size": &lt;RESULT_SIZE&gt;,
  "_source": {
    "excludes": [
      "&lt;DENSE_VECTOR_FIELD_NAME&gt;"
    ]
  }
}<ul><li><p><code>"size": &lt;RESULT_SIZE&gt;</code>: Número de resultados devueltos al cliente. En esta prueba de rendimiento, el tamaño del conjunto de datos es 100 para calcular el Recall@100.</p></li><li><p><code>"k": &lt;NUMBER_OF_CANDIDATES&gt;</code>: El número de candidatos a vecinos más cercanos.</p></li><li><p><code>"ef_search": &lt;NUMBER_OF_CANDIDATES&gt;</code>: El número de vectores a examinar.</p></li><li><p><code>"oversample_factor": &lt;OVERSAMPLE&gt;</code>: ¿Cuántos vectores candidatos se recuperan antes de volver a calcular la puntuación?</p></li></ul><p>Elasticsearch</p>GET &lt;INDEX_NAME&gt;/_search
{
  "query": {
    "knn": {
      "field": "&lt;DENSE_VECTOR_FIELD_NAME&gt;",
      "query_vector": [...],
      "k": &lt;NUMBER_OF_CANDIDATES&gt;,
      "num_candidates": &lt;NUMBER_OF_CANDIDATES&gt;,
      "rescore_vector": {
        "oversample": &lt;OVERSAMPLE&gt;
      },
      "filter": {
        &lt;SOME_FILTER&gt;
      }
    }
  },
  "size": &lt;RESULT_SIZE&gt;,
  "_source": {
    "excludes": [
      "&lt;DENSE_VECTOR_FIELD_NAME&gt;"
    ]
  }
}<ul><li><p><code>"size": &lt;RESULT_SIZE&gt;</code>: Número de resultados devueltos al cliente. En esta prueba de rendimiento, el tamaño del conjunto de datos es 100 para calcular el Recall@100.</p></li><li><p><code>"k": &lt;NUMBER_OF_CANDIDATES&gt;</code>: Número de vecinos más cercanos que se debe devolver desde cada shard.</p></li><li><p><code>"num_candidates": &lt;NUMBER_OF_CANDIDATES&gt;</code>: Número de candidatos de vecinos más cercanos a considerar por shard mientras se realiza la búsqueda de <code>knn</code>.</p></li><li><p><code>"oversample": &lt;OVERSAMPLE&gt;</code>: ¿Cuántos vectores candidatos se recuperan antes de volver a calcular la puntuación?</p></li></ul><p>Ejemplo</p><p><code>Knn</code> la búsqueda, (<code>100_500_1</code>), sería de la siguiente manera:</p><p>OpenSearch</p>GET search_catalog_128/_search
{
  "query": {
    "knn": {
      "search_catalog_embedding": {
        "vector": [...],
        "k": 500,
        "method_parameters": {
          "ef_search": 500
        },
        "rescore": {
          "oversample_factor": 1
        },
        "filter": {
          "term": {
            "valid": true
          }
        }
      }
    }
  },
  "size": 100,
  "_source": {
    "excludes": [
      "search_catalog_embedding"
    ]
  }
}<p>Elasticsearch</p>GET search_catalog_128/_search
{
  "query": {
    "knn": {
      "field": "search_catalog_embedding",
      "query_vector": [...],
      "k": 500,
      "num_candidates": 500,
      "rescore_vector": {
        "oversample": 1
      },
      "filter": {
        "term": {
          "valid": true
        }
      }
    }
  },
  "size": 100,
  "_source": {
    "excludes": [
      "search_catalog_embedding"
    ]
  }
}<p>La configuración completa, junto con scripts de Terraform, manifiestos de Kubernetes y el código de benchmarking, está disponible en este <a href="https://github.com/elastic/competitive-benchmarking-studies">repositorio</a> en la carpeta <a href="https://github.com/elastic/competitive-benchmarking-studies/tree/main/es-9.3-vs-os-3.5-vector-search">es-9.3-vs-os-3.5-vector-search</a>.</p><h3>La configuración del cluster</h3><p>Ejecutamos nuestras pruebas en seis servidores cloud e2-standard-16, cada uno con 16 vCPUs y 64 GB de RAM. En cada servidor, asignamos 15 vCPUs y 56 GB de RAM a cada pod de Kubernetes que ejecutaba el nodo del motor de búsqueda, con 28 GB reservados para el heap de JVM.</p><p>Los clústeres ejecutaban Elasticsearch 9.3.0 y OpenSearch 3.5.0 (Lucene 10.3.2). Dado que ambos sistemas emplean la misma versión de Lucene en esta prueba comparativa, las diferencias de rendimiento y latencia que observamos no pueden atribuirse únicamente a Lucene, sino que reflejan diferencias en la forma en que cada motor integra y ejecuta la recuperación y recalculación filtradas del algoritmo k-vecinos más cercanos (kNN). Usamos un único índice con tres shards primarios y una réplica (es decir, 6 shards en total, 1 por nodo).</p><p>También usamos un servidor independiente en la misma región para ejecutar el cliente de pruebas de rendimiento y recopilar estadísticas de tiempos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7c7bdd567395d5b7/6a170bf3c1e8a56ee2f882f6/f81002c9186e4c2d3e92f49d72418fee9860fc5e-761x401.png" alt="Configuración del clúster para las pruebas de rendimiento de Elasticsearch y OpenSearch" /><h3>El set de datos</h3><p></p><p>Para este benchmark, empleamos un set de datos de incrustación de catálogos de tipo comercio electrónico a gran escala con 20 millones de documentos, diseñado para reflejar la recuperación vectorial filtrada a escala del mundo real.</p><p></p><p>Cada documento representa un artículo del catálogo e incluye:</p><p></p><ul><li><p>Un vector denso incrustado de 128 dimensiones utilizado para la recuperación aproximada de kNN.</p></li><li><p>Campos estructurados de metadatos usados para filtrar (por ejemplo, validez y disponibilidad de artículos más otras restricciones del catálogo) que permiten el patrón común de producción de recuperar a los vecinos más cercanos, pero solo dentro de un subconjunto elegible.</p></li></ul><p></p><p>Elegimos este set de datos porque captura el núcleo del desafío principal de rendimiento que vemos en sistemas agentes y de estilo RAG en producción: la similitud vectorial por sí sola no es suficiente, la recuperación está frecuentemente limitada por filtros y el sistema debe mantener una alta recuperación a la vez que mantiene baja la latencia bajo esas restricciones. En comparación con sets de datos más pequeños de estilo QA, un corpus de 20M de documentos también refleja mejor la escala y la presión de los candidatos que enfrentan los sistemas de ANN filtrados en la práctica.</p><h2>Conclusión</h2><p>En las arquitecturas de IA modernas, especialmente aquellas construidas alrededor de la ingeniería de contexto, la velocidad de búsqueda vectorial no es un detalle de implementación menor. Es un multiplicador. Cuando los agentes y los flujos de trabajo iteran a través de recuperar → razonar → recuperar, el rendimiento de la recuperación da forma directamente a la latencia de extremo a extremo, al rendimiento y a la calidad del contexto que se introduce en el modelo.</p><p>En nuestras pruebas de referencia, Elasticsearch ofreció consistentemente una mayor recuperación con menor latencia que OpenSearch en escenarios donde la corrección depende de recuperar el documento correcto, no solo de un vector similar. En un set de datos controlado, la diferencia es clara, y en producción esos avances se acumulan a lo largo de grandes volúmenes de llamadas de recuperación, lo que mejora la capacidad de respuesta, aumenta el margen de capacidad y reduce los costos de infraestructura.</p><h3>Lecturas adicionales</h3><ol><li><p><a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">¿Qué es la ingeniería de contexto?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/series/context-engineering-hybrid-search-evolution">La evolución de la búsqueda híbrida y la ingeniería de contexto</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/context-engineering-relevance-ai-agents-elasticsearch">El impacto de la relevancia en la ingeniería de contexto para agentes de IA</a></p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/opensearch-vs-elasticsearch-filtered-vector-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/opensearch-vs-elasticsearch-filtered-vector-search</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <dc:creator><![CDATA[Sachin Frayne]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4b38e5114bbf098c/6a170bf560084b3a6c3c459d/fb7ee623925ca6696d643e437ce8efe5fe749079-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Indexación vectorial hasta 12 veces más rápida en Elasticsearch con NVIDIA cuVS: aceleración por GPU, capítulo 2]]></title>
    <description><![CDATA[Descubre cómo Elasticsearch logra un rendimiento de indexación casi 12 veces mayor. Esto lo consigue con la indexación vectorial acelerada por GPU y NVIDIA cuVS.]]></description>
    <content:encoded><![CDATA[<p>A principios de este año, Elastic anunció la <a href="https://ir.elastic.co/news/news-details/2025/Elastic-Brings-Enterprise-Data-to-NVIDIA-AI-Factories/default.aspx">colaboración</a> con NVIDIA para llevar la aceleración por GPU a Elasticsearch, integrándola con <a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS</a>, como se detalló en una <a href="https://www.nvidia.com/en-us/on-demand/session/gtc25-S71286/">sesión en NVIDIA GTC</a> y en varios <a href="https://www.elastic.co/search-labs/blog/gpu-accelerated-vector-search-elasticsearch-nvidia">blogs</a>. Esta publicación es una actualización sobre el esfuerzo de co-ingeniería con el equipo de búsqueda de vectores de NVIDIA.</p><h2>Resumen</h2><p>Primero, pongámonos al día. Elasticsearch se consolidó como una poderosa base de datos vectorial, que ofrece un amplio conjunto de características y un sólido rendimiento para la búsqueda de similitudes a gran escala. Con capacidades como la cuantificación escalar, Better Binary Quantization (<a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">BBQ</a>), operaciones vectoriales <a href="https://www.elastic.co/blog/accelerating-vector-search-simd-instructions">SIMD</a> y algoritmos más eficientes en disco como <a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction">DiskBBQ</a>, ya ofrece opciones eficientes y flexibles para gestionar cargas de trabajo vectoriales.</p><p>Al integrar NVIDIA cuVS como un módulo invocable para tareas de búsqueda vectorial, nuestro objetivo es ofrecer beneficios significativos en el rendimiento y la eficiencia de la indexación vectorial para soportar mejor las cargas de trabajo vectoriales a gran escala.</p><h2>El desafío</h2><p>Uno de los mayores desafíos al construir una base de datos vectorial de alto rendimiento es construir el índice vectorial: el grafo <a href="https://arxiv.org/abs/1603.09320">HNSW</a>. La construcción de índices se ve dominada con rapidez por millones o incluso miles de millones de operaciones aritméticas a medida que cada vector se compara con muchos otros. Además, las operaciones del ciclo de vida del índice, como la compactación y las fusiones, pueden aumentar aún más la sobrecarga total de cálculo de la indexación. A medida que los volúmenes de datos y las incrustaciones vectoriales asociadas crecen de manera exponencial, las GPU de cómputo acelerado, diseñadas para el paralelismo masivo y las matemáticas de alto rendimiento, se posicionan idealmente para manejar estas cargas de trabajo.</p><h2>Ingresa al plugin Elasticsearch-GPU</h2><p><a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS</a> es una biblioteca open source de CUDA-X para la búsqueda vectorial acelerada por GPU y la agrupación de datos, que permite una rápida construcción de índices y recuperación de incrustaciones para cargas de trabajo de AI y de sistemas de recomendación.</p><p>Elasticsearch utiliza cuVS a través de <a href="https://mvnrepository.com/artifact/com.nvidia.cuvs/cuvs-java">cuvs-java</a>, una biblioteca open source desarrollada por la comunidad y que mantiene NVIDIA. La biblioteca cuvs-java es ligera, se basa en la <a href="https://docs.rapids.ai/api/cuvs/nightly/c_api/">API C de cuVS</a> y usa la función externa del <a href="https://openjdk.org/projects/panama/">Proyecto Panamá</a> para exponer las características de cuVS de una manera idiomática en Java, al tiempo que es moderna y presenta un alto rendimiento.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc7fd7361099da05a/6a17e920be608670af00477f/5f6daa1eb07f704a6707d9e6b7ccb81d0abaa8c9-566x419.png" alt="Cómo funciona Elasticsearch con NVIDIA cuVS, CPU e indexación con GPU" /><p>La biblioteca cuvs-java está integrada en un <a href="https://github.com/elastic/elasticsearch/pull/135545">nuevo plugin de Elasticsearch</a>; por lo tanto, la indexación vectorial en la GPU puede ocurrir en el mismo nodo y proceso de Elasticsearch, sin la necesidad de provisionar ningún código o hardware externo. Durante la construcción del índice, si se instala la biblioteca cuVS y hay una GPU presente y configurada, Elasticsearch usará la GPU para acelerar el proceso de indexación vectorial. Los vectores se asignan a la GPU, que construye un grafo <a href="https://arxiv.org/abs/2308.15136">CAGRA</a>. Este grafo se convierte entonces al formato HNSW, y hace que esté disponible de inmediato para la búsqueda vectorial en la CPU. El formato final del grafo construido es el mismo que el que se construiría en la CPU; esto permite a Elasticsearch aprovechar las GPU para indexar vectores de alto rendimiento cuando el hardware subyacente lo admite, al tiempo que libera poder de la CPU para otras tareas (búsqueda simultánea, procesamiento de datos, etc.).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt485f55f29d6df5c4/6a17e922be6086dcf3004785/3ea255bd9bfd7983f78143c5eba999d2149d72be-671x356.png" alt="" /><h2>Aceleración en la generación de índices</h2><p>Como parte de la integración de la aceleración de GPU en Elasticsearch, se realizaron varias mejoras en cuvs-java, centradas en la entrada/salida eficiente de datos y la invocación de funciones. Una mejora clave es el uso de <a href="https://github.com/rapidsai/cuvs/blob/2cf5fa7666d703dccbe655f8214656b0952bb69b/java/cuvs-java/src/main/java/com/nvidia/cuvs/CuVSMatrix.java">cuVSMatrix</a> para modelar vectores de forma transparente, ya sea que residan en la memoria heap de Java, fuera de la memoria heap o en la memoria de la GPU. Esto permite que los datos se muevan de manera eficiente entre la memoria y la GPU, lo que evita copias innecesarias de potencialmente miles de millones de vectores.</p><p>Gracias a esta abstracción subyacente de copia cero, tanto la transferencia a la memoria de la GPU como la recuperación del grafo se pueden realizar directamente. Durante la indexación, los vectores se almacenan primero en búfer en la memoria heap de Java, y luego se envían a la GPU para construir el grafo CAGRA. Después, el grafo se recupera de la GPU, se convierte al formato HNSW y se mantiene en el disco.</p><p>En el momento de la fusión, los vectores ya están almacenados en disco, sin pasar por la memoria heap de Java. Los archivos de índice se mapean en memoria, y los datos se transfieren directamente a la memoria de la GPU. El diseño también se adapta fácilmente a diferentes anchos de bits, como float32 o int8, y se extiende de forma natural a otros esquemas de cuantificación.</p><h2>Redoble de tambores… entonces, ¿cómo se funciona?</h2><p>Antes de entrar en los números, un poco de contexto es útil. La fusión de segmentos en Elasticsearch suele ejecutarse automáticamente en segundo plano durante la indexación, lo que dificulta hacer benchmarks de forma aislada. Para obtener resultados reproducibles, utilizamos la fusión forzosa para activar explícitamente la combinación de segmentos en un experimento controlado. Dado que la fusión forzosa hace las mismas operaciones de fusión subyacentes que la fusión en segundo plano, su rendimiento sirve como un indicador útil de las mejoras esperadas, aunque las ganancias exactas pueden diferir en las cargas de trabajo de indexación del mundo real.</p><p>Ahora, veamos los números.</p><p>Nuestros resultados iniciales de evaluaciones comparativas son muy prometedores. Ejecutamos la evaluación comparativa en una instancia de AWS <code>g6.4xlarge</code> con almacenamiento NVMe conectado a nivel local. Se configuró un único nodo de Elasticsearch para que use el número predeterminado y óptimo de subprocesos de indexación (8, uno por cada núcleo físico) y deshabilite <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/merge">la limitación de la fusión</a> (que es menos aplicable con discos NVMe rápidos).</p><p>Para el conjunto de datos, utilizamos 2,6 millones de vectores con 1536 dimensiones del <a href="https://github.com/elastic/rally-tracks/blob/master/openai_vector/README.md">vector de pista de Rally de OpenAI</a>, codificados como <a href="https://github.com/elastic/elasticsearch/pull/137072">cadenas de texto base64</a> e indexados como float32 <em>hnsw</em>. En todos los escenarios, los grafos construidos alcanzan niveles de recuperación de hasta el 95 %. A continuación, presentamos nuestros hallazgos:</p><ul><li><p><strong>Rendimiento de indexación:</strong> al trasladar la construcción de grafos a la GPU durante los vaciados de búferes en memoria, aumentamos el rendimiento en aproximadamente 12 veces.</p></li><li><p><strong>Fusión forzada:</strong> una vez que finaliza la indexación, la GPU continúa acelerando la fusión de segmentos, lo que acelera la fase de fusión forzada en alrededor de 7 veces.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfea4ee13b5a3b10d/6a17e923e9ea879c6aa9c616/f60ea9ee5996e456f393ffd195ee7eada6e5a7c2-948x387.png" alt="" /><ul><li><p><strong>Uso de la CPU:</strong> descargar la construcción de grafos a la GPU reduce de manera significativa el uso promedio y máximo de la CPU. Los grafos a continuación ilustran el uso de la CPU durante la indexación y la fusión, y permiten ver cuánto menor es cuando estas operaciones se ejecutan en la GPU. Un menor uso de la CPU durante la indexación por GPU libera ciclos de CPU que pueden redirigirse para mejorar el rendimiento de la búsqueda.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt80ff9c53f9b6884a/6a17e925445de9ee4b4d0187/5e680a5fc41700a877f3d8b2e5ce18ebd3f37a0b-1600x562.png" alt="" /><ul><li><p><strong>Recuperación:</strong> la precisión se mantiene prácticamente igual entre las ejecuciones de CPU y GPU, y el grafo construido por la GPU alcanza una recuperación un poco mayor.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5cbe084eca27b8e4/6a17e926faa913317093c8b7/48a2b7758606bd321712b7d8378cd2640e652a4e-1384x544.png" alt="" /><h2>Comparación en otra dimensión: el precio</h2><p>La comparación anterior utilizaba intencionalmente hardware idéntico, y la única diferencia era si se usaba la GPU durante la indexación o no. Esa configuración es útil para aislar los efectos de la computación en bruto, pero también podemos analizar la comparación desde una perspectiva de costos.</p><p>A un precio horario similar al de la configuración acelerada por GPU, se puede provisionar una configuración de solo CPU con aproximadamente el doble de recursos comparables de CPU y memoria: 32 vCPUs (AMD EPYC) y 64 GB de RAM, lo que permite duplicar el número de hilos de indexación a 16.</p><p>Para mantener la comparación justa y consistente, ejecutamos este experimento solo de CPU en una instancia AWS g6.8xlarge, con la GPU explícitamente desactivada. Esto nos permitió mantener constantes todas las demás características del hardware mientras evaluábamos la compensación entre costo y rendimiento de la aceleración de GPU frente al indexado solo con CPU.</p><p>Como era de esperar, la instancia de CPU más potente sí muestra un mejor rendimiento en comparación con las evaluaciones comparativas de la sección anterior. Sin embargo, cuando comparamos esta instancia de CPU más potente con los resultados acelerados por GPU originales, la GPU aún ofrece beneficios sustanciales en cuanto al rendimiento: <strong>~ 5 veces</strong> de mejora en el rendimiento de indexación y <strong>~ 6 veces </strong>en la fusión forzada, todo ello mientras se construyen grafos que alcanzan niveles de recuperación de hasta un <strong>95 %.</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt94b5eb6f95ba307d/6a17e928abe0f255d4dfea35/8ffa58cae3ad175ef2932a351aeef4c34a1407b9-948x394.png" alt="" /><h2>Conclusión</h2><p>En escenarios completos, la aceleración por GPU con NVIDIA cuVS ofrece una mejora de casi 12 veces en el rendimiento de la indexación y una disminución de 7 veces en la latencia de fusión forzada, con un uso de CPU significativamente menor. Esto demuestra que la indexación vectorial y las cargas de trabajo de fusión se benefician de manera significativa de la aceleración por GPU. En una comparación ajustada por costos, la aceleración por GPU continúa mostrando beneficios sustanciales en cuanto al rendimiento, con alrededor de 5 veces más rendimiento de indexación y 6 veces más rapidez en las operaciones de fusión forzada.</p><p>La indexación vectorial acelerada por GPU tiene planificada hoy en día una vista previa técnica en Elasticsearch 9.3, cuyo lanzamiento está programado para principios de 2026.</p><p>Mantente atento a lo que viene.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-gpu-accelerated-vector-indexing-nvidia</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-gpu-accelerated-vector-indexing-nvidia</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <dc:creator><![CDATA[Chris Hegarty,Hemant Malik,Corey Nolet,Manas Singh,Mithun Radhakrishnan,Mayya Sharipova,Lorenzo Dematte,Ben Frederickson]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1248d51633bd75d9/6a17e92ae9ea8714b3a9c61a/08f7469a4daaf67b7c5999585aae179b6680c78d-896x746.png" length="0" type="image/png"/>
    <pubDate>Wed, 03 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Búsqueda multimodal de picos montañosos con Elasticsearch y SigLIP-2 ]]></title>
    <description><![CDATA[Aprende a implementar búsqueda multimodal texto a imagen e imagen a imagen usando incrustaciones SigLIP-2 y búsqueda vectorial kNN en Elasticsearch. Enfoque del proyecto: encontrar fotos del pico del Monte Ama Dablam durante una travesía por el Everest.]]></description>
    <content:encoded><![CDATA[<p>¿Alguna vez quisiste buscar en tu álbum de fotos por significado? Prueba con preguntas como "muéstrame mis fotos donde llevo una chaqueta azul y estoy sentado en un banco", "muéstrame fotos del Monte Everest" o "sake y sushi". Toma una taza de café (o tu bebida favorita) y sigue leyendo. En este blog, te mostramos cómo construir una aplicación de búsqueda híbrida multimodal. Multimodal significa que la app puede entender y buscar entre diferentes tipos de entradas—texto, imágenes y audio—no solo palabras. Híbrido significa que combina técnicas como la coincidencia de palabras clave, la búsqueda vectorial kNN y el geofencing para ofrecer resultados más precisos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa1ec1ccd450e94/6a17da751d1b8308ee93e344/0ec6bbb45013846b59ee00d2bf73ee2182ee7392-1920x1080.gif" alt="Recopilatorio de diferentes fotos de las cumbres de montaña de la ruta al Everest." /><p>Para lograrlo, empleamos SigLIP-2 de Google para generar incrustaciones vectoriales tanto para imágenes como para texto, y las almacenamos en la base de datos vectorial Elasticsearch. En el momento de la consulta, convertimos la entrada de búsqueda, texto o imagen, en incrustaciones y realizamos búsquedas rápidas con vectores kNN para obtener resultados. Esta configuración permite una búsqueda eficiente de texto a imagen y de imagen a imagen. Una interfaz Streamlit da vida a este proyecto proporcionándonos una interfaz no solo para hacer búsquedas por texto para encontrar y ver las fotos coincidentes del álbum, sino también para identificar la cima de la montaña a partir de la imagen subida y ver otras fotos de esa montaña en el álbum.
También cubrimos los pasos que seguimos para mejorar la precisión de las búsquedas, junto con consejos y trucos prácticos. Para una exploración más profunda, proporcionamos un <a href="https://github.com/navneet83/multimodal-mountain-peak-search">repositorio de GitHub</a> y un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">cuaderno de Colab</a>.</p><h2>Cómo empezó todo</h2><p>Esta entrada del blog fue inspirada por un niño de 10 años que me pidió que les mostrara todas las fotos del Monte Ama Dablam de mi travesía al campamento base del Everest. Mientras revisábamos el álbum de fotos, también me pidieron que identificara varias otras cumbres montañosas, algunas de las cuales no podía nombrar.</p><p>Eso me dio la idea de que esto puede ser un proyecto divertido de visión por computadora. Lo que queríamos conseguir:</p><ul><li><p>Encuentra fotos de un pico montañoso por nombre</p></li><li><p>Adivina el nombre de la cima de la montaña a partir de una imagen y también encuentra picos similares en el álbum de fotos</p></li><li><p>Haz que las consultas conceptuales funcionen (<em>persona</em>, <em>río</em>, <em>banderas de oración</em>, <em>etc.)</em></p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf82df9d7005fc3fe/6a17da78abe0f2e77bdfe8b9/e9d0d720a9b565d5b749bdc915068852d4f157ad-1200x1600.png" alt="Monte Ama Dablam " /><h2>Formando el equipo soñado: SigLIP-2, Elasticsearch y Streamlit</h2><p>Pronto quedó claro que, para que esto funcionara, tendríamos que convertir tanto el texto ("Ama Dablam") como las imágenes (fotos de mi álbum) en vectores que puedan comparar de forma significativa, es decir, en el mismo espacio vectorial. Una vez que hacemos eso, la búsqueda es simplemente "encontrar a los vecinos más cercanos".</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f80b69a9d5bd28a/6a17da7a4b055ddd1243209e/20e6f8b7d4fa48414f407ec200adbe00ee28d517-1536x1024.png" alt="SigLIP-2, Elasticsearch y Streamlit: equipo de ensueño." /><p>Para generar incrustaciones de imágenes, usamos un<a href="https://huggingface.co/blog/vlms-2025"> codificador multilingüe visión-lenguaje</a>, de modo que una foto de una montaña y una frase como "Ama Dablam" caen en el mismo espacio vectorial.</p><p><a href="https://huggingface.co/blog/siglip2"><strong>SigLIP-2</strong></a>, lanzado recientemente por Google, encaja bien aquí. Puede generar incrustaciones sin entrenamiento específico de tarea (un ajuste <strong>de cero disparos</strong> ) y funciona bien para nuestro caso: fotos sin etiqueta y picos con diferentes nombres e idiomas. Como está capacitado para la coincidencia de imágenes de texto ↔, una foto de montaña de la travesía y un breve prompt de texto acaban siendo similares a incrustaciones, incluso cuando el idioma de consulta o la ortografía varían.</p><p>SigLIP-2 ofrece un fuerte equilibrio calidad-velocidad, soporta múltiples resoluciones de entrada y funciona tanto en CPU como en GPU. El SigLIP-2 está diseñado para ser más robusto para fotos exteriores en comparación con modelos anteriores como el CLIP original. Durante nuestras pruebas, SigLIP-2 generó resultados fiables de forma constante. Además, está muy bien apoyado, lo que lo convierte en la opción obvia para este proyecto.</p><p>A continuación, necesitamos una base de datos vectorial para almacenar los embebidos y la búsqueda de potencia. Debe soportar no solo búsqueda kNN coseno sobre incrustaciones de imágenes, sino también aplicar filtros de geocerca y texto en una sola consulta. Elasticsearch encaja bien aquí: maneja vectores (HNSW kNN en campos dense_vector), soporta búsqueda híbrida que combina texto, vectores y consultas geográficas, y ofrece filtrado y ordenación desde el principio. Además, escala horizontalmente, lo que facilita crecer de unas pocas fotos a miles. El cliente oficial <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/python">de Python de Elasticsearch</a> mantiene la fontanería sencilla y se integra perfectamente con el proyecto. Por último, necesitamos un frontend ligero donde podamos introducir consultas de búsqueda y ver resultados. Para una demostración rápida basada en Python, Streamlit es una opción ideal. Proporciona las primitivas que necesitamos: carga de archivos, una cuadrícula de imágenes responsiva y menús desplegables para ordenar y geovaller. Es fácil de clonar y ejecutar localmente, y también funciona en un cuaderno de Colab.</p><h2>Implementación</h2><h3>Diseño y estrategia de indexación de Elasticsearch</h3><p>Emplearemos dos índices para este proyecto: <code>peaks_catalog</code> y <code>photos</code>.</p><h4>Peaks_catalog índice</h4><p>Este índice sirve como un catálogo compacto de picos montañosos prominentes visibles durante la travesía al Campamento Base del Everest. Cada documento de este índice corresponde a una sola cima montañosa, como el Monte Everest. Para cada documento de pico de montaña, almacenamos nombres/alias, coordenadas opcionales de latitud-longitud y un único vector prototipo construido mediante la mezcla de prompts de texto SigLIP-2 (+ imágenes de referencia opcionales).</p><p><strong>Mapeo indexado:</strong></p><p>Campo</p><p>Tipo</p><p>Ejemplo</p><p>Propósito/Notas</p><p>Vector/Indexación</p><p>identificación</p><p>palabra clave</p><p>ama-dablam</p><p>Slug/id estable</p><p>—</p><p>Nombres</p><p>Subcampo texto + palabra clave</p><p>["Ama Dablam","Amadablam"]</p><p>Alias / nombres multilingües; names.raw para filtros exactos</p><p>—</p><p>Latlon</p><p>geo_point</p><p>{"lat":27.8617,"lon":86.8614}</p><p>Coordenadas GPS de pico como combinación de latitud/longitud (opcional)</p><p>—</p><p>elev_m</p><p>entero</p><p>6812</p><p>Elevación (opcional)</p><p>—</p><p>text_embed</p><p>dense_vector</p><p>768</p><p>Prototipo mezclado (prompts y, opcionalmente, 1–3 imágenes de referencia) para este pico</p><p>Index:True, Similitud:"Coseno", index_options:{type:"hnsw", m:16, ef_construction:128}</p><p>Este índice se emplea principalmente para búsquedas imagen a imagen, como identificar picos montañosos a partir de imágenes. También empleamos este índice para mejorar los resultados de búsqueda de texto a imagen.</p><p>En resumen, el <code>peaks_catalog</code> transforma la pregunta "¿Qué montaña es esta?" en un problema enfocado del vecino más cercano, separando efectivamente la comprensión conceptual de las complejidades de los datos de imagen.</p><p><strong>Estrategia de indexación para el índice peaks_catalog: </strong>Comenzamos creando una lista de los picos más destacados visibles durante la travesía por el EBC. Para cada pico, almacenamos su ubicación geográfica, nombre, sinónimos y elevación en un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/data/peaks.yaml">archivo yaml</a>. El siguiente paso es <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L351">generar la incrustación</a> de cada pico y almacenarla en <code>text_embed</code> campo. Para generar incrustaciones robustas, empleamos la siguiente técnica:</p><ul><li><p>Crea un prototipo de texto usando:</p><ul><li><p>Nombres de los picos</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L301">Conjunto de prompts</a> (usando varios prompts diferentes para intentar responder a la misma pregunta), por ejemplo:</p><ul><li><p>"una foto natural de la cima de la montaña {name} en el Himalaya, Nepal"</p></li><li><p>"{name} pico emblemático en la región del Khumbu, paisaje alpino"</p></li><li><p>"{name} cima de montaña, nieve, cresta rocosa"</p></li></ul></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L333">anticoncepto</a> opcional (indicar a SigLIP-2 en qué no debe coincidir): resta un pequeño vector para "pintura, ilustración, afiche, mapa, logo" para inclinarnos hacia fotos reales.</p></li></ul></li><li><p>Opcionalmente <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L388C13-L388C29">, crea un prototipo de imagen</a> si se proporcionan imágenes de referencia del pico.</p></li></ul><p>Luego <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L392">mezclamos el prototipo de texto e imagen</a> para generar la incrustación final. Finalmente, el documento está <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L396">indexado</a> con todos los campos requeridos:</p>def l2norm(v: np.ndarray) -&gt; np.ndarray:
    return v / (np.linalg.norm(v) + 1e-12)
def compute_blended_peak_vec(
        emb: Siglip2,
        names: List[str],
        peak_id: str,
        peaks_images_root: str,
        alpha_text: float = 0.5,
        max_images: int = 3,
) -&gt; Tuple[np.ndarray, int, int, List[str]]:
    """
    Build blended vector for a single peak.

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

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

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

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

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

    # 3) Blend TEXT and IMAGE vectors, clamp alpha to [0,1]
    a = max(0.0, min(1.0, float(alpha_text)))
    vec = l2norm(tv if img_v is None else (a * tv + (1.0 - a) * img_v)).astype("float32")
    return vec, found, used, [p.name for p in used_list]<p>Documento de ejemplo de <code>peaks_catalog</code> índice:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1219f5d0e39b512c/6a17da7c57726263161bcace/bc05fbd0c4f8d721d5170c28a3884a9eda80bb7d-1210x1132.png" alt="Un documento de ejemplo del índice de peaks_catalog en Elasticsearch." /><h4>Índice de fotos</h4><p>Este índice principal almacena información detallada sobre todas las fotos del álbum. Cada documento representa una sola foto, que contiene la siguiente información:</p><ul><li><p>Camino relativo a la foto del álbum. Esto puede usar para ver la imagen correspondiente o cargarla en la interfaz de búsqueda.</p></li><li><p>GPS e información horaria de la imagen.</p></li><li><p>Vector denso para codificación de imágenes generado por SigLIP-2.</p></li><li><p><code>predicted_peaks</code> Eso nos permite filtrar por nombre de pico.

<strong>Mapeo de índices</strong></p></li></ul><p>Campo</p><p>Tipo</p><p>Ejemplo</p><p>Propósito/Notas</p><p>Vector / Indexación</p><p>camino</p><p>palabra clave</p><p>datos/imágenes/IMG_1234.HEIC</p><p>Cómo se abre la interfaz en miniatura/imagen completa</p><p>—</p><p>clip_image</p><p>dense_vector</p><p>768</p><p>Incrustación de imágenes SigLIP-2</p><p>Index:True, Similitud:"Coseno", index_options:{type:"hnsw", m:16, ef_construction:128}</p><p>predicted_peaks</p><p>palabra clave</p><p>["ama-dablam", "pumori"]</p><p>Top-K suposiciones en el tiempo del índice (filtro UX barato / facet)</p><p>—</p><p>GPS</p><p>geo_point</p><p>{"lat":27.96,"lon":86.83}</p><p>Activa los filtros geográficos</p><p>—</p><p>shot_time</p><p>date</p><p>2023-10-18T09:41:00Z</p><p>Tiempo de captura: ordenar/filtrar</p><p>—</p><p><strong>Estrategia de indexación para el índice de fotos: </strong>Para cada foto del álbum, hacemos lo siguiente:
Extrae <code>shot_time</code> de imagen y <code>gps</code> información <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L526">de los metadatos de las imágenes</a>.</p><ul><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L511">Embedding de imagen SigLIP-2</a>: pasar la imagen por el modelo y normalizar el vector en modo L2. Almacena el embedding en <code>clip_image</code> campo.</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L519">Predecir los picos</a> y almacenarlos en el campo <code>predicted_peaks</code> . Para ello, primero tomamos el vector de imagen de la foto generado en el paso anterior y luego ejecutamos una búsqueda rápida kNN en el campo text_embed en el índice de <code>peaks_catalog</code> . Mantenemos los 3-4 primeros picos e ignoramos el resto.</p></li><li><p>Calculamos el campo <code>_id</code> haciendo un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L509">hash</a> en el nombre y el camino de la imagen. Esto cerciora que no acabemos con duplicados tras varias partidas.</p></li></ul><p>Una vez que determinamos todos los campos para la foto, los documentos fotográficos se indexan en lotes usando <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L530">indexación masiva</a> :</p>def bulk_index_photos(
        es: Elasticsearch,
        images_root: str,
        photos_index: str = "photos",
        peaks_index: str = "peaks_catalog",
        topk_predicted: int = 5,
        batch_size: int = 200,
        refresh: str = "false",
) -&gt; None:
    """Walk a folder of images, embed + enrich, and bulk index to Elasticsearch."""
    root = Path(images_root)
    if not root.exists():
        raise SystemExit(f"Images root not found: {images_root}")

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

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

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

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

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

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

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

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

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

    print("[done] photos indexing")<p>Documento de ejemplo del índice de fotos:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt744b7e6326937cfc/6a17da7e6df731d3040a0da8/1dc1406ac2a97440b6804838795b3c2205c4c6b2-1080x1234.png" alt="Un documento de ejemplo del índice de fotos en Elasticsearch." /><p>En resumen, el índice de las fotos es el almacén rápido, filtrable y listo para kNN de todas las fotos del álbum. Su mapeo es mínimo a propósito: la estructura justa para recuperar rápidamente, mostrar limpiamente y recortar los resultados por espacio y tiempo. Este índice sirve tanto para casos de búsqueda como para el uso. Aquí se <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/create_indices.py">puede encontrar un</a> script en Python para crear ambos índices.</p><p>La visualización de mapas de Kibana que aparece a continuación muestra documentos del álbum de fotos como puntos verdes y picos montañosos del índice de <code>peaks_catalog</code> como triángulos rojos, con los puntos verdes alinear bien con el sendero de la ruta del campamento base del Everest.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5bf016e8d9c3e84/6a17da80be608681f10045e6/1c75d0ed0ce53d28a94bf2f47a354e25581d2baf-1600x1402.png" alt="Una visualización de Kibana muestra documentos del álbum de fotos como puntos verdes y las cumbres de montaña del índice de peaks_catalog como triángulos rojos, con los puntos verdes alinear bien con el sendero de la ruta de trekking al Campamento Base del Everest." /><h2>Casos de uso de búsqueda</h2><p><strong>Buscar por nombre (texto a imagen):</strong> Esta función permite a los usuarios localizar fotos de picos montañosos (e incluso conceptos abstractos como "banderas de oración") mediante consultas de texto. Para lograrlo, la entrada de texto <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L87C5-L87C20">se convierte en un vector de texto</a> usando SigLIP-2. Para una generación robusta de vectores de texto, empleamos la misma estrategia que se usa para crear incrustaciones de texto en el índice <code>peaks_catalog</code> : <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104">combinar</a> la entrada de texto con un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L100">pequeño conjunto de prompts</a>, restar un<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L103"> pequeño vector anti-concepto</a> y aplicar la <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104">normalización L2</a> para producir el vector de consulta final. A continuación, se ejecuta una <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L140">consulta</a> kNN en el campo <code>photos.clip_image</code> para recuperar los picos que coinciden con la parte superior, basar en la similitud coseno para encontrar las imágenes más cercanas. Opcionalmente, los resultados de búsqueda pueden ser más relevantes aplicando <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L152">filtros</a> geográficos y de fecha, y/o un filtro de <code>photos.predicted_peaks</code> términos como parte de la consulta (ver ejemplos de consultas más abajo). Esto ayuda a excluir picos que se parecen y que en realidad no se ven durante la travesía.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9bb9abf5ce64fcbb/6a17da81e8fbce20db3a17da/b5fac28ffdbedb820505365ca07df125cd01b939-946x370.png" alt="Qué tan multimodal funciona, la búsqueda por nombre (texto a imagen) en Elasticsearch." /><p><strong>Consulta de Elasticsearch con filtro geográfico:</strong></p>POST photos/_search
{
  "knn": {
    "field": "clip_image",
    "query_vector": [ ... ],
    "k": 60,
    "num_candidates": 2000
  },
  "query": {
    "bool": {
      "filter": [
        { "geo_bounding_box": { "gps": { "top_left": "...", "bottom_right": "..." } } }
      ]
    }
  },
  "_source": ["path","predicted_peaks","gps","shot_time"]
}

Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<p><strong>Buscar por imagen (imagen a imagen):</strong> Esta función nos permite identificar una montaña en una imagen y encontrar otras imágenes de esa misma montaña dentro del álbum. Cuando se sube una imagen, el codificador de imagen SigLIP-2 la procesa para generar un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L228">vector de imagen</a>. A continuación, se realiza una <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L234">búsqueda kNN</a> en el campo <code>peaks_catalog.text_embed</code> para identificar los nombres de picos que mejor coinciden. Posteriormente, <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L257">se genera un vector de texto</a> a partir de estos nombres de picos coincidentes, y se realiza otra <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L263">búsqueda kNN</a> en el índice de fotos para localizar las imágenes correspondientes.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab9d16333e2a9e69/6a17da827f6f155448c099cc/3a3d5635bee7a222b95529dd7f9fbee016381610-1226x550.png" alt="Cómo funciona la búsqueda multimodal por imagen (imagen a imagen) en Elasticsearch." /><p><strong>Consulta Elasticsearch:</strong></p><p>Paso 1: Encontrar los nombres de picos que coincidan</p>GET peaks_catalog/_search
{
 "knn": {
   "field": "text_embed",
   "query_vector": [...image-vector... ],
   "k": 3,
   "num_candidates": 500
 },
 "_source": [
   "id",
   "names",
   "latlon",
   "text_embed"
 ]
}


Response (first two documents):
{
 "took": 2,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 3,
     "relation": "eq"
   },
   "max_score": 0.58039916,
   "hits": [
     {
       "_index": "peaks_catalog",
       "_id": "pumori",
       "_score": 0.58039916,
       "_source": {
         "id": "pumori",
         "names": [
           "Pumori",
           "Pumo Ri"
         ],
         "latlon": {
           "lat": 28.01472,
           "lon": 86.82806
         },
         "text_embed": [
                  ... embeddings...
         ]
       }
     },
     {
       "_index": "peaks_catalog",
       "_id": "kyajo-ri",
       "_score": 0.57942784,
       "_source": {
         "id": "kyajo-ri",
         "names": [
           "Kyajo Ri",
           "Kyazo Ri"
         ],
         "latlon": {
           "lat": 27.909167,
           "lon": 86.673611
         },
         "text_embed": [
           ... embeddings...
         ]
       }
     }
   ]
 }
}<p>Paso 2: Realiza una búsqueda en el índice de <code>photos</code> para encontrar las imágenes coincidentes (misma consulta que se muestra en el caso de búsqueda text-to-image):</p>POST photos/_search
{
 "knn": {
   "field": "clip_image",
   "query_vector": [ ...image-vector... ],
   "k": 30,
   "num_candidates": 2000
 },
 "_source": [
   "path",
   "gps",
   "shot_time",
   "predicted_peaks",
   "clip_image"
 ],
 "query": {
   "bool": {
     "filter": [
       {
         "term": {
           "predicted_peaks": "Pumori"
         }
       }
     ]
   }
 }
}


Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<h2>Interfaz Streamlit</h2><p>Para unir todo, creamos una interfaz sencilla de Streamlit que nos permite realizar ambos casos de uso de búsqueda. El riel izquierdo muestra una lista desplazable de picos (agregados a partir de <code>photos.predicted_peaks</code>) con casillas de verificación y un minimapa/filtro geográfico. En la parte superior hay una caja <strong>de búsqueda por nombre</strong> y un botón <strong>de identificación por subida de fotos</strong> . El panel central presenta una cuadrícula en miniatura sensible que muestra puntajes kNN, insignias de picos predichos y tiempos de captura. Cada imagen incluye un botón <strong>para ver imagen</strong> para vistas previas en resolución completa.</p><p><strong>Busca subiendo una imagen:</strong> Predecimos el pico y encontramos picos coincidentes del álbum de fotos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1fb2b0304a310d2/6a17da8425daab7cda08a0fa/dca540cbf5279e6d6102c5a0c0351ddd4ac91cda-1600x1112.png" alt="Una interfaz sencilla de iluminación streamlit que permite buscar tanto texto a imagen como imagen a imagen multimodal los picos del Monte Ama Dablam." /><p><strong>Buscar por texto</strong>: Encuentra los picos coincidentes en el álbum a partir del texto</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt496c1ae8f7886320/6a17da86abe0f2da48dfe8bd/b1e8618db746cd49ea4962d3dc73031387b975dd-1600x1166.png" alt="Cómo buscar un pico del Monte Everest buscando por texto en la biblioteca de picos de montaña." /><h2>Conclusión</h2><p>¿Qué empezó como <em>, ¿podemos simplemente ver las </em>imágenes<em><strong>de Ama Dablam</strong></em><em>?</em> se convirtió en un pequeño sistema de <strong>búsqueda multimodal</strong> funcional. Tomamos fotos en bruto de trekking, las convertimos en <strong>incrustaciones SigLIP-2</strong> y usamos <strong>Elasticsearch</strong> para hacer <strong>kNN</strong> rápido sobre vectores, además de filtros geo/temporales simples para mostrar las imágenes <em>correctas por significado</em>. En el camino, separamos las preocupaciones con dos índices: un pequeño <code>peaks_catalog</code> de prototipos combinados (para identificación) y un índice escalable de <code>photos</code> de vectores de imagen y EXIF (para recuperación). Es práctica, reproducible y fácil de ampliar.</p><p>Si quieres afinarla, hay algunos ajustes con los que puedes experimentar:</p><ul><li><p><strong>Ajustes de tiempo de consulta:</strong> <code>k</code> (cuántos vecinos quieres que devuelvan) y <code>num_candidates</code> (qué ancho buscar antes del puntaje final). Estos ajustes se discuten en el <a href="https://www.elastic.co/search-labs/blog/elasticsearch-knn-and-num-candidates-strategies">blog aquí</a>.</p></li><li><p><strong>Ajustes de tiempo de índice:</strong> <code>m</code> (conectividad de grafos) y <code>ef_construction</code> (precisión en tiempo de construcción frente a memoria). Para consultas, experimenta también con <code>ef_search</code> : más alto suele significar mejor recordación con cierto compromiso de latencia. Consulta <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">este blog</a> para más detalles sobre estos entornos.</p></li></ul><p>De cara al futuro, los modelos nativos/reclasificadores para búsqueda <strong>multimodal</strong> y <strong>multilingüe</strong> pronto llegarán al ecosistema Elastic, lo que debería mejorar aún más la recuperación de imágenes/texto y el ranking híbrido desde el primer momento.<a href="https://ir.elastic.co/news/news-details/2025/Elastic-Completes-Acquisition-of-Jina-AI-a-Leader-in-Frontier-Models-for-Multimodal-and-Multilingual-Search/default.aspx?utm_source=chatgpt.com"> ir.elastic.co+1</a></p><p>Si quieres probar esto tú mismo:</p><ul><li><p><strong>Repositorio de GitHub:</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search"><em>https://github.com/navneet83/multimodal-mountain-peak-search</em></a></p></li><li><p><strong>Inicio rápido de Colab:</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb</a></p></li></ul><p>Con esto, nuestro viaje llegó a su fin y es hora de volar de regreso. Espero que esto te fue útil y si lo rompes (o lo mejoras), me encantaría saber qué cambiaste.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdce2fff1569d2a8b/6a17da894b055dd1f24320a2/d324d1e1472f1bfbd8f25747f57bdeeb9c7f16b2-1600x1200.png" alt="" />]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[Búsqueda híbrida]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Navneet Kumar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltccb66279debb05f9/6a17da8b63baffe228741b15/ffcf93358a7c5dadcea82faf3de460bf060d003c-1600x1200.png" length="0" type="image/png"/>
    <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Mejora de la relevancia del modelo de incrustación multilingüe con reclasificación híbrida en búsquedas]]></title>
    <description><![CDATA[Aprende cómo mejorar la relevancia de los resultados de búsqueda del modelo de incrustación multilingüe E5 usando el reclasificador y la búsqueda híbrida de Cohere en Elasticsearch.]]></description>
    <content:encoded><![CDATA[<h2>Introducción</h2><p>En la <a href="https://www.elastic.co/search-labs/blog/multilingual-embedding-model-deployment-elasticsearch">última parte de este serial</a>, explicamos cómo desplegar el modelo E5 preentrenado de Elastic (así como otros modelos multilingües de incrustación de texto de Hugging Face) y nos adentramos en la generación de incrustaciones vectoriales densas a partir de tus datos textuales usando Elasticsearch y Kibana. En este blog, analizaremos los resultados de estas incrustaciones y destacaremos los beneficios significativos de aprovechar un modelo multilingüe.</p><p>Ahora que tenemos nuestro índice <code>coco_multilingual</code>, realizar la búsqueda nos dará documentos en varios idiomas, con el campo "en" para que podamos consultar:</p># GET coco_multilingual/_search
    {
       "_index": "coco_multilingual",
       "_id": "WAiXQJYBgf6odR9bLohZ",
       "_score": 1,
       "_source": {
         "description": "Ein Parkmeßgerät auf einer Straße mit Autos",
         "en": "A row of parked cars sitting next to parking meters.",
         "language": "de",
         "vector_description": {...}
       }
     },
     . . .<h2>Realizando una búsqueda en inglés</h2><p>Intentemos hacer la búsqueda en inglés y ver qué tal va:</p>GET coco_multi/_search
{
"size": 10,
"_source": [
  "description", "language", "en"
],
"knn": {
  "field": "vector_description.predicted_value",
  "k": 10,
  "num_candidates": 100,
  "query_vector_builder": {
    "text_embedding": {
      "model_id": ".multilingual-e5-small_linux-x86_64_search",
      "model_text": "query: kitty"
    }
  }
}
}{
       "_index": "coco_multi",
       "_id": "JQiXQJYBgf6odR9b6Yz0",
       "_score": 0.9334303,
       "_source": {
         "description": "Eine Katze, die in einem kleinen, gepackten Koffer sitzt.",
         "en": "A brown and white cat is in a suitcase.",
         "language": "de"
       }
     },
      {
       "_index": "coco_multi",
       "_id": "3AiXQJYBgf6odR9bFod6",
       "_score": 0.9281012,
       "_source": {
         "description": "Una bambina che tiene un gattino vicino a una recinzione blu.",
         "en": "A little girl holding a kitten next to a blue fence.",
         "language": "it"
       }
     },
     . . .<p>Aquí, aunque la consulta parezca engañosamente simple, estamos buscando las incrustaciones numéricas de la palabra 'kitty' en todos los documentos y todos los idiomas que aparecen debajo del capó. Y como realizamos búsqueda vectorial, podemos buscar semánticamente todas las palabras que puedan estar relacionadas con 'kitty': "cat", "kitten", "felino", "gatto" (italiano), "meo" (vietnamita), 고양이 (coreano), 猫 (chino), etc. Como resultado, aunque mi consulta esté en inglés, también podemos buscar contenido en todos los demás idiomas. Por ejemplo, buscar un gatito<code>ying on something</code> también devuelve documentos en italiano, neerlandés o vietnamita. ¡Eso sí que es eficiencia!</p><h2>Realizar una búsqueda de contenido en otros idiomas</h2>GET coco_multi/_search
{  
 "size": 100,
 "_source": [
   "description", "language", "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 50,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: kitty lying on something"
     }
   }
 }
}{
 "description": "A black kitten lays on her side beside remote controls.",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "en"
},
{
 "description": "un gattino sdraiato su un letto accanto ad alcuni telefoni ",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "it"
},
{
 "description": "eine Katze legt sich auf ein ausgestopftes Tier",
 "en": "a cat lays down on a stuffed animal",
 "language": "de"
},
{
 "description": "Một chú mèo con màu đen nằm nghiêng bên cạnh điều khiển từ xa.",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "vi"
}
. . .<p>De manera similar, realizar una búsqueda por palabra clave de "cat" en coreano ("고양이") también devolverá resultados significativos. Lo espectacular aquí es que ni siquiera tenemos documentos en coreano en este índice.</p>GET coco_multi/_search
{
 "size": 100,
 "_source": [
   "description", "language", "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 50,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: 고양이"
     }
   }
 }
} {
       {
         "description": "eine Katze legt sich auf ein ausgestopftes Tier",
         "en": "a cat lays down on a stuffed animal",
         "language": "de"
       }
     },
     {
       {
         "description": "Một con chó và con mèo đang ngủ với nhau trên một chiếc ghế dài màu cam.",
         "en": "A dog and cat lying  together on an orange couch. ",
         "language": "vi"
       }
     },<p>Esto funciona porque el modelo de incrustación representa el significado en un espacio semántico compartido, permitiendo la recuperación de imágenes relevantes incluso con una consulta en un idioma diferente al de los subtítulos indexados.</p><h2>Aumento de resultados de búsqueda relevantes con búsqueda híbrida y reposicionamiento</h2><p>Estamos contentos de que los resultados relevantes llegaron como se esperaba. Pero, en el mundo real, por ejemplo en comercio electrónico o en aplicaciones RAG que necesitan reducir a los 5-10 resultados más aplicables, podemos usar un modelo de reclasificación para priorizar los resultados más relevantes.</p><p>Aquí, realizar una consulta que pregunte "¿de qué color es el gato?" en vietnamita dará muchos resultados, pero el top 1 o 2 puede no ser el más relevante.</p>GET coco_multi/_search
{
 "size": 20,
 "_source": [
   "description",
   "language",
   "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 20,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: con mèo màu gì?"
     }
   }
 }
}<p>Todos los resultados mencionan gato, o algún tipo de color:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt979f5944b1708042/6a17ef76420229ff6829f6aa/33e1e887dbbdd1066cfedc7375f5e3b46538529e-859x847.png" alt="" /><p>¡Así que vamos a mejorar eso! Integremos el modelo multilingüe de reclasificación de <a href="https://cohere.com/blog/rerank-3pt5">Cohere</a>para mejorar el razonamiento correspondiente a nuestra pregunta.</p>PUT _inference/rerank/cohere_rerank
{
 "service": "cohere",
 "service_settings": {
   "api_key": "your_api_key",
   "model_id": "rerank-v3.5"
 },
 "task_settings": {
   "top_n": 10,
   "return_documents": true
 }
}


GET coco_multi/_search
{
"size": 10,
"_source": [
  "description",
  "language",
  "en"
],
"retriever": {
  "text_similarity_reranker": {
    "retriever": {
      "rrf": {
        "retrievers": [
          {
            "knn": {
              "field": "vector_description.predicted_value",
              "k": 50,
              "num_candidates": 100,
              "query_vector_builder": {
                "text_embedding": {
                  "model_id": ".multilingual-e5-small_linux-x86_64_search",
                  "model_text": "query: con mèo màu gì?" // English: What color is the cat?
                }
              }
            }
          }
        ],
        "rank_window_size": 100,
        "rank_constant": 0
      }
    },
    "field": "description",
    "inference_id": "cohere_rerank",
    "inference_text": "con mèo màu gì?"
  }
}
} {
       "_index": "coco_multi",
       "_id": "rQiYQJYBgf6odR9bBYyH",
       "_score": 1.5501487,
       "_source": {
         "description": "Hai cái điện thoại được đặt trên một cái chăn cạnh một con mèo con màu đen.",
         "en": "A black kitten lays on her side beside remote controls.",
         "language": "vi"
       }
     },
     {
       "_index": "coco_multi",
       "_id": "swiXQJYBgf6odR9b04uf",
       "_score": 1.5427427,
       "_source": {
         "description": "Một con mèo sọc nâu nhìn vào máy quay.", // Real translation: A brown striped cat looks at the camera 
         "en": "This cat is sitting on a porch near a tire.",
         "language": "vi"
       }
     },<p>Ahora, con los mejores resultados, nuestra solicitud puede responder con confianza que el color del gatito es negro o marrón con rayas. Lo que resulta aún más interesante aquí es que nuestra búsqueda vectorial detectó una omisión en el pie de foto en inglés del conjunto de datos original. Es capaz de encontrar al gato de rayas marrones aunque la traducción de referencia al inglés no mencionó ese detalle. Este es el poder de la búsqueda vectorial.</p><h2>Conclusión</h2><p>En este blog, explicamos la utilidad de un modelo de incrustación multilingüe y cómo aprovechar Elasticsearch para integrar los modelos y generar embeddings, y mejorar eficazmente la relevancia y la precisión mediante una búsqueda híbrida y un reclasificador. Puedes <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">crear tu propio clúster en la nube</a> para probar <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5">la búsqueda semántica multilingüe usando nuestro modelo E5 estándar</a> en el idioma y conjunto de datos que elijas.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multilingual-embedding-model-hybrid-search-reranking</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multilingual-embedding-model-hybrid-search-reranking</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[Operaciones]]></category>
    <dc:creator><![CDATA[Quynh Nguyen]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf625e3f63fcd9f54/6a17ef7796142a61f8eb1bcd/d341b04acecc8eeec321f5404e1643447ecc8526-720x420.png" length="0" type="image/png"/>
    <pubDate>Mon, 03 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Despliegue de un modelo de incrustación multilingüe en Elasticsearch]]></title>
    <description><![CDATA[Aprende a desplegar un modelo de incrustación multilingüe e5 para búsqueda vectorial y recuperación cross-lingual en Elasticsearch.]]></description>
    <content:encoded><![CDATA[<h2>Introducción</h2><p>En un mundo de usuarios globales, la recuperación de información multilingüe (CLIR) es fundamental. En lugar de limitar las búsquedas a un solo idioma, CLIR te permite encontrar información en <em>cualquier</em> idioma, mejorando la experiencia del usuario y agilizando las operaciones. Imagina un mercado global donde los clientes de comercio electrónico puedan buscar artículos en su idioma, y los resultados adecuados aparezcan, sin necesidad de localizar los datos de antemano. O bien, donde los investigadores académicos pueden buscar artículos en su lengua materna, con matices y complejidad, incluso si la fuente está en otro idioma.</p><p>Los modelos de incrustación de texto multilingüe nos permiten hacer precisamente eso. Las incrustaciones son una forma de representar el significado del texto como vectores numéricos. Estos vectores están diseñados para que textos con significados similares estén situados cerca unos de otros en un espacio de alta dimensión. Los modelos de incrustación de texto multilingüe están diseñados específicamente para mapear palabras y frases con el mismo significado entre diferentes idiomas en un espacio vectorial similar.</p><p>Modelos como el Multilingüe E5 de código abierto se capacitan con enormes cantidades de datos textuales, a menudo empleando técnicas como el aprendizaje contrastivo. En este enfoque, el modelo aprende a distinguir entre pares de textos con significados similares (pares positivos) y aquellos con significados diferentes (pares negativos). El modelo se capacita para ajustar los vectores que produce de modo que se maximice la similitud entre pares positivos y se minimice la similitud entre pares negativos. Para modelos multilingües, estos datos de entrenamiento incluyen pares de texto en diferentes idiomas que son traducciones entre sí, permitiendo al modelo aprender un espacio de representación compartido para múltiples idiomas. Las incrustaciones resultantes pueden usar para diversas tareas de PLN, incluyendo la búsqueda cross-lingual, donde la similitud entre incrustaciones de texto se emplea para encontrar documentos relevantes independientemente del idioma de la consulta.</p><h2>Beneficios de la búsqueda vectorial multilingüe</h2><ul><li><p><strong>Matiz:</strong> La búsqueda vectorial destaca en captar el significado semántico, yendo más allá de la simple búsqueda de palabras clave. Esto es crucial para tareas que requieren comprender el contexto y las sutilezas del lenguaje.</p></li><li><p><strong>Comprensión interlingüe</strong>: Permite una recuperación efectiva de información entre idiomas, incluso cuando la consulta y los documentos emplean vocabulario diferente.</p></li><li><p><strong>Relevancia</strong>: Ofrece resultados más relevantes centrar en la similitud conceptual entre consultas y documentos.</p></li></ul><p>Por ejemplo, consideremos a un investigador académico que estudia el "impacto de las redes sociales en el discurso político" en diferentes países. Con la búsqueda vectorial, pueden introducir consultas como "l'impacto dei social media sul discorso politico" (italiano) o "ảnh hưởng của mạng xã hội đối với diễn ngôn chính trị" (vietnamita) y encontrar artículos relevantes en inglés, español o cualquier otro idioma indexado. Esto se debe a que la búsqueda vectorial identifica artículos que discuten el <em>concepto</em> de influencia de las redes sociales en la política, no solo aquellos que contienen las palabras clave exactas. Esto mejora enormemente la amplitud y profundidad de su investigación.</p><h2>Primeros pasos</h2><p>Así es como configurar CLIR usando Elasticsearch, con el modelo E5 que se proporciona de fábrica. Emplearemos el <a href="https://huggingface.co/datasets/romrawinjp/multilingual-coco">conjunto de datos multilingüe de código abierto COCO</a>, que contiene pies de foto en varios idiomas, para ayudarnos a visualizar dos tipos de búsquedas:</p><ol><li><p>Consultas y términos de búsqueda en otros idiomas en un conjunto de datos en inglés, y</p></li><li><p>Consultas en varios idiomas sobre un conjunto de datos que contiene documentos en varios idiomas.</p></li></ol><p>Luego, aprovecharemos el poder de la búsqueda híbrida y el reposicionamiento para mejorar aún más los resultados de búsqueda.</p><h2>Prerrequisitos</h2><ul><li><p>Python 3.6+</p></li><li><p>Elasticsearch 8+</p></li><li><p>Cliente Python de Elasticsearch: instalación de pip elasticsearch</p></li></ul><h2>Conjunto de datos</h2><p>El <a href="https://huggingface.co/datasets/romrawinjp/multilingual-coco">conjunto de datos COCO</a> es un conjunto de datos de subtitulado a gran escala. Cada imagen del conjunto de datos está subtitulada en varios idiomas diferentes, con varias traducciones disponibles por idioma. Para fines demostrativos, indexaremos cada traducción como un documento individual, junto con la primera traducción al inglés disponible para referencia.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc7e508e9a7dffe8/6a17f3e2b1e113249579f394/d4f0632529c71a22fbdecf21c9f4f0bb64b8e69c-1600x567.png" alt="" /><h3>Paso 1: descargar el conjunto de datos multilingüe COCO</h3><p>Para simplificar el blog y facilitar el seguimiento, aquí estamos cargando las primeras 100 filas de Restval en un archivo JSON local con una llamada API sencilla. Alternativamente, puedes usar los conjuntos de datos de la biblioteca de HuggingFace para cargar el conjunto de datos completo o subconjuntos del conjunto.</p>import requests
import json
import os
### Download multilingual coco dataset into a json file (for easy viewing)
### Here we are retrieving first 100 rows for this example
### Alternatively, you can use `datasets` library from Hugging Face
url = "https://datasets-server.huggingface.co/rows?dataset=romrawinjp%2Fmultilingual-coco&amp;config=default&amp;split=restval&amp;offset=0&amp;length=100"
response = requests.get(url)


if response.status_code == 200:
   data = response.json()
   output_file = "multilingual_coco_sample.json" 
   ### Loading the downloaded content into a json file locally
   with open(output_file, "w", encoding="utf-8") as f:
       json.dump(data, f, indent=4, ensure_ascii=False)
   print(f"Data successfully downloaded and saved to {output_file}")
else:
   print(f"Failed to download data: {response.status_code}")
   print(response.text)<p>Si los datos se cargan correctamente en un archivo JSON, deberías ver algo similar a lo siguiente:</p><p><code>Data successfully downloaded and saved to multilingual_coco_sample.json</code></p><h3>Paso 2: (Iniciar Elasticsearch) e indexar los datos en Elasticsearch</h3><p>a) Inicia tu servidor local de Elasticsearch.</p><p>b) Iniciar el cliente Elasticsearch.</p>from elasticsearch import Elasticsearch
from getpass import getpass


# Initialize Elasticsearch client
es = Elasticsearch(getpass("Host: "), api_key=getpass("API Key: "))


index_name = "coco"


# Create the index if it doesn't exist
if not es.indices.exists(index=index_name):
   es.indices.create(index=index_name, body=mapping)<p>c) Datos de índice</p># Load the JSON data
with open('./multilingual_coco_sample.json', 'r') as f:
   data = json.load(f)


rows = data["rows"]
# List of languages to process
languages = ["en", "es", "de", "it", "vi", "th"]


# For each image, we will process each individual caption as its own document
bulk_data = []
for data in rows:
   row = data["row"]
   image = row.get("image")
   image_url = image["src"]


   # Process each language
   for lang in languages:
       # Skip if language not present in this row
       if lang not in row:
           continue


       # Get all descriptions for this language
 # along with first available English caption for reference
       descriptions = row[lang]
       first_eng_caption = row["en"][0]


       # Prepare bulk indexing data
       for description in descriptions:
           if description == "":
               continue
           # Add index operation
           bulk_data.append(
               {"index": {"_index": index_name}}
           )
           # Add document
           bulk_data.append({
               "language": lang,
               "description": description,
               "en": first_eng_caption,
               "image_url": image_url,
           })


# Perform bulk indexing
if bulk_data:
   try:
       response = es.bulk(operations=bulk_data)
       if response["errors"]:
           print("Some documents failed to index")
       else:
           print(f"Successfully bulk indexed {len(bulk_data)} documents")
   except Exception as e:
       print(f"Error during bulk indexing: {str(e)}")


print("Indexing complete!")<p>Una vez indexados los datos, deberías ver algo similar a lo siguiente:</p><p><code>Successfully bulk indexed 4840 documents</code></p><p><code>Indexing complete!</code></p><h3>Paso 3: Desplegar el modelo capacitado con E5</h3><p>En Kibana, accede a la página de Stack Management &gt; <strong>Trained Models</strong> y haz clic <strong>en Desplegar</strong> para el .multilingual-e5-small_linux-x86_64 opción. Este modelo E5 es un pequeño multilingüe optimizado para linux-x86_64, que podemos usar de fábrica. Al hacer clic en 'Desplegar' aparecerá una pantalla donde puedes ajustar la configuración de despliegue o las configuraciones de los vCPU. Para simplificar, optaremos por las opciones predeterminadas, con recursos adaptativos seleccionados, que escalarán automáticamente nuestro despliegue según el uso.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfbc09867063a8e6f/6a17f3e3148009a295b4889d/95cd8f352425d1db2d04b00c3c88d1e71d1ef19a-1600x440.png" alt="" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt264a2016341e9b6f/6a17f3f0e8fbce18de3a1aa7/1599d99949dda8267acc58f400a403a3af5373ef-1600x655.png" alt="" /><p>Opcionalmente, si quieres usar otros modelos de incrustación de texto, puedes hacerlo. Por ejemplo, para usar el BGE-M3, puedes usar <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/eland/machine-learning#ml-nlp-pytorch">el cliente Eland Python de Elastic</a> para importar el modelo desde HuggingFace.</p>export MODEL_ID="bge-m3"
export HUB_MODEL_ID="BAAI/bge-m3"
export CLOUD_ID={{CLOUD_ID}}
export ES_API_KEY={{API_KEY}}
docker run -it --rm docker.elastic.co/eland/eland \
eland_import_hub_model --cloud-id $CLOUD_ID --es-api-key $ES_API_KEY --hub-model-id $HUB_MODEL_ID --es-model-id $MODEL_ID --task-type text_embedding --start<p>Luego, ve a la página de Modelos Capacitados para desplegar el modelo importado con las configuraciones deseadas.</p><h3>Paso 4: Vectorizar o crear incrustaciones para los datos originales con el modelo desplegado</h3><p>Para crear los embeddings, primero necesitamos crear un pipeline de ingesta que nos permita tomar el texto y pasarlo por el modelo de embedding de texto de inferencia. Puedes hacerlo en la interfaz de usuario de Kibana o a través de la API de Elasticsearch.</p><p><strong>Para hacerlo a través de la interfaz Kibana</strong>, tras desplegar el Modelo Capacitado, haz clic en el botón <strong>Test </strong>. Esto te dará la posibilidad de probar y previsualizar los embeddings generados. Crea una nueva vista de datos para elíndice de <code>coco</code>, configura la vista de datos en la vista de datos coco recién creada y pon el campo en <code>description</code> porque ese es el campo para el que queremos generar incrustaciones.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt24a2b9a9a5111bbc/6a17f3f13e9e452c7fba15d5/cfe189e13dc118d325e7fb90bdace0c912e29f51-1088x1600.png" alt="" /><p>¡Eso funciona genial! Ahora podemos proceder a crear la pipeline de ingest, reindexar nuestros documentos originales, pasarlos por la pipeline y crear un nuevo índice con los embeddings. Puedes conseguirlo haciendo clic <strong>en Crear pipeline</strong>, lo que te guiará durante el proceso de creación de pipeline, con procesadores auto-repoblados necesarios para ayudarte a crear los embeddings.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte39c5ad52702103d/6a17f3f3e9ea87dd05a9c734/1e043c1c3279b66fbdf19c06b41e76e613043998-1600x1126.png" alt="" /><p>El asistente también puede rellenar automáticamente los procesadores necesarios para gestionar fallos mientras se ingieren y procesan los datos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt63e002a79d177cf4/6a17f3f596142a3e91eb1c48/8804d31b4f869078e3b2245040bbb0ab1720a94a-1600x1084.png" alt="" /><p>Ahora creemos la canalización de ingest. Voy a nombrar el oleoducto <code>coco_e5</code>. Una vez que la tubería se crea correctamente, puedes usarla inmediatamente para generar las incrustaciones reindexando los datos originales indexados a un nuevo índice en el asistente. Haz clic <strong>en Reindexar </strong>para iniciar el proceso.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt39243d9ad1779fdf/6a17f3f696142a13eaeb1c4c/e34b1b18f5b24420d4581fe4d657c569926c2023-1600x1126.png" alt="" /><h2>Para configuraciones más complejas, podemos usar la API de Elasticsearch.</h2><p>Para algunos modelos, debido a la forma en que se capacitaron, puede que necesitemos anteponer o agregar ciertos textos a la entrada real antes de generar los embeddings; de lo contrario, veremos una degradación del rendimiento.</p><p>Por ejemplo, con el e5, el modelo espera que el texto de entrada siga a "passage: {content of passage}". Empleemos los pipelines de ingest para lograrlo: crearemos un nuevo <strong>pipeline de ingest vectorize_descriptions</strong>. En esta canalización, crearemos un nuevo campo de <code>temp_desc</code> temporal, antepondremos "passage: " al texto <code>description</code> , pasaremos <code>temp_desc</code> por el modelo para generar incrustaciones de texto y luego eliminaremos el <code>temp_desc</code>.</p>PUT _ingest/pipeline/vectorize_descriptions
{
"description": "Pipeline to run the descriptions text_field through our inference text embedding model",
"processors": [
 {
   "set": {
     "field": "temp_desc",
     "value": "passage: {{description}}"
   }
 },
 {
   "inference": {     
"field_map": {
       "temp_desc": "text_field"
     },
     "model_id": ".multilingual-e5-small_linux-x86_64_search",
     "target_field": "vector_description"
   }
 },
 {
   "remove": {
     "field": "temp_desc"
   }
 }
]
}<p>Además, podríamos querer especificar qué <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-quantization">tipo de cuantización</a> queremos usar para el vector generado. Por defecto, Elasticsearch usa <code>int8_hnsw</code>, pero aquí quiero <a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Better Binary Quantization</a> (o <code>bqq_hnsw</code>), que reduce cada dimensión a una precisión de un solo bit. Esto reduce la huella de memoria en un 96% (o 32 veces) a un costo mayor en la precisión. Opto por este tipo de cuantización porque sé que usaré un reclasificador más adelante para mejorar la pérdida de precisión.</p><p>Para ello, crearemos un nuevo índice llamado <strong>coco_multi</strong> y especificaremos los mapeos. La magia aquí está en el campo vector_description, donde especificamos que el tipo del <strong>index_options</strong>debe <strong>ser bbq_hnsw</strong>.</p>PUT coco_multi
{
 "mappings": {
   "properties": {
     "description": {
       "type": "text"
     },
     "en": {
       "type": "text"
     },
     "image_url": {
       "type": "keyword"
     },
     "language": {
       "type": "keyword"
     },
     "vector_description.predicted_value": {
       "type": "dense_vector",
       "dims": 384,
       "index": "true",
       "similarity": "cosine",
       "index_options": {
         "type": "bbq_hnsw" 
       }
     }
   }
 }
}<p>Ahora, podemos reindexar los documentos originales a un nuevo índice, con nuestra pipeline de ingesta que "vectorizará" o creará incrustaciones para el campo de descripciones.</p>POST _reindex?wait_for_completion=false
{
 "source": {
   "index": "coco"
 },
 "dest": {
   "index": "coco_multilingual",
   "pipeline": "vectorize_descriptions"
 }
}<p>¡Y eso es todo! Desplegamos con éxito un modelo multilingüe con Elasticsearch y Kibana y aprendido paso a paso cómo crear las incrustaciones vectoriales con tus datos con Elastic, ya sea a través de la interfaz de usuario de Kibana o con la API de Elasticsearch. En la segunda parte de este serial, exploraremos los resultados y las particularidades del uso de un modelo multilingüe. Mientras tanto, puedes <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">crear tu propio clúster en la nube</a> para probar <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5">la búsqueda semántica multilingüe usando nuestro modelo E5 estándar</a> en el idioma y conjunto de datos que elijas.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multilingual-embedding-model-deployment-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multilingual-embedding-model-deployment-elasticsearch</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[Operaciones]]></category>
    <dc:creator><![CDATA[Quynh Nguyen]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt59254226694f93a6/6a17f3f81480098988b488a1/8f2aa7bebb6b2f701e274ba7282273f9ab4abed6-720x432.png" length="0" type="image/png"/>
    <pubDate>Wed, 22 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Filtrado de búsqueda vectorial: Mantenerlo relevante]]></title>
    <description><![CDATA[Realizar una búsqueda vectorial para encontrar los resultados más similares a una consulta no es suficiente. A menudo se necesita filtrar para reducir los resultados de búsqueda. Este artículo explica cómo funciona el filtrado para la búsqueda vectorial en Elasticsearch y Apache Lucene.]]></description>
    <content:encoded><![CDATA[<p>La búsqueda vectorial no es suficiente para encontrar resultados relevantes. Es muy común usar criterios de filtrado que ayudan a reducir los resultados de búsqueda y a filtrar los resultados irrelevantes.</p><p>Entender cómo funciona el filtrado en la búsqueda vectorial te ayudará a equilibrar los compromisos entre rendimiento y recordación, así como descubrir algunas de las optimizaciones que se usan para que la búsqueda vectorial sea eficiente al usar filtrado.</p><h2>¿Por qué filtrar?</h2><p>La búsqueda vectorial revolucionó la forma en que encontramos información relevante en grandes conjuntos de datos, permitiéndonos descubrir elementos que son semánticamente similares a una consulta.</p><p>Sin embargo, simplemente encontrar objetos similares no es suficiente. A menudo necesitamos reducir los resultados de búsqueda en función de criterios o atributos específicos.</p><p>Imagina que buscas un producto en una tienda online. Una búsqueda vectorial pura puede mostrarte artículos visualmente similares, pero también podrías filtrar por rango de precio, marca, disponibilidad o valoraciones de clientes. Sin filtrar, te presentarías con una gran variedad de productos similares, lo que dificultaría encontrar exactamente lo que buscas.</p><p>El filtrado permite un control preciso sobre los resultados de búsqueda, cerciorando que los elementos recuperados no solo se alineen semánticamente, sino que también cumplan todos los requisitos necesarios. Esto conduce a una experiencia de búsqueda mucho más precisa, eficiente y fácil de usar.</p><p>Aquí es donde Elasticsearch y Apache Lucene excelen: usar filtrado efectivo entre varios tipos de datos es una de las diferencias clave con otras bases de datos vectoriales.</p><h2>Filtrado para búsqueda vectorial exacta</h2><p>Existen dos formas principales de realizar búsquedas vectoriales exactas:</p><ul><li><p>Usar un tipo de índice <code>flat</code> para tu campo de dense_vector. Esto hace que <code>knn</code> búsquedas empleen la búsqueda exacta en lugar de aproximada.</p></li><li><p>Emplear una <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-script-score-query#vector-functions">consulta script_score</a> que emplea funciones vectoriales para calcular el puntaje. Esto puede usar con cualquier tipo de índice.</p></li></ul><p>Al ejecutar una búsqueda vectorial exacta, todos los vectores se comparan con la consulta. En este escenario, el filtrado ayudará al rendimiento, ya que solo se necesitan comparar los vectores que pasan el filtro.</p><p>Esto no afecta a la calidad del resultado, ya que todos los vectores se consideran de todos modos. Simplemente filtramos de antemano los resultados que no son interesantes, para poder reducir el número de operaciones.</p><p>Esto es muy importante, ya que puede ser más eficiente ejecutar una búsqueda exacta en lugar de una búsqueda aproximada cuando los filtros aplicados resultan en un pequeño número de documentos.</p><p>La regla general es usar la búsqueda exacta cuando menos de 10.000 documentos pasan el filtro. Los índices <a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">BBQ</a> son mucho más rápidos para comparar, así que tiene sentido usar la búsqueda exacta cuando hay menos de 100k para los índices basados. Consulta <a href="https://www.elastic.co/search-labs/blog/knn-exact-vs-approximate-search">esta entrada del blog</a> para más detalles.</p><p>Si tus filtros siempre son muy restrictivos, puedes considerar indexar centrado en la búsqueda exacta en lugar de en la búsqueda aproximada, usando un tipo de índice <code>flat</code> en lugar de uno basado en HNSW. Para más detalles, <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-params">ver las propiedades de index_options</a>.</p><h2>Filtrado para búsqueda vectorial aproximada</h2><p>Al ejecutar búsqueda vectorial aproximada, cambiamos la precisión de los resultados por el rendimiento. Las estructuras de datos de búsqueda vectorial como HNSW buscan eficientemente vecinos aproximados en millones de vectores. Se centran en recuperar los vectores más similares haciendo la menor cantidad posible de comparaciones vectoriales, que son costosas de calcular.</p><p>Esto significa que otros atributos de filtrado no forman parte de los datos vectoriales. Diferentes tipos de datos tienen sus propias estructuras de indexación que son eficientes para encontrarlos y filtrarlos, como diccionarios de términos, listas de publicación y valores de documentos.</p><p>Dado que estas estructuras de datos son independientes del mecanismo de búsqueda vectorial, ¿cómo aplicamos el filtrado a la búsqueda vectorial? Hay dos opciones: aplicar filtros luego de la búsqueda vectorial (postfiltrado) o antes de la búsqueda vectorial (prefiltrado).</p><p>Cada una de esas opciones tiene sus pros y sus contras. ¡Vamos a profundizar en ellos!</p><h3>Postfiltrado</h3><p>El postfiltrado aplica filtros después de que se realizó la búsqueda vectorial. Esto significa que los filtros se aplican después de que se encontraron los k primeros resultados vectoriales más similares.</p><p>Obviamente, podemos obtener menos de k resultados aplicando los filtros a los resultados. Por supuesto, podríamos obtener más resultados de la búsqueda vectorial (valores k más altos), pero no estaremos seguros de obtener k o más tras aplicar los filtros.</p><p>El beneficio del postfiltrado es que no cambia el comportamiento en tiempo de ejecución de la búsqueda vectorial: la búsqueda vectorial no es consciente del filtrado. Pero sí cambia el número final de resultados obtenidos.</p><p>A continuación se muestra un ejemplo de postfiltrado usando la <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query">consulta knn</a>. Comprueba que la cláusula de filtrado esté separada de la consulta knn:</p>{
  "query": {
    "bool": {
      "must": {
        "knn": {
          "field": "image-vector",
          "query_vector": [54, 10, -2],
          "k": 5,
          "num_candidates": 50
        }
      },
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}<p>El filtrado de postfiltrado también está disponible para la búsqueda de knn usando <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/filter-search-results#post-filter">el filtro de postfiltro</a>:</p>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, 2],
    "k": 5,
    "num_candidates": 50
  },
  "post_filter": {
    "term": {
      "file-type": "png"
    }
  }
}<p>Ten en cuenta que necesitas usar una sección explícita de filtro posterior con la búsqueda de knn. Si no usas un filtro de post, la búsqueda de knn <a href="https://www.elastic.co/docs/solutions/search/vector/knn#_combine_approximate_knn_with_other_features">combinará los resultados de vecinos más cercanos</a> con otras consultas o filtros en lugar de hacer un filtro de post.</p><h3>Prefiltrado</h3><p>Aplicar filtros antes de la búsqueda vectorial primero recuperará los documentos que cumplan con los filtros y luego transmitirá esa información a la búsqueda vectorial.</p><p>Lucene emplea <a href="https://github.com/apache/lucene/blob/7a60d7ce92392181e137361336e5196bd486cdd9/lucene/core/src/java/org/apache/lucene/util/BitSet.java">BitSets</a> para almacenar eficientemente los documentos que cumplen la condición de filtro. La búsqueda vectorial recorre entonces el grafo HNSW, teniendo en cuenta los documentos que cumplen la condición. Antes de agregar un candidato a los resultados, comprueba que esté contenido en el BitSet de documentos válidos.</p><p>Sin embargo, el candidato debe ser explorado y comparado con la consulta, aunque no sea un documento válido. La efectividad de HNSW depende de la conexión entre los vectores del grafo: si dejáramos de explorar un candidato, significaría que podríamos estar saltándonos también sus vecinos.</p><p>Piénsalo como manejar para llegar a una gasolinera. Si descartas cualquier carretera que no tenga gasolinera, es poco probable que llegues a tu destino. Puede que otras carreteras no sean lo que necesitas, pero te <em>conectan</em> con tu destino. ¡Lo mismo ocurre con los vectores en un grafo HNSW!</p><p>Por tanto, aplicar prefiltrado es menos eficiente que no aplicar filtros. Tenemos que trabajar en <em>todos</em> los vectores que visitamos en nuestra búsqueda, y desechar aquellos que no coinciden con el filtro. Estamos trabajando más y tardando más en conseguir los mejores resultados de la k.</p><p>A continuación se muestra un ejemplo de pretfiltering en la DSL de Elasticsearch Consult. Comprueba que la cláusula de filtrado ahora forma parte de la sección knn:</p>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, -2],
    "k": 5,
    "num_candidates": 50,
    "filter": {
      "term": {
        "file-type": "png"
      }
    }
  }
}<p>El prefiltrado está disponible tanto para <a href="https://www.elastic.co/docs/solutions/search/vector/knn#knn-search-filter-example">la búsqueda como</a> para <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query#knn-query-filtering">la consulta knn</a>:</p>{
  "query": {
    "knn": {
      "field": "image-vector",
      "query_vector": [-5, 9, -12],
      "k": 5,
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}<h4>Optimizaciones de prefiltrado</h4><p>Hay un par de optimizaciones que podemos aplicar para cerciorar que el prefiltrado sea eficiente.</p><p>Podemos cambiar a búsqueda exacta si el filtro es muy restrictivo. Cuando hay pocos vectores para comparar, es más rápido realizar una búsqueda exacta en los pocos documentos que cumplen con el filtro.</p><p>Esta es una optimización que se aplica automáticamente en <a href="https://github.com/apache/lucene/blob/eb876b618da5d04c1ad14b04a48321638318493a/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L218">Lucene</a> y Elasticsearch.</p><p>Otro método de optimización implica ignorar los vectores que no satisfacen el filtro. En su lugar, este método comprueba los vecinos de los vectores filtrados que sí pasan el filtro. Este enfoque reduce efectivamente el número de comparaciones ya que no se consideran los vectores filtrados, y continúa explorando vectores conectados al camino actual.</p><p>Este algoritmo es ACORN-1, y el proceso se describe en detalle en <a href="https://www.elastic.co/search-labs/blog/filtered-hnsw-knn-search">esta entrada del blog</a>.</p><h2>Filtrado usando la seguridad a nivel de documento</h2><p><a href="https://www.elastic.co/docs/deploy-manage/users-roles/cluster-or-deployment-auth/controlling-access-at-document-field-level#document-level-security">La Seguridad a Nivel de Documento (DLS)</a> es una función de Elasticsearch que especifica los documentos que los roles de usuario pueden recuperar.</p><p>DLS se realiza mediante consultas. Un rol puede tener una consulta asociada a índices, lo que limita efectivamente los documentos que un usuario que pertenece a ese rol puede recuperar de los índices.</p><p>La consulta de rol se emplea como filtro para <a href="https://github.com/elastic/elasticsearch/blob/c3a1cb34294e902a9f46d7e840ea09965019f456/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java#L92">recuperar los documentos que coinciden con ella</a>, y se almacenan en caché como un BitSet. Este BitSet se emplea entonces para envolver el lector Lucene subyacente, de modo que solo los documentos que se devolvieron de la consulta se consideran <em>activos,</em>es decir, existen en el índice y no fueron eliminados.</p><p>A medida que los documentos en tiempo real se <a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L196">recuperan del lector</a> para realizar la consulta knn, solo se considerarán los documentos disponibles para el usuario. Si hay un prefiltro, se <a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L204">agregarán los documentos DLS a él</a>.</p><p>Esto significa que el filtrado DLS funciona como prefiltro para la búsqueda vectorial aproximada, con las mismas participaciones de rendimiento y optimizaciones.</p><p>DLS con búsqueda exacta tendrá los mismos beneficios que aplicar cualquier filtro: cuantos menos documentos se recuperen de DLS, más eficiente será una búsqueda exacta. Considera también el número de documentos devueltos por DLS; si los roles DLS son muy restrictivos, puedes considerar usar búsqueda exacta en lugar de búsqueda aproximada.</p><h2>Evaluación comparativa</h2><p>En Elasticsearch, queremos cerciorarnos de que el filtrado de búsqueda vectorial sea eficiente. Disponemos <a href="https://elasticsearch-benchmarks.elastic.co/#tracks/so_vector/nightly/default/90d">de un benchmark específico para el filtrado vectorial</a> que realiza búsquedas vectoriales aproximadas con diferentes filtros para cerciorar que la búsqueda vectorial siga recuperando resultados relevantes lo más rápido posible.</p><p>Consulta las <a href="https://elasticsearch-benchmark-analytics.elastic.co/app/dashboards#/view/43b63e80-5ba2-11ed-aede-a742809feed4?_g=(refreshInterval:(pause:!t,value:60000),time:(from:'2025-05-28T01:27:58.456Z',to:'2025-06-30T13:53:26.430Z'))&amp;_a=()">mejoras</a> cuando se introdujo ACORN-1. Para pruebas en las que solo el 2% de los vectores pasan el filtro, la latencia de consulta se reduce al 55% de la duración original:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt820cb0b715cf291c/6a17e227dbb4ff8d49fb5615/3eac3748a33376fc97d957364a5c1f5108d5c58b-1023x896.png" alt="" /><h2>Conclusión</h2><p>El filtrado es una parte integral de la búsqueda. Cerciorar que el filtrado sea eficiente en la búsqueda vectorial y comprender los compromisos y optimizaciones es lo que hace que una búsqueda sea eficiente y precisa o fracase.</p><p>El filtrado afecta al rendimiento de la búsqueda vectorial:</p><ul><li><p>La búsqueda exacta es más rápida cuando se usa filtrado. Deberías considerar usar la búsqueda exacta en lugar de la aproximada si tu filtrado es lo suficientemente restrictivo. Esta es una optimización automática en Elasticsearch.</p></li><li><p>La búsqueda aproximada es más lenta cuando se emplea prefiltrado. El prefiltrado nos permite obtener los k primeros resultados que coinciden con el filtro, a costa de una búsqueda más lenta.</p></li><li><p>El postfiltrado no necesariamente recupera los k primeros resultados, ya que pueden filtrar mediante el filtro cuando se aplica.</p></li></ul><p>¡Feliz filtrado!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-search-filtering</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-search-filtering</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[Lucene]]></category>
    <category><![CDATA[Dentro de Elastic]]></category>
    <dc:creator><![CDATA[Carlos Delgado]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt39ede1736e0f1456/6a17e2282f4a5c5031fa8843/03b1dd4c7bda4fbabd8e374bc2e4f12d5be6ef5f-1600x1150.png" length="0" type="image/png"/>
    <pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Mapear incrustaciones a tipos de campos de Elasticsearch: semantic_text, dense_vector, sparse_vector]]></title>
    <description><![CDATA[Discutir cómo y cuándo usar semantic_text, dense_vector o sparse_vector, y cómo se relacionan con la generación de incrustaciones.]]></description>
    <content:encoded><![CDATA[<p>El uso de incrustaciones para mejorar la relevancia y precisión de la recuperación de información creció significativamente a lo largo de los años. Herramientas como Elasticsearch evolucionaron para soportar este tipo de datos mediante tipos de campos especializados como vectores densos, vectores dispersos y texto semántico. Sin embargo, para lograr buenos resultados, es esencial entender cómo mapear correctamente las incrustaciones a los tipos de campos disponibles de Elasticsearch: <code>semantic_text</code>, <code>dense_vector</code>y <code>sparse_vector</code>.</p><p>En este artículo, discutiremos estos tipos de campos, cuándo usar cada uno y cómo se relacionan con la generación y las estrategias de uso de incrustaciones, tanto durante la indexación como durante la consulta.</p><h2>Tipo vectorial denso</h2><p>El tipo de campo <code>dense_vector</code> en Elasticsearch se emplea para almacenar vectores densos, que son representaciones numéricas de datos como texto, imágenes y audio, donde casi todas las dimensiones son relevantes. Estos vectores se generan empleando modelos de incrustación proporcionados por plataformas como OpenAI, Cohere o Hugging Face, y están diseñados para captar el significado semántico general de los datos, incluso cuando no comparten términos exactos con otros documentos.</p><p>En Elasticsearch, los vectores densos pueden tener hasta 4096 dimensiones dependiendo del modelo empleado. Por ejemplo, el modelo totalmente MiniLM-L6-v2 genera vectores con 384 dimensiones, mientras que el text-embedding-ada-002 de OpenAI produce vectores con 1536 dimensiones.</p><p>El campo <code>dense_vector</code> se adopta comúnmente como el tipo predeterminado para almacenar este tipo de incrustación cuando se necesita mayor control, como usando vectores pregenerados, aplicando funciones de similitud personalizadas o integrando con modelos externos.</p><h3>¿Cuándo y por qué usar dense_vector tipo?</h3><p>Los vectores densos son excelentes para capturar similitudes semánticas entre oraciones, párrafos o documentos completos. Funcionan muy bien cuando el objetivo es comparar el significado general de los textos, aunque no compartan los mismos términos.</p><p>El campo vectorial denso es ideal cuando ya tienes una pipeline externa de generación de embedding usando modelos proporcionados por plataformas como OpenAI, Cohere o Hugging Face y solo quieres almacenar y consultar estos vectores manualmente. Este tipo de campo ofrece alta compatibilidad con modelos de incrustación y total flexibilidad en la generación y consulta, permitiéndote controlar cómo se producen, indexan y emplean los vectores durante la búsqueda.</p><p>Además, soporta diferentes formas de búsqueda semántica, con consultas como k-NN o script_score para casos en los que sea necesario ajustar la lógica de clasificación. Estas posibilidades hacen que el vector denso sea ideal para aplicaciones como RAG (Generación Aumentada por Recuperación), sistemas de recomendación y búsquedas personalizadas basadas en similitudes.</p><p>Por último, el campo te permite personalizar la lógica de relevancia, usando funciones como <code>cosineSimilarity</code>, <code>dotProduct</code> o <code>l2norm</code> para adaptar la clasificación según las necesidades de tu caso de uso. </p><p>Los vectores densos siguen siendo la mejor opción para quienes necesitan flexibilidad, personalización y compatibilidad con casos de uso avanzados como los mencionados anteriormente.</p><h3>¿Cómo usar la consulta para el tipo de vector denso?</h3><p>Las búsquedas en campos definidos como <strong><code>dense_vector</code></strong> emplean la consulta k-vecinos más cercanos. Esta consulta es responsable de encontrar documentos cuyo vector denso está más cercano al vector de consulta. A continuación se muestra un ejemplo de cómo aplicar una consulta k-NN a un campo vectorial denso:</p>{
  "knn": {
    "field": "my_dense_vector",
    "k": 10,
    "num_candidates": 50,
    "query_vector": [/* vector generated by model */]
  }
}<p>Además de la consulta k-NN, si es necesario personalizar el puntaje del documento, también es posible usar la consulta script_score, combinándola con funciones de comparación vectorial como <strong>cosenoSimilitud, Producto PuntoPunto o l2norm</strong> para calcular la relevancia de forma más controlada. Mira el ejemplo:</p>{
"script_score": {
    "query": { "match_all": {} },
    "script": {
      "source": "cosineSimilarity(params.query_vector,
'my_dense_vector') + 1.0",
      "params": {
        "query_vector": [/* vector */]
      }
    }
  }
}<p>Si quieres profundizar más, te recomiendo explorar el artículo <a href="https://www.elastic.co/search-labs/blog/vector-search-set-up-elasticsearch">Cómo configurar la búsqueda vectorial en Elasticsearch.</a></p><p></p><h2>Tipo vectorial disperso</h2><p>El tipo de campo <strong><code>sparse_vector</code></strong> se emplea para almacenar vectores dispersos, que son representaciones numéricas donde la mayoría de los valores son cero y solo unos pocos términos tienen pesos significativos. Este tipo de vector es común en modelos basados en términos como SPLADE o ELSER (Elastic Learned Sparse EncodeR).</p><h3>¿Cuándo y por qué usar tipo vectorial disperso?</h3><p>Los vectores dispersos son ideales cuando se necesita una búsqueda más precisa en términos léxicos, sin sacrificar la inteligencia semántica. Representan el texto como pares token/valor, resaltando solo los términos más relevantes con pesos asociados, lo que proporciona claridad, control y eficiencia.</p><p>Este tipo de campo es especialmente útil cuando se generan vectores basados en términos, como en los modelos ELSER o SPLADE, que asignan diferentes pesos a cada token según su importancia relativa en el texto.</p><p>Para las ocasiones en las que quieres controlar la influencia de palabras específicas en la consulta, los tipos vectoriales dispersos te permiten ajustar manualmente el peso de los términos para optimizar el orden de los resultados.</p><p>Entre los principales beneficios están la transparencia en la búsqueda, ya que es posible entender claramente por qué un documento se consideraba relevante, y la eficiencia de almacenamiento, ya que solo se almacenan los tokens con valor distinto de cero, a diferencia de los vectores densos que almacenan todas las dimensiones.</p><p>Además, los vectores dispersos son el complemento ideal en estrategias de búsqueda híbrida, e incluso pueden combinar con vectores densos para combinar precisión léxica con comprensión semántica.</p><h3>¿Cómo usar la consulta para el tipo de vector disperso?</h3><p>La consulta <strong><code>sparse_vector</code></strong> te permite buscar documentos basándote en un vector de consulta en formato token/valor. Consulta un ejemplo de la consulta a continuación:</p>{
  "query": {
    "sparse_vector": {
      "field": "field_sparse",
      "query_vector": {
        "token1": 0.6,
        "token2": 0.2,
        "token3": 0.9
      }
    }
  }
}<p>Si prefieres usar un modelo capacitado, es posible emplear un punto final de inferencia que transforme automáticamente el texto de consulta en un vector disperso:</p>{
  "query": {
    "sparse_vector": {
      "field": "field_sparse",
      "inference_id": "the inference ID to produce the token/weights",
      "query": "search text"
    }
  }
}<p>Para profundizar en este tema, sugiero leer <a href="https://www.elastic.co/search-labs/blog/sparse-vector-embedding">Understanding sparse vector embeddings with trained ML</a> models.</p><h2>Tipo de texto semántico</h2><p>El tipo de campo <strong><code>semantic_text</code></strong> es la forma más sencilla y directa de usar la búsqueda semántica en Elasticsearch. Gestiona automáticamente la generación de incrustaciones, tanto en tiempo de indexación como en tiempo de consulta, a través de un punto final de inferencia. Esto significa que no tienes que preocuparte por generar o almacenar vectores manualmente.</p><h3>¿Cuándo y por qué usar texto semántico?</h3><p>El campo <code>semantic_text</code> es ideal para quienes quieren empezar con el mínimo esfuerzo técnico y sin tener que manejar vectores manualmente. Este campo automatiza pasos como la generación de incrustaciones y el mapeo de búsqueda vectorial, haciendo que la configuración sea más rápida y cómoda.</p><p>Deberías considerar usar <code>semantic_text</code> cuando valoras la <strong>simplicidad y la abstracción</strong>, ya que <strong>elimina la complejidad de configurar manualmente mapeos, generación de incrustaciones y pipelines de ingestión</strong>. Solo tienes que seleccionar el modelo de inferencia, y Elasticsearch se encarga del resto.</p><p>Los principales beneficios incluyen <strong>la generación automática de incrustaciones,</strong> realizada tanto durante la indexación como durante la consulta, y <strong>el mapeo listo para usar</strong>, que viene preconfigurado para soportar el modelo de inferencia seleccionado.</p><p>Además, el campo <strong>ofrece soporte nativo para la división automática de textos largos (fragmentación de texto),</strong> permitiendo dividir textos grandes en pasajes más pequeños, cada uno con su propia incrustación, lo que mejora la precisión en la búsqueda. Esto aumenta enormemente la productividad, especialmente para equipos que quieren ofrecer valor rápidamente sin tener que lidiar con la ingeniería subyacente de la búsqueda semántica.</p><p>Sin embargo, aunque <code>semantic_text</code> proporciona rapidez y simplicidad, este enfoque tiene algunas limitaciones. Permite el uso de modelos estándar de mercado, siempre que estén disponibles como puntos finales de inferencia en Elasticsearch. Pero <strong>no soporta incrustaciones generadas externamente</strong>, como es posible con el campo <code>dense_vector</code> .</p><p>Si necesitas más control sobre cómo se generan los vectores, quieres usar tus propios embeddings o necesitas combinar varios campos para estrategias avanzadas, los campos <code>dense_vector</code> y <code>sparse_vector</code> ofrecen la flexibilidad necesaria para escenarios más personalizados o específicos de dominio.</p><h3>Cómo usar la consulta para el tipo de texto semántico</h3><p>Antes de <strong><code>semantic_text</code></strong>, era necesario usar una consulta diferente según el tipo de incrustación (densa o dispersa). Se empleaba una consulta <code>sparse_vector</code> para campos dispersos, mientras que <code>dense_vector</code> campos requerían consultas KNN.</p><p>Con el tipo de texto semántico, la búsqueda se realiza usando la <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-semantic-query">consulta semántica</a>, que genera automáticamente el vector de consulta y lo compara con las incrustaciones de los documentos indexados. El tipo <strong><code>semantic_text</code></strong> permite definir un extremo de inferencia para incrustar la consulta, pero si no se especifica ninguno, se aplicará el mismo punto final que se usa durante la indexación a la consulta.</p>{
  "query": {
    "semantic": {
      "field": "semantic_text_field",
      "query": "search text"
    }
  }
}<p>Para saber más, te sugiero leer el artículo <a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Elasticsearch nueva semantic_text mapeo: Simplificando la búsqueda semántica</a>.</p><h2>Conclusión</h2><p>Al elegir cómo mapear incrustaciones en Elasticsearch, es esencial entender cómo quieres generar los vectores y qué nivel de control necesitas sobre ellos. Si buscas simplicidad, el campo de texto semántico permite una búsqueda semántica automática y escalable, lo que lo hace ideal para muchos casos de uso iniciales. Cuando se requiere más control, un rendimiento ajustado o integración con modelos personalizados, los campos vectoriales densos y dispersos proporcionan la flexibilidad necesaria.</p><p>El tipo de campo ideal depende de tu caso de uso, la infraestructura disponible y la madurez de tu pila de aprendizaje automático. Lo más importante es que Elastic ofrece las herramientas para construir sistemas de búsqueda modernos y altamente adaptables.</p><h2>Referencias</h2><ul><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html">Tipo de campo de texto semántico</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/sparse-vector.html">Tipo de campo vectorial disperso</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html">Tipo de campo vectorial denso</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html">Consulta semántica</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-sparse-vector-query.html">Consulta vectorial dispersa</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">Búsqueda kNN</a></p></li><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/sparse-vector-embedding">Comprendiendo las incrustaciones de vectores dispersos con modelos de aprendizaje automático capacitados</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/mapping-embeddings-to-elasticsearch-field-types</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/mapping-embeddings-to-elasticsearch-field-types</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[Mapeos]]></category>
    <dc:creator><![CDATA[Andre Luiz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt72cd3c2601b22886/6a17083b0c4857259901a9dc/f98fdff837db55b466780c0bae672aa6f6c3a966-1200x628.png" length="0" type="image/png"/>
    <pubDate>Tue, 13 May 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Cómo implementar una Mejor Cuantización Binaria (BBQ) en tu caso de uso]]></title>
    <description><![CDATA[Explora por qué implementarías Better Binary Quantization (BBQ) en tu caso de uso y cómo hacerlo.]]></description>
    <content:encoded><![CDATA[<p>La búsqueda vectorial proporciona la base al implementar la búsqueda semántica de texto o la búsqueda de similitud de imágenes, videos o audio. Con la búsqueda vectorial, los vectores son representaciones matemáticas de datos que pueden ser enormes y, a veces, lentos. Better Binary Quantization (en lo sucesivo, BBQ) funciona como un método de compresión para vectores. Le permite encontrar las coincidencias correctas mientras reduce los vectores para que sean más rápidos de buscar y procesar. Este artículo cubrirá BBQ y rescore_vector, un campo solo disponible para índices cuantificados que vuelve a calificar automáticamente los vectores.</p><p>Todas las consultas y salidas completas mencionadas en este artículo se pueden encontrar en nuestro <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/how-and-why-bbq">repositorio de código de Elasticsearch Labs</a>.</p><h2>¿Por qué implementar Better Binary Quantization (BBQ) en tu caso de uso?</h2>Nota: para una comprensión profunda de cómo funcionan las matemáticas detrás del asado, consulte la <a href="https://www.elastic.co/es/search-labs/blog/bbq-implementation-into-use-case#further-learning">sección "Aprendizaje adicional"</a> a continuación. Para los propósitos de este blog, la atención se centra en la implementación.<p>Aunque las matemáticas son interesantes, son cruciales si quieres comprender completamente por qué tus búsquedas vectoriales siguen siendo precisas. En última instancia, todo esto se reduce a la compresión, ya que resulta que con los algoritmos actuales de búsqueda vectorial estás limitado por la velocidad de lectura de los datos. Por lo tanto, si puedes meter todos esos datos en la memoria, obtienes un aumento significativo de velocidad en comparación con leer desde el almacenamiento (<a href="https://sre.google/static/pdf/rule-of-thumb-latency-numbers-letter.pdf">la memoria es aproximadamente 200 veces más rápida que los SSD</a>).</p><p>Hay algunas cosas a tener en cuenta:</p><ul><li><p>Los índices basados en gráficos como <a href="https://arxiv.org/pdf/1603.09320">HNSW</a> (Hierarchical Navigable Small World) son los más rápidos para la recuperación de vectores.</p><ul><li><p>HNSW: Un algoritmo de búsqueda aproximado del vecino más cercano que construye una estructura de gráficos multicapa para permitir búsquedas eficientes de similitud de alta dimensión.</p></li></ul></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt760bd95c206bfa8f/6a17e2ad505ac393f7ad8a95/590f3b3c72a76023a38a0436cd9ff90a9f80e936-1964x1262.png" alt="HNSW: Un algoritmo de búsqueda aproximado del vecino más cercano que construye una estructura de gráficos multicapa para permitir búsquedas eficientes de similitud de alta dimensión." /><ul><li><p>HNSW está fundamentalmente limitado en velocidad por la velocidad de lectura de datos de la memoria o, en el peor de los casos, del almacenamiento.</p><ul><li><p>Idealmente, desea poder cargar todos sus vectores almacenados en la memoria.</p></li></ul></li><li><p>Los modelos de incrustación generalmente producen vectores con precisión float32, 4 bytes por número de punto flotante.</p></li><li><p>Y finalmente, dependiendo de cuántos vectores y / o dimensiones tenga, puede quedar sin memoria muy rápidamente para mantener todos sus vectores.</p></li></ul><p>Dando esto por sentado, ve que surge un problema rápidamente una vez que comienza a ingerir millones o incluso miles de millones de vectores, cada uno con potencialmente cientos o incluso miles de dimensiones. La sección titulada "<a href="https://www.elastic.co/es/search-labs/blog/bbq-implementation-into-use-case#approximate-numbers-on-the-compression-ratios">Números aproximados en las relaciones de compresión</a>" proporciona algunos números aproximados.</p><h2>¿Qué necesitas para empezar?</h2><p>Para comenzar, necesitará lo siguiente:</p><ul><li><p>Si usas Elastic Cloud o en las instalaciones, necesitarás una versión de Elasticsearch superior a la 8.18. Si bien BBQ se introdujo en 8.16, en este artículo, usará <code>vector_rescore</code>, que se introdujo en 8.18.</p></li><li><p>Además, también deberá cerciorar de que haya un <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/8.18/ml-settings.html">nodo de aprendizaje automático (ML)</a> en el clúster. (Nota: se necesita un nodo de ML con un mínimo de 4 GB para cargar el modelo, pero es probable que necesite nodos mucho más grandes para cargas de trabajo de producción completas).</p></li><li><p>Si emplea Serverless, deberá seleccionar una instancia optimizada para vectores.</p></li><li><p>También necesitará un nivel básico de conocimiento sobre bases de datos vectoriales. Si aún no estás familiarizado con los conceptos de búsqueda vectorial en Elastic, es posible que desees consultar primero los siguientes recursos:</p><ul><li><p><a href="https://www.elastic.co/es/search-labs/blog/elastic-vector-database-practical-example">Navegación por una base de datos vectorial elástica</a></p></li><li><p><a href="https://www.elastic.co/es/blog/retrieval-augmented-generation-explained">Las grandes ideas detrás de la generación aumentada de recuperación</a></p></li></ul></li></ul><h2>Mejor implementación de cuantización binaria (BBQ)</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt18df00df95ff2ca7/6a17e2af414c6411989450df/4d388078495566f0527e931e0c2e38facdce83c6-1503x748.png" alt="Implementación de asado con Elasticsearch." /><p>Para simplificar este blog, empleará funciones integradas cuando estén disponibles. En este caso, tienes el modelo de incrustación de vectores <a href="https://www.elastic.co/es/guide/en/machine-learning/8.17/ml-nlp-e5.html"><code>.multilingual-e5-small</code></a> que se ejecutará directamente dentro de Elasticsearch en un nodo de aprendizaje automático. Tenga en cuenta que puede reemplazar el modelo <code>text_embedding</code> con el incrustador de su elección (<a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/8.18/infer-service-openai.html">OpenAI,</a> <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/8.18/infer-service-google-ai-studio.html">Google AI Studio</a>, <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/8.18/infer-service-cohere.html">Cohere</a> y muchos más). Si su modelo preferido aún no está integrado, también puede <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/8.18/bring-your-own-vectors.html">traer sus propias incrustaciones de vectores densos</a>).</p><p>En primer lugar, deberá crear un punto de enlace de inferencia para generar vectores para un fragmento de texto determinado. Ejecutarás todos estos comandos desde la <a href="https://www.elastic.co/es/guide/en/kibana/8.18/console-kibana.html">consola de herramientas de desarrollo de Kibana</a>. Este comando descargará el <code>.multilingual-e5-small</code>. Si aún no existe, configurará su punto final; Esto puede tardar un minuto en ejecutar. Puede ver la salida esperada en el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/01-create-an-inference-endpoint-output.json">archivo 01-create-an-inference-endpoint-output.json</a> en la carpeta Salidas. </p>PUT _inference/text_embedding/my_e5_model
{
  "service": "elasticsearch",
  "service_settings": {
    "num_threads": 1,
    "model_id": ".multilingual-e5-small",
    "adaptive_allocations": {
      "enabled": true,
      "min_number_of_allocations": 1
    }
  }
}<p>Una vez que esto regresó, su modelo se configurará y podrá probar que el modelo funciona como se espera con el siguiente comando. Puede ver el resultado esperado en el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/02-embed-text-output.json">archivo 02-embed-text-output.json</a> en la carpeta Salidas.</p>POST _inference/text_embedding/my_e5_model
{
  "input": "my awesome piece of text"
}<p>Si tiene problemas relacionados con el modelo capacitado que no se asigna a ningún nodo, es posible que deba iniciar el modelo manualmente.</p>POST _ml/trained_models/.multilingual-e5-small/deployment/_start<p>Ahora vamos a crear una nueva asignación con 2 propiedades, un campo de texto estándar (<code>my_field</code>) y un campo vectorial denso (<code>my_vector</code>) con 384 dimensiones para que coincida con la salida del modelo de incrustación. También anulará el <code>index_options.type to bbq_hnsw</code>. Puede ver el resultado esperado en el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/03-create-byte-qauntized-index-output.json">archivo 03-create-byte-qauntized-index-output.json</a> en la carpeta Salidas.</p>PUT bbq-my-byte-quantized-index
{
  "mappings": {
    "properties": {
      "my_field": {
        "type": "text"
      },
      "my_vector": {
        "type": "dense_vector",
        "dims": 384,
        "index_options": {
          "type": "bbq_hnsw"
        }
      }
    }
  }
}<p>Para cerciorarte de que Elasticsearch genere tus vectores, puedes usar una <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/8.18/ingest.html">canalización de ingesta</a>. Esta canalización requerirá 3 cosas: el punto final, (<code>model_id</code>), el <code>input_field</code> para el que desea crear vectores y el <code>output_field</code> en el que almacenar esos vectores. El primer comando siguiente creará una canalización de ingesta de inferencia, que usa el <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/current/inference-apis.html">servicio de inferencia </a>en segundo plano, y el segundo probará que la canalización funciona correctamente. Puede ver la salida esperada en el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/04-create-and-simulate-ingest-pipeline-output.json">archivo 04-create-and-simulate-ingest-pipeline-output.json</a> en la carpeta Salidas. </p>PUT _ingest/pipeline/my_inference_pipeline
{
  "processors": [
    {
      "inference": {
        "model_id": "my_e5_model",
        "input_output": [
          {
            "input_field": "my_field",
            "output_field": "my_vector"
          }
        ]
      }
    }
  ]
}

POST _ingest/pipeline/my_inference_pipeline/_simulate
{
  "docs": [
    {
      "_source": {
        "my_field": "my awesome text field"
      }
    }
  ]
}<p>Ahora está listo para agregar algunos documentos con los primeros 2 comandos a continuación y para probar que sus búsquedas funcionan con el 3er comando. Puede desproteger el resultado esperado en el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/05-bbq-index-output.json">archivo 05-bbq-index-output.json</a> en la carpeta Salidas. </p>PUT bbq-my-byte-quantized-index/_doc/1?pipeline=my_inference_pipeline
{
    "my_field": "my awesome text field"
}

PUT bbq-my-byte-quantized-index/_doc/2?pipeline=my_inference_pipeline
{
    "my_field": "some other sentence"
}

GET bbq-my-byte-quantized-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}<p>Como se recomienda en <a href="https://www.elastic.co/es/search-labs/blog/better-binary-quantization-lucene-elasticsearch#lucene-benchmarking">esta publicación</a>, se recomienda volver a puntuar y sobremuestrear cuando se escala a cantidades no triviales de datos porque ayudan a mantener una alta precisión de recuperación mientras se benefician de los beneficios de la compresión. A partir de la versión 8.18 de Elasticsearch, puedes hacerlo de esta manera usando <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/8.18/knn-search.html#dense-vector-knn-search-rescoring">rescore_vector</a>. La salida esperada se encuentra en el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/06-bbq-search-8-18-output.json">archivo 06-bbq-search-8-18-output.json</a> en la carpeta Outputs.</p>GET bbq-my-byte-quantized-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "rescore_vector": {
              "oversample": 3
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}<p>¿Cómo se comparan estos puntajes con los que obtendría por los datos sin procesar? Si vuelves a hacer todo lo anterior pero con <code>index_options.type: hnsw</code>, verás que los puntajes son muy comparables. Puede ver el resultado esperado en el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/07-raw-vector-output.json">archivo 07-raw-vector-output.json</a> en la carpeta Salidas.</p>PUT my-raw-vector-index
{
  "mappings": {
    "properties": {
      "my_field": {
        "type": "text"
      },
      "my_vector": {
        "type": "dense_vector",
        "dims": 384,
        "index_options": {
          "type": "hnsw"
        }
      }
    }
  }
}

PUT my-raw-vector-index/_doc/1?pipeline=my_inference_pipeline
{
    "my_field": "my awesome text field"
}

PUT my-raw-vector-index/_doc/2?pipeline=my_inference_pipeline
{
    "my_field": "some other sentence"
}

GET my-raw-vector-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}<h2>Números aproximados en las relaciones de compresión</h2><p>Los requisitos de almacenamiento y memoria pueden convertir rápidamente en un desafío importante cuando se trabaja con la búsqueda vectorial. El siguiente desglose ilustra cómo las diferentes técnicas de cuantificación reducen significativamente la huella de memoria de los datos vectoriales.</p><p>Vectores (V)</p><p>Dimensiones (D)</p><p>sin procesar (V x P x 4)</p><p>int8 (V x (D x 1 + 4))</p><p>int4 (V x (D x 0.5 + 4))</p><p>asado (V x (D x 0.125 + 4))</p><p>10,000,000</p><p>384</p><p>14,31 GB</p><p>3,61 GB</p><p>1,83 GB</p><p>0,58 GB</p><p>50,000,000</p><p>384</p><p>71,53 GB</p><p>18,07 GB</p><p>9,13 GB</p><p>2,89 GB</p><p>100,000,000</p><p>384</p><p>143,05 GB</p><p>36,14 GB</p><p>18,25 GB</p><p>5,77 GB</p><h2>Conclusión</h2><p>BBQ es una optimización que puede aplicar a sus datos vectoriales para la compresión sin sacrificar la precisión. Funciona convirtiendo vectores en bits, lo que le permite buscar los datos de manera efectiva y le permite escalar sus flujos de trabajo de IA para acelerar las búsquedas y optimizar el almacenamiento de datos.</p><h2>Aprendizaje adicional</h2><p>Si está interesado en obtener más información sobre el asado, cerciorar de consultar los siguientes recursos:</p><ul><li><p><a href="https://www.elastic.co/es/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Cuantificación binaria (BBQ) en Lucene y Elasticsearch</a></p></li><li><p><a href="https://www.elastic.co/es/search-labs/blog/bit-vectors-elasticsearch-bbq-vs-pq">Mejor cuantización binaria (BBQ) frente a cuantificación de productos</a></p></li><li><p><a href="https://www.elastic.co/es/search-labs/blog/optimized-scalar-quantization-elasticsearch">Cuantización escalar optimizada: cuantificación binaria aún mejor</a></p></li><li><p><a href="https://www.youtube.com/watch?v=04NzMt2Nigc">Mejor cuantificación binaria (BBQ): De bytes a BBQ, el secreto para una mejor búsqueda vectorial por Ben Trent</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/bbq-implementation-into-use-case</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/bbq-implementation-into-use-case</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[Conceptos básicos]]></category>
    <dc:creator><![CDATA[Sachin Frayne,Jessica Garson]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3dd0495b536b2615/6a17e2b0414c6488459450e3/66842055367cdd795532b01c167f2a4b03dc65e3-1200x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 23 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch BBQ vs. OpenSearch FAISS: comparación del rendimiento de la búsqueda vectorial]]></title>
    <description><![CDATA[Una comparación de rendimiento entre Elasticsearch BBQ y OpenSearch FAISS.]]></description>
    <content:encoded><![CDATA[<p><strong>Búsqueda vectorial con cuantificación binaria: Elasticsearch con BBQ es 5 veces más rápido que OpenSearch con FAISS</strong>. Elastic recibió solicitudes de nuestra comunidad para aclarar las diferencias de rendimiento entre Elasticsearch y OpenSearch, particularmente en el ámbito de la búsqueda semántica/búsqueda vectorial, por lo que realizamos estas pruebas de rendimiento para proporcionar comparaciones claras y basadas en datos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta8792b5284e7a553/6a1709b9961e69a084c4cee6/7f4f8d08f7bee188423e4e65f0caefc7e34f0355-1600x681.png" alt="Elasticsearch BBQ vs. OpenSearch FAISS - Comparación de retiradas de velocidad y rendimiento" /><h2>Enfrentamiento de cuantificación binaria</h2><p>Almacenar vectores de alta dimensión en su forma original puede requerir mucha memoria. Las técnicas de cuantificación comprimen estos vectores en una representación compacta, lo que reduce significativamente la huella de memoria. Luego, la búsqueda opera en el espacio comprimido, lo que reduce la complejidad computacional y hace que las búsquedas sean más rápidas, especialmente en grandes conjuntos de datos.</p><p>Elastic está comprometida a convertir Lucene en un motor vectorial de alto rendimiento. Introdujimos <a href="https://www.elastic.co/es/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Better Binary Quantization</a> (BBQ) en Elasticsearch 8.16 sobre Lucene y lo evolucionamos aún más en 8.18 y 9.0. BBQ se basa en un nuevo enfoque de <a href="https://www.elastic.co/es/search-labs/blog/optimized-scalar-quantization-elasticsearch">cuantización escalar</a> que reduce las dimensiones float32 a bits, ofreciendo una reducción de memoria del ~95% manteniendo una alta calidad de clasificación.</p><p>OpenSearch, por otro lado, emplea múltiples motores vectoriales: nmslib (ahora obsoleto), Lucene y FAISS. En un <a href="https://www.elastic.co/es/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison">blog anterior</a>, comparamos Elasticsearch y OpenSearch para la búsqueda vectorial. Empleamos tres conjuntos de datos diferentes y probamos diferentes combinaciones de motores y configuraciones en ambos productos.</p><p>Este blog se centra en los algoritmos de cuantificación binaria disponibles actualmente en ambos productos. Probamos Elasticsearch con BBQ y OpenSearch con la <a href="https://opensearch.org/docs/latest/search-plugins/knn/knn-vector-quantization/#binary-quantization">cuantificación binaria de FAISS</a> empleando la pista <a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector">openai_vector</a> Rally.</p><p>El objetivo principal fue evaluar el desempeño de ambas soluciones bajo el mismo nivel de recuperación. ¿Qué significa <em>recordar</em> ? La recuperación es una métrica que mide cuántos de los resultados relevantes son recuperados con éxito por un sistema de búsqueda.</p><p>En esta evaluación, recall@k es particularmente importante, donde <em>k</em> representa el número de resultados principales considerados. <strong>Recall@10</strong>, <strong>Recall@50 y Recall@100</strong> medir cuántos de los resultados relevantes reales aparecen en los 10, 50 y 100 elementos principales recuperados, respectivamente. La recuperación se expresa en una escala de 0 a 1 (o de 0% a 100% de precisión). Y eso es importante porque estamos hablando de KNN aproximado (ANN) y no de KNN exacto, donde la recordación es siempre 1 (100%).</p><p>Para cada valor de <em>k</em> también especificamos <em>n, </em>que es el número de candidatos considerados antes de aplicar la clasificación final. Esto significa que para Recall@10, Recall@50 y Recall@100, el sistema primero recupera <em>n</em> candidatos empleando el algoritmo de cuantificación binaria y luego los clasifica para determinar si los <em>k</em> resultados principales contienen los elementos relevantes esperados.</p><p>Al controlar <em>n</em>, podemos analizar el equilibrio entre eficiencia y precisión. Un <em>n</em> más alto generalmente <strong>aumenta la</strong> recuperación, ya que hay más candidatos disponibles para la clasificación, pero también <strong>aumenta</strong> la latencia y<strong> disminuye el </strong>rendimiento. Por el contrario, un <em>n</em> más bajo acelera la recuperación, pero puede reducir la recordación si se incluyen muy pocos candidatos relevantes en el conjunto inicial.</p><p>En esta comparación, Elasticsearch demostró una latencia más baja y un rendimiento más alto que OpenSearch en configuraciones idénticas.</p><h2>Metodología</h2><p>La configuración completa, junto con los scripts de Terraform, los manifiestos de Kubernetes y la pista de Rally específica está disponible en este <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/tree/bbq">repositorio</a> en <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/tree/bbq/rally-custom/custom_tracks/elasticsearch/openai_vector_bq"><em>openai_vector_bq</em></a>.</p><p>Al igual que con los puntos de referencia anteriores, empleamos un clúster de Kubernetes compuesto por:</p><ul><li><p>1 pool de nodos para Elasticsearch 9.0 con 3 máquinas <code>e2-standard-32</code> (128 GB de RAM y 32 CPU)</p></li><li><p>1 grupo de nodos para OpenSearch 2.19 con 3 máquinas <code>e2-standard-32</code> (128 GB de RAM y 32 CPU)</p></li><li><p>1 grupo de nodos para Rally con 2 máquinas <code>e2-standard-4</code> (16 GB de RAM y 4 CPU)</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt934e988e96a5849f/6a1709bbacf08838f9be9ac1/169bb6033f6eebfd1b177b3446bf916fde4ee5c5-1600x856.png" alt="Montaje de la metodología Elasticsearch BBQ vs Opensearch FAISS" /><p>Configuramos un clúster de Elasticsearch versión 9.0 y un clúster de OpenSearch versión 2.19.</p><p>Tanto Elasticsearch como OpenSearch se probaron exactamente con la misma configuración: usamos <a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector">openai_vector</a> pista de Rally con <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/commit/b97d5d95c22c8cf862f2030964524bdd156a5da3">algunas modificaciones</a> , que emplea 2,5 millones de documentos del <a href="https://huggingface.co/datasets/BeIR/nq">conjunto de datos NQ</a> enriquecidos con incrustaciones generadas con el <a href="https://openai.com/blog/new-and-improved-embedding-model">modelo text-embedding-ada-002</a> de OpenAI.</p>{
  "source-file": "open_ai_corpus-initial-indexing.json.bz2",
  "document-count": 2580961,
  "compressed-bytes": 32076749416,
  "uncompressed-bytes": 90263571686
}<p>Los resultados informan sobre la latencia y el rendimiento medidos en diferentes niveles de recuperación (recall@10, recall@50 y recall@100) empleando 8 clientes simultáneos para realizar operaciones de búsqueda. Usamos un solo fragmento y no réplicas.</p><p>Ejecutamos las siguientes combinaciones de k-n-rescore, p. ej. 10-2000-2000, o <em>K:10</em>, <em>N:2000</em> y <em>Rescore:2000</em> recuperarían los mejores K (10) sobre N candidatos (2000) aplicando un Rescore sobre 2000 resultados (que es equivalente a un "factor de sobremuestreo" de 1). Cada búsqueda se ejecutó 10.000 veces con 1000 búsquedas como calentamiento:</p><p></p><p><u><strong>Recall@10</strong></u></p><ul><li><p>10-40-40</p></li><li><p>10-50-50</p></li><li><p>10-100-100</p></li><li><p>10-200-200</p></li><li><p>10-500-500</p></li><li><p>10-750-750</p></li><li><p>10-1000-1000</p></li><li><p>10-1500-1500</p></li><li><p>10-2000-2000</p></li></ul><p><u><strong>Recall@50</strong></u></p><ul><li><p>50-150-150</p></li><li><p>50-200-200</p></li><li><p>50-250-250</p></li><li><p>50-500-500</p></li><li><p>50-750-750</p></li><li><p>50-1000-1000</p></li><li><p>50-1200-1200</p></li><li><p>50-1500-1500</p></li><li><p>50-2000-2000</p></li></ul><p><u><strong>Recall@100</strong></u></p><ul><li><p>100-200-200</p></li><li><p>100-250-250</p></li><li><p>100-300-300</p></li><li><p>100-500-500</p></li><li><p>100-750-750</p></li><li><p>100-1000-1000</p></li><li><p>100-1200-1200</p></li><li><p>100-1500-1500</p></li><li><p>100-2000-2000</p></li></ul><p>Para replicar el punto de referencia, los manifiestos de Kubernetes para rally-elasticsearch y rally-opensearch tienen todas las variables relevantes externalizadas en un ConfigMap, disponible <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/bbq/k8s/rally-openai_vector-es-bq.yml">aquí</a> (ES) y <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/bbq/k8s/rally-openai_vector-os-bq.yml">aquí</a> (SO). El parámetro <em>search_ops</em> se puede personalizar para probar cualquier combinación de k, n y rescore.</p><h3>Configuración de OpenSearch Rally</h3><p><code>/k8s/rally-openai_vector-os-bq.yml</code></p>apiVersion: v1
kind: ConfigMap
metadata:
  name: rally-params-os
  labels:
    app: rally-opensearch
data:
  user-tags.json: |
    {
      "product": "OpenSearch",
      "product-version": "OpenSearch-2.19.0",
      "product-label": "OpenSearch-2.19-faiss",
      "benchmark-run": "19-feb-recall@100"
    }
  track-params.json: |
    {
      "mapping_type": "vectors-only-mapping-with-docid",
      "standalone_search_clients": 8,
      "standalone_search_iterations": 5000,
      "ann_threshold": 0,
      "vector_mode": "on_disk",
      "compression_level": "32x",
      "vector_method_name": "hnsw",
      "vector_method_engine": "faiss",
      "search_ops": [
        [100, 200, 200],
        [100, 250, 250],
        [100, 300, 300],
        [100, 500, 500],
        [100, 750, 750],
        [100, 1000, 1000],
        [100, 1200, 1200],
        [100, 1500, 1500],
        [100, 2000, 2000]
      ]
    }<h3>Configuración del índice de Opensearch</h3><p>Las variables de ConfigMap se emplean en la configuración del índice, algunos parámetros se dejan sin cambios. La cuantización de 1 bit en OpenSearch se configura <a href="https://opensearch.org/docs/latest/search-plugins/knn/knn-vector-quantization/#binary-quantization">estableciendo el nivel de compresión en "32x".</a></p><p><code>index-vectors-only-mapping-with-docid-mapping.json</code></p>{
  "settings": {
    {% if preload_pagecache %}
    "index.store.preload": [
      "vec", "vex", "vem", "veq", "veqm", "veb", "vebm"
    ],
    {% endif %}
    "index.number_of_shards": {{ number_of_shards | default(1) }},
    "index.number_of_replicas": {{ number_of_replicas | default(0) }},
    "index.knn": true,
    "index.knn.advanced.approximate_threshold": {{ ann_threshold | default(15000) }}
  },
  "mappings": {
    "dynamic": false,
    "properties": {
      "docid": {
        "type": "keyword"
      },
      "emb": {
        "type": "knn_vector",
        "dimension": 1536,
        "space_type": "innerproduct",
        "data_type": "float",
        "mode": {{ vector_mode | default("in_memory") | tojson }},
        "compression_level": {{ compression_level | default("32x") | tojson }},
        "method": {
          "name": {{ vector_method_name | default("hnsw") | tojson }},
          "engine": {{ vector_method_engine | default("faiss") | tojson }},
          "parameters": {
            "ef_construction": 100,
            "m": 16
          }
        }
      }
    }
  }
}<h3>Configuración de Elasticsearch Rally</h3><p><code>/k8s/rally-openai_vector-es-bq.yml</code></p>apiVersion: v1
kind: ConfigMap
metadata:
  name: rally-params-es
  labels:
    app: rally-elasticsearch
data:
  user-tags.json: |
    {
      "product": "Elasticsearch",
      "product-version": "Elasticsearch-9.0.0-ade01164",
      "product-label": "Elasticsearch-9.0-BBQ",
      "benchmark-run": "19-feb-recall@100"
    }
  track-params.json: |
    {
      "mapping_type": "vectors-only-mapping-with-docid",
      "standalone_search_clients": 8,
      "standalone_search_iterations": 5000,
      "vector_index_type": "bbq_hnsw",
      "search_ops": [
        [100, 200, 200],
        [100, 250, 250],
        [100, 300, 300],
        [100, 500, 500],
        [100, 750, 750],
        [100, 1000, 1000],
        [100, 1200, 1200],
        [100, 1500, 1500],
        [100, 2000, 2000]
      ]
    }<h3>Configuración del índice de Elasticsearch</h3><p><code>index-vectors-only-mapping-with-docid-mapping.json</code></p>{
  "settings": {
    {# non-serverless-index-settings-marker-start #}
    {%- if build_flavor != "serverless" or serverless_operator == true -%}
    {% if preload_pagecache %}
    "index.store.preload": [ "vec", "vex", "vem", "veq", "veqm", "veb", "vebm" ],
    {% endif %}
    "index.number_of_shards": {{ number_of_shards | default(1) }},
    "index.number_of_replicas": {{ number_of_replicas | default(0) }}
    {%- endif -%}
    {# non-serverless-index-settings-marker-end #}
  },
  "mappings": {
    "dynamic": false,
    "properties": {
      "docid": {
        "type": "keyword"
      },
      "emb": {
        "type": "dense_vector",
        "element_type": "float",
        "dims": 1536,
        "index": true,
        "similarity": "dot_product",
        "index_options": {
          "type": {{ vector_index_type | default("bbq_hnsw") | tojson }},
          "ef_construction": 100,
          "m": 16
        }
      }
    }
  }
}<h2>Resultados</h2><p>Hay varias formas de interpretar los resultados. Tanto para la latencia como para el rendimiento, trazamos un gráfico simplificado y detallado en cada nivel de recuperación. Es fácil ver diferencias si consideramos que "más alto es mejor" para cada métrica. Sin embargo, la latencia es negativa (más baja es mejor), mientras que el rendimiento es positivo. Para los gráficos simplificados, usamos <strong>(recuperación / latencia) * 10000 </strong>(llamado simplemente "velocidad") y<strong> recuperación * rendimiento</strong>, por lo que ambas métricas significan que más velocidad y más rendimiento son mejores. Vamos a ello.</p><h3>Recuperación @ 10 - simplificado</h3><p>En ese nivel de recuperación, Elasticsearch BBQ es hasta <strong>5 veces más rápido </strong>(3,9 veces más rápido en promedio) y tiene <strong>3,2 veces más rendimiento</strong> en promedio que OpenSearch FAISS.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3de6b3460127a8ae/6a1709bcb339d55975769f6f/d580ad53e8974bd3aa75957c413a0136c4e465c5-1600x681.png" alt="Elasticsearch BBQ es hasta 5 veces más rápido (3,9 veces más rápido de media) y tiene un rendimiento media 3,2 veces mayor que OpenSearch FAISS en velocidad y Recall@10" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd47045013cac5e9d/6a1709be0e2e49599a41a088/18edce667fe36ab95033264ef8df6f352dda2425-2044x866.png" alt="Elasticsearch BBQ es hasta 5 veces más rápido (3,9 veces más rápido de media) y tiene un rendimiento media 3,2 veces mayor que OpenSearch FAISS." /><h4>Retiro @ 10 - Detallado</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0ecb02d4a2967dbd/6a1709bf14b270c04fe3c5c6/a7459b87e679f4ad963d0e2f1685499b40f6f050-1600x799.png" alt="Comparación detallada de latencias de recall@10 Elasticsearch BBQ vs Opensearch FAISS" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt182da309ad6cfd39/6a1709c166c4f99077f8bfd8/c6036f55a13377654d296eb3148c7199e1965475-1600x799.png" alt="Comparación detallada de recall@10 rendimiento Elasticsearch BBQ vs Opensearch FAISS." /><p></p><p>tarea</p><p>latencia.media</p><p>rendimiento.mean</p><p>avg_recall</p><p>Elasticsearch-9.0-BBQ</p><p>10-100-100</p><p>11.70</p><p>513.58</p><p>0.89</p><p>Elasticsearch-9.0-BBQ</p><p>10-1000-100</p><p>27.33</p><p>250.55</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>10-1500-1500</p><p>35.93</p><p>197.26</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>10-200-200</p><p>13.33</p><p>456.16</p><p>0.92</p><p>Elasticsearch-9.0-BBQ</p><p>10-2000-2000</p><p>44.27</p><p>161.40</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>10-40-40</p><p>10.97</p><p>539.94</p><p>0.84</p><p>Elasticsearch-9.0-BBQ</p><p>10-50-50</p><p>11.00</p><p>535.73</p><p>0.85</p><p>Elasticsearch-9.0-BBQ</p><p>10-500-500</p><p>19.52</p><p>341.45</p><p>0.93</p><p>Elasticsearch-9.0-BBQ</p><p>10-750-750</p><p>22.94</p><p>295.19</p><p>0.94</p><p>OpenSearch-2.19-faiss</p><p>10-100-100</p><p>35.59</p><p>200.61</p><p>0.94</p><p>OpenSearch-2.19-faiss</p><p>10-1000-1000</p><p>156.81</p><p>58.30</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>10-1500-1500</p><p>181.79</p><p>42.97</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>10-200-200</p><p>47.91</p><p>155.16</p><p>0.95</p><p>OpenSearch-2.19-faiss</p><p>10-2000-2000</p><p>232.14</p><p>31.84</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>10-40-40</p><p>27.55</p><p>249.25</p><p>0.92</p><p>OpenSearch-2.19-faiss</p><p>10-50-50</p><p>28.78</p><p>245.14</p><p>0.92</p><p>OpenSearch-2.19-faiss</p><p>10-500-500</p><p>79.44</p><p>97.06</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>10-750-750</p><p>104.19</p><p>75.49</p><p>0.96</p><h3>Recuperación @ 50 - simplificado</h3><p>En ese nivel de recuperación, Elasticsearch BBQ es <strong>hasta 5 veces más rápido</strong> (4,2 veces más rápido en promedio) y tiene <strong>3,9 veces más rendimiento</strong> en promedioque OpenSearch FAISS.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt486683f2c7a1d677/6a1709c2a6c2b995bde796a3/3189ffb330948b35854eeea9ae317d4846c14972-1600x681.png" alt="Comparación de rendimiento vectorial Recal @50 Elasticsearch BBQ vs Opensearch FAISS" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltba830c6741784682/6a1709c4a292990627d00ff7/607383d674dcf0b8f94bfb1a450063f52fcbeb15-2060x876.png" alt="Elasticsearch BBQ puede ser hasta 5 veces más rápido (4,2 veces más rápido de media) y tiene un rendimiento media 3,9 veces mayor que OpenSearch FAISS." /><h4>Resultados detallados - Recall @ 50</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt821775e7f87e5ede/6a1709c6a6c2b90af3e796a7/ebfffe0b776aad31dd03d315cfbf5aa098b41226-1600x789.png" alt="Recall@50 Resultados de latencia de Elasticsearch BBQ y Opensearch FAISS" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4f47784ed1017c15/6a1709c7e8fbce0fbe39fbf5/ae20cf870a65c400a2112bbad62eb56e244f549a-1600x799.png" alt="Recall@50 Resultados del rendimiento de Elasticsearch BBQ y Opensearch FAISS" /><p></p><p>Tarea</p><p>Latencia media</p><p>Media de rendimiento</p><p>Retiro promedio</p><p>Elasticsearch-9.0-BBQ</p><p>50-1000-1000</p><p>25.71</p><p>246.44</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>50-1200-1200</p><p>28.81</p><p>227.85</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>50-150-150</p><p>13.43</p><p>362.90</p><p>0.90</p><p>Elasticsearch-9.0-BBQ</p><p>50-1500-1500</p><p>33.38</p><p>202.37</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>50-200-200</p><p>12.99</p><p>406.30</p><p>0.91</p><p>Elasticsearch-9.0-BBQ</p><p>50-2000-2000</p><p>42.63</p><p>163.68</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>50-250-250</p><p>14.41</p><p>373.21</p><p>0.92</p><p>Elasticsearch-9.0-BBQ</p><p>50-500-500</p><p>17.15</p><p>341.04</p><p>0.93</p><p>Elasticsearch-9.0-BBQ</p><p>50-750-750</p><p>31.25</p><p>248.60</p><p>0.94</p><p>OpenSearch-2.19-faiss</p><p>50-1000-1000</p><p>125.35</p><p>62.53</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>50-1200-1200</p><p>143.87</p><p>54.75</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>50-150-150</p><p>43.64</p><p>130.01</p><p>0.89</p><p>OpenSearch-2.19-faiss</p><p>50-1500-1500</p><p>169.45</p><p>46.35</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>50-200-200</p><p>48.05</p><p>156.07</p><p>0.91</p><p>OpenSearch-2.19-faiss</p><p>50-2000-2000</p><p>216.73</p><p>36.38</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>50-250-250</p><p>53.52</p><p>142.44</p><p>0.93</p><p>OpenSearch-2.19-faiss</p><p>50-500-500</p><p>78.98</p><p>97.82</p><p>0.95</p><p>OpenSearch-2.19-faiss</p><p>50-750-750</p><p>103.20</p><p>75.86</p><p>0.96</p><h3>Retiro @ 100</h3><p>En ese nivel de recuperación, Elasticsearch BBQ es <strong>hasta 5 veces más rápido </strong>(promedio 4,6 veces más rápido) y tiene <strong>3,9 veces más rendimiento </strong>en promedio que OpenSearch FAISS.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt380eb9165343a7b3/6a1709c9acf0882669be9ac5/d3f29db64cbde9956de1fa3ae64a75f15141a2bb-1600x681.png" alt="Retirar los resultados de FAISS de @100 Elasticsearch BBQ VS Opensearch" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt505ce8aa70898390/6a1709cb1949f73a37e7a9bb/10aff7f8c61fdac895b9ba9c5342baf239ba3ffc-2072x864.png" alt="Comparación de latencia y rendimiento FAISS en Elasticsearch BBQ y Opensearch" /><h4>Resultados detallados - Recall @ 100</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb2a73e29940e9d6b/6a1709cccf4f257615b2d138/8790fdf9512b850447f6875fb69969f6f1d4da5f-1600x799.png" alt="Resultados detallados de latencia - Retirada @ 100 Elasticsearch BBQ vs Opensearch FAISS" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte2c91ca21da03d96/6a1709ce0c4857d3fc01aa39/d47032fac6c288cd4eedd9f25001e417b2fa9d65-1600x787.png" alt="Resultados detallados de rendimiento - Recall @ 100 Elasticsearch BBQ vs Opensearch FAISS." /><p></p><p>tarea</p><p>latencia.media</p><p>rendimiento.mean</p><p>avg_recall</p><p>Elasticsearch-9.0-BBQ</p><p>100-1000-1000</p><p>27.82</p><p>243.22</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>100-1200-1200</p><p>31.14</p><p>224.04</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>100-1500-1500</p><p>35.98</p><p>193.99</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>100-200-200</p><p>14.18</p><p>403.86</p><p>0.88</p><p>Elasticsearch-9.0-BBQ</p><p>100-2000-2000</p><p>45.36</p><p>159.88</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>100-250-250</p><p>14.77</p><p>433.06</p><p>0.90</p><p>Elasticsearch-9.0-BBQ</p><p>100-300-300</p><p>14.61</p><p>375.54</p><p>0.91</p><p>Elasticsearch-9.0-BBQ</p><p>100-500-500</p><p>18.88</p><p>340.37</p><p>0.93</p><p>Elasticsearch-9.0-BBQ</p><p>100-750-750</p><p>23.59</p><p>285.79</p><p>0.94</p><p>OpenSearch-2.19-faiss</p><p>100-1000-1000</p><p>142.90</p><p>58.48</p><p>0.95</p><p>OpenSearch-2.19-faiss</p><p>100-1200-1200</p><p>153.03</p><p>51.04</p><p>0.95</p><p>OpenSearch-2.19-faiss</p><p>100-1500-1500</p><p>181.79</p><p>43.20</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>100-200-200</p><p>50.94</p><p>131.62</p><p>0.83</p><p>OpenSearch-2.19-faiss</p><p>100-2000-2000</p><p>232.53</p><p>33.67</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>100-250-250</p><p>57.08</p><p>131.23</p><p>0.87</p><p>OpenSearch-2.19-faiss</p><p>100-300-300</p><p>62.76</p><p>120.10</p><p>0.89</p><p>OpenSearch-2.19-faiss</p><p>100-500-500</p><p>84.36</p><p>91.54</p><p>0.93</p><p>OpenSearch-2.19-faiss</p><p>100-750-750</p><p>111.33</p><p>69.95</p><p>0.94</p><h2>Mejoras en el asado</h2><p>BBQ recorrió un largo camino desde su primer lanzamiento. En Elasticsearch 8.16, en aras de la comparación, incluimos una ejecución de referencia de 8.16 junto con la actual, y podemos ver cómo la recuperación y la latencia mejoraron desde entonces.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f1f7ae1bb425a3a/6a1709cf67045bde8e45c192/45a0acfe5985bff28ccada76ec4eca190fe65f72-1600x799.png" alt="Las mejoras en la recuperación de latencia de Elasticsearch 9.0 BBQ se compararon con Elasticsearch 8.16 BBQ" /><p>En Elasticsearch 8.18 y 9.0, reescribimos el algoritmo central para cuantificar los vectores. Entonces, si bien BBQ en 8.16 fue bueno, las versiones más nuevas son aún mejores. Puedes leer sobre esto <a href="https://www.elastic.co/es/search-labs/blog/optimized-scalar-quantization-elasticsearch">aquí</a> y <a href="https://www.elastic.co/es/search-labs/blog/scalar-quantization-optimization">aquí</a>. En resumen, cada vector se cuantifica individualmente a través de cuantiles escalares optimizados. Como resultado, los usuarios se benefician de una mayor precisión en la búsqueda vectorial sin comprometer el rendimiento, lo que hace que la recuperación vectorial de Elasticsearch sea aún más poderosa.</p><h2>Conclusión</h2><p>En esta comparación de rendimiento entre Elasticsearch BBQ y OpenSearch FAISS, Elasticsearch supera significativamente a OpenSearch para la búsqueda vectorial, logrando velocidades de consulta hasta 5 veces más rápidas y un rendimiento 3,9 veces mayor en promedio en varios niveles de recuperación.</p><p>Los hallazgos clave incluyen:</p><ul><li><p><strong>Recall@10</strong>: Elasticsearch BBQ es hasta 5 veces más rápido (3,9 veces más rápido en promedio) y tiene 3,2 veces más rendimiento en promedio en comparación con OpenSearch FAISS.</p></li><li><p><strong>Recall@50</strong>: Elasticsearch BBQ es hasta 5 veces más rápido (4,2 veces más rápido en promedio) y tiene 3,9 veces más rendimiento en promedio en comparación con OpenSearch FAISS.</p></li><li><p><strong>Recall@100</strong>: Elasticsearch BBQ es hasta 5 veces más rápido (4,6 veces más rápido en promedio) y tiene 3,9 veces más rendimiento en promedio en comparación con OpenSearch FAISS.</p></li></ul><p>Estos resultados resaltan los beneficios de eficiencia y rendimiento de Elasticsearch BBQ, particularmente en escenarios de búsqueda vectorial de alta dimensión. La técnica Better Binary Quantization (BBQ), introducida en Elasticsearch 8.16, proporciona una reducción sustancial de la memoria (~95%) al tiempo que mantiene una alta calidad de clasificación, lo que la convierte en una opción superior para aplicaciones de búsqueda vectorial a gran escala.</p><p>En Elastic, estamos innovando incansablemente para mejorar Apache Lucene y Elasticsearch para proporcionar la mejor base de datos vectorial para casos de uso de búsqueda y recuperación, incluida RAG (Retrieval Augmented Generation). Nuestros <a href="https://www.elastic.co/es/search-labs/blog/optimized-scalar-quantization-elasticsearch">avances recientes</a> aumentaron significativamente el rendimiento, haciendo que la búsqueda vectorial sea más rápida y eficiente en cuanto al espacio que antes, basar en las ganancias de Lucene 10. Este blog es otra ilustración de esa innovación.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-bbq-vs-opensearch-faiss</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-bbq-vs-opensearch-faiss</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <dc:creator><![CDATA[Ugo Sangiorgi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd76ada1eebfe05cd/6a1709d1a29299ed29d00ffb/796de4829e29566f1f3efa2482f5c3e54b31b1d6-1536x1024.png" length="0" type="image/png"/>
    <pubDate>Tue, 15 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Base de datos vectorial de Elasticsearch para la conexión a tierra nativa en Vertex AI Platform de Google Cloud]]></title>
    <description><![CDATA[Descubre cómo Elasticsearch, el primer motor nativo de terceros para Vertex AI de Google Cloud, te permite crear experiencias GenAI personalizadas al conectar modelos Gemini en datos empresariales.]]></description>
    <content:encoded><![CDATA[<p>Elastic se complace en anunciar que la base de datos vectorial de Elasticsearch ahora está integrada en la plataforma Vertex AI de Google Cloud como un motor de recuperación de información compatible de forma nativa, lo que permite a los usuarios aprovechar las fortalezas multimodales de los modelos Gemini de Google con las capacidades avanzadas de búsqueda semántica e híbrida impulsadas por IA de Elasticsearch.</p><p>Los desarrolladores ahora pueden crear sus aplicaciones RAG dentro de un viaje unificado, basando sus experiencias de chat en sus datos privados de una forma flexible y low-code. Ya sea que estés construyendo agentes de IA para tus clientes y empleados internos o aprovechando la generación de LLMs dentro de tu software, la plataforma Vertex AI pone la relevancia de Elasticsearch al alcance de tus dedos con una configuración mínima. Esta integración permite una adopción más fácil y rápida de los modelos Gemini en casos de uso de producción, impulsando la GenAI de PoCs a escenarios reales.</p><p>En este blog, te guiaremos a través de la integración de Elasticsearch con la plataforma Vertex AI de Google Cloud para una base de datos fluida y la creación de aplicaciones GenAI totalmente personalizables. Descubramos cómo.</p><h2>Los modelos Vertex AI y Gemini de Google Cloud están basados en tus datos con Elasticsearch</h2><p>Los usuarios que aprovechan los servicios y herramientas de Vertex AI para crear aplicaciones GenAI ahora pueden acceder a la nueva opción "Grounding" para incorporar sus datos privados a su interacción conversacional automáticamente. Elasticsearch ahora es parte de esta característica y se puede usar a través de:</p><ul><li><p><a href="https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference">API de Vertex AI LLM</a>, que enriquecen directamente los modelos Gemini de Google en el momento de la generación (preferido);</p></li><li><p><a href="https://cloud.google.com/generative-ai-app-builder/docs/grounded-gen">Grounded Generation</a>, que se usa en su lugar en el ecosistema de Vertex AI Agent Builder para crear experiencias agenciales.</p></li></ul><p>Con esta integración, Elasticsearch, la <a href="https://www.elastic.co/es/elasticsearch/vector-database">base de datos vectorial</a> más descargada e implementada, llevará tus datos empresariales relevantes donde sea necesario en tus chats internos de cara al cliente final, lo cual es crucial para la adopción de GenAI en los procesos de negocio en la vida real.</p><p>Las API mencionadas permitirán a los desarrolladores adoptar esta nueva característica asociada en su código. Sin embargo, la ingeniería y las pruebas rápidas siguen siendo pasos cruciales en el desarrollo de aplicaciones y sirven como un campo de descubrimiento inicial. Para respaldar esto, Elasticsearch está diseñado para que los usuarios puedan evaluarlo fácilmente dentro de la herramienta de consola Vertex AI Studio.</p><p>Todo lo que se necesita son unos simples pasos para configurar los puntos finales de Elastic con los parámetros deseados (índice a buscar, la cantidad de documentos a recuperar y la plantilla de búsqueda deseada) dentro de la pestaña "Personalizar conexión a tierra" en la interfaz de usuario, como se muestra a continuación (tenga en cuenta que para que funcione, debe escribir la clave API con la palabra "ApiKey" en la interfaz de usuario y los ejemplos de código a continuación). ¡Ahora estás listo para generar con tu conocimiento privado!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d313968acca1fea/6a17e624b1e113278079f250/69b3d979d18fa90742d9397f57975c586edf5d9f-1003x710.gif" alt="Los modelos Vertex AI y Gemini de Google Cloud se basan en tus datos con Elasticsearch" /><h2>Aplicaciones GenAI listas para producción con facilidad</h2><p>Elastic y Google Cloud trabajan para proporcionar experiencias integrales, agradables y que priorizan a los desarrolladores. La conexión a Elastic de forma nativa tanto en LLM como en la API de generación de conexión a tierra reduce la complejidad y la sobrecarga al crear aplicaciones GAI en Vertex AI, lo que evita API adicionales innecesarias y la orquestación de datos mientras se basa en una sola llamada unificada.</p><p>Veamos cómo funciona en ambos escenarios.</p><p>El primer ejemplo se ejecuta con la API de LLM:</p>curl -X POST \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \https://us-central1-aiplatform.googleapis.com/v1beta1/projects/&lt;PROJECT_ID&gt;/locations/us-central1/publishers/google/models/gemini-2.0-flash-001:generateContent \
  -d '
{
  "contents": [
    {
      "role": "user",
      "parts": [
        {
          "text": "What's my company car policy?"
        }
      ]
    }
  ],
  "tools": [{
    "retrieval": {
      "externalApi": {
        "api_spec": "ELASTIC_SEARCH",
    "endpoint": "https://&lt;my-elastic-cluster&gt;.gcp.elastic-cloud.com:9243",
    "apiAuth": {
      "apiKeyConfig": {
            "apiKeyString": "ApiKey &lt;API_KEY&gt;"
      }
    },
    "elasticSearchParams": {
      "index": "&lt;my-index&gt;",
      "searchTemplate": "&lt;my-search-template&gt;"
    }
      }
    }
  }]
}<p>En el ejemplo anterior, con el campo <code>retrieval</code> de la API que aplicar la generación de contenido a Gemini 2.0 Flash, podemos configurar contextualmente un motor de recuperación para la solicitud. Establecer <code>api_spec</code> en "ELASTIC_SEARCH" permite el uso de parámetros de configuración adicionales, como la clave de API y el punto de enlace del clúster (necesarios para enrutar una solicitud a tu clúster de Elastic), el índice del que recuperar datos y la plantilla de búsqueda que se empleará para tu lógica de búsqueda.</p><p>Del mismo modo, se podría lograr el mismo resultado con la API de generación de puesta a tierra, estableciendo el parámetro <code>groundingSpec</code> :</p>curl -X POST -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Content-Type: application/json" https://us-discoveryengine.googleapis.com/v1alpha/projects/&lt;PROJECT_ID&gt;/locations/global:generateGroundedContent -d '
{
  "contents": [{
    "role": "user",
    "parts": [{
      "text": "What do I need to patch a hole in my drywall?"
    }]
  }],
  "groundingSpec": {
    "groundingSources": [{
      "elasticSource": {
        "endpoint": "https://&lt;my-elastic-cluster&gt;.gcp.elastic-cloud.com:9243",
        "index": "&lt;my-index&gt;",
        "searchTemplate": "&lt;my-search-template",
        "apiKey": "projects/&lt;PROJECT_ID&gt;/secrets/api-key/versions/latest"
      }
    }]
  }
}
'<p>Con ambos enfoques, la respuesta proporcionará una respuesta con los documentos privados más relevantes que se encuentran en Elasticsearch, y las fuentes de datos conectadas relacionadas, para respaldar tu consulta.</p><p>Sin embargo, la simplicidad no debe confundir con la falta de personalización para satisfacer sus necesidades y casos de uso específicos. Con esto en mente, lo diseñamos para permitirle adaptar perfectamente la configuración de búsqueda a su escenario.</p><h2>Búsqueda totalmente personalizable al alcance de tu mano: plantillas de búsqueda</h2><p>Para proporcionar la máxima personalización a tu escenario de búsqueda, creamos, en colaboración con Google Cloud, la experiencia sobre nuestras conocidas <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/current/search-template.html">plantillas de búsqueda</a>. Las plantillas de búsqueda de Elasticsearch son una excelente herramienta para crear consultas de búsqueda dinámicas, reutilizables y mantenibles. Permiten predefinir y reutilizar estructuras de consulta. Son particularmente útiles cuando se ejecutan consultas similares con diferentes parámetros, ya que ahorran tiempo de desarrollo y reducen la posibilidad de errores. Las plantillas pueden incluir marcadores de posición para variables, lo que hace que las consultas sean dinámicas y adaptables a diferentes requisitos de búsqueda.</p><p>Al usar las API de Vertex AI y Elasticsearch para la conexión a tierra, debes hacer referencia a una plantilla de búsqueda deseada, como se muestra en los fragmentos de código anteriores, donde se implementa la lógica de búsqueda y se envía a Elasticsearch. Los usuarios avanzados de Elastic pueden gestionar, configurar y actualizar de forma asíncrona los enfoques de búsqueda y adaptarlos a los índices, modelos y datos específicos de una manera totalmente transparente para los usuarios de Vertex AI, los desarrolladores de aplicaciones sitio web o los ingenieros de IA, que solo necesitan especificar el nombre de la plantilla en la API de fundamentación.</p><p>Este diseño permite una personalización completa, poniendo a tu disposición las amplias funciones de recuperación de Elasticsearch en un entorno de IA de Google Cloud, al tiempo que garantiza la modularidad, la transparencia y la facilidad de uso para diferentes desarrolladores, incluso aquellos que no están familiarizados con Elastic.</p><p>Siempre que necesite la búsqueda BM25, la búsqueda semántica o un enfoque híbrido entre los dos (¿Ya exploró <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/current/retrievers-overview.html">los retrievers</a> ? Técnicas de recuperación componibles en una sola llamada a la API de búsqueda), puedes definir tu lógica personalizada en una plantilla de búsqueda, que Vertex AI puede aprovechar automáticamente.</p><p>Esto también se aplica a las incrustaciones y reclasificaciones de modelos que elija para gestionar vectores y resultados. Según tu caso de uso, es posible que desees alojar modelos en los nodos de ML de Elastic, usar un endpoint de servicio de terceros a través de la API de inferencia o ejecutar tu modelo local en las instalaciones. Esto se puede hacer a través de una plantilla de búsqueda, y veremos cómo funciona en la siguiente sección.</p><h2>Comience con plantillas de referencia y luego cree las suyas propias</h2><p>Para ayudarlo a empezar rápidamente, proporcionamos un conjunto de ejemplos de plantillas de búsqueda compatibles que se emplearán como referencia inicial; Luego puede modificar y construir sus personalizados sobre:</p><ul><li><p>Búsqueda semántica con modelo ELSER (vectores dispersos y fragmentación)</p></li><li><p>Búsqueda semántica con modelo multilingüe e5 (vectores densos y fragmentación)</p></li><li><p>Búsqueda híbrida con modelo de incrustación de texto Vertex AI</p></li></ul><p>Puede encontrarlos en este <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/Cloud-Vertex-AI/search-templates">repositorio de GitHub</a>.</p><p>Veamos un ejemplo: crear incrustaciones con las API de IA de Vertex de Google Cloud en un catálogo de productos. Primero, necesitamos crear la plantilla de búsqueda en Elasticsearch como se muestra a continuación:</p>PUT _scripts/google-template-knn
{
  "script": {
    "lang": "mustache",
    "source": {
      "_source": {
        "excludes": [ "title_embedding", "description_embedding", "images" ]
      },
        "size": "{{num_hits}}",
          "knn" : [
          { 
            "field": "description_embedding",
            "k": 5,
            "num_candidates": 10,
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "googlevertexai_embeddings_004",
                "model_text": "{{query}}"
              }
            },
            "boost": 0.4
          },
          {
            "field": "title_embedding",
            "k": 5,
            "num_candidates": 10,
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "googlevertexai_embeddings_004",
                "model_text": "{{query}}"
            }
          },
          "boost": 0.6
          }
          ]
    }  
  }
}<p>En este ejemplo, ejecutaremos la búsqueda KNN en dos campos dentro de una sola búsqueda: <code>title_embedding</code> – el campo vectorial que contiene el nombre del producto – y <code>description_embedding</code> – el que contiene la representación de su descripción.</p><p>Puede aprovechar la sintaxis <code>excludes</code> para evitar devolver campos innecesarios al LLM, lo que puede causar ruido en su procesamiento y afectar la calidad de la respuesta final. En nuestro ejemplo, excluimos los campos que contienen vectores y url de imágenes.</p><p>Los vectores se crean sobre la marcha en el momento de la consulta en la entrada enviada a través de un extremo de inferencia a la API de incrustaciones de Vertex AI, <code>googlevertexai_embeddings_004</code>, definida anteriormente de la siguiente manera:</p>PUT /_inference/text_embedding/googlevertexai_embeddings_004
{
    "service": "googlevertexai",
    "service_settings": {
        "service_account_json": "&lt;your_service_account_key&gt;",
        "model_id": "text-embedding-004",
        "location": "us-central1",
        "project_id": "&lt;your_gcp_project&gt;"
    }
}<p>Puedes encontrar información adicional sobre cómo usar la API de inferencia de Elastic <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/current/inference-apis.html">aquí</a>.</p><p>Ahora estamos listos para probar nuestra búsqueda con plantillas:</p>GET product-catalog-with-embeddings/_search/template
{
  "id": "google-template-knn",
  "params": {
    "query": "What do I need to patch a hole in my drywall?",
    "index_name": "product-catalog-with-embeddings",
    "num_hits": 3
  }
}<p>Los campos <code>params</code> reemplazarán las variables que establezcamos en los scripts de plantilla entre corchetes dobles. Actualmente, las API de Vertex AI LLM y Grounded Generation pueden enviar a Elastic las siguientes variables de entrada:</p><ul><li><p>"query" - la consulta del usuario que se va a buscar</p></li><li><p>"index_name" - el nombre del índice donde buscar</p></li><li><p>"num_hits": cuántos documentos queremos recuperar en la salida final</p></li></ul><p>Aquí hay un ejemplo de salida:</p>{
        "_index": "product-catalog-with-embeddings",
        "_id": "9ZQCm5IBcrGI1ivqV-f_",
        "_score": 0.4925191,
        "_ignored": [
          "description.keyword",
          "images.keyword"
        ],
        "_source": {
          "description": "DAP Eclipse Rapid Wall Repair Patch is a new, revolutionary product solution for repairing drywall damage. No more waiting for spackling to dry or messy sanding. DAP Eclipse allows you to patch drywall damage and paint immediately, allowing you to finish your project faster. This all-in-1, mess free solution not only provides a permanent, long-lasting repair but also superior impact resistance for areas that may see reoccurring impact, such as behind a door.",
          "availability": "InStock",
          "model_id": "googlevertexai_embeddings_004",
          "title": "4 in. Eclipse Wall Repair Patch (2-Pack)",
          "url": "https://www.myDIYwebsite.com/p/DAP-4-in-Eclipse-Wall-Repair-Patch-2-Pack-7079809164/317967195",
          "price": 23.96,
          "product_id": 317967195,
          "currency": "USD",
          "brand": "DAP"
        }<p>La consulta anterior es precisamente lo que Vertex AI de Google Cloud ejecutará en Elasticsearch en segundo plano cuando se refiera a la plantilla de búsqueda creada anteriormente. Los modelos de Gemini usarán los documentos de salida para fundamentar su respuesta: cuando pregunte "¿Qué necesito para parchear mi panel de yeso?" en lugar de recibir una sugerencia genérica, ¡el agente de chat le proporcionará productos específicos!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte88a0c34242df6fa/6a17e6263e03d704504f2c2e/8c8b1374da7e9b2c17df8758acb448eed5b74e2d-1473x913.png" alt="Creando un prompt en la plataforma Vertex AI de Google" /><h2>Viaje de GenAI de extremo a extremo con Elastic y Google Cloud</h2><p>Elastic se asocia con Google Cloud para crear experiencias y soluciones GenAI integrales y listas para la producción. Como acabamos de ver, Elastic es el primer ISV que se integra directamente en la interfaz de usuario y el SDK para la plataforma Vertex AI, lo que permite que los modelos de Gemini sean precisos y fundamentados con indicaciones y agentes que emplean nuestras funciones de búsqueda vectorial. Además, Elastic se integra con <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/current/infer-service-google-vertex-ai.html">Vertex AI</a> y los modelos de inserción, reclasificación y finalización de <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/current/infer-service-google-ai-studio.html">Google AI Studio</a>para crear y clasificar vectores sin salir del panorama de Google Cloud, lo que garantiza los principios de <a href="https://cloud.google.com/responsible-ai?hl=en">IA responsable </a>. Al admitir enfoques multimodales, facilitamos conjuntamente las aplicaciones en diversos formatos de datos.</p><p>Puedes ajustar, probar y exportar tu código de búsqueda de GenAI a través de nuestro <a href="https://www.elastic.co/es/search-labs/blog/vertex-ai-elasticsearch-playground-fast-rag-apps">Playground</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a978835a5418eb9/6a17e628fbc5f8abff491a6a/b1fc48dba1713cd01c4d7f3296587d0c4e6e7e0c-862x651.png" alt="Viaje de GenAI de extremo a extremo con Elastic y Google Cloud " /><p>Pero no se trata solo de crear aplicaciones de búsqueda: Elastic aprovecha los modelos de Gemini para potenciar las operaciones de TI, como en las <a href="https://www.elastic.co/es/blog/elastic-google-vertex-ai-integration">funciones Elastic AI Assistants, Attack Discovery y Automatic Import</a>, lo que reduce la fatiga diaria de los analistas de seguridad y los SRE en tareas de bajo valor y les permite concentrar en mejorar su negocio. Elastic también permite <a href="https://www.elastic.co/es/guide/en/integrations/current/gcp_vertexai.html">un monitoreo integral del uso de Vertex AI</a>, el seguimiento de métricas y logs, como tiempos de respuesta, tokens y recursos, para garantizar un rendimiento óptimo. Juntos, gestionamos todo el ciclo de vida de GenAI, desde la ingesta de datos y la generación de incrustaciones hasta la conexión a tierra con búsqueda híbrida, al tiempo que garantizamos una estable observabilidad y seguridad de las herramientas de GenAI con acciones impulsadas por LLM.</p><h2>¡Explora más y pruébalo!</h2><p>¿Estás interesado en probar esto? ¡La función está actualmente disponible con disponibilidad general en tus proyectos de Google Cloud!</p><p>Si aún no lo hiciste, una de las formas más fáciles de comenzar a usar Elastic Search AI Platform y explorar nuestras capacidades es con tu <a href="https://cloud.elastic.co/registration">prueba gratis de Elastic Cloud</a> o suscribiéndote a través de <a href="https://console.cloud.google.com/marketplace/product/elastic-prod/elastic-cloud?pli=1">Google Cloud Marketplace</a>.</p><p><em>El lanzamiento y el momento de cualquier característica o funcionalidad descrita en esta publicación quedan a discreción exclusiva de Elastic. Es posible que cualquier característica o funcionalidad que no esté disponible actualmente no se entregue a tiempo o no se entregue en absoluto. Elastic, Elasticsearch y las marcas asociadas son marcas comerciales, logotipos o marcas comerciales registradas de Elasticsearch N.V. en los Estados Unidos y otros países. Todos los demás nombres de empresas y productos son marcas comerciales, logotipos o marcas comerciales registradas de sus respectivos propietarios.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-google-cloud-vertex-ai-native-grounding</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-google-cloud-vertex-ai-native-grounding</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <dc:creator><![CDATA[Valerio Arvizzigno]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt973959b32f2070d6/6a17e62a6317304ec1585a5e/d1f1c8860f1f0b989ad698a882f869de7284ab78-1200x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Acelerar la fusión de gráficos HNSW]]></title>
    <description><![CDATA[Explore el trabajo que estuvimos haciendo para reducir la sobrecarga de crear varios gráficos HNSW, en individuo reducir el costo de fusionar gráficos.]]></description>
    <content:encoded><![CDATA[<p>En el pasado, <a href="https://www.elastic.co/es/search-labs/blog/multi-graph-vector-search">hablamos</a> de algunos de los retos de tener que buscar <a href="https://www.elastic.co/es/search-labs/blog/hnsw-graph">en varios gráficos HNSW</a> y cómo pudimos mitigarlos. En ese momento insinuamos algunas mejoras adicionales que planeamos. Esta publicación es la culminación de ese trabajo.</p><p>Podrías preguntarte, ¿por qué usar varios gráficos? Este es un efecto secundario de una elección arquitectónica en Lucene: segmentos inmutables. Como ocurre con la mayoría de las opciones arquitectónicas, hay pros y contras. Por ejemplo, recientemente lanzamos Elasticsearch sin servidor con GA. En este contexto, obtuvimos beneficios muy significativos de los segmentos inmutables, incluida la replicación eficiente de índices y la capacidad de desacoplar el índice y el proceso de consulta y escalarlos automáticamente de forma independiente. Para la cuantificación vectorial, las fusiones de segmentos nos dan la oportunidad de actualizar los parámetros para adaptarlos a las características de los datos. En este sentido, creemos que hay otros beneficios que ofrece tener oportunidades para medir las características de los datos y revisar las opciones de indexación.</p><p>En esta publicación, discutiremos el trabajo que estuvimos haciendo para reducir significativamente la sobrecarga de crear múltiples gráficos HNSW y, en individuo, para reducir el costo de fusionar gráficos.</p><h3>Fondo</h3><p>Para mantener un número manejable de segmentos, Lucene verifica periódicamente si debe fusionar segmentos. Esto equivale a comprobar si el recuento de segmentos actual supera un recuento de segmentos de destino, que está determinado por el tamaño del segmento base y la política de combinación. Si se supera el recuento, Lucene combina grupos de segmentos mientras se infringe la restricción. Este proceso se describió en detalle <a href="https://blog.mikemccandless.com/2011/02/visualizing-lucenes-segment-merges.html">en otra parte</a>.</p><p>Lucene elige fusionar segmentos de tamaño similar porque esto logra un crecimiento logarítmico en la amplificación de escritura. En el caso de un índice vectorial, la amplificación de escritura es el número de veces que se insertará un vector en un gráfico. Lucene intentará fusionar segmentos en grupos de aproximadamente 10. En consecuencia, los vectores se insertan en un gráfico aproximadamente veces, donde  es el recuento de vectores índice y  es el recuento de vectores de segmento base esperado. Debido al crecimiento logarítmico, la amplificación de escritura es de un solo dígito incluso para índices enormes. Sin embargo, el tiempo total dedicado a fusionar gráficos es linealmente proporcional a la amplificación de escritura.</p><p>Al fusionar gráficos HNSW ya hacemos una pequeña optimización: retener el gráfico para el segmento más grande e insertar vectores de los otros segmentos en él. Esta es la razón del factor 9/10 anterior. A continuación, mostramos cómo podemos hacerlo significativamente mejor empleando información de todos los gráficos que estamos fusionando.</p><h3>Fusión de grafos HNSW</h3><p>Antes conservábamos el grafo más grande e insertábamos vectores de los otros ignorando los gráficos que los contenían. La idea clave que aprovechamos a continuación es que cada grafo HNSW que descartamos contiene información importante de proximidad sobre los vectores que contiene. Nos gustaría usar esta información para acelerar la inserción, al menos algunos, de los vectores.</p><p>Nos enfocamos en el problema de insertar un gráfico más pequeño  en un gráfico más grande , ya que esta es una operación atómica que podemos usar para construir cualquier política de combinación.</p><p>La estrategia consiste en encontrar un subconjunto de vértices de  insertarlos en el gráfico grande. Luego usamos la conectividad de estos vértices en el gráfico pequeño para acelerar la inserción de los vértices restantes . A continuación, usamos  y  para denotar los vecinos de un vértice  en el grafo pequeño y grande, respectivamente. Esquemáticamente, el proceso es el siguiente.</p><p><code>MERGE-HNSW</code></p><p><code>Inputs </code><code> and </code></p><p><code>1</code><code>Find </code><code> to insert into </code><code> using COMPUTE-JOIN-SET</code>
<code>2</code><code>Insert each vertex </code><code> into </code>
<code>3</code><code>for </code><code> do</code>
<code>4</code>
<code>5</code>
<code>6</code><code>FAST-SEARCH-LAYER</code>
<code>7</code><code>SELECT-NEIGHBORS-HEURISTIC</code>
<code>8</code></p><p>Calculamos el conjunto  empleando un procedimiento que discutimos a continuación (línea 1). Luego insertamos cada vértice en  en el gráfico grande usando el procedimiento de inserción HNSW estándar (línea 2). Para cada vértice que no insertamos encontramos sus vecinos que insertamos y sus vecinos en el gráfico grande (líneas 4 y 5). Usamos un procedimiento <code>FAST-SEARCH-LAYER</code> sembrado con este conjunto (línea 6) para encontrar los candidatos para el <code>SELECT-NEIGHBORS-HEURISTIC</code> del <a href="https://arxiv.org/pdf/1603.09320">artículo</a> HNSW (línea 7). En efecto, estamos reemplazando <code>SEARCH-LAYER</code> para encontrar el conjunto candidato en el método <code>INSERT</code> (Algoritmo 1 del artículo), que de lo contrario no cambia. Finalmente, agregamos el vértice que acabamos de insertar en  (línea 8).</p><p>Está claro que para que esto funcione, cada vértice en  debe tener al menos un vecino en . De hecho, requerimos que para cada vértice en  que  para algunos , la máxima conectividad de capa. Observamos que en los gráficos reales de HNSW vemos una gran dispersión de grados de vértice. La siguiente figura muestra una función de densidad acumulativa típica del grado de vértice para la capa inferior de un gráfico HNSW de Lucene.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc815e1a9c3bb0d06/6a17e178ec0f89308a5a6564/44f001b3a1bc6627e172fed5c52a02cfdcb4cd66-1324x898.png" alt="Grafo HNSW: Ejemplo de distribución de grados de vértices" /><p>Exploramos el uso de un valor fijo para  así como convertirlo en una función del grado del vértice. Esta segunda opción conduce a mayores aceleramientos con un impacto mínimo en la calidad del gráfico, por lo que optó por lo siguiente</p><p>Tenga en cuenta que | es igual al grado del vértice  en el gráfico pequeño por definición. Tener un límite inferior de dos significa que insertaremos cada vértice cuyo grado sea menor que dos.</p><p>Un argumento de conteo simple sugiere que si elegimos  con cuidado, solo necesitamos insertar alrededor  en  directamente. Específicamente, coloreamos una arista del grafo si insertamos exactamente uno de sus vértices finales en . Entonces sabemos que para que cada vértice en  tenga al menos  vecinos en  , necesitamos colorear al menos  aristas. Además, esperamos que</p><p>Aquí,  es el grado medio del vértice en la gráfica pequeña. Para cada vértice  coloreamos como máximo  Bordes. Por lo tanto, el número total de bordes que esperamos colorear es como máximo . Esperamos que al elegir  con cuidado coloreemos cerca de este número de aristas y así cubrir todos los vértices  necesidades para satisfacer</p><p>Esto implica que .</p><p>Siempre que <code>SEARCH-LAYER</code> domine el tiempo de ejecución, esto sugiere que podríamos lograr un aceleramiento de hasta  en el tiempo de fusión. Dado el crecimiento logarítmico de la amplificación de escritura, esto significa que incluso para índices muy grandes, normalmente solo duplicaríamos el tiempo de construcción en comparación con la construcción de un gráfico.</p><p>El riesgo en esta estrategia es que dañamos la calidad del gráfico. Inicialmente lo intentamos con un <code>FAST-SEARCH-LAYER</code>sin operación. Descubrimos que esto degrada la calidad del gráfico en la medida en que la recuperación en función de la latencia se vio afectada, particularmente cuando se fusiona en un solo segmento. Luego exploramos varias alternativas empleando una búsqueda limitada del gráfico. Al final, la elección más efectiva fue la más simple. Usar <code>SEARCH-LAYER</code> pero con un <code>ef_construction</code>bajo. Con esta parametrización pudimos lograr gráficos de excelente calidad y aún así disminuir el tiempo de fusión en un poco más del 30% en promedio.</p><h3>Cálculo del conjunto de unión</h3><p>Encontrar un buen conjunto de unión puede formular como un problema de cobertura de grafos HNSW. Una heurística codiciosa es una heurística simple y efectiva para aproximar cubiertas óptimas de grafos. El enfoque que tomamos elige vértices uno a uno para sumar a  en orden decreciente de ganancia. La ganancia se define de la siguiente manera:</p><p>Aquí,  denota el recuento de vecinos de un vector  en  y  es la función indicadora. La ganancia incluye el cambio en el recuento del vértice que agregamos a , es decir,  ya que nos acercamos a nuestro objetivo al agregar un vértice menos cubierto. El cálculo de la ganancia se ilustra en la siguiente figura para el vértice naranja central.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7a353d56fa09af5f/6a17e17a2f4a5c33f7fa8825/fe11cd55a7d94d9e0b0f4d47ea309a99075c355d-540x474.png" alt="Ganancia de vértices para agregar al conjunto de unión J en el grafo HNSW" /><p>Mantenemos el siguiente estado para cada vértice :</p><ol><li><p>Ya sea rancio,</p></li><li><p>Su ganancia </p></li><li><p>El recuento de vértices adyacentes en  denotado </p></li><li><p>Un número aleatorio en el rango [0,1] que se usa para el desempate.</p></li></ol><p>El pseudocódigo para calcular el conjunto de combinaciones es el siguiente.</p><p><code>COMPUTE-JOIN-SET</code></p><p><code>Inputs </code></p><p><code>1</code>
<code>2</code>
<code>3</code><code>for </code><code> do</code>
<code>4</code>
<code>5</code>
<code>6</code>
<code>7</code><code>while </code><code> do
8</code><code> maximum gain vertex in </code>
<code>9</code><code>Remove the state for </code><code> from </code>
<code>10</code><code>if </code><code> is not stale then</code>
<code>11</code>
<code>12</code>
<code>13</code><code>for </code><code> do</code>
<code>14</code><code>mark </code><code> as stale if </code>
<code>15</code><code>mark neighbors of </code><code> stale if </code><code>
16</code><code>
17</code><code>else
18</code><code>
19</code><code>if </code><code> then
20</code><code>
21</code><code>return </code></p><p>Primero inicializamos el estado en las líneas 1-5.</p><p>En cada iteración del bucle principal extraemos inicialmente el vértice de ganancia máxima (línea 8), rompiendo los empates al azar. Antes de realizar cualquier cambio, debemos verificar si la ganancia del vértice está obsoleta. En individuo, cada vez que agregamos un vértice a  afectamos la ganancia de otros vértices:</p><ol><li><p>Dado que todos sus vecinos tienen un vecino adicional en  , sus ganancias pueden cambiar (línea 14)</p></li><li><p>Si alguno de sus vecinos ahora está completamente cubierto, todas las ganancias de sus vecinos pueden cambiar (líneas 14-16)</p></li></ol><p>Recalculamos las ganancias de manera perezosa, por lo que solo recalculamos la ganancia de un vértice si queremos insertarlo en  (líneas 18-20). Dado que las ganancias solo disminuyen, nunca podemos perder un vértice que debamos insertar.</p><p>Tenga en cuenta que simplemente necesitamos realizar un seguimiento de la ganancia total de vértices que agregamos a  para determinar cuándo salir. Además,  al menos un vértice tendrá una ganancia distinta de cero, por lo que siempre progresamos.</p><h3>Resultados</h3><p>Realizamos experimentos en cuatro conjuntos de datos que juntos cubren nuestras tres métricas de distancia admitidas (euclidiana, coseno y producto interno):</p><ol><li><p>quora-E5-small: 522931 documentos, 384 dimensiones y emplea la similitud del coseno,</p></li><li><p>cohe-wikipedia-v2: 1M de documentos, 768 dimensiones y emplea similitud de coseno,</p></li><li><p>gist: 1M documentos, 960 dimensiones y emplea la distancia euclidiana, y</p></li><li><p>cohe-wikipedia-v3: 1M de documentos, 1024 dimensiones y emplea el máximo producto interno.</p></li></ol><p>Para cada conjunto de datos evaluamos dos niveles de cuantificación:</p><ol><li><p>int8: que emplea un entero de 1 byte por dimensión y</p></li><li><p>BBQ: que emplea un solo bit por dimensión.</p></li></ol><p>Finalmente, para cada experimento evaluamos la calidad de la búsqueda a dos profundidades de recuperación y examinamos luego de construir el índice y luego luego de forzar la fusión en un solo segmento.</p><p>En resumen, logramos aceleramientos sustanciales consistentes en la indexación y la fusión mientras mantenemos la calidad del gráfico y, por lo tanto, el rendimiento de búsqueda en todos los casos.</p><h4>Experimento 1: cuantización int8</h4><p>Los aceleramientos promedio desde la línea de base hasta el candidato, los cambios propuestos, son:</p><p>Aceleramiento del tiempo del índice: <strong>1.28</strong></p><p>Forzar la velocidad de combinación: <strong>1.72</strong></p><p>Esto corresponde al siguiente desglose en tiempos de ejecución</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8422be122ef1a9c9/6a17e17bbe6086522e00467d/5f9f6d487b4c74c4998aa47465912c1a9743e928-734x479.png" alt="Indexar y fusionar tiempos para las estrategias de fusión de línea base y candidatas" /><p>Para completar, los tiempos exactos son</p><p></p><p>Índice</p><p></p><p>Fusionar</p><p></p><p>Conjunto de datos</p><p>referencia</p><p>candidato</p><p>Desarrolla</p><p>candidato</p><p>quora-E5-pequeño</p><p>112.41s</p><p>81.55s</p><p>113.81s</p><p>70.87s</p><p>wiki-cohesión-v2</p><p>158.1s</p><p>122,95 segundos</p><p>425.20s</p><p>239.28s</p><p>quid</p><p>141.82s</p><p>119.26s</p><p>536.07s</p><p>279.05s</p><p>wiki-cohesión-v3</p><p>211.86s</p><p>168.22s</p><p>654,97 segundos</p><p>414.12s</p><p>A continuación, mostramos los gráficos de recuperación frente a latencia que comparan el candidato (líneas discontinuas) con la línea de base en dos profundidades de recuperación: recall@10 y recall@100 para índices con múltiples segmentos (el resultado final de nuestra estrategia de fusión predeterminada luego de indexar todos los vectores) y luego de forzar la fusión en un solo segmento. Una curva más alta y más a la izquierda es mejor, lo que significa una mayor recuperación con una latencia más baja.</p><p>Como puede ver, para índices de múltiples segmentos, el candidato es mejor para el conjunto de datos Cohere v3 y un poco peor, pero casi comparable, para todos los demás conjuntos de datos. Luego de fusionar en un solo segmento, las curvas de recuperación son casi idénticas para todos los casos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf674622469bf6a90/6a17e17dfbc5f874a84919c9/9195297f19f90b172d832ba56bdada7b0d8a768b-985x392.png" alt="Recuperar @10 y @100 frente a la latencia luego de crear el índice" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltde89f7e94ac823f4/6a17e17fe9ea876429a9c507/c866f32d579ae2b841e92ca733dd29c0e214ac6b-986x386.png" alt="Recuperar @10 y @100 frente a la latencia luego de fusionar en un solo segmento" /><h4>Experimento 2: Cuantización de asado</h4><p>Los aceleramientos promedio desde la línea de base hasta el candidato son:</p><p>Aceleramiento del tiempo de índice: <strong>1.33</strong></p><p>Forzar la velocidad de combinación: <strong>1.34</strong></p><p>Esto corresponde al siguiente desglose en tiempos de ejecución</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5b0eb305222fa5c2/6a17e18125daab514f08a18c/11be0cf6b63480dc703409329357ea4847b55051-740x415.png" alt="Indexar y fusionar el tiempo para las estrategias de fusión de línea base y candidatas" /><p>Para completar, los tiempos exactos son</p><p></p><p>Índice</p><p></p><p>Fusionar</p><p></p><p>Conjunto de datos</p><p>referencia</p><p>candidato</p><p>Desarrolla</p><p>candidato</p><p>quora-E5-pequeño</p><p>70.71s</p><p>58.25s</p><p>59.38s</p><p>40.15s</p><p>wiki-cohesión-v2</p><p>203.08s</p><p>142.27s</p><p>107.27s</p><p>85.68s</p><p>quid</p><p>110.35s</p><p>105,52 segundos</p><p>323,66 segundos</p><p>202.2s</p><p>wiki-cohesión-v3</p><p>313.43s</p><p>190.63s</p><p>165,98 segundos</p><p>159,95 segundos</p><p>Para índices de segmentos múltiples, el candidato es mejor para casi todos los conjuntos de datos, excepto cohere v2, donde la línea de base es ligeramente mejor. Para los índices de un solo segmento, las curvas de recuperación son casi idénticas para todos los casos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb12174abeadbcdd1/6a17e1822f4a5c4cc2fa8829/7da1be27bab5a7f5f9c9a1882ea35761a4a0ba5c-973x383.png" alt="Recuperar @10 y @100 frente a la latencia luego de crear el índice" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf1b88d4f7fec9905/6a17e184414c64a8de9450b4/a26af30a56c55a12addee7e4fb7e8f606f366668-979x386.png" alt="Recordar @10 y @100 frente a la latencia que se fusionó en un solo segmento" /><h3>Conclusión</h3><p>El algoritmo discutido en este blog estará disponible en el próximo Lucene 10.2 y en la versión de Elasticsearch que se basa en él. Los usuarios podrán aprovechar el rendimiento mejorado de la combinación y el tiempo de compilación de índices reducido en estas nuevas versiones. Este cambio es parte de nuestro esfuerzo continuo para hacer que Lucene y Elasticsearch sean rápidos y eficientes para la búsqueda vectorial e híbrida.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hnsw-graphs-speed-up-merging</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hnsw-graphs-speed-up-merging</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[Lucene]]></category>
    <dc:creator><![CDATA[Thomas Veasey,Mayya Sharipova]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9fca6a01d0541cfb/6a17e187033c8d46486bb0e1/49a6c880f5dedd0fa502ece5be124824ee218cc0-1792x1024.png" length="0" type="image/png"/>
    <pubDate>Mon, 07 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Escalar modelos de interacción tardía en Elasticsearch: parte 2]]></title>
    <description><![CDATA[Este artículo analiza técnicas para preparar los vectores de interacción tardía para las cargas de trabajo de producción a gran escala, como reducir el uso de espacio en disco y mejorar la eficiencia de cómputo.]]></description>
    <content:encoded><![CDATA[<p>En nuestro <a href="https://www.elastic.co/search-labs/blog/elastiacsearch-colpali-document-search">blog anterior sobre ColPali</a>, analizamos cómo crear aplicaciones de búsqueda visual con Elasticsearch. Nos centramos en el valor que modelos como ColPali aportan a nuestras aplicaciones, pero presentan inconvenientes de rendimiento en comparación con la búsqueda vectorial con bicodificadores como E5.</p><p>Partiendo de los ejemplos de <a href="https://www.elastic.co/search-labs/blog/elastiacsearch-colpali-document-search">la parte 1</a>, este blog analiza cómo usar diferentes técnicas y el poderoso kit de herramientas de búsqueda vectorial de Elasticsearch para preparar vectores de interacción tardía para cargas de trabajo de producción a gran escala.</p><p>Los ejemplos de código completos se pueden encontrar en <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/colpali">GitHub.</a></p><h2>Desafíos de los modelos de interacción tardía</h2><p>ColPali crea más de 1000 vectores por página para los documentos de nuestro índice.</p><p>Esto plantea dos desafíos al trabajar con vectores de interacción tardía:</p><ol><li><p>Espacio en disco: almacenar todos estos vectores en los discos implicará un uso considerable de almacenamiento, lo que será costoso a escala.</p></li><li><p>Cómputo: al clasificar los documentos mediante la comparación <code>maxSimDotProduct()</code>, necesitamos comparar todos estos vectores de cada documento con los N vectores de nuestra búsqueda.</p></li></ol><p>Veamos algunas técnicas para abordar estos problemas.</p><h2>Técnicas para optimizar modelos de interacción tardía</h2><h3>Vectores de bits</h3><p>Para reducir el espacio en disco, podemos comprimir las imágenes en vectores de bits. Podemos usar una función sencilla en Python para transformar nuestros multivectores en vectores de bits:</p>def to_bit_vectors(embeddings: list) -&gt; list:
    return [
        np.packbits(np.where(np.array(embedding) &gt; 0, 1, 0))
        .astype(np.int8)
        .tobytes()
        .hex()
        for embedding in embeddings
    ]<p>El principio básico de la función es simple: los valores mayores que 0 se convierten en 1 y los valores menores que 0 se convierten en 0. Esto da como resultado un arreglo de 0 y 1, que luego se convierte en una cadena hexadecimal que representa el vector de bits.</p><p>Para nuestro mapping de índice, establecemos el parámetro <code>element_type</code> en <code>bit</code>:</p>mappings = {
    "mappings": {
        "properties": {
            "col_pali_vectors": {
                "type": "rank_vectors",
                "element_type": "bit"
            }
        }
    }
}

es.indices.create(index=INDEX_NAME, body=mappings)<p>Después de haber escrito todos nuestros nuevos vectores de bits en nuestro índice, podemos clasificar nuestros vectores de bits usando el siguiente código:</p>query = "What do companies use for recruiting?"
query_vector = to_bit_vectors(create_col_pali_query_vectors(query))
es_query = {
    "_source": False,
    "query": {
        "script_score": {
            "query": {
                "match_all": {}
            },
            "script": {
                "source": "maxSimInvHamming(params.query_vector, 'col_pali_vectors')",
                "params": {
                    "query_vector": query_vector
                }
            }
        }
    },
    "size": 5
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcaf0c8f999d68701/6a17f46696142a6f46eb1c56/51b989446e4099745971e1eac27d147a78d13e0a-1600x480.png" alt="" /><p>A cambio de una pequeña pérdida de precisión, esto nos permite utilizar la distancia de Hamming (<code>maxSimInvHamming(...)</code>), la cual aprovecha optimizaciones como máscaras de bits, SIMD, etc. Obtén más información sobre <a href="https://www.elastic.co/search-labs/blog/bit-vectors-in-elasticsearch">los vectores de bits y la distancia de Hamming en nuestro blog</a>.</p><p>De forma alternativa, podemos no convertir nuestro vector de búsqueda en vectores de bits y realizar una búsqueda con el vector de interacción tardía de fidelidad completa:</p>query = "What do companies use for recruiting?"
query_vector = create_col_pali_query_vectors(query)
es_query = {
    "_source": False,
    "query": {
        "script_score": {
            "query": {
                "match_all": {}
            },
            "script": {
                "source": "maxSimDotProduct(params.query_vector, 'col_pali_vectors')",
                "params": {
                    "query_vector": query_vector
                }
            }
        }
    },
    "size": 5
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2fa6c89fb451b6e0/6a17f468af47b65da9cde0d9/89f3c795c6dc44b2f7d46801320288316b47e1b2-1600x488.png" alt="Resultados del uso de vectores de bits para optimizar modelos de interacción tardía" /><p>Esto comparará nuestros vectores usando una función de similitud asimétrica.</p><p></p><p>Pensemos en una distancia de Hamming regular entre dos vectores de bits. Supongamos que tenemos un vector de documento <em>D:</em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4b6ece1ec4dedac/6a17f4694b055d248143234e/366a70a23e37d403788327b7aefc873fd4482f5e-1235x86.png" alt="" /><p>Y un vector de búsqueda  <em>Q:</em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7721df9c8f0d84f1/6a17f46b3e03d77cf54f2dcb/978ec31e0afbc0eaebf548a015b628c0cad84625-1247x84.png" alt="" /><p></p><p>La cuantificación binaria simple transformará los vectores <em>D</em> en <code>10101101</code> y <em>Q</em> en <code>11111011</code>. Para encontrar la distancia de Hamming, necesitamos matemáticas de bits directas; es extremadamente rápido. En este caso, la distancia de Hamming es <code>01010110</code>, que tiene un recuento de bits de 4. Así que, el puntaje se convierte en el inverso de esa distancia de Hamming. Hay que recordar que cuanto más vectores similares haya, menor será la distancia de Hamming, por lo que invertirla permite que los vectores más similares tengan una puntuación más alta. Concretamente aquí, el puntaje sería 1/4 = <code>0.25</code>.</p><p>Sin embargo, observa cómo perdemos la magnitud de cada dimensión. Un <code>1</code> es un <code>1</code>. Así que, para <em>Q</em>, la diferencia entre <code>0.01</code> y <code>0.79</code> desaparece. Como simplemente estamos cuantizando según <code>&gt;0</code>, podemos hacer un pequeño truco donde el vector Q no está cuantizado. Esto no permite realizar operaciones matemáticas bit a bit extremadamente rápidas, pero mantiene bajo el costo de almacenamiento, ya que D sigue cuantificado.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blted50315518e2d599/6a17f46c414c6463709452d3/508e8498ba969534d2a0131d431c75735fc27cb9-1399x611.png" alt="" /><p>En resumen, esto conserva la información proporcionada en <em>Q</em>, lo que aumenta la calidad de estimación de distancia y mantiene el almacenamiento bajo.</p><p>El uso de vectores de bits nos permite ahorrar significativamente en espacio en disco y carga computacional en el momento de la búsqueda. Pero hay más que podemos hacer.</p><h3>Vectores promedio</h3><p>Para escalar nuestra búsqueda a cientos de miles de documentos, ni siquiera las ventajas de rendimiento que nos ofrecen los vectores de bits serán suficientes. Para escalar a este tipo de cargas de trabajo, querremos aprovechar la estructura de índice HNSW de Elasticsearch para la búsqueda vectorial.</p><p>ColPali genera alrededor de mil vectores por documento, lo que es demasiado para agregar a nuestro grafo HNSW. Por lo tanto, necesitamos reducir el número de vectores. Para esto, se puede crear una única representación del significado del documento promediando todos los vectores del documento que genera ColPali al incrustar nuestra imagen.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc053425fbfcf8de8/6a17f46f148009faf1b488aa/7c3b4dffb70bd95f67deb35f8c01e73c5286c2ab-1476x1102.png" alt="Vector promedio sobre todos los vectores de interacción tardía" /><p>Por ahora, esto no es posible dentro de Elastic, por lo que tendremos que procesar previamente los vectores antes de ingestarlos en Elasticsearch. </p><p>Podemos hacer esto con Logstash o pipelines de ingesta, pero aquí usaremos una función sencilla en Python:</p>def to_avg_vector(vectors):
    vectors_array = np.array(vectors)
    
    avg_vector = np.mean(vectors_array, axis=0)
    
    norm = np.linalg.norm(avg_vector)
    if norm &gt; 0:
        normalized_avg_vector = avg_vector / norm
    else:
        normalized_avg_vector = avg_vector

    return normalized_avg_vector.tolist()<p>Además, normalizamos el vector; de este modo, podremos usar la similitud por producto punto.</p><p>Luego de transformar todos nuestros vectores de ColPali en vectores promedio, podemos indexarlos en nuestro campo dense_vector:</p>mappings = {
    "mappings": {
        "properties": {
            "avg_vector": {
                "type": "dense_vector",
                "dims": 128,
                "index": True,
                "similarity": "dot_product"
            },
            "col_pali_vectors": {
                "type": "rank_vectors",
                "element_type": "bit"
            }
        }
    }
}

es.indices.create(index=INDEX_NAME, body=mappings)<p>Debemos tener en cuenta que esto aumentará el uso total del disco, ya que estamos almacenando más información junto con nuestros vectores de interacción tardíos. Además, usaremos RAM adicional para almacenar el grafo HNSW, lo que nos permitirá escalar la búsqueda a miles de millones de vectores. Para reducir el uso de RAM, podemos utilizar nuestra popular <a href="https://www.elastic.co/search-labs/blog/optimized-scalar-quantization-elasticsearch">característica BBQ</a>. A cambio, obtenemos resultados de búsqueda rápidos en conjuntos de datos masivos que de otra manera no serían posibles.</p><p>Ahora, simplemente hacemos una búsqueda con la consulta knn para encontrar nuestros documentos más relevantes.</p>query = "What do companies use for recruiting?"
query_vector = to_avg_vector(create_col_pali_query_vectors(query))
es_query = {
    "_source": False,
    "knn": {
        "field": "avg_vector",
        "query_vector": query_vector,
        "k": 10,
        "num_candidates": 100
    },
    "size": 5
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf4ac6b7fd444f935/6a17f471a29299d76ad02dc0/a500f9907020f032c3b1c7a48ed8c759dc2a1dd4-1600x498.png" alt="" /><p>La mejor coincidencia anterior, lamentablemente, cayó al tercer rango.</p><p>Para solucionar este problema, podemos realizar una recuperación en varias etapas. En nuestra primera etapa, utilizamos la búsqueda knn para buscar los mejores candidatos para nuestra búsqueda en millones de documentos. En la segunda etapa, solo reclasificamos los principales k (aquí: 10) con la mayor fidelidad de los vectores de interacción tardía de ColPali. </p>query = "What do companies use for recruiting?"
col_pali_vector = create_col_pali_query_vectors(query)
avg_vector = to_avg_vector(col_pali_vector)
es_query = {
  "_source": False,
  "retriever": {
    "rescorer": {
      "retriever": {
        "knn": {
          "field": "avg_vector",
          "query_vector": avg_vector,
          "k": 10,
          "num_candidates": 100
        }
      },
      "rescore": {
        "window_size": 10,
        "query": {
          "rescore_query": {
            "script_score": {
              "query": {
                "match_all": {}
              },
              "script": {
                "source": "maxSimDotProduct(params.query_vector, 'col_pali_vectors')",
                "params": {
                  "query_vector": col_pali_vector
                }
              }
            }
          }
        }
      }
    }
  },
  "size": 5
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt265edd5f37be40b7/6a17f4733e9e45583dba15e6/797f490860af87fcf29f34668a5c4511419c6fd0-1600x501.png" alt="Resultados del uso de vectores promedio para optimizar modelos de interacción tardía" /><p>Aquí, usamos el <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.18/retriever.html#rescorer-retriever">recalificador</a> introducido en 8.18 para reclasificar nuestros resultados. Después de la recalificación, vemos que nuestra mejor coincidencia está nuevamente en la primera posición. </p><p>Nota: En una aplicación de producción, podemos usar un valor de k mucho mayor que 10, ya que la función de similitud máxima sigue siendo comparativamente eficiente.</p><h3>Agrupación de tokens</h3><p>La agrupación de tokens reduce la longitud de la secuencia de incrustaciones de vectores múltiples al agrupar información redundante, como parches de fondo blanco. Esta técnica reduce el número de incrustaciones, al tiempo que conserva la mayor parte de la señal de la página.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3d643e6ac908f640/6a17f4754b055d3783432352/09eae0b768f4b450e555d7e53e57561bd52de2c0-1412x1056.png" alt="Agrupación de tokens para optimizar modelos de interacción tardía" /><p>El agrupamiento de tokens funciona mediante la agrupación de incrustaciones similares de tokens dentro de un documento en clústeres con un algoritmo de agrupamiento en clusters. A continuación, se calcula la media de los vectores de cada grupo para crear una representación única y agregada. Este vector agregado reemplaza los tokens originales en el grupo, lo que reduce el número total de vectores sin una pérdida significativa de la señal del documento.</p><p>El documento de ColPali propone un valor inicial de factor de agrupación de 3 para la mayoría de los sets de datos, lo que mantiene el 97,8 % del rendimiento original y al mismo tiempo reduce el número total de vectores en un 66,7 %. </p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d78a15c6944a1ea/6a17f477414c64a2929452d9/343cc9eaf54af8c7a125d4115838f3c7d5659de0-1600x1007.png" alt="Factor de agrupación para optimizar modelos de interacción tardía" /><p>Pero debemos tener cuidado: el set de datos “Shift”, que contiene documentos muy densos y con mucho texto con poco espacio en blanco, disminuye rápidamente en rendimiento a medida que aumentan los factores de grupo.</p><p>Para crear los vectores agrupados, podemos usar la biblioteca colpali_engine:</p>from colpali_engine.compression.token_pooling import HierarchicalTokenPooler

pooler = HierarchicalTokenPooler(pool_factor=3) # test on your data for a good pool_factor

def pool_vectors(embedding: list) -&gt; list:
    tensor = torch.tensor(embedding).unsqueeze(0)
    pooled = pooler.pool_embeddings(tensor)
    return pooled.squeeze(0).tolist()<p>Ahora tenemos un vector que se redujo en aproximadamente un 66,7 % en sus dimensiones. Lo indexamos como siempre y podemos hacer búsquedas en él con nuestra función <code>maxSimDotProduct()</code>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc460c14031814ede/6a17f4794202292bf629f72d/2412c5db7d79a590b96d42fe01f140c42f010612-1600x481.png" alt="Resultados de modelos de interacción tardía" /><p>Podemos obtener buenos resultados de búsqueda a costa de cierta precisión en los resultados.</p><p>Sugerencia: con un pool_factor más alto (100-200), también puedes tener un término medio entre la solución vectorial promedio y la que analizamos aquí. Con unos 5 a 10 vectores por documento, es viable indexarlos en un campo anidado para aprovechar el índice HNSW.</p><h2>Codificador cruzado vs. interacción tardía vs. bicodificador</h2><p>Con lo que hemos aprendido hasta ahora, ¿dónde se sitúan los modelos de interacción tardía, como ColPali o ColBERT, cuando los comparamos con otras técnicas de recuperación mediante AI?</p><p>Aunque la función max sim es más económica en comparación con los codificadores cruzados, todavía requiere muchas más comparaciones y cálculos que la búsqueda vectorial con codificadores binarios, en la que solo se comparan dos vectores para cada par de búsqueda-documento. </p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbfd97f3bba3e5b17/6a17f47be31791b0052d5943/75e1fc9e601aa7a6e88f137565c1919166c7a71c-1480x458.png" alt="Codificador cruzado vs. modelos de interacción tardía vs. bicodificador" /><p>Por eso, nuestra recomendación para modelos de interacción tardía es usarlos generalmente solo para reclasificar los k mejores resultados de búsqueda. También reflejamos esto en el nombre del tipo de campo: rank_vectors.</p><p>¿Pero qué pasa con el codificador cruzado? ¿Son mejores los modelos de interacción tardía porque son más baratos de ejecutar en el momento de la búsqueda? Como suele suceder, la respuesta es: depende. Los codificadores cruzados generalmente producen resultados de mayor calidad, pero requieren una gran cantidad de recursos de cómputo porque los pares de búsqueda-documento necesitan hacer un pase completo a través del modelo transformador. También se benefician del hecho de que no requieren indexar vectores y pueden operar de manera sin estado. Esto da como resultado lo siguiente:</p><ul><li><p>Menor uso de espacio en disco.</p></li><li><p>Un sistema más sencillo.</p></li><li><p>Mayor calidad de los resultados de búsqueda</p></li><li><p>Una latencia más alta y, por lo tanto, la imposibilidad de realizar una reclasificación tan profunda.</p></li></ul><p>Por otro lado, los modelos de interacción tardía pueden descargar parte de este cálculo en el índice, lo que hace que la búsqueda sea más económica. El precio que pagar es la necesidad de indexar los vectores, lo que aumenta la complejidad de nuestros pipelines de indexación y requiere más espacio en disco para almacenarlos.</p><p>En el caso concreto de ColPali, el análisis de la información de las imágenes resulta muy costoso, porque contienen una gran cantidad de datos. En este caso, el equilibrio se desplaza a favor de utilizar un modelo de interacción tardía como ColPali porque evaluar esta información en el momento de la búsqueda consumiría demasiados recursos o sería demasiado lento. </p><p>Para un modelo de interacción tardía como ColBERT, que funciona con datos de texto como la mayoría de los codificadores cruzados (por ejemplo, elastic-rerank-v1), la decisión podría inclinarse más hacia usar el codificador cruzado para beneficiarse del ahorro y la simplicidad en disco.</p><p>Te recomendamos que evalúes las ventajas y desventajas para tu caso de uso y experimentes con las diferentes herramientas que Elasticsearch te proporciona para crear las mejores aplicaciones de búsqueda.</p><h2>Conclusión</h2><p>En este blog, analizamos diversas técnicas para optimizar modelos de interacción tardía como ColPali para búsquedas vectoriales a gran escala en Elasticsearch. Si bien los modelos de interacción tardía proporcionan un sólido equilibrio entre la eficiencia de recuperación y la calidad de clasificación, también presentan desafíos relacionados con el almacenamiento de información y el cómputo.</p><p>Para abordar estos desafíos, analizamos:</p><ul><li><p><strong>Vectores de bits</strong> para reducir significativamente el espacio en disco, al tiempo que se aprovechan los cálculos de similitud eficientes como la distancia de Hamming o la similitud máxima asimétrica.</p></li><li><p><strong>Promedio de vectores</strong> para comprimir múltiples inserciones en una sola representación densa, lo que permite una recuperación eficiente con indexación HNSW.</p></li><li><p><strong>Agrupación de tokens</strong> para fusionar de forma inteligente las incorporaciones redundantes mientras se mantiene la integridad semántica, lo que reduce la sobrecarga computacional en el momento de la búsqueda.</p></li></ul><p>Elasticsearch ofrece un conjunto potente de herramientas para personalizar y optimizar las aplicaciones de búsqueda en función de tus necesidades. Ya sea que priorices la velocidad de recuperación, la calidad de clasificación o la eficiencia de almacenamiento, estas herramientas y técnicas te permiten equilibrar el rendimiento y la calidad según lo que necesites para tus aplicaciones reales.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/late-interaction-model-colpali-scale</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/late-interaction-model-colpali-scale</guid>
    <category><![CDATA[Relevancia]]></category>
    <category><![CDATA[Base de datos vectorial]]></category>
    <dc:creator><![CDATA[Peter Straßer,Benjamin Trent]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt97a536033e6b0a56/6a17f47dfbc5f88c86491c0d/c780b78a07573f2df1cfef8b29a7109f839b0ab3-1200x628.png" length="0" type="image/png"/>
    <pubDate>Thu, 20 Mar 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Explorando la búsqueda vectorial acelerada por GPU en Elasticsearch con NVIDIA: Capítulo I]]></title>
    <description><![CDATA[Con tecnología NVIDIA cuVS, la colaboración busca proporcionar a los desarrolladores aceleramiento de GPU para la búsqueda vectorial en Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>En la organización de Elastic Engineering estuvimos ocupados optimizando el rendimiento de la base de datos vectorial desde hace un tiempo. Nuestra misión: hacer de Lucene y Elasticsearch la mejor base de datos vectorial. A través de <a href="https://www.elastic.co/es/blog/accelerating-vector-search-simd-instructions">instrucciones SIMD de CPU</a> aceleradas por hardware, introduciendo nuevas innovaciones de compresión de datos vectoriales (<a href="https://www.elastic.co/es/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Better Binary Quantization, también conocido como BBQ</a>), y luego superando las expectativas actualizando el enfoque algorítmico de BBQ para obtener aún más beneficios, y también <a href="https://www.elastic.co/es/search-labs/blog/filtered-hnsw-knn-search">haciendo que Filtered HNSW sea más rápido</a>. Entiendes la esencia: estamos construyendo un sistema más rápido, mejor, eficiente (¿más?) para los desarrolladores mientras resuelven esos problemas RAG-gedy!</p><p>Como parte de nuestra misión de no dejar atrás la eficiencia, estamos explorando oportunidades de aceleramiento con estos curiosos chips de computadora, de los que quizás oíste hablar: ¡las GPU NVIDIA! (En serio, ¿no es así?).</p><p>Cuando nos obsesionamos con el rendimiento, tenemos varios espacios problemáticos para explorar: cómo indexar exponencialmente más datos, cómo recuperar información de ellos y cómo hacerlo cuando sus modelos de ML están involucrados. Debería poder obtener hasta el último beneficio disponible cuando tenga GPU.</p><p>En esta publicación, nos sumergimos en nuestra colaboración con el equipo de búsqueda vectorial de NVIDIA mientras exploramos la búsqueda vectorial acelerada por GPU en Elasticsearch. Este trabajo allana el camino para casos de uso en los que los desarrolladores podrían usar una combinación de GPU y CPU para aplicaciones con tecnología de Elasticsearch del mundo real. ¡Tiempos emocionantes!</p><h2>GPU de Elasticsearch</h2><p>Nos complace compartir que el equipo de ingeniería de Elasticsearch está ayudando a crear la experiencia de la API de Java cuVS de código abierto para desarrolladores, que expone enlaces para algoritmos de búsqueda vectorial. Este trabajo aprovecha nuestra experiencia previa con Panama FFI. Elasticsearch y Apache Lucene emplean la API NVIDIA cuVS para crear el gráfico durante la indexación. Bien, estamos saltando hacia adelante; Rebobinemos un poco.</p><p><a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS,</a> una biblioteca de C++ de código abierto, está en el corazón de esta colaboración. Su objetivo es llevar el aceleramiento de GPU a la búsqueda vectorial al proporcionar un mayor rendimiento, menor latencia y tiempos de compilación de índices más rápidos. Pero Elasticsearch y Apache Lucene están escritos en Java; ¿Cómo funcionará esto?</p><p>Ingrese <a href="https://github.com/SearchScale/lucene-cuvs">lucene-cuvs</a> y la colaboración Elastic-NVIDIA-SearchScale para llevarlo al ecosistema de Lucene para explorar la búsqueda vectorial acelerada por GPU en Elasticsearch. En la reciente versión 25.02 de NVIDIA cuVS, agregamos una API de Java para cuVS. La nueva API es experimental y seguirá evolucionando, pero actualmente está disponible para su uso. Puede surgir la pregunta: ¿no son lentas las llamadas de Java a funciones nativas? ¡Ya no! Estamos usando la nueva <a href="https://openjdk.org/projects/panama/">FFI</a> (interfaz de funciones externas) de Panamá para los enlaces, que tiene una sobrecarga mínima para Java a las llamadas descendentes nativas.</p><p>Estuvimos usando <a href="https://www.elastic.co/es/search-labs/blog/lucene-and-java-moving-forward-together">Panama FFI en Elasticsearch y Lucene</a> desde hace un tiempo. ¡Es asombroso! Pero... Siempre hay un "pero", ¿no? FFI tiene desafíos de disponibilidad en todas las versiones de Java. Superamos esto compilando la API de cuVS en Java 21 y encapsulando la implementación dentro de un jar de varias versiones dirigido a Java 22. Esto permite el uso de cuVS Java directamente en Lucene y Elasticsearch.</p><p>Ok, ahora que tenemos la API de Java de cuVS, ¿qué más necesitaríamos?</p><h2>Una historia de dos algoritmos para CPU</h2><p>Elasticsearch admite el <a href="https://arxiv.org/abs/1603.09320">algoritmo HNSW</a> para una búsqueda KNN aproximada escalable. Sin embargo, para sacar el máximo provecho de la GPU, empleamos un algoritmo diferente, <a href="https://arxiv.org/pdf/2308.15136">CAGRA [</a><a href="https://arxiv.org/pdf/2308.15136"><strong>C</strong></a><a href="https://arxiv.org/pdf/2308.15136"><em>UDA</em></a> <a href="https://arxiv.org/pdf/2308.15136"><strong>A</strong></a><a href="https://arxiv.org/pdf/2308.15136"><em>NN</em></a> <a href="https://arxiv.org/pdf/2308.15136"><strong>GRA</strong></a><a href="https://arxiv.org/pdf/2308.15136"><em>ph</em></a><a href="https://arxiv.org/pdf/2308.15136">],</a> que fue diseñado específicamente para los altos niveles de paralelismo que ofrece la GPU.</p><p>Antes de entrar en cómo buscamos agregar soporte para CAGRA, veamos cómo Elasticsearch y Lucene acceden a los datos del índice a través de un "formato de códec". Consiste en</p><ol><li><p>la representación en disco,</p></li><li><p>las interfaces para leer y escribir datos,</p></li><li><p>y la maquinaria para lidiar con la arquitectura basada en segmentos de Lucene.</p></li></ol><p>Estamos implementando un nuevo <a href="https://lucene.apache.org/core/10_1_0/core/org/apache/lucene/codecs/KnnVectorsFormat.html">formato vectorial</a> KNN (k-vecinos más cercanos) que emplea internamente la API de Java de cuVS para indexar y buscar en la GPU. A partir de aquí, "sondeamos" este tipo de códec a través de las asignaciones de Elasticsearch a un tipo de campo en el índice. Como resultado, las consultas KNN existentes siguen funcionando independientemente de si el índice de respaldo emplea un gráfico CAGRA o HNSW. Por supuesto, esto pasa por alto muchos detalles, que planeamos cubrir en un blog futuro. La siguiente es la arquitectura de alto nivel para un Elasticsearch acelerado por GPU.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb6197b631a34f8b6/6a170b0da6c2b9e60ce7970b/be6b7356c03df4dee7230625c2c9af3b019f93be-756x510.png" alt="" /><p>Este nuevo formato de códec tiene como valor predeterminado CAGRA. Sin embargo, también admite la conversión de un gráfico CAGRA en un gráfico HNSW para la búsqueda en la CPU.</p><h2>Indexación y búsqueda en la GPU: Tomar algunas decisiones "básicas"</h2><p>Con la <a href="https://www.elastic.co/es/search-labs/blog/stateless-your-new-state-of-find-with-elasticsearch">arquitectura</a> sin estado de Elasticsearch Serverless, que separa la indexación y la búsqueda, ahora hay una clara delimitación de responsabilidades. Elegimos el mejor perfil de hardware para cumplir con cada una de estas responsabilidades independientes.</p><p>Anticipamos que los usuarios considerarán dos estrategias de implementación principales:</p><ol><li><p>Indexación y búsqueda en la GPU: durante la indexación, cree un gráfico CAGRA y empléelo durante la búsqueda, ideal cuando se requiere una búsqueda de latencia extremadamente baja.</p></li><li><p>Indexar en GPU y buscar en CPU: durante la indexación, cree un gráfico CAGRA y conviértalo en un gráfico HNSW. El gráfico HNSW se almacena en el índice, que luego se puede usar en la CPU para la búsqueda.</p></li></ol><p>Esta flexibilidad proporciona diferentes modelos de implementación, ofreciendo compensaciones entre costo y rendimiento. Por ejemplo, un servicio de indexación podría usar GPU para compilar y combinar gráficos de manera eficiente de manera oportuna mientras usa una CPU de menor potencia para la búsqueda.</p><h2>Así que aquí está el plan para la búsqueda vectorial acelerada por GPU en Elasticsearch</h2><p>Esperamos brindar ganancias de rendimiento y flexibilidad con estrategias de implementación a los usuarios, ofreciendo varias perillas para equilibrar el costo y el rendimiento. <a href="https://www.nvidia.com/gtc/session-catalog/?tab.catalogallsessionstab=16566177511100015Kus&amp;search=Lucene#/">Aquí está la sesión de NVIDIA GTC 2025</a> donde se presentó este trabajo en detalle.</p><p>Nos gustaría agradecer a los equipos de ingeniería de NVIDIA y SearchScale por su fantástica colaboración. En un próximo blog, exploraremos los detalles de implementación y el análisis de rendimiento con mayor profundidad. ¡Agárrate a tus sombreros de 🎩 curiosidad!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/gpu-accelerated-vector-search-elasticsearch-nvidia</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/gpu-accelerated-vector-search-elasticsearch-nvidia</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <dc:creator><![CDATA[Chris Hegarty,Hemant Malik]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt298e839e708ca11c/6a170b0fb339d560c2769fc2/38bc0377a6adce7eae0099f61902fdbbe644eb4a-1440x960.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 19 Mar 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Una breve introducción a la búsqueda vectorial]]></title>
    <description><![CDATA[Este artículo es el primero de un serial de tres que profundizarán en las complexidades de la búsqueda vectorial, también conocida como búsqueda semántica, y cómo se implementa en Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>Este artículo es el primero de un serial de tres que profundizarán en las complexidades de la búsqueda vectorial, también conocida como búsqueda semántica, y cómo se implementa en Elasticsearch.</p><p>Esta primera parte se centra en ofrecer una introducción general a los conceptos básicos de incrustación de vectores y cómo funciona la búsqueda vectorial en el interior.</p><p>Armado con todo el conocimiento aprendido en el primer artículo, la <a href="https://www.elastic.co/search-labs/blog/vector-search-set-up-elasticsearch">segunda parte</a> te guiará por los ménsilos de cómo configurar la búsqueda vectorial en Elasticsearch.</p><p>En la <a href="https://www.elastic.co/search-labs/blog/hybrid-search-elasticsearch">tercera parte</a>, aprovecharemos lo que aprendimos en las dos primeras partes, construiremos sobre ese conocimiento y profundizaremos en cómo crear poderosas consultas híbridas de búsqueda en Elasticsearch.</p><p>Antes de adentrarnos en el asunto real de este artículo, volvamos atrás en el tiempo y repasemos parte de la historia de los vectores, que es un concepto clave en la búsqueda semántica.</p><h2>Los vectores no son nuevos</h2><p>Estoy bastante seguro de que todo el mundo estaría de acuerdo en que desde la llegada de ChatGPT en noviembre de 2022, no pasa ni un solo día sin oír o leer sobre la "búsqueda vectorial". Está en todas partes y es tan común que a menudo tenemos la impresión de que es una tecnología de vanguardia que acaba de salir, ¡pero la verdad es que esta tecnología lleva más de seis décadas en el mercado! La investigación sobre el tema comenzó a mediados de los años 60, y los primeros artículos científicos se publicaron en 1978 por Gerard Salton, un experto en recuperación de información, y sus colegas de la Universidad de Cornell. El trabajo de Salton sobre modelos vectoriales densos y dispersos constituye la raíz de la tecnología moderna de búsqueda vectorial.</p><p>En los últimos 20 años, se crearon y llevado al mercado muchos <a href="https://db-engines.com/en/ranking/vector+dbms/all">SGBD vectoriales</a> diferentes basados en su investigación. Entre ellos se encuentra Elasticsearch, impulsado por el proyecto Apache Lucene, que comenzó <a href="https://issues.apache.org/jira/browse/LUCENE-9004">a trabajar en búsqueda vectorial</a> en 2019.</p><p>Los vectores están ahora en todas partes y son tan omnipresentes que es importante primero comprender bien su teoría subyacente y su funcionamiento interno antes de experimentar con ellos. Antes de profundizar en eso, repasemos rápidamente las diferencias entre la búsqueda léxica y la búsqueda vectorial para entender mejor cómo difieren y cómo pueden complementar entre sí.</p><h2>Búsqueda vectorial vs. búsqueda léxica</h2><p>Una forma sencilla de introducir la búsqueda vectorial es comparándola con la búsqueda léxica más convencional a la que probablemente estés acostumbrado. La búsqueda vectorial, también conocida comúnmente como búsqueda semántica, y la búsqueda léxica funcionan de forma muy diferente. La búsqueda léxica es el tipo de búsqueda que todos estuvimos usando durante años en Elasticsearch. Resumiéndolo muy brevemente, no intenta entender el verdadero significado de lo que se indexa y consulta, sino que hace un gran esfuerzo por hacer coincidir <strong>léxicamente</strong> los literales de las palabras o variantes de ellas (piensa en stemming, sinónimos, etc.) que el usuario escribe en una consulta con todos los literales que fueron indexados previamente en la base de datos usando algoritmos de similitud,  como la TF-IDF</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfbb8e150865a5ec8/6a17e0c725daab50f408a16e/a4f3eba19599e5330e9b0d29ecdbbeac211cdab0-1600x583.png" alt="Ejemplo de búsqueda léxica" /><p>Como podemos ver, los tres documentos de la esquina superior izquierda están tokenizados y analizados. Luego, los términos resultantes se indexan en un índice invertido, que simplemente mapea los términos analizados a los IDs de los documentos que los contienen. Ten en cuenta que todos los términos solo están presentes una vez y ninguno es compartido por ningún documento. Buscar "buen profesor de alemán" hará coincidir los tres documentos con puntajes variables, aunque ninguno capte realmente el significado real de la consulta.</p><p>Como se puede ver en la Figura 2, más abajo, se complica aún más cuando se trata de polisemia u homógrafos, es decir, palabras que se escriben igual pero tienen <strong>significados diferentes</strong> (derecha, palma, murciélago, media, etc.) Tomemos la palabra "correcto", que puede significar tres cosas diferentes, y veamos qué ocurre.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltea0cc829ff2c8029/6a17e0c92f4a5c8107fa8811/ebb9cc1be03475bbf68269a8dcea5f08bcda0b64-1594x754.png" alt="Búsqueda de homógrafos con búsqueda léxica" /><p>Buscar <em>"I'm not right"</em> devuelve un documento que tiene el significado exactamente opuesto al resultado original. Si buscas exactamente los mismos términos pero los ordenas de forma diferente para producir un significado distinto, por ejemplo, <em>"voltea a la derecha"</em> y <em>"giro a la derecha",</em> obtienes el mismo resultado exacto (es decir, el tercer documento "Toma un giro a la derecha"). Es cierto que nuestras consultas son excesivamente simplificadas y no emplean consultas más avanzadas como la comparación de frases, pero esto ayuda a ilustrar que la búsqueda léxica no entiende el verdadero significado detrás de lo que se indexa y lo que se busca. Si no queda claro, no te preocupes, retomaremos este ejemplo en el tercer artículo para ver cómo la búsqueda vectorial puede ayudar en este caso.</p><p>Para hacer justicia a la búsqueda léxica, cuando tienes control sobre cómo indexas tus datos <strong>estructurados</strong> (piensa en mapeos, análisis de texto, pipelines de ingest, etc.) y cómo elaboras tus consultas (piensa en consultas DSL ingeniosamente diseñadas, análisis de términos de consulta, etc.), puedes hacer maravillas con motores de búsqueda léxicos, ¡no hay duda! El historial de Elasticsearch en cuanto a sus capacidades de búsqueda léxica es simplemente asombroso. Lo que logró y cuánto popularizó y mejorado el campo de la búsqueda léxica en los últimos años es realmente notable.</p><p>Sin embargo, cuando se te encarga proporcionar soporte para consultar<a href="https://www.elastic.co/what-is/unstructured-data"> datos</a> <a href="https://www.elastic.co/what-is/unstructured-data"><strong>no estructurados</strong></a> (piensa en imágenes, videos, audios, texto en bruto, etc.) a usuarios que necesitan hacer preguntas de texto libre, la búsqueda léxica no funciona. Además, a veces la consulta ni siquiera es texto, podría ser una imagen, como veremos en breve. La principal razón por la que la búsqueda léxica es inadecuada en estas situaciones es que los datos no estructurados no pueden ni indexar ni consultar de la misma manera que los datos estructurados. Cuando se trata de datos no estructurados, entra <strong>en juego la semántica</strong> . ¿Qué significa la semántica? Muy simplemente, ¡el significado!</p><p>Tomemos el ejemplo sencillo de un motor de búsqueda de imágenes (por ejemplo, Google Image Search o Lens). Arrastras y soltas una imagen, y el motor de búsqueda semántica de Google encontrará y devolverá las imágenes más similares a la que consultaste. En la Figura 3, a continuación, podemos ver a la izquierda la imagen de un pastor alemán y a la derecha todas las imágenes similares que se recuperaron, siendo el primer resultado la misma imagen que la proporcionada (es decir, la más parecida).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3e0a0fb3c300537a/6a17e0cbe8fbce69d13a185d/c0dff24d4379141abd1038c9c4b5577fd2dca802-1600x657.png" alt="Ejemplo de búsqueda semántica: Búsqueda de una imagen" /><p>Aunque esto parezca simple y lógico para nosotros, para las computadoras es otra historia completamente distinta. Eso es lo que permite y ayuda a lograr la búsqueda vectorial. El poder desbloqueado por la búsqueda vectorial es enorme, como el mundo presenció recientemente. Ahora levantemos la capucha y descubramos qué se esconde debajo.</p><h2>Vectores de incrustación</h2><p>Como vimos antes, con los motores de búsqueda léxicos, los datos estructurados como el texto pueden tokenizarse fácilmente en términos que pueden coincidir en el momento de la búsqueda, independientemente del significado real de los términos. Sin embargo, los datos no estructurados pueden adoptar diferentes formas, como grandes objetos binarios (imágenes, videos, audios, etc.), y no son en absoluto adecuados para el mismo proceso de tokenización. Además, el propósito principal de la búsqueda semántica es indexar los datos de tal manera que puedan buscar según el significado que representan. ¿Cómo lo logramos? La respuesta está en dos palabras: <strong>¡Aprendizaje Automático</strong>! ¡O más concretamente, Aprendizaje Profundo!</p><p><strong>El aprendizaje profundo</strong> es un área específica del aprendizaje automático que se basa en modelos basados en redes neuronales artificiales compuestas por múltiples capas de procesamiento que pueden extraer progresivamente el verdadero significado de los datos. La forma en que funcionan esos modelos de redes neuronales está fuertemente inspirada en el cerebro humano. La figura 4, a continuación, muestra cómo es una red neuronal, con sus capas de entrada y salida, así como múltiples capas ocultas:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ca5fd8f0e61d939/6a17e0cd7f6f152a67c09a53/aeea3bab3c29e1c591a9c9082f2e73e7d3990ef1-1600x1116.png" alt="Capas de redes neuronales en la búsqueda vectorial" /><p>La verdadera hazaña de las redes neuronales es que son capaces de convertir un solo dato no estructurado en una secuencia de valores de punto flotante, conocidos como <strong>vectores de incrustación</strong> o simplemente <strong>incrustaciones</strong>. Como seres humanos, podemos entender bastante bien qué son los vectores siempre que los visualizemos en un espacio bidimensional o tridimensional. Cada componente del vector representa una coordenada en un plano x-y 2D o en un espacio 3D x-y-z.</p><p>Sin embargo, los vectores de incrustación sobre los que trabajan los modelos de redes neuronales pueden tener varios cientos o incluso miles de dimensiones y simplemente representar un punto en un espacio multidimensional. Cada dimensión vectorial representa una <strong>característica</strong>, o una característica, de los datos no estructurados. Ilustremos esto con un modelo de aprendizaje profundo que convierte imágenes en vectores de incrustación de 2048 dimensiones. Ese modelo convertiría la imagen del pastor alemán que usamos en la Figura 3 en el vector de incrustación mostrado en la tabla siguiente. Ten en cuenta que solo mostramos el primer y último tres elementos, pero habría 2.042 columnas/dimensiones más en la tabla.</p><p></p><p>is_red</p><p>is_dog</p><p>blue_sky</p><p>…</p><p>no_gras</p><p>german_shepherd</p><p>is_tree</p><p>Pastor alemán Incrustaciones</p><p>0.0121</p><p>0.9572</p><p>0.8735</p><p>…</p><p>0.1198</p><p>0.9712</p><p>0.0512</p><p>Cada columna es una dimensión del modelo y representa una característica, o característica, que la red neuronal subyacente busca modelar. Cada entrada dada al modelo se caracterizará dependiendo de lo similar que sea esa entrada a cada una de las 2048 dimensiones. Por tanto, el valor de cada elemento en el vector de incrustación denota la <strong>similitud</strong> de esa entrada con una dimensión específica. En este ejemplo, podemos ver que el modelo detectó una alta similitud entre perros y pastores alemanes, así como la presencia de algo de cielo azul.</p><p>A diferencia de la búsqueda léxica, donde un término puede ser emparejado o no, con la búsqueda vectorial podemos obtener una idea mucho mejor de cuán <em>similar</em> es un dato no estructurado a cada una de las dimensiones soportadas por el modelo. Por tanto, los vectores de incrustación sirven como una representación semántica fantástica de los datos no estructurados.</p><h2>La salsa secreta</h2><p>Ahora que sabemos cómo los datos no estructurados son segmentados y fragmentados por redes neuronales de aprendizaje profundo en vectores de incrustación que capturan la similitud de los datos a lo largo de un gran número de dimensiones, necesitamos entender cómo funciona la coincidencia de esos vectores. Resulta que la respuesta es bastante sencilla. Los vectores de incrustación <strong>que están cerca</strong> entre sí representan piezas de datos <strong>semánticamente similares</strong> . Así que, cuando consultamos una base de datos vectorial, la entrada de búsqueda (imagen, texto, etc.) se convierte primero en un vector de incrustación usando el mismo modelo que se usó para indexar todos los datos no estructurados, y el objetivo final es encontrar los <strong>vectores vecinos más cercanos</strong> a ese vector de consulta. Por tanto, solo tenemos que averiguar cómo medir la "distancia" o "similitud" entre el vector de consulta y todos los vectores existentes indexados en la base de datos, y eso es básicamente todo.</p><h3>Distancia y similitud</h3><p>Por suerte para nosotros, medir la distancia entre dos vectores es un problema fácil de resolver gracias a la aritmética vectorial. Así que, veamos las funciones de distancia y similitud más populares que soportan las bases de datos modernas de búsqueda vectorial, como Elasticsearch. ¡Aviso, matemáticas adelante!</p><h4>Distancia L1</h4><p>La distancia L1, también llamada distancia de Manhattan, de dos vectores x e y se mide sumando la diferencia absoluta por pares de todos sus elementos. Obviamente, cuanto menor es la distancia d, más cerca están los dos vectores. La fórmula es bastante sencilla, como se puede ver a continuación:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2df2e9bc20883491/6a17e0ce033c8d006a6bb0bf/2b17bcedfbedde61117a3e7970af55bc62318c6c-312x102.png" alt="Fórmula de distancia L1 en la búsqueda vectorial" /><p>Visualmente, la distancia L1 puede ilustrar como se muestra en la Figura 5, a continuación:</p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb90a33e6b1cbe00b/6a17e0d0414c64eb76945096/52075441892151536ed216081817a8e852566daa-474x464.png" alt="Visualización de la distancia L1 entre dos vectores" /><p>Tomemos dos vectores x e y, como x = (1, 2) y y = (4, 3), entonces la distancia L1 de ambos vectores sería | 1 - 4 | + | 2 - 3 | = 4.</p><h4>Distancia L2</h4><p>La distancia L2, también llamada distancia euclidiana, de dos vectores x e y se mide primero sumando el cuadrado de la diferencia de pares de todos sus elementos y luego tomando la raíz cuadrada del resultado. Básicamente es el camino más corto entre dos puntos (también llamado hipotenusa). De forma similar a L1, cuanto menor sea la distancia d, más cerca estarán los dos vectores:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltae9b63fb593ae225/6a17e0d1af47b68379cddea8/b7675aa41f4f381e954c21dacd23a51a2dde6780-384x112.png" alt="Distancia L2 en la búsqueda vectorial" /><p>La distancia L2 se muestra en la Figura 6 a continuación:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt32d913b46e07e79d/6a17e0d27f6f1582f7c09a57/bac99d08d6cf8a3a387a8acdc2e8f67357dad235-448x456.png" alt="Visualización de la distancia L2 entre dos vectores" /><p>Reutilicemos los mismos dos vectores de muestra x e y que usamos para la distancia L1, y ahora podemos calcular la distancia L2 como . Tomando la raíz cuadrada de 10, obtendría 3,16.</p><p></p><h4>Distancia de Linf</h4><p>La distancia de Linf (por L infinito), también llamada distancia de Chebyshev o distancia del tablero de ajedrez, de dos vectores x e y se define simplemente como la distancia más larga entre dos de sus elementos o la distancia más larga medida a lo largo de uno de los ejes/dimensiones. La fórmula es muy sencilla y se muestra a continuación:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt863411e15de16fa3/6a17e0d4505ac30939ad8a48/179ea6c6520a59acd97638313a2fb1b105e2ffed-436x82.png" alt="Fórmula de distancia de Linf en búsqueda vectorial" /><p>Una representación de la distancia Linf se muestra en la Figura 7 a continuación:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt863ee7b51fd5fc15/6a17e0d56864a4772db686af/b75540d793cdc0645244d77dc36da9d5734ccbac-548x560.png" alt="Distancia de Linf entre dos vectores" /><p>De nuevo, tomando los mismos dos vectores muestrales x e y, podemos calcular la distancia infinita L como max ( | 1 - 4 | , | 2 - 3 | ) = max (3, 1) = 3.</p><h4>Similitud coseno</h4><p>A diferencia de L1, L2 y Linf, la similitud coseno no mide la distancia entre dos vectores x e y, sino su ángulo relativo, es decir, si ambos apuntan aproximadamente en la misma dirección. Cuanto mayor sea la similitud s, más "cerca" están los dos vectores. La fórmula es de nuevo muy sencilla y se muestra a continuación:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt61d55341a6457ab7/6a17e0d7e9ea872b4ea9c4e2/931b6b90f0ee83e63a66496d06d6b4cef3affddd-304x72.png" alt="Fórmula de similitud coseno en la búsqueda vectorial" /><p>Una forma de representar la similitud coseno entre dos vectores se muestra en la Figura 8 a continuación:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt50eb6399510b5380/6a17e0d83e9e45302cba139a/52092e8b5d366178e50a35f8c954ee8eaca76965-582x586.png" alt="Similitud coseno entre dos vectores" /><p>Además, como los valores del coseno siempre están en el intervalo [-1, 1], -1 significa similitud opuesta (es decir, un ángulo de 180° entre ambos vectores), 0 significa similitud no relacionada (es decir, un ángulo de 90°) y 1 significa idéntico (es decir, un ángulo 0°), como se muestra en la Figura 9 más abajo:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt785e195c75a5089e/6a17e0da1d1b8355bc93e398/fd2690a845b89b69ff202bd04192893638538aec-1600x456.png" alt="El espectro de similitud coseno en la búsqueda vectorial" /><p>
Una vez más, reutilicemos los mismos vectores muestrales x e y calculemos la similitud coseno usando la fórmula anterior. Primero, podemos calcular el producto escalar de ambos vectores como . Luego, multiplicamos la longitud (también llamada magnitud) de ambos vectores:  Finalmente, dividimos el producto escalar por la longitud multiplicada 10 / 11,18034 = 0,894427 (es decir, un ángulo de 26°), que es bastante cercano a 1, por lo que ambos vectores pueden considerar bastante similares.</p><h4>Similitud del producto escalar</h4><p>Una desventaja de la similitud entre coseno es que solo tiene en cuenta el ángulo entre dos vectores pero no su magnitud (es decir, la longitud), lo que significa que si dos vectores apuntan aproximadamente en la misma dirección pero uno es mucho más largo que el otro, ambos seguirán considerar similares. La similitud del producto escalar, también llamada escalar o producto interior, mejora esto teniendo en cuenta tanto el ángulo como la magnitud de los vectores, lo que proporciona una métrica de similitud mucho más precisa.</p><p>Se emplean dos fórmulas equivalentes para calcular la similitud del producto escalar. La primera es la misma que vimos anteriormente en el numerador de similitud coseno:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f1c3369f8628457/6a17e0db1d1b832a1893e39c/52e2723926ab27cd96b688e805fae15f607073c8-482x104.png" alt="Fórmula de similitud por producto escalar en búsqueda vectorial" /><p>La segunda fórmula simplemente multiplica la longitud de ambos vectores por el coseno del ángulo entre ellos:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt136dd2a749c2398a/6a17e0dc6df73108a80a0e1f/dc9b11fc67dd748d9f1f29b735f4726138cb7d39-452x70.png" alt="Fórmula de similitud por producto escalar simplificada en la búsqueda vectorial" /><p>La similitud del producto escalar se visualiza en la Figura 10, a continuación:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb2c095e1c1948752/6a17e0dd6df731e30b0a0e23/1bda38cf82a1e1037f44b6e9657602c9efe1c0a6-558x574.png" alt="Similitud del producto escalar entre dos vectores" /><p>Una última vez, tomamos los vectores de muestra x e y calculamos su similitud del producto escalar usando la primera fórmula, como hicimos antes para la similitud coseno, como (1 4) + (2 3) = 10.</p><p>Usando la segunda fórmula, multiplicamos la longitud de ambos vectores:  y multiplicamos eso por el coseno del ángulo de 26° entre ambos vectores, y obtenemos 11,18034 cos(26°) = 10.</p><p>Algo que vale la pena señalar es que si todos los <strong>vectores se normalizan</strong> primero (es decir, su longitud es 1), entonces la similitud del producto escalar se convierte exactamente en la misma que la similitud del coseno (porque |x| |y| = 1), es decir, el coseno del ángulo entre ambos vectores. Como veremos más adelante, normalizar vectores es una buena práctica para hacer que la magnitud del vector sea irrelevante y que la similitud se centre simplemente en el ángulo. También acelera el cálculo de distancias en el tiempo de indexación y consulta, lo que puede ser un gran problema al operar con miles de millones de vectores.</p><h3>Resumen rápido</h3><p>Vaya, revisamos MUCHÍSIMA información hasta ahora, así que vamos a hacer una pausa un momento y hacer un breve resumen de dónde estamos. Aprendimos que...</p><ul><li><p>… La búsqueda semántica se basa en modelos de redes neuronales de aprendizaje profundo que destacan en transformar datos no estructurados en vectores de incrustación multidimensionales.</p></li><li><p>… Cada dimensión del modelo representa una característica o característica de los datos no estructurados.</p></li><li><p>… Un vector de incrustación es una secuencia de valores de similitud (uno para cada dimensión) que representa lo similar que es a cada dimensión un dato no estructurado dado.</p></li><li><p>… cuanto más "cercanos" estén dos vectores (es decir, los vecinos más cercanos), más representan conceptos semánticamente similares.</p></li><li><p>… Las funciones de distancia (L1, L2, Linf) nos permiten medir la proximidad de dos vectores.</p></li><li><p>… Las funciones de similitud (coseno y producto escalar) nos permiten medir cuánto se dirigen dos vectores en la misma dirección.</p></li></ul><p></p><p>Ahora, la última pieza en la que tenemos que profundizar es el propio motor de búsqueda vectorial. Cuando llega una consulta, primero se vectoriza y luego el motor de búsqueda vectorial encuentra los vectores vecinos más cercanos a ese vector. El enfoque de fuerza bruta de medir la distancia o similitud entre el vector de consulta y todos los vectores de la base de datos puede funcionar para conjuntos de datos pequeños, pero pronto se queda corto a medida que aumenta el número de vectores. Dicho de otro modo, ¿cómo podemos indexar millones, miles de millones o incluso billones de vectores y encontrar los vecinos más cercanos del vector de consulta en un tiempo razonable? Ahí es donde necesitamos ser inteligentes y encontrar formas óptimas de indexar vectores para poder centrarnos en los vecinos más cercanos lo más rápido posible sin degradar demasiado la precisión.</p><h3>Algoritmos y técnicas de búsqueda vectorial</h3><p>A lo largo de los años, muchos equipos de investigación invirtieron mucho esfuerzo en desarrollar algoritmos de búsqueda vectorial muy ingeniosos. Aquí vamos a presentar brevemente los principales. Dependiendo del caso de uso, algunos son más adecuados que otros.</p><h4>Búsqueda lineal</h4><p>Hablamos brevemente de la búsqueda lineal, o indexación plana, cuando mencionamos el enfoque de fuerza bruta de comparar el vector de consulta con todos los vectores presentes en la base de datos. Aunque puede funcionar bien en conjuntos de datos pequeños, el rendimiento disminuye rápidamente a medida que aumenta el número de vectores y dimensiones (complejidad O(n)).</p><p>Por suerte, existen enfoques más eficientes llamados <strong>vecinos aproximados</strong> (ANN), donde las distancias entre vectores de incrustación se precalculan y se almacenan y organizan vectores similares de forma que se mantengan cerca unos de otros, por ejemplo usando clústeres, árboles, hashes o grafos. Estos enfoques se denominan "aproximados" porque normalmente no garantizan una precisión del 100%. El objetivo final es <strong>reducir el alcance de la búsqueda</strong> tanto y lo más rápido posible para centrar solo en las áreas que tienen más probabilidades de contener vectores similares o <strong>reducir la dimensionalidad de los vectores</strong>.</p><h4>Árboles de dimensión K</h4><p>Un árbol de dimensión K, o árbol KD, es una generalización de un árbol binario de búsqueda que almacena puntos en un espacio de dimensión k y funciona dividiendo continuamente el espacio de búsqueda en árboles más pequeños a la izquierda y a la derecha donde se indexan los vectores. En el momento de buscar, el algoritmo simplemente tiene que visitar algunas ramas del árbol alrededor del vector de consulta (el punto rojo de la Figura 11) para encontrar el vecino más cercano (el punto verde de la Figura 11). Si se aplicar más de k vecinos, entonces el área amarilla se extiende hasta que el algoritmo encuentre más vecinos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt55e01707cd9fca57/6a17e0dfbe60866a90004653/e21af2d613112279089b6fa2166359233b10019d-829x860.png" alt="Algoritmo de árbol KD en búsqueda vectorial" /><p>El mayor beneficio del algoritmo del árbol KD es que nos permite centrarnos rápidamente solo en algunas ramas localizadas del árbol, eliminando así la mayoría de los vectores de la consideración. Sin embargo, la eficiencia de este algoritmo disminuye a medida que aumenta el número de dimensiones, ya que es necesario visitar muchas más ramas que en espacios de dimensiones inferiores.</p><h4>Índice de archivos invertido</h4><p>El enfoque de índice de archivos invertido (IVF) también es un <strong>algoritmo de partición de espacio</strong> que asigna vectores cercanos entre sí a su centroide compartido. En el espacio 2D, esto se visualiza mejor con un diagrama de Voronoi, como se muestra en la Figura 12:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b7e4fe6deff5ef3/6a17e0e17b54f940eb8b381c/33ddaaa818ab2f2a5fc87982c82c2a34cb849e33-640x640.png" alt="Representación de Voronoi de un índice de archivo invertido en el espacio 2D " /><p>Podemos ver que el espacio 2D anterior está dividido en 20 grupos, cada uno con su centroide denotado como puntos negros. Todos los vectores de incrustación en el espacio se asignan al clúster cuyo centroide está más cercano a ellos. En el momento de buscar, el algoritmo primero determina el clúster en el que centrar encontrando el centroide más cercano al vector de consulta, y luego puede simplemente enfocar en esa zona, y también en las circundantes si es necesario, para encontrar los vecinos más cercanos.</p><p>Este algoritmo sufre el mismo problema que los árboles KD cuando se usa en espacios de alta dimensión. Esto se llama la maldición de la dimensionalidad, y ocurre cuando el volumen del espacio aumenta tanto que todos los datos parecen escasos y la cantidad de datos que se necesitaría para obtener resultados más precisos crece exponencialmente. Cuando los datos son escasos, resulta más difícil para estos algoritmos de partición del espacio organizar los datos en clústeres. Por suerte para nosotros, existen otros algoritmos y técnicas que alivian este problema, como se detalla a continuación.</p><h4>Cuantificación</h4><p>La cuantización es un enfoque basado en <strong>compresión</strong>que nos permite reducir el tamaño total de la base de datos disminuyendo la precisión de los vectores de incrustación. Esto se puede lograr usando <strong>cuantización escalar (SQ)</strong> convirtiendo los valores vectoriales de punto flotante en valores enteros. Esto no solo reduce el tamaño de la base de datos en un factor de 8, sino que también disminuye el consumo de memoria y acelera el cálculo de distancias entre vectores en el momento de búsqueda.</p><p>Otra técnica se llama <strong>cuantización por producto (PQ),</strong> que primero divide el espacio en subespacios de dimensión inferior, y luego los vectores muy cercanos se agrupan en cada subespacio usando un algoritmo de agrupamiento (similar a las k-medias).</p><p>Cabe señalar que la cuantización es diferente de <strong>la reducción de dimensionalidad</strong>, donde se reduce el número de dimensiones, es decir, los vectores simplemente se acortan.</p><h4>Pequeños Mundos Jerárquicos Navegables (HNSW)</h4><p>Si parece complejo solo con leer el nombre, no te preocupes, ¡no lo es realmente! En resumen, Jerarchical Navigable Small Worlds es un algoritmo basado en grafos de múltiples capas que es muy popular y eficiente. Es empleado por muchas bases de datos vectoriales diferentes, incluyendo Apache Lucene. Una representación conceptual de HNSW puede ver en la Figura 13, a continuación.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f4f4a8e393c8d53/6a17e0e3e8fbcefa193a1861/189ef9a8bec476e379222c454644ba4c1f952085-1400x840.png" alt="Pequeños Mundos Jerárquicos Navegables (HNSW)" /><p>En la capa superior, podemos ver un grafo de muy pocos vectores que tienen los enlaces más largos entre ellos, es decir, un grafo de vectores conectados con la menor similitud. Cuanto más profundizamos en capas inferiores, más vectores encontramos y más denso se vuelve el grafo, con cada vez más vectores más cerca entre sí. En la capa más baja, podemos encontrar todos los vectores, siendo los más similares los más cercanos entre sí.</p><p>En el momento de la búsqueda, el algoritmo comienza desde la capa superior en un punto de entrada arbitrario y encuentra el vector más cercano al vector de consulta (mostrado por el punto gris). Luego, se mueve una capa por debajo y repite el mismo proceso, empezando por el mismo vector que dejó en la capa anterior, y así sucesivamente, capa tras capa, hasta llegar a la capa más baja y encontrar al vecino más cercano al vector de consulta.</p><h4>Hashing sensible a la localidad (LSH)</h4><p>En la misma línea que todos los demás enfoques presentados hasta ahora, el hashing sensible a la localidad busca reducir significativamente el espacio de búsqueda para aumentar la velocidad de recuperación. Con esta técnica, los vectores de incrustación se transforman en valores hash, todo preservando la información de similitud, de modo que el espacio de búsqueda se convierte finalmente en una simple tabla hash que se puede consultar en lugar de un grafo o árbol que hay que recorrer. El principal beneficio de los métodos basados en hash es que los vectores que contienen un número arbitrario (grande) de dimensiones pueden mapearse a hashes de tamaño fijo, lo que acelera enormemente el tiempo de recuperación sin sacrificar demasiada precisión.</p><p>Existen muchas formas diferentes de hacer hashing de datos en general, y de incrustar vectores en individuo, pero este artículo no profundizará en los detalles de cada una de ellas. Los métodos de hashing convencionales suelen producir hashes muy diferentes para datos que parecen muy similares. Dado que los vectores de incrustación están compuestos por valores flotantes, tomemos dos valores de flotación de muestra que se consideran muy cercanos entre sí en aritmética vectorial (por ejemplo, 0,73 y 0,74) y los pasemos por algunas funciones de hash comunes. Al mirar los resultados a continuación, es bastante obvio que las funciones de hash comunes no mantienen la similitud entre las entradas.</p><p>Función de hash</p><p>0.73</p><p>0.74</p><p>MD5</p><p>1342129d04cd2924dd06cead4cf0a3ca</p><p>0aec1b15371bd979cfa66b0a50ebecc5</p><p>SHA1</p><p>49d2c3e0e44bff838e1db571a121be5ea874e8d9</p><p>a534e76482ade9d9fe4bff3035a7f31f2f363d77</p><p>SHA256</p><p>99d03fc3771fe6848d675339fc49eeb1cb8d99a12e6358173336b99a2ec530ea</p><p>5ecbc825ba5c16856edfdaf0abc5c6c41d0d8a9c508e34188239521dc7645663</p><p>Mientras que los métodos convencionales de hashing intentan <em>minimizar las colisiones de hashing</em> entre fragmentos de datos similares, el objetivo principal del hashing sensible a la localidad es hacer exactamente lo contrario, es decir, <em>maximizar las colisiones de hashing</em> para que datos similares caigan dentro del mismo cubo con alta probabilidad. Al hacerlo, los vectores de incrustación que están muy cerca en un espacio multidimensional se hashearán hasta un valor de tamaño fijo que cae en el mismo cubo. Dado que LSH permite que esos vectores hasheados mantengan su proximidad, esta técnica resulta muy útil para agrupar datos y búsquedas de vecinos más cercanos.</p><p>Todo el trabajo pesado ocurre en el momento de indexación, cuando hay que calcular los hashes, mientras que en busca solo necesitamos hacer hash del vector de consulta para buscar el cubo que contiene los vectores de incrustación más cercanos. Una vez encontrado el cubo candidato, normalmente se realiza una segunda ronda para identificar los vectores vecinos más cercanos al vector de consulta.</p><h2>Concluyamos</h2><p>Para introducir la búsqueda vectorial, tuvimos que cubrir bastante terreno en este artículo. Tras comparar las diferencias entre la búsqueda léxica y la búsqueda vectorial, aprendimos cómo los modelos de redes neuronales de aprendizaje profundo logran capturar la semántica de los datos no estructurados y transcodificar su significado en vectores de incrustación de alta dimensión, una secuencia de números de coma flotante que representan la similitud de los datos a lo largo de cada una de las dimensiones del modelo. También cabe destacar que la búsqueda vectorial y la búsqueda léxica no son técnicas de recuperación de información complementarias en competencia (como veremos en la tercera parte de este serial cuando profundizaremos en la búsqueda híbrida).</p><p>Luego de eso, introdujimos un bloque fundamental de la búsqueda vectorial, a saber, las funciones de distancia (y similitud) que nos permiten medir la proximidad de dos vectores y evaluar la similitud de los conceptos que representan.</p><p>Por último, revisamos diferentes variantes de los algoritmos y técnicas de búsqueda vectorial más populares, que pueden basar en árboles, grafos, clústeres o hashes, cuyo objetivo es acotar rápidamente una zona específica del espacio multidimensional para encontrar a los vecinos más cercanos sin tener que recorrer todo el espacio como haría una búsqueda lineal por fuerza bruta.</p><p>Si te gusta lo que estás leyendo, cerciórate de echar un vistazo a las otras partes de este serial:</p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/vector-search-set-up-elasticsearch">Parte 2: Cómo configurar la búsqueda vectorial en Elasticsearch</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/hybrid-search-elasticsearch">Parte 3: Búsqueda híbrida usando Elasticsearch</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/introduction-to-vector-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/introduction-to-vector-search</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <dc:creator><![CDATA[Valentin Crettaz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt70da374b8dc0c490/6a17e0e46864a473aab686b3/63eea8ea95b49e7241e539f65bf5aa3bb8823fff-1200x628.png" length="0" type="image/png"/>
    <pubDate>Thu, 06 Feb 2025 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[Cómo usar la búsqueda híbrida para un catálogo de productos de comercio electrónico]]></title>
    <description><![CDATA[Aprende a usar la búsqueda híbrida para crear un catálogo de productos de comercio electrónico, empleando facetados, promociones, personalización y análisis de comportamiento.]]></description>
    <content:encoded><![CDATA[<p>En este artículo, demostraremos cómo implementar una búsqueda híbrida que combine los resultados de la búsqueda en texto completo con la búsqueda vectorial. Al unificar estos dos enfoques, la búsqueda híbrida mejora la amplitud de resultados, aprovechando lo mejor de ambas estrategias de búsqueda.</p><p>Además de integrar la búsqueda híbrida, demostraremos cómo agregar funciones que hagan que tu solución de búsqueda sea aún más robusta. Estas incluyen facetados y promociones personalizadas de productos. Además, te mostraremos cómo capturar las interacciones del usuario y generar información valiosa empleando la herramienta de Análisis de Comportamiento de Elastic.</p><p>En esta implementación, verás cómo construir tanto la interfaz que permite a los usuarios ver e interactuar con los resultados de búsqueda como la API responsable de devolver la información. Para acceder a los repositorios con el código fuente, los enlaces se proporcionan a continuación:</p><ul><li><p><a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search">https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search</a></p></li><li><p><a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/app-product-store">https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/app-product-store</a> </p></li></ul><p>Dividimos esta guía en varios pasos, desde la creación del índice hasta la implementación de funciones avanzadas como el facetado y la personalización de resultados. Al final, tendrás una solución de búsqueda robusta lista para usar en un escenario de comercio electrónico.</p><h2>Configuración de entornos para búsqueda híbrida de comercio electrónico</h2><p>Antes de comenzar la implementación, necesitamos configurar el entorno. Puedes elegir usar un servicio en Elastic Cloud o una solución contenedora para gestionar Elasticsearch. Si eliges contenerización, en este repositorio puedes encontrar una configuración mediante Docker Compose: <a href="https://github.com/andreluiz1987/product-store-search/blob/main/docker/docker-compose.yml">docker-compose.yml</a>.</p><h2>Creación de índices e ingesta de catálogos de productos</h2><p>El índice se creará a partir de un catálogo de productos cosméticos, que incluye campos como nombre, descripción, foto, categoría y etiquetas. Los campos usados para la búsqueda en texto completo, como "nombre" y "descripción", se mapearán como <code>text</code>, mientras que los campos usados para agregaciones, como "categoría" y "marca", se mapearán como <code>keyword</code> para permitir el facetado.</p><p>El campo "descripción" se empleará para la búsqueda vectorial, ya que proporciona más contexto sobre los productos. Este campo se definirá como <code>dense_vector,</code> almacenar la representación vectorial de la descripción.</p><p>El mapeo del índice será el siguiente:</p>{
   "mappings":{
      "properties":{
         "id":{
            "type":"keyword"
         },
         "brand":{
            "type":"text",
            "fields":{
               "keyword":{
                  "type":"keyword"
               }
            }
         },
         "name":{
            "type":"text"
         },
         "price":{
            "type":"float"
         },
         "price_sign":{
            "type":"keyword"
         },
         "currency":{
            "type":"keyword"
         },
         "image_link":{
            "type":"keyword"
         },
         "description":{
            "type":"text"
         },
         "description_embeddings":{
            "type":"dense_vector",
            "dims":384
         },
         "rating":{
            "type":"keyword"
         },
         "category":{
            "type":"keyword"
         },
         "product_type":{
            "type":"keyword"
         },
         "tag_list":{
            "type":"keyword"
         }
      }
   }
}<p>El script para crear el índice se puede encontrar <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search/infra/create_index.py">aquí</a>.</p><h2>Generación de incrustación</h2><p>Para vectorizar las descripciones de productos, empleamos el modelo all-MiniLM-L6-v2. En este caso, la aplicación es responsable de generar las incrustaciones antes de indexar. Otra opción sería importar el modelo al clúster de Elasticsearch, pero para este entorno local, elegimos realizar la vectorización directamente dentro de la aplicación.</p><p>Empleamos el conjunto de datos cosméticos disponible en <a href="https://www.kaggle.com/datasets/shivd24coder/cosmetic-brand-products-dataset">Kaggle</a> para poblar el índice y, para mejorar la eficiencia de la ingesta de datos, empleamos procesamiento por lotes. Durante esta misma etapa de ingestión, generaremos las incrustaciones para el campo "descripción" e indexarémolas en el nuevo campo "description_embeddings".</p><p>El proceso completo de ingesta de datos puede seguir y ejecutar directamente a través del <strong>Jupyter Notebook</strong> disponible en el repositorio. El cuaderno ofrece una guía paso a paso sobre cómo se leen, procesan e indexan los datos en Elasticsearch, permitiendo una fácil replicación y experimentación.</p><p>Puedes acceder al cuaderno en el siguiente enlace: <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search/ingestion/ingestion.ipynb">Cuaderno de Ingestión.</a></p><h2>Implementación de búsqueda híbrida</h2><p>Ahora, implementemos la búsqueda híbrida. Para la búsqueda basada en palabras clave, usamos la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html">consulta multi_match</a> , dirigida a los campos "nombre", "categoría" y "descripción". Esto garantiza que se recuperen los documentos que contienen el término de búsqueda en cualquiera de estos campos.</p><p>Para la búsqueda vectorial, usamos la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">consulta KNN</a>. El término de búsqueda debe vectorizarse antes de ejecutar la consulta, y esto se hace usando el método que vectoriza el término de entrada. Ten en cuenta que el mismo modelo usado durante la ingestión también se emplea para el término de búsqueda.</p><p>La combinación de ambas búsquedas se realiza mediante el algoritmo <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">de Fusión de Rango Recíproco (RRF), </a>que fusiona los resultados de ambas consultas y aumenta la precisión de la búsqueda al reducir el ruido. RRF permite que tanto las búsquedas basadas en palabras clave como las vectoriales trabajen conjuntamente, mejorando la comprensión de la consulta del usuario.</p>query = {
   "retriever": {
       "rrf": {
           "retrievers": [
               {
                   "standard": {
                       "query": organic_query['query']
                   }
               },
               {
                   "knn": {
                       "field": "description_embeddings",
                       "query_vector": vector,
                       "k": 5,
                       "num_candidates": 20
                   }
               }
           ],
           "rank_window_size": 20,
           "rank_constant": 5
       }
   },
   "_source": organic_query['_source']
}<h3>Comparando resultados: búsqueda por palabras clave vs. búsqueda híbrida</h3><p>Ahora, comparemos los resultados de una búsqueda tradicional por palabras clave con la búsqueda híbrida. Al buscar "base para piel seca" usando la búsqueda por palabras clave, obtenemos los siguientes resultados:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcb5405df787bc8f5/6a17023e961e697ca1c4cdb4/52e717aa3c9c1fadfadb639f5fb77cf8e47e3b34-1600x1021.png" alt="Comparando resultados: búsqueda por palabras clave vs. búsqueda híbrida" /><ol><li><p><strong>Maquillaje Revlon ColorStay para piel normal / secaDescripción</strong>: Revlon ColorStay Makeup ofrece una cobertura duradera con una fórmula ligera que no se desvanece, ni se desvanece. Con Time Release \nTechnology, esta fórmula sin aceites y con equilibrio de humedad está especialmente formulada para piel normal o seca para proporcionar hidratación continua. Características: El maquillaje resulta cómodo y se usa hasta 24 horas\nCobertura media a total\nDisponible en una gama de tonos preciosos
</p></li><li><p><strong>Maybelline Dream Smooth Mousse BaseDescripción</strong>: Por qué te encantaráLa base única batida con crema ofrece una perfección 100% suave para bebés.\n\nPiel Se ve y se siente hidratado durante 14 horas - nunca áspero ni seco\tLa fórmula ligera ofrece una cobertura perfectamente hidratante\�Se difumina perfectamente y se siente fresca todo el día\tSin aceite, sin fragancia, probado por dermatólogos, probado por alergias, no comedogénico \u2019 no obstruirá los poros.\nSeguro Para piel sensible.</p></li></ol><p><strong>Análisis</strong>: Al buscar "base para piel seca", los resultados se obtuvieron mediante la coincidencia exacta entre las palabras clave de búsqueda y los títulos y descripciones de los productos. Sin embargo, este partido no siempre refleja la mejor elección. Por ejemplo, el <strong>maquillaje Revlon ColorStay para piel normal / seca</strong> es una buena opción, ya que está formulado específicamente para piel seca. Aunque no contiene aceite, su fórmula está diseñada para proporcionar hidratación continua. En cambio, también recibimos <strong>la Maybelline Dream Smooth Mousse Foundation</strong>, que, aunque sin aceite y mencionando hidratación, suele recomendar más para piel grasa o mixta, ya que los productos sin aceite tienden a centrar en controlar el sebo en lugar de proporcionar la hidratación extra necesaria para la piel seca. Esto pone de manifiesto la limitación de las búsquedas basadas en palabras clave, que pueden devolver productos que no satisfacen completamente las necesidades específicas de las personas con piel seca.</p><p>Ahora, al realizar la misma búsqueda usando el enfoque híbrido:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt412e38b327cf6a4f/6a17024066c4f94c52f8beb9/13b562a5120619355ba0861976102e208964a49f-1600x1021.png" alt="Realizar búsqueda mediante el enfoque híbrido" /><ol><li><p><strong>CoverGirl Outlast Stay Luminous Foundation Creamy Natural (820):D escription</strong>: CoverGirl Outlast Stay Luminous Foundation es perfecta para lograr un acabado luminoso y un brillo sutil. Es sin aceites, con una fórmula no grasienta que da a tu piel una luminosidad natural que dura todo el día. Esta base de maquillaje durante todo el día hidrata la piel y proporciona una cobertura impecable.

<strong>Análisis: </strong>Este producto es una combinación relevante porque enfatiza la hidratación, algo fundamental para usuarios con piel seca. Los términos "hidrata la piel" y "acabado húmedo" coinciden con la intención del usuario de encontrar una base para piel seca. La búsqueda vectorial probablemente entendió el concepto de hidratación y lo relacionó con la necesidad de una base que trate la piel seca.
</p></li><li><p><strong>Maquillaje Revlon ColorStay para piel normal / seca: Descripción:</strong> El maquillaje Revlon ColorStay ofrece una cobertura duradera con una fórmula ligera que no se empacha, no se desvanece ni se desprende de la piel. Con la Tecnología de Liberación Prolongada, esta fórmula sin aceites y equilibrada por la humedad está especialmente formulada para pieles normales o secas para proporcionar hidratación continua.

<strong>Análisis: </strong>Este producto responde directamente a las necesidades de los usuarios con piel seca, mencionando explícitamente que está formulado para piel normal o seca. La "fórmula de equilibrio de humedad" y la hidratación continua son ideales para quien busca una base adaptada a la piel seca. La búsqueda vectorial recuperó con éxito este resultado, no solo por la coincidencia de palabras clave, sino también por el enfoque en la hidratación y la mención específica de la piel seca como grupo objetivo.
</p></li><li><p><strong>Base de SérumDescripción: </strong>Las bases sérum son formulaciones ligeras de cobertura media disponibles en una amplia gama de tonos en 21 tonos. Estas bases ofrecen una cobertura moderada que parece natural y con una sensación de sérum muy ligera. Tienen una viscosidad muy baja y se dispensan con la bomba suministrada o con el gotero de vidrio opcional, disponible para su compra por separado si se prefiere.

<strong>Análisis:</strong> En este caso, la descripción enfatiza una base sérum ligera con un toque natural, que se adapta a las necesidades de las personas con piel seca, ya que suelen buscar productos suaves, hidratantes y que ofrezcan un acabado no empalagoso. La búsqueda vectorial probablemente captó el contexto más amplio de la ligera y la cobertura natural y la textura similar al sérum, que se asocia con la retención de humedad y una aplicación cómoda, lo que la hace relevante para piel seca, aunque el término "piel seca" no se mencione explícitamente.</p></li></ol><h2>Implementación de facetas</h2><p>Los aspectos son esenciales para refinar y filtrar los resultados de búsqueda de forma eficiente, proporcionando a los usuarios una navegación más enfocada, especialmente en escenarios con una gran variedad de productos, como el comercio electrónico. Permiten a los usuarios ajustar los resultados según atributos como categoría, marca o precio, haciendo la búsqueda más precisa. Para implementar esta función, empleamos <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html">agregaciones de términos</a> en los campos <code>category</code> y <code>brand</code> , que se definieron como <code>keyword</code> durante la fase de creación del índice.</p>    query = build_query(term, categories, product_types, brands)
    query["aggs"] = {
        "product_types": {"terms": {"field": "product_type"}},
        "categories": {"terms": {"field": "category"}},
        "brands": {"terms": {"field": "brand.keyword"}}
    }<p>El código completo de la implementación se puede encontrar <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search/api/api.py#L144">aquí</a>.</p><p>A continuación se vean los resultados de la búsqueda de "base para piel seca":</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt31080652a60d8600/6a1702416234e0af7ddb18e7/e8907af359c021eaa38ec7a6a53f10c325ee8ade-1146x1248.png" alt="Resultados facetarios de la búsqueda de &quot;base para piel seca&quot;" /><h2>Personalización de resultados: consultas fijadas</h2><p>En algunos casos, puede ser beneficioso promocionar ciertos productos en los resultados de búsqueda. Para esto, usamos <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-pinned-query.html"><strong>Consultas Fijadas</strong></a>, que permiten que productos específicos aparezcan en la parte superior de los resultados. A continuación, realizaremos una búsqueda del término "Fundación" sin promocionar ningún producto:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt31e75c815262993e/6a170243509168ae42e1b95d/9396c4ed358e7a68eed47f17dd6915d0bad492aa-1600x1157.png" alt="Busca el término &quot;Fundación&quot; sin promocionar ningún producto" /><p>En nuestro ejemplo, podemos promocionar productos que lleven la etiqueta "Sin gluten". Al emplear los identificadores de producto, nos cercioramos de que se prioricen en los resultados de búsqueda. En concreto, promocionaremos los siguientes productos: <strong>Serum Foundation</strong> (ID: 1043), <strong>Coverage Foundation</strong> (ID: 1042) y <strong>Realist Invisible Setting Powder</strong> (ID: 1039).</p>{
   "query":{
      "pinned":{
         "ids":[
            "1043",
            "1042",
            "1039"
         ],
         "organic":{
            "bool":{
               "must":[
                  {
                     "multi_match":{
                        "query":"foundation",
                        "fields":[
                           "name",
                           "category",
                           "description"
                        ]
                     }
                  }
               ]
            }
         }
      }
   }
}<p>Empleamos identificadores de producto específicos para cerciorarnos de que se prioricen en los resultados de la consulta. La estructura de la consulta incluye una lista de IDs de producto que deben estar "fijados" en la parte superior (en este caso, IDs 1043, 1042 y 1039), mientras que los resultados restantes siguen el flujo orgánico de la búsqueda, usando una combinación de condiciones como la consulta de texto en los campos "nombre", "categoría",  y "descripción". De este modo, es posible promocionar los elementos de forma controlada, cerciorando su visibilidad, mientras que el resto de la búsqueda se basa en la relevancia habitual.</p><p>A continuación, puedes ver el resultado de la ejecución de la consulta con los productos promovidos:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt53a6d5cbef227f16/6a1702458b73cb68f0189ef3/8c1a547bfe31360c405eae0891c666635051a51b-1600x1039.png" alt="Resultado de la ejecución de la consulta con los productos promovidos" /><p>El código completo de la consulta se puede encontrar <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search/api/api.py#L112">aquí</a>.</p><h2>Análisis del comportamiento de búsqueda con análisis conductual</h2><p>Hasta ahora, ya agregamos funcionalidades para mejorar la relevancia de los resultados de búsqueda y facilitar la descubribilidad del producto. Ahora, finalizaremos nuestra solución de búsqueda incluyendo una función que nos ayudará a analizar el comportamiento de búsqueda de los usuarios, identificando patrones como consultas con o sin resultados y clics en los resultados. Para ello, emplearemos la función <strong>de Análisis de Comportamiento</strong> proporcionada por Elastic. Con él, en solo unos pocos pasos, podemos monitorizar y analizar el comportamiento de búsqueda de los usuarios, obteniendo información valiosa para optimizar la experiencia de búsqueda.</p><h3>Creación de la colección de análisis de comportamiento</h3><p>Nuestra primera acción será crear una colección que será responsable de recibir todos los eventos de análisis de conducta. Para crear la colección, accede a la interfaz Kibana en <strong>Search &gt; Behavioral Analytics</strong>. En el ejemplo siguiente, creamos la colección llamada <code>tracking-search</code>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9b4cb5480ab0bfef/6a1702474a531b189936a7e7/c05e533212a3690c5ea9f2d5226cbcdc901c482a-1600x1009.png" alt="Análisis de comportamiento: nombrar tu colección" /><h3>Integración del análisis de comportamiento en la interfaz</h3><p>Nuestra aplicación <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/app-product-store">front-end</a> fue desarrollada en JavaScript y, para integrar Behavioral Analytics, seguiremos los pasos descritos en la documentación oficial de Elastic para instalar el <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-start.html#behavioral-analytics-start-ui-integration-js-client"><strong>Behavioral Analytics JavaScript Tracker</strong></a>.</p><h3>Implementación del rastreador JavaScript</h3><p>Ahora, importaremos el cliente tracker a nuestra aplicación y usaremos los métodos <code>trackPageView</code>, <code>trackSearch</code>y <code>trackSearchClick</code> para capturar las interacciones del usuario.</p><p><strong>Aviso legal</strong>: Aunque estamos empleando una herramienta para recopilar datos de interacción de usuarios, es esencial garantizar el cumplimiento del <strong>RGPD</strong>. Esto significa informar claramente a los usuarios sobre qué datos se están recopilando, cómo se emplearán y ofrecer la opción de optar por no participar en el seguimiento. Además, debemos implementar medidas de seguridad estables para proteger la información recopilada y respetar los derechos de los usuarios, como el acceso y la eliminación de datos, cerciorando que todos los pasos cumplan con los principios del RGPD.
</p><p><strong>Paso 1: Creación de la instancia del rastreador</strong></p><p>Primero, crearemos la instancia tracker que monitorizará las interacciones. En esta configuración, definimos el punto final objetivo, el nombre de la colección y la clave API:</p>createTracker({
  endpoint: "https://endpoint:443",
  collectionName: "tracking-search",
  apiKey: "api-key"
});<p><strong>Paso 2: Captura de vistas de página</strong></p><p>Para registrar las visualizaciones de página, podemos configurar el evento <code>trackPageView</code> :</p>    trackPageView({
      page: {
        title: "home-page"
      },
    });<p>Para más detalles sobre el evento <code>trackPageView</code> , puedes consultar esta <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-event-reference.html#behavioral-analytics-event-reference-pageview-fields">documentación</a>.</p><p><strong>Paso 3: Captura de consultas de búsqueda</strong></p><p>Para monitorizar las acciones de búsqueda de los usuarios, emplearemos el método <code>trackSearch</code> :</p>      trackSearch({
        search: {
          query: searchTerm,
          results: {
            items: documents,
            total_results: response.data.length,
          },
        },
      });<p>Aquí estamos recopilando el término de búsqueda y los resultados de la búsqueda.</p><p><strong>Paso 4: Seguimiento de clics en los resultados de búsqueda</strong></p><p>Por último, para capturar clics en los resultados de búsqueda, emplearemos el método <code>trackSearchClick</code> :</p>trackSearchClick({
      document: { id: product.id, index: "products-catalog"},
      search: {
        query: searchTerm,
        page: {
          current: 1,
          size: products.length,
        },
        results: {
          items: documents,
          total_results: products.length,
        },
        search_application: "app-product-store"
      },
    });<p>Recopilamos información sobre el ID del documento al que se hace clic, así como el término de búsqueda y los resultados.</p><h3>Análisis de los datos en Kibana</h3><p>Ahora que se están capturando eventos de interacción de usuarios, podemos obtener datos valiosos sobre las acciones de búsqueda. Kibana emplea la herramienta de Análisis del Comportamiento para visualizar y analizar estos datos conductuales. Para ver los resultados, simplemente navega a <strong>Búsqueda &gt; Análisis de Comportamiento &gt; Mi Colección</strong>, donde se mostrará un resumen de los eventos capturados.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdab4f4507c5a4732/6a17024860084b34ca3c4411/e219c4af2e01b0ccc1b9079459a07832835abc75-1600x1155.png" alt="Análisis de los datos en Kibana" /><p>En esta visión general, obtenemos una visión general de los eventos capturados para cada acción integrada en nuestra interfaz. A partir de esta información, podemos obtener información valiosa sobre el comportamiento de búsqueda de los usuarios. Sin embargo, si quieres crear paneles personalizados con métricas más relevantes para tu escenario específico, Kibana ofrece herramientas poderosas para crear paneles, permitiéndote crear diversas visualizaciones de tus métricas.</p><p>A continuación, creé algunas visualizaciones y gráficos para monitorizar, por ejemplo, los términos más buscados a lo largo del tiempo, consultas que no devolvieron resultados, una nube de palabras que resalta los términos más buscados y, finalmente, una visualización geográfica para identificar de dónde proviene el acceso a las búsquedas.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt04ced4406436f942/6a17024a5091685116e1b961/84a2c39dab77a3f9366c258a3a45d2cbc7df125e-1600x689.png" alt="Visualizaciones y gráficos para monitorizar" /><h2>Conclusión</h2><p>En este artículo, implementamos una solución de búsqueda híbrida que combina búsqueda por palabras clave y vectorial, ofreciendo resultados más precisos y relevantes para los usuarios. También exploramos cómo emplear funciones adicionales, como facetas y personalización de resultados con Consultas Fijadas, para crear una experiencia de búsqueda más completa y eficiente.</p><p>Además, integramos el <strong>Análisis Conductual</strong> de Elastic para capturar y analizar el comportamiento del usuario durante sus interacciones con el motor de búsqueda. Empleando métodos como <code>trackPageView</code>, <code>trackSearch</code>y <code>trackSearchClick</code>, pudimos monitorizar consultas de búsqueda, clics en los resultados y vistas de página, generando información valiosa sobre el comportamiento de búsqueda.</p><h2>Referencias</h2><p>Conjunto de datos</p><p><a href="https://www.kaggle.com/datasets/shivd24coder/cosmetic-brand-products-dataset">https://www.kaggle.com/datasets/shivd24coder/cosmetic-brand-products-dataset</a></p><p>Transformador</p><p><a href="https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2">https://huggingface.co/sentence-transformers/all-minilm-l6-v2</a></p><p>Fusión recíproca de rangos</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html</a></p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/retriever.html#rrf-retriever">https://www.elastic.co/guide/en/elasticsearch/reference/current/retriever.html#rrf-retriever</a></p><p>Consulta Knn</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html</a></p><p>Consulta fijada</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-pinned-query.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-pinned-query.html</a></p><p>APIs de Análisis de Comportamiento</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-apis.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-apis.html</a></p><p>https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-overview.html</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hybrid-search-ecommerce</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hybrid-search-ecommerce</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <dc:creator><![CDATA[Andre Luiz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt63c711d1bf9b2501/6a17024c66c4f9c2b7f8bebd/05578fc595a12f6b1ebf88a10a2a31e9971b545e-1200x628.png" length="0" type="image/png"/>
    <pubDate>Tue, 12 Nov 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Crear una aplicación de búsqueda con Blazor y Elasticsearch]]></title>
    <description><![CDATA[Aprende a construir una aplicación de búsqueda usando Blazor y Elasticsearch, y cómo emplear el cliente Elasticsearch .NET para búsqueda híbrida.]]></description>
    <content:encoded><![CDATA[<p>En este artículo, aprenderás cómo aprovechar tus habilidades en C# para crear una aplicación de búsqueda usando Blazor y Elasticsearch. Vamos a usar el cliente <a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/introduction.html">Elasticsearch .NET</a> para ejecutar consultas <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/full-text-queries.html">de texto completo</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search.html">semánticas</a> y <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">búsquedas híbridas</a> .</p><p><strong>NOTA</strong> Si conoces la versión anterior <a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/nest.html">del cliente</a> Nest de Elasticsearch en C#, lee esta <a href="https://www.elastic.co/search-labs/blog/net-client-evolution">entrada del blog</a> sobre la deprecación del cliente NEST y las nuevas funciones. <em>NEST fue la generación anterior del cliente .NET que fue reemplazado por el </em><code>Elastic.Clients.Elasticsearch package</code></p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltca8d1da68641fac0/6a17f65763173044d4585bfb/18f890286ca122cd97286c09ebf740208b802d0b-650x395.png" alt="Build app Blazor: esre con diagrama Blazor" /><ul><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#what-is-blazor?">¿Qué es Blazor?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#what-is-esre?">¿Qué es ESRE?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#configuring-elser">Configuración de ELSER</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#indexing-data">Datos de indexación</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#building-the-app-with-blazor-&amp;-elasticsearch">Aplicación en edificios</a></p></li></ul><h2>¿Qué es Blazor?</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91278ae422883e28/6a17f6592f4a5c3213fa8a95/622741915d016b68bf94f742332d10d736d60052-707x461.png" alt="Servidor Blazor" /><p><a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor">Blazor</a> es un framework sitio web de código abierto basado en HTML, CSS y C# creado por Microsoft para permitir a los desarrolladores crear aplicaciones sitio web que se ejecuten en el cliente o en el servidor. Blazor también permite crear componentes reutilizables para construir aplicaciones más rápido; permite a los desarrolladores construir la vista HTML y las acciones en C# dentro del mismo archivo, lo que ayuda a mantener un código legible y limpio. Además, con Blazor Hybrid puedes crear aplicaciones móviles nativas accediendo a las capacidades nativas de la plataforma mediante código .NET.</p><p>Algunas de las características que hacen de Blazor un excelente marco para trabajar:</p><ul><li><p>Opciones de renderizado tanto en el lado servidor como en el cliente</p></li><li><p>Componentes reutilizables de la interfaz</p></li><li><p>Actualizaciones en tiempo real con SignalR</p></li><li><p>Gestión del estado integrada</p></li><li><p>Sistema de enrutamiento integrado</p></li><li><p>Comprobaciones fuertes de tipado y tiempo de compilación</p></li></ul><h3>¿Por qué Blazor?</h3><p>Blazor ofrece varios beneficios sobre otros frameworks y librerías: permite a los desarrolladores usar C# tanto para el código cliente como para el servidor, proporcionando una fuerte verificación de tipado y compilación que mejora la fiabilidad. Se integra perfectamente con el ecosistema .NET, permitiendo la reutilización de librerías y herramientas .NET, y ofrece un soporte robusto para depuración.</p><h2>¿Qué es ESRE?</h2><p><a href="https://www.elastic.co/elasticsearch/elasticsearch-relevance-engine">El Elasticsearch Relevance Engine™ (ESRE)</a> es un conjunto de <a href="https://www.elastic.co/guide/en/esre/current/learn.html">herramientas para construir aplicaciones de búsqueda</a> empleando aprendizaje automático e inteligencia artificial sobre el poderoso motor de búsqueda Elasticsearch.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf313ba986de01b93/6a17d851505ac306fcad8975/4c0f2645ed1c27fe3ef61a1a9126adadfd8d5368-721x421.png" alt="Esre" /><p>Para saber más sobre ESRE, puedes leer nuestra interesante entrada de blog <a href="https://www.elastic.co/search-labs/blog/introducing-elasticsearch-relevance-engine-esre">aquí</a></p><h2>Configuración de ELSER</h2><p>Para aprovechar las capacidades <a href="https://www.elastic.co/elasticsearch/elasticsearch-relevance-engine">ESRE</a> de Elastic, vamos a emplear <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">ELSER</a> como nuestro proveedor de modelos.</p><p><em>Nota: para usar los modelos ELSER de Elasticsearch, debes tener una licencia Platinum o Enterprise, y disponer de un nodo mínimo dedicado de Machine Lerning (ML) de 4GB de tamaño. Lee más</em> <a href="https://www.elastic.co/guide/en/machine-learning/8.15/ml-nlp-elser.html#elser-req"><em>sobre esto aquí.</em></a></p><p>Empieza creando el punto final de inferencia:</p>PUT _inference/sparse_embedding/my-elser-model
{
  "service": "elser",
  "service_settings": {
    "num_allocations": 1,
    "num_threads": 1
  }
}<p>Si es la primera vez que usas ELSER, puede que te encuentres con un error 502 de Mal Gateway mientras el modelo se carga en segundo plano. Puedes consultar el estado del modelo en <code>Machine Learning &gt; Trained Models</code> en Kibana. Una vez desplegado, puedes pasar al siguiente paso.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltee295a3516338302/6a17f65b414c640deb94531d/a7ff94b72d337cde892165d744b9f42fba702a87-1440x649.png" alt="Modelos capacitados comprobando" /><h2>Datos de indexación</h2><p>Puedes descargar el conjunto <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/esre-with-blazor/books.zip">de datos aquí</a> y luego importar los datos usando Kibana. Para ello, ve a la página principal y haz clic en "Subir datos". Luego, sube el archivo y haz clic <code>Import</code>. Por último, ve a la pestaña <code>Advanced</code> y pega los siguientes mapeos:</p>{
   "properties":{
      "authors":{
         "type":"keyword"
      },
      "categories":{
         "type":"keyword"
      },
      "longDescription":{
         "type":"semantic_text",
         "inference_id":"my-elser-model",
         "model_settings":{
            "task_type":"sparse_embedding"
         }
      },
      "pageCount":{
         "type":"integer"
      },
      "publishedDate":{
         "type":"date"
      },
      "shortDescription":{
         "type":"text"
      },
      "status":{
         "type":"keyword"
      },
      "thumbnailUrl":{
         "type":"keyword"
      },
      "title":{
         "type":"text"
      }
   }
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0f07300647edd2ca/6a17f65dfaa913172a93ca0c/1aea0b9c51e275f89339f5d463fcaef799fc3943-1235x1083.png" alt="Datos importados" /><p>Vamos a crear un índice capaz de ejecutar consultas semánticas y de texto completo. El tipo <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html">de campo semantic_text</a> se encargará del fragmento de datos y la incrustación. <em>Ten en cuenta que estamos indexando</em> <em><code>longDescription</code></em> <em>como</em> <em><code>semantic_text</code></em><em>, puedes usar copy_to si quieres indexar un campo tanto como</em> <em><code>semantic_text</code></em> <em>como texto'</em>.</p><h2>Construcción de la app con Blazor &amp; Elasticsearch</h2><h3>Clave API</h3><p>Lo primero que tenemos que hacer es crear una clave API para autenticar nuestras solicitudes a Elasticsearch. La clave API debe ser de solo lectura y solo permitir consultar el índice de <code>books-blazor</code> .</p>POST /_security/api_key
{
  "name": "books-blazor-key",
  "role_descriptors": {
    "books-blazor-reader": {
      "indices": [
        {
          "names": ["books-blazor"],
          "privileges": ["read"]
        }
      ]
    }
  }
}<p>Verás algo así:
</p>{
  "id": "XXXXXXXXXXXXXXXXXXXXXXXX",
  "name": "books-blazor-key",
  "api_key": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "encoded": "XXXXXXXXXXXXXXXXXXXXXXXX=="
}<p>Almacena el valor del campo de respuesta <code>encoded</code> cuando sea necesario más adelante. Si usas <a href="https://www.elastic.co/cloud/">Elastic Cloud</a>, también necesitarás tu ID de la nube. (Puedes <a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud#finding-your-cloud-id">encontrarlo aquí</a>).</p><h4>Creación del proyecto Blazor</h4><p>Empieza instalando Blazor y creando un proyecto de muestra siguiendo las <a href="https://dotnet.microsoft.com/en-us/learn/aspnet/blazor-tutorial/install">instrucciones oficiales</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5c20b896fe1e98e/6a17f65f6317301566585bff/f5720867d960c91fd1b3c4ae9be174e062e6b2fc-975x830.png" alt="Tutorial de Blazor - Creando el proyecto Blazor" /><p>Una vez que tengas el proyecto creado, la estructura de carpetas y los archivos deberían ver así:</p>BlazorApp/
|-- BlazorApp.csproj
|-- BlazorApp.sln
|-- Program.cs
|-- appsettings.Development.json
|-- appsettings.json
|-- Properties/
|   `-- launchSettings.json
|-- Components/
|   |-- App.razor
|   |-- Routes.razor
|   |-- _Imports.razor
|   |-- Layout/
|   |   |-- MainLayout.razor
|   |   |-- MainLayout.razor.css
|   |   |-- NavMenu.razor
|   |   `-- NavMenu.razor.css
|   `-- Pages/
|       |-- Counter.razor
|       |-- Error.razor
|       |-- Home.razor
|       `-- Weather.razor
|-- wwwroot/
|-- bin/
`-- obj/ <p>La aplicación plantilla incluye <a href="https://blog.getbootstrap.com/2021/08/04/bootstrap-5-1-0/">Bootstrap v5.1.0</a> para peinar.</p><p>Termina la configuración del proyecto instalando el cliente <a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/8.0/installation.html">Elasticsearch .NET</a> :</p>dotnet add package Elastic.Clients.Elasticsearch<p>Una vez que completes este paso, tu página debería ver así:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3b90e394ded7ff56/6a17f660faa913d49f93ca10/ef9d2186c2cf49e78a307c4aa69c51632e84578d-940x529.png" alt="Blazor, hola mundo" /><h3>Estructura de carpetas</h3><p>Ahora, vamos a organizar nuestras carpetas de la siguiente manera:</p>BlazorApp/
|-- Components/
|   |-- Pages/
|   |   |-- Search.razor
|   |   `-- Search.razor.css
|   `-- Elasticsearch/
|       |-- SearchBar.razor
|       |-- Results.razor
|       `-- Facet.razor
|-- Models/
|   |-- Book.cs
|   `-- Response.cs
`-- Services/
    `-- ElasticsearchService.cs<p>Archivos explicados:</p><ul><li><p>Components/Pages/Search.razor: página principal que contiene la barra de búsqueda, los resultados y los filtros.</p></li><li><p>Componentes/Páginas/Search.razor.css: estilos de página.</p></li><li><p>Components/Elasticsearch/SearchBar.razor: componente de la barra de búsqueda.</p></li><li><p>Components/Elasticsearch/Results.razor: componente de resultados.</p></li><li><p>Components/Elasticsearch/Facet.razor: componente de filtros.</p></li><li><p>Components/Svg/GlassIcon.razor: icono de búsqueda.</p></li><li><p>Components/_Imports.razor: esto importará todos los componentes.</p></li><li><p>Modelos/Book.cs: esto almacenará el esquema del campo libro.</p></li><li><p>Modelos/Response.cs: esto almacenará el esquema de respuesta, incluyendo los resultados de búsqueda, facetas y resultados totales.</p></li><li><p>Servicios/ElasticsearchService.cs: Servicio Elasticsearch. Se encargará de la conexión y las consultas a Elasticsearch.</p></li></ul><h4>Configuración inicial</h4><p>Empecemos con una limpieza.</p><p>Elimina los archivos:</p><ul><li><p>Componentes/Páginas/Counter.razor</p></li><li><p>Componentes/Páginas/Weather.razor</p></li><li><p>Componentes/Páginas/Home.razor</p></li><li><p>Componentes/Layout/NavMenu.razor</p></li><li><p>Componentes/Diseño/NavMenu.razor.css</p></li></ul><p>Revisa el archivo <code>/Components/_Imports.razor</code> . Deberías tener las siguientes importaciones:</p>@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorApp
@using BlazorApp.Components<h4>Integración de Elastic en el proyecto</h4><p>Ahora, importemos los componentes de Elasticsearch:</p>@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorApp
@using BlazorApp.Components
@using BlazorApp.Components.Elasticsearch @* &lt;--- Add this line *@<p>Vamos a eliminar la barra lateral predeterminada para tener más espacio para nuestra aplicación eliminándola del archivo <code>/Components/Layout/MainLayout.razor</code> :</p>@inherits LayoutComponentBase

&lt;div class="page"&gt;
    &lt;main&gt;
        &lt;article class="content"&gt;
            @Body
        &lt;/article&gt;
    &lt;/main&gt;
&lt;/div&gt;

&lt;div id="blazor-error-ui"&gt;
    An unhandled error has occurred.
    &lt;a href="" class="reload"&gt;Reload&lt;/a&gt;
    &lt;a class="dismiss"&gt;🗙&lt;/a&gt;
&lt;/div&gt;<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt441012c5aeac85f8/6a17f661ec0f8949f15a67ac/55bfff607f9167a532f83ad074b479364a346c6e-816x472.png" alt="Barra de navegación eliminada del diseño" /><p>Ahora introduzcamos las credenciales de Elasticsearch para los <a href="https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-8.0&amp;tabs=linux#secret-manager">secretos de usuario</a>:</p>dotnet user-secrets init
dotnet user-secrets set ElasticsearchCloudId "your Cloud ID"
dotnet user-secrets set ElasticsearchApiKey "your API Key"<p>Con este enfoque, .Net 8 almacena datos sensibles en una ubicación separada, fuera de la carpeta del proyecto, y los hace accesibles mediante la interfaz <code>IConfiguration</code> . Estas variables estarán disponibles para cualquier proyecto .Net que emplee los mismos secretos de usuario.</p><p>Luego, modifiquemos el archivo <code>Program.cs</code> para leer los secretos y montar el cliente Elasticsearch:</p><p>Primero, importa las bibliotecas necesarias:</p>using BlazorApp.Services;
using Elastic.Clients.Elasticsearch;
using Elastic.Transport;<ul><li><p>BlazorApp.Services: contiene el servicio Elasticsearch.</p></li><li><p>Elastic.Clients.Elasticsearch: importa la biblioteca cliente .Net 8 de Elasticsearch.</p></li><li><p>Elastic.Transport: importa la biblioteca de transporte Elasticsearch, que nos permite usar la clase ApiKey para autenticar nuestras solicitudes.</p></li></ul><p>Segundo, inserta el siguiente código antes de la línea <code>var app = builder.Build()</code> :</p>// Initialize the Elasticsearch client.
builder.Services.AddScoped(sp =&gt;
{
    // Getting access to the configuration service to read the Elasticsearch credentials.
    var configuration = sp.GetRequiredService&lt;IConfiguration&gt;();
    var cloudId = configuration["ElasticsearchCloudId"];
    var apiKey = configuration["ElasticsearchApiKey"];

    if (string.IsNullOrEmpty(cloudId) || string.IsNullOrEmpty(apiKey))
    {
        throw new InvalidOperationException(
            "Elasticsearch credentials are missing in configuration."
        );
    }

    var settings = new ElasticsearchClientSettings(cloudId, new ApiKey(apiKey)).EnableDebugMode();
    return new ElasticsearchClient(settings);
});<p>Este código leerá las credenciales de Elasticsearch de los secretos de usuario y creará una instancia cliente de Elasticsearch.</p><p>Tras la inicialización del cliente ElasticSearch, agrega la siguiente línea para registrar el servicio Elasticsearch:</p>builder.Services.AddScoped&lt;ElasticsearchService&gt;();<p>El siguiente paso será construir la lógica de búsqueda en el archivo <code>/Services/ElasticsearchService.cs</code> :</p><p>Primero, importa las bibliotecas y modelos necesarios:</p>using BlazorApp.Models;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.QueryDsl;<p>Segundo, agregar la clase <code>ElasticsearchService</code>, constructor y variables:</p>namespace BlazorApp.Services
{
    public class ElasticsearchService
    {
        private readonly ElasticsearchClient _client;

        // The logger is used to log information, warnings and errors about the Elasticsearch service and requests.
        private readonly ILogger&lt;ElasticsearchService&gt; _logger;

        public ElasticsearchService(
            ElasticsearchClient client,
            ILogger&lt;ElasticsearchService&gt; logger
        )
        {
            _client = client ?? throw new ArgumentNullException(nameof(client));
            _logger = logger;
        }
    }
}<h4>Configuración de búsqueda</h4><p>Ahora, vamos a construir nuestra lógica de búsqueda:</p>private static Action&lt;RetrieverDescriptor&lt;BookDoc&gt;&gt; BuildHybridQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    return retrievers =&gt;
        retrievers.Rrf(rrf =&gt;
            rrf.RankWindowSize(50)
                .RankConstant(20)
                .Retrievers(
                    retrievers =&gt;
                        retrievers.Standard(std =&gt;
                            std.Query(q =&gt;
                                q.Bool(b =&gt;
                                    b.Must(m =&gt;
                                            m.MultiMatch(mm =&gt;
                                                mm.Query(searchTerm)
                                                    .Fields(
                                                        new[]
                                                        {
                                                            "title",
                                                            "shortDescription",
                                                        }
                                                    )
                                            )
                                        )
                                        .Filter(filters.ToArray())
                                )
                            )
                        ),
                    retrievers =&gt;
                        retrievers.Standard(std =&gt;
                            std.Query(q =&gt;
                                q.Bool(b =&gt;
                                    b.Must(m =&gt;
                                            m.Semantic(sem =&gt;
                                                sem.Field("longDescription")
                                                    .Query(searchTerm)
                                            )
                                        )
                                        .Filter(filters.ToArray())
                                )
                            )
                        )
                )
        );
}

public static List&lt;Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt;&gt; BuildFilters(
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = new List&lt;Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt;&gt;();

    if (selectedFacets != null)
    {
        foreach (var facet in selectedFacets)
        {
            foreach (var value in facet.Value)
            {
                var field = facet.Key.ToLower();
                if (!string.IsNullOrEmpty(field))
                {
                    filters.Add(m =&gt; m.Term(t =&gt; t.Field(new Field(field)).Value(value)));
                }
            }
        }
    }

    return filters;
}<ul><li><p><code>BuildFilters</code> construirá los filtros para la consulta de búsqueda usando las facetas seleccionadas por el usuario.</p></li><li><p><code>BuildHybridQuery</code> construirá una consulta <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">de búsqueda híbrida</a> que combine texto completo y búsqueda semántica.</p></li></ul><p>A continuación, agrega el método de búsqueda:</p>public async Task&lt;ElasticResponse&gt; SearchBooksAsync(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    try
    {
        _logger.LogInformation($"Performing search for: {searchTerm}");

        // Retrieve the hybrid query with filters applied.
        var retrieverQuery = BuildHybridQuery(searchTerm, selectedFacets);

        var response = await _client.SearchAsync&lt;BookDoc&gt;(s =&gt;
            s.Index("elastic-blazor-books")
                .Retriever(retrieverQuery)
                .Aggregations(aggs =&gt;
                    aggs.Add("Authors", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Authors)))
                        .Add(
                            "Categories",
                            agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Categories))
                        )
                        .Add("Status", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Status)))
                )
        );

        if (response.IsValidResponse)
        {
            _logger.LogInformation($"Found {response.Documents.Count} documents");

            var hits = response.Total;
            var facets =
                response.Aggregations != null
                    ? FormatFacets(response.Aggregations)
                    : new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

            var elasticResponse = new ElasticResponse
            {
                TotalHits = hits,
                Documents = response.Documents.ToList(),
                Facets = facets,
            };

            return elasticResponse;
        }
        else
        {
            _logger.LogWarning($"Invalid response: {response.DebugInformation}");
            return new ElasticResponse();
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error performing search");
        return new ElasticResponse();
    }
}

public static Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; FormatFacets(
    Elastic.Clients.Elasticsearch.Aggregations.AggregateDictionary aggregations
)
{
    var facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

    foreach (var aggregation in aggregations)
    {
        if (
            aggregation.Value
            is Elastic.Clients.Elasticsearch.Aggregations.StringTermsAggregate termsAggregate
        )
        {
            var facetName = aggregation.Key;
            var facetDictionary = ConvertFacetDictionary(
                termsAggregate.Buckets.ToDictionary(b =&gt; b.Key, b =&gt; b.DocCount)
            );
            facets[facetName] = facetDictionary;
        }
    }

    return facets;
}

private static Dictionary&lt;string, long&gt; ConvertFacetDictionary(
    Dictionary&lt;Elastic.Clients.Elasticsearch.FieldValue, long&gt; original
)
{
    var result = new Dictionary&lt;string, long&gt;();
    foreach (var kvp in original)
    {
        result[kvp.Key.ToString()] = kvp.Value;
    }
    return result;
}<ul><li><p><code>SearchBooksAsync</code>: realizará la búsqueda usando la consulta híbrida y devolverá los resultados incluidas agregaciones para construir las facetas.</p></li><li><p><code>FormatFacets</code>: formateará la respuesta de agregación en un diccionario.</p></li><li><p><code>ConvertFacetDictionary</code>: convertirá el diccionario de facetas en un formato más legible.</p></li></ul><p>El siguiente paso es crear los modelos que representarán los datos devueltos en el <code>hits</code> de la consulta de Elasticsearch que se imprimirán como resultados en nuestra página de búsqueda.</p><p>Comenzamos creando el <code>/Models/Book.cs</code> de archivo y agregando lo siguiente:</p>namespace BlazorApp.Models
{
    public class BookDoc
    {
        public string? Title { get; set; }
        public int? PageCount { get; set; }
        public string? PublishedDate { get; set; }
        public string? ThumbnailUrl { get; set; }
        public string? ShortDescription { get; set; }
        public LongDescription? LongDescription { get; set; }
        public string? Status { get; set; }
        public List&lt;string&gt;? Authors { get; set; }
        public List&lt;string&gt;? Categories { get; set; }
    }

    public class LongDescription
    {
        public string? Text { get; set; }
    }
}<p>Luego, configurar la respuesta Elastic en el archivo <code>/Models/Response.cs</code> y agregar lo siguiente:</p>namespace BlazorApp.Models
{
    public class ElasticResponse
    {
        public ElasticResponse()
        {
            Documents = new List&lt;BookDoc&gt;();
            Facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();
        }

        public long TotalHits { get; set; }
        public List&lt;BookDoc&gt; Documents { get; set; }
        public Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; Facets { get; set; }
    }
}<h4>Configuración de una interfaz básica</h4><p>A continuación, agrega el componente Barra de búsqueda. En el archivo <code>/Components/Elasticsearch/SearchBar.razor</code> y agrega lo siguiente:</p>@using System.Threading.Tasks

&lt;form @onsubmit="SubmitSearch"&gt;
  &lt;div class="input-group mb-3"&gt;
    &lt;input type="text" @bind-value="searchTerm" class="form-control" placeholder="Enter search term..." /&gt;
    &lt;button type="submit" class="btn btn-primary input-btn"&gt;
      &lt;span class="input-group-svg"&gt;
        Search
      &lt;/span&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/form&gt;

@code {
  [Parameter]
  public EventCallback&lt;string&gt; OnSearch { get; set; }

  private string searchTerm = "";

  private async Task SubmitSearch()
  {
    await OnSearch.InvokeAsync(searchTerm);
  }
}<p>Este componente contiene una barra de búsqueda y un botón para realizar la búsqueda.</p><p>Blazor ofrece una gran flexibilidad al permitir generar HTML dinámicamente usando código C# dentro del mismo archivo.</p><p>Después, en el archivo <code>/Components/Elasticsearch/Results.razor</code> construiremos el componente de resultados que mostrará los resultados de búsqueda:</p>@using BlazorApp.Models

@if (SearchResults != null &amp;&amp; SearchResults.Any())
{
  &lt;div class="row"&gt;
  @foreach (var result in SearchResults)
    {
      &lt;div class="col-12 mb-3"&gt;
        &lt;div class="card"&gt;
          &lt;div class="row g-0"&gt;
            &lt;div class="col-md-3 image-container"&gt;
              @if (!string.IsNullOrEmpty(result?.ThumbnailUrl))
              {
                &lt;img src="@result?.ThumbnailUrl" class="img-fluid rounded-start" alt="Thumbnail"&gt;
              }
              else
              {
                &lt;div class="placeholder"&gt;
                  @result?.Title
                &lt;/div&gt;
              }
            &lt;/div&gt;

            &lt;div class="col-md-9"&gt; &lt;!-- Adjusted to use the remaining 75% --&gt;
              &lt;div class="card-body"&gt;
                &lt;h4 class="card-title"&gt;
                  @result?.Title
                &lt;/h4&gt;

                &lt;div class="details-container"&gt;
                  &lt;div class=""&gt;

                    @if (result?.Authors?.Any() == true)
                    {
                      &lt;p class="card-text p-first"&gt;
                        Authors: &lt;small class="text-muted"&gt;@string.Join(", ", result.Authors)&lt;/small&gt;
                      &lt;/p&gt;
                    }

                    @if (result?.Categories?.Any() == true)
                    {
                      &lt;p class="card-text p-second"&gt;
                        Categories: &lt;small class="text-muted"&gt;@string.Join(", ", result.Categories)&lt;/small&gt;
                      &lt;/p&gt;
                    }
                  &lt;/div&gt;
                  &lt;div class="numPages-status"&gt;
                    @if (result?.PageCount != null)
                    {
                      &lt;p class="card-text p-first"&gt;
                        Pages: &lt;small class="text-muted"&gt;@result.PageCount&lt;/small&gt;
                      &lt;/p&gt;
                    }

                    @if (result?.Status != null)
                    {
                      &lt;p class="card-text p-second"&gt;
                        Status: &lt;small class="text-muted"&gt;@result.Status&lt;/small&gt;
                      &lt;/p&gt;
                    }
                  &lt;/div&gt;
                &lt;/div&gt;

                &lt;div class="long-text-container"&gt;
                  &lt;p class="card-text"&gt;&lt;small class="text-muted"&gt;@result?.LongDescription?.Text&lt;/small&gt;&lt;/p&gt;
                &lt;/div&gt;
                @if (!string.IsNullOrEmpty(result?.PublishedDate))
                {
                  &lt;div class="date-container"&gt;
                    &lt;p class="card-text"&gt;
                      Published Date: &lt;small class="text-muted small-date"&gt;@FormatDate(result.PublishedDate)&lt;/small&gt;
                    &lt;/p&gt;
                  &lt;/div&gt;
                }
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    }
  &lt;/div&gt;
}
else if (SearchResults != null)
{
  &lt;p&gt;No results found.&lt;/p&gt;
}

@code {
  [Parameter]
  public List&lt;BookDoc&gt; SearchResults { get; set; } = new List&lt;BookDoc&gt;();

  private string FormatDate(string? date)
  {
    if (DateTime.TryParse(date, out DateTime parsedDate))
    {
      return parsedDate.ToString("MMMM dd, yyyy");
    }
    return "";
  }
}<p>Finalmente, necesitaremos crear facetas para filtrar los resultados de búsqueda.</p><p><em>Nota: Los facetos son filtros que permiten a los usuarios reducir los resultados de búsqueda en función de atributos o categorías específicas, como el tipo de producto, el rango de precio o la marca. Estos filtros suelen presentar como opciones clicables, a menudo en forma de casillas para marcar, para ayudar a los usuarios a refinar su búsqueda y encontrar resultados relevantes con mayor facilidad. En el contexto de Elasticsearch, las facetas se crean mediante</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html"><em>agregaciones</em></a><em>.</em></p><p>Configuramos facetas poniendo el siguiente código en el archivo <code>/Components/Elasticsearch/Facet.razor</code>:</p>@if (Facets != null)
{
  &lt;div class="facets-container"&gt;
  @foreach (var facet in Facets)
    {
      &lt;h3&gt;@facet.Key&lt;/h3&gt;
      @foreach (var option in facet.Value)
      {
        &lt;div&gt;
          &lt;input type="checkbox" checked="@IsFacetSelected(facet.Key, option.Key)"
            @onclick="() =&gt; ToggleFacet(facet.Key, option.Key)" /&gt;
          @option.Key (@option.Value)
        &lt;/div&gt;
      }
    }
  &lt;/div&gt;
}


@code {
  [Parameter]
  public Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;? Facets { get; set; }

  [Parameter]
  public EventCallback&lt;Dictionary&lt;string, List&lt;string&gt;&gt;&gt; OnFacetChanged { get; set; }

  private Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets = new();

  private void ToggleFacet(string facetName, string facetValue)
  {
    if (!selectedFacets.TryGetValue(facetName, out var facetValues))
    {
      facetValues = selectedFacets[facetName] = new List&lt;string&gt;();
    }

    if (!facetValues.Remove(facetValue))
    {
      facetValues.Add(facetValue);
    }

    OnFacetChanged.InvokeAsync(selectedFacets);
  }

  private bool IsFacetSelected(string facetName, string facetValue)
  {
    return selectedFacets.ContainsKey(facetName) &amp;&amp; selectedFacets[facetName].Contains(facetValue);
  }
}<p>Este componente lee de una agregación <code>terms</code> en los campos <code>author</code>, <code>categories</code>y <code>status</code> , y luego produce una lista de filtros para enviar de vuelta a Elasticsearch.</p><p>Ahora, vamos a juntar todo.</p><p>En <code>/Components/Pages/Search.razor</code> expediente:</p>@page "/"
@rendermode InteractiveServer
@using BlazorApp.Models
@using BlazorApp.Services
@inject ElasticsearchService ElasticsearchService
@inject ILogger&lt;Search&gt; Logger

&lt;PageTitle&gt;Search&lt;/PageTitle&gt;

&lt;div class="top-row px-4 "&gt;

    &lt;div class="searchbar-container"&gt;
        &lt;h4&gt;Semantic Search with Elasticsearch and Blazor&lt;/h4&gt;

        &lt;SearchBar OnSearch="PerformSearch" /&gt;
    &lt;/div&gt;

    &lt;a href="https://www.elastic.co/search-labs/esre-with-blazor" target="_blank"&gt;About&lt;/a&gt;
&lt;/div&gt;

&lt;div class="px-4"&gt;

    &lt;div class="search-details-container"&gt;
        &lt;p role="status"&gt;Current search term: @currentSearchTerm&lt;/p&gt;
        &lt;p role="status"&gt;Total results: @totalResults&lt;/p&gt;
    &lt;/div&gt;

    &lt;div class="results-facet-container"&gt;
        &lt;div class="facets-container"&gt;
            &lt;Facet Facets="facets" OnFacetChanged="OnFacetChanged" /&gt;
        &lt;/div&gt;
        &lt;div class="results-container"&gt;
            &lt;Results SearchResults="searchResults" /&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

@code {
    private string currentSearchTerm = "";
    private long totalResults = 0;
    private List&lt;BookDoc&gt; searchResults = new List&lt;BookDoc&gt;();
    private Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();
    private Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets = new Dictionary&lt;string, List&lt;string&gt;&gt;();

    protected override async Task OnInitializedAsync()
    {
        await PerformSearch();
    }

    private async Task PerformSearch(string searchTerm = "")
    {
        try
        {
            currentSearchTerm = searchTerm;

            var response = await ElasticsearchService.SearchBooksAsync(currentSearchTerm, selectedFacets);
            if (response != null)
            {
                searchResults = response.Documents;
                facets = response.Facets;
                totalResults = response.TotalHits;
            }
            else
            {
                Logger.LogWarning("Search response is null.");
            }

            StateHasChanged();
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Error performing search.");
        }
    }

    private async Task OnFacetChanged(Dictionary&lt;string, List&lt;string&gt;&gt; newSelectedFacets)
    {
        selectedFacets = newSelectedFacets;
        await PerformSearch(currentSearchTerm);
    }
}<p>¡Nuestra página funciona!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc750e55fff43de4b/6a17f6636864a4ab08b68931/5f0f25cb029a34df7f577a9f307278226ed07c3f-816x473.png" alt="Ejemplo de página en Blazor" /><p>Como puedes ver, la página es funcional pero carece de estilos. Vamos a agregar algo de CSS para que parezca más organizado y receptivo.</p><p>Empecemos a reemplazar los estilos de distribución. En el archivo <code>Components/Layout/MainLayout.razor.css</code> :</p>.page {
  position: relative;
  display: flex;
  flex-direction: column;
}

main {
  flex: 1;
}

#blazor-error-ui {
  background: lightyellow;
  bottom: 0;
  box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
  display: none;
  left: 0;
  padding: 0.6rem 1.25rem 0.7rem 1.25rem;
  position: fixed;
  width: 100%;
  z-index: 1000;
}

#blazor-error-ui .dismiss {
  cursor: pointer;
  position: absolute;
  right: 0.75rem;
  top: 0.5rem;
}<p>Agrega los estilos de la página de búsqueda en el archivo <code>Components/Pages/Search.razor.css</code> :</p>.input-group .input-group-svg {
  background: transparent;
  border: transparent;
  pointer-events: none;
}

.results-facet-container {
  display: flex;
  margin-top: 1rem;
  overflow-x: auto;
}

.search-details-container {
  display: flex;
  justify-content: space-between;
  margin-top: 1rem;
}

.searchbar-container {
  padding-top: 2rem;
  display: flex;
  flex-direction: column; 
  flex-grow: 1;
  height: 100%;
  max-width: 100%; 
}

.searchbar-container h4 {
  margin: 0;
}

.top-row {
  margin-top: -1.1rem;
  position: relative; 
  background-color: hsl(216, 29%, 67%);
  border-bottom: 1px solid #d6d5d5;
  display: flex;
  align-items: center;
  height: 100%;
  padding: 0 1rem;
}

.top-row a {
  margin-left: auto;
  margin-top: -4rem; 
  color: #000000;
  text-decoration: none;
}

.top-row a:hover {
  text-decoration: underline;
}

@media (max-width: 640.98px) {
  .top-row {
    justify-content: space-between;
  }

  .top-row ::deep a,
  .top-row ::deep .btn-link {
    margin-left: 0;
  }
}

@media (min-width: 641px) {
  .top-row.auth ::deep a:first-child {
    flex: 1;
    text-align: right;
    width: 0;
  }

  .top-row,
  article {
    padding-left: 2rem !important;
    padding-right: 1.5rem !important;
  }
}<p>Nuestra página empieza a ver mejor:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ecc49f404f18591/6a17f6654b055d959b432384/3dadd7ade2dd9070300a4e0514a4da2ae9cc9fb9-817x473.png" alt="Página Blazor luego de agregar estilos para la página de búsqueda" /><p>Vamos a darle los toques finales:</p><p>Crea los siguientes archivos:</p><ul><li><p>Componentes/Elasticsearch/Facet.razor.css</p></li><li><p>Components/Elasticsearch/Results.razor.css</p></li></ul><p>Y agrega los estilos para <code>Facet.razor.css</code>:</p>.facets-container {
  font-size: 15px;
  margin-right: 4rem;
  overflow-x: auto;
  white-space: nowrap;
  max-width: 300px;
}

.results-facet-container {
  display: flex;
  margin-top: 1rem;
  overflow-x: auto;
}

.results-facet-container &gt; * {
  flex-shrink: 0;
}<p>Para <code>Results.razor.css</code>:</p>.image-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  padding: 1rem;
  box-sizing: border-box;
}

.image-container img {
  max-width: 100%;
  height: auto;
  border-radius: 0.5rem;
}

.placeholder {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  width: 100%;
  background-color: #f0f0f0;
  border: 1px solid #ccc;
  font-size: 0.9rem;
  color: #888;
  text-align: center;
  padding: 1rem;
  border-radius: 0.5rem;
}

.card-body {
  padding: 1rem;
}

.details-container {
  display: flex;
  justify-content: space-between;
  padding: 1.5rem 0;
}

.date-container {
  margin-top: 1rem;
  display: flex;
  justify-content: flex-end;
}

.date-container .small-date {
  font-weight: bold;
}<p>Resultado final:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt78bbd8e6f9e23988/6a17f6664b055d2ed8432388/3e68ec4a38775fdfbeaa1a990e6a1e11dfb081d8-816x472.png" alt="Resultado final de la creación de la página de la app Blazor" /><p>Para ejecutar la aplicación puedes usar el siguiente comando:</p><p><code>dotnet watch</code></p><p>¡Lo conseguiste! Ahora puedes buscar libros en tu índice de Elasticsearch usando la barra de búsqueda y filtrar los resultados por autor, categoría y estado.</p><h3>Realización de búsqueda en texto completo y semántica</h3><p>Por defecto, nuestra app realiza una <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">búsqueda híbrida</a> usando tanto <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html">texto completo</a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html">como búsqueda semántica</a>. Puedes cambiar la lógica de búsqueda creando dos métodos separados, uno para texto completo y otro para búsqueda semántica, y luego seleccionar uno para construir la consulta basar en la entrada del usuario.</p><p>Agrega los siguientes métodos a la clase <code>ElasticsearchService</code> en el archivo <code>/Services/ElasticsearchService.cs</code> :</p>private static Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt; BuildSemanticQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    return query =&gt;
        query.Bool(b =&gt;
            b.Must(m =&gt; m.Semantic(sem =&gt; sem.Field("longDescription").Query(searchTerm)))
                .Filter(filters.ToArray())
        );
}

private static Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt; BuildMultiMatchQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    if (string.IsNullOrEmpty(searchTerm))
    {
        return query =&gt; query.Bool(b =&gt; b.Filter(filters.ToArray()));
    }

    return query =&gt;
        query.Bool(b =&gt;
            b.Should(m =&gt;
                    m.MultiMatch(mm =&gt;
                        mm.Query(searchTerm).Fields(new[] { "title", "shortDescription" })
                    )
                )
                .Filter(filters.ToArray())
        );
}<p>Ambos métodos funcionan de forma similar al método <code>BuildHybridQuery</code> , pero solo realizan búsqueda en texto completo o semántica.</p><p>Puedes modificar el método <code>SearchBooksAsync</code> para usar el método de búsqueda seleccionado:</p>public async Task&lt;ElasticResponse&gt; SearchBooksAsync(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    try
    {
        _logger.LogInformation($"Performing search for: {searchTerm}");
        
        // Modify the query builder to use the selected search method.
        var multiMatchQuery = BuildMultiMatchQuery(searchTerm, selectedFacets); // For full text search
        var semanticQuery = BuildSemanticQuery(searchTerm, selectedFacets); // For semantic search

        // In this case we will not use retrievers, but you can add them if you want to use them.
        var response = await _client.SearchAsync&lt;BookDoc&gt;(s =&gt;
            s.Index("elastic-blazor-books")
                .Query(multiMatchQuery) // Change this line to use different search methods, for example: .Query(semanticQuery) for semantic search
                .Aggregations(aggs =&gt;
                    aggs.Add("Authors", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Authors)))
                        .Add(
                            "Categories",
                            agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Categories))
                        )
                        .Add("Status", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Status)))
                )
        );

        if (response.IsValidResponse)
        {
            _logger.LogInformation($"Found {response.Documents.Count} documents");

            var hits = response.Total;
            var facets =
                response.Aggregations != null
                    ? FormatFacets(response.Aggregations)
                    : new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

            var elasticResponse = new ElasticResponse
            {
                TotalHits = hits,
                Documents = response.Documents.ToList(),
                Facets = facets,
            };

            return elasticResponse;
        }
        else
        {
            _logger.LogWarning($"Invalid response: {response.DebugInformation}");
            return new ElasticResponse();
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error performing search");
        return new ElasticResponse();
    }
}<p>Puedes encontrar la solicitud completa <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/esre-with-blazor">aquí</a></p><h2>Conclusión</h2><p>Blazor es un framework eficaz que te permite crear aplicaciones sitio web usando C#. Elasticsearch es un poderoso motor de búsqueda que te permite crear aplicaciones de búsqueda. Combinando ambos, puedes construir fácilmente aplicaciones de búsqueda robustas, aprovechando el poder de ESRE para crear una <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search.html">experiencia de búsqueda semántica</a> en poco tiempo.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[.NET]]></category>
    <dc:creator><![CDATA[Gustavo Llermaly]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte7424ac5f0b223b4/6a17f668414c641971945323/7ba0d6bec908bfcae966b7f38626fabd682c6f3d-1200x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[LangChain4j con Elasticsearch como almacén de incrustaciones]]></title>
    <description><![CDATA[LangChain4j (LangChain para Java) tiene Elasticsearch como almacén de incrustación. Descubre cómo usarlo para construir tu aplicación RAG en Java puro.]]></description>
    <content:encoded><![CDATA[<p>
En la <a href="https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction">publicación anterior</a>, descubrimos qué es LangChain4j y cómo:</p><ul><li><p>Habla con los LLMs implementando un <code>ChatLanguageModel</code> y un <code>ChatMemory</code></p></li><li><p>Conserva el historial de chats en memoria para recordar el contexto de una conversación previa con un LLM</p></li></ul><p>Esta entrada del blog trata sobre cómo:</p><ul><li><p>Crear incrustaciones vectoriales a partir de ejemplos de texto</p></li><li><p>Almacenar incrustaciones vectoriales en el almacenamiento de incrustaciones de Elasticsearch </p></li><li><p>Búsqueda de vectores similares</p></li></ul><h2>Crear incrustaciones</h2><p>Para crear embeddings, necesitamos definir un <code>EmbeddingModel</code> a emplear. Por ejemplo, podemos usar el mismo modelo de mistral que usamos en la <a href="https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction">publicación anterior</a>. Estaba corriendo con ollama:</p>EmbeddingModel model = OllamaEmbeddingModel.builder()
  .baseUrl(ollama.getEndpoint())
  .modelName(MODEL_NAME)
  .build();<p>Un modelo es capaz de generar vectores a partir de texto. Aquí podemos comprobar el número de dimensiones generadas por el modelo:</p>Logger.info("Embedding model has {} dimensions.", model.dimension());
// This gives: Embedding model has 4096 dimensions.<p>Para generar vectores a partir de un texto, podemos usar:</p>Response&lt;Embedding&gt; response = model.embed("A text here");<p>O si también queremos proporcionar metadatos para filtrar cosas como texto, precio, fecha de lanzamiento o lo que sea, podemos usar <code>Metadata.from()</code>. Por ejemplo, aquí agregamos el nombre del juego como campo de metadatos:</p>TextSegment game1 = TextSegment.from("""
    The game starts off with the main character Guybrush Threepwood stating "I want to be a pirate!"
    To do so, he must prove himself to three old pirate captains. During the perilous pirate trials, 
    he meets the beautiful governor Elaine Marley, with whom he falls in love, unaware that the ghost pirate 
    LeChuck also has his eyes on her. When Elaine is kidnapped, Guybrush procures crew and ship to track 
    LeChuck down, defeat him and rescue his love.
""", Metadata.from("gameName", "The Secret of Monkey Island"));
Response&lt;Embedding&gt; response1 = model.embed(game1);
TextSegment game2 = TextSegment.from("""
    Out Run is a pseudo-3D driving video game in which the player controls a Ferrari Testarossa 
    convertible from a third-person rear perspective. The camera is placed near the ground, simulating 
    a Ferrari driver's position and limiting the player's view into the distance. The road curves, 
    crests, and dips, which increases the challenge by obscuring upcoming obstacles such as traffic 
    that the player must avoid. The object of the game is to reach the finish line against a timer.
    The game world is divided into multiple stages that each end in a checkpoint, and reaching the end 
    of a stage provides more time. Near the end of each stage, the track forks to give the player a 
    choice of routes leading to five final destinations. The destinations represent different 
    difficulty levels and each conclude with their own ending scene, among them the Ferrari breaking 
    down or being presented a trophy.
""", Metadata.from("gameName", "Out Run"));
Response&lt;Embedding&gt; response2 = model.embed(game2);<p>Si quieres ejecutar este código, por favor echa un vistazo a la <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step5EmbedddingsTest.java">clase Step5EmbedddingsTest.java</a> .</p><h2>Agregar Elasticsearch para almacenar nuestros vectores</h2><p>LangChain4j proporciona un almacén de incrustación en memoria. Esto es útil para realizar pruebas sencillas:</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore = new InMemoryEmbeddingStore&lt;&gt;();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>Pero obviamente, esto no podría funcionar con un conjunto de datos mucho mayor porque este almacén almacena todo en memoria y no tenemos memoria infinita en nuestros servidores. Así que, en su lugar, podríamos almacenar nuestras incrustaciones en Elasticsearch, que por definición es "elástico" y puede escalar hacia arriba y hacia fuera con tus datos. Para ello, agregamos Elasticsearch a nuestro proyecto:</p>&lt;dependency&gt;
  &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
  &lt;artifactId&gt;langchain4j-elasticsearch&lt;/artifactId&gt;
  &lt;version&gt;${langchain4j.version}&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
  &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;
  &lt;artifactId&gt;elasticsearch&lt;/artifactId&gt;
  &lt;version&gt;1.20.1&lt;/version&gt;
  &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;<p>Como notasteis, también agregamos el módulo Elasticsearch TestContainers al proyecto, para poder iniciar una instancia de Elasticsearch a partir de nuestras pruebas:</p>// Create the elasticsearch container
ElasticsearchContainer container =
  new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:8.15.0")
    .withPassword("changeme");

// Start the container. This step might take some time...
container.start();

// As we don't want to make our TestContainers code more complex than
// needed, we will use login / password for authentication.
// But note that you can also use API keys which is preferred.
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "changeme"));

// Create a low level Rest client which connects to the elasticsearch container.
client = RestClient.builder(HttpHost.create("https://" + container.getHttpHostAddress()))
  .setHttpClientConfigCallback(httpClientBuilder -&gt; {
    httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
    httpClientBuilder.setSSLContext(container.createSslContextFromCa());
    return httpClientBuilder;
  })
  .build();

// Check the cluster is running
client.performRequest(new Request("GET", "/"));<p>Para usar Elasticsearch como almacén de embebed, "solo" tienes que cambiar del almacén de datos en memoria de LangChain4j al de Elasticsearch:</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .restClient(client)
    .build();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>Esto almacenará tus vectores en Elasticsearch en un índice <code>default</code> . También puedes cambiar el nombre del índice por algo más significativo:</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .indexName("games")
    .restClient(client)
    .build();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>Si quieres ejecutar este código, por favor echa un vistazo a la <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step6ElasticsearchEmbedddingsTest.java">clase Step6ElasticsearchEmbedddingsTest.java</a> .</p><h2>Búsqueda de vectores similares</h2><p>Para buscar vectores similares, primero necesitamos transformar nuestra pregunta en una representación vectorial usando el mismo modelo que usamos anteriormente. Ya lo hicimos, así que no es difícil hacerlo de nuevo. Ten en cuenta que en este caso no necesitamos los metadatos:</p>String question = "I want to pilot a car";
Embedding questionAsVector = model.embed(question).content();<p>Podemos construir una solicitud de búsqueda con esta representación de nuestra pregunta y pedir al almacén de incrustaciones que encuentre los primeros vectores principales:</p>EmbeddingSearchResult&lt;TextSegment&gt; result = embeddingStore.search(
  EmbeddingSearchRequest.builder()
    .queryEmbedding(questionAsVector)
    .build());<p>Podemos iterar sobre los resultados ahora e imprimir algo de información, como el nombre del juego que proviene de los metadatos y el puntaje:</p>result.matches().forEach(m -&gt; Logger.info("{} - score [{}]",
  m.embedded().metadata().getString("gameName"), m.score()));<p>Como era de esperar, esto nos da "Out Run" como primer éxito:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ca0dcfdb1a9c94f/6a170291cf4f256938b2d017/140b6a962e5edbb4870419250e30bfb815b0d73e-640x480.gif" alt="Carrera" />Out Run - score [0.86672974]
The Secret of Monkey Island - score [0.85569763]<p>Si quieres ejecutar este código, por favor echa un vistazo a la <a href="https://github.com/dadoonet/langchain4j-demo/blob/9ec4b1d4c7c69821f143ddf272bbfed273c67b14/src/test/java/fr/pilato/demo/Step7SearchForVectorsTest.java#L110-L129">clase Step7SearchForVectorsTest.java</a> . </p><h2>Tras bambalinas</h2><p>La configuración predeterminada para la tienda de incrustación de Elasticsearch emplea la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-knn-query.html">consulta aproximada kNN</a> detrás de escena.</p>POST games/_search
{
  "query" : {
    "knn": {
      "field": "vector",
      "query_vector": [-0.019137882, /* ... */, -0.0148779955]
    }
  }
}<p>Pero esto podría cambiar proporcionando otra configuración (<code>ElasticsearchConfigurationScript</code>) distinta a la predeterminada (<code>ElasticsearchConfigurationKnn</code>) en la tienda de incrustación:</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .configuration(ElasticsearchConfigurationScript.builder().build())
    .indexName("games")
    .restClient(client)
    .build();<p>La implementación <code>ElasticsearchConfigurationScript</code> ejecuta entre bastidores una <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html">consulta</a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html"><code>script_score</code></a> usando una <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html#vector-functions-cosine">función</a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html#vector-functions-cosine"><code>cosineSimilarity</code></a>.</p><p>Básicamente, al llamar:</p>EmbeddingSearchResult&lt;TextSegment&gt; result = embeddingStore.search(
  EmbeddingSearchRequest.builder()
    .queryEmbedding(questionAsVector)
    .build());<p>Esto ahora llama:</p>POST games/_search
{
  "query": {
    "script_score": {
      "script": {
        "source": "(cosineSimilarity(params.query_vector, 'vector') + 1.0) / 2",
        "params": {
          "queryVector": [-0.019137882, /* ... */, -0.0148779955]
        }
      }
    }
  }
}<p>En ese caso, el resultado no cambia en términos de "orden", sino que solo se ajusta el puntaje porque la llamada <code>cosineSimilarity</code> no emplea ninguna aproximación, sino que calcula el coseno para cada uno de los vectores emparejados:</p>Out Run - score [0.871952]
The Secret of Monkey Island - score [0.86380446]<p>Si quieres ejecutar este código, por favor echa un vistazo a la <a href="https://github.com/dadoonet/langchain4j-demo/blob/9ec4b1d4c7c69821f143ddf272bbfed273c67b14/src/test/java/fr/pilato/demo/Step7SearchForVectorsTest.java#L132-L155">clase Step7SearchForVectorsTest.java</a> .</p><h2>Conclusión</h2><p>Explicamos lo fácil que es generar incrustaciones a partir de tu texto y cómo puedes almacenar y buscar los vecinos más cercanos en Elasticsearch usando dos enfoques diferentes:</p><ul><li><p>Usando la consulta de <code>knn</code> aproximada y rápida con la opción de <code>ElasticsearchConfigurationKnn</code> por defecto</p></li><li><p>Usando la consulta de <code>script_score</code> exacta pero más lenta con la opción <code>ElasticsearchConfigurationScript</code></p></li></ul><p>El siguiente paso será construir una aplicación completa de RAG, basada en lo que aprendimos aquí.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store</guid>
    <category><![CDATA[Java]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Base de datos vectorial]]></category>
    <dc:creator><![CDATA[David Pilato]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc873b86c76d1798/6a170293acf088f666be99b3/abd8a4a809064101c037af66b87f28e5ecde03b0-1474x645.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 08 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Técnicas avanzadas de RAG parte 2: Consultas y pruebas]]></title>
    <description><![CDATA[Discutir e implementar técnicas que puedan aumentar el rendimiento de RAG. Parte 2 de 2, centrada en consultar y probar una pipeline avanzada de RAG.]]></description>
    <content:encoded><![CDATA[<p><em>Todo el código puede </em><a href="https://github.com/elastic/elasticsearch-labs/tree/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques"><em>encontrar en el repositorio Searchlabs, en la rama advanced-rag-techniques</em></a><em>.</em></p><p>¡Bienvenidos a la Parte 2 de nuestro artículo sobre Técnicas Avanzadas de RAG! En <a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1">la parte 1 de este serial</a>, establecimos, discutimos e implementamos los componentes de procesamiento de datos de la avanzada tubería RAG:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" alt="Pipeline avanzado de RAG" /><p>En esta parte, vamos a proceder con la consulta y la prueba de nuestra implementación. ¡Vamos al grano!</p><h3>Índice</h3><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#searching-and-retrieving,-generating-answers">Búsqueda y recuperación, generando respuestas</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#enriching-queries-with-synonyms">Enriqueciendo consultas con sinónimos</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hyde-hypothetical-document-embedding">HyDE (Incrustación de Documentos Hipotéticos)</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hybrid-search">Búsqueda híbrida</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#experiments">Experimentos</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#summary-of-results">Resumen de resultados</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-1-who-audits-elastic">Prueba 1: ¿Quién audita a Elastic?</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-2--total-revenue-2023">Test 2: ingresos totales 2023</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-1">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-1">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-3-what-product-does-growth-primarily-depend-on-how-much">Prueba 3: ¿De qué producto depende principalmente el crecimiento? ¿Cuánto?</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-2">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-2">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-4-describe-employee-benefit-plan">Prueba 4: Describe el plan de beneficios para empleados</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-3">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-3">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-5-which-companies-did-elastic-acquire">Prueba 5: ¿Qué compañías adquirió Elastic?</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-4">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-4">SimpleRAG</a></p></li></ul></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#conclusion">Conclusión</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#appendix">Apéndice</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#prompts">Prompts</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#rag-question-answering-prompt">Prompt de respuesta a preguntas RAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#elastic-query-generator-prompt">Prompt generador de consultas elástico</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#potential-questions-generator-prompt">Prompt generador de preguntas potenciales</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hyde-generator-prompt">Prompt generador de HyDE</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#sample-hybrid-search-query">Consulta de búsqueda híbrida de ejemplo</a></p></li></ul></li></ul><h2>Búsqueda y recuperación, generando respuestas</h2><p>Hagamos nuestra primera pregunta, idealmente alguna información que se encuentre principalmente en el reporte anual. ¿Qué tal esto:</p>Who audits Elastic?"
<p>Ahora, apliquemos algunas de nuestras técnicas para mejorar la consulta.</p><h3>Enriqueciendo consultas con sinónimos</h3><p>Primero, mejoremos la diversidad de la redacción de la consulta y convirtiéramos en un formulario que pueda procesar fácilmente en una consulta de Elasticsearch. Aplicar la ayuda de GPT-4o para convertir la consulta en una lista de cláusulas OR. Vamos a escribir este prompt:</p>
ELASTIC_SEARCH_QUERY_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating Elasticsearch query strings. Your task is to create the most effective query string for the given user question. This query string will be used to search for relevant documents in an Elasticsearch index.

Guidelines:
1. Analyze the user's question carefully.
2. Generate ONLY a query string suitable for Elasticsearch's match query.
3. Focus on key terms and concepts from the question.
4. Include synonyms or related terms that might be in relevant documents.
5. Use simple Elasticsearch query string syntax if helpful (e.g., OR, AND).
6. Do not use advanced Elasticsearch features or syntax.
7. Do not include any explanations, comments, or additional text.
8. Provide only the query string, nothing else.

For the question "What is Clickthrough Data?", we would expect a response like:
clickthrough data OR click-through data OR click through rate OR CTR OR user clicks OR ad clicks OR search engine results OR web analytics

AND operator is not allowed. Use only OR.

User Question:
[The user's question will be inserted here]

Generate the Elasticsearch query string:
'''
<p>Cuando se aplica a nuestra consulta, GPT-4o genera sinónimos de la consulta base y el vocabulario relacionado.</p>'audits elastic OR 
elasticsearch audits OR 
elastic auditor OR 
elasticsearch auditor OR 
elastic audit firm OR 
elastic audit company OR 
elastic audit organization OR 
elastic audit service'
<p>En la clase <code>ESQueryMaker</code> , definí una función para dividir la consulta:</p>def parse_or_query(self, query_text: str) -&gt; List[str]:
    # Split the query by 'OR' and strip whitespace from each term
    # This converts a string like "term1 OR term2 OR term3" into a list ["term1", "term2", "term3"]
    return [term.strip() for term in query_text.split(' OR ')]
<p>Su función es tomar esta cadena de cláusulas OR y dividirlas en una lista de términos, permitiéndonos hacer una coincidencia múltiple en nuestros campos clave del documento:</p>["original_text", 'keyphrases', 'potential_questions', 'entities']
<p>Por fin terminando con esta pregunta:</p> 'query': {
    'bool': {
        'must': [
            {
                'multi_match': {
                'query': 'audits Elastic Elastic auditing Elastic audit process Elastic compliance Elastic security audit Elasticsearch auditing Elasticsearch compliance Elasticsearch security audit',
                'fields': [
                    'original_text',
                'keyphrases',
                'potential_questions',
                'entities'
                ],
                'type': 'best_fields',
                'operator': 'or'
                }
            }
      ]
<p>Esto cubre muchas más bases que la consulta original, con suerte reduciendo el riesgo de perder un resultado de búsqueda porque olvidamos un sinónimo. Pero podemos hacer más.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">Volver arriba</a></p><h3>HyDE (Incrustación de Documentos Hipotéticos)</h3><p>Vamos a reclutar de nuevo GPT-4o, esta vez para implementar <a href="https://arxiv.org/abs/2212.10496">HyDE</a>.</p><p>La premisa básica de HyDE es generar un documento hipotético: el tipo de documento que probablemente contenga la respuesta a la consulta original. La veracidad o exactitud del documento no es un problema. Con eso en mente, escribamos el siguiente prompt:</p>HYDE_DOCUMENT_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating hypothetical documents based on user queries. Your task is to create a detailed, factual document that would likely contain the answer to the user's question. This hypothetical document will be used to enhance the retrieval process in a Retrieval-Augmented Generation (RAG) system.

Guidelines:
1. Carefully analyze the user's query to understand the topic and the type of information being sought.
2. Generate a hypothetical document that:
   a. Is directly relevant to the query
   b. Contains factual information that would answer the query
   c. Includes additional context and related information
   d. Uses a formal, informative tone similar to an encyclopedia or textbook entry
3. Structure the document with clear paragraphs, covering different aspects of the topic.
4. Include specific details, examples, or data points that would be relevant to the query.
5. Aim for a document length of 200-300 words.
6. Do not use citations or references, as this is a hypothetical document.
7. Avoid using phrases like "In this document" or "This text discusses" - write as if it's a real, standalone document.
8. Do not mention or refer to the original query in the generated document.
9. Ensure the content is factual and objective, avoiding opinions or speculative information.
10. Output only the generated document, without any additional explanations or meta-text.

User Question:
[The user's question will be inserted here]

Generate a hypothetical document that would likely contain the answer to this query:
'''
<p>Dado que la búsqueda vectorial suele operar sobre similitud vectorial coseno, la premisa de HyDE es que podemos obtener mejores resultados emparejando documentos con documentos en lugar de consultas con documentos.</p><p>Lo que nos importa es la estructura, el flujo y la terminología. No tanto la factualidad. GPT-4o genera un documento HyDE así:</p>'Elastic N.V., the parent company of Elastic, the organization known for developing Elasticsearch, is subject to audits to ensure financial accuracy, 
regulatory compliance, and the integrity of its financial statements. The auditing of Elastic N.V. is typically conducted by an external, 
independent auditing firm. This is common practice for publicly traded companies to provide stakeholders with assurance regarding the company\'s 
financial position and operations.\n\nThe primary external auditor for Elastic is the audit firm Ernst &amp; Young LLP (EY). Ernst &amp; Young is one of the 
four largest professional services networks in the world, commonly referred to as the "Big Four" audit firms. These firms handle a substantial number 
of audits for major corporations around the globe, ensuring adherence to generally accepted accounting principles (GAAP) and international financial 
reporting standards (IFRS).\n\nThe audit process conducted by EY involves several steps. Initially, the auditors perform a risk assessment to identify 
areas where misstatements due to error or fraud could occur. They then design audit procedures to test the accuracy and completeness of financial statements,
 which include examining financial transactions, assessing internal controls, and reviewing compliance with relevant laws and regulations. Upon completion of 
 the audit, Ernst &amp; Young issues an audit report, which includes the auditor’s opinion on whether the financial statements are free from material misstatement 
 and are presented fairly in accordance with the applicable financial reporting framework.\n\nIn addition to external audits by firms like Ernst &amp; Young, 
 Elastic may also be subject to internal audits. Internal audits are performed by the company’s own internal auditors to evaluate the effectiveness of internal 
 controls, risk management, and governance processes.\n\nOverall, the auditing process plays a crucial role in maintaining the transparency and reliability of 
 Elastic\'s financial information, providing confidence to investors, regulators, and other stakeholders.'
<p>Parece bastante creíble, como el candidato ideal para los tipos de documentos que queremos indexar. Vamos a incrustar esto y usarlo para búsqueda híbrida.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">Volver arriba</a></p><h3>Búsqueda híbrida</h3><p>Esta es la base de nuestra lógica de búsqueda. Nuestro componente de búsqueda léxica serán las cadenas de cláusulas OR generadas. Nuestro componente vectorial denso será el documento HyDE embebido (también conocido como el vector de búsqueda). Empleamos KNN para identificar eficientemente varios documentos candidatos más cercanos a nuestro vector de búsqueda. Por defecto, llamamos a nuestro componente de búsqueda <em>léxico Puntaje con TF-IDF y BM25</em> . Finalmente, los puntajes léxicos y vectoriales densas se combinarán usando la proporción 30/70 recomendada por <a href="https://arxiv.org/abs/2407.01219">Wang et al</a>.</p>def hybrid_vector_search(self, index_name: str, query_text: str, query_vector: List[float], 
                         text_fields: List[str], vector_field: str, 
                         num_candidates: int = 100, num_results: int = 10) -&gt; Dict:
    """
    Perform a hybrid search combining text-based and vector-based similarity.

    Args:
        index_name (str): The name of the Elasticsearch index to search.
        query_text (str): The text query string, which may contain 'OR' separated terms.
        query_vector (List[float]): The query vector for semantic similarity search.
        text_fields (List[str]): List of text fields to search in the index.
        vector_field (str): The name of the field containing document vectors.
        num_candidates (int): Number of candidates to consider in the initial KNN search.
        num_results (int): Number of final results to return.

    Returns:
        Dict: A tuple containing the Elasticsearch response and the search body used.
    """
    try:
        # Parse the query_text into a list of individual search terms
        # This splits terms separated by 'OR' and removes any leading/trailing whitespace
        query_terms = self.parse_or_query(query_text)

        # Construct the search body for Elasticsearch
        search_body = {
            # KNN search component for vector similarity
            "knn": {
                "field": vector_field,  # The field containing document vectors
                "query_vector": query_vector,  # The query vector to compare against
                "k": num_candidates,  # Number of nearest neighbors to retrieve
                "num_candidates": num_candidates  # Number of candidates to consider in the KNN search
            },
            "query": {
                "bool": {
                    # The 'must' clause ensures that matching documents must satisfy this condition
                    # Documents that don't match this clause are excluded from the results
                    "must": [
                        {
                            # Multi-match query to search across multiple text fields
                            "multi_match": {
                                "query": " ".join(query_terms),  # Join all query terms into a single space-separated string
                                "fields": text_fields,  # List of fields to search in
                                "type": "best_fields",  # Use the best matching field for scoring
                                "operator": "or"  # Match any of the terms (equivalent to the original OR query)
                            }
                        }
                    ],
                    # The 'should' clause boosts relevance but doesn't exclude documents
                    # It's used here to combine vector similarity with text relevance
                    "should": [
                        {
                            # Custom scoring using a script to combine vector and text scores
                            "script_score": {
                                "query": {"match_all": {}},  # Apply this scoring to all documents that matched the 'must' clause
                                "script": {
                                    # Script to combine vector similarity and text relevance
                                    "source": """
                                    # Calculate vector similarity (cosine similarity + 1)
                                    # Adding 1 ensures the score is always positive
                                    double vector_score = cosineSimilarity(params.query_vector, params.vector_field) + 1.0;
                                    # Get the text-based relevance score from the multi_match query
                                    double text_score = _score;
                                    # Combine scores: 70% vector similarity, 30% text relevance
                                    # This weighting can be adjusted based on the importance of semantic vs keyword matching
                                    return 0.7 * vector_score + 0.3 * text_score;
                                    """,
                                    # Parameters passed to the script
                                    "params": {
                                        "query_vector": query_vector,  # Query vector for similarity calculation
                                        "vector_field": vector_field  # Field containing document vectors
                                    }
                                }
                            }
                        }
                    ]
                }
            }
        }

        # Execute the search request against the Elasticsearch index
        response = self.conn.search(index=index_name, body=search_body, size=num_results)
        # Log the successful execution of the search for monitoring and debugging
        logger.info(f"Hybrid search executed on index: {index_name} with text query: {query_text}")
        # Return both the response and the search body (useful for debugging and result analysis)
        return response, search_body
    except Exception as e:
        # Log any errors that occur during the search process
        logger.error(f"Error executing hybrid search on index: {index_name}. Error: {e}")
        # Re-raise the exception for further handling in the calling code
        raise e
<p>Finalmente, podemos reconstruir una función RAG. Nuestro RAG, desde la consulta hasta la respuesta, seguirá este flujo:</p><ol><li><p>Convertir la consulta en cláusulas OR.</p></li><li><p>Genera un documento HyDE e incrustalo.</p></li><li><p>Pásame ambos como entradas a la búsqueda híbrida.</p></li><li><p>Recuperar los resultados top-n, invertirlos para que el puntaje más relevante sea la "más reciente" en la memoria contextual del LLM (Empaquetado inverso) Ejemplo de empaquetado inverso: Consulta: "Técnicas de optimización de consultas Elasticsearch" Documentos recuperados (ordenados por relevancia): Orden invertido para el contexto del LLM: Al invertir el orden, la información más relevante (1) aparece al final en el contexto,  Potencialmente recibiendo más atención del LLM durante la generación de respuestas.</p><ol><li><p>"Emplea consultas bool para combinar múltiples criterios de búsqueda de forma eficiente."</p></li><li><p>"Implementar estrategias de caché para mejorar los tiempos de respuesta a las consultas."</p></li><li><p>"Optimizar los mapeos de índice para un rendimiento de búsqueda más rápido."</p></li><li><p>"Optimizar los mapeos de índice para un rendimiento de búsqueda más rápido."</p></li><li><p>"Implementar estrategias de caché para mejorar los tiempos de respuesta a las consultas."</p></li><li><p>"Emplea consultas bool para combinar múltiples criterios de búsqueda de forma eficiente."</p></li></ol></li><li><p>Pasa el contexto al LLM para que genere.</p></li></ol>def get_context(index_name, 
                match_query, 
                text_query, 
                fields, 
                num_candidates=100, 
                num_results=20, 
                text_fields=["original_text", 'keyphrases', 'potential_questions', 'entities'], 
                embedding_field="primary_embedding"):

    embedding=embedder.get_embeddings_from_text(text_query)

    results, search_body = es_query_maker.hybrid_vector_search(
        index_name=index_name,
        query_text=match_query,
        query_vector=embedding[0][0],
        text_fields=text_fields,
        vector_field=embedding_field,
        num_candidates=num_candidates,
        num_results=num_results
    )

    # Concatenates the text in each 'field' key of the search result objects into a single block of text.
    context_docs=['\n\n'.join([field+":\n\n"+j['_source'][field] for field in fields]) for j in results['hits']['hits']]

    # Reverse Packing to ensure that the highest ranking document is seen first by the LLM.
    context_docs.reverse()
    return context_docs, search_body

def retrieval_augmented_generation(query_text):
    match_query= gpt4o.generate_query(query_text)
    fields=['original_text']

    hyde_document=gpt4o.generate_HyDE(query_text)

    context, search_body=get_context(index_name, match_query, hyde_document, fields)

    answer= gpt4o.basic_qa(query=query_text, context=context)
    return answer, match_query, hyde_document, context, search_body

<p>Vamos a hacer nuestra consulta y obtener nuestra respuesta:</p>According to the context, Elastic N.V. is audited by an independent registered public accounting firm, PricewaterhouseCoopers (PwC). 
This information is found in the section titled "report of independent registered public accounting firm," which states:

"We have audited the accompanying consolidated balance sheets of Elastic N.V. [...] / s / pricewaterhouseco."
<p>Muy bien. Así es.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">Volver arriba</a></p><h2>Experimentos</h2><p>Hay una pregunta importante que responder ahora. ¿Qué obtuvimos invirtiendo tanto esfuerzo y complejidad adicional en estas implementaciones?</p><p>Hagamos una pequeña comparación. La pipeline RAG que implementamos frente a la búsqueda híbrida base, sin ninguna de las mejoras que hicimos. Haremos un pequeño serial de pruebas para ver si notamos diferencias sustanciales. Nos referiremos al RAG que acabamos de implementar como AdvancedRAG, y al pipeline básico como SimpleRAG.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf605c8246989df32/6a1711178b73cbc61d18a11d/8da40067835ab8b4dc12fe52a51a6c26858ad32f-1440x1095.jpg" alt="Simple RAG Pipeline" /><h4>Resumen de resultados</h4><p>Esta tabla resume los resultados de cinco pruebas de ambas tuberías RAG. Juzgué la superioridad relativa de cada método basándome en el detalle y la calidad de las respuestas, pero este es un juicio totalmente subjetivo. Las respuestas reales se reproducen a continuación de esta tabla para que lo consideres. Dicho esto, ¡echemos cómo les fue!</p><p>SimpleRAG no pudo responder a las preguntas 1 y 5. AdvancedRAG también profundizó mucho más en las preguntas 2, 3 y 4. Basándome en el mayor detalle, evalué mejor la calidad de las respuestas de AdvancedRAG.</p><p>Prueba</p><p>Pregunta</p><p>Rendimiento avanzado de RAG</p><p>Rendimiento de SimpleRAG</p><p>Latencia de AdvancedRAG</p><p>Latencia de SimpleRAG</p><p>Ganador</p><p>1</p><p>¿Quién audita Elastic?</p><p>Identificó correctamente a PwC como contralor.</p><p>No se identificó al contralor.</p><p>11,6</p><p>4,4</p><p>AdvancedRAG</p><p>2</p><p>¿Cuál fue el total de ingresos en 2023?</p><p>Proporcionó la cifra correcta de ingresos. Incluyó contexto adicional sobre ingresos de años anteriores.</p><p>Proporcionó la cifra correcta de ingresos.</p><p>13,3s</p><p>2,8</p><p>AdvancedRAG</p><p>3</p><p>¿De qué producto depende principalmente el crecimiento? ¿Cuánto?</p><p>Identificamos correctamente a Elastic Cloud como el motor clave. Incluyó el contexto general de ingresos y mayor detalle.</p><p>Identificamos correctamente a Elastic Cloud como el motor clave.</p><p>14.1</p><p>12,8</p><p>AdvancedRAG</p><p>4</p><p>Describe el plan de beneficios para empleados</p><p>Ofreció una descripción completa de los planes de jubilación, programas de salud y otros beneficios. Incluyó cantidades específicas de contribución para distintos años.</p><p>Ofreció una buena visión general de los beneficios, incluyendo compensación, planes de jubilación, entorno laboral y el programa Elastic Cares.</p><p>26,6</p><p>11,6</p><p>AdvancedRAG</p><p>5</p><p>¿Qué compañías adquirió Elastic?</p><p>Listé correctamente las adquisiciones recientes mencionadas en el reporte (CmdWatch, Build Security, Optimyze). Proporcioné algunas fechas de adquisición y precios de compra.</p><p>No se consiguió recuperar la información relevante del contexto proporcionado.</p><p>11,9</p><p>2,7</p><p>AdvancedRAG</p><h4>Prueba 1: ¿Quién audita a Elastic?</h4><p>AdvancedRAG</p>According to the context, Elastic N.V. is audited by an independent 
registered public accounting firm, PricewaterhouseCoopers (PwC). 
This information is found in the section titled "report of 
independent registered public accounting firm," which states:
"We have audited the accompanying consolidated balance sheets 
of Elastic N.V. [...] / s / pricewaterhouseco."
<p>SimpleRAG</p>According to the provided context, no specific auditing firm 
or company is mentioned as the auditor for Elastic. 
The context provided talks about various features, strengths, 
deployment options, and other details related to Elastic and 
its operations but does not specify which auditing firm is 
responsible for auditing Elastic's financial statements or 
internal controls.
<p><strong>Resumen</strong>: SimpleRAG no identificó a PWC como contralor</p><p>Vale, eso en realidad es bastante sorprendente. Eso parece un fallo de búsqueda por parte de SimpleRAG. No se recuperaron documentos relacionados con la auditoría. Bajemos un poco la dificultad con la siguiente prueba.</p><h4>Test 2: ingresos totales 2023</h4><p>AdvancedRAG</p>### Total Revenue for 2023

According to the provided context, the total revenue for the 
year ended April 30, 2023, was **$1,068,989,000**. This 
information can be found several times in the provided text. 
Specifically, one part states:

"The following table sets forth our results of operations 
for the periods presented in dollars and as a percentage of 
our total revenue. ... year ended April 30, 2023, ... 
total revenue 1,068,989".

Additionally, it is mentioned here as well:

"For the years ended April 30, 2023, 2022, and 2021, 
representing year-over-year growth of 24% for the year 
ended April 30, 2023... total revenue $1,068,989".

Therefore, the total revenue for 2023 is $1.069 billion.
<p>SimpleRAG</p>According to the context provided, the total revenue for the fiscal year ended April 30, 2023, is:

$1,068,989,000 (or approximately $1.1 billion).
<p><strong>Resumen</strong>: Ambos RAG obtuvieron la respuesta correcta: 1.068.989.000 dólares de ingresos totales en 2023</p><p>Ambos estaban justo aquí. ¿Parece que AdvancedRAG adquirió una gama más amplia de documentos? Sin duda, la respuesta es más detallada e incorpora información de años anteriores. Eso es de esperar dadas las mejoras que hicimos, pero es demasiado pronto para decidir.</p><p>Subamos la dificultad.</p><h4>Prueba 3: ¿De qué producto depende principalmente el crecimiento? ¿Cuánto?</h4><p>AdvancedRAG</p>### Product Growth Primarily Depends On:
According to the context, "our revenue is derived primarily from 
subscriptions of search, observability and security built into 
the elastic stack." The document mentions various strategies and 
factors contributing to the company’s growth which include investment 
in "elastic cloud," "elastic stack," and the conversion of free 
users to paid subscribers.

### Quantitative Data:
- **Revenue**: The document states, "Our total revenue was $1.1 billion, 
$862.4 million, and $608.5 million for the years ended April 30, 2023, 
2022, and 2021, respectively."
- **Subscription Revenue**: "Subscriptions accounted for 92%, 93%, 
and 93% of our total revenue for the years ended April 30, 2023, 
2022, and 2021, respectively."
- **Elastic Cloud Contribution**: "For the years ended April 30, 2023, 
2022, and 2021, Elastic Cloud contributed 40%, 35%, and 27% of our 
total revenue, respectively."
- **Customer Growth**: The number of customers increased from 
"over 15,000 as of April 30, 2021," to "approximately 20,200 as of 
April 30, 2023."

### Summary:
Product growth for the company primarily depends on the "Elastic Stack" 
(which includes search, observability, and security solutions) and 
particularly the "Elastic Cloud." The Elastic Cloud's growing contribution 
to revenue highlights its significance, which grew from 27% in 2021 to 40% in 2023.
<p>SimpleRAG</p>According to the provided context, growth primarily depends on **Elastic Cloud**, 
which is described as the company's "family of cloud-based offerings." 
The context also emphasizes the significance of Elastic Cloud in achieving 
the company's long-term growth potential and its role in increasing subscription revenue.

From the context:

- "Increasing adoption of Elastic Cloud... is an important growth opportunity 
for our business. Organizations are increasingly looking for hosted 
deployment alternatives with reduced administrative burdens."
- "We believe that offering Elastic Cloud is important for achieving 
our long-term growth potential, and we expect Elastic Cloud’s contribution 
to our subscription revenue to continue to increase."

In terms of the contribution to total revenue, here are the specific figures provided:

- Elastic Cloud contributed 40% of the total revenue for the year ended April 30, 2023.
- Elastic Cloud contributed 35% of the total revenue for the year ended April 30, 2022.
- Elastic Cloud contributed 27% of the total revenue for the year ended April 30, 2021.

This progression shows a clear upward trend in the importance and 
contribution of Elastic Cloud to the company's overall revenue.
<p><strong>Resumen</strong>: Ambos RAGs identificaron correctamente a Elastic Cloud como el principal motor de crecimiento. Sin embargo, AdvancedRAG incluye más detalles, teniendo en cuenta los ingresos por subscripción y el crecimiento de clientes, y menciona explícitamente otras ofertas de Elastic.</p><h4>Prueba 4: Describe el plan de beneficios para empleados</h4><p>AdvancedRAG</p>### Employee Benefit Plan Overview

The provided context outlines various elements of the employee benefit plan at 
Elastic, often referred to as "Elasticians." Here are the key components 
described in the texts:

1. **401(k) Plan**:
   - **Coverage**: The 401(k) plan is available to substantially all U.S. 
   employees who meet minimum age and service requirements.
   - **Contributions**: Elastic makes contributions to the 401(k) plan up to 
   6% of the participating employee’s W-2 earnings and wages.
   - **Expenses**: For the fiscal years ended April 30, Elastic recorded 
   expenses of $17.9 million (2023), $15.2 million (2022), and $11.4 million (2021) 
   related to the 401(k) plan.
   - **Defined-Contribution Plans in Other Countries**: Elastic has 
   defined-contribution plans in various other countries and recorded respective 
   expenses of $9.4 million (2023), $7.2 million (2022), and $5.1 million (2021).

2. **Stock-Based Compensation**:
   - **Types of Awards**: Stock options, restricted stock units (RSUs), 
   and shares under the Employee Stock Purchase Plan (ESPP).
   - **Fair Value Measurement**: Fair value of these stock awards is 
   measured using models like Black-Scholes.
   - **Employee Stock Purchase Plan (2022 ESPP)**: 
     - Started in 2022, it allows employees to acquire ordinary 
     shares at a discount (85% of the market value at the beginning 
     or end of the offering period).
     - Offering periods are approximately six months long.

3. **Total Rewards Compensation**:
   - **Components**: Includes cash compensation as well as equity awards, 
   reflecting a comprehensive interest in the well-being of the employees.
   - **Programs**: Market competitive medical and dental programs, 
   mental health, and holistic well-being programs.
   - **Paid Time Off**: Features 16 weeks of paid leave for new 
   parents and life-planning benefits.

4. **Retirement and Income Protection Plans**:
   - **401(k) Match**: Elastic provides a dollar-for-dollar 
   match up to 6% of eligible earnings up to a plan-limit 
   maximum for U.S.-based employees.
   - **Similar Plans Outside the U.S.**: Competitive retirement 
   plans available for employees based outside the United States.

5. **Flexible Work Environment**: 
   - **Distributed Workforce**: Elastic operates with a global 
   distributed workforce model, allowing employees to work remotely 
   and providing a broad candidate pool.
   
6. **Learning and Development**:
   - **Programs**: Access to on-demand video-based learning, 
   leadership development programs, and other specific training 
   to develop managers and leaders.

7. **Employee Engagement and Surveys**:
   - **Mechanisms**: Annual employee engagement survey and 
   mid-year pulse survey.
   - **Objectives**: To monitor morale, develop action plans, 
   and maintain high engagement scores.

8. **Onboarding Program**:
   - **X-School**: An extensive new-hire orientation program to 
   help new employees meet peers and learn about the company's 
   products and solutions.

In summary, Elastic's employee benefit plan encompasses a broad 
spectrum of financial, health, well-being, and professional development 
benefits designed to support and enhance the lives of its employees. 
The plan is both competitive and comprehensive, aiming to attract,
retain, and engage its global workforce.
<p>SimpleRAG</p>The employee benefit plan at the Company includes several key 
components aimed at enhancing the overall well-being and 
professional development of employees, referred to as "Elasticians." 

1. **Compensation and Benefits:**
   - The Company provides market-competitive compensation 
   packages which typically include cash compensation and equity awards.
   - Comprehensive medical and dental programs are offered, 
   emphasizing mental health and holistic well-being.
   - Paid time off programs feature:
     - 16 weeks of paid leave for new parents.
     - Life-planning benefits.
     - Travel reimbursements for certain healthcare services.

2. **Retirement and Income Protection Plans:**
   - In the United States, employees are eligible for a 401k 
   plan with a dollar-for-dollar match by the Company up to 6% 
   of eligible earnings, subject to a plan limit.
   - Similar competitive retirement and income protection plans 
   are available for employees outside the United States.

3. **Work Environment:**
   - The Company offers a flexible work environment, allowing most 
   employees to work from anywhere as often as they would like.
   - Physical offices around the world are available for employees 
   who prefer to work on-site or need a space for face-to-face interactions.

4. **Community Involvement:**
   - The Elastic Cares Program supports charitable organizations 
   that matter to employees on both local and global levels.
   - The program includes:
     - Donation matching.
     - Providing the Company's technology for free to certain 
     nonprofit organizations.
     - Volunteer time off initiative.
<p><strong>Resumen</strong>: AdvancedRAG entra en mucho más detalle y profundidad, mencionando el plan 401K para empleados con base en EE. UU., así como definiendo planes de contribución fuera de EE. UU. También menciona planes de salud y bienestar, pero no incluye el programa Elastic Cares, que menciona SimpleRAG.</p><h4>Prueba 5: ¿Qué compañías adquirió Elastic?</h4><p>AdvancedRAG</p>Elastic acquired several companies over the years to enhance its 
technology and market presence. According to the provided context, 
the specific acquisitions include:

1. **CmdWatch Security Inc.**: Acquired on September 17, 2021. 
The total purchase consideration was $77.8 million.
2. **Build Security Ltd.**: Acquired on September 2, 2021. The 
combined purchase price for Build Security Ltd. and another acquisition was $57.2 million.
3. **Optimyze Cloud Inc.**: Acquired on November 1, 2021. Part 
of the combined purchase price mentioned above with Build Security Ltd.
4. **Endgame, Inc.**: Although the context mentions an amended 
and restated stock incentive plan, it indicates that Endgame, Inc. was 
associated with Elastic, suggesting it could have been acquired at some point.

These acquisitions are part of Elastic's strategy to 
enhance the technology underlying its security and 
observability offerings.
<p>SimpleRAG</p>The provided context does not specify any companies that Elastic has acquired. 
Therefore, based on the context, there is no information available about the companies acquired by Elastic.
<p><strong>Resumen</strong>: SimpleRAG no recupera ninguna información relevante sobre adquisiciones, lo que lleva a una respuesta fallida. AdvancedRAG lista correctamente CmdWatch, Build Security y Optimyze, que fueron las adquisiciones clave listadas en el reporte.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">Volver arriba</a></p><h2>Conclusión</h2><p>Según nuestras pruebas, nuestras técnicas avanzadas parecen aumentar el rango y la profundidad de la información presentada, lo que podría mejorar la calidad de las respuestas RAG.</p><p>Además, puede haber mejoras en la fiabilidad, ya que preguntas formuladas de forma ambigua como <code>Which companies did Elastic acquire?</code> y <code>Who audits Elastic</code> fueron respondidas correctamente por AdvancedRAG pero no por SimpleRAG.</p><p>Sin embargo, conviene tener en cuenta que en 3 de cada 5 casos, la tubería básica de RAG, que incorpora Búsqueda Híbrida pero ninguna otra técnica, logró producir respuestas que capturaron la mayor parte de la información clave.</p><p>Cabe señalar que, debido a la incorporación de LLMs en las fases de preparación y consulta de datos, la latencia de AdvancedRAG suele ser entre 2 y 5 veces mayor que la de SimpleRAG. Este es un costo significativo que puede hacer que AdvancedRAG sea adecuado solo para situaciones donde la calidad de la respuesta se prioriza sobre la latencia.</p><p>Los importantes costos de latencia pueden aliviar usando un LLM más pequeño y barato como Claude Haiku o GPT-4o-mini en la fase de preparación de datos. Almacena los modelos avanzados para generar respuestas.</p><p>Esto está en línea con los hallazgos de Wang et al. Como muestran sus resultados, cualquier mejora realizada es relativamente incremental. En resumen, un RAG básico te lleva casi hasta un producto final decente, siendo además más barato y rápido. Para mí, es una conclusión interesante. Para casos de uso donde la velocidad y la eficiencia son clave, SimpleRAG es la opción sensata. Para casos de uso en los que hay que exprimir hasta la última gota de rendimiento, las técnicas incorporadas en AdvancedRAG pueden ofrecer una vía a seguir.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt56b7067a9d41d5a8/6a171119acf0886fb4be9c45/ea811706b6adc4731d90b925a9fefa0ac15901b4-1440x1060.jpg" alt="Oleoducto Wang" /><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">Volver arriba</a></p><h2>Apéndice</h2><h3>Prompts</h3><h4>Prompt de respuesta a preguntas RAG</h4><p>Prompt para que el LLM genere respuestas basadas en la consulta y el contexto.</p>BASIC_RAG_PROMPT = '''
You are an AI assistant tasked with answering questions based primarily on the provided context, while also drawing on your own knowledge when appropriate. Your role is to accurately and comprehensively respond to queries, prioritizing the information given in the context but supplementing it with your own understanding when beneficial. Follow these guidelines:

1. Carefully read and analyze the entire context provided.
2. Primarily focus on the information present in the context to formulate your answer.
3. If the context doesn't contain sufficient information to fully answer the query, state this clearly and then supplement with your own knowledge if possible.
4. Use your own knowledge to provide additional context, explanations, or examples that enhance the answer.
5. Clearly distinguish between information from the provided context and your own knowledge. Use phrases like "According to the context..." or "The provided information states..." for context-based information, and "Based on my knowledge..." or "Drawing from my understanding..." for your own knowledge.
6. Provide comprehensive answers that address the query specifically, balancing conciseness with thoroughness.
7. When using information from the context, cite or quote relevant parts using quotation marks.
8. Maintain objectivity and clearly identify any opinions or interpretations as such.
9. If the context contains conflicting information, acknowledge this and use your knowledge to provide clarity if possible.
10. Make reasonable inferences based on the context and your knowledge, but clearly identify these as inferences.
11. If asked about the source of information, distinguish between the provided context and your own knowledge base.
12. If the query is ambiguous, ask for clarification before attempting to answer.
13. Use your judgment to determine when additional information from your knowledge base would be helpful or necessary to provide a complete and accurate answer.

Remember, your goal is to provide accurate, context-based responses, supplemented by your own knowledge when it adds value to the answer. Always prioritize the provided context, but don't hesitate to enhance it with your broader understanding when appropriate. Clearly differentiate between the two sources of information in your response.

Context:
[The concatenated documents will be inserted here]

Query:
[The user's question will be inserted here]

Please provide your answer based on the above guidelines, the given context, and your own knowledge where appropriate, clearly distinguishing between the two:
'''
<h4>Prompt generador de consultas elástico</h4><p>Prompt para enriquecer consultas con sinónimos y convertirlas al formato OR.</p>ELASTIC_SEARCH_QUERY_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating Elasticsearch query strings. Your task is to create the most effective query string for the given user question. This query string will be used to search for relevant documents in an Elasticsearch index.

Guidelines:
1. Analyze the user's question carefully.
2. Generate ONLY a query string suitable for Elasticsearch's match query.
3. Focus on key terms and concepts from the question.
4. Include synonyms or related terms that might be in relevant documents.
5. Use simple Elasticsearch query string syntax if helpful (e.g., OR, AND).
6. Do not use advanced Elasticsearch features or syntax.
7. Do not include any explanations, comments, or additional text.
8. Provide only the query string, nothing else.

For the question "What is Clickthrough Data?", we would expect a response like:
clickthrough data OR click-through data OR click through rate OR CTR OR user clicks OR ad clicks OR search engine results OR web analytics

AND operator is not allowed. Use only OR.

User Question:
[The user's question will be inserted here]

Generate the Elasticsearch query string:
'''
<h4>Prompt generador de preguntas potenciales</h4><p>Prompt para generar posibles preguntas, enriquecer metadatos del documento.</p>RAG_QUESTION_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating questions for Retrieval-Augmented Generation (RAG) systems. Your task is to analyze a given document and create 10 diverse questions that would effectively test a RAG system's ability to retrieve and synthesize information from this document.

Guidelines:
1. Thoroughly analyze the entire document.
2. Generate exactly 10 questions that cover various aspects and levels of complexity within the document's content.
3. Create questions that specifically target:
   a. Key facts and information
   b. Main concepts and ideas
   c. Relationships between different parts of the content
   d. Potential applications or implications of the information
   e. Comparisons or contrasts within the document
4. Ensure questions require answers of varying lengths and complexity, from simple retrieval to more complex synthesis.
5. Include questions that might require combining information from different parts of the document.
6. Frame questions to test both literal comprehension and inferential understanding.
7. Avoid yes/no questions; focus on open-ended questions that promote comprehensive answers.
8. Consider including questions that might require additional context or knowledge to fully answer, to test the RAG system's ability to combine retrieved information with broader knowledge.
9. Number the questions from 1 to 10.
10. Output only the ten questions, without any additional text, explanations, or answers.

Document:
[The document content will be inserted here]

Generate 10 questions optimized for testing a RAG system based on this document:
'''
<h4>Prompt generador de HyDE</h4><p>Prompt para generar documentos hipotéticos usando HyDE</p>HYDE_DOCUMENT_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating hypothetical documents based on user queries. Your task is to create a detailed, factual document that would likely contain the answer to the user's question. This hypothetical document will be used to enhance the retrieval process in a Retrieval-Augmented Generation (RAG) system.

Guidelines:
1. Carefully analyze the user's query to understand the topic and the type of information being sought.
2. Generate a hypothetical document that:
   a. Is directly relevant to the query
   b. Contains factual information that would answer the query
   c. Includes additional context and related information
   d. Uses a formal, informative tone similar to an encyclopedia or textbook entry
3. Structure the document with clear paragraphs, covering different aspects of the topic.
4. Include specific details, examples, or data points that would be relevant to the query.
5. Aim for a document length of 200-300 words.
6. Do not use citations or references, as this is a hypothetical document.
7. Avoid using phrases like "In this document" or "This text discusses" - write as if it's a real, standalone document.
8. Do not mention or refer to the original query in the generated document.
9. Ensure the content is factual and objective, avoiding opinions or speculative information.
10. Output only the generated document, without any additional explanations or meta-text.

User Question:
[The user's question will be inserted here]

Generate a hypothetical document that would likely contain the answer to this query:
'''
<h3>Consulta de búsqueda híbrida de ejemplo</h3>{'knn': {'field': 'primary_embedding',
  'query_vector': [0.4265527129173279,
   -0.1712949573993683,
   -0.042020395398139954,
   ...],
  'k': 100,
  'num_candidates': 100},
 'query': {'bool': {'must': [{'multi_match': {'query': 'audits Elastic Elastic auditing Elastic audit process Elastic compliance Elastic security audit Elasticsearch auditing Elasticsearch compliance Elasticsearch security audit',
      'fields': ['original_text',
       'keyphrases',
       'potential_questions',
       'entities'],
      'type': 'best_fields',
      'operator': 'or'}}],
   'should': [{'script_score': {'query': {'match_all': {}},
      'script': {'source': '\n                                        double vector_score = cosineSimilarity(params.query_vector, params.vector_field) + 1.0;\n                                        double text_score = _score;\n                                        return 0.7 * vector_score + 0.3 * text_score;\n                                        ',
       'params': {'query_vector': [0.4265527129173279,
         -0.1712949573993683,
         -0.042020395398139954,
        ...],
        'vector_field': 'primary_embedding'}}}}]}},
 'size': 10}
]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Han Xiang Choong]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf605c8246989df32/6a1711178b73cbc61d18a11d/8da40067835ab8b4dc12fe52a51a6c26858ad32f-1440x1095.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 15 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Técnicas avanzadas de RAG parte 1: Procesamiento de datos]]></title>
    <description><![CDATA[Discutir e implementar técnicas que puedan aumentar el rendimiento de RAG. Parte 1 de 2, centrada en el componente de procesamiento e ingesta de datos de una tubería avanzada de RAG.]]></description>
    <content:encoded><![CDATA[<p><em>Esta es la Parte 1 de nuestra exploración sobre las Técnicas Avanzadas de RAG. </em><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2"><em>¡Haz clic aquí para la Parte 2!</em></a></p><p>El reciente artículo <a href="https://arxiv.org/abs/2407.01219">Searching for Best Practices in Retrieval-Augmented Generation</a> evalúa empíricamente la eficacia de diversas técnicas de mejora del RAG, con el objetivo de converger en un conjunto de mejores prácticas para el RAG.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt671704ff06a4011d/6a170b3ea929cf2d19ae09d8/dafa7250e7c4ead4d9b4aed7c407509131929749-1440x572.png" alt="Oleoducto RAG recomendado por Wang" /><p>Implementaremos algunas de estas mejores prácticas propuestas, concretamente aquellas que buscan mejorar la calidad de la búsqueda <strong>(fragmentación de oraciones, HyDE, empaquetado inverso).</strong></p><p>Por brevedad, omitiremos aquellas técnicas centradas en mejorar la eficiencia <strong>(Clasificación de Consultas y Resumen).</strong></p><p>También implementaremos algunas técnicas que no se trataron, pero que personalmente encuentro útiles e <strong>interesantes (Inclusión de Metadatos, Incrustaciones Compuestas Multi-Campo, Enriquecimiento de Consultas).</strong></p><p>Finalmente, realizaremos una breve prueba para ver si la calidad de nuestros resultados de búsqueda y respuestas generadas mejoró respecto a la línea base. ¡Vamos a ello!</p><h2>Resumen de RAG</h2><p>RAG tiene como objetivo mejorar los LLMs recuperando información de bases de conocimiento externas para enriquecer las respuestas generadas. Al proporcionar información específica de dominio, los LLM pueden adaptar rápidamente a casos de uso fuera del alcance de sus datos de entrenamiento; Significativamente más barato que el ajuste fino y más fácil de mantener actualizado.</p><p>Las medidas para mejorar la calidad de RAG suelen centrar en dos pistas:</p><ol><li><p>Mejorar la calidad y claridad de la base de conocimiento.</p></li><li><p>Mejorar la cobertura y especificidad de las consultas de búsqueda.</p></li></ol><p>Estas dos medidas lograrán el objetivo de mejorar las probabilidades de que el LLM tenga acceso a hechos e información relevantes, y así sea menos probable que alucine o se base en su propio conocimiento, que puede estar desactualizado o irrelevante.</p><p>La diversidad de métodos es difícil de aclarar en solo unas pocas frases. Vamos directamente a la implementación para aclarar las cosas.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" alt="Pipeline avanzado de RAG" /><h3>Índice</h3><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#overview">Visión general</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Índice</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#set-up">Preparación</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#ingesting-processing-and-embedding-documents">Ingestión, procesamiento e incrustación de documentos</a>  </p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#data-ingestion">Ingesta de datos</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#sentence-level-token-wise-chunking">Fragmentación a nivel de frase, por fichas</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#metadata-inclusion-and-generation">Inclusión y generación de metadatos</a> </p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#keyphrases-extracted-by-textrank">Frases clave extraídas por TextRank</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#potential-questions-generated-by-gpt-4o">Posibles preguntas generadas por GPT-4o</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#entities-extracted-by-spacy">Entidades extraídas por Spacy</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#composite-multi-field-embeddings">Incrustaciones compuestas multicampo</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#indexing-to-elastic">Indexación a Elastic</a></p></li></ul></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#cat-break">Ruptura de gato</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#appendix">Apéndice</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#definitions">Definiciones</a></p></li></ul></li></ul><h2>Preparación</h2><p><em>Todo el código puede </em><a href="https://github.com/elastic/elasticsearch-labs/tree/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques"><em>encontrar en el repositorio de Searchlabs</em></a><em>.</em></p><p>Primero lo primero. Necesitarás lo siguiente:</p><ol><li><p>Un despliegue de nube elástica</p></li><li><p>Una API LLM - Estamos usando un despliegue GPT-4o en Azure OpenAI en este cuaderno</p></li><li><p>Python Versión 3.12.4 o posterior</p></li></ol><p>Ejecutaremos todo el código desde <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/main.ipynb">el cuaderno main.ipynb.</a></p><p>Adelante, clona el repositorio por git, navega a supporting-blog-content/advanced-rag-techniques y luego ejecuta los siguientes comandos:</p># Create a new virtual environment named 'rag_env'
python -m venv rag_env

# Activate the virtual environment (for Unix-based systems)
source rag_env/bin/activate

# (For Windows)
.\rag_env\Scripts\activate

# Install packages listed in requirements.txt
pip install -r requirements.txt
<p>Una vez hecho esto, crea un <em>.env</em> y rellenar los siguientes campos (Referenciado en <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/.env.example"><em>.env.example</em></a>). Créditos a mi coautor, Claude-3.5, por los comentarios útiles.</p># Elastic Cloud: Found in the 'Deployment' page of your Elastic Cloud 
# console
ELASTIC_CLOUD_ENDPOINT=""
ELASTIC_CLOUD_ID=""

# Elastic Cloud: Created during deployment setup or in 'Security' 
# settings
ELASTIC_USERNAME=""
ELASTIC_PASSWORD=""

# Elastic Cloud: The name of the index you created in Kibana or via API
ELASTIC_INDEX_NAME=""

# Azure AI Studio: Found in 'Keys and Endpoint' section of your Azure 
# OpenAI resource
AZURE_OPENAI_KEY_1=""
AZURE_OPENAI_KEY_2=""
AZURE_OPENAI_REGION=""
AZURE_OPENAI_ENDPOINT=""

# Azure AI Studio: Found in 'Deployments' section of your Azure OpenAI 
# resource
AZURE_OPENAI_DEPLOYMENT_NAME=""

# Using BAAI/bge-small-en-v1.5 because I think it is a good balance of 
# resource efficiency and performance. 
HUGGINGFACE_EMBEDDING_MODEL="BAAI/bge-small-en-v1.5"
<p>A continuación, elegimos el documento a ingerir y lo colocaremos en la carpeta de documentos. Para este artículo, emplearemos el <a href="https://s201.q4cdn.com/217177842/files/doc_downloads/OtherDocuments/2023/AnnualMeeting/Annual-Report-Fiscal-Year-2023.pdf">Reporte Anual 2023 de Elastic N.V</a>. Es un documento bastante exigente y denso, perfecto para poner a prueba nuestras técnicas RAG.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte292dc6030d496cc/6a170b40dc55de9b03e00dfc/e513b9d67adac43da794c25a5969b893127bbbe3-1440x395.jpg" alt="Reporte Anual de Elastic 2023" /><p>Ahora que estamos listos, vamos a ingestión. Abre <em>main.ipynb</em> y ejecuta las dos primeras celdas para importar todos los paquetes e iniciar todos los servicios.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Volver arriba</a></p><h2>Ingestión, procesamiento e incrustación de documentos</h2><h3>Ingesta de datos</h3><ul><li><p><em>Nota personal: Me sorprende la comodidad de LlamaIndex. En la antigüedad, antes de los LLMs y LlamaIndex, ingerir documentos de varios formatos era un proceso doloroso de recopilar paquetes esotéricos de todas partes. Ahora se reduce a una sola llamada de función. Salvaje.</em></p></li></ul><p>El <code>SimpleDirectoryReader</code> cargará todos los documentos del <code>directory_path.</code> Para <code>.pdf</code> archivos, devuelve una lista de objetos documento, que convierto a diccionarios de Python porque me resultan más fáciles de manejar.</p># llamaindex_processor.py
from llama_index.core import SimpleDirectoryReader

class LlamaIndexProcessor:
   def __init__(self):
       pass 
   
   def load_documents(self, directory_path):
       ''' 
       Load all documents in directory
       '''
       reader = SimpleDirectoryReader(input_dir=directory_path)
       return reader.load_data()

# main.ipynb
llamaindex_processor=LlamaIndexProcessor()
documents=llamaindex_processor.load_documents('./documents/')
documents=[dict(doc_obj) for doc_obj in documents]
<p>Cada diccionario contiene el contenido clave en el campo <code>text</code> . También contiene metadatos útiles como número de página, nombre de archivo, tamaño y tipo.</p>{
  'id_': '5f76f0b3-22d8-49a8-9942-c2bbab14f63f',
  'metadata': {'page_label': '5',
   'file_name': 'Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf',
   'file_path': '/Users/han/Desktop/Projects/truckasaurus/documents/Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf',
   'file_type': 'application/pdf',
   'file_size': 3724426,
   'creation_date': '2024-07-27',
   'last_modified_date': '2024-07-27'},
   'text': 'Table of Contents\nPage\nPART I\nItem 1. Business 3\n15 Item 1A. Risk Factors\nItem 1B. Unresolved Staff Comments 48\nItem 2. Properties 48\nItem 3. Legal Proceedings 48\nItem 4. Mine Safety Disclosures 48\nPART II\nItem 5. Market for Registrant's Common Equity, Related Stockholder Matters and Issuer Purchases of \nEquity Securities49\nItem 6. [Reserved] 49\nItem 7. Management's Discussion and Analysis of Financial Condition and Results of Operations 50\nItem 7A. Quantitative and Qualitative Disclosures About Market Risk 64\nItem 8. Financial Statements and Supplementary Data 66\nItem 9. Changes in and Disagreements With Accountants on Accounting and Financial Disclosure 100\n100\n101Item 9A. Controls and Procedures\nItem 9B. Other Information\nItem 9C. Disclosure Regarding Foreign Jurisdictions That Prevent Inspections 101\nPART III\n102\n102\n102\n102Item 10. Directors, Executive Officers and Corporate Governance\nItem 11. Executive Compensation\nItem 12. Security Ownership of Certain Beneficial Owners and Management, and Related Stockholder Matters  \nItem 13. Certain Relationships and Related Transactions, and Director Independence\nItem 14. Principal Accountant Fees and Services 102\nPART IV\n103\n105Item 15. Exhibits and Financial Statement Schedules  \nItem 16. Form 10-K Summary\nSignatures 106\ni',
   ...
}
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Volver arriba</a></p><h3>Fragmentación a nivel de frase, por fichas</h3><p>Lo primero que hay que hacer es reducir nuestros documentos a fragmentos de una longitud estándar (para garantizar la coherencia y la manejabilidad). Los modelos de incrustación tienen límites únicos de tokens (tamaño máximo de entrada que pueden procesar). Los tokens son las unidades básicas de texto que procesan los modelos. Para evitar la pérdida de información (truncamiento u omisión de contenido), deberíamos proporcionar texto que no exceda esos límites (dividiendo textos más largos en segmentos más pequeños).</p><p>El chunking tiene un impacto significativo en el rendimiento. Idealmente, cada fragmento representaría una pieza de información autónoma, capturando información contextual sobre un único tema. Los métodos de fragmentación incluyen el fragmento a nivel de palabra, donde los documentos se dividen por el recuento de palabras, y el fragmento semántico, que emplea un LLM para identificar puntos de interrupción lógicos.</p><p>El fragmento a nivel de palabra es barato, rápido y sencillo, pero corre el riesgo de fragmentar las frases y así romper el contexto. El fragmento semántico se vuelve lento y caro, especialmente si se trata de documentos como el Reporte Anual de Elastic de 116 páginas.</p><p>Elijamos un enfoque intermedio. El fragmento a nivel de oración sigue siendo sencillo, pero puede preservar el contexto de forma más eficaz que el fragmento a nivel de palabra, siendo significativamente más barato y rápido. Además, implementaremos una ventana deslizante para capturar parte del contexto circundante y aliviar el impacto de dividir los párrafos.</p># chunker.py 

import uuid
import re


class Chunker: 
    def __init__(self, tokenizer):
        self.tokenizer = tokenizer 
    
    def split_into_sentences(self, text):
        """Split text into sentences."""
        return re.split(r'(?&lt;=[.!?])\s+', text)
 
    def sentence_wise_tokenized_chunk_documents(self, documents, chunk_size=512, overlap=20, min_chunk_size=50):
        '''
        1. Split text into sentences.
        2. Tokenize using the provided tokenizer method.
        3. Build chunks up to the chunk_size limit.
        4. Create an overlap based on tokens - to preserve context.
        5. Only keep chunks that meet the minimum token size requirement.
        '''
        chunked_documents = []

        for doc in documents:
            sentences = self.split_into_sentences(doc['text'])
            tokens = []
            sentence_boundaries = [0]

            # Tokenize all sentences and keep track of sentence boundaries
            for sentence in sentences:
                sentence_tokens = self.tokenizer.encode(sentence, add_special_tokens=True)
                tokens.extend(sentence_tokens)
                sentence_boundaries.append(len(tokens))

            # Create chunks
            chunk_start = 0
            while chunk_start &lt; len(tokens):
                chunk_end = chunk_start + chunk_size

                # Find the last complete sentence that fits in the chunk
                sentence_end = next((i for i in sentence_boundaries if i &gt; chunk_end), len(tokens))
                chunk_end = min(chunk_end, sentence_end)

                # Create the chunk
                chunk_tokens = tokens[chunk_start:chunk_end]

                # Check if the chunk meets the minimum size requirement
                if len(chunk_tokens) &gt;= min_chunk_size:
                    # Create a new document object for this chunk
                    chunk_doc = {
                        'id_': str(uuid.uuid4()),
                        'chunk': chunk_tokens,
                        'original_text': self.tokenizer.decode(chunk_tokens),
                        'chunk_index': len(chunked_documents),
                        'parent_id': doc['id_'],
                        'chunk_token_count': len(chunk_tokens)
                    }

                    # Copy all other fields from the original document
                    for key, value in doc.items():
                        if key != 'text' and key not in chunk_doc:
                            chunk_doc[key] = value

                    chunked_documents.append(chunk_doc)

                # Move to the next chunk start, considering overlap
                chunk_start = max(chunk_start + chunk_size - overlap, chunk_end - overlap)

        return chunked_documents

# main.ipynb 
# Initialize Embedding Model
HUGGINGFACE_EMBEDDING_MODEL = os.environ.get('HUGGINGFACE_EMBEDDING_MODEL')
embedder=EmbeddingModel(model_name=HUGGINGFACE_EMBEDDING_MODEL)

# Initialize Chunker
chunker=Chunker(embedder.tokenizer)
<p>La clase <code>Chunker</code> incorpora el tokenizador del modelo de incrustación para codificar y decodificar texto. Ahora construiremos fragmentos de 512 tokens cada uno, con una superposición de 20 tokens. Para ello, dividiremos el texto en frases, tokenizaremos esas frases y luego agregaremos las frases tokenizadas a nuestro fragmento actual hasta que no podamos agregar más sin superar nuestro límite de tokens.</p><p>Finalmente, decodifica las frases de nuevo al texto original para incrustarlas, almacenándola en un campo llamado <code>original_text</code>. Los chunks se almacenan en un campo llamado <code>chunk</code>. Para reducir el ruido (es decir, documentos inútiles), descartaremos cualquier documento de menos de 50 tokens de longitud.</p><p>Vamos a repasarla por nuestros documentos:</p>chunked_documents=chunker.sentence_wise_tokenized_chunk_documents(documents, chunk_size=512)
<p>Y que me devolvan fragmentos de texto que se parezcan a esto:</p>print(chunked_documents[4]['original_text'])

[CLS] the aggregate market value of the ordinary shares held by non - affiliates of the registrant, 
based on the closing price of the shares of ordinary shares on the new york stock exchange on 
october 31, 2022 ( the last business day of the registrant 's second fiscal quarter ), was 
approximately $ 6. 1 billion. [SEP] [CLS] as of may 31, 2023, the registrant had 97, 390, 886 
ordinary shares, par value €0. 01 per share, outstanding. [SEP] [CLS] documents incorporated by 
reference portions of the registrant 's definitive proxy statement relating to the registrant 's 2
023 annual general meeting of shareholders are incorporated by reference into part iii of this annual 
...
...
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Volver arriba</a></p><h3>Inclusión y generación de metadatos</h3><p>Dividimos nuestros documentos. Ahora es el momento de enriquecer los datos. Quiero generar o extraer metadatos adicionales. Estos metadatos adicionales pueden emplear para influir y mejorar el rendimiento en las búsquedas.</p><p>Definiremos una clase <code>DocumentEnricher</code> , cuyo papel es incluir una lista de documentos (diccionarios de Python) y una lista de funciones del procesador. Estas funciones se ejecutarán sobre la columna <code>original_text</code> de los documentos y almacenarán sus salidas en nuevos campos.</p><p>Primero, extraemos las frases clave usando <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/nltk_processor.py">TextRank</a>. TextRank es un algoritmo basado en gráficos que extrae frases clave y oraciones del texto clasificando su importancia en función de las relaciones entre palabras.</p><p>A continuación, <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/llm.py">generaremos potential_questions usando GPT-4o</a>.</p><p>Finalmente, <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/entity_extractor.py">extraeremos entidades</a> usando <a href="https://spacy.io/">Spacy</a>.</p><p>Dado que el código de cada uno de estos es bastante extenso y complejo, me abstendré de reproducirlo aquí. Si te interesa, los archivos están marcados en los ejemplos de código que aparecen a continuación.</p><p>Vamos a ejecutar el enriquecimiento de datos:</p># documentenricher.py
from tqdm import tqdm

class DocumentEnricher:

    def __init__(self):
        pass 

    def enrich_document(self, documents, processors, text_col='text'):
        for doc in tqdm(documents, desc="Enriching documents using processors: "+str(processors)): 
            for (processor, field) in processors: 
                metadata=processor(doc[text_col])
                if isinstance(metadata, list):
                    metadata='\n'.join(metadata)
                doc.update({field: metadata})
 
# main.ipynb
# Initialize processor classes 
nltkprocessor=NLTKProcessor() // nltk_processor.py
entity_extractor=EntityExtractor() // entity_extractor.py
gpt4o = LLMProcessor(model='gpt-4o') // llm.py

# Initialize LLM
documentenricher=DocumentEnricher()

# Create new fields in the documents - These are the outputs of the processor functions.
processors=[
    (nltkprocessor.textrank_phrases, "keyphrases"),
    (gpt4o.generate_questions, "potential_questions"),
    (entity_extractor.extract_entities, "entities")
    ]

# .enrich_document() will modify chunked_docs in place. 
# To view the results, we'll print chunked_docs in the next few cells!
documentenricher.enrich_document(chunked_docs, text_col='original_text', processors=processors)
<p>Y echa un vistazo a los resultados:</p><h4>Frases clave extraídas por TextRank</h4><p>Estas frases clave son un sustituto de los temas centrales del fragmento. Si una consulta tiene que ver con ciberseguridad, el puntaje de este segmento se incrementará.</p>print(chunked_documents[25]['keyphrases'])

'elastic agent stop', 'agent stop malware', 
'stop malware ransomware', 'malware ransomware environment', 
'ransomware environment wide', 'environment wide visibility', 
'wide visibility threat', 'visibility threat detection', 
'sep cl key', 'cl key feature'
<h4>Posibles preguntas generadas por GPT-4o</h4><p>Estas posibles preguntas pueden coincidir directamente con las consultas de los usuarios, ofreciendo un aumento en el puntaje. Pedimos a GPT-4o que genere preguntas que pueden responder usando la información encontrada en el fragmento actual.</p>print(chunked_documents[25]['potential_questions'])

1. What are the primary functions that Elastic Agent provides in terms of cybersecurity?
2. Describe how Logstash contributes to data management within an IT environment.
3. List and explain any key features of Logstash mentioned in the document.
4. How does Elastic Agent enhance environment-wide visibility in threat detection?
5. What capabilities does Logstash offer for handling data beyond simple collection?
6. In what ways does the document suggest that Elastic Agent stops malware and ransomware?
7. Can you identify any relationships between the functionalities of Elastic Agent and Logstash in an integrated environment?
8. What implications might the advanced threat detection capabilities of Elastic Agent have for organizational security policies?
9. Compare and contrast the roles of Elastic Agent and Logstash based on their described functions.
10. How might the centralized collection ability of Logstash support the threat detection capabilities of Elastic Agent?
<h4>Entidades extraídas por Spacy</h4><p>Estas entidades cumplen un propósito similar al de las frases clave, pero capturan los nombres de organizaciones e individuos, que la extracción de frases clave puede pasar por alto.</p>print(chunked_documents[29]['entities'])

'appdynamics', 'apm data', 'azure sentinel', 
'microsoft', 'mcafee', 'broadcom', 'cisco', 
'dynatrace', 'coveo', 'lucidworks'
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Volver arriba</a></p><h3>Incrustaciones compuestas multicampo</h3><p>Ahora que enriquecimos nuestros documentos con metadatos adicionales, podemos aprovechar esta información para crear incrustaciones más robustas y conscientes del contexto.</p><p>Repasemos nuestro punto actual en el proceso. Tenemos cuatro campos de interés en cada documento.</p>{
    "chunk": "...",
    "keyphrases": "...", 
    "potential_questions": "...", 
    "entities": "..." 
}
<p>Cada campo representa una perspectiva diferente sobre el contexto del documento, lo que puede destacar un área clave en la que el LLM debe centrar.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt84cb328fce6aae23/6a170b42964cea3e4408bbc4/aea1f513009a0c7c8545a79fad8f072a5bcae24c-1440x1067.jpg" alt="Pipeline de Enriquecimiento de Metadatos en RAG" /><p>El plan es incrustar cada uno de estos campos y luego crear una suma ponderada de las incrustaciones, conocida como Incrustación Compuesta.</p><p>Con suerte, esta Incrustación Compuesta permitirá que el sistema sea más consciente del contexto, además de introducir otro hiperparámetro ajustable que controla el comportamiento de búsqueda.</p><p>Primero, embebamos cada campo y actualicemos cada documento en su lugar, usando nuestro modelo de incrustación definido localmente importado al inicio del cuaderno main.ipynb.</p># EmbeddingModel defined in embedding_model.py
embedder=EmbeddingModel(model_name=HUGGINGFACE_EMBEDDING_MODEL)

cols_to_embed=['keyphrases', 'potential_questions', 'entities']

embedding_cols=[]
for col in cols_to_embed:
    # Works on text input
    embedding_col=embedder.embed_documents_text_wise(chunked_documents, text_field=col)
    embedding_cols.append(embedding_col)
# Works on token input
embedding_col=embedder.embed_documents_token_wise(chunked_documents, token_field="chunk")
embedding_cols.append(embedding_col)
<p>Cada función de incrustación devuelve el campo de la incrustación, que es simplemente el campo de entrada original con un <code>_embedding</code> postfijo.</p><p>Ahora definamos las ponderaciones de nuestra incrustación compuesta:</p>embedding_cols=[
                'keyphrases_embedding',
                'potential_questions_embedding',
                'entities_embedding',
                'chunk_embedding']
combination_weights=[
                    0.1,
                    0.15,
                    0.05,
                    0.7
                ]
<p>Las ponderaciones te permiten asignar prioridades a cada componente, basándote en tu caso de uso y la calidad de tus datos. Intuitivamente, el tamaño de estos pesos depende del valor semántico de cada componente. Como el texto en fragmentos en sí es, con diferencia, el más rico, asigno un peso del 70%. Como las entidades son las más pequeñas, siendo solo una lista de nombres de organizaciones o personas, le asigno un peso del 5%. La configuración precisa de estos valores debe determinar empíricamente, caso de uso por caso.</p><p>Finalmente, escribamos una función para aplicar los pesos y creemos nuestra incrustación compuesta. También eliminaremos todas las incrustaciones de componentes para ahorrar espacio.</p>from tqdm import tqdm 
def combine_embeddings(objects, embedding_cols, combination_weights, primary_embedding='primary_embedding'):
    # Ensure the number of weights matches the number of embedding columns
    assert len(embedding_cols) == len(combination_weights), "Number of embedding columns must match number of weights"
    
    # Normalize weights to sum to 1
    weights = np.array(combination_weights) / np.sum(combination_weights)
    
    for obj in tqdm(objects, desc="Combining embeddings"):
        # Initialize the combined embedding
        combined = np.zeros_like(obj[embedding_cols[0]])
        
        # Compute the weighted sum
        for col, weight in zip(embedding_cols, weights):
            combined += weight * np.array(obj[col])
        
        # Add the new combined embedding to the object
        obj.update({primary_embedding:combined.tolist()})
        
        # Remove the original embedding columns
        for col in embedding_cols:
            obj.pop(col, None)

combine_embeddings(chunked_documents, embedding_cols, combination_weights)
<p>Con esto, completamos el procesamiento de los documentos. Ahora tenemos una lista de objetos documento que se ven así:</p>{ 'id_': '7fe71686-5cd0-4831-9e79-998c6dbeae0c', 'chunk': [2312, 14613, ...], 'original_text': 'if an emerging growth company, indicate by check mark if the registrant has elected not to use the extended ...', 'chunk_index': 3, 'chunk_token_count': 399, 'metadata': {'page_label': '3', 'file_name': 'Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf', ... 'keyphrases': 'sep cl unk\ncheck mark registrant\ncl unk indicate\nunk indicate check\nindicate check mark\nprincipal executive office\naccelerate filer unk\ncompany unk emerge\nunk emerge growth\nemerge growth company', 'potential_questions': '1. What are the different types of registrant statuses mentioned in the document?\n2. Under what section of the Sarbanes-Oxley Act must registrants file a report on the effectiveness of their internal ...', 'entities': 'the effe ctiveness of\nsection 13\nSEP\nUNK\nsection 21e\n1934\n1933\nu. s. c.\nsection 404\nsection 12\nal', 'primary_embedding': [-0.3946287803351879, -0.17586839850991964, ...] }
<h4>Indexación a Elastic</h4><p>Subamos nuestros documentos en masa a Elastic Search. Para este propósito, hace mucho tiempo definí un conjunto de funciones auxiliares elásticas en <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/elastic_helpers.py"><code>elastic_helpers.py</code></a>. Es un código muy largo, así que vamos a centrarnos en las llamadas a funciones.</p><p><code>es_bulk_indexer.bulk_upload_documents</code> funciona con cualquier lista de objetos de diccionario, aprovechando los convenientes mapeos dinámicos de Elasticsearch.</p># Initialize Elasticsearch
ELASTIC_CLOUD_ID = os.environ.get('ELASTIC_CLOUD_ID')
ELASTIC_USERNAME = os.environ.get('ELASTIC_USERNAME')
ELASTIC_PASSWORD = os.environ.get('ELASTIC_PASSWORD')
ELASTIC_CLOUD_AUTH = (ELASTIC_USERNAME, ELASTIC_PASSWORD)
es_bulk_indexer = ESBulkIndexer(cloud_id=ELASTIC_CLOUD_ID, credentials=ELASTIC_CLOUD_AUTH)
es_query_maker = ESQueryMaker(cloud_id=ELASTIC_CLOUD_ID, credentials=ELASTIC_CLOUD_AUTH)

# Define Index Name
index_name=os.environ.get('ELASTIC_INDEX_NAME')


# Create index and bulk upload 
index_exists = es_bulk_indexer.check_index_existence(index_name=index_name)
if not index_exists:
    logger.info(f"Creating new index: {index_name}")
    es_bulk_indexer.create_es_index(es_configuration=BASIC_CONFIG, index_name=index_name)

success_count = es_bulk_indexer.bulk_upload_documents(
    index_name=index_name, 
    documents=chunked_documents, 
    id_col='id_',
    batch_size=32
)
<p>Ve a Kibana y verifica que todos los documentos fueron indexados. Deberían ser 224. ¡No está mal para un documento tan extenso!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8efeface6effe01d/6a170b447d8d67652870e72a/1b3b07f6b98ceb65f6594ce4be83c5b0ed7e7cf9-1440x1380.jpg" alt="Índice Kibana" /><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Volver arriba</a></p><h2>Ruptura de gato</h2><p>Vamos a hacer una pausa, el artículo es un poco pesado, lo sé. Mira a mi gato:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc1db5595f71c12ff/6a170b450e2e49940241a0fe/baca4eb52b801b21ced97352cc55462f0a12d6b0-969x996.jpg" alt="Gasoducto Han" /><p>Adorable. El sombrero desapareció y sospecho que lo robó y escondió en algún sitio :(</p><p>¡Enhorabuena por llegar hasta aquí :)</p><p>¡Únete a mí en <a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">la Parte 2</a> para probar y evaluar nuestra cadena RAG!</p><h2>Apéndice</h2><h3>Definiciones</h3><p><strong>1. Fragmentación de frases</strong></p><ul><li><p>Una técnica de preprocesamiento empleada en sistemas RAG para dividir el texto en unidades más pequeñas y significativas.</p></li><li><p><em>Proceso:</em> </p><ol><li><p>Entrada: Gran bloque de texto (por ejemplo, documento, párrafo)</p></li><li><p>Salida: Segmentos de texto más pequeños (normalmente oraciones o pequeños grupos de oraciones)</p></li></ol></li><li><p><em>Propósito:</em> </p><ul><li><p>Crea segmentos de texto granulares y específicos de contexto</p></li><li><p>Permite una indexación y recuperación más precisas</p></li><li><p>Mejora la relevancia de la información recuperada en sistemas RAG</p></li></ul></li><li><p><em>Características:</em> </p><ul><li><p>Los segmentos tienen significado semántico</p></li><li><p>Puede ser indexado y recuperado de forma independiente</p></li><li><p>A menudo preserva cierto contexto para garantizar la comprensibilidad independiente</p></li></ul></li><li><p><em>Beneficios:</em> </p><ul><li><p>Mejora la precisión de la recuperación</p></li><li><p>Permite una ampliación más enfocada en las canalizaciones RAG</p></li></ul></li></ul><p><strong>2. HyDE (Embedding de Documentos Hipotéticos)</strong></p><ul><li><p>Una técnica que emplea un LLM para generar un documento hipotético para la expansión de consultas en sistemas RAG.</p></li><li><p><em>Proceso:</em>  </p><ol><li><p>Consulta de entrada a un LLM</p></li><li><p>El LLM genera un documento hipotético que responde a la consulta</p></li><li><p>Incrustar el documento generado</p></li><li><p>Emplea la incrustación para búsqueda vectorial</p></li></ol></li><li><p><em>Diferencia clave:</em> </p><ul><li><p>RAG tradicional: Empareja la consulta con documentos</p></li><li><p>HyDE: Empareja documentos con documentos</p></li></ul></li><li><p><em>Propósito:</em> </p><ul><li><p>Mejorar el rendimiento de la recuperación, especialmente para consultas complejas o ambiguas</p></li><li><p>Captura un contexto semántico más rico que una consulta corta</p></li></ul></li><li><p><em>Beneficios:</em> </p><ul><li><p>Aprovecha el conocimiento de LLM para ampliar las consultas</p></li><li><p>Puede mejorar potencialmente la relevancia de los documentos recuperados</p></li></ul></li><li><p><em>Desafíos:</em> </p><ul><li><p>Requiere inferencia adicional de LLM, aumentando la latencia y el costo</p></li><li><p>El rendimiento depende de la calidad del documento hipotético generado</p></li></ul></li></ul><p><strong>3. Empaquetado inverso</strong></p><ul><li><p>Una técnica empleada en sistemas RAG para reordenar los resultados de búsqueda antes de pasarlos al LLM.</p></li><li><p><em>Proceso:</em> </p><ol><li><p>El motor de búsqueda (por ejemplo, Elasticsearch) devuelve los documentos en orden descendente de relevancia.</p></li><li><p>El orden se invierte, colocando el documento más relevante al final.</p></li></ol></li><li><p><em>Propósito:</em> </p><ul><li><p>Aprovecha el sesgo de actualidad de los LLM, que tienden a centrar más en la información más reciente en su contexto.</p></li><li><p>Garantiza que la información más relevante esté "más fresca" en la ventana de contexto del LLM.</p></li></ul></li><li><p><em>Ejemplo:</em> Orden original: [Más relevante, Segundo más, Tercero más, ...] Orden invertido: [..., Tercero más, segundo más relevante]</p></li></ul><p><strong>4. Clasificación de consultas</strong></p><ul><li><p>Una técnica para optimizar la eficiencia del sistema RAG determinando si una consulta requiere RAG o puede ser respondida directamente por el LLM.</p></li><li><p><em>Proceso:</em> </p><ol><li><p>Desarrollar un conjunto de datos personalizado específico para el LLM en uso</p></li><li><p>Capacitar un modelo de clasificación especializado</p></li><li><p>Emplea el modelo para categorizar las consultas entrantes</p></li></ol></li><li><p><em>Propósito:</em> </p><ul><li><p>Mejorar la eficiencia del sistema evitando el procesamiento innecesario de RAG</p></li><li><p>Consulta directa al mecanismo de respuesta más adecuado</p></li></ul></li><li><p><em>Requisitos:</em> </p><ul><li><p>Conjunto de datos y modelo específicos de LLM</p></li><li><p>Refinamiento continuo para mantener la precisión</p></li></ul></li><li><p><em>Beneficios:</em> </p><ul><li><p>Reduce la sobrecarga computacional para consultas simples</p></li><li><p>Potencialmente mejora el tiempo de respuesta para consultas que no son RAG</p></li></ul></li></ul><p><strong>5. Resumen</strong></p><ul><li><p>Una técnica para condensar documentos recuperados en sistemas RAG.</p></li><li><p><em>Proceso:</em> </p><ol><li><p>Recuperar documentos relevantes</p></li><li><p>Genera resúmenes concisos de cada documento</p></li><li><p>Emplea resúmenes en lugar de documentos completos en la tubería RAG</p></li></ol></li><li><p><em>Propósito:</em> </p><ul><li><p>Mejora el rendimiento de RAG centrándote en la información esencial</p></li><li><p>Reducir el ruido y las interferencias de contenido menos relevante</p></li></ul></li><li><p><em>Beneficios:</em> </p><ul><li><p>Potencialmente mejora la relevancia de las respuestas de los LLM</p></li><li><p>Permite incluir más documentos dentro de los límites del contexto</p></li></ul></li><li><p><em>Desafíos:</em> </p><ul><li><p>Riesgo de perder detalles importantes en la resumen</p></li><li><p>Sobrecarga computacional adicional para la generación de resúmenes</p></li></ul></li></ul><p><strong>6. Inclusión de metadatos</strong></p><ul><li><p>Una técnica para enriquecer documentos con información contextual adicional.</p></li><li><p><em>Tipos de metadatos:</em>  </p><ul><li><p>Frases clave</p></li><li><p>Títulos</p></li><li><p>Fechas</p></li><li><p>Detalles de la autoría</p></li><li><p>Resumen</p></li></ul></li><li><p><em>Propósito:</em> </p><ul><li><p>Aumentar la información contextual disponible para el sistema RAG</p></li><li><p>Proporcionar a los LLMs una comprensión más clara del contenido y la relevancia del documento</p></li></ul></li><li><p><em>Beneficios:</em> </p><ul><li><p>Potencialmente mejora la precisión de la recuperación</p></li><li><p>Mejora la capacidad del LLM para evaluar la utilidad de los documentos</p></li></ul></li><li><p><em>Implementación:</em> </p><ul><li><p>Se puede hacer durante el preprocesamiento de documentos</p></li><li><p>Puede requerir pasos adicionales de extracción de datos o generación</p></li></ul></li></ul><p><strong>7. Incrustaciones compuestas multicampo</strong></p><ul><li><p>Una técnica avanzada de incrustación para sistemas RAG que crea incrustaciones separadas para diferentes componentes del documento.</p></li><li><p><em>Proceso:</em> </p><ol><li><p>Identificar campos relevantes (por ejemplo, título, frases clave, resúmenes, contenido principal)</p></li><li><p>Genera incrustaciones separadas para cada campo</p></li><li><p>Combina o almacena estos embeddings para su uso en la recuperación</p></li></ol></li><li><p><em>Diferencia con el enfoque estándar:</em> </p><ul><li><p>Tradicional: Embedding único para todo el documento</p></li><li><p>Compuesto: Múltiples incrustaciones para diferentes aspectos del documento</p></li></ul></li><li><p><em>Propósito:</em> </p><ul><li><p>Crear representaciones documentales más matizadas y conscientes del contexto</p></li><li><p>Captura información de una mayor variedad de fuentes dentro de un documento</p></li></ul></li><li><p><em>Beneficios:</em> </p><ul><li><p>Potencialmente mejora el rendimiento en consultas ambiguas o multifacéticas</p></li><li><p>Permite una ponderación más flexible de los diferentes aspectos del documento en la recuperación</p></li></ul></li><li><p><em>Desafíos:</em> </p><ul><li><p>Mayor complejidad en los procesos de incrustación, almacenamiento y recuperación</p></li><li><p>Puede requerir algoritmos de emparejamiento más sofisticados</p></li></ul></li></ul><p><strong>8. Enriquecimiento de consultas</strong></p><ul><li><p>Una técnica para ampliar la consulta original con términos relacionados para mejorar la cobertura de búsqueda.</p></li><li><p><em>Proceso:</em> </p><ol><li><p>Analizar la consulta original</p></li><li><p>Generar sinónimos y frases semánticamente relacionadas</p></li><li><p>Complementa la consulta con estos términos adicionales</p></li></ol></li><li><p><em>Propósito:</em> </p><ul><li><p>Ampliar el rango de posibles coincidencias en el corpus documental</p></li><li><p>Mejorar el rendimiento de recuperación para consultas con lenguaje específico o técnico</p></li></ul></li><li><p><em>Beneficios:</em> </p><ul><li><p>Potencialmente recupera documentos relevantes que no coinciden exactamente con los términos originales de la consulta</p></li><li><p>Puede ayudar a superar la discrepancia de vocabulario entre consultas y documentos</p></li></ul></li><li><p><em>Desafíos:</em> </p><ul><li><p>Riesgo de deriva de consulta si no se implementa cuidadosamente</p></li><li><p>Puede aumentar la sobrecarga computacional en el proceso de recuperación</p></li></ul></li></ul><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Volver arriba</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Han Xiang Choong]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 14 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch vs. OpenSearch: Comparación del rendimiento de búsqueda vectorial]]></title>
    <description><![CDATA[Elasticsearch es de 2 a 12 veces más rápido que OpenSearch para la búsqueda vectorial]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison#up-to-12x-faster-out-of-the-box">TLDR: Elasticsearch es hasta 12 veces más rápido</a> - En Elastic hemos recibido numerosas solicitudes de nuestra comunidad para aclarar las diferencias de rendimiento entre Elasticsearch y OpenSearch, particularmente en el realm de la búsqueda semántica/búsqueda vectorial, por lo que hemos efectuado esta prueba de rendimiento para proporcionar una comparación clara y basada en datos: sin ambigüedades, solo datos directos para informar a nuestros usuarios. Los resultados muestran que <strong>Elasticsearch es hasta 12 veces más rápido</strong> que OpenSearch para la búsqueda de vectores y, por lo tanto, requiere menos recursos computacionales. Esto refleja el enfoque de Elastic en consolidar Lucene como la mejor base de datos vectorial para casos de uso de búsqueda y recuperación.</p><p>La búsqueda vectorial está revolucionando la forma en que hacemos búsquedas de similitud, particularmente en campos como la IA y el machine learning. Con la creciente adopción de modelos de incrustación de vectores, la capacidad de búsqueda eficiente a través de millones de vectores de alta dimensionalidad se vuelve crítica.</p><p>Cuando se trata de habilitar bases de datos vectoriales, Elastic y OpenSearch han adoptado enfoques notablemente diferentes. Elastic ha invertido mucho en la optimización de Apache Lucene junto con Elasticsearch para elevarlos como la opción de primer nivel para las aplicaciones de búsqueda vectorial. Por el contrario, OpenSearch ha ampliado su enfoque, integrando otras implementaciones de búsqueda vectorial y explorando más allá del alcance de Lucene. Nuestro enfoque en Lucene es estratégico, lo que nos permite brindar soporte sumamente integrado en nuestra versión de Elasticsearch, resultando en un conjunto de características mejorado en el que cada componente complementa y amplifica las capacidades del otro.</p><p>Este blog presenta una comparación detallada entre Elasticsearch 8.14 y OpenSearch 2.14, considerando diferentes configuraciones y motores vectoriales. En este análisis de rendimiento, Elasticsearch demostró ser la plataforma superior para las operaciones de búsqueda de vectores. Incluso las próximas <a href="https://www.elastic.co/search-labs/blog/vector-similarity-computations-ludicrous-speed">características</a> ampliarán las diferencias de forma más <a href="https://www.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains">significativa</a>. Cuando se enfrentó a OpenSearch, sobresalió en todas las pistas de referencia, <strong>con un rendimiento de 2 a 12 veces más rápido en promedio</strong>. Esto sucedió en todos los casos que utilizaban cantidades y dimensiones vectoriales variables, como <code>so_vector</code> (2M vectores, 768D), <code>openai_vector</code> (2.5M vectores, 1536D) y <code>dense_vector</code> (10M vectores, 96D), todos disponibles en <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance">este repositorio</a> junto con los scripts de Terraform para provisionar toda la infraestructura requerida en Google Cloud y los manifiestos de Kubernetes para ejecutar las pruebas.</p><p>Los resultados detallados en este blog complementan los resultados de un <a href="https://www.elastic.co/blog/elasticsearch-opensearch-performance-gap">estudio previamente publicado y validado por terceros</a> que muestra que Elasticsearch es 40%–140% más rápido que OpenSearch para las operaciones de análisis de búsqueda más comunes: consulta de texto, clasificación, rango, histograma de fechas y filtrado de términos. Ahora podemos agregar otro diferenciador: la búsqueda vectorial.</p><h2>Hasta 12 veces más rápido desde el primer momento</h2><p>Nuestros puntos de referencia enfocados en los cuatro conjuntos de datos vectoriales involucraron tanto búsquedas de KNN aproximados como de KNN exactos, considerando diferentes tamaños, dimensiones y configuraciones, totalizando <code>40.189.820</code> solicitudes de búsqueda no almacenadas en caché. Los resultados: <strong>Elasticsearch es hasta 12 veces más rápido</strong> que OpenSearch para la búsqueda vectorial y, por lo tanto, requiere menos recursos computacionales.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt34b83c6eba3bcb6e/6a17d727dbb4ff18d6fb54fb/cdb26e91f085b90e9b12aeb8fee53b04d365ecae-1440x1156.webp" alt="promedio de P90" /><p>Figura 1: Tareas agrupadas para ANN y KNN exacto en diferentes combinaciones en Elasticsearch y OpenSearch.</p><p>Los grupos como <code>knn-10-100</code> implican una búsqueda KNN con  y . En la búsqueda vectorial HNSW,  determina el número de vecinos más cercanos a recuperar para un vector de consulta. Especifica cuántos vectores similares se deben encontrar como resultado.  establece el número de vectores candidatos a recuperar en cada segmento. Más candidatos pueden mejorar la precisión, pero requieren mayores recursos computacionales.</p><p>También probamos con diferentes técnicas de cuantización y aprovechamos las optimizaciones específicas del motor; los resultados detallados para cada pista, tarea y motor vectorial están disponibles a continuación.</p><h2>KNN exacto y KNN aproximado</h2><p>Al tratar con conjuntos de datos y casos de uso variados, el enfoque correcto para la búsqueda vectorial será diferente. En este blog, todas las tareas indicadas como <code>knn-*</code> como <code>knn-10-100</code> utilizan <strong>KNN aproximado</strong> y <code>script-score-*</code> se refieren a <strong>KNN exacto</strong>, pero ¿en qué se diferencian y por qué son importantes?</p><p>En definitiva, si estás manejando conjuntos de datos más sustanciales, el método preferido es Approximate K-Nearest Neighbor (ANN) debido a su escalabilidad superior. Para conjuntos de datos más modestos que pueden requerir un proceso de filtración, el método Exact KNN es ideal.</p><p>El KNN exacto utiliza un método de fuerza bruta, calculando la distancia entre un vector y todos los demás vectores en el conjunto de datos. Luego clasifica estas distancias para encontrar los  vecinos más cercanos. Si bien este método garantiza una coincidencia exacta, enfrenta desafíos de escalabilidad para conjuntos de datos grandes y de alta dimensionalidad. Sin embargo, hay muchos casos en los que se necesita un KNN exacto:</p><ul><li><p><strong>Recalificación</strong>: En escenarios que involucran búsquedas léxicas o semánticas seguidas de recalificación basada en vectores, el KNN exacto es esencial. Por ejemplo, en un motor de búsqueda de productos, los resultados de búsqueda iniciales se pueden filtrar en función de consultas textuales (por ejemplo, palabras clave, categorías) y luego se emplean vectores asociados con los elementos filtrados para una evaluación de similitud más precisa.</p></li><li><p><strong>Personalización</strong>: Al tratar con un gran número de usuarios, cada uno representado por un número relativamente pequeño (como 1 millón) de vectores distintos, la clasificación del índice por metadatos específicos del usuario (por ejemplo, user_id) y el cálculo de puntajes mediante fuerza bruta con vectores se vuelve eficiente. Este enfoque permite recomendaciones personalizadas o la entrega de contenido basadas en comparaciones vectoriales precisas adaptadas a las preferencias individuales del usuario.</p></li></ul><p>Por lo tanto, Exact KNN garantiza que la clasificación final y las recomendaciones basadas en la similitud de vectores sean precisas y estén adaptadas a las preferencias del usuario.</p><p>Por otro lado, el KNN aproximado (o ANN) emplea métodos para que la búsqueda de datos sea más rápida y eficaz que el KNN exacto, especialmente en conjuntos de datos grandes y de alta dimensionalidad. En lugar de un enfoque de fuerza bruta, que mide la distancia más cercana exacta entre una consulta y todos los puntos, lo que plantea problemas de cálculo y escalado, el ANN emplea ciertas técnicas para reestructurar de forma eficiente los índices y las dimensiones de los vectores buscables en el conjunto de datos. Aunque esto puede provocar una ligera imprecisión, aumenta considerablemente la velocidad del proceso de búsqueda, lo que lo convierte en una alternativa eficaz para tratar con grandes conjuntos de datos.</p><p>En este blog, todas las tareas indicadas como <code>knn-*</code> como <code>knn-10-100</code> usan <strong>KNN aproximado</strong> y <code>script-score-*</code> se refieren a <strong>KNN exacto</strong>.</p><h2>Metodología de prueba</h2><p>Si bien Elasticsearch y OpenSearch son similares en términos de API para las operaciones de búsqueda BM25, ya que este último es una bifurcación del primero, no ocurre lo mismo con la búsqueda vectorial, que se introdujo después de la bifurcación. OpenSearch adoptó un enfoque diferente al de Elasticsearch en lo que respecta a los algoritmos, al introducir otros dos motores — <code>nmslib</code> y <code>faiss</code> — además de <code>lucene</code>, cada uno con sus configuraciones y limitaciones específicas (por ejemplo, <code>nmslib</code> en OpenSearch no permite filtros, una característica esencial para muchos casos de uso).</p><p>Los tres motores utilizan el algoritmo Hierarchical Navigable Small World (HNSW), que es eficiente para la búsqueda aproximada de vecinos más cercanos y especialmente potente al trabajar con datos de alta dimensionalidad. Es importante señalar que <code>faiss</code> también admite un segundo algoritmo, <code>ivf</code>, pero dado que requiere entrenamiento previo en el conjunto de datos, nos centraremos únicamente en HNSW. La idea núcleo de HNSW es organizar los datos en varias capas de grafos conectados, donde cada capa representa una granularidad diferente del conjunto de datos. La búsqueda comienza en la capa superior con la vista más burda y progresa hacia capas cada vez más finas hasta llegar al nivel base.</p><p>Ambos motores de búsqueda se probaron en condiciones idénticas en un entorno controlado para asegurar la imparcialidad. El método aplicado es similar a <a href="https://www.elastic.co/blog/elasticsearch-opensearch-performance-gap#testing-methodology">esta comparación de rendimiento publicada anteriormente</a>, con grupos de nodo dedicados para Elasticsearch, OpenSearch y Rally. El <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/main/terraform/main.tf">script de terraform</a> está disponible (junto con todas las fuentes) para provisionar un clúster de Kubernetes con:</p><ul><li><p>1 grupo de nodo para Elasticsearch con 3 <code>e2-standard-32</code> máquinas (128 GB de RAM y 32 CPU)</p></li><li><p>Grupo de 1 Node para OpenSearch con 3 máquinas <code>e2-standard-32</code> (128 GB de RAM y 32 CPU)</p></li><li><p>Grupo de 1 Node para Rally con 2 máquinas <code>t2a-standard-16</code> (64 GB de RAM y 16 CPU)</p></li></ul><p>Cada "pista" (o prueba) se ejecutó 10 veces para cada configuración, que incluyó diferentes motores, diferentes configuraciones y diferentes tipos de vectores. Las pistas tienen tareas que se repiten entre 1000 y 10 000 veces, dependiendo de la pista. Si una de las tareas de una pista fallaba, por ejemplo, debido a un tiempo de espera de red, todas las tareas se descartaban, por lo que todos los resultados representan pistas que comenzaron y terminaron sin problemas. Todos los resultados de las pruebas se validan estadísticamente, lo que garantiza que las mejoras no sean una coincidencia.</p><h2>Resultados detallados</h2><p>¿Por qué comparar usando el percentil 99 y no el promedio de latencia? Consideremos un ejemplo hipotético de los precios promedio de las viviendas en un barrio determinado. El precio promedio puede indicar una zona cara, pero en una inspección más cercana, puede resultar que la mayoría de las viviendas estén valoradas mucho más bajo, con solo unas pocas propiedades de lujo inflando la cifra promedio. Esto ilustra cómo el precio promedio puede no representar con precisión el espectro completo de valores de las viviendas en esa zona. Esto es similar a examinar los tiempos de respuesta, en los que el promedio puede ocultar problemas críticos.</p><h4>Tareas</h4><ul><li><p>KNN aproximado con k:10 n:50</p></li><li><p>KNN aproximado con k:10 n:100</p></li><li><p>KNN aproximado con k:100 n:1000</p></li><li><p>KNN aproximado con k:10 n:50 y filtros de palabras clave</p></li><li><p>KNN aproximado con k:10 n:100 y filtros de palabras clave</p></li><li><p>KNN aproximado con k:100 n:1000 y filtros de palabras clave</p></li><li><p>KNN aproximado con k:10 n:100 en combinación con indexación</p></li><li><p>KNN exacto (puntaje del script)</p></li></ul><h4>Motores vectoriales</h4><ul><li><p><code>lucene</code> en Elasticsearch y OpenSearch, ambos en la versión 9.10</p></li><li><p><code>faiss</code> en OpenSearch</p></li><li><p><code>nmslib</code> en OpenSearch</p></li></ul><h4>Tipos de vectores</h4><ul><li><p><code>hnsw</code> en Elasticsearch y OpenSearch</p></li><li><p><code>int8_hnsw</code> en Elasticsearch (HNSW con cuantificación automática de 8 bits: <a href="https://www.elastic.co/search-labs/blog/evaluating-scalar-quantization">enlace</a>)</p></li><li><p><code>sq_fp16 hnsw </code>en OpenSearch (HNSW con cuantificación automática de 16 bits: <a href="https://opensearch.org/docs/2.14/search-plugins/knn/knn-vector-quantization#faiss-16-bit-scalar-quantization">enlace</a>)</p></li></ul><h4>Búsqueda de segmentos concurrentes y lista para usar</h4><p>Como probablemente sabes, Lucene es una biblioteca de motor de búsqueda de texto de alto rendimiento escrita en Java que sirve como estructura para muchas plataformas de búsqueda como Elasticsearch, OpenSearch y Solr. Básicamente, Lucene organiza los datos en segmentos, que son esencialmente índices autónomos que permiten a Lucene ejecutar búsquedas de manera más eficiente. Entonces, cuando emites una búsqueda a cualquier motor de búsqueda basado en Lucene, tu búsqueda terminará siendo ejecutada en esos segmentos, ya sea secuencialmente o en paralelo.</p><p>OpenSearch introdujo la búsqueda de segmentos concurrentes como una opción adicional y no la utiliza por defecto; debes habilitarla mediante una configuración especial del índice <code>index.search.concurrent_segment_search.enabled</code> como se detalla <a href="https://opensearch.org/docs/latest/search-plugins/concurrent-segment-search/">aquí</a>, con algunas <a href="https://opensearch.org/docs/latest/search-plugins/concurrent-segment-search/#other-considerations">limitaciones</a>.</p><p>Elasticsearch, por otro lado, hace búsquedas en segmentos de forma concurrente <a href="https://github.com/elastic/elasticsearch/pull/101230">listas para usar</a>, por lo que las comparaciones que hacemos en este blog tendrán en cuenta, además de los diferentes motores de vectores y tipos de vectores, también las diferentes configuraciones:</p><ul><li><p>Elasticsearch ootb: Elasticsearch listo para usar, con búsqueda concurrente por segmentos;</p></li><li><p>OpenSearch ootb: sin búsqueda concurrente de segmentos habilitada;</p></li><li><p>OpenSearch css: con búsqueda concurrente de segmentos habilitada</p></li></ul><p>Comencemos con algunos resultados detallados para cada conjunto de datos vectoriales probado:</p><h2>2,5 millones de vectores, 1536 dimensiones (openai_vector)</h2><p>Comenzando con la ruta más simple, pero también la más grande en términos de dimensiones, <a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector">openai_vector</a>, que utiliza el <a href="https://huggingface.co/datasets/BeIR/nq">conjunto de datos NQ</a> enriquecido con incrustaciones generadas usando el <a href="https://openai.com/blog/new-and-improved-embedding-model">modelo text-embedding-ada-002</a> de OpenAI. Es el más simple ya que solo prueba KNN aproximado y tiene solo 5 tareas. Se prueba de forma independiente (sin indexar) así como junto con la indexación, y utilizando un solo cliente y 8 clientes simultáneos.</p><h3>Tareas</h3><ul><li><p><strong>standalone-search-knn-10-100-multiple-clients</strong>: búsqueda en 2,5 millones de vectores con 8 clientes simultáneamente, k: 10 y n:100</p></li><li><p><strong>standalone-search-knn-100-1000-multiple-clients</strong>: búsqueda en 2,5 millones de vectores con 8 clientes simultáneamente, k: 100 y n:1000</p></li><li><p><strong>standalone-search-knn-10-100-single-client</strong>: búsqueda en 2,5 millones de vectores con un solo cliente, k: 10 y n:100</p></li><li><p><strong>standalone-search-knn-100-1000-single-client</strong>: búsqueda en 2,5 millones de vectores con un solo cliente, k: 100 y n: 1000</p></li><li><p><strong>parallel-documents-indexing-search-knn-10-100</strong>: búsqueda en 2.5 millones de vectores mientras se indexan 100 000 documentos adicionales, k:10 y n:100</p></li></ul><p>El rendimiento promedio del p99 se describe a continuación:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf5791848eb0c12fb/6a17d72925daab9cae08a09e/eea0b2b49c690baada3e09d6968e513bfffe51a9-1440x318.webp" alt="tabla openai_vector" /><p>Aquí observamos que Elasticsearch es de <strong>3x a 8x más rápido</strong> que OpenSearch al realizar la búsqueda vectorial junto con la indexación (por ej. lectura+escritura) con :10 y :100 y de <strong>2x a 3x más rápido</strong> sin indexar para los mismos k y n. Para :100 y :1000 (<em>standalone-search-knn-100-1000-single-client</em> y <em>standalone-search-knn-100-1000-multiple-clients</em> Elasticsearch es de <strong>2x a 7x</strong> más rápido que OpenSearch, en promedio.</p><p>Los resultados detallados muestran los casos exactos y los motores vectoriales comparados:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbe41d3187ced7ec/6a17d72a445de951c44cff4c/a7a761ed631d3e6211beb83d9d93d752d10123c9-1440x1728.webp" alt="openai_vector" /><h4>Recall</h4><p></p><p>knn-recall-10-100</p><p>knn-recall-100-1000</p><p>Elasticsearch-8.14.0@lucene-hnsw</p><p>0.969485</p><p>0.995138</p><p>Elasticsearch-8.14.0@lucene-int8_hnsw</p><p>0.781445</p><p>0.784817</p><p>OpenSearch-2.14.0@lucene-hnsw</p><p>0.96519</p><p>0.995422</p><p>OpenSearch-2.14.0@faiss</p><p>0.984154</p><p>0.98049</p><p>OpenSearch-2.14.0@faiss-sq_fp16</p><p>0.980012</p><p>0.97721</p><p>OpenSearch-2.14.0@nmslib</p><p>0.982532</p><p>0.99832</p><h2>10 millones de vectores, 96 dimensiones (dense_vector)</h2><p>En <a href="https://github.com/elastic/rally-tracks/tree/master/dense_vector">dense_vector</a> con 10M vectores y 96 dimensiones. Se basa en el conjunto de datos de imágenes <a href="https://big-ann-benchmarks.com/">Yandex DEEP1B</a>. El conjunto de datos se crea a partir de los primeros 10 millones de vectores del archivo "sample data" llamado <code>learn.350M.fbin</code>. Las operaciones de búsqueda utilizan vectores de la búsqueda de archivos "query data".<code>public.10K.fbin</code>.</p><p>Tanto Elasticsearch como OpenSearch funcionan muy bien en este conjunto de datos, especialmente después de un <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html">force merge</a>, que generalmente se realiza en índices de solo lectura y es similar a desfragmentar el índice para tener una sola "tabla" en la que realizar la búsqueda.</p><h3>Tareas</h3><p>Cada tarea se prepara para 100 solicitudes y luego se miden 1000 solicitudes</p><ul><li><p><strong>knn-search-10-100</strong>: búsqueda en 10 millones de vectores, k: 10 y n:100</p></li><li><p><strong>knn-search-100-1000</strong>: búsqueda en 10 millones de vectores, k: 100 y n: 1000</p></li><li><p><strong>knn-search-10-100-force-merge</strong>: búsqueda en 10 millones de vectores después de una fusión forzada, k: 10 y n:100</p></li><li><p><strong>knn-search-100-1000-force-merge</strong>: búsqueda en 10 millones de vectores después de una fusión forzada, k: 100 y n:1000</p></li><li><p><strong>knn-search-100-1000-concurrent-with-indexing</strong>: búsqueda en 10 millones de vectores mientras también se actualiza <a href="https://github.com/elastic/rally-tracks/blob/master/dense_vector/challenges/default.json#L76C36-L76C37">el 5 % del conjunto de datos</a>, k: 100 y n: 1000</p></li><li><p><strong>script-score-query</strong>: búsqueda KNN exacta de <a href="https://github.com/elastic/rally-tracks/blob/master/dense_vector/queries.json">2000 vectores específicos</a>.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4629d06af85eb96c/6a17d72c6864a423e7b685dc/174995e0a2156d86359cdb7aa446dfaae6312ea4-1440x316.webp" alt="dense_vector" /><p>Tanto Elasticsearch como OpenSearch tuvieron un buen desempeño para el KNN aproximado. Cuando el índice se fusiona (es decir, tiene un solo segmento) en <em>knn-search-100-1000-force-merge</em> y <em>knn-search-10-100-force-merge</em>, OpenSearch funciona mejor que los demás cuando se usan <code>nmslib</code> y <code>faiss</code>, aunque todos estén alrededor de 15 ms y todos muy cerca.</p><p>Sin embargo, cuando el índice tiene varios segmentos (una situación típica en la que un índice recibe actualizaciones de sus documentos) en <em>knn-search-10-100</em> y <em>knn-search-100-1000</em>, Elasticsearch mantiene la latencia en aproximadamente ~7 ms y ~16 ms, mientras que todos los demás motores de OpenSearch son más lentos.</p><p>También cuando se busca en el índice y se indexa en él al mismo tiempo (<em>knn-search-100-1000-concurrent-with-indexing</em>), Elasticsearch mantiene la latencia por debajo de 15 ms (a 13.8 ms), siendo casi <strong>4x más rápido</strong> que OpenSearch out-of-the-box (49.3 ms) y aún más rápido cuando se habilita la búsqueda concurrente por segmentos (17.9 ms), pero demasiado cerca para ser significativo.</p><p>En cuanto al KNN exacto, la diferencia es mucho mayor: Elasticsearch <strong>es 6 veces más rápido</strong> que OpenSearch (~260 ms vs ~1600 ms).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt254f43bcaa3dbfc2/6a17d72ddbb4ffc780fb54ff/17aec6be31117440bc4d1f99984aed95df1c4f6b-1440x1728.webp" alt="dense_vector" /><h4>Recall</h4><p></p><p>knn-recall-10-100</p><p>knn-recall-100-1000</p><p>Elasticsearch-8.14.0@lucene-hnsw</p><p>0.969843</p><p>0.996577</p><p>Elasticsearch-8.14.0@lucene-int8_hnsw</p><p>0.775458</p><p>0.840254</p><p>OpenSearch-2.14.0@lucene-hnsw</p><p>0.971333</p><p>0.996747</p><p>OpenSearch-2.14.0@faiss</p><p>0.9704</p><p>0.914755</p><p>OpenSearch-2.14.0@faiss-sq_fp16</p><p>0.968025</p><p>0.913862</p><p>OpenSearch-2.14.0@nmslib</p><p>0.9674</p><p>0.910303</p><h2>2 millones de vectores, 768 dimensiones (so_vector)</h2><p>Esta <a href="https://github.com/elastic/rally-tracks/tree/master/so_vector">pista</a>, <code>so_vector</code>, se deriva de un <a href="https://archive.org/download/stackexchange/stackoverflow.com-Posts.7z">volcado de publicaciones de StackOverflow descargadas</a> el 21 de abril de 2022. Solo contiene documentos de preguntas; se eliminaron todos los documentos que representan respuestas. Cada título de pregunta se codificó en un vector usando el modelo de transformador de oraciones <a href="https://huggingface.co/sentence-transformers/multi-qa-mpnet-base-cos-v1">multi-qa-mpnet-base-cos-v1</a>. Este conjunto de datos contiene los primeros 2 millones de preguntas.</p><p>A diferencia de la pista anterior, cada documento aquí contiene otros campos además de vectores para soportar características de prueba como KNN aproximado con filtrado y búsqueda híbrida. <code>nmslib</code> para OpenSearch está notablemente ausente en esta prueba ya que <a href="https://opensearch.org/docs/latest/search-plugins/knn/filter-search-knn/#k-nn-search-with-filters">no admite filtros</a>.</p><h3>Tareas</h3><p>Cada tarea se calienta con 100 solicitudes y luego se miden 100 solicitudes. Tenga en cuenta que las tareas se agruparon por simplicidad, ya que la prueba contiene 16 tipos de búsqueda * 2 valores k diferentes * 3 valores n diferentes.</p><ul><li><p><strong>knn-10-50</strong>: búsqueda en 2 millones de vectores sin filtros, k:10 y n:50</p></li><li><p><strong>knn-10-50-filtered</strong>: búsqueda en 2 millones de vectores <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">con filtros</a>, k:10 y n:50</p></li><li><p><strong>knn-10-50-after-force-merge</strong>: búsqueda en 2 millones de vectores con filtros y después de una fusión forzosa, k:10 y n:50</p></li><li><p><strong>knn-10-100</strong>: búsqueda en 2 millones de vectores sin filtros, k:10 y n:100</p></li><li><p><strong>knn-10-100-filtered</strong>: búsqueda en 2 millones de vectores <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">con filtros</a>, k:10 y n:100</p></li><li><p><strong>knn-10-100-after-force-merge</strong>: búsqueda en 2 millones de vectores con filtros y luego de una fusión forzada, k: 10 y n: 100</p></li><li><p><strong>knn-100-1000</strong>: búsqueda en 2 millones de vectores sin filtros, k:100 y n:1000</p></li><li><p><strong>knn-100-1000-filtered</strong>: búsqueda en 2 millones de vectores <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">con filtros</a>, k:100 y n:1000</p></li><li><p><strong>knn-100-1000-after-force-merge</strong>: búsqueda en 2 millones de vectores con filtros y luego de una fusión forzada, k:100 y n:1000</p></li><li><p><strong>exact-knn</strong>: búsqueda de KNN exacto <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json#L56">con y sin filtros</a>.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8b44e1306af35877/6a17d72f577262aca11bca3e/d4ed2982d55370cad4b2048b23ce97caa55017c0-1440x316.webp" alt="tabla so_vector" /><p>Elasticsearch es <strong>consistentemente más rápido</strong> que OpenSearch de manera inmediata en esta prueba, solo en dos casos OpenSearch es más rápido, y no por mucho (<em>knn-10-100</em> y <em>knn-100-1000</em>). Las tareas que involucran <em>knn-10-50</em>, <em>knn-10-100</em> y <em>knn-100-1000</em> en combinación con filtros muestran una diferencia de hasta <strong>7 veces</strong> (112 ms versus 803 ms).</p><p>El rendimiento de ambas soluciones parece igualarse después de un "force merge" (fusión forzada), lógicamente, como lo demuestran <em>knn-10-50-after-force-merge</em>, <em>knn-10-100-after-force-merge</em> y <em>knn-100-1000-after-force-merge.</em> En esas tareas, <code>faiss</code> es más rápido.</p><p>Como mencionamos, el rendimiento para Exact KNN es muy diferente, ya que Elasticsearch fue <strong>13 veces más rápido</strong> que OpenSearch esta vez (~385 ms vs ~5262 ms).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf80ccc7c2df84559/6a17d7314b055de00d43203f/615cb9228eb05ddd2e9512b3a6a5bc88d4088a1a-1440x1440.webp" alt="so_vector" /><h4>Recall</h4><p></p><p>knn-recall-10-100</p><p>knn-recall-100-1000</p><p>knn-recall-10-50</p><p>Elasticsearch-8.14.0@lucene-hnsw</p><p>1</p><p>1</p><p>1</p><p>Elasticsearch-8.14.0@lucene-int8_hnsw</p><p>1</p><p>0.986667</p><p>1</p><p>OpenSearch-2.14.0@lucene-hnsw</p><p>1</p><p>1</p><p>1</p><p>OpenSearch-2.14.0@faiss</p><p>1</p><p>1</p><p>1</p><p>OpenSearch-2.14.0@faiss-sq_fp16</p><p>1</p><p>1</p><p>1</p><p>OpenSearch-2.14.0@nmslib</p><p>0.9674</p><p>0.910303</p><p>0.976394</p><h2>Elasticsearch y Lucene como claros vencedores</h2><p>En Elastic, estamos innovando incansablemente Apache Lucene y Elasticsearch para garantizar que podamos proporcionar la base de datos vectorial de primer nivel para casos de uso de búsqueda y recuperación, incluido RAG (Retrieval Augmented Generation). Nuestros últimos avances han aumentado significativamente el rendimiento, haciendo que la búsqueda vectorial <a href="https://search-labs.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains">sea más rápida y eficiente en cuanto a espacio</a> que antes, basándose en las mejoras de Lucene 9.10. En este blog, se presentó un estudio que muestra que al comparar versiones actualizadas, Elasticsearch es hasta 12 veces más rápido que OpenSearch.</p><p>Vale la pena señalar que ambos productos usan la misma versión de Lucene (<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/release-notes-8.14.0.html">Notas de lanzamiento de Elasticsearch 8.14</a> y <a href="https://github.com/opensearch-project/OpenSearch/blob/2.14/release-notes/opensearch.release-notes-2.14.0.md">Notas de lanzamiento de OpenSearch 2.14</a>).</p><p>El ritmo de innovación en Elastic ofrecerá aún más, no solo para nuestros clientes locales y de Elastic Cloud, sino también para aquellos que utilizan nuestra <a href="https://www.elastic.co/search-labs/blog/stateless-your-new-state-of-find-with-elasticsearch">plataforma sin estado</a>. Las características como el soporte para la <a href="https://www.elastic.co/search-labs/blog/int4-scalar-quantization-in-lucene">cuantificación escalar a int4</a> se ofrecerán con pruebas rigurosas para garantizar que los clientes puedan utilizar estas técnicas sin una caída significativa en la recuperación, similar a <a href="https://www.elastic.co/search-labs/blog/evaluating-scalar-quantization">nuestras pruebas para int8</a>.</p><p>La eficiencia de la búsqueda vectorial se está convirtiendo en una característica no negociable en los motores de búsqueda modernos debido a la proliferación de aplicaciones de inteligencia artificial y machine learning. Para las organizaciones que buscan un motor de búsqueda poderoso capaz de mantenerse al día con las demandas de datos vectoriales de alto volumen y alta complejidad, Elasticsearch es la respuesta definitiva.</p><p>Ya sea que quieres expandir una plataforma establecida o iniciar nuevos proyectos, integrar Elasticsearch para las necesidades de búsqueda vectorial es un movimiento estratégico que generará beneficios tangibles a largo plazo. Con su ventaja de rendimiento comprobada, Elasticsearch está a punto de apuntalar la próxima ola de innovaciones en búsqueda.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[Lucene]]></category>
    <dc:creator><![CDATA[Ugo Sangiorgi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5d70b25967c2194e/6a17d732b1e11383f879f0ca/13c3c0053e2968fb835ba2f90f34bec3a011b5c0-880x592.webp" length="0" type="image/webp"/>
    <pubDate>Wed, 26 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Medidas de similitud vectorial y puntaje]]></title>
    <description><![CDATA[Explora las medidas de similitud vectorial y el puntaje en Elasticsearch, incluyendo la distancia L1 y L2, la similitud coseno, la similitud por producto escalar y la similitud máxima en el producto interno.]]></description>
    <content:encoded><![CDATA[<p>Cuando surge la necesidad de buscar texto libre y Ctrl+F / Cmd+F ya no es suficiente, el uso de un motor de búsqueda léxico suele ser la siguiente opción lógica que se le viene a la mente. Los motores de búsqueda léxicos se destacan en el análisis y la tokenización del texto que se buscará en términos que se pueden comparar en el momento de la búsqueda, pero generalmente se quedan cortos cuando se trata de comprender y dar sentido al verdadero significado del texto que se indexa y busca.</p><p>Ahí es exactamente donde brillan los motores de búsqueda vectorial. Pueden indexar el mismo texto de tal manera que se pueda buscar tanto en función del significado que representa como de sus relaciones con otros conceptos que tienen un significado similar o relacionado.</p><p>En este blog, abordaremos brevemente cómo los vectores son un gran concepto matemático para transmitir el significado del texto. Luego, profundizaremos en las diferentes técnicas de similitud compatibles con Elasticsearch cuando se trata de buscar vectores vecinos, es decir, buscar vectores que tengan un significado similar y cómo puntuarlos.</p><h2>¿Qué son las incrustaciones de vectores?</h2><p>Este artículo no profundiza en las complejidades de las incrustaciones de vectores. Si quieres explorar este tema más a fondo o necesitas una introducción antes de continuar, te recomendamos que consultes la <a href="https://www.elastic.co/es/what-is/vector-embedding">siguiente guía</a>.</p><p>En pocas palabras, las incrustaciones vectoriales se obtienen a través de un proceso de aprendizaje automático (p. ej. redes neuronales de aprendizaje profundo) que transforma cualquier tipo de datos de entrada no estructurados (por ejemplo, texto sin procesar, imagen, video, sonido, etc.) en datos numéricos que llevan su significado y relaciones. Los diferentes tipos de datos no estructurados requieren diferentes tipos de modelos de aprendizaje automático que fueron capacitados para "comprender" cada tipo de datos.</p><p>Cada vector localiza un dato específico como un punto en un espacio multidimensional y esa ubicación representa un conjunto de características que el modelo emplea para caracterizar los datos. El número de dimensiones depende del modelo de aprendizaje automático, pero generalmente oscilan entre un par de cientos y unos pocos miles. Por ejemplo, <a href="https://platform.openai.com/docs/guides/embeddings">los modelos OpenAI Embeddings</a> cuentan con 1536 dimensiones, mientras que <a href="https://docs.cohere.com/reference/embed">los modelos Cohere Embeddings</a> pueden variar de 382 a 4096 dimensiones. El tipo de campo Elasticsearch dense_vector admite hasta 4096 dimensiones a partir de la última versión.</p><p>La verdadera hazaña de las incrustaciones vectoriales es que los puntos de datos que comparten un significado similar están muy juntos en el espacio. Otro aspecto interesante es que las incrustaciones vectoriales también ayudan a capturar relaciones entre puntos de datos.</p><h2>¿Cómo comparamos vectores?</h2><p>Sabiendo que los datos no estructurados son divididos por modelos de aprendizaje automático en incrustaciones vectoriales que capturan la similitud de los datos a lo largo de una gran cantidad de dimensiones, ahora necesitamos comprender cómo funciona la coincidencia de esos vectores. Resulta que la respuesta es bastante simple.</p><p>Las incrustaciones vectoriales que están <strong>cerca</strong> unas de otras representan datos <strong>semánticamente similares</strong> . Entonces, cuando consultamos una base de datos vectorial, la entrada de búsqueda (imagen, texto, etc.) se convierte primero en incrustaciones vectoriales empleando el mismo modelo de aprendizaje automático que se empleó para indexar todos los datos no estructurados, y el objetivo final es encontrar los <strong>vectores vecinos más cercanos</strong> a ese vector de consulta. Por lo tanto, todo lo que tenemos que hacer es averiguar cómo medir la "distancia" o "similitud" entre el vector de consulta y todos los vectores existentes indexados en la base de datos, es así de simple.</p><h2>Distancia, similitud y puntaje</h2><p>Por suerte para nosotros, medir la distancia o similitud entre dos vectores es un problema fácil de resolver gracias a la aritmética vectorial. Entonces, veamos las funciones de distancia y similitud más populares que son compatibles con Elasticsearch. ¡Advertencia, matemáticas por delante!</p><p>Justo antes de sumergirnos, echemos un vistazo rápido al puntaje. De hecho, Lucene solo permite que los puntajes sean positivos. Todas las funciones de distancia y similitud que presentaremos en breve producen una medida de qué tan cerca o similares están dos vectores, pero esas cifras en bruto rara vez son aptas para usar como puntaje, ya que pueden ser negativas. Por esta razón, el puntaje final debe derivar del valor de distancia o similitud de una manera que garantice que el puntaje sea positivo y que un puntaje mayor corresponda a una clasificación más alta (es decir, a vectores más cercanos).</p><h3>Distancia L1</h3><p>La distancia L1, también llamada distancia de Manhattan, de dos vectores  y  se mide sumando la diferencia absoluta por pares de todos sus elementos. Obviamente, cuanto menor sea la distancia , más cerca estarán los dos vectores. La fórmula de la distancia L1 (1) es bastante simple, como se puede ver a continuación:</p><p>Visualmente, la distancia L1 se puede ilustrar como se muestra en la imagen a continuación (en rojo):</p><p>Calculando la distancia L1 de los siguientes dos vectores  y  obtendría </p><p><strong>Importante:</strong> Vale la pena señalar que la función de distancia L1 solo es compatible con la <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/current/query-dsl-script-score-query.html#vector-functions-l1">búsqueda vectorial exacta</a> (también conocida como búsqueda de fuerza bruta) empleando la consulta DSL <code>script_score</code>, pero no para la <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/8.11/dense-vector.html#dense-vector-params">búsqueda aproximada de kNN</a> empleando la<a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/8.11/knn-search.html#approximate-knn"> opción de búsqueda</a> <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/8.11/knn-search.html#approximate-knn"><code>knn</code></a>o <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/current/query-dsl-knn-query.html"><code>knn</code></a><a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/current/query-dsl-knn-query.html"> consulta DSL</a>.</p><h3>Distancia L2</h3><p>La distancia L2, también llamada distancia euclidiana, de dos vectores  y  se mide sumando primero el cuadrado de la diferencia por pares de todos sus elementos y luego tomando la raíz cuadrada del resultado. Es básicamente el camino más corto entre dos puntos. De manera similar a L1, cuanto menor sea la distancia , más cerca estarán los dos vectores:</p><p>La distancia L2 se muestra en rojo en la siguiente imagen:</p><p>Reutilicemos los mismos dos vectores de muestra  y  que usamos para la distancia , y ahora podemos calcular la distancia  como .</p><p>En cuanto al puntaje, cuanto menor es la distancia entre dos vectores, más cerca (es decir, más similares) están. Entonces, para derivar un puntaje, necesitamos invertir la medida de distancia, de modo que la distancia más pequeña produzca el puntaje más alta. La forma en que se calcula el puntaje cuando se usa la distancia L2 se ve como se muestra en la fórmula (3) a continuación:</p><p>Reutilizando los vectores de muestra del ejemplo anterior, su puntaje sería . Dos vectores que están muy cerca uno del otro se acercarán a un puntaje de 1, mientras que el puntaje de dos vectores que están muy lejos uno del otro tenderá hacia 0.</p><p>Terminando con las funciones de distancia L1 y L2, una buena analogía para compararlas es pensar en A y B como dos edificios en Manhattan, Nueva York. Un taxi que va de A a B tendría que manejar por el camino L1 (calles y avenidas), mientras que un pájaro probablemente usaría el camino L2 (línea recta).</p><h3>Similitud de coseno</h3><p>A diferencia de L1 y L2, la similitud del coseno no mide la distancia entre dos vectores  y , sino su ángulo relativo, es decir, si ambos apuntan aproximadamente en la misma dirección. Cuanto mayor sea la similitud , menor será el ángulo  entre los dos vectores y, por lo tanto, más "cercanos" están y "similar" es su significado transmitido.</p><p>Para ilustrar esto, pensemos en dos personas en la naturaleza que miran en diferentes direcciones. En la siguiente figura, la persona en azul mira en la dirección simbolizada por el vector  y la persona en rojo en la dirección del vector . Cuanto más dirijan su vista hacia la misma dirección (es decir, cuanto más se acerquen sus vectores), más se superpondrá su campo de visión simbolizado por las áreas azul y roja. Cuánto se superpone su campo de visión es su similitud con el coseno. Sin embargo, tenga en cuenta que la persona B mira más lejos que la persona A (es decir, el vector  es más largo). La persona B podría estar mirando una montaña lejana en el horizonte, mientras que la persona A podría estar mirando un árbol cercano. Para la similitud del coseno, eso no juega ningún papel, ya que solo se trata del ángulo.</p><p>Ahora calculemos esa similitud de coseno. La fórmula (4) es bastante simple, donde el numerador consiste en el producto punto de ambos vectores y el denominador contiene el producto de su magnitud (es decir, su longitud):</p><p>La similitud del coseno entre  y  se muestra en la imagen de abajo como una medida del ángulo entre ellos (en rojo):</p><p>Tomemos un desvío rápido para explicar qué significan concretamente estos valores de similitud de coseno. Como se puede ver en la imagen a continuación que muestra la función de coseno, los valores siempre oscilan en el intervalo </p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc0e4eb98ebb64cf3/6a17da287b54f983818b3783/e31145284b8c27d0bd9e3d2831f811388134fd83-1188x272.png" alt="cos-función" /><p>Recuerde que para que dos vectores se consideren similares, su ángulo debe ser lo más agudo posible, idealmente cerca de un ángulo de  , lo que se reduciría a una similitud perfecta de . En otras palabras, cuando los vectores son...</p><ol><li><p>...<strong>cerca</strong> uno del otro, el coseno de su ángulo se acerca a  (es decir, cerca de )</p></li></ol><ol><li><p>...<strong>sin relación</strong>, el coseno de su ángulo se acerca a  (es decir, cerca de )</p></li></ol><ol><li><p>...<strong>opuesto</strong>, el coseno de su ángulo se acerca a  (es decir, cerca de )</p></li></ol><p>Ahora que sabemos cómo calcular la similitud del coseno entre dos vectores y tenemos una buena idea de cómo interpretar el valor resultante, podemos reutilizar los mismos vectores  y  y calcular su similitud de coseno usando la fórmula (4) que vimos anteriormente.</p><p>Obtenemos una similitud de coseno de , que está más cerca de  que de , lo que significa que los dos vectores son <strong>algo similares</strong>, es decir, no perfectamente similares, pero tampoco completamente desrelacionados, y ciertamente no tienen un significado opuesto.</p><p>Para obtener un puntaje positivo de cualquier valor de similitud de coseno, necesitamos usar la siguiente fórmula (5), que transforma los valores de similitud de coseno que oscilan dentro del intervalo  en puntajes en el intervalo </p><p>El puntaje para los vectores  y  sería: .</p><h3>Similitud de productos Dot</h3><p>Un inconveniente de la similitud del coseno es que solo tiene en cuenta el ángulo entre dos vectores pero no su magnitud, lo que significa que si dos vectores apuntan aproximadamente en la misma dirección pero uno es mucho más largo que el otro, ambos se considerarán similares. La similitud de producto punto, también llamada similitud de producto escalar o interno, mejora eso al tener en cuenta tanto el ángulo como la magnitud de los vectores, lo que proporciona una métrica de similitud más precisa. Para hacer que la magnitud de los vectores sea irrelevante, la similitud del producto punto requiere que los vectores se normalicen primero, por lo que, en última instancia, solo estamos comparando vectores de longitud unitaria 1.</p><p>Intentemos ilustrar esto nuevamente con las mismas dos personas que antes, pero esta vez, las colocamos en el medio de una habitación circular, de modo que su alcance visual sea exactamente el mismo (es decir, el radio de la habitación). De manera similar a la similitud del coseno, cuanto más volteen hacia la misma dirección (es decir, cuanto más se acerquen sus vectores), más se superpondrá su campo de visión. Sin embargo, al contrario de la similitud del coseno, ambos vectores tienen la misma longitud y ambas áreas tienen la misma superficie, lo que significa que las dos personas miran exactamente la misma imagen ubicada a la misma distancia. La superposición de esas dos áreas denota su similitud de producto punto.</p><p>Antes de introducir la fórmula de similitud de producto punto, veamos rápidamente cómo se puede normalizar un vector. Es bastante simple y se puede hacer en dos pasos triviales:</p><ol><li><p>calcular la magnitud del vector</p></li><li><p>Divida cada componente por la magnitud obtenida en 1.</p></li></ol><p>Como ejemplo, tomemos el vector . Podemos calcular su magnitud  como vimos anteriormente al revisar la similitud del coseno, es decir, . Luego, dividiendo cada componente del vector por su magnitud, obtenemos el siguiente vector estandarizado :</p><p>Pasar por el mismo proceso para el segundo vector  obtendría el siguiente vector estandarizado :</p><p>Para derivar la fórmula de similitud de producto punto, podemos calcular la similitud de coseno entre nuestros vectores estandarizados  y  usando la fórmula (4), como se muestra a continuación:</p><p>Y dado que la magnitud de ambos vectores estandarizados es ahora , la fórmula de similitud del producto punto (6) simplemente se convierte en... Lo adivinaste, un producto punto de ambos vectores estandarizados:</p><p>En la imagen a continuación, mostramos los vectores estandarizados  y  y podemos ilustrar su similitud de producto punto como la proyección de un vector sobre el otro (en rojo).</p><p>Usando nuestra nueva fórmula (6), podemos calcular la similitud del producto punto de nuestros dos vectores estandarizados, lo que como era de esperar produce exactamente el mismo valor de similitud que el del coseno:</p><p>Al aprovechar la similitud de productos puntos, el puntaje se calcula de forma diferente en función de si los vectores contienen valores flotantes o de bytes. En el primer caso, el puntaje se calcula de la misma manera que para la similitud del coseno empleando la fórmula (7) a continuación:</p><p>Sin embargo, cuando el vector se compone de valores de bytes, el puntaje se calcula de manera un poco diferente, como se muestra en la fórmula (8) a continuación, donde  es el número de dimensiones del vector:</p><p>Además, una restricción para obtener puntajes precisos es que todos los vectores, incluido el vector de consulta, deben tener la misma longitud, pero no necesariamente 1.</p><h3>Máxima similitud interna del producto</h3><p>Desde la versión 8.11, hay una nueva función de similitud que está menos restringida que la similitud del producto punto, ya que no es necesario normalizar los vectores. La razón principal de esto se explica en detalle en el <a href="https://www.elastic.co/es/search-labs/blog/lucene-bringing-maximum-inner-product-to-lucene">siguiente artículo</a>, pero para resumirlo muy brevemente, ciertos conjuntos de datos no están muy bien adaptados a tener sus vectores estandarizados (por ejemplo, <a href="https://www.elastic.co/es/search-labs/blog/elasticsearch-cohere-embeddings-support">incrustaciones coherentes</a>) y hacerlo puede causar problemas de relevancia.</p><p>La fórmula para calcular la similitud máxima del producto interno es exactamente la misma que la del producto punto (6). Lo que cambia es la forma en que se calcula el puntaje escalando la similitud máxima del producto interno empleando una función por partes cuya fórmula depende de si la similitud es positiva o negativa, como se muestra en la fórmula (9) a continuación:</p><p>Lo que hace esta función por partes es que escala todos los valores negativos de similitud del producto interno máximo en el intervalo  y todos los valores positivos en el intervalo  .</p><h2>En resumen</h2><p>Fue un gran viaje, matemáticamente hablando, pero aquí hay algunas conclusiones que pueden resultarle útiles.</p><p>La función de similitud que puede usar, en última instancia, depende de si sus incrustaciones vectoriales están normalizadas o no. Si sus vectores ya están estandarizados o si su conjunto de datos es independiente de la normalización de vectores (es decir, la relevancia no se verá afectada), puede continuar y normalizar sus vectores y usar la similitud de producto punto, ya que es mucho más rápido de calcular que el de coseno ya que no es necesario calcular la longitud de cada vector. Al comparar millones de vectores, esos cálculos pueden sumar bastante.</p><p>Si los vectores no están estandarizados, tiene dos opciones:</p><ol><li><p>Use la similitud de coseno si normalizar sus vectores no es una opción</p></li><li><p>use la nueva similitud de producto interno máximo si desea que la magnitud de sus vectores contribuya al puntaje porque tienen significado (por ejemplo, incrustaciones coherentes)</p></li></ol><p>En este punto, calcular la distancia o similitud entre las incrustaciones vectoriales y cómo derivar sus puntajes debería tener sentido para usted. Esperamos que este artículo le resultó útil.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-similarity-measures-and-scoring</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-similarity-measures-and-scoring</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <dc:creator><![CDATA[Valentin Crettaz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5a3e9d39849d3ed/6a17da31a2929960a3d02b3f/4d9e89678798b9de68357b5cc06dbbd8b9c6e5e8-1440x823.webp" length="0" type="image/webp"/>
    <pubDate>Mon, 13 May 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Plagio por IA: detección de plagio con Elasticsearch]]></title>
    <description><![CDATA[Aquí tienes cómo comprobar el plagio de IA usando Elasticsearch, centrándote en casos de uso con modelos de PLN y Búsqueda Vectorial.]]></description>
    <content:encoded><![CDATA[<p>El plagio puede ser <strong>directo</strong>, implicando la copia de partes o de todo el contenido, o <strong>parafraseado</strong>, donde la obra del autor se reformula cambiando algunas palabras o frases.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb6e139760e56ec98/6a171147dc55de0ad2e00edf/5d7073187fda829438aeec8d3a1194a5bea2ba57-1440x347.png" alt="" /><p>Hay una distinción entre inspiración y parafraseo. Es posible leer un contenido, inspirar y luego explorar la idea con tus propias palabras, incluso si llegas a una conclusión similar.</p><p>Aunque el plagio fue un tema de debate durante mucho tiempo, el aceleramiento de la producción y publicación de contenido lo mantuvo relevante y supuso un desafío constante.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc2af1678cb04506b/6a171149cf4f251c2ab2d257/a0b9a98d729db09dae0a79315c001e6763c12704-1400x1016.png" alt="" /><p>Este desafío no se limita a libros, investigaciones académicas o documentos judiciales, donde con frecuencia se realizan comprobaciones de plagio. También puede extender a los periódicos e incluso a las redes sociales.</p><p>Con la abundancia de información y el fácil acceso a la publicación, ¿cómo se puede controlar el plagio de forma eficaz a un nivel escalable?</p><p>Las universidades, entidades gubernamentales y compañías emplean herramientas diversas, pero aunque una <a href="https://www.elastic.co/search-labs/lexical-and-semantic-search-with-elasticsearch">búsqueda léxica</a> sencilla puede detectar el plagio directo, el principal desafío radica en identificar <strong>contenido parafraseado.</strong></p><h2>Detección de plagio con IA generativa</h2><p>Surge un nuevo reto con la IA generativa. ¿Se considera plagio el contenido generado por IA cuando se copia?</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a1ed1b6fa3f1a56/6a17114b4a531bc40736aa69/9345b28d6d27c37469bc38e823c41780b4eabfe5-1440x875.png" alt="" /><p>Los <a href="https://openai.com/policies/terms-of-use">términos de uso</a> de <a href="https://openai.com/">OpenAI, por ejemplo, especifican que OpenAI no reclamará derechos de autor sobre el contenido generado por la API para los usuarios.</a> En este caso, las personas que usan su IA generativa pueden emplear el contenido generado como prefieran sin necesidad de citas.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbc2e08ab5b808e38/6a17114dab7f086cbadb9f93/e1f415f69a247666f02ddc81468944920c874cd7-968x814.png" alt="" /><p>Sin embargo, la aceptación del uso de IA generativa para mejorar la eficiencia sigue siendo un tema de debate.</p><p>En un esfuerzo por contribuir a la detección de plagio, OpenAI desarrolló un <a href="https://huggingface.co/roberta-base-openai-detector">modelo de detección</a> , pero más tarde reconoció que su precisión no es suficientemente alta.</p><p><em>"Creemos que esto no es lo suficientemente preciso para una detección independiente y debe combinar con enfoques basados en metadatos, juicio humano y educación pública para ser más efectivo."</em></p><p>El desafío persiste; sin embargo, con la disponibilidad de más herramientas, ahora hay más opciones para detectar plagio, incluso en casos de contenido parafraseado y de IA.</p><h2>Detección de plagio con Elasticsearch</h2><p>Reconociendo esto, en este blog exploramos un caso de uso más con los modelos de Procesamiento del Lenguaje Natural (PLN) y la búsqueda vectorial, la detección de plagio, más allá de las búsquedas de metadatos.</p><p>Esto se demuestra con <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/plagiarism-detection-with-elasticsearch/plagiarism_detection_es.ipynb">ejemplos en Python</a>, donde empleamos un conjunto de <a href="https://sbert.net/datasets/emnlp2016-2018.json">datos</a> de <a href="https://www.sbert.net/">SentenceTransformers</a> que contiene artículos relacionados con el PLN. Comprobamos los resúmenes en busca de plagio realizando una 'similitud textual semántica' considerando incrustaciones 'abstractas' generadas con un <a href="https://huggingface.co/sentence-transformers/all-mpnet-base-v2">modelo de incrustación de texto</a> previamente importado a Elasticsearch. Además, para identificar contenido generado por IA — plagio por IA, también se importó un <a href="https://huggingface.co/roberta-base-openai-detector">modelo de PLN</a> desarrollado por OpenAI a Elasticsearch.</p><p>La siguiente imagen ilustra el flujo de datos:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0464f88d3ed12070/6a17114fab7f084905db9f97/1ad89c98a2f42a497548ca3947749bad54ec1172-1440x880.png" alt="" /><p>Durante la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html">tubería de ingesta</a> con un <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/inference-processor.html">procesador de inferencia</a>, el párrafo 'abstracto' se mapea a un vector de 768 dimensiones, el 'valor_predicho abstract_vector.predicho'.</p><p>Cartografía:</p>"abstract_vector.predicted_value": { # Inference results field
"type": "dense_vector", 
"dims": 768, # model embedding_size
"index": "true", 
"similarity": "dot_product" # When indexing vectors for approximate kNN search, you need to specify the similarity function for comparing the vectors.
<p>La similitud entre representaciones vectoriales se mide mediante una métrica de similitud vectorial, definida mediante el <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html#dense-vector-params">parámetro</a> de 'similitud'.</p><p><a href="https://en.wikipedia.org/wiki/Cosine_similarity">El coseno</a> es la métrica de similitud por defecto, calculada como '(1 + coseno(consulta, vector)) / 2'. A menos que necesites preservar los vectores originales y no puedas normalizarlos de antemano, la forma más eficiente de realizar similitud coseno es normalizar todos los vectores a longitud unitaria. Esto ayuda a evitar realizar cálculos adicionales de longitud vectorial durante la búsqueda, y en su lugar emplea 'dot_product'.</p><p>En esta misma canalización, otro procesador de inferencia que contiene el <a href="https://huggingface.co/roberta-base-openai-detector">modelo de clasificación de texto</a> detecta si el contenido es 'real' probablemente escrito por humanos, o 'falso' probablemente escrito por IA, agregando el 'openai-detector.predicted_value' a cada documento.</p><p>Pipeline de ingestión:</p>client.ingest.put_pipeline( 
    id="plagiarism-checker-pipeline",
    processors = [
    {
      "inference": { #for ml models - to infer against the data that is being ingested in the pipeline
        "model_id": "roberta-base-openai-detector", #text classification model id
        "target_field": "openai-detector", # Target field for the inference results
        "field_map": { #Maps the document field names to the known field names of the model.
        "abstract": "text_field" # Field matching our configured trained model input. 
        }
      }
    },
    {
      "inference": {
        "model_id": "sentence-transformers__all-mpnet-base-v2", #text embedding model id
        "target_field": "abstract_vector", # Target field for the inference results
        "field_map": {
        "abstract": "text_field" # Field matching our configured trained model input. Typically for NLP models, the field name is text_field.
        }
      }
    }
    
  ]
)
<p>En el momento de la consulta, también se emplea el mismo modelo de incrustación de texto para generar la representación vectorial de la consulta 'model_text' en un objeto 'query_vector_builder'.</p><p>Una búsqueda de k vecinos más cercanos (kNN) encuentra el k vector más cercano al vector de consulta medido por la métrica de similitud.</p><p>El _score de cada documento se deriva de la similitud, cerciorando que un puntaje mayor corresponda a una clasificación más alta. Esto significa que el documento es más similar semánticamente. Como resultado, imprimimos tres posibilidades: si el puntaje &gt; 0,9, consideramos 'alta similitud'; si &lt; 0,7, 'baja similitud', de lo contrario, 'similitud moderada'. Tienes la flexibilidad de establecer diferentes valores umbral para determinar qué nivel de _score califica como plagio o no, según tu caso de uso.</p><p>Además, se realiza la clasificación de texto para comprobar también la presencia de elementos generados por IA en la consulta de texto.</p><p>Consulta:</p>from elasticsearch import Elasticsearch
from elasticsearch.client import MlClient

#duplicated text - direct plagiarism test

model_text = 'Understanding and reasoning about cooking recipes is a fruitful research direction towards enabling machines to interpret procedural text. In this work, we introduce RecipeQA, a dataset for multimodal comprehension of cooking recipes. It comprises of approximately 20K instructional recipes with multiple modalities such as titles, descriptions and aligned set of images. With over 36K automatically generated question-answer pairs, we design a set of comprehension and reasoning tasks that require joint understanding of images and text, capturing the temporal flow of events and making sense of procedural knowledge. Our preliminary results indicate that RecipeQA will serve as a challenging test bed and an ideal benchmark for evaluating machine comprehension systems. The data and leaderboard are available at http://hucvl.github.io/recipeqa.'

response = client.search(index='plagiarism-checker', size=1,
    knn={
        "field": "abstract_vector.predicted_value",
        "k": 9,
        "num_candidates": 974,
        "query_vector_builder": { #The 'all-mpnet-base-v2' model is also employed to generate the vector representation of the query in a 'query_vector_builder' object.
            "text_embedding": {
                "model_id": "sentence-transformers__all-mpnet-base-v2",
                "model_text": model_text
            }
        }
    }
)

for hit in response['hits']['hits']:
    score = hit['_score']
    title = hit['_source']['title']
    abstract = hit['_source']['abstract']
    openai = hit['_source']['openai-detector']['predicted_value']
    url = hit['_source']['url']

    if score &gt; 0.9:
        print(f"\nHigh similarity detected! This might be plagiarism.")
        print(f"\nMost similar document: '{title}'\n\nAbstract: {abstract}\n\nurl: {url}\n\nScore:{score}\n\n")

        if openai == 'Fake':
            print("This document may have been created by AI.\n")

    elif score &lt; 0.7:
        print(f"\nLow similarity detected. This might not be plagiarism.")

        if openai == 'Fake':
            print("This document may have been created by AI.\n")

    else:
        print(f"\nModerate similarity detected.")
        print(f"\nMost similar document: '{title}'\n\nAbstract: {abstract}\n\nurl: {url}\n\nScore:{score}\n\n")

        if openai == 'Fake':
            print("This document may have been created by AI.\n")

ml_client = MlClient(client)

model_id = 'roberta-base-openai-detector' #open ai text classification model

document = [
    {
        "text_field": model_text
    }
]

ml_response = ml_client.infer_trained_model(model_id=model_id, docs=document)

predicted_value = ml_response['inference_results'][0]['predicted_value']

if predicted_value == 'Fake':
    print("\nNote: The text query you entered may have been generated by AI.\n")
<p>Salida:</p>High similarity detected! This might be plagiarism.

Most similar document: 'RecipeQA: A Challenge Dataset for Multimodal Comprehension of Cooking Recipes'

Abstract: Understanding and reasoning about cooking recipes is a fruitful research direction towards enabling machines to interpret procedural text. In this work, we introduce RecipeQA, a dataset for multimodal comprehension of cooking recipes. It comprises of approximately 20K instructional recipes with multiple modalities such as titles, descriptions and aligned set of images. With over 36K automatically generated question-answer pairs, we design a set of comprehension and reasoning tasks that require joint understanding of images and text, capturing the temporal flow of events and making sense of procedural knowledge. Our preliminary results indicate that RecipeQA will serve as a challenging test bed and an ideal benchmark for evaluating machine comprehension systems. The data and leaderboard are available at[ http://hucvl.github.io/recipeqa](http://hucvl.github.io/recipeqa).

url:[http://aclweb.org/anthology/D18-1166](http://aclweb.org/anthology/D18-1166)

Score:1.0
<p>En este ejemplo, tras emplear uno de los valores 'abstractos' de nuestro conjunto de datos como consulta de texto 'model_text', se identificó plagio. El puntaje de similitud es 1,0, lo que indica un alto nivel de similitud: <strong>plagio directo</strong>. La consulta vectorizada y el documento no fueron reconocidos como contenido generado por IA, lo cual era de esperar.</p><p>Consulta:</p>#similar text - paraphrase plagiarism test 

model_text = 'Comprehending and deducing information from culinary instructions represents a promising avenue for research aimed at empowering artificial intelligence to decipher step-by-step text. In this study, we present CuisineInquiry, a database for the multifaceted understanding of cooking guidelines. It encompasses a substantial number of informative recipes featuring various elements such as headings, explanations, and a matched assortment of visuals. Utilizing an extensive set of automatically crafted question-answer pairings, we formulate a series of tasks focusing on understanding and logic that necessitate a combined interpretation of visuals and written content. This involves capturing the sequential progression of events and extracting meaning from procedural expertise. Our initial findings suggest that CuisineInquiry is poised to function as a demanding experimental platform.'
<p>Salida:</p>High similarity detected! This might be plagiarism.

Most similar document: 'RecipeQA: A Challenge Dataset for Multimodal Comprehension of Cooking Recipes'

Abstract: Understanding and reasoning about cooking recipes is a fruitful research direction towards enabling machines to interpret procedural text. In this work, we introduce RecipeQA, a dataset for multimodal comprehension of cooking recipes. It comprises of approximately 20K instructional recipes with multiple modalities such as titles, descriptions and aligned set of images. With over 36K automatically generated question-answer pairs, we design a set of comprehension and reasoning tasks that require joint understanding of images and text, capturing the temporal flow of events and making sense of procedural knowledge. Our preliminary results indicate that RecipeQA will serve as a challenging test bed and an ideal benchmark for evaluating machine comprehension systems. The data and leaderboard are available at[ http://hucvl.github.io/recipeqa](http://hucvl.github.io/recipeqa).

url:[http://aclweb.org/anthology/D18-1166](http://aclweb.org/anthology/D18-1166)

Score:0.9302529

Note: The text query you entered may have been generated by AI.
<p>Al actualizar la consulta de texto 'model_text' con un texto generado por IA que transmite el mismo mensaje minimizando la repetición de palabras similares, la similitud detectada seguía siendo alta, pero el puntaje era de 0,9302529 en lugar de 1,0 — <strong>plagio de paráfrasis</strong>. También se esperaba que esta consulta, generada por IA, fuera detectada.</p><p>Por último, considerando la consulta de texto 'model_text' como texto sobre Elasticsearch, que no es un resumen de ninguno de estos documentos, la similitud detectada fue 0,68991005, lo que indica baja similitud según los valores umbral considerados.</p><p>Consulta:</p>#different text - not a plagiarism

model_text = 'Elasticsearch provides near real-time search and analytics for all types of data.'
<p>Salida:</p>Low similarity detected. This might not be plagiarism.
<p>Aunque el plagio se identificó correctamente en la consulta de texto generada por IA, así como en casos de paráfrasis y contenido copiado directamente, navegar por el panorama de la detección de plagio implica reconocer diversos aspectos.</p><p>En el contexto de la detección de contenido generado por IA, exploramos un modelo que aporta una contribución valiosa. Sin embargo, es fundamental reconocer las limitaciones inherentes a la detección independiente, lo que requiere incorporar otros métodos para mejorar la precisión.</p><p>La variabilidad introducida por la elección de modelos de incrustación de texto es otra consideración. Diferentes modelos, capacitados con conjuntos de datos distintos, resultan en distintos niveles de similitud, lo que destaca la importancia de las incrustaciones de texto generadas.</p><p>Por último, en estos ejemplos, usamos el resumen del documento. Sin embargo, la detección de plagio suele implicar documentos grandes, por lo que es esencial abordar el reto de la longitud del texto. Es común que el texto supere el límite de tokens de un modelo, requiriendo segmentación en fragmentos antes de construir incrustaciones. Un <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.11/knn-search.html#nested-knn-search">enfoque práctico</a> para manejar esto implica emplear estructuras anidadas con dense_vector.</p><h2>Conclusión</h2><p>En este blog, hablamos sobre los desafíos de detectar plagio, especialmente en contenido parafraseado y generado por IA, y cómo la similitud textual semántica y la clasificación de texto pueden emplear para este propósito.</p><p>Combinando estos métodos, proporcionamos un ejemplo de detección de plagio donde identificamos con éxito contenido generado por IA, plagio directo y parafraseado.</p><p>El objetivo principal era establecer un sistema de filtrado que simplificara la detección, pero la evaluación humana sigue siendo esencial para la validación.</p><p>Si te interesa aprender más sobre similitud textual semántica y PNL, te animamos a que también consultes estos enlaces:</p><ul><li><p><a href="https://www.elastic.co/what-is/semantic-search">¿Qué es la búsqueda semántica?</a></p></li><li><p><a href="https://www.elastic.co/what-is/natural-language-processing">¿Qué es el procesamiento del lenguaje natural (PLN)?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch">Búsqueda léxica y semántica con Elasticsearch</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/chunking-via-ingest-pipelines">Fragmentar documentos grandes mediante pipelines de ingesta más vectores anidados equivale a una búsqueda de pasajes fácil</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-plagiarism-checker-with-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-plagiarism-checker-with-elasticsearch</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Priscilla Parodi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68a5bc2434a9b03b/6a1711510e2e49a09641a22a/83e05cd4f81799fbb7b7950ed87600e825ec81e9-1024x1024.png" length="0" type="image/png"/>
    <pubDate>Tue, 19 Dec 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Usar búsqueda híbrida para la detección de errores con Elasticsearch y Go]]></title>
    <description><![CDATA[Aprende cómo lograr búsqueda híbrida combinando búsqueda por palabras clave y vectorial usando Elasticsearch y el cliente Elasticsearch Go.]]></description>
    <content:encoded><![CDATA[<p>En las partes anteriores de este serial, se demostró cómo usar el cliente Elasticsearch Go para <a href="https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client">la búsqueda tradicional por palabras clave</a> y <a href="https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client">la búsqueda vectorial</a>. Esta tercera parte trata sobre la búsqueda híbrida. Compartiremos <a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch">ejemplos</a> de cómo puedes combinar tanto búsqueda vectorial como búsqueda por palabras clave usando <a href="https://www.elastic.co/elasticsearch/">Elasticsearch</a> y el <a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">cliente Elasticsearch Go</a>.</p><h2>Prerrequisitos</h2><p>Al igual que en la primera parte de este serial, se requieren los siguientes requisitos previos para este ejemplo:</p><ol><li><p>Instalación de Go versión 1.21 o posterior</p></li><li><p>Crea tu propio repositorio de Go usando la estructura recomendada y la gestión de paquetes que se menciona en la <a href="https://go.dev/doc/code">documentación de Go</a></p></li><li><p>Creando tu propio clúster de Elasticsearch, poblado con un conjunto de <a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch#sources">páginas basadas en roedores</a>, incluyendo nuestro amigable <a href="https://en.wikipedia.org/wiki/Gopher">Gopher</a>, de Wikipedia:</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a436c42996e5172/6a1704cf0c48573d1c01a957/34fa81a9b4c292634c719b8303a9b6b7506d7920-1440x662.png" alt="Página de Wikipedia sobre los Topos" /><h2>Conexión con Elasticsearch</h2><p>Como recordatorio, en nuestros ejemplos emplearemos la <a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/typedapi.html">API</a> tipada que ofrece el cliente Go. Establecer una conexión segura para cualquier consulta requiere configurar al cliente usando cualquiera de:</p><ol><li><p>ID de la nube y clave API si se emplea Elastic Cloud</p></li><li><p>URL del clúster, nombre de usuario, contraseña y el certificado</p></li></ol><p>Conectarse a nuestro clúster ubicado en Elastic Cloud sería así:</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>La conexión <code>client</code> puede entonces emplear para búsqueda, como se demuestra en las secciones posteriores.</p><h2>Impulso manual para búsqueda híbrida</h2><p>Al combinar cualquier conjunto de algoritmos de búsqueda, el enfoque tradicional fue configurar manualmente las constantes para potenciar cada tipo de consulta. Específicamente, se especifica un factor para cada consulta, y el conjunto de resultados combinados se compara con el conjunto esperado para determinar la recuperación de la consulta. Luego repetimos para varios conjuntos de factores y elegimos el que esté más cerca de nuestro estado deseado.</p><p>Por ejemplo, combinar una consulta de búsqueda de texto individual potenciada por un factor de <code>0.8</code> con una consulta knn de menor factor de <code>0.2</code> puede hacer especificando el campo <code>Boost</code> en ambos tipos de consulta, como se muestra en el siguiente ejemplo:</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>El factor especificado en la opción de <code>Boost</code> para cada consulta se suma al puntaje del documento. Al aumentar el puntaje de nuestra consulta de coincidencia en un factor mayor que la consulta knn, los resultados de la consulta por palabra clave tienen un peso más fuerte.</p><p>El reto del aumento manual, especialmente si no eres un experto en búsquedas, es que requiere ajuste para identificar los factores que manejarán al conjunto de resultados deseado. Simplemente se trata de probar valores aleatorios para ver qué te acerca más al conjunto de resultados deseado.</p><h2>Fusión de rangos recíprocos en un cliente híbrido de búsqueda y Go</h2><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">Reciprocal Rank Fusion</a>, o RRF, se publicó en formato de vista previa técnica para búsqueda híbrida en Elasticsearch 8.9. Su objetivo es reducir la curva de aprendizaje asociada al ajuste y disminuir el tiempo dedicado a experimentar con factores para optimizar el conjunto de resultados.</p><p>Con RRF, el puntaje del documento se recalcula mezclando los puntajes mediante el siguiente algoritmo:</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>El beneficio de usar RRF es que podemos aprovechar los valores predeterminados sensatos dentro de Elasticsearch. La constante de clasificación <code>k</code> por defecto es <code>60</code>. Para proporcionar un equilibrio entre la relevancia de los documentos devueltos y el rendimiento de la consulta al buscar sobre grandes conjuntos de datos, el tamaño del conjunto de resultados para cada consulta considerada se limita al valor de <code>window_size</code>, que por defecto es <code>100</code> según se indica en la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html#rrf-api">documentación</a>.</p><p><code>k</code> y <code>windows_size</code> también pueden configurar dentro de la configuración <code>Rrf</code> dentro del método <code>Rank</code> en el cliente Go, como se muestra en el siguiente ejemplo:</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>Conclusión</h2><p>Aquí hablamos de cómo combinar la búsqueda vectorial y la búsqueda por palabras clave en Elasticsearch usando el <a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">cliente Elasticsearch Go</a>.</p><p>Echa un vistazo al <a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch">repositorio de GitHub</a> para ver todo el código de este serial. Si no lo hiciste ya, echa un vistazo a <a href="https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client">la parte 1</a> y <a href="https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client">la parte 2</a> para ver todo el código de este serial.</p><p><em>¡Feliz caza de topos!</em></p><h2>Recursos</h2><ol><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html">Guía Elasticsearch</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Cliente Elasticsearch Go</a></p></li><li><p><a href="https://www.elastic.co/what-is/vector-search">¿Qué es la búsqueda vectorial? | Elástico</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">Fusión recíproca de rangos</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[Base de datos vectorial]]></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[Realiza búsqueda vectorial en Elasticsearch con el cliente Elasticsearch Go]]></title>
    <description><![CDATA[Aprende a realizar búsqueda vectorial en Elasticsearch usando el cliente Elasticsearch Go a través de un ejemplo práctico.]]></description>
    <content:encoded><![CDATA[<p>Desarrollar software en cualquier lenguaje de programación, incluido Go, es comprometer con una vida entera de aprendizaje. A lo largo de su carrera universitaria y profesional, Carly experimentó con muchos lenguajes de programación y tecnologías, incluyendo las últimas y mejores implementaciones de búsqueda vectorial. ¡Pero eso no fue suficiente! Así que recientemente Carly también empezó a jugar con Go.</p><p>Al igual que los animales, los lenguajes de programación y tu autor amable, la búsqueda experimentó una evolución de diferentes prácticas que pueden ser difíciles de elegir para tu propio caso de uso. En este blog, compartiremos una visión general de la búsqueda vectorial junto con <a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch">ejemplos</a> de cada enfoque usando <a href="https://www.elastic.co/elasticsearch/">Elasticsearch</a> y el <a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">cliente Elasticsearch Go</a>. Estos ejemplos te mostrarán cómo encontrar topos y determinar qué comen usando búsqueda vectorial en Elasticsearch y Go.</p><h2>Prerrequisitos</h2><p>Para seguir con este ejemplo, cerciorar de cumplir los siguientes requisitos:</p><ol><li><p>Instalación de Go versión 1.21 o posterior</p></li><li><p>Creación de tu propio repositorio Go con el</p></li><li><p>Creación de tu propio clúster Elasticsearch, poblado con un conjunto de páginas basadas en roedores, incluyendo para nuestro <a href="https://en.wikipedia.org/wiki/Gopher">amable Gopher</a>, de Wikipedia:</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a436c42996e5172/6a1704cf0c48573d1c01a957/34fa81a9b4c292634c719b8303a9b6b7506d7920-1440x662.png" alt="Página de Wikipedia sobre los Topos" /><h2>Conexión con Elasticsearch</h2><p>En nuestros ejemplos, emplearemos la <a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/typedapi.html">API tipada</a> que ofrece el cliente Go. Establecer una conexión segura para cualquier consulta requiere configurar al cliente usando cualquiera de:</p><ol><li><p>Cloud ID y clave API si se emplea Elastic Cloud.</p></li><li><p>URL del clúster, nombre de usuario, contraseña y el certificado.</p></li></ol><p>Conectarse a nuestro clúster ubicado en Elastic Cloud sería así:</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>La conexión <code>client</code> puede entonces emplear para búsqueda vectorial, como se muestra en secciones posteriores.</p><h2>Búsqueda de vectores</h2><p>La búsqueda vectorial intenta resolver este problema convirtiendo el problema de búsqueda en una comparación matemática usando vectores. El proceso de incrustación de documentos tiene una etapa adicional de convertir el documento usando un modelo en una representación vectorial densa, o simplemente en un flujo de números. El beneficio de este enfoque es que puedes buscar documentos no textuales, como imágenes y audio, traduciéndolos a un vector junto con una consulta.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc4b3b9b2ad1e8f46/6a1704d06234e0800fdb1927/b113358093e367358d684f7aaf0a6684ebb2d0dd-1440x653.png" alt="Diagrama de búsqueda vectorial" /><p>En términos simples, la búsqueda vectorial es un conjunto de cálculos de distancia vectorial. En la ilustración siguiente, la representación vectorial de nuestro <code>Go Gopher</code>de consulta se compara con los documentos en el espacio vectorial, y se devuelven los resultados más cercanos (denotados por <code>k</code>constantes):</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5c736ecfc49bb038/6a1704d2cf4f25bcb6b2d075/54a17a4b41029644e7c66f33d428e8d01a6f4ce3-1184x743.png" alt="Ejemplo de espacio vectorial de topo" /><p>Dependiendo del método que se emplee para generar los embeddings de tus documentos, hay dos formas diferentes de saber qué comen los topos.</p><h3>Enfoque 1: Trae tu propio modelo</h3><p>Con una licencia Platinum, es posible generar las incrustaciones dentro de Elasticsearch subiendo el modelo y usando la API de inferencia. Hay seis pasos implicados para establecer el modelo:</p><ol><li><p>Selecciona un modelo de PyTorch para subirlo desde un repositorio de modelos. Para este ejemplo, estamos usando <a href="https://huggingface.co/sentence-transformers/msmarco-MiniLM-L-12-v3">los transformadores de frase/msmarco-MiniLM-L-12-v3</a> de Hugging Face para generar los embeddings.</p></li><li><p>Carga el modelo en Elastic usando el <a href="https://www.elastic.co/guide/en/elasticsearch/client/eland/current/overview.html">cliente Eland Machine Learning para Python</a> usando las credenciales de nuestro clúster Elasticsearch y tipo de tarea <code>text_embeddings</code>. Si no tienes Eland instalado, puedes <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-import-model.html#ml-nlp-import-docker">ejecutar el paso de importación usando Docker</a>, como se muestra a continuación:</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>Una vez cargado, prueba rápidamente el <code>sentence-transformers__msmarco-minilm-l-12-v3</code> del modelo con un documento de muestra para cerciorarte de que las incrustaciones se generan como se espera:</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a399e1f50ecfdaf/6a1704d4ab7f08935edb9d78/fbbdde621361a487eb07304bed029c228d0f7aa6-1440x789.png" alt="Ejemplo de modelo capacitado por pruebas elásticas" /><ol><li><p>Crear una tubería de ingesta que contenga un procesador de inferencia. Esto permitirá generar la representación vectorial usando el modelo subido:</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>Crea un nuevo índice que contenga el <code>text_embedding.predicted_value</code> de campo de tipo <code>dense_vector</code> para almacenar las incrustaciones vectoriales generadas para cada documento:</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>Reindexa los documentos usando la nueva tubería de ingesta para generar las incrustaciones de texto como el campo adicional <code>text_embedding.predicted_value</code> en cada documento:</p></li></ol>POST _reindex
{
  "source": {
    "index": "search-rodents"
  },
  "dest": {
    "index": "vector-search-rodents",
    "pipeline": "search-rodents-vector-embedding-pipeline"
  }
}
<p>Ahora podemos usar la opción <code>Knn</code> en la misma API de búsqueda usando el nuevo índice <code>vector-search-rodents</code>, como se muestra en el siguiente ejemplo:</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>La conversión del objeto de resultado JSON mediante desmaravillamiento se realiza exactamente de la misma manera que en el ejemplo de búsqueda por palabra clave. Las constantes <code>K</code> y <code>NumCandidates</code> nos permiten configurar el número de documentos vecinos que devolver y el número de candidatos a considerar por fragmento. Ten en cuenta que aumentar el número de candidatos incrementa la precisión de los resultados pero conduce a una consulta de mayor duración a medida que se realizan más comparaciones.</p><p>Cuando el código se ejecuta usando la consulta <code>What do Gophers eat?</code>, los resultados devuelto se ven similares a los siguientes, destacando que el artículo de Gopher contiene la información aplicar, a diferencia de la búsqueda por palabra clave anterior:</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>Enfoque 2: API de inferencia de rostros de abrazo</h3><p>Otra opción es generar estas mismas incrustaciones fuera de Elasticsearch e ingerirlas como parte de tu documento. Como esta opción no emplea un nodo de aprendizaje automático de Elasticsearch, puede hacer en la capa libre.</p><p>Hugging Face expone una <a href="https://huggingface.co/docs/api-inference/index">API de inferencia</a> gratis y limitada a la velocidad que, con una cuenta y un token de API, puede usar para generar manualmente las mismas incrustaciones para experimentación y prototipado que te ayude a empezar. No se recomienda para uso en producción. Invocar tus propios modelos localmente para generar embeddings o usar la API de pago también puede hacer con un enfoque similar.</p><p>En la función <code>GetTextEmbeddingForQuery</code> siguiente usamos la API de inferencia contra nuestra cadena de consulta para generar el vector devuelto de una petición <code>POST</code> al punto final:</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>El vector resultante, de tipo <code>[]float32</code> , se pasa entonces como <code>QueryVector</code> en lugar de usar la opción <code>QueryVectorBuilder</code> para aprovechar el modelo previamente subido a 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>Cabe señalar que las opciones de <code>K</code> y <code>NumCandidates</code> siguen siendo las mismas independientemente de las dos opciones y que se generan los mismos resultados, ya que estamos usando el mismo modelo para generar las incrustaciones</p><h2>Conclusión</h2><p>Aquí discutimos cómo realizar búsqueda vectorial en Elasticsearch usando el <a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">cliente Elasticsearch</a> Go. Echa un vistazo al <a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch">repositorio de GitHub</a> para ver todo el código de este serial. Sigue la <a href="https://www.elastic.co/search-labs/blog/hybrid-search-with-the-elasticsearch-go-client">parte 3</a> para obtener una visión general de cómo combinar la búsqueda vectorial con las capacidades de búsqueda por palabras clave que se tratan en <a href="https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client">la primera parte</a> de Go.</p><p>Hasta entonces, ¡feliz caza de topos!</p><h2>Recursos</h2><ol><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html">Guía Elasticsearch</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Cliente Elasticsearch Go</a></p></li><li><p><a href="https://www.elastic.co/what-is/vector-search">¿Qué es la búsqueda vectorial? | Elástico</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[Base de datos vectorial]]></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[Búsqueda léxica y semántica con Elasticsearch]]></title>
    <description><![CDATA[En este blog, exploraremos diversos enfoques para recuperar información empleando Elasticsearch, centrándonos en la búsqueda léxica y semántica.]]></description>
    <content:encoded><![CDATA[<p>La búsqueda es el proceso de localizar la información más relevante basar en tu consulta de búsqueda o en la combinación de consultas y resultados de búsqueda relevantes son documentos que mejor se ajustan a estas consultas. Aunque existen varios desafíos y métodos asociados a la búsqueda, el objetivo final sigue siendo el mismo: <strong>encontrar la mejor respuesta posible a tu pregunta</strong>.</p><p>Teniendo en cuenta este objetivo, en esta entrada de blog exploraremos diferentes enfoques para recuperar información usando Elasticsearch, con un enfoque específico en la búsqueda de texto: <strong>búsqueda léxica y semántica.</strong></p><h2>Prerrequisitos</h2><p>Para lograrlo, proporcionaremos ejemplos en Python que demuestren diversos escenarios de búsqueda en un <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/products-ecommerce.json">conjunto de datos</a> generado para simular información de productos de comercio electrónico.</p><p>Este conjunto de datos contiene más de 2.500 productos, cada uno con una descripción. Estos productos se clasifican en 76 categorías distintas, cada una conteniendo un número variable de productos, como se muestra a continuación:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt34415151c00ced2b/6a17d8710b0bed6c9bdd342c/4104466050f3024b6bcaf382da2a702650f62227-1440x708.png" alt="" /><p><em>Visualización de treemap: 22 valores principales de category.keyword (categorías de productos)</em></p><p>Para la configuración necesitarás:</p><ul><li><p>Python 3.6 o posterior</p></li><li><p>El <a href="https://www.elastic.co/guide/en/elasticsearch/client/python-api/current/index.html">cliente Python Elástico</a></p></li><li><p>Despliegue Elastic 8.8 o posterior, con nodo de aprendizaje automático de 8GB de memoria</p></li><li><p>El modelo <a href="https://www.elastic.co/guide/en/machine-learning/8.9/ml-nlp-elser.html">Elastic Learned Sparse EncodeR</a> , que viene preinstalado en Elastic, se instaló y comenzó en tu despliegue</p></li></ul><p>Usaremos Elastic Cloud, <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">hay una prueba gratis disponible</a>.</p><p>Además de las consultas de búsqueda que se proporcionan en esta entrada del blog, un <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">cuaderno de Python</a> te guiará a través de los siguientes procesos:</p><ul><li><p>Establece una conexión con nuestro despliegue de Elastic usando el cliente de Python</p></li><li><p>Carga un modelo de incrustación de texto en el clúster de Elasticsearch</p></li><li><p>Crea un índice con mapeos para indexar vectores de características y vectores densos.</p></li><li><p>Crear una tubería de ingesta con procesadores de inferencia para incrustación y expansión de texto</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0e95406ece8f08d9/6a17d8731d1b83d32e93e2f4/54a9a490a3b0cf1b9c2228bee8eddd3f566bd435-1418x1102.png" alt="" /><h2>Búsqueda léxica - recuperación dispersa</h2><p>La forma tradicional en que los documentos se clasifican para relevancia según Elasticsearch basar en una consulta de texto emplea la implementación Lucene del modelo <a href="https://en.wikipedia.org/wiki/Okapi_BM25">BM25</a> , un <strong>modelo disperso para la búsqueda léxica</strong>. Este método sigue el enfoque tradicional de búsqueda de texto, buscando coincidencias exactas de términos.</p><p>Para hacer posible esta búsqueda, Elasticsearch convierte los datos <strong>de los campos de texto</strong> en un formato buscable mediante análisis de texto.</p><p><strong>El análisis de texto</strong> se realiza mediante un<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer-anatomy.html"> analizador</a>, un conjunto de reglas que regulan el proceso de extracción de tokens relevantes para la búsqueda. Un analizador debe tener exactamente un<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html"> tokenizador</a>. El tokenizador recibe una secuencia de caracteres y la divide en fichas individuales (normalmente palabras individuales), como en el ejemplo siguiente:</p><h3>Tokenización de cadenas para búsqueda léxica</h3>#Performs text analysis on a string and returns the resulting tokens.

# Define the text to be analyzed
text = "Comfortable furniture for a large balcony"

# Define the analyze request
request_body = {
  "analyzer": "standard",
  "text": text
}

# Perform the analyze request
response = client.indices.analyze(analyzer=request_body["analyzer"], text=request_body["text"])

# Extract and display the analyzed tokens
tokens = [token["token"] for token in response["tokens"]]
print("Analyzed Tokens:", tokens)
<p>Salida</p>Analyzed Tokens: ['comfortable', 'furniture', 'for', 'a', 'large', 'balcony']
<p>En este ejemplo estamos usando el analizador por defecto, el <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-standard-analyzer.html">analizador estándar</a> , que funciona bien para la mayoría de los casos de uso ya que proporciona tokenización basada en gramática inglesa. La tokenización permite la coincidencia en términos individuales, pero cada token sigue siendo emparejado literalmente.</p><p>Si quieres personalizar tu experiencia de búsqueda, puedes elegir otro<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html"> analizador integrado</a> diferente. Por ejemplo, actualizando el código para usar el <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-stop-analyzer.html">analizador de atajada</a> , se descompone el texto en tokens en cualquier carácter que no sea letra, con soporte para eliminar palabras de atajada.</p>...
# Define the analyze request
request_body = {
  "analyzer": "stop",
  "text": text
}
...
<p>Salida</p>Analyzed Tokens: ['comfortable', 'furniture', 'large', 'balcony']
<p>Cuando los analizadores integrados no satisfacen tus necesidades, puedes crear un <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-custom-analyzer.html">analizador personalizado</a>, que emplee la combinación adecuada de filtros de cero o <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-charfilters.html">más caracteres</a>, un <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html">tokenizador</a> y filtros de cero o más <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenfilters.html">tokens</a>.</p>"analyzer":  {

  "my_analyzer": {

    "type": "custom", #For custom analyzers, use a type of custom or omit the type parameter.

    "tokenizer": "standard", #Built-in or customized tokenizer

    "filter": ["lowercase", "synonym"] #Built-in or customized token filters
  }
}
<p>En el ejemplo anterior, que combina un tokenizador y filtros de token, el texto se pondrá en minúsculas con el <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lowercase-tokenfilter.html">filtro</a> minúsculo antes de ser procesado por el <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-synonym-tokenfilter.html#:~:text=Elasticsearch%20will%20use%20the%20token,applied%20to%20the%20synonym%20entries.">filtro de sinónimos.</a></p><h2>Emparejamiento léxico</h2><p><a href="https://www.elastic.co/blog/practical-bm25-part-2-the-bm25-algorithm-and-its-variables">BM25</a> medirá la relevancia de los documentos para una consulta de búsqueda determinada en función de la frecuencia de los términos y su importancia.</p><p>El código siguiente realiza una consulta <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html">de coincidencia</a>, buscando hasta dos documentos que consideren los valores del campo <em>"descripción"</em> del índice <em>"ecommerce-search"</em> y la consulta <strong>de búsqueda "</strong><em><strong>Muebles cómodos para un balcón grande</strong></em><strong>".</strong></p><p>Refinar los criterios para que un documento se considere compatible con esta consulta puede mejorar la precisión. Sin embargo, los resultados más específicos tienen el costo de una menor tolerancia a las variaciones.</p># BM25

response = client.search(size=2,
index="ecommerce-search",
query= {
  "match": {
    "description" : {  
      "query": "Comfortable furniture for a large balcony",
      "analyzer": "stop"
    }
  }
}
)

hits = response['hits']['hits']

if not hits:
  print("No matches found")

else:
  for hit in hits:
    score = hit['_score']
    product = hit['_source']['product']
    category = hit['_source']['category']
    description = hit['_source']['description']
    print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>Salida</p>Score: 15.607948
Product: Barbie Dreamhouse
Category: Toys
Description: is a classic Barbie playset with multiple rooms, furniture, a large balcony, a pool, and accessories. It allows kids to create their dream Barbie world.

Score: 9.137739
Product: Comfortable Rocking Chair
Category: Indoor Furniture
Description: enjoy relaxing moments with this comfortable rocking chair. Its smooth motion and cushioned seat make it an ideal piece of furniture for unwinding.
<p>Analizando el resultado, el resultado más relevante es el producto "<em>Barbie Dreamhouse</em>", en la categoría "<em>Juguetes</em>", y su descripción es muy relevante ya que incluye los términos "<em>muebles</em>", "<em>grande"</em> y <em>"balcón";</em>este es el único producto con 3 términos en la descripción que coinciden con la consulta de búsqueda,  El producto es también el único que tiene el <em>término "balcón"</em> en la descripción.</p><p>El segundo producto más relevante es una "<em>Mecedora Cómoda</em>" categorizada como "<em>Mobiliario de Interior</em>" y su descripción incluye los términos "<em>cómodo</em>" y "<em>mueble".</em> Solo 3 productos en el conjunto de datos coinciden con al menos 2 términos de esta consulta de búsqueda, este producto es uno de ellos.</p><p><em>"Cómodo"</em> aparece en la descripción de 105 productos y <em>"muebles"</em> en la descripción de 4 productos con 4 categorías diferentes: <em>Juguetes</em>, <em>Muebles de interior, Muebles de exterior y 'Suministros y Juguetes para perros y gatos'.</em></p><p>Como puedes ver, el producto más relevante para la pregunta es un juguete y el segundo producto más relevante son los muebles de interior. Si quieres información detallada sobre el cálculo de puntaje para saber por qué estos documentos coinciden, puedes poner el <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-explain.html"><em>parámetro de explicación</em></a> __query en verdadero.</p><p>A pesar de que ambos resultados son los más relevantes, considerando tanto el número de documentos como la presencia de términos en este conjunto de datos, la intención detrás de la consulta "<em>Cómodos muebles para un balcón grande</em>" es buscar muebles para un balcón grande real, excluyendo, entre otros, juguetes y muebles de interior.</p><p>La búsqueda léxica es relativamente <strong>sencilla y rápida</strong>, pero tiene limitaciones ya que no siempre es posible conocer todos los términos y sinónimos posibles sin necesariamente conocer la intención y las consultas del usuario. Un fenómeno común en el uso del lenguaje natural es la <strong>desadaptación del vocabulario</strong>. <a href="https://dl.acm.org/doi/abs/10.1145/32206.32212">Las investigaciones</a> muestran que, de media, el <strong>80% de las veces</strong> diferentes personas (expertos en el mismo campo) nombran el mismo nombre de forma distinta.</p><p>Estas limitaciones nos motivan a buscar otros modelos de puntaje que incorporen conocimientos semánticos. Los modelos basados en transformadores, que destacan en el procesamiento de tokens de entrada secuenciales como el lenguaje natural, capturan el significado subyacente de tu búsqueda considerando representaciones matemáticas tanto de documentos como de consultas. Esto permite una representación vectorial densa y consciente del contexto del texto, impulsando <strong>la Búsqueda Semántica</strong>, una forma refinada de encontrar contenido relevante.</p><h2>Búsqueda semántica - recuperación densa</h2><p>En este contexto, tras convertir tus datos en valores vectoriales significativos, se emplea el algoritmo de búsqueda <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">de k-vecinos más cercanos (kNN)</a> para encontrar representaciones vectoriales en un conjunto de datos que sean más similares a un vector de consulta. Elasticsearch soporta dos métodos para la búsqueda en kNN: <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#exact-knn">kNN exacto de fuerza bruta</a> y <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#approximate-knn">kNN aproximado</a>, también conocido como ANN.</p><p>La kNN de fuerza bruta garantiza resultados precisos pero no escala bien con grandes conjuntos de datos. El kNN aproximado encuentra eficientemente a los vecinos más cercanos sacrificando cierta precisión para mejorar el rendimiento.</p><p>Con el soporte de Lucene para la búsqueda kNN y los índices vectoriales densos, Elasticsearch aprovecha el algoritmo Hierarchical Navigable Small World (HNSW), que demuestra un rendimiento estable en búsquedas en una variedad de <a href="http://ann-benchmarks.com/">conjuntos de datos ann-benchmark</a>. Se puede realizar una búsqueda aproximada de kNN en Python usando el código de ejemplo siguiente.</p><h3>Búsqueda semántica con kNN aproximado</h3># KNN - approximate kNN

response = client.search(index='ecommerce-search', size=2,
knn={
  "field": "description_vector.predicted_value",
  "k": 50, # Number of nearest neighbors to return as top hits.
#The optimal value of k is dependent on the data. It can vary in different scenarios.

  "num_candidates": 500, # Number of nearest neighbor candidates to consider per shard.

#Increasing num_candidates tends to improve the accuracy of the final k results.

  "query_vector_builder": { # Object indicating how to build a query_vector. kNN search enables you to perform semantic search by using a previously deployed text embedding model, the steps for this process are demonstrated in the Python notebook.
    "text_embedding": { 
      "model_id": "sentence-transformers__all-mpnet-base-v2", # Text embedding model id
      "model_text": "Comfortable furniture for a large balcony" # Query
    }
  }
}
)

for hit in response['hits']['hits']:
        
  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>Este bloque de código emplea kNN de Elasticsearch para devolver hasta dos productos con una descripción similar a la consulta vectorizada (query_vector_build) de "<em>Cómodo mobiliario para un balcón grande</em>" considerando las incrustaciones del campo "<em>descripción</em>" en el conjunto de datos de productos.</p><p>Las incrustaciones de productos se generaban previamente en una tubería de ingesta con un procesador de inferencia que contenía el modelo de incrustación <em>de texto "</em><a href="https://huggingface.co/sentence-transformers/all-mpnet-base-v2"><em>all-mpnet-base-v2</em></a><em>"</em> para inferir contra los datos que se estaban ingeriendo en la canalización.</p><p>Este modelo se eligió basar en la evaluación de modelos preentrenados usando <em>"</em><a href="https://github.com/UKPLab/sentence-transformers/blob/master/docs/package_reference/sentence_transformer/evaluation.md"><em>sentence_transformers.evaluation</em></a><em>"</em> donde se emplean diferentes clases para evaluar un modelo durante el entrenamiento. El modelo "all-mpnet-base-v2" mostró el mejor rendimiento medio según la <a href="https://www.sbert.net/docs/pretrained_models.html">clasificación de Sentence-Transformers</a> y también cercioró una posición favorable en la tabla de clasificación <a href="https://huggingface.co/spaces/mteb/leaderboard">del Massive Text Embedding Benchmark (MTEB</a> ). El modelo preentrenado<a href="https://huggingface.co/microsoft/mpnet-base"> es Microsoft y mpnet-base</a> y ajustado finamente en un conjunto de datos de pares de oraciones de 1B, que mapea oraciones a un espacio vectorial denso de 768 dimensiones.</p><p>Alternativamente, existen muchos otros modelos disponibles que se pueden emplear, especialmente aquellos ajustados para los datos específicos de tu dominio.</p><p>Salida</p>Score: 0.79207325
Product: Patio Sofa Set with Ottoman
Category: Outdoor Furniture
Description: is a versatile and comfortable patio sofa set, including a sofa, ottoman, and coffee table, great for outdoor lounging.

Score: 0.7836937
Product: Patio Sofa Set with Canopy
Category: Outdoor Furniture
Description: is a luxurious and comfortable patio sofa set with a canopy, providing shade and style for outdoor lounging.
<p><em>La salida puede variar según el modelo elegido,</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#knn-search-filter-example"><em>los filtros</em></a> <em>y</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#tune-approximate-knn-for-speed-accuracy"><em>la afinación aproximada de kNN</em></a><em>.</em></p><p>Los resultados de búsqueda de kNN están ambos en la categoría de "<em>Muebles de exterior</em>", aunque la palabra "<em>exterior</em>" no se mencionó explícitamente en la consulta, lo que resalta la importancia de la comprensión semántica en este contexto.</p><p>La búsqueda vectorial densa ofrece varios beneficios:</p><ul><li><p>Activación de la búsqueda semántica</p></li><li><p>Escalabilidad para manejar conjuntos de datos muy grandes</p></li><li><p>Flexibilidad para manejar una amplia variedad de tipos de datos</p></li></ul><p>Sin embargo, la <strong>búsqueda vectorial densa también conlleva sus propios desafíos</strong>:</p><ul><li><p>Seleccionar el modelo de incrustación adecuado para tu caso de uso</p></li><li><p>Una vez elegido un modelo, puede ser necesario ajustarlo para optimizar el rendimiento en un conjunto de datos específico de un dominio, un proceso que requiere la participación de expertos en el dominio</p></li><li><p>Además, indexar vectores de alta dimensión puede ser computacionalmente costoso</p></li></ul><h2>Búsqueda semántica - recuperación escasa aprendida</h2><p>Exploremos un enfoque alternativo: la recuperación esparsa aprendida, otra forma de realizar búsqueda semántica.</p><p>Como modelo escaso, emplea el índice invertido basado en Lucene de Elasticsearch, que se beneficia de décadas de optimizaciones. Sin embargo, este enfoque va más allá de simplemente agregar sinónimos con funciones de puntaje léxico como BM25. En su lugar, incorpora asociaciones aprendidas empleando un conocimiento más profundo a escala del lenguaje para optimizar su relevancia.</p><p>Al ampliar las consultas de búsqueda para incluir términos relevantes que no están presentes en la consulta original, el <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">Codificador Escaso Aprendido</a> <strong>Elástico mejora las incrustaciones vectoriales dispersas</strong>, como puedes ver en el ejemplo siguiente.</p><h3>Búsqueda vectorial dispersa con codificador elástico aprendido de dispersión</h3># Elastic Learned Sparse Encoder

response = client.search(index='ecommerce-search', size=2,
query={
  "text_expansion": {
    "ml.tokens": {
      "model_id":"elser_model",
      "model_text":"Comfortable furniture for a large balcony"                
    }
  }
}
)

for hit in response['hits']['hits']:

  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>Salida</p>Score: 14.405318
Product: Garden Lounge Set with Side Table
Category: Garden Furniture
Description: is a comfortable and stylish garden lounge set, including a sofa, chairs, and a side table for outdoor relaxation.

Score: 14.281318
Product: Rattan Patio Conversation Set
Category: Outdoor Furniture
Description: is a stylish and comfortable outdoor furniture set, including a sofa, two chairs, and a coffee table, all made of durable rattan material.
<p>Los resultados en este caso incluyen la categoría "<em>Muebles de jardín</em>", que ofrece productos bastante similares a los "<em>Muebles de Exterior</em>".</p><p>Analizando "ml.tokens", el campo "rank_features" que contiene tokens generados por Recuperación Escasa Aprendida, se hace evidente que entre los diversos tokens generados hay términos que, aunque no forman parte de la consulta de búsqueda, siguen siendo relevantes en su significado, como "<em>relax</em>" (cómodo), "<em>sofá</em>" (muebles) y "<em>exterior</em>" (balcón).</p><p>La imagen de abajo destaca algunos de estos términos junto con la consulta, tanto con como sin ampliación de términos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7e869985347b19a7/6a17d875e31791dc2c2d56a1/dd86607fce6137d843a3ec002390eaa988b432f9-1440x502.png" alt="" /><p>Como se observó, este modelo proporciona una búsqueda consciente del contexto y ayuda a mitigar el problema de la descoordinación de vocabulario, a la vez que proporciona resultados más interpretables. Incluso puede superar a los modelos vectoriales densos cuando no se aplica un reentrenamiento específico de dominio.</p><h2>Búsqueda híbrida: resultados relevantes combinando la búsqueda léxica y semántica</h2><p>Cuando se trata de búsqueda, no hay una solución universal. Cada uno de estos métodos de recuperación tiene sus fortalezas pero también sus desafíos. Dependiendo del caso de uso, la mejor opción puede cambiar. A menudo, los mejores resultados entre los métodos de recuperación pueden ser complementarios. Por tanto, para mejorar la relevancia, veremos la combinación de las fortalezas de cada método.</p><p>Existen múltiples formas de implementar una <strong>búsqueda híbrida</strong>, incluyendo la combinación lineal, que da un peso a cada puntaje y la fusión recíproca de rangos (RRF), donde no es necesario especificar un peso.</p><h3>Elasticsearch: lo mejor de ambos mundos con búsqueda léxica y semántica</h3># BM25 + Elastic Learned Sparse Encoder (Linear Combination)

response = client.search(index='ecommerce-search', size=2,

query= {
  "bool": {
    "should": [
    {
      "match": {
        "description" : {  
          "query": "A dining table and comfortable chairs for a large balcony",
          "boost": 1
        }
      }
    },                   
    {
      "text_expansion": {
        "ml.tokens": {
          "model_id": "elser_model",
          "model_text": "A dining table and comfortable chairs for a large balcony",
          "boost": 1
        }
      }
     }
    ]
  }
}
)

# The boost value is 1 for the text expansion and match query. This means that the relevance score of the results of these queries are not boosted. You can specify a boost value to give a weight to each score in the sum. The scores will be calculated as: score = boost value * match_score + boost value * text_expansion_score

for hit in response['hits']['hits']:

  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>En este código, realizamos una búsqueda híbrida con dos consultas que tenían el valor "<em>Una mesa de comedor y sillas cómodas para un balcón grande</em>". En lugar de usar "<em>muebles</em>" como término de búsqueda, especificamos lo que buscamos, y ambas búsquedas consideran los mismos valores de campo, "descripción". La clasificación se determina mediante una combinación lineal con el mismo peso para los puntajes BM25 y ELSER.</p><p>Salida</p>Score: 31.628141
Product: Garden Dining Set with Swivel Rockers
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel rockers for easy movement.

Score: 31.334227
Product: Garden Dining Set with Swivel Chairs
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel seats for convenience.
<p>En el código siguiente, usaremos el mismo valor para la consulta, pero combinaremos los puntajes de BM25 (parámetro de consulta) y kNN (parámetro knn) usando el método de fusión de rangos recíprocos para combinar y clasificar los documentos.</p># BM25 + KNN (RRF)

response = client.search(index='ecommerce-search', size=2,
query={
  "bool": {
    "should": [
    {
      "match": {
        "description": {
        "query": "A dining table and comfortable chairs for a large balcony"
        }
      }
    }
    ]
  }
},
knn={
  "field": "description_vector.predicted_value",
  "k": 50,
  "num_candidates": 500,
  "query_vector_builder": {
    "text_embedding": {
      "model_id": "sentence-transformers__all-mpnet-base-v2",
      "model_text": "A dining table and comfortable chairs for a large balcony"
    }
  }
},
rank={
  "rrf": { # Reciprocal rank fusion
    "window_size": 50, # This value determines the size of the individual result sets per query.
    "rank_constant": 20 # This value determines how much influence documents in individual result sets per query have over the final ranked result set.
  }
}
)

for hit in response['hits']['hits']:
        
  rank = hit['_rank']
  category = hit['_source']['category']
  product = hit['_source']['product']
  description = hit['_source']['description']
  print(f"\nRank: {rank}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p><em>La funcionalidad de RRF está en vista previa técnica. La sintaxis probablemente cambiará antes de GA.</em></p><p>Salida</p>Rank: 1
Product: Patio Dining Set with Bench
Category: Outdoor Furniture
Description: is a spacious and functional patio dining set, including a dining table, chairs, and a bench for additional seating.

Rank: 2
Product: Garden Dining Set with Swivel Chairs
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel seats for convenience.
<p>Aquí también podríamos usar diferentes campos y valores; algunos de estos ejemplos están disponibles en el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">cuaderno de Python</a>.</p><p>Como puedes ver, con Elasticsearch tienes lo mejor de ambos mundos: la búsqueda léxica tradicional y la búsqueda vectorial, ya sea escasa o densa, para alcanzar tu objetivo <strong>y encontrar la mejor respuesta posible a tu pregunta.</strong></p><p>Si quieres seguir aprendiendo sobre los enfoques mencionados aquí, estos blogs pueden ser útiles:</p><ul><li><p><a href="https://www.elastic.co/blog/improving-information-retrieval-elastic-stack-hybrid">Mejorar la recuperación de información en el Elastic Stack: Recuperación híbrida</a></p></li><li><p><a href="https://www.elastic.co/blog/vector-search-elasticsearch-rationale">Búsqueda vectorial en Elasticsearch: La razón detrás del diseño</a></p></li><li><p><a href="https://www.elastic.co/blog/lexical-ai-powered-search-elastic-vector-database">Cómo obtener lo mejor de la búsqueda léxica y basada en IA con la base de datos vectorial de Elastic</a></p></li><li><p><a href="https://www.elastic.co/blog/may-2023-launch-sparse-encoder-ai-model">Presentamos Elastic Learned Sparse Encoder: el modelo de IA de Elastic para búsqueda semántica</a></p></li><li><p><a href="https://www.elastic.co/blog/may-2023-launch-information-retrieval-elasticsearch-ai-model">Mejorando la recuperación de información en el Elastic Stack: Presentamos Elastic Learned Sparse Encoder, nuestro nuevo modelo de recuperación</a></p></li></ul><p>Elasticsearch proporciona una base de datos vectorial, junto con todas las herramientas que necesitas para construir la búsqueda vectorial:</p><ul><li><p><a href="https://www.elastic.co/elasticsearch/vector-database">Base de datos de vectoresElasticsearch</a></p></li><li><p>Casos de uso <a href="https://www.elastic.co/enterprise-search/vector-search">de búsqueda vectorial</a> con Elastic</p></li></ul><h2>Conclusión</h2><p>En esta entrada del blog, exploramos varios enfoques para recuperar información usando Elasticsearch, centrándonos específicamente en la búsqueda textual, léxica y semántica. Para demostrarlo, proporcionamos ejemplos en Python que muestran diferentes escenarios de búsqueda empleando un conjunto de datos que contiene información de productos de comercio electrónico.</p><p>Revisamos la búsqueda léxica tradicional con BM25 y discutimos sus beneficios y desafíos, como la descoordinación del vocabulario. Destacamos la importancia de incorporar el conocimiento semántico para superar este problema. Además, hablamos sobre la búsqueda vectorial densa, que permite la búsqueda semántica, y abordamos los retos asociados a este método de recuperación, incluyendo el costo computacional al indexar vectores de alta dimensión.</p><p>Por otro lado, mencionamos que los vectores dispersos comprimen excepcionalmente bien. Por ello, hablamos del Learned Sparse Encoder de Elastic, que amplía las consultas de búsqueda para incluir términos relevantes que no estaban presentes en la consulta original.</p><p>No existe una solución única para todos en cuanto a búsqueda. Cada método de recuperación tiene sus fortalezas y desafíos. Por ello, también discutimos el concepto de búsqueda híbrida.</p><p>Como puedes ver, con Elasticsearch puedes tener lo mejor de ambos mundos: ¡búsqueda léxica tradicional y búsqueda vectorial!</p><p>¿Listo para empezar? Consulta el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">cuaderno Python</a> disponible y comienza una <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">prueba gratis de Elastic Cloud</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <category><![CDATA[Python]]></category>
    <category><![CDATA[Lenguajes de búsqueda]]></category>
    <dc:creator><![CDATA[Priscilla Parodi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbd7e961f594005e7/6a17d80f033c8d981f6bb009/d240bfef29e9d432069059b312dd044eb76eec6c-1440x840.png" length="0" type="image/png"/>
    <pubDate>Tue, 03 Oct 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Mejorando las capacidades de los chatbots con PLN y búsqueda vectorial en Elasticsearch]]></title>
    <description><![CDATA[Explora cómo la búsqueda vectorial y el PLN mejoran las capacidades de los chatbots y descubre cómo Elasticsearch facilita el proceso.]]></description>
    <content:encoded><![CDATA[<p>Las interfaces conversacionales existen desde hace tiempo y cada vez son más populares como medio para ayudar en diversas tareas, como atención al cliente, recuperación de información y automatización de tareas. Normalmente accesibles a través de asistentes de voz o aplicaciones de mensajería, estas interfaces simulan la conversación humana para ayudar a los usuarios a resolver sus consultas de forma más eficiente.</p><p>A medida que avanza la tecnología, los chatbots se emplean para gestionar tareas más complejas —y de forma rápida— sin dejar de ofrecer una experiencia personalizada para los usuarios. El procesamiento del lenguaje natural (PLN) permite a los chatbots procesar el lenguaje del usuario, identificar la intención detrás de su mensaje y extraer información relevante de él. Por ejemplo, el Reconocimiento de Entidades Nombradas extrae información clave de un texto clasificándolos en un conjunto de categorías. El análisis de sentimiento identifica el tono emocional y la pregunta responde a la "respuesta" a una consulta. El objetivo del PLN es permitir que los algoritmos procesen el lenguaje humano y realicen tareas que históricamente solo los humanos eran capaces de realizar, como encontrar pasajes relevantes entre grandes cantidades de texto, resumir textos y generar contenido nuevo y original.</p><p>Estas avanzadas capacidades de PLN se basan en una tecnología conocida como <a href="https://www.elastic.co/what-is/vector-search">búsqueda vectorial</a>. Elastic tiene soporte nativo para búsqueda vectorial, realizando búsqueda exacta y aproximada de <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#knn-search">k-nearest neighbor (kNN),</a> y para NLP, permitiendo el uso directo de modelos personalizados o <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-model-ref.html#ml-nlp-model-ref">de terceros</a> en Elasticsearch.</p><p>En esta entrada del blog, exploraremos cómo la búsqueda vectorial y el PLN mejoran las capacidades de los chatbots y demostraremos cómo Elasticsearch facilita este proceso. Empecemos con una breve visión general de la búsqueda vectorial.</p><h2>Búsqueda de vectores</h2><p>Aunque los humanos pueden comprender el significado y el contexto del lenguaje escrito, las máquinas no pueden hacer lo mismo. Aquí es donde entran los vectores. Al convertir el texto en representaciones vectoriales (representaciones numéricas del significado del texto), las máquinas pueden superar esta limitación. En comparación con una búsqueda tradicional, en lugar de depender de palabras clave y búsqueda léxica basada en frecuencias, los vectores permiten el proceso de datos textuales mediante operaciones definidas para valores numéricos.</p><p>Esto permite la búsqueda vectorial localizar datos que comparten conceptos o contextos similares empleando distancias en el "espacio de incrustación" para representar similitud dado un vector de consulta. Cuando los datos son similares, los vectores correspondientes serán iguales.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt53a615a8ba0ac931/6a17d795fbc5f8b257491910/08542abf8108aace288745b1aca8579b476ddc1b-1440x618.png" alt="" /><p>La búsqueda vectorial no solo se emplea en aplicaciones de PLN, sino que también se emplea en varios otros ámbitos donde se involucran datos no estructurados, incluyendo el procesamiento de imágenes y video.</p><p>En un flujo de chatbot, puede haber varios enfoques para las consultas de los usuarios y, como resultado, existen diferentes formas de mejorar la recuperación de información para una mejor experiencia de usuario. Dado que cada alternativa tiene su propio conjunto de beneficios y posibles desventajas, es esencial tener en cuenta los datos y recursos disponibles, así como el tiempo de entrenamiento (cuando corresponda) y la precisión esperada. En la siguiente sección, trataremos estos aspectos para modelos de PLN con preguntas frecuentes.</p><h2>Preguntas frecuentes</h2><p>Un modelo de preguntas frecuentes (QA) es un tipo de modelo de PLN diseñado para responder preguntas formuladas en lenguaje natural. Cuando los usuarios tienen preguntas que requieren inferir respuestas a partir de múltiples fuentes, sin una respuesta objetivo preexistente disponible en los documentos, los modelos de QA generativa pueden ser útiles. Sin embargo, estos modelos pueden ser computacionalmente costosos y requieren grandes cantidades de datos para el entrenamiento relacionado con el dominio, lo que puede hacerlos menos prácticos en algunas situaciones, aunque este método puede ser especialmente valioso para tratar preguntas fuera del dominio.</p><p>Por otro lado, cuando los usuarios tienen preguntas sobre un tema específico y la respuesta real está presente en el documento, se pueden emplear modelos extractivos de control de calidad. Estos modelos extraen directamente la respuesta del documento fuente, proporcionando resultados transparentes y verificables, lo que los convierte en una opción más práctica para compañías u organizaciones que desean ofrecer una forma sencilla y eficiente de responder a sus preguntas.</p><p>El ejemplo siguiente demuestra el uso de un modelo extractivo de control de calidad preentrenado, <a href="https://huggingface.co/deepset/minilm-uncased-squad2">disponible en Hugging Face</a> y desplegado en Elasticsearch, para extraer respuestas de un contexto dado:</p>POST _ml/trained_models/deepset__minilm-uncased-squad2/deployment/_infer
{
    "docs": [{"text_field": "Canvas is a data visualization and presentation application within Kibana. With Canvas, live data can be pulled directly from Elasticsearch and combined with colors, images, text, and other customized options to create dynamic, multi-page displays."}],
    "inference_config": {"question_answering": {"question": "What is Kibana Canvas?"}}
}


{
  "predicted_value": "a data visualization and presentation application",
  "start_offset": 10,
  "end_offset": 59,
  "prediction_probability": 0.28304219431376443
}
<p><a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-deploy-models.html">Despliega modelos capacitados.</a></p><p><a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-ner-example.html#ex-ner-ingest">Agrega un modelo a una tubería de ingestión de inferencia.</a></p><p>Existen varias formas de gestionar consultas de usuarios y recuperar información, y emplear múltiples modelos de lenguaje y fuentes de datos puede ser una alternativa eficaz al tratar con datos no estructurados. Para ilustrar esto, tenemos un ejemplo del procesamiento de datos de un chatbot empleado para responder a consultas con respuestas que consideran datos extraídos de documentos seleccionados.</p><h2>Procesamiento de datos en chatbots: PLN y búsqueda vectorial</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta418a9c54bb16cf9/6a17d7975772624b371bca43/c2d1a2f110e937b1d3e5df0d5caac3c906c98fb0-1440x748.png" alt="" /><p>Como se mostró anteriormente, el procesamiento de datos para nuestro chatbot puede dividir en tres partes:</p><ul><li><p><strong>Procesamiento vectorial:</strong> Esta parte convierte documentos en representaciones vectoriales.</p></li><li><p><strong>Procesamiento de entrada por parte del usuario:</strong> Esta parte extrae información relevante de la consulta del usuario y realiza búsqueda semántica y recuperación híbrida.</p></li><li><p><strong>Optimización:</strong> Esta parte incluye la monitorización y es fundamental para garantizar la fiabilidad del chatbot, su rendimiento óptimo y una excelente experiencia de usuario.</p></li></ul><h2>Procesamiento vectorial</h2><p>Para la <strong>parte de procesamiento</strong> , el primer paso es determinar las partes componentes de cada documento para luego convertir cada elemento a una representación vectorial; Estas representaciones pueden crear para una amplia variedad de formatos de datos.</p><p>Existen varios métodos que pueden usar para calcular incrustaciones, incluyendo modelos y bibliotecas preentrenadas.</p><p>Es importante señalar que la efectividad de la búsqueda y recuperación en estas representaciones depende de los datos existentes y de la calidad y relevancia del método empleado.</p><p>A medida que se calculan los vectores, se almacenan en Elasticsearch con un <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html">tipo de campo dense_vector</a> .</p>PUT &lt;target&gt;
{
  "mappings": {
    "properties": {
      "doc_part_vector": {
        "type": "dense_vector",
        "dims": 3
      },
      "doc_part" : {
        "type" : "keyword"
      }
    }
  }
}
<h2>Procesamiento de entrada de usuario en chatbots</h2><p>Para el <strong>usuario</strong> , tras recibir una pregunta, es útil extraer toda la información posible antes de continuar. Esto ayuda a entender la intención del usuario y, en este caso, estamos empleando un <a href="https://huggingface.co/dslim/bert-base-NER">modelo de Reconocimiento de Entidades Nombradas (NER)</a> para ayudar con ello. NER es el proceso de identificar y clasificar entidades nombradas en categorías de entidades predefinidas.</p>POST _ml/trained_models/dslim__bert-base-ner/deployment/_infer
{
  "docs": { "text_field": "How many people work for Elastic?"}
}


{
  "predicted_value": "How many people work for [Elastic](ORG&amp;Elastic)?",
  "entities": [
    {
      "entity": "Elastic",
      "class_name": "ORG",
      "class_probability": 0.4993975435876747,
      "start_pos": 25,
      "end_pos": 32
    }
  ]
}
<p>Aunque no es un paso necesario, usando datos estructurados o el anterior u otro resultado de modelo NLP para categorizar la consulta del usuario, podemos restringir la búsqueda kNN usando un <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#knn-search-filter-example">filtro</a>. Esto ayuda a mejorar el rendimiento y la precisión al reducir la cantidad de datos que deben ser procesados.</p>    "filter": {
      "term": {
        "org": "Elastic"
      }
    }
<h2>Búsqueda semántica y recuperación híbrida</h2><p>Dado que el prompt se origina en consultas de usuarios y el chatbot necesita procesar el lenguaje humano con su variabilidad y ambigüedad, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#semantic-search">la búsqueda semántica</a> es una excelente opción. En Elasticsearch, puedes realizar búsqueda semántica en un solo paso pasando la cadena de consulta y el ID del <a href="https://huggingface.co/sentence-transformers/msmarco-MiniLM-L-12-v3">modelo de incrustación</a> a un objeto query_vector_builder. Esto vectorizará la consulta y realizará <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html">una búsqueda</a> kNN para recuperar las k coincidencias principales que sean las más cercanas en significado a la consulta:</p>POST /&lt;target&gt;/_search
{
  "knn": {
    "field": "doc_part_vector",
    "k": 5,
    "num_candidates": 20,
    "query_vector_builder": {
      "text_embedding": {
        "model_id": "&lt;text-embedding-model-id&gt;",
        "model_text": "&lt;query_string&gt;"
      }
    }
  }
 }
<p><a href="https://www.elastic.co/guide/en/machine-learning/8.7/ml-nlp-text-emb-vector-search-example.html">Ejemplo de extremo a extremo: Cómo desplegar un modelo de incrustación de texto y usarlo para búsqueda semántica.</a> Elasticsearch emplea la implementación Lucene del Okapi BM25, un <strong>modelo disperso</strong> , para clasificar consultas de texto y determinar su relevancia, mientras que <strong>los modelos densos</strong> se emplean para <strong>la búsqueda semántica</strong>. Para <strong>combinar</strong> las <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#_combine_approximate_knn_with_other_features"><strong>fortalezas de ambos</strong></a> <strong>,</strong> coincidencias vectoriales y coincidencias obtenidas a partir de la consulta de texto, puedes realizar una <strong>recuperación híbrida</strong> :</p>POST &lt;target&gt;/_search
{
  "query": {
          "match": {
            "content": {
              "query": "&lt;query_string&gt;"
            }
        }
  },
  "knn": {
    "field": "doc_part_vector",
    "query_vector_builder": {
      "text_embedding": {
    "model_id": "&lt;text-embedding-model-id&gt;",
     "model_text": "&lt;query_string&gt;"
      }
    },
    "filter": {
      "term": {
        "org": "Elastic"
      }
    }
  }
}
<h3>Combinar modelos dispersos y densos suele dar los mejores resultados</h3><p>Los modelos dispersos generalmente rinden mejor en consultas cortas y terminologías específicas, mientras que los modelos densos aprovechan el contexto y las asociaciones. Si quieres aprender más sobre cómo estos métodos se comparan y complementan, aquí comparamos BM25 con dos modelos densos que fueron capacitados específicamente para la recuperación.</p><p>El resultado más relevante suele ser la primera respuesta dada al usuario, el_score es un número usado para determinar la <strong>relevancia</strong> del documento devuelto.</p><h2>Optimización de chatbots</h2><p>Para ayudar a mejorar la experiencia del usuario, el rendimiento y la fiabilidad de tu chatbot, además de aplicar puntaje híbrido, puedes incorporar los siguientes enfoques: <strong>Análisis de Sentimiento:</strong> Para proporcionar conciencia de los comentarios y reacciones de los usuarios a medida que se desarrolla el diálogo, puedes incorporar un <a href="https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english">modelo de análisis de sentimiento</a>:</p>POST _ml/trained_models/distilbert-base-uncased-finetuned-sst-2-english/deployment/_infer
{
  "docs": { "text_field": "That was not my question!"}
}


{
  "predicted_value": "NEGATIVE",
  "prediction_probability": 0.980080439016437
}
<p><a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data"><strong>Capacidades de GPT</strong></a> <strong>:</strong> Como alternativa para mejorar la experiencia global, puedes combinar la relevancia de búsqueda de Elasticsearch con las capacidades de respuesta a preguntas de GPT de OpenAI, empleando la <a href="https://platform.openai.com/docs/guides/chat">API de Finalización de Chat</a> para devolver a las respuestas generadas por el modelo de usuario considerando estos k primeros documentos como contexto. <em>Prompt: "responder a esta pregunta &lt;user_question&gt; usando solo este documento &lt;top_search_result&gt;"</em></p><p><strong>Observancia:</strong> Garantizar el rendimiento de cualquier chatbot es crucial, y la monitorización es un componente esencial para lograrlo. Además de los registros que capturan las interacciones con chatbots, es importante hacer un seguimiento del tiempo de respuesta, la latencia y otras métricas relevantes del chatbot. De este modo, puedes identificar patrones, tendencias e incluso detectar anomalías.<a href="https://www.elastic.co/blog/monitor-openai-api-gpt-models-opentelemetry-elastic">Las herramientas de observabilidad elástica</a> te permiten recopilar y analizar esta información.</p><h2>Resumen</h2><p>Esta entrada de blog explica qué son el PLN y la búsqueda vectorial y profundiza en un ejemplo de chatbot empleado para responder a consultas de usuarios considerando datos extraídos de la representación vectorial de documentos.</p><p>Como se demostró, usando PLN y búsqueda vectorial, los chatbots son capaces de realizar tareas complejas que van más allá de los datos estructurados y dirigidos. Esto incluye hacer recomendaciones y responder a consultas específicas relacionadas con productos o negocios empleando múltiples fuentes de datos y formatos como contexto, además de proporcionar una experiencia de usuario personalizada.</p><p>Los casos de uso van desde ofrecer atención al cliente ayudando a los clientes con sus consultas hasta ayudar a los desarrolladores con sus consultas, proporcionando orientación paso a paso, sugiriendo recomendaciones o incluso automatizando tareas. Dependiendo del objetivo y de los datos existentes, también se pueden emplear otros modelos y métodos para lograr resultados aún mejores y mejorar la experiencia global del usuario.</p><p>Aquí tienes algunos enlaces sobre el tema que pueden ser útiles:</p><ol><li><p><a href="https://www.elastic.co/blog/how-to-deploy-natural-language-processing-nlp-getting-started">Cómo desplegar el procesamiento de lenguaje natural (PLN): Comienzo</a></p></li><li><p><a href="https://www.elastic.co/blog/overview-image-similarity-search-in-elastic">Resumen de la búsqueda por similitud de imágenes en Elasticsearch</a></p></li><li><p><a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data">ChatGPT y Elasticsearch: OpenAI se encuentra con datos privados</a></p></li><li><p><a href="https://www.elastic.co/blog/monitor-openai-api-gpt-models-opentelemetry-elastic">Monitoriza los modelos de OpenAI API y GPT con OpenTelemetry y Elastic</a></p></li><li><p><a href="https://www.elastic.co/blog/why-technology-leaders-need-vector-search">5 razones por las que los líderes de TI necesitan la búsqueda vectorial para mejorar la experiencia de búsqueda</a></p></li></ol><p>Al incorporar PLN y búsqueda vectorial nativa en Elasticsearch, puedes aprovechar su velocidad, escalabilidad y capacidades de búsqueda para crear chatbots altamente eficientes y eficaces, capaces de manejar grandes cantidades de datos, ya sean estructurados o no.</p><p>¿Listo para empezar? Comienza una <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">prueba gratis de Elastic Cloud</a>.</p><p><em>En esta entrada del blog, es posible que empleamos o podamos referirnos a herramientas de IA generativa de terceros, que son propiedad y están gestionadas por sus respectivos propietarios. Elastic no tiene ningún control sobre las herramientas de terceros y no tenemos responsabilidad ni responsabilidad por su contenido, funcionamiento o uso, ni por ninguna pérdida o daño que pueda surgir por su uso de dichas herramientas. Por favor, ten precaución al emplear herramientas de IA con información personal, sensible o confidencial. Cualquier dato que envíes puede usar para entrenamiento de IA u otros fines. No hay garantía de que la información que proporciones se mantenga segura o confidencial. Deberías familiarizarte con las prácticas de privacidad y los términos de uso de cualquier herramienta de IA generativa antes de emplearla.</em></p><p><em>Elastic, Elasticsearch y las marcas asociadas son marcas registradas, logotipos o marcas registradas de Elasticsearch N.V. en Estados Unidos y otros países. Todos los demás nombres de empresas y productos son marcas registradas, logotipos o marcas registradas de sus respectivos propietarios.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/enhancing-chatbot-capabilities-with-nlp-and-vector-search-in-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/enhancing-chatbot-capabilities-with-nlp-and-vector-search-in-elasticsearch</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <dc:creator><![CDATA[Priscilla Parodi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5c545fc80b6d79d6/6a170214839dfad776dcfd6f/d968e646240cd3ef7c79b5124d562a5f951d812b-1440x840.png" length="0" type="image/png"/>
    <pubDate>Wed, 21 Jun 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Búsqueda por similitud textual con campos vectoriales]]></title>
    <description><![CDATA[Esta publicación explora cómo las incrustaciones de texto y el nuevo tipo de dense_vector de Elasticsearch podrían usar para apoyar la búsqueda por similitud.]]></description>
    <content:encoded><![CDATA[<p>Desde sus inicios como <a href="https://www.elastic.co/about/history-of-elasticsearch">motor de búsqueda de recetas</a>, Elasticsearch fue diseñado para ofrecer una búsqueda rápida y poderosa en texto completo. Dadas estas orígenes, mejorar la búsqueda de texto fue una motivación importante para nuestro trabajo continuo con vectores. En Elasticsearch 7.0, introdujimos tipos de campo experimentales para vectores de alta dimensión, y ahora la versión 7.3 incorpora soporte para el uso de estos vectores en el puntaje de documentos.</p><p>Esta publicación se centra en una técnica particular llamada búsqueda por similitud textual. En este tipo de búsqueda, un usuario introduce una consulta corta en texto libre, y los documentos se clasifican según su similitud con la consulta. La similitud de texto puede ser útil en una variedad de casos de uso:</p><ul><li><p><strong>Respuestas a preguntas:</strong> Dada una colección de preguntas frecuentes, busca preguntas similares a la que introdujo el usuario.</p></li><li><p><strong>Búsqueda de artículos:</strong> En una colección de artículos de investigación, devuelven artículos con un título estrechamente relacionado con la consulta del usuario.</p></li><li><p><strong>Búsqueda de imágenes:</strong> En un conjunto de datos de imágenes con subtítulos, encuentra imágenes cuyo pie de foto sea similar a la descripción del usuario.</p></li></ul><p>Un enfoque sencillo para la búsqueda por similitud sería clasificar los documentos según cuántas palabras compartan con la consulta. Pero un documento puede ser similar a la consulta aunque tengan muy pocas palabras en común; una noción más robusta de similitud tendría en cuenta también su contenido sintáctico y <a href="https://en.wikipedia.org/wiki/Semantic_similarity">semántico</a> .</p><p>La comunidad de procesamiento de lenguaje natural (PLN) desarrolló una técnica llamada incrustación de texto que codifica palabras y oraciones como vectores numéricos. Estas representaciones vectoriales están diseñadas para capturar el contenido lingüístico del texto y pueden emplear para evaluar la similitud entre una consulta y un documento.</p><p>Esta publicación explora cómo las incrustaciones de texto y el tipo de dense_vector de Elasticsearch podrían usar para apoyar la búsqueda por similitud. Primero daremos una visión general de las técnicas de incrustación y luego pasaremos por un prototipo sencillo de búsqueda por similitud usando Elasticsearch.</p><strong>Nota:</strong> El uso de incrustaciones de texto en la búsqueda es un área compleja y en evolución. Este blog no es una recomendación para una arquitectura o implementación concreta. Empieza aquí para aprender cómo puedes mejorar tu experiencia de búsqueda con el poder de <a href="https://www.elastic.co/what-is/vector-search">la búsqueda vectorial</a>.<h2>¿Qué son las incrustaciones de texto?</h2><p>Echemos un vistazo más de cerca a los diferentes tipos de incrustaciones de texto y cómo se comparan con los enfoques tradicionales de búsqueda.</p><h3>Incrustaciones de palabras</h3><p>Un modelo de <a href="https://en.wikipedia.org/wiki/Word_embedding">incrustación de palabras</a> representa una palabra como un vector numérico denso. Estos vectores buscan capturar las propiedades semánticas de la palabra: palabras cuyos vectores están muy cerca deben ser similares en términos de significado semántico. En una buena incrustación, las direcciones en el espacio vectorial están ligadas a diferentes aspectos del significado de la palabra. Por ejemplo, el vector de "Canadá" podría estar cerca de "Francia" en una dirección y cerca de "Toronto" en otra.</p><p>Las comunidades de PLN y búsqueda llevan tiempo interesadas en representaciones vectoriales de palabras. En los últimos años hubo un resurgimiento del interés por las incrustaciones de palabras, cuando muchas tareas tradicionales se revisaban empleando redes neuronales. Se desarrollaron algunos algoritmos exitosos de incrustación de palabras, incluyendo <a href="https://papers.nips.cc/paper/5021-distributed-representations-of-words-and-phrases-and-their-compositionality.pdf">word2vec</a> y <a href="https://nlp.stanford.edu/pubs/glove.pdf">GloVe</a>. Estos enfoques emplean grandes colecciones de texto y examinan el contexto en el que aparece cada palabra para determinar su representación vectorial:</p><ul><li><p>El modelo Skip-gram word2vec capacita una red neuronal para predecir las palabras de contexto alrededor de una palabra en una oración. Los pesos internos de la red dan la palabra embeddings.</p></li><li><p>En GloVe, la similitud de las palabras depende de la frecuencia con la que aparecen junto a otras palabras contextuales. El algoritmo capacita un modelo lineal simple sobre los conteos de co-ocurrencia de palabras.</p></li></ul><p>Muchos grupos de investigación distribuyen modelos que fueron preentrenados en grandes corpus de texto como Wikipedia o Common Crawl, lo que los hace cómodos de descargar e integrar en tareas posteriores. Aunque a veces se emplean versiones preentrenadas directamente, puede ser útil ajustar el modelo para ajustarlo al conjunto de datos y tarea objetivo específicos. Esto a menudo se logra ejecutando un paso de 'ajuste fino' sobre el modelo preentrenado.</p><p>Las incrustaciones de palabras demostraron ser bastante robustas y efectivas, y ahora es práctica común usar incrustaciones en lugar de tokens individuales en tareas de PLN como la traducción automática y la clasificación de sentimiento.</p><h3>Incrustaciones de sentencias</h3><p>Más recientemente, los investigadores empezaron a centrar en técnicas de incrustación que representan no solo palabras, sino también secciones más largas de texto. La mayoría de los enfoques actuales se basan en arquitecturas de redes neuronales complejas y, a veces, incorporan datos etiquetados durante el entrenamiento para ayudar a capturar información semántica.</p><p>Una vez capacitados, los modelos pueden tomar una oración y producir un vector para cada palabra en contexto, así como un vector para toda la oración. De forma similar a la incrustación de palabras, existen versiones preentrenadas de muchos modelos, lo que permite a los usuarios saltar el costoso proceso de entrenamiento. Aunque el proceso de entrenamiento puede ser muy intensivo en recursos, invocar el modelo es mucho más ligero — los modelos de incrustación de oraciones suelen ser lo suficientemente rápidos como para usar como parte de aplicaciones en tiempo real.</p><p>Algunas técnicas comunes de incrustación de oraciones incluyen <a href="https://arxiv.org/abs/1705.02364">InferSent</a>, <a href="https://arxiv.org/abs/1803.11175">Universal Sentence Encoder</a>, <a href="https://arxiv.org/abs/1802.05365">ELMo</a> y <a href="https://arxiv.org/abs/1810.04805">BERT</a>. Mejorar la incrustación de palabras y frases es un área activa de investigación, y es probable que se introduzcan modelos más estables.</p><h3>Comparación con los enfoques tradicionales de búsqueda</h3><p>En la recuperación tradicional de información, una forma común de representar el texto como un vector numérico es asignar una dimensión a cada palabra del vocabulario. El vector de un fragmento de texto se basa entonces en el número de veces que aparece cada término en el vocabulario. Esta forma de representar el texto suele denominar "bolsa de palabras", porque simplemente contamos las apariciones de palabras sin tener en cuenta la estructura de las oraciones.</p><p>Las incrustaciones de texto difieren de las representaciones vectoriales tradicionales en algunos aspectos importantes:</p><ul><li><p>Los vectores codificados son densos y relativamente de baja dimensión, a menudo con una dimensión que oscila entre 100 y 1.000 dimensiones. En cambio, los vectores de la bolsa de palabras son escasos y pueden comprender 50.000+ dimensiones. Los algoritmos de incrustación codifican el texto en un espacio de dimensión inferior como parte del modelado de su significado semántico. Idealmente, las palabras y frases sinónimas terminan con una representación similar en el nuevo espacio vectorial.</p></li><li><p>Las incrustaciones de oraciones pueden tener en cuenta el orden de las palabras al determinar la representación vectorial. Por ejemplo, la frase "tune in" puede asignar como un vector muy diferente a "in tune".</p></li><li><p>En la práctica, las incrustaciones de oraciones a menudo no se generalizan bien a grandes secciones de texto. No se usan comúnmente para representar textos más largos que un párrafo corto.</p></li></ul><h2>Uso de incrustaciones para la búsqueda de similitud</h2><p>Supongamos que tuviéramos una gran colección de preguntas frecuentes. Un usuario puede hacer una pregunta, y queremos recuperar la pregunta más similar de nuestra colección para ayudarlo a encontrar una respuesta.</p><p>Podríamos usar incrustaciones de texto para permitir recuperar preguntas similares:</p><ul><li><p>Durante la indexación, cada pregunta se pasa por un modelo de incrustación de oraciones para producir un vector numérico.</p></li><li><p>Cuando un usuario introduce una consulta, esta se ejecuta a través del mismo modelo de incrustación de frases para producir un vector. Para clasificar las respuestas, calculamos la similitud del vector entre cada pregunta y el vector de consulta. Al comparar vectores de incrustación, es común usar <a href="https://en.wikipedia.org/wiki/Cosine_similarity">similitud coseno</a>.</p></li></ul><p><a href="https://github.com/jtibshirani/text-embeddings">Este repositorio</a> ofrece un ejemplo sencillo de cómo esto podría lograr en Elasticsearch. El script principal indexa ~20.000 preguntas del <a href="https://github.com/elastic/rally-tracks/tree/master/so">conjunto de datos StackOverflow</a>, y luego permite al usuario introducir consultas en texto libre sobre el conjunto de datos.</p><p>Pronto repasaremos cada parte del guion en detalle, pero primero veamos algunos resultados de ejemplo. En muchos casos, el método es capaz de captar similitudes incluso cuando no hubo una fuerte superposición de palabras entre la consulta y la pregunta indexada:</p><ul><li><p>"comprimir archivos" devuelve "Comprimir / Descomprimir carpetas y archivos"</p></li><li><p>"determinar si algo es una IP" devuelve "¿Cómo sabes si una cadena es una IP o un nombre de host"</p></li><li><p>"translate bytes to doubles" devuelve "Convert Bytes to Floating Points Numbers in Python"</p></li></ul><h3>Detalles de implementación</h3><p><a href="https://github.com/jtibshirani/text-embeddings/blob/blog/src/main.py">El script</a> comienza descargando y creando el modelo de incrustación en TensorFlow. Elegimos el Codificador Universal de Oraciones de Google, pero es posible usar muchos otros métodos de incrustación. El script emplea el modelo de incrustación tal cual, sin ningún entrenamiento o ajuste fino adicional.</p><p>A continuación, creamos el índice Elasticsearch, que incluye mapeos para el título de la pregunta, las etiquetas y también el título de la pregunta codificado como vector:</p>"mappings": {
"properties": {
"title": {
"type": "text"
},
"title_vector": {
"type": "dense_vector",
"dims": 512
}
"tags": {
"type": "keyword"
},
...
}
}
<p>En el mapeo para dense_vector, debemos especificar el número de dimensiones que contendrán los vectores. Al indexar un campo title_vector, Elasticsearch comprueba que tenga el mismo número de dimensiones especificado en el mapeo.</p><p>Para indexar documentos, pasamos el título de la pregunta por el modelo de incrustación para obtener un array numérico. Este array se agrega al documento en el campo title_vector.</p><p>Cuando un usuario introduce una consulta, el texto se pasa primero por el mismo modelo de incrustación y se almacena en el parámetro query_vector. A partir de la versión 7.3, Elasticsearch proporciona una <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.6/query-dsl-script-score-query.html#vector-functions">función cosineSimilarity</a> en su lenguaje de scripting nativo. Así que para clasificar las preguntas según su similitud con la consulta del usuario, usamos una consulta script_score:</p>{
"script_score": {
"query": {"match_all": {}},
"script": {
"source": "cosineSimilarity(params.query_vector, 'title_vector') + 1.0",
"params": {"query_vector": query_vector}
}
}
}
<p>Nos cercioramos de pasar el vector de consulta como parámetro de script para <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.6/modules-scripting-using.html#prefer-params">evitar recompilar el script</a>() en cada nueva consulta. Como Elasticsearch no permite puntajes negativos, es necesario agregar una a la similitud coseno.</p><p>| <strong>Nota:</strong> esta entrada de blog originalmente usaba una <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.3/query-dsl-script-score-query.html#vector-functions">sintaxis diferente para funciones vectoriales</a> que estaba disponible en Elasticsearch 7.3, pero que fue obsoleta en la 7.6.
|</p><h3>Limitaciones importantes</h3><p>La consulta script_score está diseñada para envolver una consulta restrictiva y modificar los puntajes de los documentos que devuelve. Sin embargo, proporcionamos una consulta match_all, lo que significa que el script se ejecutará sobre todos los documentos del índice. Esta es una limitación actual de la similitud vectorial en Elasticsearch: los vectores pueden usar para puntuar documentos, pero no en el paso inicial de recuperación. El soporte para la recuperación basado en la similitud vectorial es un área importante de <a href="https://github.com/elastic/elasticsearch/issues/42326">trabajo en curso</a>.</p><p>Para evitar escanear todos los documentos y mantener un rendimiento rápido, la consulta de match_all puede ser reemplazada por una consulta más selectiva. La consulta adecuada para la recuperación probablemente dependerá del caso de uso específico.</p><p>Aunque vimos algunos ejemplos alentadores arriba, es importante señalar que los resultados también pueden ser ruidosos y poco intuitivos. Por ejemplo, "comprimir archivos" también asigna puntajes altos a "Partial .csproj Archivos" y "Cómo evitar .pyc ¿archivos?". Y cuando el método arroja resultados sorprendentes, no siempre está claro cómo depurar el problema: el significado de cada componente vectorial suele ser opaco y no corresponde a un concepto interpretable. Con las técnicas tradicionales de puntaje basadas en solapamientos de palabras, a menudo es más fácil responder a la pregunta "¿por qué este documento está muy bien clasificado?"</p><p>Como se mencionó antes, este prototipo está pensado como un ejemplo de cómo los modelos de incrustación podrían usar con campos vectoriales, y no como una solución lista para producción. Al desarrollar una nueva estrategia de búsqueda, es fundamental probar cómo funciona el enfoque con tus propios datos, cerciorándote de comparar con una línea base estable como una consulta de coincidencia. Puede ser necesario realizar cambios importantes en la estrategia antes de que logre resultados estables, incluyendo afinar el modelo de incrustación para el conjunto de datos objetivo o probar diferentes formas de incorporar incrustaciones como la expansión de consultas a nivel de palabra.</p><h2>Conclusiones</h2><p>Las técnicas de incrustación proporcionan una forma poderosa de captar el contenido lingüístico de un texto. Indexando incrustaciones y puntaje basándonos en la distancia vectorial, podemos comparar documentos usando una noción de similitud que va más allá de su solapamiento a nivel de palabra.</p><p>Esperamos introducir más funcionalidades basadas en el tipo de campo vectorial. El uso de vectores para la búsqueda es un área matizada y en desarrollo — como siempre, nos encantaría conocer vuestros casos de uso y experiencias en <a href="https://github.com/elastic/elasticsearch">Github</a> y en <a href="https://discuss.elastic.co/">los foros de discusión</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/text-similarity-search-with-vectors-in-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/text-similarity-search-with-vectors-in-elasticsearch</guid>
    <category><![CDATA[Base de datos vectorial]]></category>
    <dc:creator><![CDATA[Julie Tibshirani]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91384bd99b05cd28/6a17e7e01d1b835cc593e467/c633ed737add7d22a7d65b3ca5c56480ef3d8b2c-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 06 Oct 2022 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>