<?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[Tomás Murúa - 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[Tomás Murúa - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/pt/search-labs/author/tomas-murua</link>
    </image>
    <link>https://www.elastic.co/pt/search-labs/author/tomas-murua</link>
    <atom:link href="https://www.elastic.co/pt/search-labs/rss/author/tomas-murua.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[pt]]></language>
    <lastBuildDate>Mon, 21 Sep 2026 02:51:27 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Criar um conector do ChatGPT com o Elasticsearch para consultar problemas no GitHub]]></title>
    <description><![CDATA[Saiba como criar um conector ChatGPT personalizado e implantar um servidor Elasticsearch MCP que usa a pesquisa híbrida para buscar problemas internos do GitHub.]]></description>
    <content:encoded><![CDATA[<p>Recentemente, a OpenAI anunciou o recurso de <a href="https://help.openai.com/en/articles/11487775-connectors-in-chatgpt">conectores personalizados</a> para o ChatGPT nos planos Pro/Business/Empresarial e Edu. Além dos conectores prontos para uso para acessar dados no Gmail, GitHub, Dropbox etc. É possível criar conectores personalizados usando servidores MCP.</p><p>Os conectores personalizados permitem que você combine seus conectores ChatGPT existentes com fontes adicionais de dados, como o Elasticsearch, para obter respostas abrangentes.</p><p>Neste artigo, criaremos um servidor <a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP</a> que conecta o ChatGPT a um índice Elasticsearch contendo informações sobre problemas internos e solicitações de pull do GitHub. Isso permite que consultas em linguagem natural sejam respondidas usando os dados do seu Elasticsearch.</p><p>Implantaremos o servidor MCP usando o <a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a> no Google Colab com ngrok para obter um URL público ao qual o ChatGPT possa se conectar, eliminando a necessidade de uma configuração de infraestrutura complexa.</p><p>Para uma visão geral do MCP e seu ecossistema, consulte <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">O Estado Atual do MCP</a>.</p><h2>Pré-requisitos</h2><p>Antes de começar, você precisará de:</p><ul><li><p>Cluster do Elasticsearch (8.X ou superior)</p></li><li><p>Chave de API do Elasticsearch com acesso de leitura ao seu índice</p></li><li><p>Conta do Google (para o Google Colab)</p></li><li><p>Conta Ngrok (versão gratuita funciona)</p></li><li><p>Conta do ChatGPT com plano Pro/Empresarial/Business ou Edu</p></li></ul><h2>Entendendo os requisitos do conector MCP do ChatGPT</h2><p>Os conectores MCP do ChatGPT exigem a implementação de duas ferramentas: <code>search</code> e <code>fetch</code>. Para mais detalhes, consulte <a href="https://platform.openai.com/docs/mcp#create-an-mcp-server">OpenAI Docs</a>.</p><h3><a href="https://platform.openai.com/docs/mcp#search-tool">Ferramenta de busca</a></h3><p>Retorna uma lista de resultados relevantes do seu índice Elasticsearch com base em uma consulta do usuário.</p><h4>O que ele recebe:</h4><ul><li><p>Uma única string com a consulta de linguagem natural do usuário.</p></li><li><p>Exemplo: "Encontre problemas relacionados à migração do Elasticsearch."</p></li></ul><h4>O que ele retorna: </h4><ul><li><p>Um objeto com uma chave <code>result</code> contendo um array de objetos de resultado. Cada resultado inclui:</p><ul><li><p><code>id</code> - Identificador único do documento</p></li><li><p><code>title</code> - Título da issue ou do PR</p></li><li><p><code>url</code> - Link para o problema/PR</p></li></ul></li></ul><h4>Na nossa implementação:</h4>return {
    "results": [
        {
            "id": "PR-612",
            "title": "Fix memory leak in WebSocket notification service",
            "url": "https://internal-git.techcorp.com/pulls/612"
        },
        # ... more results
    ]
}<h3><a href="https://platform.openai.com/docs/mcp#fetch-tool">Ferramenta de recuperação</a></h3><p>Recupera o conteúdo completo de um documento específico.</p><h4>O que ele recebe:</h4><ul><li><p>Uma única string com o ID do documento Elasticsearch do resultado de busca</p></li><li><p>Exemplo: "Me dê os detalhes do PR-578."</p></li></ul><h4>O que ele retorna:</h4><ul><li><p>Um objeto de documento completo com:</p><ul><li><p><code>id</code> - Identificador único do documento</p></li><li><p><code>title</code> - Título da issue ou do PR</p></li><li><p><code>text</code> - Complete a descrição e os detalhes do problema/PR</p></li><li><p><code>url</code> - Link para o problema/PR</p></li><li><p><code>type</code> - Tipo de documento (issue, pull_request)</p></li><li><p><code>status</code> - Status atual (aberto, em_andamento, resolvido)</p></li><li><p><code>priority</code> - Nível de prioridade (baixo, médio, alto, crítico)</p></li><li><p><code>assignee</code> - Pessoa designada para o problema/PR</p></li><li><p><code>created_date</code> - Quando foi criado</p></li><li><p><code>resolved_date</code> - Quando foi resolvido (se aplicável)</p></li><li><p><code>labels</code> - Tags associadas ao documento</p></li><li><p><code>related_pr</code> - ID de pull request relacionado</p></li></ul></li></ul>return {
    "id": "PR-578",
    "title": "Security hotfix: Patch SQL injection vulnerabilities",
    "text": "Description: CRITICAL SECURITY FIX for ISSUE-1889. Patches SQL...",
    "url": "https://internal-git.techcorp.com/pulls/578",
    "type": "pull_request",
    "status": "closed",
    "priority": "critical",
    "assignee": "sarah_dev",
    "created_date": "2025-09-19",
    "resolved_date": "2025-09-19",
    "labels": "security, hotfix, sql",
    "related_pr": null
}<p><strong>Observação</strong>: este exemplo usa uma estrutura plana onde todos os campos estão no nível raiz. Os requisitos do OpenAI são flexíveis e também permitem objetos de metadados aninhados.</p><h2>Questões do GitHub e conjunto de dados PRs</h2><p>Para este tutorial, vamos usar um conjunto de dados interno do GitHub contendo problemas e solicitações de pull. Isso representa um cenário em que você deseja consultar dados privados e internos por meio do ChatGPT.</p><p>O conjunto de dados pode ser encontrado <a href="https://gist.github.com/TomasMurua/4e7bbdf7a7ebbdffaa663c43578d934a">aqui</a>. E atualizaremos o índice dos dados usando a <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk">bulk API</a>.</p><p>Esse conjunto de dados inclui:</p><ul><li><p>Problemas com descrições, status, prioridade e responsáveis</p></li><li><p>Solicitações de pull com alterações de código, revisões e informações de implantação</p></li><li><p>Relações entre problemas e PRs (por exemplo, PR-578 corrige o ISSUE-1889)</p></li><li><p>Rótulos, datas e outros metadados</p></li></ul><h3>Mapeamentos de índice</h3><p>O índice usa os seguintes <a href="https://www.elastic.co/docs/manage-data/data-store/mapping">mapeamentos</a> para permitir a pesquisa híbrida com o <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-elser">ELSER</a>. A <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">text_semantic</a> é usada para busca semântica, enquanto outros campos permitem a busca por palavras-chave.</p>{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "title": {
        "type": "text"
      },
      "text": {
        "type": "text"
      },
      "text_semantic": {
        "type": "semantic_text",
        "inference_id": ".elser-2-elasticsearch"
      },
      "url": {
        "type": "keyword"
      },
      "type": {
        "type": "keyword"
      },
      "status": {
        "type": "keyword"
      },
      "priority": {
        "type": "keyword"
      },
      "assignee": {
        "type": "keyword"
      },
      "created_date": {
        "type": "date",
        "format": "iso8601"
      },
      "resolved_date": {
        "type": "date",
        "format": "iso8601"
      },
      "labels": {
        "type": "keyword"
      },
      "related_pr": {
        "type": "keyword"
      }
    }
  }
}<h2>Construa o servidor MCP</h2><p>Nosso servidor MCP implementa duas ferramentas seguindo as especificações da OpenAI, usando busca híbrida para combinar correspondência semântica e de texto para obter melhores resultados.</p><h3>Ferramenta de busca</h3><p>Utiliza busca híbrida com <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">RRF</a> (Reciprocal Rank Fusion), combinando buscar semântica com correspondência de texto:</p>@mcp.tool()
    async def search(query: str) -&gt; Dict[str, List[Dict[str, Any]]]:
        """
        Search for internal issues and PRs using hybrid search (semantic + text with RRF).
        Returns list with id, title, and url per OpenAI spec.
        """
        if not query or not query.strip():
            return {"results": []}

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

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

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

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

        except Exception as e:
            logger.error(f"Search error: {e}")
            raise ValueError(f"Search failed: {str(e)}")<h3>Pontos principais:</h3><ul><li><p><strong>Busca híbrida com RRF:</strong> combina busca semântica (ELSER) e busca por texto (BM25) para melhores resultados.</p></li><li><p><strong>Consulta multi-correspondência:</strong> <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-multi-match-query">busca em múltiplos campos</a> com aumento de relevância (title^3, text^2, assignee^2). O símbolo de caret (^) multiplica as pontuações de relevância, priorizando as correspondências nos títulos em detrimento do conteúdo.</p></li><li><p><strong>Correspondência inexata:</strong> <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/common-options#fuzziness"><code>fuzziness: AUTO</code></a> lida com erros de digitação e ortografia, permitindo correspondências aproximadas.</p></li><li><p><strong>Ajuste dos parâmetros do RRF:</strong></p><ul><li><p><code>rank_window_size: 50</code> - Especifica quantos resultados principais de cada recuperador (semântico e textual) são considerados antes da mesclagem.</p></li><li><p><code>rank_constant: 60</code> - Esse valor determina quanta influência os documentos em conjuntos de resultados individuais têm sobre o resultado final classificado.</p></li></ul></li><li><p><strong>Retorna somente os campos obrigatórios:</strong> <code>id</code>, <code>title</code>, <code>url</code> de acordo com a especificação da OpenAI e evita a exposição desnecessária de campos adicionais.</p></li></ul><h3>Ferramenta de recuperação</h3><p>Recupera detalhes do documento pelo ID do documento, quando existe:</p>@mcp.tool()
    async def fetch(id: str) -&gt; Dict[str, Any]:
        """
        Retrieve complete issue/PR details by ID.
        Returns id, title, text, url.
        """
        if not id:
            raise ValueError("ID is required")

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

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

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

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

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

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

        except Exception as e:
            logger.error(f"Fetch error: {e}")
            raise ValueError(f"Failed to fetch '{id}': {str(e)}")<h3>Pontos principais:</h3><ul><li><p><strong>Buscar por campo de ID do documento:</strong> Utiliza consulta de termo no campo personalizado <code>id</code></p></li><li><p><strong>Retorna o documento completo:</strong> inclui o campo <code>text</code> completo com todo o conteúdo</p></li><li><p><strong>Estrutura plana:</strong> Todos os campos no nível da raiz, correspondendo à estrutura de documentos do Elasticsearch.</p></li></ul><h2>Implantar no Google Colab</h2><p>Usaremos o Google Colab para executar nosso servidor MCP e o ngrok para expô-lo publicamente, permitindo que o ChatGPT se conecte a ele.</p><h3>Etapa 1: Abra o notebook do Google Colab</h3><p>Acesse nosso notebook pré-configurado <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/elasticsearch-chatgpt-connector">Elasticsearch MCP para ChatGPT</a>.</p><h3>Etapa 2: Configure suas credenciais</h3><p>Você precisará de três informações:</p><ul><li><p><strong>URL do Elasticsearch:</strong> seu <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-enterprise/connect-elasticsearch">URL do cluster do Elasticsearch</a>.</p></li><li><p><strong>Chave da API do Elasticsearch:</strong> <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">Chave da API</a> com permissão de leitura do seu índice.</p></li><li><p><strong>Token de autenticação Ngrok:</strong> token grátis do <a href="https://ngrok.com/">ngrok</a>. Vamos usar o ngrok para expor a URL do MCP à internet para que o ChatGPT possa se conectar a ela.</p></li></ul><h4>Obter seu token ngrok</h4><ol><li><p>Cadastre-se para uma conta gratuita em <a href="https://ngrok.com/">ngrok</a></p></li><li><p>Acesse seu <a href="https://dashboard.ngrok.com/">painel do ngrok</a></p></li><li><p>Copie seu token de autenticação.</p></li></ol><h4>Adicionando segredos ao Google Colab</h4><p>No notebook do Google Colab:</p><ol><li><p>Clique no <strong>ícone de chave </strong>na barra lateral esquerda para abrir <strong>Secrets</strong>.</p></li><li><p>Adicione estes três segredos:</p></li></ol>ELASTICSEARCH_URL=https://your-cluster.elastic.com:443
