<?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[Búsqueda híbrida - 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[Búsqueda híbrida - 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/hybrid-search</link>
    </image>
    <link>https://www.elastic.co/es/search-labs/blog/category/hybrid-search</link>
    <atom:link href="https://www.elastic.co/es/search-labs/rss/category/hybrid-search.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[es]]></language>
    <lastBuildDate>Tue, 29 Sep 2026 02:11:15 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[Resolución de entidades con Elasticsearch, parte 4: el desafío final]]></title>
    <description><![CDATA[Resolver y evaluar los desafíos de resolución de entidades en sets de datos de "desafío final" altamente diversos, diseñados para prevenir atajos.]]></description>
    <content:encoded><![CDATA[<p>Ya hemos visto cómo se implementa la resolución inteligente de entidades de dos maneras. Ambos enfoques comienzan de la misma forma: preparación y extracción de entidades, seguidas de la recuperación de candidatos con Elasticsearch. A partir de ahí, evaluamos a esos candidatos usando un modelo de lenguaje grande (LLM), ya sea mediante generación de JSON basada en prompts o mediante llamadas de funciones, y exigimos que el modelo ofrezca una explicación transparente de su juicio.</p><p>Como vimos en la <a href="https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-function-calling">entrada anterior</a>, la consistencia que aportan las llamadas a funciones no es solo una optimización interesante; es esencial. Una vez que eliminamos los errores estructurales del ciclo de evaluación, los resultados en escenarios estándar (como los de los sets de datos de nivel 4) mejoraron significativamente.</p><p>Sin embargo, queda una pregunta obvia por responder:</p><p><em>¿Funciona de igual manera este enfoque cuando todo se complica realmente?</em></p><p>La resolución de entidades del mundo real rara vez falla debido a casos simples. Falla cuando los nombres atraviesan idiomas, culturas, sistemas de escritura, períodos de tiempo y límites organizacionales. Falla cuando las personas se mencionan por sus cargos en lugar de por sus nombres, cuando las empresas cambian de nombre, cuando las transliteraciones no son consistentes y cuando el contexto (no la ortografía) es lo único que vincula una mención a una entidad del mundo real.</p><p>Así que, para la publicación final de esta serie, sometimos el sistema a lo que llamamos <strong>el desafío definitivo</strong>.</p><h2>¿Qué hace que esto sea el desafío definitivo?</h2><p>En evaluaciones anteriores, probamos el sistema con sets de datos cada vez más complejos. Para cuando llegamos al nivel 4, analizado en la publicación anterior, ya estábamos lidiando con una mezcla de apodos, cargos, nombres multilingües y referencias semánticas. Esas pruebas demostraron que la arquitectura en sí era estable, pero que los problemas de confiabilidad, especialmente el JSON mal formado, estaban reduciendo la capacidad de recuperación.</p><p>Con la llamada a funciones implementada, finalmente conseguimos una base estable. Eso nos dio la oportunidad de hacer una pregunta más interesante:</p><p><em>¿Puede una única pipeline unificada gestionar </em><em><strong>muchos tipos diferentes</strong></em><em> de problemas de resolución de entidades al mismo tiempo?</em></p><p>El set de datos del desafío final se diseñó precisamente para poner a prueba esa dimensión.</p><p>En lugar de centrarse en una sola dificultad (como apodos o transliteración), este sets de datos combina <strong>más de 50 tipos distintos de desafíos</strong>, incluyendo:</p><ul><li><p>Convenciones de nomenclatura cultural.</p></li><li><p>Referencias basadas en cargos.</p></li><li><p>Relaciones comerciales y cambios históricos de nombre.</p></li><li><p>Menciones multilingües y entre sistemas de escritura.</p></li><li><p>Desafíos compuestos que mezclan varios de los anteriores.</p></li></ul><p>Lo importante es que no se trata de optimizar para un solo caso de uso concreto. Se trata de probar si el <em>patrón de diseño</em> se mantiene cuando las reglas cambian de entidad a entidad.</p><h2>Resumen del set de datos</h2><p>El set de datos del desafío final consta de:</p><ul><li><p><strong>50 entidades</strong>, que abarcan personas, organizaciones e instituciones.</p></li><li><p><strong>~60 artículos</strong>, con estructuras y complejidad lingüísticas variables.</p></li><li><p><strong>51 categorías distintas de desafíos</strong>, agrupadas en términos generales en:</p><ul><li><p>Convenciones de nomenclatura cultural.</p></li><li><p>Títulos y contexto profesional.</p></li><li><p>Relaciones empresariales y organizacionales.</p></li><li><p>Desafíos multilingües y de transliteración.</p></li><li><p>Escenarios combinados y de casos límite.</p></li></ul></li></ul><p>Al principio de la serie, vimos que usar IA generativa (GenAI) para crear sets de datos puede tener pros y contras. Sin eso, sería muy difícil reunir datos de prueba lo suficientemente amplios y variados. Pero si no se controla, el modelo tiende a facilitar demasiado todo.</p><p>En una fase inicial de generación, por ejemplo, descubrimos que el modelo había incluido frases como «el presidente ruso» como alias explícitos para Vladimir Putin. Eso podría parecer razonable hoy en día, pero anula el propósito de probar la resolución contextual. ¿Qué pasa si el artículo habla de Rusia en la década de 1990? El sistema debería deducir la entidad correcta a partir del contexto, en lugar de basarse en un alias predefinido.</p><p>Por esa razón, este set de datos fue diseñado deliberadamente para que los <strong>atajos no funcionen</strong>. Los alias no se enumeran explícitamente cuando se espera que el sistema deduzca su significado. Las frases descriptivas no están previnculadas a entidades. Las coincidencias correctas suelen depender del contexto del artículo, no solo del texto local.</p><p><strong>Nota importante:</strong> Aunque demostramos las capacidades del sistema en diversos escenarios, este sigue siendo un prototipo educativo. Los sistemas de producción que manejan el monitoreo de entidades sancionadas en el mundo real requerirían validación adicional, verificaciones de cumplimiento, pistas de auditoría y manejo especializado para casos de uso confidenciales.</p><h2>¿Por qué estos escenarios son difíciles?</h2><p>¡En la primera publicación de esta serie, presentamos un ejemplo simple pero ambiguo: “¡La nueva actualización de Swift está aquí!”! El desafío es que “Swift” puede resolverse en múltiples entidades del mundo real, dependiendo del contexto. Ese ejemplo refleja una verdad más amplia: el lenguaje natural es inherentemente ambiguo.</p><p>La resolución de entidades, por lo tanto, no es solo un problema de coincidencia de texto. Los humanos solemos basarnos en el conocimiento compartido, las normas culturales y el contexto de cada situación para interpretar las referencias, y casi nunca nos damos cuenta de que lo estamos haciendo.</p><p>Considera algunos casos comunes:</p><ul><li><p>Un título como “el presidente” no tiene sentido sin contexto geopolítico y temporal.</p></li><li><p>El nombre de una empresa puede referirse a la empresa matriz, a una filial o a una marca anterior, dependiendo de cuándo se escribió el artículo.</p></li><li><p>El nombre de una persona puede aparecer en diferentes órdenes, sistemas de escritura o transcripciones, dependiendo del idioma y la cultura.</p></li><li><p>La misma frase puede referirse legítimamente a diferentes entidades en diferentes contextos, y el sistema debe ser capaz de <em>rechazar</em> coincidencias con la misma confianza con la que las acepta.</p></li></ul><p>No existe un único conjunto de reglas que maneje todo esto de manera clara. Por eso este prototipo separa las responsabilidades de forma tan marcada:</p><ul><li><p>Elasticsearch reduce el espacio de candidatos de manera eficiente y transparente.</p></li><li><p>El LLM se usa solo donde se requiere juicio y está obligado a explicarse a sí mismo.</p></li><li><p>La recuperación y el razonamiento siguen siendo pasos distintos.</p></li></ul><p>Esta distinción cobra aún más importancia a medida que aumenta la variedad de tipos de desafíos.</p><h2>Cómo el sistema gestiona la diversidad sin casos especiales</h2><p>Uno de los resultados más interesantes de esta evaluación es lo que <em>no</em> cambió:</p><ul><li><p><strong>No</strong> agregamos lógica especial para nombres japoneses.</p></li><li><p><strong>No</strong> agregamos reglas personalizadas para patronímicos árabes.</p></li><li><p><strong>No</strong> agregamos mapeos codificados para nombres históricos de compañías.</p></li></ul><p>En cambio, el sistema se basó en los mismos elementos principales presentados anteriormente en la serie:</p><ul><li><p>Entidades enriquecidas con contexto indexadas para búsqueda semántica.</p></li><li><p>Recuperación híbrida (exacta, alias y semántica) en Elasticsearch.</p></li><li><p>Un conjunto pequeño y bien definido de posibles coincidencias.</p></li><li><p>El juicio del LLM está limitado por las llamadas a funciones y los esquemas mínimos.</p></li></ul><p>Esto sugiere que la flexibilidad del sistema proviene de la <strong>representación y arquitectura</strong>, no de una colección cada vez mayor de reglas.</p><p>Cuando el sistema tiene éxito, es porque se recuperan los candidatos adecuados y el LLM tiene suficiente contexto para explicar por qué una referencia mapea (o no) a una entidad específica.</p><h2>Resultados: ¿cómo funcionó?</h2><p>En los sets de datos del desafío final, el sistema obtuvo los siguientes resultados generales:</p><ul><li><p><strong>Precisión:</strong> ~91 %</p></li><li><p><strong>Recuperación:</strong> ~86 %</p></li><li><p><strong>Puntuación F1:</strong> ~89 %</p></li><li><p><strong>Tasa de aceptación en LLM:</strong> ~72 %</p></li></ul><h3>Rendimiento en los tipos de desafíos</h3><p>El desglose de resultados por tipo de desafío revela fortalezas y limitaciones:</p><p><strong>El mejor desempeño (100 % de puntuación F1)</strong> se observó en áreas como:</p><ul><li><p>Coincidencia entre diferentes sistemas de escritura (entidades comerciales en cirílico, coreano y chino).</p></li><li><p>Escenarios hebreos (patronímicos, títulos profesionales, títulos religiosos, transliteración).</p></li><li><p>Jerarquías empresariales (aeroespacial, producción diversificada, corporaciones multidivisionales).</p></li><li><p>Títulos profesionales (académicos, militares, políticos, religiosos).</p></li><li><p>Escenarios japoneses combinados que involucran múltiples sistemas de escritura.</p></li></ul><p><strong>Fuerte rendimiento (80-99 % de puntuación F1)</strong> incluido:</p><ul><li><p>Figuras políticas internacionales (98%).</p></li><li><p>Cambios de nombre históricos (90 %).</p></li><li><p>Jerarquías empresariales complejas (89 %).</p></li><li><p>Nombres de empresas japonesas (93 %).</p></li><li><p>Transliteración entre alfabetos (86 %).</p></li><li><p>Patronímicos árabes (86 %).</p></li></ul><p><strong>Las áreas más desafiantes</strong> fueron:</p><ul><li><p>Transliteración avanzada (chino, coreano): 0 % F1.</p></li><li><p>Ciertos escenarios japoneses (honoríficos, orden de los nombres, variación del sistema de escritura): ~67 % F1.</p></li><li><p>Algunos escenarios árabes (nombres de empresas, referencias institucionales): ~40% F1.</p></li></ul><p>Lo importante aquí es <em>por qué</em> el sistema tuvo dificultades en estos casos. Las fallas no se debieron a la ruptura del enfoque general, sino a limitaciones en componentes específicos, sobre todo el modelo vectorial denso utilizado para la búsqueda semántica en ciertos escenarios multilingües.</p><p>Como la recuperación y la evaluación están claramente separadas, para mejorar el rendimiento no hace falta reescribir el sistema. Si se sustituyera por un modelo de incrustación multilingüe más potente, se enriquecería el contexto de las entidades o se perfeccionarían las estrategias de recuperación, se mejorarían los resultados en todas estas categorías sin cambiar la arquitectura central.</p><p>Desde el punto de vista arquitectónico, esa es la verdadera medida del éxito.</p><h2>Qué nos dice esto sobre el diseño</h2><p>Si vemos nuevamente las series, se observan algunas tendencias:</p><ul><li><p><strong>La preparación importa más que la coincidencia inteligente. </strong>El enriquecimiento de entidades con contexto de antemano reduce drásticamente la ambigüedad posteriormente.</p></li><li><p><strong>Los LLM son más valiosos como jueces, no como recuperadores. </strong>Pedirles que expliquen <em>por qué</em> una coincidencia tiene sentido es mucho más poderoso que pedirles que realicen una búsqueda.</p></li><li><p><strong>La fiabilidad permite la precisión. </strong>La llamada a funciones no solo limpió el JSON; desbloqueó la memoria que ya estaba latente en el paso de recuperación.</p></li><li><p><strong>La generalización supera a la especialización. </strong>Un pequeño número de abstracciones bien seleccionadas manejó decenas de tipos de desafío sin lógica personalizada.</p></li></ul><p>Esta es la razón por la que el prototipo es intencionalmente nativo de Elasticsearch y es intencionalmente conservador en cómo utiliza los LLM. El objetivo no es reemplazar la búsqueda; es hacer que la búsqueda sea explicable en situaciones donde el significado importa.</p><h2>Reflexiones finales</h2><p>El desafío final no se trataba de perseguir métricas perfectas; se trataba de responder una pregunta más fundamental:</p><p><em>¿Puede una arquitectura transparente, centrada en la búsqueda y asistida por LLM, manejar la ambigüedad de entidades en el mundo real sin colapsar en reglas o cajas negras?</em></p><p>Para este prototipo educativo, la respuesta es sí, con claras advertencias sobre el fortalecimiento de la producción, el cumplimiento, el monitoreo y la calidad de los datos. Si estás creando sistemas que necesitan justificar <em>por qué</em> se hizo una coincidencia de entidades, este patrón merece ser considerado seriamente. Espero que esta serie haya demostrado que la resolución de entidades no tiene que ser misteriosa. Con una correcta separación de responsabilidades, se convierte en algo sobre lo que puedes razonar, medir y mejorar.</p><p>Este trabajo también sugiere un patrón arquitectónico más amplio. Lo que surge es una ligera pero importante evolución de la clásica Retrieval-Augmented Generation (RAG). En lugar de permitir que la recuperación alimente directamente la generación, introducimos un paso de evaluación explícito. El LLM se usa primero para juzgar y verificar el estado de los candidatos recuperados, y solo los resultados aprobados pueden incrementar la generación. Se puede pensar en esto como Generation-Augmented Retrieval-Augmented Generation with Evaluation, o GARAGE, porque a quién no le gusta un buen acrónimo.</p><p>¿Qué otros casos de uso podrían beneficiarse de este patrón? Los sistemas que requieren confianza, transparencia y razonamiento justificable son candidatos naturales. Los trabajos futuros en este ámbito deberían ser tan convincentes como los resultados que hemos visto aquí, y tengo muchas ganas de ver hacia dónde lo lleva la comunidad.</p><h2>Próximos pasos: Pruébalo tú mismo</h2><p>¿Quieres ver el desafío final en acción? Mira el <a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/notebooks#:~:text=5%20minutes%20ago-,05_ultimate_challenge_v3.ipynb,-Initial%20public%20lab"><strong>cuaderno de desafío final</strong></a> para obtener una guía completa con implementaciones reales, explicaciones detalladas y ejemplos prácticos.</p><p>El pipeline completo de resolución de entidades demuestra los conceptos del núcleo y la arquitectura necesarios para uso en producción. Se puede usar como base para construir sistemas que monitoreen los artículos de noticias, rastreen las menciones de entidades y respondan preguntas sobre qué entidades aparecen en qué artículos, al tiempo que se mantiene la transparencia y la explicabilidad.
</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/entity-resolution-elasticsearch-llm-challenges</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/entity-resolution-elasticsearch-llm-challenges</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Búsqueda híbrida]]></category>
    <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc58be329ffebcd60/6a17043e47d49c0bc62d88ab/70fb0ff949f6db9ac9b8a28ecb4329ab915ebf46-720x420.png" length="0" type="image/png"/>
    <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Resolución de entidades con Elasticsearch y LLMs, parte 2: emparejamiento de entidades con evaluación de LLM y búsqueda semántica]]></title>
    <description><![CDATA[Usar la búsqueda semántica y las evaluaciones transparentes de LLM para la resolución de entidades en Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>En<a href="https://www.elastic.co/search-labs/blog/entity-resolution-llm-elasticsearch"> Parte 1</a>, preparamos nuestra lista de reproducción y extrajimos menciones de entidades. Ahora estamos listos para responder la pregunta difícil: ¿A qué entidad te refieres realmente con una mención? Volvamos al ejemplo del primer blog de esta serie, que establece por qué necesitamos la resolución de entidades: " ¡La actualización de Swift ya está aquí! " Imagina que este titular va acompañado de un poco más de contexto:</p><ol><li><p>¡La nueva actualización de Swift está aquí! Los desarrolladores están ansiosos por probar las nuevas características.</p></li><li><p>¡La nueva actualización de Swift está aquí! El nuevo álbum se lanzará el próximo mes.</p></li></ol><p>Con este contexto añadido, deberíamos ser capaces de resolver el nombre "Swift" a la entidad correcta.</p><p>En la <a href="https://www.elastic.co/search-labs/blog/entity-resolution-llm-elasticsearch">publicación anterior</a>, configuramos nuestra lista de seguimiento y enriquecimos las entidades con contexto adicional. Al ver nuestros ejemplos anteriores, necesitamos tener, al menos, las siguientes dos entidades en la lista: Taylor Swift y Swift Programming Language. También explicamos cómo extraemos las menciones de entidades del texto. Ambos ejemplos extraerían "Swift". Con estos ingredientes en su lugar, la lista de vigilancia enriquecida y las entidades extraídas, finalmente estamos listos para presentar la estrella del espectáculo: la coincidencia de entidades.</p><p><strong>Recuerda:</strong> Este es un prototipo didáctico diseñado para enseñar conceptos de emparejar entidades. Los sistemas de producción pueden usar diferentes modelos de lenguaje grandes (LLM), reglas de coincidencia personalizadas, pipelines de evaluación especializados o enfoques conjuntos que combinan varias estrategias de coincidencia.</p><h2>El problema: Por qué la coincidencia es difícil</h2><p>El lenguaje humano es algo extraordinario. Una de sus propiedades más interesantes es su creatividad infinita. Podemos generar y entender un número infinito de frases nuevas. ¿Es de extrañar, entonces, que las coincidencias exactas en la resolución de entidades sean raras? Los autores se esfuerzan por ser creativos cuando pueden. Sería bastante tedioso si tuviéramos que escribir y leer nombres completos cada vez que se menciona una entidad. Entonces, si bien las coincidencias exactas son fáciles, la realidad es que necesitamos un enfoque más sofisticado para la resolución de entidades: uno que sea lo suficientemente robusto para manejar al menos parte de la creatividad ilimitada de los autores humanos. Por eso dividimos el problema en dos pasos: usar Elasticsearch para recuperar candidatos plausibles a escala, y luego usar un LLM para evaluar si esos candidatos realmente se refieren a la misma entidad del mundo real.</p><h2>La solución: Emparejamiento en tres pasos con evaluaciones transparentes de LLM</h2><p>Estamos en medio de un cambio de paradigma en la forma en que usamos las computadoras. Así como el auge de internet nos llevó de la computación localizada a una red globalmente conectada, la IA generativa (GenAI) está cambiando fundamentalmente la forma en que se crean el contenido, el código y la información. De hecho, el prototipo educativo que acompaña a esta serie fue casi exclusivamente "codificado con onda" usando un LLM con indicaciones cuidadosas del autor. Esto no quiere decir que los LLM tengan o incluso alcancen el tipo de productividad inherente al lenguaje humano, pero sí significa que ahora tenemos un recurso poderoso para ayudar con la resolución de entidades.</p><p>Un patrón común que usamos con GenAI es Retrieval-Augmented Generation (RAG). Aquí, <em>recuperación</em> significa recuperar candidatos de entidades (no generar respuestas), y el LLM se utiliza estrictamente para la evaluación y explicación de coincidencias. Si bien <em>podríamos</em> pedirle a un LLM que nos ayude con la resolución de entidades de extremo a extremo, ese es un enfoque costoso, tanto en términos de tiempo como de dinero. La RAG ayuda a los LLM a hacer su trabajo mediante el uso de formas más eficientes de proporcionar contexto al LLM, lo que permite que el LLM ayude de manera eficiente con la resolución de la entidad.</p><p>Para la parte de recuperación de RAG, volvemos a Elasticsearch. Primero encontramos posibles coincidencias usando una combinación de coincidencia exacta, coincidencia con alias y búsqueda híbrida, que combina búsqueda por palabras clave y búsqueda semántica. Una vez que encontramos estas posibles coincidencias, las enviamos a un LLM para su evaluación. El LLM actúa como el evaluador final de coincidencias. También hacemos que el LLM explique su razonamiento, un diferenciador importante con otros sistemas de resolución de entidades. Sin estas explicaciones, la resolución de entidades es una caja negra; con ellas, podemos ver por nosotros mismos por qué una coincidencia tiene sentido.</p><h2>Conceptos clave: Coincidencia de tres pasos, búsqueda híbrida y evaluación transparente del LLM</h2><p><strong>¿Qué es la coincidencia de tres pasos?</strong> Al inicio de este proyecto, planteamos la hipótesis de que la búsqueda semántica sería una parte crucial del sistema, pero no todas las coincidencias requieren una búsqueda tan sofisticada. Para encontrar coincidencias de manera eficiente, adoptamos un enfoque progresivo para solucionar el problema. Primero, buscamos coincidencias exactas mediante la búsqueda por palabra clave. Si encontramos dicha coincidencia, nuestro trabajo está terminado y podemos seguir adelante. Si falla la coincidencia exacta, recurrimos a la coincidencia de alias. En el prototipo, la coincidencia de alias también se efectúa usando la coincidencia exacta con palabras clave, para simplificar. En producción, puedes ampliar este paso con normalización, reglas de transliteración, coincidencia difusa o tablas de alias seleccionadas. Si aún no hemos encontrado una posible coincidencia en los dos primeros pasos, entonces es hora de incorporar la búsqueda semántica a través de la búsqueda híbrida de Elasticsearch con fusión de rango recíproco (RRF, por sus siglas en inglés).</p><p><strong>¿Qué es la búsqueda híbrida?</strong> En Elasticsearch, puedes utilizar la búsqueda semántica para encontrar coincidencias significativas que tengan en cuenta el contexto. Elasticsearch se emplea ampliamente para la búsqueda vectorial y la recuperación híbrida. La similitud semántica es muy útil para el significado, pero no sustituye al filtrado estructurado (por ejemplo, por rangos de tiempo, ubicaciones o identificadores), y a menudo es innecesaria cuando se dispone de una coincidencia exacta. Elasticsearch hizo su marca con la búsqueda léxica, que es excelente en tareas donde la búsqueda semántica no encaja. Para aprovechar al máximo ambos enfoques, utilizamos la búsqueda léxica junto con la búsqueda semántica en una única consulta híbrida. Luego combinamos los resultados para encontrar las coincidencias más probables usando RRF. En el prototipo, los dos primeros resultados se convierten en posibles coincidencias que pueden enviarse para la evaluación del LLM.</p><p><strong>¿Por qué una evaluación del LLM?</strong> Las evaluaciones y explicaciones del LLM permiten que nuestro sistema maneje la ambigüedad y el contexto de manera transparente. Esto es vital para casos como "el presidente", que podría referirse a múltiples entidades, dependiendo del contexto, pero también hace que cosas como los apodos y las variaciones culturales funcionen bien en el sistema. Por último, cuando consideramos tareas de misión crítica, como identificar entidades de listas de sanciones, necesitamos saber por qué se aceptó una coincidencia para poder confiar en el sistema. Fundamentalmente, el LLM no busca en todo el corpus; evalúa solo el pequeño conjunto de candidatos devuelto por Elasticsearch.</p><h2>Resultados del mundo real: Coincidencia con el razonamiento del LLM</h2><p>Un gran desafío para cualquier tarea de procesamiento de lenguaje natural es la creación de un documento de referencia, una "clave de respuestas" que nos dice cuáles son los resultados previstos. Sin esto, es casi imposible evaluar el rendimiento de un sistema en una tarea, pero crear un documento de este tipo puede ser un proceso laborioso. Para el prototipo de resolución de entidades, volvimos a recurrir a la GenAI para ayudar a configurar datos contra los que pudiéramos probar.</p><p>Primero definimos varios tipos de desafíos, como apodos y transliteraciones, y luego pedimos al LLM que creara una colección escalonada de sets de datos que se hiciera progresivamente más grande y desafiante para el sistema. La creación de los sets de datos fue menos sencilla de lo que cabría esperar. El LLM tenía una fuerte tendencia a "hacer trampa" al hacer demasiado fácil obtener la respuesta correcta. Por ejemplo, uno de los tipos de desafíos se centró en el contexto semántico. Este tipo incluyó cosas como resolver "autor ruso" a "León Tolstói". El LLM colocó incorrectamente "autor ruso", como alias de "León Tolstói", lo que eliminó la necesidad de hacer una búsqueda híbrida para encontrar la coincidencia.</p><p>Después de varias refactorizaciones para solucionar problemas como este, teníamos cinco niveles de sets de datos con los que trabajar. Los niveles 1 a 4 eran progresivamente más amplios y presentaban más tipos de desafíos. El Nivel 5 fue el "desafío definitivo" de sets de datos, compuesto por los ejemplos más complicados de todos los tipos de desafíos. Todos los datos de las pruebas están disponibles en el <a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/comprehensive_evaluation">directorio de evaluación exhaustiva</a>.</p><p>Para evaluar nuestro enfoque de resolución de entidades basado en indicaciones, centramos nuestra atención en los sets de datos de nivel 4. Una nota importante es que la evaluación se hizo como un experimento controlado para que pudiéramos concentrarnos en la calidad de la coincidencia de entidades. Los datos de la lista de vigilancia se enriquecieron previamente con contexto y las entidades se extrajeron del artículo antes de tiempo. Así se garantizó que la evaluación se centrara en la coincidencia y no en la precisión de la extracción. Esto aísla la calidad de la coincidencia; el rendimiento de extremo a extremo dependería adicionalmente de la recuperación de la extracción y la calidad del enriquecimiento.</p><h3>Sets de datos de evaluación</h3><p>El set de datos de evaluación de nivel 4 proporciona una prueba integral de las capacidades del sistema:[1]</p><ul><li><p><strong>Entidades de la lista de vigilancia:</strong> 66 entidades de diversos tipos (personas, organizaciones, ubicaciones).</p></li><li><p><strong>Artículos de prueba:</strong> 69 artículos que abarcan casos de resolución de entidades del mundo real.</p></li><li><p><strong>Coincidencias esperadas:</strong> 206 coincidencias de entidades esperadas en todos los artículos.</p></li><li><p><strong>Tipos de desafíos: </strong>15 tipos diferentes de desafíos que evalúan varios aspectos de la resolución de entidades.</p></li></ul><p>Los tipos de desafíos incluidos en los sets de datos son:</p><ul><li><p><strong>Apodos:</strong> "Bob Smith" → "Robert Smith" (siete artículos).</p></li><li><p><strong>Títulos y honoríficos:</strong> "Dr. Sarah Williams" → "Sarah Williams" (cinco artículos).</p></li><li><p><strong>Contexto semántico:</strong> "Autor ruso" → "León Tolstói" (ocho artículos).</p></li><li><p><strong>Nombres multilingües:</strong> Manejo de nombres en diferentes escrituras (seis artículos).</p></li><li><p><strong>Entidades comerciales:</strong> Variaciones del nombre corporativo (siete artículos).</p></li><li><p><strong>Referencias ejecutivas: </strong>"CEO de Microsoft" → "Satya Nadella" (cinco artículos).</p></li><li><p><strong>Líderes políticos:</strong> Referencias basadas en títulos (cinco artículos).</p></li><li><p><strong>Iniciales:</strong> "J. Smith " → "John Smith" (tres artículos).</p></li><li><p><strong>Variaciones en el orden de los nombres:</strong> Diferentes convenciones de ordenamiento de nombres (tres artículos).</p></li><li><p><strong>Nombres truncados:</strong> Coincidencias parciales de nombres (tres artículos).</p></li><li><p><strong>División de nombres:</strong> Nombres divididos en el texto (tres artículos).</p></li><li><p><strong>Espacios/guiones faltantes:</strong> Variaciones de formato (dos artículos).</p></li><li><p><strong>Transliteración:</strong> Coincidencia de nombres entre sistemas de escritura (dos artículos).</p></li><li><p><strong>Desafíos combinados:</strong> Varios desafíos en un artículo (seis artículos).</p></li><li><p><strong>Negocios complejos:</strong> Relaciones comerciales jerárquicas (cinco artículos).</p></li></ul><p>Veamos cómo se desempeñó la resolución de entidades basada en indicaciones.</p><h3>Rendimiento general</h3><p>Los resultados muestran que la evaluación de coincidencias basada en LLM es muy prometedora, pero también revelan un importante problema de confiabilidad. Debido a que cada par de candidatos debe ser evaluado por el LLM, las fallas en la salida estructurada pueden suprimir la aceptación y la recuperación incluso cuando la recuperación está funcionando bien.</p><p>Métrica</p><p>Valor</p><p>Precisión</p><p>83.8 %</p><p>Recuperación</p><p>62.6 %</p><p>Puntuación F1</p><p>71,7%</p><p>Total de coincidencias encontradas</p><p>344</p><p>Tasa de aceptación de LLM</p><p>44,8%</p><p>Tasa de error</p><p>30.2%</p><h3>El problema de la tasa de error</h3><p>Recuerda que el primer paso que damos en el prototipo es crear posibles pares de coincidencia usando Elasticsearch. Cada una de estas posibles coincidencias necesita ser evaluada por el LLM. Para procesar eficientemente todas esas coincidencias, agrupamos las llamadas a los LLM en batches. Esto reduce los costos y la latencia de la API, pero también aumenta el riesgo de obtener JSON malformado en la salida. A medida que aumenta el tamaño del batch, el JSON se vuelve más largo y complejo, lo que aumenta la probabilidad de que el LLM genere un JSON no válido. De aquí proviene la tasa de error del 30 %. En la evaluación, usamos un tamaño de batch de cinco coincidencias por solicitud. Incluso con este tamaño de batch conservador, seguimos viendo fallos en el análisis de JSON, lo que distorsiona significativamente los resultados de la evaluación.</p><h2>Lo que sigue: Optimización de la integración del LLM</h2><p>Ahora que hemos emparejado entidades usando la búsqueda semántica y la evaluación a cargo del LLM, tenemos un pipeline completo de resolución de entidades. Sin embargo, este enfoque introduce un nuevo modo de fallo cuando la evaluación del modelo es correcta, pero su salida no es utilizable. Podemos optimizar la integración de los LLM para mejorar la fiabilidad y la eficiencia de costos. En la próxima publicación, analizaremos cómo usar la llamada de funciones para una salida estructurada, que proporciona una estructura y seguridad de tipo garantizadas a la vez que reduce errores y costos.</p><h2>Pruébalo tú mismo</h2><p>¿Quieres ver la coincidencia de entidades en acción? Mira el <a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/notebooks#:~:text=5%20minutes%20ago-,03_entity_matching_v3.ipynb,-Initial%20public%20lab">cuaderno de Entity Matching</a> para obtener una guía completa con implementaciones reales, explicaciones detalladas y ejemplos prácticos. El cuaderno te muestra exactamente cómo hacer coincidir entidades empleando la búsqueda de tres pasos, la búsqueda híbrida con RRF y la evaluación impulsada por LLM con razonamiento.</p><p><strong>Recuerda:</strong> Este es un prototipo didáctico diseñado para enseñar los conceptos. Al construir sistemas de producción, considera factores adicionales, como la selección del modelo, la optimización de costos, los requisitos de latencia, la validación de calidad, el manejo de errores y la monitorización, que no están cubiertos en este prototipo orientado al aprendizaje.</p><h2>Notas</h2><ol><li><p>Estos sets de datos son sintéticos y están diseñados con fines didácticos; se aproximan a desafíos reales pero no son representativos de ningún dominio de producción específico.</p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-semantic-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-semantic-search</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Búsqueda híbrida]]></category>
    <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltefc59243d9990405/6a17056ab339d5778f769ebf/473ca4357c7d60f690edbd2a844acda169aca9c3-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 26 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Garantizar la precisión semántica con una puntuación mínima]]></title>
    <description><![CDATA[Mejora la precisión semántica con umbrales de puntuación mínima. El artículo incluye ejemplos concretos de búsqueda semántica e híbrida. ]]></description>
    <content:encoded><![CDATA[<p>La búsqueda semántica ha abierto un mundo de oportunidades para la relevancia en la búsqueda. Los modelos dispersos y densos de alta calidad, como ELSER, E5 y Jina Embedding v4, devuelven resultados relevantes basados en el significado de las palabras, en lugar de la coincidencia de palabras clave. Sin embargo, la búsqueda semántica a veces devuelve resultados irrelevantes al final o para búsquedas que carecen de resultados relevantes en el índice. Esta propiedad de los modelos dispersos y densos puede confundir a los usuarios o desperdiciar valiosos tokens para los modelos de lenguaje grandes (LLM).</p><p>En este artículo, aprenderás cómo puedes utilizar el parámetro de puntuación mínima para aumentar la precisión de tus resultados de búsqueda semántica. Si deseas probar los ejemplos en esta publicación de blog, ve al <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/ensuring-semantic-precision-with-minimum-score/ensuring_semantic_precision_with_minimum_score.ipynb">cuaderno de Jupyter asociado</a>.</p><h2>Antecedentes: Precisión y recuperación</h2><p>En la búsqueda, la relevancia, la <em>precisión </em>y la<em> recuperación </em>son conceptos clave. Se recomienda encarecidamente a todo lector que no esté familiarizado que se interiorice sobre estos conceptos. A continuación se presenta un resumen.</p><ul><li><p><strong>Precisión: </strong>La fracción de resultados de búsqueda devueltos que son relevantes para el usuario.</p></li><li><p><strong>Recuerda: </strong>La fracción de todos los documentos relevantes del corpus que se incluyen en el conjunto de resultados de búsqueda.</p></li></ul><p>O, en otras palabras, la precisión está devolviendo <strong>solo </strong>resultados relevantes; y la recuperación está devolviendo <strong>todos </strong>los resultados relevantes. Como puedes imaginar, estos son, a menudo, requisitos contradictorios. La búsqueda semántica tiende a tener una recuperación muy alta, pero puede tener dificultades con la precisión. Continúa leyendo para saber cómo moverte por esta propiedad.</p><h2>Introducción del parámetro de puntaje mínimo</h2><p>El parámetro "min_score" nos permite mejorar la precisión al establecer una puntuación mínima, lo que truncará el conjunto de resultados y eliminará cualquier coincidencia con una puntuación inferior al umbral definido. A continuación se muestra un ejemplo sencillo:</p>GET search-movies/_search
{
  "retriever": {
    "linear": {
      "min_score": 4,
      "retrievers": [
        ...
      ]
    }
  }
}<h2>Normalizando la puntuación.</h2><p>Establecer una puntuación mínima está bien; sin embargo, no todos los modelos semánticos devuelven una puntuación adecuada para un umbral estático. ELSER, por ejemplo, devuelve una puntuación que no tiene límite. <a href="https://huggingface.co/intfloat/e5-small#faq">Algunas</a> puntuaciones de un modelo denso están estrechamente agrupadas y solo tienen sentido en el contexto de la consulta específica.</p><p>Para la mayoría de los casos de búsqueda semántica, recomendamos usar un enfoque de normalización antes de aplicar el "min_score". La normalización garantiza que la puntuación del documento esté dentro de un intervalo definido. Elasticsearch ofrece dos <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/linear-retriever#linear-retriever-normalizers">normalizadores</a>, "l2_norm" y "minmax". El más comúnmente usado es "minmax", ya que es fácil de entender y funciona bien en muchos escenarios. Las propiedades clave de "minmax" incluyen:</p><ul><li><p>Las puntuaciones de los documentos se distribuyen entre 0 y 1.</p></li><li><p>El documento con la puntuación más alta siempre tiene una puntuación de 1.</p></li><li><p>El documento con la puntuación más baja siempre tiene una puntuación de 0.</p><ul><li><p>Esto puede hacer que sea menos adecuado para la búsqueda por palabras clave. Consulta la sección “Búsqueda híbrida” para más información.</p></li></ul></li></ul><p>A continuación se muestra un ejemplo de una consulta semántica normalizada con <code>min_score</code>. El tamaño de la ventana de clasificación se ha aumentado a 500 para permitirnos devolver una lista más larga de resultados de búsqueda, comenzando en 100.</p>GET search-movies/_search
{
  "size": 100,
  "_source": [
    "title", "overview"
  ],
  "retriever": {
    "linear": {
      "rank_window_size": 500,
      "min_score": 0.25,
      "retrievers": [
        {
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "overview_vector",
                  "query": "superhero movie"
                }
              }
            }
          }
        }
      ]
    }
  }
}<p>El tamaño se ha establecido en un valor más alto de lo que normalmente se ve en producción. Esto es para que podamos inspeccionar la calidad de los resultados de búsqueda y ajustar los resultados.</p><h2>Búsqueda híbrida usando el retriever lineal</h2><p>Para la búsqueda híbrida, el enfoque más sencillo es normalizar todos las puntuaciones, asignar ponderaciones y aplicar una puntuación mínima. Ten en cuenta que al elegir ponderaciones con una suma de 1, mantienes la puntuación total dentro de un rango de 0–1. Esto hace que sea más fácil entender las puntuaciones finales y afinar <code>min_score</code>. A continuación se muestra un ejemplo:</p>GET search-movies/_search
{
  "size": 100,
  "_source": ["title", "overview","keywords"],
  "retriever": {
    "linear": {
      "rank_window_size": 500,
      "min_score": 0.25,
      "retrievers": [
        {
          "weight": 0.6,
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "overview_vector",
                  "query": "superhero movie"
                }
              }
            }
          }
        },
        {
          "weight": 0.4,
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "multi_match": {
                  "query": "superhero movie",
                  "fields": ["overview","keywords", "title"],
                  "type": "cross_fields",
                  "minimum_should_match": "2"
                }
              }
            }
          }
        }
      ]
    }
  }
}<h2>Búsqueda híbrida usando RRF.</h2><p>Con BM25, a menudo controlamos la precisión por otros medios, usando, por ejemplo, el operador de <code>AND</code> o <code>minimum_should_match</code>. Además, las consultas que consisten en términos únicos, precisos y poco frecuentes naturalmente generarán resultados con pocos resultados de búsqueda, a menudo todos muy relevantes. Esto puede llevar a:</p><ul><li><p>Los resultados más lejanos en el resultado reciben una puntuación normalizada baja en el recuperador BM25, incluso si la puntuación absoluta de BM25 está cerca de las puntuaciones más altas.</p></li><li><p>Al agregar una puntuación BM25 muy baja a la puntuación semántica, el total puede aproximarse a la puntuación semántica.</p></li><li><p>La falta de contribución de la puntuación BM25 puede hacer que el documento sea descartado por el <code>min_score threshold</code>.</p></li></ul><p>Como solución, podemos utilizar la fusión de rangos recíprocos (RRF) para combinar BM25 y los resultados semánticos. RRF consigue sortear el desafío de comparar puntuaciones de diferentes algoritmos de búsqueda al colocar el foco en la posición en cada conjunto de resultados. En este escenario, la <code>min_score</code> solo se aplica al recuperador semántico.</p>GET search-movies/_search
{
  "_source": ["title", "overview","keywords"],
  "retriever": {
    "rrf": {
      "rank_window_size": 500,
      "retrievers": [
        {
          "linear": {
            "rank_window_size": 500,
            "min_score": 0.25,
            "retrievers": [
              {
                "normalizer": "minmax",
                "retriever": {
                  "standard": {
                    "query": {
                      "semantic": {
                        "field": "overview_vector",
                        "query": "superhero movie"
                      }
                    }
                  }
                }
              }
            ]
          }
        },
        {
          "standard": {
            "query": {
              "multi_match": {
                "query": "superhero movie",
                "fields": ["overview", "keywords","title"],
                "type": "cross_fields",
                "minimum_should_match": "2"
              }
            }
          }
        }
      ]
    }
  }
}<h2>Conclusión</h2><p>Al usar <code>min_score</code>, hemos demostrado cómo podemos reducir el número de falsos positivos en nuestros conjuntos de resultados causados por la alta recuperación de algoritmos de búsqueda semántica. Para saber más sobre los recuperadores, consulta esta <a href="https://www.elastic.co/search-labs/blog/elasticsearch-retrievers">publicación de blog</a> y la <a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">documentación de Elasticsearch</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/semantic-precision-minimum-score</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/semantic-precision-minimum-score</guid>
    <category><![CDATA[Relevancia]]></category>
    <category><![CDATA[Búsqueda híbrida]]></category>
    <dc:creator><![CDATA[Mattias Brunnert]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4a3fba607900049/6a170e0fcdacbf8fe17d2a7a/8b3b5910abfe16d48d309341a0027008b16c4340-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Crear un conector de ChatGPT con Elasticsearch para buscar incidencias de GitHub.]]></title>
    <description><![CDATA[Aprende cómo crear un conector personalizado de ChatGPT y desplegar un servidor MCP de Elasticsearch que utiliza una búsqueda híbrida para buscar incidencias internas de GitHub.]]></description>
    <content:encoded><![CDATA[<p>Recientemente, OpenAI anunció la característica de <a href="https://help.openai.com/en/articles/11487775-connectors-in-chatgpt">conectores personalizados</a> para ChatGPT en los planes Pro/Business/Enterprise y Edu. Además de los conectores listos para usar que permiten acceder a datos en Gmail, GitHub, Dropbox, etc. Es posible crear conectores personalizados utilizando servidores MCP.</p><p>Los conectores personalizados te permiten combinar tus conectores de ChatGPT existentes con otras fuentes de datos como Elasticsearch para obtener respuestas integrales.</p><p>En este artículo, crearemos un servidor <a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP</a> que conecta ChatGPT a un índice de Elasticsearch que contiene información sobre incidencias internas de GitHub y solicitudes de extracción. Esto permite responder a búsquedas en lenguaje natural mediante los datos de Elasticsearch.</p><p>Desplegaremos el servidor MCP utilizando <a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a> en Google Colab con ngrok para obtener una URL pública a la que ChatGPT pueda conectarse, lo que eliminará la necesidad de una infraestructura compleja.</p><p>Para una visión general del MCP y su ecosistema, consulta <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">El estado actual de MCP</a>.</p><h2>Prerrequisitos</h2><p>Antes de comenzar, necesitarás:</p><ul><li><p>Clúster de Elasticsearch (8.X o superior).</p></li><li><p>Clave API de Elasticsearch con acceso de lectura a tu índice.</p></li><li><p>Cuenta de Google (para Google Colab)</p></li><li><p>Cuenta de Ngrok (el nivel gratuito funciona)</p></li><li><p>Cuenta de ChatGPT con plan Pro/Enterprise/Business o Edu.</p></li></ul><h2>Comprensión de los requisitos del conector MCP de ChatGPT.</h2><p>Los conectores MCP de ChatGPT requieren la implementación de dos herramientas: <code>search</code> y <code>fetch</code>. Para más detalles, consulta <a href="https://platform.openai.com/docs/mcp#create-an-mcp-server">OpenAI Docs</a>.</p><h3><a href="https://platform.openai.com/docs/mcp#search-tool">Herramienta de búsqueda</a></h3><p>Devuelve una lista de resultados relevantes de tu índice de Elasticsearch según la búsqueda del usuario.</p><h4>Lo que recibe:</h4><ul><li><p>Un solo texto con la búsqueda de lenguaje natural del usuario.</p></li><li><p>Ejemplo: “Encuentra incidencias relacionadas con la migración de Elasticsearch”.</p></li></ul><h4>Lo que devuelve: </h4><ul><li><p>Un objeto con una clave <code>result</code> que contiene un arreglo de objetos de resultado. Cada resultado incluye:</p><ul><li><p><code>id</code> - Identificador único de documentos.</p></li><li><p><code>title</code> - Título de la incidencia o PR.</p></li><li><p><code>url</code> - Enlace a la incidencia o PR.</p></li></ul></li></ul><h4>En nuestra implementación:</h4>return {
    "results": [
        {
            "id": "PR-612",
            "title": "Fix memory leak in WebSocket notification service",
            "url": "https://internal-git.techcorp.com/pulls/612"
        },
        # ... more results
    ]
}<h3><a href="https://platform.openai.com/docs/mcp#fetch-tool">Herramienta de extracción</a></h3><p>Recupera el contenido completo de un documento específico.</p><h4>Lo que recibe:</h4><ul><li><p>Una sola cadena de texto con el ID del documento de Elasticsearch del resultado de la búsqueda.</p></li><li><p>Ejemplo: “Consígueme los detalles de PR-578”.</p></li></ul><h4>Lo que devuelve:</h4><ul><li><p>Un objeto de documento completo con:</p><ul><li><p><code>id</code> - Identificador único de documentos.</p></li><li><p><code>title</code> - Título de la incidencia o PR.</p></li><li><p><code>text</code> - Descripción completa del problema/PR y sus detalles</p></li><li><p><code>url</code> - Enlace a la incidencia o PR.</p></li><li><p><code>type</code> - Tipo de documento (incidencia, pull_request).</p></li><li><p><code>status</code> - Estado actual (abierto, en progreso, resuelto)</p></li><li><p><code>priority</code> - Nivel de prioridad (bajo, medio, alto, crítico)</p></li><li><p><code>assignee</code> - Persona asignada al problema/PR</p></li><li><p><code>created_date</code> - Fecha de creación.</p></li><li><p><code>resolved_date</code> - Cuando se resolvió (si procede)</p></li><li><p><code>labels</code> - Etiquetas asociadas al documento</p></li><li><p><code>related_pr</code> - ID de solicitud de extracción relacionado</p></li></ul></li></ul>return {
    "id": "PR-578",
    "title": "Security hotfix: Patch SQL injection vulnerabilities",
    "text": "Description: CRITICAL SECURITY FIX for ISSUE-1889. Patches SQL...",
    "url": "https://internal-git.techcorp.com/pulls/578",
    "type": "pull_request",
    "status": "closed",
    "priority": "critical",
    "assignee": "sarah_dev",
    "created_date": "2025-09-19",
    "resolved_date": "2025-09-19",
    "labels": "security, hotfix, sql",
    "related_pr": null
}<p><strong>Nota</strong>: Este ejemplo usa una estructura plana donde todos los campos están en el nivel raíz. Los requisitos de OpenAI son flexibles y también admiten objetos de metadatos anidados.</p><h2>Sets de datos de incidencias y PR de GitHub</h2><p>Para este tutorial, vamos a usar un set de datos interno de GitHub que contenga incidencias y solicitudes de extracción. Esto representa un escenario en el que deseas buscar datos internos privados a través de ChatGPT.</p><p>Los sets de datos se pueden encontrar <a href="https://gist.github.com/TomasMurua/4e7bbdf7a7ebbdffaa663c43578d934a">aquí</a>. Y actualizaremos el índice de los datos mediante la <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk">API de bulk</a>.</p><p>Este sets de datos incluye:</p><ul><li><p>Problemas con descripciones, estado, prioridad y responsables.</p></li><li><p>Solicitudes de extracción con cambios de código, revisiones e información de despliegue.</p></li><li><p>Relaciones entre incidencias y PR (p. ej., PR-578 soluciona ISSUE-1889).</p></li><li><p>Etiquetas, fechas y otros metadatos</p></li></ul><h3>Mappings de índices</h3><p>El índice utiliza los siguientes <a href="https://www.elastic.co/docs/manage-data/data-store/mapping">mappings</a> para brindar soporte a la búsqueda híbrida con <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-elser">ELSER</a>. El campo <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">text_semantic</a> se utiliza para la búsqueda semántica, mientras que los demás campos permiten la búsqueda por palabras clave.</p>{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "title": {
        "type": "text"
      },
      "text": {
        "type": "text"
      },
      "text_semantic": {
        "type": "semantic_text",
        "inference_id": ".elser-2-elasticsearch"
      },
      "url": {
        "type": "keyword"
      },
      "type": {
        "type": "keyword"
      },
      "status": {
        "type": "keyword"
      },
      "priority": {
        "type": "keyword"
      },
      "assignee": {
        "type": "keyword"
      },
      "created_date": {
        "type": "date",
        "format": "iso8601"
      },
      "resolved_date": {
        "type": "date",
        "format": "iso8601"
      },
      "labels": {
        "type": "keyword"
      },
      "related_pr": {
        "type": "keyword"
      }
    }
  }
}<h2>Construye el servidor MCP</h2><p>Nuestro servidor MCP implementa dos herramientas que siguen las especificaciones de OpenAI, y utilizan búsquedas híbridas para combinar coincidencia semántica y textual para obtener mejores resultados.</p><h3>Herramienta de búsqueda</h3><p>Usa la búsqueda híbrida con <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">RRF</a> (Fusión de Rango Recíproco), combinando la búsqueda semántica con la coincidencia de texto:</p>@mcp.tool()
    async def search(query: str) -&gt; Dict[str, List[Dict[str, Any]]]:
        """
        Search for internal issues and PRs using hybrid search (semantic + text with RRF).
        Returns list with id, title, and url per OpenAI spec.
        """
        if not query or not query.strip():
            return {"results": []}

        logger.info(f"Searching for: '{query}'")

        try:
            # Hybrid search with RRF (Reciprocal Rank Fusion)
            response = es_client.search(
                index=ELASTICSEARCH_INDEX,
                size=10,
                source=["id", "title", "url", "type", "priority"],
                retriever={
                    "rrf": {
                        "retrievers": [
                            {
                                # Semantic search with ELSER
                                "standard": {
                                    "query": {
                                        "semantic": {
                                            "field": "text_semantic",
                                            "query": query
                                        }
                                    }
                                }
                            },
                            {
                                # Text search (BM25) for keyword matching
                                "standard": {
                                    "query": {
                                        "multi_match": {
                                            "query": query,
                                            "fields": [
                                                "title^3",
                                                "text^2",
                                                "assignee^2",
                                                "type",
                                                "labels",
                                                "priority"
                                            ],
                                            "type": "best_fields",
                                            "fuzziness": "AUTO"
                                        }
                                    }
                                }
                            }
                        ],
                        "rank_window_size": 50,
                        "rank_constant": 60
                    }
                }
            )

            results = []
            if response and 'hits' in response:
                for hit in response['hits']['hits']:
                    source = hit['_source']
                    results.append({
                        "id": source.get('id', hit['_id']),
                        "title": source.get('title', 'Unknown'),
                        "url": source.get('url', '')
                    })

            logger.info(f"Found {len(results)} results")
            return {"results": results}

        except Exception as e:
            logger.error(f"Search error: {e}")
            raise ValueError(f"Search failed: {str(e)}")<h3>Puntos clave:</h3><ul><li><p><strong>Búsqueda híbrida con RRF:</strong> Combina búsqueda semántica (ELSER) y búsqueda por texto (BM25) para mejores resultados.</p></li><li><p><strong>Búsqueda de múltiples coincidencias:</strong> <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-multi-match-query">Busca en múltiples campos</a> con mejores ponderaciones (título^3, texto^2, responsable^2). El símbolo de intercalación (^) multiplica las puntuaciones de relevancia, y prioriza las coincidencias en los títulos sobre el contenido.</p></li><li><p><strong>Correspondencia aproximada:</strong> <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/common-options#fuzziness"><code>fuzziness: AUTO</code></a> maneja los errores tipográficos y ortográficos al permitir coincidencias aproximadas.</p></li><li><p><strong>Ajuste de parámetros de RRF:</strong></p><ul><li><p><code>rank_window_size: 50</code> - Especifica cuántos resultados principales de cada recuperador (semántico y textual) se consideran antes de combinarlos.</p></li><li><p><code>rank_constant: 60</code> - Este valor determina cuánta influencia tienen los documentos en los conjuntos de resultados individuales sobre el resultado final clasificado.</p></li></ul></li><li><p><strong>Solo devuelve los campos obligatorios:</strong> <code>id</code>, <code>title</code>, <code>url</code> según la especificación de OpenAI, y evita exponer otros campos innecesariamente.</p></li></ul><h3>Herramienta de extracción</h3><p>Recupera los detalles del documento por ID de documento, si existe:</p>@mcp.tool()
    async def fetch(id: str) -&gt; Dict[str, Any]:
        """
        Retrieve complete issue/PR details by ID.
        Returns id, title, text, url.
        """
        if not id:
            raise ValueError("ID is required")

        logger.info(f"Fetching: {id}")

        try:
            # Search by the 'id' field (not _id) since IDs are stored as a field
            response = es_client.search(
                index=ELASTICSEARCH_INDEX,
                body={
                    "query": {
                        "term": {
                            "id": id  # Search by your custom 'id' field
                        }
                    },
                    "size": 1
                }
            )

            if not response or not response['hits']['hits']:
                raise ValueError(f"Document with id '{id}' not found")

            hit = response['hits']['hits'][0]
            source = hit['_source']

            result = {
                "id": source.get('id', id),
                "title": source.get('title', 'Unknown'),
                "text": source.get('text', ''),
                "url": source.get('url', ''),
                "type": source.get('type', ''),
                "status": source.get('status', ''),
                "priority": source.get('priority', ''),
                "assignee": source.get('assignee', ''),
                "created_date": source.get('created_date', ''),
                "resolved_date": source.get('resolved_date', ''),
                "labels": source.get('labels', ''),
                "related_pr": source.get('related_pr', '')
            }

            logger.info(f"Fetched: {result['title']}")
            return result

        except Exception as e:
            logger.error(f"Fetch error: {e}")
            raise ValueError(f"Failed to fetch '{id}': {str(e)}")<h3>Puntos clave:</h3><ul><li><p><strong>Búsqueda por campo de ID de documento:</strong> usa la búsqueda de término en el campo personalizado <code>id</code>.</p></li><li><p><strong>Devuelve el documento completo:</strong> incluye el campo completo <code>text</code> con todo el contenido.</p></li><li><p><strong>Estructura plana:</strong> todos los campos en el nivel raíz, coincidiendo con la estructura de documentos de Elasticsearch.</p></li></ul><h2>Desplegar en Google Colab</h2><p>Usaremos Google Colab para ejecutar nuestro servidor MCP y ngrok para exponerlo públicamente de forma tal que ChatGPT pueda conectarse.</p><h3>Paso 1: Abre el cuaderno de Google Colab.</h3><p>Accede a nuestro cuaderno preconfigurado <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/elasticsearch-chatgpt-connector">Elasticsearch MCP para ChatGPT</a>.</p><h3>Paso 2: Configura tus credenciales</h3><p>Necesitarás tres datos:</p><ul><li><p><strong>URL de Elasticsearch:</strong> Tu <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-enterprise/connect-elasticsearch">URL del cluster de Elasticsearch</a>.</p></li><li><p><strong>Clave API de Elasticsearch:</strong> <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">Clave API</a> con acceso de lectura a tu índice.</p></li><li><p><strong>Token de autenticación ngrok:</strong> Token gratis de <a href="https://ngrok.com/">ngrok</a>. Usaremos ngrok para exponer la URL de MCP a internet y así ChatGPT pueda conectarse.</p></li></ul><h4>Obtener tu token ngrok</h4><ol><li><p>Regístrate para una cuenta gratis en <a href="https://ngrok.com/">ngrok</a></p></li><li><p>Ve a tu <a href="https://dashboard.ngrok.com/">dashboard de ngrok</a></p></li><li><p>Copia tu token de autenticación</p></li></ol><h4>Agregar secretos a Google Colab</h4><p>En el cuaderno de Google Colab:</p><ol><li><p>Haz clic en el <strong>icono de llave </strong>en la barra lateral izquierda para abrir <strong>Secretos</strong>.</p></li><li><p>Añade estos tres secretos:</p></li></ol>ELASTICSEARCH_URL=https://your-cluster.elastic.com:443
