<?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[Elasticsearch Labs]]></title>
        <description><![CDATA[Artículos y tutoriales del equipo de búsqueda de Elastic]]></description>
        <link>https://www.elastic.co/search-labs/es</link>
        <image>
            <url>https://www.elastic.co/search-labs/favicon.svg</url>
            <title>Elasticsearch Labs</title>
            <link>https://www.elastic.co/search-labs/es</link>
        </image>
        <generator>Elasticsearch Labs</generator>
        <lastBuildDate>Thu, 23 Apr 2026 11:23:18 GMT</lastBuildDate>
        <atom:link href="https://www.elastic.co/search-labs/es/rss/feed" rel="self" type="application/rss+xml"/>
        <copyright><![CDATA[© 2026. Elasticsearch B.V. Todos los derechos reservados]]></copyright>
        <language><![CDATA[es]]></language>
        <item>
            <title><![CDATA[Las incrustaciones v3 de Jina ya están disponibles en el Model Garden de Gemini Enterprise Agent Platform]]></title>
            <description><![CDATA[El modelo base de búsqueda de Jina, jina-embeddings-v3, ya se puede implementar de forma autónoma en el Model Garden de Gemini Enterprise Agent Platform, y próximamente habrá más modelos disponibles. Ejecuta jina-embeddings-v3 en una única GPU L4 dentro de tu propia VPC.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/jina-embeddings-v3-gemini-enterprise-model-garden</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/jina-embeddings-v3-gemini-enterprise-model-garden</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Sa Zhang]]></dc:creator>
            <pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/42f72633f1e5453dbfd47730b5f776429f9f633e-721x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Hoy lanzamos <code>jina-embeddings-v3</code>, el primer modelo de base de búsqueda de Jina que estará disponible en <a href="https://console.cloud.google.com/vertex-ai/publishers/jinaai/model-garden/jina-embeddings-v3">Model Garden de Gemini Enterprise Agent</a><a href="https://console.cloud.google.com/vertex-ai/publishers/jinaai/model-garden/jina-embeddings-v3"> Platform</a> como un modelo de socio autodesplegable. El <em>autodespliegue</em> significa que el modelo se ejecuta en instancias de GPU dentro de tu proyecto de Google Cloud y Virtual Private Cloud (VPC). Sin llamadas API externas, sin medición por token, sin límites de velocidad.</p><p>Con esta integración, los usuarios de Elasticsearch obtienen una nueva opción de despliegue que mantiene los datos dentro de su perímetro de seguridad, ofrece costos previsibles de infraestructura y se ejecuta de forma nativa en Google Cloud. Al mismo tiempo, el ecosistema más amplio de Google Cloud obtiene acceso a los modelos de búsqueda y recuperación de última generación de Jina, diseñados específicamente para este fin.</p><p>Esta es la primera fase de una implementación más amplia. Junto con los modelos que vienen después, la selección formará una pila de recuperación completa: incrusta tus datos, incrusta búsquedas, recupera y reclasifica candidatos, y extiende la búsqueda a imágenes con incrustaciones multimodales, todo en la infraestructura que controles. Puedes empezar hoy mismo con <code>jina-embeddings-v3</code>, el modelo que ya impulsa las pipelines de búsqueda de producción en todo el ecosistema de Elasticsearch a través de Elastic Inference Service (EIS).</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Modelo</td><td style="border: 1px solid #ccc; padding: 4px;">Tipo</td><td style="border: 1px solid #ccc; padding: 4px;">Parámetros</td><td style="border: 1px solid #ccc; padding: 4px;">Capacidad clave</td><td style="border: 1px solid #ccc; padding: 4px;">Estado en el Model Garden</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`jina-embeddings-v3`</td><td style="border: 1px solid #ccc; padding: 4px;">Incrustación de texto</td><td style="border: 1px solid #ccc; padding: 4px;">572M</td><td style="border: 1px solid #ccc; padding: 4px;">Un caballo de batalla multilingüe probado, con un contexto de 8000, una salida de 1024 dimensiones, truncable a 32</td><td style="border: 1px solid #ccc; padding: 4px;">Disponible ahora</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`jina-embeddings-v5-text-small`</td><td style="border: 1px solid #ccc; padding: 4px;">Incrustación de texto</td><td style="border: 1px solid #ccc; padding: 4px;">677M</td><td style="border: 1px solid #ccc; padding: 4px;">Multilingüe sub-1B de última generación, contexto de 32 000, una salida de 1024 dimensiones, truncable a 32</td><td style="border: 1px solid #ccc; padding: 4px;">Próximamente</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`jina-embeddings-v5-text-nano`</td><td style="border: 1px solid #ccc; padding: 4px;">Incrustación de texto</td><td style="border: 1px solid #ccc; padding: 4px;">239M</td><td style="border: 1px solid #ccc; padding: 4px;">El mejor de su categoría con menos de 500 millones de parámetros, contexto de 8000, una salida de 768 dimensiones, truncable a 32</td><td style="border: 1px solid #ccc; padding: 4px;">Próximamente</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`jina-reranker-v3`</td><td style="border: 1px solid #ccc; padding: 4px;">Reclasificador</td><td style="border: 1px solid #ccc; padding: 4px;">600M</td><td style="border: 1px solid #ccc; padding: 4px;">Reclasificador por lista, contexto de 131K, hasta 64 documentos</td><td style="border: 1px solid #ccc; padding: 4px;">Próximamente</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`jina-clip-v2`</td><td style="border: 1px solid #ccc; padding: 4px;">Incrustación multimodal</td><td style="border: 1px solid #ccc; padding: 4px;">900 millones</td><td style="border: 1px solid #ccc; padding: 4px;">Texto e imagen en un espacio compartido, 89 idiomas y un contexto de texto de 8000, imágenes de 512 × 512</td><td style="border: 1px solid #ccc; padding: 4px;">Próximamente</td>
                    </tr>
              </tbody>
            </table>
          <p>Todos los modelos se ejecutan en una sola NVIDIA L4 (24 GB), el nivel de GPU más rentable de Google Cloud. La mayoría de los otros modelos de incrustación en Model Garden de Google Cloud requieren un A100 80 GB o H100, aproximadamente tres veces el costo de instancia por hora incluso antes de comenzar a contar tokens.</p><p>No se requiere licencia comercial adicional cuando se despliega a través de Vertex AI.</p><h2><strong>¿Por qué Model Garden?</strong></h2><p>¿Por qué desplegar a través de Model Garden en lugar de usar una API? Se reduce a tres cosas: control, costo y contexto.</p><h3>Tus datos nunca salen de casa</h3><p>Lo que más atrae a la mayoría de los desarrolladores es la arquitectura de autodespliegue. Cuando despliegas un modelo de Jina a través de Model Garden, los pesos se ejecutan en instancias de GPU dentro de tu propio proyecto de Google Cloud y tu propia VPC. Esto supone un cambio revolucionario para cualquiera que trabaje en sectores donde la seguridad de los datos es una preocupación, como las finanzas o la salud. Como no hay llamadas externas a API, tus datos confidenciales permanecen dentro de tu perímetro de seguridad.</p><h3>Escalado con predicción</h3><p>En lugar de pagar cada vez que incrustas una oración o reclasificas un documento, pagas un costo fijo por hora de instancia. Y dado que todos los modelos de Jina pueden ejecutarse en una sola NVIDIA L4, el nivel de GPU más asequible de Google Cloud, la barrera de entrada es baja. Tanto si procesas mil solicitudes como mil millones, tu factura de infraestructura se mantiene previsible. Este sistema te recompensa por aumentar tu tráfico en lugar de cobrarte impuestos por ello.</p><h3>Todo bajo un mismo techo</h3><p>Si tus datos ya están en Elasticsearch en Google Cloud, BigQuery o almacenamiento en el cloud, tiene sentido mantener tus motores de inferencia cerca. Al desplegar a través de Model Garden, los modelos de búsqueda de Jina heredan todas las características empresariales que ya estás utilizando: gestión de identidad y acceso (IAM) para el control de acceso, facturación unificada en tu factura existente de Google Cloud, y la capacidad de conectarse a las pipelines de Vertex AI para flujos de trabajo de operaciones de machine learning (MLOps).</p><p>Si bien la API de Jina AI Cloud y Elastic Cloud permiten escalar rápidamente ante picos de tráfico o integrarse con flujos de búsqueda ya existentes, Model Garden resulta la mejor opción para aplicaciones empresariales que exigen altos estándares de seguridad de datos y costos predecibles a gran escala. Elastic quiere adaptarse a tus necesidades.</p><h2><strong>Modelos de Jina AI</strong></h2><h3><strong>jina-embeddings-v3</strong></h3><p>Nuestro probado modelo de incrustación multilingüe con 572 millones de parámetros y 8000 de contexto de tokens. Obtiene una puntuación de 65,5 en el Massive Text Embedding Benchmark (MTEB) en inglés. Admite cinco adaptadores de adaptación de rango bajo (LoRA) específicos de la tarea (consulta de recuperación/pasaje, coincidencia de texto, clasificación, agrupar) y truncamiento de Matryoshka de 1024 a 64 dimensiones. Ya está ampliamente adoptado en todo el ecosistema de Elasticsearch a través de EIS.</p><p>Estamos liderando con la v3 porque muchos sistemas de producción ya dependen de ella. Si estás migrando una pipeline basado en la v3 a Google Cloud, ahora puedes ejecutar el mismo modelo de forma nativa sin tener que cambiar las dimensiones de incrustación ni volver a indexar.</p><h3><strong>jina-embeddings-v5-text (pequeño y nano)</strong></h3><p>Nuestros modelos de incrustación de texto de quinta generación, lanzados en febrero de 2026, logran un rendimiento de primer nivel, y compiten con modelos muchas veces más grandes.</p><p><code>v5-text-small</code> (677 millones) obtiene una puntuación de 67,0 en el conjunto de pruebas MTEB multilingües (MMTEB), que abarca 131 tareas de nueve tipos de tareas, y 71,7 en el MTEB en inglés. Es el modelo de incrustación multilingüe sub-1B más potente en la tabla de clasificación de MTEB.</p><p><code>v5-text-nano</code> (239 millones) obtiene una puntuación de 65,5 en MMTEB. Ningún otro modelo con menos de 500 millones de parámetros alcanza este nivel. Con menos de la mitad del tamaño que la mayoría de modelos comparables, es la elección natural para despliegues en el edge y sensibles a la latencia.</p><p>Ambos modelos son compatibles con:</p><ul><li><strong>Cuatro adaptadores LoRA específicos para cada tarea:</strong> recuperación, coincidencia de texto, clasificación, agrupación. Se selecciona un adaptador apropiado a través del parámetro <code>task</code> en el momento de la inferencia.</li><li><strong>Truncamiento de dimensiones de Matryoshka:</strong> reduce las dimensiones de incrustación de 1024 (o 768 para nano) a 32. La pérdida de calidad es mínima con un truncamiento moderado (p. ej., 256 dimensiones). Reducir las dimensiones a la mitad supone, aproximadamente, reducir el espacio de almacenamiento a la mitad.</li><li><strong>Cuantización binaria:</strong> comprime incrustaciones de 1024 dimensiones de 2 KB a 128 bytes mediante binarización. Un entrenamiento especial hace que esta compresión tenga pérdidas mínimas.</li><li><strong>Multilingüe: </strong>119 idiomas (pequeño) y 93 (nano).</li></ul><h3><strong>jina-reranker-v3</strong></h3><p>Un reclasificador multilingüe de listas de 0,6 mil millones de parámetros construido con una arquitectura de interacción <em>de vanguardia</em>. La consulta y hasta 64 coincidencias candidatas se ingresan en una única ventana de contexto de 131 000 tokens, y el modelo realiza una comparación entre documentos antes de la puntuación. El reclasificador v3 de Jina alcanza un nDCG@10 de 61,94 en BEIR, lo que supera al modelo que tiene un tamaño seis veces menor. Esto difiere fundamentalmente de los reclasificadores puntuales, que puntúan cada documento de forma aislada, lo que produce mejores resultados, especialmente para la recuperación de pasajes de documentos individuales.</p><h3><strong>jina-clip-v2</strong></h3><p>Un modelo de incrustación multimodal y multilingüe de 0,9 mil millones que mapea texto e imágenes en un espacio compartido de 1024 dimensiones. Es compatible con:</p><ul><li><strong>89 idiomas</strong> para la recuperación de imágenes de texto.</li><li><strong>Resolución de imagen de 512 × 512.</strong></li><li><strong>Entrada de texto de 8000 tokens.</strong></li><li><strong>Truncamiento Matryoshka</strong> de 1024 a 64 dimensiones para ambas modalidades.</li></ul><p>Altamente competitivo en pruebas comparativas de conversión de imagen a texto, incluidas las tareas multilingües.</p><h2><strong>Primeros pasos</strong></h2><p>Jina Embeddings v3 está disponible en Model Garden hoy. Aquí te explicamos cómo ponerlo en marcha.</p><p>Necesitas un proyecto de Google Cloud con la API de Vertex AI habilitada y suficiente cuota de GPU para al menos una instancia g2-standard-8 (NVIDIA L4). Si eres nuevo en Google Cloud, <a href="https://cloud.google.com/vertex-ai/docs/start/cloud-environment">empieza por la guía de configuración.</a></p><p><a href="https://console.cloud.google.com/vertex-ai/publishers/jinaai/model-garden/jina-embeddings-v3">La página Model Garden para las incrustaciones v3 de Jina</a> te guía por todo el flujo: sube el modelo, crea un endpoint, elige el tipo de máquina y despliega. Ábrela en tu propio proyecto y sigue los pasos guiados. Las máquinas A100 y H100 también están disponibles donde la región y la cuota lo permitan, pero L4 es todo lo que necesitas para comenzar.</p><p>Desde el clic hasta la primera incrustación, todo el proceso toma unos minutos.</p><h2><strong>Lo que viene después</strong></h2><p>Las incrustaciones v3 de Jina son el punto de partida. En las próximas semanas, llevaremos el resto de la pila de recuperación de Jina a Model Garden: incrustaciones de texto v5 (pequeñas y nano), jina-reranker-v3 y jina-clip-v2 para búsqueda multimodal. Todos se ejecutarán en una sola GPU L4 con el mismo modelo de autodespliegue.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elastic Security, Observability y Search ahora ofrecen una UI interactiva en tus herramientas de IA]]></title>
            <description><![CDATA[Elastic es el primer proveedor de seguridad en enviar una interfaz de usuario interactiva en AI Conversations, desarrollada sobre el estándar MCP App, del cual Anthropic y OpenAI son coautores. Se lanza junto con otras dos MCP Apps de Elastic para Observability y Search.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/mcp-apps-elastic</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/mcp-apps-elastic</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[David Elgut, Graham Hudgins, Jesse Miller]]></dc:creator>
            <pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/55cea79fd5c554656e8605525a61d4ad3ae0ee1b-2048x1143.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Los agentes de AI son tan buenos como las interfaces que devuelven. El resultado de una herramienta que solo ofrece texto aplana todo lo que es naturalmente visual e interactivo: un dashboard, una lista de alertas, un grafo de investigación o una traza distribuida. La conversación se convierte en un lugar donde uno pregunta y el agente responde, pero en cuanto el trabajo se vuelve complejo, hay que salir hacia otra pestaña, otro producto u otro límite de autenticación.</p><p>Las <a href="https://modelcontextprotocol.io/extensions/apps/overview">MCP Apps</a> cambian la forma de esa respuesta. Una herramienta puede ahora devolver una UI interactiva junto a su resumen de texto, y el host (Claude Desktop, Claude.ai, VS Code Copilot, Cursor) lo muestra en línea en la conversación. El modelo mantiene el texto compacto para razonar. El usuario obtiene una interfaz interactiva en tiempo real justo al lado del chat.</p><p>Tres propiedades hacen que esta sea una integración diferente de &quot;un webhook que devuelve una URL&quot;:</p><ul><li><strong>Preservación del contexto.</strong> La UI reside dentro de la conversación. Sin cambiar de pestaña, sin traspasos.</li><li><strong>Flujo de datos bidireccional.</strong> La UI puede llamar a las herramientas del servidor MCP para obtener datos nuevos, y el host puede devolver nuevos resultados del agente a la UI. No hay una capa de API independiente ni lógica de autenticación adicional.</li><li><strong>Límite de confianza en un entorno de prueba.</strong> Las MCP Apps se ejecutan en un iframe controlado por el host. No pueden acceder a la página principal, leer cookies ni salir de su contenedor.</li></ul><p>Las operaciones de seguridad se basan en la clasificación, los grafos de investigación y la detección de ataques, donde un agente de AI correlaciona cientos de alertas en unas pocas cadenas de ataque. Observability significa trazas distribuidas y análisis en profundidad de series temporales. Desarrollar en Kibana significa una cuadrícula de dashboard. Si conviertes todo eso en texto, pierdes lo que lo hace útil. Desarrollamos MCP Apps para las tres áreas y las lanzamos juntas como open source, de modo que la misma conversación pueda pasar de una cola de clasificación a un grafo de dependencias o a un dashboard en tiempo real, sin abandonar nunca el chat.</p><p>Cada una de las tres apps de referencia es un servidor MCP que ofrece muchas vistas interactivas, no un conjunto de productos separados. La app de seguridad por sí sola presenta seis dashboards que comparten el mismo contenedor de servidor, el mismo modelo de visibilidad de herramientas y el mismo puente de host. El patrón es pequeño; el área de superficie es donde el valor se acumula.</p><h2>Elastic Security MCP App</h2><h3>Por qué es importante para el SOC</h3><p>Cuando un agente le dice a un analista de SOC: “<em>Hay 47 alertas en el host-314, aquí hay un resumen</em>”, no ha hecho ningún trabajo. Simplemente indica dónde comienza el trabajo. El trabajo real se encuentra en la lista de alertas, el árbol de procesos, el grafo de investigación y el archivo del caso. No puedes hacerlo a partir de un párrafo de texto.</p><p>La MCP App de seguridad devuelve el flujo de trabajo en sí. El analista consulta al agente, y este devuelve un dashboard interactivo en el chat donde el analista puede profundizar en alertas, ejecutar búsquedas de amenazas, correlacionar cadenas de ataques y abrir casos, todo sin perder el hilo de la conversación. Y debido a que los hallazgos, las consultas y los casos llegan a Elasticsearch, la misma investigación está esperando en Kibana, donde el analista puede retomar una vez que se haya cerrado la conversación.</p><h3>Seis dashboards interactivos</h3><p>La <a href="https://github.com/elastic/example-mcp-app-security">Elastic Security MCP App</a> incluye seis elementos interactivos, uno por cada flujo de trabajo principal del SOC. Cada una es una UI de React que se renderiza en línea cuando el agente llama a la herramienta correspondiente:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Herramienta</td><td style="border: 1px solid #ccc; padding: 4px;">Qué hace</td><td style="border: 1px solid #ccc; padding: 4px;">UI interactiva</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Clasificación de alertas</td><td style="border: 1px solid #ccc; padding: 4px;">Recupera, filtra y clasifica las alertas de seguridad</td><td style="border: 1px solid #ccc; padding: 4px;">Agrupación por severidad, tarjetas de veredicto de AI, árbol de procesos, eventos de red</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Attack Discovery</td><td style="border: 1px solid #ccc; padding: 4px;">Análisis de cadena de ataque correlacionado por AI con generación bajo demanda</td><td style="border: 1px solid #ccc; padding: 4px;">Tarjetas narrativas de ataque con puntuación de confianza, riesgo de entidad, mapeo MITRE</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Gestión de casos</td><td style="border: 1px solid #ccc; padding: 4px;">Crear, buscar y gestionar casos de investigación</td><td style="border: 1px solid #ccc; padding: 4px;">Lista de casos con alertas, observables, pestañas de comentarios, acciones de IA</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Reglas de detección</td><td style="border: 1px solid #ccc; padding: 4px;">Navega, ajusta y gestiona las reglas de detección</td><td style="border: 1px solid #ccc; padding: 4px;">Navegador de reglas con búsqueda KQL, validación de consultas, análisis de reglas ruidosas</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Búsqueda de amenazas</td><td style="border: 1px solid #ccc; padding: 4px;">Entorno de trabajo ES|QL con investigación de entidades</td><td style="border: 1px solid #ccc; padding: 4px;">Editor de consultas, entidades en las que se puede hacer clic y grafo de investigación</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Datos de muestra</td><td style="border: 1px solid #ccc; padding: 4px;">Generar eventos de seguridad ECS para escenarios de ataque comunes</td><td style="border: 1px solid #ccc; padding: 4px;">Selector de escenarios con cuatro cadenas de ataque predefinidas</td>
                    </tr>
              </tbody>
            </table>
          <p>Cada herramienta devuelve un resumen textual compacto sobre el que el modelo puede razonar, junto con la UI interactiva en la que actúa el analista. La UI también puede obtener datos actualizados en segundo plano a través del puente MCP del host. El modelo completo de la herramienta y la API del puente se encuentran en <a href="https://github.com/elastic/example-mcp-app-security/blob/main/docs/architecture.md">la documentación de arquitectura del repositorio</a>.</p><p>La app también incluye <a href="https://github.com/elastic/example-mcp-app-security/tree/main/skills">habilidades de Claude Desktop</a>, <code>SKILL.md</code> archivos que enseñan al agente cuándo y cómo usar cada herramienta. Descarga los archivos ZIP de habilidades preconfiguradas desde la <a href="https://github.com/elastic/example-mcp-app-security/releases/latest">última versión</a>.</p><h3>De la alerta al caso</h3><p>Cuatro capacidades cubren el ciclo central del SOC. Cada una recibe un mensaje, llama a una herramienta y devuelve un dashboard interactivo junto con un resumen de texto que el modelo analiza. El día de un analista generalmente comienza con una cola de alertas.</p><p><strong>Clasificación de alertas. </strong>Pídele al agente que clasifique los datos por host, regla, usuario o intervalo de tiempo. Pídele al agente que clasifique los datos por host, regla, usuario o intervalo de tiempo. La habilidad de Clasificación de alertas devuelve un dashboard de veredictos de AI sobre la lista de alertas sin procesar, con un veredicto por regla de detección que clasifica la actividad de esa regla como benigna, sospechosa o maliciosa, cada uno con una puntuación de confianza y una acción recomendada. Haz clic en cualquier alerta para abrir una vista detallada con un árbol de procesos, eventos de red, alertas relacionadas y etiquetas MITRE ATT&amp;CK. No es necesario cambiar de contexto entre la conversación con la AI y tu dashboard dentro de Kibana, todo sucede en tiempo real dentro de tu conversación.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b0239ea916a828ee0bfd0cc046af17330568fe09-2048x1313.png" style="width: 100%;" /><p><strong>Busca amenazas. </strong>Pide al agente que busque en tus índices. La habilidad de Búsqueda de amenazas devuelve un banco de trabajo ES|QL con la búsqueda precompletada y ejecutada automáticamente, con todas las entidades de los resultados en las que se puede hacer clic para profundizar. El modelo escribe una breve lectura debajo de la tabla: qué es inusual, qué está conectado, qué vale la pena observar más de cerca. Luego propone el siguiente paso: profundizar en la investigación de amenazas o iniciar una nueva habilidad dentro de la MCP App que complemente el trabajo realizado hasta el momento. Lo que complementa muy bien esto es iniciar una detección de ataques para recopilar más contexto sobre las alertas que ya has analizado en profundidad y las amenazas que has investigado hasta ahora. Lo que articula muy bien todo esto es lanzar una detección de ataques para obtener más contexto sobre las alertas en las que has profundizado y las amenazas que has explorado hasta el momento.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9f0bde828bcedb24e2b45995cfa306f03e57d300-2048x1313.png" style="width: 100%;" /><p><strong>Ejecuta una detección de ataques.</strong> La habilidad de Detección de ataques activa la API de detección de ataques y devuelve una lista clasificada de hallazgos. Cada hallazgo es un conjunto de alertas relacionadas que se unen en una cadena de ataque, en la que se muestran de inmediato las tácticas de MITRE, una puntuación de riesgo, un indicador de confianza y los hosts y usuarios afectados. El resumen del agente se ubica debajo de los hallazgos en el mismo orden de clasificación, y la conversación ahora contiene todo lo necesario para actuar: consultas de búsqueda, decisiones de clasificación, cadenas correlacionadas, todo preparado para el siguiente paso.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e52af22dbaa070192170280d519dfb219ad807f5-2048x1313.png" style="width: 100%;" /><p><strong>Abrir casos sin salir del chat.</strong> Aprueba hallazgos en bloque o pide al agente que abra casos para alertas específicas. La habilidad de Gestión de casos crea un caso por hallazgo aprobado (alertas de origen adjuntas, tácticas MITRE heredadas de la cadena de ataque) y muestra la lista de casos en vivo en línea. Haz clic en un caso para ver su vista de detalles, que incluye una fila de botones de acción de AI: Resumir caso, Sugerir próximos pasos, Extraer IOC y Generar cronología. Cada uno devuelve un prompt estructurado al chat, para que el agente capte el contexto del caso sin necesidad de reintroducirlo. El resumen del agente se encuentra debajo de la lista de casos y cubre toda la cola de IR, lo que incluye los casos recién abiertos y hallazgos anteriores que aún necesitan uno.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2057310b450c2917c5434c713f2b34732067429a-2048x1283.png" style="width: 100%;" /><p>Cada paso de esta guía ejecuta el mismo ciclo: llega un aviso, la habilidad lo detecta, la herramienta devuelve un resumen de texto compacto para que el modelo razone, junto con una interfaz interactiva sobre la que actúa el analista. Encadena las habilidades y se componen en un flujo integral de SOC: búsqueda, clasificación, correlación, apertura de casos y el impulso del siguiente pivote, todo esto con el modelo preservando el contexto de la sesión en cada paso. Si invocas cualquiera de ellas de forma individual, seguirá siendo el dashboard completo, que apunta a la porción de datos que especifiques. De cualquier manera, el trabajo se acumula dentro de la conversación; sin cambiar de pestaña, sin copiar y pegar, sin transferencias.</p><p>Dos habilidades más completan la app: un navegador de reglas de detección para ajustar reglas ruidosas y un generador de datos de muestra para generar eventos ECS realistas contra un cluster nuevo. En una próxima publicación hablaremos en detalle de los seis: el grafo de investigación, el canvas del flujo de ataque y el recorrido paso a paso.<br/></p><blockquote><em>“La Elastic Security MCP App acorta la brecha entre la detección automatizada y la búsqueda manual. Al llevar nuestros datos de seguridad directamente a una única interfaz dentro de Claude Desktop, detectamos amenazas “silenciosas” en menos de una hora, riesgos que no activaban alertas estándar, pero que requerían una acción inmediata. Es un multiplicador de fuerza para nuestros analistas”. Mandy Andress: directora de seguridad de la información (CISO), Elastic.</em></blockquote><h3>Cómo funciona</h3><p>Cada MCP App es un pequeño servidor de Node.js cuyas herramientas devuelven tanto un resumen de texto compacto para el modelo como una UI de React que el host renderiza en línea. Como está basado en la especificación abierta de la MCP App, el mismo servidor se ejecuta en cualquier host compatible; consulta el <a href="https://github.com/elastic/example-mcp-app-security/blob/main/docs/architecture.md">documento de arquitectura del repositorio</a> para conocer el diseño completo.</p><h3>Pruébalo</h3><p>Requiere Elasticsearch 9.x con Security activada, además de Kibana para casos, reglas y descubrimiento de ataques. El camino más rápido es el paquete <code>.mcpb</code> de un solo clic de la <a href="https://github.com/elastic/example-mcp-app-security/releases/latest">última versión.</a> Haz doble clic en Claude Desktop y te pedirán la URL de Elasticsearch y la clave API. Las guías de configuración para <a href="https://github.com/elastic/example-mcp-app-security/blob/main/docs/setup-cursor.md">Cursor</a>, <a href="https://github.com/elastic/example-mcp-app-security/blob/main/docs/setup-vscode.md">VS Code</a>, <a href="https://github.com/elastic/example-mcp-app-security/blob/main/docs/setup-claude-code.md">Claude Code</a>, <a href="https://github.com/elastic/example-mcp-app-security/blob/main/docs/setup-claude-ai.md">Claude.ai</a> y la compilación desde el código fuente se encuentran en el <a href="https://github.com/elastic/example-mcp-app-security">repositorio</a>.</p><a href="https://videos.elastic.co/watch/voDxV9RWWpY9HeBYTWTS93">Watch video on Vidyard</a><h2>Elastic Search MCP App: dashboards creados a partir de conversaciones</h2><p>Cada usuario de Kibana conoce el desvío del dashboard: deja lo que estás trabajando, abre Kibana, elige un índice, elige campos, elige una visualización, retoca y guarda. Son cinco cambios de contexto antes de que aparezca un solo gráfico en pantalla.</p><p>La nueva <a href="https://github.com/elastic/example-mcp-dashbuilder">app de referencia example-mcp-dashbuilder</a> lo integra en un prompt. Pídele al agente: “<strong>créame un dashboard con métricas de ingresos, tendencias de pedidos y desgloses por categoría</strong>” y el dashboard aparece dentro de la conversación, sin necesidad de cambiar de pestaña.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b096b493726488f53040b08b5c8bc53ca0e44f9d-2048x1182.png" style="width: 100%;" /><p>Detrás de ese prompt, el agente explora tus datos de Elasticsearch a través de ES|QL y selecciona tipos de gráficos que coincidan con los datos: barras para comparaciones, líneas para tendencias, tarjetas métricas para KPI y mapas de calor para patrones bidimensionales. Coloca paneles sobre la cuadrícula de 48 columnas de Kibana usando el tema Elastic UI Borealis, y el resultado es totalmente interactivo: puedes arrastrar, redimensionar y agrupar paneles en secciones plegables directamente en el chat. Cuando el dashboard se ve bien, una sola llamada de herramienta lo exporta a Kibana, y preserva las búsquedas ES|QL y los colores personalizados. También puedes importar los paneles de Kibana existentes al chat para la edición asistida por IA.</p><p>El principio es el mismo detrás de la app de Security: cuando el artefacto es el producto, devolverlo dentro de la conversación cierra el ciclo entre describir lo que quieres y verlo.</p><p>Internamente, sigue el mismo patrón que MCP App. Un servidor Node.js registra una herramienta <code>view_dashboard</code> orientada al modelo junto a un conjunto de herramientas exclusivas para aplicaciones que la UI llama directamente (obtención de datos, persistencia del diseño, detección de campos de tiempo, exportación/importación). La vista del dashboard en sí es un único archivo HTML autocontenido, empaquetado con <code>vite-plugin-singlefile</code> y servido como un recurso de MCP App. Los desarrolladores que hacen un fork del repositorio obtienen el mismo contenedor de servidor y el mismo puente de host que ven en la aplicación Security, pero apuntando a un trabajo diferente. El README de example-mcp-dashbuilder incluye la arquitectura completa y la referencia de tipos de gráficos.</p><a href="https://videos.elastic.co/watch/wep6PxPiEDyyvsYTKq6v5B">Watch video on Vidyard</a><h2>Elastic Observability MCP App</h2><p>La tercera app de referencia, <a href="https://github.com/elastic/example-mcp-app-observability">Elastic Observability MCP App</a>, aborda la versión SRE del mismo problema de forma. Cuando algo falla en producción, lo que necesita el ingeniero de guardia no es un gráfico, sino un diagnóstico construido a partir de métricas de K8s, la topología de APM, anomalías de ML y una evaluación de riesgos. La forma de la respuesta es una narrativa causal: qué falló, por qué, de qué depende y qué hacer a continuación.</p><p>Seis herramientas que apoyan el flujo de trabajo de investigación de observabilidad</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Herramienta</td><td style="border: 1px solid #ccc; padding: 4px;">Dependencia</td><td style="border: 1px solid #ccc; padding: 4px;">Lo que hace</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">observe</td><td style="border: 1px solid #ccc; padding: 4px;">Universal</td><td style="border: 1px solid #ccc; padding: 4px;">Primitiva de acceso transitorio de ES|QL + anomalías de ML: ejecuta una búsqueda una sola vez, toma una muestra en vivo de una métrica o bloquea la ejecución hasta que se alcance un umbral o se detecte una anomalía.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">gestionar-alertas</td><td style="border: 1px solid #ccc; padding: 4px;">Universal</td><td style="border: 1px solid #ccc; padding: 4px;">Crea, visualiza, recupera y elimina reglas de alerta con umbrales personalizados en Kibana. Omite la URL de Kibana para ejecutarlo en modo de solo lectura.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">anomalías-ml</td><td style="border: 1px solid #ccc; padding: 4px;">Trabajos de ML</td><td style="border: 1px solid #ccc; padding: 4px;">Consulta los registros de anomalías de ML y abre una vista explicadora de anomalías en línea.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">resumen-de-estado-de-apm</td><td style="border: 1px solid #ccc; padding: 4px;">Elastic APM</td><td style="border: 1px solid #ccc; padding: 4px;">Agregación del estado a nivel de cluster a partir de la telemetría de APM; incorpora capas de contexto de K8s y de ML cuando están disponibles.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">dependencias-servicio-apm</td><td style="border: 1px solid #ccc; padding: 4px;">Elastic APM</td><td style="border: 1px solid #ccc; padding: 4px;">Grafo de dependencias del servicio: servicios previos y posteriores, protocolos, volumen de llamadas.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">radio-de-impacto-en-K8s</td><td style="border: 1px solid #ccc; padding: 4px;">Métricas de Kubernetes</td><td style="border: 1px solid #ccc; padding: 4px;">Impacto de interrupción de Node: interrupción total, degradado, no afectado, factibilidad de reprogramación.</td>
                    </tr>
              </tbody>
            </table>
          <h3><strong>Agregación del estado del cluster</strong></h3><p>Haz preguntas como “¿qué está fallando?” o “dame un informe de estado” y obtén una vista de orientación en una sola ejecución: indicador general de salud, servicios degradados con sus causas, principales consumidores de memoria por pod, desglose de la severidad de anomalías y rendimiento de los servicios, todo en una única vista integrada. Este es el punto de partida cuando algo no parece estar bien, pero no sabes dónde buscar. La vista se adapta en función de lo que soporta tu despliegue. APM te ofrece información sobre el estado de los servicios. Las métricas de Kubernetes agregan contexto de pod y nodo. Capa de trabajos de ML en anomalías.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f31be8bbcffd54623fcec4e51fd914dae666c026-1510x1810.png" style="width: 100%;" /><h3><strong>Grafo de dependencia de servicios</strong></h3><p>Haz preguntas como “¿qué llama a finalizar la compra?” o “muéstrame la topología” y obtén un grafo de dependencias por capas: llamadas upstream, dependencias downstream, protocolos, volumen de llamadas y latencia por cada enlace. Pidámosle a Claude que “me muestre las dependencias de servicio del frontend”:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2afec7dbd2153bc3a9f05068f323911af766a258-2048x1184.png" style="width: 100%;" /><p>Haz zoom, desplázate y pasa el cursor por encima para ver todos los detalles que necesitas para entender las complejas relaciones entre los servicios:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ff99d2fd0a54c22c9f7b975e9d8d7073c4311e7a-2048x970.png" style="width: 100%;" /><h3><strong>Evaluar el riesgo con un radio de impacto</strong></h3><p>Haz preguntas como “¿qué pasa si se cae mi nodo de K8s?” y obtén un diagrama radial de impacto: el nodo objetivo en el centro, los despliegues con interrupción total en rojo, los degradados en ámbar y los no afectados en gris. Una tarjeta de resumen flotante muestra los pods en riesgo y la viabilidad de su reprogramación. Los despliegues de una sola réplica se señalan como puntos únicos de falla. </p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7736bef9609e45a3f0c7f9f3090a314b0834315d-1272x1838.png" style="width: 100%;" /><h3><strong>Observe</strong></h3><p>La primitiva de acceso principal del agente para Elastic: una sola herramienta, tres modos para tres necesidades distintas. Di “¿qué está pasando con la CPU ahora mismo?” y ejecuta una consulta ES|QL una vez y devuelve una tabla. Di “muéstrame la latencia del frontend durante los próximos 60 segundos” y toma muestras en vivo de la métrica, actualizando el gráfico en el momento. Di “avísame cuando la memoria baje de 80 MB” o “vigila cualquier anomalía durante los próximos 10 minutos” y bloquea la ejecución hasta que se cumpla la condición o expire la ventana. La vista se adapta al modo: una tabla de resultados para búsquedas puntuales, un gráfico de tendencia en vivo con estadísticas actuales/pico/línea base para el muestreo y condiciones de umbral, y una tarjeta de activación con severidad para el modo de anomalías.</p><h3>Cómo funciona</h3><p>El mismo patrón de MCP App que las apps de Security y Search: un servidor Node.js y seis herramientas orientadas al modelo conectadas a seis recursos de vista en archivos individuales. Las herramientas se agrupan según el backend de despliegue (universal, dependiente de APM, dependiente de K8s, dependiente de ML), de modo que tanto el agente como el usuario saben desde el inicio qué herramientas aplican a un despliegue determinado, en lugar de descubrir limitaciones de capacidad en el momento de la ejecución. MCP App también incluye un flujo de trabajo de ejemplo de Agent Builder: <code>k8s-crashloop-investigation-otel</code>, que puede activarse ante una alerta de Kubernetes y devolver un resumen estructurado de la causa raíz antes de que hayas abierto un solo panel.</p><a href="https://videos.elastic.co/watch/xcF3nKDziVgphc5bPAkv9R">Watch video on Vidyard</a><h2>El stack agéntico, interactivo</h2><p>Hay tres propiedades de este patrón que vale la pena mencionar directamente. Primero, el resultado de la herramienta ya no es el final del trabajo, es el comienzo de este: la conversación devuelve una interfaz en la que puedes actuar, no un resumen desde el que tienes que actuar. Segundo, el mismo agente, el mismo contexto de modelo y el mismo hilo de conversación ahora pueden moverse entre las superficies de Security, Search y Observability sin abandonar la conversación. En tercer lugar, esto solo funciona porque Elasticsearch y Kibana ya ofrecen las API. La MCP App es una capa interactiva delgada sobre las capacidades del producto que ya enviamos.</p><p>Attack Discovery ya potencia la vista de hallazgos correlacionados dentro de esta app. Dentro del stack, el mismo patrón agente va más allá: Elastic Workflows automatiza los pasos deterministas (enriquecer entidades, crear casos, aislar hosts), mientras que Agent Builder razona sobre los datos e invoca esos flujos de trabajo como herramientas. La MCP App trae esa misma superficie de seguridad a la conversación externa; Workflows y Agent Builder la profundizan dentro del stack. Diferentes puntos de acceso, pero las mismas API de Elastic.</p><p><strong>Pruébalo:</strong></p><ul><li><strong>Security:</strong> <a href="https://github.com/elastic/example-mcp-app-security">example-mcp-app-security</a></li><li><strong>Search y dashboards:</strong> <a href="https://github.com/elastic/example-mcp-dashbuilder">example-mcp-dashbuilder</a></li><li><strong>Observability: </strong><a href="https://github.com/elastic/example-mcp-app-observability">example-mcp-observability</a></li></ul><p>¿Aún no tienes un cluster de Elasticsearch? Comienza una <a href="https://cloud.elastic.co/registration">prueba gratuita de Elastic Cloud</a>. Para obtener más información sobre los componentes básicos detrás de la app de seguridad, consulta las publicaciones relacionadas de Security Labs en <a href="https://www.elastic.co/security-labs/from-alert-fatigue-to-agentic-response">Elastic Workflows y Agent Builder</a>, <a href="https://www.elastic.co/security-labs/agent-skills-elastic-security">Habilidades de agentes</a> y <a href="https://www.elastic.co/security-labs/speeding-apt-attack-discovery-confirmation-with-attack-discovery-workflows-and-agent-builder">Detección de ataques</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Introducción de claves API unificadas para Elastic Cloud Serverless y Elasticsearch]]></title>
            <description><![CDATA[Aprende cómo Elastic unificó la autenticación del plano de control y del plano de datos en Serverless con una arquitectura de IAM distribuida globalmente. Usa una sola clave de API para las API de Cloud y de Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elastic-cloud-api-keys-unified-serverless</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elastic-cloud-api-keys-unified-serverless</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[ Alex Chalkias]]></dc:creator>
            <pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/864e229f00eb2018084f13dd7f0e390e18383ed4-1980x1188.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Imagina que eres un ingeniero de confiabilidad de sitios (SRE, por sus siglas en inglés) responsable de una creciente cartera de proyectos Elastic Cloud Serverless: Elastic Observability para tu infraestructura de producción, Elastic Security para tu equipo del centro de operaciones de seguridad (SOC, por sus siglas en inglés) y Elasticsearch para tu aplicación orientada al cliente. Cada proyecto tiene su propia clave de API de Elasticsearch. Tu pipeline de integración continua y entrega continua (CI/CD) necesita una clave de API de Cloud independiente para aprovisionar y administrar esos proyectos. El día de rotación llega cada trimestre: recorres cada proyecto, acuñas nuevas claves, actualizas tu estado de Terraform, redistribuyes tus pipelines y esperas que nada se te escape. Cuando un incidente ocurre a las 2 a. m. y necesitas revocar el acceso rápidamente, estás consultando una hoja de cálculo de credenciales para averiguar qué clave pertenece a qué proyecto y qué servicio.</p><p>Hoy, esa historia se vuelve mucho más sencilla. Ahora puedes usar <strong>las claves de API de Elastic Cloud</strong> para autenticarte directamente contra las API de <strong>Elasticsearch</strong> y <strong>Kibana</strong> en <strong>Elastic Cloud Serverless</strong>. Ahora puedes usar una sola credencial para gestionar los recursos de tu organización <em>y</em> ejecutar operaciones de datos, como consultas de ES|QL (lenguaje de búsqueda de Elasticsearch), ingesta de datos y generación de alertas.</p><p>Veamos por qué lo creamos, cómo diseñamos una capa de identidad distribuida en todo el mundo para hacerlo posible y cómo sienta las bases para la búsqueda entre proyectos.</p><h2>La carga secreta de la gestión</h2><p>Construir pipelines fiables de CI/CD, flujos de trabajo de GitOps o automatización de Terraform alrededor de plataformas de datos conlleva un costo oculto: la proliferación de secretos.</p><p>En el modelo anterior, los desarrolladores se enfrentaban a un proceso de autenticación fragmentado:</p><ul><li><strong>Plano de control (claves de la API de Elastic Cloud):</strong> Claves con ámbito de organización que se emplean para crear proyectos, invitar a usuarios y gestionar la facturación a través de la <a href="https://www.elastic.co/docs/api/doc/cloud/">API de Elastic Cloud</a>.</li><li><strong>Plano de datos (claves de la API de Elasticsearch):</strong> Claves con alcance de proyecto <em>creadas dentro</em> de un proyecto Serverless específico para interactuar con las API <a href="https://www.elastic.co/docs/api/doc/elasticsearch-serverless/">de Elasticsearch</a> y <a href="https://www.elastic.co/docs/api/doc/serverless">Kibana</a>.</li></ul><p>Esto significaba que tu script de despliegue tenía que autenticarse en Elastic Cloud, aprovisionar un proyecto Serverless, extraer una clave de API de Elasticsearch recién creada de ese proyecto específico y luego inyectar <em>esa</em> segunda clave en la aplicación o herramienta de automatización downstream, lo que resultaba en pipelines complejos, logs de auditoría fragmentados y un mayor riesgo de fugas de credenciales.</p><h2>Autenticación unificada en Elastic Cloud Serverless</h2><p>Con este lanzamiento, la división ha desaparecido para los proyectos Serverless. Ahora puedes crear una clave de la API de Elastic Cloud que esté explícitamente autorizada para <strong>las API de Cloud, Elasticsearch y Kibana</strong>.</p><ul><li><strong>Antes:</strong> Una clave de API de Elastic Cloud era estrictamente un token del plano de control. Podía crear proyectos, gestionar la facturación e invitar a usuarios, pero tenía una limitación importante: no se podía usar para llamar a las API de Elasticsearch o Kibana dentro de esos proyectos. Siempre necesitabas una segunda clave específica del proyecto para las operaciones con datos.</li><li><strong>Ahora:</strong> Al optar por el acceso a <strong>la API de Cloud, Elasticsearch y Kibana</strong> cuando creas una clave de la API de Elastic Cloud, se elimina el límite rígido para Serverless. Esa clave de la API se convierte en una credencial verdaderamente unificada. Mantiene su capacidad para gestionar la infraestructura de tu organización, mientras obtiene acceso nativo para consultar, ingerir y analizar datos en cualquier proyecto Serverless autorizado.</li></ul><p>Al unificarlo bajo una única clave de API de Elastic Cloud, obtienes una única identidad que puede ser delimitada, sometida a auditoría, rotada y revocada como una sola unidad. Cada llamada a la API, ya sea que aprovisione un nuevo proyecto o ejecute un ES|QL, aparece bajo la misma credencial en tus logs de auditoría, dándote un único rastro a seguir durante investigaciones de incidentes o revisiones de cumplimiento. La rotación de credenciales se convierte en una operación de un solo paso en lugar de una actualización coordinada a través de secretos separados del plano de control y del plano de datos. Y dado que las asignaciones de roles son por proyecto, una única clave puede abarcar varios proyectos, gestionando la ingesta en tu proyecto de observabilidad y ejecutando consultas en tu proyecto de seguridad, sin tener que gestionar credenciales separadas para cada uno.</p><p>Es importante destacar que <em>unificado</em> no significa <em>todopoderoso</em>. Al usar la carga útil <code>role_assignments</code>, puedes asignar una clave unificada estrictamente a un solo proyecto y a un rol específico (como solo lectura), cerciorando que el radio de alcance permanezca completamente contenido si alguna vez se expone una credencial. Si un desarrollador se marcha o una aplicación es desactivada, puedes revocar una sola clave de la Consola de Elastic Cloud, terminando inmediatamente el acceso tanto en el plano de control como en todos los proyectos asociados de Elasticsearch.</p><p><em>(Nota: Para despliegues gestionados/alojados en Elastic Cloud Hosted, las claves de API de la cloud siguen gestionando solo el plano de control. La función para extenderlo a las API de pila alojadas está previsto para una versión futura).</em></p><h2>Automatiza tus flujos de trabajo</h2><p>Los primeros pasos son simples. Puedes configurarlo completamente a través de la consola de Elastic Cloud o automatizarlo empleando la <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elastic-cloud-api-keys">API de Elastic Cloud</a>.</p><p>El proceso de la UI sigue igual, pero ahora puedes seleccionar <strong>acceso a la API de Cloud, Elasticsearch y Kibana</strong> bajo la asignación de rol del proyecto.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c4f802f130655290cd474b283001a954d14c3088-2801x1681.png" style="width: 100%;" /><p>Aquí te mostramos cómo crear una clave unificada mediante programación empleando la API de Elastic Cloud. Fíjate en el array <code>application_roles</code>, ya que es el que otorga acceso nativo al plano de datos de Elasticsearch:</p><pre><code>curl -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: ApiKey $EC_API_KEY" \
  "https://api.elastic-cloud.com/api/v1/users/auth/keys" \
  -d '{
    "description": "unified-automation-key",
    "expiration": "90d",
    "role_assignments": {
      "project": {
        "elasticsearch": [
          {
            "role_id": "elasticsearch-admin",
            "organization_id": "YOUR_ORG_ID",
            "all": false,
            "project_ids": ["YOUR_PROJECT_ID"],
            "application_roles": ["admin"]
          }
        ]
      }
    }
  }'</code></pre><p>Una vez creada, simplemente pasas esta misma clave en el encabezado <code>Authorization: ApiKey</code> tanto a <code>api.elastic-cloud.com</code> como a tus puntos finales específicos de Serverless Elasticsearch.</p><h2>Bajo el capó: construyendo una capa de identidad distribuida</h2><p>Hacer que una clave API de Cloud funcione tanto en el plano de control como en el plano de datos no es tan simple como pasar un token. Implica resolver un reto fundamental de los sistemas distribuidos.</p><p>Históricamente, las claves de API de Cloud residían en un clúster de seguridad global centralizado. Esto funciona bien para las operaciones del plano de control donde una latencia más alta es aceptable. Sin embargo, las solicitudes de datos de Elasticsearch requieren una latencia ultrabaja. No podemos permitirnos un viaje de ida y vuelta alrededor del mundo hasta un plano de control central para validar cada consulta de búsqueda o solicitud de ingesta.</p><p>Para resolverlo, implementamos una nueva arquitectura de autenticación respaldada por un almacén de datos distribuido a nivel mundial. El siguiente diagrama de secuencia muestra a un cliente enviando una consulta a Elasticsearch con una clave de API de Elastic Cloud, lo que ilustra cómo la autenticación se lleva a cabo íntegramente dentro de la región local, sin necesidad de un viaje de ida y vuelta al plano de control global. Elasticsearch delega la autenticación al servicio regional de IAM, que valida la clave y comprueba las asignaciones de roles en una réplica local de la base de datos distribuida en todo el mundo. Una vez autorizado, Elasticsearch ejecuta la consulta y devuelve los resultados al cliente.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3e38d7a862b9981523c5393c441b92eae13aeb90-2401x1351.webp" style="width: 100%;" /><h3>Persistencia distribuida a nivel global</h3><p>En lugar de depender únicamente de un clúster de seguridad centralizado, las claves de la API de Elastic Cloud y las definiciones de roles asociadas ahora se almacenan permanentemente en una base de datos distribuida en todo el mundo y de alta disponibilidad. Esta base de datos sincroniza los datos de administración de identidad y acceso (IAM) a través del plano de control global y los planos de datos regionales donde realmente se ejecutan tus proyectos sin servidor.</p><h3>Validación local con IAM regional</h3><p>Cuando tu cliente envía una solicitud a Elasticsearch usando una clave de API de Elastic Cloud, la solicitud no se remite al plano de control mundial. En su lugar, es redirigido al nuevo servicio regional de IAM. Valida la clave contra la réplica de la base de datos local, asegurando que la autenticación ocurra con una latencia cercana a cero y esté completamente aislada de las interrupciones del plano de control mundial.</p><h3>Mapping dinámico de roles</h3><p>La autenticación es solo la mitad del camino; el sistema también tiene que autorizar la solicitud. El servicio regional de IAM traduce instantáneamente tus asignaciones de rol a nivel de cloud (por ejemplo, <code>application_roles</code>) en privilegios nativos de Elasticsearch. Elasticsearch puede entonces autorizar y ejecutar la solicitud localmente, sin necesidad de un índice <code>.security</code> local.</p><h2>La base para la búsqueda entre proyectos</h2><p>Esta arquitectura de identidad distribuida es un pilar fundamental para el futuro de la plataforma Elastic.</p><p>Como la identidad y el acceso ahora están unificados y sincronizados globalmente, contamos con el marco de trabajo necesario para transferir tu identidad de forma segura entre diferentes proyectos. Esto permite las próximas funcionalidades de <strong>búsqueda entre proyectos (CPS)</strong> para Serverless.</p><p>Con CPS, podrás consultar datos que abarcan varios proyectos serverless remotos, como combinar cargas de trabajo de seguridad y observabilidad, tan fácilmente como si fueran un solo set de datos. Al utilizar claves API unificadas, el sistema puede evaluar automáticamente tus permisos en todos los proyectos a la vez, sin que tengas que configurar relaciones de confianza complejas, certificados ni duplicar credenciales en cada proyecto de destino.</p><h2>Más información</h2><p>¿Listo para simplificar tu stack?</p><ul><li>Lee la <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elastic-cloud-api-keys">documentación sobre las claves de la API de Elastic Cloud</a> para saber cómo asignar acceso a la pila.</li><li>Consulta la referencia <a href="https://www.elastic.co/docs/api/doc/cloud/operation/operation-create-api-key">Crear clave de la API (Elastic Cloud API)</a> para automatizar la generación de claves.</li><li>Consulta <a href="https://www.elastic.co/docs/deploy-manage/api-keys">las claves de API de Elastic</a> para ver una comparación completa de los tipos de claves disponibles en toda la Platform de Elastic.</li></ul><p>Empieza o continúa construyendo en <a href="https://cloud.elastic.co/registration">Elastic Cloud</a> hoy mismo.</p><h2>Descargo de responsabilidad</h2><p>El lanzamiento y el momento de cualquier característica o funcionalidad descrita en esta publicación quedan a exclusivo criterio de Elastic. Es posible que alguna característica o funcionalidad que no esté disponible en este momento no se lance a tiempo o no se lance en absoluto.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Agrupación no supervisada de documentos con Elasticsearch + incrustaciones de Jina]]></title>
            <description><![CDATA[Un enfoque práctico y reproducible para la agrupación no supervisada de documentos con Elasticsearch y embeddings de Jina.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/unsupervised-document-clustering-elasticsearch-jina-embeddings</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/unsupervised-document-clustering-elasticsearch-jina-embeddings</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Matthew Adams]]></dc:creator>
            <pubDate>Fri, 10 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/662c00694c3e0c2fb2128098bdb6813df9e86a72-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La búsqueda vectorial empieza con una consulta, pero ¿qué pasa si no tienes una?</p><p>Las organizaciones acumulan grandes colecciones de documentos, como tickets de soporte, presentaciones legales, feeds de noticias y trabajos de investigación, pero para poder hacer las preguntas correctas, primero necesitan entender lo que contienen. Sin etiquetas o datos de entrenamiento, revisar manualmente miles de documentos es poco práctico. La búsqueda tradicional no es útil cuando no sabes qué buscar.</p><p>Esta publicación describe un enfoque nativo de Elasticsearch para la agrupación no supervisada de documentos y el seguimiento temporal de temas que aborda este problema de descubrimiento. Después de leerla, podrás seguir la evolución de distintos temas a lo largo de varios días:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/100a60a7fb85da8ab3813fd071a82c93f2c3f318-1300x650.png" style="width: 100%;" /><p><strong>Lo que descubrirás:</strong></p><ul><li>Por qué <strong>las incrustaciones de agrupación</strong> (y no las incrustaciones de recuperación) son fundamentales para descubrir temas sin una consulta.</li><li>Cómo la clasificación de centroides con sonda de densidad agrupa documentos por tema usando Elasticsearch k vecinos más cercanos (kNN) y lotes de <code>msearch</code>.</li><li>¿Cómo puede <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significanttext-aggregation"><code>significant_text</code></a> autoetiquetar clústeres para que los temas sean legibles sin entrenar un modelo?</li><li>De qué forma las cadenas temporales de temas conectan los clústeres de datos diarios para mostrar la evolución de los temas día a día.</li></ul><div style="display:none">Unknown block type "infoPanel", specify a component for it in the `components.types` option</div><p>El pipeline utiliza unos 8500 artículos de febrero de 2025 de BBC News y The Guardian como corpus de prueba. Si bien las noticias son convenientes porque tienen un comportamiento temporal claro, el patrón es aplicable si el descubrimiento de documentos es importante: revisión legal, monitoreo del cumplimiento normativo, síntesis de investigación, triage de atención al cliente.</p><p><strong>Stack:</strong></p><ul><li><a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text"><strong>Jina v5</strong></a> <strong>incrustaciones de agrupación:</strong> adaptadores específicos de Low-Rank Adaptation (LoRA) para la agrupación de temas. <a href="https://www.elastic.co/blog/elastic-jina-ai">Jina se unió a Elastic</a>, y sus modelos están disponibles de forma nativa a través del <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service (EIS)</a>.</li><li><strong>Elasticsearch:</strong> <a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN</a> escalable, etiquetado <code>significant_text</code> y almacenamiento de vectores.</li><li><a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction"><strong>DiskBBQ:</strong></a> un formato de índice vectorial basado en disco que combina <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/bbq">Better Binary Quantization (BBQ)</a> con una partición jerárquica k-medios para la aceleración aproximada de los vecinos más cercanos (ANN). Esta partición de índice es interna a la búsqueda vectorial y separada del algoritmo de agrupación con sonda de densidad utilizado en esta publicación. <code>bbq_disk</code> almacena vectores cuantificados en el disco y mantiene solo los metadatos de partición en el heap, lo que reduce drásticamente los requisitos de recursos en comparación con <code>bbq_hnsw</code>, mientras mantiene un alto nivel de recuperación.</li><li><strong>Agrupación global + vinculación temporal diaria:</strong> descubrimiento y evolución del tema.</li></ul><p><strong>Lo que necesitarás:</strong></p><ul><li>Un despliegue de Elasticsearch (Elastic Cloud, Elasticsearch Serverless o Elastic Self-Managed 8.18+/9.0+): <code>bbq_disk</code> requiere la versión 8.18 o posterior. La sección opcional de recuperación diversificada requiere 9.3+ o sin servidor.</li><li>Una <a href="https://jina.ai/embeddings/">clave de API de Jina</a>: la capa gratuita incluye 10 millones de tokens, lo que cubre la pipeline de agrupación del núcleo (~4.25 millones de tokens). La comparación opcional entre recuperación y agrupación usa una segunda pasada de incrustación.</li><li>Una <a href="https://bonobo.capi.gutools.co.uk/register/developer">clave API de Guardian</a> (gratis).</li></ul><h2>Configuración</h2><p>Instala los paquetes requeridos:</p><pre><code>pip install elasticsearch pandas numpy plotly umap-learn python-dotenv pydantic-settings datasets requests</code></pre><p>Opcional (solo si ejecutas los asistentes de raspado desde este repositorio):</p><pre><code>pip install beautifulsoup4</code></pre><p>Luego configura las claves API en un archivo <code>.env</code> en la raíz del proyecto:</p><pre><code>ELASTIC_CLOUD_ID=your-cloud-id        # or ELASTIC_HOST=https://...
ELASTIC_API_KEY=your-api-key
JINA_API_KEY=your-jina-key
GUARDIAN_API_KEY=your-guardian-key</code></pre><p>Este cuaderno llama a <code>load_dotenv(override=True)</code>, por lo que los valores locales <code>.env</code> tienen prioridad.</p><pre><code>Connected to Elasticsearch</code></pre><h2>Parte 1: agrupación de descubrimiento - ¿Por qué agrupar incrustaciones?</h2><p>La mayoría de las búsquedas vectoriales usan <strong>incrustaciones de recuperación</strong> entrenadas para hacer coincidir una <em>consulta</em> con <em>documentos</em> relevantes. Eso es ideal para la búsqueda, pero no para el descubrimiento. Cuando quieras encontrar qué temas existen en un corpus sin ninguna consulta, necesitas incrustaciones que agrupen documentos similares.</p><p>Jina v5 resuelve esto con <strong>adaptadores de Low-Rank Adaptation (LoRA) específicos para cada tarea</strong>. LoRA agrega pequeñas actualizaciones de bajo rango a las capas internas específicas mientras mantiene la mayoría de los pesos del modelo base congelados, por lo que el comportamiento del modelo se desplaza hacia una tarea específica sin repetir el entrenamiento completo. El mismo modelo base produce diferentes incrustaciones según el parámetro <code>task</code>:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Tarea</td><td style="border: 1px solid #ccc; padding: 4px;">Capacitado para</td><td style="border: 1px solid #ccc; padding: 4px;">Caso de uso</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">retrieval.passage</td><td style="border: 1px solid #ccc; padding: 4px;">Coincidencia búsqueda-documento</td><td style="border: 1px solid #ccc; padding: 4px;">Búsqueda, Retrieval-Augmented Generation (RAG)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">agrupación</td><td style="border: 1px solid #ccc; padding: 4px;">Agrupación de temas (optimizada para clústeres estrechos)</td><td style="border: 1px solid #ccc; padding: 4px;">Descubrimiento, categorización</td>
                    </tr>
              </tbody>
            </table>
          <p>El adaptador de agrupación está entrenado para hacer que los documentos sobre el mismo tema estén <em>más cerca</em> en el espacio de incrustaciones y que los documentos sobre temas diferentes estén <em>más separados</em>. La comparación visual a continuación muestra la diferencia de forma concreta.</p><h3>Recuperación frente a agrupación: una comparación visual</h3><p>Para ver la diferencia, se incrusta una muestra de documentos con ambos tipos de tareas. La agrupación se realiza en el espacio de incrustación original de 1024 dimensiones; Uniform Manifold Approximation and Projection (UMAP) se usa solo para proyectar esas incrustaciones en 2D para su visualización. UMAP preserva la estructura de vecindad local, por lo cual es útil para comparar la separación entre clústeres.</p><p>A continuación, se muestra la misma muestra de 480 documentos con ambos tipos de tareas y proyectada en 2D con UMAP. Busca grupos de colores más compactos y mejor diferenciados en el panel de agrupación.</p><pre><code>    Full dataset: 8,495 articles
    Sources: guardian: 5749, bbc: 2746
    Date range: 2025-02-01 to 2025-02-28


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


    Clustering embeddings: 480
    Retrieval embeddings:  480


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


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


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


Indexed 8,495 docs into 28 daily indices


Temporal links found: 808 in 145.4s

Strongest links:
  2025.02.01 'league | arsenal | premier' -> 2025.02.02 'league | season | striker'  (100%)
  2025.02.03 'league | striker | loan' -> 2025.02.04 'league | striker | season'  (100%)
  2025.02.03 'score | operator | gedling' -> 2025.02.04 'league | striker | season'  (100%)
  2025.02.12 'playoff | leg | bayern' -> 2025.02.13 'league | players | injury'  (100%)
  2025.02.14 'league | injury | football' -> 2025.02.15 'league | premier | football'  (100%)
  2025.02.18 'russia | ukraine | talks' -> 2025.02.19 'saudi | russia | arabia'  (100%)
  2025.02.18 'football | league | bayern' -> 2025.02.19 'league | manchester | players'  (100%)
  2025.02.21 'league | premier | manchester' -> 2025.02.22 'game | players | defeat'  (100%)
  2025.02.21 'rugby | calcutta | brilliant' -> 2025.02.22 'game | players | defeat'  (100%)
  2025.02.26 'metals | kyiv | ukrainian' -> 2025.02.27 'ukraine | russia | talks'  (100%)</code></pre><p>Una fracción kNN del 100 % significa que cada documento muestreado del clúster de origen llegó al mismo clúster de destino, el vínculo entre días más fuerte posible. La mayoría de los vínculos anteriores están relacionados con el fútbol, lo que tiene sentido: la cobertura de la Premier League se ejecuta a diario con alta consistencia temática.</p><p>El enlace <code>score | operator | gedling</code> → <code>league | striker | season</code> es un ejemplo de un clúster de fútbol local de nicho (Gedling es un club de liga no profesional) que se integra en el clúster más amplio de la Premier League al día siguiente, una consecuencia natural de la reagrupación diaria a diferentes niveles de granularidad.</p><h3>Construyendo cadenas de historias</h3><p>Una cadena de temas es una secuencia de agrupaciones vinculadas en días consecutivos.</p><p>Los enlaces individuales por pares indican que el clúster &quot;Política del Reino Unido&quot; del lunes se conecta con el del martes. Las cadenas revelan la evolución completa: una historia que comienza el lunes, evoluciona a lo largo de la semana y se desvanece el viernes.</p><p>Las cadenas se construyen de forma voraz a partir de enlaces con una fracción kNN ≥ 0.4, lo que significa que al menos el 40 % de los documentos muestreados del clúster de origen terminaron en un único clúster de destino. Comenzando desde el clúster más antiguo, el algoritmo siempre sigue el enlace saliente más fuerte.<br/></p><pre><code>    Strong links (kNN fraction >= 0.4): 244
    Story chains spanning 3+ days: 18
      Chain 1: 'ukrainian | kyiv | eastern' (19 days: Feb 3 → Feb 21)
      Chain 2: 'playing | opposition' (19 days: Feb 10 → Feb 28)
      Chain 3: 'tadhg | maro | cadan' (10 days: Feb 1 → Feb 10)
      Chain 4: 'invade | china | putin' (8 days: Feb 21 → Feb 28)
      Chain 5: 'elected | labour | leader' (7 days: Feb 12 → Feb 18)
      Chain 6: 'film | swift | awards' (6 days: Feb 2 → Feb 7)
      Chain 7: 'amendment | termination | reporting' (6 days: Feb 12 → Feb 17)
      Chain 8: 'officers | scene | police' (5 days: Feb 1 → Feb 5)</code></pre><p>La cadena más larga rastrea la cobertura Ucrania-Rusia durante 19 días consecutivos, lo cual no es sorprendente dado el sostenido nivel de intensidad geopolítica en febrero de 2025. El segundo más largo sigue al fútbol de la Premier League durante 19 días del mes. Las cadenas más cortas capturan la temporada de premios (cine/premios, seis días), el rugby de las Seis Naciones (10 días) y la cobertura del liderazgo político del Reino Unido (siete días). Cada cadena representa una evolución temática que el algoritmo descubrió puramente a partir de la similitud de incrustaciones a través de índices diarios.</p><h3>Sankey: visualizando el flujo de la historia</h3><p>Un diagrama de Sankey es una visualización de un flujo en la que el grosor de los enlaces indica la intensidad de la conexión. Aquí, cada banda vertical es un día, cada nodo es un clúster diario (dimensionado por el recuento de documentos) y cada ruta de color traza una cadena de temas a lo largo del tiempo. El ancho del enlace codifica la fuerza de superposición de kNN: los enlaces más gruesos significan que más documentos muestreados llegaron al clúster de destino. Los colores son consistentes por cadena, así que una sola ruta de color de izquierda a derecha se lee como la progresión de un tema.</p><p>Por ejemplo, la cadena Ucrania-Rusia (que se ve como una de las rutas más largas) fluye de manera continua desde principios de febrero hasta la tercera semana, con enlaces que se mantienen gruesos, lo que indica una fuerte continuidad temática a lo largo de los días.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/100a60a7fb85da8ab3813fd071a82c93f2c3f318-1300x650.png" style="width: 100%;" /><p><em>Cadenas temporales de temas que se desarrollan a lo largo de febrero de 2025. Cada camino coloreado es un tema que persiste a lo largo de los días; el ancho del enlace indica la fuerza de superposición kNN.</em></p><h2>Qué ofrece este enfoque</h2><p>Esta guía cubrió una pipeline completa de agrupación de documentos sin supervisión desarrollada sobre Elasticsearch:</p><ol><li><strong>Agrupar incrustaciones</strong>: los adaptadores específicos de la tarea de Jina v5 producen incrustaciones optimizadas para la agrupación por temas, no solo para la coincidencia entre consulta y documento.</li><li><strong>Agrupación de descubrimiento global</strong>: agrupar el mes completo en un índice maximiza el descubrimiento de temas entre días.</li><li><strong>Clasificación de centroides con densidad probada</strong>: muestrea el 5 %, sondea densidad vía <code>msearch</code> kNN, selecciona semillas diversas de alta densidad, clasifica todos los documentos contra los centroides. Elasticsearch maneja el cómputo pesado; solo la selección de semillas se ejecuta del lado del cliente (~0.01 s).</li><li><a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significanttext-aggregation"><strong><code>significant_text</code></strong></a> <strong>etiquetado</strong>: las pruebas de significancia producen etiquetas de clúster significativas sin ningún modelo de ML o anotación manual. Los clústeres que no producen términos significativos son incoherentes y se degradan a ruido: una barrera de calidad integrada.</li><li><strong>Vinculación temporal de temas</strong>: los índices diarios y el rastreo de muestras y consultas de kNN entre índices permiten rastrear cómo evolucionan los temas a lo largo del tiempo.</li></ol><p><strong>Conclusiones clave:</strong></p><ul><li>El tipo de tarea de incrustación es importante: las incrustaciones de agrupación producen grupos temáticos notablemente más compactos.</li><li>Elasticsearch puede funcionar tanto como capa de almacenamiento <em>como</em> motor de agrupación mediante <a href="https://www.elastic.co/docs/solutions/search/vector/knn">búsqueda kNN</a>.</li><li>La clasificación de centroides con sonda de densidad mantiene casi todos los datos del lado del servidor y produce clústeres con tamaños naturales determinados por la densidad de espacio de incrustaciones.</li><li><code>significant_text</code> es rápido, interpretable y efectivo tanto para el etiquetado automático como para el control de calidad.</li></ul><p><strong>Este enfoque es útil en los siguientes casos:</strong></p><ul><li>Tienes texto con fecha y quieres descubrir un tema sin datos de entrenamiento etiquetados.</li><li>Deseas una pila para almacenamiento, búsqueda de vectores, etiquetado y vinculación temporal.</li></ul><p><strong>Extensiones para explorar:</strong></p><ul><li>Agrupación de varios períodos (resúmenes semanales y mensuales).</li><li>Ingesta en tiempo real con asignación incremental de clúster.</li><li>Resúmenes de clústeres generados por LLM usando los términos de significant_text como semillas.</li><li>A mayor escala, los centroides de K-Medios obtenidos mediante muestreo pueden servir como semillas de inicio rápido para la agrupación basada en densidad, lo que reduce el costo de la fase de exploración.</li></ul><h2>Pruébalo tú mismo</h2><p>Sustituye el corpus de documentos con marcas de tiempo por el tuyo; cualquier colección de texto con fechas funciona con este pipeline. El cuaderno completo y el código de soporte están disponibles en el <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/unsupervised-document-clustering-elasticsearch-jina-embeddings">repositorio complementario</a>.</p><ul><li><a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs"><strong>Activa una prueba gratuita de Elastic Cloud</strong></a>: crea un clúster administrado con soporte para <code>bbq_disk</code> en cuestión de minutos.</li><li><a href="https://www.elastic.co/elasticsearch/serverless"><strong>Prueba Elasticsearch Serverless</strong></a>: sin gestión de clústeres, escala automáticamente y brinda soporte para todo en este tutorial.</li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Por qué la búsqueda en el comercio electrónico necesita gobernanza]]></title>
            <description><![CDATA[Conoce por qué la búsqueda de ecommerce queda corta sin gobernanza, y cómo una capa de control garantiza resultados predecibles e impulsados por la intención, a la vez que mejora la recuperación.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/ecommerce-search-governance-improve-retrieval</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/ecommerce-search-governance-improve-retrieval</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Alexander Marquardt, Honza Král, Taylor Roy]]></dc:creator>
            <pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3793259b01a5653a7520393a2f006610de0d21e7-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Los minoristas de comercio electrónico deben gestionar distintos tipos de consultas muy diferentes dentro del mismo sistema. Un comprador que busca “naranjas” espera la fruta, no productos que contengan la palabra “naranja”, como el jugo de naranja o la mermelada de naranja, y no productos cítricos semánticamente relacionados. Un comprador que busca un “regalo para el abuelo al que le gusta lo dulce” necesita descubrimiento semántico, no coincidencia literal de palabras clave.</p><p>La <em>recuperación léxica</em> (coincidencia de texto), la <em>recuperación semántica</em> (coincidencia de conceptos) ni la <em>recuperación híbrida</em> (combinación de señales léxicas y semánticas) resuelven estos problemas por sí solas. La recuperación léxica puede arrojar cualquier resultado que contenga la palabra “naranjas”, mientras que la recuperación semántica pura en una consulta de alta intención como “naranjas” puede ampliarse hacia elementos relacionados como limones o toronjas. La recuperación híbrida combina estas señales léxicas y semánticas, pero aún no determina si esta consulta debe considerarse navegacional, qué restricciones deben aplicarse o qué políticas comerciales deben implementarse. La brecha no es la tecnología de recuperación en sí; es la ausencia de una capa de gobernanza que entienda de qué tipo de consulta se trata y qué restricciones deben implementarse antes de que comience la recuperación.</p><p>En este blog, abordamos la gobernanza de la búsqueda en el comercio electrónico, su relevancia y cómo garantizar una recuperación predecible y precisa con una capa de control.</p><h2>Qué significa la gobernanza en la búsqueda en el comercio electrónico</h2><p><em>Gobernanza</em>, en este contexto, significa introducir una capa de decisión entre la consulta del usuario y el motor de recuperación. Esta capa realiza las siguientes funciones:</p><ul><li>Clasifica la intención de la consulta: ¿se trata de navegación (&quot;naranjas&quot;) o descubrimiento (&quot;regalo para el abuelo&quot;)?</li><li>Aplica restricciones comerciales: ¿qué límites de categoría, reglas de elegibilidad, restricciones de disponibilidad o políticas de comercialización se aplican?</li><li>Apunta hacia la estrategia adecuada: ¿debería usar recuperación léxica, recuperación semántica o híbrida?</li></ul><p>Una capa de gobernanza determina qué método de recuperación debe emplearse para cada consulta, qué restricciones deben aplicarse y qué políticas empresariales deben implementarse antes de que comience la recuperación. Es importante no confundir la gobernanza con la recuperación híbrida: la recuperación híbrida es una estrategia que combina señales léxicas y semánticas, mientras que la gobernanza es la capa de decisión previa que determina si deben usarse señales léxicas, semánticas o híbridas.</p><h2>Situación actual: la implementación &quot;espagueti&quot; de la capa de aplicación</h2><p>Hoy en día, muchos minoristas intentan resolver esto agregando lógica directamente en la capa de aplicación. A menudo resulta en <em>código espagueti</em>, es decir, miles de líneas de afirmaciones “si-entonces” codificadas de forma rígida, regex y plantillas de búsqueda complejas.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f532b099ee103458e15563a711dae92952f8df02-1024x765.png" style="width: 100%;" /><p>Este enfoque puede proporcionar los resultados de búsqueda deseados como se muestra arriba; sin embargo, crea una fricción operativa significativa:</p><ul><li><strong>Dependencia de ingeniería:</strong> los usuarios empresariales y los comercializadores no pueden modificar el comportamiento de búsqueda sin tickets de ingeniería y largos ciclos de despliegue que a menudo abarcan varias semanas.</li><li><strong>Fragmentación:</strong> la lógica de búsqueda queda dispersa entre el código de la aplicación y las plantillas de búsqueda, y es difícil de explicar o auditar, lo que vuelve su evolución arriesgada.</li></ul><p>Incluso cuando los equipos reconocen la necesidad de enrutamiento, el debate a menudo se centra en la pregunta equivocada: qué método de recuperación elegir.</p><h2>La falsa elección: léxico vs. semántico vs. híbrido</h2><p>Los equipos de búsqueda suelen enmarcar el desafío como una elección estratégica de recuperación: léxico/BM25 frente a semántico/vectores frente a híbrido. Ese encuadre es comprensible (los métodos de recuperación importan), pero pasa por alto el modo de fallo más común en despliegues reales: usar un único enfoque de recuperación para todas las consultas dará resultados subóptimos.</p><p>La búsqueda comercial es una mezcla de intenciones fundamentalmente diferentes:</p><ul><li><strong>Navegación determinista y de alta intención</strong> (&quot;naranjas&quot;, &quot;leche&quot;, &quot;chocolate sin maní&quot;, &quot;aceite de oliva barato&quot;).</li><li><strong>Descubrimiento exploratorio</strong> (&quot;chaqueta para hacer senderismo en las montañas&quot;, &quot;regalo para una niña o niño de 12 años a quien le gusta la robótica&quot;).</li><li><strong>Restricciones operativas</strong> (disponibilidad, tamaño, precio, color).</li><li><strong>Merchandising y campañas</strong> (impulsar, relegar, campañas estacionales).</li></ul><p>Cuando el sistema enruta todo esto a través de la misma estrategia de recuperación, los resultados a menudo son sistemáticamente incorrectos de manera predecible porque el modelo operativo carece de gobernanza. Cuando los equipos no se dan cuenta de que esto es una falla en la gobernanza, responden con la única herramienta que tienen: ajustar más el sistema.</p><h2>Por qué “afinar la relevancia” puede volverse algo cíclico</h2><p>Sin una capa de enrutamiento, la &quot;relevancia&quot; suele convertirse en una lista de pendientes interminable:</p><ul><li>¿Por qué esta búsqueda muestra accesorios por encima del producto núcleo?</li><li>¿Por qué esta búsqueda principal de repente comenzó a mostrar elementos relacionados?</li><li>¿Por qué cambiaron los resultados después de que agregamos sinónimos, ajustamos los analizadores o habilitamos el híbrido?</li><li>¿Por qué el equipo de negocios necesita un lanzamiento de ingeniería para arreglar una consulta única?</li></ul><p>Los equipos responden con más ajustes: más sinónimos, más mejoras, más experimentos de reordenamiento, más excepciones en el código de la aplicación. Esto puede funcionar por un tiempo, pero a menudo produce un comportamiento frágil porque el sistema aún carece de una capa de decisión explícita para determinar el tipo de consulta y aplicar las restricciones adecuadas antes de la recuperación.</p><h2>La anatomía de la intención del comercio electrónico: cabeza y cola</h2><p>En esta sección, usamos “cabeza” y “cola” como una notación práctica para patrones comunes de búsqueda navegacional y exploratoria en el comercio electrónico. En el mundo real, muchas búsquedas contienen aspectos de ambos:</p><h3>Consultas de cabeza (intención determinista)</h3><p>Estas son consultas directas y de navegación en las que el usuario sabe exactamente lo que quiere:</p><ul><li>Intención de un solo artículo (&quot;naranjas&quot;, &quot;leche&quot;, &quot;pan&quot;).</li><li>Marcas exactas o familias de productos (&quot;iPhone 15 Pro&quot;, &quot;Coca Light&quot;).</li><li>Referencias, números de modelo, tallas (&quot;ABC123&quot;, &quot;air max 270&quot;).</li></ul><p>Para estas consultas, la recuperación léxica puede ocuparse de la correspondencia de tokens (palabras coincidentes), pero la empresa también espera respetar las restricciones, arrojar clasificaciones predecibles y tener resultados controlables. Un comerciante necesita asegurarse de que una consulta se resuelva dentro de los límites correctos de la categoría, respete la elegibilidad y muestre prioridades específicas del negocio.</p><p>Se necesita una estructura de gobernanza para garantizar el cumplimiento de la resolución prevista. Por ejemplo, las “naranjas” deben mapearse a la categoría de productos, no a jugo de naranja, mermelada de naranja o soda de naranja.</p><h3>Consultas de cola (descubrimiento exploratorio)</h3><p>Estas son búsquedas descriptivas y ricas en intención donde los compradores están explorando:</p><ul><li>&quot;Regalo para el abuelo que tiene debilidad por lo dulce&quot;</li><li>&quot;Chaqueta para senderismo en la montaña&quot;</li><li>&quot;Zapatos para estar de pie todo el día&quot;</li></ul><p>La recuperación léxica a menudo tiene dificultades en este punto. La búsqueda semántica destaca porque puede conectar el concepto de la consulta con el producto, incluso cuando las palabras no coinciden. Pero la recuperación semántica por sí sola no suele ser suficiente. Las consultas reales a menudo requieren que se apliquen restricciones, independientemente del método de recuperación que se utilice.</p><h2>Las restricciones son ortogonales al método de recuperación</h2><p>Aplicar restricciones a la recuperación semántica no significa que sea una <em>búsqueda híbrida</em>. Son conceptos ortogonales. Las restricciones, como los filtros y las mejoras (boosts) en Elasticsearch, se pueden aplicar a cualquier recuperación léxica, semántica o híbrida. El desafío es decidir cómo interpretar la consulta, qué restricciones se deben aplicar y qué estrategia de recuperación se debe usar.</p><p>A continuación se muestran algunos ejemplos de consultas que combinan la recuperación con restricciones rígidas:</p><ul><li><strong>Naranjas:</strong> recuperación léxica para “naranjas” más una restricción de categoría, como “frutas” o “productos”, eliminando mermelada de naranja, jugo de naranja y soda de naranja.</li><li><strong>Frutas ricas en vitamina C por menos de $4:</strong> búsqueda semántica basada en la intención nutricional, además de filtros que limitan los resultados a la categoría de frutas y a productos por menos de $4.</li><li><strong>Zapatos cómodos para el trabajo:</strong> búsqueda semántica basada en la intención contextual, además de una restricción de categoría que limita los resultados a los zapatos.</li></ul><p>Estas consultas no se pueden manejar con un solo enfoque:</p><ul><li>La <strong>recuperación léxica pura</strong> a menudo es insuficiente en este caso porque frases como “alto contenido de vitamina C” o “cómodo” pueden no existir como atributos limpios y estructurados. Puede que sea necesario inferirlos a partir de descripciones de productos, reseñas o especificaciones.</li><li><strong>La recuperación semántica pura</strong> tampoco es suficiente porque, sin restricciones explícitas, una consulta como “frutas con alto contenido de vitamina C” podría ampliarse hacia suplementos vitamínicos, bebidas con sabor a fruta o vegetales con alto contenido de vitaminas fuera de la categoría y el rango de precios previstos.</li></ul><p>Una capa de gobernanza determina si una consulta necesita recuperación léxica, comprensión semántica, aplicación de restricciones o alguna combinación de estas. Sin esta capa, los equipos de comercio electrónico pueden caer en lo siguiente:</p><ul><li><strong>Restricción excesiva:</strong> uso de recuperación léxica para solicitudes semánticas (por ejemplo, &quot;regalo para el abuelo&quot;).</li><li><strong>Restricción insuficiente: </strong>emplear consultas semánticas para consultas de cabeza con alta intención (por ejemplo, &quot;naranjas&quot;).</li></ul><p>El desafío de la gobernanza es construir un sistema que pueda tomar la decisión correcta para cada clase de consulta.</p><h2>Qué sucede sin gobernanza</h2><p>El modo de falla más común es sencillo: los equipos toman la consulta del usuario sin procesar y la pasan directamente a una única estrategia de recuperación (léxica, semántica o híbrida), sin una capa de gobernanza intermedia.</p><h3>La búsqueda léxica no da el resultado esperado</h3><p>Cuando un usuario busca “naranjas”, una estrategia de recuperación léxica puede devolver cualquier cosa que contenga ese token: jugo de naranja, mermelada de naranja o soda de naranja. El sistema hizo coincidir el término correctamente, pero sin gobernanza es posible que no resuelva el contexto de compra previsto (la fruta).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/99abc7a46f9c56a26a68d0a089d7ab830b9b5568-1560x814.png" style="width: 100%;" /><h3>La recuperación semántica se amplía más allá de las limitaciones previstas</h3><p>Cuando un usuario busca “naranjas”, un sistema semántico puede recuperar elementos conceptualmente relacionados a través de conceptos de productos cercanos. El sistema puede comprender correctamente el dominio más amplio (fruta o productos), pero sin gobernanza explícita aún puede ampliarse más allá de la restricción intencionada del usuario (específicamente naranjas).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c9de86363ecbed499fe48259f47b3c5b2c26bc43-1568x796.png" style="width: 100%;" /><h3>La brecha es la gobernanza</h3><p>Lo que se requiere es una capa de decisión previa que determine la intención de la consulta y aplique las restricciones adecuadas antes de que comience la recuperación. Esto soluciona problemas como los siguientes:</p><ul><li>Elementos similares o relacionados que aparecen junto a lo que el usuario realmente quería.</li><li>Límites difusos de categorías (&quot;bebidas&quot; en vez de &quot;frutas&quot;).</li><li>Incapacidad para implementar mejoras o campañas estacionales.</li><li>Resultados impredecibles e inexplicables.</li></ul><h2>Comprensión de intenciones y enrutamiento: el plano de control necesario</h2><p>Un sistema de búsqueda gestionada incorpora un plano de control ligero antes de la recuperación (antes de ejecutar una consulta en Elasticsearch). El control se explicará en detalle en las partes 3 y 4 de esta serie de blogs; por ahora, solo abarcaremos lo que puede hacer pero no cómo funciona:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/88c3d0f9731a128d73a765dcdffed897308110a6-2680x766.png" style="width: 100%;" /><p>Un plano de control puede detectar la intención, aplicar políticas comerciales y garantizar la estrategia de recuperación apropiada de la siguiente manera:</p><p><strong>1. Detectar señales de intención</strong></p><ul><li>¿Es probable que esta búsqueda sea de navegación en vez de descubrimiento?</li><li>¿Es una búsqueda principal conocida (leche, pan, bananas)?</li><li>Existe una interpretación conocida de producto, marca o categoría (por ejemplo, “naranjas” debería resolverse como fruta).</li><li>¿La consulta tiene un patrón tipo SKU?</li><li>¿La consulta se enmarca dentro de una campaña activa o una política estacional (por ejemplo, durante la Navidad, mejorar los resultados relacionados con el pavo)?</li><li>¿La consulta implica restricciones (categoría, atributos, exclusiones, precio, tamaño o color)?</li></ul><p><strong>2. Aplicar políticas empresariales y de gobernanza</strong></p><ul><li>Primero aplica restricciones deterministas (categoría, atributo, negación, disponibilidad).</li><li>Aplicar políticas activas de merchandising (mejorar/enterrar/fijar/anular).</li><li>Resuelve los conflictos con reglas de precedencia (por ejemplo, anulaciones de campaña frente a políticas globales).</li></ul><p><strong>3. Dirige a la estrategia de recuperación adecuada</strong></p><ul><li>Léxico (rápido, determinista) para consultas de navegación o de alta intención.</li><li>Recuperación semántica para búsquedas de descubrimiento real.</li><li>Híbrido en el que la combinación de señales léxicas y semánticas aporta valor añadido dentro de unos límites empresariales explícitos.</li></ul><p>En la práctica, la salida del plano de control no es simplemente “usar híbrido” o “usar semántico”. Es un plan de recuperación regulado: una interpretación de la intención del comprador, las restricciones y políticas que deben aplicar, y la estrategia de recuperación que debe ejecutar. Unos pocos ejemplos sencillos lo demuestran:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Consulta de comprador</td><td style="border: 1px solid #ccc; padding: 4px;">Interpretación regulada</td><td style="border: 1px solid #ccc; padding: 4px;">Ejemplo de plan de recuperación</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">“chocolate sin maní”</td><td style="border: 1px solid #ccc; padding: 4px;">Consulta orientada al producto con una restricción de exclusión estricta</td><td style="border: 1px solid #ccc; padding: 4px;">Recuperación léxica para chocolate más un filtro de exclusión para productos que contienen maní</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">“aceite de oliva barato”</td><td style="border: 1px solid #ccc; padding: 4px;">Búsqueda de producto o categoría con restricción de precio</td><td style="border: 1px solid #ccc; padding: 4px;">Recuperación léxica para aceite de oliva más un filtro de precio limitado al umbral del minorista para ser económico</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">“Fruta con alto contenido de vitamina C por menos de $4”</td><td style="border: 1px solid #ccc; padding: 4px;">Consulta de descubrimiento que requiere comprensión semántica y restricciones estrictas</td><td style="border: 1px solid #ccc; padding: 4px;">Búsqueda semántica basada en la intención nutricional, limitada a la categoría de frutas y filtrada a productos con un precio inferior a 4 dólares</td>
                    </tr>
              </tbody>
            </table>
          <p>Un plano de control selecciona la política y la estrategia de recuperación correctas para cada búsqueda de forma consistente, predecible y a escala. Esto hace que los métodos de recuperación avanzados sean más predecibles en producción porque las restricciones alineadas con la intención se aplican primero y las decisiones de enrutamiento son explícitas en lugar de implícitas.</p><h2>Cómo esto se relaciona con otros enfoques</h2><p>Algunos equipos usan modelos de incrustación mejorados para captar mejor la semántica de los productos, lo que puede mejorar de forma considerable la calidad de la búsqueda semántica. Otros utilizan enfoques de reclasificación, como <a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr">Learning To Rank (LTR)</a>, para optimizar el orden de los resultados basado en la participación o señales de negocio después de la recuperación. Ambos son valiosos y a menudo complementarios. Las incrustaciones superiores mejoran la coincidencia de similitudes. La reclasificación mejora el ordenamiento entre los candidatos recuperados.</p><p>La gobernanza aborda un aspecto diferente del problema: se sitúa en una etapa previa a la recuperación. Decide qué estrategia de recuperación utilizar (por ejemplo, léxica, semántica o híbrida), qué restricciones deterministas se requieren y qué consultas deben combinar varias políticas de negocio.</p><h2>Qué aporta un plano de control gestionado</h2><p>Una vez que se establece una capa de gobernanza, el modelo operativo cambia de forma rotunda. Las consultas críticas para los ingresos se vuelven predecibles. Los equipos de negocio pueden actualizar el comportamiento de búsqueda sin esperar los ciclos de lanzamiento de ingeniería. Y los métodos de recuperación avanzados (como los semánticos y los híbridos) pueden adoptarse de forma gradual, con mecanismos de enrutamiento y controles de seguridad, en vez de como un interruptor global de encendido o apagado.</p><p>La siguiente publicación de esta serie explora cómo se ve ese modelo operativo en la práctica y por qué puede ser tan importante como la tecnología de recuperación subyacente.</p><p>Si un comerciante tiene que abrir un ticket de Jira y esperar un despliegue para corregir una búsqueda crítica para los ingresos, el cuello de botella no es el motor; es el modelo operativo. La búsqueda moderna de comercio electrónico necesita una manera de traducir la intención comercial en un comportamiento de búsqueda controlado y auditable de manera rápida y segura, sin dejar de usar recuperación avanzada cuando aporta un valor medible.</p><h2>Lo que se viene</h2><p>Los patrones explorados en esta serie operan río arriba de la recuperación: traduciendo la intención del negocio en la estrategia de búsqueda correcta antes de que comience la generación de la consulta. En la siguiente publicación, pasamos del problema técnico al operativo: qué ocurre cuando los equipos de negocio pueden cambiar el comportamiento de búsqueda sin un despliegue de ingeniería, y por qué la gobernanza hace que eso sea seguro.</p><h2>Pon en práctica la búsqueda gobernada de comercio electrónico</h2><p>Los cuellos de botella de ingeniería, la lógica frágil de la capa de aplicación y los resultados de búsqueda impredecibles son problemas que Elastic Services puede ayudarte a resolver en los proyectos de servicios de comercio electrónico empresarial. La arquitectura del plano de control gobernado que se describe en esta serie fue desarrollada por Elastic Services Engineering.</p><p>Si tu equipo está dedicando recursos de ingeniería a convertir las solicitudes de merchandising en cambios de código, o si la lista de tareas pendientes relacionadas con la relevancia de las búsquedas parece no reducirse nunca, podemos ayudarte a evaluar tu arquitectura actual y a trazar un plan para lograr un sistema de búsqueda controlado y editable por el equipo de negocios. Ponte en contacto con <a href="https://www.elastic.co/consulting">Elastic Services</a>.</p><h2>Únete a la discusión</h2><p>¿Tienes preguntas sobre la gestión de búsquedas, las estrategias de recuperación o la arquitectura de búsqueda en el comercio electrónico? Únete a la <a href="https://discuss.elastic.co/">conversación general de la comunidad de Elastic</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cómo construir aplicaciones de IA con agentes con Mastra y Elasticsearch]]></title>
            <description><![CDATA[Aprende a construir aplicaciones de IA agéntica usando Mastra y Elasticsearch a través de un ejemplo práctico.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/build-agentic-ai-applications-mastra-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/build-agentic-ai-applications-mastra-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Enrico Zimuel]]></dc:creator>
            <pubDate>Wed, 08 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ab30baf2f908534840c5d71a46705773807baf54-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>En este artículo, haremos un recorrido por cómo usar el marco de trabajo <a href="https://mastra.ai/">Mastra</a> TypeScript para crear aplicaciones agénticas que interactúen con <a href="https://www.elastic.co/elasticsearch">Elasticsearch</a>.</p><p>Recientemente, contribuimos al proyecto de <a href="https://github.com/mastra-ai/mastra">mastra-ai/mastra</a> open source agregando soporte para Elasticsearch como base de datos vectorial. Con esta nueva característica, puedes usar Elasticsearch de forma nativa en Mastra para almacenar incrustaciones. Además de los vectores, Elasticsearch ofrece un conjunto de funciones avanzadas para satisfacer todas tus necesidades de ingeniería de contexto (por ejemplo, <a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">búsqueda híbrida y reordenamiento</a>).</p><p>En este artículo, se detalla la creación de un agente para implementar una arquitectura de Retrieval-Augmented Generation (RAG) con Elasticsearch. Te mostraremos un proyecto de demostración en el que se utiliza un enfoque agéntico para interactuar con un corpus de datos de películas de ciencia ficción almacenados en Elasticsearch. El proyecto está disponible en <a href="https://github.com/elastic/mastra-elasticsearch-example">elastic/mastra-elasticsearch-example</a>.</p><h2>Mastra</h2><p>Mastra es un marco de trabajo de TypeScript para crear aplicaciones de IA agéntica.</p><p>Una estructura de proyecto en Mastra se ve así:</p><pre><code>src/
├── mastra/
│   ├── agents/
│   │   └── weather-agent.ts
│   ├── tools/
│   │   └── weather-tool.ts
│   ├── workflows/
│   │   └── weather-workflow.ts
│   ├── scorers/
│   │   └── weather-scorer.ts
│   └── index.ts
├── .env.example
├── package.json
└── tsconfig.json</code></pre><p>En Mastra, puedes crear <a href="https://mastra.ai/docs/agents/overview">agentes</a>, <a href="https://mastra.ai/docs/agents/using-tools">herramientas</a>, <a href="https://mastra.ai/docs/workflows/overview">flujos de trabajo</a> y <a href="https://mastra.ai/docs/evals/overview">puntajes</a>.</p><p>Un <strong>agente</strong> es una clase que acepta un mensaje como entrada y produce una respuesta como salida. Un agente puede usar herramientas, modelos de lenguaje a gran escala (LLM) y una memoria (figura 1).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f6aca2dcc7fcc45d25e06681649be1b2b7eb6781-706x721.png" style="width: 100%;" /><p>Las <strong>herramientas</strong> de un agente le permiten interactuar con el &quot;mundo externo&quot;, como comunicarse con una API web o hacer una operación interna, como consultar Elasticsearch. El componente de <strong>memoria</strong> es crucial para almacenar el historial de conversaciones, incluidas las entradas y salidas pasadas. Este contexto almacenado permite que el agente proporcione respuestas más informadas y relevantes a preguntas futuras mediante el uso de sus interacciones pasadas.</p><p>Los <strong>flujos de trabajo</strong> te permiten definir secuencias complejas de tareas mediante pasos claros y estructurados, en lugar de depender del razonamiento de un solo agente (figura 2). Te brindan control total sobre cómo se desglosan las tareas, cómo se mueven los datos entre ellas y qué se ejecuta y cuándo. Los flujos de trabajo se ejecutan con el motor de ejecución integrado de forma predeterminada o se pueden desplegar en <a href="https://mastra.ai/docs/deployment/workflow-runners">ejecutores de flujos de trabajo</a>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0dce161cf7891207015dc87532b5b90df1822432-880x252.png" style="width: 100%;" /><p>En Mastra, también puedes definir puntuaciones, que son pruebas automatizadas que evalúan las salidas de los agentes mediante métodos calificados por modelos, basados en reglas y estadísticos. Los evaluadores devuelven <em>puntuaciones</em>: valores numéricos (normalmente entre 0 y 1) que cuantifican qué tan bien una salida cumple con tus criterios de evaluación. Estas puntuaciones te permiten hacer un seguimiento objetivo del rendimiento, comparar diferentes enfoques e identificar áreas de mejora en tus sistemas de IA. Los evaluadores pueden personalizarse con tus propias solicitudes y funciones de puntuación.</p><h2>Elasticsearch</h2><p>Para ejecutar el proyecto de demostración, necesitamos tener una instancia de Elasticsearch en ejecución. Puedes activar una prueba gratis en <a href="https://www.elastic.co/cloud">Elastic Cloud</a> o instalarlo localmente usando el script <a href="https://github.com/elastic/start-local"><code>start-local</code></a>:</p><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre><p>Se instalarán Elasticsearch y Kibana en tu computadora y se generará una clave API que se utilizará para configurar la integración de Mastra.</p><p>La clave de API se mostrará como salida del comando anterior y se almacenará en un archivo <strong>.env</strong> en la carpeta elastic-start-local.</p><h2>Instalar y configurar la demo</h2><p>Creamos un repositorio <a href="https://github.com/elastic/mastra-elasticsearch-example">elastic/mastra-elasticsearch-example</a> que contiene el código fuente del proyecto de demostración. El ejemplo reportado en el repositorio ilustra cómo crear un agente en Mastra que implemente una arquitectura RAG para recuperar documentos de Elasticsearch.</p><p>Proporcionamos un set de datos para la demostración sobre películas de ciencia ficción. Extrajimos 500 películas de los <a href="https://www.kaggle.com/datasets/rajugc/imdb-movies-dataset-based-on-genre/versions/2?select=scifi.csv">sets de datos</a> de IMDb en Kaggle.</p><p>El primer paso es instalar las dependencias del proyecto con npm, usando el siguiente comando:</p><pre><code>npm install</code></pre><p>Luego tenemos que configurar el archivo <strong>.env</strong> que contendrá la configuración. Podemos generar este archivo copiando la estructura del archivo <strong>.env.example</strong> , usando el siguiente comando:</p><pre><code>cp .env.example .env</code></pre><p>Ahora podemos editar el archivo .env, agregando la información faltante:</p><pre><code>OPENAI_API_KEY=
ELASTICSEARCH_URL=
ELASTICSEARCH_API_KEY=
ELASTICSEARCH_INDEX_NAME=scifi-movies</code></pre><p>El nombre del índice de Elasticsearch es <strong><code>scifi-movies</code></strong>. Si quieres, puedes cambiarlo usando la variable de entorno <code>ELASTICSEARCH_INDEX_NAME</code>.</p><p>Usamos OpenAI como servicio de incrustación, lo que significa que necesitas proporcionar una clave API para OpenAI en la variable <code>OPENAI_API_KEY</code> .env.</p><p>El modelo de incrustación empleado en el ejemplo es <a href="https://developers.openai.com/api/docs/models/text-embedding-3-small">openai/text-embedding-3-small</a>, con una dimensión de incrustación de 1536.</p><p>Para generar la respuesta final, empleamos el modelo <a href="https://developers.openai.com/api/docs/models/gpt-5-nano">openai/gpt-5-nano</a> para reducir costos.</p><p>La arquitectura RAG te permite usar un modelo LLM final menos potente (y generalmente menos costoso) porque el trabajo pesado de fundamentar la respuesta está a cargo del componente de recuperación (Elasticsearch en este caso).</p><p>El LLM más pequeño solo se encarga de dos tareas principales:</p><ul><li><strong>Reformulación/incrustación de la consulta:</strong> Convertir la pregunta en lenguaje natural del usuario en una incrustación de vectores para la búsqueda semántica.</li><li><strong>Sintetizar la respuesta:</strong> Se toman los fragmentos de contexto muy relevantes y recuperados (documentos/películas) y se los sintetiza en una respuesta coherente, final y legible por seres humanos, siguiendo las instrucciones del prompt proporcionado.</li></ul><p>Dado que el proceso RAG <strong>proporciona el contexto fáctico exacto</strong> necesario para la respuesta, el LLM final no necesita ser masivo o muy complejo y no necesita poseer todo el conocimiento requerido dentro de sus propios parámetros (que es donde sobresalen los modelos grandes y caros). Esencialmente actúa como un sofisticado resumidor y formateador de texto para el contexto proporcionado por Elasticsearch, en lugar de ser una base de conocimientos completa en sí mismo. Esto permite el uso de modelos como <code>gpt-5-nano</code> para optimizar costos y latencia.</p><p>Después de la configuración del archivo .env, puedes ingestar las películas a Elasticsearch usando el siguiente comando:</p><pre><code>npx tsx src/utility/store.ts</code></pre><p>Deberías ver una salida como la siguiente:</p><pre><code>🚀 Starting ingestion of 500 movies from 500_scifi_movies.jsonl...
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 1/500 (0%) | ok:1 | fail:0 | chunks:1 | eta:19m 33s | current:Capricorn One
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 2/500 (0%) | ok:2 | fail:0 | chunks:2 | eta:10m 32s | current:Doghouse
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 3/500 (1%) | ok:3 | fail:0 | chunks:3 | eta:7m 33s | current:Dinocroc
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 4/500 (1%) | ok:4 | fail:0 | chunks:7 | eta:6m 10s | current:Back to the Future           
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 5/500 (1%) | ok:5 | fail:0 | chunks:9 | eta:5m 14s | current:The Projected Man            
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 6/500 (1%) | ok:6 | fail:0 | chunks:11 | eta:4m 41s | current:I, Robot
...
✅ Ingestion complete in 1m 46s. Success: 500, Failed: 0, Chunks: 693.</code></pre><p>El mapping del índice de películas de ciencia ficción contiene los siguientes campos:</p><ul><li><strong>incrustación</strong>, dense_vector con 1536 dimensiones, similitud del coseno.</li><li><strong>descripción</strong>, texto que contiene la descripción de la película.</li><li><strong>director</strong>, texto que contiene el nombre del director.</li><li><strong>título</strong>, texto que contiene el título de la película.</li></ul><p>Generamos las incrustaciones usando el título + la descripción. Como el título y la descripción son dos campos separados, la concatenación de ambos asegura que el vector de incrustación resultante capture tanto la identidad única y específica (título) como el contexto rico y descriptivo (descripción) de la película, lo que lleva a resultados de búsqueda semántica más precisos y completos. Esta entrada combinada le brinda al modelo de incrustación una mejor representación del contenido del documento para la búsqueda por similitud.</p><h2>Ejecuta la demostración</h2><p>Puedes ejecutar la demo con el siguiente comando:</p><pre><code>npm run dev</code></pre><p>Este comando iniciará una aplicación web en <strong>localhost:4111</strong> para acceder a Mastra Studio (figura 3).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1567e309df21a12bcf1dfef4429f82342549956c-1705x1079.png" style="width: 100%;" /><p><a href="https://mastra.ai/docs/getting-started/studio">Mastra Studio</a> ofrece una UI interactiva para crear y probar tus agentes, junto con una API REST que expone tu aplicación Mastra como un servicio local. Esto te permite comenzar a construir de inmediato sin preocuparte por la integración.</p><p>Proporcionamos un <strong>Agente Elasticsearch</strong> que emplea <a href="https://mastra.ai/reference/tools/vector-query-tool">createVectorQueryTool</a> de Mastra como herramienta para ejecutar búsqueda semántica usando Elasticsearch. Este agente emplea el enfoque RAG para buscar documentos relevantes (es decir, películas) que respondan a la pregunta del usuario.</p><p>Este agente usa el siguiente prompt:</p><pre><code>You are a helpful assistant that answers questions based on the provided context.
Follow these steps for each response:

1. First, carefully analyze the retrieved context chunks and identify key information.
2. Break down your thinking process about how the retrieved information relates to the query.
3. Draw conclusions based only on the evidence in the retrieved context.
4. If the retrieved chunks don't contain enough information, explicitly state what's missing.

Format your response as:
THOUGHT PROCESS:
- Step 1: [Initial analysis of retrieved chunks]
- Step 2: [Reasoning based on chunks]

FINAL ANSWER:
[Your concise answer based on the retrieved context]

Important: When asked to answer a question, please base your answer only on the context provided in the tool. 
If the context doesn't contain enough information to fully answer the question, please state that explicitly and stop it.
Do not add more information than what is present in the retrieved chunks.
Remember: Explain how you're using the retrieved information to reach your conclusions.</code></pre><p>Si haces clic en el menú <code>Mastra Studio &gt; Agents</code> y seleccionas <strong>Agente de Elasticsearch</strong>, puedes probar el agente usando un sistema de chat. Por ejemplo, puedes pedir información sobre películas de ciencia ficción con una pregunta como esta:</p><p><em>Encuentra 5 películas o series de TV sobre ovnis</em>.</p><p>Notarás que el agente ejecutará el vectorQueryTool. Puedes hacer clic en la herramienta invocada para ver la entrada y la salida. Al final de la ejecución, el LLM responderá tu pregunta, dado el contexto que proviene del índice de películas de ciencia ficción de Elasticsearch (figura 4).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d9998d4f687984de98845dae52d1288166abf448-1344x1071.png" style="width: 100%;" /><p>Mastra ejecuta los siguientes pasos internamente:</p><ol><li><strong>Conversión en vectores:</strong> La pregunta del usuario, <em>Encuentra 5 películas o series de televisión sobre ovnis,</em> se convierte en una incrustación de vectores mediante el modelo <code>openai/text-embedding-3-small</code> de OpenAI.</li><li><strong>Búsqueda de vectores:</strong> Esta incrustación se usa luego para hacer una búsqueda en Elasticsearch mediante una búsqueda de vectores.</li><li><strong>Recuperación de resultados:</strong> Elasticsearch devuelve una serie de 10 películas muy relevantes para la consulta (es decir, aquellas con vectores más cercanos al vector de consulta del usuario).</li><li><strong>Generación de respuestas:</strong> Las películas recuperadas y la pregunta original del usuario se envían al LLM, específicamente <code>openai/gpt-5-nano</code>. El LLM procesa esta información y genera una respuesta final, asegurando que se cumpla la solicitud del usuario para cinco resultados.</li></ol><h2>El agente de Elasticsearch</h2><p>Aquí presentamos el código fuente del agente de Elasticsearch.</p><pre><code>import { Agent } from "@mastra/core/agent";
import { ElasticSearchVector } from '@mastra/elasticsearch';
import { createVectorQueryTool } from '@mastra/rag';
import { ModelRouterEmbeddingModel } from "@mastra/core/llm";
import { Memory } from "@mastra/memory";

const es_url = process.env.ELASTICSEARCH_URL;
const es_apikey = process.env.ELASTICSEARCH_API_KEY;
const es_index_name = process.env.ELASTICSEARCH_INDEX_NAME;
const prompt = 'insert here the previous prompt';

const esVector = new ElasticSearchVector({
  id: 'elasticsearch-vector',
  url: es_url,
  auth: {
    apiKey : es_apikey
  }
});

const vectorQueryTool = createVectorQueryTool({
  vectorStore: esVector,
  indexName: es_index_name,
  model: new ModelRouterEmbeddingModel("openai/text-embedding-3-small")
});

export const elasticsearchAgent = new Agent({
  id: "elasticsearch-agent",
  name: "Elasticsearch Agent",
  instructions: prompt,
  model: 'openai/gpt-5-nano',
  tools: { vectorQueryTool },
  memory: new Memory(),
});</code></pre><p>El <strong>vectorQueryTool</strong> es la herramienta que se invoca para implementar la parte de recuperación del ejemplo de RAG. Emplea la implementación de <a href="https://mastra.ai/reference/vectors/elasticsearch">ElasticSearchVector</a> que Elastic aportó a Mastra.</p><p>El agente es un objeto de la clase agente que utiliza la herramienta vectorQueryTool, el prompt y una memoria. Como puedes ver, el código que necesitamos implementar para conectar Elasticsearch a un agente es muy sencillo.</p><h2>Conclusión</h2><p>En este artículo, se demostró la simplicidad y el poder de integrar Elasticsearch con el marco de trabajo Mastra para construir sofisticadas aplicaciones de IA agéntica. En concreto, te mostramos cómo crear un agente RAG capaz de efectuar búsquedas semánticas sobre un corpus de datos de películas de ciencia ficción indexados en Elasticsearch.</p><p>Una de las principales conclusiones es la contribución directa de Elastic al proyecto de código abierto Mastra, que ofrece compatibilidad nativa con Elasticsearch como almacén de vectores. Esta integración reduce considerablemente las barreras de acceso, como se puede ver en el código fuente del <strong>agente de Elasticsearch</strong>. Usando el <code>ElasticSearchVector</code> y <code>createVectorQueryTool</code>, la configuración completa para conectar Elasticsearch a tu agente requiere solo un número mínimo de líneas de código de configuración.</p><p>Elasticsearch ofrece varias características avanzadas para mejorar la relevancia de los resultados. Por ejemplo, la <a href="https://www.elastic.co/elasticsearch/hybrid-search">búsqueda híbrida</a> aumenta significativamente la precisión combinando la búsqueda léxica con la búsqueda de vectores. Otra característica interesante es la reclasificación con los últimos <a href="https://www.elastic.co/search-labs/tutorials/jina-tutorial/jina-reranker-v3">modelos de Jina</a>, que puede aplicarse al final de la búsqueda híbrida. Para obtener más información sobre estas técnicas, consulta los siguientes artículos de Elasticsearch Labs:</p><ul><li><a href="https://www.elastic.co/search-labs/blog/hybrid-search-elasticsearch">Búsqueda híbrida Elasticsearch</a> de Valentin Crettaz</li><li><a href="https://www.elastic.co/search-labs/blog/jina-models-elasticsearch-guide">Introducción a los modelos de Jina, su funcionalidad y usos en Elasticsearch</a> de Scott Martens</li></ul><p>También te animamos a consultar el ejemplo proporcionado y comenzar a crear tus propios agentes basados en datos con Mastra y Elasticsearch. Para más información sobre Mastra, puedes consultar la documentación oficial <a href="https://mastra.ai/docs">aquí</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Monitoreo de las vistas del dashboard de Kibana con flujos de trabajo de Elastic]]></title>
            <description><![CDATA[Conoce cómo usar flujos de trabajo de Elastic para recopilar métricas de vista del dashboard de Kibana cada 30 minutos e indexarlas en Elasticsearch, para que puedas crear análisis y vistas personalizadas sobre tus propios datos.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/monitor-kibana-dashboard-views-elastic-workflows</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/monitor-kibana-dashboard-views-elastic-workflows</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Gustavo Llermaly]]></dc:creator>
            <pubDate>Fri, 03 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0ed94ce00962287b5507f45c92ecb60fdcbf2718-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><a href="https://www.elastic.co/kibana">Kibana</a> registra cuántas veces se visualiza cada dashboard, pero esos datos no se muestran de forma nativa en ningún dashboard integrado. En este artículo, usaremos los <strong>flujos de trabajo de Elastic</strong> para recopilar automáticamente esos datos cada 30 minutos e indexarlos en Elasticsearch, para poder crear nuestras propias analíticas sobre ellos.</p><p><a href="https://www.elastic.co/docs/explore-analyze/workflows">Los flujos de trabajo de Elastic</a> son un motor de automatización integrado dentro de Kibana que te permite definir procesos multipaso usando una configuración sencilla de YAML. Cada flujo de trabajo puede activarse según una programación o un evento, o como una herramienta en <a href="https://www.elastic.co/docs/explore-analyze/ai-features/elastic-agent-builder">Elastic Agent Builder</a>, y cada paso puede llamar a las API de Kibana, consultar Elasticsearch o transformar datos.</p><p>Usaremos los recuentos de vistas del dashboard como ejemplo concreto, pero el mismo patrón aplica a cualquier métrica expuesta a través de la API de objetos guardados de Kibana.</p><h2>Requisitos previos</h2><ul><li><a href="https://www.elastic.co/cloud">Elastic Cloud</a> o un clúster <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed">autogestionado </a>ejecutando la versión 9.3</li><li><a href="https://www.elastic.co/docs/explore-analyze/workflows/get-started#workflows-prerequisites">Flujos de trabajo activados</a> (Configuración avanzada)</li></ul><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><p>Antes de crear cualquier cosa, entendamos qué datos tenemos. Kibana almacena la mayor parte de su configuración y metadatos como <a href="https://www.elastic.co/docs/api/doc/kibana/group/endpoint-saved-objects">objetos guardados</a> en un índice interno dedicado. Una de las cosas que Kibana rastrea de esta manera es el número de vistas del dashboard, mediante un tipo especial de objeto guardado llamado contadores de uso. Puedes consultarlas directamente desde las Herramientas de desarrollo:</p><pre><code>GET kbn:/api/saved_objects/_find?type=usage-counter&filter=usage-counter.attributes.domainId:"dashboard"%20and%20usage-counter.attributes.counterType:"viewed"&per_page=10000</code></pre><p>La respuesta tiene el siguiente aspecto:</p><pre><code>{
  "page": 1,
  "per_page": 10000,
  "total": 1,
  "saved_objects": [
    {
      "type": "usage-counter",
      "id": "dashboard:346f3c64-ebca-484d-9d57-ec600067d596:viewed:server:20260310",
      "attributes": {
        "domainId": "dashboard",
        "counterName": "346f3c64-ebca-484d-9d57-ec600067d596",
        "counterType": "viewed",
        "source": "server",
        "count": 1
      },
      ...
    }
  ]</code></pre><p>El campo <code>counterName</code> es el ID del dashboard y <code>count</code> es el recuento acumulado de vistas para ese dashboard en ese día específico. Kibana crea un objeto de contador por dashboard al día; puedes ver el sufijo de fecha en el ID del objeto (...viewed:server:20260310). El conteo aumenta a lo largo del día a medida que los usuarios abren el dashboard.</p><p>En lugar de replicar este modelo de documento diario en nuestro índice, crearemos un documento por cada ejecución del flujo de trabajo. Cada documento registra cuántas vistas había acumulado ese dashboard durante el día en el momento de la captura.</p><h2>Paso 2: Crear el índice de destino</h2><p>Necesitamos un índice para almacenar las snapshots de la vista del dashboard. El siguiente comando lo crea con mapeos explícitos para que podamos agregar y visualizar más tarde. Ejecuta esto en herramientas de desarrollo:</p><pre><code>PUT dashboard-views
{
  "mappings": {
    "properties": {
      "captured_at": {
        "type": "date"
      },
      "dashboard_id": {
        "type": "keyword"
      },
      "dashboard_name": {
        "type": "keyword"
      },
      "view_count": {
        "type": "integer"
      }
    }
  }
}</code></pre><p>Usar<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/keyword"><code>keyword</code></a> mapeos para ID y nombres permite <a href="https://www.elastic.co/docs/explore-analyze/query-filter/aggregations">agregaciones</a>. Usar <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/number"><code>integer</code></a> para <code>view_count</code> es un valor predeterminado seguro, ya que Kibana restablece el contador diariamente, por lo que alcanzar el límite de 32 bits (más de 2 mil millones de vistas en un solo día) no es una preocupación realista. Todavía admite operaciones numéricas, como <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-metrics-max-aggregation"><code>max</code></a>, <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-metrics-avg-aggregation"><code>avg</code></a> y <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-metrics-min-aggregation"><code>min</code></a>, entre otras.</p><h2>Paso 3: Crear el flujo de trabajo</h2><p>Ve a <strong>Stack Management &gt; Flujos de trabajo &gt; Nuevo flujo de trabajo</strong>, y pega la siguiente configuración YAML del flujo de trabajo:</p><pre><code>name: dashboard-views-ingestion
triggers:
  - type: scheduled
    with:
      every: 30m

steps:
  - name: fetch_dashboard_views
    type: kibana.request
    with:
      method: GET
      path: >-
        /api/saved_objects/_find?type=usage-counter&per_page=10000&filter=usage-counter.attributes.domainId:"dashboard"%20and%20usage-counter.attributes.counterType:"viewed"

  - name: index_each_dashboard
    type: foreach
    foreach: "{{ steps.fetch_dashboard_views.output.saved_objects }}"
    steps:
      - name: fetch_dashboard_name
        type: kibana.request
        with:
          method: GET
          path: /api/saved_objects/dashboard/{{ foreach.item.attributes.counterName }}
        on-failure:
          continue: true

      - name: index_doc
        type: elasticsearch.request
        with:
          method: POST
          path: /dashboard-views/_doc
          body:
            dashboard_id: "{{ foreach.item.attributes.counterName }}"
            dashboard_name: "{{ steps.fetch_dashboard_name.output.attributes.title }}"
            view_count: "${{ foreach.item.attributes.count | plus: 0 }}"
            captured_at: "{{ execution.startedAt | date: '%Y-%m-%dT%H:%M:%SZ' }}"</code></pre><p>En la próxima sección, hagamos un desglose del flujo de trabajo paso a paso.</p><h3>Cómo funciona el flujo de trabajo</h3><h4>Desencadenantes</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5670991d65c64ee833924225c2d375a1be868b13-325x162.png" style="width: 100%;" /><p>El flujo de trabajo se ejecuta mediante un desencadenante programado cada 30 minutos. Esto nos proporciona datos temporales sin saturar la API.</p><h4>fetch_dashboard_views</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/66eaec147c3d01c524c67cf1c7f663ac56a3259d-812x215.png" style="width: 100%;" /><p>Usa<code>kibana.request</code> para llamar a la API de objetos guardados de Kibana. No hace falta configurar la autenticación: el motor de flujos de trabajo añade automáticamente los encabezados correctos según el contexto de ejecución.</p><h4>index_each_dashboard (foreach)</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/aad45e8aed8dc81ded6260cd6199ff78dcffe3b4-1892x290.png" style="width: 100%;" /><p>Itera sobre la matriz<a href="https://www.elastic.co/docs/api/doc/kibana/group/endpoint-saved-objects"><code>saved_objects</code></a> devuelta por el paso anterior. El elemento actual en cada iteración está disponible como <code>foreach.item</code>. Dentro del bucle, ejecutamos dos pasos anidados para cada dashboard.</p><p><strong>1. <code>fetch_dashboard_name</code>:</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/db21ec5094b743018b9cd66c5052681f14c7d7e3-1999x431.png" style="width: 100%;" /><p>Resuelve el título legible para los humanos del dashboard al llamar a <code>GET /api/saved_objects/dashboard/{id}</code>. Agregamos <code>on-failure: continue: true</code> para que, si un dashboard se eliminó pero aún tiene contadores de vistas, el bucle continúe en lugar de fallar toda la ejecución.</p><p><strong>2. <code>index_doc</code>:</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f49dd0c9f0817bb1e1e5d9f4a2b05d13ef331054-1999x626.png" style="width: 100%;" /><p>Indexa cada documento usando <code>POST /dashboard-views/_doc</code> (sin un ID explícito), lo que permite que Elasticsearch genere automáticamente los ID. Esto crea un nuevo documento en cada ejecución, lo que crea un historial del número de vistas a lo largo del tiempo en lugar de sobreescribir el snapshot anterior.</p><p>Dos cosas que vale la pena destacar:</p><ul><li>El campo <code>captured_at</code> usa el filtro de fecha para formatear la marca de tiempo como <a href="https://www.iso.org/iso-8601-date-and-time-format.html">ISO 8601</a>. Sin ella, el valor sale como un texto de fechas en JavaScript, como <code>Tue Mar 10 2026 05:03:47 GMT+0000</code>, que Elasticsearch no asigna como fecha.</li><li>El <code>view_count</code> usa la sintaxis <code>${{ }}</code> con <code>| plus: 0</code> para preservar el tipo numérico. Usar <code>{{ }}</code> lo mostraría como un texto, lo que impediría realizar operaciones matemáticas en el dashboard.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6d48c8784d5df6192e8b5175e69dbab5098194bc-919x774.png" style="width: 100%;" /><p><em>La UI te permite depurar fácilmente cada uno de los pasos del flujo de trabajo.</em></p><h2>Paso 4: Crea el dashboard de estadísticas</h2><p>Una vez que el flujo de trabajo se haya ejecutado varias veces y se hayan recopilado los datos, crea un nuevo dashboard en Kibana usando la Data view de vistas del dashboard.</p><p>Algunos paneles para empezar:</p><ul><li><strong>Los dashboards más vistos:</strong> Usa un <a href="https://www.elastic.co/docs/explore-analyze/visualize/charts/bar-charts"><strong>Gráfico de barras</strong></a> con <code>dashboard_name</code> en el eje X y <code>last_value(view_count)</code> en el eje Y. Aquí se muestra el número actual de vistas diarias por dashboard.</li><li><strong>Vistas a lo largo del tiempo:</strong> usa un <a href="https://www.elastic.co/docs/explore-analyze/visualize/charts/line-charts"><strong>gráfico de líneas</strong></a> con <code>captured_at</code> en el eje X y <code>last_value(view_count)</code> en el eje Y, desglosado por <code>dashboard_name</code>. Dado que cada ejecución agrega un nuevo documento, usa el último valor para obtener el recuento máximo por cubetas de tiempo en lugar de sumar duplicados.</li><li><strong>Snapshot actual:</strong> usa una <a href="https://www.elastic.co/docs/explore-analyze/visualize/charts/tables"><strong>tabla de datos</strong></a> con el <code>captured_at</code> más reciente para mostrar los recuentos de vistas más recientes en todos los dashboards.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e245b95f67daf76a2aaf4cb9df2c75ef4cfef582-1462x747.png" style="width: 100%;" /><p>Dado que cada flujo de trabajo crea un nuevo documento, puedes filtrar por intervalo de tiempo para analizar la actividad en períodos específicos, comparar semana a semana o configurar alertas cuando un dashboard caiga por debajo de un umbral de visitas.</p><h2><strong>Conclusión</strong></h2><p>Elastic Flujos de trabajo es una buena opción para este tipo de recopilación periódica de datos porque tanto el origen (API de Kibana) como el destino (Elasticsearch) son nativos, lo que significa cero gestión de credenciales. El motor de flujo de trabajo maneja la autenticación automáticamente para los pasos <code>kibana.request</code> y <code>elasticsearch.request</code>, por lo que lo único que escribes es la lógica.</p><h2><strong>Recursos</strong></h2><ul><li><a href="https://www.elastic.co/docs/explore-analyze/workflows">Flujos de trabajo de Elastic</a></li><li><a href="https://www.elastic.co/docs/api/doc/kibana/">API de Kibana</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cuando TSDS se une a ILM: diseñar flujos de datos temporales que no rechazan los datos tardíos]]></title>
            <description><![CDATA[Cómo los límites de tiempo de TSDS interactúan con las fases de ILM; y cómo diseñar políticas que toleren métricas tardías.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/tsds-ilm-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/tsds-ilm-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Bret Wortman]]></dc:creator>
            <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e4c46e4a6f746d9c845857e80de036f5d51cd4e7-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Recientemente, migré el clúster de métricas de un cliente de “todo en el nivel caliente” a una arquitectura de caliente/frío/congelado. Fue un cambio que había realizado docenas de veces antes. En cuestión de minutos, Logstash dejó de avanzar los datos por completo.</p><p>Elasticsearch rechazaba métricas que llegaban con retraso. Esos rechazos hicieron que el pipeline se retrasara, lo que dio como resultado más datos tardíos, lo que desencadenó aún más rechazos. Finalmente, el pipeline se detuvo por completo.</p><p>Tuvimos que restaurar desde un snapshot, reindexar los datos y rediseñar el pipeline de ingesta para recuperarnos.</p><p>La causa raíz no era la gestión del ciclo de vida de los índices (ILM) en sí. Eran los flujos de datos temporales (TSDS) y cómo imponen índices de respaldo acotados en el tiempo.</p><p>TSDS puede reducir las necesidades de almacenamiento para las métricas en un 40–70 %, pero los cambios de arquitectura que hacen que TSDS sea eficiente también alteran el comportamiento de los índices con el tiempo. Esos cambios importan al diseñar políticas de ILM o cuando tus Pipelines de ingesta pueden generar datos que llegan con retraso.</p><h2>TL;DR</h2><p>Al usar TSDS:</p><ul><li>Los índices de respaldo solo aceptan documentos dentro de una ventana de tiempo específica.</li><li>Si llegan datos tardíos después de que un índice pasa a frío o congelado, Elasticsearch rechaza esos documentos o los envía al almacén de fallas, si está configurado.</li></ul><p>Regla de diseño:</p><pre><code>warm_min_age > rollover_max_age + maximum_expected_lateness</code></pre><h2>¿Qué es un flujo de datos temporales?</h2><p>Un<em> flujo de datos temporales</em> (TSDS) es un flujo de datos especializado optimizado para datos de métricas. Los datos se distribuyen de manera que los documentos relacionados se encuentren dentro de los mismos fragmentos, lo que optimiza su búsqueda y recuperación. Así es como lo hace Elasticsearch:</p><p>Cada documento contiene:</p><ul><li>Una marca de tiempo.</li><li>Campos de dimensión que identifican las series temporales.</li><li>Campos métricos que representan valores medidos.</li></ul><p>Entre los ejemplos, se incluyen los siguientes:</p><ul><li>Uso de CPU por host.</li><li>Latencia de las solicitudes por servicio.</li><li>Lecturas de temperatura por sensor.</li></ul><p><em>Las dimensiones identifican </em>lo que queremos medir, mientras que <em>las métricas representan </em>valores que cambian con el tiempo.</p><h3>Dimensiones</h3><p>Las dimensiones describen la entidad medida.</p><p>Ejemplos:</p><pre><code>host.name
service.name
container.id</code></pre><p>Los definimos en mapeos con:</p><pre><code>time_series_dimension: true</code></pre><h3>Métricas</h3><p>Las métricas representan valores numéricos y se definen mediante:</p><pre><code>time_series_metric</code></pre><p>Tipos comunes de métricas:</p><ul><li>Indicador: valores que suben y bajan.</li><li>Contador: valores que aumentan hasta que se reinician.</li></ul><p>Elastic Agent recopila principalmente métricas y datos de logs, por lo que, incluso si no has habilitado ningún índice TSDS manualmente, puedes tenerlos aún en tu clúster.</p><h3>El campo _tsid</h3><p>Elasticsearch genera internamente un valor <code>_tsid</code> a partir de campos dimensionales. Esto permite que los documentos con dimensiones idénticas se dirijan al mismo shard, lo que mejora:</p><ul><li>Compresión.</li><li>Localidad de búsqueda.</li><li>Rendimiento de agregación.</li></ul><h2>La diferencia clave: índices de respaldo limitados en el tiempo</h2><p>Los flujos de datos tradicionales siempre escriben en el índice de respaldo más reciente, llamado <em>índice de escritura</em>, pero TSDS se comporta de manera diferente.</p><p>Cada índice de respaldo TSDS tiene una ventana de tiempo definida y solo acepta documentos con <code>@timestamp</code> valores que se encuentren en esa ventana:</p><pre><code>GET _data_stream/my-metrics-data-stream


     "index_mode": "time_series",
     "time_series": {
       "temporal_ranges": [
         {
           "start": "2026-01-15T14:35:50.000Z",
           "end": "2026-03-16T11:34:40.000Z"
         }
       ]
     }</code></pre><p>Cuando se indexa un documento, Elasticsearch lo dirige al índice de respaldo responsable de esa marca de tiempo, lo que significa que, a diferencia de los índices tradicionales, un TSDS puede escribir en varios índices de respaldo al mismo tiempo.</p><p>Por ejemplo:</p><ul><li>Datos en tiempo real → índice más reciente.</li><li>Datos tardíos → índice anterior que abarca ese intervalo de tiempo.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/31af2bb3b3dc24db8342e791e1db77a44659ba7a-1589x502.png" style="width: 100%;" /><h2>Diseño para datos con retraso</h2><p>Las pipelines de ingesta reales rara vez entregan métricas perfectamente a tiempo. Las métricas pueden retrasarse por interrupciones de la red, retrasos en el camino, ingesta por batch y pérdida de dispositivos periféricos, que se vuelven a conectar y comienzan a ponerse al día.</p><p>Los índices tradicionales absorben silenciosamente esos retrasos. TSDS no lo hace.</p><p>Si la marca de tiempo de un documento queda fuera del intervalo de índices de respaldo con capacidad de escritura, Elasticsearch lo rechaza, lo que significa que tu política de ILM debe tener en cuenta los datos que llegan con retraso.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/32a103b95b20e31615c214271e27811a7ee315ae-1999x691.png" style="width: 100%;" /><h2>La restricción crítica</h2><p>Los índices de respaldo deben mantenerse con permisos de escritura el tiempo suficiente para aceptar datos tardíos.</p><p>En términos prácticos:</p><pre><code>time_until_readonly > maximum_expected_lateness</code></pre><p>Debido a que ILM mide las antigüedades desde el desplazamiento, la regla operativa se convierte en:</p><pre><code>warm_or_cold_min_age > rollover_max_age + maximum_expected_lateness</code></pre><p></p><p>Por ejemplo, si las métricas pueden llegar con hasta seis horas de retraso, los índices deben permanecer editables al menos seis horas después de la transferencia.</p><p></p><p>No tener en cuenta esta restricción fue exactamente lo que causó la falla de ingesta descrita anteriormente. Los datos que llegaban tarde se dirigieron a un índice anterior, que ya estaba en el nivel frío y, por tanto, bloqueaba la escritura.</p><p></p><h2>Gestión de documentos rechazados</h2><p>Cuando TSDS rechaza un documento, Elasticsearch devuelve un error, lo que indica que la marca de tiempo no está dentro del rango de índices de escritura. La forma en que tu pipeline de ingesta maneja ese error determina si pierdes datos o si la ingesta se detiene.</p><p>El mecanismo principal para manejar documentos rechazados es el almacén de fallas.</p><h3>Almacén de fallas (recomendado en Elasticsearch 9.1+)</h3><p>Elasticsearch 9.1 introdujo el almacén de fallas, que captura automáticamente los documentos rechazados. En lugar de devolver errores a los clientes, Elasticsearch escribe los documentos rechazados en un índice de fallas dedicado dentro del flujo de datos.</p><p>Puedes inspeccionar las fallas usando:</p><pre><code>GET metrics-myapp::failures/_search</code></pre><p>Usar el almacén de fallas evita que los pipelines de ingesta se bloqueen por errores de rechazo, a la vez que conserva los datos fallidos para analizarlos o <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/reindex-tsds">reindexarlos</a>.</p><h2>Monitoreo de problemas de rechazo</h2><p>Los problemas de retraso suelen aparecer primero como anomalías de ingesta. Puedes notarlos primero como:</p><ul><li>Caídas repentinas en la tasa de indexación.</li><li>Aumentos repentinos en el número de documentos rechazados.</li><li>Una cantidad cada vez mayor de entradas del almacén de fallas.</li><li>Discrepancias entre el número de entradas y salidas del pipeline.</li></ul><p>Alertar sobre estas señales permite a los operadores detectar problemas antes de que los pipelines se detengan. Se pueden utilizar flujos de trabajo, tareas de machine learning y otros mecanismos para automatizar la detección y la notificación.</p><h2>Lista de verificación de migración para TSDS + ILM</h2><p>Si estás migrando un cluster de métricas a TSDS, introduciendo la organización en niveles con ILM o actualizando a una versión de Elasticsearch en la que las métricas son TSDS por defecto, revisa primero estos elementos.</p><h3><strong>1. Mide la latencia de la ingesta</strong></h3><p>Antes de cambiar las políticas de ILM, determina:</p><ul><li>Retraso normal de ingesta.</li><li>Retraso en el peor de los casos durante incidentes.</li><li>Retrasos causados por pipelines de batch.</li></ul><p>Tu diseño de ILM debe contemplar el retraso máximo realista.</p><h3><strong>2. Verifica las ventanas de tiempo de indexación</strong></h3><p>Inspecciona tus índices de respaldo TSDS:</p><pre><code>GET _data_stream/<your-stream></code></pre><p>Busca:</p><ul><li><code>time_series.start_time</code></li><li><code>time_series.end_time</code></li></ul><p>Estos límites determinan qué índices pueden aceptar documentos. Entender estas ventanas puede ayudarte a determinar cuán tarde pueden llegar los datos antes de que se rechacen.</p><h3><strong>3. Dimensiona el nivel caliente para llegadas tardías</strong></h3><p>Garantiza que los índices de respaldo permanezcan con permisos de escritura el tiempo suficiente para los datos retrasados.</p><p>Regla operativa:</p><ul><li><code>warm_min_age &gt; rollover_max_age + maximum_expected_lateness</code></li></ul><p>Recuerda, los índices deben permanecer en modo de escritura durante al menos seis horas si las métricas pueden llegar con seis horas de retraso.</p><h3><strong>4. Decide cómo manejar los documentos rechazados</strong></h3><p>Elige una estrategia antes de habilitar TSDS:</p><ul><li>Almacén de fallas (recomendado en Elasticsearch 9.1+).</li><li>Cola de mensajes no procesados de Logstash.</li><li>Índice de respaldo para retrasos.</li><li>Aceptar la pérdida de datos limitada.</li></ul><h3><strong>5. Monitorea el estado de la ingesta</strong></h3><p>Agrega alertas para:</p><ul><li>La tasa de indexación disminuye.</li><li>Documentos rechazados.</li><li>Crecimiento del almacén de fallas.</li><li>Desajustes de entrada/salida en la pipeline.</li></ul><p>Los problemas de datos tardíos a menudo aparecen primero como anomalías de ingesta.</p><h2>Resumen</h2><p>Los flujos de datos temporales ofrecen importantes mejoras de almacenamiento y rendimiento para las cargas de trabajo de métricas, pero introducen un cambio arquitectónico importante: los índices de respaldo están acotados en el tiempo, lo que afecta el comportamiento de ILM.</p><p>Al usar TSDS:</p><ul><li>Los índices deben mantenerse con permisos de escritura el tiempo suficiente para aceptar datos tardíos.</li><li>Los pipelines de ingesta deben gestionar los documentos rechazados de forma segura.</li></ul><p>La regla clave a recordar es:</p><pre><code>warm_min_age > rollover_max_age + maximum_expected_lateness</code></pre><p>Si diseñas políticas de ILM teniendo en cuenta esa limitación, TSDS funciona muy bien para cargas de trabajo de métricas.</p><p>Sin embargo, si lo ignoras, tu pipeline de ingesta puede descubrir esos límites de tiempo de la manera difícil.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[LINQ a ES|QL: escribe código C#, busca en Elasticsearch]]></title>
            <description><![CDATA[Explorar el nuevo proveedor LINQ a ES|QL en el cliente .NET de Elasticsearch, que te permite escribir código C# que se traduce automáticamente a búsquedas de ES|QL.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/linq-esql-c-elasticsearch-net-client</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/linq-esql-c-elasticsearch-net-client</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Florian Bernd, Martijn Laarman]]></dc:creator>
            <pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e54132e915217063e9ed0ec45059c6cfc38e31dd-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>A partir de <strong>v9.3.4</strong> y <strong>v8.19.18</strong>, el cliente de Elasticsearch para .NET incluye un <a href="https://learn.microsoft.com/en-us/dotnet/csharp/linq/">proveedor de Language Integrated Query (LINQ) </a>que traduce las expresiones LINQ de C# a búsquedas del <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">lenguaje de búsqueda de Elasticsearch (ES|QL)</a> en tiempo de ejecución. En lugar de escribir textos de ES|QL manualmente, compones búsquedas con <code>Where</code>, <code>Select</code>, <code>OrderBy</code>, <code>GroupBy</code> y otros operadores estándar. El proveedor se encarga de la traducción, la parametrización y la deserialización de los resultados, incluido el streaming fila por fila, lo que mantiene el uso de memoria constante independientemente del tamaño del conjunto de resultados.</p><h2>Tu primera búsqueda</h2><p>Comienza por definir un objeto CLR (POCO) simple que se mapea a tu índice de Elasticsearch. Los nombres de las propiedades se resuelven a nombres de columnas ES|QL a través de atributos <code>System.Text.Json</code> estándar, como <code>[JsonPropertyName]</code>, o a través de un <code>JsonNamingPolicy</code> configurado. Las mismas reglas de <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization">serialización de origen</a> que se aplican en el resto del cliente también se aplican aquí.</p><pre><code>using System.Text.Json.Serialization;

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

    public string Name { get; set; }

    public string Brand { get; set; }

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

    [JsonPropertyName("in_stock")]
    public bool InStock { get; set; }
}</code></pre><p>Con el tipo ya definido, una consulta se ve así:</p><pre><code>var minPrice = 100.0;
var brand = "TechCorp";

await foreach (var product in client.Esql.QueryAsync<Product>(q => q
    .From("products")
    .Where(p => p.InStock && p.Price >= minPrice && p.Brand == brand)
    .OrderByDescending(p => p.Price)
    .Take(10)))
{
    Console.WriteLine($"{product.Name}: ${product.Price}");
}</code></pre><p>El proveedor traduce esto al siguiente ES|QL:</p><pre><code>FROM products
| WHERE (in_stock == true AND price_usd >= ?minPrice AND brand == ?brand)
| SORT price_usd DESC
| LIMIT 10</code></pre><p>Algunos detalles a tener en cuenta:</p><ul><li><strong>Resolución de nombres de propiedades:</strong> <code>p.Price</code> se vuelve <code>price_usd</code> debido al atributo <code>[JsonPropertyName]</code>, y <code>p.Brand</code> se convierte en <code>brand</code> siguiendo la política predeterminada de nombres camelCase.</li><li><strong>Captura de parámetros:</strong> Las variables C# <code>minPrice</code> y <code>brand</code> se capturan como parámetros nombrados (<code>?minPrice</code>, <code>?brand</code>). Se envían por separado del texto de búsqueda en la carga útil JSON, lo que previene la inyección y habilita el almacenamiento en caché del plan de búsqueda del lado del servidor.</li><li><strong>Streaming:</strong> <code>QueryAsync&lt;T&gt;</code> devuelve <code>IAsyncEnumerable&lt;T&gt;</code>. Las filas se materializan una a la vez a medida que llegan desde Elasticsearch.</li></ul><p>También puedes inspeccionar la búsqueda generada y sus parámetros sin ejecutarla:</p><pre><code>var query = client.Esql.CreateQuery<Product>()
    .Where(p => p.InStock && p.Price >= minPrice && p.Brand == brand)
    .OrderByDescending(p => p.Price)
    .Take(10);

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

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

var parameters = query.GetParameters();
// { "minPrice": 100.0, "brand": "TechCorp" }</code></pre><h2>¿Cómo funciona esto? Un repaso rápido de LINQ</h2><p>El mecanismo que hace posibles los proveedores LINQ es la distinción entre <code>IEnumerable&lt;T&gt;</code> y <code>IQueryable&lt;T&gt;</code>.</p><p>Cuando llamas a <code>.Where(p =&gt; p.Price &gt; 100)</code> en un <code>IEnumerable&lt;T&gt;</code>, la lambda se compila en un <code>Func&lt;Product, bool&gt;</code>, un delegado común que el runtime ejecuta en proceso. Esto es LINQ a objetos.</p><p>Cuando llamas al mismo método en un <code>IQueryable&lt;T&gt;</code>, el compilador de C# encapsula la expresión lambda en un <code>Expression&lt;Func&lt;Product, bool&gt;&gt;</code> en su lugar. Esta es una estructura de datos que representa la <em>estructura</em> del código en lugar de su forma ejecutable. El árbol de expresión puede inspeccionarse, analizarse y traducirse a otro idioma en tiempo de ejecución.</p><pre><code>// IEnumerable: the lambda is a compiled delegate
IEnumerable<Product> local = products.Where(p => p.Price > 100);

// IQueryable: the lambda is an expression tree, a data structure
IQueryable<Product> remote = queryable.Where(p => p.Price > 100);</code></pre><p>La interfaz <code>IQueryProvider</code> es el punto de extensión. Cualquier proveedor puede implementar <code>CreateQuery&lt;T&gt;</code> y <code>Execute&lt;T&gt;</code> para traducir estos árboles de expresiones a un idioma destino. Entity Framework usa esto para emitir SQL. El proveedor de LINQ a ES|QL lo usa para emitir ES|QL.</p><p>El árbol de expresión para la búsqueda anterior se ve así:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f864cd18a390831f8d28503a29b5835efb1842f7-1000x720.png" style="width: 100%;" /><p><em>Árbol de expresiones para la búsqueda de ejemplo.</em></p><p>El árbol está anidado al revés: <code>Take</code> envuelve <code>OrderByDescending</code>, que envuelve <code>Where</code>, que envuelve <code>From</code>, que envuelve la constante raíz <code>EsqlQueryable&lt;Product&gt;</code>. El predicado <code>Where</code> es en sí mismo un subárbol de <code>BinaryExpression</code> nodos para los operadores <code>&amp;&amp;</code>, <code>&gt;=</code> y <code>==</code>, con <code>MemberExpression</code> hojas para accesos a propiedades y capturas de cierre para las variables <code>minPrice</code> y <code>brand</code>. Esta es la estructura de datos que el proveedor recorre para producir el ES|QL final.</p><h2>En detalle: el pipeline de traducción</h2><p>La ruta de una expresión LINQ a los resultados de la búsqueda sigue un pipeline de seis etapas:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2a2c772b63d720f61fc9a28b2f85668fa2db8d38-1999x1036.png" style="width: 100%;" /><p><em>Visión general del pipeline de traducción.</em></p><h3>1. Captura del árbol de expresiones</h3><p>Cuando se encadenan <code>.Where()</code>, <code>.OrderBy()</code>, <code>.Take()</code> y otros operadores en un <code>IQueryable&lt;T&gt;</code>, la infraestructura LINQ estándar crea un árbol de expresiones. <code>EsqlQueryable&lt;T&gt;</code> implementa <code>IQueryable&lt;T&gt;</code> y delega a <code>EsqlQueryProvider</code>.</p><h3>2. Traducción</h3><p>Cuando se ejecuta la búsqueda (al enumerar, llamar a <code>ToList()</code> o usar <code>await foreach)</code>, <code>EsqlExpressionVisitor</code> recorre el árbol de expresiones de adentro hacia afuera. Envía cada llamada al método LINQ a un visitante especializado:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Visitante</td><td style="border: 1px solid #ccc; padding: 4px;">Traduce</td><td style="border: 1px solid #ccc; padding: 4px;">En</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">whereClauseVisitor</td><td style="border: 1px solid #ccc; padding: 4px;">.Where(predicado)</td><td style="border: 1px solid #ccc; padding: 4px;">Condición WHERE</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">SelectProjectionVisitor</td><td style="border: 1px solid #ccc; padding: 4px;">.Select(selector)</td><td style="border: 1px solid #ccc; padding: 4px;">EVAL + KEEP + RENAME</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">GroupByVisitor</td><td style="border: 1px solid #ccc; padding: 4px;">.GroupBy().Select()</td><td style="border: 1px solid #ccc; padding: 4px;">STATS ... BY</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OrderByVisitor</td><td style="border: 1px solid #ccc; padding: 4px;">.OrderBy() / .ThenBy()</td><td style="border: 1px solid #ccc; padding: 4px;">Campo SORT [ASC\|DESC]</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">EsqlFunctionTranslator</td><td style="border: 1px solid #ccc; padding: 4px;">EsqlFunctions.*, Math.*, métodos de texto</td><td style="border: 1px solid #ccc; padding: 4px;">Más de 80 funciones ES|QL</td>
                    </tr>
              </tbody>
            </table>
          <p>Durante la traducción, las variables de C# a las que se hace referencia en las expresiones se capturan como parámetros con nombre.</p><h3>3. Modelo de búsqueda</h3><p>Los visitantes no producen textos directamente. En cambio, producen objetos <code>QueryCommand</code>, una representación intermedia inmutable. Un <code>FromCommand</code>, un <code>WhereCommand</code>, un <code>SortCommand</code> y un <code>LimitCommand</code>, cada uno representa un comando de procesamiento de ES|QL. Estos se recopilan en un modelo <code>EsqlQuery</code>.</p><p></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2adc349b6cf655b96b7b3e826a134e8a17fe42fd-1999x1036.png" style="width: 100%;" /><p><em>Modelo de búsqueda y patrón de comandos.</em></p><p>Este modelo intermedio está desacoplado tanto del árbol de expresiones como del formato de salida. Se puede inspeccionar, interceptar (vía <code>IEsqlQueryInterceptor</code>) o modificar antes de dar formato.</p><h3>4. Formato</h3><p><code>EsqlFormatter</code> visita cada <code>QueryCommand</code> en orden y produce el texto final de ES|QL. Cada comando se convierte en una línea, separada por el operador de barra vertical (|) que ES|QL usa para encadenar comandos de procesamiento. Los identificadores que contienen caracteres especiales se escapan automáticamente con comillas invertidas.</p><h3>5. Ejecución</h3><p>El texto ES|QL formateado y los parámetros capturados se envían al endpoint <code>/_query</code> de Elasticsearch como carga útil JSON. La interfaz <code>IEsqlQueryExecutor</code> abstrae la capa de transporte, que es donde entra en juego la arquitectura de paquetes en capas.</p><h3>6. Materialización</h3><p><code>EsqlResponseReader</code> transmite la respuesta JSON sin almacenar en memoria todo el conjunto de resultados. Un árbol <code>ColumnLayout</code>, precomputado una vez por búsqueda, mapea nombres de columnas planas de ES|QL (como <code>address.street</code>, <code>address.city</code>) a propiedades anidadas de POCO. Cada fila se ensambla en una instancia <code>T</code> y se genera una a la vez a través de <code>IEnumerable&lt;T&gt;</code> o <code>IAsyncEnumerable&lt;T&gt;</code>.</p><h2>La arquitectura en capas</h2><p>La funcionalidad de LINQ a ES|QL se divide en tres paquetes:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/41b8aae860ecdc2480edcb1c1d4cc9b03cfb78c9-1999x1036.png" style="width: 100%;" /><p><em>Arquitectura de paquetes.</em><br/><a href="https://www.nuget.org/packages/Elastic.Esql"><strong><code>Elastic.Esql</code></strong></a> es el motor puro de traducción. No tiene dependencias HTTP y contiene los visitantes de expresiones, el modelo de búsqueda, el formateador y el lector de respuestas. Puedes usarlo de forma independiente para crear e inspeccionar búsquedas de ES|QL sin una conexión de Elasticsearch, lo que es útil para pruebas, logging de búsquedas o para crear tu propia capa de ejecución.</p><pre><code>// Translation-only: no Elasticsearch connection needed
var provider = new EsqlQueryProvider();
var query = new EsqlQueryable<Product>(provider)
    .From("products")
    .Where(p => p.InStock)
    .OrderByDescending(p => p.Price);

Console.WriteLine(query.ToEsqlString());
// FROM products | WHERE in_stock == true | SORT price_usd DESC</code></pre><p><a href="https://www.nuget.org/packages/Elastic.Clients.Esql"><strong><code>Elastic.Clients.Esql</code></strong></a> es un cliente ES|QL ligero e independiente. Añade ejecución HTTP sobre <code>Elastic.Esql</code> a través de <code>Elastic.Transport</code>. Si tu aplicación solo necesita ES|QL y ninguna de las otras API de Elasticsearch, esta es la opción de dependencia mínima.</p><p><a href="https://www.nuget.org/packages/Elastic.Clients.Elasticsearch"><strong><code>Elastic.Clients.Elasticsearch</code></strong></a> es el cliente completo de Elasticsearch .NET. También se basa en <code>Elastic.Esql</code> y expone al proveedor LINQ a través del espacio de nombres <code>client.Esql</code>. Este es el punto de entrada recomendado para la mayoría de las aplicaciones.</p><p>Ambos paquetes de capa de ejecución proporcionan su propia implementación de <code>IEsqlQueryExecutor</code>, la interfaz estratégica que une la traducción y el transporte.</p><p>Los tres paquetes son compatibles con Native AOT cuando se usan con un <code>JsonSerializerContext</code> generado por el código fuente. Para el cliente completo, consulta la <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization#native-aot">documentación de Native AOT</a>.</p><h2>Mas allá de los conceptos básicos</h2><p>El ejemplo anterior cubrió el filtrado, la clasificación y la paginación. El proveedor admite un conjunto más amplio de operaciones.</p><h3>Agregaciones</h3><p><code>GroupBy</code>, combinado con funciones agregadas en <code>Select</code>, se traduce a ES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/stats-by"><code>STATS ... BY</code></a>:</p><pre><code>var stats = client.Esql.Query<Product, object>(q => q
    .GroupBy(p => p.Brand)
    .Select(g => new
    {
        Brand = g.Key,
        Count = g.Count(),
        AvgPrice = g.Average(p => p.Price),
        MaxPrice = g.Max(p => p.Price)
    }));

// -> FROM products | STATS COUNT(*), AVG(price_usd), MAX(price_usd) BY brand</code></pre><h3>Proyecciones</h3><p><code>Select</code>, con tipos anónimos genera comandos <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval"><code>EVAL</code></a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/keep"><code>KEEP</code></a>, y <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/rename"><code>RENAME</code></a>:</p><pre><code>var query = client.Esql.CreateQuery<Product>()
    .Select(p => new { ProductName = p.Name, p.Price, p.InStock });

// -> FROM products | KEEP name, price_usd, in_stock | RENAME name AS ProductName</code></pre><h3>Biblioteca de funciones enriquecida</h3><p>Hay más de 80 funciones ES|QL disponibles a través de la clase <code>EsqlFunctions</code>, que abarcan fecha/hora, texto, matemáticas, IP, coincidencia de patrones y puntuación. También se traducen los métodos estándar <code>Math.*</code> y <code>string.*</code>:</p><pre><code>.Where(p => p.Name.Contains("Pro"))       // -> WHERE name LIKE "*Pro*"
.Where(p => EsqlFunctions.CidrMatch(      // -> WHERE CIDR_MATCH(ip, "10.0.0.0/8")
    p.IpAddress, "10.0.0.0/8"))</code></pre><h3>LOOKUP JOIN</h3><p>Las consultas cruzadas de índices se traducen a ES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join"><code>LOOKUP JOIN</code></a>:</p><pre><code>var enriched = client.Esql.Query<Product, object>(q => q
    .LookupJoin<Product, CategoryLookup, string, object>(
        "category-lookup-index",
        product => product.Id,
        category => category.CategoryId,
        (product, category) => new { product.Name, category!.CategoryLabel }));</code></pre><h3>Acceso directo a ES|QL sin procesar</h3><p>Para las características de ES|QL que aún no están cubiertas por el proveedor de LINQ, puedes anexar fragmentos sin procesar:</p><pre><code>var results = client.Esql.Query<Product>(q => q
    .Where(p => p.InStock)
    .RawEsql("| EVAL discounted = price_usd * 0.9"));</code></pre><h3>Búsquedas asíncronas del lado del servidor</h3><p>Para búsquedas de ejecución prolongada, envíalas para procesamiento en segundo plano en el servidor:</p><pre><code>await using var asyncQuery = await client.Esql.SubmitAsyncQueryAsync<Product>(
    q => q.Where(p => p.InStock),
    asyncQueryOptions: new EsqlAsyncQueryOptions
    {
        WaitForCompletionTimeout = TimeSpan.FromSeconds(5),
        KeepAlive = TimeSpan.FromMinutes(10)
    });

await asyncQuery.WaitForCompletionAsync();
await foreach (var product in asyncQuery.AsAsyncEnumerable())
    Console.WriteLine(product.Name);</code></pre><p>Las búsquedas asíncronas del lado del servidor son especialmente útiles para búsquedas analíticas de larga duración/procesamiento de grandes sets de datos que pueden superar los umbrales típicos de tiempo de espera, o en entornos sensibles al tiempo de espera con balanceadores de carga, gateways API o proxies que imponen tiempos de espera HTTP estrictos. Las búsquedas asíncronas evitan las caídas de conexión al separar el envío de la solicitud de la recuperación de los resultados.</p><h2>Primeros pasos</h2><p>LINQ a ES|QL está disponible a partir de:</p><ul><li><strong>Elastic.Clients.Elasticsearch v9.3.4</strong> (rama 9.x)</li><li><strong>Elastic.Clients.Elasticsearch v8.19.18</strong> (rama 8.x)</li></ul><p>Instalar desde NuGet:</p><p><code>dotnet add package Elastic.Clients.Elasticsearch</code></p><p>Los puntos de entrada están en <code>client.Esql</code>:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Método</td><td style="border: 1px solid #ccc; padding: 4px;">Devuelve</td><td style="border: 1px solid #ccc; padding: 4px;">Caso de uso</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Query<T>(...)</td><td style="border: 1px solid #ccc; padding: 4px;">IEnumerable<T></td><td style="border: 1px solid #ccc; padding: 4px;">Ejecución sincrónica</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">QueryAsync<T>(...)</td><td style="border: 1px solid #ccc; padding: 4px;">IAsyncEnumerable<T></td><td style="border: 1px solid #ccc; padding: 4px;">Transmisión asíncrona</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">CreateQuery<T>()</td><td style="border: 1px solid #ccc; padding: 4px;">IEsqlQueryable<T></td><td style="border: 1px solid #ccc; padding: 4px;">Composición e inspección avanzadas</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">SubmitAsyncQueryAsync<T>(...)</td><td style="border: 1px solid #ccc; padding: 4px;">EsqlAsyncQuery<T></td><td style="border: 1px solid #ccc; padding: 4px;">Búsquedas de larga ejecución del lado del servidor</td>
                    </tr>
              </tbody>
            </table>
          <p>Para consultar la referencia completa de características, incluidas las opciones de búsqueda, el acceso a múltiples campos, los objetos anidados y el manejo de campos de valores múltiples, consulta la <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/linq-to-esql">documentación de LINQ a ES|QL</a>.</p><h2>Conclusión</h2><p>LINQ a ES|QL aporta toda la expresividad de LINQ de C# al lenguaje de búsqueda ES|QL de Elasticsearch, lo que te permite realizar búsquedas con tipado fuerte y combinables sin crear manualmente cadenas de texto. Con captura automática de parámetros, materialización de streaming y una arquitectura de paquetes en capas que escala desde el paquete de traducción autónomo hasta el cliente completo de Elasticsearch, se adapta naturalmente a aplicaciones .NET de cualquier tamaño. Instala el cliente más reciente, dirige tus expresiones LINQ a un índice y deja que el proveedor se encargue del resto.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cómo crear un servidor MCP de Elasticsearch con TypeScript]]></title>
            <description><![CDATA[Aprende a crear un servidor MCP de Elasticsearch con TypeScript y Claude Desktop.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elastic-mcp-server-typescript-claude</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elastic-mcp-server-typescript-claude</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
            <pubDate>Fri, 27 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0bb24c05fff391f42070c2883182ea6fe9cb9680-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Cuando se trabaja con grandes bases de conocimiento en Elasticsearch, encontrar información es solo la mitad de la batalla. Los ingenieros suelen necesitar sintetizar resultados de varios documentos, generar resúmenes y rastrear las respuestas hasta sus fuentes. El protocolo de contexto de modelo (MCP) proporciona una manera estandarizada de conectar Elasticsearch con aplicaciones basadas en modelos de lenguaje grande (LLM) para lograr esto. Mientras que Elastic ofrece soluciones oficiales, como Elastic Agent Builder (que incluye un <a href="https://www.elastic.co/docs/solutions/search/agent-builder/mcp-server">endpoint MCP</a> entre sus características), construir un servidor MCP personalizado te brinda control total sobre la lógica de búsqueda, el formato de resultados y cómo se pasa el contenido recuperado a un LLM para síntesis, resúmenes y citas.</p><p>En este artículo, exploraremos las ventajas de construir un servidor MCP personalizado de Elasticsearch y mostraremos cómo crear uno en TypeScript que conecte Elasticsearch con aplicaciones impulsadas por LLM.</p><h2>¿Por qué construir un servidor MCP personalizado de Elasticsearch?</h2><p>Elastic ofrece algunas alternativas para los <a href="https://www.elastic.co/docs/solutions/search/mcp">servidores MCP</a>:</p><ul><li><a href="https://www.elastic.co/docs/solutions/search/agent-builder/mcp-server">Servidor MCP de Elastic Agent Builder para Elasticsearch 9.2+</a></li><li><a href="https://github.com/elastic/mcp-server-elasticsearch?tab=readme-ov-file#elasticsearch-mcp-server">Servidor MCP de Elasticsearch para versiones anteriores (Python)</a></li></ul><p>Si necesitas más control sobre cómo tu servidor MCP interactúa con Elasticsearch, construir tu propio servidor personalizado te da la flexibilidad de adaptarlo exactamente a tus necesidades. Por ejemplo, el endpoint MCP de Agent Builder está limitado a las consultas de lenguaje de búsqueda (ES|QL) de Elasticsearch, mientras que un servidor personalizado te permite usar el DSL de consulta completo. También obtienes control sobre cómo se formatean los resultados antes de pasarlos al LLM y puedes integrar pasos de procesamiento adicionales, como el resumen impulsado por OpenAI que implementaremos en este tutorial.</p><p>Al final de este artículo, tendrás un servidor MCP en TypeScript que busca información almacenada en un índice de Elasticsearch, la resume y proporciona citas. Usaremos Elasticsearch para la recuperación, el modelo <code>gpt-4o-mini</code> de OpenAI para resumir y generar citas, y Claude Desktop como cliente MCP y UI para recibir las búsquedas de los usuarios y dar respuestas. El resultado final es un asistente de conocimiento interno que ayuda a los ingenieros a descubrir y sintetizar las mejores prácticas en los documentos técnicos de su organización.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ec5771a874cf9740d4cac6888622cbe8cd6aede7-1999x1133.png" style="width: 100%;" /><h2>Requisitos previos:</h2><ul><li>Node.js 20 +</li><li>Elasticsearch</li><li>Clave de API de OpenAI</li><li>Claude Desktop</li></ul><h3>¿Qué es MCP?</h3><p><a href="https://www.elastic.co/what-is/mcp">MCP</a> es un estándar abierto, creado por <a href="https://www.anthropic.com/news/model-context-protocol">Anthropic</a>, que ofrece conexiones seguras y bidireccionales entre los modelos de lenguaje grande (LLM) y sistemas externos, como Elasticsearch. Puedes leer más sobre el estado actual del MCP en <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">este artículo</a>.</p><p>El panorama de MCP <a href="https://www.elastic.co/search-labs/blog/mcp-current-state#mcp-project-updates:-transport,-elicitation,-and-structured-tooling">evoluciona cada día</a>, con servidores disponibles para una amplia gama de casos de uso. Además de eso, desarrollar tu propio servidor MCP personalizado es fácil, como te mostraremos en este artículo.</p><h3>Clientes del MCP</h3><p>Hay una larga <a href="https://modelcontextprotocol.io/clients">lista de clientes del MCP disponibles</a>, cada uno con sus propias características y limitaciones. Por su sencillez y popularidad, usaremos <a href="https://claude.ai/download">Claude Desktop</a> como nuestro cliente MCP. Servirá como interfaz de chat en la que los usuarios podrán hacer preguntas en lenguaje natural e invocar automáticamente las herramientas expuestas por nuestro servidor MCP para buscar documentos y generar resúmenes.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/66eb0b11473347b6cf2d85718251eeac38d6249d-1999x1491.png" style="width: 100%;" /><h2>Cómo crear un servidor MCP de Elasticsearch</h2><p>Con el <a href="https://github.com/modelcontextprotocol/typescript-sdk">SDK de TypeScript</a>, podemos crear fácilmente un servidor que entiende cómo hacer búsquedas en nuestros datos de Elasticsearch con base en la entrada de búsqueda del usuario.</p><p>Estos son los pasos en este artículo para integrar el servidor MCP de Elasticsearch con el cliente Claude Desktop:</p><ol><li><a href="https://www.elastic.co/search-labs/blog/elastic-mcp-server-typescript-claude#configure-mcp-server-for-elasticsearch">Configurar el servidor MCP para Elasticsearch.</a></li><li><a href="https://www.elastic.co/search-labs/blog/elastic-mcp-server-typescript-claude#load-the-mcp-server-into-claude-desktop">Carga el servidor MCP en Claude Desktop.</a></li><li><a href="https://www.elastic.co/search-labs/blog/elastic-mcp-server-typescript-claude#test-it-out">Pruébalo.</a></li></ol><h3>Configurar el servidor MCP para Elasticsearch</h3><p>Para comenzar, inicialicemos una aplicación de nodo:</p><pre><code>npm init -y</code></pre><p>Esto creará un archivo <code>package.json</code> y, con él, podremos empezar a instalar las dependencias necesarias para esta aplicación.</p><pre><code>npm install @elastic/elasticsearch @modelcontextprotocol/sdk openai zod && npm install --save-dev ts-node @types/node typescript</code></pre><ul><li><strong>@elastic/elasticsearch</strong> nos dará acceso a la biblioteca de Elasticsearch para Node.js.</li><li><strong>@modelcontextprotocol/sdk</strong> proporciona las herramientas básicas para crear y administrar un servidor MCP, registrar herramientas y manejar la comunicación con los clientes de MCP.</li><li><strong>openAI</strong> permite la interacción con modelos OpenAI para generar resúmenes o respuestas en lenguaje natural.</li><li><strong><a href="https://zod.dev/">zod</a> </strong>ayuda a definir y validar esquemas estructurados para los datos de entrada y salida en cada herramienta.</li></ul><p><code>ts-node</code>, <code>@types/node</code> y <code>typescript</code> se usarán durante el desarrollo para escribir el código y compilar los scripts.</p><h4>Configura los sets de datos</h4><p>Para proporcionar los datos que Claude Desktop puede consultar con nuestro servidor de MCP, utilizaremos un <a href="https://github.com/Delacrobix/typescript-elasticsearch-mcp/blob/main/dataset.json">conjunto de datos de base de conocimiento interna simulado</a>. Así es como se verá un documento de este sets de datos:</p><pre><code>{
    "id": 5,
    "title": "Logging Standards for Microservices",
    "content": "Consistent logging across microservices helps with debugging and tracing. Use structured JSON logs and include request IDs and timestamps. Avoid logging sensitive information. Centralize logs in Elasticsearch or a similar system. Configure log rotation to prevent storage issues and ensure logs are searchable for at least 30 days.",
    "tags": ["logging", "microservices", "standards"]
}</code></pre><p>Para cargar los datos, preparamos un script que cree un índice en Elasticsearch y cargue el set de datos en él. Puedes encontrarlo <a href="https://github.com/Delacrobix/typescript-elasticsearch-mcp/blob/main/setup.ts">aquí</a>.</p><h4>Servidor MCP</h4><p>Crea un archivo llamado <a href="https://github.com/Delacrobix/typescript-elasticsearch-mcp/blob/main/index.ts"><code>index.ts</code></a> y agrega el siguiente código para importar las dependencias y gestionar las variables de entorno:</p><pre><code>// index.ts
import { z } from "zod";
import { Client } from "@elastic/elasticsearch";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import OpenAI from "openai";

const ELASTICSEARCH_ENDPOINT =
  process.env.ELASTICSEARCH_ENDPOINT ?? "http://localhost:9200";
const ELASTICSEARCH_API_KEY = process.env.ELASTICSEARCH_API_KEY ?? "";
const OPENAI_API_KEY = process.env.OPENAI_API_KEY ?? "";
const INDEX = "documents";</code></pre><p>Además, preparemos a los clientes para que gestionen las llamadas a Elasticsearch y OpenAI:</p><pre><code>const openai = new OpenAI({
  apiKey: OPENAI_API_KEY,
});

const _client = new Client({
  node: ELASTICSEARCH_ENDPOINT,
  auth: {
    apiKey: ELASTICSEARCH_API_KEY,
  },
});</code></pre><p>Para hacer nuestra implementación más robusta y asegurar entradas y salidas estructuradas, definiremos esquemas usando <a href="https://zod.dev/"><code>zod</code></a>. Esto nos permite validar los datos en tiempo de ejecución, detectar errores a tiempo y facilitar el procesamiento de las respuestas de la herramienta mediante código:</p><pre><code>const DocumentSchema = z.object({
  id: z.number(),
  title: z.string(),
  content: z.string(),
  tags: z.array(z.string()),
});

const SearchResultSchema = z.object({
  id: z.number(),
  title: z.string(),
  content: z.string(),
  tags: z.array(z.string()),
  score: z.number(),
});

type Document = z.infer<typeof DocumentSchema>;
type SearchResult = z.infer<typeof SearchResultSchema>;</code></pre><p>Descubre más sobre las salidas estructuradas <a href="https://www.elastic.co/search-labs/blog/structured-outputs-elasticsearch-guide">aquí</a>.</p><p>Ahora vamos a inicializar el servidor MCP:</p><pre><code>const server = new McpServer({
  name: "Elasticsearch RAG MCP",
  description:
    "A RAG server using Elasticsearch. Provides tools for document search, result summarization, and source citation.",
  version: "1.0.0",
});</code></pre><h4>Definición de las herramientas MCP</h4><p>Ahora que ya tenemos todo configurado, podemos empezar a desarrollar las herramientas que ofrecerá nuestro servidor MCP. Este servidor ofrece dos herramientas:</p><ul><li><strong><code>search_docs</code>: </strong>Búsquedas de documentos en Elasticsearch mediante la búsqueda de texto.</li><li><strong><code>summarize_and_cite</code>:</strong> Resume y sintetiza información de documentos previamente recuperados para responder a una pregunta del usuario. Esta herramienta también agrega citas que hacen referencia a los documentos originales.</li></ul><p>Juntas, estas herramientas forman un flujo de trabajo simple de “recuperación y resumen”, donde una herramienta busca documentos relevantes y la otra usa esos documentos para generar una respuesta resumida y citada.</p><h4>Formato de respuesta de herramienta</h4><p>Cada herramienta puede aceptar parámetros de entrada arbitrarios, pero debe responder con la siguiente estructura:</p><ul><li><strong>Contenido:</strong> esta es la respuesta de la herramienta en un formato no estructurado. Este campo se suele usar para mostrar texto, imágenes, audio, enlaces o contenido incrustado. Para esta aplicación, se utilizará para devolver texto formateado con la información generada por las herramientas.</li><li><strong>structuredContent: </strong>este es un retorno opcional que se usa para proporcionar los resultados de cada herramienta en un formato estructurado. Esto es útil para fines programáticos. Aunque no se usa en este servidor de MCP, puede ser útil si quieres desarrollar otras herramientas o procesar los resultados mediante programación.</li></ul><p>Con esa estructura en mente, comencemos con cada herramienta en detalle.</p><h4>Herramienta Search_docs</h4><p>Esta herramienta realiza una <a href="https://www.elastic.co/docs/solutions/search/full-text">búsqueda de texto completo</a> en el índice de Elasticsearch para recuperar los documentos más relevantes según la consulta del usuario. Destaca los resultados clave y ofrece una visión general rápida con puntuaciones de relevancia.</p><pre><code>server.registerTool(
  "search_docs",
  {
    title: "Search Documents",
    description:
      "Search for documents in Elasticsearch using full-text search. Returns the most relevant documents with their content, title, tags, and relevance score.",
    inputSchema: {
      query: z
        .string()
        .describe("The search query terms to find relevant documents"),
      max_results: z
        .number()
        .optional()
        .default(5)
        .describe("Maximum number of results to return"),
    },
    outputSchema: {
      results: z.array(SearchResultSchema),
      total: z.number(),
    },
  },
  async ({ query, max_results }) => {
    if (!query) {
      return {
        content: [
          {
            type: "text",
            text: "Query parameter is required",
          },
        ],
        isError: true,
      };
    }

    try {
      const response = await _client.search({
        index: INDEX,
        size: max_results,
        query: {
          bool: {
            must: [
              {
                multi_match: {
                  query: query,
                  fields: ["title^2", "content", "tags"],
                  fuzziness: "AUTO",
                },
              },
            ],
            should: [
              {
                match_phrase: {
                  title: {
                    query: query,
                    boost: 2,
                  },
                },
              },
            ],
          },
        },
        highlight: {
          fields: {
            title: {},
            content: {},
          },
        },
      });

      const results: SearchResult[] = response.hits.hits.map((hit: any) => {
        const source = hit._source as Document;

        return {
          id: source.id,
          title: source.title,
          content: source.content,
          tags: source.tags,
          score: hit._score ?? 0,
        };
      });

      const contentText = results
        .map(
          (r, i) =>
            `[${i + 1}] ${r.title} (score: ${r.score.toFixed(
              2,
            )})\n${r.content.substring(0, 200)}...`,
        )
        .join("\n\n");

      const totalHits =
        typeof response.hits.total === "number"
          ? response.hits.total
          : (response.hits.total?.value ?? 0);

      return {
        content: [
          {
            type: "text",
            text: `Found ${results.length} relevant documents:\n\n${contentText}`,
          },
        ],
        structuredContent: {
          results: results,
          total: totalHits,
        },
      };
    } catch (error: any) {
      console.log("Error during search:", error);

      return {
        content: [
          {
            type: "text",
            text: `Error searching documents: ${error.message}`,
          },
        ],
        isError: true,
      };
    }
  }
);</code></pre><p><em>Configuramos <code><a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-fuzzy-query">fuzziness</a>: “AUTO”</code> para que tenga una tolerancia tipográfica variable basada en la longitud del token que se está analizando. También establecemos <code>title^2</code> para aumentar la puntuación de los documentos donde se produce la coincidencia en el campo de título.</em></p><h4>herramienta de resumen y cita: summarize_and_cite</h4><p>Esta herramienta genera un resumen basado en los documentos recuperados en la búsqueda anterior. Usa el modelo <code>gpt-4o-mini</code> de OpenAI para sintetizar la información más relevante y responder a la pregunta del usuario para obtener respuestas derivadas directamente de los resultados de búsqueda. Además del resumen, también devuelve metadatos de citas para los documentos fuente utilizados.</p><pre><code>server.registerTool(
  "summarize_and_cite",
  {
    title: "Summarize and Cite",
    description:
      "Summarize the provided search results to answer a question and return citation metadata for the sources used.",
    inputSchema: {
      results: z
        .array(SearchResultSchema)
        .describe("Array of search results from search_docs"),
      question: z.string().describe("The question to answer"),
      max_length: z
        .number()
        .optional()
        .default(500)
        .describe("Maximum length of the summary in characters"),
      max_docs: z
        .number()
        .optional()
        .default(5)
        .describe("Maximum number of documents to include in the context"),
    },
    outputSchema: {
      summary: z.string(),
      sources_used: z.number(),
      citations: z.array(
        z.object({
          id: z.number(),
          title: z.string(),
          tags: z.array(z.string()),
          relevance_score: z.number(),
        })
      ),
    },
  },
  async ({ results, question, max_length, max_docs }) => {
    if (!results || results.length === 0 || !question) {
      return {
        content: [
          {
            type: "text",
            text: "Both results and question parameters are required, and results must not be empty",
          },
        ],
        isError: true,
      };
    }

    try {
      const used = results.slice(0, max_docs);

      const context = used
        .map(
          (r: SearchResult, i: number) =>
            `[Document ${i + 1}: ${r.title}]\\n${r.content}`
        )
        .join("\n\n---\n\n");

      // Generate summary with OpenAI
      const completion = await openai.chat.completions.create({
        model: "gpt-4o-mini",
        messages: [
          {
            role: "system",
            content:
              "You are a helpful assistant that answers questions based on provided documents. Synthesize information from the documents to answer the user's question accurately and concisely. If the documents don't contain relevant information, say so.",
          },
          {
            role: "user",
            content: `Question: ${question}\\n\\nRelevant Documents:\\n${context}`,
          },
        ],
        max_tokens: Math.min(Math.ceil(max_length / 4), 1000),
        temperature: 0.3,
      });

      const summaryText =
        completion.choices[0]?.message?.content ?? "No summary generated.";

      const citations = used.map((r: SearchResult) => ({
        id: r.id,
        title: r.title,
        tags: r.tags,
        relevance_score: r.score,
      }));

      const citationText = citations
        .map(
          (c: any, i: number) =>
            `[${i + 1}] ID: ${c.id}, Title: "${c.title}", Tags: ${c.tags.join(
              ", ",
            )}, Score: ${c.relevance_score.toFixed(2)}`,
        )
        .join("\n");

      const combinedText = `Summary:\\n\\n${summaryText}\\n\\nSources used (${citations.length}):\\n\\n${citationText}`;

      return {
        content: [
          {
            type: "text",
            text: combinedText,
          },
        ],
        structuredContent: {
          summary: summaryText,
          sources_used: citations.length,
          citations: citations,
        },
      };
    } catch (error: any) {
      return {
        content: [
          {
            type: "text",
            text: `Error generating summary and citations: ${error.message}`,
          },
        ],
        isError: true,
      };
    }
  }
);</code></pre><p>Finalmente, necesitamos iniciar el servidor a través de <a href="https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#stdio">stdio</a>. Esto significa que el cliente MCP se comunicará con nuestro servidor leyendo y escribiendo en sus flujos estándar de entrada y salida. stdio es la opción de transporte más sencilla y funciona bien para servidores MCP locales lanzados como subprocesos por el cliente. Agrega el siguiente código al final del archivo:</p><pre><code>const transport = new StdioServerTransport();
server.connect(transport);</code></pre><p>Ahora compila el proyecto usando el siguiente comando:</p><pre><code>npx tsc index.ts --target ES2022 --module node16 --moduleResolution node16 --outDir ./dist --strict --esModuleInterop</code></pre><p>Esto creará una carpeta <code>dist</code> y, dentro de ella, un archivo <code>index.js</code>.</p><h3>Carga el servidor MCP en Claude Desktop</h3><p>Sigue <a href="https://modelcontextprotocol.io/docs/develop/connect-local-servers">esta guía</a> para configurar el servidor MCP con Claude Desktop. En el archivo de configuración de Claude, tienes que establecer los siguientes valores:</p><pre><code>{
  "mcpServers": {
    "elasticsearch-rag-mcp": {
      "command": "node",
      "args": [   "/Users/user-name/app-dir/dist/index.js"
      ],
      "env": {
        "ELASTICSEARCH_ENDPOINT": "your-endpoint-here",
        "ELASTICSEARCH_API_KEY": "your-api-key-here",
        "OPENAI_API_KEY": "your-openai-key-here"
      }
    }
  }
}</code></pre><p>El valor <code>args</code> debe apuntar al archivo compilado en la carpeta <code>dist</code>. También es necesario configurar las variables de entorno en el archivo de configuración con los mismos nombres exactos definidos en el código.</p><h3>Pruébalo</h3><p>Antes de ejecutar cada herramienta, haz clic en <strong>Búsqueda y herramientas</strong> para asegurarte de que las herramientas estén habilitadas. Aquí también puedes habilitar o deshabilitar cada una de ellas:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/172981c2a54adabc70d5819013c3007670935605-1999x1002.png" style="width: 100%;" /><p>Finalmente, probemos el servidor MCP desde el chat de Claude Desktop y comencemos a hacer preguntas:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/03654c0f8c53c714f801fba8b25747071179209b-1999x1353.png" style="width: 100%;" /><p>Para la consulta<strong>&quot;Buscar documentos sobre métodos de autenticación y control de acceso basado en roles&quot;</strong>, se ejecuta la herramienta <code>search_docs</code> y arroja los siguientes resultados:</p><pre><code>Most Relevant Documents:
Access Control and Role Management (highest relevance) - This document covers role-based access control (RBAC) principles, including ensuring users only have necessary permissions, regular auditing of user roles, revoking inactive accounts, and implementing just-in-time access for sensitive operations.
User Authentication with OAuth 2.0 - This document explains OAuth 2.0 authentication, which enables secure delegated access without credential sharing. It covers configuring identity providers, token management with limited scope and lifetime, and secure storage of refresh tokens.
Container Security Guidelines - While primarily about container security, this document touches on access control aspects like running containers as non-root users and avoiding embedded credentials.
Incident Response Playbook - This mentions role assignment during incidents (incident commander, communications lead, etc.), which relates to access control in emergency scenarios.
Logging Standards for Microservices - This document includes guidance on avoiding logging sensitive information, which is relevant to authentication security.</code></pre><p>La respuesta es: “¡Genial! Encontré 5 documentos relevantes sobre métodos de autenticación y control de acceso basado en roles. Esto es lo que se encontró:”</p><p>La llamada a la herramienta devuelve los documentos de origen como parte de su carga útil de respuesta, que luego se utilizan para generar citas.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/25ff4c7e9563d99752f95540dafdc7fd211a66e3-800x530.gif" style="width: 100%;" /><p>También es posible encadenar varias herramientas en una sola interacción. En este caso, Claude Desktop analiza la pregunta del usuario y determina que primero debe llamar a <code>search_docs</code> para recuperar documentos relevantes y luego pasar esos resultados a <code>summarize_and_cite</code> para generar la respuesta final, todo eso sin requerir indicaciones separadas del usuario:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e6a8a46acb4236e77058f18bcd2f0737b5882c05-1999x1101.png" style="width: 100%;" /><p>En este caso, para la búsqueda: &quot;<strong>¿Cuáles son las principales recomendaciones para mejorar la autenticación y el control de acceso en todos nuestros sistemas? Incluye referencias.</strong>&quot;, obtuvimos los siguientes resultados:</p><pre><code>Based on the documentation, here are the main recommendations to improve authentication and access control across your systems:

Key Recommendations

1. Implement Role-Based Access Control (RBAC)
Ensure users have only the permissions necessary for their job functions. This principle of least privilege minimizes security risks by limiting access to what's truly needed. [1]

2. Regular Access Audits
Conduct regular audits of user roles and promptly revoke access for inactive accounts. This helps prevent unauthorized access from dormant or compromised accounts. [1]

3. Just-in-Time (JIT) Access
Implement just-in-time access for sensitive operations, where permissions are granted only when needed and for a limited duration. This reduces the attack surface by minimizing standing privileges. [1]

4. OAuth 2.0 for Secure Authentication
Use OAuth 2.0 to enable secure delegated access without sharing user credentials. This protocol allows third-party applications to access resources on behalf of users without exposing passwords. [2]

5. Token Security and Management
Configure your identity provider to issue tokens with limited scope and lifetime

Store refresh tokens securely
Validate access tokens consistently to maintain security [2]
References

Access Control and Role Management (Tags: security, access-control)
User Authentication with OAuth 2.0 (Tags: authentication, oauth)
These recommendations work together to create a defense-in-depth approach, where multiple security layers protect your systems from unauthorized access.</code></pre><p>Al igual que en el paso anterior, podemos ver la respuesta de cada herramienta a esta pregunta:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cb606d356b2f7d5e4878a5eff71bc881869ac0ee-800x585.gif" style="width: 100%;" /><p><em>Nota: si aparece un submenú que pregunta si apruebas el uso de cada herramienta, selecciona <strong>Permitir siempre</strong> o <strong>Permitir una vez</strong>.</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/aea942ba9b0037526ea215bec65690f1a5c3099c-1522x250.png" style="width: 100%;" /><h2>Conclusión</h2><p>Los servidores MCP representan un paso significativo hacia la estandarización de las herramientas LLM para aplicaciones tanto locales como remotas. Aunque la compatibilidad total todavía está en proceso, nos estamos moviendo rápido en esa dirección.</p><p>En este artículo, aprendimos cómo construir un servidor MCP personalizado en TypeScript que conecta Elasticsearch con aplicaciones impulsadas por modelos LLM. Nuestro servidor expone dos herramientas: <code>search_docs</code> para recuperar documentos relevantes con Query DSL y <code>summarize_and_cite</code> para generar resúmenes con citas a través de modelos de OpenAI y Claude Desktop como client UI.</p><p>El futuro de la compatibilidad entre los distintos proveedores de clientes y servidores parece prometedor. Los próximos pasos consisten en agregar más funcionalidades y flexibilidad a tu agente. Hay un <a href="https://www.elastic.co/search-labs/blog/llm-functions-elasticsearch-intelligent-query">artículo</a> práctico sobre cómo puedes agregar parámetros a tus consultas a través de plantillas de búsqueda para ganar precisión y flexibilidad.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Anuncio de los permisos de solo lectura para los dashboards de Kibana]]></title>
            <description><![CDATA[Presentamos los dashboards de solo lectura en Kibana, que ofrecen a los creadores de dashboards controles granulares para compartir y mantener los resultados precisos y protegidos de cambios no deseados.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/kibana-dashboards-read-only-permissions</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/kibana-dashboards-read-only-permissions</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Fabio Busatto, Teresa Alvarez Soler]]></dc:creator>
            <pubDate>Thu, 26 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/11e578bc317aea30d2e10ccc0334a532f6af2ef9-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Te ha pasado. Pasas una hora creando el dashboard perfecto para monitorear tus logs: cada gráfico, cada filtro y cada etiqueta. Lo compartes con tu equipo. Unos días después, lo abres y algo no cuadra. Un colega modificó una búsqueda. O alguien cambió el rango de fecha. Quizá pensaron que estaban ayudando. Ahora tienes que indagar en las revisiones y dudando de cada número. ¿Te suena?</p><p>Esa es exactamente la razón por la que construimos dashboards de <strong>solo lectura</strong>. Es el control que estabas pidiendo. Comparte dashboards con confianza, sin preocuparte de que la próxima persona con acceso de edición los cambie o rompa.</p><p>Nota: los permisos de solo lectura están disponibles en Elastic Cloud Serverless y, a partir de la versión 9.3, en Elastic Cloud Hosted y Elastic Cloud Self-Managed.</p><h2>Cuando &quot;todos pueden editar&quot; se interpone en el camino</h2><p>En Kibana, <em>compartir </em>generalmente ha significado permisos a nivel de espacio. Si alguien puede crear dashboards en un espacio, también puede editar o eliminar los de cualquier otro. Eso es excelente para la colaboración hasta que deja de serlo. Un solo error accidental puede propagarse en decisiones equivocadas, pérdida de confianza y mucho trabajo de limpieza.</p><p>Hemos escuchado las soluciones alternativas: <strong>&quot;Ponemos &#x27;solo lectura&#x27; en el nombre del dashboard y esperamos que la gente lo note&quot;.</strong> O: <strong>&quot;Los etiquetamos y cruzamos los dedos&quot;.</strong> Hope no es un modelo de permiso. Necesitabas una manera real de bloquear un dashboard sin bloquear a todos y dejarlos fuera del espacio.</p><h2>¿Qué sale mal realmente?</h2><p>Tanto Deb como Kevin tienen acceso de edición al dashboard de monitoreo de logs dentro del espacio de Operaciones. Kevin realiza algunos cambios en los gráficos. Cuando Deb vuelve, los números no coinciden con lo que ella presentó. Tiene que rastrear qué cambió (a menudo de memoria), corregirlo y preguntarse cuántos reportes se enviaron con datos incorrectos.</p><h2>Dashboards de solo lectura: propiedad y control que tienen sentido</h2><p>Los dashboards de solo lectura resuelven esto dándote el control para decidir si otros usuarios pueden editar el dashboard. Al compartir un dashboard, eliges: <strong>edit</strong> (editar, predeterminado, igual que hoy) o <strong>view</strong> (ver). En el modo <strong>view </strong>(ver), solo tú (y los administradores de Kibana) pueden cambiarlo o eliminarlo. Todos los demás pueden abrirlo, usarlo y confiar en él, pero no pueden modificarlo.</p><h3>Lo que obtienes</h3><ul><li><strong>Dashboard integrity:</strong> En el modo <strong>de visualización</strong>, los demás usuarios con acceso de edición en el espacio no pueden modificar ni eliminar el dashboard. Si lo intentan, se les indica que está bloqueado. Tus gráficos y lógica se mantienen tal como los dejaste.</li><li><strong>Mantienes el control:</strong> Tú eres el propietario. Siempre puedes editar, refinar y actualizar. Compartir en modo de solo lectura no te impide el acceso; restringe la versión que ven los demás.</li><li><strong>Ciclo de vida flexible:</strong> puedes volver a cambiar un dashboard a &quot;puedes editar&quot; en cualquier momento. Y los administradores de Kibana aún pueden gestionar todos los dashboards (por ejemplo, si el propietario se va). No hay callejón sin salida.</li></ul><p>Puedes compartir ampliamente los dashboards finales críticos para la misión y saber que se mantendrán coherentes. Esto está disponible en <strong>todos los niveles y ofertas de Elastic</strong>, incluido Serverless.</p><h3>¿Quién puede hacer qué?</h3><p>Guía rápida por rol:</p><ul><li><strong>Dashboard owner:</strong> Propietario del Dashboard. Tú lo creaste; tienes acceso completo de edición.</li><li><strong>Kibana admin:</strong> Administrador de Kibana. Puede administrar todos los dashboards.</li><li><strong>User with space edit:</strong> Usuario con edición de espacio. Puede crear y editar sus dashboards; no puede editar o eliminar dashboards de solo visualización.</li><li><strong>User with space view:</strong> Usuario con vista de espacio. Solo puede ver (y enumerar) dashboards.</li></ul>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Acción</td><td style="border: 1px solid #ccc; padding: 4px;">Propietario del dashboard</td><td style="border: 1px solid #ccc; padding: 4px;">Admin de Kibana</td><td style="border: 1px solid #ccc; padding: 4px;">Usuario con edición de espacio</td><td style="border: 1px solid #ccc; padding: 4px;">Usuario con vista de espacio</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Ver y enumerar dashboards</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Crear nuevos dashboards</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✘</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Modificar/eliminar dashboards editables</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✘</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Modificar/eliminar dashboards de solo lectura</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✘</td><td style="border: 1px solid #ccc; padding: 4px;">✘</td>
                    </tr>
              </tbody>
            </table>
          <h2>Cómo activar el modo de solo lectura</h2><p>Puedes configurar solo lectura cuando guardas un dashboard nuevo o más tarde desde el menú para compartir.</p><h3>Al guardar un nuevo dashboard</h3><ul><li>Construye tu dashboard y haz clic en <strong>Guardar</strong>.</li><li>En el modal “Guardar como nuevo dashboard”, busca <strong>Permisos</strong>.</li><li>Cambia de <strong>Can edit</strong> (Puede editar) a <strong>Can view</strong> (Puede ver).</li><li>Haz clic en <strong>Save</strong> (Guardar). Listo. Es de solo lectura para todos los demás.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/42a71d1bb55f9d50bd079f53bf45a0e1999b27f7-1214x1306.png" style="width: 100%;" /><h2>Para un dashboard que ya posees</h2><ul><li>Abre el dashboard.</li><li>Abre el menú <strong>Compartir dashboard</strong>.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e8405938557c879b1d4c262b98cf5a7f66408c04-1246x264.png" style="width: 100%;" /><ul><li>En el modal de compartir, ve a <strong>Permissions</strong> (Permisos) y cambia a <strong>Can view</strong> (Puede ver). El cambio entra en vigor de inmediato; los demás usuarios del espacio ya no podrán editarlo ni borrarlo.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/849405bc32701f3ebe0def012d8ae3cf3813ea0a-996x750.png" style="width: 100%;" /><ul><li>Puedes pasar el ratón sobre la acción <strong>Compartir</strong> para ver qué tipo de permisos tiene un dashboard determinado.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/80541ddb1b1bc567b0aeff693944ea8b6871d6a7-1270x320.png" style="width: 100%;" /><h3>Ver qué dashboards están bloqueados</h3><p>En la lista principal de dashboards, los dashboards que no puedes editar ni eliminar tienen una casilla de verificación de selección deshabilitada. Esto proporciona una forma sencilla de detectar lo que es solo de visualización.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/24b2eba08df86174db949c662e7886c5aea1b460-1999x876.png" style="width: 100%;" /><p>En el dashboard, también encontrarás que la acción Editar está deshabilitada y aparecerá un mensaje emergente que explica que el dashboard se configuró como solo lectura.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e0a2f9da6dc854e876fc6dc2a7c3ef8b313b52ef-1358x330.png" style="width: 100%;" /><h2>Pruébalo</h2><p>Los dashboards de solo lectura están disponibles ahora. Crea un dashboard, cambia a <strong>Can view</strong> (Puede ver) y compártelo. Tu equipo obtiene una única fuente de verdad, y tú obtienes tranquilidad. No más &quot;por favor, no editar&quot; en el título.</p><p>Nos encantaría saber cómo usas los dashboards de solo lectura. Comparte tus comentarios en nuestro <a href="https://discuss.elastic.co">foro de la comunidad</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[La herramienta de shell no es una solución mágica para la ingeniería de contexto]]></title>
            <description><![CDATA[Aprenda qué herramientas de recuperación de contexto existen para la ingeniería de contexto, cómo funcionan y sus compensaciones.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/search-tools-context-engineering</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/search-tools-context-engineering</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Leonie Monigatti]]></dc:creator>
            <pubDate>Wed, 25 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f91e4d07915ba7bf3b7abf15fac8fab3350f7df2-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Las herramientas más importantes con las que cuenta un agente son las herramientas de búsqueda que puede usar para construir su propio contexto. Publicaciones recientes de <a href="https://www.llamaindex.ai/blog/files-are-all-you-need">LlamaIndex</a> y <a href="https://x.com/hwchase17/status/2011814697889316930">LangChain</a> han desatado una discusión: <em>¿Son una herramienta shell y un sistema de archivos todo lo que un agente necesita para la ingeniería de contexto? </em>Desafortunadamente, la discusión se desvió rápidamente hacia el enfoque equivocado: filesystem versus base de datos.</p><p>Esta publicación vuelve a centrarse en la pregunta:<em>¿Cuáles son las interfaces de búsqueda adecuadas que necesita un agente para construir su propio contexto?</em> Primero, cubre las disyuntivas entre las herramientas de shell y las herramientas de base de datos especiales. A partir de ahí, te ofrece un marco de trabajo práctico para encontrar las interfaces adecuadas a las necesidades de tu agente.</p><h2>¿Qué significa realmente &quot;construir contexto&quot; para un agente?</h2><p>En las primeras <a href="https://www.elastic.co/what-is/retrieval-augmented-generation">pipelines de retrieval augmented generation (RAG)</a>, el desarrollador diseñó un pipeline de recuperación fija y el modelo de lenguaje grande (LLM) era un receptor pasivo del contexto. Esta era una limitación fundamental: el contexto se recuperaba en cada consulta, fuera o no necesario, sin verificar que realmente ayudara.</p><p>Con el cambio a la RAG agéntica, los agentes ahora tienen acceso a un conjunto de herramientas de búsqueda para crear su propio contexto. Por ejemplo, tanto Claude Code [1] como Cursor [2] permiten que el agente elija entre diferentes herramientas de búsqueda e incluso las combine para consultas encadenadas, dependiendo de lo que la tarea realmente requiera.</p><h2>¿Qué interfaces de búsqueda existen para la ingeniería del contexto?</h2><p>El contexto puede estar en diferentes lugares, como en la web, en un sistema de archivos local o en una base de datos. Un agente puede interactuar con cada una de estas fuentes de datos fuera de contexto mediante diferentes herramientas:</p><ul><li>Las <strong>herramientas de shell</strong> pueden ejecutar comandos de shell y tener acceso al sistema de archivos local. Algunos ejemplos de herramientas de shell integradas son <a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/bash-tool">la herramienta bash de Claude API</a>, <a href="https://docs.openclaw.ai/tools/exec">la herramienta ejecutiva de OpenClaw</a> y <a href="https://docs.langchain.com/oss/python/integrations/tools/bash">la herramienta de shell de LangChain</a>.</li><li>Las <strong>herramientas de base de datos especiales,</strong> como las herramientas de un servidor Model Context Protocol (MCP) (p. ej., el <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/mcp-server">servidor MCP de Elastic Agent Builder)</a> o las herramientas personalizadas (p. ej., <code>run_esql(query)</code> o <code>db_list_index()</code>), pueden consultar bases de datos.</li><li><strong>Las herramientas especiales de búsqueda de archivos</strong> pueden buscar y leer archivos locales (o subidos) (sin acceso completo al shell). Algunos ejemplos de herramientas de búsqueda de archivos integradas son <a href="https://ai.google.dev/gemini-api/docs/file-search">Herramienta de búsqueda de archivos de Gemini API</a> o <a href="https://developers.openai.com/api/docs/guides/tools-file-search">Herramienta de búsqueda de archivos de OpenAI</a>.</li><li>Las <strong>herramientas de búsqueda web</strong> pueden recuperar información de la web.</li><li>Las <strong>herramientas de memoria</strong> almacenan y recuperan de la memoria a largo plazo (independientemente de cómo se almacene).</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/115f20c8ded259e508f51524b2c06bdc702d70ab-1999x1050.png" style="width: 100%;" /><p>Como puedes ver, la herramienta shell es versátil y se puede usar para recuperar contexto de diferentes fuentes de datos, incluyendo:</p><ul><li><strong>Sistema de archivos:</strong> el agente explora la estructura de directorios (ls, find), busca contenido relevante (grep, cat) y repite hasta que ha construido suficiente contexto.</li><li><strong>Base de datos:</strong> el agente puede usar herramientas de interfaz de línea de comandos (CLI) para bases de datos (por ejemplo, <a href="https://www.elastic.co/docs/reference/query-languages/sql/sql-cli"><code>elasticsearch-sql-cli</code></a>), llamar al HTTP de la API mediante curl o ejecutar scripts, lo cual resulta especialmente útil en combinación con las habilidades del agente, que son ejemplos reutilizables y documentados que se incorporan al contexto del agente para guiar el uso correcto de las herramientas (por ejemplo, <a href="https://github.com/elastic/agent-skills">Elastic Agent Skills para Elasticsearch</a>).</li><li><strong>Web: </strong>el agente puede ejecutar búsquedas web mediante un comando curl a través de la API de un proveedor de búsqueda.</li></ul><p>Sin embargo, la herramienta de shell proporciona acceso directo al sistema y, por lo tanto, requiere medidas de seguridad, como ejecutarse en un entorno sandbox aislado y el logging de todos los comandos ejecutados.</p><h2>Cuándo deberías usar ciertas interfaces de búsqueda</h2><p>La interfaz de búsqueda adecuada depende de tus datos, tus patrones de consulta y tu caso de uso. Esta sección sirve como un punto de partida práctico.</p><h3>Los sistemas de archivos no hacen que las bases de datos sean obsoletas.</h3><p>La discusión entre sistemas de archivos y bases de datos no es sobre la capa de almacenamiento. Por ejemplo, LangChain explica que <a href="https://x.com/hwchase17/status/2011814697889316930">su sistema de memoria</a> en realidad no almacena la memoria en un verdadero sistema de archivos. En su lugar, almacena la memoria en una base de datos y la <em>representa</em> como un conjunto de archivos para el agente [3].</p><p>Los sistemas de archivos son una opción natural para casos de uso nativos de archivos, como los agentes de codificación. También funcionan bien como bloc de notas temporal o memoria de trabajo, y en situaciones con un solo usuario o un solo agente en las que la concurrencia no es un problema. En estos casos, un sistema de archivos físico o representar los datos como un sistema de archivos te da flexibilidad antes de comprometerte con una interfaz diseñada específicamente para ello.</p><p>Pero el almacenamiento en sistemas de archivos tiene desventajas reales, como una concurrencia limitada, la aplicación manual de esquemas y las transacciones atómicas. Estos se vuelven más evidentes cuando tu aplicación necesita escalar o pasar a un escenario de múltiples agentes. Cualquiera que ignore estas desventajas está condenado a <a href="https://dx.tips/oops-database">reinventar dolorosamente bases de datos peores</a> sin las décadas de ingeniería detrás de la seguridad de transacciones o el control de acceso que las bases de datos de producción ya proporcionan. Además, en la mayoría de contextos empresariales, no eliges si usar una base de datos porque ya está ahí, almacenando datos críticos para el negocio.</p><h3>Herramienta de shell + sistema de archivos</h3><p>Una herramienta shell es el punto de partida natural para la búsqueda en sistemas de archivos. En la actualidad, los agentes de codificación están impulsando muchos avances en este campo. Como trabajan con código en archivos locales, son, por naturaleza, casos de uso que implican un gran volumen de archivos. Por lo tanto, los LLM se ajustan en la etapa posterior al entrenamiento para tareas de codificación. Es por eso que muchos LLMs no solo son buenos para escribir código, sino también para usar comandos de shell y navegar por sistemas de archivos.</p><p>Usar una herramienta de shell con CLI integradas, como <code>ls</code> y <code>grep</code>, para encontrar archivos es efectivo. Con grep, una consulta como &quot;Encontrar todos los archivos que importan <code>matplotlib</code>&quot; es rápida, precisa y económica. Pero cuando el agente necesita manejar consultas conceptuales, como &quot;¿Cómo maneja nuestra app la falla de autenticación?&quot;, la coincidencia de patrones con grep puede alcanzar un límite rápidamente. Han surgido varias alternativas que incorporan capacidades de búsqueda semántica a la línea de comandos para cubrir esta carencia, incluidas <a href="https://github.com/jina-ai/jina-grep-cli"><code>jina-grep</code></a>.</p><p>Sin embargo, grep y muchas de sus alternativas de búsqueda semántica se ejecutan en O(n) sobre el corpus. Para casos de uso sobre bases de código, esto podría estar bien. Sin embargo, si tus datos crecen, la latencia se notará. En este caso, un almacén de datos indexado se vuelve necesario para mantener el rendimiento.</p><h3>Herramienta de shell + base de datos</h3><p>Otra forma de agregar más capacidades de búsqueda, como la búsqueda semántica o híbrida, a tus datos es almacenarlos en una base de datos, como hace Cursor, por ejemplo. Además, cuando los datos requieren uniones relacionales o agregaciones complejas, una interfaz de base de datos es imprescindible.</p><p>Cuando los datos se almacenan en una base de datos en lugar de en el sistema de archivos, una herramienta de shell puede servir como una interfaz ligera de base de datos para ciertos casos de uso. Si tus consultas son lo suficientemente simples para una CLI o una llamada curl, una herramienta de base de datos especial podría añadir una complejidad innecesaria.</p><p>Este enfoque también es adecuado en las etapas iniciales de exploración, cuando aún no sabes qué patrones de consulta desarrollará tu agente. En este caso, Agent Skills puede darle al agente suficiente estructura para consultar correctamente sin comprometerse con una herramienta específica. Sin embargo, cuando el agente requiere muchas iteraciones para encontrar la forma correcta de consultar en la base de datos para tareas repetidas, la sobrecarga de tokens de usar una herramienta de línea de comandos como interfaz ya no justifica el beneficio de la simplicidad de evitar una herramienta adicional.</p><h3>Herramienta especial de base de datos</h3><p>Especialmente cuando los patrones de consulta repetidos son estructurados o analíticos, se hacen necesarias herramientas de base de datos especiales. Una <a href="https://vercel.com/blog/testing-if-bash-is-all-you-need">publicación de blog de Vercel y Braintrust</a> comparó a los agentes con diferentes conjuntos de herramientas de búsqueda para tareas de recuperación del mundo real en lugar de datos estructurados, como tickets de atención al cliente y transcripciones de llamadas de ventas (por ejemplo, “¿Cuántos problemas abiertos mencionan &#x27;seguridad&#x27;?&quot; o &quot;¿Encontraste problemas en los que alguien reportó un error y luego alguien envió un PR diciendo que lo había arreglado?&quot;) [4].</p><p>Los agentes con herramientas de base de datos especiales utilizaron menos tokens, fueron más rápidos y cometieron menos errores que los agentes con solo una herramienta de shell y un sistema de archivos. La conclusión es que las herramientas de bases de datos directas son la opción correcta cuando la consulta requiere razonamiento analítico sobre datos semiestructurados.</p><h3>Combinar interfaces de búsqueda</h3><p>Ninguna interfaz de búsqueda gestiona bien todas las consultas. Por ejemplo, Cursor combina herramientas de shell (para búsquedas con grep) y herramientas de búsqueda semántica, y permite que el agente seleccione la herramienta correcta según el mensaje del usuario. Informa que el agente elige grep para hacer coincidir símbolos o textos específicos, búsqueda semántica para preguntas conceptuales o de comportamiento, y ambos para tareas exploratorias.</p><p>El experimento de Vercel presenta el mismo reporte: su agente híbrido con acceso tanto a una herramienta de shell como a una herramienta dedicada para bases de datos logró el mejor rendimiento de todos los agentes probados al usar primero las herramientas dedicadas para bases de datos y luego verificar los resultados mediante búsquedas con grep en el sistema de archivos. Sin embargo, este enfoque utiliza más tokens y tiempo para razonar sobre la elección de herramientas y la verificación.</p><p>El patrón en ambos ejemplos es el mismo: La combinación es superior a cualquier interfaz individual, pero conlleva un costo y una latencia adicionales.</p><h2>Recomendaciones prácticas para encontrar el conjunto adecuado de herramientas</h2><p>El conjunto adecuado de interfaces de búsqueda es pequeño, intencionado y específico para los patrones de consulta reales de tu agente. Las mejores prácticas actuales son tener un agente con la menor cantidad de herramientas posible en lugar de tener un agente con cientos de herramientas MCP. Esto se debe a que la desventaja de exponer todas las herramientas posibles de antemano es que infla la ventana de contexto y confunde al agente sobre qué herramienta usar realmente. Por ejemplo, se dice que Claude Code solo tiene unas 20 herramientas.</p><p>En cambio, la idea de la divulgación progresiva es comenzar con un conjunto mínimo de herramientas y dejar que el agente descubra capacidades adicionales solo cuando sea necesario. Investigaciones de Anthropic [5] y Cursor [6] demostraron que este enfoque genera un ahorro de tokens entre el 47%–85%. Claude Code, por ejemplo, implementa esto directamente, lo que permite al agente descubrir de forma incremental cómo consultar una API o una base de datos, sin que ese conocimiento consuma contexto en cada llamada al LLM.</p><p>Una vez que te familiarices con los patrones de búsqueda del agente, puedes volver a revisar el conjunto de herramientas de búsqueda a las que el agente tiene acceso de forma predeterminada. Una forma útil de pensar en este compromiso es el <a href="https://www.elastic.co/search-labs/blog/database-retrieval-tools-context-engineering#building-the-right-database-retrieval-tools-%5C(%E2%80%9Clow-floor,-high-ceiling%E2%80%9D%5C">&quot;principio de piso bajo, techo alto&quot;</a> para decidir qué herramientas son las adecuadas. Las herramientas de alto nivel no limitan el potencial del agente. Por ejemplo, una herramienta de shell versátil permite al agente escribir consultas de base de datos completas, incluidas las ambiguas, pero a costa de una mayor sobrecarga de razonamiento, latencia más alta y menor confiabilidad.</p><p>Las herramientas de bajo umbral son todo lo contrario. Son herramientas especializadas que encapsulan búsquedas específicas y son inmediatamente accesibles para el agente con una mínima sobrecarga de razonamiento, ofreciendo menor costo y mayor confiabilidad. No obstante, requieren ingeniería previa, no pueden cubrir cada posible consulta y pueden dificultar que el agente elija la herramienta correcta.</p><p>Piensa en cada herramienta en un espectro: las herramientas de bajo nivel son fáciles de usar correctamente para el agente, pero tienen un alcance limitado. Las herramientas de alto potencial son versátiles pero requieren más razonamiento para usarlas bien.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e6d1b973be4b0a0a25c99c74f02a47e98395a3f7-1200x630.png" style="width: 100%;" /><p>La mayoría de los agentes necesitan una combinación de diferentes herramientas de búsqueda. Pero cada herramienta necesita aportar algo. Recomendamos comenzar con una herramienta de búsqueda multiuso (por ejemplo, una herramienta <code>search_database()</code> o una herramienta de shell). Luego, reutiliza los registros de comandos que ya conservas por motivos de seguridad para rastrear lo que realmente hace tu agente, incluidas las llamadas a herramientas, los reintentos y el número de llamadas por consulta de usuario. Y, cuando ves que un patrón de consulta se repite o falla, esa es la señal para construir una herramienta especialmente diseñada para ello.</p><h2>Resumen</h2><p>El debate entre sistema de archivos y base de datos distrae de la pregunta real que los ingenieros deben hacerse: <em>¿Cuáles son las interfaces de búsqueda adecuadas que un agente necesita para construir su propio contexto?</em> La respuesta es muy probable, <em>no una sola</em>.</p><p>Una herramienta de shell es una herramienta versátil para interactuar con diferentes fuentes fuera de contexto y, por lo tanto, un buen punto de partida. Sin embargo, resulta menos eficiente y precisa para casos de uso con consultas analíticas estructuradas que las herramientas de bases de datos especializadas.</p><p>El objetivo es encontrar el conjunto mínimo de herramientas de búsqueda que manejen bien los patrones reales de consulta de tu agente. Empieza con una herramienta de shell y registra lo que realmente hace tu agente. Cuando veas un patrón de consulta que se repite y falla, es momento de diseñar herramientas especializadas.</p><h2>Referencias</h2><p>1. Thariq (Anthropic). <a href="https://x.com/trq212/status/2027463795355095314">Lessons from Building Claude Code: Seeing like an Agent</a> (2026).</p><p>2. Cursor: Documentation. <a href="https://cursor.com/docs/agent/tools/search">Semantic &amp; agentic search</a> (2026).</p><p>3. Harrison Chase (LangChain). <a href="https://x.com/hwchase17/status/2011814697889316930">Cómo construimos el sistema de memoria de Agent Builder</a> (2026).</p><p>4. Ankur Goyal (Braintrust) y Andrew Qu (Vercel). <a href="https://vercel.com/blog/testing-if-bash-is-all-you-need">Testing if &quot;bash is all you need&quot;</a> (2026).</p><p>5. Anthropic. <a href="https://www.anthropic.com/engineering/advanced-tool-use">Introducing advanced tool use on the Claude Developer Platform</a> (2025).</p><p>6. Cursor. <a href="https://cursor.com/blog/dynamic-context-discovery">Dynamic context discovery</a> (2026).</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Réplicas de Elasticsearch para balanceo de carga en Serverless]]></title>
            <description><![CDATA[Aprende cómo Elastic Cloud Serverless ajusta automáticamente las réplicas del índice según la carga de búsqueda, lo que garantiza un rendimiento óptimo de búsqueda sin configuración manual.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elasticsearch-replicas-load-balancing-serverless</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elasticsearch-replicas-load-balancing-serverless</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Andrei Dan]]></dc:creator>
            <pubDate>Tue, 24 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3c4c1e99b892f026b7aba098973593f8298e2ea6-1280x717.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>En Elastic Cloud Serverless, ajustamos automáticamente el número de réplicas de tus índices en función de la carga de búsqueda, lo que garantiza un rendimiento óptimo de las consultas sin necesidad de realizar una configuración manual. En este blog, te mostraremos cómo se escalan las réplicas, cuándo el sistema las agrega o elimina, y qué significa esto para tus índices.</p><h2>La fiesta se está llenando de gente</h2><p>Organizas una fiesta de pizzas. Tienes a unos cuantos amigos que te ayudan a servirlas, cada uno en un lugar distinto de la sala. Le das a cada amigo una pizza y ellos empiezan a repartir rebanadas a los invitados hambrientos a medida que llegan.</p><p>Al principio, todo marcha sin problemas. Unos cuantos invitados van llegando de a poco, tus amigos sirven porciones y todos están contentos. Pero entonces se corre la voz sobre tus pizzas de masa madre. El timbre sigue sonando. Los invitados siguen llegando en masa. Pronto se forma una multitud alrededor de uno de tus amigos, el que lleva la pizza de pepperoni, que parece ser la que quieren todos.</p><p>Tu amigo con la pizza de pepperoni está abrumado. Los invitados están esperando, se ponen impacientes y se formó una larga cola. Mientras tanto, tu amiga que sostiene la pizza margherita está de pie con casi nadie pidiéndole una porción.</p><p>¿Qué haces?</p><p>Pides un par de pizzas de pepperoni más y se las das a otros amigos. Ahora tres amigos tienen pepperoni en lugar de uno. La multitud se dispersa y, de repente, puedes atender a tres veces más invitados a la vez.</p><p>A medida que organizas más fiestas, hay algunas cosas que se vuelven claras:</p><ul><li><strong>No todas las pizzas son igual de populares.</strong> Algunas tienen mucha demanda; otras tienen menos interesados. No necesitas &quot;copias&quot; adicionales de las menos populares. Necesitas más de las que todos esperan en cola.</li><li><strong>Pide más pizzas antes de que la fila se haga demasiado larga.</strong> Si esperas hasta que tu amigo esté completamente abrumado y los invitados se estén yendo enojados, esperaste demasiado tiempo. Es mejor pedir una pizza más cuando ves que se está formando una multitud.</li><li><strong>No deseches las pizzas demasiado rápido.</strong> Solo porque la multitud alrededor del pepperoni se redujo durante cinco minutos no significa que la avalancha haya terminado. Tal vez solo están sirviéndose bebidas, o incluso hablando entre ellos (¿eso todavía se hace?). Mantén las pizzas adicionales listas. Si la calma se mantiene por un rato, entonces puedes guardarlas.</li><li><strong>Solo puedes repartir tantas pizzas como amigos tengas que te estén ayudando.</strong> Si solo tienes cuatro amigos ayudándote, diez pizzas no cambiarán el resultado. Solo se pueden servir cuatro a la vez. Haz coincidir tu cantidad de pizzas con tus manos disponibles.</li><li><strong>Cuando un amigo se vaya, llévate su pizza.</strong> Si uno de tus amigos necesita salir, toma su pizza inmediatamente. No puedes dejar las pizzas sin supervisión. Dásela a otra persona, o guárdala.</li></ul><h2>Desde pizzas hasta réplicas</h2><p>Vamos a mapear esto de vuelta a Elasticsearch.</p><p>En nuestra analogía, las pizzas son réplicas (copias de tus fragmentos de índice), tus colegas ayudando a servir son nodos de búsqueda, los invitados hambrientos son consultas de búsqueda, y esa pizza popular con mucha gente alrededor es un índice caliente con alta carga de búsqueda.</p><p>Cuando el tráfico de búsqueda aumenta en un índice en particular, creamos réplicas adicionales y las distribuimos en tus nodos de búsqueda. Cualquier réplica puede responder a cualquier consulta sobre ese índice, igual que cualquier amigo que tenga pepperoni puede repartir rebanadas de pepperoni. Más réplicas significan un mayor rendimiento: tres réplicas pueden manejar tres veces las consultas por segundo de una sola réplica.</p><h2>Medir el hambre</h2><p>Antes de decidir cuántas pizzas pedir, necesitamos saber qué tan hambrienta está la multitud.</p><p>Elasticsearch rastrea la <strong>carga de búsqueda</strong> de cada fragmento. Es una métrica que mide el volumen de actividad de búsqueda que gestiona un fragmento. Sumamos estos datos de todos los fragmentos de un índice para conocer la demanda total de búsquedas.</p><p>Lo que más importa es la <strong>carga de búsqueda relativa</strong>: ¿qué proporción del tráfico de búsqueda total de tu proyecto está llegando a cada índice? Si un índice recibe el 60 % de todas las búsquedas mientras que otro recibe el 5 %, sabemos dónde debemos aumentar la capacidad.</p><h2>La matemática detrás de las pizzas</h2><p>Calculamos el número óptimo de réplicas siguiendo esta fórmula:</p><pre><code>desired_replicas = min(ceil(L × N / (S × X)), N)</code></pre><p>Dónde:</p><ul><li><strong>L</strong> = la carga de búsqueda relativa del índice (entre 0 y 1).</li><li><strong>N</strong> = el número de nodos de búsqueda deseados en tu proyecto.</li><li><strong>S</strong> = la cantidad de fragmentos en el índice.</li><li><strong>X</strong> = un umbral para evitar puntos calientes (predeterminado: 0,5).</li></ul><p>Un ejemplo: cuatro nodos de búsqueda, un índice con dos fragmentos primarios que reciben el 80 % del tráfico de búsqueda:</p><pre><code>desired_replicas = min(ceil(0.8 × 4 / (2 × 0.5)), 4)
                 = min(4, 4)
                 = 4</code></pre><p>Este índice activo tiene cuatro réplicas distribuidas entre los nodos de búsqueda.</p><p>El umbral X (con un valor predeterminado de 0,5) es importante. No esperamos hasta que una réplica esté completamente saturada, sino que escalamos cuando está a la mitad de su capacidad. Reparte la pizza adicional cuando veas que la multitud empieza a formarse, no cuando los invitados ya se están yendo.</p><h2>Escala rápido, reduce la escala lentamente</h2><p>Cuando aumenta la carga de búsqueda, agregamos réplicas inmediatamente. No hay razón para hacer esperar a los usuarios.</p><p>Cuando la carga de búsqueda disminuye, esperamos un poco antes de tomar alguna acción. Necesitamos ver una demanda baja constante durante unos 30 minutos antes de reducir las réplicas. (Esto es para manejar el tráfico irregular donde un momento de calma no significa que la fiesta haya terminado.)</p><p>Esto importa porque agregar una réplica tiene un costo. La réplica nueva copia datos y calienta sus cachés antes de servir las consultas de manera eficiente. Si eliminas las réplicas con demasiada prisa, terminarás pagando este costo inicial constantemente, ya que el tráfico fluctúa de forma natural.</p><h2>Respetando los límites de la topología</h2><p>Las réplicas nunca pueden superar el número de nodos de búsqueda. Tener más réplicas que nodos no aporta ningún beneficio (solo puedes servir tantas pizzas como colegas que ayuden a servir las porciones).</p><p>Cuando se eliminan nodos de tu proyecto, reducimos las réplicas inmediatamente para que coincidan. Sin esperar el enfriamiento, ya que no puedes tener réplicas sin asignar. En cuanto un usuario se va, le quitamos la pizza.</p><h2>El panorama general de Elastic Cloud Serverless</h2><p>Las réplicas para el balanceo de carga de búsqueda funcionan junto con otros sistemas de autoescalado:</p><ul><li><strong>El escalado automático de búsqueda</strong> ajusta el número de nodos de búsqueda (cuántos amigos están ayudando).</li><li><strong>Las réplicas para el balanceo de carga de búsqueda</strong> distribuyen el tráfico al ajustar la cantidad de réplicas por índice (cuántas pizzas de cada tipo necesitamos).</li><li><strong>La fragmentación automática de flujos de datos</strong> optimiza el número de particiones para las operaciones de escritura (cómo cortar cada pizza, como se explicó en la <a href="https://www.elastic.co/search-labs/blog/datastream-autosharding-serverless">publicación anterior</a>).</li></ul><p>Un principio de diseño importante: las réplicas para el equilibrio de carga no activan directamente el escalado automático de búsqueda. En cambio, distribuir las consultas de búsqueda entre más réplicas permite aumentar la utilización de recursos en todos tus nodos de búsqueda. Este mayor uso luego activa nuestra lógica de escalado automático existente para agregar capacidad si es necesario. Las réplicas para el balanceo de carga permiten que el autoescalado haga su trabajo, lo que asegura que tus nodos de búsqueda realmente se estén utilizando, en lugar de que todo el tráfico esté bloqueado en una sola réplica mientras otros nodos permanecen inactivos.</p><h2>Qué significa esto para ti</h2><p>No necesitas predecir qué índices serán populares. No necesitas ajustar manualmente las réplicas cuando cambian los patrones de tráfico. No necesitas despertarte a las 3 a. m. porque un aumento repentino sobrecargó tu índice más ocupado.</p><p>El sistema observa dónde se están formando colas y pide más pizzas para esos lugares. Los índices fríos no desperdician recursos en réplicas innecesarias. Los índices calientes obtienen la capacidad que necesitan. Tu presupuesto se destina a donde importa.</p><h2>Conclusión</h2><p>En la <a href="https://www.elastic.co/search-labs/blog/datastream-autosharding-serverless">publicación sobre la autofragmentación (autosharding)</a> nos aseguramos de que tus pizzas estén cortadas correctamente. Ahora, con réplicas para el equilibrio de carga de búsqueda, nos aseguramos de que tengas suficientes pizzas, en las manos adecuadas, cuando llegue la multitud hambrienta.</p><p>Prueba <a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a> y déjanos encargarnos de la logística de la pizza.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Uso de la API de inferencia de Elasticsearch junto con modelos de Hugging Face]]></title>
            <description><![CDATA[Aprende a conectar Elasticsearch a modelos de Hugging Face usando endpoints de inferencia y crea un sistema multilingüe de recomendación de blogs con búsqueda semántica y finalización de chat.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/hugging-face-elasticsearch-inference-api</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/hugging-face-elasticsearch-inference-api</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
            <pubDate>Mon, 23 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1417d6ff033712206c9bd4bcc22074ee3437ce96-1999x1125.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>En actualizaciones recientes, Elasticsearch introdujo una integración nativa para conectar a modelos hospedados en el <a href="https://endpoints.huggingface.co/">servicio de inferencia Hugging Face</a>. En esta publicación, veremos cómo configurar esta integración y realizar inferencias mediante llamadas a la API sencillas utilizando un modelo de lenguaje grande (LLM). Usaremos <a href="https://huggingface.co/HuggingFaceTB/SmolLM3-3B">SmolLM3-3B</a>, un modelo ligero de propósito general con un buen equilibrio entre el uso de recursos y la calidad de las respuestas.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7ddadf1976421a860a7d62087239adb9150d808b-1999x1388.png" style="width: 100%;" /><h2>Requisitos previos</h2><ul><li><strong>Elasticsearch 9.3 o Elastic Cloud Serverless: </strong>puedes crear un despliegue en la cloud siguiendo <a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud">estas instrucciones</a>, o bien puedes utilizar la guía de inicio rápido de <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart#local-dev-quick-start"><code>start-local</code></a>.</li><li><strong>Python 3.12: </strong>Descarga Python <a href="https://www.python.org/">aquí</a>.</li><li><a href="https://huggingface.co/docs/hub/en/security-tokens">Token de acceso </a><strong>Hugging Face</strong>.</li></ul><h2>Finalización de chat usando un endpoint de inferencia de Hugging Face</h2><p>Primero, vamos a crear un ejemplo práctico que conecte Elasticsearch con un <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put">endpoint de inferencia</a> de Hugging Face para generar recomendaciones basadas en IA a partir de una colección de publicaciones de blog. Para la base de conocimientos de la app, usaremos un set de datos de artículos del blog de la compañía, que contiene información valiosa pero a menudo difícil de navegar.</p><p>Con este endpoint, <a href="https://www.elastic.co/docs/solutions/search/semantic-search">la búsqueda semántica</a> recupera los artículos más relevantes para una consulta dada, y un LLM de Hugging Face genera recomendaciones breves y contextuales basadas en esos resultados.</p><p>Echemos un vistazo a una visión general de alto nivel del flujo de información que vamos a construir:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1dfbc2323438feaaa42e13ab242dd1f7166f74aa-1200x676.png" style="width: 100%;" /><p>En este artículo, probaremos la <strong>capacidad de SmolLM3-3B </strong>para<strong> </strong>combinar su tamaño compacto con fuertes capacidades multilingües de razonamiento y llamadas a herramientas. A partir de una búsqueda, enviaremos todo el contenido correspondiente (en inglés y español) al LLM para generar una lista de artículos recomendados con una descripción personalizada basada en la búsqueda y los resultados.</p><p>Así podría ser la UI de un sitio web de artículos con un sistema de generación de recomendaciones basado en inteligencia artificial.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8d3b86b212f28ff279f2da67a33e6134039f0e4e-1999x949.png" style="width: 100%;" /><p>Puedes encontrar la implementación completa de esta aplicación en el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/elasticsearch-inference-api-and-hugging-face/notebook.ipynb">cuaderno</a> adjunto.</p><h3>Configurar endpoints de inferencia de Elasticsearch</h3><p>Para usar el <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-hugging-face">endpoint de inferencia Elasticsearch Hugging Face</a>, necesitamos dos elementos importantes: una clave API de Hugging Face y una URL del endpoint de Hugging Face en funcionamiento. Debería verse así:</p><pre><code>PUT _inference/chat_completions/hugging-face-smollm3-3b
{
    "service": "hugging_face",
    "service_settings": {
        "api_key": "hugging-face-access-token", 
        "url": "url-endpoint" 
    }
}</code></pre><p>El endpoint de inferencia Hugging Face en Elasticsearch admite diferentes tipos de tareas: <code>text_embedding</code>, <code>completion</code>, <code>chat_completion</code> y <code>rerank</code>. En esta publicación de blog, usamos <code>chat_completion</code> porque necesitamos que el modelo genere recomendaciones conversacionales basadas en los resultados de búsqueda y una solicitud del sistema. Este endpoint nos permite realizar finalizaciones de chat directamente desde Elasticsearch de una manera sencilla empleando la API de Elasticsearch:</p><pre><code>POST _inference/chat_completion/hugging-face-smollm3-3b/_stream
{
  "messages": [
      { "role": "user", "content": "<user prompt>" }
  ]
}</code></pre><p>Esto servirá como núcleo de la aplicación, recibirá la indicación y los resultados de búsqueda que pasarán por el modelo. Ya abordamos la teoría, ahora comencemos a implementar la aplicación.</p><h4>Configuración de un endpoint de inferencia en Hugging Face</h4><p>Para desplegar el modelo de Hugging Face, vamos a usar <a href="https://huggingface.co/inference-endpoints/dedicated">despliegues con un clic de Hugging Face</a>, un servicio fácil y rápido para desplegar endpoints de modelos. Ten en cuenta que este es un servicio de pago y que su uso puede generar costos adicionales. En este paso se creará la instancia del modelo que se usará para generar las recomendaciones de artículos.</p><p>Puedes elegir un modelo del catálogo de un clic:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b816e9fba1fe172687bf58f5143fb1f838c1077f-549x331.png" style="width: 100%;" /><p>Vamos a elegir el modelo <strong>SmolLM3-3B</strong>:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/610d3aba0429f3666c2df3616d513eb6a4397c0c-502x478.png" style="width: 100%;" /><p>Desde aquí, copia la URL del endpoint de Hugging Face:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/025094ddb2cfbd1f0f216a5ec4e119b0f4fa2c42-646x328.png" style="width: 100%;" /><p>Como se menciona en la <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-hugging-face">documentación de endpoints de inferencia Hugging Face</a> de Elasticsearch, la generación de texto requiere un modelo compatible con la API de OpenAI. Por esa razón, necesitamos anexar la ruta secundaria <code>/v1/chat/completions</code> a la URL del endpoint de Hugging Face. El resultado final se verá así:</p><pre><code>https://j2g31h0futopfkli.us-east-1.aws.endpoints.huggingface.cloud/v1/chat/completions</code></pre><p>Ahora que está todo listo, podemos empezar a programar en un cuaderno de Python.</p><h4>Generando clave API de Hugging Face</h4><p>Crea una <a href="https://huggingface.co/join">cuenta en Hugging Face</a> y obtén un token de API siguiendo <a href="https://huggingface.co/docs/hub/en/security-tokens#user-access-tokens">estas instrucciones</a>. Puedes elegir entre tres tipos de token: <em>detallado</em> (recomendado para producción, ya que proporciona acceso solo a recursos específicos); <em>lectura</em> (para acceso de solo lectura); o <em>escritura</em> (para acceso de lectura y escritura). Para este tutorial, un token de lectura es suficiente, ya que solo necesitamos llamar al endpoint de inferencia. Guarda esta clave para el siguiente paso.</p><h4>Configuración del endpoint de inferencia de Elasticsearch</h4><p>Primero, declaremos un cliente de Python para Elasticsearch:</p><pre><code>os.environ["ELASTICSEARCH_API_KEY"] = "your-elasticsearch-api-key"
os.environ["ELASTICSEARCH_URL"] = "https://xxxx.us-central1.gcp.cloud.es.io:443"

es_client = Elasticsearch(
    os.environ["ELASTICSEARCH_URL"], api_key=os.environ["ELASTICSEARCH_API_KEY"]
)</code></pre><p>A continuación, vamos a crear un endpoint de inferencia de Elasticsearch que use el modelo Hugging Face. Este endpoint nos permitirá generar respuestas basadas en las entradas del blog y en el prompt que se pasó al modelo.</p><pre><code>INFERENCE_ENDPOINT_ID = "smollm3-3b-pnz"

os.environ["HUGGING_FACE_INFERENCE_ENDPOINT_URL"] = (
 "https://j2g31h0futopfkli.us-east-1.aws.endpoints.huggingface.cloud/v1/chat/completions"
)
os.environ["HUGGING_FACE_API_KEY"] = "hf_xxxxx"

resp = es_client.inference.put(
        task_type="chat_completion",
        inference_id=INFERENCE_ENDPOINT_ID,
        body={
            "service": "hugging_face",
            "service_settings": {
                "api_key": os.environ["HUGGING_FACE_API_KEY"],
                "url": os.environ["HUGGING_FACE_INFERENCE_ENDPOINT_URL"],
            },
        },
    )</code></pre><h3>Set de datos</h3><p>El conjunto de datos contiene las <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/elasticsearch-inference-api-and-hugging-face/dataset.json">publicaciones de blog</a> que se consultarán, representando un conjunto de contenido multilingüe utilizado a lo largo del flujo de trabajo:</p><pre><code>// Articles dataset document example: 
{
    "id": "6",
    "title": "Complete guide to the new API: Endpoints and examples",
    "author": "Tomas Hernandez",
    "date": "2025-11-06",
    "category": "tutorial",
    "content": "This guide describes in detail all endpoints of the new API v2. It includes code examples in Python, JavaScript, and cURL for each endpoint. We cover authentication, resource creation, queries, updates, and deletion. We also explain error handling, rate limiting, and best practices. Complete documentation is available on our developer portal."
  }</code></pre><h4>Mappings de Elasticsearch</h4><p>Una vez definido el set de datos, necesitamos crear un esquema de datos que se ajuste correctamente a la estructura de la publicación de blog. Se emplearán las siguientes <a href="https://www.elastic.co/docs/manage-data/data-store/mapping">mappings de índices</a> para almacenar los datos en Elasticsearch:</p><pre><code>INDEX_NAME = "blog-posts"

mapping = {
    "mappings": {
        "properties": {
            "id": {"type": "keyword"},
            "title": {
                "type": "object",
                "properties": {
                    "original": {
                        "type": "text",
                        "copy_to": "semantic_field",
                        "fields": {"keyword": {"type": "keyword"}},
                    },
                    "translated_title": {
                        "type": "text",
                        "fields": {"keyword": {"type": "keyword"}},
                    },
                },
            },
            "author": {"type": "keyword", "copy_to": "semantic_field"},
            "category": {"type": "keyword", "copy_to": "semantic_field"},
            "content": {"type": "text", "copy_to": "semantic_field"},
            "date": {"type": "date"},
            "semantic_field": {"type": "semantic_text"},
        }
    }
}


es_client.indices.create(index=INDEX_NAME, body=mapping)</code></pre><p>Aquí, podemos ver mejor cómo se estructuran los datos. Usaremos la búsqueda semántica para recuperar resultados basados en lenguaje natural, junto con la propiedad <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/copy-to"><code>copy_to</code></a> para copiar el contenido del campo en el campo <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"><code>semantic_text</code></a>. Además, el campo <code>title</code> contiene dos subcampos: el subcampo <code>original</code> almacena el título en inglés o español, dependiendo del idioma original del artículo; y el subcampo <code>translated_title</code> está presente solo para artículos en español y contiene la traducción al inglés del título original.</p><h3>Ingesta de datos</h3><p>El siguiente fragmento de código ingesta el conjunto de datos de las publicaciones de blog en Elasticsearch mediante la <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/javascript/bulk_examples">API de bulk</a>:</p><pre><code>def build_data(json_file, index_name):
    with open(json_file, "r") as f:
        data = json.load(f)

    for doc in data:
        action = {"_index": index_name, "_source": doc}
        yield action


try:
    success, failed = helpers.bulk(
        es_client,
        build_data("dataset.json", INDEX_NAME),
    )
    print(f"{success} documents indexed successfully")

    if failed:
        print(f"Errors: {failed}")
except Exception as e:
    print(f"Error: {str(e)}")</code></pre><p>Ahora que tenemos los artículos ingeridos en Elasticsearch, necesitamos crear una función capaz de buscar en el campo <code>semantic_text</code> :</p><pre><code>def perform_semantic_search(query_text, index_name=INDEX_NAME, size=5):
    try:
        query = {
            "query": {
                "match": {
                    "semantic_field": {
                        "query": query_text,
                    }
                }
            },
            "size": size,
        }

        response = es_client.search(index=index_name, body=query)
        hits = response["hits"]["hits"]

        return hits
    except Exception as e:
        print(f"Semantic search error: {str(e)}")
        return []</code></pre><p>También necesitamos una función que llame al endpoint de inferencia. En este caso, llamaremos al endpoint usando el tipo de tarea <strong><code>chat_completion</code> </strong>para obtener respuestas de transmisión:</p><pre><code>def stream_chat_completion(messages: list, inference_id: str = INFERENCE_ENDPOINT_ID):
    url = f"{ELASTICSEARCH_URL}/_inference/chat_completion/{inference_id}/_stream"
    payload = {"messages": messages}
    headers = {
        "Authorization": f"ApiKey {ELASTICSEARCH_API_KEY}",
        "Content-Type": "application/json",
    }

    try:
        response = requests.post(url, json=payload, headers=headers, stream=True)
        response.raise_for_status()

        for line in response.iter_lines(decode_unicode=True):
            if line:
                line = line.strip()

                if line.startswith("event:"):
                    continue

                if line.startswith("data: "):
                    data_content = line[6:]

                    if not data_content.strip() or data_content.strip() == "[DONE]":
                        continue

                    try:
                        chunk_data = json.loads(data_content)

                        if "choices" in chunk_data and len(chunk_data["choices"]) > 0:
                            choice = chunk_data["choices"][0]
                            if "delta" in choice and "content" in choice["delta"]:
                                content = choice["delta"]["content"]
                                if content:
                                    yield content

                    except json.JSONDecodeError as json_err:
                        print(f"\nJSON decode error: {json_err}")
                        print(f"Problematic data: {data_content}")
                        continue

    except requests.exceptions.RequestException as e:
        yield f"Error: {str(e)}"</code></pre><p>Ahora podemos escribir una función que llame a la función de búsqueda semántica, junto con el endpoint de inferencia <code>chat_completions</code> y el endpoint de recomendaciones, para generar los datos que se asignarán en las tarjetas:</p><pre><code>def recommend_articles(search_query, index_name=INDEX_NAME, max_articles=5):
    print(f"\n{'='*80}")
    print(f"🔍 Search Query: {search_query}")
    print(f"{'='*80}\n")

    articles = perform_semantic_search(search_query, index_name, size=max_articles)

    if not articles:
        print("❌ No relevant articles found.")
        return None, None

    print(f"✅ Found {len(articles)} relevant articles\n")

    # Build context with found articles
    context = "Available blog articles:\n\n"
    for i, article in enumerate(articles, 1):
        source = article.get("_source", article)
        context += f"Article {i}:\n"
        context += f"- Title: {source.get('title', 'N/A')}\n"
        context += f"- Author: {source.get('author', 'N/A')}\n"
        context += f"- Category: {source.get('category', 'N/A')}\n"
        context += f"- Date: {source.get('date', 'N/A')}\n"
        context += f"- Content: {source.get('content', 'N/A')}\n\n"

    system_prompt = """You are an expert content curator that recommends blog articles.

    Write recommendations in a conversational style starting with phrases like:
    - "If you're interested in [topic], this article..."
    - "This post complements your search with..."
    - "For those looking into [topic], this article provides..."


    FORMAT REQUIREMENTS:
    - Return ONLY a JSON array
    - Each element must have EXACTLY these three fields: "article_number", "title", "recommendation"
    - If the original title is in spanish, use the "translated_title" subfield in the "title" field

    Keep each recommendation concise (2-3 sentences max) and focused on VALUE to the reader.

    EXAMPLE OF CORRECT FORMAT:
    [
        {"article_number": 1, "title": "Article title in english", "recommendation": "If you are interested in [topic], this article provides..."},
        {"article_number": 2, "title": "Article title in english", "recommendation": " for those looking into [topic], this article provides..."}
    ]

    Return ONLY the JSON array following this exact structure."""

    user_prompt = f"""Search query: "{search_query}"

    Generate recommendations for the following articles: {context}
    """

    messages = [
        {"role": "system", "content": "/no_think"},
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_prompt},
    ]

    # LLM generation
    print(f"{'='*80}")
    print("🤖 Generating personalized recommendations...\n")

    full_response = ""

    for chunk in stream_chat_completion(messages):
        print(chunk, end="", flush=True)
        full_response += chunk

    return context, articles, full_response</code></pre><p>Por último, extrae la información y dale formato para imprimirla:</p><pre><code>def display_recommendation_cards(articles, recommendations_text):
    print("\n" + "=" * 100)
    print("📇 RECOMMENDED ARTICLES".center(100))
    print("=" * 100 + "\n")

    # Parse JSON recommendations - clean tags and extract JSON
    recommendations_list = []
    try:

        # Clean up <think> tags
        cleaned_text = re.sub(
            r"<think>.*?</think>", "", recommendations_text, flags=re.DOTALL
        )
        # Remove markdown code blocks ( ... ``` or ``` ... ```)
        cleaned_text = re.sub(r"```(?:json)?", "", cleaned_text)
        cleaned_text = cleaned_text.strip()

        parsed = json.loads(cleaned_text)

        # Extract recommendations from list format
        for item in parsed:
            article_number = item.get("article_number")
            title = item.get("title", "")
            rec_text = item.get("recommendation", "")

            if article_number and rec_text:
                recommendations_list.append(
                    {
                        "article_number": article_number,
                        "title": title,
                        "recommendation": rec_text,
                    }
                )
    except json.JSONDecodeError as e:
        print(f"⚠️  Could not parse recommendations as JSON: {e}")
        return

    for i, article in enumerate(articles, 1):
        source = article.get("_source", article)

        # Card border
        print("┌" + "─" * 98 + "┐")

        # Find recommendation and title for this article number
        recommendation = None
        title = None
        for rec in recommendations_list:
            if rec.get("article_number") == i:
                recommendation = rec.get("recommendation")
                title = rec.get("title")
                break

        # Print title
        title_lines = textwrap.wrap(f"📌 {title}", width=94)
        for line in title_lines:
            print(f"│  {line}".ljust(99) + "│")

        # Card border
        print("├" + "─" * 98 + "┤")

        # Print recommendation
        if recommendation:
            recommendation_lines = textwrap.wrap(recommendation, width=94)
            for line in recommendation_lines:
                print(f"│  {line}".ljust(99) + "│")

        # Card bottom
        print("└" + "─" * 98 + "┘")</code></pre><p>Pongámoslo a prueba haciendo una pregunta sobre las publicaciones de blog de seguridad:</p><pre><code>search_query = "Security and vulnerabilities"

context, articles, recommendations = recommend_articles(search_query)

print("\nElasticsearch context:\n", context)

# Display visual cards
display_recommendation_cards(articles, recommendations)</code></pre><p>Aquí podemos ver las tarjetas de la consola generadas por el flujo de trabajo:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/730d35212594bb3db30447c3ea7e2a92857287b7-1999x1515.png" style="width: 100%;" /><p>Puedes ver los resultados completos, incluso todas las coincidencias y la respuesta del LLM, en <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/elasticsearch-inference-api-and-hugging-face/results.md">este archivo</a>.</p><p>Estamos solicitando artículos relacionados con: “Seguridad y vulnerabilidades”. Esta pregunta se emplea como consulta de búsqueda en los documentos almacenados en Elasticsearch. Los resultados obtenidos se pasan al modelo, el cual genera recomendaciones basadas en su contenido. Como podemos ver, el modelo hizo un excelente trabajo generando textos cortos y atractivos que pueden motivar al lector a hacer clic en ellos.</p><h2>Conclusión</h2><p>Este ejemplo muestra cómo se pueden combinar Elasticsearch y Hugging Face para crear un sistema centralizado rápido y eficiente para aplicaciones de IA. Este enfoque reduce el esfuerzo manual y proporciona flexibilidad, gracias al extenso catálogo de modelos de Hugging Face. El uso de SmolLM3-3B, en particular, muestra cómo los modelos compactos y multilingües aún pueden ofrecer razonamiento significativo y generación de contenido cuando se combinan con la búsqueda semántica. En conjunto, estas herramientas ofrecen una base escalable y eficaz para construir análisis de contenidos inteligentes y aplicaciones multilingües.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Rapidez vs. precisión: medición de recuperación de la búsqueda vectorial cuantificada]]></title>
            <description><![CDATA[Explicación de cómo medir la recuperación para la búsqueda vectorial en Elasticsearch con una configuración mínima.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/recall-vector-search-quantization</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/recall-vector-search-quantization</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeff Vestal]]></dc:creator>
            <pubDate>Fri, 20 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/09f03b9239d66c36763cdab3fafcdac207ff6d83-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Todos quieren que la búsqueda de vectores sea instantánea. Pero los vectores de alta dimensión son pesados. Un único vector float-32 de 1024 dimensiones ocupa una memoria significativa, y compararlo con millones de otros es computacionalmente costoso.</p><p>Para resolver esto, los motores de búsqueda como Elasticsearch usan dos estrategias principales de optimización:</p><ol><li><strong>Búsqueda aproximada (mundo pequeño jerárquico navegable [HNSW]):</strong> en lugar de examinar cada documento, construimos un grafo de navegación para saltar rápidamente al vecindario probable de la respuesta.</li><li><strong>Cuantización:</strong> Comprimimos los vectores (por ejemplo, de flotantes de 32 bits a enteros de 8 bits o incluso valores binarios de 1 bit) para reducir el uso de memoria y acelerar los cálculos.</li></ol><p>Pero la optimización a menudo tiene un precio: <strong>la precisión</strong>.</p><p>El miedo es válido: &quot;Si comprimo mis datos y tomo atajos durante la búsqueda, ¿me perderé los mejores resultados?&quot;. &quot;¿Esta optimización degrada la relevancia de mi motor de búsqueda?&quot;.</p><p>Para demostrar que la cuantificación de Elastic no degrada los resultados, construimos un marco de pruebas repetible mediante el conjunto de datos de <a href="https://huggingface.co/datasets/fancyzhx/dbpedia_14"><strong>DBPedia-14</strong></a> para calcular exactamente cuánta precisión intercambiamos (específicamente, la <strong>recuperación)</strong> por velocidad al usar las optimizaciones predeterminadas en Elasticsearch.</p><p>Resumen: es probable que sea mucho menos de lo que piensas. Echa un vistazo al <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/fast_vs_accurate_measuring_the_recall_of_quantized_vector_search/vector_recall_notebook.ipynb">cuaderno aquí</a> e inténtalo por tu cuenta</p><h2><strong>Las definiciones (para los no expertos)</strong></h2><p>Antes de ver el código, establezcamos algunos términos.</p><ul><li><strong>Relevancia versus recuperación:</strong> <strong>La relevancia</strong> es subjetiva (¿encontré cosas buenas?). <strong>La recuperación</strong> es matemática. Si hay 10 documentos en la base de datos que son las <em>coincidencias matemáticas perfectas</em> para tu consulta, y el motor de búsqueda encuentra nueve de ellos, tu recuperación es del 90 % (o 0,9).</li><li><strong>Búsqueda exacta (plana):</strong> A veces denominado el método de &quot;fuerza bruta&quot;. El motor de búsqueda analiza cada uno de los documentos de un índice y calcula la distancia.<ul><li><em>Pros:</em> 100 % de recuperación perfecta.</li><li><em>Contras:</em> computacionalmente caro y lento en escala.</li></ul></li><li><strong>Búsqueda aproximada (HNSW):</strong> El método del &quot;atajo&quot;. El motor de búsqueda crea un grafo <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">HNSW</a>. Recorre el grafo para encontrar a los vecinos más cercanos.<ul><li><em>Ventajas:</em> extremadamente rápido y escalable.</li><li><em>Desventajas:</em> podrías perderte algún vecino si el recorrido del grafo se detiene demasiado pronto.</li></ul></li></ul><h2><strong>El experimento: exactitud versus aproximación</strong></h2><p>Para probar la recuperación, usamos el conjunto de datos <strong>DBPedia-14</strong>, un gran set de datos de títulos y resúmenes de 14 clases de ontología que normalmente se utilizan para entrenar y evaluar modelos de categorización de texto. En concreto, nos centraremos en la categoría de &quot;Cine&quot;. Queríamos comparar los ajustes de producción optimizados con una verdad de base matemáticamente perfecta.</p><p>Para este experimento, utilizamos el <a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text">modelo jina-embeddings-v5-text-small</a>, un modelo multilingüe de última generación que lidera los estándares de la industria para la representación de texto. Elegimos este modelo porque define el estándar actual para embeddings de alto rendimiento. Al combinar la precisión de élite de Jina v5 con la cuantización nativa de Elasticsearch, podemos demostrar una arquitectura de búsqueda que es tanto computacionalmente eficiente como intransigente en la calidad de la recuperación.</p><p>Configuramos un índice con un mapeo doble. Ingerimos el mismo texto en dos campos diferentes de forma simultánea:</p><ol><li><strong><code>content.raw</code> </strong>con el tipo: <code>flat</code>. Esto obliga a Elasticsearch a realizar un escaneo por fuerza bruta de los vectores Float32 completos. Esto devuelve resultados de coincidencia exacta y se utilizará para nuestra línea de base.</li><li><strong><code>content</code> </strong>con tipo <code>semantic_text</code>. Con valores predeterminados que utilizan HNSW + la mejor cuantificación binaria (BBQ). Esta es la configuración de producción estándar y optimizada para una coincidencia aproximada.</li></ol><h3><strong>La prueba de recall@10</strong></h3><p>Para nuestra métrica, usamos Recall@10.</p><p>Elegimos 50 películas aleatorias y ejecutamos la misma consulta en ambos campos.</p><ul><li>Si la búsqueda <strong>exacta (plana)</strong> indica que los 10 vecinos más cercanos son los ID [1, 2, 3... 10].</li><li>Y la búsqueda <strong>aproximada (HNSW)</strong> devuelve los ID [1, 2, 3... 9, 99].</li><li>Encontramos nueve de los 10 principales correctamente. La puntuación es <strong>0,9</strong>.</li></ul><p>Este es el mapeo que utilizamos:</p><pre><code># The "Control Group": Forces exact brute-force scan
"raw": {
    "type": "semantic_text",
    "inference_id": ".jina-embeddings-v5-text-small",
    "index_options": {
        "dense_vector": {
            "type": "flat"
        }
    }
}</code></pre><p><strong>Los resultados: la &quot;línea plana&quot; del éxito</strong></p><p>Realizamos una prueba de escala, recargando el conjunto de datos completo y probando con tamaños de índice de entre 1000 y 40 000 documentos.</p><p>Esto es lo que sucedió con la puntuación de recuperación:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Documentos</td><td style="border: 1px solid #ccc; padding: 4px;">Puntuación de recall@10</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">1000</td><td style="border: 1px solid #ccc; padding: 4px;">1000 (100 %)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">5000</td><td style="border: 1px solid #ccc; padding: 4px;">0,998 (100 %)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">10,000</td><td style="border: 1px solid #ccc; padding: 4px;">0,992 (99,4 %)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">20 000</td><td style="border: 1px solid #ccc; padding: 4px;">0,999 (99,0 %)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">40 000</td><td style="border: 1px solid #ccc; padding: 4px;">0,992 (98,8 %)</td>
                    </tr>
              </tbody>
            </table>
          <p>Los resultados fueron increíblemente estables. Incluso a medida que escalábamos, la búsqueda aproximada coincidió con la búsqueda exacta de fuerza bruta <strong>&gt;99 % del tiempo</strong>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a4bfacb1d0cce6fdf6df0e1a9d4fc5d4007a66da-1999x1209.png" style="width: 100%;" /><h2><strong>¿Por qué funcionó tan bien?</strong></h2><p>Podrías esperar que comprimir vectores a valores binarios perjudicaría más la precisión. La razón por la que esto no ocurre está en la forma en que Elasticsearch gestiona la recuperación.</p><p>La mayoría de los modelos de incrustación actuales dan como salida vectores Float32, que son grandes. Para hacer la búsqueda eficiente, Elasticsearch emplea cuantización para vectores de alta dimensión. Concretamente, desde la versión 9.2, usa <a href="https://www.elastic.co/search-labs/blog/elasticsearch-9-1-bbq-acorn-vector-search">BBQ</a> por defecto.</p><p>BBQ usa un mecanismo de <strong>recalificación</strong>:</p><ol><li><strong>Recorrido:</strong> el motor de búsqueda utiliza los vectores comprimidos (cuantificados) para recorrer rápidamente el grafo HNSW. Como los vectores son pequeños, puedes realizar un sobremuestreo de manera eficiente, recopilando una lista más amplia de candidatos (por ejemplo, los 100 documentos más parecidos) sin que afecte al rendimiento.</li><li><strong>Recálculo de la puntuación:</strong> una vez que obtiene esos candidatos, recupera los valores de precisión completa solo para esos pocos documentos para calcular la clasificación final y precisa.</li></ol><p>Esto te brinda lo mejor de ambos mundos, la velocidad de la cuantización para el trabajo pesado y la precisión de los flotantes para la ordenación final.</p><h2><strong>¿Podemos hacerlo mejor?</strong></h2><p>Cabe destacar que los resultados que vemos aquí utilizan la configuración predeterminada y una muestra aleatoria de datos. Piensa en esto como un punto de partida de alto rendimiento. Aunque Jina v5 es una bestia, estas puntuaciones de recuperación no son una garantía de &quot;talla única&quot; para todos los conjuntos de datos. Cada conjunto de datos tiene sus propias peculiaridades, y aunque sin duda puedes seguir ajustando los parámetros para obtener un mejor rendimiento, siempre debes realizar pruebas con tus propios datos específicos para ver cuál es tu límite.</p><h2><strong>Conclusión</strong></h2><p>Esta es una prueba a muy pequeña escala. Pero el punto del ejercicio no es medir el modelo de incrustación o BBQ específicamente, sino demostrar cómo puedes medir fácilmente la recuperación de tu conjunto de datos con una configuración mínima.</p><p>Si quieres ejecutar esta prueba con tus propios datos, puedes consultar el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/fast_vs_accurate_measuring_the_recall_of_quantized_vector_search/vector_recall_notebook.ipynb">cuaderno aquí</a> e intentarlo por tu cuenta.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Extensión CLI de Gemini para Elasticsearch con herramientas y habilidades]]></title>
            <description><![CDATA[Te presentamos la extensión de Elastic para la CLI de Gemini de Google, que te permite hacer búsquedas, recuperar y analizar datos de Elasticsearch en flujos de trabajo de desarrollo y de agentes.
]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/gemini-cli-extension-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/gemini-cli-extension-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Walter Rafelsberger, Enrico Zimuel]]></dc:creator>
            <pubDate>Tue, 17 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6dfcec4a77b3dc83bf0d974417bf2e211abb1f4f-876x468.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Nos complace anunciar el lanzamiento de nuestra extensión Elastic para la CLI de Gemini de Google, que trae todo el poder de <a href="https://www.elastic.co/elasticsearch">Elasticsearch</a> y <a href="https://www.elastic.co/elasticsearch/agent-builder">Elastic Agent Builder</a> directamente a tu flujo de trabajo de desarrollo de IA. Esta extensión también ofrece varias funciones de agente desarrolladas recientemente para interactuar con Elasticsearch.</p><p>La extensión está disponible como proyecto de open source <a href="https://github.com/elastic/gemini-cli-elasticsearch">aquí</a>.</p><h2>¿Qué es Gemini CLI y cómo lo instalas?</h2><p><a href="https://geminicli.com/">Gemini CLI</a> es un agente open source de IA que lleva los modelos Gemini de Google directamente a la línea de comando. Permite a los desarrolladores interactuar con la IA desde la terminal para hacer tareas como generar código, editar archivos, ejecutar comandos de shell y obtener información de la web.</p><p>A diferencia de las interfaces de chat típicas, Gemini CLI se integra con tu entorno local de desarrollo, lo que significa que puede entender el contexto del proyecto, modificar archivos, ejecutar compilaciones o pruebas, y automatizar flujos de trabajo directamente dentro de la terminal. Esto lo hace útil para desarrolladores, ingenieros de confiabilidad del sitio (SRE) e ingenieros que buscan codificación y automatización asistidas por IA sin salir de su flujo de trabajo de línea de comandos.</p><p>Gemini CLI puede instalarse usando diversos gestores de paquetes. El método más común es a través de npm:</p><pre><code>npm install -g @google/gemini-cli</code></pre><p>Si quieres conocer opciones de instalación alternativas, consulta la <a href="https://geminicli.com/docs/get-started/installation/">página de instalación oficial</a>.</p><p>Luego de la instalación, inicia la CLI ejecutando:</p><pre><code>gemini</code></pre><p>Verás una pantalla, como se muestra en la Figura 1:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6dfcec4a77b3dc83bf0d974417bf2e211abb1f4f-876x468.png" style="width: 100%;" /><h2>Configurar Elasticsearch</h2><p>Es necesario tener una instancia de Elasticsearch en funcionamiento. Si quieres usar el servidor Model Context Protocol (MCP), también necesitas instalar Kibana 9.3+. Para emplear la habilidad del lenguaje de búsqueda Elasticsearch (ES|QL) (<code>esql</code>) descrito más abajo, Kibana no es obligatorio.</p><p>Puedes activar una prueba gratis en <a href="https://www.elastic.co/cloud">Elastic Cloud</a> o instalarlo localmente usando el script <a href="https://github.com/elastic/start-local"><code>start-local</code></a>:</p><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre><p>Se instalarán Elasticsearch y Kibana en tu computadora y se generará una clave API que se utilizará para configurar Gemini CLI.</p><p>La clave de API se mostrará como salida del comando anterior y se almacenará en un archivo <strong>.env</strong> en la carpeta <strong><code>elastic-start-local</code></strong>.</p><p>Si usas Elasticsearch local (por ejemplo, usar <code>start-local</code>), y quieres usar Elastic Agent Builder con MCP, también necesitas conectar un modelo de lenguaje grande (LLM). Puedes leer <a href="https://www.elastic.co/docs/explore-analyze/ai-features/llm-guides/llm-connectors">esta página de documentación</a> para entender las diferentes opciones.</p><p>Si usas Elastic Cloud (o sin servidor), ya tienes una conexión con el LLM preconstruida.</p><h2>Instala la extensión de Elasticsearch</h2><p>Puedes instalar la extensión de Elasticsearch para Gemini CLI con el siguiente comando:</p><pre><code>gemini extensions install https://github.com/elastic/gemini-cli-elasticsearch</code></pre><p>Puedes verificar que las extensiones se hayan instalado correctamente abriendo Gemini y ejecutando el siguiente comando:</p><pre><code>/extensions list</code></pre><p>Deberías ver la extensión de Elasticsearch disponible.</p><p>Si quieres usar la integración de MCP, necesitas tener instalada una versión de Elasticsearch 9.3 o superior. Necesitas la URL de tu servidor MCP de <a href="https://www.elastic.co/kibana">Kibana</a>:</p><ul><li>Obtén la URL del servidor MCP desde Agentes &gt; Ver todas las herramientas &gt; Administrar MCP &gt; Copiar URL del servidor MCP.</li><li>La URL tendrá este formato: https://your-kibana-instance/api/agent_builder/mcp</li></ul><p>Necesitas la URL del endpoint de Elasticsearch. Esta generalmente se informa en la parte superior de la página de Kibana Elasticsearch. Si estás ejecutando Elasticsearch con <code>start-local</code>, ya tienes el endpoint en la clave <code>ES_LOCAL_URL</code>en el archivo <code>start-local</code>.env .</p><p>También necesitas una clave API. Si estás ejecutando Elasticsearch con <code>start-local</code>, ya tienes <code>ES_LOCAL_API_KEY</code> en el archivo <code>start-local</code> .env . De lo contrario, puedes crear una clave API desde la interfaz de Kibana, como se indica <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">aquí</a>:</p><ul><li>En Kibana: Stack Management &gt; Seguridad &gt; Claves de API &gt; Crear clave de API.</li><li>Sugerimos establecer solo los privilegios de lectura para la clave API, habilitando el privilegio <code>feature_agentBuilder.read</code> como <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/permissions#grant-access-with-roles">se informa aquí</a>.</li><li>Copia el valor de la clave API codificada.</li></ul><p>Configura las variables de entorno requeridas en tu shell:</p><pre><code>export ELASTIC_URL="your-elasticsearch-url"
export ELASTIC_MCP_URL="your-elasticsearch-mcp-url"
export ELASTIC_API_KEY="your-encoded-api-key"</code></pre><h2>Instala el set de datos de ejemplo</h2><p>Puedes instalar el set de datos de <strong>pedidos de comercio electrónico </strong>disponible desde Kibana. Incluye un único índice llamado <strong><code>kibana_sample_data_ecommerce</code></strong>, que contiene información sobre 4,675 pedidos de un sitio web de comercio electrónico. Para cada pedido, tenemos la siguiente información:</p><ul><li>Información del cliente (nombre, identificación, fecha de nacimiento, correo electrónico y más).</li><li>Fecha del pedido.</li><li>ID de pedido.</li><li>Productos (lista de todos los productos con precio, cantidad, identificación, categoría, descuento y otros detalles).</li><li>SKU.</li><li>Precio total (sin impuestos, con impuestos).</li><li>Cantidad total.</li><li>Información geográfica (ciudad, país, continente, ubicación, región).</li></ul><p>Para instalar los datos de muestra, abre la página <strong>Integraciones</strong> en Kibana (busca &quot;Integración&quot; en la barra superior de búsqueda) e instala los <strong>Datos de muestra</strong>. Para más detalles, consulta la documentación <a href="https://www.elastic.co/docs/explore-analyze/#gs-get-data-into-kibana">aquí</a>.</p><p>El objetivo de este artículo es mostrar lo fácil que es configurar la CLI Gemini para conectarse a Elasticsearch e interactuar con el índice <strong><code>kibana_sample_data_ecommerce</code></strong> .</p><h2>Cómo usar el MCP de Elasticsearch</h2><p>Puedes comprobar la conexión usando el siguiente comando en Gemini:</p><pre><code>/mcp list</code></pre><p>Deberías ver el <strong><code>elastic-agent-builder</code></strong> activado, como se muestra en la Figura 2:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1508423bc1d1bc3c04a1cb01e2d59495a3516ed1-1465x844.png" style="width: 100%;" /><p>Elasticsearch proporciona un conjunto predeterminado de herramientas. Ve la descripción <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/tools/builtin-tools-reference">aquí</a>.</p><p>Con estas herramientas, puedes interactuar con Elasticsearch y hacer preguntas como:</p><ul><li><code>Give me the list of all the indexes available in Elasticsearch.</code></li><li><code>How many customers are based in the USA in the kibana_sample_data_ecommerce index of Elasticsearch?</code></li></ul><p>Dependiendo de la pregunta, Gemini usará una o varias de las herramientas disponibles para intentar responderla.</p><h2>Los comandos /elastic</h2><p>En la extensión Elasticsearch para Gemini CLI, también agregamos<strong> </strong> comandos<strong><code>/elastic</code></strong>.</p><p>Si ejecutas el comando <strong><code>/help</code></strong>, verás todas las opciones <code>/elastic</code> disponibles (Figura 3):</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5b2a0727ce7a04354878dd048253d3f4d062324b-1983x230.png" style="width: 100%;" /><p>Estos comandos pueden ser útiles si quieres ejecutar directamente una herramienta específica del servidor MCP <code>elastic-agent-builder</code>. Por ejemplo, usando el siguiente comando, puedes obtener el mapping del <code>kibana_sample_data_ecommerce</code>:</p><pre><code>/elastic:get-mapping kibana_sample_data_ecommerce</code></pre><p>Estos comandos son esencialmente atajos para ejecutar herramientas específicas, en lugar de depender del modelo Gemini para determinar qué herramienta debe invocarse.</p><h2>Cómo usar las habilidades de Elasticsearch</h2><p>Esta extensión también incluye una <a href="https://github.com/elastic/gemini-cli-elasticsearch/tree/main/skills/esql">habilidad de agente para ES|QL</a>, el <a href="https://www.elastic.co/docs/explore-analyze/discover/try-esql">lenguaje de búsqueda Elasticsearch</a> disponible en Elasticsearch. <a href="https://agentskills.io/home">Habilidades del Agente</a> es un formato abierto que proporciona a los agentes de codificación de IA, como Gemini CLI, instrucciones personalizadas para tareas específicas. Utilizan un concepto llamado <em>revelación progresiva</em>, lo que significa que solo se agrega una breve descripción de la habilidad a la indicación inicial del sistema. Cuando le pides al agente que haga una tarea, como consultar Elasticsearch, hace coincidir la solicitud con la habilidad relevante y carga dinámicamente las instrucciones detalladas. Esta es una forma eficaz de gestionar los presupuestos de tokens y, al mismo tiempo, proporcionar a la IA exactamente el contexto que necesita.</p><p>La <strong>habilidad<code>esql</code></strong> está diseñada para permitir que la CLI de Gemini escriba y ejecute consultas en ES|QL directamente en tu cluster. ES|QL es un poderoso lenguaje de búsqueda con barras verticales que hace que la exploración de datos, el análisis de logs y las agregaciones sean sumamente intuitivas. Con esta habilidad activada, no necesitas buscar la sintaxis de ES|QL; simplemente puedes hacer preguntas en lenguaje natural a la CLI de Gemini sobre tus datos, y el agente se encargará del resto.</p><p>Las ejecuciones se efectúan mediante comandos <a href="https://curl.se/">curl</a> sencillos ejecutados en una terminal. Esto es posible porque Elasticsearch ofrece un amplio conjunto de API REST que se pueden utilizar fácilmente para integrar el sistema en cualquier arquitectura.</p><p><strong>Lo que la </strong> habilidad<strong><code>esql</code> ofrece:</strong></p><ul><li><strong>Detección de índices y esquemas:</strong> El agente puede usar las herramientas integradas de la habilidad para mostrar los índices disponibles y obtener el mapping de campos. Por ejemplo, antes de escribir una consulta para el set de datos de comercio electrónico, el agente puede ejecutar una verificación de esquema en <strong><code>kibana_sample_data_ecommerce</code></strong> para comprender los campos disponibles, como <strong><code>taxful_total_price</code></strong> o <strong><code>category</code></strong>.</li><li><strong>Traducción perfecta al lenguaje natural:</strong> la habilidad le da al agente algo más que un simple manual de referencia; proporciona una guía específica para interpretar la intención del usuario. Cuando escribes solicitudes en lenguaje natural, como &quot;Mostrar el tiempo de respuesta promedio agrupado por servicio&quot;, el agente utiliza la función de coincidencia de patrones integrada en la habilidad para traducir al instante tus palabras en las agregaciones, filtros y comandos en ES|QL correctos.</li><li><strong>Autocorrección:</strong> Si una consulta falla (por ejemplo, debido a una incompatibilidad de tipos o un error de sintaxis), la habilidad devuelve la consulta generada junto con el mensaje de error exacto de Elasticsearch, lo que permite al agente corregirla al instante y volver a intentarlo sin que tengas que intervenir.</li></ul><p>Dado que la habilidad <code>esql</code> también está disponible como una herramienta en el servidor MCP <code>elastic-agent-builder</code>, necesitamos deshabilitar este servidor momentáneamente. Puedes usar el siguiente comando para desactivarlo:</p><pre><code>/mcp disable elastic-agent-builder</code></pre><p>Entonces puedes simplemente escribir un mensaje como este en tu CLI de Gemini:</p><pre><code>Find the top 5 product categories by total sales revenue in the kibana_sample_data_ecommerce index</code></pre><p>El agente deberá:</p><ul><li>Reconoce la necesidad de la habilidad <code>esql</code> .</li><li>Verifica el esquema de <strong><code>kibana_sample_data_ecommerce</code></strong>.</li><li>Construye una consulta en ES|QL, como: <code>FROM kibana_sample_data_ecommerce | STATS total_revenue = SUM(taxful_total_price) BY category.keyword | SORT total_revenue DESC | LIMIT 5</code>.</li><li>Ejecutar la consulta en la API de Elasticsearch.</li><li>Presentar la respuesta final directamente en la terminal.</li></ul><p>Aquí, presentamos un ejemplo de respuesta de Gemini al mensaje anterior:</p><pre><code>───────────────────────────────────────────────────────────
> Find the top 5 product categories by total sales revenue in the kibana_sample_data_ecommerce index   ───────────────────────────────────────────────────────────
✓  Activate Skill "esql": Interact with Elasticsearch using ES|QL and curl. Use when querying, managing indices,  …
Skill esql activated. Resources loaded from /home/user/.gemini/extensions/elasticsearch/skills/esql: 
Showing up to 200 items (files + folders).                                                                     /home/user/.gemini/extensions/elasticsearch/skills/esql/                                                            ├───SKILL.md 
└───references/
├───esql-reference.md 
├───generation-tips.md 
└───query-patterns.md

✦ I will start by exploring the mapping of the kibana_sample_data_ecommerce index to identify the fields for product category and sales revenue. Then, I will construct and execute an ES|QL query to determine the top 5 product categories by total sales revenue.

✓  Shell curl -s "${ELASTIC_URL%/}/kibana_sample_data_ecommerce/_mapping" -H "Authorization: ApiKey $(printenv ELAS…                                                                                                                       {  
   "kibana_sample_data_ecommerce": { 
     "mappings": { 
       "properties": {
         "category": { 
           "type": "text", 
           "fields": {
             "keyword": {
               "type": "keyword"
             }
           }
         }, 
         "currency": {
           "type": "keyword"
         }, 

✦ Okay, I have the mapping. The category is products.category.keyword and sales revenue is taxful_total_price. I'll now execute the ES|QL query to get the top 5 categories by revenue.

✓  Shell curl -s -X POST "${ELASTIC_URL%/}/_query" \  -H "Authorization: ApiKey $(printenv ELASTIC_API_KEY)" \  -H …
{ 
   "took": 62,
   "is_partial": false, 
   "completion_time_in_millis": 1772805644164,
   "documents_found": 4675,
   "values_loaded": 12084, 
   "start_time_in_millis": 1772805644102, 
   "expiration_time_in_millis": 1773237644087,  
   "columns": [ 
     { 
       "name": "total_revenue",    
       "type": "double"  
     }, 
     {   
       "name": "products.category.keyword",

✦ The query for top 5 product categories by revenue was successful. I'll format the returned JSON values into a   user-friendly list.

 ✓  Shell 
                             
 Top 5 product categories by total sales revenue: 
 1. Men's Clothing: 149393.91
 2. Women's Clothing: 135099.91
 3. Women's Shoes: 105479.17
 4. Men's Shoes: 91797.92
 5. Women's Accessories: 60830.31 

✦ I've found the answer; now I'll format it for clarity.

 ✓  Shell 

 Here are the top 5 product categories by total sales revenue:                                                        
                                                                      
 | Rank | Category              | Total Revenue | 
 |------|-----------------------|---------------| 
 | 1    | Men's Clothing        | 149,393.91    |
 | 2    | Women's Clothing      | 135,099.91    |
 | 3    | Women's Shoes         | 105,479.17    |
 | 4    | Men's Shoes           | 91,797.92     | 
 | 5    | Women's Accessories   | 60,830.31     | 

✦ I've formatted and presented the answer. I'm finished.
 ───────────────────────────────────────────────────────────</code></pre><p>Es interesante ver cómo el modelo Gemini genera la respuesta final mostrando todos los pasos que sigue. Aquí, puedes ver claramente la influencia de la habilidad en el proceso de razonamiento del modelo. La primera vez que el modelo reconoce que necesita usar una habilidad o ejecutar un comando de shell, pide permiso empleando el enfoque de intervención humana.</p><p>Al manejar el trabajo pesado de descubrimiento de esquemas, generación de consultas y ejecución, la habilidad <code>esql</code> te permite concentrarte completamente en las respuestas en lugar de en la mecánica para obtenerlas. Obtendrás los datos que necesitas, correctamente formateados y directamente en tu terminal, todo sin escribir una sola línea de sintaxis o cambiar de contexto a otra aplicación.</p><h2>Conclusión</h2><p>En este artículo, presentamos la extensión Elasticsearch para Gemini CLI que lanzamos recientemente. Esta extensión te permite interactuar con tu instancia de Elasticsearch mediante Gemini y el servidor MCP de Elasticsearch que ofrece Elastic Agent Builder, disponible a partir de la versión 9.3.0, así como el comando <code>/elastic</code> .</p><p>Además, la extensión también incluye una habilidad <code>esql</code> que convierte la solicitud del usuario de lenguaje natural en una consulta en ES|QL. Esta habilidad puede resultar especialmente útil cuando no se puede usar el servidor MCP, ya que la comunicación subyacente se gestiona mediante simples comandos curl ejecutados en una terminal. Elasticsearch ofrece un amplio conjunto de API REST que se pueden integrar fácilmente en cualquier proyecto. Esto es especialmente útil cuando se desarrollan aplicaciones de AI agéntica.</p><p>Para más información sobre nuestra extensión Gemini CLI, visita el repositorio del proyecto <a href="https://github.com/elastic/gemini-cli-elasticsearch">aquí</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Habilidades de agente para Elastic: convierte a tu agente de IA en un experto en Elastic]]></title>
            <description><![CDATA[Otorga a tu agente de codificación de IA el conocimiento para buscar, visualizar, asegurar y automatizar con Elastic Agent Skills.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/agent-skills-elastic</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/agent-skills-elastic</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Graham Hudgins, Matt Ryan]]></dc:creator>
            <pubDate>Mon, 16 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/09e64953819083168a9ecef0888c7f8bde1a43bd-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Todos los desarrolladores, ingenieros de confiabilidad del sitio (SRE) o analistas que han intentado usar un agente de programación basado en IA con una plataforma especializada se han enfrentado al mismo obstáculo. Pides al agente que escriba una búsqueda, configure una alerta o realice una investigación y se acerca, pero no es del todo correcto. Elastic tiene una ventaja aquí: más de una década de documentación, publicaciones de blog y respuestas de la comunidad significa que los agentes de IA ya conocen Elastic mejor que la mayoría de las plataformas de datos. Pero esa profundidad viene con ruido. Las API obsoletas coexisten con las actuales. Los patrones desactualizados tienen tanta importancia como las mejores prácticas. El agente reproduce con confianza un enfoque que funcionó hace tres versiones, porque en sus datos de entrenamiento sí funcionó. El resultado es un costo de corrección: los usuarios ingresan manualmente la documentación en el contexto, corrigen la sintaxis alucinada y trabajan alrededor del agente en lugar de con él. Lo que es peor: las funciones avanzadas no se usan en absoluto, no porque los usuarios no las necesiten, sino porque el agente no sabe que existen.</p><p>Por eso estamos liberando el código de <a href="https://github.com/elastic/agent-skills">Elastic Agent Skills</a> como open-source: experiencia nativa en plataformas para Elasticsearch, Kibana, Elastic Observability y Elastic Security. Insértalo en el tiempo de ejecución del agente que ya usas y cambia a tu agente de “generalista” que adivina mucha de la sintaxis a darle experiencia, como, por ejemplo, poder usar muchos de los estándares arquitectónicos como los propios equipos de ingeniería de Elastic. Esta versión inicial de vista previa técnica se centra en las habilidades con máxima compatibilidad para <a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a>, pero evolucionará rápidamente para incluir un mejor soporte para versiones anteriores del stack.</p><p>Además, Elastic está abordando este problema desde ambos frentes. Para los agentes en la plataforma de Elastic, <a href="https://www.elastic.co/search-labs/blog/agent-builder-elastic-ga">Elastic Agent Builder</a> (ahora disponible en general) te permite crear y chatear con agentes de IA que heredan los controles de acceso de tus datos, usan herramientas integradas de búsqueda y análisis, y trabajan en contexto junto a tus dashboards, alertas e investigaciones. Estamos trabajando intensamente para asegurar experiencias agénticas increíbles en la plataforma de Elastic. Pero no todos los agentes están dentro de Elastic. Tu equipo ya usa Cursor, Claude Code u otros entornos de ejecución, y esos agentes también necesitan que Elastic funcione bien. Ahí es donde entra en juego Agent Skills.</p><h2>Por qué los agentes tienen dificultades con plataformas especializadas</h2><p>Los modelos de lenguaje grandes (LLM) son generalistas con capacidades extraordinarias. Pueden escribir Python, explicar los manifiestos de Kubernetes y refactorizar los componentes de React porque sus datos de entrenamiento están llenos de ejemplos. Pero cuando se trata de trabajo específico de la plataforma, del tipo que implica lenguajes de búsqueda patentados, superficies de API profundas y mejores prácticas específicas del dominio, muestran limitaciones previsibles.</p><p>En el caso de Elasticsearch, la diferencia se nota claramente:</p><ul><li><strong>El lenguaje de búsqueda de Elasticsearch (ES|QL) es un nuevo territorio.</strong> Los LLM están muy entrenados en SQL, pero ES|QL es un lenguaje de búsquedas con barras verticales, con sintaxis, funciones y semántica diferentes. Los agentes suelen escribir búsquedas que parecen razonables, pero no se pueden parsear. Confunden <code>WHERE</code> con <code>| WHERE</code>, inventan funciones que no existen y pierden por completo el modelo de composición basado en barras verticales.</li><li>Las <strong>superficies de API son anchas y profundas.</strong> Elasticsearch, Kibana y Elastic Security ofrecen cientos de API para búsqueda, ingesta, alertas, reglas de detección, gestión de casos, dashboards y mucho más. Un agente que solo cuenta con datos de entrenamiento generales tiene que adivinar a qué endpoint debe enviar la solicitud, cómo es el cuerpo de la solicitud y cómo gestionar la respuesta. Sus suposiciones son incorrectas con demasiada frecuencia como para erosionar la confianza.</li><li><strong>Las mejores prácticas no están en los datos de entrenamiento.</strong> ¿Cuándo deberías usar <code>semantic_text</code> frente a una pipeline de incrustación personalizada? ¿Cómo deberías estructurar una pipeline de ingesta para un CSV de 10GB? ¿Cuál es la sintaxis correcta de reglas de detección para una técnica <a href="https://www.elastic.co/docs/solutions/security/detect-and-alert/mitre-attandckr-coverage">MITRE ATT&amp;CK®</a>? Los agentes de uso general no tienen, por defecto, conocimientos específicos de Elastic seleccionados y estructurados de forma confiable. Tendrían que buscarlo, y aunque lo hicieran, la documentación sin procesar no siempre codifica los juicios y mejores prácticas que ofrecen los profesionales calificados.</li></ul><p>El resultado: los desarrolladores pasan más tiempo corrigiendo el resultado del agente que el que habrían dedicado a escribir el código ellos mismos. Esa es la experiencia para la que nadie se registró.</p><h2>Agent Skills: conocimiento de la plataforma, empaquetado para los agentes</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8d16ec00d16e70a916c5eef0aaa23fcc735b7186-1067x1280.png" style="width: 100%;" /><p>Agent Skills son directorios independientes que contienen instrucciones, scripts y material de referencia que los entornos de ejecución de agentes pueden cargar dinámicamente. Cuando una habilidad está activa, el agente tiene acceso al contexto adecuado en el momento oportuno: sintaxis de búsqueda, patrones de API, lógica de validación y ejemplos prácticos, lo que le permite completar las tareas correctamente desde el primer intento.</p><p>Cada habilidad sigue la especificación abierta <a href="https://agentskills.io">agentskills.io</a>: una carpeta con un archivo <code>SKILL.md</code> que contiene metadatos e instrucciones estructuradas. Sin formato propietario, sin dependencia del proveedor. Las habilidades funcionan en diferentes entornos de ejecución de agentes; esto incluye Cursor, Claude Code, GitHub Copilot, Windsurf, Gemini CLI, Cline, Codex y <a href="https://agentskills.io">muchos más</a>.</p><h3>¿Qué hay en la versión inicial v0.1.0?</h3><p>El primer conjunto de habilidades abarca cinco áreas del Elastic Stack:</p><ul><li>Interactuar con las API de Elasticsearch (búsqueda, indexación, gestión de clústeres)</li><li>Crear y gestionar contenido de Kibana como dashboards, alertas, conectores y más</li><li>Especialización en el dominio para Elastic Observability</li><li>Pericia en la materia para Elastic Security</li><li>Cómo crear agentes eficaces en Agent Builder</li></ul><h3>Las habilidades son componibles</h3><p>Las habilidades no son homogéneas. Son modulares por diseño. Tu agente carga solo las habilidades relevantes para la tarea en cuestión. ¿Estás trabajando en una búsqueda ES|QL? Se activa la habilidad ES|QL. ¿Necesitas crear un dashboard a partir de esos resultados? Aparece la habilidad de dashboard. ¿Estás evaluando el estado de tu aplicación? La habilidad de estado del servicio entra en juego. ¿Estás investigando una alerta de seguridad? La habilidad de triaje se asocia con habilidades de gestión de casos y respuesta a medida que avanza la investigación.</p><p>Esta capacidad de composición significa que no necesitas un único aviso masivo que intente cubrir todo. Cada habilidad lleva exactamente el contexto que tu dominio requiere, nada más, nada menos.</p><h2>Para desarrolladores que crean aplicaciones de búsqueda e IA</h2><p>Si estás cargando datos en Elasticsearch, escribiendo búsquedas o migrando índices, las habilidades reducen el ciclo de generación de código, errores y búsqueda de documentos a partir de los errores.</p><p>Pídele a tu agente que cargue un archivo CSV, y este utilizará una herramienta de ingesta en tiempo real que gestiona la contrapresión y deduce el mapping a partir de los datos. No es un bucle _bulk a medida que se queda sin memoria con el primer archivo grande. Pídele que realice búsquedas con ES|QL, y detectará los nombres reales de tus índices y los esquemas de los campos; luego generará búsquedas con barras verticales válidas, con la sintaxis correcta, las agregaciones adecuadas y una selección de características que tiene en cuenta la versión, en lugar de una conjetura al estilo SQL que requiere tres rondas de depuración. Si le pides que vuelva a indexar los clusters, sigue todo el flujo de trabajo operativo: crea el destino con mapping explícitos, configura los ajustes para el rendimiento, ejecuta el trabajo de forma asincrónica y restaura la configuración de producción cuando termina, no una llamada de _reindex que omite la mitad de los pasos que seguiría un operador experimentado.</p><p>En lugar de un agente que te da un punto de partida razonable que tienes que arreglar, obtienes uno que codifica la disciplina operativa que hace que el resultado realmente funcione.</p><p><strong>Ejemplos de impactos del uso de Elastic Agent Skills</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Eval</td><td style="border: 1px solid #ccc; padding: 4px;">Qué cambió la habilidad</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-audit-query-failed-logins</td><td style="border: 1px solid #ccc; padding: 4px;">Usó los patrones de búsqueda del log de auditoría de la habilidad en lugar de una búsqueda genérica</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-authz-role-mapping-ldap</td><td style="border: 1px solid #ccc; padding: 4px;">Generó la estructura correcta de la llamada de API para el mapping de roles</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">esql-basic-query</td><td style="border: 1px solid #ccc; padding: 4px;">Escribió la sintaxis de barras verticales ES|QL en lugar de Query DSL</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">esql-error-handling</td><td style="border: 1px solid #ccc; padding: 4px;">Primero el esquema, en lugar de adivinar los nombres de los campos</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">esql-schema-discovery</td><td style="border: 1px solid #ccc; padding: 4px;">Nunca adivinó el nombre de un índice</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-ingest-csv-with-infer</td><td style="border: 1px solid #ccc; padding: 4px;">Usó únicamente --infer-mappings, evitó combinarlo con --source-format csv, que genera un índice vacío.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-ingest-json-file</td><td style="border: 1px solid #ccc; padding: 4px;">Usó un método de ingesta sólido capaz de gestionar archivos de gran tamaño</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-reindex-local-async</td><td style="border: 1px solid #ccc; padding: 4px;">Creó primero el índice de destino con réplicas: 0 y refresh_interval: "-1", luego la reindexación asíncrona. La línea base omitió cualquier preparación</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-seguridad-403-privilegios</td><td style="border: 1px solid #ccc; padding: 4px;">Siguió el flujo de trabajo de diagnóstico de la herramienta para los errores de privilegios, en lugar de seguir consejos genéricos</td>
                    </tr>
              </tbody>
            </table>
          <h2>Para equipos de seguridad</h2><p>Los equipos de seguridad repiten los mismos flujos de trabajo operativos a diario: clasificación de alertas, ajuste de reglas de detección, gestión de casos. Las habilidades del agente codifican ese conocimiento de procesos para que tu agente de IA pueda ejecutar estos flujos de trabajo correctamente al llamar a las API adecuadas en el orden correcto con los nombres de campo correctos. Para una guía práctica que te lleva de cero a un entorno de Elastic Security completamente configurado sin salir de tu IDE, consulta <a href="https://www.elastic.co/security-labs/agent-skills-elastic-security">Empieza con Elastic Security desde tu agente de IA</a>.</p><h2>Para equipos de observabilidad y operaciones</h2><p>Las nuevas funciones de los agentes de Elastic Observability reducen la carga operativa que supone instrumentar sistemas complejos, gestionar los SLO, analizar datos complejos y evaluar el estado de los servicios. Incorporar la experiencia nativa de Elastic directamente en los agentes de IA permite a los equipos ejecutar flujos de trabajo complejos de observabilidad utilizando un lenguaje natural sencillo. Esto permite a los SRE y equipos de operaciones resolver incidentes más rápido y mantener sistemas fiables con mayor facilidad. Obtén más información en <a href="https://www.elastic.co/observability-labs/blog/elastic-agent-skills-observability-workflows">este blog</a>.</p><h2>Open source, especificación abierta, impulsado por la comunidad</h2><p>Estamos lanzando Agent Skills bajo la licencia Apache 2.0 porque creemos que el conocimiento de los agentes debe ser abierto. La especificación <a href="https://agentskills.io">agentskills.io</a> que siguen las habilidades es un estándar abierto, no un formato propietario de Elastic. Queremos que las habilidades sean un esfuerzo comunitario, no un entorno cerrado.</p><h2>Parte de un panorama más amplio</h2><p>Agent Skills es una parte de una iniciativa más amplia para hacer de Elasticsearch la plataforma de datos disponible más preparada para los agentes. Para los agentes que están en la plataforma Elasticsearch, <a href="https://www.elastic.co/search-labs/blog/agent-builder-elastic-ga">Agent Builder</a> va más allá al heredar los controles de acceso y permisos de tus datos, ofrecer herramientas integradas y personalizadas para búsqueda y análisis, y permitir que los usuarios interactúen con los agentes en contexto junto con sus dashboards, alertas e investigaciones. Finalmente, el soporte para habilidades llegará pronto a Agent Builder, lo que permitirá a los desarrolladores la flexibilidad de aprovechar las habilidades de Elastic Agent, así como habilidades de cualquier otra fuente para habilitar chats y automatizaciones seguras y mejoradas por contexto en la plataforma Elasticsearch.</p><p>Para los agentes que viven en cualquier otro lugar, estamos invirtiendo en el ecosistema abierto:</p><ul><li><strong>Expansión del servidor del Model Context Protocol (MCP):</strong> Ampliando el <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/mcp-server">endpoint MCP</a> en Agent Builder con más herramientas, más allá de la búsqueda actual, ES|QL y operaciones de índice.</li><li><strong>Mejoras en la autenticación:</strong> Facilitar a los agentes la conexión segura, con el objetivo de eliminar la necesidad de copiar y pegar manualmente las claves de API.</li><li><strong>Documentación legible en LLM:</strong> publicar archivos <code>llms.txt</code> y <code>AGENTS.md</code> para que los agentes puedan descubrir y entender las API de Elastic por sí mismos.</li><li><strong>Una interfaz de línea de comandos (CLI) para los flujos de trabajo de los agentes:</strong> Herramientas de línea de comandos que facilitan la gestión de conexiones y las operaciones comunes para los agentes.</li></ul><p>Las habilidades son la capa que puedes usar hoy. El resto llegará más adelante.</p><h2>Comenzar</h2><p><strong>Antes de que empieces: </strong>los agentes de programación con IA operan con credenciales reales, acceso real a la shell y, a menudo, con todos los permisos del usuario que los ejecuta. Cuando esos agentes se orientan a flujos de trabajo de seguridad, hay más en juego: le estás dando a un sistema automatizado acceso a la lógica de detección, las acciones de respuesta y la telemetría sensible. El perfil de riesgo de cada organización es diferente. Antes de habilitar los flujos de trabajo de seguridad impulsados por IA, <strong>evalúa a qué datos puede acceder el agente, qué acciones puede realizar y qué pasa si se comporta de forma inesperada</strong>.</p><p>Instala Elastic Agent Skills en el tiempo de ejecución de tu agente:</p><p><code>npx skills add elastic/agent-skills</code></p><p>Esto detecta automáticamente tus tiempos de ejecución del agente instalado y coloca las habilidades en el directorio de configuración correcto. Desde allí, tu agente los recoge automáticamente.</p><p>También puedes navegar directamente en el <a href="https://github.com/elastic/agent-skills">catálogo de habilidades</a> e instalar habilidades individuales manualmente si copias la carpeta de la habilidad en el directorio de configuración de tu agente.</p><p>¿Aún no tienes un clúster de Elasticsearch? Comienza una <a href="https://cloud.elastic.co/registration">prueba gratis de Elastic Cloud</a>. Lleva aproximadamente un minuto obtener un entorno completamente configurado.</p><p><strong>Explora el proyecto:</strong></p><ul><li><a href="https://github.com/elastic/agent-skills">Repositorio de habilidades de agentes</a></li><li><a href="https://agentskills.io">especificación de agentskills.io</a></li><li><a href="https://www.elastic.co/docs">Documentación de Elasticsearch</a></li><li><a href="https://cloud.elastic.co/registration">Prueba gratuita de Elastic Cloud</a></li></ul>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/es/blog/entity-resolution-elasticsearch-llm-challenges</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/entity-resolution-elasticsearch-llm-challenges</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
            <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/70fb0ff949f6db9ac9b8a28ecb4329ab915ebf46-720x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <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 <strong>muchos tipos diferentes</strong> 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>Convenciones de nomenclatura cultural.</li><li>Referencias basadas en cargos.</li><li>Relaciones comerciales y cambios históricos de nombre.</li><li>Menciones multilingües y entre sistemas de escritura.</li><li>Desafíos compuestos que mezclan varios de los anteriores.</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><strong>50 entidades</strong>, que abarcan personas, organizaciones e instituciones.</li><li><strong>~60 artículos</strong>, con estructuras y complejidad lingüísticas variables.</li><li><strong>51 categorías distintas de desafíos</strong>, agrupadas en términos generales en:<ul><li>Convenciones de nomenclatura cultural.</li><li>Títulos y contexto profesional.</li><li>Relaciones empresariales y organizacionales.</li><li>Desafíos multilingües y de transliteración.</li><li>Escenarios combinados y de casos límite.</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>Un título como “el presidente” no tiene sentido sin contexto geopolítico y temporal.</li><li>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.</li><li>El nombre de una persona puede aparecer en diferentes órdenes, sistemas de escritura o transcripciones, dependiendo del idioma y la cultura.</li><li>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.</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>Elasticsearch reduce el espacio de candidatos de manera eficiente y transparente.</li><li>El LLM se usa solo donde se requiere juicio y está obligado a explicarse a sí mismo.</li><li>La recuperación y el razonamiento siguen siendo pasos distintos.</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><strong>No</strong> agregamos lógica especial para nombres japoneses.</li><li><strong>No</strong> agregamos reglas personalizadas para patronímicos árabes.</li><li><strong>No</strong> agregamos mapeos codificados para nombres históricos de compañías.</li></ul><p>En cambio, el sistema se basó en los mismos elementos principales presentados anteriormente en la serie:</p><ul><li>Entidades enriquecidas con contexto indexadas para búsqueda semántica.</li><li>Recuperación híbrida (exacta, alias y semántica) en Elasticsearch.</li><li>Un conjunto pequeño y bien definido de posibles coincidencias.</li><li>El juicio del LLM está limitado por las llamadas a funciones y los esquemas mínimos.</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><strong>Precisión:</strong> ~91 %</li><li><strong>Recuperación:</strong> ~86 %</li><li><strong>Puntuación F1:</strong> ~89 %</li><li><strong>Tasa de aceptación en LLM:</strong> ~72 %</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>Coincidencia entre diferentes sistemas de escritura (entidades comerciales en cirílico, coreano y chino).</li><li>Escenarios hebreos (patronímicos, títulos profesionales, títulos religiosos, transliteración).</li><li>Jerarquías empresariales (aeroespacial, producción diversificada, corporaciones multidivisionales).</li><li>Títulos profesionales (académicos, militares, políticos, religiosos).</li><li>Escenarios japoneses combinados que involucran múltiples sistemas de escritura.</li></ul><p><strong>Fuerte rendimiento (80-99 % de puntuación F1)</strong> incluido:</p><ul><li>Figuras políticas internacionales (98%).</li><li>Cambios de nombre históricos (90 %).</li><li>Jerarquías empresariales complejas (89 %).</li><li>Nombres de empresas japonesas (93 %).</li><li>Transliteración entre alfabetos (86 %).</li><li>Patronímicos árabes (86 %).</li></ul><p><strong>Las áreas más desafiantes</strong> fueron:</p><ul><li>Transliteración avanzada (chino, coreano): 0 % F1.</li><li>Ciertos escenarios japoneses (honoríficos, orden de los nombres, variación del sistema de escritura): ~67 % F1.</li><li>Algunos escenarios árabes (nombres de empresas, referencias institucionales): ~40% F1.</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><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.</li><li><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.</li><li><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.</li><li><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.</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.<br/></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Terminación temprana adaptativa para HNSW en Elasticsearch]]></title>
            <description><![CDATA[Presentamos una nueva estrategia adaptativa de terminación temprana para HNSW en Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/hnsw-elasticsearch-adaptive-early-termination</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/hnsw-elasticsearch-adaptive-early-termination</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Tommaso Teofili]]></dc:creator>
            <pubDate>Mon, 02 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e6d3186f609dd56dc5ffe33d70fa9e5cfa05b51f-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch utiliza el algoritmo <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">Hierarchical Navigable Small World</a> (HNSW) para realizar búsquedas de vectores en un grafo de proximidad. Se sabe que HNSW ofrece un buen equilibrio entre la calidad de los resultados del método k vecino más cercano (KNN) y el costo asociado.</p><p>En HNSW, la búsqueda avanza expandiendo iterativamente los nodos candidatos en el grafo, lo que mantiene un conjunto acotado de vecinos más cercanos descubiertos hasta ahora. Cada expansión tiene un costo (operaciones vectoriales, búsquedas aleatorias en el disco y más), y el beneficio marginal de ese costo tiende a disminuir a medida que avanza la búsqueda.</p><p>Una forma de optimizar el recorrido de grafos de HNSW es dejar de buscar cuando la probabilidad marginal de encontrar nuevos vecinos verdaderos no aumenta. Por esta razón, en <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules#index-dense-vector-hnsw-early-termination">Elasticsearch 9.2</a>, introdujimos un nuevo <a href="https://www.elastic.co/search-labs/blog/hnsw-knn-search-early-termination">mecanismo de terminación temprana</a>. Esto detiene el proceso de búsqueda cuando visitar nodos del grafo no proporciona suficientes vecinos más cercanos nuevos, consecutivamente, para un número fijo de veces.</p><p>En este artículo, te orientamos sobre cómo mejoramos el mecanismo de terminación temprana mencionado en HNSW para hacerlo más adecuado para diferentes sets de datos y distribuciones de datos.</p><h2><strong>Terminación temprana en HNSW</strong></h2><p>En HNSW, la búsqueda avanza expandiendo iterativamente nodos candidatos en el grafo de proximidad, lo que mantiene un conjunto limitado de vecinos más cercanos descubiertos hasta el momento, hasta que haya visitado todo el grafo o cumpla con algunos criterios de terminación temprana.</p><p>Por lo tanto, la terminación temprana no siempre es necesariamente una optimización, sino que es <strong>parte del algoritmo de búsqueda en sí</strong>. El momento en que decidimos detenernos determina el equilibrio entre eficiencia y recuperación. En Elasticsearch, ya hay varias formas en que una consulta en HNSW puede terminar de forma temprana:</p><ul><li>Se visita una cantidad máxima fija de nodos.</li><li>Se alcanza un tiempo de espera fijo.</li></ul><p>Si bien son simples y previsibles, estas reglas son, en gran medida, <strong>independientes de lo que realmente está haciendo la búsqueda</strong>. También se emplean, principalmente, para garantizar que la búsqueda finalice en un tiempo razonable para el usuario final.</p><p>En una <a href="https://www.elastic.co/search-labs/blog/hnsw-knn-search-early-termination">entrada anterior del blog</a>, presentamos el concepto de redundancia en HNSW. En resumen, los cálculos redundantes ocurren cuando HNSW continúa evaluando nuevos nodos candidatos que no dan como resultado la búsqueda de más vecinos más cercanos.</p><h2><strong>Paciencia: medir el progreso en lugar del esfuerzo</strong></h2><p>La noción de <em>paciencia</em> replantea la terminación temprana en torno al <strong>progreso, en lugar del esfuerzo</strong>.</p><p>En lugar de preguntar:</p><p>“¿Cuántos pasos hemos dado?”</p><p>La nueva pregunta es:</p><p>“¿Qué cantidad de cómputo aceptamos desperdiciar hasta que perdemos la esperanza?”</p><p>Durante la búsqueda de HNSW, por lo general, la exploración temprana produce mejoras máximas en el conjunto de candidatos top-k. Durante los primeros pasos de la exploración de grafos de HNSW, el conjunto de vecinos se actualiza continuamente a medida que el algoritmo sigue descubriendo vecinos cada vez más cercanos al vector de búsqueda. Con el tiempo, estas mejoras se vuelven más raras a medida que la búsqueda converge. <a href="https://cs.uwaterloo.ca/~jimmylin/publications/Teofili_Lin_ECIR2025.pdf">La terminación basada en la paciencia</a> monitorea este patrón y finaliza la búsqueda una vez que las mejoras han cesado durante un período prolongado.</p><p>En la práctica, al visitar el grafo de HNSW, también calculamos la relación de saturación de la cola al saltar entre nodos candidatos. Esto mide el porcentaje de vecinos más cercanos que se dejaron sin cambios al visitar el nodo del grafo más reciente (o el inverso del número de nuevos vecinos introducidos durante la última iteración). Cuando esa proporción se vuelve demasiado grande para demasiadas iteraciones consecutivas, dejamos de visitar el grafo.</p><p>Conceptualmente, la paciencia trata la búsqueda de HNSW como un <strong>proceso de rendimientos decrecientes</strong>. Cuando los rendimientos se estabilizan, seguir analizando el grafo aporta pocos beneficios.</p><p>Esta estructura es poderosa porque vincula la terminación directamente a <em>resultados observables</em>, en lugar de a límites fijos arbitrarios.</p><p>El beneficio de usar esta técnica inteligente de terminación temprana es que las exploraciones del grafo de HNSW tienden a visitar un número menor de nodos del grafo, mientras mantienen una recuperación relativa casi perfecta.</p><p>Para visualizar esto, podemos graficar la cantidad de recuperación por nodo visitado que obtuvimos con la terminación temprana basada en la paciencia (etiquetada como <em><code>et=static</code></em>), en comparación con el comportamiento predeterminado de HNSW (etiquetado como <em><code>et=no</code></em>) en un par de sets de datos, FinancialQA y Quora, y modelos, JinaV3 y E5-small.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a9d07c5153ea64a2426c82487c36846030692bb9-1600x945.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/dac56125c4b16d1b596c9876b6ca9ac7b2dc87fa-1600x944.png" style="width: 100%;" /><h2><strong>Umbrales estáticos y dinámicas de HNSW</strong></h2><p>En la práctica, en Elasticsearch esto se implementa utilizando <strong>umbrales estáticos</strong>. Un umbral se refiere al <strong>umbral de saturación</strong>, es decir, la proporción de saturación que consideramos subóptima. El otro umbral se refiere al número de nodos del grafo consecutivos que permitimos que se visiten sin dejar de tener una saturación de cola subóptima: es decir, el <strong>umbral de paciencia.</strong></p><p>Cuando introdujimos esta estrategia de terminación temprana en Elasticsearch 9.2, decidimos optar por valores predeterminados conservadores, para permitir la recuperación tanto como fuera posible, mientras se mejora la latencia y el consumo de memoria. Por esta razón, establecemos el umbral de saturación al 100 % y el umbral de paciencia se establece como un 30 % (limitado) del <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query#knn-query-top-level-parameters:~:text=search%20request%20size.-,num_candidates,-(Optional%2C%20integer)%20The"><em><code>num_candidates</code></em></a> en la búsqueda de KNN.</p><p>En muchas situaciones, estas configuraciones resultaron funcionar bien; sin embargo, dos búsquedas que solicitan el mismo número de vecinos podrían tener comportamientos de convergencia radicalmente diferentes. Algunas búsquedas encuentran vecindarios locales densos y se saturan rápidamente; otras deben atravesar caminos largos y dispersos antes de encontrar candidatos competitivos. Estas últimas resultaron ser las más difíciles de manejar con eficacia.</p><p>Como resultado, a veces notamos lo siguiente:</p><ul><li>Sobreexploración para búsquedas sencillas.</li><li>Terminación prematura para búsquedas difíciles.</li></ul><p>Por lo tanto, consideramos que los valores de umbral fijos codifican suposiciones globales sobre la convergencia, mientras que podríamos hacer que HNSW se adapte mejor a diferentes dinámicas.</p><h2><strong>Hacer que la terminación temprana de HNSW sea adaptativa</strong></h2><p>La terminación temprana adaptativa aborda este problema desde otro ángulo. En lugar de imponer umbrales de parada predefinidos, el algoritmo <strong>infiere cuándo detenerse de la propia dinámica de búsqueda</strong>.</p><p>Entonces, en lugar de comparar la proporción de saturación de cola entre dos candidatos consecutivos, decidimos introducir una tasa de descubrimiento suavizada instantánea $d_{q,i} $ (cuántos vecinos nuevos se introdujeron para una búsqueda <em>q</em> en la <em>última visita i</em>) junto con el promedio móvil $\mu_{q,i}$ y la desviación estándar $\sigma_{q,i}$ de tal tasa de descubrimiento durante la visita del grafo (usando el <a href="https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford&#x27;s_online_algorithm">algoritmo de Welford</a>). Estas estadísticas sobre la tasa de descubrimiento se calculan por búsqueda, de modo que esta información pueda utilizarse para decidir diferentes grados de paciencia para cada búsqueda.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1958be7ca4425ade66eaf621ada3533173183598-694x118.png" style="width: 100%;" /><p>Los umbrales anteriormente estáticos se vuelven adaptativos a las estadísticas de tasa de descubrimiento: el umbral de saturación se convierte en el promedio móvil más la desviación estándar; mientras tanto, hacemos que la paciencia se adapte y escale inversamente con la desviación estándar.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f7ee4a55c24853b657df26052b275e8bd76cf0f9-654x156.png" style="width: 100%;" /><p>Las reglas de salida anticipada siguen siendo las mismas; la saturación se produce cuando la tasa de descubrimiento instantáneo es menor que el umbral de saturación adaptativa. La visita al grafo se detiene si la saturación persiste durante un número de visitas consecutivas de candidatos que es mayor que la paciencia adaptativa.</p><p>De este modo, obtenemos un comportamiento que no depende del parámetro <em><code>num_candidates</code></em> en la búsqueda de KNN (que puede estar siempre establecido o dejado como predeterminado, independientemente de si sale pronto) y que se adapta mejor dinámicamente a cada consulta y distribución vectorial.</p><p>La recuperación por nodo visitado en FinancialQA y Quora con la estrategia adaptativa (etiquetada como <em><code>et=adaptive</code></em>) reporta una mayor recuperación por nodo visitado, en comparación con la estrategia estática (<em><code>et=static</code></em>) y el comportamiento predeterminado de HNSW (<em><code>et=no</code></em>).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2a906997d9a25d74c7038bd9661bc97581e7258e-1600x938.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3a114911e232c351dbb814cea20e8b0f1415a717-1600x925.png" style="width: 100%;" /><p>La terminación temprana adaptativa está activada de manera predeterminada en Elasticsearch 9.3 para los campos vectoriales densos de HNSW (y, eventualmente, se puede desactivar a través de la <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules#index-dense-vector-hnsw-early-termination">misma configuración de nivel de índice</a>).</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Lenguaje de expresión común (CEL): cómo la entrada CEL mejora la recopilación de datos en las integraciones de Elastic Agent]]></title>
            <description><![CDATA[Aprende cómo se diferencia el lenguaje de expresión común de otros lenguajes de programación, cómo lo ampliamos para la entrada CEL de Filebeat y la flexibilidad que te ofrece para expresar la lógica de recopilación de datos en integraciones con Elastic Agent.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/common-expression-language-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/common-expression-language-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Chris Berkhout]]></dc:creator>
            <pubDate>Fri, 27 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/985c50bfabee3348494eb4307f0b3375a97a0644-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Las <a href="https://www.elastic.co/integrations">integraciones</a> de Elastic Agent permiten a los usuarios realizar la ingesta de datos en Elasticsearch desde una amplia variedad de fuentes. Combinan la lógica de recopilación, canalizaciones de ingesta, dashboards y otros artefactos en un paquete que se puede instalar y administrar desde la interfaz web de Kibana.</p><p>Las integraciones configuran <a href="https://www.elastic.co/docs/reference/beats/filebeat/configuration-filebeat-options">las entradas de Filebeat</a> para realizar la recopilación de datos. Para recopilar datos de APIs HTTP, a menudo utilizamos la <a href="https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-httpjson">entrada HTTP JSON</a>. Sin embargo, incluso las APIs básicas de listado pueden diferir mucho en los detalles, y el modelo de transformaciones configuradas en YAML de la entrada HTTP JSON puede hacer que sea engorroso y a veces imposible expresar la lógica de recopilación requerida.</p><p>Se introdujo la <a href="https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-cel">entrada del lenguaje de expresión común (CEL)</a> para permitir una interacción más flexible con las APIs HTTP. <a href="https://cel.dev/">CEL</a> es un lenguaje diseñado para integrarse en aplicaciones que requieren una forma rápida, segura y extensible de expresar condiciones y transformaciones de datos. La entrada CEL permite a un creador de integraciones escribir una expresión que puede leer configuraciones, realizar un seguimiento de su propio estado, realizar solicitudes, procesar respuestas y, en última instancia, devolver eventos listos para su ingesta.</p><p>En este artículo, veremos cómo se diferencia el lenguaje de expresión común de otros lenguajes de programación, cómo lo ampliamos para la entrada del CEL, y la flexibilidad y el poder que te da para expresar tu lógica de recopilación de datos.</p><h2>CEL y cómo funciona en la entrada</h2><p>CEL es un lenguaje de expresión. No tiene declaraciones. Cuando escribes CEL, no le dices qué hacer al escribir declaraciones, sino que le dices qué valor producir al escribir una expresión. Cada expresión CEL produce un valor, y las expresiones más pequeñas pueden combinarse en una expresión mayor para producir un resultado según reglas más complejas. Más adelante veremos cómo usar expresiones que se pueden escribir con declaraciones en otros idiomas.</p><p>CEL es intencionadamente un lenguaje completo no Turing. No permite bucles ilimitados. Más adelante, veremos cómo puedes procesar listas y mapas mediante macros, pero al evitar bucles sin límites, el lenguaje garantiza un tiempo de ejecución predecible y limitado para expresiones individuales.</p><p>La entrada CEL está configurada con un programa CEL (una expresión) y un estado inicial. El estado se proporcionará como entrada al programa. El programa se evalúa para producir un estado de salida. Si el estado de salida incluye una lista de eventos, estos se eliminarán y se publicarán. El resto del estado de salida se usará como entrada para la próxima evaluación. Si el estado de salida incluye uno o más eventos y la advertencia <code>want_more: true</code>, la siguiente evaluación se realizará inmediatamente. De lo contrario, permanecerá en suspensión durante el resto del intervalo configurado antes de continuar. Aquí tienes un diagrama simplificado del flujo de control de la entrada:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/42671541f97e2dba808fd53969fe12f517917f9a-1600x529.png" style="width: 100%;" /><p>La salida de cada evaluación se pasará como entrada a la siguiente evaluación, mientras se ejecute la entrada. Los datos de salida bajo la clave &quot;<code>cursor</code>&quot; se mantendrán en el disco y se volverán a cargar después de que la entrada se reinicie, pero el resto del estado no se conservará entre los reinicios.</p><p>El lenguaje CEL en sí tiene funcionalidades limitadas y evita efectos secundarios, pero es extensible. La implementación <a href="https://github.com/google/cel-go">cel-go</a> agrega algunas funcionalidades, como sintaxis y tipos opcionales. La biblioteca <a href="https://github.com/elastic/mito">Mito</a> se basa en cel-go y agrega más funcionalidades, como la capacidad de hacer solicitudes HTTP. La entrada CEL usa la versión de CEL de Mito.</p><h2>Trabajando con Mito</h2><p>Para construir o depurar una integración mediante una entrada CEL, lo más importante que debes entender es qué estado de salida producirá tu programa CEL para un estado de entrada dado. Durante el desarrollo, puede ser engorroso que tu programa CEL la ejecute la entrada, rodeada por toda la pila de Elastic Stack. Una forma de lograr un ciclo de retroalimentación más rápido es usar la herramienta de línea de comando de Mito, que te permitirá ejecutar un programa CEL directamente y ver la salida que produce para una entrada dada.</p><p>Mito está escrito en Go y se puede instalar de la siguiente manera:</p><pre><code>go install github.com/elastic/mito/cmd/mito@latest</code></pre><p>Cuando ejecutas un programa CEL con Mito, normalmente le asignas dos archivos: un archivo JSON con el estado inicial de entrada y otro archivo con el código fuente de tu programa CEL:</p><pre><code>mito -data state.json src.cel</code></pre><p>Para facilitar la copia y el pegado, los ejemplos en este artículo están escritos como comandos únicos que hacen que el shell cree archivos temporales al instante al envolver el contenido de cada archivo en <code>&lt;(echo &#x27;...content...&#x27;)</code>. En tu propio desarrollo, trabajar con archivos reales será más fácil.</p><h2>Obtención de datos de incidencias desde GitHub</h2><p>El siguiente ejemplo incluye un programa CEL completo que obtendrá datos sobre los problemas de la <a href="https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#list-repository-issues">API de GitHub</a>. Su estado de entrada inicial tiene una URL para el endpoint de la API y cierta información sobre cómo debe manejar la paginación. El programa CEL utiliza los datos del estado de entrada para generar una solicitud. Decodificará la respuesta, generará eventos a partir de ella y los devolverá como parte de su estado de salida.</p><pre><code>mito -data <(echo '
  {
    "url": "https://api.github.com/repos/elastic/integrations/issues",
    "per_page": 3,
    "max_pages": 3
  }
') <(echo '
  int(state.?cursor.page.orValue(1)).as(page,
    (
      state.url + "?" + {
        "state": ["all"],
        "sort": ["created"],
        "direction": ["asc"],
        "per_page": [string(state.per_page)],
        "page": [string(page)],
      }.format_query()
    ).as(full_url,
      request("GET", full_url).with({
        "Header": {
          "Accept": ["application/vnd.github+json"],
          "X-GitHub-Api-Version": ["2022-11-28"],
        }
      }).do_request().as(resp,
        resp.Body.decode_json().as(data,
          state.with({
            "events": data.map(i, {
              "html_url": i.html_url,
              "title": i.title,
              "created_at": i.created_at,
            }),
            "cursor": { "page": page + 1 },
            "want_more": size(data) == state.per_page && page < state.max_pages,
          })
        )
      )
    )
  )
')</code></pre><p>Su primera evaluación produce la siguiente salida:</p><pre><code>{
  "cursor": {
    "page": 2
  },
  "events": [
    {
      "created_at": "2018-09-14T09:47:35Z",
      "html_url": "https://github.com/elastic/integrations/issues/3250",
      "title": "Increase support of log formats in haproxy filebeat module"
    },
    {
      "created_at": "2019-02-06T12:37:37Z",
      "html_url": "https://github.com/elastic/integrations/issues/487",
      "title": "ETCD Metricbeat module needs polishing and grooming"
    },
    {
      "created_at": "2019-08-13T11:33:11Z",
      "html_url": "https://github.com/elastic/integrations/pull/1",
      "title": "Initial structure"
    }
  ],
  "max_pages": 3,
  "per_page": 3,
  "url": "https://api.github.com/repos/elastic/integrations/issues",
  "want_more": true
}</code></pre><p>Los eventos se eliminarán y, cuando se ejecuten en la entrada CEL, se publicarán para su ingesta. El resto de la salida se proporcionará a la siguiente evaluación del programa CEL como estado de entrada.</p><p></p><p>Para entender cómo funciona ese programa CEL, veremos algunos ejemplos más pequeños de CEL y hablaremos con más detalle sobre cómo funciona la entrada de CEL.</p><h2>Conceptos básicos de CEL</h2><p>En el lenguaje CEL, no hay declaraciones, solo hay expresiones. Cada expresión CEL exitosa se evalúa hasta un valor final. Esta es una de las expresiones CEL más pequeñas que puedes escribir, junto con su salida:</p><pre><code>mito <(echo '
  "hello" + " " + "world"
')</code></pre><pre><code>"hello world"</code></pre><p>Muchas expresiones simples son intuitivas. Las operaciones matemáticas solo se admiten en valores del mismo tipo (por ejemplo, <code>int</code> con <code>int</code>), así que convierte los tipos según necesites (aquí de <code>int</code> a <code>double</code>):</p><pre><code>mito <(echo '
  double((1 + 2) * (3 + 4)) / 2.0
')</code></pre><pre><code>10.5</code></pre><p>No existen variables en el lenguaje CEL, pero una expresión puede recibir un nombre y usarse en una expresión más amplia con la ayuda de la macro <a href="https://pkg.go.dev/github.com/elastic/mito/lib#hdr-As__Macro_-Collections"><code>as</code></a> de Mito. En este ejemplo, la expresión <code>(1 + 1)</code> evalúa al valor <code>2</code> y <code>.as(n, ...)</code> da a ese valor el nombre <code>n</code> para su uso en la expresión <code>&quot;one plus one is &quot;+string(n)</code>:</p><pre><code>mito <(echo '
  (1 + 1).as(n, "one plus one is "+string(n))
')</code></pre><pre><code>"one plus one is 2"</code></pre><p>También es posible acumular información en un mapa y usarla más tarde en la expresión, como se demuestra aquí al usar <a href="https://pkg.go.dev/github.com/elastic/mito/lib#hdr-With-Collections"><code>with</code></a>:</p><pre><code>mito <(echo '
  { "key": "value" }.with({ "key2": "value2" }).as(data,
    {
      "data": data,
      "size": size(data),
    }
  )
')</code></pre><pre><code>{
  "data": {
    "key": "value",
    "key2": "value2"
  },
  "size": 2
}</code></pre><p>Mira ese ejemplo otra vez. Observa que la parte anidada, <code>({ &quot;data&quot;: data, &quot;size&quot;: size(data), })</code>, nos da la forma del valor final. Es un mapa con las claves <code>&quot;data&quot;</code> y <code>&quot;size&quot;</code>. Los valores de esas claves dependen de <code>data</code>, que la define la parte exterior de la expresión. Leer las expresiones de CEL desde adentro hacia afuera puede ayudar a ver rápidamente qué devolverán.</p><p>CEL no tiene declaraciones de flujo de control, como <code>if</code>, pero la ramificación condicional se puede hacer con el operador ternario:</p><pre><code>mito <(echo '
  1 + 1 < 12 ? "few" : "many"
')</code></pre><pre><code>"few"</code></pre><p>Los bucles ilimitados y la recursión no son compatibles, ya que CEL no es un lenguaje de Turing completo. Esto hace que el tiempo de ejecución sea predecible y proporcional al tamaño de los datos de entrada y a la complejidad de la expresión.</p><p>Aunque los bucles sin límites no son posibles en expresiones CEL individuales, puedes procesar listas y mapas con macros como <a href="https://github.com/google/cel-spec/blob/master/doc/langdef.md#macros"><code>map</code></a>:</p><pre><code>mito <(echo '
  [1, 2, 3].map(x, x * 2)
')</code></pre><pre><code>[2, 4, 6]</code></pre><p>En esta sección, abordamos los siguientes temas:</p><ul><li>Textos, números, listas y mapas.</li><li>Concatenación de texto.</li><li>Operaciones matemáticas.</li><li>Conversión de tipos.</li><li>Condicionales.</li><li>Nombrando subexpresiones.</li><li>Procesando colecciones.</li></ul><p>A continuación, veremos cómo hacer solicitudes HTTP.</p><h2>Solicitudes</h2><p>Mito extiende el CEL con la capacidad de realizar <a href="https://pkg.go.dev/github.com/elastic/mito/lib#HTTP">solicitudes HTTP</a>:</p><pre><code>mito <(echo '
  get("https://example.com").as(resp, string(resp.Body))
')</code></pre><pre><code>"<!doctype html><html lang=\"en\"><head><title>Example Domain</title>..."</code></pre><p>Las solicitudes se pueden construir explícitamente antes de que se ejecuten. Esto permite utilizar diferentes métodos HTTP y agregar encabezados y un cuerpo.</p><p>En este ejemplo, construimos una URL con la ayuda de <a href="https://pkg.go.dev/github.com/elastic/mito/lib#hdr-Format_Query-HTTP"><code>format_query</code></a>, agregamos un encabezado a la solicitud y parseamos el cuerpo de la respuesta con <a href="https://pkg.go.dev/github.com/elastic/mito/lib#hdr-Decode_JSON-JSON"><code>decode_json</code></a>. Cuando se te da la opción <code>-log_requests</code>, Mito registra información detallada en formato JSON sobre cada solicitud y respuesta.</p><pre><code>mito -log_requests <(echo '
  request("GET",
    "https://postman-echo.com/get?" + {
        "q": ["query value"]
     }.format_query()
  ).with({
    "Header": { "Accept": ["application/json"] }
  }).do_request().as(resp, {
    "status": resp.StatusCode,
    "data": resp.Body.decode_json(),
  })
')</code></pre><pre><code>{"time":"...","level":"INFO","msg":"HTTP request",...}
{"time":"...","level":"INFO","msg":"HTTP response",...}
{
  "data": {
    "args": {
      "q": "query value"
    },
    "headers": {
      "accept": "application/json",
      "accept-encoding": "gzip, br",
      "host": "postman-echo.com",
      "user-agent": "Go-http-client/2.0",
      "x-forwarded-proto": "https"
    },
    "url": "https://postman-echo.com/get?q=query+value"
  },
  "status": 200
}</code></pre><h2>Gestión del estado y evaluaciones</h2><p>Ahora que ya vimos cómo realizar solicitudes y los conceptos básicos de CEL necesarios para producir el estado de salida deseado, veamos más de cerca qué debemos incluir en el estado de salida y cómo eso nos permite dirigir el procesamiento posterior.</p><p>El programa CEL de una integración debe asegurarse de que su estado de salida sea adecuado para su uso como entrada de la siguiente evaluación. La configuración establece el estado inicial, y eso debe repetirse en la salida con los cambios apropiados. Una forma sencilla de hacerlo es usar <code>state.with({ ... })</code>, para repetir el mapa de estado con algunas sobrescrituras. Un patrón común para programas pequeños es envolver todo el programa en <code>state.with()</code>, para que la propagación de estados no tenga que repetirse en cada rama que genere datos de salida (por ejemplo: éxito, errores).</p><p>Cuando hay valores de estado que se inicializan mediante una evaluación en lugar de estar codificados de forma fija en el estado de entrada inicial, el programa tendrá que comprobar si existe un valor antes de establecer el inicial. Eso es algo en lo que el soporte para <a href="https://pkg.go.dev/github.com/google/cel-go/cel#OptionalTypes">sintaxis y tipos opcionales</a> puede ayudar. Al usar un signo de interrogación antes del nombre del campo en una clave de mapa, el acceso se vuelve opcional: puede o no resolver a un valor, pero son posibles accesos opcionales adicionales y es fácil proporcionar un valor por defecto si no hay un valor presente:<br/></p><pre><code>mito -data <(echo '{}') <(echo '
  int(state.?counter.orValue(0)).as(counter,
    state.with({
      "counter": counter + 1,
      "want_more": counter + 1 < 3,
    })
  )
')</code></pre><pre><code>{ "counter": 1, "want_more": true }
{ "counter": 2, "want_more": true }
{ "counter": 3, "want_more": false }</code></pre><p>En ese ejemplo, el valor del contador leído desde el estado se convierte en <code>int</code> porque todos los números se serializan en el estado como números de coma flotante, de acuerdo con las convenciones establecidas por el tipo <code>Number</code> de JSON y JavaScript. También debe tenerse en cuenta que Mito honra <code>&quot;want_more&quot;: true</code>, pero cuando se ejecuta en la entrada CEL, la evaluación solo se repetiría si la salida también contiene eventos.</p><p>Es un requisito de los programas CEL ejecutados por la entrada CEL que devuelvan una clave <code>&quot;events&quot;</code> en su mapa de salida. Su valor puede ser una lista de mapas de eventos, una lista vacía o un solo mapa de eventos. El caso de evento único generalmente se utiliza para errores. El evento se publicará mediante la entrada, pero su valor también se registrará y, si estableces un valor de <code>error.message</code>, se usará para actualizar el estado de salud de la Fleet de la integración. Si tu programa produce un solo evento sin error, es mejor envolverlo en una lista.</p><p>Echa otro vistazo a la salida de nuestro programa de problemas de GitHub de antes:</p><pre><code>{
  "url": "https://api.github.com/repos/elastic/integrations/issues",
  "per_page": 3,
  "max_pages": 3,
  "cursor": {
    "page": 2
  },
  "events": [
    { ... },
    { ... },
    { ... }
  ],
  "want_more": true
}</code></pre><p>El programa gestionó eficazmente su estado, mediante:</p><ul><li>Valores de estado iniciales repetidos en <code>url</code>, <code>per_page</code>y <code>max_pages</code>.</li><li>Agregar un estado que debería persistir durante los reinicios en <code>cursor.page</code>.</li><li>Devolver eventos listos para publicar en la lista <code>events</code>.</li><li>Solicito una reevaluación inmediata con <code>want_more: true</code>.</li></ul><p>Ahora que entiendes el acceso opcional y la gestión de estados, así como los conceptos básicos de CEL y las solicitudes HTTP, deberías poder leer el programa completo de incidencias de GitHub. Intenta ejecutarlo con Mito y experimentar con algunos cambios.</p><h2>Revisión y recursos</h2><p>En este artículo, analizamos qué es el lenguaje CEL y cómo se ha extendido en la biblioteca Mito para su uso en la entrada CEL. Vimos la flexibilidad de CEL en un programa de ejemplo que obtiene información de incidencias de la API de GitHub, y repasamos todos los detalles necesarios para comprender ese programa, incluidos el acceso a la configuración en el estado inicial, la interacción con las API HTTP, el retorno de eventos para su ingesta y la gestión del estado para ejecuciones posteriores del programa.</p><p>Para aprender más y construir integraciones mediante la entrada CEL, hay varios recursos que valen la pena explorar:</p><ul><li><a href="https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-cel">Entrada CEL - documentación de Filebeat</a></li><li><a href="https://pkg.go.dev/github.com/elastic/mito">Documentación de Mito</a></li><li><a href="https://cel.dev/">Lenguaje de expresión común - sitio web cel.dev</a></li><li><a href="https://www.elastic.co/docs/extend/integrations">Crear una integración - Documentación de Elastic</a></li></ul><p>Y quizás el recurso más valioso para crear integraciones con la entrada CEL sea el código CEL de las integraciones existentes de Elastic, que se puede encontrar en GitHub:</p><p><a href="https://github.com/search?q=repo%3Aelastic%2Fintegrations+path%3A**%2Fcel.yml.hbs&amp;type=code"><code>cel.yml.hbs</code> archivos en el repositorio de integraciones de Elastic - GitHub</a></p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/es/blog/elasticsearch-entity-resolution-llm-semantic-search</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elasticsearch-entity-resolution-llm-semantic-search</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
            <pubDate>Thu, 26 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/473ca4357c7d60f690edbd2a844acda169aca9c3-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <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: &quot; ¡La actualización de Swift ya está aquí! &quot; Imagina que este titular va acompañado de un poco más de contexto:</p><ol><li>¡La nueva actualización de Swift está aquí! Los desarrolladores están ansiosos por probar las nuevas características.</li><li>¡La nueva actualización de Swift está aquí! El nuevo álbum se lanzará el próximo mes.</li></ol><p>Con este contexto añadido, deberíamos ser capaces de resolver el nombre &quot;Swift&quot; 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 &quot;Swift&quot;. 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 &quot;codificado con onda&quot; 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 &quot;el presidente&quot;, 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 &quot;clave de respuestas&quot; 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 &quot;hacer trampa&quot; 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 &quot;autor ruso&quot; a &quot;León Tolstói&quot;. El LLM colocó incorrectamente &quot;autor ruso&quot;, como alias de &quot;León Tolstói&quot;, 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 &quot;desafío definitivo&quot; 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><strong>Entidades de la lista de vigilancia:</strong> 66 entidades de diversos tipos (personas, organizaciones, ubicaciones).</li><li><strong>Artículos de prueba:</strong> 69 artículos que abarcan casos de resolución de entidades del mundo real.</li><li><strong>Coincidencias esperadas:</strong> 206 coincidencias de entidades esperadas en todos los artículos.</li><li><strong>Tipos de desafíos: </strong>15 tipos diferentes de desafíos que evalúan varios aspectos de la resolución de entidades.</li></ul><p>Los tipos de desafíos incluidos en los sets de datos son:</p><ul><li><strong>Apodos:</strong> &quot;Bob Smith&quot; → &quot;Robert Smith&quot; (siete artículos).</li><li><strong>Títulos y honoríficos:</strong> &quot;Dr. Sarah Williams&quot; → &quot;Sarah Williams&quot; (cinco artículos).</li><li><strong>Contexto semántico:</strong> &quot;Autor ruso&quot; → &quot;León Tolstói&quot; (ocho artículos).</li><li><strong>Nombres multilingües:</strong> Manejo de nombres en diferentes escrituras (seis artículos).</li><li><strong>Entidades comerciales:</strong> Variaciones del nombre corporativo (siete artículos).</li><li><strong>Referencias ejecutivas: </strong>&quot;CEO de Microsoft&quot; → &quot;Satya Nadella&quot; (cinco artículos).</li><li><strong>Líderes políticos:</strong> Referencias basadas en títulos (cinco artículos).</li><li><strong>Iniciales:</strong> &quot;J. Smith &quot; → &quot;John Smith&quot; (tres artículos).</li><li><strong>Variaciones en el orden de los nombres:</strong> Diferentes convenciones de ordenamiento de nombres (tres artículos).</li><li><strong>Nombres truncados:</strong> Coincidencias parciales de nombres (tres artículos).</li><li><strong>División de nombres:</strong> Nombres divididos en el texto (tres artículos).</li><li><strong>Espacios/guiones faltantes:</strong> Variaciones de formato (dos artículos).</li><li><strong>Transliteración:</strong> Coincidencia de nombres entre sistemas de escritura (dos artículos).</li><li><strong>Desafíos combinados:</strong> Varios desafíos en un artículo (seis artículos).</li><li><strong>Negocios complejos:</strong> Relaciones comerciales jerárquicas (cinco artículos).</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>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Métrica</td><td style="border: 1px solid #ccc; padding: 4px;">Valor</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Precisión</td><td style="border: 1px solid #ccc; padding: 4px;">83.8 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Recuperación</td><td style="border: 1px solid #ccc; padding: 4px;">62.6 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Puntuación F1</td><td style="border: 1px solid #ccc; padding: 4px;">71,7%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Total de coincidencias encontradas</td><td style="border: 1px solid #ccc; padding: 4px;">344</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Tasa de aceptación de LLM</td><td style="border: 1px solid #ccc; padding: 4px;">44,8%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Tasa de error</td><td style="border: 1px solid #ccc; padding: 4px;">30.2%</td>
                    </tr>
              </tbody>
            </table>
          <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>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.</li></ol>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[La búsqueda de vectores de Elasticsearch es hasta 8 veces más rápida que OpenSearch]]></title>
            <description><![CDATA[Exploramos los puntos de referencia de búsqueda vectorial con filtrado entre OpenSearch y Elasticsearch, y por qué el rendimiento de la búsqueda vectorial es crucial para los sistemas diseñados en función del contexto.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/opensearch-vs-elasticsearch-filtered-vector-search</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/opensearch-vs-elasticsearch-filtered-vector-search</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Sachin Frayne]]></dc:creator>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fb7ee623925ca6696d643e437ce8efe5fe749079-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>¿Por qué es importante la velocidad de búsqueda para los agentes de IA y la ingeniería de contexto?</h2><p>Nuestros benchmarks en un corpus de documentos de 20M muestran que Elasticsearch ofrece hasta 8 veces más rendimiento que OpenSearch para búsqueda vectorial filtrada, además de lograr mayores Recall@100 en las configuraciones que probamos. La ingeniería de contexto depende de más que la rápida recuperación de vectores. Los equipos también necesitan fuertes controles de relevancia, como búsqueda y filtrado híbridos, simplicidad operativa y rendimiento predecible, a medida que los flujos de trabajo se repiten. Pero como los agentes suelen ejecutar bucles de recuperación y razonamiento muchas veces por cada solicitud, la latencia de la recuperación se convierte en un factor multiplicador, por lo que las mejoras en este aspecto se traducen directamente en una mejor capacidad de respuesta de extremo a extremo y en un menor costo.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5a52a07773f0942c2baa732dacfe782aac0f415-1600x683.png" style="width: 100%;" /><p>En la ingeniería de contexto, la recuperación no es un paso único. Los agentes y las aplicaciones ejecutan bucles repetidamente, como recuperar → razonar → recuperar, para refinar consultas, verificar hechos, reunir contexto fundamentado y completar tareas. Este patrón es común en los flujos de trabajo agénticos y en la Retrieval-Augmented Generation (RAG) iterativa. Como la recuperación puede invocarse muchas veces por cada consulta del usuario, agrega demora a la respuesta o aumenta los costos de infraestructura.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/54ac476ff20a3cf93484298c9ae47612c12fc110-800x417.png" style="width: 100%;" /><h2>¿Por qué es crítico el rendimiento de la búsqueda de vectores?</h2><p></p><p>Imagina un asistente de compras respondiendo la pregunta: &quot;Necesito una mochila de equipaje de mano de menos de $60 que quepa una laptop de 15 pulgadas, sea resistente al agua y pueda llegar para el viernes&quot;.</p><p>En producción, el asistente rara vez emite una consulta vectorial y se detiene ahí. Ejecuta un ciclo de recuperación para crear el contexto correcto, y cada paso suele estar limitado por filtros, como disponibilidad, región, promesa de envío, reglas de marca y elegibilidad de políticas.</p><p><strong>Paso 1: Interpretar la intención y traducirla a restricciones.</strong></p><p>El agente convierte la solicitud en filtros estructurados y una consulta semántica, tales como:</p><ul><li>Filtros: En stock, entregable al código postal del usuario, entrega antes del viernes, precio inferior a $60, listado válido</li><li>Consulta vectorial: &quot;Mochila de equipaje de mano computadora portátil de 15 pulgadas resistente al agua&quot;</li></ul><p><strong>Paso 2: Recuperar candidatos y luego refinar la selección.</strong></p><p>A menudo repite la recuperación con variaciones para evitar perder buenas coincidencias:</p><ul><li>&quot;mochila de viaje de equipaje de mano con funda para computadora portátil&quot;</li><li>&quot;mochila de viaje resistente al agua de 15 pulgadas&quot;</li><li>“mochila de cabina ligera”</li></ul><p>Cada consulta utiliza los mismos filtros de elegibilidad, porque recuperar elementos irrelevantes o no disponibles es un desperdicio de contexto.</p><p><strong>Paso 3: Expandir para confirmar detalles y reducir el riesgo.</strong></p><p>A continuación, el agente vuelve a consultar para verificar los atributos clave que influyen en la respuesta final:</p><ul><li>Palabras utilizadas para describir los materiales y la resistencia al agua</li><li>Dimensiones y ajuste del compartimento de la computadora portátil</li><li>Restricciones de la garantía o política de devolución</li><li>Opciones alternativas si hay poco inventario</li></ul><p>Esto es ingeniería de contexto en múltiples pasos: recuperar, razonar, recuperar, ensamblar.</p><h2>¿Por qué la latencia y la recuperación son importantes para la ingeniería de contexto?</h2><p>Estas interacciones pueden implicar decenas de llamadas de recuperación filtradas por sesión de usuario. Eso hace que la latencia por llamada sea un multiplicador directo en el tiempo de respuesta de extremo a extremo, y la baja recuperación obliga a reintentos adicionales o hace que el agente pierda elementos elegibles, lo que degrada la calidad de la respuesta.</p><p>Conclusión: En sistemas diseñados con contexto, los vecinos más cercanos aproximados (ANN, por su sigla en inglés) filtrados no son una sola consulta. Es una operación repetida bajo restricciones, por lo que el rendimiento de la búsqueda vectorial se nota enseguida en la latencia, la capacidad de procesamiento y el costo, incluso cuando el modelo de lenguaje grande (LLM) es el componente más visible.</p><h2>Evaluación comparativa</h2><h3>Resultados</h3><p>En el grafo 2, cada punto representa una configuración de prueba. Los mejores resultados aparecen hacia la parte superior izquierda, lo que significa una mayor recuperación con menor latencia. Los resultados de Elasticsearch se sitúan sistemáticamente más cerca de la esquina superior izquierda que los de OpenSearch, lo que indica una mayor velocidad y precisión con los mismos ajustes de carga de trabajo.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c50d1df00968cac18149a2799e6242fbe49b66a0-1600x990.png" style="width: 100%;" /><h4>Algunas ideas clave</h4><ul><li><code>s_n_r_value</code>: La abreviatura de <code>size_numCandidates_rescoreOversample</code> (k y numCandidates iguales a numCandidates en estas pruebas), por ejemplo, <code>100_500_1</code> significa tamaño=100, numCandidates=500 y k=500, rescore oversample=1</li><li>Recuperación: Mide Recall@100 para esa configuración</li><li>Latencia promedio (ms): Latencia de extremo a extremo promedio por consulta</li><li>Rendimiento: Búsquedas por segundo</li><li>Recall %: Mejora relativa de recuperación de Elasticsearch frente a OpenSearch (Elasticsearch menos OpenSearch)/OpenSearch</li><li>Latencia Xs: Latencia promedio de OpenSearch dividida por la latencia media de Elasticsearch</li><li>Rendimiento Xs: rendimiento de Elasticsearch dividido por el rendimiento de OpenSearch</li></ul>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Motor</td><td style="border: 1px solid #ccc; padding: 4px;">'s_n_r_value'</td><td style="border: 1px solid #ccc; padding: 4px;">Recuperación</td><td style="border: 1px solid #ccc; padding: 4px;">Latencia promedio (ms)</td><td style="border: 1px solid #ccc; padding: 4px;">Rendimiento</td><td style="border: 1px solid #ccc; padding: 4px;">Porcentaje de recuperación</td><td style="border: 1px solid #ccc; padding: 4px;">Latencia Xs</td><td style="border: 1px solid #ccc; padding: 4px;">Rendimiento Xs</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_250_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.7704</td><td style="border: 1px solid #ccc; padding: 4px;">25</td><td style="border: 1px solid #ccc; padding: 4px;">534.75</td><td style="border: 1px solid #ccc; padding: 4px;">9.70 %</td><td style="border: 1px solid #ccc; padding: 4px;">2.28</td><td style="border: 1px solid #ccc; padding: 4px;">1.91</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_250_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.7023</td><td style="border: 1px solid #ccc; padding: 4px;">57.08</td><td style="border: 1px solid #ccc; padding: 4px;">279.58</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.8577</td><td style="border: 1px solid #ccc; padding: 4px;">25.42</td><td style="border: 1px solid #ccc; padding: 4px;">524.14</td><td style="border: 1px solid #ccc; padding: 4px;">7.20 %</td><td style="border: 1px solid #ccc; padding: 4px;">2.4</td><td style="border: 1px solid #ccc; padding: 4px;">2</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.8001</td><td style="border: 1px solid #ccc; padding: 4px;">60.9</td><td style="border: 1px solid #ccc; padding: 4px;">262.12</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_750_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.8947</td><td style="border: 1px solid #ccc; padding: 4px;">29.67</td><td style="border: 1px solid #ccc; padding: 4px;">528.09</td><td style="border: 1px solid #ccc; padding: 4px;">5.72 %</td><td style="border: 1px solid #ccc; padding: 4px;">2.25</td><td style="border: 1px solid #ccc; padding: 4px;">2.21</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_750_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.8463</td><td style="border: 1px solid #ccc; padding: 4px;">66.76</td><td style="border: 1px solid #ccc; padding: 4px;">239.11</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_1000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9156</td><td style="border: 1px solid #ccc; padding: 4px;">29.65</td><td style="border: 1px solid #ccc; padding: 4px;">534.5</td><td style="border: 1px solid #ccc; padding: 4px;">4.66 %</td><td style="border: 1px solid #ccc; padding: 4px;">2.46</td><td style="border: 1px solid #ccc; padding: 4px;">2.44</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_1000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.8748</td><td style="border: 1px solid #ccc; padding: 4px;">72.88</td><td style="border: 1px solid #ccc; padding: 4px;">219.01</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_1500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9386</td><td style="border: 1px solid #ccc; padding: 4px;">31.84</td><td style="border: 1px solid #ccc; padding: 4px;">497.3</td><td style="border: 1px solid #ccc; padding: 4px;">3.38 %</td><td style="border: 1px solid #ccc; padding: 4px;">2.71</td><td style="border: 1px solid #ccc; padding: 4px;">2.68</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_1500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9079</td><td style="border: 1px solid #ccc; padding: 4px;">86.16</td><td style="border: 1px solid #ccc; padding: 4px;">185.4</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_2000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0,9507</td><td style="border: 1px solid #ccc; padding: 4px;">34.69</td><td style="border: 1px solid #ccc; padding: 4px;">457.2</td><td style="border: 1px solid #ccc; padding: 4px;">2.57 %</td><td style="border: 1px solid #ccc; padding: 4px;">2.98</td><td style="border: 1px solid #ccc; padding: 4px;">2.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_2000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9269</td><td style="border: 1px solid #ccc; padding: 4px;">103.36</td><td style="border: 1px solid #ccc; padding: 4px;">154.55</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_2500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9582</td><td style="border: 1px solid #ccc; padding: 4px;">37.9</td><td style="border: 1px solid #ccc; padding: 4px;">418.43</td><td style="border: 1px solid #ccc; padding: 4px;">1.99 %</td><td style="border: 1px solid #ccc; padding: 4px;">3.28</td><td style="border: 1px solid #ccc; padding: 4px;">3.26</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_2500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9395</td><td style="border: 1px solid #ccc; padding: 4px;">124.29</td><td style="border: 1px solid #ccc; padding: 4px;">128.53</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_3000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9636</td><td style="border: 1px solid #ccc; padding: 4px;">41.86</td><td style="border: 1px solid #ccc; padding: 4px;">379.4</td><td style="border: 1px solid #ccc; padding: 4px;">1.62 %</td><td style="border: 1px solid #ccc; padding: 4px;">3.46</td><td style="border: 1px solid #ccc; padding: 4px;">3.44</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_3000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9482</td><td style="border: 1px solid #ccc; padding: 4px;">144.67</td><td style="border: 1px solid #ccc; padding: 4px;">110.34</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_4000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9705</td><td style="border: 1px solid #ccc; padding: 4px;">50.28</td><td style="border: 1px solid #ccc; padding: 4px;">316.21</td><td style="border: 1px solid #ccc; padding: 4px;">1,06%</td><td style="border: 1px solid #ccc; padding: 4px;">3.87</td><td style="border: 1px solid #ccc; padding: 4px;">3.85</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_4000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9603</td><td style="border: 1px solid #ccc; padding: 4px;">194.36</td><td style="border: 1px solid #ccc; padding: 4px;">82.22</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_5000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9749</td><td style="border: 1px solid #ccc; padding: 4px;">58.77</td><td style="border: 1px solid #ccc; padding: 4px;">270.91</td><td style="border: 1px solid #ccc; padding: 4px;">0.73 %</td><td style="border: 1px solid #ccc; padding: 4px;">4.43</td><td style="border: 1px solid #ccc; padding: 4px;">4.41</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_5000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9678</td><td style="border: 1px solid #ccc; padding: 4px;">260.33</td><td style="border: 1px solid #ccc; padding: 4px;">61.38</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_6000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9781</td><td style="border: 1px solid #ccc; padding: 4px;">66.75</td><td style="border: 1px solid #ccc; padding: 4px;">238.59</td><td style="border: 1px solid #ccc; padding: 4px;">0.52 %</td><td style="border: 1px solid #ccc; padding: 4px;">4.91</td><td style="border: 1px solid #ccc; padding: 4px;">4.89</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_6000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.973</td><td style="border: 1px solid #ccc; padding: 4px;">327.44</td><td style="border: 1px solid #ccc; padding: 4px;">48.81</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_7000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9804</td><td style="border: 1px solid #ccc; padding: 4px;">74.64</td><td style="border: 1px solid #ccc; padding: 4px;">213.49</td><td style="border: 1px solid #ccc; padding: 4px;">0.38 %</td><td style="border: 1px solid #ccc; padding: 4px;">5.28</td><td style="border: 1px solid #ccc; padding: 4px;">5.27</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_7000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9767</td><td style="border: 1px solid #ccc; padding: 4px;">394.24</td><td style="border: 1px solid #ccc; padding: 4px;">40.53</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_8000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9823</td><td style="border: 1px solid #ccc; padding: 4px;">82.28</td><td style="border: 1px solid #ccc; padding: 4px;">193.59</td><td style="border: 1px solid #ccc; padding: 4px;">0.27 %</td><td style="border: 1px solid #ccc; padding: 4px;">6.86</td><td style="border: 1px solid #ccc; padding: 4px;">6.83</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_8000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9797</td><td style="border: 1px solid #ccc; padding: 4px;">564.14</td><td style="border: 1px solid #ccc; padding: 4px;">28.33</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_9000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9837</td><td style="border: 1px solid #ccc; padding: 4px;">90.08</td><td style="border: 1px solid #ccc; padding: 4px;">176.96</td><td style="border: 1px solid #ccc; padding: 4px;">0.16 %</td><td style="border: 1px solid #ccc; padding: 4px;">7.63</td><td style="border: 1px solid #ccc; padding: 4px;">7.61</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_9000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9821</td><td style="border: 1px solid #ccc; padding: 4px;">687.25</td><td style="border: 1px solid #ccc; padding: 4px;">23.25</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_10000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9848</td><td style="border: 1px solid #ccc; padding: 4px;">97.64</td><td style="border: 1px solid #ccc; padding: 4px;">163.31</td><td style="border: 1px solid #ccc; padding: 4px;">0.08 %</td><td style="border: 1px solid #ccc; padding: 4px;">8.38</td><td style="border: 1px solid #ccc; padding: 4px;">8.36</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_10000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.984</td><td style="border: 1px solid #ccc; padding: 4px;">818.64</td><td style="border: 1px solid #ccc; padding: 4px;">19.53</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
              </tbody>
            </table>
          <p>Por ejemplo, en <code>100_9000_1</code>, OpenSearch tiene un promedio de 687 milisegundos por recuperación frente a 90 milisegundos en Elasticsearch, y en un bucle de recuperación de 10 pasos eso equivale a aproximadamente 10 × (687 - 90) = seis segundos de tiempo de espera adicional. </p><p>Consulta los <a href="https://github.com/elastic/competitive-benchmarking-studies/tree/main/es-9.3-vs-os-3.5-vector-search/jingra/results/20260220">resultados completos</a>.</p><h3>Metodología</h3><p>Al usar Python para enviar las consultas y rastrear el tiempo de respuesta y otras estadísticas, enviamos las siguientes consultas a los motores. Ten en cuenta que el rendimiento de cualquier motor de búsqueda vectorial depende de cómo ajustes sus parámetros núcleo: cuántos candidatos considerar, cuán agresivamente volver a puntuar y cuánto contexto devolver. Estos ajustes afectan directamente tanto la exhaustividad (la probabilidad de encontrar la respuesta correcta) como la latencia (la rapidez con la que obtienes los resultados).</p><p>En nuestras pruebas comparativas, empleamos la misma configuración de candidatos, repuntuación y tamaño de resultados que normalmente ajustarías en un bucle de recuperación basado en agentes, y medimos el rendimiento de Elasticsearch bajo esa carga de trabajo. Luego ejecutamos OpenSearch con la misma configuración como referencia.</p><p>OpenSearch</p><pre><code>GET <INDEX_NAME>/_search
{
  "query": {
    "knn": {
      "<DENSE_VECTOR_FIELD_NAME>": {
        "vector": [...],
        "k": <NUMBER_OF_CANDIDATES>,
        "method_parameters": {
          "ef_search": <NUMBER_OF_CANDIDATES>
        },
        "rescore": {
          "oversample_factor": <OVERSAMPLE>
        },
        "filter": {
          <SOME_FILTER>
        }
      }
    }
  },
  "size": <RESULT_SIZE>,
  "_source": {
    "excludes": [
      "<DENSE_VECTOR_FIELD_NAME>"
    ]
  }
}</code></pre><ul><li><code>&quot;size&quot;: &lt;RESULT_SIZE&gt;</code>: Número de resultados devueltos al cliente. En esta prueba de rendimiento, el tamaño del conjunto de datos es 100 para calcular el Recall@100.</li><li><code>&quot;k&quot;: &lt;NUMBER_OF_CANDIDATES&gt;</code>: El número de candidatos a vecinos más cercanos.</li><li><code>&quot;ef_search&quot;: &lt;NUMBER_OF_CANDIDATES&gt;</code>: El número de vectores a examinar.</li><li><code>&quot;oversample_factor&quot;: &lt;OVERSAMPLE&gt;</code>: ¿Cuántos vectores candidatos se recuperan antes de volver a calcular la puntuación?</li></ul><p>Elasticsearch</p><pre><code>GET <INDEX_NAME>/_search
{
  "query": {
    "knn": {
      "field": "<DENSE_VECTOR_FIELD_NAME>",
      "query_vector": [...],
      "k": <NUMBER_OF_CANDIDATES>,
      "num_candidates": <NUMBER_OF_CANDIDATES>,
      "rescore_vector": {
        "oversample": <OVERSAMPLE>
      },
      "filter": {
        <SOME_FILTER>
      }
    }
  },
  "size": <RESULT_SIZE>,
  "_source": {
    "excludes": [
      "<DENSE_VECTOR_FIELD_NAME>"
    ]
  }
}</code></pre><ul><li><code>&quot;size&quot;: &lt;RESULT_SIZE&gt;</code>: Número de resultados devueltos al cliente. En esta prueba de rendimiento, el tamaño del conjunto de datos es 100 para calcular el Recall@100.</li><li><code>&quot;k&quot;: &lt;NUMBER_OF_CANDIDATES&gt;</code>: Número de vecinos más cercanos que se debe devolver desde cada shard.</li><li><code>&quot;num_candidates&quot;: &lt;NUMBER_OF_CANDIDATES&gt;</code>: Número de candidatos de vecinos más cercanos a considerar por shard mientras se realiza la búsqueda de <code>knn</code>.</li><li><code>&quot;oversample&quot;: &lt;OVERSAMPLE&gt;</code>: ¿Cuántos vectores candidatos se recuperan antes de volver a calcular la puntuación?</li></ul><p>Ejemplo</p><p><code>Knn</code> la búsqueda, (<code>100_500_1</code>), sería de la siguiente manera:</p><p>OpenSearch</p><pre><code>GET search_catalog_128/_search
{
  "query": {
    "knn": {
      "search_catalog_embedding": {
        "vector": [...],
        "k": 500,
        "method_parameters": {
          "ef_search": 500
        },
        "rescore": {
          "oversample_factor": 1
        },
        "filter": {
          "term": {
            "valid": true
          }
        }
      }
    }
  },
  "size": 100,
  "_source": {
    "excludes": [
      "search_catalog_embedding"
    ]
  }
}</code></pre><p>Elasticsearch</p><pre><code>GET search_catalog_128/_search
{
  "query": {
    "knn": {
      "field": "search_catalog_embedding",
      "query_vector": [...],
      "k": 500,
      "num_candidates": 500,
      "rescore_vector": {
        "oversample": 1
      },
      "filter": {
        "term": {
          "valid": true
        }
      }
    }
  },
  "size": 100,
  "_source": {
    "excludes": [
      "search_catalog_embedding"
    ]
  }
}</code></pre><p>La configuración completa, junto con scripts de Terraform, manifiestos de Kubernetes y el código de benchmarking, está disponible en este <a href="https://github.com/elastic/competitive-benchmarking-studies">repositorio</a> en la carpeta <a href="https://github.com/elastic/competitive-benchmarking-studies/tree/main/es-9.3-vs-os-3.5-vector-search">es-9.3-vs-os-3.5-vector-search</a>.</p><h3>La configuración del cluster</h3><p>Ejecutamos nuestras pruebas en seis servidores cloud e2-standard-16, cada uno con 16 vCPUs y 64 GB de RAM. En cada servidor, asignamos 15 vCPUs y 56 GB de RAM a cada pod de Kubernetes que ejecutaba el nodo del motor de búsqueda, con 28 GB reservados para el heap de JVM.</p><p>Los clústeres ejecutaban Elasticsearch 9.3.0 y OpenSearch 3.5.0 (Lucene 10.3.2). Dado que ambos sistemas emplean la misma versión de Lucene en esta prueba comparativa, las diferencias de rendimiento y latencia que observamos no pueden atribuirse únicamente a Lucene, sino que reflejan diferencias en la forma en que cada motor integra y ejecuta la recuperación y recalculación filtradas del algoritmo k-vecinos más cercanos (kNN). Usamos un único índice con tres shards primarios y una réplica (es decir, 6 shards en total, 1 por nodo).</p><p>También usamos un servidor independiente en la misma región para ejecutar el cliente de pruebas de rendimiento y recopilar estadísticas de tiempos.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f81002c9186e4c2d3e92f49d72418fee9860fc5e-761x401.png" style="width: 100%;" /><h3>El set de datos</h3><p></p><p>Para este benchmark, empleamos un set de datos de incrustación de catálogos de tipo comercio electrónico a gran escala con 20 millones de documentos, diseñado para reflejar la recuperación vectorial filtrada a escala del mundo real.</p><p></p><p>Cada documento representa un artículo del catálogo e incluye:</p><p></p><ul><li>Un vector denso incrustado de 128 dimensiones utilizado para la recuperación aproximada de kNN.</li><li>Campos estructurados de metadatos usados para filtrar (por ejemplo, validez y disponibilidad de artículos más otras restricciones del catálogo) que permiten el patrón común de producción de recuperar a los vecinos más cercanos, pero solo dentro de un subconjunto elegible.</li></ul><p></p><p>Elegimos este set de datos porque captura el núcleo del desafío principal de rendimiento que vemos en sistemas agentes y de estilo RAG en producción: la similitud vectorial por sí sola no es suficiente, la recuperación está frecuentemente limitada por filtros y el sistema debe mantener una alta recuperación a la vez que mantiene baja la latencia bajo esas restricciones. En comparación con sets de datos más pequeños de estilo QA, un corpus de 20M de documentos también refleja mejor la escala y la presión de los candidatos que enfrentan los sistemas de ANN filtrados en la práctica.</p><h2>Conclusión</h2><p>En las arquitecturas de IA modernas, especialmente aquellas construidas alrededor de la ingeniería de contexto, la velocidad de búsqueda vectorial no es un detalle de implementación menor. Es un multiplicador. Cuando los agentes y los flujos de trabajo iteran a través de recuperar → razonar → recuperar, el rendimiento de la recuperación da forma directamente a la latencia de extremo a extremo, al rendimiento y a la calidad del contexto que se introduce en el modelo.</p><p>En nuestras pruebas de referencia, Elasticsearch ofreció consistentemente una mayor recuperación con menor latencia que OpenSearch en escenarios donde la corrección depende de recuperar el documento correcto, no solo de un vector similar. En un set de datos controlado, la diferencia es clara, y en producción esos avances se acumulan a lo largo de grandes volúmenes de llamadas de recuperación, lo que mejora la capacidad de respuesta, aumenta el margen de capacidad y reduce los costos de infraestructura.</p><h3>Lecturas adicionales</h3><ol><li><a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">¿Qué es la ingeniería de contexto?</a></li><li><a href="https://www.elastic.co/search-labs/blog/series/context-engineering-hybrid-search-evolution">La evolución de la búsqueda híbrida y la ingeniería de contexto</a></li><li><a href="https://www.elastic.co/search-labs/blog/context-engineering-relevance-ai-agents-elasticsearch">El impacto de la relevancia en la ingeniería de contexto para agentes de IA</a></li></ol>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[jina-embeddings-v5-text: incrustaciones de texto compactas y de última generación para aplicaciones de búsqueda e inteligentes]]></title>
            <description><![CDATA[Introducimos los modelos jina-embeddings-v5-text, lo que incluye jina-embeddings-v5-text-small y jina-embeddings-v5-text-nano, y explicamos cómo usar estos modelos de incrustación multilingüe a través del Elastic Inference Service (EIS).]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/jina-embeddings-v5-text</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/jina-embeddings-v5-text</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Scott Martens, Sofia Vasileva]]></dc:creator>
            <pubDate>Mon, 23 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e98dd30ab925b4bb32830228d71a1a51d02a0917-1600x840.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Jina AI y Elastic están lanzando <code>jina-embeddings-v5-text</code>, una familia de nuevos modelos compactos de incrustación de texto de alto rendimiento con un rendimiento de última generación para modelos de tamaño comparable en todos los tipos de tareas principales.</p><p>La familia incluye dos modelos:</p><ul><li><code>jina-embeddings-v5-text-small</code></li><li><code>jina-embeddings-v5-text-nano</code></li></ul><p>Estos modelos son el resultado exitoso de una receta de entrenamiento innovadora para modelos de incrustación. Ambos superan a modelos varias veces más grandes, lo que genera ahorros en memoria y recursos informáticos y responde más rápido a las solicitudes.</p><p>El modelo <code>jina-embeddings-v5-text-small</code> tiene 677 millones de parámetros, admite una ventana de contexto de entrada de 32 768 tokens y produce incrustaciones de 1024 dimensiones por defecto.</p><p><code>jina-embeddings-v5-text-nano</code> Pesa aproximadamente un tercio del tamaño de su hermano, con 239 millones de parámetros y una ventana de contexto de entrada de 8192 tokens, lo que produce incrustaciones compactas de 768 dimensiones.</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Nombre del modelo</td><td style="border: 1px solid #ccc; padding: 4px;">Tamaño total</td><td style="border: 1px solid #ccc; padding: 4px;">Tamaño de la ventana de contexto de entrada</td><td style="border: 1px solid #ccc; padding: 4px;">Tamaño de incrustación</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">jina-v5-text-small</td><td style="border: 1px solid #ccc; padding: 4px;">677M parámetros</td><td style="border: 1px solid #ccc; padding: 4px;">32768 tokens</td><td style="border: 1px solid #ccc; padding: 4px;">1024 dimensiones</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">jina-v5-text-nano</td><td style="border: 1px solid #ccc; padding: 4px;">239M parámetros</td><td style="border: 1px solid #ccc; padding: 4px;">8192 tokens</td><td style="border: 1px solid #ccc; padding: 4px;">768 dimensiones</td>
                    </tr>
              </tbody>
            </table>
          <p>Estos dos modelos son los mejores en su categoría en cuanto al rendimiento general en la evaluación comparativa MMTEB (<a href="https://huggingface.co/spaces/mteb/leaderboard">MTEB multilingüe</a>). Entre los modelos con menos de 500 millones de parámetros, <code>jina-embeddings-v5-text-nano</code> es el de mejor rendimiento, a pesar de tener menos de 250 millones de parámetros, y el modelo <code>jina-embeddings-v5-text-small</code> es el líder entre los modelos de incrustación multilingüe con menos de 750 millones de parámetros.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d97126285fa196c3045b1913b7754e4b186c1e4c-1300x1100.png" style="width: 100%;" /><p>Estos modelos están disponibles a través del Elastic Inference Service (EIS), mediante una API en línea y están disponibles para alojamiento local. Para obtener instrucciones sobre cómo acceder a los modelos <code>jina-embeddings-v5-text</code>, consulta la sección “<strong>Primeros pasos</strong>” a continuación.</p><p>Los modelos de incrustación y la indexación semántica aumentan considerablemente la precisión de los algoritmos de búsqueda, pero también tienen otros usos para tareas relacionadas con la similitud semántica y la extracción de significado, por ejemplo:</p><ul><li>Búsqueda de textos duplicados.</li><li>Reconocimiento de paráfrasis y traducciones.</li><li>Descubrimiento de temas.</li><li>Motores de recomendación.</li><li>Análisis de sentimiento e intención.</li><li>Filtrado de spam.</li><li>Y muchos otros.</li></ul><h2><strong>Características</strong></h2><p>Esta nueva familia de modelos cuenta con varias características diseñadas para mejorar la relevancia y reducir costos.</p><h3>Optimización de tareas</h3><p>Optimizamos los modelos <code>jina-embeddings-v5-text</code> para cuatro tipos generales de tareas:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Tarea</td><td style="border: 1px solid #ccc; padding: 4px;">Ejemplos de casos de uso</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Recuperación</td><td style="border: 1px solid #ccc; padding: 4px;">Buscar con consultas en lenguaje natural y recuperar las coincidencias más relevantes en una colección de documentos.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Coincidencia de texto</td><td style="border: 1px solid #ccc; padding: 4px;">Similitud semántica, deduplicación, paráfrasis y alineación de traducciones, y mucho más.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Agrupación</td><td style="border: 1px solid #ccc; padding: 4px;">Descubrimiento de temas, organización automática de colecciones de documentos.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Clasificación</td><td style="border: 1px solid #ccc; padding: 4px;">Categorización de documentos, detección de sentimientos e intenciones, tareas similares.</td>
                    </tr>
              </tbody>
            </table>
          <p>La optimización para una tarea suele implicar tener que renunciar a otra, por lo que la mayoría de los modelos de incrustación solo tienen un rendimiento competitivo para un tipo de tarea. Pero los modelos <code>jina-embeddings-v5-text</code> pueden especializarse en las cuatro áreas sin comprometer el entrenamiento de <a href="https://arxiv.org/abs/2106.09685">adaptadores de Low-Rank Adaptation (LoRA)</a> específicos para cada tarea.</p><p>Los adaptadores de LoRA son un tipo de plugin para un modelo de AI que cambia drásticamente su comportamiento y solo aumenta ligeramente el tamaño total. En lugar de tener un modelo completo para cada tarea, cada uno con cientos de millones de parámetros, la familia de modelos <code>jina-embeddings-v5-text</code> te permite usar solo un modelo con un adaptador de LoRA compacto para cada tarea. Esto ahorra memoria, espacio de almacenamiento y costos de inferencia.</p><h3>Incrustaciones truncadas</h3><p>Entrenamos los modelos <code>jina-embeddings-v5-text</code> utilizando <a href="https://arxiv.org/abs/2205.13147">Aprendizaje de representación de Matryoshka</a>, que te permite reducir tus incrustaciones a tamaños más pequeños con un costo mínimo para su calidad.</p><p>Por defecto, <code>jina-embeddings-v5-text-small</code> genera vectores de incrustación de 1024 dimensiones, cada uno representado por un número de 16 bits, lo que hace que cada incrustación tenga 2 KB de tamaño. En el caso de una gran colección de documentos, esto puede suponer una gran cantidad de datos que almacenar, y la búsqueda en una base de datos vectorial llena de incrustaciones es proporcional tanto al tamaño de la base de datos como al número de dimensiones que tiene cada vector almacenado.</p><p>Pero puedes simplemente reducir a la mitad el tamaño de las incrustaciones (desechar 512 de las 1024 dimensiones) y ocupar la mitad del espacio mientras duplicas las velocidades de búsqueda. Esto tiene un impacto en el rendimiento. Eliminar parte de la información reduce la precisión. Sin embargo, como muestra el grafo siguiente, incluso al eliminar la mitad de la incrustación, el rendimiento solo se reduce ligeramente:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f11969682c81bcf22b5b29a6204ba6ec40471b3d-900x700.png" style="width: 100%;" /><p>Siempre y cuando tus incrustaciones tengan al menos 256 dimensiones, la pérdida de precisión debería ser bastante pequeña. Sin embargo, por debajo de ese nivel, la relevancia y la precisión se deterioran rápidamente.</p><p>Las incrustaciones truncadas como esta permiten a los usuarios gestionar sus propias compensaciones entre precisión y costos de computación. Te dan las herramientas para obtener grandes ganancias de eficiencia y grandes ahorros de costos de tu AI de búsqueda.</p><h3>Cuantización robusta</h3><p><em>La cuantización </em>es otra forma de reducir el tamaño de las incrustaciones. En lugar de desechar parte de cada incrustación, la cuantización reduce la precisión de los números en la incrustación. Los modelos <code>jina-embeddings-v5-text</code> generan incrustaciones con números de 16 bits, pero podemos redondear esos números, reduciendo su precisión y la cantidad de bits necesarios para almacenarlos. En el caso más extremo, podemos reducir cada número a un bit (0 o 1), comprimiendo las incrustaciones predeterminadas de 1024 dimensiones <code>jina-embeddings-v5-text</code>de 2 kilobytes a 128 bytes, una reducción del 94 % solo por cuantización binaria. Al igual que para el truncamiento, esto produce grandes ahorros en memoria y costos de computación. Sin embargo, al igual que el truncamiento, la cuantización reduce la precisión de las incrustaciones.</p><p>Entrenamos los modelos de <code>jina-embeddings-v5-text</code> para que funcionen con <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/bbq">Better Binary Quantization</a> de <a href="https://www.elastic.co/es/elasticsearch">Elasticsearch</a> al minimizar esa pérdida de precisión, y las pruebas de evaluación comparativa de incrustaciones binarizadas de estos modelos muestran un rendimiento casi igual al de sus equivalentes no binarizados. Consulta <a href="https://arxiv.org/abs/2602.15547">el reporte técnico</a> para obtener estudios detallados sobre el rendimiento de la binarización.</p><h3>Rendimiento multilingüe</h3><p>Muchos modelos de incrustación son multilingües porque se entrenaron con materiales que incluyen un gran número de idiomas. Pero eso no significa que todos funcionen igual de bien en todos los idiomas compatibles.</p><p>Identificamos 211 idiomas en la evaluación comparativa multilingüe MMTEB y los separamos para poder comparar nuestros modelos con modelos similares idioma por idioma. La imagen de abajo resume nuestros resultados como un mapa de calor. Cada parche es un idioma (identificado por su código ISO-639), y cuanto más verde sea, mejor rendimiento tuvo el modelo en comparación con el promedio de modelos similares:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/852ac5d0f1977bb0c1124d87f8863a9bb94eb7da-1600x765.png" style="width: 100%;" /><p>Aunque la precisión varía entre idiomas, los modelos <code>jina-embeddings-v5-text</code> son de última generación o casi lo son en la mayoría de los idiomas del mundo.</p><p>Para obtener detalles sobre el rendimiento multilingüe, consulta el<a href="https://arxiv.org/abs/2602.15547"> reporte técnico</a> <a href="https://arxiv.org/abs/2602.15547"><code>jina-embeddings-v5-text</code></a>.</p><h2><strong>Jina en Elastic: IA nativa de última generación para búsqueda</strong></h2><p>Con los modelos <code>jina-embeddings-v5-text</code> en EIS, puedes ejecutar modelos de incrustación multilingüe de alto rendimiento de forma nativa en <a href="https://www.elastic.co/es/elasticsearch">Elasticsearch</a>, con inferencia totalmente gestionada y acelerada por GPU y sin infraestructura para aprovisionar o escalar. Los modelos <code>jina-embeddings-v5-text</code> amplían el creciente catálogo de modelos EIS con modelos compactos y multilingües impulsados por los últimos desarrollos en AI. Estos modelos tienen un rendimiento de última generación en recuperación de información y análisis de datos estándar, y ofrecen un soporte multilingüe inigualable que abarca todo el globo.</p><p>Con dos modelos de tamaños muy diferentes, los usuarios pueden determinar cuál es el más adecuado para sus aplicaciones y presupuestos. Además, con incrustaciones sólidas que siguen siendo eficientes cuando se truncan a tamaños más pequeños o se cuantifican con menor precisión, los modelos <code>jina-embeddings-v5-text </code>ofrecen oportunidades para ahorros concretos adicionales en costos de almacenamiento y computación, así como en latencia de procesamiento.</p><p>Con la familia <code>jina-embeddings-v5-text</code> , Jina Reranker y la rápida búsqueda vectorial y BM25 de Elastic, los usuarios ahora tienen acceso a <a href="https://www.elastic.co/docs/solutions/search/hybrid-search">la búsqueda híbrida</a> de última generación de extremo a extremo de Elastic. Cuando necesitas los resultados más relevantes, ya sea para pipelines de Retrieval-Augmented Generation (RAG), aplicaciones de búsqueda o análisis de datos, Elastic con modelos de AI de búsqueda Jina ofrece una calidad estable y rentable.</p><h2><strong>Primeros pasos</strong></h2><p>Los modelos <code>jina-embeddings-v5-text</code> están completamente integrados en <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">EIS</a> y puedes usarlos al configurar el campo<strong><code>type</code> </strong>en<strong> <code>semantic_text</code></strong> al crear tu índice y especificar el modelo (<code>jina-embeddings-v5-text-small</code> o <code>jina-embeddings-v5-text-nano</code>) en el campo <code>inference_id</code><strong> </strong>, como en este ejemplo:</p><pre><code>PUT multilingual-semantic-index
{
  "mappings": {
    "properties": {
      "content": {
        "type": "semantic_text",
        "inference_id": ".jina-embeddings-v5-text-small"
      }
    }
  }
}

# Ingest data about France
POST multilingual-semantic-index/_doc
{
  "content": "The capital of France is Paris"}

GET multilingual-semantic-index/_search
{
  "query": {
    "semantic": {
      "field": "content",
      "query": "What is the French capital?"
    }
  }
}</code></pre><p><a href="https://www.elastic.co/es/elasticsearch">Elasticsearch</a> selecciona automáticamente el adaptador de LoRA adecuado durante la indexación y la recuperación. Las dimensiones de incrustación (consulta la sección “<strong>Incrustaciones truncadas</strong>” anterior) se pueden establecer al <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put">crear un endpoint de inferencia personalizado</a>.</p><p>Consulta la <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector">documentación de Elasticsearch</a> para obtener más información sobre el uso de modelos <strong><code>jina-embeddings-v5-text</code> </strong> .</p><h2><strong>Más información</strong></h2><p>Para obtener más información sobre los modelos <code>jina-embeddings-v5-text</code>, lee las <a href="https://jina.ai/news/jina-embeddings-v5-text-distilling-4b-quality-into-sub-1b-multilingual-embeddings/">notas de lanzamiento en el blog de Jina AI</a> y el <a href="https://arxiv.org/abs/2602.15547">reporte técnico</a>, con información técnica más detallada sobre el rendimiento y el nuevo procedimiento de entrenamiento innovador de Jina AI. Para obtener información sobre cómo descargar y ejecutar estos modelos localmente, visita la <a href="https://huggingface.co/collections/jinaai/jina-embeddings-v5-text"><code>jina-embeddings-v5-text</code> página de la colección</a> en Hugging Face.</p><p>Los modelos de Jina AI están disponibles bajo <a href="https://spdx.org/licenses/CC-BY-NC-4.0">una licencia CC-BY-NC-4.0</a>, así que puedes descargarlos y probarlos libremente, pero para uso comercial, ponte en contacto con <a href="https://www.elastic.co/contact?pg=global&amp;plcmt=nav&amp;cta=205352">Ventas de Elastic</a>.</p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/es/blog/semantic-precision-minimum-score</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/semantic-precision-minimum-score</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Mattias Brunnert]]></dc:creator>
            <pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8b3b5910abfe16d48d309341a0027008b16c4340-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <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><strong>Precisión: </strong>La fracción de resultados de búsqueda devueltos que son relevantes para el usuario.</li><li><strong>Recuerda: </strong>La fracción de todos los documentos relevantes del corpus que se incluyen en el conjunto de resultados de búsqueda.</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 &quot;min_score&quot; 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><pre><code>GET search-movies/_search
{
  "retriever": {
    "linear": {
      "min_score": 4,
      "retrievers": [
        ...
      ]
    }
  }
}</code></pre><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 &quot;min_score&quot;. 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>, &quot;l2_norm&quot; y &quot;minmax&quot;. El más comúnmente usado es &quot;minmax&quot;, ya que es fácil de entender y funciona bien en muchos escenarios. Las propiedades clave de &quot;minmax&quot; incluyen:</p><ul><li>Las puntuaciones de los documentos se distribuyen entre 0 y 1.</li><li>El documento con la puntuación más alta siempre tiene una puntuación de 1.</li><li>El documento con la puntuación más baja siempre tiene una puntuación de 0.<ul><li>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.</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><pre><code>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"
                }
              }
            }
          }
        }
      ]
    }
  }
}</code></pre><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><pre><code>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"
                }
              }
            }
          }
        }
      ]
    }
  }
}</code></pre><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>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.</li><li>Al agregar una puntuación BM25 muy baja a la puntuación semántica, el total puede aproximarse a la puntuación semántica.</li><li>La falta de contribución de la puntuación BM25 puede hacer que el documento sea descartado por el <code>min_score threshold</code>.</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><pre><code>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"
              }
            }
          }
        }
      ]
    }
  }
}</code></pre><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>
        </item>
        <item>
            <title><![CDATA[Gestión de dependencias en Kubernetes]]></title>
            <description><![CDATA[Cómo optimizar la gestión de dependencias en Kubernetes mediante Renovate CLI y Argo Workflows.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/dependency-management-kubernetes</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/dependency-management-kubernetes</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Nikos Fotiou]]></dc:creator>
            <pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/00519840e6eec7101c1fb096afcae976ee0c454e-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Así fue como construimos una plataforma de gestión de dependencias autohospedada mediante Kubernetes, Argo Workflows, Argo Events y Renovate CLI para automatizar actualizaciones, abordar rápidamente vulnerabilidades y exposiciones comunes (CVE), y propagar eficientemente nuevas versiones de paquetes en miles de repositorios.</p><h2><strong>Gestión de dependencias en Elastic</strong></h2><p>En Elastic, tenemos que gestionar cientos o incluso miles de repositorios, tanto privados como públicos. Cuando se descubre una CVE crítica, necesitamos respuestas y acciones inmediatas: ¿qué repositorios son vulnerables? ¿Con qué rapidez podemos solucionarlos? Además de la seguridad, surgen cuestiones relacionadas con la productividad: ¿cómo podemos propagar rápidamente el lanzamiento de una nueva versión de un paquete a todos los repositorios que dependen de él, sin dedicar demasiado tiempo a tareas manuales?</p><p>El disparador inicial para buscar formas de hacer la gestión de dependencias fue la necesidad de establecer una base segura con actualizaciones automatizadas para <a href="https://www.elastic.co/blog/reducing-cves-in-elastic-container-images">reducir los CVEs</a>. Después de considerar cuidadosamente las soluciones para la gestión de dependencias, empezamos a trabajar en una infraestructura autohospedada. Usábamos nuestro propio clúster de Kubernetes para ejecutar Mend Renovate Community Self-Hosted. La idea era poder ofrecer una plataforma de gestión de dependencias a la que nuestros usuarios pudieran acceder de forma autónoma.</p><p>El experimento inicial tuvo éxito, así que cada vez más equipos comenzaron a implementar nuestra plataforma y usarla en el ciclo de vida de sus repositorios diarios para actualizaciones y parches CVE. Esto sucedió tan rápido que pronto alcanzamos el límite de nuestra instalación autohospedada.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e14d9f98d4eccaa08a335d5bd23d88e5debbb344-1600x1103.png" style="width: 100%;" /><h3><strong>El reto: ¿Cómo podemos escalar una plataforma de gestión de dependencias en una gran organización con una cantidad significativa de repositorios?</strong></h3><p>Nuestra plataforma de gestión de dependencias procesaba un repositorio a la vez, entonces el modelo de procesamiento secuencial no podía seguir el ritmo debido a la gran cantidad de repositorios que tenemos. Ya habíamos identificado que el problema residía en el concepto de que <strong>una sola instancia</strong> de nuestra herramienta de gestión de dependencias podría procesar nuestra gran y siempre creciente lista de repositorios. Los repositorios esperaban en una cola, a veces durante muchas horas. Más del 50 % de nuestros repositorios ni siquiera se procesaban diariamente. Eso significa que más del 50 % de nuestros repositorios esperaron más de 24 horas entre los escaneos.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/45ade5bda08f82bed0b3d0d3736cbd6f056e7a4e-1312x816.jpg" style="width: 100%;" /><p>Los grandes repositorios creaban cuellos de botella mayores, debido a sus grandes bases de código y a sus múltiples PRs abiertas. Los eventos de webhook de GitHub interrumpieron la secuencia. Automerge se volvió poco confiable debido a que el tiempo de escaneo era impredecible. Habíamos hecho una promesa a nuestros usuarios sobre la frecuencia de los escaneos y no pudimos cumplirla.</p><h3><strong>La decisión de integrarnos internamente: satisfacer las necesidades únicas de escalabilidad y seguridad de Elastic</strong></h3><p>Aunque considerábamos opciones comerciales, como <strong>la edición Renovate autohospedada empresarial de Mend</strong>, internamente en Elastic teníamos algunas iniciativas clave en marcha.</p><p>Nuestra decisión de crear una plataforma interna se basó en el reconocimiento de que solo una solución altamente personalizada podría satisfacer los requisitos específicos e innegociables de Elastic:</p><ol><li><strong>Inversión en nuestra plataforma interna para desarrolladores:</strong> en ese momento, ya habíamos comenzado a invertir considerablemente en nuestra plataforma interna para desarrolladores. Estábamos discutiendo y diseñando formas en las que cada uno de nuestros servicios pudiera encajar. Esto significaba que queríamos probar nuestras propias reglas y prácticas para nuestra plataforma de gestión de dependencias. Además de eso, entraban en juego nuevas pautas y queríamos diseñar la plataforma antes de los eventos.</li><li><strong>Integración nativa y personalización del flujo de trabajo:</strong> requeríamos una integración directa con nuestras herramientas y procesos internos. Por ejemplo, queríamos centralizar la configuración como código con nuestro Catálogo de servicios (Backstage). Tenemos necesidades específicas sobre el uso de Backstage con las que queríamos que nuestra plataforma fuera compatible. Así que, aunque fuera posible usar las API de Renovate autohospedadas junto con nuestra automatización de Backstage, esto no cubriría completamente nuestros procesos internos.</li><li><strong>Seguridad en profundidad específica de Elastic:</strong> nuestro cumplimiento de seguridad estricto requería mecanismos personalizados adaptados a nuestro ecosistema. Estábamos trabajando para <a href="https://entro.security/blog/how-elastic-scaled-secrets-nhi-security-elastics-playbook-from-visibility-to-automation/">fortalecer nuestro uso de “identidades no humanas”.</a> Las medidas de seguridad implementadas implicaban que los métodos no estándar de autenticación en GitHub no funcionarían con una herramienta comercial que no fuera compatible con esta implementación interna. Nuestro flujo de trabajo incluía la implementación de un patrón de cifrado secreto de flujo de trabajo principal-secundario y el uso de tokens de GitHub transitorios y de un solo uso. La creación interna era la única forma práctica de integrar estas capas de seguridad únicas y minimizar la superficie de ataque en nuestro complejo entorno multinube.</li></ol><h2><strong>La solución: Orquestación de flujo de trabajo para la gestión de dependencias</strong></h2><p>Nuestra solución partió del hecho de que queríamos basarnos en la herramienta de gestión de dependencias que ya usábamos, no reemplazarla y buscar otras soluciones. Había mostrado signos de su potencial, y su flexibilidad es importante para las distintas necesidades de toda nuestra organización. Consideramos diferentes soluciones, y lo que nos ayudó a decidir fueron las necesidades grandes y a veces especiales que tenemos que cubrir. Decidimos crear una plataforma de gestión de dependencias fiable y escalable, donde cada repositorio se procesa por sí mismo, lo que eliminaría los cuellos de botella y nos prepararía para crecer.</p><p>Diseñamos la plataforma mediante tres principios fundamentales:</p><h3><strong>1. Procesamiento en paralelo</strong></h3><p>Cada repositorio tiene su propio entorno de procesamiento de gestión de dependencias. No más colas. Nuestra concurrencia solo está limitada por la cantidad de recursos que gastamos. También aplicamos una programación distribuida inteligente para evitar que GitHub limite la velocidad.</p><h3><strong>2. Autoservicio</strong></h3><p>Usamos nuestro Catálogo de servicios (Backstage) para incorporar y gestionar automáticamente cualquier repositorio nuevo. Usamos nuestra propia definición de recursos para darle al usuario final la opción de seleccionar con qué frecuencia se procesará un repositorio, cuántos recursos quiere asignar a sus programaciones y si quiere desactivar o volver a activar el procesamiento por cualquier motivo. Planeamos agregar más opciones así a medida que las necesidades de nuestros usuarios evolucionen y se adapten mejor a la nueva instalación.</p><h3><strong>3. Alcance secreto reducido y aislamiento del espacio de nombres</strong></h3><p>Para aumentar la seguridad, suministramos a nuestros pods de gestión de dependencias tokens efímeros de GitHub que se generan al inicio de cada flujo de trabajo. Además, aislamos nuestras cargas de trabajo en espacios de nombres específicos para que solo se les proporcionen los secretos necesarios. Controlamos a qué secretos puede acceder cada uno de los flujos de trabajo de gestión de dependencias mediante Kubernetes RBAC. También utilizamos el cifrado para propagar el token de GitHub desde los flujos de trabajo principales a los secundarios.</p><p>Reconstruimos nuestra plataforma mediante Kubernetes y, al aprovechar el poder de Kubernetes, Argo Workflows impulsa la lógica de nuestros procesos, y Renovate CLI está configurado para escanear y procesar un repositorio a la vez.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5560ed20e2bd9ecdd574a9c835126d12b24c332f-1600x1157.png" style="width: 100%;" /><p><strong>La belleza:</strong> estamos utilizando proyectos de código abierto probados en batalla de una manera original, lo que ofrece nuevos ejemplos de trabajo para todos esos proyectos y, al mismo tiempo, aumenta la velocidad de desarrollo y consolida la reducción de CVE para nuestros equipos.</p><h2><strong>Arquitectura de gestión de dependencias: Cuatro microservicios</strong></h2><p>La plataforma cuenta con cuatro componentes personalizados:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2b3d4046c05bb261e45d40c59f864eb51fb9eaa9-1217x1600.png" style="width: 100%;" /><h3><strong>Operador de los flujos de trabajo (Go/Kubebuilder)</strong></h3><p>Un operador de Kubernetes que gestiona el ciclo de vida del flujo de trabajo a través de tres definiciones de recursos personalizados (CRD):</p><ul><li><strong>CRD de RepoConfig:</strong> Única fuente de verdad para la configuración de repositorios.</li></ul><p>Así es como se define RepoConfig en el operador:</p><pre><code>// RepoConfig is the Schema for the repoconfigs API
type RepoConfig struct {
	metav1.TypeMeta `json:",inline"`

	// metadata is a standard object metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty,omitzero"`

	// spec defines the desired state of RepoConfig
	// +required
	Spec RepoConfigSpec `json:"spec"`

	// status defines the observed state of RepoConfig
	// +optional
	Status RepoConfigStatus `json:"status,omitempty,omitzero"`
}</code></pre><p>Y así es como se vería una instancia de RepoConfig:</p><pre><code>apiVersion: workflows.elastic.co/v1
kind: RepoConfig
metadata:
  generation: 3
  name: elastic-test-repo
  namespace: dependency-management-operator
spec:
  owner: group:my-team
  renovate:
    config:
      resourceGroup: SMALL
      runFrequency: 4h
    enabled: true
  repository: elastic/test-repo</code></pre><ul><li><strong>CRD principal:</strong> Gestiona CronWorkflows para escaneos programados.</li></ul><p>Dentro del bucle de reconciliación del controlador principal, nos aseguramos de que los flujos de trabajo se creen y se mantengan actualizados o incluso se eliminen si es necesario.</p><p>En primer lugar, obtienes algunos ajustes configurados globalmente para los flujos de trabajo:</p><pre><code>func (r *ParentReconciler) reconcileSubResources(ctx context.Context, req ctrl.Request, parent *workflowsv1.Parent) error {
	logger := logf.FromContext(ctx)
	logger.Info("Reconcile SubResources for Parent", "name", req.NamespacedName)
	wfSet := workflowsettings.WorkflowSettings{
		RunFrequency:   parent.Spec.RunFrequency,
		ResourceGroups: "parent",
	}</code></pre><p>Se asegura de que un configmap de mutex esté actualizado para evitar que flujos de trabajo similares se ejecuten juntos:</p><pre><code>	cfMngr := resources.NewConfigMapManager(r.Client, r.Scheme, r.OperatorConfig.ParentNamespace)
	err := cfMngr.CreateOrUpdateSyncMutexConfigmap(ctx, fmt.Sprintf("%s%s", r.OperatorConfig.ResourcesPrefix, r.OperatorConfig.SyncMutexCfgMapName), strings.TrimPrefix(parent.Spec.Repository, "elastic/"), r.OperatorConfig.SemaphoreConcurrencyLimit)</code></pre><p>Luego crea un gestor de flujo de trabajo que es la estructura que creará o actualizará los CronWorkflows y las plantillas de flujo de trabajo:</p><pre><code>	wfMngr := resources.NewArgoWorkflowManager(r.Client,
		r.Scheme,
		curateResourceName(
			strings.ReplaceAll(parent.Spec.Repository, "/", "-"),
		),
		parent.Namespace,
		"parent-workflow",
		false).
		WithOrganization(r.OperatorConfig.GitHubOrg).
		WithRepoName(parent.Spec.Repository).
		Init(true, true).
		WithPrefix(r.OperatorConfig.ResourcesPrefix).
		WithWfTemplateName(r.OperatorConfig.ParentWorkflowTemplate).
		WithResources(wfSet.GetResourceCategory()).
		WithSchedule(wfSet.GetCronSchedule()).
		WithImagePullSecrets([]corev1.LocalObjectReference{{
			Name: r.OperatorConfig.WorkflowImagePullSecrets,
		}}).
		AddArgument(true, true, "extra_cli_args").
		SetArgument(true, false, "extra_cli_args", "none").
		AddTemplate(resources.NewParentDAGTemplateInstance()).
		AddTemplate(resources.NewWorkflowsTemplateInstance("check-child-workflows", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector)).
		AddTemplate(resources.NewWorkflowsTemplateInstance("security", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector)).
		AddTemplate(resources.NewWorkflowsTemplateInstance("submit-child-workflow", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector))
	wfMngr.OverWriteCommand("submit-child-workflow", r.OperatorConfig.ChildNamespace)
	wfMngr.OverwriteWfTemplateName("parent-wftmpl")
	wfMngr.AddSynchronization(fmt.Sprintf("%s%s", r.OperatorConfig.ResourcesPrefix, r.OperatorConfig.SyncMutexCfgMapName), "{{workflow.parameters.repo_name}}")
	err = wfMngr.CreateOrUpdateCronWorkflow(ctx)
	if err != nil {
		return fmt.Errorf("failed to create or update cron workflow: %w", err)
	}
	err = wfMngr.CreateOrUpdateWorkflowTemplate(ctx)
	if err != nil {
		return fmt.Errorf("failed to create or update workflow template: %w", err)
	}
	return nil</code></pre><ul><li><strong>CRD infantil:</strong> Gestiona las plantillas de flujo de trabajo con recursos por repositorio.</li></ul><p>El controlador secundario tiene un deber de reconciliación similar al del padre, pero esta vez es responsable de las plantillas de flujo de trabajo en el espacio de nombres secundario que se activarán por los flujos de trabajo principales.</p><pre><code>func (r *ChildReconciler) reconcileSubResources(ctx context.Context, req ctrl.Request, child *workflowsv1.Child) error {
	logger := logf.FromContext(ctx)
	logger.Info("Reconcile SubResources for Child", "name", req.NamespacedName)
	wfSet := workflowsettings.WorkflowSettings{
		ResourceGroups: child.Spec.ResourceCategory,
	}
	wfMngr := resources.NewArgoWorkflowManager(r.Client,
		r.Scheme,
		curateResourceName(
			strings.ReplaceAll(child.Spec.Repository, "/", "-"),
		),
		child.Namespace,
		"runner",
		true).
		Init(false, true). // only manage workflow template
		WithPrefix(r.OperatorConfig.ResourcesPrefix).
		WithSuffix("-child-wftmpl").
		WithRepoName(child.Spec.Repository).
		WithOrganization(r.OperatorConfig.GitHubOrg).
		WithResources(wfSet.GetResourceCategory()). // will override resources of presets if set
		WithImagePullSecrets([]corev1.LocalObjectReference{{
			Name: r.OperatorConfig.WorkflowImagePullSecrets,
		}}).
		AddTemplate(resources.NewWorkflowsTemplateInstance("runner", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector)).
		AddArgument(false, true, "repo_full_name").
		AddArgument(false, true, "repo_name").
		AddArgument(false, true, "encrypted_token").
		AddArgument(false, true, "extra_cli_args")
	wfMngr.OverWriteCommand("runner", r.OperatorConfig.ChildNamespace)
	err := wfMngr.CreateOrUpdateWorkflowTemplate(ctx)
	if err != nil {
		return fmt.Errorf("failed to create or update workflow template: %w", err)
	}
	return nil
}</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7ac70492a1266ba02cb8afbafc5a486cb38a0edc-1600x1290.png" style="width: 100%;" /><p>El patrón de controlador múltiple proporciona una clara separación: el controlador RepoConfig maneja la incorporación/eliminación, el controlador principal administra la programación y el controlador secundario maneja las plantillas de ejecución.</p><h3><strong>Puerta de enlace de eventos de GitHub (Go)</strong></h3><p>Un proxy de webhook seguro que recibe webhooks de GitHub, verifica firmas, filtra por organización/repositorio y los redirige a Argo Events. Creamos 10 sensores distintos que respondían a interacciones en paneles de dependencias, eventos de relaciones públicas y actualizaciones de paquetes.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4828625456cbd6efa8020a20f10d23f294f98a02-1306x1600.png" style="width: 100%;" /><p>Este gateway permite la integración con las aplicaciones de GitHub de la siguiente manera:</p><ul><li>Verifica las firmas entrantes de webhook de GitHub para mayor seguridad.</li><li>Reenvía eventos válidos al EventSource de Argo Events con todos los encabezados relevantes y la autenticación.</li><li>También configuramos un AuthSecret en EventSource y lo proporcionamos como un encabezado Bearer en las solicitudes reenviadas.</li><li>Proporcionamos logging, métricas y lógica de reintentos.</li></ul><p>Realiza varias validaciones en cada solicitud de evento de GitHub.</p><p>Se asegura de que algunos atributos HTTP estén presentes:</p><pre><code>// ValidateRequestMethod checks if the request method is POST.
func ValidateRequestMethod(r *http.Request) error {
	if r.Method != http.MethodPost {
		return fmt.Errorf("method not allowed, only POST is accepted")
	}
	return nil
}

// ValidateRequiredHeaders checks for required GitHub headers.
func ValidateRequiredHeaders(r *http.Request) error {
	eventType := r.Header.Get("X-GitHub-Event")
	deliveryID := r.Header.Get("X-GitHub-Delivery")
	signature := r.Header.Get("X-Hub-Signature-256")
	if eventType == "" || deliveryID == "" || signature == "" {
		return fmt.Errorf("missing required GitHub headers")
	}
	return nil
}

// ValidateUserAgent checks that the User-Agent header starts with GitHub-Hookshot/
func ValidateUserAgent(r *http.Request) error {
	userAgent := r.Header.Get("User-Agent")
	if !strings.HasPrefix(userAgent, "GitHub-Hookshot/") {
		return fmt.Errorf("invalid User-Agent")
	}
	return nil
}</code></pre><p>Al mismo tiempo que valida la firma de cada solicitud y su organización:</p><pre><code>// ValidateSignature verifies the GitHub webhook signature.
func ValidateSignature(r *http.Request, secret string) ([]byte, error) {
	payload, err := GitHub.ValidatePayload(r, []byte(secret))
	if err != nil {
		return nil, fmt.Errorf("invalid GitHub signature: %w", err)
	}
	return payload, nil
}

// ValidateAllowedOwner checks if the organization login is in the allowed organizations list.
func ValidateAllowedOwner(payload []byte, allowedGitHubOrganizations []string) (string, error) {
	var orgLogin string
	var payloadMap map[string]any
	if err := json.Unmarshal(payload, &payloadMap); err == nil {
		if orgObj, ok := payloadMap["organization"].(map[string]any); ok {
			if login, ok := orgObj["login"].(string); ok {
				orgLogin = login
			} else if name, ok := orgObj["name"].(string); ok {
				orgLogin = name
			}
		}
	}
	if !slices.Contains(allowedGitHubOrganizations, orgLogin) {
		return orgLogin, fmt.Errorf("organization login not allowed")
	}
	return orgLogin, nil
}</code></pre><p>Por último, se redirige a Argo Events según el tipo de evento:</p><pre><code>	// Map eventType to Argo `EventSource` path
	var endpoint string
	switch eventType {
	case "push":
		endpoint = "/push"
	case "issues":
		endpoint = "/issues"
	case "pull_request":
		endpoint = "/pull-requests"
	default:
		slog.Info("Ignoring unhandled event type", "event_type", eventType, "delivery_id", deliveryID)
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("ok"))
		return
	}
	forwardURL := h.config.ArgoEventSourceForwardURL + endpoint</code></pre><p>En lo que respecta a Argo Events, 10 sensores vigilan el EventBus de Argo Events en busca de nuevos eventos.</p><pre><code>apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
  name: {{ .Values.sensors.packageUpdateOnDefaultBranch.name }}
  namespace: {{ .Release.Namespace }}
spec:
  eventBusName: {{ .Values.eventBus.name }}</code></pre><p>Luego, el script aplica la lógica de cada sensor:</p><pre><code>script: |
          local e = event
          if not e or not e.body or not e.body.repository then
            return false
          end

          -- e.g., "refs/heads/main"
          local ref = e.body.ref
          local default_branch = e.body.repository.default_branch
          if not ref or not default_branch then
            return false
          end

          local expected = "refs/heads/" .. default_branch
          if ref ~= expected then
            return false
          end

        {{- if .Values.sensors.packageUpdateOnDefaultBranch.packageFiles }}
          patterns = { {{- range $i, $f := .Values.sensors.packageUpdateOnDefaultBranch.packageFiles }}{{ if $i }}, {{ end }}"{{ $f }}"{{- end }} }
        {{- end }}

          local function anyMatch(path)
            if type(path) ~= "string" then return false end
            for _, pat in ipairs(patterns) do
              -- match filename at repo root, or anywhere under subdirs
              if path:match(pat) or path:match(".+/" .. pat) then
                return true
              end
            end
            return false
          end

          local function filesContainPackage(paths)
            if type(paths) ~= "table" then return false end
            for _, p in ipairs(paths) do
              if anyMatch(p) then return true end
            end
            return false
          end

          -- Inspect all commits (GitHub includes added/modified/removed lists)
          local commits = e.body.commits
          if type(commits) ~= "table" then
            -- Fallback: some payloads include only head_commit
            commits = {}
            if type(e.body.head_commit) == "table" then
              table.insert(commits, e.body.head_commit)
            end
          end

          for _, c in ipairs(commits) do
            if filesContainPackage(c.added) or filesContainPackage(c.modified) or filesContainPackage(c.removed) then
              return true
            end
          end

          return false</code></pre><h3><strong>Backstage Syncer (Go)</strong></h3><p>Esto sondea nuestro catálogo de servicios (Backstage) para las entidades de recursos reales del repositorio, las transforma en CRD de RepoConfig y mantiene la Platform sincronizada con los cambios de configuración. Los cambios se aplican en tres minutos.</p><pre><code>repoMap := make(map[string]map[string]interface{})
			for i := range entities {
				entity := &entities[i]
				if entity.Spec.Type != "GitHub-repository" {
					continue
				}

				implRaw, err := json.Marshal(entity.Spec.Implementation)
				if err != nil {
					logger.Error("Failed to marshal implementation", "error", err)
					continue
				}

				var implMap map[string]interface{}
				err = json.Unmarshal(implRaw, &implMap)
				if err != nil {
					logger.Error("Failed to unmarshal implementation map", "error", err)
					continue
				}
				var repoName string
				if specMap, ok := implMap["spec"].(map[string]interface{}); ok {
					if repo, ok := specMap["repository"].(string); ok {
						repoName = repo
					}
				}
				if repoName == "" {
					continue
				}

				var workflowsRaw []byte
				if v, ok := implMap["spec"].(map[string]interface{}); ok {
					if r, ok := v["renovate"]; ok {
						workflowsRaw, _ = json.Marshal(r)
					} else {
						workflowsRaw = []byte(`{}`)
					}
				} else {
					workflowsRaw = []byte(`{}`)
				}

				var workflowsWithDefaults schema.WorkflowsMetadata
				err = json.Unmarshal(workflowsRaw, &rworkflowsWithDefaults)
				if err != nil {
					logger.Error("Failed to unmarshal workflows config", "error", err)
					continue
				}

				workflowsMap := map[string]interface{}{
					"enabled":        workflowsWithDefaults.Enabled,
					"require_pr":     workflowsWithDefaults.RequirePr,
					"resource_group": string(workflowsWithDefaults.ResourceGroup),
					"run_frequency":  string(workflowsWithDefaults.RunFrequency),
				}
				repoMap[repoName] = map[string]interface{}{
					"renovate": workflowsMap,
					"owner":    entity.Spec.Owner,
				}
			}
			logger.Info("Fetched GitHub Repository data from Backstage", "repository_count", len(repoMap), "status_code", resp.StatusCode)</code></pre><p>Por último, escribe esos datos en instancias de RepoConfig.</p><h3><strong>Base de flujos de trabajo (mixta: JavaScript, Go, Helm)</strong></h3><p>La capa base contiene gráficos de Helm, configuraciones de JavaScript, un contenedor Go para Renovate CLI con soporte de cifrado y un indexador APK personalizado para paquetes Alpine.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/908d19278face3ce1119dbee9146c1264b6e2f30-1600x873.png" style="width: 100%;" /><h2><strong>Configuración de autoservicio</strong></h2><p>Los equipos configuran sus repositorios de manera declarativa a través de Backstage:</p><pre><code>spec:
  renovate:
    enabled: true
    config:
      resourceGroup: LARGE      # SMALL | MEDIUM | LARGE  
      runFrequency: "0 */4 * * *"  # Every 4 hours</code></pre><p>Los grupos de recursos asignan CPU y memoria en función del tamaño del repositorio:</p><ul><li><strong>PEQUEÑO:</strong> CPU de 500m, memoria 1Gi.</li><li><strong>MEDIO:</strong> CPU 1000m, memoria 2Gi.</li><li><strong>GRANDE:</strong> CPU de 2000 m, memoria de 4 Gi.</li></ul><p>La configuración está bajo control de versiones, es auditable y se aplica automáticamente.</p><h2><strong>El patrón padre-hijo</strong></h2><p>El modelo de ejecución usa un patrón de flujo de trabajo primario y secundario:</p><ul><li><strong>Flujo de trabajo principal:</strong> CronWorkflow ligero que se ejecuta según lo programado. Cifra los secretos, determina si se debe ejecutar un escaneo y pasa la configuración al secundario.</li><li><strong>Flujo de trabajo infantil:</strong> pod efímero donde se ejecuta Renovate CLI. Asigna recursos de forma dinámica, descifra secretos de forma aislada y se cierra al completar la tarea.</li></ul><p>Esta separación proporciona seguridad (los secretos se cifran en el nivel superior), optimización de recursos (los niveles superiores utilizan recursos mínimos) y escalabilidad (los niveles inferiores se ejecutan en paralelo).</p><h2><strong>Los resultados</strong></h2><h3><strong>Transformación del rendimiento</strong></h3><ul><li><strong>Antes:</strong> Un repositorio a la vez, algunos repositorios no se procesaban posiblemente incluso por un día o más, menos de 1000 escaneos por día.</li><li><strong>Después:</strong> más de 100 escaneos simultáneos, normalmente 8000 escaneos y hasta 10 000 escaneos registrados al día, limitados únicamente por la cantidad de recursos que estamos dispuestos a invertir y cómo gestionamos los límites de GitHub.</li></ul><h3><strong>Rentabilidad</strong></h3><p>Sin embargo, por extraño que parezca, ejecutar 8000 pods al día puede darte el mismo resultado de forma mucho más económica que tener un pod de larga duración que intenta lograr los mismos resultados.</p><p>En la configuración anterior, ejecutábamos una sola instancia que, en un buen día, realizaba entre 500 y 600 escaneos. Al mismo tiempo, debido al hecho de que se ejecutarían diferentes tipos de repositorios en el mismo pod, necesitábamos dimensionar el pod para los más grandes. Ese tamaño sería mucho mayor que nuestra oferta extra grande actual, que usa 8 CPU para el pod y 16 GB de memoria.</p><p>Para cumplir con la salida diaria actual, el pod único tendría que ejecutarse durante 12 días. Entonces, al comparar el costo de un solo pod que funciona durante 12 días con 8000 pods de nuestro tamaño “MEDIO” funcionando cada día, nuestro nuevo diseño es mucho más eficiente para la misma salida de escaneos:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Métrica</td><td style="border: 1px solid #ccc; padding: 4px;">Escenario A (Flujos de trabajo)</td><td style="border: 1px solid #ccc; padding: 4px;">Escenario B (El pod único de larga duración)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Configuración</td><td style="border: 1px solid #ccc; padding: 4px;">8000 pods (1 vCPU / 2 GB)</td><td style="border: 1px solid #ccc; padding: 4px;">1 pod (8 vCPU / 16 GB)*</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Duración</td><td style="border: 1px solid #ccc; padding: 4px;">10 minutos cada uno</td><td style="border: 1px solid #ccc; padding: 4px;">12 días continuos</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Tiempo total de trabajo</td><td style="border: 1px solid #ccc; padding: 4px;">1333 horas de procesamiento</td><td style="border: 1px solid #ccc; padding: 4px;">288 horas de computación</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Costo total</td><td style="border: 1px solid #ccc; padding: 4px;">$65,83</td><td style="border: 1px solid #ccc; padding: 4px;">$113,75</td>
                    </tr>
              </tbody>
            </table>
          <p>Sin embargo, tomemos en consideración que nuestro valor predeterminado para nuestras cargas de trabajo está configurado en “PEQUEÑO”, con la gran mayoría ejecutándose con éxito con 0.5 CPU y 1 G de RAM, y solo unos pocos necesitan cambiar a mediano y grande. Veamos qué sucede si el 60 % de nuestras cargas de trabajo se ejecutan en “PEQUEÑO”, el 30 % en “MEDIANO” y el 10 % en “GRANDE”, lo cual está más cerca de la realidad.</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Métrica</td><td style="border: 1px solid #ccc; padding: 4px;">Escenario A (Enjambre mixto)</td><td style="border: 1px solid #ccc; padding: 4px;">Escenario B (El corredor de fondo)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Estrategia</td><td style="border: 1px solid #ccc; padding: 4px;">8000 pods (tamaños mixtos)</td><td style="border: 1px solid #ccc; padding: 4px;">1 pod (8 vCPU / 16 GB)*</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Duración</td><td style="border: 1px solid #ccc; padding: 4px;">10 minutos cada uno</td><td style="border: 1px solid #ccc; padding: 4px;">12 días continuos</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Costo total</td><td style="border: 1px solid #ccc; padding: 4px;">$52,66</td><td style="border: 1px solid #ccc; padding: 4px;">$113,75</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Ahorros</td><td style="border: 1px solid #ccc; padding: 4px;">$61,09 (54 % más barato)</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
              </tbody>
            </table>
          <p>Podemos ver que, con la misma salida, somos mucho más rentables en nuestra configuración actual.</p><h3><strong>Seguridad mejorada</strong></h3><ul><li>Tokens efímeros de GitHub (minutos de exposición versus días).</li><li>Aislamiento del espacio de nombres con límites de control de acceso basado en roles (RBAC).</li><li>Cifrado secreto en reposo en flujos de trabajo principales.</li><li>Acceso directo a Vault eliminado.</li></ul><h3><strong>Rendimiento previsible</strong></h3><p>Con una frecuencia de escaneo garantizada, finalmente podemos establecer Objetivos de nivel de servicio (SLO). La autofusión funciona de forma confiable. Los equipos confían en la plataforma para cumplir lo prometido.</p><h2><strong>Decisiones arquitectónicas clave</strong></h2><p>Aquí tienes algunas de las decisiones clave de diseño que moldearon el aspecto de la plataforma.</p><ul><li><strong>¿Por qué flujos de trabajo padre-hijo?</strong></li></ul><p>Adoptamos este patrón para aplicar una estrategia de <strong>defensa en profundidad</strong>. Al restringir las credenciales de alto valor (como los secretos de GitHub App) a un espacio de nombres dedicado y bloqueado, usamos <strong>RBAC para asegurarnos de que los pods de ejecución efímeros no puedan acceder</strong> arbitrariamente a datos confidenciales. Las vulnerabilidades recientes de la cadena de suministro (por ejemplo, los ataques <strong>&quot;Shai Hulud&quot;</strong> de integración continua/entrega continua [CI/CD]) han demostrado la importancia de aislar entornos de ejecución que ejecutan scripts dinámicos desde el almacén de credenciales.</p><p>Simultáneamente, este desacoplamiento permite una <strong>optimización granular de recursos</strong>. Los flujos de trabajo &quot;primarios&quot; actúan como orquestadores ligeros con una huella mínima, mientras que los flujos de trabajo &quot;secundarios&quot; manejan el escaneo de dependencia con uso intensivo de computación. Esta separación simplifica la <strong>gestión de ciclo de vida</strong> al permitirnos aplicar una lógica de reconciliación distinta a cada capa, lo que brinda a los usuarios control sobre los parámetros de ejecución (hijo) mientras conservamos el control administrativo sobre la programación y la infraestructura de seguridad (principal).</p><ul><li><strong>¿Por qué es de autoservicio?</strong></li></ul><p>Eliminar a nuestro equipo como un cuello de botella para la configuración del repositorio fue un requisito crítico. Nuestra misión era diseñar una <strong>plataforma</strong> escalable y de autoservicio capaz de admitir diversos casos de uso. Nos dimos cuenta de que actuar como <strong>filtros</strong> para cada cambio de configuración era insostenible, dado el gran volumen de repositorios. En su lugar, adoptamos una filosofía de habilitación: proporcionar los “rieles” (infraestructura y <strong>barandillas</strong>) mientras capacitamos a los usuarios para conducir los “trenes” (ejecución y personalización). Creemos que este cambio hacia la <strong>autonomía del equipo</strong> mejora significativamente la productividad al permitir a los usuarios adaptar el sistema a sus necesidades operativas específicas.</p><ul><li><strong>¿Por qué el patrón de Kubernetes Operator?</strong></li></ul><p>Como se mencionó anteriormente, un principio de diseño fundamental era garantizar que la plataforma fuera completamente de <strong>autoservicio</strong>. Necesitábamos un mecanismo automatizado para capturar la intención de los usuarios (como alternar escaneos, ajustar la frecuencia de programación o ajustar los límites de recursos de tiempo de ejecución) y propagar instantáneamente esos cambios a los flujos de trabajo subyacentes. Al anticipar los requisitos futuros, el sistema también necesitaba ser fácilmente <strong>extensible</strong>.</p><p>Para lograr esto, desarrollamos un <strong>Operador de Kubernetes para la gestión de dependencias</strong> personalizado. Al utilizar <strong>CRD</strong> como interfaz para la configuración, establecimos un <strong>bucle de reconciliación nativo de Kubernetes</strong>. Este operador monitoriza continuamente el estado deseado definido por el usuario y orquesta automáticamente las actualizaciones necesarias en la infraestructura del flujo de trabajo. Esto asegura una operación fluida y <strong>basada en eventos</strong>, donde la lógica de la plataforma maneja toda la complejidad detrás de escena.</p><ul><li><strong>¿Para qué sirve diseñar una puerta de enlace de eventos de GitHub?</strong></li></ul><p>Adoptar una <strong>arquitectura impulsada por eventos (EDA)</strong> fue esencial para la capacidad de respuesta de la plataforma. Aunque CronWorkflows proporcionaba un calendario de referencia fiable, requeríamos la agilidad para gestionar <strong>ejecuciones ad hoc, </strong>como que los usuarios activaran escaneos manualmente a través del panel. Para lograr esto, necesitábamos una <strong>puerta de enlace de ingestión</strong> dedicada para validar la integridad de la carga útil y enrutar las solicitudes de manera inteligente.</p><p>Evaluamos las soluciones existentes, incluido el GitHub EventSource nativo para Argo, pero identificamos riesgos significativos en cuanto a <strong>la sobrecarga operativa</strong> y las estrictas <strong>cuotas de la API de GitHub</strong> (por ejemplo, los límites de webhook por repositorio). En consecuencia, creamos una puerta de enlace personalizada para desacoplar nuestra infraestructura de estas limitaciones.</p><p>Crucialmente, esta puerta de enlace sirvió como un <strong>punto de control de tráfico</strong> estratégico durante nuestra migración. Actuó como un interruptor, lo que nos permitió realizar una <strong>implementación gradual y granular</strong> (cambio de tráfico) del sistema heredado a la nueva infraestructura. Esto garantizó que la incorporación de miles de repositorios fuera un proceso controlado y sin riesgos, en lugar de un cambio radical.</p><p></p><h2><strong>Lecciones aprendidas</strong></h2><p>Algunas lecciones que hemos aprendido van de la mano con el <a href="https://www.elastic.co/about/our-source-code">código fuente de Elastic</a>:</p><ol><li><strong>El cliente primero: </strong>las plataformas están diseñadas para los usuarios. Por eso es importante tener las necesidades de los usuarios como prioridad número uno. Esto moldea la plataforma en una infraestructura y aplicaciones diseñadas de manera eficiente que reducen la fricción con los usuarios, simplifican el escalado de la plataforma y facilitan la adopción.</li><li><strong>Espacio-tiempo: </strong>a veces el camino de menor resistencia lleva a <strong>arenas movedizas</strong>. Inicialmente, intentamos optimizar el modelo de procesamiento secuencial existente, pero esto no resolvió nuestros problemas. De hecho, solo introdujo más complejidad y cabos sueltos. La audaz decisión de <strong>rediseñar</strong> la plataforma con procesamiento paralelo requirió un esfuerzo inicial significativo. Sin embargo, finalmente allanó el camino para un crecimiento sostenible de la plataforma y prácticamente eliminó el tedioso trabajo administrativo diario.</li><li><strong>Depende: </strong>una plataforma no puede operar de forma aislada. Su éxito depende de qué tan bien se integre con el ecosistema más amplio. En nuestro caso, la integración con <strong>Backstage</strong> fue crítica, ya que sirve como la única fuente de verdad para la incorporación fluida de servicios. Del mismo modo, conectarnos a <strong>Artifactory</strong> nos permitió gestionar las actualizaciones de paquetes privados de manera eficiente, y la lista de integraciones esenciales continúa.</li><li><strong>Progreso, perfección simple: </strong>a lo largo de la implementación, sometimos constantemente a prueba nuestras hipótesis iniciales y nos adaptamos a los nuevos obstáculos que iban surgiendo. En lugar de quedar paralizados por el perfeccionismo, adoptamos un <strong>enfoque iterativo</strong>, abordamos los desafíos uno por uno y ajustamos nuestra estrategia de migración para cumplir con las condiciones del mundo real.</li></ol><h2><strong>Lo que se viene</strong></h2><p>La entrega de la plataforma nos permite realizar un trabajo más significativo que nos ayudará a mejorar la UX y la eficiencia de nuestra plataforma. Algunos ejemplos son:<br/></p><ul><li><strong>Aumento y protección de la adopción de la fusión automática</strong></li></ul><p>La característica de fusión automática acelera significativamente la velocidad del equipo al eliminar las tareas manuales tediosas. Sin embargo, tenemos que asegurarnos de que haya <strong>barreras</strong> estrictas para garantizar que este aumento de velocidad no se haga a expensas de la seguridad.<br/></p><ul><li><strong>Mejora la observabilidad en torno a la experiencia del usuario final</strong></li></ul><p>Una prioridad fundamental de nuestra hoja de ruta es mejorar la observabilidad, no solo a nivel de plataforma, sino también específicamente desde la <strong>perspectiva del usuario final</strong>. Aunque capturar métricas de infraestructura es sencillo, comprender la experiencia real del usuario requiere conocimientos más profundos. Estamos trabajando para definir los indicadores de rendimiento (KPI) centrados en el usuario de núcleo para que nuestra telemetría pueda detectar los puntos de fricción y los problemas de rendimiento <strong>antes</strong> de que se conviertan en quejas de los usuarios.</p><ul><li><strong>Elimina obstáculos para una mayor adopción</strong></li></ul><p>De cara al futuro, nuestra prioridad es identificar y eliminar cualquier barrera que dificulte la adopción de plataformas. Ya sea que esto requiera desarrollar nuevas integraciones o desplegar conjuntos de características específicas, estamos comprometidos con la planificación basada en datos. Construimos con éxito una plataforma diseñada para escalar; nuestro enfoque ahora cambia a <strong>maximizar su potencial</strong>.<br/></p><h2><strong>El panorama general</strong></h2><p>El proyecto de flujos de trabajo de gestión de dependencias demuestra un principio más amplio: <strong>cuando necesites escalar herramientas de código abierto más allá de su modelo de despliegue predeterminado, los patrones nativos de Kubernetes proporcionan un camino a seguir</strong>.</p><p>Al adoptar:</p><ul><li>CRDs para configuración.</li><li>Operadores para la gestión de ciclo de vida.</li><li>Arquitectura basada en eventos para mayor capacidad de respuesta.</li><li>GitOps para el despliegue.</li></ul><p>Creamos una orquestación que escala independientemente de la cantidad de repositorios que gestiona. El rendimiento de escanear un solo repositorio es el mismo tanto si gestionamos 100 como si gestionamos 1000.</p><p>Cuando se anuncia un CVE crítico, ahora tenemos respuestas en minutos, no en horas. Esa es la diferencia entre un cuello de botella y una ventaja competitiva.</p><h2><strong>Agradecimientos</strong></h2><p>Esta plataforma se basa en excelentes herramientas de código abierto:</p><ul><li><strong>Kubebuilder:</strong> el marco de trabajo de código abierto que usamos para poner en marcha nuestros operadores Kubernetes que inician y orquestan nuestros flujos de trabajo. [<a href="https://github.com/kubernetes-sigs/kubebuilder">1</a>][<a href="https://book.kubebuilder.io/">2</a>]</li><li><strong>Backstage:</strong> el marco de trabajo de código abierto sobre el cual hemos construido nuestro catálogo de servicios y que utilizamos como nuestra fuente de la verdad. [<a href="https://github.com/backstage/backstage">1</a>][<a href="https://backstage.io/">2</a>]</li><li><strong>Argo Workflows y Argo Events:</strong> la suite de código abierto que usábamos para orquestar procesos complejos y agregar procesamiento dinámico basado en eventos. [<span class="unknown__pt__mark__1e5711660421">1</span>][<a href="https://argo-workflows.readthedocs.io/en/stable/">2</a>][<a href="https://argoproj.github.io/argo-events/">3</a>][<a href="https://github.com/argoproj/argo-events">4</a>]</li><li><strong>Renovate CLI:</strong> la herramienta de gestión de dependencias de código abierto que procesa nuestros repositorios. [<a href="https://github.com/renovatebot/renovate">1</a>][<a href="https://docs.renovatebot.com/getting-started/running/">2</a>]</li></ul><p>* El modelo de precios de AWS Fargate se usó como referencia para el costo de un solo pod, aunque nuestras cargas de trabajo no se ejecutan necesariamente en AWS y se ejecutan en clústeres de Kubernetes completos.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mejora el rendimiento de búsqueda con `best_compression`]]></title>
            <description><![CDATA[Aunque best_compression suele considerarse una característica de ahorro de almacenamiento para los casos de uso de Elastic Observability y Elastic Security, este blog demuestra su eficacia como herramienta de optimización del rendimiento para las búsquedas.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/improve-elasticsearch-performance-best-compression</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/improve-elasticsearch-performance-best-compression</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Sherry Ger, Ryan Eno]]></dc:creator>
            <pubDate>Fri, 23 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5141a8c2618337207d848ce16b258a86885955b2-1600x1034.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p></p><p>Al ajustar Elasticsearch para cargas de trabajo de alta concurrencia, el enfoque estándar es maximizar la RAM para mantener el conjunto de documentos en la memoria y lograr una baja latencia de búsqueda. En consecuencia, <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules"><code>best_compression</code></a> rara vez se considera para cargas de trabajo de búsqueda, ya que se ve principalmente como una medida de ahorro de almacenamiento para casos de uso de Elastic Observability y Elastic Security donde la eficiencia del almacenamiento tiene prioridad.</p><p>En este blog, demostramos que cuando el tamaño de los sets de datos supera significativamente la caché de la página del sistema operativo, <code>best_compression</code> mejora el rendimiento de búsqueda y la eficiencia de los recursos al reducir el cuello de botella de las E/S.</p><h2><strong>La configuración</strong></h2><p>Nuestro caso de uso es una aplicación de búsqueda de alta concurrencia que se ejecuta en <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/ec-change-hardware-profile#ec-profiles-compute-optimized-arm">instancias optimizadas para CPU de Elastic Cloud</a>.</p><ul><li>Volumen de datos: ~500 millones de documentos</li><li>Infraestructura: 6 instancias de Elastic Cloud (Elasticsearch Service) (cada instancia: 1,76 TB de almacenamiento | 60 GB de RAM | 31,9 vCPU).</li><li>Proporción de memoria a almacenamiento: ~5 % del total de sets de datos que cabe en la RAM</li></ul><h2><strong>Los síntomas: alta latencia</strong></h2><p>Observamos que cuando el número de solicitudes actuales aumenta alrededor de las 19:00, la latencia de búsqueda se deteriora significativamente. Como se muestra en la figura 1 y la figura 2, mientras que el tráfico alcanza un máximo de 400 solicitudes por minuto por instancia de Elasticsearch, el tiempo promedio del servicio de consulta se degrada a más de 60 ms.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f9c6cc1882e7db24336c65c54bbc1d38dcdb7fa3-697x311.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bbb705ae2fcd14c81d335bf322346caf3bf33765-996x618.png" style="width: 100%;" /><p>El uso de la CPU seguía siendo relativamente bajo tras el manejo inicial de las conexiones, lo que indica que el cálculo no era el cuello de botella.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cec15a28d2d22e9adedd2951bb2334b3717890a1-1494x730.png" style="width: 100%;" /><p>Surgió una fuerte correlación entre el volumen de búsquedas y los errores de página. A medida que aumentaban las solicitudes, observamos un incremento proporcional en los errores de página, que alcanzaron un pico de alrededor de 400k/minuto. Esto indicaba que el set de datos activo no podía caber en la caché de la página.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/511f2f10300a9d10ba3d7a82b9a8c8d567ac5636-1492x678.png" style="width: 100%;" /><p>Al mismo tiempo, el uso del heap de JVM parecía ser normal y saludable. Esto descartó problemas de recolección de basura y confirmó que el cuello de botella era de E/S.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/336bbad638f866304358dba1d06ee987de0f23cf-1490x568.png" style="width: 100%;" /><h2><strong>El diagnóstico: limitado por E/S</strong></h2><p>El sistema estaba vinculado a E/S. <a href="https://www.elastic.co/blog/elasticsearch-caching-deep-dive-boosting-query-speed-one-cache-at-a-time">Elasticsearch se basa en la caché de páginas del sistema operativo para servir datos de índice desde la memoria</a>. Cuando el índice es demasiado grande para la caché, las consultas desencadenan lecturas de disco costosas. Aunque la solución habitual es escalar horizontalmente (agregar nodos/RAM), quisimos agotar primero las mejoras de eficiencia de nuestros recursos existentes.</p><h2><strong>La solución</strong></h2><p>De forma predeterminada, Elasticsearch utiliza la compresión <a href="https://en.wikipedia.org/wiki/LZ4_(compression_algorithm)">LZ4</a> para sus segmentos de índice, que logra un equilibrio entre la velocidad y el tamaño. Planteamos la hipótesis de que cambiarse a <code>best_compression</code> (que usa <a href="https://en.wikipedia.org/wiki/Zstd">zstd</a>) reduciría el tamaño de los índices. Un espacio más pequeño permite que un mayor porcentaje del índice encaje en la caché de páginas, lo que cambia un aumento insignificante en la CPU (para la descompresión) por una reducción en la E/S del disco.</p><p>Para habilitar <code>best_compression</code>, reindexamos los datos con la configuración de índice <code>index.codec: best_compression</code>. Alternativamente, el mismo resultado podría lograrse al cerrar el índice, que restablece el códec de índice a <code>best_compression</code>, y luego realiza una fusión de segmentos.</p><pre><code>POST my-index/_close
PUT my-index/_settings
{
    "codec": "best_compression"
}
  
POST my-index/_open  
POST my-index/_forcemerge?max_num_segments=1</code></pre><h2><strong>Los resultados</strong></h2><p>Los resultados confirmaron nuestra hipótesis: la eficiencia mejorada del almacenamiento se tradujo directamente en un aumento sustancial en el rendimiento de búsqueda sin un aumento asociado en la utilización de la CPU.</p><p>La aplicación de <code>best_compression</code> redujo el tamaño del índice en aproximadamente un 25 %. Aunque es menor que la reducción observada en los datos de log repetitivos, esta reducción del 25 % aumentó efectivamente nuestra capacidad de caché de página en el mismo margen.</p><p>Durante la siguiente prueba de carga (que comenzó a las 17:00), el tráfico fue aún mayor, y alcanzó un pico de 500 solicitudes por minuto por nodo de Elasticsearch.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fc1902f396cb2115c0013155ad07f6eb87389c60-660x309.png" style="width: 100%;" /><p>A pesar de la mayor carga, la utilización de la CPU fue menor que en la ejecución anterior. El uso elevado en la prueba anterior probablemente se debió a la sobrecarga del manejo excesivo de errores de página y la gestión de E/S del disco.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/15699ef4c65b3f0a9f8a3e1bae8bb18f7b647025-819x352.png" style="width: 100%;" /><p>Lo más importante es que los errores de página disminuyeron significativamente. Incluso a un rendimiento más alto, los errores rondaron los &lt;200k por minuto, en comparación con &gt;300k en la prueba de referencia.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f76ca967976d740af88a9359b66041701abb46fc-764x340.png" style="width: 100%;" /><p>Aunque los resultados del error de página aún fueron menos que óptimos, el tiempo de servicio de consulta se redujo en aproximadamente un 50 %, y se mantuvo por debajo de los 30 ms incluso bajo una carga más pesada.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/750ec1c59b8eb5069aed4c066d856ecea82d5bca-620x311.png" style="width: 100%;" /><p></p><h2><strong>La conclusión: best_compression para la búsqueda</strong></h2><p>Para casos de uso donde el volumen de datos supera la memoria física disponible, <code>best_compression</code> es una palanca poderosa para ajustar el rendimiento.</p><p>La solución convencional a los errores de caché es escalar para aumentar la RAM. Sin embargo, al reducir la huella del índice, logramos el mismo objetivo: maximizar el recuento de documentos en la memoria caché de páginas. Nuestro siguiente paso es explorar la <a href="https://www.elastic.co/blog/space-savings-a-lesser-known-benefit-of-index-sorting-in-elasticsearch"><strong>clasificación de índices</strong></a> para optimizar aún más el almacenamiento y obtener más rendimiento de los recursos existentes.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Agent Builder ya está disponible para el público en general: envía agentes según el contexto en cuestión de minutos]]></title>
            <description><![CDATA[Agent Builder ahora está disponible para el público en general. Aprende cómo te permite desarrollar rápidamente agentes de IA basados en el contexto.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/agent-builder-elastic-ga</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/agent-builder-elastic-ga</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Anish Mathur, Evan Castle]]></dc:creator>
            <pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6840eb7dbb884055ab0e965dcfd614fec54936af-2210x1440.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Nos complace anunciar la disponibilidad general de Agent Builder en Elastic Cloud Serverless y en la próxima versión 9.3. Agent Builder aprovecha el poder de Elasticsearch como plataforma de ingeniería del contexto para desarrollar rápidamente agentes de IA contextuales y centrados en los datos.</p><p>Los agentes están ganando terreno impulsados por su potencial para ofrecer aumentos en la eficiencia y mejores experiencias del cliente. Pero en la práctica, ofrecer a los agentes el contexto adecuado es difícil, especialmente cuando se opera sobre datos empresariales desordenados y no estructurados. Los desarrolladores deben gestionar herramientas, indicaciones, estados, lógica de razonamiento, modelos y, lo que es más importante, recuperar el contexto relevante de las fuentes empresariales para ofrecer resultados y acciones precisos. Elastic Agent Builder ofrece estos componentes centrales para desarrollar agentes seguros, confiables y basados en el contexto.</p><h2>Capacidades centrales de Agent Builder</h2><p>Agent Builder aprovecha las inversiones a largo plazo de Elastic en relevancia de búsqueda y retrieval-augmented generation, y trabaja para convertir a Elasticsearch en la mejor base de datos vectorial que simplifique el desarrollo de agentes de IA contextuales y centrados en datos.</p><p>Agent Builder te permite:</p><ul><li>Comienza inmediatamente con un agente conversacional integrado que pueda responder preguntas, realizar análisis e impulsar investigaciones sobre cualquier dato en Elasticsearch.</li><li>Pasa rápidamente de datos complejos y datos no estructurados a un agente personalizado con una experiencia de desarrollo basada en la configuración.</li><li>Aprovecha la mejor relevancia de búsqueda híbrida de su clase mediante ES|QL integrado o herramientas personalizadas para mejorar la calidad del contexto y la confiabilidad del agente.</li><li>Ejecuta flujos de trabajo complejos (vista previa) como herramientas reutilizables para enriquecer datos, actualizar registros, enviar mensajes y mucho más para la automatización basada en reglas.</li><li>Conéctate a fuentes de datos fuera de Elasticsearch usando flujos de trabajo y MCP para correlacionar y combinar el contexto para los agentes.</li><li>Integra con cualquier marco de trabajo de aplicación o agente utilizando herramientas integradas y personalizadas expuestas a través de MCP, y la capacidad de conectarte a MCP externo (vista previa), soporte para A2A y soporte completo de API.</li><li>Amplía las capacidades de Agent Builder con la integración de soluciones de terceros, como LlamaIndex para el procesamiento de documentos complejos, o Arcade.dev para un acceso seguro y estructurado a las herramientas.</li></ul><p>Para ampliar aún más la funcionalidad de Agent Builder, presentamos Elastic Workflows, nuestras nuevas capacidades de automatización basadas en reglas, ahora en versión preliminar técnica. Para las tareas organizacionales, los agentes a veces necesitan certeza y confiabilidad en las acciones basadas en reglas, que a menudo son necesarias para implementar una lógica de negocio específica. Elastic Workflows ofrece a los agentes una forma sencilla y declarativa de orquestar sistemas internos y externos para realizar acciones, recopilar y transformar datos y contexto. Los flujos de trabajo son totalmente componibles, orientados a eventos y flexibles, y pueden exponerse como herramientas a un agente mediante MCP.</p><h2>De los datos al agente en minutos</h2><p>El desarrollo de agentes puede llevar semanas de trabajo previo para consolidar almacenes de datos separados, construir pipelines manuales, ajustar búsquedas y gestionar una orquestación compleja. Agent Builder reduce el tiempo de desarrollo de agentes al eliminar la necesidad de almacenes de datos separados, bases de datos vectoriales, pipelines RAG, capas de búsqueda, traductores de búsquedas y orquestadores de herramientas, lo que te permite centrarte en la lógica de agentes y la entrega de aplicaciones.</p><p>Agent Builder integra de forma nativa las primitivas de la plataforma Elasticsearch para que el desarrollo de agentes sea rápido.</p><ul><li>Comienza con un agente conversacional integrado que pueda chatear inmediatamente y razonar con tus datos indexados.</li><li>Integra agentes en aplicaciones, dashboards o sistemas CI/CD con acceso interactivo a través de Kibana, API o MCP y A2A.</li><li>Construye con herramientas predeterminadas para entender tu estructura de datos, selecciona el índice adecuado, genera búsquedas híbridas, semánticas y estructuradas optimizadas, y crea visualizaciones configurables usando ES|QL basado en prompts de lenguaje natural.</li></ul><p>Para profundizar más, prueba un <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">recorrido práctico</a> completo.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b55b63eae40f72952967cc8f3ea4df4cd62d7d70-1080x608.gif" style="width: 100%;" /><h2>Desarrolla sobre Elasticsearch, una plataforma de datos completa para la ingeniería de contexto</h2><p>Para los agentes de IA, la calidad del contexto es esencial para brindar un razonamiento eficaz y reducir los riesgos de alucinación. Para muchos agentes de IA empresarial, los datos comerciales necesarios para realizar una tarea son el contexto más decisivo. Como almacén de datos masivamente escalable, base de datos vectorial y líder en relevancia, Elasticsearch ya ofrece muchas primitivas sólidas de ingeniería de contexto. La ingeniería de contexto va más allá de la simple retrieval-augmented generation, ya que te permite adaptar y escalar la forma en que los datos se recuperan, se clasifican, se filtran y se presentan a los agentes, lo que ayuda a reducir el ruido y la ambigüedad.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/419b9b6f13739e0a8983249d8ac31478e73dac89-1600x901.png" style="width: 100%;" /><p>Elasticsearch ofrece un motor de contexto que combina búsqueda léxica, búsqueda vectorial y filtrado estructurado para la recuperación, <a href="https://www.elastic.co/search-labs/blog/context-engineering-relevance-ai-agents-elasticsearch">lo cual mejora sustancialmente el rendimiento de los LLM</a> al garantizar que el modelo opere en un contexto relevante y preciso. Esta capacidad está respaldada por recuperación agente, junto con herramientas integradas y lógica de búsqueda que seleccionan automáticamente los índices correctos y transforman el lenguaje natural en búsquedas optimizadas para el contexto.</p><p>Con Agent Builder, puedes asegurarte de que los agentes reciban primero el contexto más útil con controles de relevancia y clasificación, lo que te permite ajustar la lógica de calificación, clasificación y filtrado. Elasticsearch te permite controlar qué importa, por qué importa y cómo se prioriza, en lugar de depender de un comportamiento de recuperación opaco. Todo esto está respaldado por Elasticsearch como una plataforma de datos escalable para almacenar y escalar todos tus datos, desde texto, vectores, metadatos, logs y más en una plataforma, lo que facilita la gestión del contexto para los agentes.</p><h2>Ejecutar flujos de trabajo complejos como herramientas reutilizables</h2><p>Aunque los agentes de IA permiten razonar tareas complejas, gran parte de la automatización depende de ejecutar de forma confiable acciones basadas en reglas que impongan una lógica de negocio específica. Elastic Workflows ofrece una forma sencilla y declarativa de orquestar sistemas internos y externos para realizar acciones, recopilar contexto o datos e integrarlos como parte de los agentes. Definidos en YAML, los flujos de trabajo son totalmente componibles, lo que permite que sean tan simples o complejos como requiera el trabajo. Esto ofrece a los agentes una forma eficiente de actuar en toda la plataforma y soluciones de Elasticsearch, así como con aplicaciones de terceros.</p><p>La integración de un flujo de trabajo con Agent Builder se puede realizar en tres pasos (requisito previo: habilitar los flujos de trabajo con los detalles que aparecen <a href="https://github.com/elastic/workflows">aquí</a>).</p><p>1. Crea y guarda un nuevo flujo de trabajo usando el sencillo editor basado en YAML con función de autocompletado y pruebas integradas.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/308888bf3d2fa013f9391a55be6a6fbd458b6dac-1600x998.png" style="width: 100%;" /><p>2. Crea una nueva herramienta en Agent Builder con el tipo “flujo de trabajo” e ingresa una descripción para ayudar al agente a determinar cuándo usar la herramienta de flujo de trabajo.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c04810d30d226112c3610bd58e208607b213fc3d-1600x945.png" style="width: 100%;" /><p>3. Agrega la herramienta de flujo de trabajo a tu agente personalizado.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/724cd4ac93c46efb0d339fd140e5caf138f8150f-1600x948.png" style="width: 100%;" /><p>4. ¡Eso es todo! Ahora el agente puede llamar al flujo de trabajo desde una conversación.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8dfdd726ab89e31c48b79372650ce33946713dca-1600x929.png" style="width: 100%;" /><h2>Tu agente, tus reglas</h2><p>Agent Builder no te limita a un solo paradigma de desarrollo. En cambio, está diseñado para permitir enfoques de desarrollo abiertos y flexibles para los agentes, con control total sobre los datos, la relevancia, los modelos, la interoperabilidad, la seguridad y el diseño de los agentes.</p><p>Las definiciones personalizadas de agentes te permiten elegir exactamente a qué herramientas puede acceder un agente, incorporar avisos personalizados del sistema, adaptar las instrucciones del agente y definir los límites de seguridad. Los agentes siguen siendo independientes del modelo, lo que te permite configurar de manera flexible un LLM de preferencia, tanto nativo como en todo el ecosistema más amplio, sin estar limitado a un solo proveedor.</p><p>Desarrolla herramientas extensibles que encapsulen lógica específica de dominio (por ejemplo, filtros de índice específicos, ES|QL joins, pipelines analíticas), y aplica limitaciones para un uso seguro en producción. El soporte completo de API permite la interoperabilidad con otros marcos de trabajo agentes, con soporte nativo para Model Context Protocol (MCP). La integración A2A significa que puedes exponer tus Elastic Agent a otros marcos de trabajo, servicios y apps de clientes, reutilizando los mismos datos y la lógica de ingeniería de contexto a través de las integraciones.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5e903ba24ffb3f40231e901f63bd494c89cb7757-1600x1004.png" style="width: 100%;" /><p>Agent Builder permite un desarrollo flexible y abierto y está diseñado para integrarse fácilmente con marcos de trabajo y plataformas populares de agentes. Estas integraciones pueden ser esenciales para ofrecer agentes efectivos. Como describe <strong>Sam Partee, cofundador de Arcade.dev</strong>,</p><p><em>“Los sistemas agénticos fallan hoy porque conectar la IA a herramientas y datos es complejo. Elastic Agent Builder con Arcade.dev ofrece a los desarrolladores una forma estructurada y segura de manejar cómo los agentes recuperan el contexto, la razón y la acción, lo que lleva a los agentes de la demostración al grado de producción&quot;.</em></p><p>Agent Builder también aprovecha la extensibilidad de Elasticsearch para manejar datos complejos. Como describe <strong>Jerry Liu, CEO de LlamaIndex</strong>,</p><p><em>“Desbloquear el contexto empresarial de las fuentes de datos no estructurados es clave para construir agentes efectivos. El procesamiento de documentos complejos de Elastic Agent Builder combinado de LlamaIndex fortalece la capa de contexto crítico, lo que ayuda a los equipos a recuperar, procesar y preparar datos para que los agentes puedan razonar con mayor precisión y ofrecer mejores resultados”.</em></p><h2>¿Qué puedes construir?</h2><p>Agent Builder ya se está utilizando para una variedad de casos de uso. A continuación, se muestran algunos ejemplos y arquitecturas de referencia para empezar a utilizar los agentes:</p><ul><li><strong>Automatizar la infraestructura: </strong>En escenarios de soporte, los agentes se han utilizado para leer, pensar y chatear, pero hasta la fecha, no pueden comunicarse y contactarse con la infraestructura que puede que sea necesario administrar. El equipo de ingeniería de Elastic creó un agente para la <a href="https://www.elastic.co/search-labs/blog/agent-builder-augmented-infrastructure">gestión automatizada de la infraestructura</a> como parte de un hackatón. El agente investiga activamente los problemas relacionados con la infraestructura de la aplicación y toma medidas automatizadas. Utiliza flujos de trabajo para optimizar configuraciones, responder a problemas y escalar recursos, todo ello basado en una comprensión inteligente de los logs de infraestructura.</li><li><strong>Análisis de amenazas a la seguridad: </strong>Se desarrolló un agente de vulnerabilidad de seguridad con Elastic Agent Builder, MCP y Elasticsearch. Automatiza el análisis de amenazas correlacionando datos de seguridad interna con inteligencia de amenazas externas. El agente realiza búsquedas semánticas sobre incidentes y configuraciones históricas, incrementa los resultados con datos en tiempo real de Internet y aplica razonamientos LLM para evaluar la relevancia ambiental, priorizar riesgos y producir remediaciones accionables. Consulta la <a href="https://www.elastic.co/search-labs/blog/agent-builder-mcp-reference-architecture-elasticsearch">arquitectura de referencia</a><strong>.</strong></li><li><strong>Soporte técnico al cliente: </strong>Los agentes pueden realizar múltiples tareas de soporte, incluyendo resumen de casos, desduplicación y creación de problemas, e investigación técnica profunda. Agent Builder permite esto mediante una búsqueda híbrida de varios pasos para encontrar solo los problemas, soluciones y procedimientos relacionados más relevantes, y formular hipótesis de causa raíz y planes de remediación. Agent Builder puede simplificar la arquitectura de <a href="https://www.elastic.co/blog/generative-ai-customer-support-elastic-support-assistant">sistemas de soporte</a> complejos y acelerar el tiempo de entrega.</li><li><strong>Descubrimiento de productos y contenido:</strong> Agent Builder simplifica el proceso de <a href="https://www.elastic.co/search-labs/blog/build-voice-agents-elastic-agent-builder">exponer catálogos de productos complejos para experiencias conversacionales</a>, al tiempo que permite a las organizaciones mantener la flexibilidad para incluir sus propios requisitos y lógica de negocio.</li><li><strong>Haz tu propio desarrollo:</strong> Únete al <a href="https://elasticsearch.devpost.com/">Agent Builder Hackathon, que</a> se celebrará del 22 de enero al 27 de febrero de 2026. Trabaja con la comunidad para crear agentes de IA basados en el contexto y en varios pasos que combinen búsqueda, flujos de trabajo, herramientas y razonamiento para automatizar tareas del mundo real*</li></ul><h2>Comienza a construir agentes personalizados ahora</h2><p>Comienza con una <a href="https://cloud.elastic.co/registration?onboarding_token=search&amp;pg=en-enterprise-search-page">prueba de Elastic Cloud</a> y revisa la documentación <a href="https://www.elastic.co/docs/solutions/search/elastic-agent-builder">aquí</a>. Para los clientes existentes, Agent Builder está disponible en Cloud Serverless y en el nivel Empresarial en Elastic Cloud Hosted y es autoadministrado.</p><p>* <a href="https://elasticsearch.devpost.com/rules">Haz clic aquí</a> para conocer los términos, condiciones y requisitos de elegibilidad completos para la hackatón</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Crear agentes de voz con Elastic Agent Builder]]></title>
            <description><![CDATA[Explorar cómo funcionan los agentes de voz y cómo crear uno con Elastic Agent Builder y LiveKit.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/build-voice-agents-elastic-agent-builder</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/build-voice-agents-elastic-agent-builder</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Kenneth Kreindler]]></dc:creator>
            <pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/43ceabb9e2c0966261c188bd40e03178d5a91e5c-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La AI ha estado atrapada en una caja de cristal. Escribes comandos, te responde con texto y eso es todo. Es útil, pero distante, como ver a alguien moverse detrás de una pantalla. Este año, 2026, será el año en que las empresas romperán ese cristal y llevarán a los agentes de AI a los productos, donde realmente aportan valor.</p><p>Una de las formas en que se romperá el “cristal” es mediante la adopción de <em>agentes de voz</em>, que son agentes de AI que reconocen el habla humana y sintetizan audio generado por computadora. Esto se ha vuelto posible gracias al auge de las transcripciones de baja latencia, los modelos de lenguaje grandes (LLM) rápidos y los modelos de texto a voz que suenan humanos.</p><p>Los agentes de voz también necesitan acceso a los datos empresariales para ser realmente valiosos. En este blog, aprenderemos cómo funcionan los agentes de voz y diseñaremos uno para ElasticSport, una tienda ficticia de equipamiento deportivo al aire libre, con <a href="https://livekit.io/">LiveKit</a> y <a href="https://www.elastic.co/elasticsearch/agent-builder">Elastic Agent Builder</a>. Nuestro agente de voz será consciente del contexto y trabajará con nuestros datos.</p><h2>Cómo funciona</h2><p>Existen dos paradigmas en el mundo de los agentes de voz: el primero usa modelos de voz a voz, y el segundo usa un pipeline de voz compuesto por voz a texto, LLM y texto a voz. Los modelos de voz a voz tienen sus propios beneficios, pero los pipelines de voz ofrecen mucha más personalización sobre las tecnologías utilizadas y cómo se gestiona el contexto, además de un mayor control sobre el comportamiento del agente. Nos enfocaremos en el modelo de pipeline de voz.</p><h3>Componentes clave</h3><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b237501903f9c3a71fe1b7755c3990e40c5495c8-1600x653.png" style="width: 100%;" /><h4>Transcripción (voz a texto)</h4><p>La transcripción es el punto de entrada del pipeline de voz. El componente de transcripción toma como entrada fragmentos de audio sin procesar, transcribe el habla en texto y entrega ese texto como salida. El texto transcrito se almacena en un búfer hasta que el sistema detecta que el habla del usuario ha terminado; en ese momento, se inicia la generación del LLM. Varios proveedores externos ofrecen transcripciones de baja latencia. Al seleccionar uno, considera la latencia y la precisión de la transcripción, y asegúrate de que soporten transcripciones en streaming.</p><p></p><p>Ejemplos de API de terceros: <a href="https://www.assemblyai.com/">AssemblyAI</a>, <a href="https://deepgram.com/product/speech-to-text">Deepgram</a>, <a href="https://platform.openai.com/docs/guides/realtime-transcription">OpenAI</a>, <a href="https://elevenlabs.io/speech-to-text">ElevenLabs</a></p><h4>Detección de turnos</h4><p>La detección de turnos es el componente del pipeline que detecta cuándo el hablante ha terminado de hablar y la generación debería comenzar. Una forma común de hacer esto es mediante un modelo de detección de actividad vocal (VAD), como <a href="https://github.com/snakers4/silero-vad">Silero VAD</a>. El VAD utiliza los niveles de energía del audio para detectar cuándo contiene habla y cuándo ha terminado. Sin embargo, el VAD por sí solo no puede identificar la diferencia entre una pausa y el final del discurso. Por eso, a menudo se combina con un modelo de fin de enunciado que predice si el hablante ha terminado de hablar, basándose en la transcripción provisional o el audio sin procesar.</p><p>Ejemplos (Hugging Face): <a href="https://huggingface.co/livekit/turn-detector">livekit/turn-detector</a>, <a href="https://huggingface.co/pipecat-ai/smart-turn-v3">pipecat-ai/smart-turn-v3</a></p><h4>Agente</h4><p>El agente es el núcleo de un pipeline de voz. Es responsable de entender la intención, reunir el contexto adecuado y formular una respuesta en formato de texto. <a href="https://www.elastic.co/elasticsearch/agent-builder">Elastic Agent Builder</a>, con sus capacidades de razonamiento integradas, su biblioteca de herramientas y la integración de flujos de trabajo, permite crear un agente que puede trabajar sobre tus datos e interactuar con servicios externos.</p><h4>LLM (texto a texto)</h4><p>Al seleccionar un LLM para Elastic Agent Builder, hay dos características principales a considerar: las evaluaciones de razonamiento del LLM y el tiempo hasta el primer token (TTFT).</p><p>Las evaluaciones de razonamiento indican qué tan bien el LLM es capaz de generar respuestas correctas. Las evaluaciones que considerar son aquellas que evalúan la adherencia a las conversaciones de varios turnos y las de inteligencia, como MT-Bench y el set de datos Humanity&#x27;s Last Exam, respectivamente.</p><p>Las evaluaciones de TTFT evalúan qué tan rápido produce el modelo su primer token de salida. Existen otros tipos de evaluaciones de latencia, pero el TTFT es particularmente importante para los agentes de voz, ya que la síntesis de audio puede comenzar tan pronto como se recibe el primer token, lo que resulta en una menor latencia entre turnos y una conversación que se siente natural.</p><p>Por lo general, hay que elegir un equilibrio entre estas dos características, ya que los modelos más rápidos suelen tener un peor desempeño en las evaluaciones de razonamiento.</p><p>Ejemplos (Hugging Face): <a href="https://huggingface.co/openai/gpt-oss-20b">openai/gpt-oss-20b</a>, <a href="https://huggingface.co/openai/gpt-oss-120b">openai/gpt-oss-120b</a></p><h4>Síntesis (texto a voz)</h4><p>La parte final del pipeline es el modelo de texto a voz. Este componente es responsable de convertir la salida de texto del LLM en audio audible. Al igual que con el LLM, la latencia es una característica para tener en cuenta al momento de seleccionar un proveedor de texto a voz. La latencia de texto a voz se mide por el tiempo hasta el primer byte (TTFB). Es el tiempo que tarda en recibirse el primer byte de audio. Un TTFB más bajo también reduce la latencia entre turnos.</p><p>Ejemplos: <a href="https://elevenlabs.io/text-to-speech-api">ElevenLabs</a>, <a href="https://cartesia.ai/sonic">Cartesia</a>, <a href="https://www.rime.ai/">Rime</a></p><h4>Desarrollar el pipeline de voz</h4><p>Elastic Agent Builder puede integrarse en un pipeline de voz en varios niveles diferentes:</p><ol><li>Solo herramientas de Agent Builder: voz a texto → LLM (con herramientas de Agent Builder) → texto a voz</li><li>Agent Builder como MCP: conversión de voz a texto → LLM (con acceso a Agent Builder a través de MCP) → conversión de texto a voz</li><li>Agent Builder como núcleo: voz a texto → Agent Builder → texto a voz</li></ol><p>Para este proyecto, elegí Agent Builder como el enfoque núcleo. Con este enfoque, se puede usar toda la funcionalidad de Agent Builder y los flujos de trabajo. El proyecto usa LiveKit para orquestar voz a texto, detección de turnos y texto a voz, e implementa un nodo LLM personalizado que se integra directamente con Agent Builder.</p><h2>Agente de voz de soporte de Elastic</h2><p>Vamos a construir un agente de voz de soporte personalizado para una tienda de deportes ficticia llamada ElasticSport. Los clientes podrán llamar a la línea de ayuda, pedir recomendaciones de productos, buscar detalles de artículos, consultar el estado de sus pedidos y pedir que se les envíe la información del pedido por mensaje de texto. Para lograr esto, primero necesitamos configurar un agente personalizado y crear herramientas para ejecutar consultas y flujos de trabajo en el lenguaje de búsqueda de Elasticsearch (ES|QL).</p><a href="https://videos.elastic.co/watch/jpX6D21URZuh3PtXVV7Ks6">Watch video on Vidyard</a><h3>Configurar el agente</h3><h4>Indicación</h4><p>La indicación le señala al agente qué personalidad debe adoptar y cómo responder. Es importante destacar que hay algunas indicaciones específicas para voz que garantizan que las respuestas se sinteticen correctamente en audio y que los malentendidos se resuelvan con elegancia.</p><pre><code>You are a Sales Assistant at ElasticSport, an outdoor sport shop specialized in hiking and winter equipment. 

[Profile]
- name: Iva
- company: ElasticSport
- role: Sales Assistant
- language: en-GB
- description: ElasticSport virtual sales assistant

[Context]
- Ask clarifying questions to understand the context.
- Use available tools to answer the user's question.
- Use the knowledge base to retrieve general information

[Style]
- Be informative and comprehensive.
- Maintain a professional, friendly and polite tone.
- Mimic human behavior and speech patterns.
- Be concise. Do not over explain initially

[Response Guideline]
- Present dates in spelled-out month date format (e.g., January fifteenth, two thousand and twenty-four).
- Avoid the use of unpronounceable punctuation such as bullet points, tables, emojis.
- Respond in plain text, avoid any formatting.
- Spell out numbers as words for more natural-sounding speech.
- Respond in short and concise sentences. Responses should be 1 or 2 sentences long.

[ERROR RECOVERY]
### Misunderstanding Protocol
1. Acknowledge potential misunderstanding
2. Request specific clarification</code></pre><h4>Flujos de trabajo</h4><p>Agregaremos un pequeño flujo de trabajo para enviar un SMS a través de la API de mensajería de Twilio. El flujo de trabajo se expondrá al agente personalizado como una herramienta, lo que dará como resultado una experiencia de usuario donde el agente puede enviar al usuario un SMS mientras está en la llamada. Esto permite que quien llama pueda decir, por ejemplo: “¿Puedes enviarme más detalles sobre <em>X</em> por mensaje de texto?”.</p><pre><code>name: send sms
enabled: true
triggers:
  - type: manual
inputs:
  - name: message
    type: string
    description: The message to send to the phone number.

  - name: phone_number
    type: string
    description: The phone number to send the message to.

consts:
  TWILIO_ACCOUNT: "****"
  BASIC_AUTH: "****"
  FROM_PHONE_NNUMBER: "****"
steps:
  - name: http_step
    type: http
    with:
      url: https://api.twilio.com/2010-04-01/Accounts/{{consts.TWILIO_ACCOUNT}}/Messages.json
      method: POST
      headers:
        Content-Type: application/x-www-form-urlencoded
        Authorization: Basic {{consts.BASIC_AUTH | base64_encode}}
      body: From={{consts.FROM_PHONE_NNUMBER}}&To={{inputs.phone_number}}&Body={{inputs.message}}
      timeout: 30s</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b057e71b0a7c50eb3da47cd4f95e77ec7b4c6126-1600x1245.png" style="width: 100%;" /><h4>Herramientas ES|QL</h4><p>Las siguientes herramientas permiten que el agente proporcione respuestas relevantes basadas en datos reales. El repositorio de ejemplo contiene un script de configuración para inicializar Kibana con sets de datos de productos, pedidos y base de conocimientos.</p><ul><li><strong>Product.search</strong></li></ul><p>El set de datos de productos contiene 65 productos ficticios. Este es un documento de ejemplo:</p><pre><code>{
      "sku": "ort3M7k",
      "name": "Ortovox Free Rider 26 Backpack",
      "price": 189,
      "currency": "USD",
      "image": "https://via.placeholder.com/150",
      "description": "The Ortovox Free Rider 26 is a technical freeride backpack with a dedicated safety compartment and diagonal ski carry system. Perfect for backcountry missions.\n\nKey Features:\n- 26L capacity\n- Diagonal ski carry system\n- Safety equipment compartment\n- Helmet holder\n- Hydration system compatible",
      "category": "Accessories",
      "subCategory": "Backpacks",
      "brand": "Ortovox",
      "sizes": ["One Size"],
      "colors": ["Black", "Blue", "Orange"],
      "materials": ["Nylon", "Polyester"]
    }</code></pre><p>Los campos de nombre y descripción se mapean como <code>semantic_text</code>, lo que permite al LLM usar la búsqueda semántica a través de ES|QL para recuperar productos relevantes. La consulta de búsqueda híbrida realiza una coincidencia semántica en ambos campos, aplicando un peso ligeramente mayor a las coincidencias en el campo de nombre mediante un refuerzo.</p><p>La búsqueda primero recupera los 20 mejores resultados clasificados por su puntuación de relevancia inicial. Luego, estos resultados se reclasifican basándose en su campo de descripción utilizando el modelo de inferencia <code>.rerank-v1-elasticsearch</code> y, finalmente, se reducen a los cinco productos más relevantes.</p><pre><code>type: ES|QL
toolId: products.search
description: Use this tool to search through the product catalogue by keywords.
query: |
    FROM products
        METADATA _score
      | WHERE
          MATCH(name, ?query, {"boost": 0.6}) OR
            MATCH(description, ?query, {"boost": 0.4})
      | SORT _score DESC
      | LIMIT 20
      | RERANK ?query
            ON description
            WITH {"inference_id": ".rerank-v1-elasticsearch"}
      | LIMIT 5

parameters:
    query: space separated keywords to search for in catalogue</code></pre><ul><li><strong>Knowledgebase.search</strong></li></ul><p>Los sets de datos de la base de conocimientos contienen documentos con la siguiente estructura, en los que los campos de título y contenido se almacenan como texto semántico:</p><pre><code>{
        id: "8273645",
        createdAt: "2025-11-14",
        title: "International Orders",
        content: `International orders are processed through our international shipping partner. Below are the countries we ship to and average delivery times.
        Germany: 3-5 working days
        France: 3-5 working days
        Italy: 3-5 working days
        Spain: 3-5 working days
        United Kingdom: 3-5 working days
        United States: 3-5 working days
        Canada: 3-5 working days
        Australia: 3-5 working days
        New Zealand: 3-5 working days
        `
}</code></pre><p>Y la herramienta usa una búsqueda similar a la herramienta <code>product.search</code>:</p><pre><code>type: "ES|QL"
toolId: knowledgebase.search
description: Use this tool to search the knowledgebase.
query: |
  FROM knowledge_base
    METADATA _score
  | WHERE
      MATCH(title, ?query, {"boost": 0.6}) OR
      MATCH(content, ?query, {"boost": 0.4})
  | SORT _score DESC
  | LIMIT 20
  | RERANK ?query
      ON content
      WITH {"inference_id": ".rerank-v1-elasticsearch"}
  | LIMIT 5

parameters:
  query: space separated keywords or natural language phrase to semantically search for in the knowledge base</code></pre><ul><li><strong>Orders.search</strong></li></ul><p>La herramienta final que agregaremos es la que se usa para recuperar pedidos por <code>order_id</code>:</p><pre><code>type: "ES|QL"
toolId: order.search
description: Use this tool to retrieve an order by its ID.
query: |
  FROM orders
    METADATA _score
  | WHERE order_id == ?order_id
  | SORT _score DESC
  | LIMIT 1

parameters:
  order_id: "the ID of the order"</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d22bdd540a95b5a9c2bd5f308620835e8e6f7ecb-1600x1361.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d91709a50fb5391876b714885242d998b2b21027-1600x1443.png" style="width: 100%;" /><p>Después de configurar el agente y vincular estos flujos de trabajo y herramientas ES|QL, puedes probar al agente dentro de Kibana.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8691f41624247a6b1352d158c970031e1426ce5e-1600x1056.png" style="width: 100%;" /><p>Además de desarrollar un agente de soporte para ElasticSport, el agente, los flujos de trabajo y las herramientas pueden adaptarse a otros casos de uso, como un agente de ventas que califica clientes potenciales, un agente de servicio para reparaciones del hogar, reservas para un restaurante o un agente para agendar citas.</p><p></p><p>La parte final es conectar el agente que acabamos de crear con LiveKit, los modelos de texto a voz y de voz a texto. El repositorio enlazado al final de este blog contiene un nodo de LLM personalizado de Elastic Agent Builder que se puede usar con LiveKit. Solo hay que sustituir el <code>AGENT_ID</code> por el tuyo propio y enlazarlo con tu instancia de Kibana.</p><h2>Primeros pasos</h2><p>Echa un vistazo al código, y pruébalo tú mismo <a href="https://github.com/KDKHD/elastic_agent_builder_livekit">aquí</a>. </p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Agent Builder, más allá del chat: presentamos la infraestructura aumentada]]></title>
            <description><![CDATA[Consigue información sobre Elastic Agent Builder con infraestructura aumentada, un agente de IA que ofrece operaciones aumentadas, desarrollo aumentado y Synthetics aumentados.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/agent-builder-augmented-infrastructure</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/agent-builder-augmented-infrastructure</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Alexander Wert, Bill Easton, Gil Raphaelli, Graham Hudgins, Norrie Taylor]]></dc:creator>
            <pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e08daf78f328e826f39d06329f6a5487f75d178d-1272x700.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><strong>Esto no es una especulación. Es un hecho.</strong></p><p>Todos somos testigos del auge de los agentes de IA. Son fantásticos para resumir texto, escribir fragmentos de código y responder preguntas basadas en la documentación. Pero para quienes trabajamos en DevOps y en ingeniería de confiabilidad de sitios (SRE), existe una limitación frustrante. La mayoría de los agentes están atrapados en el paradigma del centro de llamadas, lo que significa que pueden leer, pensar y chatear, pero no pueden interactuar con la infraestructura que se supone que deben administrar.</p><p>Para nuestro último proyecto de hackathon, decidimos superar esa limitación.</p><p>Desarrollamos <strong>infraestructura aumentada</strong>: un copiloto de infraestructura que no solo te da consejos, sino que también crea, despliega, monitorea y corrige el entorno en vivo.</p><h2><strong>El problema: copiar, reformatear, pegar</strong></h2><p>Los agentes estándar operan en el vacío. Si una app se cae y le cuesta a la compañía $5 millones, un agente estándar puede leerte el libro de apuntes sobre cómo arreglarlo. Pero sigue siendo <em>tu responsabilidad</em> hacer el trabajo. Ahora solo tienes que copiar el código, adaptarlo al entorno y pegarlo en tu terminal.</p><p>Queríamos crear un agente que entendiera la diferencia entre <em>hablar</em> de Kubernetes y <em>configurar</em> Kubernetes.</p><h2><strong>El motor: ¿Qué es Elastic Agent Builder?</strong></h2><p>Para desarrollarlo no empezamos desde cero. Partimos de <a href="https://www.elastic.co/elasticsearch/agent-builder"><strong>Elastic Agent Builder</strong></a>. Para quienes no lo conozcan, Elastic Agent Builder es un marco de trabajo diseñado para desarrollar agentes de forma rápida, y actúa como puente entre un gran modelo de lenguaje (LLM) (en nuestra demo usamos Google Gemini) y los datos privados almacenados en Elasticsearch.</p><p>Agent Builder se puede usar para la IA conversacional si se lo basa en datos internos, como documentos o registros. Pero su característica más poderosa es la capacidad de asignar <strong>herramientas</strong>. Estas herramientas permiten al LLM salir de la interfaz de chat para realizar tareas específicas. Nos dimos cuenta de que si llevábamos esta característica al límite, podíamos transformar Agent Builder en una potencia de automatización.</p><h2><strong>Cómo hacerlo funcionar: desarrollo de la primera versión</strong></h2><p>Cuando empezamos el proyecto, sabíamos que queríamos que los agentes pudieran cambiar el mundo exterior. Pensamos lo siguiente: ¿y si construimos algún software “runner” (para ejecutar cualquier comando que el agente pueda pensar en el host)? Y luego: ¿qué pasaría si los runners, Elastic Agent Builder y el usuario estuvieran en una llamada los tres?</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8dc8317c1301b8eb7b89438529e8d8d17411c95a-1024x559.png" style="width: 100%;" /><p>Empezamos desarrollando un proyecto en Python, Augmented Infrastructure Runners, que era esencialmente un bucle de <code>while(true)</code> que consultaba la API de conversaciones de Elastic Agent Builder cada segundo y verificaba una sintaxis especial que habíamos creado:</p><pre><code>{
	"tool_name": "my_tool",
       "tool_arguments": "\{stringified json arguments\}"
}</code></pre><p>Luego actualizamos la indicación para enseñarle sobre nuestra nueva sintaxis de llamada de herramienta. Bill es uno de los mantenedores de <a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a>, el marco de trabajo más popular para crear servidores del Protocolo de Contexto de Modelo (MCP) en Python. Se propuso trabajar usando el cliente FastMCP con este nuevo software runner para montar servidores MCP y poner sus herramientas a disposición del runner. Cuando el agente veía esto, ejecutaba la llamada a la herramienta y <code>POST</code> enviaba los resultados de vuelta a la conversación como si el usuario hubiera enviado los resultados. Esto hizo que el LLM respondiera al resultado, ¡y seguimos adelante!</p><p>Fue genial, pero tuvo dos problemas principales:</p><ol><li>El agente soltaba todo este JSON directamente a la conversación con el usuario.</li><li>El primer momento en el que los mensajes se podían ver a través de la API de conversaciones era cuando se completaba una ronda de conversación (es decir, cuando el LLM respondía).</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7b3755aeae17722ff1bb9677712293e9195f96a0-1058x1034.png" style="width: 100%;" /><p>Así que nos propusimos descubrir cómo llevarlo a segundo plano.</p><p>Luego probamos darle al agente una herramienta llamada <code>call_external_tool</code> con dos argumentos: el argumento de la herramienta <code>tool_name</code> y la herramienta JSON en strings. Esta llamada a la herramienta externa no devolvía nada, pero lo importante es que era visible en la solicitud <code>GET</code> a la API de conversaciones. Luego dimos licencia a los runners para escribir documentos directamente en Elasticsearch, los cuales el agente de Elastic Agent Builder podía recuperar según fuera necesario. El agente siempre está operando en respuesta a un mensaje del usuario, por lo que necesitamos iniciar el agente con un mensaje del usuario para que busque resultados y continúe con el procesamiento. Así que hicimos que los agentes insertaran un pequeño mensaje en el chat para reanudar la conversación:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/61ff59a57c68ed5fad492d19c0580644113a507d-1600x1321.png" style="width: 100%;" /><p>Así que ya teníamos llamadas a herramientas externas. Sin embargo, debido al segundo problema mencionado anteriormente, tuvimos que deshacernos de esa parte final de arranque. De lo contrario, ¡cada llamada a una herramienta externa requería una ronda completa de conversación para recuperar los resultados!</p><h2><strong>Llevarlo a otro nivel: introducción a los flujos de trabajo</strong></h2><p>Además de las llamadas al lenguaje de búsqueda de Elasticsearch (ES|QL) y a las herramientas de búsqueda de índices, los agentes de Agent Builder pueden llamar a herramientas basadas en flujo de trabajo de Elastic. Los flujos de trabajo de Elastic ofrecen una forma flexible y fácil de gestionar para ejecutar una secuencia y una lógica de acciones arbitrarias. Para nuestros objetivos, todo lo que necesitamos que haga el flujo de trabajo es almacenar una solicitud externa de herramienta en Elasticsearch y devolver un ID para consultar los resultados. Esto da lugar a la siguiente definición sencilla de flujo de trabajo:</p><pre><code>name: ai-tool-call
enabled: true
triggers:
  - type: manual
inputs:
  - name: runner_id
    type: string
  - name: tool_calls
    type: string

steps:
  - name: store_request
    type: elasticsearch.create
    with:
      index: distributed-tool-requests
      id: "{{inputs.runner_id}}_{{ execution.id }}"
      document:
        request_id: "{{ execution.id }}"
        runner_id: "{{inputs.runner_id}}"
        tool_call: "{{inputs.tool_calls}}"
        status: "unhandled"

  - name: output_result
    type: console
    with:
      message: "Called tool, with execution id: {{ execution.id }}. Use this ID to poll the results."</code></pre><p>Con eso, en lugar de depender de que la solicitud de llamada a la herramienta se escriba en la conversación, los runners pueden simplemente consultar el índice de <code>distributed-tool-requests</code> de Elasticsearch para nuevas solicitudes externas de herramienta y hacer el reporte de los resultados en otro índice de Elasticsearch con la <code>execution.id</code> proporcionada.</p><p>Esto elimina los dos problemas principales mencionados anteriormente:</p><ol><li>El historial de conversaciones ya no está lleno de datos de las llamadas a herramientas externas.</li><li>Como los runners consultan el índice de Elasticsearch en lugar del historial de conversaciones, no están bloqueados por la ronda de conversación que debe completarse para que las solicitudes de herramientas externas se hagan visibles.</li></ol><p>El segundo punto tiene la gran ventaja de que el procesamiento de las llamadas a herramientas externas comienza dentro de la fase de pensamiento del agente (y no cuando se ha completado la ronda de conversación). Esto nos permite indicarle al modelo de lenguaje grande (LLM) en la indicación del sistema que consulte los resultados de la herramienta externa hasta que estén disponibles, lo que elimina la necesidad del mensaje de inicio. En general, esto tiene el efecto positivo de que la conversación se siente más natural: el LLM puede procesar varias solicitudes de herramienta externa dentro de una sola ronda de conversación (en vez de requerir una ronda de conversación por cada solicitud de herramienta) y, por tanto, puede realizar solicitudes de usuario más complejas de una sola vez.</p><h2><strong>Todo integrado</strong></h2><p>Para cerrar la brecha entre el LLM y el rack de servidores, desarrollamos una arquitectura específica empleando las capacidades de la herramienta de Agent Builder:</p><ol><li><strong>Runners de infraestructura aumentada:</strong> desplegamos runners ligeros dentro de los entornos de destino (servidores, clústeres de Kubernetes, cuentas en la cloud). Estos runners se conectan directamente a Elastic, utilizando endpoints seguros y secretos solo disponibles para cada uno de los runners.</li><li><strong>ES|QL retrieval:</strong> el copiloto utiliza <strong>ES|QL</strong> de Elastic para realizar búsquedas híbridas. No solo realiza una búsqueda en base de conocimientos sino también de <em>capacidades</em>. Consulta a los runners conectados para ver qué herramientas están disponibles (por ejemplo, <code>list_ec2_instances</code>, <code>install_helm_chart</code>).</li><li><strong>Ejecución del flujo de trabajo:</strong> una vez que el agente decide un curso de acción, crea un flujo de trabajo estructurado.</li><li><strong>Ciclo de retroalimentación:</strong> los runners ejecutan el comando de forma local y envían los resultados de vuelta a Elasticsearch. El copiloto lee el resultado del índice y decide el siguiente paso.</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/76be256da722c1965971fc506502768bd890f0c4-1290x1076.png" style="width: 100%;" /><h2><strong>La demo: de interrupción a observabilidad</strong></h2><a href="https://videos.elastic.co/watch/bRgPJfJLeABjPMMhvy1sbB">Watch video on Vidyard</a><p>En el video, mostramos dos casos distintos que demuestran la potencia de esta arquitectura.</p><h3><strong>Escenario 1: Rescate de DevOps</strong></h3><p>Empezamos con un usuario que estaba preocupado por una interrupción de 5 millones de dólares causada por un punto ciego en su clúster de Kubernetes.</p><ul><li><strong>La solicitud:</strong> &quot;¿Cómo me aseguro de que esto no vuelva a suceder?&quot;</li><li><strong>La acción:</strong> el agente no solo proporcionó un tutorial. Identificó el clúster, creó los espacios de nombres necesarios, generó secretos de Kubernetes, instaló el operador OpenTelemetry e instantáneamente proporcionó un enlace a un dashboard APM en vivo.</li><li><strong>El resultado:</strong> observabilidad completa de Kubernetes e información de aplicación sin que el usuario escriba ni una sola línea de YAML.</li></ul><h3><strong>Caso 2: entrega de Security</strong></h3><p>Una regla fundamental de la seguridad de infraestructuras es que no puedes proteger lo que no puedes ver. Mientras llevamos a cabo nuestra intervención de DevOps, el agente ve una oportunidad para mejorar la seguridad del entorno.</p><p>Con una alerta iniciada tras una investigación previa relacionada con Elastic Observability, demostramos cómo un profesional de seguridad puede comunicarse directamente con su infraestructura: primero, para enumerar los activos y recursos en su entorno cloud; y segundo, para desplegar las herramientas necesarias para asegurarse de que el entorno esté protegido.</p><ul><li><strong>Descubrimiento:</strong> el copiloto enumeró los recursos de AWS para el profesional de la seguridad e identificó una brecha crítica: una instancia de Amazon Elastic Compute Cloud (EC2) y un clúster de Amazon Elastic Kubernetes Service (EKS) con terminales públicos que no tienen protección para endpoints.</li><li><strong>Remediación:</strong> con una simple aprobación, el copiloto desplegó <strong>Elastic Security</strong> <strong>detección y respuesta extendida (XDR) y detección y respuesta en el cloud (CDR)</strong> a los activos vulnerables, asegurando el entorno en tiempo real.</li><li><strong>El resultado:</strong> protección de los activos y recursos de AWS desplegados con seguridad en tiempo de ejecución completa.</li></ul><h2><strong>El futuro: todo aumentado</strong></h2><p>Este proyecto demuestra que Elastic Agent Builder puede ser el cerebro central de las operaciones distribuidas. No nos limitamos solo a infraestructura. Nuestra tecnología runner puede impulsar:</p><ul><li><strong>Synthetics aumentados:</strong> diagnosticar errores de TLS en runners globales.</li><li><strong>Desarrollo aumentado:</strong> crear pull requests e implementar CAPTCHAs en servicios frontend.</li><li><strong>Operaciones aumentadas:</strong> reconfiguración automática de los servidores DNS durante una interrupción.</li></ul><h2><strong>Pruébalo tú mismo</strong></h2><p>Creemos que el futuro de la IA no se trata solo de soporte por chat; se trata de <strong>infraestructura aumentada</strong>. Se trata de tener un socio que pueda desplegar, arreglar, observar y proteger a la par tuya.</p><p>¡Descubre el código y pruébalo tú mismo con runners distribuidos (<a href="https://github.com/strawgate/augmented-infrastructure">GitHub</a>) más Elastic Agent Builder en <a href="https://cloud.elastic.co/">Elastic Cloud Serverless</a> hoy mismo!</p><ul><li>Crea un proyecto serverless en Elastic Cloud.</li><li>Despliega el código en un ejecutor.</li><li>Configura el runner.</li><li>Configura tu archivo mcp.json.</li><li>Inicia el runner, el cual creará de forma automática tu agente y sus herramientas.</li><li>¡Chatea con un agente que puede razonar, planificar y ejecutar acciones en tus runners distribuidos!</li></ul><p><strong>El equipo: </strong><em>Alex, Bill, Gil, Graham, &amp; Norrie</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Estadísticas ES|QL más rápidas con tablas hash de estilo suizo]]></title>
            <description><![CDATA[Cómo el hashing inspirado en Suiza y el diseño compatible con SIMD ofrecen mejoras consistentes y medibles en el lenguaje de búsqueda de Elasticsearch (ES|QL).]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/esql-swiss-hash-stats</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/esql-swiss-hash-stats</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Chris Hegarty, Matthew Alp, Nik Everet]]></dc:creator>
            <pubDate>Mon, 19 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/21036e031070f14faccb2b53b22723de2750c391-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Recientemente reemplazamos partes clave de la implementación de tablas hash de Elasticsearch por un diseño de estilo suizo y observamos tiempos de construcción e iteración hasta 2–3 veces más rápidos en cargas de trabajo uniformes y de alta cardinalidad. El resultado es una latencia más baja, un mejor rendimiento y un desempeño más predecible para las operaciones de estadísticas y análisis del lenguaje de búsqueda de Elasticsearch (ES|QL).</p><h2>¿Por qué importa esto?</h2><p>La mayoría de los flujos de trabajo analíticos típicos acaban reduciéndose a agrupar datos. Ya sea para calcular el promedio de bytes por host, contar eventos por usuario o agregar métricas en diferentes dimensiones, la operación de núcleo es la misma: asignar claves a grupos y actualizar los agregados en ejecución.</p><p>A pequeña escala, casi cualquier tabla hash razonable funciona bien. A gran escala (cientos de millones de documentos y millones de grupos distintos), los detalles empiezan a importar. Los factores de carga, la estrategia de sondeo, el diseño de la memoria y el comportamiento de la memoria caché pueden marcar la diferencia entre un rendimiento lineal y una barrera de fallos de caché.</p><p>Elasticsearch ha soportado estas cargas de trabajo durante años, pero siempre estamos buscando oportunidades para modernizar los algoritmos de núcleo. Por lo tanto, evaluamos un enfoque más reciente inspirado en las tablas suizas y lo aplicamos a cómo ES|QL calcula las estadísticas.</p><h2>¿Qué son realmente las tablas suizas?</h2><p>Las tablas suizas son una familia de tablas hash modernas popularizadas por la SwissTable de Google y posteriormente adoptadas en Abseil y otras bibliotecas.</p><p>Las tablas hash tradicionales pasan mucho tiempo persiguiendo punteros o cargando claves solo para descubrir que no coinciden. La característica definitoria de las tablas suizas es la capacidad de rechazar la mayoría de las sondas usando una pequeña estructura de matriz residente en caché, almacenada separadamente de las claves y valores, llamadas <em>bytes de control</em>, para reducir significativamente el tráfico de memoria.</p><p>Cada byte de control representa un solo slot y, en nuestro caso, codifica dos cosas: si el slot está vacío y una huella corta derivada del hash. Estos bytes de control están dispuestos de forma continua en la memoria, típicamente en grupos de 16, lo que los hace ideales para el <a href="https://en.wikipedia.org/wiki/Single_instruction,_multiple_data">procesamiento de una sola instrucción y múltiples datos</a> (SIMD).</p><p>En lugar de sondear una ranura a la vez, las tablas suizas escanean todo un bloque de bytes de control utilizando instrucciones vectoriales. En una sola operación, la CPU compara la huella digital de la clave entrante con 16 ranuras y filtra las entradas vacías. Solo los pocos candidatos que sobreviven a esta ruta rápida requieren cargar y comparar las claves reales.</p><p>Este diseño intercambia una pequeña cantidad de metadatos adicionales por una mejor localización de caché y muchas menos cargas aleatorias. A medida que la tabla crece y las cadenas de sonda se alargan, esas propiedades se vuelven cada vez más valiosas.</p><h2>SIMD en el centro</h2><p>La verdadera estrella del espectáculo es SIMD.</p><p>Los bytes de control no solo son compactos, sino que también están diseñados explícitamente para ser procesados con instrucciones vectoriales. Una sola comparación SIMD puede verificar 16 huellas dactilares a la vez, lo que convierte lo que normalmente sería un bucle en un puñado de operaciones amplias. Por ejemplo:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bd418778f0c6144f8f5f18419f6220ac0c935c7a-903x407.png" style="width: 100%;" /><p>En la práctica, esto significa:</p><ul><li>Menos ramas.</li><li>Cadenas de sondeo más cortas.</li><li>Menos cargas de la memoria de claves y valores.</li><li>Mucho mejor utilización de las unidades de ejecución de la CPU.</li></ul><p>La mayoría de las búsquedas nunca pasan del escaneo de bytes de control. Cuando lo hacen, el trabajo restante es enfocado y previsible. Este es precisamente el tipo de carga de trabajo en el que destacan las CPU modernas.</p><h2>SIMD bajo el capó</h2><p>Para los lectores a quienes les gusta echar un vistazo por dentro, aquí está lo que sucede al insertar una nueva clave en la tabla. Utilizamos la API Panama Vector con vectores de 128 bits, por lo que opera en 16 bytes de control en paralelo.</p><p>El siguiente fragmento muestra el código generado en un Intel Rocket Lake con AVX-512. Aunque las instrucciones reflejan ese entorno, el diseño no depende de AVX-512. Las mismas operaciones vectoriales de alto nivel se emiten en otras plataformas usando instrucciones equivalentes (por ejemplo, AVX2, SSE o NEON).</p><pre><code>; Load 16 control bytes from the control block
vmovdqu xmm0, XMMWORD PTR [r9+r10*1+0x10]

; Broadcast the 7-bit fingerprint of the new key across the vector
vpbroadcastb xmm1, r11d

; Compare all 16 control bytes to the new fingerprint
vpcmpeqb k7, xmm0, xmm1
kmovq rbx, k7

; Check if any matches were found
test rbx, rbx
jne <handle_match></code></pre><p>Cada instrucción tiene una función clara en el proceso de inserción:</p><ul><li><code>vmovdqu</code>: Carga 16 bytes de control consecutivos en el registro <code>xmm0</code> de 128 bits.</li><li><code>vpbroadcastb</code>:Replica la huella digital de 7 bits de la nueva clave en todos los carriles del registro <code>xmm1</code>.</li><li><code>vpcmpeqb</code>: Compara cada byte de control con la huella digital transmitida, lo que genera una máscara de posibles coincidencias.</li><li><code>kmovq</code> + <code>test</code>: Mueve la máscara a un registro de propósito general y comprueba rápidamente si existe una coincidencia.</li></ul><p>Finalmente, decidimos sondear grupos de 16 bytes de control a la vez, ya que las pruebas de rendimiento mostraron que expandirse a 32 o 64 bytes con registros más amplios no proporcionaba ningún beneficio de rendimiento medible.</p><h2>Integración en ES|QL</h2><p>Adoptar el hash al estilo suizo en Elasticsearch no fue solo un reemplazo inmediato. ES|QL tiene exigencias estrictas en cuanto a contabilidad de memoria, seguridad e integración con el resto del motor de cálculo.</p><p>Integramos la nueva tabla hash estrechamente con la gestión de memoria de Elasticsearch, que incluye el reciclador de páginas y la contabilidad del interruptor de circuito, lo que garantiza que las asignaciones permanezcan visibles y limitadas. Las agregaciones de Elasticsearch se almacenan densamente y se indexan por un ID de grupo, lo que mantiene el diseño de memoria compacto y rápido para la iteración, además de habilitar ciertas optimizaciones de rendimiento al permitir el acceso aleatorio.</p><p>Para las claves de bytes de longitud variable, almacenamos en caché el hash completo junto con el ID del grupo. Esto evita la recomputación de costosos códigos hash durante el sondeo y mejora la localidad de la caché al mantener los metadatos relacionados juntos. Durante el reprocesamiento, podemos confiar en el hash en caché y en los bytes de control sin inspeccionar los valores en sí, lo que mantiene bajos los costos de redimensionamiento.</p><p>Una simplificación importante en nuestra implementación es que las entradas nunca se eliminan. Esto elimina la necesidad de <em>marcadores</em> (marcadores para identificar ranuras previamente ocupadas) y permite que las ranuras vacías permanezcan verdaderamente vacías, lo que mejora aún más el comportamiento de la sonda y mantiene eficientes los escaneos de bytes de control.</p><p>El resultado es un diseño que se ajusta naturalmente al modelo de ejecución de Elasticsearch a la vez que preserva las características de rendimiento que hacen atractivas a las tablas suizas.</p><h2>¿Cómo funciona?</h2><p>En cardinalidades pequeñas, las tablas suizas rinden aproximadamente al mismo nivel que la implementación existente. Esto es lo que se espera: cuando las tablas son pequeñas, los efectos de la caché tienen menos importancia y hay poco que optimizar.</p><p>A medida que aumenta la cardinalidad, la imagen cambia rápidamente.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/24900afc47ab07b0e9933f6117b99d0f4613f794-962x599.png" style="width: 100%;" /><p>El mapa de calor anterior traza los factores de mejora del tiempo para diferentes tamaños de clave (8, 32, 64 y 128 bytes) en cardinalidades desde 1,000 hasta 10,000,000 de grupos. A medida que aumenta la cardinalidad, el factor de mejora aumenta constantemente, y llega a hasta 2–3x para distribuciones uniformes.</p><p>Esta tendencia es exactamente lo que predice el diseño. Una cardinalidad más alta conduce a cadenas de sondeo más largas en las tablas hash tradicionales, mientras que el sondeo de estilo suizo continúa resolviendo la mayoría de las búsquedas dentro de los bloques de bytes de control amigables con SIMD.</p><h2>El comportamiento de la caché cuenta la historia</h2><p>Para comprender mejor las aceleraciones, ejecutamos el mismo JMH <a href="https://github.com/elastic/elasticsearch/pull/139343/files#diff-d0e0cc91a7495bf36b2d44eacce95f5185d01879e5f6c38089ac7a89aad17da7"><code>benchmarks</code></a> en Linux <code>perf</code> y capturamos estadísticas de caché y TLB.</p><p>En comparación con la implementación original, la versión suiza realiza aproximadamente un 60% menos de referencias de caché en general. Las cargas de la caché de último nivel disminuyen más de 4 veces, y los fallos de carga de la LLC caen en más de 6 veces. Dado que las omisiones de LLC a menudo se traducen directamente en accesos a la memoria principal, esta reducción por sí sola explica una gran parte de la mejora de extremo a extremo.</p><p>Más cerca de la CPU, vemos menos pérdidas de caché de datos L1 y casi 6 veces menos pérdidas TLB de datos, lo que apunta a una localidad espacial más estrecha y patrones de acceso a la memoria más predecibles.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6e49b7609fba83e33692cb9834552b6ca7e42a83-998x499.png" style="width: 100%;" /><p>Esta es la recompensa práctica de los bytes de control compatibles con SIMD. En lugar de cargar repetidamente claves y valores desde ubicaciones de memoria dispersas, la mayoría de las pruebas se resuelven escaneando una estructura compacta residente en la caché. Menos memoria afectada significa menos fallos, y menos fallos significan consultas más rápidas.</p><h2>Resumen</h2><p>Al adoptar un diseño de tabla hash al estilo suizo y al inclinarnos por el sondeo amigable con SIMD, logramos una velocidad de 2 a 3 veces mayor para cargas de trabajo de estadísticas ES|QL de alta cardinalidad, junto con un rendimiento más estable y predecible.</p><p>Este trabajo destaca cómo las estructuras de datos modernas con conocimiento de CPU pueden desbloquear ganancias sustanciales, incluso para problemas bien conocidos, como las tablas hash. Hay más para explorar aquí, como especializaciones adicionales de tipo primitivo y el uso en otras rutas de alta cardinalidad, como las uniones, que son solo parte del esfuerzo más amplio y continuo para modernizar continuamente los internos de Elasticsearch.</p><p>Si te interesan los detalles o quieres seguir el trabajo, echa un vistazo a esta <a href="https://github.com/elastic/elasticsearch/pull/139343">solicitud de extracción</a> y <a href="https://github.com/elastic/elasticsearch/issues/138799">problema meta</a> en Github.</p><p>¡Feliz hash!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Gestión de la memoria agentic con Elasticsearch]]></title>
            <description><![CDATA[Crear agentes más eficientes y sensibles al contexto mediante la gestión de memorias usando Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/agentic-memory-management-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/agentic-memory-management-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Someshwaran Mohankumar]]></dc:creator>
            <pubDate>Fri, 16 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/80907fd072e72d6ec902470b449c9f337957a0d7-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>En la disciplina emergente de <strong>ingeniería de contexto</strong>, proporcionar a los agentes de IA la información correcta en el momento adecuado es crucial. Uno de los aspectos más importantes de la ingeniería de contexto es gestionar la <strong>memoria</strong> de una IA. Al igual que los seres humanos, los sistemas de IA dependen tanto de la memoria a corto plazo como de la memoria a largo plazo para recordar información. Si queremos que los agentes de grandes modelos de lenguaje (LLM) mantengan conversaciones lógicas, recuerden las preferencias del usuario o construyan sobre resultados o respuestas previas, necesitamos equiparlos con mecanismos de memoria efectivos.</p><p>Después de todo, todo en el contexto influye en las respuestas de la IA. Es cierto lo que dicen: &quot;<em>Lo que das es lo que recibes&quot;</em>.</p><p>En este artículo, presentaremos lo que significan la memoria a corto y a largo plazo para los agentes de IA, específicamente:</p><ul><li>La diferencia entre la memoria a corto y a largo plazo.</li><li>Cómo se relacionan con las técnicas de RAG con bases de datos vectoriales, como Elasticsearch, y por qué es necesaria una gestión cuidadosa de la memoria.</li><li>Los riesgos de descuidar la memoria, como el desbordamiento de contexto y el envenenamiento por contexto.</li><li>Las mejores prácticas, como podar el contexto, resumir y recuperar solo lo relevante, para mantener la memoria de un agente útil y segura.</li><li>Finalmente, hablaremos sobre cómo compartir y propagar la memoria en sistemas multiagente para que los agentes colaboren sin confusión mediante Elasticsearch.</li></ul><h2>Memoria a corto plazo frente a memoria a largo plazo en los agentes de IA</h2><p><strong><em>La memoria a corto plazo</em></strong> en un agente de IA suele referirse al contexto o estado conversacional inmediato, es decir, al historial de chat actual o a los mensajes recientes de la sesión activa. Esto incluye la última consulta del usuario y los intercambios recientes. Es muy similar a la información que una persona tiene en mente durante una conversación.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4fbcc6f68055b2bccefc4176297a4ca50056dc0d-764x498.png" style="width: 100%;" /><p>Los marcos de trabajo de IA suelen mantener esta memoria transitoria como parte del estado del agente (por ejemplo, al utilizar un checkpointer para almacenar el estado de la conversación, como se muestra en <a href="https://docs.langchain.com/oss/python/langgraph/persistence#checkpoints">este ejemplo de LangGraph</a>). La memoria a corto plazo es <strong><em>de sesión</em></strong>; es decir, existe dentro de una sola conversación o tarea y se restablece o borra cuando esa sesión termina, a menos que se guarde explícitamente en otro lugar. Un ejemplo de memoria a corto plazo limitada a sesiones sería el <strong><a href="https://help.openai.com/en/articles/8914046-temporary-chat-faq">chat temporal</a> </strong>disponible en ChatGPT.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/150bdf209cda5ed20b59cddf34e624ad1a8016aa-1100x577.png" style="width: 100%;" /><p><strong><em>Memoria a largo plazo</em></strong>, por otro lado, se refiere a la información que persiste <strong>a través de conversaciones o sesiones</strong>. Este es el conocimiento que un agente conserva a lo largo del tiempo, los datos que aprendió antes, las preferencias del usuario o cualquier dato que le hayamos dicho que recuerde permanentemente.</p><p>La memoria a largo plazo generalmente se implementa almacenándola y recuperándola de una fuente externa, como un archivo o una base de datos vectorial que está fuera de la ventana de contexto inmediata. A diferencia de la memoria de chat a corto plazo, la memoria a largo plazo no se incluye automáticamente en cada solicitud. En cambio, basado en un escenario dado, el agente debe <strong>recuperarla</strong> u obtenerla cuando se invocan las herramientas relevantes. En la práctica, la memoria a largo plazo puede incluir la información del perfil del usuario, respuestas o análisis previos realizados por el agente, o una base de conocimientos que el agente puede consultar.</p><p>Por ejemplo, si tienes un agente planificador de viajes, la <em>memoria a corto plazo</em> contendría detalles de la consulta actual del viaje (fechas, destino, presupuesto) y cualquier pregunta de seguimiento en esa conversación; mientras que la <em>memoria a largo plazo</em> podría almacenar las preferencias generales de viaje del usuario, itinerarios pasados y otros datos compartidos en sesiones anteriores. Cuando el usuario regresa más tarde, el agente puede extraer de este almacenamiento a largo plazo (por ejemplo, al usuario le encantan las playas y las montañas, tiene un presupuesto promedio de 100 000 INR, tiene una lista de deseos de lugares para visitar y prefiere experimentar la historia y la cultura en lugar de atracciones para niños) de modo que no trate al usuario como una pizarra en blanco todo el tiempo.</p><p>La memoria a corto plazo (historial de chat) proporciona un contexto inmediato y continuidad, mientras que la memoria a largo plazo proporciona un contexto más amplio del que el agente puede extraer cuando sea necesario. La mayoría de los marcos de trabajo de agentes de IA avanzados permiten ambas posibilidades: realizan un seguimiento de los diálogos recientes para mantener el contexto <em>y</em> ofrecen mecanismos para buscar o almacenar información en un repositorio a más largo plazo. La gestión de la memoria a corto plazo garantiza que se mantenga dentro de la ventana de contexto, mientras que la gestión de la memoria a largo plazo ayuda al agente a fundamentar las respuestas basadas en interacciones previas y personalidades.</p><h2>Memoria y RAG en ingeniería de contexto</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/46635aa11ceff89b8d6a26ac3e22da52407d82f3-1600x900.png" style="width: 100%;" /><p><strong><em>¿Cómo le damos a un agente de IA una memoria útil a largo plazo en la práctica?</em></strong></p><p>Un enfoque destacado para la memoria a largo plazo es <strong><em>la memoria semántica</em></strong>, que a menudo se implementa mediante <strong>generación aumentada de recuperación (RAG)</strong>. Esto implica acoplar el LLM con una tienda de conocimiento externa o un almacén de datos habilitado para vectores, como Elasticsearch. Cuando el LLM necesita información más allá de lo que aparece en el prompt o en su entrenamiento integrado, realiza una recuperación semántica contra Elasticsearch e inyecta los resultados más relevantes en el prompt como contexto. De esta manera, el contexto efectivo del modelo incluye no solo la conversación reciente (memoria a corto plazo), sino también datos pertinentes a largo plazo que se obtienen sobre la marcha. A continuación, el LLM basa su respuesta tanto en su propio razonamiento como en la información recuperada, combinando eficazmente la memoria a corto plazo y la memoria a largo plazo para producir una respuesta más precisa y consciente del contexto.</p><p><strong>Elasticsearch </strong>puede emplearse para implementar memoria a largo plazo para agentes de IA. Aquí hay un ejemplo de alto nivel de cómo se puede recuperar el contexto de Elasticsearch para la memoria a largo plazo.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/41ccbc7b5171e8170ac300139a963c0708816ba6-1600x900.png" style="width: 100%;" /><p>De esta manera, el agente &quot;recuerda&quot; al buscar datos relevantes en lugar de almacenar todo en su limitado prompt, <strong>lo que conduce a diferentes riesgos.</strong></p><p><strong>Usar RAG con Elasticsearch o cualquier almacén vectorial ofrece múltiples beneficios:</strong></p><p>Primero, <strong>amplía el conocimiento</strong> del modelo más allá de su límite de entrenamiento. El agente puede recuperar información actualizada o datos específicos del dominio que el LLM podría desconocer. Esto es crucial para preguntas sobre eventos recientes o temas especializados.</p><p>Segundo, obtener contexto bajo demanda ayuda a reducir las alucinaciones, especialmente porque los LLM no están capacitados con datos propietarios o altamente especializados en relación con tu caso de uso específico, lo que probablemente los exponga a alucinaciones. En lugar de que el LLM adivine o invente nueva información como se incentivó mediante la evaluación, como se destaca en un reciente artículo de OpenAI (<a href="https://arxiv.org/pdf/2509.04664">Por qué alucinan los modelos de lenguaje</a>), el modelo puede basarse en referencias factuales de Elasticsearch. Naturalmente, el LLM depende de la fiabilidad de los datos almacenados en el vector para realmente prevenir la desinformación, y los datos relevantes se recuperan según las medidas de relevancia de núcleo.</p><p>Tercero, la RAG permite que un agente trabaje con bases de conocimientos mucho más grandes de lo que podrías incluir en un prompt. En lugar de enviar documentos completos, como largos trabajos de investigación o documentos de políticas, a la ventana de contexto y correr el riesgo de que la sobrecarga o el contexto de información irrelevante <a href="https://www.elastic.co/search-labs/blog/agentic-memory-management-elasticsearch#context-poisoning">envenene</a> el razonamiento del modelo, la RAG se basa en <a href="https://www.elastic.co/search-labs/blog/chunking-strategies-elasticsearch">la fragmentación</a>. Los documentos grandes se dividen en piezas más pequeñas y semánticamente significativas, y el sistema recupera solo los fragmentos más relevantes para la consulta. De esta manera, el modelo no necesita un contexto de un millón de tokens para parecer conocedor; solo necesita acceso a los fragmentos correctos de un corpus mucho más grande.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e6897356c9f0940e35a63d005e9cd20bc33e5dd7-1600x931.png" style="width: 100%;" /><p>Vale la pena señalar que, a medida que las ventanas de contexto de LLM crecieron (<a href="https://www.anthropic.com/news/1m-context">algunos modelos ahora admiten cientos de miles o incluso millones de tokens</a><em>)</em>, surgió un debate sobre si la RAG está &quot;muerta&quot;. ¿Por qué no enviar todos los datos al prompt? Si te preguntas lo mismo, consulta este maravilloso artículo de mis colegas, Jeffrey Rengifo y Eduard Martin, <a href="https://www.elastic.co/search-labs/blog/rag-vs-long-context-model-llm">Contexto más largo ≠ mejor: Por qué la RAG sigue siendo importante</a>. Esto evita el problema de la &quot;Lo que das es lo que recibes&quot;: El LLM se mantiene enfocado en los pocos fragmentos que importan, en lugar de ejecutarse a través del ruido.</p><p>Dicho esto, integrar Elasticsearch o cualquier almacén vectorial en una arquitectura de agente de IA proporciona <strong>memoria a largo plazo</strong>. El agente almacena el conocimiento externamente y lo recupera como contexto de memoria cuando es necesario. Esto se podría implementar como una <em>arquitectura</em>, en la que, tras cada consulta de usuario, el agente realiza una búsqueda en Elasticsearch para obtener información relevante y luego agrega los primeros resultados al prompt antes de llamar al LLM. La respuesta también podría guardarse en el almacén a largo plazo si contiene nueva información útil (lo que crea un bucle de retroalimentación de aprendizaje). Al usar una memoria basada en recuperación, el agente se mantiene informado y actualizado, sin tener que abarrotar todo lo que sabe en cada solicitud, a pesar de que la ventana de contexto admite <em>un millón de tokens</em>. Esta técnica es una piedra angular de la ingeniería de contexto, ya que combina las ventajas de la recuperación de información y la IA generativa. </p><p>Aquí hay un ejemplo de un estado de conversación gestionado en memoria usando el sistema de puntos de control de LangGraph para la memoria a corto plazo durante la sesión. (Consulta nuestra <a href="https://github.com/someshwaranM/elastic-context-engineering-short-term-long-term-memory">app de ingeniería de contexto de apoyo</a>).</p><pre><code># Initialize chat memory (Note: This is in-memory only, not persistent)
memory = MemorySaver()

# Create a LangGraph agent
langgraph_agent = create_react_agent(model=llm, tools=tools, checkpointer=memory)

...
...
# Only process and display checkpoints if verbose mode is enabled
if args.verbose:
    # List all checkpoints that match a given configuration
    checkpoints = memory.list({"configurable": {"thread_id": "1"}})
    # Process the checkpoints
    process_checkpoints(checkpoints)</code></pre><p>Así es como almacena los <strong>puntos de control</strong>:</p><pre><code>Checkpoint:
Timestamp: 2025-12-30T09:19:41.691087+00:00
Checkpoint ID: 1f0e560a-c2fa-69ec-8001-14ee5373f9cf
User: Hi I'm Som, how are you? (Message ID: ad0a8415-5392-4a58-85ad-84154875bbf2)
Agent: Hi Som! I'm doing well, thank you! How about you? (Message ID: 
56d31efb-14e3-4148-806e-24a839799ece)
Agent:  (Message ID: lc_run--019b6e8e-553f-7b52-8796-a8b1fbb206a4-0)

Checkpoint:
Timestamp: 2025-12-30T09:19:40.350507+00:00
Checkpoint ID: 1f0e560a-b631-6a08-8000-7796d108109a
User: Hi I'm Som, how are you? (Message ID: ad0a8415-5392-4a58-85ad-84154875bbf2)
Agent: Hi Som! I'm doing well, thank you! How about you? (Message ID: 
56d31efb-14e3-4148-806e-24a839799ece)

Checkpoint:
Timestamp: 2025-12-30T09:19:40.349027+00:00
Checkpoint ID: 1f0e560a-b62e-6010-bfff-cbebe1d865f6</code></pre><p>Para la memoria a largo plazo, así es como realizamos la búsqueda semántica en Elasticsearch para recuperar conversaciones previas relevantes usando embeddings vectoriales tras resumir e indexar los puntos de control en Elasticsearch.</p><pre><code>Functions: 
retrieve_from_elasticsearch() 

# Enhanced Elasticsearch retrieval with rank_window and verbose display
def retrieve_from_elasticsearch(query: str, k: int = 5, rank_window: int = None) -> tuple[List[Dict[str, Any]], str]:
    """
    Retrieve context from Elasticsearch with score-based ranking
    
    Args:
        query: Search query
        k: Number of results to return
        rank_window: Number of candidates to retrieve before ranking (default: args.rank_window)
        
    Returns:
        Tuple of (retrieved_documents, formatted_context_string)
    """
    if not es_client or not es_index_name:
        return [], "Elasticsearch is not available. Cannot search long-term memory."
    
    if rank_window is None:
        rank_window = args.rank_window
    
    try:
        # Check if index exists and has documents
        if not es_client.indices.exists(index=es_index_name):
            return [], "No previous conversations stored in long-term memory yet."
        
        # Get document count
        try:
            doc_count = es_client.count(index=es_index_name)["count"]
            if doc_count == 0:
                return [], "Long-term memory is empty. No previous conversations to search."
        except Exception as e:
            return [], f"Error checking memory: {str(e)}"
        
        # Generate embedding for the query
        try:
            query_embedding = embeddings.embed_query(query)
        except Exception as e:
            return [], f"Error generating embedding: {str(e)}"
        
        # Perform semantic search using kNN with rank_window
        try:
            search_body = {
                "knn": {
                    "field": "vector",
                    "query_vector": query_embedding,
                    "k": k,
                    "num_candidates": rank_window  # Retrieve more candidates, then rank top k
                },
                "_source": ["text", "content", "message_type", "timestamp", "thread_id"],
                "size": k
            }
            
            response = es_client.search(index=es_index_name, body=search_body)
            
            if not response.get("hits") or len(response["hits"]["hits"]) == 0:
                return [], "No relevant previous conversations found in long-term memory."
            
            # Extract documents with scores
            retrieved_docs = []
            for hit in response["hits"]["hits"]:
                source = hit["_source"]
                score = hit["_score"]
                retrieved_docs.append({
                    "content": source.get("content", source.get("text", "")),
                    "message_type": source.get("message_type", "unknown"),
                    "timestamp": source.get("timestamp", "unknown"),
                    "thread_id": source.get("thread_id", "unknown"),
                    "score": score
                })
            
            # Format context string
            context_parts = []
            for i, doc in enumerate(retrieved_docs, 1):
                context_parts.append(doc["content"])
            
            context_string = "\n\n".join(context_parts)
            
            # Verbose display
            if args.verbose:
                rich.print(f"\n[bold yellow]🔍 RETRIEVAL ANALYSIS[/bold yellow]")
                rich.print("="*80)
                rich.print(f"[blue]Query:[/blue] {query}")
                rich.print(f"[blue]Retrieved:[/blue] {len(retrieved_docs)} documents (from {rank_window} candidates)")
                rich.print(f"[blue]Total context length:[/blue] {len(context_string)} characters\n")
                
                for i, doc in enumerate(retrieved_docs, 1):
                    rich.print(f"[cyan]📄 Document {i} | Score: {doc['score']:.4f} | Type: {doc['message_type']}[/cyan]")
                    rich.print(f"[cyan]   Timestamp: {doc['timestamp']} | Thread: {doc['thread_id']}[/cyan]")
                    content_preview = doc['content'][:200] + "..." if len(doc['content']) > 200 else doc['content']
                    rich.print(f"[cyan]   Content: {content_preview}[/cyan]")
                    rich.print("-" * 80)
            
            return retrieved_docs, context_string
            
        except Exception as e:
            return [], f"Error searching memory: {str(e)}"
            
    except Exception as e:
        return [], f"Error accessing long-term memory: {str(e)}"</code></pre><p>Ahora que hemos explorado cómo se indexan y recuperan la memoria a corto y largo plazo usando los puntos de control de LangGraph en Elasticsearch, tomemos un momento para entender por qué indexar y eliminar las conversaciones completas puede ser riesgoso.</p><h2>Riesgos de no gestionar la memoria de contexto</h2><p>Como hablamos mucho sobre ingeniería de contexto, junto con la memoria a corto y largo plazo, entendamos qué sucede si no gestionamos bien la memoria y el contexto de un agente.</p><p>Desafortunadamente, muchas cosas pueden salir mal cuando el contexto de una IA se vuelve extremadamente largo o contiene información errónea. A medida que las ventanas de contexto se agrandan, surgen <strong>nuevos tipos de falla</strong>, como:</p><ul><li><strong>Envenenamiento por contexto</strong></li><li><strong>Distracción del contexto</strong></li><li><strong>Confusión de contexto</strong></li><li><strong>Choque de contexto</strong></li><li><strong>Fuga de contexto y conflictos de conocimiento</strong></li><li><strong>Alucinaciones e información errónea</strong></li></ul><p>Hagamos un desglose de estos problemas y otros riesgos que surgen de una mala gestión del contexto:</p><h3>Envenenamiento por contexto</h3><p>El <em>envenenamiento por contexto</em> se refiere a cuando la información incorrecta o dañina termina en el contexto y &quot;envenena&quot; las salidas posteriores del modelo. Un ejemplo común es una alucinación del modelo que se trata como un hecho y se inserta en el historial de conversaciones. El modelo podría entonces aprovechar ese error en respuestas posteriores, lo que agravaría el error. En los bucles iterativos de agentes, una vez que una información falsa se introduce en el contexto compartido (por ejemplo, en un resumen de las notas de trabajo del agente), puede reforzarse una y otra vez. </p><p><a href="https://storage.googleapis.com/deepmind-media/gemini/gemini_v2_5_report.pdf">Los investigadores de DeepMind, en la publicación del reporte Gemini 2.5</a> (TL;DR, consulta <a href="https://www.dbreunig.com/2025/06/17/an-agentic-case-study-playing-pok%C3%A9mon-with-gemini.html">aquí</a>), observaron esto en un agente <em>que jugaba a Pokémon</em>desde hacía mucho tiempo: si el agente alucinaba un estado de juego erróneo y eso quedaba registrado en su <em>contexto (</em>su memoria de objetivos), el agente formaba <strong>estrategias sin sentido</strong> en torno a un objetivo imposible y se quedaba atascado. En otras palabras, un recuerdo contaminado puede llevar al agente por el camino equivocado de forma indefinida.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3f2facf5aad67613ad557422e09ec23a66adc0ed-1600x1388.png" style="width: 100%;" /><p>El envenenamiento del contexto puede ocurrir de forma inocente (por error) o incluso maliciosa, por ejemplo, mediante ataques de inyección de prompt donde un usuario o un tercero introduce una instrucción oculta o un hecho falso que el agente luego recuerda y sigue.</p><p><strong>Contramedidas recomendadas:</strong></p><p>Basándose en la información de <a href="https://www.wiz.io/academy/data-poisoning">Wiz</a>, <a href="https://zerlo.net/en/blog/what-is-llm-data-poisoning">Zerlo</a> y <a href="https://www.anthropic.com/research/small-samples-poison">Anthropic</a>, las contramedidas para el envenenamiento del contexto se centran en prevenir que la información errónea o engañosa entre en la ventana de contexto, el pipeline de recuperación o la ventana de contexto de un LLM. Los pasos clave incluyen:</p><ul><li>Revisa el contexto constantemente: monitoriza la conversación o el texto recuperado para detectar cualquier cosa sospechosa o dañina, no solo el prompt inicial.</li><li>Utiliza fuentes confiables: Puntúa o etiqueta los documentos según su credibilidad para que el sistema prefiera la información confiable e ignore los datos con baja puntuación.</li><li>Detecta datos inusuales: usa herramientas que detecten contenido extraño, fuera de lugar o manipulado, y elimínalo antes de que el modelo lo use.</li><li>Filtra entradas y salidas: Añade salvaguardas para que el texto dañino o engañoso no pueda entrar fácilmente en el sistema ni ser repetido por el modelo.</li><li>Mantén el modelo actualizado con datos limpios: actualiza regularmente el sistema con información verificada para contrarrestar cualquier dato incorrecto que haya pasado desapercibido.</li><li>Intervención humana: Haz que las personas revisen las salidas importantes o las comparen con fuentes conocidas y confiables.</li></ul><p>Los hábitos sencillos de los usuarios también ayudan, como restablecer los chats largos, compartir solo información relevante, dividir las tareas complejas en pasos más pequeños y mantener notas claras fuera del modelo.</p><p>En conjunto, estas medidas crean una defensa en capas que protege a los LLMs del envenenamiento del contexto y mantiene las salidas precisas y fiables.</p><p>Sin contramedidas como las mencionadas aquí, un agente podría recordar instrucciones, como ignorar directrices previas<em> </em>o datos triviales que un atacante introdujo, lo que podría provocar salidas dañinas.</p><h3>Distracción del contexto</h3><p><em>Distracción por contexto</em> es cuando un contexto crece tanto que el modelo se sobreenfoca en el contexto, y descuida lo que aprendió durante el entrenamiento. En casos extremos, esto se asemeja al <a href="https://en.wikipedia.org/wiki/Catastrophic_interference"><em>olvido catastrófico</em></a>; es decir, el modelo efectivamente &quot;olvida&quot; su conocimiento subyacente y se apega demasiado a la información colocada frente a él. Estudios previos han demostrado que los LLM a menudo pierden el enfoque cuando la solicitud es extremadamente larga.</p><p>El agente Gemini 2.5, por ejemplo, admitía una ventana de un millón de tokens, pero una vez que su contexto creció más allá de cierto punto (del orden de 100 000 tokens en un experimento), comenzó a <strong>fijarse en repetir sus acciones pasadas</strong> en lugar de encontrar nuevas soluciones. En cierto sentido, el agente se convirtió en prisionero de su extensa historia. Siguió mirando su largo log de movimientos anteriores (el contexto) e imitándolos, en lugar de usar su conocimiento de entrenamiento subyacente para diseñar estrategias nuevas y novedosas.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e08e5b6d2e8ec7e3511d455985eed3d7fa6241e0-1352x636.png" style="width: 100%;" /><p>Esto es contraproducente. Queremos que el modelo emplee el contexto relevante para ayudar al razonamiento, no para anular su capacidad de pensamiento. Cabe destacar que incluso los modelos con ventanas enormes presentan esta <a href="https://research.trychroma.com/context-rot"><em>podredumbre contextual</em></a>: su rendimiento se degrada de forma no uniforme a medida que se agregan más tokens. Parece haber un <em>presupuesto de atención</em>. Al igual que los humanos con memoria de trabajo limitada, un LLM tiene una capacidad finita para atender a los tokens, y a medida que ese presupuesto se estira, su precisión y enfoque disminuyen.</p><p>Como medida de mitigación, puedes prevenir la distracción del contexto usando fragmentación, ingeniería de la información correcta, resumen regular del contexto y técnicas de evaluación y seguimiento para medir la precisión de la respuesta mediante puntaje.</p><p>Estos métodos mantienen el modelo basado tanto en el contexto relevante como en su entrenamiento subyacente, lo que reduce el riesgo de distracción y mejora la calidad general del razonamiento.</p><h3>Confusión de contexto</h3><p><em>La confusión de contexto</em> ocurre cuando el modelo emplea contenido superfluo en el contexto para generar una respuesta de baja calidad. Un ejemplo claro es dar a un agente un gran conjunto de herramientas o definiciones de API que podría emplear. Si muchas de esas herramientas no están relacionadas con la tarea actual, el modelo puede intentar usarlas de forma inapropiada, simplemente porque están presentes en contexto. Los experimentos demostraron que proporcionar <em>más</em> herramientas o documentos puede <em>perjudicar</em> el rendimiento si no se necesitan todos. El agente empieza a cometer errores, como llamar a la función equivocada o referenciar texto irrelevante. </p><p>En un caso, un pequeño <strong>modelo Llama 3.1 8B</strong> falló en una tarea cuando se le dieron 46 herramientas para considerar, pero tuvo éxito cuando se le dieron solo 19 herramientas. Las herramientas adicionales crearon confusión, a pesar de que el contexto se ajustaba a los límites de longitud. El problema subyacente es que cualquier información en el mensaje será <em>atendida</em> por el modelo. Si no sabe ignorar algo, ese algo podría influir en su salida de maneras no deseadas. Los elementos irrelevantes pueden &quot;robar&quot; parte de la atención del modelo y llevarlo por el camino equivocado (por ejemplo, un documento irrelevante podría hacer que el agente responda a una pregunta diferente a la que se le hizo). La confusión contextual a menudo se manifiesta cuando el modelo produce una respuesta de baja calidad que integra contextos no relacionados. Consulta el artículo de investigación: <a href="https://arxiv.org/pdf/2411.15399">Menos es más: optimización de la llamada de funciones para la ejecución de LLM en dispositivos periféricos.</a></p><p>Nos recuerda que más contexto no siempre es mejor, especialmente si no está <strong>curado</strong> para que sea relevante.</p><h3>Choque de contexto</h3><p><em>Choque de contexto</em> ocurre cuando <strong>partes del contexto se contradicen entre sí</strong>, lo que causa inconsistencias internas que desvían el razonamiento del modelo. Puede producir un choque si el agente acumula múltiples piezas de información que están en conflicto. </p><p>Por ejemplo, imagina un agente que obtuvo datos de dos fuentes: una dice que <em>el vuelo A sale a las 5 p. m.</em> y la otra dice que <em>el vuelo A sale a las 6 p. m</em>. Si ambos hechos terminan en el contexto, el pobre modelo no tiene forma de saber cuál es el correcto; puede confundirse o producir una respuesta incorrecta o no similar.</p><p>El choque de contexto también ocurre frecuentemente en conversaciones de múltiples turnos donde los <strong>intentos anteriores del modelo</strong> de responder todavía persisten en el contexto junto con información refinada posterior.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/500d7a80dc8db1923f9b5ca84728eed64fa296f7-1316x580.png" style="width: 100%;" /><p>Un <a href="https://arxiv.org/pdf/2505.06120">estudio de investigación realizado</a> por Microsoft y Salesforce muestra que si divides una consulta compleja en múltiples turnos de chatbot (agregando detalles gradualmente), la precisión final disminuye significativamente, en comparación con dar todos los detalles en un solo mensaje. ¿Por qué? Porque las primeras vueltas contienen respuestas intermedias parciales o incorrectas del modelo, y estas permanecen en el contexto. Cuando el modelo luego intenta responder con toda la información, su <em>memoria</em> aún incluye esos intentos incorrectos, que entran en conflicto con la información corregida y lo desvían del camino. Básicamente, el contexto de la conversación entra en conflicto consigo mismo. El modelo puede usar inadvertidamente una pieza de contexto desactualizada (de un turno anterior) que no se aplica después de que se agrega nueva información.</p><p>En los sistemas de agentes, el choque de contexto es especialmente peligroso porque un agente puede combinar las salidas de diferentes herramientas o subagentes. Si esas salidas no coinciden, el contexto agregado es inconsistente. El agente podría entonces quedarse atascado o producir resultados absurdos al tratar de conciliar las contradicciones. La prevención del choque de contexto implica asegurarse de que el contexto sea <strong>fresco y consistente</strong>,<strong> </strong>por ejemplo, borrar o actualizar cualquier información obsoleta y no mezclar fuentes que no hayan sido objeto de un estudio de consistencia.</p><h3>Fuga de contexto y conflictos de conocimiento</h3><p>En los sistemas en los que varios agentes o usuarios comparten un almacén de memoria, existe el riesgo de que la información se filtre entre contextos.</p><p>Por ejemplo, si las incrustaciones de datos de dos usuarios distintos residen en la misma base de datos vectorial sin un control de acceso adecuado, un agente que responda a la consulta del usuario A podría recuperar accidentalmente parte de la memoria del usuario B. Esta <strong><em>fuga entre contextos</em></strong> puede exponer información privada o simplemente crear confusión en las respuestas.</p><p>Según el <a href="https://wtit.com/blog/2025/04/17/owasp-top-10-for-llm-applications-2025/">Top 10 de OWASP para aplicaciones LLM</a>, las bases de datos vectoriales de usuarios múltiples deben protegerse contra este tipo de fugas:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8f0ccf0b2f7bd6715c14aceee2deffb213d50bd9-1600x936.png" style="width: 100%;" /><p>Según <a href="https://wtit.com/blog/2025/04/17/owasp-top-10-for-llm-applications-2025/">LLM08:2025 Debilidades de Vectores y Embedding</a><em>,</em> uno de los riesgos comunes es la fuga de contexto:</p><blockquote><em>En entornos de múltiples usuarios donde varias clases de usuarios o aplicaciones comparten la misma base de datos vectorial, existe el riesgo de pérdida de contexto entre usuarios o consultas. Los errores de conflicto de conocimiento en la federación de datos pueden ocurrir cuando los datos de múltiples fuentes se contradicen entre sí. Esto también puede suceder cuando un LLM no puede reemplazar el conocimiento antiguo que aprendió durante el entrenamiento con los nuevos datos de generación aumentada.</em></blockquote><p>Otro aspecto es que un LLM podría tener problemas para anular su <strong>conocimiento integrado</strong> con nueva información de memoria. Si el modelo fue entrenado con algún hecho y el contexto recuperado dice lo contrario, el modelo puede confundirse sobre cuál confiar. Sin un diseño adecuado, el agente podría confundir contextos o no actualizar el conocimiento antiguo con nueva evidencia, lo que llevaría a respuestas obsoletas o incorrectas.</p><h3><strong>Alucinaciones e información errónea</strong></h3><p>Mientras que una <em>alucinación </em>(el LLM inventa información plausible pero falsa) es un problema conocido incluso sin contextos largos, una mala gestión de la memoria puede amplificarlo. </p><p>Si la memoria del agente carece de un hecho crucial, el modelo puede <strong>llenar el vacío con una suposición</strong>, y si esa suposición entra en el contexto (envenenándolo), el error persiste. </p><p>El informe de seguridad de los LLM <a href="https://wtit.com/blog/2025/04/17/owasp-top-10-for-llm-applications-2025/"><strong>OWASP (LLM09:2025 Desinformación)</strong></a> destaca la desinformación como una vulnerabilidad de núcleo: los LLM pueden ofrecer respuestas seguras pero fabricadas, y los usuarios pueden confiar demasiado en ellos. Un agente con una memoria a largo plazo deficiente o desactualizada podría citar con confianza algo que era cierto el año pasado pero que ahora es falso, a menos que su memoria se mantenga actualizada. </p><p>La dependencia excesiva en la salida de la IA (ya sea por parte de los usuarios o del propio agente en un bucle) puede empeorar esta situación. Si nadie revisa nunca la información almacenada en la memoria, el agente puede acumular falsedades. Esta es la razón por la que la RAG se usa a menudo para reducir las alucinaciones: al recuperar una fuente autorizada, el modelo no tiene que inventar hechos. Pero si tu recuperación trae el documento incorrecto (digamos, uno que contiene información errónea) o si una alucinación temprana no se poda, el sistema puede propagar esa información errónea a través de sus acciones. </p><p>La conclusión: no administrar la memoria puede conducir a <strong>salidas incorrectas y engañosas</strong>, lo que puede ser perjudicial, especialmente si hay mucho en juego (por ejemplo, malos consejos en un dominio financiero o médico). Un agente necesita mecanismos para verificar o corregir su contenido de memoria, no solo confiar incondicionalmente en lo que esté en el contexto.</p><p>En resumen, darle a un agente de IA una memoria infinitamente larga o volcar cada cosa posible en su contexto <em>no</em> es una receta para el éxito.</p><h2>Mejores prácticas para la gestión de memoria en aplicaciones LLM</h2><p>Para evitar las trampas anteriores, los desarrolladores e investigadores idearon una serie de <strong>mejores prácticas para administrar el contexto y la memoria en sistemas</strong> de IA. Estas prácticas tienen como objetivo mantener el contexto de trabajo de la IA ágil, relevante y actualizado. Aquí hay algunas de las estrategias clave, junto con ejemplos de cómo ayudan.</p><h3>RAG: Utiliza un contexto específico.</h3><p>Gran parte de la RAG ya se ha cubierto en la sección anterior, así que esto sirve como un conjunto conciso de recordatorios prácticos:</p><ul><li>Usa la recuperación dirigida, no la carga masiva: Recupera solo los fragmentos más relevantes en lugar de insertar documentos enteros o historiales de conversación completos en el prompt.</li><li>Trata la RAG como una recuperación de memoria justo a tiempo: Obtén el contexto solo cuando sea necesario, en lugar de traer todo en cada turno.</li><li>Prefiere estrategias de recuperación conscientes de la relevancia: enfoques como la búsqueda semántica top-k, la fusión de rango recíproco o el filtrado de carga de herramientas ayudan a reducir el ruido y mejorar la conexión a tierra.</li><li>Las ventanas de contexto más grandes no eliminan la necesidad de RAG: dos párrafos muy relevantes son casi siempre más efectivos que 20 páginas poco relacionadas.</li></ul><p>Dicho esto, la RAG no se trata de agregar más contexto; se trata de agregar el contexto adecuado.</p><h3>Carga de herramientas</h3><p><em>Configuración de herramientas</em> se trata de darle a un modelo solo las herramientas que realmente necesita para una tarea. El término proviene de los juegos: Eliges un equipo que se ajuste a la situación. Demasiadas herramientas te ralentizan; las incorrectas causan fallas. Los LLM se comportan de la misma manera, según el documento de investigación <a href="https://arxiv.org/abs/2411.15399">Menos es más</a>. Una vez que pasas de unas 30 herramientas, las descripciones empiezan a solaparse y el modelo se confunde. Después de ~100 herramientas, el fracaso está casi garantizado. Esto no es un problema de ventana de contexto, es confusión de contexto.</p><p>Una solución simple y efectiva es <a href="https://arxiv.org/abs/2505.03275"><strong>RAG-MCP</strong></a>. En lugar de poner todas las herramientas en el mensaje, las descripciones de las herramientas se almacenan en una base de datos vectorial y solo se recuperan las más relevantes por solicitud. En la práctica, esto permite mantener un equipamiento reducido y específico, acorta considerablemente los tiempos de respuesta y puede mejorar hasta tres veces la precisión en la selección de herramientas.</p><p>Los modelos más pequeños alcanzan este límite incluso antes. La investigación muestra que un modelo 8B falla con docenas de herramientas, pero tiene éxito una vez que se reduce la carga. La selección dinámica de herramientas, a veces con un LLM primero, razonando sobre lo que cree que necesita, puede aumentar el rendimiento en un 44%, al tiempo que reduce el uso de poder y la latencia. La clave es que la mayoría de los agentes solo necesitan unas pocas herramientas, pero a medida que tu sistema crece, la carga de herramientas y el RAG-MCP se convierten en decisiones de diseño de primer orden.</p><h3>Poda de contexto: limita la duración del historial de chat</h3><p>Si una conversación se prolonga durante muchos turnos, el historial de chat acumulado puede llegar a ser demasiado grande para caber, lo que provoca un desbordamiento del contexto o distrae demasiado al modelo. </p><p><em>Recortar</em> significa eliminar o acortar programáticamente las partes menos importantes del diálogo a medida que crece. Una forma simple es descartar los turnos más antiguos de la conversación cuando alcanzas un cierto límite, manteniendo solo los últimos <em>N</em> mensajes. Una poda más sofisticada podría eliminar digresiones irrelevantes o instrucciones previas que ya no son necesarias. El objetivo es <strong>mantener la ventana de contexto despejada</strong> de noticias antiguas. </p><p>Por ejemplo, si el agente resolvió un subproblema hace 10 turnos y hemos seguido adelante desde entonces, podríamos eliminar esa parte del historial del contexto (asumiendo que ya no será necesaria). Muchas implementaciones basadas en chat hacen esto: mantienen una ventana móvil con los mensajes recientes. </p><p>Recortar puede ser tan simple como &quot;olvidar&quot; las primeras partes de una conversación una vez que se han resumido o se consideran irrelevantes. De esta manera, reducimos el riesgo de errores por exceso de contexto y también reducimos <a href="https://www.elastic.co/search-labs/blog/agentic-memory-management-elasticsearch#context-distraction"><strong>la distracción del contexto</strong></a>, por lo que el modelo no verá contenido antiguo o fuera de tema ni se distraerá con él. Este enfoque es muy similar a cómo los humanos podrían no recordar cada palabra de una charla de una hora, pero retendrán los puntos destacados. </p><p>Si tienes dudas acerca de la poda de contexto, como lo destaca el autor Drew Breunig <a href="https://www.dbreunig.com/2025/06/26/how-to-fix-your-context.html#tool-loadout:~:text=Provence%20is%20fast%2C%20accurate%2C%20simple%20to%20use%2C%20and%20relatively%20small%20%E2%80%93%20only%201.75%20GB.%20You%20can%20call%20it%20in%20a%20few%20lines%2C%20like%20so%3A">aquí</a>, el uso del modelo Provence (`<a href="https://huggingface.co/naver/provence-reranker-debertav3-v1">naver/provence-reranker-debertav3-v1</a>`), un podador de contexto ligero (1.75 GB), eficiente y preciso para la respuesta a preguntas, puede marcar la diferencia. Puedes reducir documentos grandes a solo el texto más relevante para una búsqueda determinada. Puedes llamarlo en intervalos específicos.</p><p>Así es como invocamos el modelo `provence-reranker` en nuestro código para podar el contexto:</p><pre><code># Context pruning with Provence
def prune_with_provence(query: str, context: str, threshold: Optional[float] = None) -> str:
    """
    Prune context using Provence reranker model
    
    Args:
        query: User's query/question
        context: Original context to prune
        threshold: Relevance threshold (0-1) for Provence reranker.
                   If None, uses args.pruning_threshold.
                   0.1 = conservative (recommended, no performance drop)
                   0.3-0.5 = moderate to aggressive pruning
    
    Returns:
        Pruned context with only relevant sentences
    """
    if provence_model is None:
        return context
    
    if threshold is None:
        threshold = args.pruning_threshold
    
    try:
        # Use Provence's process method
        provence_output = provence_model.process(
            question=query,
            context=context,
            threshold=threshold,
            always_select_title=False,
            enable_warnings=False
        )
        
        # Extract pruned context from output
        pruned_context = provence_output.get('pruned_context', context)
        reranking_score = provence_output.get('reranking_score', 0.0)
        
        # Log statistics
        original_length = len(context)
        pruned_length = len(pruned_context)
        reduction_pct = ((original_length - pruned_length) / original_length * 100) if original_length > 0 else 0
        
        if args.verbose:
            rich.print(f"[cyan]📊 Pruning stats: {pruned_length}/{original_length} chars ({reduction_pct:.1f}% reduction, threshold={threshold:.2f}, rerank_score={reranking_score:.3f})[/cyan]")
        
        return pruned_context if pruned_context else context
        
    except Exception as e:
        rich.print(f"[yellow]⚠️ Error in Provence pruning: {str(e)}[/yellow]")
        rich.print(f"[yellow]⚠️ Falling back to original context[/yellow]")
        return context</code></pre><p>Empleamos el modelo de reranking de Provenza (`naver/provence-reranker-debertav3-v1`) para calificar la relevancia de las oraciones. El filtrado basado en umbrales mantiene las oraciones por encima del umbral de relevancia. Además, introducimos un mecanismo de respaldo, donde volvemos al contexto original si la poda falla. Finalmente, el logging de estadísticas rastrea el porcentaje de reducción en el modo detallado.</p><h3>Resumen de contexto: Condensa la información antigua en lugar de eliminarla por completo</h3><p><em>El resumen</em> es un complemento al recorte. Cuando la historia o la base de conocimientos se vuelve demasiado grande, puedes emplear el LLM para generar un breve resumen de los puntos importantes y usar ese resumen en lugar del contenido completo en el futuro, como realizamos en nuestro código anterior.</p><p>Por ejemplo, si un asistente de IA tuvo una conversación de 50 turnos, en lugar de enviar los 50 turnos al modelo en el turno 51 (que probablemente no encaje), el sistema podría tomar los turnos 1 a 40, hacer que el modelo los resuma en un párrafo y luego solo proporcionar ese resumen más los últimos 10 turnos en el siguiente mensaje. De esta manera, el modelo aún sabe lo que se discutió sin necesidad de conocer todos los detalles. Los primeros usuarios del chatbot lo hacían manualmente preguntando: &quot;¿Puedes resumir lo que hablamos hasta ahora?&quot; y luego continuaban en una nueva sesión con el resumen. Ahora se puede automatizar. El resumen no solo ahorra espacio en la ventana de contexto, sino que también puede reducir <strong>la confusión y distracción del contexto</strong> al eliminar detalles adicionales y conservar solo los hechos más importantes.</p><p>Aquí es cómo usamos los modelos de OpenAI (puedes usar cualquier LLM) para condensar el contexto a la vez que preservamos toda la información relevante, lo que elimina la redundancia y la duplicación.<br/></p><pre><code># Context summarization
def summarize_context(query: str, context: str) -> str:
    """
    Summarize context using LLM to reduce duplication and focus on relevant information
    
    Args:
        query: User's query/question
        context: Context to summarize
        
    Returns:
        Summarized context
    """
    try:
        summary_prompt = f"""You are an expert at summarizing conversation context.

Your task: Analyze the provided conversation context and produce a condensed summary that fully answers or supports the user's specific question.

The summary must:
1. Preserve every fact, detail, and information that directly relates to the question
2. Eliminate redundancy and duplicate information
3. Maintain chronological flow when relevant
4. Focus on information that helps answer: "{query}"

Context to summarize:
{context}

Provide a concise summary that preserves all relevant information:"""

        summary = llm.invoke(summary_prompt).content
        
        if args.verbose:
            original_length = len(context)
            summary_length = len(summary)
            reduction_pct = ((original_length - summary_length) / original_length * 100) if original_length > 0 else 0
            rich.print(f"[cyan]📝 Summarization stats: {summary_length}/{original_length} chars ({reduction_pct:.1f}% reduction)[/cyan]")
        
        return summary
        
    except Exception as e:
        rich.print(f"[yellow]⚠️ Error in context summarization: {str(e)}[/yellow]")
        rich.print(f"[yellow]⚠️ Falling back to original context[/yellow]")
        return context</code></pre><p>Es importante destacar que cuando se resume el contexto, es menos probable que el modelo se vea abrumado por detalles triviales o errores pasados (suponiendo que el resumen sea exacto). </p><p>Sin embargo, el resumen debe hacerse con cuidado. Un mal resumen puede omitir un detalle crucial o incluso provocar un error. Es esencialmente otro mensaje para el modelo (&quot;resume esto&quot;), por lo que puede alucinar o perder matices. La mejor práctica es resumir de forma incremental y quizás mantener algunos hechos canónicos sin resumir.</p><p>No obstante, ha demostrado ser muy útil. <a href="https://storage.googleapis.com/deepmind-media/gemini/gemini_v2_5_report.pdf">En el escenario del agente Gemini, </a>resumir el contexto cada ~100k tokens era una forma de contrarrestar la tendencia del modelo a repetir. El resumen actúa como una memoria comprimida de la conversación o los datos. Como desarrolladores, podemos implementar esto haciendo que un agente llame periódicamente a una función de resumen (tal vez un LLM más pequeño o una rutina dedicada) en el historial de la conversación o en un documento largo. El resumen resultante reemplaza el contenido original en el prompt. Esta táctica se utiliza ampliamente para mantener los contextos dentro de unos límites y sintetizar la información.</p><h3>Cuarentena de contexto: aislar los contextos cuando sea posible</h3><p>Esto es más relevante en sistemas de agentes complejos o flujos de trabajo de varios pasos. La idea de la segmentación del contexto es dividir una tarea grande en tareas más pequeñas e independientes, cada una con su propio contexto, para que nunca acumules un contexto enorme que lo contenga todo. Cada subagente o subtarea trabaja en una parte del problema con un contexto enfocado, y luego un agente de nivel superior, o supervisor o coordinador integra los resultados.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f2de68c3339883d7658e633af3948f29f427e6cf-1600x900.png" style="width: 100%;" /><p><a href="https://www.anthropic.com/engineering/multi-agent-research-system">La estrategia de investigación de Anthropic emplea múltiples subagentes</a>, cada uno investigando un aspecto diferente de una pregunta, con sus propias ventanas de contexto, y un agente principal que lee los resultados destilados de esos subagentes. Este enfoque paralelo y modular significa que ninguna ventana de contexto individual se vuelve demasiado voluminosa. También reduce la posibilidad de que se mezcle información irrelevante, cada hilo se mantiene en el tema (sin confusión de contexto) y no lleva equipaje innecesario al responder su subpregunta específica. En cierto sentido, es como ejecutar hilos separados de pensamiento que solo comparten sus resultados, no todo su proceso de pensamiento.</p><p>En sistemas multiagente, este enfoque es esencial. Si el agente A se encarga de la tarea A y el agente B se encarga de la tarea B, no hay razón para que ninguno de los dos consuma todo el contexto del otro, a menos que sea realmente necesario. En cambio, los agentes pueden intercambiar solo la información necesaria. Por ejemplo, el agente A puede pasar un resumen consolidado de sus hallazgos al agente B a través de un agente supervisor, mientras que cada subagente mantiene su propio hilo de contexto dedicado. Esta configuración no requiere intervención humana; se basa en un agente supervisor con herramientas habilitadas y con un intercambio de contexto mínimo y controlado.</p><p>No obstante, diseñar tu sistema de manera que los agentes o herramientas operen con la mínima superposición de contexto necesaria puede mejorar considerablemente la claridad y el rendimiento. Piensa en ello como <strong>microservicios para IA</strong>, cada componente se ocupa de su contexto y pasa mensajes entre ellos de una manera controlada, en lugar de un contexto monolítico. Estas mejores prácticas a menudo se usan en combinación. Además, esto te da la flexibilidad de recortar historiales triviales, resumir mensajes o conversaciones antiguas importantes, transferir los registros detallados a Elasticsearch para contexto a largo plazo y usar la recuperación para recuperar cualquier cosa relevante cuando sea necesario.</p><p>Como se mencionó <a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents#:~:text=While%20some%20models,to%20the%20LLM">aquí</a>, el principio rector es que el contexto es un recurso limitado y valioso. Quieres que cada token del prompt se gane su conservación, lo que significa que debería contribuir a la calidad de la salida. Si algo en la memoria no está cumpliendo con su función (o peor aún, está causando confusión), entonces debe ser eliminado, resumido o descartado.</p><p>Como desarrolladores, ahora podemos programar el contexto igual que programamos el código, decidiendo qué información incluir, cómo formatearla y cuándo omitirla o actualizarla. Siguiendo estas prácticas, podemos proporcionar a los agentes LLM el contexto necesario para realizar tareas sin caer en los modos de fallo descritos anteriormente. El resultado son agentes que recuerdan lo que deben, olvidan lo que no necesitan y recuperan lo que requieren justo a tiempo.</p><h2>Conclusión</h2><p>La memoria no es algo que añades a un agente; es algo que diseñas. La memoria a corto plazo es el bloc de notas de trabajo del agente, y la memoria a largo plazo es su almacén duradero de conocimiento. La RAG es el puente entre los dos, ya que convierte un almacén de datos pasivo, como Elasticsearch, en un mecanismo de recuperación activo que puede conectar a tierra las salidas y mantener el agente actualizado.</p><p>Pero la memoria es un arma de doble filo. En el momento en que dejas que el contexto crezca sin control, invitas al envenenamiento, la distracción, la confusión y los choques, y en los sistemas compartidos, incluso la fuga de datos. Por eso, el trabajo de memoria más importante no es &quot;almacenar más&quot;, sino &quot;seleccionar mejor&quot;: recuperar selectivamente, podar agresivamente, resumir cuidadosamente y evitar mezclar contextos no relacionados a menos que la tarea realmente lo demande.</p><p>En la práctica, una buena ingeniería de contexto parece un buen diseño de sistemas: contextos más pequeños y suficientes, interfaces controladas entre componentes y una clara separación entre el estado crudo y el estado destilado que realmente quieres que vea el modelo. Si se hace correctamente, no terminas con un agente que lo recuerda todo, sino con un agente que recuerda las cosas adecuadas, en el momento adecuado, por la razón correcta.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mayor rendimiento y menor latencia: Elastic Cloud Serverless en AWS recibe un aumento significativo del rendimiento]]></title>
            <description><![CDATA[Hemos actualizado la infraestructura de AWS para Elasticsearch Serverless con hardware más nuevo y rápido. Descubre cómo este aumento masivo del rendimiento ofrece consultas más rápidas, mejor escalado y menores costos.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elasticsearch-serverless-aws-performance-boost</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elasticsearch-serverless-aws-performance-boost</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Pete Galeotti, Yuvraj Gupta, Rachel Forshee]]></dc:creator>
            <pubDate>Wed, 14 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3aa0b481994d2445ba979d3c79fff64c5ee6676a-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elastic Cloud Serverless ya es la solución definitiva para los desarrolladores que desean crear aplicaciones eficientes de búsqueda e inteligencia artificial sin la carga operativa que supone la gestión de la infraestructura. Ahora, estamos llevando el rendimiento de tus proyectos sin servidor a un nivel completamente nuevo.</p><p>Completamos una importante actualización de infraestructura para todos los proyectos de <a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a> que funcionan en AWS, al migrar a hardware más nuevo y rápido. Este cambio se ha implementado automáticamente en todos los proyectos sin servidor. Ofrece <strong>mayor rendimiento y menor latencia</strong> para proyectos serverless de Elasticsearch, Elastic Observability y Elastic Security en AWS.</p><h2><strong>Beneficios clave de rendimiento para desarrolladores</strong></h2><p>La nueva infraestructura de hardware de AWS sustenta todo lo que haces con Elastic Cloud Serverless, lo que se traduce en beneficios tangibles para la velocidad y la capacidad de respuesta de tus aplicaciones.</p><h3><strong>Latencia de consulta reducida… rendimiento aumentado.</strong></h3><p>El hardware mejorado aumenta drásticamente la velocidad de los recursos informáticos, lo que significa que tus consultas de búsqueda se procesan más rápido que nunca.</p><ul><li><strong>Búsqueda y búsqueda vectorial:</strong> ya sea que estés ejecutando búsquedas de texto tradicionales o empleando una búsqueda vectorial de vanguardia para tus <a href="https://www.elastic.co/generative-ai">aplicaciones de inteligencia artificial generativa y RAG</a>, verás una marcada disminución en la latencia. La evaluación interna mostró una disminución promedio del 35% en la latencia de búsqueda.</li><li><strong>Indexación más rápida:</strong> Las tasas de ingesta de datos están optimizadas, lo que te permite indexar volúmenes masivos de datos y documentos complejos con mayor rendimiento. Esto es crucial para las aplicaciones que requieren visibilidad de datos casi en tiempo real. La evaluación comparativa interna mostró un aumento promedio del 26% en el rendimiento al indexar.</li></ul><h3><strong>Rendimiento constante bajo carga</strong></h3><p>Elastic Cloud Serverless está diseñado para escalar dinámicamente en tiempo real y satisfacer la demanda, lo que minimiza la latencia, independientemente de tu carga de trabajo. Gracias a esta actualización de hardware, ahora el escalado es más eficaz y ofrece una mayor capacidad de respuesta.</p><ul><li><strong>Manejar los picos con facilidad:</strong> ya sea que te enfrentes a un aumento repentino en el tráfico de usuarios o a una ingesta masiva de datos batch, la nueva infraestructura garantiza que tus recursos de búsqueda e indexación se escalen de manera más eficiente para mantener una latencia constantemente baja.</li><li><strong>Desacoplamiento optimizado de computación y almacenamiento:</strong> La arquitectura serverless separa computación y almacenamiento, lo que permite que las cargas de trabajo escalen de forma independiente para lograr un rendimiento óptimo y eficiencia de costos. El hardware más rápido mejora la capa de cómputo, lo que maximiza la eficiencia de este diseño desacoplado.</li></ul><h2><strong>Por dentro: Resultados de evaluación comparativa interna</strong></h2><p>Para cuantificar el impacto de la actualización de nuestra infraestructura de AWS, el equipo de ingeniería de Elastic llevó a cabo una exhaustiva evaluación comparativa interna con una serie de cargas de trabajo sin servidor. Estas cargas de trabajo proporcionaron evidencia empírica de mejoras de rendimiento que puedes esperar en todas tus aplicaciones, independientemente de tu caso de uso.</p><h3><strong>El enfoque comparativo</strong></h3><p>Centramos nuestras pruebas en las métricas clave que afectan directamente a la experiencia de los desarrolladores y a la capacidad de respuesta de las aplicaciones: el tiempo de respuesta (es decir, la latencia) y el rendimiento en las operaciones de búsqueda e indexación.</p><ul><li><strong>Cargas de trabajo probadas:</strong> Las pruebas incluyeron operaciones de búsqueda de alta concurrencia típicas de las aplicaciones orientadas al usuario, consultas de búsqueda vectorial complejas y la ingesta/indexación de grandes volúmenes de datos para casos de uso de observabilidad y seguridad. En concreto, nuestra metodología de pruebas utilizó <a href="https://github.com/elastic/rally-tracks/tree/master">sets de datos disponibles</a> <a href="https://github.com/elastic/rally-tracks/tree/master">públicamente para Rally</a>, la herramienta de evaluación comparativa de Elastic.<ul><li><a href="https://github.com/elastic/rally-tracks/tree/3bedd51/wikipedia"><code>wikipedia</code></a>: Un conjunto de datos derivado de un snapshot del contenido textual de Wikipedia, para medir el rendimiento de la búsqueda de texto de propósito general.</li><li><a href="https://github.com/elastic/rally-tracks/tree/3bedd51/msmarco-passage-ranking"><code>MSMARCO-Passage-Ranking</code></a>: Un conjunto de datos derivado de la comprensión de lectura automática de Microsoft (MS MARCO), para medir el rendimiento de búsqueda en campos vectoriales dispersos.</li><li><a href="https://github.com/elastic/rally-tracks/tree/3bedd51/openai_vector"><code>OpenAI_Vector</code></a>: Un set de datos derivado del NQ de BEIR y enriquecido con incrustaciones generadas por el modelo <code>text-embedding-ada-002</code> de OpenAI, para medir el rendimiento de búsqueda en campos vectoriales densos.</li></ul></li><li><strong>Medición:</strong> Comparamos el rendimiento en la infraestructura antigua y nueva, al medir la latencia en el percentil 99 (P99) para capturar el peor de los casos, el rendimiento de latencia en la cola y las operaciones por segundo. Cada pista se ejecutó cinco veces para cada perfil de hardware para garantizar la consistencia en los resultados.</li><li><strong>El objetivo:</strong> nuestro objetivo era validar la capacidad de la infraestructura para ofrecer <strong>un rendimiento más rápido y predecible</strong> de forma constante en todos los ámbitos, incluso durante los periodos de autoescalado rápido.</li></ul><h3><strong>Resumen de datos de rendimiento</strong></h3><p>Los resultados confirman un aumento significativo en la eficiencia y la velocidad. Estas ganancias se traducen directamente en tiempos de respuesta más bajos para tus usuarios y menores costos operativos como resultado de la capacidad de completar la misma cantidad de trabajo con menos recursos de cómputo.</p><p>Las siguientes tablas detallan las mejoras cuantitativas. Los valores más altos son mejores para el rendimiento; los valores más bajos son mejores para la latencia.</p><p><strong>Búsqueda de resultados del índice de referencia:</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Benchmark</td><td style="border: 1px solid #ccc; padding: 4px;">Comparación</td><td style="border: 1px solid #ccc; padding: 4px;">Infraestructura antigua</td><td style="border: 1px solid #ccc; padding: 4px;">Nueva infraestructura</td><td style="border: 1px solid #ccc; padding: 4px;">Diferencial</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`wikipedia` (texto sin formato)</td><td style="border: 1px solid #ccc; padding: 4px;">Rendimiento de operaciones de búsqueda (ops/s)</td><td style="border: 1px solid #ccc; padding: 4px;">729</td><td style="border: 1px solid #ccc; padding: 4px;">1107</td><td style="border: 1px solid #ccc; padding: 4px;">+52 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`wikipedia` (texto sin formato)</td><td style="border: 1px solid #ccc; padding: 4px;">Latencia de la operación de búsqueda (p99, ms)</td><td style="border: 1px solid #ccc; padding: 4px;">56</td><td style="border: 1px solid #ccc; padding: 4px;">35</td><td style="border: 1px solid #ccc; padding: 4px;">-37 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`MSMARCO-Passage-Ranking` (vectores dispersos)</td><td style="border: 1px solid #ccc; padding: 4px;">Rendimiento de operaciones de búsqueda (ops/s)</td><td style="border: 1px solid #ccc; padding: 4px;">22</td><td style="border: 1px solid #ccc; padding: 4px;">31</td><td style="border: 1px solid #ccc; padding: 4px;">+40 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`MSMARCO-Passage-Ranking` (vectores dispersos)</td><td style="border: 1px solid #ccc; padding: 4px;">Latencia de la operación de búsqueda (p99, ms)</td><td style="border: 1px solid #ccc; padding: 4px;">108</td><td style="border: 1px solid #ccc; padding: 4px;">67</td><td style="border: 1px solid #ccc; padding: 4px;">-38 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`OpenAI_Vector` (vectores densos)</td><td style="border: 1px solid #ccc; padding: 4px;">Rendimiento de operaciones de búsqueda (ops/s)</td><td style="border: 1px solid #ccc; padding: 4px;">475</td><td style="border: 1px solid #ccc; padding: 4px;">624</td><td style="border: 1px solid #ccc; padding: 4px;">+31 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`OpenAI_Vector` (vectores densos)</td><td style="border: 1px solid #ccc; padding: 4px;">Latencia de la operación de búsqueda (p99, ms)</td><td style="border: 1px solid #ccc; padding: 4px;">35</td><td style="border: 1px solid #ccc; padding: 4px;">22</td><td style="border: 1px solid #ccc; padding: 4px;">-37 %</td>
                    </tr>
              </tbody>
            </table>
          <p><strong>Resultados de referencia de indexación:</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Benchmark</td><td style="border: 1px solid #ccc; padding: 4px;">Comparación</td><td style="border: 1px solid #ccc; padding: 4px;">Infraestructura antigua</td><td style="border: 1px solid #ccc; padding: 4px;">Nueva infraestructura</td><td style="border: 1px solid #ccc; padding: 4px;">Diferencial</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`wikipedia` (texto sin formato)</td><td style="border: 1px solid #ccc; padding: 4px;">Rendimiento de operaciones de búsqueda (ops/s)</td><td style="border: 1px solid #ccc; padding: 4px;">2845</td><td style="border: 1px solid #ccc; padding: 4px;">3220</td><td style="border: 1px solid #ccc; padding: 4px;">+13 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`wikipedia` (texto sin formato)</td><td style="border: 1px solid #ccc; padding: 4px;">Latencia de la operación de búsqueda (p99, ms)</td><td style="border: 1px solid #ccc; padding: 4px;">1769</td><td style="border: 1px solid #ccc; padding: 4px;">1120</td><td style="border: 1px solid #ccc; padding: 4px;">-37 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`MSMARCO-Passage-Ranking` (vectores dispersos)</td><td style="border: 1px solid #ccc; padding: 4px;">Rendimiento de operaciones de búsqueda (ops/s)</td><td style="border: 1px solid #ccc; padding: 4px;">7087</td><td style="border: 1px solid #ccc; padding: 4px;">8900</td><td style="border: 1px solid #ccc; padding: 4px;">+26 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`MSMARCO-Passage-Ranking` (vectores dispersos)</td><td style="border: 1px solid #ccc; padding: 4px;">Latencia de la operación de búsqueda (p99, ms)</td><td style="border: 1px solid #ccc; padding: 4px;">824</td><td style="border: 1px solid #ccc; padding: 4px;">677</td><td style="border: 1px solid #ccc; padding: 4px;">-18 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`OpenAI_Vector` (vectores densos)</td><td style="border: 1px solid #ccc; padding: 4px;">Rendimiento de operaciones de búsqueda (ops/s)</td><td style="border: 1px solid #ccc; padding: 4px;">2972</td><td style="border: 1px solid #ccc; padding: 4px;">3187</td><td style="border: 1px solid #ccc; padding: 4px;">+7 %</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`OpenAI_Vector` (vectores densos)</td><td style="border: 1px solid #ccc; padding: 4px;">Latencia de la operación de búsqueda (p99, ms)</td><td style="border: 1px solid #ccc; padding: 4px;">2946</td><td style="border: 1px solid #ccc; padding: 4px;">2944</td><td style="border: 1px solid #ccc; padding: 4px;">0 %</td>
                    </tr>
              </tbody>
            </table>
          <h2><strong>La ventaja adicional: reducción de costos</strong></h2><p>Aunque nuestro objetivo es ofrecer un rendimiento de baja latencia, la eficiencia del nuevo hardware también tiene un impacto directo y positivo en los costos de los proyectos de Elasticsearch.</p><p><a href="https://www.elastic.co/pricing/serverless-search">El precio de Elasticsearch Serverless</a> se basa en el uso, lo que significa que solo pagas por los recursos de ingesta y búsqueda que consumes. Debido a que el hardware más nuevo y rápido es más eficiente, tus cargas de trabajo a menudo completarán tareas empleando menos recursos, lo que genera una reducción de costos inherente para la mayoría de los proyectos. Obtendrás un aumento de rendimiento superior sin un precio premium: la definición de eficiencia optimizada.</p><h2><strong>¿Qué significa esto para ti, el desarrollador?</strong></h2><p>Esta actualización de infraestructura está gestionada íntegramente por Elastic, así que no tienes que mover un dedo: no hay migraciones ni cambios de configuración. La mejora es inmediata y automática en todos tus proyectos serverless basados en AWS.</p><p>Esta actualización te permite:</p><ul><li><strong>Crea aplicaciones más rápidas:</strong> concéntrate en la velocidad de las características, sabiendo que tu plataforma de búsqueda subyacente ofrece la velocidad que exigen tus usuarios.</li><li><strong>Innova con confianza:</strong> despliega nuevas características de búsqueda, observabilidad y seguridad, incluidas capacidades complejas de IA, como búsqueda vectorial y clasificación de relevancia, con la seguridad de que la Platform puede manejar la carga al máximo rendimiento.</li><li><strong>Simplifica tu stack:</strong> Usa un servicio totalmente gestionado que gestione la infraestructura, la planificación de la capacidad y el escalado, para que puedas centrarte en tu código y datos.<br/></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Automatización del análisis de logs en Streams con ML.]]></title>
            <description><![CDATA[Descubre cómo un enfoque híbrido de ML logró un 94 % de precisión en el análisis de logs y un 91 % en la partición de logs mediante experimentos de automatización con huellas digitales de formato de registro en Streams.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/log-parsing-partitioning-automation-experiments-streams</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/log-parsing-partitioning-automation-experiments-streams</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Nastia Havriushenko]]></dc:creator>
            <pubDate>Fri, 02 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/965c58f19742361160593c38fcaa8b2f4b0d6cc5-3838x2159.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>En las pilas de observabilidad modernas, la ingesta de logs no estructurados de diversos proveedores de datos en plataformas como Elasticsearch sigue siendo un reto. La dependencia de reglas de análisis creadas manualmente crea pipelines frágiles, donde incluso las actualizaciones menores de código ascendente conducen a fallas en los análisis y datos no indexados. Esta fragilidad se agrava con el desafío de la escalabilidad: en entornos dinámicos de microservicios, la incorporación continua de nuevos servicios convierte el mantenimiento manual de las reglas en una pesadilla operativa.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9108ec303339dd091faa3c363c7cf5c228155f49-3840x2160.png" style="width: 100%;" /><p>Nuestro objetivo era pasar a un enfoque automatizado y adaptable capaz de gestionar tanto el análisis de logs (extracción de campos) como la partición de logs (identificación de las fuentes). Planteamos la hipótesis de que los modelos de lenguaje grandes (LLMs), con su comprensión inherente de la sintaxis del código y los patrones semánticos, podrían automatizar estas tareas con una intervención humana mínima.</p><p>¡Nos complace anunciar que esta característica ya está disponible en <a href="http://elastic.co/elasticsearch/streams"><span style="text-decoration:underline">Streams</span></a>!</p><h2>Descripción de los sets de datos</h2><p>Elegimos un conjunto de logs <strong><a href="https://github.com/logpai/loghub">Loghub</a> </strong>para fines de prueba de concepto (POC). Para nuestra investigación, seleccionamos muestras representativas de las siguientes áreas clave:</p><ul><li>Sistemas distribuidos: utilizamos los sets de datos HDFS (Hadoop Distributed File System) y Spark. Estas contienen una mezcla de mensajes de información, depuración y error típicos de las plataformas de big data.</li><li>Aplicaciones de servidor y web: los logs de los servidores web Apache y OpenSSH proporcionaron una fuente de acceso, error y eventos relevantes para la seguridad. Son fundamentales para supervisar el tráfico web y detectar posibles amenazas.</li><li>Sistemas operativos: incluimos logs de Linux y Windows. Estos sets de datos representan los eventos comunes y semiestructurados a nivel de sistema que los equipos de operaciones encuentran a diario.</li><li>Sistemas móviles: para asegurarnos de que nuestro modelo pueda manejar logs de entornos móviles, incluimos los sets de datos de Android. Estos logs suelen ser muy detallados y recogen una amplia gama de actividades a nivel de aplicación y de sistema en los dispositivos móviles.</li><li>Supercomputadoras: para probar el rendimiento en entornos de computación de alto rendimiento (HPC), incorporamos el conjunto de datos BGL (Blue Gene/L), que presenta logs altamente estructurados con terminología específica de dominio.</li></ul><p>Una ventaja clave de la colección Loghub es que los logs están en gran medida sin depurar y sin etiquetar, lo que refleja un entorno de producción en vivo ruidoso con arquitectura de microservicios.</p><p>Ejemplos de logs:</p><pre><code>[Sun Dec 04 20:34:21 2005] [notice] jk2_init() Found child 2008 in scoreboard slot 6
[Sun Dec 04 20:34:25 2005] [notice] workerEnv.init() ok /etc/httpd/conf/workers2.properties
[Mon Dec 05 11:06:51 2005] [notice] workerEnv.init() ok /etc/httpd/conf/workers2.properties
17/06/09 20:10:58 INFO output.FileOutputCommitter: Saved output of task 'attempt_201706092018_0024_m_000083_1138' to hdfs://10.10.34.11:9000/pjhe/test/1/_temporary/0/task_201706092018_0024_m_000083
17/06/09 20:10:58 INFO mapred.SparkHadoopMapRedUtil: attempt_201706092018_0024_m_000083_1138: Committed</code></pre><p>Además, creamos un clúster Kubernetes con una aplicación web típica y una base de datos configurada para extraer logs adicionales en el dominio más común.</p><p>Ejemplo de campos comunes de log: marca de tiempo, nivel de log (INFO, WARN, ERROR), fuente, mensaje.</p><h2>Análisis de logs con pocos ejemplos con un LLM</h2><p>Nuestro primer conjunto de experimentos se centró en una pregunta fundamental: ¿<strong>puede un LLM identificar de manera confiable los campos clave y generar reglas de análisis consistentes para extraerlos?</strong></p><p>Pedimos a un modelo que analizara muestras de log sin procesar y generara reglas de análisis de log en formatos de expresión regular (regex) y <a href="https://www.elastic.co/docs/explore-analyze/scripting/grok">Grok</a>. Nuestros resultados demostraron que este enfoque tiene un gran potencial, pero también plantea desafíos importantes de implementación.</p><h3>Alta confianza y conciencia del contexto</h3><p>Los resultados iniciales fueron prometedores. El LLM demostró una gran capacidad para generar reglas de análisis sintáctico que coincidieran con los pocos ejemplos proporcionados con un alto grado de confianza. Además de la simple comparación de patrones, el modelo demostró su capacidad para comprender los logs: podía identificar y nombrar correctamente la fuente del log (por ejemplo, la aplicación de seguimiento de salud, la aplicación web Nginx, la base de datos Mongo).</p><h3>El dilema &quot;Goldilocks&quot; de las muestras de entrada</h3><p>Nuestros experimentos revelaron rápidamente una falta significativa de solidez debido a la extrema<strong> sensibilidad a la muestra de entrada.</strong> El rendimiento del modelo fluctúa drásticamente en función de los ejemplos específicos de logs que se incluyan en el prompt. Observamos un problema similar de log en el que la muestra debe incluir logs <em>lo suficientemente diversos </em>:</p><ul><li>Demasiado homogéneo (sobreajuste)<strong>:</strong> si los registros de entrada son demasiado similares, el LLM tiende a <strong>sobreespecificar</strong>. Trata datos variables (como nombres de clases Java específicas en un seguimiento de pila) como partes estáticas de la plantilla. Esto genera reglas frágiles que cubren una proporción pequeña de logs y extraen campos inutilizables.</li><li>Demasiado heterogéneo (confusión): por el contrario, si la muestra contiene una variación significativa de formato, o peor aún, &quot;trash logs&quot; como barras de progreso, tablas de memoria o arte ASCII, el modelo tendrá dificultades para encontrar un denominador común. Suele recurrir a generar regexes complejos y rotos o a generalizar descuidadamente toda la línea en un solo campo de mensajes.</li></ul><h3>La restricción de la ventana de contexto</h3><p>También encontramos un cuello de botella en la ventana de contexto. Cuando los logs de entrada eran largos, heterogéneos o ricos en campos extraíbles, la salida del modelo solía deteriorarse, y se &quot;desordenaba&quot; o era demasiado larga para caber en la ventana de contexto de salida. Naturalmente, la fragmentación (chunking) ayuda en este caso. Al dividir los logs mediante delimitadores basados en caracteres y entidades, podríamos ayudar al modelo a centrarse en extraer los campos principales sin sentirse abrumado por el ruido.</p><h3>La brecha de consistencia y estandarización</h3><p>Incluso cuando el modelo genera reglas correctamente, notamos ligeras inconsistencias:</p><ul><li>Variaciones en la denominación de servicios: el modelo propone diferentes nombres para la misma entidad (por ejemplo, etiquetar la fuente como &quot;Spark&quot;, &quot;Apache Spark&quot; y &quot;Spark Log Analytics&quot; en diferentes ejecuciones).</li><li>Variaciones en los nombres de los campos: los nombres de los campos carecían de estandarización (p. ej.: <code>id</code> vs. <code>service.id</code> vs. <code>device.id</code>). Normalizamos los nombres al utilizar una <a href="https://www.elastic.co/docs/reference/ecs/ecs-field-reference">nomenclatura de campos de Elastic</a> estandarizada.</li><li>Variación de la resolución: la resolución de la extracción de campos variaba en función del grado de similitud entre los logs de entrada.</li></ul><h2>Huella digital en formato de log</h2><p>Para abordar el reto de la similitud de logs, presentamos una heurística de alto rendimiento: <strong>la huella digital en formato de log (LFF)</strong>.</p><p>En lugar de alimentar logs crudos y ruidosos directamente a un LLM, primero aplicamos una transformación determinista para revelar la estructura subyacente de cada mensaje. Este paso de preprocesamiento abstrae los datos de las variables y genera una &quot;huella digital&quot; simplificada que nos permite agrupar logs relacionados.</p><p>La lógica de mapping es sencilla para garantizar la velocidad y la consistencia:</p><ol><li>Abstracción de dígitos: cualquier secuencia de dígitos (0-9) se reemplaza por un solo &quot;0&quot;.</li><li>Abstracción de texto: cualquier secuencia de caracteres alfabéticos con espacios en blanco se reemplaza por una sola &quot;a&quot;.</li><li>Normalización de espacios en blanco: todas las secuencias de espacios en blanco (espacios, tabulaciones, saltos de línea) se colapsan en un solo espacio.</li><li>Conservación de símbolos: se conservan los signos de puntuación y los caracteres especiales (por ejemplo: :, [, ], /), ya que suelen ser los indicadores más claros de la estructura del log.</li></ol><p>Presentamos el enfoque de mapping de logs. Los patrones básicos de mapping incluyen lo siguiente:</p><ul><li>Dígitos de 0-9 de cualquier longitud -&gt; a &quot;0&quot;.</li><li>Texto (caracteres alfabéticos con espacios) de cualquier longitud -&gt; a &quot;a&quot;.</li><li>Espacios en blanco, tabulaciones y líneas nuevas -&gt; a un solo espacio.</li></ul><p>Veamos un ejemplo de cómo este mapping nos permite transformar los logs.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/78fa2887486eb9417804354ee3bf2a4fdb0f6383-846x252.png" style="width: 100%;" /><p>Como resultado, obtenemos las siguientes máscaras de log:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ec439a3d3a25002498b97defcff733ea5ebc6b55-826x94.png" style="width: 100%;" /><p>Mira las huellas dactilares de los dos primeros logs. A pesar de las diferentes marcas de tiempo, clases de origen y contenido de mensajes, sus prefijos (<code>0/0/0 0:0:0 a a.a:</code>) son idénticos. Esta alineación estructural nos permite agrupar automáticamente estos logs en el mismo clúster.</p><p>Sin embargo, el tercer log produce una huella digital completamente divergente (<code>0-0-0...</code>). Esto nos permite separarlo algorítmicamente del primer grupo <em>antes</em> de que invoquemos un LLM.</p><h2>Parte adicional: Implementación instantánea con ES|QL</h2><p>Es tan fácil como ingresar esta búsqueda en Discover.</p><pre><code>FROM loghub |
EVAL pattern = REPLACE(REPLACE(REPLACE(REPLACE(raw_message, "[ \t\n]+", " "), "[A-Za-z]+", "a"), "[0-9]+", "0"), "a( a)+", "a") |
STATS total_count = COUNT(), ratio = COUNT() / 2000.0, datasources=VALUES(filename), example=TOP(raw_message, 3, "desc") BY SUBSTRING(pattern, 0, 15) |
SORT total_count DESC |
LIMIT 100</code></pre><p><strong>Desglose de la búsqueda:</strong></p><p><strong>DE</strong> loghub: Se dirige a nuestro índice que contiene los datos de log sin procesar.</p><p><strong>EVAL</strong> pattern = …: La lógica de mapping del núcleo. Encadenamos las funciones REPLACE para realizar la abstracción (por ejemplo, dígitos a &#x27;0&#x27;, texto a &#x27;a&#x27;, etc.) y guardamos el resultado en un campo de “patrón”.</p><p><strong>STATS</strong>[columna1 =] expresión1, …<strong> POR </strong>SUBSTRING(patrón, 0, 15):</p><p>Este es un paso de agrupación. Agrupamos logs que comparten los primeros 15 caracteres de su patrón y creamos campos agregados como recuento total de logs por grupo, lista de fuentes de datos de log, prefijo de patrón, 3 ejemplos de log</p><p><strong>SORT</strong> total_count DESC | <strong>LIMIT</strong> 100: Destaca los 100 patrones log más frecuentes</p><p>A continuación, se muestran los resultados de la búsqueda en LogHub:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b119498f124376c41d242a099bf9081fd6536be8-1600x394.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4dcfc0a5b7fa753497cc5def5ea3cd54449c0481-1600x719.png" style="width: 100%;" /><p>Como aparece en la visualización, este enfoque “sin LLM” particiona los logs con alta precisión. Agrupó con éxito 10 de las 16 fuentes de datos (basadas en etiquetas de LogHub) por completo (&gt;90 %) y logró un agrupamiento mayoritario en 13 de 16 fuentes (&gt;60 %), todo ello sin necesidad de realizar una limpieza, preprocesamiento o ajustes finos adicionales.</p><p>El formato de huella de log ofrece una alternativa pragmática y de alto impacto, además de soluciones sofisticadas de ML como el <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-categorize-text-aggregation">Análisis de patrones de log</a>. Aporta información inmediata sobre las relaciones entre los log y gestiona de forma eficaz grandes grupos de logs.</p><ul><li>Versatilidad como primitiva </li></ul><p>Gracias a la implementación de <a href="https://www.elastic.co/blog/getting-started-elasticsearch-query-language">ES|QL</a>, LFF sirve como una herramienta independiente para diagnósticos y visualizaciones de datos rápidos y como un componente básico en los pipelines de análisis de registros para casos de uso de gran volumen. </p><ul><li>Flexibilidad</li></ul><p>LFF es fácil de personalizar y ampliar para capturar patrones específicos, es decir, números hexadecimales y direcciones IP.</p><ul><li>Estabilidad determinista</li></ul><p>A diferencia de los algoritmos de agrupamiento basados en ML, la lógica LFF es sencilla y determinista. Los nuevos logs entrantes no afectan retroactivamente a los grupos de logs existentes.</p><ul><li>Rendimiento y mMemory</li></ul><p>Requiere memoria mínima, sin entrenamiento ni GPU, lo que lo hace ideal para entornos de alto rendimiento en tiempo real.</p><h2>Combinación de la huella digital del formato de log con un LLM</h2><p>Para validar la arquitectura híbrida propuesta, cada experimento contenía un subconjunto aleatorio del 20 % de los logs de cada fuente de datos. Esta restricción simula un entorno de producción real donde los logs se procesan en lotes en lugar de como un vertido histórico monolítico.</p><p>El objetivo era demostrar que el LFF actúa como una capa de compresión eficaz. Nuestro objetivo fue demostrar que las reglas de análisis de alta cobertura podrían generarse a partir de muestras pequeñas y seleccionadas, y generalizarse con éxito a todo el set de datos.</p><h2>Pipeline de ejecución</h2><p>Implementamos un pipeline de varias etapas que filtra, agrupa y aplica un muestreo estratificado a los datos antes de que lleguen al LLM.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b3f46ea471760b406a32fc7d4bc74cc03faaced2-3840x1660.png" style="width: 100%;" /><p>1. Agrupamiento jerárquico en dos etapas</p><ul><li>Subclases (coincidencia exacta): los logs se agregan por huellas idénticas. Cada log en una subclase comparte exactamente la misma estructura de formato.</li><li>Limpieza de valores atípicos. Descartamos cualquier subclase que represente menos del 5 % del volumen total del log. Esto asegura que el LLM se centre en la señal dominante y no se desvíe por ruido o logs mal formados.</li><li>Metaclases (coincidencia de prefijo): las subclases restantes se agrupan en metaclases por los primeros caracteres N del formato de coincidencia de huellas dactilares. Esta estrategia de agrupación divide efectivamente formatos léxicamente similares bajo un mismo paraguas. Elegimos N=5 para el análisis de log y N=15 para la partición de log cuando las fuentes de datos son desconocidas.</li></ul><p>2. Muestreo estratificado. Una vez construido el árbol jerárquico, construimos la muestra de log para el LLM. El objetivo estratégico es maximizar la cobertura de las variaciones y minimizar el uso de tokens.</p><ul><li>Seleccionamos logs representativos de <em>cada</em> subclase válida dentro de la metaclase más amplia.</li><li>Para gestionar un caso extremo de demasiadas subclases, aplicamos un muestreo aleatorio reducido para ajustarse al tamaño de la ventana objetivo.</li></ul><p>3. Generación de reglas. Finalmente, le pedimos al LLM que genere una regla de análisis de regex que se ajuste a todos los logs de la muestra proporcionada para cada metaclase. Para nuestra prueba de concepto, utilizamos el modelo GPT-4o mini.</p><h2>Resultados experimentales y observaciones</h2><p>Logramos una precisión de análisis del 94 % y una precisión de partición del 91 % en los sets de datos de Loghub.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/49b2b6a1401dd1f33951da68e5a3fac37d0b5aaa-1600x1506.png" style="width: 100%;" /><p>La matriz de confusión anterior ilustra los resultados de la partición de logs. El eje vertical representa las fuentes de datos reales, y el eje horizontal representa las fuentes de datos previstas. La intensidad del mapa de calor corresponde al volumen de logs, con mosaicos más ligeros que indican un recuento más alto. La alineación diagonal demuestra la alta fidelidad del modelo en la atribución de fuentes, con una dispersión mínima.</p><h2>Nuestra información sobre los parámetros de rendimiento:</h2><ul><li><strong>Línea de base óptima:</strong> una ventana de contexto de <strong>30–40 muestras de log</strong> por categoría demostró ser el &quot;punto óptimo&quot;, que produce consistentemente un análisis robusto con patrones tanto de Regex como de Grok.</li><li><strong>Minimización de entrada:</strong> pusimos el tamaño de entrada a 10 logs por categoría para los patrones Regex y observamos solo una caída del 2 % en el rendimiento de análisis, lo que confirma que el muestreo basado en la diversidad es más crítico que el volumen bruto.</li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Introducción a los modelos de Jina, su funcionalidad y usos en Elasticsearch]]></title>
            <description><![CDATA[Explora las incrustaciones multimodales de Jina, Reranker v3 y los modelos de incrustación semántica, y aprende cómo usarlos de forma nativa en Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/jina-models-elasticsearch-guide</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/jina-models-elasticsearch-guide</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Scott Martens]]></dc:creator>
            <pubDate>Thu, 01 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/407b4c862b51ebdfc7f26db4e25950a65caf1673-656x442.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Jina de Elastic proporciona modelos fundamentales de búsqueda para aplicaciones y una automatización de procesos empresariales. Estos modelos proporcionan funcionalidad del núcleo para llevar la IA a las aplicaciones de Elasticsearch y a los proyectos innovadores de IA.</p><p>Los modelos de Jina se dividen en tres amplias categorías diseñadas para apoyar el procesamiento, la organización y la recuperación de información:</p><ul><li>Modelos de incrustación semántica</li><li>Modelos de reordenamiento</li><li>Pequeños modelos de lenguaje generativo</li></ul><h2>Modelos de incrustación semántica</h2><p>La idea detrás de las incrustaciones semánticas es que un modelo de IA puede aprender a representar aspectos del significado de sus entradas en términos de la geometría de espacios de alta dimensión.</p><p>Puedes pensar en una incrustación semántica como un punto (técnicamente un <em>vector</em>) en un espacio de alta dimensión. Un modelo de incrustación es una red neuronal que toma algunos datos digitales como entrada (potencialmente cualquier cosa, pero con mayor frecuencia un texto o una imagen) y genera la ubicación de un punto de alta dimensión correspondiente como un conjunto de coordenadas numéricas. Si el modelo funciona bien, la distancia entre dos incrustaciones semánticas es proporcional a la medida en que sus objetos digitales correspondientes significan lo mismo.</p><p>Para entender cómo esto es importante para las aplicaciones de búsqueda, imagina una incrustación para la palabra “perro” y una para la palabra “gato” como puntos en el espacio:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/802cf9bbcb82180d3fc91009f9f62027eee8f031-615x615.png" style="width: 100%;" /><p>Un buen modelo de incrustación debe generar una para la palabra “felino” que esté mucho más cerca de “gato” que de “perro”, y “canino” debe tener una incrustación mucho más cercana a “perro” que de “gato”, porque esas palabras significan casi lo mismo:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bce49daf9a31b8fb7ce1c6ef7ae4e8117a4e8b33-615x615.png" style="width: 100%;" /><p>Si un modelo es multilingüe, esperaríamos lo mismo para las traducciones de “gato” y “perro”:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ce4d030385324526cbd7539140e0e634d939371c-615x615.png" style="width: 100%;" /><p>Los modelos de incrustación traducen la similitud o diferencia en el significado entre las cosas en relaciones espaciales entre incrustaciones. Las imágenes anteriores solo tienen dos dimensiones para que puedas verlas en una pantalla, pero los modelos de incrustación producen vectores con docenas o miles de dimensiones. Esto les permite codificar sutilezas de significado para textos completos y asignar un punto en un espacio que tenga cientos o miles de dimensiones para documentos de miles de palabras o más.</p><h2>Incrustaciones multimodales</h2><p>Los modelos multimodales amplían el concepto de incrustaciones semánticas a otros elementos, además de los textos, especialmente a las imágenes. Esperaríamos que una incrustación para una imagen esté cerca de una incrustación de una descripción fiel de la imagen:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1ac6aef5b1423e5fe4853e8a547a74e66b0885c2-615x615.png" style="width: 100%;" /><p>Las incrustaciones semánticas tienen muchos usos. Entre otras cosas, puedes utilizarlos para crear clasificadores eficientes, agrupar datos y llevar a cabo una variedad de tareas, como la deduplicación de datos y la investigación de la diversidad de datos, ambas importantes para aplicaciones de big data que implican trabajar con demasiados datos para gestionarlos manualmente.</p><p>El mayor uso directo de las incrustaciones es en la recuperación de información. Elasticsearch puede almacenar objetos de recuperación con incrustaciones como claves. Las consultas se convierten en vectores de incrustación y una búsqueda devuelve los objetos almacenados cuyas claves son las más cercanas a la incrustación de la consulta.</p><p>Donde la recuperación tradicional <em>basada en vectores</em> (a veces llamada <em>recuperación de vectores dispersos</em>) utiliza vectores basados en palabras o <em>metadatos</em> en documentos y búsquedas, la <em>recuperación basada en incrustaciones</em> (también conocida como recuperación de vectores densos) utiliza significados evaluados por la IA en lugar de palabras. Esto los hace, en general, mucho más flexibles y precisos que los métodos de búsqueda tradicionales.</p><h2>Aprendizaje de representación de Matryoshka</h2><p>El número de dimensiones que tiene una incrustación y la precisión de los números que contiene tienen un impacto significativo en el rendimiento. Los espacios de muy alta dimensión y los números extremadamente de alta precisión pueden representar información muy detallada y compleja, pero exigen modelos de IA más grandes que sean más caros de entrenar y de ejecutar. Los vectores que generan requieren más espacio de almacenamiento y se necesitan más ciclos de computación para calcular las distancias entre ellos. El uso de modelos de incrustación semántica implica hacer compensaciones importantes entre la precisión y el consumo de recursos.</p><p>Para maximizar la flexibilidad para los usuarios, los modelos de Jina se entrenan con una técnica llamada <a href="https://arxiv.org/abs/2205.13147">Aprendizaje de representación de Matryoshka</a>. Esto hace que los modelos carguen las distinciones semánticas más importantes en las primeras dimensiones del vector de incrustación para que simplemente puedas cortar las dimensiones superiores y aún obtener un buen rendimiento.</p><p>En la práctica, esto significa que los usuarios de los modelos de Jina pueden elegir cuántas dimensiones desean que tengan sus incrustaciones. Elegir menos dimensiones reduce la precisión, pero la degradación en el rendimiento es menor. En la mayoría de las tareas, las métricas de rendimiento de los modelos de Jina disminuyen entre un 1 % y un 2 % cada vez que reduces el tamaño de la incrustación en un 50 %, hasta aproximadamente una reducción del 95 % en el tamaño.</p><h2>Recuperación asimétrica</h2><p>La similitud semántica usualmente se mide de manera simétrica. El valor que obtienes al comparar “gato” con “perro” es el mismo que el valor que obtendrías al comparar “perro” con “gato”. Pero cuando usas incrustaciones para la recuperación de información, funcionan mejor si rompes la simetría y codificas las búsquedas de manera diferente a como codificas los objetos de recuperación.</p><p>Esto se debe a la forma en que entrenamos los modelos de incrustación. Los datos de entrenamiento contienen ejemplos de los mismos elementos, como palabras, en muchos contextos diferentes, y los modelos aprenden semántica al comparar las similitudes y diferencias contextuales entre los elementos.</p><p>Entonces, por ejemplo, podríamos encontrar que la palabra “animal” no aparece en muchos de los mismos contextos que “gato” o “perro”, y por lo tanto, la incrustación para “animal” podría no estar particularmente cerca de “gato” o “perro”:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9a33163405af6c71ee7f4ba8ebc86af39e295a69-615x615.png" style="width: 100%;" /><p>Esto hace que sea menos probable que una búsqueda de &quot;animal&quot; recupere documentos sobre gatos y perros, lo cual es lo opuesto a nuestro objetivo. Así que, en su lugar, codificamos &quot;animal&quot; de forma diferente cuando es una consulta que cuando es un objetivo para la recuperación:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/363992d4f0affba7937c0c8a9f82c9a531fcd3ba-615x615.png" style="width: 100%;" /><p>La <em>recuperación asimétrica</em> significa usar un modelo diferente para las búsquedas o entrenar especialmente un modelo de incrustación para codificar cosas de una manera cuando se almacenan para su recuperación y para codificar consultas de otra manera.</p><h2>Incrustaciones multivectoriales</h2><p>Las incrustaciones únicas son buenas para la recuperación de información porque se ajustan al marco de trabajo básico de una base de datos indexada: almacenamos objetos para su recuperación con un único vector de incrustación como clave de recuperación. Cuando los usuarios consultan el almacén de documentos, sus búsquedas se traducen en vectores de incrustación y los documentos cuyas claves están más cercanas a la incrustación de búsquedas (en el espacio de incrustación de alta dimensión) se recuperan como posibles coincidencias.</p><p>Las incrustaciones multivectoriales funcionan un poco diferente. En lugar de generar un vector de longitud fija para representar una búsqueda y un objeto almacenado completo, producen una secuencia de incrustaciones que representan partes más pequeñas de ellos. Las partes suelen ser tokens o palabras para textos y son mosaicos de imagen para datos visuales. Estas incrustaciones reflejan el significado de la parte en su contexto.</p><p>Por ejemplo, considera estas oraciones:</p><ul><li>Ella tenía un corazón de oro.</li><li>Ella cambió de opinión con el corazón.</li><li>Ella tuvo un ataque al corazón.</li></ul><p>Superficialmente, se ven muy similares, pero un modelo multivectorial probablemente generaría incrustaciones muy diferentes para cada instancia de &quot;corazón&quot;, representando cómo cada una significa algo diferente en el contexto de la oración completa:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a33e60c8d8ee3d312bca8375ca2a8b0a0cd40ba9-615x615.png" style="width: 100%;" /><p>La comparación de dos objetos a través de sus incrustaciones multivectoriales a menudo implica medir su distancia de chaflán: comparar cada parte de una incrustación multivectorial con cada parte de otra y sumar las distancias mínimas entre ellas. Otros sistemas, incluidos los Jina Rerankers que se describen a continuación, los incluyen en un modelo de IA entrenado específicamente para evaluar su similitud. Ambos enfoques suelen tener mayor precisión que la simple comparación de incrustaciones de un solo vector, ya que las incrustaciones multivectoriales contienen información mucho más detallada que las de un solo vector.</p><p>Sin embargo, las incrustaciones multivectoriales no son adecuadas para indexar. A menudo se utilizan en tareas de reclasificación, como se describe para el modelo de <code>jina-colbert-v2</code> en la siguiente sección.</p><h2>Modelos de incrustación de Jina</h2><h3>Jina embeddings v4</h3><p><a href="https://jina.ai/news/jina-embeddings-v4-universal-embeddings-for-multimodal-multilingual-retrieval/"><strong>jina-embeddings-v4</strong></a> es un modelo de incrustación multilingüe y multimodal de 3.8 mil millones (3.8×10⁹) de parámetros que admite imágenes y textos en una variedad de idiomas ampliamente utilizados. Utiliza una arquitectura novedosa para aprovechar el conocimiento visual y el conocimiento del lenguaje para mejorar el rendimiento en ambas tareas, lo que le permite sobresalir en la recuperación de imágenes y especialmente en la recuperación <a href="https://huggingface.co/tasks/visual-document-retrieval">visual de documentos</a>. Esto significa que maneja imágenes como gráficos, diapositivas, mapas, capturas de pantalla, escaneos de páginas y diagramas, tipos comunes de imágenes que a menudo contienen texto importante incrustado y que quedan fuera del alcance de los modelos de visión artificial entrenados con imágenes de escenas del mundo real.</p><p>Hemos optimizado este modelo para varias tareas diferentes a través de <a href="https://huggingface.co/docs/peft/en/package_reference/lora">adaptadores de Low-Rank Adaptation (LoRA)</a> compactos. Esto nos permite entrenar un único modelo para que se especialice en varias tareas, sin comprometer el rendimiento en ninguna de ellas, con un costo adicional mínimo en memoria o procesamiento.</p><p>Las características principales incluyen las siguientes:</p><ul><li>Rendimiento de vanguardia en la recuperación visual de documentos, junto con texto multilingüe e imágenes regulares que superan significativamente a modelos mucho más grandes.</li><li>El soporte para un gran tamaño de contexto de entrada: 32.768 tokens equivale aproximadamente a 80 páginas de texto en inglés a doble espacio, y 20 megapíxeles equivalen a una imagen de 4.500 x 4.500 píxeles.</li><li>Tamaños de incrustación seleccionados por el usuario, desde un máximo de 2048 dimensiones hasta 128 dimensiones. Descubrimos empíricamente que el rendimiento se degrada significativamente por debajo de ese umbral.</li><li>Soporte para ambas incrustaciones simples y multivector. En el caso de los textos, la salida multivectorial consiste en una incrustación de 128 dimensiones para cada token de entrada. Para las imágenes, produce una incrustación de 128 dimensiones para cada mosaico de 28x28 píxeles necesario para cubrir la imagen.</li><li>Optimización para la recuperación asimétrica mediante un par de adaptadores LoRA entrenados específicamente para tal fin.</li><li>Un adaptador LoRA optimizado para el cálculo de similitud semántica.</li><li>Soporte especial para lenguajes de programación informática y marcos de trabajo de TI, también a través de un adaptador LoRA.</li></ul><p>Desarrollamos <code>jina-embeddings-v4</code> para servir como una herramienta general y multipropósito para una amplia variedad de tareas de búsqueda común, comprensión del lenguaje natural y análisis de IA. Es un modelo relativamente pequeño teniendo en cuenta sus capacidades, pero su despliegue requiere recursos considerables y es más adecuado para su uso a través de una API en la nube o en un entorno de gran volumen.</p><h3>Jina embeddings v3</h3><p><a href="https://jina.ai/news/jina-embeddings-v3-a-frontier-multilingual-embedding-model/"><strong>jina-embeddings-v3</strong></a> es un modelo de incrustación compacto, de alto rendimiento, multilingüe y solo de texto con menos de 600 millones de parámetros. Admite hasta 8192 tokens de entrada de texto y genera incrustaciones de vector único con tamaños elegidos por el usuario, desde un valor predeterminado de 1024 dimensiones hasta 64.</p><p>Capacitamos a <code>jina-embeddings-v3</code> para una variedad de tareas de texto. No solo para la recuperación de información y la similitud semántica, sino también para tareas de clasificación, como análisis de sentimiento y moderación de contenido, así como tareas de agrupar, como agregación de noticias y recomendaciones. Al igual que <code>jina-embeddings-v4</code>, este modelo proporciona adaptadores LoRA especializados para las siguientes categorías de uso:</p><ul><li>Recuperación asimétrica</li><li>Similitud semántica</li><li>Clasificación</li><li>Agrupación</li></ul><p><code>jina-embeddings-v3</code> es un modelo mucho más pequeño que <code>jina-embeddings-v4</code> con un tamaño de contexto de entrada significativamente reducido, pero su funcionamiento cuesta menos. No obstante, tiene un rendimiento muy competitivo, aunque solo para textos, y es una mejor opción para muchos casos de uso.</p><h3>Inmercaciones del código Jina</h3><p>Los modelos especializados de incrustación de código de Jina, <a href="https://jina.ai/models/jina-code-embeddings-1.5b"><strong>jina-code-embeddings (0.5b y 1.5b)</strong></a>, admiten 15 esquemas y marcos de trabajo de programación, así como textos en inglés relacionados con la informática y la tecnología de la información. Son modelos compactos con quinientos millones (0.5x10⁹) y mil quinientos millones (1.5x10⁹) de parámetros, respectivamente. Ambos modelos admiten tamaños de contexto de entrada de hasta 32.768 tokens y permiten a los usuarios seleccionar los tamaños de su incrustación de salida, desde 896 hasta 64 dimensiones para el modelo más pequeño y 1536 hasta 128 para el modelo más grande.</p><p>Estos modelos admiten la recuperación asimétrica para cinco especializaciones específicas de tareas, mediante el <a href="https://arxiv.org/abs/2101.00190">ajuste de prefijos</a> en lugar de adaptadores LoRA:</p><ul><li><strong>Código a código.</strong> Recupera un código similar en todos los lenguajes de programación. Esto se utiliza para la alineación de códigos, deduplicación de códigos y soporte para la traslación y refactorización.</li><li><strong>Lenguaje natural para programar.</strong> Recupera códigos para hacer coincidir consultas, comentarios, descripciones y documentación en lenguaje natural.</li><li><strong>Código a lenguaje natural. </strong>Haz coincidir el código con la documentación u otros textos en lenguaje natural.</li><li><strong>Finalización de código a código.</strong> Sugiere un código relevante para completar o mejorar el código existente.</li><li><strong>Preguntas y respuestas técnicas.</strong> Identifica las respuestas en lenguaje natural a las preguntas sobre tecnologías de la información, que son ideales para casos de uso de soporte técnico.</li></ul><p>Estos modelos ofrecen un rendimiento superior para tareas relacionadas con documentación informática y materiales de programación con un costo computacional relativamente bajo. Son muy adecuados para integrarse en entornos de desarrollo y asistentes de código.</p><h3>Jina ColBERT v2</h3><p><a href="https://jina.ai/models/jina-colbert-v2"><strong>jina-colbert-v2</strong></a> es un modelo de incrustación de texto multivectorial de 560 millones de parámetros. Es multilingüe, entrenado con materiales en 89 idiomas, y soporta tamaños de incrustación variables y recuperación asimétrica.</p><p>Como se ha señalado anteriormente, las incrustaciones multivectoriales no son adecuadas para la indexación, pero resultan muy útiles para aumentar la precisión de los resultados de otras estrategias de búsqueda. Mediante <code>jina-colbert-v2</code><strong>,</strong> puedes calcular incrustaciones multivector de antemano y luego usarlas para reclasificar candidatos de recuperación al momento de la búsqueda. Este enfoque es menos preciso que usar uno de los modelos de reclasificación en la siguiente sección, pero es mucho más eficiente porque simplemente implica comparar incrustaciones multivectoriales almacenadas en lugar de invocar todo el modelo de IA para cada búsqueda y coincidencia posible. Es ideal para casos de uso en los que la latencia y la sobrecarga computacional que supone el uso de modelos de reclasificación son demasiado grandes, o cuando el número de candidatos que comparar es demasiado elevado para los modelos de reclasificación.</p><p>Este modelo genera una secuencia de incrustaciones, una por token de entrada, y los usuarios pueden seleccionar incrustaciones de token de 128, 96 o 64 dimensiones. Las coincidencias de texto de candidatos están limitadas a 8,192 tokens. Las búsquedas se codifican de manera asimétrica, por lo que los usuarios deben especificar si un texto es una búsqueda o una coincidencia candidata y deben limitar las búsquedas a 32 tokens.</p><h3>Jina CLIP v2</h3><p><a href="https://jina.ai/news/jina-clip-v2-multilingual-multimodal-embeddings-for-text-and-images/"><strong>Jina-Clip-V2</strong></a> es un modelo de incrustación multimodal de 900 millones de parámetros, y está capacitado para que los textos e imágenes produzcan incrustaciones muy cercanas si el texto describe el contenido de la imagen. Su uso principal es para recuperar imágenes basadas en consultas de textura, pero también es un modelo de solo texto de alto rendimiento, lo que reduce los costos de usuario porque no necesitas modelos separados para la recuperación de texto a texto y de texto a imagen.</p><p>Este modelo admite un contexto de entrada de texto de 8,192 tokens, y las imágenes se escalan a 512x512 píxeles antes de generar incrustaciones.</p><p>Las arquitecturas de preentrenamiento de lenguaje-imagen contrastivo (CLIP) son fáciles de entrenar y operar y pueden producir modelos muy compactos, pero tienen algunas limitaciones fundamentales. No pueden utilizar los conocimientos adquiridos en un medio para mejorar su rendimiento en otro. No pueden usar un medio para mejorar su rendimiento en otro. Así que, aunque pueda saber que las palabras &quot;perro&quot; y &quot;gato&quot; están más cercanas en significado que a &quot;auto&quot;, no necesariamente sabrá que una foto de un perro y una de un gato están más relacionadas que cualquiera de las dos con una foto de un auto.</p><p>También sufren de lo que se llama la <em>brecha de modalidad</em>: es probable que una incrustación de un texto sobre perros esté más cerca de una incrustación de un texto sobre gatos que de una incrustación de una imagen de perros. Debido a esta limitación, te recomendamos utilizar CLIP como modelo de recuperación de texto a imagen o como modelo de solo texto, pero sin mezclar los dos en una sola búsqueda.</p><h2>Modelos de reordenamiento</h2><p>Los modelos de reclasificación toman una o más coincidencias candidatas, junto con una consulta como entrada al modelo, y las comparan directamente, produciendo coincidencias de mucha mayor precisión.</p><p>En principio, podrías usar un reranker directamente para la recuperación de información al comparar cada búsqueda con cada documento almacenado, pero esto sería muy costoso desde el punto de vista computacional y no es práctico para cualquier colección excepto para las más pequeñas. Como resultado, los rerankers tienden a usarse para evaluar listas relativamente cortas de coincidencias de candidatos encontradas por otros medios, como la búsqueda basada en incrustaciones u otros algoritmos de recuperación. Los modelos de reclasificación son ideales para esquemas de búsqueda híbridos y federados, donde realizar una búsqueda puede significar que las consultas se envían a sistemas de búsqueda separadas con conjuntos de datos distintos, cada una devolviendo resultados distintos. Funcionan muy bien al combinar resultados diversos en un único resultado de alta calidad.</p><p>La búsqueda basada en incrustaciones puede ser un gran compromiso, ya que implica reindexar todos tus datos almacenados y cambiar las expectativas del usuario sobre los resultados. Agregar un reranker a un esquema de búsqueda existente puede sumar muchos de los beneficios de la IA sin necesidad de rediseñar toda tu solución de búsqueda.</p><h2>Modelos de reordenación de Jina</h2><h3>Jina Reranker m0</h3><p><a href="https://jina.ai/models/jina-reranker-m0/"><strong>jina-reranker-m0</strong></a> es un reranker multimodal de 2.4 mil millones (2.4x10⁹) de parámetros que admite consultas textuales y coincidencias candidatas que consisten en textos y/o imágenes. Es el modelo líder en recuperación visual de documentos, lo que lo convierte en una solución ideal para almacenar archivos PDF, escaneos de texto, capturas de pantalla y otras imágenes generadas o modificadas por computadora que contengan texto u otra información semiestructurada, así como datos mixtos que consistan en documentos de texto e imágenes.</p><p>Este modelo toma una búsqueda única y una coincidencia candidata y devuelve una puntuación. Cuando la misma consulta se usa con diferentes candidatos, las puntuaciones son comparables y pueden usarse para clasificarlas. Soporta un tamaño total de entrada de hasta 10 240 tokens, incluido el texto de la consulta y el texto o imagen candidata. Cada mosaico de 28x28 píxeles necesario para cubrir una imagen cuenta como un token para calcular el tamaño de entrada.</p><h3>Jina Reranker v3</h3><p><a href="https://jina.ai/models/jina-reranker-v3/"><strong>jina-reranker-v3</strong></a> es un reranker de texto de 600 millones de parámetros con rendimiento de vanguardia para modelos de tamaño comparable. A diferencia de <code>jina-reranker-m0</code>, toma una sola búsqueda y una lista de hasta 64 candidatos coincidentes y devuelve el orden de clasificación. Tiene un contexto de entrada de 131 000 tokens, incluida la consulta y todos los candidatos de texto.</p><h3>Jina Reranker v2</h3><p><a href="https://jina.ai/models/jina-reranker-v2"><strong>jina-reranker-v2-base-multilingual</strong></a> es un reranker muy compacto y de uso general con características adicionales diseñadas para admitir llamadas a funciones y consultas SQL. Con un peso inferior a 300 millones de parámetros, proporciona una reclasificación de texto multilingüe rápido, eficiente y preciso, con soporte adicional para seleccionar tablas SQL y funciones externas que coincidan con consultas de texto, lo que lo hace adecuado para casos de uso de agentes.</p><h2>Pequeños modelos de lenguaje generativo</h2><p>Los modelos de lenguaje generativo son modelos como ChatGPT de OpenAI, Google Gemini y Claude de Anthropic que toman entradas de texto o multimedia y responden con salidas de texto. No existe una línea bien definida que separe los modelos de lenguaje <em>grandes</em> (LLM) de los modelos de lenguaje <em>pequeños</em> (SLM), pero los problemas prácticos de desarrollar, operar y utilizar LLM de primera línea son bien conocidos. Los más conocidos no se distribuyen públicamente, por lo que solo podemos estimar su tamaño, pero se espera que ChatGPT, Gemini y Claude estén en el rango de parámetros de 1 a 3 billones (1–3x10¹²).</p><p>Ejecutar estos modelos, incluso si están disponibles públicamente, está muy lejos del alcance del hardware convencional, que requiere los chips más avanzados dispuestos en grandes matrices paralelas. Puedes acceder a LLMs a través de API pagas, pero esto conlleva costos significativos, tiene una gran latencia y es difícil de alinear con las demandas de protección de datos, la soberanía digital y la repatriación en la nube. Además, los costos relacionados con la capacitación y la personalización de modelos de ese tamaño pueden ser considerables.</p><p>En consecuencia, se han realizado numerosas investigaciones para desarrollar modelos más pequeños que, aunque carecen de todas las capacidades de los LLM más grandes, pueden realizar determinados tipos de tareas con la misma eficacia y a un menor costo. Las empresas generalmente despliegan software para abordar problemas específicos, y el software de IA no es diferente, por lo que las soluciones basadas en SLM se suelen preferir a las de LLM. Por lo general, pueden ejecutarse en hardware básico, son más rápidos y consumen menos energía para ejecutarse, y son mucho más fáciles de personalizar.</p><p>Las ofertas de SLM de Jina están creciendo a medida que nos enfocamos en la mejor manera de llevar la IA a soluciones de búsqueda prácticas.</p><h2>Jina SLM</h2><h3>ReaderLM v2</h3><p><a href="https://jina.ai/models/ReaderLM-v2"><strong>ReaderLM-v2</strong></a> es un modelo de lenguaje generativo que convierte HTML en Markdown o JSON, según los esquemas JSON proporcionados por el usuario y las instrucciones en lenguaje natural.</p><p>El preprocesamiento y la normalización de datos son una parte esencial del desarrollo de buenas soluciones de búsqueda para datos digitales, pero los datos del mundo real, especialmente la información derivada de la web, suelen ser caóticos, y las estrategias de conversión simples a menudo resultan ser muy frágiles. En cambio, <code>ReaderLM-v2</code> ofrece una solución de modelo de IA inteligente que puede entender el caos de un volcado de árbol DOM de una página web e identificar de manera segura elementos útiles.</p><p>Con 1500 millones (1,5 x 10⁹) de parámetros, es tres órdenes de magnitud más compacto que los LLM de última generación, pero su rendimiento es similar al de estos en esta tarea específica.</p><h3>Jina VLM</h3><p><a href="https://jina.ai/models/jina-vlm"><strong>jina-vlm</strong></a> es un modelo de lenguaje generativo con 2400 millones (2,4 x 10⁹) de parámetros que está entrenado para responder preguntas en lenguaje natural sobre imágenes. Tiene un fuerte soporte para el análisis visual de documentos, es decir, responder preguntas sobre escaneos, capturas de pantalla, diapositivas, diagramas y datos similares de imágenes no naturales.</p><p>Por ejemplo:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/adfa1420d079ca4fd5582eef4349b1265b378e76-950x500.png" style="width: 100%;" /><p>También es muy bueno para leer texto en imágenes:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ea3956e7ad86f8e171841cab2c28c8b3498da1d4-1002x500.png" style="width: 100%;" /><p>Pero donde <code>jina-vlm</code> realmente se destaca es en entender el contenido de las imágenes informativas y creadas por el hombre:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f68606f9d2d99e2cd616d4ff81db3574dc4e26a5-1020x700.png" style="width: 100%;" /><p>O:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/297e85e7e78f296388a02301e1e08fed70827423-1000x500.png" style="width: 100%;" /><p><code>jina-vlm</code> es adecuado para la generación automática de subtítulos, descripciones de productos, texto alternativo de imágenes y aplicaciones de accesibilidad para personas con discapacidad visual. También crea posibilidades para que los sistemas de generación aumentada por recuperación (RAG) utilicen información visual y para que los agentes de IA procesen imágenes sin ayuda humana.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Primeros pasos con Elastic Agent Builder y Strands Agents SDK]]></title>
            <description><![CDATA[Aprende a crear un agente con Elastic Agent Builder y, a continuación, descubre cómo utilizar el agente a través del protocolo A2A orquestado con el SDK de Strands Agents.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/agent-builder-a2a-strands-agents-guide</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/agent-builder-a2a-strands-agents-guide</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jonathan Simon]]></dc:creator>
            <pubDate>Mon, 15 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/31b6a5c1c30dacbb4d5e58d1c566071e7143a0c8-1600x879.gif?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>¿Tienes una idea para un agente de IA? Probablemente implica hacer algo con datos porque, si un agente va a iniciar una acción útil, necesita tomar una decisión y necesita los datos correctos para tomar la decisión correcta.</p><p>Elastic Agent Builder facilita la creación de agentes de IA conectados a datos. Te mostraremos cómo hacerlo en esta publicación de blog. Veamos todos los pasos necesarios para crear un agente con una herramienta MCP que acceda a los datos almacenados en Elastic. Luego usaremos el SDK de Strands Agents y sus capacidades Agent2Agent (A2A) para operar el agente. El <a href="https://strandsagents.com/">SDK de Strands Agents</a> es una plataforma de desarrollo de IA multiagente que puedes usar para crear apps de agentes con el código justo para asegurarte de obtener el resultado que quieres.</p><p>Construyamos un agente de IA que juegue el juego de RPS+, que es una versión del clásico juego de &quot;Piedra, papel o tijeras&quot; con un toque adicional; les da a los jugadores del juego un par de opciones adicionales.</p><h2>Requisitos previos</h2><p>Aquí está lo que se requiere para seguir los pasos en esta publicación de blog:</p><ul><li>Un editor de texto ejecutándose en tu computadora local<ul><li><a href="https://code.visualstudio.com/download">Visual Studio Code</a> es lo que usaremos para las instrucciones de ejemplo en esta publicación de blog</li></ul></li><li><a href="https://www.python.org/downloads/">Python 3.10 o superior</a> funcionando en tu computadora local</li></ul><h2>Crea un proyecto Serverless</h2><p>Lo primero que necesitamos es un proyecto Elasticsearch Serverless, que incluye Elastic Agent Builder.</p><p>Ve a <a href="http://cloud.elastic.co/">cloud.elastic.co</a> y crea un nuevo proyecto de Elasticsearch Serverless.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/31b6a5c1c30dacbb4d5e58d1c566071e7143a0c8-1600x879.gif" style="width: 100%;" /><h2>Crea un índice y agrega datos</h2><p>A continuación, vamos a agregar algunos datos a nuestro proyecto de Elasticsearch. Abre Developer Tools, donde podemos ejecutar comandos para crear un nuevo índice e insertar algunos datos en él. Selecciona Herramientas para desarrolladores en el menú de navegación de nivel superior.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f97d5af077504463155655a9e27c171a7f974f71-1600x879.jpg" style="width: 100%;" /><p>Copia y pega el siguiente comando PUT en el área de entrada de solicitud de la consola de Developer Tools. Esta declaración crea un índice de Elasticsearch llamado “game-docs”.</p><pre><code>PUT /game-docs
{
  "mappings": {
    "properties": {
      "title": { "type": "text" },
      "content": { 
        "type": "text"
      },
      "filename": { "type": "keyword" },
      "last_modified": { "type": "date" }
    }
  }
}</code></pre><p>Haz clic en el botón <strong>Enviar solicitud</strong> que aparece en el lado derecho de la declaración en Herramientas para desarrolladores. Deberías ver una notificación que confirme que el índice <em>game-docs</em> se creó en el área de respuesta de Herramientas para desarrolladores.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/be0555a1930e4d4f58b7ed8b669c9b702532ed17-1600x880.jpg" style="width: 100%;" /><p>Un índice llamado <em>game-docs</em> es el lugar ideal para almacenar los datos del juego que estamos creando. Pongamos un documento llamado <em>rps+-md</em> en este índice que contiene todos los datos que requiere nuestro juego. Copia y pega el siguiente comando PUT en la consola de Herramientas para desarrolladores.</p><pre><code>PUT /game-docs/_doc/rps+-md
{
  "title": "Rock Paper Scissors +",
  "content": "
# Game Name
RPS+

# Starting Prompt
Let's play RPS+ !
---
What do you choose?

# Game Objects
1. Rock 🪨 👊
2. Paper 📜 🖐
3. Scissors ✄ ✌️
4. Light ☼ 👍
5. Dark Energy ☄ 🫱

# Judgement of Victory
* Rock beats Scissors
  * because rocks break scissors
* Paper beats Rock
  * because paper covers rock
* Scissors beat Paper
  * because scissors cut paper
* Rock beats Light
  * because you can build a rock structure to block out light
* Paper beats Light
  * because knowledge stored in files and paper books helps us understand light
* Light beats Dark Energy
  * because light enables humans to lighten up and laugh in the face of dark energy as it causes the eventual heat death of the universe
* Light beats Scissors
  * because light is needed to use scissors safely
* Dark Energy beats Rock
  * because dark energy rocks more than rocks. It rocks rocks and everything else in its expansion of the universe
* Dark Energy beats Paper
  * because humans, with their knowledge stored in files and paper books, can't explain dark energy 
* Scissors beat Dark Energy
  * because a human running with scissors is darker than dark energy

# Invalid Input
I was hoping for an worthy opponent
  - but alas it appears that time has past
  - but alas there's little time for your todo list when [todo:fix this] is so vast

# Cancel Game
The future belongs to the bold. Goodbye..
",
  "filename": "RPS+.md",
  "last_modified": "2025-11-25T12:00:00Z"
}</code></pre><p>Haz clic en el <strong>botón Enviar solicitud</strong> junto a la instrucción para ejecutarlo y agregar el documento <em>rps+-md</em> al índice de documentación del juego.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3c01d8a4602de5c33337457591a388a4a4e3fad3-1600x879.jpg" style="width: 100%;" /><p>Ahora deberíamos tener algunos datos para consultar, y con Agent Builder, eso es más simple que nunca.</p><p>Selecciona <strong>Agentes</strong> en el menú de navegación superior.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/82dbd2e9a439cabd5a5eea3d0ce005b87df0c3ea-1600x879.jpg" style="width: 100%;" /><p>Luego, todo lo que tienes que hacer es preguntarle al agente de Elastic AI predeterminado: &quot;¿Qué datos tengo?&quot;.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f7a2f39c9d1486bdf02d9e88a732b540ac2e2cd1-1600x872.gif" style="width: 100%;" /><p>El agente de Elastic AI evalúa los datos y devuelve una explicación concisa de los datos que tenemos.</p><h2>Crear una herramienta</h2><p>Listo, ahora tenemos algunos datos en Elastic; vamos a ponerlos en práctica. Agent Builder incluye soporte integrado para crear herramientas <a href="https://modelcontextprotocol.io/">MCP</a> que ayudan a los agentes a acceder a los datos que necesitan para tener el contexto adecuado para su tarea. Vamos a crear una herramienta sencilla que recupere los datos de nuestro juego.</p><p>Haz clic en el menú de acciones de Agent Builder.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0e327c202674dda33bcc0e494d2b588fa8b32e4f-1600x879.png" style="width: 100%;" /><p>Selecciona <strong>Ver todas las herramientas </strong>en las opciones del menú.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1ebf58650e9fb56750d3f0b1700fab50b44f9bdf-1600x879.png" style="width: 100%;" /><p>Haz clic en <strong>+ Nueva herramienta.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6c03a7f28b99ac2d805f34f39948979893316a00-1600x879.png" style="width: 100%;" /><p>En el formulario <strong>Crear herramienta</strong>, selecciona <strong><a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql">ES|QL</a> </strong>como <strong>Tipo</strong> de herramienta e ingresa los siguientes valores.</p><p><strong>Para el ID de la herramienta</strong>:</p><pre><code>example.get_game_docs</code></pre><p>Para la <strong>Descripción</strong>:</p><pre><code>Get RPS+ doc from Elasticsearch game-docs index.</code></pre><p>Para <strong>la configuración, </strong>ingresa la siguiente consulta en el área de texto <strong>Consulta ES|QL </strong> :</p><pre><code>FROM game-docs | WHERE filename == "RPS+.md"</code></pre><p>Tu formulario completado de <strong>Crear herramienta</strong> debería verse así. Haz clic en <strong>Guardar</strong> para crear la herramienta.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b6c93e344600f319b9d2c3030020cf2d171ac1c4-1600x1312.png" style="width: 100%;" /><p>Tenemos una nueva herramienta disponible en el estante de herramientas. Las herramientas no deberían estar colgadas perpetuamente en un estante; hay que darles un uso digno. Creemos un agente que pueda emplear nuestra nueva herramienta personalizada.</p><h2>Crea un agente y asígnale una herramienta</h2><p>Crear un agente es sorprendentemente sencillo con Agent Builder. Solo tienes que ingresar las instrucciones del agente con algunos detalles y eso es todo lo que necesitas. Vamos a crear un agente ahora.</p><p>Haz <strong>clic en Gestionar agentes.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/53934b93db07187e251d4b321cb9ca647e2fd51b-1600x858.png" style="width: 100%;" /><p>Haz clic en<strong> + Nuevo agente.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fae3ad8f31e71a6dfd044e1daa025a4e280b4e68-1600x490.png" style="width: 100%;" /><p>Introduce la siguiente información en el formulario <strong>Nuevo agente</strong>.</p><p>Para <strong>ID de agente, </strong>ingresa el texto a continuación:</p><pre><code>rps_plus_agent</code></pre><p>En el área de texto de <strong>Instrucciones personalizadas</strong> , ingresa las siguientes instrucciones:</p><pre><code>When prompted, if the prompt contains an integer, then select the corresponding numbered item in the list of "Game Objects" from your documents. Otherwise select a random game object. This is your chosen game object for a single round of the game.

# General Game Rules
* 2 players
    - the user: the person playing the game
    - you: the agent playing the game and serving as the game master
* Each player chooses a game object which will be compared and cause them to tie, win or lose.

# Start the game
1. This is the way each new game always starts. You make the first line of your response only the name of your chosen game object. 

2. The remainder of your response should be the "Starting Prompt" text from your documents and generate a list of "Game Objects" for the person playing the game to choose a game object from.  

# End of Game: The game ends in one of the following three outcomes:
1. Invalid Input: If the player responds with an invalid game object choice, respond with variations of the "Invalid Input" text from your documents and then end the game.

2. Tie: The game ends in a tie if the user chooses the same game object as your game object choice.

3. Win or Lose: The game winner is decided based on the "Judgement of Victory" conditions from your documents. Compare the user's game object choice and your game object choice and determine who chose the winning game object.

# Game conclusion
Respond with a declaration of the winner of the game by outputting the corresponding text in the "Judgement of Victory" section of your documents.</code></pre><p>Para el <strong>Nombre de visualización, </strong>ingresa el texto a continuación:</p><pre><code>RPS+ Agent</code></pre><p>Para la <strong>Descripción de la pantalla, </strong>ingresa el texto a continuación:</p><pre><code>An agent that plays the game RPS+</code></pre><p>Brinda al agente la herramienta personalizada que creamos previamente al hacer clic en la pestaña <strong>Herramientas</strong>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1778f64bc3a1b4004998dc3668ef7f666788e193-1600x1390.png" style="width: 100%;" /><p>Selecciona solo la herramienta <em>example.get_game_docs</em> que creamos anteriormente.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7d734cd80161bcc058817482eb330ffcf1cb567b-1600x1363.png" style="width: 100%;" /><p>Haz clic en <strong>Guardar</strong> para crear el nuevo agente.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c0014faf605ce50c03679ed0d073bd9f3ae7234d-1600x468.png" style="width: 100%;" /><p>Probemos nuestro nuevo agente. Hay un enlace útil para iniciar un chat con cualquier agente de la lista de agentes.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b7d6943ad90a4f68691207caf66b81742e712145-1600x560.png" style="width: 100%;" /><p>Simplemente ingresa “iniciar juego” y el juego comenzará. ¡Funciona!</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/984d008e4cc3f08cc1f101720673b0f7347c066c-1600x874.gif" style="width: 100%;" /><p>Puedes ver que el agente muestra su elección de objeto de juego en la parte superior de su respuesta. Esto es útil porque podemos ver la elección del agente y confirmar que el juego funciona como se espera. Sin embargo, conocer la elección de tu oponente antes de elegir no lo convierte en un juego muy divertido de &quot;Piedra, papel o tijeras&quot;. Para pulir y perfeccionar el juego hasta su forma final, podemos usar una plataforma de orquestación de agentes que pueda controlar a los agentes con código.</p><p>El SDK de Strands Agents entra al chat.</p><h2>SDK de agentes de Strands</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c195bba6ff2754f5d8fda174a0c1d247bc283710-456x156.png" style="width: 100%;" /><p>Si tienes curiosidad por probar nuevos marcos de trabajo de desarrollo de agentes, entonces vale la pena probar el <a href="https://strandsagents.com/latest/">SDK de Strands Agents</a>. El <a href="https://aws.amazon.com/blogs/opensource/introducing-strands-agents-an-open-source-ai-agents-sdk/">SDK de Strands Agents fue lanzado por AWS (mayo de 2025)</a> como una implementación de open source <a href="https://github.com/strands-agents/sdk-python">Python</a>, y ahora también hay una versión en <a href="https://dev.to/aws/strands-agents-now-speaks-typescript-a-side-by-side-guide-12b3">Typescript</a>.</p><h2>Introducción al SDK de Strands Agents en Python</h2><p>Enciende tus motores de programación, ahora vamos a pasar rápidamente por el proceso de clonación y ejecución de una app de ejemplo que usa Strands Agents para controlar el agente de <em>RPS+</em> mediante el protocolo A2A. Vamos a crear una versión ajustada del juego RPS+ para que la elección del agente se revele después de que hagas tu elección, porque, después de todo, es la adivinación y el resultado sorpresa lo que hace que juegos como &quot;Piedra, papel o tijeras&quot; sean divertidos.</p><p>En tu computadora local, abre <a href="https://code.visualstudio.com/download">Visual Studio Code</a> y abre una nueva terminal.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2339cc37c89a3524f2b2a21684bc61dae958e1cf-915x460.jpg" style="width: 100%;" /><p>En la terminal recién abierta, ejecuta el siguiente comando para clonar el repositorio de Elasticsearch Labs:</p><pre><code>git clone https://github.com/elastic/elasticsearch-labs</code></pre><p>Ejecuta el siguiente comando <em>cd </em>para cambiar el directorio al directorio elasticsearch-labs:</p><pre><code>cd elasticsearch-labs</code></pre><p>A continuación, ejecuta el siguiente comando para abrir el repositorio en Visual Studio Code:</p><pre><code>code .</code></pre><p>En el Explorador de archivos de Visual Studio, expande las carpetas <em>supporting-blog-content</em> y <em>agent-builder-a2a-strands-agents</em> y luego abre el archivo <em>elastic_agent_builder_a2a_rps+.py.</em> Así es como se ve el archivo abierto en Visual Studio Code:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d153b19e0e016c701576edb99ccab5af7c554f34-1484x1530.jpg" style="width: 100%;" /><p>Aquí tienes el contenido de <em>elastic_agent_builder_a2a_rps+.py </em>que deberías ver en tu editor de texto:</p><pre><code>import asyncio
from dotenv import load_dotenv
from uuid import uuid4
import httpx
import os
import random
from a2a.client import A2ACardResolver, ClientConfig, ClientFactory
from a2a.types import Message, Part, Role, TextPart

DEFAULT_TIMEOUT = 60  # set request timeout to 1 minute


def create_message(*, role: Role = Role.user, text: str, context_id=None) -> Message:
    return Message(
        kind="message",
        role="user",
        parts=[Part(TextPart(kind="text", text=text))],
        message_id=uuid4().hex,
        context_id=context_id,
    )


async def main():
    load_dotenv()
    a2a_agent_host = os.getenv("ES_AGENT_URL")
    a2a_agent_key = os.getenv("ES_API_KEY")
    custom_headers = {"Authorization": f"ApiKey {a2a_agent_key}"}

    async with httpx.AsyncClient(
        timeout=DEFAULT_TIMEOUT, headers=custom_headers
    ) as httpx_client:
        # Get agent card
        resolver = A2ACardResolver(httpx_client=httpx_client, base_url=a2a_agent_host)
        agent_card = await resolver.get_agent_card(
            relative_card_path="/rps_plus_agent.json"
        )
        # Create client using factory
        config = ClientConfig(
            httpx_client=httpx_client,
            streaming=True,
        )
        factory = ClientFactory(config)
        client = factory.create(agent_card)
        # Use the client to communicate with the agent
        print("\nSending 'start game' message to Elastic A2A agent...")
        random_game_object = random.randint(1, 5)
        msg = create_message(text=f"start with game object {random_game_object}")
        async for event in client.send_message(msg):
            if isinstance(event, Message):
                context_id = event.context_id
                response_complete = event.parts[0].root.text
                # Get agent choice from the first line of the response
                parsed_response = response_complete.split("\n", 1)
                agent_choice = parsed_response[0]
                print(parsed_response[1])
        # User choice sent for game results from the agent
        prompt = input("Your Choice  : ")
        msg = create_message(text=prompt, context_id=context_id)
        async for event in client.send_message(msg):
            if isinstance(event, Message):
                print(f"Agent Choice : {agent_choice}")
                print(event.parts[0].root.text)


if __name__ == "__main__":
    asyncio.run(main())</code></pre><p>Repasemos qué está pasando en este código. A partir del método <em><code>main()</code></em> , el código comienza accediendo a las variables de entorno para la URL del agente y la clave de API. Luego, usamos esos valores para crear un <code><em>httpx</em> client</code> que podamos usar para obtener la tarjeta de agente para el agente. El cliente luego usa los detalles de la tarjeta del agente para enviar una solicitud de &quot;iniciar juego&quot; al agente. Un aspecto interesante a tener en cuenta aquí es que incluimos un valor <code>random_game_object</code> como parte de la solicitud <code>&quot;start game&quot;</code>. Este valor es un número aleatorio generado con el módulo <em>aleatorio</em> de la biblioteca estándar de Python. La razón para hacerlo es que resulta que los poderosos LLM (que hacen posibles a los agentes de IA) no son muy buenos en la aleatoriedad. No hay problema, Python viene al rescate.</p><p>Continuando con la programación, una vez que el agente responde a la solicitud de &quot;iniciar juego&quot;, el código elimina la selección del objeto de juego del agente y lo guarda en la variable <em>agent_choice</em>. El resto de la respuesta se muestra como texto para el usuario final. Luego, se le solicita al usuario su entrada de elección de objeto de juego, la cual se envía al agente. El código muestra la elección del objeto del juego del agente junto con la determinación final del agente sobre el resultado del juego.</p><h2>Establecer la URL de tu agente y la clave de API como variables de entorno</h2><p>Dado que la app de ejemplo se ejecutará en tu computadora local, para comunicarla con nuestro agente Agent Builder, debemos proporcionar al SDK de Strands Agents una URL A2A y una clave API para el agente. La app de ejemplo emplea un archivo llamado <em>.env</em> para almacenar estos valores.</p><p>Haz una copia del <em>archivo env.example</em> y nombra el nuevo <em>archivo .env</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/25ead5f15a17dedb777132a082097cffb06cae4d-1600x843.jpg" style="width: 100%;" /><p>Vuelve a Elastic Agent Builder, donde podrás obtener los dos valores que necesitas.</p><p>Selecciona <strong>Ver todas las herramientas</strong> en el menú de acciones de Agent Builder en la parte superior derecha de la página.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9c4f4e4a3bd76e11e0a182fa007a2f6aec7777b4-1600x880.jpg" style="width: 100%;" /><p>Haz clic en el menú desplegable <strong>servidor MCP</strong> en la parte superior de la página Herramientas y selecciona <strong>Copiar URL del servidor MCP.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6cde0de678bb6f81bef8a59deffb110ad6c6ce26-1600x882.jpg" style="width: 100%;" /><p>Pega la <strong>URL del servidor MCP</strong> en el <em>archivo .env</em> como reemplazo para el valor de marcador de posición <strong>&lt;YOUR-ELASTIC-AGENT-BUILDER-URL&gt; </strong>. Ahora necesitamos hacer una actualización a la URL, es decir, reemplazar el texto final “mcp” con “a2a” porque el <a href="https://a2a-protocol.org/">protocolo A2A</a> es lo que usará el Agent Strands SDK para comunicarse con el agente que se ejecuta en Elastic Agent Builder.</p><p>La URL editada debería verse así:</p><pre><code>https://rps-game-project-12345a.kb.us-east-1.aws.elastic.cloud/api/agent_builder/a2a</code></pre><p>El otro valor que necesitamos obtener mientras estamos aquí en Elastic Cloud es una clave API. Haz clic en <strong>Elasticsearch en </strong>el menú de navegación superior.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/651676b9be65178cdad50b5d24f26441c0bf3f97-1600x549.jpg" style="width: 100%;" /><p>Haz clic en el <strong>botón Copiar clave API </strong>para copiar la clave API.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/17f1e2ed5c7682630c71e75b0b09ffb1d9036210-1600x879.jpg" style="width: 100%;" /><p>Ahora, de vuelta en Visual Studio Code, pega la clave API en el archivo <em>.env</em> para reemplazar el texto del marcador de posición <strong>&lt;YOUR-ELASTIC-API-KEY&gt; </strong>. Tu archivo <em>.env</em> debería verse algo así:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a357947e07f29c8c03382e00c7baedf04a399297-1600x286.jpg" style="width: 100%;" /><h2>Ejecuta la app de ejemplo</h2><p>Abre una nueva terminal en Visual Studio Code.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/33e1174c68ea1ed47c7fe62ab6a6da657c606f56-1413x711.jpg" style="width: 100%;" /><p>Empieza ejecutando el siguiente comando <em>cd</em> en la terminal:</p><pre><code>cd elasticsearch-labs/supporting-blog-content/agent-builder-a2a-strands-agents</code></pre><p>Ejecuta el siguiente comando para crear un entorno virtual en Python.</p><pre><code>python -m venv .venv</code></pre><p>Dependiendo del sistema operativo de tu computadora local, ejecuta el siguiente comando para activar el entorno virtual.</p><ul><li>MacOS/Linux</li></ul><pre><code>source .venv/bin/activate</code></pre><ul><li>Windows</li></ul><pre><code>.venv\Scripts\activate</code></pre><p>La app de ejemplo utiliza Strands Agents SDK, y ahora nos encontramos en el punto de este tutorial en el que debes instalarlo. Ejecuta el siguiente comando para instalar el SDK de Strands Agents junto con todas tus dependencias requeridas de la biblioteca de Python.</p><pre><code>pip install -r requirements.txt</code></pre><p>Es hora de limpiar la plataforma de lanzamiento y comenzar la cuenta regresiva. Estamos listos para lanzar esta app. Retírate. Vamos a ejecutarla usando el siguiente comando:</p><pre><code>python elastic_agent_builder_a2a_rps+.py</code></pre><p>Deberías desafiarte con un juego de RPS+. ¡Bien hecho y mucha suerte!</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/041df81fbf1776f09e1243af0a435c4c0af6aca1-1600x948.gif" style="width: 100%;" /><h2>Crea tus apps de IA con contexto relevante</h2><p>Crear un agente con IA es ahora una habilidad en tu caja de herramientas. Y has visto lo fácil que es usar agentes de Elastic Agent Builder a través de A2A en marcos de trabajo de agentes como Strands Agents SDK. <a href="https://cloud.elastic.co/registration?utm_source=agentic-ai-category&amp;utm_medium=search-labs&amp;utm_campaign=agent-builder">Prueba Elastic</a> para crear agentes de IA conectados al contexto relevante en tus datos personalizados.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Introducción del soporte de Elasticsearch en Google MCP Toolbox for Databases]]></title>
            <description><![CDATA[Descubre cómo el soporte de Elasticsearch ya está disponible en Google MCP Toolbox for Databases y aprovecha las herramientas ES|QL para integrar de forma segura tu índice con cualquier cliente MCP.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/google-mcp-toolbox-elasticsearch-support</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/google-mcp-toolbox-elasticsearch-support</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Enrico Zimuel, Laurent Saint-Félix]]></dc:creator>
            <pubDate>Fri, 12 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/425a48691f436ed47c9bdfaf5d561ac122b2c472-1062x668.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>En este artículo, explicaremos cómo usar Google MCP Toolbox con <a href="https://github.com/elastic/elasticsearch">Elasticsearch</a> para crear una herramienta sencilla que permita extraer información de un índice de Elasticsearch.</p><p>Recientemente contribuimos al proyecto open source <a href="https://github.com/googleapis/genai-toolbox">Google MCP Toolbox for Databases</a> agregando soporte para Elasticsearch como base de datos.</p><p>Con esta nueva característica, ahora puedes usar Google MCP Toolbox para conectarte a Elasticsearch y “conversar” directamente con tus datos.</p><h2>Elasticsearch</h2><p>Es necesario tener una instancia de Elasticsearch en funcionamiento. Puedes activar una prueba gratuita en <a href="https://www.elastic.co/cloud">Elastic Cloud</a> o instalarla localmente utilizando el script <a href="https://github.com/elastic/start-local">start-local</a>:</p><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre><p>Esto instalará Elasticsearch y Kibana en tu computadora y generará una clave API que se utilizará para configurar Google MCP Toolbox.</p><p>La clave de API se mostrará como salida del comando anterior y se almacenará en un archivo .env. en la carpeta elastic-start-local.</p><h2>Instala el set de datos de ejemplo</h2><p>Tras la instalación, puedes iniciar sesión en Kibana con el nombre de usuario <em>elastic</em> y la contraseña generada por el script start-local (almacenada en un archivo .env).</p><p>Puedes instalar el conjunto de datos de <strong>pedidos de comercio electrónico </strong>disponible desde Kibana. Incluye un único índice llamado <strong>kibana_sample_data_ecommerce</strong> que contiene información sobre 4675 pedidos de un sitio web de comercio electrónico. Para cada pedido, tenemos la siguiente información:</p><ul><li>Información del cliente (nombre, identificación, fecha de nacimiento, correo electrónico, etc.)</li><li>Fecha del pedido</li><li>ID de pedido</li><li>Productos (lista de todos los productos con precio, cantidad, identificación, categoría, descuento, etc.)</li><li>SKU</li><li>Precio total (sin impuestos, con impuestos)</li><li>Cantidad total</li><li>Información geográfica (ciudad, país, continente, ubicación, región)</li></ul><p>Para instalar los datos de muestra, abre la página <strong>Integraciones</strong> en Kibana (busca “Integración” en la barra superior de búsqueda) e instala los “Datos de muestra”. Para obtener más detalles, consulta la documentación aquí: <a href="https://www.elastic.co/docs/explore-analyze/#gs-get-data-into-kibana">https://www.elastic.co/docs/explore-analyze/#gs-get-data-into-kibana</a>.</p><p>El objetivo de este artículo es mostrar lo fácil que es configurar Google MCP Toolbox para conectarse a Elasticsearch e interactuar con el <strong>índice de kibana_sample_data_ecommerce</strong> usando lenguaje natural.</p><h2>Google MCP Toolbox</h2><p>Google MCP Toolbox es un servidor MCP open source diseñado para facilitar la interacción segura y eficiente de aplicaciones y agentes de IA con bases de datos. El proyecto, anteriormente conocido como el “GenAI Toolbox for Databases”, cambió su denominación después de adoptar la compatibilidad total con el <a href="https://www.anthropic.com/news/model-context-protocol">Protocolo de contexto de modelo</a> (MCP). Su propósito es eliminar el trabajo pesado que tradicionalmente se requiere al conectar agentes con bases de datos, gestionando la agrupación de conexiones, autenticación, observabilidad y otras preocupaciones operativas en segundo plano.</p><p>Esencialmente, Toolbox permite a los desarrolladores definir herramientas reutilizables de alto nivel que encapsulan las interacciones con la base de datos. Estas herramientas pueden ser invocadas por cualquier cliente compatible con MCP (como un agente de IA) sin requerir que el cliente implemente consultas SQL de bajo nivel o administre conexiones de base de datos. Este enfoque reduce drásticamente la cantidad de código repetitivo necesario para crear agentes compatibles con bases de datos, lo que permite integrar operaciones de datos avanzadas en solo unas pocas líneas de lógica de aplicación. Una vez definida una herramienta, se puede compartir entre varios agentes, marcos de trabajo o lenguajes (Figura 1).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/137cea290bb70ad5da21853f9a6358cef4cf7451-1248x1056.png" style="width: 100%;" /><p>Una de las ventajas principales de usar la Toolbox es el modelo de seguridad integrado. Los flujos de autenticación como OAuth2 y OIDC son compatibles de forma nativa, lo que permite a los desarrolladores evitar manejar o almacenar credenciales confidenciales de bases de datos en agentes. La plataforma también ofrece características de observabilidad (como métricas y rastreo) a través de OpenTelemetry, que es esencial para la depuración, la supervisión y los despliegues de producción. En conjunto, MCP Toolbox sirve como una interfaz unificada, segura y extensible para interactuar con tus datos desde cualquier sistema compatible con MCP.</p><h2>Cómo instalar MCP Toolbox</h2><p>Puedes instalar el servidor MCP Toolbox en Linux usando el siguiente comando:</p><pre><code>export VERSION=0.21.0
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox</code></pre><p>Si quieres instalarlo en macOS o Windows, puedes seguir las instrucciones <a href="https://googleapis.github.io/genai-toolbox/getting-started/introduction/#installing-the-server">detalladas aquí</a>.</p><h2>Configura la Toolbox para Elasticsearch</h2><p>Para configurar el MCP Toolbox para Elasticsearch, necesitamos crear un archivo <strong>tools.yaml</strong>, de la siguiente manera:</p><pre><code>sources:
  my-cluster:
    kind: elasticsearch
    addresses:
      - http://localhost:9200
    apikey: <insert-here-api-key>

tools:
  customer-orders:
    kind: elasticsearch-esql
    source: my-cluster
    description: Get the orders made by a customer identified by name.
    query: |
    	FROM kibana_sample_data_ecommerce | WHERE MATCH(customer_full_name, ?name, {"operator": "AND"})
    parameters:
      - name: name
        type: string
        description: The customer name.

toolsets:
  elasticsearch-tools:
    - customer-orders</code></pre><p>Debes reemplazar el valor <strong>&lt;insert-here-api-key&gt;</strong> por una clave API válida de Elasticsearch. Si estás ejecutando Elasticsearch localmente usando start-local, puedes encontrar la clave de API en el archivo.env generado por start-local, bajo la variable <strong>ES_LOCAL_API_KEY</strong> . Si usas Elastic Cloud, puedes generar una clave API siguiendo el procedimiento <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elastic-cloud-api-keys">descrito aquí</a>.</p><p>Las herramientas anteriores contienen la siguiente consulta ES|QL para Elasticsearch:</p><pre><code>FROM kibana_sample_data_ecommerce | WHERE MATCH(customer_full_name, ?name)</code></pre><p>Si no estás familiarizado con ES|QL, es un lenguaje de búsqueda desarrollado por Elastic, similar a SQL, que puedes usar para buscar en uno o más índices. Puedes leer más sobre ES|QL en la documentación oficial <a href="https://www.elastic.co/docs/reference/query-languages/esql">aquí</a>.</p><p>La búsqueda anterior busca todos los pedidos almacenados en el <strong>índice kibana_sample_data_ecommerce</strong> que contienen el nombre del cliente especificado, usando el parámetro <strong>?name</strong> (el signo de interrogación indica un parámetro).</p><p>El nombre del cliente se define en la configuración YAML anterior empleando el texto de tipo y la descripción &quot;El nombre del cliente&quot;.</p><p>Esta herramienta se puede usar para responder preguntas sobre los pedidos de un cliente, por ejemplo: <em>¿Cuántos pedidos realizó el cliente Foo en octubre de 2025?</em></p><p>Las descripciones de las herramientas y sus parámetros son esenciales para extraer la información relevante de la solicitud en lenguaje natural del usuario. Esta extracción se realiza utilizando la capacidad de <strong>llamada de función</strong> de un modelo de lenguaje grande (LLM). En la práctica, un LLM puede determinar qué función (herramienta) debe ejecutar para obtener la información necesaria, junto con los parámetros apropiados para esa función.</p><p>Para más información sobre las llamadas a funciones, sugerimos leer el artículo de <a href="https://www.elastic.co/search-labs/blog/function-calling-with-elastic">OpenAI sobre llamadas a funciones con Elasticsearch</a> de Ashish Tiwari.</p><h2>Ejecuta el servidor de Toolbox</h2><p>Puedes ejecutar la MCP Toolbox usando el archivo tools.yaml anterior con el siguiente comando:</p><pre><code>./toolbox --tools-file tools.yaml --ui</code></pre><p>El parámetro<strong> –ui</strong> ejecuta una aplicación sitio web en <a href="http://127.0.0.1:5000/ui">http://127.0.0.1:5000/ui</a> (Figura 2).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3caf2339b632bafd5847af1ed8b33b518a25b8a2-1600x314.png" style="width: 100%;" /><p>Puedes seleccionar la <strong>Herramientas</strong> &gt; <strong>pedidos-clientes</strong> e insertar un nombre de cliente en el parámetro <strong>nombre</strong> (por ejemplo, Gwen Sanders) y haz clic en el botón <strong>Ejecutar herramienta</strong>. Deberías ver una respuesta JSON como se indica en la Figura 3.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b167e0142afb8919d9cedf6d0fa431d33d0e55f8-1600x933.png" style="width: 100%;" /><p>La configuración se ha completado y MCP Toolbox puede ejecutar la herramienta de <strong>pedidos de clientes</strong> para comunicarse con Elasticsearch, ejecutando la consulta ES|QL.</p><h2>Usar la herramienta MCP Toolbox con Gemini CLI</h2><p>Podemos usar cualquier cliente del MCP para comunicarnos con MCP Toolbox for Database. Por ejemplo, podemos usar <a href="https://github.com/google-gemini/gemini-cli">Gemini CLI</a>, una herramienta de línea de comandos, para usar Gemini. Puedes instalar Gemini CLI siguiendo las instrucciones indicadas <a href="https://geminicli.com/docs/get-started/installation/">aquí</a>.</p><p>Gemini CLI ofrece una extensión preconfigurada para MCP Toolbox, disponible en <a href="https://github.com/gemini-cli-extensions/mcp-toolbox">gemini-cli-extensions/mcp-toolbox</a>. Puedes instalar esta extensión ejecutando el comando siguiente:</p><pre><code>gemini extensions install https://github.com/gemini-cli-extensions/mcp-toolbox</code></pre><p>Tras la instalación, debes ir al directorio donde almacenaste el archivo de configuración tools.yaml para MCP Toolbox y ejecutar la CLI Gemini de la siguiente manera (este paso es necesario para que la CLI Gemini se configure automáticamente con MCP Toolbox):</p><pre><code>gemini</code></pre><p>Deberías ver un anuncio de salida como el que se muestra en la Figura 4.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0f22df6d3da13c1dc50dcb560414fa7c630eb9a7-1434x341.png" style="width: 100%;" /><p>Puedes comprobar si MCP Toolbox está conectada usando el siguiente comando:</p><pre><code>/mcp list</code></pre><p>Deberías ver el <strong>mcp_toolbox</strong> con las herramientas de<strong> customer-orders</strong> listadas (Figura 5).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/97edbc40de9e44f469f6f3a09427532be167de0e-493x155.png" style="width: 100%;" /><p>Si el MCP Toolbox está conectado a la CLI de Gemini, ahora podemos intentar hacer algunas preguntas, como: “<em>Dame los pedidos del cliente Gwen Sanders</em>”. La CLI de Gemini solicitará entonces permiso para ejecutar la herramienta de pedidos de clientes desde el servidor mcp_toolbox (ver Figura 6).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c052f3b5e49436903b804280c0065f67ee02444b-1432x284.png" style="width: 100%;" /><p>Tras la confirmación, Gemini CLI ejecutará la solicitud a MCP Toolbox, obteniendo una respuesta JSON como resultado y utilizándola para dar formato a la respuesta (Figura 7).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7ea5128f1705951c2757af6da4b456d394d4a080-1432x734.png" style="width: 100%;" /><p>La respuesta de Gemini CLI será un reporte que indica que Gwen Sanders hizo solo un pedido de 2 productos, por un precio total de 132 euros.</p><h2>SDK de MCP Toolbox</h2><p>Google MCP Toolbox también ofrece un SDK para acceder a todas las funcionalidades desde un programa escrito en Go, Python y Javascript.</p><p>Por ejemplo, el SDK de Python está disponible en Github en la siguiente página: <a href="https://github.com/googleapis/mcp-toolbox-sdk-python">https://github.com/googleapis/mcp-toolbox-sdk-python</a>.</p><p>Es necesario crear un agente simple para conectarnos a MCP Toolbox. Debemos instalar los siguientes paquetes:</p><pre><code>pip install toolbox-core
pip install google-adk</code></pre><p>Además, crear un nuevo proyecto de agente usando los siguientes comandos:</p><pre><code>adk create my_agent</code></pre><p>Esto creará un nuevo directorio llamado <strong>my_agent</strong> con un <strong>archivo agent.py</strong>.</p><p>Actualiza <strong>my_agent/agent.py</strong> con el siguiente contenido para conectar con Toolbox:</p><pre><code>from google.adk import Agent
from google.adk.apps import App
from toolbox_core import ToolboxSyncClient

client = ToolboxSyncClient("http://127.0.0.1:5000")

root_agent = Agent(
    name='root_agent',
    model='gemini-2.5-flash',
    instruction="You are a helpful AI assistant designed to search information about a dataset of ecommerce orders.",
    tools=client.load_toolset(),
)

app = App(root_agent=root_agent, name="my_agent")</code></pre><p>Crea un archivo <strong>.env</strong> con tu clave de API de Google:</p><pre><code>echo 'GOOGLE_API_KEY="YOUR_API_KEY"' > my_agent/.env</code></pre><p>Finalmente, podemos ejecutar el agente y observar los resultados. Para ejecutar el agente, puedes ejecutar el siguiente comando:</p><pre><code>adk run my_agent</code></pre><p>O bien, puedes servirlo a través de una interfaz web:</p><pre><code>adk web --port 8000</code></pre><p>En ambos casos, puedes interactuar con MCP Toolbox usando una interfaz de preguntas frecuentes. Por ejemplo, puedes hacer la pregunta anterior: <em>Dame las órdenes de la cliente Gwen Sanders</em>.</p><p>Para más información sobre los diferentes SDK, puedes consultar <a href="https://googleapis.github.io/genai-toolbox/sdks/">esta página de documentación</a>.</p><h2>Conclusión</h2><p>En este artículo, hemos mostrado la integración de Elasticsearch con Google MCP Toolbox for Databases. Mediante un sencillo archivo de configuración YAML, podemos definir un conjunto de herramientas que traducen preguntas en lenguaje natural a consultas de Elasticsearch utilizando el lenguaje ES|QL.</p><p>Mostramos cómo interactuar con el set de datos kibana_sample_data_ecommerce, que contiene pedidos de un sitio web de comercio electrónico. Con este archivo de configuración, podemos simplemente ejecutar el servidor MCP Toolbox y conectarnos a él desde cualquier cliente MCP.</p><p>Por último, mostramos cómo utilizar la CLI de Gemini como cliente para conectarse a MCP Toolbox for Databases y consultar los datos de comercio electrónico almacenados en Elasticsearch. Ejecutamos una consulta en lenguaje natural para recuperar información sobre pedidos para un cliente específico identificado por su nombre.</p><p>A medida que el ecosistema MCP sigue creciendo, este patrón (definiciones ligeras de herramientas respaldadas por infraestructuras seguras y listas para producción) crea nuevas oportunidades para construir agentes cada vez más capaces y conscientes de los datos con un esfuerzo mínimo. Ya sea que experimentes localmente con los sets de datos de muestra de Elastic o integres capacidades de búsqueda en una aplicación más amplia, MCP Toolbox ofrece una base fiable y extensible para interactuar con tus datos de Elasticsearch usando lenguaje natural.</p><p>Para obtener más información sobre el desarrollo de aplicaciones de IA agentic, puedes leer el artículo <a href="https://search-labs-redesign.vercel.app/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">Creación de flujos de trabajo de IA agentic con Elasticsearch</a> de Anish Mathur y Dana Juratoni.</p><p>Para obtener más información sobre Google MCP Toolbox, puedes visitar <a href="https://googleapis.github.io/genai-toolbox/getting-started/introduction/">https://googleapis.github.io/genai-toolbox/getting-started/introduction/</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Evaluación de la relevancia de las consultas de búsqueda con listas de evaluaciones]]></title>
            <description><![CDATA[Explora cómo crear listas de evaluación para evaluar objetivamente la relevancia de las consultas de búsqueda y mejorar métricas de rendimiento como la recuperación, para pruebas de búsqueda escalables en Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/judgment-lists-search-query-relevance-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/judgment-lists-search-query-relevance-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jhon Guzmán]]></dc:creator>
            <pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/25478d0ffb228afd5d65d82312998ec1c299c565-700x490.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Los desarrolladores que trabajan en motores de búsqueda a menudo se encuentran con el mismo problema: el equipo empresarial no está satisfecho con una búsqueda concreta porque los documentos que esperan que estén en la parte superior de los resultados de búsqueda aparecen en tercer o cuarto lugar en la lista de resultados.</p><p>Sin embargo, cuando solucionas este problema, accidentalmente rompes otras consultas porque no pudiste probar todos los casos manualmente. Pero, ¿cómo puedes tú o tu equipo de control de calidad comprobar si un cambio en una consulta tiene un efecto dominó en otras consultas? O aún más importante, ¿cómo puedes estar seguro de que tus cambios realmente mejoraron una consulta?</p><h2>Hacia una evaluación sistemática</h2><p>Aquí es donde las listas de evaluación resultan útiles. En lugar de depender de pruebas manuales y subjetivas cada vez que realices un cambio, puedes definir un conjunto fijo de búsquedas que sean relevantes para tu caso de negocio, junto con sus resultados relevantes.</p><p>Este conjunto se convierte en tu referencia. Cada vez que implementas un cambio, lo utilizas para evaluar si tu búsqueda realmente mejoró o no.</p><p>El valor de este enfoque radica en que:</p><ul><li><strong>Elimina la incertidumbre</strong>: ya no necesitas preguntarte si tus cambios afectan a otras consultas; los datos te lo dirán.</li><li><strong>Detiene las pruebas manuales</strong>: una vez que se registran los conjuntos de evaluación, la prueba es automática.</li><li><strong>Soporta cambios</strong>: puedes mostrar métricas claras que respaldan los beneficios de un cambio.</li></ul><h2>Cómo empezar a crear tu lista de evaluaciones</h2><p>Una de las maneras más fáciles de comenzar es tomar una búsqueda representativa y seleccionar manualmente los documentos relevantes. Hay dos formas de hacer esta lista:</p><ul><li><strong>Evaluaciones binarias:</strong> cada documento asociado con una búsqueda recibe una <strong>etiqueta simple</strong>: <em>relevante</em> (generalmente con una puntuación de “1”) y no relevante (“0”).</li><li><strong>Evaluaciones graduadas:</strong> aquí, cada documento obtiene una puntuación con diferentes niveles. Por ejemplo: establecer un escala de 0 a 4, similar a un <a href="https://en.wikipedia.org/wiki/Likert_scale">escala Likert</a>, donde 0 = “nada relevante” y 4 = “totalmente relevante”, con variaciones como “relevante”, “algo relevante”, etc.</li></ul><p>Los juicios binarios funcionan bien cuando la intención de búsqueda tiene límites claros: ¿Debería este documento estar en los resultados o no?</p><p>Las evaluaciones graduadas son más útiles cuando hay áreas grises: algunos resultados son mejores que otros, así que puedes obtener resultados “muy buenos”, “buenos” e “inútiles” y usar métricas que valoren el orden de los resultados y los comentarios del usuario. Sin embargo, las escalas graduadas también presentan inconvenientes: diferentes revisores pueden usar los niveles de puntuación de manera diferente, lo que hace que las evaluaciones sean menos consistentes. Y debido a que las métricas graduadas dan más peso a las puntuaciones más altas, incluso un pequeño cambio (como calificar algo con un 3 en lugar de un 4) puede crear un cambio mucho mayor en la métrica de lo que el revisor pretendía. Esta subjetividad añadida hace que las evaluaciones graduadas sean más complicadas y difíciles de manejar con el tiempo.</p><h2>¿Necesito clasificar los documentos por mi cuenta?</h2><p>No necesariamente, ya que hay diferentes formas de crear tu lista de evaluaciones, cada una con sus propias ventajas y desventajas:</p><ul><li><strong>Evaluaciones explícitas:</strong> aquí, los expertos revisan cada búsqueda/documento y deciden manualmente si es relevante (o qué tan relevante es). Si bien esto proporciona calidad y control, tiene menos escalabilidad.</li><li><strong>Evaluaciones implícitas:</strong> con este método, infieres los documentos relevantes en función del comportamiento real del usuario, como clics, tasa de rebote y compras, entre otros. Este enfoque te permite recopilar datos automáticamente, aunque puede estar sesgado. Por ejemplo, los usuarios tienden a hacer clic en los primeros resultados con más frecuencia, incluso si no son relevantes.</li><li><strong>Evaluaciones generadas por IA:</strong> esta última opción utiliza modelos (como LLM) para evaluar automáticamente consultas y documentos, a menudo referidos como <a href="https://en.wikipedia.org/wiki/LLM-as-a-Judge">jurados de LLM</a>. Es rápido y fácil de escalar, pero la calidad de los datos depende de la calidad del modelo que estés utilizando y de qué tan bien los datos de entrenamiento de LLM se alinean con tus <a href="http://interests.as/">intereses</a> comerciales. Al igual que con las calificaciones humanas, los jurados LLM pueden introducir sus propios sesgos o inconsistencias, por lo que es importante validar su salida contra un conjunto más pequeño de evaluaciones confiables. Los modelos LLM son probabilísticos por naturaleza, por lo que no es raro ver un modelo LLM dando diferentes calificaciones al mismo resultado independientemente de que el parámetro de <a href="https://www.ibm.com/think/topics/llm-temperature">temperatura</a> sea 0.</li></ul><p>A continuación, se incluyen algunas recomendaciones para elegir el mejor método para crear tu conjunto de evaluaciones:</p><ul><li>Decide cuán importantes son para ti algunas características que solo los usuarios puedan juzgar correctamente (como precio, marca, idioma, estilo y detalles del producto). Si esos son críticos, necesitas <strong>evaluaciones explícitas</strong> para al menos alguna parte de tu <em>lista de evaluaciones</em>.</li><li>Usa <strong>evaluaciones implícitas</strong> cuando tu motor de búsqueda ya tenga suficiente tráfico para que puedas usar clics, conversiones y métricas de tiempo persistente para detectar tendencias de uso. Aun así, deberías interpretarlos con cuidado, contrastándolos con tus evaluaciones explícitas para prevenir sesgos (por ejemplo: los usuarios tienden a hacer clic más a menudo en los resultados mejor clasificados, incluso si los de menor rango son más relevantes)</li></ul><p>Para abordar esto, las técnicas de eliminación del sesgo de posición ajustan o reponderan los datos de clics para reflejar mejor el verdadero interés del usuario. Algunos enfoques incluyen:</p><ul><li><strong>Reordenación de resultados</strong>: cambia el orden de los resultados de búsqueda para un subconjunto de usuarios con el fin de estimar cómo afecta la posición a los clics.</li><li>Los <strong>modelos de clics</strong> incluyen<strong> </strong><a href="https://wiki.math.uwaterloo.ca/statwiki/index.php?title=a_Dynamic_Bayesian_Network_Click_Model_for_web_search_ranking">la red bayesiana dinámica (<strong>DBN)</strong></a> y el <a href="https://rsrikant.com/papers/kdd10.pdf">modelo de navegación del usuario (<strong>UBM).</strong></a> Estos modelos estadísticos estiman la probabilidad de que un clic refleje un interés real en lugar de solo la posición, utilizando patrones como el desplazamiento, el tiempo de permanencia, la secuencia de clics y el retorno a la página de resultados.</li></ul><h2>Ejemplo: app de valoración de películas</h2><h3>Requisitos previos</h3><p>Para ejecutar este ejemplo, necesitas un cluster Elasticsearch 8.x en funcionamiento, <a href="https://www.elastic.co/downloads/elasticsearch">localmente</a> o <a href="https://www.elastic.co/cloud/cloud-trial-overview">Elastic Cloud Hosted</a> (alojado o sin servidor), y acceso a la <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis">API REST</a> o Kibana.</p><p>Imagina una app en la que los usuarios puedan realizar el monitoreo de tiempo de actividad de sus opiniones sobre películas y también hacer una búsqueda de películas para ver. Como los textos son escritos por los propios usuarios, pueden contener errores tipográficos y muchas variaciones en cuanto a la expresión. Por eso es fundamental que el motor de búsqueda sea capaz de interpretar esa diversidad y ofrecer resultados útiles para los usuarios.</p><p>Para poder repetir consultas sin afectar el comportamiento general de búsqueda, el equipo de negocios de tu empresa creó el siguiente conjunto de evaluaciones binarias, basado en las búsquedas más frecuentes:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Búsqueda</td><td style="border: 1px solid #ccc; padding: 4px;">DocID</td><td style="border: 1px solid #ccc; padding: 4px;">Texto</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Actuación de DiCaprio</td><td style="border: 1px solid #ccc; padding: 4px;">doc1</td><td style="border: 1px solid #ccc; padding: 4px;">La actuación de DiCaprio en El renacido fue impresionante.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Actuación de DiCaprio</td><td style="border: 1px solid #ccc; padding: 4px;">doc2</td><td style="border: 1px solid #ccc; padding: 4px;">El origen muestra a Leonardo DiCaprio en uno de sus papeles más icónicos.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Actuación de DiCaprio</td><td style="border: 1px solid #ccc; padding: 4px;">doc3</td><td style="border: 1px solid #ccc; padding: 4px;">Brad Pitt ofrece una actuación estable en este thriller policial.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Actuación de DiCaprio</td><td style="border: 1px solid #ccc; padding: 4px;">doc4</td><td style="border: 1px solid #ccc; padding: 4px;">Una aventura llena de acción con impresionantes efectos visuales.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">películas tristes que te hacen llorar</td><td style="border: 1px solid #ccc; padding: 4px;">doc5</td><td style="border: 1px solid #ccc; padding: 4px;">Una historia desgarradora de amor y pérdida que me hizo llorar durante horas.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">películas tristes que te hacen llorar</td><td style="border: 1px solid #ccc; padding: 4px;">doc6</td><td style="border: 1px solid #ccc; padding: 4px;">Una de las películas más tristes de todos los tiempos: ¡trae pañuelos!</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">películas tristes que te hacen llorar</td><td style="border: 1px solid #ccc; padding: 4px;">doc7</td><td style="border: 1px solid #ccc; padding: 4px;">Una comedia ligera que te hará reír</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">películas tristes que te hacen llorar</td><td style="border: 1px solid #ccc; padding: 4px;">doc8</td><td style="border: 1px solid #ccc; padding: 4px;">Una epopeya de ciencia ficción llena de acción y emoción.</td>
                    </tr>
              </tbody>
            </table>
          <p>Creación del índice:</p><pre><code>PUT movies
{
  "mappings": {
    "properties": {
      "text": {
        "type": "text"
      }
    }
  }
}</code></pre><p>Solicitud en masa:</p><pre><code>POST /movies/_bulk
{ "index": { "_id": "doc1" } }
{ "text": "DiCaprio performance in The Revenant was breathtaking." }
{ "index": { "_id": "doc2" } }
{ "text": "Inception shows Leonardo DiCaprio in one of his most iconic roles." }
{ "index": { "_id": "doc3" } }
{ "text": "Brad Pitt delivers a solid performance in this crime thriller." }
{ "index": { "_id": "doc4" } }
{ "text": "An action-packed adventure with stunning visual effects." }
{ "index": { "_id": "doc5" } }
{ "text": "A heartbreaking story of love and loss that made me cry for hours." }
{ "index": { "_id": "doc6" } }
{ "text": "One of the saddest movies ever made -- bring tissues!" }
{ "index": { "_id": "doc7" } }
{ "text": "A lighthearted comedy that will make you laugh." }
{ "index": { "_id": "doc8" } }
{ "text": "A science-fiction epic full of action and excitement." }</code></pre><p>A continuación se muestra la consulta Elasticsearch que emplea la app:</p><pre><code>GET movies/_search
{
 "query": {
   "match": {
     "text": {
       "query": "DiCaprio performance",
       "minimum_should_match": "100%"
     }
   }
 }
}</code></pre><h3>De juicio a métricas</h3><p>Por sí solas, las listas de evaluaciones no proporcionan mucha información; son solo una expectativa de los resultados de nuestras consultas. Donde realmente destacan es cuando los usamos para calcular métricas objetivo que midan nuestro rendimiento en búsqueda.</p><p>Actualmente, la mayoría de las métricas populares incluyen</p><ul><li><strong><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-precision">Precisión</a>: </strong>mide la proporción de resultados que son realmente relevantes dentro de todos los resultados de búsqueda.</li><li><strong><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-recall">Recuperación</a>: </strong>mide la proporción de resultados relevantes que el motor de búsqueda encontró entre x resultados.</li><li><strong><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#_discounted_cumulative_gain_dcg">Ganancia acumulada descontada (DCG):</a> </strong>mide la calidad de la clasificación del resultado, considerando que los resultados más relevantes deben estar en la parte superior.</li><li><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#_mean_reciprocal_rank"><strong>Rango recíproco medio (MRR):</strong></a> mide la posición del primer resultado relevante. Cuanto más alto estés en la lista, mayor será tu puntuación.</li></ul><p>Usando la misma app de clasificación de películas como ejemplo, calcularemos la métrica de recuperación para ver si hay alguna información que se esté excluyendo de nuestras consultas.</p><p>En Elasticsearch, podemos usar las <em>listas de evaluaciones</em> para calcular métricas mediante la <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval">API de Evaluación de Rankings</a>. Esta API recibe como entrada la lista de evaluaciones, la consulta y la métrica que deseas evaluar, y devuelve un valor, que es una comparación del resultado de la consulta con la lista de evaluaciones.</p><p>Vamos a ejecutar la lista de evaluaciones para las dos consultas que tenemos:</p><pre><code>POST /movies/_rank_eval
{
 "requests": [
   {
     "id": "dicaprio-performance",
     "request": {
       "query": {
         "match": {
           "text": {
             "query": "DiCaprio performance",
             "minimum_should_match": "100%"
           }
         }
       }
     },
     "ratings": [
       {
         "_index": "movies",
         "_id": "doc1",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc2",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc3",
         "rating": 0
       },
       {
         "_index": "movies",
         "_id": "doc4",
         "rating": 0
       }
     ]
   },
   {
     "id": "sad-movies",
     "request": {
       "query": {
         "match": {
           "text": {
             "query": "sad movies that make you cry",
             "minimum_should_match": "100%"
           }
         }
       }
     },
     "ratings": [
       {
         "_index": "movies",
         "_id": "doc5",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc6",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc7",
         "rating": 0
       },
       {
         "_index": "movies",
         "_id": "doc8",
         "rating": 0
       }
     ]
   }
 ],
 "metric": {
   "recall": {
     "k": 10,
     "relevant_rating_threshold": 1
     }
 }
}</code></pre><p>Usaremos dos solicitudes para _rank_eval: una para la búsqueda de DiCaprio y otra para películas tristes. Cada solicitud incluye una búsqueda y su lista de evaluaciones (calificaciones). No necesitamos calificar todos los documentos ya que los que no se incluyen en las calificaciones se consideran sin evaluación. Para realizar los cálculos, recuerda que la recuperación solo considera “el conjunto relevante”, los documentos que se consideran relevantes en la clasificación.</p><p>En este caso, la búsqueda de DiCaprio tiene una recuperación de 1, mientras que las películas tristes obtuvieron 0. Esto significa que para la primera búsqueda, pudimos obtener todos los resultados relevantes, mientras que en la segunda búsqueda, no obtuvimos ninguno. Por tanto, la recuperación medio es de 0,5.</p><pre><code>{
 "metric_score": 0.5,
 "details": {
   "dicaprio-performance": {
     "metric_score": 1,
     "unrated_docs": [],
     "hits": [
       {
         "hit": {
           "_index": "movies",
           "_id": "doc1",
           "_score": 2.4826927
         },
         "rating": 1
       },
       {
         "hit": {
           "_index": "movies",
           "_id": "doc2",
           "_score": 2.0780432
         },
         "rating": 1
       }
     ],
     "metric_details": {
       "recall": {
         "relevant_docs_retrieved": 2,
         "relevant_docs": 2
       }
     }
   },
   "sad-movies": {
     "metric_score": 0,
     "unrated_docs": [],
     "hits": [],
     "metric_details": {
       "recall": {
         "relevant_docs_retrieved": 0,
         "relevant_docs": 2
       }
     }
   }
 },
 "failures": {}
}</code></pre><p>Tal vez estamos siendo demasiado estrictos con el parámetro <strong>minimum_should_match </strong>ya que al exigir que el 100 % de las palabras en la consulta se encuentren en los documentos, probablemente estamos excluyendo resultados relevantes. Eliminemos el parámetro <strong>minimum_should_match</strong> para que un documento se considere relevante si solo se encuentra una palabra de la consulta en él.</p><pre><code>POST /movies/_rank_eval
{
 "requests": [
   {
     "id": "dicaprio-performance",
     "request": {
       "query": {
         "match": {
           "text": {
             "query": "DiCaprio performance"
           }
         }
       }
     },
     "ratings": [
       {
         "_index": "movies",
         "_id": "doc1",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc2",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc3",
         "rating": 0
       },
       {
         "_index": "movies",
         "_id": "doc4",
         "rating": 0
       }
     ]
   },
   {
     "id": "sad-movies",
     "request": {
       "query": {
         "match": {
           "text": {
             "query": "sad movies that make you cry"
           }
         }
       }
     },
     "ratings": [
       {
         "_index": "movies",
         "_id": "doc5",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc6",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc7",
         "rating": 0
       },
       {
         "_index": "movies",
         "_id": "doc8",
         "rating": 0
       }
     ]
   }
 ],
 "metric": {
   "recall": {
     "k": 10,
     "relevant_rating_threshold": 1
     }
 }
}</code></pre><p>Como puedes ver, al eliminar el parámetro <strong>minimum_should_match</strong> en una de las dos consultas, ahora obtenemos una recuperación promedio de 1 en ambas.</p><pre><code>{
  "metric_score": 1,
  "details": {
    "dicaprio-performance": {
      "metric_score": 1,
      "unrated_docs": [],
      "hits": [
        {
          "hit": {
            "_index": "movies",
            "_id": "doc1",
            "_score": 2.0661702
          },
          "rating": 1
        },
        {
          "hit": {
            "_index": "movies",
            "_id": "doc3",
            "_score": 0.732218
          },
          "rating": 0
        },
        {
          "hit": {
            "_index": "movies",
            "_id": "doc2",
            "_score": 0.6271719
          },
          "rating": 1
        }
      ],
      "metric_details": {
        "recall": {
          "relevant_docs_retrieved": 2,
          "relevant_docs": 2
        }
      }
    },
    "sad-movies": {
      "metric_score": 1,
      "unrated_docs": [],
      "hits": [
        {
          "hit": {
            "_index": "movies",
            "_id": "doc7",
            "_score": 2.1307156
          },
          "rating": 0
        },
        {
          "hit": {
            "_index": "movies",
            "_id": "doc5",
            "_score": 1.3160692
          },
          "rating": 1
        },
        {
          "hit": {
            "_index": "movies",
            "_id": "doc6",
            "_score": 1.190063
          },
          "rating": 1
        }
      ],
      "metric_details": {
        "recall": {
          "relevant_docs_retrieved": 2,
          "relevant_docs": 2
        }
      }
    }
  },
  "failures": {}
}</code></pre><p>En resumen, al eliminar la cláusula minimum_should_match: 100%, podemos obtener una recuperación perfecta para ambas búsquedas.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/24d055da4348c63827ba7046fe8cafb6f47cadd8-546x628.png" style="width: 100%;" /><p>¡Lo logramos! ¿Cierto?</p><p>¡No tan rápido!</p><p>Al mejorar la memoria, abrimos la puerta a un rango más amplio de resultados. Sin embargo, cada ajuste implica una compensación. Por esto es importante definir casos de prueba completos, utilizando diferentes métricas para evaluar los cambios.</p><p>El uso de listas de evaluaciones y métricas previene que hagas cambios a ciegas, ya que ahora tienes datos para respaldarlos. La validación ya no es manual y repetitiva, y puedes probar tus cambios en más de un caso de uso. Además, las pruebas A/B te permiten probar en tiempo real qué configuración funciona mejor para tus usuarios y tu caso de negocio, cerrando así la brecha entre métricas técnicas y métricas reales.</p><h2>Recomendaciones finales para el uso de listas de evaluaciones</h2><p>Trabajar con listas de evaluaciones no solo consiste en medir, sino también en crear un marco de trabajo que te permita iterar con confianza. Para lograr esto, puedes seguir estas recomendaciones:</p><ol><li><strong>Empieza poco a poco, pero empieza</strong>. No es necesario que tengas 10 000 consultas con 50 listas de evaluaciones cada una. Solo necesitas identificar las 5 a 10 consultas más críticas para tu caso de negocio y definir qué documentos esperas ver en la parte superior de los resultados. Esto ya te da una base. Por lo general, te conviene comenzar con las consultas principales y las consultas sin resultados. También puedes comenzar a probar con una métrica fácil de configurar como Precisión y luego ir aumentando la complejidad.</li><li><strong>Valida con los usuarios.</strong> Complementa los números con pruebas A/B en producción. De esta manera, sabrás si los cambios que se ven bien en las métricas también están generando un impacto real.</li><li><strong>Haz un mantenimiento de la lista.</strong> Tu caso de negocio evolucionará, y también lo harán tus consultas críticas. Actualiza tu evaluación de forma periódica para reflejar las necesidades nuevas.</li><li><strong>Haz que sea parte del flujo.</strong> Integra listas de evaluaciones en tus pipelines de desarrollo. Asegúrate de que cada cambio de configuración, sinónimo o análisis de texto se valide automáticamente contra tu lista base.</li><li><strong>Conecta conocimientos técnicos con estrategia.</strong> No te limites a medir parámetros técnicos como la precisión o la recuperación. Usa tus resultados de la evaluación para influir en los resultados comerciales.</li></ol>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construye un flujo de trabajo de búsqueda de IA financiera utilizando LangGraph.js y Elasticsearch]]></title>
            <description><![CDATA[Aprende a usar LangGraph.js con Elasticsearch para crear un flujo de trabajo de búsqueda financiera impulsado por IA que convierte consultas en lenguaje natural en filtros dinámicos y condicionales para análisis de inversión y mercado.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/ai-agent-workflow-finance-langgraph-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/ai-agent-workflow-finance-langgraph-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
            <pubDate>Fri, 05 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/12b6057d84c6356267cd178a3c6c1a5c61123ece-2000x1256.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Construir aplicaciones de búsqueda con IA suele implicar la coordinación de múltiples tareas, la recuperación de datos y la extracción de datos en un flujo de trabajo sin problemas. LangGraph simplifica este proceso permitiendo a los desarrolladores orquestar agentes de IA mediante una estructura basada en nodes. En este artículo, explicaremos cómo construir una solución financiera usando <a href="https://langchain-ai.github.io/langgraphjs/">LangGraph.js</a></p><h2>¿Qué es LangGraph?</h2><p><a href="https://langchain-ai.github.io/langgraphjs/">LangGraph</a> es un marco de trabajo para construir agentes de IA y orquestarlos en un flujo de trabajo para crear aplicaciones asistidas por IA. LangGraph tiene una arquitectura de nodes donde podemos declarar funciones que representan tareas y asignarlas como nodes del flujo de trabajo. El resultado de la interacción de varios nodes será un grafo. LangGraph es parte del ecosistema más amplio <a href="https://js.langchain.com/docs/introduction/">LangChain</a>, que proporciona herramientas para construir sistemas de IA modulares y componibles.</p><p>Para explicar mejor por qué LangGraph es útil, vamos a usarlo para resolver una situación problemática.</p><h2>Visión general de la solución</h2><p>En una firma de capital de riesgo, los inversores tienen acceso a una gran base de datos con muchas opciones de filtrado, pero cuando uno quiere combinar criterios, se vuelve difícil y lento. Esto puede hacer que algunas iniciativas relevantes no se encuentren para la inversión. Además, implica pasar muchas horas intentando identificar a los mejores candidatos, o incluso perder oportunidades.</p><p>Con LangGraph y Elasticsearch, podemos realizar búsquedas filtradas utilizando lenguaje natural, eliminando la necesidad de que los usuarios construyan manualmente solicitudes complejas con docenas de filtros. Para hacerlo más flexible, el flujo de trabajo decide automáticamente (basándose en la entrada del usuario) entre dos tipos de consulta:</p><ul><li><strong>Consultas centradas en la inversión</strong>: estas se dirigen a aspectos financieros y de financiación de las startups, como <a href="https://www.investopedia.com/articles/personal-finance/102015/series-b-c-funding-what-it-all-means-and-how-it-works.asp">rondas de financiación</a>, valoración o <a href="https://www.investopedia.com/terms/r/revenue.asp">ingresos</a>. <em>Ejemplo:</em> “Encuentra startups con financiamiento Serie A o Serie B entre $8M y $25M e ingresos mensuales superiores a $500K”.</li><li><strong>Consultas centradas en el mercado</strong>: estas se concentran en <a href="https://en.wikipedia.org/wiki/Vertical_market">verticales de la industria</a>, <a href="https://en.wikipedia.org/wiki/Target_market">mercados geográficos</a> o <a href="https://www.investopedia.com/terms/b/businessmodel.asp">modelos de negocio</a>, ayudando a identificar oportunidades en sectores o regiones específicos. <em>Ejemplo:</em> “Encuentra startups de fintech y salud en San Francisco, Nueva York o Boston”.</li></ul><p>Para mantener la solidez de las consultas, haremos que el LLM cree <a href="https://www.elastic.co/docs/solutions/search/search-templates">plantillas de búsqueda</a> en lugar de <a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/querydsl">consultas DSL</a> completas. De esta manera, siempre obtienes la consulta que deseas, y el LLM solo tiene que completar los espacios en blanco y no cargar con la responsabilidad de construir la consulta que necesitas cada vez.</p><h2>Lo que necesitas para comenzar</h2><ul><li>Clave de API de Elasticsearch</li><li>Clave de API de OpenAPI</li><li>Node 18 o más reciente</li></ul><h2>Instrucciones paso a paso</h2><p>En esta sección, mostramos cómo se verá la app. Para ello, emplearemos <a href="https://www.typescriptlang.org/">TypeScript</a>, un superconjunto de JavaScript que agrega tipos estáticos para hacer el código más fiable, fácil de mantener y seguro, detectando errores pronto mientras se mantiene totalmente compatible con el JavaScript existente.</p><p>El flujo de los nodos será el siguiente:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b47d7f238c4964a6febc0de7fe5e68b186f539c3-363x555.png" style="width: 100%;" /><p>La imagen de arriba es generada por LangGraph y representa el flujo de trabajo que define el orden de ejecución y la lógica condicional entre nodos:</p><ul><li><strong>decideStrategy: </strong>utiliza un LLM para analizar la consulta del usuario y decidir entre dos estrategias de búsqueda especializadas, como centrada en la inversión u orientada al mercado.</li><li><strong>prepareInvestmentSearch: </strong>extrae valores de filtro de la consulta y construye una plantilla predefinida que destaca los parámetros financieros y de financiación.</li><li><strong>prepareMarketSearch</strong>: extrae también los valores del filtro, pero construye dinámicamente parámetros que enfatizan el mercado, la industria y el contexto geográfico.</li><li><strong>executeSearch: </strong>envía la consulta construida a Elasticsearch usando una plantilla de búsqueda y recupera los documentos de startups correspondientes.</li><li><strong>visualizarResultados: </strong>formatea los resultados finales en un resumen claro y legible que muestre atributos clave de la startup, como financiación, industria e ingresos.</li></ul><p>Este flujo incluye una <a href="https://langchain-ai.github.io/langgraphjs/how-tos/branching/?h=conditional#how-to-create-branches-for-parallel-node-execution">ramificación condicional</a>, que funciona como una declaración “si” que determina si se debe usar la ruta de búsqueda de inversión o de mercado según la entrada del usuario. Esta lógica de decisión, impulsada por el LLM, hace que el flujo de trabajo sea adaptativo y consciente del contexto, un mecanismo que exploraremos con más detalle en las siguientes secciones.</p><h3>Estado de LangGraph</h3><p>Antes de ver cada node individualmente, necesitamos entender cómo se comunican y comparten datos. Para ello, LangGraph nos permite definir el estado del flujo de trabajo. Esto define el estado compartido que se pasará entre los nodes.</p><p>El estado actúa como un contenedor compartido que almacena datos intermedios a lo largo del flujo de trabajo: comienza con la consulta en lenguaje natural del usuario, luego guarda la estrategia de búsqueda seleccionada, los parámetros preparados para Elasticsearch, los resultados de búsqueda recuperados y, finalmente, la salida formateada.</p><p>Esta estructura permite que cada node lea y actualice el estado, asegurando un flujo coherente de información desde la entrada del usuario hasta la visualización final.</p><pre><code>const VCState = Annotation.Root({
  input: Annotation<string>(), // User's natural language query
  searchStrategy: Annotation<string>(), // Search strategy chosen by LLM
  searchParams: Annotation<any>(), // Prepared search parameters
  results: Annotation<any[]>(), // Search results
  final: Annotation<string>(), // Final formatted response
});</code></pre><h3>Configure la aplicación</h3><p>Todo el código de esta sección se puede encontrar en el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch">repositorio elasticsearch-labs</a>.</p><p>Abra un terminal en la carpeta donde estará la app e inicialice una aplicación Node.js con el comando:</p><pre><code>npm init -y</code></pre><p>Ahora podemos instalar las dependencias necesarias para este proyecto:</p><pre><code>npm install @elastic/elasticsearch @langchain/langgraph @langchain/openai @langchain/core dotenv zod && npm install --save-dev @types/node tsx typescript</code></pre><ul><li><strong><code>@elastic/elasticsearch</code></strong>: nos ayuda a gestionar las solicitudes de Elasticsearch, como la ingesta y la recuperación de datos.</li><li><strong><code>@langchain/langgraph</code></strong>: Dependencia de JS para proporcionar todas las herramientas de LangGraph.</li><li><strong><code>@langchain/openai</code></strong>: cliente de OpenAI LLM para LangChain.</li><li>@langchain/core: proporciona los bloques fundamentales del núcleo para las apps de LangChain, incluidas las plantillas de prompts.</li><li><strong><code>dotenv</code></strong>: dependencia necesaria para usar variables de entorno en JavaScript.</li><li><strong><code>zod</code></strong>: dependencia para escribir datos.</li></ul><p><code>@types/node</code> <code>tsx</code> <code>typescript</code> nos permite escribir y ejecutar código TypeScript.</p><p>Ahora crea los siguientes archivos:</p><ul><li><code>elasticsearchSetup<a href="http://ingest.ts/">.ts</a></code>: creará los mapping de índice, cargará el conjunto de datos desde un archivo JSON e ingerirá los datos en Elasticsearch.</li><li><a href="http://main.ts/"><code>main.ts</code></a>: incluirá la aplicación LangGraph.</li><li><code>.env</code>: archivo para almacenar las variables de entorno</li></ul><p>En el archivo <code>.env</code>, agreguemos las siguientes variables de entorno:</p><pre><code>ELASTICSEARCH_ENDPOINT="your-endpoint-here"
ELASTICSEARCH_API_KEY="your-key-here"
OPENAI_API_KEY="your-key-here"</code></pre><p>La clave API de OpenAPI no se usará directamente en el código; en su lugar, se usará internamente por la biblioteca <code>@langchain/openai</code>.</p><p>Toda la lógica relacionada con la creación de mapping, la creación de plantillas de búsqueda y la ingesta de sets de datos se encuentra en el archivo <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/elasticsearchSetup.ts"><code>elasticsearchSetup.ts</code></a>. En los próximos pasos, nos centraremos en el archivo <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/main.ts"><code>main.ts</code></a> . Además, puedes consultar los sets de datos para entender mejor cómo se ven los datos en el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/dataset.json"><code>dataset.json</code></a>.</p><h3>Aplicación LangGraph</h3><p>En el archivo <code>main.ts</code>, vamos a importar algunas dependencias necesarias para consolidar la aplicación LangGraph. En este archivo, también debes incluir las funciones del node y la declaración de estado. La declaración del grafo se realizará en un método <code>main</code> en los siguientes pasos. El archivo <code>elasticsearchSetup.ts</code> contendrá ayudantes de Elasticsearch que vamos a usar dentro de los nodes en los próximos pasos.</p><pre><code>import { writeFileSync } from "node:fs";
import { StateGraph, Annotation, START, END } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
import {
  esClient,
  ingestDocuments,
  createSearchTemplates,
  INDEX_NAME,
  INVESTMENT_FOCUSED_TEMPLATE,
  MARKET_FOCUSED_TEMPLATE,
  createIndex,
} from "./elasticsearchSetup.js";

const llm = new ChatOpenAI({ model: "gpt-4o-mini" });</code></pre><p>Como se mencionó anteriormente, el cliente LLM se utilizará para generar los parámetros de la plantilla de búsqueda de Elasticsearch basados en la pregunta del usuario.</p><pre><code>async function saveGraphImage(app: any): Promise<void> {
  try {
    const drawableGraph = app.getGraph();
    const image = await drawableGraph.drawMermaidPng();
    const arrayBuffer = await image.arrayBuffer();

    const filePath = "./workflow_graph.png";
    writeFileSync(filePath, new Uint8Array(arrayBuffer));
    console.log(`📊 Workflow graph saved as: ${filePath}`);
  } catch (error: any) {
    console.log("⚠️  Could not save graph image:", error.message);
  }
}</code></pre><p>El método anterior genera la imagen del grafo en formato png y utiliza la <a href="https://mermaid.ink/">API de Mermaid.INK</a> en segundo plano. Esto es útil si deseas ver cómo interactúan los nodes de la app con una visualización estilizada.</p><h3>Nodes LangGraph</h3><p>Ahora veamos cada node en detalle:</p><h3>node decideSearchStrategy</h3><p>El <code>decideSearchStrategy</code> node analiza la entrada del usuario y determina si realizar una búsqueda centrada en la inversión o en el mercado. Utiliza un LLM con un esquema de salida estructurado (definido con Zod) para clasificar el tipo de consulta. Antes de tomar la decisión, recupera los filtros disponibles del índice mediante una agregación, lo que garantiza que el modelo cuente con información actualizada sobre sectores, ubicaciones y datos de financiación.</p><p>Para extraer los posibles valores de los filtros y enviarlos al LLM, usemos una consulta <a href="https://www.elastic.co/docs/explore-analyze/query-filter/aggregations">de agregación</a> para obtenerlos directamente del índice de Elasticsearch. Esta lógica se encuentra en un método llamado <code>getAvailableFilters</code>:</p><pre><code>async function getAvailableFilters() {
  try {
    const response = await esClient.search({
      index: INDEX_NAME,
      size: 0,
      aggs: {
        industries: {
          terms: { field: "industry", size: 100 },
        },
        locations: {
          terms: { field: "location", size: 100 },
        },
        funding_stages: {
          terms: { field: "funding_stage", size: 20 },
        },
        business_models: {
          terms: { field: "business_model", size: 10 },
        },
        lead_investors: {
          terms: { field: "lead_investor", size: 100 },
        },
        funding_amount_stats: {
          stats: { field: "funding_amount" },
        },
      },
    });

    return response.aggregations;
  } catch (error) {
    console.error("❌ Error getting available filters:", error);
    return {};
  }
}</code></pre><p>Con la consulta de agregación anterior, tenemos los siguientes resultados:</p><pre><code>{
  "industries": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "logistics",
        "doc_count": 5
      },
      ...
    ]
  },
  "locations": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "San Francisco, CA",
        "doc_count": 4
      },
      {
        "key": "New York, NY",
        "doc_count": 3
      },
      ...
    ]
  },
  "funding_stages": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "Series A",
        "doc_count": 8
      },
      ...
    ]
  },
  "business_models": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "B2B",
        "doc_count": 13
      },
      ...
    ]
  },
  "lead_investors": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "Battery Ventures",
        "doc_count": 1
      },
      {
        "key": "Benchmark Capital",
        "doc_count": 1
      },
      ...
    ]
  },
  "funding_amount_stats": {
    "count": 20,
    "min": 4500000,
    "max": 35000000,
    "avg": 14075000,
    "sum": 281500000
  }
}</code></pre><p>Vea todos los resultados <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/responses/aggregationsResponse.json">aquí</a>.</p><p>Para ambas estrategias, utilizaremos la búsqueda híbrida para detectar tanto la parte estructurada de la pregunta (filtros) como las partes más subjetivas (semántica). A continuación se muestra un ejemplo de ambas consultas utilizando <a href="https://www.elastic.co/docs/solutions/search/search-templates">plantillas de búsqueda</a>:</p><pre><code>await esClient.putScript({
      id: INVESTMENT_FOCUSED_TEMPLATE,
      script: {
        lang: "mustache",
        source: `{
          "size": 5,
          "retriever": {
            "rrf": {
              "retrievers": [
                {
                  "standard": {
                    "query": {
                      "semantic": {
                        "field": "semantic_field",
                        "query": "{{query_text}}"
                      }
                    }
                  }
                },
                {
                  "standard": {
                    "query": {
                      "bool": {
                        "filter": [
                          {"terms": {"funding_stage": {{#join}}{{#toJson}}funding_stage{{/toJson}}{{/join}}}},
                          {"range": {"funding_amount": {"gte": {{funding_amount_gte}}{{#funding_amount_lte}},"lte": {{funding_amount_lte}}{{/funding_amount_lte}}}}},
                          {"terms": {"lead_investor": {{#join}}{{#toJson}}lead_investor{{/toJson}}{{/join}}}},
                          {"range": {"monthly_revenue": {"gte": {{monthly_revenue_gte}}{{#monthly_revenue_lte}},"lte": {{monthly_revenue_lte}}{{/monthly_revenue_lte}}}}}
                        ]
                      }
                    }
                  }
                }
              ],
              "rank_window_size": 100,
              "rank_constant": 20
            }
          }
        }`,
      },
    });</code></pre><p>Vea las consultas detalladas en el archivo <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/elasticsearchSetup.ts#L119"><code>elasticsearchSetup.ts</code></a> . En el siguiente node, se decidirá cuál de las dos consultas se empleará:</p><pre><code>// Node 1: Decide search strategy using LLM
async function decideSearchStrategy(state: typeof VCState.State) {
  // Zod schema for specialized search strategy decision
  const SearchDecisionSchema = z.object({
    search_type: z
      .enum(["investment_focused", "market_focused"])
      .describe("Type of specialized search strategy to use"),
    reasoning: z
      .string()
      .describe("Brief explanation of why this search strategy was chosen"),
  });

  const decisionLLM = llm.withStructuredOutput(SearchDecisionSchema);

  // Get dynamic filters from Elasticsearch
  const availableFilters = await getAvailableFilters();

  const prompt = `Query: "${state.input}"
    Available filters: ${JSON.stringify(availableFilters, null, 2)}

    Choose between two specialized search strategies:
    
    - investment_focused: For queries about funding stages, funding amounts, monthly revenue, lead investors, financial performance
    
    - market_focused: For queries about industries, locations, business models, market segments, geographic markets
    
    Analyze the query intent and choose the most appropriate strategy.
  `;

  try {
    const result = await decisionLLM.invoke(prompt);
    console.log(
      `🤔 Search strategy: ${result.search_type} - ${result.reasoning}`
    );

    return {
      searchStrategy: result.search_type,
    };
  } catch (error: any) {
    console.error("❌ Error in decideSearchStrategy:", error.message);
    return {
      searchStrategy: "investment_focused",
    };
  }
}</code></pre><h3>Nodes prepareInvestmentSearch y prepareMarketSearch</h3><p>Ambos nodos emplean una función auxiliar compartida, <code>extractFilterValues</code>, que aprovecha el LLM para identificar los filtros relevantes mencionados en la entrada del usuario, como la industria, la ubicación, la etapa de financiación, el modelo de negocio, etc. Estamos utilizando este esquema para crear nuestra <a href="https://www.elastic.co/docs/solutions/search/search-templates">plantilla de búsqueda</a>.</p><pre><code>// Extract all possible filter values from user input
async function extractFilterValues(input: string) {
  const FilterValuesSchema = z.object({
    // Investment-focused filters
    funding_stage: z
      .array(z.string())
      .default([])
      .describe("Funding stage values mentioned in query"),
    funding_amount_gte: z
      .number()
      .default(0)
      .describe("Minimum funding amount in USD"),
    funding_amount_lte: z
      .number()
      .default(100000000)
      .describe("Maximum funding amount in USD"),
    lead_investor: z
      .array(z.string())
      .default([])
      .describe("Lead investor values mentioned in query"),
    monthly_revenue_gte: z
      .number()
      .default(0)
      .describe("Minimum monthly revenue in USD"),
    monthly_revenue_lte: z
      .number()
      .default(10000000)
      .describe("Maximum monthly revenue in USD"),
    industry: z
      .array(z.string())
      .default([])
      .describe("Industry values mentioned in query"),
    location: z
      .array(z.string())
      .default([])
      .describe("Location values mentioned in query"),
    business_model: z
      .array(z.string())
      .default([])
      .describe("Business model values mentioned in query"),
  });

  const extractorLLM = llm.withStructuredOutput(FilterValuesSchema);
  const availableFilters = await getAvailableFilters();

  const extractPrompt = `Extract ALL relevant filter values from: "${input}"
    Available options: ${JSON.stringify(availableFilters, null, 2)}
    Extract only values explicitly mentioned in the query. Leave fields empty if not mentioned.`;

  return await extractorLLM.invoke(extractPrompt);
}</code></pre><p>Según de la intención detectada, el flujo de trabajo selecciona una de dos rutas:</p><p><strong>prepareInvestmentSearch:</strong> desarrolla parámetros de búsqueda orientados a la financiación, incluyendo la etapa de financiación, el importe de la inversión, el inversionista y la información de renovación. Puedes encontrar la plantilla completa de consulta en el archivo <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/elasticsearchSetup.ts"><code>elasticsearchSetup.ts</code></a> :</p><pre><code>// Node 2A: Prepare Investment-Focused Search Parameters 
async function prepareInvestmentSearch(state: typeof VCState.State) {
  console.log(
    "💰 Preparing INVESTMENT-FOCUSED search parameters with financial emphasis..."
  );

  try {
    // Extract all filter values from input
    const values = await extractFilterValues(state.input);

    let searchParams: any = {
      template_id: INVESTMENT_FOCUSED_TEMPLATE,
      query_text: state.input,
      ...values,
    };

    return { searchParams };
  } catch (error) {
    console.error("❌ Error preparing investment-focused params:", error);
    return {
      searchParams: {},
    };
  }
}</code></pre><p><strong>prepareMarketSearch:</strong> crea parámetros orientados al mercado centrados en industrias, geografías y modelos de negocio. Ver la consulta completa en el archivo <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/elasticsearchSetup.ts"><code>elasticsearchSetup.ts</code></a>:</p><pre><code>// Node 2B: Prepare Market-Focused Search Parameters
async function prepareMarketSearch(state: typeof VCState.State) {
  console.log(
    "🔍 Preparing MARKET-FOCUSED search parameters with market emphasis..."
  );

  try {
    // Extract all filter values from input
    const values = await extractFilterValues(state.input);

    let searchParams: any = {
      template_id: MARKET_FOCUSED_TEMPLATE,
      query_text: state.input,
      ...values,
    };

    return { searchParams };
  } catch (error) {
    console.error("❌ Error preparing market-focused params:", error);
    return {};
  }
}</code></pre><h3>Node executeSearch</h3><p>Este node toma los parámetros de búsqueda generados del estado y los envía primero a Elasticsearch, usando la <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-render-search-template">_render API</a> para visualizar la consulta con fines de depuración, y luego envía una petición para recuperar los resultados.</p><pre><code>// Node 3: Execute Search
async function executeSearch(state: typeof VCState.State) {
  const { searchParams } = state;

  try {
    // getting formed query from template for debugging
    const renderedTemplate = await esClient.renderSearchTemplate({
      id: searchParams.template_id,
      params: searchParams,
    });

    console.log(
      "📋 Complete query:",
      JSON.stringify(renderedTemplate.template_output, null, 2)
    );

    const results = await esClient.searchTemplate({
      index: INDEX_NAME,
      id: searchParams.template_id,
      params: searchParams,
    });

    return {
      results: results.hits.hits.map((hit: any) => hit._source),
    };
  } catch (error: any) {
    console.error(`❌ ${state.searchParams.search_type} search error:`, error);
    return { results: [] };
  }
}</code></pre><h3>node visualizarResultados</h3><p>Finalmente, este nodo muestra los resultados de Elasticsearch.</p><pre><code>// Node 4: Visualize results
async function visualizeResults(state: typeof VCState.State) {
  const results = state.results || [];

  let formattedResults = `🎯 Found ${results.length} startups matching your criteria:\n\n`;

  results.forEach((startup: any, index: number) => {
    formattedResults += `${index + 1}. **${startup.company_name}**\n`;
    formattedResults += `   📍 ${startup.location} | 🏢 ${startup.industry} | 💼 ${startup.business_model}\n`;
    formattedResults += `   💰 ${startup.funding_stage} - $${(
      startup.funding_amount / 1000000
    ).toFixed(1)}M\n`;
    formattedResults += `   👥 ${startup.employee_count} employees | 📈 $${(
      startup.monthly_revenue / 1000
    ).toFixed(0)}K MRR\n`;
    formattedResults += `   🏦 Lead: ${startup.lead_investor}\n`;
    formattedResults += `   📝 ${startup.description}\n\n`;
  });

  return {
    final: formattedResults,
  };
}</code></pre><p>Programáticamente, todo el grafo se ve así:</p><pre><code>  const workflow = new StateGraph(VCState)
    // Register nodes - these are the processing functions
    .addNode("decideStrategy", decideSearchStrategy)
    .addNode("prepareInvestment", prepareInvestmentSearch)
    .addNode("prepareMarket", prepareMarketSearch)
    .addNode("executeSearch", executeSearch)
    .addNode("visualizeResults", visualizeResults)
    // Define execution flow with conditional branching
    .addEdge(START, "decideStrategy") // Start with strategy decision
    .addConditionalEdges(
      "decideStrategy",
      (state: typeof VCState.State) => state.searchStrategy, // Conditional function
      {
        investment_focused: "prepareInvestment", // If investment focused -> RRF template preparation
        market_focused: "prepareMarket", // If market focused -> dynamic query preparation
      }
    )
    .addEdge("prepareInvestment", "executeSearch") // Investment prep -> execute
    .addEdge("prepareMarket", "executeSearch") // Market prep -> execute
    .addEdge("executeSearch", "visualizeResults") // Execute -> visualize
    .addEdge("visualizeResults", END); // End workflow</code></pre><p>Como puede ver, tenemos una aplicación condicional donde la aplicación decide qué “ruta” o node ejecutar a continuación. Esta característica es útil cuando los flujos de trabajo necesitan lógica de ramificación, como elegir entre múltiples herramientas o incluir un paso con intervención de una persona.</p><p>Con las características básicas del núcleo de LangGraph entendidas, podemos configurar la aplicación donde se ejecutará el código:</p><p>Juntando todo en un flujo de trabajo <code>main</code>, aquí declaramos el grafo con todos los elementos bajo la variable flujo de trabajo:</p><pre><code>async function main() {
  await createIndex();
  await createSearchTemplates();
  await ingestDocuments();

  // Create the workflow graph with shared state
  const workflow = new StateGraph(VCState)
    // Register nodes - these are the processing functions
    .addNode("decideStrategy", decideSearchStrategy)
    .addNode("prepareInvestment", prepareInvestmentSearch)
    .addNode("prepareMarket", prepareMarketSearch)
    .addNode("executeSearch", executeSearch)
    .addNode("visualizeResults", visualizeResults)
    // Define execution flow with conditional branching
    .addEdge(START, "decideStrategy") // Start with strategy decision
    .addConditionalEdges(
      "decideStrategy",
      (state: typeof VCState.State) => state.searchStrategy, // Conditional function
      {
        investment_focused: "prepareInvestment", // If investment focused -> RRF template preparation
        market_focused: "prepareMarket", // If market focused -> dynamic query preparation
      }
    )
    .addEdge("prepareInvestment", "executeSearch") // Investment prep -> execute
    .addEdge("prepareMarket", "executeSearch") // Market prep -> execute
    .addEdge("executeSearch", "visualizeResults") // Execute -> visualize
    .addEdge("visualizeResults", END); // End workflow


  const app = workflow.compile();

  await saveGraphImage(app);

  const query =
    "Find startups with Series A or Series B funding between $8M-$25M and monthly revenue above $500K";

  const marketResult = await app.invoke({ input: query });
  console.log(marketResult.final);
}</code></pre><p>La variable de consulta simula la entrada del usuario introducida en una barra de búsqueda hipotética:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e8d76909eb2bc1bb62f3ca9a8b3e4b85fcec2893-1600x164.png" style="width: 100%;" /><p>De la frase en lenguaje natural “Encuentra startups con financiamiento de Serie A o Serie B entre $8M y $25M, e ingresos mensuales superiores a $500K” se extraerán todos los filtros.</p><p>Finalmente, invoca el método principal:</p><pre><code>main().catch(console.error);</code></pre><h3>Resultados</h3><pre><code>🔍 Checking if index exists...
🏗️ Creating index...
✅ Index created successfully!
Ingesting documents...
✅ Documents ingested successfully!
✅ Investment-focused template created successfully!
✅ Market-focused template created successfully!

📊 Workflow graph saved as: ./workflow_graph.png

🔍 Query: "Find startups with Series A or Series B funding between $8M-$25M and monthly revenue above $500K"

🤔 Search strategy: investment_focused - The query specifically seeks profitable fintech startups with defined funding amounts and high monthly revenue, which aligns closely with financial performance metrics and investment-related criteria.

💰 Preparing INVESTMENT-FOCUSED search parameters with financial emphasis...

📋 Complete query: {
  "size": 5,
  "retriever": {
    "rrf": {
      "retrievers": [
        {
          "standard": {
            "query": {
              "semantic": {
                "field": "semantic_field",
                "query": "Find startups with Series A or Series B funding between $8M-$25M and monthly revenue above $500K"
              }
            }
          }
        },
        {
          "standard": {
            "query": {
              "bool": {
                "filter": [
                  {
                    "terms": {
                      "funding_stage": [
                        "Series A",
                        "Series B"
                      ]
                    }
                  },
                  {
                    "range": {
                      "funding_amount": {
                        "gte": 8000000,
                        "lte": 25000000
                      }
                    }
                  },
                  {
                    "terms": {
                      "lead_investor": []
                    }
                  },
                  {
                    "range": {
                      "monthly_revenue": {
                        "gte": 500000,
                        "lte": 0
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      ],
      "rank_window_size": 100,
      "rank_constant": 20
    }
  }
}
🎯 Found 5 startups matching your criteria:

1. **TechFlow**
   📍 San Francisco, CA | 🏢 logistics | 💼 B2B
   💰 Series A - $8.0M
   👥 45 employees | 📈 $500K MRR
   🏦 Lead: Sequoia Capital
   📝 TechFlow optimizes supply chain operations using AI-powered route optimization and real-time tracking. Founded in 2023, shows remarkable growth with $500K monthly revenue.

2. **DataViz**
   📍 New York, NY | 🏢 enterprise software | 💼 B2B
   💰 Series A - $10.0M
   👥 42 employees | 📈 $450K MRR
   🏦 Lead: Battery Ventures
   📝 DataViz creates intuitive data visualization tools for enterprise customers. No-code platform allows business users to create dashboards without technical expertise.

3. **FinanceAI**
   📍 San Francisco, CA | 🏢 fintech | 💼 B2C
   💰 Series C - $25.0M
   👥 120 employees | 📈 $1200K MRR
   🏦 Lead: Tiger Global Management
   📝 FinanceAI provides AI-powered investment advisory services to retail investors. Uses machine learning to analyze market trends with over 100,000 active users.

4. **UrbanMobility**
   📍 New York, NY | 🏢 logistics | 💼 B2B2C
   💰 Series B - $15.0M
   👥 78 employees | 📈 $750K MRR
   🏦 Lead: Kleiner Perkins
   📝 UrbanMobility revolutionizes urban transportation through autonomous delivery drones and smart logistics hubs. Partners with major retailers for same-day delivery across Manhattan and Brooklyn.

5. **HealthTech Solutions**
   📍 Boston, MA | 🏢 healthcare | 💼 B2B
   💰 Series B - $18.0M
   👥 95 employees | 📈 $900K MRR
   🏦 Lead: General Catalyst
   📝 HealthTech Solutions develops medical devices and software for remote patient monitoring. Comprehensive telehealth platform reducing hospital readmissions by 30%.

✨  Done in 18.80s.</code></pre><p>Para la entrada enviada, la aplicación elige la ruta <strong>centrada en la inversión</strong> y, como resultado, podemos ver la consulta de Elasticsearch generada por el flujo de trabajo de LangGraph, que extrae los valores y los rangos de la entrada del usuario. También podemos ver la consulta enviada a Elasticsearch con los valores extraídos aplicados y, finalmente, los resultados formateados por el node <code>visualizeResults</code> con los resultados.</p><p>Ahora vamos a probar el node <strong>centrado en el mercado</strong> usando la consulta “Encuentre startups de fintech y salud en San Francisco, Nueva York o Boston”:</p><pre><code>...

🔍 Query: Find fintech and healthcare startups in San Francisco, New York, or Boston

🤔 Search strategy: market_focused - The query is focused on finding fintech startups in San Francisco that are disrupting traditional banking and payment systems, which pertains to specific industries (fintech) and locations (San Francisco). Thus, a market-focused strategy is more appropriate.

🔍 Preparing MARKET-FOCUSED search parameters with market emphasis...

📋 Complete query: {
  "size": 5,
  "retriever": {
    "rrf": {
      "retrievers": [
        {
          "standard": {
            "query": {
              "semantic": {
                "field": "semantic_field",
                "query": "Find fintech and healthcare startups in San Francisco, New York, or Boston"
              }
            }
          }
        },
        {
          "standard": {
            "query": {
              "bool": {
                "filter": [
                  {
                    "terms": {
                      "industry": [
                        "fintech",
                        "healthcare"
                      ]
                    }
                  },
                  {
                    "terms": {
                      "location": [
                        "San Francisco, CA",
                        "New York, NY",
                        "Boston, MA"
                      ]
                    }
                  },
                  {
                    "terms": {
                      "business_model": []
                    }
                  }
                ]
              }
            }
          }
        }
      ],
      "rank_window_size": 50,
      "rank_constant": 10
    }
  }
}
🎯 Found 5 startups matching your criteria:

1. **FinanceAI**
   📍 San Francisco, CA | 🏢 fintech | 💼 B2C
   💰 Series C - $25.0M
   👥 120 employees | 📈 $1200K MRR
   🏦 Lead: Tiger Global Management
   📝 FinanceAI provides AI-powered investment advisory services to retail investors. Uses machine learning to analyze market trends with over 100,000 active users.

2. **CryptoWallet**
   📍 Miami, FL | 🏢 fintech | 💼 B2C
   💰 Series B - $16.0M
   👥 73 employees | 📈 $820K MRR
   🏦 Lead: Coinbase Ventures
   📝 CryptoWallet provides secure digital wallet solutions for cryptocurrency trading and storage. Multi-chain support with enterprise-grade security features.

...

✨  Done in 7.41s.</code></pre><h2>Aprendizajes</h2><p>Durante el proceso de escritura aprendí:</p><ul><li>Debemos mostrar al LLM los valores exactos de los filtros; de lo contrario, dependemos de que el usuario escriba los valores exactos de las cosas. Para baja cardinalidad, este enfoque está bien, pero cuando la cardinalidad es alta, necesitamos algún mecanismo para filtrar los resultados.</li><li>El uso de plantillas de búsqueda hace que los resultados sean mucho más consistentes que dejar que el LLM escriba la consulta de Elasticsearch, y también es más rápido.</li><li>Los bordes condicionales son un mecanismo potente para crear aplicaciones con múltiples variantes y rutas de ramificación.</li><li>La salida estructurada es extremadamente útil cuando se genera información con LLM porque impone respuestas predecibles y de tipo seguro. Esto mejora la confiabilidad y reduce las interpretaciones incorrectas de los prompts.</li></ul><p>La combinación de búsqueda semántica y estructurada a través de la recuperación híbrida produce resultados mejores y más relevantes, equilibrando la precisión y la comprensión del contexto.</p><h2>Conclusión</h2><p>En este ejemplo, combinamos LangGraph.js con Elasticsearch para crear un flujo de trabajo dinámico capaz de interpretar consultas en lenguaje natural y decidir entre estrategias de búsqueda financieras u orientadas al mercado. Este enfoque reduce la complejidad de la creación de consultas manuales, al tiempo que mejora la flexibilidad y la precisión para los analistas de capital de riesgo.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mejorar la interactividad del dashboard de Kibana con controles variables]]></title>
            <description><![CDATA[Descubre cómo utilizar los controles variables en Kibana 8.18+ para filtrar visualizaciones individuales, ajustar intervalos de tiempo y agrupar por diferentes campos en los dashboards de Kibana.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/kibana-dashboard-interactivity-variable-controls-overview</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/kibana-dashboard-interactivity-variable-controls-overview</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Teresa Alvarez Soler]]></dc:creator>
            <pubDate>Thu, 04 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/59aa3adffc8c759e42b961ef7d63719ce232893a-1348x830.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>¡Nos complace compartir que los <strong>controles variables ahora están disponibles en los dashboards de Kibana</strong> a partir de la versión 8.18 y en toda la serie 9.x! Esta característica ha sido una de las mejoras más solicitadas con más frecuencia por los usuarios de los dashboards—y finalmente está aquí 🎉 Durante los últimos meses, hemos continuado expandiendo y refinando <a href="https://www.elastic.co/docs/explore-analyze/dashboards/add-controls#add-variable-control">controles de variables</a>, por lo que este es el momento perfecto para dedicarles su propia publicación en el blog.</p><h2>¿Qué son los controles variables?</h2><p>Si has trabajado antes con los dashboards de Kibana, probablemente conozcas nuestros controles clásicos de dashboard: esos prácticos menús desplegables que muestran valores de tus datos para que puedas aplicar filtros con un par de clics.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b82c1b25a0b38661e5ce4552f763be487d5074aa-1600x701.png" style="width: 100%;" /><p>A simple vista, los controles variables son parecidos , pero tienen un giro ingenioso: en lugar de filtrar automáticamente cada panel en tu dashboard, pueden conectarse directamente a <a href="https://www.elastic.co/docs/explore-analyze/visualize/esorql">búsquedas ES|QL dentro de visualizaciones individuales</a>.</p><p>Eso significa que <em>tú</em> decides dónde se aplica cada control. Y aún más, puedes usarlos para todo tipo de trucos creativos, como ajustar intervalos de tiempo, cambiar campos de desglose o modificar parámetros de visualización en el momento. Básicamente, ofrecen a tus dashboards una experiencia realmente interactiva, lo que permite obtener tu información de manera más rápida y fácil.</p><h2>Casos de uso para controles variables</h2><p>Muy bien, los controles variables parecen útiles, pero ¿para qué sirven realmente? A continuación se muestran algunos ejemplos de cómo mejorar tus dashboards:</p><h3>Filtrar visualizaciones seleccionadas.</h3><p>¿Quieres filtrar <em>algunas</em> visualizaciones sin cambiar otras? Los controles de variables te permiten hacer exactamente eso. Selecciona los paneles a los que deseas responder y conéctalos en las búsquedas de ES|QL detrás de tus visualizaciones.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/efa367363830b03bc67028aceafe78c4b44e578f-1440x562.gif" style="width: 100%;" /><h3>Selecciona diferentes intervalos de tiempo</h3><p>Otorga a tus usuarios el poder de cambiar entre “5 minutos”, “1 hora”, “1 día” o cualquier cubeta de tiempo que tenga sentido. Crea un control variable con intervalos predefinidos y conéctalo a tu búsqueda de series temporales.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/62aa9f4e728036f8c70213b76b1cf131f36f5b4d-1440x606.gif" style="width: 100%;" /><h3>Cambiar funciones</h3><p>En lugar de crear múltiples gráficos para cada operación, permite que los usuarios del dashboard elijan si quieren ver el máximo, el promedio, diferentes percentiles o cualquier otro agregador.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f6a2b4c73dc35efe462c2924a153d7b3fa3a7922-1436x606.gif" style="width: 100%;" /><h3>Agrupar por diferentes campos</h3><p>A veces necesitas desglosar los datos por diferentes dimensiones durante una investigación. Con controles variables, puedes definir múltiples campos “agrupar por” y permitir que los usuarios del dashboard elijan cuál les permite descubrir su información.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fe8745a6fddccadba0666686b8ebc67fdaf64158-1438x606.gif" style="width: 100%;" /><h2>¿Cómo puedes crearlos?</h2><p>La manera más sencilla (y probablemente más amena) de crear un control de variable es directamente desde el <strong>editor de búsquedas ES|QL</strong> en tu visualización. Simplemente comienza a escribir tu búsqueda, usa el menú de autocompletado y Kibana configurará el control de una manera que te resulte útil.</p><p>Pero si prefieres empezar desde la variable en sí, también puedes ir a: <strong>Agregar panel → Controles → Control de variables</strong> y agregar la variable a tus visualizaciones después de crear el control.</p><h3>Ejemplo 1: Control de filtrado con selección de múltiples valores.</h3><p>1. Elija una visualización que se realice a partir de una búsqueda ES|QL y haga clic en “Crear control” dentro de la cláusula WHERE.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/46cb6f2a6775aee152d42eb5ee85170f1bdf26cb-1600x668.png" style="width: 100%;" /><p>2. Serás redirigido automáticamente al elemento flotante de creación de variables, donde se seleccionará el tipo “Valores de una búsqueda” para ti, y el nombre de la variable ya estará previamente completado. Recuerda que el nombre de un control siempre debe comenzar con “?...” para que funcione en la búsqueda de visualización.</p><p>Normalmente necesitarás una búsqueda como esta para obtener los valores de un campo y actualizarlos según el rango de tiempo seleccionado en el dashboard:</p><pre><code>FROM <datasource_name>
| WHERE @timestamp <=?_tend and @timestamp >?_tstart
| STATS BY <field_name></code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a2a72d4e3159923c6207908da9b4172e27cd5f81-1600x716.png" style="width: 100%;" /><p>3. Al guardar el control, lo verás aparecer en la parte superior del dashboard y tu búsqueda de visualización se actualizará con el nombre del control variable.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5fc434c8951889e9769652b675191711d126a685-1600x653.png" style="width: 100%;" /><p>4. Si deseas agregar una <a href="https://www.elastic.co/docs/explore-analyze/dashboards/add-controls#esql-multi-values-controls">selección de valores múltiples</a> al control, debes usar la función <code>MV_CONTAINS</code> en la búsqueda y seleccionar “Permitir selecciones múltiples” durante la creación del control en el paso 2 (disponible desde la versión 9.3).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1f237cea0a37cb25a7917a2a683707a269adae8e-1600x670.png" style="width: 100%;" /><h3>Ejemplo 2: Control de intervalos de tiempo.</h3><p>Si estás creando una serie temporal, puedes agregar fácilmente un control variable en el intervalo de histograma de fecha:</p><p>1. Cuando escribas una búsqueda ES|QL para tu serie temporal, haz clic en “Crear control”. Al construir una variable para intervalos, es mejor usar <code>TBUCKET</code> en lugar de <code>BUCKET</code> para que acepte intervalos más legibles como “1 hora”, “1 día”, etc. Pronto habrá una opción automática para <code>TBUCKET</code> que se adaptará automáticamente a los intervalos de tiempo.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b0ad53d790ff9bdd42db5e77477318319f423534-1600x664.png" style="width: 100%;" /><p>2. Define los intervalos para completar las opciones en el menú desplegable.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f3bd83f530cfa4698c1a3b1ae60d08d0414043b5-1600x757.png" style="width: 100%;" /><p>3. Selecciona diferentes intervalos en el menú desplegable y observa cómo cambia tu visualización.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0f928d9c70929f64926e065059188d140cd48943-1600x671.png" style="width: 100%;" /><h3>Ejemplo 3: variables para funciones</h3><ol><li>Crea una variable usando el tipo de control “Valores estáticos” y agrega nombres de funciones a los valores de tu menú desplegable. Es importante usar un nombre para la variable que comience con “??...” para reemplazar funciones.</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/531444237b7e152d3c8a6f3ca7e464f954f9e856-1600x663.png" style="width: 100%;" /><p>2. Incluye el nombre de la variable en la búsqueda ES|QL.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9858442abb26d8d266d464852871b139fde63b89-1600x665.png" style="width: 100%;" /><h3>Ejemplo 4: variables para campos</h3><ol><li>Puedes usar el tipo de control “Valores estáticos” y escribir los nombres de los campos que desees. Es importante usar un nombre de variable que comience con “??...” para que funcione en los campos.</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/33534c3df2fae024b25c28b4aed5d742e54202a2-1600x710.png" style="width: 100%;" /><p>2. Crea una referencia a la variable donde la necesites en la búsqueda de la visualización.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/71cdf3e9df72c59d957628a3aa6e4aa9bd60d6d5-1600x676.png" style="width: 100%;" /><h2>Controles de variables en Discover</h2><p>Los controles variables no son solo una característica del dashboard: también están disponibles directamente en el editor de ES|QL en Discover. Puede crear controles para obtener una experiencia de exploración de datos más rápida en Discover, llevarlos al dashboard y viceversa.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fdddeec902d0bc746caed9276d01d7d48793dd85-1600x709.png" style="width: 100%;" /><h2>Detalles técnicos</h2><p>A estas alturas, probablemente hayas notado que los controles de variables incluyen algunas reglas, como a qué partes de una búsqueda pueden hacer referencia y los prefijos de nombres que necesitas usar (“?...” para valores y “??...” para campos o funciones). Eso se debe a que las variables no son simplemente reemplazos de texto que ocurren en el cliente. En realidad, son ciudadanos de primera clase en el propio lenguaje de búsquedas (conocidos como <a href="https://www.elastic.co/docs/solutions/search/agent-builder/tools/esql-tools#parameter-types">parámetros en ES|QL</a>).</p><p></p><p>Este diseño ofrece grandes ventajas. Por un lado, Kibana puede entender el contexto de cada variable, lo que nos permite generar y completar previamente, de manera automática, su configuración. Además, es mucho más seguro: debido a que el lenguaje valida estrictamente las entradas de variables, previene las inyecciones maliciosas y genera errores fácilmente, si algo parece incorrecto. Asimismo, mejora el rendimiento y la estabilidad al trasladar la compleja validación y el manejo de errores al servidor en lugar de al cliente. Recordatorio sobre el rendimiento: una de las mejores prácticas es crear variables que incluyan búsquedas rápidas, ya que se cargan antes que el dashboard, por lo que las búsquedas lentas pueden afectar al rendimiento de todo el dashboard.</p><p>Por supuesto, esta arquitectura también tiene algunas <a href="https://www.elastic.co/docs/solutions/search/agent-builder/limitations-known-issues#esql-limitations">limitaciones</a>—por ahora. Las variables aún no admiten una opción “Cualquiera” para filtrar, y actualmente no se pueden usar con ciertos operadores como <code>LIKE</code><strong> </strong>o <code>FROM</code> (para cambiar fuentes de datos). ¿La buena noticia? Estamos trabajando activamente para agregar estas funciones.</p><h2>Qué depara el futuro para los controles</h2><p>¡Este no es el final! Algunas de las mejoras importantes incluyen:</p><p>✨ La capacidad de colocar controles en cualquier lugar del dashboard</p><p>✨ Encadenar tus controles, lo que significa que la salida de un control se convierte en la entrada para el siguiente</p><p>✨ Mejores opciones de selección como la opción “Cualquiera” para variables.</p><p>✨ Nuevos tipos de control (control de tipo búsqueda y variables para tus fuentes de datos)</p><p>✨ Y otras mejoras en la experiencia del usuario que han estado solicitando, como el filtro previo de controles normales.</p><p>Si tienes ideas o comentarios, nos encantaría saber de ti.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Indexación vectorial hasta 12 veces más rápida en Elasticsearch con NVIDIA cuVS: aceleración por GPU, capítulo 2]]></title>
            <description><![CDATA[Descubre cómo Elasticsearch logra un rendimiento de indexación casi 12 veces mayor. Esto lo consigue con la indexación vectorial acelerada por GPU y NVIDIA cuVS.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elasticsearch-gpu-accelerated-vector-indexing-nvidia</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elasticsearch-gpu-accelerated-vector-indexing-nvidia</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Chris Hegarty, Hemant Malik, Corey Nolet, Manas Singh, Mithun Radhakrishnan, Mayya Sharipova, Lorenzo Dematte, Ben Frederickson]]></dc:creator>
            <pubDate>Wed, 03 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/08f7469a4daaf67b7c5999585aae179b6680c78d-896x746.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>A principios de este año, Elastic anunció la <a href="https://ir.elastic.co/news/news-details/2025/Elastic-Brings-Enterprise-Data-to-NVIDIA-AI-Factories/default.aspx">colaboración</a> con NVIDIA para llevar la aceleración por GPU a Elasticsearch, integrándola con <a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS</a>, como se detalló en una <a href="https://www.nvidia.com/en-us/on-demand/session/gtc25-S71286/">sesión en NVIDIA GTC</a> y en varios <a href="https://www.elastic.co/search-labs/blog/gpu-accelerated-vector-search-elasticsearch-nvidia">blogs</a>. Esta publicación es una actualización sobre el esfuerzo de co-ingeniería con el equipo de búsqueda de vectores de NVIDIA.</p><h2>Resumen</h2><p>Primero, pongámonos al día. Elasticsearch se consolidó como una poderosa base de datos vectorial, que ofrece un amplio conjunto de características y un sólido rendimiento para la búsqueda de similitudes a gran escala. Con capacidades como la cuantificación escalar, Better Binary Quantization (<a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">BBQ</a>), operaciones vectoriales <a href="https://www.elastic.co/blog/accelerating-vector-search-simd-instructions">SIMD</a> y algoritmos más eficientes en disco como <a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction">DiskBBQ</a>, ya ofrece opciones eficientes y flexibles para gestionar cargas de trabajo vectoriales.</p><p>Al integrar NVIDIA cuVS como un módulo invocable para tareas de búsqueda vectorial, nuestro objetivo es ofrecer beneficios significativos en el rendimiento y la eficiencia de la indexación vectorial para soportar mejor las cargas de trabajo vectoriales a gran escala.</p><h2>El desafío</h2><p>Uno de los mayores desafíos al construir una base de datos vectorial de alto rendimiento es construir el índice vectorial: el grafo <a href="https://arxiv.org/abs/1603.09320">HNSW</a>. La construcción de índices se ve dominada con rapidez por millones o incluso miles de millones de operaciones aritméticas a medida que cada vector se compara con muchos otros. Además, las operaciones del ciclo de vida del índice, como la compactación y las fusiones, pueden aumentar aún más la sobrecarga total de cálculo de la indexación. A medida que los volúmenes de datos y las incrustaciones vectoriales asociadas crecen de manera exponencial, las GPU de cómputo acelerado, diseñadas para el paralelismo masivo y las matemáticas de alto rendimiento, se posicionan idealmente para manejar estas cargas de trabajo.</p><h2>Ingresa al plugin Elasticsearch-GPU</h2><p><a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS</a> es una biblioteca open source de CUDA-X para la búsqueda vectorial acelerada por GPU y la agrupación de datos, que permite una rápida construcción de índices y recuperación de incrustaciones para cargas de trabajo de AI y de sistemas de recomendación.</p><p>Elasticsearch utiliza cuVS a través de <a href="https://mvnrepository.com/artifact/com.nvidia.cuvs/cuvs-java">cuvs-java</a>, una biblioteca open source desarrollada por la comunidad y que mantiene NVIDIA. La biblioteca cuvs-java es ligera, se basa en la <a href="https://docs.rapids.ai/api/cuvs/nightly/c_api/">API C de cuVS</a> y usa la función externa del <a href="https://openjdk.org/projects/panama/">Proyecto Panamá</a> para exponer las características de cuVS de una manera idiomática en Java, al tiempo que es moderna y presenta un alto rendimiento.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5f6daa1eb07f704a6707d9e6b7ccb81d0abaa8c9-566x419.png" style="width: 100%;" /><p>La biblioteca cuvs-java está integrada en un <a href="https://github.com/elastic/elasticsearch/pull/135545">nuevo plugin de Elasticsearch</a>; por lo tanto, la indexación vectorial en la GPU puede ocurrir en el mismo nodo y proceso de Elasticsearch, sin la necesidad de provisionar ningún código o hardware externo. Durante la construcción del índice, si se instala la biblioteca cuVS y hay una GPU presente y configurada, Elasticsearch usará la GPU para acelerar el proceso de indexación vectorial. Los vectores se asignan a la GPU, que construye un grafo <a href="https://arxiv.org/abs/2308.15136">CAGRA</a>. Este grafo se convierte entonces al formato HNSW, y hace que esté disponible de inmediato para la búsqueda vectorial en la CPU. El formato final del grafo construido es el mismo que el que se construiría en la CPU; esto permite a Elasticsearch aprovechar las GPU para indexar vectores de alto rendimiento cuando el hardware subyacente lo admite, al tiempo que libera poder de la CPU para otras tareas (búsqueda simultánea, procesamiento de datos, etc.).</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3ea255bd9bfd7983f78143c5eba999d2149d72be-671x356.png" style="width: 100%;" /><h2>Aceleración en la generación de índices</h2><p>Como parte de la integración de la aceleración de GPU en Elasticsearch, se realizaron varias mejoras en cuvs-java, centradas en la entrada/salida eficiente de datos y la invocación de funciones. Una mejora clave es el uso de <a href="https://github.com/rapidsai/cuvs/blob/2cf5fa7666d703dccbe655f8214656b0952bb69b/java/cuvs-java/src/main/java/com/nvidia/cuvs/CuVSMatrix.java">cuVSMatrix</a> para modelar vectores de forma transparente, ya sea que residan en la memoria heap de Java, fuera de la memoria heap o en la memoria de la GPU. Esto permite que los datos se muevan de manera eficiente entre la memoria y la GPU, lo que evita copias innecesarias de potencialmente miles de millones de vectores.</p><p>Gracias a esta abstracción subyacente de copia cero, tanto la transferencia a la memoria de la GPU como la recuperación del grafo se pueden realizar directamente. Durante la indexación, los vectores se almacenan primero en búfer en la memoria heap de Java, y luego se envían a la GPU para construir el grafo CAGRA. Después, el grafo se recupera de la GPU, se convierte al formato HNSW y se mantiene en el disco.</p><p>En el momento de la fusión, los vectores ya están almacenados en disco, sin pasar por la memoria heap de Java. Los archivos de índice se mapean en memoria, y los datos se transfieren directamente a la memoria de la GPU. El diseño también se adapta fácilmente a diferentes anchos de bits, como float32 o int8, y se extiende de forma natural a otros esquemas de cuantificación.</p><h2>Redoble de tambores… entonces, ¿cómo se funciona?</h2><p>Antes de entrar en los números, un poco de contexto es útil. La fusión de segmentos en Elasticsearch suele ejecutarse automáticamente en segundo plano durante la indexación, lo que dificulta hacer benchmarks de forma aislada. Para obtener resultados reproducibles, utilizamos la fusión forzosa para activar explícitamente la combinación de segmentos en un experimento controlado. Dado que la fusión forzosa hace las mismas operaciones de fusión subyacentes que la fusión en segundo plano, su rendimiento sirve como un indicador útil de las mejoras esperadas, aunque las ganancias exactas pueden diferir en las cargas de trabajo de indexación del mundo real.</p><p>Ahora, veamos los números.</p><p>Nuestros resultados iniciales de evaluaciones comparativas son muy prometedores. Ejecutamos la evaluación comparativa en una instancia de AWS <code>g6.4xlarge</code> con almacenamiento NVMe conectado a nivel local. Se configuró un único nodo de Elasticsearch para que use el número predeterminado y óptimo de subprocesos de indexación (8, uno por cada núcleo físico) y deshabilite <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/merge">la limitación de la fusión</a> (que es menos aplicable con discos NVMe rápidos).</p><p>Para el conjunto de datos, utilizamos 2,6 millones de vectores con 1536 dimensiones del <a href="https://github.com/elastic/rally-tracks/blob/master/openai_vector/README.md">vector de pista de Rally de OpenAI</a>, codificados como <a href="https://github.com/elastic/elasticsearch/pull/137072">cadenas de texto base64</a> e indexados como float32 <em>hnsw</em>. En todos los escenarios, los grafos construidos alcanzan niveles de recuperación de hasta el 95 %. A continuación, presentamos nuestros hallazgos:</p><ul><li><strong>Rendimiento de indexación:</strong> al trasladar la construcción de grafos a la GPU durante los vaciados de búferes en memoria, aumentamos el rendimiento en aproximadamente 12 veces.</li><li><strong>Fusión forzada:</strong> una vez que finaliza la indexación, la GPU continúa acelerando la fusión de segmentos, lo que acelera la fase de fusión forzada en alrededor de 7 veces.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f60ea9ee5996e456f393ffd195ee7eada6e5a7c2-948x387.png" style="width: 100%;" /><ul><li><strong>Uso de la CPU:</strong> descargar la construcción de grafos a la GPU reduce de manera significativa el uso promedio y máximo de la CPU. Los grafos a continuación ilustran el uso de la CPU durante la indexación y la fusión, y permiten ver cuánto menor es cuando estas operaciones se ejecutan en la GPU. Un menor uso de la CPU durante la indexación por GPU libera ciclos de CPU que pueden redirigirse para mejorar el rendimiento de la búsqueda.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5e680a5fc41700a877f3d8b2e5ce18ebd3f37a0b-1600x562.png" style="width: 100%;" /><ul><li><strong>Recuperación:</strong> la precisión se mantiene prácticamente igual entre las ejecuciones de CPU y GPU, y el grafo construido por la GPU alcanza una recuperación un poco mayor.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/48a2b7758606bd321712b7d8378cd2640e652a4e-1384x544.png" style="width: 100%;" /><h2>Comparación en otra dimensión: el precio</h2><p>La comparación anterior utilizaba intencionalmente hardware idéntico, y la única diferencia era si se usaba la GPU durante la indexación o no. Esa configuración es útil para aislar los efectos de la computación en bruto, pero también podemos analizar la comparación desde una perspectiva de costos.</p><p>A un precio horario similar al de la configuración acelerada por GPU, se puede provisionar una configuración de solo CPU con aproximadamente el doble de recursos comparables de CPU y memoria: 32 vCPUs (AMD EPYC) y 64 GB de RAM, lo que permite duplicar el número de hilos de indexación a 16.</p><p>Para mantener la comparación justa y consistente, ejecutamos este experimento solo de CPU en una instancia AWS g6.8xlarge, con la GPU explícitamente desactivada. Esto nos permitió mantener constantes todas las demás características del hardware mientras evaluábamos la compensación entre costo y rendimiento de la aceleración de GPU frente al indexado solo con CPU.</p><p>Como era de esperar, la instancia de CPU más potente sí muestra un mejor rendimiento en comparación con las evaluaciones comparativas de la sección anterior. Sin embargo, cuando comparamos esta instancia de CPU más potente con los resultados acelerados por GPU originales, la GPU aún ofrece beneficios sustanciales en cuanto al rendimiento: <strong>~ 5 veces</strong> de mejora en el rendimiento de indexación y <strong>~ 6 veces </strong>en la fusión forzada, todo ello mientras se construyen grafos que alcanzan niveles de recuperación de hasta un <strong>95 %.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8ffa58cae3ad175ef2932a351aeef4c34a1407b9-948x394.png" style="width: 100%;" /><h2>Conclusión</h2><p>En escenarios completos, la aceleración por GPU con NVIDIA cuVS ofrece una mejora de casi 12 veces en el rendimiento de la indexación y una disminución de 7 veces en la latencia de fusión forzada, con un uso de CPU significativamente menor. Esto demuestra que la indexación vectorial y las cargas de trabajo de fusión se benefician de manera significativa de la aceleración por GPU. En una comparación ajustada por costos, la aceleración por GPU continúa mostrando beneficios sustanciales en cuanto al rendimiento, con alrededor de 5 veces más rendimiento de indexación y 6 veces más rapidez en las operaciones de fusión forzada.</p><p>La indexación vectorial acelerada por GPU tiene planificada hoy en día una vista previa técnica en Elasticsearch 9.3, cuyo lanzamiento está programado para principios de 2026.</p><p>Mantente atento a lo que viene.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ES|QL en la versión 9.2: incorporación de la búsqueda inteligente (Lookup Joins) y compatibilidad con series temporales]]></title>
            <description><![CDATA[Explora tres actualizaciones separadas de ES|QL en Elasticsearch 9.2: un LOOKUP JOIN mejorado para una correlación de datos más expresiva, el nuevo comando TS para análisis de series temporales y el comando flexible INLINE STATS para la agregación.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/esql-elasticsearch-9-2-multi-field-joins-ts-command</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/esql-elasticsearch-9-2-multi-field-joins-ts-command</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Tyler Perkins, Kostas Krikellas, Julian Kiryakov]]></dc:creator>
            <pubDate>Tue, 02 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/415894e21e7758c907d6e60d4efc94230349beef-2012x1164.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch 9.2, lanzado en octubre, está repleto de avances significativos que hacen que el análisis de tus datos sea más rápido, más flexible y más accesible que nunca. En el corazón de esta versión se encuentran importantes mejoras a ES|QL, nuestro lenguaje de búsqueda canalizado, diseñado para brindar aún más valor directamente a los usuarios finales.</p><p>A continuación, se muestran las características de Elasticsearch 9.2 que transformarán tus flujos de trabajo de análisis de datos con ES|QL.</p><h2>Revolucionando la correlación de datos: Lookup Join más inteligente, rápido y flexible</h2><p>El comando <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join">LOOKUP JOIN</a> en ES|QL experimentó una transformación significativa en Elasticsearch 9.2, y se volvió mucho más eficiente y versátil. Lookup JOIN combina datos de la tabla de resultados de búsquedas ES|QL con registros coincidentes de un índice de modo de consulta especificado. Agrega campos del índice de búsqueda como nuevas columnas a la tabla de resultados en función de los valores coincidentes en el campo de combinación. Anteriormente, la unión de datos se limitaba a un solo campo y a una igualdad simple. ¡Ya no! Estas mejoras te permiten abordar escenarios complejos de correlación de datos con facilidad.</p><p><strong>Las mejoras clave de Lookup Join incluyen:</strong></p><ul><li><strong>Uniones de múltiples campos:</strong> únete fácilmente a varios campos. Por ejemplo, para unir <code>application_logs</code> con <code>service_registry</code> en <code>service_name</code>, <code>environment</code> y <code>version:</code></li></ul><pre><code>FROM application_logs
| LOOKUP JOIN service_registry ON service_name, environment, version</code></pre><ul><li><strong>Utilización de predicados de unión complejos con expresiones (vista previa técnica):</strong></li></ul><p>Ya no estás limitado a la igualdad simple. LOOKUP JOIN ahora permite especificar <strong>múltiples criterios</strong> de correlación e incorporar una variedad de <strong>operadores binarios,</strong> como ==, !=, &lt;, &gt;, &lt;= y &gt;=. Esto significa que puedes crear condiciones de unión muy matizadas, lo que te permite plantear preguntas mucho más complejas sobre tus datos.</p><p>Ejemplo 1: Búsqueda de métricas de aplicaciones con umbrales de SLA por servicio</p><pre><code>FROM application_metrics
| LOOKUP JOIN sla_thresholds
      ON service_name == sla_service AND response_time > sla_response_time</code></pre><p>Ejemplo 2: Esta búsqueda calcula el monto adeudado, basado en políticas de precios regionales que cambian con el tiempo. Une tres sets de datos basados en condiciones complejas de rango de fechas e igualdad para calcular un <code>due_amount</code> final. La segunda unión de búsqueda utiliza el campo <code>measurement_date</code> del índice de <code>meter_readings</code> y el campo <code>region_id</code> del índice de <code>customers</code> para unirse al índice de <code>pricing_policies</code> y encontrar la política de precios correcta según la <code>region</code> y la <code>measurement_date</code>particular.</p><pre><code>FROM meter_readings
| LOOKUP JOIN customers
      ON meter_id
| LOOKUP JOIN pricing_policies
      ON
        region_id == region AND
          measurement_date >= policy_begin_date AND
          measurement_date < policy_end_date
| EVAL due_amount = (kwh_consumed * rate_per_kwh + base_charge) * (1 + tax_rate)
| EVAL period = policy_name
| KEEP customer_name, period, due_amount, measurement_date, kwh_consumed,
    rate_per_kwh, base_charge, tax_rate
| SORT measurement_date</code></pre><ul><li><strong>Grandes ganancias de rendimiento para uniones filtradas: </strong></li></ul><p>Hemos mejorado el rendimiento de las &quot;uniones en expansión&quot; que se filtran al utilizar condiciones de tabla de búsqueda. Las uniones expansivas producen múltiples coincidencias por fila de entrada, lo que puede generar grandes conjuntos de resultados intermedios. Esto empeora cuando muchas de esas filas se descartan mediante un filtro posterior. En la versión 9.2, optimizamos estas uniones al filtrar las filas innecesarias cuando se aplica un filtro a los datos de búsqueda, lo que evita procesar filas que se descartarían. ¡En algunos casos, estas uniones pueden ser hasta <strong>1000 veces más rápidas</strong>!</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/002c014ee29b1aaf9ddeb8c554bb76efe3ed180c-1572x954.png" style="width: 100%;" /><p>Esta optimización es crucial cuando se trata de &quot;uniones en expansión&quot;, en las que una búsqueda podría generar inicialmente muchas coincidencias potenciales. Al aplicar filtros de forma inteligente, solo se procesan los datos relevantes, lo que reduce drásticamente el tiempo de ejecución de las consultas y permite realizar análisis en tiempo real en sets de datos masivos. Esto significa que obtienes tu información mucho más rápido, incluso con operaciones de unión muy grandes o complejas.</p><p><strong>Compatibilidad de la búsqueda de agrupación con Cross-Cluster Search (CCS):</strong></p><p>Cuando Lookup Join se lanzó al mercado en las versiones 8.19 y 9.1, carecía de compatibilidad con Cross-Cluster Search (CCS). Para organizaciones que operan en múltiples agrupaciones, LOOKUP JOIN ahora se integra perfectamente con CCS en la versión 9.2. Simplemente coloca tu índice de búsqueda en todos los clústeres remotos donde desees realizar una unión, y ES|QL aprovechará automáticamente estos índices de búsqueda para unirse a sus datos remotos. Esto simplifica el análisis distribuido de datos y garantiza un enriquecimiento consistente en todo el despliegue de Elasticsearch.</p><p>Estas mejoras permiten correlacionar diversos conjuntos de datos con una precisión, velocidad y facilidad sin precedentes, lo que permite obtener información más profunda y útil sin necesidad de soluciones alternativas complejas ni pasos de preprocesamiento.</p><h2>Enriquece tus datos con facilidad: Kibana Discover UX para índices de búsqueda</h2><p>El enriquecimiento de datos debe ser sencillo, no un obstáculo. Introdujimos una experiencia de usuario fantástica en Discover de Kibana para crear y gestionar índices de búsqueda.</p><p><strong>Flujo de trabajo intuitivo:</strong> el autocompletado integral de Discover te guiará a través del proceso y te sugerirá índices de búsqueda y campos de unión en el editor ES|QL, lo que hace que sea increíblemente fácil conectar tus datos de monitoreo del tiempo de actividad con índices existentes. Escribe el nombre de un índice de búsqueda que no exista y obtén acceso directo al editor de búsqueda con un clic para crear el índice. Escribe el nombre de un índice de búsqueda existente y te sugeriremos una opción para editarlo:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d36fd6ffd6bc0bf8d31067f6266445c68d15c71c-1400x184.png" style="width: 100%;" /><p><strong>Gestión en línea (CRUD):</strong> mantén actualizados tus sets de datos de referencia con las funciones de edición en línea (crear, leer, actualizar, eliminar) directamente en Discover.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a0e4aa66540b1f725c24ccb0519d978415073bb6-1453x842.png" style="width: 100%;" /><p><strong>Carga de archivos sin esfuerzo: </strong>ahora puedes subir archivos directamente, como CSVs, dentro de Discover y usarlos instantáneamente en <code>LOOKUP JOIN</code>. ¡Ya no es necesario cambiar de contexto al saltar de un área a otra de Kibana!</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0f5dc2c712af4c4cada50292a7c8b836eb02aa67-1600x748.png" style="width: 100%;" /><p>Ya sea que estés usando mapping de IDs de usuario a nombres, agregando metadatos empresariales o uniendo archivos de referencia estáticos, esta característica democratiza el enriquecimiento de datos, lo que pone el poder de las uniones directamente en manos de cada usuario de forma rápida, sencilla y en un solo lugar.</p><h2>Preserva tu contexto: presentación de INLINE STATS (versión preliminar de tecnología)</h2><p>La agregación de datos es crucial, pero a veces necesitas ver los agregados <em>junto a</em> tus datos originales. Nos complace presentar <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/inlinestats-by">INLINE STATS</a> como una <strong>característica de vista previa técnica</strong>.</p><p>A diferencia del comando <code>STATS</code>, que reemplaza tus campos de entrada por una salida agregada, <code>INLINE STATS</code> conserva todos tus campos de entrada originales y simplemente agrega los nuevos campos agregados. Esto te permite realizar más operaciones en tus campos de entrada originales <em>después</em> de la agregación, lo que genera un flujo de trabajo de análisis más continuo y flexible.</p><p>Por ejemplo, para calcular la distancia promedio de vuelo mientras se mantienen las filas de vuelo individuales:</p><pre><code>FROM kibana_sample_data_flights
 | KEEP Carrier, Dest, DistanceMiles
 | INLINE STATS avgDist = ROUND(AVG(DistanceMiles))
       BY Dest
 | WHERE DistanceMiles > avgDist</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6eea943035e0ab371270084c504a06bb89f8b82b-1496x290.png" style="width: 100%;" /><p>En esta consulta, se agrega <code>avgDist</code> a cada fila con el <code>Dest</code>correspondiente (ination) por el que agrupamos y, como aún tenemos las columnas de información de vuelo, podemos filtrar los resultados a los vuelos con una distancia mayor que la media.</p><h2>Compatibilidad con series temporales en ES|QL (vista previa técnica)</h2><p>Elasticsearch usa <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">flujos de datos temporales</a> para almacenar métricas. Estamos agregando soporte para agregaciones de series temporales en ES|QL, a través del comando fuente <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code></a>. Esto está disponible en Elastic Cloud Serverless y la versión 9.2 básica como vista previa técnica.</p><p>El análisis de series temporales se basa en gran medida en consultas de agregación que resumen los valores métricos a lo largo de las cubetas de tiempo, divididos por una o más dimensiones de filtrado. La mayoría de las consultas de agregación se basan en un procesamiento de dos pasos, con (a) una función de agregación interna que resume los valores por serie temporal y (b) una función de agregación externa, que combina los resultados de (a) en todas las series temporales.</p><p>El comando de origen <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code></a>, combinado con <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/stats-by"><code>STATS</code></a>, proporciona una forma concisa, pero efectiva de expresar tales consultas sobre series temporales. Más concretamente, considera el siguiente ejemplo para calcular la tasa total de solicitudes por host y hora:</p><pre><code>TS my_metrics
| WHERE @timestamp > NOW() - 1 day
| STATS SUM(RATE(requests))
      BY host, TBUCKET(1h)</code></pre><p>En este caso, la función de agregación de seriales temporales <code>RATE</code> se evalúa primero por series temporales y hora. Los agregados parciales producidos se combinan luego al usar <code>SUM</code> para calcular los valores agregados finales por host y por hora.</p><p>Puedes consultar la lista de funciones de agregación de series temporales disponibles <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/time-series-aggregation-functions">aquí</a>. Ahora se admite la tasa <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds#time-series-metric">de contador</a>, posiblemente la función de agregación más importante para procesar contadores.</p><p>El comando fuente <code>TS</code> está diseñado para combinarse con <code>STATS</code>, con ejecución ajustada para soportar eficientemente agregaciones de series temporales. Por ejemplo, los datos se ordenan antes de pasar a las <code>STATS</code>. Actualmente, no se permiten comandos de procesamiento que puedan enriquecer o alterar los datos temporales o su orden, como <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fork"><code>FORK</code></a> o <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/inlinestats-by"><code>INLINE STATS</code></a>, entre <code>TS</code> y <code>STATS</code>. Esta limitación podría eliminarse en el futuro.</p><p>La salida tabular <code>STATS</code> se puede procesar aún más con cualquier comando aplicable. Por ejemplo, la siguiente búsqueda calcula la relación del promedio de <code>cpu_usage</code> por host hospedado y hora con el valor máximo por host:</p><pre><code>TS my_metrics
| STATS avg_usage = AVG(AVG_OVER_TIME(cpu_usage))
      BY host, time_bucket = TBUCKET(1h)
| INLINE STATS max_avg_usage = MAX(avg_usage)
      BY host
| EVAL ratio = avg_usage / max_avg_usage
| KEEP host, time_bucket, ratio
| SORT host, time_bucket DESC</code></pre><p>Los datos temporales se almacenan en nuestro motor de almacenamiento columnar subyacente que funciona con los valores de documentos de Lucene. El comando TS agrega ejecución de consultas vectorizadas a través del motor de cómputo ES|QL. El rendimiento de las búsquedas a menudo se mejora en más de un orden de magnitud, en comparación con las consultas <a href="https://www.elastic.co/docs/reference/query-languages/querydsl">DSL</a> equivalentes, y está a la par con los sistemas establecidos específicos de métricas. En el futuro ofreceremos un análisis detallado de arquitectura y rendimiento, así que mantente alerta.</p><h2>Ampliación de tu conjunto de herramientas: funciones nuevas de ES|QL</h2><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><p><strong>Manipulación de cadenas: </strong><a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/string-functions#esql-contains">CONTAINS</a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/mv-functions#esql-mv_contains">MV_CONTAINS</a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/string-functions#esql-url_encode">URL_ENCODE</a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/string-functions#esql-url_encode_component">URL_ENCODE_COMPONENT</a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/string-functions#esql-url_decode">URL_DECODE</a> para un procesamiento más robusto de texto y URL.</p><p><strong>Serie temporal y geoespacial:</strong> <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/grouping-functions#esql-tbucket">TBUCKET</a> para cubetas de tiempo flexibles, TO_DENSE_VECTOR para operaciones vectoriales y un conjunto completo de <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/spatial-functions">funciones geoespaciales</a> como <code>ST_GEOHASH</code>, <code>ST_GEOTILE</code>, <code>ST_GEOHEX</code>, <code>TO_GEOHASH</code>, <code>TO_GEOTILE</code>, <code>TO_GEOHEX</code> para un análisis avanzado basado en la ubicación.</p><p><strong>Formato de fechas:</strong> <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/date-time-functions#esql-day_name">DAY_NAME</a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/date-time-functions#esql-month_name">MONTH_NAME</a> para representaciones de fechas más legibles.</p><p>Estas funciones te proporcionan un conjunto más completo de herramientas para manipular y analizar tus datos directamente dentro de ES|QL.</p><h2>Bajo el capó: Más rendimiento y eficiencia</h2><p>Más allá de las características destacadas, Elasticsearch 9.2 incluye varias optimizaciones de rendimiento en ES|QL. Aceleramos <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/where#like-and-rlike">RLIKE (LIST</a>) con pushdown en casos en los que la función reemplaza múltiples consultas RLIKE similares. Con <code>RLIKE</code> (LIST), podemos fusionar esas búsquedas en un único autómata y aplicar un autómata en vez de varios. También tenemos una carga más rápida de los campos de palabras clave con ordenamientos de índice y optimizaciones generales de consultas; estas mejoras aseguran que tus consultas ES|QL se ejecuten más eficientemente que nunca.</p><h2>¡Comienza hoy mismo!</h2><p>Elasticsearch 9.2 representa un avance significativo para ES|QL, ya que brinda un poder y flexibilidad sin precedentes a sus flujos de trabajo de análisis de datos. Te invitamos a explorar estas funciones nuevas y a experimentar la diferencia que generan.</p><p>Para obtener una lista completa de todos los cambios y mejoras de Elasticsearch 9.2, consulte las <a href="https://www.elastic.co/guide/en/elasticsearch/reference/9.2/release-notes-9.2.0.html">notas de lanzamiento oficiales</a>. ¡Feliz búsqueda!</p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/es/blog/chatgpt-connector-mcp-server-github-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/chatgpt-connector-mcp-server-github-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
            <pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <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>Clúster de Elasticsearch (8.X o superior).</li><li>Clave API de Elasticsearch con acceso de lectura a tu índice.</li><li>Cuenta de Google (para Google Colab)</li><li>Cuenta de Ngrok (el nivel gratuito funciona)</li><li>Cuenta de ChatGPT con plan Pro/Enterprise/Business o Edu.</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>Un solo texto con la búsqueda de lenguaje natural del usuario.</li><li>Ejemplo: “Encuentra incidencias relacionadas con la migración de Elasticsearch”.</li></ul><h4>Lo que devuelve: </h4><ul><li>Un objeto con una clave <code>result</code> que contiene un arreglo de objetos de resultado. Cada resultado incluye:<ul><li><code>id</code> - Identificador único de documentos.</li><li><code>title</code> - Título de la incidencia o PR.</li><li><code>url</code> - Enlace a la incidencia o PR.</li></ul></li></ul><h4>En nuestra implementación:</h4><pre><code>return {
    "results": [
        {
            "id": "PR-612",
            "title": "Fix memory leak in WebSocket notification service",
            "url": "https://internal-git.techcorp.com/pulls/612"
        },
        # ... more results
    ]
}</code></pre><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>Una sola cadena de texto con el ID del documento de Elasticsearch del resultado de la búsqueda.</li><li>Ejemplo: “Consígueme los detalles de PR-578”.</li></ul><h4>Lo que devuelve:</h4><ul><li>Un objeto de documento completo con:<ul><li><code>id</code> - Identificador único de documentos.</li><li><code>title</code> - Título de la incidencia o PR.</li><li><code>text</code> - Descripción completa del problema/PR y sus detalles</li><li><code>url</code> - Enlace a la incidencia o PR.</li><li><code>type</code> - Tipo de documento (incidencia, pull_request).</li><li><code>status</code> - Estado actual (abierto, en progreso, resuelto)</li><li><code>priority</code> - Nivel de prioridad (bajo, medio, alto, crítico)</li><li><code>assignee</code> - Persona asignada al problema/PR</li><li><code>created_date</code> - Fecha de creación.</li><li><code>resolved_date</code> - Cuando se resolvió (si procede)</li><li><code>labels</code> - Etiquetas asociadas al documento</li><li><code>related_pr</code> - ID de solicitud de extracción relacionado</li></ul></li></ul><pre><code>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
}</code></pre><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>Problemas con descripciones, estado, prioridad y responsables.</li><li>Solicitudes de extracción con cambios de código, revisiones e información de despliegue.</li><li>Relaciones entre incidencias y PR (p. ej., PR-578 soluciona ISSUE-1889).</li><li>Etiquetas, fechas y otros metadatos</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><pre><code>{
  "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"
      }
    }
  }
}</code></pre><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><pre><code>@mcp.tool()
    async def search(query: str) -> 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)}")</code></pre><h3>Puntos clave:</h3><ul><li><strong>Búsqueda híbrida con RRF:</strong> Combina búsqueda semántica (ELSER) y búsqueda por texto (BM25) para mejores resultados.</li><li><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.</li><li><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.</li><li><strong>Ajuste de parámetros de RRF:</strong><ul><li><code>rank_window_size: 50</code> - Especifica cuántos resultados principales de cada recuperador (semántico y textual) se consideran antes de combinarlos.</li><li><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.</li></ul></li><li><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.</li></ul><h3>Herramienta de extracción</h3><p>Recupera los detalles del documento por ID de documento, si existe:</p><pre><code>@mcp.tool()
    async def fetch(id: str) -> 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)}")</code></pre><h3>Puntos clave:</h3><ul><li><strong>Búsqueda por campo de ID de documento:</strong> usa la búsqueda de término en el campo personalizado <code>id</code>.</li><li><strong>Devuelve el documento completo:</strong> incluye el campo completo <code>text</code> con todo el contenido.</li><li><strong>Estructura plana:</strong> todos los campos en el nivel raíz, coincidiendo con la estructura de documentos de Elasticsearch.</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><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>.</li><li><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.</li><li><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.</li></ul><h4>Obtener tu token ngrok</h4><ol><li>Regístrate para una cuenta gratis en <a href="https://ngrok.com/">ngrok</a></li><li>Ve a tu <a href="https://dashboard.ngrok.com/">dashboard de ngrok</a></li><li>Copia tu token de autenticación</li></ol><h4>Agregar secretos a Google Colab</h4><p>En el cuaderno de Google Colab:</p><ol><li>Haz clic en el <strong>icono de llave </strong>en la barra lateral izquierda para abrir <strong>Secretos</strong>.</li><li>Añade estos tres secretos:</li></ol><pre><code>ELASTICSEARCH_URL=https://your-cluster.elastic.com:443
ELASTICSEARCH_API_KEY=your-api-key
NGROK_TOKEN=your-ngrok-token</code></pre><p>3.&nbsp;&nbsp; Habilitar el acceso al cuaderno para cada secreto</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5dd6ac19fe816a562c6351fdb0f11369da0e877-609x321.jpg" style="width: 100%;" /><h3>Paso 3: Ejecutar el cuaderno</h3><ol><li>Haz clic en <strong>Tiempo de ejecución</strong> y, a continuación, en <strong>Ejecutar todo</strong> para ejecutar todas las celdas.</li><li>Espera que el servidor se inicie (aproximadamente 30 segundos).</li><li>Busque la salida que muestre su URL pública de ngrok</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f185100e8869624bc9e1c7b2b4eb32785e2d89e7-1189x283.png" style="width: 100%;" /><p>4. La salida mostrará algo como:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e02e625e91ed9136454e4401b184575fb03a336e-1052x465.jpg" style="width: 100%;" /><h2>Conéctate a ChatGPT.</h2><p>Ahora conectaremos el servidor MCP a tu cuenta de ChatGPT.</p><ol><li>Abre ChatGPT y ve a <strong>Configuración</strong>.</li><li>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.</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/32c02192912fc0e7e5a52e9399077ba7ae3b4901-739x715.png" style="width: 100%;" /><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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/15c53e5ccc381108a9dc0052cca05bf0fc97679a-755x683.png" style="width: 100%;" /><p><em><strong>Nota</strong>: 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><strong>Nombre:</strong> Elasticsearch MCP</li><li><strong>Descripción: </strong>MCP personalizado para buscar y extraer información interna de GitHub.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/162a85705cc8ac48a3f2f665551d513e0719f93d-479x684.png" style="width: 100%;" /><p>5. Presione <strong>Crear</strong> para guardar el MCP personalizado.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/97eb5fb0a32b86bfadfb35561f698616f217c049-913x629.png" style="width: 100%;" /><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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" style="width: 100%;" /><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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cfd781acbff8cd7c8095bbe29224f8b26d581f77-650x375.png" style="width: 100%;" /><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><pre><code>{
  "query": "Elasticsearch migration issues"
}</code></pre><h4>Respuesta de la herramienta:</h4><pre><code>{
  "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
  ]
}</code></pre><p>ChatGPT procesa los resultados y los presenta en un formato natural y conocido.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9d5b6cff85c7e54ccc2584b8ae96d45495fae8c1-923x1352.png" style="width: 100%;" /><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>La <strong>búsqueda semántica</strong> entiende conceptos como “actualización” y “<em>compatibilidad de versiones”.</em></li><li>La <strong>búsqueda de texto</strong> encuentra coincidencias exactas de &quot;<em>Elasticsearch</em>&quot; y &quot;migración&quot;.</li><li><strong>RRF</strong> combina y clasifica los resultados de ambos enfoques</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: <strong><em>“Obtén detalles de ISSUE-1889”</em></strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5c0db5245535ce67a36056e61e135bddc97ce496-934x629.png" style="width: 100%;" /><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><pre><code>{
  "id": "ISSUE-1889"
}</code></pre><h4>Respuesta de la herramienta:</h4><pre><code>{
  "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"
}</code></pre><p>ChatGPT sintetiza la información y la presenta de manera clara.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/410f19f213e94fc4e3c47eeef6e04b69e0c86159-602x462.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/54d8ffa117628a1e3afc317c3ab75d4f7731d7ab-767x1600.png" style="width: 100%;" /><h3>Entre bastidores</h3><h4>Indicación: «Obtén los detalles de ISSUE-1889»</h4><ol><li>Llamadas de ChatGPT <code>fetch(“ISSUE-1889”)</code></li><li>Elasticsearch recupera el documento completo</li><li>Devuelve un documento completo con todos los campos a nivel raíz.</li><li>ChatGPT sintetiza la información y responde con citas adecuadas.</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>
        </item>
        <item>
            <title><![CDATA[Desarrollo de un asistente RAG agente usando LangChain y Elasticsearch]]></title>
            <description><![CDATA[Aprende a construir un asistente de noticias de prensa de prensa agential usando LangChain y Elasticsearch que responda a consultas sobre artículos con enrutamiento adaptativo.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/agentic-rag-news-assistant-langchain-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/agentic-rag-news-assistant-langchain-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Kirti Sodhi]]></dc:creator>
            <pubDate>Fri, 28 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d7213956309c6b5863627aca2ba95569b02fc4c7-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Esta entrada de blog profundiza en los flujos de trabajo de RAG agentes, explicando sus características clave y patrones de diseño habituales. Además, demuestra cómo implementar estos flujos de trabajo mediante un ejemplo práctico que emplea Elasticsearch como almacén vectorial y LangChain para construir el marco agente RAG. Finalmente, el artículo analiza brevemente las mejores prácticas y los desafíos asociados al diseño e implementación de dichas arquitecturas. Puedes seguir el proceso para crear una pipeline agente RAG sencilla con este <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/agentic-rag/agent_rag_news_assistant.ipynb">cuaderno Jupyter</a>.</p><h2>Introducción al RAG agente</h2><p>La Generación Aumentada por Recuperación (<a href="https://www.elastic.co/docs/solutions/search/rag">RAG</a>) se convirtió en una piedra angular en aplicaciones basadas en LLM, permitiendo a los modelos proporcionar respuestas óptimas al recuperar el contexto relevante basado en las consultas de los usuarios. Los sistemas RAG mejoran la precisión y el contexto de las respuestas de los LLM al aprovechar información externa de APIs o almacenes de datos, en lugar de limitar al conocimiento preentrenado de los LLM. Por otro lado, los agentes de IA operan de forma autónoma, tomando decisiones y tomando medidas para alcanzar sus objetivos designados.</p><p>El RAG agente es un marco que unifica las fortalezas tanto de la generación aumentada por recuperación como del razonamiento agentivo. Integra RAG en el proceso de toma de decisiones del agente, permitiendo al sistema elegir dinámicamente fuentes de datos, refinar consultas para una mejor recuperación de contexto, generar respuestas más precisas y aplicar un bucle de retroalimentación para mejorar continuamente la calidad de la salida.</p><h2>Características clave del RAG agente</h2><p>El marco RAG agente supone un avance importante respecto a los sistemas RAG tradicionales. En lugar de seguir un proceso de recuperación fijo, aprovecha agentes dinámicos capaces de planear, ejecutar y optimizar resultados en tiempo real.</p><p>Veamos algunas de las características clave que distinguen a las pipelines RAG agenticas:</p><ul><li><strong>Toma de decisiones dinámica</strong>: El RAG agente emplea un mecanismo de razonamiento para entender la intención del usuario y enrutar cada consulta a la fuente de datos más relevante, produciendo respuestas precisas y conscientes del contexto.</li><li><strong>Análisis exhaustivo de consultas:</strong> Agentic RAG analiza en profundidad las consultas de los usuarios, incluyendo subpreguntas y su intención general. Evalúa la complejidad de las consultas y selecciona dinámicamente las fuentes de datos más relevantes para obtener información, cerciorando respuestas precisas y completas.</li><li><strong>Colaboración en varias etapas</strong>: Este marco permite la colaboración en varias etapas a través de una red de agentes especializados. Cada agente gestiona una parte específica de un objetivo mayor, trabajando de forma secuencial o simultánea para lograr un resultado coherente.</li><li><strong>Mecanismos de autoevaluación</strong>: La cadena agente RAG emplea la autorreflexión para evaluar documentos recuperados y respuestas generadas. Puede comprobar si la información recuperada responde completamente a la consulta y luego revisar la salida para comprobar su exactitud, completitud y consistencia fáctica.</li><li><strong>Integración con herramientas externas</strong>: Este flujo de trabajo puede interactuar con APIs externas, bases de datos y fuentes de información en tiempo real, incorporando información actualizada y adaptar dinámicamente a los datos en evolución.</li></ul><h2>Patrones de flujo de trabajo del RAG agente</h2><p>Los patrones de flujo de trabajo definen cómo la IA agente estructura, gestiona y orquesta aplicaciones basadas en LLM de manera fiable y eficiente. Varios frameworks y plataformas, como <a href="https://www.langchain.com/">LangChain</a>, <a href="https://www.langchain.com/langgraph">LangGraph</a>, <a href="https://www.crewai.com/">CrewAI</a> y <a href="https://www.llamaindex.ai/">LlamaIndex</a>, pueden emplear para implementar estos flujos de trabajo agentes.</p><ol><li><strong>Cadena de recuperación secuencial</strong>: Los flujos de trabajo secuenciales dividen tareas complejas en pasos simples y ordenados. Cada paso mejora la entrada para el siguiente, lo que conduce a mejores resultados. Por ejemplo, al crear un perfil de cliente, un agente puede extraer datos básicos de un CRM, otro obtener el historial de compras de una base de datos de transacciones y un agente final combinar esta información para generar un perfil completo de recomendaciones o reportes.</li><li><strong>Cadena de recuperación de enrutamiento</strong>: En este patrón de flujo de trabajo, un agente router analiza la entrada y la dirige al proceso o fuente de datos más adecuada. Este enfoque es especialmente eficaz cuando existen múltiples fuentes de datos distintas con una superposición mínima. Por ejemplo, en un sistema de atención al cliente, el agente del router categoriza las solicitudes entrantes, como problemas técnicos, reembolsos o reclamaciones, y las encamina al departamento correspondiente para su gestión eficiente.</li><li><strong>Cadena de recuperación paralela</strong>: En este patrón de flujo de trabajo, se ejecutan simultáneamente múltiples subtareas independientes y sus salidas se agregan posteriormente para generar una respuesta final. Este enfoque reduce significativamente el tiempo de procesamiento y aumenta la eficiencia del flujo de trabajo. Por ejemplo, en un flujo de trabajo paralelo de atención al cliente, un agente recupera solicitudes pasadas similares y otro consulta artículos relevantes de la base de conocimiento. Un agregador combina entonces estas salidas para generar una resolución completa.</li><li><strong>Cadena de trabajadores Orchestrator</strong>: Este flujo de trabajo comparte similitudes con la paralelización debido a su utilización de subtareas independientes. Sin embargo, una distinción clave radica en la integración de un agente orquestador. Este agente es responsable de analizar las consultas de los usuarios, segmentarlas dinámicamente en subtareas durante la ejecución e identificar los procesos o herramientas adecuadas necesarias para formular una respuesta precisa.</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ece6fc2403f211556c93e99d5227bfb7053b0c31-1600x1047.png" style="width: 100%;" /><h2>Construyendo una pipeline RAG agentica desde cero</h2><p>Para ilustrar los principios del RAG agente, diseñemos un flujo de trabajo usando LangChain y Elasticsearch. Este flujo de trabajo adopta una arquitectura basada en enrutamiento, donde varios agentes colaboran para analizar consultas, recuperar información relevante, evaluar resultados y generar respuestas coherentes. Podrías consultar este <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/agentic-rag/agent_rag_news_assistant.ipynb">cuaderno Jupyter</a> para seguir este ejemplo.</p><p>El flujo de trabajo comienza con el agente router, que analiza la consulta del usuario para seleccionar el método óptimo de recuperación, es decir, un enfoque <code>vectorstore</code>, <code>websearch</code>o <code>composite</code> . La vectorstore se encarga de la recuperación tradicional de documentos basada en RAG, la búsqueda sitio web obtiene la información más reciente que no está almacenada en la vectorstore, y el enfoque compuesto combina ambas cuando se necesita información de múltiples fuentes.</p><p>Si los documentos se consideran adecuados, el agente de resumen genera una respuesta clara y contextualmente adecuada. Sin embargo, si los documentos son insuficientes o irrelevantes, el agente de reescritura de la consulta reformula la consulta para mejorar la búsqueda. Esta consulta revisada resetear entonces el proceso de enrutamiento, permitiendo al sistema refinar su búsqueda y mejorar la salida final.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ed8701a7f15558fbf2e967a884b3e770eccb826b-1256x1092.png" style="width: 100%;" /><h3>Prerrequisitos</h3><p>Este flujo de trabajo se basa en los siguientes componentes clave para ejecutar el ejemplo de forma eficaz:</p><ul><li>Python 3.10</li><li><a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/agentic-rag/agent_rag_news_assistant.ipynb">Cuaderno Jupyter</a></li><li>Azure OpenAI</li><li>Elasticsearch</li><li>LangChain</li></ul><p>Antes de continuar, se te pedirá que configures el siguiente conjunto de variables de entorno requeridas para este ejemplo.</p><pre><code>AZURE_OPENAI_ENDPOINT="Add your azure openai endpoint"
AZURE_OPENAI_KEY="Add your azure openai key"
AZURE_OPENAI_DEPLOYMENT="gpt-4.1"
AZURE_OPENAI_API_VERSION="Add your azure openai api version"

ES_ENDPOINT = "Add your Elasticsearch ENDPOINT"
ES_API_KEY = "Add your Elasticsearch API KEY"</code></pre><h3>Fuentes de datos</h3><p>Este flujo de trabajo se ilustra empleando un subconjunto del conjunto de datos de AG News. El conjunto de datos comprende artículos de noticias en diversas categorías, como Internacional, Deportes, Negocios y Ciencia/Tecnología.</p><pre><code>dataset = load_dataset("ag_news", split="train[:1000]")
docs = [
    Document(
        page_content=sample["text"],
        metadata={"category": sample["label"]}
    )
    for sample in dataset
]</code></pre><p>El <a href="https://python.langchain.com/docs/integrations/vectorstores/elasticsearch/">módulo ElasticsearchStore</a> se emplea desde el <code>langchain_elasticsearch</code> como nuestro almacén vectorial. Para la recuperación, implementamos SparseVectorStrategy, empleando <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-elser">ELSER</a>, el modelo propietario de incrustación de Elastic. Es esencial confirmar que el modelo ELSER está correctamente instalado y desplegado en tu entorno Elasticsearch antes de iniciar el almacén vectorial.</p><pre><code>elastic_vectorstore = ElasticsearchStore.from_documents(
    docs,
    es_url=ES_ENDPOINT,
    es_api_key=ES_API_KEY,
    index_name=index_name,
    strategy=SparseVectorStrategy(model_id=".elser_model_2"),
)

elastic_vectorstore.client.indices.refresh(index=index_name)</code></pre><p>La funcionalidad de búsqueda sitio web se implementa usando <a href="https://python.langchain.com/api_reference/community/tools/langchain_community.tools.ddg_search.tool.DuckDuckGoSearchRun.html">DuckDuckGoSearchRun</a> de las herramientas comunitarias LangChain, lo que permite al sistema recuperar información en tiempo real de la web de forma eficiente. También puedes considerar usar otras APIs de búsqueda que puedan ofrecer resultados más relevantes. Esta herramienta fue elegida porque permite búsquedas sin necesidad de clave API.</p><pre><code>duckduckgo = DuckDuckGoSearchRun(description= "A custom DuckDuckGo search tool for finding latest news stories.", verbose=True)
def websearch_retriever(query):
    results = duckduckgo.run(f"{query}")
    return results</code></pre><p>El retriever compuesto está diseñado para consultas que requieren una combinación de fuentes. Se emplea para proporcionar una respuesta completa y contextualmente precisa al recuperar datos en tiempo real de la web y consultar noticias históricas del almacén vectorial.</p><pre><code>def composite_retriever(query):
    related_docs = vectorstore_retriever(query)
    related_docs += websearch_retriever(query)
    return related_docs</code></pre><h3>Preparando a los agentes</h3><p>En el siguiente paso, los agentes LLM se definen para proporcionar capacidades de razonamiento y toma de decisiones dentro de este flujo de trabajo. Las cadenas de LLM que crearemos incluyen: <code>router_chain</code>, <code>grade_docs_chain</code>, <code>rewrite_query_chain</code>y <code>summary_chain</code>.</p><p>El agente router emplea un asistente LLM para determinar la fuente de datos más adecuada para una consulta determinada en tiempo de ejecución. El agente evaluador evalúa la relevancia de los documentos recuperados. Si los documentos se consideran relevantes, se entregan al agente de resumen para que genere un resumen. De lo contrario, el agente de consulta de reescritura reformula la consulta y la envía de vuelta al proceso de enrutamiento para otro intento de recuperación. Puedes encontrar las instrucciones de todos los agentes en la sección de cadenas de LLM del <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/agentic-rag/agent_rag_news_assistant.ipynb">cuaderno</a>.</p><pre><code>class RouteQuery(BaseModel):
    datasource: Literal["vectorstore", "websearch", "composite"] = Field(
        ...,
        description="Choose to route the query to web search, vectorstore or composite."
    )

router_prompt = ChatPromptTemplate.from_template("""You are an assistant that decides the best data source for questions based on news articles.
Choose one of the following options:
- 'vectorstore': for general, background, or historical news articles.
- 'websearch': for recent discoveries, 'latest', 'current', or '2025' type queries.
- 'composite': when the question needs both historical and current knowledge on news articles.

Question: {query}

Return one word: 'vectorstore', 'websearch', or 'composite'.
""")
router_structured = llm.with_structured_output(RouteQuery)
router_chain: RunnableSequence = router_prompt | router_structured</code></pre><p>La <code>llm.with_structured_output</code> limita la salida del modelo para seguir un esquema predefinido definido por el BaseModel bajo la clase <code>RouteQuery</code> , cerciorando la consistencia de los resultados. La segunda línea compone una <code>RunnableSequence</code> conectando <code>router_prompt</code> con <code>router_structured</code>, formando una tubería en la que el modelo de lenguaje procesa el prompt de entrada para producir resultados estructurados y compatibles con el esquema.</p><h3>Definir nodos de grafo</h3><p>Esta parte implica definir los estados del grafo, que representan los datos que fluyen entre los diferentes componentes del sistema. Una especificación clara de estos estados cerciora que cada nodo del flujo de trabajo sepa qué información puede acceder y actualizar.</p><pre><code>class RAGState(TypedDict):
    query: str
    docs: List[Document]
    router: str
    summary: str
    self_reflection: bool
    retry_count: int = 0</code></pre><p>Una vez definidos los estados, el siguiente paso es definir los nodos del grafo. Los nodos son como las unidades funcionales del grafo que realizan operaciones específicas sobre los datos. Hay 7 nodos diferentes en nuestra pipeline.</p><pre><code>def router(state: RAGState):
   router = router_chain.invoke({'query': state["query"]})
   logger.info(f"Router selected the datasource: {router.datasource}")
   logger.info(f"User query: {state['query']}")
   return {"router": router.datasource}

def vectorstore(state: RAGState):
   return {"docs": vectorstore_retriever(state["query"])}

def websearch(state: RAGState):
   return {"docs": websearch_retriever(state["query"])}

def composite(state: RAGState):
   return {"docs": composite_retriever(state["query"])}

def self_reflection(state: RAGState):
   evaluation = grade_docs_chain.invoke(
       {"query": state["query"], "docs": state["docs"]}
   )
   if evaluation.binary_score:
       logger.info(f"Self-reflection passed -- binary_score={evaluation.binary_score}")
   else:
       logger.info(f"Self-reflection failed -- binary_score={evaluation.binary_score}")

   return {
       "self_reflection": evaluation.binary_score,
   }

def query_rewriter(state: RAGState):
   retry_count = state.get("retry_count", 0) + 1
   new_query = rewrite_query_chain.invoke({"query": state["query"]})
   logger.info(f"Query rewritten: {new_query}, retry_count: {retry_count}")
   return {
       "query": new_query,
       "retry_count": retry_count,
   }

def summarize(state: RAGState):
   summary = summarize_chain.run(
       query=state["query"],
       docs=state["docs"],
   )
   return {"summary": summary}</code></pre><p>El nodo <code>query_rewriter</code> cumple dos funciones en el flujo de trabajo. Primero, reescribe la consulta del usuario usando el <code>rewrite_query_chain</code> para mejorar la recuperación cuando los documentos evaluados por el agente autorreflexivo se consideran insuficientes o irrelevantes. Segundo, actúa como un contador que registra cuántas veces se reescribió la consulta.</p><p>Cada vez que se invoca el nodo, incrementa la <code>retry_count</code> almacenada en el estado del flujo de trabajo. Este mecanismo impide que el flujo de trabajo entre en un bucle infinito. Si el <code>retry_count</code> supera un umbral predefinido, el sistema puede recurrir a un estado de error, una respuesta por defecto o cualquier otra condición predefinida que elijas.</p><h3>Compilación del grafo</h3><p>El último paso es definir las aristas del grafo y agregar las condiciones necesarias antes de compilarlo. Cada grafo debe comenzar desde un nodo inicial designado, que sirve como punto de entrada para el flujo de trabajo. Las aristas en el gráfico representan el flujo de datos entre nodos y pueden ser de dos tipos:</p><ul><li>Aristas rectas: Estas definen un flujo directo e incondicional de un nodo a otro. Cada vez que el primer nodo completa su tarea, el flujo de trabajo avanza automáticamente al siguiente nodo a lo largo de la arista recta.</li><li>Aristas condicionales: Estas permiten que el flujo de trabajo se ramifice según el estado actual o los resultados del cálculo de un nodo. El siguiente nodo se selecciona dinámicamente en función de condiciones como resultados de evaluación, decisiones de enrutamiento o recuentos de intentos.</li></ul><pre><code>graph.add_edge(START, "router")

def after_router(state: RAGState):
   route = state.get("router", None)
   if route == "vectorstore":
       return "vectorstore"
   elif route == "websearch":
       return "websearch"
   else:
       return "composite"

def after_self_reflection(state: RAGState):
   if state["self_reflection"]:
           return "summarize"
   return "query_rewriter"

def after_query_rewriter(state: RAGState):
   while state['retry_count'] <= 3:
           return "router"
   raise RuntimeError("Maximum retries (3) reached -- evaluation failed.")

graph.add_conditional_edges(
   "router",
   after_router,
   {
       "vectorstore": "vectorstore",
       "websearch": "websearch",
       "composite": "composite"
   }
)

graph.add_edge("vectorstore", "self_reflection")
graph.add_edge("websearch", "self_reflection")
graph.add_edge("composite", "self_reflection")
graph.add_conditional_edges(
   "self_reflection",
   after_self_reflection,
   {
       "summarize": "summarize",
       "query_rewriter": "query_rewriter"
   }
)
graph.add_conditional_edges("query_rewriter", after_query_rewriter, {"router": "router"})
graph.add_edge("summarize", END)
agent=graph.compile()</code></pre><p>Con eso, tu primera pipeline de RAG agente está lista y puede probar usando el agente compilado.</p><pre><code>result = agent.invoke({"query": query1})
logger.info(f"\nFinal Summary:\n: {result['summary']}")</code></pre><h3>Prueba de la tubería agente RAG</h3><p>Ahora probaremos esta canalización usando tres tipos distintos de consultas como se indica a continuación. Ten en cuenta que los resultados pueden variar, y los ejemplos que se muestran a continuación ilustran solo un posible resultado.</p><pre><code>query1="What are the latest AI models released this month?"
query2="What technological innovations are discussed in Sci/Tech news?"
query3="Compare a Sci/Tech article from the dataset with a current web article about AI trends."</code></pre><p>Para la primera consulta, el router selecciona <code>websearch</code> como fuente de datos. La consulta no supera la evaluación de autorreflexión y posteriormente se redirige a la etapa de reescritura de la consulta, como se muestra en el resultado.</p><pre><code>INFO     | __main__:router:11 - Router selected the datasource: websearch
INFO     | __main__:router:12 - User query: What are the latest AI models released this month?
Latest Singapore news, including the city state's relationships with Malaysia and Mahathir, China and Xi Jinping, and the rest of Southeast Asia. 3 days ago · The latest military news, insights and analysis from China. All the latest news, opinions and analysis on Hong Kong, China, Asia and around the world Latest news, in-depth features and opinion on Malaysia, covering politics, economy, society and the Asean member-nation's relationships with China, Singapore, and other Southeast Asian ... Oct 12, 2025 · Brics (an acronym for Brazil, Russia, India, China and South Africa) refers to an association of 10 leading emerging markets. The other member states are Egypt, Ethiopia, ...
INFO     | __main__:self_reflection:31 - Self-reflection failed -- binary_score=False
INFO     | __main__:query_rewriter:40 - Query rewritten: query='Which AI models have been officially released in June 2024?', retry_count: 1
INFO     | __main__:router:11 - Router selected the datasource: websearch
INFO     | __main__:router:12 - User query: query='Which AI models have been officially released in June 2024?'
Dream Machine is a text-to-video model created by Luma Labs and launched in June 2024 . It generates video output based on user prompts or still images. Dream Machine has been noted for its ability to realistically capture motion... Released in June 2023. In June 2024 , Baidu announced Ernie 4.0 Turbo. In April 2025, Ernie 4.5 Turbo and X1 Turbo were released . These models are optimized for faster response times and lower operational costs.[28][29]. The meaning of QUERY is question, inquiry. How to use query in a sentence. Synonym Discussion of Query. QUERY definition: 1. a question, often expressing doubt about something or looking for an answer from an authority.... Learn more. Query definition: a question; an inquiry.. See examples of QUERY used in a sentence.
INFO     | __main__:self_reflection:29 - Self-reflection passed -- binary_score=True
INFO     | __main__:<module>:2 - 
Final Summary:
: In June 2024, two AI models were officially released: Dream Machine, a text-to-video model launched by Luma Labs, and Ernie 4.0 Turbo, announced by Baidu, which is optimized for faster response times and lower operational costs.</code></pre><p>A continuación, examinamos un ejemplo en el que se emplea <code>vectorstore</code> recuperación, demostrado con la segunda consulta.</p><pre><code>INFO     | __main__:router:11 - Router selected the datasource: vectorstore
INFO     | __main__:router:12 - User query: What technological innovations are discussed in Sci/Tech news?
INFO     | __main__:self_reflection:29 - Self-reflection passed -- binary_score=True
INFO     | __main__:<module>:2 - 
Final Summary:
: Recent Sci/Tech news highlights several technological innovations: NASA is collaborating with Silicon Valley firms to build a powerful Linux-based supercomputer to support theoretical research and shuttle engineering; new chromatin transfer techniques have enabled the cloning of cats; cybersecurity advancements are being discussed in relation to protecting personal technology; Princeton University scientists assert that existing technologies can be used immediately to stabilize global warming; and a set of GameBoy micro-games has been recognized for innovation in game design.</code></pre><p>La consulta final se dirige a la recuperación compuesta, que emplea tanto la vectorstore como la búsqueda sitio web.</p><pre><code>INFO     | __main__:router:11 - Router selected the datasource: composite
INFO     | __main__:router:12 - User query: Compare a Sci/Tech article from the dataset with a current web article about AI trends.
Atlas currently only available on macOS, built on Chromium with planned features like ad-blocking still in development. OpenAI's Atlas browser launched with bold promises of AI -powered web browsing, but early real-world testing reveals a different story. Career-long data are updated to end-of-2024 and single recent year data pertain to citations received during calendar year 2024. The selection is based on the top 100,000 scientists by c-score (with and without self-citations) or a percentile rank of 2% or above in the sub-field. In this article I list 45 AI tools across 21 different categories. After exploring all the available options in each category, I've carefully selected the best tools based on my personal experience. Reading a complex technical article ? Simply highlight confusing terminology and ask "what's this?" to receive instant explanations. compare browsers. Comparison showing traditional browser navigation versus OpenAI Atlas AI -powered workflows. After putting Gemini, ChatGPT, Grok, and DeepSeek through rigorous testing in October 2025, it's clear that there isn't one AI that reigns supreme across all categories.
INFO     | __main__:self_reflection:29 - Self-reflection passed -- binary_score=True
INFO     | __main__:<module>:2 - 
Final Summary:
: A Sci/Tech article from the dataset highlights NASA's development of robust artificial intelligence software for planetary rovers, aiming to make them more self-reliant and capable of decision-making during missions. In contrast, a current web article about AI trends focuses on the proliferation of AI-powered tools across various categories, including browsers like OpenAI Atlas, and compares leading models such as Gemini, ChatGPT, Grok, and DeepSeek, noting that no single AI currently excels in all areas. While the NASA article emphasizes specialized AI applications for autonomous robotics in space exploration, the current trends article showcases the broadening impact of AI across consumer and professional technologies, with ongoing competition and rapid innovation among major AI platforms.</code></pre><p>En el flujo de trabajo anterior, el RAG agente determina de forma inteligente qué fuente de datos emplear al recuperar información para una consulta de usuario, mejorando así la precisión y relevancia de la respuesta. Puedes crear ejemplos adicionales para probar al agente y revisar los resultados para ver si dan resultados interesantes.</p><h2>Mejores prácticas para construir flujos de trabajo agenticos RAG</h2><p>Ahora que entendemos cómo funciona el RAG agente, veamos algunas buenas prácticas para construir estos flujos de trabajo. Seguir estas directrices ayudará a mantener el sistema eficiente y fácil de mantener.</p><ul><li><strong>Prepárate para los recursos de respaldo</strong>: Planea estrategias de respaldo con antelación para escenarios en los que algún paso del flujo de trabajo falle. Estos pueden incluir devolver respuestas por defecto, activar estados de error o emplear herramientas alternativas. Esto garantiza que el sistema gestione los fallos con elegancia sin romper el flujo de trabajo global.</li><li><strong>Implementa registros completos</strong>: Prueba a implementar registros en cada etapa del flujo de trabajo, como intentos, salidas generadas, elecciones de enrutamiento y reescrituras de consultas. Estos registros ayudan a mejorar la transparencia, facilitan la depuración y ayudan a refinar los prompts, el comportamiento de los agentes y las estrategias de recuperación con el tiempo.</li><li><strong>Selecciona el patrón de flujo de trabajo adecuado</strong>: Examina tu caso de uso y selecciona el patrón que mejor se adapte a tus necesidades. Emplea flujos de trabajo secuenciales para razonamiento paso a paso, flujos de trabajo paralelos para fuentes de datos independientes y patrones orquestador-trabajador para consultas multiherramienta o complejas.</li><li><strong>Incorpora estrategias de evaluación</strong>: Integra mecanismos de evaluación en diferentes etapas del flujo de trabajo. Estos pueden incluir agentes de autorreflexión, calificación de documentos recuperados o controles automáticos de calidad. La evaluación ayuda a verificar que los documentos recuperados son relevantes, que las respuestas son precisas y que todas las partes de una consulta compleja están abordadas.</li></ul><h2>Desafíos</h2><p>Aunque los sistemas RAG agenticos ofrecen beneficios significativos en términos de adaptabilidad, precisión y razonamiento dinámico, también presentan ciertos desafíos que deben abordar durante sus etapas de diseño e implementación. Algunos de los principales retos incluyen:</p><ul><li><strong>Flujos de trabajo complejos</strong>: A medida que se agregan más agentes y puntos de decisión, el flujo de trabajo global se vuelve cada vez más complejo. Esto puede llevar a mayores probabilidades de errores o fallos en tiempo de ejecución. Siempre que sea posible, prioriza flujos de trabajo optimizados eliminando agentes redundantes y puntos de decisión innecesarios.</li><li><strong>Escalabilidad</strong>: Puede ser complicado escalar sistemas RAG agentes para manejar grandes conjuntos de datos y grandes volúmenes de consultas. Incorpora estrategias eficientes de indexación, caché y procesamiento distribuido para mantener el rendimiento a gran escala.</li><li><strong>Orquestación y sobrecarga computacional</strong>: La ejecución de flujos de trabajo con múltiples agentes requiere orquestación avanzada. Esto incluye una planeación cuidadosa, gestión de dependencias y coordinación de agentes para evitar cuellos de botella y conflictos, todo lo cual contribuye a la complejidad general del sistema.</li><li><strong>Complejidad de la evaluación</strong>: La evaluación de estos flujos de trabajo presenta desafíos inherentes, ya que cada etapa requiere una estrategia de evaluación distinta. Por ejemplo, la etapa RAG debe evaluar para verificar la relevancia y completitud de los documentos recuperados, mientras que los resúmenes generados deben verificar para garantizar su calidad y precisión. Del mismo modo, la efectividad de la reformulación de consultas requiere una lógica de evaluación separada para determinar si la consulta reescrita mejora los resultados de recuperación.</li></ul><h2>Conclusión</h2><p>En esta entrada de blog, presentamos el concepto de RAG agente y destacamos cómo mejora el marco tradicional de RAG al incorporar capacidades autónomas de la IA agente. Exploramos las características principales de RAG agente y demostramos estas características mediante un ejemplo práctico, construyendo un asistente de noticias usando Elasticsearch como almacén vectorial y LangChain para crear el marco agente.</p><p>Además, discutimos las mejores prácticas y los principales retos a considerar al diseñar e implementar una pipeline agentica RAG. Estos conocimientos están destinados a guiar a los desarrolladores en la creación de sistemas agentivos robustos, escalables y eficientes que combinen eficazmente la recuperación, el razonamiento y la toma de decisiones.</p><h2>¿Qué sigue ahora?</h2><p>El flujo de trabajo que creamos es sencillo, dejando amplio margen para mejoras y experimentación. Podemos mejorar esto experimentando con varios modelos de incrustación y refinando estrategias de recuperación. Además, integrar a un agente de reclasificación para priorizar los documentos recuperados podría ser beneficioso. Otra área de exploración implica desarrollar estrategias de evaluación para marcos agentivos, identificando específicamente enfoques comunes y reutilizables aplicables a diferentes tipos de marcos. Por último, experimentar con estos marcos en conjuntos de datos grandes y más complejos.</p><p>Mientras tanto, si tienes experimentos similares que compartir, ¡nos encantaría saberlos! No dudes en dar tus opiniones o conectar con nosotros a través de nuestro <a href="https://ela.st/slack">canal comunitario de Slack</a> o <a href="https://discuss.elastic.co/c/security">foros de discusión</a>.</p><h2>Recursos</h2><ul><li><a href="https://arxiv.org/abs/2310.11511">Auto-RAG: Aprender a recuperar, generar y criticar a través de la autorreflexión</a></li><li><a href="https://arxiv.org/abs/2501.09136">Generación aumentada por recuperación agentica: una encuesta sobre el RAG agente</a></li></ul>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/es/blog/hybrid-search-multi-field-query-retrievers-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/hybrid-search-multi-field-query-retrievers-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Mike Pellegrini]]></dc:creator>
            <pubDate>Thu, 27 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4494f98ae3958bbdbc6171df9677fc4d65ec5640-1536x1024.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c8b49153c47d2cdc233c0d2e440db04711d48ca5-1600x1600.jpg" style="width: 100%;" /><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> .<br/></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/264481268c8b6ac259e3c257b85431b513f16672-1077x586.png" style="width: 100%;" /><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><pre><code>Total Score = 𝚺(N(Sx))</code></pre><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><pre><code>PUT linear_retriever_example
{
  "mappings": {
    "properties": {
      "semantic_text_field": { <1>
        "type": "semantic_text",
        "inference_id": ".multilingual-e5-small-elasticsearch"
      },
      "text_field": { <2>
        "type": "text"
      }
    }
  }
}</code></pre><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><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "standard": {
              "query": {
                "match": { <1>
                  "semantic_text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}</code></pre><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><br/>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><pre><code>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"
        }
      ]
    }
  }
}</code></pre><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><pre><code>Total Score = N(semantic_text_field_1 score) + N(semantic_text_field_2 score) + N(text_field score)</code></pre><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><pre><code>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"
        }
      ]
    }
  }
}</code></pre><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><pre><code>Total Score = N(N(semantic_text_field_1 score) + N(semantic_text_field_2 score)) + N(text_field score)</code></pre><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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/dd04e1686076a574b717c1460acfe4eb79299208-1600x1600.jpg" style="width: 100%;" /><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><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}</code></pre><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>Determinar el tipo de cada campo consultado</li><li>Agrupa cada cuerpo en una categoría léxica o semántica</li><li>Pondera cada categoría de forma equitativa en el puntaje final</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><pre><code>GET rrf_retriever_example/_search
{
  "retriever": {
    "rrf": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field" ],
      "query": "foo"
    }
  }
}</code></pre><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><pre><code>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"
    }
  }
}</code></pre><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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ba31c76189e3a1e5b1638437ccf0528aafec2598-1600x549.png" style="width: 100%;" /><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><pre><code>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"
    }
  }
}</code></pre><p>Ahora el desglose del puntaje es así:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/de20e555d52f914bf483a048d056f54f4fece757-1600x549.png" style="width: 100%;" /><p>Cada grupo de consulta sigue teniendo un peso igual, pero ahora el peso del campo dentro de los grupos cambió:</p><ul><li><code>semantic_text_field_1</code> es el 66% del puntaje del grupo de consultas semánticas, 33% del puntaje total</li><li><code>text_field_1</code> es el 66% del puntaje del grupo de consultas léxicas, 33% del puntaje total</li></ul>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">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í.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">i️ El aumento por campo también puede usar con el recuperador RRF en Elasticsearch 9.2+</td>
                    </tr>
              </tbody>
            </table>
          <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><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_*", "*_field_1" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}</code></pre><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>Coincidencia de prefijos (ej: <code>*_text_field</code>)</li><li>Emparejamiento en línea (ej: <code>semantic_*_field</code>)</li><li>Coincidencia de sufijos (ej: <code>semantic_text_field_*</code>)</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><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}</code></pre><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><code>dense_vector</code> Campos</li><li><code>rank_vector</code> Campos</li><li>Campos geométricos: <code>geo_point</code>, <code>shape</code></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><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre><p></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construir un agente de IA para RRHH con Elastic Agent Builder y GPT-OSS]]></title>
            <description><![CDATA[Descubre cómo crear un agente de IA que pueda responder consultas en lenguaje natural sobre los datos de RRHH de tus empleados usando Elastic Agent Builder y GPT-OSS.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/build-an-ai-agent-hr-elastic-agent-builder-gpt-oss</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/build-an-ai-agent-hr-elastic-agent-builder-gpt-oss</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
            <pubDate>Wed, 26 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/05d2d0513fff67d975f9223d75108aa9f50646bc-1600x914.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>Introducción</h2><p>Este artículo te mostrará cómo construir un agente de IA para RRHH usando <a href="https://openai.com/index/introducing-gpt-oss/">GPT-OSS</a> y Elastic Agent Builder. El agente puede responder a tus preguntas sin enviar datos a OpenAI, Anthropic ni ningún servicio externo.</p><p>Usaremos LM Studio para servir GPT-OSS localmente y lo conectaremos a Elastic Agent Builder.</p><p>Al final de este artículo, tendrás un agente de IA personalizado que podrá responder preguntas en lenguaje natural sobre los datos de tus empleados manteniendo el control total sobre tu información y modelo.</p><h2>Prerrequisitos</h2><p>Para este artículo, necesitas:</p><ul><li><a href="https://www.elastic.co/cloud">Elastic Cloud</a> alojaba la versión 9.2, despliegue serverless o <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">local</a></li><li>Máquina con 32GB de RAM recomendada (mínimo 16GB para GPT-OSS 20B)</li><li><a href="https://lmstudio.ai/">LM Studio</a> instalado</li><li><a href="https://www.docker.com/products/docker-desktop/">Escritorio Docker</a> Instalado</li></ul><h2>¿Por qué usar GPT-OSS?</h2><p>Con un LLM local tienes el control para desplegarlo en tu propia infraestructura y ajustarlo para adaptarlo a tus propias necesidades. Todo esto manteniendo el control sobre los datos que compartes con el modelo y, por supuesto, no tienes que pagar una tasa de licencia a un proveedor externo.</p><p>OpenAI <a href="https://openai.com/index/introducing-gpt-oss/">lanzó GPT-OSS</a> el 5 de agosto de 2025, como parte de su compromiso con el ecosistema del modelo abierto.</p><p>El modelo de parámetros 20B ofrece:</p><ul><li><strong>Capacidades de uso de herramientas</strong></li><li><strong>Inferencia eficiente</strong></li><li><strong>Compatible con SDK OpenAI</strong></li><li><strong>Compatible con flujos de trabajo agentes</strong></li></ul><p>Comparación de referencias:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/29160e3345352088e8213297630882f252b00c47-1600x680.png" style="width: 100%;" /><h2>Arquitectura de soluciones</h2><p>La arquitectura funciona completamente en tu máquina local. Elastic (que funciona en Docker) se comunica directamente con tu LLM local a través de LM Studio, y Elastic Agent Builder emplea esta conexión para crear agentes de IA personalizados que pueden consultar los datos de tus empleados.</p><p>Para más detalles, consulte esta <a href="https://www.elastic.co/docs/solutions/observability/connect-to-own-local-llm">documentación</a>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a4a886750ff25fa8bb7aefc7448161e52cf73ed3-1600x896.png" style="width: 100%;" /><h2>Construir un agente de IA para RRHH: Pasos</h2><p>Dividiremos la implementación en 5 pasos:</p><ol><li>Configurar LM Studio con un modelo local</li><li>Despliega Elastic Local con Docker</li><li>Crea el conector OpenAI en Elastic</li><li>Sube los datos de los empleados a Elasticsearch</li><li>Construye y prueba tu agente de IA</li></ol><h2>Paso 1: Configurar LM Studio con GPT-OSS 20B</h2><p>LM Studio es una aplicación fácil de usar que te permite ejecutar grandes modelos de lenguaje localmente en tu computadora. Proporciona un servidor API compatible con OpenAI, lo que facilita su integración con herramientas como Elastic sin un proceso de configuración complejo. Para más detalles, consulta la <a href="https://lmstudio.ai/docs/app">documentación de LM Studio</a>.</p><p>Primero, descarga e instala <span class="unknown__pt__mark__51566e628851">LM Studio</span> desde el sitio web oficial. Una vez instalado, abre la aplicación.</p><h3>En la interfaz de LM Studio:</h3><ol><li>Ve a la pestaña de búsqueda y busca &quot;GPT-OSS&quot;</li><li>Selecciona el <code>openai/gpt-oss-20b</code> de OpenAI</li><li>Haz clic en descargar</li></ol><p>El tamaño de este modelo debería ser aproximadamente <strong>12,10GB</strong>. La descarga puede tardar unos minutos, dependiendo de tu conexión a Internet.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5d01bc4dcb377b5259fc6b521fe2425a31b90ca4-1312x872.png" style="width: 100%;" /><h4>Una vez descargado el modelo:</h4><ol><li>Ve a la pestaña del servidor local</li><li>Selecciona el openai/gpt-oss-20b</li><li>Usa el puerto predeterminado 1234</li><li>En el panel derecho, ve a <strong>Cargar </strong>y establece la longitud de contexto en <strong>40K</strong> o más</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e546033f916381647b876815b2c1f1ae2a08365f-326x337.png" style="width: 100%;" /><p>5. Haz clic en iniciar servidor</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/28ee78a3caa84d14e04db3d42f30acbe4d4d005a-1312x872.png" style="width: 100%;" /><p>Deberías ver esto si el servidor está funcionando.</p><pre><code>[LM STUDIO SERVER] Success! HTTP server listening on port 1234
[LM STUDIO SERVER] Supported endpoints:
[LM STUDIO SERVER] ->	GET  http://localhost:1234/v1/models
[LM STUDIO SERVER] ->	POST http://localhost:1234/v1/responses
[LM STUDIO SERVER] ->	POST http://localhost:1234/v1/chat/completions
[LM STUDIO SERVER] ->	POST http://localhost:1234/v1/completions
[LM STUDIO SERVER] ->	POST http://localhost:1234/v1/embeddings
Server started.</code></pre><h2>Paso 2: Desplegar Elastic Local con Docker</h2><p>Ahora configuraremos Elasticsearch y Kibana localmente usando Docker. Elastic proporciona un script conveniente que gestiona todo el proceso de configuración. Para más detalles, consulte la <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">documentación oficial</a>.</p><h3>Ejecuta el script de inicio local</h3><p>Ejecuta el siguiente comando en tu terminal:</p><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre><p>Este guion será:</p><ul><li>Descargar y configurar Elasticsearch y Kibana</li><li>Inicia ambos servicios usando Docker Compose</li><li>Activa automáticamente una licencia de prueba Platinum de 30 días</li></ul><h3>Producción esperada</h3><p>Solo espera el siguiente mensaje y almacena la contraseña y la clave API que se muestra; los necesitarás para acceder a Kibana:</p><pre><code>🎉 Congrats, Elasticsearch and Kibana are installed and running in Docker!
🌐 Open your browser at http://localhost:5601
   Username: elastic
   Password: KSUlOMNr
🔌 Elasticsearch API endpoint: http://localhost:9200
🔑 API key: cnJGX0pwb0JhOG00cmNJVklUNXg6cnNJdXZWMnM4bncwMllpQlFlUTlWdw==
Learn more at https://github.com/elastic/start-local</code></pre><h3>Acceso a Kibana</h3><p>Abre tu navegador y navega a:</p><pre><code>http://localhost:5601</code></pre><p>Inicia sesión usando las credenciales obtenidas en la salida del terminal.</p><h3>Habilitar Agent Builder</h3><p>Una vez iniciado sesión en Kibana, navega a <strong>Management </strong>&gt;<strong> AI </strong>&gt;<strong> Agent Builder </strong>y activa el Agent Builder.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/92e104cb846c20d875865ded8a3d37f5c7daae9b-1491x1528.png" style="width: 100%;" /><h2>Paso 3: Crea el conector OpenAI en Elastic</h2><p>Ahora configuraremos Elastic para que use tu LLM local.</p><h3>Conectores de acceso</h3><ol><li>En Kibana</li><li>Ve a Configuración &gt; <strong>Gestión del Proyecto</strong> <strong></strong></li><li>En <strong>Alertas e Información</strong>, selecciona <strong>Conectores</strong></li><li>Haz clic en Crear conector</li></ol><h3>Configurar el conector</h3><p>Selecciona <strong>OpenAI</strong> de la lista de conectores. LM Studio emplea el SDK OpenAI, lo que lo hace compatible.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5ac87042e086c7a2bd47a8039e646ec831f0dcc6-923x974.png" style="width: 100%;" /><p>Diligencia el espacio con estos valores:</p><ul><li><strong>Nombre del conector: </strong>LM Studio - GPT-OSS 20B</li><li><strong>Selecciona un proveedor de OpenAI: </strong>Otros (Servicio compatible con OpenAI)</li><li><strong>URL: </strong><code>http://host.docker.internal:1234/v1/chat/completions</code></li><li><strong>Modelo por defecto: </strong>openai/gpt-oss-20b</li><li><strong>API Key:</strong> testkey-123 (cualquier texto funciona, porque LM Studio Server no requiere autenticación.)</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2084ac32fcf1fb810c8b54ecab1c85a1e3e8905b-672x1302.png" style="width: 100%;" /><p>Para terminar la configuración, haz clic en <strong>Almacenar y probar</strong>.</p><p><strong>Importante:</strong> Activa la opción &quot;<strong>Habilitar la llamada a funciones nativas</strong>&quot;; esto es necesario para que el Constructor de Agentes funcione correctamente. Si no activas esto, te aparecerá un error de <strong><code>No tool calls found in the response</code></strong> .</p><h3>Prueba la conexión</h3><p>El elástico debería probar automáticamente la conexión. Si todo está configurado correctamente, verás un mensaje de éxito como este:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f567d767f1969c4730c1daa92f651789dc3742ac-1042x812.png" style="width: 100%;" /><p>Respuesta:</p><pre><code>{
  "status": "ok",
  "data": {
    "id": "chatcmpl-flj9h0hy4wcx4bfson00an",
    "object": "chat.completion",
    "created": 1761189456,
    "model": "openai/gpt-oss-20b",
    "choices": [
      {
        "index": 0,
        "message": {
          "role": "assistant",
          "content": "Hello! 👋 How can I assist you today?",
          "reasoning": "Just greet.",
          "tool_calls": []
        },
        "logprobs": null,
        "finish_reason": "stop"
      }
    ],
    "usage": {
      "prompt_tokens": 69,
      "completion_tokens": 23,
      "total_tokens": 92
    },
    "stats": {},
    "system_fingerprint": "openai/gpt-oss-20b"
  },
  "actionId": "ee1c3aaf-bad0-4ada-8149-118f52dad757"
}</code></pre><h2>Paso 4: Subir los datos de los empleados a Elasticsearch</h2><p>Ahora subiremos el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/gpt-oss-with-elasticsearch/hr-employees-bulk.json">conjunto de datos de empleados de RRHH</a> para demostrar cómo el agente trabaja con datos sensibles. Generé un conjunto de datos ficticio con esta estructura.</p><h3>Estructura del conjunto de datos</h3><pre><code>{
  "employee_id": "0f4dce68-2a09-4cb1-b2af-6bcb4821539b",
  "full_name": "Daffi Stiebler",
  "email": "lscutchings0@huffingtonpost.com",
  "date_of_birth": "1975-06-20T15:39:36Z",
  "hire_date": "2025-07-28T00:10:45Z",
  "job_title": "Physical Therapy Assistant",
  "department": "HR",
  "salary": "108455",
  "performance_rating": "Needs Improvement",
  "years_of_experience": 2,
  "skills": "Java",
  "education_level": "Master's Degree",
  "manager": "Carl MacGibbon",
  "emergency_contact": "Leigha Scutchings",
  "home_address": "5571 6th Park"
}</code></pre><h3>Crea el índice con mapeos</h3><p>Primero, crea el índice con mapeos adecuados. Ten en cuenta que estamos usando <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">semantic_text</a> campos para algunos campos clave; Esto permite capacidades de búsqueda semántica para nuestro índice.</p><pre><code>​​PUT hr-employees
{
  "mappings": {
    "properties": {
      "@timestamp": {
        "type": "date"
      },
      "employee_id": {
        "type": "keyword"
      },
      "full_name": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "email": {
        "type": "keyword"
      },
      "date_of_birth": {
        "type": "date",
        "format": "iso8601"
      },
      "hire_date": {
        "type": "date",
        "format": "iso8601"
      },
      "job_title": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "department": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "salary": {
        "type": "double"
      },
      "performance_rating": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "years_of_experience": {
        "type": "long"
      },
      "skills": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "education_level": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "manager": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "emergency_contact": {
        "type": "keyword"
      },
      "home_address": {
        "type": "keyword"
      },
      "employee_semantic": {
        "type": "semantic_text"
      }
    }
  }
}</code></pre><h3>Índice con API Bulk</h3><p>Copia y pega el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/gpt-oss-with-elasticsearch/hr-employees-bulk.json">conjunto de datos</a> en tus Dev Tools en Kibana y ejecutalo:</p><pre><code>POST hr-employees/_bulk
{"index": {}}
{"employee_id": "57728b91-e5d7-4fa8-954a-2384040d3886", "full_name": "Filide Gane", "email": "vhallahan1@booking.com", "job_title": "Business Systems Development Analyst", "department": "Marketing", "salary": "$52330.27", "performance_rating": "Meets Expectations", "years_of_experience": 12, "skills": "Java", "education_level": "Bachelor's Degree", "date_of_birth": "2000-02-07T16:49:32Z", "hire_date": "2023-11-07T13:03:16Z", "manager": "Freedman Kings", "emergency_contact": "Vilhelmina Hallahan", "home_address": "75 Dennis Junction"}
{"index": {}}
{"employee_id": "...", ...}</code></pre><h3>Verifica los datos</h3><p>Realiza una consulta para verificar:</p><pre><code>GET hr-employees/_search</code></pre><h2>Paso 5: Construye y prueba tu agente de IA</h2><p>Con todo configurado, es hora de crear un agente de IA personalizado usando Elastic Agent Builder. Para más detalles, consulte la <a href="https://www.elastic.co/docs/solutions/search/agent-builder/get-started">documentación de Elastic</a>.</p><h3>Agregar el conector</h3><p>Antes de poder crear nuestro nuevo agente, tenemos que configurar nuestro constructor de agentes para usar nuestro conector personalizado llamado <code>LM Studio - GPT-OSS 20B</code> porque el predeterminado es el <a href="https://www.elastic.co/docs/reference/kibana/connectors-kibana/elastic-managed-llm">Elastic Managed LLM</a>. Para eso, necesitamos ir a <strong>Configuración</strong> &gt; <strong>Gestión de Proyectos</strong> &gt; <strong>Configuración</strong> de GenAI; ahora seleccionamos la que creamos y hacemos clic <strong>en</strong> Almacenar.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/11e830c3e2fb4c298b020c928fa5422f3397ba08-1600x1152.png" style="width: 100%;" /><h3>Constructor de Agentes de Acceso</h3><ol><li>Ir a <strong>Agentes</strong></li><li>Haz clic <strong>en Crear un nuevo agente</strong></li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c1e60541563650163f972ac9088dc1ed1de759a7-1600x1054.png" style="width: 100%;" /><h3>Configurar el agente</h3><p>Para crear un nuevo agente, los campos requeridos son el <strong>ID del agente</strong>, <strong>el nombre de visualización</strong> y <strong>las instrucciones de visualización</strong>.</p><p>Pero hay más opciones de personalización, como las Instrucciones Personalizadas que guían cómo se comportará tu agente e interactuará con tus herramientas, similar a una indicación del sistema, pero para nuestro agente personalizado. Las etiquetas ayudan a organizar tus agentes, el color del avatar y el símbolo del avatar.</p><p>Los que elegí para nuestro agente basándome en el conjunto de datos son:<br/><br/><strong>ID del agente:</strong> <code>hr_assistant</code></p><p><strong>Instrucciones personalizadas:</strong></p><pre><code>You are an HR Analytics Assistant that helps answer questions about employee data.
When responding to queries:
- Provide clear, concise answers
- Include relevant employee details (name, department, salary, skills)
- Format monetary values with currency symbols
- Be professional and maintain data confidentiality</code></pre><p><br/>Etiquetas: <code>Human Resources</code> y <code>GPT-OSS</code></p><p>Nombre de visualización: <code>HR Analytics Assistant</code></p><p>Descripción de la exposición:</p><pre><code>A specialized AI assistant for Human Resources that helps analyze employee data, compensation, performance metrics, and talent management. Ask questions about employees, departments, salaries, or performance analytics.</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f94bb2bf08497e5e756ca76b30a3a51f42927756-1424x1217.png" style="width: 100%;" /><p>Con todos los datos ahí, podemos hacer clic en <strong>Almacenar</strong> a nuestro nuevo agente.</p><h3>Prueba al agente</h3><p>Ahora puedes hacer preguntas en lenguaje natural sobre los datos de tus empleados, y GPT-OSS 20B entenderá la intención y generará una respuesta adecuada.</p><h4>Pronto:</h4><pre><code>Which employee is the one with the highest salary in the hr-employees index?</code></pre><h4>Respuesta:</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/94ad19f80b96304028a59f60beca51dfc9aecc8a-899x631.png" style="width: 100%;" /><p>El proceso de Agente fue:</p><p>1. Entiende tu pregunta usando el conector GPT-OSS</p><p>2. Generar la consulta Elasticsearch adecuada (usando las herramientas integradas o <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES| personalizadoQL</a>)</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6f2961d0d1b97475f6dda300acee84da540938e6-844x466.png" style="width: 100%;" /><p>3. Recuperar los registros de empleados coincidentes</p><p>4. Presentar resultados en lenguaje natural con un formato adecuado</p><p>A diferencia de la búsqueda léxica tradicional, el agente impulsado por GPT-OSS entiende la intención y el contexto, facilitando encontrar información sin conocer los nombres exactos de los campos o la sintaxis de la consulta. Para más detalles sobre el proceso de pensamiento del agente, consulta este <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance">artículo</a>.</p><h2>Conclusión</h2><p>En este artículo, creamos un agente de IA personalizado usando el Agent Builder de Elastic para conectarse al modelo OpenAI GPT-OSS que se ejecuta localmente. Al desplegar tanto Elastic como el LLM en tu máquina local, esta arquitectura te permite aprovechar las capacidades de IA generativa manteniendo el control total sobre tus datos, todo ello sin enviar información a servicios externos.</p><p>Usamos GPT-OSS 20B como experimento, pero <a href="https://www.elastic.co/docs/solutions/search/agent-builder/models#recommended-models">aquí</a> se hacen referencia a los modelos oficialmente recomendados para Elastic Agent Builder. Si necesitas capacidades de razonamiento más avanzadas, también está la <a href="https://huggingface.co/openai/gpt-oss-120b">variante de parámetros 120B</a> que rinde mejor para escenarios complejos, aunque requiere una máquina de especificaciones más altas para ejecutar localmente. Para más detalles, consulte la <a href="https://openai.com/open-models/">documentación oficial de OpenAI</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Los principales proyectos y aprendizajes de Elastic Agent Builder de Cal Hacks 12.0]]></title>
            <description><![CDATA[Explora los principales proyectos de Elastic Agent Builder de Cal Hacks 12.0 y sumérgete en nuestras conclusiones técnicas sobre Serverless, ES|QL y arquitecturas de agentes.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/agent-builder-projects-learnings-cal-hacks-12-0</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/agent-builder-projects-learnings-cal-hacks-12-0</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[JD Armada]]></dc:creator>
            <pubDate>Tue, 25 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8ba034a6f19b50521f541b8131756a8acdb52975-1280x960.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<a href="https://www.youtube.com/watch/GUI0WXNZduo">Watch video on YouTube</a><p>Hace unas semanas, tuvimos la asombrosa oportunidad de patrocinar <a href="https://cal-hacks-12-0.devpost.com/">Cal Hacks 12.0</a>, uno de los hackatones presenciales más grandes con más de 2000 participantes de todo el mundo. Ofrecimos una pista dedicada al mejor uso de Elastic Agent Builder en Serverless, y la respuesta fue fenomenal. En solo 36 horas, recibimos 29 envíos que usaban Agent Builder de formas creativas, desde la creación de herramientas de inteligencia contra incendios forestales hasta validadores StackOverflow.</p><p>Más allá de los impresionantes proyectos, la experiencia en Cal Hacks 12.0 también nos dio algo igualmente valioso: comentarios rápidos y sin filtros de desarrolladores que se encuentran con nuestra Stack por primera vez. Los hackathons son pruebas de presión únicas con plazos ajustados, cero familiaridad previa y obstáculos impredecibles (como los famosos cortes de WiFi). Exponen exactamente dónde brilla la experiencia del desarrollador y dónde aún necesita mejoras. Esto importa aún más ahora, ya que los desarrolladores interactúan con el Elastic Stack de nuevas maneras, cada vez más a través de flujos de trabajo impulsados por LLM. En esta entrada del blog, profundizaremos en lo que los participantes construyeron con Agent Builder y lo que aprendimos en el proceso.</p><h2>Los proyectos ganadores</h2><h3>Primer puesto: AgentOverflow</h3><p>Stack Overflow reconstruido para la era de los LLM y los agentes.</p><p>Lee más sobre <a href="https://devpost.com/software/agentoverflow">AgentOverflow aquí</a>.</p><p>AgentOverflow aborda un problema que la mayoría de los desarrolladores de IA enfrentan: los LLMs alucinan, los historiales de chat desaparecen y los desarrolladores pierden tiempo resolviendo los mismos problemas.</p><p>AgentOverflow captura, valida y reactiva pares reales problema-solución, para que los desarrolladores puedan romper la espiral de alucinaciones y enviar más rápido.</p><h4>Cómo funciona:</h4><p><strong>1. Compartir JSON - el &quot;Esquema de Solución&quot;.</strong></p><p>Un clic desde un recurso compartido de Claude extraerá, extrae y ensamblará un JSON de Solución de Compartir, que es un formato estructurado que contiene:</p><ul><li>Problema</li><li>Contexto</li><li>Código</li><li>Etiquetas</li><li>Pasos verificados de la solución.</li></ul><p>Un validador (LAVA) comprueba y aplica la estructura, el usuario agrega una línea de contexto extra y luego se almacena e indexa dentro de Elasticsearch.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/45a3e96f4474050a855419628c2a7338bb12c706-1600x877.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2d65cb154d8ee32fc96ff17dfa5b0bf2636e3777-1600x1002.png" style="width: 100%;" /><p><strong>2. Encontrar solución</strong></p><p>Cuando te quedes atascado, haz clic en <code>Find Solution</code> y AgentOverflow extraerá tu conversación actual, la usará para crear una consulta y ejecutará una búsqueda híbrida en Elasticsearch para que aparezca:</p><ul><li>Arreglos clasificados y validados por la comunidad</li><li>Los exactos prompts que originalmente resolvieron el problema</li></ul><p>Esto permite a los desarrolladores copiar, pegar y desbloquear rápidamente su sesión actual.</p><p><strong>3. MCP - inyección de contexto para LLMs</strong></p><p>Al conectarse a las soluciones estructuradas almacenadas dentro de Elasticsearch a través de MCP (Model Context Protocol), los LLM reciben un contexto de alta señal (código, registros, configuraciones, correcciones previas) en tiempo de ejecución sin ruido adicional.</p><p>AgentOverflow emplea Agent Builder con Elasticsearch como una capa de memoria estructurada que inyecta el contexto relevante en los LLMs. Esto los transforma de chatbots pasivos en solucionadores de problemas conscientes del contexto.</p><h3>Segundo puesto: MarketMind</h3><p>Una visión interpretable en tiempo real de la energía del mercado, impulsada por seis Agentes Elásticos.</p><p>Lee más sobre <a href="https://devpost.com/software/marketmind-b6cy2q">MarketMind aquí</a>.</p><p>MarketMind consiguió su spot al ofrecer a los traders novatos una plataforma que convierte datos de mercado fragmentados en señales claras y en tiempo real. En lugar de compaginar la acción del precio, los fundamentos, el sentimiento y la volatilidad entre diferentes herramientas, MarketMind consolida toda esta información en una sola plataforma, ayudando a los traders a obtener información útil. Este proyecto también empleó algunos complejos ES|QL consulta al construir sus agentes.</p><h4>Cómo funciona:</h4><p><strong>1. Recopilar datos de mercado en tiempo real</strong></p><p>MarketMind extrae métricas de precio-acción, fundamentos, sentimiento, volatilidad y riesgo de Yahoo Finance. Estos datos se ingieren y organizan en múltiples índices de Elasticsearch.</p><p><strong>2. Seis agentes especializados analizan el mercado</strong></p><p>Cada agente, construido con Agent Builder, se centra en una capa diferente del mercado. Leen desde un índice de Elasticsearch, calculan sus propias métricas específicas de dominio y generan una salida JSON estandarizada con puntajes y razonamientos.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7d9716cca06a047a2b3584378b5c7e592a785ba1-1284x878.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5aac6a833347c0d2e596c02049ec4b4d3aae5cd7-794x764.png" style="width: 100%;" /><p><strong>3. Agregar señales en un modelo unificado de &quot;energía de mercado&quot;</strong></p><p>Las salidas combinadas aparecen como pulsos brillantes alrededor de cada acción, ilustrando si el impulso está creciendo, el riesgo está aumentando o el sentimiento está cambiando.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/46b3da8e3d528c5dd4e2829416c5446098acb3aa-744x718.png" style="width: 100%;" /><p><strong>4. Visualizar percepciones</strong></p><p>El frontend se construyó con React y <a href="https://github.com/vercel/next.js">Next.js</a>, usando TypeScript, gráficos basados en física SVG y <a href="https://github.com/chartjs">Chart.js</a> para gráficos en tiempo real con candelabros. Esto convierte el análisis en bruto en feedback accionable en tiempo real.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3f000c043117b77ed4127202be5a49c12e3682ba-1600x930.png" style="width: 100%;" /><h2>Otros proyectos interesantes:</h2><p>Aquí tienes otros candidatos fuertes que usaron Elastic en diferentes partes de su stack:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/76c49a853426844f475cd6b2a74999e60af20e8c-926x1080.png" style="width: 100%;" /><p>Encuentra aquí la lista completa de proyectos que se presentaron a <a href="https://cal-hacks-12-0.devpost.com/submissions/search?utf8=%E2%9C%93&amp;prize_filter%5Bprizes%5D%5B%5D=91882">nuestra especialidad.</a></p><h2>Lo que aprendimos de los desarrolladores</h2><ul><li><strong>Agent Builder es fácil de usar:</strong></li></ul><p>La mayoría de los equipos nunca usaron Elastic antes y aún así podían crear agentes rápidamente con poco apoyo. Organizamos un taller para quienes necesitaban más orientación, pero la mayoría pudo absorber sus datos y crear un agente para realizar acciones sobre esos datos.</p><ul><li><strong>Los LLMs son excelentes en <code>kNN</code> consultas, pero aún necesitan orientación para generar ES|QL:</strong></li></ul><p>Pidiendo a ChatGPT-5 que genere ES|Las consultas QL devolvían información incorrecta, a menudo mezclando ES|QL y SQL. Alimentar al LLM la documentación en un archivo de markdown parecía una solución viable.</p><ul><li><strong>ES| solo para instantáneasFunciones QL filtradas en documentos:</strong></li></ul><p>Las próximas funciones de agregación <code>FIRST</code> y <code>LAST</code> pasaron sin querer a nuestro ES|Documentos de QL. Como enviamos esa documentación a ChatGPT, el modelo empleó fielmente estas funciones, aunque aún no estén disponibles en Serverless. Gracias a los comentarios del grupo, ingeniería abrió y fusionó rápidamente una corrección para eliminar las funciones de la documentación publicada (<a href="https://github.com/elastic/elasticsearch/pull/137341">PR #137341</a>).</p><ul><li><strong>Falta orientación específica para Serverless:</strong></li></ul><p>Un equipo intentó activar <code>LOOKUP JOIN</code> en un índice que no se creó en modo búsqueda. El mensaje de error les hizo perseguir comandos que no existen en Serverless. Transmitimos esto al equipo de producto, que inmediatamente abrió una corrección para un mensaje específico y accionable para Serverless. A largo plazo, la visión es ocultar por completo la complejidad del reindexado (<a href="https://github.com/elastic/elasticsearch-serverless/issues/4838">Número #4838</a>).</p><ul><li><strong>Valor de los eventos presenciales:</strong></li></ul><p>Los hackathons online son geniales, pero nada iguala el bucle de retroalimentación rápida que tienes cuando depuras codo a codo con los constructores. Vimos cómo los equipos integraban Agent Builder en diferentes casos de uso, y detectamos la experiencia de los desarrolladores con ES|QL podría mejorar y solucionar los problemas mucho más rápido que intentar hacerlo por canales asincrónicos.</p><h2>Conclusión</h2><p>Cal Hacks 12.0 nos ofreció más que un fin de semana de demos chulas; también nos dio una visión de cómo los nuevos desarrolladores interactúan con el Elastic Stack. En tan solo 36 horas, vimos a los equipos adquirir Agent Builder, incorporar datos en Elasticsearch, diseñar sistemas multiagente y probar nuestras funcionalidades de diversas maneras. El evento también nos recordó por qué son importantes los eventos presenciales. Los rápidos bucles de retroalimentación, las conversaciones reales y la depuración práctica nos ayudaron a entender las necesidades actuales de los desarrolladores. Estamos entusiasmados por devolver lo aprendido al equipo de ingeniería. Nos vemos en el próximo hackathon.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Creación de una redacción de agentes LLM con protocolo A2A y MCP en Elasticsearch: Parte II]]></title>
            <description><![CDATA[Descubre cómo construir una sala de redacción híbrida especializada para agentes LLM empleando el protocolo A2A para la colaboración de agentes y MCP para el acceso a herramientas en Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/a2a-protocol-mcp-llm-agent-workflow-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/a2a-protocol-mcp-llm-agent-workflow-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Justin Castilla]]></dc:creator>
            <pubDate>Mon, 24 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f87330e5d4ca961593b3cfb861ca850a4cc34186-1519x1173.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>A2A y MCP: el código en acción</h2><p>Este es el artículo complementario al artículo &quot;¡Creando una redacción para agentes LLM con protocolo A2A y MCP en Elasticsearch!&quot;, que explicaba los beneficios de implementar tanto las arquitecturas A2A como MCP dentro del mismo agente para aprovechar realmente los beneficios únicos de ambos frameworks. Hay un <a href="https://github.com/justincastilla/elastic-newsroom">repositorio</a> disponible si deseas ejecutar la demo por tu cuenta.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7196f004089127f83547b2e5dc3f663205cfcdce-1162x1600.png" style="width: 100%;" /><p>Vamos a repasar cómo colaboran nuestros agentes de redacción empleando tanto A2A como MCP para producir un artículo de noticias. El repositorio adjunto para ver a los agentes en acción se puede <a href="https://github.com/justincastilla/elastic-newsroom">encontrar aquí</a>.</p><h3>Paso 1: Asignación de la historia</h3><p>El <strong>Jefe de Noticias</strong> (actuando como cliente) asigna una noticia:</p><pre><code>{
  "message_type": "task_request",
  "sender": "news_chief",
  "receiver": "reporter_agent",
  "payload": {
    "task_id": "story_renewable_energy_2024",
    "assignment": {
      "topic": "Renewable Energy Adoption in Europe",
      "angle": "Policy changes driving solar and wind expansion",
      "target_length": 1200,
      "deadline": "2025-09-30T18:00:00Z"
    }
  }
}</code></pre><h3>Paso 2: El reportero aplicar investigación</h3><p>El <strong>Agente Reportero</strong> reconoce que necesita información de fondo y delega al <strong>Agente Investigador</strong> mediante A2A:</p><pre><code>{
  "message_type": "task_request",
  "sender": "reporter_agent",
  "receiver": "researcher_agent",
  "payload": {
    "task_id": "research_eu_renewable_2024",
    "parent_task_id": "story_renewable_energy_2024",
    "capability": "fact_gathering",
    "parameters": {
      "queries": [
        "EU renewable energy capacity 2024",
        "Solar installations growth Europe",
        "Wind energy policy changes 2024"
      ],
      "depth": "comprehensive"
    }
  }
}</code></pre><h3>Paso 3: El reportero aplicar contexto histórico al Agente de Archivo</h3><p>El <strong>Reporter Agent</strong> reconoce que el contexto histórico fortalecería la historia. Delega al <strong>Agente de Archivo</strong> (impulsado <a href="https://www.elastic.co/docs/solutions/search/elastic-agent-builder">por el Agente A2A de Elastic</a>) a través de A2A para buscar en el archivo de artículos impulsado por Elasticsearch de la redacción:</p><pre><code>{
  "message_type": "task_request",
  "sender": "reporter_agent",
  "receiver": "archive_agent",
  "payload": {
    "task_id": "archive_search_renewable_2024",
    "parent_task_id": "story_renewable_energy_2024",
    "capability": "search_archive",
    "parameters": {
      "query": "European renewable energy policy changes and adoption trends over past 5 years",
      "focus_areas": ["solar", "wind", "policy", "Germany", "France"],
      "time_range": "2019-2024",
      "result_count": 10
    }
  }
}</code></pre><h3>Paso 4: El Agente de Archivo emplea el Agente Elastic A2A con MCP</h3><p><strong>El Archive Agent</strong> emplea el A2A Agent de Elastic, que a su vez emplea MCP para acceder a las herramientas de Elasticsearch. Esto demuestra la arquitectura híbrida donde A2A permite la colaboración entre agentes mientras que MCP proporciona acceso a herramientas:</p><pre><code># Archive Agent using Elastic A2A Agent
async def search_historical_articles(self, query_params):
    # The Archive Agent sends a request to Elastic's A2A Agent
    elastic_response = await self.a2a_client.send_request(
        agent="elastic_agent",
        capability="search_and_analyze",
        parameters={
            "natural_language_query": query_params["query"],
            "index_pattern": "newsroom-articles-*",
            "filters": {
                "topics": query_params["focus_areas"],
                "date_range": query_params["time_range"]
            },
            "analysis_type": "trend_analysis"
        }
    )
    
    # Elastic's A2A Agent internally uses MCP tools:
    # - platform.core.search (to find relevant articles)
    # - platform.core.generate_esql (to analyze trends)
    # - platform.core.index_explorer (to identify relevant indices)
    
    return elastic_response</code></pre><p>El <strong>Agente de Archivo</strong> recibe datos históricos completos del Agente A2A de Elastic y los devuelve al Reportero:</p><pre><code>{
  "message_type": "task_response",
  "sender": "archive_agent",
  "receiver": "reporter_agent",
  "payload": {
    "task_id": "archive_search_renewable_2024",
    "status": "completed",
    "archive_data": {
      "historical_articles": [
        {
          "title": "Germany's Energiewende: Five Years of Solar Growth",
          "published": "2022-06-15",
          "key_points": [
            "Germany added 7 GW annually 2020-2022",
            "Policy subsidies drove 60% of growth"
          ],
          "relevance_score": 0.94
        },
        {
          "title": "France Balances Nuclear and Renewables",
          "published": "2023-03-20",
          "key_points": [
            "France increased renewable target to 40% by 2030",
            "Solar capacity doubled 2021-2023"
          ],
          "relevance_score": 0.89
        }
      ],
      "trend_analysis": {
        "coverage_frequency": "EU renewable stories increased 150% since 2019",
        "emerging_themes": ["policy incentives", "grid modernization", "battery storage"],
        "coverage_gaps": ["Small member states", "offshore wind permitting"]
      },
      "total_articles_found": 47,
      "search_confidence": 0.91
    }
  }
}</code></pre><p>Este paso demuestra cómo el Agente A2A de Elastic se integra en el flujo de trabajo de la redacción. El Agente de Archivo (un agente específico de redacción) coordina con el Agente A2A de Elastic (un especialista externo) para aprovechar las poderosas capacidades de búsqueda y análisis de Elasticsearch. El agente de Elastic emplea MCP internamente para acceder a las herramientas de Elasticsearch, mostrando la separación limpia entre la coordinación del agente (A2A) y el acceso a la herramienta (MCP).</p><h3>Paso 5: El investigador emplea servidores MCP</h3><p>El <strong>Agente Investigador</strong> accede a múltiples servidores MCP para recopilar información:</p><pre><code># Researcher Agent using MCP to access tools
async def gather_facts(self, queries):
    results = []
    
    # Use News API MCP Server
    news_data = await self.mcp_client.invoke_tool(
        server="news_api_mcp",
        tool="search_articles",
        parameters={
            "query": queries[0],
            "date_range": "2024-01-01 to 2024-09-30",
            "sources": ["reuters", "bloomberg", "euractiv"]
        }
    )
    
    # Use Statistics MCP Server
    stats_data = await self.mcp_client.invoke_tool(
        server="eurostat_mcp",
        tool="get_energy_statistics",
        parameters={
            "metrics": ["solar_capacity_mw", "wind_capacity_mw"],
            "countries": ["EU27"],
            "year": 2024
        }
    )
    
    # Use Fact-Checking MCP Server
    verified_facts = await self.mcp_client.invoke_tool(
        server="factcheck_mcp",
        tool="verify_claims",
        parameters={
            "claims": [
                "EU solar capacity increased 40% in 2024",
                "Germany leads in wind installations"
            ]
        }
    )
    
    return self._synthesize_research(news_data, stats_data, verified_facts)</code></pre><h3>Paso 6: El investigador devuelve los datos al Reportero</h3><p>El <strong>Agente Investigador</strong> envía una investigación exhaustiva de vuelta a través de A2A:</p><pre><code>{
  "message_type": "task_response",
  "sender": "researcher_agent",
  "receiver": "reporter_agent",
  "payload": {
    "task_id": "research_eu_renewable_2024",
    "status": "completed",
    "research_data": {
      "key_facts": [
        {
          "claim": "EU solar capacity reached 260 GW in 2024",
          "source": "Eurostat Q3 2024 Report",
          "confidence": 0.95,
          "verified": true
        },
        {
          "claim": "Germany installed 12 GW of wind capacity in 2024",
          "source": "German Federal Network Agency",
          "confidence": 0.92,
          "verified": true
        }
      ],
      "statistics": {
        "solar_growth_rate": "35%",
        "wind_growth_rate": "28%"
      },
      "sources_count": 15
    }
  }
}</code></pre><h3>Paso 7: Reportero escribe artículo</h3><p>El <strong>Reporter Agent</strong> emplea los datos de investigación y sus propias capacidades de LLM para redactar el artículo. Durante la redacción, el Reportero emplea los servidores MCP para el estilo y las plantillas:</p><pre><code># Reporter Agent writing with MCP assistance
async def write_article(self, research_data, assignment):
    # Get style guidelines via MCP
    style_guide = await self.mcp_client.get_resource(
        server="newsroom_mcp",
        resource="style://editorial/ap_style_guide"
    )
    
    # Get article template via MCP
    template = await self.mcp_client.get_resource(
        server="newsroom_mcp",
        resource="template://articles/news_story"
    )
    
    # Generate article using LLM + research + style
    draft = await self.llm.generate(
        prompt=f"""
        Write a news article following these guidelines:
        {style_guide}
        
        Using this template:
        {template}
        
        Based on this research:
        {research_data}
        
        Assignment: {assignment}
        """
    )
    
    # Self-evaluate confidence in claims
    confidence_check = await self._evaluate_confidence(draft)
    
    return draft, confidence_check</code></pre><h3>Paso 8: la baja confianza desencadena la re-investigación</h3><p>El <strong>Agente Reportero</strong> evalúa su borrador y encuentra que una afirmación tiene baja confianza. Envía otra solicitud al <strong>Agente Investigador</strong>:</p><pre><code>{
  "message_type": "collaboration_request",
  "sender": "reporter_agent",
  "receiver": "researcher_agent",
  "payload": {
    "request_type": "fact_verification",
    "claims": [
      {
        "text": "France's nuclear phase-down contributed to 15% increase in renewable capacity",
        "context": "Discussing policy drivers for renewable growth",
        "current_confidence": 0.45,
        "required_confidence": 0.80
      }
    ],
    "urgency": "high"
  }
}</code></pre><p>El <strong>investigador</strong> verifica la afirmación empleando servidores MCP de verificación de hechos y devuelve información actualizada:</p><pre><code>{
  "message_type": "collaboration_response",
  "sender": "researcher_agent",
  "receiver": "reporter_agent",
  "payload": {
    "verified_claims": [
      {
        "original_claim": "France's nuclear phase-down contributed to 15% increase...",
        "verified_claim": "France's renewable capacity increased 18% in 2024, partially offsetting reduced nuclear output",
        "confidence": 0.88,
        "corrections": "Percentage was 18%, not 15%; nuclear phase-down is gradual, not primary driver",
        "sources": ["RTE France", "French Energy Ministry Report 2024"]
      }
    ]
  }
}</code></pre><h3>Paso 9: El reportero revisa y envía al editor</h3><p>El <strong>Reportero</strong> incorpora los hechos verificados y envía el borrador completo al <strong>Agente Editor</strong> a través de A2A:</p><pre><code>{
  "message_type": "task_request",
  "sender": "reporter_agent",
  "receiver": "editor_agent",
  "payload": {
    "task_id": "edit_renewable_story",
    "parent_task_id": "story_renewable_energy_2024",
    "content": {
      "headline": "Europe's Renewable Revolution: Solar and Wind Surge 30% in 2024",
      "body": "[Full article text...]",
      "word_count": 1185,
      "sources": [/* array of sources */]
    },
    "editing_requirements": {
      "check_style": true,
      "check_facts": true,
      "check_seo": true
    }
  }
}</code></pre><h3>Paso 10: Revisiones del editor usando herramientas MCP</h3><p>El <strong>Editor Agent</strong> emplea múltiples servidores MCP para revisar el artículo:</p><pre><code># Editor Agent using MCP for quality checks
async def review_article(self, content):
    # Grammar and style check
    grammar_issues = await self.mcp_client.invoke_tool(
        server="grammarly_mcp",
        tool="check_document",
        parameters={"text": content["body"]}
    )
    
    # SEO optimization check
    seo_analysis = await self.mcp_client.invoke_tool(
        server="seo_mcp",
        tool="analyze_content",
        parameters={
            "headline": content["headline"],
            "body": content["body"],
            "target_keywords": ["renewable energy", "Europe", "solar", "wind"]
        }
    )
    
    # Plagiarism check
    originality = await self.mcp_client.invoke_tool(
        server="plagiarism_mcp",
        tool="check_originality",
        parameters={"text": content["body"]}
    )
    
    # Generate editorial feedback
    feedback = await self._generate_feedback(
        grammar_issues, 
        seo_analysis, 
        originality
    )
    
    return feedback</code></pre><p>El <strong>editor</strong> aprueba el artículo y lo envía hacia adelante:</p><pre><code>{
  "message_type": "task_response",
  "sender": "editor_agent",
  "receiver": "reporter_agent",
  "payload": {
    "status": "approved",
    "quality_score": 9.2,
    "minor_edits": [
      "Changed 'surge' to 'increased' in paragraph 3 for AP style consistency",
      "Added Oxford comma in list of countries"
    ],
    "approved_content": "[Final edited article]"
  }
}</code></pre><h3>Paso 11: El editor publica vía CI/CD</h3><p>Finalmente, el <strong>Agente de Impresora</strong> publica el artículo aprobado empleando los servidores MCP para la tubería CMS y CI/CD:</p><pre><code># Publisher Agent publishing via MCP
async def publish_article(self, content, metadata):
    # Upload to CMS via MCP
    cms_result = await self.mcp_client.invoke_tool(
        server="wordpress_mcp",
        tool="create_post",
        parameters={
            "title": content["headline"],
            "body": content["body"],
            "status": "draft",
            "categories": metadata["categories"],
            "tags": metadata["tags"],
            "featured_image_url": metadata["image_url"]
        }
    )
    
    post_id = cms_result["post_id"]
    
    # Trigger CI/CD deployment via MCP
    deploy_result = await self.mcp_client.invoke_tool(
        server="cicd_mcp",
        tool="trigger_deployment",
        parameters={
            "pipeline": "publish_article",
            "environment": "production",
            "post_id": post_id,
            "schedule": "immediate"
        }
    )
    
    # Track analytics
    await self.mcp_client.invoke_tool(
        server="analytics_mcp",
        tool="register_publication",
        parameters={
            "post_id": post_id,
            "publish_time": datetime.now().isoformat(),
            "story_id": metadata["story_id"]
        }
    )
    
    return {
        "status": "published",
        "post_id": post_id,
        "url": f"https://newsroom.example.com/articles/{post_id}",
        "deployment_id": deploy_result["deployment_id"]
    }</code></pre><p>El <strong>editor</strong> confirma la publicación a través de A2A:</p><pre><code>{
  "message_type": "task_complete",
  "sender": "printer_agent",
  "receiver": "news_chief",
  "payload": {
    "task_id": "story_renewable_energy_2024",
    "status": "published",
    "publication": {
      "url": "https://newsroom.example.com/articles/renewable-europe-2024",
      "published_at": "2025-09-30T17:45:00Z",
      "post_id": "12345"
    },
    "workflow_metrics": {
      "total_time_minutes": 45,
      "agents_involved": ["reporter", "researcher", "archive", "editor", "printer"],
      "iterations": 2,
      "mcp_calls": 12
    }
  }
}</code></pre><p>Aquí está la secuencia completa del flujo de trabajo A2A en el repositorio adjunto usando los mismos Agentes descritos anteriormente.</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">#</td><td style="border: 1px solid #ccc; padding: 4px;">De</td><td style="border: 1px solid #ccc; padding: 4px;">Para</td><td style="border: 1px solid #ccc; padding: 4px;">Acción</td><td style="border: 1px solid #ccc; padding: 4px;">Protocolo</td><td style="border: 1px solid #ccc; padding: 4px;">Descripción</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">1</td><td style="border: 1px solid #ccc; padding: 4px;">Usuario</td><td style="border: 1px solid #ccc; padding: 4px;">Jefe de Noticias</td><td style="border: 1px solid #ccc; padding: 4px;">Asignar historia</td><td style="border: 1px solid #ccc; padding: 4px;">HTTP POST</td><td style="border: 1px solid #ccc; padding: 4px;">El usuario presenta la historia, tema y ángulo</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">2</td><td style="border: 1px solid #ccc; padding: 4px;">Jefe de Noticias</td><td style="border: 1px solid #ccc; padding: 4px;">Interno</td><td style="border: 1px solid #ccc; padding: 4px;">Crear historia</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">Crea un registro de historia con un ID único</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">3</td><td style="border: 1px solid #ccc; padding: 4px;">Jefe de Noticias</td><td style="border: 1px solid #ccc; padding: 4px;">Reportero</td><td style="border: 1px solid #ccc; padding: 4px;">Asignación de Delegados</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Envía asignación de historia mediante el protocolo A2A</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">4</td><td style="border: 1px solid #ccc; padding: 4px;">Reportero</td><td style="border: 1px solid #ccc; padding: 4px;">Interno</td><td style="border: 1px solid #ccc; padding: 4px;">Aceptar Asignación</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">Asignación interna de almacenes</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">5</td><td style="border: 1px solid #ccc; padding: 4px;">Reportero</td><td style="border: 1px solid #ccc; padding: 4px;">Servidor MCP</td><td style="border: 1px solid #ccc; padding: 4px;">Generar esquema</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">Crea un esquema de artículos y preguntas de investigación</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">6A</td><td style="border: 1px solid #ccc; padding: 4px;">Reportero</td><td style="border: 1px solid #ccc; padding: 4px;">Investigador</td><td style="border: 1px solid #ccc; padding: 4px;">Solicitud de investigación</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Envía preguntas (paralelo con 6b)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">6b</td><td style="border: 1px solid #ccc; padding: 4px;">Reportero</td><td style="border: 1px solid #ccc; padding: 4px;">Archivero</td><td style="border: 1px solid #ccc; padding: 4px;">Archivo de búsqueda</td><td style="border: 1px solid #ccc; padding: 4px;">A2A JSONRPC</td><td style="border: 1px solid #ccc; padding: 4px;">Busca en artículos históricos (paralelo con 6a)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">7</td><td style="border: 1px solid #ccc; padding: 4px;">Investigador</td><td style="border: 1px solid #ccc; padding: 4px;">Servidor MCP</td><td style="border: 1px solid #ccc; padding: 4px;">Preguntas de investigación</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">Emplea Anthropic vía MCP para responder preguntas</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">8</td><td style="border: 1px solid #ccc; padding: 4px;">Investigador</td><td style="border: 1px solid #ccc; padding: 4px;">Reportero</td><td style="border: 1px solid #ccc; padding: 4px;">Investigación de Retorno</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Respuestas de investigación de retornos</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">9</td><td style="border: 1px solid #ccc; padding: 4px;">Archivero</td><td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">Índice de búsqueda</td><td style="border: 1px solid #ccc; padding: 4px;">ES REST API</td><td style="border: 1px solid #ccc; padding: 4px;">Consultas news_archive índice</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">10</td><td style="border: 1px solid #ccc; padding: 4px;">Archivero</td><td style="border: 1px solid #ccc; padding: 4px;">Reportero</td><td style="border: 1px solid #ccc; padding: 4px;">Archivo de retorno</td><td style="border: 1px solid #ccc; padding: 4px;">A2A JSONRPC</td><td style="border: 1px solid #ccc; padding: 4px;">Devuelve resultados históricos de búsqueda</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">11</td><td style="border: 1px solid #ccc; padding: 4px;">Reportero</td><td style="border: 1px solid #ccc; padding: 4px;">Servidor MCP</td><td style="border: 1px solid #ccc; padding: 4px;">Generar artículo</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">Crea un artículo con contexto de investigación/archivo</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">12</td><td style="border: 1px solid #ccc; padding: 4px;">Reportero</td><td style="border: 1px solid #ccc; padding: 4px;">Interno</td><td style="border: 1px solid #ccc; padding: 4px;">Tiro de la tienda</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">Salvamentos en el draft interno</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">13</td><td style="border: 1px solid #ccc; padding: 4px;">Reportero</td><td style="border: 1px solid #ccc; padding: 4px;">Jefe de Noticias</td><td style="border: 1px solid #ccc; padding: 4px;">Enviar Borrador</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Entrega borrador completado</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">14</td><td style="border: 1px solid #ccc; padding: 4px;">Jefe de Noticias</td><td style="border: 1px solid #ccc; padding: 4px;">Interno</td><td style="border: 1px solid #ccc; padding: 4px;">Actualización de la historia</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">Borrador de la tienda, actualiza el estado a "draft_submitted"</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">15</td><td style="border: 1px solid #ccc; padding: 4px;">Jefe de Noticias</td><td style="border: 1px solid #ccc; padding: 4px;">Editor</td><td style="border: 1px solid #ccc; padding: 4px;">Borrador de revisión</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Rutas automáticas al Editor para su revisión</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">16</td><td style="border: 1px solid #ccc; padding: 4px;">Editor</td><td style="border: 1px solid #ccc; padding: 4px;">Servidor MCP</td><td style="border: 1px solid #ccc; padding: 4px;">Artículo de revisión</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">Analiza contenido usando Anthropic a través de MCP</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">17</td><td style="border: 1px solid #ccc; padding: 4px;">Editor</td><td style="border: 1px solid #ccc; padding: 4px;">Jefe de Noticias</td><td style="border: 1px solid #ccc; padding: 4px;">Reseña de Retorno</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Envía comentarios y sugerencias editoriales</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">18</td><td style="border: 1px solid #ccc; padding: 4px;">Jefe de Noticias</td><td style="border: 1px solid #ccc; padding: 4px;">Interno</td><td style="border: 1px solid #ccc; padding: 4px;">Reseña de la tienda</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">Comentarios sobre el editor de tiendas</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">19</td><td style="border: 1px solid #ccc; padding: 4px;">Jefe de Noticias</td><td style="border: 1px solid #ccc; padding: 4px;">Reportero</td><td style="border: 1px solid #ccc; padding: 4px;">Aplicar ediciones</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Comentarios de la revisión de rutas al Reportero</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">20</td><td style="border: 1px solid #ccc; padding: 4px;">Reportero</td><td style="border: 1px solid #ccc; padding: 4px;">Servidor MCP</td><td style="border: 1px solid #ccc; padding: 4px;">Aplicar ediciones</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">Revisa el artículo basar en los comentarios</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">21</td><td style="border: 1px solid #ccc; padding: 4px;">Reportero</td><td style="border: 1px solid #ccc; padding: 4px;">Interno</td><td style="border: 1px solid #ccc; padding: 4px;">Actualización del borrador</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">Borrador de actualizaciones con revisiones</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">22</td><td style="border: 1px solid #ccc; padding: 4px;">Reportero</td><td style="border: 1px solid #ccc; padding: 4px;">Jefe de Noticias</td><td style="border: 1px solid #ccc; padding: 4px;">Devolución revisada</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Artículo revisado de declaraciones</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">23</td><td style="border: 1px solid #ccc; padding: 4px;">Jefe de Noticias</td><td style="border: 1px solid #ccc; padding: 4px;">Interno</td><td style="border: 1px solid #ccc; padding: 4px;">Actualización de la historia</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">Borrador revisado de las tiendas, estado a "revisado"</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">24</td><td style="border: 1px solid #ccc; padding: 4px;">Jefe de Noticias</td><td style="border: 1px solid #ccc; padding: 4px;">Editor</td><td style="border: 1px solid #ccc; padding: 4px;">Publicar artículo</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Rutas automáticas hacia el editor</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">25</td><td style="border: 1px solid #ccc; padding: 4px;">Editor</td><td style="border: 1px solid #ccc; padding: 4px;">Servidor MCP</td><td style="border: 1px solid #ccc; padding: 4px;">Generar etiquetas</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">Crea etiquetas y categorías</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">26</td><td style="border: 1px solid #ccc; padding: 4px;">Editor</td><td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">Artículo índice</td><td style="border: 1px solid #ccc; padding: 4px;">ES REST API</td><td style="border: 1px solid #ccc; padding: 4px;">Índice de artículos a índice news_archive</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">27</td><td style="border: 1px solid #ccc; padding: 4px;">Editor</td><td style="border: 1px solid #ccc; padding: 4px;">Sistema de archivos</td><td style="border: 1px solid #ccc; padding: 4px;">Almacenar Markdown</td><td style="border: 1px solid #ccc; padding: 4px;">E/S de archivos</td><td style="border: 1px solid #ccc; padding: 4px;">Almacena el artículo como .md archivo en /articles</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">28</td><td style="border: 1px solid #ccc; padding: 4px;">Editor</td><td style="border: 1px solid #ccc; padding: 4px;">Jefe de Noticias</td><td style="border: 1px solid #ccc; padding: 4px;">Confirmar publicación</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">Resultados de éxito</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">29</td><td style="border: 1px solid #ccc; padding: 4px;">Jefe de Noticias</td><td style="border: 1px solid #ccc; padding: 4px;">Interno</td><td style="border: 1px solid #ccc; padding: 4px;">Actualización de la historia</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">Actualiza el estado de la noticia a "publicado"</td>
                    </tr>
              </tbody>
            </table>
          <h2>Conclusión</h2><p>Tanto A2A como MCP tienen papeles importantes en el paradigma moderno de infraestructura de LLM aumentado. A2A ofrece flexibilidad para sistemas complejos multiagente, pero potencialmente menos portabilidad y mayor complejidad operativa. MCP ofrece un enfoque estandarizado para la integración de herramientas que es más sencillo de implementar y mantener, aunque no está diseñado para manejar orquestación multiagente.</p><p>La elección no es binaria. Como demuestra nuestro ejemplo de redacción, los sistemas respaldados por LLM más sofisticados y efectivos suelen combinar ambos enfoques: los agentes coordinan y se especializan mediante protocolos A2A mientras acceden a sus herramientas y recursos a través de servidores MCP. Esta arquitectura híbrida ofrece los beneficios organizacionales de los sistemas multiagente junto con los beneficios de estandarización y ecosistema del MCP. Esto sugiere que puede que no sea necesario elegir en absoluto: simplemente usar ambos como un enfoque estándar</p><p>Depende de ti, como desarrollador o arquitecto, probar y determinar la mejor combinación de ambas soluciones para crear el resultado adecuado para tu caso de uso específico. Comprender las fortalezas, limitaciones y aplicaciones adecuadas de cada enfoque te permitirá construir sistemas de IA más eficaces, mantenibles y escalables.</p><p>Ya sea que estés construyendo una redacción digital, una plataforma de atención al cliente, un asistente de investigación o cualquier otra aplicación impulsada por LLM, considerar cuidadosamente tus necesidades de coordinación (A2A) y requisitos de acceso a herramientas (MCP) te pondrá en el camino hacia el éxito.</p><h2>Recursos adicionales</h2><ul><li><strong>Constructor de agentes Elasticsearch: </strong><a href="https://www.elastic.co/docs/solutions/search/elastic-agent-builder">https://www.elastic.co/docs/solutions/search/elastic-agent-builder</a></li><li><strong>Especificación A2A</strong>: <a href="https://a2a-protocol.org/latest/specification/">https://a2a-protocol.org/latest/specification/</a></li><li><strong>Integración de A2A y MCP</strong>: <a href="https://a2a-protocol.org/latest/topics/a2a-and-mcp/">https://a2a-protocol.org/latest/topics/a2a-and-mcp/</a></li><li><strong>Protocolo de Contexto del Modelo</strong>: <a href="https://modelcontextprotocol.io/">https://modelcontextprotocol.io</a></li></ul>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/es/blog/context-engineering-hybrid-search-agentic-ai-accuracy</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/context-engineering-hybrid-search-agentic-ai-accuracy</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Woody Walton]]></dc:creator>
            <pubDate>Thu, 20 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b82ff25242e4229cc20b218d9cc91c60cfd680bc-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <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 &quot;la búsqueda está muerta&quot; (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><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 &quot;me gusta&quot;). 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.</li><li><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.</li><li><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 &#x27;criticidad&#x27; y &#x27;más debatido&#x27; para sopesar diferentes factores de forma independiente, cerciorando que los temas más críticos y debatidos salgan a la superficie</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><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.</li><li><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.</li><li><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.</li><li><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.</li><li><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.</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><pre><code>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</code></pre><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 &#x27;<a href="https://www.elastic.co/search-labs/blog/mcp-intelligent-search">MCP for intelligent search</a>&#x27;, 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 &quot;razonamientos&quot; 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 &quot;<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>&quot; 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 &quot;ralentizar para ir más rápido&quot;, la simplicidad en la capa de generación de contexto significa una IA agente más rápida y fiable.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ya sabes, para contextualizar - Parte II: IA agente y la necesidad de la ingeniería del contexto]]></title>
            <description><![CDATA[Aprende cómo la evolución de los LLMs hacia la IA agente aumenta la necesidad de ingeniería de contexto para resolver los límites de contexto de RAG y la gestión de la memoria.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/context-engineering-llm-evolution-agentic-ai</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/context-engineering-llm-evolution-agentic-ai</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Woody Walton]]></dc:creator>
            <pubDate>Tue, 18 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6153ce399e5498841ef2ffa5363a3e2d2dea726d-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Con ese conocimiento (bastante <a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">extenso)</a> sobre cómo los LLMs cambiaron los procesos subyacentes de recuperación de información, veamos cómo también cambiaron la forma en que consultamos datos.</p><h2>Una nueva forma de interactuar con los datos</h2><p>La IA generativa (genIA) y la IA agente hacen las cosas de forma diferente a la búsqueda tradicional. Mientras que la forma en que empezábamos a investigar la información era buscando (&quot;déjame buscar en Google...&quot;), la acción inicial tanto para la IA de generación como para los Agentes suele ser mediante lenguaje natural introducido en una interfaz de chat. La interfaz de chat es una discusión con un LLM que emplea su comprensión semántica para convertir nuestra pregunta en una respuesta destilada, una respuesta resumida que aparentemente proviene de un oráculo que tiene un amplio conocimiento de todo tipo de información. Lo que realmente lo vende es la capacidad del LLM para generar frases coherentes y reflexivas que enlazan los fragmentos de conocimiento que saca a la luz — incluso cuando son inexactas o totalmente alucinadas, tienen <a href="https://en.wikipedia.org/wiki/Truthiness">cierta veracidad</a> .</p><p>Esa vieja barra de búsqueda con la que estábamos tan acostumbrados a interactuar puede considerar el motor RAG que usábamos cuando <strong><em>nosotros mismos</em></strong> éramos el agente de razonamiento. Ahora, incluso los motores de búsqueda de Internet están convirtiendo nuestra experiencia léxica de búsqueda &quot;caza y picotea&quot; en una visión general impulsada por IA que responde a la consulta con un resumen de los resultados, ayudando a los usuarios a evitar la necesidad de hacer clic y evaluar los resultados individuales por sí mismos.</p><h2>IA generativa y RAG</h2><p>La IA generativa intenta usar su comprensión semántica del mundo para analizar la intención subjetiva expresada a través de una solicitud de chat, y luego emplea sus habilidades de inferencia para crear una respuesta experta sobre la marcha. Hay varias partes en una interacción generativa con IA: comienza con la entrada/consulta del usuario, conversaciones previas en la sesión de chat pueden usar como contexto adicional, y el prompt instructivo que indica al LLM cómo razonar y qué procedimientos seguir para construir la respuesta. Los prompts evolucionaron desde simples &quot;explícame esto como si tuviera cinco años&quot; a desglosar completos sobre cómo procesar solicitudes. Estos desgloses suelen incluir secciones distintas que describen detalles de la persona/rol de la IA, razonamiento pregeneración/proceso de pensamiento interno, criterios objetivos, restricciones, formato de salida, audiencia, así como ejemplos para ayudar a demostrar los resultados esperados.</p><p>Además de la consulta del usuario y el prompt del sistema, la generación aumentada por recuperación (RAG) proporciona información contextual adicional en lo que se denomina una &quot;ventana de contexto&quot;. RAG fue una adición fundamental a la arquitectura; es lo que usamos para informar al LLM sobre las piezas que faltan en su comprensión semántica del mundo.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5b9671d5d07d4caefde372bb3188000754a91eed-1470x746.png" style="width: 100%;" /><p>Las ventanas de contexto pueden ser un <a href="https://www.dbreunig.com/2025/06/22/how-contexts-fail-and-how-to-fix-them.html">poco quisquillosas</a> en cuanto a qué, dónde y cuánto les das. Qué contexto se selecciona es muy importante, por supuesto, pero también importa la relación señal-ruido del contexto proporcionado, así como la duración de la ventana.</p><h3>Muy poca información</h3><p>Proporcionar muy poca información en una consulta, una indicación o una ventana de contexto puede provocar alucinaciones porque el LLM no puede determinar con precisión el contexto semántico correcto desde el que generar una respuesta. También existen problemas con la similitud vectorial de los tamaños de fragmentos de documentos: una pregunta corta y sencilla puede no coincidir semánticamente con los documentos completos y detallados que encontramos en nuestras bases de conocimiento vectorizadas. Se desarrollaron técnicas de expansión de consultas como <a href="https://medium.com/data-science/how-to-use-hyde-for-better-llm-rag-retrieval-a0aa5d0e23e8">los Embeddings de Documentos Hipotéticos (HyDE)</a> que emplean LLMs para generar una respuesta hipotética más rica y expresiva que la consulta corta. El peligro aquí, por supuesto, es que el documento hipotético es en sí mismo una alucinación que aleja aún más al LLM del contexto correcto.</p><h3>Demasiada información</h3><p>Al igual que nos pasa a los humanos, demasiada información en una ventana de contexto puede abrumar y confundir a un LLM sobre cuáles deberían ser las partes importantes. El desbordamiento de contexto (o &quot;<a href="https://research.trychroma.com/context-rot">podredumbre del contexto</a>&quot;) afecta a la calidad y el rendimiento de las operaciones de IA generativa; afecta enormemente a la &quot;cotización de atención&quot; del LLM (su memoria de trabajo) y diluye la relevancia entre muchos tokens competidores. El concepto de &quot;podredumbre del contexto&quot; también incluye la observación de que los LLMs tienden a tener un <a href="https://alexandrabarr.beehiiv.com/p/context-windows">sesgo posicional</a> : prefieren el contenido al principio o al final de una ventana de contexto sobre el contenido de la sección central.</p><h3>Información que distrae o contradice</h3><p>Cuanto más grande es una ventana de contexto, más posibilidades hay de que incluya información superflua o contradictoria que pueda distraer al LLM de seleccionar y procesar el contexto correcto. En cierto modo, se convierte en un problema de basura entrando y saliendo basura: simplemente volcar un conjunto de documentos resulta en una ventana de contexto le da al LLM mucha información para analizar (potencialmente demasiado), pero dependiendo de cómo se seleccionó el contexto hay una mayor posibilidad de que se filtre información contradictoria o irrelevante.</p><h2>Agentic AI</h2><p>Te dije que había mucho por cubrir, pero lo conseguimos — ¡por fin estamos hablando de temas de IA agente! La IA Agente es un uso muy emocionante de las interfaces de chat LLM que amplía la capacidad de la IA generativa (¿podemos llamarla ya &quot;legado&quot;?) para sintetizar respuestas basar en su propio conocimiento y la información contextual que proporcionas. A medida que la IA generativa maduraba, nos dimos cuenta de que había un cierto nivel de tareas y automatización que podíamos hacer con los LLMs, inicialmente relegados a actividades tediosas y de bajo riesgo que un humano podía comprobar o validar fácilmente. En un corto periodo de tiempo, ese alcance inicial creció: una ventana de chat de un LLM puede ahora ser la chispa que envíe a un agente de IA para planear, ejecutar y evaluar iterativamente su plan para lograr su objetivo especificado. Los agentes tienen acceso al razonamiento propio de sus LLMs, al historial de chat y a la memoria de pensamiento (tal como es), y también disponen de herramientas específicas que pueden emplear para ese objetivo. También estamos viendo arquitecturas que permiten a un agente de alto nivel actuar como orquestador de múltiples <a href="https://www.philschmid.de/the-rise-of-subagents">subagentes</a>, cada uno con sus propias cadenas lógicas, conjuntos de instrucciones, contexto y herramientas.</p><p>Los agentes son el punto de entrada a un flujo de trabajo mayormente automatizado: son autodirigidos en el sentido de que pueden chatear con un usuario y luego usar la &#x27;lógica&#x27; para determinar qué herramientas tienen disponibles para ayudar a responder a la pregunta del usuario. Las herramientas suelen considerar pasivas en comparación con los agentes y están diseñadas para realizar un solo tipo de tarea. Los <em>tipos</em> de tareas que una herramienta puede realizar son bastante ilimitados (¡lo cual es realmente emocionante!), pero una tarea principal que realizan las herramientas es recopilar información contextual para que un agente la tenga en cuenta al ejecutar su flujo de trabajo.</p><p>Como tecnología, la IA agente aún está en pañezas y propensa al equivalente LLM del trastorno por déficit de atención: olvida fácilmente lo que se le pide hacer y a menudo se escapa a hacer otras cosas que no formaban parte del encargo. Bajo la aparente magia, las habilidades de &quot;razonamiento&quot; de los LLM siguen basar en predecir el siguiente token más probable en una secuencia. Para que el razonamiento (o algún día, la inteligencia artificial general (AGI)) sea fiable y digno de confianza, necesitamos poder verificar que, cuando se nos da la información correcta y más actualizada, razonarán como esperamos (y quizás nos darán ese poco más que quizá no pensamos). Para que eso ocurra, las arquitecturas agenticas necesitarán la capacidad de comunicar claramente (protocolos), adherir a los flujos de trabajo y restricciones que les damos (barreras de seguridad), recordar en qué punto de una tarea (estado) se sienten, gestionar su espacio de memoria disponible y validar que sus respuestas son precisas y cumplen los criterios de la tarea.</p><h2>Háblame en un idioma que pueda entender</h2><p>Como es habitual en nuevas áreas de desarrollo (especialmente en el mundo de los LLM), inicialmente existían bastantes enfoques para la comunicación agente-herramienta, pero rápidamente convergieron hacia el <a href="https://modelcontextprotocol.io/docs/getting-started/intro">Protocolo de Contexto del Modelo (MCP)</a> como estándar de facto. La definición de Protocolo de Contexto de Modelo está realmente en el nombre: es el <strong>protocolo</strong> que emplea un <strong>modelo</strong> para aplicar y recibir información <strong>contextual</strong> . MCP actúa como un adaptador universal para que los agentes LLM se conecten a herramientas externas y fuentes de datos; simplifica y estandariza las APIs para que diferentes frameworks y herramientas de LLM puedan interoperar fácilmente. Eso convierte a MCP en una especie de punto de pivote entre la lógica de orquestación y los indicios del sistema dados a un agente para actuar de forma autónoma al servicio de sus objetivos, y las operaciones enviadas a las herramientas para que se ejecuten de forma más aislada (aislada al menos respecto al agente iniciador).</p><p>Este ecosistema es tan nuevo que cada dirección de expansión se siente como una nueva frontera. Tenemos protocolos similares para interacciones agente a agente (<a href="https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/">Agent2Agent (A2A</a> , por supuesto!) así como otros proyectos para mejorar la memoria de razonamiento de agentes (<a href="https://venturebeat.com/ai/new-memory-framework-builds-ai-agents-that-can-handle-the-real-worlds">ReasoningBank</a>), para seleccionar el mejor servidor MCP para el trabajo en cuestión (<a href="https://arxiv.org/abs/2505.03275">RAG-MCP</a>), y usar análisis semántico como la clasificación zero-shot y la detección de patrones en entrada y salida como <a href="https://openai.github.io/openai-guardrails-python/">Guardrails</a> para controlar sobre qué puede operar un agente.</p><p>Quizá notaste que la intención subyacente de cada uno de estos proyectos es mejorar la calidad y el control de la información que se devuelve en una ventana de contexto agente/genAI. Aunque el ecosistema de IA agente continúa desarrollando la capacidad de manejar mejor esa información contextual (para controlarla, gestionar y operar sobre ella), siempre habrá necesidad de recuperar la información <em>contextual más relevante</em> como materia para que el agente siga adelante.</p><h2>¡Bienvenido a la ingeniería de contexto!</h2><p>Si conoces los términos de IA generativa, probablemente oíste hablar de la &#x27;ingeniería de prompts&#x27;; a estas alturas, es casi una pseudociencia en sí misma. La ingeniería de prompts se emplea para encontrar las mejores y más eficientes formas de describir proactivamente los comportamientos que quieres que el LLM emplee para generar su respuesta. La &#x27;<a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">ingeniería de contexto</a>&#x27; extiende las técnicas de &#x27;ingeniería de prompts&#x27; más allá del lado del agente para cubrir también las fuentes y sistemas de contexto disponibles en el lado de herramientas del protocolo MCP, e incluye los temas generales de gestión, procesamiento y generación de contexto:</p><ul><li><strong>Gestión del contexto </strong>- Relacionada con mantener el estado y la eficiencia del contexto en flujos de trabajo agentivos de larga duración y/o más complejos. Planeación iterativa, seguimiento y orquestación de tareas y llamada a herramientas para lograr los objetivos del agente. Debido a la limitada &quot;cotización de atención&quot; que los agentes deben trabajar, la gestión del contexto se centra principalmente en técnicas que ayudan a refinar la ventana de contexto para capturar tanto el alcance más completo como los aspectos más importantes del contexto (¡su precisión frente a la memoria!). Las técnicas incluyen compresión, resumen y persistencia de contexto de pasos previos o llamadas a herramientas para dejar espacio en la memoria de trabajo para contexto adicional en los pasos posteriores.</li><li><strong>Procesamiento de contexto </strong>: los pasos lógicos y, con suerte, mayormente programáticos para integrar, normalizar o refinar el contexto adquirido de fuentes dispares, de modo que el agente pueda razonar a través de todo el contexto de manera más o menos uniforme. El trabajo subyacente consiste en hacer que el contexto de todas las fuentes (prompts, RAG, memoria, etc.), todo sea consumible por el agente de la forma más eficiente posible. </li><li><strong>Generación de contexto </strong>- Si el procesamiento de contexto consiste en hacer que el contexto recuperado sea utilizable para el agente, entonces la generación de contexto le da al agente el alcance para aplicar y recibir esa información contextual adicional a voluntad, pero también con restricciones.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4a8240e1eb078b2294b8d981b9caa8593589cac4-1600x900.png" style="width: 100%;" /><p>Los distintos efímeros de las aplicaciones de chat LLM se corresponden directamente (y a veces de formas superpuestas) a esas funciones de alto nivel de la ingeniería del contexto:</p><ul><li><strong>Instrucciones / prompt del sistema</strong> - Los prompts son el marco de cómo la actividad generativa (o agente) de IA dirigirá su pensamiento hacia el logro del objetivo del usuario. Los prompts son contexto en sí mismos; No son solo instrucciones tonales: también suelen incluir lógica de ejecución de tareas y reglas para cosas como &quot;pensar paso a paso&quot; o &quot;respirar hondo&quot; antes de responder para validar que la respuesta responde completamente a la petición del usuario. Pruebas recientes demostraron que los lenguajes de marcado son muy eficaces para enmarcar las diferentes partes de un prompt, pero también hay que tener cuidado de calibrar las instrucciones para que quede en un punto óptimo entre demasiado vago y demasiado específico; queremos dar suficiente instrucción para que el LLM encuentre el contexto adecuado, pero sin ser tan prescriptivo que pierda ideas inesperadas.</li><li><strong>Memoria a corto plazo</strong> (estado/historial) - La memoria a corto plazo es esencialmente la interacción de la sesión de chat entre el usuario y el LLM. Estos son útiles para refinar el contexto en sesiones en tiempo real y pueden almacenar para su recuperación y continuación futuras. </li><li><strong>Memoria a largo</strong> plazo - La memoria a largo plazo debe consistir en información útil a lo largo de varias sesiones. Y no solo se accede a bases de conocimiento específicas de dominio a través de RAG; investigaciones recientes emplean los resultados de solicitudes previas de IA agente/generativa para aprender y referenciar dentro de las interacciones agentices actuales. Algunas de las innovaciones más interesantes en el ámbito de la memoria a largo plazo están relacionadas con ajustar cómo <a href="https://steve-yegge.medium.com/introducing-beads-a-coding-agent-memory-system-637d7d92514a">se almacena y</a> enlaza el estado para que los agentes puedan retomar donde lo dejaron. </li><li><strong>Salida estructurada</strong> - La cognición requiere esfuerzo, así que probablemente no sea de extrañar que, incluso con capacidades de razonamiento, los LLMs (igual que los humanos) quieran gastar menos esfuerzo al pensar, y en ausencia de una API o protocolo definido, tener un mapa (un esquema) para leer los datos devueltos de una llamada a una herramienta es de gran ayuda. La inclusión de <a href="https://platform.openai.com/docs/guides/structured-outputs?lang=javascript">Salidas Estructuradas</a> como parte del marco agential ayuda a hacer que estas interacciones máquina a máquina sean más rápidas y fiables, con menos necesidad de análisis sintáctico impulsado por el pensamiento.</li><li><strong>Herramientas disponibles</strong> - Las herramientas pueden hacer todo tipo de cosas, desde recopilar información adicional (por ejemplo, enviar consultas RAG a repositorios de datos empresariales o a través de APIs en línea) hasta realizar acciones automatizadas en nombre del agente (como reservar una habitación de hotel según los criterios de la solicitud del agente). Las herramientas también podrían ser subagentes con sus propias cadenas de procesamiento agenticos. </li><li><strong>Generación Aumentada por Recuperación (RAG)</strong> - Me gusta mucho la descripción de RAG como &quot;integración dinámica del conocimiento&quot;. Como se describió antes, RAG es la técnica para proporcionar la información adicional a la que el LLM no tenía acceso cuando fue capacitado, o es una reiteración de las ideas que consideramos más importantes para obtener la respuesta correcta — la que es más relevante para nuestra consulta subjetiva.</li></ul><h2>¡Un poder cósmico fenomenal, un espacio vital diminuto!</h2><p>¡La IA Agente tiene tantos reinos nuevos fascinantes y emocionantes por explorar! Todavía quedan muchos de los problemas tradicionales de recuperación y procesamiento de datos por resolver, pero también nuevas clases de desafíos que solo ahora se están exponiendo a la luz en la nueva era de los LLM. Muchos de los problemas inmediatos con los que lidiamos hoy están relacionados con la ingeniería de contexto, es decir, conseguir que los LLMs reciban la información contextual adicional que necesitan sin saturar su limitado espacio de memoria de trabajo.</p><p>La flexibilidad de los agentes semiautónomos que tienen acceso a una variedad de herramientas (y otros agentes) da lugar a tantas ideas nuevas para implementar IA que es difícil imaginar las diferentes formas en que podríamos unir las piezas. La mayor parte de la investigación actual se centra en el campo de la ingeniería del contexto y se centra en construir estructuras de gestión de memoria capaces de manejar y rastrear mayores cantidades de contexto — esto se debe a que los problemas de pensamiento profundo que realmente queremos que resuelvan los LLMs presentan una mayor complejidad y pasos de pensamiento multifásicos y de larga duración, donde la memoria es extremadamente importante.</p><p>Gran parte de la experimentación continua en el campo consiste en intentar encontrar la gestión óptima de tareas y configuraciones de herramientas para alimentar la boca agente. Cada llamada a una herramienta en la cadena de razonamiento de un agente genera un costo acumulado, tanto en términos de cálculo para realizar la función de esa herramienta como del impacto en la ventana de contexto limitada. Algunas de las técnicas más recientes para gestionar el contexto de agentes LLM provocaron efectos de cadena no intencionados como el &quot;<a href="https://venturebeat.com/ai/ace-prevents-context-collapse-with-evolving-playbooks-for-self-improving-ai">colapso del contexto</a>&quot;, donde comprimir/resumir el contexto acumulado para tareas de larga duración se vuelve <em>demasiado</em> perdiente. El resultado deseado son herramientas que devuelvan un contexto conciso y preciso, sin que información extraña se filtre en el valioso espacio de memoria de la ventana de contexto.</p><h3>Demasiadas posibilidades</h3><p>Queremos separación de tareas con flexibilidad para reutilizar herramientas/componentes, así que tiene todo el sentido crear herramientas agentes dedicadas para conectar a fuentes de datos específicas: cada herramienta puede especializar en consultar un tipo de repositorio, un tipo de flujo de datos o incluso un caso de uso. Pero cuidado: en la lucha por ahorrar tiempo/dinero/demostrar que algo es posible, va a haber una fuerte tentación de usar los LLMs como herramienta de federación... Intenta no hacerlo, ¡ya pasamos <a href="https://www.elastic.co/pdf/elastic-distributed-not-federated-search.pdf">por eso</a> antes! La consulta federada actúa como un &quot;traductor universal&quot; que convierte una consulta entrante en la sintaxis que el repositorio remoto entiende, y luego tiene que racionalizar de alguna manera los resultados de múltiples fuentes para obtener una respuesta coherente. La federación como técnica <em>funciona</em> <em>bien</em> a pequeña escala, pero a gran escala y especialmente cuando los datos son multimodales, la federación intenta salvar brechas que son demasiado amplias.</p><p>En el mundo agente, el agente sería el federador y las herramientas (a través de MCP) serían las conexiones definidas manualmente con recursos dispares. Emplear herramientas dedicadas para llegar a fuentes de datos no conectadas puede parecer una forma poderosa de unir dinámicamente diferentes flujos de datos por consulta, pero usar herramientas para hacer la misma pregunta a múltiples fuentes probablemente acabará causando más problemas de los que resuelve. Cada una de esas fuentes de datos probablemente sean diferentes tipos de repositorios debajo, cada uno con sus propias capacidades para recuperar, clasificar y cerciorar los datos que contienen. Esas variaciones o &quot;desajustes de impedancia&quot; entre repositorios agregan carga de procesamiento, por supuesto. También pueden introducir información o señales contradictorias, donde algo aparentemente inocuo como un desalineamiento de puntaje podría desajustar radicalmente la importancia dada a un poco de contexto devuelto y afectar la relevancia de la respuesta generada al final.</p><h3>El cambio de contexto también es difícil para las computadoras</h3><p>Cuando envías a un agente en una misión, a menudo su primera tarea es encontrar todos los datos relevantes a los que tiene acceso. Al igual que ocurre con los humanos, si cada fuente de datos que el agente conecta a respuestas con respuestas disímiles y desagregadas, habrá carga cognitiva (aunque no exactamente del mismo tipo) asociada a extraer los fragmentos contextuales salientes del contenido recuperado. Eso lleva tiempo/cálculo, y cada pequeño detalle se acumula en la cadena lógica agentica. Esto lleva a la conclusión de que, al igual que se discute sobre <a href="https://blog.cloudflare.com/code-mode/">MCP</a>, la mayoría de las herramientas agenticas deberían comportar más como APIs — funciones aisladas con entradas y salidas conocidas, ajustadas para soportar las necesidades de diferentes tipos de agentes. Incluso nos estamos dando cuenta de que <a href="https://arxiv.org/html/2501.12372v5">los LLM necesitan contexto para contexto</a> — son mucho mejores conectando los puntos semánticos, especialmente cuando es una tarea como traducir lenguaje natural a sintaxis estructurada, cuando tienen un esquema al que referir (¡RTFM, sin duda!).</p><h2>¡Séptima entrada!</h2><p>Ahora cubrimos el <a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">impacto que los LLMs tuvieron en la recuperación y consulta de datos</a>, así como cómo la ventana de chat está madurando hacia la experiencia de IA agente. Pongamos los dos temas juntos y veamos cómo podemos emplear nuestras nuevas capacidades de búsqueda y recuperación para mejorar nuestros resultados en ingeniería de contexto. ¡Pasando a <a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-agentic-ai-accuracy">la Parte III: ¡El poder de la búsqueda híbrida en la ingeniería de contexto</a>!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cómo desplegar Elasticsearch en Azure AKS Automatic]]></title>
            <description><![CDATA[Aprende cómo desplegar Elasticsearch con Kibana en Azure usando AKS Automatic y ECK para una configuración parcialmente gestionada de Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elasticsearch-azure-aks-automatic-deployment</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elasticsearch-azure-aks-automatic-deployment</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Eduard Martin]]></dc:creator>
            <pubDate>Fri, 14 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4d821659a606e04390b09215e9a0d32eb01f0d1b-854x489.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Este artículo forma parte de un serial en la que aprenderemos a instalar Elasticsearch usando diferentes infraestructuras.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0a65ca8b62fd8a42d7751b8f4bf057e33d877304-940x458.png" style="width: 100%;" /><p>ECK requiere mucho más esfuerzo que las soluciones Elastic Cloud basadas en el Marketplace, pero es más automatizado que desplegar VMs por tu cuenta, porque el operador Kubernetes se encargará de la orquestación del sistema y el escalado de nodos.</p><p>Esta vez, vamos a trabajar con el Azure Kubernetes Service (AKS), usando Automático. En los otros artículos aprenderás a usar <a href="https://www.elastic.co/search-labs/blog/azure-elasticsearch-vm-deployment">Azure VM</a> y <a href="https://www.elastic.co/search-labs/blog/deploy-elasticsearch-azure-marketplace">Azure Marketplace</a>.</p><h2>¿Qué es AKS Automático?</h2><p><a href="https://learn.microsoft.com/en-us/azure/aks/intro-aks-automatic">Azure Kubernetes Service (AKS) </a>gestiona automáticamente la configuración del clúster, asigna dinámicamente los recursos e integra las mejores prácticas de seguridad mientras preserva la flexibilidad de Kubernetes, permitiendo a los desarrolladores pasar de la imagen del contenedor a la aplicación desplegada en cuestión de minutos.</p><p>AKS Automatic elimina la mayor parte de la gestión del cluster y logra un buen equilibrio entre simplicidad y flexibilidad. La elección correcta depende de tu caso de uso, pero la decisión es más fácil si planeas:</p><ul><li><strong>Despliega un entorno de pruebas: </strong>El despliegue es rápido y sencillo, lo que lo hace ideal para experimentos rápidos o clústeres de corta duración.</li><li><strong>Trabajar sin requisitos estrictos de máquinas virtuales, almacenamiento o redes: </strong>AKS Automatic ofrece valores predeterminados, así que si esos se ajustan a tus necesidades, te ahorra más configuraciones.</li><li><strong>Empieza por Kubernetes por primera vez: </strong>Al gestionar gran parte de la configuración del clúster, AKS Automatic reduce la curva de aprendizaje y permite a los equipos centrar en sus aplicaciones.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2e4c09b8c5e0ce5e8ea9c369626a373b7030a5ba-854x489.png" style="width: 100%;" /><p>Para Elasticsearch, vamos a usar <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s">Elastic Cloud on Kubernetes </a>(ECK), que es el operador oficial de Elastic Kubernetes que simplifica la orquestación de despliegues de Kubernetes en Elastic Stack.</p><h2>Cómo configurar AKS Automatic</h2><p>1. Iniciar sesión en el <a href="https://azure.microsoft.com/">Portal Microsoft Azure</a>.</p><p>2. En <strong>la parte superior derecha, </strong>haz clic<strong> </strong>en el botón<strong> Cloud Shell</strong> para acceder a la consola y desplegar el clúster AKS desde allí. Alternativamente, puedes usar <a href="https://learn.microsoft.com/en-us/azure/cloud-shell/overview">Azure Cloud Shell</a>.</p><p><strong><em>Recuerda actualizar el ID del proyecto con el tuyo durante el tutorial.</em></strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0aa60605777c0a6e3aef8faa4e54388c2cb582c8-624x495.png" style="width: 100%;" /><p><em>Abrir el AKS debería ver como en la captura de pantalla de arriba.</em></p><p>3. Instalar la extensión aks-preview de Azure CLI. Esta versión previa nos permitirá seleccionar <code>--sku automatic</code> en la creación del clúster, lo que habilita la función AKS Automática.</p><pre><code>az extension add --name aks-preview</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2e05ab67fc554c5fb5208683c179fdeaeadd95db-624x56.png" style="width: 100%;" /><p><em>Si ves este mensaje, significa que la extensión AKS se instaló correctamente.</em></p><p>4. Registrar<a href="https://learn.microsoft.com/en-us/azure/azure-app-configuration/concept-feature-management"> banderas de características</a> usando el comando <code>az feature register</code></p><pre><code>az feature register --namespace Microsoft.ContainerService --name AutomaticSKUPreview</code></pre><p><em>Veréis los detalles sobre la subscripción a la función que acabamos de crear:</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3aca710c1f312ba91de461638e518386919ec722-801x138.png" style="width: 100%;" /><p>Verifica el estado del registro hasta que cambie de &quot;<strong><em>Registrado</em></strong>&quot; a &quot;<strong><em>Registrado</em></strong>&quot;. Puede tardar unos minutos en terminar el registro.</p><pre><code>az feature show --namespace Microsoft.ContainerService --name AutomaticSKUPreview</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/186486b08ab8e1c372efaff50f10cbddeaf4e0cd-844x177.png" style="width: 100%;" /><p>Ejecuta <code>az provider register</code> para propagar los cambios.</p><pre><code>az provider register --namespace Microsoft.ContainerService</code></pre><p>5. Crear un grupo de recursos</p><p>Un grupo de recursos es un grupo lógico de recursos Azure que deben gestionar y desplegar.</p><pre><code>az group create --name elastic-resource --location eastus</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d1952269e97d94f914020754bd02702f9eafd037-770x212.png" style="width: 100%;" /><p>6. Crear un clúster de Autopilot. Lo llamaremos <strong><em>myAKSAutomaticCluster </em></strong>y usaremos el grupo de recursos que acabamos de crear. Cerciórate de tener <strong><em>disponibles 16 vCPUs</em></strong> en cualquiera de los siguientes tamaños de VM: <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dpsv5-series">Standard_D4pds_v5</a>, <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dldsv5-series">Standard_D4lds_v5</a>, <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dadsv5-series">Standard_D4ads_v5</a>, <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/ddsv5-series">Standard_D4ds_v5</a>, <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/ddv5-series">Standard_D4d_v5</a>, <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/ddv4-series">Standard_D4d_v4</a>, <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dsv3-series">Standard_DS3_v2</a> <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/memory-optimized/dv2-dsv2-series-memory">Standard_DS12_v2</a> para que AKS asigne recursos.</p><pre><code>az aks create \
    --resource-group elastic-resource \
    --name myAKSAutomaticCluster \
    --sku automatic \
    --generate-ssh-keys</code></pre><p><em>* Si aparecen <code>MissingSubscriptionRegistration</code> errores, vuelve al paso 4 con las subscripciones que faltan. Por ejemplo, <code>The subscription is not registered to use namespace &#x27;<strong>microsoft.insights</strong>&#x27;</code> requiere correr <code>az provider register --namespace Microsoft.Insights.</code></em></p><p>Sigue el inicio de sesión interactivo:</p><p><em>Aparecerá un mensaje pidiendo ejecutar &quot;az login&quot;. Tienes que ejecutar ese comando y luego esperar.</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/14c0238f755fe6347519e69d3cb28c0fa52ec044-775x203.png" style="width: 100%;" /><p>7. Esperar a que esté listo. Tarda unos 10 minutos en crear.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/eb00c3ad18f884f47db6645b196808ebec07c1fc-797x177.png" style="width: 100%;" /><p>8. Configurar el acceso en línea de comandos de kubectl.</p><pre><code>az aks get-credentials --resource-group elastic-resource --name myAKSAutomaticCluster</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5136d72a5d455345b0b6205bb232c4bdf7762998-793x52.png" style="width: 100%;" /><p><em>Ten en cuenta que la extensión que instalamos está habilitando AKS Automatic.</em></p><p>9. Confirmar que los nodos se desplegaron.</p><pre><code>kubectl get nodes</code></pre><p>Verás un mensaje de error prohibido; copia el ID de usuario del error.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d6c021fa54f4db00d2d795f5ba9b5a93376d03cd-818x47.png" style="width: 100%;" /><p>10. Agregar a tu usuario al control de acceso de AKS.</p><p>Consigue el ID AKS. Copia la salida del comando.</p><pre><code>az aks show --resource-group elastic-resource  --name myAKSAutomaticCluster --query id --output tsv</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/22a1cdc538bd379812a752c6a368a0651000abb8-810x36.png" style="width: 100%;" /><p>Crea una asignación de roles usando el ID AKS y el ID principal de tu usuario.</p><pre><code>az role assignment create --role "Azure Kubernetes Service RBAC Cluster Admin" --assignee <PRINCIPAL_ID> --scope <AKS_ID></code></pre><p>11. Intenta confirmar que los nodos se desplegaron de nuevo.</p><pre><code>kubectl get nodes</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c9b3a5c1cc540ef732c3e7f60b0a973bdbd0b6fd-617x99.png" style="width: 100%;" /><p>12. Instalar la Nube Elástica en el operador Kubernetes (ECK).</p><pre><code># Install ECK Custom Resource Definitions
kubectl create -f https://download.elastic.co/downloads/eck/2.16.1/crds.yaml

# Install the ECK operator
kubectl apply -f https://download.elastic.co/downloads/eck/2.16.1/operator.yaml</code></pre><p>13. Vamos a crear una instancia de Elasticsearch de un solo nodo con los valores predeterminados.</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 9.0.0
  nodeSets:
  - name: default
    count: 1
    config:
      node.store.allow_mmap: false
EOF</code></pre><p>Desactivamos <code>nmap</code> porque la máquina AKS predeterminada tiene un valor de <code>vm.max_map_count</code> demasiado bajo. No se recomienda desactivarlo para la producción, pero sí aumentar el valor de <code>vm.max_map_count</code>. Puedes leer más sobre cómo hacerlo <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/virtual-memory">aquí</a>.</p><p>14. Desplieguemos también un clúster Kibana de un solo nodo. Para Kibana, agregaremos un balanceador de carga, que nos dará una IP externa que podremos usar para llegar a Kibana desde nuestro dispositivo.</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: quickstart
spec:
  version: 9.0.0
  http:
    service:
      spec:
        type: LoadBalancer
  count: 1
  elasticsearchRef:
    name: quickstart
EOF</code></pre><p>Por defecto, AKS Automatic configurará el balanceador de carga como público; Puedes cambiar el comportamiento configurando la anotación de metadatos:</p><p><code>service.beta.kubernetes.io/azure-load-balancer-internal: &quot;true&quot;</code></p><p>15. Comprueba que tus cápsulas estén funcionando.</p><pre><code>kubectl get pods</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/213a897176c0af6cea19c7c777cfaf8734e3ee6e-616x84.png" style="width: 100%;" /><p>16. También puedes ejecutar <code>kubectl get elasticsearch</code> y <code>kubectl get kibana</code> para estadísticas más específicas como la versión de Elasticsearch, nodos y salud.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c84988e725ef892eddd8fb7e5a03d58c35a8f9d6-470x62.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/452dd03d314cd00c8a3c19e19862b968592a0435-415x62.png" style="width: 100%;" /><p>17. Accede a tus servicios.</p><pre><code>kubectl get svc</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b2d3e8f368be22b89aa2ed4d4d514f97dd6cbabd-624x115.png" style="width: 100%;" /><p>Esto te mostrará la <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/accessing-services">URL externa</a> de Kibana bajo EXTERNAL-IP. Puede que el balanceador de carga tarde unos minutos en provisionarse. <strong><em>Copia el valor de EXTERNAL-IP.</em></strong></p><p>18. Consigue la contraseña de Elasticsearch para el usuario &#x27;elastic&#x27;:</p><pre><code>kubectl get secret quickstart-es-elastic-user -o=jsonpath='{.data.elastic}' | base64 --decode</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bd74537f8c35c4e027c518913fdb0a0524621d56-624x31.png" style="width: 100%;" /><p>19. <strong>Accede a Kibana</strong> desde tu navegador:</p><p>a. URL: https://&lt;EXTERNAL_IP&gt;:5601</p><p>b. Nombre de usuario:elastic</p><p>c. Contraseña:c44A295CaEt44D6xIzN6Zs5m (del paso anterior)</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a601112527d80721b292328ed8da58386d2837eb-463x503.png" style="width: 100%;" /><p>20. Al acceder a Elastic Cloud desde tu navegador, verás la pantalla de bienvenida.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f91e71fa961d215a8d886601d1a9fc5c452ce329-1999x1256.png" style="width: 100%;" /><p>Si quieres cambiar las especificaciones del clúster de Elasticsearch, como cambiar o redimensionar nodos, puedes volver a aplicar el manifiesto YML con la nueva configuración:</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 9.0.0
  nodeSets:
    - name: default
      count: 2
      config:
        node.store.allow_mmap: false
      podTemplate:
        spec:
          containers:
            - name: elasticsearch
              resources:
                requests:
                  memory: 1.5Gi
                  cpu: 2
                limits:
                  memory: 1.5Gi
                  cpu: 2
EOF</code></pre><p>En este ejemplo, vamos a agregar un nodo más y modificar la RAM y la CPU. Como puedes ver, ahora <code>kubectl get elasticsearch</code> muestra 2 nodos:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4b139a476b50933d45d99e09479112817964f76a-624x60.png" style="width: 100%;" /><p>Lo mismo ocurre con Kibana:</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: quickstart
spec:
  version: 9.0.0
  http:
    service:
      spec:
        type: LoadBalancer
  count: 1
  elasticsearchRef:
    name: quickstart
  podTemplate:
    spec:
      containers:
        - name: kibana
          env:
            - name: NODE_OPTIONS
              value: "--max-old-space-size=1024"
          resources:
            requests:
              memory: 0.5Gi
              cpu: 0.5
            limits:
              memory: 1Gi
              cpu: 1
EOF</code></pre><p>Podemos ajustar la CPU/RAM del contenedor y también el <a href="https://nodejs.org/">uso de Node.js </a>memoria (<a href="https://nodejs.org/api/cli.html#--max-old-space-sizesize-in-mib">máximo-tamaño de espacio antiguo</a>)</p><p>Ten en cuenta que <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/volume-claim-templates">las reclamaciones de volumen existentes no pueden reducir</a>. Tras aplicar la actualización, el operador realizará los cambios con un tiempo de interrupción mínimo.</p><p>Recuerda eliminar el clúster cuando termines de hacer pruebas para evitar costos innecesarios.</p><pre><code>az aks delete --name myAKSAutomaticCluster --resource-group elastic-resource</code></pre><h2>Conclusión</h2><p>Emplear Azure AKS Automatic con ECK proporciona una solución equilibrada para desplegar Elasticsearch y Kibana: reduce la complejidad operativa, garantiza escalado y actualizaciones automatizadas, y aprovecha la flexibilidad de Kubernetes. Este enfoque es ideal para equipos que buscan un proceso de despliegue fiable, repetible y manteniendo sin gestionar manualmente cada detalle de la infraestructura, lo que lo convierte en una opción práctica tanto para entornos de pruebas como de producción.</p><h2>Pasos siguientes</h2><p>Si quieres leer más sobre Kubernetes, puedes consultar la documentación oficial aquí:</p><ul><li><a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s">Nube elástica en Kubernetes | Documentos elásticos</a></li><li><a href="https://learn.microsoft.com/en-us/azure/aks/intro-aks-automatic">Introduction to Azure Kubernetes Service (AKS) Automatic (preview)</a></li><li><a href="https://azure.github.io/AKS/2024/05/22/aks-automatic">AKS Automatic - Blog de ingeniería de AKS</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Creando una sala de redacción de un agente LLM con protocolo A2A y MCP en Elasticsearch: Parte I]]></title>
            <description><![CDATA[Explora los conceptos del protocolo A2A y MCP en un ejemplo práctico de redacción donde agentes especializados en LLM colaboran para investigar, escribir, editar y publicar artículos de noticias.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/a2a-protocol-mcp-llm-agent-newsroom-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/a2a-protocol-mcp-llm-agent-newsroom-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Justin Castilla]]></dc:creator>
            <pubDate>Thu, 13 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9f938d8e2f0fdf7509edf028816c48bdbc8b3fc7-1600x900.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>Introducción</h2><p>Los sistemas actuales respaldados por LLM están evolucionando rápidamente más allá de aplicaciones de modelo único hacia redes complejas donde agentes especializados trabajan juntos para realizar tareas que la informática moderna nunca antes consideró posibles. A medida que estos sistemas se vuelven más complejos, la infraestructura que permite la comunicación de agentes y el acceso a herramientas se convierte en el principal foco de desarrollo. Surgieron dos enfoques complementarios para abordar estas necesidades: los protocolos <strong>Agent2Agent (A2A)</strong> para la coordinación multiagente y el <strong>Protocolo de Contexto del Modelo (MCP)</strong> para el acceso estandarizado a herramientas y recursos.</p><p>Entender cuándo usar cada uno en armonía con y sin el otro puede afectar significativamente la escalabilidad, mantenibilidad y efectividad de tus aplicaciones. Este artículo explora los conceptos e implementaciones de <strong>A2A</strong> dentro del ejemplo práctico de una redacción digital, donde agentes especializados en LLM colaboran para investigar, escribir, editar y publicar artículos de noticias.</p><p>Aquí <a href="https://github.com/justincastilla/elastic-newsroom/tree/main">se puede encontrar</a> un repositorio adjunto, y examinaremos ejemplos concretos de A2A en acción cerca del final del artículo en la Sección 5.</p><h3>Prerrequisitos</h3><p>El <a href="https://github.com/justincastilla/elastic-newsroom/tree/main">repositorio</a> consiste en implementaciones basadas en Python de los agentes A2A. Flask proporciona un servidor API, así como un servicio de mensajería personalizado en Python llamado Event Hub, que enruta mensajes para registro y actualizaciones de la interfaz. Por último, se dispone de una interfaz React para el uso independiente de las funciones de la redacción. Todo está contenido dentro de una imagen Docker para facilitar su implementación. Si quieres ejecutar los servicios directamente con tu máquina, querrás cerciorarte de tener estas tecnologías instaladas:</p><p>Lenguajes y tiempos de ejecución</p><ul><li>Python 13.12 - Lenguaje central de backend</li><li>Node.js 18+ - Interfaz de React opcional</li></ul><p>Frameworks y SDKS básicos:</p><ul><li>A2A SDK 0.3.8 - Coordinación y comunicación de agentes</li><li>SDK Anthropic - Integración de Claude para generación de IA</li><li>Uvicorn - servidor ASGI para ejecutar agentes</li><li>FastMCP 2.12.5+ - Implementación del servidor MCP</li><li>React 18.2 - Marco de interfaz de usuario frontend</li></ul><p>Datos y búsqueda</p><ul><li>Elasticsearch 9.1.1+ - Indexación y búsqueda de artículos</li></ul><p>Despliegue en Docker (opcional, pero recomendado)</p><ul><li>Docker 28.5.1+</li></ul><h2>Sección 1: ¿Qué es Agent2Agent (A2A)?</h2><h3>Definición y conceptos fundamentales</h3><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><p><strong>Especificaciones oficiales</strong>: <a href="https://a2a-protocol.org/latest/specification/">https://a2a-protocol.org/latest/specification/</a></p><h3>Orígenes y evolución</h3><p>El concepto de comunicación Agent2Agent, o sistemas multiagente, tiene raíces en sistemas distribuidos, microservicios e investigación multiagente que se remontan <a href="https://en.wikipedia.org/wiki/Multi-agent_system">a décadas</a> atrás. Los primeros trabajos en inteligencia artificial distribuida sentaron las bases para agentes capaces de negociar, coordinar y colaborar. Estos primeros sistemas estaban dedicados a <a href="https://www.jasss.org/5/1/7.html">simulaciones sociales</a> a gran escala, <a href="https://arxiv.org/html/2410.09403v1">investigación académica</a> y <a href="https://www.researchgate.net/publication/334765661_Generation_Expansion_Planning_Considering_Investment_Dynamic_of_Market_Participants_Using_Multi-agent_System">gestión de redes eléctricas</a>.</p><p>Con la llegada de la disponibilidad de LLMs y la reducción del costo operativo, los sistemas multiagente estuvieron disponibles para los mercados &quot;prosumers&quot;, con el respaldo de Google y la comunidad de investigación en IA en general. Ahora conocido como sistemas Agent2Agent, la incorporación del protocolo A2A evolucionó hasta convertir en un estándar moderno diseñado específicamente para la era de múltiples grandes modelos de lenguaje coordinando esfuerzos y tareas.</p><p>El protocolo A2A garantiza una comunicación y coordinación fluidas entre agentes aplicando estándares y principios consistentes a los puntos de interacción donde los LLMs se conectan y comunican. Esta estandarización permite que agentes de diferentes desarrolladores —empleando distintos modelos subyacentes— trabajen juntos de forma eficaz.</p><p>Los protocolos de comunicación no son nuevos y tienen raíces ampliamente arraigadas en casi todas las transacciones digitales realizadas en Internet. Si <a href="https://www.elastic.co/search-labs">escribiste https://www.elastic.co/search-labs</a> en un navegador para acceder a este artículo, las probabilidades son altas de que se ejecutaron protocolos TCP/IP, transporte HTTP y consulta DNS, cerciorando una experiencia de navegación coherente para nosotros.</p><h3>Características clave</h3><p>Los sistemas A2A se basan en varios principios fundamentales para garantizar una comunicación fluida. Construir sobre estos principios garantiza que diferentes agentes, basados en distintos LLMs, frameworks y lenguajes de programación, interactúen de forma fluida.</p><p>Estos son los cuatro principios principales:</p><ul><li><strong>Paso de mensajes</strong>: Los agentes se comunican mediante mensajes estructurados con propiedades y formatos bien definidos</li><li><strong>Coordinación</strong>: Los agentes orquestan flujos de trabajo complejos delegando tareas entre sí y gestionando dependencias sin bloquear a otros agentes</li><li><strong>Especialización</strong>: Cada agente se centra en un dominio o capacidad específica, convertir en un experto en su área y ofreciendo la realización de tareas basar en ese conjunto de habilidades</li><li><strong>Estado distribuido</strong>: El estado y el conocimiento se distribuyen entre agentes en lugar de centralizar, con los agentes con la capacidad de actualizar mutuamente sobre el progreso con el estado de la tarea y retornos parciales (artefactos)</li></ul><h3>La redacción: Un ejemplo recurrente</h3><p>Imagina una redacción digital impulsada por agentes de IA, cada uno especializado en un aspecto diferente del periodismo:</p><ul><li><strong>Jefe de Noticias</strong> (coordinador/cliente): Asigna historias y monitorear el flujo de trabajo</li><li><strong>Agente reportero</strong>: Escribe artículos basados en investigaciones y entrevistas</li><li><strong>Agente Investigador</strong>: recopila datos, estadísticas e información de fondo</li><li><strong>Archive Agent</strong>: Busca en artículos históricos e identifica tendencias usando Elasticsearch</li><li><strong>Editor Agent</strong>: Reseña artículos para calidad, estilo y optimización SEO</li><li><strong>Agente editorial</strong>: Publica artículos aprobados en la plataforma de blogs vía CI/CD</li></ul><p>Estos agentes no actúan de forma aislada; cuando el Jefe de Noticias asigna una noticia sobre <em>la adopción de energías renovables</em>, el Reportero necesita que el Investigador recopile estadísticas, que el Editor revise el borrador y que el Editor publique el texto final. Esta coordinación se realiza a través de protocolos A2A.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cc0760dbd74c49b92fa00dafbb8c2e8740eb70b6-963x693.png" style="width: 100%;" /><h2>Sección 2: comprensión de la arquitectura A2A</h2><h3>Roles de Agente de Cliente y Agente Remoto</h3><p>En la arquitectura A2A, los agentes asumen dos roles principales. El <strong>Agente Cliente</strong> es responsable de formular y comunicar tareas a otros agentes del sistema. Identifica a los agentes remotos y sus capacidades, empleando esta información para tomar decisiones informadas sobre la delegación de tareas. El agente cliente coordina el flujo de trabajo general, cerciorando que las tareas se distribuyan correctamente y que el sistema avance hacia sus objetivos.</p><p>El <strong>Agente Remoto</strong>, en cambio, actúa en tareas delegadas por los clientes. Proporciona información o toma acciones específicas en respuesta a solicitudes, pero no inicia acciones de forma independiente. Los agentes remotos también pueden comunicar con otros agentes remotos según sea necesario para cumplir con sus responsabilidades asignadas, creando una red colaborativa de capacidades especializadas.</p><p>En nuestra redacción, el Jefe de Noticias actúa como agente cliente, mientras que el Reportero, el Investigador, el Editor y el Editor son agentes remotos que responden a las solicitudes y coordinan entre sí.</p><h3>Capacidades básicas de A2A</h3><p>Los protocolos A2A definen varias capacidades que permiten la colaboración multiagente:</p><h4>1. Descubrimiento</h4><p>Los servidores A2A deben anunciar sus capacidades para que los clientes sepan cuándo y cómo emplearlas para tareas específicas. Esto se logra mediante Tarjetas de Agente—documentos JSON que describen las habilidades, entradas y salidas de un agente. Las Tarjetas de Agente están disponibles en puntos finales consistentes y conocidos (como el recomendado <code>/.well-known/agent-card.json</code> endpoint), permitiendo a los clientes descubrir y consultar las capacidades de un agente antes de iniciar la colaboración.</p><p>A continuación hay un ejemplo de Tarjeta de Agente para el Agente de Archivo personalizado de Elastic, &quot;Archie Archivist&quot;. Ten en cuenta que proveedores de software como Elastic alojan sus agentes A2A y proporcionan una URL para el acceso:</p><pre><code>{
  "name": "Archie Archivist",
  "description": "Helps find historical news documents in the Elasticsearch Index of archived news articles and content.",
  "url": "https://xxxxxxxxxxxxx-abc123.kb.us-central1.gcp.elastic.cloud/api/agent_builder/a2a/archive-agent",
  "provider": {
    "organization": "Elastic",
    "url": "https://elastic.co"
  },
  "version": "0.1.0",
  "protocolVersion": "0.3.0",
  "preferred_transport": "JSONRPC",
  "documentationURL": "https://www.elastic.co/docs/solutions/search/agent-builder/a2a-server"
  "capabilities": {
    "streaming": false,
    "pushNotifications": false,
    "stateTransitionHistory": false
  },
  "skills": [
    {
      "id": "platform.core.search",
      "name": "platform.core.search",
      "description": "A powerful tool for searching and analyzing data within your Elasticsearch cluster.",
      "inputModes": ["text/plain", "application/json"],
      "outputModes": ["text/plain", "application/json"]
    },
    {
      "id": "platform.core.index_explorer",
      "name": "platform.core.index_explorer",
      "description": "List relevant indices, aliases and datastreams based on a natural language query.",
      "inputModes": ["text/plain", "application/json"],
      "outputModes": ["text/plain", "application/json"]
    }
  ],
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"]
}</code></pre><p>Esta Tarjeta de Agente revela varios aspectos importantes del Agente de Archivo de Elastic. El agente se identifica como &quot;Archie Archivist&quot; y deja claro su propósito: ayudar a encontrar documentos históricos de noticias en un índice de Elasticsearch. La tarjeta especifica el proveedor (Elastic) y la versión del protocolo (0.3.0), cerciorando la compatibilidad con otros agentes compatibles con A2A. Lo más importante es que el <code>skills</code> array enumera las capacidades específicas que ofrece este agente, incluyendo una poderosa funcionalidad de búsqueda y una exploración inteligente de índices. Cada habilidad define qué modos de entrada y salida soporta, permitiendo a los clientes entender exactamente cómo comunicar con este agente. Este agente deriva del servicio Agent Builder de Elastic, que ofrece un conjunto de herramientas nativas respaldadas por LLM y endpoints API para mantener una conversación con tu almacén de datos, no solo para recuperarlo de él. El acceso a los agentes A2A en Elasticsearch puede <a href="https://www.elastic.co/docs/solutions/search/agent-builder/a2a-server">encontrar aquí</a>.</p><h4>2. Negociación</h4><p>Clientes y agentes deben acordar los métodos de comunicación —ya sea que las interacciones se realicen mediante texto, formularios, iframes o incluso audio/video— para garantizar una interacción adecuada del usuario y el intercambio de datos. Esta negociación ocurre al inicio de la colaboración entre agentes y establece los protocolos que gobernarán su interacción a lo largo del flujo de trabajo. Por ejemplo, un agente de atención al cliente basado en voz podría negociar para comunicar mediante flujos de audio, mientras que un agente de análisis de datos podría preferir JSON estructurado. El proceso de negociación garantiza que ambas partes puedan intercambiar información de forma eficaz en un formato que se adapte a sus capacidades y a los requisitos de la tarea en cuestión.</p><p>Las capacidades listadas en el fragmento JSON anterior tienen todas esquemas de entrada y salida; Estos establecen una expectativa de cómo interactuar con este agente por parte de otros agentes.</p><h4>3. Gestión de tareas y estado</h4><p>Los clientes y agentes necesitan mecanismos para comunicar el estado de la tarea, cambios y dependencias a lo largo de la ejecución. Esto incluye gestionar todo el ciclo de vida de una tarea, desde su creación y asignación hasta las actualizaciones de progreso y los cambios de estado. Los estados típicos incluyen estados pendientes, en curso, completados o fallidos. El sistema también debe hacer un seguimiento de las dependencias entre tareas para cerciorar que el trabajo previo se complete antes de que comiencen las tareas dependientes. El manejo de errores y la lógica de reintentos también son componentes esenciales, que permiten al sistema recuperar de forma elegante tras fallos y seguir avanzando hacia el objetivo principal.</p><p>Ejemplo de mensaje de tarea:</p><pre><code>{
  "message_id": "msg_789xyz",
  "message_type": "task_request",
  "sender": "news_chief",
  "receiver": "researcher_agent",
  "timestamp": "2025-09-30T10:15:00Z",
  "payload": {
    "task_id": "task_456abc",
    "capability": "fact_gathering",
    "parameters": {
      "query": "renewable energy adoption rates in Europe 2024",
      "sources": ["eurostat", "iea", "ember"],
      "depth": "comprehensive"
    },
    "context": {
      "story_id": "story_123",
      "deadline": "2025-09-30T18:00:00Z",
      "priority": "high"
    }
  }
}</code></pre><p>Este mensaje de tarea de ejemplo demuestra varios aspectos clave de la comunicación A2A.</p><ul><li>La estructura <strong>del mensaje</strong> incluye metadatos como un identificador único del mensaje, el tipo de mensaje enviado, la identificación del remitente y receptor, y una marca de tiempo para el seguimiento y la depuración.</li><li>La <strong>carga útil</strong> contiene la información real de la tarea, especificando qué capacidad se está invocando en el agente remoto y proporcionando los parámetros necesarios para ejecutar dicha capacidad.</li><li>La sección <strong>de contexto</strong> proporciona información adicional que ayuda al agente receptor a comprender el flujo de trabajo más amplio, incluyendo plazos y niveles de prioridad que informan sobre cómo debe asignar sus recursos y programar su trabajo.</li></ul><h4>4. Colaboración</h4><p>Clientes y agentes <strong>deben</strong> apoyar una interacción dinámica pero estructurada, permitiendo a los agentes aplicar aclaraciones, información o subacciones al cliente, otros agentes o usuarios. Esto crea un entorno colaborativo donde los agentes pueden hacer preguntas de seguimiento cuando las instrucciones iniciales son ambiguas, aplicar contexto adicional para tomar mejores decisiones, delegar subtareas a otros agentes con experiencia más adecuada y proporcionar resultados intermedios para recibir retroalimentación antes de proceder con la tarea completa. Esta comunicación multidireccional cerciora que los agentes no actúen de forma aislada, sino que participen en un diálogo continuo que conduce a mejores resultados.</p><h3>Comunicación distribuida entre pares</h3><p>A2A permite la comunicación distribuida donde los agentes pueden ser alojados por diferentes organizaciones, con algunos agentes mantenidos internamente mientras que otros son proporcionados por servicios externos. Estos agentes pueden funcionar en diferentes infraestructuras, potencialmente abarcando múltiples proveedores de nube o centros de datos locales. Pueden usar diferentes LLMs subyacentes, con algunos agentes impulsados por modelos GPT, otros por Claude y otros más por alternativas de código abierto. Los agentes incluso podrían operar en diferentes regiones geográficas para cumplir con los requisitos de soberanía de datos o reducir la latencia. A pesar de esta diversidad, todos los agentes acuerdan un protocolo de comunicación común para el intercambio de información, garantizando la interoperabilidad independientemente de los detalles de la implementación. Esta arquitectura distribuida ofrece flexibilidad en la construcción y despliegue de los sistemas, permitiendo a las organizaciones combinar los mejores agentes e infraestructuras según sus necesidades específicas.</p><p>Esta es la arquitectura final de la aplicación de redacción:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/82e01a0d9746038eafd69d11177042b5390507ae-1600x838.png" style="width: 100%;" /><h2>Sección 3: Protocolo de Contexto de Modelo (MCP)</h2><h3>Definición y propósito</h3><p>El Protocolo de Contexto del Modelo (MCP) es un protocolo estandarizado desarrollado por Anthropic para mejorar y potenciar un LLM individual con herramientas, recursos y prompts definidos por el usuario, entre otras adiciones suplementarias a la base de código. MCP proporciona una interfaz universal entre los modelos de lenguaje y los recursos externos que necesitan para completar tareas de manera eficaz. Este <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">artículo</a> describe el estado actual de MCP con ejemplos de casos de uso, tendencias emergentes y la propia implementación de Elastic.</p><h3>Conceptos centrales de MCP</h3><p>MCP opera sobre una arquitectura cliente-servidor con tres componentes principales:</p><ul><li><strong>Clientes:</strong> aplicaciones (como Claude Desktop o aplicaciones de IA personalizadas) que se conectan a servidores MCP para acceder a sus capacidades.</li><li><strong>Servidores</strong>: aplicaciones que exponen recursos, herramientas y prompts a modelos de lenguaje. Cada servidor se especializa en proporcionar acceso a capacidades o fuentes de datos específicas.<ul><li><strong>Herramientas</strong>: funciones definidas por el usuario que los modelos pueden invocar para realizar acciones, como bases de datos de búsqueda, llamar a APIs externas o transformaciones de ejecución sobre datos</li><li><strong>Recursos:</strong> fuentes de datos de las que los modelos pueden leer, que se sirven con datos dinámicos o estáticos y se acceden mediante patrones URI (similares a rutas REST)</li><li><strong>Prompts: </strong>plantillas reutilizables de prompts con variables que guían el modelo para realizar tareas específicas.</li></ul></li></ul><h3>Patrón de petición-respuesta</h3><p>MCP sigue un patrón de interacción petición-respuesta familiar similar a las APIs REST. El cliente (LLM) aplicar un recurso o invoca una herramienta, luego el servidor MCP procesa la solicitud y devuelve el resultado, que el LLM emplea para continuar su tarea. Este modelo centralizado con servidores periféricos proporciona un patrón de integración más sencillo en comparación con la comunicación entre agentes entre iguales.</p><h3>MCP en la redacción</h3><p>En nuestro ejemplo de redacción, los agentes individuales emplean servidores MCP para acceder a las herramientas y datos que necesitan:</p><ul><li><strong>Utilizaciones de Researcher Agent</strong> :<ul><li>Servidor MCP de la API de Noticias (acceso a bases de datos de noticias)</li><li>Verificación de hechos del servidor MCP (verificar afirmaciones frente a fuentes confiables)</li><li>Servidor MCP de la base de datos académica (artículos académicos e investigaciones)</li></ul></li><li><strong>Reporter Agent</strong> emplea:<ul><li>Guía de estilo MCP Server (estándares de redacción para redacción)</li><li>Servidor MCP de plantillas (plantillas y formatos de artículos)</li><li>Servidor MCP de Biblioteca de Imágenes (fotos de archivo y gráficos)</li></ul></li><li><strong>Editor Agent</strong> emplea:<ul><li>Grammar Checker MCP Server (herramientas de calidad del lenguaje)</li><li>Servidor MCP de detección de plagio (verificación de originalidad)</li><li>Análisis SEO Servidor MCP (optimización de titulares y palabras clave)</li></ul></li><li><strong>Utilizaciones del Agente de la Editorial</strong> :<ul><li>CMS MCP Server (API del sistema de gestión de contenidos)</li><li>Servidor CI/CD MCP (Pipeline de despliegue)</li><li>Servidor MCP de Análisis (seguimiento y monitorización)</li></ul></li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b67311e3b58b27f9eb1b42a7b1dbad47ef3be4ad-808x535.png" style="width: 100%;" /><h2><br/>Sección 4: comparación de arquitectura</h2><h3>Cuándo usar A2A</h3><p>La arquitectura A2A destaca en <strong>escenarios que requieren colaboración genuina entre múltiples agentes</strong>. Los flujos de trabajo de varios pasos que requieren coordinación se benefician enormemente de A2A, especialmente cuando las tareas implican múltiples pasos secuenciales o paralelos, flujos de trabajo que requieren iteración y refinamiento, y procesos con puntos de control y necesidades de validación. En nuestro ejemplo de redacción, el flujo de trabajo de la historia requiere que el Reportero escriba, pero puede que tenga que iterar de nuevo al Investigador si la confianza en ciertos hechos es baja, luego proceder al Editor y finalmente al Editor.</p><p><strong>La especialización específica de dominio en múltiples áreas</strong> es otro caso de uso estable para A2A. Cuando se necesitan múltiples expertos en distintos campos para realizar una tarea mayor, con cada agente aportando un profundo conocimiento del dominio y capacidades de razonamiento especializadas para distintos aspectos, A2A proporciona el marco de coordinación necesario para establecer esas conexiones. La redacción lo ejemplifica perfectamente: el investigador se especializa en la recopilación de información, el reportero en la escritura y el editor en el control de calidad, cada uno con una experiencia distinta.</p><p>La necesidad de un comportamiento autónomo de agentes hace que A2A sea especialmente valioso. Los agentes que pueden<strong> tomar decisiones independientes, mostrar un comportamiento proactivo basado en condiciones cambiantes y adaptar dinámicamente a los requisitos del flujo de trabajo</strong> prosperan en una arquitectura A2A. La escalada horizontal de funciones especializadas es otro beneficio clave: en lugar de tener un único maestro de todos los oficios, múltiples agentes especializados trabajan coordinados, y múltiples instancias del mismo agente pueden manejar subtareas de forma asíncrona. Durante noticias de última hora en nuestra redacción, por ejemplo, varios agentes de Reporter pueden trabajar simultáneamente en diferentes ángulos de la misma historia.</p><p>Por último, las tareas que requieren colaboración genuina entre múltiples agentes son ideales para A2A. Esto incluye mecanismos <a href="https://arxiv.org/abs/2404.18796">de evaluación de los LLM como jurado</a> , sistemas de construcción de consenso y votación, y <strong>la resolución colaborativa de problemas donde se necesitan múltiples perspectivas</strong> para alcanzar el mejor resultado.</p><h3>Cuándo usar MCP</h3><p>El Protocolo de Contexto del Modelo es ideal para ampliar las capacidades de un solo modelo de IA. Cuando un único modelo de IA necesita acceso a múltiples herramientas y fuentes de datos, MCP ofrece la solución perfecta con razonamiento centralizado combinado con herramientas distribuidas e integración sencilla de herramientas. En nuestro ejemplo de redacción, el Agente de Investigación (un modelo) necesita acceso a múltiples fuentes de datos, incluyendo News API, servicios de verificación de hechos y bases de datos académicas, todo ello accedido a través de servidores MCP estandarizados.</p><p>La integración estandarizada de herramientas se convierte en una prioridad cuando la ampliación de la compartición y la reutilización de las integraciones de herramientas importan. MCP destaca aquí con su ecosistema de servidores MCP preconstruidos que reducen significativamente el tiempo de desarrollo para integraciones comunes. Cuando se requiere simplicidad y mantenibilidad, los patrones de solicitud-respuesta de MCP son familiares para los desarrolladores, más fáciles de entender y depurar que los sistemas distribuidos, y tienen una menor complejidad operativa.</p><p>Por último, MCP suele ser ofrecido por proveedores de software para facilitar la comunicación remota con sus sistemas. Estos servidores MCP ofrecidos por el proveedor reducen significativamente el tiempo de incorporación y desarrollo, al tiempo que ofrecen una interfaz estandarizada con sistemas propietarios, haciendo que la integración sea mucho más sencilla que el desarrollo de APIs personalizadas.</p><h3>Cuándo usar ambos (MCP de A2A ❤️)</h3><p>Muchos sistemas sofisticados se benefician de combinar A2A y MCP, como se indica en la <a href="https://a2a-protocol.org/latest/topics/a2a-and-mcp/">documentación de A2A sobre integración MCP</a>. Los sistemas que requieren tanto coordinación como estandarización son candidatos ideales para un enfoque híbrido. A2A se encarga de la coordinación de agentes y la orquestación de flujos de trabajo, mientras que MCP proporciona acceso a herramientas individuales a los agentes. En nuestro ejemplo de redacción, los agentes coordinan mediante A2A; con el flujo de trabajo pasando de reportero a investigador, luego editor y luego editor. Sin embargo, cada agente emplea servidores MCP para sus herramientas especializadas, creando una separación arquitectónica limpia.</p><p>Múltiples agentes especializados, cada uno usando MCP para acceder a herramientas, representan un patrón común donde hay una capa de coordinación de agentes gestionada por A2A y una capa de acceso a herramientas gestionada por MCP. Esta clara separación de preocupaciones facilita la comprensión y el mantenimiento de los sistemas.</p><p>Los beneficios de combinar ambos enfoques son sustanciales. Obtienes los beneficios organizacionales de los sistemas multiagente, incluyendo especialización, autonomía y procesamiento en paralelo, además de disfrutar de los beneficios de estandarización y ecosistema del MCP, como la integración de herramientas y el acceso a recursos. Hay una clara separación entre la coordinación de agentes (A2A) y el acceso a recursos (MCP), y lo importante es que A2A no es necesario solo para tareas pequeñas como el acceso a API—MCP las gestiona de forma eficiente sin la sobrecarga de la orquestación multiagente.</p><p><strong>Preguntas frecuentes: A2A vs. MCP - Casos de uso</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Característica</td><td style="border: 1px solid #ccc; padding: 4px;">Agent2Agent (A2A)</td><td style="border: 1px solid #ccc; padding: 4px;">Protocolo de Contexto de Modelo (MCP)</td><td style="border: 1px solid #ccc; padding: 4px;">Híbrido (A2A + MCP)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Objetivo principal</td><td style="border: 1px solid #ccc; padding: 4px;">Coordinación Multiagente: Permite que un equipo de agentes especializados trabaje juntos en flujos de trabajo complejos y de varios pasos.</td><td style="border: 1px solid #ccc; padding: 4px;">Mejora de un solo agente: Extiende la capacidad de un único LLM/Agente con herramientas, recursos y datos externos.</td><td style="border: 1px solid #ccc; padding: 4px;">Fuerza combinada: A2A gestiona el flujo de trabajo del equipo, mientras que MCP proporciona herramientas a cada miembro del equipo.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Ejemplo de equipo de redacción</td><td style="border: 1px solid #ccc; padding: 4px;">La cadena de flujo de trabajo: Jefe de Noticias → Reportero → Investigador → Editor → Editor. Esta es la capa de coordinación.</td><td style="border: 1px solid #ccc; padding: 4px;">Herramientas individuales del agente: El Agente Reportero accede al servidor de la guía de estilo y al servidor de plantillas (a través de MCP). Esta es la capa de acceso a herramientas.</td><td style="border: 1px solid #ccc; padding: 4px;">El sistema completo: el reportero coordina con el editor (A2A), y el reportero emplea el servidor MCP de la Biblioteca de Imágenes para encontrar un gráfico para la historia.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Cuándo usar cuál</td><td style="border: 1px solid #ccc; padding: 4px;">Cuando necesitas colaboración genuina, iteración y refinamiento, o experiencia especializada repartida entre varios agentes.</td><td style="border: 1px solid #ccc; padding: 4px;">Cuando un solo agente necesita acceso a múltiples herramientas y fuentes de datos o requiere integración estandarizada con sistemas propietarios.</td><td style="border: 1px solid #ccc; padding: 4px;">Cuando necesitas los beneficios organizacionales de los sistemas multiagente y los beneficios de estandarización y ecosistema del MCP.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Beneficio fundamental</td><td style="border: 1px solid #ccc; padding: 4px;">Autonomía y escalado: Los agentes pueden tomar decisiones independientes, y el sistema permite la escalado horizontal de funciones especializadas.</td><td style="border: 1px solid #ccc; padding: 4px;">Simplicidad y estandarización: Más fácil de depurar y mantener gracias al razonamiento centralizado, y proporciona una interfaz universal para los recursos.</td><td style="border: 1px solid #ccc; padding: 4px;">Separación clara de preocupaciones: Facilita la comprensión del sistema: A2A = trabajo en equipo, MCP = acceso a la herramienta.</td>
                    </tr>
              </tbody>
            </table>
          <img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ddf6a29b1107ac6a63e94ecef703abc561a29e1e-986x656.png" style="width: 100%;" /><h2>Conclusión</h2><p>Esta es la primera sección de dos partes que cubren la implementación de agentes basados en A2A reforzados con servidores MCP para proporcionar soporte y acceso externo a datos y herramientas. El siguiente artículo explorará el código real para demostrar que trabajan juntos para emular las actividades en una redacción online. Aunque ambos sistemas son extremadamente capaces y flexibles por sí mismos, verás cuánto se complementan cuando trabajan en conjunto.</p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/es/blog/context-engineering-hybrid-search-evolution-agentic-ai</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/context-engineering-hybrid-search-evolution-agentic-ai</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Woody Walton]]></dc:creator>
            <pubDate>Wed, 12 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7e59a0671aa9ea32d68188a693936a66ebf48625-1000x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <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 &quot;IA agente&quot; 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 &#x27;ingeniería de prompts&#x27; 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 &#x27;ingeniería de contexto&#x27; 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><pre><code>POST my-index/_search
{
  "size": 10,
  "query": {
    "semantic": {
      "query": "machine learning applications",
      "field": "semantic-content-field"
    }
  }
}</code></pre><p><em>Un ejemplo de consulta léxica <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-multi-match-query">multi-match</a> </em></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 &quot;caja negra&quot; 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><pre><code>POST my-index/_search 
{
  "size": 10, 
  "query": {
    "semantic": {
      "query": "machine learning applications",
      "field": "semantic-content-field"
    }
  }
} </code></pre><p><em>Un ejemplo <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-semantic-query">de</a> 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 &quot;vecinos más cercanos&quot; 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 &quot;séptimo símbolo&quot; — 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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1ddfaae0c1496d08d7d30419e6d2aeaeacfc0ea2-1600x544.png" style="width: 100%;" /><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><pre><code>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
  }
}</code></pre><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><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 &quot;data lake&quot;), 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 &quot;k primeros resultados&quot; — 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?</li><li><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.</li><li><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.</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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f25180cb430414b99ae553d3b8eb161dbccea4d4-1920x1080.png" style="width: 100%;" /><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>
        </item>
        <item>
            <title><![CDATA[Configuración del fragmento recursivo para documentos estructurados en Elasticsearch]]></title>
            <description><![CDATA[Aprende a configurar el chunking recursivo en Elasticsearch con tamaño de bloque, grupos de separadores y listas de separadores personalizadas para una indexación óptima de documentos estructurados.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/recursive-chunking-structured-documents-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/recursive-chunking-structured-documents-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Daniel Rubinstein]]></dc:creator>
            <pubDate>Tue, 11 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/591872e31880768ca927507654a621addc0d124d-1600x960.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Desde la versión 8.16, los usuarios pudieron configurar la estrategia de fragmentación empleada al ingirir documentos largos en campos de texto semánticos. A partir de 9.1 / 8.19, introdujimos una nueva estrategia configurable de fragmentación recursiva que emplea una lista de expresiones regulares para fragmentar el documento. El objetivo del chunking es dividir un documento largo en secciones que encapsulen contenido relacionado. Nuestras estrategias actuales dividirán el texto en un detalle de palabras/oraciones, pero los documentos escritos en formatos estructurados (por ejemplo, Markdown) a menudo contienen contenido relacionado dentro de secciones definidas por algunas cadenas de separación (por ejemplo, cabeceras). Para este tipo de documentos, introducimos la estrategia de fragmentación recursiva para aprovechar el formato de documentos estructurados y crear mejores fragmentos.</p><h2>¿Qué es el chunking recursivo?</h2><p>El fragmento recursivo iterará a través de una lista de secciones proporcionadas que separan patrones para dividir progresivamente un documento en segmentos más pequeños hasta alcanzar el tamaño máximo deseado del fragmento.</p><h3>¿Cómo configuro el chunking recursivo?</h3><p>A continuación se presentan los valores configurables proporcionados por el usuario para el fragmento recursivo:</p><ul><li>(requerido) <code>max_chunk_size</code>: El número máximo de palabras en un bloque.</li><li>Cualquiera de:<ul><li><code>separators</code>: Una lista de patrones de cadenas regex que se usarán para dividir el documento en bloques.</li><li><code>separator_group</code>: Una cadena que se mapea a una lista predeterminada de separadores definida por Elastic para usar en tipos específicos de documentos. Actualmente, hay <code>markdown</code> y <code>plaintext</code> disponibles.</li></ul></li></ul><h3>¿Cómo funciona el chunking recursivo?</h3><p>El proceso de fragmentación recursiva dado un documento de entrada, una <code>max_chunk_size</code> (medida en palabras) y una lista de cadenas de separadores es el siguiente:</p><ol><li>Si el documento de entrada ya está dentro del tamaño máximo del bloque, devuelva un solo bloque que cubra toda la entrada.</li><li>Divide el texto en posibles fragmentos según la aparición del separador. Para cada segmento potencial:<ol><li>Si el chunk potencial está dentro del tamaño máximo del chunk, agrégalo a la lista de chunks para devolverlos al usuario.</li><li>De lo contrario, repite desde el paso 2, usando solo el texto del posible fragmento y dividiendo usando el siguiente separador de la lista. Si ya no quedan separadores por probar, recurre a fragmentos basados en frases.</li></ol></li></ol><h2>Ejemplos de configuración del chunking recursivo</h2><p>Además del tamaño del bloque, la configuración principal para el fragmento recursivo es seleccionar qué separadores deben usar para dividir tus documentos. Si no sabes por dónde empezar, Elasticsearch ofrece algunos grupos de separadores por defecto que se pueden usar en casos de uso comunes.</p><h3>Utilización de grupos separadores</h3><p>Para emplear un grupo separador, simplemente indica el nombre del grupo que quieres usar al configurar la configuración de fragmentos. Por ejemplo:</p><pre><code>"chunking_settings": {
    "strategy": "recursive",
    "max_chunk_size": 25,
    "separator_group": "plaintext"
}</code></pre><p>Esto te dará una estrategia de fragmentación recursiva que emplea la lista de separadores <code>[&quot;(?&lt;!\\n)\\n\\n(?!\\n)&quot;, &quot;(?&lt;!\\n)\\n(?!\\n)&quot;)]</code>. Esto funciona bien para aplicaciones genéricas de texto plano, dividiendo en 2 caracteres de nueva línea, seguidos de 1 carácter de nueva línea.</p><p>También ofrecemos un grupo de separadores <code>markdown</code> que empleará la lista de separadores:</p><pre><code>[
"\n# ",
       "\n## ",
       "\n### ",
       "\n#### ",
       "\n##### ",
       "\n###### ",
       "\n^(?!\\s*$).*\\n-{1,}\\n",
       "\n^(?!\\s*$).*\\n={1,}\\n"
]</code></pre><p>Esta lista de separadores funcionará bien para casos generales de reducción de categorías, dividiendo en cada uno de los 6 niveles de encabezado y en los caracteres que rompen secciones.</p><p>Al crear un recurso (extremo de inferencia/campo de texto semántico), la lista de separadores correspondiente al grupo de separadores en ese momento se almacenará en tus configuraciones. Si el grupo separador se actualiza más adelante, no cambiará el comportamiento de los recursos ya creados.</p><h3>Empleando una lista de separadores personalizada</h3><p>Si uno de los grupos de separadores predefinidos no funciona para tu caso de uso, puedes definir una lista personalizada de separadores que se adapte a tus necesidades. Ten en cuenta que las expresiones regulares pueden proporcionar dentro de la lista de separadores. A continuación se muestra un ejemplo de configuración de fragmentación configurada con separadores personalizados:</p><pre><code>"chunking_settings": {
    "strategy": "recursive",
    "max_chunk_size": 25,
    "separators": ["\n\n", "\n", "<my-custom-separator>"]
}</code></pre><p>La estrategia de fragmentación anterior se dividirá en 2 caracteres de nueva línea, seguidos de 1 carácter de nueva línea y, por último, en una cadena <code>“&lt;my-custom-separator&gt;”</code>.</p><h2>Un ejemplo de fragmentación recursiva en acción</h2><p>Veamos un ejemplo de fragmentación recursiva en acción. Para este ejemplo, usaremos los siguientes ajustes de fragmentación con una lista personalizada de separadores que dividen un documento markdown usando los dos niveles superiores de cabecera:</p><pre><code>"chunking_settings": {
    "strategy": "recursive",
    "max_chunk_size": 25,
    "separators": ["\n# ", "\n## "]
}</code></pre><p>Veamos un documento sencillo de Markdown sin fragmentos:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3a5507f4a1288065097231548e5b18e240508785-1302x1446.png" style="width: 100%;" /><p>Ahora usemos los ajustes de fragmentación definidos arriba para fragmentar el documento:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a3313c4c40ff39b8dbcdd7c4878c723f088e6c1a-1600x1187.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/79a2921943191ea631df94c9d465818ec8d3e738-1600x1206.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/459e695cce7540267422396b9a62ff4ad35f61db-1600x1260.png" style="width: 100%;" /><p>Nota: La nueva línea al final de cada bloque (excepto el Fragmento 3) no está resaltada, pero sí se incluye dentro de los límites reales del bloque.</p><h3>¡Empieza hoy mismo con el chunking recursivo!</h3><p>Para más información sobre cómo emplear esta función, consulta la documentación sobre <span class="unknown__pt__mark__88e241676926">cómo configurar la configuración de fragmentos</span>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Introducción de la interfaz de reglas de consulta Elasticsearch en Kibana]]></title>
            <description><![CDATA[Aprende a usar la interfaz de Reglas de Consulta de Elasticsearch para agregar o excluir documentos de consultas de búsqueda usando conjuntos de reglas personalizables en Kibana, sin afectar al ranking orgánico.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elasticsearch-query-rules-ui-introduction</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elasticsearch-query-rules-ui-introduction</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jhon Guzmán]]></dc:creator>
            <pubDate>Fri, 07 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1fb10bd31c509cc9b9bb4f71f49970f140e6c36f-1600x945.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La función de un motor de búsqueda es devolver resultados relevantes. Sin embargo, hay necesidades empresariales que van más allá de eso, como destacar las ventas, priorizar productos de temporada o mostrar artículos patrocinados, y los desarrolladores no siempre pueden hacer esto en la consulta de búsqueda.</p><p>Además, estos casos de uso suelen ser sensibles al tiempo, y pasar por las etapas típicas de desarrollo (crear una rama de código y luego esperar una nueva versión) es un proceso que consume mucho tiempo.</p><p>Entonces, ¿y si pudiéramos hacer todo este proceso solo con una llamada a la API, o mejor aún, con solo unos clics en Kibana?</p><h2>Interfaz de Reglas de Consulta</h2><p>Elasticsearch 8.10 introdujo Reglas de <a href="https://www.elastic.co/blog/introducing-query-rules-elasticsearch-8-10"><strong>Consulta</strong></a> y <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/rule-retriever"><strong>Retriever de Reglas</strong></a>. Estas son herramientas diseñadas para inyectar <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-pinned-query"><em>resultados fijados</em></a> en las consultas sin afectar la clasificación de los resultados orgánicos según reglas. Solo agregan lógica de negocio encima de los resultados de forma declarativa y sencilla.</p><p>Algunos casos de uso comunes para las Reglas de Consulta son:</p><ul><li><strong>Destacar anuncios u ofertas promocionadas</strong>: Mostrar artículos en oferta o patrocinados en la parte superior.</li><li><strong>Excluyendo por contexto o geolocalización</strong>: ocultar ciertos objetos cuando la normativa local no permite mostrarlos.</li><li><strong>Priorizar los resultados clave</strong>: Cerciorar de que las búsquedas populares o fijas estén siempre en la cima, independientemente del ranking orgánico.</li></ul><p>Para acceder a la interfaz e interactuar con estas herramientas, necesitas hacer clic en el menú lateral de Kibana e ir a <strong>Reglas de consulta</strong>, en <strong>Relevancia:</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/242e33e89d1a07ffa0e76009c46b3a9236722741-458x1010.png" style="width: 100%;" /><p>Cuando aparezca el menú de reglas de consulta, haz clic <strong>en Crear tu primer conjunto de reglas:</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/30b3a91bbbf243d314cf38298e01ca5cff784430-1600x945.png" style="width: 100%;" /><p>A continuación, tienes que nombrar tu conjunto de normas.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/26c5462f88678867776f933b5655ca0df0d72a16-708x446.png" style="width: 100%;" /><p>La forma para definir cada regla tiene tres componentes clave:</p><ul><li><strong>Criterios</strong>: Las condiciones que deben cumplir para que la norma se aplique. Por ejemplo, &quot;cuando el campo query_string contiene el valor <em>Christmas</em>&quot; o &quot;cuando el campo del país es <em>CO.&quot;</em></li><li><strong>Acción</strong>: Esto es lo que quieres que ocurra cuando se cumplan las condiciones. Puede fijar (fijar un documento a los resultados superiores) o excluir (ocultar un documento).</li><li><strong>Metadatos</strong>: Estos son los campos que acompañan la consulta cuando se ejecuta. Pueden incluir la información del usuario (como ubicación o idioma), así como datos de búsqueda (query_string). Estos son los valores que emplean los criterios para decidir si aplicar o no una regla.</li></ul><h2>Ejemplo: objetos populares</h2><p>Imaginemos que tenemos un sitio de comercio electrónico con diferentes artículos. Al analizar las métricas, observamos que uno de los artículos más vendidos en la categoría de consolas es el &quot;DualShock 4 Wireless Controller&quot;, especialmente cuando los usuarios buscan las palabras clave &quot;PS4&quot; o &quot;PlayStation 4&quot;. Así que decidimos poner este producto encima de los resultados cada vez que un usuario busque esas palabras clave.</p><p>Primero, indexemos los documentos de cada elemento usando una solicitud Bulk API:</p><pre><code>POST _bulk
{ "index": { "_index": "products", "_id": "1" } }
{ "id": "1", "name": "PlayStation 4 Slim 1TB", "category": "console", "brand": "Sony", "price": 1200 }
{ "index": { "_index": "products", "_id": "2" } }
{ "id": "2", "name": "DualShock 4 Wireless Controller", "category": "accessory", "brand": "Sony", "price": 250 }
{ "index": { "_index": "products", "_id": "3" } }
{ "id": "3", "name": "PlayStation 4 Camera", "category": "accessory", "brand": "Sony", "price": 200 }
{ "index": { "_index": "products", "_id": "4" } }
{ "id": "4", "name": "PlayStation 4 VR Headset", "category": "accessory", "brand": "Sony", "price": 900 }
{ "index": { "_index": "products", "_id": "5" } }
{ "id": "5", "name": "Charging Station for DualShock 4", "category": "accessory", "brand": "Sony", "price": 80 }</code></pre><p>Si no intervenimos en la consulta, el elemento suele aparecer en cuarto lugar. Aquí está la pregunta:</p><pre><code>GET products/_search
{
 "query": {
   "match": {
     "name": "PlayStation 4"
   }
 }
}</code></pre><p>Y aquí están los resultados</p><pre><code>{
 "took": 1,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 5,
     "relation": "eq"
   },
   "max_score": 0.6973252,
   "hits": [
     {
       "_index": "products",
       "_id": "3",
       "_score": 0.6973252,
       "_source": {
         "id": "3",
         "name": "PlayStation 4 Camera",
         "category": "accessory",
         "brand": "Sony",
         "price": 200
       }
     },
     {
       "_index": "products",
       "_id": "1",
       "_score": 0.6260078,
       "_source": {
         "id": "1",
         "name": "PlayStation 4 Slim 1TB",
         "category": "console",
         "brand": "Sony",
         "price": 1200
       }
     },
     {
       "_index": "products",
       "_id": "4",
       "_score": 0.6260078,
       "_source": {
         "id": "4",
         "name": "PlayStation 4 VR Headset",
         "category": "accessory",
         "brand": "Sony",
         "price": 900
       }
     },
     {
       "_index": "products",
       "_id": "2",
       "_score": 0.08701137,
       "_source": {
         "id": "2",
         "name": "DualShock 4 Wireless Controller",
         "category": "accessory",
         "brand": "Sony",
         "price": 250
       }
     },
     {
       "_index": "products",
       "_id": "5",
       "_score": 0.07893815,
       "_source": {
         "id": "5",
         "name": "Charging Station for DualShock 4",
         "category": "accessory",
         "brand": "Sony",
         "price": 80
       }
     }
   ]
 }
}</code></pre><p>Vamos a crear una regla de consulta para cambiar esto. Primero, vamos a agregarlo al reglamento de esta manera:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fdc42646fb3e76a09bca7d19047a76efe343f7a2-1600x650.png" style="width: 100%;" /><p>O <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-query-rules-put-ruleset">solicitud API</a> equivalente:</p><pre><code>PUT _query_rules/my-rules
{
  "rules": [
    {
      "rule_id": "rule-1232",
      "type": "pinned",
      "criteria": [
        {
          "type": "exact",
          "metadata": "query_string",
          "values": [
            "PS4",
            "PlayStation 4"
          ]
        }
      ],
      "actions": {
        "docs": [
          {
            "_index": "products",
            "_id": "2"
          }
        ]
      }
    }
  ]
}</code></pre><p>Para usar el <strong>conjunto de reglas </strong>en nuestra consulta, debemos usar un tipo de regla de consulta. Este tipo de consulta se compone de dos partes principales:</p><pre><code>GET /products/_search
{
 "retriever": {
   "rule": {
     "retriever": {
       "standard": {
         "query": {
           "match": { "name": "PlayStation 4" }
         }
       }
     },
     "match_criteria": {
       "query_string": "PlayStation 4"
     },
     "ruleset_ids": ["my-rules"]
   }
 }
}</code></pre><ul><li><strong>match_criteria</strong>: Estos son los metadatos que se emplean para comparar con la consulta del usuario. En este ejemplo, el conjunto de reglas se activa cuando el campo query_string tiene el valor &quot;PlayStation 4.&quot;</li><li><strong>Consulta</strong>: La consulta real que se usará para buscar y obtener los resultados orgánicos.</li></ul><p>De este modo, primero ejecutas la consulta orgánica y luego Elasticsearch aplica las reglas de tu conjunto de reglas:</p><pre><code>{
 "took": 17,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 5,
     "relation": "eq"
   },
   "max_score": 1.7014122e+38,
   "hits": [
     {
       "_index": "products",
       "_id": "2",
       "_score": 1.7014122e+38,
       "_source": {
         "id": "2",
         "name": "DualShock 4 Wireless Controller",
         "category": "accessory",
         "brand": "Sony",
         "price": 250
       }
     },
     {
       "_index": "products",
       "_id": "3",
       "_score": 0.6973252,
       "_source": {
         "id": "3",
         "name": "PlayStation 4 Camera",
         "category": "accessory",
         "brand": "Sony",
         "price": 200
       }
     },
     {
       "_index": "products",
       "_id": "1",
       "_score": 0.6260078,
       "_source": {
         "id": "1",
         "name": "PlayStation 4 Slim 1TB",
         "category": "console",
         "brand": "Sony",
         "price": 1200
       }
     },
     {
       "_index": "products",
       "_id": "4",
       "_score": 0.6260078,
       "_source": {
         "id": "4",
         "name": "PlayStation 4 VR Headset",
         "category": "accessory",
         "brand": "Sony",
         "price": 900
       }
     },
     {
       "_index": "products",
       "_id": "5",
       "_score": 0.07893815,
       "_source": {
         "id": "5",
         "name": "Charging Station for DualShock 4",
         "category": "accessory",
         "brand": "Sony",
         "price": 80
       }
     }
   ]
 }
}</code></pre><h2>Ejemplo: metadatos basados en el usuario</h2><p>Otra aplicación interesante de las Reglas de Consulta es usar metadatos para mostrar documentos específicos basar en información contextual del usuario o del sitio web.</p><p>Por ejemplo, imaginemos que queremos destacar artículos o ventas personalizadas basándonos en el nivel de fidelidad del usuario, representado como un valor numérico.</p><p>Podemos hacerlo ingiriendo estos metadatos directamente en la consulta para que las reglas se activen cuando dicho valor cumple ciertos criterios.</p><p>Primero, indexaremos un documento que solo los usuarios con un alto nivel de lealtad puedan ver:</p><pre><code>POST _bulk
{ "index": { "_index": "products", "_id": "6" } }
{ "id": "6", "name": "PlayStation Plus Deluxe Card - 12 months", "category": "membership", "brand": "Sony", "price": 300 }</code></pre><p>Ahora, creemos una nueva regla dentro del mismo conjunto de reglas para que cuando el loyalty_level sea igual o superior a 80, el elemento aparezca encima de los resultados.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/58de12dff93305440608f51465462fcc68653a08-1421x496.png" style="width: 100%;" /><p>Almacena la regla y el reglamento.</p><p>Aquí está la solicitud REST equivalente:</p><pre><code>PUT _query_rules/my-rules
{
  "rules": [
    {
      "rule_id": "pin-premiun-user",
      "type": "pinned",
      "criteria": [
        {
          "type": "gte",
          "metadata": "loyalty_level",
          "values": [
            80
          ]
        }
      ],
      "actions": {
        "docs": [
          {
            "_index": "products",
            "_id": "6"
          }
        ]
      }
    }
  ]
}</code></pre><p>Ahora, al ejecutar una consulta, necesitamos incluir el nuevo <strong>parámetro loyalty_level </strong>en los metadatos. Si se cumple la condición de la regla, el nuevo documento aparecerá encima de los resultados.</p><p>Por ejemplo, al enviar una consulta donde el loyalty_level es 80:</p><pre><code>POST /products/_search
{
  "retriever": {
    "rule": {
      "retriever": {
        "standard": {
          "query": {
            "match": {
              "name": "PlayStation"
            }
          }
        }
      },
      "match_criteria": {
        "query_string": "PlayStation",
        "loyalty_level": 80
      },
      "ruleset_ids": ["my-rules"]
    }
  }
}</code></pre><p>Veremos el documento de lealtad encima de los resultados:</p><pre><code>{
  "took": 31,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 4,
      "relation": "eq"
    },
    "max_score": 1.7014122e+38,
    "hits": [
      {
        "_index": "products",
        "_id": "6",
        "_score": 1.7014122e+38,
        "_source": {
          "id": "6",
          "name": "PlayStation Plus Deluxe Card - 12 months",
          "category": "membership",
          "brand": "Sony",
          "price": 300
        }
      },
      {
        "_index": "products",
        "_id": "3",
        "_score": 0.5054567,
        "_source": {
          "id": "3",
          "name": "PlayStation 4 Camera",
          "category": "accessory",
          "brand": "Sony",
          "price": 200
        }
      },
      {
        "_index": "products",
        "_id": "1",
        "_score": 0.45618832,
        "_source": {
          "id": "1",
          "name": "PlayStation 4 Slim 1TB",
          "category": "console",
          "brand": "Sony",
          "price": 1200
        }
      },
      {
        "_index": "products",
        "_id": "4",
        "_score": 0.45618832,
        "_source": {
          "id": "4",
          "name": "PlayStation 4 VR Headset",
          "category": "accessory",
          "brand": "Sony",
          "price": 900
        }
      }
    ]
  }
}</code></pre><p>En el caso siguiente, dado que el nivel de lealtad es 70, la regla no se cumple y el objeto no debería aparecer arriba:</p><pre><code>POST /products/_search
{
  "retriever": {
    "rule": {
      "retriever": {
        "standard": {
          "query": {
            "match": {
              "name": "PlayStation"
            }
          }
        }
      },
      "match_criteria": {
        "query_string": "PlayStation",
        "loyalty_level": 70
      },
      "ruleset_ids": ["my-rules"]
    }
  }
}</code></pre><p>Aquí están los resultados:</p><pre><code>{
  "took": 7,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 4,
      "relation": "eq"
    },
    "max_score": 0.5054567,
    "hits": [
      {
        "_index": "products",
        "_id": "3",
        "_score": 0.5054567,
        "_source": {
          "id": "3",
          "name": "PlayStation 4 Camera",
          "category": "accessory",
          "brand": "Sony",
          "price": 200
        }
      },
      {
        "_index": "products",
        "_id": "1",
        "_score": 0.45618832,
        "_source": {
          "id": "1",
          "name": "PlayStation 4 Slim 1TB",
          "category": "console",
          "brand": "Sony",
          "price": 1200
        }
      },
      {
        "_index": "products",
        "_id": "4",
        "_score": 0.45618832,
        "_source": {
          "id": "4",
          "name": "PlayStation 4 VR Headset",
          "category": "accessory",
          "brand": "Sony",
          "price": 900
        }
      },
      {
        "_index": "products",
        "_id": "6",
        "_score": 0.3817649,
        "_source": {
          "id": "6",
          "name": "PlayStation Plus Deluxe Card - 12 months",
          "category": "membership",
          "brand": "Sony",
          "price": 300
        }
      }
    ]
  }
}</code></pre><h2>Ejemplo: exclusión inmediata</h2><p>Supongamos que nuestro <strong>mando inalámbrico DualShock 4 (ID 2)</strong> está temporalmente indisponible y no puede vender. Así que, en lugar de eliminar manualmente el documento o esperar a que algún proceso de datos se active, el equipo de negocio decide eliminarlo de los resultados de búsqueda mientras tanto.</p><p>Usaremos un proceso similar al que acabamos de aplicar a los objetos populares, pero esta vez en lugar de seleccionar <em>Fijado</em>, elegiremos <em>Excluir</em>. Esta regla funciona como una especie de lista negra. Cambia los criterios a <strong>Siempre</strong> para que la exclusión funcione cada vez que se ejecute la consulta.</p><p>La regla debería ser así:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f10971e4f1bc9520105111adfa3a476581a27130-1600x623.png" style="width: 100%;" /><p>Almacena la regla y el conjunto de reglas para aplicar los cambios. Aquí está la solicitud REST equivalente:</p><pre><code>PUT _query_rules/my-rules
{
  "rules": [
    {
      "rule_id": "rule-6358",
      "type": "pinned",
      "criteria": [
        {
          "type": "always"
        }
      ],
      "actions": {
        "docs": [
          {
            "_index": "products",
            "_id": "2"
          }
        ]
      }
    }
  ]
}</code></pre><p>Ahora, cuando ejecutamos la consulta de nuevo, verás que el elemento ya no aparece en los resultados, aunque la regla anterior sea fijarlo. Esto se debe a <strong>que las exclusiones tienen prioridad sobre los resultados de fijación</strong>.</p><pre><code>{
 "took": 6,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 4,
     "relation": "eq"
   },
   "max_score": 2.205655,
   "hits": [
     {
       "_index": "products",
       "_id": "3",
       "_score": 2.205655,
       "_source": {
         "id": "3",
         "name": "PlayStation 4 Camera",
         "category": "accessory",
         "brand": "Sony",
         "price": 200
       }
     },
     {
       "_index": "products",
       "_id": "1",
       "_score": 1.9738505,
       "_source": {
         "id": "1",
         "name": "PlayStation 4 Slim 1TB",
         "category": "console",
         "brand": "Sony",
         "price": 1200
       }
     },
     {
       "_index": "products",
       "_id": "4",
       "_score": 1.9738505,
       "_source": {
         "id": "4",
         "name": "PlayStation 4 VR Headset",
         "category": "accessory",
         "brand": "Sony",
         "price": 900
       }
     },
     {
       "_index": "products",
       "_id": "5",
       "_score": 0.69247496,
       "_source": {
         "id": "5",
         "name": "Charging Station for DualShock 4",
         "category": "accessory",
         "brand": "Sony",
         "price": 80
       }
     }
   ]
 }
}</code></pre><h2>Conclusión</h2><p><strong>Las Reglas de Consulta</strong> facilitan mucho ajustar la relevancia sin ningún cambio en el código. La nueva interfaz <strong>Kibana</strong> <strong> </strong>te<strong> </strong>permite realizar estos cambios en cuestión de segundos, dándote a ti y a tu equipo empresarial más control sobre los resultados de búsqueda.</p><p>Más allá del comercio electrónico, las Reglas de Consulta pueden impulsar muchos otros escenarios: destacar guías de resolución de problemas en portales de soporte, mostrar documentos internos clave en bases de conocimiento, promover noticias de última hora en sitios de noticias o filtrar ofertas de empleo o contenido caducado. Incluso pueden hacer cumplir normas de cumplimiento, como ocultar material restringido por rol de usuario o región.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construir un agente de conocimiento con recordación semántica usando Mastra y Elasticsearch]]></title>
            <description><![CDATA[Aprende a construir un agente de conocimiento con recordación semántica usando Mastra y Elasticsearch como almacén vectorial para la recuperación de memoria e información.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/knowledge-agent-semantic-recall-mastra-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/knowledge-agent-semantic-recall-mastra-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[JD Armada]]></dc:creator>
            <pubDate>Thu, 06 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b8d51c2726d5573385c9246a7821d12ade4f1b0e-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">La Ingeniería del Contexto</a> está ganando cada vez más importancia para construir agentes y arquitecturas de IA fiables. A medida que los modelos mejoran, su eficacia y fiabilidad dependen menos de sus datos capacitados y más de lo bien que estén fundamentados en el contexto adecuado. Los agentes que pueden recuperar y aplicar la información más relevante en el momento adecuado tienen muchas más probabilidades de producir resultados precisos y fiables.</p><p>En este blog, emplearemos <a href="https://mastra.ai/">Mastra</a> para construir un agente de conocimiento que recuerda lo que dicen los usuarios y puede recuperar información relevante más adelante, empleando Elasticsearch como backend de memoria y recuperación. Puedes extender fácilmente este mismo concepto a casos de uso reales, piensa en agentes de soporte que puedan recordar conversaciones y resoluciones pasadas, permitiéndoles adaptar las respuestas a usuarios específicos o a soluciones superficiales más rápido basar en contextos previos.</p><p>Sigue aquí para ver cómo construirlo paso a paso. Si te pierdes o simplemente quieres ejecutar un ejemplo terminado, echa un vistazo al <a href="https://github.com/jdarmada/getting-started-mastra-elastic/tree/main">repositorio aquí</a>.</p><h2>¿Qué es Mastra?</h2><p>Mastra es un framework TypeScript de código abierto para construir agentes de IA con partes intercambiables para razonamiento, memoria y herramientas. Su función <a href="https://mastra.ai/docs/memory/semantic-recall">de recuperación semántica</a> permite a los agentes recordar y recuperar interacciones pasadas almacenando mensajes como incrustaciones en una base de datos vectorial. Esto permite a los agentes mantener el contexto y la continuidad de la conversación a largo plazo. Elasticsearch es un excelente almacén vectorial para habilitar esta función, ya que soporta una búsqueda vectorial densa eficiente. Cuando se activa la recuperación semántica, el agente extrae mensajes pasados relevantes en la ventana de contexto del modelo, permitiendo que el modelo emplee ese contexto recuperado como base para su razonamiento y respuestas.</p><h2>Lo que necesitas para empezar</h2><ul><li>Nodo v18+</li><li>Elasticsearch (versión 8.15 o posterior)</li><li>Clave API de Elasticsearch</li><li><a href="https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key">Clave API de OpenAI</a></li></ul><p>Nota: Necesitarás esto porque la demo usa el proveedor OpenAI, pero Mastra soporta otros SDKs de IA y proveedores de modelos comunitarios, así que puedes cambiarlo fácilmente según tu configuración.</p><h2>Construyendo un proyecto de Mastra</h2><p>Emplearemos la CLI integrada de Mamra para proporcionar el andamiaje de nuestro proyecto. Ejecuta el comando:</p><pre><code>npm create mastra@latest</code></pre><p>Recibirás un conjunto de indicaciones, que empiezan por:</p><p>1. Pon un nombre a tu proyecto.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2b9fe559e0276140dd539e24f916a73c60870405-620x84.png" style="width: 100%;" /><p>2. Podemos mantener este valor predeterminado; No dudes en dejar esto en blanco.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e04729eb03bce8499e973e18c28642402340d0e5-852x68.png" style="width: 100%;" /><p>3. Para este proyecto, emplearemos un modelo proporcionado por OpenAI.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a86596a469a71bdf8bd99cbaf528d0f0cf7272c0-436x222.png" style="width: 100%;" /><p>4. Selecciona la opción &quot;Saltar por ahora&quot; porque almacenaremos todas nuestras variables de entorno en un archivo &#x27;.env&#x27; que configuraremos en un paso posterior.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/02b19ccc34af0bdacf52fd94b519d036540ca2e6-426x114.png" style="width: 100%;" /><p>5. También podemos saltar esta opción.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b3fe63d19d270bc2e0de1dd92033bf8b26750819-990x208.png" style="width: 100%;" /><p>Una vez que termines de inicializar, podemos pasar al siguiente paso.</p><h3>Instalación de dependencias</h3><p>A continuación, necesitamos instalar algunas dependencias:</p><pre><code>npm install ai @ai-sdk/openai @elastic/elasticsearch dotenv</code></pre><ul><li><code>ai</code> - Paquete básico de SDK de IA que proporciona herramientas para gestionar modelos de IA, prompts y flujos de trabajo en JavaScript/TypeScript. Mastra está construido sobre el <a href="https://ai-sdk.dev/">SDK de IA</a> por Vercel, así que necesitamos esta dependencia para permitir la interacción del modelo con tu agente.</li><li><code>@ai-sdk/openai</code> - Plugin que conecta el SDK de IA con modelos OpenAI (como GPT-4, GPT-4o, etc.), habilitando llamadas API usando tu clave API OpenAI.</li><li><code>@elastic/elasticsearch</code> - <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/javascript">Cliente oficial de Elasticsearch para Node.js</a>, se emplea para conectarse a tu Elastic Cloud o a un clúster local para operaciones de indexación, búsqueda y vectores.</li><li><code>dotenv</code> - Carga variables de entorno desde un .env archivar en process.env, permitiendo inyectar de forma segura credenciales como claves API y endpoints Elasticsearch.</li></ul><h3>Configuración de variables de entorno</h3><p>Crea un archivo <code>.env</code> en el directorio raíz de tu proyecto si aún no ves uno. Alternativamente, puedes copiar y renombrar el ejemplo <code>.env</code> que proporcioné en el <a href="https://github.com/jdarmada/getting-started-mastra-elastic/blob/main/.env.example">repositorio</a>. En este archivo, podemos agregar las siguientes variables:</p><pre><code>ELASTICSEARCH_ENDPOINT="your-endpoint-here"
ELASTICSEARCH_API_KEY="your-key-here"
OPENAI_API_KEY="your-key-here"</code></pre><p>Eso concluye la configuración básica. Desde aquí, ya puedes empezar a construir y orquestar agentes. Vamos un paso más allá y agregaremos Elasticsearch como la capa de almacenamiento y búsqueda vectorial.</p><h2>Agregar Elasticsearch como almacenamiento vectorial</h2><p>Crea una nueva carpeta llamada <code>stores</code> y dentro, agrega este <a href="https://github.com/jdarmada/getting-started-mastra-elastic/blob/main/src/mastra/stores/elastic-store.ts">archivo</a>. Antes de que Mastra y Elastic lanzaran una integración oficial de almacenamiento vectorial de Elasticsearch, <a href="https://github.com/abhiaiyer91">Abhi Aiyer</a>(CTO de Mestra) compartió esta clase prototipo temprana llamada <code>ElasticVector</code>. Simplemente, conecta la abstracción de memoria de Mamra con las densas capacidades vectoriales de Elasticsearch, para que los desarrolladores puedan incluir Elasticsearch como base de datos vectorial para sus agentes.</p><p>Echemos un vistazo más profundo a las partes importantes de la integración:</p><h3>Ingestión del cliente Elasticsearch</h3><p>Esta sección define la clase <code>ElasticVector</code> y configura la conexión cliente de Elasticsearch con soporte tanto para despliegues estándar como serverless.</p><pre><code>export interface ElasticVectorConfig extends ClientOptions {
    /**
     * Explicitly specify if connecting to Elasticsearch Serverless.
     * If not provided, will be auto-detected on first use.
     */
    isServerless?: boolean;
    
    /**
     * Maximum documents to count accurately when describing indices.
     * Higher values provide accurate counts but may impact performance on large indices.
     * 
     * @default 10000
     */
    maxCountAccuracy?: number;
}

export class ElasticVector extends MastraVector {
    private client: Client;
    private isServerless: boolean | undefined;
    private deploymentChecked: boolean = false;
    private readonly maxCountAccuracy: number;

    constructor(config: ElasticVectorConfig) {
        super();
        this.client = new Client(config);
        this.isServerless = config.isServerless;
        this.maxCountAccuracy = config.maxCountAccuracy ?? 10000;
    }
}</code></pre><ul><li><code>ElasticVectorConfig extends ClientOptions</code>: Esto crea una nueva interfaz de configuración que hereda todas las opciones del cliente de Elasticsearch (como <code>node</code>, <code>auth</code>, <code>requestTimeout</code>) y agrega nuestras propiedades personalizadas. Esto significa que los usuarios pueden pasar cualquier configuración válida de Elasticsearch junto con nuestras opciones específicas para serverless.</li><li><code>extends MastraVector</code>: Esto permite <code>ElasticVector</code> heredar de la clase base de <code>MastraVector</code> de Mastra, que es una interfaz común a la que se ajustan todas las integraciones de almacenamiento vectorial. Esto garantiza que Elasticsearch se comporte como cualquier otro backend de vectores Mastra desde la perspectiva del agente.</li><li><code>private client: Client</code>: Esta es una propiedad privada que contiene una instancia del cliente JavaScript Elasticsearch. Esto permite que la clase hable directamente con tu grupo.</li><li><code>isServerless</code> y <code>deploymentChecked</code>: Estas propiedades trabajan juntas para detectar y almacenar en caché si estamos conectados a un despliegue serverless o estándar de Elasticsearch. Esta detección ocurre automáticamente en el primer uso, o puede configurar explícitamente.</li><li><code>constructor(config: ClientOptions)</code>: Este constructor toma un objeto de configuración (que contiene tus credenciales de Elasticsearch y configuraciones opcionales de serverless) y lo emplea para inicializar el cliente en la línea <code>this.client = new Client(config)</code>.</li><li><code>super()</code>: Esto llama constructor base de Mastra, por lo que hereda el registro, los asistentes de validación y otros ganchos internos.</li></ul><p>En este punto, Mastra sabe que hay un nuevo almacén vectorial llamado <code>ElasticVector</code></p><h3>Detección del tipo de despliegue</h3><p>Antes de crear índices, el adaptador detecta automáticamente si estás usando Elasticsearch estándar o Elasticsearch Serverless. Esto es importante porque los despliegues serverless no permiten la configuración manual de shards.</p><pre><code>private async detectServerless(): Promise<boolean> {
    // Return cached result if already detected
    if (this.deploymentChecked) {
        return this.isServerless ?? false;
    }

    // Use explicit configuration if provided
    if (this.isServerless !== undefined) {
        this.deploymentChecked = true;
        this.logger?.info(
            `Using explicit deployment type: ${this.isServerless ? 'Serverless' : 'Standard'}`
        );
        return this.isServerless;
    }

    try {
        const info = await this.client.info();
        
        // Primary detection: build flavor (most reliable)
        const isBuildFlavorServerless = info.version?.build_flavor === 'serverless';
        
        // Secondary detection: tagline (fallback)
        const isTaglineServerless = info.tagline?.toLowerCase().includes('serverless') ?? false;
        
        this.isServerless = isBuildFlavorServerless || isTaglineServerless;
        this.deploymentChecked = true;
        
        this.logger?.info(
            `Auto-detected ${this.isServerless ? 'Serverless' : 'Standard'} Elasticsearch deployment`,
            { 
                buildFlavor: info.version?.build_flavor, 
                version: info.version?.number,
                detectionMethod: isBuildFlavorServerless ? 'build_flavor' : 'tagline'
            }
        );
        
        return this.isServerless;
    } catch (error) {
        this.logger?.warn(
            'Could not auto-detect deployment type, assuming Standard Elasticsearch. ' +
            'Set isServerless: true explicitly in config if using Serverless.',
            { error: error instanceof Error ? error.message : String(error) }
        );
        this.isServerless = false;
        this.deploymentChecked = true;
        return false;
    }
}</code></pre><p>Qué pasa:</p><ul><li>Primero comprueba si pusiste explícitamente <code>isServerless</code> en la configuración (se salta la auto-detección).</li><li>Llama a la API <code>info()</code> de Elasticsearch para obtener información del clúster</li><li>Comprueba el <code>build_flavor field</code> (los despliegues serverless devuelven <code>serverless</code>)</li><li>Vuelve a revisar el lema si no hay variedad de build disponible</li><li>Almacena en caché el resultado para evitar llamadas repetidas a la API</li><li>Por defecto se aplica al despliegue estándar si falla la detección</li></ul><p> Ejemplo de uso:</p><pre><code>// Option 1: Auto-detect (recommended)
const vector = new ElasticVector({
    node: 'https://your-cluster.es.cloud',
    auth: { apiKey: 'your-api-key' }
});
// Detection happens automatically on first index operation

// Option 2: Explicit configuration (faster startup)
const vector = new ElasticVector({
    node: 'https://your-serverless.es.cloud',
    auth: { apiKey: 'your-api-key' },
    isServerless: true  // Skips auto-detection
});</code></pre><h3>Creación del almacén de &quot;memoria&quot; en Elasticsearch</h3><p>La función siguiente establece un índice Elasticsearch para almacenar incrustaciones. Comprueba si el índice ya existe. Si no, crea uno con el mapeo que aparece abajo y contiene un campo <code>dense_vector</code> para almacenar incrustaciones y métricas de similitud personalizadas.</p><p>Algunas cosas a tener en cuenta:</p><ul><li>El parámetro <code>dimension</code> es la longitud de cada vector de incrustación, que depende del modelo de incrustación que estés usando. En nuestro caso, generaremos incrustaciones usando el modelo <code>text-embedding-3-small</code> de OpenAI, que genera vectores de tamaño <code>1536</code>. Usaremos esto como nuestro valor por defecto.</li><li>La variable <code>similarity</code> empleada en el mapeo a continuación se define a partir de la función auxiliar c<code>onst similarity = this.mapMetricToSimilarity(metric)</code>, que toma el valor del parámetro <code>metric</code> y lo convierte en una palabra clave compatible con Elasticsearch para la métrica de distancia elegida.<ul><li>Por ejemplo: Mastra emplea términos generales para similitud vectorial como <code>cosine</code>, <code>euclidean</code>, y <code>dotproduct</code>. Si pasáramos la métrica <code>euclidean</code> directamente al mapeo de Elasticsearch, generaría un error porque Elasticsearch espera que la palabra clave <code>l2_norm</code> represente la distancia euclidiana.</li></ul></li><li>Compatibilidad sin servidor: El código omite automáticamente los ajustes de shard y réplica para despliegues sin servidor, ya que estos son gestionados automáticamente por Elasticsearch Serverless.</li></ul><pre><code>async createIndex(params: CreateIndexParams): Promise<void> {
    const { indexName, dimension = 1536, metric = 'cosine' } = params;

    try {
        const exists = await this.client.indices.exists({ index: indexName });

        if (exists) {
            try {
                await this.validateExistingIndex(indexName, dimension, metric);
                this.logger?.info(`Index "${indexName}" already exists and is valid`);
                return;
            } catch (validationError) {
                throw new Error(
                    `Index "${indexName}" exists but does not match the required configuration: ${
                        validationError instanceof Error ? validationError.message : String(validationError)
                    }`
                );
            }
        }

        const isServerless = await this.detectServerless();
        const similarity = this.mapMetricToSimilarity(metric);

        const indexConfig: any = {
            index: indexName,
            mappings: {
                properties: {
                    vector: {
                        type: 'dense_vector',
                        dims: dimension,
                        index: true,
                        similarity: similarity,
                    },
                    metadata: {
                        type: 'object',
                        enabled: true,
                        dynamic: true, // Allows flexible metadata structures
                    },
                },
            },
        };

        // Only configure shards/replicas for non-serverless deployments
        // Serverless manages infrastructure automatically
        if (!isServerless) {
            indexConfig.settings = {
                number_of_shards: 1,
                number_of_replicas: 0, // Increase for production HA deployments
            };
        }

        await this.client.indices.create(indexConfig);

        this.logger?.info(
            `Created ${isServerless ? 'Serverless' : 'Standard'} Elasticsearch index "${indexName}"`,
            { dimension, metric, similarity }
        );
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        this.logger?.error(`Failed to create index "${indexName}": ${errorMessage}`);
        throw new Error(`Failed to create index "${indexName}": ${errorMessage}`);
    }
}</code></pre><h3>Almacenar una nueva recordación o nota tras una interacción</h3><p>Esta función toma nuevas incrustaciones generadas tras cada interacción, junto con los metadatos, y luego las inserta o actualiza en el índice usando la API <code>bulk</code> de Elastic. La API <code>bulk</code> agrupa múltiples operaciones de escritura en una sola solicitud; Esta mejora en nuestro rendimiento de indexación garantiza que las actualizaciones se mantengan eficientes a medida que la memoria de nuestro agente sigue creciendo.</p><pre><code>async upsert(params: UpsertVectorParams): Promise<string[]> {
    const { indexName, vectors, metadata = [], ids } = params;

    try {
        // Generate unique IDs if not provided
        const vectorIds = ids || vectors.map((_, i) => 
            `vec_${Date.now()}_${i}_${Math.random().toString(36).substr(2, 9)}`
        );

        const operations = vectors.flatMap((vec, index) => [
            { index: { _index: indexName, _id: vectorIds[index] } },
            {
                vector: vec,
                metadata: metadata[index] || {},
            },
        ]);

        const response = await this.client.bulk({
            refresh: true,
            operations,
        });

        if (response.errors) {
            const erroredItems = response.items.filter((item: any) => item.index?.error);
            const erroredIds = erroredItems.map((item: any) => item.index?._id);
            const errorDetails = erroredItems.slice(0, 3).map((item: any) => ({
                id: item.index?._id,
                error: item.index?.error?.reason || item.index?.error,
                type: item.index?.error?.type
            }));
            
            const errorMessage = `Failed to upsert ${erroredIds.length}/${vectors.length} vectors`;
            console.error(`${errorMessage}. Sample errors:`, JSON.stringify(errorDetails, null, 2));
            this.logger?.error(errorMessage, { 
                failedCount: erroredIds.length, 
                totalCount: vectors.length,
                sampleErrors: errorDetails 
            });
            
            // Still return successfully inserted IDs
            const successfulIds = vectorIds.filter((id, idx) => 
                !erroredIds.includes(id)
            );
            
            if (successfulIds.length === 0) {
                throw new Error(`${errorMessage}. All operations failed. See logs for details.`);
            }
            
            return successfulIds;
        }

        this.logger?.info(`Successfully upserted ${vectors.length} vectors to "${indexName}"`);
        return vectorIds;
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        this.logger?.error(`Failed to upsert vectors to "${indexName}": ${errorMessage}`);
        throw new Error(`Failed to upsert vectors to "${indexName}": ${errorMessage}`);
    }
}</code></pre><h3>Consulta de vectores similares para la recuperación semántica</h3><p>Esta función es el núcleo de la característica de recuperación semántica. El agente emplea búsqueda vectorial para encontrar incrustaciones almacenadas similares dentro de nuestro índice.</p><pre><code>async query(params: QueryVectorParams<any>): Promise<QueryResult[]> {
    const { indexName, queryVector, topK = 10, filter, includeVector = false } = params;

    try {
        const knnQuery: any = {
            field: 'vector',
            query_vector: queryVector,
            k: topK,
            num_candidates: Math.max(topK * 10, 100), // Search more candidates for better recall
        };

        // Apply metadata filters if provided
        if (filter) {
            knnQuery.filter = this.buildElasticFilter(filter);
        }

        const sourceFields = ['metadata'];
        if (includeVector) {
            sourceFields.push('vector');
        }

        const response = await this.client.search({
            index: indexName,
            knn: knnQuery,
            size: topK,
            _source: sourceFields,
        });

        const results = response.hits.hits.map((hit: any) => ({
            id: hit._id,
            score: hit._score || 0,
            metadata: hit._source?.metadata || {},
            vector: includeVector ? hit._source?.vector : undefined,
        }));

        this.logger?.debug(`Query returned ${results.length} results from "${indexName}"`);
        return results;
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        this.logger?.error(`Failed to query vectors from "${indexName}": ${errorMessage}`);
        throw new Error(`Failed to query vectors from "${indexName}": ${errorMessage}`);
    }
}</code></pre><p>Bajo el capó:</p><ul><li>Ejecuta una consulta <a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN</a> (k-vecinos más cercanos) usando la API <code>knn</code> en Elasticsearch.</li><li>Recupera los vectores top-K similares al vector de consulta de entrada.</li><li>Opcionalmente, aplica filtros de metadatos para reducir resultados (por ejemplo, buscar solo dentro de una categoría o rango de tiempo específico)</li><li>Devuelve resultados estructurados que incluyen el ID del documento, el puntaje de similitud y los metadatos almacenados.</li></ul><h2>Creación del agente del conocimiento</h2><p>Ahora que vimos la conexión entre Mastra y Elasticsearch a través de la integración <code>ElasticVector</code> , creemos el propio Knowledge Agent.</p><p>Dentro de la carpeta <code>agents</code>, crea un archivo llamado <code>knowledge-agent.ts</code>. Podemos empezar conectando nuestras variables de entorno e inicializando el cliente Elasticsearch.</p><pre><code>import { Agent } from '@mastra/core/agent';
import { Memory } from '@mastra/memory';
import { openai } from '@ai-sdk/openai';
import { Client } from '@elastic/elasticsearch';
import { ElasticVector } from '../stores/elastic-store';
import dotenv from "dotenv";

dotenv.config();

const ELASTICSEARCH_ENDPOINT = process.env.ELASTICSEARCH_ENDPOINT;
const ELASTICSEARCH_API_KEY = process.env.ELASTICSEARCH_API_KEY;

//Error check for undefined credentials
if (!ELASTICSEARCH_ENDPOINT || !ELASTICSEARCH_API_KEY) {
  throw new Error('Missing Elasticsearch credentials');
}

//Check to see if a connection can be established
const testClient = new Client({
  node: ELASTICSEARCH_ENDPOINT,
  auth: { 
    apiKey: ELASTICSEARCH_API_KEY 
  },
});

try {
  await testClient.ping();
  console.log('Connected to Elasticsearch successfully');
} catch (error: unknown) {
  if (error instanceof Error) {
    console.error('Failed to connect to Elasticsearch:', error.message);
  } else {
    console.error('Failed to connect to Elasticsearch:', error);
  }
  process.exit(1);
}
//Initialize the Elasticsearch vector store
const vectorStore = new ElasticVector({
  node: ELASTICSEARCH_ENDPOINT,
  auth: {
    apiKey: ELASTICSEARCH_API_KEY,
  },
//Optional: Explicitly set to true if using Elasticsearch Serverless to skip auto-detection and improve startup time
//isServerless: true,
});</code></pre><p>Aquí, nosotros:</p><ul><li>Usa <code>dotenv</code> para cargar nuestras variables desde nuestro archivo <code>.env</code> .</li><li>Comprueba si las credenciales de Elasticsearch se están inyectando correctamente y podemos establecer una conexión exitosa con el cliente.</li><li>Pasa el endpoint de Elasticsearch y la clave API al constructor <code>ElasticVector</code> para crear una instancia de nuestro almacén vectorial que definimos antes.</li><li>Opcionalmente, especifica <code>isServerless: true</code> si usas Elasticsearch Serverless. Esto omite el paso de detección automática y mejora el tiempo de arranque. Si se omite, el adaptador detectará automáticamente el tipo de despliegue en el primer uso.</li></ul><p>A continuación, podemos definir el agente usando la clase <code>Agent</code> de Mastra.</p><pre><code>export const knowledgeAgent = new Agent({
    name: 'KnowledgeAgent',
    instructions: 'You are a helpful knowledge assistant.',
    model: openai('gpt-4o'),
    memory: new Memory({

        vector: vectorStore,

        //embedder used to create embeddings for each message
        embedder: 'openai/text-embedding-3-small',

        //set semantic recall options
        options: {
            semanticRecall: {
                topK: 3, // retrieve 3 similar messages
                messageRange: 2, // include 2 messages before/after each match
                scope: 'resource',
            },
        },
    }),
});</code></pre><p>Los campos que podemos definir son:</p><ul><li><code>name</code> y <code>instructions</code>: Darle una identidad y función primaria.</li><li><code>model</code>: Estamos usando la <code>gpt-4o</code> de OpenAI a través del paquete <code>@ai-sdk/openai</code> .</li><li><code>memory</code>:<ul><li><code>vector</code>: Apunta a nuestra tienda Elasticsearch, así que los embeddings se almacenan y recuperan desde allí.</li><li><code>embedder</code>: Qué modelo usar para generar incrustaciones</li><li><code>semanticRecall</code> Las opciones deciden cómo funciona la retirada:<ul><li><code>topK</code>: Cuántos mensajes semánticamente similares recuperar.</li><li><code>messageRange</code>: Cuánto de la conversación incluir en cada partido.</li><li><code>scope</code>: Define el límite de la memoria.</li></ul></li></ul></li></ul><p>Casi termino. Solo tenemos que agregar este agente recién creado a nuestra configuración de Mestra. En el archivo llamado <a href="http://index.ts/"><code>index.ts</code></a>, importa el agente de conocimiento e insértalo en el campo <code>agents</code> .</p><pre><code>export const mastra = new Mastra({
  agents: { knowledgeAgent },
  storage: new LibSQLStore({
    // stores observability, scores, ... into memory storage, if it needs to persist, change to file:../mastra.db
    url: ":memory:",
  }),
  logger: new PinoLogger({
    name: 'Mastra',
    level: 'info',
  }),
  telemetry: {
    // Telemetry is deprecated and will be removed in the Nov 4th release
    enabled: false, 
  },
  observability: {
    // Enables DefaultExporter and CloudExporter for AI tracing
    default: { enabled: true }, 
  },
});</code></pre><p>Los otros campos incluyen:</p><ul><li><code>storage</code>: Este es el almacén interno de datos de Mamra para historial de ejecuciones, métricas de observabilidad, puntajes y cachés. Para más información sobre el almacenamiento de mastras, visita <a href="https://mastra.ai/docs/server-db/storage">aquí</a>.</li><li><code>logger</code>: Mastra emplea <a href="https://github.com/pinojs/pino">Pino</a>, que es un registrador JSON estructurado y ligero. Captura eventos como inicios y atajada de agentes, llamadas y resultados de herramientas, errores y tiempos de respuesta de los LLM.</li><li><code>observability</code>: Controla el rastreo de IA y la visibilidad de ejecución de los agentes. Sigue lo siguiente:<ul><li>Inicio/final de cada paso de razonamiento.</li><li>Qué modelo o herramienta se empleó.</li><li>Entradas y salidas.</li><li>Puntajes y evaluaciones</li></ul></li></ul><h3>Probando al agente con Mastra Studio</h3><p>¡Felicidades! Si llegaste hasta aquí, estás listo para ejecutar este agente y probar sus capacidades semánticas de recuperación. Por suerte, Mastra ofrece una interfaz de chat integrada para que no tengamos que crear la nuestra.</p><p>Para iniciar el servidor de desarrollo de Mestra, abre un terminal y ejecuta el siguiente comando:</p><pre><code>npm run dev</code></pre><p>Tras el empaquetado y el arranque inicial del servidor, debería proporcionarte una dirección del Playground.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8b045f70008d26aec4d2e6b59d61085555b9c5b2-686x116.png" style="width: 100%;" /><p>Pega esta dirección en tu navegador y te recibirás con Mastra Studio.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/69bc80fe8486edd9e0cf91d87b39f465aeb23111-1600x438.png" style="width: 100%;" /><p>Selecciona la opción de <code>knowledgeAgent</code> y charla sin parar.</p><p>Para una prueba rápida y ver si todo está correctamente cableado, dale información como: &quot;El equipo anunció que el rendimiento de ventas en octubre subió un 12%, impulsado principalmente por renovaciones empresariales. El siguiente paso es ampliar su alcance a clientes de gama media.&quot; Después, inicia un nuevo chat y haz una pregunta como: &quot;¿En qué segmento de clientes dijimos que debemos centrarnos a continuación?&quot; El agente de conocimiento debería ser capaz de recordar la información que le diste en el primer chat. Deberías ver una respuesta como:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/da8ebddad89874023ed440a8f1ad2cb04ed043f4-1070x288.png" style="width: 100%;" /><p>Ver una respuesta así significa que el agente almacenó con éxito nuestro mensaje anterior como incrustaciones en Elasticsearch y lo recuperó después usando búsqueda vectorial.</p><h3>Inspección del almacenamiento de memoria a largo plazo del agente</h3><p>Ve a la pestaña <code>memory</code> en la configuración de tu agente en Mastra Studio. Esto te permite ver lo que tu agente aprendió con el tiempo. Cada mensaje, respuesta e interacción que se incrusta y almacena en Elasticsearch pasa a formar parte de esta memoria a largo plazo. Puedes buscar semánticamente en interacciones pasadas para encontrar rápidamente información o contexto recordado que el agente aprendió antes. Este es esencialmente el mismo mecanismo que emplea el agente durante la recuperación semántica, pero aquí puedes inspeccionarlo directamente. En nuestro ejemplo a continuación, buscamos el término &quot;ventas&quot; y recibimos cada interacción que incluyera algo relacionado con las ventas.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3decaa0c332d288c5ae0b11c25f592c7d50c2f0f-1104x1320.png" style="width: 100%;" /><h2>Conclusión</h2><p>Al conectar Mastra y Elasticsearch, podemos dar memoria a nuestros agentes, que es una capa clave en la ingeniería de contexto. Con la memoria semántica, los agentes pueden construir contexto con el tiempo, basando sus respuestas en lo que aprendieron. Eso significa interacciones más precisas, fiables y naturales.</p><p>Esta integración temprana es solo el punto de partida. El mismo patrón aquí puede permitir que los agentes de soporte recuerden tiquetes anteriores, bots internos que recuperen la documentación relevante o asistentes de IA que puedan recuperar detalles de los clientes en medio de una conversación. También estamos trabajando en una integración oficial de Mestra, haciendo que esta pareja sea aún más fluida en un futuro próximo.</p><p>Estamos deseando ver qué construyes a continuación. Pruébalo, explora <a href="https://mastra.ai/">Mastra</a> y sus funciones de memoria, y siéntete libre de compartir lo que descubras con la comunidad.</p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/es/blog/multimodal-search-siglip-2-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/multimodal-search-siglip-2-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Navneet Kumar]]></dc:creator>
            <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ffcf93358a7c5dadcea82faf3de460bf060d003c-1600x1200.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>¿Alguna vez quisiste buscar en tu álbum de fotos por significado? Prueba con preguntas como &quot;muéstrame mis fotos donde llevo una chaqueta azul y estoy sentado en un banco&quot;, &quot;muéstrame fotos del Monte Everest&quot; o &quot;sake y sushi&quot;. 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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0ec6bbb45013846b59ee00d2bf73ee2182ee7392-1920x1080.gif" style="width: 100%;" /><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.<br/>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>Encuentra fotos de un pico montañoso por nombre</li><li>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</li><li>Haz que las consultas conceptuales funcionen (<em>persona</em>, <em>río</em>, <em>banderas de oración</em>, <em>etc.)</em></li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e9d0d720a9b565d5b749bdc915068852d4f157ad-1200x1600.png" style="width: 100%;" /><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 (&quot;Ama Dablam&quot;) 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 &quot;encontrar a los vecinos más cercanos&quot;.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/20e6f8b7d4fa48414f407ec200adbe00ee28d517-1536x1024.png" style="width: 100%;" /><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><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>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Campo</td><td style="border: 1px solid #ccc; padding: 4px;">Tipo</td><td style="border: 1px solid #ccc; padding: 4px;">Ejemplo</td><td style="border: 1px solid #ccc; padding: 4px;">Propósito/Notas</td><td style="border: 1px solid #ccc; padding: 4px;">Vector/Indexación</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">identificación</td><td style="border: 1px solid #ccc; padding: 4px;">palabra clave</td><td style="border: 1px solid #ccc; padding: 4px;">ama-dablam</td><td style="border: 1px solid #ccc; padding: 4px;">Slug/id estable</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Nombres</td><td style="border: 1px solid #ccc; padding: 4px;">Subcampo texto + palabra clave</td><td style="border: 1px solid #ccc; padding: 4px;">["Ama Dablam","Amadablam"]</td><td style="border: 1px solid #ccc; padding: 4px;">Alias / nombres multilingües; names.raw para filtros exactos</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Latlon</td><td style="border: 1px solid #ccc; padding: 4px;">geo_point</td><td style="border: 1px solid #ccc; padding: 4px;">{"lat":27.8617,"lon":86.8614}</td><td style="border: 1px solid #ccc; padding: 4px;">Coordenadas GPS de pico como combinación de latitud/longitud (opcional)</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">elev_m</td><td style="border: 1px solid #ccc; padding: 4px;">entero</td><td style="border: 1px solid #ccc; padding: 4px;">6812</td><td style="border: 1px solid #ccc; padding: 4px;">Elevación (opcional)</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">text_embed</td><td style="border: 1px solid #ccc; padding: 4px;">dense_vector</td><td style="border: 1px solid #ccc; padding: 4px;">768</td><td style="border: 1px solid #ccc; padding: 4px;">Prototipo mezclado (prompts y, opcionalmente, 1–3 imágenes de referencia) para este pico</td><td style="border: 1px solid #ccc; padding: 4px;">Index:True, Similitud:"Coseno", index_options:{type:"hnsw", m:16, ef_construction:128}</td>
                    </tr>
              </tbody>
            </table>
          <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 &quot;¿Qué montaña es esta?&quot; 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>Crea un prototipo de texto usando:<ul><li>Nombres de los picos</li><li><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:<ul><li>&quot;una foto natural de la cima de la montaña {name} en el Himalaya, Nepal&quot;</li><li>&quot;{name} pico emblemático en la región del Khumbu, paisaje alpino&quot;</li><li>&quot;{name} cima de montaña, nieve, cresta rocosa&quot;</li></ul></li><li><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 &quot;pintura, ilustración, afiche, mapa, logo&quot; para inclinarnos hacia fotos reales.</li></ul></li><li>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.</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><pre><code>def l2norm(v: np.ndarray) -> 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,
) -> 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 (<= 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 > 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]</code></pre><p>Documento de ejemplo de <code>peaks_catalog</code> índice:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bc05fbd0c4f8d721d5170c28a3884a9eda80bb7d-1210x1132.png" style="width: 100%;" /><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>Camino relativo a la foto del álbum. Esto puede usar para ver la imagen correspondiente o cargarla en la interfaz de búsqueda.</li><li>GPS e información horaria de la imagen.</li><li>Vector denso para codificación de imágenes generado por SigLIP-2.</li><li><code>predicted_peaks</code> Eso nos permite filtrar por nombre de pico.<br/><br/><strong>Mapeo de índices</strong></li></ul>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Campo</td><td style="border: 1px solid #ccc; padding: 4px;">Tipo</td><td style="border: 1px solid #ccc; padding: 4px;">Ejemplo</td><td style="border: 1px solid #ccc; padding: 4px;">Propósito/Notas</td><td style="border: 1px solid #ccc; padding: 4px;">Vector / Indexación</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">camino</td><td style="border: 1px solid #ccc; padding: 4px;">palabra clave</td><td style="border: 1px solid #ccc; padding: 4px;">datos/imágenes/IMG_1234.HEIC</td><td style="border: 1px solid #ccc; padding: 4px;">Cómo se abre la interfaz en miniatura/imagen completa</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">clip_image</td><td style="border: 1px solid #ccc; padding: 4px;">dense_vector</td><td style="border: 1px solid #ccc; padding: 4px;">768</td><td style="border: 1px solid #ccc; padding: 4px;">Incrustación de imágenes SigLIP-2</td><td style="border: 1px solid #ccc; padding: 4px;">Index:True, Similitud:"Coseno", index_options:{type:"hnsw", m:16, ef_construction:128}</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">predicted_peaks</td><td style="border: 1px solid #ccc; padding: 4px;">palabra clave</td><td style="border: 1px solid #ccc; padding: 4px;">["ama-dablam", "pumori"]</td><td style="border: 1px solid #ccc; padding: 4px;">Top-K suposiciones en el tiempo del índice (filtro UX barato / facet)</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">GPS</td><td style="border: 1px solid #ccc; padding: 4px;">geo_point</td><td style="border: 1px solid #ccc; padding: 4px;">{"lat":27.96,"lon":86.83}</td><td style="border: 1px solid #ccc; padding: 4px;">Activa los filtros geográficos</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">shot_time</td><td style="border: 1px solid #ccc; padding: 4px;">date</td><td style="border: 1px solid #ccc; padding: 4px;">2023-10-18T09:41:00Z</td><td style="border: 1px solid #ccc; padding: 4px;">Tiempo de captura: ordenar/filtrar</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
              </tbody>
            </table>
          <p><strong>Estrategia de indexación para el índice de fotos: </strong>Para cada foto del álbum, hacemos lo siguiente:<br/>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><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.</li><li><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.</li><li>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.</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><pre><code>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",
) -> 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) >= 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")</code></pre><p>Documento de ejemplo del índice de fotos:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1dc1406ac2a97440b6804838795b3c2205c4c6b2-1080x1234.png" style="width: 100%;" /><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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1c75d0ed0ce53d28a94bf2f47a354e25581d2baf-1600x1402.png" style="width: 100%;" /><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 &quot;banderas de oración&quot;) 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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b5fac28ffdbedb820505365ca07df125cd01b939-946x370.png" style="width: 100%;" /><p><strong>Consulta de Elasticsearch con filtro geográfico:</strong></p><pre><code>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"
       }
     }
}</code></pre><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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3a3d5635bee7a222b95529dd7f9fbee016381610-1226x550.png" style="width: 100%;" /><p><strong>Consulta Elasticsearch:</strong></p><p>Paso 1: Encontrar los nombres de picos que coincidan</p><pre><code>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...
         ]
       }
     }
   ]
 }
}</code></pre><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><pre><code>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"
       }
     }
}</code></pre><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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/dca540cbf5279e6d6102c5a0c0351ddd4ac91cda-1600x1112.png" style="width: 100%;" /><p><strong>Buscar por texto</strong>: Encuentra los picos coincidentes en el álbum a partir del texto</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b1e8618db746cd49ea4962d3dc73031387b975dd-1600x1166.png" style="width: 100%;" /><h2>Conclusión</h2><p>¿Qué empezó como <em>, ¿podemos simplemente ver las </em>imágenes<em><strong>de Ama Dablam</strong>?</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><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>.</li><li><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.</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><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></li><li><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></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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d324d1e1472f1bfbd8f25747f57bdeeb9c7f16b2-1600x1200.png" style="width: 100%;" />]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mejora de la relevancia del modelo de incrustación multilingüe con reclasificación híbrida en búsquedas]]></title>
            <description><![CDATA[Aprende cómo mejorar la relevancia de los resultados de búsqueda del modelo de incrustación multilingüe E5 usando el reclasificador y la búsqueda híbrida de Cohere en Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/multilingual-embedding-model-hybrid-search-reranking</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/multilingual-embedding-model-hybrid-search-reranking</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Quynh Nguyen]]></dc:creator>
            <pubDate>Mon, 03 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d341b04acecc8eeec321f5404e1643447ecc8526-720x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>Introducción</h2><p>En la <a href="https://www.elastic.co/search-labs/blog/multilingual-embedding-model-deployment-elasticsearch">última parte de este serial</a>, explicamos cómo desplegar el modelo E5 preentrenado de Elastic (así como otros modelos multilingües de incrustación de texto de Hugging Face) y nos adentramos en la generación de incrustaciones vectoriales densas a partir de tus datos textuales usando Elasticsearch y Kibana. En este blog, analizaremos los resultados de estas incrustaciones y destacaremos los beneficios significativos de aprovechar un modelo multilingüe.</p><p>Ahora que tenemos nuestro índice <code>coco_multilingual</code>, realizar la búsqueda nos dará documentos en varios idiomas, con el campo &quot;en&quot; para que podamos consultar:</p><pre><code># GET coco_multilingual/_search
    {
       "_index": "coco_multilingual",
       "_id": "WAiXQJYBgf6odR9bLohZ",
       "_score": 1,
       "_source": {
         "description": "Ein Parkmeßgerät auf einer Straße mit Autos",
         "en": "A row of parked cars sitting next to parking meters.",
         "language": "de",
         "vector_description": {...}
       }
     },
     . . .</code></pre><h2>Realizando una búsqueda en inglés</h2><p>Intentemos hacer la búsqueda en inglés y ver qué tal va:</p><pre><code>GET coco_multi/_search
{
"size": 10,
"_source": [
  "description", "language", "en"
],
"knn": {
  "field": "vector_description.predicted_value",
  "k": 10,
  "num_candidates": 100,
  "query_vector_builder": {
    "text_embedding": {
      "model_id": ".multilingual-e5-small_linux-x86_64_search",
      "model_text": "query: kitty"
    }
  }
}
}</code></pre><pre><code>{
       "_index": "coco_multi",
       "_id": "JQiXQJYBgf6odR9b6Yz0",
       "_score": 0.9334303,
       "_source": {
         "description": "Eine Katze, die in einem kleinen, gepackten Koffer sitzt.",
         "en": "A brown and white cat is in a suitcase.",
         "language": "de"
       }
     },
      {
       "_index": "coco_multi",
       "_id": "3AiXQJYBgf6odR9bFod6",
       "_score": 0.9281012,
       "_source": {
         "description": "Una bambina che tiene un gattino vicino a una recinzione blu.",
         "en": "A little girl holding a kitten next to a blue fence.",
         "language": "it"
       }
     },
     . . .</code></pre><p>Aquí, aunque la consulta parezca engañosamente simple, estamos buscando las incrustaciones numéricas de la palabra &#x27;kitty&#x27; en todos los documentos y todos los idiomas que aparecen debajo del capó. Y como realizamos búsqueda vectorial, podemos buscar semánticamente todas las palabras que puedan estar relacionadas con &#x27;kitty&#x27;: &quot;cat&quot;, &quot;kitten&quot;, &quot;felino&quot;, &quot;gatto&quot; (italiano), &quot;meo&quot; (vietnamita), 고양이 (coreano), 猫 (chino), etc. Como resultado, aunque mi consulta esté en inglés, también podemos buscar contenido en todos los demás idiomas. Por ejemplo, buscar un gatito<code>ying on something</code> también devuelve documentos en italiano, neerlandés o vietnamita. ¡Eso sí que es eficiencia!</p><h2>Realizar una búsqueda de contenido en otros idiomas</h2><pre><code>GET coco_multi/_search
{  
 "size": 100,
 "_source": [
   "description", "language", "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 50,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: kitty lying on something"
     }
   }
 }
}</code></pre><pre><code>{
 "description": "A black kitten lays on her side beside remote controls.",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "en"
},
{
 "description": "un gattino sdraiato su un letto accanto ad alcuni telefoni ",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "it"
},
{
 "description": "eine Katze legt sich auf ein ausgestopftes Tier",
 "en": "a cat lays down on a stuffed animal",
 "language": "de"
},
{
 "description": "Một chú mèo con màu đen nằm nghiêng bên cạnh điều khiển từ xa.",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "vi"
}
. . .</code></pre><p>De manera similar, realizar una búsqueda por palabra clave de &quot;cat&quot; en coreano (&quot;고양이&quot;) también devolverá resultados significativos. Lo espectacular aquí es que ni siquiera tenemos documentos en coreano en este índice.</p><pre><code>GET coco_multi/_search
{
 "size": 100,
 "_source": [
   "description", "language", "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 50,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: 고양이"
     }
   }
 }
}</code></pre><pre><code> {
       {
         "description": "eine Katze legt sich auf ein ausgestopftes Tier",
         "en": "a cat lays down on a stuffed animal",
         "language": "de"
       }
     },
     {
       {
         "description": "Một con chó và con mèo đang ngủ với nhau trên một chiếc ghế dài màu cam.",
         "en": "A dog and cat lying  together on an orange couch. ",
         "language": "vi"
       }
     },</code></pre><p>Esto funciona porque el modelo de incrustación representa el significado en un espacio semántico compartido, permitiendo la recuperación de imágenes relevantes incluso con una consulta en un idioma diferente al de los subtítulos indexados.</p><h2>Aumento de resultados de búsqueda relevantes con búsqueda híbrida y reposicionamiento</h2><p>Estamos contentos de que los resultados relevantes llegaron como se esperaba. Pero, en el mundo real, por ejemplo en comercio electrónico o en aplicaciones RAG que necesitan reducir a los 5-10 resultados más aplicables, podemos usar un modelo de reclasificación para priorizar los resultados más relevantes.</p><p>Aquí, realizar una consulta que pregunte &quot;¿de qué color es el gato?&quot; en vietnamita dará muchos resultados, pero el top 1 o 2 puede no ser el más relevante.</p><pre><code>GET coco_multi/_search
{
 "size": 20,
 "_source": [
   "description",
   "language",
   "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 20,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: con mèo màu gì?"
     }
   }
 }
}</code></pre><p>Todos los resultados mencionan gato, o algún tipo de color:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/33e1e887dbbdd1066cfedc7375f5e3b46538529e-859x847.png" style="width: 100%;" /><p>¡Así que vamos a mejorar eso! Integremos el modelo multilingüe de reclasificación de <a href="https://cohere.com/blog/rerank-3pt5">Cohere</a>para mejorar el razonamiento correspondiente a nuestra pregunta.</p><pre><code>PUT _inference/rerank/cohere_rerank
{
 "service": "cohere",
 "service_settings": {
   "api_key": "your_api_key",
   "model_id": "rerank-v3.5"
 },
 "task_settings": {
   "top_n": 10,
   "return_documents": true
 }
}


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


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


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


index_name = "coco"


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


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


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


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


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


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


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


print("Indexing complete!")</code></pre><p>Una vez indexados los datos, deberías ver algo similar a lo siguiente:</p><p><code>Successfully bulk indexed 4840 documents</code></p><p><code>Indexing complete!</code></p><h3>Paso 3: Desplegar el modelo capacitado con E5</h3><p>En Kibana, accede a la página de Stack Management &gt; <strong>Trained Models</strong> y haz clic <strong>en Desplegar</strong> para el .multilingual-e5-small_linux-x86_64 opción. Este modelo E5 es un pequeño multilingüe optimizado para linux-x86_64, que podemos usar de fábrica. Al hacer clic en &#x27;Desplegar&#x27; aparecerá una pantalla donde puedes ajustar la configuración de despliegue o las configuraciones de los vCPU. Para simplificar, optaremos por las opciones predeterminadas, con recursos adaptativos seleccionados, que escalarán automáticamente nuestro despliegue según el uso.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/95cd8f352425d1db2d04b00c3c88d1e71d1ef19a-1600x440.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1599d99949dda8267acc58f400a403a3af5373ef-1600x655.png" style="width: 100%;" /><p>Opcionalmente, si quieres usar otros modelos de incrustación de texto, puedes hacerlo. Por ejemplo, para usar el BGE-M3, puedes usar <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/eland/machine-learning#ml-nlp-pytorch">el cliente Eland Python de Elastic</a> para importar el modelo desde HuggingFace.</p><pre><code>export MODEL_ID="bge-m3"
export HUB_MODEL_ID="BAAI/bge-m3"
export CLOUD_ID={{CLOUD_ID}}
export ES_API_KEY={{API_KEY}}
docker run -it --rm docker.elastic.co/eland/eland \
eland_import_hub_model --cloud-id $CLOUD_ID --es-api-key $ES_API_KEY --hub-model-id $HUB_MODEL_ID --es-model-id $MODEL_ID --task-type text_embedding --start</code></pre><p>Luego, ve a la página de Modelos Capacitados para desplegar el modelo importado con las configuraciones deseadas.</p><h3>Paso 4: Vectorizar o crear incrustaciones para los datos originales con el modelo desplegado</h3><p>Para crear los embeddings, primero necesitamos crear un pipeline de ingesta que nos permita tomar el texto y pasarlo por el modelo de embedding de texto de inferencia. Puedes hacerlo en la interfaz de usuario de Kibana o a través de la API de Elasticsearch.</p><p><strong>Para hacerlo a través de la interfaz Kibana</strong>, tras desplegar el Modelo Capacitado, haz clic en el botón <strong>Test </strong>. Esto te dará la posibilidad de probar y previsualizar los embeddings generados. Crea una nueva vista de datos para el<strong> </strong>índice de <code>coco</code>, configura la vista de datos en la vista de datos coco recién creada y pon el campo en <code>description</code> porque ese es el campo para el que queremos generar incrustaciones.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cfe189e13dc118d325e7fb90bdace0c912e29f51-1088x1600.png" style="width: 100%;" /><p>¡Eso funciona genial! Ahora podemos proceder a crear la pipeline de ingest, reindexar nuestros documentos originales, pasarlos por la pipeline y crear un nuevo índice con los embeddings. Puedes conseguirlo haciendo clic <strong>en Crear pipeline</strong>, lo que te guiará durante el proceso de creación de pipeline, con procesadores auto-repoblados necesarios para ayudarte a crear los embeddings.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1e043c1c3279b66fbdf19c06b41e76e613043998-1600x1126.png" style="width: 100%;" /><p>El asistente también puede rellenar automáticamente los procesadores necesarios para gestionar fallos mientras se ingieren y procesan los datos.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8804d31b4f869078e3b2245040bbb0ab1720a94a-1600x1084.png" style="width: 100%;" /><p>Ahora creemos la canalización de ingest. Voy a nombrar el oleoducto <code>coco_e5</code>. Una vez que la tubería se crea correctamente, puedes usarla inmediatamente para generar las incrustaciones reindexando los datos originales indexados a un nuevo índice en el asistente. Haz clic <strong>en Reindexar </strong>para iniciar el proceso.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e34b1b18f5b24420d4581fe4d657c569926c2023-1600x1126.png" style="width: 100%;" /><h2>Para configuraciones más complejas, podemos usar la API de Elasticsearch.</h2><p>Para algunos modelos, debido a la forma en que se capacitaron, puede que necesitemos anteponer o agregar ciertos textos a la entrada real antes de generar los embeddings; de lo contrario, veremos una degradación del rendimiento.</p><p>Por ejemplo, con el e5, el modelo espera que el texto de entrada siga a &quot;passage: {content of passage}&quot;. Empleemos los pipelines de ingest para lograrlo: crearemos un nuevo <strong>pipeline de ingest vectorize_descriptions</strong>. En esta canalización, crearemos un nuevo campo de <code>temp_desc</code> temporal, antepondremos &quot;passage: &quot; al texto <code>description</code> , pasaremos <code>temp_desc</code> por el modelo para generar incrustaciones de texto y luego eliminaremos el <code>temp_desc</code>.</p><pre><code>PUT _ingest/pipeline/vectorize_descriptions
{
"description": "Pipeline to run the descriptions text_field through our inference text embedding model",
"processors": [
 {
   "set": {
     "field": "temp_desc",
     "value": "passage: {{description}}"
   }
 },
 {
   "inference": {     
"field_map": {
       "temp_desc": "text_field"
     },
     "model_id": ".multilingual-e5-small_linux-x86_64_search",
     "target_field": "vector_description"
   }
 },
 {
   "remove": {
     "field": "temp_desc"
   }
 }
]
}</code></pre><p>Además, podríamos querer especificar qué <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-quantization">tipo de cuantización</a> queremos usar para el vector generado. Por defecto, Elasticsearch usa <code>int8_hnsw</code>, pero aquí quiero <a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Better Binary Quantization</a> (o <code>bqq_hnsw</code>), que reduce cada dimensión a una precisión de un solo bit. Esto reduce la huella de memoria en un 96% (o 32 veces) a un costo mayor en la precisión. Opto por este tipo de cuantización porque sé que usaré un reclasificador más adelante para mejorar la pérdida de precisión.</p><p>Para ello, crearemos un nuevo índice llamado <strong>coco_multi</strong> y especificaremos los mapeos. La magia aquí está en el campo vector_description, donde especificamos que el tipo del <strong>index_options</strong>debe <strong>ser bbq_hnsw</strong>.</p><pre><code>PUT coco_multi
{
 "mappings": {
   "properties": {
     "description": {
       "type": "text"
     },
     "en": {
       "type": "text"
     },
     "image_url": {
       "type": "keyword"
     },
     "language": {
       "type": "keyword"
     },
     "vector_description.predicted_value": {
       "type": "dense_vector",
       "dims": 384,
       "index": "true",
       "similarity": "cosine",
       "index_options": {
         "type": "bbq_hnsw" 
       }
     }
   }
 }
}</code></pre><p>Ahora, podemos reindexar los documentos originales a un nuevo índice, con nuestra pipeline de ingesta que &quot;vectorizará&quot; o creará incrustaciones para el campo de descripciones.</p><pre><code>POST _reindex?wait_for_completion=false
{
 "source": {
   "index": "coco"
 },
 "dest": {
   "index": "coco_multilingual",
   "pipeline": "vectorize_descriptions"
 }
}</code></pre><p>¡Y eso es todo! Desplegamos con éxito un modelo multilingüe con Elasticsearch y Kibana y aprendido paso a paso cómo crear las incrustaciones vectoriales con tus datos con Elastic, ya sea a través de la interfaz de usuario de Kibana o con la API de Elasticsearch. En la segunda parte de este serial, exploraremos los resultados y las particularidades del uso de un modelo multilingüe. Mientras tanto, puedes <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">crear tu propio clúster en la nube</a> para probar <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5">la búsqueda semántica multilingüe usando nuestro modelo E5 estándar</a> en el idioma y conjunto de datos que elijas.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Servidor Elastic MCP: Expone las herramientas de Agent Builder a cualquier agente de IA]]></title>
            <description><![CDATA[Descubre cómo emplear el servidor Elastic MCP integrado en Agent Builder para ampliar de forma segura cualquier agente de IA con acceso a tus datos privados y herramientas personalizadas.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elastic-mcp-server-agent-builder-tools</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elastic-mcp-server-agent-builder-tools</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jedr Blaszyk, Joe McElroy]]></dc:creator>
            <pubDate>Mon, 20 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ef5153551a1d14833c7f512fede554d1dfb31553-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elastic Agent Builder es una plataforma para crear herramientas y agentes profundamente integrados con tus propios datos en Elasticsearch. Por ejemplo, puedes crear herramientas que realicen búsqueda semántica sobre documentos internos, analicen registros de observabilidad o consulten alertas de seguridad.</p><p>Pero la verdadera magia ocurre cuando puedes llevar estas herramientas personalizadas y conscientes de los datos a los entornos donde pasas la mayor parte del tiempo. ¿Y si tu agente editor de código pudiera acceder de forma segura a la base de conocimiento privada de tu organización?</p><p>Ahí es donde entra el <strong>Protocolo de Contexto del Modelo (MCP).</strong> Elastic Agent Builder viene con un servidor MCP integrado que proporciona acceso a las herramientas de la plataforma.</p><h2>¿Por qué usar el servidor MCP de Elastic Agent Builder?</h2><p>Los agentes de IA son increíblemente poderosos, pero su conocimiento suele limitar a los datos con los que fueron capacitados y a la información que pueden buscar activamente en Internet público. No conocen los documentos internos de diseño de tu compañía, los libros de despliegue específicos de tu equipo ni la estructura única de tus registros de aplicaciones.</p><p>El reto es darle a tu asistente de IA el contexto especializado que necesita. Este es precisamente el problema que MCP está diseñado para resolver. <strong>MCP es un estándar abierto que permite a un modelo o agente de IA descubrir y emplear herramientas externas.</strong></p><p>Para hacerlo posible, el Constructor de Agentes Elastic expone de forma nativa tus herramientas personalizadas a través de un servidor MCP integrado. Esto significa que puedes conectar fácilmente cualquier cliente compatible con MCP, como <strong>Cursor</strong>, <strong>VS Code</strong> o <strong>Claude Desktop</strong>, con las herramientas especializadas y conscientes de los datos que desarrollaste con Elastic Agent Builder.</p><h2>Cuándo usar MCP (y cuándo no)</h2><p>Elastic Agent Builder incluye varios protocolos para soportar diferentes patrones de integración. Elegir el adecuado es clave para construir flujos de trabajo efectivos en IA.</p><ul><li><strong>Usa <a href="https://www.elastic.co/docs/solutions/search/agent-builder/mcp-server">MCP</a></strong> para complementar tu agente de IA (como en <strong>Cursor</strong> o <strong>VS Code</strong>) con herramientas especializadas. Es el enfoque de &quot;trae tus propias herramientas&quot;, mejorando el asistente que ya usas con acceso seguro a tus datos privados. Solo las herramientas se exponen a través del servidor MCP — los agentes de Elastic son independientes de ese.</li><li><strong>Emplea el <a href="https://www.elastic.co/docs/solutions/search/agent-builder/a2a-server">Protocolo A2A</a></strong> para permitir que tu Agente Elastic personalizado completo colabore con otros agentes autónomos (como en <a href="https://www.elastic.co/search-labs/blog/a2a-protocol-elastic-agent-builder-gemini-enterprise"><strong>Gemini Enterprise de Google</strong></a>). Esto es para la delegación de agente a agente, donde cada agente actúa como un par para resolver un problema.</li><li><strong>Emplea las <a href="https://www.elastic.co/docs/solutions/search/agent-builder/kibana-api">APIs de Agent Builder</a></strong> para un control programático completo al construir una aplicación personalizada desde cero.</li></ul><p>Para un desarrollador que busca obtener respuestas de su documentación interna sin salir de su IDE, MCP es la opción perfecta.</p><h2>Ejemplo: tus herramientas personalizadas en Cursor con el servidor MCP de Agent Builder</h2><p>Vamos a repasar un ejemplo práctico que uso a diario. Primero, rastreé e indexé nuestra documentación interna de ingeniería en un índice de Elasticsearch llamado <code>elastic-dev-docs</code>. Aunque podríamos usar las herramientas genéricas integradas disponibles en Agent Builder, crearemos nuestra propia herramienta personalizada para consultar esta base de conocimiento específica.</p><p>La razón para construir una herramienta personalizada es sencilla: <strong>control y precisión</strong>. Este enfoque nos da la capacidad de ejecutar una consulta rápida y semántica directamente contra nuestro índice de <code>elastic-dev-docs</code> . Tenemos control total sobre qué índice se dirige y cómo se recuperan los datos.</p><p>Ahora, aquí tienes cómo podemos usar esta base de conocimiento personalizada en un editor de código impulsado por IA, como Cursor.</p><h3>Paso 1: Crea una herramienta de base de conocimiento personalizada en Agent Builder</h3><p>Primero, crea una nueva herramienta en Agent Builder. Una descripción clara y específica de la herramienta es importante porque es como cualquier agente de IA, ya sea el Agente Elástico interno o una herramienta externa como Cursor conectar mediante MCP, descubre y selecciona tu herramienta para la tarea adecuada.</p><p>Una descripción contundente debe ser explícita. Por ejemplo: &quot;Realiza una búsqueda semántica en el índice elastic-dev-docs para encontrar documentación interna de ingeniería, libros de ejecución y procedimientos de liberación.&quot;</p><p>Con eso en marcha, la herramienta está configurada para realizar una búsqueda semántica contra nuestro índice específico. Una vez almacenado, está disponible inmediatamente para ser servido.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1eea079908fdf7cc72dbe81abd07ff51601a43d4-1472x1600.png" style="width: 100%;" /><p>Antes de conectarlo al exterior, puedes probarlo directamente en la interfaz. Simplemente haz clic en el botón <strong>Test</strong> para rellenar manualmente los parámetros, emulando lo que hará el LLM, e inspecciona los resultados para confirmar que todo funciona correctamente.</p><h3>Paso 2: Conecta Cursor al servidor Elastic MCP</h3><p>Elastic Agent Builder expone automáticamente todas las herramientas disponibles a través de un endpoint MCP seguro. Puedes encontrar la URL única de tu servidor en la interfaz de herramientas dentro de Kibana.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ba137be30f0eaa7f028b96bd8af4e2779c3f8a33-1600x589.png" style="width: 100%;" /><p>Para conectarnos a Cursor, simplemente agregamos esta URL a su archivo de configuración, junto con una clave API elástica para autenticación (<a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">aprende a crear clave API ES</a>). Usamos una clave API para la autorización, ya que garantiza que las herramientas solo se ejecuten con las licencias que concediste, respetando todas tus reglas de control de acceso.</p><p>La configuración MCP en el <code>~/.cursor/mcp.json</code> de Cursor es la siguiente:</p><pre><code>{
  "mcpServers": {
    "elastic-agent-builder": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://your-kibana.kb.company.io/api/agent_builder/mcp",
        "--header",
        "Authorization:${AUTH_HEADER}"
      ],
      "env": {
        "AUTH_HEADER": "ApiKey <ELASTIC_API_KEY>"
      }
    }
  }
}</code></pre><p>Una vez almacenada la configuración, deberías ver la herramienta de servidor MCP de Elastic Agent Builder disponible en Cursor.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d302c6d3609fbf14fd40e21b9e69e567bf12553f-1600x1002.png" style="width: 100%;" /><h3>Paso 3: ¡pregunta lo que quieras!</h3><p>Con la conexión establecido, los agentes Cursor pueden ahora invocar tus herramientas personalizadas para responder a tus preguntas o guiar el proceso de generación de código.</p><p>Hagamos una pregunta concreta:</p><p><em>&quot;Pasos de búsqueda para liberar el servicio de rastreo desde la documentación interna de ingeniería de la organización elastic search&quot;</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/14f572730203c23615bb9dd38234bcb3b0f81155-1600x1468.png" style="width: 100%;" /><p>Tras bambalinas, ocurre la magia:</p><ol><li>El agente del cursor decide cómo responder a tu pregunta de la mejor manera y decide llamar a la <code>engineering_documentation_internal_search</code></li><li>Invoca la herramienta mediante una consulta en lenguaje natural</li><li>La herramienta ejecuta una búsqueda semántica contra el índice de <code>elastic-dev-docs</code> y devuelve los procedimientos más relevantes y actualizados.</li></ol><p>Obtenemos una respuesta precisa y fiable basada en nuestra documentación interna, todo sin salir nunca del editor de código. La experiencia es fluida y poderosa.</p><h2>Te toca construir</h2><p>Ahora viste cómo usar el servidor MCP integrado en Elastic Agent Builder para ampliar tus asistentes de IA con acceso seguro a tus datos privados. Fundamentar los modelos en tu propia información es clave para que sean realmente útiles.</p><p>Para recapitular, cubrimos los pasos principales:</p><ul><li>Elegir el protocolo adecuado para tus necesidades (MCP).</li><li>Crear una herramienta de base de conocimiento personalizada.</li><li>Conectar esa herramienta a un asistente IDE como Cursor.</li></ul><p>Tus agentes y herramientas ya no necesitan estar desconectados de su contexto más valioso. Esperamos que esta guía te ayude a crear flujos de trabajo más efectivos y conscientes de los datos. ¡Feliz construcción!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Capacitar modelos LTR en Elasticsearch con listas de juicio basadas en datos de comportamiento del usuario]]></title>
            <description><![CDATA[Aprende a usar datos de RBU para crear listas de juicios que automatizen el entrenamiento de tus modelos Learning to Rank (LTR) en Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/training-learning-to-rank-models-elasticsearch-ubi-data</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/training-learning-to-rank-models-elasticsearch-ubi-data</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Alexander Dávila]]></dc:creator>
            <pubDate>Wed, 15 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/762bf09c28829d626d42c2cfadc719e1dd618d1b-1536x1024.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Un gran reto al usar <a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr"><strong><em>modelos Learning-to-rank</em></strong></a> es crear una <a href="https://www.elastic.co/search-labs/blog/judgment-lists"><strong><em>lista de juicios</em></strong></a> de alta calidad para capacitar el modelo. Tradicionalmente, este proceso implica una evaluación <strong><em>manual</em></strong> de la relevancia de los documentos de consulta para asignar una calificación a cada uno. Es un proceso lento que no escala bien y es difícil de mantener (imagina tener que actualizar una lista con cientos de entradas a mano).</p><p>Ahora, ¿y si pudiéramos usar interacciones reales de usuario con nuestra aplicación de búsqueda para crear estos datos de entrenamiento? Emplear datos <a href="https://www.elastic.co/search-labs/blog/elasticsearch-plugin-user-behavior-insights"><strong><em>de RBU</em></strong></a> nos permite hacer precisamente eso. Crear un sistema automático que pueda capturar y usar nuestras búsquedas, clics y otras interacciones para generar una lista de juicios. Este proceso puede escalar y repetir mucho más fácilmente que una interacción manual y tendería a dar mejores resultados. En este blog, exploraremos cómo podemos consultar datos de RBU almacenados en Elasticsearch para calcular señales significativas que generen un conjunto de datos de entrenamiento para un modelo <a href="https://www.elastic.co/search-labs/blog/elasticsearch-learning-to-rank-introduction"><strong><em>LTR</em></strong></a> .</p><p><strong><em>Puedes encontrar el experimento completo <a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog.git">aquí</a>.</em></strong></p><h2>Por qué los datos de RBU pueden ser útiles para capacitar tu modelo de LTR</h2><p>Los datos de la RBU ofrecen varios beneficios sobre una anotación manual:</p><ul><li><strong>Volumen:</strong> Dado que los datos de RBU provienen de interacciones reales, podemos recopilar muchos más datos de los que generamos manualmente. Esto suponiendo que tengamos suficiente tráfico para generar estos datos, por supuesto.</li><li><strong>Intención real del usuario:</strong> Tradicionalmente, una lista de juicios manual proviene de una evaluación experta de los datos disponibles. Por otro lado, los datos de RBU reflejan el comportamiento real de los usuarios. Esto significa que podemos generar mejores datos de entrenamiento que mejorarán la precisión de nuestro sistema de búsqueda, porque se basan en cómo los usuarios interactúan y encuentran valor en tu contenido, más que en suposiciones teóricas sobre lo que debería ser relevante.</li><li><strong>Actualizaciones continuas:</strong> Las listas de juicios necesitan actualizar con el tiempo. Si los creamos a partir de datos de la RBU, podemos tener datos actuales que resulten en listas de juicios actualizadas.</li><li><strong>Rentabilidad:</strong> Sin la carga de crear manualmente una lista de juicios, el proceso puede repetir eficientemente cualquier número de veces.</li><li><strong>Distribución natural de consultas</strong>: Los datos de RBU representan consultas reales de usuario, lo que puede impulsar cambios más profundos. Por ejemplo, ¿nuestros usuarios usan lenguaje natural para buscar en nuestro sistema? Si es así, podríamos querer implementar un enfoque de búsqueda semántica o de búsqueda híbrida.</li></ul><p>Sin embargo, viene con algunas advertencias:</p><ul><li><strong>Amplificación de polarización: </strong>El contenido popular tiene más probabilidades de recibir clics, simplemente porque tiene más visibilidad. Así que esto podría acabar amplificando los productos populares y posiblemente ahogando opciones mejores.</li><li><strong>Cobertura incompleta: </strong>El contenido nuevo carece de interacciones, por lo que puede ser difícil que los resultados sean altos. Las consultas raras también pueden carecer de suficientes puntos de datos para crear datos de entrenamiento significativos.</li><li><strong>Variaciones estacionales:</strong> Si esperas que el comportamiento del usuario cambie significativamente con el tiempo, los datos históricos pueden no decirte mucho sobre qué es un buen resultado.</li><li><strong>Ambigüedad de la tarea:</strong> Un clic no siempre garantiza que el usuario encontró lo que buscaba.</li></ul><h2>Cálculo de calificaciones</h2><h3>Calificaciones para el entrenamiento a largo plazo</h3><p>Para capacitar modelos LTR, necesitamos proporcionar alguna representación numérica de cuán relevante es un documento para una consulta. En nuestra implementación, este número es un puntaje continuo que va de 0,0 a 5,0+, donde puntajes más altos indican mayor relevancia.</p><p>Para mostrar cómo funciona este sistema de calificación, consideremos este ejemplo creado manualmente:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Búsqueda</td><td style="border: 1px solid #ccc; padding: 4px;">Contenido del documento</td><td style="border: 1px solid #ccc; padding: 4px;">Grado</td><td style="border: 1px solid #ccc; padding: 4px;">Explicación</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">"La mejor receta de pizza"</td><td style="border: 1px solid #ccc; padding: 4px;">"Receta auténtica de masa de pizza italiana con fotos paso a paso"</td><td style="border: 1px solid #ccc; padding: 4px;">4.0</td><td style="border: 1px solid #ccc; padding: 4px;">Muy relevante, exactamente lo que el usuario busca</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">"La mejor receta de pizza"</td><td style="border: 1px solid #ccc; padding: 4px;">"Historia de la pizza en Italia"</td><td style="border: 1px solid #ccc; padding: 4px;">1.0</td><td style="border: 1px solid #ccc; padding: 4px;">Algo en el tema, trata sobre pizza pero no es una receta</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">"La mejor receta de pizza"</td><td style="border: 1px solid #ccc; padding: 4px;">"Receta rápida de pizza de 15 minutos para principiantes"</td><td style="border: 1px solid #ccc; padding: 4px;">3.0</td><td style="border: 1px solid #ccc; padding: 4px;">Relevante, un buen resultado pero quizá no cumpla con la "mejor" receta.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">"La mejor receta de pizza"</td><td style="border: 1px solid #ccc; padding: 4px;">"Guía de mantenimiento de autos"</td><td style="border: 1px solid #ccc; padding: 4px;">0.0</td><td style="border: 1px solid #ccc; padding: 4px;">No tiene nada que ver, completamente ajeno a la consulta</td>
                    </tr>
              </tbody>
            </table>
          <p>Como podemos ver aquí, la calificación es una representación numérica de cuán relevante es un documento para nuestra consulta de ejemplo de &quot;mejor receta de pizza&quot;. Con estos puntajes, nuestro modelo de LTR puede aprender qué documentos deben presentar mejor en los resultados.</p><p>Cómo calcular las notas es el núcleo de nuestro conjunto de datos de entrenamiento. Existen <a href="https://www.elastic.co/search-labs/blog/judgment-lists">múltiples enfoques</a> para hacerlo, cada uno con sus propias fortalezas y debilidades. Por ejemplo, podríamos asignar un puntaje binario de 1 para el 0 relevante para no relevante o simplemente contar el número de clics en un documento resultante para cada consulta.</p><p>En esta entrada del blog, emplearemos un enfoque diferente, <strong><em>teniendo en cuenta el comportamiento del usuario como nuestra entrada y calculando un número de calificación como resultado</em></strong>. También corregiremos el sesgo que podría surgir por el hecho de que los resultados más altos tienden a ser más clicados, independientemente de la relevancia del documento.</p><h2>Cálculo de las calificaciones - algoritmo COEC</h2><p>El algoritmo COEC (<a href="https://www.wsdm-conference.org/2010/proceedings/docs/p351.pdf">Clics over Expected Clics</a>) es una metodología para calcular las calificaciones de juicio a partir de clics de los usuarios.<br/>Como mencionamos antes, los usuarios tienden a hacer clic en resultados de mejor posición incluso si el documento no es el más relevante para la consulta; esto se <a href="https://eugeneyan.com/writing/position-bias/">llama sesgo de posición</a>. La idea central para usar el algoritmo COEC es que no todos los clics son igual de significativos; Un clic en un documento en la posición 10 indica que el documento es mucho más relevante para la consulta que un clic en un documento en la posición 1. Para citar el artículo de investigación sobre el algoritmo COEC (enlazado arriba):</p><p><em>&quot;Es bien sabido que la tasa de clics (CTR) de los resultados de búsqueda o anuncios disminuye significativamente dependiendo de la posición de los resultados.&quot;</em></p><p>Puedes leer más sobre el sesgo de <a href="https://www.researchgate.net/publication/200110550_An_experimental_comparison_of_click_position-bias_models">posición aquí</a>.</p><p>Para abordar esto con el algoritmo COEC, seguimos estos pasos:</p><p><strong>1. Establecer líneas base de posición:</strong> Calculamos la tasa de clics (CTR) para cada posición de búsqueda del 1 al 10. Esto significa que determinamos qué porcentaje de usuarios suelen hacer clic en la posición 1, posición 2, y así sucesivamente. Este paso captura el sesgo natural de posición de los usuarios.<br/><br/>Calculamos el CTR usando:</p><div class="katex">CTRp=CpIp</div><p>Dónde:</p><p>p = Posición. Del 1 al 10<br/>Cp = Total de clics (en cualquier documento) en la posición p en todas las consultas<br/>Ip = Total de impresiones: Cuántas veces apareció cualquier documento en la posición p en todas las consultas</p><p>Aquí, esperamos que los puestos más altos consigan más clics.</p><p><strong>2.</strong> <strong>Calcular los clics esperados (EC):</strong></p><p>Esta métrica establece cuántos clics &quot;debería&quot; recibir un documento en función de las posiciones en las que apareció y el CTR para esas posiciones. Calculamos la EC usando:</p><div class="katex">EC (for a document) = qQdCTRpos(d,q)</div><p>Dónde:</p><p>Qd = Todas las consultas donde apareció el documento d<br/>pos(d,q)= Posición del documento d en los resultados de la consulta q</p><p>3. <strong>Contar clics reales: </strong>Contamos el total real de clics que un documento recibió en todas las consultas donde apareció, en adelante llamado <strong>A(d).</strong></p><p>4. <strong>Calcular el puntaje del COEC:</strong> Esta es la proporción de clics reales (A(d)) sobre los clics esperados (EC(d)):</p><div class="katex">COEC = A(d)EC(d)</div><p>Esta métrica normaliza para el sesgo de posición como este:</p><ul><li>Un puntaje de 1,0 significa que el documento funcionó exactamente como se espera dadas las posiciones en las que apareció.</li><li>Un puntaje superior a 1,0 significa que el documento tuvo un mejor rendimiento de lo esperado al observar sus posiciones. Así que este documento es más relevante para la consulta.</li><li>Un puntaje inferior a 1,0 significa que el documento tuvo un rendimiento peor de lo esperado al observar sus posiciones. Así que este documento es menos relevante para la consulta.</li></ul><p><strong><em>El resultado final es un número de calificación que captura lo que los usuarios buscan, teniendo en cuenta expectativas basadas en la posición extraídas de interacciones reales con nuestro sistema de búsqueda.</em></strong></p><h2>Implementación técnica</h2><p>Crearemos un script para crear una lista de juicios y capacitar un modelo de LTR.</p><p>La entrada para este script es los datos de la RBU indexados en Elastic (consultas y eventos).</p><p>La salida es una lista de juicios en un archivo CSV generada a partir de estos documentos de RBU empleando el algoritmo COEC. Esta lista de juicios puede usar con <a href="https://www.elastic.co/search-labs/blog/elasticsearch-learning-to-rank-introduction">Eland</a> para extraer características relevantes y capacitar un modelo LTR.</p><h3>Inicio rápido</h3><p>Para generar una lista de juicios a partir de los datos de muestra de este blog, puedes seguir estos pasos:</p><p>1. Clonar el repositorio:</p><pre><code>git clone https://github.com/Alex1795/elastic-ltr-judgement_list-blog.git  
cd elastic-ltr-judgement_list-blog</code></pre><p>2. Instalar las librerías necesarias</p><p>Para este guion, necesitamos las siguientes librerías:</p><ul><li><em>Pandas</em>: Para salvar la lista de juicios</li><li><em>elasticsearch</em>: Para obtener los datos de RBU de nuestro despliegue de Elastic</li></ul><p>También necesitamos Python 3.11</p><pre><code>pip install -r requirements.txt</code></pre><p>3. Actualizar las variables de entorno para tu despliegue de Elastic en un <a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog/blob/main/.env-example">archivo .env</a></p><ul><li>ES_HOST</li><li>API_KEY</li></ul><p>Para agregar las variables de entorno, emplea:</p><pre><code>source .env</code></pre><p>4. Crear el ubi_queries, ubi_events índices y subir los datos de muestra. Ejecuta el archivo setup.py:</p><pre><code>python setup.py</code></pre><p>5. Ejecutar el script en Python:</p><pre><code>python judgement_list-generator.py</code></pre><p>Si sigues estos pasos, deberías ver un archivo nuevo llamado judgment_list.csv que se ve así:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2531090131ac9fe3e4e1d79de9d156fc47a7825a-782x531.png" style="width: 100%;" /><p>Este script calcula las calificaciones aplicando el algoritmo COEC discutido antes de usar la función <strong>calculate_relevance_grade()</strong> que se muestra a continuación.</p><h2>Arquitectura de datos</h2><h3>Consultas Ubi</h3><p>Nuestro índice de consultas de RBU contiene información sobre las consultas ejecutadas en nuestro sistema de búsqueda. Este es un documento de ejemplo:</p><pre><code>{
          "client_id": "client_002",
          "query": "italian pasta recipes",
          "query_attributes": {
            "search_type": "recipe",
            "category": "food",
            "cuisine": "italian"
          },
          "query_id": "q002",
          "query_response_id": "qr002",
          "query_response_object_ids": [
            "doc_011",
            "doc_012",
            "doc_013",
            "doc_014",
            "doc_015",
            "doc_016",
            "doc_017",
            "doc_018",
            "doc_019",
            "doc_020"
          ],
          "timestamp": "2024-08-14T11:15:00Z",
          "user_query": "italian pasta recipes"
        }</code></pre><p>Aquí podemos ver datos del usuario (client_id), de los resultados de la consulta (query_response_object_ids) y de la propia consulta (marca de tiempo, user_query)</p><h3>Eventos de Ubi Clic</h3><p>Nuestro índice de ubi_events contiene datos de cada vez que un usuario hizo clic en un documento en los resultados. Este es un documento de ejemplo:</p><pre><code>{
          "action_name": "click",
          "application": "recipe_search",
          "client_id": "client_001",
          "event_attributes": {
            "object": {
              "description": "Authentic Italian Pizza Dough Recipe with Step-by-Step Photos",
              "device": "desktop",
              "object_id": "doc_001",
              "position": {
                "ordinal": 1,
                "page_depth": 1
              },
              "user": {
                "city": "New York",
                "country": "USA",
                "ip": "192.168.1.100",
                "location": {
                  "lat": 40.7128,
                  "lon": -74.006
                },
                "region": "NY"
              }
            }
          },
          "message": "User clicked on document doc_001",
          "message_type": "click",
          "query_id": "q001",
          "timestamp": "2024-08-14T10:31:00Z",
          "user_query": "best pizza recipe"
        }</code></pre><h2>Script de generación de lista de sentencias</h2><h3>Resumen general de la escritura</h3><p>Este script automatiza la generación de la lista de juicios empleando datos de RBU de los eventos de Consultas y Clics almacenados en Elasticsearch. Ejecuta estas tareas:</p><ul><li>Recupera y procesa los datos de la RBU en Elasticsearch.</li><li>Correlaciona los eventos de RBU con sus consultas.</li><li>Calcula el CTR para cada posición.</li><li>Calcula los clics esperados (EC) para cada documento.</li><li>Cuenta los clics reales de cada documento.</li><li>Calcula el puntaje del COEC para cada par consulta-documento.</li><li>Genera una lista de juicios y la escribe en un archivo CSV.</li></ul><p>Repasemos cada función:</p><h3>connect_to_elasticsearch()</h3><pre><code>def connect_to_elasticsearch(host, api_key):
    """Create and return Elasticsearch client"""
    try:
        es = Elasticsearch(
            hosts=[host],
            api_key=api_key,
            request_timeout=60
        )
        # Test the connection
        if es.ping():
            print(f"✓ Successfully connected to Elasticsearch at {host}")
            return es
        else:
            print("✗ Failed to connect to Elasticsearch")
            return None
    except Exception as e:
        print(f"✗ Error connecting to Elasticsearch: {e}")
        return None</code></pre><p>Esta función devuelve un objeto cliente de Elasticsearch usando la clave host y API.</p><h3>fetch_ubi_data()</h3><pre><code>def fetch_ubi_data(es_client: Elasticsearch, queries_index: str, events_index: str,
                   size: int = 10000) -> Tuple[List[Dict], List[Dict]]:
    """
    Fetch UBI queries and events data from Elasticsearch indices.

    Args:
        es_client: Elasticsearch client
        queries_index: Name of the UBI queries index
        events_index: Name of the UBI events index
        size: Maximum number of documents to fetch

    Returns:
        Tuple of (queries_data, events_data)
    """
    logger.info(f"Fetching data from {queries_index} and {events_index}")

    # Fetch queries with error handling
    try:
        queries_response = es_client.search(
            index=queries_index,
            body={
                "query": {"match_all": {}},
                "size": size
            }
        )
        queries_data = [hit['_source'] for hit in queries_response['hits']['hits']]
        logger.info(f"Fetched {len(queries_data)} queries")

    except Exception as e:
        logger.error(f"Error fetching queries from {queries_index}: {e}")
        raise

    # Fetch events (only click events for now) with error handling
    try:
        events_response = es_client.search(
            index=events_index,
            body={
                "query": {
                    "term": {"message_type.keyword": "CLICK_THROUGH"}
                },
                "size": size
            }
        )
        events_data = [hit['_source'] for hit in events_response['hits']['hits']]
        logger.info(f"Fetched {len(events_data)} click events")

    except Exception as e:
        logger.error(f"Error fetching events from {events_index}: {e}")
        raise

    logger.info(f"Data fetch completed successfully - Queries: {len(queries_data)}, Events: {len(events_data)}")

    return queries_data, events_data</code></pre><p>Esta función es la capa de extracción de datos; se conecta con Elasticsearch para obtener consultas de RBU usando una consulta match_all y filtra los eventos de RBU para obtener solo eventos &#x27;CLICK_THROUGH&#x27;.</p><h3>process_ubi_data()</h3><pre><code>def process_ubi_data(queries_data: List[Dict], events_data: List[Dict]) -> pd.DataFrame:
    """
    Process UBI data and generate judgment list.

    Args:
        queries_data: List of query documents from UBI queries index
        events_data: List of event documents from UBI events index

    Returns:
        DataFrame with judgment list (qid, docid, grade, keywords)
    """
    logger.info("Processing UBI data to generate judgment list")

    # Group events by query_id
    clicks_by_query = {}
    for event in events_data:
        query_id = event['query_id']
        if query_id not in clicks_by_query:
            clicks_by_query[query_id] = {}

        # Extract clicked document info
        object_id = event['event_attributes']['object']['object_id']
        position = event['event_attributes']['object']['position']['ordinal']

        clicks_by_query[query_id][object_id] = {
            'position': position,
            'timestamp': event['timestamp']
        }

    judgment_list = []

    # Process each query
    for query in queries_data:
        query_id = query['query_id']
        user_query = query['user_query']
        document_ids = query['query_response_object_ids']

        # Get clicks for this query
        query_clicks = clicks_by_query.get(query_id, {})

        # Generate judgment for each document shown
        for doc_id in document_ids:
            grade = calculate_relevance_grade(doc_id, query_clicks, document_ids, queries_data, events_data)

            judgment_list.append({
                'qid': query_id,
                'docid': doc_id,
                'grade': grade,
                'query': user_query
            })

    df = pd.DataFrame(judgment_list)
    logger.info(f"Generated {len(df)} judgment entries for {df['qid'].nunique()} unique queries")

    return df</code></pre><p>Esta función se encarga de la generación de la lista de juicios. Comienza a procesar los datos de RBU asociando eventos y consultas de RBU. Luego llama a la función calculate_relevance_grade() para cada par documento-consulta para obtener las entradas de la lista de juicios. Finalmente, devuelve la lista resultante como un dataframe pandas.</p><h3>calculate_relevance_grade()</h3><pre><code>def calculate_relevance_grade(document_id: str, clicks_data: Dict,
                              query_response_ids: List[str], all_queries_data: List[Dict] = None,
                              all_events_data: List[Dict] = None) -> float:
    """
    Calculate COEC (Click Over Expected Clicks) relevance score for a document.

    Args:
        document_id: ID of the document
        clicks_data: Dictionary of clicked documents with their positions for current query
        query_response_ids: List of document IDs shown in search results (ordered by position)
        all_queries_data: All queries data for calculating position CTR averages
        all_events_data: All events data for calculating position CTR averages

    Returns:
        COEC relevance score (continuous value, typically 0.0 to 5.0+)
    """

    # If no global data provided, fall back to simple position-based grading
    if all_queries_data is None or all_events_data is None:
        logger.warning("No global data provided, falling back to position-based grading")
        # Simple fallback logic
        if document_id in clicks_data:
            position = clicks_data[document_id]['position']
            if position > 3:
                return 4.0
            elif position >= 1 and position <= 3:
                return 3.0
        if document_id in query_response_ids:
            position = query_response_ids.index(document_id) + 1
            if position <= 5:
                return 2.0
            elif position >= 6 and position <= 10:
                return 1.0
        return 0.0

    # Calculate rank-aggregated click-through rates
    position_ctr_averages = {}
    position_impression_counts = {}
    position_click_counts = {}

    # Initialize counters
    for pos in range(1, 11):  # Positions 1-10
        position_impression_counts[pos] = 0
        position_click_counts[pos] = 0

    # Count impressions (every document shown contributes)
    for query in all_queries_data:
        for i, doc_id in enumerate(query['query_response_object_ids'][:10]):  # Top 10 positions
            position = i + 1
            position_impression_counts[position] += 1

    # Count clicks by position
    for event in all_events_data:
        if event.get('action_name') == 'click':
            position = event['event_attributes']['object']['position']['ordinal']
            if position <= 10:
                position_click_counts[position] += 1

    # Calculate average CTR per position
    for pos in range(1, 11):
        if position_impression_counts[pos] > 0:
            position_ctr_averages[pos] = position_click_counts[pos] / position_impression_counts[pos]
        else:
            position_ctr_averages[pos] = 0.0

    # Calculate expected clicks for this specific document
    expected_clicks = 0.0

    # Count how many times this document appeared at each position for any query
    for query in all_queries_data:
        if document_id in query['query_response_object_ids']:
            position = query['query_response_object_ids'].index(document_id) + 1
            if position <= 10:
                expected_clicks += position_ctr_averages[position]

    # Count total actual clicks for this document across all queries
    actual_clicks = 0
    for event in all_events_data:
        if (event.get('action_name') == 'click' and
                event['event_attributes']['object']['object_id'] == document_id):
            actual_clicks += 1

    # Calculate COEC score
    if expected_clicks > 0:
        coec_score = actual_clicks / expected_clicks
    else:
        coec_score = 0.0

    logger.debug(
        f"Document {document_id}: {actual_clicks} clicks / {expected_clicks:.3f} expected = {coec_score:.3f} COEC")

    return coec_score</code></pre><p>Esta es la función que implementa el algoritmo COEC. Calcula el CTR para cada posición, luego compara los clics reales de un par documento-consulta, y finalmente calcula el puntaje real del COEC para cada una.</p><h3>generate_judgment_statistics()</h3><pre><code>def generate_judgment_statistics(df: pd.DataFrame) -> Dict:
    """Generate statistics about the judgment list."""
    stats = {
        'total_judgments': len(df),
        'unique_queries': df['qid'].nunique(),
        'unique_documents': df['docid'].nunique(),
        'grade_distribution': df['grade'].value_counts().to_dict(),
        'avg_judgments_per_query': len(df) / df['qid'].nunique() if df['qid'].nunique() > 0 else 0,
        'queries_with_clicks': len(df[df['grade'] > 1]['qid'].unique()),
        'click_through_rate': len(df[df['grade'] > 1]) / len(df) if len(df) > 0 else 0
    }
    return stats</code></pre><p>Genera estadísticas útiles a partir de la lista de valores, como el total de consultas, el total de documentos únicos o la distribución de calificaciones. Esto es puramente informativo y no cambia la lista de sentencias resultante.</p><h2>Resultados e impacto</h2><p>Si sigues las instrucciones de la sección de Inicio rápido, deberías ver un archivo CSV resultante que contiene una lista de juicios con 320 entradas (puedes ver una <a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog/blob/main/judgment_list.csv">salida de ejemplo</a> en el repositorio). Con estos campos:</p><ul><li>qid: ID único de la consulta</li><li>DocID: Identificador único para un documento resultante</li><li>Grado: la calificación calculada para el par consulta-documento</li><li>consulta: La consulta de usuario</li></ul><p> Veamos los resultados de la consulta &quot;recetas italianas&quot;:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Qid</td><td style="border: 1px solid #ccc; padding: 4px;">docid</td><td style="border: 1px solid #ccc; padding: 4px;">grado</td><td style="border: 1px solid #ccc; padding: 4px;">Búsqueda</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Q1-recetas-italianas</td><td style="border: 1px solid #ccc; padding: 4px;">recipe_pasta_basics</td><td style="border: 1px solid #ccc; padding: 4px;">0.0</td><td style="border: 1px solid #ccc; padding: 4px;">Recetas italianas</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Q1-recetas-italianas</td><td style="border: 1px solid #ccc; padding: 4px;">recipe_pizza_margherita</td><td style="border: 1px solid #ccc; padding: 4px;">3.333333</td><td style="border: 1px solid #ccc; padding: 4px;">Recetas italianas</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Q1-recetas-italianas</td><td style="border: 1px solid #ccc; padding: 4px;">recipe_risotto_guide</td><td style="border: 1px solid #ccc; padding: 4px;">10.0</td><td style="border: 1px solid #ccc; padding: 4px;">Recetas italianas</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Q1-recetas-italianas</td><td style="border: 1px solid #ccc; padding: 4px;">recipe_french_croissant</td><td style="border: 1px solid #ccc; padding: 4px;">0.0</td><td style="border: 1px solid #ccc; padding: 4px;">Recetas italianas</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Q1-recetas-italianas</td><td style="border: 1px solid #ccc; padding: 4px;">recipe_spanish_paella</td><td style="border: 1px solid #ccc; padding: 4px;">0.0</td><td style="border: 1px solid #ccc; padding: 4px;">Recetas italianas</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Q1-recetas-italianas</td><td style="border: 1px solid #ccc; padding: 4px;">recipe_greek_moussaka</td><td style="border: 1px solid #ccc; padding: 4px;">1.875</td><td style="border: 1px solid #ccc; padding: 4px;">Recetas italianas</td>
                    </tr>
              </tbody>
            </table>
          <p>Podemos ver en los resultados que para la consulta &quot;recetas italianas&quot;:</p><ul><li>La receta de risotto es sin duda el mejor resultado para la consulta, recibiendo 10 veces más clics de lo esperado</li><li>Pizza Margherita también es un gran resultado.</li><li>La mousaka griega (sorprendentemente) también es un buen resultado y rinde mejor de lo que su posición en los resultados sugeriría. Esto significa que algunos usuarios que buscaban recetas italianas se interesaron por esta receta. Quizá estos usuarios estén interesados en platos mediterráneos en general. Al final, esto nos dice que podría ser un buen resultado para mostrar bajo los otros dos partidos &#x27;mejores&#x27; que mencionamos antes.</li></ul><h2>Conclusión</h2><p>Emplear datos de RBU nos permite automatizar el entrenamiento de modelos de LTR, creando listas de juicios de alta calidad de nuestros propios usuarios. Los datos de RBU proporcionan un gran conjunto de datos que refleja cómo se está empleando nuestro sistema de búsqueda. Al emplear el algoritmo de la COEC para generar las calificaciones, tenemos en cuenta el sesgo inherente y, al mismo tiempo, refleja lo que el usuario considera un mejor resultado. El método descrito aquí puede aplicar a casos de uso reales para ofrecer una mejor experiencia de búsqueda que evoluciona con las tendencias reales de uso.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Evaluación de agentes de IA: Cómo Elastic prueba los marcos agentivos]]></title>
            <description><![CDATA[Aprende cómo evaluamos y probamos los cambios en un sistema agente antes de entregarlos a los usuarios de Elastic para garantizar resultados precisos y verificables.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/ai-agent-evaluation-elastic</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/ai-agent-evaluation-elastic</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Susan Chang, Abhimanyu Anand]]></dc:creator>
            <pubDate>Mon, 13 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e681aab6ee77dfef38dc95e36c8535dfe7bcc105-721x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>Introducción</h2><p>En el Elastic Stack hay muchas aplicaciones agenticas impulsadas por LLM, como el próximo Elastic AI Agent en<a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder"> Agent Builder</a> (actualmente en vista previa tecnológica) y <a href="https://www.elastic.co/docs/solutions/security/ai/attack-discovery">Attack Discovery</a> (<a href="https://www.elastic.co/blog/whats-new-elastic-security-9-0-0">GA</a> en 8.18 y 9.0+), con más en desarrollo. Durante el desarrollo, e incluso luego del despliegue, es importante responder a estas preguntas:</p><ul><li>¿Cómo estimamos la calidad de las respuestas de estas aplicaciones de IA?</li><li>Si hacemos un cambio, ¿cómo garantizamos que el cambio sea realmente una mejora y que no cause degradación en la experiencia del usuario?</li><li>¿Cómo podemos probar estos resultados de forma fácilmente repetible?</li></ul><p>A diferencia de las pruebas tradicionales de software, evaluar aplicaciones de IA generativa implica métodos estadísticos, revisión cualitativa matizada y un profundo entendimiento de los objetivos del usuario.</p><p>Este artículo detalla el proceso que emplea el equipo de desarrollo de Elastic para realizar evaluaciones, garantizar la calidad de los cambios antes del despliegue y monitorizar el rendimiento del sistema. Nuestro objetivo es garantizar que cada cambio esté respaldado por evidencias, lo que conduzca a resultados fiables y verificables. Parte de este proceso está integrada directamente en Kibana, reflejando nuestro compromiso con la transparencia como parte de nuestra filosofía de código abierto. Al compartir abiertamente partes de nuestros datos y métricas de evaluación, buscamos fomentar la confianza de la comunidad y proporcionar un marco claro para cualquiera que desarrolle agentes de IA o emplee nuestros productos.</p><h2>Ejemplos de productos</h2><p>Los métodos empleados en este documento fueron la base para iterar y mejorar soluciones como Attack Discovery y Elastic AI Agent. Una breve introducción de ambos, respectivamente:</p><h3>Descubrimiento de ataques de Elastic Security</h3><p>El Descubrimiento de Ataques emplea LLMs para identificar y resumir secuencias de ataques en Elastic. Dado el sistema de alertas de Elastic Security en un plazo determinado (por defecto 24 horas), el flujo de trabajo agente de Attack Discovery detectará automáticamente si se produjeron ataques, así como información importante como qué host o usuarios fueron comprometidos y qué alertas contribuyeron a la conclusión.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/20fabb47642dad7b588daaaa8c3a98de860ad01d-1251x758.png" style="width: 100%;" /><p></p><p>El objetivo es que la solución basada en LLM produzca una salida al menos tan buena como la de un humano.</p><h3>Agente de IA elástica</h3><p><strong>Elastic Agent Builder</strong> es nuestra nueva plataforma para construir agentes de IA conscientes del contexto que aprovechan todas nuestras capacidades de búsqueda. Incluye el <strong>Elastic AI Agent</strong>, un agente preconstruido y de propósito general diseñado para ayudar a los usuarios a comprender y obtener respuestas de sus datos mediante la interacción conversacional.</p><p>El agente lo logra identificando automáticamente la información relevante dentro de Elasticsearch o bases de conocimiento conectadas y aprovechando un conjunto de herramientas prediseñadas para interactuar con ellas. Esto permite al Agente Elastic AI responder a una amplia variedad de consultas de usuario, desde simples preguntas frecuentes en un solo documento hasta solicitudes complejas que requieren agregación y búsquedas de un o varios pasos en múltiples índices.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d29dee100bb8a17bb623acd745773a5164a1df4f-1600x1014.png" style="width: 100%;" /><h2>Medición de mejoras mediante experimentos</h2><p>En el contexto de los agentes de IA, un experimento es un cambio estructurado y comprobable en el sistema diseñado para mejorar el rendimiento en dimensiones bien definidas (por ejemplo, utilidad, corrección, latencia). El objetivo es responder de forma definitiva: &quot;Si fusionamos este cambio, ¿podemos garantizar que es una verdadera mejora y que no degradará la experiencia del usuario?&quot;</p><p>La mayoría de los experimentos que realizamos suelen incluir:</p><ul><li><strong>Una hipótesis:</strong> Una afirmación específica y falsable. <em>Ejemplo:</em> &quot;Agregar acceso a una herramienta de detección de ataques mejora la corrección en consultas relacionadas con la seguridad.&quot;</li><li><strong>Criterios de éxito:</strong> Umbrales claros que definan qué significa &quot;éxito&quot;. <em>Ejemplo:</em> &quot;+5% de mejora en el puntaje de corrección en el conjunto de datos de seguridad, sin degradación en otros lugares.&quot;</li><li><strong>Plan de evaluación:</strong> Cómo medimos el éxito (métricas, conjuntos de datos, método de comparación)</li></ul><p>Un experimento exitoso es un proceso sistemático de investigación. Cada cambio, desde un pequeño ajuste de prompt hasta un cambio arquitectónico importante, sigue estos siete pasos para cerciorar que los resultados sean significativos y accionables:</p><ul><li>Paso 1: Identificar el problema</li><li>Paso 2: Definir métricas</li><li>Paso 3: Formular una hipótesis clara</li><li>Paso 4: Preparar el conjunto de datos de evaluación</li><li>Paso 5: Ejecutar el experimento</li><li>Paso 6: Analizar resultados + iterar</li><li>Paso 7: Toma una decisión y documenta</li></ul><p>Un ejemplo de estos pasos se ilustra en <em>la Figura 1</em>. Las siguientes subsecciones explicarán cada paso y ampliaremos los detalles técnicos de cada paso en los próximos documentos.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3a9f5a3e92dd4922a795a19104c6e4ad8c98958d-2400x1352.png" style="width: 100%;" /><h2>Paso a paso con ejemplos reales de Elastic</h2><h3>Paso 1: Identificar el problema</h3><p><em>¿Cuál es exactamente el problema que pretende resolver este cambio?</em></p><p>Ejemplo de descubrimiento de ataque: Los resúmenes a veces son incompletos, o una actividad benigna se marca erróneamente como ataque (falsos positivos).</p><p>Ejemplo de Elastic AI Agent: La selección de herramientas del agente, especialmente para consultas analíticas, es subóptima e inconsistente, lo que a menudo lleva a que se elija la herramienta equivocada. Esto, a su vez, incrementa los costos y la latencia de los tokens.</p><h3>Paso 2: Definir métricas</h3><p><em>Haz que el problema sea medible, para que podamos comparar un cambio con el estado actual.</em></p><p>Las métricas comunes incluyen <a href="https://developers.google.com/machine-learning/crash-course/classification/accuracy-precision-recall">precisión y recordación</a>, <a href="https://en.wikipedia.org/wiki/Semantic_similarity">similitud semántica</a>, factualidad, etc. Dependiendo del caso de uso, usamos comprobaciones de código para calcular las métricas, como coincidir IDs de alerta o URLs correctamente recuperadas, o usar técnicas como LLM-as-judge para respuestas más libres.</p><p>A continuación, algunos ejemplos de métricas (<em>no exhaustivas</em>) empleadas en los experimentos:</p><p><strong>Detección de ataques</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Métrico</td><td style="border: 1px solid #ccc; padding: 4px;">Descripción</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Precisión y recordación</td><td style="border: 1px solid #ccc; padding: 4px;">Compara los IDs de alerta entre las salidas reales y esperadas para medir la precisión de la detección.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Similitud</td><td style="border: 1px solid #ccc; padding: 4px;">Usa BERTScore para comparar la similitud semántica del texto de respuesta.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Hechos</td><td style="border: 1px solid #ccc; padding: 4px;">¿Existen indicadores clave de compromiso (IOC)? ¿Se reflejan correctamente las tácticas MITRE (taxonomía industrial de los ataques)?</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Consistencia de la cadena de ataque</td><td style="border: 1px solid #ccc; padding: 4px;">Compara el número de descubrimientos para comprobar si hay sobrenotificación o infranotificación del ataque.</td>
                    </tr>
              </tbody>
            </table>
          <p><strong>Agente de IA elástica</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Métrico</td><td style="border: 1px solid #ccc; padding: 4px;">Descripción</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Precisión y recordación</td><td style="border: 1px solid #ccc; padding: 4px;">Compara documentos/información recuperada por el agente para responder a una consulta del usuario con la información o documentos reales necesarios para responder a la consulta y así medir la precisión de la recuperación.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Hechos</td><td style="border: 1px solid #ccc; padding: 4px;">¿Están presentes los datos clave necesarios para responder a la consulta del usuario? ¿Están los hechos en el orden correcto para las consultas procesales?</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Relevancia de la respuesta</td><td style="border: 1px solid #ccc; padding: 4px;">¿La respuesta contiene información que es periférica o no relacionada con la consulta del usuario?</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Completitud de respuesta</td><td style="border: 1px solid #ccc; padding: 4px;">¿La respuesta responde a todas las partes de la consulta del usuario? ¿Contiene la respuesta toda la información presente en la verdad sobre el terreno?</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ES|Validación QL</td><td style="border: 1px solid #ccc; padding: 4px;">¿Es el ES| generado?¿QL sintácticamente correcto? ¿Es funcionalmente idéntico a la verdad básica ES|¿QL?</td>
                    </tr>
              </tbody>
            </table>
          <h3>Paso 3: Formular una hipótesis clara</h3><p><em>Establece un criterio claro de éxito empleando el problema y las métricas definidas anteriormente.</em></p><p>Ejemplo de Elastic AI Agent:</p><ol><li>Implementa <strong>cambios en las descripciones de las herramientas de relevance_search y nl_search para definir claramente sus funciones y casos de uso específicos</strong>.</li><li>Predecimos que <strong>mejoraremos</strong> la <strong>precisión de invocación de nuestra herramienta</strong> en <strong>un 25%.</strong></li><li>Verificaremos que esto sea un beneficio neto cerciorándonos de que no haya impacto negativo en otras métricas, por ejemplo, la <strong>certeza y la completitud</strong>.</li><li>Creemos que esto funcionará porque <strong>descripciones precisas de las herramientas ayudarán al agente a seleccionar y aplicar con mayor precisión la herramienta de búsqueda más adecuada para diferentes tipos de consulta, reduciendo la mala aplicación y mejorando la efectividad general de la búsqueda</strong>.</li></ol><h3>Paso 4: Preparar el conjunto de datos de evaluación</h3><p><em>Para medir el rendimiento del sistema, empleamos conjuntos de datos que capturan escenarios del mundo real.</em></p><p>Dependiendo del tipo de evaluación que estemos realizando, puede que necesitemos diferentes tipos de formatos de datos, como datos en bruto que se alimentan a un LLM (por ejemplo, escenarios de ataque para Descubrimiento de Ataques) y resultados esperados. Si la aplicación es un chatbot, entonces las entradas pueden ser consultas del usuario, y las salidas pueden ser respuestas correctas del chatbot, enlaces correctos que debería recuperar, y así sucesivamente.</p><p>Ejemplo de Descubrimiento de Ataque:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">10 escenarios de ataque novedosos</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">8 episodios de Oh My Malware (ohmymalware.com)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">4 escenarios de múltiples ataques (creados combinando ataques en las dos primeras categorías)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">3 escenarios benignos</td>
                    </tr>
              </tbody>
            </table>
          <p>Ejemplo de conjunto de datos de evaluación de agentes de Elastic AI (<a href="https://github.com/elastic/kibana/blob/main/x-pack/platform/packages/shared/onechat/kbn-evals-suite-onechat/evals/kb/kb.spec.ts">Enlace al conjunto de datos Kibana</a>):</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">14 índices usando conjuntos de datos de código abierto para simular múltiples fuentes en la base de datos.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">5 tipos de consulta (analítica, recuperación de texto, híbrida...)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">7 Tipos de intención de consulta (procedimental, fáctica - clasificación, investigativa; ...)</td>
                    </tr>
              </tbody>
            </table>
          <h3>Paso 5: Ejecutar el experimento</h3><p>Ejecuta el experimento generando respuestas tanto del agente existente como de la versión modificada contra el conjunto de datos de evaluación. Calcula métricas como la factualidad (ver el paso 2).</p><p>Combinamos diversas evaluaciones basadas en las métricas requeridas en el Paso 2:</p><ul><li>Evaluación basada en reglas (por ejemplo, usar Python/TypeScript para comprobar si .json es válido)</li><li>LLM-as-judge (preguntar a un LLM separado si una respuesta es factualmente coherente con un documento fuente)</li><li>Revisión de Human in the Loop para controles de calidad de matices</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8648e75818d3291f0ac66f069438a500d42b8225-1600x1099.png" style="width: 100%;" /><h3>Paso 6: Analizar resultados + iterar</h3><p>Ahora que tenemos las métricas, analizamos los resultados. <em><span style="text-decoration:underline">Incluso si los resultados cumplen los criterios de éxito definidos en el paso 3, seguiremos teniendo una revisión humana antes de fusionar el cambio con la producción</span></em>; Si los resultados no cumplen los criterios, iterar y corregir los problemas, y luego ejecutar las evaluaciones sobre el nuevo cambio.</p><p>Esperamos que haga falta varias iteraciones para encontrar el mejor cambio antes de fusionar. De forma similar a ejecutar pruebas locales de software antes de enviar un commit, las evaluaciones offline pueden ejecutar con cambios locales o múltiples propuestas de cambios. Es útil automatizar el almacenado de resultados de experimentos, puntajes compuestos y visualizaciones para agilizar el análisis.</p><h3>Paso 7: Toma una decisión y documenta</h3><p>Basar en un marco de decisión y criterios de aceptación, decide fusionar el cambio y documenta el experimento. La toma de decisiones es multifacética y puede considerar factores más allá del conjunto de datos de evaluación, como comprobar escenarios de regresión en otros conjuntos de datos o valorar el costo-beneficio de un cambio propuesto.</p><p>Ejemplo: Tras probar y comparar varias iteraciones, elige el cambio con mejor puntaje para enviar a los responsables de producto y otros interesados relevantes para su aprobación. Anexa los resultados de los pasos anteriores para ayudar a guiar la decisión. Para más ejemplos en el área de Descubrimiento de Ataques, <a href="https://www.elastic.co/blog/elastic-security-generative-ai-features">ver Detrás de las cámaras de las funciones de IA generativa de Elastic Security</a>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/74c80b8f34dce8ddd20873ecb2f553873587ed35-1600x618.png" style="width: 100%;" /><h2>Conclusión</h2><p>En este blog, repasamos el proceso integral de un flujo de trabajo experimental, ilustrando cómo evaluamos y probamos los cambios en un sistema agente antes de lanzarlos a los usuarios de Elastic. También proporcionamos algunos ejemplos de cómo mejorar los flujos de trabajo basados en agentes en Elastic. En próximas entradas del blog, ampliaremos los detalles de diferentes pasos, como cómo crear un buen conjunto de datos, cómo diseñar métricas fiables y cómo tomar decisiones cuando hay múltiples métricas de por medio.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Conectando agentes Elastic a Gemini Enterprise mediante protocolo A2A]]></title>
            <description><![CDATA[Aprende a usar Agent Builder para exponer tu Agente Elastic personalizado a servicios externos como Gemini Enterprise con el protocolo A2A.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/a2a-protocol-elastic-agent-builder-gemini-enterprise</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/a2a-protocol-elastic-agent-builder-gemini-enterprise</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jedr Blaszyk, Valerio Arvizzigno, Joe McElroy]]></dc:creator>
            <pubDate>Thu, 09 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5be8a425fab55dca2f9717d2e50812b0450fa625-1440x840.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><strong>Elastic Agent Builder</strong> es un conjunto de capacidades para crear agentes de IA basados en datos directamente en Elasticsearch. En publicaciones anteriores de la <a href="https://www.elastic.co/search-labs/blog/series/context-aware-ai-agentic-workflows-with-elastic">serial</a>, demostramos cómo equipar a los agentes personalizados con herramientas para realizar tareas complejas y proporcionarles un conjunto de instrucciones personalizadas para guiar su comportamiento.</p><p>Pero, ¿y si quieres usar tus agentes personalizados con las aplicaciones y herramientas de productividad de las que ya dependes?</p><p>Ahí es donde entra el <strong>protocolo Agent-a-Agente (A2A</strong> ). A2A es un <a href="https://github.com/a2aproject/A2A">estándar abierto</a> de interoperabilidad, que permite a agentes de diferentes plataformas comunicar y colaborar. Y lo integramos directamente en el Elastic Agent Builder.</p><p>Hoy vamos a mostrarte cómo tomar un agente personalizado que creaste y exponerlo a otros servicios, concretamente <strong>Gemini Enterprise </strong>(antes Agentspace).</p><h2>El poder de los estándares abiertos: por qué importa A2A</h2><p>En la entrada del <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">blog Tu primer agente Elastic</a>, mostramos cómo crear agentes personalizados, como un <em>agente de Asistente Financiero</em> con acceso seguro a tus datos de mercado. Pero su valor es limitado si no puedes poner sus conocimientos a disposición en otros entornos, como Gemini Enterprise, sin reconstruir tu trabajo.</p><p>Este desafío de interoperabilidad es lo que frena la IA agente. Los agentes necesitan un lenguaje común para comunicar entre plataformas, que es precisamente el papel del protocolo A2A. Proporciona una capa estándar de comunicación que no solo te permite interactuar directamente con tu agente, sino que también abre un futuro donde agentes especializados de toda tu organización pueden colaborar y compartir conocimientos.</p><p>Para hacerlo posible, Elastic Agent Builder soporta nativamente el protocolo A2A a través de dos endpoints estándar para todos tus agentes:</p><ol><li><strong>El punto final de la Tarjeta Agente (<code>GET {your-kibana-url}/api/agent_builder/a2a/{agentId}.json</code>) - </strong>Esto actúa como la tarjeta de visita de tu agente personalizado. Proporciona metadatos sobre tu agente (nombre, descripción, capacidades, etc.) a cualquier servicio compatible con A2A.</li><li><strong>El punto final del Protocolo A2A (<code>POST {your-kibana-url}/api/agent_builder/a2a/{agentId}</code>)</strong> - Este es el canal de comunicación. Otros agentes envían sus solicitudes aquí, y tu agente las procesa y responde, todo siguiendo la <a href="https://a2a-protocol.org/latest/specification/">especificación del protocolo A2A</a>.</li></ol><h2>Prueba a tu agente con el inspector A2A</h2><p>Antes de conectar a nuestro agente a un sistema de producción, es bueno comprobar que se comunica correctamente. La forma más sencilla de hacerlo es con el <strong>Inspector A2A</strong>, una herramienta diseñada específicamente para probar y depurar integraciones A2A.</p><p>Poner en marcha al inspector es sencillo. Puedes clonar el repositorio <a href="https://github.com/a2aproject/a2a-inspector">a2a-inspector</a> y seguir las instrucciones README para <a href="https://github.com/a2aproject/a2a-inspector?tab=readme-ov-file#3-run-the-application">ejecutar la aplicación</a>. Una vez iniciada, la interfaz está disponible por defecto en <code>http://localhost:5001/</code>.</p><p>Para conectar al Inspector A2A con tu agente, tendrás que proporcionar dos datos clave:</p><ul><li>URL de la tarjeta del agente: Este es el punto final que describe a tu agente. Para el <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">agente de Asistente Financiero de nuestra publicación anterior</a>, esta URL sería <code>{your-kibana-url}/api/agent_builder/a2a/financial_assistant.json</code>.</li><li>Cabecera de autenticación: Emplearemos una clave API estándar para la autenticación.</li></ul><p>Una vez introduzcas estos datos en la interfaz del inspector, puedes conectarte y empezar a hablar con tu agente inmediatamente.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7231c72bf30bed2a854f58658c1eca2843f43bfc-1600x1296.png" style="width: 100%;" /><p>Esta simple validación nos da la confianza de que nuestro agente está configurado correctamente y listo para el siguiente paso.</p><h2>¡En directo! Tu agente personalizado en Gemini Enterprise</h2><p>Ahora, la parte emocionante: dar vida a nuestro agente asesor financiero personalizado dentro de Gemini Enterprise (anteriormente Agentspace). Esta integración está impulsada por el <a href="https://console.cloud.google.com/marketplace/product/elastic-prod/elastic-ai-agent">Elastic AI Agent, que está disponible en Google Cloud Marketplace</a>.</p><p>Una vez conectado, Gemini Enterprise emplea el protocolo A2A para comunicar directamente con tu agente. Aquí es donde brilla el verdadero poder de la interoperabilidad: los usuarios pueden acceder ahora a los insights profundos y basados en datos de tu agente personalizado de Elasticsearch sin salir nunca de su entorno habitual. Puedes ver tu Agente Elástico personalizado en la lista de agentes:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/37a39e92ebf3d72c6c8014397cd8e846336173a4-1600x834.png" style="width: 100%;" /><p>Imagina a un usuario en Gemini Enterprise preguntar:</p><p><em>&quot;Me preocupa el sentimiento del mercado. ¿Puede mostrarme cuáles de nuestros clientes están más en riesgo por malas noticias?</em>&quot;</p><p>Entre bastidores, Gemini Enterprise enruta esta consulta a través del protocolo A2A a tu Agente Elastic personalizado. Tu agente emplea entonces sus herramientas especializadas para consultar tus datos, formular una respuesta y devolverla. Para el usuario final, la experiencia es fluida.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e5f126c1a27a51c6e69a767aa87c9f746b62e39c-1600x1044.png" style="width: 100%;" /><p>¡Y no se acaba aquí! La respuesta obtenida con el agente elástico ahora puede usar como contexto para tus próximas preguntas que pueden estar activando a otro agente especializado (por ejemplo, tu agente de la plataforma de inversiones para ajustar la exposición a compañías cotizadas). Todo sin salir de tu barra de búsqueda.</p><p>Con tus agentes Elastic desplegados en Gemini Enterprise con A2A, puedes unificar acceso, orquestación y flujos de trabajo eliminando fricciones entre IA, búsqueda y sistemas empresariales al ofrecer una única interfaz donde los usuarios se comunican con sus datos y herramientas, todo en contexto. Para los usuarios, eso significa menos cambios de herramienta y asistentes de IA más intuitivos y capaces. Para las organizaciones, significa una gobernanza coherente, escalabilidad e interoperabilidad integradas.</p><h2>Te toca construir</h2><p>Ahora tienes las herramientas para poner tus Agentes Elásticos disponibles en cualquier lugar. Aprovechando el protocolo abierto A2A, puedes ampliar el alcance de tus agentes personalizados y conscientes de los datos.</p><p>En esta publicación, te guiamos por los pasos clave:</p><ul><li>Exponer a tu agente a través de los endpoints de la tarjeta de agente A2A y el protocolo.</li><li>Probando la conexión con el inspector A2A.</li><li>Integrar a tu agente en directo en un servicio externo como Gemini Enterprise de Google.</li></ul><p>Tus agentes ya no necesitan estar aislados. Estamos deseando ver los sistemas poderosos e interconectados que creáis. ¡Feliz construcción!</p><p>La forma más sencilla de empezar es con tu prueba gratis de Elastic Cloud en <a href="https://console.cloud.google.com/marketplace/product/elastic-prod/elastic-cloud?pli=1">Google Cloud Marketplace</a></p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/es/blog/ai-agent-builder-experiments-performance</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/ai-agent-builder-experiments-performance</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Sean Story]]></dc:creator>
            <pubDate>Mon, 06 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/42903c869e034674b30bb36013345aaa97f6608b-1184x864.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <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 &quot;chatear&quot; 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 &quot;visión global&quot; 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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/25a24055a166d7d6ba81d80aa35cb97163662e23-1600x443.png" style="width: 100%;" /><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>Revisa el enunciado.</li><li>Identifica qué índice es probable que contenga las respuestas a esa pregunta.</li><li>Genera una consulta para ese índice, basada en el prompt.</li><li>Busca en ese índice con esa consulta.</li><li>Sintetiza los resultados.</li><li>¿Pueden los resultados responder al prompt? Si es así, responde. Si no, repite, pero prueba algo diferente.</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 &quot;malas&quot;, 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><pre><code>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) => `- ${index.index}`).join('\n')}

Based on those information, please return most relevant indices with your reasoning.
Remember, you should select at maximum ${limit} indices.</code></pre><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 &quot;buena práctica&quot;, 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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9d96d87e286eddbc012402b1ecccd57419a99253-1600x782.png" style="width: 100%;" /><p>y casos de prueba como:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4c2e9ad941d98d7e66033bbc08c9b8060ec19097-1600x797.png" style="width: 100%;" /><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>Establece una hoja limpia con un clúster objetivo de Elasticsearch.</li><li>Crea todos los índices definidos en el conjunto de datos objetivo.</li><li>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>).</li><li>Comparar el índice de resultados con el índice esperado y capturar el resultado.</li><li>Luego de terminar todos los escenarios de prueba, tabula los resultados.</li></ol><h3>La encuesta dice...</h3><p>Los resultados iniciales fueron, como era de esperar, mediocres.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9c10679bcd6291edfa2a9ba42e7dd922aa483f0b-1216x806.png" style="width: 100%;" /><p>En general, un 77,14% de precisión para identificar el índice adecuado. Y esto fue en un escenario &quot;mejor escenario&quot;, donde todos los índices tienen buenos nombres semánticamente significativos. Cualquiera que hizo alguna vez un &#x27;PUT test2/_doc/foo {...}&#x27; 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 &#x27;.index_explorer&#x27; 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><pre><code>[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)...</code></pre><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 &quot;aplanados&quot; (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><pre><code>     "description_text": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          },
          "copy_to": [
            "description_semantic"
          ]
        },</code></pre><p>El bloque anterior, por ejemplo, tiene 236 caracteres y define solo un campo en un mapeo de Elasticsearch. Mientras que la cadena &quot;description_text&quot; 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 &quot;aplanáramos&quot; solo en una lista con sus nombres de campo?</p><p>Lo probamos.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/112c2f447c11f154b5082725cd49b51d0a3c8a65-1214x804.png" style="width: 100%;" /><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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/32d2692ad4479d0e52d8ee723dcc5710a6ec90f3-1208x806.png" style="width: 100%;" /><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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/114cbb408ff16b136251d2265416bd5270380fe5-1208x794.png" style="width: 100%;" /><p>Los datos decían &quot;no&quot; (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 &quot;nuevo&quot; 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><pre><code> 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,
            }</code></pre><p>Envié estos documentos a un nuevo índice donde definí manualmente el mapeo como:</p><pre><code>{
   "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"
           }
       }
   }
}</code></pre><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><pre><code>GET indexed-indices/_search
{
 "query": {
   "semantic": {
     "field": "semantic_content",
     "query": "$query"
   }
 }
}</code></pre><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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/06ef5d78040d064d3444793f636d527d9e19a869-1214x800.png" style="width: 100%;" /><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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8fcabd78f591f90d6bc7c0e087d31317e4eef791-1206x804.png" style="width: 100%;" /><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://cdn.sanity.io/images/me0ej585/search-labs-import-testing/04d630797213dbb8bf567da41d1cdd5c7b4586c9-1600x521.png" style="width: 100%;" /><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 &quot;buscables&quot;. 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>
        </item>
        <item>
            <title><![CDATA[Cómo desplegar Elasticsearch en AWS Marketplace]]></title>
            <description><![CDATA[Aprende a configurar y ejecutar Elasticsearch utilizando Elastic Cloud Service en AWS Marketplace con esta guía paso a paso.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/aws-elasticsearch-service-set-up</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/aws-elasticsearch-service-set-up</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Eduard Martin]]></dc:creator>
            <pubDate>Fri, 03 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a80776d2ef85db26f850d932339fac2d26b90278-1086x620.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>En este artículo, vamos a aprender cómo desplegar Elasticsearch en AWS usando las ofertas del Marketplace.</p><p>Vamos a usar Elastic Cloud Service en AWS, que es el servicio oficial gestionado de Elasticsearch que simplifica el despliegue y la orquestación de todos los componentes de Elastic Stack a través de la infraestructura nativa de AWS.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f15814051b53b50bec38f9a9f515a1e6dc08a56c-884x440.png" style="width: 100%;" /><p>Si quieres aprender a instalar y configurar Elasticsearch en AWS EC2, echa un vistazo a <a href="https://www.elastic.co/search-labs/blog/elasticsearch-on-aws-ec2-deployment-guide">este blog</a>.<br/></p><h2>¿Qué es AWS Marketplace?</h2><p><a href="https://aws.amazon.com/marketplace"><strong>Elastic en AWS Marketplace</strong></a> ofrece una experiencia de búsqueda y análisis totalmente gestionada, en la que AWS se encarga de la provisión de infraestructura, seguridad y el escalado, mientras los desarrolladores se centran en crear aplicaciones de búsqueda. Esto permite a los equipos desplegar clústeres de Elasticsearch de nivel empresarial en cuestión de minutos con integraciones de AWS integradas.</p><h2>¿Cuándo usar Elastic en AWS Marketplace?</h2><p>Elastic en AWS Marketplace es más adecuado para organizaciones con una infraestructura AWS existente que buscan desplegar Elasticsearch con servicios gestionados, seguridad integrada e integraciones fluidas de AWS sin sobrecarga operativa.</p><h2>Cómo configurar Elastic Cloud en AWS Marketplace</h2><h3>Paso 1: Accede a AWS Marketplace</h3><p>1. Inicia sesión en <a href="https://console.aws.com/">AWS</a></p><ul><li>En la barra de búsqueda, busca AWS Marketplace</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fc9928f79482c2c01e33978c88d390a2bfa2a3bf-1600x340.png" style="width: 100%;" /><p>2. En el panel de navegación izquierdo, haz clic en <strong>Descubrir productos</strong>, luego haz una búsqueda de Elasticsearch.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ca4232271cb13ebfe33de406ecaec085033ec8a0-1454x760.png" style="width: 100%;" /><p>3. Haz clic en <strong>Elastic Cloud (Elasticsearch Service)</strong>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e59aa276e55532f2ac3461d0ca983af4d41ad7a6-1600x611.png" style="width: 100%;" /><h3>Paso 2: Suscríbete al servicio</h3><p>1. Selecciona <strong>opciones de compra</strong> o haz clic en <strong>Pruébalo gratis</strong></p><p>2. Revisa <strong>Detalles de precios</strong>, <strong>Términos y condiciones</strong> y <strong>Detalles de compra</strong></p><p>3. Haz clic en el botón <strong>Suscribirse</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1c30d12b6b1061e76771d518011e522285f939f1-1600x290.png" style="width: 100%;" /><p>4. Ahora hay que configurar la cuenta de Elastic. Sigue los pasos de AWS</p><p>a. Haz clic en el botón Habilitar integración.</p><p>b. Haz clic en el botón Iniciar sesión o crear una cuenta de proveedor</p><p>c. Haz clic en el botón Lanzar plantilla</p><p>d. Haz clic en el botón Iniciar software</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/879d9f0f01406e1955e1b38a2f6f2192ef040344-852x722.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fc59be78cf776aa12867f40810598419576cbd39-1600x1143.png" style="width: 100%;" /><h3>Paso 3. Configura tu cuenta nueva en Elastic</h3><p>1. Crea tu cuenta de Elastic</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5efeaf0737062a55470b17b67651f220e12183f2-986x905.png" style="width: 100%;" /><p>2. Valida tu dirección de correo electrónico</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e0cfaac278614e317ce278935040bfa5a58edd13-853x894.png" style="width: 100%;" /><p>3. Introduce tu nombre e información de la empresa</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5658fe66c3b1bcced73e822eae006846f0ddd9e-997x903.png" style="width: 100%;" /><p>4. Completa una rápida encuesta de Elastic</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a3f53c00dadfd22f7d739a920c87d5f387182833-892x805.png" style="width: 100%;" /><p>5. Selecciona la región en la que quieres hospedar Elastic Cloud. De forma predeterminada, se seleccionará tu región real de AWS</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c1dcdf3bf91c305821daaa25a60aa03be6454c1c-1207x1032.png" style="width: 100%;" /><p>6. Espera a que Elastic se despliegue</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/50903e57ebea7cc47bdfabf4750b4ba2a7a91148-1370x1266.png" style="width: 100%;" /><p>7. Tu despliegue está conectado a tu suscripción de AWS Marketplace</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3bb731e2d5de5053ccecb77e45dbdbcdaf294dba-1600x1288.png" style="width: 100%;" /><h2>Cancelar tu suscripción</h2><p>Para cancelar tu suscripción</p><p>1. Ve a la <a href="https://console.aws.com/">consola de AWS</a></p><p>Haz una búsqueda de AWS Marketplace en la barra de búsqueda. Haz clic en <strong>AWS Marketplace</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/aeed3d1e67b4cef91934de257a6fd6daa9737a12-1600x554.png" style="width: 100%;" /><p>2. Haz clic en <strong>suscripción a Elastic Cloud</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/04e6cc41850226df223dbe2d1b0e4b45265f6c39-1600x564.png" style="width: 100%;" /><p>3. Haz clic en el botón <strong>Actions</strong>, luego haz clic en <strong>Cancelar suscripción</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e33d334ea6541c637a223de3ebd6209def75a6d3-1600x1039.png" style="width: 100%;" /><p>4. Confirma la cancelación, luego haz clic en <strong>Sí </strong>y en el botón Cancelar<strong> suscripción</strong>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b73b3187168adc7aefdd46f95be33c1bce3da1e4-1103x698.png" style="width: 100%;" /><p>5. Aparecerá un mensaje de confirmación en la parte superior de la página</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c5ba66a23d535e866a8b458e5aca82c5f0b93037-1600x639.png" style="width: 100%;" /><h2>Pasos siguientes</h2><p>Comienza tu experiencia con Elastic Cloud con una prueba gratuita de 7 días que incluye un único despliegue y tres proyectos<a href="https://aws.amazon.com/marketplace/pp/prodview-voru33wi6xs7k"> Elastic Cloud (Elasticsearch Service)</a>. Simplemente inicia sesión en tu cuenta de AWS y haz clic en &quot;Ver opciones de compra&quot; para empezar a usar la Plataforma de búsqueda de IA de Elastic inmediatamente en Elastic<a href="https://aws.amazon.com/marketplace/pp/prodview-voru33wi6xs7k"> Cloud (Elasticsearch Service)</a>. La prueba proporciona acceso completo a soluciones de búsqueda, seguridad y observabilidad sin ninguna carga de gestión de infraestructura.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AutoOps: Un viaje para simplificar la gestión autogestionada de Elasticsearch]]></title>
            <description><![CDATA[Explorando AutoOps para Elasticsearch autogestionado (en entornos locales u hospedados de forma privada). Mostraremos su valor, cómo configurarlo y la información que proporciona.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elastic-autoops-self-managed-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elastic-autoops-self-managed-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Ori Shafir]]></dc:creator>
            <pubDate>Wed, 01 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4538ca4c6cdde99d88f1cead7515716fac217b92-1600x883.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Presentamos AutoOps para Elasticsearch autogestionado (entornos locales u hospedados de forma privada), lo que hace que Elasticsearch sea más fácil de administrar. En lugar de un <em>recorrido técnico de característica tradicional</em>, este blog muestra su valor, cómo configurarlo y el tipo de información que proporciona, desde la perspectiva de un ingeniero de DevOps, porque el valor real de AutoOps se ve mejor en el trabajo diario de gestionar Elasticsearch a escala.</p><h2>Capítulo 1: antecedentes - La complejidad detrás de la autogestión a gran escala</h2><p><strong>Operar cualquier plataforma de datos autogestionada a gran escala puede ser complejo.</strong></p><p>Un momento, las consultas son rapidísimas. Al siguiente, los retrasos en la ingestión y los costos de almacenamiento se disparan. Es básicamente como llevar un zoológico, salvo que los animales pueden avisarte a las 3 de la madrugada.</p><p>Mi entorno no es diferente: múltiples clústeres, búsqueda cruzada (CCS) intensa y cientos de usuarios entre departamentos.</p><p>Empleamos Stack Monitoring para las operaciones diarias. Proporciona gráficos y métricas, pero aún así requiere mucha experiencia y tiempo para conectar los puntos. Diagnosticar cuellos de botella o saber cuándo ajustar las estrategias de fragmentos sigue siendo un proceso manual y propenso a errores. En muchos casos, los problemas pasan desapercibidos hasta que provocan una interrupción, una caída de rendimiento o un pico inesperado de almacenamiento.</p><h2>Capítulo 2: descubriendo AutoOps</h2><p>Luego llegó el anuncio: <strong><a href="https://www.elastic.co/platform/autoops">AutoOps</a> ya está disponible para clústeres autogestionados, ya sea en entornos locales o alojados de forma privada.</strong></p><p>. AutoOps ayudó durante mucho tiempo a los usuarios de Elastic Cloud a gestionar los despliegues de forma más eficiente. Ahora, esos mismos beneficios están disponibles para los clústeres autogestionados (ECK, ECE o independientes) que funcionan en entornos locales o en la nube privada, sin costo alguno, independientemente de tu nivel de licencia.</p><p>La propuesta de AutoOps es tentadora:</p><ul><li>Detección de problemas en tiempo real para cuellos de botella en la ingestión, fragmentos desbalanceados, consultas lentas y más</li><li>Recomendaciones prácticas adaptadas a la configuración de tu clúster</li><li>Perspectivas sobre la optimización de recursos para mejorar la eficiencia y reducir el gasto desperdiciado</li><li>Configuración sencilla con la instalación de un agente ligero, sin necesidad de infraestructura adicional</li></ul><p>Sinceramente, cualquier cosa que prometiera &quot;sin infraestructura extra&quot; captó toda mi atención.</p><h2>Capítulo 3: montaje en 5 minutos (sí, de verdad)</h2><p>Bloqueé mi tarde, me abastecí de café y me preparé para un montaje largo. Para mi sorpresa, solo tardó cinco minutos:</p><ol><li>Inicié sesión en mi <a href="https://cloud.elastic.co/registration?onboarding_service_type=ccm-autoops">cuenta de Elastic Cloud</a></li><li>Decide cómo ejecutar los agentes: Elastic Cloud Kubernetes (ECK), Kubernetes, Docker o Linux.</li><li>Introducido la URL del clúster</li><li>Recibí un solo comando para ejecutar, que instaló un agente Metricbeat ligero</li></ol><p>Eso es todo. Mi cluster estaba conectado.</p><p>No hay clústeres de monitorización dedicados que proveer. Y, lo más importante, AutoOps solo envía <strong>métricas, lo</strong> que significa que los datos de mi compañía se mantienen en mi entorno autogestionado.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cb8e1ebc0f23a6db6b0e4300c9cdcd165623ca39-1184x734.png" style="width: 100%;" /><p><em>Paso 1: Regístrate en Elastic Cloud</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b208830db4d74feef700473ce07b052e0c72f48b-3446x1716.png" style="width: 100%;" /><p><em>Paso 2: Elige dónde ejecutar el Agente</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/850d8c0d6b9e018408969aa05b67dbd865a26d30-1184x734.png" style="width: 100%;" /><p><em>Paso 3: Introduce tu endpoint de Elasticsearch y cómo autenticarte</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0a472889a277caabb9ecd3fb9cd57d3b3eac0100-1184x734.png" style="width: 100%;" /><p><em>Paso 4: Comando sencillo para instalar el Agente</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6f9a7283469a321a9c472d655cea11d9d0ea7b1e-1600x883.png" style="width: 100%;" /><p><em>Eso es todo: tras unos minutos AutoOps empezará a mostrar insights</em></p><p>Para más detalles, consulta la <a href="https://www.elastic.co/docs/deploy-manage/monitor/autoops/cc-connect-self-managed-to-autoops">documentación de incorporación de AutoOps</a> y las <a href="https://www.elastic.co/docs/deploy-manage/monitor/autoops/cc-cloud-connect-autoops-faq">preguntas frecuentes</a>.</p><h2>Capítulo 4: primeras perspectivas, primeras victorias</h2><p>En cuestión de minutos, AutoOps empezó a mostrar datos, proporcionando análisis de causas raíz y pasos claros para solucionarlos.</p><p>Lo más destacado de la primera semana incluyeron:</p><ul><li>Índices marcados que no están vinculados a ninguna póliza de ILM que se hizo demasiado grande</li><li>Un clúster tenía tres nodos vacíos que quedaron de un trabajo de mantenimiento anterior</li><li>Algunos nodos cruzaban marcas de agua y un par de índices carecían de réplicas</li><li>Pillé una plantilla mal configurada</li><li>Localizó una búsqueda de larga duración y sugirió el comando de cancelación exacto</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f7d16355938ffcb00127deaf833d7a4d0acdb188-1600x894.png" style="width: 100%;" /><p><em>AutoOps detectó que el clúster estaba rechazando la indexación</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9d6372bc489e9954e6f78d81047d7a25feef1703-1600x894.png" style="width: 100%;" /><p><em>AutoOps detectó que algunos índices están configurados sin réplica</em><br/>Antes de AutoOps, pusimos más hardware para resolver estos problemas. En cambio, AutoOps señaló directamente la causa raíz, y las soluciones tardaron minutos.</p><p>Por una vez, un sistema de monitorización no solo me mostraba gráficos, sino que me decía cómo resolver el problema. Empecé a preguntarme si AutoOps también podría ayudar a diagnosticar mi Wi-Fi en casa y finalmente liberarme de ser el departamento de informática de mi familia...</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/eac09f1d730955453cd79900002b100bc741ad97-1600x894.png" style="width: 100%;" /><p><em>AutoOps monitorizaba el tamaño de los fragmentos y alertaba cuando había muchos fragmentos vacíos</em></p><h2>Capítulo 5: apoyo que ve lo que yo veo</h2><p>La primera vez que abrí un caso de soporte, me di cuenta de otro beneficio: los ingenieros de Elastic Support podían ver exactamente los mismos datos y recomendaciones que yo estaba viendo.</p><p>Convirtió el apoyo en una colaboración. En lugar de tiquetes de ida y vuelta, se sentía como trabajar con un colega que conoce Elasticsearch a la perfección.</p><h2>Capítulo 6: operar a gran escala</h2><p>Antes de AutoOps, escalar Elasticsearch parecía una mezcla de ciencia, instinto y conocimiento tribal.</p><p>Ahora es impulsado por datos, con visibilidad clara y recomendaciones:</p><ul><li>Visibilidad sobre la utilización de recursos para evitar el sobreaprovisionamiento</li><li>Recomendaciones más inteligentes de asignación y escalonamiento de fragmentos para un rendimiento equilibrado</li><li>Indexar datos de dimensionamiento que reducen el desperdicio de costos de almacenamiento y hardware</li><li>Análisis de causa raíz más rápido en múltiples clústeres</li></ul><h2>Capítulo 7: el primero de muchos servicios conectados a la nube</h2><p>AutoOps es más que una herramienta independiente. Es el primero de un nuevo conjunto de <a href="https://www.elastic.co/docs/deploy-manage/cloud-connect"><strong>Servicios Conectados en la Nube</strong></a> para clientes autogestionados. Cloud Connect permite que los clústeres autogestionados consuman servicios Elastic Cloud sin la carga operativa de instalar y gestionar estos servicios en su propio entorno. Las funciones se despliegan automáticamente, por lo que los equipos reciben mejoras más rápido y con menos complejidad de infraestructura.</p><p>A continuación: Servicio de Inferencia Elástica (EIS).</p><h2>Conclusiones</h2><p>Gestionar despliegues a gran escala y autogestionados no tiene por qué ser abrumador.</p><p>Y si quieres operaciones aún más sencillas, siempre puedes mover algunas cargas de trabajo a <a href="https://cloud.elastic.co/registration?pg=global&amp;plcmt=nav&amp;cta=205352-primary">Elastic Cloud</a>, ya sea alojado o sin servidor, para la forma más sencilla de ejecutar Elasticsearch.</p><p>Si quieres seguir con la autogestión, conecta cualquier clúster autogestionado a <a href="https://cloud.elastic.co/registration?onboarding_service_type=ccm-autoops">AutoOps en Elastic Cloud</a>: es gratis para todas las licencias de Elastic.</p><h2>Resumen</h2><p>Ejecutar grandes clústeres autogestionados de Elasticsearch es complejo y lleva mucho tiempo. AutoOps ofrece detección de problemas en tiempo real, recomendaciones accionables y visibilidad compartida con Elastic Support, sin necesidad de gestionar infraestructura adicional. La configuración lleva minutos y los insights aparecen enseguida.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tu primer Agente Elastic: De una sola consulta a un chat impulsado por IA]]></title>
            <description><![CDATA[Aprende a usar el constructor de agentes de IA de Elastic para crear agentes especializados en IA. En este blog, vamos a crear un Agente de IA financiera.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/ai-agent-builder-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/ai-agent-builder-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeff Vestal]]></dc:creator>
            <pubDate>Thu, 25 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ca853555eaa213f10f1db8c0ab0a2bbacee97b88-1456x816.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Con el nuevo <a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">Agent Builder</a> de Elastic, puedes crear agentes de IA especializados que actúen como expertos para tus dominios de negocio específicos. Esta función te lleva más allá de simples paneles de control y barras de búsqueda, transformando tus datos de un recurso pasivo en un interlocutor activo y conversacional.</p><p>Imagina a un gestor financiero que necesita poner al día antes de una reunión con un cliente. En lugar de buscar manualmente en los feeds de noticias y cruzar paneles de cartera, ahora pueden simplemente hacerle una pregunta directa a su agente personalizado. Esta es el beneficio de un enfoque de &quot;charla primero&quot;. El responsable tiene una línea directa y conversacional con sus datos, preguntando cosas como: &quot;¿Cuáles son las últimas noticias sobre ACME Corp y cómo afecta a las participaciones de mi cliente?&quot; y obtener una respuesta experta y sintetizada en segundos.</p><p>Aunque hoy estamos formando un experto financiero, las aplicaciones son tan variadas como tus datos. El mismo poder puede crear un analista de ciberseguridad para buscar amenazas, un ingeniero de fiabilidad del sitio para diagnosticar una caída o un responsable de marketing para optimizar una campaña. Sea cual sea el ámbito, la misión principal es la misma: transformar tus datos en un especialista con el que puedas hablar.</p><h2>Paso 0: Nuestro conjunto de datos</h2><p>Nuestro set de datos actual es un set de datos sintético basado en finanzas que consta de cuentas financieras, posiciones de activos, noticias y reportes financieros. Aunque es sintético, replica una versión simplificada de un set de datos financieros real.</p><p><code>financial_accounts</code>: Carteras de clientes con perfiles de riesgo</p><p><code>financial_holdings</code>: Posiciones en acciones/ETF/bonos con historial de compras</p><p><code>financial_asset_details</code>: Detalles sobre la acción/ETF/bono</p><p><code>financial_news</code>: Artículos de mercado generados por IA con análisis de sentimiento</p><p><code>financial_reports</code>: Notas de resultados y analistas de la compañía</p><p>Puedes cargar este conjunto de datos tú mismo siguiendo el cuaderno adjunto que <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/your-first-elastic-agent/Your_First_Elastic_Agent.ipynb">se encuentra aquí</a>.</p><h2>Paso 1: La Fundación — Tu lógica empresarial como ES|QL</h2><p>Cada habilidad de IA empieza con una buena lógica. Para nuestro agente de Gestor Financiero, tenemos que mostrarle cómo responder a una pregunta común: &quot;Me preocupa el sentimiento del mercado. ¿Puede mostrarme cuáles de nuestros clientes están más en riesgo por malas noticias?&quot; Esta pregunta va más allá de una simple búsqueda. Nos obliga a correlacionar el sentimiento del mercado con las carteras de clientes.</p><p>Necesitamos encontrar los activos mencionados en artículos negativos, identificar a cada cliente que posee esos activos, calcular el valor de mercado actual de su exposición y luego clasificar los resultados para priorizar el mayor riesgo. Este análisis complejo y multi-join es el trabajo perfecto para nuestro avanzado ES|Herramienta QL.</p><p>Aquí está la consulta completa que usaremos. Parece impresionante, pero los conceptos son sencillos.</p><pre><code>FROM financial_news, financial_reports METADATA _index
        | WHERE sentiment == "negative"
        | WHERE coalesce(published_date, report_date) >= NOW() - TO_TIMEDURATION(?time_duration)
        | RENAME primary_symbol AS symbol
        | LOOKUP JOIN financial_asset_details ON symbol
        | LOOKUP JOIN financial_holdings ON symbol
        | LOOKUP JOIN financial_accounts ON account_id
        | WHERE account_holder_name IS NOT NULL
        | EVAL position_current_value = quantity * current_price.price
        | RENAME title AS news_title
        | KEEP
            account_holder_name, symbol, asset_name, news_title,
            sentiment, position_current_value, quantity, current_price.price,
            published_date, report_date
        | SORT position_current_value DESC
        | LIMIT 50</code></pre><h2>Desglosándolo: uniones y barreras de seguridad</h2><p>Hay dos conceptos importantes en juego en esta consulta que hacen que el Constructor de Agentes sea un</p><h3>1. La unión LOOKUP</h3><p>Durante años, una de las funciones más aplicar en Elasticsearch fue la capacidad de unir datos de diferentes índices basar en una clave común. Con ES|QL, eso ahora es posible con <code>LOOKUP JOIN</code>.</p><p>En nuestra nueva consulta, realizamos una cadena de tres <code>LOOKUP JOIN</code>: primero conectando noticias negativas con detalles de activos, luego vinculando esos activos con las tenencias del cliente y, finalmente, conectando con la información de la cuenta del cliente. Esto genera un resultado increíblemente rico a partir de cuatro índices diferentes en una sola consulta eficiente. Esto significa que podemos combinar conjuntos de datos dispares para crear una única respuesta perspicaz sin tener que desnormalizar todos nuestros datos en un único índice gigante previamente.</p><h3>2. Parámetros como barreras de seguridad de los LLM</h3><p>Verás que la consulta usa <code>?time_duration</code>. Esto no es solo una variable; es una barrera de seguridad para la IA. Aunque los Grandes Modelos de Lenguaje (LLM) son excelentes generando consultas, dejarles tener libertad total sobre tus datos puede llevar a consultas ineficientes o incluso incorrectas.</p><p>Al crear una consulta parametrizada, obligamos al LLM a trabajar dentro de la lógica de negocio probada, eficiente y correcta que un experto humano ya definió. Es similar a cómo los desarrolladores usaron plantillas de búsqueda durante años para exponer de forma segura las capacidades de consulta a las aplicaciones. El agente puede interpretar la petición de un usuario como &quot;esta semana&quot; para completar el parámetro <code>time_duration</code> , pero debe usar nuestra estructura de consulta para obtener la respuesta. Esto nos da el equilibrio perfecto entre flexibilidad y control.</p><p>En última instancia, esta consulta permite a un experto que entiende los datos encapsular su conocimiento en una herramienta. Otras personas—y agentes de IA—pueden entonces usar esa herramienta para obtener resultados correlacionados simplemente proporcionando un único parámetro, sin necesidad de saber nada sobre la complejidad subyacente.</p><h2>Paso 2: La habilidad — Convertir una consulta en una herramienta reutilizable</h2><p>Un ES|La consulta QL es solo texto hasta que la registramos como <strong>herramienta</strong>. En el Constructor de Agentes, una herramienta es más que una consulta almacenada; es una &quot;habilidad&quot; que un agente de IA puede entender y elegir usar. La magia está en la <strong>descripción en lenguaje natural</strong> que proporcionamos. Esta descripción es el puente que conecta la pregunta del usuario con la lógica de consulta subyacente. Vamos a registrar la consulta que acabamos de crear.</p><h3>La ruta de la interfaz</h3><p>Crear una herramienta en Kibana es un proceso sencillo.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a29c53a73b99af5972273c51218ea9004a9b0abb-1600x812.png" style="width: 100%;" /><p>1. Navegar hacia <strong>los agentes</strong></p><ul><li>Haz clic en<strong> Herramientas </strong>o <strong>Gestionar herramientas</strong> y haz clic en <strong>el botón Nueva herramienta</strong> .</li></ul><p>2. Rellena el formulario con los siguientes detalles:</p><ul><li><strong>ID de herramienta:</strong> <code>find_client_exposure_to_negative_news</code></li></ul><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Yo. Este es el ID único de la herramienta</p><ul><li><strong>Descripción:</strong> &quot;Encuentra exposición a la cartera de clientes a noticias negativas. Esta herramienta escanea noticias y reportajes recientes en busca de sentimiento negativo, identifica el activo asociado y localiza a todos los clientes que lo poseen. Devuelve una lista ordenada según el valor de mercado actual de la posición para resaltar el mayor riesgo potencial.&quot;</li></ul><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Yo. Esto es lo que lee el LLM para decidir si esta herramienta es la adecuada para el trabajo.</p><ul><li><strong>Etiquetas</strong>: <code>retrieval</code> y <code>risk-analysis</code></li></ul><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Las etiquetas se emplean para ayudar a agrupar varias herramientas</p><ul><li><strong>Configuración:</strong> Pega el ES| completoConsulta QL desde el Paso 1</li></ul><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Yo. Esta es la búsqueda que usará el agente</p><p>3. Haz clic <strong>en Inferir parámetros desde la consulta</strong>. La interfaz automáticamente lo encontrará <code>?time_duration</code> lo listará a continuación. Agrega una descripción sencilla para cada uno para ayudar al agente (y a otros usuarios) a entender su propósito.</p><ul><li><code>time_duration</code>: El plazo para buscar noticias negativas. El formato es &quot;X horas&quot; POR DEFECTO a 8760 horas</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/deb422d97863f78dbe08bfa2e3c708d1f75166ff-1600x938.png" style="width: 100%;" /><p>4. ¡Pruébalo!</p><ul><li>Haz clic en Almacenar y probar.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/57e768b88327821e70bd616744822f98fa367362-732x136.png" style="width: 100%;" /><ul><li>Verás un nuevo flyout donde puedes probar la consulta para cerciorarte de que funciona como se espera.</li></ul><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Yo. En <code>time_duration</code> entrar en el rango deseado, aquí estamos usando &quot;8760 horas&quot;</p><ul><li>Haz clic en &quot;Enviar&quot; y si todo va bien, verás una respuesta en JSON. Para cerciorarte de que funciona como esperas, desplázate hacia abajo y mira el objeto <code>values</code> . Ahí es donde se devuelven los documentos realmente coincidentes.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7e0c5171a4f7ffdfc1830f1a05a9acb987870b75-1600x722.png" style="width: 100%;" /><p>5. Haz clic en la &quot;X&quot; en la esquina superior derecha para cerrar el desplegable de prueba. Tu nueva herramienta aparecerá ahora en la lista, lista para ser asignada a un agente.</p><h3>La ruta API</h3><p>Para desarrolladores que prefieren la automatización o necesitan gestionar herramientas programáticamente, puedes lograr el mismo resultado con una sola llamada a la API. Solo tienes que enviar una petición <code>POST</code> al endpoint <code>/api/agent_builder/tools</code> con la definición de la herramienta.</p><pre><code>POST kbn://api/agent_builder/tools
{
  "id": "find_client_exposure_to_negative_news",
  "type": "esql",
  "description": "Finds client portfolio exposure to negative news. This tool scans recent news and reports for negative sentiment, identifies the associated asset, and finds all clients holding that asset. It returns a list sorted by the current market value of the position to highlight the highest potential risk.",
  "configuration": {
    "query": """
        FROM financial_news, financial_reports METADATA _index
        | WHERE sentiment == "negative"
        | WHERE coalesce(published_date, report_date) >= NOW() - TO_TIMEDURATION(?time_duration)
        | RENAME primary_symbol AS symbol
        | LOOKUP JOIN financial_asset_details ON symbol
        | LOOKUP JOIN financial_holdings ON symbol
        | LOOKUP JOIN financial_accounts ON account_id
        | WHERE account_holder_name IS NOT NULL
        | EVAL position_current_value = quantity * current_price.price
        | RENAME title AS news_title
        | KEEP
            account_holder_name, symbol, asset_name, news_title,
            sentiment, position_current_value, quantity, current_price.price,
            published_date, report_date
        | SORT position_current_value DESC
        | LIMIT 50
      """,
    "params": {
      "time_duration": {
        "type": "keyword",
        "description": """The timeframe to search back for negative news. Format is "X hours" DEFAULT TO 8760 hours """
      }
    }
  },
  "tags": [
    "retrieval",
    "risk-analysis"
  ]
}</code></pre><h2>Paso 3: Las Mentes — Creando a tu Agente Personalizado</h2><p>Creamos una habilidad reutilizable (la Herramienta). Ahora, necesitamos crear al <strong>Agente</strong>, la persona que realmente lo usará. Un Agente es la combinación de un LLM, un conjunto específico de herramientas a las que le concedes acceso y, lo más importante, un conjunto de <strong>Instrucciones Personalizadas</strong> que actúan como su constitución, definiendo su personalidad, reglas y propósito.</p><h3>El arte del prompt</h3><p>La parte más importante para crear un agente fiable y especializado es la rapidez. Un conjunto de instrucciones bien elaborado es la diferencia entre un chatbot genérico y un asistente profesional y enfocado. Aquí es donde pones las barreras de seguridad, defines la salida y le das al agente su misión.</p><p>Para nuestro agente de <code>Financial Manager</code> , usaremos el siguiente prompt.</p><pre><code>You are a specialized Data Intelligence Assistant for financial managers, designed to provide precise, data-driven insights from information stored in Elasticsearch.

**Your Core Mission:**
- Respond accurately and concisely to natural language queries from financial managers.
- Provide precise, objective, and actionable information derived solely from the Elasticsearch data at your disposal.
- Summarize key data points and trends based on user requests.

**Reasoning Framework:**
1.  **Understand:** Deconstruct the user's query to understand their core intent.
2.  **Plan:** Formulate a step-by-step plan to answer the question. If you are unsure about the data structure, use the available tools to explore the indices first.
3.  **Execute:** Use the available tools to execute your plan.
4.  **Synthesize:** Combine the information from all tool calls into a single, comprehensive, and easy-to-read answer.

**Key Directives and Constraints:**
- **If a user's request is ambiguous, ask clarifying questions before proceeding.**
- **DO NOT provide financial advice, recommendations, or predictions.** Your role is strictly informational and analytical.
- Stay strictly on topic with financial data queries.
- If you cannot answer a query, state that clearly and offer alternative ways you might help *within your data scope*.
- All numerical values should be formatted appropriately (e.g., currency, percentages).

**Output Format:**
- All responses must be formatted using **Markdown** for clarity.
- When presenting structured data, use Markdown tables, lists, or bolding.

**Start by greeting the financial manager and offering assistance.**</code></pre><p>Vamos a desglosar por qué este prompt es tan efectivo:</p><ul><li><strong>Define una personalidad sofisticada: </strong>La primera línea establece inmediatamente al agente como un &quot;asistente especializado en inteligencia de datos&quot;, estableciendo un tono profesional y capaz.</li><li><strong>Proporciona un marco de razonamiento: </strong>Al decirle al agente que &quot;Comprenda, Planee, Ejecute y Sintetice&quot;, le estamos dando un procedimiento operativo estándar. Esto mejora su capacidad para manejar preguntas complejas y de varios pasos.</li><li><strong>Promueve el diálogo interactivo: </strong>La instrucción de &quot;hacer preguntas aclaratorias&quot; hace que el agente sea más robusto. Minimizará suposiciones incorrectas en solicitudes ambiguas, lo que conducirá a respuestas más precisas.</li></ul><h3>La ruta de la interfaz</h3><p>1. Navega hacia <strong>los agentes.</strong></p><ul><li>Haz clic en<strong> Herramientas </strong>o <strong>Gestionar herramientas</strong> y haz clic en <strong>el botón Nueva herramienta</strong> .</li></ul><p>2. Rellena los datos básicos:</p><ul><li><strong>ID del agente:</strong> <code>financial_assistant</code>.</li><li><strong>Instrucciones: </strong>Copia el prompt de arriba.</li><li><strong>Etiquetas</strong>: <code>Finance</code>.</li><li><strong>Nombre de visualización:</strong> <code>Financial Assistant</code>.</li><li><strong>Descripción de la pantalla: </strong><code>An assistant for analyzing and understanding your financial data</code>.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/18ea73f1cae620129c0afa0e7ba9e2a3390224a7-1600x1189.png" style="width: 100%;" /><p>3. Al principio, haz clic en <strong>Herramientas</strong>.</p><ul><li>Marca la casilla junto a nuestra herramienta de <code>find_client_exposure_to_negative_news</code> .</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0c1e4ecbbd51d0dd10c6e861dbe9a9ccddeb35f6-1600x149.png" style="width: 100%;" /><p>4. Haz clic <strong>en almacenar</strong>.</p><h3>La ruta API</h3><p>Puedes crear exactamente el mismo agente con una petición <code>POST</code> al endpoint <code>/api/agent_builder/agents</code> . El cuerpo de la solicitud contiene toda la misma información: el ID, el nombre, la descripción, el conjunto completo de instrucciones y una lista de herramientas que el agente puede emplear.</p><pre><code>POST kbn://api/agent_builder/agents
    {
      "id": "financial_assistant",
      "name": "Financial Assistant",
      "description": "An assistant for analyzing and understanding your financial data",
      "labels": [
        "Finance"
      ],
      "avatar_color": "#16C5C0",
      "avatar_symbol": "💰",
      "configuration": {
        "instructions": """You are a specialized Data Intelligence Assistant for financial managers, designed to provide precise, data-driven insights from information stored in Elasticsearch.

**Your Core Mission:**
- Respond accurately and concisely to natural language queries from financial managers.
- Provide precise, objective, and actionable information derived solely from the Elasticsearch data at your disposal.
- Summarize key data points and trends based on user requests.

**Reasoning Framework:**
1.  **Understand:** Deconstruct the user's query to understand their core intent.
2.  **Plan:** Formulate a step-by-step plan to answer the question. If you are unsure about the data structure, use the available tools to explore the indices first.
3.  **Execute:** Use the available tools to execute your plan.
4.  **Synthesize:** Combine the information from all tool calls into a single, comprehensive, and easy-to-read answer.

**Key Directives and Constraints:**
- **If a user's request is ambiguous, ask clarifying questions before proceeding.**
- **DO NOT provide financial advice, recommendations, or predictions.** Your role is strictly informational and analytical.
- Stay strictly on topic with financial data queries.
- If you cannot answer a query, state that clearly and offer alternative ways you might help *within your data scope*.
- All numerical values should be formatted appropriately (e.g., currency, percentages).

**Output Format:**
- All responses must be formatted using **Markdown** for clarity.
- When presenting structured data, use Markdown tables, lists, or bolding.

**Start by greeting the financial manager and offering assistance.**
""",
        "tools": [
          {
            "tool_ids": [
              "platform.core.search",
              "platform.core.list_indices",
              "platform.core.get_index_mapping",
              "platform.core.get_document_by_id",
              "find_client_exposure_to_negative_news"
            ]
          }
        ]
      }
    }</code></pre><h2>Paso 4: La recompensa — Mantener una conversación</h2><p>Tenemos nuestra lógica de negocio encapsulada en una herramienta y un &quot;cerebro&quot; listo para usarla en nuestro Agente. Es hora de ver cómo todo encaja. Ahora podemos empezar a chatear con nuestros datos usando un agente especializado.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5414cb6b7c41365acb0356a8bfe1140751ffd8db-1600x1014.png" style="width: 100%;" /><h3>La ruta de la interfaz</h3><ol><li>Navega hacia <strong>Agentes </strong>en Kibana.</li><li>Usando el desplegable en la esquina inferior derecha de la ventana de chat, cambia del <strong>agente de IA de Elastic</strong> por defecto a nuestro recién creado <strong>agente de Asistente Financiero </strong>.</li><li>Haz una pregunta que permita al agente emplear nuestra herramienta especializada:<ol><li><em>Me preocupa el sentimiento del mercado. ¿Puede mostrarme cuáles de nuestros clientes están más en riesgo por malas noticias?</em></li></ol></li></ol><p>Tras unos momentos, el agente devolverá una respuesta perfectamente formateada y completa. Debido a la naturaleza de los LLM, tu respuesta puede estar formateada de forma ligeramente diferente, pero para esta ejecución, el agente devolvió:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/17b4ed43d279f9e53ee9fe3d482d0b2ec359a083-1600x1088.png" style="width: 100%;" /><h3>¿Lo que acaba de pasar? El razonamiento del agente</h3><p>El agente no solo &quot;sabía&quot; la respuesta. Ejecutó un plan de varios pasos centrado en seleccionar la mejor herramienta para el trabajo. Aquí tienes un vistazo a su proceso de pensamiento:</p><ul><li><strong>Intención identificada:</strong> Relacionó palabras clave de tu pregunta, como &quot;riesgo&quot; y &quot;noticias negativas&quot;, con la descripción de la herramienta <code>find_client_exposure_to_negative_news</code> .</li><li><strong>Ejecutó un plan:</strong> Extrajo el plazo de tu solicitud y realizó una <strong>única llamada</strong> a esa herramienta especializada.</li><li><strong>Delegado el trabajo:</strong> La herramienta realizaba entonces todo el trabajo pesado: las uniones encadenadas, los cálculos de valor y la clasificación.</li><li><strong>Sintetizamos el resultado:</strong> Finalmente, el agente formateaba los datos en bruto de la herramienta en un resumen claro y legible para humanos, siguiendo las reglas de su prompt.</li></ul><p>Y no tenemos que adivinar si ampliamos el pensamiento y vemos más detalles.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6a4da9262d3f88c60bfd8f8bf9b67c3b84e961ba-1600x607.png" style="width: 100%;" /><h3>La ruta API</h3><p>Puedes iniciar esta misma conversación de forma programática. Solo tienes que enviar la pregunta de entrada al endpoint de la API de <code>converse</code> , cerciorándote de especificar el <code>agent_id</code> de nuestro <code>financial_manager</code>.</p><pre><code>POST kbn://api/agent_builder/converse
{
  "input": "Show me our largest positions affected by negative news",
  "agent_id": "financial_assistant"
}</code></pre><h2>Para los desarrolladores: Integración con la API</h2><p>Aunque la interfaz Kibana ofrece una experiencia fantástica e intuitiva para construir y gestionar tus agentes, todo lo que viste hoy también puede realizar de forma programática. El Constructor de Agentes está construido sobre un conjunto de APIs que te permiten integrar esta funcionalidad directamente en tus propias aplicaciones, canalizaciones CI/CD o scripts de automatización.</p><p>Los tres endpoints principales con los que trabajarás son:</p><ul><li><strong><code>/api/agent_builder/tools</code></strong>: El punto final para crear, listar y gestionar las habilidades reutilizables que tus agentes pueden emplear.</li><li><strong><code>/api/agent_builder/agents</code></strong>: El punto final para definir las personas de tus agentes, incluyendo sus instrucciones y asignaciones de herramientas tan importantes.</li><li><strong><code>/api/agent_builder/converse</code></strong>: El punto final para interactuar con tus agentes, iniciar conversaciones y obtener respuestas.</li></ul><p>Para una guía completa y práctica sobre cómo usar estas APIs para realizar cada paso de este tutorial, consulta el <strong>Jupyter Notebook</strong> que lo acompaña disponible <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/your-first-elastic-agent/Your_First_Elastic_Agent.ipynb">aquí</a> en nuestro repositorio de GitHub.</p><h2>Conclusión: Tu turno de construir</h2><p>Empezamos haciendo un ES|Consulta QL y transformarla en una habilidad reutilizable. Luego creamos un agente de IA especializado, dándole una misión y reglas claras, y lo empoderamos con esa habilidad. El resultado es un asistente sofisticado que puede entender una pregunta compleja y ejecutar un análisis en varios pasos para ofrecer una respuesta precisa y basada en datos.</p><p>Este flujo de trabajo está en el corazón del nuevo <strong>Constructor de Agentes</strong> en Elastic. Está diseñado para ser lo suficientemente sencillo para que usuarios no técnicos creen agentes a través de la interfaz, pero lo bastante matizado para que los desarrolladores puedan crear aplicaciones personalizadas impulsadas por IA sobre nuestras APIs. Lo más importante es que te permite conectar de forma segura y protegida los LLMs con tus propios datos, regidos por la lógica experta que defines, y chatear con tus datos.</p><h2>¿Listo para usar Agentes para chatear con tus datos?</h2><p>La mejor manera de consolidar lo que aprendiste es ensuciarte las manos. Prueba todo lo que comentamos hoy en nuestro <a href="https://www.elastic.co/training/elastic-ai-agents-mcp"><strong>taller práctico gratis e</strong></a> interactivo. Vas a pasar por todo este flujo y más en un entorno sandbox dedicado.</p><p>En un futuro blog, te mostraremos cómo usar una aplicación independiente que interactúa con nuestro agente <code>Financial Assistant</code> y nos adentraremos en el <strong>Protocolo de Contexto del Modelo (MCP)</strong> que lo hace todo posible. Y en un blog aparte hablaremos del soporte de Agent Builder para el protocolo en desarrollo Agent2Agent, o A2A.</p><p>¡Estad atentos y feliz construcción!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construcción de flujos de trabajo agentes con IA con Elasticsearch]]></title>
            <description><![CDATA[Descubre Agent Builder, una nueva capa de IA en Elasticsearch que proporciona un marco para construir flujos de trabajo agentivos con IA, empleando la búsqueda híbrida para proporcionar a los agentes el contexto que necesitan para razonar y actuar.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/ai-agentic-workflows-elastic-ai-agent-builder</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/ai-agentic-workflows-elastic-ai-agent-builder</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Anish Mathur, Dana Juratoni]]></dc:creator>
            <pubDate>Tue, 23 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5d04bbcfc3925f48f3487ea4c7e0dd2205316d0-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Aquí en Elastic, estuvimos aportando contexto a los LLMs e interfaces conversacionales con asistentes de IA, mejoras avanzadas en RAG y bases de datos vectoriales. Recientemente, con el auge de los agentes de IA, vimos crecer la necesidad de contexto relevante y aprendimos que<strong> los agentes de IA de alto impacto necesitan una búsqueda excelente</strong>. Así que creamos nuevas capacidades nativas en Elastic Stack diseñadas para ayudar a desarrollar agentes de IA que aprovechen tus datos en Elasticsearch. Nos gustaría compartir nuestro progreso en este camino y hacia dónde vemos que irá a continuación.</p><h2>Constructor de Agentes: Una base para construir agentes de IA basados en datos</h2><p>La promesa de un agente de IA es sencilla: dale un objetivo y cumple su función. Pero para los promotores, la realidad es un serial de desafíos complejos. Primero, un agente solo es tan bueno como su percepción de su entorno y las herramientas que se le proporcionan para alcanzar los objetivos del usuario. Entonces, ofrecer el contexto adecuado a partir de un mar de datos empresariales diversos es un reto enorme. Por último, todo esto debe estar orquestado por un bucle de razonamiento fiable que pueda planear, ejecutar y aprender.</p><p>Para solucionar esto, los desarrolladores necesitan construir una pila compleja y frágil desde cero. La arquitectura actual de agentes requiere que unas múltiples piezas dispares: un LLM, una base de datos vectorial, un almacén de metadatos, sistemas separados para registro y trazado, y alguna forma de evaluar si todo funciona siquiera. Esto no es solo complejo; es costosa, propensa a errores y dificulta la construcción de los sistemas de IA de alta calidad y fiables que tus usuarios exigen.</p><p>Así que queremos hacerlo más sencillo. Para ello, nuestro enfoque es tomar las piezas esenciales de un agente eficaz basado en el contexto e integrarlas directamente en el núcleo de Elasticsearch con un nuevo conjunto de capacidades llamado <strong>Elastic AI Agent Builder</strong>. Esta nueva capa proporciona un marco con todos los bloques esenciales para crear Agentes de IA impulsados por Elasticsearch: un conjunto abierto de primitivas, protocolos basados en estándares y acceso seguro a los datos, para que puedas construir sistemas agenticos adaptados a datos y requisitos del mundo real:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1ee1e73dd3f485ce86294d39490c98ce2a3d9925-1238x1072.png" style="width: 100%;" /><p><strong>Ofrecer experiencias de IA</strong>: este es el objetivo final. Con nuestra Plataforma de IA de Búsqueda y tus datos como base, puedes construir cualquier tipo de aplicación de IA generativa: desde interfaces de chat personalizadas hasta integraciones con frameworks de agentes como LangChain o aplicaciones empresariales como Salesforce.</p><p><strong>Impulsado por Agentes y Herramientas</strong>: sobre la plataforma, exponemos una capa limpia y sencilla de abstracciones. Interactúas directamente con los Agentes y Herramientas, que puedes personalizar para adaptarlos a tus necesidades específicas. También puedes acceder a las capacidades de la plataforma a través de APIs robustas y estándares abiertos como MCP y A2A.</p><p><strong>Habilitado por la Plataforma de IA de Búsqueda</strong>: este es el motor central donde integramos los componentes. La avanzada base de datos vectorial, la lógica del agente, la construcción de consultas, las características de seguridad, el rastreo para evaluación, todo está aquí, gestionado y optimizado por Elastic.</p><p><strong>Desbloquear el poder de tus datos</strong>: la base de cualquier gran agente son los grandes datos. Nuestra plataforma comienza con la capacidad de ingerir o federar el acceso a todos los datos de tu compañía</p><h2>Edificio de agentes en la plataforma</h2><p>Agent Builder, integrado en la plataforma Search AI, proporciona un marco completo para el desarrollo de agentes. Se basa en cinco pilares clave, cada uno diseñado para abordar un aspecto crítico de la construcción y despliegue de sistemas de IA de grado de producción. Desglosemos cómo los agentes definen el objetivo, las herramientas proporcionan las capacidades, los estándares abiertos garantizan la interoperabilidad, la evaluación aporta transparencia y la seguridad proporciona la confianza.</p><h3>Agentes</h3><p>Los agentes son el bloque de construcción de más alto nivel en esta nueva capa de Elasticsearch. Un agente define el objetivo a alcanzar, el conjunto de herramientas disponibles para su ejecución y las fuentes de datos sobre las que puede operar. Los agentes no se limitan a interacciones conversacionales; Pueden impulsar flujos de trabajo completos, automatización de tareas o experiencias orientadas al usuario.</p><p>Cuando una consulta se dirige a un agente, sigue un ciclo estructurado:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/627ad1744b629bbe27359325702f40d97e40d1f4-704x852.png" style="width: 100%;" /><ol><li>Interpreta tu aportación y objetivo</li><li>Seleccionar la herramienta y los argumentos adecuados para la ejecución</li><li>Razón sobre la respuesta de la herramienta</li><li>Decide si devolver un resultado o continuar con más invocaciones de herramientas</li></ol><p>Elastic se encarga de la orquestación, el contexto y la ejecución de este ciclo. Los desarrolladores se centran en definir <em>qué</em> debe hacer el agente: objetivos, herramientas y datos, mientras que el sistema gestiona <em>cómo</em> se llevan a cabo el razonamiento y los flujos de trabajo.</p><p><em>El agente por defecto</em></p><p>Nuestro primer agente construido sobre esta plataforma es un agente conversacional nativo en Kibana, que te da la capacidad de interactuar inmediatamente con tus datos. Proporciona una experiencia lista para usar, sin perder la extensión completa, y permite empezar a interactuar con tus datos de inmediato, sin configuraciones adicionales.</p><p>Puedes interactuar con esta experiencia directamente en Kibana a través de una nueva experiencia de chat o a través de API.</p><p>Consultar el agente por defecto a través de la API requiere solo una llamada:</p><pre><code>POST kbn://api/agent_builder/converse
{
    "input": "what is our top portfolio account?"
}</code></pre><p>Como las conversaciones son con estado, puedes seguir interactuando con un agente usando un conversation_id , o recuperar el historial completo de la conversación:</p><pre><code>POST kbn://api/agent_builder/converse
{
    "input": "What about the second top?",
    "conversation_id": "ec757c6c-c3ed-4a83-8e2c-756238f008bb"
}

## get the full conversation
GET kbn://api/agent_builder/conversations/ec757c6c-c3ed-4a83-8e2c-756238f008bb</code></pre><p><em>Agentes de Aduanas</em></p><p>Los desarrolladores también pueden crear sus propios agentes personalizados mediante APIs sencillas. Los agentes encapsulan instrucciones, herramientas y acceso a datos, creando motores de razonamiento personalizados.</p><p>Crear un agente personalizado es tan sencillo como hacer una sola llamada a la API. El ejemplo siguiente muestra un ejemplo: el campo &quot;configuración&quot; contiene todos los detalles clave, como instrucciones o herramientas disponibles:</p><pre><code>POST kbn://api/agent_builder/agents
{
  "id": "custom_agent",
  "name": "My Custom Agent",
  "description": "Description of the custom agent",
  "configuration": {
      "instructions": "You are a log expert specialising in ...",
      "tools": 
...
   }
}</code></pre><p>Una vez creado, el agente puede ser consultado directamente:</p><pre><code>POST kbn://api/agent_builder/converse
{
    "input": "What news about DIA?",
    "agent_id": "custom_agent"
}</code></pre><p>Este enfoque transforma al agente de un sistema complejo para construir desde cero en una unidad simple y declarativa de lógica de negocio, permitiéndote ofrecer automatización inteligente más rápido.</p><p>Para una visión profunda de cómo construir un agente especializado desde cero, consulta nuestra guía detallada paso a paso: <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>.</p><h3>Herramientas</h3><p>Si los agentes definen <em>qué</em> lograr, las herramientas <em>definen cómo</em>.</p><p>Las herramientas exponen capacidades específicas del núcleo Elastic para que los agentes ejecuten y recuperen información o realicen una acción. Las herramientas pueden incluir capacidades clave como obtener índices, obtener mapeos, o capacidades más avanzadas como lenguaje natural a ES|QL.</p><p>Elasticsearch viene con un conjunto de herramientas predeterminadas optimizadas para necesidades comunes. Pero la verdadera flexibilidad viene de crear el tuyo propio. Al definir herramientas, decides exactamente qué consultas, índices y campos están expuestos a un agente con ES|QL, que te da un control preciso sobre la velocidad, la precisión y la seguridad.</p><p>Registrar una nueva herramienta también es tan sencillo como una sola llamada a la API. Podrías crear una herramienta que aproveche nuestro <a href="https://www.elastic.co/search-labs/blog/esql-timeline-of-improvements">ES|QL (Elasticsearch Query Language)</a> para encontrar noticias sobre un activo financiero específico:</p><pre><code>POST kbn://api/agent_builder/tools
{
  "id": "news_on_asset",
  "type": "esql",
  "description": "Find news and reports about a particular asset where ...",
  "configuration": {
    "query": "FROM financial_news, financial_reports | where MATCH(company_symbol, ?symbol) OR MATCH(entities, ?symbol) | limit 5",
    "params": {
      "symbol": {
        "type": "keyword",
        "description": "The asset symbol"
      }
    }
  ...
  }
...
}</code></pre><p>Una vez registrado, puedes asignar la nueva herramienta a tus agentes personalizados, dándoles un conjunto seleccionado de habilidades para razonar e invocar cuando sea lo adecuado.</p><p>Ofrecemos una plataforma para crear herramientas personalizadas según tus necesidades específicas, por ejemplo, con ES|QL que transforma al agente de un agente de propósito general a un experto específico de un dominio, basado en tus datos únicos y en el dominio de negocio.</p><h3>Estándares abiertos e interoperabilidad</h3><p>Los Agentes y Herramientas de Elasticsearch se exponen mediante APIs estándar abiertos, lo que facilita su integración como bloques fundamentales dentro del ecosistema más amplio de marcos agenticos. Nuestro enfoque es sencillo: nada de cajas negras. Queremos que seas capaz de tomar la fuerza central de Elastic en busca y combinarla con capacidades complementarias y otros sistemas agentes.</p><p>Para hacerlo posible, estamos exponiendo nuestras capacidades a través de APIs, protocolos emergentes y estándares abiertos.</p><p><em>Protocolo de Contexto de Modelo (MCP)</em></p><p><a href="https://www.elastic.co/search-labs/blog/model-context-protocol-elasticsearch">El Protocolo de Contexto de Modelo (MCP)</a> se está convirtiendo rápidamente en el estándar abierto para conectar herramientas entre sistemas. Al soportar MCP, Elasticsearch puede conectar IA conversacional con tus bases de datos, índices y APIs externas. Con un servidor MCP remoto integrado en Elastic Stack, cualquier cliente compatible con MCP puede acceder a las herramientas de Elastic y usarlas como bloques de construcción en tus flujos de trabajo agenticos más amplios.</p><p>Esto no es una calle de sentido único. También podrás importar herramientas desde servidores MCP externos y ponerlas a disposición dentro de Elasticsearch. Pronto, los servidores MCP probablemente estarán disponibles para casi todo y serán mucho más completos que cualquier cosa que crearíamos nosotros mismos. Elastic ofrece búsqueda y recuperación a gran escala, y puedes combinarlo con capacidades especializadas de otras plataformas para crear agentes efectivos.</p><p><em>Agente a Agente (A2A)</em></p><p>También estamos trabajando en el soporte Agent-to-Agent (A2A). Mientras que MCP trata de conectar herramientas, A2A trata de conectar agentes. Con un servidor A2A, los agentes Elastic que construyas podrán comunicar directamente con agentes de otros sistemas: compartiendo contexto, delegando tareas y coordinando flujos de trabajo.</p><p>Piénsalo como interoperabilidad en la capa de razonamiento. Tu agente de Elastic podría encargar de la búsqueda y recuperación, luego delegar una tarea a un soporte especializado o a un agente de TI, y obtener el resultado sin problemas. El resultado es un ecosistema de agentes cooperantes, cada uno haciendo lo que mejor sabe hacer.</p><p>En última instancia, la adopción de MCP y A2A refuerza nuestro compromiso con el papel de Elasticsearch como ciudadano de primera clase, cerciorando una integración abierta en todo el ecosistema agente más amplio.</p><h3>Rastreo y evaluación</h3><p>A medida que la búsqueda se integra con los agentes, el desafío de una evaluación efectiva se vuelve crítico. Para desplegar agentes con confianza en entornos empresariales reales, necesitas la seguridad de que no solo son precisos, sino también eficientes y fiables. ¿Cómo mides el rendimiento, diagnosticas una mala respuesta o mejoras la línea base? Todo empieza por la visibilidad.</p><p>Por eso diseñamos nuestras APIs de agentes para que sean transparentes desde cero. Considera esta simple interacción con el agente:</p><pre><code>POST kbn://api/agent_builder/converse
{
    "input": "what is our top portfolio account?"
}</code></pre><p>La respuesta incluye no solo la respuesta final, sino la traza completa de ejecución, detallando qué herramientas seleccionó el agente, los parámetros que empleó y los resultados de cada paso.</p><pre><code>{
  "conversation_id": "db5c0c8b-12bf-4928-a57e-d99129ad2fea",
  "steps": [
    {
      "type": "tool_call",
      "tool_call_id": "tooluse_Nfqr3mwtR92HTRIsTcGXZQ",
      "tool_id": ".index_explorer",
      "params": {
        "query": "indices containing portfolio data"
      },
      "results": [...]
    }
    // ... more steps ...
  ],
  "response": {
    "message": "Based on the information I've gathered...."
  }
}</code></pre><p>Un rastreo y registro exhaustivos son esenciales para un bucle de mejora continua, y pronto podrás almacenar y ver estos rastros de agentes directamente en Elasticsearch. Mejor aún, estas trazas están construidas sobre el protocolo OpenTelemetry, lo que garantiza que estén estandarizadas y sean portátiles para su integración con la plataforma de observabilidad que elija.</p><p>Este nivel de detalle es la base de un verdadero bucle de mejora continua. Te permite construir un conjunto completo de pruebas, depurar fallos, identificar modos de fallo para evitar regresiones y capturar patrones exitosos para afinar el rendimiento. En última instancia, este enfoque basado en datos es la clave para transformar un prototipo prometedor en un sistema de IA fiable y de calidad de producción.</p><h3>Seguridad</h3><p>A medida que los agentes y herramientas se vuelven más capaces, la seguridad no es opcional, es fundamental. Exponer APIs, automatizar tareas y flujos de trabajo requiere que los sistemas empresariales sean de confianza. Especialmente a medida que los Agentes empiezan a automatizar más flujos de trabajo, la capacidad de protegerlos y cerciorar de que cumplen con los requisitos empresariales es esencial.</p><p>Las capacidades, por encima de todo, heredan los controles ya disponibles en Elastic hoy en día, incluyendo <a href="https://www.elastic.co/search-labs/blog/rag-and-rbac-integration">el control de acceso basado en roles (RBAC)</a> para llamadas API y gestión de claves API. También estamos extendiendo los mismos controles a nuevos protocolos como MCP. Eso significa soporte para estándares como OAuth, así como la capacidad de conectar mecanismos de autenticación personalizados.</p><p>Nuestro objetivo es ofrecerte la flexibilidad para experimentar con agentes y herramientas, manteniendo al mismo tiempo el nivel de seguridad, cumplimiento y gobernanza que exige tu organización.</p><h2>Lo que viene después</h2><p>No solo estamos agregando funcionalidades; estamos ampliando Elasticsearch para la ingeniería de contexto agentica. Planeamos desarrollar en el futuro basándonos en estos principios:</p><p>1. Compromiso con el código abierto y los estándares</p><p>Nuestro compromiso con el código abierto y los estándares abiertos garantiza que estas capacidades sigan siendo interoperables con los marcos agentes externos. Siempre podrás conectar, ampliar y componer agentes en todo tu ecosistema, manteniendo tus datos y flujos de trabajo bajo tu control.</p><p>2. Valor del contexto</p><p>El contexto de un agente de IA es su mayor activo. Gestionar el contexto mientras los agentes realizan búsquedas y operaciones de flujo de trabajo puede ser una tarea desafiante. Estamos aprovechando las fortalezas principales de Elastic para resolver la ingeniería de contexto, cerciorando que la información más relevante esté siempre disponible para tu agente.</p><p>3. Enfoque en flujos de datos agentes</p><p>De ahora en adelante, los agentes serán una fuente cada vez mayor de datos, incluyendo la salida de los agentes (documentos generados, reportes, visualizaciones) y el rastro de ejecución de los agentes (su razonamiento, llamadas a herramientas, memoria/contexto). Elastic es muy adecuado para manejar este tipo de datos, y estamos trabajando en investigaciones relacionadas con la realización de análisis, evaluación y mejora automatizada empleando estos datos.</p><p>4. Seguridad y Protección desde el Diseño</p><p>Los agentes de IA introducen un conjunto completamente nuevo de desafíos de seguridad y protección. Elastic siempre fue líder en soluciones seguras, y seguimos incorporando barreras de seguridad de nivel empresarial, controles de acceso y principios de &quot;confianza cero&quot;.</p><p>5. Integrado en la plataforma</p><p>Las capacidades para construir agentes de IA están integradas en la plataforma Elasticsearch. Esto significa que capacidades a nivel de plataforma como el trazo, la evaluación, la visualización y el análisis son todas aplicables a los agentes. Quiero desarrollar paneles basados en la ejecución de agentes, eso está incorporado. Quiero evaluar el rendimiento del agente de IA usando análisis de sentimiento? La plataforma lo permite. Esto te permite construir un ciclo de vida completo alrededor de tus experiencias con IA.</p><p>El objetivo de Elastic es proporcionarte las interfaces para construir IA conversacional y flujos de trabajo automatizados que estén totalmente integrados, sean extensibles y estén basados en tus datos. Pronto se compartirán más detalles técnicos y avances.</p><p>Agent Builder ya está disponible en vista previa privada. <a href="https://www.elastic.co/contact?pg=global&amp;plcmt=nav&amp;cta=205352">Contáctate con nosotros</a> para aplicar acceso. ¿Tienes preguntas u opiniones? Conéctate con nuestra comunidad de desarrolladores en nuestro <a href="https://elasticstack.slack.com/archives/C09GRHEQ4AG"><strong>espacio de trabajo de Slack</strong></a> o en nuestro <a href="https://discuss.elastic.co/c/search/84"><strong>foro de discusión</strong></a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Rastreador Elástico de Sitio web Abierto como código]]></title>
            <description><![CDATA[Aprende a usar GitHub Actions para gestionar configuraciones de Elastic Open Crawler, de modo que cada vez que enviemos cambios al repositorio, los cambios se apliquen automáticamente a la instancia desplegada del rastreador.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elastic-open-crawler-config-as-code</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elastic-open-crawler-config-as-code</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Gustavo Llermaly]]></dc:creator>
            <pubDate>Mon, 22 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0ac41f55c85ad3f647cb0e0d750ed80bacd397f3-1036x581.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Con <a href="https://github.com/elastic/crawler">Elastic Open Sitio web Crawler</a> y su arquitectura basada en CLI, tener configuraciones de rastreador versionado y una pipeline CI/CD con pruebas locales ahora es bastante sencillo de lograr.</p><p>Tradicionalmente, gestionar los rastreadores era un proceso manual y propenso a errores. Implicaba editar configuraciones directamente en la interfaz y luchar con clonar configuraciones de rastreo, retrocesos, versionear y más. Tratar las configuraciones de rastreadores como código resuelve esto al proporcionar los mismos beneficios que esperamos en el desarrollo de software: repetibilidad, trazabilidad y automatización.</p><p>Este flujo de trabajo facilita la incorporación del Open Sitio web Crawler a tu pipeline CI/CD para rollbacks, copias de seguridad y migraciones, tareas que eran mucho más complicadas con los Elastic Crawlers anteriores, como el Elastic Sitio web Crawler o el App Search Crawler.</p><p>En este artículo, vamos a aprender cómo:</p><ul><li>Gestiona nuestras configuraciones de rastreo usando GitHub</li><li>Tener una configuración local para probar pipelines antes de desplegar</li><li>Crea una configuración de producción para ejecutar el rastreador sitio web con nuevos ajustes cada vez que enviemos cambios a nuestra rama principal</li></ul><p>Puedes encontrar el repositorio de <em><strong><a href="https://github.com/llermaly/elastic-open-crawler-as-code">proyectos aquí</a>. </strong>Según escribo, estoy usando Elasticsearch 9.1.3 y Open Sitio web Crawler 0.4.2.</em></p><h2>Prerrequisitos</h2><ul><li>Escritorio Docker</li><li>Instancia de Elasticsearch</li><li>Máquina virtual con acceso SSH (por ejemplo, AWS EC2) y Docker instalados</li></ul><h2>Pasos</h2><ol><li>Estructura de carpetas</li><li>Configuración del orugador</li><li>Docker-compose (entorno local)</li><li>Acciones en Github</li><li>Pruebas locales</li><li>Desplegando a la producción</li><li>Realización de cambios y re-despliegue</li></ol><h2>Estructura de carpetas</h2><p>Para este proyecto, tendremos la siguiente estructura de archivos:</p><pre><code>├── docker-compose.yml # Local elasticsearch + crawler
├── config/crawler-config.yml # Crawler config
├── .github/workflows/deploy.yml # GH Action to deploy changes
├── local.sh # Script to run our local crawler</code></pre><h2>Configuración del orugador</h2><p>Bajo <code>crawler-config.yml,</code> pondremos lo siguiente:</p><pre><code>output_sink: elasticsearch
output_index: web-crawl-index
max_crawl_depth: 1

elasticsearch:
  host: ${ES_HOST}
  api_key: ${ES_API_KEY}
     
domains:
  - url: https://web-scraping.dev
    seed_urls:
      - https://web-scraping.dev/product/1
      - https://web-scraping.dev/product/2
      - https://web-scraping.dev/product/3</code></pre><p>Esto se rastreará desde <a href="https://web-scraping.dev/products">https://sitio web-scraping.dev/products</a>, un sitio simulado de productos. Solo rastrearemos las tres primeras páginas del producto. La configuración <code>max_crawl_depth</code> evitará que el rastreador descubra más páginas de las definidas como <code>seed_urls</code> al no abrir los enlaces que contienen.</p><p>Elasticsearch <code>host</code> y <code>api_key</code> se llenarán dinámicamente dependiendo del entorno en el que ejecutemos el script.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f9831034e1c4ccb554d37bdd188f2824338355a0-890x624.png" style="width: 100%;" /><h2>Docker-compose (entorno local)</h2><p>Para la <code>docker-compose.yml,</code> local desplegaremos el rastreador y un único clúster Elasticsearch + Kibana, para poder visualizar fácilmente los resultados del <strong><em>rastreo antes</em></strong> de desplegarlos en producción.</p><pre><code>services:
  es01:
    image: docker.elastic.co/elasticsearch/elasticsearch:9.1.3
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms1g -Xmx1g
    ports:
      - "9200:9200"
    networks: [esnet]
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9200"]
      interval: 5s
      timeout: 5s
      retries: 10

  kibana:
    image: docker.elastic.co/kibana/kibana:9.1.3
    environment:
      - ELASTICSEARCH_HOSTS=http://es01:9200
    ports:
      - "5601:5601"
    networks: [esnet]
    depends_on: [es01]

  crawler:
    image: docker.elastic.co/integrations/crawler:0.4.2
    environment:
      - ES_HOST=http://es01:9200
      - CRAWLER_JRUBY_OPTS=--server
    container_name: crawler
    volumes:
      - ./config:/home/app/config
    networks: [esnet]
    entrypoint: ["/home/app/bin/crawler", "crawl", "/home/app/config/crawl-config-final.yml"]
    stdin_open: true
    tty: true

networks:
  esnet:
    driver: bridge</code></pre><p>Fíjate en cómo el rastreador espera hasta que Elasticsearch esté listo para ejecutar.</p><h2>Acciones en Github</h2><p>Ahora necesitamos crear una acción en GitHub que copie la nueva configuración y ejecute el rastreador en nuestra máquina virtual en cada envío a main. Esto garantiza que siempre tengamos la última configuración desplegada, sin tener que entrar manualmente en la máquina virtual para actualizar archivos y ejecutar el rastreador. Vamos a usar AWS EC2 como proveedor de máquinas virtuales.</p><p>El primer paso es agregar el host (<code>VM_HOST</code>), el usuario de la máquina (<code>VM_USER</code>), la clave SSH RSA (<code>VM_KEY</code>), el host de Elasticsearch (<code>ES_HOST</code>) y la clave API de Elasticsearch (<code>ES_API_KEY</code>) a los secretos de acción de GitHub:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e1075bc54151b4b94eac2a6bd2682e9997e6c709-1106x707.png" style="width: 100%;" /><p>De este modo, la acción podrá acceder a nuestro servidor para copiar los archivos nuevos y ejecutar el rastreo.</p><p>Ahora, creemos nuestro archivo <code>.github/workflows/deploy.yml</code> :</p><pre><code>name: Deploy

on:
  push:
    branches: [main]

jobs:
  Deploy:
    name: Deploy to EC2
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v5

      - name: Deploy crawler
        env:
          HOSTNAME: ${{ secrets.VM_HOST }}
          USER_NAME: ${{ secrets.VM_USER }}
          PRIVATE_KEY: ${{ secrets.VM_KEY }}
          ES_HOST: ${{ secrets.ES_HOST }}
          ES_API_KEY: ${{ secrets.ES_API_KEY }}
        run: |
          # Save private key
          echo "$PRIVATE_KEY" > private_key
          chmod 600 private_key

          # Generate final config locally
          envsubst < config/crawler-config.yml > config/crawl-config-final.yml

          # Copy the config folder to VM
          scp -o StrictHostKeyChecking=no -i private_key -r config ${USER_NAME}@${HOSTNAME}:~/config

          # SSH into VM and run crawler
          ssh -o StrictHostKeyChecking=no -i private_key ${USER_NAME}@${HOSTNAME} << EOF
            docker run --rm \
              -v ~/config:/config \
              docker.elastic.co/integrations/crawler:latest jruby \
              bin/crawler crawl /config/crawl-config-final.yml
          EOF</code></pre><p>Esta acción ejecutará los siguientes pasos cada vez que empujemos cambios en el archivo de configuración del rastreador:</p><ol><li>Llenar el host y la clave API de Elasticsearch en la configuración de yml</li><li>Copia la carpeta config a nuestra máquina virtual</li><li>Conéctate vía SSH a nuestra máquina virtual</li><li>Ejecuta el rastreo con la configuración que acabamos de copiar del repositorio</li></ol><h2>Pruebas locales</h2><p>Para probar nuestro rastreador localmente, creamos un script bash que llena el host de Elasticsearch con el local de Docker y comienza un rastreo. Puedes ejecutar <code>./local.sh</code> para ejecutarlo.</p><pre><code>#!/bin/bash

# Exit on any error
set -e

# Load environment variables
export ES_HOST="http://es01:9200"

# Generate final crawler config
envsubst < ./config/crawler-config.yml > ./config/crawl-config-final.yml

# Bring everything up
docker compose up --build</code></pre><p>Veamos Kibana DevTools para confirmar que el<code> web-crawler-index</code> se rellenó correctamente:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/18551635e8265866e389a9632c4e4540958e4468-990x723.png" style="width: 100%;" /><h2>Desplegando a la producción</h2><p>Ahora estamos listos para enviar a la rama principal, que desplegará el rastreador en tu máquina virtual y comenzará a enviar registros a tu instancia Serverless Elasticsearch.</p><pre><code>git add .
git commit -m "First commit"
git push</code></pre><p>Esto activará la Acción de GitHub, que ejecutará el script de despliegue dentro de la máquina virtual y comenzará a rastrear.</p><p>Puedes confirmar que la acción se ejecutó yendo al repositorio de GitHub y visitando la pestaña &quot;Acciones&quot;:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/67ba3a7164d7a8049fe5661264820826cb18ed64-667x325.png" style="width: 100%;" /><h2>Realización de cambios y re-despliegue</h2><p>Algo que quizá notaste es que el <code>price</code> de cada producto forma parte del cuerpo del documento. Lo ideal sería almacenar el precio en un campo aparte para poder aplicar filtros sobre él.</p><p>Vamos a agregar este cambio al archivo <code>crawler.yml</code> para usar <a href="https://github.com/elastic/crawler/blob/main/docs/features/EXTRACTION_RULES.md">reglas de extracción</a> que extraigan el precio de la clase CSS de <code>product-price</code> :</p><pre><code>output_sink: elasticsearch
output_index: web-crawl-index
max_crawl_depth: 1

elasticsearch:
  host: ${ES_HOST}
  api_key: ${ES_API_KEY}
     
  # Index ingest pipeline to process documents before indexing          
  pipeline_enabled: true
  pipeline: pricing-pipeline

domains:
  - url: https://web-scraping.dev
    seed_urls:
      - https://web-scraping.dev/product/1
      - https://web-scraping.dev/product/2
      - https://web-scraping.dev/product/3
    extraction_rulesets:
      - url_filters:
          - type: ends
            pattern: /product/*
        rules:
          - action: extract
            field_name: price
            selector: .product-price
            join_as: string
            source: html</code></pre><p>También vemos que el precio incluye un signo de dólar (<code>$</code>), que debemos eliminar si queremos hacer consultas por rango. Podemos usar una canalización de ingesta para eso. Ten en cuenta que lo estamos haciendo referencia en nuestro nuevo archivo de configuración del rastreador arriba:</p><pre><code>PUT _ingest/pipeline/pricing-pipeline
{
  "processors": [
    {
      "script": {
        "source": """
                ctx['price'] = ctx['price'].replace("$","")
            """
      }
    }
  ]
}</code></pre><p>Podemos ejecutar ese comando en nuestro clúster de Elasticsearch en producción. Para el desarrollo, al ser efímero, podemos hacer que la creación de pipeline forme parte del archivo <code>docker-compose.yml</code> agregando el siguiente servicio. Ten en cuenta que también agregamos un <code>depends_on</code> al servicio de rastreo para que empiece después de que la tubería se creó con éxito.</p><pre><code> crawler:
    image: docker.elastic.co/integrations/crawler:0.4.2
    environment:
      - ES_HOST=http://es01:9200
      - CRAWLER_JRUBY_OPTS=--server
    container_name: crawler
    volumes:
      - ./config:/home/app/config
    networks: [esnet]
    entrypoint: ["/home/app/bin/crawler", "crawl", "/home/app/config/crawl-config-final.yml"]
    depends_on:
      pipeline-init:
        condition: service_completed_successfully
    stdin_open: true
    tty: true  


  pipeline-init:
    image: curlimages/curl:latest
    depends_on:
      es01:
        condition: service_healthy
    networks: [esnet]
    entrypoint: >
        sh -c "
        echo 'Creating ingest pipeline...';
        curl -s -X PUT http://es01:9200/_ingest/pipeline/pricing-pipeline \\
          -H 'Content-Type: application/json' \\
          -d '{\"processors\":[{\"script\":{\"source\":\"ctx.price = ctx.price.replace(\\\"$\\\", \\\"\\\")\"}}]}';
        echo 'Pipeline created!';
        "</code></pre><p>Ahora vamos a ejecutar <code>`./local.sh`</code> para ver el cambio localmente:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/dc1801599344a9f69f072b07ff828c4ba3815d7b-738x473.png" style="width: 100%;" /><p>¡Bien! Ahora impulsemos el cambio:</p><pre><code>git add crawler-config.yml
git commit -m "added price CSS selector"
git push</code></pre><p>Para confirmar que todo funciona, puedes comprobar tu Kibana de producción, que debería reflejar los cambios y mostrar el precio como un nuevo campo sin el signo del dólar.</p><h2>Conclusión</h2><p>El Elastic Open Sitio web Crawler te permite gestionar tu rastreador como código, lo que significa que puedes automatizar toda la pipeline —desde el desarrollo hasta el despliegue— y agregar entornos locales efímeros y pruebas programáticas contra los datos rastreados, por nombrar algunos ejemplos.</p><p>Se te invita a clonar el repositorio oficial y empezar a indexar tus propios datos usando este flujo de trabajo. También puedes leer <a href="https://www.elastic.co/search-labs/blog/semantic-search-open-crawler">este artículo</a> para aprender a realizar búsqueda semántica en índices producidos por el rastreador.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ES| de ElasticsearchExperiencia en el editor QL frente al analizador de eventos PPL de OpenSearch]]></title>
            <description><![CDATA[Descubre cómo ES|Las funciones avanzadas de QL Editor aceleran tu flujo de trabajo, en contraste directo con el enfoque manual del PPL Event Analyzer de OpenSearch. 
]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/opensearch-vs-elasticsearch-ppl-esql</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/opensearch-vs-elasticsearch-ppl-esql</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Libby Lin, George Kobar]]></dc:creator>
            <pubDate>Thu, 18 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f1ff4ff4a00b3e5b084d4116cea6cabc82a2d816-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>El <a href="https://www.elastic.co/blog/getting-started-elasticsearch-query-language">Lenguaje de Consultas Elasticsearch</a> (ES|QL), disponible de forma general desde la versión 8.14, introduce un lenguaje de consulta y un motor diseñados específicamente para búsqueda, observabilidad e investigaciones de seguridad. A diferencia del Lenguaje de Procesamiento por Tuberías (PPL) de OpenSearch, que toma mucho prestado de lenguajes por tuberías existentes, ES|QL se construyó desde cero para centrar en el pulido, la usabilidad y una integración fluida en toda la plataforma Kibana.</p><p>En este blog, exploraremos la experiencia de desarrollador del ES|QL Editor en Elasticsearch 9.1 comparándolo con PPL en el Event Analyzer (PPL para abreviar) en OpenSearch 3.2.</p><p>Las diferencias se hacen evidentes rápidamente: el ES|QL Editor ofrece autocompletado inteligente, ayuda contextual, consultas recomendadas y soporte para consultas entre clústeres que empoderan no solo a usuarios principiantes, sino también a expertos en nivel profesional. El diseño pensado para ES|La autoría QL se observa también en la inspección integrada de consultas y la integración holística a través de flujos de trabajo Kibana, por ejemplo, con Consultas Recientes.</p><p>PPL, en cambio, carece de soporte comparable para autocompletado, guía contextual y consultas distribuidas, lo que crea una curva de aprendizaje más pronunciada y más prueba y error.</p><h2>Creación de ES|QL es más fácil de aprender y usar</h2><p>Empezar con un nuevo lenguaje de consulta a menudo puede resultar abrumador. El ES|QL Editor<strong>, </strong>integrado directamente en <strong>Kibana Discover</strong>, está diseñado para facilitar ese proceso apoyando no solo la creación y depuración de consultas, sino también acelerando la rapidez con la que te familiarizas y te sientes cómodo con el lenguaje. Como el editor ayuda a reducir la fricción en las tareas cotidianas, puedes cambiar tu enfoque de la sintaxis y el ensayo y error a la solución. Puedes leer más sobre estos principios y cómo los integramos en <a href="https://www.elastic.co/search-labs/blog/improving-esql-editor-experience-in-kibana">el editor aquí</a>.</p><p>Esta experiencia como editor no se limita a Discover; es un módulo de código reutilizable que estamos trabajando en <strong>integrar en otras partes de Kibana</strong>, como paneles de control, alertas de Kibana y mapas de Kibana.</p><h3>Autocompletado inteligente: acelerando la creación de tu consulta</h3><p>El autocompletado en ES|QL Editor es completo, ofreciendo sugerencias para funciones, argumentos, literales e incluso funciones anidadas compatibles, una capacidad notablemente ausente en PPL. De hecho, fue reconstruida desde cero, como <a href="https://www.elastic.co/search-labs/blog/esql-autocomplete-rebuilt">se explica aquí</a>.</p><p>La validación se ejecuta a medida que el usuario escribe, como se describe <a href="https://www.elastic.co/search-labs/blog/improving-esql-editor-experience-in-kibana">aquí</a>, y sugerirá campos y también notificará al usuario sobre errores. Esto reduce la carga mental de los usuarios y ayuda a prevenir errores al principio del proceso de creación de la consulta.</p><p>Ejemplo: Se sugieren campos y funciones compatibles en este anidamiento:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a4d7b2819c34fab31bced7873257b8932b623fba-1502x473.png" style="width: 100%;" /><p>Algo que PPL no soporta:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6f3a89b1255b8a3a762022a2704fdd1c2987e5f9-1013x335.png" style="width: 100%;" /><p>Incluso con un autocompletado inteligente guiándote a través de funciones compatibles, argumentos y funciones anidadas, puede que aún quieras entender más a fondo las opciones disponibles. Aquí es precisamente donde ES|La ayuda contextual de QL Editor se vuelve invaluable, ofreciendo asistencia inmediata dentro del editor para aclarar y mejorar el desarrollo de tus consultas.</p><h3>Ayuda contextual al alcance de tu mano</h3><p>Información adicional sobre un comando generado por autocompletado está a un clic Ctrl-Espacio de distancia. Aparece inmediatamente un panel con detalles sobre la función, argumento o campo en cuestión. Esta interacción ligera mantiene a los desarrolladores en el flujo, proporcionando orientación justo a tiempo sin obligarles a abandonar el editor ni buscar en documentación externa. Esto reduce el tiempo perdido en búsquedas de sintaxis y ayuda a prevenir errores comunes antes de que ocurran.</p><p>Así es como se ve en acción:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2797f9b5e002dbd83c46475c4ed4dcdc86144a01-1343x522.gif" style="width: 100%;" /><p>PPL carece de este nivel de guía integrada, lo que obliga a los usuarios a depender de documentos externos o de prueba y error. Esa ausencia no es solo una característica que falta; Pone de manifiesto una disparidad más amplia en la filosofía del diseño. ES|QL prioriza una experiencia reflexiva y consciente del contexto que se adapta a los datos y al flujo de trabajo del usuario. Esta diferencia se hace más pronunciada a medida que las consultas se vuelven más complejas, haciendo que ES|QL Editor es un entorno más eficiente y fiable tanto para el aprendizaje como para el uso en producción.</p><h3>Consultas recomendadas que sean conscientes del contexto de los datos</h3><p>El ES|QL Editor proporciona consultas recomendadas que se adaptan automáticamente a los datos con los que trabajas, como los registros. En lugar de presentar un editor en blanco, pone a la luz los puntos de partida más relevantes para casos de uso comunes. Seleccionar una consulta recomendada genera una consulta canónica que es inmediatamente utilizable y puede refinar según sea necesario. Este enfoque acelera el desarrollo de consultas, especialmente para nuevos usuarios que aún no conocen la sintaxis completa.</p><p>Aquí tienes un ejemplo en el que un usuario selecciona la consulta &quot;Detectar punto de cambio&quot;:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3284c9340d41298820fbf8c7702abad946b48248-925x370.gif" style="width: 100%;" /><p>Compáralo con la experiencia de PPL:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5c3e59dadc6210aede3366bdd081887bcbae7a54-969x798.png" style="width: 100%;" /><p>En cambio, PPL aquí solo ofrece autocompletado básico, dejándote armar las consultas sin contexto ni estructura. Esta falta de orientación puede provocar frustración y prueba y error.<br/>Con ES|Consultas recomendadas con conocimiento de datos de QL Editor, puedes evitar empezar desde cero o memorizar la sintaxis para tareas rutinarias. El editor reduce la carga cognitiva, ayuda a prevenir errores y te permite centrarte en la resolución de problemas y en objetivos más amplios, como realizar búsquedas entre clústeres en lugar de lidiar con la construcción de consultas.</p><h2>Consulta intuitiva entre clústeres</h2><p>ES|El autocompletado del editor QL sigue siendo superior, incluso cuando se trabaja con múltiples clústeres <a href="https://elastic.aiops.work/search-labs/blog/esql-cross-cluster-search">remotos con CCS</a>. He aquí por qué:</p><h3>ES|QL Editor ofrece un autocompletado fluido incluso entre clústeres</h3><p>Autocompletado en el ES|QL Editor soporta no solo nombres de clústeres sino también<strong> índices locales y remotos</strong>. Como se explica <a href="https://www.elastic.co/search-labs/blog/esql-cross-cluster-search">aquí</a>, esto funciona gracias a una arquitectura de nodos coordinadores, que ayuda a validar y generar el plan de consulta para enviar a los nodos locales, ejecutar la consulta y agregar los resultados antes de enviarlos de vuelta al usuario. Sin introducir el nombre completo del clúster remoto, escribir &quot;:&quot; inicia el proceso de autocompletado para el índice remoto. Y no estás limitado al prefijo.</p><p>Esto facilita descubrir y consultar entre conjuntos de datos distribuidos sin memorizar convenciones de nombres ni cambiar de contexto.</p><p>Aquí tienes un ejemplo en el que el usuario simplemente escribe &quot;clu:g&quot; para localizar un índice remoto:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bae1fbacb2320e4d07f41291ea57c9bcf15bf8a5-1092x523.gif" style="width: 100%;" /><p>En marcado contraste, la PPL solo proporciona completitud básica para índices locales, con sugerencias restringidas a coincidencias con prefijos. Los clústeres remotos deben ser tipados manualmente, lo que aumenta la probabilidad de errores y ralentiza la creación de consultas.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e38793c0cc7c6cc7dc0fd4779a3e24ffbb6e0838-1094x263.gif" style="width: 100%;" /><p>PPL solo proporciona completitud para índices locales y las sugerencias se restringen al prefijo:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5310f824942f94485cace2558ea72c56a0971e22-862x197.png" style="width: 100%;" /><p>ES|QL va más allá <a href="https://www.elastic.co/docs/solutions/search/cross-cluster-search#exclude-problematic-clusters">permitiendo exclusiones</a> directamente usando un signo negativo, dándote un control detallado sobre qué clústeres participan en tu exploración. Esta capacidad es especialmente valiosa al trabajar con entornos híbridos, donde puede ser necesario incluir u omitir conjuntos de datos específicos durante investigaciones entre clústeres.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f23ca17f58fbf8e6d27419c028274cb91f30a549-937x78.png" style="width: 100%;" /><p>Estas mejoras reflejan el enfoque más amplio de Elasticsearch en reducir la fricción en la búsqueda entre clústeres. Al facilitar la construcción y gestión de consultas distribuidas, ES|QL Editor permite a analistas y desarrolladores centrar en los insights en lugar de la sintaxis, mientras que PPL deja mayor parte de esa carga al usuario. Y igual que el ES|QL Editor simplifica la creación de consultas entre clústeres y también proporciona herramientas para inspeccionar cómo se ejecutan esas consultas, garantizando transparencia y monitorización del rendimiento en múltiples clústeres.</p><h3>Uso de la herramienta Inspect para analizar los detalles de búsqueda entre clústeres</h3><p>La Herramienta de Inspección, accesible desde el ES|QL Editor está diseñado para proporcionar metadatos con información explícita sobre la ejecución de consultas en todos los clústeres. Esta funcionalidad está habilitada en Kibana Discover y es accesible directamente en el inspector de consultas, permitiéndote analizar el progreso y los detalles de la búsqueda, algo especialmente crucial para <strong>la búsqueda entre clústeres</strong> (<a href="https://www.elastic.co/docs/reference/query-languages/esql/esql-cross-clusters">CCS</a>). Esta capacidad te ayuda a monitorizar el progreso de las búsquedas y a entender cómo funcionan las consultas entre conjuntos de datos distribuidos.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5d1c204f70171526fff924c30ea8ad08121a0f8d-919x523.gif" style="width: 100%;" /><p>Esta visibilidad detallada de la ejecución de consultas, especialmente para búsquedas distribuidas complejas, te permite garantizar un rendimiento y resolución de problemas óptimos.</p><p>Más allá de entender la mecánica de las consultas individuales, ES|QL Editor mejora aún más el recorrido del usuario al integrar profundamente funcionalidades esenciales en toda la plataforma Kibana, fomentando un flujo de trabajo fluido e ininterrumpido.</p><h2>Experiencia unificada de consultas con ES|QL y Kibana</h2><p>Una de las fuentes más comunes de fricción en el análisis guiado por consultas es el cambio de contexto. A menudo necesitas recordar consultas que ya escribiste. Cada interrupción rompe el foco y ralentiza las investigaciones. ES|QL Editor aborda esto integrando el historial de consultas en Kibana.</p><h3>Consultas recientes</h3><p>La función <a href="https://www.elastic.co/search-labs/blog/esql-piped-query-language-goes-ga">de Consultas Recientes</a> en ES|QL Editor te ayuda a mantener el flujo haciendo que el trabajo pasado sea instantáneamente accesible. Dentro del ES|En el Editor QL en Discover, puedes ver, volver a ejecutar y poner estrellas en tus últimas 20 consultas, cerciorando que las consultas frecuentes o complejas estén a solo un clic de distancia. Estas consultas almacenadas también se transmiten a Kibana, integrar con paneles, visualizaciones, alertas y mapas, así que no necesitas salir de la pantalla actual ni volver a escribir comandos desde cero. Esto reduce el trabajo repetitivo, acelera las investigaciones y minimiza el riesgo de errores.</p><p>Por ejemplo, un usuario puede emplear las Consultas Recientes en ES|Editor de QL en Discover (y ponles la estrella):</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/eb0f9fe37b92dec421c394d31ae7d90afebe062e-1421x793.png" style="width: 100%;" /><p>Las consultas recientes están integradas en el Panel de Control:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b84cd3a9bdec58812360d2aba4fc7713363ee3cc-1411x797.png" style="width: 100%;" /><p>PPL no ofrece una capacidad comparable, por lo que los usuarios dependen de copiar y pegar manualmente o notas externas para reutilizar consultas. La diferencia es más que la comodidad; refleja la estrategia de Elastic de construir ES|QL como un lenguaje verdaderamente integrado dentro del ecosistema Kibana. Con funciones como Consultas Recientes, ES|QL Editor no solo agiliza los flujos de trabajo diarios, sino que también sienta las bases para funcionalidades más avanzadas que ahora están en vista previa técnica, cerciorando que la experiencia siga evolucionando.</p><h2>Conclusión</h2><p>ES|QL es más que una sintaxis; refleja la estrategia de Elastic para mejorar la forma en que los usuarios buscan, exploran y analizan los datos. Con autocompletado inteligente, consultas recomendadas que consigan el contexto, guía en el editor y herramientas como Inspect, ES|QL Editor acelera el aprendizaje, reduce errores y simplifica flujos de trabajo complejos como el análisis entre clústeres. Integrado en Kibana, conecta consultas de forma fluida con paneles, alertas y visualizaciones para un flujo de trabajo ininterrumpido.</p><p>En resumen, ES|QL no es simplemente otro lenguaje canalizado; es un motor de consultas cuidadosamente diseñado combinado con una interfaz intuitiva que redefine fundamentalmente cómo interactúas con tus datos, ofreciendo una experiencia integrada, inteligente y en constante evolución que contrasta fuertemente con la naturaleza a menudo secuencial y menos guiada de OpenSearch PPL.</p><h2>¿Qué viene después?</h2><p>Este blog solo rasca la superficie de ES|QL. Las futuras publicaciones profundizarán en comparaciones con OpenSearch PPL y explorarán funciones geoespaciales, de visualización y de próximos editores como <a href="https://www.elastic.co/docs/explore-analyze/dashboards/add-controls">los Controles</a> (ya disponibles en Dashboards), pestañas de exploración de múltiples datos, búsqueda en segundo plano, historial de consultas más completo y FUSE.</p><h2>Prueba ES|QL hoy</h2><p>Puedes echar un vistazo a ES|QL en proyectos <a href="https://www.elastic.co/cloud/serverless">Serverless</a> de Elasticsearch totalmente gestionados 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 que abarcan desde la 8.11, pero se experimenta mejor en <a href="https://www.elastic.co/blog/whats-new-elastic-9-1-0">la 8.19 y la 9.1</a>.</p><p>Empieza en minutos en tu entorno local con un solo comando:</p><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Presentamos el ES|Generador de consultas QL para el cliente Ruby de Elasticsearch]]></title>
            <description><![CDATA[Aprende a usar el recientemente lanzado ES|Generador de consultas QL para el cliente Ruby de Elasticsearch. Una herramienta para construir ES|QL consulta más fácilmente con código Ruby.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/esql-query-builder-elasticsearch-ruby-client</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/esql-query-builder-elasticsearch-ruby-client</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Fernando Briano]]></dc:creator>
            <pubDate>Wed, 17 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f8e1263ab53d356824a4fc539084151be80899db-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Recientemente lanzamos <a href="https://github.com/elastic/esql-ruby/"><code>elastic-esql</code></a>, una joya Ruby publicada bajo la licencia Apache 2. Esta gema te permite construir el <a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql">ES| de ElasticConsultas QL</a> en Ruby idiomático, que luego puedes usar con el ES|API de consulta QL. ES|QL permite a los desarrolladores filtrar, transformar y analizar los datos almacenados en Elasticsearch mediante consultas. Emplea &quot;tuberías&quot; ( <code>|</code> ) para trabajar paso a paso con los datos. La gema emplea funciones Ruby en su lugar, que puedes encadenar al objeto original para crear consultas más complejas:</p><p><strong>ESQL:</strong></p><pre><code>FROM sample_data | LIMIT 2 | SORT @timestamp DESC</code></pre><p><strong>Rubí:</strong></p><pre><code>Elastic::ESQL.from('sample_data').limit(2).sort('@timestamp').descending</code></pre><h2>Instalación</h2><p>La gema puede instalar desde RubyGems con:</p><pre><code>gem install elastic-esql</code></pre><p>O puede agregar al archivo de gemas de un proyecto:</p><pre><code>gem 'elastic-esql'</code></pre><h2>Uso</h2><p>Puedes construir una consulta completa de una vez o crear un objeto de consulta con un comando fuente como <code>from</code> o <code>row</code> y luego encadenar ES|QL métodos para construir sobre ella.</p><pre><code>query = Elastic::ESQL.from('sample_data')
query.limit(2).sort('@timestamp')</code></pre><p>La gema traduce el código a ES|QL en el método <code>to_s</code> , así que devuelve el ES|Consulta QL cuando se imprime o se convierte en una cadena:</p><pre><code>query = Elastic::ESQL.from('sample_data').limit(2).sort('@timestamp').descending
query.to_s
# => "FROM sample_data | LIMIT 2 | SORT @timestamp DESC"</code></pre><p>Puedes instanciar un objeto de consulta y mutar su estado inicial usando los equivalentes <code>!</code> de cada función:</p><pre><code>query = Elastic::ESQL.from('sample_data')
query.to_s
# => "FROM sample_data"
query.limit!(2).sort!('@timestamp')
query.to_s
# => "FROM sample_data | LIMIT 2 | SORT @timestamp"</code></pre><p>La herramienta ofrece formas cómodas de encadenar pasos extra a un ES|Función QL, como <code>enrich</code> y <code>sort</code>. Una vez que llamas <code>enrich</code> a un objeto <code>Elastic::ESQL</code> , puedes encadenar <code>on</code> y <code>with</code> a él:</p><pre><code>esql.enrich!('policy').on('a').with({ name: 'language_name' })</code></pre><p>También puedes encadenar <code>desc</code>, <code>asc</code>, <code>nulls_first</code> y <code>nulls_last</code> a tu consulta tras usar <code>sort</code>:</p><pre><code>Elastic::ESQL.from('sample_data').sort('@timestamp').asc.to_s
# => 'FROM sample_data | SORT @timestamp ASC'

Elastic::ESQL.from('sample_data').sort('@timestamp').desc.nulls_first.to_s
# => 'FROM sample_data | SORT @timestamp DESC NULLS FIRST'</code></pre><p>También soporta cadenas personalizadas, por si quieres escribir el ES|Consulta QL tú mismo, o usa una función que aún no se agregó a la biblioteca. <code>custom</code> se unirán a las cadenas al final de la consulta. Los agregará a medida que se envían a la función, sin agregar ningún carácter de la tubería. Se combinarán con el resto de la consulta mediante un carácter espacio.</p><pre><code>esql = Elastic::ESQL.from('sample_data')
esql.custom('| MY_VALUE = "test value"').to_s
# => 'FROM sample_data | MY_VALUE = "test value"'</code></pre><p>También puedes encadenar <code>custom</code> funciones:</p><pre><code>esql.custom('| MY_VALUE = "test value"').custom('| ANOTHER, VALUE')
'FROM sample_data | MY_VALUE = "test value" | ANOTHER, VALUE'</code></pre><h2>Usando el ES|QL Query Builder con el cliente Ruby</h2><p>Puedes usar el constructor de consultas directamente con <a href="https://github.com/elastic/elasticsearch-ruby">elasticsearch-ruby</a> y la API <code>esql.query</code> enviando el objeto de consulta:</p><pre><code>require 'elasticsearch'
require 'elastic/esql'

client = Elasticsearch::Client.new
index = 'sample_data'

query = Elastic::ESQL.from(index)
                     .sort('@timestamp')
                     .desc
                     .where('event_duration > 5000000')
                     .limit(3)
                     .eval({ duration_ms: 'ROUND(event_duration/1000000.0, 1)' })
client.esql.query(body: { query: query })</code></pre><p>También puedes usarlo con el ES|QL Helper del cliente Ruby de Elasticsearch, <a href="https://www.elastic.co/search-labs/blog/esql-ruby-helper-elasticsearch">para saber más</a>:</p><pre><code>require 'elasticsearch/helpers/esql_helper'

Elasticsearch::Helpers::ESQLHelper.query(client, query)</code></pre><h2>Como herramienta independiente</h2><p>La gema está diseñada como una herramienta independiente para construir ES|QL consulta de forma idiomática. No tiene dependencias en tiempo de ejecución; puedes usarlo con el cliente oficial de Elasticsearch Ruby, o por separado.</p><p>La consulta generada puede usar con la API <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-query"><code>esql.query</code></a> de cualquier forma que una aplicación interactúe con la API de Elasticsearch (Ruby o no). Una vez que una consulta se construye con <code>elastic-esql</code>, la cadena generada puede enviar a la API como el parámetro <code>query</code> en el cuerpo de la solicitud. </p><p>Anteriormente escribí sobre <a href="https://www.elastic.co/search-labs/blog/elasticsearch-ruby-tools">el uso de Elasticsearch con las herramientas Ruby populares</a>. Esta gema puede usar con cualquiera de las herramientas Ruby populares para consultar Elasticsearch con ES|QL.</p><h2>Conclusión</h2><p>Esta biblioteca está en desarrollo activo y la API final aún no se completó. Actualmente está lanzado como un avance técnico. Si tienes algún comentario sobre la API actual o su uso general, no dudes en <a href="https://github.com/elastic/esql-ruby/issues">abrir un nuevo número</a>. Por favor, consulta <a href="https://github.com/elastic/esql-ruby/?tab=readme-ov-file#ruby-esql-query-builder">el README</a> para saber más sobre el Ruby ES|Constructor de consultas QL.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Filtrado de búsqueda vectorial: Mantenerlo relevante]]></title>
            <description><![CDATA[Realizar una búsqueda vectorial para encontrar los resultados más similares a una consulta no es suficiente. A menudo se necesita filtrar para reducir los resultados de búsqueda. Este artículo explica cómo funciona el filtrado para la búsqueda vectorial en Elasticsearch y Apache Lucene.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/vector-search-filtering</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/vector-search-filtering</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Carlos Delgado]]></dc:creator>
            <pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/03b1dd4c7bda4fbabd8e374bc2e4f12d5be6ef5f-1600x1150.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La búsqueda vectorial no es suficiente para encontrar resultados relevantes. Es muy común usar criterios de filtrado que ayudan a reducir los resultados de búsqueda y a filtrar los resultados irrelevantes.</p><p>Entender cómo funciona el filtrado en la búsqueda vectorial te ayudará a equilibrar los compromisos entre rendimiento y recordación, así como descubrir algunas de las optimizaciones que se usan para que la búsqueda vectorial sea eficiente al usar filtrado.</p><h2>¿Por qué filtrar?</h2><p>La búsqueda vectorial revolucionó la forma en que encontramos información relevante en grandes conjuntos de datos, permitiéndonos descubrir elementos que son semánticamente similares a una consulta.</p><p>Sin embargo, simplemente encontrar objetos similares no es suficiente. A menudo necesitamos reducir los resultados de búsqueda en función de criterios o atributos específicos.</p><p>Imagina que buscas un producto en una tienda online. Una búsqueda vectorial pura puede mostrarte artículos visualmente similares, pero también podrías filtrar por rango de precio, marca, disponibilidad o valoraciones de clientes. Sin filtrar, te presentarías con una gran variedad de productos similares, lo que dificultaría encontrar exactamente lo que buscas.</p><p>El filtrado permite un control preciso sobre los resultados de búsqueda, cerciorando que los elementos recuperados no solo se alineen semánticamente, sino que también cumplan todos los requisitos necesarios. Esto conduce a una experiencia de búsqueda mucho más precisa, eficiente y fácil de usar.</p><p>Aquí es donde Elasticsearch y Apache Lucene excelen: usar filtrado efectivo entre varios tipos de datos es una de las diferencias clave con otras bases de datos vectoriales.</p><h2>Filtrado para búsqueda vectorial exacta</h2><p>Existen dos formas principales de realizar búsquedas vectoriales exactas:</p><ul><li>Usar un tipo de índice <code>flat</code> para tu campo de dense_vector. Esto hace que <code>knn</code> búsquedas empleen la búsqueda exacta en lugar de aproximada.</li><li>Emplear una <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-script-score-query#vector-functions">consulta script_score</a> que emplea funciones vectoriales para calcular el puntaje. Esto puede usar con cualquier tipo de índice.</li></ul><p>Al ejecutar una búsqueda vectorial exacta, todos los vectores se comparan con la consulta. En este escenario, el filtrado ayudará al rendimiento, ya que solo se necesitan comparar los vectores que pasan el filtro.</p><p>Esto no afecta a la calidad del resultado, ya que todos los vectores se consideran de todos modos. Simplemente filtramos de antemano los resultados que no son interesantes, para poder reducir el número de operaciones.</p><p>Esto es muy importante, ya que puede ser más eficiente ejecutar una búsqueda exacta en lugar de una búsqueda aproximada cuando los filtros aplicados resultan en un pequeño número de documentos.</p><p>La regla general es usar la búsqueda exacta cuando menos de 10.000 documentos pasan el filtro. Los índices <a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">BBQ</a> son mucho más rápidos para comparar, así que tiene sentido usar la búsqueda exacta cuando hay menos de 100k para los índices basados. Consulta <a href="https://www.elastic.co/search-labs/blog/knn-exact-vs-approximate-search">esta entrada del blog</a> para más detalles.</p><p>Si tus filtros siempre son muy restrictivos, puedes considerar indexar centrado en la búsqueda exacta en lugar de en la búsqueda aproximada, usando un tipo de índice <code>flat</code> en lugar de uno basado en HNSW. Para más detalles, <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-params">ver las propiedades de index_options</a>.</p><h2>Filtrado para búsqueda vectorial aproximada</h2><p>Al ejecutar búsqueda vectorial aproximada, cambiamos la precisión de los resultados por el rendimiento. Las estructuras de datos de búsqueda vectorial como HNSW buscan eficientemente vecinos aproximados en millones de vectores. Se centran en recuperar los vectores más similares haciendo la menor cantidad posible de comparaciones vectoriales, que son costosas de calcular.</p><p>Esto significa que otros atributos de filtrado no forman parte de los datos vectoriales. Diferentes tipos de datos tienen sus propias estructuras de indexación que son eficientes para encontrarlos y filtrarlos, como diccionarios de términos, listas de publicación y valores de documentos.</p><p>Dado que estas estructuras de datos son independientes del mecanismo de búsqueda vectorial, ¿cómo aplicamos el filtrado a la búsqueda vectorial? Hay dos opciones: aplicar filtros luego de la búsqueda vectorial (postfiltrado) o antes de la búsqueda vectorial (prefiltrado).</p><p>Cada una de esas opciones tiene sus pros y sus contras. ¡Vamos a profundizar en ellos!</p><h3>Postfiltrado</h3><p>El postfiltrado aplica filtros después de que se realizó la búsqueda vectorial. Esto significa que los filtros se aplican después de que se encontraron los k primeros resultados vectoriales más similares.</p><p>Obviamente, podemos obtener menos de k resultados aplicando los filtros a los resultados. Por supuesto, podríamos obtener más resultados de la búsqueda vectorial (valores k más altos), pero no estaremos seguros de obtener k o más tras aplicar los filtros.</p><p>El beneficio del postfiltrado es que no cambia el comportamiento en tiempo de ejecución de la búsqueda vectorial: la búsqueda vectorial no es consciente del filtrado. Pero sí cambia el número final de resultados obtenidos.</p><p>A continuación se muestra un ejemplo de postfiltrado usando la <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query">consulta knn</a>. Comprueba que la cláusula de filtrado esté separada de la consulta knn:</p><pre><code>{
  "query": {
    "bool": {
      "must": {
        "knn": {
          "field": "image-vector",
          "query_vector": [54, 10, -2],
          "k": 5,
          "num_candidates": 50
        }
      },
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}</code></pre><p>El filtrado de postfiltrado también está disponible para la búsqueda de knn usando <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/filter-search-results#post-filter">el filtro de postfiltro</a>:</p><pre><code>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, 2],
    "k": 5,
    "num_candidates": 50
  },
  "post_filter": {
    "term": {
      "file-type": "png"
    }
  }
}</code></pre><p>Ten en cuenta que necesitas usar una sección explícita de filtro posterior con la búsqueda de knn. Si no usas un filtro de post, la búsqueda de knn <a href="https://www.elastic.co/docs/solutions/search/vector/knn#_combine_approximate_knn_with_other_features">combinará los resultados de vecinos más cercanos</a> con otras consultas o filtros en lugar de hacer un filtro de post.</p><h3>Prefiltrado</h3><p>Aplicar filtros antes de la búsqueda vectorial primero recuperará los documentos que cumplan con los filtros y luego transmitirá esa información a la búsqueda vectorial.</p><p>Lucene emplea <a href="https://github.com/apache/lucene/blob/7a60d7ce92392181e137361336e5196bd486cdd9/lucene/core/src/java/org/apache/lucene/util/BitSet.java">BitSets</a> para almacenar eficientemente los documentos que cumplen la condición de filtro. La búsqueda vectorial recorre entonces el grafo HNSW, teniendo en cuenta los documentos que cumplen la condición. Antes de agregar un candidato a los resultados, comprueba que esté contenido en el BitSet de documentos válidos.</p><p>Sin embargo, el candidato debe ser explorado y comparado con la consulta, aunque no sea un documento válido. La efectividad de HNSW depende de la conexión entre los vectores del grafo: si dejáramos de explorar un candidato, significaría que podríamos estar saltándonos también sus vecinos.</p><p>Piénsalo como manejar para llegar a una gasolinera. Si descartas cualquier carretera que no tenga gasolinera, es poco probable que llegues a tu destino. Puede que otras carreteras no sean lo que necesitas, pero te <em>conectan</em> con tu destino. ¡Lo mismo ocurre con los vectores en un grafo HNSW!</p><p>Por tanto, aplicar prefiltrado es menos eficiente que no aplicar filtros. Tenemos que trabajar en <em>todos</em> los vectores que visitamos en nuestra búsqueda, y desechar aquellos que no coinciden con el filtro. Estamos trabajando más y tardando más en conseguir los mejores resultados de la k.</p><p>A continuación se muestra un ejemplo de pretfiltering en la DSL de Elasticsearch Consult. Comprueba que la cláusula de filtrado ahora forma parte de la sección knn:</p><pre><code>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, -2],
    "k": 5,
    "num_candidates": 50,
    "filter": {
      "term": {
        "file-type": "png"
      }
    }
  }
}</code></pre><p>El prefiltrado está disponible tanto para <a href="https://www.elastic.co/docs/solutions/search/vector/knn#knn-search-filter-example">la búsqueda como</a> para <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query#knn-query-filtering">la consulta knn</a>:</p><pre><code>{
  "query": {
    "knn": {
      "field": "image-vector",
      "query_vector": [-5, 9, -12],
      "k": 5,
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}</code></pre><h4>Optimizaciones de prefiltrado</h4><p>Hay un par de optimizaciones que podemos aplicar para cerciorar que el prefiltrado sea eficiente.</p><p>Podemos cambiar a búsqueda exacta si el filtro es muy restrictivo. Cuando hay pocos vectores para comparar, es más rápido realizar una búsqueda exacta en los pocos documentos que cumplen con el filtro.</p><p>Esta es una optimización que se aplica automáticamente en <a href="https://github.com/apache/lucene/blob/eb876b618da5d04c1ad14b04a48321638318493a/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L218">Lucene</a> y Elasticsearch.</p><p>Otro método de optimización implica ignorar los vectores que no satisfacen el filtro. En su lugar, este método comprueba los vecinos de los vectores filtrados que sí pasan el filtro. Este enfoque reduce efectivamente el número de comparaciones ya que no se consideran los vectores filtrados, y continúa explorando vectores conectados al camino actual.</p><p>Este algoritmo es ACORN-1, y el proceso se describe en detalle en <a href="https://www.elastic.co/search-labs/blog/filtered-hnsw-knn-search">esta entrada del blog</a>.</p><h2>Filtrado usando la seguridad a nivel de documento</h2><p><a href="https://www.elastic.co/docs/deploy-manage/users-roles/cluster-or-deployment-auth/controlling-access-at-document-field-level#document-level-security">La Seguridad a Nivel de Documento (DLS)</a> es una función de Elasticsearch que especifica los documentos que los roles de usuario pueden recuperar.</p><p>DLS se realiza mediante consultas. Un rol puede tener una consulta asociada a índices, lo que limita efectivamente los documentos que un usuario que pertenece a ese rol puede recuperar de los índices.</p><p>La consulta de rol se emplea como filtro para <a href="https://github.com/elastic/elasticsearch/blob/c3a1cb34294e902a9f46d7e840ea09965019f456/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java#L92">recuperar los documentos que coinciden con ella</a>, y se almacenan en caché como un BitSet. Este BitSet se emplea entonces para envolver el lector Lucene subyacente, de modo que solo los documentos que se devolvieron de la consulta se consideran <em>activos,</em>es decir, existen en el índice y no fueron eliminados.</p><p>A medida que los documentos en tiempo real se <a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L196">recuperan del lector</a> para realizar la consulta knn, solo se considerarán los documentos disponibles para el usuario. Si hay un prefiltro, se <a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L204">agregarán los documentos DLS a él</a>.</p><p>Esto significa que el filtrado DLS funciona como prefiltro para la búsqueda vectorial aproximada, con las mismas participaciones de rendimiento y optimizaciones.</p><p>DLS con búsqueda exacta tendrá los mismos beneficios que aplicar cualquier filtro: cuantos menos documentos se recuperen de DLS, más eficiente será una búsqueda exacta. Considera también el número de documentos devueltos por DLS; si los roles DLS son muy restrictivos, puedes considerar usar búsqueda exacta en lugar de búsqueda aproximada.</p><h2>Evaluación comparativa</h2><p>En Elasticsearch, queremos cerciorarnos de que el filtrado de búsqueda vectorial sea eficiente. Disponemos <a href="https://elasticsearch-benchmarks.elastic.co/#tracks/so_vector/nightly/default/90d">de un benchmark específico para el filtrado vectorial</a> que realiza búsquedas vectoriales aproximadas con diferentes filtros para cerciorar que la búsqueda vectorial siga recuperando resultados relevantes lo más rápido posible.</p><p>Consulta las <a href="https://elasticsearch-benchmark-analytics.elastic.co/app/dashboards#/view/43b63e80-5ba2-11ed-aede-a742809feed4?_g=(refreshInterval:(pause:!t,value:60000),time:(from:&#x27;2025-05-28T01:27:58.456Z&#x27;,to:&#x27;2025-06-30T13:53:26.430Z&#x27;))&amp;_a=()">mejoras</a> cuando se introdujo ACORN-1. Para pruebas en las que solo el 2% de los vectores pasan el filtro, la latencia de consulta se reduce al 55% de la duración original:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3eac3748a33376fc97d957364a5c1f5108d5c58b-1023x896.png" style="width: 100%;" /><h2>Conclusión</h2><p>El filtrado es una parte integral de la búsqueda. Cerciorar que el filtrado sea eficiente en la búsqueda vectorial y comprender los compromisos y optimizaciones es lo que hace que una búsqueda sea eficiente y precisa o fracase.</p><p>El filtrado afecta al rendimiento de la búsqueda vectorial:</p><ul><li>La búsqueda exacta es más rápida cuando se usa filtrado. Deberías considerar usar la búsqueda exacta en lugar de la aproximada si tu filtrado es lo suficientemente restrictivo. Esta es una optimización automática en Elasticsearch.</li><li>La búsqueda aproximada es más lenta cuando se emplea prefiltrado. El prefiltrado nos permite obtener los k primeros resultados que coinciden con el filtro, a costa de una búsqueda más lenta.</li><li>El postfiltrado no necesariamente recupera los k primeros resultados, ya que pueden filtrar mediante el filtro cuando se aplica.</li></ul><p>¡Feliz filtrado!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fragmentos y réplicas de Elasticsearch: Una guía práctica]]></title>
            <description><![CDATA[Domina los conceptos de fragmentos y réplicas de Elasticsearch y aprende a optimizarlos.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elasticsearch-shards-and-replicas-guide</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elasticsearch-shards-and-replicas-guide</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Piotr Przybyl]]></dc:creator>
            <pubDate>Thu, 14 Aug 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7775c44f01f2516c4ff4cce6d6bbe9e7b2c38908-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch potencia Lucene construyendo un sistema distribuido sobre él, que aborda los problemas de escalabilidad y tolerancia a fallos. También expone una API REST basada en JSON, lo que facilita mucho la interoperabilidad con otros sistemas.</p><p>Sistemas distribuidos como Elasticsearch pueden ser muy complejos, con muchos factores que pueden afectar su rendimiento y estabilidad. <strong>Los fragmentos</strong> son uno de los conceptos más fundamentales en Elasticsearch, y entender cómo funcionan te permitirá gestionar eficazmente un clúster de Elasticsearch.</p><p>Este artículo explica qué son los shards primarios y réplica, su impacto en un clúster de Elasticsearch y qué herramientas existen para ajustarlos a diferentes demandas.</p><h2>Entendiendo fragmentos</h2><p>Los datos en un índice de Elasticsearch pueden crecer a proporciones enormes. Para mantenerlo manejable, cada dato se almacena en un índice, y los índices son un índice <strong>dividido en varios fragmentos</strong>. Cada fragmento de Elasticsearch es un índice de Lucene Apache, donde cada índice individual de Lucene contiene un subconjunto de los documentos del índice de Elasticsearch. Dividir los índices de esta manera mantiene el uso de recursos bajo control. Un índice de Lucena apache tiene un límite de 2.147.483.519 (2³¹ - 129) documentos.</p><p>A veces, es necesario mover índices entre nodos para fines de reequilibrio. Dado que este proceso puede requerir tanto tiempo como recursos, los índices no deberían crecer demasiado, lo que ayuda a mantener el tiempo de recuperación manejable. Además, dado que los índices están compuestos por segmentos de Lucene que deben fusionar constantemente, es importante que los segmentos no se hagan demasiado grandes. Por estas razones, Elasticsearch divide los datos del índice en fragmentos más pequeños y manejables, <strong>llamados fragmentos primarios</strong>, que pueden distribuir más fácilmente entre varias máquinas. <strong>Los fragmentos réplica</strong> son simplemente una copia exacta de un fragmento primario correspondiente y repasaremos su función más adelante en este artículo.</p><p>Tener el número adecuado de fragmentos es importante para el rendimiento. Por tanto, es prudente planear con antelación. Cuando las consultas se ejecutan en diferentes fragmentos en paralelo, se ejecutan más rápido que un índice compuesto por un solo fragmento, pero solo si cada fragmento está ubicado en un nodo diferente y hay suficientes nodos en el clúster. Sin embargo, al mismo tiempo, los fragmentos consumen memoria y espacio en disco, tanto en términos de datos indexados como de metadatos de clúster. Tener demasiados fragmentos (también conocido como sobrefragmentación) puede ralentizar consultas, solicitudes de indexación y operaciones de gestión, por lo que mantener el equilibrio adecuado es fundamental.</p><p>El número de fragmentos primarios se define en el momento de la creación <strong>del índice para esa instancia específica del índice</strong>. Si necesitas un número diferente de fragmentos primarios más adelante, puedes usar las<strong> APIs de redimensionamiento</strong>: <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-split">split</a> (más shards primarios), <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-shrink">shrink</a> (menos shards primarios) o <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-clone">clone</a> (el mismo número de shards primarios con nuevos ajustes para réplicas). Estas operaciones copian los segmentos de Lucene y <strong>evitan una reindexación completa de todos los documentos</strong>. Al crear un índice, puedes establecer el número de fragmentos primarios y réplica como ajustes del índice:</p><pre><code>PUT /sensor
{
   "settings" : {
       "index" : {
           "number_of_shards" : 6,
           "number_of_replicas" : 2
       }
   }
}</code></pre><p>(Si no especificas el número de fragmentos o réplicas, el valor por defecto de ambos es 1, según Elasticsearch 7.0). El número ideal de fragmentos debe determinar en función de la cantidad de datos en un índice. Generalmente, <a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/size-shards">un shard óptimo debe contener entre 10 y 50GB de datos</a>, con menos de 200 millones de documentos por shard. Por ejemplo, si esperas acumular alrededor de 300GB de registros de aplicaciones en un día, tener alrededor de 10 fragmentos en ese índice sería razonable, siempre que tengas suficientes nodos para alojarlos.</p><p>Durante su vida, los fragmentos pueden pasar por varios estados, entre ellos:</p><ul><li><strong>Inicializar:</strong> Un estado inicial antes de que se pueda usar el fragmento.</li><li><strong>Comenzó:</strong> Un estado en el que el fragmento está activo y puede recibir solicitudes.</li><li><strong>Reubicación:</strong> Un estado que ocurre cuando los fragmentos están en proceso de mover a otro nodo. Esto puede ser necesario bajo ciertas condiciones, por ejemplo, cuando el nodo en el que están se está quedando sin espacio en disco.</li><li><strong>No asignado:</strong> El estado de un fragmento que no fue asignado. Se proporciona una razón cuando esto ocurre, por ejemplo, si el nodo que aloja el fragmento ya no está en el clúster <em>(NODE_LEFT)</em> o debido a la restauración en un índice cerrado <em>(EXISTING_INDEX_RESTORED).</em></li></ul><p>Para ver todos los fragmentos, sus estados y otros metadatos, puedes usar la siguiente solicitud:</p><pre><code>GET _cat/shards</code></pre><p>Para ver fragmentos de un índice específico, puedes agregar el nombre del índice a la URL, por ejemplo, sensor:</p><pre><code>GET _cat/shards/sensor</code></pre><p>Este comando produce una salida, como en el siguiente ejemplo. Por defecto, las columnas que aparecen incluyen el nombre del índice, el nombre (es decir, número) del fragmento, si es un fragmento principal o una réplica, su estado, el número de documentos, el tamaño en disco, así como la dirección IP y el ID del nodo donde se encuentra el fragmento.</p><pre><code>sensor 5 p STARTED    0  283b 127.0.0.1 ziap
sensor 5 r UNASSIGNED                  
sensor 2 p STARTED    1 3.7kb 127.0.0.1 ziap
sensor 2 r UNASSIGNED                  
sensor 3 p STARTED    3 7.2kb 127.0.0.1 ziap
sensor 3 r UNASSIGNED                  
sensor 1 p STARTED    1 3.7kb 127.0.0.1 ziap
sensor 1 r UNASSIGNED                  
sensor 4 p STARTED    2 3.8kb 127.0.0.1 ziap
sensor 4 r UNASSIGNED                  
sensor 0 p STARTED    0  283b 127.0.0.1 ziap
sensor 0 r UNASSIGNED</code></pre><h2>Comprensión de réplicas</h2><p>Aunque cada fragmento contiene una única copia de los datos, un índice puede contener varias copias del fragmento. Por tanto, existen dos tipos de fragmentos: el <strong>fragmento principal</strong> y una copia, o <strong>réplica</strong>. Cada réplica de un fragmento primario siempre se encuentra en un nodo diferente, lo que garantiza una alta disponibilidad de tus datos en caso de fallo de un nodo. Además de la redundancia y su papel en la prevención de la pérdida de datos y el tiempo de inactividad, las réplicas también pueden ayudar a mejorar el rendimiento de búsqueda al permitir que las consultas se procesen en paralelo con el shard primario y, por tanto, más rápido.</p><p>Existen diferencias importantes en el comportamiento de los fragmentos primarios y réplica. Aunque ambos son capaces de procesar consultas, las solicitudes de indexación (es decir, Agregar datos al índice) debe pasar primero por los fragmentos primarios antes de poder replicar en los fragmentos réplica. Como se indicó antes, si un fragmento primario deja de estar disponible—por ejemplo, debido a una desconexión de nodo o fallo de hardware—se promueve una réplica para asumir su función.</p><p>Aunque las réplicas pueden ayudar en caso de fallo de un nodo, es importante no tener demasiadas porque consumen memoria, espacio en disco y potencia de cálculo al indexar. Otra diferencia entre los fragmentos primarios y las réplicas es que, aunque el número de fragmentos primarios no puede cambiar una vez creado el índice, el número de réplicas puede modificar dinámicamente en cualquier momento actualizando la configuración del índice.</p><p>Otro factor a considerar con las réplicas es el número de nodos disponibles. Las réplicas siempre se colocan en nodos diferentes del fragmento primario, ya que dos copias de los mismos datos en el mismo nodo no ofrecerían protección si el nodo fallara. Como resultado, para que un sistema soporte <em>n</em> réplicas, debe haber al <em>menos n + 1</em> nodos en el clúster. Por ejemplo, si hay dos nodos en un clúster y un índice está configurado con seis réplicas, solo se asignará una réplica. Por otro lado, un sistema con siete nodos es perfectamente capaz de manejar un fragmento principal y seis réplicas.</p><h2>Optimización de fragmentos y réplicas</h2><p>Incluso después de que se creó un índice con el equilibrio adecuado entre fragmentos primarios y réplica, estos deben ser monitorizados, ya que la dinámica alrededor de un índice cambia con el tiempo. Por ejemplo, al tratar con datos de seriales temporales, los índices con datos recientes suelen estar más activos que los más antiguos. Sin ajustar estos índices, todos consumirían la misma cantidad de recursos, a pesar de sus requisitos muy diferentes.</p><p>La API de índices de rollover puede usar para separar índices más nuevos y antiguos. Se puede configurar para crear automáticamente un nuevo índice una vez alcanzado cierto umbral—el tamaño del índice en el disco, el número de documentos o la antigüedad. Esta API también es útil para mantener bajo control el tamaño de los fragmentos. Dado que el número de fragmentos no puede modificar fácilmente tras la creación del índice, los fragmentos seguirán acumulando datos si no se cumplen las condiciones de rollover. Para índices antiguos que solo requieren acceso poco frecuente, reducir y forzar la fusión de un índice son dos formas diferentes de reducir su huella de memoria y disco. La primera reduce el número de fragmentos en un índice, mientras que la segunda reduce el número de segmentos Lucene y libera espacio empleado por documentos que fueron eliminados.</p><h2>Fragmentos primarios y réplica como base de Elasticsearch</h2><p>Elasticsearch construyó una estable reputación como plataforma distribuida de almacenamiento, búsqueda y análisis para enormes volúmenes de datos. Sin embargo, al operar a tal escala, inevitablemente surgen desafíos. Por eso entender cómo funcionan los fragmentos primarios y réplica es tan importante y fundamental para Elasticsearch, ya que esto puede ayudar a optimizar la fiabilidad y el rendimiento de la plataforma.</p><p>Saber cómo funcionan y cómo optimizarlos es fundamental para lograr un clúster de Elasticsearch más robusto y eficiente. Si experimentas respuestas lentas o cortes de información con regularidad, este conocimiento puede ser la clave para superar estos obstáculos.</p><p>Sigue la documentación oficial de Elasticsearch para saber más sobre <a href="https://www.elastic.co/docs/deploy-manage/distributed-architecture/clusters-nodes-shards">clústeres, nodos y fragmentos</a>, <a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/size-shards">cómo dimensionar tus fragmentos</a>, <a href="https://www.elastic.co/docs/deploy-manage/distributed-architecture/shard-allocation-relocation-recovery">asignación y recuperación de fragmentos</a>.</p><p>Este tema también está disponible como curso introductorio en el <a href="https://youtu.be/sAySPSyL2qE">canal de YouTube de Elastic Community.</a></p><p>Por último, pero no menos importante: si no quieres preocuparte por nodos, fragmentos o réplicas, puedes probar <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/serverless">Elastic Cloud Serverless</a>. Esta oferta de Elastic Cloud está completamente gestionada por Elastic y automatizada para escalar con tu carga de trabajo. Una prueba gratis puede ayudarte a familiarizarte con otros beneficios del enfoque sin servidor.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cómo mostrar los campos de un índice de Elasticsearch]]></title>
            <description><![CDATA[Aprende a mostrar campos de un índice de Elasticsearch usando las APIs _mapping y _search, subcampos, _source sintéticos y campos de ejecución.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elasticsearch-index-show-fields</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elasticsearch-index-show-fields</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[JD Armada]]></dc:creator>
            <pubDate>Wed, 06 Aug 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/23b96aaa1a38b1f4747b4a87695d816f24c0cf70-720x421.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>En este artículo, hablaremos de cómo mostrar los campos de un índice de Elasticsearch. Esto puede ser útil para entender la estructura de tus datos, identificar campos específicos y solucionar problemas. Vamos a tratar los siguientes temas:</p><ol><li>Uso de la API <code>_mapping</code> para recuperar información de campos</li><li>Uso de la API <code>_search</code> para mostrar los valores de los campos</li><li>Visualización de subcampos</li><li>_source sintética</li><li>Campos de tiempo de ejecución</li></ol><h2>1. Uso de la API _mapping para recuperar información de campo</h2><p>La API <code>_mapping</code> permite recuperar la definición de mapeo para un índice o varios índices. Esto incluye información sobre los campos, sus tipos de datos y otras propiedades. Para recuperar el mapeo de un índice específico, emplee la siguiente petición:</p><pre><code>GET /<index_name>/_mapping</code></pre><p>Por ejemplo, si tienes un índice llamado <code>my_index</code>, puedes recuperar su mapeo con la siguiente petición:</p><pre><code>GET /my_index/_mapping</code></pre><p>La respuesta incluirá la definición de mapeo para el índice, que contiene información sobre los campos y sus propiedades.</p><p>También es posible recuperar el mapeo de un campo específico. Esto puede ser útil si tu mapeo es bastante grande y solo quieres centrarte en un campo específico. Para recuperar el mapeo de un campo específico, emplee la siguiente petición:</p><pre><code>GET /my_index/_mapping/field/my_field</code></pre><p>También puedes recuperar las asignaciones de varios campos separando sus nombres con comas, como en la siguiente petición:</p><pre><code>GET /my_index/_mapping/field/my_field_1,my_field_2,my_field_3</code></pre><h2>2. Uso de la API _search para mostrar los valores de los campos</h2><p>Para mostrar los valores de los campos en un índice de Elasticsearch, puedes usar la API <code>_search</code> . La API <code>_search</code> te ofrece múltiples formas de controlar qué campos se devuelven; Los dos principales son:</p><ol><li><strong><code>_source</code></strong>: El campo <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-source-field"><code>_source</code></a> contiene el cuerpo original del documento JSON exactamente como estaba indexado, incluyendo cualquier cambio realizado por las canalizaciones de ingestión o pasos de preprocesamiento. Para mostrar campos específicos del documento fuente, implementa filtrado de fuentes como veremos a continuación.</li><li><strong><code>fields</code></strong>: El parámetro <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrieve-selected-fields"><code>fields</code></a> te permite recuperar campos específicos de tus documentos al realizar una búsqueda, basándote en el mapeo de índice. A diferencia de <code>_source</code>, <code>fields</code> también puede devolver valores de campos almacenados, valores de documentación o campos de ejecución sin referenciar la <code>_source</code>, aunque para campos estándar sin valores de documento ni configuraciones almacenadas, vuelve a <code>_source</code>. Esto puede aportar muchos beneficios como el rendimiento y más, como veremos a continuación.</li></ol><h3>Uso del campo _source<code> </code></h3><p>Por defecto, la API<code> _search</code> devuelve el campo <code>_source</code> , que contiene el documento JSON original que se indexó. Para mostrar campos específicos, puedes agregar filtros en el parámetro <code>_source </code>de la solicitud de búsqueda; Esto se llama filtrado de fuente.</p><p>Aquí tienes un ejemplo de una solicitud de búsqueda que devuelve los valores de los campos <code>title </code>y <code>author</code> para documentos en el índice <code>my_index</code> :</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "_source": ["title", "author"]
}</code></pre><p>En este ejemplo, el parámetro <code>_source</code> especifica los campos que se deben devolver.</p><p>Si necesitas aún más control, puedes usar las propiedades de <code>includes</code> y <code>excludes </code>del objeto <code>_source</code>. Por ejemplo, la consulta siguiente devuelve el campo <code>title</code> de nivel superior y todos los subcampos de <code>author</code> excepto <code>author.description</code>.</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "_source": {
     “includes”: [“title”, “author.*],
     “excludes”: [“author.description”]
  }
}</code></pre><p>En este ejemplo, usamos el patrón <code>author.* </code>para recuperar todos los subcampos directos del objeto <code>author </code>. Luego excluimos explícitamente <code>author.description </code>para que solo se devuelvan los otros campos de autor. Ten en cuenta que esto no tiene mejoras de rendimiento ya que aún tiene que cargar y analizar el JSON de origen, pero puede reducir el tamaño de la respuesta enviada por la red.</p><h3>Uso del parámetro de campos</h3><p>Puedes usar el parámetro <code>fields</code> para filtrar los campos que aparecen en la respuesta de búsqueda. Emplear <code>fields</code> <code>_source</code> ofrece varios beneficios, entre ellos:</p><ul><li><strong>Mejora de rendimiento: </strong><code>fields </code>puede devolver valores directamente desde <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-store">campos almacenados</a> o <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/doc-values">valores de documentos</a> sin tener que cargar toda la <code>_source</code>, haciendo que el tamaño de la carga útil de respuesta sea menor.</li><li><strong>Salida formateada:</strong> Para campos estándar,<code> fields</code> puede recurrir a <code>_source</code> para obtener los valores, pero revisa el mapeo del índice para formatear correctamente la salida, como las fechas formateadas, haciéndolas consistentes con lo que se usa para agregaciones y ordenación.</li><li><strong>Acceso a campos de tiempo de ejecución:</strong> <code>fields</code> puede devolver campos de tiempo de ejecución, que no existen en el <code>_source</code>original.</li><li>Aquí <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrieve-selected-fields#search-fields-param">se pueden encontrar</a> más beneficios.</li></ul><p>Por ejemplo, para devolver solo los campos <code>title</code> y <code>author</code> en el índice <code>my_index</code> , puedes usar la siguiente solicitud de búsqueda:</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author"],
  "_source": false
}</code></pre><p>En la consulta anterior, ponemos el campo <code>_source </code>en false para no devolver el documento fuente. Esto puede minimizar significativamente el tamaño de la carga útil para la respuesta, pero recuerda que esto solo funciona porque los campos <code>title</code> y <code>author</code> son del tipo <code>keyword </code>campo, que <code>doc_values</code> habilitaron por defecto. Si el campo no tiene <code>doc_values</code> activado y el <code>_source</code> está configurado como falso, Elasticsearch no tendría forma de recuperarlos y se omitiría en la respuesta.</p><p>Es importante señalar que la respuesta <code>fields</code> siempre devuelve un array de valores para cada campo, incluso si solo hay un único valor. Esto se debe a que Elasticsearch no tiene un tipo de array dedicado, y cualquier campo puede tener varios valores. Para más información sobre los arrays en Elasticsearch, haz <a href="http://elastic.co/docs/reference/elasticsearch/mapping-reference/array">clic aquí</a>.</p><h3>Otras formas de recuperar campos</h3><p>Aunque recuperar campos usando <code>_source</code> o <code>fields</code> son los métodos recomendados, existen diferentes métodos disponibles para casos de uso específicos, como:</p><p><strong>Campos de valor de documentos:</strong> Si quieres evitar <code>_source</code> por completo, puedes buscar usando el parámetro <code><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrieve-selected-fields#docvalue-fields">docvalue_fields</a> </code>. Los valores de documentación almacenan los mismos valores de campo que <code>_source</code> pero en una estructura de datos en disco, optimizada para ordenar y agregar.</p><p>Como está separado de los valores almacenados con <code>_source</code>, puedes aplicar campos específicos sin cargar toda la <code>_source</code>. Esto es útil si consultas documentos grandes pero solo necesitas unos pocos campos pequeños que soporten valores de documentos. Otro caso de uso para usar <code>docvalue_fields </code>es cuando quieres usar formato personalizado en campos <code>date</code> y <code>numeric</code> , como veremos en el ejemplo más abajo.</p><p>Ten en cuenta que esto solo funciona para campos que activas <code>doc_values</code> o para tipos de campos que lo tienen activado por defecto, como <code>keyword</code>, <code>date</code>, tipos numéricos y <code>boolean</code>, no para <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/text"><code>text</code></a> o <a href="https://www.elastic.co/docs/reference/elasticsearch/plugins/mapper-annotated-text-usage"><code>annotated_text</code></a>.</p><p>En este ejemplo, usamos el parámetro <code>docvalue_fields</code> para recuperar los campos <code>title</code>, <code>author</code>, y <code>published</code> sin cargar el documento completo de <code>_source</code> :</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "docvalue_fields": [
    "title",
    "author",
    {
      "field": "published",
      "format": "epoch_millis"
    }
  ],
  "_source": false
}</code></pre><p>Cuando se ejecuta esta consulta, Elasticsearch toma los valores directamente de su almacenamiento columnar en disco en lugar de referenciar el <code>_source </code>de cada documento. El campo <code>published</code> se devuelve con el formato <code>epoch_millis</code> en lugar del formato por defecto, gracias al parámetro <code>format</code> proporcionado en la consulta.</p><p><strong>Campos almacenados:</strong> Si marcas explícitamente campos específicos como <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-store">almacenados</a> en el mapeo, puedes usar el parámetro <code>stored_fields</code> para filtrar esos campos. Esto es útil si quieres respuestas ligeras solo con esos campos específicos o para campos que almacenaste deliberadamente para recuperarlos después. Se almacena por separado de <code>_source</code>, por lo que este método también es útil para evitar la necesidad de cargar <code>_source</code>.</p><p>Es importante señalar que esta opción está desactivada por defecto y generalmente no se recomienda. Emplea filtrado de fuentes para devolver ciertos subconjuntos del documento fuente original.</p><p>En la consulta de ejemplo a continuación, usamos el parámetro <code>stored_fields</code> para recuperar el campo <code>summary</code> , que tiene la configuración de mapeo de índice &quot;<code>store”: true</code>.</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "stored_fields": ["summary"]
}</code></pre><p>Cuando se ejecuta esta consulta, Elasticsearch busca si este campo está marcado con <code>”store”: true</code>, si no lo encuentra, se saltará el campo por completo.</p><h2>3. Visualización de subcampos</h2><p>Si tu índice contiene subcampos, puedes usar la notación de puntos para especificar el camino de campo en el parámetro <code>fields</code> . Ten en cuenta que los subcampos son diferentes del <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/nested">tipo de campo anidado</a>. Por ejemplo, si tienes un subcampo llamado <code>address.city</code>, puedes incluirlo en la respuesta de búsqueda así:</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author", "address.city"],
  "_source": false
}</code></pre><p>En este ejemplo, la respuesta de búsqueda incluirá los valores de los campos <code>title</code>, <code>author</code> <code>address.city</code> .</p><h2>4. _source sintético</h2><p>Si quieres mantener la funcionalidad de usar<code> _source</code> pero también ahorrar espacio en disco, tienes la opción de usar <code>_source</code> sintético en tu mapeo de índice. <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-source-field#synthetic-source">La_source</a> sintética es una función que permite a Elasticsearch reconstruir el <code>_source</code> a partir de datos existentes como campos almacenados y valores de documentos, incluso cuando <code>_source</code> está desactivado. Esto te permite ahorrar mucho espacio de almacenamiento a cambio de velocidades ligeramente menores en el momento de la consulta, ya que la reconstrucción se realiza sobre la marcha. Activa esta función usando los valores que aparecen a continuación en la configuración de tu índice:</p><pre><code>PUT idx
{
  "settings": {
    "index": {
      "mapping": {
        "source": {
          "mode": "synthetic"
        }
      }
    }
  }
}</code></pre><p>Algunos beneficios de usar <code>_source </code>sintéticos incluyen: visualización completa del documento al usar la API <code>_search</code> , filtrado de código fuente y compatibilidad con otras funciones y herramientas como Kibana que esperan <code>_source</code> estén disponibles, todo ello evitando la necesidad de almacenar el documento completo <code>_source</code> .</p><h2>5. Campos de tiempo de ejecución</h2><p><a href="https://www.elastic.co/docs/manage-data/data-store/mapping/runtime-fields">Los campos de ejecución</a> te permiten definir campos guionizados en el momento de la consulta o en tu mapeo de índice bajo un bloque de ejecución. Estos campos nunca se indexan, por lo que agregar un campo en tiempo de ejecución no aumenta el tamaño del índice pero nunca aparecerá en <code>_source</code>. Los campos de ejecución definidos en el mapeo son persistentes y están disponibles para todas las consultas, mientras que los campos de ejecución definidos en el momento de la consulta son temporales y solo están disponibles en esa solicitud de búsqueda.</p><p>El principal beneficio de usar campos de tiempo de ejecución es la posibilidad de agregar campos a documentos luego de haberlos ingerido, simplificando así tus decisiones de mapeo. Los campos de ejecución también son ideales para enriquecer tus documentos con valores que no existen en el documento original pero que se generan mediante un script, como formatear una cadena o calcular un puntaje.</p><p>También cabe destacar que los campos de ejecución pueden perjudicar el rendimiento, ya que será necesario ejecutar un script para cada documento del conjunto de resultados. Para <a href="https://www.elastic.co/docs/manage-data/data-store/mapping/retrieve-runtime-field">recuperar un campo de ejecución</a>, también puedes usar el parámetro <code>fields</code> en la API <code>_search</code> .</p><h2>Conclusión</h2><p>Mostrar campos de un índice de Elasticsearch puede ir desde simplemente recuperar valores usando el mapeo de índice o el <code>_source</code>, hasta métodos más avanzados usando campos <code>fields</code>, <code>docvalue_fields</code>o en tiempo de ejecución para mayor control y eficiencia. Comprender los compromisos entre diferentes métodos es clave para optimizar tus experiencias de búsqueda. Ya sea que estés optimizando cargas útiles, enriqueciendo documentos o empleando <code>_source</code> sintéticos para ahorrar espacio, Elasticsearch te ofrece múltiples herramientas y funciones para encontrar los datos que necesitas, de la manera que necesitas. Estas técnicas pueden ayudarte a entender la estructura de tus datos, identificar campos específicos y solucionar problemas.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Paneles impulsados por IA: de una visión a Kibana]]></title>
            <description><![CDATA[Genera un panel de control usando un LLM para procesar una imagen y convertirla en un Panel de Kibana.
]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/ai-powered-dashboards</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/ai-powered-dashboards</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo, Tomás Murúa]]></dc:creator>
            <pubDate>Wed, 16 Jul 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/eb60ceb2fbc3941745b21ae3357cbb6ea8fab18c-1443x811.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><a href="https://www.elastic.co/kibana/kibana-lens">Kibana Lens</a> hace que los paneles de control sean muy sencillos, pero cuando necesitas decenas de paneles, los clics se acumulan. ¿Y si pudieras hacer un boceto de un panel de control, hacer capturas de pantalla y dejar que un LLM termine todo el proceso por ti?</p><p>En este artículo, haremos que eso suceda. Crearemos una aplicación que tome una imagen de un panel de control, analice nuestros mapeos y luego genere un panel sin que tengamos que tocar Kibana en absoluto.</p><p><strong>Pasos</strong>:</p><ol><li><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#background-&amp;-application-workflow">Antecedentes y flujo de trabajo de la aplicación</a></li><li><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#prepare-data">Preparar datos</a></li><li><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#llm-configuration">Configuración de LLM</a></li><li><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#application-functions">Funciones de aplicación</a></li></ol><h2>Antecedentes y flujo de trabajo de la aplicación</h2><p>La primera idea que se me ocurrió fue dejar que el LLM generara todo el formato NDJSON <a href="https://www.elastic.co/docs/explore-analyze/find-and-organize/saved-objects">de los objetos almacenados</a> de Kibana y luego los importara a Kibana.</p><p>Probamos algunos modelos:</p><ul><li>Gemini 2.5 pro</li><li>GPT o3 / o4-mini-alto / 4,1</li><li>Claude 4 soneto</li><li>Grok 3</li><li>Deepseek (Deepthink R1)</li></ul><p>Y para los prompts, empezamos tan sencillos como:</p><pre><code>You are an Elasticsearch Saved-Object generator (Kibana 9.0).
INPUTS
=====
1. PNG screenshot of a 4-panel dashboard (attached).
2. Index mapping (below) – trimmed down to only the fields present in the screenshot.
3. Example NDJSON of *one* metric visualization (below) for reference.

TASK
====
Return **only** a valid NDJSON array that recreates the dashboard exactly:
* 2 metric panels (Visits, Unique Visitors)
* 1 pie chart (Most used OS)
* 1 vertical bar chart (State Geo Dest)
* Use index pattern `kibana_sample_data_logs`.
* Preserve roughly the same layout (2×2 grid).
* Use `panelIndex` values 1-4 and random `id` strings.
* Kibana version: 9.0</code></pre><p>A pesar de repasar <a href="https://www.elastic.co/search-labs/blog/function-calling-with-elastic#:~:text=Few%2Dshot%20prompting%20involves%20providing%20examples%20of%20the%20types%20of%20queries%20you%20want%20it%20to%20return%2C%20which%20helps%20in%20increasing%20consistency.">algunos ejemplos de planos</a> y explicaciones detalladas sobre cómo construir cada visualización, no tuvimos suerte. Si te interesa esta experimentación, puedes encontrar <a href="https://gist.github.com/TomasMurua/a78dc283e115624731beffc98984b70b">detalles aquí</a>.</p><p>El resultado de este enfoque fue ver estos mensajes al intentar subir a Kibana los archivos producidos por el LLM:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2b599443b5613c9f0fc3235581614add5b4b3900-891x98.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d87ccfc033bc00ee8188c5cae18043fbca22784c-741x233.png" style="width: 100%;" /><p>Esto significa que el JSON generado es inválido o está mal formateado. Los problemas más comunes eran que el LLM producía NDJSON incompleto, alucinaban parámetros o devolvían JSON normal en lugar de NDJSON, por mucho que intentáramos hacer cumplir lo contrario.</p><p>Inspirados por <a href="https://www.elastic.co/search-labs/blog/llm-functions-elasticsearch-intelligent-query">este artículo</a> —donde <a href="https://www.elastic.co/docs/solutions/search/search-templates">las plantillas de búsqueda</a> funcionaban mejor que el estilo libre de un LLM— decidimos dar plantillas al LLM en lugar de pedir generar el archivo NDJSON completo y luego, en código, usar los parámetros dados por el LLM para crear las visualizaciones adecuadas. Este enfoque no decepcionó, y es previsible y ampliable, ya que ahora el código hace el trabajo duro y no el LLM.</p><p>El flujo de trabajo de la aplicación será el siguiente:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/52c587cf0cf3517fdd4ee7ab95581dd4f2bce030-725x668.png" style="width: 100%;" /><p></p><p><em>Omitiremos algo de código para simplificar, pero puedes encontrar el código funcional de la aplicación completa en <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/from-image-idea-to-kibana-dashboard-using-ai/from-image-idea-to-kibana-dashboard-using-ai.ipynb"><strong>este</strong></a> cuaderno.</em></p><h2>Prerrequisitos</h2><p>Antes de empezar a desarrollar, necesitarás lo siguiente:</p><ol><li>Python 3.8 o superior</li><li>Un entorno <a href="https://docs.python.org/3/library/venv.html">Venv</a> Python</li><li>Una instancia de Elasticsearch en ejecución, junto con su endpoint y clave API</li><li>Una clave de API de OpenAI almacenada bajo el nombre de la variable de entorno OPENAI_API_KEY:</li></ol><pre><code>export OPENAI_API_KEY="your-openai-api-key"</code></pre><h2>Preparar datos</h2><p>Para los datos, lo mantendremos sencillo y usaremos registros sitio web de muestra de Elastic. Puedes aprender cómo importar esos datos a tu <a href="https://www.elastic.co/docs/manage-data/ingest/sample-data#add-sample-data-sets">clúster aquí</a>.</p><p>Cada documento incluye detalles sobre el anfitrión que emitió las solicitudes a la aplicación, junto con información sobre la propia solicitud y su estado de respuesta. A continuación se muestra un documento de ejemplo:</p><pre><code>{
    "agent": "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24",
    "bytes": 8509,
    "clientip": "70.133.115.149",
    "extension": "css",
    "geo": {
        "srcdest": "US:IT",
        "src": "US",
        "dest": "IT",
        "coordinates": {
            "lat": 38.05134111,
            "lon": -103.5106908
        }
    },
    "host": "cdn.elastic-elastic-elastic.org",
    "index": "kibana_sample_data_logs",
    "ip": "70.133.115.149",
    "machine": {
        "ram": 5368709120,
        "os": "osx"
    },
    "memory": null,
    "message": "70.133.115.149 - - [2018-08-30T23:35:31.492Z] \"GET /styles/semantic-ui.css HTTP/1.1\" 200 8509 \"-\" \"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24\"",
    "phpmemory": null,
    "referer": "http://twitter.com/error/john-phillips",
    "request": "/styles/semantic-ui.css",
    "response": 200,
    "tags": [
        "success",
        "info"
    ],
    "@timestamp": "2025-07-03T23:35:31.492Z",
    "url": "https://cdn.elastic-elastic-elastic.org/styles/semantic-ui.css",
    "utc_time": "2025-07-03T23:35:31.492Z",
    "event": {
        "dataset": "sample_web_logs"
    },
    "bytes_gauge": 8509,
    "bytes_counter": 51201128
}</code></pre><p>Ahora, vamos a tomar los mapeos del índice que acabamos de cargar, <code>kibana_sample_data_logs</code>:</p><pre><code>INDEX_NAME = "kibana_sample_data_logs"

es_client = Elasticsearch(
    [os.getenv("ELASTICSEARCH_URL")],
    api_key=os.getenv("ELASTICSEARCH_API_KEY"),
)

result = es_client.indices.get_mapping(index=INDEX_NAME)
index_mappings = result[list(result.keys())[0]]["mappings"]["properties"]</code></pre><p>Vamos a pasar los mapeos junto con la imagen que cargaremos más adelante.</p><h2>Configuración de LLM</h2><p>Configuremos el LLM para que use <a href="https://python.langchain.com/docs/concepts/structured_outputs/">una salida estructurada</a> para introducir una imagen y recibir un JSON con la información que necesitamos pasar a nuestra función para producir los objetos JSON.</p><p>Instalamos las dependencias:</p><pre><code>pip install elasticsearch pydantic langchain langchain-openai -q</code></pre><p>Elasticsearch nos ayudará a recuperar los <a href="https://www.elastic.co/docs/manage-data/data-store/mapping">mapeos de índice</a>. Pydantic nos permite definir esquemas en Python para luego pedir al LLM que lo siga, y <a href="https://www.elastic.co/search-labs/integrations/langchain">LangChain</a> es el framework que facilita la llamada a LLMs y herramientas de IA.</p><p>Crearemos un esquema Pydantic para definir la salida que queremos del LLM. Lo que necesitamos saber de la imagen es el tipo de gráfico, campo, título de visualización y título del panel de control:</p><pre><code>class Visualization(BaseModel):
    title: str = Field(description="The dashboard title")
    type: List[Literal["pie", "bar", "metric"]]
    field: str = Field(
        description="The field that this visualization use based on the provided mappings"
    )


class Dashboard(BaseModel):
    title: str = Field(description="The dashboard title")
    visualizations: List[Visualization]</code></pre><p>Para la entrada de imagen enviaremos un panel de control que acabo de dibujar:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/36441d7b5dc1f3ff2ac2a30710208d57ad41c716-1600x898.jpg" style="width: 100%;" /><p>Ahora declaramos la llamada al modelo LLM y la carga de imagen. Esta función recibirá los mapeos del índice de Elasticsearch y una imagen del panel de control que queremos generar.</p><p>Con <code>with_structured_output</code> podemos usar nuestro esquema de <code>Dashboard</code> Pydantic como objeto de respuesta que producirá el LLM. Con <a href="https://docs.pydantic.dev/latest/">Pydantic</a>, podemos definir modelos de datos con validación, lo que garantiza que la salida del LLM coincida con la estructura esperada.</p><p>Para convertir la imagen a base64 y enviarla como entrada, puedes usar un <a href="https://www.base64-image.de/">convertidor online</a> o hacerlo <a href="https://www.geeksforgeeks.org/python-convert-image-to-string-and-vice-versa/">en código</a>.</p><pre><code>prompt = f"""
    You are an expert in analyzing Kibana dashboards from images for the version 9.0.0 of Kibana.

    You will be given a dashboard image and an Elasticsearch index mapping.

    Below are the index mappings for the index that the dashboard is based on.
    Use this to help you understand the data and the fields that are available.

    Index Mappings:
    {index_mappings}

    Only include the fields that are relevant for each visualization, based on what is visible in the image.
    """

message = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": prompt},
            {
                "type": "image",
                "source_type": "base64",
                "data": image_base64,
                "mime_type": "image/png",
            },
        ],
    }
]


try:
    llm = init_chat_model("gpt-4.1-mini")
    llm = llm.with_structured_output(Dashboard)
    dashboard_values = llm.invoke(message)

    print("Dashboard values generated by the LLM successfully")
    print(dashboard_values)
except Exception as e:
    print(f"Failed to analyze image and match fields: {str(e)}")</code></pre><p>El LLM ya tiene contexto sobre los paneles Kibana, así que no necesitamos explicar todo en el prompt, solo algunos detalles para cerciorarnos de que no olvide que está funcionando con Elasticsearch y Kibana.</p><p>Vamos a desglosar el prompt:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Sección</td><td style="border: 1px solid #ccc; padding: 4px;">Razón</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Eres un experto en analizar paneles de Kibana a partir de imágenes para la versión 9.0.0 de Kibana.</td><td style="border: 1px solid #ccc; padding: 4px;">Al reforzar esto es Elasticsearch, y la versión de Elasticsearch reducimos la probabilidad de que el LLM alucine parámetros antiguos o inválidos.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Se te dará una imagen del panel de control y un mapeo de índice de Elasticsearch.</td><td style="border: 1px solid #ccc; padding: 4px;">Explicamos que la imagen trata sobre paneles para evitar interpretaciones erróneas por parte del LLM.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">A continuación se muestran los mapeos de índices del índice en el que se basa el panel de control. Emplea esto para ayudarte a entender los datos y los campos disponibles. Mapeos de índice: {index_mappings}</td><td style="border: 1px solid #ccc; padding: 4px;">Es crucial proporcionar los mapeos para que el LLM pueda seleccionar campos válidos dinámicamente. De lo contrario, podríamos codificar los mapeos aquí, lo cual es demasiado rígido, o confiar en la imagen que contiene los nombres de campo correctos, lo cual no es fiable.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Incluye solo los campos relevantes para cada visualización, basándote en lo que sea visible en la imagen.</td><td style="border: 1px solid #ccc; padding: 4px;">Tuvimos que agregar este refuerzo porque a veces intenta agregar campos que no son relevantes para la imagen.</td>
                    </tr>
              </tbody>
            </table>
          <p>Esto devolverá un objeto con un serial de visualizaciones para mostrar:</p><pre><code>"Dashboard values generated by the LLM successfully
title=""Client, Extension, OS, and Response Keyword Analysis""visualizations="[
   "Visualization(title=""Count of Client IP",
   "type="[
      "metric"
   ],
   "field=""clientip"")",
   "Visualization(title=""Extension Keyword Distribution",
   "type="[
      "pie"
   ],
   "field=""extension.keyword"")",
   "Visualization(title=""Most Used OS",
   "type="[
      "bar"
   ],
   "field=""machine.os.keyword"")",
   "Visualization(title=""Response Keyword Distribution",
   "type="[
      "bar"
   ],
   "field=""response.keyword"")"
]</code></pre><h2>Procesamiento de la respuesta de los LLM</h2><p>Creamos<strong> </strong>un panel de panel de muestra 2x2 y luego lo exportamos en JSON usando <a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-get-dashboards-dashboard">la API Get a dashboard</a>, y después almacenamos los paneles como plantillas de visualización (pastel, barra, métrica) donde podemos reemplazar algunos parámetros para crear nuevas visualizaciones con diferentes campos según la pregunta.</p><p>Puedes ver los archivos JSON <a href="https://github.com/Delacrobix/elasticsearch-labs/tree/supporting-blog-content/from-image-idea-to-kibana-dashboard-using-ai/supporting-blog-content/from-image-idea-to-kibana-dashboard-using-ai/templates"><strong>de plantilla aquí</strong></a>. Fíjate en cómo cambiamos los valores de los objetos que queremos reemplazar más adelante por {<code>variable_name</code>}<br/></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ec7e1ac0cd8b470df13e60940162b56778acb386-315x234.png" style="width: 100%;" /><p>Con la información que nos proporcionó el LLM, podemos decidir qué plantilla usar y qué valores reemplazar.</p><p><code>fill_template_with_analysis</code> recibirá los parámetros de un único panel, incluyendo la plantilla JSON de la visualización, un título, un campo y las coordenadas de la visualización en la cuadrícula.</p><p>Luego, reemplazará los valores de la plantilla y devolverá la visualización JSON final.</p><pre><code>def fill_template_with_analysis(
    template: Dict[str, Any],
    visualization: Visualization,
    grid_data: Dict[str, Any],
):
    template_str = json.dumps(template)
    replacements = {
	 "{visualization_id}": str(uuid.uuid4()),
        "{title}": visualization.title,
        "{x}": grid_data["x"],
        "{y}": grid_data["y"],
    }

    if visualization.field:
        replacements["{field}"] = visualization.field

    for placeholder, value in replacements.items():
        template_str = template_str.replace(placeholder, str(value))

    return json.loads(template_str)</code></pre><p>Para simplificar, tendremos coordenadas estáticas que asignaremos a los paneles que el LLM decida crear y produciremos un panel de cuadrícula 2x2 como en la imagen anterior.</p><pre><code># Filling templates fields
panels = []    
grid_data = [
    {"x": 0, "y": 0},
    {"x": 12, "y": 0},
    {"x": 0, "y": 12},
    {"x": 12, "y": 12},
]


i = 0

for vis in dashboard_values.visualizations:
    for vis_type in vis.type:
        template = templates.get(vis_type, templates.get("bar", {}))
        filled_panel = fill_template_with_analysis(template, vis, grid_data[i])
        panels.append(filled_panel)
        i += 1</code></pre><p>Dependiendo del tipo de visualización decidido por el LLM, elegiremos una plantilla de archivo JSON y reemplazaremos la información relevante usando <code>fill_template_with_analysis</code> , luego agregaremos el nuevo panel a un array que usaremos más adelante para crear el panel de control.</p><p>Cuando el panel esté listo, usaremos la <a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-post-dashboards-dashboard-id">API Crear un panel</a> para enviar el nuevo archivo JSON a Kibana y generar el panel:<br/></p><pre><code>try:
    dashboard_id = str(uuid.uuid4())

    # post request to create the dashboard endpoint
    url = f"{os.getenv('KIBANA_URL')}/api/dashboards/dashboard/{dashboard_id}"

    dashboard_config = {
        "attributes": {
            "title": dashboard_values.title,
            "description": "Generated by AI",
            "timeRestore": True,
            "panels": panels,  # Visualizations with the values generated by the LLM
            "timeFrom": "now-7d/d",
            "timeTo": "now",
        },
    }

    headers = {
        "Content-Type": "application/json",
        "kbn-xsrf": "true",
        "Authorization": f"ApiKey {os.getenv('ELASTICSEARCH_API_KEY')}",
    }

    requests.post(
        url,
        headers=headers,
        json=dashboard_config,
    )

    # Url to the generated dashboard
    dashboard_url = f"{os.getenv('KIBANA_URL')}/app/dashboards#/view/{dashboard_id}"

    print("Dashboard URL: ", dashboard_url)
    print("Dashboard ID: ", dashboard_id)

except Exception as e:
    print(f"Failed to create dashboard: {str(e)}")</code></pre><p>Para ejecutar el script y generar el panel de control, ejecuta el siguiente comando en la consola:</p><pre><code>python <file_name>.py</code></pre><p>El resultado final será el siguiente:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e909afbf0e47d9a6e0f7bd07dfb2efcfa5cf06ac-921x715.png" style="width: 100%;" /><h2>Conclusión</h2><p>Los LLMs muestran sus fuertes capacidades visuales al hacer texto a código o convertir imágenes en código. La API de los paneles también permite convertir archivos JSON en paneles, y con un LLM y algo de código, podemos convertir imágenes en un panel Kibana.</p><p>El siguiente paso es mejorar la flexibilidad de los gráficos del salpicadero empleando diferentes configuraciones de cuadra, tamaños y posiciones de tablero. Además, ofrecer soporte para visualizaciones y tipos de visualización más complejos sería una adición útil a esta aplicación.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Revelando patrones únicos: Guía para la agregación de términos significativos en Elasticsearch]]></title>
            <description><![CDATA[Aprende a emplear la agregación de términos significativos para descubrir información en tus datos.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/significant-terms-aggregation-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/significant-terms-aggregation-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Alexander Dávila]]></dc:creator>
            <pubDate>Mon, 07 Jul 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/13adbc901837835bb56abf15e377127b017cfac8-1536x1024.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>En Elasticsearch, una <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significantterms-aggregation">agregación significativa de términos</a> va más allá de los <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-terms-aggregation">términos más comunes</a> para encontrar valores estadísticamente inusuales en un conjunto de datos. Esto nos permite descubrir ideas valiosas y patrones no evidentes. Una agregación significativa de términos proporciona una respuesta con dos parámetros útiles:</p><ul><li><strong>bg_count (recuento de fondo): </strong>Número de documentos encontrados en el conjunto de datos principal</li><li><strong>doc_count:</strong> Número de documentos encontrados en el conjunto de datos resultante</li></ul><p>Por ejemplo, en un conjunto de datos de ventas de teléfonos, podemos buscar términos significativos en las ventas del iPhone 16 como este:</p><pre><code>GET phone_sales_analysis/_search
{
 "size": 0,
 "query": {
   "term": {
     "phone_model": {
       "value": "iPhone 16"
     }
   }
 },
 "aggs": {
   "significant_cities": {
     "significant_terms": {
       "field": "city_region",
       "size": 1
     }
   }
 }
}</code></pre><p>Luego, la respuesta nos da:</p><pre><code>{
 "aggregations": {
   "significant_cities": {
     "doc_count": 122,
     "bg_count": 424,
     "buckets": [
       {
         "key": "Houston",
         "doc_count": 12,
         "score": 0.1946481360617346,
         "bg_count": 14
       }

     ]
   }
 }
}</code></pre><p>Houston no está entre las 10 principales ciudades de todo el conjunto de datos ni la ciudad principal para el iPhone 16. Sin embargo, la agregación significativa de términos mostró que el<strong><em> iPhone 16 se está comprando de forma desproporcionada en esta ciudad</em></strong> en comparación con el resto de datos. Vamos a profundizar en los números:</p><ul><li><strong>En el nivel superior:</strong><ul><li><strong>doc_count: 122 — </strong>La consulta coincidió con un total de 122 documentos</li><li><strong>bg_count: 424 — </strong>El conjunto de antecedentes (todos los documentos de ventas) contiene 424 documentos</li></ul></li><li><strong>En el grupo de Houston:</strong><ul><li><strong>doc_count: 12 — </strong>Houston aparece en 12 de los 122 resultados de consulta</li><li><strong>bg_count: 14 — </strong>Houston aparece en 14 de los 424 documentos totales del conjunto de datos de fondo</li></ul></li></ul><p>Esto nos indica que, de un total de 424 compras, solo 14 ocurrieron en Houston; Eso supone el 3,3% de todas las compras. Sin embargo, si solo miramos las ventas del iPhone 16, vemos que 12 de 122 ocurrieron en Houston, lo que supone un 9,8%, tres veces más que en todo el conjunto de datos; ¡Eso es significativo!</p><p>Así es como se ve eso en una visualización: ventas totales por city_region.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f31335b0b7793650025f941820f238dd35bfb09f-1486x1066.png" style="width: 100%;" /><p>Podemos ver que hay 14 ventas en Houston, lo que la convierte en la 14ª ciudad con más ventas en el conjunto de datos.</p><p>Ahora, si aplicamos un filtro para mirar solo las ventas del iPhone 16, tenemos 12 ventas en Houston, lo que la convierte en la segunda ciudad con más ventas para este modelo específico:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6516040db77e6c62af5541a74c723b18008ad3c6-1472x1038.png" style="width: 100%;" /><h2>Comprensión de la agregación de términos significativos</h2><p>Según la documentación de Elastic, los <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significantterms-aggregation">términos significativos agregación</a>:</p><p><em>&quot;(Encuentra) términos que experimentaron un cambio significativo de popularidad medido entre un conjunto de primer plano y un de fondo.&quot;</em></p><p>Esto significa que emplea métricas estadísticas para comparar la frecuencia de un término en un subconjunto de datos (el conjunto de primer plano) con la frecuencia del mismo término en el conjunto padre de datos (el conjunto de fondo). De este modo, el puntaje refleja la significación estadística en lugar de la frecuencia con la que aparece un término en los datos.</p><p>Las principales diferencias entre una agregación de términos significativos y una agregación de términos normales son:</p><ul><li>Los términos significativos comparan un subconjunto de los datos, mientras que la agregación de términos solo funciona sobre el conjunto de datos resultante de la consulta.</li><li>Los resultados de una agregación de términos son los más comunes en el conjunto de datos, mientras que los resultados de términos significativos ignoran los términos comunes para encontrar qué hace único el conjunto de datos.</li><li>Los términos significativos pueden tener un mayor impacto en el rendimiento, dado que necesitan obtener datos del disco en lugar de de la memoria, como ocurre con los términos agregación.</li></ul><h2>Aplicación práctica (análisis del comportamiento del consumidor)</h2><h3>Preparación de datos para el análisis</h3><p>Para este análisis, generamos un conjunto de datos de ventas de teléfonos sintéticos que incluye precio, especificaciones del teléfono, demografía del comprador y opiniones. También generamos incrustaciones a partir de la retroalimentación del usuario para poder ejecutar una consulta semántica más adelante. Usamos el <a href="https://huggingface.co/intfloat/multilingual-e5-small">modelo pequeño multilingüe e5</a>, disponible de fábrica en Elasticsearch.</p><p></p><p>Para usar este conjunto de datos en Elasticsearch:</p><ol><li>Sube el archivo CSV (descargable <a href="https://github.com/Alex1795/significant_terms_blog_dataset/blob/main/phone_sales_analysis_dataset.csv">desde aquí</a>) usando la función <a href="https://www.elastic.co/docs/manage-data/ingest/upload-data-files">de Upload de archivos de datos</a> de Kibana.</li><li>Configura un campo semántico, como se muestra en <a href="https://www.elastic.co/search-labs/blog/chat-with-pdf-elastic-playground#upload-pdfs-to-kibana">este blog,</a> llamado &quot;embedding&quot;, que emplea el <code>multilingual-e5-small model</code></li><li>Termina la importación con los valores predeterminados del tipo de campo (palabra clave para todos los campos excepto <code>purchase_date</code> y <code>user_feedback)</code>. Cerciórate de agregar el nombre del índice <code>phone_sales_analysis</code> para poder ejecutar las consultas que se presentan aquí tal cual.</li></ol><p>El objetivo principal de este análisis es <strong><em>descubrir &quot;¿Qué diferencia a los compradores del iPhone 16 frente a otros segmentos de la población?&quot;</em></strong> y segmentar a los compradores con fines de marketing. </p><p>Este es un documento de ejemplo del conjunto de datos:</p><pre><code>{
         "customer_type": "Returning",
         "user_feedback": "I have to say, quality is great for the price. The battery life is really good.",
         "upgrade_frequency": "2 years",
         "storage_capacity": "256GB",
         "occupation": "Technology & Data",
         "color": "Phantom Black",
         "gender": "Male",
         "price_paid": 899,
         "previous_brand_loyalty": "Mixed",
         "location_type": "Urban",
         "phone_model": "Samsung Galaxy S24",
         "city_region": "San Francisco Bay Area",
         "@timestamp": "2024-03-15T00:00:00.000-05:00",
         "income_bracket": "75000-100000",
         "purchase_channel": "Online",
         "feedback_sentiment": "positive",
         "education_level": "Bachelor",
         "embedding": "I have to say, quality is great for the price. The battery life is really good.",
         "customer_id": "C001",
         "purchase_date": "2024-03-15",
         "age": 34,
         "trade_in_model": "iPhone 13"
}</code></pre><h3>Comprensión de los patrones demográficos</h3><p>Aquí, vamos a hacer un análisis sobre la población general y compararlo con hallazgos interesantes de las agregaciones significativas de términos para usuarios de iPhone 16.</p><h4>Patrones normales</h4><p>Para entender los patrones normales de compra, podemos agregar datos de todos los documentos de diferentes campos. Para simplificar, nos centraremos en explorar las ocupaciones de las personas que compraron un teléfono. Podemos hacer esto con una solicitud a Elasticsearch.</p><pre><code>GET phone_sales_analysis/_search
{
 "aggs": {
   "occupation_distribution": {
     "terms": {
       "size": 5,
       "field": "occupation"
     }
   }
 },
 "size": 0
}</code></pre><p>Esto nos indica que las principales ocupaciones en el conjunto de datos (por número de registros) son:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/99136ddddd7abad5d74481158a04501b6915441b-1518x480.png" style="width: 100%;" /><h4>Patrones de los usuarios del iPhone 16</h4><p>Para entender qué diferencia la gente que compró un iPhone 16, hagamos una agregación de términos en el mismo campo con un filtro para encontrar a las personas de la consulta, así:</p><pre><code>GET phone_sales_analysis/_search
{
  "query": {
    "term": {
      "phone_model": "iPhone 16"
    }
  },
  "aggs": {
    "occupation_distribution": {
      "terms": {
        "size": 5,
        "field": "occupation"
      }
    }
  },
  "size": 0
}</code></pre><p>Así que, para los usuarios del iPhone 16, las principales ocupaciones son:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/36ce86475beb03810c6ad81d7c776d1eec736654-1500x484.png" style="width: 100%;" /><p>Podemos ver que los usuarios del iPhone 16 tienen patrones de ocupación diferentes en comparación con los de otros modelos de teléfono. Usemos Kibana para visualizar fácilmente los resultados:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b70ab05bc962a274e1617b6caf20575c489a62d8-1448x1128.png" style="width: 100%;" /><p></p><p>En este gráfico, podemos ver que la tendencia del iPhone 16 es diferente a la de toda la población.</p><p>Podemos saltarnos todo este análisis para ver qué diferencia a los usuarios del iPhone 16 de la población general mediante una agregación significativa de términos:</p><pre><code>GET phone_sales_analysis/_search
{
  "query": {
    "term": {
      "phone_model": "iPhone 16"
    }
  },
  "aggs": {
    "occupation_distribution": {
      "significant_terms": {
        "size": 5,
        "field": "occupation"
      }
    }
  },
  "size": 0
}</code></pre><p>En resumen, obtenemos esta respuesta:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Valores de las ocupaciones para el iPhone 16</td><td style="border: 1px solid #ccc; padding: 4px;">doc_count</td><td style="border: 1px solid #ccc; padding: 4px;">bg_count</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">occupation_distribution (nivel superior)</td><td style="border: 1px solid #ccc; padding: 4px;">122</td><td style="border: 1px solid #ccc; padding: 4px;">424</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Sector Médico y Sanitario</td><td style="border: 1px solid #ccc; padding: 4px;">45</td><td style="border: 1px solid #ccc; padding: 4px;">57</td>
                    </tr>
              </tbody>
            </table>
          <p>La respuesta sugiere claramente que los usuarios del iPhone 16 tienen un caso poco común (¡leer, significativo!) número de personas en el ámbito médico y sanitario en comparación con la población general. Veamos qué significan los números de la respuesta:</p><ul><li><strong>En el nivel superior:</strong><ul><li><strong>doc_count: 122 — </strong>La consulta coincidió con un total de 122 documentos</li><li><strong>bg_count: 424 — </strong>El conjunto de antecedentes (todos los documentos de ventas) contiene 424 documentos</li></ul></li><li><strong>En el ámbito Médico y Sanitario:</strong><ul><li><strong>doc_count: 45 — </strong>&quot;Medical &amp; Healthcare&quot; aparece en 45 de los 122 resultados de consulta</li><li><strong>bg_count: 57 — </strong>&quot;Médico y Salud&quot; aparece en 57 de los 424 documentos totales del conjunto de datos de antecedentes</li></ul></li></ul><p>De 424 compradores, 57 trabajan en el ámbito médico y sanitario — o el 13,44%. Pero cuando miramos a los compradores del iPhone 16, 45 de 122 trabajan en el ámbito médico y sanitario — eso supone el 36,88%. ¡Esto significa que tenemos el doble de probabilidades de encontrar a alguien trabajando en el ámbito médico y sanitario entre los usuarios del iPhone 16!</p><p>Podemos aplicar este mismo análisis a otros campos (edad, ubicación, rango de ingresos, etc.) para encontrar más información sobre qué hace único a los usuarios del iPhone 16. </p><h3>Segmentación del consumidor</h3><p>Podemos emplear los términos agregación significativa para extraer información sobre las relaciones entre productos, categorías y segmentos de clientes. Para ello, construimos una agregación madre para la categoría que nos interesa explorar. También usamos términos significativos y una subagregación de términos normales para encontrar ideas interesantes sobre esa categoría y compararlas con lo que la mayoría de las personas en esa ocupación usan.</p><p>Por ejemplo, veamos qué prefieren las personas en algunos campos laborales:</p><ol><li>Para aclarar el análisis, limitemos nuestra búsqueda a 3 campos de trabajo: [&quot;Administrativo y Soporte&quot;, &quot;Tecnología y Datos&quot;, &quot;Médico y Sanidad&quot;]</li><li>En el lado de las agregaciones, comenzamos con una agregación por ocupación</li><li>Agrega una subagregación: términos por modelo de teléfono—para descubrir qué modelos están comprando los usuarios que trabajan en cada campo</li><li>Agregar una segunda subagregación: términos significativos por modelos de teléfono, para encontrar qué modelos son especiales en cada campo de trabajo</li></ol><pre><code>GET phone_sales_analysis/_search
{
 "query": {
   "terms": {
     "occupation": [
       "Administrative & Support",
       "Technology & Data",
       "Medical & Healthcare"
     ]
   }
 },
 "aggs": {
   "occupations": {
     "terms": {
       "size": 15,
       "field": "occupation"
     },
     "aggs": {
       "general_models": {
         "terms": {
           "field": "phone_model"
         }
       },
       "significant_models": {
         "significant_terms": {
           "field": "phone_model"
         }
       }
     }
   }
 },
 "size": 0
}</code></pre><p>Desglosemos los resultados de la agregación:</p><p><strong>Ocupación</strong>: Administrativa y de Apoyo</p><p><strong>Agregación de términos</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a4ad519c9013867a3f4cee032160eadd8a47804a-1506x398.png" style="width: 100%;" /><p><strong>Agregación de términos significativa</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e5604fa8036667bdfe733576a5e7c6153760dd3a-306x220.png" style="width: 100%;" /><p>De esta tabla, podemos inferir que no hay diferencias significativas entre la tendencia de esta ocupación y la tendencia de toda la población</p><p><strong>Ocupación</strong>: Tecnología y Datos</p><p><strong>Agregación de términos</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/13b09039bb7d183276451007d2d69dc190b1d3c0-1508x836.png" style="width: 100%;" /><p></p><p><strong>Agregación de términos significativa</strong></p><p>Total de documentos: 424</p><p>Documentos de esta ocupación: 71</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Modelo de teléfono</td><td style="border: 1px solid #ccc; padding: 4px;">doc_count (este modelo en esta ocupación)</td><td style="border: 1px solid #ccc; padding: 4px;">bg_count (este modelo en todos los documentos)</td><td style="border: 1px solid #ccc; padding: 4px;">% en todos los documentos</td><td style="border: 1px solid #ccc; padding: 4px;">% en esta ocupación</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Google Pixel 8</td><td style="border: 1px solid #ccc; padding: 4px;">12</td><td style="border: 1px solid #ccc; padding: 4px;">22</td><td style="border: 1px solid #ccc; padding: 4px;">5.19%</td><td style="border: 1px solid #ccc; padding: 4px;">16.90%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OnePlus 11</td><td style="border: 1px solid #ccc; padding: 4px;">9</td><td style="border: 1px solid #ccc; padding: 4px;">14</td><td style="border: 1px solid #ccc; padding: 4px;">3.30 %</td><td style="border: 1px solid #ccc; padding: 4px;">12.68%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OnePlus 12 Pro</td><td style="border: 1px solid #ccc; padding: 4px;">3</td><td style="border: 1px solid #ccc; padding: 4px;">3</td><td style="border: 1px solid #ccc; padding: 4px;">0.71 %</td><td style="border: 1px solid #ccc; padding: 4px;">4.23%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Google Pixel 8 Pro</td><td style="border: 1px solid #ccc; padding: 4px;">9</td><td style="border: 1px solid #ccc; padding: 4px;">21</td><td style="border: 1px solid #ccc; padding: 4px;">4.95%</td><td style="border: 1px solid #ccc; padding: 4px;">12.68%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Nothing Phone 2</td><td style="border: 1px solid #ccc; padding: 4px;">5</td><td style="border: 1px solid #ccc; padding: 4px;">8</td><td style="border: 1px solid #ccc; padding: 4px;">1.89%</td><td style="border: 1px solid #ccc; padding: 4px;">7.04%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Samsung Galaxy Z Fold5</td><td style="border: 1px solid #ccc; padding: 4px;">4</td><td style="border: 1px solid #ccc; padding: 4px;">6</td><td style="border: 1px solid #ccc; padding: 4px;">1.42%</td><td style="border: 1px solid #ccc; padding: 4px;">5.63%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OnePlus 12</td><td style="border: 1px solid #ccc; padding: 4px;">8</td><td style="border: 1px solid #ccc; padding: 4px;">20</td><td style="border: 1px solid #ccc; padding: 4px;">4.72%</td><td style="border: 1px solid #ccc; padding: 4px;">11.27 %</td>
                    </tr>
              </tbody>
            </table>
          <p><strong>Ocupación</strong>: Medicina y Sanidad</p><p><strong>Agregación de términos</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b008e996742fc0bb48dc6bacff17cfbc56cf0d73-1492x398.png" style="width: 100%;" /><p><strong>Agregación de términos significativa</strong></p><p>Total de documentos: 424</p><p>Documentos en esta ocupación: 57</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Modelo de teléfono</td><td style="border: 1px solid #ccc; padding: 4px;">doc_count (este modelo en esta ocupación)</td><td style="border: 1px solid #ccc; padding: 4px;">bg_count (este modelo en todos los documentos)</td><td style="border: 1px solid #ccc; padding: 4px;">% en todos los documentos</td><td style="border: 1px solid #ccc; padding: 4px;">% en esta ocupación</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">iPhone 16</td><td style="border: 1px solid #ccc; padding: 4px;">45</td><td style="border: 1px solid #ccc; padding: 4px;">122</td><td style="border: 1px solid #ccc; padding: 4px;">28.77%</td><td style="border: 1px solid #ccc; padding: 4px;">78.95%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">iPhone 15 Pro Max</td><td style="border: 1px solid #ccc; padding: 4px;">3</td><td style="border: 1px solid #ccc; padding: 4px;">13</td><td style="border: 1px solid #ccc; padding: 4px;">3.07 %</td><td style="border: 1px solid #ccc; padding: 4px;">5.26%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">iPhone 15</td><td style="border: 1px solid #ccc; padding: 4px;">7</td><td style="border: 1px solid #ccc; padding: 4px;">40</td><td style="border: 1px solid #ccc; padding: 4px;">9.43%</td><td style="border: 1px solid #ccc; padding: 4px;">12.28%</td>
                    </tr>
              </tbody>
            </table>
          <p>Veamos qué historia nos cuentan estos datos:</p><ul><li>Los profesionales sanitarios prefieren el iPhone 16 y suelen estar muy inclinados a usar teléfonos Apple en general.</li><li>Los profesionales de tecnología y datos prefieren teléfonos Android de gama alta, pero no necesariamente usan la marca Samsung. También hay una tendencia considerable para los iPhones en esta categoría.</li><li>Los profesionales administrativos y de soporte prefieren los teléfonos Samsung y Google, pero no tienen una tendencia fuerte y única.</li></ul><h3>Agregación de términos significativos y búsqueda híbrida</h3><p>La búsqueda híbrida combina la búsqueda por texto y resultados semánticos para ofrecer una experiencia de búsqueda mejorada. En este contexto, una agregación significativa de términos puede aportar información sobre los resultados de una búsqueda consciente del contexto al responder a la pregunta: <strong>¿Qué tiene de especial este conjunto de datos en comparación con todos los documentos?</strong>Para demostrar esta característica, veamos qué modelos están sobrerrepresentados cuando los usuarios hablan de buen rendimiento: </p><ul><li>Vamos a construir una consulta semántica donde encontramos la mejor retroalimentación de los usuarios más cerca de la entrada de &quot;buen rendimiento&quot; en comparación con la incrustación de campos</li><li>También usaremos una búsqueda de texto con los mismos términos sobre el campo de texto user_feedback</li><li>También agregaremos una consulta significativa de términos para encontrar modelos telefónicos que se encuentren con más frecuencia entre estos resultados que en el conjunto de datos completo<br/></li></ul><pre><code>GET phone_sales_analysis/_search
{
 "retriever": {
   "rrf": {
     "retrievers": [
       {
         "standard": {
           "query": {
             "bool": {
               "must": [
                 {
                   "match": {
                     "user_feedback": {
                       "query": "good performance",
                       "operator": "and"
                     }
                   }
                 }
               ]
             }
           }
         }
       },
       {
         "standard": {
           "query": {
             "semantic": {
               "field": "embedding",
               "query": "good performance"
             }
           }
         }
       }
     ],
    "rank_window_size": 20
   }
 },
 "aggs": {
   "Models": {
     "significant_terms": {
       "field": "phone_model"
     }
   }
 }
}</code></pre><p>Veamos un ejemplo de los documentos que coinciden:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4cb488097a382f0c28c21540db4f593d23633473-1600x162.png" style="width: 100%;" /><p>Esta es la respuesta que recibimos:</p><pre><code>{
  "took": 388,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 20,
      "relation": "eq"
    },
    "max_score": 0.016393442,
    "hits": [...]
  },
  "aggregations": {
    "Models": {
      "doc_count": 20,
      "bg_count": 424,
      "buckets": [
        {
          "key": "iPhone 15",
          "doc_count": 5,
          "score": 0.4125,
          "bg_count": 40
        }
      ]
    }
  }
}</code></pre><p></p><p>Esto nos indica que, aunque un iPhone 15 se encuentra 40 veces de un total de 424 documentos (el 9,4% de los documentos), puede encontrar 5 veces en los 20 documentos que coinciden con la búsqueda semántica de &quot;buen rendimiento&quot; (el 25% de los documentos). Por tanto, podemos sacar una conclusión: un iPhone 15 tiene 2,7 veces más probabilidades de encontrar cuando se habla de buen rendimiento que por casualidad.</p><h2>Conclusión</h2><p>Los términos significativos agregación pueden descubrir detalles únicos de un conjunto de datos comparándolo con el universo de documentos. Esto puede revelar relaciones inesperadas en nuestros datos, que van más allá del conteo de ocurrencias. Podemos aplicar términos significativos en diversos casos de uso que permiten características muy interesantes, por ejemplo:</p><ul><li><a href="https://www.elastic.co/blog/significant-terms-aggregation#credit">Encuentra patrones al trabajar en </a>la detección de fraude — identifica transacciones comunes de tarjetas de crédito robadas.</li><li>Información sobre la calidad de la marca a partir de opiniones de usuarios — detecta una marca con un número desproporcionado de malas valoraciones.</li><li><a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significantterms-aggregation#_use_on_free_text_fields">Documentos mal clasificados </a>— documentos puntuales que pertenecen a una categoría (filtro de términos) que usan palabras poco comunes para la categoría en una descripción (agregación significativa de términos).</li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Construcción de un asistente RAG agente con JavaScript, Mastra y Elasticsearch]]></title>
            <description><![CDATA[Aprende a construir agentes de IA en el ecosistema JavaScript]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/agentic-rag</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/agentic-rag</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[JD Armada]]></dc:creator>
            <pubDate>Tue, 01 Jul 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8132ed781c1ea5d46ca244182f421ed5c721f23b-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Esta idea se me ocurrió en medio de una liga de baloncesto de fantasía muy intensa y de alto riesgo. Me preguntaba: <em>¿Podría crear un agente de IA que me ayudara a dominar mis enfrentamientos semanales? ¡Absolutamente!</em></p><p>En esta entrada, exploraremos cómo construir un asistente RAG agente usando <a href="https://mastra.ai/en/docs">Mastra</a> y una aplicación sitio web ligero en JavaScript para interactuar con él. Al conectar este agente con Elasticsearch, le damos acceso a datos estructurados de jugadores y la capacidad de ejecutar agregaciones estadísticas en tiempo real, para ofrecerte recomendaciones basadas en estadísticas de jugadores. Visita el <a href="https://github.com/jdarmada/nba-ai-assistant-js.git">repositorio</a> de GitHub para seguirle; el <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/README.md">README</a> proporciona instrucciones sobre cómo clonar y ejecutar la aplicación por tu cuenta. </p><p>Así debería ver cuando todo esté montado:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1c73bd1dc1b5fe54f025c7a2b7c322acc9122f3a-1999x1393.png" style="width: 100%;" /><p>Nota: Esta entrada del blog se basa en &quot;<a href="https://www.elastic.co/search-labs/blog/ai-agents-ai-sdk-elasticsearch">Construcción de agentes de IA con SDK de IA y Elastic</a>&quot;. Si eres nuevo en los agentes de IA en general y en qué podrían servir, empieza por ahí.<br/></p><h2><strong>Visión general de la arquitectura</strong></h2><p>En el núcleo del sistema se encuentra un gran modelo de lenguaje (LLM), que actúa como motor de razonamiento del agente (el cerebro). Interpreta la entrada del usuario, decide qué herramientas llamar y orquesta los pasos necesarios para generar una respuesta relevante.</p><p>El propio agente está estructurado por Mastra, un framework de agentes dentro del ecosistema JavaScript. Mastra envuelve el LLM con infraestructura backend, lo expone como un endpoint API y proporciona una interfaz para definir herramientas, indicaciones del sistema y comportamiento de agentes.</p><p>En el frontend, usamos <a href="https://vite.dev/guide/">Vite</a> para andamiar rápidamente una aplicación sitio web React que proporciona una interfaz de chat para enviar consultas al agente y recibir sus respuestas.</p><p>Por último, tenemos Elasticsearch, que almacena estadísticas de jugadores y datos de emparejamientos que el agente puede consultar y agregar.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/443bdc00d84ed1dd49e9f9e431e86ca4b0892563-1999x977.png" style="width: 100%;" /><h2><strong>Fondo</strong></h2><p>Vamos a repasar algunos conceptos fundamentales:</p><h3><strong>¿Qué es el RAG agente?</strong></h3><p>Los agentes de IA pueden interactuar con otros sistemas, operar de forma independiente y realizar acciones basadas en sus parámetros definidos. El RAG agente combina la autonomía de un agente de IA con los principios de generación aumentada por recuperación, permitiendo a un LLM elegir qué herramientas llamar y qué datos usar como contexto para generar una respuesta. Lee más sobre <a href="https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag">RAG aquí</a>.</p><h3><strong>Elegir un framework, ¿por qué ir más allá del SDK de IA?</strong></h3><p>Hay muchos frameworks de agentes de IA disponibles y probablemente oíste hablar de los más populares como <a href="https://www.elastic.co/search-labs/blog/using-crewai-with-elasticsearch">CrewAI</a>, <a href="https://www.elastic.co/search-labs/blog/using-autogen-with-elasticsearch">AutoGen</a> y <a href="https://www.elastic.co/search-labs/blog/build-rag-workflow-langgraph-elasticsearch">LangGraph</a>. La mayoría de estos frameworks comparten un conjunto común de funcionalidades, incluyendo soporte para diferentes modelos, uso de herramientas y gestión de memoria.</p><p>Aquí tienes una <a href="https://docs.google.com/spreadsheets/d/1B37VxTBuGLeTSPVWtz7UMsCdtXrqV5hCjWkbHN8tfAo/edit?gid=0#gid=0">hoja comparativa</a> de marcos de Harrison Chase (CEO de LangChain).</p><p>Lo que despertó mi interés con Mastra es que es un framework JavaScript-first diseñado para que desarrolladores full-stack integren fácilmente agentes en su ecosistema. El AI-SDK de Vercel también hace la mayoría de esto, pero donde Mastra destaca es cuando tus proyectos incluyen flujos de trabajo de agentes más complejos. Mastra mejora los patrones base establecido por el AI-SDK y en este proyecto los usaremos conjuntamente.</p><h3><strong>Marcos y consideraciones sobre la elección del modelo</strong></h3><p>Aunque estos frameworks pueden ayudarte a construir agentes de IA rápidamente, existen algunos inconvenientes a tener en cuenta. Por ejemplo, al usar cualquier otro framework fuera de agentes de IA o cualquier capa de abstracción en general, pierdes un poco de control. Si el LLM no emplea correctamente las herramientas o hace algo que no quieres, la abstracción dificulta la depuración. Aun así, en mi opinión, este equilibrio merece la facilidad y rapidez que obtienes al construir, especialmente porque estos frameworks están ganando impulso y se iteran constantemente.</p><p>De nuevo, estos frameworks son independientes de los modelos, lo que significa que puedes conectar y jugar diferentes modelos; recuerda que los modelos varían en los conjuntos de datos con los que fueron capacitados y, a su vez, varían en las respuestas que dan. Algunos modelos ni siquiera soportan la llamada de herramientas. Así que es posible cambiar y probar diferentes modelos para ver cuál te da las mejores respuestas, pero ten en cuenta que probablemente tendrás que reescribir el prompt del sistema para cada uno. Por ejemplo, usando Llama3.3 en comparación con GPT-4o, implica mucho más indicación e instrucciones específicas para obtener la respuesta que buscas.</p><h3><strong>Baloncesto de fantasía de la NBA</strong></h3><p>El baloncesto de fantasía consiste en empezar una liga con un grupo de colegas (advertencia, dependiendo de lo competitivo que sea tu grupo, esto podría afectar al estado de tus amistades), normalmente con algo de dinero en juego. Cada uno de vosotros selecciona un equipo de 10 jugadores para competir contra los 10 jugadores de otro amigo alternando semanalmente. Los puntos que contribuyen a tu puntaje global son cómo se desempeña cada uno de tus jugadores contra sus oponentes en una semana determinada.</p><p>Si un jugador de tu equipo se lesiona, es suspendido, etc., hay una lista de jugadores agentes libres disponibles para agregar a tu equipo. Aquí es donde ocurre gran parte de la dificultad en los deportes de fantasía, porque solo tienes un número limitado de fichas y todos están constantemente a la caza del mejor jugador.</p><p>Aquí es donde nuestro asistente de IA de la NBA brillará, especialmente en situaciones en las que tienes que decidir rápidamente qué jugador elegir. En lugar de tener que buscar manualmente cómo rinde un jugador contra un oponente específico, el asistente puede encontrar esos datos rápidamente y comparar promedios para darte una recomendación informada.</p><p>Ahora que ya sabes lo básico sobre el RAG agente y el baloncesto fantasy de la NBA, vamos a verlo en la práctica.</p><h2><strong>Construcción del proyecto</strong></h2><p>Si te quedas atascado en algún momento o no quieres montarlo desde cero, por favor consulta el <a href="https://github.com/jdarmada/nba-ai-assistant-js.git">repositorio</a>.</p><h3><strong>Lo que vamos a cubrir</strong></h3><ol><li><strong>Andamiaje del proyecto:</strong><ol><li><strong>Backend (Mastra):</strong> Usa NPX Create mastra@latest para andamiar el backend y definir la lógica del agente.</li><li><strong>Frontend (Vite + React):</strong> Usa npm create vite@latest para construir la interfaz de chat del frontend y así interactuar con el agente.</li></ol></li><li><strong>Configuración de variables de entorno</strong><ol><li>Instala dotenv para gestionar las variables del entorno.</li><li>Crea un .env archiva y proporciona las variables requeridas.</li></ol></li><li><strong>Configuración de Elasticsearch</strong><ol><li>Crea un clúster de Elasticsearch (ya sea localmente o en la nube).</li><li>Instala el cliente oficial de Elasticsearch.</li><li>Cerciórate de que las variables del entorno sean accesibles.</li><li>Establece conexión con el cliente.</li></ol></li><li><strong>Ingiriendo en masa datos de la NBA en Elasticsearch</strong><ol><li>Crea un índice con los mapeos apropiados para permitir agregaciones.</li><li>Ingirir en masa las estadísticas de los jugadores de un archivo CSV en un índice de Elasticsearch.</li></ol></li><li><strong>Definir agregaciones de Elasticsearch</strong><ol><li>Consulta para calcular promedios históricos contra un oponente específico.</li><li>Consulta para calcular los promedios de temporada contra un rival específico.</li></ol></li><li><strong>Archivo utilitario de comparación de jugadores</strong><ol><li>Consolida funciones auxiliares y agregaciones de Elasticsearch.</li></ol></li><li><strong>Construcción del agente</strong><ol><li>Agrega la definición del agente y el prompt del sistema.</li><li>Instala Zod y define herramientas.</li><li>Agregar una configuración de middleware para manejar CORS.</li></ol></li><li><strong>Integración del frontend</strong><ol><li>Usando el useChat de AI-SDK para interactuar con el agente.</li><li>Crea la interfaz para mantener conversaciones bien formateadas.</li></ol></li><li><strong>Ejecutando la aplicación</strong><ol><li>Inicia tanto el backend (servidor Mestra) como el frontend (app React).</li><li>Ejemplos de consultas y uso.</li></ol></li><li><strong>Qué sigue: Hacer que el agente sea más inteligente</strong><ol><li>Agregar capacidades de búsqueda semántica para permitir recomendaciones más profundas.</li><li>Habilitar la consulta dinámica moviendo la lógica de búsqueda al servidor Elasticsearch MCP (Model Context Protocol).</li></ol></li></ol><h3><strong>Prerrequisitos</strong></h3><ul><li><strong>Node.js y NPM</strong>: Tanto el backend como el frontend funcionan en Node. Cerciórate de tener instalados Node 18+ y npm v9+ (que vienen incluidos con Node 18+).</li><li><strong>Clúster de Elasticsearch:</strong> Un clúster activo de Elasticsearch, ya sea localmente o en la nube.</li><li><strong>Clave API de OpenAI</strong>: Genera una en la página de claves API <a href="https://platform.openai.com/api-keys">del portal para desarrolladores de OpenAI</a>.</li></ul><p></p><h3><strong>Estructura del proyecto</strong></h3><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1c0bde11ad0eead523a95e03b9b905aa776e3fd1-1420x934.png" style="width: 100%;" /><h4><strong>Paso 1: Estructurar el proyecto</strong></h4><ol><li>Primero, crea el directorio nba-ai-assistant-js y navega dentro usando: </li></ol><pre><code>mkdir nba-ai-assistant-js && cd nba-ai-assistant-js</code></pre><p><strong>Backend:</strong></p><ol><li>Usa la herramienta de creación de Mastra con el comando: </li></ol><pre><code>npx create-mastra@latest</code></pre><p>2. Deberías recibir algunos prompts en tu terminal; para el primero, llamaremos al backend del proyecto:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/de2725031ed6837db99a979efcdd0ece1e197dbb-608x84.png" style="width: 100%;" /><p>3. A continuación, mantendremos la estructura predeterminada para almacenar los archivos Mastra, así que introduzca <code>src/</code>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/88919d9ff1852126395e1fcd700ecb1b59aac63c-866x116.png" style="width: 100%;" /><p>4. Luego, elegiremos OpenAI como nuestro proveedor de LLM por defecto.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2328761e769f3ded134e5a21e8a0bf8f41e88f68-404x210.png" style="width: 100%;" /><p>5. Finalmente, te pedirá la clave de la API de OpenAI. Por ahora, elegiremos la opción de saltarlo y proporcionarlo más adelante en un archivo<code> .env</code> .</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0662de9bd28758e377e4c63df8d08b479068ce63-444x120.png" style="width: 100%;" /><p><strong>Frontend:</strong></p><ol><li>Vuelve al directorio raíz y ejecuta la <a href="https://vite.dev/guide/">herramienta de creación de Vite</a> usando este comando: <code>npm create vite@latest frontend -- --template react</code></li></ol><p>Esto debería crear una aplicación ligera de React llamada <code>frontend</code> con una plantilla específica para React.</p><p>Si todo va bien, dentro de tu directorio de proyecto, deberías estar mirando un directorio backend que contenga el código Mastra y un directorio <code>frontend</code> con tu app React.</p><p></p><h4><strong>Paso 2: Configuración de variables de entorno</strong></h4><ol><li>Para gestionar claves sensibles, usaremos el paquete <code>dotenv</code> para cargar nuestras variables de entorno desde el .env archivo. Navega al directorio del backend e instala <code>dotenv</code>:</li></ol><pre><code>cd backend
npm install dotenv --save</code></pre><p>2. Mientras está en el directorio backend, se proporciona un archivo example.env con las variables adecuadas para rellenar. Si creas el tuyo propio, cerciórate de incluir las siguientes variables:</p><pre><code># OpenAI Configuration
OPENAI_API_KEY=your_openai_api_key_here

# Elasticsearch Configuration
ELASTIC_ENDPOINT=your_elasticsearch_endpoint_here
ELASTIC_API_KEY=your_elasticsearch_api_key_here
</code></pre><p></p><p>Nota: Cerciórate de que este archivo esté excluido de tu control de versiones agregando <code>.env</code> a <code>.gitignore</code>.</p><h4><strong>Paso 3: Configuración de Elasticsearch</strong></h4><p>Primero, necesitas un clúster activo de Elasticsearch. Hay dos opciones:</p><ul><li><strong>Opción A: Usar Elasticsearch Cloud</strong><ul><li>Apúntate a <a href="https://cloud.elastic.co/registration">Elastic Cloud</a></li><li>Crear un nuevo despliegue</li><li>Obtén la URL de tu endpoint y la clave API (codificada)</li></ul></li><li><strong>Opción B: Ejecutar Elasticsearch localmente</strong><ul><li>Instala y ejecuta Elasticsearch localmente</li><li>Usa http://localhost:9200 como punto final</li><li>Generar una clave API</li></ul></li></ul><p></p><p><strong>Instalación del cliente Elasticsearch en el backend:</strong></p><ol><li>Primero, instala el cliente oficial de Elasticsearch en tu directorio backend:</li></ol><pre><code>npm install @elastic/elasticsearch</code></pre><p>2. Luego crea una lib de directorios para almacenar funciones reutilizables y navega hacia ella:</p><pre><code>mkdir lib && cd lib</code></pre><p>3. Dentro, crea un nuevo archivo llamado <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/elasticClient.js">elasticClient.js</a>. Este archivo inicializará el cliente Elasticsearch y lo expondrá para su uso en todo tu proyecto.</p><p>4. Como usamos módulos ECMAScript (ESM), __dirname and __nombre de archivo no están disponibles. Para cerciorarte de que tus variables de entorno se carguen correctamente desde el archivo .env Archivo en la carpeta de backend, agrega esta configuración en la parte superior de tu archivo:</p><pre><code>import { config } from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { Client } from '@elastic/elasticsearch';

// Grab current directory and load .env from backend folder
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const envPath = join(__dirname, '../.env');

// Load environment variables from the correct path
config({ path: envPath });</code></pre><p>5. Ahora, inicializa el cliente Elasticsearch usando tus variables de entorno y comprueba la conexión:</p><pre><code>//Elastic client Initialization, make sure environment variables are being loaded in correctly
const config= {
    node: `${process.env.ELASTIC_ENDPOINT}`,
    auth: {
        apiKey: `${process.env.ELASTIC_API_KEY}`,
    },
};

export const elasticClient = new Client(config);

//Check if the client is connected
async function checkConnection() { 
    try {
        const info = await elasticClient.info();
        console.log('Elasticsearch is connected:', info);
    } catch (error) {
        console.error('Elasticsearch connection error:', error);
    }
}

checkConnection();
</code></pre><p>Ahora, podemos importar esta instancia cliente a cualquier archivo que necesite interactuar con tu clúster de Elasticsearch.</p><p></p><h4><strong>Paso 4: Ingirir en masa datos de la NBA en Elasticsearch</strong></h4><p><strong>Conjunto de datos:</strong></p><p>Para este proyecto, consultaremos los conjuntos de datos disponibles en el <a href="https://github.com/jdarmada/nba-ai-assistant-js/tree/main/backend">directorio de backend/datos</a> del repositorio. Nuestro asistente de la NBA empleará estos datos como base de conocimiento para realizar comparaciones estadísticas y generar recomendaciones.</p><ul><li><a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/data/sample_nba_data.csv">sample_player_game_stats.csv</a> - Estadísticas de juego de un jugador (por ejemplo, puntos, rebotes, robos, etc., por partido por jugador a lo largo de toda su carrera en la NBA). Emplearemos este conjunto de datos para realizar agregaciones. (Nota: Estos son datos simulados, pregenerados para fines demo y no provenientes de fuentes oficiales de la NBA.)</li><li><a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/data/playerAndTeamInfo.js">playerAndTeamInfo.js</a> - Sustituye los metadatos de jugadores y equipos que normalmente proporcionaría una llamada a la API para que el agente pueda relacionar los nombres de jugadores y equipos con IDs. Como usamos datos de muestra, no queremos la sobrecarga de buscar desde una API externa, así que codificamos de forma fija algunos valores que el agente puede referenciar.</li></ul><p></p><p><strong>Implementación:</strong></p><ol><li>Mientras estés en el directorio <code>backend/lib</code> , crea un archivo llamado <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/playerDataIngestion.js">playerDataIngestion.js</a>.</li><li>Configura las importaciones, resuelve la ruta del archivo CSV y configura el análisis sintáctico. De nuevo, como estamos usando ESM, necesitamos reconstruir <code>__dirname</code> para resolver el camino hacia el CSV de muestra. Además, importaremos <a href="http://node.js/">Node.js</a> módulos incorporados, <code>fs</code> y <code>readline</code>, para analizar el archivo CSV dado línea por línea.</li></ol><pre><code>import fs from 'fs';
import readline from 'readline';
import path from 'path';
import { fileURLToPath } from 'url';
import { elasticClient } from './elasticClient.js';

const indexName = 'sample-nba-player-data'; //Replace with your preferred index name

//Since we are using ES modules __dirname and __filename don't exist, so this is a workaround that allows us to use the absolute file path for our sample data.
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const filePath = path.resolve(__dirname, '../data/sample_nba_data.csv');</code></pre><p>Esto te prepara para leer y analizar eficientemente el CSV cuando lleguemos al paso de la ingestión masiva.</p><p>3. Crear un índice con el mapeo adecuado. Aunque Elasticsearch puede inferir automáticamente los tipos de campos con <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dynamic">mapeo dinámico</a>, queremos ser explícitos aquí para que cada estadística se trate como un campo numérico. Esto es importante porque usaremos estos campos para agregaciones más adelante. También queremos usar el tipo <code>float </code>para estadísticas como puntos, rebotes, etc., para cerciorarnos de incluir valores decimales. Finalmente, queremos agregar la propiedad de mapeo <code>dynamic: &#x27;strict&#x27;</code> para que Elasticsearch no mapee dinámicamente campos no reconocidos. <br/></p><pre><code>// Function to create an index with mappings
async function createIndex() {
    try {
        // Check if the index already exists
        const exists = await elasticClient.indices.exists({ index: indexName });

        if (exists) {
            console.log(`Index "${indexName}" already exists, deleting it now.`);
            await elasticClient.indices.delete({ index: indexName });
            console.log(`Deleted index "${indexName}".`);
        }
        // Create the index with mappings
        const response = await elasticClient.indices.create({
            index: indexName,
            body: {
                mappings: {
                    dynamic: 'strict', // Prevent dynamic mapping
                    properties: {
                        game_id: { type: 'integer' },
                        game_date: { type: 'date' },
                        player_id: { type: 'integer' },
                        player_full_name: { type: 'text' },
                        player_team_id: { type: 'integer' },
                        player_team_name: { type: 'text' },
                        home_team: { type: 'boolean' },
                        opponent_team_id: { type: 'integer' },
                        opponent_team_name: { type: 'text' },
                        points: { type: 'float' },
                        rebounds: { type: 'float' },
                        assists: { type: 'float' },
                        steals: { type: 'float' },
                        blocks: { type: 'float' },
                        fg_percentage: { type: 'float' },
                        minutes_played: { type: 'float' },
                    },
                },
            },
        });

        console.log('Index created:', response);
        return true;
    } catch (error) {
        console.error('Error creating index:', error);
        return false;
    }
}
</code></pre><p>4. Agregar la función para ingirte en masa los datos CSV en tu índice de Elasticsearch. Dentro del bloque de código, saltamos la línea de cabecera. Luego, divide cada elemento de línea por una coma y empújalos en el objeto documento. Este paso también los limpia y cerciora que sean del tipo adecuado. A continuación, enviamos los documentos al array bulkBody junto con la información del índice, que servirá como carga útil para la ingestión masiva en Elasticsearch.</p><pre><code>async function bulkIngestCsv(filePath) {
    const readStream = fs.createReadStream(filePath);
    const rl = readline.createInterface({
        input: readStream,
        crlfDelay: Infinity,
    });

    const bulkBody = [];
    let lineNum = 0;

    //Skip the header line
    let headerLine = true;
    for await (const line of rl) {
        if (headerLine) {
            headerLine = false;
            continue;
        }
        lineNum++;

        // Split the line by comma and remove whitespace
        const [
            game_id,
            game_date,
            player_id,
            player_full_name,
            player_team_id,
            player_team_name,
            home_team,
            opponent_team_id,
            opponent_team_name,
            points,
            rebounds,
            assists,
            steals,
            blocks,
            fg_percentage,
            minutes_played,
        ] = line.split(',');

        // Create a document object
        const document = {
            game_id: parseInt(game_id),
            game_date: game_date.trim(),
            player_id: parseInt(player_id),
            player_full_name: player_full_name.trim(),
            player_team_id: parseInt(player_team_id),
            player_team_name: player_team_name.trim(),
            home_team: home_team.trim() === 'True', // Converts True/False into a boolean
            opponent_team_id: parseInt(opponent_team_id),
            opponent_team_name: opponent_team_name.trim(),
            points: parseFloat(points),
            rebounds: parseFloat(rebounds),
            assists: parseFloat(assists),
            steals: parseFloat(steals),
            blocks: parseFloat(blocks),
            fg_percentage: parseFloat(fg_percentage),
            minutes_played: parseFloat(minutes_played),
        };

        // Prepare the bulk operation format
        bulkBody.push({ index: { _index: indexName } });
        bulkBody.push(document);
    }

    console.log(`Parsed ${lineNum} lines from CSV`);
</code></pre><p>5. Luego, podemos usar la <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk">API Bulk</a> de Elasticsearch con <code>elasticClient.bulk()</code> para ingirse varios documentos en una sola solicitud. La gestión de errores a continuación está estructurada para darte un recuento de cuántos documentos no se ingirieron y cuántos fueron exitosos.</p><pre><code>try {
        // Perform the bulk request
        const response = await elasticClient.bulk({ body: bulkBody });

        if (response.errors) {
            console.log('Bulk Ingestion had some hiccups:');

            // Count successful vs failed operations
            let successCount = 0;
            let errorCount = 0;
            const errorDetails = [];

            response.items.forEach((item, index) => {
                const operation = item.index || item.create || item.update || item.delete;
                if (operation.error) {
                    errorCount++;
                    errorDetails.push({
                        document: index + 1,
                        error: operation.error,
                    });
                } else {
                    successCount++;
                }
            });

            console.log(`Successfully indexed: ${successCount} documents`);
            console.log(`Failed to index: ${errorCount} documents, here are the details`, errorDetails);

        } else {
            console.log(`Bulk Ingestion fully successful!`);
        }

    } catch (error) {
        console.error('Error performing bulk ingestion:', error);
    }
}
</code></pre><p>6. Ejecuta la función <code>main()</code> a continuación para ejecutar secuencialmente las funciones <code>createIndex()</code> y <code>bulkIngestCsv()</code> .</p><pre><code>// Run this function
async function main() {
    const result = await createIndex();
    if (!result) {
        console.error('Index setup failed. Aborting.');
        return;
    }

    await bulkIngestCsv(filePath);
    console.log('Bulk ingestion completed!');
}

main();
</code></pre><p>Si ves un registro de consola que indique que la ingesta masiva fue exitosa, realiza una comprobación rápida en tu índice de Elasticsearch para ver si los documentos realmente se ingirieron con éxito.</p><h4><strong>Paso 5: Definición de agregaciones de Elasticsearch y consolidación</strong></h4><p>Estas serán las funciones principales que se emplearán cuando definamos las herramientas para el Agente de IA para comparar las estadísticas de los jugadores entre sí.</p><p>1. Navega al directorio <code>backend/lib</code> y crea un archivo llamado <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/elasticAggs.js">elasticAggs.js</a>.</p><p>2. Agregar la consulta siguiente para calcular los promedios históricos de un jugador frente a un oponente específico. Esta consulta emplea un <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/full-text-search/filters">filtro</a> <code>bool</code> con 2 condiciones: una que coincide con <code>player_id</code> y otra que coincide con la <code>opponent_team_id</code>, para recuperar solo los juegos relevantes. No necesitamos devolver ningún documento, solo nos importan las agregaciones, así que establecemos <code>size:0</code>. Bajo el bloque <code>aggs</code> , ejecutamos múltiples <a href="https://www.elastic.co/docs/explore-analyze/query-filter/aggregations">agregaciones métricas</a> en paralelo en campos como <code>points, rebounds, assists, steals, blocks</code> y <code>fg_percentage</code> para calcular sus valores medios. Los LLMs pueden ser impredecibles con los cálculos y esto transfiere ese proceso a Elasticsearch, cerciorando que nuestro asistente de IA de la NBA tenga acceso a datos precisos.</p><pre><code>export async function getHistoricalAveragesAgainstOpponent(player_id, opponent_team_id) {
    try {
        //Query for Historical Averages
        const historicalQuery = await elasticClient.search({
            index: 'sample-nba-player-data', 
            size: 0,
            query: {
                bool: {
                    must: [
                        {
                            term: {
                                player_id: {
                                    value: player_id,
                                },
                            },
                        },
                        {
                            term: {
                                opponent_team_id: {
                                    value: opponent_team_id,
                                },
                            },
                        },
                    ],
                },
            },
            aggs: {
                avg_points: { avg: { field: 'points' } },
                avg_rebounds: { avg: { field: 'rebounds' } },
                avg_assists: { avg: { field: 'assists' } },
                avg_steals: { avg: { field: 'steals' } },
                avg_blocks: { avg: { field: 'blocks' } },
             avg_fg_percentage: { avg: { field: 'fg_percentage' } },
            },
        });

        return {
            points: historicalQuery.aggregations.avg_points.value || 0,
            rebounds: historicalQuery.aggregations.avg_rebounds.value || 0,
            assists: historicalQuery.aggregations.avg_assists.value || 0,
            steals: historicalQuery.aggregations.avg_steals.value || 0,
            blocks: historicalQuery.aggregations.avg_blocks.value || 0,
            fgPercentage: historicalQuery.aggregations.avg_fg_percentage.value || 0,
        };
    } catch (error) {
        console.error('Query error from getHistoricalAveragesAgainstOpponent function:', error);
        return { error: 'Queries failed in getting historical averages against opponent.' };
    }
}
</code></pre><p>3. Para calcular los promedios de temporada de un jugador frente a un oponente específico, usaremos prácticamente la misma consulta que la histórica. La única diferencia en esta consulta es que el filtro <code>bool</code> tiene una condición adicional para <code>game_date</code>. El <code>game_date</code> del campo tiene que estar dentro del rango de la temporada actual de la NBA. En este caso, el rango está entre <code>2024-10-01</code> y <code>2025-06-30</code>. Esta condición adicional a continuación garantiza que las agregaciones que sigan aislarán solo los partidos de esta temporada.<br/></p><pre><code>        {
                            range: {
                    //Range for this season, change to match current season
                                game_date: {
                                    gte: '2024-10-01',
                                    lte: '2025-06-30',
                                },
                            },
</code></pre><h4><strong>Paso 6: Utilidad de comparación de jugadores</strong></h4><p>Para mantener nuestro código modular y mantenible, crearemos un archivo de utilidad que consolide las funciones auxiliares de metadatos y las agregaciones de Elasticsearch. Esto alimentará la herramienta principal que emplea el agente. Más sobre eso más adelante:</p><p>1. Crear un nuevo <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/comparePlayers.js">archivo comparePlayers.js</a> en el directorio <code>backend/lib</code> .</p><p>2. Agregar la función siguiente para consolidar los asistentes de metadatos y la lógica de agregación de Elasticsearch en una única función que alimente la herramienta principal empleada por el agente.<br/></p><pre><code>import { playersByName } from '../data/playerAndTeamInfo.js';
import { teamsByName } from '../data/playerAndTeamInfo.js';
import { upcomingMatchups } from '../data/playerAndTeamInfo.js';
import { getHistoricalAveragesAgainstOpponent } from './elasticAggs.js';
import { getSeasonAveragesAgainstOpponent } from './elasticAggs.js';

//Simple helper functions to simulate API calls for player and team metadata. These reference the hardcoded values from playerAndTeamInfo.js in the data directory
export function getPlayerInfo(playerFullName) {
    return playersByName[playerFullName];
}

export function getTeamID(teamFullName) {
    return teamsByName[teamFullName];
}

export function getUpcomingMatchups(teamId) {
    return upcomingMatchups[teamId];
}

//Main function used by the 'playerComparisonTool' agent tool
export async function comparePlayersForNextMatchup(player1Name, player2Name) {
    //Get Player Info
    const player1Info = getPlayerInfo(player1Name);
    const player2Info = getPlayerInfo(player2Name);

    //Get upcoming matchups
    const player1NextGame = getUpcomingMatchups(player1Info.team_id)[0];
    const player2NextGame = getUpcomingMatchups(player2Info.team_id)[0];

    //Get season and historical averages against next opponent for player 1
    const player1SeasonAverages = await getSeasonAveragesAgainstOpponent(
        player1Info.player_id,
        player1NextGame.opponent_team_id
    );
    const player1HistoricalAverages = await getHistoricalAveragesAgainstOpponent(
        player1Info.player_id,
        player1NextGame.opponent_team_id
    );

    //Get season and historical averages against next opponent for player 2
    const player2SeasonAverages = await getSeasonAveragesAgainstOpponent(
        player2Info.player_id,
        player2NextGame.opponent_team_id
    );
    const player2HistoricalAverages = await getHistoricalAveragesAgainstOpponent(
        player2Info.player_id,
        player2NextGame.opponent_team_id
    );

    const player1 = {
        name: player1Name,
        playerId: player1Info.player_id,
        teamId: player1Info.team_id,
        nextOpponent: {
            teamId: player1NextGame.opponent_team_id,
            teamName: player1NextGame.opponent_team_name,
            home: player1NextGame.home,
        },
        stats: {
            seasonAverages: player1SeasonAverages,
            historicalAverages: player1HistoricalAverages,
        },
    };

    const player2 = {
        name: player2Name,
        playerId: player2Info.player_id,
        teamId: player2Info.team_id,
        nextOpponent: {
            teamId: player2NextGame.opponent_team_id,
            teamName: player2NextGame.opponent_team_name,
            home: player2NextGame.home,
        },
        stats: {
            seasonAverages: player2SeasonAverages,
            historicalAverages: player2HistoricalAverages,
        },
    };

    return [player1, player2];
}
</code></pre><h4><strong>Paso 7: Construir el agente</strong></h4><p>Ahora que creaste el frontend y el backend de estructura, absorbiste datos de los partidos de la NBA y establecido una conexión con Elasticsearch, podemos empezar a juntar todas las piezas para construir el agente.</p><p><strong>Definición del agente</strong></p><p>1. Navega al archivo <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/src/mastra/agents/index.ts">index.ts</a> dentro del directorio <code>backend/src/mastra/agents</code> y agrega la definición del agente. Puedes especificar campos como:</p><ul><li><strong>Nombre:</strong> Dale a tu agente un nombre que se use como referencia cuando te llamen en el frontend.</li><li><strong>Instrucciones/prompt del sistema: </strong>Un prompt del sistema proporciona al LLM el contexto inicial y las reglas a seguir durante la interacción. Es similar al prompt que los usuarios envían a través del cuadro de chat, pero este se da antes de cualquier entrada del usuario. De nuevo, esto cambiará según el modelo que elijas.</li><li><strong>Modelo:</strong> ¿Qué LLM usar (Mastra soporta OpenAI, Anthropic, modelos locales, etc.).</li><li><strong>Herramientas:</strong> Una lista de funciones de la herramienta que el agente puede llamar.</li><li><strong>Memoria:</strong> (Opcional) si queremos que el agente recuerde el historial de conversaciones, etc. Para simplificar, podemos empezar sin memoria persistente, aunque Mastra la admite.</li></ul><p></p><pre><code>import { openai } from '@ai-sdk/openai';
import { Agent } from '@mastra/core/agent';
import { playerComparisonTool } from '../tools';

export const basketballAgent = new Agent({
    name: 'Basketball Agent',
    instructions: `
      You are a NBA Basketball expert.
      Your primary function is to compare two NBA players and recommend which one is the better fantasy pickup.

      Only compare players from the following list:
      - LeBron James
      - Stephen Curry
      - Jayson Tatum
      - Jaylen Brown
      - Nikola Jokic
      - Luka Doncic
      - Kyrie Irving
      - Anthony Davis
      - Kawhi Leonard
      - Russell Westbrook

      Input Handling Rules:
      - If the user asks about a player that is not on this list, respond with the list of available players for comparison.
      - If the user only inputs one player, ask the user to add another player from the list provided.
      - If the user inputs a player with the wrong spelling or capitalizations, infer from the list of available players provided.
      - IMPORTANT: If the user asks a question or asks you to generate a response about anything outside of basketball or the scope of this project, DO NOT answer and affirm you can only talk about basketball.

      Tool Usage:
      - Extract and standardize player names to match the list exactly.
      - Use the playerComparisonTool, passing both names as strings.
      - The tool will return an object with game information, stats, and analysis.

      Format your response using Markdown syntax. Use:

        Example output format:

       
        #### Next Game Info
        - ***LeBron James** vs Warriors, May 24 (Home)  
        - ***Stephen Curry** vs Lakers, May 24 (Away)


        #### Stats Comparison  
        \`\`\`  
        Stat                  LeBron James (vs Warriors)    Stephen Curry (vs Lakers)  
        --------------------  -----------------------------  ----------------------------  
        Historical Points     28.3                          30.3  
        Historical Assists    6.7                           8.7  
        Season Points         28.8                          23.3  
        Season Assists        6.2                           4.7  
        \`\`\`

        #### Fantasy Recommendation  
        Explain which player is the better fantasy pickup and why.
      
    `,
    model: openai('gpt-4o'),
    tools: { playerComparisonTool },
});
</code></pre><p><strong><br/>Definición de herramientas</strong></p><ol><li>Navega al archivo <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/src/mastra/tools/index.ts">index.ts</a> dentro del directorio <code>backend/src/mastra/tools</code> .</li><li>Instala Zod usando el comando:</li></ol><pre><code>npm install zod</code></pre><p>3. Agregar definiciones de herramientas. Ten en cuenta que importamos la función dentro del archivo <code>comparePlayers.js</code> como la función principal que el agente usará al llamar a esta herramienta. Usando la función <code>createTool()</code> de Mastra, registraremos nuestro <code>playerComparisonTool</code>. Los campos incluyen:</p><ul><li><code>id</code>: Esta es una descripción en lenguaje natural para ayudar al agente a entender qué hace la herramienta.</li><li><code>input schema</code>: Para definir la forma de la entrada de la herramienta, Mastra emplea el esquema <a href="https://zod.dev/">Zod</a> , que es una biblioteca de validación de esquema TypeScript. Zod ayuda cerciorar de que el agente reciba entradas estructuradas correctamente y evita que la herramienta se ejecute si la estructura de entrada no coincide.</li><li><code>description</code>: Esta es una descripción en lenguaje natural para ayudar al agente a entender cuándo llamar y usar la herramienta.</li><li><code>execute</code>: La lógica que se ejecuta cuando se llama a la herramienta. En nuestro caso, estamos usando una función auxiliar importada para devolver estadísticas de rendimiento.</li></ul><pre><code>import { comparePlayersForNextMatchup } from '../../../lib/comparePlayers.js'
import { createTool } from "@mastra/core/tools";
import { z } from "zod";

export const playerComparisonTool = createTool({
    id: "Compare two NBA players",
    inputSchema: z.object({
        player1:z.string(),
        player2:z.string()
    }),
    description: "Use this tool to compare two players given in the user prompt.",
    execute: async ({ context: { player1, player2 } }) => {
        return await comparePlayersForNextMatchup(player1, player2);
      },
})</code></pre><p><strong>Adición de middleware para manejar CORS</strong></p><p>Agregar middleware en el servidor Mastra para manejar <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS">CORS</a>. Dicen que hay tres cosas en la vida que no puedes evitar: la muerte, los impuestos, y para los desarrolladores web es el CORS. En resumen, el Intercambio de Recursos entre Orígenes es una función de seguridad en el navegador que bloquea al frontend para que no haga peticiones a un backend que se ejecuta en otro dominio o puerto. Aunque ejecutamos tanto el backend como el frontend en localhost, usan puertos diferentes, lo que activa la política CORS. Necesitamos agregar el middleware especificado en la <a href="https://mastra.ai/en/docs/server-db/middleware">documentación de Mastra</a> para que nuestro backend permita esas peticiones desde el frontend.</p><p>1. Navega al archivo <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/src/mastra/index.ts">index.ts</a> dentro del directorio <code>backend/src/mastra</code> y agrega la configuración para CORS:</p><ul><li><code>origin: [&#x27;http://localhost:5173&#x27;]</code><ul><li>Permite solicitudes solo desde esta dirección (dirección predeterminada de Vite)</li></ul></li><li><code>allowMethods: [&quot;GET&quot;, &quot;POST&quot;]</code><ul><li>Métodos HTTP que están permitidos. La mayoría de las veces, será usando POST.</li></ul></li><li><code>allowHeaders: [&quot;Content-Type&quot;, &quot;Authorization&quot;, &quot;x-mastra-client-type, &quot;x-highlight-request&quot;, &quot;traceparent&quot;],</code><ul><li>Estos deciden qué encabezados personalizados pueden usar en las solicitudes</li></ul></li></ul><p></p><pre><code>import { Mastra } from '@mastra/core/mastra';
import { basketballAgent } from './agents';

console.log('Starting Mastra server...');

export const mastra = new Mastra({
  agents: { basketballAgent },
  server:{
    timeout: 10 * 60 * 1000, // 10 minutes
    cors: {
      origin: ['http://localhost:5173'],
      allowMethods: ["GET", "POST"],
      allowHeaders: [
        "Content-Type",
        "Authorization",
        "x-mastra-client-type",
        "x-highlight-request",
        "traceparent",
      ],
      exposeHeaders: ["Content-Length", "X-Requested-With"],
      credentials: false,
    },
  },

});

console.log('Mastra server configured.'); // Log after server configuration
</code></pre><h4><strong>Paso 8: Integración del frontend</strong></h4><p>Este componente React proporciona una interfaz de chat sencilla que se conecta al agente Mastra AI usando el gancho <a href="https://mastra.ai/en/docs/frameworks/agentic-uis/ai-sdk#using-the-usechat-hook">useChat()</a> de <code>@ai-sdk/react</code>. También vamos a usar este gancho para mostrar el uso de tokens, llamadas a herramientas y para renderizar la conversación. En el prompt del sistema anterior, también pedimos al agente que genere la respuesta en markdown, así que usaremos <code>react-markdown</code> para formatear correctamente la respuesta.</p><p></p><p>1. Mientras estés en el directorio frontend, instala el paquete @ai-sdk/react para usar el hook useChat().</p><pre><code>npm install @ai-sdk/react</code></pre><p>2. Mientras estés en el mismo directorio, instala React Markdown para que podamos formatear correctamente la respuesta que genera el agente.</p><pre><code>npm install react-markdown</code></pre><p>3. Implementar <code>useChat()</code>. Este gancho gestionará la interacción entre tu frontend y el backend de tu agente de IA. Gestiona el estado del mensaje, la entrada del usuario, el estado y te proporciona ganchos de ciclo de vida para fines de observabilidad. Las opciones que descartamos incluyen:</p><ul><li><code>api:</code> Esto define el punto final de tu Mastra AI Agent. Por defecto se pone en el puerto 4111 y también queremos agregar la ruta que soporta respuestas en streaming.</li><li><code>onToolCall</code>: Esto se ejecuta cada vez que el agente llama a una herramienta; Lo estamos usando para rastrear qué herramientas está llamando nuestro agente.</li><li><code>onFinish</code>: Esto se ejecuta después de que el agente complete una respuesta completa. Aunque activamos el streaming, <code>onFinish</code> seguirá ejecutar luego de recibir el mensaje completo y no luego de cada chunk. Aquí, lo estamos usando para rastrear nuestro uso de tokens. Esto puede ser útil para monitorizar los costos de los LLM y optimizarlos.</li></ul><p>4. Por último, ve al componente <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/frontend/components/ChatUI.jsx">ChatUI.jsx</a> en el directorio <code>frontend/components</code> para crear la interfaz que mantenga nuestra conversación. A continuación, envuelve la respuesta en un componente <code>ReactMarkdown</code> para formatear correctamente la respuesta del agente.</p><pre><code>import React, { useState } from 'react';
import { useChat } from '@ai-sdk/react';
import ReactMarkdown from 'react-markdown';

export default function ChatUI() {
    const [totalTokenUsage, setTotalTokenUsage] = useState(0);
    const [promptTokenUsage, setPromptTokenUsage] = useState(0);
    const [completionTokenUsage, setCompletionTokenUsage] = useState(0);
    const [toolsCalled, setToolsCalled] = useState([]);

    const { messages, input, handleInputChange, handleSubmit, status } = useChat({
        api: 'http://localhost:4111/api/agents/basketballAgent/stream', //Replace with your own endpoint for your agent
        id: 'my-chat-session',

        //Optional parameter to check agent tool calls
        onToolCall: ({ toolCall }) => {
            setToolsCalled((prev) => [...prev, toolCall.toolName]);
        },

        //Optional parameter to check token usages
        onFinish: (message, { usage }) => {
            setTotalTokenUsage((prev) => prev + usage.totalTokens);
            setPromptTokenUsage((prev) => prev + usage.promptTokens);
            setCompletionTokenUsage((prev) => prev + usage.completionTokens);
        },

        //Optional parameter for error handling
        onError: (error) => {
            console.error('Agent error:', error);
        },
    });

    return (
        <div>
            <div className="agent-info">
                <h4 className="stats-title">What's My Agent Doing?</h4>

                <div className="stats-box">
                    <strong className="stats-sub-title">Tools Called:</strong>
                    <ul className="tool-list">
                        {toolsCalled.map((tool, idx) => (
                            <li key={idx}>{tool}</li>
                        ))}
                        {toolsCalled.length === 0 && <li>No tools called yet.</li>}
                    </ul>

                    <div className="usage-stats">
                        <p>Prompt Token Usage: {promptTokenUsage}</p>
                        <p>Completion Token Usage: {completionTokenUsage}</p>
                        <p>Total Token Usage: {totalTokenUsage}</p>
                    </div>
                </div>
            </div>

            <strong>Conversation:</strong>
            <div className="convo-box">
                {messages.map((msg) => (
                    <div key={msg.id} className="message-item">
                        <strong className="message-role">{msg.role === 'assistant' ? 'Basketbot' : 'You'}:</strong>
                        <ReactMarkdown>{msg.content}</ReactMarkdown>
                    </div>
                ))}
            </div>

            <form onSubmit={handleSubmit}>
                <input
                    type="text"
                    value={input}
                    onChange={handleInputChange}
                    placeholder="Input two players you want to compare."
                    className="input-box"
                />
                <button type="submit" disabled={status === 'streaming'}>
                    {status === 'streaming' ? 'Thinking...' : 'Send'}
                </button>
            </form>
        </div>
    );
}</code></pre><h4><strong>Paso 9: Ejecutar la aplicación</strong></h4><p>¡Felicidades! Ahora estás listo para ejecutar la aplicación. Sigue estos pasos para empezar tanto el backend como el frontend.</p><ol><li>En una ventana de terminal, empezando desde el directorio raíz, navega hasta el directorio backend e inicia el servidor Mestra:</li></ol><pre><code>cd backend

npm run dev</code></pre><p>2. En otra ventana de terminal, empezando desde el directorio raíz, navega hasta el directorio frontend e inicia la aplicación React:</p><p></p><pre><code>cd frontend

npm run dev</code></pre><p></p><p>3. Ve a tu navegador y navega a:</p><p></p><p><a href="http://localhost:5173/">http://localhost:5173</a></p><p></p><p>Deberías poder ver la interfaz del chat. Prueba estos ejemplos de prompts:</p><ul><li>&quot;Compara a LeBron James y Stephen Curry&quot;</li><li>&quot;¿A quién debería elegir entre Jayson Tatum y Luka Doncic?&quot;</li></ul><p></p><h3><strong>Qué sigue: Hacer que el agente sea más inteligente</strong></h3><p>Para que el asistente sea más agenciado y las recomendaciones más perspicaces, agregaré algunas mejoras clave en la próxima iteración.</p><p></p><p><strong>Búsqueda semántica de noticias de la NBA</strong></p><p>Hay un montón de factores que pueden afectar al rendimiento de los jugadores, muchos de los cuales no aparecen en las estadísticas brutas. Cosas como reportes de lesiones, cambios en la alineación o incluso análisis postpartido, solo se pueden encontrar en artículos de prensa. Para captar este contexto adicional, agregaré capacidades de búsqueda semántica para que el agente pueda recuperar artículos relevantes de la NBA e incluir esa narrativa en sus recomendaciones.</p><p></p><p><strong>Búsqueda dinámica con el servidor MCP de Elasticsearch</strong></p><p>MCP (Model Context Protocol) se está convirtiendo rápidamente en el estándar para cómo los agentes se conectan a fuentes de datos. Migraré la lógica de búsqueda al servidor Elasticsearch MCP, que permite al agente construir consultas dinámicamente en lugar de depender de funciones de búsqueda predefinidas que proporcionamos. Esto nos permite emplear más flujos de trabajo en lenguaje natural y reduce la necesidad de escribir manualmente cada consulta de búsqueda. Descubre más sobre el servidor MCP de Elasticsearch y el estado actual del <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">ecosistema aquí</a>.</p><p></p><p>Estos cambios ya están en marcha, ¡estad atentos!</p><h3><strong>Conclusión</strong></h3><p></p><p>En este blog, creamos un asistente RAG agente que ofrece recomendaciones personalizadas para tu equipo de baloncesto de fantasía usando JavaScript, Mastra y Elasticsearch. Cubrimos lo siguiente:</p><ul><li><strong>Fundamentos del RAG agente</strong> y cómo combinar la autonomía de un agente de IA con las herramientas para usar RAG de forma eficaz puede conducir a agentes más matizados y dinámicos.</li><li><strong>Elasticsearch </strong>y cómo sus capacidades de almacenamiento de datos y poderosas agregaciones nativas lo convierten en un gran socio como base de conocimiento para un LLM.</li><li><strong>El framework Mastra </strong>y cómo simplifica la construcción de estos agentes para desarrolladores en el ecosistema javaScript.</li></ul><p>Tanto si eres un fanático del baloncesto, explorando cómo crear agentes de IA, o ambos como yo, espero que este blog te dio algunos cimientos para empezar. El repositorio completo está disponible en <a href="https://github.com/jdarmada/nba-ai-assistant-js">GitHub</a>, siéntete libre de clonar y trastear. ¡Ahora, gana esa liga de fantasía!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Guion Ruby en Logstash]]></title>
            <description><![CDATA[Infórmate sobre el plugin de filtro Ruby Logstash para transformar datos avanzados en tu pipeline Logstash.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/ruby-scripting-logstash</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/ruby-scripting-logstash</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Dai Sugimori]]></dc:creator>
            <pubDate>Tue, 24 Jun 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b1bcd63c033ccbde102c20ba3085f165f9289a71-1600x1000.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Logstash es una cadena de procesamiento de datos que ingiere datos de múltiples fuentes, los transforma y los envía a los destinos que elijas. Los plugins de filtro son clave para este proceso; Realizan operaciones específicas sobre tus datos a medida que avanzan en la pipeline.</p><p>Logstash incluye varios filtros integrados para tareas comunes como análisis sintáctico, enriquecimiento y modificación de datos. Pero a veces te encontrarás con escenarios que requieren una lógica personalizada que va más allá de lo que estos filtros estándar pueden ofrecer. Aquí es donde entra el <a href="https://www.elastic.co/docs/reference/logstash/plugins/plugins-filters-ruby">plugin de filtro Ruby</a> .</p><p><strong>El plugin de filtro Ruby te permite ejecutar código Ruby personalizado directamente dentro de tu pipeline de Logstash.</strong> Cuando los filtros estándar no son suficientes, el filtro Ruby te permite manejar transformaciones complejas de datos, implementar lógica de negocio personalizada o integrarte con sistemas externos.</p><p>En este blog, exploraremos cómo usar los filtros Ruby, desde el uso básico hasta el avanzado.</p><h2>¿Cuándo deberías usar el filtro Ruby?</h2><p>Como arquitecto consultor de Elastic, a menudo veo a clientes que emplean Logstash para la cadena de procesamiento de datos, aunque hoy en día no es un motor de procesamiento de datos de última generación. A menudo tienen dificultades con las limitaciones de los filtros estándar cuando se trata de manipulación compleja de datos o lógica personalizada. En estos casos, el filtro Ruby puede ayudar a superar esos desafíos.</p><p>El filtro Ruby es útil cuando los filtros Logstash estándar no pueden cumplir tus requisitos específicos. Aquí tienes algunos casos de uso comunes:</p><ul><li><strong>Manipulación profunda de datos anidados</strong>: Modificar estructuras JSON complejas, arrays dentro de arrays o reestructurar dinámicamente los datos en función del contenido</li><li><strong>Procesamiento avanzado de cadenas</strong>: Analizar y extraer datos estructurados de texto no estructurado</li><li><strong>Implementación de lógica de negocio compleja</strong>: Crear transformaciones personalizadas que requieran lógica condicional, bucles o cálculos complejos</li></ul><h2>Uso básico</h2><p>Empecemos con un ejemplo sencillo para entender cómo funciona el filtro Ruby.</p><h3>Configuración del filtro Ruby</h3><p>Cuando crees una pipeline de Logstash, deberías colocar el archivo de configuración en el directorio <code>/etc/logstash/conf.d</code> . Alternativamente, puedes usar <code>-f</code> opción para especificar la ruta al archivo de configuración cuando arranques Logstash manualmente, para que puedas experimentar fácilmente con tus pipelines.</p><pre><code>$ ./bin/logstash -f /path/to/your_pipeline.conf</code></pre><p>El archivo de configuración debería tener una extensión <code>.conf</code> .</p><p>Para usar el filtro Ruby, define un filtro <code>ruby</code> en la sección de filtros de tu archivo de configuración de la tubería Logstash (*.conf). Aquí tienes un ejemplo básico:</p><pre><code>filter {
  ruby {
    code => "
      event.set('new_field', 'Hello from Ruby!')
    "
  }
}</code></pre><p>Este filtro Ruby en línea define una instancia de filtro Ruby dentro de tu configuración de Logstash. El parámetro <code>code</code> proporciona el script Ruby en línea que Logstash ejecutará para cada evento procesado por este filtro. Dentro de ese script, hay una variable <code>event</code> disponible que representa el propio evento. El objeto evento contiene los datos originales enviados a Logstash y cualquier campo adicional creado durante las etapas de filtro de Logstash. Puedes acceder a esos campos a través de la API de eventos de Logstash como <code>event.get()</code> y <code>event.set()</code>. En este código de ejemplo, <code>event.set(&#x27;new_field&#x27;, &#x27;Hello from Ruby!&#x27;)</code> establecer un nuevo campo llamado <code>new_field</code> al valor de cadena <code>Hello from Ruby!</code>. Puedes agregar cualquier otro código en este bloque de <code>code</code> según lo necesites.</p><p>Ten en cuenta que este objeto <code>event</code> no es un objeto hash de Ruby habitual, aunque actúa como un contenedor de datos clave-valor. Consulta <a href="https://www.elastic.co/docs/reference/logstash/event-api">esta documentación oficial</a> para saber más sobre la API de eventos.</p><h3>Externalizar la escritura Ruby</h3><p>Para transformaciones simples, el código Ruby en línea es cómodo. Pero, para lógica compleja o funciones reutilizables, se recomienda mover el código a un script Ruby externo. Esto mejora la mantenibilidad y mantiene limpia la configuración de tu pipeline de Logstash.</p><p>Primero, crea un script Ruby y almacénalo como <code>my_ruby_script.rb</code>. El script debe definir un método <code>filter</code> que procese el evento. Toma un objeto evento como argumento, que representa el evento actual que se está procesando. El método <code>filter</code> necesita devolver un serial de eventos para emitir. Para eliminar el evento, devuelvo un array vacío.</p><p>Por ejemplo, el siguiente script lee el campo <code>message</code> , calcula su longitud y almacena el resultado en un nuevo campo llamado <code>message_length</code>.</p><pre><code>def register(params)
  # This method is called when the plugin is loaded.
  # You can use it to initialize any instance variables or perform setup tasks.
end

def filter(event)
  message = event.get('message')

  if message
    event.set('message_length', message.length)
  end

  return [event]
end</code></pre><p>A continuación, configura la configuración del filtro Ruby para que haga referencia al script usando la opción <code>path</code> . Esto indica a Logstash que cargue y ejecute el script externo. Al usar scripts externos, cerciórate de que el archivo existe y tiene las licencias correctas.</p><pre><code>filter {
  ruby {
    path => "/path/to/my_ruby_script.rb"
  }
}</code></pre><p>Ahora, cada evento se pasa al método <code>filter</code> en <code>my_ruby_script.rb</code> y es procesado por él.</p><p>Este enfoque te ayuda a gestionar la lógica compleja de forma más eficaz, facilitando probar, depurar y reutilizar tu código Ruby.</p><h2>Uso avanzado</h2><p>En esta sección, exploraremos algunos ejemplos avanzados de cómo usar el filtro Ruby en Logstash. Estos ejemplos demostrarán cómo realizar transformaciones de datos, enriquecer eventos e implementar lógica personalizada usando Ruby.</p><h3>Manipulación de estructuras de datos anidadas</h3><p>Un evento Logstash es la estructura de datos central que procesa Logstash. Puede contener varios campos, incluyendo estructuras de datos anidadas como arrays y hashes. El filtro Ruby te permite manipular fácilmente estas estructuras anidadas.</p><p>El filtro Ruby puede manejar estructuras de datos anidadas, como hashes y arrays, permitiéndote modificar o agregar campos dentro de estas estructuras. Esto es útil cuando se trata de formatos de datos complejos como JSON.</p><pre><code>input {
  generator {
    lines => [
      '{"nested": {"key1": "value1", "key2": "value2"}}'
    ]
    count => 1
    codec => "json"
    ecs_compatibility => "disabled"
  }
}

filter {
  ruby {
    code => "
      nested_data = event.get('nested')

      if nested_data.is_a?(Hash)
        nested_data['key3'] = 'value3'
        event.set('nested', nested_data)
      end
    "
  }
}

output {
  stdout { codec => rubydebug }
}</code></pre><p>Este ejemplo incluye un objeto JSON anidado en los datos de entrada. El filtro Ruby modifica los datos anidados agregando un nuevo par clave-valor. Este tipo de manipulación para datos anidados no es posible con los filtros Logstash estándar, lo que convierte al filtro Ruby en una opción útil para estructuras de datos complejas.</p><h3>Dividir un solo evento en varios eventos</h3><p>Los filtros Ruby también pueden usar para dividir un solo evento en varios eventos. Esto es útil cuando tienes un solo evento que contiene un array de objetos y quieres crear eventos separados para cada uno.</p><p>Ten en cuenta que ni la tubería de ingesta de Elasticsearch ni los procesadores de Beats/Elastic Agent soportan eventos de división. Este es uno de los casos de uso más estables de Logstash.</p><h4>Con filtro dividido</h4><p>Puedes usar el filtro <code>split</code> para dividir un evento en varios eventos según un campo especificado. Sin embargo, si necesitas realizar transformaciones adicionales o lógica durante la división, puedes usar el filtro Ruby en combinación con el filtro dividido.</p><p>En el siguiente ejemplo, tenemos un feed RSS como una sola línea de texto XML. Contiene múltiples elementos <code>&lt;item&gt;</code> . El filtro Ruby se emplea para extraer los <code>&lt;item&gt;</code> elementos del XML y almacenarlos en un nuevo campo llamado <code>items</code>. El filtro dividido se emplea entonces para dividir el evento en varios eventos según el campo <code>items</code> .</p><pre><code>input {
  generator {
    lines => [
      '<rss version="2.0"><channel><title>Sample RSS</title><item><title>Article 1</title><link>http://example.com/1</link><description>Desc 1</description></item><item><title>Article 2</title><link>http://example.com/2</link><description>Desc 2</description></item></channel></rss>'
    ]
    count => 1
    codec => "plain"
    ecs_compatibility => "disabled"
  }
}

filter {
  xml {
    source => "message"
    target => "rss"
    store_xml => true
    force_array => false
  }
  ruby {
    code => "event.set('items', event.get('[rss][channel][item]')) if event.get('[rss][channel][item]')"
  }
  split {
    field => "items"
  }
  ruby {
    code => "
      item = event.get('items')
      event.set('title', item['title']) if item['title']
      event.set('link', item['link']) if item['link']
      event.set('description', item['description']) if item['description']
    "
  }
  mutate {
    remove_field => ["@timestamp", "@version", "sequence", "host", "event", "message", "rss", "items"]
  }
}

output {
  stdout { codec => rubydebug }
}</code></pre><p>Esto dará la siguiente manera:</p><pre><code>{
          "title" => "Article 1",
           "link" => "http://example.com/1",
    "description" => "Desc 1"
}
{
          "title" => "Article 2",
           "link" => "http://example.com/2",
    "description" => "Desc 2"
}</code></pre><p>Como notaste, el filtro <code>ruby</code> no es esencial en este caso. El filtro <code>split</code> puede usar para dividir el evento en varios eventos basados en el campo <code>items</code> , y el filtro <code>mutate</code> puede usar para eliminar campos innecesarios. Sin embargo, si necesitas realizar transformaciones o lógica adicional durante la división, puedes usar el filtro Ruby.</p><h4>Emplea escritura Ruby en línea</h4><p>También puedes usar un script Ruby en línea para dividir un solo evento en varios eventos usando el método <code>event.clone</code> y el <code>new_event_block variable</code>, como <code>new_event_block.call(new_event)</code>. Esto te permite crear nuevos eventos basados en el evento original mientras se conservan sus datos.</p><p>Aquí tienes un ejemplo de cómo usar el filtro Ruby para dividir un solo evento en varios eventos. La entrada y salida son las mismas que en el ejemplo anterior.</p><pre><code>filter {
  xml {
    source => "message"
    target => "rss"
    store_xml => true
    force_array => false
  }
  ruby {
    code => "
      items = event.get('[rss][channel][item]')
      if items.is_a?(Array)
        items.each do |item|
          new_event = event.clone
          new_event.set('title', item['title'])
          new_event.set('link', item['link'])
          new_event.set('description', item['description'])
          new_event_block.call new_event
        end
        event.cancel
      elsif items.is_a?(Hash)
        event.set('title', items['title'])
        event.set('link', items['link'])
        event.set('description', items['description'])
      end
    "
  }
  mutate {
    remove_field => ["@timestamp", "@version", "sequence", "host", "event", "message", "rss", "items"]
  }
}</code></pre><h4>Usa un script Ruby externo</h4><p>También puedes usar un script externo de Ruby para dividir un solo evento en varios eventos.</p><p>Archivo de configuración:</p><pre><code>filter {
  xml {
    source => "message"
    target => "rss"
    store_xml => true
    force_array => false
  }
  ruby {
    path => "path/to/ruby/split_event.rb"
  }
  mutate {
    remove_field => ["@timestamp", "@version", "sequence", "host", "event", "message", "rss", "items"]
  }
}</code></pre><p>El sistema Ruby debe externalizar como <code>split_event.rb</code>:</p><pre><code>def filter(event)
  items = event.get('[rss][channel][item]')
  events = []
  if items.is_a?(Array)
    items.each do |item|
      new_event = event.clone
      new_event.set('title', item['title'])
      new_event.set('link', item['link'])
      new_event.set('description', item['description'])
      events << new_event
    end
    return events
  elsif items.is_a?(Hash)
    event.set('title', items['title'])
    event.set('link', items['link'])
    event.set('description', items['description'])
    return [event]
  else
    return []
  end
end</code></pre><p>Recuerda, el método <code>filter</code> debe devolver un serial de eventos. Puedes devolver varios eventos clonando un objeto de evento entrante y agregándolos al array, o puedes devolver un solo evento como un array con un solo elemento.</p><pre><code>return events
# or
# return [event]</code></pre><p>Esto te permite dividir un solo evento en varios eventos.</p><h3>Ejecuta comandos externos y analiza su salida</h3><p>El plugin de entrada ejecutiva de Logstash permite ejecutar comandos externos y su salida será un evento de Logstash. La salida del comando se almacenará en el campo <code>message</code> del evento.</p><p>Normalmente, la salida de los comandos del sistema es legible por humanos, pero no está estructurada como JSON u otros formatos que Logstash pueda analizar fácilmente. Para gestionarlo, puedes usar el filtro Ruby para analizar la salida y extraer la información de ella.</p><p>Aquí tienes un ejemplo de cómo se emplea el plugin de entrada <code>exec</code> para ejecutar el comando <code>ps -ef</code> , que lista todos los procesos en ejecución en un sistema tipo Unix. La salida será analizada por el filtro Ruby para extraer información relevante sobre cada proceso.</p><pre><code>input {
  exec {
    command => "ps -ef"
    interval => 60
  }
}

filter {
  ruby {
    code => '
      processes = []
      lines = event.get("message").split("\n")  
      lines.each_with_index do |line, index|
        # Skip header line and empty lines
        next if index == 0 || line.strip.empty?
        entry = nil
        
        # Use regex to match the ps -ef output format more flexibly
        # This pattern accounts for variable spacing and different time formats
        if line =~ /^\s*(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+([\d:]+\.?\d*)\s+(.+)$/
          uid, pid, ppid, c, stime, tty, time, cmd = $1, $2, $3, $4, $5, $6, $7, $8
          
          entry = {
            "UID" => uid,
            "PID" => pid,
            "PPID" => ppid,
            "C" => c,
            "STIME" => stime,
            "TTY" => tty,
            "TIME" => time,
            "CMD" => cmd.strip
          }
        elsif line =~ /^\s*(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(.+)$/
          # Fallback pattern for lines that might not match the exact format
          # Split the remaining part more carefully
          uid, pid, ppid, c, remainder = $1, $2, $3, $4, $5
          
          # Split remainder into STIME, TTY, TIME, CMD
          parts = remainder.strip.split(/\s+/, 4)
          if parts.length >= 4
            stime, tty, time, cmd = parts[0], parts[1], parts[2], parts[3]
            
            entry = {
              "UID" => uid,
              "PID" => pid,
              "PPID" => ppid,
              "C" => c,
              "STIME" => stime,
              "TTY" => tty,
              "TIME" => time,
              "CMD" => cmd
            }
          end
        end
        if entry && entry["UID"] == "0"
          original_line = line.strip
          entry["original_line"] = original_line if original_line.length > 0
          processes.push(entry)
        end
      end
      event.set("processes", processes)
      event.remove("message")
      event.remove("event")
    '
  }
}

output {
  stdout { codec => rubydebug }
}</code></pre><p>Este ejemplo emplea el plugin de entrada <code>exec</code> para ejecutar el comando <code>ps -ef</code> cada 60 segundos. El filtro Ruby procesa la salida, extrayendo campos relevantes como UID, PID, PPID, uso de la CPU (C), hora de inicio (STIME), TTY, tiempo total de CPU (TIME) y el comando (CMD) ejecutado. Funciona bien en mi entorno macOS, pero puede que tengas que ajustar los patrones regex para que coincidan con el formato de salida del comando <code>ps -ef</code> en tu sistema.</p><h3>Emplea librerías integradas</h3><p>El plugin de filtro Ruby permite usar librerías Ruby integradas, que pueden ser muy útiles para diversas tareas. Por ejemplo, puedes usar la librería <code>json</code> para analizar cadenas JSON o la librería <code>date</code> para manipular fechas.</p><p>Aquí tienes un ejemplo de cómo usar la librería <code>json</code> para analizar una cadena JSON almacenada en un campo:</p><pre><code>require 'json'

def filter(event)
  json_string = event.get('message')
  parsed_json = JSON.parse(json_string)
  event.set('parsed_json', parsed_json)
  return [event]
end</code></pre><p>Para evitar tener que usar la librería cada vez, deberías externalizar tu código Ruby para poder usar la sentencia <code>require</code> al principio de tu script de filtro Ruby. Esto cargará la biblioteca una vez y la pondrá disponible para tu script.</p><p>Para comprobar qué librerías están disponibles en tu entorno, puedes listar las bibliotecas integradas ejecutando el siguiente código en el filtro Ruby:</p><pre><code>Gem.loaded_specs.sort_by { |name, _| name }.each do |name, spec|
  puts "#{name}: #{spec.version}"
end</code></pre><p><strong>Nota: </strong>Las bibliotecas integradas no son soportadas oficialmente por Logstash, y su comportamiento puede cambiar o puede que no estén disponibles en versiones futuras. Úsalos bajo tu propia responsabilidad.</p><h2>Conclusión</h2><p>El filtro Ruby de Logstash te permite personalizar y ampliar las capacidades de tus pipelines de Logstash. En esta publicación, cubrimos lo básico del uso del filtro Ruby y proporcionado ejemplos avanzados de uso.</p><p>Aprovechando el filtro Ruby, puedes manejar tareas complejas de procesamiento de datos que requieren lógica personalizada o manipulación avanzada. Ya sea que trabajes con estructuras de datos anidadas, divisiones de eventos o analizes y conviertas textos complejos/no estructurados en JSON estructurado, el filtro Ruby ofrece flexibilidad para satisfacer tus necesidades específicas.</p><p>Esperamos que esta guía te proporcionó el conocimiento e inspiración para explorar todo el potencial del filtro Ruby de Logstash. ¡Feliz guion!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cómo desplegar Elasticsearch en GCP GKE Autopilot]]></title>
            <description><![CDATA[Aprende cómo desplegar un clúster de Elasticsearch en GCP usando GKE Autopilot y ECK para una configuración parcialmente gestionada de Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/eck-gke-autopilot</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/eck-gke-autopilot</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Eduard Martin]]></dc:creator>
            <pubDate>Thu, 19 Jun 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d9c462fe63011356671479ccfedd435eec1ede52-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>En este artículo, vamos a aprender cómo desplegar Elasticsearch en Google Cloud Kubernetes (GKE) usando Autopilot.</p><p>Para Elasticsearch, vamos a emplear <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s">Elastic Cloud on Kubernetes </a>(ECK), que es el operador oficial de Elasticsearch Kubernetes que simplifica la orquestación de despliegues de Kubernetes de todos los componentes de Elastic Stack.</p><p>Para obtener más información sobre cómo desplegar clústeres de Elasticsearch en diferentes infraestructuras de GCP, puedes leer nuestros artículos de introducción sobre <a href="https://www.elastic.co/search-labs/blog/elasticsearch-gpc-google-compute-engine">Google Cloud Compute</a> y <a href="https://www.elastic.co/search-labs/blog/deploy-elastic-gcp-marketplace">Google Cloud Marketplace</a>.</p><h2>Esfuerzo de despliegue de Elasticsearch</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/56b54d718dcff9af9050873c41fdf738074851da-1428x582.png" style="width: 100%;" /><h3>¿Qué es GKE Autopilot?</h3><p><a href="https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-overview?hl=es-419"><strong>Google Kubernetes Engine (GKE) Autopilot</strong></a> ofrece una experiencia Kubernetes totalmente gestionada donde Google se encarga de la configuración del clúster, la gestión de nodos, la seguridad y el escalado, mientras los desarrolladores se centran en desplegar aplicaciones, lo que les permite a los equipos pasar del código a la producción en minutos con las mejores prácticas integradas.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bbf6de63fa0a199326352f521cb22654818799f6-1600x958.png" style="width: 100%;" /><h2>¿Cuándo usar el ECK en Google Cloud?</h2><p>Elastic Cloud on Kubernetes (ECK) es el más adecuado para organizaciones con infraestructura existente de Kubernetes que buscan desplegar Elasticsearch con características avanzadas como roles de nodo dedicados, alta disponibilidad y automatización.</p><h2>¿Cómo configurar ECK en Google Cloud?</h2><p>1. Inicia sesión en <a href="https://console.cloud.google.com">Google Cloud Console</a>.</p><p>2. En la <strong>parte superior derecha </strong>haz clic<strong> </strong>en el botón <strong>Cloud Shell</strong> para acceder a la consola y desplegar el clúster de GKE desde allí. Alternativamente, puedes usar la <a href="https://cloud.google.com/cli">CLI de gcloud</a>.</p><p><strong><em>Recuerda actualizar el identificador del proyecto con el tuyo durante el tutorial.</em></strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/999b03861d4fe44f360ab4c7e2616e1dc10cf182-1558x1248.png" style="width: 100%;" /><p>3. Habilita la <a href="https://console.cloud.google.com/flows/enableapi?apiid=container.googleapis.com">API de Google Kubernetes Engine</a>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/03827d3dc0e987c019e7747d33e7c01920047beb-911x246.png" style="width: 100%;" /><p>Haz <strong><em>clic en Siguiente.</em></strong></p><p>Ahora, la API de Kubernetes Engine debería mostrarse habilitada al buscar la API de Kubernetes Engine.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5cd46f0333086bcb31b80cf9c08a469b449ec0f-640x250.png" style="width: 100%;" /><p>4. En Cloud Shell, crea un clúster de Autopilot. Lo llamaremos autopilot-cluster-1, y también reemplazaremos autopilot-test por el id de tu proyecto.</p><pre><code>gcloud beta container --project "autopilot-test-457216" clusters create-auto "autopilot-cluster-1" --region "us-central1" --release-channel "regular" --tier "standard" --enable-ip-access --no-enable-google-cloud-access --network "projects/autopilot-test-457216/global/networks/default" --subnetwork "projects/autopilot-test-457216/regions/us-central1/subnetworks/default" --cluster-ipv4-cidr "/17" --binauthz-evaluation-mode=DISABLED</code></pre><p>5. Espera hasta que esté listo. Tarda unos 10 minutos en crearse.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/81fbc45ba56d0f16ba42724cb8ae45e60b327dbc-1581x258.png" style="width: 100%;" /><p>Aparecerá un mensaje de confirmación después de configurar correctamente el clúster.</p><p>6. Configura el acceso a la línea de comandos de kubectl.</p><pre><code>gcloud container clusters get-credentials autopilot-cluster-1 --region us-central1 --project autopilot-test-457216</code></pre><p>Deberías ver:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b0beba1ee00ce9029f586ee32693fc2aa58c7f65-3442x142.png" style="width: 100%;" /><p><em>Entrada de kubeconfig generada para autopilot-cluster-1.</em></p><p>7. Instala el operador <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s">Elastic Cloud on Kubernetes</a> (ECK).</p><pre><code># Install ECK Custom Resource Definitions
kubectl create -f https://download.elastic.co/downloads/eck/2.16.1/crds.yaml

# Install the ECK operator
kubectl apply -f https://download.elastic.co/downloads/eck/2.16.1/operator.yaml</code></pre><p>8. Creemos una instancia de Elasticsearch de un solo nodo con los valores por defecto.</p><p>Si deseas consultar algunas recetas para diferentes configuraciones, puedes visitar <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/recipes">este enlace</a>.</p><p>Ten en cuenta que si no especificas un <code>storageClass</code>, ECK usará el que se configura por defecto, que para GKE es <code>standard-rwo</code> y usa el <a href="https://cloud.google.com/kubernetes-engine/docs/how-to/persistent-volumes/gce-pd-csi-driver?cloudshell=true">controlador CSI de disco persistente de Compute Engine</a>, y creará un volumen de 1GB con él.</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 9.0.0
  nodeSets:
  - name: default
    count: 1
    config:
      node.store.allow_mmap: false
EOF</code></pre><p>Deshabilitamos <code>nmap</code> porque la máquina GKE predeterminada tiene un valor <code>vm.max_map_count</code> demasiado bajo. No se recomienda desactivarlo para la producción, pero sí aumentar el valor de <code>vm.max_map_count</code>. Puedes leer más sobre cómo hacerlo <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/virtual-memory">aquí</a>.</p><p>9. También despleguemos un clúster de un solo nodo Kibana. Para Kibana, agregaremos un LoadBalancer que nos dará una IP externa que podemos usar para acceder a Kibana desde nuestro dispositivo.</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: quickstart
spec:
  version: 9.0.0
  http:
    service:
      metadata:
        annotations:
          cloud.google.com/l4-rbs: "enabled"
      spec:
        type: LoadBalancer
  count: 1
  elasticsearchRef:
    name: quickstart
EOF</code></pre><p>Nota la anotación: </p><p><code>cloud.google.com/l4-rbs: &quot;enabled&quot;</code></p><p><strong><em>Es muy importante porque le indica a Autopilot que proporcione un LoadBalancer orientado al público. Si no se configura, el LoadBalancer será interno.</em></strong></p><p>10. Verifica que tus pods estén en ejecución</p><pre><code>kubectl get pods</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/99da1270581a137683770efdb9c6e1577ec9fc01-3150x442.png" style="width: 100%;" /><p>11. También puedes <code>run kubectl get elasticsearch</code> y <code>kubectl get kibana</code> para obtener estadísticas más específicas como la versión de Elasticsearch, nodos y salud.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/86234f307970fd5f78b8acd41496e8cc89ff82d3-3414x326.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/29160286ccc88928734c8ea11b1923db8e85d49d-3142x318.png" style="width: 100%;" /><p>12. Accede a tus servicios.</p><pre><code>kubectl get svc</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ed1a07314b84a99b4aa1fec3db4b9badeb9587ee-3446x610.png" style="width: 100%;" /><p>Esto te mostrará la URL externa de Kibana bajo EXTERNAL-IP. Puede que el LoadBalancer tarde unos minutos en provisionar. <strong><em>Copia el valor de EXTERNAL-IP.</em></strong></p><p>13 Obtén la contraseña de Elasticsearch para el usuario ‘elastic’:</p><pre><code>kubectl get secret quickstart-es-elastic-user -o=jsonpath='{.data.elastic}' | base64 --decode</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ab5054219216ebc15fc0d96e27605aaf13b720c6-3448x210.png" style="width: 100%;" /><p>14. <strong>Accede a Kibana</strong> desde tu navegador:</p><ul><li>URL:&lt;EXTERNAL_IP&gt; https://:5601</li><li>Nombre de usuario: elastic</li><li>Contraseña: 28Pao50lr2GpyguX470L2uj5 (del paso anterior)</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/47cbe88dc14db64db3a256f3f7504cc86a843475-463x503.png" style="width: 100%;" /><p>15. Al acceder desde tu navegador, verás la pantalla de bienvenida.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a75c151c0144b7efe2b730698c0ed0156fa9b16a-1600x1005.png" style="width: 100%;" /><p>Si quieres cambiar las especificaciones del clúster de Elasticsearch, como cambiar o redimensionar nodos, puedes volver a aplicar el manifesto yml con los nuevos ajustes:</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 9.0.0
  nodeSets:
    - name: default
      count: 2
      config:
        node.store.allow_mmap: false
      podTemplate:
        spec:
          containers:
            - name: elasticsearch
              resources:
                requests:
                  memory: 1.5Gi
                  cpu: 2
                limits:
                  memory: 1.5Gi
                  cpu: 2
EOF</code></pre><p>En este ejemplo, vamos a agregar un nodo más y modificar la RAM y la CPU. Como puedes ver, ahora <code>kubectl get elasticsearch</code> muestra 2 nodos:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/48b475622cc48890bff8105d151f2cbde28d7021-3418x298.png" style="width: 100%;" /><p>Lo mismo aplica para Kibana:</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: quickstart
spec:
  version: 9.0.0
  http:
    service:
      metadata:
        annotations:
          cloud.google.com/l4-rbs: "enabled"
      spec:
        type: LoadBalancer
  count: 1
  elasticsearchRef:
    name: quickstart
  podTemplate:
    spec:
      containers:
        - name: kibana
          env:
            - name: NODE_OPTIONS
              value: "--max-old-space-size=1024"
          resources:
            requests:
              memory: 0.5Gi
              cpu: 0.5
            limits:
              memory: 1Gi
              cpu: 1
EOF</code></pre><p>Podemos ajustar la CPU/RAM del contenedor y también el uso de memoria <a href="https://nodejs.org/">de Node.js </a>(<a href="https://nodejs.org/api/cli.html#--max-old-space-sizesize-in-mib">max-old-space-size</a>).</p><p>Ten en cuenta que <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/volume-claim-templates">las solicitudes de volumen existentes no pueden reducirse</a>. Después de aplicar la actualización, el operador realizará los cambios con el mínimo tiempo de interrupción.</p><p>Recuerda eliminar el clúster cuando termines de probar para evitar costos innecesarios.</p><pre><code>gcloud container clusters delete autopilot-cluster-1</code></pre><h2>Pasos siguientes</h2><p>Si quieres saber más sobre Kubernetes y el motor de Google Kubernetes, consulta estos artículos:</p><ul><li><a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s">Elastic Cloud en Kubernetes | Elastic Docs</a></li><li><a href="https://cloud.google.com/blog/products/containers-kubernetes/introducing-gke-autopilot">Presentamos GKE Autopilot | Blog de Google Cloud</a></li><li><a href="https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-overview">Visión general del piloto automático | Google Kubernetes Engine (GKE)</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Uso de Azure LLM Functions con Elasticsearch para experiencias de consulta más inteligentes]]></title>
            <description><![CDATA[Explora un ejemplo de aplicación de búsqueda inmobiliaria que emplea Azure Gen AI LLM Functions con Elasticsearch para ofrecer resultados híbridos flexibles. Consulta paso a paso cómo configurar y ejecutar la aplicación de ejemplo en GitHub Codespaces.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/azure-llm-functions-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/azure-llm-functions-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jonathan Simon]]></dc:creator>
            <pubDate>Fri, 13 Jun 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1431b90c7e00de06574c1e33c44a2e89296c824e-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Precisión. Cuando importa, importa mucho. Cuando buscas algo específico, la precisión es muy importante. Sin embargo, a veces una consulta demasiado precisa no devuelve resultados, por lo que es ventajoso tener la flexibilidad de ampliar el ámbito de una consulta para encontrar datos adicionales potencialmente relevantes.</p><p>En esta entrada de blog se explica el uso de Elasticsearch y Azure Open AI para crear una aplicación de ejemplo que demuestre cómo encontrar resultados exactos al buscar propiedades inmobiliarias muy específicas y, al mismo tiempo, proporcionar resultados relevantes cuando una coincidencia específica no está disponible. Te guiaremos por todos los pasos necesarios para crear un índice de Elasticsearch junto con una plantilla de búsqueda. Luego, expondremos todo el proceso de cómo crear una aplicación que use Azure OpenAI para tomar consultas de usuarios y transformarlas en consultas de plantillas de búsqueda de Elasticsearch que pueden producir resultados increíblemente personalizados.</p><p>Aquí hay una lista de todos los recursos que usaremos para crear nuestra aplicación de búsqueda de bienes raíces de ejemplo:</p><ul><li>Índice y plantilla de búsqueda de Elasticsearch</li><li>Azure OpenAI</li><li>Azure Maps API</li><li><a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb">Codespaces Jupyter Notebook</a></li><li>Núcleo semántico</li><li>Aplicación de C# con front-end de Blazor</li></ul><h2>Flujo de trabajo de consulta inteligente</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0c4a7c835e06c514f158c00ab1055a7ba719a35f-1600x765.png" style="width: 100%;" /><p>Este flujo de trabajo combina LLM, herramientas de LLM y búsqueda para transformar las consultas en lenguaje natural en resultados de búsqueda estructurados y relevantes:</p><ul><li><strong>LLM (Large Language Model):</strong> interpreta consultas complejas de usuarios y organiza herramientas para extraer la intención de búsqueda y enriquecer el contexto.</li><li><strong>Herramientas LLM</strong> : cada herramienta LLM es un programa de C # que creamos para esta publicación. Hay tres herramientas:<ul><li><em>Herramienta de extracción de parámetros</em>: extrae atributos clave como dormitorios, baños, características y precio de la consulta.</li><li><em>Herramienta GeoCode</em>: convierte los nombres de ubicación en latitud/longitud para el filtrado espacial.</li><li><em>Herramienta de búsqueda</em>: completa una plantilla de búsqueda de Elasticsearch con parámetros de consulta y ejecuta una búsqueda. <strong>Búsqueda híbrida</strong> : ejecuta la búsqueda híbrida (texto completo + vector denso) con inferencia de ML integrada. Este enfoque por capas garantiza experiencias de consulta más inteligentes y sensibles al contexto para el usuario final.</li></ul></li></ul><h2>Arquitectura de la aplicación</h2><p>Este es un diagrama de arquitectura del sistema de la aplicación de ejemplo. Usaremos un bloc de notas de Codespaces Jupyter para interactuar con Elastic Cloud.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2880afee184cd9270c0eb4310e51418e2339784d-936x452.png" style="width: 100%;" /><h2>Prerrequisitos</h2><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><h2>Clonación de repositorio de aplicación de ejemplo en Codespaces</h2><p>Comience clonando el código de la aplicación de ejemplo. Puede hacerlo en <a href="https://github.com/codespaces/">GitHub Codespaces</a>, que proporciona una manera de clonar y ejecutar aplicaciones. Haga clic en <strong>Nuevo codespace.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b89cbec491659b6c8a0bb9551ed2629f7a37f9fd-1600x427.png" style="width: 100%;" /><p>A continuación, seleccione el repositorio <a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo">jwilliams-elastic/msbuild-intelligent-query-demo</a> en la lista desplegable <strong>Repositorio</strong> y haga clic en <strong>Crear codespace</strong>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/43ea377554527af9578400f16cd2342bf8fff3a2-1600x1049.png" style="width: 100%;" /><h2>Crear .env archivo</h2><p>Usaremos un cuaderno de Python Jupyter para acceder e interactuar con Elastic Cloud y lo hace empleando los valores de configuración que se almacenan en un archivo de configuración. El archivo de configuración del Notebook debe tener el nombre de archivo <strong><em>.env</em></strong> y lo crearás ahora.</p><ol><li>En GitHub Codespaces, haga clic en el botón <strong>Nuevo archivo </strong>y agregue un archivo denominado <strong><em>.env</em></strong></li><li>Agregue el siguiente contenido al <strong><em>.env</em></strong> recién creado archivo</li></ol><pre><code>ELASTIC_URL=
ELASTIC_API_KEY=</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/84d4f327948858ba61db0001dd8cf780d42fe0a7-1600x875.gif" style="width: 100%;" /><p>Como puede ver, tenemos un par de valores faltantes, <strong>ELASTIC_URL</strong> y <strong>ELASTIC_API_KEY,</strong> que deben agregar al <em>.env</em> archivo. Obtengámoslos ahora creando un proyecto sin servidor de Elasticsearch para que sirva como backend que impulsará la funcionalidad de búsqueda para nuestra aplicación de ejemplo.</p><h2>Crear un proyecto de Elastic Serverless</h2><ol><li>Vaya a <a href="http://cloud.elastic.co">cloud.elastic.co</a> y haga clic en <strong>Crear nuevo proyecto sin servidor</strong></li><li>Haz clic en <strong>Siguiente </strong>para la solución <strong>de Elasticsearch</strong></li><li>Seleccione <strong>Optimizado para vectores</strong></li><li>Establezca el <strong>proveedor de nube</strong> para que sea <strong>Azure</strong></li><li>Haga clic en <strong>Crear proyecto sin servidor</strong></li><li>Haga clic en <strong>Primeros pasos</strong> en el menú de navegación principal y desplazar hacia abajo para copiar los <strong>detalles de la conexión</strong></li><li>Haga clic en el botón <strong>Copiar </strong>para copiar el <strong>endpoint de Elasticsearch</strong> desde los detalles de la <strong>conexión</strong></li><li>Actualizar <strong><em>.env</em></strong> para establecer el <strong>ELASTIC_URL</strong> para que sea el <strong>endpoint de Elasticsearch</strong>copiado</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/77a35e55446d396066b68cfd132d1543a07b81cc-1600x875.gif" style="width: 100%;" /><h2>Crear una clave de API elástica</h2><ol><li>Abra la página <strong>Introducción a</strong> Elasticsearch, haga clic en <strong>Nuevo</strong> en la sección <strong>Agregar una clave de API</strong></li><li>Ingrese un <strong>nombre</strong>de clave</li><li>Haga clic en<strong> Crear clave de API</strong></li><li>Haga clic en el botón Copiar para copiar el valor de la clave de API</li><li>De vuelta en <strong>Codespaces,</strong> donde tenemos el <strong><em>.env</em> </strong>archivo abierto para editar, pegue el valor copiado para establecer el<strong> ELASTIC_API_KEY</strong></li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/20168cba493d8e2c0d9ae7704eb0ae707df58e4c-1600x875.gif" style="width: 100%;" /><h2>Abra el cuaderno de Codespaces e instale las dependencias de la biblioteca</h2><p>En el explorador de archivos, seleccione el archivo <strong><em>VectorDBSetup.ipynb</em></strong> para abrir el cuaderno. Después de que se cargue el Notebook, busque la sección del Notebook <a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L40-L52">titulada <strong>Instalar bibliotecas</strong></a><strong>. </strong>Haga clic en el botón de reproducción de la sección.</p><p>Si es la primera vez que ejecuta un cuaderno en GitHub Codespaces, se le pedirá que seleccione un kernel de Codespaces y configure el entorno de Python.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2272f70615dfb9dcbeb91f39b6dd5076213e24a5-1600x875.gif" style="width: 100%;" /><h2>Definición de importaciones y carga de variables de entorno mediante Codespaces Notebook</h2><p>Vaya a la siguiente sección del Notebook <a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L58-L104">titulada <strong>Definir importaciones y cargar variables de entorno</strong></a>. Haga clic en el botón de reproducción de la sección.</p><p>Este código importa las bibliotecas de Python usadas por el Notebook y carga las variables de entorno del <em>archivo .env </em>que creaste anteriormente.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/aa9f9198ff452ac0c4ce33b00f253731dbee22c5-1600x875.gif" style="width: 100%;" /><h2>Creación de un punto de enlace de inferencia de Elastic ML mediante Codespaces Notebook</h2><p>Vaya a la siguiente sección del cuaderno <a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L112-L157">titulada <strong>Crear punto de conexión de inferencia de ML</strong></a>. Haga clic en el botón de reproducción de la sección.</p><p>Esto creará un nuevo punto final de inferencia de ML en el proyecto de Elasticsearch que usaremos para generar incrustaciones de texto a partir de nuestros datos. Las incrustaciones de texto son representaciones vectoriales de texto que se almacenarán en Elasticsearch para potenciar la búsqueda semántica.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/97c1afab3e64027ee5ae77f377d56ba406ae1765-1600x875.gif" style="width: 100%;" /><h2>Creación de un índice de Elasticsearch con Codespaces Notebook</h2><p>Ve a la siguiente sección de Notebook <a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L165-L224">titulada <strong>Crear índice de Elasticsearch</strong></a>. Haga clic en el botón de reproducción de la sección.</p><p>Esto creará el índice de Elasticsearch que almacenará nuestros datos de ejemplo y cualquier dato vectorial asociado generado a través del punto de enlace de inferencia de ML.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/23a66283ee41239e24fb8455c3cd95641982ca6b-1600x875.gif" style="width: 100%;" /><h2>Crear una plantilla de búsqueda de Elasticsearch con Codespaces Notebook</h2><p>Continúe con la siguiente sección de Notebook <a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L232-L384">titulada <strong>Plantilla de búsqueda</strong></a>. Haga clic en el botón de reproducción de la sección.</p><p>Esto crea una <a href="https://www.elastic.co/es/docs/solutions/search/search-templates">plantilla de búsqueda</a>, que es lo que nuestra aplicación de ejemplo usará como plantilla rellenada con las palabras analizadas de la consulta de búsqueda de un usuario. Esto nos permite configurar y controlar qué tan específicos somos al consultar datos en el índice de Elasticsearch.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4c001a3e4d1cca4cfb5c043fea92c7ccaf9cb64a-1600x875.gif" style="width: 100%;" /><h2>Ingesta de datos en el índice de Elasticsearch mediante Codespaces Notebook</h2><p>Vaya a la siguiente sección del Notebook <a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L392-L450">titulada <strong>Ingesta de datos de propiedades</strong></a>. Haga clic en el botón de ejecución de sección.</p><p>Al ejecutar esta sección de código, se cargarán de forma masiva los datos de ejemplo contenidos en el archivo <em>properties.jsonl </em>. Luego de un par de minutos, debería ver una confirmación de que el proceso se completó correctamente. Puedes confirmar que el índice contiene el registro esperado yendo a la sección <strong>Gestión de índices</strong> en Elastic Cloud.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e9698c798541ccfc08143a939846597028e3c566-1600x875.gif" style="width: 100%;" /><h2>Creación de appsetting.json para configurar la aplicación de C#</h2><p>Con nuestro índice de Elasticsearch creado y poblado con datos, ahora estamos listos para configurar la aplicación de ejemplo para que funcione con Elastic y Azure Cloud. La aplicación de ejemplo de C# usa un archivo denominado <em>appsettings.json</em> para almacenar y cargar su información de acceso, como claves API. Creará el archivo <em>appsettings.json</em> ahora usando el editor en Codespaces.</p><p>1. Cree <em>appsettings.json</em> en la carpeta <strong>HomeFinderApp </strong></p><p>2. Pegue el siguiente código en el archivo <em>appsettings.json</em></p><pre><code>{
 "ElasticSettings": {
   "Url": "",
   "ApiKey": "",
   "IndexName": "properties",
   "TemplateId": "properties-search-template"
 },
 "AzureOpenAISettings": {
   "Endpoint": "",
   "ApiKey": "",
   "DeploymentName": "gpt-4o"
 },
 "AzureMapsSettings": {
   "Url": "https://atlas.microsoft.com/geocode",
   "ApiKey": ""
 },
 "Logging": {
   "LogLevel": {
 	"Default": "Information",
 	"Microsoft.AspNetCore": "Warning"
   }
 },
 "AllowedHosts": "*"
}
</code></pre><p>3. Busca los valores <strong>de Url</strong> y <strong>ApiKey</strong> en la sección <strong>ElasticSettings </strong>. Establézcalos para que sean los mismos valores que estableció en el <em>archivo .env</em> en un paso anterior.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/00dad25bacdea2adcbd1e6eca7658867a49b0d8c-1600x875.gif" style="width: 100%;" /><h2>Creación del servicio Azure OpenAI</h2><p>Nuestra aplicación de ejemplo usará Azure OpenAI para analizar la consulta del usuario de la aplicación y, a continuación, enviará una solicitud a Elasticsearch rellenando la plantilla de búsqueda para intentar comunicar de forma flexible lo que busca el usuario.</p><ol><li>Abra una nueva pestaña del navegador y vaya a <a href="https://portal.azure.com/#blade/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/OpenAI">AI Foundry | Azure OpenAI</a> en Azure Portal. Haga clic en +<strong>Crear</strong></li><li>En el formulario de creación, elija un <strong>grupo de recursos</strong>.</li><li>Ingrese un <strong>nombre</strong></li><li>Elija un <strong>plan de tarifa</strong></li><li>Haga clic en <strong>Siguiente</strong></li><li>En la pestaña <strong>Red</strong> , haga clic en<strong> Siguiente</strong></li><li>En la pestaña <strong>Etiquetas</strong> , haga clic en <strong>Siguiente</strong></li><li>En la pestaña <strong>Revisar y enviar</strong> , haga clic en <strong>Crear</strong></li><li>Una vez completada la creación, haga clic en <strong>Ir al recurso</strong></li><li>Seleccione <strong>Claves y punto de conexión</strong> en el menú de navegación izquierdo</li><li>Copie el <strong>punto de conexión</strong> y péguelo en el archivo <em>appsettings.json</em> que creó en la pestaña del navegador que tiene abierto el editor de Codespaces.</li><li>A continuación, vuelva a la pestaña del explorador que contiene la clave de Azure OpenAI <strong>y la página del punto de conexión</strong>. Haga clic en el botón de copia de la <strong>clave 1</strong> y pegue el valor copiado en el archivo <em>appsettings.json</em> , de nuevo en la pestaña del navegador que tiene abierto el editor de Codespaces.</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6d49197302d110410dca0a53b6ae90237cf2dfd6-1600x875.gif" style="width: 100%;" /><h2>Adición de la implementación del modelo gpt-4o al servicio Azure Open AI</h2><p>Genial, ahora tenemos un servicio Azure OpenAI en ejecución, pero aún necesita una implementación de modelo para brindarnos las funcionalidades de LLM que requiere nuestra aplicación de ejemplo. Hay un montón de modelos para elegir. Implementemos <em>gpt-4o</em> ya que ya está especificado en el archivo <em>appsettings.json</em> que creó.</p><p></p><ol><li>Vaya a <a href="https://ai.azure.com/resource/playground">Azure AI Foundry</a> y haga clic en <strong>Crear una implementación</strong></li><li>Busque <em>gpt-4o</em> y selecciónelo de los resultados</li><li>Haga clic en <strong>Confirmar</strong> para seleccionarlo</li><li>Haga clic en <strong>Implementar</strong> para implementar el modelo</li></ol><p>Una vez que implementó correctamente el modelo <em>gpt-4o, puede</em> <strong>seleccionar Implementaciones</strong> en el menú de navegación izquierdo y confirmar que la implementación <strong><em>de gpt-4o</em></strong> aparece con un <strong>estado</strong> <strong></strong>correcto.<br/></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1b817ab67c05634e9c72777593b4d1a2c6c28191-1600x875.gif" style="width: 100%;" /><h2>Creación de una cuenta de Azure Maps</h2><p>Queremos que los usuarios de nuestra aplicación de ejemplo puedan buscar propiedades inmobiliarias en áreas específicas, pero sin tener que ser demasiado específicos. Si alguien quiere buscar una propiedad cerca del mercado de agricultores local, Azure Maps es un servicio que OpenAI LLM puede usar para obtener coordenadas de latitud y longitud para el mercado. Luego, las coordenadas se pueden incluir en las solicitudes basadas en plantillas de búsqueda enviadas a Elasticsearch para consultas de usuarios que incluyen ubicaciones específicas y consideraciones de distancia geográfica.</p><ol><li>Haga clic en <strong>Crear</strong> en <a href="https://portal.azure.com/#browse/Microsoft.Maps%2Faccounts">cuentas de Azure Maps</a></li><li>Seleccionar un <strong>grupo de recursos</strong></li><li>Ingrese un <strong>nombre</strong></li><li>Aceptar la declaración de licencia y privacidad</li><li>Haz clic en <strong>Revisar y crear</strong></li><li>Haga clic en <strong>Crear</strong></li><li>Una vez completada la creación de la cuenta, haga clic en <strong>Ir al recurso</strong></li><li>Haga clic en <strong>Autenticación</strong> en el menú de navegación izquierdo</li><li>Copie el valor de la <strong>clave principal</strong> y péguelo para que sea el valor de <strong>ApiKey</strong> en la sección <strong>AzureMapsSettings</strong> del archivo <em>appsettings.json</em> , de nuevo en la pestaña del explorador que contiene el editor de Codespaces</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/90fadd48e366682e2bad91e32988f93c6354e126-1600x875.gif" style="width: 100%;" /><h2>Prueba la aplicación de ejemplo</h2><p>Ahora viene la parte divertida. Es hora de ejecutar la aplicación de ejemplo. Tenemos todos los detalles de configuración en su lugar junto con los recursos de Elastic Cloud y Azure Cloud que necesitamos para impulsar la aplicación.</p><p>1. Abra una ventana de Terminal en el editor de Codespaces.</p><p>2. Emplee el siguiente comando para cambiar el directorio activo para que sea la carpeta de la aplicación de ejemplo.<br/></p><pre><code>cd HomeFinderApp</code></pre><p>3. Use el siguiente comando <em>dotnet</em> para ejecutar la aplicación.</p><pre><code>dotnet run</code></pre><p>4. Haga clic en el botón <strong>Abrir en el navegador </strong>cuando aparezca.</p><p>5. Pruebe la búsqueda predeterminada y luego pruebe algunas de sus propias búsquedas personalizadas. Si desea ver más detalles sobre lo que se realiza en el backend para generar los resultados de búsqueda, puede hacer clic en el enlace <strong>Mostrar </strong>junto a <strong>Invocaciones de herramientas.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/821fe7b9446de5ed646d938cc9484a7ddad21030-1600x875.gif" style="width: 100%;" /><p><strong>Sobresueldo: </strong>Si realmente desea probar GPT-4o, intente la siguiente búsqueda: <em>Estoy buscando una propiedad cerca de disney world fl con 30+ habitaciones y 20+ baños y una piscina y garaje y cerca de la playa por menos de 200K. </em>Esta consulta devolverá resultados luego de varias invocaciones de herramientas de búsqueda.</p><h2>Elastic es tu solución para la IA de búsqueda</h2><p>La aplicación en ejecución es un ejemplo de búsqueda guiada de Gen AI LLM empleando Elasticsearch a través de plantillas de búsqueda como fuente de datos de base. Sentir libre de experimentar y personalizar la aplicación de ejemplo para crear una experiencia de búsqueda que sea precisa pero flexible, para ayudar a sus usuarios a encontrar lo que buscan.</p><p>Gracias por leer. Prueba <a href="https://cloud.elastic.co/registration">Elastic Cloud</a> .</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[El estado actual de MCP (Model Context Protocol)]]></title>
            <description><![CDATA[Obtén información sobre MCP, actualizaciones de proyectos, características, desafíos de seguridad, casos de uso emergentes y cómo jugar con el servidor MCP Elasticsearch de Elastic.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/mcp-current-state</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/mcp-current-state</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[JD Armada]]></dc:creator>
            <pubDate>Thu, 12 Jun 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/02b8c8672ffa129e0ed91a92d6cab612a01d27f2-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Recientemente asistí a la <a href="https://mcpdevsummit.ai/">MCP Developers Summit</a> en Santo Francisco y está claro que el Model Context Protocol (MCP) se está convirtiendo rápidamente en un pilar fundamental para agentes de IA y aplicaciones de IA ricas en contexto. En Elastic, nos inclinamos hacia esta dirección exponiendo los servidores MCP directamente desde <a href="https://www.elastic.co/es/elasticsearch/agent-builder">Agent Builder</a>, haciendo de Elasticsearch un proveedor de contexto y herramienta de primera clase para cualquier agente compatible con MCP. En esta publicación, repasaré las actualizaciones clave del evento, los casos de uso emergentes, lo que está por venir para MCP y cómo puedes usar Agent Builder para poner Elasticsearch a disposición de los agentes a través de MCP.</p><h2>¿Qué es el Protocolo de Contexto Modelo (MCP)?</h2><p>Para aquellos que no están familiarizados, <a href="https://modelcontextprotocol.io/introduction">Model Context Protocol</a> es un estándar abierto que ofrece una forma estructurada y bidireccional de conectar modelos de IA en varias fuentes de datos y herramientas, lo que les permite generar respuestas más relevantes e informadas. Se conoce comúnmente como un &quot;<a href="https://modelcontextprotocol.io/introduction">puerto USB-C para aplicaciones de IA</a>&quot;.</p><p>Aquí hay un diagrama arquitectónico que destaca su naturaleza bidireccional:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5eba1e59514eb58a5220bb92bb49e6328ee83cd7-674x466.png" style="width: 100%;" /><p>Este es un cambio significativo para los profesionales de IA, ya que uno de los principales desafíos para escalar las aplicaciones de IA es tener que crear integraciones personalizadas para cada nueva fuente de datos. MCP ofrece una arquitectura sostenible y reutilizable para gestionar y proporcionar contexto a los modelos. Es independiente del modelo, del servidor y de código abierto.</p><p>MCP es la última iteración de un linaje de especificaciones de API que buscan estandarizar la integración entre aplicaciones. En el pasado, teníamos OpenAPI para servicios RESTful, GraphQL para consultas de datos y gRPC para comunicación de microservicios. MCP no solo comparte el rigor estructurado de estas especificaciones más antiguas, sino que también lo lleva a un entorno de IA generativa, lo que facilita la conexión de agentes a diferentes sistemas sin conectores personalizados. En muchos sentidos, MCP tiene como objetivo hacer por los agentes de IA lo que HTTP hizo por el sitio web. Al igual que la comunicación estandarizada HTTP entre navegadores y sitios web, MCP busca estandarizar la forma en que los agentes de IA interactúan con el mundo de datos que los rodea.</p><h2>MCP frente a otros protocolos de agentes</h2><p>El panorama del protocolo de agentes se está expandiendo rápidamente, con más de una docena de estándares emergentes que compiten para definir cómo interactúan los agentes. <a href="https://x.com/seldo">Laurie Voss</a> de LlamaIndex describe cómo la mayoría se puede clasificar en 2 tipos: protocolos interagentes que se centran en que los agentes hablen entre sí y protocolos orientados al contexto como MCP que se centran en ofrecer un contexto estructurado a los LLM.</p><p>Otros protocolos populares como <a href="https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/">A2A</a> (Agent to Agent) de Google, <a href="https://agentcommunicationprotocol.dev/introduction/welcome">ACP</a> (Agent Communication Protocol) de Cisco e IBM y <a href="https://agoraprotocol.org/">Agora</a>, tienen como objetivo permitir negociaciones de agente a agente, creación de coaliciones e incluso sistemas de identidad descentralizados. MCP adopta un enfoque un poco más pragmático en eso, ya que se centra en cómo los agentes acceden a las herramientas y los datos y no necesariamente en cómo se comunican entre sí (aunque MCP también podría permitir eso en el futuro de diferentes maneras).</p><p>Actualmente, lo que distingue a MCP es su tracción e impulso. Al igual que React en los primeros días de los frameworks frontend, MCP comenzó con un problema de nicho y ahora se encuentra como uno de los protocolos de agente más adoptados y extensibles en la práctica.</p><h2>Resumen de la cumbre: Evolución de las prioridades para MCP</h2><p>La cumbre contó con presentadores de colaboradores de Anthropic, Okta, OpenAI, AWS, GitHub y muchos otros. Las conversaciones abarcaron desde mejoras en el protocolo central hasta implementaciones en el mundo real y delinearon prioridades inmediatas y a largo plazo. Estas charlas reflejaron un cambio de la experimentación temprana y la simple llamada de herramientas a la construcción de sistemas de IA confiables, escalables y modulares empleando MCP como base.</p><p>Varios presentadores se burlaron de un futuro en el que MCP es más que una simple plomería de protocolo, puede convertir en la base de un sitio web nativo de IA. Al igual que JavaScript permitió a los usuarios hacer clic e interactuar con los sitios web, MCP podría permitir que los agentes lleven a cabo esas mismas acciones en nuestro nombre. Por ejemplo, en el comercio electrónico, en lugar de que los usuarios naveguen manualmente a un sitio web para comprar, simplemente podrían decirle a un agente que inicie sesión, encuentre un producto específico, lo agregue a su carrito y pague.</p><p>Esto tampoco es pura especulación y exageración; PayPal mostró su nuevo kit de herramientas para agentes y el servidor MCP en la cumbre, lo que permite esta experiencia de comercio agencial exacta. Con MCP proporcionando acceso seguro y confiable a herramientas y fuentes de datos, los agentes no solo leerán el sitio web, sino que podrán actuar en consecuencia. Hoy en día, MCP ya es un estándar poderoso con mucho impulso y, en el futuro, podría convertir en el estándar de interacciones de usuario mejoradas por IA en el sitio web.</p><h2>Actualizaciones del proyecto MCP: transporte, elicitación y herramientas estructuradas</h2><p><a href="https://x.com/JeromeSwannack">Jerome Swannack</a>, colaborador principal de MCP, compartió algunas actualizaciones de la especificación del protocolo de los últimos 6 meses. Los principales objetivos de estos cambios son:</p><ol><li>Para habilitar MCP remoto con la adición de HTTP Streamable</li><li>Para habilitar modelos de interacción de agentes más completos con la adición de esquemas de salida de herramientas y elicitación</li></ol><p>Dado que MCP es de código abierto, los cambios como Streamable HTTP ya están disponibles para que los desarrolladores los implementen. Los esquemas de elicitación y salida de herramientas no se publicaron actualmente; están en la etapa de draft y pueden evolucionar.</p><p><strong>HTTP transmitible </strong>(<a href="https://modelcontextprotocol.io/specification/2025-03-26/basic/transports">lanzado el 26-03-2025</a><strong>):</strong> Una actualización técnica impactante fue la introducción de HTTP transmitible como un nuevo mecanismo de transporte. Esto reemplaza los eventos enviados por el servidor (SSE) con un modelo bidireccional más escalable que admite la codificación de transferencia fragmentada y la entrega progresiva de mensajes a través de una sola conexión HTTP. Esto le permite implementar servidores MCP en una infraestructura en la nube como AWS Lambda y admitir restricciones de red empresarial sin conexiones de larga duración ni necesidad de sondeo.</p><p><strong>Elicitation </strong>(<a href="https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation">Publicado el 18-06-2025</a><strong>):</strong> Elicitation permite a los servidores definir un esquema sobre cómo quieren que se estructure el contexto de un cliente. Esencialmente, el servidor puede describir lo que necesita y el tipo de entrada que espera. Esto tiene algunas participaciones: Para los creadores de servidores, pueden construir interacciones agenciales más complejas. Para los creadores de clientes, pueden implementar interfaces de usuario dinámicas que se adapten a estos esquemas. Sin embargo, la obtención no debe usar para extraer información confidencial o de identificación personal de los usuarios. Los desarrolladores deben seguir <a href="https://modelcontextprotocol.io/specification/draft/client/elicitation#security-considerations">las mejores prácticas</a> para cerciorar de que las indicaciones de elicitación permanezcan seguras y apropiadas, especialmente a medida que MCP madura. Esto se relaciona con preocupaciones de seguridad más amplias que discutiremos más adelante en esta publicación.</p><p><strong>Esquemas de salida de herramientas </strong>(<a href="https://modelcontextprotocol.io/specification/draft/server/tools#output-schema">publicado el 18-06-2025</a><strong>): </strong>Este concepto permite al cliente y al LLM conocer las formas de salida de la herramienta con anticipación. Los esquemas de salida de herramientas permiten a los desarrolladores describir lo que se espera que devuelva una herramienta. Estos esquemas abordan una de las principales limitaciones de la llamada directa a herramientas, que es el uso ineficiente de la ventana de contexto. La ventana de contexto se considera uno de los recursos más importantes cuando se trabaja con LLM y cuando se llama directamente a una herramienta, devuelve contenido sin procesar que se inserta por completo en el contexto del LLM. Los esquemas de salida de herramientas pueden ayudarlo a hacer un mejor uso de sus tokens y la ventana de contexto al permitir que el servidor MCP proporcione datos estructurados. Aquí hay algunas <a href="https://modelcontextprotocol.io/specification/draft/server/tools#security-considerations">mejores prácticas</a> sobre herramientas en general.</p><p>Juntas, estas nuevas actualizaciones y futuras adiciones ayudarán a MCP a convertir en un protocolo de agente más modular, tipificado y listo para la producción.</p><h2>Características de potencia infrautilizadas: ejemplificación y raíces</h2><p>Si bien no es nuevo en la especificación MCP, tanto la ejemplificación como las raíces se destacaron durante el discurso de apertura. Estas dos primitivas actualmente se pasan por alto y no se exploran, pero pueden contribuir significativamente a interacciones más ricas y seguras entre agentes.</p><p><strong>Ejemplificación: los servidores pueden aplicar finalizaciones del cliente: el </strong><a href="https://modelcontextprotocol.io/docs/concepts/sampling">ejemplificación</a> permite a los servidores MCP aplicar finalizaciones del LLM del lado del cliente. Esto se suma a la naturaleza bidireccional del protocolo, donde el servidor no solo responde a las solicitudes; Puede aplicar y pedir al modelo del cliente que genere una respuesta. Esto permite al cliente mantener un control total sobre el costo, la seguridad y el modelo que emplea el servidor MCP. Por lo tanto, en el caso de emplear un servidor MCP externo con un modelo preconfigurado, no necesitará proporcionar sus propias claves API ni configurar su propia subscripción a ese modelo, ya que el servidor puede simplemente aplicar el modelo ya conectado al cliente. Esto permite comportamientos de agente más complejos e interactivos.</p><p><strong>Raíces: acceso con ámbito a los recursos: </strong><a href="https://modelcontextprotocol.io/docs/concepts/roots">las raíces</a> se diseñaron para proporcionar una forma a los clientes de informar a los servidores sobre los recursos y áreas de trabajo relevantes en los que centrar. Esto es poderoso para establecer el alcance en el que operan los servidores. Es importante tener en cuenta que las raíces son &quot;<a href="https://modelcontextprotocol.io/docs/concepts/roots#how-roots-work">informativas y no estrictamente aplicables</a>&quot;, lo que significa que no definen derechos o licencias para servidores o agentes MCP. En otras palabras, no puede confiar solo en las raíces para evitar que un servidor o agente ejecute ciertas herramientas o realice acciones de escritura. Con roots, las licencias aún deben manejar en el lado del cliente con mecanismos para las aprobaciones de los usuarios. Además, los desarrolladores deben tener en cuenta el uso de servidores diseñados para respetar los límites establecido por las raíces y emplear <a href="https://modelcontextprotocol.io/docs/concepts/roots#best-practices">las mejores prácticas</a>.</p><h2>Autenticación para agentes: OAuth 2.1 y metadatos protegidos</h2><p>Esta sección se centra en OAuth 2.1, que es la última iteración de OAuth 2.0 que elimina los flujos inseguros y consolida las prácticas recomendadas.</p><p>El soporte de OAuth era un tema muy esperado, especialmente porque la seguridad y la escalabilidad se consideran los principales obstáculos que impiden que MCP se convierta en el estándar para conectar agentes a herramientas. <a href="https://x.com/aaronpk">Aaron Parecki</a> (editor de OAuth 2.1 y experto en estándares de identidad en Okta) discutió cómo MCP puede adoptar un flujo de OAuth limpio y escalable que descarga la mayor parte de la complejidad de los desarrolladores de servidores. La especificación oficial de autorización de OAuth 2.1 se publicó recientemente en la última revisión del protocolo el <a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization">18-6-2025</a>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2ea159116fe5e03ff800f077adf16d6ca9f1c1d1-1594x1280.png" style="width: 100%;" /><p>En esta implementación, las responsabilidades de OAuth se pueden dividir entre el cliente MCP y el servidor. La mayor parte del flujo de autenticación es iniciado y manejado por el cliente MCP, solo involucrando al servidor al final para recibir y verificar el token seguro. Esta división ayuda a resolver un problema crítico de escalado de cómo autenticar en muchas herramientas sin necesidad de que los desarrolladores configuren cada conexión y garantiza que los desarrolladores de servidores MCP no tengan que convertir en expertos en OAuth.</p><p>Dos aspectos destacados clave de la charla:</p><ol><li><a href="https://datatracker.ietf.org/doc/rfc9728/"><strong>Metadatos de recursos protegidos</strong></a>: Los servidores MCP pueden publicar un archivo JSON que describe su propósito, puntos finales y métodos de autenticación. Esto permite a los clientes iniciar flujos de OAuth solo con la URL del servidor, lo que simplifica el proceso de conexión. Más información: <a href="https://aaronparecki.com/2025/04/03/15/oauth-for-model-context-protocol">Arreglemos OAuth en MCP</a></li><li><a href="https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13"><strong>Soporte para IDP y SSO:</strong></a> las compañías pueden integrar proveedores de identidad para gestionar el acceso de forma centralizada. Esta es una victoria tanto para la experiencia del usuario como para la seguridad. Los usuarios no necesitarían hacer clic en 10 pantallas de consentimiento diferentes y los equipos de seguridad pueden observar cada conexión.</li></ol><p>Al enviar la lógica de OAuth al cliente y confiar en los metadatos de los servidores, el ecosistema MCP evita un cuello de botella importante. Esto alinea más estrechamente a MCP con la forma en que se protegen las API modernas en los entornos de producción actuales.</p><p>Lectura adicional: <a href="https://aaronparecki.com/oauth-2-simplified/">OAuth 2 simplificado</a>.</p><h2>Desafíos de seguridad en un ecosistema componible</h2><p>Los nuevos desarrollos también vienen con nuevas superficies de ataque. Arjun Sambamoorthy de Cisco enumera varias amenazas clave en el panorama de MCP, que incluyen:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Perfiles</td><td style="border: 1px solid #ccc; padding: 4px;">Descripción</td><td style="border: 1px solid #ccc; padding: 4px;">Remediación y mejores prácticas</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Inyección rápida y envenenamiento de herramientas</td><td style="border: 1px solid #ccc; padding: 4px;">Una forma de inyectar un mensaje malicioso dentro del contexto del sistema LLM o la descripción de la herramienta, lo que hace que el LLM realice acciones no deseadas como leer archivos o filtrar datos.</td><td style="border: 1px solid #ccc; padding: 4px;">Emplee herramientas como MCP Scan para realizar comprobaciones de los metadatos de la herramienta. Valide las descripciones y los parámetros antes de incluirlos en las solicitudes. Por último, considere implementar aprobaciones de usuarios para herramientas de alto riesgo. Para obtener más detalles, consulte la guía de inyección rápida de OWASP en la lista de lectura adicional debajo de la tabla.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Ataques de ejemplificación</td><td style="border: 1px solid #ccc; padding: 4px;">En el contexto de MCP, la ejemplificación abre la puerta para que el servidor MCP realice ataques de inyección rápida en el LLM.</td><td style="border: 1px solid #ccc; padding: 4px;">Deshabilite la ejemplificación para servidores que no son de confianza y considere la posibilidad de agregar aprobaciones de personas en el bucle para las solicitudes de ejemplificación.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Servidores MCP maliciosos</td><td style="border: 1px solid #ccc; padding: 4px;">En las colecciones actuales de servidores MCP, es difícil examinar todos y cada uno de ellos para garantizar la seguridad. Los servidores no autorizados pueden recopilar y exponer silenciosamente sus datos a actores maliciosos.</td><td style="border: 1px solid #ccc; padding: 4px;">Conectar únicamente a servidores MCP desde registros confiables o listas internas. Ejecute servidores de terceros en contenedores con sandboxing.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Herramientas de instalación de MCP maliciosas</td><td style="border: 1px solid #ccc; padding: 4px;">Los instaladores y scripts de línea de comandos son convenientes para implementar rápidamente servidores o herramientas MCP, pero podría terminar instalando código comprometido y no verificado.</td><td style="border: 1px solid #ccc; padding: 4px;">Instale en entornos de espacio aislado y valide las firmas de los paquetes. Nunca actualice automáticamente desde fuentes no verificadas.</td>
                    </tr>
              </tbody>
            </table>
          <p>Para combatir aún más esto, Arjun sugiere un registro de MCP confiable para manejar todas las verificaciones (un tema que estaba al frente y al centro; para obtener más detalles, consulte los dos elementos principales en la lista de lectura a continuación), así como el uso de esta <a href="https://github.com/slowmist/MCP-Security-Checklist">lista de verificación de seguridad</a>.</p><p>Lectura adicional:</p><ul><li><a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices">Mejores prácticas oficiales de seguridad de MCP</a></li><li><a href="https://owasp.org/www-project-top-10-for-large-language-model-applications/">Top 10 de aplicaciones de OWASP LLM</a></li><li><a href="https://hiddenlayer.com/innovation-hub/">Investigación de amenazas de HiddenLayer</a></li><li><a href="https://github.com/invariantlabs-ai/mcp-scan">Escaneo MCP</a></li><li><a href="https://genai.owasp.org/llmrisk/llm01-prompt-injection/">Guía de inyección rápida de OWASP</a></li></ul><h2>Lo que sigue: Registros, gobernanza y ecosistema</h2><p>Se está desarrollando un registro centralizado de MCP y fue uno de los temas más discutidos en la cumbre. El ecosistema actual de servidores sufre de fragmentación, baja confianza y capacidad de descubrimiento. Es difícil para los desarrolladores encontrar servidores MCP, verificar lo que hacen e instalarlos de forma segura, especialmente en un ecosistema descentralizado donde los metadatos pueden estar incompletos o falsificados.</p><p>Un registro centralizado aborda estos puntos débiles directamente al actuar como una fuente confiable de verdad, mejorando la capacidad de descubrimiento, cerciorando la integridad de los metadatos del servidor y reduciendo el riesgo de instalar herramientas maliciosas.</p><p>Los objetivos del registro MCP son:</p><ul><li>Ofrecer una única fuente de verdad para los metadatos del servidor (qué hace un servidor, cómo autenticarlo, instalarlo y llamarlo)</li><li>Deshacer de los registros de terceros incompletos y la fragmentación para que cuando un servidor quiera registrar, no tenga que actualizar todos los demás registros en el Internet.</li><li>Proporcionar un flujo de registro de servidor que incluya una herramienta CLI y un archivo server.json que contenga los metadatos mencionados anteriormente.</li></ul><p>La esperanza más amplia es que un registro confiable ayude a escalar el ecosistema de manera segura, permitiendo a los desarrolladores crear y compartir nuevas herramientas con confianza.</p><p>La gobernanza fue otro tema prioritario para Anthropic. Dejaron en claro que MCP debe permanecer abierto e impulsado por la comunidad, pero escalar ese modelo de gobernanza aún es un trabajo en progreso. Actualmente están buscando ayuda en ese campo y piden a cualquier persona que tenga experiencia con la gobernanza en protocolos de código abierto que se comunique. Esto lleva al otro tema que quería mencionar. A lo largo del evento, los presentadores enfatizaron que el ecosistema solo puede crecer con las contribuciones de los desarrolladores internos. Es necesario que haya un esfuerzo concentrado para hacer de MCP el nuevo estándar sitio web y destacar de los otros protocolos de agentes populares.</p><h2>MCP en el mundo real: estudios de casos y demostraciones</h2><p>Varias organizaciones compartieron cómo MCP ya se está empleando en aplicaciones prácticas:</p><ul><li><strong>PayPal - Servidor MCP para Comercio Agentico: </strong>PayPal presentó su nuevo <a href="https://github.com/paypal/agent-toolkit/">kit de herramientas para agentes</a> y el servidor MCP, que pueden cambiar fundamentalmente la experiencia de compra de un usuario. En lugar de buscar en las redes sociales para encontrar artículos, comparar precios y pagar, los usuarios pueden chatear con un agente que se conecta al servidor MCP de PayPal para manejar todas esas acciones.<br/></li><li><strong>EpicAI.pro - Jarvis:</strong> Los desarrollos en MCP nos acercan cada vez más a tener un asistente tipo Jarvis de la vida real. Para aquellos que no están familiarizados con las películas de Iron Man, Jarvis es un asistente de IA que usa lenguaje natural, responde a entradas multimodales, tiene latencia cero al responder, es proactivo en anticipar las necesidades del usuario, gestiona integraciones automáticamente y puede cambiar de contexto entre dispositivos y ubicaciones. Si imaginamos a Jarvis como un asistente robótico físico, MCP le da a Jarvis &quot;manos&quot; o la capacidad de manejar tareas complejas.<br/></li><li><strong>Postman - <a href="https://www.postman.com/explore/mcp-generator">Generador de servidores MCP</a>: </strong>Proporciona una experiencia de carrito de compras para solicitudes de API donde puede elegir diferentes solicitudes de API, ponerlas en una cesta y descargar toda la cesta como un servidor MCP.<br/></li><li><strong>Bloomberg - </strong>Bloomberg resolvió un cuello de botella clave en el desarrollo empresarial de GenAI. Con casi 10,000 ingenieros, necesitaban una forma estandarizada de integrar herramientas y agentes en todos los equipos. Con MCP, transformaron sus herramientas internas en componentes modulares y remotos a los que los agentes pueden llamar fácilmente en una interfaz unificada. Esto permitió a sus ingenieros contribuir con herramientas en toda la organización, mientras que los equipos de IA se centraron en crear agentes en lugar de integraciones personalizadas. Bloomberg ahora admite flujos de trabajo de agentes escalables y seguros que desbloquean la interoperabilidad total con el ecosistema MCP. Bloomberg no vinculó ningún recurso público, pero esto es lo que presentaron en público en la cumbre.<br/></li><li><strong>Bloquear- </strong>Block emplea MCP para impulsar <a href="https://github.com/block/goose?tab=readme-ov-file">Goose</a>, un agente interno de IA que permite a los empleados automatizar tareas en ingeniería, ventas, marketing y más. Construyeron más de 60 servidores MCP para herramientas como Git, Snowflake, Jira y Google Workspace para permitir la interacción del lenguaje natural con los sistemas que usan todos los días. Los empleados de Block ahora usan Goose para consultar datos, detectar fraudes, gestionar incidentes, navegar por procesos internos y más, todo sin tener que escribir código. MCP ayudó a Block a escalar la adopción de IA en muchas funciones laborales en solo 2 meses.<br/></li><li><strong>AWS - <a href="https://github.com/awslabs/mcp">Servidores MCP de AWS</a>: </strong>AWS presentó un divertido servidor MCP con temática de Dungeons and Dragons que simula tirar dados, realiza un seguimiento de las tiradas anteriores y devuelve resultados mediante HTTP transmitible. Este ejemplo ligero destacó lo fácil que es crear e implementar servidores MCP empleando herramientas e infraestructura de AWS como Lambda y Fargate. También presentaron <a href="https://aws.amazon.com/blogs/opensource/introducing-strands-agents-an-open-source-ai-agents-sdk/">Strands SDK</a>, un conjunto de herramientas de código abierto para crear agentes multimodales que interactúan con los servidores MCP.</li></ul><h2>Soporte para MCP en Elastic Agent Builder</h2><p>Puedes empezar a experimentar con MCP hoy mismo usando <a href="https://www.elastic.co/es/search-labs/blog/elastic-ai-agent-builder-context-engineering-introduction">Elastic Agent Builder,</a> que es la forma más sencilla de construir agentes directamente sobre tus datos. Agent Builder te permite exponer herramientas impulsadas por Elasticsearch a agentes compatibles con MCP y ya incluye algunas herramientas integradas poderosas, entre ellas:</p><ul><li><code>platform.core.search</code> - Ejecuta búsquedas usando el DSL completo de Elasticsearch Query</li><li><code>platform.core.list_indices</code> - Lista todos los índices disponibles dentro de Elasticsearch (ayuda a los agentes a descubrir qué datos existen)</li><li><code>platform.core.get_index_mapping</code> - Recupera mapeos de campos para un índice específico (ayuda a los agentes a entender la forma y los tipos de tus datos)</li><li><code>platform.core.get_document_by_id</code> - Obtiene un documento específico por ID (para una recuperación precisa)</li></ul><p>Solo con estas herramientas, puedes dotar a tu agente de búsqueda y relevancia a nivel empresarial, lo cual es fundamental para construir agentes de IA fiables.</p><p>Lo que hace que Agent Builder sea aún más poderoso es la capacidad de definir y exponer tus propias herramientas personalizadas adaptadas a las necesidades de tu aplicación. Esto es especialmente útil para flujos de trabajo con opiniones o repetibles, donde quieres que el agente realice un tipo específico de búsqueda contra un índice concreto sin tener que redescubrir esa lógica cada vez. En lugar de gastar tokens en planeación y razonamiento para llegar a la misma conclusión, puedes codificar esa intención directamente en una herramienta, haciendo que tus agentes sean más rápidos, fiables y rentables.</p><p>Dentro de la interfaz de Agent Builder, aquí tienes un ejemplo de definición personalizada de herramienta que emplea ES|QL:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c1f6405a374b707e8e6fa36b9e21db5f3c7cd127-1376x864.png" style="width: 100%;" /><p>Una vez que definiste tus herramientas personalizadas, puedes exponerlas (además de las herramientas nativas integradas) usando MCP haciendo clic en el desplegable para <code>Manage MCP</code> y copiando la URL del servidor MCP.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9f39588525ca2643475de557ea54a6bcf5c150f6-1282x616.png" style="width: 100%;" /><p>Ahora puedes importar este endpoint MCP a cualquier cliente que consuma MCP para conectarlo a Agent Builder y darle acceso a todas las herramientas disponibles. Para más información, lee esta introducción a <a href="https://www.elastic.co/es/search-labs/blog/elastic-ai-agent-builder-context-engineering-introduction">Agent Builder</a>.</p><h2>Conclusión</h2><p>La Cumbre de Desarrollo de MCP dejó en claro que MCP está dando forma a la forma en que estos agentes de IA interactúan entre sí y con el mundo de datos que los rodea. Ya sea que esté conectando un agente a los datos de la compañía o diseñando agentes totalmente autónomos, MCP ofrece una forma estandarizada y componible de integración que se está volviendo rápidamente útil a escala. Desde los protocolos de transporte y los patrones de seguridad hasta los registros y la gobernanza, el ecosistema MCP está madurando rápidamente. MCP seguirá siendo abierto e impulsado por la comunidad, por lo que los desarrolladores de hoy tienen la oportunidad de dar forma a su evolución.</p>]]></content:encoded>
        </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>
            <link>https://www.elastic.co/search-labs/es/blog/linear-retriever-hybrid-search</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/linear-retriever-hybrid-search</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Wed, 28 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7a07e27e3095463ff93b4cb7f8a0cf3b8e44eab0-1777x1000.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <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><div class="katex">score = 5 * knn + 1.5 * bm25</div><p>Es tan simple como:</p><pre><code>GET linear_retriever_blog/_search
{
   "retriever": {
       "linear": {
           "retrievers": [
               {
                   "retriever": {
                       "knn": {
                          ...
                        }
                    },
                   "weight": 5
               },
                  {
                   "retriever": {
                       "standard": {
                          ...
                        }
                    },
                   "weight": 1.5
               },


           ]
        }
     }
}</code></pre><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>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">doc1</td><td style="border: 1px solid #ccc; padding: 4px;">doc2</td><td style="border: 1px solid #ccc; padding: 4px;">doc3</td><td style="border: 1px solid #ccc; padding: 4px;">doc4</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">knn</td><td style="border: 1px solid #ccc; padding: 4px;">0.347</td><td style="border: 1px solid #ccc; padding: 4px;">0.35</td><td style="border: 1px solid #ccc; padding: 4px;">0.348</td><td style="border: 1px solid #ccc; padding: 4px;">0.346</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">bm25</td><td style="border: 1px solid #ccc; padding: 4px;">100</td><td style="border: 1px solid #ccc; padding: 4px;">1.5</td><td style="border: 1px solid #ccc; padding: 4px;">1</td><td style="border: 1px solid #ccc; padding: 4px;">0.5</td>
                    </tr>
              </tbody>
            </table>
          <p>Puntajes de la consulta B:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">doc1</td><td style="border: 1px solid #ccc; padding: 4px;">doc2</td><td style="border: 1px solid #ccc; padding: 4px;">doc3</td><td style="border: 1px solid #ccc; padding: 4px;">doc4</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">knn</td><td style="border: 1px solid #ccc; padding: 4px;">0.347</td><td style="border: 1px solid #ccc; padding: 4px;">0.35</td><td style="border: 1px solid #ccc; padding: 4px;">0.348</td><td style="border: 1px solid #ccc; padding: 4px;">0.346</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">bm25</td><td style="border: 1px solid #ccc; padding: 4px;">0.63</td><td style="border: 1px solid #ccc; padding: 4px;">0.01</td><td style="border: 1px solid #ccc; padding: 4px;">0.3</td><td style="border: 1px solid #ccc; padding: 4px;">0.4</td>
                    </tr>
              </tbody>
            </table>
          <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><div class="katex">normalized_score = (score - min) / (max - min)</div><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>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">doc1</td><td style="border: 1px solid #ccc; padding: 4px;">doc2</td><td style="border: 1px solid #ccc; padding: 4px;">doc3</td><td style="border: 1px solid #ccc; padding: 4px;">doc4</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">knn</td><td style="border: 1px solid #ccc; padding: 4px;">0.347</td><td style="border: 1px solid #ccc; padding: 4px;">0.35</td><td style="border: 1px solid #ccc; padding: 4px;">0.348</td><td style="border: 1px solid #ccc; padding: 4px;">0.346</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">bm25</td><td style="border: 1px solid #ccc; padding: 4px;">1.00</td><td style="border: 1px solid #ccc; padding: 4px;">0.01</td><td style="border: 1px solid #ccc; padding: 4px;">0.005</td><td style="border: 1px solid #ccc; padding: 4px;">0.000</td>
                    </tr>
              </tbody>
            </table>
          <p>Puntajes de la consulta B:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">doc1</td><td style="border: 1px solid #ccc; padding: 4px;">doc2</td><td style="border: 1px solid #ccc; padding: 4px;">doc3</td><td style="border: 1px solid #ccc; padding: 4px;">doc4</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">knn</td><td style="border: 1px solid #ccc; padding: 4px;">0.347</td><td style="border: 1px solid #ccc; padding: 4px;">0.35</td><td style="border: 1px solid #ccc; padding: 4px;">0.348</td><td style="border: 1px solid #ccc; padding: 4px;">0.346</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">bm25</td><td style="border: 1px solid #ccc; padding: 4px;">1.00</td><td style="border: 1px solid #ccc; padding: 4px;">0.000</td><td style="border: 1px solid #ccc; padding: 4px;">0.465</td><td style="border: 1px solid #ccc; padding: 4px;">0.645</td>
                    </tr>
              </tbody>
            </table>
          <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>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">doc1</td><td style="border: 1px solid #ccc; padding: 4px;">doc2</td><td style="border: 1px solid #ccc; padding: 4px;">doc3</td><td style="border: 1px solid #ccc; padding: 4px;">doc4</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">knn</td><td style="border: 1px solid #ccc; padding: 4px;">0.347</td><td style="border: 1px solid #ccc; padding: 4px;">0.35</td><td style="border: 1px solid #ccc; padding: 4px;">0.348</td><td style="border: 1px solid #ccc; padding: 4px;">0.346</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">bm25</td><td style="border: 1px solid #ccc; padding: 4px;">100</td><td style="border: 1px solid #ccc; padding: 4px;">1.5</td><td style="border: 1px solid #ccc; padding: 4px;">1</td><td style="border: 1px solid #ccc; padding: 4px;">0.5</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">puntaje de la fuerza de avance</td><td style="border: 1px solid #ccc; padding: 4px;">0.03226</td><td style="border: 1px solid #ccc; padding: 4px;">0.03252</td><td style="border: 1px solid #ccc; padding: 4px;">0.03200</td><td style="border: 1px solid #ccc; padding: 4px;">0.03125</td>
                    </tr>
              </tbody>
            </table>
          <p>RRF clasificaría los documentos como:</p><div class="katex">doc2 > doc1 > doc3 > doc4</div><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>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">doc1</td><td style="border: 1px solid #ccc; padding: 4px;">doc2</td><td style="border: 1px solid #ccc; padding: 4px;">doc3</td><td style="border: 1px solid #ccc; padding: 4px;">doc4</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">knn</td><td style="border: 1px solid #ccc; padding: 4px;">0.347</td><td style="border: 1px solid #ccc; padding: 4px;">0.35</td><td style="border: 1px solid #ccc; padding: 4px;">0.348</td><td style="border: 1px solid #ccc; padding: 4px;">0.346</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">bm25</td><td style="border: 1px solid #ccc; padding: 4px;">1</td><td style="border: 1px solid #ccc; padding: 4px;">0.01</td><td style="border: 1px solid #ccc; padding: 4px;">0.005</td><td style="border: 1px solid #ccc; padding: 4px;">0</td>
                    </tr>
              </tbody>
            </table>
          <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><pre><code>GET linear_retriever_blog/_search
{
   "retriever": {
       "linear": {
           "retrievers": [
               {
                   "retriever": {
                       "knn": {
                          ...
                        }
                    },
                   "weight": 5
               },
                  {
                   "retriever": {
                       "standard": {
                          ...
                        }
                    },
                   "weight": 1.5,
                   "normalizer": "minmax"
               },


           ]
       }
   }
}</code></pre><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>Preserva la importancia relativa al aprovechar los puntajes reales, no solo los rangos.</li><li>Permite el ajuste fino con contribuciones ponderadas de diferentes consultas.</li><li>Mejora la coherencia mediante la normalización, lo que hace que la búsqueda híbrida sea más estable y previsible.</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>
        </item>
        <item>
            <title><![CDATA[Creación de listas de evaluación con Quepid]]></title>
            <description><![CDATA[Aprende cómo crear listas de evaluación en Quepid utilizando un proceso colaborativo de calificadores humanos y usa los puntos de referencia para ajustar la relevancia.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/quepid-judgement-lists</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/quepid-judgement-lists</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Daniel Wrigley]]></dc:creator>
            <pubDate>Mon, 26 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/307c1774bd31f92bb4aa7b69e1a6796240465100-1600x914.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La creación de <a href="https://www.elastic.co/search-labs/blog/judgment-lists">listas de juicio</a> es un paso crucial para optimizar la calidad de los resultados de búsqueda, pero puede ser una tarea complicada y difícil. Una lista de juicios es un conjunto seleccionado de consultas de búsqueda combinadas con valoraciones de relevancia para sus resultados correspondientes, también conocida como colección de prueba. Las métricas calculadas mediante esta lista actúan como referencia para medir el rendimiento de un motor de búsqueda. Para agilizar el proceso de creación de listas de juicio, el equipo <a href="https://opensourceconnections.com/">de OpenSource Connections</a> desarrolló <a href="https://quepidapp.com/">Quepid</a>. El juicio puede ser explícito o basar en la retroalimentación implícita de los usuarios. Este blog te guiará para establecer un entorno colaborativo en Quepid que permita eficazmente a los evaluadores humanos hacer juicios explícitos, que es la base de cada lista de juicios.</p><p>Quepid apoya a los equipos de búsqueda en el proceso de evaluación de la calidad de búsqueda:</p><ul><li>Construcción de conjuntos de consultas</li><li>Crear listas de juicios</li><li>Calcular métricas de calidad de búsqueda</li><li>Compara diferentes algoritmos/rankingers de búsqueda basándote en métricas calculadas de calidad de búsqueda</li></ul><p>Para nuestro blog, supongamos que gestionamos una tienda de alquiler de películas y que tenemos como objetivo mejorar la calidad de los resultados de búsqueda.</p><h2>Prerrequisitos</h2><p>Este blog emplea los datos y los mapeos del <a href="https://github.com/o19s/es-tmdb">repositorio es-tmdb</a>. Los datos provienen <a href="https://www.themoviedb.org/">de The Movie Database</a>. Para seguir el ritmo, configura un índice llamado tmdb con los mapeos e indexa los datos. No importa si configuras una instancia local o usas un despliegue de Elastic Cloud para esto: cualquiera de las dos funciona bien. Asumimos un despliegue de Elastic Cloud para este blog. Puedes encontrar información sobre cómo indexar los datos en el <a href="https://github.com/o19s/es-tmdb/blob/master/README.md">README del repositorio es-tmdb</a>.</p><p>Haz una consulta sencilla de coincidencias en el campo del título para <code>rocky</code> confirmar que tienes datos para buscar:</p><pre><code>GET tmdb/_search
{
 "query": {
   "match": {
     "title": "rocky"
   }
 }
}</code></pre><p>Deberías ver 8 resultados.</p><pre><code>{
 "took": 2,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 8,
     "relation": "eq"
   }
…
}</code></pre><h2>Iniciar sesión en Quepid</h2><p><a href="https://github.com/o19s/quepid">Quepid</a> es una herramienta que permite a los usuarios medir la calidad de los resultados de búsqueda y ejecutar experimentos offline para mejorarla.</p><p>Puedes usar Quepid de dos maneras: o bien usar la versión gratis y disponible públicamente alojada en <a href="https://app.quepid.com">https://app.quepid.com</a>, o configura Quepid en una máquina a la que tengas acceso. Esta publicación asume que estás usando la versión alojada gratis. Si quieres configurar una instancia de Quepid en tu entorno, sigue la <a href="https://github.com/o19s/quepid/wiki/Installation-Guide">Guía de Instalación</a>.</p><p>Sea cual sea la configuración que elijas, tendrás que crear una cuenta si aún no tienes una.</p><h2>Cómo configurar un caso de Quepid</h2><p>Quepid está organizado en torno a los &quot;Casos&quot;. Un caso almacena consultas junto con ajustes de relevancia y cómo establecer una conexión con tu motor de búsqueda.</p><ul><li>Para usuarios primerizos, selecciona <strong>Crear tu primer caso de relevancia</strong>.</li><li>Los usuarios que regresan pueden seleccionar <strong>Casos de Relevancia</strong> desde el menú superior y hacer clic <strong>en + Crear un caso</strong>.</li></ul><p>Nombra tu caso de forma descriptiva, por ejemplo, &quot;Línea base de búsqueda de películas&quot;, ya que queremos empezar a medir y mejorar nuestra búsqueda de referencia.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8f9e480d9aae0d706cfc5371e41f19c706dd452a-594x251.png" style="width: 100%;" /><p>Confirma el nombre <strong>seleccionando Continuar</strong>.</p><p>A continuación, establecemos una conexión de Quepid con el motor de búsqueda. Quepid puede conectarse a una variedad de motores de búsqueda, incluido Elasticsearch.</p><p>La configuración variará según tu configuración de Elasticsearch y Quepid. Para conectar Quepid a un despliegue de Elastic Cloud, necesitamos habilitar y configurar CORS para nuestro despliegue de Elastic Cloud y tener lista una clave de API. Las instrucciones detalladas están en el tutorial correspondiente <a href="https://quepid-docs.dev.o19s.com/2/quepid/49/how-to-connect-quepid-to-elastic-cloud">en la documentación de Quepid</a>.</p><p>Introduce la información de tu endpoint de Elasticsearch (<code>https://YOUR_ES_HOST:PORT/tmdb/_search</code>) y cualquier información adicional necesaria para conectarte (la clave API en caso de un despliegue de Elastic Cloud en las opciones <strong>de configuración avanzada</strong> ), prueba la conexión haciendo <strong>clic en ping y</strong> <strong>selecciona Continuar</strong> para pasar al siguiente paso.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/267339dfaecae2740eb2ee2739bdc971608bdb5f-588x1169.png" style="width: 100%;" /><p>Ahora definimos qué campos queremos que se muestren en el caso. Selecciona todos los que ayuden a nuestros evaluadores humanos a evaluar posteriormente la relevancia de un documento para una consulta determinada.</p><p>Establece <code>title</code> como <em>Campo de Título</em>, deja <code>_id</code> como <em>Campo ID</em> y agrega <code>overview, tagline, cast, vote_average, thumb:poster_path</code> como <em>Campos de Visualización Adicionales</em>. La última entrada muestra pequeñas imágenes en miniatura de las películas en nuestros resultados para guiarnos visualmente a nosotros y a los evaluadores humanos.</p><p>Confirma la configuración de pantalla seleccionando el botón <strong>Continuar</strong> .</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/02c58aae8c2ebb6d31f538b27462b4c65428fdc3-594x493.png" style="width: 100%;" /><p>El último paso es agregar consultas de búsqueda al caso. Agrega las tres consultas <em>Star Wars</em>, <em>Harrison Ford</em> y <em>la mejor película de acción</em> una por una a través del campo de entrada y <strong>Continúa</strong>.</p><p>Idealmente, un caso contiene consultas que representan consultas reales de usuarios e ilustran diferentes tipos de consultas. Por ahora, podemos <em>imaginar que Star Wars</em> es una consulta que representa todas las consultas de títulos de películas, <em>Harrison Ford</em> una consulta que representa todas las consultas de los miembros del reparto, y <em>Best Action Movie</em> una consulta que representa todas las consultas que buscan películas de un género específico. Esto se denomina típicamente conjunto de consultas.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/baccfe96766319aa7255e9bff08913ac87d1517f-595x326.png" style="width: 100%;" /><p>En un escenario de producción, muestrearíamos consultas de datos de seguimiento de eventos aplicando técnicas estadísticas como <a href="https://opensourceconnections.com/blog/2022/10/13/how-to-succeed-with-explicit-relevance-evaluation-using-probability-proportional-to-size-sampling/">ejemplificación de probabilidad proporcional al tamaño</a> e importaríamos estas consultas muestreadas a Quepid para incluir consultas desde la cabeza (consultas frecuentes) y cola (consultas poco frecuentes) en relación con su frecuencia, lo que significa que tendemos a optar por consultas más frecuentes sin excluir las raras.</p><p>Finalmente, selecciona <strong>Terminar</strong> y serás redirigido a la interfaz de casos donde verás las tres consultas definidas.</p><h2>Búsquedas y necesidades de información</h2><p>Para llegar a nuestro objetivo general de una lista de juicios, los evaluadores humanos deberán juzgar un resultado de búsqueda (normalmente un documento) para una consulta determinada. Esto se llama par consulta/documento.</p><p>A veces, parece fácil saber qué quería un usuario al revisar la consulta. La intención detrás de la <code>harrison ford</code> es encontrar películas protagonizadas por Harrison Ford, el actor. ¿Y qué pasa con la <code>action</code>de la consulta? Sé que me tentaría decir que la intención del usuario es encontrar películas del género de acción. ¿Pero cuáles? ¿Los más recientes, los más populares, los mejores según las valoraciones de los usuarios? ¿O quizá el usuario quiere encontrar todas las películas que se llaman &quot;Acción&quot;? <a href="https://www.themoviedb.org/search/movie?query=Action">Hay al menos 12 (!) películas llamadas &quot;Acción&quot; en The Movie Database</a> y sus nombres difieren principalmente en el número de signos de exclamación en el título.</p><p>Dos evaluadores humanos pueden diferir en la interpretación de una consulta cuando la intención no está clara. Entra en escena la necesidad de información: Una <a href="https://en.wikipedia.org/wiki/Information_needs">necesidad de información</a> es un deseo consciente o inconsciente de información. Definir una necesidad de información ayuda a los evaluadores humanos a juzgar documentos para una consulta, por lo que desempeñan un papel importante en el proceso de elaboración de listas de juicio. Los usuarios expertos o expertos en la materia son buenos candidatos para especificar necesidades de información. Es buena práctica definir las necesidades de información desde la perspectiva del usuario, ya que es su necesidad la que los resultados de búsqueda deben satisfacer.</p><p>Necesidades de información para las consultas de nuestro caso &quot;Línea de Base de Búsqueda de Películas&quot;:</p><ol><li><strong>Star Wars</strong>: El usuario quiere encontrar películas o seriales de la franquicia Star Wars. Potencialmente relevantes son los documentales sobre Star Wars.</li><li><strong>Harrison Ford</strong>: El usuario quiere encontrar películas protagonizadas por el actor Harrison Ford. Potencialmente relevantes son las películas en las que Harrison Ford tiene un papel diferente, como el de narrador.</li><li><strong>mejor película de acción</strong>: El usuario quiere encontrar películas de acción, preferiblemente aquellas con votos medios altos.</li></ol><h2>Cómo definir las necesidades de información en Quepid</h2><p>Para definir una necesidad de información en Quepid, accede a la interfaz de casos:</p><p>1. Abre una consulta (por ejemplo,<em>s tar wars</em>) y selecciona <em>Alternar Notas.</em></p><p>2. Introduce la Necesidad de Información en el primer campo y cualquier nota adicional en el segundo campo:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e01d3d5242a350d8797faa665eb3170039f5dfa2-1483x559.png" style="width: 100%;" /><p>3. Haz clic <strong>en almacenar</strong>.</p><p>Para un puñado de consultas, este proceso está bien. Sin embargo, cuando amplías tu caso de tres a 100 consultas (los casos de Quepid suelen estar en el rango de 50 a 100 consultas), puede que quieras definir necesidades de información fuera de Quepid (por ejemplo, en una hoja de cálculo) y luego subirlas mediante <strong>Importar</strong> y seleccionar <strong>Necesidades de Información</strong>.</p><h2>Crea un equipo en Quepid y comparte tu caso</h2><p>Los juicios colaborativos mejoran la calidad de las evaluaciones de relevancia. Para formar un equipo:</p><p>1. Navega a <strong>Teams</strong> en el menú superior.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/797706e8d130b474a95d30b6fa22ecaf36f98c03-613x58.png" style="width: 100%;" /><p>2. Haz clic <strong>+ Agregar Nuevo</strong>, introduce el nombre de un equipo (por ejemplo, &quot;Search Relevance Raters&quot;) y haz clic <strong>en Crear</strong>.</p><p>3. Agregar miembros escribiendo sus direcciones de email y haciendo clic <strong>en Agregar usuario</strong>.</p><p>4. En la interfaz de casos, selecciona <strong>Compartir Caso.</strong></p><p>5. Elegir el equipo adecuado y confirmarlo.</p><h2>Crea un libro de evaluaciones en Quepid</h2><p>Un libro en Quepid permite a varios evaluadores evaluar sistemáticamente los pares consulta/documento. Para crear uno:</p><p>1. Ve a <strong>Sentencias</strong> en la interfaz del caso y haz <strong>clic + Crear un libro</strong>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6a077f26225961150b7414463d7db04f090b68d6-896x365.png" style="width: 100%;" /><p>2. Configura el libro con un nombre descriptivo, asigna el libro a tu equipo, selecciona un método de puntaje (por ejemplo, DCG@10) y establece la estrategia de selección (uno o varios evaluadores). Emplea los siguientes ajustes para el libro:</p><ul><li><strong>Nombre</strong>: &quot;Búsqueda de películas a escala 0-3&quot;</li><li><strong>Equipos con los que compartir este libro</strong>: Marca la casilla con el equipo que creaste</li><li><strong>Goleador</strong>: DCG@10</li></ul><p>3. Haz clic <strong>en Crear libro.</strong></p><p>El nombre es descriptivo y contiene información sobre lo que se busca en (&quot;Películas&quot;) y también la escala de las sentencias (&quot;0-3&quot;). El DCG@10 seleccionado de Scorer define la forma en que se calculará la métrica de búsqueda. &quot;DCG&quot; es la abreviatura de <a href="https://en.wikipedia.org/wiki/Discounted_cumulative_gain">Ganancia Acumulada Descontada</a> y &quot;@10&quot; es el número de resultados desde la parte superior que se tiene en cuenta al calcular la métrica.</p><p>En este caso, estamos usando una métrica que mide la ganancia de información y la combina con ponderación posicional. Puede que haya otras métricas de búsqueda más adecuadas para tu caso de uso y <a href="https://opensourceconnections.com/blog/2020/02/28/choosing-your-search-relevance-metric">elegir la adecuada es un desafío en sí</a> mismo.</p><h2>Llena el libro con pares de búsqueda/documento</h2><p>Para agregar pares de consulta/documento para la evaluación de relevancia, sigue estos pasos:</p><p>1. En la interfaz del caso, navega a &quot;Sentencias&quot;.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d717c5b06ae6cb42ed2b9e771486a12f738a9890-1041x218.png" style="width: 100%;" /><p>2. Selecciona tu libro creado.</p><p>3. Haz clic en &quot;Poblar libro&quot; y confirma seleccionando &quot;Actualizar pares de consulta/documentos para libro.&quot;</p><p>Esta acción genera pares basados en los principales resultados de búsqueda de cada consulta, listos para su evaluación por parte de su equipo.</p><h2>Deja que tu equipo de evaluadores humanos juzgue </h2><p>Hasta ahora, los pasos completados fueron bastante técnicos y administrativos. Ahora que esta preparación necesaria está hecha, podemos dejar que nuestro equipo de jueces haga su trabajo. En esencia, el trabajo del juez es valorar la relevancia de un documento concreto para una consulta determinada. El resultado de este proceso es la lista de juicios que contiene todas las etiquetas de relevancia para los pares de documentos de consulta evaluados. A continuación, se explica este proceso y la interfaz para él con más detalle.</p><h3>Visión general de la interfaz de calificación humana</h3><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/be3016091b49655dab3354d84e6dc638f3468390-1283x664.png" style="width: 100%;" /><p>La interfaz de calificación humana de Quepid está diseñada para evaluaciones eficientes:</p><ul><li><strong>Consulta:</strong> Muestra el término de búsqueda.</li><li><strong>Necesidad de información:</strong> Muestra la intención del usuario.</li><li><strong>Directrices de puntaje:</strong> Proporciona instrucciones para evaluaciones consistentes.</li><li><strong>Metadatos del documento:</strong> Presenta detalles relevantes sobre el documento.</li><li><strong>Botones de valoración:</strong> Permite a los evaluadores asignar juicios con los atajos de teclado correspondientes.</li></ul><h3>Uso de la interfaz de calificación humana</h3><p>Como evaluador humano, accedo a la interfaz a través de la visión general del libro:</p><p>1. Navega a la interfaz del caso y haz clic <strong>en Sentencias</strong>.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d717c5b06ae6cb42ed2b9e771486a12f738a9890-1041x218.png" style="width: 100%;" /><p>2. <strong>¡Haz clic en Más Juicios Se Necesitan!</strong></p><p>El sistema presentará un par de consulta/documento que aún no fue valorado y que requiere juicios adicionales. Esto está determinado por la estrategia de selección del Libro:</p><ul><li><em>Evaluador único</em>: Un único juicio por par de consulta/documento.</li><li><em>Evaluadores múltiples</em>: hasta tres juicios por par de consulta/documento.</li></ul><h3>Calificación de pares de búsqueda/documento</h3><p>Vamos a repasar un par de ejemplos. Al seguir esta guía, lo más probable es que te presenten diferentes películas. Sin embargo, los principios de clasificación se mantienen igual.</p><p>Nuestro primer ejemplo es la película &quot;Heroes&quot; para la consulta <em>de Harrison Ford</em>:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c1e14b0d8b04dd579471932dbe4ff72ae5692a02-981x571.png" style="width: 100%;" /><p>Primero analizamos la consulta, seguida de la necesidad de información y después juzgamos la película en función de los metadatos proporcionados.</p><p>Esta película es un resultado relevante para nuestra consulta, ya que Harridson Ford forma parte del reparto. Puede que consideremos las películas más recientes como más relevantes subjetivamente, pero esto no forma parte de nuestra necesidad informativa. Así que calificamos este documento con &quot;Perfecto&quot;, que es un 3 en nuestra escala de calificación.</p><p>Nuestro siguiente ejemplo es la película &quot;Ford v Ferrari&quot; para la consulta <em>Harrison Ford</em>:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/052af7894506d7a765af156ba8e26ceec3559973-981x789.png" style="width: 100%;" /><p>Siguiendo la misma práctica, juzgamos esta consulta/documento analizando la consulta, la necesidad de información y luego cuán bien los metadatos del documento coinciden con la necesidad de información.</p><p>Este es un resultado pobre. Probablemente veamos este resultado como uno de nuestros términos de consulta, &quot;ford&quot;, coincide en el título. Pero Harrison Ford no tiene ningún papel en esta película, ni en ningún otro. Así que calificamos este documento como &quot;Pobre&quot;, que es un 0 en nuestra escala de calificación.</p><p>Nuestro tercer ejemplo es la película &quot;Action Jackson&quot; para la <em>mejor película de acción</em> que se pregunta:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/247ab862fbc7435537709f8c96619cb331133d09-985x606.png" style="width: 100%;" /><p>Esto parece una película de acción, así que la necesidad de información está al menos parcialmente cubierta. Sin embargo, la media de votos es de 5,4 sobre 10. Y eso hace que esta película probablemente no sea la mejor de acción de nuestra colección. Esto me llevaría, como juez, a calificar este documento como &quot;Justo&quot;, que es un 1 en nuestra escala de calificación.</p><p>Estos ejemplos ilustran el proceso de valorar pares de consulta/documento con Quepid en individua, tanto a nivel general como en general.</p><h2>Mejores prácticas para evaluadores humanos</h2><p>Los ejemplos mostrados pueden hacer que parezca fácil llegar a juicios explícitos. Pero establecer un programa fiable de valoración humana no es tarea fácil. Es un proceso lleno de desafíos que pueden comprometer fácilmente la calidad de tus datos:</p><ul><li>Los evaluadores humanos pueden fatigar por tareas repetitivas.</li><li>Las preferencias personales pueden sesgar los juicios.</li><li>Los niveles de experiencia en el sector varían de un juez a otro.</li><li>Los evaluadores suelen compaginar múltiples responsabilidades.</li><li>La relevancia percibida de un documento puede no coincidir con su verdadera relevancia para una consulta.</li></ul><p>Estos factores pueden dar lugar a juicios inconsistentes y de baja calidad. Pero no te preocupes: existen buenas prácticas probadas que pueden ayudarte a minimizar estos problemas y construir un proceso de evaluación más estable y fiable:</p><ul><li><strong>Evaluación constante:</strong> Revisa la consulta, la necesidad de información y los metadatos del documento en orden.</li><li><strong>Consulte las Directrices:</strong> Emplea directrices de puntaje para mantener la consistencia. Las directrices de puntaje pueden incluir ejemplos de cuándo aplicar cada nota, lo que ilustra el proceso de evaluación. Tener una consulta con evaluadores humanos tras la primera tanda de sentencias resultó ser una buena práctica para aprender sobre casos límite difíciles y dónde se necesita apoyo adicional.</li><li><strong>Aprovecha las opciones:</strong> Si tienes dudas, emplea &quot;Juzgaré más tarde&quot; o &quot;No puedo saberlo&quot;, proporcionando explicaciones cuando sea necesario.</li><li><strong>Toma descansos:</strong> Las pausas regulares ayudan a mantener la calidad del juicio. Quepid ayuda con los descansos regulares haciendo estallar confeti cada vez que un evaluador humano termina un serial de juicios.</li></ul><p>Siguiendo estos pasos, estableces un enfoque estructurado y colaborativo para crear listas de juicios en Quepid, mejorando la eficacia de tus esfuerzos de optimización de relevancia en búsqueda.</p><h2>Pasos siguientes</h2><p>¿A dónde ir a partir de aquí? Las listas de juicio son solo un paso fundamental para mejorar la calidad de los resultados de búsqueda. Aquí están los siguientes pasos:</p><h3>Calcula métricas y comienza a experimentar</h3><p>Una vez que hay listas de juicios disponibles, aprovechar dichos juicios y calcular <a href="https://opensourceconnections.com/blog/2020/02/28/choosing-your-search-relevance-metric/">métricas de calidad de búsqueda</a> es una progresión natural. Quepid calcula automáticamente la métrica configurada para el caso actual cuando hay sentencias disponibles. Las métricas se implementan como &quot;Puntuadores&quot; y puedes proporcionar las tuyas propias cuando las compatibles no incluyen a tu favorito.</p><p>Ve a la interfaz del caso, navega hasta <strong>Seleccionar Anotador</strong>, <em>elige DCG@10</em> y confirma haciendo clic en <strong>Seleccionar Anotador</strong>. Quepid ahora calculará DCG@10 por consulta y también promediará el número total de consultas para cuantificar la calidad de los resultados de búsqueda de tu caso.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0ff3b9918342071744d681a43d542102e927abd3-1163x551.png" style="width: 100%;" /><p>Ahora que la calidad de los resultados de búsqueda está cuantificada, puedes realizar los primeros experimentos. La experimentación comienza generando hipótesis. Mirar las tres consultas en la captura de pantalla tras hacer algunas valoraciones queda claro que las tres consultas rinden de forma muy diferente en cuanto a la métrica de calidad de búsqueda: <em>Star Wars</em> funciona bastante bien, <em>Harrison Ford</em> parece aceptable pero el mayor potencial está en <em>la mejor película de acción</em>.</p><p>Ampliando esta consulta, vemos sus resultados y podemos profundizar en los detalles más minuciosos y explorar por qué los documentos coincidieron y qué influye en sus puntajes:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c45c6cae085a492198c0f8b7060a1a7204e3724e-1131x691.png" style="width: 100%;" /><p>Al hacer clic en &quot;Explicar la consulta&quot; y entrar en la pestaña &quot;Análisis sintáctico&quot; vemos que la consulta es una búsqueda de DisjunctionMaxxQuery en tres campos: <em>cast</em>, <em>resumen</em> y <em>título</em>:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4a1e1bb2a9cd28e9c48e0ba16357d17ed9d3a5cf-894x557.png" style="width: 100%;" /><p>Normalmente, como ingenieros de búsqueda, conocemos algunos detalles específicos de nuestro dominio sobre nuestra plataforma de búsqueda. En este caso, puede que sepamos que tenemos un campo <em>de géneros</em> . Vamos a agregar eso a la consulta y ver si la calidad de búsqueda mejora.</p><p>Usamos el <strong>Sandbox de Consultas</strong> que se abre al seleccionar <strong>Relevancia de Ajuste</strong> en la interfaz de casos. Adelante, explora esto agregando el campo <em>de géneros</em> en el que busques:</p><pre><code>{
  "query": {
    "multi_match": {
      "query": "#$query##",
      "type": "best_fields",
      "fields": [
        "title^10",
        "overview",
        "cast",
        "genres"
      ]
    }
  }
}</code></pre><p>¡Haz clic en Volver a Ejecutar Mis Búsquedas! Y mira los resultados. ¿Cambiaron? Desgraciadamente no. Ahora tenemos muchas opciones para explorar, básicamente todas las opciones de consulta que ofrece Elasticsearch:</p><ul><li>Podríamos aumentar el peso del campo en el campo de géneros.</li><li>Podríamos agregar una función que aumente los documentos por su media de votos.</li><li>Podríamos crear una consulta más compleja que solo mejore los documentos por su promedio de votos si hay una coincidencia fuerte de géneros.</li><li>…</li></ul><p>Lo mejor de tener todas estas opciones y explorarlas en Quepid es que tenemos una forma de cuantificar los efectos no solo en la única consulta que intentamos mejorar, sino en todas las consultas que tenemos en nuestro caso. Eso nos impide mejorar una consulta que no rinde sacrificando la calidad de los resultados de búsqueda para otras. Podemos iterar rápida y barata y validar el valor de nuestra hipótesis sin ningún riesgo, haciendo de la experimentación offline una capacidad fundamental de todos los equipos de búsqueda.</p><h3>Mide la confiabilidad entre evaluadores</h3><p>Incluso con descripciones de tareas, necesidades de información y una interfaz de evaluador humano como la que ofrece Quepid, los evaluadores humanos pueden discrepar.</p><p>El desacuerdo en sí mismo no es algo malo, todo lo contrario: medir el desacuerdo puede sacar a la luz cuestiones que quizá quieras abordar. La relevancia puede ser subjetiva, las consultas pueden ser ambiguas y los datos pueden ser incompletos o incorrectos. <a href="https://en.wikipedia.org/wiki/Fleiss%27_kappa">El Kappa de Fleiss</a> es una medida estadística del acuerdo entre evaluadores y hay un cuaderno de ejemplo en Quepid que puedes usar. Para encontrarlo, selecciona <strong>Cuadernos</strong> en la navegación superior y selecciona el cuaderno <strong>Fleiss Kappa.ipynb</strong> en la carpeta <strong>de ejemplos</strong> .</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f0669ae96371368ef4d84bb28669560ef09d755c-624x61.png" style="width: 100%;" /><h2>Conclusión</h2><p>Quepid te permite afrontar incluso los retos de relevancia en búsquedas más complejos y sigue evolucionando: <a href="https://github.com/o19s/quepid/blob/main/CHANGELOG.md#800----2024-02-14">desde la versión 8, Quepid soporta juicios generados por IA</a>, lo cual es especialmente útil para equipos que quieren escalar su proceso de generación de juicios.</p><p>Los flujos de trabajo quepid te permiten crear listas de juicios escalables de forma eficiente, lo que finalmente resulta en resultados de búsqueda que realmente satisfacen las necesidades de los usuarios. Con listas de juicios establecidas, tienes una base estable para medir la relevancia en las búsquedas, iterar mejoras y mejorar la experiencia de usuario.</p><p>A medida que avanzas, recuerda que la afinación de la relevancia es un proceso continuo. Las listas de juicio te permiten evaluar sistemáticamente tu progreso, pero son más poderosos cuando se combinan con experimentación, análisis métrico y mejoras iterativas.</p><h2>Lecturas adicionales</h2><ul><li>Documentos de Quepid:<ul><li><a href="https://quepid-docs.dev.o19s.com/2/quepid/32/relevancy-is-a-team-sport">La relevancia es un deporte de equipo</a></li><li><a href="https://quepid-docs.dev.o19s.com/2/quepid/18/quepid-for-human-raters">Quepid para evaluadores humanos</a></li><li><a href="https://quepid-docs.dev.o19s.com/2/quepid/49/how-to-connect-quepid-to-elastic-cloud">Cómo conectar Quepid a Elastic Cloud</a></li></ul></li><li><a href="https://github.com/o19s/quepid">Repositorio de Quepid en Github</a></li><li><a href="https://opensourceconnections.com/blog/2020/07/07/meet-pete-the-e-commerce-search-product-manager/">Conoce a Pete, un serial de blogs sobre cómo mejorar la búsqueda en comercio electrónico</a></li><li><a href="https://opensourceconnections.com/slack">Relevance Slack</a>: únete al canal #quepid</li></ul><p><strong>Colabora con <a href="https://opensourceconnections.com/">Open Source Connections</a></strong> para transformar tus capacidades de búsqueda e inteligencia artificial y empoderar a tu equipo para que las evolucione continuamente. Nuestro historial probado abarca todo el mundo, con clientes que logran de forma constante mejoras notables en la calidad de búsqueda, la capacidad del equipo y el rendimiento empresarial. <a href="https://opensourceconnections.com/contact/">Contacta con nosotros hoy</a> mismo para obtener más información.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Visualización de campos en un índice de Elasticsearch]]></title>
            <description><![CDATA[Explorando técnicas para mostrar campos en un índice de Elasticsearch.
]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/displaying-fields-in-an-elasticsearch-index</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/displaying-fields-in-an-elasticsearch-index</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Kofi Bartlett]]></dc:creator>
            <pubDate>Mon, 26 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fa386d7bbaeab6855e62897ace8d7dca91a060b4-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>En este artículo, hablaremos de cómo mostrar campos en un índice de Elasticsearch. Esto puede ser útil para entender la estructura de tus datos, identificar campos específicos y solucionar problemas. Vamos a tratar los siguientes temas:</p><ol><li><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#1.-using-the--mapping-api-to-retrieve-field-information">Uso de la </a>API<a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#1.-using-the--mapping-api-to-retrieve-field-information"><code>_mapping</code> para recuperar información de campo</a></li><li><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#2.-using-the--search-api-to-display-field-values">Uso de la </a>API<a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#2.-using-the--search-api-to-display-field-values"><code>_search</code> para mostrar los valores de los campos</a></li><li><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#3.-filtering-fields-using-the-fields-parameter">Filtrado de campos usando el  parámetrofields</a></li><li><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#4.-displaying-nested-fields">Visualización de campos anidados</a></li></ol><h2>1. Uso de la API _mapping para recuperar información de campo</h2><p>La API <code>_mapping</code> permite recuperar la definición de mapeo para un índice o varios <a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-index/">índices</a>. Esto incluye información sobre los campos, sus tipos de datos y otras propiedades. Para recuperar el mapeo de un índice específico, emplee la siguiente petición:</p><pre><code>GET /<index_name>/_mapping</code></pre><p>Por ejemplo, si tienes un índice llamado <code>my_index</code>, puedes recuperar su mapeo con la siguiente petición:</p><pre><code>GET /my_index/_mapping</code></pre><p>La respuesta incluirá la definición de mapeo para el índice, que contiene información sobre los campos y sus propiedades.</p><p>También es posible recuperar el mapeo de un campo específico. Esto puede ser útil si tu mapeo es bastante grande y solo quieres centrarte en un campo específico. Para recuperar el mapeo de un campo específico, emplee la siguiente petición:</p><pre><code>GET /my_index/_mapping/field/my_field</code></pre><p>También puedes recuperar los mapeos de varios campos separando sus nombres con comas, como en la siguiente petición:</p><pre><code>GET /my_index/_mapping/field/my_field_1,my_field_2,my_field_3</code></pre><h2>2. Uso de la API _search para mostrar los valores de los campos</h2><p>Para mostrar los valores de los campos en un índice de Elasticsearch, puedes usar la API <code>_search</code> . Por defecto, la API <code>_search</code> devuelve el campo <code>_source</code> , que contiene el documento JSON original que se indexó. Para mostrar solo campos específicos, puedes usar el parámetro <code>_source</code> en la solicitud de búsqueda.</p><p>Aquí tienes un ejemplo de una solicitud de búsqueda que devuelve los valores de los campos <code>title</code> y <code>author</code> para documentos en el índice <code>my_index</code> :</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "_source": ["title", "author"]
}</code></pre><p>En este ejemplo, el parámetro <code>_source</code> especifica los campos que se deben devolver.</p><h2>3. Filtrado de campos usando el parámetro de campos</h2><p>También puedes usar el parámetro <code>fields</code> para filtrar los campos que aparecen en la respuesta de búsqueda. Esto puede ser útil si solo necesitas campos específicos y quieres reducir el tamaño de la respuesta. El parámetro <code>fields</code> acepta una matriz de nombres de campos o patrones comodines.</p><p>Por ejemplo, para devolver solo los campos <code>title</code> y <code>author</code> de los documentos en el índice de <code>my_index</code> , puedes usar la siguiente solicitud de búsqueda:</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author"],
  "_source": false
}</code></pre><p>Ten en cuenta que el parámetro <code>_source</code> está configurado como falso para no devolver el documento fuente.</p><p>Para devolver todos los campos con un <code>text</code> tipo de dato, puedes usar un patrón comodín como este:</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["*.text"],
  "_source": false
}</code></pre><h2>4. Visualización de campos anidados</h2><p>Si tu índice contiene campos anidados, puedes usar la notación de puntos para especificar el camino de campo anidado en el parámetro <code>fields</code> . Por ejemplo, si tienes un campo anidado llamado <code>address.city</code>, puedes incluirlo en la respuesta de búsqueda así:</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author", "address.city"],
  "_source": false
}</code></pre><p>En este ejemplo, la respuesta de búsqueda incluirá los valores de los campos <code>title</code>, <code>author</code> <code>address.city</code> .</p><h2>Conclusión</h2><p>En conclusión, se puede lograr mostrar campos en un índice de Elasticsearch empleando la API <code>_mapping</code> para recuperar información de campos y la API <code>_search</code> para mostrar los valores de campo. Puedes filtrar los campos que aparecen en la respuesta de búsqueda usando los parámetros de <code>_source</code> o <code>fields</code> y mostrar los campos anidados usando la notación de puntos. Estas técnicas pueden ayudarte a entender la estructura de tus datos, identificar campos específicos y solucionar problemas.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Spring AI y Elasticsearch como tu base de datos vectorial]]></title>
            <description><![CDATA[Aprende a construir una app RAG lista para producción usando Spring AI y Elasticsearch e integra los LLM con tus datos propietarios mediante una Base de datos vectorial.
]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/spring-ai-elasticsearch-application</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/spring-ai-elasticsearch-application</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Philipp Krenn, Laura Trotta]]></dc:creator>
            <pubDate>Tue, 20 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0771fb5b3d9234697cb868cd7d9d1b840000bf29-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><strong>Spring AI</strong> ya está disponible en general, con su primera <a href="https://spring.io/blog/2025/05/20/spring-ai-1-0-GA-released">versión estable 1.0</a> lista para que la descargues en <a href="https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-core">Maven Central</a>. Vamos a usarla de inmediato para construir una aplicación de IA completa, usando tu <a href="https://www.elastic.co/what-is/large-language-models">LLM</a> favorito y nuestra base de <a href="https://www.elastic.co/elasticsearch/vector-database">datos vectorial</a> favorita. O lanzarte directamente al <a href="https://github.com/xeraa/rag-with-java-springai-elasticsearch">repositorio</a> con la aplicación final.</p><a href="https://www.youtube.com/watch/ms2xRO8B1YU">Watch video on YouTube</a><h2>¿Qué es Spring AI?</h2><p><strong>Spring AI 1.0</strong>, una solución integral para la ingeniería de IA en Java, ya está disponible tras un importante periodo de desarrollo influido por los rápidos avances en el campo de la IA. La versión incluye numerosas funciones nuevas esenciales para ingenieros de IA.</p><p>Java y Spring están en una posición ideal para sumar a toda esta ola de IA. Un montón de empresas están ejecutando sus productos en Spring Boot, lo que facilita muchísimo conectar IA a lo que ya están haciendo. Básicamente puedes vincular tu lógica de negocio y datos directamente a esos modelos de IA sin demasiadas complicaciones.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/328d7c51e1c145e94ea1e73ee9ff91836d3b180e-1600x773.png" style="width: 100%;" /><p>Spring AI ofrece soporte para <a href="https://docs.spring.io/spring-ai/reference/api/index.html">diversos modelos y tecnologías de IA</a>, tales como:</p><ul><li><strong>Modelos de imagen</strong>: generan imágenes con indicaciones de texto.</li><li><strong>Modelos de transcripción</strong>: toma fuentes de audio y conviértelas en texto.</li><li><strong>Modelos de incrustación: </strong>convertir datos arbitrarios en <a href="https://www.elastic.co/what-is/vector-embedding">vectores</a>, que son tipos de datos optimizados para la búsqueda por similitud semántica.</li><li><strong>Modelos de chat: </strong>¡estos<strong> </strong>deberían ser familiares! Sin duda tuviste una breve conversación con alguno en algún sitio.</li></ul><p>Los modelos de chat son donde parece estar la mayor parte de la atención en el ámbito de la IA, y con razón, ¡son geniales! Puedes pedirles ayuda para corregir un documento o escribir un poema. (Solo no les pidas que cuenten un chiste... todavía.) Son geniales, pero tienen algunos problemas.</p><h2>Soluciones de IA de primavera a los retos de la IA</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2ebd68a90ebc73847df6ef7325936d4d06b35c8c-1600x900.jpg" style="width: 100%;" /><p>Vamos a repasar algunos de estos problemas y sus soluciones en Spring AI.</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">Problema</td><td style="border: 1px solid #ccc; padding: 4px;">Solución</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Consistencia</td><td style="border: 1px solid #ccc; padding: 4px;">Los modelos de chat son de mente abierta y propensos a distracciones</td><td style="border: 1px solid #ccc; padding: 4px;">Puedes darles un sistema de consignas para gobernar su forma y estructura general</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Memoria</td><td style="border: 1px solid #ccc; padding: 4px;">Los modelos de IA no tienen memoria, por lo que no pueden correlacionar un mensaje de un usuario dado a otro</td><td style="border: 1px solid #ccc; padding: 4px;">Puedes darles un sistema de memoria para almacenar las partes relevantes de la conversación</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Aislamiento</td><td style="border: 1px solid #ccc; padding: 4px;">Los modelos de IA viven en pequeños espacios aislados, pero pueden hacer cosas realmente asombrosas si les das acceso a herramientas, funciones que pueden invocar cuando lo consideren necesario</td><td style="border: 1px solid #ccc; padding: 4px;">Spring AI soporta la llamada a herramientas, que te permite informar al modelo de IA sobre herramientas en su entorno, que luego puede pedirte que invoques. Esta interacción de varios turnos se gestiona de forma transparente para ti</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Datos privados</td><td style="border: 1px solid #ccc; padding: 4px;">Los modelos de IA son inteligentes, ¡pero no omniscientes! No saben lo que hay en vuestras bases de datos propietarias, ¡ni creemos que querríais que lo supieran!</td><td style="border: 1px solid #ccc; padding: 4px;">Necesitas informar sus respuestas rellenando los prompts, básicamente usando el todopoderoso operador de concatenación de cadenas para poner texto en la petición antes de que el modelo mire la pregunta que se está planteando. Información de fondo, si quieres. ¿Cómo decides qué debe enviar y qué no? Emplea un almacén vectorial para seleccionar solo los datos relevantes y enviarlos a continuación. Esto se denomina generación aumentada por recuperación, o RAG</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Alucinación</td><td style="border: 1px solid #ccc; padding: 4px;">A los modelos de chat de IA les gusta de, bueno, ¡charlar! Y a veces lo hacen con tanta confianza que pueden inventar cosas</td><td style="border: 1px solid #ccc; padding: 4px;">Necesitas usar la evaluación —usando un modelo para validar la salida de otro— para confirmar resultados razonables</td>
                    </tr>
              </tbody>
            </table>
          <p></p><p>Y, por supuesto, ninguna aplicación de IA es una isla. Hoy en día, los sistemas y servicios modernos de IA funcionan mejor cuando se integran con otros sistemas y servicios. <strong><a href="https://modelcontextprotocol.io/introduction">El Protocolo de Contexto de Modelo</a> </strong>(MCP) permite conectar tus aplicaciones de IA con otros servicios basados en MCP, independientemente del lenguaje en el que estén escritos. Puedes ensamblar todo esto en flujos <strong>de trabajo agenticos </strong>que manejan hacia un objetivo mayor.</p><p>¿Lo mejor? Todo esto puedes hacer mientras construyes sobre los modismos y abstracciones familiares que cualquier desarrollador de Spring Boot llegó a esperar: dependencias de inicio convenientes para prácticamente todo están disponibles en <strong><a href="https://start.spring.io">el Inicializador de Spring</a>.</strong></p><p>Spring AI ofrece configuraciones automáticas convenientes de Spring Boot que te dan la configuración de convención sobre configuración que ya conoces y esperas. Y Spring AI soporta observabilidad con el Actuator de Spring Boot y el proyecto Micrometer. También funciona bien con GraalVM y los hilos virtuales, permitiéndote crear aplicaciones de IA súper rápidas y eficientes que escalan.</p><h2>Por qué Elasticsearch</h2><p>Elasticsearch es un motor de búsqueda de texto completo, probablemente lo sepas. ¿Entonces por qué lo estamos usando para este proyecto? ¡Pues <em>también</em> es una tienda vectorial! Y bastante bueno, donde los datos están junto al texto completo. Otros beneficios notables:</p><ul><li>Súper fácil de configurar</li><li>Código abierto</li><li>Escalable horizontalmente</li><li>La mayoría de los datos libres de tu organización probablemente ya estén en un clúster de Elasticsearch</li><li>Cuenta con capacidad completa de motor de búsqueda</li><li>¡ <a href="https://docs.spring.io/spring-ai/reference/api/vectordbs/elasticsearch.html">Totalmente integrado en Spring AI</a>!</li></ul><p>Teniendo todo en cuenta, Elasticsearch cumple con todos los requisitos para ser un excelente almacén vectorial, así que vamos a configurarlo y empezar a construir nuestra aplicación.</p><h2>Primeros pasos con Elasticsearch</h2><p>Vamos a necesitar tanto Elasticsearch como Kibana, la consola de interfaz que usarás para interactuar con los datos alojados en la base de datos.</p><p>Puedes probar todo en tu máquina local gracias a la calidad de las imágenes de Docker y la <a href="http://elastic.co">página principal Elastic.co</a>. Ve allí, baja para encontrar el comando <code>curl</code> , ejecuta y canaliza directamente a tu carcasa:</p><pre><code> curl -fsSL https://elastic.co/start-local | sh 
  ______ _           _   _      
 |  ____| |         | | (_)     
 | |__  | | __ _ ___| |_ _  ___ 
 |  __| | |/ _` / __| __| |/ __|
 | |____| | (_| \__ \ |_| | (__ 
 |______|_|\__,_|___/\__|_|\___|
-------------------------------------------------
🚀 Run Elasticsearch and Kibana for local testing
-------------------------------------------------
ℹ️  Do not use this script in a production environment
⌛️ Setting up Elasticsearch and Kibana v9.0.0...
- Generated random passwords
- Created the elastic-start-local folder containing the files:
  - .env, with settings
  - docker-compose.yml, for Docker services
  - start/stop/uninstall commands
- Running docker compose up --wait
[+] Running 25/26
 ✔ kibana_settings Pulled                                                 16.7s 
 ✔ kibana Pulled                                                          26.8s 
 ✔ elasticsearch Pulled                                                   17.4s                                                                     
[+] Running 6/6
 ✔ Network elastic-start-local_default             Created                 0.0s 
 ✔ Volume "elastic-start-local_dev-elasticsearch"  Created                 0.0s 
 ✔ Volume "elastic-start-local_dev-kibana"         Created                 0.0s 
 ✔ Container es-local-dev                          Healthy                12.9s 
 ✔ Container kibana_settings                       Exited                 11.9s 
 ✔ Container kibana-local-dev                      Healthy                21.8s 
🎉 Congrats, Elasticsearch and Kibana are installed and running in Docker!
🌐 Open your browser at http://localhost:5601
   Username: elastic
   Password: w1GB15uQ
🔌 Elasticsearch API endpoint: http://localhost:9200
🔑 API key: SERqaGlKWUJLNVJDODc1UGxjLWE6WFdxSTNvMU5SbVc5NDlKMEhpMzJmZw==
Learn more at https://github.com/elastic/start-local
➜  ~ </code></pre><p>Esto simplemente extraerá y configurará imágenes Docker para Elasticsearch y Kibana, y tras unos minutos las tendrás funcionando en tu máquina local, con credenciales de conexión completas.</p><p>También tienes dos URLs diferentes que puedes usar para interactuar con tu instancia de Elasticsearch. Haz lo que indica el aviso y apunta tu navegador a <a href="http://localhost:5601">http://localhost:5601</a>.</p><p>Fíjate también en el nombre de usuario <code>elastic</code> y la contraseña impresos en la consola: los necesitarás para iniciar sesión (en el ejemplo de arriba son respectivamente <code>elastic</code> y <code>w1GB15uQ</code>).</p><p></p><h2>Preparando la app</h2><p>Ve a la página <a href="https://start.spring.io">de Spring Initializr</a> y genera un nuevo proyecto de Spring AI con las siguientes dependencias:</p><ul><li><code>Elasticsearch Vector Store</code></li><li><code>Spring Boot Actuator</code></li><li><code>GraalVM</code></li><li><code>OpenAI</code></li><li><code>Web</code></li></ul><p>Cerciórate de elegir la versión más reciente y mejor de Java (idealmente Java 24 - a fecha de este texto - o posterior) y la herramienta de compilación que elijas. Estamos usando Apache Maven en este ejemplo.</p><p>Haz clic <code>Generate</code> y luego descomprime el proyecto e impórtalo al IDE que elijas. (Estamos usando IntelliJ IDEA.)</p><p>Lo primero de todo: especifiquemos tus datos de conexión para tu aplicación Spring Boot. En <code>application.properties,</code> escribir lo siguiente:</p><pre><code>spring.elasticsearch.uris=http://localhost:9200
spring.elasticsearch.username=elastic
spring.elasticsearch.password=w1GB15uQ</code></pre><p>También emplearemos la capacidad de almacenamiento vectorial de Spring AI para inicializar lo que sea necesario en Elasticsearch en cuanto a estructuras de datos, así que especifica:</p><pre><code>spring.ai.vectorstore.elasticsearch.initialize-schema=true</code></pre><p>Vamos a usar <strong>OpenAI</strong> en esta demo, concretamente el <strong>Modelo de Embedding</strong> y <strong>el Modelo de Chat </strong>(siéntete libre de usar el servicio que prefieras, siempre que <a href="https://docs.spring.io/spring-ai/reference/api/embeddings.html#available-implementations">Spring AI lo soporte</a>).</p><p>El Modelo de Incrustación es necesario para crear incrustaciones de los datos antes de almacenarlos en Elasticsearch. Para que OpenAI funcione, necesitamos especificar la <code>API key</code>:</p><pre><code>spring.ai.openai.api-key=...</code></pre><p>Puedes definirlo como una variable de entorno como <code>SPRING_AI_OPENAI_API_KEY</code> para evitar almacenar la credencial en tu código fuente.</p><p>Vamos a subir archivos, así que cerciórate de personalizar cuánto dato se puede subir al contenedor de servlet:</p><pre><code>spring.servlet.multipart.max-file-size=20MB
spring.servlet.multipart.max-request-size=20MB</code></pre><p>¡Ya casi llegamos! Antes de sumergirnos en la escritura del código, vamos a ver cómo va a funcionar esto.</p><p>En nuestra máquina, descargamos el <a href="https://images-cdn.fantasyflightgames.com/filer_public/9f/aa/9faa23a3-9f71-4c77-865f-bba4aac8a258/runewars-revised-_rulebook.pdf">siguiente archivo</a> (una lista de reglas para un juego de mesa), lo renombramos a <code>test.pdf</code> y lo pusimos en <code>~/Downloads/test.pdf</code>.</p><p>El archivo se enviará al punto final <code>/rag/ingest</code> (reemplaza la ruta según tu configuración local):</p><pre><code>http --form POST http://localhost:8080/rag/ingest path@/Users/jlong/Downloads/test.pdf</code></pre><p>Esto puede tardar unos segundos...</p><p>Entre bastidores, los datos se envían a OpenAI, que crea incrustaciones de los datos; esos datos se escriben entonces en Elasticsearch, tanto los vectores como el texto original.</p><p>Esos datos, junto con todas las incrustaciones que contienen, es donde ocurre la magia. Luego podemos consultar Elasticsearch usando la interfaz <code>VectorStore</code> .</p><p>El flujo completo es así:</p><ul><li>El cliente HTTP sube el PDF que elijas a la aplicación Spring.</li><li>Spring AI se encarga de la extracción de texto de nuestro PDF y divide cada página en fragmentos de 800 caracteres.</li><li>OpenAI genera la representación vectorial para cada bloque.</li><li>Tanto el texto fragmentado como la incrustación se almacenan entonces en Elasticsearch.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9734adb2d7128e61c515d5855dfad6d3a326a4a1-1454x706.png" style="width: 100%;" /><p>Por último, haremos una consulta:</p><pre><code>http :8080/rag/query question=="where do you place the reward card after obtaining it?" </code></pre><p>Y obtendremos una respuesta relevante:</p><pre><code>After obtaining a Reward card, you place it facedown under the Hero card of the hero who received it.
Found at page: 28 of the manual</code></pre><p>¡Muy bien! ¿Cómo funciona todo esto?</p><ul><li>El cliente HTTP envia la pregunta a la aplicación Spring.</li><li>Spring AI obtiene la representación vectorial de la pregunta de OpenAI.</li><li>Con esa incrustación, busca documentos similares en los chunks almacenados de Elasticsearch y recupera los documentos más parecidos.</li><li>Spring AI envía entonces la pregunta y el contexto recuperado a OpenAI para generar una respuesta LLM.</li><li>Finalmente, devuelve la respuesta generada y una referencia al contexto recuperado.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3799de6e8cb13ce49b9e136cfe593263030231a8-1464x1050.png" style="width: 100%;" /><p>Vamos a profundizar en el código de Java para ver cómo funciona realmente.</p><p>Primero que nada, la clase <strong>Main</strong> : es una clase principal estándar estándar para cualquier aplicación de botas de muelle.</p><pre><code>@SpringBootApplication
public class DemoApplication {
 	public static void main(String[] args) { 
     		SpringApplication.run(DemoApplication.class, args);
 	}
}</code></pre><p>No hay nada que ver allí. Sigamos...</p><p>A continuación, un controlador HTTP básico:</p><pre><code>@RestController
class RagController {

   private final RagService rag;

   RagController(RagService rag) {
       this.rag = rag;
   }

   @PostMapping("/rag/ingest")
   ResponseEntity<?> ingestPDF(@RequestBody MultipartFile path) {
       rag.ingest(path.getResource());
       return ResponseEntity.ok().body("Done!");
   }

   @GetMapping("/rag/query")
   ResponseEntity<?> query(@RequestParam String question) {
       String response = rag.directRag(question);
       return ResponseEntity.ok().body(response);
   }
}</code></pre><p>El controlador simplemente llama a un servicio que creamos para gestionar la ingesta de archivos y escribirlos en el almacén vectorial de Elasticsearch, y luego facilitar consultas contra ese mismo almacén vectorial.</p><p>Veamos el servicio:</p><pre><code>@Service
class RagService {

   private final ElasticsearchVectorStore vectorStore;

   private final ChatClient ai;

   RagService(ElasticsearchVectorStore vectorStore, ChatClient.Builder clientBuilder) {
       this.vectorStore = vectorStore;
       this.ai = clientBuilder.build();
   }

   void ingest(Resource path) {
       PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(path);
       List<Document> batch = new TokenTextSplitter().apply(pdfReader.read());
       vectorStore.add(batch);
   }

  // TBD
}</code></pre><p>Este código gestiona toda la ingestión: dado un Framework Spring <code>Resource</code>, que es un contenedor alrededor de bytes, leemos los datos PDF (presumiblemente un archivo <code>.PDF</code> - cerciórate de validar lo máximo antes de aceptar entradas arbitrarias) usando el <code>PagePdfDocumentReader</code> de Spring AI y luego tokenizándolos usando el <code>TokenTextSplitter</code>de Spring AI ,&nbsp; Finalmente, agregando el <code>List&lt;Document&gt;</code>resultante a la implementación <code>VectorStore</code> , <code>ElasticsearchVectorStore</code>.</p><p>Puedes confirmarlo usando Kibana: luego de enviar un archivo al endpoint <code>/rag/ingest</code> , abre tu navegador para <code>localhost:5601</code> y en el menú lateral de la izquierda navega hasta <code>Dev Tools</code>. Allí puedes emitir consultas para interactuar con los datos de la instancia de Elasticsearch.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c85e522f02f8b2da7462cd428dc7e952c9692542-1600x1040.png" style="width: 100%;" /><p>Haz una consulta como esta:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/00974a176cbce11e70fcab24fb4b3f9c6e205982-1600x1040.png" style="width: 100%;" /><p>Ahora, lo divertido: ¿cómo sacamos esos datos de nuevo en respuesta a las consultas de los usuarios?</p><p>Aquí tienes un primer corte en una implementación de la consulta, en un método llamado <code>directRag</code>.</p><pre><code>String directRag(String question) {
   // Query the vector store for documents related to the question
   List<Document> vectorStoreResult =
           vectorStore.doSimilaritySearch(SearchRequest.builder().query(question).topK(5)
                   .similarityThreshold(0.7).build());

   // Merging the documents into a single string
   String documents = vectorStoreResult.stream()
           .map(Document::getText)
           .collect(Collectors.joining(System.lineSeparator()));

   // Exit if the vector search didn't find any results
   if (documents.isEmpty()) {
       return "No relevant context found. Please change your question.";
   }

   // Setting the prompt with the context
   String prompt = """
           You're assisting with providing the rules of the tabletop game Runewars.
           Use the information from the DOCUMENTS section to provide accurate answers to the
           question in the QUESTION section.
           If unsure, simply state that you don't know.
          
           DOCUMENTS:
           """ + documents
           + """
           QUESTION:
           """ + question;


   // Calling the chat model with the question
   String response = ai
           .prompt()
           .user(prompt)
           .call()
           .content();

   return response +
           System.lineSeparator() +
           "Found at page: " +
           // Retrieving the first ranked page number from the document metadata
           vectorStoreResult.getFirst().getMetadata().get(PagePdfDocumentReader.METADATA_START_PAGE_NUMBER) +
           " of the manual";

}</code></pre><p>El código es bastante sencillo, pero vamos a desglosarlo en varios pasos:</p><ol><li>Usa el <code>VectorStore</code> para realizar una búsqueda de similitud.</li><li>Con todos los resultados, consigue los <code>Document</code>AI de Spring subyacentes y extrae su texto, concatenándolos todos en un solo resultado.</li><li>Envía los resultados del <code>VectorStore</code> al modelo, junto con un prompt que indique al modelo qué hacer con ellos y la pregunta del usuario. Espera la respuesta y devuélvelo.</li></ol><p></p><p>Esto es <strong>RAG</strong> - generación aumentada por recuperación. Es la idea de que usamos datos de un almacén vectorial para informar el procesamiento y análisis que realiza el modelo. Ahora que sabes cómo hacerlo, ¡esperemos que nunca tengas que hacerlo! Al menos no así: los <a href="https://docs.spring.io/spring-ai/reference/api/advisors.html">Asesores</a> de Spring AI están aquí para simplificar aún más este proceso.</p><p>Advisors te permite preprocesar y postprocesar una solicitud a un modelo dado, además de proporcionar una capa de abstracción entre tu aplicación y el almacén vectorial. Agrega la siguiente dependencia a tu build:<br/></p><pre><code><dependency>
   <groupId>org.springframework.ai</groupId>
   <artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency></code></pre><p>Agrega otro método llamado <code>advisedRag(String question)</code> a la clase:</p><pre><code>String advisedRag(String question) {
   return this.ai
           .prompt()
           .user(question)
           .advisors(new QuestionAnswerAdvisor(vectorStore))
           .call()
           .content();
}</code></pre><p>Toda la lógica de patrón RAG está encapsulada en la <code>QuestionAnswerAdvisor</code>. ¡Todo lo demás es igual que cualquier petición a un <code>ChatModel</code> ! ¡Muy bien!</p><p>Y puedes <a href="https://github.com/xeraa/rag-with-java-springai-elasticsearch">obtener el código completo desde GitHub</a>.</p><h2>Conclusión</h2><p>En esta demostración, usamos imágenes Docker y todo lo hicimos en nuestra máquina local, pero el objetivo aquí es construir sistemas y servicios de IA aptos para producción. Hay varias cosas que podrías hacer para que eso sea realidad.</p><p>En primer lugar, puedes agregar <a href="https://docs.spring.io/spring-boot/reference/actuator/index.html#actuator">Spring Boot Actuator</a> para monitorizar el consumo de tokens. Los tokens son un indicador de la complejidad (y a veces del costo en dólares y centavos) de una solicitud dada al modelo.</p><p>Ya tienes el Actuador de Bota de Muelle en la ruta de clase, así que solo tienes que especificar las siguientes propiedades para mostrar todas las métricas (capturadas por el magnífico <a href="http://micrometer.io">proyecto Micrometer.io</a> ):</p><pre><code>management.endpoints.web.exposure.include=*</code></pre><p>Resetear tu solicitud. Haz una consulta y luego ve a: <a href="http://localhost:8080/actuator/metrics">http://localhost:8080/actuator/metrics</a>. Busca &quot;<code>token</code>&quot; y verás información sobre los tokens que está usando la aplicación. Cerciórate de estar atento a esto. Por supuesto, puedes usar la <a href="https://docs.micrometer.io/micrometer/reference/implementations/elastic.html">integración de Micrometer para Elasticsearch</a> para impulsar esas métricas y que Elasticsearch actúe también como tu base de datos de seriales temporales preferida.</p><p>Entonces deberías considerar que cada vez que hacemos una petición a un almacén de datos como Elasticsearch, o a OpenAI, u otros servicios de red, estamos haciendo IO y —a menudo— ese IO bloquea los hilos sobre los que se ejecuta. Java 21 y posteriores incluyen <strong>hilos virtuales</strong> que no bloquean y mejoran significativamente la escalabilidad. Habilitarlo con:<br/></p><pre><code>spring.threads.virtual.enabled=true</code></pre><p>Y, por último, querrás alojar tu aplicación y tus datos en un lugar donde puedan prosperar y escalar. Estamos seguros de que probablemente ya pensaste dónde ejecutar tu aplicación, pero ¿dónde alojarás tus datos? ¿Podemos recomendar el <a href="https://cloud.elastic.co/">Elastic Cloud</a>? Es seguro, privado, escalable y lleno de funciones. ¿Nuestra parte favorita? Si quieres, puedes conseguir la edición Serverless donde Elastic lleva el buscapersonas, ¡no tú!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch en JavaScript de la manera correcta, parte II]]></title>
            <description><![CDATA[Aprende sobre las mejores prácticas de producción y cómo ejecutar el cliente de Node.js de Elasticsearch en entornos sin servidor para reducir errores de código. ]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/how-to-use-elasticsearch-in-javascript-part-ii</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/how-to-use-elasticsearch-in-javascript-part-ii</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
            <pubDate>Mon, 19 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/70fb0ff949f6db9ac9b8a28ecb4329ab915ebf46-720x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Esta es la segunda parte de nuestro serial Elasticsearch en JavaScript. En la<a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i"> primera parte,</a> aprendimos a configurar correctamente nuestro entorno, configurar el cliente Node.js, indexar datos y buscar. En esta segunda parte, aprenderemos a implementar las mejores prácticas de producción y a ejecutar el cliente <a href="http://node.js">de Node.js</a> Elasticsearch en entornos sin servidor.</p><p>Revisaremos:</p><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#production-best-practices">Mejores prácticas de producción</a><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#error-handling">Manejo de errores</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#testing">Pruebas</a></li></ul></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#serverless-environments">Entornos sin servidor</a><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#running-the-client-on-elastic-serverless">Ejecutando el cliente en Elastic Serverless</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#running-the-client-on-function-as-a-service-environment">Ejecutando el cliente en un entorno de función como servicio</a></li></ul></li></ul><p><em>Puedes consultar el código fuente con los ejemplos <strong><a href="https://github.com/Delacrobix/JS-client-best-practices_article">aquí</a>.</strong></em></p><h2>Mejores prácticas de producción</h2><h3>Manejo de errores en Elasticsearch</h3><p>Una característica útil del cliente Elasticsearch en Node.js es que expone objetos para los posibles errores en Elasticsearch, de modo que puedes validarlos y manejarlos de diferentes maneras.</p><p>Para <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/javascript/connecting#client-error-handling">verlos todos</a>, pon esto: </p><pre><code>const { errors } = require('@elastic/elasticsearch')
console.log(errors)</code></pre><p>Volvamos al ejemplo de búsqueda y tratemos algunos de los posibles errores:</p><pre><code>app.get("/search/lexic", async (req, res) => {
 ....
  } catch (error) {
    if (error instanceof errors.ResponseError) {
      let errorMessage =
        "Response error!, query malformed or server down, contact the administrator!";

      if (error.body.error.type === "parsing_exception") {
        errorMessage = "Query malformed, make sure mappings are set correctly";
      }

      res.status(error.meta.statusCode).json({
        erroStatus: error.meta.statusCode,
        success: false,
        results: null,
        error: errorMessage,
      });
    }

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});</code></pre><p><code>ResponseError</code> en individuo, ocurrirá cuando la respuesta sea <code>4xx</code> o <code>5xx</code>, lo que significa que la solicitud es incorrecta o el servidor no está disponible.</p><p>Podemos probar este tipo de error generando consultas erróneas, como intentar <strong>hacer una consulta de términos en un campo de tipo texto:</strong></p><p>Error por defecto:</p><pre><code> {
    "success": false,
    "results": null,
    "error": "parsing_exception\n\tRoot causes:\n\t\tparsing_exception: [terms] query does not support [visit_details]"
}</code></pre><p>Error personalizado: </p><pre><code>{
    "erroStatus": 400,
    "success": false,
    "results": null,
    "error": "Response error!, query malformed or server down; contact the administrator!"
}</code></pre><p>También podemos capturar y manejar cada tipo de error de una manera determinada. Por ejemplo, podemos agregar lógica de reintentos en un <code>TimeoutError</code>.</p><pre><code>app.get("/search/semantic", async (req, res) => {
    try {
  ...
  } catch (error) {
    if (error instanceof errors.TimeoutError) {


     // Retry logic...

      res.status(error.meta.statusCode).json({
        erroStatus: error.meta.statusCode,
        success: false,
        results: null,
        error:
          "The request took more than 10s after 3 retries. Try again later.",
      });
    }
  }
});</code></pre><h3>Pruebas</h3><p>Las pruebas son clave para garantizar la estabilidad de la aplicación. Para probar el código de forma aislada de Elasticsearch, podemos usar la biblioteca <a href="https://github.com/elastic/elasticsearch-js-mock">elasticsearch-js-mock</a> al crear nuestro clúster.</p><p>Esta biblioteca nos permite instanciar un cliente muy similar al real, pero que responderá a nuestra configuración reemplazando solo la capa HTTP del cliente por una mock, manteniendo el resto igual que el original.</p><p>Instalaremos la biblioteca de simulacros y <a href="https://github.com/avajs/ava">AVA</a> para los exámenes automatizados.</p><p><code>npm install @elastic/elasticsearch-mock</code></p><p><code>npm install --save-dev ava</code></p><p>Configuraremos el archivo <code>package.json</code> para ejecutar las pruebas. Cerciórate de que se vea así:</p><pre><code>"type": "module",
	"scripts": {
		"test": "ava"
	},
	"devDependencies": {
		"ava": "^5.0.0"
	}</code></pre><p>Ahora creemos un archivo <code>test.js</code> e instalemos nuestro cliente simulado:</p><pre><code>const { Client } = require('@elastic/elasticsearch')
const Mock = require('@elastic/elasticsearch-mock')

const mock = new Mock()
const client = new Client({
  node: 'http://localhost:9200',
  Connection: mock.getConnection()
})</code></pre><p>Ahora, agrega un simulacro para la búsqueda semántica:</p><pre><code>function createSemanticSearchMock(query, indexName) {
  mock.add(
    {
      method: "POST",
      path: `/${indexName}/_search`,
      body: {
        query: {
          semantic: {
            field: "semantic_field",
            query: query,
          },
        },
      },
    },
    () => {
      return {
        hits: {
          total: { value: 2, relation: "eq" },
          hits: [
            {
              _id: "1",
              _score: 0.9,
              _source: {
                owner_name: "Alice Johnson",
                pet_name: "Buddy",
                species: "Dog",
                breed: "Golden Retriever",
                vaccination_history: ["Rabies", "Parvovirus", "Distemper"],
                visit_details:
                  "Annual check-up and nail trimming. Healthy and active.",
              },
            },
            {
              _id: "2",
              _score: 0.7,
              _source: {
                owner_name: "Daniel Kim",
                pet_name: "Mochi",
                species: "Rabbit",
                breed: "Mixed",
                vaccination_history: [],
                visit_details:
                  "Nail trimming and general health check. No issues.",
              },
            },
          ],
        },
      };
    }
  );
}</code></pre><p>Ahora podemos crear una prueba para nuestro código, cerciorándonos de que la parte de Elasticsearch siempre devolverá los mismos resultados:</p><pre><code>import test from 'ava';

test("performSemanticSearch must return formatted results correctly", async (t) => {
  const indexName = "vet-visits";
  const query = "Which pets had nail trimming?";

  createSemanticSearchMock(query, indexName);

  async function performSemanticSearch(esClient, q, indexName = "vet-visits") {
    try {
      const result = await esClient.search({
        index: indexName,
        body: {
          query: {
            semantic: {
              field: "semantic_field",
              query: q,
            },
          },
        },
      });

      return {
        success: true,
        results: result.hits.hits,
      };
    } catch (error) {
      if (error instanceof errors.TimeoutError) {
        return {
          success: false,
          results: null,
          error: error.body.error.reason,
        };
      }

      return {
        success: false,
        results: null,
        error: error.message,
      };
    }
  }

  const result = await performSemanticSearch(esClient, query, indexName);

  t.true(result.success, "The search must be successful");
  t.true(Array.isArray(result.results), "The results must be an array");

  if (result.results.length > 0) {
    t.true(
      "_source" in result.results[0],
      "Each result must have a _source property"
    );
    t.true(
      "pet_name" in result.results[0]._source,
      "Results must include the pet_name field"
    );
    t.true(
      "visit_details" in result.results[0]._source,
      "Results must include the visit_details field"
    );
  }
});</code></pre><p>Hagamos las pruebas.</p><p><code>npm run test</code></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/42feae845ae8eae03c37ad7ad114e8db35984812-1186x302.png" style="width: 100%;" /><p>¡Hecho! A partir de ahora, podemos probar nuestra app centrándonos al 100% en el código y no en factores externos.</p><h2>Entornos sin servidor</h2><h3>Cómo ejecutar el cliente en Elastic Serverless</h3><p>Cubrimos la ejecución de Elasticsearch en la nube o local; sin embargo, el cliente Node.js también soporta conexiones a <a href="https://www.elastic.co/guide/en/serverless/current/intro.html">Elastic Cloud Serverless</a>.</p><p>Elastic Cloud Serverless te permite crear un proyecto donde no tienes que preocuparte por la infraestructura, ya que Elastic se encarga de eso internamente, y solo tienes que preocuparte por los datos que quieres indexar y cuánto tiempo quieres tener acceso a ellos.</p><p>Desde la perspectiva del uso, los servidores sin servidor desacoplan el cálculo del almacenamiento, proporcionando funciones de escalado automático tanto para <a href="https://www.elastic.co/search-labs/blog/elasticsearch-serverless-tier-autoscaling">búsqueda</a> como <a href="https://www.elastic.co/search-labs/blog/elasticsearch-ingest-autoscaling">para indexación</a>. Esto te permite solo hacer crecer los recursos que realmente necesitas.</p><p>El cliente realiza las siguientes adaptaciones para conectarse a Serverless:</p><ul><li>Apaga el olfateo e ignora cualquier opción relacionada con el olfateo</li><li>Ignora todos los nodos que pasan en la configuración excepto el primero, e ignora cualquier filtrado de nodos y seleccionando opciones</li><li>Activa la compresión y el &#x27;TLSv1_2_method&#x27; (igual que cuando se configura para Elastic Cloud)</li><li>Agrega un encabezado HTTP &#x27;elastic-api-version&#x27; a todas las solicitudes</li><li>Emplea &#x27;CloudConnectionPool&#x27; por defecto en lugar de &#x27;WeightedConnectionPool&#x27;</li><li>Desactiva los encabezados &#x27;content-type&#x27; y &#x27;accept&#x27; que venden en favor de los tipos MIME estándar</li></ul><p>Para conectar tu proyecto serverless, necesitas usar el parámetro serverMode: serverless.</p><pre><code>const { Client } = require('@elastic/elasticsearch')
const client = new Client({
  node: 'ELASTICSEARCH_ENDPOINT',
  auth: { apiKey: 'ELASTICSEARCH_API_KEY' },
  serverMode: "serverless",
});</code></pre><h3>Cómo ejecutar el cliente en un entorno de función como servicio</h3><p>En el ejemplo, usamos un servidor Node.js, pero también puedes conectarte usando un entorno de función como servicio con funciones como AWS Lambda, GCP Run, etc.</p><pre><code>'use strict'

const { Client } = require('@elastic/elasticsearch')

const client = new Client({
  // client initialisation
})

exports.handler = async function (event, context) {
  // use the client
}</code></pre><p>Otro ejemplo es conectarse a servicios como Vercel, que también es sin servidor. Puedes consultar este <a href="https://github.com/elastic/elasticsearch-js/blob/main/docs/examples/proxy/README.md">ejemplo completo</a> de cómo hacerlo, pero la parte más relevante del <a href="https://github.com/elastic/elasticsearch-js/blob/main/docs/examples/proxy/api/search.js">endpoint de búsqueda</a> es la siguiente:</p><pre><code>const response = await client.search(
  {
    index: INDEX,
    // You could directly send from the browser
    // the Elasticsearch's query DSL, but it will
    // expose you to the risk that a malicious user
    // could overload your cluster by crafting
    // expensive queries.
    query: {
      match: { field: req.body.text },
    },
  },
  {
    headers: {
      Authorization: `ApiKey ${token}`,
    },
  }
);</code></pre><p>Este endpoint reside en la carpeta /api y se ejecuta desde el lado del servidor, de modo que el cliente solo tiene control sobre el parámetro &quot;texto&quot; que corresponde al término de búsqueda.</p><p>La participación de usar función como servicio es que, a diferencia de un servidor que funciona 24/7, las funciones solo activan la máquina que ejecuta la función y, una vez terminada, la máquina entra en modo reposo para consumir menos recursos.</p><p>Esta configuración puede ser conveniente si la aplicación no recibe demasiadas solicitudes; De lo contrario, los costos pueden ser elevados. También tienes que considerar el <a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html">ciclo de vida de las funciones</a> y los tiempos de ejecución (que en algunos casos pueden ser solo segundos).</p><h2>Conclusión</h2><p>En este artículo, aprendimos a manejar errores, algo crucial en entornos de producción. También cubrimos pruebas de nuestra aplicación simulando el servicio Elasticsearch, que proporciona pruebas fiables independientemente del estado del clúster y nos permite centrarnos en nuestro código.</p><p>Finalmente, demostramos cómo crear una pila totalmente serverless aprovisionando tanto Elastic Cloud Serverless como una aplicación Vercel.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cómo optimizar el espacio en disco y el uso de Elasticsearch]]></title>
            <description><![CDATA[Aprende a prevenir y manejar los casos en los que el disco de Elasticsearch está demasiado lleno (sobreutilización) y cuando la capacidad del disco está infrautilizada para optimizar los costos del clúster.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/optimize-elasticsearch-disk-space-and-usage</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/optimize-elasticsearch-disk-space-and-usage</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Fri, 16 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5d41a078dd03e4480a0ff4e9591c8618b9bab4d0-720x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La gestión de discos es importante en cualquier base de datos, y Elasticsearch no es una excepción. Si no tienes suficiente espacio en disco disponible, Elasticsearch dejará de asignar fragmentos al nodo. Esto acabará impidiéndote poder escribir datos en el clúster, con el riesgo potencial de pérdida de datos en tu aplicación. Por otro lado, si tienes demasiado espacio en disco, entonces estás pagando por más recursos de los que necesitas.</p><h2>Antecedentes sobre las marcas de agua</h2><p>Hay varios umbrales de &quot;marca de agua&quot; en tu clúster de Elasticsearch que te ayudan a rastrear el espacio disponible en disco. A medida que el disco se llena en un nodo, el primer umbral que se cruzará será la &quot;marca de agua de disco bajo&quot;. El segundo umbral será entonces el &quot;umbral alto de marca de agua en disco&quot;. Finalmente, se alcanzará la &quot;fase de inundación de disco&quot;. Una vez superado este umbral, el clúster bloqueará la escritura en TODOS los índices que tengan un fragmento (primario o réplica) en el nodo que pasó la marca de agua. Las lecturas (búsquedas) seguirán siendo posibles.</p><h2>Cómo prevenir y manejar casos cuando el disco está demasiado lleno (sobreutilización)</h2><p>Existen varios métodos para gestionar casos cuando tu disco de Elasticsearch está demasiado lleno:</p><ol><li><strong>Eliminar</strong> <strong>datos antiguos:</strong> Normalmente, los datos no deben conservar indefinidamente. Una forma de evitar y solucionar que el disco esté demasiado lleno es cerciorar de que, cuando los datos alcancen cierta edad, se archiven y eliminen de forma fiable. Una forma de hacerlo es usando <a href="https://www.elastic.co/docs/manage-data/lifecycle/index-lifecycle-management">ILM</a>.</li><li><strong>Agregar capacidad de almacenamiento:</strong> Si no puedes eliminar los datos, quizá quieras agregar más nodos de datos o aumentar el tamaño de los discos para conservar todos los datos sin afectar negativamente al rendimiento. Si necesitas agregar capacidad de almacenamiento al clúster, deberías considerar si necesitas agregar solo capacidad de almacenamiento, o tanto capacidad de almacenamiento como RAM y recursos de CPU en proporción (ver la sección sobre <a href="https://www.elastic.co/search-labs/blog/optimize-elasticsearch-disk-space-and-usage#the-relationship-between-disk-size,-ram-and-cpu">la proporción entre tamaño de disco, RAM y CPU</a> más abajo).</li></ol><h2>Cómo agregar capacidad de almacenamiento a tu clúster de Elasticsearch</h2><ol><li><strong>Aumentar el número de nodos de datos: </strong>Recuerda que los nuevos nodos deben tener el mismo tamaño que los nodos existentes y la misma versión de Elasticsearch.</li><li><strong>Aumentar el tamaño de los nodos existentes: </strong>En entornos basados en la nube, suele ser fácil aumentar el tamaño del disco y la RAM/CPU en los nodos existentes.</li><li><strong>Aumenta solo el tamaño del disco: </strong>En entornos basados en la nube, a menudo es relativamente fácil aumentar el tamaño del disco.</li><li><a href="https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore"><strong>Instantánea</strong> <strong>y</strong> <strong>restauración</strong></a><strong>:</strong> Si estás dispuesto a permitir que se recuperen datos antiguos a petición en un proceso automatizado desde copias de seguridad, puedes hacer instantáneas de índices antiguos, eliminarlos y restaurar datos temporalmente a petición de las instantáneas. </li><li><strong>Reducir réplicas por fragmento:</strong> Otra opción para reducir los datos es reducir el número de réplicas de cada fragmento. Para alta disponibilidad, te gustaría tener una réplica por fragmento, pero cuando los datos envejecen, podrías trabajar sin réplicas. Esto normalmente podría funcionar si los datos son persistentes o si tienes una copia de seguridad que restaurar si es necesario.</li><li><strong>Crea alertas:</strong> Para evitar que los discos se llenen en el futuro y actuar de forma proactiva, deberías crear alertas basadas en el uso del disco que te avisen cuando el disco empiece a llenar. </li></ol><h2>Cómo prevenir y gestionar casos en los que la capacidad del disco está infrautilizada</h2><p>Si la capacidad de tu disco está infrautilizada, existen varias opciones para reducir el volumen de almacenamiento en tu clúster.</p><h3>Cómo reducir el volumen de almacenamiento en un clúster de Elasticsearch</h3><p>Existen varios métodos para reducir el volumen de almacenamiento de un clúster.</p><p><strong>1. Reducir el número de nodos de datos</strong></p><p>Si quieres reducir el almacenamiento de datos y también reducir los recursos de RAM y CPU en la misma proporción, esta es la estrategia más sencilla. Desmantelar nodos innecesarios probablemente suponga el mayor ahorro de costos.</p><p>Antes de desactivar el nodo, deberías:</p><ul><li>Cerciorar de que el nodo a desmantelar no sea necesario como nodo MAESTRO. Siempre deberías tener al menos tres nodos con el rol de nodo MAESTRO.</li><li>Migra los fragmentos de datos fuera del nodo para ser desmantelados.</li></ul><p><strong>2. Sustituir nodos existentes por nodos más pequeños</strong></p><p>Si no puedes reducir aún más el número de nodos (normalmente 3 sería una configuración mínima), entonces quizá quieras reducir el tamaño de los nodos existentes. Recuerda que es recomendable cerciorar de que todos los nodos de datos tengan la misma memoria RAM y tamaño de disco, ya que los fragmentos se equilibran en función del número de fragmentos por nodo.</p><p>El proceso sería:</p><ul><li>Agregar nuevos nodos más pequeños al clúster</li><li>Migra los fragmentos lejos de los nodos para ser desmantelados</li><li>Apaga los nodos antiguos</li></ul><p><strong>3. Reducir el tamaño del disco en los nodos</strong></p><p>Si SOLO quieres reducir el tamaño del disco en los nodos sin cambiar la RAM o la CPU total del clúster, entonces puedes reducir el tamaño del disco para cada nodo. Reducir el tamaño del disco en un nodo Elasticsearch no es un proceso trivial.</p><p>La forma más sencilla de hacerlo suele ser:</p><ul><li>Migrar fragmentos desde el nodo</li><li>Detener el nodo</li><li>Montar un nuevo volumen de datos en el nodo con el tamaño adecuado</li><li>Copiar todos los datos del volumen de disco antiguo al volumen nuevo</li><li>Desacoplar el antiguo volumen A</li><li>Nodo inicial y migra fragmentos de vuelta a nodo</li></ul><p>Esto requiere que tengas suficiente capacidad en los otros nodos para almacenar temporalmente los fragmentos extra del nodo durante este proceso. En muchos casos, el costo de gestionar este proceso puede superar los posibles ahorros en el uso del disco. Por esta razón, puede ser más sencillo reemplazar el nodo por completo por uno nuevo con el tamaño de disco deseado (ver &quot;Sustituir nodos existentes por nodos más pequeños&quot; arriba).</p><p>Al pagar por recursos innecesarios, el costo obviamente puede reducir optimizando la utilización de los recursos.</p><h2>La relación entre el tamaño del disco, la RAM y la CPU</h2><p>La proporción ideal de capacidad de disco respecto a RAM en tu clúster dependerá de tu caso de uso particular. Por esta razón, al considerar cambios en tu capacidad de almacenamiento, también deberías considerar si las relaciones actuales de disco/RAM/CPU están adecuadamente equilibradas y si, como consecuencia, necesitas agregar o reducir RAM y CPU en la misma proporción.</p><p>Los requisitos de RAM y CPU dependen del volumen de actividad <a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-indexing/">de indexación</a> , el número y tipo de consultas, y también de la cantidad de datos que se están buscando y agregando. Esto suele ser proporcional a la cantidad de datos almacenados en el clúster y, por tanto, también debe estar relacionado con el tamaño del disco.</p><p>La proporción entre la capacidad del disco y la RAM puede cambiar según el caso de uso. Consulta algunos ejemplos aquí:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">Actividad en el índice</td><td style="border: 1px solid #ccc; padding: 4px;">Retención</td><td style="border: 1px solid #ccc; padding: 4px;">Actividad de búsqueda</td><td style="border: 1px solid #ccc; padding: 4px;">Capacidad del disco</td><td style="border: 1px solid #ccc; padding: 4px;">CARNERO</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Aplicación de búsqueda empresarial</td><td style="border: 1px solid #ccc; padding: 4px;">Ingestión moderada de logarítmic</td><td style="border: 1px solid #ccc; padding: 4px;">Largo</td><td style="border: 1px solid #ccc; padding: 4px;">Luz</td><td style="border: 1px solid #ccc; padding: 4px;">2TB</td><td style="border: 1px solid #ccc; padding: 4px;">32GB</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Monitorización de aplicaciones</td><td style="border: 1px solid #ccc; padding: 4px;">Ingesta intensiva de troncos</td><td style="border: 1px solid #ccc; padding: 4px;">Corto</td><td style="border: 1px solid #ccc; padding: 4px;">Luz</td><td style="border: 1px solid #ccc; padding: 4px;">1TB</td><td style="border: 1px solid #ccc; padding: 4px;">32GB</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Comercio electrónico</td><td style="border: 1px solid #ccc; padding: 4px;">Indexación de datos de luz</td><td style="border: 1px solid #ccc; padding: 4px;">Indefinido</td><td style="border: 1px solid #ccc; padding: 4px;">Pesado</td><td style="border: 1px solid #ccc; padding: 4px;">500GB</td><td style="border: 1px solid #ccc; padding: 4px;">32GB</td>
                    </tr>
              </tbody>
            </table>
          <p><em>Recuerda que modificar la configuración de las máquinas de nodos debe hacer con cuidado, ya que puede implicar tiempo de inactividad de nodos y debes cerciorarte de que los fragmentos no empiecen a migrar a otros nodos ya sobreextendidos.</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch en JavaScript de la manera correcta, parte I]]></title>
            <description><![CDATA[Explicando cómo crear un backend de Elasticsearch listo para producción en JavaScript.  

Explora cómo usar Elasticsearch con JavaScript para crear un servidor con diferentes endpoints de búsqueda para consultar documentos de Elasticsearch siguiendo las mejores prácticas de cliente/servidor.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/how-to-use-elasticsearch-in-javascript-part-i</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/how-to-use-elasticsearch-in-javascript-part-i</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
            <pubDate>Thu, 15 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/72200540ed258779d87e53a72ea189f8a138540c-1600x901.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Este es el primer artículo de un serial que explica cómo usar Elasticsearch con JavaScript. En este serial, aprenderás lo básico sobre cómo usar Elasticsearch en un entorno JavaScript y revisarás las características y mejores prácticas más relevantes para crear una aplicación de búsqueda. Al final, sabrás todo lo necesario para ejecutar Elasticsearch usando JavaScript.</p><p>En esta primera parte, repasaremos:</p><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#environment">Medio ambiente</a><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#frontend,-backend,-or-serverless?">¿Frontend, backend o serverless?</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#connecting-the-client">Conexión del cliente</a></li></ul></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#indexing-documents">Indexación de documentos</a><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#elasticsearch-client">Cliente Elasticsearch</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#semantic-mappings">Aplicaciones semánticas</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#bulk-helper">Ayudante a granel</a></li></ul></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#searching-data">Búsqueda de datos</a><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#lexical-query-(/search/lexic?q=%3Cquery-term%3E)">Consulta léxica</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#semantic-query-(/search/semantic?q=%3Cquery-term%3E)">Consulta semántica</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#hybrid-query-(/search/hybrid?q=%3Cquery-term%3E)">Consulta híbrida</a></li></ul></li></ul><p><em>Puedes consultar el código fuente con los ejemplos <strong><a href="https://github.com/Delacrobix/JS-client-best-practices_article">aquí</a>.</strong></em></p><h3>¿Qué es el cliente de Node.js Elasticsearch?</h3><p>El <a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html">cliente Elasticsearch Node.js</a> es una biblioteca de JavaScript que introduce las llamadas HTTP REST de la API Elasticsearch a JavaScript. Esto facilita la gestión y cuenta con ayudas que simplifican tareas como indexar documentos en lotes.</p><h2>Medio ambiente</h2><h3>¿Frontend, backend o serverless?</h3><p>Para crear nuestra aplicación de búsqueda usando el cliente JavaScript, necesitamos al menos dos componentes: un clúster Elasticsearch y un entorno de ejecución en JavaScript para ejecutar el cliente.</p><p>El cliente JavaScript soporta todas las soluciones Elasticsearch (Cloud, local y Serverless), y no hay diferencias importantes entre ellas ya que el cliente gestiona todas las variaciones internamente, así que no tienes que preocuparte por cuál usar.</p><p>Sin embargo, el tiempo de ejecución de JavaScript debe ejecutar desde el <strong>servidor</strong> y <strong>no directamente desde el navegador.</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/92ce6cfd923c8008fa44f617a58193642d9d5879-661x410.png" style="width: 100%;" /><p>Esto se debe a que, al llamar a Elasticsearch desde el navegador, el usuario puede obtener información sensible como la clave de la API del clúster, el host o la propia consulta. Elasticsearch recomienda <strong>no exponer nunca el clúster directamente a Internet </strong>y usar una capa intermedia que abstraiga toda esta información para que el usuario solo pueda ver los parámetros. Puedes leer más sobre <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/es-security-principles.html#security-protect-cluster-traffic">este tema aquí</a>.</p><p>Sugerimos usar un esquema como este:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a08769f08ec73fe57bf2e961cfdfbb1cdd57919d-972x429.png" style="width: 100%;" /><p>En este caso, el cliente solo envía los términos de búsqueda y una clave de autenticación para tu servidor mientras este tiene el control total de la consulta y la comunicación con Elasticsearch.</p><h3>Conexión del cliente</h3><p>Empieza creando una clave API siguiendo <a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud">estos pasos</a>.</p><p>Siguiendo el ejemplo anterior, crearemos un servidor Express sencillo y nos conectaremos a él usando un cliente de un servidor Node.JS.</p><p>Inicializaremos el proyecto con NPM e instalaremos el cliente Elasticsearch y <a href="https://expressjs.com/">Express.</a> Esta última es una biblioteca para abrir servidores en Node.js. Usando Express, podemos interactuar con nuestro backend vía HTTP.</p><p>Inicialemos el proyecto:</p><p><code>npm init -y</code></p><p>Dependencias de instalación:</p><p><code>npm install @elastic/elasticsearch express split2 dotenv</code></p><p>Déjame explicártelo:</p><ul><li><a href="https://www.npmjs.com/package/@elastic/elasticsearch"><strong><em>@elastic/elasticsearch</em></strong></a>: Es el cliente oficial de Node.js</li><li><a href="https://www.npmjs.com/package/express"><strong><em>express</em></strong></a>: Nos permitirá montar un servidor nodejs ligero para exponer Elasticsearch</li><li><a href="https://www.npmjs.com/package/split2"><strong><em>split2</em></strong></a>: Divide líneas de texto en un flujo. Útil para procesar nuestros archivos de ndjson línea a línea</li><li><a href="https://www.npmjs.com/package/dotenv"><strong><em>dotenv</em></strong></a>: Permítenos gestionar variables de entorno usando un .env archivo</li></ul><p>Crea un .env archiva en la raíz del proyecto y agrega las siguientes líneas:</p><pre><code>ELASTICSEARCH_ENDPOINT="Your Elasticsearch endpoint"
ELASTICSEARCH_API_KEY="Your Elasticssearch API"</code></pre><p>De esta manera, podemos importar esas variables usando el paquete <code>dotenv</code> .</p><p>Crea un archivo <code>server.js</code> :</p><pre><code>const express = require("express");
const bodyParser = require("body-parser");
const { Client } = require("@elastic/elasticsearch");
 
require("dotenv").config(); //environment variables setup

const ELASTICSEARCH_ENDPOINT = process.env.ELASTICSEARCH_ENDPOINT;
const ELASTICSEARCH_API_KEY = process.env.ELASTICSEARCH_API_KEY;
const PORT = 3000;


const app = express();

app.listen(PORT, () => {
  console.log("Server running on port", PORT);
});
app.use(bodyParser.json());


let esClient = new Client({
  node: ELASTICSEARCH_ENDPOINT,
  auth: { apiKey: ELASTICSEARCH_API_KEY },  
});

app.get("/ping", async (req, res) => {
  try {
    const result = await esClient.info();

    res.status(200).json({
      success: true,
      clusterInfo: result,
    });
  } catch (error) {
    console.error("Error getting Elasticsearch info:", error);

    res.status(500).json({
      success: false,
      clusterInfo: null,
      error: error.message,
    });
  }
});</code></pre><p>Este código configura un servidor de Express.js básico que escucha en el puerto 3000 y se conecta a un clúster Elasticsearch usando una clave API para autenticación. Incluye un punto final /ping que, al acceder mediante una solicitud GET, consulta al clúster de Elasticsearch información básica empleando el método <code>.info()</code> del cliente Elasticsearch. </p><p>Si la consulta tiene éxito, devuelve la información del clúster en formato JSON; de lo contrario, devuelve un mensaje de error. El servidor también emplea middleware de parser corporal para gestionar los cuerpos de las solicitudes JSON.</p><p>Ejecuta el archivo para abrir el servidor:</p><p><code>node server.js</code></p><p>La respuesta debería ser la siguiente:</p><pre><code>Server running on port 3000</code></pre><p>Y ahora, consultemos el <code>/ping</code> endpoint para comprobar el estado de nuestro clúster de Elasticsearch.</p><pre><code>curl http://localhost:3000/ping
{
    "success": true,
    "clusterInfo": {
        "name": "instance-0000000000",
        "cluster_name": "61b7e19eec204d59855f5e019acd2689",
        "cluster_uuid": "BIfvfLM0RJWRK_bDCY5ldg",
        "version": {
            "number": "9.0.0",
            "build_flavor": "default",
            "build_type": "docker",
            "build_hash": "112859b85d50de2a7e63f73c8fc70b99eea24291",
            "build_date": "2025-04-08T15:13:46.049795831Z",
            "build_snapshot": false,
            "lucene_version": "10.1.0",
            "minimum_wire_compatibility_version": "8.18.0",
            "minimum_index_compatibility_version": "8.0.0"
        },
        "tagline": "You Know, for Search"
    }
}</code></pre><h2>Indexación de documentos</h2><p>Una vez conectados, podemos indexar documentos usando mapeos como <a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">semantic_text</a> para búsqueda semántica y texto para consultas de texto completo. Con estos dos tipos de campos, también podemos hacer <a href="https://www.elastic.co/what-is/hybrid-search">búsqueda híbrida</a>.</p><p>Crearemos un nuevo archivo <code>load.js</code> para generar los mapeos y subir los documentos.</p><h3>Cliente Elasticsearch</h3><p>Primero necesitamos instanciar y autenticar al cliente:</p><pre><code>const { Client } = require("@elastic/elasticsearch");

const ELASTICSEARCH_ENDPOINT = "cluster/project_endpoint";
const ELASTICSEARCH_API_KEY = "apiKey";

const esClient = new Client({
  node: ELASTICSEARCH_ENDPOINT,
  auth: { apiKey: ELASTICSEARCH_API_KEY },
});</code></pre><h3>Aplicaciones semánticas</h3><p>Crearemos un índice con datos sobre un hospital veterinario. Almacenaremos la información del dueño, la mascota y los detalles de la visita.</p><p>Los datos sobre los que queremos realizar la búsqueda en texto completo, como nombres y descripciones, se almacenarán como texto. Los datos de categorías, como la especie o raza del animal, se almacenarán como palabras clave.</p><p>Además, copiaremos los valores de todos los campos en un campo semantic_text para poder ejecutar una búsqueda semántica también con esa información.</p><pre><code>const INDEX_NAME = "vet-visits";

const createMappings = async (indexName, mapping) => {
  try {
    const body = await esClient.indices.create({
      index: indexName,
      body: {
        mappings: mapping,
      },
    });

    console.log("Index created successfully:", body);
  } catch (error) {
    console.error("Error creating mapping:", error);
  }
};

await createMappings(INDEX_NAME, {
  properties: {
    owner_name: {
      type: "text",
      copy_to: "semantic_field",
    },
    pet_name: {
      type: "text",
      copy_to: "semantic_field",
    },
    species: {
      type: "keyword",
      copy_to: "semantic_field",
    },
    breed: {
      type: "keyword",
      copy_to: "semantic_field",
    },
    vaccination_history: {
      type: "keyword",
      copy_to: "semantic_field",
    },
    visit_details: {
      type: "text",
      copy_to: "semantic_field",
    },
    semantic_field: {
      type: "semantic_text",
    },
  },
});</code></pre><h3>Ayudante a granel</h3><p>Otro beneficio del cliente es que podemos usar el <a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/client-helpers.html#bulk-helper">helper de mayor</a> volumen para indexar en lotes. El helper en bulk nos permite gestionar fácilmente cosas como la concurrencia, los intentos y qué hacer con cada documento que pasa por la función y que tiene éxito o fracasa.</p><p>Una característica atractiva de este asistente es que puedes trabajar con streams. Esta función te permite enviar un archivo línea por línea en lugar de almacenar el archivo completo en la memoria y enviarlo a Elasticsearch de una sola vez.</p><p>Para subir los datos a Elasticsearch, crea un archivo llamado data.ndjson en la raíz del proyecto y agrega la información que aparece a continuación (alternativamente, puedes descargar el archivo con el conjunto de datos <a href="https://github.com/Delacrobix/JS-client-best-practices_article/blob/main/data.ndjson">desde aquí</a>):</p><pre><code>{"owner_name":"Alice Johnson","pet_name":"Buddy","species":"Dog","breed":"Golden Retriever","vaccination_history":["Rabies","Parvovirus","Distemper"],"visit_details":"Annual check-up and nail trimming. Healthy and active."}
{"owner_name":"Marco Rivera","pet_name":"Milo","species":"Cat","breed":"Siamese","vaccination_history":["Rabies","Feline Leukemia"],"visit_details":"Slight eye irritation, prescribed eye drops."}
{"owner_name":"Sandra Lee","pet_name":"Pickles","species":"Guinea Pig","breed":"Mixed","vaccination_history":[],"visit_details":"Loss of appetite, recommended dietary changes."}
{"owner_name":"Jake Thompson","pet_name":"Luna","species":"Dog","breed":"Labrador Mix","vaccination_history":["Rabies","Bordetella"],"visit_details":"Mild ear infection, cleaning and antibiotics given."}
{"owner_name":"Emily Chen","pet_name":"Ziggy","species":"Cat","breed":"Mixed","vaccination_history":["Rabies","Feline Calicivirus"],"visit_details":"Vaccination update and routine physical."}
{"owner_name":"Tomás Herrera","pet_name":"Rex","species":"Dog","breed":"German Shepherd","vaccination_history":["Rabies","Parvovirus","Leptospirosis"],"visit_details":"Follow-up for previous leg strain, improving well."}
{"owner_name":"Nina Park","pet_name":"Coco","species":"Ferret","breed":"Mixed","vaccination_history":["Rabies"],"visit_details":"Slight weight loss; advised new diet."}
{"owner_name":"Leo Martínez","pet_name":"Simba","species":"Cat","breed":"Maine Coon","vaccination_history":["Rabies","Feline Panleukopenia"],"visit_details":"Dental cleaning. Minor tartar buildup removed."}
{"owner_name":"Rachel Green","pet_name":"Rocky","species":"Dog","breed":"Bulldog Mix","vaccination_history":["Rabies","Parvovirus"],"visit_details":"Skin rash, antihistamines prescribed."}
{"owner_name":"Daniel Kim","pet_name":"Mochi","species":"Rabbit","breed":"Mixed","vaccination_history":[],"visit_details":"Nail trimming and general health check. No issues."}</code></pre><p>Usamos split2 para transmitir las líneas de archivo mientras el asistente masivo las envía a Elasticsearch.</p><pre><code>const { createReadStream } = require("fs");
const split = require("split2");
 
const indexData = async (filePath, indexName) => {
  try {
    console.log(`Indexing data from ${filePath} into ${indexName}...`);

    const result = await esClient.helpers.bulk({
      datasource: createReadStream(filePath).pipe(split()),

      onDocument: () => {
        return {
          index: { _index: indexName },
        };
      },
      onDrop(doc) {
        console.error("Error processing document:", doc);
      },
    });

    console.log("Bulk indexing successful elements:", result.items.length);
  } catch (error) {
    console.error("Error indexing data:", error);
    throw error;
  }
};

await indexData("./data.ndjson", INDEX_NAME);</code></pre><p>El código anterior dice un .ndjson archivo línea por línea y en volumen indexa cada objeto JSON en un índice Elasticsearch especificado usando el método <code>helpers.bulk</code> . Transmite el archivo usando <code>createReadStream</code> y <code>split2</code>, establece metadatos de indexación para cada documento y registra cualquier documento que no se procese. Una vez completado, registra el número de elementos indexados con éxito.</p><p>Alternativamente a la función <code>indexData</code> , puedes subir el archivo directamente a través de la interfaz usando Kibana y usar la <a href="https://www.elastic.co/docs/manage-data/ingest/upload-data-files">interfaz de archivos de datos de subida.</a></p><p>Ejecutamos el archivo para subir los documentos a nuestro clúster de Elasticsearch.</p><p><code>node load.js</code></p><pre><code>Creating mappings for index vet-visits...
Index created successfully: { acknowledged: true, shards_acknowledged: true, index: 'vet-visits' }
Indexing data from ./data.ndjson into vet-visits...
Bulk indexing completed. Total documents: 10, Failed: 0</code></pre><h2>Búsqueda de datos en Elasticsearch</h2><p>Volviendo a nuestro archivo <code>server.js</code> , crearemos diferentes endpoints para realizar búsquedas léxicas, semánticas o híbridas.</p><p>En resumen, este tipo de búsquedas no son mutuamente excluyentes, sino que dependerán del tipo de pregunta que necesites responder.</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Tipo de consulta</td><td style="border: 1px solid #ccc; padding: 4px;">Caso de uso</td><td style="border: 1px solid #ccc; padding: 4px;">Pregunta de ejemplo</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Consulta léxica</td><td style="border: 1px solid #ccc; padding: 4px;">Las palabras o raíces de palabras en la pregunta probablemente aparecerán en los documentos del índice. Similitud de token entre la pregunta y los documentos.</td><td style="border: 1px solid #ccc; padding: 4px;">Busco una camiseta deportiva azul.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Consulta semántica</td><td style="border: 1px solid #ccc; padding: 4px;">Las palabras de la pregunta probablemente no aparecerán en los documentos. Similitud conceptual entre la pregunta y los documentos.</td><td style="border: 1px solid #ccc; padding: 4px;">Busco ropa para el frío.</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Búsqueda híbrida</td><td style="border: 1px solid #ccc; padding: 4px;">La pregunta contiene componentes léxicos y/o semánticos. Similitud de tokens y semántica entre la pregunta y los documentos.</td><td style="border: 1px solid #ccc; padding: 4px;">Estoy buscando un vestido talla S para una boda en la playa.</td>
                    </tr>
              </tbody>
            </table>
          <p>Las partes <strong><em>léxicas </em></strong>de la pregunta probablemente formarán parte de títulos y descripciones, o nombres de categorías, mientras que las partes <strong><em>semánticas </em></strong>son conceptos relacionados con esos campos. <strong><em>El azul</em></strong> probablemente será un nombre de categoría o parte de una descripción, y <strong><em>la boda en la playa</em></strong> probablemente no lo sea, pero puede estar semánticamente relacionada con la ropa de lino.</p><h3>Consulta léxica (/search/lexic?q=&lt;query_term&gt;)</h3><p>La búsqueda léxica, también llamada búsqueda de texto completo, significa búsqueda basada en la similitud de los tokens; es decir, tras un análisis, se devolverán los documentos que incluyan los tokens en la búsqueda.</p><p>Puedes consultar nuestro tutorial práctico de búsqueda <a href="https://www.elastic.co/demo-gallery/lexical-search">léxica aquí</a>.</p><pre><code>app.get("/search/lexic", async (req, res) => {
  const { q } = req.query;

  const INDEX_NAME = "vet-visits";

  try {
    const result = await esClient.search({
      index: INDEX_NAME,
      size: 5,
      body: {
        query: {
          multi_match: {
            query: q,
            fields: ["owner_name", "pet_name", "visit_details"],
          },
        },
      },
    });

    res.status(200).json({
      success: true,
      results: result.hits.hits
    });
  } catch (error) {
    console.error("Error performing search:", error);

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});</code></pre><p>Probamos con: <strong><em>corte de uñas</em></strong></p><pre><code>curl http://localhost:3000/search/lexic?q=nail%20trimming</code></pre><p>Respuesta:</p><pre><code>{
    "success": true,
    "results": [
        {
            "_index": "vet-visits",
            "_id": "-RY6RJYBLe2GoFQ6-9n9",
            "_score": 2.7075968,
            "_source": {
                "pet_name": "Mochi",
                "owner_name": "Daniel Kim",
                "species": "Rabbit",
                "visit_details": "Nail trimming and general health check. No issues.",
                "breed": "Mixed",
                "vaccination_history": []
            }
        },
        {
            "_index": "vet-visits",
            "_id": "8BY6RJYBLe2GoFQ6-9n9",
            "_score": 2.560356,
            "_source": {
                "pet_name": "Buddy",
                "owner_name": "Alice Johnson",
                "species": "Dog",
                "visit_details": "Annual check-up and nail trimming. Healthy and active.",
                "breed": "Golden Retriever",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Distemper"
                ]
            }
        }
    ]
}</code></pre><h3>Consulta semántica (/search/semantic?q=&lt;query_term&gt;)</h3><p>La búsqueda semántica, a diferencia de la búsqueda léxica, encuentra resultados similares al significado de los términos de búsqueda mediante la búsqueda vectorial.</p><p>Puedes consultar nuestro tutorial práctico de búsqueda <a href="https://www.elastic.co/demo-gallery/semantic-search">semántica aquí</a>.</p><pre><code>app.get("/search/semantic", async (req, res) => {
  const { q } = req.query;

  const INDEX_NAME = "vet-visits";

  try {
    const result = await esClient.search({
      index: INDEX_NAME,
      size: 5,
      body: {
        query: {
          semantic: {
            field: "semantic_field",
            query: q
          },
        },
      },
    });

    res.status(200).json({
      success: true,
      results: result.hits.hits,
    });
  } catch (error) {
    console.error("Error performing search:", error);

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});</code></pre><p>Hacemos la prueba con: <strong><em>¿Quién se hizo una pedicura?</em></strong></p><pre><code>curl http://localhost:3000/search/semantic?q=Who%20got%20a%20pedicure?</code></pre><p>Respuesta:</p><pre><code>{
    "success": true,
    "results": [
        {
            "_index": "vet-visits",
            "_id": "-RY6RJYBLe2GoFQ6-9n9",
            "_score": 4.861466,
            "_source": {
                "owner_name": "Daniel Kim",
                "pet_name": "Mochi",
                "species": "Rabbit",
                "breed": "Mixed",
                "vaccination_history": [],
                "visit_details": "Nail trimming and general health check. No issues."
            }
        },
        {
            "_index": "vet-visits",
            "_id": "8BY6RJYBLe2GoFQ6-9n9",
            "_score": 4.7152824,
            "_source": {
                "pet_name": "Buddy",
                "owner_name": "Alice Johnson",
                "species": "Dog",
                "visit_details": "Annual check-up and nail trimming. Healthy and active.",
                "breed": "Golden Retriever",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Distemper"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "9RY6RJYBLe2GoFQ6-9n9",
            "_score": 1.6717153,
            "_source": {
                "pet_name": "Rex",
                "owner_name": "Tomás Herrera",
                "species": "Dog",
                "visit_details": "Follow-up for previous leg strain, improving well.",
                "breed": "German Shepherd",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Leptospirosis"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "9xY6RJYBLe2GoFQ6-9n9",
            "_score": 1.5600781,
            "_source": {
                "pet_name": "Simba",
                "owner_name": "Leo Martínez",
                "species": "Cat",
                "visit_details": "Dental cleaning. Minor tartar buildup removed.",
                "breed": "Maine Coon",
                "vaccination_history": [
                    "Rabies",
                    "Feline Panleukopenia"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "-BY6RJYBLe2GoFQ6-9n9",
            "_score": 1.2696637,
            "_source": {
                "pet_name": "Rocky",
                "owner_name": "Rachel Green",
                "species": "Dog",
                "visit_details": "Skin rash, antihistamines prescribed.",
                "breed": "Bulldog Mix",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus"
                ]
            }
        }
    ]
}</code></pre><h3>Consulta híbrida (/search/hybrid?q=&lt;query_term&gt;)</h3><p>La búsqueda híbrida nos permite combinar la búsqueda semántica y léxica, obteniendo así lo mejor de ambos mundos: se obtiene la precisión de buscar por token, junto con la proximidad de significado de la búsqueda semántica.</p><pre><code>app.get("/search/hybrid", async (req, res) => {
  const { q } = req.query;

  const INDEX_NAME = "vet-visits";

  try {
    const result = await esClient.search({
      index: INDEX_NAME,
      body: {
        retriever: {
          rrf: {
            retrievers: [
              {
                standard: {
                  query: {
                    bool: {
                      must: {
                         multi_match: {
             query: q,
            fields: ["owner_name", "pet_name", "visit_details"],
          },
                      },
                    },
                  },
                },
              },
              {
                standard: {
                  query: {
                    bool: {
                      must: {
                        semantic: {
                          field: "semantic_field",
                          query: q,
                        },
                      },
                    },
                  },
                },
              },
            ],
          },
        },
        size: 5,
      },
    });

    res.status(200).json({
      success: true,
      results: result.hits.hits,
    });
  } catch (error) {
    console.error("Error performing search:", error);

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});</code></pre><p>Hacemos pruebas con &quot;<strong><em>¿Quién se hizo pedicura o tratamiento dental?&quot;</em></strong></p><pre><code>curl http://localhost:3000/search/hybrid?q=who%20got%20a%20pedicure%20or%20dental%20treatment</code></pre><p>Respuesta:</p><pre><code>{
    "success": true,
    "results": [
        {
            "_index": "vet-visits",
            "_id": "9xY6RJYBLe2GoFQ6-9n9",
            "_score": 0.032522473,
            "_source": {
                "pet_name": "Simba",
                "owner_name": "Leo Martínez",
                "species": "Cat",
                "visit_details": "Dental cleaning. Minor tartar buildup removed.",
                "breed": "Maine Coon",
                "vaccination_history": [
                    "Rabies",
                    "Feline Panleukopenia"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "-RY6RJYBLe2GoFQ6-9n9",
            "_score": 0.016393442,
            "_source": {
                "pet_name": "Mochi",
                "owner_name": "Daniel Kim",
                "species": "Rabbit",
                "visit_details": "Nail trimming and general health check. No issues.",
                "breed": "Mixed",
                "vaccination_history": []
            }
        },
        {
            "_index": "vet-visits",
            "_id": "8BY6RJYBLe2GoFQ6-9n9",
            "_score": 0.015873017,
            "_source": {
                "pet_name": "Buddy",
                "owner_name": "Alice Johnson",
                "species": "Dog",
                "visit_details": "Annual check-up and nail trimming. Healthy and active.",
                "breed": "Golden Retriever",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Distemper"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "9RY6RJYBLe2GoFQ6-9n9",
            "_score": 0.015625,
            "_source": {
                "pet_name": "Rex",
                "owner_name": "Tomás Herrera",
                "species": "Dog",
                "visit_details": "Follow-up for previous leg strain, improving well.",
                "breed": "German Shepherd",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Leptospirosis"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "8xY6RJYBLe2GoFQ6-9n9",
            "_score": 0.015384615,
            "_source": {
                "pet_name": "Luna",
                "owner_name": "Jake Thompson",
                "species": "Dog",
                "visit_details": "Mild ear infection, cleaning and antibiotics given.",
                "breed": "Labrador Mix",
                "vaccination_history": [
                    "Rabies",
                    "Bordetella"
                ]
            }
        }
    ]
}</code></pre><h2>Conclusión</h2><p>En esta primera parte de nuestro serial, explicamos cómo configurar nuestro entorno y crear un servidor con diferentes endpoints de búsqueda para consultar los documentos de Elasticsearch siguiendo las mejores prácticas cliente/servidor. Consulta <a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i">la segunda parte</a> de nuestro serial, en la que aprenderás las mejores prácticas de producción y cómo ejecutar el cliente de Node.js Elasticsearch en entornos serverless.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cómo configurar el número de réplicas en un índice de Elasticsearch]]></title>
            <description><![CDATA[Aprende a configurar el parámetro number_of_replicas en un índice de Elasticsearch para mejorar el rendimiento en las búsquedas y ofrecer resistencia ante fallos de nodos. 
]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elasticsearch-index-number-of_replicas</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elasticsearch-index-number-of_replicas</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Wed, 14 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/23b96aaa1a38b1f4747b4a87695d816f24c0cf70-720x421.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch está diseñado para ser un sistema distribuido que puede manejar una gran cantidad de datos y ofrecer una alta disponibilidad. Una de las características clave que permite esto es el concepto de replicación de índice, que está regulado por la configuración <code>number_of_replicas</code> . Este artículo profundizará en los detalles de este escenario, sus participaciones y cómo configurarlo correctamente.</p><h2>El papel de las réplicas en Elasticsearch</h2><p>En Elasticsearch, un índice es una colección de documentos que se dividen en múltiples fragmentos primarios. Cada fragmento primario es un índice Apache Lucene autosuficiente, y los documentos dentro de un índice se distribuyen entre todos los fragmentos primarios. Para garantizar una alta disponibilidad y redundancia de datos, Elasticsearch permite que cada fragmento tenga una o más copias, conocidas como réplicas.<br/><br/>La configuración <code>number_of_replicas</code> controla el número de fragmentos réplica (copias) que Elasticsearch crea para cada fragmento principal en un índice. Por defecto, Elasticsearch crea una réplica para cada shard primario, pero esto puede cambiar según los requisitos de tu sistema.</p><h2>Configuración de la number_of_replicas</h2><p>La configuración de <code>number_of_replicas</code> puede configurar en el momento de la creación del índice o actualizar más adelante. Así es como puedes configurarlo durante la creación del índice:</p><pre><code>PUT /my_index
{
  "settings": {
    "number_of_replicas": 2
  }
}</code></pre><p>En este ejemplo, Elasticsearch creará dos réplicas para cada fragmento primario en el índice de <code>my_index</code> .</p><p>Para actualizar la configuración de <code>number_of_replicas</code> de un índice existente, puedes usar la API <code>_settings</code> :</p><pre><code>PUT /my_index/_settings
{
  "number_of_replicas": 3
}</code></pre><p>Este comando actualizará el índice de <code>my_index</code> para tener tres réplicas por cada fragmento primario.</p><h2>Participaciones del entorno number_of_replicas</h2><p>La configuración <code>number_of_replicas</code> tiene un impacto significativo en el rendimiento y la resiliencia de tu <a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-cluster/">clúster</a> de Elasticsearch. Aquí tienes algunos puntos clave a tener en cuenta:</p><ol><li><strong>Redundancia y disponibilidad de datos:</strong> Aumentar la <code>number_of_replicas</code> mejora la disponibilidad de tus datos creando más copias de cada fragmento. Si un nodo falla, Elasticsearch aún puede servir datos de los fragmentos réplica en los <a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-node/">nodos</a> restantes.</li><li><strong>Rendimiento en la búsqueda:</strong> Los fragmentos réplica pueden atender solicitudes de lectura, por lo que tener más réplicas puede mejorar el rendimiento de búsqueda al distribuir la carga entre más fragmentos.</li></ol><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><ol><li><strong>Requisitos de almacenamiento:</strong> Más réplicas significan más espacio de almacenamiento. Debes cerciorarte de que tu clúster tenga suficiente capacidad para almacenar las réplicas adicionales.</li><li><strong>Resiliencia ante el fallo de nodos:</strong> El <code>number_of_replicas</code> debería establecer teniendo en cuenta el número de nodos en tu clúster. Si el <code>number_of_replicas</code> es igual o mayor que el número de nodos, tu clúster puede tolerar el fallo de varios nodos sin pérdida de datos.</li></ol><h2>Mejores prácticas para establecer number_of_replicas</h2><p>La configuración óptima de <code>number_of_replicas</code> depende de los requisitos específicos de tu sistema. Sin embargo, aquí tienes algunas buenas prácticas generales:</p><ul><li>Para un clúster de un solo nodo, <code>number_of_replicas</code> debe estar en 0, ya que no hay otros nodos que almacenen réplicas.</li><li>Para un clúster multinodo, <code>number_of_replicas</code> debe estar configurado al menos en 1 para garantizar redundancia de datos y alta disponibilidad.</li><li>Si el rendimiento en las búsquedas es una prioridad, considera aumentar la <code>number_of_replicas</code>. Sin embargo, ten en cuenta el equilibrio entre el rendimiento de escritura y los requisitos de almacenamiento.</li><li>Cerciórate siempre de que tu clúster tenga suficiente capacidad para almacenar las réplicas adicionales.</li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mapear incrustaciones a tipos de campos de Elasticsearch: semantic_text, dense_vector, sparse_vector]]></title>
            <description><![CDATA[Discutir cómo y cuándo usar semantic_text, dense_vector o sparse_vector, y cómo se relacionan con la generación de incrustaciones.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/mapping-embeddings-to-elasticsearch-field-types</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/mapping-embeddings-to-elasticsearch-field-types</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Tue, 13 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f98fdff837db55b466780c0bae672aa6f6c3a966-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>El uso de incrustaciones para mejorar la relevancia y precisión de la recuperación de información creció significativamente a lo largo de los años. Herramientas como Elasticsearch evolucionaron para soportar este tipo de datos mediante tipos de campos especializados como vectores densos, vectores dispersos y texto semántico. Sin embargo, para lograr buenos resultados, es esencial entender cómo mapear correctamente las incrustaciones a los tipos de campos disponibles de Elasticsearch: <code>semantic_text</code>, <code>dense_vector</code>y <code>sparse_vector</code>.</p><p>En este artículo, discutiremos estos tipos de campos, cuándo usar cada uno y cómo se relacionan con la generación y las estrategias de uso de incrustaciones, tanto durante la indexación como durante la consulta.</p><h2>Tipo vectorial denso</h2><p>El tipo de campo <code>dense_vector</code> en Elasticsearch se emplea para almacenar vectores densos, que son representaciones numéricas de datos como texto, imágenes y audio, donde casi todas las dimensiones son relevantes. Estos vectores se generan empleando modelos de incrustación proporcionados por plataformas como OpenAI, Cohere o Hugging Face, y están diseñados para captar el significado semántico general de los datos, incluso cuando no comparten términos exactos con otros documentos.</p><p>En Elasticsearch, los vectores densos pueden tener hasta 4096 dimensiones dependiendo del modelo empleado. Por ejemplo, el modelo totalmente MiniLM-L6-v2 genera vectores con 384 dimensiones, mientras que el text-embedding-ada-002 de OpenAI produce vectores con 1536 dimensiones.</p><p>El campo <code>dense_vector</code> se adopta comúnmente como el tipo predeterminado para almacenar este tipo de incrustación cuando se necesita mayor control, como usando vectores pregenerados, aplicando funciones de similitud personalizadas o integrando con modelos externos.</p><h3>¿Cuándo y por qué usar dense_vector tipo?</h3><p>Los vectores densos son excelentes para capturar similitudes semánticas entre oraciones, párrafos o documentos completos. Funcionan muy bien cuando el objetivo es comparar el significado general de los textos, aunque no compartan los mismos términos.</p><p>El campo vectorial denso es ideal cuando ya tienes una pipeline externa de generación de embedding usando modelos proporcionados por plataformas como OpenAI, Cohere o Hugging Face y solo quieres almacenar y consultar estos vectores manualmente. Este tipo de campo ofrece alta compatibilidad con modelos de incrustación y total flexibilidad en la generación y consulta, permitiéndote controlar cómo se producen, indexan y emplean los vectores durante la búsqueda.</p><p>Además, soporta diferentes formas de búsqueda semántica, con consultas como k-NN o script_score para casos en los que sea necesario ajustar la lógica de clasificación. Estas posibilidades hacen que el vector denso sea ideal para aplicaciones como RAG (Generación Aumentada por Recuperación), sistemas de recomendación y búsquedas personalizadas basadas en similitudes.</p><p>Por último, el campo te permite personalizar la lógica de relevancia, usando funciones como <code>cosineSimilarity</code>, <code>dotProduct</code> o <code>l2norm</code> para adaptar la clasificación según las necesidades de tu caso de uso. </p><p>Los vectores densos siguen siendo la mejor opción para quienes necesitan flexibilidad, personalización y compatibilidad con casos de uso avanzados como los mencionados anteriormente.</p><h3>¿Cómo usar la consulta para el tipo de vector denso?</h3><p>Las búsquedas en campos definidos como <strong><code>dense_vector</code></strong> emplean la consulta k-vecinos más cercanos. Esta consulta es responsable de encontrar documentos cuyo vector denso está más cercano al vector de consulta. A continuación se muestra un ejemplo de cómo aplicar una consulta k-NN a un campo vectorial denso:</p><pre><code>{
  "knn": {
    "field": "my_dense_vector",
    "k": 10,
    "num_candidates": 50,
    "query_vector": [/* vector generated by model */]
  }
}</code></pre><p>Además de la consulta k-NN, si es necesario personalizar el puntaje del documento, también es posible usar la consulta script_score, combinándola con funciones de comparación vectorial como <strong>cosenoSimilitud, Producto PuntoPunto o l2norm</strong> para calcular la relevancia de forma más controlada. Mira el ejemplo:</p><pre><code>{
"script_score": {
    "query": { "match_all": {} },
    "script": {
      "source": "cosineSimilarity(params.query_vector,
'my_dense_vector') + 1.0",
      "params": {
        "query_vector": [/* vector */]
      }
    }
  }
}</code></pre><p>Si quieres profundizar más, te recomiendo explorar el artículo <a href="https://www.elastic.co/search-labs/blog/vector-search-set-up-elasticsearch">Cómo configurar la búsqueda vectorial en Elasticsearch.</a></p><p></p><h2>Tipo vectorial disperso</h2><p>El tipo de campo <strong><code>sparse_vector</code></strong> se emplea para almacenar vectores dispersos, que son representaciones numéricas donde la mayoría de los valores son cero y solo unos pocos términos tienen pesos significativos. Este tipo de vector es común en modelos basados en términos como SPLADE o ELSER (Elastic Learned Sparse EncodeR).</p><h3>¿Cuándo y por qué usar tipo vectorial disperso?</h3><p>Los vectores dispersos son ideales cuando se necesita una búsqueda más precisa en términos léxicos, sin sacrificar la inteligencia semántica. Representan el texto como pares token/valor, resaltando solo los términos más relevantes con pesos asociados, lo que proporciona claridad, control y eficiencia.</p><p>Este tipo de campo es especialmente útil cuando se generan vectores basados en términos, como en los modelos ELSER o SPLADE, que asignan diferentes pesos a cada token según su importancia relativa en el texto.</p><p>Para las ocasiones en las que quieres controlar la influencia de palabras específicas en la consulta, los tipos vectoriales dispersos te permiten ajustar manualmente el peso de los términos para optimizar el orden de los resultados.</p><p>Entre los principales beneficios están la transparencia en la búsqueda, ya que es posible entender claramente por qué un documento se consideraba relevante, y la eficiencia de almacenamiento, ya que solo se almacenan los tokens con valor distinto de cero, a diferencia de los vectores densos que almacenan todas las dimensiones.</p><p>Además, los vectores dispersos son el complemento ideal en estrategias de búsqueda híbrida, e incluso pueden combinar con vectores densos para combinar precisión léxica con comprensión semántica.</p><h3>¿Cómo usar la consulta para el tipo de vector disperso?</h3><p>La consulta <strong><code>sparse_vector</code></strong> te permite buscar documentos basándote en un vector de consulta en formato token/valor. Consulta un ejemplo de la consulta a continuación:</p><pre><code>{
  "query": {
    "sparse_vector": {
      "field": "field_sparse",
      "query_vector": {
        "token1": 0.6,
        "token2": 0.2,
        "token3": 0.9
      }
    }
  }
}</code></pre><p>Si prefieres usar un modelo capacitado, es posible emplear un punto final de inferencia que transforme automáticamente el texto de consulta en un vector disperso:</p><pre><code>{
  "query": {
    "sparse_vector": {
      "field": "field_sparse",
      "inference_id": "the inference ID to produce the token/weights",
      "query": "search text"
    }
  }
}</code></pre><p>Para profundizar en este tema, sugiero leer <a href="https://www.elastic.co/search-labs/blog/sparse-vector-embedding">Understanding sparse vector embeddings with trained ML</a> models.</p><h2>Tipo de texto semántico</h2><p>El tipo de campo <strong><code>semantic_text</code></strong> es la forma más sencilla y directa de usar la búsqueda semántica en Elasticsearch. Gestiona automáticamente la generación de incrustaciones, tanto en tiempo de indexación como en tiempo de consulta, a través de un punto final de inferencia. Esto significa que no tienes que preocuparte por generar o almacenar vectores manualmente.</p><h3>¿Cuándo y por qué usar texto semántico?</h3><p>El campo <code>semantic_text</code> es ideal para quienes quieren empezar con el mínimo esfuerzo técnico y sin tener que manejar vectores manualmente. Este campo automatiza pasos como la generación de incrustaciones y el mapeo de búsqueda vectorial, haciendo que la configuración sea más rápida y cómoda.</p><p>Deberías considerar usar <code>semantic_text</code> cuando valoras la <strong>simplicidad y la abstracción</strong>, ya que <strong>elimina la complejidad de configurar manualmente mapeos, generación de incrustaciones y pipelines de ingestión</strong>. Solo tienes que seleccionar el modelo de inferencia, y Elasticsearch se encarga del resto.</p><p>Los principales beneficios incluyen <strong>la generación automática de incrustaciones,</strong> realizada tanto durante la indexación como durante la consulta, y <strong>el mapeo listo para usar</strong>, que viene preconfigurado para soportar el modelo de inferencia seleccionado.</p><p>Además, el campo <strong>ofrece soporte nativo para la división automática de textos largos (fragmentación de texto),</strong> permitiendo dividir textos grandes en pasajes más pequeños, cada uno con su propia incrustación, lo que mejora la precisión en la búsqueda. Esto aumenta enormemente la productividad, especialmente para equipos que quieren ofrecer valor rápidamente sin tener que lidiar con la ingeniería subyacente de la búsqueda semántica.</p><p>Sin embargo, aunque <code>semantic_text</code> proporciona rapidez y simplicidad, este enfoque tiene algunas limitaciones. Permite el uso de modelos estándar de mercado, siempre que estén disponibles como puntos finales de inferencia en Elasticsearch. Pero <strong>no soporta incrustaciones generadas externamente</strong>, como es posible con el campo <code>dense_vector</code> .</p><p>Si necesitas más control sobre cómo se generan los vectores, quieres usar tus propios embeddings o necesitas combinar varios campos para estrategias avanzadas, los campos <code>dense_vector</code> y <code>sparse_vector</code> ofrecen la flexibilidad necesaria para escenarios más personalizados o específicos de dominio.</p><h3>Cómo usar la consulta para el tipo de texto semántico</h3><p>Antes de <strong><code>semantic_text</code></strong>, era necesario usar una consulta diferente según el tipo de incrustación (densa o dispersa). Se empleaba una consulta <code>sparse_vector</code> para campos dispersos, mientras que <code>dense_vector</code> campos requerían consultas KNN.</p><p>Con el tipo de texto semántico, la búsqueda se realiza usando la <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-semantic-query">consulta semántica</a>, que genera automáticamente el vector de consulta y lo compara con las incrustaciones de los documentos indexados. El tipo <strong><code>semantic_text</code></strong> permite definir un extremo de inferencia para incrustar la consulta, pero si no se especifica ninguno, se aplicará el mismo punto final que se usa durante la indexación a la consulta.</p><pre><code>{
  "query": {
    "semantic": {
      "field": "semantic_text_field",
      "query": "search text"
    }
  }
}</code></pre><p>Para saber más, te sugiero leer el artículo <a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Elasticsearch nueva semantic_text mapeo: Simplificando la búsqueda semántica</a>.</p><h2>Conclusión</h2><p>Al elegir cómo mapear incrustaciones en Elasticsearch, es esencial entender cómo quieres generar los vectores y qué nivel de control necesitas sobre ellos. Si buscas simplicidad, el campo de texto semántico permite una búsqueda semántica automática y escalable, lo que lo hace ideal para muchos casos de uso iniciales. Cuando se requiere más control, un rendimiento ajustado o integración con modelos personalizados, los campos vectoriales densos y dispersos proporcionan la flexibilidad necesaria.</p><p>El tipo de campo ideal depende de tu caso de uso, la infraestructura disponible y la madurez de tu pila de aprendizaje automático. Lo más importante es que Elastic ofrece las herramientas para construir sistemas de búsqueda modernos y altamente adaptables.</p><h2>Referencias</h2><ul><li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html">Tipo de campo de texto semántico</a></li><li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/sparse-vector.html">Tipo de campo vectorial disperso</a></li><li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html">Tipo de campo vectorial denso</a></li><li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html">Consulta semántica</a></li><li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-sparse-vector-query.html">Consulta vectorial dispersa</a></li><li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">Búsqueda kNN</a></li><li><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Mapeado de nuevas semantic_text de Elasticsearch: Simplificación de la búsqueda semántica</a></li><li><a href="https://www.elastic.co/search-labs/blog/sparse-vector-embedding">Comprendiendo las incrustaciones de vectores dispersos con modelos de aprendizaje automático capacitados</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Excluyendo campos de Elasticsearch de la indexación]]></title>
            <description><![CDATA[Aprende a configurar Elasticsearch para excluir campos, las principales razones para excluir campos de indexación y las mejores prácticas a seguir.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/excluding-elasticsearch-fields-from-indexing</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/excluding-elasticsearch-fields-from-indexing</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Mon, 12 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3aa0b481994d2445ba979d3c79fff64c5ee6676a-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>En Elasticsearch, la indexación se refiere al proceso de almacenar y organizar los datos de una manera que los hace fácilmente consultables. Aunque indexar todos los campos de un documento puede ser útil en algunos casos, hay situaciones en las que podrías querer excluir ciertos campos de la indexación. Esto puede ayudar a mejorar el rendimiento, reducir los costos de almacenamiento y minimizar el tamaño total de tu índice de Elasticsearch.</p><p>En este artículo, hablaremos de las razones por las que excluye campos de la indexación, cómo configurar Elasticsearch para excluir campos específicos y algunas buenas prácticas a seguir al hacerlo.</p><h2>Razones para excluir campos de la indexación</h2><ol><li><strong>Rendimiento: </strong>Indexar todos los campos de un documento puede aumentar el tiempo de indexación y ralentizar el rendimiento de búsqueda. Excluyendo campos que no son necesarios para búsqueda o agregación, puedes mejorar el rendimiento general de tu clúster de Elasticsearch.</li><li><strong>Almacenamiento: </strong>Los campos de indexación consumen espacio de almacenamiento. Excluir campos que no son necesarios para búsqueda o agregación puede ayudar a reducir los requisitos de almacenamiento de tu clúster de Elasticsearch.</li><li><strong>Tamaño del índice: </strong>El tamaño de un índice de Elasticsearch está directamente relacionado con el número de campos indexados. Al excluir campos innecesarios, puedes minimizar el tamaño de tu índice, lo que puede llevar a un rendimiento de búsqueda e indexación más rápido.</li></ol><h2>Configuración de Elasticsearch para excluir campos</h2><p>Para excluir un campo de la indexación en Elasticsearch, puedes usar la propiedad &quot;index&quot; en el mapeo del campo. Al poner la propiedad &quot;index&quot; en &quot;false&quot;, Elasticsearch no indexará el campo, y no será buscable ni estará disponible para agregaciones.</p><p>Aquí tienes un ejemplo de cómo excluir un campo de la indexación usando el mapeo Elasticsearch:</p><pre><code>PUT /my_index
{
  "mappings": {
    "properties": {
      "field_to_exclude": {
        "type": "text",
        "index": false
      }
    }
  }
}</code></pre><p>En este ejemplo, estamos creando un nuevo índice llamado &quot;my_index&quot; con un solo campo llamado &quot;field_to_exclude&quot;. Al poner la propiedad &quot;index&quot; en &quot;false&quot;, le decimos a Elasticsearch que no indexe este campo. Sin embargo, el campo seguirá estando disponible en el documento fuente.</p><h2>Mejores prácticas para excluir campos de la indexación</h2><ol><li><strong>Analiza tus datos: </strong>Antes de excluir campos de la indexación, es esencial analizar tus datos y entender qué campos son necesarios para la búsqueda y agregación. Esto te ayudará a tomar decisiones informadas sobre qué campos excluir.</li><li><strong>Prueba tus cambios: </strong>Al excluir campos de la indexación, es fundamental probar tus cambios para cerciorarte de que la funcionalidad de búsqueda y agregación sigue funcionando como se espera. Esto puede ayudarte a evitar problemas inesperados o de rendimiento.</li><li><strong>Rendimiento del monitor:</strong> Luego de excluir campos de la indexación, monitoriza el rendimiento de tu clúster de Elasticsearch para cerciorarte de que los cambios tuvieron el efecto deseado. Esto puede ayudarte a identificar posibles optimizaciones adicionales que puedan ser necesarias.</li><li><strong>Emplea filtrado de fuente:</strong> Si necesitas almacenar un campo en Elasticsearch pero no quieres que sea buscable ni disponible para agregaciones, considera usar filtrado de fuente. Esto te permite almacenar el campo en el campo _source pero excluirlo del índice.</li></ol><h2>Conclusión</h2><p>Excluir campos de la indexación en Elasticsearch puede ayudar a mejorar el rendimiento, reducir los costos de almacenamiento y minimizar el tamaño total de tu índice. Analizando cuidadosamente tus datos y entendiendo qué campos son necesarios para la búsqueda y agregación, puedes tomar decisiones informadas sobre cuáles excluir. Prueba siempre tus cambios y monitoriza el rendimiento de tu clúster de Elasticsearch para cerciorarte de que tus optimizaciones tienen el efecto deseado.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Eliminar un campo de un documento en Elasticsearch]]></title>
            <description><![CDATA[Aprende a eliminar campos de los documentos de Elasticsearch mediante la API de actualización, scripts o reindexación para eliminaciones individuales y masivas.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elasticsearch-delete-field-from-document</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elasticsearch-delete-field-from-document</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Fri, 09 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/89278eb7309b7f3018c61be2b514d1fd25b9564d-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>En Elasticsearch, es un requisito común eliminar un campo de un documento. Esto puede ser útil cuando quieres eliminar información innecesaria o desactualizada de tu índice. En este artículo, discutiremos diferentes métodos para eliminar un campo de un documento en Elasticsearch, junto con ejemplos e instrucciones paso a paso. </p><h2>Método 1: Uso de la API de actualización</h2><p>La <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/update-document">API de actualización</a> te permite actualizar un documento mediante un script que modifica el código fuente del documento. Puedes usar esta API para eliminar un campo de un documento al configurarlo como nulo. Aquí tienes una guía paso a paso sobre cómo hacerlo:</p><p>1. Identifica el índice, el tipo de documento (si se emplea Elasticsearch 6.x o anterior) y el ID del documento que se desea actualizar.</p><p>2. Usar la API Update con un script que ponga el campo en null, o mejor aún, que lo elimine del documento fuente. El siguiente ejemplo demuestra cómo eliminar el campo &quot;field_to_delete&quot; de un documento con ID &quot;1&quot; en el índice &quot;my_index&quot;:</p><pre><code>POST /my_index/_update/1
{
  "script": "ctx._source.remove('field_to_delete')"
}</code></pre><p>3. Ejecutar la solicitud. Si tiene éxito, Elasticsearch devolverá una respuesta indicando que el documento fue actualizado.</p><p>Nota: Este método solo elimina el campo del documento especificado. El campo seguirá existiendo en el mapeo y en otros documentos del índice.</p><h2>Método 2: Reindexación con una fuente modificada</h2><p>Si deseas eliminar un campo de todos los documentos de una indexación, puedes usar la <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-reindex">API de reindexación</a> para crear una indexación nueva con la fuente modificada. Aquí te explicamos cómo hacerlo:</p><p>1. Crear un nuevo índice con los mismos ajustes y asignaciones que el índice original. Puedes usar la API Get Index para recuperar la configuración y mapeo del índice original.</p><p>2. Emplear la API Reindex para copiar documentos del índice original al nuevo índice, eliminando el campo del código fuente. El siguiente ejemplo demuestra cómo eliminar el campo &quot;field_to_delete&quot; de todos los documentos en el índice &quot;my_index&quot;:</p><pre><code>POST /_reindex
{
  "source": {
    "index": "my_index"
  },
  "dest": {
    "index": "new_index"
  },
  "script": {
    "source": "ctx._source.remove('field_to_delete')"
  }
}</code></pre><p><br/>3. Verificar que el nuevo índice contiene los documentos correctos con el campo eliminado.</p><p>4. Si todo parece bien, puedes eliminar el índice original y, si es necesario, agregar un alias al nuevo índice con el nombre del índice original.</p><h2>Método 3: Actualización del mapping y la reindexación</h2><p>Si quieres eliminar un campo del mapeo y todos los documentos de un índice, puedes actualizar el mapeo y luego volver a indexar los documentos. Así es como se hace:</p><p>1. Crear un nuevo índice con la misma configuración que el índice original.</p><p>2. Recuperar los mapeos del índice original usando la API Get Mapping.</p><p>3. Modifica los mapeos eliminando el campo que quieres eliminar.</p><p>4. Aplicar los mapeos modificados al nuevo índice usando la API de Put Maping.</p><p>5. Emplear la API Reindex para copiar documentos del índice original al nuevo índice, como se describe en el Método 2.</p><p>6. Verificar que el nuevo índice contiene los documentos correctos sin eliminar el campo y que el campo no esté presente en el mapeo.</p><p>7. Si todo se ve bien, puedes eliminar la indexación original y, si es necesario, agregar un alias a la indexación nueva con el nombre de la original.</p><h2>Conclusión</h2><p>En este artículo, discutimos tres métodos para eliminar un campo de un documento en Elasticsearch: usar la API Update, reindexar con un código fuente modificado y actualizar el mapeo y el reindexado. Cada método tiene sus propios casos de uso y compromisos, así que elige el que mejor se adapte a tus necesidades. Recuerda siempre probar tus cambios y verificar los resultados antes de aplicarlos a entornos de producción.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cómo unir dos índices en Elasticsearch]]></title>
            <description><![CDATA[Explicando cómo usar los términos query, Logstash elasticsearch filter, el procesador enrich y ES|QL para unir dos índices en Elasticsearch.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elasticsearch-join-two-indexes</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elasticsearch-join-two-indexes</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Carly Richmond]]></dc:creator>
            <pubDate>Wed, 07 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0d4736d10fa3e12e6233cd59993299c7bd48911b-680x450.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>En Elasticsearch, unir dos índices no es tan sencillo como en las bases de datos relacionales SQL tradicionales. Sin embargo, es posible lograr resultados similares empleando ciertas técnicas y características proporcionadas por Elasticsearch.</p><p>Históricamente, muchas personas usaban el<a href="https://www.elastic.co/es/docs/reference/elasticsearch/mapping-reference/nested"> tipo de campo</a> <a href="https://www.elastic.co/es/docs/reference/elasticsearch/mapping-reference/nested"><code>nested</code></a>como un mecanismo para unir diferentes índices. Sin embargo, fue limitado debido a consultas costosas y soporte incompleto en Kibana, específicamente visualizaciones de Lens.</p><p>Este artículo profundizará en el proceso de unir dos índices en Elasticsearch, centrar en los siguientes enfoques: </p><ol><li>Uso de la consulta <code>terms</code></li><li>Uso del procesador <code>enrich</code> en canalizaciones de ingesta</li><li>Complemento de filtro <code>elasticsearch</code> Logstash</li><li>ES|QL <code>ENRICH</code></li><li>ES|QL <code>LOOKUP JOIN</code></li></ol><h2>Uso de la consulta de términos</h2><p>La <a href="https://www.elastic.co/es/docs/reference/query-languages/query-dsl/query-dsl-terms-query">consulta de términos</a> es una de las formas más efectivas de unir dos índices en Elasticsearch. Esta consulta se emplea para recuperar documentos que contienen uno o más términos exactos en un campo específico. Aquí discutimos cómo usarlo para unir dos índices.</p><p>En primer lugar, debe recuperar los datos necesarios del primer índice. Esto se puede hacer usando una simple solicitud GET y extrayendo los valores del atributo <code>_source</code> .</p><pre><code># Simple GET request
GET first_index/_search</code></pre><p>Una vez que tenga los datos del primer índice, puede usarlos para consultar el segundo índice. Esto se hace mediante la consulta <code>terms</code> , donde se especifica el campo y los valores que desea que coincidan.</p><p>Aquí hay un ejemplo:</p><pre><code>GET second_index/_search
{
  "query": {
    "terms": {
      "field_in_second_index": ["value1_from_first_index", "value2_from_first_index"]
    }
  }
}</code></pre><p><br/>En este ejemplo, <code>field_in_second_index</code> es el campo del segundo índice que desea hacer coincidir con los valores del primer índice. <code>value1_from_first_index</code> y <code>value2_from_first_index</code> son los valores del primer índice que desea que coincidan en el segundo índice.</p><p>La consulta de términos también proporciona soporte para realizar los dos pasos anteriores de una sola vez mediante una técnica denominada <a href="https://www.elastic.co/es/docs/reference/query-languages/query-dsl/query-dsl-terms-query#query-dsl-terms-lookup">búsqueda de términos</a>. Elasticsearch se encargará de recuperar de forma transparente los valores para que coincidan con otro índice. Por ejemplo, si tiene un índice de equipos que contiene una lista de jugadores:</p><pre><code>PUT teams/_doc/team1
{
  "players":   ["john", "bill", "michael"]
}
PUT teams/_doc/team2
{
  "players":   ["aaron", "joe", "donald"]
}</code></pre><p>Es posible consultar un índice de personas para todas las personas que juegan en team1, como se muestra a continuación:</p><pre><code>GET people/_search?pretty
{
  "query": {
    "terms": {
        "name" : {
            "index" : "teams",
            "id" : "team1",
            "path" : "players"
        }
    }
  }
}</code></pre><p>En el ejemplo anterior, Elasticsearch recuperará de forma transparente los nombres de los jugadores del documento con id team1 en el índice de equipos (es decir, &quot;John&quot;, &quot;Bill&quot; y &quot;Michael&quot;) y busque todos los documentos en el índice de personas que contengan cualquiera de esos valores en su campo de nombre.</p><p>Para aquellos que tienen curiosidad, la consulta SQL equivalente sería:</p><pre><code>SELECT p.* FROM people p
INNER JOIN teams t ON p.name = t.players</code></pre><h2>Uso del procesador enrich</h2><p>El<a href="https://www.elastic.co/es/docs/reference/enrich-processor/enrich-processor"> procesador</a> <a href="https://www.elastic.co/es/docs/reference/enrich-processor/enrich-processor"><code>enrich</code></a>es otra herramienta poderosa que se puede usar para unir dos índices en Elasticsearch. Este procesador enriquece los datos de los documentos entrantes agregando datos de un índice de enriquecimiento predefinido.</p><p>A continuación, le indicamos cómo puede usar el procesador de enriquecimiento para unir dos índices:</p><p>1. Primero, debe crear una política de enriquecimiento. Esta directiva define qué índice usar para el enriquecimiento, en qué campo coincidir y qué campos usar para enriquecer los documentos entrantes.</p><p>Aquí hay un ejemplo:</p><pre><code>PUT _enrich/policy/my_enrich_policy
{
  "match": {
    "indices": "first_index",
    "match_field": "field_in_first_index",
    "enrich_fields": ["field_to_enrich"]
  }
}</code></pre><p>2. Una vez creada la política, debe ejecutarla para crear el índice de enriquecimiento a partir de la política recién creada:</p><pre><code>PUT _enrich/policy/my_enrich_policy/_execute</code></pre><p>Esto creará un nuevo índice enriquecido oculto que se usará durante el enriquecimiento. En función del tamaño del índice de origen, esta operación puede tardar algún tiempo. Cerciorar de que la política de enriquecimiento esté completamente desarrollada antes de continuar con el siguiente paso.</p><p>3. Una vez creada la directiva de enriquecimiento, puede emplear el procesador de enriquecimiento en una canalización de ingesta para enriquecer los datos de los documentos entrantes:</p><pre><code>PUT _ingest/pipeline/my_pipeline
{
  "processors": [
    {
      "enrich": {
        "policy_name": "my_enrich_policy",
        "field": "field_in_second_index",
        "target_field": "enriched_field"
      }
    }
  ]
}</code></pre><p>En este ejemplo, <code>field_in_second_index</code> es el campo del segundo índice que debe coincidir con el <code>match_field</code> del primer índice. <code>enriched_field</code> es el nuevo campo del segundo índice que contendrá los datos enriquecidos de la <code>enrich_fields</code> del primer índice.</p><p>Un inconveniente de este enfoque es que si los datos cambian en <code>first_index</code>, la política de enriquecimiento debe volver a ejecutar. El índice enriquecido no se actualiza ni sincroniza automáticamente desde el índice de origen a partir del cual se creó. Sin embargo, si <code>first_index</code> es relativamente estable, entonces este enfoque funciona bien.</p><h2>Complemento de filtro Logstash elasticsearch</h2><p>Si usa Logstash, otra opción similar al procesador de <code>enrich</code> descrito anteriormente es usar el complemento de filtro <code>elasticsearch</code> para agregar campos relevantes al evento en función de una consulta específica. La configuración de nuestra canalización de Logstash residiría en un archivo <code>.conf</code> , como <code>my-pipeline.conf</code>.</p><p>Imaginemos que nuestra canalización está extrayendo registros de Elasticsearch usando el<a href="https://www.elastic.co/es/docs/reference/logstash/plugins/plugins-inputs-elasticsearch"> complemento de entrada</a> <a href="https://www.elastic.co/es/docs/reference/logstash/plugins/plugins-inputs-elasticsearch"><code>elasticsearch</code></a>, con una consulta para reducir la selección:</p><pre><code>input {
  # Read all documents from Elasticsearch matching the given query
  elasticsearch {
    hosts => "localhost"
    query => '{ "query": { "match": { "statuscode": 200 } }, "sort": [ "_doc" ] }'
  }
}</code></pre><p>Si queremos enriquecer estos mensajes con información de un índice determinado, podemos usar el<a href="https://www.elastic.co/es/docs/reference/logstash/plugins/plugins-filters-elasticsearch"> complemento de filtro</a> <a href="https://www.elastic.co/es/docs/reference/logstash/plugins/plugins-filters-elasticsearch"><code>elasticsearch</code></a>en la sección <code>filter</code> para enriquecer nuestros registros:</p><pre><code>filter {
   elasticsearch {
      hosts => ["localhost"]
      index => "index_name"
      query => "type:start AND operation:%{[opid]}"
      fields => { "@timestamp" => "started" }
   }
}</code></pre><p>El código anterior buscará los documentos del índice <code>index_name</code> donde se inicia <code>type</code> y el campo de operación coincide con el <code>opid</code>especificado y, a continuación, copiará el valor del campo <code>@timestamp</code> en un nuevo campo denominado <code>started</code>.</p><p>Los documentos enriquecidos se enviarían a la fuente de salida adecuada, en este caso a Elasticsearch mediante el<a href="https://www.elastic.co/es/docs/reference/logstash/plugins/plugins-outputs-elasticsearch"> complemento de salida</a> <a href="https://www.elastic.co/es/docs/reference/logstash/plugins/plugins-outputs-elasticsearch"><code>elasticsearch</code></a>:</p><pre><code>output {
    elasticsearch {
        hosts => "localhost"
        data_stream => "true"
    }
}</code></pre><p>Si ya está empleando Logstash, esta opción puede ser útil para consolidar su lógica de enriquecimiento en un solo lugar y procesar a medida que ingresan nuevos eventos. Sin embargo, si no lo está, agrega complejidad a su solución y otro componente que necesita ejecutar y mantener.</p><h2>ES|ENRIQUECER QL</h2><p>La introducción de <a href="https://www.elastic.co/es/docs/explore-analyze/query-filter/languages/esql">ES|QL</a>, que pasó a ser GA en la versión 8.14, es un lenguaje de consulta canalizado compatible con Elasticsearch que permite filtrar, transformar y analizar datos. El uso del comando de procesamiento ENRICH nos permite agregar datos de índices existentes mediante una política de enriquecimiento.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/03aadddc08afffff3f6526c9c052999c97fa09dd-1600x989.png" style="width: 100%;" /><p>Tomando la misma <code>my_enrich_policy</code> de políticas del ejemplo original del procesador de enriquecimiento, el ES|El ejemplo de QL se vería así:</p><pre><code>FROM first_index
| WHERE field_in_first_index IS NOT NULL
| ENRICH my_enrich_policy</code></pre><p>También es posible anular los campos de coincidencia y enriquecimiento, que en nuestro ejemplo son <code>field_in_first_index</code> y <code>field_to_enrich</code> respectivamente:</p><pre><code>FROM first_index
| WHERE field_in_first_index IS NOT NULL
| ENRICH my_enrich_policy ON another_field_in_first_index WITH different_field_to_enrich</code></pre><p>Si bien la limitación obvia es que primero debe especificar una política de enriquecimiento, ES|QL proporciona la flexibilidad para ajustar los campos según sea necesario.</p><h2>ES|UNIÓN DE BÚSQUEDA DE QL</h2><p>Elasticsearch 8.18 presenta una nueva forma de unir índices en Elasticsearch, a saber, el comando <code>LOOKUP JOIN</code> . Este comando funciona como una LEFT OUTER JOIN de estilo SQL empleando el nuevo modo de <a href="https://www.elastic.co/es/docs/reference/elasticsearch/index-settings/index-modules#index-mode-setting">índice de búsqueda</a> en el lado derecho de la unión.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1d73495979c4d6bb675c4c966ea86d9a72dc1c48-510x605.png" style="width: 100%;" /><p>Revisando nuestro ejemplo anterior, la nueva consulta es la siguiente, donde <code>match_field</code> debe estar presente tanto en <code>first_index</code> como en <code>second_index</code>:</p><pre><code>FROM first_index
| WHERE field_in_first_index IS NOT NULL
| LOOKUP JOIN second_index ON match_field</code></pre><p>El beneficio de LOOKUP JOIN sobre los otros enfoques es que no requiere ninguna directiva de <code>enrich</code> y, por lo tanto, el procesamiento adicional asociado con la configuración de la directiva. Es útil cuando se trabaja con datos de enriquecimiento que cambian con frecuencia, a diferencia de los otros enfoques que se analizan en este artículo.</p><h2>Conclusión</h2><p>En conclusión, si bien Elasticsearch no admite operaciones de unión tradicionales, proporciona varias características que se pueden usar para lograr resultados similares. Específicamente, cubrimos cómo lograr operaciones de unión usando:</p><ol><li>La consulta <code>terms</code></li><li>El procesador <code>enrich</code> en canalizaciones de ingesta</li><li>Complemento de filtro <code>elasticsearch</code> Logstash</li><li>ES|QL <code>ENRICH</code></li><li>ES|QL <code>LOOKUP JOIN</code></li></ol><p>Es importante tener en cuenta que estos métodos tienen sus limitaciones y deben usar juiciosamente según los requisitos específicos y la naturaleza de los datos.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Entendiendo el puntaje de Elasticsearch y la API Explain]]></title>
            <description><![CDATA[Aprende sobre los mecanismos de puntuación de Elasticsearch y la función de puntuación práctica para la auditoría de la relevancia de búsquedas y mejorar la clasificación de documentos con la API Explain.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elasticsearch-scoring-and-explain-api</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elasticsearch-scoring-and-explain-api</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Mon, 05 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a70c5403064d5bbceff66a17373332362227f13c-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch es un poderoso motor de búsqueda que proporciona resultados rápidos y relevantes calculando un puntaje para cada documento del índice. Este puntaje es un factor crucial para determinar el orden de los resultados de búsqueda. En este artículo, profundizaremos en el mecanismo de puntaje de Elasticsearch y exploraremos la API Explica, que ayuda a comprender el proceso de puntaje.</p><h2>Mecanismos de puntaje en Elasticsearch</h2><p>Elasticsearch emplea por defecto un modelo de puntaje llamado Practical Scoring Function (BM25). Este modelo se basa en la teoría probabilística de recuperación de información y tiene en cuenta factores como la frecuencia de términos, la frecuencia inversa de documentos y la normalización longitud-campo. Hablemos brevemente de estos factores:</p><ol><li><strong>Frecuencia de término (TF):</strong> Esto representa el número de veces que un término aparece en un documento. Una mayor frecuencia de término indica una relación más fuerte entre el término y el documento.</li><li><strong>Frecuencia inversa del documento (IDF):</strong> Este factor mide la importancia de un término en toda la colección documental. Un término que aparece en muchos documentos se considera menos importante, mientras que un término que aparece en menos documentos se considera más importante.</li><li><strong>Normalización de longitud de campo</strong>: Este factor tiene en cuenta la longitud del campo en el que aparece el término. Los campos más cortos tienen más peso, ya que el término se considera más significativo en un campo más corto.</li></ol><h2>Usando la API Explain</h2><p>La API Explain en Elasticsearch es una herramienta valiosa para entender el proceso de puntaje. Proporciona una explicación detallada de cómo se calculó el puntaje de un documento específico. Para usar la API Explic, necesitas enviar una solicitud GET al siguiente endpoint:</p><pre><code>GET /<index>/_explain/<document_id></code></pre><p>En el cuerpo de la solicitud, debes proporcionar la consulta para la que quieres entender el puntaje. Aquí tienes un ejemplo:</p><pre><code>{
  "query": {
    "match": {
      "title": "elasticsearch"
    }
  }
}</code></pre><p>La respuesta de la API Explain incluirá un desglose detallado del proceso de puntaje, incluyendo los factores individuales (TF, IDF y la normalización de la longitud del campo) y sus contribuciones al puntaje final. Aquí tienes una respuesta de ejemplo:</p><pre><code>{
  "_index": "example_index",
  "_type": "_doc",
  "_id": "1",
  "matched": true,
  "explanation": {
    "value": 1.2,
    "description": "weight(title:elasticsearch in 0) [PerFieldSimilarity], result of:",
    "details": [
      {
        "value": 1.2,
        "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
        "details": [
          {
            "value": 2.2,
            "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
            "details": [
              {
                "value": 1,
                "description": "docFreq",
                "details": []
              },
              {
                "value": 1,
                "description": "docCount",
                "details": []
              }
            ]
          },
          {
            "value": 0.5,
            "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
            "details": [
              {
                "value": 1,
                "description": "termFreq=1.0",
                "details": []
              },
              {
                "value": 1.2,
                "description": "parameter k1",
                "details": []
              },
              {
                "value": 0.75,
                "description": "parameter b",
                "details": []
              },
              {
                "value": 1,
                "description": "avgFieldLength",
                "details": []
              },
              {
                "value": 1,
                "description": "fieldLength",
                "details": []
              }
            ]
          }
        ]
      }
    ]
  }
}</code></pre><p>En este ejemplo, la respuesta muestra que el puntaje de 1,2 es un producto del valor IDF (2,2) y el valor tfNorm (0,5). La explicación detallada ayuda a entender los factores que contribuyen al puntaje y puede ser útil para afinar la relevancia en la búsqueda.</p><h2>Conclusión</h2><p>El puntaje de elasticsearch es un aspecto fundamental para proporcionar resultados de búsqueda relevantes. Al comprender los mecanismos de puntaje y emplear la API Explice, puedes obtener información sobre los factores que afectan a los resultados de búsqueda y optimizar tus consultas para mejorar la relevancia y el rendimiento.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Plantillas de índice en Elasticsearch: Cómo usar plantillas componibles]]></title>
            <description><![CDATA[Explora cómo crear plantillas de índice componibles y de componentes en Elasticsearch para garantizar mappings consistentes y automatizar la configuración de la indexación.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/index-composable-templates</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/index-composable-templates</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Fri, 02 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/510750708df50bf79463586a1bbf35bf94acfa30-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Un índice de Elasticsearch puede configurar mediante mapeo, ajustes y alias: </p><ul><li>Las definiciones de mapeo especifican el esquema de datos.</li><li>Los ajustes ajustan el tamaño del fragmento y las frecuencias de refresco. </li><li>Se emplean alias para dar nombres alternativos al índice.</li></ul><p>Cuando indexamos un documento por primera vez o creamos un índice vacío usando la API Create Index, el índice se creará con la configuración predeterminada, sin esquema de datos y sin alias. Estos valores por defecto funcionan bastante bien en entornos de desarrollo y pruebas, pero puede que necesitemos personalizar nuestros índices para entornos de producción.</p><p>Trabajar con los mapeos y ajustes predeterminados en producción puede resultar en un índice y un rendimiento de búsqueda deficientes. Instanciar índices manualmente es un proceso tedioso y que consume mucho tiempo. Recrear tales índices en cualquier entorno es especialmente poco práctico si disponemos de un esquema de mapeo elaborado, así como de configuraciones y alias personalizados.</p><p>Por suerte, Elasticsearch nos proporciona una herramienta para aplicar automáticamente una configuración predefinida al crear índices en forma de plantillas <em>de índices</em> <em>.</em></p><h2>Plantillas de índice</h2><p>Las plantillas de índice nos permiten crear índices con una configuración definida por el usuario. Un índice puede extraer la configuración de estas plantillas, por ejemplo un número determinado de fragmentos y réplicas o mapeos de campos, durante su instanciación. Se definirá una plantilla con un patrón de nombre y alguna configuración en él. Si el nombre del índice coincide con el patrón de nombres de la plantilla, el nuevo índice se creará con la configuración definida en la plantilla.</p><p>Elasticsearch mejoró su funcionalidad de plantillas en la versión 7.8 con plantillas componibles. Esta versión más reciente ofrece plantillas de índice mucho más reutilizables, como se demuestra en este artículo.</p><h3>Tipos de plantillas de indexación</h3><p>Las plantillas de índice pueden clasificar en dos categorías:</p><ul><li><strong>Plantillas de índice (o plantillas de índice composable):</strong> Las plantillas de índice componibles pueden existir por sí solas o estar compuestas por no tener o más plantillas componentes (ver la segunda categoría).</li><li><strong>Plantillas de componentes:</strong> La plantilla de componentes es una plantilla <em>reutilizable</em> por sí sola que define la configuración requerida. Normalmente se espera que la plantilla de componentes esté asociada a una plantilla de índice. Cada una de las plantillas de componentes puede anexar con una o varias plantillas de índice. </li></ul><p>Como puedes ver en la imagen de abajo, las plantillas índice A y B comparten entre sí las plantillas de componentes (en este caso solo una, la Plantilla 3). Una plantilla de índice puede consistir en ninguna o muchas plantillas de componentes y cada una de las plantillas de componentes puede asociar a ninguna o a muchas plantillas de índice. Ambos tipos de plantillas pueden existir por separado, sin embargo, las plantillas de componentes no sirven a menos que estén adjuntas a una plantilla de índice.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/96c0aac29d3992e54a79be34e14cf909e0ca2ea9-1202x556.png" style="width: 100%;" /><p>La idea general es desarrollar un catálogo de plantillas de componentes para que una organización las emplee para diversas necesidades (por ejemplo, especificar las distintas plantillas de componentes para entornos individuales) y anexarlas a varios índices mediante las plantillas de índices componibles.</p><h2>Cómo crear plantillas componibles (indexadas)</h2><p>Elasticsearch proporciona un punto final _index_template para gestionar plantillas de índice. El usuario proporciona todos los mapeos, ajustes y alias necesarios junto con un patrón de nombres de índice en esta plantilla. Vamos a repasar un ejemplo de cómo crear una plantilla para una aplicación de microservicios <em>client-order-service</em> que es responsable de la lógica de generación de pedidos. </p><p>Supongamos que nuestro requisito es crear una plantilla para pedidos de clientes, representada con un patrón que incluye comodines: *pedidos. Se espera que esta plantilla tenga ciertos mapeos y configuraciones, como el campo order_date, así como fragmentos y números de réplica.</p><p>Cualquier índice que se empareje con esta plantilla durante su creación hereda las configuraciones definidas en esta plantilla. Por ejemplo, un índice de black_friday_orders tendrá el campo order_date, los fragmentos se pondrán en 5 y las réplicas en 2. Además, <em>todos</em> los índices creados a partir de esta plantilla heredan también un único <a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-alias/">nombre de alias</a> . Creemos este orders_template con un patrón de índice definido como *orders y con un esquema de mapeo que consiste en un solo campo de oder_date con un formato de fecha predefinido dd-MM-yyyy. El código que aparece a continuación muestra cómo crear esta plantilla de índice.</p><pre><code>PUT _index_template/orders_template
{
  "index_patterns": ["*orders"],
  "priority": 300,
  "template": {
    "mappings": {
      "properties": {
        "order_date": {
          "type": "date",
          "format":"dd-MM-yyyy"
        }
      }
    },
    "settings":{
      "number_of_shards":5,
      "number_of_replicas":2
    },
    "aliases":{
      "all_orders":{}
    }
  }
}</code></pre><p>Cuando ejecutas esta consulta en DevTools de Kibana, la plantilla se crea con el patrón índice *orders junto con el mapeo predefinido, los ajustes y un alias. El index_patterns es una variedad de patrones de coincidencia; cualquier índice que coincida con este patrón derivará la configuración de la plantilla. Puedes ejecutar lo siguiente para recuperar la plantilla persistente que debería reiterar lo que hicimos:</p><pre><code>GET _index_template/orders_template </code></pre><p>También hay una prioridad, un número positivo, definido al crear el atributo de plantilla definido en la plantilla: cada plantilla se define con una prioridad para que cualquier cambio conflictivo de diferentes plantillas se resuelva usando este valor con precedencia dada al valor de mayor prioridad. A continuación profundizaremos en la prioridad de las plantillas.</p><h2>Crear un índice con la plantilla</h2><p>Ahora que tenemos una plantilla – un plano para crear índices – el siguiente paso es crear un índice. Cuando el nombre del índice coincide con el patrón dado, las configuraciones con plantilla se aplican automáticamente. Para demostrar el punto, como muestra el código de abajo, creemos un índice completamente nuevo llamado: blackfriday_orders:</p><pre><code>PUT blackfriday_orders</code></pre><p>Como el nombre del índice (blackfriday_orders) coincide con el patrón de nombres definido en la plantilla (es decir, *órdenes), el índice debería obtener todas las configuraciones derivadas de la plantilla. Recuperemos este índice recién creado y compruebemos si esto es realmente cierto ejecutando el siguiente código:</p><pre><code>GET blackfriday_orders</code></pre><p>Esto debería volver:</p><pre><code>{
  "blackfriday_orders" : {
    "aliases" : {
      "all_orders" : { }
    },
    "mappings" : {
      "properties" : {
        "order_date" : {
          "type" : "date",
          "format" : "dd-MM-yyyy"
        }
      }
    },
    "settings" : {
      "index" : {
         ...
        "number_of_shards" : "5",
        "number_of_replicas" : "2"
      }
    }
  }
}</code></pre><p>Como indica la respuesta, la configuración del blackfriday_orders fue heredada de la plantilla. Podemos probar con varias combinaciones de los índices que hereden con éxito la configuración plantillada:</p><pre><code>PUT blackfriday_orders
PUT americaorders
PUT cancelled--orders
PUT undefined101orders</code></pre><p>Sin embargo, los siguientes índices no heredarán la configuración ya que el nombre no coincidirá con el patrón:</p><pre><code>PUT blackfriday_orders2
PUT open_orders_
PUT allorders_total</code></pre><p>Algo importante a recordar es que todos los índices derivados de una plantilla tienen el mismo alias – all_orders – en este caso. Existe un beneficio en tener este alias: podemos consultar simplemente con este único alias en lugar de en múltiples índices.</p><pre><code>GET blackfriday_orders,americaorders,undefined101orders/_search
GET all_orders/_search 
{
  "query": {
    "range": {
      "order_date": {
        "gte": "01-12-2021",
        "lte": "31-12-2021"
      }
    }
  }
}</code></pre><p>Aunque creamos una plantilla para *pedidos, se espera que cualquier índice coincidente adopte la configuración de la plantilla. Normalmente, consciente o inconscientemente, los equipos pueden crear algunas plantillas más por diversas razones. Esto significa que a veces el nombre del índice puede coincidir con dos patrones de plantilla diferentes. Elasticsearch tiene que decidir cuál de las configuraciones de esas plantillas debe aplicar. Afortunadamente, este dilema puede resolver usando la prioridad de la plantilla.</p><h2>Cómo crear plantillas de componentes</h2><p>Aprendimos sobre las plantillas de índice en la parte anterior de este artículo. Hay un par de desventajas al crear las plantillas con la configuración incorporada; una de ellas es que la configuración no es exportable para otras plantillas. Si queremos tener una configuración similar, por ejemplo para plantillas relacionadas con clientes (*clientes), puede que tengamos que recrear toda la plantilla. Eso significa que podemos estar creando docenas de ellos en una organización típica (además puede que tengas algunos más según los entornos).</p><p>Como siempre esperamos la reutilización, Elasticsearch rediseñó las plantillas teniendo en cuenta la reutilización. Las plantillas de componentes cumplen con ese requisito. Si vienes de un entorno DevOps, lo más probable es que tengas que crear índices con una configuración preestablecida para cada uno de los entornos. En lugar de aplicar manualmente cada una de estas configuraciones, puedes crear una plantilla de componentes para cada uno de los entornos.</p><p>Una plantilla de componentes no es más que un bloque reutilizable de configuraciones que podemos usar para crear más plantillas de índice. Ten en cuenta que las plantillas de componentes no tienen valor a menos que estén agrupadas con plantillas de índice. Se exponen a través de un punto final _component_template. Veamos cómo encaja todo esto.</p><h3>Configuraciones en una plantilla de índice</h3><p>Vamos a extraer los ajustes que definimos en nuestra plantilla de índice antes y crear una plantilla de componente a partir de ella. Se espera que el settings_component_template tenga cinco fragmentos principales con dos réplicas por fragmento principal. El primer paso, como muestra la lista de código a continuación, es declarar y ejecutar una plantilla de componente con esta configuración.</p><pre><code>PUT _component_template/settings_component_template
{
  "template":{
    "settings":{
      "number_of_shards":5,
      "number_of_replicas":2
    }
  }
}</code></pre><p>Como muestra el código anterior, usamos el punto final _component_template para crear una plantilla de componentes. El cuerpo de la solicitud contiene la información de la plantilla en un objeto plantilla. El settings_component_template ya está disponible para su uso en otras partes de las plantillas del índice. Una diferencia notable es que esta plantilla no define ningún patrón de índice; Simplemente es un bloque de código que configura algunas propiedades para nosotros.</p><h3>Plantilla de mapeo</h3><p>De la misma manera, creemos otra plantilla. Esta vez, extraigamos el esquema de mapeo que definimos antes en las plantillas de índice independientes. El código siguiente muestra el guion:</p><pre><code>PUT _component_template/mappings_component_template
{
  "template": {
    "mappings": {
      "properties": {
        "order_date": {
          "type": "date",
          "format":"dd-MM-yyyy"
        }
      }
    }
  }
}</code></pre><h3>Plantilla de alias</h3><p>Siguiendo el mismo flujo, también podemos tener una plantilla de componentes con los alias – dos alias (all_orders y sales_orders):</p><pre><code>PUT _component_template/aliases_component_template
{
  "template": {
    "aliases": {
      "all_orders": {},
      "sales_orders":{}
    }
  }
}</code></pre><h3>Plantilla de índice componible</h3><p>Ahora que tenemos estas tres plantillas de componentes, el siguiente paso es ponerlas en práctica. Podemos hacerlo dejando que una plantilla de índice para, por ejemplo, christmas_orders, la emplee:</p><pre><code>PUT _index_template/composed_orders_template
{
  "index_patterns": [
    "*orders"
  ],
  "priority": 500,
  "composed_of": [
    "settings_component_template",
    "mappings_component_template",
    "aliases_component_template"
  ]
}</code></pre><p>La etiqueta composed_of es una colección de todas las plantillas de componentes que conforman esta plantilla. En este caso, elegimos las plantillas de componentes de configuración, mapeo y alias. También estamos subiendo la prioridad, así que esta plantilla supera a cualquier otra. Una vez que la plantilla está lista, cualquier índice que coincida con el patrón *orders heredará la configuración de estas tres plantillas componentes.</p><p>Dicho esto, si deseamos crear una nueva plantilla, por ejemplo clientes, con solo una de las plantillas existentes (settings_component_template) y una nueva (aliases_component_template – ver más abajo), podemos hacerlo con:</p><pre><code>PUT _component_template/aliases_component_template2
{
  "template": {
    "aliases": {
      "all_customers": {}
    }
  }
}</code></pre><p>La plantilla del índice es la siguiente:</p><pre><code>PUT _index_template/composed_customers_template
{
  "index_patterns": [
    "*customers*"
  ],
  "priority": 200,
  "composed_of": [
    "settings_component_template",
    "aliases_component_template2"
  ]
}</code></pre><p>¿Viste que el settings_component_template se ha (re)empleado en dos plantillas diferentes? Ese es el poder de las plantillas de componentes.</p><h2>Prioridad de la plantilla de indexación</h2><p>Existe la posibilidad de que los desarrolladores creen múltiples plantillas de índice sin mirar el stock existente. Es importante establecer una prioridad en cada una de estas plantillas para que se emplee la de mayor prioridad. Por ejemplo, el my_orders_template_1 anula la my_orders_template_2 en el siguiente fragmento de código:</p><pre><code>PUT _index_template/my_orders_template_1
{
  "index_patterns": ["*orders"],
  "priority": 1000,
  "template": { ... }
}
PUT _index_template/my_orders_template2
{
  "index_patterns": ["*orders"],
  "priority": 300,
  "template": { ... }
}</code></pre><p>Cuando tienes varias plantillas que coinciden con los índices que se están creando, Elasticsearch aplica todas las configuraciones de todas las plantillas coincidentes pero anula cualquier cosa que tenga mayor prioridad.</p><h2>Precedencia de plantillas</h2><p>Por último, puede que te preguntes por la precedencia de las plantillas: ¿la configuración definida en la plantilla de componentes anula la que aparece en la plantilla principal del índice? ¿O al revés? Bueno, hay algunas reglas:</p><ul><li>Un índice creado con configuraciones tiene prioridad explícita sobre todo; esto significa que si creas un índice con configuración explícita, no esperes que las plantillas las sobreescriban.</li><li>Las plantillas heredadas (plantillas creadas antes de la versión 7.8) tienen una prioridad inferior a las plantillas componibles.</li></ul><h2>Resumen</h2><ul><li>Un índice contiene mapeos, configuraciones y alias: los mapeos definen el esquema de campos, los ajustes establecen los parámetros del índice como el número de fragmentos y réplicas, y los alias dan nombres alternativos al índice.</li><li>Las plantillas nos permiten crear índices con configuraciones predefinidas. Nombrar un índice con un nombre que coincida con el patrón de índice definido en una plantilla específica configurará automáticamente ese índice según la plantilla.</li><li>Elasticsearch introdujo plantillas de índice componibles en la versión 7.8. Las plantillas de índice componibles permiten modularidad y versionado de las plantillas.</li><li>Las plantillas componibles consisten en no tener o más plantillas de componentes.</li><li>Una plantilla de índice también puede tener su propia configuración definida.</li><li>Una plantilla de componente es una plantilla reutilizable con configuración predefinida, igual que una plantilla de índice componible.</li><li>Sin embargo, se espera que las plantillas de componentes formen parte de una plantilla de índice; No sirven de nada si no están &quot;compuestas&quot; en una plantilla de índice.</li><li>Las plantillas de componentes no tienen un patrón de índice definido, lo que es otra razón por la que se &quot;espera&quot; que formen parte de una plantilla de índice.</li><li>Cada una de las plantillas tiene una prioridad: un número positivo. Cuanto mayor sea el número, mayor es la precedencia para que se aplique esa plantilla.</li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Búsqueda de elasticsearch mediante dos campos]]></title>
            <description><![CDATA[Explora técnicas para buscar por dos campos, incluidas consultas multi-coincidencia, consultas booleanas y aumento de campos en tiempo de consulta.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elasticsearch-search-by-two-fields</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elasticsearch-search-by-two-fields</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Wed, 30 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5d04bbcfc3925f48f3487ea4c7e0dd2205316d0-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Buscar en varios campos en Elasticsearch es un requisito común en muchas aplicaciones. En este artículo, exploraremos técnicas avanzadas para realizar búsquedas por dos campos, incluyendo consultas multi-coincidencia, consultas bool y aumento de campos en tiempo de consulta. Estas técnicas te ayudarán a crear resultados de búsqueda más precisos y relevantes para tus usuarios.</p><h2>Técnicas avanzadas para realizar búsquedas en dos campos</h2><h3>1. Consulta multi-coincidencia</h3><p>Una consulta multi-coincidencia te permite buscar una sola cadena de consulta en varios campos. Esto es útil cuando quieres encontrar documentos que contengan la cadena de consulta dada en cualquiera de los dos campos. Aquí tienes un ejemplo de consulta multi-coincidencia que busca el término &quot;ejemplo&quot; en los campos &quot;título&quot; o &quot;descripción&quot;:</p><pre><code>{
  "query": {
    "multi_match": {
      "query": "example",
      "fields": ["title", "description"]
    }
  }
}</code></pre><h3>2. Consulta de bool</h3><p>Una consulta bool permite combinar varias consultas usando lógica booleana. Puedes usar la cláusula &quot;debería&quot; para buscar documentos que coincidan con la consulta en cualquiera de los dos campos. Aquí tienes un ejemplo de consulta bool que busca el término &quot;ejemplo&quot; en los campos &quot;título&quot; y &quot;descripción&quot;:</p><pre><code>{
  "query": {
    "bool": {
      "should": [
        {"match": {"title": "example"}},
        {"match": {"description": "example"}}
      ]
    }
  }
}</code></pre><h3>3. Aumento de campos en tiempo de consulta</h3><p>A veces, puede que quieras dar más importancia a un campo que a otro durante la búsqueda. Puedes conseguirlo aplicando un factor de mejora al campo en el momento de la consulta. Un valor de aumento más alto da más peso al campo, haciendo que sea más probable que influya en el puntaje final de búsqueda. Aquí tienes un ejemplo de consulta multi-coincidencia con un factor de impulso aplicado al campo &quot;título&quot;:</p><pre><code>{
  "query": {
    "multi_match": {
      "query": "example",
      "fields": ["title^3", "description"]
    }
  }
}</code></pre><p>En este ejemplo, el campo &quot;título&quot; tiene un factor de mejora de 3, lo que lo hace tres veces más importante que el campo &quot;descripción&quot; para determinar el puntaje de búsqueda.</p><h3>4. Combinar consultas con diferentes factores de impulso</h3><p>También puedes combinar varias consultas con diferentes factores de boost usando una consulta bool. Esto te permite afinar la importancia de cada campo en los resultados de búsqueda. Aquí tienes un ejemplo de consulta bool con diferentes factores de boost aplicados a los campos &quot;título&quot; y &quot;descripción&quot;:</p><pre><code>{
  "query": {
    "bool": {
      "should": [
        {"match": {"title": {"query": "example", "boost": 3}}},
        {"match": {"description": {"query": "example", "boost": 1}}}
      ]
    }
  }
}</code></pre><p>En este ejemplo, el campo &quot;título&quot; tiene un factor de mejora de 3, mientras que el campo de &quot;descripción&quot; tiene un factor de aumento de 1.</p><h2>Conclusión</h2><p>La búsqueda mediante dos campos en Elasticsearch se puede lograr mediante técnicas avanzadas como consultas multi-coincidencia, consultas bool y aumento de campos en tiempo de consulta. Combinando estas técnicas, puedes crear resultados de búsqueda más precisos y relevantes para tus usuarios. Experimenta con diferentes combinaciones de consultas y factores de mejora para encontrar la configuración óptima de búsqueda para tu caso de uso específico.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[​​Construcción de un flujo de trabajo RAG usando LangGraph y Elasticsearch]]></title>
            <description><![CDATA[Aprende a configurar y personalizar una plantilla de agente de recuperación LangGraph con Elasticsearch para crear un flujo de trabajo RAG que permita una recuperación eficiente de datos y respuestas impulsadas por IA.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/build-rag-workflow-langgraph-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/build-rag-workflow-langgraph-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Fri, 25 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/17b7e7b336f73e232375d1add582ae5f6c52a279-1440x840.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La <a href="https://github.com/langchain-ai/retrieval-agent-template">plantilla del agente de recuperación de LangGraph</a> es un proyecto inicial desarrollado por LangChain para facilitar la creación de sistemas de respuesta a preguntas basados en la recuperación empleando LangGraph en LangGraph Studio. Esta plantilla está preconfigurada para integrar perfectamente con Elasticsearch, lo que permite a los desarrolladores crear rápidamente agentes que puedan indexar y recuperar documentos de manera eficiente.</p><p>Este blog se centra en la ejecución y personalización de la plantilla del agente de recuperación de LangChain mediante LangGraph Studio y LangGraph CLI. La plantilla proporciona un marco para crear aplicaciones de generación aumentada de recuperación (RAG), aprovechando varios backends de recuperación como Elasticsearch.</p><p>Te guiaremos a través de la instalación, la configuración del entorno y la ejecución de la plantilla de manera eficiente con Elastic mientras personalizas el flujo del agente.</p><h2>Prerrequisitos</h2><p>Antes de continuar, cerciorar de tener instalado lo siguiente:</p><ul><li>Despliegue de Elasticsearch Cloud o despliegue de Elasticsearch local (o crea una <a href="https://www.elastic.co/es/cloud/cloud-trial-overview">prueba gratis </a>de 14 días en Elastic Cloud) - Versión 8.0.0 o superior</li><li>Python 3.9+</li><li>Acceso a un proveedor de LLM como <a href="https://cohere.com/">Cohere</a> (empleado en esta guía), <a href="https://openai.com/">OpenAI</a> o <a href="https://www.anthropic.com/claude">Anthropic/Claude</a></li></ul><h2>Creación de la aplicación LangGraph</h2><h3>1. Instalar la CLI de LangGraph</h3><pre><code>pip install --upgrade "langgraph-cli[inmem]"</code></pre><h3>2. Crear la aplicación LangGraph a partir de retrieval-agent-template</h3><pre><code>mkdir lg-agent-demo
cd lg-agent-demo
langgraph new lg-agent-demo </code></pre><p><em>Se le presentará un menú interactivo que le permitirá elegir entre una lista de plantillas disponibles. </em>Seleccione 4 para el Agente de recuperación y 1 para Python, como se muestra a continuación:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6a41a41f95c2477c67810adc7be46d91faf06878-1600x407.png" style="width: 100%;" /><ul><li><strong>Solución de problemas</strong>: Si encuentra el error &quot;urllib.error.URLError: error &lt;urlopen [SSL: CERTIFICATE_VERIFY_FAILED] error de verificación del certificado: no se puede obtener el certificado del emisor local (_ssl.c:1000)&gt; “</li></ul><p>Ejecute el comando Instalar certificado de Python para resolver el problema, como se muestra a continuación.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/83ec238136c41738457299fd42c83aff32eb5b97-1407x75.png" style="width: 100%;" /><h3>3. Instalar dependencias</h3><p>En la raíz de su nueva aplicación LangGraph, cree un entorno virtual e instale las dependencias en modo <code>edit</code> para que el servidor emplee sus cambios locales:</p><pre><code>#For Mac
python3 -m venv lg-demo
source lg-demo/bin/activate 
pip install -e .

#For Windows
python3 -m venv lg-demo
lg-demo\Scripts\activate 
pip install -e .</code></pre><h2>Configuración del entorno</h2><h3>1. Crear un entorno .. archivo</h3><p>El archivo <code>.env</code> contiene claves y configuraciones de API para que la aplicación pueda conectarse al proveedor de recuperación y LLM elegido. Genere un nuevo archivo <code>.env</code> duplicando la configuración de ejemplo:</p><pre><code>cp .env.example .env</code></pre><h3>2. Configurar el .env archivo</h3><p>El archivo <code>.env</code> viene con un conjunto de configuraciones predeterminadas. Puede actualizarlo agregando las claves y los valores de API necesarios según su configuración. Las claves que no sean relevantes para tu caso de uso se pueden dejar sin cambios o quitar.</p><pre><code># To separate your traces from other applications
LANGSMITH_PROJECT=retrieval-agent

# LLM choice (set the API key for your selected provider):
ANTHROPIC_API_KEY=your_anthropic_api_key
FIREWORKS_API_KEY=your_fireworks_api_key
OPENAI_API_KEY=your_openai_api_key

# Retrieval provider (configure based on your chosen service):

## Elastic Cloud:
ELASTICSEARCH_URL=https://your_elastic_cloud_url
ELASTICSEARCH_API_KEY=your_elastic_api_key

## Elastic Local:
ELASTICSEARCH_URL=http://host.docker.internal:9200
ELASTICSEARCH_USER=elastic
ELASTICSEARCH_PASSWORD=changeme

## Pinecone:
PINECONE_API_KEY=your_pinecone_api_key
PINECONE_INDEX_NAME=your_pinecone_index_name

## MongoDB Atlas:
MONGODB_URI=your_mongodb_connection_string

# Cohere API key:
COHERE_API_KEY=your_cohere_api_key</code></pre><ul><li>Ejemplo <code>.env</code> archivo (con Elastic Cloud y Cohere)</li></ul><p>A continuación, se muestra un ejemplo de configuración <code>.env</code> para usar <strong>Elastic Cloud</strong> como proveedor de recuperación y <strong>Cohere</strong> como LLM, como se muestra en este blog:</p><pre><code># To separate your traces from other applications
LANGSMITH_PROJECT=retrieval-agent
#Retrieval Provider
# Elasticsearch configuration
ELASTICSEARCH_URL=elastic-url:443
ELASTICSEARCH_API_KEY=elastic_api_key
# Cohere API key
COHERE_API_KEY=cohere_api_key</code></pre><p><em>Nota: Si bien esta guía usa Cohere tanto para la generación de respuestas como para las incrustaciones, puede usar otros proveedores de LLM como <strong>OpenAI,</strong> <strong>Claude</strong>o incluso un modelo de LLM local, según su caso de uso. Cerciorar de que cada tecla que desea emplear esté presente y configurada correctamente en el  archivo.env.</em></p><h3>3. Actualizar archivo de configuración -configuration.py </h3><p>Luego de configurar tu archivo <code>.env</code> con las claves de API adecuadas, el siguiente paso es actualizar la configuración del modelo predeterminado de tu aplicación. La actualización de la configuración garantiza que el sistema use los servicios y modelos que especificó en el archivo <code>.env</code> .</p><p>Vaya al archivo de configuración:</p><pre><code> cd src/retrieval_graph</code></pre><p>El archivo <code>configuration.py</code> contiene la configuración predeterminada del modelo empleada por el agente de recuperación para tres tareas principales:</p><ul><li><strong>Modelo de incrustación</strong> : convierte documentos en representaciones vectoriales</li><li><strong>Modelo de consulta</strong> : procesa la consulta del usuario en un vector</li><li><strong>Modelo de respuesta</strong> : genera la respuesta final</li></ul><p>De forma predeterminada, el código emplea modelos de <strong>OpenAI</strong> (por ejemplo, <code>openai/text-embedding-3-small</code>) y <strong>Anthropic</strong> (por ejemplo, <code>anthropic/claude-3-5-sonnet-20240620 and anthropic/claude-3-haiku-20240307</code>).<br/><br/>En este blog, estamos cambiando al uso de modelos Cohere. Si ya está empleando OpenAI o Anthropic, no se necesitan cambios.</p><h4>Ejemplos de cambios (usando Cohere):</h4><p>Abra <code>configuration.py</code> y modifique los valores predeterminados del modelo como se muestra a continuación:</p><pre><code>…
 embedding_model: Annotated[
       str,
       {"__template_metadata__": {"kind": "embeddings"}},
   ] = field(
       default="cohere/embed-english-v3.0",
…
response_model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field(
       default="cohere/command-r-08-2024",
…
query_model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field(
       default="cohere/command-r-08-2024",
       metadata={</code></pre><h2>Ejecutando el agente de recuperación con la CLI de LangGraph</h2><h3>1. Inicie el servidor LangGraph</h3><pre><code>cd lg-agent-demo
langgraph dev</code></pre><p>Esto iniciará el servidor de la API de LangGraph localmente. Si esto se ejecuta correctamente, debería ver algo como:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e3c3344b24651067e2d0892d870feca505b3be35-1494x542.png" style="width: 100%;" /><p>Abra la URL de la interfaz de usuario de Studio.</p><p>Hay dos gráficos disponibles:</p><ul><li><strong>Gráfico de recuperación</strong>: Recupera datos de Elasticsearch y responde a la consulta usando un LLM.</li><li><strong>Gráfico indexador</strong>: Indexa documentos en Elasticsearch y genera incrustaciones usando un LLM.</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/99b579ca44b2d13f61da7e1de5886ea5b9a5e16f-1600x1009.png" style="max-width:100%; margin-bottom:1em;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f54d182d2789b6a4d113b0c88063531ea0b6de5d-1600x1009.png" style="max-width:100%; margin-bottom:1em;" /><h3>2. Configuración del grafo indexador</h3><ul><li>Abre el gráfico del indexador.</li><li>Haz clic en gestionar asistentes.<ul><li>Haz clic <strong>en &#x27;Agregar nuevo asistente</strong>&#x27;, introduce los datos del usuario según lo especificado y luego cierra la ventana.</li></ul></li></ul><pre><code>{"user_id": "101"}</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6f87958e40760ac9671639f4995962bcea090991-1600x1139.png" style="max-width:100%; margin-bottom:1em;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8f802f6b65b3f06d7d1a521c3f40385c6006413b-1422x1212.png" style="max-width:100%; margin-bottom:1em;" /><h3>3. Indexación de documentos de muestra</h3><ul><li>Indexe los siguientes documentos de muestra, que representan un reporte trimestral hipotético para la organización NoveTech:</li></ul><pre><code>[
  {    "page_content": "NoveTech Solutions Q1 2025 Report - Revenue: $120.5M, Net Profit: $18.2M, EPS: $2.15. Strong AI software launch and $50M government contract secured."
  },
  {
    "page_content": "NoveTech Solutions Business Highlights - AI-driven analytics software gained 15% market share. Expansion into Southeast Asia with two new offices. Cloud security contract secured."
  },
  {
    "page_content": "NoveTech Solutions Financial Overview - Operating expenses at $85.3M, Gross Margin 29.3%. Stock price rose from $72.5 to $78.3. Market Cap reached $5.2B."
  },
  {
    "page_content": "NoveTech Solutions Challenges - Rising supply chain costs impacting hardware production. Regulatory delays slowing European expansion. Competitive pressure in cybersecurity sector."
  },
  {
    "page_content": "NoveTech Solutions Future Outlook - Expected revenue for Q2 2025: $135M. New AI chatbot and blockchain security platform launch planned. Expansion into Latin America."
  },
  {
    "page_content": "NoveTech Solutions Market Performance - Year-over-Year growth at 12.7%. Stock price increase reflects investor confidence. Cybersecurity and AI sectors remain competitive."
  },
  {
    "page_content": "NoveTech Solutions Strategic Moves - Investing in R&D to enhance AI-driven automation. Strengthening partnerships with enterprise cloud providers. Focusing on data privacy solutions."
  },
  {
    "page_content": "NoveTech Solutions CEO Statement - 'NoveTech Solutions continues to innovate in AI and cybersecurity. Our growth strategy remains strong, and we foresee steady expansion in the coming quarters.'"
  }
]</code></pre><p>Una vez indexados los documentos, verá un mensaje de eliminación en el hilo, como se muestra a continuación.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fd3a1efd64cb54d54ea56ef5055249dd066d5708-1600x854.png" style="width: 100%;" /><h3>4. Ejecutar el grafo de recuperación</h3><ul><li>Cambia al gráfico de recuperación.</li><li>Introduzca la siguiente consulta de búsqueda:</li></ul><pre><code>What was NovaTech Solutions total revenue in Q1 2025?</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/eb4d8ddfe0effd7e1868fba921b8ef13f7baf27a-1600x755.png" style="width: 100%;" /><p>El sistema devolverá los documentos relevantes y proporcionará una respuesta exacta basada en los datos indexados.</p><h2>Personalizar el agente de recuperación</h2><p>Para mejorar la experiencia del usuario, introducimos un paso de personalización en el gráfico de recuperación para predecir las siguientes tres preguntas que un usuario podría hacer. Esta predicción se basa en:</p><ul><li>Contexto de los documentos recuperados</li><li>Interacciones anteriores de los usuarios</li><li>Última consulta de usuario</li></ul><p>Se requieren los siguientes cambios de código para implementar la función de predicción de consultas:</p><h3>1. Actualización graph.py</h3><ul><li>Agregue <code>predict_query</code> función:</li></ul><pre><code>async def predict_query(
   state: State, *, config: RunnableConfig
) -> dict[str, list[BaseMessage]]:
   logger.info(f"predict_query predict_querypredict_query predict_query predict_query predict_query")  # Log the query

   configuration = Configuration.from_runnable_config(config)
   prompt = ChatPromptTemplate.from_messages(
       [
           ("system", configuration.predict_next_question_prompt),
           ("placeholder", "{messages}"),
       ]
   )
   model = load_chat_model(configuration.response_model)
   user_query = state.queries[-1] if state.queries else "No prior query available"
   logger.info(f"user_query: {user_query}")
   logger.info(f"statemessage: {state.messages}")
   #human_messages = [msg for msg in state.message if isinstance(msg, HumanMessage)]

   message_value = await prompt.ainvoke(
       {
           "messages": state.messages,
           "user_query": user_query,  # Use the most recent query as primary input
           "system_time": datetime.now(tz=timezone.utc).isoformat(),
       },
       config,
   )

   next_question = await model.ainvoke(message_value, config)
   return {"next_question": [next_question]}</code></pre><ul><li>Modifique <code>respond</code> función para devolver <strong><code>response</code></strong> Object , en lugar de message:</li></ul><pre><code>async def respond(
   state: State, *, config: RunnableConfig
) -> dict[str, list[BaseMessage]]:
   """Call the LLM powering our "agent"."""
   configuration = Configuration.from_runnable_config(config)
   # Feel free to customize the prompt, model, and other logic!
   prompt = ChatPromptTemplate.from_messages(
       [
           ("system", configuration.response_system_prompt),
           ("placeholder", "{messages}"),
       ]
   )
   model = load_chat_model(configuration.response_model)

   retrieved_docs = format_docs(state.retrieved_docs)
   message_value = await prompt.ainvoke(
       {
           "messages": state.messages,
           "retrieved_docs": retrieved_docs,
           "system_time": datetime.now(tz=timezone.utc).isoformat(),
       },
       config,
   )
   response = await model.ainvoke(message_value, config)
   # We return a list, because this will get added to the existing list
   return {"response": [response]}</code></pre><ul><li>Actualice la estructura del gráfico para agregar un nuevo nodo y borde para predict_query:</li></ul><pre><code>builder.add_node(generate_query)
builder.add_node(retrieve)
builder.add_node(respond)
builder.add_node(predict_query)
builder.add_edge("__start__", "generate_query")
builder.add_edge("generate_query", "retrieve")
builder.add_edge("retrieve", "respond")
builder.add_edge("respond", "predict_query")</code></pre><h3>2. Actualización prompts.py</h3><ul><li>Prompt de creación para predicción de guery en <code>prompts.py</code>:</li></ul><pre><code>PREDICT_NEXT_QUESTION_PROMPT = """Given the user query and the retrieved documents, suggest the most likely next question the user might ask.

**Context:**
- Previous Queries:
{previous_queries}

- Latest User Query: {user_query}

- Retrieved Documents:
{retrieved_docs}

**Guidelines:**
1. Do not suggest a question that has already been asked in previous queries.
2. Consider the retrieved documents when predicting the next logical question.
3. If the user's query is already fully answered, suggest a relevant follow-up question.
4. Keep the suggested question natural and conversational.
5. Suggest at least 3 question

System time: {system_time}"""</code></pre><h3>3. Actualización configuration.py</h3><ul><li>Agregar <code>predict_next_question_prompt</code>:</li></ul><pre><code>predict_next_question_prompt: str = field(
       default=prompts.PREDICT_NEXT_QUESTION_PROMPT,
       metadata={"description": "The system prompt used for generating responses."},
   )</code></pre><h3>4. Actualización state.py</h3><ul><li>Agregue los siguientes atributos:</li></ul><pre><code>response: Annotated[Sequence[AnyMessage], add_messages]
next_question : Annotated[Sequence[AnyMessage], add_messages]</code></pre><h3>5. Volver a ejecutar el grafo de recuperación</h3><ul><li>Vuelva a introducir la siguiente consulta de búsqueda:</li></ul><pre><code>What was NovaTech Solutions total revenue in Q1 2025?</code></pre><p>El sistema procesará la entrada y predecirá tres preguntas relacionadas que los usuarios podrían hacer, como se muestra a continuación.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/70e855a2e4edc0ba5a147588df0de30eb081d053-1600x777.png" style="width: 100%;" /><h2>Conclusión</h2><p>La integración de la plantilla del agente de recuperación dentro de LangGraph Studio y CLI proporciona varios beneficios clave:</p><ul><li><strong>Desarrollo acelerado</strong>: las herramientas de plantilla y visualización agilizan la creación y depuración de flujos de trabajo de recuperación, lo que reduce el tiempo de desarrollo.</li><li><strong>Implementación perfecta</strong>: la compatibilidad integrada con las API y el escalado automático garantiza una implementación fluida en todos los entornos.</li><li><strong>Actualizaciones fáciles:</strong> Modificar los flujos de trabajo, agregar nuevas funcionalidades e integrar nodos adicionales es simple, lo que facilita escalar y mejorar el proceso de recuperación.</li><li><strong>Memoria persistente</strong>: el sistema conserva los estados y el conocimiento de los agentes, lo que mejora la coherencia y la confiabilidad.</li><li><strong>Modelado de flujo de trabajo flexible:</strong> los desarrolladores pueden personalizar la lógica de recuperación y las reglas de comunicación para casos de uso específicos.</li><li><strong>Interacción y depuración en tiempo real</strong>: la capacidad de interactuar con los agentes en ejecución permite realizar pruebas y resolver problemas de manera eficiente.</li></ul><p>Al aprovechar estas características, las organizaciones pueden crear sistemas de recuperación poderosas, eficientes y escalables que mejoren la accesibilidad de los datos y la experiencia del usuario.</p><p>El código fuente completo de este proyecto está disponible en <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/langraph-retrieval-agent-template-demo">GitHub</a>.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cómo implementar una Mejor Cuantización Binaria (BBQ) en tu caso de uso]]></title>
            <description><![CDATA[Explora por qué implementarías Better Binary Quantization (BBQ) en tu caso de uso y cómo hacerlo.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/bbq-implementation-into-use-case</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/bbq-implementation-into-use-case</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Sachin Frayne]]></dc:creator>
            <pubDate>Wed, 23 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/66842055367cdd795532b01c167f2a4b03dc65e3-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>La búsqueda vectorial proporciona la base al implementar la búsqueda semántica de texto o la búsqueda de similitud de imágenes, videos o audio. Con la búsqueda vectorial, los vectores son representaciones matemáticas de datos que pueden ser enormes y, a veces, lentos. Better Binary Quantization (en lo sucesivo, BBQ) funciona como un método de compresión para vectores. Le permite encontrar las coincidencias correctas mientras reduce los vectores para que sean más rápidos de buscar y procesar. Este artículo cubrirá BBQ y rescore_vector, un campo solo disponible para índices cuantificados que vuelve a calificar automáticamente los vectores.</p><p>Todas las consultas y salidas completas mencionadas en este artículo se pueden encontrar en nuestro <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/how-and-why-bbq">repositorio de código de Elasticsearch Labs</a>.</p><h2>¿Por qué implementar Better Binary Quantization (BBQ) en tu caso de uso?</h2><blockquote>Nota: para una comprensión profunda de cómo funcionan las matemáticas detrás del asado, consulte la <a href="https://www.elastic.co/es/search-labs/blog/bbq-implementation-into-use-case#further-learning">sección &quot;Aprendizaje adicional&quot;</a> a continuación. Para los propósitos de este blog, la atención se centra en la implementación.</blockquote><p>Aunque las matemáticas son interesantes, son cruciales si quieres comprender completamente por qué tus búsquedas vectoriales siguen siendo precisas. En última instancia, todo esto se reduce a la compresión, ya que resulta que con los algoritmos actuales de búsqueda vectorial estás limitado por la velocidad de lectura de los datos. Por lo tanto, si puedes meter todos esos datos en la memoria, obtienes un aumento significativo de velocidad en comparación con leer desde el almacenamiento (<a href="https://sre.google/static/pdf/rule-of-thumb-latency-numbers-letter.pdf">la memoria es aproximadamente 200 veces más rápida que los SSD</a>).</p><p>Hay algunas cosas a tener en cuenta:</p><ul><li>Los índices basados en gráficos como <a href="https://arxiv.org/pdf/1603.09320">HNSW</a> (Hierarchical Navigable Small World) son los más rápidos para la recuperación de vectores.<ul><li>HNSW: Un algoritmo de búsqueda aproximado del vecino más cercano que construye una estructura de gráficos multicapa para permitir búsquedas eficientes de similitud de alta dimensión.</li></ul></li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/590f3b3c72a76023a38a0436cd9ff90a9f80e936-1964x1262.png" style="width: 100%;" /><ul><li>HNSW está fundamentalmente limitado en velocidad por la velocidad de lectura de datos de la memoria o, en el peor de los casos, del almacenamiento.<ul><li>Idealmente, desea poder cargar todos sus vectores almacenados en la memoria.</li></ul></li><li>Los modelos de incrustación generalmente producen vectores con precisión float32, 4 bytes por número de punto flotante.</li><li>Y finalmente, dependiendo de cuántos vectores y / o dimensiones tenga, puede quedar sin memoria muy rápidamente para mantener todos sus vectores.</li></ul><p>Dando esto por sentado, ve que surge un problema rápidamente una vez que comienza a ingerir millones o incluso miles de millones de vectores, cada uno con potencialmente cientos o incluso miles de dimensiones. La sección titulada &quot;<a href="https://www.elastic.co/es/search-labs/blog/bbq-implementation-into-use-case#approximate-numbers-on-the-compression-ratios">Números aproximados en las relaciones de compresión</a>&quot; proporciona algunos números aproximados.</p><h2>¿Qué necesitas para empezar?</h2><p>Para comenzar, necesitará lo siguiente:</p><ul><li>Si usas Elastic Cloud o en las instalaciones, necesitarás una versión de Elasticsearch superior a la 8.18. Si bien BBQ se introdujo en 8.16, en este artículo, usará <code>vector_rescore</code>, que se introdujo en 8.18.</li><li>Además, también deberá cerciorar de que haya un <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/8.18/ml-settings.html">nodo de aprendizaje automático (ML)</a> en el clúster. (Nota: se necesita un nodo de ML con un mínimo de 4 GB para cargar el modelo, pero es probable que necesite nodos mucho más grandes para cargas de trabajo de producción completas).</li><li>Si emplea Serverless, deberá seleccionar una instancia optimizada para vectores.</li><li>También necesitará un nivel básico de conocimiento sobre bases de datos vectoriales. Si aún no estás familiarizado con los conceptos de búsqueda vectorial en Elastic, es posible que desees consultar primero los siguientes recursos:<ul><li><a href="https://www.elastic.co/es/search-labs/blog/elastic-vector-database-practical-example">Navegación por una base de datos vectorial elástica</a></li><li><a href="https://www.elastic.co/es/blog/retrieval-augmented-generation-explained">Las grandes ideas detrás de la generación aumentada de recuperación</a></li></ul></li></ul><h2>Mejor implementación de cuantización binaria (BBQ)</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4d388078495566f0527e931e0c2e38facdce83c6-1503x748.png" style="width: 100%;" /><p>Para simplificar este blog, empleará funciones integradas cuando estén disponibles. En este caso, tienes el modelo de incrustación de vectores <a href="https://www.elastic.co/es/guide/en/machine-learning/8.17/ml-nlp-e5.html"><code>.multilingual-e5-small</code></a> que se ejecutará directamente dentro de Elasticsearch en un nodo de aprendizaje automático. Tenga en cuenta que puede reemplazar el modelo <code>text_embedding</code> con el incrustador de su elección (<a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/8.18/infer-service-openai.html">OpenAI,</a> <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/8.18/infer-service-google-ai-studio.html">Google AI Studio</a>, <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/8.18/infer-service-cohere.html">Cohere</a> y muchos más). Si su modelo preferido aún no está integrado, también puede <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/8.18/bring-your-own-vectors.html">traer sus propias incrustaciones de vectores densos</a>).</p><p>En primer lugar, deberá crear un punto de enlace de inferencia para generar vectores para un fragmento de texto determinado. Ejecutarás todos estos comandos desde la <a href="https://www.elastic.co/es/guide/en/kibana/8.18/console-kibana.html">consola de herramientas de desarrollo de Kibana</a>. Este comando descargará el <code>.multilingual-e5-small</code>. Si aún no existe, configurará su punto final; Esto puede tardar un minuto en ejecutar. Puede ver la salida esperada en el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/01-create-an-inference-endpoint-output.json">archivo 01-create-an-inference-endpoint-output.json</a> en la carpeta Salidas. </p><pre><code>PUT _inference/text_embedding/my_e5_model
{
  "service": "elasticsearch",
  "service_settings": {
    "num_threads": 1,
    "model_id": ".multilingual-e5-small",
    "adaptive_allocations": {
      "enabled": true,
      "min_number_of_allocations": 1
    }
  }
}</code></pre><p>Una vez que esto regresó, su modelo se configurará y podrá probar que el modelo funciona como se espera con el siguiente comando. Puede ver el resultado esperado en el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/02-embed-text-output.json">archivo 02-embed-text-output.json</a> en la carpeta Salidas.</p><pre><code>POST _inference/text_embedding/my_e5_model
{
  "input": "my awesome piece of text"
}</code></pre><p>Si tiene problemas relacionados con el modelo capacitado que no se asigna a ningún nodo, es posible que deba iniciar el modelo manualmente.</p><pre><code>POST _ml/trained_models/.multilingual-e5-small/deployment/_start</code></pre><p>Ahora vamos a crear una nueva asignación con 2 propiedades, un campo de texto estándar (<code>my_field</code>) y un campo vectorial denso (<code>my_vector</code>) con 384 dimensiones para que coincida con la salida del modelo de incrustación. También anulará el <code>index_options.type to bbq_hnsw</code>. Puede ver el resultado esperado en el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/03-create-byte-qauntized-index-output.json">archivo 03-create-byte-qauntized-index-output.json</a> en la carpeta Salidas.</p><pre><code>PUT bbq-my-byte-quantized-index
{
  "mappings": {
    "properties": {
      "my_field": {
        "type": "text"
      },
      "my_vector": {
        "type": "dense_vector",
        "dims": 384,
        "index_options": {
          "type": "bbq_hnsw"
        }
      }
    }
  }
}</code></pre><p>Para cerciorarte de que Elasticsearch genere tus vectores, puedes usar una <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/8.18/ingest.html">canalización de ingesta</a>. Esta canalización requerirá 3 cosas: el punto final, (<code>model_id</code>), el <code>input_field</code> para el que desea crear vectores y el <code>output_field</code> en el que almacenar esos vectores. El primer comando siguiente creará una canalización de ingesta de inferencia, que usa el <a href="https://www.elastic.co/es/guide/en/elasticsearch/reference/current/inference-apis.html">servicio de inferencia </a>en segundo plano, y el segundo probará que la canalización funciona correctamente. Puede ver la salida esperada en el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/04-create-and-simulate-ingest-pipeline-output.json">archivo 04-create-and-simulate-ingest-pipeline-output.json</a> en la carpeta Salidas. </p><pre><code>PUT _ingest/pipeline/my_inference_pipeline
{
  "processors": [
    {
      "inference": {
        "model_id": "my_e5_model",
        "input_output": [
          {
            "input_field": "my_field",
            "output_field": "my_vector"
          }
        ]
      }
    }
  ]
}

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

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

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

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

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

GET my-raw-vector-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}</code></pre><h2>Números aproximados en las relaciones de compresión</h2><p>Los requisitos de almacenamiento y memoria pueden convertir rápidamente en un desafío importante cuando se trabaja con la búsqueda vectorial. El siguiente desglose ilustra cómo las diferentes técnicas de cuantificación reducen significativamente la huella de memoria de los datos vectoriales.</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Vectores (V)</td><td style="border: 1px solid #ccc; padding: 4px;">Dimensiones (D)</td><td style="border: 1px solid #ccc; padding: 4px;">sin procesar (V x P x 4)</td><td style="border: 1px solid #ccc; padding: 4px;">int8 (V x (D x 1 + 4))</td><td style="border: 1px solid #ccc; padding: 4px;">int4 (V x (D x 0.5 + 4))</td><td style="border: 1px solid #ccc; padding: 4px;">asado (V x (D x 0.125 + 4))</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">10,000,000</td><td style="border: 1px solid #ccc; padding: 4px;">384</td><td style="border: 1px solid #ccc; padding: 4px;">14,31 GB</td><td style="border: 1px solid #ccc; padding: 4px;">3,61 GB</td><td style="border: 1px solid #ccc; padding: 4px;">1,83 GB</td><td style="border: 1px solid #ccc; padding: 4px;">0,58 GB</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">50,000,000</td><td style="border: 1px solid #ccc; padding: 4px;">384</td><td style="border: 1px solid #ccc; padding: 4px;">71,53 GB</td><td style="border: 1px solid #ccc; padding: 4px;">18,07 GB</td><td style="border: 1px solid #ccc; padding: 4px;">9,13 GB</td><td style="border: 1px solid #ccc; padding: 4px;">2,89 GB</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">100,000,000</td><td style="border: 1px solid #ccc; padding: 4px;">384</td><td style="border: 1px solid #ccc; padding: 4px;">143,05 GB</td><td style="border: 1px solid #ccc; padding: 4px;">36,14 GB</td><td style="border: 1px solid #ccc; padding: 4px;">18,25 GB</td><td style="border: 1px solid #ccc; padding: 4px;">5,77 GB</td>
                    </tr>
              </tbody>
            </table>
          <h2>Conclusión</h2><p>BBQ es una optimización que puede aplicar a sus datos vectoriales para la compresión sin sacrificar la precisión. Funciona convirtiendo vectores en bits, lo que le permite buscar los datos de manera efectiva y le permite escalar sus flujos de trabajo de IA para acelerar las búsquedas y optimizar el almacenamiento de datos.</p><h2>Aprendizaje adicional</h2><p>Si está interesado en obtener más información sobre el asado, cerciorar de consultar los siguientes recursos:</p><ul><li><a href="https://www.elastic.co/es/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Cuantificación binaria (BBQ) en Lucene y Elasticsearch</a></li><li><a href="https://www.elastic.co/es/search-labs/blog/bit-vectors-elasticsearch-bbq-vs-pq">Mejor cuantización binaria (BBQ) frente a cuantificación de productos</a></li><li><a href="https://www.elastic.co/es/search-labs/blog/optimized-scalar-quantization-elasticsearch">Cuantización escalar optimizada: cuantificación binaria aún mejor</a></li><li><a href="https://www.youtube.com/watch?v=04NzMt2Nigc">Mejor cuantificación binaria (BBQ): De bytes a BBQ, el secreto para una mejor búsqueda vectorial por Ben Trent</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Uso del tamaño del montón de Elasticsearch y recogida de basura de la JVM]]></title>
            <description><![CDATA[Explorando el uso del tamaño del heap en Elasticsearch y la recogida de basura de la JVM, incluyendo las mejores prácticas y cómo resolver problemas cuando el uso de memoria del heap es demasiado alto o cuando el rendimiento de la JVM no es óptimo.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elasticsearch-heap-size-jvm-garbage-collection</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elasticsearch-heap-size-jvm-garbage-collection</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Tue, 22 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b162c28623b9070fd1980bcd891b9dd1e868f2f0-720x421.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>El tamaño del heap es la cantidad de RAM asignada a la Máquina Virtual Java de un nodo Elasticsearch.</p><p>A partir de la versión 7.11, Elasticsearch por defecto establece automáticamente el tamaño del montón de la JVM en función de los roles y la memoria total de un nodo. Se recomienda usar el tamaño por defecto para la mayoría de entornos de producción. Sin embargo, si quieres configurar manualmente el tamaño del montón de tu JVM, como regla general deberías poner -Xms y -Xmx al MISMO valor, que debería ser el 50% de tu RAM disponible total, sujeto a un máximo (aproximadamente) 31GB.</p><p>Un tamaño de heap mayor le dará a tu nodo más memoria para las operaciones de indexación y búsqueda. Sin embargo, tu nodo también requiere memoria para la caché, así que usar el 50% mantiene un equilibrio saludable entre ambos. Por esta misma razón, en producción deberías evitar usar otros procesos que consumen mucho memoria en el mismo nodo que Elasticsearch.</p><p>Normalmente, el uso del montón sigue un patrón de dientes de sierra, oscilando entre alrededor del 30 y el 70% del montón máximo empleado. Esto se debe a que la JVM aumenta de forma constante el porcentaje de uso del montón hasta que el proceso de recogida de basura libera memoria de nuevo. El alto uso del montón ocurre cuando el proceso de recogida de basura no puede seguir el ritmo. Un indicador de un alto uso del montón es cuando la recolección de basura no es capaz de reducir el uso del montón a alrededor del 30%.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0a17a67cc589a3c1fbf9e918eadc119df7bd7619-858x278.png" style="width: 100%;" /><p>En la imagen de arriba, puedes ver un diente de sierra normal del montón de JVM.</p><p>También verás que hay dos tipos de recogida de basura: GC joven y vieja.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8df5c24c4894404de4617be7a13683c9027d607d-875x281.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e01eb2537310b052580411153b8eddc187d97687-890x264.png" style="width: 100%;" /><p>En una JVM saludable, la recogida de basura debería cumplir idealmente las siguientes condiciones:</p><ul><li>El GC joven se procesa rápidamente (en menos de 50 ms).</li><li>La GC joven no se ejecuta con frecuencia (unos 10 segundos).</li><li>El GC antiguo se procesa rápidamente (en menos de 1 segundo).</li><li>El GC antiguo no se ejecuta con frecuencia (una vez cada 10 minutos o más).</li></ul><h3><strong>Cómo resolver cuando el uso de memoria del heap es demasiado alto o cuando el rendimiento de la JVM no es óptimo</strong></h3><p>Puede haber varias razones por las que el uso de memoria heap puede aumentar:</p><h4><strong>Fragmentación de fragmentos</strong></h4><p>Por favor, consulta el documento sobre <a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/size-shards#sizing-shard-guidelines">sobrefragmentación aquí</a>.</p><h4><strong>Grandes tamaños de agregación</strong></h4><p>Para evitar grandes tamaños de agregación, mantén al mínimo el número de cubos de agregación (tamaño) en tus consultas.</p><pre><code>GET /_search
{
   "aggs" : {
       "products" : {
           "terms" : {
               "field" : "product",
               "size" : 5
                          }
       }
   }
}</code></pre><p>Puedes usar el registro lento de consultas (registros lentos) e implementarlo en un índice específico usando lo siguiente.</p><pre><code>PUT /my_index/_settings
{
   "index.search.slowlog.threshold.query.warn": "10s",
   "index.search.slowlog.threshold.query.info": "5s",
   "index.search.slowlog.threshold.query.debug": "2s",
   "index.search.slowlog.threshold.query.trace": "500ms",
   "index.search.slowlog.threshold.fetch.warn": "1s",
   "index.search.slowlog.threshold.fetch.info": "800ms",
   "index.search.slowlog.threshold.fetch.debug": "500ms",
   "index.search.slowlog.threshold.fetch.trace": "200ms",
   "index.search.slowlog.level": "info"
}</code></pre><p>Las consultas que tardan mucho en devolver los resultados suelen ser las que requieren muchos recursos.</p><h4><strong>Tamaño excesivo del índice de volumen</strong></h4><p>Si envías peticiones grandes, esto puede ser una causa de un alto consumo de heaps. Prueba a reducir el tamaño de las solicitudes de índice masivo.</p><h4><strong>Problemas de cartografía</strong></h4><p>En individuo, si usas &quot;fielddata: true&quot;, entonces puede ser un usuario importante de tu montón JVM.</p><h4><strong>Tamaño del montón incorrectamente configurado</strong></h4><p>El tamaño del heap puede definir manualmente por:</p><p>Establecer la variable de entorno:</p><pre><code>ES_JAVA_OPTS="-Xms2g -Xmx2g"</code></pre><p>Editar el archivo jvm.options en tu directorio de configuración de Elasticsearch:</p><pre><code>-Xms2g
-Xmx2g</code></pre><p>La configuración de la variable ambiental tiene prioridad sobre la configuración de archivo.</p><p>Es necesario resetear el nodo para tener en cuenta la configuración.</p><h4><strong>Nuevo ratio de JVM configurado incorrectamente</strong></h4><p>Generalmente NO es necesario establecer esto, ya que Elasticsearch establece este valor por defecto. Este parámetro define la proporción de espacio disponible para objetos de &quot;nueva generación&quot; y &quot;generación antigua&quot; en la JVM.</p><p>Si ves que el antiguo GC se está volviendo muy frecuente, puedes probar a establecer específicamente este valor en el archivo jvm.options de tu directorio de configuración de Elasticsearch.</p><pre><code>-XX:NewRatio=3</code></pre><h3><strong>¿Cuáles son las mejores prácticas para gestionar el uso del tamaño del heap y la recogida de basura de la JVM en un gran clúster de Elasticsearch?</strong></h3><p>Las mejores prácticas para gestionar el uso del tamaño del heap y la recolección de basura de la JVM en un gran clúster de Elasticsearch son cerciorar que el tamaño del heap esté fijado en un máximo del 50% de la RAM disponible, y que la configuración de recogida de basura de la JVM esté optimizada para el caso de uso específico. Es importante monitorizar el tamaño del montón y las métricas de recogida de basura para cerciorar de que el clúster funciona de forma óptima. En concreto, es importante monitorizar el tamaño del montón de la JVM, el tiempo de recogida de basura y las pausas en la recogida. Además, es importante controlar el número de ciclos de recogida de basura y el tiempo dedicado a la recogida. Al monitorizar estas métricas, es posible identificar posibles problemas con el tamaño del montón o la configuración de recogida de basura y tomar medidas correctivas si es necesario.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cómo aumentar el conteo de fragmentos primarios en Elasticsearch]]></title>
            <description><![CDATA[Aprende cómo aumentar la cantidad de shards primarios en Elasticsearch usando las API split y reindex para un escalado óptimo de shards.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elasticsearch-increase-primary-shard-count</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elasticsearch-increase-primary-shard-count</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Thu, 17 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7034b76019a0cba52c25eda29fceb18afc96ed0b-720x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>No es posible aumentar el número de fragmentos primarios de un índice existente, lo que significa que hay que recrear un índice si quieres aumentar el número de fragmentos primarios. En estas situaciones se emplean generalmente dos métodos: la API _reindex y la API _split.</p><p>La API _split suele ser un método más rápido que la API _reindex. <strong>La indexación</strong> <strong>debe detener</strong> antes de ambas operaciones, de lo contrario, el source_index y el target_index el recuentos de documentos variarán.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/aa0ae010c2f5691ca00440fb453ed6b47bacd24f-1200x628.png" style="width: 100%;" /><h2>Método 1 – usando la API dividida</h2><p>La API dividida se emplea para crear un nuevo índice con el número deseado de fragmentos primarios copiando los ajustes y mapeando un índice existente. El número deseado de fragmentos primarios puede establecer durante la creación. Se deben comprobar las siguientes configuraciones antes de implementar la API de división:</p><ol><li>El índice de origen debe ser de solo lectura. Esto significa que el proceso de indexación debe detener.</li><li>El número de fragmentos primarios en el índice objetivo debe ser un múltiplo del número de fragmentos primarios en el índice fuente. Por ejemplo, si el índice fuente tiene 5 fragmentos primarios, los fragmentos primarios del índice objetivo pueden establecer en 10, 15, 20, y así sucesivamente.</li></ol><p>Nota: Si solo es necesario cambiar el número principal del fragmento, se prefiere la API dividida porque es mucho más rápida que la API de Reindex.</p><h3>Implementación de la API de división</h3><p>Crea un índice de prueba:</p><pre><code>POST test_split_source/_doc
{
  "test": "test"
}</code></pre><p>El índice fuente debe ser de solo lectura para poder dividir:</p><pre><code>PUT test_split_source/_settings
{
  "index.blocks.write": true
}</code></pre><p>Los ajustes y mapeos se copiarán automáticamente desde el índice fuente:</p><pre><code>POST /test_split_source/_split/test_split_target
{
  "settings": {
    "index.number_of_shards": 3
  }
}</code></pre><p>Puedes consultar el progreso con:</p><pre><code>GET _cat/recovery/test_split_target?v&h=index,shard,time,stage,files_percent,files_total</code></pre><p>Como los ajustes y mapeos se copian de los índices fuente, el índice destino es de solo lectura. Activemos la operación de escritura para el índice objetivo:</p><pre><code>PUT test_split_target/_settings
{
    "index.blocks.write": null
}</code></pre><p>Consulta el índice de origen y destino docs.count antes de eliminar el índice original:</p><pre><code>GET _cat/indices/test_split*?v&h=index,pri,rep,docs.count</code></pre><p>El nombre del índice y el nombre del alias no pueden ser iguales. Necesitas eliminar el índice fuente y agregar el nombre del índice fuente como alias al índice objetivo:</p><pre><code>DELETE test_split_source
PUT /test_split_target/_alias/test_split_source</code></pre><p>Luego de agregar el <strong>alias test_split_source</strong> al <strong>índice de test_split_target</strong> , deberías probarlo con:</p><pre><code>GET test_split_source
POST test_split_source/_doc
{
  "test": "test"
}</code></pre><h2>Método 2 – usando la API de reindex</h2><p>Al crear un nuevo índice con la API Reindex, se puede obtener cualquier número de conteos de fragmentos primarios. Tras crear un nuevo índice con el número previsto de fragmentos primarios, todos los datos del índice fuente pueden reindexar a este nuevo índice.</p><p>Además de las funciones de API dividida, los datos pueden manipular usando el ingest_pipeline en el AP de reindexación. Con la tubería de ingest, solo los campos especificados que encajen con el filtro se indexarán en el índice objetivo usando la consulta. El contenido de los datos puede modificar usando un script sencillo, y varios índices pueden fusionar en un solo índice.</p><h3>Implementación de la API de reindex</h3><p>Crea un reindexado de prueba:</p><pre><code>POST test_reindex_source/_doc
{
    "test": "test"
}</code></pre><p>Copia la configuración y los mapeos del índice fuente:</p><pre><code>GET test_reindex_source</code></pre><p>Crea un índice objetivo con ajustes, mapeos y el número de fragmentos deseado:</p><pre><code>PUT test_reindex_target
{
  "mappings" : {},
  "settings": {
    "number_of_shards": 10,
    "number_of_replicas": 0,
    "refresh_interval": -1
  }
}</code></pre><p>*Nota: ajustar number_of_replicas: 0 y refresh_interval: -1 aumentará la velocidad de reindexación.</p><p>Inicia el proceso de reindexación. Configurar requests_per_second=-1 y slices=auto ajustará la velocidad de reindexación.</p><pre><code>POST _reindex?requests_per_second=-1&slices=auto&wait_for_completion=false
{
  "source": {
    "index": "test_reindex_source"
  },
  "dest": {
    "index": "test_reindex_target"
  }
}</code></pre><p>Verás el task_id cuando ejecutes la API de reindex. Cópila y comprueba con _tasks API:</p><pre><code>GET _tasks/<task_id></code></pre><p>Actualiza la configuración después de que termine el reindexado:</p><pre><code>PUT test_reindex_target/_settings
{
  "number_of_replicas": 1,
  "refresh_interval": "1s"
}</code></pre><p>Consulta el índice fuente y destino docs.count antes de borrar el índice original, debería ser el mismo:</p><pre><code>GET _cat/indices/test_reindex_*?v&h=index,pri,rep,docs.count</code></pre><p>El nombre del índice y el nombre del alias no pueden ser iguales. Elimina el índice fuente y agrega el nombre del índice fuente como alias al índice objetivo:</p><pre><code>DELETE test_reindex_source
PUT /test_reindex_target/_alias/test_reindex_source</code></pre><p>Luego de agregar el alias test_split_source al índice de test_split_target, pruébalo usando:</p><pre><code>GET test_reindex_source</code></pre><h2>Resumen</h2><p>Si quieres aumentar el recuento de fragmentos primarios de un índice existente, necesitas recrear los ajustes y asignaciones a un nuevo índice. Hay 2 métodos principales para hacerlo: la API de reindex y la API de split. La indexación activa debe detener antes de usar cualquiera de los dos métodos.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch BBQ vs. OpenSearch FAISS: comparación del rendimiento de la búsqueda vectorial]]></title>
            <description><![CDATA[Una comparación de rendimiento entre Elasticsearch BBQ y OpenSearch FAISS.]]></description>
            <link>https://www.elastic.co/search-labs/es/blog/elasticsearch-bbq-vs-opensearch-faiss</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/es/blog/elasticsearch-bbq-vs-opensearch-faiss</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Ugo Sangiorgi]]></dc:creator>
            <pubDate>Tue, 15 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/796de4829e29566f1f3efa2482f5c3e54b31b1d6-1536x1024.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><strong>Búsqueda vectorial con cuantificación binaria: Elasticsearch con BBQ es 5 veces más rápido que OpenSearch con FAISS</strong>. Elastic recibió solicitudes de nuestra comunidad para aclarar las diferencias de rendimiento entre Elasticsearch y OpenSearch, particularmente en el ámbito de la búsqueda semántica/búsqueda vectorial, por lo que realizamos estas pruebas de rendimiento para proporcionar comparaciones claras y basadas en datos.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7f4f8d08f7bee188423e4e65f0caefc7e34f0355-1600x681.png" style="width: 100%;" /><h2>Enfrentamiento de cuantificación binaria</h2><p>Almacenar vectores de alta dimensión en su forma original puede requerir mucha memoria. Las técnicas de cuantificación comprimen estos vectores en una representación compacta, lo que reduce significativamente la huella de memoria. Luego, la búsqueda opera en el espacio comprimido, lo que reduce la complejidad computacional y hace que las búsquedas sean más rápidas, especialmente en grandes conjuntos de datos.</p><p>Elastic está comprometida a convertir Lucene en un motor vectorial de alto rendimiento. Introdujimos <a href="https://www.elastic.co/es/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Better Binary Quantization</a> (BBQ) en Elasticsearch 8.16 sobre Lucene y lo evolucionamos aún más en 8.18 y 9.0. BBQ se basa en un nuevo enfoque de <a href="https://www.elastic.co/es/search-labs/blog/optimized-scalar-quantization-elasticsearch">cuantización escalar</a> que reduce las dimensiones float32 a bits, ofreciendo una reducción de memoria del ~95% manteniendo una alta calidad de clasificación.</p><p>OpenSearch, por otro lado, emplea múltiples motores vectoriales: nmslib (ahora obsoleto), Lucene y FAISS. En un <a href="https://www.elastic.co/es/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison">blog anterior</a>, comparamos Elasticsearch y OpenSearch para la búsqueda vectorial. Empleamos tres conjuntos de datos diferentes y probamos diferentes combinaciones de motores y configuraciones en ambos productos.</p><p>Este blog se centra en los algoritmos de cuantificación binaria disponibles actualmente en ambos productos. Probamos Elasticsearch con BBQ y OpenSearch con la <a href="https://opensearch.org/docs/latest/search-plugins/knn/knn-vector-quantization/#binary-quantization">cuantificación binaria de FAISS</a> empleando la pista <a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector">openai_vector</a> Rally.</p><p>El objetivo principal fue evaluar el desempeño de ambas soluciones bajo el mismo nivel de recuperación. ¿Qué significa <em>recordar</em> ? La recuperación es una métrica que mide cuántos de los resultados relevantes son recuperados con éxito por un sistema de búsqueda.</p><p>En esta evaluación, recall@k es particularmente importante, donde <em>k</em> representa el número de resultados principales considerados. <strong>Recall@10</strong>, <strong>Recall@50 y Recall@100</strong> medir cuántos de los resultados relevantes reales aparecen en los 10, 50 y 100 elementos principales recuperados, respectivamente. La recuperación se expresa en una escala de 0 a 1 (o de 0% a 100% de precisión). Y eso es importante porque estamos hablando de KNN aproximado (ANN) y no de KNN exacto, donde la recordación es siempre 1 (100%).</p><p>Para cada valor de <em>k</em> también especificamos <em>n, </em>que es el número de candidatos considerados antes de aplicar la clasificación final. Esto significa que para Recall@10, Recall@50 y Recall@100, el sistema primero recupera <em>n</em> candidatos empleando el algoritmo de cuantificación binaria y luego los clasifica para determinar si los <em>k</em> resultados principales contienen los elementos relevantes esperados.</p><p>Al controlar <em>n</em>, podemos analizar el equilibrio entre eficiencia y precisión. Un <em>n</em> más alto generalmente <strong>aumenta la</strong> recuperación, ya que hay más candidatos disponibles para la clasificación, pero también <strong>aumenta</strong> la latencia y<strong> disminuye el </strong>rendimiento. Por el contrario, un <em>n</em> más bajo acelera la recuperación, pero puede reducir la recordación si se incluyen muy pocos candidatos relevantes en el conjunto inicial.</p><p>En esta comparación, Elasticsearch demostró una latencia más baja y un rendimiento más alto que OpenSearch en configuraciones idénticas.</p><h2>Metodología</h2><p>La configuración completa, junto con los scripts de Terraform, los manifiestos de Kubernetes y la pista de Rally específica está disponible en este <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/tree/bbq">repositorio</a> en <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/tree/bbq/rally-custom/custom_tracks/elasticsearch/openai_vector_bq"><em>openai_vector_bq</em></a>.</p><p>Al igual que con los puntos de referencia anteriores, empleamos un clúster de Kubernetes compuesto por:</p><ul><li>1 pool de nodos para Elasticsearch 9.0 con 3 máquinas <code>e2-standard-32</code> (128 GB de RAM y 32 CPU)</li><li>1 grupo de nodos para OpenSearch 2.19 con 3 máquinas <code>e2-standard-32</code> (128 GB de RAM y 32 CPU)</li><li>1 grupo de nodos para Rally con 2 máquinas <code>e2-standard-4</code> (16 GB de RAM y 4 CPU)</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/169bb6033f6eebfd1b177b3446bf916fde4ee5c5-1600x856.png" style="width: 100%;" /><p>Configuramos un clúster de Elasticsearch versión 9.0 y un clúster de OpenSearch versión 2.19.</p><p>Tanto Elasticsearch como OpenSearch se probaron exactamente con la misma configuración: usamos <a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector">openai_vector</a> pista de Rally con <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/commit/b97d5d95c22c8cf862f2030964524bdd156a5da3">algunas modificaciones</a> , que emplea 2,5 millones de documentos del <a href="https://huggingface.co/datasets/BeIR/nq">conjunto de datos NQ</a> enriquecidos con incrustaciones generadas con el <a href="https://openai.com/blog/new-and-improved-embedding-model">modelo text-embedding-ada-002</a> de OpenAI.</p><pre><code>{
  "source-file": "open_ai_corpus-initial-indexing.json.bz2",
  "document-count": 2580961,
  "compressed-bytes": 32076749416,
  "uncompressed-bytes": 90263571686
}</code></pre><p>Los resultados informan sobre la latencia y el rendimiento medidos en diferentes niveles de recuperación (recall@10, recall@50 y recall@100) empleando 8 clientes simultáneos para realizar operaciones de búsqueda. Usamos un solo fragmento y no réplicas.</p><p>Ejecutamos las siguientes combinaciones de k-n-rescore, p. ej. 10-2000-2000, o <em>K:10</em>, <em>N:2000</em> y <em>Rescore:2000</em> recuperarían los mejores K (10) sobre N candidatos (2000) aplicando un Rescore sobre 2000 resultados (que es equivalente a un &quot;factor de sobremuestreo&quot; de 1). Cada búsqueda se ejecutó 10.000 veces con 1000 búsquedas como calentamiento:</p><p></p><p><strong><span style="text-decoration:underline">Recall@10</span></strong></p><ul><li>10-40-40</li><li>10-50-50</li><li>10-100-100</li><li>10-200-200</li><li>10-500-500</li><li>10-750-750</li><li>10-1000-1000</li><li>10-1500-1500</li><li>10-2000-2000</li></ul><p><strong><span style="text-decoration:underline">Recall@50</span></strong></p><ul><li>50-150-150</li><li>50-200-200</li><li>50-250-250</li><li>50-500-500</li><li>50-750-750</li><li>50-1000-1000</li><li>50-1200-1200</li><li>50-1500-1500</li><li>50-2000-2000</li></ul><p><strong><span style="text-decoration:underline">Recall@100</span></strong></p><ul><li>100-200-200</li><li>100-250-250</li><li>100-300-300</li><li>100-500-500</li><li>100-750-750</li><li>100-1000-1000</li><li>100-1200-1200</li><li>100-1500-1500</li><li>100-2000-2000</li></ul><p>Para replicar el punto de referencia, los manifiestos de Kubernetes para rally-elasticsearch y rally-opensearch tienen todas las variables relevantes externalizadas en un ConfigMap, disponible <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/bbq/k8s/rally-openai_vector-es-bq.yml">aquí</a> (ES) y <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/bbq/k8s/rally-openai_vector-os-bq.yml">aquí</a> (SO). El parámetro <em>search_ops</em> se puede personalizar para probar cualquier combinación de k, n y rescore.</p><h3>Configuración de OpenSearch Rally</h3><p><code>/k8s/rally-openai_vector-os-bq.yml</code></p><pre><code>apiVersion: v1
kind: ConfigMap
metadata:
  name: rally-params-os
  labels:
    app: rally-opensearch
data:
  user-tags.json: |
    {
      "product": "OpenSearch",
      "product-version": "OpenSearch-2.19.0",
      "product-label": "OpenSearch-2.19-faiss",
      "benchmark-run": "19-feb-recall@100"
    }
  track-params.json: |
    {
      "mapping_type": "vectors-only-mapping-with-docid",
      "standalone_search_clients": 8,
      "standalone_search_iterations": 5000,
      "ann_threshold": 0,
      "vector_mode": "on_disk",
      "compression_level": "32x",
      "vector_method_name": "hnsw",
      "vector_method_engine": "faiss",
      "search_ops": [
        [100, 200, 200],
        [100, 250, 250],
        [100, 300, 300],
        [100, 500, 500],
        [100, 750, 750],
        [100, 1000, 1000],
        [100, 1200, 1200],
        [100, 1500, 1500],
        [100, 2000, 2000]
      ]
    }</code></pre><h3>Configuración del índice de Opensearch</h3><p>Las variables de ConfigMap se emplean en la configuración del índice, algunos parámetros se dejan sin cambios. La cuantización de 1 bit en OpenSearch se configura <a href="https://opensearch.org/docs/latest/search-plugins/knn/knn-vector-quantization/#binary-quantization">estableciendo el nivel de compresión en &quot;32x&quot;.</a></p><p><code>index-vectors-only-mapping-with-docid-mapping.json</code></p><pre><code>{
  "settings": {
    {% if preload_pagecache %}
    "index.store.preload": [
      "vec", "vex", "vem", "veq", "veqm", "veb", "vebm"
    ],
    {% endif %}
    "index.number_of_shards": {{ number_of_shards | default(1) }},
    "index.number_of_replicas": {{ number_of_replicas | default(0) }},
    "index.knn": true,
    "index.knn.advanced.approximate_threshold": {{ ann_threshold | default(15000) }}
  },
  "mappings": {
    "dynamic": false,
    "properties": {
      "docid": {
        "type": "keyword"
      },
      "emb": {
        "type": "knn_vector",
        "dimension": 1536,
        "space_type": "innerproduct",
        "data_type": "float",
        "mode": {{ vector_mode | default("in_memory") | tojson }},
        "compression_level": {{ compression_level | default("32x") | tojson }},
        "method": {
          "name": {{ vector_method_name | default("hnsw") | tojson }},
          "engine": {{ vector_method_engine | default("faiss") | tojson }},
          "parameters": {
            "ef_construction": 100,
            "m": 16
          }
        }
      }
    }
  }
}</code></pre><h3>Configuración de Elasticsearch Rally</h3><p><code>/k8s/rally-openai_vector-es-bq.yml</code></p><pre><code>apiVersion: v1
kind: ConfigMap
metadata:
  name: rally-params-es
  labels:
    app: rally-elasticsearch
data:
  user-tags.json: |
    {
      "product": "Elasticsearch",
      "product-version": "Elasticsearch-9.0.0-ade01164",
      "product-label": "Elasticsearch-9.0-BBQ",
      "benchmark-run": "19-feb-recall@100"
    }
  track-params.json: |
    {
      "mapping_type": "vectors-only-mapping-with-docid",
      "standalone_search_clients": 8,
      "standalone_search_iterations": 5000,
      "vector_index_type": "bbq_hnsw",
      "search_ops": [
        [100, 200, 200],
        [100, 250, 250],
        [100, 300, 300],
        [100, 500, 500],
        [100, 750, 750],
        [100, 1000, 1000],
        [100, 1200, 1200],
        [100, 1500, 1500],
        [100, 2000, 2000]
      ]
    }</code></pre><h3>Configuración del índice de Elasticsearch</h3><p><code>index-vectors-only-mapping-with-docid-mapping.json</code></p><pre><code>{
  "settings": {
    {# non-serverless-index-settings-marker-start #}
    {%- if build_flavor != "serverless" or serverless_operator == true -%}
    {% if preload_pagecache %}
    "index.store.preload": [ "vec", "vex", "vem", "veq", "veqm", "veb", "vebm" ],
    {% endif %}
    "index.number_of_shards": {{ number_of_shards | default(1) }},
    "index.number_of_replicas": {{ number_of_replicas | default(0) }}
    {%- endif -%}
    {# non-serverless-index-settings-marker-end #}
  },
  "mappings": {
    "dynamic": false,
    "properties": {
      "docid": {
        "type": "keyword"
      },
      "emb": {
        "type": "dense_vector",
        "element_type": "float",
        "dims": 1536,
        "index": true,
        "similarity": "dot_product",
        "index_options": {
          "type": {{ vector_index_type | default("bbq_hnsw") | tojson }},
          "ef_construction": 100,
          "m": 16
        }
      }
    }
  }
}</code></pre><h2>Resultados</h2><p>Hay varias formas de interpretar los resultados. Tanto para la latencia como para el rendimiento, trazamos un gráfico simplificado y detallado en cada nivel de recuperación. Es fácil ver diferencias si consideramos que &quot;más alto es mejor&quot; para cada métrica. Sin embargo, la latencia es negativa (más baja es mejor), mientras que el rendimiento es positivo. Para los gráficos simplificados, usamos <strong>(recuperación / latencia) * 10000 </strong>(llamado simplemente &quot;velocidad&quot;) y<strong> recuperación * rendimiento</strong>, por lo que ambas métricas significan que más velocidad y más rendimiento son mejores. Vamos a ello.</p><h3>Recuperación @ 10 - simplificado</h3><p>En ese nivel de recuperación, Elasticsearch BBQ es hasta <strong>5 veces más rápido </strong>(3,9 veces más rápido en promedio) y tiene <strong>3,2 veces más rendimiento</strong> en promedio que OpenSearch FAISS.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d580ad53e8974bd3aa75957c413a0136c4e465c5-1600x681.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/18edce667fe36ab95033264ef8df6f352dda2425-2044x866.png" style="width: 100%;" /><h4>Retiro @ 10 - Detallado</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a7459b87e679f4ad963d0e2f1685499b40f6f050-1600x799.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c6036f55a13377654d296eb3148c7199e1965475-1600x799.png" style="width: 100%;" />
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">tarea</td><td style="border: 1px solid #ccc; padding: 4px;">latencia.media</td><td style="border: 1px solid #ccc; padding: 4px;">rendimiento.mean</td><td style="border: 1px solid #ccc; padding: 4px;">avg_recall</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-100-100</td><td style="border: 1px solid #ccc; padding: 4px;">11.70</td><td style="border: 1px solid #ccc; padding: 4px;">513.58</td><td style="border: 1px solid #ccc; padding: 4px;">0.89</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-1000-100</td><td style="border: 1px solid #ccc; padding: 4px;">27.33</td><td style="border: 1px solid #ccc; padding: 4px;">250.55</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">35.93</td><td style="border: 1px solid #ccc; padding: 4px;">197.26</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">13.33</td><td style="border: 1px solid #ccc; padding: 4px;">456.16</td><td style="border: 1px solid #ccc; padding: 4px;">0.92</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-2000-2000</td><td style="border: 1px solid #ccc; padding: 4px;">44.27</td><td style="border: 1px solid #ccc; padding: 4px;">161.40</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-40-40</td><td style="border: 1px solid #ccc; padding: 4px;">10.97</td><td style="border: 1px solid #ccc; padding: 4px;">539.94</td><td style="border: 1px solid #ccc; padding: 4px;">0.84</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-50-50</td><td style="border: 1px solid #ccc; padding: 4px;">11.00</td><td style="border: 1px solid #ccc; padding: 4px;">535.73</td><td style="border: 1px solid #ccc; padding: 4px;">0.85</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">19.52</td><td style="border: 1px solid #ccc; padding: 4px;">341.45</td><td style="border: 1px solid #ccc; padding: 4px;">0.93</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">22.94</td><td style="border: 1px solid #ccc; padding: 4px;">295.19</td><td style="border: 1px solid #ccc; padding: 4px;">0.94</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-100-100</td><td style="border: 1px solid #ccc; padding: 4px;">35.59</td><td style="border: 1px solid #ccc; padding: 4px;">200.61</td><td style="border: 1px solid #ccc; padding: 4px;">0.94</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-1000-1000</td><td style="border: 1px solid #ccc; padding: 4px;">156.81</td><td style="border: 1px solid #ccc; padding: 4px;">58.30</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">181.79</td><td style="border: 1px solid #ccc; padding: 4px;">42.97</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">47.91</td><td style="border: 1px solid #ccc; padding: 4px;">155.16</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-2000-2000</td><td style="border: 1px solid #ccc; padding: 4px;">232.14</td><td style="border: 1px solid #ccc; padding: 4px;">31.84</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-40-40</td><td style="border: 1px solid #ccc; padding: 4px;">27.55</td><td style="border: 1px solid #ccc; padding: 4px;">249.25</td><td style="border: 1px solid #ccc; padding: 4px;">0.92</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-50-50</td><td style="border: 1px solid #ccc; padding: 4px;">28.78</td><td style="border: 1px solid #ccc; padding: 4px;">245.14</td><td style="border: 1px solid #ccc; padding: 4px;">0.92</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">79.44</td><td style="border: 1px solid #ccc; padding: 4px;">97.06</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">104.19</td><td style="border: 1px solid #ccc; padding: 4px;">75.49</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
              </tbody>
            </table>
          <h3>Recuperación @ 50 - simplificado</h3><p>En ese nivel de recuperación, Elasticsearch BBQ es <strong>hasta 5 veces más rápido</strong> (4,2 veces más rápido en promedio) y tiene <strong>3,9 veces más rendimiento</strong> en promedio<strong> </strong>que OpenSearch FAISS.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3189ffb330948b35854eeea9ae317d4846c14972-1600x681.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/607383d674dcf0b8f94bfb1a450063f52fcbeb15-2060x876.png" style="width: 100%;" /><h4>Resultados detallados - Recall @ 50</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ebfffe0b776aad31dd03d315cfbf5aa098b41226-1600x789.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ae20cf870a65c400a2112bbad62eb56e244f549a-1600x799.png" style="width: 100%;" />
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">Tarea</td><td style="border: 1px solid #ccc; padding: 4px;">Latencia media</td><td style="border: 1px solid #ccc; padding: 4px;">Media de rendimiento</td><td style="border: 1px solid #ccc; padding: 4px;">Retiro promedio</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-1000-1000</td><td style="border: 1px solid #ccc; padding: 4px;">25.71</td><td style="border: 1px solid #ccc; padding: 4px;">246.44</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-1200-1200</td><td style="border: 1px solid #ccc; padding: 4px;">28.81</td><td style="border: 1px solid #ccc; padding: 4px;">227.85</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-150-150</td><td style="border: 1px solid #ccc; padding: 4px;">13.43</td><td style="border: 1px solid #ccc; padding: 4px;">362.90</td><td style="border: 1px solid #ccc; padding: 4px;">0.90</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">33.38</td><td style="border: 1px solid #ccc; padding: 4px;">202.37</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">12.99</td><td style="border: 1px solid #ccc; padding: 4px;">406.30</td><td style="border: 1px solid #ccc; padding: 4px;">0.91</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-2000-2000</td><td style="border: 1px solid #ccc; padding: 4px;">42.63</td><td style="border: 1px solid #ccc; padding: 4px;">163.68</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-250-250</td><td style="border: 1px solid #ccc; padding: 4px;">14.41</td><td style="border: 1px solid #ccc; padding: 4px;">373.21</td><td style="border: 1px solid #ccc; padding: 4px;">0.92</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">17.15</td><td style="border: 1px solid #ccc; padding: 4px;">341.04</td><td style="border: 1px solid #ccc; padding: 4px;">0.93</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">31.25</td><td style="border: 1px solid #ccc; padding: 4px;">248.60</td><td style="border: 1px solid #ccc; padding: 4px;">0.94</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-1000-1000</td><td style="border: 1px solid #ccc; padding: 4px;">125.35</td><td style="border: 1px solid #ccc; padding: 4px;">62.53</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-1200-1200</td><td style="border: 1px solid #ccc; padding: 4px;">143.87</td><td style="border: 1px solid #ccc; padding: 4px;">54.75</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-150-150</td><td style="border: 1px solid #ccc; padding: 4px;">43.64</td><td style="border: 1px solid #ccc; padding: 4px;">130.01</td><td style="border: 1px solid #ccc; padding: 4px;">0.89</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">169.45</td><td style="border: 1px solid #ccc; padding: 4px;">46.35</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">48.05</td><td style="border: 1px solid #ccc; padding: 4px;">156.07</td><td style="border: 1px solid #ccc; padding: 4px;">0.91</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-2000-2000</td><td style="border: 1px solid #ccc; padding: 4px;">216.73</td><td style="border: 1px solid #ccc; padding: 4px;">36.38</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-250-250</td><td style="border: 1px solid #ccc; padding: 4px;">53.52</td><td style="border: 1px solid #ccc; padding: 4px;">142.44</td><td style="border: 1px solid #ccc; padding: 4px;">0.93</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">78.98</td><td style="border: 1px solid #ccc; padding: 4px;">97.82</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">103.20</td><td style="border: 1px solid #ccc; padding: 4px;">75.86</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
              </tbody>
            </table>
          <h3>Retiro @ 100</h3><p>En ese nivel de recuperación, Elasticsearch BBQ es <strong>hasta 5 veces más rápido </strong>(promedio 4,6 veces más rápido) y tiene <strong>3,9 veces más rendimiento </strong>en promedio que OpenSearch FAISS.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d3f29db64cbde9956de1fa3ae64a75f15141a2bb-1600x681.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/10aff7f8c61fdac895b9ba9c5342baf239ba3ffc-2072x864.png" style="width: 100%;" /><h4>Resultados detallados - Recall @ 100</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8790fdf9512b850447f6875fb69969f6f1d4da5f-1600x799.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d47032fac6c288cd4eedd9f25001e417b2fa9d65-1600x787.png" style="width: 100%;" />
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">tarea</td><td style="border: 1px solid #ccc; padding: 4px;">latencia.media</td><td style="border: 1px solid #ccc; padding: 4px;">rendimiento.mean</td><td style="border: 1px solid #ccc; padding: 4px;">avg_recall</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-1000-1000</td><td style="border: 1px solid #ccc; padding: 4px;">27.82</td><td style="border: 1px solid #ccc; padding: 4px;">243.22</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-1200-1200</td><td style="border: 1px solid #ccc; padding: 4px;">31.14</td><td style="border: 1px solid #ccc; padding: 4px;">224.04</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">35.98</td><td style="border: 1px solid #ccc; padding: 4px;">193.99</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">14.18</td><td style="border: 1px solid #ccc; padding: 4px;">403.86</td><td style="border: 1px solid #ccc; padding: 4px;">0.88</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-2000-2000</td><td style="border: 1px solid #ccc; padding: 4px;">45.36</td><td style="border: 1px solid #ccc; padding: 4px;">159.88</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-250-250</td><td style="border: 1px solid #ccc; padding: 4px;">14.77</td><td style="border: 1px solid #ccc; padding: 4px;">433.06</td><td style="border: 1px solid #ccc; padding: 4px;">0.90</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-300-300</td><td style="border: 1px solid #ccc; padding: 4px;">14.61</td><td style="border: 1px solid #ccc; padding: 4px;">375.54</td><td style="border: 1px solid #ccc; padding: 4px;">0.91</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">18.88</td><td style="border: 1px solid #ccc; padding: 4px;">340.37</td><td style="border: 1px solid #ccc; padding: 4px;">0.93</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">23.59</td><td style="border: 1px solid #ccc; padding: 4px;">285.79</td><td style="border: 1px solid #ccc; padding: 4px;">0.94</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-1000-1000</td><td style="border: 1px solid #ccc; padding: 4px;">142.90</td><td style="border: 1px solid #ccc; padding: 4px;">58.48</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-1200-1200</td><td style="border: 1px solid #ccc; padding: 4px;">153.03</td><td style="border: 1px solid #ccc; padding: 4px;">51.04</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">181.79</td><td style="border: 1px solid #ccc; padding: 4px;">43.20</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">50.94</td><td style="border: 1px solid #ccc; padding: 4px;">131.62</td><td style="border: 1px solid #ccc; padding: 4px;">0.83</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-2000-2000</td><td style="border: 1px solid #ccc; padding: 4px;">232.53</td><td style="border: 1px solid #ccc; padding: 4px;">33.67</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-250-250</td><td style="border: 1px solid #ccc; padding: 4px;">57.08</td><td style="border: 1px solid #ccc; padding: 4px;">131.23</td><td style="border: 1px solid #ccc; padding: 4px;">0.87</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-300-300</td><td style="border: 1px solid #ccc; padding: 4px;">62.76</td><td style="border: 1px solid #ccc; padding: 4px;">120.10</td><td style="border: 1px solid #ccc; padding: 4px;">0.89</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">84.36</td><td style="border: 1px solid #ccc; padding: 4px;">91.54</td><td style="border: 1px solid #ccc; padding: 4px;">0.93</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">111.33</td><td style="border: 1px solid #ccc; padding: 4px;">69.95</td><td style="border: 1px solid #ccc; padding: 4px;">0.94</td>
                    </tr>
              </tbody>
            </table>
          <h2>Mejoras en el asado</h2><p>BBQ recorrió un largo camino desde su primer lanzamiento. En Elasticsearch 8.16, en aras de la comparación, incluimos una ejecución de referencia de 8.16 junto con la actual, y podemos ver cómo la recuperación y la latencia mejoraron desde entonces.</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/45a0acfe5985bff28ccada76ec4eca190fe65f72-1600x799.png" style="width: 100%;" /><p>En Elasticsearch 8.18 y 9.0, reescribimos el algoritmo central para cuantificar los vectores. Entonces, si bien BBQ en 8.16 fue bueno, las versiones más nuevas son aún mejores. Puedes leer sobre esto <a href="https://www.elastic.co/es/search-labs/blog/optimized-scalar-quantization-elasticsearch">aquí</a> y <a href="https://www.elastic.co/es/search-labs/blog/scalar-quantization-optimization">aquí</a>. En resumen, cada vector se cuantifica individualmente a través de cuantiles escalares optimizados. Como resultado, los usuarios se benefician de una mayor precisión en la búsqueda vectorial sin comprometer el rendimiento, lo que hace que la recuperación vectorial de Elasticsearch sea aún más poderosa.</p><h2>Conclusión</h2><p>En esta comparación de rendimiento entre Elasticsearch BBQ y OpenSearch FAISS, Elasticsearch supera significativamente a OpenSearch para la búsqueda vectorial, logrando velocidades de consulta hasta 5 veces más rápidas y un rendimiento 3,9 veces mayor en promedio en varios niveles de recuperación.</p><p>Los hallazgos clave incluyen:</p><ul><li><strong>Recall@10</strong>: Elasticsearch BBQ es hasta 5 veces más rápido (3,9 veces más rápido en promedio) y tiene 3,2 veces más rendimiento en promedio en comparación con OpenSearch FAISS.</li><li><strong>Recall@50</strong>: Elasticsearch BBQ es hasta 5 veces más rápido (4,2 veces más rápido en promedio) y tiene 3,9 veces más rendimiento en promedio en comparación con OpenSearch FAISS.</li><li><strong>Recall@100</strong>: Elasticsearch BBQ es hasta 5 veces más rápido (4,6 veces más rápido en promedio) y tiene 3,9 veces más rendimiento en promedio en comparación con OpenSearch FAISS.</li></ul><p>Estos resultados resaltan los beneficios de eficiencia y rendimiento de Elasticsearch BBQ, particularmente en escenarios de búsqueda vectorial de alta dimensión. La técnica Better Binary Quantization (BBQ), introducida en Elasticsearch 8.16, proporciona una reducción sustancial de la memoria (~95%) al tiempo que mantiene una alta calidad de clasificación, lo que la convierte en una opción superior para aplicaciones de búsqueda vectorial a gran escala.</p><p>En Elastic, estamos innovando incansablemente para mejorar Apache Lucene y Elasticsearch para proporcionar la mejor base de datos vectorial para casos de uso de búsqueda y recuperación, incluida RAG (Retrieval Augmented Generation). Nuestros <a href="https://www.elastic.co/es/search-labs/blog/optimized-scalar-quantization-elasticsearch">avances recientes</a> aumentaron significativamente el rendimiento, haciendo que la búsqueda vectorial sea más rápida y eficiente en cuanto al espacio que antes, basar en las ganancias de Lucene 10. Este blog es otra ilustración de esa innovación.</p>]]></content:encoded>
        </item>
    </channel>
</rss>