ELASTICSEARCH_API_KEY=your-api-key
NGROK_TOKEN=your-ngrok-token<p>3. Habilitar o acesso ao notebook para cada segredo</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5acae97b386277f8/6a17f08f5ea30f74c964b6c2/d5dd6ac19fe816a562c6351fdb0f11369da0e877-609x321.jpg" alt="Adicionar segredos ao Google Collab" /><h3>Passo 3: Execute o notebook</h3><ol><li><p>Clique em <strong>Runtime</strong> e depois em <strong>Executar tudo</strong> para executar todas as células</p></li><li><p>Aguarde o servidor iniciar (cerca de 30 segundos)</p></li><li><p>Procure a saída mostrando seu URL público do ngrok</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdd11aacf2deab67c/6a17f091e8fbce81f13a1a41/f185100e8869624bc9e1c7b2b4eb32785e2d89e7-1189x283.png" alt="" /><p>4. A saída exibirá algo como:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8891d917fdbaaf48/6a17f092abe0f208c7dfeaf6/e02e625e91ed9136454e4401b184575fb03a336e-1052x465.jpg" alt="A saída da execução de um notebook no Google Collab" /><h2>Conectar-se ao ChatGPT</h2><p>Agora vamos conectar o servidor MCP à sua conta do ChatGPT.</p><ol><li><p>Abra o ChatGPT e vá para <strong>Configurações</strong>.</p></li><li><p>Navegue até <strong>Conectores. </strong>Se você estiver usando uma conta Pro, precisará ativar <a href="https://platform.openai.com/docs/guides/developer-mode">o modo de desenvolvedor</a> nos conectores.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt95efdcb2c39307e7/6a17f094abe0f24d8edfeafa/32c02192912fc0e7e5a52e9399077ba7ae3b4901-739x715.png" alt="Conectando o servidor MPC a uma conta do ChatGPT" /><p><em>Se você está usando o ChatGPT em empresas ou negócios, precisa disponibilizar o conector para seu local de trabalho.</em></p><p>3. Clique em <strong>Criar</strong>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4c8fc8dd6033918/6a17f095631730de19585b7b/15c53e5ccc381108a9dc0052cca05bf0fc97679a-755x683.png" alt="Adicionando um conector ao ChatGPT" /><p><em><strong>Observação</strong></em><em>: nos espaços de trabalho Business, Empresarial e Edu, somente os proprietários, administradores e usuários com a respectiva configuração ativada (para Empresarial/Edu) podem adicionar conectores personalizados. Usuários com a função de membro padrão não têm permissão para adicionar conectores personalizados.</em></p><p><em>Após um conector ser adicionado e habilitado por um proprietário ou usuário administrador, ele fica disponível para todos os membros do espaço de trabalho.</em></p><p>4. Insira as informações necessárias e sua URL ngrok que termina em <code>/sse/</code>. Repare no "/" após "sse". Não vai funcionar sem ele:</p><ul><li><p><strong>Nome:</strong> Elasticsearch MCP</p></li><li><p><strong>Descrição: </strong>MCP personalizado para pesquisar e recuperar informações internas do GitHub.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd716ad0beeeb1d35/6a17f09714d90c11cc79b6d7/162a85705cc8ac48a3f2f665551d513e0719f93d-479x684.png" alt="Criar um Conector MCP Elastic " /><p>5. Pressione <strong>Criar</strong> para salvar o MCP personalizado.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt857794237d7d3b5a/6a17f0983e03d729b74f2d54/97eb5fb0a32b86bfadfb35561f698616f217c049-913x629.png" alt="Salvar o conector MCP personalizado clicando em criar" /><p>A conexão será instantânea se seu servidor estiver em execução. Não é necessária autenticação adicional, pois a chave da API do Elasticsearch está configurada no seu servidor.</p><h2>Teste o servidor MCP</h2><p>Antes de fazer perguntas, você precisa selecionar qual conector o ChatGPT deve usar.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1602c48878dc7a9/6a17f09a6df731cca90a0fff/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" alt="Selecionar qual conector o ChatGPT deve usar" /><h3>Prompt 1: Buscar por problemas</h3><p>Pergunte: "<strong>Encontre problemas relacionados à migração do Elasticsearch" </strong>e confirme a chamada da ferramenta de ações.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c204ceacf897f61/6a17f09c9da390fb1de4657d/cfd781acbff8cd7c8095bbe29224f8b26d581f77-650x375.png" alt="Peça ao ChatGPT &quot;Encontre problemas relacionados à migração do Elasticsearch&quot; e confirme a chamada da ferramenta de ações." /><p>O ChatGPT chamará a ferramenta <code>search</code> com sua consulta. Você pode ver que ele está procurando as ferramentas disponíveis, se preparando para chamar a ferramenta Elasticsearch e confirma com o usuário antes de tomar qualquer medida em relação à ferramenta.</p><h4>Solicitação de chamada de ferramenta:</h4>{
  "query": "Elasticsearch migration issues"
}<h4>Resposta da ferramenta:</h4>{
  "results": [
    {
      "id": "PR-598",
      "title": "Elasticsearch 8.x migration - Application code changes",
      "url": "https://internal-git.techcorp.com/pulls/598"
    },
    {
      "id": "ISSUE-1712",
      "title": "Migrate from Elasticsearch 7.x to 8.x",
      "url": "https://internal-git.techcorp.com/issues/1712"
    },
    {
      "id": "RFC-045",
      "title": "Design Proposal: Microservices Migration Architecture",
      "url": "https://internal-git.techcorp.com/rfcs/045"
    }
    // ... 7 more results
  ]
}<p>O ChatGPT processa os resultados e os apresenta em um formato natural e conversacional.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1b4378e7d26b4ad0/6a17f09ddbb4ff4de1fb57bf/9d5b6cff85c7e54ccc2584b8ae96d45495fae8c1-923x1352.png" alt="Como o ChatGPT processa os resultados da solicitação de chamada da ferramenta e da resposta à chamada da ferramenta" /><h3>Nos bastidores</h3><h4>Prompt: "Encontrar problemas relacionados à migração do Elasticsearch"</h4><p>1. Chamadas do ChatGPT <code>search(“Elasticsearch migration”)</code></p><p>2. O Elasticsearch realiza uma busca híbrida</p><ul><li><p><strong>A busca semântica</strong> compreende conceitos como "atualização" e "<em>compatibilidade de versões"</em>.</p></li><li><p>A <strong>busca de texto</strong> encontra correspondências exatas para "<em>Elasticsearch</em>" e "migração".</p></li><li><p>O <strong>RRF</strong> combina e classifica os resultados de ambas as abordagens</p></li></ul><p>3. Retorna os 10 melhores eventos de correspondência com <code>id</code>, <code>title</code>, <code>url</code></p><p>4. O ChatGPT identifica "<em>ISSUE-1712: migrar do Elasticsearch 7.x para o 8.x</em>" como o resultado mais relevante</p><h3>Prompt 2: Obter todos os detalhes</h3><p>Perguntar: <em><strong>"Informe detalhes sobre o ISSUE-1889"</strong></em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8d1a53db8bfe8326/6a17f09f445de966104d021a/5c0db5245535ce67a36056e61e135bddc97ce496-934x629.png" alt="O ChatGPT reconhece que você quer informações detalhadas sobre um problema específico, aciona a ferramenta fetch e confirma com o usuário antes de tomar qualquer medida em relação à ferramenta." /><p>O ChatGPT reconhece que você quer informações detalhadas sobre um problema específico e aciona a ferramenta <code>fetch</code>, confirmando com o usuário antes de tomar qualquer medida em relação à ferramenta.</p><h4>Solicitação de chamada de ferramenta:</h4>{
  "id": "ISSUE-1889"
}<h4>Resposta da ferramenta:</h4>{
  "id": "ISSUE-1889",
  "title": "SQL injection vulnerability in search endpoint",
  "text": "Description: Security audit identified SQL injection vulnerability in /api/v1/search endpoint. User input from query parameter is not properly sanitized before being used in raw SQL query. Severity: HIGH - Immediate action required Affected Code: - File: services/search/query_builder.py - Line: 145-152 - Issue: String concatenation used instead of parameterized queries Investigation: - @security_team_alice: Confirmed exploitable with UNION-based injection - @sarah_dev: Checking all other endpoints for similar patterns - @john_backend: Found 3 more instances in legacy codebase Remediation: - Rewrite using SQLAlchemy ORM or parameterized queries - Add input validation and sanitization - Implement WAF rules as additional layer - Security regression tests Comments: - @tech_lead_mike: Stop all other work, this is P0 - @sarah_dev: PR-578 ready with fixes for all 4 vulnerable endpoints - @alex_devops: Deployed hotfix to production 2025-09-19 at 14:30 UTC - @security_team_alice: Verified fix, conducting full pentest next week Resolution: All vulnerable endpoints patched. Added pre-commit hooks to catch raw SQL queries. Security training scheduled for team.",
  "url": "https://internal-git.techcorp.com/issues/1889",
  "type": "issue",
  "status": "closed",
  "priority": "critical",
  "assignee": "sarah_dev",
  "created_date": "2025-09-18",
  "resolved_date": "2025-09-19",
  "labels": "security, vulnerability, bug, sql",
  "related_pr": "PR-578"
}<p>O ChatGPT sintetiza as informações e as apresenta claramente.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt560958fa3bd212d0/6a17f0a0faa91355ba93c974/410f19f213e94fc4e3c47eeef6e04b69e0c86159-602x462.png" alt="Como o ChatGPT sintetiza as informações e as apresenta " /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcccf35a584e8373b/6a17f0a2505ac3471cad8c2e/54d8ffa117628a1e3afc317c3ab75d4f7731d7ab-767x1600.png" alt="Como o ChatGPT apresenta as informações" /><h3>Nos bastidores</h3><h4>Prompt: “Informe mais detalhes sobre ISSUE-1889”</h4><ol><li><p>Chamadas do ChatGPT <code>fetch(“ISSUE-1889”)</code></p></li><li><p>O Elasticsearch recupera o documento completo</p></li><li><p>Retorna um documento completo com todos os campos no nível raiz</p></li><li><p>O ChatGPT sintetiza as informações e responde com citações adequadas.</p></li></ol><h2>Conclusão</h2><p>Neste artigo, criamos um servidor MCP personalizado que conecta o ChatGPT ao Elasticsearch usando ferramentas MCP dedicadas de <strong>busca</strong> e <strong>recuperação</strong>, permitindo consultas em linguagem natural sobre dados privados.</p><p>Este padrão MCP funciona para qualquer índice Elasticsearch, documentação, produtos, log ou quaisquer outros dados que você queira consultar por meio de linguagem natural.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/chatgpt-connector-mcp-server-github-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/chatgpt-connector-mcp-server-github-elasticsearch</guid>
    <category><![CDATA[IA agêntica]]></category>
    <category><![CDATA[Busca híbrida]]></category>
    <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1602c48878dc7a9/6a17f09a6df731cca90a0fff/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" length="0" type="image/gif"/>
    <pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Criando um agente de IA para RH com Elastic Agent Builder e GPT-OSS]]></title>
    <description><![CDATA[Descubra como criar um agente de IA capaz de responder a consultas em linguagem natural sobre os dados de RH dos seus funcionários usando o Elastic Agent Builder e o GPT-OSS.]]></description>
    <content:encoded><![CDATA[<h2>Introdução</h2><p>Este artigo mostrará como criar um agente de IA para RH usando <a href="https://openai.com/index/introducing-gpt-oss/">GPT-OSS</a> e Elastic Agent Builder. O agente pode responder às suas perguntas sem enviar dados para a OpenAI, Anthropic ou qualquer serviço externo.</p><p>Usaremos o LM Studio para disponibilizar o GPT-OSS localmente e conectá-lo ao Elastic Agent Builder.</p><p>Ao final deste artigo, você terá um agente de IA personalizado capaz de responder a perguntas em linguagem natural sobre os dados de seus funcionários, mantendo o controle total sobre suas informações e modelo.</p><h2>Pré-requisitos</h2><p>Para ler este artigo, você precisa de:</p><ul><li><p><a href="https://www.elastic.co/cloud">Elastic Cloud</a> hospedado na versão 9.2, implantação <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">local</a> ou sem servidor.</p></li><li><p>Recomenda-se máquina com 32 GB de RAM (mínimo de 16 GB para GPT-OSS 20B).</p></li><li><p><a href="https://lmstudio.ai/">LM Studio</a> instalado</p></li><li><p><a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a> instalado</p></li></ul><h2>Por que usar GPT-OSS?</h2><p>Com um LLM local, você tem o controle para implantá-lo em sua própria infraestrutura e ajustá-lo para atender às suas necessidades específicas. Tudo isso mantendo o controle sobre os dados que você compartilha com o modelo e, claro, sem precisar pagar nenhuma taxa de licença a um fornecedor externo.</p><p>A OpenAI <a href="https://openai.com/index/introducing-gpt-oss/">lançou o GPT-OSS</a> em 5 de agosto de 2025, como parte de seu compromisso com o ecossistema de modelos abertos.</p><p>O modelo de parâmetros 20B oferece:</p><ul><li><p><strong>capacidades de utilização da ferramenta</strong></p></li><li><p><strong>Inferência eficiente</strong></p></li><li><p><strong>Compatível com o SDK OpenAI</strong></p></li><li><p><strong>Compatível com fluxos de trabalho agentes</strong></p></li></ul><p>Comparação de referência:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt58fab956edb40412/6a170cfcb0367da43a72bd80/29160e3345352088e8213297630882f252b00c47-1600x680.png" alt="" /><h2>Arquitetura da solução</h2><p>A arquitetura é executada inteiramente em sua máquina local. O Elastic (executado em um contêiner Docker) se comunica diretamente com seu LLM local por meio do LM Studio, e o Elastic Agent Builder usa essa conexão para criar agentes de IA personalizados que podem consultar os dados de seus funcionários.</p><p>Para obter mais detalhes, consulte esta <a href="https://www.elastic.co/docs/solutions/observability/connect-to-own-local-llm">documentação</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt80db5bb0a797f51b/6a170cfd0e2e492f2c41a16f/a4a886750ff25fa8bb7aefc7448161e52cf73ed3-1600x896.png" alt="" /><h2>Construindo um agente de IA para RH: Etapas</h2><p>Dividiremos a implementação em 5 etapas:</p><ol><li><p>Configure o LM Studio com um modelo local.</p></li><li><p>Implante o Elastic local com o Docker.</p></li><li><p>Crie o conector OpenAI no Elastic</p></li><li><p>Carregar dados de funcionários no Elasticsearch</p></li><li><p>Crie e teste seu agente de IA.</p></li></ol><h2>Etapa 1: Configurar o LM Studio com GPT-OSS 20B</h2><p>O LM Studio é um aplicativo fácil de usar que permite executar grandes modelos de linguagem localmente em seu computador. Ele fornece um servidor de API compatível com OpenAI, facilitando a integração com ferramentas como o Elastic, sem um processo de configuração complexo. Para obter mais detalhes, consulte a <a href="https://lmstudio.ai/docs/app">documentação do LM Studio</a>.</p><p>Primeiro, baixe e instale o LM Studio a partir do site oficial. Após a instalação, abra o aplicativo.</p><h3>Na interface do LM Studio:</h3><ol><li><p>Acesse a aba de pesquisa e procure por “GPT-OSS”.</p></li><li><p>Selecione o <code>openai/gpt-oss-20b</code> da OpenAI</p></li><li><p>Clique em baixar</p></li></ol><p>O tamanho deste modelo deverá ser de aproximadamente <strong>12,10 GB</strong>. O download pode demorar alguns minutos, dependendo da sua conexão com a internet.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2dc341a6625e34b7/6a170cff839dfa2eb4dcff44/5d01bc4dcb377b5259fc6b521fe2425a31b90ca4-1312x872.png" alt="" /><h4>Após o download do modelo:</h4><ol><li><p>Acesse a aba do servidor local.</p></li><li><p>Selecione o openai/gpt-oss-20b</p></li><li><p>Use a porta padrão 1234</p></li><li><p>No painel direito, acesse <strong>Carregar </strong>e defina o Comprimento do Contexto para <strong>40K</strong> ou mais.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3704ca1b28465cc4/6a170d00d7c022ed8fde64ef/e546033f916381647b876815b2c1f1ae2a08365f-326x337.png" alt="" /><p>5. Clique em Iniciar servidor</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b9170a4945ff857/6a170d0266c4f9ffadf8c0a6/28ee78a3caa84d14e04db3d42f30acbe4d4d005a-1312x872.png" alt="" /><p>Você deverá ver isso se o servidor estiver em execução.</p>[LM STUDIO SERVER] Success! HTTP server listening on port 1234
[LM STUDIO SERVER] Supported endpoints:
[LM STUDIO SERVER] -&gt;	GET  http://localhost:1234/v1/models
[LM STUDIO SERVER] -&gt;	POST http://localhost:1234/v1/responses
[LM STUDIO SERVER] -&gt;	POST http://localhost:1234/v1/chat/completions
[LM STUDIO SERVER] -&gt;	POST http://localhost:1234/v1/completions
[LM STUDIO SERVER] -&gt;	POST http://localhost:1234/v1/embeddings
Server started.<h2>Etapa 2: Implante o Elastic local com o Docker</h2><p>Agora vamos configurar o Elasticsearch e o Kibana localmente usando o Docker. A Elastic fornece um script prático que lida com todo o processo de configuração. Para obter mais detalhes, consulte a <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">documentação oficial</a>.</p><h3>Execute o script start-local</h3><p>Execute o seguinte comando no seu terminal:</p>curl -fsSL https://elastic.co/start-local | sh<p>Este script irá:</p><ul><li><p>Baixe e configure o Elasticsearch e o Kibana.</p></li><li><p>Inicie ambos os serviços usando o Docker Compose.</p></li><li><p>Ative automaticamente uma licença de avaliação Platinum de 30 dias.</p></li></ul><h3>Resultado esperado</h3><p>Aguarde a seguinte mensagem e salve a senha e a chave da API exibidas; você precisará delas para acessar o Kibana:</p>🎉 Congrats, Elasticsearch and Kibana are installed and running in Docker!
🌐 Open your browser at http://localhost:5601
   Username: elastic
   Password: KSUlOMNr
🔌 Elasticsearch API endpoint: http://localhost:9200
🔑 API key: cnJGX0pwb0JhOG00cmNJVklUNXg6cnNJdXZWMnM4bncwMllpQlFlUTlWdw==
Learn more at https://github.com/elastic/start-local<h3>Acesse o Kibana</h3><p>Abra seu navegador e acesse:</p>http://localhost:5601<p>Faça login utilizando as credenciais obtidas na saída do terminal.</p><h3>Habilitar o Construtor de Agentes</h3><p>Após fazer login no Kibana, navegue até <strong>Gerenciamento </strong>&gt;<strong> IA </strong>&gt;<strong> Construtor de Agentes </strong>e ative o Construtor de Agentes.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0a934bd99fa6a0ce/6a170d046234e019c3db1a5a/92e104cb846c20d875865ded8a3d37f5c7daae9b-1491x1528.png" alt="" /><h2>Etapa 3: Crie o conector OpenAI no Elastic</h2><p>Agora vamos configurar o Elastic para usar seu LLM local.</p><h3>Conectores de acesso</h3><ol><li><p>Em Kibana</p></li><li><p>Acesse <strong>Configurações do projeto</strong> &gt; <strong>Gerenciamento</strong></p></li><li><p>Em <strong>Alertas e insights</strong>, selecione <strong>Conectores.</strong></p></li><li><p>Clique em Criar conector</p></li></ol><h3>Configure o conector</h3><p>Selecione <strong>OpenAI</strong> na lista de conectores. O LM Studio utiliza o SDK da OpenAI, o que o torna compatível.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt762023c39781eb78/6a170d06a29299a59ed01087/5ac87042e086c7a2bd47a8039e646ec831f0dcc6-923x974.png" alt="" /><p>Preencha os campos com estes valores:</p><ul><li><p><strong>Nome do conector: </strong>LM Studio - GPT-OSS 20B</p></li><li><p><strong>Selecione um provedor OpenAI: </strong>Outro (Serviço compatível com OpenAI)</p></li><li><p><strong>URL: </strong><code>http://host.docker.internal:1234/v1/chat/completions</code></p></li><li><p><strong>Modelo padrão: </strong>openai/gpt-oss-20b</p></li><li><p><strong>Chave da API:</strong> testkey-123 (qualquer texto funciona, pois o LM Studio Server não requer autenticação).</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt980e595f80e2be2e/6a170d086f7f0468a19148cc/2084ac32fcf1fb810c8b54ecab1c85a1e3e8905b-672x1302.png" alt="" /><p>Para finalizar a configuração, clique em <strong>Salvar e testar</strong>.</p><p><strong>Importante:</strong> Ative a opção “<strong>Habilitar chamada de função nativa</strong>”; isso é necessário para que o Construtor de Agentes funcione corretamente. Se você não habilitar isso, você receberá um erro <strong><code>No tool calls found in the response</code></strong> .</p><h3>Teste a conexão</h3><p>O Elastic deve testar a conexão automaticamente. Se tudo estiver configurado corretamente, você verá uma mensagem de sucesso como esta:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4d2e815dd558f881/6a170d090e2e49076541a177/f567d767f1969c4730c1daa92f651789dc3742ac-1042x812.png" alt="" /><p>Resposta.</p>{
  "status": "ok",
  "data": {
    "id": "chatcmpl-flj9h0hy4wcx4bfson00an",
    "object": "chat.completion",
    "created": 1761189456,
    "model": "openai/gpt-oss-20b",
    "choices": [
      {
        "index": 0,
        "message": {
          "role": "assistant",
          "content": "Hello! 👋 How can I assist you today?",
          "reasoning": "Just greet.",
          "tool_calls": []
        },
        "logprobs": null,
        "finish_reason": "stop"
      }
    ],
    "usage": {
      "prompt_tokens": 69,
      "completion_tokens": 23,
      "total_tokens": 92
    },
    "stats": {},
    "system_fingerprint": "openai/gpt-oss-20b"
  },
  "actionId": "ee1c3aaf-bad0-4ada-8149-118f52dad757"
}<h2>Etapa 4: Carregar os dados dos funcionários no Elasticsearch</h2><p>Agora vamos carregar o <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/gpt-oss-with-elasticsearch/hr-employees-bulk.json">conjunto de dados de funcionários de RH</a> para demonstrar como o agente trabalha com dados confidenciais. Eu gerei um conjunto de dados fictício com essa estrutura.</p><h3>Estrutura do conjunto de dados</h3>{
  "employee_id": "0f4dce68-2a09-4cb1-b2af-6bcb4821539b",
  "full_name": "Daffi Stiebler",
  "email": "lscutchings0@huffingtonpost.com",
  "date_of_birth": "1975-06-20T15:39:36Z",
  "hire_date": "2025-07-28T00:10:45Z",
  "job_title": "Physical Therapy Assistant",
  "department": "HR",
  "salary": "108455",
  "performance_rating": "Needs Improvement",
  "years_of_experience": 2,
  "skills": "Java",
  "education_level": "Master's Degree",
  "manager": "Carl MacGibbon",
  "emergency_contact": "Leigha Scutchings",
  "home_address": "5571 6th Park"
}<h3>Criar o índice com mapeamentos</h3><p>Primeiro, crie o índice com os mapeamentos adequados. Observe que estamos usando campos <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">semantic_text</a> para alguns campos-chave; isso possibilita recursos de busca semântica em nosso índice.</p>​​PUT hr-employees
{
  "mappings": {
    "properties": {
      "@timestamp": {
        "type": "date"
      },
      "employee_id": {
        "type": "keyword"
      },
      "full_name": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "email": {
        "type": "keyword"
      },
      "date_of_birth": {
        "type": "date",
        "format": "iso8601"
      },
      "hire_date": {
        "type": "date",
        "format": "iso8601"
      },
      "job_title": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "department": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "salary": {
        "type": "double"
      },
      "performance_rating": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "years_of_experience": {
        "type": "long"
      },
      "skills": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "education_level": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "manager": {
        "type": "text",
        "copy_to": "employee_semantic"
      },
      "emergency_contact": {
        "type": "keyword"
      },
      "home_address": {
        "type": "keyword"
      },
      "employee_semantic": {
        "type": "semantic_text"
      }
    }
  }
}<h3>Indexar com API em lote</h3><p>Copie e cole o <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/gpt-oss-with-elasticsearch/hr-employees-bulk.json">conjunto de dados</a> nas suas Ferramentas de Desenvolvedor no Kibana e execute-o:</p>POST hr-employees/_bulk
{"index": {}}
{"employee_id": "57728b91-e5d7-4fa8-954a-2384040d3886", "full_name": "Filide Gane", "email": "vhallahan1@booking.com", "job_title": "Business Systems Development Analyst", "department": "Marketing", "salary": "$52330.27", "performance_rating": "Meets Expectations", "years_of_experience": 12, "skills": "Java", "education_level": "Bachelor's Degree", "date_of_birth": "2000-02-07T16:49:32Z", "hire_date": "2023-11-07T13:03:16Z", "manager": "Freedman Kings", "emergency_contact": "Vilhelmina Hallahan", "home_address": "75 Dennis Junction"}
{"index": {}}
{"employee_id": "...", ...}<h3>Verifique os dados</h3><p>Execute uma consulta para verificar:</p>GET hr-employees/_search<h2>Etapa 5: Crie e teste seu agente de IA</h2><p>Com tudo configurado, é hora de criar um agente de IA personalizado usando o Elastic Agent Builder. Para obter mais detalhes, consulte a <a href="https://www.elastic.co/docs/solutions/search/agent-builder/get-started">documentação da Elastic</a>.</p><h3>Adicione o conector</h3><p>Antes de podermos criar nosso novo agente, precisamos configurar nosso construtor de agentes para usar nosso conector personalizado chamado <code>LM Studio - GPT-OSS 20B</code> , porque o padrão é o <a href="https://www.elastic.co/docs/reference/kibana/connectors-kibana/elastic-managed-llm">Elastic Managed LLM</a>. Para isso, precisamos acessar <strong>Configurações do Projeto</strong> &gt; <strong>Gerenciamento</strong> &gt; <strong>Configurações do GenAI</strong>; agora selecionamos a que criamos e clicamos em <strong>Salvar</strong>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc42f079c5e756057/6a170d0acf4f2501d9b2d1c7/11e830c3e2fb4c298b020c928fa5422f3397ba08-1600x1152.png" alt="" /><h3>Construtor de Agentes de Acesso</h3><ol><li><p>Acesse a seção de <strong>Agentes.</strong></p></li><li><p>Clique em <strong>Criar um novo agente</strong></p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb8e734817c5a7c6a/6a170d0ca929cf867cae0a34/c1e60541563650163f972ac9088dc1ed1de759a7-1600x1054.png" alt="" /><h3>Configure o agente</h3><p>Para criar um novo agente, os campos obrigatórios são o <strong>ID do Agente</strong>, <strong>o Nome de Exibição</strong> e <strong>as Instruções de Exibição</strong>.</p><p>Mas existem mais opções de personalização, como as Instruções Personalizadas, que orientam o comportamento do seu agente e a forma como ele interagirá com as suas ferramentas, de forma semelhante a um prompt do sistema, mas para o nosso agente personalizado. As etiquetas ajudam a organizar seus agentes, a cor do avatar e o símbolo do avatar.</p><p>Os agentes que escolhi para o nosso agente, com base no conjunto de dados, são:

