Tableaux de bord alimentés par l'IA : D'une vision à Kibana
Générer un tableau de bord en utilisant un LLM pour traiter une image et la transformer en tableau de bord Kibana.
Kibana Lens simplifie le glisser-déposer des tableaux de bord, mais lorsque vous avez besoin de dizaines de panneaux, les clics s'accumulent. Et si vous pouviez dessiner un tableau de bord, en faire une capture d'écran et laisser un LLM terminer tout le processus à votre place ?
Dans cet article, nous allons y parvenir. Nous allons créer une application qui prend une image d'un tableau de bord, analyse nos mappings et génère un tableau de bord sans que nous ayons à toucher à Kibana !
Les étapes:
Contexte & flux de travail de l'application
La première idée qui m'est venue à l'esprit a été de laisser le LLM générer l'ensemble des objets sauvegardés au format NDJSON dans Kibana, puis de les importer dans Kibana.
Nous avons essayé une poignée de modèles :
Gemini 2.5 pro
GPT o3 / o4-mini-high / 4.1
Sonnet de Claude 4
Grok 3
Deepseek (Deepthink R1)
En ce qui concerne les messages-guides, nous avons commencé par une phrase simple :
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.0Bien que nous ayons parcouru des exemples en quelques images et des explications détaillées sur la manière de construire chaque visualisation, nous n'avons pas eu de chance. Si vous êtes intéressé par cette expérimentation, vous pouvez trouver des détails ici.
Le résultat de cette approche était l'apparition de ces messages lorsque l'on essayait de télécharger vers Kibana les fichiers produits par le LLM :


Cela signifie que le JSON généré est invalide ou mal formaté. Les problèmes les plus fréquents étaient que le LLM produisait des NDJSON incomplets, des paramètres hallucinants ou retournait du JSON normal au lieu de NDJSON, même si nous essayions de faire en sorte qu'il en soit autrement.
Inspirés par cet article - où les modèles de recherche ont mieux fonctionné que le LLM freestyle - nous avons décidé de donner des modèles au LLM au lieu de demander de générer le fichier NDJSON complet et ensuite nous, dans le code, utilisons les paramètres donnés par le LLM pour créer les visualisations appropriées.
Le processus de candidature sera le suivant :

Nous omettons une partie du code pour des raisons de simplicité, mais vous pouvez trouver le code de travail de l'application complète sur ce carnet.
Produits requis
Avant de commencer à développer, vous aurez besoin des éléments suivants :
Python 3.8 ou supérieur
Un environnement Venv Python
Une instance Elasticsearch en cours d'exécution, ainsi que son point d'accès et sa clé API
Une clé d'API OpenAI stockée dans la variable d'environnement OPENAI_API_KEY :
export OPENAI_API_KEY="your-openai-api-key"Préparer les données
Pour les données, nous resterons simples et utiliserons les journaux web de l'échantillon Elastic. Pour savoir comment importer ces données dans votre cluster , cliquez ici.
Chaque document contient des informations sur l'hôte qui a envoyé des demandes à l'application, ainsi que des informations sur la demande elle-même et l'état de sa réponse. Vous trouverez ci-dessous un exemple de document :
{
"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
}Prenons maintenant les mappings de l'index que nous venons de charger, 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"]Nous allons transmettre les mappings avec l'image que nous chargerons plus tard.
Configuration LLM
Configurons le LLM pour qu'il utilise la sortie structurée afin d'entrer une image et de recevoir un JSON contenant les informations que nous devons transmettre à notre fonction pour produire les objets JSON.
Nous installons les dépendances :
pip install elasticsearch pydantic langchain langchain-openai -qElasticsearch nous aidera à récupérer les mappages d'index. Pydantic nous permet de définir des schémas en Python pour demander au LLM de les suivre, et LangChain est le cadre qui facilite l'appel aux LLM et aux outils d'IA.
Nous allons créer un schéma pydantique pour définir les résultats que nous voulons obtenir du LLM. Ce que nous devons savoir à partir de l'image, c'est le type de graphique, le champ, le titre de la visualisation et le titre du tableau de bord :
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]Pour la saisie de l'image, nous enverrons un tableau de bord que je viens de dessiner :

