ブログ

AI搭載ダッシュボード:ビジョンからKibanaへ

LLM を使用してイメージを処理し、Kibana ダッシュボードに変換してダッシュボードを生成します。

Kibana Lens を使用するとダッシュボードのドラッグ アンド ドロップが非常に簡単になりますが、数十個のパネルが必要な場合はクリック回数が増えてしまいます。ダッシュボードをスケッチし、スクリーンショットを撮り、LLM にプロセス全体を任せることができたらどうでしょうか?

この記事では、それを実現します。ダッシュボードのイメージを取得し、マッピングを分析し、Kibana にまったく触れることなくダッシュボードを生成するアプリケーションを作成します。

手順:

  1. 背景とアプリケーションのワークフロー

  2. データを準備する

  3. LLM構成

  4. アプリケーション機能

背景とアプリケーションのワークフロー

最初に思いついたのは、LLM に NDJSON 形式の Kibana保存オブジェクト全体を生成させて、それを Kibana にインポートさせることでした。

私たちはいくつかのモデルを試しました:

  • ジェミニ 2.5 プロ

  • GPT o3 / o4-ミニハイ / 4.1

  • クロード 4つのソネット

  • グロク3

  • ディープシーク(ディープシンク R1)

プロンプトについては、次のように単純なものから始めました。

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

いくつかのショットの例と、各視覚化の構築方法に関する詳細な説明を確認したにもかかわらず、うまくいきませんでした。この実験に興味がある方は、こちらで詳細をご覧ください。

このアプローチの結果、LLM によって生成されたファイルを Kibana にアップロードしようとしたときに、次のメッセージが表示されました。

これは、生成された JSON が無効であるか、形式が間違っていることを意味します。最も一般的な問題は、LLM が不完全な NDJSON を生成したり、パラメータを幻覚させたり、あるいは、どれだけ強制しようとしても NDJSON ではなく通常の JSON を返したりすることでした。

この記事検索テンプレートがLLM フリースタイルよりもうまく機能した)に触発され、完全な NDJSON ファイルを生成するように要求するのではなく、テンプレートを LLM に提供し、コード内で LLM によって提供されたパラメータを使用して適切な視覚化を作成することにしました。このアプローチは期待を裏切らず、予測可能で拡張可能です。LLM ではなくコードが重い処理を実行するようになったためです。

アプリケーションのワークフローは次のようになります。

簡潔にするために一部のコードは省略しますが、完全なアプリケーションの動作コードはこのノートブックに記載されています。

要件

開発を始める前に、次のものが必要です。

  1. Python 3.8以上

  2. Venv Python環境

  3. 実行中のElasticsearchインスタンス、そのエンドポイント、APIキー

  4. 環境変数名 OPENAI_API_KEY に保存された OpenAI API キー:

export OPENAI_API_KEY="your-openai-api-key"

データを準備する

データについては、シンプルさを保ち、Elastic のサンプル Web ログを使用します。ここで、そのデータをクラスターにインポートする方法を学習できます。

各ドキュメントには、アプリケーションにリクエストを発行したホストの詳細と、リクエスト自体とその応答ステータスに関する情報が含まれています。以下にサンプル文書を示します。

{
    "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
}

ここで、先ほどロードしたインデックス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"]

後で読み込むイメージと一緒にマッピングを渡します。

LLM構成

構造化出力を使用して画像を入力し、JSON オブジェクトを生成するために関数に渡す必要がある情報を含む JSON を受け取るように LLM を構成しましょう。

依存関係をインストールします。

pip install elasticsearch pydantic langchain langchain-openai -q

Elasticsearch はインデックス マッピングの取得に役立ちます。Pydantic を使用すると、Python でスキーマを定義して LLM に従うように要求することができ、 LangChain はLLM と AI ツールの呼び出しを容易にするフレームワークです。

LLM から必要な出力を定義するために、Pydantic スキーマを作成します。画像からわかる必要があるのは、グラフの種類、フィールド、視覚化タイトル、ダッシュボード タイトルです。

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]

画像入力には、先ほど描いたダッシュボードを送信します。

ここで、LLM モデルの呼び出しとイメージの読み込みを宣言します。この関数は、Elasticsearch インデックスのマッピングと、生成するダッシュボードの画像を受け取ります。

with_structured_outputを使用すると、Pydantic Dashboardスキーマを LLM が生成する応答オブジェクトとして使用できます。Pydanticを使用すると、検証付きのデータ モデルを定義できるため、LLM 出力が期待される構造と一致することが保証されます。

画像を base64 に変換して入力として送信するには、オンライン コンバーターを使用するか、コードで実行します。

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)}")

