Painéis de controle com inteligência artificial: da visão ao Kibana
Gere um painel de controle usando um LLM para processar uma imagem e transformá-la em um painel do Kibana.
O Kibana Lens 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ê?
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!
Passos:
Contexto e fluxo de trabalho do aplicativo
A primeira ideia que me veio à mente foi deixar o LLM gerar todo o formato NDJSON dos objetos salvos pelo Kibana e, em seguida, importá-los para o Kibana.
Experimentamos alguns modelos:
Gemini 2.5 pro
GPT o3 / o4-mini-high / 4.1
Soneto 4 de Claude
Grok 3
Deepseek (Deepthink R1)
E para as sugestões, começamos com algo tão simples quanto:
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.0Apesar de termos analisado poucos exemplos e explicações detalhadas sobre como construir cada visualização, não tivemos sucesso. Se você estiver interessado nessa experiência, pode encontrar detalhes aqui.
O resultado com essa abordagem foi a visualização dessas mensagens ao tentar carregar os arquivos produzidos pelo LLM no Kibana:


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.
Inspirados por este artigo – onde os modelos de pesquisa 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.
O fluxo de trabalho da aplicação será o seguinte:

Para simplificar, omitiremos parte do código, mas você pode encontrar o código funcional da aplicação completa neste notebook.
Pré-requisitos
Antes de começar o desenvolvimento, você precisará do seguinte:
Python 3.8 ou superior
Um ambiente Python Venv
Uma instância do Elasticsearch em execução, juntamente com seu endpoint e chave de API.
Uma chave de API da OpenAI armazenada na variável de ambiente com o nome OPENAI_API_KEY:
export OPENAI_API_KEY="your-openai-api-key"Preparar dados
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 aqui.
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:
{
"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
}Agora, vamos obter os mapeamentos do índice que acabamos de carregar, kibana_sample_data_logs:
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"]Vamos passar os mapeamentos junto com a imagem que carregaremos posteriormente.
Configuração LLM
Vamos configurar o LLM para usar saída estruturada 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.
Instalamos as dependências:
pip install elasticsearch pydantic langchain langchain-openai -qO Elasticsearch nos ajudará a recuperar os mapeamentos de índice. Pydantic permite definir esquemas em Python para depois solicitar que o LLM os siga, e LangChain é a estrutura que facilita a chamada de LLMs e ferramentas de IA.
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:
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]Para a entrada de imagem, enviaremos um painel que acabei de desenhar:

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.
Com with_structured_output podemos usar nosso esquema Pydantic Dashboard como o objeto de resposta que o LLM produzirá. Com o Pydantic, podemos definir modelos de dados com validação, o que garante que a saída do modelo linear linear (LLM) corresponda à estrutura esperada.
Para converter a imagem para base64 e enviá-la como entrada, você pode usar um conversor online ou fazer isso por meio de código.
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)}")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.
Vamos analisar a pergunta:
Seção | Razão |
|---|---|
Você é especialista em analisar dashboards do Kibana a partir de imagens para a versão 9.0.0 do Kibana. | Ao reforçar isso no Elasticsearch e na versão do Elasticsearch, reduzimos a probabilidade de o LLM gerar parâmetros antigos/inválidos. |
Você receberá uma imagem do painel de controle e um mapeamento do índice do Elasticsearch. | Explicamos que a imagem se refere a painéis de controle para evitar quaisquer interpretações errôneas por parte do LLM. |
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} | É 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. |
Inclua apenas os campos relevantes para cada visualização, com base no que está visível na imagem. | Precisávamos adicionar esse reforço porque, às vezes, o programa tenta adicionar campos que não são relevantes para a imagem. |
Isso retornará um objeto com uma matriz de visualizações para exibir:
"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"")"
]Processando a resposta do LLM
Nós Criamos um painel de exemplo 2x2 e o exportamos em JSON usando a API "Obter um painel". 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.
Você pode ver os arquivos JSON do modelo aqui. Observe como alteramos os valores dos objetos que queremos substituir posteriormente por {variable_name}

Com as informações fornecidas pelo LLM, podemos decidir qual modelo usar e quais valores substituir.
fill_template_with_analysis 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.
Em seguida, substituirá os valores do modelo e retornará a visualização JSON final.
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)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.
# 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 += 1Dependendo do tipo de visualização decidido pelo LLM, escolheremos um modelo de arquivo JSON e substituiremos as informações relevantes usando fill_template_with_analysis , depois adicionaremos o novo painel a uma matriz que usaremos posteriormente para criar o painel de controle.
Quando o painel estiver pronto, usaremos a API Criar um painel para enviar o novo arquivo JSON ao Kibana e gerar o painel:
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)}")Para executar o script e gerar o painel de controle, execute o seguinte comando no console:
python <file_name>.pyO resultado final será semelhante a este:

URL do painel: https://your-kibana-url/app/dashboards#/view/generated-dashboard-id ID do painel: generated-dashboard-id
Conclusão
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.
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.
Conteúdo relacionado




