<?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[JD Armada - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[JD Armada - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/es/search-labs/author/jd-armada</link>
    </image>
    <link>https://www.elastic.co/es/search-labs/author/jd-armada</link>
    <atom:link href="https://www.elastic.co/es/search-labs/rss/author/jd-armada.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[es]]></language>
    <lastBuildDate>Tue, 22 Sep 2026 22:22:03 GMT</lastBuildDate>
  <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>
    <content:encoded><![CDATA[<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 "Esquema de Solución".</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><p>Problema</p></li><li><p>Contexto</p></li><li><p>Código</p></li><li><p>Etiquetas</p></li><li><p>Pasos verificados de la solución.</p></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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte7bc35b6d54921e8/6a17f0176df73162760a0fe6/45a3e96f4474050a855419628c2a7338bb12c706-1600x877.png" alt="Al hacer clic en &quot;Compartir solución&quot; se extraerá la sesión actual junto con los metadatos relevantes" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9967f52007fff99e/6a17f019ec0f8987c45a6701/2d65cb154d8ee32fc96ff17dfa5b0bf2636e3777-1600x1002.png" alt="Los usuarios proporcionan contexto adicional a través del frontend sitio web, luego el JSON se indexa en Elasticsearch" /><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><p>Arreglos clasificados y validados por la comunidad</p></li><li><p>Los exactos prompts que originalmente resolvieron el problema</p></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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4ba582f9872b65b/6a17f01b7f6f15c2d8c09c1c/7d9716cca06a047a2b3584378b5c7e592a785ba1-1284x878.png" alt="6 agentes especializados de IA GOOGL que analizan el mercado" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd86ed3bfe4b8bd2b/6a17f01c5ea30f868164b6ba/5aac6a833347c0d2e596c02049ec4b4d3aae5cd7-794x764.png" alt="Anomalías de volumen y capacidades de análisis de detección de catástrofes de los agentes especializados GOOGL" /><p><strong>3. Agregar señales en un modelo unificado de "energía de mercado"</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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5af7c7c838308275/6a17f01e42022917b629f6ca/46b3da8e3d528c5dd4e2829416c5446098acb3aa-744x718.png" alt="Modelo unificado de &quot;energía de mercado&quot; de agentes especializados GOOGL" /><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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt775e1880aa7afacc/6a17f01f1d1b83ce1f93e528/3f000c043117b77ed4127202be5a49c12e3682ba-1600x930.png" alt="Cómo visualizar los insights del análisis de agentes especializados de GOOGL" /><h2>Otros proyectos interesantes:</h2><p>Aquí tienes otros candidatos fuertes que usaron Elastic en diferentes partes de su stack:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltffe292009e446a70/6a17f0216df731068c0a0fea/76c49a853426844f475cd6b2a74999e60af20e8c-926x1080.png" alt="" /><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><p><strong>Agent Builder es fácil de usar:</strong></p></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><p><strong>Los LLMs son excelentes en </strong><strong><code>kNN</code></strong><strong> consultas, pero aún necesitan orientación para generar ES|QL:</strong></p></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><p><strong>ES| solo para instantáneasFunciones QL filtradas en documentos:</strong></p></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><p><strong>Falta orientación específica para Serverless:</strong></p></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><p><strong>Valor de los eventos presenciales:</strong></p></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>
    <link>https://www.elastic.co/search-labs/blog/agent-builder-projects-learnings-cal-hacks-12-0</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/agent-builder-projects-learnings-cal-hacks-12-0</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[AI agéntica]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0f079179be9832d4/6a17f023631730a69c585b6d/8ba034a6f19b50521f541b8131756a8acdb52975-1280x960.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 25 Nov 2025 00:00:00 GMT</pubDate>
  </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>
    <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><p>Nodo v18+</p></li><li><p>Elasticsearch (versión 8.15 o posterior)</p></li><li><p>Clave API de Elasticsearch</p></li><li><p><a href="https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key">Clave API de OpenAI</a></p></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>npm create mastra@latest<p>Recibirás un conjunto de indicaciones, que empiezan por:</p><p>1. Pon un nombre a tu proyecto.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt87f941f654d03827/6a16f7af67045b214d45bfa1/2b9fe559e0276140dd539e24f916a73c60870405-620x84.png" alt="Nombrar un prompt en la app Mastra" /><p>2. Podemos mantener este valor predeterminado; No dudes en dejar esto en blanco.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7dbb3d4f27435cac/6a16f7b0cdacbf29497d27de/e04729eb03bce8499e973e18c28642402340d0e5-852x68.png" alt="Indicándole a Mastra dónde almacenar los archivos de prompt" /><p>3. Para este proyecto, emplearemos un modelo proporcionado por OpenAI.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1f654f6cb9397e94/6a16f7b2964cea899a08b942/a86596a469a71bdf8bd99cbaf528d0f0cf7272c0-436x222.png" alt="Selección de un modelo proporcionado por OpenAI en Mastra" /><p>4. Selecciona la opción "Saltar por ahora" porque almacenaremos todas nuestras variables de entorno en un archivo '.env' que configuraremos en un paso posterior.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltff106117521a3519/6a16f7b3c1e8a5031af880d8/02b19ccc34af0bdacf52fd94b519d036540ca2e6-426x114.png" alt="Seleccionando saltar por ahora para la clave OpenAI" /><p>5. También podemos saltar esta opción.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcda7d9c51c3878d7/6a16f7b450916809dbe1b892/b3fe63d19d270bc2e0de1dd92033bf8b26750819-990x208.png" alt="" /><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>npm install ai @ai-sdk/openai @elastic/elasticsearch dotenv<ul><li><p><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.</p></li><li><p><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.</p></li><li><p><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.</p></li><li><p><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.</p></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>ELASTICSEARCH_ENDPOINT="your-endpoint-here"
ELASTICSEARCH_API_KEY="your-key-here"
OPENAI_API_KEY="your-key-here"<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>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;
    }
}<ul><li><p><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.</p></li><li><p><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.</p></li><li><p><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.</p></li><li><p><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.</p></li><li><p><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>.</p></li><li><p><code>super()</code>: Esto llama constructor base de Mastra, por lo que hereda el registro, los asistentes de validación y otros ganchos internos.</p></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>private async detectServerless(): Promise&lt;boolean&gt; {
    // 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;
    }
}<p>Qué pasa:</p><ul><li><p>Primero comprueba si pusiste explícitamente <code>isServerless</code> en la configuración (se salta la auto-detección).</p></li><li><p>Llama a la API <code>info()</code> de Elasticsearch para obtener información del clúster</p></li><li><p>Comprueba el <code>build_flavor field</code> (los despliegues serverless devuelven <code>serverless</code>)</p></li><li><p>Vuelve a revisar el lema si no hay variedad de build disponible</p></li><li><p>Almacena en caché el resultado para evitar llamadas repetidas a la API</p></li><li><p>Por defecto se aplica al despliegue estándar si falla la detección</p></li></ul><p> Ejemplo de uso:</p>// 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
});<h3>Creación del almacén de "memoria" 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><p>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.</p></li><li><p>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.</p><ul><li><p>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.</p></li></ul></li><li><p>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.</p></li></ul>async createIndex(params: CreateIndexParams): Promise&lt;void&gt; {
    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}`);
    }
}<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>async upsert(params: UpsertVectorParams): Promise&lt;string[]&gt; {
    const { indexName, vectors, metadata = [], ids } = params;

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

        const operations = vectors.flatMap((vec, index) =&gt; [
            { 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) =&gt; item.index?.error);
            const erroredIds = erroredItems.map((item: any) =&gt; item.index?._id);
            const errorDetails = erroredItems.slice(0, 3).map((item: any) =&gt; ({
                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) =&gt; 
                !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}`);
    }
}<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>async query(params: QueryVectorParams&lt;any&gt;): Promise&lt;QueryResult[]&gt; {
    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) =&gt; ({
            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}`);
    }
}<p>Bajo el capó:</p><ul><li><p>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.</p></li><li><p>Recupera los vectores top-K similares al vector de consulta de entrada.</p></li><li><p>Opcionalmente, aplica filtros de metadatos para reducir resultados (por ejemplo, buscar solo dentro de una categoría o rango de tiempo específico)</p></li><li><p>Devuelve resultados estructurados que incluyen el ID del documento, el puntaje de similitud y los metadatos almacenados.</p></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>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,
});<p>Aquí, nosotros:</p><ul><li><p>Usa <code>dotenv</code> para cargar nuestras variables desde nuestro archivo <code>.env</code> .</p></li><li><p>Comprueba si las credenciales de Elasticsearch se están inyectando correctamente y podemos establecer una conexión exitosa con el cliente.</p></li><li><p>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.</p></li><li><p>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.</p></li></ul><p>A continuación, podemos definir el agente usando la clase <code>Agent</code> de Mastra.</p>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',
            },
        },
    }),
});<p>Los campos que podemos definir son:</p><ul><li><p><code>name</code> y <code>instructions</code>: Darle una identidad y función primaria.</p></li><li><p><code>model</code>: Estamos usando la <code>gpt-4o</code> de OpenAI a través del paquete <code>@ai-sdk/openai</code> .</p></li><li><p><code>memory</code>:</p><ul><li><p><code>vector</code>: Apunta a nuestra tienda Elasticsearch, así que los embeddings se almacenan y recuperan desde allí.</p></li><li><p><code>embedder</code>: Qué modelo usar para generar incrustaciones</p></li><li><p><code>semanticRecall</code> Las opciones deciden cómo funciona la retirada:</p><ul><li><p><code>topK</code>: Cuántos mensajes semánticamente similares recuperar.</p></li><li><p><code>messageRange</code>: Cuánto de la conversación incluir en cada partido.</p></li><li><p><code>scope</code>: Define el límite de la memoria.</p></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>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 }, 
  },
});<p>Los otros campos incluyen:</p><ul><li><p><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>.</p></li><li><p><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.</p></li><li><p><code>observability</code>: Controla el rastreo de IA y la visibilidad de ejecución de los agentes. Sigue lo siguiente:</p><ul><li><p>Inicio/final de cada paso de razonamiento.</p></li><li><p>Qué modelo o herramienta se empleó.</p></li><li><p>Entradas y salidas.</p></li><li><p>Puntajes y evaluaciones</p></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>npm run dev<p>Tras el empaquetado y el arranque inicial del servidor, debería proporcionarte una dirección del Playground.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5f857fddc74ffc9/6a16f7b6a6c2b995d5e794c0/8b045f70008d26aec4d2e6b59d61085555b9c5b2-686x116.png" alt="Dirección del servidor para Playground" /><p>Pega esta dirección en tu navegador y te recibirás con Mastra Studio.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc7fdda6ce46ce068/6a16f7b7b0367d4f7672bacf/69bc80fe8486edd9e0cf91d87b39f465aeb23111-1600x438.png" alt="Pegar la dirección de Playground para acceder a Mastra Studio" /><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: "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." Después, inicia un nuevo chat y haz una pregunta como: "¿En qué segmento de clientes dijimos que debemos centrarnos a continuación?" 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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfec3266e81a7213b/6a16f7b92b835f6f70f4afe2/da8ebddad89874023ed440a8f1ad2cb04ed043f4-1070x288.png" alt="Al charlar con un agente de conocimiento en Mastra Studio: el agente puede recordar información" /><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 "ventas" y recibimos cada interacción que incluyera algo relacionado con las ventas.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte428134d7bf2a43a/6a16f7bbb0367d185872bad3/3decaa0c332d288c5ae0b11c25f592c7d50c2f0f-1104x1320.png" alt="Cómo inspeccionar el almacenamiento de memoria a largo plazo de los agentes de conocimiento" /><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>
    <link>https://www.elastic.co/search-labs/blog/knowledge-agent-semantic-recall-mastra-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/knowledge-agent-semantic-recall-mastra-elasticsearch</guid>
    <category><![CDATA[AI agéntica]]></category>
    <category><![CDATA[Experiencia del desarrollador]]></category>
    <category><![CDATA[Integraciones]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt09afdbff05603865/6a16f7bd839dfabbf2dcfcb5/b8d51c2726d5573385c9246a7821d12ade4f1b0e-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 06 Nov 2025 00:00:00 GMT</pubDate>
  </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>
    <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><p>Uso de la API <code>_mapping</code> para recuperar información de campos</p></li><li><p>Uso de la API <code>_search</code> para mostrar los valores de los campos</p></li><li><p>Visualización de subcampos</p></li><li><p>_source sintética</p></li><li><p>Campos de tiempo de ejecución</p></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>GET /&lt;index_name&gt;/_mapping<p>Por ejemplo, si tienes un índice llamado <code>my_index</code>, puedes recuperar su mapeo con la siguiente petición:</p>GET /my_index/_mapping<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>GET /my_index/_mapping/field/my_field<p>También puedes recuperar las asignaciones de varios campos separando sus nombres con comas, como en la siguiente petición:</p>GET /my_index/_mapping/field/my_field_1,my_field_2,my_field_3<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><p><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.</p></li><li><p><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.</p></li></ol><h3>Uso del campo _source</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>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "_source": ["title", "author"]
}<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>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "_source": {
     “includes”: [“title”, “author.*],
     “excludes”: [“author.description”]
  }
}<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><p><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.</p></li><li><p><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.</p></li><li><p><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.</p></li><li><p>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.</p></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>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author"],
  "_source": false
}<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 <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrieve-selected-fields#docvalue-fields"><code>docvalue_fields</code></a>. 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>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "docvalue_fields": [
    "title",
    "author",
    {
      "field": "published",
      "format": "epoch_millis"
    }
  ],
  "_source": false
}<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 "<code>store”: true</code>.</p>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "stored_fields": ["summary"]
}<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>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author", "address.city"],
  "_source": false
}<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>PUT idx
{
  "settings": {
    "index": {
      "mapping": {
        "source": {
          "mode": "synthetic"
        }
      }
    }
  }
}<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>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-index-show-fields</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-index-show-fields</guid>
    <category><![CDATA[Datos de índice]]></category>
    <category><![CDATA[Mapeos]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd041e871a8935448/6a17de320b0bedf404dd34ab/23b96aaa1a38b1f4747b4a87695d816f24c0cf70-720x421.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 06 Aug 2025 00:00:00 GMT</pubDate>
  </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>
    <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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt63ea3e7a09306fbf/6a17f1d97f6f150e22c09c50/1c73bd1dc1b5fe54f025c7a2b7c322acc9122f3a-1999x1393.png" alt="" /><p>Nota: Esta entrada del blog se basa en "<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>". Si eres nuevo en los agentes de IA en general y en qué podrían servir, empieza por ahí.
</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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte13f09493f217047/6a17f1db1d1b83178d93e546/443bdc00d84ed1dd49e9f9e431e86ca4b0892563-1999x977.png" alt="" /><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><p><strong>Andamiaje del proyecto:</strong></p><ol><li><p><strong>Backend (Mastra):</strong> Usa NPX Create mastra@latest para andamiar el backend y definir la lógica del agente.</p></li><li><p><strong>Frontend (Vite + React):</strong> Usa npm create vite@latest para construir la interfaz de chat del frontend y así interactuar con el agente.</p></li></ol></li><li><p><strong>Configuración de variables de entorno</strong></p><ol><li><p>Instala dotenv para gestionar las variables del entorno.</p></li><li><p>Crea un .env archiva y proporciona las variables requeridas.</p></li></ol></li><li><p><strong>Configuración de Elasticsearch</strong></p><ol><li><p>Crea un clúster de Elasticsearch (ya sea localmente o en la nube).</p></li><li><p>Instala el cliente oficial de Elasticsearch.</p></li><li><p>Cerciórate de que las variables del entorno sean accesibles.</p></li><li><p>Establece conexión con el cliente.</p></li></ol></li><li><p><strong>Ingiriendo en masa datos de la NBA en Elasticsearch</strong></p><ol><li><p>Crea un índice con los mapeos apropiados para permitir agregaciones.</p></li><li><p>Ingirir en masa las estadísticas de los jugadores de un archivo CSV en un índice de Elasticsearch.</p></li></ol></li><li><p><strong>Definir agregaciones de Elasticsearch</strong></p><ol><li><p>Consulta para calcular promedios históricos contra un oponente específico.</p></li><li><p>Consulta para calcular los promedios de temporada contra un rival específico.</p></li></ol></li><li><p><strong>Archivo utilitario de comparación de jugadores</strong></p><ol><li><p>Consolida funciones auxiliares y agregaciones de Elasticsearch.</p></li></ol></li><li><p><strong>Construcción del agente</strong></p><ol><li><p>Agrega la definición del agente y el prompt del sistema.</p></li><li><p>Instala Zod y define herramientas.</p></li><li><p>Agregar una configuración de middleware para manejar CORS.</p></li></ol></li><li><p><strong>Integración del frontend</strong></p><ol><li><p>Usando el useChat de AI-SDK para interactuar con el agente.</p></li><li><p>Crea la interfaz para mantener conversaciones bien formateadas.</p></li></ol></li><li><p><strong>Ejecutando la aplicación</strong></p><ol><li><p>Inicia tanto el backend (servidor Mestra) como el frontend (app React).</p></li><li><p>Ejemplos de consultas y uso.</p></li></ol></li><li><p><strong>Qué sigue: Hacer que el agente sea más inteligente</strong></p><ol><li><p>Agregar capacidades de búsqueda semántica para permitir recomendaciones más profundas.</p></li><li><p>Habilitar la consulta dinámica moviendo la lógica de búsqueda al servidor Elasticsearch MCP (Model Context Protocol).</p></li></ol></li></ol><h3><strong>Prerrequisitos</strong></h3><ul><li><p><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+).</p></li><li><p><strong>Clúster de Elasticsearch:</strong> Un clúster activo de Elasticsearch, ya sea localmente o en la nube.</p></li><li><p><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>.</p></li></ul><p></p><h3><strong>Estructura del proyecto</strong></h3><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt749baa120552e4ab/6a17f1dd1d1b83bfe993e54a/1c0bde11ad0eead523a95e03b9b905aa776e3fd1-1420x934.png" alt="" /><h4><strong>Paso 1: Estructurar el proyecto</strong></h4><ol><li><p>Primero, crea el directorio nba-ai-assistant-js y navega dentro usando: </p></li></ol>mkdir nba-ai-assistant-js &amp;&amp; cd nba-ai-assistant-js<p><strong>Backend:</strong></p><ol><li><p>Usa la herramienta de creación de Mastra con el comando: </p></li></ol>npx create-mastra@latest<p>2. Deberías recibir algunos prompts en tu terminal; para el primero, llamaremos al backend del proyecto:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt65abf68fe588e968/6a17f1de63baff2814741d5b/de2725031ed6837db99a979efcdd0ece1e197dbb-608x84.png" alt="" /><p>3. A continuación, mantendremos la estructura predeterminada para almacenar los archivos Mastra, así que introduzca <code>src/</code>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt89bd829fcf0ae6b9/6a17f1e04b055dd30e432302/88919d9ff1852126395e1fcd700ecb1b59aac63c-866x116.png" alt="" /><p>4. Luego, elegiremos OpenAI como nuestro proveedor de LLM por defecto.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfd167cc77a40b9a8/6a17f1e11480099e29b48863/2328761e769f3ded134e5a21e8a0bf8f41e88f68-404x210.png" alt="" /><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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt12654151ed495370/6a17f1e22f4a5c0f84fa89f9/0662de9bd28758e377e4c63df8d08b479068ce63-444x120.png" alt="" /><p><strong>Frontend:</strong></p><ol><li><p>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></p></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><p>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>:</p></li></ol>cd backend
npm install dotenv --save<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># 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
<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><p><strong>Opción A: Usar Elasticsearch Cloud</strong></p><ul><li><p>Apúntate a <a href="https://cloud.elastic.co/registration">Elastic Cloud</a></p></li><li><p>Crear un nuevo despliegue</p></li><li><p>Obtén la URL de tu endpoint y la clave API (codificada)</p></li></ul></li><li><p><strong>Opción B: Ejecutar Elasticsearch localmente</strong></p><ul><li><p>Instala y ejecuta Elasticsearch localmente</p></li><li><p>Usa http://localhost:9200 como punto final</p></li><li><p>Generar una clave API</p></li></ul></li></ul><p></p><p><strong>Instalación del cliente Elasticsearch en el backend:</strong></p><ol><li><p>Primero, instala el cliente oficial de Elasticsearch en tu directorio backend:</p></li></ol>npm install @elastic/elasticsearch<p>2. Luego crea una lib de directorios para almacenar funciones reutilizables y navega hacia ella:</p>mkdir lib &amp;&amp; cd lib<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>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 });<p>5. Ahora, inicializa el cliente Elasticsearch usando tus variables de entorno y comprueba la conexión:</p>//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();
<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><p><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.)</p></li><li><p><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.</p></li></ul><p></p><p><strong>Implementación:</strong></p><ol><li><p>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>.</p></li><li><p>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.</p></li></ol>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');<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: 'strict'</code> para que Elasticsearch no mapee dinámicamente campos no reconocidos. 
</p>// 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;
    }
}
<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>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`);
<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>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) =&gt; {
                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);
    }
}
<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>// 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();
<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>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.' };
    }
}
<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.
</p>        {
                            range: {
                    //Range for this season, change to match current season
                                game_date: {
                                    gte: '2024-10-01',
                                    lte: '2025-06-30',
                                },
                            },
<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.
</p>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];
}
<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><p><strong>Nombre:</strong> Dale a tu agente un nombre que se use como referencia cuando te llamen en el frontend.</p></li><li><p><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.</p></li><li><p><strong>Modelo:</strong> ¿Qué LLM usar (Mastra soporta OpenAI, Anthropic, modelos locales, etc.).</p></li><li><p><strong>Herramientas:</strong> Una lista de funciones de la herramienta que el agente puede llamar.</p></li><li><p><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.</p></li></ul><p></p>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 },
});
<p><strong>
Definición de herramientas</strong></p><ol><li><p>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> .</p></li><li><p>Instala Zod usando el comando:</p></li></ol>npm install zod<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><p><code>id</code>: Esta es una descripción en lenguaje natural para ayudar al agente a entender qué hace la herramienta.</p></li><li><p><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.</p></li><li><p><code>description</code>: Esta es una descripción en lenguaje natural para ayudar al agente a entender cuándo llamar y usar la herramienta.</p></li><li><p><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.</p></li></ul>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 } }) =&gt; {
        return await comparePlayersForNextMatchup(player1, player2);
      },
})<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><p><code>origin: ['http://localhost:5173']</code></p><ul><li><p>Permite solicitudes solo desde esta dirección (dirección predeterminada de Vite)</p></li></ul></li><li><p><code>allowMethods: ["GET", "POST"]</code></p><ul><li><p>Métodos HTTP que están permitidos. La mayoría de las veces, será usando POST.</p></li></ul></li><li><p><code>allowHeaders: ["Content-Type", "Authorization", "x-mastra-client-type, "x-highlight-request", "traceparent"],</code></p><ul><li><p>Estos deciden qué encabezados personalizados pueden usar en las solicitudes</p></li></ul></li></ul><p></p>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
<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>npm install @ai-sdk/react<p>2. Mientras estés en el mismo directorio, instala React Markdown para que podamos formatear correctamente la respuesta que genera el agente.</p>npm install react-markdown<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><p><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.</p></li><li><p><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.</p></li><li><p><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.</p></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>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 }) =&gt; {
            setToolsCalled((prev) =&gt; [...prev, toolCall.toolName]);
        },

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

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

    return (
        &lt;div&gt;
            &lt;div className="agent-info"&gt;
                &lt;h4 className="stats-title"&gt;What's My Agent Doing?&lt;/h4&gt;

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

                    &lt;div className="usage-stats"&gt;
                        &lt;p&gt;Prompt Token Usage: {promptTokenUsage}&lt;/p&gt;
                        &lt;p&gt;Completion Token Usage: {completionTokenUsage}&lt;/p&gt;
                        &lt;p&gt;Total Token Usage: {totalTokenUsage}&lt;/p&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;

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

            &lt;form onSubmit={handleSubmit}&gt;
                &lt;input
                    type="text"
                    value={input}
                    onChange={handleInputChange}
                    placeholder="Input two players you want to compare."
                    className="input-box"
                /&gt;
                &lt;button type="submit" disabled={status === 'streaming'}&gt;
                    {status === 'streaming' ? 'Thinking...' : 'Send'}
                &lt;/button&gt;
            &lt;/form&gt;
        &lt;/div&gt;
    );
}<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><p>En una ventana de terminal, empezando desde el directorio raíz, navega hasta el directorio backend e inicia el servidor Mestra:</p></li></ol>cd backend