LLM にはすでに Kibana ダッシュボードに関するコンテキストがあるため、プロンプトですべてを説明する必要はなく、Elasticsearch と Kibana で動作していることを忘れないようにするための詳細のみを説明します。

プロンプトを分解してみましょう:

セクション

理由

あなたは、Kibana バージョン 9.0.0 の画像から Kibana ダッシュボードを分析するエキスパートです。

これを Elasticsearch と Elasticsearch バージョンで強化することで、LLM が古い/無効なパラメータを幻覚する可能性を減らします。

ダッシュボード イメージと Elasticsearch インデックス マッピングが提供されます。

LLM による誤った解釈を避けるために、この画像はダッシュボードに関するものであることを説明します。

以下は、ダッシュボードのベースとなるインデックスのインデックス マッピングです。これを使用すると、使用可能なデータとフィールドを理解するのに役立ちます。インデックス マッピング: {index_mappings}

LLM が有効なフィールドを動的に選択できるようにマッピングを提供することが重要です。そうしないと、ここでのマッピングをハードコードすることになり、厳しすぎることになります。あるいは、正しいフィールド名を含むイメージに依存することになりますが、これは信頼できません。

画像に表示されている内容に基づいて、各視覚化に関連するフィールドのみを含めます。

画像に関係のないフィールドを追加しようとすることがあるため、この強化を追加する必要がありました。

これにより、表示する視覚化の配列を含むオブジェクトが返されます。

"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"")"
]

LLM応答の処理

私たちは サンプルの 2x2 パネル ダッシュボードを作成し、 Get a dashboard API を使用して JSON 形式でエクスポートしました。その後、パネルを視覚化テンプレート (円グラフ、棒グラフ、メトリック) として保存し、いくつかのパラメータを置き換えて、質問に応じて異なるフィールドを持つ新しい視覚化を作成できます。

テンプレート JSON ファイルはここで確認できます。後で置き換えたいオブジェクトの値を {variable_name} に変更したことに注意してください。

LLM が提供した情報を使用して、どのテンプレートを使用し、どの値を置き換えるかを決定できます。

fill_template_with_analysis 視覚化の JSON テンプレート、タイトル、フィールド、グリッド上の視覚化の座標など、単一のパネルのパラメータを受け取ります。

次に、テンプレートの値を置き換えて、最終的な JSON 視覚化を返します。

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)

簡単にするために、LLM が作成することを決定したパネルに割り当てる静的座標があり、上の画像のように 2x2 グリッド ダッシュボードが生成されます。

# 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

LLM によって決定された視覚化タイプに応じて、JSON ファイル テンプレートを選択し、 fill_template_with_analysisを使用して関連情報を置き換え、後でダッシュボードを作成するために使用する配列に新しいパネルを追加します。

ダッシュボードの準備ができたら、ダッシュボードの作成 APIを使用して新しい JSON ファイルを Kibana にプッシュし、ダッシュボードを生成します。

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)}")

スクリプトを実行してダッシュボードを生成するには、コンソールで次のコマンドを実行します。

python <file_name>.py

最終結果は次のようになります。

ダッシュボード URL: https://your-kibana-url/app/dashboards#/view/generated-dashboard-id ダッシュボードID: generated-dashboard-id

まとめ

LLM は、テキストをコード化したり、画像をコード化したりするときに、強力な視覚機能を発揮します。ダッシュボード API を使用すると、JSON ファイルをダッシュボードに変換することも可能で、LLM といくつかのコードを使用して、画像を Kibana ダッシュボードに変換することもできます。

次のステップは、さまざまなグリッド設定、ダッシュボードのサイズ、位置を使用して、ダッシュボードのビジュアルの柔軟性を向上させることです。また、より複雑な視覚化と視覚化タイプのサポートを提供することも、このアプリケーションにとって便利な追加機能となるでしょう。

関連記事

Kibanaはダッシュボードの読み込み時間を最大25%短縮 - その背後にあるポーリング戦略を紹介

Drew Tate

Kibana を使用して地理空間データを Elasticsearch に取り込み、ES|QL で使用できるようにする

Craig Taverner

変数コントロールによるKibanaダッシュボードのインタラクション性の向上

Teresa Alvarez Soler

描くのではなく、説明する:MCPとES|QLによるAIネイティブのKibanaダッシュボード

Stratoula Kalafateli

Spotify Wrapped パート2: データ分析と視覚化

Philipp Kahr

最先端の検索体験を構築する準備はできましたか?

十分に高度な検索は 1 人の努力だけでは実現できません。Elasticsearch は、データ サイエンティスト、ML オペレーター、エンジニアなど、あなたと同じように検索に情熱を傾ける多くの人々によって支えられています。ぜひつながり、協力して、希望する結果が得られる魔法の検索エクスペリエンスを構築しましょう。

はじめましょう