Nous déclarons maintenant l'appel au modèle LLM et le chargement de l'image. Cette fonction recevra les mappings de l'index Elasticsearch et une image du tableau de bord que nous voulons générer.
Avec with_structured_output, nous pouvons utiliser notre schéma Pydantic Dashboard comme objet de réponse que le LLM produira. Avec Pydantic, nous pouvons définir des modèles de données avec validation, ce qui garantit que la sortie LLM correspond à la structure attendue.
Pour convertir l'image en base64 et l'envoyer en entrée, vous pouvez utiliser un convertisseur en ligne ou le faire en code.
prompt = f"""
You are an expert in analyzing Kibana dashboards from images for the version 9.0.0 of Kibana.
You will be given a dashboard image and an Elasticsearch index mapping.
Below are the index mappings for the index that the dashboard is based on.
Use this to help you understand the data and the fields that are available.
Index Mappings:
{index_mappings}
Only include the fields that are relevant for each visualization, based on what is visible in the image.
"""
message = [
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{
"type": "image",
"source_type": "base64",
"data": image_base64,
"mime_type": "image/png",
},
],
}
]
try:
llm = init_chat_model("gpt-4.1-mini")
llm = llm.with_structured_output(Dashboard)
dashboard_values = llm.invoke(message)
print("Dashboard values generated by the LLM successfully")
print(dashboard_values)
except Exception as e:
print(f"Failed to analyze image and match fields: {str(e)}")Le LLM connaît déjà le contexte des tableaux de bord Kibana, nous n'avons donc pas besoin de tout expliquer dans l'invite, juste quelques détails pour s'assurer qu'il n'oublie pas qu'il travaille avec Elasticsearch et Kibana.
Décortiquons l'invitation :
Section | Raison |
|---|---|
Vous êtes un expert en analyse de tableaux de bord Kibana à partir d'images pour la version 9.0.0 de Kibana. | En insistant sur le fait qu'il s'agit d'Elasticsearch et de la version d'Elasticsearch, nous réduisons la probabilité que le LLM hallucine des paramètres anciens/invalides. |
Vous recevrez une image de tableau de bord et un mappage d'index Elasticsearch. | Nous expliquons que l'image concerne les tableaux de bord afin d'éviter toute interprétation erronée de la part du LLM. |
Vous trouverez ci-dessous les correspondances d'index pour l'index sur lequel le tableau de bord est basé, ce qui vous aidera à comprendre les données et les champs disponibles. Mappages d'index : {index_mappings} | Il est essentiel de fournir les correspondances afin que le LLM puisse sélectionner les champs valides de manière dynamique. Sinon, nous pourrions coder en dur les correspondances ici, ce qui est trop rigide, ou compter sur le fait que l'image contienne les bons noms de champs, ce qui n'est pas fiable. |
N'incluez que les champs pertinents pour chaque visualisation, en fonction de ce qui est visible dans l'image. | Nous avons dû ajouter ce renforcement parce qu'il arrive que l'on essaie d'ajouter des champs qui ne sont pas pertinents pour l'image. |
Cela renvoie un objet contenant un tableau de visualisations à afficher :
"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"")"
]Traitement de la réponse au mécanisme d'apprentissage tout au long de la vie
Nous avons créé un exemple de tableau de bord 2x2 panneaux à l'adresse et l'avons exporté en JSON à l'aide de l'API Get a dashboard, puis nous avons stocké les panneaux en tant que modèles de visualisation (camembert, barre, métrique) dans lesquels nous pouvons remplacer certains paramètres pour créer de nouvelles visualisations avec différents champs en fonction de la question.
Vous pouvez consulter les fichiers JSON du modèle ici. Notez que nous avons modifié les valeurs de l'objet que nous voulons remplacer plus tard par {variable_name}

Grâce aux informations fournies par le mécanisme d'apprentissage tout au long de la vie, nous pouvons décider du modèle à utiliser et des valeurs à remplacer.
fill_template_with_analysis recevra les paramètres pour un seul panneau, y compris le modèle JSON de la visualisation, un titre, un champ et les coordonnées de la visualisation sur la grille.
Ensuite, il remplacera les valeurs du modèle et renverra la visualisation JSON finale.
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)Pour faire simple, nous aurons des coordonnées statiques que nous assignerons aux panneaux que le LLM décidera de créer et nous produirons un tableau de bord à grille 2x2 comme l'image ci-dessus.
# 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 += 1En fonction du type de visualisation décidé par le LLM, nous choisirons un modèle de fichier JSON et remplacerons les informations pertinentes à l'aide de fill_template_with_analysis , puis nous ajouterons le nouveau panneau à un tableau que nous utiliserons ultérieurement pour créer le tableau de bord.
Lorsque le tableau de bord est prêt, nous utilisons l'API Create a dashboard pour envoyer le nouveau fichier JSON à Kibana afin de générer le tableau de bord :
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)}")Pour exécuter le script et générer le tableau de bord, exécutez la commande suivante dans la console :
python <file_name>.pyLe résultat final sera le suivant :

URL du tableau de bord : https://your-kibana-url/app/dashboards#/view/generated-dashboard-id ID du tableau de bord : generated-dashboard-id
Conclusion
Les LLM démontrent leurs fortes capacités visuelles lorsqu'ils transforment du texte en code ou des images en code. L'API des tableaux de bord permet également de transformer des fichiers JSON en tableaux de bord, et avec un LLM et un peu de code, nous pouvons transformer des images en tableau de bord Kibana.
L'étape suivante consiste à améliorer la flexibilité des visuels des tableaux de bord en utilisant différents paramètres de grille, différentes tailles de tableau de bord et différentes positions. De plus, la prise en charge de visualisations et de types de visualisation plus complexes serait un ajout utile à cette application.
Pour aller plus loin