npm run dev<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>cd frontend

npm run dev<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><p>"Compara a LeBron James y Stephen Curry"</p></li><li><p>"¿A quién debería elegir entre Jayson Tatum y Luka Doncic?"</p></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><p><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.</p></li><li><p><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.</p></li><li><p><strong>El framework Mastra </strong>y cómo simplifica la construcción de estos agentes para desarrolladores en el ecosistema javaScript.</p></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>
    <link>https://www.elastic.co/search-labs/blog/agentic-rag</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/agentic-rag</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[AI agéntica]]></category>
    <category><![CDATA[Javascript]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ffd561836a4cb20/6a17f1e47b54f978588b39e4/8132ed781c1ea5d46ca244182f421ed5c721f23b-1200x628.png" length="0" type="image/png"/>
    <pubDate>Tue, 01 Jul 2025 00:00:00 GMT</pubDate>
  </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>
    <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 "<a href="https://modelcontextprotocol.io/introduction">puerto USB-C para aplicaciones de IA</a>".</p><p>Aquí hay un diagrama arquitectónico que destaca su naturaleza bidireccional:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ff0e141b5dfda29/6a17e7ffe8fbcee5263a1946/5eba1e59514eb58a5220bb92bb49e6328ee83cd7-674x466.png" alt="Arquitectura del protocolo de contexto del modelo (MCP)" /><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><p>Para habilitar MCP remoto con la adición de HTTP Streamable</p></li><li><p>Para habilitar modelos de interacción de agentes más completos con la adición de esquemas de salida de herramientas y elicitación</p></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 "<a href="https://modelcontextprotocol.io/docs/concepts/roots#how-roots-work">informativas y no estrictamente aplicables</a>", 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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4d53e7bb091b4f43/6a17e80163baff80dc741c56/2ea159116fe5e03ff800f077adf16d6ca9f1c1d1-1594x1280.png" alt="Autenticación MCP para agentes" /><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><p><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></p></li><li><p><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.</p></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><p>Perfiles</p><p>Descripción</p><p>Remediación y mejores prácticas</p><p>Inyección rápida y envenenamiento de herramientas</p><p>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.</p><p>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.</p><p>Ataques de ejemplificación</p><p>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.</p><p>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.</p><p>Servidores MCP maliciosos</p><p>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.</p><p>Conectar únicamente a servidores MCP desde registros confiables o listas internas. Ejecute servidores de terceros en contenedores con sandboxing.</p><p>Herramientas de instalación de MCP maliciosas</p><p>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.</p><p>Instale en entornos de espacio aislado y valide las firmas de los paquetes. Nunca actualice automáticamente desde fuentes no verificadas.</p><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><p><a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices">Mejores prácticas oficiales de seguridad de MCP</a></p></li><li><p><a href="https://owasp.org/www-project-top-10-for-large-language-model-applications/">Top 10 de aplicaciones de OWASP LLM</a></p></li><li><p><a href="https://hiddenlayer.com/innovation-hub/">Investigación de amenazas de HiddenLayer</a></p></li><li><p><a href="https://github.com/invariantlabs-ai/mcp-scan">Escaneo MCP</a></p></li><li><p><a href="https://genai.owasp.org/llmrisk/llm01-prompt-injection/">Guía de inyección rápida de OWASP</a></p></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><p>Ofrecer una única fuente de verdad para los metadatos del servidor (qué hace un servidor, cómo autenticarlo, instalarlo y llamarlo)</p></li><li><p>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.</p></li><li><p>Proporcionar un flujo de registro de servidor que incluya una herramienta CLI y un archivo server.json que contenga los metadatos mencionados anteriormente.</p></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><p><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.
</p></li><li><p><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 "manos" o la capacidad de manejar tareas complejas.
</p></li><li><p><strong>Postman - </strong><a href="https://www.postman.com/explore/mcp-generator"><strong>Generador de servidores MCP</strong></a><strong>: </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.
</p></li><li><p><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.
</p></li><li><p><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.
</p></li><li><p><strong>AWS - </strong><a href="https://github.com/awslabs/mcp"><strong>Servidores MCP de AWS</strong></a><strong>: </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.</p></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><p><code>platform.core.search</code> - Ejecuta búsquedas usando el DSL completo de Elasticsearch Query</p></li><li><p><code>platform.core.list_indices</code> - Lista todos los índices disponibles dentro de Elasticsearch (ayuda a los agentes a descubrir qué datos existen)</p></li><li><p><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)</p></li><li><p><code>platform.core.get_document_by_id</code> - Obtiene un documento específico por ID (para una recuperación precisa)</p></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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltca9e3d0a4e7031c0/6a17e803faa913d8c393c897/c1f6405a374b707e8e6fa36b9e21db5f3c7cd127-1376x864.png" alt="Interfaz de Constructor de Agentes" /><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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf7d8b29b06c08f94/6a17e805033c8d07f06bb1b6/9f39588525ca2643475de557ea54a6bcf5c150f6-1282x616.png" alt="Herramientas MCP" /><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>
    <link>https://www.elastic.co/search-labs/blog/mcp-current-state</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/mcp-current-state</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2f63f23bbecd2a18/6a17e8066317302039585aa7/02b8c8672ffa129e0ed91a92d6cab612a01d27f2-1200x628.png" length="0" type="image/png"/>
    <pubDate>Thu, 12 Jun 2025 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>