ELASTICSEARCH_API_KEY=your-api-key
NGROK_TOKEN=your-ngrok-token<p>3.   Habilitar el acceso al cuaderno para cada secreto</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5acae97b386277f8/6a17f08f5ea30f74c964b6c2/d5dd6ac19fe816a562c6351fdb0f11369da0e877-609x321.jpg" alt="Agregar secretos a Google Collab" /><h3>Paso 3: Ejecutar el cuaderno</h3><ol><li><p>Haz clic en <strong>Tiempo de ejecución</strong> y, a continuación, en <strong>Ejecutar todo</strong> para ejecutar todas las celdas.</p></li><li><p>Espera que el servidor se inicie (aproximadamente 30 segundos).</p></li><li><p>Busque la salida que muestre su URL pública de ngrok</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdd11aacf2deab67c/6a17f091e8fbce81f13a1a41/f185100e8869624bc9e1c7b2b4eb32785e2d89e7-1189x283.png" alt="" /><p>4. La salida mostrará algo como:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8891d917fdbaaf48/6a17f092abe0f208c7dfeaf6/e02e625e91ed9136454e4401b184575fb03a336e-1052x465.jpg" alt="La salida de ejecutar un cuaderno en Google Collab" /><h2>Conéctate a ChatGPT.</h2><p>Ahora conectaremos el servidor MCP a tu cuenta de ChatGPT.</p><ol><li><p>Abre ChatGPT y ve a <strong>Configuración</strong>.</p></li><li><p>Navega a <strong>Conectores</strong>.Si estás usando una cuenta Pro, debes activar el <a href="https://platform.openai.com/docs/guides/developer-mode">modo de desarrollador</a> en los conectores.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt95efdcb2c39307e7/6a17f094abe0f24d8edfeafa/32c02192912fc0e7e5a52e9399077ba7ae3b4901-739x715.png" alt="Conectando el servidor MPC a una cuenta de ChatGPT" /><p><em>Si estás usando la versión Enterprise o Business de ChatGPT, debes publicar el conector en tu lugar de trabajo.</em></p><p>3. Haz clic en <strong>Crear.</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4c8fc8dd6033918/6a17f095631730de19585b7b/15c53e5ccc381108a9dc0052cca05bf0fc97679a-755x683.png" alt="Agregar un conector a ChatGPT" /><p><em><strong>Nota</strong></em><em>: En los espacios de trabajo Business, Enterprise y Edu, solo los propietarios, administradores y usuarios que tengan habilitada la correspondiente configuración (para Enterprise/Edu) pueden agregar conectores personalizados. Los usuarios con un rol de miembro común no tienen la capacidad de agregar conectores personalizados por su cuenta.</em></p><p><em>Una vez que un propietario o usuario administrador agrega un conector y lo habilita, estará disponible para que lo usen todos los miembros del espacio de trabajo.</em></p><p>4. Introduce la información requerida y la URL de tu ngrok que termina en <code>/sse/</code>. Recuerda la “/” después de “sse”. No funcionará si no la agregas:</p><ul><li><p><strong>Nombre:</strong> Elasticsearch MCP</p></li><li><p><strong>Descripción: </strong>MCP personalizado para buscar y extraer información interna de GitHub.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd716ad0beeeb1d35/6a17f09714d90c11cc79b6d7/162a85705cc8ac48a3f2f665551d513e0719f93d-479x684.png" alt="Crear un conector MCP de Elastic " /><p>5. Presione <strong>Crear</strong> para guardar el MCP personalizado.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt857794237d7d3b5a/6a17f0983e03d729b74f2d54/97eb5fb0a32b86bfadfb35561f698616f217c049-913x629.png" alt="Guardar el conector MCP personalizado haciendo clic en crear" /><p>La conexión es instantánea si tu servidor está en funcionamiento. No se necesita ninguna otra autenticación, ya que la clave API de Elasticsearch está configurada en tu servidor.</p><h2>Prueba el servidor MCP</h2><p>Antes de hacer preguntas, necesitas seleccionar qué conector ChatGPT debe usar.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1602c48878dc7a9/6a17f09a6df731cca90a0fff/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" alt="Seleccionar qué conector debe usar ChatGPT" /><h3>Indicación 1: Buscar incidencias</h3><p>Pregunta: “<strong>Encuentre incidencias relacionadas con la migración de Elasticsearch” </strong>y confirma la llamada a la herramienta de acciones.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c204ceacf897f61/6a17f09c9da390fb1de4657d/cfd781acbff8cd7c8095bbe29224f8b26d581f77-650x375.png" alt="Pide a ChatGPT “Encuentra incidencias relacionadas con la migración de Elasticsearch” y confirma la llamada a la herramienta de acciones." /><p>ChatGPT llamará a la herramienta <code>search</code> con tu búsqueda. Puedes ver que está buscando herramientas disponibles y preparándose para llamar a la herramienta Elasticsearch y confirma con el usuario antes de tomar cualquier acción en relación con la herramienta.</p><h4>Solicitud de llamada a la herramienta:</h4>{
  "query": "Elasticsearch migration issues"
}<h4>Respuesta de la herramienta:</h4>{
  "results": [
    {
      "id": "PR-598",
      "title": "Elasticsearch 8.x migration - Application code changes",
      "url": "https://internal-git.techcorp.com/pulls/598"
    },
    {
      "id": "ISSUE-1712",
      "title": "Migrate from Elasticsearch 7.x to 8.x",
      "url": "https://internal-git.techcorp.com/issues/1712"
    },
    {
      "id": "RFC-045",
      "title": "Design Proposal: Microservices Migration Architecture",
      "url": "https://internal-git.techcorp.com/rfcs/045"
    }
    // ... 7 more results
  ]
}<p>ChatGPT procesa los resultados y los presenta en un formato natural y conocido.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1b4378e7d26b4ad0/6a17f09ddbb4ff4de1fb57bf/9d5b6cff85c7e54ccc2584b8ae96d45495fae8c1-923x1352.png" alt="Cómo procesa ChatGPT los resultados de la solicitud de llamada a la herramienta y la respuesta de la herramienta" /><h3>Entre bastidores</h3><h4>Indicación: “Busca incidencias relacionadas con la migración de Elasticsearch”</h4><p>1. Llamadas de ChatGPT <code>search(“Elasticsearch migration”)</code></p><p>2. Elasticsearch realiza una búsqueda híbrida</p><ul><li><p>La <strong>búsqueda semántica</strong> entiende conceptos como “actualización” y “<em>compatibilidad de versiones”.</em></p></li><li><p>La <strong>búsqueda de texto</strong> encuentra coincidencias exactas de "<em>Elasticsearch</em>" y "migración".</p></li><li><p><strong>RRF</strong> combina y clasifica los resultados de ambos enfoques</p></li></ul><p>3. Devuelve los 10 mejores eventos que coinciden con <code>id</code>, <code>title</code>, <code>url</code></p><p>4. ChatGPT identifica “<em>ISSUE-1712: migrar de Elasticsearch 7.x a 8.x</em>” como el resultado más relevante.</p><h3>Indicación 2: Obtén los detalles completos</h3><p>Pregunta: <em><strong>“Obtén detalles de ISSUE-1889”</strong></em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8d1a53db8bfe8326/6a17f09f445de966104d021a/5c0db5245535ce67a36056e61e135bddc97ce496-934x629.png" alt="ChatGPT reconoce que quieres información detallada sobre un problema específico y llama a la herramienta de búsqueda y confirma con el usuario antes de hacer cualquier acción con la herramienta." /><p>ChatGPT reconoce que deseas información detallada sobre una incidencia específica, llama a la herramienta <code>fetch</code> y confirma con el usuario antes de tomar cualquier acción con la herramienta.</p><h4>Solicitud de llamada a la herramienta:</h4>{
  "id": "ISSUE-1889"
}<h4>Respuesta de la herramienta:</h4>{
  "id": "ISSUE-1889",
  "title": "SQL injection vulnerability in search endpoint",
  "text": "Description: Security audit identified SQL injection vulnerability in /api/v1/search endpoint. User input from query parameter is not properly sanitized before being used in raw SQL query. Severity: HIGH - Immediate action required Affected Code: - File: services/search/query_builder.py - Line: 145-152 - Issue: String concatenation used instead of parameterized queries Investigation: - @security_team_alice: Confirmed exploitable with UNION-based injection - @sarah_dev: Checking all other endpoints for similar patterns - @john_backend: Found 3 more instances in legacy codebase Remediation: - Rewrite using SQLAlchemy ORM or parameterized queries - Add input validation and sanitization - Implement WAF rules as additional layer - Security regression tests Comments: - @tech_lead_mike: Stop all other work, this is P0 - @sarah_dev: PR-578 ready with fixes for all 4 vulnerable endpoints - @alex_devops: Deployed hotfix to production 2025-09-19 at 14:30 UTC - @security_team_alice: Verified fix, conducting full pentest next week Resolution: All vulnerable endpoints patched. Added pre-commit hooks to catch raw SQL queries. Security training scheduled for team.",
  "url": "https://internal-git.techcorp.com/issues/1889",
  "type": "issue",
  "status": "closed",
  "priority": "critical",
  "assignee": "sarah_dev",
  "created_date": "2025-09-18",
  "resolved_date": "2025-09-19",
  "labels": "security, vulnerability, bug, sql",
  "related_pr": "PR-578"
}<p>ChatGPT sintetiza la información y la presenta de manera clara.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt560958fa3bd212d0/6a17f0a0faa91355ba93c974/410f19f213e94fc4e3c47eeef6e04b69e0c86159-602x462.png" alt="Cómo ChatGPT sintetiza la información y la presenta " /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcccf35a584e8373b/6a17f0a2505ac3471cad8c2e/54d8ffa117628a1e3afc317c3ab75d4f7731d7ab-767x1600.png" alt="Cómo ChatGPT presenta la información" /><h3>Entre bastidores</h3><h4>Indicación: «Obtén los detalles de ISSUE-1889»</h4><ol><li><p>Llamadas de ChatGPT <code>fetch(“ISSUE-1889”)</code></p></li><li><p>Elasticsearch recupera el documento completo</p></li><li><p>Devuelve un documento completo con todos los campos a nivel raíz.</p></li><li><p>ChatGPT sintetiza la información y responde con citas adecuadas.</p></li></ol><h2>Conclusión</h2><p>En este artículo, creamos un servidor MCP personalizado que conecta ChatGPT a Elasticsearch con herramientas MCP dedicadas de <strong>búsqueda</strong> y <strong>extracción</strong>, lo cual permite realizar búsquedas en lenguaje natural sobre datos privados.</p><p>Este patrón MCP funciona para cualquier índice, documentación, productos, logs o cualquier otro dato de Elasticsearch que quieras buscar mediante lenguaje natural.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/chatgpt-connector-mcp-server-github-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/chatgpt-connector-mcp-server-github-elasticsearch</guid>
    <category><![CDATA[AI agéntica]]></category>
    <category><![CDATA[Búsqueda híbrida]]></category>
    <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1602c48878dc7a9/6a17f09a6df731cca90a0fff/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" length="0" type="image/gif"/>
    <pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Búsqueda híbrida sin dolores de cabeza: simplificando la búsqueda híbrida con retrievers]]></title>
    <description><![CDATA[Explora cómo simplificar la búsqueda híbrida en Elasticsearch con un formato de consulta multicampo para retrievers lineales y RRF, y crea consultas sin conocimientos previos sobre tu índice de Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/what-is/hybrid-search">La búsqueda híbrida</a> es ampliamente reconocida como un enfoque de búsqueda poderosa, que combina la precisión y rapidez de la <a href="https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch#lexical-search---sparse-retrieval">búsqueda léxica</a> con las capacidades de lenguaje natural de <a href="https://www.elastic.co/what-is/semantic-search">la búsqueda semántica</a>. Sin embargo, aplicarlo en la práctica puede ser complicado, ya que a menudo requiere un conocimiento profundo del índice y la construcción de consultas extensas con configuraciones no triviales. En este blog, exploraremos cómo el <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers#multi-field-query-format">formato de consulta multicampo para retrievers lineales y RRF</a> hace que la búsqueda híbrida sea más sencilla y accesible, eliminando los dolores de cabeza comunes y permitiéndote aprovechar todo su poder con mayor facilidad. También revisaremos cómo el formato de consulta multicampo te permite realizar consultas de búsqueda híbridas sin conocimiento previo de tu índice.</p><h2>El problema del rango de puntaje</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3521a6558cd477ca/6a17f174445de94d3c4d0225/c8b49153c47d2cdc233c0d2e440db04711d48ca5-1600x1600.jpg" alt="" /><p>Para contextualizar, repasemos una de las principales razones por las que la búsqueda híbrida puede ser difícil: los rangos de puntaje variables. Nuestro viejo <a href="https://www.elastic.co/elasticon/conf/2016/sf/improved-text-scoring-with-bm25">amigo BM25</a> produce puntajes ilimitados. En otras palabras, BM25 puede generar puntajes que van desde cerca de 0 hasta (teóricamente) infinitas. En cambio, las consultas contra <code>dense_vector</code> campos producirán puntajes acotados entre 0 y 1. Para empeorar este problema, <code>semantic_text</code> ofusca el tipo de campo empleado para indexar incrustaciones, así que a menos que tengas un conocimiento detallado sobre tu configuración de índices y endpoints de inferencia, puede ser difícil saber cuál será el rango de puntaje de tu consulta. Esto presenta un problema al intentar entrecalar resultados de búsqueda léxica y semántica, ya que los resultados léxicos pueden tener prioridad sobre los semánticos incluso si los resultados semánticos son más relevantes. La solución generalmente aceptada para este problema es normalizar los puntajes antes de entrelazar los resultados. Elasticsearch dispone de dos herramientas para esto: los <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/linear-retriever">retrievers lineales</a> y <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/rrf-retriever">RRF</a> .
</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt09ed6bf3d25066bd/6a17f1750b0bedbd70dd3686/264481268c8b6ac259e3c257b85431b513f16672-1077x586.png" alt="Comparación de resultados de búsqueda con lineales/rrf vs. sin" /><p>El <strong>recuperador RRF</strong> aplica el <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">algoritmo RRF</a>, usando el rango del documento como medida de relevancia y descartando el puntaje. Como el puntaje no se tiene en cuenta, las discrepancias en el rango de puntaje no son un problema.</p><p>El <strong>retriever lineal</strong> emplea una combinación lineal para determinar el puntaje final de un documento. Esto implica tomar el puntaje de cada consulta componente para el documento, normalizarla y sumarla para generar el puntaje total. Matemáticamente, la operación puede expresar como:</p>Total Score = 𝚺(N(Sx))<p>Donde <code>N</code> es la función de normalización, y SX es el puntaje para la consulta X. La función de normalización es clave aquí, ya que transforma el puntaje de cada consulta para usar el mismo rango. Puedes aprender más sobre el retriever <a href="https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search">lineal aquí</a>.</p><h2>Desglosándolo</h2><p>Los usuarios pueden implementar una búsqueda híbrida eficaz con estas herramientas, pero requiere cierto conocimiento sobre tu índice. Veamos un ejemplo con el retriever lineal, donde consultaremos un índice con dos campos:</p>PUT linear_retriever_example
{
  "mappings": {
    "properties": {
      "semantic_text_field": { &lt;1&gt;
        "type": "semantic_text",
        "inference_id": ".multilingual-e5-small-elasticsearch"
      },
      "text_field": { &lt;2&gt;
        "type": "text"
      }
    }
  }
}<p>1. <code>semantic_text_field</code> es un campo <code>semantic_text</code> que emplea <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5">E5</a>, un modelo de incrustación de texto</p><p>2. <code>text_field</code> es un campo estándar de <code>text</code></p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "standard": {
              "query": {
                "match": { &lt;1&gt;
                  "semantic_text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}<p>1. Usamos una consulta <code>match</code> en nuestro campo <code>semantic_text</code> , para la que <a href="https://www.elastic.co/search-labs/blog/semantic-search-match-knn-sparse-vector#we-made-match-happen-in-semantic-search!">agregamos soporte en Elasticsearch 8.18/9.0</a></p><p>
Al construir la consulta, debemos tener en cuenta que <code>semantic_text_field</code> emplea un modelo de incrustación de texto, por lo que cualquier consulta generará un puntaje entre 0 y 1. También necesitamos saber que <code>text_field</code> es un campo de <code>text</code> estándar y, por tanto, las consultas sobre él generarán un puntaje no acotada. Para crear un conjunto de resultados con la relevancia adecuada, necesitamos usar un retriever que normalice los puntajes de consulta antes de combinarlas. En este ejemplo, usamos el retriever lineal con <code>minmax</code> normalización, que normaliza el puntaje de cada consulta a un valor entre 0 y 1.</p><p>La construcción de la consulta en este ejemplo es bastante sencilla porque solo están involucrados dos campos. Sin embargo, puede complicar muy rápido a medida que se agregan más campos, y de diferentes tipos. Esto demuestra cómo escribir una consulta híbrida eficaz suele requerir un conocimiento más profundo del índice que se consulta, de modo que los puntajes de los componentes se normalicen correctamente antes de la combinación. Esto supone una barrera para la adopción más amplia de la búsqueda híbrida.</p><h3>Agrupación de consultas</h3><p>Ampliemos el ejemplo: ¿Y si quisiéramos consultar un campo <code>text</code> y dos campos <code>semantic_text</code> ? Podríamos construir una consulta así:</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "semantic_text_field_1",
                  "query": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "semantic_text_field_2",
                  "query": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}<p>Eso parece bueno a simple vista, pero hay un posible problema. Ahora los <code>semantic_text</code> partidos de campo representan dos tercios del total del puntaje:</p>Total Score = N(semantic_text_field_1 score) + N(semantic_text_field_2 score) + N(text_field score)<p>Probablemente esto no es lo que buscas porque crea un puntaje desequilibrado. Los efectos pueden no ser tan evidentes en un ejemplo como este con solo 3 campos, pero se vuelve problemático cuando se consultan más campos. Por ejemplo, la mayoría de los índices contienen muchos más cuerpos léxicos que la semántica (es decir, <code>dense_vector</code>, <code>sparse_vector</code>, o <code>semantic_text</code>). ¿Y si consultáramos un índice con 9 campos léxicos y 1 campo semántico usando el patrón anterior? Las coincidencias léxicas representarían el 90% del puntaje, lo que reduce la efectividad de la búsqueda semántica.</p><p>Una forma común de abordar esto es agrupar las consultas en categorías léxicas y semánticas y ponderar ambas de forma uniforme. Esto impide que cualquiera de las dos categorías domine el puntaje total.</p><p>Pongámoslo en práctica. ¿Cómo sería este enfoque de consultas agrupadas en este ejemplo al usar el retriever lineal?</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "linear": {
              "retrievers": [
                {
                  "retriever": {
                    "standard": {
                      "query": {
                        "semantic": {
                          "field": "semantic_text_field_1",
                          "query": "foo"
                        }
                      }
                    }
                  },
                  "normalizer": "minmax"
                },
                {
                  "retriever": {
                    "standard": {
                      "query": {
                        "semantic": {
                          "field": "semantic_text_field_2",
                          "query": "foo"
                        }
                      }
                    }
                  },
                  "normalizer": "minmax"
                }
              ]
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}<p>¡Vaya, esto se está poniendo muy extenso! ¡Puede que incluso tuviste que desplazarte arriba y abajo varias veces para revisar toda la consulta! Aquí, usamos dos niveles de normalización para crear los grupos de consultas. Matemáticamente, puede expresar como:</p>Total Score = N(N(semantic_text_field_1 score) + N(semantic_text_field_2 score)) + N(text_field score)<p>Este segundo nivel de normalización garantiza que las consultas contra los campos <code>semantic_text</code> y <code>text</code> campo tengan un peso uniforme. Ten en cuenta que en este ejemplo omitimos la normalización de segundo nivel para <code>text_field</code> ya que solo hay un cuerpo léxico, lo que te ahorra <em>aún más</em> verbosidad.</p><p>Esta estructura de consulta ya es engorrosa y solo estamos consultando tres campos. Se vuelve cada vez más inmanejable, incluso para los profesionales experimentados en búsqueda, a medida que consultas más campos.</p><h2>El formato de consulta multicampo</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5d9007d279f0da29/6a17f17714d90c39cf79b6f5/dd04e1686076a574b717c1460acfe4eb79299208-1600x1600.jpg" alt="" /><p>Agregamos el <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers#multi-field-query-format">formato de consulta multicampo</a> para los retrievers lineales y RRF en Elasticsearch 8.19, 9.1 y <a href="https://www.elastic.co/cloud/serverless">serverless</a> para simplificar todo esto. Ahora puedes realizar la misma consulta que antes con simplemente:</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>¡Lo que reduce la consulta de 55 líneas a solo 9! Elasticsearch emplea automáticamente los mapeos de índice para:</p><ul><li><p>Determinar el tipo de cada campo consultado</p></li><li><p>Agrupa cada cuerpo en una categoría léxica o semántica</p></li><li><p>Pondera cada categoría de forma equitativa en el puntaje final</p></li></ul><p>Esto permite a cualquiera ejecutar una consulta híbrida eficaz sin necesidad de conocer detalles sobre el índice o los endpoints de inferencia empleados.</p><p>Al usar RRF, puedes omitir la <code>normalizer</code>, ya que rango se usa como indicador de relevancia:</p>GET rrf_retriever_example/_search
{
  "retriever": {
    "rrf": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field" ],
      "query": "foo"
    }
  }
}<h2>Impulso por campo</h2><p>Al usar el retriever lineal, puedes aplicar un aumento por campo para ajustar la importancia de los combates en ciertos campos. Por ejemplo, supongamos que consultas cuatro campos: dos campos <code>semantic_text</code> y dos campos <code>text</code> :</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field_1", "text_field_2" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>Por defecto, cada cuerpo tiene un peso igual en su grupo (léxico o semántico). El desglose del puntaje es el siguiente:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdc6e197e5ff7b68d/6a17f179505ac32834ad8c46/ba31c76189e3a1e5b1638437ccf0528aafec2598-1600x549.png" alt="Comparación de grupos de consulta y puntajes de campo" /><p>En otras palabras, cada campo representa el 25% del puntaje total.</p><p>Podemos usar la sintaxis <code>field^boost</code> para agregar un impulso por campo a cualquier campo. Vamos a aplicar un aumento de 2 a <code>semantic_text_field_1</code> y <code>text_field_1</code>:</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1^2", "semantic_text_field_2", "text_field_1^2", "text_field_2" ]
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>Ahora el desglose del puntaje es así:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltccc9ed7d6e865a5e/6a17f17abe6086a31d004882/de20e555d52f914bf483a048d056f54f4fece757-1600x549.png" alt="Cambio en el peso del campo con referencia y búsqueda híbrida" /><p>Cada grupo de consulta sigue teniendo un peso igual, pero ahora el peso del campo dentro de los grupos cambió:</p><ul><li><p><code>semantic_text_field_1</code> es el 66% del puntaje del grupo de consultas semánticas, 33% del puntaje total</p></li><li><p><code>text_field_1</code> es el 66% del puntaje del grupo de consultas léxicas, 33% del puntaje total</p></li></ul><p>i️ Ten en cuenta que el rango total de puntaje no cambiará cuando se aplica un aumento por campo. Este es un efecto secundario previsto de la normalización de puntajes, que garantiza que los puntajes de consulta léxica y semántica sigan siendo directamente comparables entre sí.</p><p>i️ El aumento por campo también puede usar con el recuperador RRF en Elasticsearch 9.2+</p><h3>Resolución comodín</h3><p>Puedes usar el comodín <code>*</code> en el parámetro <code>fields</code> para que coincida con varios campos. Continuando con el ejemplo anterior, esta consulta es funcionalmente equivalente a consultar s<code>emantic_text_field_1</code>, <code>semantic_text_field_2</code>, y <code>text_field_1</code> explícitamente:</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_*", "*_field_1" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>Es interesante notar que el patrón de <code>*_field_1</code> coincide tanto con <code>text_field_1</code> como con <code>semantic_text_field_1</code>. Esto se gestiona automáticamente; La consulta se ejecutará como si cada uno de los campos fuera consultado explícitamente. También está bien que el <code>semantic_text_field_1</code> coincida con ambos patrones; Todas las coincidencias de nombres de campo se deduplican antes de la ejecución de la consulta.</p><p>Puedes usar el comodín de varias maneras:</p><ul><li><p>Coincidencia de prefijos (ej: <code>*_text_field</code>)</p></li><li><p>Emparejamiento en línea (ej: <code>semantic_*_field</code>)</p></li><li><p>Coincidencia de sufijos (ej: <code>semantic_text_field_*</code>)</p></li></ul><p>También puedes usar varios comodines para aplicar una combinación de lo anterior, como <code>*_text_field_*</code>.</p><h3>Campos de consulta predeterminados</h3><p>El formato de consulta multicampo también te permite consultar un índice del que no sabes nada. Si omites el parámetro <code>fields</code> , consultará todos los campos especificados por la <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules">configuración de índice index.query.default_field</a>:</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>Por defecto, <code>index.query.default_field</code> está configurado como <code>*</code>. Este comodín se resolverá a todos los tipos de campo del índice que admitan consultas de término, que es la mayoría. Las excepciones son:</p><ul><li><p><code>dense_vector</code> Campos</p></li><li><p><code>rank_vector</code> Campos</p></li><li><p>Campos geométricos: <code>geo_point</code>, <code>shape</code></p></li></ul><p>Esta funcionalidad es especialmente útil cuando se quiere realizar una consulta de búsqueda híbrida sobre un índice proporcionado por un tercero. El formato de consulta multicampo permite ejecutar una consulta adecuada de forma sencilla. Simplemente excluye el parámetro <code>fields</code> y se consultarán todos los campos aplicables.</p><h2>Conclusión</h2><p>El problema del rango de puntaje puede hacer que la búsqueda híbrida efectiva sea un dolor de cabeza de implementar, especialmente cuando hay poca comprensión del índice que se consulta o de los endpoints de inferencia empleados. El formato de consulta multicampo para los retrievers lineales y RRF alivia este problema al empaquetar un enfoque automatizado de búsqueda híbrida basado en agrupación de consultas en una API simple y accesible. Funcionalidades adicionales, como el aumento por campo, la resolución de comodines y los campos de consulta por defecto, amplían la funcionalidad para cubrir muchos casos de uso.</p><h2>Prueba hoy el formato de consulta multicampo</h2><p>Puedes consultar los retrievers lineales y RRF con el formato de consulta multicampo en proyectos <a href="https://www.elastic.co/cloud/serverless">Serverless</a> totalmente gestionados de Elasticsearch con <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/create-serverless-project">una prueba gratis</a>. También está disponible en versiones de pila a partir de la 8.19 y 9.1.</p><p>Empieza en minutos en tu entorno local con un solo comando:</p>curl -fsSL https://elastic.co/start-local | sh<p></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hybrid-search-multi-field-query-retrievers-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hybrid-search-multi-field-query-retrievers-elasticsearch</guid>
    <category><![CDATA[Búsqueda híbrida]]></category>
    <category><![CDATA[Relevancia]]></category>
    <dc:creator><![CDATA[Mike Pellegrini]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt89e066372c549595/6a17f17c3e9e457c38ba1583/4494f98ae3958bbdbc6171df9677fc4d65ec5640-1536x1024.png" length="0" type="image/png"/>
    <pubDate>Thu, 27 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Ya sabes, para contexto - Parte III: El poder de la búsqueda híbrida en ingeniería de contexto]]></title>
    <description><![CDATA[Descubre cómo usar la ingeniería de contexto y la búsqueda híbrida para mejorar la precisión de la salida de la IA mediante agregaciones, RBAC y señales no relacionadas con contenido.]]></description>
    <content:encoded><![CDATA[<p>Hablamos tanto de búsqueda híbrida (<a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">Parte I</a>) como de ingeniería del contexto (<a href="https://www.elastic.co/search-labs/blog/context-engineering-llm-evolution-agentic-ai">Parte II</a>); Ahora, vamos a profundizar en cómo trabajan juntos para lograr el mayor efecto en proporcionar contexto dirigido a las operaciones de IA RAG y agente.</p><h2>La búsqueda no está muerta, solo se movió</h2><p>Así que tuvimos este cambio de buscar principalmente contexto a través de un cuadro de texto y usar la información (el contexto) que devuelven para construir las respuestas nosotros mismos, a ahora usar lenguaje natural para decirle a un agente lo que queremos y dejar que él investigue y compile automáticamente la respuesta por nosotros. Muchos en el mundo tecnológico señalan este cambio y proclaman que "la búsqueda está muerta" (bueno, el mundo del SEO y las palabras publicitarias <a href="https://www.pewresearch.org/short-reads/2025/07/22/google-users-are-less-likely-to-click-on-links-when-an-ai-summary-appears-in-the-results/">definitivamente está cambiando</a>: ¿ <a href="https://www.wired.com/story/goodbye-seo-hello-geo-brandlight-openai/">alguien quiere GEO</a> ?), pero la búsqueda sigue siendo absolutamente crítica para las operaciones agenticas — solo que ahora se realiza en gran medida fuera de la vista a través de las herramientas.</p><p>Anteriormente, los humanos eran los principales árbitros de relevancia subjetiva: cada usuario tiene sus propios motivos para realizar la búsqueda, y su experiencia personal influye en la precisión relativa de los resultados. Si queremos confiar en que los agentes pueden llegar a la misma conclusión (o mejor) que nosotros, debemos cerciorarnos de que la información contextual a la que tienen acceso esté lo más cerca posible de nuestra intención subjetiva. ¡Tenemos que diseñar el contexto que proporcionamos a los LLMs para ese objetivo!</p><h2>Generación de contexto con recuperación de búsqueda híbrida</h2><p>Solo un recordatorio de la Parte I de que la búsqueda híbrida de Elastic combina las fortalezas de la búsqueda tradicional basada en palabras clave (flexibilidad sintaxis, precisión de palabras clave y puntaje de relevancia) con la comprensión semántica de la búsqueda por similitud vectorial, y ofrece múltiples técnicas de reclasificación. Esta sinergia (¡nunca se encontró un uso más verdadero de esa palabra!) Permite resultados muy relevantes, con consultas que pueden ser mucho más matizadas en cómo dirigen el contenido. No es solo que puedas aplicar la relevancia subjetiva como <em>una</em> de tus etapas de recuperación; En realidad, la recuperación de la primera etapa puede incluir puntaje de relevancia junto con todos esos otros modos a la vez.</p><h3>Precisión y eficiencia superiores</h3><p>Emplear una plataforma de datos que pueda ofrecer búsqueda, recuperación y reclasificación distribuidas como tu principal motor de recuperación de contexto tiene mucho sentido. Puedes usar sintaxis avanzada de consulta para agregar el componente que falta de la intención subjetiva y filtrar contenido que pueda distraer o enturbiar el valor de la información contextual devuelta. Puedes seleccionar cualquiera de las opciones sintácticas individuales disponibles, o combinar modalidades en una única búsqueda que se dirija a cada tipo de datos de la manera que mejor entienda, y luego combinarlas o reordenarlas con el reclasificamiento. Puedes filtrar la respuesta para incluir solo los campos/valores que quieres, manteniendo a distancia los datos superfluos. En servicio de los agentes, esa flexibilidad de segmentación te permite construir herramientas extremadamente precisas en cómo recuperan el contexto.</p><h3>Refinamiento del contexto (agregaciones y señales no de contenido)</h3><p>Las agregaciones pueden ser especialmente útiles para moldear el contenido que una herramienta entrega a la ventana de contexto. Las agregaciones proporcionan naturalmente datos numéricos sobre la forma de los datos contextuales devueltos, lo que facilita y hace más preciso que los LLMs razonen. Como las agregaciones pueden anidar jerárquicamente, es una forma sencilla de agregar detalles multinivel para que el LLM genere una comprensión más matizada. Las agregaciones también pueden ayudar a gestionar el tamaño de la ventana de contexto — puedes reducir fácilmente un resultado de consulta de 100k documentos a unos pocos cientos de tokens de insights agregados.</p><p>Las señales no relacionadas con el contenido son los indicadores inherentes a tus datos que te muestran una visión general de lo que estás viendo; Son las características adicionales de los resultados, como popularidad, frescura, geolocalización, categorías, diversidad de anfitriones o bandas de precios. Estos datos pueden ser útiles para informar al agente sobre cómo valora la importancia del contexto que recibió. Algunos ejemplos sencillos podrían ayudar a ilustrar esto mejor:</p><ul><li><p><strong>Potenciar contenido publicado recientemente y popular</strong> - Imagina que tienes una base de conocimientos de artículos. Quieres encontrar artículos relevantes para la consulta de un usuario, pero también potenciar artículos que sean recientes y que fueron útiles por otros usuarios (por ejemplo, que tengan un alto número de "me gusta"). En este escenario, podemos usar una búsqueda híbrida para encontrar artículos relevantes y luego reclasificarlos en función de una combinación de su fecha de publicación y popularidad.</p></li><li><p><strong>Búsqueda de comercio electrónico con ajustes de ventas y stock</strong> - En un entorno de comercio electrónico, quieres mostrar a los clientes productos que coincidan con su término de búsqueda, pero también quieres promocionar productos que se venden bien y estén en stock. También podrías bajar el rango de productos con poco stock para evitar frustraciones del cliente.</p></li><li><p><strong>Priorizar los problemas de alta gravedad en un rastreador de errores</strong> : para un equipo de desarrollo de software, al buscar problemas, es fundamental destacar primero los problemas de alta gravedad, alta prioridad y actualizados recientemente. Puedes usar no señales como 'criticidad' y 'más debatido' para sopesar diferentes factores de forma independiente, cerciorando que los temas más críticos y debatidos salgan a la superficie</p></li></ul><p>Estas consultas de ejemplo y más se pueden encontrar en la <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/you-know-for-context/">página de contenido</a> de Elasticsearch Labs que la acompaña.</p><h3>Aplicación de la seguridad</h3><p>Un beneficio crítico de aprovechar una capa de velocidad basada en búsqueda como Elastic para la ingeniería de contexto es su marco de seguridad integrado. La plataforma de Elastic garantiza que el contexto entregado a las operaciones de IA agente y generativa respete y proteja la información privada sensible mediante un control de acceso basado en roles (RBAC) y un control de acceso basado en atributos (ABAC). Esto significa que no solo las consultas se gestionan con eficiencia, sino que los resultados se filtran según las licencias específicas del agente o del usuario que inicia la solicitud.</p><p>Los agentes se ejecutan como el usuario autenticado, por lo que la seguridad se aplica implícitamente a través de las características de seguridad integradas en la plataforma:</p><ul><li><p><strong>Licencias detalladas:</strong> Define el acceso a nivel de documento, campo o incluso término, cerciorando que los agentes de IA solo reciban los datos que están autorizados a ver.</p></li><li><p><strong>Control de acceso basado en roles (RBAC):</strong> Asignar roles a agentes o usuarios, otorgando acceso a conjuntos de datos o funcionalidades específicas según sus responsabilidades definidas.</p></li><li><p><strong>Control de acceso basado en atributos (ABAC):</strong> Implementar políticas de acceso dinámicas basadas en los atributos de los datos, del usuario o del entorno, permitiendo una seguridad altamente adaptable y consciente del contexto.</p></li><li><p><strong>Seguridad a nivel de documento (DLS) y seguridad a nivel de campo (FLS):</strong> Estas capacidades cercioran que, incluso dentro de un documento recuperado, solo sean visibles las partes autorizadas, evitando que se exponga información sensible.</p></li><li><p><strong>Integración con la seguridad empresarial:</strong> Integra sin problemas con los sistemas de gestión de identidades existentes (como LDAP, SAML, OIDC) para hacer cumplir políticas de seguridad coherentes en toda la organización.</p></li></ul><p>Al integrar estas medidas de seguridad directamente en el mecanismo de recuperación de contexto, Elastic actúa como un guardián seguro, cerciorando que los agentes de IA operen dentro de límites de datos definidos, evitando exposiciones no autorizadas y manteniendo el cumplimiento de las normativas de privacidad de datos. Esto es fundamental para generar confianza en sistemas de IA agente que manejan información confidencial o propietaria.</p><p>Como beneficio adicional, al usar una capa unificada de velocidad de datos sobre las fuentes de datos de tu compañía, alivias las cargas inesperadas de consultas ad hoc en esos repositorios que crearían las herramientas agentes. Tienes un único lugar para buscar todo casi en tiempo real, y un lugar para aplicar controles de seguridad y gobernanza.</p><h2>Herramientas híbridas basadas en búsqueda</h2><p>Hay algunas características fundamentales (y <a href="https://www.elastic.co/blog/whats-new-elastic-9-2-0">cada vez van más</a> y más) de la plataforma Elastic que impulsan mucho la búsqueda de la ingeniería de contexto. Lo principal aquí es que la plataforma ofrece multitud de formas de lograr cosas, con la flexibilidad de adaptar, cambiar y ampliar métodos a medida que avanza el ecosistema de IA.</p><h3>Presentando Agent Builder</h3><p>Elastic <a href="https://www.elastic.co/elasticsearch/agent-builder">Agent Builder</a> es nuestra primera incursión en el ámbito de herramientas de IA agente diseñadas para comunicar con los datos que ya almacenas en Elastic. Agent Builder ofrece una interfaz de chat que permite a los usuarios crear y gestionar sus propios agentes y herramientas dentro de Kibana. Incluye servidores MCP y A2A integrados, APIs programáticas y un conjunto de herramientas de sistema prediseñadas para consultar y explorar índices de Elasticsearch, así como para generar ES|Consultas QL desde lenguaje natural. Agent Builder te permite crear herramientas personalizadas que dirigen y esculpen los datos contextuales devueltos al agente a través de <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES| expresivoSintaxis de consultas QL</a> .</p><p>¿Cómo funciona ES|¿Quieres que QL realice búsqueda híbrida, preguntas? La capacidad principal se logra mediante la combinación del tipo de campo <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">semantic_text</a> y los comandos <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fork">FORK</a>/<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fuse">FUSE</a> (FUSE usa <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">RRF</a> por defecto para fusionar los resultados de cada bifurcación). Aquí tienes un ejemplo sencillo de una búsqueda ficticia de producto:</p>FROM products
| FORK
  (MATCH description "high performance gaming laptop" | EVAL search_type = "bm25"),
  (MATCH description_semantic "high performance gaming laptop" | EVAL search_type = "semantic")
| FUSE 
| LIMIT 20
| KEEP product_name, description, _score, search_type<p>La cláusula <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval">EVAL</a> incluida con cada una de las ramas FORK en el ejemplo anterior no es estrictamente necesaria; Solo se incluye para demostrar cómo se puede rastrear de qué modalidad de búsqueda se devuelve un resultado determinado.</p><h3>Plantillas de búsqueda</h3><p>Supongamos que quieres apuntar tus propias herramientas de agencia externa a tu despliegue de Elastic. Y en lugar de ES|QL, quieres usar recuperadores multietapa o reutilizar la sintaxis DSL existente que desarrollaste, y también quieres poder controlar las entradas que acepta la consulta, la sintaxis usada para ejecutar la búsqueda y los campos devueltos en la salida. Las <a href="https://www.elastic.co/docs/solutions/search/search-templates">plantillas de búsqueda</a> permiten a los usuarios definir estructuras predefinidas para patrones de búsqueda comunes, mejorando la eficiencia y la consistencia en la obtención de datos. Esto es especialmente beneficioso para herramientas agentes que interactúan con APIs de búsqueda, ya que ayudan a estandarizar el código estándar y permiten una iteración más rápida de la lógica de búsqueda. Y si alguna vez necesitas ajustar alguno de esos factores, solo actualizas la plantilla de búsqueda y voilà que los cambios se implementan. Si buscas un ejemplo de plantillas de búsqueda en acción con herramientas agentes, echa un vistazo al blog de Elasticsearch Labs '<a href="https://www.elastic.co/search-labs/blog/mcp-intelligent-search">MCP for intelligent search</a>', que emplea una plantilla de búsqueda detrás de una llamada a herramienta desde un servidor MCP externo.</p><h3>Flujos de trabajo integrados (¡por la primera vez!)</h3><p>Una de las cosas más difíciles de navegar en nuestro nuevo mundo de IA agente es la naturaleza no determinista de agentes "razonamientos" semi-autónomos y autodirigidos. La ingeniería de contexto es una disciplina crítica para la IA agentica: son las técnicas que ayudan a reducir las posibles conclusiones que puede generar nuestro agente a lo que sabemos de la verdad fundamental. Incluso con una ventana de contexto altamente precisa y relevante (cuando salimos del ámbito de los hechos numéricos) seguimos faltando esa pequeña garantía de que la respuesta del agente es totalmente repetible y fiable.</p><p>Cuando envías la misma solicitud a un agente varias veces, las respuestas pueden ser <em>esencialmente</em> las mismas con <em>solo una pequeña</em> diferencia en la respuesta. Eso suele estar bien para consultas simples, quizá apenas perceptibles, y podemos intentar moldear el resultado con técnicas de ingeniería de contexto. Pero a medida que las tareas que pedimos a nuestros agentes se vuelven más complejas, existe más probabilidad de que una o más de las subtareas introduzcan una variación que cambie ligeramente el resultado final. Probablemente empeorará a medida que empecemos a depender más de las comunicaciones agente a agente, y esas variaciones se acumularán. Esto vuelve a la idea de que las herramientas con las que interactúan nuestros agentes deben ser muy flexibles y ajustables para dirigir con precisión los datos contextuales, y que deben responder en un formato de salida esperado. También indica que, en muchos casos de uso, necesitamos dirigir las interacciones entre agentes y herramientas — ¡aquí es donde entran en juego los flujos de trabajo!</p><p>Elastic pronto tendrá flujos de trabajo completamente personalizables integrados en el núcleo de la plataforma. Estos flujos de trabajo podrán operar con agentes y herramientas de forma bidireccional, por lo que los flujos de trabajo podrán llamar a agentes y herramientas, y agentes y herramientas podrán llamar a flujos de trabajo. Tener estas capacidades totalmente integradas en la misma plataforma de IA de búsqueda, donde todos tus datos viven siendo transformadores, ¡el potencial de los flujos de trabajo es extremadamente emocionante! ¡Pronto, muy pronto!</p><h3>Elastic como banco de memoria unificado</h3><p>Al ser una plataforma de datos distribuida diseñada para búsquedas casi en tiempo real, Elastic realiza naturalmente las funciones de memoria a largo plazo para sistemas de IA agente. Con la experiencia de chat integrada en Agent Builder, también tenemos seguimiento y gestión de la memoria a corto plazo y el historial de chat. Y dado que toda la plataforma es API-first, es extremadamente fácil emplear Elastic como plataforma para mantener la salida contextual de una herramienta (y poder consultar ella después) que podría saturar la ventana de contexto del agente; Esta técnica a veces se denomina "<a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents#:~:text=Agents%20can%20assemble%20understanding%20layer%20by%20layer%2C%20maintaining%20only%20what%27s%20necessary%20in%20working%20memory%20and%20leveraging%20note%2Dtaking%20strategies%20for%20additional%20persistence">toma de notas</a>" en círculos de ingeniería contextual.</p><p>Tener memoria a corto y largo plazo en la misma plataforma de búsqueda aporta muchos beneficios intrínsecos: imagina poder usar historiales de chat y respuestas contextuales persistentes como parte de los influencers semánticos para futuras interacciones en chat, o para realizar análisis de amenazas, o para crear productos de datos persistentes que se generan automáticamente a partir de llamadas a herramientas repetidas con frecuencia... ¡Las posibilidades son infinitas!</p><h2>Conclusión</h2><p>La aparición de grandes modelos de lenguaje cambió la forma en que podemos comparar contenido y los métodos que empleamos para analizar nuestros datos. Nos estamos alejando rápidamente de nuestro mundo actual, donde los humanos realizan la investigación, la consideración contextual y el razonamiento lógico para responder a sus propias preguntas, a uno donde esos pasos están en gran medida automatizados mediante IA agente. Para confiar en las respuestas generadas que recibimos, necesitamos la seguridad de que el agente consideró <em>toda</em> la información <em>más relevante</em> (incluido el factor de relevancia subjetiva) al generar su respuesta. Nuestro método principal para hacer que la IA agente sea fiable es fundamentar las herramientas que recuperan contexto adicional mediante técnicas de RAG e ingeniería contextual, pero cómo esas herramientas realizan la <em>recuperación inicial</em> puede ser crucial para la precisión de la respuesta.</p><p>La plataforma Elastic Search AI ofrece la flexibilidad y beneficio de la búsqueda híbrida, junto con varias funciones integradas que ayudan a la IA agente en términos de precisión, rendimiento y escalabilidad; en otras palabras, Elastic es una plataforma fantástica para varios aspectos de la ingeniería de contexto. Al estandarizar la recuperación de contexto a través de una plataforma de búsqueda, simplificamos las operaciones de las herramientas agenticas en varios frentes — y, similar al oxímoron de "ralentizar para ir más rápido", la simplicidad en la capa de generación de contexto significa una IA agente más rápida y fiable.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-agentic-ai-accuracy</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-agentic-ai-accuracy</guid>
    <category><![CDATA[Búsqueda híbrida]]></category>
    <category><![CDATA[AI agéntica]]></category>
    <dc:creator><![CDATA[Woody Walton]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt42a203a316f0e22e/6a170932b339d58ebc769f5f/b82ff25242e4229cc20b218d9cc91c60cfd680bc-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 20 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Para contexto, Parte I: La evolución de la búsqueda híbrida y la ingeniería del contexto]]></title>
    <description><![CDATA[Explora cómo la búsqueda híbrida y la ingeniería contextual evolucionaron desde fundamentos léxicos hasta permitir la próxima generación de flujos de trabajo de IA agente.]]></description>
    <content:encoded><![CDATA[<h2>Nuestro nuevo mundo de IA agente</h2><p>Como muchos de nosotros, me siento a la vez eufórico y asombrado por el ritmo al que evolucionan las capacidades de la IA. Primero vimos cómo los grandes modelos de lenguaje (LLMs) y la búsqueda vectorial nos lanzaron a la revolución semántica, donde ya no buscábamos ni explorábamos palabras clave para encontrar cosas. Después, los LLMs nos mostraron nuevas formas de interactuar con nuestros datos, usando interfaces de chat para transformar solicitudes en lenguaje natural en respuestas que destilan vastas bases de conocimiento en resúmenes fáciles de consumir. Ya sabemos (¡ya!) tienen los inicios de la lógica automatizada impulsada por LLM en forma de flujos de trabajo de "IA agente" que pueden entender semánticamente una petición entrante, razonar los pasos a seguir y luego elegir entre las herramientas disponibles para ejecutar iterativamente acciones y alcanzar esos objetivos.</p><p>La promesa de la IA agente nos está obligando a evolucionar desde el uso principal de 'ingeniería de prompts' para moldear nuestras interacciones generativas con IA, hasta centrarnos en cómo podemos ayudar a las herramientas agenticas a obtener la información adicional más relevante y eficiente que el LLM debe tener en cuenta al generar sus respuestas — la 'ingeniería de contexto' es la próxima frontera. La búsqueda híbrida es, con diferencia, el medio más poderoso y flexible para sacar a la luz el contexto relevante, y la plataforma de Search AI de Elastic abre una nueva vía para aprovechar los datos en servicio de la ingeniería contextual. En este artículo, vamos a hablar de cómo los LLM cambiaron el mundo de la recuperación de información desde dos ángulos, y luego de cómo pueden trabajar juntos para obtener mejores resultados. Hay bastante terreno que cubrir...</p><h2>Parte I: Cómo cambiaron los LLM la búsqueda</h2><p>Empecemos desde el ángulo de cómo los LLM cambiaron la forma en que accedemos y recuperamos información.</p><h3>Nuestro legado léxico</h3><p>Todos vivimos en el mundo de la búsqueda léxica algo limitado (bastante bien, en la medida de lo posible) durante mucho tiempo. La búsqueda es la primera herramienta a la que recurrimos cuando investigamos o empezamos un nuevo proyecto, y hasta hace poco, nos correspondía formular nuestras consultas de una manera que un motor de búsqueda léxico comprenda. La búsqueda léxica se basa en asociar algún tipo de término de consulta con palabras clave que se encuentran en un corpus documental — independientemente de si el contenido es no estructurado o no estructurado. Para que una búsqueda léxica devuelva un documento como resultado, debe coincidir con esa palabra clave (o tener un vocabulario controlado como una lista de sinónimos o diccionario para establecer la conexión conceptual para nosotros).</p>POST my-index/_search
{
  "size": 10,
  "query": {
    "semantic": {
      "query": "machine learning applications",
      "field": "semantic-content-field"
    }
  }
}<p><em>Un ejemplo de consulta léxica </em><a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-multi-match-query"><em>multi-match</em></a></p><p>Al menos los motores de búsqueda tienen la capacidad de devolver resultados con un puntaje de relevancia. Los motores de búsqueda ofrecen una gran variedad de opciones de sintaxis de consulta para dirigir eficazmente los datos indexados y algoritmos de relevancia incorporados que puntuan los resultados en función de la intención de la sintaxis de consulta del usuario. Los motores de búsqueda se benefician de décadas de avances en algoritmos de clasificación de relevancia, y eso los convierte en una plataforma eficiente de recuperación de datos capaz de ofrecer resultados puntuados y ordenados según su relevancia para la consulta. Las bases de datos y otros sistemas que emplean SQL como su método principal para recuperar datos están en desventaja aquí: no existe un concepto de relevancia en una consulta de base de datos; Lo mejor que pueden hacer es ordenar los resultados alfabéticamente o numéricamente. La buena noticia es que obtendrás todos los resultados (recordación) con esas palabras clave, pero no necesariamente están en un orden útil respecto <em>al motivo por el</em> que las pediste (precisión). Es un punto importante, como veremos en breve...</p><h3>Entra en escena el dragón (semántico)</h3><p>El potencial de las representaciones vectoriales de la información como alternativa a la búsqueda por palabras clave se investigó durante <a href="https://www.elastic.co/search-labs/blog/introduction-to-vector-search">bastante tiempo</a>. Los vectores tienen mucho potencial porque nos sacan del modo de emparejamiento de contenido basado solo en palabras clave — al ser representaciones numéricas de términos y pesos, los vectores permiten que los conceptos sean matemáticamente cercanos según la comprensión que tiene un modelo de lenguaje sobre cómo se relacionan los términos entre sí en el ámbito de entrenamiento. El largo retraso en la búsqueda vectorial de propósito general se debió a que los modelos estaban mayormente limitados a dominios específicos, simplemente no eran lo suficientemente grandes para comprender suficientemente los muchos conceptos diferentes que un término podría representar en distintos contextos.</p><p>No fue hasta que los Grandes Modelos de Lenguaje (LLMs) aparecieron hace unos años, con su capacidad de capacitar con cantidades mucho mayores de datos (usando <a href="https://en.wikipedia.org/wiki/Transformer_(deep_learning_architecture)">transformadores</a> y <a href="https://en.wikipedia.org/wiki/Attention_(machine_learning)">atención</a>), que la búsqueda vectorial se volvió práctica: el tamaño y la profundidad de los LLMs finalmente permitieron que los vectores almacenaran suficiente matiz para captar realmente el significado semántico. Ese aumento repentino en la profundidad de comprensión permitió que los LLMs ahora sirvieran a un gran número de funciones de procesamiento del lenguaje natural (PLN) que antes estaban bloqueadas, siendo quizás la más impactante la capacidad de inferir el siguiente término más probable de una secuencia dado el contexto de lo que hay en la secuencia hasta ese momento. La inferencia es el proceso que otorga a la IA generativa su capacidad casi humana para producir texto. El texto generado por IA se basa en la comprensión que tiene el LLM sobre cómo se relacionan los términos dentro de sus datos de entrenamiento y también emplea la redacción de la petición para desambiguar entre diferentes contextos en los que los términos pueden aparecer.</p><p>Por mágica que sea la IA generativa <em>, existen</em> limitaciones en los LLM que causan errores de calidad y precisión, comúnmente llamados alucinaciones. Las alucinaciones ocurren cuando el LLM no tiene acceso a la información (o no es guiado al contexto correcto) para basar su respuesta en la verdad, por lo que, siendo útil, generará en su lugar una respuesta segura y plausible que es inventada. Parte de la causa es que, aunque los LLMs aprenden el uso del lenguaje dentro de grandes dominios de información diversa, tienen que dejar de capacitar en un momento determinado, por lo que su comprensión tiene un factor de puntualidad — es decir, que el modelo solo puede saber qué era preciso hasta el momento en que dejó de capacitar. Otro factor para las alucinaciones es que el modelo normalmente no conoce datos privados (datos no disponibles en Internet público), y eso es especialmente significativo cuando esos datos contienen términos y nomenclatura específicos.</p><h3>Bases de datos vectoriales</h3><p>Los LLM vectorizan el contenido a su espacio de modelo empleando una técnica llamada incrustación de texto, que se refiere a <a href="https://www.elastic.co/search-labs/blog/hybrid-search-multiple-embeddings">incrustar</a> o mapear el significado semántico del contenido dentro de la visión del mundo del modelo en función del entrenamiento recibido. Hay varios pasos para preparar y procesar contenido para incrustar, incluyendo <a href="https://www.elastic.co/search-labs/blog/chunking-strategies-elasticsearch">el chunking</a> y la tokenización (y <a href="https://www.kaggle.com/code/danishmahdi/subword-tokenization-bpe-wordpiece-and-unigram">tokenización de subpalabras</a>). El resultado suele ser un conjunto de vectores densos que representan la comprensión del modelo sobre el significado de ese fragmento de contenido dentro de su espacio vectorial. El chunking es un proceso inexacto que pretende encajar el contenido en las limitaciones de las restricciones de procesamiento de un modelo para generar incrustaciones, mientras intenta agrupar texto relacionado en un bloque usando construcciones semánticas como indicadores de oraciones y párrafos.</p><p>La necesidad de hacer chunks puede crear cierta flexibilidad semántica en un documento incrustado porque los chunks individuales no están completamente asociados con otros chunks del mismo documento. La opacidad inherente de las redes neuronales puede empeorar esta flexibilidad: un LLM es realmente una "caja negra" donde las conexiones entre términos y conceptos establecidas durante el entrenamiento no son deterministas y no pueden interpretar para los humanos. Esto genera problemas de explicabilidad, repetibilidad, sesgos inconscientes y, potencialmente, pérdida de confianza y precisión. Aun así, la capacidad de conectar ideas semánticamente, sin estar atado a palabras clave específicas al enviar consultas, es extremadamente poderosa:</p>POST my-index/_search 
{
  "size": 10, 
  "query": {
    "semantic": {
      "query": "machine learning applications",
      "field": "semantic-content-field"
    }
  }
} <p><em>Un ejemplo </em><a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-semantic-query"><em>de</em></a><em> consulta semántica</em></p><p>Hay un aspecto más a considerar para las bases de datos vectoriales: ¡no son motores de búsqueda, son bases de datos! Cuando se realiza una <a href="https://www.elastic.co/search-labs/blog/introduction-to-vector-search">búsqueda de similitud vectorial</a> , los términos de consulta se codifican para encontrar un conjunto de coordenadas (incrustadas) dentro del espacio vectorial del modelo. Esas coordenadas se emplean entonces como el centro para encontrar los documentos que son los "vecinos más cercanos" al centro — es decir, el rango (o la posición en los resultados) de un documento se determina por la <em>distancia</em> de similitud calculada de las coordenadas de ese documento respecto a las coordenadas de la consulta. ¿En qué dirección debe prevalecer el ranking, cuál de los posibles contextos está más cerca de la intención del usuario? La imagen con la que la comparo es una escena de la película <a href="https://www.youtube.com/watch?v=x3h7xz558EY&amp;start=3&amp;end=86">Stargate</a>, donde tenemos los seis puntos de coordenada que se cruzan para indicarnos el destino (el centro), pero no podemos llegar sin conocer el "séptimo símbolo" — las coordenadas del punto de partida que representan la intención subjetiva del usuario. Así que, en lugar de que la clasificación relativa de los vectores se base en una esfera de similitud en constante expansión e indiferenciada, al considerar la intención subjetiva de la consulta mediante la sintaxis expresiva y el puntaje de relevancia, podemos obtener algo que se asemeje a un <em>cilindro</em> de relevancia subjetiva graduada.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb49ae330d3de1fc1/6a17053ee8fbce089639fb46/1ddfaae0c1496d08d7d30419e6d2aeaeacfc0ea2-1600x544.png" alt="Un cilindro de relevancia subjetiva graduada." /><p>Las capacidades de inferencia de un LLM podrían ayudar a identificar el contexto más probable <em>que tiene</em> para la consulta, pero el problema es que <em>sin ayuda,</em> las coordenadas de la consulta entrante <em>solo</em> pueden determinar por cómo se capacitó originalmente el modelo.</p><p>En cierto modo, se podría decir que la similitud vectorial va al extremo opuesto a una coincidencia estricta de palabras clave — su fortaleza radica en su capacidad para superar los problemas de desajuste de términos, pero <a href="https://medium.com/data-science/vector-embeddings-are-lossy-heres-what-to-do-about-it-4f9a8ee58bb7">casi en exceso</a>: los LLM tienden a unificar conceptos relacionados en lugar de diferenciarlos. La similitud vectorial mejora nuestra capacidad para emparejar contenido semánticamente, pero no garantiza precisión porque puede pasar por alto palabras clave exactas y detalles específicos que el modelo no desambiguó lo suficiente. La búsqueda por similitud vectorial es poderosa en sí misma, pero necesitamos formas de correlacionar los resultados que recuperamos de una base de datos vectorial con los resultados de otros métodos de recuperación.</p><h3>Técnicas de reclasificación</h3><p>Ahora es un buen momento para mencionar una técnica general llamada reclasificación, que vuelve a puntuar o normalizar conjuntos de resultados a un orden de rangos unificado. La necesidad de reclasificar podría deber a que los resultados de múltiples fuentes o métodos de recuperación tengan mecanismos de clasificación/puntaje diferentes (¡o ninguno, SQL!), o bien se podría usar la reclasificación para alinear semánticamente los resultados de fuentes no semánticas con la consulta del usuario. La reclasificación es una operación de segunda etapa, es decir, un conjunto de resultados que fueron recogidos mediante algún método <em>inicial de recuperación</em> (es decir, SQL, búsqueda léxica, búsqueda vectorial) se reordenan con un método de puntaje diferente.</p><p>Existen varios enfoques disponibles, incluyendo <a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr">Learning-To-Rank (LTR)</a> y <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">Reciprocal Rank Fusion (RRF)</a> — LTR es útil para capturar características de los resultados de búsqueda (me gusta, valoraciones, clics, etc.) y usarlas para puntuar y potenciar o sesgar resultados. RRF es perfecto para fusionar resultados retornados de diferentes modalidades de consulta (por ejemplo, búsquedas en bases de datos léxicas y vectoriales) juntas en una única lista de resultados. Elastic también ofrece la flexibilidad de ajustar los puntajes mediante métodos <a href="https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search">de reclasificación lineal</a> .</p><p>Sin embargo, una de las técnicas de reclasificación más efectivas es la <a href="https://www.elastic.co/docs/solutions/search/ranking/semantic-reranking">reclasificación semántica</a>, que emplea la comprensión semántica de un LLM para analizar las incrustaciones vectoriales tanto de la consulta como de los resultados juntos, y luego aplicar el puntaje/repuntuación de relevancia para determinar el orden final. El reranking semántico requiere, por supuesto, una conexión a un modelo de reclasificación, y Elasticsearch proporciona una <a href="https://www.elastic.co/docs/api/doc/elasticsearch/group/endpoint-inference">API de inferencia</a> que permite crear endpoints <strong>de reclasificación</strong> que aprovechan modelos integrados (<a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-rerank">Elastic Rerank</a>), modelos <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/eland/machine-learning">importados</a> de terceros o servicios alojados externamente como <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-cohere">Cohere</a> o <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-googlevertexai">Google Vertex AI</a>. Luego puedes realizar un reordenamiento mediante la sintaxis de abstracción <a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">de la consulta del retriever</a> :</p>POST my-index/_search 
{
  "size": 10,
  "retriever": {
    "text_similarity_reranker": {
      "retriever": {
        "rrf": {
          "retrievers": [
            {
              "standard": {
                "query": {
                  "multi_match": {
                    "query": "machine learning applications",
                    "fields": ["title", "content"]
                  }
                }
              }
            },
            {
              "knn": {
                "field": "semantic-content-field",
                "k": 10,
                "num_candidates": 100,
                "query_vector_builder": {
                  "text_embedding": {
                    "model_id": "my-text-embedding-model",
                    "model_text": "machine learning applications"
                  }
                }
              }
            }
          ],
          "rank_window_size": 50,
          "rank_constant": 20
        }
      }
    },
    "field": "content",
    "inference_id": "my-reranker",
    "inference_text": "machine learning applications",
    "rank_window_size": 20
  }
}<p><em>Un ejemplo de operación de reclasificación de recuperadores en varias etapas</em></p><p>Suena genial, ¿verdad? Podemos realizar reclasificaciones con resultados de fuentes dispares y acercarnos a una comprensión semántica de todo tipo de contenido... La reclasificación semántica puede ser costosa tanto computacionalmente como en el tiempo de procesamiento requerido, y por ello, la reclasificación semántica solo puede hacer con un número limitado de resultados, lo que significa <em>que la forma</em> en que se recuperan esos resultados iniciales es importante.</p><h3>El método de recuperación del contexto es importante</h3><p>La intención subjetiva es un factor importante para determinar la precisión de un resultado y para valorar su relevancia. Sin la capacidad de considerar la intención del usuario para realizar la consulta (expresada mediante sintaxis flexible, o mediante reclasificación en la segunda etapa), solo podemos seleccionar de los contextos existentes ya codificados dentro del espacio del modelo. La forma en que normalmente abordamos esta falta de contexto es mediante técnicas como <a href="https://en.wikipedia.org/wiki/Retrieval-augmented_generation">la Generación de Aumentos por Recuperación (RAG).</a> El funcionamiento de RAG es que desplaza efectivamente las coordenadas de la consulta al incluir términos relacionados adicionales devueltos de una consulta previa para datos contextualmente relevantes. ¡Eso hace que el motor que proporciona ese contexto adicional y <em>su</em> método inicial para realizar la recuperación sean aún más importantes para la precisión del contexto!</p><p>Repasemos los diferentes métodos de recuperación de contexto y cómo pueden ayudar o perjudicar a una operación RAG:</p><ul><li><p><strong>La recuperación de búsqueda híbrida sin motor de búsqueda sigue careciendo de relevancia subjetiva.</strong> Si la plataforma que proporciona RAG es principalmente SQL (lo que incluye la mayoría de las plataformas "data lake"), carece de puntaje de relevancia en la fase inicial de recuperación. Muchas plataformas de data lake ofrecen su propia versión de recuperación híbrida (no de búsqueda), normalmente combinando técnicas de reclasificación como la reclasificación semántica y la RRF en sus resultados de recuperación basada en SQL y bases de datos vectoriales. Un ordenamiento simple es obviamente insuficiente para la clasificación subjetiva, pero incluso cuando se usa como base para una operación de reclasificación semántica de segunda etapa, SQL como recuperación de primera etapa se convierte en un problema cuando el reclasificación semántica se realiza solo en los "k primeros resultados" — sin alguna forma de puntuar resultados en la recuperación, ¿qué garantía tenemos de que los <em>mejores</em> resultados estén realmente en los primeros resultados?</p></li><li><p><strong>La similitud vectorial por sí sola no es suficiente para RAGs</strong>. Realmente se debe a un conjunto de problemas que se acumulan: es la rapidez del embedding, junto con métodos ingenuos de fragmentación, cómo se calcula la similitud y el componente crucial que falta de la intención subjetiva. Uno de los principales objetivos de RAG es fundamentar las interacciones generativas de IA en la verdad objetiva, tanto para prevenir alucinaciones como para informar al LLM sobre la información privada que no conocía durante el entrenamiento. Podemos emplear el contexto adicional proporcionado por RAG para restringir y dirigir a los LLMs a considerar las conexiones y detalles que sabemos que son más importantes para responder a la pregunta que nos planteamos. Para ello, necesitamos usar <em>tanto</em> enfoques semánticos como léxicos.</p></li><li><p><strong>RAG grep/regex basado en archivos.</strong> Hay algunos <a href="https://www.nicolasbustamante.com/p/the-rag-obituary-killed-by-agents">sectores</a> del universo de IA agente que apuntan al uso de ventanas de contexto enormemente ampliadas que acceden a archivos locales mediante grep y regex para RAG en lugar de plataformas externas de recuperación. La idea es que, con una ventana de contexto mucho más amplia disponible, los LLMs podrán establecer conexiones conceptuales dentro de su propio espacio de pensamiento en lugar de depender de fragmentos y múltiples métodos/plataformas de recuperación para recopilar información relevante. Aunque en teoría es cierto que tener un documento completo ofrece una imagen más completa que los segmentos del documento, esto solo puede funcionar en pequeños dominios de datos (o, por ejemplo, al suministrar archivos para <a href="https://en.wikipedia.org/wiki/Vibe_coding">vibecoding</a>), y aun así, el método inicial de recuperación es un escaneo de todos los documentos con una coincidencia solo por palabra clave.</p></li></ul><p><strong>La búsqueda es más que una recuperación</strong></p><p>Los motores de búsqueda están diseñados específicamente para hacer que las consultas sean lo más rápidas y flexibles posible. Internamente, emplean estructuras de datos especializadas para almacenar y recuperar diferentes tipos de datos de manera que se adapten a esos tipos de datos. Elasticsearch proporciona almacenamiento y consulta optimizados de prácticamente todo tipo de datos, incluyendo búsqueda léxica no estructurada/texto completo (coincidencia, frase, proximidad, multi-coincidencia), coincidencia y filtrado rápido de palabras clave (coincidencia exacta), rangos numéricos, fechas, direcciones IP, y es muy flexible en cómo almacena las estructuras de documentos (por ejemplo, Docs anidados o aplanados). Elasticsearch es también una base de datos vectorial nativa que puede almacenar y consultar tanto tipos vectoriales dispersos como densos, y seguimos explorando formas innovadoras (por ejemplo, <a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Better Binary Quantization (BBQ)</a> y <a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction">DiskBBQ</a>) para mantener la fidelidad de búsqueda mientras mejoramos la velocidad, escalabilidad y costos asociados al contenido vectorizado. La plataforma Elasticsearch también proporciona resiliencia y alta disponibilidad de datos integradas, e incluye capacidades de gestión del ciclo de vida de los datos como <a href="https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/searchable-snapshots">Searchable Snapshots</a> que permiten mantener datos de poca frecuencia o de retención a largo plazo en un almacenamiento de objetos rentable, pero aún totalmente buscables.</p><h3>La búsqueda híbrida es lo mejor de todos los mundos</h3><p><a href="https://www.elastic.co/what-is/hybrid-search">Búsqueda híbrida</a> (¡no solo recuperación híbrida!) combina las fortalezas de la búsqueda léxica tradicional con la comprensión semántica de los LLMs y la búsqueda por similitud vectorial. Esta sinergia permite dirigir resultados altamente relevantes en la fase <em>de recuperación</em> mediante cualquiera de las opciones flexibles de sintaxis de consulta que ofrece un motor de búsqueda: opciones de sintaxis impulsadas por intención y puntaje de relevancia, recuperación de datos multimodales, filtrado, agregaciones y sesgos. Con sintaxis de búsqueda como <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> y <a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">recuperadores</a> de varias etapas, podemos combinar de forma flexible la búsqueda tradicional con búsqueda semántica, filtros y múltiples técnicas de reclasificación, todo en una sola petición.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6ee9512910856ee3/6a17053fcdacbf2ea97d291e/f25180cb430414b99ae553d3b8eb161dbccea4d4-1920x1080.png" alt="Cómo funciona la búsqueda híbrida" /><p>Uno de los mayores beneficios de la búsqueda híbrida es que tus consultas pueden usar sintaxis especializada para múltiples tipos de datos diferentes simultáneamente. Esas diferentes sintaxis de consulta pueden usar no solo para <em>encontrar</em> resultados, sino también como filtros o agregaciones <em>en</em> los resultados. Por ejemplo, uno de los tipos de consulta más comunes que frecuentemente se combina con otra sintaxis es el <a href="https://www.elastic.co/docs/explore-analyze/geospatial-analysis">análisis geoespacial</a>. Puedes hacer cosas como consultar resultados que tengan coordenadas geográficas dentro de una distancia especificada de un punto, o aplicar agregaciones de tus resultados por región, o agregaciones para rastrear y alertar sobre movimientos dentro o fuera de una zona. Con la búsqueda híbrida tienes la flexibilidad de combinar sintaxis para dirigir los resultados de la manera más precisa, para recuperar el contenido más cercano a tu contexto.</p><h2>Entreacto</h2><p>Esta primera parte cuenta la historia de cómo la búsqueda vectorial cambió la forma en que podemos recuperar datos y sienta el terreno para los cambios que los LLMs trajeron a los mecanismos de consulta que empleamos para interactuar con los datos. Vamos a fingir que tuvimos que descomponer esto en varias partes para que los LLM pudieran entenderlo sin perder el contexto... ;-) Aprendamos más <em>sobre por qué esto es importante</em> en <a href="https://www.elastic.co/search-labs/blog/context-engineering-llm-evolution-agentic-ai">la Parte II: IA Agente y la necesidad de ingeniería de contexto</a>, y en la Parte III volveremos a nuestra discusión sobre la búsqueda híbrida.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai</guid>
    <category><![CDATA[Búsqueda híbrida]]></category>
    <category><![CDATA[Relevancia]]></category>
    <dc:creator><![CDATA[Woody Walton]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc49e39872984c8eb/6a1705410e2e49e42c419fd5/7e59a0671aa9ea32d68188a693936a66ebf48625-1000x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 12 Nov 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[Experimentos para mejorar las herramientas de IA Agentic para Elasticsearch]]></title>
    <description><![CDATA[Descubre cómo mejoramos los flujos de trabajo de agentes de IA para Elasticsearch mediante experimentos iterativos combinando retrievers lineales, búsqueda híbrida y semantic_text para una optimización escalable de RAG.]]></description>
    <content:encoded><![CDATA[<p>Como todos hoy en día, aquí en Elastic apostamos por completo a Chat, Agents y RAG. En el departamento de búsqueda, estuvimos trabajando recientemente en un Constructor de Agentes y un Registro de Herramientas, todo con la intención de hacer que sea trivial "chatear" con tus datos en Elasticsearch.</p><p>Lee el <a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">blog Construiendo flujos de trabajo agentes con IA con Elasticsearch</a> para más información sobre la "visión global" de ese esfuerzo, o <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">Tu primer agente elástico: de una sola consulta a un chat impulsado por IA</a> para una introducción más práctica.</p><p>Sin embargo, en este blog vamos a hacer un poco de zoom para ver una de las primeras cosas que ocurren cuando empiezas a charlar y para guiarte por algunas de las mejoras recientes que hicimos.</p><h2>¿Qué está pasando aquí?</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1331b1043612efe3/6a17f115505ac3dc41ad8c3c/25a24055a166d7d6ba81d80aa35cb97163662e23-1600x443.png" alt="" /><p>Cuando chateas con tus datos de Elasticsearch, nuestro agente de IA predeterminado te guía a través de este flujo estándar:</p><ol><li><p>Revisa el enunciado.</p></li><li><p>Identifica qué índice es probable que contenga las respuestas a esa pregunta.</p></li><li><p>Genera una consulta para ese índice, basada en el prompt.</p></li><li><p>Busca en ese índice con esa consulta.</p></li><li><p>Sintetiza los resultados.</p></li><li><p>¿Pueden los resultados responder al prompt? Si es así, responde. Si no, repite, pero prueba algo diferente.</p></li></ol><p>Esto no debería parecer demasiado novedoso: es simplemente Generación Aumentada por Recuperación (RAG). Y como era de esperar, la calidad de tus respuestas depende mucho de la relevancia de tus resultados iniciales. Así que, mientras trabajamos en mejorar la calidad de nuestra respuesta, estuvimos prestando mucha atención a las consultas que generábamos en el paso 3 y ejecutábamos en el paso 4. Y notamos un patrón interesante.</p><p>A menudo, cuando nuestras primeras respuestas eran "malas", no era porque hicimos una consulta mala. Fue porque <em>elegimos el índice equivocado</em> para hacer la consulta. Los pasos 3 y 4 normalmente no eran nuestro problema, sino el paso 2.</p><h2>¿Qué estábamos haciendo?</h2><p>Nuestra implementación inicial fue sencilla. Creamos una herramienta (llamada index_explorer) que efectivamente hacía un <code>_cat/indices</code> para listar todos los índices disponibles y luego pedir al LLM que identificara cuál de estos índices era el mejor para el mensaje/pregunta/prompt del usuario. Puedes ver esta <a href="https://github.com/elastic/kibana/blob/0cc78184957fcd12110dabae50353392ea937508/x-pack/platform/packages/shared/onechat/onechat-genai-utils/tools/index_explorer.ts#L98-L113">implementación original aquí</a>.</p>You are an AI assistant for the Elasticsearch company.
based on a natural language query from the user, your task is to select up to ${limit} most relevant indices from a list of indices.

*The natural language query is:* ${nlQuery}

*List of indices:*
${indices.map((index) =&gt; `- ${index.index}`).join('\n')}

Based on those information, please return most relevant indices with your reasoning.
Remember, you should select at maximum ${limit} indices.<p>¿Qué tal funcionaba? ¡No estábamos seguros! Teníamos ejemplos claros de que <em>no</em> funcionaba bien, pero nuestro verdadero primer reto fue cuantificar nuestro estado actual.</p><h2>Establecimiento de una línea base</h2><h3>Todo empieza con los datos</h3><p>Lo que necesitábamos era un conjunto de datos dorado para medir la eficacia de una herramienta a la hora de seleccionar el índice adecuado dado un prompt del usuario y un conjunto preexistente de índices. Y no disponíamos de un conjunto de datos así. Así que generamos uno.</p><p>Agradecimiento: Esto no es "buena práctica", lo sabemos. Pero a veces, es mejor seguir adelante que abandonar la bicicleta. <a href="https://www.elastic.co/about/our-source-code#progress-perfection">Progreso, perfección SIMPLE</a>.</p><p>Generamos índices semilla para varios dominios diferentes usando <a href="https://gist.github.com/seanstory/a08db2e149897da656db3a1ca72e17ac">este prompt</a>. Luego, para cada dominio generado, generamos algunos índices más usando<a href="https://gist.github.com/seanstory/a280a85d067e61bfeb5911bf2654e6e2"> este prompt</a> (el objetivo aquí es sembrar confusión para el LLM con negativos duros y ejemplos difíciles de clasificar). Después, editamos manualmente cada índice generado y sus descripciones. Finalmente, generamos consultas de prueba usando <a href="https://gist.github.com/seanstory/44291b666c05a383136f6e36bb9106fa">este prompt</a>. Esto nos dejó con datos de muestra como:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1bd9cd78154195e3/6a17f117dbb4fff7b5fb57d2/9d96d87e286eddbc012402b1ecccd57419a99253-1600x782.png" alt="" /><p>y casos de prueba como:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltadf30a0aeafd56ef/6a17f1192f4a5c160ffa89eb/4c2e9ad941d98d7e66033bbc08c9b8060ec19097-1600x797.png" alt="" /><h3>Elaboración de un arnés de prueba</h3><p>El proceso a partir de aquí fue muy sencillo. Crea un script para una herramienta que pueda:</p><ol><li><p>Establece una hoja limpia con un clúster objetivo de Elasticsearch.</p></li><li><p>Crea todos los índices definidos en el conjunto de datos objetivo.</p></li><li><p>Para cada escenario de prueba, ejecuta la herramienta i<code>ndex_explorer</code> (prácticamente tenemos una <a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-post-agent-builder-tools-execute">API de Herramienta de Ejecución</a>).</p></li><li><p>Comparar el índice de resultados con el índice esperado y capturar el resultado.</p></li><li><p>Luego de terminar todos los escenarios de prueba, tabula los resultados.</p></li></ol><h3>La encuesta dice...</h3><p>Los resultados iniciales fueron, como era de esperar, mediocres.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt73367741359e258d/6a17f11a505ac39749ad8c40/9c10679bcd6291edfa2a9ba42e7dd922aa483f0b-1216x806.png" alt="" /><p>En general, un 77,14% de precisión para identificar el índice adecuado. Y esto fue en un escenario "mejor escenario", donde todos los índices tienen buenos nombres semánticamente significativos. Cualquiera que hizo alguna vez un 'PUT test2/_doc/foo {...}' sabe que tus índices no siempre tienen nombres significativos.</p><p>Así que tenemos una línea de base, y muestra mucho margen de mejora. ¡Ahora era hora de hacer algo de ciencia! 🧪</p><h2>Experimentación</h2><h3>Hipótesis 1: Los mapeos ayudarán</h3><p>El objetivo aquí es identificar un índice que contenga datos relevantes para la consigna original. Y la parte de un índice que mejor describe los datos que contiene son los <em>mapeos</em> del índice. Incluso sin obtener muestras del contenido del índice, saber que el índice tiene un campo de precios de tipo doble implica que los datos representan algo que se puede vender. Un campo autor de texto tipográfico implica algunos datos de lenguaje no estructurados. Ambos juntos podrían implicar que los datos son libros/relatos/poemas. Hay muchas pistas semánticas que podemos derivar simplemente conociendo las propiedades de un índice. Así que en una sucursal local, ajusté nuestro '.index_explorer' herramienta para enviar los mapeos completos de un índice (junto con su nombre) al LLM para tomar su decisión. </p><p>El resultado (de los registros de Kibana):</p>[2025-09-05T11:01:21.552-05:00][ERROR][plugins.onechat] Error: Error calling connector: event: error
data: {"error":{"code":"request_entity_too_large","message":"Received a content too large status code for request from inference entity id [.rainbow-sprinkles-elastic] status [413]","type":"error"}}


    at createInferenceProviderError (errors.ts:90:10)
    at convertUpstreamError (convert_upstream_error.ts:39:38)
    at handle_connector_response.ts:26:33
    at Observable.init [as _subscribe] (/Users/seanstory/Desktop/Dev/kibana/node_modules/rxjs/src/internal/observable/throwError.ts:123:68)...<p>Los autores iniciales de la herramienta ya lo habían anticipado. Aunque el mapeo de un índice es una mina de oro de información, también es un bloque bastante extenso de JSON. Y en un escenario realista donde comparas numerosos índices (nuestro conjunto de datos de evaluación define 20), estos blobs JSON suman. Así que queremos dar al LLM más contexto para su decisión que solo los nombres de índices de todas las opciones, pero no tanto como los mapeos completos de cada una.</p><h3>Hipótesis 2: Mapeos "aplanados" (listas de campos) como compromiso</h3><p>Partimos de la suposición de que los creadores de índices usarán nombres de índices semánticamente significativos. ¿Y si extendemos esa suposición también a los nombres de campos? Nuestro experimento anterior falló porque el mapeo JSON incluye MUCHOS metadatos y datos basurales y un estándar estándar.</p>     "description_text": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          },
          "copy_to": [
            "description_semantic"
          ]
        },<p>El bloque anterior, por ejemplo, tiene 236 caracteres y define solo un campo en un mapeo de Elasticsearch. Mientras que la cadena "description_text" tiene solo 16 caracteres. Eso supone casi un aumento de 15 veces en el recuento de caracteres, sin una mejora semántica significativa en la descripción de lo que ese campo implica sobre los datos disponibles. ¿Y si recogiéramos los mapeos de todos los índices, pero antes de enviarlos al LLM, los "aplanáramos" solo en una lista con sus nombres de campo?</p><p>Lo probamos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta5eda7a79493ee81/6a17f11c9da390327fe46590/112c2f447c11f154b5082725cd49b51d0a3c8a65-1214x804.png" alt="" /><p>¡Esto es genial! Mejoras en todos los ámbitos. ¿Pero podríamos hacerlo mejor?</p><h3>Hipótesis 3: Descripciones en el _meta de cartografía</h3><p>Si solo los nombres de campos sin contexto adicional causaran un salto tan grande, ¡supongo que agregar un contexto sustancial sería aún mejor! No es necesariamente convencional que cada índice tenga una descripción adjunta, pero sí es posible agregar metadatos a nivel de índice de cualquier tipo al objeto _meta del mapeo. Volvimos a nuestros índices generados y agregamos descripciones para cada índice de nuestro conjunto de datos. Mientras las descripciones no sean demasiado largas, deberían usar menos tokens que el mapeo completo y proporcionar una visión significativamente mejor sobre qué datos se incluyen en el índice. Nuestro experimento validó esta hipótesis.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt61b85cf40e0e6357/6a17f11dfbc5f82809491bbe/32d2692ad4479d0e52d8ee723dcc5710a6ec90f3-1208x806.png" alt="" /><p>Una mejora modesta, y ahora somos &gt;90% precisos en todos los aspectos.</p><h3>Hipótesis 4: La suma es mayor que sus partes</h3><p>Los nombres de campos aumentaron nuestros resultados. Las descripciones aumentaron nuestros resultados. Así que, empleando <em>tanto </em>descripciones COMO nombres de campos debería dar resultados aún mejores, ¿no?</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte6297c6aaf7db802/6a17f11e14d90c1bd779b6e6/114cbb408ff16b136251d2265416bd5270380fe5-1208x794.png" alt="" /><p>Los datos decían "no" (sin cambios respecto al experimento anterior). La teoría principal aquí era que, dado que las descripciones se generaron a partir de los campos índice/mapeos desde el principio, no hay suficiente información diferente entre estos dos contextos para ayudar a agregar algo "nuevo" al combinarlos. Además, la carga útil que enviamos para nuestros 20 índices de prueba está creciendo bastante. El hilo de pensamiento que seguimos hasta ahora no es escalable. De hecho, hay buenas razones para creer que ninguno de nuestros experimentos hasta ahora funcionaría en clústeres de Elasticsearch donde hay cientos o miles de índices para elegir. Cualquier enfoque que aumente linealmente el tamaño del mensaje enviado al LLM a medida que aumenta el número total de índices probablemente no será una estrategia generalizable.</p><p>Lo que realmente necesitamos es un enfoque que nos ayude a reducir un gran número de candidatos a las opciones más relevantes...</p><p>Lo que tenemos aquí es un problema de búsqueda.</p><h3>Hipótesis 5: Selección mediante búsqueda semántica</h3><p>Si el nombre de un índice tiene significado semántico, entonces puede almacenar como un vector y buscar semánticamente.</p><p>Si los nombres de campos de un índice tienen significado semántico, entonces pueden almacenar como vectores y buscar semánticamente.</p><p>Si un índice tiene una descripción con significado semántico, también puede almacenar como vector y buscar semánticamente.</p><p>Hoy en día, los índices de Elasticsearch no hacen que ninguna de esta información sea buscable (¡quizá deberíamos!), pero fue bastante trivial<a href="https://github.com/elastic/connectors/pull/3638"> improvisar algo</a> que pudiera superar esa brecha. Usando el framework de conectores de Elastic, construí un conector que generaba un documento para cada índice de un clúster. Los documentos de salida serían algo así:</p> doc = {
                "_id": index_name,
                "index_name": index_name,
			"meta_description”: description,
"field_descriptions" = field_descriptions,
                "mapping": json.dumps(mapping),  
                "source_cluster": self.es_client.configured_host,
            }<p>Envié estos documentos a un nuevo índice donde definí manualmente el mapeo como:</p>{
   "mappings": {
       "properties": {
           "semantic_content": {
               "type": "semantic_text"
           },
           "index_name": {
               "type": "text",
               "copy_to": "semantic_content"
           },
           "mapping": {
               "type": "keyword",
               "copy_to": "semantic_content"
           },
           "source_cluster": {
               "type": "keyword"
           },
           "meta_description": {
               "type": "text",
               "copy_to": "semantic_content"
           },
           "field_descriptions": {
               "type": "text",
               "copy_to": "semantic_content"
           }
       }
   }
}<p>Esto crea un solo campo semantic_content, donde todos los demás campos con significado semántico se fragmentan e indexan. Buscar en este índice se vuelve trivial, simplemente:</p>GET indexed-indices/_search
{
 "query": {
   "semantic": {
     "field": "semantic_content",
     "query": "$query"
   }
 }
}<p>La herramienta de <code>index_explorer</code> modificada es <em>ahora mucho</em> más rápida, ya que no necesita hacer una solicitud a un LLM, sino que puede aplicar una única incrustación para la consulta dada y realizar una operación eficiente de búsqueda vectorial. Tomando el resultado más alto como índice seleccionado, obtuvimos resultados de:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc27c302e6bef0b23/6a17f120577262d2f21bccdc/06ef5d78040d064d3444793f636d527d9e19a869-1214x800.png" alt="" /><p>Este enfoque es escalable. Este enfoque es eficiente. Pero este enfoque es apenas mejor que nuestra línea base. Sin embargo, esto no es sorprendente; El enfoque de búsqueda aquí es increíblemente ingenuo. No hay matices. No hay reconocimiento de que el nombre y la descripción de un índice deban tener más peso que un nombre arbitrario de campo que contiene el índice. No hay posibilidad de ponderar coincidencias léxicas exactas sobre coincidencias sinónimas. Sin embargo, construir una consulta muy matizada requeriría asumir MUCHO sobre los datos disponibles. Hasta ahora, ya hicimos algunas grandes suposiciones sobre que los nombres de índices y campos tienen significado semántico, pero tendríamos que ir un paso más allá y empezar a suponer <em>cuánto</em> significado tienen y cómo se relacionan entre sí. Sin hacerlo, probablemente no podamos identificar de forma fiable la mejor coincidencia como nuestro resultado principal, pero es más probable que digamos que la mejor coincidencia está en algún lugar de los primeros N resultados. Necesitamos algo que pueda consumir información semántica en el contexto en el que existe, comparando con otra entidad que pueda representar a sí misma de manera semánticamente distinta, y juzgar entre ellas. Como un LLM.</p><h3>Hipótesis 6: Reducción de conjuntos candidatos</h3><p>Hubo bastantes experimentos más que voy a pasar por alto, pero el avance clave fue dejar de lado el deseo de elegir la mejor coincidencia únicamente a partir de una búsqueda semántica, y en su lugar emplear la búsqueda semántica como filtro para eliminar índices irrelevantes de la consideración del LLM. Combinamos Retrievers Lineales, Búsqueda Híbrida con RRF y <code>semantic_text</code> para <a href="https://gist.github.com/seanstory/d704443120e20f6c844db10e30066860">nuestra búsqueda</a>, limitando los resultados a los 5 principales índices de coincidencia.</p><p>Luego, para cada coincidencia, agregamos el nombre, la descripción y los nombres de campos del índice a un mensaje para el LLM. Los resultados fueron fantásticos:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ac4cb8f7153fdf9/6a17f121af47b66d1dcde082/8fcabd78f591f90d6bc7c0e087d31317e4eef791-1206x804.png" alt="" /><p>¡La mayor precisión de cualquier experimento hasta ahora! Y como este enfoque no aumenta el tamaño del mensaje proporcional al número total de índices, es mucho más escalable.</p><h2>Resultados</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8d66130d9fae6bea/6a17f123ddf97d7527910c19/04d630797213dbb8bf567da41d1cdd5c7b4586c9-1600x521.png" alt="" /><p>El primer resultado claro fue que nuestra línea <em>base puede</em> mejorar. Esto parece obvio en retrospectiva, pero antes de que comenzara la experimentación, hubo un debate serio sobre si deberíamos abandonar por completo nuestra herramienta de <code>index_explorer</code> y confiar en la configuración explícita del usuario para limitar el espacio de búsqueda. Aunque sigue siendo una opción viable y válida, esta investigación muestra que existen caminos prometedores para automatizar la selección de índices cuando dichas entradas de usuario no están disponibles.</p><p>El siguiente resultado claro fue que simplemente agregar más personajes descriptivos al problema tiene rendimientos decrecientes. Antes de esta investigación, debatimos si deberíamos invertir en ampliar la capacidad de Elasticsearch para almacenar <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-field-meta">metadatos a nivel de campo</a>. Hoy en día, estos valores de <code>meta</code> están limitados a 50 caracteres, y se asumía que tendríamos que aumentar este valor para poder obtener una comprensión semántica de nuestros campos. Claramente no es así, y el LLM parece funcionar bastante bien solo con los nombres de campos. Puede que investiguemos esto más adelante, pero ya no nos parece urgente.</p><p>Por el contrario, esto dio evidencia clara de la importancia de tener metadatos de índice "buscables". Para estos experimentos, hackeamos un índice de índices. Pero esto es algo que podríamos explorar integrando directamente en Elasticsearch, creando APIs para gestionar, o al menos estableciendo una convención en torno a ella. Estaremos valorando nuestras opciones y hablando internamente, así que estad atentos.</p><p>Por último, este esfuerzo confirmó el valor de que nos tomemos nuestro tiempo para experimentar y tomar decisiones basadas en datos. De hecho, nos ayudó a reafirmar que nuestro producto Agent Builder va a necesitar capacidades robustas de evaluación dentro del producto. Si necesitamos construir un arnés de pruebas completo solo para una herramienta que selecciona índices, nuestros clientes necesitarán absolutamente formas de evaluar cualitativamente sus herramientas personalizadas mientras hacen ajustes iterativos.</p><p>Estoy deseando ver qué construiremos, ¡y espero que tú también!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance</guid>
    <category><![CDATA[AI agéntica]]></category>
    <category><![CDATA[Dentro de Elastic]]></category>
    <category><![CDATA[Búsqueda híbrida]]></category>
    <dc:creator><![CDATA[Sean Story]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68d11a4c7fd11d4c/6a17f1257b54f9b6598b39d4/42903c869e034674b30bb36013345aaa97f6608b-1184x864.png" length="0" type="image/png"/>
    <pubDate>Mon, 06 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Búsqueda híbrida revisitada: ¡presentando el retriever lineal en Elasticsearch!]]></title>
    <description><![CDATA[Descubre cómo el retriever lineal mejora la búsqueda híbrida aprovechando puntajes ponderados y la normalización MinMax para clasificaciones más precisas y consistentes, y aprende a usarlo.]]></description>
    <content:encoded><![CDATA[<p>En nuestra publicación <a href="https://www.elastic.co/es/search-labs/blog/elasticsearch-retrievers-ga-8.16.0">de blog anterior</a> , presentamos el marco de recuperadores rediseñado desde cero, que permite la creación de canalizaciones de clasificación complejas. También exploramos cómo el recuperador Reciprocal Rank Fusion (RRF) permite la búsqueda híbrida al fusionar resultados de diferentes consultas. Si bien RRF es fácil de implementar, tiene una limitación notable: se enfoca puramente en rangos relativos, ignorando los puntajes reales. Esto hace que el ajuste y la optimización sean un desafío.</p><h2>¡Conoce al retriever lineal!</h2><p>En esta publicación, presentamos el <a href="https://www.elastic.co/es/docs/solutions/search/retrievers-overview#retrievers-overview-types"> linear</a> retriever, ¡nuestra última incorporación para apoyar la búsqueda híbrida! A diferencia de <code>rrf</code>, el recuperador de <code>linear</code> calcula una suma ponderada en todas las consultas que coinciden con un documento. Este enfoque conserva la importancia relativa de cada documento dentro de un conjunto de resultados al tiempo que permite un control preciso sobre la influencia de cada consulta en el puntaje final. Como resultado, proporciona una forma más intuitiva y flexible de ajustar la búsqueda híbrida.</p><p>Definición de un recuperador lineal donde el puntaje final se calculará como:</p><p>Es tan simple como:</p>GET linear_retriever_blog/_search
{
   "retriever": {
       "linear": {
           "retrievers": [
               {
                   "retriever": {
                       "knn": {
                          ...
                        }
                    },
                   "weight": 5
               },
                  {
                   "retriever": {
                       "standard": {
                          ...
                        }
                    },
                   "weight": 1.5
               },


           ]
        }
     }
}<p>¿Notas lo simple e intuitivo que es? (¡y muy similar a <code>rrf</code>!) Esta configuración le permite controlar con precisión cuánto contribuye cada tipo de consulta a la clasificación final, a diferencia de <code>rrf</code>, que se basa únicamente en clasificaciones relativas.</p><p>Queda una advertencia: <code>knn</code> puntajes pueden estar estrictamente limitados, dependiendo de la métrica de similitud empleada. Por ejemplo, con la similitud del coseno o el producto punto de los vectores estandarizados por unidades, los puntajes siempre estarán dentro del rango <code>[0, 1]</code> . Por el contrario, <code>bm25</code> puntajes son menos previsibles y no tienen límites claramente definidos.</p><h2>Escalando los puntajes: kNN vs BM25</h2><p>Un desafío de la búsqueda híbrida es que diferentes recuperadores producen puntajes en diferentes escalas. Considere, por ejemplo, el siguiente escenario:</p><p>Puntajes de la consulta A:</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>100</p><p>1.5</p><p>1</p><p>0.5</p><p>Puntajes de la consulta B:</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>0.63</p><p>0.01</p><p>0.3</p><p>0.4</p><p>Puede ver la disparidad arriba: <code>kNN</code> puntajes oscilan entre 0 y 1, mientras que <code>bm25</code> puntajes pueden variar enormemente. Esta diferencia hace que sea difícil establecer pesos óptimos estáticos para combinar los resultados.</p><h2>Normalización al rescate: el normalizador MinMax</h2><p>Para solucionar esto, introdujimos un normalizador de <code>minmax</code> opcional que escala los puntajes, de forma independiente para cada consulta, al rango de <code>[0, 1]</code> mediante la siguiente fórmula:</p><p>Esto conserva la importancia relativa de cada documento dentro del conjunto de resultados de una consulta, lo que facilita la combinación de puntajes de diferentes recuperadores. Con la normalización, los puntajes se convierten en:</p><p>Puntajes de la consulta A:</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>1.00</p><p>0.01</p><p>0.005</p><p>0.000</p><p>Puntajes de la consulta B:</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>1.00</p><p>0.000</p><p>0.465</p><p>0.645</p><p>Todos los puntajes ahora se encuentran en el rango de <code>[0, 1]</code> y optimizar la suma ponderada es mucho más sencillo, ya que ahora capturamos la importancia (relativa a la consulta) de un resultado en lugar de su puntaje absoluto y mantenemos la coherencia entre las consultas.</p><h2>Ejemplo de recuperador lineal </h2><p>Veamos un ejemplo ahora para mostrar cómo se ve lo anterior y cómo el <code>linear</code> retriever aborda algunas de las deficiencias de <code>rrf</code>. RRF se basa únicamente en rangos relativos y no considera las diferencias de puntaje reales. Por ejemplo, dadas estos puntajes:</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>100</p><p>1.5</p><p>1</p><p>0.5</p><p>puntaje de la fuerza de avance</p><p>0.03226</p><p>0.03252</p><p>0.03200</p><p>0.03125</p><p>RRF clasificaría los documentos como:</p><p>Sin embargo, doc1 tiene un puntaje de <code>bm25</code> significativamente más alta que los demás, que <code>rrf</code> no logra capturar porque solo analiza los rangos relativos. El recuperador de <code>linear</code> , combinado con la normalización, explica correctamente tanto los puntajes como sus diferencias, produciendo una clasificación más significativa:</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>1</p><p>0.01</p><p>0.005</p><p>0</p><p>Como podemos ver en lo anterior, la gran clasificación y <code>score</code> de doc1 para <code>bm25</code> se contabiliza adecuadamente y se refleja en los puntajes finales. Además de eso, todos los puntajes se encuentran ahora en el rango <code>[0, 1]</code> para que podamos compararlas y combinarlas de una manera mucho más intuitiva (e incluso construir procesos de optimización offline).</p><h2>Poniéndolo todo junto</h2><p>Para aprovechar al máximo el recuperador de <code>linear</code> con normalización, la solicitud de búsqueda tendría el siguiente aspecto:</p>GET linear_retriever_blog/_search
{
   "retriever": {
       "linear": {
           "retrievers": [
               {
                   "retriever": {
                       "knn": {
                          ...
                        }
                    },
                   "weight": 5
               },
                  {
                   "retriever": {
                       "standard": {
                          ...
                        }
                    },
                   "weight": 1.5,
                   "normalizer": "minmax"
               },


           ]
       }
   }
}<p>Este enfoque combina lo mejor de ambos mundos: conserva la flexibilidad y el puntaje intuitivo del recuperador de <code>linear</code> , al tiempo que garantiza una escala de puntaje consistente con la normalización MinMax.</p><p>Al igual que con todos nuestros retrievers, el <code>linear</code> retriever se puede integrar en cualquier nivel de un árbol jerárquico de retriever, con soporte para explicabilidad, resaltado de coincidencias, colapso de campo y más.</p><h2>Cuándo elegir el retriever lineal y por qué marca la diferencia</h2><p>El <code>linear</code> retriever:</p><ul><li><p>Preserva la importancia relativa al aprovechar los puntajes reales, no solo los rangos.</p></li><li><p>Permite el ajuste fino con contribuciones ponderadas de diferentes consultas.</p></li><li><p>Mejora la coherencia mediante la normalización, lo que hace que la búsqueda híbrida sea más estable y previsible.</p></li></ul><h2>Conclusión</h2><p>El recuperador de <code>linear</code> ya está disponible en Elasticsearch Serverless y en las versiones 8.18 y 9.0. También se pueden encontrar más ejemplos y parámetros de configuración en nuestra documentación. Pruébelo y vea cómo puede mejorar su experiencia de búsqueda híbrida: esperamos sus comentarios. ¡Feliz búsqueda!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search</guid>
    <category><![CDATA[Relevancia]]></category>
    <category><![CDATA[Búsqueda híbrida]]></category>
    <dc:creator><![CDATA[Panagiotis Bailis]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte58a751cbcc1153d/6a17e3c76317305c4f585a10/7a07e27e3095463ff93b4cb7f8a0cf3b8e44eab0-1777x1000.png" length="0" type="image/png"/>
    <pubDate>Wed, 28 May 2025 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>