<strong>ID do agente:</strong> <code>hr_assistant</code></p><p><strong>Instruções personalizadas:</strong></p>You are an HR Analytics Assistant that helps answer questions about employee data.
When responding to queries:
- Provide clear, concise answers
- Include relevant employee details (name, department, salary, skills)
- Format monetary values with currency symbols
- Be professional and maintain data confidentiality<p>
Rótulos: <code>Human Resources</code> e <code>GPT-OSS</code></p><p>Nome de exibição: <code>HR Analytics Assistant</code></p><p>Descrição da tela:</p>A specialized AI assistant for Human Resources that helps analyze employee data, compensation, performance metrics, and talent management. Ask questions about employees, departments, salaries, or performance analytics.<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt23fb011e5b4f4d49/6a170d0e7d8d67f47a70e77f/f94bb2bf08497e5e756ca76b30a3a51f42927756-1424x1217.png" alt="" /><p>Com todos os dados inseridos, podemos clicar em <strong>Salvar</strong> nosso novo agente.</p><h3>Teste o agente</h3><p>Agora você pode fazer perguntas em linguagem natural sobre os dados de seus funcionários, e o GPT-OSS 20B entenderá a intenção e gerará uma resposta apropriada.</p><h4>Incitar:</h4>Which employee is the one with the highest salary in the hr-employees index?<h4>Responder:</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc0c52faacf63b583/6a170d0f0e2e497bfd41a17b/94ad19f80b96304028a59f60beca51dfc9aecc8a-899x631.png" alt="" /><p>O processo do Agente foi o seguinte:</p><p>1. Compreenda sua pergunta usando o conector GPT-OSS.</p><p>2. Gere a consulta Elasticsearch apropriada (usando as ferramentas integradas ou <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> personalizado).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte32a8a7e6363c7f2/6a170d115091680077e1bb44/6f2961d0d1b97475f6dda300acee84da540938e6-844x466.png" alt="" /><p>3. Recuperar registros de funcionários correspondentes</p><p>4. Apresentar os resultados em linguagem natural com formatação adequada.</p><p>Diferentemente da busca lexical tradicional, o agente baseado em GPT-OSS entende a intenção e o contexto, facilitando a localização de informações sem a necessidade de conhecer os nomes exatos dos campos ou a sintaxe da consulta. Para obter mais detalhes sobre o processo de pensamento do agente, consulte este <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance">artigo</a>.</p><h2>Conclusão</h2><p>Neste artigo, criamos um agente de IA personalizado usando o Agent Builder da Elastic para conectar-se ao modelo GPT-OSS da OpenAI em execução localmente. Ao implantar o Elastic e o LLM em sua máquina local, essa arquitetura permite que você aproveite os recursos de IA generativa, mantendo o controle total sobre seus dados, tudo isso sem enviar informações para serviços externos.</p><p>Utilizamos o GPT-OSS 20B como experimento, mas os modelos oficialmente recomendados para o Elastic Agent Builder podem ser consultados <a href="https://www.elastic.co/docs/solutions/search/agent-builder/models#recommended-models">aqui</a>. Se você precisar de recursos de raciocínio mais avançados, existe também a <a href="https://huggingface.co/openai/gpt-oss-120b">variante com 120 parâmetros</a> , que apresenta melhor desempenho em cenários complexos, embora exija uma máquina com especificações mais altas para ser executada localmente. Para obter mais detalhes, consulte a <a href="https://openai.com/open-models/">documentação oficial da OpenAI</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/build-an-ai-agent-hr-elastic-agent-builder-gpt-oss</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/build-an-ai-agent-hr-elastic-agent-builder-gpt-oss</guid>
    <category><![CDATA[IA agêntica]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt664f490053e46e6b/6a170d13b0367d2d7e72bd84/05d2d0513fff67d975f9223d75108aa9f50646bc-1600x914.png" length="0" type="image/png"/>
    <pubDate>Wed, 26 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Painéis de controle com inteligência artificial: da visão ao Kibana]]></title>
    <description><![CDATA[Gere um painel de controle usando um LLM para processar uma imagem e transformá-la em um painel do Kibana.
]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/kibana/kibana-lens">O Kibana Lens</a> torna o arrastar e soltar de dashboards muito simples, mas quando você precisa de dezenas de painéis, o número de cliques aumenta. E se você pudesse esboçar um painel de controle, tirar uma captura de tela e deixar um profissional de Direito concluir todo o processo para você?</p><p>Neste artigo, vamos fazer isso acontecer. Criaremos um aplicativo que captura uma imagem de um painel, analisa nossos mapeamentos e, em seguida, gera um painel sem que precisemos usar o Kibana!</p><p><strong>Passos</strong>:</p><ol><li><p><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#background-&amp;-application-workflow">Contexto e fluxo de trabalho do aplicativo</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#prepare-data">Preparar dados</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#llm-configuration">Configuração LLM</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#application-functions">Funções do aplicativo</a></p></li></ol><h2>Contexto e fluxo de trabalho do aplicativo</h2><p>A primeira ideia que me veio à mente foi deixar o LLM gerar todo o formato NDJSON <a href="https://www.elastic.co/docs/explore-analyze/find-and-organize/saved-objects">dos objetos salvos</a> pelo Kibana e, em seguida, importá-los para o Kibana.</p><p>Experimentamos alguns modelos:</p><ul><li><p>Gemini 2.5 pro</p></li><li><p>GPT o3 / o4-mini-high / 4.1</p></li><li><p>Soneto 4 de Claude</p></li><li><p>Grok 3</p></li><li><p>Deepseek (Deepthink R1)</p></li></ul><p>E para as sugestões, começamos com algo tão simples quanto:</p>You are an Elasticsearch Saved-Object generator (Kibana 9.0).
INPUTS
=====
1. PNG screenshot of a 4-panel dashboard (attached).
2. Index mapping (below) – trimmed down to only the fields present in the screenshot.
3. Example NDJSON of *one* metric visualization (below) for reference.

TASK
====
Return **only** a valid NDJSON array that recreates the dashboard exactly:
* 2 metric panels (Visits, Unique Visitors)
* 1 pie chart (Most used OS)
* 1 vertical bar chart (State Geo Dest)
* Use index pattern `kibana_sample_data_logs`.
* Preserve roughly the same layout (2×2 grid).
* Use `panelIndex` values 1-4 and random `id` strings.
* Kibana version: 9.0<p>Apesar de termos analisado <a href="https://www.elastic.co/search-labs/blog/function-calling-with-elastic#:~:text=Few%2Dshot%20prompting%20involves%20providing%20examples%20of%20the%20types%20of%20queries%20you%20want%20it%20to%20return%2C%20which%20helps%20in%20increasing%20consistency.">poucos exemplos</a> e explicações detalhadas sobre como construir cada visualização, não tivemos sucesso. Se você estiver interessado nessa experiência, pode encontrar detalhes <a href="https://gist.github.com/TomasMurua/a78dc283e115624731beffc98984b70b">aqui</a>.</p><p>O resultado com essa abordagem foi a visualização dessas mensagens ao tentar carregar os arquivos produzidos pelo LLM no Kibana:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9ea005966a783057/6a1707d266c4f90e4ef8bf88/2b599443b5613c9f0fc3235581614add5b4b3900-891x98.png" alt="" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5e5632d6d95b998c/6a1707d3a6c2b9441de79661/d87ccfc033bc00ee8188c5cae18043fbca22784c-741x233.png" alt="" /><p>Isso significa que o JSON gerado é inválido ou está mal formatado. Os problemas mais comuns foram o LLM produzir NDJSON incompleto, apresentar parâmetros incorretos ou retornar JSON comum em vez de NDJSON, independentemente de quanto nos esforçássemos para forçar o contrário.</p><p>Inspirados por <a href="https://www.elastic.co/search-labs/blog/llm-functions-elasticsearch-intelligent-query">este artigo</a> – onde <a href="https://www.elastic.co/docs/solutions/search/search-templates">os modelos de pesquisa</a> funcionaram melhor do que o método freestyle do LLM – decidimos fornecer modelos ao LLM em vez de solicitar a geração do arquivo NDJSON completo e, em seguida, usar os parâmetros fornecidos pelo LLM no código para criar as visualizações adequadas. Essa abordagem não decepcionou, além de ser previsível e extensível, já que agora o código realiza o trabalho pesado, e não o LLM.</p><p>O fluxo de trabalho da aplicação será o seguinte:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f7738a4c7ddd0cd/6a1707d52b835f0a25f4b166/52c587cf0cf3517fdd4ee7ab95581dd4f2bce030-725x668.png" alt="" /><p></p><p><em>Para simplificar, omitiremos parte do código, mas você pode encontrar o código funcional da aplicação completa neste </em><a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/from-image-idea-to-kibana-dashboard-using-ai/from-image-idea-to-kibana-dashboard-using-ai.ipynb"><em><strong>notebook</strong></em></a><em>.</em></p><h2>Pré-requisitos</h2><p>Antes de começar o desenvolvimento, você precisará do seguinte:</p><ol><li><p>Python 3.8 ou superior</p></li><li><p>Um ambiente Python <a href="https://docs.python.org/3/library/venv.html">Venv</a></p></li><li><p>Uma instância do Elasticsearch em execução, juntamente com seu endpoint e chave de API.</p></li><li><p>Uma chave de API da OpenAI armazenada na variável de ambiente com o nome OPENAI_API_KEY:</p></li></ol>export OPENAI_API_KEY="your-openai-api-key"<h2>Preparar dados</h2><p>Para os dados, vamos manter a simplicidade e usar os logs de amostra da Elastic. Você pode aprender como importar esses dados para o seu cluster <a href="https://www.elastic.co/docs/manage-data/ingest/sample-data#add-sample-data-sets">aqui</a>.</p><p>Cada documento inclui detalhes sobre o host que enviou as solicitações ao aplicativo, juntamente com informações sobre a própria solicitação e seu status de resposta. Segue abaixo um exemplo de documento:</p>{
    "agent": "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24",
    "bytes": 8509,
    "clientip": "70.133.115.149",
    "extension": "css",
    "geo": {
        "srcdest": "US:IT",
        "src": "US",
        "dest": "IT",
        "coordinates": {
            "lat": 38.05134111,
            "lon": -103.5106908
        }
    },
    "host": "cdn.elastic-elastic-elastic.org",
    "index": "kibana_sample_data_logs",
    "ip": "70.133.115.149",
    "machine": {
        "ram": 5368709120,
        "os": "osx"
    },
    "memory": null,
    "message": "70.133.115.149 - - [2018-08-30T23:35:31.492Z] \"GET /styles/semantic-ui.css HTTP/1.1\" 200 8509 \"-\" \"Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24\"",
    "phpmemory": null,
    "referer": "http://twitter.com/error/john-phillips",
    "request": "/styles/semantic-ui.css",
    "response": 200,
    "tags": [
        "success",
        "info"
    ],
    "@timestamp": "2025-07-03T23:35:31.492Z",
    "url": "https://cdn.elastic-elastic-elastic.org/styles/semantic-ui.css",
    "utc_time": "2025-07-03T23:35:31.492Z",
    "event": {
        "dataset": "sample_web_logs"
    },
    "bytes_gauge": 8509,
    "bytes_counter": 51201128
}<p>Agora, vamos obter os mapeamentos do índice que acabamos de carregar, <code>kibana_sample_data_logs</code>:</p>INDEX_NAME = "kibana_sample_data_logs"

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

result = es_client.indices.get_mapping(index=INDEX_NAME)
index_mappings = result[list(result.keys())[0]]["mappings"]["properties"]<p>Vamos passar os mapeamentos junto com a imagem que carregaremos posteriormente.</p><h2>Configuração LLM</h2><p>Vamos configurar o LLM para usar <a href="https://python.langchain.com/docs/concepts/structured_outputs/">saída estruturada</a> para receber uma imagem como entrada e obter um JSON com as informações necessárias para passar à nossa função e gerar os objetos JSON.</p><p>Instalamos as dependências:</p>pip install elasticsearch pydantic langchain langchain-openai -q<p>O Elasticsearch nos ajudará a recuperar os <a href="https://www.elastic.co/docs/manage-data/data-store/mapping">mapeamentos de índice</a>. Pydantic permite definir esquemas em Python para depois solicitar que o LLM os siga, e <a href="https://www.elastic.co/search-labs/integrations/langchain">LangChain</a> é a estrutura que facilita a chamada de LLMs e ferramentas de IA.</p><p>Criaremos um esquema Pydantic para definir a saída desejada do LLM. O que precisamos saber da imagem é o tipo de gráfico, campo, título da visualização e título do painel:</p>class Visualization(BaseModel):
    title: str = Field(description="The dashboard title")
    type: List[Literal["pie", "bar", "metric"]]
    field: str = Field(
        description="The field that this visualization use based on the provided mappings"
    )


class Dashboard(BaseModel):
    title: str = Field(description="The dashboard title")
    visualizations: List[Visualization]<p>Para a entrada de imagem, enviaremos um painel que acabei de desenhar:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7870f6421986d11d/6a1707d78b73cb3408189fa3/36441d7b5dc1f3ff2ac2a30710208d57ad41c716-1600x898.jpg" alt="" /><p>Agora declaramos a chamada do modelo LLM e o carregamento da imagem. Essa função receberá os mapeamentos do índice do Elasticsearch e uma imagem do painel que desejamos gerar.</p><p>Com <code>with_structured_output</code> podemos usar nosso esquema Pydantic <code>Dashboard</code> como o objeto de resposta que o LLM produzirá. Com <a href="https://docs.pydantic.dev/latest/">o Pydantic</a>, podemos definir modelos de dados com validação, o que garante que a saída do modelo linear linear (LLM) corresponda à estrutura esperada.</p><p>Para converter a imagem para base64 e enviá-la como entrada, você pode usar um <a href="https://www.base64-image.de/">conversor online</a> ou fazer isso <a href="https://www.geeksforgeeks.org/python-convert-image-to-string-and-vice-versa/">por meio de código</a>.</p>prompt = f"""
    You are an expert in analyzing Kibana dashboards from images for the version 9.0.0 of Kibana.

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

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

    Index Mappings:
    {index_mappings}

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

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


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

    print("Dashboard values generated by the LLM successfully")
    print(dashboard_values)
except Exception as e:
    print(f"Failed to analyze image and match fields: {str(e)}")<p>O LLM já possui contexto sobre os dashboards do Kibana, então não precisamos explicar tudo no prompt, apenas alguns detalhes para garantir que ele não se esqueça de que está trabalhando com o Elasticsearch e o Kibana.</p><p>Vamos analisar a pergunta:</p><p>Seção</p><p>Razão</p><p>Você é especialista em analisar dashboards do Kibana a partir de imagens para a versão 9.0.0 do Kibana.</p><p>Ao reforçar isso no Elasticsearch e na versão do Elasticsearch, reduzimos a probabilidade de o LLM gerar parâmetros antigos/inválidos.</p><p>Você receberá uma imagem do painel de controle e um mapeamento do índice do Elasticsearch.</p><p>Explicamos que a imagem se refere a painéis de controle para evitar quaisquer interpretações errôneas por parte do LLM.</p><p>Abaixo estão os mapeamentos de índice para o índice no qual o painel se baseia. Use-os para ajudá-lo a entender os dados e os campos disponíveis. Mapeamentos de índice: {index_mappings}</p><p>É crucial fornecer os mapeamentos para que o LLM possa selecionar campos válidos dinamicamente. Caso contrário, poderíamos codificar os mapeamentos diretamente aqui, o que é muito rígido, ou confiar na imagem que contém os nomes de campo corretos, o que não é confiável.</p><p>Inclua apenas os campos relevantes para cada visualização, com base no que está visível na imagem.</p><p>Precisávamos adicionar esse reforço porque, às vezes, o programa tenta adicionar campos que não são relevantes para a imagem.</p><p>Isso retornará um objeto com uma matriz de visualizações para exibir:</p>"Dashboard values generated by the LLM successfully
title=""Client, Extension, OS, and Response Keyword Analysis""visualizations="[
   "Visualization(title=""Count of Client IP",
   "type="[
      "metric"
   ],
   "field=""clientip"")",
   "Visualization(title=""Extension Keyword Distribution",
   "type="[
      "pie"
   ],
   "field=""extension.keyword"")",
   "Visualization(title=""Most Used OS",
   "type="[
      "bar"
   ],
   "field=""machine.os.keyword"")",
   "Visualization(title=""Response Keyword Distribution",
   "type="[
      "bar"
   ],
   "field=""response.keyword"")"
]<h2>Processando a resposta do LLM</h2><p>NósCriamos um painel de exemplo 2x2 e o exportamos em JSON usando a <a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-get-dashboards-dashboard">API "Obter um painel"</a>. Em seguida, armazenamos os painéis como modelos de visualização (pizza, barra, métrica), onde podemos substituir alguns parâmetros para criar novas visualizações com campos diferentes, dependendo da pergunta.</p><p>Você pode ver os arquivos JSON do modelo <a href="https://github.com/Delacrobix/elasticsearch-labs/tree/supporting-blog-content/from-image-idea-to-kibana-dashboard-using-ai/supporting-blog-content/from-image-idea-to-kibana-dashboard-using-ai/templates"><strong>aqui</strong></a>. Observe como alteramos os valores dos objetos que queremos substituir posteriormente por {<code>variable_name</code>}
</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc55d69d84a08e668/6a1707d8a2929903acd00fb8/ec7e1ac0cd8b470df13e60940162b56778acb386-315x234.png" alt="" /><p>Com as informações fornecidas pelo LLM, podemos decidir qual modelo usar e quais valores substituir.</p><p><code>fill_template_with_analysis</code> receberão os parâmetros para um único painel, incluindo o modelo JSON da visualização, um título, um campo e as coordenadas da visualização na grade.</p><p>Em seguida, substituirá os valores do modelo e retornará a visualização JSON final.</p>def fill_template_with_analysis(
    template: Dict[str, Any],
    visualization: Visualization,
    grid_data: Dict[str, Any],
):
    template_str = json.dumps(template)
    replacements = {
	 "{visualization_id}": str(uuid.uuid4()),
        "{title}": visualization.title,
        "{x}": grid_data["x"],
        "{y}": grid_data["y"],
    }

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

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

    return json.loads(template_str)<p>Para simplificar, teremos coordenadas estáticas que atribuiremos aos painéis que o LLM decidir criar e produziremos um painel de controle em grade 2x2, como na imagem acima.</p># Filling templates fields
panels = []    
grid_data = [
    {"x": 0, "y": 0},
    {"x": 12, "y": 0},
    {"x": 0, "y": 12},
    {"x": 12, "y": 12},
]


i = 0

for vis in dashboard_values.visualizations:
    for vis_type in vis.type:
        template = templates.get(vis_type, templates.get("bar", {}))
        filled_panel = fill_template_with_analysis(template, vis, grid_data[i])
        panels.append(filled_panel)
        i += 1<p>Dependendo do tipo de visualização decidido pelo LLM, escolheremos um modelo de arquivo JSON e substituiremos as informações relevantes usando <code>fill_template_with_analysis</code> , depois adicionaremos o novo painel a uma matriz que usaremos posteriormente para criar o painel de controle.</p><p>Quando o painel estiver pronto, usaremos a <a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-post-dashboards-dashboard-id">API Criar um painel</a> para enviar o novo arquivo JSON ao Kibana e gerar o painel:
</p>try:
    dashboard_id = str(uuid.uuid4())

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

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

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

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

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

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

except Exception as e:
    print(f"Failed to create dashboard: {str(e)}")<p>Para executar o script e gerar o painel de controle, execute o seguinte comando no console:</p>python &lt;file_name&gt;.py<p>O resultado final será semelhante a este:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ceffed004153a4f/6a1707d9a929cf9147ae0901/e909afbf0e47d9a6e0f7bd07dfb2efcfa5cf06ac-921x715.png" alt="" /><h2>Conclusão</h2><p>Os profissionais com formação em Letras demonstram suas fortes habilidades visuais ao realizar tarefas de conversão de texto em código ou ao transformar imagens em código. A API de dashboards também permite transformar arquivos JSON em dashboards e, com um LLM e algum código, podemos transformar imagens em um dashboard do Kibana.</p><p>O próximo passo é melhorar a flexibilidade dos elementos visuais do painel de controle, utilizando diferentes configurações de grade, tamanhos e posições do painel. Além disso, oferecer suporte a visualizações e tipos de visualização mais complexos seria uma adição útil a este aplicativo.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-powered-dashboards</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-powered-dashboards</guid>
    <category><![CDATA[Kibana]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo,Tomás Murúa]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt41727cbee6155a68/6a1707dbb0367dd2fd72bc86/eb60ceb2fbc3941745b21ae3357cbb6ea8fab18c-1443x811.png" length="0" type="image/png"/>
    <pubDate>Wed, 16 Jul 2025 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>