<?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[Elasticsearch Labs]]></title>
        <description><![CDATA[Elasticの検索チームからの記事とチュートリアル]]></description>
        <link>https://www.elastic.co/search-labs/jp</link>
        <image>
            <url>https://www.elastic.co/search-labs/favicon.svg</url>
            <title>Elasticsearch Labs</title>
            <link>https://www.elastic.co/search-labs/jp</link>
        </image>
        <generator>Elasticsearch Labs</generator>
        <lastBuildDate>Tue, 21 Apr 2026 19:34:25 GMT</lastBuildDate>
        <atom:link href="https://www.elastic.co/search-labs/jp/rss/feed" rel="self" type="application/rss+xml"/>
        <copyright><![CDATA[© 2026. Elasticsearch B.V. すべての権利を保有]]></copyright>
        <language><![CDATA[jp]]></language>
        <item>
            <title><![CDATA[Elasticsearch + Jina埋め込みによる教師なし文書クラスタリング]]></title>
            <description><![CDATA[ElasticsearchとJina埋め込みを使用した教師なし文書クラスタリングへの実用的で再現可能なアプローチ。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/unsupervised-document-clustering-elasticsearch-jina-embeddings</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/unsupervised-document-clustering-elasticsearch-jina-embeddings</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Matthew Adams]]></dc:creator>
            <pubDate>Fri, 10 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/662c00694c3e0c2fb2128098bdb6813df9e86a72-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>ベクターサーチはクエリから始まりますが、もしクエリがなければどうすればよいのでしょうか？</p><p>組織は大量のドキュメントコレクション（サポートチケット、法的書類、ニュースフィード、研究論文など）を蓄積しており、適切な質問をする前にその内容を理解する必要があります。ラベルやトレーニングデータがなければ、何千もの文書を手動で確認するのは非現実的です。何を検索すればいいかわからない場合、従来の検索は役に立ちません。</p><p>この投稿では、この検索の問題に対処するElasticsearchネイティブのアプローチによる教師なし文書クラスタリングと時間軸に沿ったストーリー追跡について説明します。最終的には、次のようにストーリーの文脈を日々にわたってたどることができるようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/100a60a7fb85da8ab3813fd071a82c93f2c3f318-1300x650.png" style="width: 100%;" /><p><strong>読み取れる内容：</strong></p><ul><li>なぜ<strong>クラスタリング埋め込み</strong>（検索埋め込みではない）が、クエリーなしでトピック発見を行いたい場合に重要なのか。</li><li>Elasticsearchのk-nearest-neighbor（kNN）とバッチ処理<code>msearch</code> を使用したトピックにより、密度探査型重心分類で文書をグループ化する方法。</li><li><a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significanttext-aggregation"><code>significant_text</code></a> がクラスターに自動ラベルを付けることで、モデルをトレーニングしなくてもテーマを読めるようにする方法。</li><li>テーマが日々どのように変化するかを示す上で、時間軸に沿ったストーリーチェーンが日々のクラスターをどのように結び付けるのか。</li></ul><div style="display:none">Unknown block type "infoPanel", specify a component for it in the `components.types` option</div><p>このパイプラインでは、2025年2月のBBCニュースとガーディアンの記事を約8,500件、テストコーパスとして使用しています。ニュースは明確な時間的推移を示すため便利ですが、このパターンは文書の発見が重要なあらゆる場面に適用されます：法的レビュー、コンプライアンスの監視、研究の統合、カスタマーサポートのトリアージ。</p><p><strong>スタック：</strong></p><ul><li><a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text"><strong>Jina v5</strong></a>の<strong>クラスタリング埋め込み：</strong>トピックグループ化のためのタスク固有の低ランク適応（LoRA）アダプター。<a href="https://www.elastic.co/blog/elastic-jina-ai">JinaはElasticに統合され</a>、そのモデルは<a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service（EIS）</a>を通じてネイティブで利用可能です。</li><li><strong>Elasticsearch：</strong>拡張性のある<a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN</a>、<code>significant_text</code>ラベリング、ベクトルストレージ。</li><li><a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction"><strong>DiskBBQ：</strong></a>ディスクベースのベクトルインデックス形式で、 <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/bbq">Better Binary Quantization（BBQ）</a>と階層的なk-平均法による分割を組み合わせ、近似最近近接傍（ANN）加速を実現します。このインデックス分割はベクトル探索の内部で行われ、本投稿で用いる密度探査型クラスタリング・アルゴリズムとは別です。<code>bbq_disk</code>はディスクに量子化されたベクトルを格納し、ヒープにはパーティションメタデータのみを保持することで、<code>bbq_hnsw</code>と比較してリソース要件を大幅に削減しながら、高い再現率を維持します。</li><li><strong>グローバルクラスタリング + 日々の時間軸に沿ったリンク付け：</strong>発見とストーリーの進化。</li></ul><p><strong>以下が必要です：</strong></p><ul><li>Elasticsearchの導入（Elastic Cloud、Elasticsearchサーバーレス、またはElastic Self-Managed 8.18+/9.0+）：<code>bbq_disk</code> 8.18以降が必要です。オプションのdiversify retrieverセクションは9.3+またはサーバーレスが必要です。</li><li><a href="https://jina.ai/embeddings/">Jina APIキー</a>：無料プランには1000万トークンが含まれており、これはコアクラスタリング・パイプライン（約425万トークン）をカバーします。オプションの検索対クラスタリング比較は、2番目の埋め込みパスを使用します。</li><li><a href="https://bonobo.capi.gutools.co.uk/register/developer">Guardian APIキー</a>（無料）。</li></ul><h2>セットアップ</h2><p>必要なパッケージをインストールしてください：</p><pre><code>pip install elasticsearch pandas numpy plotly umap-learn python-dotenv pydantic-settings datasets requests</code></pre><p>オプション（このリポジトリからスクレイピングヘルパーを実行する場合のみ）：</p><pre><code>pip install beautifulsoup4</code></pre><p>次に、プロジェクトのルートにある<code>.env</code>ファイルでAPIキーを設定します。</p><pre><code>ELASTIC_CLOUD_ID=your-cloud-id        # or ELASTIC_HOST=https://...
ELASTIC_API_KEY=your-api-key
JINA_API_KEY=your-jina-key
GUARDIAN_API_KEY=your-guardian-key</code></pre><p>このノートブックは <code>load_dotenv(override=True)</code>を呼び出し、局所的な <code>.env</code> 値が優先されます。</p><pre><code>Connected to Elasticsearch</code></pre><h2>パート1：ディスカバリークラスタリング - なぜ埋め込みをクラスタリングするのか？</h2><p>ほとんどのベクター検索は、<em>クエリ</em>を関連<em>文書</em>に一致させるようにトレーニングされた<strong>検索埋め込み</strong>を使用します 。これは検索には最適ですが、発見には適していません。まったくクエリーせずにコーパスに存在するトピックを見つけたい場合は、類似した文書をグループ化する埋め込みが必要です。</p><p>Jina v5では、<strong>タスク固有の低ランク適応（LoRA）アダプタ</strong>を使用してこの問題を解決します。LoRAは、ほとんどのベースモデルの重みを凍結したまま、ターゲットとなる内部層に小さな低ランクの更新を加えるため、完全な再トレーニングを行うことなく、モデルの挙動が特定のタスクにシフトします。同じベースモデルでも、<code>task</code>パラメータによって異なる埋め込みが生成されます。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">タスク</td><td style="border: 1px solid #ccc; padding: 4px;">訓練の目的</td><td style="border: 1px solid #ccc; padding: 4px;">ユースケース</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">retrieval.passage</td><td style="border: 1px solid #ccc; padding: 4px;">クエリーと文書のマッチング</td><td style="border: 1px solid #ccc; padding: 4px;">Search、Retrieval-Augmented Generation（RAG）</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">クラスタリング</td><td style="border: 1px solid #ccc; padding: 4px;">トピックのグループ化（密度が高いクラスタリングの最適化）</td><td style="border: 1px solid #ccc; padding: 4px;">発見、分類</td>
                    </tr>
              </tbody>
            </table>
          <p>クラスタリング・アダプターは、同じトピックに関する文書を埋め込み空間で<em>近づけ</em>、異なるトピックに関する文書を<em>離す</em>ように訓練されています。下のビジュアル比較で、その違いが具体的にわかります。</p><h3>検索とクラスタリング：視覚的な比較</h3><p>違いを確認するために、両方のタスクタイプを含むサンプル文書を埋め込んでいます。クラスタリングは元の1024次元の埋め込み空間で実行されます。均一多様体近似と射影（UMAP）は、可視化のためそれらの埋め込みを2Dに投影する目的でのみ使用されます。東映UMAPは局所的な近傍構造を保持するため、クラスターの分離を比較する上で有用です。</p><p>以下では、同じ480件の文書のサンプルが両方のタスクタイプに埋め込まれ、UMAPで2Dに投影されています。クラスタリングパネルで、より密集していて、分離された色群を探します。</p><pre><code>    Full dataset: 8,495 articles
    Sources: guardian: 5749, bbc: 2746
    Date range: 2025-02-01 to 2025-02-28


    Sample: 480 docs across 8 sections
    section
    Film              60
    World news        60
    Australia news    60
    Opinion           60
    Football          60
    US news           60
    Sport             60
    Business          60


    Clustering embeddings: 480
    Retrieval embeddings:  480


    UMAP projection complete</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9bcf7a744900560c1c6c63a2dc3af2f9bfd33e11-1100x500.png" style="width: 100%;" /><p><em>検索埋め込み（左）はトピックを広く分散させます。クラスタリング埋め込み（右）は、同じドキュメントからより緊密で分離されたグループを生成します。</em></p><p>クラスタリング埋め込みは、より緊密で視覚的に際立ったグループを生み出します。検索用の埋め込みはトピックをより均等に分散させ、（きめの細かい類似性で）検索する上で理想的です。しかし、発見のためには、緊密なトピッククラスターが重要です。</p><p>このため、このチュートリアルの残りの部分では <code>task=&quot;clustering&quot;</code> が使用されています。</p><h3>データセットの読み込み</h3><p>次のコーパスは、2025年2月の2つのニュースソースを組み合わせています。</p><ul><li><a href="https://huggingface.co/datasets/RealTimeData/bbc_news_alltime">RealTimeData/bbc_news_alltime</a> HuggingFaceデータセット経由の<strong>BBCニュース</strong>。</li><li><a href="https://open-platform.theguardian.com/">Guardian Open Platform API</a>経由の<strong>The Guardian</strong>。</li></ul><p>複数のソースがあると、クラスタリングが<em>トピック</em>ではなく<em>ソース固有のスタイル</em>を見つけることを検証するのに役立ちます。</p><pre><code>    Total articles:  8,495
    
    Source breakdown:
    source
    guardian    5749
    bbc         2746
    
    Date range: 2025-02-01 → 2025-02-28
    Days covered: 28
    
    Sample article:
      Source:  guardian
      Title:   Carbon monoxide poisoning ruled out in death of Gene Hackman and wife, police sa
      Section: Film
      Text:    Authorities have ruled out that Gene Hackman and his wife, Betsy Arakawa, died from carbon monoxide poisoning earlier this week in their home in Santa Fe, New Mexico. The Santa Fe county sheriff, Adan...</code></pre><h3>クラスタリングタスクによる埋め込み</h3><p>Jina v5 APIはすべての文書に対して<code>task=&quot;clustering&quot;</code>で呼び出されます。埋め込みはディスクにキャッシュされるため、その後の実行ではAPIを完全にスキップします。</p><p>API呼び出しはシンプルです。<code>task</code>パラメータが、典型的な埋め込み使用との主な違いです。</p><pre><code>payload = {
    "model": "jina-embeddings-v5-text-small",
    "input": texts,
    "task": "clustering",  # ← This selects the clustering LoRA adapter
}</code></pre><p>以下のタイミングはキャッシュヒットを反映しています。APIに対する最初の実行は、コーパスサイズによって時間がかかります。</p><pre><code>    Embeddings ready: 8,495 vectors of dimension 1024
    Time: 0.6s</code></pre><h3>単一のElasticsearchインデックスへのインデキシング</h3><p>発見クラスタリングでは、1か月間を1つのインデックス（<code>docs-clustering-all</code>）にまとめます。日々の分割は、時間軸に沿ったストーリーリンクのために後から行われます。</p><p>インデックスマッピングでは、ベクトルフィールドに<a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/bbq"><code>bbq_disk</code></a>を使用します。</p><pre><code>{
  "embedding": {
    "type": "dense_vector",
    "dims": 1024,
    "index": true,
    "similarity": "cosine",
    "index_options": {
      "type": "bbq_disk"        // hierarchical k-means partitioning for ANN index lookup; separate from this post's clustering algorithm
    }
  }
}</code></pre><p>1024次元のfloat32ベクトルは4KBです。<a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction"><code>bbq_disk</code></a> は階層的k-means法を使用してベクトルを小さなクラスターに分割し、それらをバイナリ量子化し、再スコアリングのためにフルプレシジョンのベクトルをディスクに格納します。パーティションのメタデータのみがヒープに存在し、そのため大規模なコーパスでもメモリ要件は低く抑えられます。より多くのヒープを許容できるワークロードの場合、<a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/bbq"><code>bbq_hnsw</code></a> は、より高いリソースコストでより高速な検索を行うための階層的ナビゲーシブル・スモールワールド（HNSW）グラフを構築します。</p><p><a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector"><code>dense_vector</code></a>フィールドタイプは複数の量子化戦略をサポートしています：<code>bbq_disk</code>と<code>bbq_hnsw</code>は、ここで使用されている1024次元ベクトルのような高次元埋め込みに最適です。</p><pre><code>    Indexed 8,495 documents into docs-clustering-all
    Time: 57.5s</code></pre><h3>クラスタリング：密度探査型重心分類</h3><p>HDBSCANのような従来型のクラスタリングアルゴリズムでは、完全なn×dベクトル行列をメモリに保持し、フルパス更新を繰り返し実行できることを前提としています。8,495件のドキュメントを1024次元で扱う場合、処理可能（最大35MB）ではあるものの、このアプローチは追加のインフラストラクチャーなしでは数百万の文書にスケールすることはできません。</p><p>このアルゴリズムは、ボロノイ領域への割り当て割り当てとノイズフロアによるKMeans++法の初期化と概念的には似ていますが、Elasticsearchの<a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN検索</a>を計算プリミティブとして使用し、ほぼ全ての作業をサーバー側で行います。</p><ol><li><strong>文書の5%を密度プローブとしてサンプリング</strong>します（ランダムサンプル、最低50件）。</li><li><strong>バッチ処理された　msearch　kNNによるプローブ密度。</strong>各プローブはkNNクエリを実行し、隣接プローブの平均類似度を記録します。平均類似度が高い＝埋め込み空間の密な領域。<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-msearch"><code>msearch</code></a> は単一のHTTPコールで複数の検索リクエストを送信し、これは重要です。密度プローブは数百のkNNクエリを生成し、それらをバッチ処理することでリクエストごとのオーバーヘッドを回避します。</li><li><strong>多様性を考慮した高密度シードの選択</strong>：中央値以上の密度を持つ候補は、密度の降順でソートされ、既存のすべてのシードとのコサイン類似度が分離閾値を下回る場合にのみ貪欲に受け入れられます。これがクライアント側での唯一の計算です（8,000件の文書で最大0.01秒）。</li><li><strong><code>msearch</code></strong> <strong>kNNで全ての書類を重心に照らして分類します</strong>。各シードが重心として機能し、kNN検索は類似度しきい値を超える近接文書を取得します。各文書は、最も高いスコアを返した重心に割り当てられます。小さなクラスターはノイズとして処理されます。</li></ol><p>Elasticsearchが面倒な処理を担当します：<code>msearch</code>は密度プローブ、<code>msearch</code>は分類、<code>significant_text</code>はラベル付けを担います。このコーパス（8,495件の文書）では、5％の密度プローブサンプルが425件のkNNプローブクエリを開始し、<code>msearch</code>が9つのHTTPコール（バッチサイズ50）にバッチ処理され、プローブごとに1つのリクエストを処理することによるオーバーヘッドを回避します。<code>bbq_disk</code>ANNルックアップと組み合わせることで、クラスタリング段階を高速かつスケーラブルに保つことができます。kNNクエリは、クラスタリングパス中の速度のために最小の<a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/approximate-knn-search"><code>num_candidates</code></a>値を使用します。本番環境の検索クエリでは、レイテンシを犠牲にしてリコールを向上させるために、より高い<code>num_candidates</code>値を使用する必要があります。</p><p>クラスターは各重心の周りの埋め込み空間の密度によって決定される実際的なサイズを持ち、厳格な<code>k</code>の上限によって決定されるものではありません。密なトピック領域はより大きなクラスターを生み出し、ニッチなトピックは小さなクラスターを生み出します。</p><h4>KMeansやHDBSCANが実用的ではない理由</h4><p>KMeans法は球状クラスターを想定しており、メモリに完全なn×d行列が必要です。メモリに収まるコーパスの場合、<a href="https://scikit-learn.org/stable/modules/generated/sklearn.cluster.HDBSCAN.html">HDBSCAN</a>は強力な代替手段です。任意のクラスタ形状に対応でき、密度に関するセマンティクスも十分に理解されています。</p><p>密度探査型重心アプローチは、異なるニッチをターゲットとしています。ストレージ、検索、およびクラスタリングを1つのシステムで行いたい場合や、スケールがクライアント側の行列操作を実用的でなくするようなコーパスです。これは、Elasticsearch kNNを計算プリミティブとして使用し、任意のクラスターサイズを処理し、ほぼすべての計算をサーバー側で行います。</p><pre><code>    Clustered global index in 31.6s
      Total clusters: 82
      Total noise:    2420 (28.5%)
      Density probes: 425 kNN queries via 9 _msearch HTTP calls</code></pre><h4>ノイズレートについて理解する</h4><p>最大28%のノイズ率は設計によるもので、故障モードではありません。設定された<code>similarity_threshold</code>でどの高密度クラスターにも適合しない文書は、一致率が低いと強制的に判断されるのではなく、割り当てられずに残されます。これは品質ゲートの役割を果たします：意見を記したコラム、短い記事、そして一回限りのストーリーは、一貫したグループを定義する主題の密度が不足しているため、クラスタリングに抵抗する性質があります。</p><p>しきい値は調整可能です。<code>similarity_threshold</code>を下げると、より積極的なクラスタリングが生成されます（より多くの文書が割り当てられますが、クラスターは緩くなります）。一方、これを上げると、クラスターが引き締まり、ノイズの割合が増加します。こうした様々なニュースコンテンツを含むコーパスにおいては、約30％のノイズは妥当な動作基点と言えます。本番環境での導入は、分野固有の品質基準に合わせてしきい値を調整することが推奨されます。</p><h3>significant_textを使用した自動ラベル付け</h3><p>各クラスターには人間が読みやすいラベルが必要です。Elasticsearchの<code>significant_text</code>アグリゲーションは、フォアグラウンドセット（クラスター）とバックグラウンドセット（完全なコーパス）を比較して、異常に頻繁に現れる用語を見つけます。</p><p>内部的には、絶対頻度の変動と相対頻度の変動のバランスを取る統計的ヒューリスティック（デフォルトではJLHスコア）を使用しており、機械学習や大規模言語モデル（LLM）の呼び出しは行っていません。英国の政治に関するクラスターでは、<code>starmer</code>、<code>labour</code>、<code>downing</code>のような用語が浮かび上がる可能性があります。これらの用語は、全体的なニュースコーパスと比較して、そのクラスターで不釣り合いに頻出しているためです。</p><p>このグローバルパスでは、ラベルは<code>docs-clustering-all</code>に対して直接計算されるため、前景と背景の両方が月全体のデータから描画されます。パート2では、ラベル付けに日次インデックスパターン（<code>docs-clustering-*</code>）を使用します。これは、クエリで一致するすべてのインデックスを同時に対象にできるワイルドカードで、<code>significant_text</code>により広い背景を与えてコントラストを高めます。</p><p>最小クエリー形状は次のようになります。</p><pre><code>{
  "size": 0,
  "query": { "term": { "cluster_id": "72" } },
  "aggs": {
    "label_terms": {
      "significant_text": {
        "field": "text",
        "size": 5,
        "filter_duplicate_text": true
      }
    }
  }
}</code></pre><p><code>significant_text</code> また、品質ゲートの役割も果たします。有意な用語を生成しないクラスターには、特徴的な語彙がありません。これらはまとまりのないグループであり、誤解を招くようなラベルを付けるのではなく、ノイズとして分解する必要があります。</p><p>軽量で決定論的なクリーンアップステップで、ノイズの多いラベルの用語（数値トークン、一般的な単語）を削除し、必要に応じて代表的な見出しに切り替えます。これにより、ラベルをElasticsearchネイティブのまま維持しつつ、可読性を向上させます。</p><pre><code>    Sample cluster labels:
      cluster   3  (200 docs)  arsenal | mikel | villa
      cluster   1  (198 docs)  volodymyr | ukrainian | kyiv
      cluster   0  (196 docs)  hostages | hamas | israeli
      cluster   4  (187 docs)  scrum | rugby | borthwick
      cluster  52  (185 docs)  fossil | renewable | renewables
      cluster  10  (156 docs)  labour | gwynne | mps
      cluster  40  (151 docs)  novel | novels | literary
      cluster  11  (149 docs)  mewis | sarina | wiegman
      cluster  44  (143 docs)  flooding | rainfall | rain
      cluster  13  (131 docs)  doge | musk | elon
      cluster  12  (128 docs)  murder | insp | knockholt
      cluster   5  (124 docs)  putin | backstop | starmer


    Reassigned 35 docs from incoherent clusters to noise
    Total docs: 8,495
    Clustered:  6,040 (71.1%)
    Noise:      2,455 (28.9%)</code></pre><h3>クラスターの可視化</h3><p>以下の可視化は、グローバルクラスタリングパスが発見した内容を示しています：クラスター化された文書とノイズ文書の日付ごとの内訳、全月のUMAP投影、およびクラスターがソースではなくトピックを反映していることを確認するソースミックスチャート。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/99099f5adaa945ae4097c50b0d7151c7dd28872e-1000x400.png" style="width: 100%;" /><p>2025年2月におけるクラスター化された文書とノイズ文書の日次分布。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/477c6c7177942955a942f85f5c881da50e517915-1100x700.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4f4302556c8974c416842452cf33bca06e90b966-1100x700.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/327f96a79e382ef30614cb0570aa7fccd822b8f8-1100x700.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/acfb1524a10e24d6ff2412e7c3ec0f2b3ac75193-900x600.png" style="width: 100%;" /><p>UMAPの色のついた島はそれぞれがクラスターを表しています。クラスターは、純粋に類似性を埋め込むことで発見された、同じトピックに関する記事の集合です。灰色のノイズポイントは、いずれのクラスターにもきれいに収まらなかった記事（多くの場合、短い記事、意見記事、または一回限りのストーリー）です。</p><p>情報源の内訳図を見ると、各クラスターにはBBCニュースとガーディアンの<strong>両方</strong>からの記事が含まれていることが確認できます。クラスタリングは、<em>ソース</em>ではなく<em>トピック</em>を見つけ出すものであり、まさに教師なし学習から期待される結果です。</p><h3>Diversify Retrieverによるクラスター幅の調査</h3><p>通常のkNNは、クラスターの重心（密なコア）に最も類似した文書を返します。しかし、実際のクラスターはサブトピックも対象にします。The <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/diversify-retriever"><strong>Diversify Retriever</strong></a>は、Maximal Marginal Relevance（MMR） を使用して、重心に関連するだけでなく、<em>互いに異なる</em>文書を抽出します。</p><p>鍵となるパラメータは <strong>λ（ラムダ）</strong>です。</p><ul><li>λ = 1.0 → 純粋な関連性（通常のkNNと同じ）。</li><li>λ = 0.0 → 純粋多様性（最大限に分散された結果）。</li><li>λ = 0.5 → バランスが取れている：トピックに関連しているが、異なる角度をカバーしている。</li></ul><div style="display:none">Unknown block type "infoPanel", specify a component for it in the `components.types` option</div><p>最小Retrieverリクエストの形状は次のようになります。</p><pre><code>{
  "size": 8,
  "retriever": {
    "diversify": {
      "type": "mmr",
      "field": "embedding",
      "lambda": 0.5,
      "query_vector": "<cluster-centroid-vector>",
      "retriever": {
        "knn": {
          "field": "embedding",
          "query_vector": "<cluster-centroid-vector>",
          "k": 50,
          "num_candidates": 100
        }
      }
    }
  }
}</code></pre><p><code>type</code>、<code>field</code>、および <code>query_vector</code> パラメーターは、diversifyレベルで必要です。<code>field</code> はMMRに結果間の類似性に使用する dense_vector フィールドを指示し、<code>query_vector</code> は関連性スコアリングの参照点を提供します。</p><p>これにより、単に「その中心は何か？」という問いではなく、「このクラスターは実際に何を対象としているのか？」という問いに答えることができます。</p><pre><code>    Exploring cluster 52 (185 docs)
    Label: fossil | renewable | renewables
    Centroid computed (dim=1024)


    ========================================================================
    Plain kNN (closest to centroid)
    ========================================================================
      1. [0.9738] Green campaigners fear ministers are poised to award billions of pounds in fresh subsidies to Drax power station, despite strong concerns...
      2. [0.9710] Thirteen more oil and gas licences could be cancelled as ministers decide new guidance for fossil fuel extraction after a landmark court...
      3. [0.9699] Experts have accused the fossil fuel industry of seeking special treatment after lobbyists argued greenhouse gas emissions from oilfields...
      4. [0.9681] Burning wood is a terrible way of producing electricity . Chopping down trees destroys habitats for wildlife, and growing new trees cannot...
      5. [0.9649] Keir Starmer will do huge damage to the global fight against climate change if he gives in to political pressure and allows the development...
      6. [0.9641] Labour will next week be confronted with stark policy choices that threaten to expose the fault lines between the Treasury and the...
      7. [0.9638] The Drax power station near Selby in north Yorkshire burns imported wood pellets  The government has agreed a new funding arrangement with...
      8. [0.9581] If you care about the world we are handing on to future generations, the news on Thursday morning was dramatic. This January was the...
    
    ========================================================================
    Diversify retriever (MMR, lambda=0.5)
    ========================================================================
      1. [0.9738] Green campaigners fear ministers are poised to award billions of pounds in fresh subsidies to Drax power station, despite strong concerns...
      2. [0.9434] Oil and gas interests have waged a coordinated campaign to kill pro-electrification policies that ban gas connections in new buildings ,...
      3. [0.9303] It was interesting to read that new licences for oil and gas production in the North Sea are being delayed by legal action ( Thirteen more...
      4. [0.9139] The US energy secretary, Chris Wright, has said he “would love to see Australia get in the game of supplying uranium and maybe going down...
      5. [0.9077] Rachel Reeves was facing criticism on Saturday night as it was confirmed that a report she cited as evidence that a third ­runway at...
      6. [0.8996] When Margaret Thatcher opened the Hadley Centre for Climate Change in 1990 journalists suggested she was attempting to appear to be doing...
      7. [0.8993] The vast majority of governments are likely to miss a looming deadline to file vital plans that will determine whether or not the world has...
      8. [0.8987] European imports of seaborne gas shipments fell by a fifth last year to their lowest level since the pandemic, according to a new report,...
    
    Overlap: 1/8 documents appear in both result sets
    
    Avg pairwise similarity (lower = more diverse):
      Plain kNN:          0.9057
      Diversify retriever: 0.6965</code></pre><p>プレーンなkNNは、トピックの1つの角度、つまり中心点および互いに最も類似したドキュメントの周りにクラスターを形成します。Diversify Retrieverは、同じクラスターの異なるファセット、つまりサブトピック、異なるソース、多様な視点を提示します。</p><p>多様性指標はこれを定量的に裏付けます。Diversify Retrieverの結果では、平均ペアワイズ類似度が低く、これは返された文書がより広範囲をカバーしていることを意味します。</p><p>これは以下の用途に役立ちます。</p><ul><li><strong>クラスターが実際に対象としている内容を理解</strong>。その中心だけでなく端も含め理解します。</li><li><strong>要約の生成</strong>。多様で代表的な文書は、LLMにより良い材料を提供します。</li><li>人によるレビューや下流工程でのラベリングのために、<strong>代表的な例を発見</strong>。</li><li><strong>品質チェック</strong>。多様な結果が一貫性に欠けている場合、クラスターの分割が必要かもしれません。</li></ul><h2>パート2：時間軸に沿ったストーリーチェーン</h2><h3>日をまたいだデータストーリーの追跡</h3><p>パート1では、トピック発見のために1か月全体をグローバルにクラスタリングしました。時間軸に沿ったフローでは、同じ密度プローブの重心分類が<strong>日次インデックス</strong>ごとに1日単位で独立して実行され、その後、クラスターが隣接する日々にわたってリンクされます。注意：日々のクラスターはパート1のグローバルクラスターとは独立しており、それぞれの1日はその日のコンテンツに合わせて独自のクラスター割り当てとラベルを生成します。</p><h4><strong>関連付けの手法：サンプルとクエリ</strong></h4><p>A日目の各クラスターについて：</p><ol><li>いくつかの代表的な文書をサンプルとして選択します。</li><li>kNNをB日目のインデックスに対して実行します。</li><li>B日目のクラスターごとにヒット数を数えます。</li><li>ヒット率がしきい値（kNNの割合≥ 0.4）を超える場合は、リンクを記録します。</li></ol><p>この処理は高速で（クラスターごとにわずかなドキュメントのみがクエリされ、すべてではありません）、ElasticsearchのネイティブkNNを使用し、外部ツールは必要ありません。</p><pre><code>Preparing daily indices for temporal linkage...


Indexed 8,495 docs into 28 daily indices


Temporal links found: 808 in 145.4s

Strongest links:
  2025.02.01 'league | arsenal | premier' -> 2025.02.02 'league | season | striker'  (100%)
  2025.02.03 'league | striker | loan' -> 2025.02.04 'league | striker | season'  (100%)
  2025.02.03 'score | operator | gedling' -> 2025.02.04 'league | striker | season'  (100%)
  2025.02.12 'playoff | leg | bayern' -> 2025.02.13 'league | players | injury'  (100%)
  2025.02.14 'league | injury | football' -> 2025.02.15 'league | premier | football'  (100%)
  2025.02.18 'russia | ukraine | talks' -> 2025.02.19 'saudi | russia | arabia'  (100%)
  2025.02.18 'football | league | bayern' -> 2025.02.19 'league | manchester | players'  (100%)
  2025.02.21 'league | premier | manchester' -> 2025.02.22 'game | players | defeat'  (100%)
  2025.02.21 'rugby | calcutta | brilliant' -> 2025.02.22 'game | players | defeat'  (100%)
  2025.02.26 'metals | kyiv | ukrainian' -> 2025.02.27 'ukraine | russia | talks'  (100%)</code></pre><p>kNN分数が100％ということは、ソースクラスターからサンプリングされたすべてのドキュメントが同じターゲットクラスターに到達していることを意味し、これは異なる日付間のリンクが理論上最も強くなります。ほとんどの上のリンクはフットボール関連で、これは理にかなっています。プレミアリーグは毎日報道され、高いトピックの一貫性があります。</p><p>The <code>score | operator | gedling</code> → <code>league | striker | season</code> リンクは、ニッチなローカルフットボールクラスター（Gedling はノンリーグクラブです）が翌日に広範なプレミアリーグクラスターに吸収される例であり、これは異なる粒度での日々の再クラスタリングの自然な効果です。</p><h3>ストーリーチェーンの構築</h3><p>ストーリーチェーンとは、連日にわたって連続した一連のクラスターです。</p><p>個々のペアワイズリンクは、月曜日の「UK politics」クラスターが火曜日のクラスターにリンクしていることを示しています。チェーンはストーリー全体を明らかにします。月曜日に始まり、週を通して展開し、金曜日までに消えていくストーリーです。</p><p>チェーンは、kNN分数が0.4以上のリンクから貪欲に構築されます。これは、ソースクラスターからサンプリングされたドキュメントの少なくとも40％が単一のターゲットクラスターに到達したことを意味します。最も古いクラスターから開始し、アルゴリズムは常に最も強い発信リンクを追跡します。<br/></p><pre><code>    Strong links (kNN fraction >= 0.4): 244
    Story chains spanning 3+ days: 18
      Chain 1: 'ukrainian | kyiv | eastern' (19 days: Feb 3 → Feb 21)
      Chain 2: 'playing | opposition' (19 days: Feb 10 → Feb 28)
      Chain 3: 'tadhg | maro | cadan' (10 days: Feb 1 → Feb 10)
      Chain 4: 'invade | china | putin' (8 days: Feb 21 → Feb 28)
      Chain 5: 'elected | labour | leader' (7 days: Feb 12 → Feb 18)
      Chain 6: 'film | swift | awards' (6 days: Feb 2 → Feb 7)
      Chain 7: 'amendment | termination | reporting' (6 days: Feb 12 → Feb 17)
      Chain 8: 'officers | scene | police' (5 days: Feb 1 → Feb 5)</code></pre><p>最も長いチェーンは、ウクライナとロシアに関する報道を19日間連続で追跡していますが、2025年2月の地政学的な緊張が持続していることを考えると、これは驚くべきことではありません。2番目に長いチェーンは、19日間にわたるプレミアリーグのサッカーを追跡するものです。より短いチェーンは、映画賞シーズン（6日間）、シックス・ネーションズラグビー（10日間）、英国の政治指導者に関する報道（7日間）を追跡しています。それぞれのチェーンは、アルゴリズムが日々のインデックス全体にわたる埋め込み類似性のみから発見したストーリー展開を表しています。</p><h3>サンキー：ストーリーの流れを可視化する</h3><p>サンキーダイアグラムは、リンクの幅がつながりの強さを表す流れの可視化です。ここでは、各垂直バンドが1日を表し、各ノードは日々のクラスター（ドキュメント数によってサイズが決まる）であり、各色のパスは時間の経過に沿って1つのストーリーチェーンを追跡します。リンク幅はkNNのオーバーラップ強度をエンコードします。リンクが太いほど、サンプリングされたドキュメントの数がより多くターゲットクラスターに到達したことになります。色はチェーンごとに分かれているので、左から右へ流れる1つのカラーパスで1つのストーリーの進行がわかります。</p><p>たとえば、ウクライナとロシアのチェーン（比較的長い経路の一つとして示されている）は、2月初旬から第3週まで途切れることなく続いており、一貫して太い線で結ばれていることから、日々の話題の連続性が強いことがわかります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/100a60a7fb85da8ab3813fd071a82c93f2c3f318-1300x650.png" style="width: 100%;" /><p><em>2025年2月に流れる時間軸に沿ったストーリーチェーン各色のパスは、複数日にわたって続くストーリーを表し、リンクの幅はkNNの重なりの強さを示します。</em></p><h2>このアプローチがもたらすメリット</h2><p>このウォークスルーでは、Elasticsearch上に構築された完全な教師なしドキュメントクラスタリング・パイプラインについて説明しました。</p><ol><li><strong>クラスタリング埋め込み</strong>：Jina v5のタスク固有のアダプターは、トピックのグループ化に最適化された埋め込みを生成し、単なるクエリと文書のマッチングだけではありません。</li><li><strong>グローバルな発見クラスタリング</strong>：1つのインデックスで1か月間をクラスタリングすることで、日をまたいだトピックの発見を最大化します。</li><li><strong>密度プローブによる重心分類</strong>：5％をサンプリングし、<code>msearch</code> kNNを介して密度をプローブし、多様な高密度シードを選択し、すべての文書を重心に対して分類します。Elasticsearchは負荷の高い計算を処理します。シード選択のみがクライアント側で実行されます（最大0.01秒）。</li><li><strong><a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significanttext-aggregation"><code>significant_text</code></a>ラベリング</strong>：有意性テストは、MLモデルや手動アノテーションなしで意味のあるクラスタラベルを生成します。有意な項を生み出さないクラスタは非整合となり、ノイズに格下げされます。これは内蔵された品質ゲートです。</li><li><strong>時間軸に沿ったストーリーのリンク付け</strong>：日次インデックスとサンプルおよびクエリのクロスインデックスkNNは、ストーリーが時間とともにどのように進化するかを追跡します。</li></ol><p><strong>重要なポイント</strong></p><ul><li>埋め込みタスクの種類が重要です。クラスタリング埋め込みは、測定可能かつより緊密な話題のグループを生成します。</li><li>Elasticsearchはストレージ層<em>および</em>クラスタリングエンジンの両方として<a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN検索</a>を通じて機能します。</li><li>密度探査型重心分類は、ほぼすべての計算をサーバー側で行い、埋め込み空間の密度によって決定される合理的なサイズのクラスターを生成します。</li><li><code>significant_text</code> 高速で解釈可能で、自動ラベリングと品質管理の両方に効果的です。</li></ul><p><strong>このアプローチは、以下の場合に有用です。</strong></p><ul><li>タイムスタンプ付きのテキストがあり、ラベル付きトレーニングデータを使用せずにトピックを発見したい場合があります。</li><li>ストレージ、ベクトル検索、ラベル付け、および時間軸に沿ったリンク付けのために、1つのスタックが必要な場合があります。</li></ul><p><strong>検討すべき拡張機能：</strong></p><ul><li>複数期間クラスタリング（週間、月間集計）。</li><li>リアルタイムのインジェストと段階的なクラスター割り当て。</li><li>LLM生成のクラスタサマリーは、significant_text項をシードとして用います。</li><li>より大規模なスケールでは、サンプリングされたKMeansの重心が密度ベースのクラスタリングのウォームスタートシードとして機能し、探査フェーズのコストを削減できます。</li></ul><h2>はじめましょう</h2><p>タイムスタンプ付きの文書コーパスを差し替えます。日付のあるテキストのコレクションであれば、このパイプラインで利用可能です。完全なノートブックとサポートコードは、<a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/unsupervised-document-clustering-elasticsearch-jina-embeddings">付属レポジトリ</a>で入手できます。</p><ul><li><a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs"><strong>Elastic Cloudの無料トライアルを開始</strong></a>：<code>bbq_disk</code>サポート付きのマネージドクラスターを数分でご利用いただけます。</li><li><a href="https://www.elastic.co/elasticsearch/serverless"><strong>Elasticsearch Serverlessをお試しください</strong></a>：クラスターの管理は不要で、自動的にスケールし、このウォークスルーのすべてをサポートします。</li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[eコマース検索にガバナンスが必要な理由]]></title>
            <description><![CDATA[ガバナンスがないeコマース検索が不十分になる理由と、制御レイヤーが予測可能で意図に基づいた結果を保証し、検索の改善を実現する方法について学びましょう。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/ecommerce-search-governance-improve-retrieval</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/ecommerce-search-governance-improve-retrieval</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Alexander Marquardt, Honza Král, Taylor Roy]]></dc:creator>
            <pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3793259b01a5653a7520393a2f006610de0d21e7-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>eコマース小売業者は、根本的に異なる様々な種類のクエリを同一システム内で処理する必要があります。「オレンジ」を検索している購入者は果物そのものを期待しているのであって、オレンジジュースやオレンジマーマレードなど、「オレンジ」という単語を含む商品や、意味的に関連する柑橘類製品を期待しているわけではありません。「甘いもの好きなおじいちゃんへのプレゼント」を探す購入者は、文字通りのキーワードマッチングではなく、意味的な発見を必要としています。</p><p><em>語彙検索</em>（テキストのマッチング）、<em>セマンティック検索</em>（概念のマッチング）、<em>ハイブリッド検索</em>（語彙シグナルとセマンティックシグナルの組み合わせ）は、単独ではこれらの問題を解決できません。語彙検索は「オレンジ」という単語を含むあらゆるものを返す可能性がある一方で、「オレンジ」のような意図の強いクエリに対する純粋なセマンティック検索は、レモンやグレープフルーツなどの関連項目へと広がる可能性があります。ハイブリッド検索はこれらの語彙シグナルとセマンティックシグナルを組み合わせますが、それでもこのクエリをナビゲーションとして扱うべきか、どの制約を適用すべきか、あるいはどのビジネスポリシーを適用すべきかの判断は行いません。欠けているのは検索技術そのものではなく、これがどのような種類のクエリであるか、そして検索を開始する前にどの制約を適用すべきかを理解するガバナンスレイヤーです。</p><p>このブログでは、eコマースの検索ガバナンス、その重要性、そして制御レイヤーが予測可能で正確な検索結果をどのように保証するかについて探ります。</p><h2>eコマース検索におけるガバナンスの意味</h2><p><em>ガバナンス</em>は、この文脈では、ユーザーのクエリと検索エンジンの間に意思決定レイヤーを導入することを意味します。このレイヤーは以下の機能を果たします。</p><ul><li>クエリの意図を分類する：これはナビゲーション（「オレンジ」）か発見（「おじいちゃんへの贈り物」）か？</li><li>ビジネス上の制約を適用する：どのようなカテゴリー境界、資格要件、在庫状況の制約、または販売促進ポリシーが適用されるか？</li><li>適切な戦略への道筋：語彙検索、セマンティック検索、ハイブリッド検索のどれを使用すべきか？</li></ul><p>ガバナンスレイヤーは、各クエリにどの検索アプローチを使用すべきか、どの制約を適用しなければならないか、また検索が始まる前にどのビジネスポリシーを適用すべきかを決定します。ガバナンスとハイブリッド検索は混同すべきではありません。ハイブリッド検索は語彙的シグナルと意味的シグナルを組み合わせた検索戦略の一つであり、ガバナンスは語彙、セマンティック、ハイブリッドのいずれを使用すべきかを決定する上流の意思決定レイヤーです。</p><h2>現状：アプリケーションレイヤーの「スパゲッティ」型実装</h2><p>現在、多くの小売業者は、アプリケーションレイヤーに直接ロジックを追加することでこの問題を解決しようとしています。これはしばしば<em>スパゲッティコード</em>（何千行ものハードコーディングされたif-then文、正規表現、複雑な検索テンプレート）を生み出します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f532b099ee103458e15563a711dae92952f8df02-1024x765.png" style="width: 100%;" /><p>このアプローチは、上記に示したような望ましい検索結果をもたらすことができますが、運用上の大きな摩擦を生じさせます。</p><ul><li><strong>エンジニアリングへの依存：</strong>ビジネスユーザーやマーチャンダイザーは、エンジニアリングチケットの提出や数週間に及ぶ長い導入サイクルなしに検索動作を変更することができません。</li><li><strong>断片化：</strong>検索ロジックはアプリケーションコードと検索テンプレートの間に散在し、説明や監査が困難になり、進化させるにはリスクが伴います。</li></ul><p>チームがルーティングの必要性を認識したとしても、議論はしばしば間違った点、つまりどの取得方法を選択するかという点に集中してしまいます。</p><h2>誤った選択：語彙検索、セマンティック検索、ハイブリッド検索のどれかを選ぶこと</h2><p>検索チームはしばしば、この課題を検索戦略の選択として捉えています。この場合の選択肢は語彙/BM25、セマンティック/ベクトル、ハイブリッドの組み合わせです。その考え方は理解できます（検索方法は確かに重要です）が、実際の導入環境で最も一般的な失敗パターンを見落としています。それは、すべてのクエリに対して単一の検索方法を使用すると、最適とは言えない結果しか得られないということです。</p><p>コマース検索は、根本的に異なる意図が混在したものです。</p><ul><li><strong>決定論的で意図の高いナビゲーション</strong>（「オレンジ」、「牛乳」、「ピーナッツなしのチョコレート」、「安いオリーブオイル」）。</li><li><strong>探求的な発見</strong>（「山でのハイキング用のジャケット」、「ロボット工学が好きな12歳の子供へのプレゼント」）。</li><li><strong>運用上の制約</strong>（在庫状況、サイズ、価格、色）。</li><li><strong>マーチャンダイジングおよびキャンペーン</strong>（ブースト、非表示、季節限定キャンペーン）。</li></ul><p>システムがこれらすべてを同じ検索戦略を通じてルーティングした場合、運用モデルにガバナンスが欠如しているため、予測可能な方法で体系的に誤った結果が返されることが多くなります。チームがこれをガバナンスのギャップと認識していない場合、唯一の手段であるチューニングの強化で対応します。</p><h2>「関連性のチューニング」が循環的になり得る理由</h2><p>ルーティングレイヤーがなければ、「関連性」はしばしば終わりのないバックログに変わります。</p><ul><li>なぜこのクエリでは主要製品よりも付属品が上位に表示されるのか？</li><li>なぜこのヘッドクエリは突然関連アイテムを表示し始めたのか？</li><li>同義語を追加したり、アナライザーを調整したり、ハイブリッドを有効にしたりした後に結果が変わった理由は？</li><li>なぜビジネスチームは単一のクエリを修正するのにエンジニアリングリリースが必要なのでしょうか?</li></ul><p>チームは、同義語、ブースト、再ランキング実験、アプリケーションコードにおける例外処理などの追加といったさらなるチューニングで対応します。これはしばらくの間は機能するかもしれませんが、クエリの種類を決定し、取得前に適切な制約を適用するための明示的な決定レイヤーがシステムにまだないため、不安定な動作を引き起こすことがよくあります。</p><h2>eコマースの意図の構造：ヘッドとテール</h2><p>このセクションでは、eコマースにおける一般的なナビゲーションおよび探索クエリパターンの実用的な省略形として「ヘッド」と「テール」を使用します。現実世界では、多くのクエリには次の両方の側面が含まれています。</p><h3>ヘッドクエリ（決定論的意図）</h3><p>これらは、ユーザーが何を求めているかを正確に把握している、直接的なナビゲーションクエリです。</p><ul><li>単一アイテムの意図（「オレンジ」、「牛乳」、「パン」）。</li><li>具体的なブランド名や製品ファミリー名（「iPhone 15 Pro」、「ダイエットコーク」）。</li><li>SKU、モデル番号、サイズ（「ABC123」、「air max 270」）。</li></ul><p>これらのクエリに対して、語彙検索はトークンの対応関係（単語のマッチング）を処理できますが、ビジネス側は制約を尊重し、予測可能なランキングを返し、制御可能な結果を得ることも期待しています。マーチャンダイザーは、クエリが正しいカテゴリーの境界内で解決され、適格性を尊重し、特定のビジネス優先事項を明確にすることを確認する必要があります。</p><p>意図された解決を強制するためにはガバナンスが必要です。例えば、「オレンジ」は農産物カテゴリーに分類されるべきであり、オレンジジュース、オレンジマーマレード、オレンジソーダに分類されるべきではありません。</p><h3>テールクエリ（探索的発見）</h3><p>これらは、購入者が探している情報を含む、詳細で意図が豊富なクエリです。</p><ul><li>「甘いもの好きなおじいちゃんへのプレゼント」</li><li>「山でのハイキング用のジャケット」</li><li>「一日中立っていられる靴」</li></ul><p>語彙検索は、このような場合にしばしば困難に直面します。セマンティック検索は、文言が一致しなくてもクエリの概念を製品に結びつけることができる点で優れています。しかし、セマンティック検索だけで十分なことは稀です。実際のクエリでは、どの検索方法を用いるかにかかわらず、制約を適用する必要がある場合が多くなります。</p><h2>制約条件は検索方法に直交</h2><p>制約をセマンティック検索に適用することは、<em>ハイブリッド検索</em>を意味するものではなく、これらは直交する概念です。Elasticsearchのフィルターやブーストなどの制約は、語彙、セマンティック、ハイブリッドのいずれの検索にも適用できます。課題は、クエリをどのように解釈すべきか、どの制約を強制すべきか、どの検索戦略を使用すべきかを決定することです。</p><p>以下は、検索とハードな制約を組み合わせたクエリの例です。</p><ul><li><strong>オレンジ：</strong>「オレンジ」という語句に「果物」や「農産物」などのカテゴリー制約を加えた語彙検索を行い、オレンジマーマレード、オレンジジュース、オレンジソーダを除外。</li><li><strong>ビタミンCが豊富な4ドル未満の果物：</strong>栄養に関する意図を理解し、結果を果物カテゴリーと4ドル未満の製品に制限するセマンティック検索。</li><li><strong>履きやすい仕事向けの靴：</strong>コンテキスト上の意図に基づくセマンティック検索と、結果を靴に限定するカテゴリー制約。</li></ul><p>これらのクエリは単一の方法では処理できません。</p><ul><li><strong>純粋な語彙検索</strong>では不十分です。なぜなら、「ビタミンCが豊富」や「履きやすい」といったフレーズは、きれいに構造化された属性として存在しない可能性があるからです。これらは、製品の説明、レビュー、または仕様から推測する必要があるかもしれません。</li><li><strong>純粋なセマンティック検索だけ</strong>でも十分ではありません。明示的な制約がない場合、「ビタミンCが豊富な果物」のようなクエリは、意図したカテゴリーや価格帯から外れたビタミンサプリメント、フルーツ風味の飲料、ビタミン含有量の多い野菜などにまで広がる可能性があるからです。</li></ul><p>ガバナンスレイヤーは、クエリが語彙検索、意味的理解、制約の適用、またはこれらの組み合わせを必要とするかどうかを決定します。このレイヤーがなければ、eコマースチームは以下のような状況に陥る可能性があります。</p><ul><li><strong>制約過剰：</strong>意味的なリクエスト（例：「おじいちゃんへのプレゼント」）に対して語彙検索を使用すること。</li><li><strong>制約不足：</strong>意図の明確なヘッドクエリ（「オレンジ」など）に対してセマンティッククエリを使用すること。</li></ul><p>ガバナンスの課題は、各クエリのクラスに対して適切な判断を下せるシステムを構築することです。</p><h2>ガバナンスがない場合に起こること</h2><p>最も一般的な失敗パターンは単純明快です。チームがユーザーの生のクエリを受け取り、中間的なガバナンスレイヤーを介さずに、それを単一の検索戦略（語彙、セマンティック、またはハイブリッド）に直接渡してしまうのです。</p><h3>語彙検索は意図した解決を見逃す</h3><p>ユーザーが「オレンジ」を検索すると、語彙検索戦略では、そのトークンを含むあらゆるもの（オレンジジュース、オレンジマーマレード、オレンジソーダなど）が返される可能性があります。システムはその用語を正しく照合しましたが、ガバナンスがなければ、意図した買い物のコンテキスト（果物）を解決できない可能性があります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/99abc7a46f9c56a26a68d0a089d7ab830b9b5568-1560x814.png" style="width: 100%;" /><h3>セマンティック検索は意図された制約を超えて広がる</h3><p>ユーザーが「オレンジ」を検索すると、セマンティックシステムは、関連する製品コンセプト全体から、概念的に関連するアイテムを検索する可能性があります。システムはより広い領域（果物や農産物）を正しく理解しているかもしれませんが、明確なガバナンスがなければ、ユーザーが意図した制約（特にオレンジ）を超えてしまう可能性があります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c9de86363ecbed499fe48259f47b3c5b2c26bc43-1568x796.png" style="width: 100%;" /><h3>ギャップはガバナンスです</h3><p>必要なのは、クエリの意図を判断し、検索開始前に適切な制約を適用する上流の決定レイヤーです。これにより、以下のような問題が修正されます。</p><ul><li>ユーザーが実際に求めていたものと類似または関連するアイテムが並べて表示される。</li><li>カテゴリーの境界線が曖昧になる（「飲料」対「農産物」）。</li><li>季節ごとのブーストやキャンペーンを実施できない。</li><li>予測不可能で説明のつかない結果。</li></ul><h2>意図の理解とルーティング：必要な制御プレーン</h2><p>ガバナンスを擁する検索システムは、検索の前（Elasticsearchでクエリを実行する前）に軽量な制御プレーンを導入します。この制御機能については、このブログシリーズの第3部と第4部で詳しく説明します。ここでは、その機能についてのみ説明し、動作原理については触れません。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/88c3d0f9731a128d73a765dcdffed897308110a6-2680x766.png" style="width: 100%;" /><p>コントロールプレーンは、以下のように意図を検出し、ビジネスポリシーを適用し、適切な検索戦略を確保することができます。</p><p><strong>1. 意図シグナルを検出</strong></p><ul><li>このクエリの目的はナビゲーションか、発見か？</li><li>これは既知のヘッドクエリ（牛乳、パン、バナナなど）か？</li><li>既知の製品、ブランド、またはカテゴリー解釈はあるか（「オレンジ」は果物と解釈されるべきなど）？</li><li>クエリはSKUのようなパターンですか？</li><li>その検索クエリは、現在実施中のキャンペーンや季節的なポリシー（例えば、クリスマス期間中に七面鳥関連の検索結果をブーストするなど）の対象となるか？</li><li>クエリは制約（カテゴリー、属性、除外、価格/サイズ/色）を示唆しているか？</li></ul><p><strong>2. ガバナンスおよびビジネスポリシーを適用</strong></p><ul><li>まず決定論的制約（カテゴリー/属性/否定/可用性）を強制します。</li><li>積極的なマーチャンダイジングポリシー（ブースト/非表示/ピン留め/上書き）を適用します。</li><li>優先順位ルールとの競合を解決します（キャンペーンによる上書きとグローバルポリシーなど）。</li></ul><p><strong>3. 適切な検索戦略へルーティング</strong></p><ul><li>ナビゲーションや意図の高いヘッドクエリには語彙的（高速かつ決定論的）な処理。</li><li>真の発見クエリにはセマンティック検索。</li><li>語彙と意味のシグナルを組み合わせて、明示的なビジネス制約の下で価値を加えるハイブリッド。</li></ul><p>実際には、制御プレーンの出力は単に「ハイブリッドを使用」または「セマンティックを使用」というものではありません。これはガバナンスを備えた検索計画であり、購入者の意図、適用されるべき制約とポリシー、そして実行されるべき検索戦略の解釈です。いくつかの簡単な例を挙げると、これがより具体的に理解できるでしょう。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">検索クエリ</td><td style="border: 1px solid #ccc; padding: 4px;">ガバナンスを備えた解釈</td><td style="border: 1px solid #ccc; padding: 4px;">検索計画の例</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">「ピーナッツなしのチョコレート」</td><td style="border: 1px solid #ccc; padding: 4px;">ハード除外制約付きの製品指向クエリ</td><td style="border: 1px solid #ccc; padding: 4px;">チョコレートの語彙検索とピーナッツを含む製品の除外フィルター</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">「安いオリーブオイル」</td><td style="border: 1px solid #ccc; padding: 4px;">価格制約付きの製品/カテゴリー検索</td><td style="border: 1px solid #ccc; padding: 4px;">オリーブオイルの語彙検索と小売業者の低価格基準で上限を設定した価格フィルター</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">「4ドル以下のビタミンCを多く含む果物」</td><td style="border: 1px solid #ccc; padding: 4px;">セマンティックな理解と厳格な制約を必要とする検索クエリ</td><td style="border: 1px solid #ccc; padding: 4px;">栄養に関する意図を意味的に検索し、果物カテゴリーに限定し、価格が4ドル以下の製品に絞り込み</td>
                    </tr>
              </tbody>
            </table>
          <p>制御プレーンは、各クエリに対して一貫して、予測可能かつスケールで適切なポリシーと検索戦略を選択します。これにより、意図に沿った制約が最初に適用され、ルーティングの決定が暗黙的ではなく明示的になるため、高度な検索方法が本番環境でより予測可能になります。</p><h2>他のアプローチとの関連</h2><p>一部のチームは、製品のセマンティクスをより正確に捉えるために改良された埋め込みモデルを使用しており、これによりセマンティック検索の品質を大幅に向上させることができます。その他には、検索後のエンゲージメントやビジネスシグナルに基づいて結果の順序を最適化するために、<a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr">ランク学習（LTR）</a>などの再ランキング手法を用いるものもあります。どちらも価値があり、多くの場合、互いに補完し合う関係にあります。より良い埋め込みは類似性マッチングを改善します。再ランク付けは取得した候補の順序を改善します。</p><p>ガバナンスは問題の別のレイヤーに対処するものであり、情報取得よりも上流に位置します。どの検索戦略（例えば、語彙、セマンティック、ハイブリッド）を使用するか、どのような決定論的制約が必要か、そしてどのクエリで複数のビジネスポリシーを組み合わせるべきかを決定します。</p><h2>ガバナンスされたコントロールプレーンが可能にすること</h2><p>一度ガバナンスレイヤーが設置されると、運用モデルは根本的に変わります。売上に直結するクエリは予測可能になり、ビジネスチームは、エンジニアリングリリースサイクルを待たずに検索動作を更新できます。また、セマンティック検索やハイブリッド検索といった高度な検索手法は、全体的なオン/オフスイッチとしてではなく、ルーティングやガードレールの背後で段階的に導入することができます。</p><p>このシリーズの次回の記事では、その運用モデルが実際にどのようなものなのか、そしてなぜそれが基盤となる検索技術と同じくらい重要なのかを探ります。</p><p>もしマーチャンダイザーがJiraチケットを開いて、売上に大きく影響するクエリを修正するためのデプロイを待たなければならないとしたら、ボトルネックはエンジンではなく、運用モデルにあります。現代のeコマース検索では、ビジネスの意図を迅速かつ安全に、管理可能で監査可能な検索動作に変換する方法が必要であり、同時に、測定可能な価値を付加する高度な検索機能も引き続き活用する必要があります。</p><h2>このシリーズの次回作</h2><p>本シリーズで探求するパターンは、検索の上流で機能します。クエリ生成が始まる前に、ビジネス上の意図を適切なクエリ戦略に変換します。次の投稿では、技術的な問題から運用上の問題へと視点を移します。ビジネスチームがエンジニアリングの介入なしに検索動作を変更できるようになった場合に何が起こるか、そしてガバナンスがそれを安全にする理由について説明します。</p><h2>ガバナンスを備えたeコマース検索を実践</h2><p>エンタープライズ向けeコマースサービスにおけるエンジニアリング上のボトルネック、脆弱なアプリケーションレイヤーロジック、予測不可能な検索結果などは、Elastic Servicesが解決に役立つ問題です。本シリーズで解説するガバナンスを備えた制御プレーンアーキテクチャーは、Elastic Services Engineeringによって構築されました。</p><p>もし貴社のチームが商品企画に関する要望をコード変更に落とし込むためにエンジニアリングサイクルを費やしていたり、検索関連性に関する未解決の課題が一向に減らないようであれば、Elasticは現在のアーキテクチャーを評価し、ガバナンスを備え、ビジネス部門が編集可能な検索システムへの道筋を構築するお手伝いをいたします。<a href="https://www.elastic.co/consulting">Elastic Services</a>にご連絡ください。</p><h2>議論に参加</h2><p>検索ガバナンス、検索戦略、またはeコマース検索アーキテクチャについてご質問がありますか？より広範な<a href="https://discuss.elastic.co/">Elasticコミュニティの議論</a>に参加しましょう。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[MastraとElasticsearchを使用してエージェント型AIアプリケーションを構築する方法]]></title>
            <description><![CDATA[MastraとElasticsearchを使用してエージェント型AIアプリケーションを構築する方法を実例を通じて学びましょう。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/build-agentic-ai-applications-mastra-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/build-agentic-ai-applications-mastra-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Enrico Zimuel]]></dc:creator>
            <pubDate>Wed, 08 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ab30baf2f908534840c5d71a46705773807baf54-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>この記事では、<a href="https://mastra.ai/">Mastra</a> TypeScriptフレームワークを使用して、<a href="https://www.elastic.co/elasticsearch">Elasticsearch</a>と連携するエージェント型アプリケーションを構築する方法をご紹介します。</p><p>最近、<a href="https://github.com/mastra-ai/mastra">mastra-ai/mastra</a>オープンソースプロジェクトに、ベクトルデータベースとしてのElasticsearchのサポートを追加しました。この新機能により、MastraでElasticsearchをネイティブに使用して埋め込みを保存できます。ベクターに加えて、Elasticsearchはコンテキストエンジニアリングの要件を満たすための高度な機能群を提供します（例：<a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">ハイブリッド検索と再ランク付け</a>）。</p><p>この記事では、Elasticsearchを使用したRetrieval-Augmented Generation（RAG）アーキテクチャを実装するエージェントの作成について詳しく説明します。エージェント的なアプローチを使用して、Elasticsearchに保存されているSF映画データのコーパスを操作するデモプロジェクトを紹介します。このプロジェクトは<a href="https://github.com/elastic/mastra-elasticsearch-example">elastic/mastra-elasticsearch-example</a>で入手できます。</p><h2>Mastra</h2><p>Mastraは、エージェント型AIアプリケーションを作成するためのTypeScriptフレームワークです。</p><p>Mastraのプロジェクト構造は以下のようになります：</p><pre><code>src/
├── mastra/
│   ├── agents/
│   │   └── weather-agent.ts
│   ├── tools/
│   │   └── weather-tool.ts
│   ├── workflows/
│   │   └── weather-workflow.ts
│   ├── scorers/
│   │   └── weather-scorer.ts
│   └── index.ts
├── .env.example
├── package.json
└── tsconfig.json</code></pre><p>Mastraでは、<a href="https://mastra.ai/docs/agents/overview">エージェント</a>、<a href="https://mastra.ai/docs/agents/using-tools">ツール</a>、<a href="https://mastra.ai/docs/workflows/overview">ワークフロー</a>、<a href="https://mastra.ai/docs/evals/overview">スコア</a>を作成できます。</p><p><strong>エージェント</strong>は、インプットとしてメッセージを受け取り、アウトプットとして回答を生成するクラスです。エージェントは、ツール、大規模言語モデル（LLM）、およびメモリを使用できます（図1）。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f6aca2dcc7fcc45d25e06681649be1b2b7eb6781-706x721.png" style="width: 100%;" /><p>エージェントの<strong>ツール</strong>は、「外部の世界」と対話することを可能にします。たとえば、ウェブAPIと通信したり、Elasticsearchにクエリを実行するような内部操作を行ったりします。<strong>メモリ</strong>コンポーネントは、過去の入力と出力を含む会話の履歴を格納するために非常に重要です。この格納されたコンテキストにより、エージェントは過去の対話を活用して、将来の質問に対してより情報に基づいた関連性の高い回答を提供できます。</p><p><strong>ワークフロー</strong>を使用すると、単一のエージェントの推論に頼るのではなく、明確で構造化されたステップを使用して複雑なタスクのシーケンスを定義できます（図2）。タスクをどのように分割し、データをどのように移動させ、何をいつ実行するかを網羅的に制御できます。ワークフローは、デフォルトで組み込みの実行エンジンを使用して実行されますが、<a href="https://mastra.ai/docs/deployment/workflow-runners">ワークフローランナー</a>にデプロイすることもできます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0dce161cf7891207015dc87532b5b90df1822432-880x252.png" style="width: 100%;" /><p>Mastraでは、スコアを定義することもできます。スコアは、モデル評価、ルールベース、統計的な方法を用いてエージェントの出力を評価する自動テストです。スコアラーは<em>スコア</em>を返します。これは、出力が評価基準をどれだけ満たしているかを定量化する数値（通常0から1の間）です。これらのスコアにより、パフォーマンスを客観的に追跡し、さまざまなアプローチを比較し、AIシステムの改善すべき分野を特定することができます。スコアラーは、独自のプロンプトやスコアリング関数でカスタマイズできます。</p><h2>Elasticsearch</h2><p>デモプロジェクトを実行するには、Elasticsearchインスタンスを実行する必要があります。<a href="https://www.elastic.co/cloud">Elastic Cloud</a>で無料トライアルを有効化するか、<a href="https://github.com/elastic/start-local"><code>start-local</code></a>スクリプトを使ってローカルにインストールできます。</p><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre><p>これにより、ElasticsearchとKibanaがコンピュータにインストールされ、Mastraインテグレーションの設定に使用するAPIキーが生成されます。</p><p>APIキーは前のコマンドの出力として表示され、elastic-start-localフォルダー内の<strong>.env</strong>ファイルに保存されます。</p><h2>デモをインストールして設定</h2><p>デモプロジェクトのソースコードを含む <a href="https://github.com/elastic/mastra-elasticsearch-example">elastic/mastra-elasticsearch-example</a> リポジトリを作成しました。リポジトリに記載されている例は、Elasticsearchからドキュメントを取得するためのRAGアーキテクチャを実装したエージェントをMastraで作成する方法を示しています。</p><p>SF映画のデモ用データセットを提供しました。<a href="https://www.kaggle.com/datasets/rajugc/imdb-movies-dataset-based-on-genre/versions/2?select=scifi.csv">Kaggle</a>上のIMDbデータセットから500本の映画を抽出しました。</p><p>最初のステップは、次のコマンドを使用してnpmでプロジェクトの依存関係をインストールします：</p><pre><code>npm install</code></pre><p>次に、設定内容を格納する<strong>.env</strong>ファイルを構成する必要があります。以下のコマンドを使用して、 <strong>.env.example</strong>ファイルの構造をコピーすることで、このファイルを生成できます。</p><pre><code>cp .env.example .env</code></pre><p>これで、.envを編集できるようになりました。不足している情報を追加します：</p><pre><code>OPENAI_API_KEY=
ELASTICSEARCH_URL=
ELASTICSEARCH_API_KEY=
ELASTICSEARCH_INDEX_NAME=scifi-movies</code></pre><p>Elasticsearchインデックスの名称は <strong><code>scifi-movies</code></strong>です。必要であれば、環境変数 <code>ELASTICSEARCH_INDEX_NAME</code>を使って変更できます。</p><p>OpenAIを埋め込みサービスとして使用しました。これは、<code>OPENAI_API_KEY</code>環境変数にOpenAIのAPIキーを提供する必要があることを意味します。</p><p>例で使用されている埋め込みモデルは <a href="https://developers.openai.com/api/docs/models/text-embedding-3-small">openai/text-embedding-3-small</a> であり、埋め込み次元は1,536です。</p><p>最終的な答えを導き出すために、<a href="https://developers.openai.com/api/docs/models/gpt-5-nano">openai/gpt-5-nano</a> モデルを使用してコストを削減しました。</p><p>RAGアーキテクチャでは、回答の妥当性を検証するという重労働は検索コンポーネント（この場合は Elasticsearch）が行うため、それほど強力ではない（そして一般的に安価な）最終的なLLMモデルを使用することができます。</p><p>小規模なLLMは主に2つのタスクを担当します：</p><ul><li><strong>クエリの言い換え/埋め込み：</strong>ユーザーの自然言語の質問をセマンティック検索用のベクトル埋め込みに変換します。</li><li><strong>回答の合成：</strong>取得された関連性の高いコンテキストの断片（文書/動画）を取り出し、提示された指示に従って、首尾一貫した最終的な人間が読める回答に合成します。</li></ul><p>RAGプロセスは回答に必要とされる<strong>正確な事実的文脈を提供する</strong>ため、最終的なLLMは大規模または高度に複雑である必要はなく、必要な知識をすべて自らのパラメータ内に持つ必要もありません（それは大規模で高価なモデルが優れている領域です）。本質的には、本格的なナレッジベースとしてではなく、Elasticsearchが提供するコンテキストの洗練されたテキストサマライザーおよびフォーマッターとして機能します。これにより、コストやレイテンシーの最適化に<code>gpt-5-nano</code>のようなモデルを活用できます。</p><p>.envファイルの設定後、次のコマンドで映画をElasticsearchに取り込むことができます。</p><pre><code>npx tsx src/utility/store.ts</code></pre><p>次のような出力が表示されるはずです。</p><pre><code>🚀 Starting ingestion of 500 movies from 500_scifi_movies.jsonl...
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 1/500 (0%) | ok:1 | fail:0 | chunks:1 | eta:19m 33s | current:Capricorn One
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 2/500 (0%) | ok:2 | fail:0 | chunks:2 | eta:10m 32s | current:Doghouse
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 3/500 (1%) | ok:3 | fail:0 | chunks:3 | eta:7m 33s | current:Dinocroc
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 4/500 (1%) | ok:4 | fail:0 | chunks:7 | eta:6m 10s | current:Back to the Future           
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 5/500 (1%) | ok:5 | fail:0 | chunks:9 | eta:5m 14s | current:The Projected Man            
Ingesting ░░░░░░░░░░░░░░░░░░░░░░░░ 6/500 (1%) | ok:6 | fail:0 | chunks:11 | eta:4m 41s | current:I, Robot
...
✅ Ingestion complete in 1m 46s. Success: 500, Failed: 0, Chunks: 693.</code></pre><p>scifi-moviesインデックスのマッピングには、次のフィールドが含まれています。</p><ul><li><strong>埋め込み</strong>、1536次元のdense_vector、コサイン類似度。</li><li><strong>説明</strong>、映画の説明を含むテキスト。</li><li><strong>監督</strong>、監督の名前を含むテキスト。</li><li><strong>タイトル</strong>、映画のタイトルを含むテキスト。</li></ul><p>タイトルとディスクリプションを使用して埋め込みを生成しました。タイトルとディスクリプションは2つの別個のフィールドであるため、両者を連結することで、結果として得られる埋め込みベクトルが、映画の具体的で独自の特徴（タイトル）と豊かな説明的コンテキスト（内容説明）の両方を捉えることができます。これにより、より正確で包括的なセマンティック検索結果が得られます。この結合されたインプットにより、埋め込みモデルは類似度マッチングに向けて、ドキュメントのコンテンツをより適切に単一のベクトル表現として捉えることができます。</p><h2>デモを実行</h2><p>次のコマンドでデモを実行できます：</p><pre><code>npm run dev</code></pre><p>このコマンドを実行すると<strong>localhost:4111</strong>でWebアプリケーションが起動し、Mastra Studioにアクセスできるようになります（図3）。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1567e309df21a12bcf1dfef4429f82342549956c-1705x1079.png" style="width: 100%;" /><p><a href="https://mastra.ai/docs/getting-started/studio">Mastra Studio</a> はエージェントの構築とテストのためのインタラクティブなUIを提供し、さらにMastraアプリケーションをローカルサービスとして公開するREST APIも備えています。これにより、統合を気にせずにすぐに構築を始められます。</p><p>Elasticsearchを使ったセマンティック検索を実行するツールとして、Mastraの<a href="https://mastra.ai/reference/tools/vector-query-tool">createVectorQueryTool</a>を使った<strong>Elasticsearch Agent</strong>を提供しました。このエージェントは、RAGアプローチを使用して関連文書（つまり映画）を検索し、ユーザーの質問に答えます。</p><p>このエージェントは以下のプロンプトを使用します：</p><pre><code>You are a helpful assistant that answers questions based on the provided context.
Follow these steps for each response:

1. First, carefully analyze the retrieved context chunks and identify key information.
2. Break down your thinking process about how the retrieved information relates to the query.
3. Draw conclusions based only on the evidence in the retrieved context.
4. If the retrieved chunks don't contain enough information, explicitly state what's missing.

Format your response as:
THOUGHT PROCESS:
- Step 1: [Initial analysis of retrieved chunks]
- Step 2: [Reasoning based on chunks]

FINAL ANSWER:
[Your concise answer based on the retrieved context]

Important: When asked to answer a question, please base your answer only on the context provided in the tool. 
If the context doesn't contain enough information to fully answer the question, please state that explicitly and stop it.
Do not add more information than what is present in the retrieved chunks.
Remember: Explain how you're using the retrieved information to reach your conclusions.</code></pre><p><code>Mastra Studio &gt; Agents</code>メニューをクリックして<strong>Elasticsearch Agent</strong>を選択すると、チャットシステムを使用してエージェントをテストできます。たとえば、SF映画に関する情報を次のような質問で尋ねることができます。</p><p><em>UFOを題材にした映画またはテレビシリーズを5つ見つけてください</em>。</p><p>エージェントがvectorQueryToolを実行することがわかります。起動したツールをクリックすると、入力と出力を確認できます。実行の最後に、LLMはElasticsearchのscifi-moviesインデックスから得られたコンテキストに基づいて、あなたの質問に回答します（図4）。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d9998d4f687984de98845dae52d1288166abf448-1344x1071.png" style="width: 100%;" /><p>Mastraは内部で以下の手順を実行します：</p><ol><li><strong>ベクトル変換：</strong>ユーザーの質問「<em>UFOに関する映画やテレビシリーズを5つ探して</em>」は、OpenAIの<code>openai/text-embedding-3-small</code>モデルを使ってベクトル埋め込みに変換されます。</li><li><strong>ベクトル検索：</strong>この埋め込みはその後、Elasticsearchに対してベクトル検索を用いてクエリを実行するために使用されます。</li><li><strong>結果の取得：</strong>Elasticsearchは、クエリに非常に関連性の高い10本の映画（つまり、ユーザーのクエリベクトルに最も近いベクトルを持つ映画）を返します。</li><li><strong>回答生成：</strong>取得された映画と元のユーザーの質問は、具体的には<code>openai/gpt-5-nano</code> LLM に送信されます。LLMはこの情報を処理し、最終的な回答を生成することで、ユーザーの5つの結果への要望を確実に満たします。</li></ol><h2>Elasticsearch エージェント</h2><p>ここではElasticsearch Agentのソースコードを報告しました。</p><pre><code>import { Agent } from "@mastra/core/agent";
import { ElasticSearchVector } from '@mastra/elasticsearch';
import { createVectorQueryTool } from '@mastra/rag';
import { ModelRouterEmbeddingModel } from "@mastra/core/llm";
import { Memory } from "@mastra/memory";

const es_url = process.env.ELASTICSEARCH_URL;
const es_apikey = process.env.ELASTICSEARCH_API_KEY;
const es_index_name = process.env.ELASTICSEARCH_INDEX_NAME;
const prompt = 'insert here the previous prompt';

const esVector = new ElasticSearchVector({
  id: 'elasticsearch-vector',
  url: es_url,
  auth: {
    apiKey : es_apikey
  }
});

const vectorQueryTool = createVectorQueryTool({
  vectorStore: esVector,
  indexName: es_index_name,
  model: new ModelRouterEmbeddingModel("openai/text-embedding-3-small")
});

export const elasticsearchAgent = new Agent({
  id: "elasticsearch-agent",
  name: "Elasticsearch Agent",
  instructions: prompt,
  model: 'openai/gpt-5-nano',
  tools: { vectorQueryTool },
  memory: new Memory(),
});</code></pre><p><strong>vectorQueryTool</strong>は、RAGの例における取得部分を実装するために呼び出されるツールです。MastraにElasticが提供した<a href="https://mastra.ai/reference/vectors/elasticsearch">ElasticSearchVector</a>実装を使用しています。</p><p>エージェントは、vectorQueryTool、プロンプト、およびメモリを消費するエージェントクラスのオブジェクトです。ご覧のように、Elasticsearchをエージェントに接続するために必要なコードはごくわずかです。</p><h2>まとめ</h2><p>この記事では、ElasticsearchとMastraフレームワークを統合し、洗練されたエージェント型AIアプリケーションを構築することのシンプルさとパワーについて実証しました。具体的には、Elasticsearchでインデックス化されたSF映画データのコーパスに対してセマンティック検索を実行できるRAGエージェントの作成について説明しました。</p><p>重要なポイントは、ElasticがMastraのオープンソースプロジェクトに直接貢献し、ベクターストアとしてのElasticsearchをネイティブサポートしていることです。この統合により、導入時の障壁が大幅に低下します。これは、 <strong>Elasticsearch Agent</strong>のソースコードを見れば明らかです。<code>ElasticSearchVector</code>と<code>createVectorQueryTool</code>を使用することで、Elasticsearchをエージェントに接続するための総合的なセットアップが、最小限の設定コード行数で済みます。</p><p>Elasticsearchは、結果の関連性を高めるためのいくつかの高度な機能を提供しています。例として、<a href="https://www.elastic.co/elasticsearch/hybrid-search">ハイブリッド検索</a>は、語彙検索とベクトル検索を組み合わせることで、精度を大幅に向上させます。もう一つの興味深い機能は、ハイブリッド検索の最後に適用できる最新の<a href="https://www.elastic.co/search-labs/tutorials/jina-tutorial/jina-reranker-v3">Jinaモデル</a>を使用した再ランク付けです。これらの技術についてさらに詳しく知るには、Elasticsearch Labsの以下の記事を参照してください。</p><ul><li><a href="https://www.elastic.co/search-labs/blog/hybrid-search-elasticsearch">Elasticsearchのハイブリッド探索</a> by ヴァレンティン・クレっタス</li><li><a href="https://www.elastic.co/search-labs/blog/jina-models-elasticsearch-guide">Jinaモデル入門、その機能、Elasticsearchでの利用</a> by スコット・マーテンス</li></ul><p>また、提供されている例を参考に、MastraとElasticsearchを使って独自のデータ駆動型エージェントを構築し始めることをお勧めします。Mastraについての詳細は、 <a href="https://mastra.ai/docs">こちら</a>のオフィシャルドキュメントをご覧ください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elastic Workflowsを使用したKibanaのダッシュボード表示の監視]]></title>
            <description><![CDATA[Elastic Workflowsを使用して、Kibanaのダッシュボードのビューメトリクスを30分ごとに収集し、それらをElasticsearchにインデックスする方法を学びましょう。これにより、独自のデータ上にカスタム分析と可視化を構築できます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/monitor-kibana-dashboard-views-elastic-workflows</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/monitor-kibana-dashboard-views-elastic-workflows</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Gustavo Llermaly]]></dc:creator>
            <pubDate>Fri, 03 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0ed94ce00962287b5507f45c92ecb60fdcbf2718-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><a href="https://www.elastic.co/kibana">Kibana</a>は各ダッシュボードが何回閲覧されたかを追跡しますが、そのデータは組み込みのダッシュボードにはネイティブに公開されていません。この記事では、<strong>Elastic Workflows</strong>を使って30分ごとにそのデータを自動収集し、Elasticsearchにインデックス化して、その上に独自の分析を構築します。</p><p><a href="https://www.elastic.co/docs/explore-analyze/workflows">Elastic Workflows</a>はKibana内部に組み込まれた自動化エンジンで、シンプルなYAML構成を使用して多段階プロセスを定義できます。各ワークフローはスケジュールやイベント、または<a href="https://www.elastic.co/docs/explore-analyze/ai-features/elastic-agent-builder">Elastic Agent Builder</a>のツールとしてトリガーでき、各ステップでKibana APIを呼び出し、Elasticsearchをクエリし、データを変換できます。</p><p>具体的な例としてダッシュボードのビュー数を使用しますが、同じパターンがKibanaの保存済みオブジェクトAPIを通じて提供されるすべてのメトリックにも当てはまります。</p><h2>要件</h2><ul><li><a href="https://www.elastic.co/cloud">Elastic Cloud</a>または<a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed">セルフマネージド</a>クラスター（バージョン9.3を実行）</li><li><a href="https://www.elastic.co/docs/explore-analyze/workflows/get-started#workflows-prerequisites">ワークフローが有効</a>（詳細設定）</li></ul><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><p>何かを作る前に、どんなデータがあるのかを理解しましょう。Kibanaはほとんどの設定やメタデータを専用の内部インデックスに<a href="https://www.elastic.co/docs/api/doc/kibana/group/endpoint-saved-objects">保存済みオブジェクト</a>として保存しています。Kibanaがこの方法で追跡している項目の一つに、使用量カウンターと呼ばれる、特別な保存オブジェクトタイプを使ったダッシュボードの閲覧数があります。次のように、開発ツールから直接クエリできます。</p><pre><code>GET kbn:/api/saved_objects/_find?type=usage-counter&filter=usage-counter.attributes.domainId:"dashboard"%20and%20usage-counter.attributes.counterType:"viewed"&per_page=10000</code></pre><p>応答は下記のようになります。</p><pre><code>{
  "page": 1,
  "per_page": 10000,
  "total": 1,
  "saved_objects": [
    {
      "type": "usage-counter",
      "id": "dashboard:346f3c64-ebca-484d-9d57-ec600067d596:viewed:server:20260310",
      "attributes": {
        "domainId": "dashboard",
        "counterName": "346f3c64-ebca-484d-9d57-ec600067d596",
        "counterType": "viewed",
        "source": "server",
        "count": 1
      },
      ...
    }
  ]</code></pre><p><code>counterName</code>フィールドはダッシュボードIDであり、<code>count</code>はそのダッシュボードに対する当日中の累積閲覧数です。Kibanaは、1つのダッシュボードにつき1日に1つのカウンターオブジェクトを作成します。オブジェクトIDに日付サフィックス（...views:server:20260310）が表示されます。ユーザーがダッシュボードを開くにつれて、その数は一日を通して増加していきます。</p><p>この日常的なドキュメントモデルをインデックスで複製するのではなく、ワークフローの実行ごとに1つのドキュメントを作成します。各ドキュメントは、キャプチャの瞬間におけるその日のダッシュボードの累積ビュー数を記録します。</p><h2>ステップ2：宛先インデックスの作成</h2><p>ダッシュボードビューのスナップショットを格納するためのインデックスが必要です。次のコマンドは明示的なマッピングで作成し、後で集約や可視化ができるようにします。Dev Toolsで実行してください：</p><pre><code>PUT dashboard-views
{
  "mappings": {
    "properties": {
      "captured_at": {
        "type": "date"
      },
      "dashboard_id": {
        "type": "keyword"
      },
      "dashboard_name": {
        "type": "keyword"
      },
      "view_count": {
        "type": "integer"
      }
    }
  }
}</code></pre><p>IDと名前に<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/keyword"><code>keyword</code></a>マッピングを使用すると、<a href="https://www.elastic.co/docs/explore-analyze/query-filter/aggregations">アグリゲーション</a>が可能になります。<code>view_count</code>のために<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/number"><code>integer</code></a>を使用することは安全なデフォルト設定です。Kibanaはカウンターを毎日リセットするため、32ビットの制限（1日で20億回以上のビュー）に達することは現実的な懸念事項ではありません。数値演算も引き続きサポートしており、<a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-metrics-max-aggregation"><code>max</code></a>、<a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-metrics-avg-aggregation"><code>avg</code></a>、<a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-metrics-min-aggregation"><code>min</code></a>などが含まれます。</p><h2>ステップ3: ワークフローを作成する</h2><p><strong>Stack Management &gt; Workflows &gt; New Workflow</strong>に移動し、次のワークフローのYAML構成を貼り付けます。</p><pre><code>name: dashboard-views-ingestion
triggers:
  - type: scheduled
    with:
      every: 30m

steps:
  - name: fetch_dashboard_views
    type: kibana.request
    with:
      method: GET
      path: >-
        /api/saved_objects/_find?type=usage-counter&per_page=10000&filter=usage-counter.attributes.domainId:"dashboard"%20and%20usage-counter.attributes.counterType:"viewed"

  - name: index_each_dashboard
    type: foreach
    foreach: "{{ steps.fetch_dashboard_views.output.saved_objects }}"
    steps:
      - name: fetch_dashboard_name
        type: kibana.request
        with:
          method: GET
          path: /api/saved_objects/dashboard/{{ foreach.item.attributes.counterName }}
        on-failure:
          continue: true

      - name: index_doc
        type: elasticsearch.request
        with:
          method: POST
          path: /dashboard-views/_doc
          body:
            dashboard_id: "{{ foreach.item.attributes.counterName }}"
            dashboard_name: "{{ steps.fetch_dashboard_name.output.attributes.title }}"
            view_count: "${{ foreach.item.attributes.count | plus: 0 }}"
            captured_at: "{{ execution.startedAt | date: '%Y-%m-%dT%H:%M:%SZ' }}"</code></pre><p>次のセクションでは、ワークフローをステップごとに分解していきます。</p><h3>ワークフローの仕組み</h3><h4>トリガー</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5670991d65c64ee833924225c2d375a1be868b13-325x162.png" style="width: 100%;" /><p>ワークフローは30分ごとにスケジュールされたトリガーで実行されます。これにより、APIに負担をかけずに時系列データが得られます。</p><h4>ダッシュボードビューを取得</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/66eaec147c3d01c524c67cf1c7f663ac56a3259d-812x215.png" style="width: 100%;" /><p><code>kibana.request</code>を使ってKibanaの保存済みオブジェクトAPIを呼び出します。認証の設定は不要です。ワークフローエンジンは実行コンテキストに基づいて適切なヘッダーを自動的に添付します。</p><h4>index_each_dashboard (foreach)</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/aad45e8aed8dc81ded6260cd6199ff78dcffe3b4-1892x290.png" style="width: 100%;" /><p>前のステップで返された <a href="https://www.elastic.co/docs/api/doc/kibana/group/endpoint-saved-objects"><code>saved_objects</code></a> 配列を反復します。各反復の現在の項目は <code>foreach.item</code>として利用可能です。ループ内では、各ダッシュボードごとに2つの入れ子手順を実行します。</p><p><strong>1. <code>fetch_dashboard_name</code>：</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/db21ec5094b743018b9cd66c5052681f14c7d7e3-1999x431.png" style="width: 100%;" /><p>人間が読み取れるダッシュボードのタイトルを <code>GET /api/saved_objects/dashboard/{id}</code> を呼び出して解決します。<code>on-failure: continue: true</code> を追加することで、ダッシュボードが削除されてもビューカウンターが残っている場合、ループが継続して全体の実行が失敗しないようにします。</p><p><strong>2. <code>index_doc</code>：</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f49dd0c9f0817bb1e1e5d9f4a2b05d13ef331054-1999x626.png" style="width: 100%;" /><p>各ドキュメントを<code>POST /dashboard-views/_doc</code>（明示的なIDなし）でインデックス化することで、Elasticsearchが自動でIDを生成します。これにより、毎回の実行時に新しいドキュメントが作成され、以前のスナップショットを上書きするのではなく、時間の経過とともにビューカウントの履歴が構築されます。</p><p>次の2点にご注意ください。</p><ul><li><code>captured_at</code>フィールドは日付フィルターを使ってタイムスタンプを<a href="https://www.iso.org/iso-8601-date-and-time-format.html">ISO 8601</a>としてフォーマットします。それなしでは、値はJavaScriptの日付文字列として出力され（例：<code>Tue Mar 10 2026 05:03:47 GMT+0000</code>）、Elasticsearchは日付としてマップしません。</li><li><code>view_count</code>は数値型を保持するために<code>${{ }}</code>構文を<code>| plus: 0</code>と共に用います。<code>{{ }}</code>を使うと文字列としてレンダリングされ、ダッシュボードでの計算操作ができなくなります。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6d48c8784d5df6192e8b5175e69dbab5098194bc-919x774.png" style="width: 100%;" /><p><em>UIでは、各ワークフローステップを快適にデバッグできます。</em></p><h2>ステップ4：統計ダッシュボードを構築</h2><p>ワークフローが数回実行されてデータが収集されたら、dashboard-views Data viewを使用してKibanaで新しいダッシュボードを作成します。</p><p>まずは以下のパネルから始めましょう：</p><ul><li><strong>トップのダッシュボード（閲覧数別）：</strong> X軸に<code>dashboard_name</code>、Y軸に<code>last_value(view_count)</code>を持つ<a href="https://www.elastic.co/docs/explore-analyze/visualize/charts/bar-charts"><strong>棒グラフ</strong></a>を使用します。これはダッシュボードごとの現在の日次閲覧数を示しています。</li><li><strong>時間経過に伴うビュー：</strong>X軸に<code>captured_at</code>、Y軸に<code>last_value(view_count)</code>を用いた<a href="https://www.elastic.co/docs/explore-analyze/visualize/charts/line-charts"><strong>折れ線グラフ</strong></a>を使用し、<code>dashboard_name</code>で分類されます。各実行が新しいドキュメントを追加するため、重複を合計するのではなく、最後の値を使用して時間バケットごとのピークカウントを取得します。</li><li><strong>現在のスナップショット：</strong>すべてのダッシュボードの最新のビュー数を表示するには、最新の<code>captured_at</code> を含む<a href="https://www.elastic.co/docs/explore-analyze/visualize/charts/tables"><strong>データテーブル</strong></a>を使用します。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e245b95f67daf76a2aaf4cb9df2c75ef4cfef582-1462x747.png" style="width: 100%;" /><p>各ワークフローが新しいドキュメントを作成するため、時間範囲でフィルタリングして特定の期間のアクティビティを分析したり、週ごとの比較を行ったり、ダッシュボードのビューしきい値を下回ったときにアラートを設定したりすることができます。</p><h2><strong>まとめ</strong></h2><p>Elastic Workflowsは、ソース（Kibana API）と送信先（Elasticsearch）の両方がネイティブであるため、認証管理が全く必要なく、この種の定期的なデータ収集に適しています。ワークフローエンジンは<code>kibana.request</code>と<code>elasticsearch.request</code>のステップで認証を自動的に処理するため、記述するのはロジックだけです。</p><h2><strong>資料</strong></h2><ul><li><a href="https://www.elastic.co/docs/explore-analyze/workflows">Elastic Workflows</a></li><li><a href="https://www.elastic.co/docs/api/doc/kibana/">Kibana API</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[TSDSとILMが出会うとき：遅延データを拒否しない時系列データストリームの設計]]></title>
            <description><![CDATA[TSDSの時間制限はILMフェーズとどのように相互作用するのか、そして遅れて到着するメトリクスを許容するポリシーを設計する方法。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/tsds-ilm-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/tsds-ilm-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Bret Wortman]]></dc:creator>
            <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e4c46e4a6f746d9c845857e80de036f5d51cd4e7-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>最近、ある顧客のメトリクス・クラスタを「すべてホットティアに格納する」構成からHot/Cold/Frozenアーキテクチャに移行しました。これまでに何十回も実施してきた変更でした。数分のうちに、Logstashはデータの転送を完全に停止しました。</p><p>Elasticsearchは、遅れて到着するメトリクスを拒否していました。これらの拒否により、パイプラインが遅延し、結果としてより多くの遅延データが発生し、さらに多くの拒否を引き起こしました。最終的にパイプラインは完全に停止しました。</p><p>復旧のためには、スナップショットからの復元、データの再インデックス作成、データ取り込みパイプラインの再設計が必要でした。</p><p>根本的な原因はインデックスライフサイクル管理（ILM）自体ではなく、時系列データストリーム（TSDS）と、それらが時間的制約のあるバッキングインデックスを強制する方法にありました。</p><p>TSDSは指標のストレージ要件を40〜70％削減できますが、TSDSを効率的にするアーキテクチャの変更により、時間の経過とともにインデックスの動作も変わります。これらの変更は、ILMポリシーを設計する時、またはインジェストパイプラインで遅れて到着するデータが生成される可能性がある場合に重要です。</p><h2>TL;DR</h2><p>TSDSを使用する場合：</p><ul><li>バックアップインデックスは、特定の時間枠内でのみ文書を受け付けます。</li><li>遅延データがインデックスがColdまたはFrozen状態に移行した後に取り込まれた場合、Elasticsearchはそれらのドキュメントを拒否するか、設定されている場合は障害ストアにルーティングします。</li></ul><p>デザインルール：</p><pre><code>warm_min_age > rollover_max_age + maximum_expected_lateness</code></pre><h2>時系列データストリームとは何ですか？</h2><p><em>時系列データストリーム</em>（TSDS）は、メトリクスデータに最適化された特殊なデータストリームです。データは関連するドキュメントが同じシャード内に配置されるようにルーティングされ、クエリと検索のための最適化が行われます。Elasticsearchは以下の方法で行います：</p><p>各文書には以下が含まれます。</p><ul><li>1つのタイムスタンプ。</li><li>時系列を識別するディメンションフィールド。</li><li>測定値を表すメトリックフィールド。</li></ul><p>例：</p><ul><li>ホストあたりのCPU使用率。</li><li>サービスごとのリクエスト遅延。</li><li>センサーごとの温度測定値。</li></ul><p><em>ディメンション</em>は測定したい対象を特定し、<em>メトリクス</em>は時間とともに変化する値を表します。</p><h3>ディメンション</h3><p>ディメンションは測定対象を表します。</p><p>例：</p><pre><code>host.name
service.name
container.id</code></pre><p>それらをマッピングで次のように定義します：</p><pre><code>time_series_dimension: true</code></pre><h3>メトリクス</h3><p>メトリクスは数値を表し、以下によって定義されます。</p><pre><code>time_series_metric</code></pre><p>一般的なメトリクスの種類：</p><ul><li>ゲージ：増減する値。</li><li>カウンター：リセットされるまで増加する値。</li></ul><p>Elastic Agentは主にメトリクスおよびログデータを収集するため、TSDSのインデックスを手動で有効にしていなくても、クラスター内に存在している場合があります。</p><h3>_tsidフィールド</h3><p>Elasticsearchは内部的にディメンションフィールドから <code>_tsid</code> 値を生成します。これにより、同一のディメンションを持つドキュメントを同じシャードにルーティングすることができ、以下が改善されます：</p><ul><li>圧縮。</li><li>クエリのローカル環境。</li><li>アグリゲーションのパフォーマンス。</li></ul><h2>主な違い：時間制限付きバッキングインデックス</h2><p>従来のデータストリームは常に最新のバッキングインデックス（<em>書き込みインデックス</em>と呼ばれる）に書き込みますが、TSDSは異なる動作をします。</p><p>各TSDSバッキングインデックスには定義された時間ウィンドウがあり、そのウィンドウ内に収まる<code>@timestamp</code>値を持つドキュメントのみを受け入れます：</p><pre><code>GET _data_stream/my-metrics-data-stream


     "index_mode": "time_series",
     "time_series": {
       "temporal_ranges": [
         {
           "start": "2026-01-15T14:35:50.000Z",
           "end": "2026-03-16T11:34:40.000Z"
         }
       ]
     }</code></pre><p>ドキュメントがインデックス化されると、Elasticsearchはそれをタイムスタンプに関わるバッキングインデックスにルーティングします。これは、従来のインデックスとは異なり、TSDSが複数のバッキングインデックスに同時に書き込む可能性があることを意味します。</p><p>例：</p><ul><li>リアルタイムデータ → 最新のインデックス。</li><li>遅延データ → その期間をカバーする以前のインデックス。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/31af2bb3b3dc24db8342e791e1db77a44659ba7a-1589x502.png" style="width: 100%;" /><h2>遅延データに対応する設計方法</h2><p>実際のインジェストパイプラインは、ほとんどの場合、完全に期限内に指標を届けるわけではありません。メトリクスは、ネットワークの停止、途中のバックログ、バッチのインジェスト、およびエッジデバイスの損失（これらのデバイスは再接続し、キャッチアップを始めます）によって遅延する可能性があります。</p><p>従来のインデックスは、静かにその遅延を吸収しますが、TSDSはしません。</p><p>ドキュメントのタイムスタンプが書き込み可能なバッキングインデックスの範囲外にある場合、Elasticsearchはそれを拒否します。これは、ILMポリシーが遅延データを考慮する必要があることを意味します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/32a103b95b20e31615c214271e27811a7ee315ae-1999x691.png" style="width: 100%;" /><h2>重要な制約</h2><p>バッキングインデックスは遅延データを受け入れられるだけの期間、書き込み可能な状態を維持する必要があります。</p><p>実際には、これは次のことを意味します。</p><pre><code>time_until_readonly > maximum_expected_lateness</code></pre><p>ILMはロールオーバーからの時間経過を測定するため、運用ルールは次のようになります。</p><pre><code>warm_or_cold_min_age > rollover_max_age + maximum_expected_lateness</code></pre><p></p><p>たとえば、メトリクスが最大6時間遅延する可能性がある場合、インデックスはロールオーバー後少なくとも6時間は書き込み可能な状態を維持する必要があります。</p><p></p><p>この制約を考慮しなかったことが、前述のインジェスト障害の正確な原因でした。遅れて到着したデータは、既にColdティアにあり、そのため書き込みがブロックされていた以前のインデックスに向けられていました。</p><p></p><h2>拒否されたドキュメントの処理</h2><p>TSDS がドキュメントを拒否すると、Elasticsearch は、タイムスタンプが書き込み可能なインデックスの範囲内にないことを示すエラーを返します。インジェストパイプラインがそのエラーをどのように処理するかによって、データを失うか、インジェストが停止するかが決まります。</p><p>拒否されたドキュメントを処理する主要なメカニズムは、障害ストアです。</p><h3>障害ストア（Elasticsearch 9.1以降で推奨）</h3><p>Elasticsearch 9.1では、拒否されたドキュメントを自動的に格納するfailure storeが導入されました。Elasticsearchは、クライアントにエラーを返す代わりに、失敗したドキュメントをデータストリーム内の専用の障害インデックスに書き込みます。</p><p>障害の調査には以下の方法があります。</p><pre><code>GET metrics-myapp::failures/_search</code></pre><p>障害ストアを使用することで、拒否エラーによるインジェストパイプラインの停止を防ぎつつ、障害データを分析または<a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/reindex-tsds">再インデックス</a>のために保存します。</p><h2>拒否される問題の監視</h2><p>遅れて到着する問題は通常、最初にインジェスト異常として現れます。最初は、以下によって気付く場合があります：</p><ul><li>インデキシングレートの突然の低下。</li><li>拒否されたドキュメントの急増。</li><li>障害ストアエントリ数の増加。</li><li>パイプラインの入力数と出力数の不一致。</li></ul><p>これらの兆候に基づいて警告を発することで、オペレーターはパイプラインが停止する前に問題を検知することができます。ワークフロー、機械学習ジョブ、およびその他のメカニズムを使用して、検出と通知を自動化できます。</p><h2>TSDS + ILMの移行チェックリスト</h2><p>メトリクスクラスターをTSDSに移行する場合、ILM階層化を導入する場合、またはメトリクスがデフォルトでTSDSであるElasticsearchバージョンにアップグレードする場合は、まずこれらの項目を確認してください。</p><h3><strong>1. インジェストレイテンシの測定</strong></h3><p>ILMの方針を変更する前に、以下を決定してください。</p><ul><li>通常のインジェスト遅延。</li><li>最悪の場合、インシデント時の遅延。</li><li>バッチパイプラインによる遅延。</li></ul><p>ILM設計は、現実的な最大限の遅延に対応する必要があります。</p><h3><strong>2. インデックス付けの時間ウィンドウを検証する</strong></h3><p>TSDSのバッキングインデックスを調べます。</p><pre><code>GET _data_stream/<your-stream></code></pre><p>以下について探します：</p><ul><li><code>time_series.start_time</code></li><li><code>time_series.end_time</code></li></ul><p>これらの制限が、どのインデックスがドキュメントを受け入れられるかを決定します。これらのウィンドウを理解することで、データがどのくらい遅れていても拒否されないのかを判断できます。</p><h3><strong>3. 遅延データを考慮してHotティアのサイズを調整する</strong></h3><p>遅延データに対しても、バッキングインデックスが書き込める状態を保ちます。</p><p>運用ルール：</p><ul><li><code>warm_min_age &gt; rollover_max_age + maximum_expected_lateness</code></li></ul><p>メトリクスが6時間遅れて届く場合、インデックスは少なくとも6時間は書き込める状態を維持する必要があることに留意してください。</p><h3><strong>4. 拒否されたドキュメントの処理方法を決定する</strong></h3><p>TSDSを有効にする前に以下のストラテジーを選択します：</p><ul><li>障害ストア（Elasticsearch 9.1以降で推奨）。</li><li>Logstashのデッドレターキュー。</li><li>到着が遅れた場合のフォールバックインデックス。</li><li>限定的なデータ損失を受け入れる。</li></ul><h3><strong>5. インジェストの正常性を監視する</strong></h3><p>以下のアラートを追加します：</p><ul><li>インデキシングの速度低下。</li><li>却下された書類。</li><li>ストアの拡大失敗。</li><li>パイプラインの入力と出力の不一致。</li></ul><p>遅延データの問題は、多くの場合、インジェスト時の異常として最初に現れます。</p><h2>まとめ</h2><p>時系列データストリームは、メトリックワークロードに対して主要なストレージとパフォーマンスの改善を提供しますが、重要なアーキテクチャ上の変更も導入します。バッキングインデックスには時間的な制約があり、これがILMの動作に影響します。</p><p>TSDSを使用する場合：</p><ul><li>インデックスは、遅延データを受け入れられるだけの期間、書き込み可能な状態を維持する必要があります。</li><li>インジェストパイプラインは、拒否されたドキュメントを安全に処理する必要があります。</li></ul><p>留意する重要なルールは次の通りです：</p><pre><code>warm_min_age > rollover_max_age + maximum_expected_lateness</code></pre><p>その制約に基づいてILMポリシーを設計すると、TSDSはメトリクスのワークロードに非常に適しています。</p><p>無視した場合、インジェストパイプラインがその時間的制約によって機能不全に陥る可能性があります。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[LINQ to ES|QL：C#を記述してElasticsearchをクエリ]]></title>
            <description><![CDATA[Elasticsearch .NETクライアントに新しく追加されたLINQ to ES|QLプロバイダをご紹介します。C#コードを自動的にES|QLクエリに変換できます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/linq-esql-c-elasticsearch-net-client</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/linq-esql-c-elasticsearch-net-client</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Florian Bernd, Martijn Laarman]]></dc:creator>
            <pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e54132e915217063e9ed0ec45059c6cfc38e31dd-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><strong>v9.3.4</strong>および<strong>v8.19.18</strong>以降のElasticsearch .NETクライアントには、実行時にC# LINQ式を<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">Elasticsearchクエリ言語（ES|QL）クエリに変換する</a><a href="https://learn.microsoft.com/en-us/dotnet/csharp/linq/">Language Integrated</a> Query（LINQ）プロバイダーが含まれています。ES|QL文字列を手作業で記述する代わりに、 <code>Where</code>、 <code>Select</code>、 <code>OrderBy</code>、 <code>GroupBy</code>などの標準演算子を使用してクエリを構成します。このプロバイダーは、結果セットのサイズに関係なくメモリ使用量を一定に保つ行ごとのストリーミングを含め、変換、パラメータ化、結果の逆シリアル化を処理します。</p><h2>最初のクエリ</h2><p>まず、Elasticsearchインデックスにマップする普通のCLRオブジェクト（POCO）を定義します。プロパティ名は、標準的な<code>System.Text.Json</code>属性（<code>[JsonPropertyName]</code>など）または設定された<code>JsonNamingPolicy</code>を通じてES|QL列名に解決されます。クライアントの他の部分に適用される<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization">ソースシリアル化</a>ルールは、ここでも同様に適用されます。</p><pre><code>using System.Text.Json.Serialization;

public class Product
{
    [JsonPropertyName("product_id")]
    public string Id { get; set; }

    public string Name { get; set; }

    public string Brand { get; set; }

    [JsonPropertyName("price_usd")]
    public double Price { get; set; }

    [JsonPropertyName("in_stock")]
    public bool InStock { get; set; }
}</code></pre><p>型を指定すると、クエリは次のようになります。</p><pre><code>var minPrice = 100.0;
var brand = "TechCorp";

await foreach (var product in client.Esql.QueryAsync<Product>(q => q
    .From("products")
    .Where(p => p.InStock && p.Price >= minPrice && p.Brand == brand)
    .OrderByDescending(p => p.Price)
    .Take(10)))
{
    Console.WriteLine($"{product.Name}: ${product.Price}");
}</code></pre><p>プロバイダーはこれを次のES|QLに変換します。</p><pre><code>FROM products
| WHERE (in_stock == true AND price_usd >= ?minPrice AND brand == ?brand)
| SORT price_usd DESC
| LIMIT 10</code></pre><p>いくつか注意すべき点があります。</p><ul><li><strong>プロパティ名の解決：</strong> <code>p.Price</code>は<code>[JsonPropertyName]</code> 属性のため<code>price_usd</code>になり、<code>p.Brand</code>はデフォルトのcamelCase命名規則に従って <code>brand</code>になります。</li><li><strong>パラメーターのキャプチャ：</strong>C#変数 <code>minPrice</code>と<code>brand</code>は、名前付きパラメーター（<code>?minPrice</code>、<code>?brand</code>）としてキャプチャされます。これらはJSONペイロード内のクエリ文字列とは別に送信されるため、インジェクション攻撃を防ぎ、サーバー側のクエリプランのキャッシュを可能にします。</li><li><strong>ストリーミング：</strong><code>QueryAsync&lt;T&gt;</code>は<code>IAsyncEnumerable&lt;T&gt;</code>を返します。Elasticsearchからデータが到着すると、行は1つずつマテリアライズされます。</li></ul><p>また、実行せずに生成されたクエリとそのパラメーターを検査することもできます。</p><pre><code>var query = client.Esql.CreateQuery<Product>()
    .Where(p => p.InStock && p.Price >= minPrice && p.Brand == brand)
    .OrderByDescending(p => p.Price)
    .Take(10);

Console.WriteLine(query.ToEsqlString());
// FROM products | WHERE (in_stock == true AND price_usd >= 100) | SORT price_usd DESC | LIMIT 10

Console.WriteLine(query.ToEsqlString(inlineParameters: false));
// FROM products | WHERE (in_stock == true AND price_usd >= ?minPrice AND brand == ?brand) | SORT price_usd DESC | LIMIT 10

var parameters = query.GetParameters();
// { "minPrice": 100.0, "brand": "TechCorp" }</code></pre><h2>これはどのように機能するのでしょうか？LINQ の簡単なおさらい</h2><p>LINQプロバイダーを可能にするメカニズムは、<code>IEnumerable&lt;T&gt;</code>と<code>IQueryable&lt;T&gt;</code>の区別にあります。</p><p><code>.Where(p =&gt; p.Price &gt; 100)</code> を <code>IEnumerable&lt;T&gt;</code> 上で呼び出すと、ラムダは <code>Func&lt;Product, bool&gt;</code> にコンパイルされます。これは、ランタイムがインプロセスで実行する通常のデリゲートです。これはLINQ-to-Objectsです。</p><p>同じメソッドを <code>IQueryable&lt;T&gt;</code> で呼び出すと、C#コンパイラはラムダを <code>Expression&lt;Func&lt;Product, bool&gt;&gt;</code> でラップします。これは実行可能な形式ではなく、コードの<em>構造</em>を表すデータ構造です。式ツリーは実行時に検査、分析、および別の言語への変換を行うことができます。</p><pre><code>// IEnumerable: the lambda is a compiled delegate
IEnumerable<Product> local = products.Where(p => p.Price > 100);

// IQueryable: the lambda is an expression tree, a data structure
IQueryable<Product> remote = queryable.Where(p => p.Price > 100);</code></pre><p><code>IQueryProvider</code>インターフェースは拡張ポイントです。どのプロバイダーでも、これらの式ツリーをターゲット言語に変換するために <code>CreateQuery&lt;T&gt;</code> と <code>Execute&lt;T&gt;</code> を実装できます。Entity FrameworkはSQLを発行するためにこれを使用します。LINQからES|QLへのプロバイダーはこれをES|QLの生成に使用します。</p><p>上記のクエリの式ツリーは次のようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f864cd18a390831f8d28503a29b5835efb1842f7-1000x720.png" style="width: 100%;" /><p><em>例のクエリに対する式ツリー。</em></p><p>ツリーは内側から外側にネストされています。<code>Take</code>が <code>OrderByDescending</code>をラップし、これが<code>Where</code>をラップし、これが<code>From</code>, をルート定数<code>EsqlQueryable&lt;Product&gt;</code> をラップします。<code>Where</code>述語自体が<code>BinaryExpression</code>ノードのサブツリーであり、<code>&amp;&amp;</code>、<code>&gt;=</code>、および<code>==</code>演算子に対して<code>MemberExpression</code>リーフがプロパティアクセス用、<code>minPrice</code>および<code>brand</code>変数用のクロージャキャプチャ用に存在します。これは、プロバイダーが最終的なES|QLを生成するために使用するデータ構造です。</p><h2>内部構造：変換パイプライン</h2><p>LINQ式からクエリ結果までの経路は、6段階のパイプラインをたどります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2a2c772b63d720f61fc9a28b2f85668fa2db8d38-1999x1036.png" style="width: 100%;" /><p><em>データ変換パイプラインの概要。</em></p><h3>1. 式ツリーのキャプチャ</h3><p><code>.Where()</code>、<code>.OrderBy()</code>、<code>.Take()</code>などの演算子を<code>IQueryable&lt;T&gt;</code> に連鎖させると、標準のLINQインフラストラクチャーが式ツリーを構築します。<code>EsqlQueryable&lt;T&gt;</code> は<code>IQueryable&lt;T&gt;</code> を実装し、<code>EsqlQueryProvider</code> に委譲します。</p><h3>2. 変換</h3><p>クエリが実行されると（列挙、 <code>ToList()</code>の呼び出し、または<code>await foreach)</code>使用によって）、 <code>EsqlExpressionVisitor</code>は式ツリーを内側から外側へと走査します。各LINQメソッド呼び出しを専門のビジターに送信します。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ビジター</td><td style="border: 1px solid #ccc; padding: 4px;">翻訳します</td><td style="border: 1px solid #ccc; padding: 4px;">対象</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">WhereClauseVisitor</td><td style="border: 1px solid #ccc; padding: 4px;">.Where(predicate)</td><td style="border: 1px solid #ccc; padding: 4px;">WHERE 条件</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">SelectProjectionVisitor</td><td style="border: 1px solid #ccc; padding: 4px;">.Select(selector)</td><td style="border: 1px solid #ccc; padding: 4px;">EVAL + KEEP + RENAME</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">訪問者別にグループ化</td><td style="border: 1px solid #ccc; padding: 4px;">.GroupBy().Select()</td><td style="border: 1px solid #ccc; padding: 4px;">STATS ... BY</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OrderByVisitor</td><td style="border: 1px solid #ccc; padding: 4px;">.OrderBy() / .ThenBy()</td><td style="border: 1px solid #ccc; padding: 4px;">SORTフィールド [ASC\|DESC]</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">EsqlFunctionTranslator</td><td style="border: 1px solid #ccc; padding: 4px;">EsqlFunctions.*、Math.*、文字列メソッド</td><td style="border: 1px solid #ccc; padding: 4px;">80+ ES|QL関数</td>
                    </tr>
              </tbody>
            </table>
          <p>翻訳中、式で参照されるC#変数は名前付きパラメーターとしてキャプチャされます。</p><h3>3. クエリモデル</h3><p>ビジターは直接文字列を生成しません。代わりに、<code>QueryCommand</code>オブジェクト、すなわち不変の中間表現を生成します。<code>FromCommand</code>、<code>WhereCommand</code>、<code>SortCommand</code>、および<code>LimitCommand</code>の各々が、1つのES|QL処理コマンドを表しています。これらは<code>EsqlQuery</code>モデルに集められます。</p><p></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2adc349b6cf655b96b7b3e826a134e8a17fe42fd-1999x1036.png" style="width: 100%;" /><p><em>クエリモデルとコマンドパターン。</em></p><p>この中間モデルは、式ツリーと出力形式の両方から切り離されています。フォーマット前に検査、傍受（<code>IEsqlQueryInterceptor</code>経由）、または修正が可能です。</p><h3>4. フォーマット</h3><p><code>EsqlFormatter</code> 各<code>QueryCommand</code>を順番に訪問し、最終的なES|QL文字列を生成します。各コマンドは1行になり、ES|QLが処理コマンドを連鎖させるために使用するパイプ (|) 演算子で区切られます。特殊文字を含む識別子は自動的にバッククォートでエスケープされます。</p><h3>5. 実行</h3><p>フォーマットされたES|QL文字列とキャプチャされたパラメーターは、JSONペイロードとしてElasticsearchの<code>/_query</code>エンドポイントに送信されます。<code>IEsqlQueryExecutor</code>インターフェースはトランスポートレイヤーを抽象化し、ここで階層型パッケージアーキテクチャが登場します。</p><h3>6. マテリアライズ</h3><p><code>EsqlResponseReader</code> JSON応答をストリーム化し、結果セット全体をバッファリングせずに処理します。<code>ColumnLayout</code>ツリーは、1クエリにつき1回事前に計算され、フラットなES|QL列名（<code>address.street</code>、<code>address.city</code>など）をネストされたPOCOプロパティにマップします。各行は<code>T</code>インスタンスに組み立てられ、 <code>IEnumerable&lt;T&gt;</code> または <code>IAsyncEnumerable&lt;T&gt;</code>によって1行ずつ生成されます。</p><h2>レイヤーアーキテクチャ</h2><p>LINQ to ES|QL機能は、以下の3つのパッケージに分かれています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/41b8aae860ecdc2480edcb1c1d4cc9b03cfb78c9-1999x1036.png" style="width: 100%;" /><p><em>パッケージアーキテクチャー。</em><a href="https://www.nuget.org/packages/Elastic.Esql"><strong><code>Elastic.Esql</code></strong></a> は純粋な変換エンジンです。HTTPへの依存関係は一切なく、式ビジター、クエリモデル、フォーマッター、レスポンスリーダーが含まれています。スタンドアロンで使用すると、Elasticsearch接続がなくてもES|QLクエリを構築および検査できます。これは、テスト、クエリロギング、または独自の実行レイヤーの構築に役立ちます。</p><pre><code>// Translation-only: no Elasticsearch connection needed
var provider = new EsqlQueryProvider();
var query = new EsqlQueryable<Product>(provider)
    .From("products")
    .Where(p => p.InStock)
    .OrderByDescending(p => p.Price);

Console.WriteLine(query.ToEsqlString());
// FROM products | WHERE in_stock == true | SORT price_usd DESC</code></pre><p><a href="https://www.nuget.org/packages/Elastic.Clients.Esql"><strong><code>Elastic.Clients.Esql</code></strong></a> は軽量なスタンドアロンのES|QLクライアントです。<code>Elastic.Transport</code>を経由して<code>Elastic.Esql</code>上にHTTP実行を追加します。もしアプリケーションが他のElasticsearch APIではなく、ES|QLのみを必要とする場合、これが最小限の依存関係オプションです。</p><p><a href="https://www.nuget.org/packages/Elastic.Clients.Elasticsearch"><strong><code>Elastic.Clients.Elasticsearch</code></strong></a> は完全なElasticsearch .NETクライアントです。また、<code>Elastic.Esql</code> を基盤とし、<code>client.Esql</code>名前空間を通じてLINQプロバイダーを公開します。これはほとんどのアプリケーションで推奨されるエントリーポイントです。</p><p>どちらの実行層パッケージも、変換と転送をつなぐ戦略インターフェースである<code>IEsqlQueryExecutor</code>の独自の実装を提供します。</p><p>これら3つのパッケージはすべて、ソース生成の<code>JsonSerializerContext</code>と併用する場合、ネイティブAOTと互換性があります。完全なクライアントについては、<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization#native-aot">Native AOTのドキュメント</a>をご覧ください。</p><h2>基本を超えて</h2><p>上記の例では、フィルタリング、ソート、ページネーションについて説明しています。このプロバイダーはより幅広い操作をサポートしています。</p><h3>アグリゲーション</h3><p><code>GroupBySelect</code>の集約関数と組み合わせるとES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/stats-by"><code>STATS ... BY</code></a>に変換されます。</p><pre><code>var stats = client.Esql.Query<Product, object>(q => q
    .GroupBy(p => p.Brand)
    .Select(g => new
    {
        Brand = g.Key,
        Count = g.Count(),
        AvgPrice = g.Average(p => p.Price),
        MaxPrice = g.Max(p => p.Price)
    }));

// -> FROM products | STATS COUNT(*), AVG(price_usd), MAX(price_usd) BY brand</code></pre><h3>予測</h3><p><code>Select</code>匿名型を持つと、 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval"><code>EVAL</code></a>、 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/keep"><code>KEEP</code></a>、 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/rename"><code>RENAME</code></a> コマンドが生成されます。</p><pre><code>var query = client.Esql.CreateQuery<Product>()
    .Select(p => new { ProductName = p.Name, p.Price, p.InStock });

// -> FROM products | KEEP name, price_usd, in_stock | RENAME name AS ProductName</code></pre><h3>豊富な関数ライブラリ</h3><p>80以上のES|QL関数が <code>EsqlFunctions</code>クラスを通じて利用可能で、日付/時間、文字列、数学、IP、パターンマッチング、スコアリングをカバーしています。標準的な<code>Math.*</code>および<code>string.*</code>メソッドも変換されています。</p><pre><code>.Where(p => p.Name.Contains("Pro"))       // -> WHERE name LIKE "*Pro*"
.Where(p => EsqlFunctions.CidrMatch(      // -> WHERE CIDR_MATCH(ip, "10.0.0.0/8")
    p.IpAddress, "10.0.0.0/8"))</code></pre><h3>ルックアップ結合</h3><p>クロスインデックス検索はES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join"><code>LOOKUP JOIN</code></a>に変換されます。</p><pre><code>var enriched = client.Esql.Query<Product, object>(q => q
    .LookupJoin<Product, CategoryLookup, string, object>(
        "category-lookup-index",
        product => product.Id,
        category => category.CategoryId,
        (product, category) => new { product.Name, category!.CategoryLabel }));</code></pre><h3>未加工のES|QLエスケープハッチ</h3><p>LINQプロバイダーでまだサポートされていないES|QL機能については、生のフラグメントを追加できます。</p><pre><code>var results = client.Esql.Query<Product>(q => q
    .Where(p => p.InStock)
    .RawEsql("| EVAL discounted = price_usd * 0.9"));</code></pre><h3>サーバー側の非同期クエリ</h3><p>実行時間の長いクエリについては、サーバー上でバックグラウンド処理を行うように設定します。</p><pre><code>await using var asyncQuery = await client.Esql.SubmitAsyncQueryAsync<Product>(
    q => q.Where(p => p.InStock),
    asyncQueryOptions: new EsqlAsyncQueryOptions
    {
        WaitForCompletionTimeout = TimeSpan.FromSeconds(5),
        KeepAlive = TimeSpan.FromMinutes(10)
    });

await asyncQuery.WaitForCompletionAsync();
await foreach (var product in asyncQuery.AsAsyncEnumerable())
    Console.WriteLine(product.Name);</code></pre><p>サーバー側の非同期クエリは、通常のタイムアウトしきい値を超える可能性のある長時間実行される分析クエリや大規模データセットの処理、あるいはロードバランサー、APIゲートウェイ、プロキシなど、厳格なHTTPタイムアウトを強制するタイムアウトに敏感な環境で特に役立ちます。非同期クエリは、結果の取得から提出を切り離すことで接続切断を回避します。</p><h2>はじめに</h2><p>LINQ to ES|QLは次のバージョンから利用可能です。</p><ul><li><strong>Elastic.Clients.Elasticsearch v9.3.4</strong> (9.x ブランチ)</li><li><strong>Elastic.Clients.Elasticsearch v8.19.18</strong>（8.xブランチ）</li></ul><p>NuGetからのインストール：</p><p><code>dotnet add package Elastic.Clients.Elasticsearch</code></p><p>エントリーポイントは<code>client.Esql</code>にあります。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">メソッド</td><td style="border: 1px solid #ccc; padding: 4px;">戻り値</td><td style="border: 1px solid #ccc; padding: 4px;">ユースケース</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Query<T>(...)</td><td style="border: 1px solid #ccc; padding: 4px;">IEnumerable<T></td><td style="border: 1px solid #ccc; padding: 4px;">同期実行</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">QueryAsync<T>(...)</td><td style="border: 1px solid #ccc; padding: 4px;">IAsyncEnumerable<T></td><td style="border: 1px solid #ccc; padding: 4px;">非同期ストリーミング</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">CreateQuery<T>()</td><td style="border: 1px solid #ccc; padding: 4px;">IEsqlQueryable<T></td><td style="border: 1px solid #ccc; padding: 4px;">高度な構成と検査</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">SubmitAsyncQueryAsync<T>(...)</td><td style="border: 1px solid #ccc; padding: 4px;">EsqlAsyncQuery<T></td><td style="border: 1px solid #ccc; padding: 4px;">長時間実行されるサーバー側クエリ</td>
                    </tr>
              </tbody>
            </table>
          <p>クエリオプション、複数フィールドへのアクセス、ネストされたオブジェクト、複数値フィールドの処理など、機能の詳細については<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/linq-to-esql">LINQ to ES|QLのドキュメントを</a>参照してください。</p><h2>まとめ</h2><p>LINQ to ES|QLは、C# LINQの完全な表現力をElasticsearchのES|QLクエリ言語にもたらし、クエリ文字列を手作業で作成することなく、厳密に型付けされた構成可能なクエリを書くことができます。自動パラメーターキャプチャ、ストリーミングマテリアライゼーション、スタンドアロン変換から完全なElasticsearchクライアントまで拡張できる階層型パッケージアーキテクチャーにより、あらゆる規模の.NETアプリケーションに自然に適合します。最新のクライアントをインストールし、LINQ式をインデックスに向け、残りはプロバイダーに任せましょう。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[TypeScriptを使用したElasticsearch MCPサーバーの作成]]></title>
            <description><![CDATA[TypeScriptとClaude Desktopを使用してElasticsearch MCPサーバーを作成する方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elastic-mcp-server-typescript-claude</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elastic-mcp-server-typescript-claude</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
            <pubDate>Fri, 27 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0bb24c05fff391f42070c2883182ea6fe9cb9680-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearchで大規模なナレッジベースを扱う場合、情報を見つけるだけでは片手落ちです。エンジニアは複数の文書から結果を統合し、要約を作成し、回答を情報源にたどる必要があることが多いです。モデルコンテキストプロトコル（MCP）は、Elasticsearchと大規模言語モデル（LLM）アプリケーションを接続するための標準化された方法を提供します。ElasticはElastic Agent Builder（<a href="https://www.elastic.co/docs/solutions/search/agent-builder/mcp-server">MCPエンドポイントを</a>機能の一つに含む）のような公式ソリューションを提供していますが、カスタムMCPサーバーを構築することで、検索ロジック、結果のフォーマット、取得したコンテンツをLLMに渡して合成、要約、引用を行う方法などを完全に制御できます。</p><p>この記事では、カスタムElasticsearch MCPサーバーを構築するメリットを探り、ElasticsearchをLLM対応アプリケーションに接続するサーバーをTypeScriptで作成する方法を紹介します。</p><h2>カスタムのElasticsearch MCPサーバーを構築する理由</h2><p>Elasticは<a href="https://www.elastic.co/docs/solutions/search/mcp">MCPサーバー</a>のいくつかの代替手段を提供しています。</p><ul><li><a href="https://www.elastic.co/docs/solutions/search/agent-builder/mcp-server">Elasticsearch 9.2+用Elastic Agent Builder MCPサーバー</a></li><li><a href="https://github.com/elastic/mcp-server-elasticsearch?tab=readme-ov-file#elasticsearch-mcp-server">旧バージョン向けのElasticsearch MCPサーバー（Python）</a></li></ul><p>MCPサーバーとElasticsearchの連携方法をより細かく制御したい場合は、独自のカスタムサーバーを構築することで、ニーズに合わせて柔軟にカスタマイズできます。例えば、Agent BuilderのMCPエンドポイントはElasticsearchクエリ言語（ES|QL）クエリに限定されていますが、カスタムサーバーでは完全なクエリDSLを使用できます。また、LLMに渡される前に結果をどのようにフォーマットするかを制御でき、このチュートリアルで実装するOpenAIを利用した要約など、追加の処理ステップを統合することもできます。</p><p>この記事を読み終える頃には、Elasticsearchインデックスに保存されている情報を検索し、要約し、引用を提供するTypeScriptで記述されたMCPサーバーが完成しているでしょう。Elasticsearchを使用して情報を検索し、OpenAIの<code>gpt-4o-mini</code>モデルを用いて要約と引用を生成し、Claude DesktopをMCPクライアントおよびUIとして活用してユーザーのクエリを受け取り、応答を提供します。最終的には、エンジニアが組織内の技術文書全体からベストプラクティスを発見し、統合するのに役立つ内部ナレッジアシスタントが完成します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ec5771a874cf9740d4cac6888622cbe8cd6aede7-1999x1133.png" style="width: 100%;" /><h2>要件：</h2><ul><li>Node.js 20 +</li><li>Elasticsearch</li><li>OpenAI APIキー</li><li>Claude Desktop</li></ul><h3>MCPとは何ですか？</h3><p><a href="https://www.elastic.co/what-is/mcp">MCP</a>は<a href="https://www.anthropic.com/news/model-context-protocol">Anthropic</a>によって作成されたオープンスタンダードで、LLMとElasticsearchのような外部システムとの間で安全かつ双方向の接続を提供します。MCP の現状については<a href="https://www.elastic.co/search-labs/blog/mcp-current-state">この記事</a>で詳しく読むことができます。</p><p>MCPの環境は<a href="https://www.elastic.co/search-labs/blog/mcp-current-state#mcp-project-updates:-transport,-elicitation,-and-structured-tooling">日々進化</a>しており、多様なユースケースに対応したサーバーが利用可能です。さらに、この記事でご紹介するように、独自のカスタムMCPサーバーを簡単に構築することもできます。</p><h3>MCPクライアント</h3><p><a href="https://modelcontextprotocol.io/clients">利用可能なMCPクライアント</a>は多数あり、それぞれに特徴や制限があります。簡便性と普及度を考慮し、今回はMCPクライアントとして<a href="https://claude.ai/download">Claude Desktop</a>を使用します。これは、ユーザーが自然言語で質問できるチャットインターフェースとして機能し、MCPサーバーが公開しているツールを自動的に呼び出して、文書を検索し、要約を生成します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/66eb0b11473347b6cf2d85718251eeac38d6249d-1999x1491.png" style="width: 100%;" /><h2>Elasticsearch MCPサーバーの作成</h2><p><a href="https://github.com/modelcontextprotocol/typescript-sdk">TypeScript SDK</a>を使えば、ユーザーのクエリ入力に基づいてElasticsearchデータのクエリ方法を理解するサーバーを簡単に作成できます。</p><p>この記事では、Elasticsearch MCPサーバーとClaude Desktopクライアントを統合するための手順を説明します。</p><ol><li><a href="https://www.elastic.co/search-labs/blog/elastic-mcp-server-typescript-claude#configure-mcp-server-for-elasticsearch">Elasticsearch 用の MCP サーバーを設定してください。</a></li><li><a href="https://www.elastic.co/search-labs/blog/elastic-mcp-server-typescript-claude#load-the-mcp-server-into-claude-desktop">MCPサーバーをClaude Desktopにロード</a></li><li><a href="https://www.elastic.co/search-labs/blog/elastic-mcp-server-typescript-claude#test-it-out">試してみてください。</a></li></ol><h3>Elasticsearch MCPサーバーを設定してください</h3><p>まず、Nodeアプリケーションを初期化します。</p><pre><code>npm init -y</code></pre><p>これで <code>package.json</code>ファイルが作成され、このアプリケーションに必要な依存関係のインストールを開始できます。</p><pre><code>npm install @elastic/elasticsearch @modelcontextprotocol/sdk openai zod && npm install --save-dev ts-node @types/node typescript</code></pre><ul><li><strong>@elastic/elasticsearch</strong> はElasticsearchのNode.jsライブラリにアクセスするためのものです。</li><li><strong>@modelcontextprotocol/sdk</strong>は、MCPサーバーの作成と管理、ツールの登録、MCPクライアントとの通信処理を行うためのコアツールを提供します。</li><li><strong>openai</strong>は、OpenAIのモデルと対話し、要約や自然言語による対応を生成することができます。</li><li><strong><a href="https://zod.dev/">zod</a></strong>は、各ツールの入出力データの構造化スキーマの定義と検証に役立ちます。</li></ul><p><code>ts-node</code>、<code>@types/node</code>、 <code>typescript</code>は開発中にコードの入力やスクリプトのコンパイルに使用されます。</p><h4>データセットを設定</h4><p>Claude DesktopがMCPサーバーを使用してクエリできるデータを提供するために、<a href="https://github.com/Delacrobix/typescript-elasticsearch-mcp/blob/main/dataset.json">内部の模擬ナレッジベースデータセット</a>を使用します。このデータセットから作成される文書は以下のような形式になります。</p><pre><code>{
    "id": 5,
    "title": "Logging Standards for Microservices",
    "content": "Consistent logging across microservices helps with debugging and tracing. Use structured JSON logs and include request IDs and timestamps. Avoid logging sensitive information. Centralize logs in Elasticsearch or a similar system. Configure log rotation to prevent storage issues and ensure logs are searchable for at least 30 days.",
    "tags": ["logging", "microservices", "standards"]
}</code></pre><p>データを取り込むために、Elasticsearchにインデックスを作成し、そこにデータセットをロードするスクリプトを用意しました。<a href="https://github.com/Delacrobix/typescript-elasticsearch-mcp/blob/main/setup.ts">こちらで</a>ご覧いただけます。</p><h4>MCPサーバー</h4><p><a href="https://github.com/Delacrobix/typescript-elasticsearch-mcp/blob/main/index.ts"><code>index.ts</code></a>というファイルを作成し、依存関係をインポートして環境変数を処理するための以下のコードを追加します。</p><pre><code>// index.ts
import { z } from "zod";
import { Client } from "@elastic/elasticsearch";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import OpenAI from "openai";

const ELASTICSEARCH_ENDPOINT =
  process.env.ELASTICSEARCH_ENDPOINT ?? "http://localhost:9200";
const ELASTICSEARCH_API_KEY = process.env.ELASTICSEARCH_API_KEY ?? "";
const OPENAI_API_KEY = process.env.OPENAI_API_KEY ?? "";
const INDEX = "documents";</code></pre><p>また、ElasticsearchとOpenAIの呼び出しを処理するようにクライアントを初期化します。</p><pre><code>const openai = new OpenAI({
  apiKey: OPENAI_API_KEY,
});

const _client = new Client({
  node: ELASTICSEARCH_ENDPOINT,
  auth: {
    apiKey: ELASTICSEARCH_API_KEY,
  },
});</code></pre><p>実装をより堅牢にし、構造化された入出力を保証するために、 <a href="https://zod.dev/"><code>zod</code></a>を使用してスキーマを定義します。これにより、ランタイムでデータを検証し、エラーを早期に捕捉し、ツールの対応をプログラムで処理しやすくすることができます。</p><pre><code>const DocumentSchema = z.object({
  id: z.number(),
  title: z.string(),
  content: z.string(),
  tags: z.array(z.string()),
});

const SearchResultSchema = z.object({
  id: z.number(),
  title: z.string(),
  content: z.string(),
  tags: z.array(z.string()),
  score: z.number(),
});

type Document = z.infer<typeof DocumentSchema>;
type SearchResult = z.infer<typeof SearchResultSchema>;</code></pre><p>構造化出力の詳細については、<a href="https://www.elastic.co/search-labs/blog/structured-outputs-elasticsearch-guide">こちらを</a>ご覧ください。</p><p>それでは、MCPサーバーを初期化しましょう。</p><pre><code>const server = new McpServer({
  name: "Elasticsearch RAG MCP",
  description:
    "A RAG server using Elasticsearch. Provides tools for document search, result summarization, and source citation.",
  version: "1.0.0",
});</code></pre><h4>MCPツールの定義</h4><p>すべての設定が完了したので、MCPサーバーによって公開されるツールの作成を開始できます。このサーバーは2つのツールを公開します。</p><ul><li><strong><code>search_docs</code>：</strong>Elasticsearchで文書を全文検索で検索します。</li><li><strong><code>summarize_and_cite</code>：</strong>以前に取得した文書から情報を要約・統合し、ユーザーの質問に答えます。このツールは、出典となる文書を参照する引用も追加します。</li></ul><p>これらのツールを組み合わせることで、シンプルな「検索してから要約」ワークフローが構築されます。一方のツールが関連文書を取得し、もう一方のツールがその文書を使用して要約と引用を含む回答を生成します。</p><h4>ツールの応答形式</h4><p>各ツールは任意の入力パラメータを受け入れることができますが、以下の構造で応答する必要があります。</p><ul><li><strong>Content：</strong> これは非構造化形式でのツールの応答です。このフィールドは通常、テキスト、画像、音声、リンク、または埋め込みを返すために使用されます。この用途では、ツールによって生成された情報を含む整形済みテキストを返すために使用されます。</li><li><strong>structuredContent：</strong>これは、各ツールの結果を構造化された形式で提供するために使用されるオプションの戻り値です。これはプログラム上の目的に役立ちます。このMCPサーバーでは使用されていませんが、他のツールを開発したり、結果をプログラムで処理したりする場合に便利です。</li></ul><p>その構造を念頭に置いて、各ツールについて詳しく見ていきましょう。</p><h4>Search_docsツール</h4><p>このツールは、Elasticsearchインデックスで<a href="https://www.elastic.co/docs/solutions/search/full-text">全文検索</a>を実行し、ユーザークエリに基づいて最も関連性の高いドキュメントを取得します。主要な一致をハイライトし、関連性スコアを素早くまとめてくれます。</p><pre><code>server.registerTool(
  "search_docs",
  {
    title: "Search Documents",
    description:
      "Search for documents in Elasticsearch using full-text search. Returns the most relevant documents with their content, title, tags, and relevance score.",
    inputSchema: {
      query: z
        .string()
        .describe("The search query terms to find relevant documents"),
      max_results: z
        .number()
        .optional()
        .default(5)
        .describe("Maximum number of results to return"),
    },
    outputSchema: {
      results: z.array(SearchResultSchema),
      total: z.number(),
    },
  },
  async ({ query, max_results }) => {
    if (!query) {
      return {
        content: [
          {
            type: "text",
            text: "Query parameter is required",
          },
        ],
        isError: true,
      };
    }

    try {
      const response = await _client.search({
        index: INDEX,
        size: max_results,
        query: {
          bool: {
            must: [
              {
                multi_match: {
                  query: query,
                  fields: ["title^2", "content", "tags"],
                  fuzziness: "AUTO",
                },
              },
            ],
            should: [
              {
                match_phrase: {
                  title: {
                    query: query,
                    boost: 2,
                  },
                },
              },
            ],
          },
        },
        highlight: {
          fields: {
            title: {},
            content: {},
          },
        },
      });

      const results: SearchResult[] = response.hits.hits.map((hit: any) => {
        const source = hit._source as Document;

        return {
          id: source.id,
          title: source.title,
          content: source.content,
          tags: source.tags,
          score: hit._score ?? 0,
        };
      });

      const contentText = results
        .map(
          (r, i) =>
            `[${i + 1}] ${r.title} (score: ${r.score.toFixed(
              2,
            )})\n${r.content.substring(0, 200)}...`,
        )
        .join("\n\n");

      const totalHits =
        typeof response.hits.total === "number"
          ? response.hits.total
          : (response.hits.total?.value ?? 0);

      return {
        content: [
          {
            type: "text",
            text: `Found ${results.length} relevant documents:\n\n${contentText}`,
          },
        ],
        structuredContent: {
          results: results,
          total: totalHits,
        },
      };
    } catch (error: any) {
      console.log("Error during search:", error);

      return {
        content: [
          {
            type: "text",
            text: `Error searching documents: ${error.message}`,
          },
        ],
        isError: true,
      };
    }
  }
);</code></pre><p><em><code><a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-fuzzy-query">fuzziness</a>: “AUTO”</code> </em> は <em> 分析対象のトークンの長さに応じて誤字許容度を調整するように設定しています。また </em>、<em> タイトルフィールドで一致が発生したドキュメントのスコアを上げるtitle^2も設定しました。</em>。</p><h4>summarize_and_citeツール</h4><p>このツールは、前回の検索で取得したドキュメントに基づいて要約を生成します。OpenAIの <code>gpt-4o-mini</code>モデルを使用して、ユーザーの質問に答えるために最も関連性の高い情報を統合し、検索結果に直接基づく回答を提供します。要約に加えて、使用したソースドキュメントの引用情報（メタデータ）も返します。</p><pre><code>server.registerTool(
  "summarize_and_cite",
  {
    title: "Summarize and Cite",
    description:
      "Summarize the provided search results to answer a question and return citation metadata for the sources used.",
    inputSchema: {
      results: z
        .array(SearchResultSchema)
        .describe("Array of search results from search_docs"),
      question: z.string().describe("The question to answer"),
      max_length: z
        .number()
        .optional()
        .default(500)
        .describe("Maximum length of the summary in characters"),
      max_docs: z
        .number()
        .optional()
        .default(5)
        .describe("Maximum number of documents to include in the context"),
    },
    outputSchema: {
      summary: z.string(),
      sources_used: z.number(),
      citations: z.array(
        z.object({
          id: z.number(),
          title: z.string(),
          tags: z.array(z.string()),
          relevance_score: z.number(),
        })
      ),
    },
  },
  async ({ results, question, max_length, max_docs }) => {
    if (!results || results.length === 0 || !question) {
      return {
        content: [
          {
            type: "text",
            text: "Both results and question parameters are required, and results must not be empty",
          },
        ],
        isError: true,
      };
    }

    try {
      const used = results.slice(0, max_docs);

      const context = used
        .map(
          (r: SearchResult, i: number) =>
            `[Document ${i + 1}: ${r.title}]\\n${r.content}`
        )
        .join("\n\n---\n\n");

      // Generate summary with OpenAI
      const completion = await openai.chat.completions.create({
        model: "gpt-4o-mini",
        messages: [
          {
            role: "system",
            content:
              "You are a helpful assistant that answers questions based on provided documents. Synthesize information from the documents to answer the user's question accurately and concisely. If the documents don't contain relevant information, say so.",
          },
          {
            role: "user",
            content: `Question: ${question}\\n\\nRelevant Documents:\\n${context}`,
          },
        ],
        max_tokens: Math.min(Math.ceil(max_length / 4), 1000),
        temperature: 0.3,
      });

      const summaryText =
        completion.choices[0]?.message?.content ?? "No summary generated.";

      const citations = used.map((r: SearchResult) => ({
        id: r.id,
        title: r.title,
        tags: r.tags,
        relevance_score: r.score,
      }));

      const citationText = citations
        .map(
          (c: any, i: number) =>
            `[${i + 1}] ID: ${c.id}, Title: "${c.title}", Tags: ${c.tags.join(
              ", ",
            )}, Score: ${c.relevance_score.toFixed(2)}`,
        )
        .join("\n");

      const combinedText = `Summary:\\n\\n${summaryText}\\n\\nSources used (${citations.length}):\\n\\n${citationText}`;

      return {
        content: [
          {
            type: "text",
            text: combinedText,
          },
        ],
        structuredContent: {
          summary: summaryText,
          sources_used: citations.length,
          citations: citations,
        },
      };
    } catch (error: any) {
      return {
        content: [
          {
            type: "text",
            text: `Error generating summary and citations: ${error.message}`,
          },
        ],
        isError: true,
      };
    }
  }
);</code></pre><p>最後に、<a href="https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#stdio">stdio</a>を使用してサーバーを起動する必要があります。つまり、MCPクライアントは、標準の入出力ストリームを読み書きすることでサーバーと通信します。stdioは最もシンプルな転送オプションで、クライアントによってサブプロセスとして立ち上げられるローカルMCPサーバーに適しています。ファイルの最後に以下のコードを追加します。</p><pre><code>const transport = new StdioServerTransport();
server.connect(transport);</code></pre><p>次に、以下のコマンドを使用してプロジェクトをコンパイルします。</p><pre><code>npx tsc index.ts --target ES2022 --module node16 --moduleResolution node16 --outDir ./dist --strict --esModuleInterop</code></pre><p>これにより<code>dist</code>フォルダが作成され、その中に<code>index.js</code>ファイルが作成されます。</p><h3>MCPサーバーをClaude Desktopにロード</h3><p>Claude DesktopでMCPサーバーを設定するには、<a href="https://modelcontextprotocol.io/docs/develop/connect-local-servers">このガイド</a>に従ってください。Claudeの設定ファイルでは、以下の値を設定する必要があります:</p><pre><code>{
  "mcpServers": {
    "elasticsearch-rag-mcp": {
      "command": "node",
      "args": [   "/Users/user-name/app-dir/dist/index.js"
      ],
      "env": {
        "ELASTICSEARCH_ENDPOINT": "your-endpoint-here",
        "ELASTICSEARCH_API_KEY": "your-api-key-here",
        "OPENAI_API_KEY": "your-openai-key-here"
      }
    }
  }
}</code></pre><p><code>args</code>値は、 <code>dist</code>フォルダ内のコンパイル済みファイルを指す必要があります。また、設定ファイル内の環境変数も、コード内で定義されているものと全く同じ名前で設定する必要があります。</p><h3>試してみる</h3><p>各ツールを実行する前に、「<strong>検索とツール</strong>」をクリックして、ツールが有効になっていることを確認します。ここでは、それぞれを有効または無効にすることもできます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/172981c2a54adabc70d5819013c3007670935605-1999x1002.png" style="width: 100%;" /><p>最後に、Claude DesktopのチャットからMCPサーバーをテストして、質問を始めましょう。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/03654c0f8c53c714f801fba8b25747071179209b-1999x1353.png" style="width: 100%;" /><p>「<strong>認証方法とRBACに関する文書を検索する</strong>」という質問に対して、<code>search_docs</code> ツールが実行され、以下の結果が返されます。</p><pre><code>Most Relevant Documents:
Access Control and Role Management (highest relevance) - This document covers role-based access control (RBAC) principles, including ensuring users only have necessary permissions, regular auditing of user roles, revoking inactive accounts, and implementing just-in-time access for sensitive operations.
User Authentication with OAuth 2.0 - This document explains OAuth 2.0 authentication, which enables secure delegated access without credential sharing. It covers configuring identity providers, token management with limited scope and lifetime, and secure storage of refresh tokens.
Container Security Guidelines - While primarily about container security, this document touches on access control aspects like running containers as non-root users and avoiding embedded credentials.
Incident Response Playbook - This mentions role assignment during incidents (incident commander, communications lead, etc.), which relates to access control in emergency scenarios.
Logging Standards for Microservices - This document includes guidance on avoiding logging sensitive information, which is relevant to authentication security.</code></pre><p>回答は「素晴らしい！認証方法とロールベースのアクセス制御に関する関連文書を5件見つけました。見つかった内容は以下のとおりです。」</p><p>ツール呼び出しは、対応ペイロードの一部としてソース文書を返します。これらの文書は、後に引用を生成するために使用されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/25ff4c7e9563d99752f95540dafdc7fd211a66e3-800x530.gif" style="width: 100%;" /><p>また、複数のツールを一度のインタラクションで連結することも可能です。この場合、Claude Desktopはユーザーの質問を分析し、まず<code>search_docs</code>を呼び出して関連文書を取得し、次にその結果を<code>summarize_and_cite</code>に渡して最終的な回答を生成する必要があると判断します。これらすべては、ユーザーからの個別のプロンプトを必要とせずに実行されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e6a8a46acb4236e77058f18bcd2f0737b5882c05-1999x1101.png" style="width: 100%;" /><p>この場合、「<strong>システム全体の認証とアクセス制御を改善するための主な推奨事項は何ですか？参考文献を含めてください。</strong>」というクエリに対して、以下の結果が得られました。</p><pre><code>Based on the documentation, here are the main recommendations to improve authentication and access control across your systems:

Key Recommendations

1. Implement Role-Based Access Control (RBAC)
Ensure users have only the permissions necessary for their job functions. This principle of least privilege minimizes security risks by limiting access to what's truly needed. [1]

2. Regular Access Audits
Conduct regular audits of user roles and promptly revoke access for inactive accounts. This helps prevent unauthorized access from dormant or compromised accounts. [1]

3. Just-in-Time (JIT) Access
Implement just-in-time access for sensitive operations, where permissions are granted only when needed and for a limited duration. This reduces the attack surface by minimizing standing privileges. [1]

4. OAuth 2.0 for Secure Authentication
Use OAuth 2.0 to enable secure delegated access without sharing user credentials. This protocol allows third-party applications to access resources on behalf of users without exposing passwords. [2]

5. Token Security and Management
Configure your identity provider to issue tokens with limited scope and lifetime

Store refresh tokens securely
Validate access tokens consistently to maintain security [2]
References

Access Control and Role Management (Tags: security, access-control)
User Authentication with OAuth 2.0 (Tags: authentication, oauth)
These recommendations work together to create a defense-in-depth approach, where multiple security layers protect your systems from unauthorized access.</code></pre><p>前のステップと同様に、この質問に対する各ツールの回答を確認できます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cb606d356b2f7d5e4878a5eff71bc881869ac0ee-800x585.gif" style="width: 100%;" /><p><em>注：各ツールの使用を承認するかを確認するサブメニューが表示された場合は、<strong>「常に許可」</strong>または<strong>「一度だけ許可」</strong>を選択します。</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/aea942ba9b0037526ea215bec65690f1a5c3099c-1522x250.png" style="width: 100%;" /><h2>まとめ</h2><p>MCPサーバーは、ローカルとリモートの両方のアプリケーションのLLMツールの標準化に向けた重要な一歩です。完全な互換性の実現にはまだ取り組んでいますが、その方向へ急速に進んでいます。</p><p>この記事では、ElasticsearchをLLM搭載アプリケーションに接続するカスタムMCPサーバーをTypeScriptで構築する方法を学びました。当サーバーは2つのツールを提供しています。1つはQuery DSLを使用して関連文書を取得するためのツール<code>search_docs</code>、もう1つはOpenAIモデルとクライアントUIとしてのClaude Desktopを使用して引用付きの要約を生成するためのツール<code>summarize_and_cite</code>です。</p><p>異なるクライアントとサーバープロバイダー間の互換性の将来は有望に見えます。次のステップは、エージェントにより多くの機能と柔軟性を加えることです。実用的な<a href="https://www.elastic.co/search-labs/blog/llm-functions-elasticsearch-intelligent-query">記事</a>で、検索テンプレートを使用してクエリをパラメーター化し、精度と柔軟性を得る方法を学ぶことができます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Kibanaのダッシュボードに読み取り専用権限を追加]]></title>
            <description><![CDATA[Kibanaに読み取り専用のダッシュボードを導入し、ダッシュボード作成者に詳細な共有制御を提供して、結果の正確性を保ち、不要な変更から保護します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/kibana-dashboards-read-only-permissions</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/kibana-dashboards-read-only-permissions</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Fabio Busatto, Teresa Alvarez Soler]]></dc:creator>
            <pubDate>Thu, 26 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/11e578bc317aea30d2e10ccc0334a532f6af2ef9-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>こんな経験はありませんか。ログを監視するための完璧なダッシュボードを作成するのに1時間ほど費やし、すべてのグラフ、すべてのフィルター、すべてのラベルを設定します。ダッシュボードをチームと共有します。数日後、開いてみると何かがおかしいようです。同僚がクエリを微調整したか、誰かが日付範囲を変えたのかもしれません。よかれと思ってのことかもしれませんが、加えられた修正をひとつひとつ調べることになり、すべての数値が怪しく思えてきます。実によくある話です。</p><p>そこで、Elasticは<strong>読み取り専用のダッシュボード</strong>を開発しました。求めていたコントロールが獲得でき、安心してダッシュボードを共有できます。編集アクセス権を持つ別の人が変更したり壊したりすることを心配する必要はありません。</p><p>注：読み取り専用の権限は、Elastic Cloud ServerlessおよびElastic Cloud Hosted、Elastic Self-Managedのバージョン9.3以降で利用可能です。</p><h2>「すべてのユーザーが編集可能」権限が妨げになる場合</h2><p>Kibanaにおいて、<em>共有</em>は通常、スペースレベルの権限を意味していました。誰かがスペースでダッシュボードを作成できる場合、他の人のダッシュボードも編集または削除できます。コラボレーションにとっては便利ですが、場合によってはそうとも言い切れません。たった一度の意図しない編集が、誤った判断、信頼の喪失、そして多大な後始末へと連鎖的に影響を及ぼす可能性があります。</p><p><strong>ダッシュボード名に「read-only」と入れて皆が気づくことを期待する</strong>とか、<strong>タグを付けてうまくいくことを祈る</strong>といった回避策もあるにはありますが、期待と権限モデルはイコールではありません。必要なのは、スペースから全員を締め出すことなくダッシュボードをロックするための現実的な手段でした。</p><h2>問題の実例</h2><p>DebとKevinはどちらも、オペレーションスペース内のログ監視ダッシュボードへの編集権限を持っています。Kevinがチャートにいくつか変更を加えます。Debがダッシュボードに戻ると、数字は彼女が提示したものと一致しませんでした。彼女は（多くの場合記憶を頼りに）何が変更されたのかを突き止め、それを修正し、どれだけの報告書が誤ったデータで送信されたのかを考えなければならなくなります。</p><h2>読み取り専用ダッシュボード：理にかなった所有権と制御</h2><p>読み取り専用のダッシュボードでは、他のユーザーがダッシュボードを編集できるかどうかを管理できます。ダッシュボードを共有する際、<strong>編集</strong>（デフォルト：従来どおり）または<strong>閲覧</strong>を選択します。<strong>閲覧</strong>モードでは、あなた（とKibana管理者）のみが変更または削除できます。他のユーザーは開いて利用できますが、変更することはできません。</p><h3>手に入るもの</h3><ul><li><strong>ダッシュボードの整合性：閲覧</strong>モードでは、スペースで編集アクセス権を持つ他のユーザーはダッシュボードを変更または削除できません。操作を試みると、ロックされているというメッセージが表示されます。グラフとロジックは設定した状態のまま維持されます。</li><li><strong>コントロールを維持：</strong>コントロールは所有者が維持し、いつでも編集、改良、更新が可能です。閲覧専用として共有しても、自分がアクセスできなくなるわけではありません。他の全員が表示できるバージョンが固定されるだけです。</li><li><strong>柔軟なライフサイクル：</strong> ダッシュボードはいつでも「編集可能」に戻すことができます。また、Kibanaの管理者は引き続きすべてのダッシュボードを管理できます（所有者が退職した場合など）。行き止まりになることはありません。</li></ul><p>最終決定済みの、業務上極めて重要なダッシュボードを広く共有しても、その一貫性が維持されることが保証されます。これは、Serverlessを含む<strong>すべてのElasticのティアとサービス</strong>で利用可能です。</p><h3>役割と可能な操作</h3><p>クイックリファレンス（役割別）：</p><ul><li><strong>ダッシュボードの所有者：</strong>作成者として、完全な編集権限があります。</li><li><strong>Kibana管理者：</strong>すべてのダッシュボードを管理できます。</li><li><strong>スペース編集権限を持つユーザー：</strong>ダッシュボードの作成と編集が可能ですが、閲覧専用のダッシュボードの編集や削除はできません。</li><li><strong>スペース閲覧権限を持つユーザー：</strong>ダッシュボードの表示と一覧表示のみが可能です。</li></ul>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">操作</td><td style="border: 1px solid #ccc; padding: 4px;">ダッシュボード所有者</td><td style="border: 1px solid #ccc; padding: 4px;">Kibana管理者</td><td style="border: 1px solid #ccc; padding: 4px;">スペース編集権限を持つユーザー</td><td style="border: 1px solid #ccc; padding: 4px;">スペース閲覧権限を持つユーザー</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ダッシュボードの一覧表示と表示</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">新規ダッシュボードの作成</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✘</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">編集可能なダッシュボードの変更/削除</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✘</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">読み込み専用ダッシュボードの変更/削除</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✔</td><td style="border: 1px solid #ccc; padding: 4px;">✘</td><td style="border: 1px solid #ccc; padding: 4px;">✘</td>
                    </tr>
              </tbody>
            </table>
          <h2>読み取り専用にする方法</h2><p>新しいダッシュボードを保存する際、または後で共有メニューから「閲覧のみ」に設定できます。</p><h3>新しいダッシュボードを保存する際</h3><ul><li>ダッシュボードを作成し、「<strong>保存</strong>」をクリックします。</li><li>「新しいダッシュボードとして保存」モーダルで、「<strong>権限</strong>」を探します。</li><li><strong>「編集可能」</strong>から<strong>「閲覧可能」</strong>に変更します。</li><li><strong>［保存］</strong>をクリックします。これで完了です。他のユーザーにとっては閲覧専用となります。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/42a71d1bb55f9d50bd079f53bf45a0e1999b27f7-1214x1306.png" style="width: 100%;" /><h2>既に所有しているダッシュボードの場合</h2><ul><li>ダッシュボードを開いてください。</li><li>「<strong>ダッシュボードを共有</strong>」メニューを開きます。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e8405938557c879b1d4c262b98cf5a7f66408c04-1246x264.png" style="width: 100%;" /><ul><li>共有モーダルで、「<strong>権限</strong>」を見つけて「<strong>閲覧可能</strong>」に切り替えます。変更はすぐに適用されます。そのスペースの他のユーザーは、編集や削除ができなくなります。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/849405bc32701f3ebe0def012d8ae3cf3813ea0a-996x750.png" style="width: 100%;" /><ul><li><strong>共有</strong>アクションにマウスを合わせると、特定のダッシュボードが持つ権限の種類を確認できます。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/80541ddb1b1bc567b0aeff693944ea8b6871d6a7-1270x320.png" style="width: 100%;" /><h3>どのダッシュボードがロックされているかを確認する</h3><p>メインのダッシュボードリストでは、編集または削除できないダッシュボードの選択用チェックボックスは無効化されています。これにより、閲覧専用の項目を簡単に見分けられます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/24b2eba08df86174db949c662e7886c5aea1b460-1999x876.png" style="width: 100%;" /><p>ダッシュボードでは、編集アクションも無効になっており、ツールチップが表示され、ダッシュボードが閲覧専用に設定されていることが説明されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e0a2f9da6dc854e876fc6dc2a7c3ef8b313b52ef-1358x330.png" style="width: 100%;" /><h2>試してみる</h2><p>読み取り専用ダッシュボードが利用可能になりました。ダッシュボードを作成し、「<strong>閲覧可能</strong>」に切り替えて共有します。チームは信頼できる唯一の情報源を得ることができ、あなたは安心感を得られます。タイトルに「編集しないでください」という文言を入れる必要はもうありません。</p><p>読み取り専用ダッシュボードをどのように活用されているか、ぜひお聞かせください。<a href="https://discuss.elastic.co">コミュニティフォーラム</a>でご意見をお聞かせください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[シェルツールはコンテキストエンジニアリングの万能薬ではありません]]></title>
            <description><![CDATA[コンテキストエンジニアリングに利用できるコンテキスト検索ツールにはどのようなものがあるのか、それらがどのように機能するのか、そしてそれぞれのトレードオフについて学びましょう。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/search-tools-context-engineering</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/search-tools-context-engineering</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Leonie Monigatti]]></dc:creator>
            <pubDate>Wed, 25 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f91e4d07915ba7bf3b7abf15fac8fab3350f7df2-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>エージェントが備える最も重要なツールは、自前でコンテキストを構築するために使用できる検索ツールです。<a href="https://www.llamaindex.ai/blog/files-are-all-you-need">LlamaIndex</a>と<a href="https://x.com/hwchase17/status/2011814697889316930">LangChain</a>による最近の投稿により、<em>エージェントがコンテキストエンジニアリングに必要なのは、シェルツールとファイルシステムだけなのか</em>という議論が巻き起こりました。残念ながら、この議論はすぐにファイルシステム対データベースという間違った方向に逸れてしまいました。</p><p>この投稿は、<em>エージェントが独自のコンテキストを構築するために必要な、適切な検索インターフェースとは何か</em>という問いに改めて焦点を当てるものです。まず、シェルツールと専用データベースツールのトレードオフについて論じます。それを基に、エージェントのニーズに合った適切なインターフェースを見つけるための実用的なフレームワークを提示します。</p><h2>エージェントにとって「コンテキスト構築」が具体的に意味するもの</h2><p>初期の<a href="https://www.elastic.co/what-is/retrieval-augmented-generation">Retrieval-Augmented Generation（RAG）パイプライン</a>では、開発者が肯定的な検索パイプラインを設計し、大規模言語モデル（LLM）はコンテキストを受動的に受け取るだけの存在でした。そしてこれが、根本的な制限となっていました。コンテキストは、必要かどうかにかかわらず、すべてのクエリで取得され、それが実際に役立つかどうかも確認されなかったのです。</p><p>エージェント型RAGへの移行により、エージェントは独自のコンテキストを構築するための一連の検索ツールを利用できるようになりました。たとえば、Claude Code [1]とCursor [2]は、タスクの必要に応じて、エージェントが異なる検索ツールを選択し、さらにはそれらを組み合わせてクエリーを連鎖的に実行することもできます。</p><h2>コンテキストエンジニアリングに用いる検索インターフェースの種類</h2><p>コンテキストは、ウェブ上、ローカルファイルシステム、データベースなど、さまざまな場所に存在します。エージェントは、これらのコンテキスト外の各データソースと、次のようなツールを通じてやり取りできます。</p><ul><li><strong>シェルツール</strong>はシェルコマンドを実行でき、ローカルファイルシステムにアクセスできます。組み込みシェルツールの例としては、<a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/bash-tool">Claude APIのbashツール</a>、<a href="https://docs.openclaw.ai/tools/exec">OpenClawのExecツール</a>、<a href="https://docs.langchain.com/oss/python/integrations/tools/bash">LangChainのシェルツール</a>があります。</li><li><strong>専用のデータベースツール、</strong>たとえばモデルコンテキストプロトコル（MCP）サーバー（例：<a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/mcp-server">Elastic Agent Builder MCPサーバー）</a>やカスタムツール（例：<code>run_esql(query)</code>、<code>db_list_index()</code>）などは、データベースをクエリできます。</li><li><strong>専用のファイル検索ツール</strong>は、ローカル（またはアップロードされた）ファイルを検索し、読み取ることができます（ただし、シェルに完全にアクセスする権限はありません）。組み込みファイル検索ツールの例としては、 <a href="https://ai.google.dev/gemini-api/docs/file-search">Gemini APIのファイル検索ツール</a> や <a href="https://developers.openai.com/api/docs/guides/tools-file-search">OpenAIのファイル検索ツール</a>があります。</li><li><strong>ウェブ検索ツール</strong>は、ウェブから情報を取得できます。</li><li><strong>メモリツール</strong>は、長期記憶に保存し、長期記憶から呼び出します（保存方法は任意です）。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/115f20c8ded259e508f51524b2c06bdc702d70ab-1999x1050.png" style="width: 100%;" /><p>ご覧のとおり、シェルツールは用途が多彩で、以下のようなさまざまなデータソースからコンテキストを取得するために使用できます。</p><ul><li><strong>ファイルシステム：</strong>エージェントはディレクトリ構造を探索（ls、find）、関連コンテンツを検索（grep、cat）し、十分なコンテキストが構築されるまで繰り返します。</li><li><strong>データベース：</strong>エージェントは、データベース用のコマンドラインインターフェース（CLI）ツール（例：<a href="https://www.elastic.co/docs/reference/query-languages/sql/sql-cli"><code>elasticsearch-sql-cli</code></a>）を使用したり、curl経由でのHTTP APIを呼び出たり、スクリプトを実行したりできます。これは、再利用可能な文書化した例をエージェントのコンテキストに挿入して、ツールを適切に使用できるようにするようなエージェントスキル（例：<a href="https://github.com/elastic/agent-skills">Elastic Agent Skills for Elasticsearch</a>）と組み合わせると特に役立ちます。</li><li><strong>ウェブ：</strong>エージェントは、検索プロバイダーのAPIを通じてcurlコマンドでウェブ検索を実行できます。</li></ul><p>ただし、シェルツールはシステムに直接アクセスできるため、隔離されたサンドボックス環境での実行や、実行されたすべてのコマンドのログを取得するなどの安全対策が必要です。</p><h2>いつ、どの検索インターフェースを使用すべきか</h2><p>最適な検索インターフェースは、データ、クエリパターン、ユースケースによって異なります。このセクションは、実際に取りかかる際の出発点として役立ちます。</p><h3>ファイルシステムによってデータベースが時代遅れになったわけではない</h3><p>ファイルシステムかデータベースかという議論は、ストレージ層自体の話ではありません。たとえば、LangChainの説明によると、<a href="https://x.com/hwchase17/status/2011814697889316930">LangChainのメモリシステム</a>では、実のところファイルシステムにメモリを保存しているわけではありません。代わりに、メモリをデータベースに格納し、それをエージェントに対して一連のファイルとして<em>提示</em>します[3]。</p><p>ファイルシステムは、コーディングエージェントなど、ファイルを中心としたユースケースに最適です。また、一時的なスクラッチパッドや作業メモリとしても、同時実行が問題とならない単一ユーザーや単一エージェントのシナリオにも適しています。こうした場合、物理的なファイルシステム、またはデータをファイルシステムとして提示する方法により、目的に合わせてインターフェースを構築する前の段階において柔軟性を確保できます。</p><p>しかし、ファイルシステムストレージには、同時実行性、手動によるスキーマ適用、アトミックトランザクションなどの弱点があります。これらは、アプリケーションをスケールしたり、マルチエージェントシナリオに移行したりする必要がある場合に、いっそう顕著になります。これらの弱点を無視するならば、本番用データベースがすでに備えているような、トランザクションの安全性やアクセス制御を支える何十年もの技術的蓄積のない、<a href="https://dx.tips/oops-database">劣化版のデータベースを苦労して再発明する</a>羽目に陥ります。さらに、ほとんどのエンタープライズ環境では、データベースを使用するかどうかを選ぶ余地はありません。なぜなら、ビジネスクリティカルなデータを格納するデータベースはすでに存在しているからです。</p><h3>シェルツール+ファイルシステム</h3><p>ファイルシステム検索の出発点として、シェルツールは最適な選択肢です。現在、この分野において多くの発展を牽引しているのは、コーディングエージェントです。コーディングエージェントはローカルファイル内のコードを扱うため、必然的にファイル依存度の高いユースケースとなります。したがって、LLMはコーディングタスク用に、トレーニング後の段階で微調整されます。そのため、多くのLLMはコードを書くことだけでなく、シェルコマンドの使用やファイルシステム内の移動も得意です。</p><p><code>ls</code>や<code>grep</code>のような、組み込みCLIを備えたシェルツールでファイルを検索することは効果的です。grepでは「<code>matplotlib</code>をインポートしているすべてのファイルを検索」といったクエリを、高速かつ高精度に、しかも低コストで実行できます。しかし、エージェントが「アプリは失敗した認証をどのように処理しているか」といった概念的なクエリを処理する必要がある場合、grepによるパターンマッチングはすぐに限界に突き当たります。このギャップを埋めるために、<a href="https://github.com/jina-ai/jina-grep-cli"><code>jina-grep</code></a>のような、コマンドラインにセマンティック検索機能をもたらす代替手段もいくつか登場しています。</p><p>ただしgrepと、その代替手段であるセマンティック検索の多くは、コーパス全体に対してO(n)で実行されます。コードベースを対象とするユースケースなら、これで問題ないかもしれません。しかし、データが増えるとレイテンシーが目立つようになります。この場合、パフォーマンスを維持するにはインデックス化されたデータストアが必要となります。</p><h3>シェルツール+データベース</h3><p>セマンティック検索やハイブリッド検索など、データに対する検索機能を追加する別の方法は、たとえばCursorのように、機能をデータベースに格納することです。さらに、データに複雑なリレーショナル結合や集計が必要な場合、データベースインターフェースは不可欠です。</p><p>データがファイルシステム上ではなくデータベース内にある場合、特定のユースケースでは、シェルツールを軽量なデータベースインターフェースとして利用できます。クエリがCLIやcurlコマンドで十分に実行できるほどシンプルな場合、専用のデータベースツールを使うとかえって不要な複雑さが増えることがあります。</p><p>このアプローチは、エージェントが実際にどのようなクエリパターンを生成するかまだわからない、初期の探索段階にも適しています。この場合、Agent Skillsは、目的に合わせて構築されたツールに頼ることなく、正しくクエリを実行するための十分な構造を提供できます。ただし、反復的なタスクについてデータベースへの適切なクエリを見つけ出すためにエージェントが何度も試行錯誤しなければならない場合、インターフェースとしてシェルツールを使うことによって生じる、トークンオーバーヘッドによるデメリットが、追加のツールを避けられるという単純性のメリットを上回ってしまいます。</p><h3>専用のデータベースツール</h3><p>特に、繰り返し現れるクエリパターンが構造化されていたり分析的なものであったりする場合は、専用のデータベースツールが必要になります。<a href="https://vercel.com/blog/testing-if-bash-is-all-you-need">VercelとBraintrustのブログ記事</a>では、カスタマーサポートチケットや営業電話の書き起こしなどの半構造化データに関する実際の検索タスクで、さまざまな検索ツールセットを持つエージェントを比較しました（「『セキュリティ』に言及している未解決の問題はいくつありますか？」や「バグが報告され、後に誰かがそれを修正したと主張するPRが提出された問題を検索してください」など）[4]。</p><p>その結果、専用のデータベースツールを使用するエージェントは、シェルツールとファイルシステムのみを使用するエージェントに比べて、トークンの使用数が少なく、処理速度が速く、ミスも少ないことが判明しました。ここから得られる教訓は、クエリが半構造化データに対する分析推論を必要とする場合、データベースを直接扱うツールが正しい選択肢であるということです。</p><h3>検索インターフェースを組み合わせる</h3><p>すべてのクエリを適切に処理できる単一の検索インターフェースはありません。たとえば、Cursorはシェルツール（grepによる検索用）とセマンティック検索ツールを組み合わせて、エージェントがユーザーのプロンプトに基づいて適切なツールを選択できるようにしています。Cursorによると、エージェントは特定のシンボルや文字列を照合するためにgrepを選択し、概念的または行動に関する質問にはセマンティック検索を選択し、探索的なタスクには両方を使用するとのことです。</p><p>Vercelの実験レポートでも同じ結果が報告されています。シェルツールと専用データベースツールの両方にアクセスできるハイブリッドエージェントが、まず専用のデータベースツールを使用し、次にファイルシステムをgrepで検索して結果を確認するという方法を用いることにより、テストした全エージェントの中で最高のパフォーマンスを達成しました。しかし、このアプローチでは、ツールの選択と検証について検討するためにより多くのトークンと時間がかかります。</p><p>どちらの例でもパターンは同じです。インターフェースを組み合わせればどんな単一のインターフェースにも勝りますが、コストと遅延の増加というトレードオフも伴うということです。</p><h2>適切なツールセットを見つけるための実践的な推奨事項</h2><p>適切な検索インターフェースの組み合わせとは、小さく、目的が明確で、エージェントの実際のクエリパターンに即したものです。現在のベストプラクティスは、エージェントが何百ものMCPツールを備えるのではなく、備えるツールの数を最低限に抑えるということです。これは、利用可能なすべてのツールを事前に開示すると、コンテキストウィンドウが肥大化し、エージェントが実際にどのツールを使用すべきか混乱してしまうからです。たとえば、Claude Codeが備えているツールは約20個にとどまると報告されています。</p><p>その代わりに、段階的開示の考え方では、最小限のツールセットから始め、必要になったときにのみエージェントが追加機能を探すようにします。Anthropic [5]とCursor [6]の研究によると、このアプローチで47%–85%のトークンを節約できます。たとえば、Claude Codeはこの方法を直接実装しているため、エージェントはLLMを呼び出すたびにコンテキストを消費することなく、APIやデータベースにクエリする方法を段階的に発見できます。</p><p>エージェントのクエリパターンを把握できたら、エージェントがデフォルトでアクセス可能な検索ツールセットを見直すと良いでしょう。採用すべきツールを決定するにあたり、のトレードオフを考えるのに便利なのが、<a href="https://www.elastic.co/search-labs/blog/database-retrieval-tools-context-engineering#building-the-right-database-retrieval-tools-%5C(%E2%80%9Clow-floor,-high-ceiling%E2%80%9D%5C">「敷居を低く、限界を高く」という原則</a>です。限界の高いツールはエージェントの可能性を制限しません。たとえば、汎用的なシェルツールを使用すると、エージェントは曖昧なものを含めて完全なデータベースクエリを作成できますが、推論オーバーヘッド、遅延の増加、そして信頼性の低下を伴います。</p><p>敷居の低いツールはその逆です。これは、特定のクエリをラップし、最小限の推論オーバーヘッドでエージェントに即座にアクセスできる専用のツールであり、低コストと高い信頼性を実現します。ただし、事前のエンジニアリングが必要で、あらゆるクエリをカバーできるわけではなく、エージェントが適切なツールを選びにくくなる可能性もあります。</p><p>それぞれのツールは一長一短です。敷居の低いツールは、エージェントが正しく使用するのは簡単ですが、適用範囲は限定的です。限界の高いツールは多用途ですが、使いこなすにはより多くの推論が必要となります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e6d1b973be4b0a0a25c99c74f02a47e98395a3f7-1200x630.png" style="width: 100%;" /><p>ほとんどのエージェントは、さまざまな検索ツールを組み合わせる必要があります。ただし、どのツールも、追加するに見合うだけの価値が求められます。まずは汎用的な検索ツール（たとえば <code>search_database()</code>ツールやシェルツール）から始めることをお勧めします。そして、セキュリティ目的で既に保持しているコマンドログを活用して、ツールの呼び出し、再試行、およびユーザークエリごとの呼び出し回数などを含め、エージェントが実際に何をしているかを追跡しましょう。そして、あるクエリパターンが繰り返されたり失敗したりすることを把握できたなら、それが専用ツールを作るべき合図です。</p><h2>まとめ</h2><p>ファイルシステム対データベースという議論は、エンジニアが問うべき実際の問題から目をそらしています。「<em>エージェントが独自のコンテキストを構築するために必要な、適切な検索インターフェースとは何か</em>」という問いの答えはおそらく、「<em>単一のものではない</em>」でしょう。</p><p>シェルツールは、さまざまな文脈外の情報源とやり取りするための汎用性の高いツールであり、良い出発点となります。しかし、構造化された分析クエリを使用するユースケースでは、専用のデータベースツールほど効率的で正確ではありません。</p><p>目標は、エージェントの実際のクエリパターンをうまく処理できる最小限の検索ツールを見つけることです。まずはシェルツールから始めて、エージェントが実際に何をしているかをログに記録しましょう。繰り返している、または失敗しているクエリパターンを把握できたら、専用なツールを設計すべき時です。</p><h2>参照資料</h2><p>1. Thariq（Anthropic）。<a href="https://x.com/trq212/status/2027463795355095314">Lessons from Building Claude Code: Seeing like an Agen</a>（2026年）。</p><p>2. Cursor：Documentation。<a href="https://cursor.com/docs/agent/tools/search">セマンティック検索とエージェント検索</a>（2026年）。</p><p>3. Harrison Chase (LangChain)。<a href="https://x.com/hwchase17/status/2011814697889316930">How we built Agent Builder’s memory system</a>（2026年）。</p><p>4. Ankur Goyal（Braintrust）とAndrew Qu（Vercel）。<a href="https://vercel.com/blog/testing-if-bash-is-all-you-need">Testing if &quot;bash is all you need&quot;</a>（2026年）。</p><p>5. Anthropic。<a href="https://www.anthropic.com/engineering/advanced-tool-use">Introducing advanced tool use on the Claude Developer Platform</a>（2025年）。</p><p>6. Cursor。<a href="https://cursor.com/blog/dynamic-context-discovery">動的コンテキスト検出</a>（2026年）。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Serverlessにおける負荷分散のためのElasticsearchレプリカ]]></title>
            <description><![CDATA[Elastic Cloud Serverlessが検索負荷に基づいてインデックスレプリカを自動的に調整し、手動設定なしで最適なクエリパフォーマンスを確保する方法をご覧ください。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-replicas-load-balancing-serverless</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-replicas-load-balancing-serverless</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Andrei Dan]]></dc:creator>
            <pubDate>Tue, 24 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3c4c1e99b892f026b7aba098973593f8298e2ea6-1280x717.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elastic Cloud Serverlessは、検索負荷に基づいてインデックスのレプリカ数を自動的に調整し、手動設定なしで最適なクエリパフォーマンスを確保します。このブログでは、レプリカのスケーリング方法、システムがレプリカを追加または削除するタイミング、そしてこれがインデックスに与える影響について説明します。</p><h2>パーティーは混雑してきています</h2><p>ピザパーティーを開催する予定だとします。数人の友人が配膳を手伝ってくれていて、ピザは部屋のあちこちに配置されています。あなたは友人一人ひとりにピザを渡し、友人たちは到着する空腹の客にピザを一切れずつ配り始めます。</p><p>最初は順調です。数人の客がぽつぽつとやって来て、友達が一切れずつ配ってくれて、みんな幸せそうです。しかし、あなたのサワードウピザの評判が広まると、ドアベルが鳴り続け、ゲストが次々と押し寄せるようになります。すぐに、ペパロニピザを持っている友人の周りに人だかりができ始めます。どうやら皆がそのピザを欲しがっているようです。</p><p>ペパロニピザを持ったあなたの友人は、圧倒されています。ゲストは待たされて苛立ち、長い列ができています。一方、マルゲリータピザを持った友人は、誰にも興味を持たれず、ただ突っ立っているだけでした。</p><p>さて、どうすればよいでしょうか？</p><p>あなたはさらに数枚のペパロニピザを注文し、他の友人に渡します。1人ではなく3人の友人がペパロニピザを持つようになりました。ゲストが分散すれば、一度に3倍ものゲストに対応できるようになります。</p><p>パーティーを開催するうちに、いくつかのことが明らかになります。</p><ul><li><strong>すべてのピザが同じように人気なわけではない。</strong>需要が高いものもあれば、需要が少ないものもあります。人気のないものの「コピー」を余分に用意する必要はありません。行列ができているタイプのものを余分に用意する必要があります。</li><li><strong>行列が長くなる前にピザを追加注文する。</strong>友人が完全に手に負えなくなり、ゲストが怒って帰ってしまうまで待つようでは、待ちすぎです。人だかりができているのを見たら、ピザを追加で注文するほうがよいでしょう。</li><li><strong>ピザをすぐに捨ててはいけない。</strong>ペパロニピザの人だかりが5分ほどまばらになったからといって、混雑が終わったわけではありません。飲み物を補充しているだけかもしれませんし、あるいは単におしゃべりしているだけかもしれません（今でもそういうことがあるかは別にして）。予備のピザを用意しておいてください。しばらく静かな状態が続くようなら、よけておいても構いません。</li><li><strong>手伝ってくれる友達の数だけピザを配れる。</strong>手伝ってくれる友達が4人しかいない場合は、ピザを10枚配っても結果は変わりません。一度に提供できるピザは4枚だけです。ピザの枚数と手伝いの人数を合わせてください。</li><li><strong>友達が持ち場を離れるときは、その友達のピザの担当を代わる。</strong>友達の誰かが外出する必要があったら、すぐにその友達のピザを引き継ぎます。ピザを放置しておくことはできません。誰かに渡すか、しまっておきます。</li></ul><h2>ピザからレプリカへ</h2><p>これをElasticsearchに当てはめて考えてみましょう。</p><p>この例えでは、ピザはレプリカ（インデックスシャードのコピー）、配膳を手伝ってくれる友人は検索ノード、お腹を空かせたゲストは検索クエリ、そして人だかりができている人気のピザは、検索負荷の高いホットインデックスに相当します。</p><p>特定のインデックスに対する検索トラフィックが増加すると、追加のレプリカを作成し、それらを検索ノード全体に分散させます。任意のレプリカは、そのインデックスに対して任意のクエリを処理できます。これは、ペパロニを持っている友人がペパロニの一切れを配るのと同じです。レプリカが多いほどスループットも高くなります。3つのレプリカは、1つのレプリカの3倍のクエリを処理できます。</p><h2>空腹感の測定</h2><p>ピザを何枚注文するかを決める前に、参加者の空腹度を把握する必要があります。</p><p>Elasticsearchはすべてのシャードの<strong>検索負荷</strong>を追跡します。これは、シャードが処理している検索アクティビティの量を示す指標です。検索需要全体を把握するために、インデックスのすべてのシャードにわたってこれを集計します。</p><p>最も重要なのは<strong>相対的な検索負荷</strong>です。これは、プロジェクトの総検索トラフィックのうち、各インデックスにどれだけの割合がヒットしているかを指します。あるインデックスが全検索の60％を受け取っている一方で、別のインデックスが5％を受け取っている場合、どこにキャパシティを追加すべきかがわかります。</p><h2>ピザに隠された数学的な背景</h2><p>最適なレプリカ数は次の式に従って計算します。</p><pre><code>desired_replicas = min(ceil(L × N / (S × X)), N)</code></pre><p>定義：</p><ul><li><strong>L</strong> = インデックスの相対的な検索負荷（0～1の間）。</li><li><strong>N</strong> = プロジェクト内で必要な検索ノードの数。</li><li><strong>S</strong> = インデックス内のシャード数。</li><li><strong>X</strong> = ホットスポットを回避するためのしきい値（デフォルト値：0.5）。</li></ul><p>例として、4つの検索ノード、1つのインデックス、2つのプライマリシャードが検索トラフィックの80％を受け取る場合、以下のようになります。</p><pre><code>desired_replicas = min(ceil(0.8 × 4 / (2 × 0.5)), 4)
                 = min(4, 4)
                 = 4</code></pre><p>このホットインデックスは検索ノードに分散された4つのレプリカを取得します。</p><p>しきい値X（デフォルト値は0.5）は重要です。レプリカシステムが完全に処理能力を超えるまで待つのではなく、半分の処理能力に達した時点で規模を拡大します。余ったピザは、客が帰り始めてからではなく、人だかりができ始めた時に配りましょう。</p><h2>素早くスケールアップし、ゆっくりとスケールダウン</h2><p>検索負荷が増えたら、すぐにレプリカを追加します。ユーザーを待たせる理由はありません。</p><p>検索負荷が落ちたら、少し待ってからアクションを取ります。レプリカを減らす前に、約30分間需要が安定して低い状態になることを確認する必要があります。（これは、交通量が急激に変動する状況に対処するためのもので、一時的に交通量が減ったからといって、パーティーが終わったわけではないからです。）</p><p>レプリカを追加するにはコストがかかるため、これは重要な点です。新しいレプリカは、クエリを効率的に処理する前に、データをコピーし、キャッシュを準備します。レプリカを性急に削除すると、トラフィックが自然に変動するたびに、この初期費用を継続的に支払うことになります。</p><h2>トポロジー境界を尊重</h2><p>レプリカは検索ノードの数を超えてはなりません。レプリカの数をノードの数より多くしても何のメリットもありません（ピザを配るのを手伝ってくれる友人の数だけしかピザを配ることができないからです）。</p><p>プロジェクトからノードが削除されたら、レプリカ数を即座に削減して一致させます。割り当てられていないレプリカは存在できないため、クールダウンを待つ必要はありません。友人が席を外した瞬間にそのピザを引き継ぎます。</p><h2>サーバーレスの全体像</h2><p>レプリカによる検索負荷分散は、他の自動スケーリングシステムと共に機能します。</p><ul><li><strong>検索自動スケーリング</strong>は検索ノードの数を調整します（協力する友人の数）。</li><li><strong>検索負荷分散のためのレプリカ</strong>は、インデックスごとのレプリカ数を調整することでトラフィックを分散します（各種のピザが何枚必要かを示すようなものです）。</li><li><strong>データストリームの自動シャード化</strong>は書き込みのシャード数を最適化します（各ピザをどのようにカットするかについては<a href="https://www.elastic.co/search-labs/blog/datastream-autosharding-serverless">前回の投稿</a>をご覧ください）。</li></ul><p>重要な設計原則：負荷分散のためのレプリカは、検索の自動スケーリングを直接トリガーしません。その代わりに、検索リクエストをより多くのレプリカに分散させることで、検索ノード全体のリソース利用率を高めることができます。利用率の上昇に伴い、必要に応じて既存の自動スケーリングロジックが作動し、容量が追加されます。負荷分散のためのレプリカを使用することで、自動スケーリングが本来の役割を果たせるようになり、他のノードがアイドル状態になっている間にすべてのトラフィックが単一のレプリカに集中するのではなく、検索ノードが実際に使用されるようになります。</p><h2>防御側への示唆</h2><p>どのインデックスが人気になるかを予測する必要はありません。トラフィックパターンが変わってもレプリカを手動で調整する必要はありません。最も取引量の多いインデックスが急激なアクセス集中で処理能力を超えたからといって、午前3時に起きる必要はありません。</p><p>システムは、行列ができる場所を監視し、それらのスポットではさらにピザを注文します。コールドインデックスは不要なレプリカにリソースを浪費しません。ホットインデックスは必要な容量を取得します。予算は重要なところに使われます。</p><h2>まとめ</h2><p><a href="https://www.elastic.co/search-labs/blog/datastream-autosharding-serverless">オートシャーディングに関する記事</a>では、ピザを正しくカットする方法を説明しました。検索のロードバランシングのためのレプリカにより、空腹の群衆が到着した際に、適切な人に十分なピザが行き渡るようにできます。</p><p><a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a>を試して、ピザの配送は当社にお任せください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch Inference APIとHugging Faceモデルを組み合わせて使用]]></title>
            <description><![CDATA[推論エンドポイントを使用してElasticsearchをHugging Faceモデルに接続する方法と、セマンティック検索とチャット補完機能を備えた多言語ブログ推奨システムを構築する方法を学びましょう。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/hugging-face-elasticsearch-inference-api</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/hugging-face-elasticsearch-inference-api</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
            <pubDate>Mon, 23 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1417d6ff033712206c9bd4bcc22074ee3437ce96-1999x1125.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>最近のアップデートで、Elasticsearchは<a href="https://endpoints.huggingface.co/">Hugging Face Inference Service</a>でホストされているモデルに接続するためのネイティブ統合機能を導入しました。この記事では、この統合を構成し、大規模言語モデル（LLM）を使用して簡単なAPI呼び出しを通じて推論を実行する方法を探ります。リソース使用量と解答品質のバランスが取れた軽量汎用モデルである<a href="https://huggingface.co/HuggingFaceTB/SmolLM3-3B">SmolLM3-3B</a>を使用します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7ddadf1976421a860a7d62087239adb9150d808b-1999x1388.png" style="width: 100%;" /><h2>要件</h2><ul><li><strong>Elasticsearch 9.3またはElastic Cloud Serverless：</strong><a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud">これらの指示に従って</a>クラウド導入を作成することもできますし、<a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart#local-dev-quick-start"><code>start-local</code></a>クイックスタートを使うこともできます。</li><li><strong>Python 3.12：</strong>Pythonは<a href="https://www.python.org/">こちら</a>からダウンロードしてください。</li><li><strong>Hugging Face</strong><a href="https://huggingface.co/docs/hub/en/security-tokens">アクセストークン</a>。</li></ul><h2>Hugging Face推論エンドポイントを使用したチャットの完了</h2><p>まず、ElasticsearchをHugging Faceの<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put">推論エンドポイント</a>に接続し、ブログ記事のコレクションからAIを活用したレコメンデーションを生成する実践的な例を作成します。アプリのナレッジベースには、会社のブログ記事のデータセットを使用します。これには価値のある情報が含まれていますが、多くの場合、見つけるのが困難です。</p><p>このエンドポイントでは、<a href="https://www.elastic.co/docs/solutions/search/semantic-search">セマンティック検索</a>が指定されたクエリに対して最も関連性の高い記事を取得し、Hugging Face LLMがそれらの結果に基づいて短いコンテキスト推奨を生成します。</p><p>これから構築する情報フローの概要を見ていきましょう。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1dfbc2323438feaaa42e13ab242dd1f7166f74aa-1200x676.png" style="width: 100%;" /><p>この記事では、コンパクトなサイズと強力な多言語推論能力・ツール呼び出し能力を組み合わせた<strong>SmolLM3-3B</strong>の性能を検証します。検索クエリに基づいて、一致するすべてのコンテンツ（英語とスペイン語）をLLMに送信し、検索クエリと結果に基づいたカスタムメイドの説明を含むおすすめ記事のリストを生成します。</p><p>AIによる推奨生成システムを備えた記事サイトのUIは次のようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8d3b86b212f28ff279f2da67a33e6134039f0e4e-1999x949.png" style="width: 100%;" /><p>このアプリケーションの完全な実装は、リンク先の<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/elasticsearch-inference-api-and-hugging-face/notebook.ipynb">ノートブック</a>で確認できます。</p><h3>Elasticsearch推論エンドポイントの構成</h3><p>Elasticsearch <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-hugging-face">Hugging Face推論エンドポイント</a>を使用するには、2つの重要な要素（Hugging Face APIキーと実行中のHugging FaceエンドポイントURL）が必要です。下の画像のように表示されるはずです。</p><pre><code>PUT _inference/chat_completions/hugging-face-smollm3-3b
{
    "service": "hugging_face",
    "service_settings": {
        "api_key": "hugging-face-access-token", 
        "url": "url-endpoint" 
    }
}</code></pre><p>Hugging FaceのElasticsearchにおける推論エンドポイントは、 <code>text_embedding</code>, <code>completion</code>, <code>chat_completion</code>, と <code>rerank</code>の異なるタスクタイプをサポートしています。このブログ記事では、検索結果とシステムプロンプトに基づいて会話形式のレコメンデーションをモデルに生成させる必要があるため、<code>chat_completion</code> を使用します。このエンドポイントを使用すると、Elasticsearch APIを使用してElasticsearchから直接チャットの完了を簡単に実行できます。</p><pre><code>POST _inference/chat_completion/hugging-face-smollm3-3b/_stream
{
  "messages": [
      { "role": "user", "content": "<user prompt>" }
  ]
}</code></pre><p>これはアプリケーションのコアとして機能し、モデルを通過するプロンプトと検索結果を受け取ります。理論について説明したので、アプリケーションの実装を始めましょう。</p><h4>Hugging Faceでの推論エンドポイントの設定</h4><p>Hugging Faceモデルをデプロイするために、モデルのエンドポイントをデプロイするための簡単で高速なサービス<a href="https://huggingface.co/inference-endpoints/dedicated">Hugging Faceワンクリック導入</a>を使用します。これは有料サービスであり、利用には追加料金が発生する可能性があることにご注意ください。このステップでは、記事の推奨を生成するために使うモデルインスタンスが作成されます。</p><p>ワンクリックカタログからモデルを選択できます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b816e9fba1fe172687bf58f5143fb1f838c1077f-549x331.png" style="width: 100%;" /><p><strong>SmolLM3-3B</strong>モデルを選択します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/610d3aba0429f3666c2df3616d513eb6a4397c0c-502x478.png" style="width: 100%;" /><p>ここから、Hugging FaceのエンドポイントURLを取得します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/025094ddb2cfbd1f0f216a5ec4e119b0f4fa2c42-646x328.png" style="width: 100%;" /><p>Elasticsearch <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-hugging-face">Hugging Faceの推論エンドポイントのドキュメント</a>で述べられているように、テキスト生成にはOpenAI APIと互換性のあるモデルが必要です。そのため、<code>/v1/chat/completions</code>のサブパスをHugging FaceのエンドポイントURLに追加する必要があります。最終的な結果は次のようになります。</p><pre><code>https://j2g31h0futopfkli.us-east-1.aws.endpoints.huggingface.cloud/v1/chat/completions</code></pre><p>これで準備が整いましたので、Pythonノートブックでコーディングを開始できます。</p><h4>Hugging Face APIキーの生成</h4><p><a href="https://huggingface.co/join">Hugging Faceアカウント</a>を作成し、<a href="https://huggingface.co/docs/hub/en/security-tokens#user-access-tokens">以下の指示</a>に従ってAPIトークンを取得してください。トークンの種類は、<em>fine-grained</em>（本番環境に推奨。特定のリソースへのアクセスのみを提供）、<em>read</em>（読み取り専用アクセス用）、<em>write</em>（読み取りおよび書き込みアクセス用）の3つから選択できます。このチュートリアルでは、推論エンドポイントを呼び出すだけでよいので、readトークンで十分です。次のステップのために、このキーを保存しておいてください。</p><h4>Elasticsearch推論エンドポイントの設定</h4><p>まず、Elasticsearch Pythonクライアントを宣言します。</p><pre><code>os.environ["ELASTICSEARCH_API_KEY"] = "your-elasticsearch-api-key"
os.environ["ELASTICSEARCH_URL"] = "https://xxxx.us-central1.gcp.cloud.es.io:443"

es_client = Elasticsearch(
    os.environ["ELASTICSEARCH_URL"], api_key=os.environ["ELASTICSEARCH_API_KEY"]
)</code></pre><p>次に、Hugging Faceモデルを使用するElasticsearch推論エンドポイントを作成します。このエンドポイントを使用すると、ブログ記事とモデルに渡されたプロンプトに基づいて応答を生成できます。</p><pre><code>INFERENCE_ENDPOINT_ID = "smollm3-3b-pnz"

os.environ["HUGGING_FACE_INFERENCE_ENDPOINT_URL"] = (
 "https://j2g31h0futopfkli.us-east-1.aws.endpoints.huggingface.cloud/v1/chat/completions"
)
os.environ["HUGGING_FACE_API_KEY"] = "hf_xxxxx"

resp = es_client.inference.put(
        task_type="chat_completion",
        inference_id=INFERENCE_ENDPOINT_ID,
        body={
            "service": "hugging_face",
            "service_settings": {
                "api_key": os.environ["HUGGING_FACE_API_KEY"],
                "url": os.environ["HUGGING_FACE_INFERENCE_ENDPOINT_URL"],
            },
        },
    )</code></pre><h3>データセット</h3><p>このデータセットには、クエリの対象となる<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/elasticsearch-inference-api-and-hugging-face/dataset.json">ブログ記事</a>が含まれており、ワークフロー全体で使用される多言語コンテンツセットを表しています。</p><pre><code>// Articles dataset document example: 
{
    "id": "6",
    "title": "Complete guide to the new API: Endpoints and examples",
    "author": "Tomas Hernandez",
    "date": "2025-11-06",
    "category": "tutorial",
    "content": "This guide describes in detail all endpoints of the new API v2. It includes code examples in Python, JavaScript, and cURL for each endpoint. We cover authentication, resource creation, queries, updates, and deletion. We also explain error handling, rate limiting, and best practices. Complete documentation is available on our developer portal."
  }</code></pre><h4>Elasticsearch マッピング</h4><p>データセットが定義されたので、ブログ記事の構造に適切にフィットするデータスキーマを作成する必要があります。Elasticsearchにデータを格納するために以下の<a href="https://www.elastic.co/docs/manage-data/data-store/mapping">インデックスマッピング</a>が使用されます。</p><pre><code>INDEX_NAME = "blog-posts"

mapping = {
    "mappings": {
        "properties": {
            "id": {"type": "keyword"},
            "title": {
                "type": "object",
                "properties": {
                    "original": {
                        "type": "text",
                        "copy_to": "semantic_field",
                        "fields": {"keyword": {"type": "keyword"}},
                    },
                    "translated_title": {
                        "type": "text",
                        "fields": {"keyword": {"type": "keyword"}},
                    },
                },
            },
            "author": {"type": "keyword", "copy_to": "semantic_field"},
            "category": {"type": "keyword", "copy_to": "semantic_field"},
            "content": {"type": "text", "copy_to": "semantic_field"},
            "date": {"type": "date"},
            "semantic_field": {"type": "semantic_text"},
        }
    }
}


es_client.indices.create(index=INDEX_NAME, body=mapping)</code></pre><p>ここで、データがどのように構造化されているかをより明確に見ることができます。セマンティック検索を使用して自然言語に基づいて結果を取得し、<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/copy-to"><code>copy_to</code></a>プロパティを使用してフィールドの内容を<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"><code>semantic_text</code></a>フィールドにコピーします。さらに、<code>title</code>フィールドには2つのサブフィールドが含まれています。<code>original</code>サブフィールドは、記事の元の言語に応じて英語またはスペイン語でタイトルを格納し、<code>translated_title</code>サブフィールドはスペイン語の記事にのみ存在し、元のタイトルの英語訳が含まれています。</p><h3>データの取り込み</h3><p>以下のコードスニペットは<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/javascript/bulk_examples">bulk API</a>を使用してブログ投稿データセットをElasticsearchに取り込みます。</p><pre><code>def build_data(json_file, index_name):
    with open(json_file, "r") as f:
        data = json.load(f)

    for doc in data:
        action = {"_index": index_name, "_source": doc}
        yield action


try:
    success, failed = helpers.bulk(
        es_client,
        build_data("dataset.json", INDEX_NAME),
    )
    print(f"{success} documents indexed successfully")

    if failed:
        print(f"Errors: {failed}")
except Exception as e:
    print(f"Error: {str(e)}")</code></pre><p>Elasticsearchに記事を取り込んだので、次に<code>semantic_text</code>フィールドに対して検索できる関数を作成する必要があります:</p><pre><code>def perform_semantic_search(query_text, index_name=INDEX_NAME, size=5):
    try:
        query = {
            "query": {
                "match": {
                    "semantic_field": {
                        "query": query_text,
                    }
                }
            },
            "size": size,
        }

        response = es_client.search(index=index_name, body=query)
        hits = response["hits"]["hits"]

        return hits
    except Exception as e:
        print(f"Semantic search error: {str(e)}")
        return []</code></pre><p>推論エンドポイントを呼び出す関数も必要です。この場合、<strong><code>chat_completion</code> </strong>タスクタイプを使用してエンドポイントを呼び出し、ストリーミング応答を取得します。</p><pre><code>def stream_chat_completion(messages: list, inference_id: str = INFERENCE_ENDPOINT_ID):
    url = f"{ELASTICSEARCH_URL}/_inference/chat_completion/{inference_id}/_stream"
    payload = {"messages": messages}
    headers = {
        "Authorization": f"ApiKey {ELASTICSEARCH_API_KEY}",
        "Content-Type": "application/json",
    }

    try:
        response = requests.post(url, json=payload, headers=headers, stream=True)
        response.raise_for_status()

        for line in response.iter_lines(decode_unicode=True):
            if line:
                line = line.strip()

                if line.startswith("event:"):
                    continue

                if line.startswith("data: "):
                    data_content = line[6:]

                    if not data_content.strip() or data_content.strip() == "[DONE]":
                        continue

                    try:
                        chunk_data = json.loads(data_content)

                        if "choices" in chunk_data and len(chunk_data["choices"]) > 0:
                            choice = chunk_data["choices"][0]
                            if "delta" in choice and "content" in choice["delta"]:
                                content = choice["delta"]["content"]
                                if content:
                                    yield content

                    except json.JSONDecodeError as json_err:
                        print(f"\nJSON decode error: {json_err}")
                        print(f"Problematic data: {data_content}")
                        continue

    except requests.exceptions.RequestException as e:
        yield f"Error: {str(e)}"</code></pre><p>ここで、 <code>chat_completions</code> 推論エンドポイントと推薦エンドポイントを合わせてセマンティック検索関数を呼び出し、カードに割り当てられるデータを生成する関数を書くことができます。</p><pre><code>def recommend_articles(search_query, index_name=INDEX_NAME, max_articles=5):
    print(f"\n{'='*80}")
    print(f"🔍 Search Query: {search_query}")
    print(f"{'='*80}\n")

    articles = perform_semantic_search(search_query, index_name, size=max_articles)

    if not articles:
        print("❌ No relevant articles found.")
        return None, None

    print(f"✅ Found {len(articles)} relevant articles\n")

    # Build context with found articles
    context = "Available blog articles:\n\n"
    for i, article in enumerate(articles, 1):
        source = article.get("_source", article)
        context += f"Article {i}:\n"
        context += f"- Title: {source.get('title', 'N/A')}\n"
        context += f"- Author: {source.get('author', 'N/A')}\n"
        context += f"- Category: {source.get('category', 'N/A')}\n"
        context += f"- Date: {source.get('date', 'N/A')}\n"
        context += f"- Content: {source.get('content', 'N/A')}\n\n"

    system_prompt = """You are an expert content curator that recommends blog articles.

    Write recommendations in a conversational style starting with phrases like:
    - "If you're interested in [topic], this article..."
    - "This post complements your search with..."
    - "For those looking into [topic], this article provides..."


    FORMAT REQUIREMENTS:
    - Return ONLY a JSON array
    - Each element must have EXACTLY these three fields: "article_number", "title", "recommendation"
    - If the original title is in spanish, use the "translated_title" subfield in the "title" field

    Keep each recommendation concise (2-3 sentences max) and focused on VALUE to the reader.

    EXAMPLE OF CORRECT FORMAT:
    [
        {"article_number": 1, "title": "Article title in english", "recommendation": "If you are interested in [topic], this article provides..."},
        {"article_number": 2, "title": "Article title in english", "recommendation": " for those looking into [topic], this article provides..."}
    ]

    Return ONLY the JSON array following this exact structure."""

    user_prompt = f"""Search query: "{search_query}"

    Generate recommendations for the following articles: {context}
    """

    messages = [
        {"role": "system", "content": "/no_think"},
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_prompt},
    ]

    # LLM generation
    print(f"{'='*80}")
    print("🤖 Generating personalized recommendations...\n")

    full_response = ""

    for chunk in stream_chat_completion(messages):
        print(chunk, end="", flush=True)
        full_response += chunk

    return context, articles, full_response</code></pre><p>最後に、情報を抽出して出力できるようにフォーマットする必要があります。</p><pre><code>def display_recommendation_cards(articles, recommendations_text):
    print("\n" + "=" * 100)
    print("📇 RECOMMENDED ARTICLES".center(100))
    print("=" * 100 + "\n")

    # Parse JSON recommendations - clean tags and extract JSON
    recommendations_list = []
    try:

        # Clean up <think> tags
        cleaned_text = re.sub(
            r"<think>.*?</think>", "", recommendations_text, flags=re.DOTALL
        )
        # Remove markdown code blocks ( ... ``` or ``` ... ```)
        cleaned_text = re.sub(r"```(?:json)?", "", cleaned_text)
        cleaned_text = cleaned_text.strip()

        parsed = json.loads(cleaned_text)

        # Extract recommendations from list format
        for item in parsed:
            article_number = item.get("article_number")
            title = item.get("title", "")
            rec_text = item.get("recommendation", "")

            if article_number and rec_text:
                recommendations_list.append(
                    {
                        "article_number": article_number,
                        "title": title,
                        "recommendation": rec_text,
                    }
                )
    except json.JSONDecodeError as e:
        print(f"⚠️  Could not parse recommendations as JSON: {e}")
        return

    for i, article in enumerate(articles, 1):
        source = article.get("_source", article)

        # Card border
        print("┌" + "─" * 98 + "┐")

        # Find recommendation and title for this article number
        recommendation = None
        title = None
        for rec in recommendations_list:
            if rec.get("article_number") == i:
                recommendation = rec.get("recommendation")
                title = rec.get("title")
                break

        # Print title
        title_lines = textwrap.wrap(f"📌 {title}", width=94)
        for line in title_lines:
            print(f"│  {line}".ljust(99) + "│")

        # Card border
        print("├" + "─" * 98 + "┤")

        # Print recommendation
        if recommendation:
            recommendation_lines = textwrap.wrap(recommendation, width=94)
            for line in recommendation_lines:
                print(f"│  {line}".ljust(99) + "│")

        # Card bottom
        print("└" + "─" * 98 + "┘")</code></pre><p>セキュリティブログの投稿について質問して、これをテストしてみましょう。</p><pre><code>search_query = "Security and vulnerabilities"

context, articles, recommendations = recommend_articles(search_query)

print("\nElasticsearch context:\n", context)

# Display visual cards
display_recommendation_cards(articles, recommendations)</code></pre><p>ここでは、ワークフローによって生成されたコンソール内のカードを確認できます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/730d35212594bb3db30447c3ea7e2a92857287b7-1999x1515.png" style="width: 100%;" /><p>すべてのヒットとLLMの対応を含む完全な結果を<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/elasticsearch-inference-api-and-hugging-face/results.md">このファイル</a>でご覧いただけます。</p><p>「Security and vulnerabilities」に関連する記事をクエリしています。この質問は、Elasticsearchに保存されているドキュメントに対する検索クエリとして使用されます。取得された結果はモデルに渡され、モデルはその内容に基づいてレコメンデーションを生成します。ご覧の通り、このモデルは読者がクリックする動機付けとなる魅力的な短いテキストを非常にうまく生成しています。</p><h2>まとめ</h2><p>この例では、ElasticsearchとHugging Faceを組み合わせて、AIアプリケーション向けの高速で効率的な集中型システムを構築する方法を示します。Hugging Faceの豊富なモデルカタログにより、このアプローチでは手作業を削減し、柔軟性を確保できます。特にSmolLM3-3Bを使用すると、コンパクトな多言語モデルでも、セマンティック検索と組み合わせることで有意義な推論とコンテンツ生成を実現できることがわかります。これらのツールを組み合わせることで、インテリジェントなコンテンツ分析と多言語アプリケーションを構築するための、拡張性が高く効果的な基盤を提供できます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[高速性と精度の対比：量子化ベクトル探索の再現率の測定]]></title>
            <description><![CDATA[Elasticsearchのベクトル検索における再現率を、最小限の設定で測定する方法について説明します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/recall-vector-search-quantization</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/recall-vector-search-quantization</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeff Vestal]]></dc:creator>
            <pubDate>Fri, 20 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/09f03b9239d66c36763cdab3fafcdac207ff6d83-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>誰もがベクトル検索が瞬時に行われることを望んでいますが、高次元ベクトルではデータ量が膨大になります。1,024次元のfloat-32ベクトルは1つのメモリを大量に消費し、他の何百万ものベクトルと比較すると計算量が多くなります。</p><p>これを解決するために、Elasticsearchのような検索エンジンは主に2つの最適化戦略を使用します。</p><ol><li><strong>近似検索（Hierarchical Navigable Small World [HNSW]）：</strong>すべての文書をスキャンする代わりに、ナビゲーショングラフを構築して、回答の可能性が高い近傍に素早くジャンプします。</li><li><strong>量子化：</strong>メモリ使用量を削減し、計算速度を向上させるために、ベクトルを圧縮します（例えば、32ビット浮動小数点数から8ビット整数、あるいは1ビットのバイナリ値へ）。</li></ol><p>しかし、最適化にはしばしば<strong>精度</strong>という代償が伴います。</p><p>「データを圧縮し、検索中にショートカットを取ると、最高の結果を見逃すのではないか？」「この最適化は検索エンジンの関連性を低下させるのではないか？」といった恐れは正当です。</p><p>Elasticの量子化が結果を低下させないことを証明するために、<a href="https://huggingface.co/datasets/fancyzhx/dbpedia_14"><strong>DBpedia-14</strong> </a><strong></strong>データセットを使用して再現可能なテストハーネスを構築し、Elasticsearchのデフォルトの最適化を使用する際に、速度と引き換えにどれだけの精度（具体的には再現率）を犠牲にしているかを正確に計算しました。</p><p>要約すると、それはおそらく想定よりもずっと少ないでしょう。<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/fast_vs_accurate_measuring_the_recall_of_quantized_vector_search/vector_recall_notebook.ipynb">こちらのノートブック</a>をチェックして、ぜひご自身でお試しください。</p><h2><strong>定義（非専門家向け）</strong></h2><p>コードを見る前に、いくつかの用語について確認しておきましょう。</p><ul><li><strong>関連性対再現率：関連性</strong> は主観的で（良いものが見つかったか？）、<strong>再現率</strong>は数学的なものです。データベース内にクエリと<em>完全</em>に一致する文書が10件あり、検索エンジンがそのうち9件を見つけた場合、再現率は90%（または0.9）です。</li><li><strong>完全一致検索（フラット）：</strong>総当たり法とも呼ばれ、検索エンジンはインデックス内のすべての文書をスキャンし、距離を計算します。<ul><li><em>長所：</em>100%完璧な再現率。</li><li><em>短所：</em>計算コストが高く、スケール時の処理速度が遅くなる。</li></ul></li><li><strong>近似探索（HNSW）：</strong>いわゆる「ショートカット」の方法。検索エンジンは <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">HNSW</a> グラフを作成し、グラフを巡回して最も近い隣接点を探します。<ul><li><em>長所：</em>非常に高速で拡張性が高い。</li><li><em>短所：</em>グラフの探索が早すぎて停止すると、近傍を見逃す可能性がある。</li></ul></li></ul><h2><strong>実験：完全一致と近似探索の比較</strong></h2><p>再現率をテストするために、テキスト分類モデルのトレーニングと評価によく使用される、14のオントロジークラスにわたるタイトルと要約の大規模なデータセット<strong>DBPedia-14</strong>データセットを使用しました。具体的には、「Film」カテゴリに焦点を当てます。最適化された生産設定を、数学的に完璧な真値と比較したいと考えました。</p><p>この実験では、テキスト表現の業界ベンチマークをリードする最先端の多言語モデル<a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text">jina-embeddings-v5-text-small</a>モデルを使用しています。このモデルを選んだ理由は、高性能埋め込みの現在の標準となっているためです。Jina v5の優れた精度とElasticsearchのネイティブ量子化を組み合わせることで、計算効率が高く、検索品質にも妥協のない検索アーキテクチャを実証できます。</p><p>二重マッピングを使用したインデックスを設定し、同じテキストを同時に2つの異なるフィールドに取り込みました。</p><ol><li><strong><code>content.raw</code> </strong>（タイプ: <code>flat</code>）。これにより、ElasticsearchはFloat32ベクトル全体の総当たりスキャンを実行することになります。これにより完全一致の結果が返され、ベースラインとして使用されます。</li><li><strong><code>content</code> </strong>（タイプ： <code>semantic_text</code>）。デフォルトではHNSW + Better Binary Quantization（BBQ）を使用しています。これは、近似一致のための標準的かつ最適化された生産設定です。</li></ol><h3><strong>Recall@10 テスト</strong></h3><p>指標としては、Recall@10を使用しました。</p><p>50本のランダムな映画を選び、両方のフィールドに同じクエリを実行しました。</p><ul><li><strong>完全一致（フラット）</strong>検索で上位10個の近傍が ID [1, 2, 3... 10] であると示されている場合。</li><li>また、<strong>近似（HNSW）</strong>検索では、ID [1, 2, 3... 9, 99] が返されます。</li><li>上位10位のうち、9位を正しく特定できました。スコアは<strong>0.9</strong>です。</li></ul><p>こちらが使用したマッピングです。</p><pre><code># The "Control Group": Forces exact brute-force scan
"raw": {
    "type": "semantic_text",
    "inference_id": ".jina-embeddings-v5-text-small",
    "index_options": {
        "dense_vector": {
            "type": "flat"
        }
    }
}</code></pre><p><strong>結果：成功の「横ばい線」</strong></p><p>スケールテストを実行し、完全なデータセットを再読み込みし、1,000～40,000件の文書のインデックスサイズに対してテストしました。</p><p>再現率スコアに何が起こったかは以下のとおりです。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ドキュメント</td><td style="border: 1px solid #ccc; padding: 4px;">Recall@10スコア</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">1,000</td><td style="border: 1px solid #ccc; padding: 4px;">1.000 (100%)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">5,000</td><td style="border: 1px solid #ccc; padding: 4px;">0.998 (100%)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">10,000</td><td style="border: 1px solid #ccc; padding: 4px;">0.992 (99.4%)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">20,000</td><td style="border: 1px solid #ccc; padding: 4px;">0.999 (99.0％)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">40,000</td><td style="border: 1px solid #ccc; padding: 4px;">0.992 (98.8%)</td>
                    </tr>
              </tbody>
            </table>
          <p>結果は驚くほど安定していました。スケールアップしても、近似検索は総当たりの完全一致検索と<strong>99％超の確率</strong>で一致しました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a4bfacb1d0cce6fdf6df0e1a9d4fc5d4007a66da-1999x1209.png" style="width: 100%;" /><h2><strong>なぜこれほど上手くいったでしょう？</strong></h2><p>ベクトルをバイナリ値に圧縮すると、これよりも精度が低下すると考えられるかもしれません。その理由は、Elasticsearchが検索を処理する方法にあります。</p><p>今日のほとんどの埋め込みモデルは、大きなFloat32ベクトルを出力します。探索を効率的にするために、Elasticsearchは高次元ベクトルに対して量子化を使用します。具体的には、バージョン9.2以降、デフォルトで<a href="https://www.elastic.co/search-labs/blog/elasticsearch-9-1-bbq-acorn-vector-search">BBQ</a>を使用するようになりました。</p><p>BBQは<strong>再スコアリング</strong>メカニズムを採用しています。</p><ol><li><strong>トラバーサル：</strong>検索エンジンは圧縮された（量子化された）ベクトルを使ってHNSWグラフを高速に走査します。ベクトルが小さいため、効率的にオーバーサンプリングを行い、パフォーマンスを損なうことなく、より多くの候補リスト（例えば、類似性の高い上位100件の文書）を収集できます。</li><li><strong>再スコアリング：</strong>候補が見つかったら、その数件の文書について完全精度の値を取得して、最終的な正確なランキングを計算します。</li></ol><p>これにより、量子化による高速な処理と、最終的なソートにおける浮動小数点数の精度という、両方の利点を享受できます。</p><h2><strong>もっと良くできるでしょうか？</strong></h2><p>注目すべき点は、ここで確認している結果がデフォルト設定とデータのランダムサンプリングを使用していることです。これは高性能の出発点とお考えください。Jina v5は非常に優れていますが、これらの再現率スコアはすべてのデータセットに対して「万能な」保証ではありません。すべてのデータ収集には独自の特性があり、さらにパフォーマンスを向上させるために調整することは可能ですが、常に自分の特定のデータに対してベンチマーク設定を行い、どこまで性能を引き出せるかを確認する必要があります。</p><h2><strong>まとめ</strong></h2><p>これは非常に小規模なテストです。ただし、この演習の目的は埋め込みモデルやBBQを個別に測定することではありません。最小限のセットアップで、データセットの再現率を簡単に測定する方法を示すことです。</p><p>このテストを独自のデータで実行したい場合は、<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/fast_vs_accurate_measuring_the_recall_of_quantized_vector_search/vector_recall_notebook.ipynb">こちらのノートブック</a>をチェックして試してみてください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ElasticsearchのGemini CLI拡張機能（ツールとスキル付き）]]></title>
            <description><![CDATA[GoogleのGemini CLIでElasticsearchのデータを検索、取得、分析するためのElasticの拡張機能（開発者およびエージェントのワークフロー向け）をご紹介します。
]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/gemini-cli-extension-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/gemini-cli-extension-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Walter Rafelsberger, Enrico Zimuel]]></dc:creator>
            <pubDate>Tue, 17 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6dfcec4a77b3dc83bf0d974417bf2e211abb1f4f-876x468.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>GoogleのGemini CLI用のElastic拡張機能のリリースを発表できることを嬉しく思います。これにより、<a href="https://www.elastic.co/elasticsearch">Elasticsearch</a>と<a href="https://www.elastic.co/elasticsearch/agent-builder">Elastic Agent Builder</a>のパワーを、AI開発ワークフローに直接組み込むことができます。この拡張機能には、Elasticsearchを操作するための最近開発されたエージェントスキルもいくつか用意されています。</p><p>この拡張機能はオープンソースプロジェクトとして<a href="https://github.com/elastic/gemini-cli-elasticsearch">こちら</a>から利用できます。</p><h2>Gemini CLIの概要とインストール方法</h2><p><a href="https://geminicli.com/">Gemini CLI</a> は、GoogleのGeminiモデルを直接コマンドラインに取り込むオープンソースのAIエージェントです。ターミナルからAIと対話することで、コードの生成、ファイルの編集、シェルコマンドの実行、ウェブからの情報の取得などのタスクを実行できます。</p><p>一般的なチャットインターフェースとは異なり、Gemini CLIはローカル開発環境と統合されます。つまり、プロジェクトのコンテキストを理解し、ファイルを変更し、ビルドやテストを実行し、ワークフローをターミナル内で直接自動化することができます。開発者、サイト信頼性エンジニア（SRE）、コマンドラインのワークフローを離れることなくAI支援のコーディングと自動化を求めるエンジニアにとって役立ちます。</p><p>Gemini CLIは複数のパッケージマネージャーを使ってインストール可能です。最も一般的な方法はnpm経由です。</p><pre><code>npm install -g @google/gemini-cli</code></pre><p>その他のインストール方法については、<a href="https://geminicli.com/docs/get-started/installation/">公式のインストールページ</a>を参照してください。</p><p>インストール後、以下のコマンドを実行してCLIを起動します。</p><pre><code>gemini</code></pre><p>図1に示すような画面が表示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6dfcec4a77b3dc83bf0d974417bf2e211abb1f4f-876x468.png" style="width: 100%;" /><h2>Elasticsearchを構成</h2><p>Elasticsearchインスタンスを実行する必要があります。モデルコンテキストプロトコル（MCP）サーバーを使用するには、Kibana 9.3以降もインストールする必要があります。Elasticsearchクエリ言語 (ES|QL) スキル (<code>esql</code>) を使用するためにKibanaは必要ありません。</p><p><a href="https://www.elastic.co/cloud">Elastic Cloud</a>で無料トライアルを有効化するか、<a href="https://github.com/elastic/start-local"><code>start-local</code></a>スクリプトを使ってローカルにインストールできます。</p><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre><p>これにより、ElasticsearchとKibanaがコンピュータにインストールされ、Gemini CLIの設定に使用するAPIキーが生成されます。</p><p>APIキーは前のコマンドの出力として表示され、 <strong><code>elastic-start-local</code></strong>フォルダ内の<strong>.env</strong>ファイルに保存されます。</p><p>オンプレミスのElasticsearchを使用している場合（例えば、<code>start-local</code>）、MCPでElastic Agent Builderを使用するには、大規模言語モデル（LLM）を接続する必要があります。さまざまなオプションを理解するには、<a href="https://www.elastic.co/docs/explore-analyze/ai-features/llm-guides/llm-connectors">このドキュメントページ</a>をご覧ください。</p><p>Elastic Cloud（またはサーバーレス）を使用している場合は、LLM接続が事前構築されています。</p><h2>Elasticsearch拡張機能をインストールしてください</h2><p>次のコマンドを使用して、Gemini CLI用のElasticsearch拡張機能をインストールできます。</p><pre><code>gemini extensions install https://github.com/elastic/gemini-cli-elasticsearch</code></pre><p>Geminiを開き、以下のコマンドを実行することで、拡張機能が正常にインストールされたことを確認できます。</p><pre><code>/extensions list</code></pre><p>Elasticsearch拡張機能が利用可能になっているはずです。</p><p>MCP統合を使用するには、Elasticsearch 9.3以降のバージョンがインストールされている必要があります。<a href="https://www.elastic.co/kibana">Kibana</a>からMCPサーバーのURLを取得する必要があります。</p><ul><li>MCPサーバーのURLは、[エージェント] &gt; [すべてのツールを表示] &gt; [MCPの管理] &gt; [MCPサーバーのURLをコピー] から取得できます。</li><li>URLは次のようになります：https://your-kibana-instance/api/agent_builder/mcp</li></ul><p>ElasticsearchエンドポイントのURLが必要です。これは通常、Kibana Elasticsearchページの最上部に表示されます。Elasticsearchを<code>start-local</code>で実行している場合、 <code>start-local</code> .envファイルの<code>ES_LOCAL_URL</code>キーにエンドポイントが既に存在します。</p><p>APIキーも必要です。Elasticsearchを<code>start-local</code>で実行している場合、 <code>start-local</code> .envファイルには既に<code>ES_LOCAL_API_KEY</code>が含まれています。それ以外の場合は、<a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">こちらに</a>記載されているように、Kibanaインターフェースを使用してAPIキーを作成できます。</p><ul><li>Kibanaでは、[スタック管理] &gt; [セキュリティ] &gt; [APIキー] &gt; [APIキーの作成] の順に操作します。</li><li>API キーには読み取り権限のみを設定し、<a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/permissions#grant-access-with-roles">ここに</a>記載されているように<code>feature_agentBuilder.read</code>権限を有効にすることをお勧めします。</li><li>エンコードされたAPIキーの値をコピーしてください。</li></ul><p>シェルで必要な環境変数を設定してください。</p><pre><code>export ELASTIC_URL="your-elasticsearch-url"
export ELASTIC_MCP_URL="your-elasticsearch-mcp-url"
export ELASTIC_API_KEY="your-encoded-api-key"</code></pre><h2>サンプルデータセットをインストールする</h2><p>Kibanaから入手可能な<strong>eCommerce orders</strong>データをインストールできます。このデータベースには、eコマースWebサイトからの4,675件の注文に関する情報を含む<strong><code>kibana_sample_data_ecommerce</code></strong>という単一のインデックスが含まれています。各注文について、次の情報があります。</p><ul><li>顧客情報（名前、ID、生年月日、メールなど）。</li><li>注文日。</li><li>注文ID。</li><li>商品（価格、数量、ID、カテゴリー、割引、その他の詳細を含む全商品のリスト）</li><li>SKU。</li><li>合計金額（税抜、税込）。</li><li>合計数量。</li><li>地理情報（都市、国、大陸、場所、地域）。</li></ul><p>サンプルデータをインストールするには、Kibanaの<strong>統合</strong>ページを開き（検索トップバーで「Integration」を検索）、<strong>Sample Data</strong>をインストールしてください。詳細については、<a href="https://www.elastic.co/docs/explore-analyze/#gs-get-data-into-kibana">こちらの</a>ドキュメントを参照してください。</p><p>この記事の目的は、Gemini CLIをElasticsearchに接続し、<strong><code>kibana_sample_data_ecommerce</code></strong>インデックスとやり取りするのがいかに簡単かを示すことです。</p><h2>Elasticsearch MCPの使用方法</h2><p>Geminiで以下のコマンドを使用して接続状況を確認できます。</p><pre><code>/mcp list</code></pre><p>図2に示すように、 <strong><code>elastic-agent-builder</code></strong>が有効になっているはずです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1508423bc1d1bc3c04a1cb01e2d59495a3516ed1-1465x844.png" style="width: 100%;" /><p>Elasticsearchはデフォルトのツールセットを提供しています。詳細は<a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/tools/builtin-tools-reference">こちらを</a>ご覧ください。</p><p>これらのツールを使用して、Elasticsearchと対話し、次のような質問をすることができます。</p><ul><li><code>Give me the list of all the indexes available in Elasticsearch.</code></li><li><code>How many customers are based in the USA in the kibana_sample_data_ecommerce index of Elasticsearch?</code></li></ul><p>質問に応じて、Geminiは利用可能なツールの一つ以上を使って回答を試みます。</p><h2>/elasticコマンド</h2><p>Gemini CLIのElasticsearch拡張機能では、さらに<strong> <code>/elastic</code></strong>コマンドを追加しました。</p><p><strong><code>/help</code></strong>コマンドを実行すると、利用可能なすべての<code>/elastic</code>オプション（図3）が表示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5b2a0727ce7a04354878dd048253d3f4d062324b-1983x230.png" style="width: 100%;" /><p>これらのコマンドは、<code>elastic-agent-builder</code> MCPサーバーの特定のツールを直接実行したい場合に便利です。例えば、以下のコマンドを使用すると、 <code>kibana_sample_data_ecommerce</code>のマッピングを取得できます。</p><pre><code>/elastic:get-mapping kibana_sample_data_ecommerce</code></pre><p>これらのコマンドは、どのツールを呼び出すかをGeminiモデルに頼るのではなく、基本的に特定のツールを実行するためのショートカットです。</p><h2>Elasticsearchスキルの使用方法</h2><p>この拡張機能には、Elasticsearchで利用可能な<a href="https://www.elastic.co/docs/explore-analyze/discover/try-esql">Elasticsearchクエリ言語</a>である<a href="https://github.com/elastic/gemini-cli-elasticsearch/tree/main/skills/esql">ES|QL用のエージェントスキル</a>も付属しています。<a href="https://agentskills.io/home">エージェントスキル</a> は、Gemini CLIのようなAIコーディングエージェントに特定のタスクに合わせたカスタム指示を提供するオープンフォーマットです。<em>段階的開示</em>と呼ばれる概念を採用しており、最初のシステムプロンプトにスキルの簡単な説明のみを追加します。エージェントにElasticsearchへのクエリなどのタスクを実行するように依頼すると、リクエストが関連するスキルと照合され、詳細な指示が動的に読み込まれます。これは、トークン予算を効率的に管理すると同時に、AIが必要とする正確なコンテキストを提供する方法です。</p><p><strong><code>esql</code>スキル</strong>は、Gemini CLIがES|QLクエリを直接クラスターに対して書き込み、実行するように設計されています。ES|QLは強力なパイプクエリ言語で、データ調査、ログ分析、アグリゲーションを非常に直感的に行うことができます。このスキルを有効にすると、ES|QLの構文を調べる必要はなくなり、Gemini CLIにデータについて自然言語で質問するだけであとはエージェントが処理します。</p><p>実行は、ターミナルで実行されるシンプルな<a href="https://curl.se/">curl</a>コマンドを使用して行われます。これは、Elasticsearchが豊富なREST APIを提供し、システムをあらゆるアーキテクチャに容易に統合できるためです。</p><p><strong><code>esql</code>スキルが提供するもの：</strong></p><ul><li><strong>インデックスとスキーマの検出：</strong>エージェントは、スキルに搭載されたツールを使用して、利用可能なインデックスを一覧表示し、フィールドマッピングを取得できます。例えば、eCommerce データセットのクエリを書く前に、エージェントは <strong><code>kibana_sample_data_ecommerce</code></strong> でスキーマチェックを実行して、<strong><code>taxful_total_price</code></strong> や <strong><code>category</code></strong> のような利用可能なフィールドを理解することができます。</li><li><strong>シームレスな自然言語翻訳：</strong>スキルはエージェントに単なるリファレンスマニュアルにとどまらず、ユーザーの意図を解釈するための具体的なガイドを提供します。「サービス別にグループ化された平均応答時間を表示して」といった自然言語によるリクエストを入力すると、エージェントはスキルのバンドルされたパターンマッチングを使用して、入力された言葉を即座に正しいES|QLアグリゲーション、フィルタ、コマンドに変換します。</li><li><strong>自動修正：</strong> クエリが失敗した場合（例：タイプミスマッチや構文エラーなど）、スキルは生成されたクエリとElasticsearchのエラーを正確に返します。これにより、エージェントは即座にクエリを修正して再試行でき、介入する必要がありません。</li></ul><p><code>esql</code>スキルは<code>elastic-agent-builder</code> MCPサーバーのツールとしても利用できるため、このサーバーを一時的に無効にする必要があります。以下のコマンドを使用して無効にすることができます。</p><pre><code>/mcp disable elastic-agent-builder</code></pre><p>そうすれば、Gemini CLIに次のようなプロンプトを入力するだけで済みます。</p><pre><code>Find the top 5 product categories by total sales revenue in the kibana_sample_data_ecommerce index</code></pre><p>エージェントは以下を実行します：</p><ul><li><code>esql</code> スキルの必要性を認識します。</li><li><strong><code>kibana_sample_data_ecommerce</code></strong>のスキーマを確認してください。</li><li><code>FROM kibana_sample_data_ecommerce | STATS total_revenue = SUM(taxful_total_price) BY category.keyword | SORT total_revenue DESC | LIMIT 5</code> のようなES|QLクエリを構築します。</li><li>Elasticsearch APIに対してクエリを実行します。</li><li>最終的な答えを端末に直接表示します。</li></ul><p>ここでは、前回の質問に対するGeminiからの回答例を報告します。</p><pre><code>───────────────────────────────────────────────────────────
> Find the top 5 product categories by total sales revenue in the kibana_sample_data_ecommerce index   ───────────────────────────────────────────────────────────
✓  Activate Skill "esql": Interact with Elasticsearch using ES|QL and curl. Use when querying, managing indices,  …
Skill esql activated. Resources loaded from /home/user/.gemini/extensions/elasticsearch/skills/esql: 
Showing up to 200 items (files + folders).                                                                     /home/user/.gemini/extensions/elasticsearch/skills/esql/                                                            ├───SKILL.md 
└───references/
├───esql-reference.md 
├───generation-tips.md 
└───query-patterns.md

✦ I will start by exploring the mapping of the kibana_sample_data_ecommerce index to identify the fields for product category and sales revenue. Then, I will construct and execute an ES|QL query to determine the top 5 product categories by total sales revenue.

✓  Shell curl -s "${ELASTIC_URL%/}/kibana_sample_data_ecommerce/_mapping" -H "Authorization: ApiKey $(printenv ELAS…                                                                                                                       {  
   "kibana_sample_data_ecommerce": { 
     "mappings": { 
       "properties": {
         "category": { 
           "type": "text", 
           "fields": {
             "keyword": {
               "type": "keyword"
             }
           }
         }, 
         "currency": {
           "type": "keyword"
         }, 

✦ Okay, I have the mapping. The category is products.category.keyword and sales revenue is taxful_total_price. I'll now execute the ES|QL query to get the top 5 categories by revenue.

✓  Shell curl -s -X POST "${ELASTIC_URL%/}/_query" \  -H "Authorization: ApiKey $(printenv ELASTIC_API_KEY)" \  -H …
{ 
   "took": 62,
   "is_partial": false, 
   "completion_time_in_millis": 1772805644164,
   "documents_found": 4675,
   "values_loaded": 12084, 
   "start_time_in_millis": 1772805644102, 
   "expiration_time_in_millis": 1773237644087,  
   "columns": [ 
     { 
       "name": "total_revenue",    
       "type": "double"  
     }, 
     {   
       "name": "products.category.keyword",

✦ The query for top 5 product categories by revenue was successful. I'll format the returned JSON values into a   user-friendly list.

 ✓  Shell 
                             
 Top 5 product categories by total sales revenue: 
 1. Men's Clothing: 149393.91
 2. Women's Clothing: 135099.91
 3. Women's Shoes: 105479.17
 4. Men's Shoes: 91797.92
 5. Women's Accessories: 60830.31 

✦ I've found the answer; now I'll format it for clarity.

 ✓  Shell 

 Here are the top 5 product categories by total sales revenue:                                                        
                                                                      
 | Rank | Category              | Total Revenue | 
 |------|-----------------------|---------------| 
 | 1    | Men's Clothing        | 149,393.91    |
 | 2    | Women's Clothing      | 135,099.91    |
 | 3    | Women's Shoes         | 105,479.17    |
 | 4    | Men's Shoes           | 91,797.92     | 
 | 5    | Women's Accessories   | 60,830.31     | 

✦ I've formatted and presented the answer. I'm finished.
 ───────────────────────────────────────────────────────────</code></pre><p>Geminiモデルが最終的な応答を生成する際にどのような手順を踏むかは非常に興味深い点です。ここでは、モデルの推論プロセスにおけるスキルの影響が明確に見て取れます。モデルがスキルを使用する必要がある、またはシェルコマンドを実行する必要があると初めて認識したとき、ヒューマン・イン・ザ・ループ方式を使用して許可を要求します。</p><p>スキーマの発見、クエリの生成、実行といった面倒な作業を<code>esql</code>スキルが処理することで、回答を得るための仕組みではなく、回答そのものに集中できるようになります。必要なデータが、適切な形式でターミナルに直接表示されます。構文を記述したり、別のアプリケーションに切り替えたりする必要は一切ありません。</p><h2>まとめ</h2><p>この記事では、最近リリースしたGemini CLI用のElasticsearch拡張機能を紹介しました。この拡張機能を使用すると、GeminiおよびElastic Agent Builderが提供するElasticsearch MCPサーバー（バージョン 9.3.0 以降で利用可能）と<code>/elastic</code>コマンドを使用してElasticsearchインスタンスとやり取りできます。</p><p>さらに、この拡張機能には、ユーザーの自然言語からのリクエストをES|QLに変換する <code>esql</code>スキルも含まれています。このスキルは、MCPサーバーが使用できない場合に特に役立ちます。なぜなら、基本的な通信はターミナルで実行されるシンプルなcurlコマンドによって行われるためです。Elasticsearchは、あらゆるプロジェクトに簡単に統合できる豊富なREST APIセットを提供します。これは特にエージェント型AIアプリケーションの開発時に有用です。</p><p>Gemini CLI拡張機能の詳細については<a href="https://github.com/elastic/gemini-cli-elasticsearch">こちら</a>のプロジェクトリポジトリをご覧ください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ElasticのAgent Skills：AIエージェントをElasticのエキスパートに変換]]></title>
            <description><![CDATA[Elastic Agent Skillsを使用して、AIコーディングエージェントにクエリ、可視化、保護、自動化の知識を提供します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/agent-skills-elastic</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/agent-skills-elastic</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Graham Hudgins, Matt Ryan]]></dc:creator>
            <pubDate>Mon, 16 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/09e64953819083168a9ecef0888c7f8bde1a43bd-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>専用プラットフォームでAIコーディングエージェントを使用しようとした開発者、サイト信頼性エンジニア（SRE）、アナリストは皆、同じ壁にぶつかっています。エージェントにクエリの作成、アラートの設定、または調査を依頼すると、惜しいところまではいくものの、正しくない結果になるのです。Elasticにはこの点で優位性があります。10年以上にわたるドキュメント、ブログ記事、コミュニティからの回答によって、AIエージェントは既にほとんどのデータプラットフォームよりもElasticをよく理解しているのです。しかし、その深みにはノイズが伴います。非推奨のAPIが現行のAPIと並んで表示され、古いパターンがベストプラクティスと同じくらい高い評価を受けます。エージェントは、3バージョン前にうまくいったアプローチを自信を持って再現します。なぜなら、そのトレーニングデータでは成功したからです。その結果、修正のコストが発生します。ユーザーは手動でドキュメントをコンテキストにフィードし、ハルシネーションによる構文を修正し、エージェントと一緒に作業するのではなく、エージェントを回避して作業するようになるのです。さらに悪いことに、高度な機能が全く使用されていません。これは、ユーザーがこうした機能を必要としていないからではなく、エージェントがその存在を認識していないためです。</p><p>だからこそ、当社は、Elasticsearch、Kibana、Elastic Observability、Elastic Securityといったプラットフォームに関するネイティブな専門知識である<a href="https://github.com/elastic/agent-skills">Elastic Agent Skills</a>をオープンソースとして提供しています。これらのスキルをすでに使用しているエージェントランタイムにドロップすることで、エージェントを多くの構文を推測する「ジェネラリスト」から、Elasticのエンジニアリングチームが使用する多くのアーキテクチャ標準を活用できる「スペシャリスト」へと改善します。この最初のテクニカルプレビューリリースでは<a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a>との最大限の互換性を備えたスキルに焦点を当てていますが、古いスタックリリースのサポートを強化するなど、急速に進化させる予定です。</p><p>さらに、Elasticはこの問題を両面から解決しようとしています。Elastic Platform上のエージェント向けに<a href="https://www.elastic.co/search-labs/blog/agent-builder-elastic-ga">Elastic Agent Builder</a>（現在一般公開中）を利用して、データのアクセス制御を継承し、搭載の検索・分析ツールを使用し、ダッシュボード、アラート、調査と共にコンテキストで作業するAIエージェントを作成し、チャットすることができます。Elastic Platformで素晴らしいエージェント体験を実現するために懸命に取り組んでいますが、すべてのエージェントがElasticの中に存在するわけではありません。すでにCursor、Claude Codeなどのランタイムを使用されていると思いますが、それらのエージェントでもElasticが正しく動作する必要があります。そこで役立つのがAgent Skillsです。</p><h2>エージェントが専門プラットフォームで苦労する理由</h2><p>大規模言語モデル（LLM）は、非常に有能なジェネラリストです。豊富な事例を含むトレーニングデータを持つため、Pythonを記述したり、Kubernetesのマニフェストを説明したり、Reactコンポーネントをリファクタリングしたりすることができます。しかし、独自のクエリ言語、高度なAPIサーフェス、ドメイン固有のベストプラクティスなどを伴うプラットフォーム固有の作業となると、予測可能な形で不十分さを示します。</p><p>Elasticsearchの場合、そのギャップは具体的に現れます。</p><ul><li><strong>Elasticsearchクエリ言語（ES|QL）は新しい領域です。</strong>LLMはSQLに関するトレーニングを重点的に受けていますが、ES|QLは構文、機能、セマンティクスが異なるパイプ型クエリ言語です。エージェントは、一見もっともらしく見えるものの、解析できないクエリを頻繁に作成します。彼<code>WHERE</code> を <code>| WHERE</code>と混同し、存在しない関数を発明し、パイプベースの合成モデルを完全に見落としています。</li><li><strong>APIサーフェスは広く深いものです。</strong>Elasticsearch、Kibana、Elastic Securityは、検索、インジェスト、アラート、検出ルール、ケース管理、ダッシュボードなど、何百ものAPIを公開しています。エージェントは、一般的なトレーニングデータのみを武器に、どのエンドポイントを呼び出すか、リクエスト本文がどのようなものか、そして応答をどのように処理するかを推測しなければなりません。予測ミスが頻繁に起こるため、信頼が損われることになります。</li><li><strong>ベストプラクティスはトレーニングデータには含まれません。</strong><code>semantic_text</code>とカスタム埋め込みパイプラインは、それぞれどのような場合に使い分けるべきでしょうか？10GBのCSVの取り込みパイプラインはどのように構築すべきでしょうか？<a href="https://www.elastic.co/docs/solutions/security/detect-and-alert/mitre-attandckr-coverage">MITRE ATT&amp;CK</a>技術の適切な検出ルール構文はどれですか？汎用エージェントには、デフォルトで厳選され、信頼性の高い構造化されたElastic固有の知識がロードされていません。エージェントはそうした知識を探し出さなければならず、たとえ見つけたとしても、生の文書には熟練した専門家が持つ判断やベストプラクティスが必ずしも反映されているとは限りません。</li></ul><p>その結果、開発者は自分でコードを書くよりも、エージェントの出力を修正することに多くの時間を費やすことになります。予測していた結果とは言えません。</p><h2>Agent Skills：Platformの知識をエージェント向けにパッケージ化</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8d16ec00d16e70a916c5eef0aaa23fcc735b7186-1067x1280.png" style="width: 100%;" /><p>Agent Skillsとは、エージェントランタイムが動的に読み込むことができる、指示、スクリプト、およ参照資料を含む自己完結型のディレクトリです。スキルがアクティブな時、エージェントは適切なタイミングで適切なコンテキストにアクセスできます。クエリ構文、APIパターン、検証ロジック、実践例などを活用し、一回の試行でタスクを正しく完了できます。</p><p>各スキルは、オープンな<a href="https://agentskills.io">agentskills.io</a>仕様に準拠しています。メタデータと構造化された手順を含む<code>SKILL.md</code>ファイルを含むフォルダです。独自のフォーマットもロックインもありません。スキルは、Cursor、Claude Code、GitHub Copilot、Windsurf、Gemini CLI、Cline、Codexなど、<a href="https://agentskills.io">さまざまな</a>エージェントランタイムで動作します。</p><h3>初期バージョンv0.1.0に含まれる内容</h3><p>最初のスキルセットは、Elastic Stackの5つの分野にまたがっています。</p><ul><li>Elasticsearch APIとのやり取り（検索、インデキシング、クラスター管理）</li><li>ダッシュボード、アラート、コネクターなどのKibanaコンテンツの構築と管理</li><li>Elastic Observabilityの専門知識</li><li>Elastic Securityの専門知識</li><li>Agent Builderで効果的なエージェントを作成</li></ul><h3>スキルは組み合わせ可能です</h3><p>スキルはモノリシックではなく、モジュール式の設計になっています。エージェントは、目の前のタスクに関連するスキルのみを読み込みます。ES|QLクエリを作成中なら、ES|QLスキルが発動します。その結果からダッシュボードを作る必要があるなら、ダッシュボードのスキルを習得します。アプリケーションの健全性を評価したいなら、サービス健全性スキルが対応します。セキュリティアラートの調査の場合は、トリアージスキルが調査が進むにつれてケース管理と対応スキルに連鎖します。</p><p>こうしたこの構成可能性のおかげで、すべてを網羅しようとする単一の巨大なプロンプトは必要なくなります。それぞれのスキルは、その分野に必要な文脈を正確に保持しており、それ以上でもそれ以下でもありません。</p><h2>検索やAIアプリケーションを開発する開発者向け</h2><p>Elasticsearchにデータをロードしたり、クエリを作成したり、インデックスを移行したりする際に、十分なスキルがあれば、コードを生成してエラーが発生し、その原因をドキュメントで調べるという繰り返しのサイクルを短縮できます。</p><p>エージェントにCSVファイルの読み込みを依頼すると、バックプレッシャーを処理し、データからマッピングを推測するストリーミングインジェストツールが使用されます。これは、最初の大きなファイルでメモリ不足になるような、手作業で作成した_bulkループではありません。ES|QLでクエリを実行するように依頼すると、実際のインデックス名とフィールドスキーマを検出し、その後、正しい構文、適切なアグリゲーション、バージョン対応の機能選択を備えた有効なパイプ付きクエリを作成します。これは、3回ものデバッグが必要なSQL風の推測ではなく、正確な処理です。クラスター間での再インデックスを指示すると、完全な運用ワークフローに従います。明示的なマッピングで送信先を作成し、スループットに合わせて設定を調整し、ジョブを非同期で実行し、完了時には本番環境の設定を復元します。これは、経験豊富なオペレーターが従う半分のステップをスキップする単なる_reindex呼び出しではありません。</p><p>修正が必須となるそれらしい出発点を示すエージェントの代わりに、出力が実際に機能するための運用規律を組み込んだエージェントが手に入ります。</p><p><strong>Elastic Agent Skillsの使用によるインパクトの例</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Eval</td><td style="border: 1px solid #ccc; padding: 4px;">スキルによる変更内容</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-監査-クエリ-失敗したログイン</td><td style="border: 1px solid #ccc; padding: 4px;">一般的な検索の代わりにスキルの監査ログクエリパターンを使用</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-authz-role-mapping-ldap</td><td style="border: 1px solid #ccc; padding: 4px;">正しいロールマッピングAPI呼び出し構造を出力</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">esql-basic-query</td><td style="border: 1px solid #ccc; padding: 4px;">ES|QLパイプ構文をクエリDSL上に記述</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">esql-error-handling</td><td style="border: 1px solid #ccc; padding: 4px;">フィールド名を推測する代わりにスキーマを先に設定</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">esql-schema-discovery</td><td style="border: 1px solid #ccc; padding: 4px;">インデックス名を推測したことは一度もなし</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-ingest-csv-with-infer</td><td style="border: 1px solid #ccc; padding: 4px;">--infer-mappingsのみを使用し、--source-format csvとの組み合わせは回避（組み合わせると空のインデックスがマッピングされるため）</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-ingest-json-file</td><td style="border: 1px solid #ccc; padding: 4px;">大容量ファイルを処理できる堅牢なインジェスト方式を採用</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-reindex-local-async</td><td style="border: 1px solid #ccc; padding: 4px;">まずレプリカ数0、更新間隔「-1」で宛先インデックスを作成し、その後非同期で再インデックスを実行、ベースラインはあらゆる準備をスキップ</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">es-security-403-privileges</td><td style="border: 1px solid #ccc; padding: 4px;">一般的なアドバイスではなく、スキルの診断ワークフローに従って特権エラーを解決</td>
                    </tr>
              </tbody>
            </table>
          <h2>セキュリティチーム向け</h2><p>セキュリティチームは、アラートのトリアージ、検出ルールの調整、ケースの管理といった、同じ運用ワークフローを毎日繰り返しています。Agent Skillsは、AIエージェントがこれらのワークフローを正しく実行し、適切な順序で適切なフィールド名で適切なAPIを呼び出せるように、その手順知識をエンコードします。IDEを離れることなく、ゼロから完全に機能するElastic Security環境を構築するまでの手順を実際に確認するには、<a href="https://www.elastic.co/security-labs/agent-skills-elastic-security">「AIエージェントからElastic Securityを使い始める」</a>を参照してください。</p><h2>オブザーバビリティおよび運用チーム向け</h2><p>Elastic Observabilityのための新しいAgent Skillsは、複雑なシステムのインスツルメンテーション、SLOの管理、複雑なデータの選別、サービスの健全性の評価といった運用の手間を軽減します。ネイティブのElastic専門知識をAIエージェントに直接組み込むことで、チームはシンプルな自然言語を使用して複雑なオブザーバビリティワークフローを実行できます。これにより、SREと運用チームはインシデントをより迅速に解決し、信頼性の高いシステムをより簡単に保守できます。詳しくは<a href="https://www.elastic.co/observability-labs/blog/elastic-agent-skills-observability-workflows">こちらのブログ記事</a>をご覧ください。</p><h2>オープンソース、オープン仕様、コミュニティ主導</h2><p>エージェントの知識はオープンであるべきだと当社は考えているため、Agent SkillsをApache 2.0ライセンスで公開しています。スキルが準拠する<a href="https://agentskills.io">agentskills.io</a>の仕様はオープンスタンダードであり、Elastic独自のフォーマットではありません。スキルが閉鎖的な環境ではなく、コミュニティ全体の取り組みとなることを望んでいます。</p><h2>より大きな全体像の一部</h2><p>Agent Skillsは、Elasticsearchを最もエージェントにとって使いやすいデータプラットフォームにするための広範な取り組みの一環です。Elasticsearch Platform上で動作するエージェントの場合、<a href="https://www.elastic.co/search-labs/blog/agent-builder-elastic-ga">Agent Builder</a>は、データのアクセス制御と権限を継承し、検索と分析のための組み込みツールとカスタムツールを提供し、ユーザーがダッシュボード、アラート、調査と共にコンテキスト内でエージェントと対話できるようにすることで、さらに進化します。最後に、Agent Builderでのスキルのサポートが間もなく開始されます。これにより、開発者はElastic Agent Skillsやその他のソースのスキルを柔軟に活用して、Elasticsearchプラットフォームで安全でコンテキストが強化されたチャットと自動化を実現できます。</p><p>他の領域で動作するエージェントのために、以下のようにオープンエコシステムに投資しています。</p><ul><li><strong>モデルコンテキストプロトコル（MCP）サーバーの拡張：</strong>Agent Builderの<a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/mcp-server">MCPエンドポイント</a>を、現在の検索、ES|QL、インデックス操作以外のより多くのツールで拡張します。</li><li><strong>認証機能の改善：</strong>エージェントが安全に接続しやすくすることで、APIキーの手動コピー＆ペーストを排除することを目指します。</li><li><strong>LLMで読み取り可能なドキュメント：</strong>エージェントがElastic APIを独自に発見して理解できるように、 <code>llms.txt</code>ファイルと<code>AGENTS.md</code>ファイルを公開します。</li><li><strong>エージェントワークフロー用のコマンドラインインターフェース（CLI）：</strong>接続管理や一般的な操作をエージェントにとって使いやすいものにするコマンドラインツール。</li></ul><p>スキルは本日からご利用いただけるレイヤーです。残りはこれから提供されます。</p><h2>使用を開始</h2><p><strong>始める前に：</strong>AIコーディングエージェントは、実際の認証情報、実際のシェルアクセス、そして多くの場合、実行しているユーザーの完全な権限を使用して動作します。エージェントがセキュリティワークフローに向けられる場合、自動化されたシステムに検出ロジック、対応アクション、機密テレメトリへのアクセスを委ねることになり、リスクはより高くなります。すべての組織のリスクプロファイルは異なります。AIを活用したセキュリティワークフローを有効にする前に、<strong>エージェントがアクセスできるデータ、実行できるアクション、予期しない動作が発生した場合に何が起こるかを評価してください</strong>。</p><p>Elastic Agent Skillsをエージェントランタイムにインストールする：</p><p><code>npx skills add elastic/agent-skills</code></p><p>これにより、インストールされているエージェントランタイムが自動的に検出され、スキルが適切な構成ディレクトリに配置されます。そこからエージェントが自動的にそれらを拾います。</p><p>また、<a href="https://github.com/elastic/agent-skills">スキルカタログ</a>を直接ブラウズし、スキルフォルダをエージェントの設定ディレクトリにコピーして、個別に手動でスキルをインストールすることもできます。</p><p>まだElasticsearchクラスターをお持ちではありませんか？<a href="https://cloud.elastic.co/registration">Elastic Cloudの無料トライアル</a>を始めましょう。1分ほどで完全に構成された環境を構築できます。</p><p><strong>プロジェクトを探索：</strong></p><ul><li><a href="https://github.com/elastic/agent-skills">Agent Skillsリポジトリ</a></li><li><a href="https://agentskills.io">agentskills.io 仕様</a></li><li><a href="https://www.elastic.co/docs">Elasticsearchドキュメント</a></li><li><a href="https://cloud.elastic.co/registration">Elastic Cloudの無料トライアル</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearchによるエンティティ解決、パート4：究極のチャレンジ]]></title>
            <description><![CDATA[ショートカットを防ぐために設計された、非常に多様な「究極のチャレンジ」データセットにおけるエンティティ解決の課題の解決と評価。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/entity-resolution-elasticsearch-llm-challenges</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/entity-resolution-elasticsearch-llm-challenges</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
            <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/70fb0ff949f6db9ac9b8a28ecb4329ab915ebf46-720x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>これまでのインテリジェントなエンティティ解決は2つの方法で実装されてきました。いずれのアプローチも、エンティティの準備と抽出、そしてElasticsearchによる候補の取得という同じ方法で始まります。そこから、プロンプトベースのJSON生成または関数呼び出しのいずれかを通じて、大規模言語モデル（LLM）を使用して候補を評価し、モデルにその判断について透明性のある説明を提供することを要求します。</p><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-function-calling">前回の記事</a>で見たように、関数呼び出しによってもたらされる一貫性は、単に便利な最適化ではなく、不可欠なものです。構造的なエラーを評価ループから除去したところ、標準的なシナリオ（ティア4データセットなど）の結果が劇的に向上しました。</p><p>しかし、答えるべき明白な疑問はまだ残っています。</p><p><em>状況が本当に複雑になってきた場合でも、このアプローチは有効でしょうか？</em></p><p>現実世界におけるエンティティ解決が単純なケースで失敗することはめったにありませんが、名前が言語、文化、文字体系、時代、組織の境界を越える場合に失敗します。人が名前ではなく肩書きで言及されている場合、会社名が変更された場合、音訳が一貫していない場合、そして（スペルではなく）文脈だけが言及と現実世界の実体を結びつける唯一の要素である場合、この方法は失敗します。</p><p>そこで、このシリーズの最後の記事として、このシステムにいわば<strong>究極のチャレンジ</strong>を課すこととしました。</p><h2>なぜこれが究極の挑戦なのでしょうか？</h2><p>以前の評価では、ますます複雑になるデータセットを用いてシステムをテストしました。前回の記事で触れた第4段階に到達する頃には、すでにニックネーム、称号、多言語名、意味的な参照などが混在する状況になっていました。これらのテストにより、アーキテクチャ自体は健全であることが示されましたが、信頼性の問題、特に不正な形式のJSONが原因で、リコールが抑制されていることがわかりました。</p><p>関数呼び出しの仕組みが整ったことで、ようやく安定した基盤ができました。そのおかげで、さらに興味深い質問をする機会が得られました。</p><p><em>1つの統一されたパイプラインで <strong>多くの異なる種類の</strong>エンティティ解決問題を一度に処理することは可能でしょうか？</em></p><p>究極のチャレンジデータセットは、まさにその側面を徹底的に追求するために設計されました。</p><p>このデータセットは、（ニックネームや音訳といった）単一の困難に焦点を当てるのではなく、 <strong>50種類以上の異なる課題タイプ</strong>を組み合わせています。</p><ul><li>文化的な命名規則。</li><li>タイトルに基づく参照。</li><li>事業上の関係性と過去の社名変更。</li><li>多言語および異文字表記での言及。</li><li>上記のうち複数を組み合わせた複合的な課題。</li></ul><p>重要なのは、この試みが特定の狭い用途向けに最適化することではなく、ルールがエンティティごとに変化した場合でも<em>設計パターン</em>が通用するかどうかをテストすることです。</p><h2>データセットの概要</h2><p>究極のチャレンジデータセットは以下で構成されます。</p><ul><li>個人、組織、機関などの<strong>50のエンティティ</strong>。</li><li>構造と言語の複雑さが異なる<strong>約60本の記事</strong>。</li><li>大きく以下に分類される<strong>51種類の異なるチャレンジカテゴリー</strong>。<ul><li>文化的な命名規則。</li><li>肩書きと職務上の背景。</li><li>事業と組織間の関係。</li><li>多言語および音訳の課題。</li><li>複合シナリオとエッジケースのシナリオ。</li></ul></li></ul><p>本シリーズの前半で、生成AIを用いてデータセットを作成することは諸刃の剣であることを確認しました。生成AIがなければ十分な規模と多様性を備えたテストデータを収集することは極めて困難になりますが、このモデルは放置すると、物事をあまりにも単純化しすぎる傾向があります。</p><p>例えば、初期世代の検証段階で、モデルに「ロシアの大統領」といったフレーズがウラジーミル・プーチンの明示的な別名として含まれていることが判明しました。それは今日では妥当に思えるかもしれませんが、文脈解決能力をテストするという目的を損なうことになります。記事が1990年代のロシアについて論じている場合はどうなるでしょうか？システムは、ハードコードされたエイリアスに頼るのではなく、文脈から正しいエンティティを推論するべきです。</p><p>そのため、このデータセットは<strong>ショートカットが効かない</strong>ように意図的に設計されています。システムが意味を推測することが想定されている場合、別名は明示的にリスト化されません。記述的なフレーズはエンティティにあらかじめリンクされていません。正確な一致は、単なるローカルテキストだけでなく、記事レベルの文脈によって決まることが多いです。</p><p><strong>重要な注意点：</strong>本システムは多様なシナリオにおける機能を実証していますが、これはあくまで教育用プロトタイプです。実際の制裁対象組織の監視を扱う本番システムでは、追加の検証、コンプライアンスチェック、監査証跡、および機密性の高いユースケースに対する特別な処理が必要となります。</p><h2>これらのシナリオが難しい理由</h2><p>このシリーズの最初の投稿で、単純であいまいな例「新しいSwiftアップデートが登場しました！」を紹介しました。課題は、「Swift」という単語が、文脈によって複数の現実世界の実体として解釈される可能性があることです。この例はより広範な真実、つまり、自然言語は本質的に曖昧であるということを捉えています。</p><p>したがって、エンティティ解決は単なる文字列照合の問題ではありません。人間は日常的に、共通の知識、文化的規範、状況的文脈に頼って参照関係を解決していますが、私たちは自分がそうしていることにほとんど気づきません。</p><p>よくあるケースをいくつか考えてみましょう。</p><ul><li>「大統領」という称号は地政学的・時間的な文脈なしには意味がありません。</li><li>会社名は、記事がいつ書かれたかによって、親会社、子会社、または以前のブランドを指す場合があります。</li><li>人名は、言語や文化によって、異なる順序、書体、または音訳で表記されることがあります。</li><li>同じフレーズでも、文脈によって異なる対象を指す場合があり、システムは一致を受け入れるのと同じくらい確信を持って一致を<em>拒否</em>できなければなりません。</li></ul><p>これらすべてを適切に処理する単一のルールセットは存在しないため、このプロトタイプは懸念事項を非常に積極的に分離しています。</p><ul><li>Elasticsearchは候補の範囲を効率的かつ分かりやすく絞り込みます。</li><li>LLMは、判断が必要で、それ自体を説明しなければならない場合にのみ使用されます。</li><li>検索と推論は別個のステップのままです。</li></ul><p>課題の種類が多様化するにつれて、この区分けはさらに重要になります。</p><h2>システムが特別なケースなしに多様性を処理する仕組み</h2><p>この評価で最も興味深い結果の一つは、<em>変更しなかった</em>点にあります。</p><ul><li>日本語名に関する特別なロジックは追加して<strong>いません</strong>。</li><li>アラビア語の父称に関するカスタムルールは追加して<strong>いません</strong>。</li><li>ハードコーディングされたマッピングを過去の会社名に追加して<strong>いません</strong>。</li></ul><p>その代わりに、このシステムはシリーズ前半で紹介したものと同じ主要要素に依存していました。</p><ul><li>セマンティック検索のためにインデックス化されたコンテキスト強化エンティティ。</li><li>Elasticsearchでのハイブリッド検索（完全検索、エイリアス、セマンティック）。</li><li>少数の、明確に定義された一致候補セット。</li><li>関数呼び出しと最小スキーマによって制約されたLLM判断。</li></ul><p>これは、システムの柔軟性が、増え続けるルールのコレクションからではなく、<strong>表現とアーキテクチャ</strong>から生まれることを示唆しています。</p><p>システムが成功するのは、適切な候補が取得され、LLMが参照が特定のエンティティにマッピングされる（またはされない）理由を説明できる十分なコンテキストがある場合です。</p><h2>結果：パフォーマンスの概要</h2><p>究極のチャレンジデータセットにおいて、システムは以下のような全体的な結果を生み出しました。</p><ul><li><strong>精度：</strong>約91％</li><li><strong>再現率：</strong>約86％</li><li><strong>F1スコア：</strong>約89%</li><li><strong>LLM合格率：</strong>約72％</li></ul><h3>チャレンジの種類ごとのパフォーマンス</h3><p>チャレンジの種類ごとに結果を分解すると、強みと限界が明らかになります。</p><p><strong>最も優れたパフォーマンス（F1スコア100%）</strong>が見られた分野は以下のとおりです。</p><ul><li>文字体系間の照合（キリル文字、韓国語、中国語の企業名）。</li><li>ヘブライ語のシナリオ（父称、専門職称、宗教称号、音写）。</li><li>事業階層構造（航空宇宙、多角化製造業、多部門企業）。</li><li>職業上の肩書き（学術、軍事、政治、宗教）。</li><li>複数の文字体系を含む日本語シナリオの組み合わせ。</li></ul><p><strong>優れたパフォーマンス（F1スコア80～99％）</strong>には以下が含まれます。</p><ul><li>国際的な政治家（98％）。</li><li>歴史的な名称変更（90%）。</li><li>複雑なビジネス階層（89％）。</li><li>日本の企業名（93％）。</li><li>異言語間の音訳（86％）。</li><li>アラビア語の父称（86％）。</li></ul><p><strong>より困難な分野</strong>には以下が含まれます。</p><ul><li>高度な音訳（中国語、韓国語）：0% F1。</li><li>特定の日本語シナリオ（敬称、名前の順序、表記体系のバリエーション）：約67% F1。</li><li>一部のアラビア語のシナリオ（会社名、機関の参考文献）：約40％ F1。</li></ul><p>ここで重要なのは、<em>なぜ</em>システムがこれらのケースで機能不全に陥ったのかという点です。失敗の原因は、全体的なアプローチが破綻したことではなく、特定のコンポーネントの限界、特に特定の多言語シナリオにおけるセマンティック検索に使用される高密度ベクトルモデルの限界にありました。</p><p>検索と判断が明確に分離されているため、パフォーマンスを向上させるためにシステムを書き換える必要はありません。より高性能な多言語埋め込みモデルの採用、エンティティコンテキストの強化、または検索戦略の洗練により、コアアーキテクチャを変更することなく、これらのカテゴリー全体で結果が向上します。</p><p>アーキテクチャーの観点から見ると、それが真の成功指標です。</p><h2>この結果が設計について教えてくれること</h2><p>シリーズを振り返ると、いくつかのパターンが際立っています。</p><ul><li><strong>準備は巧みなマッチングよりも重要です。 </strong>エンティティに事前にコンテキストを付加することで、後々の曖昧さを劇的に減らすことができます。</li><li><strong>LLMは、レトリバーではなく、判断者として最も価値があります。</strong>したがって、検索を求めるよりも、<em>なぜ</em>一致が意味をなすかを説明するよう求めることの方がはるかに強力です。</li><li><strong>信頼性が精度を実現します。</strong>関数呼び出しは、JSONを整理しただけでなく、取得ステップにすでに潜在していた想起を解放しました。</li><li><strong>一般化は専門化に勝ります。</strong>厳選された少数の抽象化によって、独自のロジックを必要とせずに数十種類の課題に対応できました。</li></ul><p>これが、プロトタイプが意図的にElasticsearchネイティブであり、LLMの使用方法が意図的に保守的である理由です。目標は検索を置き換えることではなく、意味が重要な状況において、検索を説明可能なものにすることです。</p><h2>結びに</h2><p>究極のチャレンジとは、完璧な指標を追い求めることではなく、より根本的な問いに答えることでした。</p><p><em>透明性が高く、検索優先で、LLMを活用したアーキテクチャは、ルールやブラックボックスに陥ることなく、現実世界のエンティティの曖昧さを処理できるでしょうか？</em></p><p>その回答は、この教育用プロトタイプに関しては「はい」ですが、本番環境での強化、コンプライアンス、監視、データの品質に関する明確な注意事項があります。エンティティの一致が行われた<em>理由</em>を正当化する必要のあるシステムを構築している場合、このパターンは真剣に検討する価値があります。このシリーズを通して、エンティティ解決は必ずしも難解なものではないということが伝われば幸いです。適切に関心事を分離することで、それは論理的に考え、測定し、改善できるものになります。</p><p>この研究はまた、より広範なアーキテクチャパターンを示唆しています。浮かび上がってくるのは、古典的な検索拡張生成（RAG）の、わずかではあるが重要な進化です。検索結果を直接生成に供給するのではなく、明示的な評価ステップを導入します。LLMはまず、取得された候補を評価し、妥当性を確認するために使用され、承認された結果のみが生成の強化に使用されます。これは、Generation-Augmented Retrieval-Augmented Generation with Evaluation、つまりGARAGEと名付けられるでしょう。うまい頭字語が嫌いな人なんていませんから。</p><p>このパターンは、他にどのような用途で活用できるでしょうか？信頼性、透明性、そして論理的な説明を必要とするシステムは、まさにうってつけの候補と言えます。この分野における今後の研究は、今回得られた成果と同様に説得力のあるものとなるはずであり、コミュニティが今後どのような展開を見せるのか、非常に楽しみです。</p><h2>次のステップ：試してみましょう</h2><p>究極のチャレンジが実際に動作する様子をご覧になりたいですか？実際の実装、詳細な説明、実践的な例を含む完全なウォークスルーについては、<a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/notebooks#:~:text=5%20minutes%20ago-,05_ultimate_challenge_v3.ipynb,-Initial%20public%20lab"><strong>Ultimate Challenge notebook</strong></a>を参照してください。</p><p>完全なエンティティ解決パイプラインにより、本番での使用に必要なコアコンセプトとアーキテクチャが示されています。これを基盤に、透明性と説明可能性を維持しながら、ニュース記事を監視し、エンティティの言及を追跡し、どのエンティティがどの記事に登場するのかについての質問に回答するシステムを構築できます。<br/></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ElasticsearchにおけるHNSWの適応的早期終了]]></title>
            <description><![CDATA[ElasticsearchにHNSWの新しい適応的早期終了戦略を導入します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/hnsw-elasticsearch-adaptive-early-termination</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/hnsw-elasticsearch-adaptive-early-termination</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Tommaso Teofili]]></dc:creator>
            <pubDate>Mon, 02 Mar 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e6d3186f609dd56dc5ffe33d70fa9e5cfa05b51f-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearchは、<a href="https://www.elastic.co/search-labs/blog/hnsw-graph">Hierarchical Navigable Small World</a>（HNSW）アルゴリズムを使用して、近接グラフ上でベクトル検索を実行します。HNSWは、k近傍法（KNN） の結果の品質と関連コストの間で適切なトレードオフを提供することが知られています。</p><p>HNSWでは、グラフ内の候補ノードを反復的に拡張し、これまでに発見された最も近い近傍の制限されたセットを維持することで検索が進行します。各拡張にはコスト（ベクトル演算、ディスクへのランダムシークなど）がかかり、そのコストに対する限界効用は検索が進むにつれて減少する傾向があります。</p><p>HNSWグラフのトラバーサルを最適化する1つの方法は、新しい真の近傍を見つける周辺尤度が増加しない場合に検索を停止することです。このため、<a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules#index-dense-vector-hnsw-early-termination">Elasticsearch 9.2</a>では、新しい<a href="https://www.elastic.co/search-labs/blog/hnsw-knn-search-early-termination">早期終了メカニズム</a>を導入しました。これは、グラフノードを訪問しても一定回数連続して十分な数の新しい最近傍が提供されない場合に、検索プロセスを停止するものです。</p><p>この記事では、HNSWの前述の早期終了メカニズムを改良して、さまざまなデータセットやデータ分布に適したものにする方法について説明します。</p><h2><strong>HNSWでの早期終了</strong></h2><p>HNSW では、近接グラフ内の候補ノードを反復的に拡張し、これまでに発見された最も近い近傍の制限されたセットを維持して、グラフ全体を訪問するか、早期終了基準を満たすまで、検索が続行されます。</p><p>したがって、早期終了は必ずしも最適化ではなく、<strong>検索アルゴリズム自体の一部</strong>です。停止を決定する瞬間が、効率性と再現率のバランスを決定します。Elasticsearchでは、HNSWのクエリを早期終了させる方法がすでにいくつか存在します。</p><ul><li>固定された最大数のノードが訪問されます。</li><li>一定のタイムアウトに達した場合。</li></ul><p>これらのルールは単純かつ予測可能ですが、<strong>検索が実際に何をしているかにはほとんど関係がありません</strong>。また、これらは主に、クエリがエンドユーザーにとって妥当な時間内に完了することを確認するために使用されます。</p><p>前回の<a href="https://www.elastic.co/search-labs/blog/hnsw-knn-search-early-termination">ブログ投稿</a>ではHNSWにおける冗長性の概念を紹介しました。つまり、HNSWが新しい候補ノードを評価し続けても、さらに最も近い近傍が見つからない場合、冗長な計算が発生します。</p><h2><strong>忍耐度：努力ではなく進歩を測る</strong></h2><p><em>忍耐度</em>という概念は、<strong>努力ではなく進歩</strong>を中心に早期終了を再構築します。</p><p>次のように尋ねる代わりに</p><p>「何ステップ進んだ？」</p><p>新たに次のように問いかけます。</p><p>「希望を失うまでに受け入れられる無駄な計算はどれだけかな？」</p><p>HNSW検索では、通常、初期の探索によっ上位k候補セットの最高の改善がもたらされます。HNSWグラフ探索の最初のステップでは、アルゴリズムがクエリベクトルにますます近い近傍を検出し続けるため、近傍のセットは継続的に更新されます。時間が経ち、検索が収束するにつれて、これらの改善はまれになります。<a href="https://cs.uwaterloo.ca/~jimmylin/publications/Teofili_Lin_ECIR2025.pdf">忍耐度ベースの終了</a>はこのパターンを監視し、改善が一定期間停止した時点で検索を終了します。</p><p>実際には、HNSWグラフを訪問する際、候補ノードをホップしながらキューの飽和比も計算します。これは、最新のグラフノードを訪問中に変更されなかった最も近い近傍の割合（または最後の反復中に導入された新しい近傍の数の逆数）を測定します。このような比率が連続した反復処理で大きくなりすぎると、グラフの訪問を停止します。</p><p>概念的には、忍耐度はHNSWの検索を<strong>収穫逓減</strong>プロセスとして扱います。リターンが平坦になると、グラフの調査を継続してもほとんど利益は得られません。</p><p>この枠組みは、終了を恣意的な固定された制限ではなく、<em>観察可能な結果</em>に直接結び付けるため、強力です。</p><p>このスマートな早期終了手法を使用する利点は、HNSWグラフ探索では、ほぼ完璧な相対再現率を維持しながら、より少数のグラフノードを訪問する傾向があることです。</p><p>これを視覚化するために、FinancialQAとQuoraという2つのデータセットと、JinaV3とE5-smallというモデルで、忍耐度に基づく早期終了（ <em><code>et=static</code></em>とラベル付け）で取得した訪問ノードあたりの再現量を、デフォルトのHNSW動作（ <em><code>et=no</code></em>とラベル付け）と比較してプロットすることができます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a9d07c5153ea64a2426c82487c36846030692bb9-1600x945.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/dac56125c4b16d1b596c9876b6ca9ac7b2dc87fa-1600x944.png" style="width: 100%;" /><h2><strong>静的しきい値とHNSWのダイナミクス</strong></h2><p>実際には、Elasticsearchでは<strong>静的しきい値</strong>を使用してこれが実装されます。1つのしきい値は、<strong>飽和しきい値</strong>、つまり、最適ではないと判断される飽和度の比率を指します。もう1つのしきい値は、最適ではないキュー飽和を維持しながら連続して訪問できるグラフノードの数、つまり<strong>忍耐しきい値</strong>を指します。</p><p>Elasticsearch 9.2でこの早期終了戦略を導入したとき、レイテンシーとメモリ消費の面でメリットを得ながら再現率を可能な限り高められるように、保守的なデフォルトを選択することにしました。このため、KNNクエリでは飽和しきい値を100%に、忍耐しきい値を <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query#knn-query-top-level-parameters:~:text=search%20request%20size.-,num_candidates,-(Optional%2C%20integer)%20The"><em><code>num_candidates</code></em></a> の（有界の）30%に設定しています。</p><p>多くのシナリオでは、これらの設定はうまく機能しますが、同じ数の近傍を要求する2つのクエリでは、収束動作が根本的に異なる可能性があります。あるクエリは密集した局所的な近傍に遭遇し、すぐに飽和します。他のクエリは競争力のある候補を見つけるまでに、長くまばらな経路を通過しなければなりません。後者は、効果的に処理するのが最も困難であることが判明しました。</p><p>その結果、次のようなことに気付くことがありました。</p><ul><li>簡単なクエリに対する過度の探索。</li><li>難しいクエリに対する時期尚早の終了。</li></ul><p>したがって、固定されたしきい値は収束に関する全体的な仮定をエンコードしますが、HNSWをさまざまなダイナミクスに適応させることができると考えました。</p><h2><strong>HNSWの早期終了を適応的に</strong></h2><p>適応的早期終了は、この問題に異なる角度からアプローチします。事前に定義された停止しきい値を強制する代わりに、アルゴリズムが<strong>検索のダイナミクス自体からいつ停止するかを推測します</strong>。</p><p>したがって、2つの連続した候補間のキュー飽和比を比較する代わりに、即時平滑化発見率 $d_{q,i} $（クエリ<em>q</em>の最後の訪問<em>i</em>で導入された新しい隣接ノードの数）と、グラフ訪問中のそのような発見率の移動平均$\mu_{q,i}$と標準偏差$\sigma_{q,i}$を導入することにしました（<a href="https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford&#x27;s_online_algorithm">ウェルフォードのアルゴリズム</a>を使用）。これらの発見率に関する統計はクエリごとに計算されるため、この情報をもとに各クエリの忍耐度を判断できます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1958be7ca4425ade66eaf621ada3533173183598-694x118.png" style="width: 100%;" /><p>以前は静的であったしきい値は、発見率の統計に対して適応的になります。飽和しきい値はローリング平均と標準偏差の合計になり、一方で忍耐力は標準偏差に反比例して適応およびスケーリングされます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f7ee4a55c24853b657df26052b275e8bd76cf0f9-654x156.png" style="width: 100%;" /><p>早期終了ルールは変わらず、飽和は即時発見率が適応飽和しきい値より低い場合に発生します。適応的忍耐度よりも大きい連続候補訪問回数にわたって飽和が継続する場合、グラフ訪問は停止します。</p><p>こうすることで、KNNクエリの <em><code>num_candidates</code></em> パラメーターに依存しない動作（早期終了に関係なく、常に設定されるか、デフォルトのままになる場合がある）が得られ、各クエリとベクトル分布に動的に適応しやすくなります。</p><p>適応型戦略（ <em><code>et=adaptive</code></em>とラベル付け）を使用したFinancialQAおよびQuoraでの訪問ノードあたりの再現率は、静的戦略（ <em><code>et=static</code></em> ）およびデフォルトのHNSW動作（ <em><code>et=no</code></em> ）と比較した場合、高くなっています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2a906997d9a25d74c7038bd9661bc97581e7258e-1600x938.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3a114911e232c351dbb814cea20e8b0f1415a717-1600x925.png" style="width: 100%;" /><p>適応的早期終了はElasticsearch 9.3ではHNSWの高密度ベクトルフィールドに対してデフォルトでオンになっています（最終的には<a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules#index-dense-vector-hnsw-early-termination">同じインデックスレベルの</a>設定でオフにすることができます）。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Common Expression Language（CEL）：CEL入力がElastic Agent統合におけるデータ収集を改善する方法]]></title>
            <description><![CDATA[Common Expression Languageと他のプログラミング言語との違い、FilebeatのCEL入力用の拡張方法、Elastic Agent統合でデータ収集ロジックを表現する上での柔軟性についてご紹介します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/common-expression-language-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/common-expression-language-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Chris Berkhout]]></dc:creator>
            <pubDate>Fri, 27 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/985c50bfabee3348494eb4307f0b3375a97a0644-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elastic Agent<a href="https://www.elastic.co/integrations">統合</a>により、ユーザーは幅広いソースからデータをElasticsearchに取り込むことができます。コレクションロジック、取り込みパイプライン、ダッシュボード、その他のアーティファクトをパッケージにまとめ、Kibana Webインターフェースからインストールおよび管理できます。</p><p>統合では、データ収集を行うために<a href="https://www.elastic.co/docs/reference/beats/filebeat/configuration-filebeat-options">Filebeat入力</a>を設定します。HTTP APIからデータを収集するために、私たちはしばしば<a href="https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-httpjson">HTTP JSON入力</a>を使用してきました。しかし、基本的なリスティングAPIでさえ、細部において大きく異なることがあり、HTTP JSON入力のYAMLで構成された変換のモデルでは必要なコレクションロジックを表現するのが難しく、場合によっては不可能になることがあります。</p><p><a href="https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-cel">Common Expression Language（CEL）入力</a>は、HTTP APIとのより柔軟な相互作用を可能にするために導入されました。<a href="https://cel.dev/">CEL</a>は、条件やデータ変換を高速、安全、かつ拡張性のある方法で表現するアプリケーションに組み込めるよう設計された言語です。CEL入力を使用すると、統合ビルダーは設定を読み取り、自身の状態を追跡し、リクエストを作成し、応答を処理し、最終的に取り込む準備が整ったイベントを返すことができる1つの式を記述できます。</p><p>この記事では、CELが他のプログラミング言語との違い、CEL入力用の拡張方法、そしてデータ収集ロジックを表現する上で提供するその柔軟性とパワーについて見ていきます。</p><h2>CELと入力での動作の仕組み</h2><p>CELは式言語で、ステートメントはありません。CELを記述する場合、ステートメントを記述して何を実行するかを指示するのではなく、式を記述してどのような値を生成するかを指示します。すべてのCEL式は値を生成し、小さな式を組み合わせて大きな式にすることで、より複雑なルールに従った結果を生成することができます。後ほど、他の言語のステートメントで記述できる内容に対して式を使用する方法について説明します。</p><p>CELは意図的に非チューリング完全言語であり、無限ループは許可されません。後ほど、マクロを使用してリストやマップを処理する方法を見ていきますが、無限ループを避けることで、この言語は個々の式に対して予測可能で制限された実行時間を保証します。</p><p>CEL入力は、CELプログラム（式）といくつかの初期状態で設定されます。状態はプログラムの入力として提供され、プログラムは出力状態を生成するために評価されます。出力状態にイベントのリストが含まれている場合、それらは削除されて公開されます。残りの出力状態は、次の評価の入力として使用されます。出力状態に1つ以上のイベントとフラグ<code>want_more: true</code>が含まれている場合、次の評価はすぐに実行されます。それ以外の場合、設定された間隔の残りの時間スリープしてから続行します。以下は入力の制御フローの簡略的な図です。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/42671541f97e2dba808fd53969fe12f517917f9a-1600x529.png" style="width: 100%;" /><p>各評価の出力は、入力が実行されている限り、次の評価への入力として順に渡されます。キー「<code>cursor</code>」下の出力データはディスクに永続化され、入力の再起動後に再ロードされますが、その他の状態は再起動をまたいで保存されません。</p><p>CEL言語自体は機能が制限されており、副作用を回避しますが、拡張可能です。<a href="https://github.com/google/cel-go">cel-go</a>の実装では、オプションの構文や型などの機能が追加されています。<a href="https://github.com/elastic/mito">Mito</a>ライブラリはcel-goを基盤とし、HTTPリクエストの機能を含むより多くの機能を追加しています。CEL入力はMito版のCELを使用しています。</p><h2>Mitoの操作</h2><p>CEL入力を使用して統合を構築またはデバッグする際、最も重要なことは、与えられた入力状態に対してCELプログラムがどのような出力状態を生成するかを理解することです。開発中は、完全なElasticスタックに囲まれた入力でCELプログラムを実行するのは面倒な場合があります。より高速なフィードバックループを実現する1つの方法は、Mitoのコマンドラインツールを使用することです。このツールを使用すると、CELプログラムを直接実行し、特定の入力に対して生成される出力を確認できます。</p><p>MitoはGoで記述されており、以下のようにインストールできます。</p><pre><code>go install github.com/elastic/mito/cmd/mito@latest</code></pre><p>MitoでCELプログラムを実行する場合、通常は2つのファイルを指定します。初期入力状態を含むJSONファイルと、CELプログラムのソースコードを含む別のファイルです。</p><pre><code>mito -data state.json src.cel</code></pre><p>コピー＆ペーストを容易にするため、この記事の例は、シェルが<code>&lt;(echo &#x27;...content...&#x27;)</code>で各ファイルの内容をラップして、その場で一時ファイルを作成する単一のコマンドとして記述されています。独自の開発では、実際のファイルを操作する方が簡単になります。</p><h2>GitHubからイシューデータを取得</h2><p>以下の例には、<a href="https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#list-repository-issues">GitHub API</a>からイシューに関するデータを取得する完全なCELプログラムが含まれています。その初期入力状態には、APIエンドポイントのURLと、ページネーションをどのように処理すべきかについての情報が含まれています。CELプログラムは、入力状態のデータを使用してリクエストを生成します。対応をデコードし、そこからイベントを生成し、出力状態の一部として返します。</p><pre><code>mito -data <(echo '
  {
    "url": "https://api.github.com/repos/elastic/integrations/issues",
    "per_page": 3,
    "max_pages": 3
  }
') <(echo '
  int(state.?cursor.page.orValue(1)).as(page,
    (
      state.url + "?" + {
        "state": ["all"],
        "sort": ["created"],
        "direction": ["asc"],
        "per_page": [string(state.per_page)],
        "page": [string(page)],
      }.format_query()
    ).as(full_url,
      request("GET", full_url).with({
        "Header": {
          "Accept": ["application/vnd.github+json"],
          "X-GitHub-Api-Version": ["2022-11-28"],
        }
      }).do_request().as(resp,
        resp.Body.decode_json().as(data,
          state.with({
            "events": data.map(i, {
              "html_url": i.html_url,
              "title": i.title,
              "created_at": i.created_at,
            }),
            "cursor": { "page": page + 1 },
            "want_more": size(data) == state.per_page && page < state.max_pages,
          })
        )
      )
    )
  )
')</code></pre><p>その最初の評価は次の出力を生成します。</p><pre><code>{
  "cursor": {
    "page": 2
  },
  "events": [
    {
      "created_at": "2018-09-14T09:47:35Z",
      "html_url": "https://github.com/elastic/integrations/issues/3250",
      "title": "Increase support of log formats in haproxy filebeat module"
    },
    {
      "created_at": "2019-02-06T12:37:37Z",
      "html_url": "https://github.com/elastic/integrations/issues/487",
      "title": "ETCD Metricbeat module needs polishing and grooming"
    },
    {
      "created_at": "2019-08-13T11:33:11Z",
      "html_url": "https://github.com/elastic/integrations/pull/1",
      "title": "Initial structure"
    }
  ],
  "max_pages": 3,
  "per_page": 3,
  "url": "https://api.github.com/repos/elastic/integrations/issues",
  "want_more": true
}</code></pre><p>イベントは削除され、CEL入力で実行されると、インジェストのために公開されます。残りの出力は、次のCELプログラム評価に入力状態として提供されます。</p><p></p><p>そのCELプログラムの仕組みを理解するために、いくつかの小さなCELの例で、CEL入力の仕組みについて詳しく説明します。</p><h2>CELの基本</h2><p>CEL言語にはステートメントはなく、式のみが存在します。成功したすべてのCEL式は最終値まで評価されます。以下は、記述できる最も小さなCEL式の1つと、その出力です。</p><pre><code>mito <(echo '
  "hello" + " " + "world"
')</code></pre><pre><code>"hello world"</code></pre><p>シンプルな表現は通常、直感的です。数学演算は同じタイプの値でのみサポートされます（例：<code>int</code> と <code>int</code> など）。そのため、必要に応じてタイプを変換します（ここでは <code>int</code> から <code>double</code> へ）。</p><pre><code>mito <(echo '
  double((1 + 2) * (3 + 4)) / 2.0
')</code></pre><pre><code>10.5</code></pre><p>CEL言語には変数はありませんが、Mitoの<a href="https://pkg.go.dev/github.com/elastic/mito/lib#hdr-As__Macro_-Collections"><code>as</code></a>マクロを使用して、式に名前を付け、より大きな式で使用することができます。この例では、式<code>(1 + 1)</code>は値<code>2</code>に評価され、<code>.as(n, ...)</code>がその値に式<code>&quot;one plus one is &quot;+string(n)</code>で使用するための名前<code>n</code>を付けます。</p><pre><code>mito <(echo '
  (1 + 1).as(n, "one plus one is "+string(n))
')</code></pre><pre><code>"one plus one is 2"</code></pre><p>また、<a href="https://pkg.go.dev/github.com/elastic/mito/lib#hdr-With-Collections"><code>with</code></a>を使用して示したように、マップに情報を蓄積し、それを後で式の中で使用することも可能です。</p><pre><code>mito <(echo '
  { "key": "value" }.with({ "key2": "value2" }).as(data,
    {
      "data": data,
      "size": size(data),
    }
  )
')</code></pre><pre><code>{
  "data": {
    "key": "value",
    "key2": "value2"
  },
  "size": 2
}</code></pre><p>その例をもう一度見てみましょう。ネストされた部分<code>({ &quot;data&quot;: data, &quot;size&quot;: size(data), })</code>が最終値の形状を示すことに注意してください。これは<code>&quot;data&quot;</code>と<code>&quot;size&quot;</code>をキーとするマップです。これらのキーの値は<code>data</code>に依存し、式の外側部分によって定義されます。CEL式を内側から外側まで読み取ると、何が返されるかをすぐに把握できるようになります。</p><p>CELには<code>if</code>のような制御フロー文はありませんが、条件分岐は三項演算子で実行可能です。</p><pre><code>mito <(echo '
  1 + 1 < 12 ? "few" : "many"
')</code></pre><pre><code>"few"</code></pre><p>CELはチューリング完全言語ではないため、無制限のループと再帰はサポートされていません。これにより、実行時間が予測可能になり、入力データのサイズと式の複雑さに比例するようになります。</p><p>個別のCEL式では無限ループはできませんが、<a href="https://github.com/google/cel-spec/blob/master/doc/langdef.md#macros"><code>map</code></a>のようなマクロを使ってリストやマップを処理できます。</p><pre><code>mito <(echo '
  [1, 2, 3].map(x, x * 2)
')</code></pre><pre><code>[2, 4, 6]</code></pre><p>このセクションでは、次の内容を説明しました。</p><ul><li>文字列、数値、リスト、マップ。</li><li>文字列の連結。</li><li>数学演算。</li><li>タイプキャスティング。</li><li>条件文。</li><li>部分式の命名。</li><li>コレクションの処理。</li></ul><p>次に、HTTPリクエストを行う方法を見ていきます。</p><h2>リクエスト</h2><p>MitoはCELを拡張して<a href="https://pkg.go.dev/github.com/elastic/mito/lib#HTTP">HTTPリクエスト</a>を行う機能を提供します。</p><pre><code>mito <(echo '
  get("https://example.com").as(resp, string(resp.Body))
')</code></pre><pre><code>"<!doctype html><html lang=\"en\"><head><title>Example Domain</title>..."</code></pre><p>リクエストは実行前に明示的に構築することができます。これにより、さまざまなHTTPメソッドを使用したり、ヘッダーや本文を追加したりできるようになります。</p><p>この例では、 <a href="https://pkg.go.dev/github.com/elastic/mito/lib#hdr-Format_Query-HTTP"><code>format_query</code></a>を使用してURLを構築し、リクエストにヘッダーを追加し、 <a href="https://pkg.go.dev/github.com/elastic/mito/lib#hdr-Decode_JSON-JSON"><code>decode_json</code></a>を使用してレスポンス本文を解析します。<code>-log_requests</code>オプションを指定すると、Mitoは各リクエストと対応に関する詳細情報をJSON形式でログに記録します。</p><pre><code>mito -log_requests <(echo '
  request("GET",
    "https://postman-echo.com/get?" + {
        "q": ["query value"]
     }.format_query()
  ).with({
    "Header": { "Accept": ["application/json"] }
  }).do_request().as(resp, {
    "status": resp.StatusCode,
    "data": resp.Body.decode_json(),
  })
')</code></pre><pre><code>{"time":"...","level":"INFO","msg":"HTTP request",...}
{"time":"...","level":"INFO","msg":"HTTP response",...}
{
  "data": {
    "args": {
      "q": "query value"
    },
    "headers": {
      "accept": "application/json",
      "accept-encoding": "gzip, br",
      "host": "postman-echo.com",
      "user-agent": "Go-http-client/2.0",
      "x-forwarded-proto": "https"
    },
    "url": "https://postman-echo.com/get?q=query+value"
  },
  "status": 200
}</code></pre><h2>状態と評価の管理</h2><p>ここまで、リクエストの作成方法と、目的の出力状態を生成するために必要なCELの基本について説明しました。次は、出力状態に何を入れるべきか、そしてそれによって後続の処理をどのように指示できるかについて詳しく見ていきましょう。</p><p>統合のCELプログラムでは、その出力状態が次の評価の入力として使用するのに適していることを確認する必要があります。構成では初期状態を設定し、適切な変更を加えて出力でそれを繰り返す必要があります。簡単な方法は <code>state.with({ ... })</code> を使って、状態マップをオーバーライドして繰り返し表示することです。小規模プログラムの一般的なパターンは、<code>state.with()</code>でプログラム全体をラップすることです。これにより、出力データを生成する各分岐（例えば、成功、エラー）で状態の伝播を繰り返す必要がなくなります。</p><p>初期入力状態にハードコードされているのではなく、評価によって初期化される状態値がある場合、プログラムは初期値を設定する前に既存の値を確認する必要があります。これには<a href="https://pkg.go.dev/github.com/google/cel-go/cel#OptionalTypes">オプションの構文と型</a>のサポートが役立ちます。マップキーのフィールド名の前に疑問符を使用すると、アクセスはオプションになります。値に解決される場合とされない場合がありますが、さらにオプションのアクセスが可能であり、値が存在しない場合にデフォルトを簡単に提供できます。<br/></p><pre><code>mito -data <(echo '{}') <(echo '
  int(state.?counter.orValue(0)).as(counter,
    state.with({
      "counter": counter + 1,
      "want_more": counter + 1 < 3,
    })
  )
')</code></pre><pre><code>{ "counter": 1, "want_more": true }
{ "counter": 2, "want_more": true }
{ "counter": 3, "want_more": false }</code></pre><p>その例では、状態から読み取られたカウンター値は<code>int</code>にキャストされます。これは、JSONとJavaScriptの<code>Number</code>型によって確立された規則に従って、状態内のすべての数値が浮動小数点数としてシリアライズされるためです。また、<code>&quot;want_more&quot;: true</code>はここでMitoによって尊重されますが、CEL入力で実行される場合、出力にもイベントが含まれている場合にのみ評価が繰り返されます。</p><p>CEL入力によって実行されるCELプログラムでは、出力マップに<code>&quot;events&quot;</code>キーを返すことが要件となります。その値はイベントマップのリスト、空のリスト、または単一のイベントマップである場合があります。単一イベントケースは通常、エラーに使用されます。イベントは入力によって公開されますが、その値もログに記録されます。<code>error.message</code>値が設定されている場合、その値は統合のFleetのヘルスステータスを更新するために使用されます。プログラムが単一の非エラーイベントを生成する場合は、それをリストにラップするのが最適です。</p><p>先ほどのGitHubイシュープログラムの出力をもう一度見てみましょう。</p><pre><code>{
  "url": "https://api.github.com/repos/elastic/integrations/issues",
  "per_page": 3,
  "max_pages": 3,
  "cursor": {
    "page": 2
  },
  "events": [
    { ... },
    { ... },
    { ... }
  ],
  "want_more": true
}</code></pre><p>プログラムは次のようにして状態を効果的に管理しました。</p><ul><li><code>url</code>、<code>per_page</code>、<code>max_pages</code>で初期状態値を繰り返します。</li><li><code>cursor.page</code>の再起動時に永続化されるべき状態を追加します。</li><li><code>events</code>リストで公開する準備ができたイベントを返します。</li><li><code>want_more: true</code>で即時の再評価をリクエストしています。</li></ul><p>オプションのアクセスと状態管理、CELの基本とHTTPリクエストを理解できたので、GitHubのイシュープログラム全体が読み取れるようになります。Mitoで実行し、いくつか変更を加えて実験してみてください。</p><h2>レビューとリソース</h2><p>この記事では、CEL言語とは何か、そしてそれがMitoライブラリでどのように拡張され、CEL入力で使用されるかを調べました。GitHub APIからイシュー情報を取得するサンプルプログラムでCELの柔軟性を確認し、初期状態での設定へのアクセス、HTTP APIとのやりとり、取り込むべきイベントのリターン、後のプログラム実行のための状態管理など、そのプログラムを理解するために必要なすべての詳細を説明しました。</p><p>CEL入力を使用してさらに学び、統合を構築ために役立つリソースは多数あります。</p><ul><li><a href="https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-cel">CEL インプット - Filebeat ドキュメント</a></li><li><a href="https://pkg.go.dev/github.com/elastic/mito">Mito ドキュメント</a></li><li><a href="https://cel.dev/">Common Expression Language - cel.devウェブサイト</a></li><li><a href="https://www.elastic.co/docs/extend/integrations">統合を作成する - Elastic ドキュメント</a></li></ul><p>CEL入力を使用した統合を構築するための最も貴重なリソースは、GitHubで入手できる既存のElastic 統合のCELコードです。</p><p><a href="https://github.com/search?q=repo%3Aelastic%2Fintegrations+path%3A**%2Fcel.yml.hbs&amp;type=code"><code>cel.yml.hbs</code> Elastic統合リポジトリ内のファイル - GitHub</a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ElasticsearchとLLMによるエンティティ解決（第2部）：LLM判定とセマンティック検索によるエンティティのマッチング]]></title>
            <description><![CDATA[Elasticsearch でのエンティティ解決にセマンティック検索と透過的なLLM判断を使用します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-entity-resolution-llm-semantic-search</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-entity-resolution-llm-semantic-search</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
            <pubDate>Thu, 26 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/473ca4357c7d60f690edbd2a844acda169aca9c3-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><a href="https://www.elastic.co/search-labs/blog/entity-resolution-llm-elasticsearch">第1部</a>では、ウォッチリストを作成し、エンティティの言及を抽出しました。これで、「言及が実際にどのエンティティを指しているのか」という難しい質問に答える準備ができました。このシリーズの最初のブログの例に戻りましょう。ここでは、エンティティ解決が必要な理由を説明しています。「新しいSwiftアップデートが登場しました！」この見出しにもう少し文脈が添えられていると想像してください。</p><ol><li>新しいSwiftアップデートが登場しました！開発者たちは新しい機能を試したがっています。</li><li>新しいSwiftアップデートが登場しました！新しいアルバムは来月リリースされます。</li></ol><p>この追加されたコンテキストにより、「Swift」という名前を正しいエンティティに解決できるはずです。</p><p><a href="https://www.elastic.co/search-labs/blog/entity-resolution-llm-elasticsearch">前回の投稿</a>では、ウォッチリストを設定し、追加のコンテキストでエンティティを充実させました。上記の例を見ると、リストには少なくとも「Taylor Swift」と「Swift Programming Language」の2つのエンティティが必要です。また、テキストからエンティティの言及を抽出する方法も説明しました。これらの例はどちらも「Swift」を抽出します。これらの材料、強化された監視リスト、抽出されたエンティティが揃ったところで、いよいよショーの主役であるエンティティマッチングを紹介する準備が整いました。</p><p><strong>注意：</strong>これは、エンティティマッチングの概念を教えるために設計された教育用プロトタイプです。本番システムは、異なる大規模言語モデル（LLM）、カスタムマッチングルール、特殊な判断パイプライン、または複数のマッチング戦略を組み合わせたアンサンブルアプローチを使用する可能性があります。</p><h2>問題：マッチングが難しい理由</h2><p>人間の言語とは驚くべきものです。その最も興味深い特性の1つは、その無限の創造性です。無限の数の新しい文を生成し、理解することができます。そうであるなら、エンティティ解決において正確な一致が稀なのも不思議ではありません。作家は可能な限り創造的であろうと努めます。エンティティが言及されるたびにフルネームを書いたり読んだりしなければならないとしたら、かなり面倒です。そのため、厳密な一致は簡単ですが、現実には、より洗練されたエンティティ解決アプローチが必要です。それは、人間の作者の無限の創造性に少なくとも部分的には対応できるほどに堅牢なアプローチであるべきです。そのため、私たちは問題を2つのステップに分けます。まずはElasticsearchを使用して大規模な候補を取得し、次にLLMを使用してそれらの候補が実際に同じ現実世界のエンティティを指しているかどうかを判断します。</p><h2>解決策：透明性の高いLLM判断による3段階のマッチング</h2><p>私たちはコンピューターの使い方におけるパラダイムシフトの真っ只中にあります。インターネットの台頭がローカルコンピューティングからグローバルに接続されたネットワークへと私たちを導いたように、生成AIはコンテンツ、コード、情報の作成方法を根本的に変えています。実際、このシリーズに付随する教育プロトタイプは、作者の慎重な指示のもと、LLMを使用してほぼ「バイブコーディング」のみで作成されました。これは、LLMが人間の言語に本来備わっている生産性を実現している、あるいは実現するだろうということと同義ではありませんが、エンティティ解決を支援する強力なリソースが手に入ったことを意味します。</p><p>生成AIでよく使うパターンは、Retrieval-Augmented Generation（RAG）です。ここにおいて、<em>取得（retrieval）</em>とは、エンティティ候補を取得すること（回答を生成することではない）を意味し、LLMは一致の評価と説明にのみ使用されます。エンドツーエンドのエンティティ解決についてLLMに支援を依頼する<em>こともできます</em>が、これは時間と費用の両面でコストのかかるアプローチです。RAGは、より効率的な方法でLLMにコンテキストを提供することでLLMの作業を支援し、それによってLLMがエンティティ解決を効率的に支援できるようにします。</p><p>RAGの取得部分については、再びElasticsearchを利用します。まず、正確な一致、エイリアスとの一致、そしてキーワード検索とセマンティック検索を組み合わせたハイブリッド検索という組み合わせを使用して、潜在的な一致を検索します。一致する可能性のある項目が見つかったら、LLMに送信して判断を仰ぎます。LLMは最終的な一致評価者として機能します。また、LLMにその理由を説明させます。これは他のエンティティ解決システムとの重要な差別化要因です。これらの説明がなければ、エンティティ解決はブラックボックスになります。説明があれば、一致にどんな意味があるのか自分で確認できます。</p><h2>主な概念：3段階マッチング、ハイブリッド検索、透過的なLLM判断</h2><p><strong>3段階マッチングとは？</strong>このプロジェクトの開始時に、セマンティック検索がシステムの重要な一部になるという仮説を立てましたが、すべての一致にこのような高度な検索が必要なわけではありません。効率的にマッチングを見つけるために、私たちは段階的なアプローチを取ります。まず、キーワード検索で正確な一致を確認します。そのような一致が見つかった場合、作業は完了し、先に進むことができます。完全一致が失敗した場合は、エイリアス一致を使用します。このプロトタイプでは、簡素化のために、キーワードとの完全一致によるエイリアスマッチングも行われています。本番環境では、正規化、翻字ルール、あいまい一致、またはキュレートされたエイリアステーブルを使用してこのステップを拡張する場合があります。それでも最初の2つのステップで一致する可能性のあるものが見つからない場合は、Elasticsearchの逆順位融合（RRF）を使用したハイブリッド検索によるセマンティック検索を導入します。</p><p><strong>ハイブリッド検索とは？</strong>Elasticsearchでは、セマンティック検索を使用して、コンテキストを考慮した意味のある一致を見つけることができます。Elasticsearchは、ベクトル検索とハイブリッド検索に広く使用されています。セマンティック類似性は意味を理解する上で強力ですが、構造化されたフィルタリング（例えば、時間範囲、場所、または識別子による）の代替にはならず、正確な一致が利用可能な場合は多くの場合不必要です。Elasticsearchは語彙検索で名声を博しており、これはセマンティック検索が適さないタスクに最適です。両方のアプローチを最大限に活用するために、単一のハイブリッドクエリで語彙検索とセマンティック検索を併用します。次に、結果をマージして、RRFを使用して最も一致する可能性が高いものを見つけます。このプロトタイプでは、上位2つの結果が、LLM判定に送信できる潜在的な一致となります。</p><p><strong>LLM判定を使用する理由とは？</strong>LLMの判断と説明により、システムは曖昧さとコンテキストを透過的に処理できます。これは「the president」のような場合において重要です。コンテキストによって複数のエンティティを指す可能性がありますが、システム内でニックネームや文化的なバリエーションをうまく機能させることもできます。最後に、制裁リストからエンティティを識別するなどのミッションクリティカルなタスクを検討する場合、システムを信頼するために、一致が受け入れられた理由を把握する必要があります。重要なのは、LLMはコーパス全体を検索せず、Elasticsearchによって返された少数の候補のみを評価するということです。</p><h2>実際の結果：LLM推論によるマッチング</h2><p>あらゆる自然言語処理タスクにおける大きな課題は、期待される結果が何であるかを示す「答えの鍵」となるゴールデンドキュメントを作成することです。これがなければ、システムがタスクをどの程度うまく実行するかを判断することはほぼ不可能ですが、そのようなドキュメントを作成するのは面倒なプロセスになる可能性があります。エンティティ解決のプロトタイプでは、テストに使用できるデータの設定に生成AIを再度利用しました。</p><p>まず、ニックネームや翻字などのいくつかのチャレンジタイプを定義し、次にLLMに、システムにとって徐々に大きく、より困難になる階層化されたデータセットコレクションを作成するように依頼しました。データセットの作成は期待していたほど簡単ではありませんでした。LLMでは、正解を得るのがあまりにも簡単すぎるため、「チート」が行われる傾向が強くなりました。例えば、あるチャレンジタイプは意味的なコンテキストに重点を置いています。このタイプには、「ロシアの作家」を「レフ・トルストイ」に解決することなどが含まれます。LLMは誤って「ロシアの作家」を「レフ・トルストイ」の別名として入力したため、一致を見つけるためのハイブリッド検索の必要性がなくなりました。</p><p>このような問題を修正するために何度かリファクタリングを行った結果、5つのデータセット層が使用できるようになりました。第1〜4層は徐々に規模が大きくなり、チャレンジの種類も増えました。第5層は「究極のチャレンジ」データセットで、すべてのチャレンジタイプから最も難しい例で構成されていました。すべてのテストデータは<a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/comprehensive_evaluation">包括的な評価ディレクトリ</a>で利用可能です。</p><p>プロンプトベースのエンティティ解決アプローチを評価するため、私たちは第4層データセットに注目しました。重要な注意点は、エンティティの一致品質に焦点を当てることができるように、評価が制御された実験として実施されたことです。ウォッチリストデータは事前にコンテキストで強化されており、エンティティは事前に記事から抽出され、評価で抽出精度ではなくマッチングに重点が置かれることが保証されました。これにより、一致品質が分離されます。エンドツーエンドのパフォーマンスは、抽出リコールとエンリッチメント品質にも依存します。</p><h3>評価データセット</h3><p>第4層の評価データセットは、システムの機能の包括的なテストを提供します。[1]</p><ul><li><strong>監視リストのエンティティ：</strong>さまざまなタイプ（人、組織、場所）にわたる66個のエンティティ。</li><li><strong>テスト記事：</strong>実際のエンティティ解決シナリオを網羅した69件の記事。</li><li><strong>予想される一致数：</strong>すべての記事で206件のエンティティが一致すると予想。</li><li><strong>チャレンジタイプ：</strong>エンティティ解決のさまざまな側面をテストする15種類のチャレンジタイプ。</li></ul><p>データセットに含まれる課題の種類は以下の通りです。</p><ul><li><strong>ニックネーム：</strong> 「ボブ・スミス」→「ロバート・スミス」（7つの記事）。</li><li><strong>称号と敬称：</strong>「Dr. Sarah Williams」→「Sarah Williams」（5つの記事）。</li><li><strong>意味的文脈：</strong> 「ロシアの作家」→「レフ・トルストイ」（8 つの記事）。</li><li><strong>多言語名：</strong>異なる文字での名前の取り扱い（6つの記事）。</li><li><strong>事業体：</strong>会社名のバリエーション（7つの記事）。</li><li><strong>役員紹介：</strong> 「Microsoft CEO」→「Satya Nadella」（5つの記事）。</li><li><strong>政治指導者：</strong>タイトルベースの参考文献（5つの記事）。</li><li><strong>イニシャル：</strong> 「J. Smith」→「John Smith」（3つの記事）。</li><li><strong>名前の順序のバリエーション：</strong>さまざまな名前の順序付け規則（3つの記事）。</li><li><strong>切り捨てられた名前：</strong>名前の一部一致（3つの記事）。</li><li><strong>名前の分割：</strong>名前がテキストに分割（3つの記事）。</li><li><strong>スペース/ハイフンの欠落：</strong>書式のバリエーション（2つの記事）。</li><li><strong>翻字：</strong>文字間の名前の一致（2つの記事）。</li><li><strong>複合チャレンジ：</strong>1つの記事に複数のチャレンジ（6つの記事）。</li><li><strong>複雑なビジネス：</strong>階層的なビジネス関係（5つの記事）。</li></ul><p>プロンプトベースのエンティティ解決がどのように機能したか見てみましょう。</p><h3>全体的なパフォーマンス</h3><p>結果は、LLMを活用したマッチ評価には大きな可能性があることを示していますが、重大な信頼性の問題も明らかにしています。各候補ペアはLLMによって評価される必要があるため、構造化された出力の失敗により、検索が適切に機能している場合でも受け入れと呼び出しが抑制される可能性があります。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">メトリック</td><td style="border: 1px solid #ccc; padding: 4px;">値</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">精度</td><td style="border: 1px solid #ccc; padding: 4px;">83.8%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">リコール</td><td style="border: 1px solid #ccc; padding: 4px;">62.6％</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">F1スコア</td><td style="border: 1px solid #ccc; padding: 4px;">71.7％</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">見つかった一致の合計</td><td style="border: 1px solid #ccc; padding: 4px;">344</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">LLM合格率</td><td style="border: 1px solid #ccc; padding: 4px;">44.8％</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">エラー率</td><td style="border: 1px solid #ccc; padding: 4px;">30.2%</td>
                    </tr>
              </tbody>
            </table>
          <h3>エラー率の問題</h3><p>このプロトタイプで最初に行うステップは、Elasticsearchを使用して潜在的な一致ペアを作成することであることを思い出してください。これらの潜在的な一致はそれぞれ、LLMによって評価される必要があります。これらすべての一致を効率的に処理するために、LLM呼び出しをバッチ処理します。これにより、APIのコストと待ち時間が削減されますが、出力に不正な形式のJSONが表示されるリスクも高まります。バッチサイズが大きくなると、JSONはより長く複雑になり、LLMが無効なJSONを生成する可能性が高くなります。これがエラー率30%となる原因です。評価では、リクエストごとに5つの一致のバッチサイズを使用しました。この保守的なバッチサイズでも、JSON解析エラーが発生し、評価結果が大幅に歪んでいます。</p><h2>次のステップ：LLM統合の最適化</h2><p>セマンティック検索とLLMによる判断を用いてエンティティをマッチングしたことで、完全なエンティティ解決パイプラインが完成しました。ただし、このアプローチでは、モデルの判断は正しいものの、その出力が使用できない場合に、新たな障害モードが発生します。LLM統合を最適化することで、信頼性とコスト効率を向上させることができます。次の投稿では、エラーとコストを削減しながら構造と型の安全性を保証する構造化出力に関数呼び出しを使用する方法について説明します。</p><h2>はじめましょう</h2><p>エンティティマッチングの実際の動作を確認したいですか？実際の実装、詳細な説明、実践的な例を含む完全なウォークスルーについては、<a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/notebooks#:~:text=5%20minutes%20ago-,03_entity_matching_v3.ipynb,-Initial%20public%20lab">エンティティマッチングノートブック</a>を参照してください。このノートブックでは、3段階の検索、RRFを使用したハイブリッド検索、LLMを利用した推論による判断を使用してエンティティを一致させる方法を正確に示します。</p><p><strong>注意：</strong>これは、概念を教えるために設計された教育用プロトタイプです。本番システムを構築するときは、モデルの選択、コストの最適化、レイテンシ要件、品質検証、エラー処理、監視など、教育に重点を置いたこのプロトタイプではカバーされていない追加の要素を考慮してください。</p><h2>メモ</h2><ol><li>これらのデータセットは合成されたもので教育用に設計されており、実際の課題に近似していますが、単一の本番ドメインを代表するものではありません。</li></ol>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearchのベクトル検索はOpenSearchの最大8倍高速]]></title>
            <description><![CDATA[OpenSearchとElasticsearchのフィルタリングされたベクトル検索ベンチマークを比較し、コンテキストエンジニアリングシステムにおいてベクトル検索のパフォーマンスが重要な理由を探ります。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/opensearch-vs-elasticsearch-filtered-vector-search</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/opensearch-vs-elasticsearch-filtered-vector-search</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Sachin Frayne]]></dc:creator>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fb7ee623925ca6696d643e437ce8efe5fe749079-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>AIエージェントとコンテキストエンジニアリングにおいて検索速度が重要な理由</h2><p>2,000万件の文書コーパスを用いたベンチマークテストの結果、Elasticsearchはフィルタリングされたベクトル検索においてOpenSearchよりも最大8倍高いスループットを実現し、テストしたすべての構成においてより高いRecall@100を達成しました。コンテキストエンジニアリングに必要な要素は高速ベクトル取得だけではありません。ワークフローの反復につれて、チームはハイブリッド検索やフィルタリングなどの強力な関連性制御、操作の簡便性、予測可能なパフォーマンスも必要とするようになります。しかし、エージェントはリクエストごとに取得、推論、取得のループを何度も実行することが多いため、取得の遅延が乗数効果となり、ここでの改善はエンドツーエンドの応答性の向上とコスト削減に直接つながります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5a52a07773f0942c2baa732dacfe782aac0f415-1600x683.png" style="width: 100%;" /><p>コンテキストエンジニアリングにおいて、取得は一度きりのステップではありません。エージェントとアプリケーションは、クエリを洗練させ、事実を検証し、根拠に基づいたコンテキストを構築し、タスクを完了するために、取得 → 推論 → 取得といったループを繰り返し実行します。このパターンは、エージェント型ワークフローや反復型検索拡張生成（RAG）においてよく見られます。検索はユーザーリクエストごとに何度も呼び出される可能性があるため、応答に遅延が生じ、インフラコストが増加します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/54ac476ff20a3cf93484298c9ae47612c12fc110-800x417.png" style="width: 100%;" /><h2>ベクトル検索のパフォーマンスが重要な理由</h2><p></p><p>「15インチのノートパソコンが入る、防水性があり、金曜日までに届く60ドル以下の機内持ち込み用バックパックが必要です」という質問に、店員が答える場面を想像してみてください。</p><p>実際の運用環境では、アシスタントがベクトルクエリを1回発行して停止することはほとんどありません。適切なコンテキストを構築するために検索ループを実行し、各ステップは通常、在庫状況、地域、出荷約束、ブランドルール、ポリシー適格性などのフィルターによって制約されます。</p><p><strong>ステップ1：意図を解釈し、制約に変換する。</strong></p><p>エージェントはリクエストを構造化されたフィルタとセマンティッククエリに変換します。例えば、次のようなものです。</p><ul><li>フィルター：在庫あり、ユーザーの郵便番号に配達可能、金曜日までに配達、価格60ドル未満、有効な出品</li><li>ベクトルクエリ：「機内持ち込み用バックパック 15インチノートパソコン対応 防水」</li></ul><p><strong>ステップ2：候補を取得し、絞り込む。</strong></p><p>適切な一致を見逃さないように、しばしばバリエーションを加えて検索を繰り返します。</p><ul><li>「旅行用バックパック 機内持ち込み可能 ノートパソコン用スリーブ」</li><li>「防水通勤用バックパック 15インチ」</li><li>「軽量 機内リュック」</li></ul><p>各クエリは同じ適格性フィルターを使用します。なぜなら、無関係な項目や利用できない項目を取得することは、コンテキストの無駄になるからです。</p><p><strong>ステップ3：詳細を確認し、リスクを軽減するために展開する。</strong></p><p>エージェントは、最終的な回答に影響を与える主要な属性を再度確認するために取得を行います。</p><ul><li>素材と耐水性に関する記述</li><li>寸法とノートパソコン収納部の適合性</li><li>返品ポリシーや保証の制約</li><li>在庫が少ない場合の代替オプション</li></ul><p>これは、取得、推論、取得、組み立てという複数段階のコンテキストエンジニアリングです。</p><h2>コンテキストエンジニアリングにおいてレイテンシと再現率が重要な理由</h2><p>これらのやり取りには、ユーザーセッションごとに数十回のフィルタリングされたデータ取得呼び出しが含まれる場合があります。それにより、呼び出しごとのレイテンシがエンドツーエンドの応答時間に直接的な乗数となり、低い再現率は追加の再試行を強制したり、エージェントが対象アイテムを見逃す原因となり、回答の質を低下させます。</p><p>要点：コンテキストエンジニアリングされたシステムでは、フィルタリングされた近似最近傍法（ANN）は単一のルックアップではありません。これは制約下での反復処理であるため、大規模言語モデル（LLM）が最も目立つ要素である場合でも、ベクトル検索のパフォーマンスはレイテンシ、スループット、コストにすぐに現れます。</p><h2>ベンチマーク</h2><h3>成果</h3><p>グラフ2では、各点が1つのテスト構成を表しています。最も良い結果は左上に表示され、これは低いレイテンシで高い再現率が得られることを意味します。Elasticsearchの結果はOpenSearchよりも常に左上に近く、同じワークロード設定下でより優れた速度と精度を示しています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c50d1df00968cac18149a2799e6242fbe49b66a0-1600x990.png" style="width: 100%;" /><h4>いくつかの重要な洞察</h4><ul><li><code>s_n_r_value</code>: <code>size_numCandidates_rescoreOversample</code>の省略形（これらのテストではkとnumCandidatesはnumCandidatesと等しく設定されます）。例えば、 <code>100_500_1</code> size=100、numCandidates=500、k=500、再スコアオーバーサンプル=1 を意味します。</li><li>再現率：その構成における測定Recall@100</li><li>平均レイテンシ（ミリ秒）：クエリごとの平均エンドツーエンドレイテンシ</li><li>スループット：1秒あたりのクエリ数</li><li>再現率（％）：ElasticsearchとOpenSearchの相対的な再現率向上率（Elasticsearch - OpenSearch）/OpenSearch</li><li>レイテンシXs：OpenSearchの平均レイテンシをElasticsearchの平均レイテンシで割った値</li><li>スループットX：Elasticsearchのスループットをオープンサーチのスループットで割った値</li></ul>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">エンジン</td><td style="border: 1px solid #ccc; padding: 4px;">`s_n_r_value`</td><td style="border: 1px solid #ccc; padding: 4px;">リコール</td><td style="border: 1px solid #ccc; padding: 4px;">平均レイテンシ（ミリ秒）</td><td style="border: 1px solid #ccc; padding: 4px;">スループット</td><td style="border: 1px solid #ccc; padding: 4px;">再現率（％）</td><td style="border: 1px solid #ccc; padding: 4px;">レイテンシ Xs</td><td style="border: 1px solid #ccc; padding: 4px;">スループット Xs</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_250_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.7704</td><td style="border: 1px solid #ccc; padding: 4px;">25</td><td style="border: 1px solid #ccc; padding: 4px;">534.75</td><td style="border: 1px solid #ccc; padding: 4px;">9.70％</td><td style="border: 1px solid #ccc; padding: 4px;">2.28</td><td style="border: 1px solid #ccc; padding: 4px;">1.91</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_250_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.7023</td><td style="border: 1px solid #ccc; padding: 4px;">57.08</td><td style="border: 1px solid #ccc; padding: 4px;">279.58</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.8577</td><td style="border: 1px solid #ccc; padding: 4px;">25.42</td><td style="border: 1px solid #ccc; padding: 4px;">524.14</td><td style="border: 1px solid #ccc; padding: 4px;">7.20%</td><td style="border: 1px solid #ccc; padding: 4px;">2.4</td><td style="border: 1px solid #ccc; padding: 4px;">2</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.8001</td><td style="border: 1px solid #ccc; padding: 4px;">60.9</td><td style="border: 1px solid #ccc; padding: 4px;">262.12</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_750_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.8947</td><td style="border: 1px solid #ccc; padding: 4px;">29.67</td><td style="border: 1px solid #ccc; padding: 4px;">528.09</td><td style="border: 1px solid #ccc; padding: 4px;">5.72％</td><td style="border: 1px solid #ccc; padding: 4px;">2.25</td><td style="border: 1px solid #ccc; padding: 4px;">2.21</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_750_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.8463</td><td style="border: 1px solid #ccc; padding: 4px;">66.76</td><td style="border: 1px solid #ccc; padding: 4px;">239.11</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_1000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9156</td><td style="border: 1px solid #ccc; padding: 4px;">29.65</td><td style="border: 1px solid #ccc; padding: 4px;">534.5</td><td style="border: 1px solid #ccc; padding: 4px;">4.66％</td><td style="border: 1px solid #ccc; padding: 4px;">2.46</td><td style="border: 1px solid #ccc; padding: 4px;">2.44</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_1000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.8748</td><td style="border: 1px solid #ccc; padding: 4px;">72.88</td><td style="border: 1px solid #ccc; padding: 4px;">219.01</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_1500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9386</td><td style="border: 1px solid #ccc; padding: 4px;">31.84</td><td style="border: 1px solid #ccc; padding: 4px;">497.3</td><td style="border: 1px solid #ccc; padding: 4px;">3.38％</td><td style="border: 1px solid #ccc; padding: 4px;">2.71</td><td style="border: 1px solid #ccc; padding: 4px;">2.68</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_1500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9079</td><td style="border: 1px solid #ccc; padding: 4px;">86.16</td><td style="border: 1px solid #ccc; padding: 4px;">185.4</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_2000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9507</td><td style="border: 1px solid #ccc; padding: 4px;">34.69</td><td style="border: 1px solid #ccc; padding: 4px;">457.2</td><td style="border: 1px solid #ccc; padding: 4px;">2.57%</td><td style="border: 1px solid #ccc; padding: 4px;">2.98</td><td style="border: 1px solid #ccc; padding: 4px;">2.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_2000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9269</td><td style="border: 1px solid #ccc; padding: 4px;">103.36</td><td style="border: 1px solid #ccc; padding: 4px;">154.55</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_2500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9582</td><td style="border: 1px solid #ccc; padding: 4px;">37.9</td><td style="border: 1px solid #ccc; padding: 4px;">418.43</td><td style="border: 1px solid #ccc; padding: 4px;">1.99％</td><td style="border: 1px solid #ccc; padding: 4px;">3.28</td><td style="border: 1px solid #ccc; padding: 4px;">3.26</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_2500_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9395</td><td style="border: 1px solid #ccc; padding: 4px;">124.29</td><td style="border: 1px solid #ccc; padding: 4px;">128.53</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_3000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9636</td><td style="border: 1px solid #ccc; padding: 4px;">41.86</td><td style="border: 1px solid #ccc; padding: 4px;">379.4</td><td style="border: 1px solid #ccc; padding: 4px;">1.62％</td><td style="border: 1px solid #ccc; padding: 4px;">3.46</td><td style="border: 1px solid #ccc; padding: 4px;">3.44</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_3000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9482</td><td style="border: 1px solid #ccc; padding: 4px;">144.67</td><td style="border: 1px solid #ccc; padding: 4px;">110.34</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_4000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9705</td><td style="border: 1px solid #ccc; padding: 4px;">50.28</td><td style="border: 1px solid #ccc; padding: 4px;">316.21</td><td style="border: 1px solid #ccc; padding: 4px;">1.06%</td><td style="border: 1px solid #ccc; padding: 4px;">3.87</td><td style="border: 1px solid #ccc; padding: 4px;">3.85</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_4000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9603</td><td style="border: 1px solid #ccc; padding: 4px;">194.36</td><td style="border: 1px solid #ccc; padding: 4px;">82.22</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_5000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9749</td><td style="border: 1px solid #ccc; padding: 4px;">58.77</td><td style="border: 1px solid #ccc; padding: 4px;">270.91</td><td style="border: 1px solid #ccc; padding: 4px;">0.73%</td><td style="border: 1px solid #ccc; padding: 4px;">4.43</td><td style="border: 1px solid #ccc; padding: 4px;">4.41</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_5000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9678</td><td style="border: 1px solid #ccc; padding: 4px;">260.33</td><td style="border: 1px solid #ccc; padding: 4px;">61.38</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_6000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9781</td><td style="border: 1px solid #ccc; padding: 4px;">66.75</td><td style="border: 1px solid #ccc; padding: 4px;">238.59</td><td style="border: 1px solid #ccc; padding: 4px;">0.52%</td><td style="border: 1px solid #ccc; padding: 4px;">4.91</td><td style="border: 1px solid #ccc; padding: 4px;">4.89</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_6000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.973</td><td style="border: 1px solid #ccc; padding: 4px;">327.44</td><td style="border: 1px solid #ccc; padding: 4px;">48.81</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_7000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9804</td><td style="border: 1px solid #ccc; padding: 4px;">74.64</td><td style="border: 1px solid #ccc; padding: 4px;">213.49</td><td style="border: 1px solid #ccc; padding: 4px;">0.38％</td><td style="border: 1px solid #ccc; padding: 4px;">5.28</td><td style="border: 1px solid #ccc; padding: 4px;">5.27</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_7000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9767</td><td style="border: 1px solid #ccc; padding: 4px;">394.24</td><td style="border: 1px solid #ccc; padding: 4px;">40.53</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_8000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9823</td><td style="border: 1px solid #ccc; padding: 4px;">82.28</td><td style="border: 1px solid #ccc; padding: 4px;">193.59</td><td style="border: 1px solid #ccc; padding: 4px;">0.27％</td><td style="border: 1px solid #ccc; padding: 4px;">6.86</td><td style="border: 1px solid #ccc; padding: 4px;">6.83</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_8000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9797</td><td style="border: 1px solid #ccc; padding: 4px;">564.14</td><td style="border: 1px solid #ccc; padding: 4px;">28.33</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_9000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9837</td><td style="border: 1px solid #ccc; padding: 4px;">90.08</td><td style="border: 1px solid #ccc; padding: 4px;">176.96</td><td style="border: 1px solid #ccc; padding: 4px;">0.16%</td><td style="border: 1px solid #ccc; padding: 4px;">7.63</td><td style="border: 1px solid #ccc; padding: 4px;">7.61</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_9000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9821</td><td style="border: 1px solid #ccc; padding: 4px;">687.25</td><td style="border: 1px solid #ccc; padding: 4px;">23.25</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_10000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.9848</td><td style="border: 1px solid #ccc; padding: 4px;">97.64</td><td style="border: 1px solid #ccc; padding: 4px;">163.31</td><td style="border: 1px solid #ccc; padding: 4px;">0.08%</td><td style="border: 1px solid #ccc; padding: 4px;">8.38</td><td style="border: 1px solid #ccc; padding: 4px;">8.36</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch</td><td style="border: 1px solid #ccc; padding: 4px;">100_10000_1</td><td style="border: 1px solid #ccc; padding: 4px;">0.984</td><td style="border: 1px solid #ccc; padding: 4px;">818.64</td><td style="border: 1px solid #ccc; padding: 4px;">19.53</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
              </tbody>
            </table>
          <p>例えば、<code>100_9000_1</code>では、OpenSearchの平均取得時間687ミリ秒に対してElasticsearchは90ミリ秒であり、10ステップの取得ループでは約10×(687-90)=6秒の追加待機時間となります。 </p><p><a href="https://github.com/elastic/competitive-benchmarking-studies/tree/main/es-9.3-vs-os-3.5-vector-search/jingra/results/20260220">全ての結果を</a>ご覧ください。</p><h3>調査手法</h3><p>Pythonを使ってクエリを送信し、対応タイミングやその他の統計を追跡し、以下のクエリをエンジンに送信しました。ベクター検索エンジンのパフォーマンスは、そのコアパラメータ（考慮する候補の数、再スコアリングの積極性、返されるコンテキストの量など）をどのように調整するかによって決まることを覚えておいてください。これらの設定は、再現率（正解を見つける可能性）とレイテンシ（結果を得るまでの速さ）の両方に直接影響します。</p><p>ベンチマークでは、エージェント検索ループで通常調整するのと同じ候補、再スコア、結果サイズの設定を使用し、そのワークロード下でElasticsearchがどのように機能するかを測定しました。その後、同じ設定でOpenSearchをリファレンスとして実行しました。</p><p>OpenSearch</p><pre><code>GET <INDEX_NAME>/_search
{
  "query": {
    "knn": {
      "<DENSE_VECTOR_FIELD_NAME>": {
        "vector": [...],
        "k": <NUMBER_OF_CANDIDATES>,
        "method_parameters": {
          "ef_search": <NUMBER_OF_CANDIDATES>
        },
        "rescore": {
          "oversample_factor": <OVERSAMPLE>
        },
        "filter": {
          <SOME_FILTER>
        }
      }
    }
  },
  "size": <RESULT_SIZE>,
  "_source": {
    "excludes": [
      "<DENSE_VECTOR_FIELD_NAME>"
    ]
  }
}</code></pre><ul><li><code>&quot;size&quot;: &lt;RESULT_SIZE&gt;</code>: クライアントに返されたヒット数。このベンチマークでは、Recall@100を計算するために、結果のサイズは100です。</li><li><code>&quot;k&quot;: &lt;NUMBER_OF_CANDIDATES&gt;</code>: 最近傍候補の数。</li><li><code>&quot;ef_search&quot;: &lt;NUMBER_OF_CANDIDATES&gt;</code>: 検査するベクトルの数。</li><li><code>&quot;oversample_factor&quot;: &lt;OVERSAMPLE&gt;</code>: 再スコアリングを行う前に取得される候補ベクトルの数。</li></ul><p>Elasticsearch</p><pre><code>GET <INDEX_NAME>/_search
{
  "query": {
    "knn": {
      "field": "<DENSE_VECTOR_FIELD_NAME>",
      "query_vector": [...],
      "k": <NUMBER_OF_CANDIDATES>,
      "num_candidates": <NUMBER_OF_CANDIDATES>,
      "rescore_vector": {
        "oversample": <OVERSAMPLE>
      },
      "filter": {
        <SOME_FILTER>
      }
    }
  },
  "size": <RESULT_SIZE>,
  "_source": {
    "excludes": [
      "<DENSE_VECTOR_FIELD_NAME>"
    ]
  }
}</code></pre><ul><li><code>&quot;size&quot;: &lt;RESULT_SIZE&gt;</code>: クライアントに返されたヒット数。このベンチマークでは、Recall@100を計算するために、結果のサイズは100です。</li><li><code>&quot;k&quot;: &lt;NUMBER_OF_CANDIDATES&gt;</code>: 各シャードから返す最近傍の数。</li><li><code>&quot;num_candidates&quot;: &lt;NUMBER_OF_CANDIDATES&gt;</code>: <code>knn</code>検索を実行する際にシャードごとに考慮する最近傍候補の数。</li><li><code>&quot;oversample&quot;: &lt;OVERSAMPLE&gt;</code>: 再スコアリングを行う前に取得される候補ベクトルの数。</li></ul><p>例</p><p><code>Knn</code> クエリ（ <code>100_500_1</code> ）は次のようになります。</p><p>OpenSearch</p><pre><code>GET search_catalog_128/_search
{
  "query": {
    "knn": {
      "search_catalog_embedding": {
        "vector": [...],
        "k": 500,
        "method_parameters": {
          "ef_search": 500
        },
        "rescore": {
          "oversample_factor": 1
        },
        "filter": {
          "term": {
            "valid": true
          }
        }
      }
    }
  },
  "size": 100,
  "_source": {
    "excludes": [
      "search_catalog_embedding"
    ]
  }
}</code></pre><p>Elasticsearch</p><pre><code>GET search_catalog_128/_search
{
  "query": {
    "knn": {
      "field": "search_catalog_embedding",
      "query_vector": [...],
      "k": 500,
      "num_candidates": 500,
      "rescore_vector": {
        "oversample": 1
      },
      "filter": {
        "term": {
          "valid": true
        }
      }
    }
  },
  "size": 100,
  "_source": {
    "excludes": [
      "search_catalog_embedding"
    ]
  }
}</code></pre><p>Terraformスクリプト、Kubernetesマニフェスト、ベンチマークコードとともに、完全な構成はこの<a href="https://github.com/elastic/competitive-benchmarking-studies">リポジトリ</a>の<a href="https://github.com/elastic/competitive-benchmarking-studies/tree/main/es-9.3-vs-os-3.5-vector-search">es-9.3-vs-os-3.5-vector-search</a>フォルダで入手可能です。</p><h3>クラスターの設定</h3><p>私たちは、16 vCPUと64 GB RAMを備えた6台のe2-standard-16クラウドサーバーでテストを実行しました。各サーバーにおいて、検索エンジンノードを実行する各Kubernetesポッドに15個のvCPUと56GBのRAMを割り当て、そのうち28GBをJVMヒープ用に確保しました。</p><p>クラスターはElasticsearch 9.3.0とOpenSearch 3.5.0（Lucene 10.3.2）で実行しました。このベンチマークでは両方のシステムが同じLuceneバージョンを使用しているため、観測されたスループットとレイテンシの違いはLucene単独に起因するものではなく、各エンジンがフィルタリングされたk近傍法（kNN）による検索と再スコアリングをどのように統合し実行するかの違いを反映します。私たちは、3つのプライマリシャードと1つのレプリカ（合計6つのシャード、ノードごとに1つ）を持つ単一のインデックスを使用しました。</p><p>我々はまた、同じリージョン内の別のサーバーを使用してベンチマーククライアントを実行し、タイミング統計を収集しました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f81002c9186e4c2d3e92f49d72418fee9860fc5e-761x401.png" style="width: 100%;" /><h3>データセット</h3><p></p><p>このベンチマークでは、2,000万件のドキュメントを含む大規模なeコマーススタイルのカタログ埋め込みデータセットを使用しました。これは、実世界のフィルタリングされたベクトル検索を大規模なスケールで反映するように設計されています。</p><p></p><p>各ドキュメントはカタログ項目を表し、以下を含みます。</p><p></p><ul><li>近似kNN検索に使用される128次元の密なベクトル埋め込み。</li><li>構造化されたメタデータフィールドを使用してフィルタリング（例：アイテムの有効性と可用性、およびその他のカタログ制約）を行うことで、適格なサブセット内でのみ最近傍を取得するという一般的な本番パターンを可能にします。</li></ul><p></p><p>このデータセットを選んだ理由は、本番環境におけるエージェント型システムやRAG型システムで見られる主要なパフォーマンス上の課題を捉えているからです。つまり、ベクトル類似性だけでは不十分であり、検索はフィルタによって制約されることが多く、システムはこれらの制約の下で高い再現率を維持しながらレイテンシを低く抑える必要があるということです。より小規模なQAスタイルのデータセットと比較すると、2000万件の文書コーパスは、フィルタリングされたANNシステムが実際に直面する規模と候補数によるプレッシャーをより適切に反映しています。</p><h2>まとめ</h2><p>現代のAIアーキテクチャー、特にコンテキストエンジニアリングを中心としたアーキテクチャーにおいては、ベクトル探索の速度は些細な実装上の問題ではなく、乗数です。エージェントとワークフローが取得 → 推論 → 取得を繰り返す際、検索パフォーマンスはエンドツーエンドのレイテンシ、スループット、モデルに供給されるコンテキストの品質を直接形作ります。</p><p>当社のベンチマークでは、Elasticsearchは、OpenSearchと比べて、より低いレイテンシで一貫して高い再現率を達成しました。これは、類似ベクトルの取得だけでなく、正確性が「適切なドキュメントを取得できること」に左右されるシナリオで顕著でした。制御されたデータセットではその差は明確であり、本番環境では、大量の検索呼び出しを通じてそれらの利点が蓄積され、応答性が向上し、容量余裕が増加し、インフラストラクチャーコストが削減されます。</p><h3>参考資料</h3><ol><li><a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">コンテキストエンジニアリングとは何ですか？</a></li><li><a href="https://www.elastic.co/search-labs/blog/series/context-engineering-hybrid-search-evolution">ハイブリッド検索とコンテキストエンジニアリングの進化</a></li><li><a href="https://www.elastic.co/search-labs/blog/context-engineering-relevance-ai-agents-elasticsearch">AIエージェント向けコンテキストエンジニアリングにおける関連性の影響</a></li></ol>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[jina-embeddings-v5-text：検索やインテリジェントアプリケーション向けのコンパクトで最先端のテキスト埋め込み]]></title>
            <description><![CDATA[jina-embeddings-v5-text-smallとjina-embeddings-v5-text-nanoを含むjina-embeddings-v5-textモデルを紹介し、Elastic Inference Service（EIS）を介してこれらの多言語埋め込みモデルを使用する方法を説明します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/jina-embeddings-v5-text</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/jina-embeddings-v5-text</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Scott Martens, Sofia Vasileva]]></dc:creator>
            <pubDate>Mon, 23 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e98dd30ab925b4bb32830228d71a1a51d02a0917-1600x840.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Jina AIとElasticは、<code>jina-embeddings-v5-text</code>をリリースします。これは、すべての主要なタスクタイプにおいて、同等サイズのモデルの中で最先端のパフォーマンスを備えた、新しい高性能でコンパクトなテキスト埋め込みモデルのファミリーです。</p><p>このファミリーには2つのモデルが含まれます。</p><ul><li><code>jina-embeddings-v5-text-small</code></li><li><code>jina-embeddings-v5-text-nano</code></li></ul><p>これらのモデルは、埋め込みモデルの革新的な新しいトレーニングレシピの成果です。いずれも、同等サイズのモデルよりも何倍も優れたパフォーマンスを発揮し、メモリとコンピューティングリソースを節約し、リクエストへの応答を高速化します。</p><p><code>jina-embeddings-v5-text-small</code>モデルは6億7700万パラメーターを持ち、32768トークンのインプットコンテキストウィンドウをサポートし、デフォルトで1024次元の埋め込みを生成します。</p><p><code>jina-embeddings-v5-text-nano</code> はその兄弟の約3分の1のサイズで、239Mのパラメーターと8192トークンのインプットコンテキストウィンドウを持ち、スリムな768次元の埋め込みを生成します。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">モデル名</td><td style="border: 1px solid #ccc; padding: 4px;">合計サイズ</td><td style="border: 1px solid #ccc; padding: 4px;">インプットコンテキストウィンドウサイズ</td><td style="border: 1px solid #ccc; padding: 4px;">埋め込みサイズ</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">jina-v5-text-small</td><td style="border: 1px solid #ccc; padding: 4px;">677M パラメータ</td><td style="border: 1px solid #ccc; padding: 4px;">32768トークン</td><td style="border: 1px solid #ccc; padding: 4px;">1024次元</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">jina-v5-text-nano</td><td style="border: 1px solid #ccc; padding: 4px;">2億3900万パラメーター</td><td style="border: 1px solid #ccc; padding: 4px;">8192 トークン</td><td style="border: 1px solid #ccc; padding: 4px;">768次元</td>
                    </tr>
              </tbody>
            </table>
          <p>これら2つのモデルは、MMTEB（<a href="https://huggingface.co/spaces/mteb/leaderboard">Multilingual MTEB</a>）ベンチマークの総合的なパフォーマンスにおいてクラス最高です。5億未満のパラメーターを持つモデルの中で<code>jina-embeddings-v5-text-nano</code>はトップパフォーマーであり、2億5000万未満のパラメータを持ちながらも、7億5000万未満のパラメータを持つ多言語埋め込みモデルの中で<code>jina-embeddings-v5-text-small</code>モデルがリーダーです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d97126285fa196c3045b1913b7754e4b186c1e4c-1300x1100.png" style="width: 100%;" /><p>これらのモデルは、Elastic Inference Service（EIS）やオンラインAPIを介して利用でき、ローカルホスティングでも利用できます。<code>jina-embeddings-v5-text</code>モデルにアクセスする方法については、以下の「<strong>はじめに</strong>」セクションを参照してください。</p><p>埋め込みモデルとセマンティックインデックスにより、検索アルゴリズムの精度が劇的に向上するだけでなく、以下のようなセマンティック類似性や意味抽出を伴うタスクにもさまざまな用途があります。</p><ul><li>重複したテキストの検索。</li><li>言い換えや翻訳の認識。</li><li>トピックの発見。</li><li>推奨エンジン。</li><li>感情分析と意図分析。</li><li>スパムフィルタリング。</li><li>その他多数。</li></ul><h2><strong>特徴</strong></h2><p>この新しいモデルファミリーには、関連性を高め、コストを削減するために設計された多くの特徴があります。</p><h3>タスクの最適化</h3><p>私たちは、<code>jina-embeddings-v5-text</code> のモデルを4つの幅広いタスクタイプに最適化しました。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">タスク</td><td style="border: 1px solid #ccc; padding: 4px;">ユースケースの例</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">検索</td><td style="border: 1px solid #ccc; padding: 4px;">自然言語クエリを使用して検索し、ドキュメントのコレクション内で最も関連性の高い一致を取得。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">テキストマッチング</td><td style="border: 1px solid #ccc; padding: 4px;">意味的類似性、重複除去、言い換えや翻訳の整合性など。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">クラスタリング</td><td style="border: 1px solid #ccc; padding: 4px;">トピックの検出、ドキュメントコレクションの自動整理。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">分類</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメントの分類、感情と意図の検出、同様のタスク。</td>
                    </tr>
              </tbody>
            </table>
          <p>あるタスクを最適化するということは、通常、別のタスクを妥協する必要があることを意味します。そのため、ほとんどの埋め込みモデルは、1種類のタスクに対してのみ競争力のあるパフォーマンスを発揮します。しかし、<code>jina-embeddings-v5-text</code>モデルは、タスク固有の<a href="https://arxiv.org/abs/2106.09685">Low-Rank Adaptation（LoRA）アダプター</a>をトレーニングすることで、妥協することなく4つのすべての分野に特化できます。</p><p>LoRAアダプターはAIモデルのプラグインの一種であり、全体のサイズをわずかに増やすだけでAIモデルの動作を劇的に変化させます。<code>jina-embeddings-v5-text</code> モデルファミリーでは、タスクごとにモデル全体を用意し、それぞれに何億ものパラメーターを持たせる代わりに、各タスクにコンパクトなLoRaアダプターを備えた1つのモデルだけを使用できます。これによりメモリ、ストレージ容量、推論コストを節約できます。</p><h3>埋め込みの切り捨て</h3><p>私たちは、 <a href="https://arxiv.org/abs/2205.13147">Matryoshka Representation Learning</a>を使用して<code>jina-embeddings-v5-text</code>モデルをトレーニングしました。これにより、品質への影響を最小限に抑えながら、埋め込みを小さなサイズに削減できます。</p><p>デフォルトでは、<code>jina-embeddings-v5-text-small</code> は1024次元の埋め込みベクトルを生成し、各ベクトルは16ビットで表されます。大量のドキュメントコレクションの場合、格納するデータ量が多くなり、埋め込みで満たされたベクトルデータベースでの検索は、データベースのサイズと各格納されたベクトルが持つ次元の数に比例します。</p><p>しかし、埋め込みのサイズを半分に減らす（1024次元のうち512次元を切り捨てる）だけで、占有スペースを半分にしながら検索速度を2倍にすることができます。これはパフォーマンスに影響を与えます。情報を捨てると精度が低下しますが、下のグラフが示すように、埋め込みの半分を取り除いてもパフォーマンスはわずかしか低下しません。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f11969682c81bcf22b5b29a6204ba6ec40471b3d-900x700.png" style="width: 100%;" /><p>埋め込みが少なくとも256次元であれば、精度の損失は比較的小さいはずです。しかし、そのレベルを下回ると、関連性と正確性はすぐに低下します。</p><p>このような埋め込みの切り捨てにより、ユーザーは精度と計算コストのバランスを自分で管理できるようになり、検索AIから大きな効率の向上と大幅なコスト削減を実現するツールを提供します。</p><h3>堅牢な量子化</h3><p><em>量子化</em>は埋め込みのサイズを縮小するもう一つの方法です。量子化では、各埋め込みの一部を破棄するのではなく、埋め込み内の数値の精度を下げます。<code>jina-embeddings-v5-text</code> モデルは16ビット数値で埋め込みを生成しますが、これらの数値を四捨五入して精度と格納に必要なビット数を減らすことができます。最も極端な場合では、各数値を1ビット（0または1）に減らすことで、<code>jina-embeddings-v5-text</code>のデフォルトの1024次元の埋め込みを2キロバイトから128バイトに圧縮できます。これは、バイナリ量子化だけで94％の削減になります。切り捨ての場合と同様に、これによりメモリと計算コストが大幅に節約されますが、切り捨てと同様に、量子化によって埋め込みの精度は低下します。</p><p><code>jina-embeddings-v5-text</code>モデルは<a href="https://www.elastic.co/jp/elasticsearch">Elasticsearch</a>の<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/bbq">Better Binary Quantization</a>に対応し、精度の損失を最小限に抑えるようにトレーニングされており、これらのモデルからの二値化埋め込みのベンチマークテストでは、非二値化モデルとほぼ同等の性能を示しています。二値化性能に関する詳細なアブレーション研究については<a href="https://arxiv.org/abs/2602.15547">テクニカルレポートを</a>ご参照ください。</p><h3>多言語パフォーマンス</h3><p>多くの埋め込みモデルは、多数の言語を含む素材でトレーニングされているため、多言語対応です。しかし、サポートされているすべての言語で、すべてが同じように優れたパフォーマンスを発揮するわけではありません。</p><p>MMTEBの多言語ベンチマークで211の言語を特定し、それらを分離して、言語ごとにモデルを類似のモデルと比較できるようにしました。下の画像は、私たちの結果をヒートマップとしてまとめたものです。各パッチは言語（ISO-639コードで識別）であり、緑色が濃くなるほど、類似モデルの平均と比較してモデルのパフォーマンスが優れていることを示します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/852ac5d0f1977bb0c1124d87f8863a9bb94eb7da-1600x765.png" style="width: 100%;" /><p>正確さは言語によって異なりますが、<code>jina-embeddings-v5-text</code> モデルは最先端か、世界のほとんどの言語でほぼ最新です。</p><p>詳細な多言語パフォーマンスについては、<a href="https://arxiv.org/abs/2602.15547"><code>jina-embeddings-v5-text</code>テクニカルレポート</a>を参照してください。</p><h2><strong>ElasticにおけるJina：最先端のネイティブAIによる検索</strong></h2><p><code>jina-embeddings-v5-text</code>モデルをEISで使用することで、<a href="https://www.elastic.co/jp/elasticsearch">Elasticsearch</a>でネイティブに高パフォーマンスの多言語埋め込みモデルを実行できます。これは、完全に管理されたGPUアクセラレーションによる推論で、インフラを提供したりスケールする必要はありません。<code>jina-embeddings-v5-text</code> モデルは、最新のAI開発によって強化されたコンパクトで多言語対応のモデルで、成長するEISモデルカタログを拡張します。これらのモデルは、情報検索と標準データ分析ベンチマークにおいて最先端のパフォーマンスを発揮し、他に類を見ない世界規模の多言語サポートを提供します。</p><p>サイズが大きく異なる2つのモデルが用意されているため、ユーザーは用途や予算に応じてどちらが最適かを判断できます。さらに、より小さなサイズに切り詰められたり、より低い精度に量子化されたりしてもパフォーマンスを維持する堅牢な埋め込みにより、<code>jina-embeddings-v5-text </code>モデルはストレージと計算コストおよび処理遅延においてさらなる具体的な節約の機会を提供します。</p><p><code>jina-embeddings-v5-text</code>ファミリー、Jina Reranker、Elasticの高速ベクトルおよびBM25検索により、ユーザーはElasticのエンドツーエンドの最先端の<a href="https://www.elastic.co/docs/solutions/search/hybrid-search">ハイブリッド検索</a>にアクセスできるようになりました。最も関連性の高い結果が必要な場合、Retrieval-Augmented Generation（RAG）パイプライン、検索アプリケーション、またはデータ分析において、ElasticとJinaの検索AIモデルは、堅固で費用対効果の高い品質を提供します。</p><h2><strong>はじめに</strong></h2><p><code>jina-embeddings-v5-text</code>モデルは<a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">EIS</a>に完全に統合されており、インデックス作成時に<strong><code>type</code> </strong>フィールドを<strong> <code>semantic_text</code></strong>に設定し、<code>inference_id</code>フィールドでモデル（<code>jina-embeddings-v5-text-small</code>または<code>jina-embeddings-v5-text-nano</code>）を指定することで使用できます（次の例を参照）</p><pre><code>PUT multilingual-semantic-index
{
  "mappings": {
    "properties": {
      "content": {
        "type": "semantic_text",
        "inference_id": ".jina-embeddings-v5-text-small"
      }
    }
  }
}

# Ingest data about France
POST multilingual-semantic-index/_doc
{
  "content": "The capital of France is Paris"}

GET multilingual-semantic-index/_search
{
  "query": {
    "semantic": {
      "field": "content",
      "query": "What is the French capital?"
    }
  }
}</code></pre><p><a href="https://www.elastic.co/jp/elasticsearch">Elasticsearch</a>はインデキシングおよび検索時に適切なLoRAアダプターを自動的に選択します。埋め込み次元（上記の「<strong>埋め込みの切り捨て</strong>」セクション参照）は、<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put">カスタム推論エンドポイントを作成</a>する際に設定できます。</p><p><strong><code>jina-embeddings-v5-text</code></strong>モデルの利用については <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector">Elasticsearchのドキュメント</a>を参照してください。</p><h2><strong>詳細情報</strong></h2><p><code>jina-embeddings-v5-text</code>モデルについて詳しくは、<a href="https://jina.ai/news/jina-embeddings-v5-text-distilling-4b-quality-into-sub-1b-multilingual-embeddings/">Jina AIブログのリリースノート</a>と<a href="https://arxiv.org/abs/2602.15547">テクニカルレポート</a>をお読みください。パフォーマンスとJina AIの革新的な新しいトレーニング手順に関するより詳細な技術情報が記載されています。これらのモデルのローカルダウンロードや運用については、Hugging Faceの<a href="https://huggingface.co/collections/jinaai/jina-embeddings-v5-text"><code>jina-embeddings-v5-text</code>コレクションページ</a>をご覧ください。</p><p>Jina AIモデルは <a href="https://spdx.org/licenses/CC-BY-NC-4.0">CC-BY-NC-4.0ライセンス</a>の下で利用可能ですので、自由にダウンロードして試すことができますが、商用利用の場合は <a href="https://www.elastic.co/contact?pg=global&amp;plcmt=nav&amp;cta=205352">Elastic Sales</a>までお問い合わせください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[最小スコアで意味的精度を確保]]></title>
            <description><![CDATA[最小スコアしきい値を採用することで意味的精度を向上させます。この記事にはセマンティック検索とハイブリッド検索の具体的な例が含まれています。 ]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/semantic-precision-minimum-score</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/semantic-precision-minimum-score</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Mattias Brunnert]]></dc:creator>
            <pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8b3b5910abfe16d48d309341a0027008b16c4340-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>セマンティック検索は、検索の関連性を高めるための無限の機会をもたらしました。ELSER、E5、Jina Embedding v4などの高品質な高密度・低密度モデルは、キーワードの一致ではなく、単語の意味に基づいて関連性の高い結果を返します。ただし、セマンティック検索では、テールで無関係な結果が返されたり、インデックス内に関連する結果がないクエリに対して無関係な結果が返されることがあります。この低密度モデルと高密度モデルの特性により、ユーザーを混乱させたり、大規模言語モデル（LLM）の貴重なトークンを無駄にしたりする可能性があります。</p><p>この記事では、最小スコアパラメータを使用して、セマンティック検索結果の精度を高める方法を学びます。このブログ記事で示された例を試したい場合は<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/ensuring-semantic-precision-with-minimum-score/ensuring_semantic_precision_with_minimum_score.ipynb">関連するJupyterノートブック</a>をご覧ください。</p><h2>背景：精度と再現率</h2><p>検索の関連性において、<em>精度</em>と<em>再現率</em>は重要な概念です。まだご存知でない読者の方は、これについて一読されることをおすすめします。以下は要約です。</p><ul><li><strong>精度：</strong>返される検索結果のうち、ユーザーに関連するものの割合。</li><li><strong>再現率：</strong>コーパス内のすべての関連ドキュメントのうち、検索結果セットに含まれるドキュメントの割合。</li></ul><p>つまり、言い換えれば、精度とは関連する結果<strong>のみ</strong>を返すことであり、再現率は<strong>すべての</strong>関連する結果を返すことです。ご想像のとおり、これらは競合する要件であることが多いです。セマンティック検索は再現率が非常に高い傾向がありますが、精度に問題が生じる可能性があります。以下では、この特性について説明します。</p><h2>最小スコアパラメーターの導入</h2><p>「min_score」パラメーターを使用すると、最小スコアを設定して精度を向上させることができます。これにより、定義されたしきい値未満のスコアを持つ一致が削除され、結果セットが切り捨てられます。以下は簡単な例です。</p><pre><code>GET search-movies/_search
{
  "retriever": {
    "linear": {
      "min_score": 4,
      "retrievers": [
        ...
      ]
    }
  }
}</code></pre><h2>スコアの正規化</h2><p>最小スコアを設定するのは良いことですが、すべてのセマンティックモデルが静的しきい値に適したスコアを返すわけではありません。例えば、ELSERは無制限のスコアを返します。<a href="https://huggingface.co/intfloat/e5-small#faq">一部の</a>高密度モデルのスコアは密集してクラスター化されており、特定のクエリのコンテキストでのみ意味を持ちます。</p><p>ほとんどのセマンティック検索では、「min_score」を適用する前に正規化アプローチを使用することをお勧めします。正規化により、ドキュメントのスコアが定義された範囲内に収まるようになります。Elasticsearchレトリバーは、「l2_norm」と「minmax」という2つの<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/linear-retriever#linear-retriever-normalizers">正規化機能</a>を提供します。最もよく使用されるのは「minmax」です。これは理解しやすく、多くのシナリオでうまく機能するためです。「minmax」の主な特性は次のとおりです。</p><ul><li>ドキュメントのスコアは0から1の間で配分されます。</li><li>最も高いスコアを持つドキュメントには常に1のスコアが付けられます。</li><li>最も低いスコアを持つドキュメントには常に0のスコアが付けられます。<ul><li>これにより、キーワード検索に適さなくなる可能性があります。詳細については「ハイブリッド検索」セクションをご覧ください。</li></ul></li></ul><p>以下は、 <code>min_score</code>を使用した正規化されたセマンティッククエリの例です。ランクウィンドウのサイズが500に増え、100から始まるより長い検索結果のリストを返すことができます。</p><pre><code>GET search-movies/_search
{
  "size": 100,
  "_source": [
    "title", "overview"
  ],
  "retriever": {
    "linear": {
      "rank_window_size": 500,
      "min_score": 0.25,
      "retrievers": [
        {
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "overview_vector",
                  "query": "superhero movie"
                }
              }
            }
          }
        }
      ]
    }
  }
}</code></pre><p>サイズは、通常の本番環境で見られるサイズよりも高い値に設定されています。これは、検索結果の品質を検査し、結果を調整できるようにするためです。</p><h2>線形レトリバーを使用したハイブリッド検索</h2><p>ハイブリッド検索の場合、最も簡単な方法は、すべてのスコアを正規化し、重みを割り当て、最小スコアを適用することです。合計が1になる重みを選択すると、合計スコアが0～1の範囲内に保たれることに注意してください。これにより、最終スコアの理解や<code>min_score</code>のチューニングが容易になります。以下はその例です。</p><pre><code>GET search-movies/_search
{
  "size": 100,
  "_source": ["title", "overview","keywords"],
  "retriever": {
    "linear": {
      "rank_window_size": 500,
      "min_score": 0.25,
      "retrievers": [
        {
          "weight": 0.6,
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "overview_vector",
                  "query": "superhero movie"
                }
              }
            }
          }
        },
        {
          "weight": 0.4,
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "multi_match": {
                  "query": "superhero movie",
                  "fields": ["overview","keywords", "title"],
                  "type": "cross_fields",
                  "minimum_should_match": "2"
                }
              }
            }
          }
        }
      ]
    }
  }
}</code></pre><h2>RRFを使用したハイブリッド検索</h2><p>BM25では、多くの場合、 <code>AND</code>演算子や<code>minimum_should_match</code>を使用するなど、他の手段で精度を制御します。さらに、単一で、正確で、まれな用語からなるクエリは、自然に検索結果の少ない検索結果を引き起こし、多くの場合、すべてが非常に関連性の高いものです。これにより、次のことが発生する可能性があります。</p><ul><li>絶対的なBM25スコアが最大スコアのヒットに近い場合でも、結果内のさらに後ろの結果にはBM25レトリバーで低い正規化スコアが割り当てられます。</li><li>非常に低いBM25スコアをセマンティックスコアに追加すると、合計がセマンティックスコアとして近似されます。</li><li>BM25スコアの寄与が不足すると、ドキュメントが<code>min_score threshold</code>によって破棄される可能性があります。</li></ul><p>解決策として、BM25とセマンティック結果を組み合わせるために、逆順位融合（RRF）を使用することができます。RRFは、各結果セット内の位置に焦点を当てることで、異なる検索アルゴリズムのスコアを比較するという課題を回避します。この場合、<code>min_score</code>はセマンティックレトリバーにのみ適用されます。</p><pre><code>GET search-movies/_search
{
  "_source": ["title", "overview","keywords"],
  "retriever": {
    "rrf": {
      "rank_window_size": 500,
      "retrievers": [
        {
          "linear": {
            "rank_window_size": 500,
            "min_score": 0.25,
            "retrievers": [
              {
                "normalizer": "minmax",
                "retriever": {
                  "standard": {
                    "query": {
                      "semantic": {
                        "field": "overview_vector",
                        "query": "superhero movie"
                      }
                    }
                  }
                }
              }
            ]
          }
        },
        {
          "standard": {
            "query": {
              "multi_match": {
                "query": "superhero movie",
                "fields": ["overview", "keywords","title"],
                "type": "cross_fields",
                "minimum_should_match": "2"
              }
            }
          }
        }
      ]
    }
  }
}</code></pre><h2>まとめ</h2><p><code>min_score</code>を使用することで、セマンティック検索アルゴリズムの高い再現率によって引き起こされる結果セット内の誤検出の数を減らす方法を示しました。レトリバーの詳細については、こちらの<a href="https://www.elastic.co/search-labs/blog/elasticsearch-retrievers">ブログ記事</a>と<a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">Elasticsearchのドキュメント</a>をご覧ください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Kubernetesでの依存関係管理]]></title>
            <description><![CDATA[Renovate CLIとArgoワークフローを使用してKubernetesの依存関係管理を効率化する方法。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/dependency-management-kubernetes</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/dependency-management-kubernetes</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Nikos Fotiou]]></dc:creator>
            <pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/00519840e6eec7101c1fb096afcae976ee0c454e-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>以下は、Kubernetes、Argo Workflows、Argo Events、Renovate CLIを使用して、更新を自動化し、Common Vulnerabilities and Exposures（CVE）に迅速に対処し、何千ものリポジトリ全体で新しいパッケージバージョンを効率的に伝播するセルフホスト型の依存関係管理プラットフォームを構築した方法です。</p><h2><strong>Elasticでの依存関係管理</strong></h2><p>Elasticでは、プライベートとパブリックの両方で、数百、数千ものリポジトリを管理する必要があります。重大なCVEが発見された場合、どのリポジトリが脆弱であるかを即座に回答して対処する必要があります。どれくらい早くパッチを当てられるでしょうか。セキュリティとは別に、生産性に関する質問も出てきます。手動のタスクにあまり時間をかけずに、新しいパッケージバージョンのリリースを、それに依存するすべてのリポジトリに迅速に広めるにはどうすればよいでしょうか。</p><p>依存関係管理の方法を探す最初のきっかけは、<a href="https://www.elastic.co/blog/reducing-cves-in-elastic-container-images">CVE削減</a>のための自動更新を備えた安全な基盤を確立する必要性でした。依存関係管理に関するソリューションを慎重に検討した後、まずセルフホスト型のインフラストラクチャーの作業を開始しました。私たちは独自のKubernetesクラスターを使用して、Mend Renovate Community Self-Hostedを実行していました。ユーザーがセルフサービスでアクセスできる依存関係管理プラットフォームを提供するというアイデアがありました。</p><p>最初の実験が成功したため、より多くのチームが私たちのプラットフォームを導入し、日常のリポジトリのライフサイクルにおける更新やCVEパッチ適用に使用するようになりました。この展開は非常に速く、私たちはすぐにセルフマネージドインストールの限界に達しました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e14d9f98d4eccaa08a335d5bd23d88e5debbb344-1600x1103.png" style="width: 100%;" /><h3><strong>課題：多数のリポジトリを持つ大規模な組織で依存関係管理プラットフォームを拡張するにはどうすればよいでしょうか。</strong></h3><p>当社の依存関係管理プラットフォームは、一度に1つのリポジトリを処理しており、シーケンシャルな処理モデルでは、当社の所有する多数のリポジトリに対応できませんでした。依存関係管理ツールの<strong>単一のインスタンス</strong>で拡大し続けるリポジトリのリストを処理するという概念に問題があることは、すでに認識していました。リポジトリはキュー内に留まり、場合によっては何時間も待機することがありました。リポジトリの50%以上は毎日処理されていませんでした。つまり、当社のリポジトリの50%以上がスキャン間でを24時間以上待っていたということです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/45ade5bda08f82bed0b3d0d3736cbd6f056e7a4e-1312x816.jpg" style="width: 100%;" /><p>大規模なリポジトリでは、コードベースのサイズが大きく、PRが複数オープンしているため、ボトルネックが大きくなります。GitHub Webhookイベントによりこのシーケンスが中断されました。スキャンのタイミングが予測できないため、Automergeの信頼性が低下しました。スキャンの頻度についてはユーザーと約束していましたが、それを果たすことができませんでした。</p><h3><strong>社内で構築するという決定：Elastic独自のスケールとセキュリティのニーズに対応</strong></h3><p>商用オプション、具体的には<strong>MendのRenovate Self-Hosted Enterprise Self-Hosted版</strong>も検討しましたが、Elastic社内ではいくつかの主要な取り組みが進行中でした。</p><p>社内プラットフォームを構築するという当社の決定は、Elastic の特定の譲れない要件を満たすには、徹底的にカスタマイズされたソリューションしかないという認識に基づいていました。</p><ol><li><strong>内部開発者プラットフォームへの投資：</strong>当時、私たちはすでに内部開発者プラットフォームに多額の投資を行っていました。それぞれのサービスをこれに適合させる方法について議論し、設計していました。つまり、依存関係管理プラットフォームの独自のルールと実践をテストドライブするというニーズがあり、それに加えて、新しいガイドラインが導入されることになり、イベントに先立ってプラットフォームを設計したいと考えていました。</li><li><strong>ネイティブ統合とワークフローのカスタマイズ：</strong>社内ツールや社内プロセスとの簡単な統合が必要で、例えば、Service Catalog（Backstage）を使用して構成をコードとして一元管理したいと考えていました。Backstageの使用に関しては、特定のニーズがあり、当社のプラットフォームと互換性を持たせたいと考えていました。したがって、Renovate Self-Hosted APIをBackstageの自動化と併用することは可能ですが、これでは当社の内部プロセスを完全にカバーすることはできません。</li><li><strong>Elastic特有の徹底したセキュリティ対策：</strong>当社の厳格なセキュリティコンプライアンスには、当社のエコシステムに合わせた特注のセキュリティメカニズムが必要でした。<a href="https://entro.security/blog/how-elastic-scaled-secrets-nhi-security-elastics-playbook-from-visibility-to-automation/">「非人間的アイデンティティ」の使用の強化</a>に取り組んでいました。このアクセス強化の仕組みにより、GitHub への認証を行う非標準の手段は、この内部実装をサポートしていない市販のツールでは機能しなくなります。当社のワークフローには、親子ワークフローの秘密の暗号化パターンを実装し、一時的な使い捨てのGitHubトークンを使用することが含まれていました。社内で構築することが、これらの独自のセキュリティレイヤーを組み込み、複雑なマルチクラウド環境全体の攻撃対象領域を最小限に抑える唯一の実用的な方法でした。</li></ol><h2><strong>解決策：依存関係管理のためのワークフローオーケストレーション</strong></h2><p>解決策の構築は、既に使用している依存関係管理ツールを基に構築し、それを置き換えたり他のソリューションを探したりするのではなく、その上に構築することから始まりました。その可能性の兆しはあり、その柔軟性は組織全体のさまざまなニーズにとって重要です。さまざまなソリューションを検討しましたが、最終的に決め手となったのは、カバーしなければならない大きくて時に特殊なニーズでした。私たちは、各リポジトリが独自に処理され、ボトルネックを解消して成長に備えられる、信頼性が高くスケーラブルな依存関係管理プラットフォームを構築することを決定しました。</p><p>プラットフォームは次の3つのコア原則に従って設計しました。</p><h3><strong>1. 並列処理</strong></h3><p>各リポジトリに独自の依存関係管理処理環境が与えられます。キューはなくなります。同時実行性は、消費するリソースの数によってのみ制限されます。また、GitHubでレート制限を受けないようにスマートな分散スケジューリングを適用しました。</p><h3><strong>2．セルフサービス可能</strong></h3><p>Service Catalog（Backstage）を使用して、新しいリポジトリを自動的にオンボードして管理します。独自のリソース定義を使用して、エンドユーザーにリポジトリの処理頻度を選択するオプション、スケジュールに割り当てるリソースの量、何らかの理由で処理をオンまたはオフにするオプションを提供します。ユーザーのニーズが進化し、新しいインストールに慣れてきたら、そのようにしてさらに多くのオプションを追加していく予定です。</p><h3><strong>3. シークレットのスコープと名前空間の分離の縮小</strong></h3><p>セキュリティを強化するために、各ワークフローの開始時に生成される一時的なGitHubトークンを依存関係管理ポッドに提供します。さらに、ワークロードを特定の名前空間に分離して、必要なシークレットのみが提供されるようにします。Kubernetes RBACを使用して、各依存関係管理ワークフローでアクセスできるシークレットを制御します。また、暗号化を使用して、親ワークフローから子ワークフローにGitHubトークンを伝播します。</p><p>Kubernetesを使用してプラットフォームを再構築し、Kubernetesのパワーを活用しました。Argo Workflowsはプロセスのロジックを強化し、Renovate CLIはリポジトリを一度に1つずつスキャンして処理するように設定されています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5560ed20e2bd9ecdd574a9c835126d12b24c332f-1600x1157.png" style="width: 100%;" /><p><strong>ここで素晴らしい点は</strong>、実績のあるオープンソースプロジェクトを独自の方法で使用し、すべてのプロジェクトに新しい実用的な例を提供すると同時に、開発速度を増幅し、チームのCVE削減を強化していることです。</p><h2><strong>依存関係管理アーキテクチャー：4つのマイクロサービス</strong></h2><p>このプラットフォームは、次の4つのカスタムビルドコンポーネントで構成されています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2b3d4046c05bb261e45d40c59f864eb51fb9eaa9-1217x1600.png" style="width: 100%;" /><h3><strong>ワークフローオペレーター（Go/Kubebuilder）</strong></h3><p>3つのカスタムリソース定義（CRD）を通じてワークフローライフサイクルを管理するKubernetesオペレーター：</p><ul><li><strong>RepoConfig CRD：</strong>リポジトリ設定のための信頼できる唯一の情報源。</li></ul><p>RepoConfigはオペレーターで次のように定義されています。</p><pre><code>// RepoConfig is the Schema for the repoconfigs API
type RepoConfig struct {
	metav1.TypeMeta `json:",inline"`

	// metadata is a standard object metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty,omitzero"`

	// spec defines the desired state of RepoConfig
	// +required
	Spec RepoConfigSpec `json:"spec"`

	// status defines the observed state of RepoConfig
	// +optional
	Status RepoConfigStatus `json:"status,omitempty,omitzero"`
}</code></pre><p>RepoConfigのインスタンスは次のようになります。</p><pre><code>apiVersion: workflows.elastic.co/v1
kind: RepoConfig
metadata:
  generation: 3
  name: elastic-test-repo
  namespace: dependency-management-operator
spec:
  owner: group:my-team
  renovate:
    config:
      resourceGroup: SMALL
      runFrequency: 4h
    enabled: true
  repository: elastic/test-repo</code></pre><ul><li><strong>親CRD：</strong>スケジュールされたスキャンのCronWorkflowを管理します。</li></ul><p>親コントローラの調整ループ内では、ワークフロー設定が作成され、最新の状態に保たれ、必要に応じて削除されることを確認します。</p><p>まず、ワークフローのグローバル設定を取得します。</p><pre><code>func (r *ParentReconciler) reconcileSubResources(ctx context.Context, req ctrl.Request, parent *workflowsv1.Parent) error {
	logger := logf.FromContext(ctx)
	logger.Info("Reconcile SubResources for Parent", "name", req.NamespacedName)
	wfSet := workflowsettings.WorkflowSettings{
		RunFrequency:   parent.Spec.RunFrequency,
		ResourceGroups: "parent",
	}</code></pre><p>mutexのconfigmapが最新であることを確認し、類似したワークフローが同時に動作しないようにします:</p><pre><code>	cfMngr := resources.NewConfigMapManager(r.Client, r.Scheme, r.OperatorConfig.ParentNamespace)
	err := cfMngr.CreateOrUpdateSyncMutexConfigmap(ctx, fmt.Sprintf("%s%s", r.OperatorConfig.ResourcesPrefix, r.OperatorConfig.SyncMutexCfgMapName), strings.TrimPrefix(parent.Spec.Repository, "elastic/"), r.OperatorConfig.SemaphoreConcurrencyLimit)</code></pre><p>そして、Cronワークフローとワークフローテンプレートを作成または更新する構造体であるワークフローマネージャーを作成します。</p><pre><code>	wfMngr := resources.NewArgoWorkflowManager(r.Client,
		r.Scheme,
		curateResourceName(
			strings.ReplaceAll(parent.Spec.Repository, "/", "-"),
		),
		parent.Namespace,
		"parent-workflow",
		false).
		WithOrganization(r.OperatorConfig.GitHubOrg).
		WithRepoName(parent.Spec.Repository).
		Init(true, true).
		WithPrefix(r.OperatorConfig.ResourcesPrefix).
		WithWfTemplateName(r.OperatorConfig.ParentWorkflowTemplate).
		WithResources(wfSet.GetResourceCategory()).
		WithSchedule(wfSet.GetCronSchedule()).
		WithImagePullSecrets([]corev1.LocalObjectReference{{
			Name: r.OperatorConfig.WorkflowImagePullSecrets,
		}}).
		AddArgument(true, true, "extra_cli_args").
		SetArgument(true, false, "extra_cli_args", "none").
		AddTemplate(resources.NewParentDAGTemplateInstance()).
		AddTemplate(resources.NewWorkflowsTemplateInstance("check-child-workflows", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector)).
		AddTemplate(resources.NewWorkflowsTemplateInstance("security", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector)).
		AddTemplate(resources.NewWorkflowsTemplateInstance("submit-child-workflow", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector))
	wfMngr.OverWriteCommand("submit-child-workflow", r.OperatorConfig.ChildNamespace)
	wfMngr.OverwriteWfTemplateName("parent-wftmpl")
	wfMngr.AddSynchronization(fmt.Sprintf("%s%s", r.OperatorConfig.ResourcesPrefix, r.OperatorConfig.SyncMutexCfgMapName), "{{workflow.parameters.repo_name}}")
	err = wfMngr.CreateOrUpdateCronWorkflow(ctx)
	if err != nil {
		return fmt.Errorf("failed to create or update cron workflow: %w", err)
	}
	err = wfMngr.CreateOrUpdateWorkflowTemplate(ctx)
	if err != nil {
		return fmt.Errorf("failed to create or update workflow template: %w", err)
	}
	return nil</code></pre><ul><li><strong>子CRD：</strong>リポジトリごとのリソースを使用してWorkflowTemplatesを管理します。</li></ul><p>子コントローラにーは親と同様の調整義務がありますが、今回は親ワークフローによってトリガーされる子名前空間内のワークフローテンプレートを担当します。</p><pre><code>func (r *ChildReconciler) reconcileSubResources(ctx context.Context, req ctrl.Request, child *workflowsv1.Child) error {
	logger := logf.FromContext(ctx)
	logger.Info("Reconcile SubResources for Child", "name", req.NamespacedName)
	wfSet := workflowsettings.WorkflowSettings{
		ResourceGroups: child.Spec.ResourceCategory,
	}
	wfMngr := resources.NewArgoWorkflowManager(r.Client,
		r.Scheme,
		curateResourceName(
			strings.ReplaceAll(child.Spec.Repository, "/", "-"),
		),
		child.Namespace,
		"runner",
		true).
		Init(false, true). // only manage workflow template
		WithPrefix(r.OperatorConfig.ResourcesPrefix).
		WithSuffix("-child-wftmpl").
		WithRepoName(child.Spec.Repository).
		WithOrganization(r.OperatorConfig.GitHubOrg).
		WithResources(wfSet.GetResourceCategory()). // will override resources of presets if set
		WithImagePullSecrets([]corev1.LocalObjectReference{{
			Name: r.OperatorConfig.WorkflowImagePullSecrets,
		}}).
		AddTemplate(resources.NewWorkflowsTemplateInstance("runner", r.OperatorConfig.WorkflowImagePullPolicy, r.OperatorConfig.WorkflowNodeSelector)).
		AddArgument(false, true, "repo_full_name").
		AddArgument(false, true, "repo_name").
		AddArgument(false, true, "encrypted_token").
		AddArgument(false, true, "extra_cli_args")
	wfMngr.OverWriteCommand("runner", r.OperatorConfig.ChildNamespace)
	err := wfMngr.CreateOrUpdateWorkflowTemplate(ctx)
	if err != nil {
		return fmt.Errorf("failed to create or update workflow template: %w", err)
	}
	return nil
}</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7ac70492a1266ba02cb8afbafc5a486cb38a0edc-1600x1290.png" style="width: 100%;" /><p>マルチコントローラーパターンでは明確な分離が実現されます。RepoConfigコントローラーはオンボーディング／オフボーディングを処理し、親コントローラーはスケジュールを管理し、子コントローラーは実行テンプレートを処理します。</p><h3><strong>GitHuイベントゲートウェイ（Go）</strong></h3><p>GitHubのwebhookを受信し、署名を検証し、組織／リポジトリでフィルタリングし、Argo Eventsにルーティングするセキュアなwebhookプロキシです。依存関係ダッシュボードのインタラクション、PRイベント、パッケージの更新に対応する10個の異なるセンサーを構築しました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4828625456cbd6efa8020a20f10d23f294f98a02-1306x1600.png" style="width: 100%;" /><p>このゲートウェイは、以下の方法でGitHub Appsとの統合を可能にします。</p><ul><li>セキュリティのため、受信したGitHub webhook署名を検証しています。</li><li>有効なイベントを、すべての関連ヘッダーと認証とともにArgo Events EventSourceに転送します。</li><li>また、EventSourceにAuthSecretを設定し、これを転送されるリクエストのBearerヘッダーとして提供します。</li><li>ログ、メトリクス、再試行ロジックを提供します。</li></ul><p>各GitHubイベントリクエストに対して様々な検証を行います。</p><p>以下のHTTP属性が存在することを確認します。</p><pre><code>// ValidateRequestMethod checks if the request method is POST.
func ValidateRequestMethod(r *http.Request) error {
	if r.Method != http.MethodPost {
		return fmt.Errorf("method not allowed, only POST is accepted")
	}
	return nil
}

// ValidateRequiredHeaders checks for required GitHub headers.
func ValidateRequiredHeaders(r *http.Request) error {
	eventType := r.Header.Get("X-GitHub-Event")
	deliveryID := r.Header.Get("X-GitHub-Delivery")
	signature := r.Header.Get("X-Hub-Signature-256")
	if eventType == "" || deliveryID == "" || signature == "" {
		return fmt.Errorf("missing required GitHub headers")
	}
	return nil
}

// ValidateUserAgent checks that the User-Agent header starts with GitHub-Hookshot/
func ValidateUserAgent(r *http.Request) error {
	userAgent := r.Header.Get("User-Agent")
	if !strings.HasPrefix(userAgent, "GitHub-Hookshot/") {
		return fmt.Errorf("invalid User-Agent")
	}
	return nil
}</code></pre><p>また、各リクエストの署名とその構成も検証します。</p><pre><code>// ValidateSignature verifies the GitHub webhook signature.
func ValidateSignature(r *http.Request, secret string) ([]byte, error) {
	payload, err := GitHub.ValidatePayload(r, []byte(secret))
	if err != nil {
		return nil, fmt.Errorf("invalid GitHub signature: %w", err)
	}
	return payload, nil
}

// ValidateAllowedOwner checks if the organization login is in the allowed organizations list.
func ValidateAllowedOwner(payload []byte, allowedGitHubOrganizations []string) (string, error) {
	var orgLogin string
	var payloadMap map[string]any
	if err := json.Unmarshal(payload, &payloadMap); err == nil {
		if orgObj, ok := payloadMap["organization"].(map[string]any); ok {
			if login, ok := orgObj["login"].(string); ok {
				orgLogin = login
			} else if name, ok := orgObj["name"].(string); ok {
				orgLogin = name
			}
		}
	}
	if !slices.Contains(allowedGitHubOrganizations, orgLogin) {
		return orgLogin, fmt.Errorf("organization login not allowed")
	}
	return orgLogin, nil
}</code></pre><p>最後に、イベントの種類に基づいてArgo Eventsにルーティングします。</p><pre><code>	// Map eventType to Argo `EventSource` path
	var endpoint string
	switch eventType {
	case "push":
		endpoint = "/push"
	case "issues":
		endpoint = "/issues"
	case "pull_request":
		endpoint = "/pull-requests"
	default:
		slog.Info("Ignoring unhandled event type", "event_type", eventType, "delivery_id", deliveryID)
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("ok"))
		return
	}
	forwardURL := h.config.ArgoEventSourceForwardURL + endpoint</code></pre><p>Argo Events側では、10個のセンサーがArgo Events EventBusで新しいイベントを監視します。</p><pre><code>apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
  name: {{ .Values.sensors.packageUpdateOnDefaultBranch.name }}
  namespace: {{ .Release.Namespace }}
spec:
  eventBusName: {{ .Values.eventBus.name }}</code></pre><p>次に、スクリプトは各センサーのロジックを適用します。</p><pre><code>script: |
          local e = event
          if not e or not e.body or not e.body.repository then
            return false
          end

          -- e.g., "refs/heads/main"
          local ref = e.body.ref
          local default_branch = e.body.repository.default_branch
          if not ref or not default_branch then
            return false
          end

          local expected = "refs/heads/" .. default_branch
          if ref ~= expected then
            return false
          end

        {{- if .Values.sensors.packageUpdateOnDefaultBranch.packageFiles }}
          patterns = { {{- range $i, $f := .Values.sensors.packageUpdateOnDefaultBranch.packageFiles }}{{ if $i }}, {{ end }}"{{ $f }}"{{- end }} }
        {{- end }}

          local function anyMatch(path)
            if type(path) ~= "string" then return false end
            for _, pat in ipairs(patterns) do
              -- match filename at repo root, or anywhere under subdirs
              if path:match(pat) or path:match(".+/" .. pat) then
                return true
              end
            end
            return false
          end

          local function filesContainPackage(paths)
            if type(paths) ~= "table" then return false end
            for _, p in ipairs(paths) do
              if anyMatch(p) then return true end
            end
            return false
          end

          -- Inspect all commits (GitHub includes added/modified/removed lists)
          local commits = e.body.commits
          if type(commits) ~= "table" then
            -- Fallback: some payloads include only head_commit
            commits = {}
            if type(e.body.head_commit) == "table" then
              table.insert(commits, e.body.head_commit)
            end
          end

          for _, c in ipairs(commits) do
            if filesContainPackage(c.added) or filesContainPackage(c.modified) or filesContainPackage(c.removed) then
              return true
            end
          end

          return false</code></pre><h3><strong>Backstage Syncer（Go）</strong></h3><p>このプロセスでは、Service Catalog（Backstage）に対してRepository Real Resource Entitiesのポーリングを行い、それらをRepoConfig CRDに変換し、プラットフォームを設定変更と同期させます。変更は3分以内に適用されます。</p><pre><code>repoMap := make(map[string]map[string]interface{})
			for i := range entities {
				entity := &entities[i]
				if entity.Spec.Type != "GitHub-repository" {
					continue
				}

				implRaw, err := json.Marshal(entity.Spec.Implementation)
				if err != nil {
					logger.Error("Failed to marshal implementation", "error", err)
					continue
				}

				var implMap map[string]interface{}
				err = json.Unmarshal(implRaw, &implMap)
				if err != nil {
					logger.Error("Failed to unmarshal implementation map", "error", err)
					continue
				}
				var repoName string
				if specMap, ok := implMap["spec"].(map[string]interface{}); ok {
					if repo, ok := specMap["repository"].(string); ok {
						repoName = repo
					}
				}
				if repoName == "" {
					continue
				}

				var workflowsRaw []byte
				if v, ok := implMap["spec"].(map[string]interface{}); ok {
					if r, ok := v["renovate"]; ok {
						workflowsRaw, _ = json.Marshal(r)
					} else {
						workflowsRaw = []byte(`{}`)
					}
				} else {
					workflowsRaw = []byte(`{}`)
				}

				var workflowsWithDefaults schema.WorkflowsMetadata
				err = json.Unmarshal(workflowsRaw, &rworkflowsWithDefaults)
				if err != nil {
					logger.Error("Failed to unmarshal workflows config", "error", err)
					continue
				}

				workflowsMap := map[string]interface{}{
					"enabled":        workflowsWithDefaults.Enabled,
					"require_pr":     workflowsWithDefaults.RequirePr,
					"resource_group": string(workflowsWithDefaults.ResourceGroup),
					"run_frequency":  string(workflowsWithDefaults.RunFrequency),
				}
				repoMap[repoName] = map[string]interface{}{
					"renovate": workflowsMap,
					"owner":    entity.Spec.Owner,
				}
			}
			logger.Info("Fetched GitHub Repository data from Backstage", "repository_count", len(repoMap), "status_code", resp.StatusCode)</code></pre><p>最後に、そのデータをRepoConfigインスタンスに書き込みます。</p><h3><strong>ワークフローベース（混合：JavaScript、Go、Helm）</strong></h3><p>基盤レイヤーには、Helmチャート、JavaScript設定、暗号化サポート付きのRenovate CLI用のGoラッパー、Alpineパッケージ用のカスタムAPKインデクサーが含まれています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/908d19278face3ce1119dbee9146c1264b6e2f30-1600x873.png" style="width: 100%;" /><h2><strong>セルフサービス構成</strong></h2><p>チームはBackstageを通じてリポジトリを宣言的に設定します。</p><pre><code>spec:
  renovate:
    enabled: true
    config:
      resourceGroup: LARGE      # SMALL | MEDIUM | LARGE  
      runFrequency: "0 */4 * * *"  # Every 4 hours</code></pre><p>リソースグループはリポジトリサイズに基づいてCPUとメモリを割り当てます。</p><ul><li><strong>SMALL:</strong> 500m CPU、1Giメモリ。</li><li><strong>MEDIUM:</strong> 1000m CPU、2Giメモリ。</li><li><strong>LARGE:</strong> 2000m CPU、4Giメモリ。</li></ul><p>構成はバージョン管理され、監査可能で、自動的に適用されます。</p><h2><strong>親子パターン</strong></h2><p>実行モデルは親子ワークフローパターンを使用しています。</p><ul><li><strong>親ワークフロー：</strong>スケジュールに従って実行される軽量のCronワークフロー。シークレットを暗号化し、スキャンを実行するかどうかを決定し、子プロセスに構成を渡します。</li><li><strong>子ワークフロー：</strong>Renovate CLIが実行される一時的なポッド。リソースを動的に割り当て、シークレットを単独で復号し、完了後に終了します。</li></ul><p>この分離により、セキュリティ（親レベルで暗号化されたシークレット）、リソースの最適化（親は最小限のリソースを使用）、拡張性（子が並行して実行）が提供されます。</p><h2><strong>結果</strong></h2><h3><strong>パフォーマンスの変化</strong></h3><ul><li><strong>変更前：</strong>一度に1つのリポジトリで、一部のリポジトリは、おそらく1日以上、1日あたり1,000回未満のスキャンでも処理されませんでした。</li><li><strong>変更後：</strong>100以上の同時スキャン、通常は8,000件のスキャンと1日あたり最大10,000件の記録スキャン。制限は、当社が費やすリソースの量とGitHubのレート制限の扱い方のみです。</li></ul><h3><strong>費用対効果</strong></h3><p>奇妙に聞こえるかもしれませんが、1 日に8,000件のポッドを実行すると、同じ結果を達成するために 1 つの長時間実行ポッドを実行するよりもはるかに安価に同じ結果を得ることができます。</p><p>以前の設定では、単一のインスタンスを実行していて、調子が良い日には500～600回のスキャンを実行していました。同時に、異なる種類のリポジトリが同じポッドで実行されることから、最大のものに合わせてポッドのサイズを設定する必要がありました。そのサイズは、現在の8つのCPUと16GBのメモリを搭載する特大モデルよりもはるかに大きくなります。</p><p>現在の日次出力を満たすには、単一のポッドなら12日間実行する必要があります。それでは、12日間稼働する単一のポッドのコストを、毎日稼働する8,000個の「MEDIUM」サイズのポッドのコストと比較すると、新しい設計は同じスキャン出力に対してはるかに効率的です。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">メトリック</td><td style="border: 1px solid #ccc; padding: 4px;">シナリオ A（ワークフロー）</td><td style="border: 1px solid #ccc; padding: 4px;">シナリオB（長時間実行される単一のポッド）</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">セットアップ</td><td style="border: 1px solid #ccc; padding: 4px;">8,000ポッド（1 vCPU／2GB）</td><td style="border: 1px solid #ccc; padding: 4px;">1 pod (8 vCPU / 16 GB)*</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">期間</td><td style="border: 1px solid #ccc; padding: 4px;">それぞれ10分</td><td style="border: 1px solid #ccc; padding: 4px;">12日間連続</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">総作業時間</td><td style="border: 1px solid #ccc; padding: 4px;">1,333計算時間</td><td style="border: 1px solid #ccc; padding: 4px;">288計算時間</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">総コスト</td><td style="border: 1px solid #ccc; padding: 4px;">65.83ドル</td><td style="border: 1px solid #ccc; padding: 4px;">$113.75</td>
                    </tr>
              </tbody>
            </table>
          <p>しかし、ワークロードのデフォルト設定が「SMALL」に設定されていることを考慮に入れましょう。大多数は0.5 CPUと1G RAMで正常に実行されており、規模を増やす変更する必要があるのはごく一部です。ワークロードの60％が「SMALL」で実行され、30％が「MEDIUM」で実行され、10％が「LARGE」で実行されている場合（これが実際に近い値です）に何が起こるかを見てみましょう。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">メトリック</td><td style="border: 1px solid #ccc; padding: 4px;">シナリオA（混合群）</td><td style="border: 1px solid #ccc; padding: 4px;">シナリオB（長時間実行）</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">戦略</td><td style="border: 1px solid #ccc; padding: 4px;">8,000ポッド（混合サイズ）</td><td style="border: 1px solid #ccc; padding: 4px;">1 pod (8 vCPU / 16 GB)*</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">期間</td><td style="border: 1px solid #ccc; padding: 4px;">それぞれ10分</td><td style="border: 1px solid #ccc; padding: 4px;">12日間連続</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">総コスト</td><td style="border: 1px solid #ccc; padding: 4px;">$52.66</td><td style="border: 1px solid #ccc; padding: 4px;">$113.75</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">節約額</td><td style="border: 1px solid #ccc; padding: 4px;">$61.09（54%安価）</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
              </tbody>
            </table>
          <p>同じ出力に対して、現在の設定ではるかに費用対効果に優れていることがわかります。</p><h3><strong>強化されたセキュリティ</strong></h3><ul><li>一時的なGitHubトークン（数分間の公開と数日間の公開の比較）。</li><li>ロールベースアクセス制御（RBAC）境界による名前空間の分離。</li><li>親ワークフロー内の保存時のシークレットの暗号化。</li><li>直接のVaultアクセスを削除。</li></ul><h3><strong>予測可能なパフォーマンス</strong></h3><p>スキャン頻度が保証されれば、最終的にサービスレベル目標（SLO）を設定できます。Automergeは確実に動作します。チームは、プラットフォームが約束どおりの成果をもたらすと信頼しています。</p><h2><strong>重要なアーキテクチャ上の決定</strong></h2><p>ここでは、プラットフォームの外観を形作る重要な設計上の決定事項をいくつか紹介します。</p><ul><li><strong>親子ワークフローが必要な理由</strong></li></ul><p><strong>多層防御</strong>戦略を実施するためにこのパターンを採用しました。価値の高い認証情報（GitHubアプリのシークレットなど）を専用のロックダウンされた名前空間に制限することで、<strong>RBAC</strong>を使用して、一時的な実行ポッドが機密データに恣意的にアクセスできないようにします。最近のサプライチェーンの脆弱性（例えば、<strong>&quot;Shai Hulud&quot;</strong>継続的インテグレーション/継続的デリバリー[CI/CD]攻撃）は、動的スクリプトを実行するランタイム環境を認証情報ストアから分離することの重要性を実証しています。</p><p>同時に、この分離により、<strong>きめ細かなリソースの最適化</strong>が可能になります。「親」ワークフローは軽量のオーケストレーターとして機能し、最小限のフットプリントで動作します。一方、「子」ワークフローは計算集約型の依存関係スキャンを処理します。この分離により、各レイヤーに個別の調整ロジックを適用し、スケジューリングとセキュリティインフラ（親）の管理制御を維持しながら、実行パラメータ（子）の制御をユーザーに許可することで、<strong>ライフサイクル管理</strong>を簡素化できます。</p><ul><li><strong>セルフサービス可能な理由</strong></li></ul><p>リポジトリ構成のボトルネックとなる当社のチームを排除することは重要な要件でした。私たちの使命は、多様なユースケースをサポートできるスケーラブルな<strong>セルフサービスプラットフォーム</strong>を設計することでした。リポジトリの膨大な量を考慮すると、すべての構成変更の<strong>ゲートキーパー</strong>として機能することは持続不可能であると認識しました。代わりに、私たちは「レール」（インフラストラクチャと<strong>ガードレール</strong>）を提供しながら、ユーザーが「列車」（実行とカスタマイズ）を運転できるようにするという支援の哲学を採用しました。こうした<strong>チームの自律性</strong>へのシフトが、ユーザーがシステムをそれぞれの具体的な業務ニーズに合わせてカスタマイズできるようにすることで、生産性を大幅に向上させると信じています。</p><ul><li><strong>Kubernetes Operatorパターンを選択した理由</strong></li></ul><p>上記で述べたように、基本的な設計原則は、プラットフォームが完全に<strong>セルフサービス可能</strong>であることを確保することでした。ユーザーの意図（スキャンの切り替え、スケジューリング頻度の調整、ランタイムリソース制限の調整など）を捉え、それらの変更を基盤となるワークフローに瞬時に反映する自動化されたメカニズムが必要でした。将来の要件を予測し、システムは簡単に<strong>拡張可能</strong>である必要もありました。</p><p>これを実現するために、カスタムの<strong>依存関係管理Kubernetes Operator</strong>を開発しました。設定のインターフェースとして<strong>CRD</strong>を使用することで、<strong>Kubernetesネイティブな調整ループ</strong>を確立しました。このオペレーターは、ユーザーが定義した望ましい状態を継続的に監視し、ワークフローインフラに必要な更新を自動的に管理します。これにより、<strong>イベント駆動型</strong>のシームレスな操作が保証され、プラットフォームのロジックが裏側ですべての複雑さを処理します。</p><ul><li><strong>GitHubイベントゲートウェイを設計する理由</strong></li></ul><p>プラットフォームの応答性を高めるために、<strong>イベントドリブンアーキテクチャー（EDA）</strong>の採用が不可欠でした。CronWorkflowsは信頼性の高いベースラインスケジュールを提供しましたが、ユーザーがダッシュボードから手動でスキャンをトリガーするなど<strong>アドホック実行</strong>を処理できる俊敏性が必要でした。これを達成するために、ペイロードの整合性を検証し、リクエストをインテリジェントにルーティングするための専用の<strong>インジェストゲートウェイ</strong>が必要でした。</p><p>既存のソリューション、特にArgoのネイティブGitHub EventSourceを評価しましたが、 <strong>運用上のオーバーヘッド</strong>や厳格な <strong>GitHub APIクォータ</strong>（例：リポジトリごとのwebhook制限）に関する重大なリスクを特定しました。結果として、これらの制限からインフラを切り離すためにカスタムゲートウェイを構築しました。</p><p>重要なのは、このゲートウェイが移行中に戦略的な<strong>トラフィック制御ポイント</strong>として機能したことです。これはスイッチとして機能し、レガシーシステムから新しいインフラストラクチャへの<strong>段階的かつ詳細なロールアウト</strong>（トラフィックの移行）を実行できるようになりました。これにより、数千のリポジトリのオンボーディングが「ビッグバン」的な切り替えではなく、制御されたリスクのないプロセスになることが保証されました。</p><p></p><h2><strong>教訓</strong></h2><p>私たちが学んだいくつかの教訓は <a href="https://www.elastic.co/about/our-source-code">Elasticソースコード</a>と密接に関連しています。</p><ol><li><strong>顧客第一：</strong>プラットフォームはユーザーのために構築されます。したがって、ユーザーのニーズを最優先にすることが重要です。これにより、プラットフォームは、ユーザーとの摩擦を減らし、プラットフォームの拡張を簡素化し、導入を容易にする、効率的に設計されたインフラストラクチャとアプリケーションに形作られます。</li><li><strong>空間と時間：</strong>時には、最も抵抗の少ない道が<strong>不安定な状況</strong>につながることがあります。当初、既存の順次処理モデルを最適化しようとしましたが、問題は解決されず、むしろ複雑さが増し、未解決の問題が増えただけでした。プラットフォームを並列処理で<strong>再構築</strong>するという大胆な決定には、多大な事前開発が必要でしたが、最終的には持続可能なプラットフォームの成長への道を切り開き、日々の面倒な管理業務を事実上排除しました。</li><li><strong>ITと依存関係：</strong>プラットフォームは単独では動作できません。その成功は、より広範なエコシステムとどれだけうまく統合できるかによって決まります。当社の場合、シームレスなサービスオンボーディングのための信頼できる情報源となるため、<strong>Backstage</strong>との統合は極めて重要でした。同様に、<strong>Artifactory</strong>に接続することで、プライベートパッケージの更新を効率的に管理できるようになります。他にも重要な統合は多々あります。</li><li><strong>進歩、シンプルな完璧さ：</strong>実装全体を通じて、当初の想定を継続的にプレッシャーテストし、新たな障壁が出現するたびにそれに適応しました。完璧主義に陥るのではなく、<strong>反復的なアプローチ</strong>を採用し、課題に一つずつ取り組み、実際の状況に合わせて移行戦略を調整しました。</li></ol><h2><strong>次のステップ</strong></h2><p>このプラットフォームの提供により、プラットフォームのUXと効率性の向上に役立つ、より有意義な作業が可能になります。いくつかの例を以下に示します。<br/></p><ul><li><strong>自動マージの採用を増やし、ガードレールを設定</strong></li></ul><p>自動マージ機能により、面倒な手動タスクが排除され、チームの速度が大幅に向上します。しかし、この速度の向上が安全性を犠牲にすることのないよう、厳格な<strong>ガードレールを</strong>確実に設置する必要があります。<br/></p><ul><li><strong>エンドユーザーエクスペリエンスに関するオブザーバビリティの向上</strong></li></ul><p>私たちのロードマップにおける重要な優先事項は、プラットフォームレベルだけでなく、特に<strong>エンドユーザー</strong>の視点からオブザーバビリティを高めることです。インフラの指標を捉えるのは簡単ですが、実際のユーザー体験を理解するにはより深い洞察が必要です。コアユーザー中心の重要業績評価指標（KPI）を定義し、テレメトリが<strong>エスカレート</strong>する前の摩擦点やパフォーマンスの問題を検出できるように取り組んでいます。</p><ul><li><strong>より広範な採用を促進するための障壁の除去</strong></li></ul><p>将来を見据えた当社の優先事項は、プラットフォームの採用を妨げている障壁を特定して取り除くことです。新しい統合の開発や特定の機能セットの展開が必要な場合でも、当社はデータ主導の計画に取り組んでいます。当社は拡張性を重視したプラットフォームの構築に成功しました。今後は、<strong>その潜在能力を最大限に引き出す</strong>ことに注力していきます。<br/></p><h2><strong>全体像</strong></h2><p>依存関係管理ワークフロープロジェクトは、より広範な原則を示しています。つまり、<strong>デフォルトの導入モデルを超えてオープンソースツールをスケールする必要がある場合、Kubernetesネイティブパターンが前進する道を提供する</strong>ということです。</p><p>以下を取り入れることで、</p><ul><li>構成用のCRD</li><li>ライフサイクル管理のためのオペレーター</li><li>応答性を高めるイベント駆動型アーキテクチャ</li><li>導入用のGitOps</li></ul><p>管理するリポジトリの数に関係なく拡張できるオーケストレーションを構築しました。1つのリポジトリをスキャンするパフォーマンスは、管理するリポジトリが100個でも1,000個でも同じです。</p><p>重要なCVEが発表されても、数時間ではなく数分で回答が得られます。それがボトルネックと競争優位の違いです。</p><h2><strong>謝辞</strong></h2><p>このプラットフォームは優れたオープンソースツールを基盤としています。</p><ul><li><strong>Kubebuilder：</strong>ワークフローをブートストラップして管理するKubernetes Operatorを起動するために使用したオープンソースフレームワーク。[<a href="https://github.com/kubernetes-sigs/kubebuilder">1</a>][<a href="https://book.kubebuilder.io/">2</a>]</li><li><strong>Backstage：</strong>Service Catalogを構築し、信頼できる情報源として使用するオープンソースフレームワーク。[<a href="https://github.com/backstage/backstage">1</a>][<a href="https://backstage.io/">2</a>]</li><li><strong>Argo WorkflowsとArgo Events：</strong>複雑なプロセスを調整し、イベントに基づいて動的な処理を追加するために使用したオープンソーススイート。[<span class="unknown__pt__mark__1e5711660421">1</span>][<a href="https://argo-workflows.readthedocs.io/en/stable/">2</a>][<a href="https://argoproj.github.io/argo-events/">3</a>][<a href="https://github.com/argoproj/argo-events">4</a>]</li><li><strong>Renovate CLI：</strong>リポジトリを処理するオープンソースの依存関係管理ツール。[<a href="https://github.com/renovatebot/renovate">1</a>][<a href="https://docs.renovatebot.com/getting-started/running/">2</a>]</li></ul><p>* AWS Fargateの料金モデルを単一ポッドのコストの基準として使用しました。ただし、当社のワークロードは必ずしもAWSで実行されているわけではなく、本格的なKubernetesクラスターで実行されています。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[「best_compression」で検索パフォーマンスを向上]]></title>
            <description><![CDATA[「best_compression」は通常、Elastic ObservabilityおよびElastic Securityのユースケースにおけるストレージ節約機能として認識されていますが、このブログでは、検索のパフォーマンスチューニング手段としての有効性について説明します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/improve-elasticsearch-performance-best-compression</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/improve-elasticsearch-performance-best-compression</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Sherry Ger, Ryan Eno]]></dc:creator>
            <pubDate>Fri, 23 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5141a8c2618337207d848ce16b258a86885955b2-1600x1034.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p></p><p>同時実行性の高いワークロード向けにElasticsearchをチューニングする場合の標準的なアプローチは、RAMを最大化してドキュメントのワーキングセットをメモリに保持し、検索レイテンシを低くすることです。結果として、<a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules"><code>best_compression</code></a>は検索ワークロードにはほとんど考慮されません。これは主に、ストレージ効率が優先されるElastic ObservabilityおよびElastic Securityのユースケースでのストレージ節約対策として見なされているためです。</p><p>このブログでは、データセットのサイズがOSページキャッシュを大幅に超える場合、<code>best_compression</code>がI/Oボトルネックを軽減することで検索パフォーマンスとリソース効率を向上させることを示します。</p><h2><strong>セットアップ</strong></h2><p>ユースケースは、<a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/ec-change-hardware-profile#ec-profiles-compute-optimized-arm">Elastic CloudのCPUに最適化されたインスタンス</a>上で実行される高同時性検索アプリケーションです。</p><ul><li>データ量：ドキュメント約5億件</li><li>インフラ：6つのElastic Cloud（Elasticsearch Service）インスタンス（各インスタンス：1.76 TBのストレージ | 60 GB RAM | 31.9 vCPU）</li><li>メモリとストレージの比率：総データセットの約5％がRAMに収まる</li></ul><h2><strong>課題：高いレイテンシ</strong></h2><p>19:00頃に現在のリクエスト数が急増すると、検索のレイテンシが大幅に悪化することが確認されました。図1と図2に示すように、Elasticsearchインスタンスあたりのトラフィックはピーク時で毎分400リクエスト程度でしたが、平均クエリサービス時間は60ミリ秒以上に低下しました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f9c6cc1882e7db24336c65c54bbc1d38dcdb7fa3-697x311.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bbb705ae2fcd14c81d335bf322346caf3bf33765-996x618.png" style="width: 100%;" /><p>初期の接続処理後、CPU使用率は比較的低いままであり、コンピューティングがボトルネックではなかったことを示しています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cec15a28d2d22e9adedd2951bb2334b3717890a1-1494x730.png" style="width: 100%;" /><p>クエリボリュームとページフォールトの間の強い相関関係が明らかになりました。リクエストが増加するにつれて、ページフォールトも比例して増加し、ピークは約40万件/分に達しました。これは、アクティブなデータセットがページキャッシュに収まらなかったことを示しています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/511f2f10300a9d10ba3d7a82b9a8c8d567ac5636-1492x678.png" style="width: 100%;" /><p>同時に、JVMヒープの使用量は正常かつ健全であるように見えました。これにより、ガベージコレクションの問題が排除され、ボトルネックが I/O であることが確認されました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/336bbad638f866304358dba1d06ee987de0f23cf-1490x568.png" style="width: 100%;" /><h2><strong>診断：I/Oバウンド</strong></h2><p>システムはI/Oバウンド状態でした。<a href="https://www.elastic.co/blog/elasticsearch-caching-deep-dive-boosting-query-speed-one-cache-at-a-time">Elasticsearchは、メモリからインデックスデータを提供するためにOSページキャッシュに依存します</a>。インデックスがキャッシュに対して大きすぎる場合、クエリによってコストのかかるディスク読み取りがトリガーされます。一般的な解決策は水平方向に拡張すること（ノード/RAMの追加）ですが、まずは既存のリソースの効率改善を最大限に図りたいと考えました。</p><h2><strong>修正</strong></h2><p>デフォルトでは、Elasticsearchはインデックスセグメントに<a href="https://en.wikipedia.org/wiki/LZ4_(compression_algorithm)">LZ4</a>圧縮を使用し、速度とサイズのバランスをとります。<code>best_compression</code> （<a href="https://en.wikipedia.org/wiki/Zstd">zstd</a>を使用）に切り替えるとインデックスのサイズが小さくなるという仮説を立てました。フットプリントが小さいほど、ページキャッシュに収まるインデックスの割合が大きくなり、CPUのわずかな増加（解凍用）と引き換えにディスクI/Oが削減されます。</p><p><code>best_compression</code>を有効にするために、インデックス設定<code>index.codec: best_compression</code>でデータを再インデックスしました。あるいは、インデックスを閉じ、インデックスコーデックを<code>best_compression</code>にリセットしてから、セグメントのマージを実行することで、同じ結果を得ることができます。</p><pre><code>POST my-index/_close
PUT my-index/_settings
{
    "codec": "best_compression"
}
  
POST my-index/_open  
POST my-index/_forcemerge?max_num_segments=1</code></pre><h2><strong>結果</strong></h2><p>結果は、ストレージ効率の向上は、CPU使用率の増加を伴わずに検索パフォーマンスの大幅な向上に直接つながるという私たちの仮説を裏付けました。</p><p><code>best_compression</code>を適用するとインデックスの大きさは約25％減少しました。反復ログデータで確認された削減よりは少ないものの、この25%の削減により、ページキャッシュ容量が同じだけ実質的に増加しました。</p><p>次のロードテスト（17:00から）では、トラフィックはさらに増加し、Elasticsearchノードあたり1分あたり500リクエストでピークに達しました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fc1902f396cb2115c0013155ad07f6eb87389c60-660x309.png" style="width: 100%;" /><p>負荷が高まったにもかかわらず、CPU使用率は前回の実行時よりも低くなりました。前のテストで使用率が高かったのは、過剰なページフォールト処理とディスクI/O管理のオーバーヘッドが原因である可能性があります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/15699ef4c65b3f0a9f8a3e1bae8bb18f7b647025-819x352.png" style="width: 100%;" /><p>重要なのは、ページフォールトが大幅に減少したことです。ベースラインテストの30万件超に比較して、より高いスループットでもフォールトは1 分あたり20万件未満に留まりました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f76ca967976d740af88a9359b66041701abb46fc-764x340.png" style="width: 100%;" /><p>ページフォールトの結果はまだ最適とは言えませんでしたが、クエリサービス時間は約50％削減され、負荷が高まった場合でも30ミリ秒未満に留まりました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/750ec1c59b8eb5069aed4c066d856ecea82d5bca-620x311.png" style="width: 100%;" /><p></p><h2><strong>結論：検索にはbest_compressionを</strong></h2><p>データ量が利用可能な物理メモリを超える検索ユースケースでは、 <code>best_compression</code>は強力なパフォーマンス調整手段となります。</p><p>キャッシュミスに対する従来の解決策はスケールアウトしてRAMを増やすことですが、インデックスのフットプリントを削減することで、ページ キャッシュ内のドキュメント数を最大化するという同じ目標を達成しました。次のステップは、<a href="https://www.elastic.co/blog/space-savings-a-lesser-known-benefit-of-index-sorting-in-elasticsearch"><strong>インデックスの並べ替え</strong></a>を探求し、ストレージをさらに最適化し、既存のリソースからさらにパフォーマンスを引き出すことです。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Agent Builderが一般提供開始：コンテキスト駆動型エージェントを数分で出荷]]></title>
            <description><![CDATA[Agent Builderが一般提供となりました。コンテキスト駆動型AIエージェントを迅速に開発する方法を学びましょう。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/agent-builder-elastic-ga</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/agent-builder-elastic-ga</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Anish Mathur, Evan Castle]]></dc:creator>
            <pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6840eb7dbb884055ab0e965dcfd614fec54936af-2210x1440.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elastic Cloud Serverlessおよび近日公開予定の9.3リリースで、Agent Builderが一般公開されることを発表できることを嬉しく思います。Agent Builderは、コンテキストエンジニアリングプラットフォームとしてElasticsearchの機能を活用し、コンテキストに基づいたデータ重視のAIエージェントを迅速に開発します。</p><p>エージェントは、効率性の向上と顧客体験の向上をもたらす可能性で注目を集めています。しかし実際には、乱雑で構造化されていないエンタープライズデータを扱う場合など、エージェントに適切なコンテキストを提供することは困難です。開発者は、ツール、プロンプト、状態、推論ロジック、モデルを管理し、ビジネスソースから関連するコンテキストを取得して正確な結果とアクションを提供する必要があります。Elastic Agent Builderは、これらのコアコンポーネントを提供して、安全で信頼性の高い、コンテキスト駆動型のエージェントを開発します。</p><h2>Agent Builderのコア機能</h2><p>Agent Builderは、検索の関連性とRetrieval-Augmented GenerationへのElasticの長期投資を活用し、Elasticsearchをコンテキストに応じたデータ重視のAIエージェントの開発を簡素化する最高のベクトルデータベースにすることを目指しています。</p><p>Agent Builderを使用すると、次のことが可能になります。</p><ul><li>質問に答え、分析を実行し、Elasticsearch内のあらゆるデータに関する調査を推進できる組み込みの会話エージェントをすぐに使い始めることができます。</li><li>複雑な非構造化データから、設定ベースの開発エクスペリエンスを用いてカスタムエージェントへ迅速に移行します。</li><li>組み込みのES|QLまたはカスタムツールを通じてクラス最高のハイブリッド検索関連性を活用し、コンテキストの品質とエージェントの信頼性を向上させます。</li><li>複雑なワークフロー（プレビュー）を再利用可能なツールとして実行し、データを充実させ、レコードを更新し、メッセージを送信するなど、ルールベースの自動化を実現します。</li><li>ワークフローとMCPを使用してElasticsearch外のデータソースに接続し、エージェントのコンテキストを関連付けたり組み合わせたりします。</li><li>搭載のまたはカスタムツールをMCP経由で公開して、任意のエージェントまたはアプリケーションフレームワークと統合し、外部MCPに接続する機能（プレビュー）、A2Aのサポート、完全なAPIサポートを提供します。</li><li>LlamaIndexを使用した複雑な文書処理や、Arcade.devを使用した安全で構造化されたツールアクセスなどのサードパーティソリューションと統合して、Agent Builderの機能を拡張します。</li></ul><p>Agent Builderの機能をさらに拡張するために、新しいルールベースの自動化機能であるElastic ワークフローを導入します。現在はテクニカルプレビュー段階です。組織のタスクでは、エージェントはルールベースのアクションの確実性と信頼性を必要とする場合があり、これは特定のビジネスロジックを実装するために不可欠となることがよくあります。Elastic Workflowsは、内部システムと外部システムを管理してアクションを実行し、データやコンテキストを収集して変換するためのシンプルで宣言的な方法をエージェントに提供します。ワークフローは完全にコンポーザブルで、イベント主導型かつ柔軟性があり、MCPを介してエージェントにツールとして公開できます。</p><h2>わずか数分でデータからエージェントへ</h2><p>エージェントの開発には、別々のデータストアを統合し、手動のパイプラインを構築し、クエリを調整し、複雑なオーケストレーションを管理するために、数週間の事前作業を要する場合があります。Agent Builderは、データストア、ベクトルデータベース、RAGパイプライン、検索レイヤー、クエリトランスレータ、ツールオーケストレータの必要性を排除することで、エージェントの開発時間を短縮し、エージェントのロジックとアプリケーションの提供に集中できるようにします。</p><p>Agent BuilderはElasticsearchプラットフォームのプリミティブをネイティブに統合して、エージェントの開発を迅速にします。</p><ul><li>インデックス付けされたデータとすぐにチャットして推論できる組み込みの会話エージェントから始めましょう。</li><li>Kibana、API、またはMCPやA2Aを介したインタラクティブなアクセスにより、エージェントをアプリケーション、ダッシュボード、CI/CDシステムに統合します。</li><li>デフォルトのツールを使用してデータ構造を理解し、適切なインデックスを選択し、最適化されたハイブリッド、セマンティック、構造化クエリを生成し、自然言語プロンプトに基づいてES|QLを使用した設定可能な可視化を作成します。</li></ul><p>さらに詳しく知りたい場合は、完全な<a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">ハンズオンウォークスルー</a>をお試しください。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b55b63eae40f72952967cc8f3ea4df4cd62d7d70-1080x608.gif" style="width: 100%;" /><h2>コンテキストエンジニアリングのための完全なデータプラットフォームであるElasticsearch上に構築</h2><p>AIエージェントにとって、コンテキストの品質は効果的な推論を提供し、ハルシネーションのリスクを軽減するために不可欠です。多くの企業のAIエージェントにとって、タスクを実行するために必要なビジネスデータは、最も重要なコンテキストです。拡張性に優れたデータ格納、ベクトルデータベース、そして関連性におけるリーダーとして、Elasticsearchはすでに多くの強力なコンテキストエンジニアリングプリミティブを提供しています。コンテキストエンジニアリングは、単なるRetrieval-Augmented Generationを超えて、データの取得、ランキング、フィルタリング、エージェントへの提示方法をカスタマイズ・スケールできるようにすることで、ノイズと曖昧さを減らすのに役立ちます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/419b9b6f13739e0a8983249d8ac31478e73dac89-1600x901.png" style="width: 100%;" /><p>Elasticsearchは、レキシカル検索、ベクトル検索、構造化フィルタリングを組み合わせたコンテキストエンジンを提供し、モデルが関連性のある正確なコンテキスト上で動作することを確実にすることで、<a href="https://www.elastic.co/search-labs/blog/context-engineering-relevance-ai-agents-elasticsearch">LLMのパフォーマンスを大幅に向上</a>させます。この機能は、エージェント検索、組み込みツール、適切なインデックスを自動的に選択し、自然言語をコンテキストに最適化されたクエリに変換する検索ロジックによってサポートされています。</p><p>Agent Builderでは、関連性とランキングを制御して、エージェントが最も役立つコンテキストを最初に受け取るようにして、スコアリング、ランキング、フィルタリングロジックを微調整できます。Elasticsearchを使用すると、不透明な検索動作に頼るのではなく、重要なこと、重要な理由、優先順位付け方法を制御できます。これらはすべて、テキスト、ベクトル、メタデータ、ログなどすべてのデータを1つのプラットフォームに保存・拡張できるスケーラブルなデータプラットフォームであるElasticsearchによって支えられており、エージェントのコンテキスト管理が容易になります。</p><h2>複雑なワークフローを再利用可能なツールとして実行</h2><p>AIエージェントは複雑なタスクの推論を可能にしますが、多くの自動化は、特定のビジネスロジックを強制するルールベースのアクションの確実な実行に依存しています。Elastic Workflowsは、内部および外部のシステムをオーケストレーションし、アクションを実行し、コンテキストやデータを収集し、エージェントの一部として統合するための、シンプルで宣言的な方法を提供します。YAMLで定義されているワークフローは完全にコンポーザブルで、ジョブに応じて単純にしたり複雑にしたりできます。これにより、エージェントはElasticsearchプラットフォームやソリューション、そしてサードパーティのアプリケーションに対して効率的にアクションを起こすことができます。</p><p>ワークフローをAgent Builderと統合するには、3つの手順を実行します（前提条件：<a href="https://github.com/elastic/workflows">ここに</a>記載されている詳細を使用してワークフローを有効にします）</p><p>1. シンプルなYAMLベースのエディターを使用して、組み込みの自動入力とテスト機能付きで新しいワークフローを作成して保存します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/308888bf3d2fa013f9391a55be6a6fbd458b6dac-1600x998.png" style="width: 100%;" /><p>2. Agent Builderでタイプ「ワークフロー」の新しいツールを作成し、エージェントがワークフローツールをいつ使用するかを判断できるように説明を入力します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c04810d30d226112c3610bd58e208607b213fc3d-1600x945.png" style="width: 100%;" /><p>3. ワークフローツールをカスタムエージェントに追加します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/724cd4ac93c46efb0d339fd140e5caf138f8150f-1600x948.png" style="width: 100%;" /><p>4. 以上です！エージェントが会話内からワークフローを呼び出せるようになりました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8dfdd726ab89e31c48b79372650ce33946713dca-1600x929.png" style="width: 100%;" /><h2>ニーズに合わせてエージェントを構築</h2><p>Agent Builderは単一の開発パラダイムに限定されず、データ、関連性、モデル、相互運用性、セキュリティ、エージェント設計を完全に制御し、エージェントに対してオープンで柔軟な開発アプローチを可能にするように設計されています。</p><p>カスタムエージェント定義を使用すると、エージェントがアクセスできるツールを正確に選択したり、カスタムシステムプロンプトを埋め込んだり、エージェントの指示を調整したり、セキュリティ境界を定義したりできます。エージェントはモデルに依存しないため、単一のプロバイダーに縛られることなく、ネイティブとより広範なエコシステムの両方で、好みのLLMを柔軟に構成できます。</p><p>拡張可能なツールを構築し、ドメイン固有のロジック（例：特定のインデックスフィルター、ES|QL結合、分析パイプライン）をカプセル化し、それらを本番環境での安全な使用に制約します。APIの完全サポートで、モデルコンテキストプロトコル（MCP）のネイティブサポートにより、他のエージェントフレームワークとの相互運用が可能になります。A2A統合とは、Elastic Agentを他のフレームワークやサービス、クライアントアプリに公開し、同じデータやコンテキストエンジニアリングロジックを統合間で再利用できることを意味します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5e903ba24ffb3f40231e901f63bd494c89cb7757-1600x1004.png" style="width: 100%;" /><p>Agent Builderは、柔軟でオープンな開発をサポートし、一般的なエージェントフレームワークやPlatformと簡単に統合できるように設計されています。これらの統合は効果的なエージェントを提供するために不可欠です。<strong>Arcade.devの共同創設者であるSam Partee氏は</strong>次のように述べています。</p><p><em>「今日のエージェントシステムが機能しないのは、AIをツールやデータに接続するのが難しいためです。Arcade.devのElastic Agent Builderは、エージェントがコンテキストを取得し、推論し、行動する方法を扱うための構造化されたセキュアな方法を開発者に提供します。」</em></p><p>Agent Builderは、複雑なデータを処理するためにElasticsearchの拡張性も活用します。<strong>LlamaIndexのCEOであるJerry Liu氏</strong>は次のように述べています。</p><p><em>「非構造化データソースから企業のコンテキストを解き放つことが、効果的なエージェントを構築する鍵となります。Elastic Agent BuilderとLlamaIndexの複雑なドキュメント処理を組み合わせることで、重要なコンテキストレイヤーが強化され、チームがデータを取得、処理、準備できるようになるため、エージェントはより正確に推論し、より良い結果を提供できるようになります。」</em></p><h2>構築できるもの</h2><p>Agent Builderはすでにさまざまなユースケースで使用されています。以下に、エージェントの使用を開始するためのいくつかの例とリファレンスアーキテクチャを示します。</p><ul><li><strong>インフラストラクチャーの自動化：</strong>サポートシナリオでは、エージェントは読み取り、思考、チャットに使用されてきましたが、これまでは、管理する必要があるインフラストラクチャにアクセスして操作することはできませんでした。Elasticのエンジニアリングチームは、ハッカソンの一環として<a href="https://www.elastic.co/search-labs/blog/agent-builder-augmented-infrastructure">自動インフラ管理</a>エージェントを構築しました。このエージェントはアプリケーションインフラストラクチャの問題を積極的に調査し、自動アクションを実行します。インフラログをインテリジェントに理解し、ワークフローを使用して構成を最適化し、問題に対応し、リソースを拡張します。</li><li><strong>セキュリティ脅威分析：</strong>Elastic Agent Builder、MCP、Elasticsearchを使用してセキュリティ脆弱性エージェントが開発されました。内部のセキュリティデータと外部の脅威インテリジェンスを相関させることにより、脅威分析を自動化します。エージェントは過去のインシデントと設定に対してセマンティック検索を実行し、結果をライブインターネットデータで強化し、LLMの推論を適用して環境の関連性を評価し、リスクを優先順位付けし、実行可能な修復策を生成します。<a href="https://www.elastic.co/search-labs/blog/agent-builder-mcp-reference-architecture-elasticsearch">リファレンスアーキテクチャ</a><strong>を参照してください。</strong></li><li><strong>テクニカルカスタマーサポート：</strong>エージェントは、ケースの要約、問題の重複排除と作成、詳細な技術調査など、複数のサポートタスクを実行できます。Agent Builderを使用すると、多段階のハイブリッド検索が可能になり、最も関連性の高い問題、ソリューション、手順のみを見つけ、根本原因の仮説と改善計画を策定できます。Agent Builderは<a href="https://www.elastic.co/blog/generative-ai-customer-support-elastic-support-assistant">複雑なサポートシステムのアーキテクチャを簡素化し</a>、提供までの時間を短縮できます。</li><li><strong>製品とコンテンツの検出：</strong>Agent Builderは、<a href="https://www.elastic.co/search-labs/blog/build-voice-agents-elastic-agent-builder">会話型エクスペリエンスのための複雑な製品カタログを公開する</a>プロセスを簡素化すると同時に、組織が独自のビジネスロジックと要件を組み込む柔軟性を維持できるようにします。</li><li><strong>自分で構築：</strong>2026年1月22日から2月27日まで開催される<a href="https://elasticsearch.devpost.com/">Agent Builder Hackathon</a>に参加しましょう。コミュニティと協力して、検索、ワークフロー、ツール、推論を組み合わせた、コンテキスト駆動型のマルチステップAIエージェントを構築し、実世界のタスクを自動化できます。*</li></ul><h2>今すぐカスタムエージェントの構築を開始</h2><p>まずは<a href="https://cloud.elastic.co/registration?onboarding_token=search&amp;pg=en-enterprise-search-page">Elastic Cloudトライアル</a>から始めて、<a href="https://www.elastic.co/docs/solutions/search/elastic-agent-builder">こちら</a>のドキュメントをご覧ください。既存のお客様の場合、Agent BuilderはCloud Serverless、Elastic Cloud Hosted、セルフマネージドのエンタープライズティアでご利用いただけます。</p><p>* ハッカソンの利用規約と参加資格の詳細については<a href="https://elasticsearch.devpost.com/rules">こちらをクリックしてください</a>。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elastic Agent Builderで音声エージェントを構築]]></title>
            <description><![CDATA[音声エージェントの仕組みと、Elastic Agent BuilderとLiveKitを使用して音声エージェントを構築する方法について説明します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/build-voice-agents-elastic-agent-builder</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/build-voice-agents-elastic-agent-builder</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Kenneth Kreindler]]></dc:creator>
            <pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/43ceabb9e2c0966261c188bd40e03178d5a91e5c-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>AIはガラスの箱に閉じ込められています。コマンドを入力すると、テキストで応答が返されます。役には立ちますが、画面の向こうで誰かが動いているのを見ているような距離感があります。今年、2026年は、ビジネスがこのガラスの壁を打ち破り、AIエージェントを製品に導入し、真の価値を提供する年となるでしょう。</p><p>ガラスを砕く方法の1つは、人間の音声を認識し、コンピューター生成の音声を合成するAIエージェントである<em>音声エージェント</em>の採用によるものです。低遅延の文字起こし、高速な大規模言語モデル（LLM）、人間のように聞こえるテキスト読み上げモデルの登場により、これが可能になりました。</p><p>音声エージェントが真に価値あるものになるためには、ビジネスデータへのアクセスも必要です。このブログでは、音声エージェントの仕組みを学び、<a href="https://livekit.io/">LiveKit</a>と<a href="https://www.elastic.co/elasticsearch/agent-builder">Elastic Agent Builder</a>を使って、架空のアウトドアスポーツ用品店であるElasticSport向けに音声エージェントを構築します。当社の音声エージェントはコンテキストを認識し、自前のデータを活用します。</p><h2>プログラム概要</h2><p>音声エージェントの世界には2つのパラダイムがあります。1つ目は音声合成（speech-to-speech）モデルを使用し、2つ目は音声テキスト変換、LLM、テキスト読み上げからなる音声パイプラインを使用します。音声合成には独自の利点がありますが、音声パイプラインは使用される技術やコンテキストの管理方法、エージェントの動作に対する制御をより柔軟にカスタマイズできます。ここでは音声パイプラインモデルに焦点を当てます。</p><h3>主要コンポーネント</h3><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b237501903f9c3a71fe1b7755c3990e40c5495c8-1600x653.png" style="width: 100%;" /><h4>文字起こし（音声テキスト変換）</h4><p>文字起こしは音声パイプラインの入口です。文字起こしコンポーネントは、生のオーディオフレームを入力として受け取り、音声をテキストに書き起こして、そのテキストを出力します。文字起こしされたテキストは、システムがユーザーの音声が終了したことを検出するまでバッファリングされます。その時点で、LLM生成が開始されます。さまざまなサードパーティプロバイダーが低遅延の文字起こしを提供しています。選択する際には、遅延と文字起こしの精度を考慮し、ストリーミング文字起こしをサポートしていることを確認してください。</p><p></p><p>サードパーティAPIの例：<a href="https://www.assemblyai.com/">AssemblyAI</a>、<a href="https://deepgram.com/product/speech-to-text">Deepgram</a>、<a href="https://platform.openai.com/docs/guides/realtime-transcription">OpenAI</a>、<a href="https://elevenlabs.io/speech-to-text">ElevenLabs</a></p><h4>ターン検出</h4><p>ターン検出は、話者が話し終わり、生成が開始されるべき時を検出するパイプラインのコンポーネントです。これを行う一般的な方法の一つは、<a href="https://github.com/snakers4/silero-vad">Silero VAD</a>のような音声アクティビティ検出（VAD）モデルを利用することです。VADは、音声のエネルギーレベルを使用して、音声に発話が含まれるタイミングと発話が終了したタイミングを検出します。しかし、VADだけでは、一時停止と発話の終了の違いを識別することはできません。このため、中間トランスクリプトまたは生の音声に基づいて話者が話し終えたかどうかを予測する発話終了モデルと組み合わせられることがよくあります。</p><p>例 (Hugging Face): <a href="https://huggingface.co/livekit/turn-detector">livekit/turn-detector</a>, <a href="https://huggingface.co/pipecat-ai/smart-turn-v3">pipecat-ai/smart-turn-v3</a></p><h4>エージェント</h4><p>エージェントは音声パイプラインのコアです。意図を理解し、適切なコンテキストを収集し、テキスト形式で返信を作成する役割を担います。<a href="https://www.elastic.co/elasticsearch/agent-builder">Elastic Agent Builder</a>は、組み込まれた推論機能、ツールライブラリ、ワークフロー統合により、データ上で動作し、外部サービスとやり取りできるエージェントとなります。</p><h4>LLM（テキストからテキスト）</h4><p>Elastic Agent Builder用のLLMを選択する際には、主に、LLMの推論ベンチマークと、最初のトークンまでの時間（TTFT）の2つの特性を考慮する必要があります。</p><p>推論ベンチマークは、LLMがどの程度正しい応答を生成できるかを示します。検討すべきベンチマークは、MT-BenchやHumanity&#x27;s Last Examデータセットなど、マルチターン会話の遵守とインテリジェンスベンチマークを評価するものです。</p><p>TTFTベンチマークは、モデルが最初の出力トークンを生成する速度を評価します。レイテンシのベンチマークには他にも種類がありますが、TTFTは音声エージェントにとって特に重要です。最初のトークンを受信するとすぐにオーディオ合成を開始できるため、ターン間のレイテンシが短縮され、自然な会話が実現します。</p><p>通常、高速モデルは推論ベンチマークでパフォーマンスが低下することが多いため、これら2つの特性は互いにトレードオフとなります。</p><p>例 (Hugging Face): <a href="https://huggingface.co/openai/gpt-oss-20b">openai/gpt-oss-20b</a>, <a href="https://huggingface.co/openai/gpt-oss-120b">openai/gpt-oss-120b</a></p><h4>合成（テキスト読み上げ）</h4><p>パイプラインの最後の部分はテキスト読み上げモデルです。このコンポーネントは、LLMから出力されたテキストを可聴音声に変換する役割を担います。LLMと同様に、遅延はテキスト読み上げプロバイダーを選択する際に注意すべき特性です。テキスト読み上げの遅延は、最初のバイトまでの時間（TTFB）で測定されます。これは、最初のオーディオバイトが受信されるまでにかかる時間です。TTFBが低いほど、ターンの待ち時間も短縮されます。</p><p>例：<a href="https://elevenlabs.io/text-to-speech-api">ElevenLabs</a>、<a href="https://cartesia.ai/sonic">Cartesia</a>、<a href="https://www.rime.ai/">Rime</a></p><h4>音声パイプラインの構築</h4><p>Elastic Agent Builderは、音声パイプラインに複数のレベルで統合できます。</p><ol><li>Agent Builderツールのみ：音声テキスト変換 → LLM（Agent Builderツール使用）→ テキスト読み上げ</li><li>MCPとしてのAgent Builder：音声テキスト変換 → LLM（MCP 経由でAgent Builderにアクセス）→ テキスト読み上げ</li><li>Agent Builderをコアとして使用：音声テキスト変換 → Agent Builder →テキスト読み上げ</li></ol><p>このプロジェクトでは、コアアプローチとしてAgent Builderを選択しました。このアプローチにより、Agent Builderとワークフローの全機能を使用できます。このプロジェクトでは、LiveKitを使用して音声テキスト変換、ターン検出、テキスト読み上げを管理する、Agent Builderと直接統合するカスタムLLMノードを実装しています。</p><h2>Elasticサポート音声エージェント</h2><p>ElasticSportという架空のスポーツショップ向けにカスタムサポート音声エージェントを構築します。顧客はヘルプラインに電話をかけ、製品の推奨を尋ねたり、製品の詳細を見つけたり、注文状況を確認したり、注文情報をテキストで送信してもらったりできるようになります。これを達成するために、まずカスタムエージェントを設定し、Elasticsearch Query Language（ES|QL）クエリとワークフローを実行するためのツールを作成する必要があります。</p><a href="https://videos.elastic.co/watch/jpX6D21URZuh3PtXVV7Ks6">Watch video on Vidyard</a><h3>エージェントの構成</h3><h4>プロンプト</h4><p>プロンプトは、エージェントがどのような性格を持ち、どのように応答するかを指示します。重要なのは、応答が適切に音声に合成され、誤解が適切に修復されることを保証する音声固有のプロンプトがいくつかあることです。</p><pre><code>You are a Sales Assistant at ElasticSport, an outdoor sport shop specialized in hiking and winter equipment. 

[Profile]
- name: Iva
- company: ElasticSport
- role: Sales Assistant
- language: en-GB
- description: ElasticSport virtual sales assistant

[Context]
- Ask clarifying questions to understand the context.
- Use available tools to answer the user's question.
- Use the knowledge base to retrieve general information

[Style]
- Be informative and comprehensive.
- Maintain a professional, friendly and polite tone.
- Mimic human behavior and speech patterns.
- Be concise. Do not over explain initially

[Response Guideline]
- Present dates in spelled-out month date format (e.g., January fifteenth, two thousand and twenty-four).
- Avoid the use of unpronounceable punctuation such as bullet points, tables, emojis.
- Respond in plain text, avoid any formatting.
- Spell out numbers as words for more natural-sounding speech.
- Respond in short and concise sentences. Responses should be 1 or 2 sentences long.

[ERROR RECOVERY]
### Misunderstanding Protocol
1. Acknowledge potential misunderstanding
2. Request specific clarification</code></pre><h4>ワークフロー</h4><p>TwilioのメッセージングAPIを介してSMSを送信するための小さなワークフローを追加します。ワークフローはカスタムエージェントに対してツールとして公開され、エージェントが通話中に発信者にSMSを送信できるユーザーエクスペリエンスが実現されます。これにより、発信者は、「 <em>X</em>についての詳細をテキストで送信できますか？」などと尋ねることができます。</p><pre><code>name: send sms
enabled: true
triggers:
  - type: manual
inputs:
  - name: message
    type: string
    description: The message to send to the phone number.

  - name: phone_number
    type: string
    description: The phone number to send the message to.

consts:
  TWILIO_ACCOUNT: "****"
  BASIC_AUTH: "****"
  FROM_PHONE_NNUMBER: "****"
steps:
  - name: http_step
    type: http
    with:
      url: https://api.twilio.com/2010-04-01/Accounts/{{consts.TWILIO_ACCOUNT}}/Messages.json
      method: POST
      headers:
        Content-Type: application/x-www-form-urlencoded
        Authorization: Basic {{consts.BASIC_AUTH | base64_encode}}
      body: From={{consts.FROM_PHONE_NNUMBER}}&To={{inputs.phone_number}}&Body={{inputs.message}}
      timeout: 30s</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b057e71b0a7c50eb3da47cd4f95e77ec7b4c6126-1600x1245.png" style="width: 100%;" /><h4>ES|QLツール</h4><p>次のツールを使用すると、エージェントは実際のデータに基づいた適切な応答を提供できます。サンプルリポジトリには、製品、注文、ナレッジベースのデータセットでKibanaを初期化するためのセットアップスクリプトが含まれています。</p><ul><li><strong>Product.search</strong></li></ul><p>製品データセットには65種類の架空の製品が含まれています。これはサンプルドキュメントです。</p><pre><code>{
      "sku": "ort3M7k",
      "name": "Ortovox Free Rider 26 Backpack",
      "price": 189,
      "currency": "USD",
      "image": "https://via.placeholder.com/150",
      "description": "The Ortovox Free Rider 26 is a technical freeride backpack with a dedicated safety compartment and diagonal ski carry system. Perfect for backcountry missions.\n\nKey Features:\n- 26L capacity\n- Diagonal ski carry system\n- Safety equipment compartment\n- Helmet holder\n- Hydration system compatible",
      "category": "Accessories",
      "subCategory": "Backpacks",
      "brand": "Ortovox",
      "sizes": ["One Size"],
      "colors": ["Black", "Blue", "Orange"],
      "materials": ["Nylon", "Polyester"]
    }</code></pre><p>名前と説明のフィールドは <code>semantic_text</code>としてマップされており、LLMがES|QLを介してセマンティック検索を実行して関連製品を取得できるようになっています。ハイブリッド検索クエリは、両方のフィールド間でセマンティックマッチングを実行し、ブーストを使用して名前フィールドの一致によりわずかに高い重みを適用します。</p><p>クエリはまず、初期関連性スコアでランク付けされた上位20件の結果を取得します。これらの結果は、<code>.rerank-v1-elasticsearch</code> 推論モデルを使用して説明フィールドに基づいて再ランク付けされ、最後に最も関連性の高い上位 5 つの製品に絞り込まれます。</p><pre><code>type: ES|QL
toolId: products.search
description: Use this tool to search through the product catalogue by keywords.
query: |
    FROM products
        METADATA _score
      | WHERE
          MATCH(name, ?query, {"boost": 0.6}) OR
            MATCH(description, ?query, {"boost": 0.4})
      | SORT _score DESC
      | LIMIT 20
      | RERANK ?query
            ON description
            WITH {"inference_id": ".rerank-v1-elasticsearch"}
      | LIMIT 5

parameters:
    query: space separated keywords to search for in catalogue</code></pre><ul><li><strong>Knowledgebase.search</strong></li></ul><p>ナレッジベースのデータセットには、以下の形式のドキュメントが含まれており、タイトルとコンテンツのフィールドはセマンティックテキストとして格納されます。</p><pre><code>{
        id: "8273645",
        createdAt: "2025-11-14",
        title: "International Orders",
        content: `International orders are processed through our international shipping partner. Below are the countries we ship to and average delivery times.
        Germany: 3-5 working days
        France: 3-5 working days
        Italy: 3-5 working days
        Spain: 3-5 working days
        United Kingdom: 3-5 working days
        United States: 3-5 working days
        Canada: 3-5 working days
        Australia: 3-5 working days
        New Zealand: 3-5 working days
        `
}</code></pre><p>このツールは、 <code>product.search</code>ツールと同様のクエリを使用します。</p><pre><code>type: "ES|QL"
toolId: knowledgebase.search
description: Use this tool to search the knowledgebase.
query: |
  FROM knowledge_base
    METADATA _score
  | WHERE
      MATCH(title, ?query, {"boost": 0.6}) OR
      MATCH(content, ?query, {"boost": 0.4})
  | SORT _score DESC
  | LIMIT 20
  | RERANK ?query
      ON content
      WITH {"inference_id": ".rerank-v1-elasticsearch"}
  | LIMIT 5

parameters:
  query: space separated keywords or natural language phrase to semantically search for in the knowledge base</code></pre><ul><li><strong>Orders.search</strong></li></ul><p>最後に追加するツールは、 <code>order_id</code>による注文を取得するために使用されるツールです。</p><pre><code>type: "ES|QL"
toolId: order.search
description: Use this tool to retrieve an order by its ID.
query: |
  FROM orders
    METADATA _score
  | WHERE order_id == ?order_id
  | SORT _score DESC
  | LIMIT 1

parameters:
  order_id: "the ID of the order"</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d22bdd540a95b5a9c2bd5f308620835e8e6f7ecb-1600x1361.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d91709a50fb5391876b714885242d998b2b21027-1600x1443.png" style="width: 100%;" /><p>エージェントを設定し、これらのワークフローとES|QLツールをエージェントにアタッチした後、エージェントはKibana内でテストできます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8691f41624247a6b1352d158c970031e1426ce5e-1600x1056.png" style="width: 100%;" /><p>ElasticSportサポートエージェントの構築以外にも、エージェント、ワークフロー、ツールを、リード資格を審査する営業エージェント、住宅修理のサービスエージェント、レストランの予約、予約スケジュールエージェントなど、他のユースケースに合わせてカスタマイズできます。</p><p></p><p>最後の部分は、先ほど作成したエージェントをLiveKit、テキスト読み上げ、音声テキスト変換モデルと連携することです。このブログの最後にリンクされているリポジトリには、LiveKitで使用できるカスタムElastic Agent Builder LLMノードが含まれています。<code>AGENT_ID</code>を独自のものに置き換えて、Kibanaインスタンスにリンクするだけです。</p><h2>はじめに</h2><p><a href="https://github.com/KDKHD/elastic_agent_builder_livekit">ここで</a>コードをチェックして試してみてください。 </p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[チャットボックスを超えたエージェントビルダー：Augmented Infrastructureの導入]]></title>
            <description><![CDATA[拡張運用、拡張開発、拡張合成を可能にするAIエージェントである、Elastic Agent Builder with Augmented Infrastructureについてご紹介します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/agent-builder-augmented-infrastructure</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/agent-builder-augmented-infrastructure</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Alexander Wert, Bill Easton, Gil Raphaelli, Graham Hudgins, Norrie Taylor]]></dc:creator>
            <pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e08daf78f328e826f39d06329f6a5487f75d178d-1272x700.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><strong>これは机上の空論ではなく、私たちはすでに行動しています。</strong></p><p>私たちは皆、AIエージェントの台頭を見てきました。テキストを要約したり、コードスニペットを書いたり、ドキュメントに基づいて質問に答えたりするのが得意です。しかし、DevOpsやサイト信頼性エンジニアリング（SRE）に携わる者にとっては、もどかしい制限がありました。ほとんどのエージェントは、コールセンターのパラダイムに囚われています。つまり、読んだり、考えたり、チャットしたりすることはできても、手を伸ばして本来管理すべきインフラに触れることはできないのです。</p><p>最新のハッカソンプロジェクトでは、その制限を打ち破ることを目指しました。</p><p>私たちは、インフラのコパイロットである<strong>Augmented Infrastructure</strong>を開発しました。これは、アドバイスを提供するだけでなく、稼働中の環境の構築、デプロイ、監視、および修正も行います。</p><h2><strong>問題点：コピー、再フォーマット、貼り付け</strong></h2><p>標準的なエージェントは、密閉空間で活動しています。アプリがダウンして会社に500万ドルの損害をもたらした場合、標準的なエージェントは、修正方法についての手順書を読み上げることができます。しかし、その作業を行うのが<em>あなた</em>であることは変わりません。コードをコピーして、自分の環境に合わせてフォーマットし直し、ターミナルに貼り付ける作業が残っています。</p><p>私たちは、Kubernetesについて<em>話すこと</em>と、Kubernetesを<em>設定すること</em>の違いを理解するエージェントが欲しいと考えていました。</p><h2><strong>エンジン：Elastic Agent Builderとは？</strong></h2><p>これを構築するにあたって、私たちはゼロから始めたわけではありません。<a href="https://www.elastic.co/elasticsearch/agent-builder"><strong>Elastic Agent Builder</strong></a>を基盤として構築しました。Elastic Agent Builderをご存知ない方のために説明すると、これはエージェントを迅速に開発するために設計されたフレームワークであり、大規模言語モデル（LLM）（今回のデモではGoogle Geminiを使用）とElasticsearchに保存されているプライベートデータとの間の橋渡し役を果たします。</p><p>Agent Builderは、ドキュメントやログなどの内部データを基盤として、会話型AIに活用できます。しかし、最も強力な機能は<strong>ツール</strong>を割り当てる機能です。これらのツールにより、LLMはチャットインターフェースに留まらず、特定のタスクを実行できます。この機能を可能な限り活用すれば、Agent Builderを自動化の強力なツールに変えることができることに気付きました。</p><h2><strong>成功のために：初期バージョンの構築</strong></h2><p>プロジェクトを開始した当初から、エージェントが外の世界を変えられるようにしたいと考えていました。私たちはあるアイデアを思いつきました。エージェントがホスト上で考えられる任意のコマンドを実行する「ランナー」ソフトウェアを構築したらどうなるでしょうか？そして、ランナーであるElastic Agent Builderとユーザーが三者通話をしていたらどうなるでしょうか？</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8dc8317c1301b8eb7b89438529e8d8d17411c95a-1024x559.png" style="width: 100%;" /><p>まず、Augmented Infrastructure RunnersというPythonプロジェクトを構築しました。これは本質的に、Elastic Agent Builderの会話APIを毎秒クエリし、当社が作成した特別な構文があるかどうかを確認する<code>while(true)</code>ループでした。</p><pre><code>{
	"tool_name": "my_tool",
       "tool_arguments": "\{stringified json arguments\}"
}</code></pre><p>次に、新しいツール呼び出し構文を認識させるために、プロンプトを更新しました。ビルは<a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a>のメンテナーです。FastMCPは、PythonでModel Context Protocol（MCP）サーバーを構築する目的において最も人気のあるフレームワークです。彼は、この新しいランナーソフトウェアとFastMCPクライアントを使用して、MCPサーバーをマウントし、ランナーがそのツールを利用できるようにするための作業に着手しました。エージェントがこれを確認するとツール呼び出しを実行し、結果をユーザーが送信したかのように会話に <code>POST</code> で返します。これがきっかけでLLMは結果に反応し、私たちの取り組みが加速したのです。</p><p>これは素晴らしい考えでしたが、主に2つの問題がありました。</p><ol><li>エージェントは、このJSONすべてをユーザーとの会話に直接吐き出します。</li><li>メッセージが会話APIを通じて表示される最も早い時点は、会話ラウンドが完了したとき（つまり、LLMが応答したとき）でした。</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7b3755aeae17722ff1bb9677712293e9195f96a0-1058x1034.png" style="width: 100%;" /><p>そこで、これをバックグラウンドに移動させる方法を模索することにしました。</p><p>次に、エージェントに <code>call_external_tool</code> というツールを与え、<code>tool_name</code> と文字列化されたJSONツール引数の2つの引数を持たせるように変更しました。この外部ツール呼び出しは何も返しませんが、重要なのは会話APIへの <code>GET</code> リクエストで確認できることです。その後、ランナーにElasticsearchに直接ドキュメントを書き込む許可を与えました。Elastic Agent Builderのエージェントは必要に応じてそれを取得できました。エージェントは常にユーザーのメッセージに対応して動作しているため、結果を検索し、処理を続行するようにエージェントをユーザーのメッセージで起動させる必要があります。そこで、会話を再開するために、エージェントにチャットに短いメッセージを挿入させました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/61ff59a57c68ed5fad492d19c0580644113a507d-1600x1321.png" style="width: 100%;" /><p>これで、外部ツールの呼び出しが行われました。しかし、前述の2つ目の問題のため、最後のキックスタート部分を削除せざるを得ませんでした。それをしなければ、外部ツールを呼び出すたびに、結果を取得するためにもう一度会話をすべてやり直す必要があったためです。</p><h2><strong>優れたものにするために：ワークフローの導入</strong></h2><p>Elasticsearch Query Language（ES|QL）とインデックス検索ツールの呼び出しに加え、Agent BuilderエージェントはElasticのワークフローベースのツールを呼び出すことができます。Elasticのワークフローは、任意のアクションのシーケンスとロジックを実行するための柔軟で管理しやすい方法を提供します。私たちの目的では、ワークフローに必要なのは、Elasticsearchに外部ツールのリクエストを格納することと、結果をポーリングするためのIDを返すことだけです。これにより、以下の簡単なワークフロー定義が得られます。</p><pre><code>name: ai-tool-call
enabled: true
triggers:
  - type: manual
inputs:
  - name: runner_id
    type: string
  - name: tool_calls
    type: string

steps:
  - name: store_request
    type: elasticsearch.create
    with:
      index: distributed-tool-requests
      id: "{{inputs.runner_id}}_{{ execution.id }}"
      document:
        request_id: "{{ execution.id }}"
        runner_id: "{{inputs.runner_id}}"
        tool_call: "{{inputs.tool_calls}}"
        status: "unhandled"

  - name: output_result
    type: console
    with:
      message: "Called tool, with execution id: {{ execution.id }}. Use this ID to poll the results."</code></pre><p>それにより、会話に書き込まれるツール呼び出しリクエストに依存する代わりに、ランナーはElasticsearch <code>distributed-tool-requests</code>インデックスをポーリングして新しい外部ツールリクエストを検索し、結果を指定された<code>execution.id</code>を使用して別のElasticsearchインデックスにレポートすることができます。</p><p>これにより、上記の2つの主な問題が解消されます。</p><ol><li>会話履歴に外部ツール呼び出しのペイロードが散乱することはなくなりました。</li><li>ランナーは会話履歴ではなくElasticsearchインデックスをポーリングしているため、外部ツールのリクエストが表示されるようになるまで、会話のラウンドが完了するのを待つ必要がなく、ブロックされることはありません。</li></ol><p>2つ目の点には、外部ツール呼び出しの処理が（会話ラウンドが完了した後ではなく）エージェントの思考フェーズ内で開始されるという大きな利点があります。これにより、システムプロンプトでLLMに外部ツールの結果が利用可能になるまでポーリングするように指示できレガシ、キックスタートメッセージが不要になります。全体として、これにより会話がより自然に感じられるという良い効果があります。LLMは（ツールリクエストごとに1回の会話ラウンドを必要とするのではなく）1回の会話ラウンドで複数の外部ツールリクエストを処理できるため、より複雑なユーザーリクエストを一度に達成できます。</p><h2><strong>すべてを集約</strong></h2><p>LLMとサーバーラックの間のギャップを埋めるために、Agent Builderのツール機能を使用してある特定のアーキテクチャを開発しました。</p><ol><li><strong>Augmented Infrastructureのランナー：</strong>ターゲット環境（サーバー、Kubernetesクラスター、クラウドアカウント）内に軽量ランナーをデプロイしました。これらのランナーは、各ランナーだけが利用できる安全なエンドポイントとシークレットを使用して、Elasticに直接接続されています。</li><li><strong>ES|QL検索：</strong>コパイロットはElasticの<strong>ES|QL</strong>を使用してハイブリッド検索を行います。単に知識を検索するだけではなく、<em>機能</em>を検索します。接続されたランナーに問い合わせて利用可能なツールを確認します（例：<code>list_ec2_instances</code>、<code>install_helm_chart</code>）。</li><li><strong>ワークフローの実行：</strong>エージェントが行動方針を決定すると、構造化されたワークフローを作成します。</li><li><strong>フィードバックループ：</strong>ランナーはローカルでコマンドを実行し、その結果をElasticsearchにレポートします。コパイロットはインデックスの結果を読み取り、次のステップを決定します。</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/76be256da722c1965971fc506502768bd890f0c4-1290x1076.png" style="width: 100%;" /><h2><strong>デモ：停止からオブザーバビリティへ</strong></h2><a href="https://videos.elastic.co/watch/bRgPJfJLeABjPMMhvy1sbB">Watch video on Vidyard</a><p>動画では、このアーキテクチャの影響力を示す2つの異なるシナリオを紹介しました。</p><h3><strong>シナリオ1：DevOpsの救出</strong></h3><p>私たちは、Kubernetesクラスター内の死角によって引き起こされた500万ドルの障害に関連してパニックに陥ったユーザーから取り組み始めました。</p><ul><li><strong>リクエスト：</strong>「このようなことが二度と起こらないようにするにはどうすればよいでしょうか？」</li><li><strong>アクション：</strong>エージェントは単にチュートリアルを提供するだけではありませんでした。クラスターを識別し、必要な名前空間を作成し、Kubernetesシークレットを生成し、OpenTelemetry Operatorをインストールして、ライブAPMダッシュボードへのリンクを即座に提供しました。</li><li><strong>結果：</strong>ユーザーがYAMLコードを一行も記述することなく、Kubernetesの網羅的なオブザーバビリティとアプリケーションの洞察を実現しました。</li></ul><h3><strong>シナリオ2：セキュリティの引き継ぎ</strong></h3><p>インフラセキュリティの基本的なルールは、見えないものは守れないということです。DevOpsの救出を実行している際、エージェントは環境のセキュリティを向上させる機会を見出します。</p><p>前回のElastic Observability関連の調査から始まったアラートを受けて、セキュリティ担当者が自社のインフラストラクチャーと直接チャットする方法を示します。1つ目はクラウド環境内の資産とリソースを列挙すること、2つ目は環境のセキュリティを確保するために必要なツールをデプロイすることです。</p><ul><li><strong>発見：</strong>コパイロットはセキュリティ担当者のためにAWSリソースを列挙し、重要なギャップを特定しました。すなわち、Amazon Elastic Compute Cloud（EC2）インスタンスと、パブリックエンドポイントにエンドポイント保護がないAmazon Elastic Kubernetes Service（EKS）クラスターです。</li><li><strong>対策：</strong>簡単な承認手続きで、コパイロットは脆弱な資産に対して<strong>Elastic Security</strong>の<strong>拡張検出および対応（XDR）とクラウド検出と対応（CDR）</strong>を展開し、環境をリアルタイムで保護しました。</li><li><strong>結果：</strong>デプロイされたAWS資産とリソースを完全なランタイムセキュリティで保護します。</li></ul><h2><strong>未来：あらゆるものが拡張される</strong></h2><p>このプロジェクトは、Elastic Agent Builderが分散運用の中心的な頭脳になり得ることを証明しています。インフラだけに留まらず、私たちのランナー技術は以下の影響力を発揮します。</p><ul><li><strong>拡張合成：</strong>グローバルランナー全体にわたるTLSエラーの診断。</li><li><strong>拡張開発：</strong>プルリクエストの作成と、フロントエンドサービスへのCAPTCHAの実装。</li><li><strong>拡張オペレーション：</strong>障害時にDNSリゾルバを自動的に再構成。</li></ul><h2><strong>はじめましょう</strong></h2><p>私たちは、AIの未来は単なるチャットサポートだけではなく、<strong>拡張されたインフラストラクチャー</strong>にあると信じています。これは、ユーザーと共にデプロイ、修正、観察、そして保護できるパートナーを持つことです。</p><p>コードをご覧になり、<a href="https://github.com/strawgate/augmented-infrastructure">GitHub</a>の分散ランナーや<a href="https://cloud.elastic.co/">Elastic Cloud Serverless</a>のElastic Agent Builderをぜひ直接お試しください。</p><ul><li>Elastic Cloudでサーバーレスプロジェクトを作成してください。</li><li>コードをランナーにデプロイしてください。</li><li>ランナーをセットアップしてください。</li><li>mcp.jsonを設定してください。</li><li>ランナーを起動すると、エージェントとそのツールが自動的に作成されます。</li><li>分散ランナーで推論、計画、およびアクションを実行できるエージェントとチャットしましょう。</li></ul><p><strong>チーム： </strong><em>アレックス、ビル、ギル、グラハム、ノーリー</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[スイススタイルのハッシュテーブルを使用したより高速なES|QL統計]]></title>
            <description><![CDATA[スイスにインスパイアされたハッシュとSIMDフレンドリーな設計が、Elasticsearch Query Language (ES|QL) で一貫性のある測定可能なスピードアップを実現する方法。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/esql-swiss-hash-stats</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/esql-swiss-hash-stats</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Chris Hegarty, Matthew Alp, Nik Everet]]></dc:creator>
            <pubDate>Mon, 19 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/21036e031070f14faccb2b53b22723de2750c391-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>最近、Elasticsearchのハッシュテーブル実装の重要な部分をスイススタイルの設計に置き換えたところ、均一でカーディナリティの高いワークロードでビルドと反復処理の時間が最大2～3倍高速化されることがわかりました。結果として、Elasticsearch Query Language (ES|QL) の統計と分析操作において、低いレイテンシ、より良いスループット、そしてより予測可能なパフォーマンスが得られます。</p><h2>これが重要である理由</h2><p>ほとんどの典型的な分析ワークフローは最終的にデータのグループ化に集約されます。ホストあたりの平均バイト数の計算、ユーザーごとのイベントのカウント、または次元全体でのメトリクスの集計など、コアとなる操作は同じです。キーをグループにマップし、実行中の集計を更新します。</p><p>小規模であれば、ほぼすべての適切なハッシュテーブルで問題なく動作します。大規模になると（数億のドキュメントと数百万の個別のグループなど）、詳細が重要になってきます。負荷係数、プローブ戦略、メモリレイアウト、キャッシュの動作によって、線形パフォーマンスとキャッシュミスの連続との間に違いが生じる可能性があります。</p><p>Elasticsearchは長年にわたってこれらのワークロードをサポートしてきましたが、コアアルゴリズムを最新化する機会を常に探しています。そのため、スイステーブルからヒントを得た新しいアプローチを評価し、それをES|QLが統計を計算する方法に適用しました。</p><h2>スイステーブルとは？</h2><p>スイステーブルは、GoogleのSwissTableによって普及し、後にAbseilやその他のライブラリに採用された最新のハッシュテーブルファミリーです。</p><p>従来のハッシュテーブルでは、ポインターの追跡やキーのロードに多くの時間を費やし、結局一致しないことが判明します。スイステーブルの特徴は、キーと値とは別に保存される<em>制御バイト</em>と呼ばれる小さなキャッシュ常駐配列構造を使用してほとんどのプローブを拒否し、メモリトラフィックを大幅に削減できることです。</p><p>各制御バイトは単一のスロットを表し、この場合、スロットが空かどうかと、ハッシュから導出された短いフィンガープリントの2つの項目をエンコードします。これらの制御バイトはメモリ上に連続的に配置され、通常16個のグループで構成されており、<a href="https://en.wikipedia.org/wiki/Single_instruction,_multiple_data">単一命令多重データ</a>（SIMD）処理に理想的です。</p><p>スイステーブルは、一度に1つのスロットをプローブする代わりに、ベクトル命令を使用して制御バイトブロック全体をスキャンします。1回の操作で、CPUは入力キーのフィンガープリントを16個のスロットと比較し、空のエントリーを除外します。この高速パスを通過する少数の候補のみが、実際のキーのロードとの比較を必要とします。</p><p>この設計では、少量の追加メタデータと引き換えに、はるかに優れたキャッシュローカリティと大幅に少ないランダムロードを実現しています。テーブルが拡大し、プローブチェーンが長くなるにつれて、それらのプロパティはますます価値が高くなります。</p><h2>中央にSIMDがあります</h2><p>ここでの真の主役はSIMDです。</p><p>制御バイトは単にコンパクトであるだけでなく、ベクトル命令で処理されるように明示的に設計されています。1 回のSIMD比較で16個のフィンガープリントを一度にチェックできるため、通常はループとなる処理が複数の広範な操作に変わります。例：</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bd418778f0c6144f8f5f18419f6220ac0c935c7a-903x407.png" style="width: 100%;" /><p>実際には、これは次のことを意味します。</p><ul><li>ブランチ数の低減。</li><li>プローブチェーンの短縮。</li><li>キーメモリや値メモリからのロードの減少。</li><li>CPU実行ユニットの利用率が大幅に向上。</li></ul><p>ほとんどの検索は制御バイトのスキャンを通過することはありません。そうすれば、残りの作業は焦点が絞られ、予測可能になります。これはまさに、最新のCPUが得意とする種類のワークロードです。</p><h2>SIMDの仕組み</h2><p>仕組みを知りたい読者のために、テーブルに新しいキーを挿入すると何が起こるかを説明します。128ビットベクトルのPanama Vector APIを使用し、16の制御バイトを並列で処理します。</p><p>次のスニペットは、Intel Rocket LakeとAVX-512で生成されたコードを示しています。手順はその環境を反映していますが、設計はAVX-512に依存しません。同じ高レベルのベクトル操作が、同等の命令（AVX2、SSE、NEONなど）を使用して他のプラットフォームでも実行されます。</p><pre><code>; Load 16 control bytes from the control block
vmovdqu xmm0, XMMWORD PTR [r9+r10*1+0x10]

; Broadcast the 7-bit fingerprint of the new key across the vector
vpbroadcastb xmm1, r11d

; Compare all 16 control bytes to the new fingerprint
vpcmpeqb k7, xmm0, xmm1
kmovq rbx, k7

; Check if any matches were found
test rbx, rbx
jne <handle_match></code></pre><p>各命令は挿入プロセスにおいて明確な役割を果たします。</p><ul><li><code>vmovdqu</code>：128ビットの <code>xmm0</code>レジスタに16個の連続制御バイトを読み込みます。</li><li><code>vpbroadcastb</code>：新しいキーの7ビットのフィンガープリントを<code>xmm1</code>レジスタのすべてのレーンにわたって複製します。</li><li><code>vpcmpeqb</code>: 各制御バイトをブロードキャストされたフィンガープリントと比較し、一致する可能性のあるマスクを生成します。</li><li><code>kmovq</code> + <code>test</code>：マスクを汎用レジスタに移動し、一致が存在するかどうかをすばやく確認します。</li></ul><p>最終的に、ベンチマークにより、レジスタの幅を広げて32バイトまたは64バイトに拡張しても測定可能なパフォーマンス上の利点が得られないことが示されたため、一度に16個の制御バイトのグループをプローブすることに決定しました。</p><h2>ES|QLにおける統合</h2><p>Elasticsearchでのスイススタイルのハッシュの採用は、単なる置き換えではありませんでした。ES|QLには、メモリアカウンティング、安全性、コンピューティングエンジンの他の部分との統合に関して厳しい要件があります。</p><p>新しいハッシュテーブルを、ページリサイクラーやサーキットブレーカーアカウンティングなどのElasticsearchのメモリ管理と緊密に統合し、割り当てが常に可視かつ制限された状態になるようにしました。Elasticsearchのアグリゲーションは密に格納され、グループIDでインデックス化されるため、メモリレイアウトはコンパクトで高速に保たれ、反復処理も高速になります。また、ランダムアクセスを許可することで特定のパフォーマンスを最適化できます。</p><p>可変長バイトキーの場合、グループIDと一緒に完全なハッシュをキャッシュします。これにより、プローブ中に高価なハッシュコードを再計算する必要がなくなり、関連するメタデータを近くに保持することでキャッシュの局所性が向上します。再ハッシュ中は、値自体を検査せずにキャッシュされたハッシュと制御バイトに依存できるため、サイズ変更のコストが低く抑えられます。</p><p>実装における重要な簡素化の一つは、エントリーが決して削除されないことです。これにより、<em>トゥームストーン</em>（以前占有されていたスロットを識別するためのマーカー）の必要性がなくなり、空のスロットは実際に空のままになるので、プローブの動作がさらに改善され、制御バイトスキャンが効率的に維持されます。</p><p>その結果、スイステーブルの魅力となるパフォーマンス特性を維持しながら、Elasticsearchの実行モデルに自然に適合する設計が実現しました。</p><h2>パフォーマンス</h2><p>カーディナリティが小さい場合、スイステーブルのパフォーマンスは既存の実装とほぼ同等になります。これは予想どおりです。テーブルが小さい場合、キャッシュの影響は少なくなり、最適化するための調査もほとんど行われません。</p><p>カーディナリティが増加するにつれて、状況は急速に変化します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/24900afc47ab07b0e9933f6117b99d0f4613f794-962x599.png" style="width: 100%;" /><p>上記のヒートマップは、異なるキーサイズ（8、32、64、128バイト）に対する時間改善係数を、1,000から10,000,000グループまでの基数にわたってプロットしています。カーディナリティが増加するにつれて、改善係数は着実に増加し、均一分布の場合は2～3倍に達します。</p><p>この傾向はまさに設計が予測していることです。カーディナリティが高くなると、従来のハッシュテーブルではプローブチェーンが長くなりますが、スイススタイルのプローブでは、SIMD対応の制御バイトブロック内でほとんどの検索が引き続き解決されます。</p><h2>キャッシュの挙動が物語るもの</h2><p>速度の向上をよりよく理解するために、同じJMH <a href="https://github.com/elastic/elasticsearch/pull/139343/files#diff-d0e0cc91a7495bf36b2d44eacce95f5185d01879e5f6c38089ac7a89aad17da7"><code>benchmarks</code></a> をLinux<code>perf</code> で実行し、キャッシュとTLBの統計を取得しました。</p><p>元の実装と比較すると、スイスバージョンでは全体的にキャッシュ参照が約60%少なくなります。最終レベルのキャッシュのロードは4倍以上減少し、LLCロードミスは6倍以上減少します。LLCのミスはメインメモリアクセスに直接変換されることが多いので、この減少だけでエンドツーエンドの改善の大部分を説明できます。</p><p>CPUに近いほどL1データキャッシュミスが少なくなり、データTLBミスが約6倍少なくなります。これは、空間的局所性が高く、メモリアクセスパターンがより予測可能であることを示しています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6e49b7609fba83e33692cb9834552b6ca7e42a83-998x499.png" style="width: 100%;" /><p>これがSIMD対応の制御バイトの実用的なメリットです。散在したメモリ位置からキーと値を繰り返しロードする代わりに、ほとんどのプローブは、コンパクトなキャッシュ常駐構造をスキャンすることによって解決されます。アクセスされるメモリが少なければミスも減り、ミスが少なければクエリも速くなります。</p><h2>まとめ</h2><p>スイススタイルのハッシュテーブル設計を採用し、SIMDフレンドリーなプロービングを積極的に活用することで、高カーディナリティのES|QL統計ワークロードで2〜3倍の速度向上を達成し、より安定的で予測可能なパフォーマンスを実現しました。</p><p>この研究は、現代のCPUに対応したデータ構造が、ハッシュテーブルのような十分に確立された問題においても、大きな性能向上を実現できることを示しています。ここでは、追加のプリミティブ型の特殊化や、結合などの他の高カーディナリティパスでの使用など、さらに検討する余地がありますが、これらはすべて、Elasticsearchの内部を継続的に近代化するための広範かつ継続的な取り組みの一部に過ぎません。</p><p>詳細に興味がある方や作業をフォローしたい方は、GitHubの<a href="https://github.com/elastic/elasticsearch/pull/139343">プルリクエスト</a>と<a href="https://github.com/elastic/elasticsearch/issues/138799">メタイシュー</a>の進捗追跡をチェックしてみてください。</p><p>ハッシュを活用しましょう！</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearchによるエージェントメモリの管理]]></title>
            <description><![CDATA[Elasticsearchを使用してメモリを管理することで、よりコンテキストを意識した効率的なエージェントを作成できます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/agentic-memory-management-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/agentic-memory-management-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Someshwaran Mohankumar]]></dc:creator>
            <pubDate>Fri, 16 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/80907fd072e72d6ec902470b449c9f337957a0d7-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>新興の分野である<strong>コンテキストエンジニアリング</strong>では、AIエージェントに適切なタイミングで適切な情報を提供することが極めて重要です。コンテキストエンジニアリングの最も重要な側面の1つは、AIの<strong>メモリ</strong>を管理することです。人間と同じように、AIシステムは情報を思い出すために短期記憶と長期記憶の両方に依存しています。大規模言語モデル（LLM）エージェントに論理的な会話をさせたり、ユーザーの好みを覚えたり、以前の結果や対応に基づいて構築させたりしたい場合は、効果的な記憶メカニズムをエージェントに装備する必要があります。</p><p>結局のところ、コンテキスト内のすべてがAIの応答に影響を与えます。「<em>ゴミを入れたらゴミが出てくる</em>」は真実です。</p><p>この記事では、AIエージェントにとって短期記憶と長期記憶が何を意味するのか、具体的に紹介します。</p><ul><li>短期記憶と長期記憶の違い。</li><li>Elasticsearchのようなベクトルデータベースを使用したRetrieval-Augmented Generation（RAG）手法との関係、そして慎重なメモリ管理が必要な理由。</li><li>コンテキストオーバーフローやコンテキストポイズニングなど、メモリを軽視することによるリスク。</li><li>エージェントのメモリを有用かつ安全に保つベストプラクティス（コンテキストのプルーニング、要約、関連するもののみの取得など）</li><li>最後に、Elasticsearchを使用してエージェントが混乱することなく協力できるように、マルチエージェントシステムでメモリを共有および伝播する方法について説明します。</li></ul><h2>AIエージェントにおける短期記憶と長期記憶の違い</h2><p><strong><em>AIエージェントの短期記憶</em></strong>は通常、直前の会話のコンテキストや状態を指します。本質的には、アクティブなセッションにおける現在のチャット履歴や最近のメッセージです。ユーザーの最新のクエリと最近のやり取りが含まれ、人が会話中に心に留めている情報と非常に似ています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4fbcc6f68055b2bccefc4176297a4ca50056dc0d-764x498.png" style="width: 100%;" /><p>AIフレームワークは、エージェントの状態の一部としてこの一時的なメモリを保持することがよくあります（例えば、<a href="https://docs.langchain.com/oss/python/langgraph/persistence#checkpoints">LangGraphのこの例</a>のように、会話の状態を格納するためにチェックポインタを使用します）。短期記憶は<strong><em>セッション範囲に限定されます</em></strong>。つまり、短期記憶は単一の会話またはタスク内に存在し、明示的に他の場所に保存されない限り、そのセッションが終了するとリセットまたはクリアされます。セッションに縛られた短期記憶の例としては、ChatGPTで利用可能な<strong><a href="https://help.openai.com/en/articles/8914046-temporary-chat-faq">一時的なチャット</a></strong>が挙げられます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/150bdf209cda5ed20b59cddf34e624ad1a8016aa-1100x577.png" style="width: 100%;" /><p>一方、<strong><em>長期記憶</em></strong>とは、<strong>会話やセッションを超えて</strong>持続する情報を指します。これは、エージェントが長期にわたって保持する知識、以前に学習した事実、ユーザーの選好、または永続的に覚えておくように指示されたデータです。</p><p>長期記憶は通常、ファイルやベクトルデータベースなどの即時コンテキストウィンドウの外部にある外部ソースに格納・取得することで実装されます。短期的なチャット履歴とは異なり、長期記憶はすべてのプロンプトに自動的に含まれるわけではありません。代わりに、特定のシナリオに基づいて、エージェントは関連するツールが呼び出されたときにそれを<strong>想起したり</strong>取得したりする必要があります。実際には、長期記憶にはユーザーのプロフィール情報、エージェントが生成した過去の回答や分析、またはエージェントがクエリを実行できるナレッジベースが含まれる可能性があります。</p><p>例えば、旅行プランナーエージェントの場合、<em>短期メモリには</em>現在の旅行の問い合わせの詳細（日付、目的地、予算）とそのチャットでのフォローアップの質問が格納され、<em>長期メモリには</em>ユーザーの一般的な旅行の好み、過去の旅程、および以前のセッションで共有されたその他の事実が格納されます。ユーザーが後で戻ってきたときに、エージェントはこの長期格納場所から情報を引き出すことができます（そのユーザーはビーチや山が好きで、平均予算はINR 100,000で、行きたい場所のリストがあり、子供向けのアトラクションよりも歴史や文化を体験することを好むなど）。そのため、エージェントは毎回ユーザーを白紙の状態として扱うことはありません。</p><p>短期記憶（チャット履歴）は即時のコンテキストと継続性を提供し、長期記憶はエージェントが必要なときに利用できるより広範なコンテキストを提供します。ほとんどの先進的なAIエージェントフレームワークは、その両方を可能にします。コンテキストを保守するために最近の対話を追跡し、<em>かつ</em>より長期的なリポジトリに情報を検索または格納するメカニズムを提供します。短期記憶を管理することで、コンテキストウィンドウ内に留まることを確保し、長期記憶を管理することで、エージェントは過去のやり取りやペルソナに基づいて回答を裏付けることができます。</p><h2>コンテキストエンジニアリングにおけるメモリとRAG</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/46635aa11ceff89b8d6a26ac3e22da52407d82f3-1600x900.png" style="width: 100%;" /><p><strong><em>実際にAIエージェントに有用な長期記憶を与えるにはどうすればよいでしょうか？</em></strong></p><p>長期記憶のための一つの顕著なアプローチは<strong><em>セマンティック記憶</em></strong>であり、しばしば<strong>検索拡張生成（RAG）</strong>を介して実装されます。これには、LLMを外部のナレッジストアやElasticsearchなどのベクトル対応データストアと結合することが含まれます。LLMは、プロンプトや組み込みのトレーニングの内容以外の情報を必要とする場合、Elasticsearchに対してセマンティック検索を実行し、最も関連性の高い結果をコンテキストとしてプロンプトに注入します。このように、モデルの効果的なコンテキストには、最近の会話（短期記憶）だけでなく、その場で得られた関連する長期的事実も含まれます。その後、LLMは、独自の推論と取得した情報の両方に基づいて回答を決定し、短期記憶と長期記憶を効果的に組み合わせて、より正確でコンテキストを意識した応答を生成します。</p><p><strong>Elasticsearch</strong>はAIエージェントの長期記憶実装にも利用できます。長期記憶のためにElasticsearchからコンテキストを取得する方法の高次的な例を示します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/41ccbc7b5171e8170ac300139a963c0708816ba6-1600x900.png" style="width: 100%;" /><p>このように、エージェントは<strong>さまざまなリスクにつながる</strong>限られたプロンプト内にすべてを格納するのではなく、関連するデータを検索することによって「思い出し」ます。</p><p><strong>RAGをElasticsearchや任意のベクトルストアで使用することで、多くの利点が得られます。</strong></p><p>第一に、モデルのトレーニングのカットオフを超えて<strong>知識を拡張</strong>できます。エージェントは、LLMが知らない可能性のある最新の情報やドメイン固有のデータを取得できます。これは最近の出来事や専門的なトピックに関する質問に非常に重要です。</p><p>第二に、オンデマンドでコンテキストを取得することで、ハルシネーションを減らすことができます。特に、LLMはニッチなユースケースに関連する独自データや高度に専門化されたデータでトレーニングされていないため、ハルシネーションが発生する可能性が非常に高いです。LLMが評価によって推測や新しい情報を作り出すのではなく、最近のOpenAIの論文（<a href="https://arxiv.org/pdf/2509.04664">Why Language Models Hallucinate</a>）で指摘されているように、このモデルはElasticsearchの事実に基づく参照に基づいています。当然ながら、LLMはベクトルストアのデータの信頼性に依存しており、誤情報を確実に防御するために、コアの関連性指標に基づいて関連データが取得されます。</p><p>第三に、RAGを使用すると、エージェントはプロンプトに収めることができるものよりもはるかに大きなナレッジベースを扱うことができます。長い研究論文や政策文書のように、文書全体をコンテキストウィンドウに押し込んで過剰負荷や無関係な情報コンテキストがモデルの推論を損なう<a href="https://www.elastic.co/search-labs/blog/agentic-memory-management-elasticsearch#context-poisoning">コンテキストポイズニング</a>を招くことなく、RAGは<a href="https://www.elastic.co/search-labs/blog/chunking-strategies-elasticsearch">チャンク化</a>に依存します。大きな文書は意味的に意味のある小さな部分に分割され、システムはクエリに最も関連性の高い少数のチャンクのみを取得します。この方法では、モデルが知識豊富であることを示すために100万トークンのコンテキストを必要とせず、はるかに大規模なコーパスの適切なチャンクにアクセスするだけで済みます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e6897356c9f0940e35a63d005e9cd20bc33e5dd7-1600x931.png" style="width: 100%;" /><p>注目すべきは、LLMのコンテキストウィンドウが拡大し（<a href="https://www.anthropic.com/news/1m-context">一部のモデルは現在、数十万または数百万のトークンをサポート</a><em>）</em>、RAGの「終焉」についての議論が起きたことです。なぜすべてのデータをプロンプトに押し込まないのでしょうか。同じようにお考えなら、同僚のJeffrey RengifoとEduard Martinによるこの素晴らしい記事<a href="https://www.elastic.co/search-labs/blog/rag-vs-long-context-model-llm">「コンテキストは長ければよいわけではない：RAGが引き続き重要な理由」</a>を参照してください。これにより、「ゴミを入れればゴミが出てくる」問題を回避できます。LLMは、ノイズを処理するのではなく、重要な少数のチャンクに焦点を合わせ続けます。</p><p>とはいえ、Elasticsearchやその他のベクトルストアをAIエージェントのアーキテクチャに統合することで、<strong>長期記憶が</strong>可能になります。エージェントは知識を外部に格納し、必要に応じてメモリコンテキストとして取り込みます。これは、各ユーザークエリの後、エージェントがElasticsearchで関連情報を検索し、LLMを呼び出す前にプロンプトに上位の結果を追加する<em>アーキテクチャとして</em>実装することができます。応答に新しい有用な情報が含まれている場合、その応答は長期保存されることもあります（学習のフィードバックループが作成されます）。そのような検索ベースのメモリを使用することで、エージェントは、コンテキストウィンドウが<em>100万トークン</em>をサポートしている場合であっても、常に情報を得て最新の状態を保ちながら、すべての知識をすべてのプロンプトに詰め込む必要がなくなります。この技術は、情報検索と生成AIの強みを組み合わせたコンテキストエンジニアリングの基礎となるものです。 </p><p>これは、セッション中の短期記憶にLangGraphのチェックポイントシステムを使用して管理されたメモリ内会話状態の例です。（当社の<a href="https://github.com/someshwaranM/elastic-context-engineering-short-term-long-term-memory">コンテキストエンジニアリングをサポートするアプリ</a>を参照してください。）</p><pre><code># Initialize chat memory (Note: This is in-memory only, not persistent)
memory = MemorySaver()

# Create a LangGraph agent
langgraph_agent = create_react_agent(model=llm, tools=tools, checkpointer=memory)

...
...
# Only process and display checkpoints if verbose mode is enabled
if args.verbose:
    # List all checkpoints that match a given configuration
    checkpoints = memory.list({"configurable": {"thread_id": "1"}})
    # Process the checkpoints
    process_checkpoints(checkpoints)</code></pre><p><strong>checkpoint</strong>の格納方法は以下の通りです。</p><pre><code>Checkpoint:
Timestamp: 2025-12-30T09:19:41.691087+00:00
Checkpoint ID: 1f0e560a-c2fa-69ec-8001-14ee5373f9cf
User: Hi I'm Som, how are you? (Message ID: ad0a8415-5392-4a58-85ad-84154875bbf2)
Agent: Hi Som! I'm doing well, thank you! How about you? (Message ID: 
56d31efb-14e3-4148-806e-24a839799ece)
Agent:  (Message ID: lc_run--019b6e8e-553f-7b52-8796-a8b1fbb206a4-0)

Checkpoint:
Timestamp: 2025-12-30T09:19:40.350507+00:00
Checkpoint ID: 1f0e560a-b631-6a08-8000-7796d108109a
User: Hi I'm Som, how are you? (Message ID: ad0a8415-5392-4a58-85ad-84154875bbf2)
Agent: Hi Som! I'm doing well, thank you! How about you? (Message ID: 
56d31efb-14e3-4148-806e-24a839799ece)

Checkpoint:
Timestamp: 2025-12-30T09:19:40.349027+00:00
Checkpoint ID: 1f0e560a-b62e-6010-bfff-cbebe1d865f6</code></pre><p>長期記憶については、Elasticsearchでセマンティック検索を実行し、チェックポイントを要約して Elasticsearchにインデックス付けした後、ベクトル埋め込みを使用して関連する以前の会話を取得する方法を次に示します。</p><pre><code>Functions: 
retrieve_from_elasticsearch() 

# Enhanced Elasticsearch retrieval with rank_window and verbose display
def retrieve_from_elasticsearch(query: str, k: int = 5, rank_window: int = None) -> tuple[List[Dict[str, Any]], str]:
    """
    Retrieve context from Elasticsearch with score-based ranking
    
    Args:
        query: Search query
        k: Number of results to return
        rank_window: Number of candidates to retrieve before ranking (default: args.rank_window)
        
    Returns:
        Tuple of (retrieved_documents, formatted_context_string)
    """
    if not es_client or not es_index_name:
        return [], "Elasticsearch is not available. Cannot search long-term memory."
    
    if rank_window is None:
        rank_window = args.rank_window
    
    try:
        # Check if index exists and has documents
        if not es_client.indices.exists(index=es_index_name):
            return [], "No previous conversations stored in long-term memory yet."
        
        # Get document count
        try:
            doc_count = es_client.count(index=es_index_name)["count"]
            if doc_count == 0:
                return [], "Long-term memory is empty. No previous conversations to search."
        except Exception as e:
            return [], f"Error checking memory: {str(e)}"
        
        # Generate embedding for the query
        try:
            query_embedding = embeddings.embed_query(query)
        except Exception as e:
            return [], f"Error generating embedding: {str(e)}"
        
        # Perform semantic search using kNN with rank_window
        try:
            search_body = {
                "knn": {
                    "field": "vector",
                    "query_vector": query_embedding,
                    "k": k,
                    "num_candidates": rank_window  # Retrieve more candidates, then rank top k
                },
                "_source": ["text", "content", "message_type", "timestamp", "thread_id"],
                "size": k
            }
            
            response = es_client.search(index=es_index_name, body=search_body)
            
            if not response.get("hits") or len(response["hits"]["hits"]) == 0:
                return [], "No relevant previous conversations found in long-term memory."
            
            # Extract documents with scores
            retrieved_docs = []
            for hit in response["hits"]["hits"]:
                source = hit["_source"]
                score = hit["_score"]
                retrieved_docs.append({
                    "content": source.get("content", source.get("text", "")),
                    "message_type": source.get("message_type", "unknown"),
                    "timestamp": source.get("timestamp", "unknown"),
                    "thread_id": source.get("thread_id", "unknown"),
                    "score": score
                })
            
            # Format context string
            context_parts = []
            for i, doc in enumerate(retrieved_docs, 1):
                context_parts.append(doc["content"])
            
            context_string = "\n\n".join(context_parts)
            
            # Verbose display
            if args.verbose:
                rich.print(f"\n[bold yellow]🔍 RETRIEVAL ANALYSIS[/bold yellow]")
                rich.print("="*80)
                rich.print(f"[blue]Query:[/blue] {query}")
                rich.print(f"[blue]Retrieved:[/blue] {len(retrieved_docs)} documents (from {rank_window} candidates)")
                rich.print(f"[blue]Total context length:[/blue] {len(context_string)} characters\n")
                
                for i, doc in enumerate(retrieved_docs, 1):
                    rich.print(f"[cyan]📄 Document {i} | Score: {doc['score']:.4f} | Type: {doc['message_type']}[/cyan]")
                    rich.print(f"[cyan]   Timestamp: {doc['timestamp']} | Thread: {doc['thread_id']}[/cyan]")
                    content_preview = doc['content'][:200] + "..." if len(doc['content']) > 200 else doc['content']
                    rich.print(f"[cyan]   Content: {content_preview}[/cyan]")
                    rich.print("-" * 80)
            
            return retrieved_docs, context_string
            
        except Exception as e:
            return [], f"Error searching memory: {str(e)}"
            
    except Exception as e:
        return [], f"Error accessing long-term memory: {str(e)}"</code></pre><p>ElasticsearchのLangGraphのチェックポイントを使用して、短期記憶と長期記憶がどのようにインデックス化され、取得されるかを調べてきました。さて、少し時間を取って、会話全体をインデキシングしてダンプすることがなぜ危険なのかを理解しましょう。</p><h2>コンテキストメモリを管理しないことのリスク</h2><p>コンテキストエンジニアリング、短期記憶、長期記憶について詳しく説明しているので、エージェントのメモリとコンテキストを適切に管理しないと何が起こるかを理解しましょう。</p><p>残念ながら、AIのコンテキストが非常に長くなったり、悪い情報が含まれたりすると、多くの問題が発生する可能性があります。コンテキストウィンドウが大きくなるにつれて、次のような<strong>新しい障害モード</strong>が発生します。</p><ul><li><strong>コンテキストポイズニング</strong></li><li><strong>コンテキストの逸脱</strong></li><li><strong>コンテキストの混乱</strong></li><li><strong>コンテキストの衝突</strong></li><li><strong>コンテキスト漏洩と知識対立</strong></li><li><strong>ハルシネーションと誤情報</strong></li></ul><p>これらの問題や、不適切なコンテキスト管理から生じるその他のリスクを分解して見ていきましょう。</p><h3>コンテキストポイズニング</h3><p><em>コンテキストポイズニング</em>とは、不正確または有害な情報がコンテキストに入り込み、モデルのその後の出力を「汚染」してしまうことを指します。よくある例としては、モデルによるハルシネーションが事実として扱われ、会話履歴に挿入されることがあります。モデルはその後、そのエラーを元に対応を構築し、誤りを複合化させることがあります。反復的なエージェントループでは、共有コンテキスト（エージェントの作業メモの要約など）に誤った情報が入ると、その情報が何度も強化されることがあります。 </p><p><a href="https://storage.googleapis.com/deepmind-media/gemini/gemini_v2_5_report.pdf">DeepMindの研究者は、Gemini 2.5レポート</a>（概略は<a href="https://www.dbreunig.com/2025/06/17/an-agentic-case-study-playing-pok%C3%A9mon-with-gemini.html">ここをチェック</a>）のリリース時に長年<em>ポケモン</em>をプレイしているエージェントでこれを観察しました。エージェントが間違ったゲーム状態のハルシネーションを起こし、それがその<em>コンテキスト</em>（ゴールの記憶）に記録された場合、エージェントは不可能な目標に関する<strong>無意味な戦略</strong>を立て、行き詰まってしまいます。言い換えれば、汚染された記憶はエージェントを永久に誤った道へと導く可能性があるのです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3f2facf5aad67613ad557422e09ec23a66adc0ed-1600x1388.png" style="width: 100%;" /><p>コンテキストポイズニングは、無意識のうちに（誤って）発生する可能性があるだけでなく、悪意を伴ってを発生する可能性もあります。例えば、プロンプトインジェクション攻撃では、ユーザーや第三者が隠れた指示や虚偽の事実を忍び込ませ、エージェントがそれを記憶し、従うようにします。</p><p><strong>推奨される対策：</strong></p><p><a href="https://www.wiz.io/academy/data-poisoning">Wiz</a>、<a href="https://zerlo.net/en/blog/what-is-llm-data-poisoning">Zerlo</a>、<a href="https://www.anthropic.com/research/small-samples-poison">Anthropic</a>からの洞察に基づき、コンテキストポイズニングの対策は、LLMのプロンプト、コンテキストウィンドウ、検索パイプラインに悪い情報や誤解を招く情報が入らないようにすることに重点を置いています。主な手順は次のとおりです。</p><ul><li>常にコンテキストを確認する：開始プロンプトだけでなく、不審な点や有害な点がないか、会話や検索されたテキストを監視します。</li><li>信頼できるソースを使用する：信頼性に基づいて文書にスコアを付けたりラベルを付けたりすることで、システムは信頼性の高い情報を優先し、スコアの低いデータを無視します。</li><li>異常なデータを見つける：奇妙なもの、場違いなもの、または操作されたコンテンツを検出するツールを使用し、モデルが使用する前に削除します。</li><li>入力と出力をフィルターする: 有害または誤解を招くテキストがシステムに簡単に入力されたり、モデルによって繰り返されたりしないようにガードレールを追加します。</li><li>モデルをクリーンなデータで最新の状態に保つ：検証済みの情報で定期的にシステムを更新して、流出してしまった不良データに対処します。</li><li>人間が関与する：重要な出力を人間が確認したり、既知の信頼できるソースと比較したりします。</li></ul><p>長いチャットをリセットし、関連情報のみを共有し、複雑なタスクを小さなステップに分割し、モデル外で明確なメモを維持するなど、使用時のシンプルな習慣も役立ちます。</p><p>これらの対策を組み合わせることで、コンテキストポイズニングからLLMを保護し、出力の正確性と信頼性を維持する階層化された防御が実現します。</p><p>ここに記載されている対策を講じないと、エージェントは以前のガイドライン<em></em>や攻撃者が挿入した些細な事実を無視するなどの指示を覚えてしまい、有害な出力につながることがあります。</p><h3>コンテキストの逸脱</h3><p><em>コンテキストの逸脱</em>とは、コンテキストが長くなりすぎて、モデルがコンテキストに過度に集中し、トレーニング中に学習した内容を無視してしまうことです。極端な場合、これは<a href="https://en.wikipedia.org/wiki/Catastrophic_interference"><em>壊滅的な忘却</em></a>のようになります。この場合、モデルは基本的な知識を「忘れ」、目の前にある情報に過度に依存するようになります。過去の研究では、プロンプトが非常に長いとLLMが焦点を失うことが多いことが示されています。</p><p>例えば、Gemini 2.5エージェントは100万トークンのウィンドウをサポートしていましたが、そのコンテキストが特定のポイント（実験では約10万トークン）を超えると、新しいソリューションを思いつく代わりに、<strong>過去の行動を繰り返すことに固執</strong>し始めました。ある意味、エージェントはその長い歴史の囚人となったのです。基礎となるトレーニング知識を活用して斬新な戦略を考案するのではなく、以前の動作の長いログ（コンテキスト）を見てそれを真似し続けました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e08e5b6d2e8ec7e3511d455985eed3d7fa6241e0-1352x636.png" style="width: 100%;" /><p>これでは逆効果です。私たちは、モデルが思考能力を無効にするのではなく、推論を助けるために関連コンテキストを使用することを望んでいます。注目すべきは、巨大なウィンドウを持つモデルでさえ、トークンが追加されるにつれて、パフォーマンスが不均一に低下するという<a href="https://research.trychroma.com/context-rot"><em>コンテキスト腐敗</em></a>を示すことです。ここには<em>注意予算</em>が見られるようです。つまり、人間の作業記憶が限られているように、LLMがトークンに注意を払う能力も有限であり、その予算が限界に達すると、その精度と焦点が低下します。</p><p>緩和策として、チャンキング、適切な情報のエンジニアリング、定期的なコンテキストの要約、応答の正確性を測定するための評価とモニタリング技術を使用して、コンテキストの逸脱を防止できます。</p><p>これらの方法により、モデルは関連するコンテキストとその基礎となるトレーニングの両方に基盤を置くようになり、逸脱のリスクが軽減され、全体的な推論品質が向上します。</p><h3>コンテキストの混乱</h3><p><em>コンテキストの混乱</em>とは、コンテキスト内の余分なコンテンツがモデルによって使用され、低品質の対応が生成されることです。代表的な例としては、エージェントが使用する可能性のある大量のツールやAPI定義をエージェントに提供することが挙げられます。それらのツールの多くが現在のタスクと無関係であっても、モデルはコンテキスト内に存在するという理由だけで、それらを不適切に使用しようとする可能性があります。実験によると、必要でないツールやドキュメントを<em>多く</em>提供すると、パフォーマンスが<em>低下</em>することがわかっています。エージェントは、間違った関数を呼び出したり、無関係なテキストを参照したりするなどの間違いを犯し始めます。 </p><p>あるケースでは、小型の<strong>Llama 3.1 8B</strong>モデルは、検討すべきツールが46個与えられたときにはタスクに失敗しましたが、19個のツールしか与えられなかったときには成功しました。コンテキストが長さ制限内であったにもかかわらず、追加のツールによって混乱が生じました。根本的な問題は、プロンプト内のすべての情報がモデルに<em>処理</em>されることです。何かを無視するべきことを認識していない場合、その何かが望ましくない方法で出力に影響を及ぼす可能性があります。無関係な部分がモデルの注意の一部を「奪い」、誤った方向に導くことがあります（無関係な文書によってエージェントが尋ねられた質問とは異なる質問に答えるなど）。コンテキストの混乱は、しばしばモデルが無関係なコンテキストを統合した低品質の応答を生成することとして現れます。研究論文「<a href="https://arxiv.org/pdf/2411.15399">Less is More: Optimizing Function Calling for LLM Execution on Edge Devices.</a>」を参照してください。</p><p>これは、特に関連性を重視して<strong>厳選</strong>されていない場合、コンテキストが多ければ多いほど良いとは限らないことを想起させます。</p><h3>コンテキストの衝突</h3><p><em>コンテキストの衝突</em>は、<strong>コンテキストの部分が互いに矛盾</strong>し、モデルの推論を脱線させる内部不整合が発生するときに起こります。エージェントが矛盾する複数の情報を蓄積すると、衝突が発生することがあります。 </p><p>例えば、あるエージェントが2つのソースからデータを取得したとします。1つは<em>午後5時にA便が出発</em>するというもので、もう1つは<em>午後6時にA便が出発</em>するというものです。両方の事実がコンテキスト内に存在する場合、貧弱なモデルではどちらが正しいのかを判断できず、混乱したり、不正確な回答や類似しない回答を生成したりする可能性があります。</p><p>コンテキストの衝突は、モデルの<strong>過去の回答の試み</strong>が、その後の洗練された情報とともにコンテキスト内に残っている、マルチターンの会話でも頻繁に発生します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/500d7a80dc8db1923f9b5ca84728eed64fa296f7-1316x580.png" style="width: 100%;" /><p>MicrosoftとSalesforceの<a href="https://arxiv.org/pdf/2505.06120">研究</a>によると、複雑なクエリを複数のチャットボットターンに分割して詳細を徐々に追加すると、単一のプロンプトですべての詳細を伝える場合と比べて、最終的な精度が大幅に低下することが示されています。なぜでしょうか。初期のターンにモデルからの部分的なまたは不正確な中間回答が含まれており、それらはコンテキスト内に残るためです。モデルが後ですべての情報を使用して回答しようとすると、その<em>メモリ</em>にはまだそれらの誤った試行が含まれており、修正された情報と矛盾して軌道から外れてしまいます。本質的に、会話のコンテキストが自己衝突しているのです。このモデルは、新しい情報が追加された後には適用されない古いコンテキスト（以前のターンからの）を誤って使用してしまう可能性があります。</p><p>エージェントが異なるツールやサブエージェントからの出力を組み合わせる可能性があるエージェントシステムでは、コンテキストの衝突は特に危険です。これらの出力が一致しない場合、集約された文脈は一貫性がありません。すると、エージェントは行き詰まったり、矛盾を調整しようとして無意味な結果を生成する可能性があります。コンテキストの衝突を防ぐには、コンテキストが<strong>新鮮で一貫している</strong>こと、<strong></strong>例えば、古い情報をクリアまたは更新し、一貫性がないソースを混ぜないことが重要です。</p><h3>コンテキスト漏洩と知識対立</h3><p>システムで複数のエージェントやユーザーがメモリストアを共有する場合、コンテキスト間で情報が漏れるリスクがあります。</p><p>例えば、適切なアクセス制御がないまま、2人の異なるユーザーのデータ埋め込みが同じベクトルデータベースに存在する場合、ユーザーAのクエリに応答するエージェントが、誤ってユーザーBのメモリの一部を取得する可能性があります。この <strong><em>クロスコンテキスト漏洩</em></strong> は、個人情報を漏洩させたり、対応に混乱を生じさせたりする可能性があります。</p><p><a href="https://wtit.com/blog/2025/04/17/owasp-top-10-for-llm-applications-2025/">OWASP Top 10 for LLM Applications</a>によれば、マルチテナントベクトルデータベースは次のような漏洩を防ぐ必要があります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8f0ccf0b2f7bd6715c14aceee2deffb213d50bd9-1600x936.png" style="width: 100%;" /><p><a href="https://wtit.com/blog/2025/04/17/owasp-top-10-for-llm-applications-2025/">LLM08:2025 Vector and Embedding Weaknesses</a><em>によると、</em>一般的なリスクの1つはコンテキストの漏洩です。</p><blockquote><em>マルチテナント環境では、複数のクラスのユーザーやアプリケーションが同じベクトルデータベースを共有するため、ユーザーやクエリ間でコンテキストが漏洩するリスクがあります。データフェデレーション知識の矛盾エラーは、複数のソースからのデータが矛盾し合う場合に発生します。これは、LLMがトレーニング中に学習した古い知識を検索拡張からの新しいデータで置き換えることができない場合にも発生する可能性があります。</em></blockquote><p>もう一つの側面は、LLMがメモリからの新しい情報で<strong>組み込まれた知識</strong>を上書きするのに苦労する可能性があることです。モデルが何らかの事実に基づいてトレーニングされていて、取得したコンテキストが逆の場合、モデルはどちらを信頼すべきか混乱する可能性があります。適切な設計がないと、エージェントがコンテキストを混同したり、古い知識を新しい証拠で更新できなかったりして、古くなった回答や間違った回答につながる可能性があります。</p><h3><strong>ハルシネーションと誤情報</strong></h3><p><em>ハルシネーション</em>（LLM がもっともらしく聞こえるが誤った情報を生成すること）は、コンテキストが長くなくても既知の問題ですが、メモリ管理が不十分だと増幅される可能性があります。 </p><p>エージェントのメモリに重要な事実が欠けている場合、モデルは単に<strong>その空白を推測で埋める</strong>可能性があり、その推測がコンテキストに入り込む（汚染）と、エラーが持続します。 </p><p>OWASPのLLMセキュリティレポート<a href="https://wtit.com/blog/2025/04/17/owasp-top-10-for-llm-applications-2025/"><strong>（LLM09:2025 Misinformation）</strong></a>では、誤情報が主な脆弱性として強調されています。LLMは自信を持って虚偽の回答を生成する可能性があり、ユーザーはそれらを過度に信頼する可能性があります。長期記憶が不良または古くなっているエージェントは、その記憶が最新の状態に保たれていない限り、昨年は真実であったが現在は間違っていることを自信を持って引用する可能性があります。 </p><p>（ループ内のユーザーまたはエージェント自体による）AIの出力への過度の依存は、この問題を悪化させる可能性があります。誰もメモリの情報を確認しなければ、エージェントは虚偽を蓄積してしまう可能性があります。RAGがハルシネーションを抑えるためによく使われるのはこのためです。信頼できる情報源を検索することで、モデルは事実を捏造する必要がなくなります。しかし、検索によって間違った文書（誤った情報が含まれている文書など）が取得されたり、初期のハルシネーションが除去されなかったりすると、システムはその誤った情報をその動作全体に伝播してしまう可能性があります。 </p><p>肝心なことは、メモリの管理に失敗すると、<strong>不正確で誤解を招く出力</strong>につながる可能性があり、特にリスクが高い場合（金融や医療分野での誤ったアドバイスなど）には損害を与える可能性があります。エージェントには、コンテキスト内のあらゆるものを無条件に信頼するだけでなく、メモリの内容を検証または修正するためのメカニズムが必要です。</p><p>まとめると、成功の秘訣は、AIエージェントに無限に長いメモリを与えたり、ありとあらゆるものをそのコンテキストに放り込んだりすることでは<em>ありません</em>。</p><h2>LLMアプリケーションにおけるメモリ管理のベストプラクティス</h2><p>上記の陥穽を避けるために、開発者と研究者はAIシステムで<strong>コンテキストとメモリを管理するためのベストプラクティス</strong>をいくつか考案しました。これらのプラクティスは、AIの動作コンテキストを無駄なく、関連性があり、最新の状態に保つことを目的としています。ここでは、いくつかの重要な戦略と、それがどのように役立つかの例を示します。</p><h3>RAG：ターゲットを絞ったコンテキストを使用する</h3><p>RAGの大部分についてはすでに前のセクションで説明しましたので、このセクションは実用的な注意事項を簡潔にまとめたものです。</p><ul><li>ターゲットを絞った検索を使用し、一括読み込みは避ける：全文書や会話の履歴全体をプロンプトにプッシュするのではなく、最も関連性の高い部分のみを取得します。</li><li>RAGをジャストインタイムのメモリ呼び出しとして扱う：ターンを超えてすべてを転送するのではなく、必要なときにのみコンテキストを取得します。</li><li>関連性に注意した検索戦略を優先する：トップkセマンティック検索、逆順位融合、ツールロードアウトフィルタリングなどのアプローチは、ノイズを減らし、グラウンディングを改善するのに役立ちます。</li><li>コンテキストウィンドウが大きくなってもRAGは必要：2つの非常に関連性の高い段落は、20の緩く関連したページよりもほとんど常に効果的です。</li></ul><p>つまり、RAGではコンテキストを増やすことではなく、適切なコンテキストを追加することが重要なのです。</p><h3>ツールのロードアウト</h3><p><em>ツールのロードアウト</em>とは、モデルにタスクに実際に必要なツールのみを与えることです。この用語はゲームに由来し、状況に合った装備を選ぶというものです。ツールが多すぎると作業が遅くなり、間違ったツールを使用すると失敗の原因になります。研究論文「<a href="https://arxiv.org/abs/2411.15399">Less is more</a>」によると、LLMも同様に動作します。ツールが30個を超えると、説明が重複し始め、モデルが混乱します。ツールの数が約100個を超えると、失敗はほぼ確実です。これはコンテキストウィンドウの問題ではなく、コンテキストの混乱です。</p><p>シンプルで効果的な解決策となるのが<a href="https://arxiv.org/abs/2505.03275"><strong>RAG-MCP</strong></a>です。すべてのツールをプロンプトにダンプする代わりに、ツールの説明はベクトルデータベースに保存され、リクエストごとに最も関連性の高いものだけが取得されます。実際には、これにより、ロードアウトが小さく集中したものになり、プロンプトが大幅に短縮され、ツール選択の精度が最大3倍向上します。</p><p>小型モデルでは、この壁にぶつかるのがさらに早くなります。調査によると、8Bモデルは数十個のツールでは失敗しますが、ロードアウトを調整すると成功します。場合によっては最初にLLMを使用して、必要と思われるツールを動的に選択すると、パフォーマンスが44%向上し、電力使用量とレイテンシも削減されます。重要な点は、ほとんどのエージェントはわずかなツールしか必要としないものの、システムが成長するにつれて、ツールのロードアウトとRAG-MCPが第一の設計上の決定事項になるということです。</p><h3>コンテキストのプルーニング：チャット履歴の長さを制限する</h3><p>会話が何ターンも続くと、蓄積されたチャット履歴が大きすぎて収まらなくなり、コンテキストがオーバーフローしたり、モデルの注意が散漫になったりします。 </p><p><em>トリミングと</em>は、会話が長くなるにつれて、重要度の低い部分をプログラムで削除または短縮することを指します。単純な形式としては、一定の制限に達したときに会話の最も古いターンを削除し、最新の<em>N</em>件のメッセージのみを保持する方法があります。より高度なプルーニングにより、無関係な余談や不要になった以前の指示を削除することもあります。目標は、<strong>コンテキストウィンドウが古いニュースで煩雑にならないよう</strong>にすることです。 </p><p>例えば、エージェントが10ターン前にサブ問題を解決し、その後先に進んだ場合、コンテキストから履歴のその部分を（それ以上必要ないという前提で）削除することがあります。多くのチャットベースの実装では、最近のメッセージのローリングウィンドウを維持することでこれを実行します。 </p><p>トリミングは、会話の最初の部分を、要約したり、無関係だと判断したりした後に「忘れる」という単純な作業である場合もあります。そうすることで、コンテキストオーバーフローエラーのリスクが軽減され、<a href="https://www.elastic.co/search-labs/blog/agentic-memory-management-elasticsearch#context-distraction"><strong>コンテキストの逸脱</strong></a>も軽減されるため、モデルが古いコンテンツやトピック外のコンテンツを見つけて脇道に逸れることがなくなります。このアプローチは、人間が1時間の講演のすべての単語を覚えているわけではないものの、重要な部分は覚えているということと非常によく似ています。 </p><p>作者のDrew Breunigが<a href="https://www.dbreunig.com/2025/06/26/how-to-fix-your-context.html#tool-loadout:~:text=Provence%20is%20fast%2C%20accurate%2C%20simple%20to%20use%2C%20and%20relatively%20small%20%E2%80%93%20only%201.75%20GB.%20You%20can%20call%20it%20in%20a%20few%20lines%2C%20like%20so%3A">ここ</a>で強調しているように、コンテキストプルーニングについて混乱している場合は、質問応答用の軽量（1.75 GB）、効率的で正確なコンテキストプルーナーであるProvence（`<a href="https://huggingface.co/naver/provence-reranker-debertav3-v1">naver/provence-reranker-debertav3-v1</a>`）モデルを使用すると違いが出る可能性があります。大きな文書を特定のクエリに最も関連性の高いテキストだけに絞り、特定の間隔で呼び出すことができます。</p><p>コード内で`provence-reranker`モデルを呼び出してコンテキストをプルーニングする方法は次のとおりです。</p><pre><code># Context pruning with Provence
def prune_with_provence(query: str, context: str, threshold: Optional[float] = None) -> str:
    """
    Prune context using Provence reranker model
    
    Args:
        query: User's query/question
        context: Original context to prune
        threshold: Relevance threshold (0-1) for Provence reranker.
                   If None, uses args.pruning_threshold.
                   0.1 = conservative (recommended, no performance drop)
                   0.3-0.5 = moderate to aggressive pruning
    
    Returns:
        Pruned context with only relevant sentences
    """
    if provence_model is None:
        return context
    
    if threshold is None:
        threshold = args.pruning_threshold
    
    try:
        # Use Provence's process method
        provence_output = provence_model.process(
            question=query,
            context=context,
            threshold=threshold,
            always_select_title=False,
            enable_warnings=False
        )
        
        # Extract pruned context from output
        pruned_context = provence_output.get('pruned_context', context)
        reranking_score = provence_output.get('reranking_score', 0.0)
        
        # Log statistics
        original_length = len(context)
        pruned_length = len(pruned_context)
        reduction_pct = ((original_length - pruned_length) / original_length * 100) if original_length > 0 else 0
        
        if args.verbose:
            rich.print(f"[cyan]📊 Pruning stats: {pruned_length}/{original_length} chars ({reduction_pct:.1f}% reduction, threshold={threshold:.2f}, rerank_score={reranking_score:.3f})[/cyan]")
        
        return pruned_context if pruned_context else context
        
    except Exception as e:
        rich.print(f"[yellow]⚠️ Error in Provence pruning: {str(e)}[/yellow]")
        rich.print(f"[yellow]⚠️ Falling back to original context[/yellow]")
        return context</code></pre><p>文の関連性のスコア付けにはProvenceリランカーモデル（`naver/provence-reranker-debertav3-v1`）を使用します。しきい値ベースのフィルタリングにより、関連性しきい値を超える文が保持されます。また、プルーニングが失敗した場合に元のコンテキストに戻るフォールバックメカニズムも導入します。最後に、統計ログが詳細モードで削減率を追跡します。</p><h3>コンテキストの要約：古い情報を完全に削除するのではなく、要約する</h3><p><em>要約</em>はトリミングの相棒です。履歴やナレッジベースが大きくなりすぎた場合は、LLMを使用して重要なポイントの簡単な要約を作成し、上記のコードで実行したように、今後のコンテンツ全体の代わりにその要約を使用できます。</p><p>例えば、AIアシスタントが50ターンの会話を行った場合、51ターン目に50ターンすべてをモデルに送信するのではなく（おそらく収まりません）、システムは1ターン目から40ターン目までを取得し、モデルにそれらを段落に要約させてから、次のプロンプトでその要約と最後の10ターンのみを提供する可能性があります。こうすることで、モデルはすべての詳細を必要とせずに議論内容を把握できます。初期のチャットボットのユーザーは、「これまでの話を要約してくれますか？」と尋ね、要約した内容を新しいセッションで継続することで、これを手動で行っていました。今ではこれを自動化できます。要約はコンテキストウィンドウのスペースを節約するだけでなく、余分な詳細を削除して重要な事実だけを保持することで<strong>コンテキストの逸脱や混乱を</strong>減らすこともできます。</p><p>OpenAIのモデル（どんなLLMでも使用可能）を使って、すべての関連情報を保持しながらコンテキストを凝縮し、冗長性や重複を排除する方法を紹介します。<br/></p><pre><code># Context summarization
def summarize_context(query: str, context: str) -> str:
    """
    Summarize context using LLM to reduce duplication and focus on relevant information
    
    Args:
        query: User's query/question
        context: Context to summarize
        
    Returns:
        Summarized context
    """
    try:
        summary_prompt = f"""You are an expert at summarizing conversation context.

Your task: Analyze the provided conversation context and produce a condensed summary that fully answers or supports the user's specific question.

The summary must:
1. Preserve every fact, detail, and information that directly relates to the question
2. Eliminate redundancy and duplicate information
3. Maintain chronological flow when relevant
4. Focus on information that helps answer: "{query}"

Context to summarize:
{context}

Provide a concise summary that preserves all relevant information:"""

        summary = llm.invoke(summary_prompt).content
        
        if args.verbose:
            original_length = len(context)
            summary_length = len(summary)
            reduction_pct = ((original_length - summary_length) / original_length * 100) if original_length > 0 else 0
            rich.print(f"[cyan]📝 Summarization stats: {summary_length}/{original_length} chars ({reduction_pct:.1f}% reduction)[/cyan]")
        
        return summary
        
    except Exception as e:
        rich.print(f"[yellow]⚠️ Error in context summarization: {str(e)}[/yellow]")
        rich.print(f"[yellow]⚠️ Falling back to original context[/yellow]")
        return context</code></pre><p>重要なのは、コンテキストを要約すると、モデルが些細な詳細や過去のエラーに圧倒されにくくなることです（要約が正確であれば）。 </p><p>しかし、要約は慎重に行わなければなりません。悪い要約は重要な部分を省略したり、エラーを生じさせたりすることがあります。これは本質的にはモデルに対する別のプロンプト（「これを要約してください」）なので、ハルシネーションを起こしたり、ニュアンスが失われたりする可能性があります。ベストプラクティスとしては、段階的に要約し、一部の標準的な事実は要約しないままにしておくことが挙げられます。</p><p>それでも非常に有用であることが証明されています。<a href="https://storage.googleapis.com/deepmind-media/gemini/gemini_v2_5_report.pdf">Geminiエージェントのシナリオでは、</a>約10万トークンごとにコンテキストを要約することが、モデルの繰り返し傾向に対抗する方法でした。要約は会話やデータの圧縮された記憶のように機能します。開発者としては、エージェントに会話履歴や長いドキュメントの要約機能（おそらく小さなLLMまたは専用ルーティン）を定期的に呼び出させることでこれを実装できます。結果の要約はプロンプト内の元のコンテンツに置き換えられます。この戦術は、コンテキストを制限内に保ち、情報を精査するために広く使用されています。</p><h3>コンテキストの隔離：可能な限りコンテキストを隔離する</h3><p>これは複雑なエージェントシステムや多段階のワークフローでより重要です。コンテキストセグメンテーションの考え方は、大きなタスクを、それぞれ独自のコンテキストを持つ小さな独立したタスクに分割し、すべてが含まれる1つの巨大なコンテキストが蓄積されないようにすることです。各サブエージェントまたはサブタスクは、焦点を絞ったコンテキストで問題の一部に取り組み、その後、上位レベルのエージェント、スーパーバイザー、またはコーディネーターが結果を統合します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f2de68c3339883d7658e633af3948f29f427e6cf-1600x900.png" style="width: 100%;" /><p><a href="https://www.anthropic.com/engineering/multi-agent-research-system">Anthropicの研究戦略はそれぞれが異なる質問の側面を調査し、それぞれのコンテキストウィンドウを持つ複数のサブエージェント</a>と、そのサブエージェントから抽出された結果を読み取るリードエージェントを用います。この並列のモジュール方式のアプローチにより、単一のコンテキストウィンドウが肥大化することはありません。また、無関係な情報が混ざる可能性も減り、各スレッドはトピックに沿って進み（コンテキストの混乱がなく）、特定のサブ質問に答えるときに不必要な負担がかかりません。ある意味、それは思考プロセス全体ではなく、結果だけを共有する別々の思考スレッドを実行するようなものです。</p><p>マルチエージェントシステムでは、このアプローチは不可欠です。エージェントAがタスクAを処理し、エージェントBがタスクBを処理する場合、本当に必要な場合を除き、どちらのエージェントも他方のエージェントの完全なコンテキストを使用する理由はありません。代わりに、エージェントは必要な情報だけを交換できます。例えば、エージェントAは、その調査結果の統合された要約をスーパーバイザーエージェントを介してエージェントBに渡すことができますが、各サブエージェントは独自の専用コンテキストスレッドを維持します。この設定では、人間による介入は必要ありません。最小限かつ制御されたコンテキスト共有を備えたツールが有効になっているスーパーバイザーエージェントに依存します。</p><p>にもかかわらず、エージェントやツールが最小限の必要なコンテキストオーバーラップで動作するようにシステムを設計することで、明確さとパフォーマンスを大幅に向上させることができます。これを<strong>AI用のマイクロサービス</strong>と考えると、各コンポーネントがそれぞれのコンテキストを処理し、1 つのモノリシックなコンテキストではなく、制御された方法でコンポーネント間でメッセージを渡すことができます。これらのベストプラクティスは、多くの場合、組み合わせて使用されます。また、これにより、些細な履歴をトリミングしたり、重要な古いメッセージや会話を要約したり、長期的なコンテキストのために詳細なログをElasticsearchにオフロードしたり、必要なときに関連するものを取得して戻したりする柔軟性が得られます。</p><p><a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents#:~:text=While%20some%20models,to%20the%20LLM">ここで</a>述べたように、コンテキストは限られた貴重なリソースであるというのが基本原則です。プロンプト内のすべてのトークンがその価値を生むようにし、出力の品質に貢献させる必要があります。メモリ内の何かが役に立たない場合（さらに悪いことに、積極的に混乱を引き起こしている場合も）、そのメモリは削除、要約、削除する必要があります。</p><p>開発者として、現在の私たちは、コードをプログラムするのと同じようにコンテキストをプログラムし、含める情報、そのフォーマット方法、および省略または更新するタイミングを決定できます。これらのプラクティスに従うことで、LLMエージェントに、前述の障害モードの被害に遭うことなくタスクを実行するために必要なコンテキストを提供できます。その結果、エージェントは必要なことを記憶し、不要なことを忘れ、必要な情報を必要なタイミングで取得できるようになります。</p><h2>まとめ</h2><p>メモリはエージェントに追加するものではなく、設計するものです。短期メモリはエージェントの一時的な作業領域であり、長期メモリはその永続的な知識格納場所です。RAGは2つの間の架け橋であり、Elasticsearchのようなパッシブデータストアを、出力を接地してエージェントを最新の状態に保つことができるアクティブリコールメカニズムに変えます。</p><p>しかし、メモリは両刃の剣です。コンテキストを制御せずに放置すると、ポイズニング、逸脱、混乱、衝突を引き起こし、共有システムではデータ漏洩さえも招く可能性があります。だからこそ、メモリに関する最も重要な作業は「より多くを格納する」のではなく、「よりよくキュレーションする」ことです。選択的に取り出し、積極的にプルーニングし、慎重に要約し、タスクが真に要求する場合を除いて、無関係なコンテキストを混ぜ合わせることを避けます。</p><p>実際には、優れたコンテキストエンジニアリングは、優れたシステム設計のように見えます。具体的には、コンテキストが小さく十分であり、コンポーネント間のインターフェイスが制御され、モデルに実際に表示したい生の状態と精製された状態が明確に区別された状態です。適切に実行すれば、すべてを記憶するエージェントではなく、適切な理由で適切なタイミングで適切なことを記憶するエージェントが得られます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[高いスループットと低いレイテンシ：AWS上のElastic Cloud Serverlessがパフォーマンスを大幅に向上]]></title>
            <description><![CDATA[Elasticsearch ServerlessのAWSインフラを、より新しい、より高速なハードウェアにアップグレードしました。この大幅なパフォーマンス向上によって、クエリの高速化、スケーリングの向上、コストの削減がどのように実現されるかを学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-serverless-aws-performance-boost</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-serverless-aws-performance-boost</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Pete Galeotti, Yuvraj Gupta, Rachel Forshee]]></dc:creator>
            <pubDate>Wed, 14 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3aa0b481994d2445ba979d3c79fff64c5ee6676a-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elastic Cloud Serverlessは、インフラを管理する運用上の負担なしに、効率的な検索・AIアプリケーションを構築したい開発者にとって、すでに決定的なソリューションとなっています。現在、私たちはサーバーレスプロジェクトのパフォーマンスをまったく新しいレベルに引き上げています。</p><p>AWSで稼働するすべての<a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a>プロジェクトに対して、主要なインフラのアップグレードを完了し、より新しく高速なハードウェアへの移行を行いました。この変更は、すべてのサーバーレスプロジェクトに自動的に適用されました。AWS上のElasticsearch、Elastic Observability、Elastic Securityのサーバーレスプロジェクトにおいて、<strong>より高いスループットと低いレイテンシを</strong>実現します。</p><h2><strong>開発者にとっての主なパフォーマンス上のメリット</strong></h2><p>新しいAWSハードウェアインフラは、Elastic Cloud Serverlessで行われるすべての作業の基盤となり、アプリケーションの速度と応答性に目に見えるメリットをもたらします。</p><h3><strong>クエリのレイテンシの短縮…スループットの向上</strong></h3><p>ハードウェアの改良によりコンピューティングリソースの速度が劇的に向上し、検索クエリがこれまで以上に高速に処理されるようになります。</p><ul><li><strong>検索とベクトル検索：</strong>従来の全文クエリを実行している場合でも、最先端のベクトル検索を使用して<a href="https://www.elastic.co/generative-ai">生成AIと検索拡張生成（RAG）アプリケーション</a>を実行している場合でも、レイテンシが大幅に減少します。内部ベンチマーキングでは、検索レイテンシが平均35%減少したことが示されました。</li><li><strong>より高速なインデキシング：</strong>データのインジェスト速度が最適化されているため、膨大なデータ量や複雑なドキュメントのインデキシングがスループット向上とともに可能になります。これは、ほぼリアルタイムのデータ可視性を必要とするアプリケーションにとって非常に重要です。内部ベンチマークではインデキシングスループットの平均26%増加が示されました。</li></ul><h3><strong>負荷下でも安定したパフォーマンス</strong></h3><p>Elastic Cloud Serverlessは、ワークロードに関係なく、需要に合わせてリアルタイムで動的に自動スケーリングし、レイテンシを最小限に抑えるように設計されています。このハードウェアのアップグレードにより、スケーリングのパフォーマンスと応答性が向上しました。</p><ul><li><strong>スパイクを容易に処理：</strong>ユーザートラフィックの突然の急増や大量のバッチデータ取り込みに直面している場合でも、新しいインフラにより、検索とインデキシングのリソースがより効率的にスケールアップし、一貫して低いレイテンシが維持されます。</li><li><strong>最適化されたコンピューティングとストレージの分離：</strong>サーバーレスアーキテクチャはコンピューティングとストレージを分離し、ワークロードを個別にスケールして、最適なパフォーマンスとコスト効率を実現します。より高速なハードウェアによりコンピューティング層が強化され、この分離設計の効率が最大化されます。</li></ul><h2><strong>舞台裏：内部のベンチマーク結果</strong></h2><p>AWSインフラのアップグレードの影響を定量化するため、Elasticのエンジニアリングチームは、さまざまなサーバーレスワークロードに対して包括的な社内ベンチマークを実施しました。これらのワークロードは、ユースケースに関係なく、アプリケーション全体で期待できるパフォーマンスの改善に関する実証的な証拠を提供しました。</p><h3><strong>ベンチマーキングのアプローチ</strong></h3><p>私たちは、開発者エクスペリエンスとアプリケーションの応答性に直接影響する主要なメトリクス、応答時間（つまり、レイテンシ）と検索およびインデキシング操作のスループットにテストを集中させました。</p><ul><li><strong>テスト対象のワークロード：</strong>テストには、ユーザー向けアプリケーションに典型的な高同時検索操作、複雑なベクトル検索クエリ、オブザーバビリティとセキュリティのユースケースのための大量データのインジェスト/インデキシングが含まれていました。特に、私たちのテスト手法では、ElasticのベンチマーキングツールであるRallyの<a href="https://github.com/elastic/rally-tracks/tree/master">公開データセット</a>を使用しました。<ul><li><a href="https://github.com/elastic/rally-tracks/tree/3bedd51/wikipedia"><code>wikipedia</code></a>: 汎用テキスト検索のパフォーマンスを測定するためにWikipediaのテキストコンテンツのスナップショットから生成されたデータセット。</li><li><a href="https://github.com/elastic/rally-tracks/tree/3bedd51/msmarco-passage-ranking"><code>MSMARCO-Passage-Ranking</code></a>：低密度ベクトルフィールドの検索パフォーマンスを測定するためのMicrosoftのMachine Reading Comprehension (MS MARCO) から派生したデータセット。</li><li><a href="https://github.com/elastic/rally-tracks/tree/3bedd51/openai_vector"><code>OpenAI_Vector</code></a>：高密度ベクトルフィールドの検索パフォーマンスを測定するための、BEIRのNQから派生し、OpenAIの<code>text-embedding-ada-002</code>モデルによって生成された埋め込みで強化されたデータセット。</li></ul></li><li><strong>測定：</strong>旧インフラと新インフラのパフォーマンスを比較し、最悪ケースのテールレイテンシを99パーセンタイル（P99）で測定し、操作回数を1秒あたりで計測しました。結果の一貫性を確保するために、各トラックはハードウェアプロファイルごとに5回実行されました。</li><li><strong>目標：</strong>私たちの目的は、インフラストラクチャーが、急速な自動スケーリングの期間中でも、一貫して<strong>より速く、より予測可能なパフォーマンス</strong>を提供する能力を検証することでした。</li></ul><h3><strong>パフォーマンスデータの概要</strong></h3><p>結果では、効率と速度が大幅に向上したことが確認されました。これらの利点は、ユーザーの応答時間の短縮や、より少ないコンピュートリソースで同じ量の作業を完了できることによる運用コストの削減に直結します。</p><p>以下の表は、定量的な改善点の詳細です。スループット値は高いほど好ましく、レイテンシは値が低いほど好ましいです。</p><p><strong>検索ベンチマーク結果：</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ベンチマーク</td><td style="border: 1px solid #ccc; padding: 4px;">比較</td><td style="border: 1px solid #ccc; padding: 4px;">旧インフラ</td><td style="border: 1px solid #ccc; padding: 4px;">新しいインフラ</td><td style="border: 1px solid #ccc; padding: 4px;">差</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`wikipedia`（プレーンテキスト）</td><td style="border: 1px solid #ccc; padding: 4px;">検索操作のスループット（ops/s）</td><td style="border: 1px solid #ccc; padding: 4px;">729</td><td style="border: 1px solid #ccc; padding: 4px;">1107</td><td style="border: 1px solid #ccc; padding: 4px;">＋52％</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`wikipedia`（プレーンテキスト）</td><td style="border: 1px solid #ccc; padding: 4px;">検索操作のレイテンシ（p99、ミリ秒）</td><td style="border: 1px solid #ccc; padding: 4px;">56</td><td style="border: 1px solid #ccc; padding: 4px;">35</td><td style="border: 1px solid #ccc; padding: 4px;">-37%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`MSMARCO-Passage-Ranking`（低密度ベクトル）</td><td style="border: 1px solid #ccc; padding: 4px;">検索操作のスループット（ops/s）</td><td style="border: 1px solid #ccc; padding: 4px;">22</td><td style="border: 1px solid #ccc; padding: 4px;">31</td><td style="border: 1px solid #ccc; padding: 4px;">+40％</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`MSMARCO-Passage-Ranking`（低密度ベクトル）</td><td style="border: 1px solid #ccc; padding: 4px;">検索操作のレイテンシ（p99、ミリ秒）</td><td style="border: 1px solid #ccc; padding: 4px;">108</td><td style="border: 1px solid #ccc; padding: 4px;">67</td><td style="border: 1px solid #ccc; padding: 4px;">-38%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`OpenAI_Vector`（高密度ベクトル）</td><td style="border: 1px solid #ccc; padding: 4px;">検索操作のスループット（ops/s）</td><td style="border: 1px solid #ccc; padding: 4px;">475</td><td style="border: 1px solid #ccc; padding: 4px;">624</td><td style="border: 1px solid #ccc; padding: 4px;">+31%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`OpenAI_Vector`（高密度ベクトル）</td><td style="border: 1px solid #ccc; padding: 4px;">検索操作のレイテンシ（p99、ミリ秒）</td><td style="border: 1px solid #ccc; padding: 4px;">35</td><td style="border: 1px solid #ccc; padding: 4px;">22</td><td style="border: 1px solid #ccc; padding: 4px;">-37%</td>
                    </tr>
              </tbody>
            </table>
          <p><strong>インデキシングベンチマークの結果：</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ベンチマーク</td><td style="border: 1px solid #ccc; padding: 4px;">比較</td><td style="border: 1px solid #ccc; padding: 4px;">旧インフラ</td><td style="border: 1px solid #ccc; padding: 4px;">新しいインフラ</td><td style="border: 1px solid #ccc; padding: 4px;">差</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`wikipedia`（プレーンテキスト）</td><td style="border: 1px solid #ccc; padding: 4px;">検索操作のスループット（ops/s）</td><td style="border: 1px solid #ccc; padding: 4px;">2845</td><td style="border: 1px solid #ccc; padding: 4px;">3220</td><td style="border: 1px solid #ccc; padding: 4px;">+13%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`wikipedia`（プレーンテキスト）</td><td style="border: 1px solid #ccc; padding: 4px;">検索操作のレイテンシ（p99、ミリ秒）</td><td style="border: 1px solid #ccc; padding: 4px;">1769</td><td style="border: 1px solid #ccc; padding: 4px;">1120</td><td style="border: 1px solid #ccc; padding: 4px;">-37%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`MSMARCO-Passage-Ranking`（低密度ベクトル）</td><td style="border: 1px solid #ccc; padding: 4px;">検索操作のスループット（ops/s）</td><td style="border: 1px solid #ccc; padding: 4px;">7087</td><td style="border: 1px solid #ccc; padding: 4px;">8900</td><td style="border: 1px solid #ccc; padding: 4px;">+26%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`MSMARCO-Passage-Ranking`（低密度ベクトル）</td><td style="border: 1px solid #ccc; padding: 4px;">検索操作のレイテンシ（p99、ミリ秒）</td><td style="border: 1px solid #ccc; padding: 4px;">824</td><td style="border: 1px solid #ccc; padding: 4px;">677</td><td style="border: 1px solid #ccc; padding: 4px;">-18%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`OpenAI_Vector`（高密度ベクトル）</td><td style="border: 1px solid #ccc; padding: 4px;">検索操作のスループット（ops/s）</td><td style="border: 1px solid #ccc; padding: 4px;">2972</td><td style="border: 1px solid #ccc; padding: 4px;">3187</td><td style="border: 1px solid #ccc; padding: 4px;">+7%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">`OpenAI_Vector`（高密度ベクトル）</td><td style="border: 1px solid #ccc; padding: 4px;">検索操作のレイテンシ（p99、ミリ秒）</td><td style="border: 1px solid #ccc; padding: 4px;">2946</td><td style="border: 1px solid #ccc; padding: 4px;">2944</td><td style="border: 1px solid #ccc; padding: 4px;">0%</td>
                    </tr>
              </tbody>
            </table>
          <h2><strong>追加のボーナス：コスト削減</strong></h2><p>私たちは低レイテンシのパフォーマンスを提供することに重点を置いていますが、新しいハードウェアの効率性もElasticsearchプロジェクトのコストに直接的なプラスの影響を与えます。</p><p><a href="https://www.elastic.co/pricing/serverless-search">Elasticsearch Serverlessの価格設定</a>は使用量ベースで、消費した取り込みと検索リソースに対してのみ料金が発生します。新しく高速なハードウェアはより効率的であるため、ワークロードはより少ないリソースを使用してタスクを完了することが多くなり、ほとんどのプロジェクトで本質的なコスト削減につながります。高額な費用をかけずに、最高のパフォーマンス向上を実現できます。まさに効率の最適化です。</p><h2><strong>開発者にとっての意義</strong></h2><p>このインフラストラクチャーのアップグレードはElasticによって完全に管理されるため、移行や構成の変更を行う必要はありません。改善は、AWSベースのすべてのサーバーレスプロジェクトで即座かつ自動的に行われます。</p><p>このアップグレードにより、次のことが可能になります。</p><ul><li><strong>より高速なアプリケーションを構築：</strong>基盤となる検索プラットフォームがユーザーが求める速度を提供していることを認識しながら、機能の速度に重点を置けます。</li><li><strong>自信を持ってイノベーションを実現：</strong>プラットフォームが最高のパフォーマンスで負荷を処理できることを保証しながら、ベクトル検索や関連性ランキングなどの複雑なAI機能を含む新しい検索、オブザーバビリティ、セキュリティの機能をデプロイします。</li><li><strong>スタックを簡素化：</strong>インフラ管理、容量計画、スケーリングを処理する完全に管理されたサービスを使用することで、コードとデータに集中できます。<br/></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Jinaモデル、その機能とElasticsearchでの使用方法の紹介]]></title>
            <description><![CDATA[Jinaのマルチモーダル埋め込み、リランカーv3、セマンティック埋め込みモデル、さらにそれらをElasticsearchでネイティブに使用する方法を探ります。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/jina-models-elasticsearch-guide</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/jina-models-elasticsearch-guide</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Scott Martens]]></dc:creator>
            <pubDate>Thu, 01 Jan 2026 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/407b4c862b51ebdfc7f26db4e25950a65caf1673-656x442.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Jina by Elasticは、アプリケーションとビジネスプロセスの自動化のための検索基盤モデルを提供します。これらのモデルは、Elasticsearchアプリケーションや革新的なAIプロジェクトにAIを導入するためのコア機能を提供します。</p><p>Jinaモデルは、情報処理、整理、検索をサポートするように設計された大きく3つのカテゴリーに分類されます。</p><ul><li>セマンティック埋め込みモデル</li><li>リランキングモデル</li><li>小規模な生成言語モデル</li></ul><h2>セマンティック埋め込みモデル</h2><p>セマンティック埋め込みの背後にある考え方は、AIモデルがインプットの意味的側面を高次元空間の幾何学の観点から表現することを学習できるというものです。</p><p>セマンティック埋め込みは、高次元空間内の点（技術的には<em>ベクトル</em>）と考えることができます。埋め込みモデルは、ニューラルネットワークの一種で、デジタルデータ（テキストや画像など、あらゆるものが入力となりえますが、最も一般的なのはテキストや画像）を入力として受け取り、対応する高次元点の位置を一連の数値座標として出力します。モデルが適切に機能している場合、2つのセマンティック埋め込み間の距離は、対応するデジタルオブジェクトがどの程度同じ意味を持つかに比例します。</p><p>これが検索アプリケーションにとっていかに重要であるかを理解するには、「dog」という単語の埋め込みと「cat」という単語の埋め込みを空間上の点として想像してみましょう。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/802cf9bbcb82180d3fc91009f9f62027eee8f031-615x615.png" style="width: 100%;" /><p>優れた埋め込みモデルは、「feline」という単語に対して「dog」よりも「cat」にずっと近い埋め込みを生成し、「canine」は「cat」よりも「dog」にずっと近い埋め込みを生成するはずです。なぜなら、これらの単語はほぼ同じ意味だからです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bce49daf9a31b8fb7ce1c6ef7ae4e8117a4e8b33-615x615.png" style="width: 100%;" /><p>モデルが多言語対応であれば、「cat」と「dog」の翻訳でも同じ結果が期待できます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ce4d030385324526cbd7539140e0e634d939371c-615x615.png" style="width: 100%;" /><p>埋め込みモデルは、物事間の意味の類似性や不一致を埋め込み間の空間的関係に翻訳します。上の図は2次元のみであるため、画面上で確認できますが、埋め込みモデルでは数十から数千の次元のベクトルが生成されます。これにより、数千語以上を含む文書に対して、何百または何千もの次元を持つ空間上の点を割り当てることで、全体のテキストの意味の微妙なニュアンスをエンコードすることが可能になります。</p><h2>マルチモーダル埋め込み</h2><p>マルチモーダルモデルは、セマンティック埋め込みの概念をテキスト以外のもの、特に画像にも拡張します。画像の埋め込みは、その画像の忠実な記述の埋め込みに近いものとなることが期待されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1ac6aef5b1423e5fe4853e8a547a74e66b0885c2-615x615.png" style="width: 100%;" /><p>セマンティック埋め込みには多くの用途があります。とりわけ、効率的な分類器の構築、データのクラスタリング、データの重複排除やデータの多様性の調査などのさまざまなタスクの実行に使用できます。いずれも、手作業では管理できないほど大量のデータを扱うビッグデータアプリケーションにとって重要です。</p><p>埋め込みの最大の直接的な利用は情報検索です。Elasticsearchでは、埋め込みを含む検索オブジェクトをキーとして格納できます。クエリは埋め込みベクトルに変換され、検索によって埋め込みに最も近いキーを持つ格納オブジェクトが返されます。</p><p>従来の<em>ベクトルベースの検索</em>（<em>低密度ベクトル検索</em>とも呼称）が、ドキュメントやクエリの単語やメタデータに基づくベクトルを使用するのに対し、<em>埋め込みベースの検索</em>（<em>高密度ベクトル検索</em>とも呼称）は、単語ではなくAIによって評価された意味を使用します。これにより、一般に従来の検索方法よりもはるかに柔軟かつ正確になります。</p><h2>マトリョーシカ表現学習</h2><p>埋め込みの次元数や数値の精度はパフォーマンスに大きな影響を与えます。空間が非常に高次元で数値が非常に高精度な場合、非常に詳細で複雑な情報を表すことができますが、トレーニングと実行に費用がかかる、より大規模なAIモデルが必要となります。生成されるベクトルはより多くのストレージ容量を必要とし、距離を計算するのにより多くの計算サイクルが必要です。セマンティック埋め込みモデルを使用するには、精度とリソース消費の間で重要なトレードオフを行う必要があります。</p><p>ユーザーの柔軟性を最大化するために、Jinaモデルは<a href="https://arxiv.org/abs/2205.13147">マトリョーシカ表現学習</a>と呼ばれる技術で訓練されています。これにより、モデルは最も重要な意味的区別を埋め込みベクトルの最初の次元に前もってロードするため、より高い次元を切り捨てても良好なパフォーマンスを得ることができます。</p><p>実際には、これはJinaモデルのユーザーが埋め込みの次元数を選択できることを意味します。次元を少なく選択すると精度は低下しますが、パフォーマンスの低下は軽微です。ほとんどのタスクで、Jinaモデルのパフォーマンス指標は、埋め込みサイズを50％縮小するたびに1〜2％低下し、サイズが約95％小さくなります。</p><h2>非対称検索</h2><p>意味的類似性は通常、対称的に測定されます。「cat」と「dog」を比較したときに得られる値は、「dog」と「cat」を比較したときに得られる値と同じです。しかし、情報検索に埋め込みを使用する場合、対称性を破り、検索オブジェクトをエンコードする方法とは異なる方法でクエリをエンコードすると、埋め込みがより効果的に機能します。</p><p>これは、埋め込みモデルをトレーニングする方法によるものです。トレーニングデータには、単語のような同じ要素が多くの異なるコンテキストでインスタンスとして含まれており、モデルは要素間のコンテキスト上の類似点と相違点を比較することで意味論を学習します。</p><p>例えば、「animal」という単語は、「cat」や「dog」と同じ文脈にはあまり出てこないので、「animal」の埋め込みは「cat」や「dog」に特に近いわけではない可能性があります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9a33163405af6c71ee7f4ba8ebc86af39e295a69-615x615.png" style="width: 100%;" /><p>これにより、「animal」のクエリで猫や犬に関するドキュメントが検索される可能性が低くなります。これは目標とは逆の結果となります。そのため、代わりに、クエリの場合と検索のターゲットの場合で「animal」を異なる方法でエンコードします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/363992d4f0affba7937c0c8a9f82c9a531fcd3ba-615x615.png" style="width: 100%;" /><p><em>非対称検索</em>とは、クエリに異なるモデルを使用したり、埋め込みモデルを特別に訓練して、検索のために格納する際に一方向にエンコードし、クエリを別の方向にエンコードすることを意味します。</p><h2>マルチベクトル埋め込み</h2><p>単一の埋め込みは、インデックス付きデータベースの基本的なフレームワークに適合するため、情報検索に適しています。検索キーとして単一の埋め込みベクトルを使用して、検索用のオブジェクトを格納します。ユーザーがドキュメントストアをクエリする際、そのクエリは埋め込みベクトルに変換され、そのキーが（高次元の埋め込み空間において）クエリ埋め込みに最も近いドキュメントが一致候補として取得されます。</p><p>マルチベクトル埋め込みの動作は少し異なります。クエリと格納されたオブジェクト全体を示す固定長のベクトルを生成する代わりに、それらの小さな部分を表す埋め込みのシーケンスを生成します。これらの部分は通常、テキストの場合はトークンまたは単語、視覚データの場合は画像タイルです。これらの埋め込みは、その文脈における部分の意味を反映しています。</p><p>例えば、次の文を考えてみましょう。</p><ul><li>She had a heart of gold（彼女は心優しい人でした）.</li><li>She had a change of heart（彼女は心変わりしたのです）.</li><li>彼女は心臓発作を起こしました。</li></ul><p>表面的には非常によく似ているように見えますが、マルチベクトルモデルでは「heart」の各インスタンスに対して非常に異なる埋め込みが生成され、文全体の文脈の中でそれぞれが別の意味を持つことが示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a33e60c8d8ee3d312bca8375ca2a8b0a0cd40ba9-615x615.png" style="width: 100%;" /><p>2つのオブジェクトのマルチベクトル埋め込みを比較する場合、多くの場合、面取り距離の測定が必要になります。つまり、1 つのマルチベクトル埋め込みの各部分を別のマルチベクトル埋め込みの各部分と比較し、それらの間の最小距離を合計します。以下に説明するJinaリランカーを含む他のシステムでは、類似性を評価するために特別にトレーニングされたAIモデルにそれらを入力します。マルチベクトル埋め込みには単一ベクトル埋め込みよりもはるかに詳細な情報が含まれているため、通常、両方のアプローチは単一ベクトル埋め込みを単純に比較するよりも精度が高くなります。</p><p>しかし、マルチベクトル埋め込みはインデキシングにはあまり適していません。次のセクションの<code>jina-colbert-v2</code>モデルで説明するように、これらはタスクのリランキングによく使用されます。</p><h2>Jina埋め込みモデル</h2><h3>Jina埋め込みv4</h3><p><a href="https://jina.ai/news/jina-embeddings-v4-universal-embeddings-for-multimodal-multilingual-retrieval/"><strong>jina-embeddings-v4</strong></a>は、広く使用されているさまざまな言語の画像とテキストをサポートする、38億（3.8x10⁹）パラメーターの多言語およびマルチモーダル埋め込みモデルです。視覚的知識と言語的知識を活用する新しいアーキテクチャを使用して両方のタスクのパフォーマンスを向上させ、画像検索、特に<a href="https://huggingface.co/tasks/visual-document-retrieval">視覚的ドキュメント検索</a>で優れた性能を発揮します。これは、チャート、スライド、マップ、スクリーンショット、ページスキャン、ダイアグラムなどの画像を処理することを意味します。これらは一般的な種類の画像で、しばしば重要な埋め込まれたテキストが含まれており、実世界のシーンの画像で訓練されたコンピュータービジョンモデルの範囲外にあります。</p><p>コンパクトな<a href="https://huggingface.co/docs/peft/en/package_reference/lora">Low-Rank Adaptation（LoRA）アダプター</a>を使って、このモデルを複数の異なるタスクに最適化しました。これにより、メモリや処理の追加コストを最小限に抑えながら、いずれのタスクでもパフォーマンスを犠牲にすることなく、単一のモデルを複数のタスクに特化させることができます。</p><p>主な機能には以下のようなものがあります。</p><ul><li>ビジュアルドキュメント検索における最先端のパフォーマンス、および大規模モデルをはるかに凌駕する多言語テキストと通常の画像のパフォーマンス。</li><li>大きなインプットコンテキストサイズのサポート：32,768トークンは約80ページのダブルスペースの英語テキストに相当し、20メガピクセルは4,500 x 4,500ピクセルの画像に相当します。</li><li>最大2048次元から128次元まで、ユーザーが選択した埋め込みサイズ。経験的に、そのしきい線を下回るとパフォーマンスが劇的に低下することがわかりました。</li><li>単一埋め込みとマルチベクトル埋め込みの両方をサポートします。テキストの場合、マルチベクトル出力は、インプットトークンごとに1つの128次元埋め込みで構成されます。画像の場合、28x28ピクセルタイルごとに1つの128次元埋め込みを生成します。</li><li>目的のために特別に訓練された2つのLoRAアダプターによる非対称検索最適化。</li><li>意味的類似度計算に最適化されたLoRAアダプター。</li><li>プログラミング言語とITフレームワークへの特別なサポート。LoRAアダプターを介してのサポートも提供します。</li></ul><p>私たちは、幅広い一般的な検索、自然言語理解、AI分析タスクのための汎用的な多目的ツールとして<code>jina-embeddings-v4</code>を開発しました。機能を考えると比較的小規模なモデルですが、導入には依然としてかなりのリソースが必要であり、クラウドAPI経由または高ボリューム環境での使用に最適です。</p><h3>Jina embeddings v3</h3><p><a href="https://jina.ai/news/jina-embeddings-v3-a-frontier-multilingual-embedding-model/"><strong>jina-embeddings-v3</strong></a>は、6億未満のパラメーターを持つ、コンパクトで高性能な多言語のテキストのみの埋め込みモデルです。最大8192トークンのテキストインプットをサポートし、デフォルトの1024次元から64次元まで、ユーザーが選択したサイズの単一ベクトル埋め込みを出力します。</p><p>私たちは、情報検索や意味的類似性だけでなく、感情分析やコンテンツモデレーションなどの分類タスク、ニュースの集約や推奨などのクラスタリングタスクなど、さまざまなテキストタスク向けに<code>jina-embeddings-v3</code>をトレーニングしてきました。<code>jina-embeddings-v4</code>と同様に、このモデルは次の使用カテゴリーに特化したLoRAアダプターを提供します。</p><ul><li>非対称検索</li><li>意味的類似性</li><li>分類</li><li>クラスタリング</li></ul><p><code>jina-embeddings-v3</code> は <code>jina-embeddings-v4</code>よりもはるかに小さいモデルであり、インプットコンテキストのサイズが大幅に削減されていますが、操作にかかるコストは少なくなります。それにもかかわらず、テキストに関しては非常に競争力のあるパフォーマンスを有し、多くのユースケースにとってより良い選択肢です。</p><h3>Jinaコード埋め込み</h3><p>Jinaの専門的なコード埋め込みモデルである<a href="https://jina.ai/models/jina-code-embeddings-1.5b"><strong>jina-code-embeddings（0.5bおよび1.5b）</strong></a>は、15プログラミング方式とフレームワーク、さらにコンピューティングや情報技術に関連する英語のテキストをサポートしています。これらは、それぞれ5億 （0.5x10⁹）と15億（1.5x10⁹）のパラメーターを持つコンパクトなモデルです。どちらのモデルも、最大32,768トークンのインプットコンテキストサイズをサポートしており、ユーザーは出力の埋め込みサイズを選択（小さいモデルでは896から64次元、大きいモデルでは1536から128次元）できます。</p><p>これらのモデルは、LoRAアダプターではなく<a href="https://arxiv.org/abs/2101.00190">プレフィックスチューニング</a>を使用して、5つのタスク固有の特殊化のための非対称検索をサポートしています。</p><ul><li><strong>コードからコードへ。</strong>さまざまなプログラミング言語で同様のコードを取得できます。コードの調整、コードの重複排除、移植とリファクタリングのサポートに使用されます。</li><li><strong>自然言語からコードへ。</strong>自然言語クエリ、コメント、説明、ドキュメントに合わせたコードを取得します。</li><li><strong>コードから自然言語へ。</strong>コードをドキュメントまたはその他の自然言語テキストと一致させます。</li><li><strong>コード間の補完。</strong>既存のコードを完成させたり強化したりするために、関連するコードを提案します。</li><li><strong>技術的な内容のQ＆A。</strong>情報技術に関する質問に対する自然言語による回答を特定します。テクニカルサポートのユースケースに最適です。</li></ul><p>これらのモデルは、比較的小さい計算コストで、コンピューターのドキュメント作成やプログラミング資料に関連するタスクに優れたパフォーマンスを提供します。開発環境やコードアシスタントに統合するのに適しています。</p><h3>Jina ColBERT v2</h3><p><a href="https://jina.ai/models/jina-colbert-v2"><strong>jina-colbert-v2</strong></a>は、5億6000万のパラメーターを持つマルチベクトルテキスト埋め込みモデルです。多言語対応で、89言語の素材を使用してトレーニングされており、可変の埋め込みサイズと非対称検索をサポートしています。</p><p>前述のように、マルチベクトル埋め込みはインデックス作成にはあまり適していませんが、他の検索戦略の結果の精度を高めるのに非常に役立ちます。<code>jina-colbert-v2</code>を使用すると<strong>、</strong>マルチベクトル埋め込みを事前に計算し、それを使用してクエリ時に検索候補をリランキングすることができます。このアプローチは、次のセクションのリランキングモデルの1つを使用するほど正確ではありませんが、クエリや候補の一致ごとにAIモデル全体を呼び出すのではなく、格納されているマルチベクター埋め込みを比較するだけなので、はるかに効率的です。これは、リランキングモデルを使用する際の遅延や計算オーバーヘッドが大きすぎる、または比較する候補の数が多すぎるユースケースに最適です。</p><p>このモデルは、インプットトークンごとに埋め込みのシーケンスを出力し、ユーザーは128次元、96次元、または64次元の埋め込みのトークンを選択できます。候補テキストの一致は8,192トークンに制限されます。クエリは非対称にエンコードされるため、ユーザーはテキストがクエリか候補一致かを指定する必要があり、クエリは32トークンに制限する必要があります。</p><h3>Jina CLIP v2</h3><p><a href="https://jina.ai/news/jina-clip-v2-multilingual-multimodal-embeddings-for-text-and-images/"><strong>jina-clip-v2</strong></a>は、9億パラメーターのマルチモーダル埋め込みモデルであり、テキストが画像のコンテンツを説明する場合に、テキストと画像が近い埋め込みを生成するようにトレーニングされています。その主な用途は、テクスチャクエリに基づいて画像を取得することですが、テキストからテキストへの検索とテキストから画像への検索に別々のモデルを必要としないため、高性能のテキストのみのモデルでもあり、ユーザーのコスト削減に役立ちます。</p><p>このモデルは8,192トークンのテキストインプットコンテキストをサポートし、画像は埋め込みを生成する前に512x512ピクセルに拡大されます。</p><p>対照言語画像事前トレーニング（CLIP）アーキテクチャは、トレーニングと操作が簡単で、非常にコンパクトなモデルを作成できますが、基本的な制限がいくつかあります。あるメディアの知識を別のメディアでのパフォーマンスの向上に活用することはできません。あるメディアを利用して別のメディアのパフォーマンスを向上させることはできません。そのため、「dog」と「cat」という単語の意味はどちらも「car」よりも近いことはわかっていても、犬の写真と猫の写真はどちらも車の写真よりも関連性が高いことは必ずしもわかっていません。</p><p>また、これらは<em>モダリティギャップ</em>と呼ばれる問題も抱えています。これはつまり、犬に関するテキストの埋め込みは、犬の画像の埋め込みよりも、猫に関するテキストの埋め込みに近い可能性が高いということです。この制限のため、CLIPはテキストから画像への検索モデルとして、またはテキストのみのモデルとして使用し、1つのクエリ内でこれら2つを混在させないことをお勧めします。</p><h2>リランキングモデル</h2><p>リランキングモデルは、1つまたは複数の候補一致と、クエリをモデルへのインプットとして取り、それらを直接比較して、はるかに高い精度の一致を生成します。</p><p>原理的には、各クエリを保存されている各ドキュメントと比較することで、情報検索にリランキングを直接使用できますが、これは計算コストが非常に高く、最小のコレクション以外では実用的ではありません。そのため、リランカーは、埋め込みベースの検索やその他の検索アルゴリズムなど、他の手段によって見つかった候補一致の比較的短いリストを評価するために使用される傾向があります。リランキングモデルは、検索を実行するとクエリが異なるデータセットを持つ個別の検索システムに送信され、それぞれが異なる結果を返す可能性があるハイブリッド検索スキームやフェデレーション検索スキームに最適です。多様な結果を1つの高品質な結果に統合する場合に非常に効果的です。</p><p>埋め込みベースの検索は、保存されているすべてのデータの再インデックスや、結果に対するユーザーの期待の変更など、大きな負担を伴う可能性があります。既存の検索スキームにリランカーを追加することで、検索ソリューション全体を再構築することなく、AIの利点の多くを追加することができます。</p><h2>Jinaリランカーモデル</h2><h3>Jinaリランカーm0</h3><p><a href="https://jina.ai/models/jina-reranker-m0/"><strong>jina-reranker-m0</strong></a>は、24億（2.4x10⁹）パラメーターのマルチモーダルリランカーで、テキストクエリとテキストや画像からなる候補一致をサポートします。これはビジュアルドキュメント検索の主要モデルであり、PDF、テキストのスキャン、スクリーンショット、テキストなど半構造化情報を含むコンピュータ生成または修正された画像、加えてテキストドキュメントと画像からなる混合データの格納に理想的なソリューションです。</p><p>このモデルは、単一のクエリと候補一致を受け取り、スコアを返します。同じクエリを異なる候補で使用すると、スコアは比較可能となり、それらのランク付けに使用できます。クエリテキストや候補テキストや画像を含む最大10,240トークンのインプットサイズをサポートします。画像をカバーするために必要な28x28ピクセルのタイルはすべて、入力サイズを計算するためのトークンとしてカウントされます。</p><h3>Jinaリランカーv3</h3><p><a href="https://jina.ai/models/jina-reranker-v3/"><strong>jina-reranker-v3</strong></a>は、同等のサイズのモデルに対して最先端のパフォーマンスを備えた6億パラメーターのテキストリランカーです。<code>jina-reranker-m0</code>とは異なり、1つのクエリと最大64件の一致候補のリストを受け取り、ランキング順を返します。クエリとすべてのテキスト候補を含む131,000トークンの入力コンテキストがあります。</p><h3>Jinaリランカーv2</h3><p><a href="https://jina.ai/models/jina-reranker-v2"><strong>jina-reranker-v2-base-multilingual</strong></a>は非常にコンパクトで汎用的なリランカーで、関数呼び出しやSQLクエリをサポートする追加の機能を備えています。3億弱のパラメーターで、高速、効率的、正確な多言語テキストリランキングを提供し、テキストクエリにマッチするSQLテーブルと外部関数を選択するための追加サポートもあり、エージェント的なユースケースに適しています。</p><h2>小規模な生成言語モデル</h2><p>生成言語モデルは、OpenAIのChatGPT、Google Gemini、AnthropicのClaudeのように、テキストまたはマルチメディアのインプットを受け取り、テキスト出力で応答するモデルです。<em>大規模</em>言語モデル（LLM）と<em>小規模</em>言語モデル（SLM）を明確に区別する境界はありませんが、最先端のLLMを開発、運用、使用する際の実用的な問題はよく知られています。最もよく知られているものは一般公開されていないため、そのサイズを推定することしかできませんが、ChatGPT、Gemini、Claudeは1～3兆（1～3x10¹²）のパラメーター範囲にあると予想されます。</p><p>これらのモデルを実行することは、たとえ公開されたものであっても、従来のハードウェアの範囲をはるかに超えており、広大な並列アレイに配置された最先端のチップを必要とします。有料のAPIを使ってLLMにアクセスすることもできますが、これには大きなコストがかかり、レイテンシーも大きく、データ保護、デジタル主権、クラウドの本国送還などの要求と整合させるのは困難です。さらに、その規模のモデルのトレーニングとカスタマイズに関連するコストはかなりの額になる可能性があります。</p><p>その結果、最大規模のLLMのすべての機能は備えていないものの、特定の種類のタスクを低コストで同様に実行できる小規模モデルの開発に多大な研究が行われてきました。一般的に、企業は特定の問題に対処するためにソフトウェアをデプロイしますが、AIソフトウェアも同様で、LLMよりもSLMベースのソリューションの方が望ましい場合が多いのです。これらは通常、一般的なハードウェア上で実行でき、実行速度が速く、消費電力が少なく、カスタマイズがはるかに簡単です。</p><p>JinaのSLMサービスは、AIを実用的な検索ソリューションに最も効果的に組み込む方法に重点を置いて拡大しています。</p><h2>Jina SLM</h2><h3>ReaderLM v2</h3><p><a href="https://jina.ai/models/ReaderLM-v2"><strong>ReaderLM-v2</strong></a>は、ユーザーが提供したJSONスキーマや自然言語命令に基づいて、HTMLをMarkdownまたはJSONに変換する生成言語モデルです。</p><p>データの前処理と正規化はデジタルデータの優れた検索ソリューションを開発する上で不可欠な部分ですが、現実世界のデータ、特にウェブから得られる情報は混沌としていることが多く、単純な変換戦略では非常に脆弱になることがよくあります。代わりに、<code>ReaderLM-v2</code> はウェブページのDOMツリーダンプの混沌を理解し、有用な要素を堅牢に識別できるインテリジェントなAIモデルソリューションを提供します。</p><p>15億（1.5x10⁹）パラメーターを持つこのシステムは最先端のLLMよりも3桁コンパクトですが、この1つの狭義のタスクにおいては最先端のLLMと同等のパフォーマンスを発揮します。</p><h3>Jina VLM</h3><p><a href="https://jina.ai/models/jina-vlm"><strong>JINA-VLM</strong></a>は、画像に関する自然言語の質問に答えるために訓練された24億（2.4×10⁹）パラメーターの生成言語モデルです。視覚的ドキュメント分析、つまりスキャン、スクリーンショット、スライド、図表、類似の非自然画像データに関する質問に回答する機能を非常に強力にサポートしています。</p><p>例：</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/adfa1420d079ca4fd5582eef4349b1265b378e76-950x500.png" style="width: 100%;" /><p>画像内のテキストの読み取りにも非常に優れています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ea3956e7ad86f8e171841cab2c28c8b3498da1d4-1002x500.png" style="width: 100%;" /><p>しかし、<code>jina-vlm</code>の真に優れた点は、情報収集や人工画像の内容を理解することです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f68606f9d2d99e2cd616d4ff81db3574dc4e26a5-1020x700.png" style="width: 100%;" /><p>または：</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/297e85e7e78f296388a02301e1e08fed70827423-1000x500.png" style="width: 100%;" /><p><code>jina-vlm</code> 自動キャプション生成、製品の説明、画像の代替テキスト、視覚障害者向けのアクセシビリティ用途に最適です。また、検索拡張生成（RAG）システムが視覚情報を使用したり、AIエージェントが人間の助けを借りずに画像を処理したりする可能性も生まれます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elastic Agent BuilderとStrands Agents SDKの使用を開始]]></title>
            <description><![CDATA[Elastic Agent Builderでエージェントを作成する方法を学び、次にStrands Agents SDKで管理されたA2Aプロトコルを介してエージェントを使用する方法を学びましょう。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/agent-builder-a2a-strands-agents-guide</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/agent-builder-a2a-strands-agents-guide</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jonathan Simon]]></dc:creator>
            <pubDate>Mon, 15 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/31b6a5c1c30dacbb4d5e58d1c566071e7143a0c8-1600x879.gif?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>AIエージェントのアイデアをお持ちですか？おそらく、データを使って何かを行うことが関係しているでしょう。エージェントが有用なアクションを開始するには、決定を下す必要があり、正しい決定を下すには正しいデータが必要だからです。</p><p>Elastic Agent Builderは、データ接続型AIエージェントを簡単に構築できるようにします。このブログ記事でその方法を説明します。まず、Elasticに格納されているデータにアクセスするMCPツールを使ってエージェントを作成するのに必要なすべてのステップを見ていきましょう。次に、Strands Agents SDKとそのAgent2Agent（A2A）機能を使用してエージェントを操作します。<a href="https://strandsagents.com/">Strands Agents SDK</a>は、望む結果を得るために十分なコードでエージェント向けアプリを構築するマルチエージェントAI開発プラットフォームです。</p><p>AIエージェントを構築しましょう。このエージェントは、RPS+というゲームをプレイします。これは古典的な「じゃんけん」に追加のひねりを加えたもので、プレイヤーにいくつかの追加の選択肢を与えます。</p><h2>要件</h2><p>こちらのブログ記事の手順に従うために必要なものは次のとおりです。</p><ul><li>ローカルコンピューターで実行されているテキストエディター<ul><li>このブログ記事の例では<a href="https://code.visualstudio.com/download">Visual Studio Code</a>を使用します。</li></ul></li><li>ローカルコンピューターで実行されている<a href="https://www.python.org/downloads/">Python 3.10以上</a></li></ul><h2>Serverlessプロジェクトを作成する</h2><p>最初に必要なのは、Elastic Agentビルダーを含むElasticsearch Serverlessプロジェクトです。</p><p><a href="http://cloud.elastic.co/">cloud.elastic.co</a>に移動して新しいElasticsearch Serverlessプロジェクトを作成します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/31b6a5c1c30dacbb4d5e58d1c566071e7143a0c8-1600x879.gif" style="width: 100%;" /><h2>インデックスを作成してデータを追加する</h2><p>次に、Elasticsearchプロジェクトにデータを追加します。開発者ツールを開き、コマンドを実行して新しいインデックスを作成し、そこにデータを挿入します。トップレベルのナビゲーションメニューから「開発者向けツール」を選択します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f97d5af077504463155655a9e27c171a7f974f71-1600x879.jpg" style="width: 100%;" /><p>コピーして、以下のPUTコマンドを開発者向けツールコンソールのリクエストインプットエリアに貼り付けてください。この文は「game-docs」という名前のElasticsearchインデックスを作成します。</p><pre><code>PUT /game-docs
{
  "mappings": {
    "properties": {
      "title": { "type": "text" },
      "content": { 
        "type": "text"
      },
      "filename": { "type": "keyword" },
      "last_modified": { "type": "date" }
    }
  }
}</code></pre><p>開発者ツールのステートメントの右側に表示される <strong>[リクエストの送信]</strong> ボタンをクリックします。開発者向けツールの対応エリアに<em>game-docs</em>インデックスが作成されたことを確認する通知が表示されるはずです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/be0555a1930e4d4f58b7ed8b669c9b702532ed17-1600x880.jpg" style="width: 100%;" /><p><em>game-docs</em>という名前のインデックスは、作成中のゲームのデータを保存するのに最適な場所です。ゲームに必要なすべてのデータを含むこのインデックスに、<em>rps+-md</em>という名前のドキュメントを配置しましょう。次のPUTコマンドをコピーして、開発者向けツールコンソールに貼り付けます。</p><pre><code>PUT /game-docs/_doc/rps+-md
{
  "title": "Rock Paper Scissors +",
  "content": "
# Game Name
RPS+

# Starting Prompt
Let's play RPS+ !
---
What do you choose?

# Game Objects
1. Rock 🪨 👊
2. Paper 📜 🖐
3. Scissors ✄ ✌️
4. Light ☼ 👍
5. Dark Energy ☄ 🫱

# Judgement of Victory
* Rock beats Scissors
  * because rocks break scissors
* Paper beats Rock
  * because paper covers rock
* Scissors beat Paper
  * because scissors cut paper
* Rock beats Light
  * because you can build a rock structure to block out light
* Paper beats Light
  * because knowledge stored in files and paper books helps us understand light
* Light beats Dark Energy
  * because light enables humans to lighten up and laugh in the face of dark energy as it causes the eventual heat death of the universe
* Light beats Scissors
  * because light is needed to use scissors safely
* Dark Energy beats Rock
  * because dark energy rocks more than rocks. It rocks rocks and everything else in its expansion of the universe
* Dark Energy beats Paper
  * because humans, with their knowledge stored in files and paper books, can't explain dark energy 
* Scissors beat Dark Energy
  * because a human running with scissors is darker than dark energy

# Invalid Input
I was hoping for an worthy opponent
  - but alas it appears that time has past
  - but alas there's little time for your todo list when [todo:fix this] is so vast

# Cancel Game
The future belongs to the bold. Goodbye..
",
  "filename": "RPS+.md",
  "last_modified": "2025-11-25T12:00:00Z"
}</code></pre><p>ステートメントの横にある<strong> [リクエストの送信] </strong>ボタンをクリックして実行し、<em>rps+-md</em>ドキュメントをgame-docsのインデックスに追加してください。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3c01d8a4602de5c33337457591a388a4a4e3fad3-1600x879.jpg" style="width: 100%;" /><p>クエリを実行するためのデータが用意されているはずです。Agent Builderを使用すると、クエリはこれまで以上に簡単になります。</p><p>トップレベルのナビゲーションメニューから<strong> [エージェント]</strong> を選択します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/82dbd2e9a439cabd5a5eea3d0ce005b87df0c3ea-1600x879.jpg" style="width: 100%;" /><p>あとは、デフォルトのElastic AI Agentに「どんなデータがありますか？」と聞くだけです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f7a2f39c9d1486bdf02d9e88a732b540ac2e2cd1-1600x872.gif" style="width: 100%;" /><p>Elastic AI Agentはデータを評価し、保有するデータの簡潔な説明を返します。</p><h2>ツールを作成する</h2><p>さて、Elasticにデータがいくつか入ったので、それを活用してみましょう。Agent Builderには、エージェントが必要なデータにアクセスし、タスクに適したコンテキストを得られるように<a href="https://modelcontextprotocol.io/">MCP</a>ツールを作成するための組み込みサポートが含まれています。ゲームデータを取得できるシンプルなツールを作りましょう。</p><p>Agent Builderのアクションメニューをクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0e327c202674dda33bcc0e494d2b588fa8b32e4f-1600x879.png" style="width: 100%;" /><p>メニューオプションから <strong>[すべてのツールを表示]</strong> を選択します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1ebf58650e9fb56750d3f0b1700fab50b44f9bdf-1600x879.png" style="width: 100%;" /><p><strong>[+ 新しいツール] </strong>をクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6c03a7f28b99ac2d805f34f39948979893316a00-1600x879.png" style="width: 100%;" /><p><strong>ツール作成</strong>フォームで<a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql"><strong>ES|QL</strong></a>を選択します。<strong></strong>ツール<strong>タイプ</strong>として次の値を入力します。</p><p><strong>ツールID</strong>について：</p><pre><code>example.get_game_docs</code></pre><p><strong>説明</strong>について：</p><pre><code>Get RPS+ doc from Elasticsearch game-docs index.</code></pre><p><strong>構成</strong>については、以下のクエリを<strong>ES|QLクエリ</strong>テキスト領域にします。</p><pre><code>FROM game-docs | WHERE filename == "RPS+.md"</code></pre><p>完了した<strong>ツール作成</strong>フォームは次のようになります。ツールを作成するには、 <strong>[保存]</strong> をクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b6c93e344600f319b9d2c3030020cf2d171ac1c4-1600x1312.png" style="width: 100%;" /><p>ツールラックに新しいツールが追加されました。ツールはラックに掛けておけばよいというものではなく、有効に活用されるべきものです。新しいカスタムツールを使用できるエージェントを作成しましょう。</p><h2>エージェントを作成し、ツールを割り当てます。</h2><p>Agent Builderを使えば、エージェントの作成は驚くほど簡単です。いくつかの詳細を記載したエージェントの指示を入力するだけで十分です。それではエージェントを作成しましょう。</p><p><strong>[エージェントを管理]</strong> をクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/53934b93db07187e251d4b321cb9ca647e2fd51b-1600x858.png" style="width: 100%;" /><p><strong>[+ 新しいエージェント] </strong>をクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fae3ad8f31e71a6dfd044e1daa025a4e280b4e68-1600x490.png" style="width: 100%;" /><p><strong>新しいエージェント</strong>フォームに次の情報を入力します。</p><p><strong>エージェントID</strong>には以下のテキストを入力します。</p><pre><code>rps_plus_agent</code></pre><p><strong>カスタム指示</strong>テキスト領域には次の指示を入力します。</p><pre><code>When prompted, if the prompt contains an integer, then select the corresponding numbered item in the list of "Game Objects" from your documents. Otherwise select a random game object. This is your chosen game object for a single round of the game.

# General Game Rules
* 2 players
    - the user: the person playing the game
    - you: the agent playing the game and serving as the game master
* Each player chooses a game object which will be compared and cause them to tie, win or lose.

# Start the game
1. This is the way each new game always starts. You make the first line of your response only the name of your chosen game object. 

2. The remainder of your response should be the "Starting Prompt" text from your documents and generate a list of "Game Objects" for the person playing the game to choose a game object from.  

# End of Game: The game ends in one of the following three outcomes:
1. Invalid Input: If the player responds with an invalid game object choice, respond with variations of the "Invalid Input" text from your documents and then end the game.

2. Tie: The game ends in a tie if the user chooses the same game object as your game object choice.

3. Win or Lose: The game winner is decided based on the "Judgement of Victory" conditions from your documents. Compare the user's game object choice and your game object choice and determine who chose the winning game object.

# Game conclusion
Respond with a declaration of the winner of the game by outputting the corresponding text in the "Judgement of Victory" section of your documents.</code></pre><p><strong>表示名</strong>には以下のテキストを入力します。</p><pre><code>RPS+ Agent</code></pre><p><strong>表示の説明</strong>には以下のテキストを入力します。</p><pre><code>An agent that plays the game RPS+</code></pre><p><strong>[ツール]</strong> タブをクリックして、以前に作成したカスタムツールをエージェントに提供します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1778f64bc3a1b4004998dc3668ef7f666788e193-1600x1390.png" style="width: 100%;" /><p>先ほど作成した<em>example.get_game_docs</em>ツールのみを選択します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7d734cd80161bcc058817482eb330ffcf1cb567b-1600x1363.png" style="width: 100%;" /><p><strong>[保存]</strong> をクリックして新しいエージェントを作成します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c0014faf605ce50c03679ed0d073bd9f3ae7234d-1600x468.png" style="width: 100%;" /><p>新しいエージェントをテストしてみましょう。エージェントのリストから任意のエージェントとチャットを開始するための便利なリンクがあります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b7d6943ad90a4f68691207caf66b81742e712145-1600x560.png" style="width: 100%;" /><p>「start game」と入力すると、ゲームが始まります。うまくいきました！</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/984d008e4cc3f08cc1f101720673b0f7347c066c-1600x874.gif" style="width: 100%;" /><p>エージェントが応答の上部にゲームオブジェクトの選択を表示することがわかります。これは、エージェントの選択を確認し、ゲームが期待どおりに機能していることを確認できる点で便利です。しかし、自分が選択する前に相手の選択がわかっていると、じゃんけんゲームはあまり楽しくありません。ゲームを最終形に磨き上げるために、コードでエージェントを制御できるエージェントオーケストレーションプラットフォームを使用できます。</p><p>Strands Agents SDKがチャットに参加します。</p><h2>Strands Agents SDK</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c195bba6ff2754f5d8fda174a0c1d247bc283710-456x156.png" style="width: 100%;" /><p>新しいエージェント開発フレームワークを試してみたい場合は、<a href="https://strandsagents.com/latest/">Strands Agents SDK</a>がおすすめです。<a href="https://aws.amazon.com/blogs/opensource/introducing-strands-agents-an-open-source-ai-agents-sdk/">Strands Agents SDKはAWSから2025年5月に</a>オープンソースの<a href="https://github.com/strands-agents/sdk-python">Python</a>実装としてリリースされ、現在は<a href="https://dev.to/aws/strands-agents-now-speaks-typescript-a-side-by-side-guide-12b3">Typescript</a>版もあります。</p><h2>PythonでStrands Agents SDKの使用を開始</h2><p>コーディングエンジンを起動して、Strandsエージェントを使用してA2Aプロトコル経由で<em>RPS+エージェント</em>を制御するサンプルアプリのクローン作成と実行のプロセスを早速実行してみましょう。RPS+ゲームの微調整バージョンを作成し、エージェントの選択がプレイヤーの選択後に明らかになるようにしてみましょう。結局のところ、じゃんけんのようなゲームを楽しいものにするのは推測と驚きの結果だからです。</p><p>ローカルコンピューターで<a href="https://code.visualstudio.com/download">Visual Studio Code</a>を開き、新しいターミナルを開きます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2339cc37c89a3524f2b2a21684bc61dae958e1cf-915x460.jpg" style="width: 100%;" /><p>新しく開いたターミナルで、以下のコマンドを実行してElasticsearch Labsリポジトリをクローンします。</p><pre><code>git clone https://github.com/elastic/elasticsearch-labs</code></pre><p>次の<em>cd</em>コマンドを実行して、ディレクトリをelasticsearch-labsディレクトリに変更します。</p><pre><code>cd elasticsearch-labs</code></pre><p>次に、次のコマンドを実行して、Visual Studio Codeでリポジトリを開きます。</p><pre><code>code .</code></pre><p>Visual Studio File Explorerで、<em>supporting-blog-content</em>フォルダーと<em>agent-builder-a2a-strands-agents</em>フォルダーを展開し、<em>elastic_agent_builder_a2a_rps+.py</em>ファイルを開きます。Visual Studio Codeで開いたファイルは次のようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d153b19e0e016c701576edb99ccab5af7c554f34-1484x1530.jpg" style="width: 100%;" /><p>テキストエディターに表示される<em>elastic_agent_builder_a2a_rps+.py</em>の内容は次のとおりです。</p><pre><code>import asyncio
from dotenv import load_dotenv
from uuid import uuid4
import httpx
import os
import random
from a2a.client import A2ACardResolver, ClientConfig, ClientFactory
from a2a.types import Message, Part, Role, TextPart

DEFAULT_TIMEOUT = 60  # set request timeout to 1 minute


def create_message(*, role: Role = Role.user, text: str, context_id=None) -> Message:
    return Message(
        kind="message",
        role="user",
        parts=[Part(TextPart(kind="text", text=text))],
        message_id=uuid4().hex,
        context_id=context_id,
    )


async def main():
    load_dotenv()
    a2a_agent_host = os.getenv("ES_AGENT_URL")
    a2a_agent_key = os.getenv("ES_API_KEY")
    custom_headers = {"Authorization": f"ApiKey {a2a_agent_key}"}

    async with httpx.AsyncClient(
        timeout=DEFAULT_TIMEOUT, headers=custom_headers
    ) as httpx_client:
        # Get agent card
        resolver = A2ACardResolver(httpx_client=httpx_client, base_url=a2a_agent_host)
        agent_card = await resolver.get_agent_card(
            relative_card_path="/rps_plus_agent.json"
        )
        # Create client using factory
        config = ClientConfig(
            httpx_client=httpx_client,
            streaming=True,
        )
        factory = ClientFactory(config)
        client = factory.create(agent_card)
        # Use the client to communicate with the agent
        print("\nSending 'start game' message to Elastic A2A agent...")
        random_game_object = random.randint(1, 5)
        msg = create_message(text=f"start with game object {random_game_object}")
        async for event in client.send_message(msg):
            if isinstance(event, Message):
                context_id = event.context_id
                response_complete = event.parts[0].root.text
                # Get agent choice from the first line of the response
                parsed_response = response_complete.split("\n", 1)
                agent_choice = parsed_response[0]
                print(parsed_response[1])
        # User choice sent for game results from the agent
        prompt = input("Your Choice  : ")
        msg = create_message(text=prompt, context_id=context_id)
        async for event in client.send_message(msg):
            if isinstance(event, Message):
                print(f"Agent Choice : {agent_choice}")
                print(event.parts[0].root.text)


if __name__ == "__main__":
    asyncio.run(main())</code></pre><p>このコードで何が起きているのか見てみましょう。<em><code>main()</code></em>メソッドから始めて、コードはエージェントのURLとAPIキーの環境変数にアクセスすることから始まります。その値を用いてエージェントカードを取得するための<code><em>httpx</em> client</code>を作成します。次に、クライアントはエージェントカードの詳細を使用して、「start game」リクエストをエージェントに送信します。ここで注目すべき興味深い点は、 <code>&quot;start game&quot;</code>リクエストの一部として<code>random_game_object</code>値が含まれていることです。この値は、Python の標準ライブラリの<em>random</em>モジュールで生成された乱数です。これを行う理由は、（AIエージェントを可能にする）強力なLLMがランダム性に関してはそれほど優れていないことが判明したためです。Pythonが助けてくれますので問題ありません。</p><p>コードの続きですが、エージェントが「start game」リクエストに応答すると、コードはエージェントのゲームオブジェクトセレクションを取り除き、<em>agent_choice</em>変数に保存します。対応の残りの部分は、エンドユーザーに対してテキストとして表示されます。次に、ユーザーはゲームオブジェクトの選択を入力するように求められ、それがエージェントに送信されます。次に、コードはエージェントのゲームオブジェクトの選択と、エージェントの最終的なゲーム結果の決定を表示します。</p><h2>エージェントのURLとAPIキーを環境変数として設定する</h2><p>サンプルアプリはローカルコンピュータ上で実行されるため、Agent Builderエージェントと通信するためには、Strands Agents SDKにエージェントのA2A URLとAPI Keyを提供する必要があります。この例のアプリは<em>`.env`</em>というファイルを使用してこれらの値を格納します。</p><p><em>env.example</em>ファイルのコピーを作成し、新しいファイル名を<em>.env</em>とします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/25ead5f15a17dedb777132a082097cffb06cae4d-1600x843.jpg" style="width: 100%;" /><p>Elastic Agent Builderに戻りましょう。ここで必要な両方の値を取得できます。</p><p>ページの右上にあるAgent Builderアクションメニューから <strong>[すべてのツールを表示]</strong> を選択します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9c4f4e4a3bd76e11e0a182fa007a2f6aec7777b4-1600x880.jpg" style="width: 100%;" /><p>ツールページ上部の<strong>MCPサーバー</strong>ドロップダウンをクリックし、<strong>[MCPサーバーURLをコピー] </strong>を選択してください。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6cde0de678bb6f81bef8a59deffb110ad6c6ce26-1600x882.jpg" style="width: 100%;" /><p><strong>MCPサーバーのURL</strong>を<em>.env</em>に貼り付けます。<strong>&lt;YOUR-ELASTIC-AGENT-BUILDER-URL&gt;</strong>プレースホルダー値の代わりにファイルを使用します。ここで、URLを1箇所更新する必要があります。つまり、末尾のテキスト「mcp」を「a2a」に置き換えます。これは、Agent Strands SDKがElastic Agent Builderで実行されているエージェントと通信するために使用するプロトコルが<a href="https://a2a-protocol.org/">A2Aプロトコル</a>であるためです。</p><p>編集したURLは次のようになるはずです。</p><pre><code>https://rps-game-project-12345a.kb.us-east-1.aws.elastic.cloud/api/agent_builder/a2a</code></pre><p>Elastic Cloudで取得する必要があるもう1つの値は、APIキーです。最上位ナビゲーションで<strong>Elasticsearch</strong>をクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/651676b9be65178cdad50b5d24f26441c0bf3f97-1600x549.jpg" style="width: 100%;" /><p><strong>[APIキーをコピー] ボタン</strong>をクリックして、APIキーをコピーします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/17f1e2ed5c7682630c71e75b0b09ffb1d9036210-1600x879.jpg" style="width: 100%;" /><p>次に、Visual Studio Codeに戻り、<em>.env</em>ファイルにAPIキーを貼り付けて、<strong>&lt;YOUR-ELASTIC-API-KEY&gt;</strong>プレースホルダーテキストを置き換えます。<em>.env</em>ファイルは次のようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a357947e07f29c8c03382e00c7baedf04a399297-1600x286.jpg" style="width: 100%;" /><h2>サンプルアプリを実行してください</h2><p>Visual Studio Codeで新しいターミナルを開いてください。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/33e1174c68ea1ed47c7fe62ab6a6da657c606f56-1413x711.jpg" style="width: 100%;" /><p>まず、ターミナルで次の<em>cd</em>コマンドを実行します。</p><pre><code>cd elasticsearch-labs/supporting-blog-content/agent-builder-a2a-strands-agents</code></pre><p>次のコマンドを実行して、Python仮想環境を作成します。</p><pre><code>python -m venv .venv</code></pre><p>お使いのローカルコンピューターのオペレーティングシステムに応じて、以下のコマンドを実行して仮想環境を有効にしてください。</p><ul><li>MacOS/Linux</li></ul><pre><code>source .venv/bin/activate</code></pre><ul><li>Windows</li></ul><pre><code>.venv\Scripts\activate</code></pre><p>サンプルアプリはStrands Agents SDKを使用するため、このチュートリアルではこれをインストールする必要があります。以下のコマンドを実行して、Strands Agents SDKとその必要なPythonライブラリの依存関係をインストールします。</p><pre><code>pip install -r requirements.txt</code></pre><p>発射台を片付けてカウントダウンを開始する時間です。アプリを起動する準備ができました。後ろに下がってください。次のコマンドを使用して実行しましょう：</p><pre><code>python elastic_agent_builder_a2a_rps+.py</code></pre><p>RPS+のゲームに挑戦してみましょう。幸運を祈ります！</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/041df81fbf1776f09e1243af0a435c4c0af6aca1-1600x948.gif" style="width: 100%;" /><h2>関連コンテキストでAIアプリを構築</h2><p>AIエージェントの構築のスキルを習得できました。また、Strands Agents SDKのようなエージェント開発フレームワークで、A2Aを介してElastic Agent Builderエージェントを使用することがいかに簡単であるかをお分かりいただけたと思います。カスタムデータの関連コンテキストに接続されたAIエージェントの構築には<a href="https://cloud.elastic.co/registration?utm_source=agentic-ai-category&amp;utm_medium=search-labs&amp;utm_campaign=agent-builder">Elasticをお試し</a>ください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Google MCP Toolbox for DatabasesにElasticsearchサポートを導入]]></title>
            <description><![CDATA[Google MCP Toolbox for Databasesで利用可能になったElasticsearchサポートの詳細を確認し、ES|QLツールを活用してインデックスを任意の MCP クライアントと安全に統合します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/google-mcp-toolbox-elasticsearch-support</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/google-mcp-toolbox-elasticsearch-support</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Enrico Zimuel, Laurent Saint-Félix]]></dc:creator>
            <pubDate>Fri, 12 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/425a48691f436ed47c9bdfaf5d561ac122b2c472-1062x668.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>この記事では、Google MCP Toolboxと<a href="https://github.com/elastic/elasticsearch">Elasticsearch</a>を併用し、Elasticsearchインデックスから情報を抽出する簡単なツールを構築する方法を解説します。</p><p>当社は最近、<a href="https://github.com/googleapis/genai-toolbox">Google MCP Toolbox for Databases</a>のオープンソースプロジェクトに貢献し、Elasticsearchをデータベースとしてサポートしました。</p><p>この新しい機能により、Google MCP Toolboxを使用してElasticsearchに接続し、データと直接「会話」できるようになりました。</p><h2>Elasticsearch</h2><p>Elasticsearchインスタンスを実行する必要があります。<a href="https://www.elastic.co/cloud">Elastic Cloud</a>で無料トライアルを有効化するか、<a href="https://github.com/elastic/start-local">start-local</a>スクリプトを使ってローカルにインストールできます。</p><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre><p>これにより、ElasticsearchとKibanaがコンピュータにインストールされ、Google MCP Toolboxの設定に使用するAPIキーが生成されます。</p><p>APIキーは前のコマンドの出力として表示され、elastic-start-localフォルダー内の.envファイルに保存されます。</p><h2>サンプルデータセットをインストールする</h2><p>インストール後、ユーザー名<em>elastic</em>とstart-localスクリプトによって生成されたパスワード（.envファイルに保存）を使用してKibanaにログインできます。</p><p>Kibanaから入手可能な<strong>eCommerce orders</strong>データをインストールできます。このデータベースには、eコマースWebサイトからの4,675件の注文に関する情報を含む<strong>kibana_sample_data_ecommerce</strong>という単一のインデックスが含まれています。各注文について、次の情報があります。</p><ul><li>顧客情報（氏名、ID、生年月日、メールアドレスなど）</li><li>注文日</li><li>注文ID</li><li>商品（価格、数量、ID、カテゴリー、割引などを含む全商品のリスト）</li><li>SKU</li><li>合計金額（税抜、税込）</li><li>合計数量</li><li>地理情報（都市、国、大陸、場所、地域）</li></ul><p>サンプルデータをインストールするには、Kibanaの<strong>統合</strong>ページを開き（検索トップバーで「Integration」を検索）、「Sample Data」をインストールしてください。詳細については、ドキュメント<a href="https://www.elastic.co/docs/explore-analyze/#gs-get-data-into-kibana">https://www.elastic.co/docs/explore-analyze/#gs-get-data-into-kibana</a>を参照してください。</p><p>この記事の目的は、Google MCP ToolboxがElasticsearchに接続し、自然言語で<strong>kibana_sample_data_ecommerce</strong>インデックスとやり取りするのがいかに簡単かを示すことです。</p><h2>Google MCP Toolbox</h2><p>Google MCP ToolboxはオープンソースのMCPサーバーで、アプリケーションやAIエージェントが安全かつ効率的にデータベースとやり取りできるように設計されています。以前は「GenAI Toolbox for Databases」と呼ばれていたこのプロジェクトは、<a href="https://www.anthropic.com/news/model-context-protocol">モデルコンテキストプロトコル</a>（MCP）との完全な互換性を採用した後に改名されました。その目的は、エージェントをデータベースに接続する際に従来必要とされていた接続プーリング、認証、オブザーバビリティ、その他の運用上の懸念をバックエンドで処理することで、重労働を排除することです。</p><p>Toolboxの本質は、開発者がデータベースのやり取りをカプセル化する再利用可能な高レベルのツールを定義できるようにすることです。これらのツールは、AIエージェントなどのMCP互換クライアントならどれでも起動できます。クライアントが低レベルのSQLクエリを実装したり、データベース接続を管理したりする必要はありません。このアプローチにより、データベース対応エージェントの構築に必要な定型コードの量が大幅に削減され、わずか数行のアプリケーションロジックに高度なデータ操作を統合できるようになります。ツールが定義されると、複数のエージェント、フレームワーク、言語間で共有できます（図1）。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/137cea290bb70ad5da21853f9a6358cef4cf7451-1248x1056.png" style="width: 100%;" /><p>Toolboxを使用する大きな利点は、組み込まれたセキュリティモデルです。OAuth2やOIDCなどの認証フローはネイティブにサポートされているため、開発者はデータベースの機密認証情報をエージェントで処理したり格納したりする必要がありません。このプラットフォームは、デバッグ、監視、本番環境への導入に不可欠な、OpenTelemetryによる指標やトレースなどの観測機能も提供します。全体として、MCP Toolboxは、あらゆるMCP対応システムのデータを操作するための、統一された安全で拡張可能なインターフェースとして機能します。</p><h2>MCP Toolboxのインストール方法</h2><p>MCP ToolboxサーバーをLinuxにインストールするには、次のコマンドを使用します。</p><pre><code>export VERSION=0.21.0
curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox</code></pre><p>macOSまたはWindowsにインストールする場合は、<a href="https://googleapis.github.io/genai-toolbox/getting-started/introduction/#installing-the-server">ここに</a>記載されている手順に従ってください。</p><h2>Elasticsearch向けにToolboxを構成する</h2><p>Elasticsearch向けにMCP Toolboxを構成するには、次のように<strong>tools.yaml</strong>ファイルを作成する必要があります。</p><pre><code>sources:
  my-cluster:
    kind: elasticsearch
    addresses:
      - http://localhost:9200
    apikey: <insert-here-api-key>

tools:
  customer-orders:
    kind: elasticsearch-esql
    source: my-cluster
    description: Get the orders made by a customer identified by name.
    query: |
    	FROM kibana_sample_data_ecommerce | WHERE MATCH(customer_full_name, ?name, {"operator": "AND"})
    parameters:
      - name: name
        type: string
        description: The customer name.

toolsets:
  elasticsearch-tools:
    - customer-orders</code></pre><p><strong>&lt;insert-here-api-key&gt;</strong>値を有効なElasticsearch APIキーに置き換える必要があります。start-localを使用してElasticsearchをローカルで実行している場合は、start-localによって生成された.envファイルの<strong>ES_LOCAL_API_KEY</strong>変数の下にAPIキーがあります。Elastic Cloudを使用している場合は<a href="https://www.elastic.co/docs/deploy-manage/api-keys/elastic-cloud-api-keys">ここで</a>説明した手順に従うことでAPIキーを生成できます。</p><p>前のツールには、Elasticsearch用の次のES|QLクエリが含まれています。</p><pre><code>FROM kibana_sample_data_ecommerce | WHERE MATCH(customer_full_name, ?name)</code></pre><p>ES|QLに慣れていない方のために説明すると、ES|QLはSQLと同様にElasticが開発したクエリ言語で、1つ以上のインデックスを検索するために使用できます。ES|QLの詳細については<a href="https://www.elastic.co/docs/reference/query-languages/esql">こちらの</a>公式ドキュメントをご覧ください。</p><p>上記のクエリは、<strong>kibana_sample_data_ecommerce</strong>インデックスに格納されている指定顧客名を含むすべての注文を<strong>?name</strong>パラメーター（疑問符はパラメーターを示します）を用いて検索します。</p><p>顧客名は、以前のYAML設定で文字列型と「顧客名」という記述で定義されています。</p><p>このツールを使用すると、顧客の注文に関する質問に答えることができます。たとえば、<em>「顧客Fooは2025年10月に何件の注文をしましたか？」</em></p><p>ツールとそのパラメーターの説明は、ユーザーの自然言語リクエストから関連情報を抽出するために不可欠です。この抽出は、大規模言語モデル（LLM）の<strong>関数呼び出し</strong>機能を使用して実行されます。実際には、LLMは、必要な情報を取得するためにどの機能（ツール）を実行する必要があるかを判断し、その機能に適したパラメーターも取得できます。</p><p>詳細については、<a href="https://www.elastic.co/search-labs/blog/function-calling-with-elastic">Elasticsearchを使用したOpenAIの関数呼び出し</a>に関するAshish Tiwariの記事を読むことをお勧めします。</p><h2>Toolboxサーバーを実行する</h2><p>次のコマンドで、以前のtools.yamlファイルを使用してMCPツールボックスを実行できます。</p><pre><code>./toolbox --tools-file tools.yaml --ui</code></pre><p><strong> –ui</strong>パラメーターは<a href="http://127.0.0.1:5000/ui">http://127.0.0.1:5000/ui</a>のウェブアプリケーションを実行します（図2）。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3caf2339b632bafd5847af1ed8b33b518a25b8a2-1600x314.png" style="width: 100%;" /><p><strong>[ツール]</strong> &gt; <strong>[customer-orders]</strong> を選択し、パラメータ<strong>名</strong>に顧客名（例：Gwen Sanders）を挿入して <strong>[ツールを実行]</strong> ボタンをクリックします。図3に示すように、JSON応答が表示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b167e0142afb8919d9cedf6d0fa431d33d0e55f8-1600x933.png" style="width: 100%;" /><p>セットアップが完了すると、MCP Toolboxは<strong>customer-orders</strong>ツールを実行してElasticsearchと通信し、ES|QLクエリを実行できるようになります。</p><h2>Gemini CLIでのMCP Toolboxの使用</h2><p>任意のMCPクライアントを使用して、MCP Toolbox for Databasesと通信できます。例えば、<a href="https://github.com/google-gemini/gemini-cli">Gemini CLI</a>というコマンドラインツールを使ってGeminiを使うことができます。Gemini CLIのインストールは、<a href="https://geminicli.com/docs/get-started/installation/">こちら</a>の手順に従って行うことができます。</p><p>Gemini CLIは、MCP Toolbox用の事前設定された拡張機能を提供しており、<a href="https://github.com/gemini-cli-extensions/mcp-toolbox">gemini-cli-extensions/mcp-toolbox</a>で入手できます。この拡張機能は次のコマンドを実行してインストールできます。</p><pre><code>gemini extensions install https://github.com/gemini-cli-extensions/mcp-toolbox</code></pre><p>インストール後、MCP Toolbox用のtools.yaml設定ファイルを格納したディレクトリに移動し、以下のようにGemini CLIを実行する必要があります（この手順は、Gemini CLIをMCP Toolboxで自動的に設定するために必要です）。</p><pre><code>gemini</code></pre><p>図4に示すように出力広告が表示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0f22df6d3da13c1dc50dcb560414fa7c630eb9a7-1434x341.png" style="width: 100%;" /><p>次のコマンドを使用して、MCP Toolboxが接続されているかどうかを確認できます。</p><pre><code>/mcp list</code></pre><p><strong>mcp_toolbox</strong>と<strong>customer-orders</strong> ツールが一覧に表示されているはずです（図5）。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/97edbc40de9e44f469f6f3a09427532be167de0e-493x155.png" style="width: 100%;" /><p>MCP ToolboxがGemini CLI に接続されている場合は、「<em>顧客Gwen Sandersの注文を教えてください</em>」などの質問をいくつか試すことができます。Gemini CLIは、mcp_toolboxサーバーからcustomer-ordersツールを実行する許可を要求します（図6を参照）。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c052f3b5e49436903b804280c0065f67ee02444b-1432x284.png" style="width: 100%;" /><p>確認後、Gemini CLIはMCP Toolboxへのリクエストを実行し、結果としてJSON応答を取得し、それを使用して応答をフォーマットします（図7）。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7ea5128f1705951c2757af6da4b456d394d4a080-1432x734.png" style="width: 100%;" /><p>Gemini CLIからの応答で、Gwen Sandersが2つの製品を1回の注文で、合計132ユーロの価格で購入したことがレポートされます。</p><h2>MCP Toolbox SDKs</h2><p>Google MCP Toolboxは、Go、Python、Javascriptで書かれたプログラムからすべての機能にアクセスするためのSDKも提供しています。</p><p>例えば、Python SDKはGithubの次のページ<a href="https://github.com/googleapis/mcp-toolbox-sdk-python">https://github.com/googleapis/mcp-toolbox-sdk-python</a>で入手可能です。</p><p>MCP Toolboxに接続するための簡単なエージェントを作成する必要があります。次のパッケージをインストールする必要があります。</p><pre><code>pip install toolbox-core
pip install google-adk</code></pre><p>次のコマンドを使用して、新しいエージェントプロジェクトを作成します。</p><pre><code>adk create my_agent</code></pre><p>これにより、ファイル<strong>agent.py</strong>を持つ新しいディレクトリが<strong>my_agent</strong>として作成されます。</p><p>Toolboxに接続するには、次の内容で<strong>my_agent/agent.py</strong>を更新します。</p><pre><code>from google.adk import Agent
from google.adk.apps import App
from toolbox_core import ToolboxSyncClient

client = ToolboxSyncClient("http://127.0.0.1:5000")

root_agent = Agent(
    name='root_agent',
    model='gemini-2.5-flash',
    instruction="You are a helpful AI assistant designed to search information about a dataset of ecommerce orders.",
    tools=client.load_toolset(),
)

app = App(root_agent=root_agent, name="my_agent")</code></pre><p>Google APIキーを使用して<strong>.env</strong>ファイルを作成します。</p><pre><code>echo 'GOOGLE_API_KEY="YOUR_API_KEY"' > my_agent/.env</code></pre><p>最後に、エージェントを実行して結果を確認します。エージェントを実行するには、次のコマンドを実行します。</p><pre><code>adk run my_agent</code></pre><p>または、Webインターフェース経由で提供することもできます。</p><pre><code>adk web --port 8000</code></pre><p>両方の場合において、Q&amp;Aインターフェースを使用してMCP Toolboxと対話することができます。たとえば、先程の質問「<em>顧客Gwen Sandersの注文を教えてください</em>」をすることができます。</p><p>さまざまなSDKの詳細については、<a href="https://googleapis.github.io/genai-toolbox/sdks/">このドキュメントページ</a>をご参照ください。</p><h2>まとめ</h2><p>この記事では、Google MCP Toolbox for DatabasesのElasticsearch統合について説明しました。シンプルなYAML設定ファイルを使用して、自然言語の質問をES|QL言語を使用してElasticsearchクエリに変換する一連のツールを定義できます。</p><p>eコマースWebサイトからの注文を含むkibana_sample_data_ecommerceデータセットとの対話方法を示しました。この設定ファイルを使用すると、MCP Toolboxサーバーを簡単に実行し、任意のMCPクライアントから接続できます。</p><p>最後に、Gemini CLIをクライアントとして使用してMCP Toolbox for Databasesに接続し、Elasticsearchに保存されているeコマースデータをクエリする方法を示しました。特定の顧客の名前で識別された注文情報を取得するために自然言語クエリを実行しました。</p><p>MCPエコシステムが成長し続けるにつれて、このパターン（安全で本番環境ですぐに使えるインフラストラクチャーに裏打ちされた軽量なツール定義）は、最小限の労力で、ますます有能でデータを認識するエージェントを構築する新しい機会を生み出します。MCP Toolboxは、Elasticのサンプルデータセットを使ってローカルで実験する場合でも、大規模なアプリケーションに検索機能を統合する場合でも、自然言語を使ってElasticsearchのデータを操作するための、信頼性と拡張性に優れた基盤を提供します。</p><p>エージェントAIアプリケーションの開発の詳細については、Anish MathurとDana Juratoniによる記事<a href="https://search-labs-redesign.vercel.app/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">「Elasticsearchを使用したAI エージェントワークフローの構築」</a>をお読みください。</p><p>Google MCP Toolboxの詳細については、<a href="https://googleapis.github.io/genai-toolbox/getting-started/introduction/">https://googleapis.github.io/genai-toolbox/getting-started/introduction/</a>をご覧ください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[判断リストによる検索クエリの関連性の評価]]></title>
            <description><![CDATA[Elasticsearchで検索クエリの関連性を客観的に評価し、リコールなどのパフォーマンス指標を改善するための判断リストの構築方法を探ります。スケーラブルな検索のテストの拡張性についても学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/judgment-lists-search-query-relevance-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/judgment-lists-search-query-relevance-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jhon Guzmán]]></dc:creator>
            <pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/25478d0ffb228afd5d65d82312998ec1c299c565-700x490.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>検索エンジンに取り組んでいる開発者は、同じ問題によく遭遇します。それは、検索結果の上位に表示されると予想していたドキュメントが結果リストの3番目か4番目に表示されるため、ビジネスチームが特定の検索に満足してくれないという問題です。</p><p>ただし、この 1 つの問題を修正すると、すべてのケースを手動でテストすることができないため、他のクエリが誤って壊れてしまいます。しかし、1つのクエリの変更が他のクエリに波及効果をもたらすかどうかを、開発者やQAチームはどのようにテストできるでしょうか。さらに重要なのは、変更によってクエリが実際に改善されたことをどうやって確認できるかということです。</p><h2>体系的な評価に向けて</h2><p>ここで役に立つのが判断リストです。変更を加えるたびに手動の主観的なテストに頼るのではなく、ビジネスケースに関連するクエリの固定セットと、関連する結果を定義できます。</p><p>このセットが基準となります。変更を実装するたびに、それを使用して検索が実際に改善されたかどうかを評価します。</p><p>このアプローチの価値は、次の点にあります。</p><ul><li><strong>不確実性の排除</strong>：変更が他のクエリに影響を与えるかどうかを心配する必要はなくなり、データが教えてくれます。</li><li><strong>手動テストの停止</strong>：判断セットが記録されると、テストは自動化されます。</li><li><strong>変更を支援</strong>：変更のメリットを裏付ける明確な指標を示すことができます。</li></ul><h2>判断リストの作成方法</h2><p>最も簡単な方法の1つは、代表的なクエリを取得して、関連するドキュメントを手動で選択することです。このリストを作成するには2つの方法があります。</p><ul><li><strong>バイナリ判定：</strong>クエリに関連付けられた各ドキュメントに<em>関連</em>（通常「1」のスコア）とと非関連（「0」）の<strong>シンプルなタグ</strong>が付けられます。</li><li><strong>段階的な判断：</strong>ここでは、各ドキュメントに異なるレベルのスコアが付けられます。例えば、0から4の尺度を設定します。これは<a href="https://en.wikipedia.org/wiki/Likert_scale">ライカート尺度</a>に似ており、0は「全く関連しない」、4は「完全に関連する」を意味し、「関連する」、「やや関連する」などのバリエーションがあります。</li></ul><p>検索意図に明確な制限がある場合、つまり「このドキュメントは結果に含まれるべきかどうか」という場合には、バイナリ判断がうまく機能します。</p><p>段階的な判断は、グレーゾーンがある場合により役立ちます。一部の結果は他の結果よりも優れているため、「非常に良い」、「良い」、「役に立たない」という結果を取得し、結果の順序とユーザーのフィードバックを評価する指標を使用できます。ただし、段階的な評価尺度には欠点もあります。評価者によってスコアリングレベルの使い方が異なり、判断の一貫性が失われることがある点です。また、評価基準では高得点により重み付けされるため、小さな変更（評価を4ではなく3にするなど）でも、レビュー担当者の意図よりもはるかに大きな変化が評価基準に生じる可能性があります。この主観性が加わることで、段階的な判断はノイズが多くなり、時間の経過とともに管理が難しくなります。</p><h2>書類を自分で分類する必要がありますか？</h2><p>必ずしもそうとは限りません。なぜなら、判断リストを作成する方法はいくつかあり、それぞれに利点と欠点があるからです。</p><ul><li><strong>明示的な判断：</strong>ここでは、SMEが各クエリやドキュメントに目を通し、関連性があるかどうか（あるいはどの程度関連性があるか）を手動で判断します。これにより品質と管理は提供されますが、拡張性は低くなります。</li><li><strong>暗黙的な判断：</strong>この方法では、クリック、直帰率、購入などの実際のユーザーの行動に基づいて関連するドキュメントを推測します。このアプローチにより、データを自動的に収集できますが、バイアスがかかる可能性があります。例えば、ユーザーは関連性がなくても上位の結果をクリックする傾向があります。</li><li><strong>AI生成の判断：</strong>この最後のオプションでは、モデル（LLMなど）を使用してクエリとドキュメントを自動的に評価します。これは、<a href="https://en.wikipedia.org/wiki/LLM-as-a-Judge">LLM審査員</a>とも呼ばれます。スケーリングは高速かつ簡単ですが、データの品質は、使用しているモデルの品質と、LLMトレーニングデータがビジネス上の<a href="http://interests.as/">利益</a>とどの程度一致しているかによって異なります。人間の採点者と同様に、LLM審査員も独自のバイアスや不整合を導入する可能性があるため、信頼できる判断の小さなセットに対してその出力を検証することが重要です。LLMモデルは本質的に確率的であるため、 <a href="https://www.ibm.com/think/topics/llm-temperature">温度</a> パラメータを0に設定しても同じ結果に対して異なる評価を与えるLLMモデルがよく見られます。</li></ul><p>判断セットを作成するための最適な方法を選択するための推奨事項を以下に示します。</p><ul><li>ユーザーのみが適切に判断できる特徴（価格、ブランド、言語、スタイル、製品詳細など）の重要性を決定します。これらが重要な場合は、<strong>判断リスト</strong>の少なくとも一部について<em>明示的な判断が</em>必要です。</li><li>検索エンジンにすでに十分なトラフィックがある場合は、<strong>暗黙的な判断</strong>を使用して、クリック、コンバージョン、滞在時間の指標を使用して使用傾向を検出できます。これらの結果は人間が注意深く解釈し、明示的な判断セットと対比させて、バイアスを防御する必要があります（例：ユーザーは、たとえ低いランクの結果がより関連性が高くても、上位にランクされた結果をクリックする傾向があります）。</li></ul><p>これに対処するために、位置バイアス除去技術はクリックデータを調整または再重み付けして、実際のユーザーの関心をより適切に反映します。アプローチには以下のようなものがあります。</p><ul><li><strong>結果のシャッフル</strong>：一部のユーザーの検索結果の順序を変更し、位置がクリックにどのように影響するかを推定します。</li><li><strong>クリックモデルには</strong><a href="https://wiki.math.uwaterloo.ca/statwiki/index.php?title=a_Dynamic_Bayesian_Network_Click_Model_for_web_search_ranking">ダイナミックベイジアンネットワーク<strong>（DBN）、</strong></a><a href="https://rsrikant.com/papers/kdd10.pdf">ユーザーブラウジングモデル<strong>（UBM）が含まれます。</strong></a>これらの統計モデルは、スクロール、滞在時間、クリックシーケンス、結果ページへの戻りなどのパターンを使用して、クリックが単なる位置ではなく実際の関心を反映している可能性を推定します。</li></ul><h2>例：映画評価アプリ</h2><h3>要件</h3><p>この例を実行するには、稼働しているElasticsearch 8.xクラスター、<a href="https://www.elastic.co/downloads/elasticsearch">ローカル</a>または<a href="https://www.elastic.co/cloud/cloud-trial-overview">Elastic Cloud</a>（HostedまたはServerless）、<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis">REST API</a>またはKibanaへのアクセスが必要です。</p><p>ユーザーが映画についての意見をアップロードしたり、見たい映画を検索したりできるアプリを考えてみてください。ユーザー自身がテキストを書くため、タイプミスや表現の多様性がある可能性があります。そのため、検索エンジンがその多様性を解釈し、ユーザーにとって有益な結果を提供できることが不可欠です。</p><p>全体的な検索動作に影響を与えずにクエリを反復処理できるようにするために、会社のビジネスチームは、最も頻繁に実行される検索に基づいて、次のバイナリ判定セットを作成しました。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">クエリ</td><td style="border: 1px solid #ccc; padding: 4px;">DocID</td><td style="border: 1px solid #ccc; padding: 4px;">テキスト</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ディカプリオの演技</td><td style="border: 1px solid #ccc; padding: 4px;">doc1</td><td style="border: 1px solid #ccc; padding: 4px;">『レヴェナント：蘇えりし者』でのディカプリオの演技は息を呑むほど素晴らしかった。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ディカプリオの演技</td><td style="border: 1px solid #ccc; padding: 4px;">doc2</td><td style="border: 1px solid #ccc; padding: 4px;">『インセプション』ではレオナルド・ディカプリオが最も象徴的な役柄の一つを演じています。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ディカプリオの演技</td><td style="border: 1px solid #ccc; padding: 4px;">doc3</td><td style="border: 1px solid #ccc; padding: 4px;">ブラッド・ピットはこの犯罪スリラーで堅実な演技を見せています。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ディカプリオの演技</td><td style="border: 1px solid #ccc; padding: 4px;">doc4</td><td style="border: 1px solid #ccc; padding: 4px;">見事な視覚効果を備えたアクション満載の冒険。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">泣ける悲しい映画</td><td style="border: 1px solid #ccc; padding: 4px;">doc5</td><td style="border: 1px solid #ccc; padding: 4px;">何時間も泣いてしまった、愛と喪失の悲痛な物語。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">泣ける悲しい映画</td><td style="border: 1px solid #ccc; padding: 4px;">doc6</td><td style="border: 1px solid #ccc; padding: 4px;">史上最も悲しい映画の1つです。ティッシュが必須。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">泣ける悲しい映画</td><td style="border: 1px solid #ccc; padding: 4px;">doc7</td><td style="border: 1px solid #ccc; padding: 4px;">笑える軽快なコメディ</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">泣ける悲しい映画</td><td style="border: 1px solid #ccc; padding: 4px;">doc8</td><td style="border: 1px solid #ccc; padding: 4px;">アクションと興奮に満ちたSF大作。</td>
                    </tr>
              </tbody>
            </table>
          <p>インデックスの作成：</p><pre><code>PUT movies
{
  "mappings": {
    "properties": {
      "text": {
        "type": "text"
      }
    }
  }
}</code></pre><p>一括リクエスト：</p><pre><code>POST /movies/_bulk
{ "index": { "_id": "doc1" } }
{ "text": "DiCaprio performance in The Revenant was breathtaking." }
{ "index": { "_id": "doc2" } }
{ "text": "Inception shows Leonardo DiCaprio in one of his most iconic roles." }
{ "index": { "_id": "doc3" } }
{ "text": "Brad Pitt delivers a solid performance in this crime thriller." }
{ "index": { "_id": "doc4" } }
{ "text": "An action-packed adventure with stunning visual effects." }
{ "index": { "_id": "doc5" } }
{ "text": "A heartbreaking story of love and loss that made me cry for hours." }
{ "index": { "_id": "doc6" } }
{ "text": "One of the saddest movies ever made -- bring tissues!" }
{ "index": { "_id": "doc7" } }
{ "text": "A lighthearted comedy that will make you laugh." }
{ "index": { "_id": "doc8" } }
{ "text": "A science-fiction epic full of action and excitement." }</code></pre><p>以下は、このアプリが使用しているElasticsearchクエリです。</p><pre><code>GET movies/_search
{
 "query": {
   "match": {
     "text": {
       "query": "DiCaprio performance",
       "minimum_should_match": "100%"
     }
   }
 }
}</code></pre><h3>判断から指標へ</h3><p>判断リスト自体は、多くの情報を提供しません。これは、クエリから得られる結果の期待値にすぎません。これらが真価を発揮するのは、検索パフォーマンスを測定するための客観的な指標の計算に使用するときです。</p><p>現在、人気の指標のほとんどには以下が含まれます。</p><ul><li><strong><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-precision">精度</a>：</strong>すべての検索結果の中で本当に関連性の高い結果の割合を測定します。</li><li><strong><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-recall">リコール</a>：</strong>検索エンジンがx件の結果の中で見つけた関連する結果の割合を測定します。</li><li><strong><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#_discounted_cumulative_gain_dcg">割引累積利得（DCG）</a>：</strong>最も関連性の高い結果が上位にあるべきであることを考慮して、結果のランキングの品質を測定します。</li><li><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#_mean_reciprocal_rank"><strong>平均逆順位（MRR）：</strong></a>最初の関連結果の位置を測定します。リストの上位にあるほど、スコアも高くなります。</li></ul><p>同じ映画評価アプリを例に使い、リコール指標を計算して、クエリに抜け落ちている情報がないかを確認します。</p><p>Elasticsearchでは、<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval">Ranking Evaluation API</a>を通じて<em>判断リスト</em>を使って指標を計算できます。このAPIは、判断リスト、クエリ、および評価する指標をインプットとして受け取り、クエリ結果と判断リストを比較した値を返します。</p><p>次の2つのクエリの判断リストを実行してみましょう。</p><pre><code>POST /movies/_rank_eval
{
 "requests": [
   {
     "id": "dicaprio-performance",
     "request": {
       "query": {
         "match": {
           "text": {
             "query": "DiCaprio performance",
             "minimum_should_match": "100%"
           }
         }
       }
     },
     "ratings": [
       {
         "_index": "movies",
         "_id": "doc1",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc2",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc3",
         "rating": 0
       },
       {
         "_index": "movies",
         "_id": "doc4",
         "rating": 0
       }
     ]
   },
   {
     "id": "sad-movies",
     "request": {
       "query": {
         "match": {
           "text": {
             "query": "sad movies that make you cry",
             "minimum_should_match": "100%"
           }
         }
       }
     },
     "ratings": [
       {
         "_index": "movies",
         "_id": "doc5",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc6",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc7",
         "rating": 0
       },
       {
         "_index": "movies",
         "_id": "doc8",
         "rating": 0
       }
     ]
   }
 ],
 "metric": {
   "recall": {
     "k": 10,
     "relevant_rating_threshold": 1
     }
 }
}</code></pre><p>_rank_evalには 2 つのリクエストを使用します。1つはディカプリオクエリ用、もう1つは悲しい映画用です。各リクエストには、クエリと判断リスト（評価）が含まれています。評価に含まれていない文書は判断対象外とみなされるため、すべての文書に等級を付ける必要はありません。計算を行うために、リコールは評価において関連性があると見なされるドキュメントである「関連セット」のみを考慮します。</p><p>この場合、ディカプリオのクエリのリコールは1ですが、悲しい映画のリコールは0です。つまり、最初のクエリでは関連する結果をすべて取得できましたが、2 番目のクエリでは何も取得できませんでした。したがって、平均リコールは0.5です。</p><pre><code>{
 "metric_score": 0.5,
 "details": {
   "dicaprio-performance": {
     "metric_score": 1,
     "unrated_docs": [],
     "hits": [
       {
         "hit": {
           "_index": "movies",
           "_id": "doc1",
           "_score": 2.4826927
         },
         "rating": 1
       },
       {
         "hit": {
           "_index": "movies",
           "_id": "doc2",
           "_score": 2.0780432
         },
         "rating": 1
       }
     ],
     "metric_details": {
       "recall": {
         "relevant_docs_retrieved": 2,
         "relevant_docs": 2
       }
     }
   },
   "sad-movies": {
     "metric_score": 0,
     "unrated_docs": [],
     "hits": [],
     "metric_details": {
       "recall": {
         "relevant_docs_retrieved": 0,
         "relevant_docs": 2
       }
     }
   }
 },
 "failures": {}
}</code></pre><p>クエリ内の単語の100%がドキュメント内で見つかることを要求することで、おそらく関連する結果が除外されるため、<strong>minimum_should_match</strong>パラメータを厳しすぎるのかもしれません。<strong>minimum_should_match</strong>パラメーターを削除して、クエリで1つの単語しか見つかっていない文書が関連性があると見なされるようにしましょう。</p><pre><code>POST /movies/_rank_eval
{
 "requests": [
   {
     "id": "dicaprio-performance",
     "request": {
       "query": {
         "match": {
           "text": {
             "query": "DiCaprio performance"
           }
         }
       }
     },
     "ratings": [
       {
         "_index": "movies",
         "_id": "doc1",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc2",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc3",
         "rating": 0
       },
       {
         "_index": "movies",
         "_id": "doc4",
         "rating": 0
       }
     ]
   },
   {
     "id": "sad-movies",
     "request": {
       "query": {
         "match": {
           "text": {
             "query": "sad movies that make you cry"
           }
         }
       }
     },
     "ratings": [
       {
         "_index": "movies",
         "_id": "doc5",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc6",
         "rating": 1
       },
       {
         "_index": "movies",
         "_id": "doc7",
         "rating": 0
       },
       {
         "_index": "movies",
         "_id": "doc8",
         "rating": 0
       }
     ]
   }
 ],
 "metric": {
   "recall": {
     "k": 10,
     "relevant_rating_threshold": 1
     }
 }
}</code></pre><p>ご覧のように、2つのクエリのうちの1つで<strong>minimum_should_match</strong>パラメーターを削除すると、両方のクエリの平均リコール率は1になります。</p><pre><code>{
  "metric_score": 1,
  "details": {
    "dicaprio-performance": {
      "metric_score": 1,
      "unrated_docs": [],
      "hits": [
        {
          "hit": {
            "_index": "movies",
            "_id": "doc1",
            "_score": 2.0661702
          },
          "rating": 1
        },
        {
          "hit": {
            "_index": "movies",
            "_id": "doc3",
            "_score": 0.732218
          },
          "rating": 0
        },
        {
          "hit": {
            "_index": "movies",
            "_id": "doc2",
            "_score": 0.6271719
          },
          "rating": 1
        }
      ],
      "metric_details": {
        "recall": {
          "relevant_docs_retrieved": 2,
          "relevant_docs": 2
        }
      }
    },
    "sad-movies": {
      "metric_score": 1,
      "unrated_docs": [],
      "hits": [
        {
          "hit": {
            "_index": "movies",
            "_id": "doc7",
            "_score": 2.1307156
          },
          "rating": 0
        },
        {
          "hit": {
            "_index": "movies",
            "_id": "doc5",
            "_score": 1.3160692
          },
          "rating": 1
        },
        {
          "hit": {
            "_index": "movies",
            "_id": "doc6",
            "_score": 1.190063
          },
          "rating": 1
        }
      ],
      "metric_details": {
        "recall": {
          "relevant_docs_retrieved": 2,
          "relevant_docs": 2
        }
      }
    }
  },
  "failures": {}
}</code></pre><p>要約すると、minimum_should_match: 100%句を削除すると、両方のクエリで完璧なリコールが得られます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/24d055da4348c63827ba7046fe8cafb6f47cadd8-546x628.png" style="width: 100%;" /><p>これで完了でしょうか？</p><p>安心するのはまだ早いです。</p><p>リコールを改善することで、より幅広い結果を得る道が開かれます。ただし、それぞれの調整にはトレードオフが伴います。だからこそ、完全なテストケースを定義し、異なる指標を使って変更を評価することが重要です。</p><p>判断リストと指標を使用すると、変更をバックアップするデータが得られるため、変更を行うときに盲目的に変更を行うことがなくなります。検証は手動で繰り返し行う必要がなくなり、変更を1つのユースケースだけでなく複数のユースケースでテストできるようになります。さらに、A/Bテストでは、どの構成がユーザーやビジネスケースに最適かをライブでテストできるため、技術的な指標と実際の指標から完全に把握できます。</p><h2>判断リストの使用に関する最終的な推奨事項</h2><p>判断リストを扱うことは、単に測定することだけでなく、自信を持って反復作業を行うためのフレームワークを作ることでもあります。これを実現するには、次の推奨事項に従ってください。</p><ol><li><strong>規模にこだわらずとにかく始める</strong>。それぞれ50個の判断リストを持つ10,000個のクエリを用意する必要はありません。ビジネスケースの最も重要な5〜10個のクエリを特定し、結果の冒頭にどの文書が表示されたいかを定義するだけで十分です。これですでに基礎が整いました。通常は、上位クエリと結果なしのクエリから始めるのが望ましいです。また、精度のような設定が簡単な指標でテストを開始し、複雑さを上げていくこともできます。</li><li><strong>ユーザーとともに検証する。</strong>本番環境でのA/Bテストで数値を補完します。こうすることで、指標で良さそうに見える変更が実際に効果を生んでいるかどうかを知ることができます。</li><li><strong>リストをアクティブに保つ。</strong>ビジネスケースは進化し、重要な問い合わせも進化していきます。新たなニーズを反映するために、定期的に判断を更新してください。</li><li><strong>フローの一部に組み込む。</strong>判断リストを開発パイプラインに統合しましょう。各構成の変更、同義語、またはテキスト分析が基本リストに対して自動的に検証されることを確認します。</li><li><strong>技術的な知識と戦略を結び付ける。</strong>精度やリコールなどの技術的な指標の測定にとどまらず、評価結果をビジネスの成果に役立てましょう。</li></ol>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[LangGraph.jsとElasticsearchを使用して金融AI検索ワークフローを構築]]></title>
            <description><![CDATA[LangGraph.jsとElasticsearchを使用して、自然言語クエリを投資や市場分析のための動的な条件付きフィルターに変換するAIを活用した金融検索ワークフローを構築する方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/ai-agent-workflow-finance-langgraph-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/ai-agent-workflow-finance-langgraph-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
            <pubDate>Fri, 05 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/12b6057d84c6356267cd178a3c6c1a5c61123ece-2000x1256.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>AI検索アプリケーションの構築では、多くの場合、複数のタスク、データ取得、データ抽出をシームレスなワークフローに調整する必要があります。LangGraphは、開発者がnodeベースの構造を使用してAIエージェントを管理することで、このプロセスを簡素化します。この記事では、<a href="https://langchain-ai.github.io/langgraphjs/">LangGraph.js</a>を使用して金融ソリューションを構築します。</p><h2>LangGraphの概要</h2><p><a href="https://langchain-ai.github.io/langgraphjs/">LangGraph</a>は、AIエージェントを構築し、ワークフロー内で管理してAI支援アプリケーションを作成するためのフレームワークです。LangGraphには、タスクを表す関数を宣言し、それらをワークフローのノードとして割り当てることができるノードアーキテクチャがあります。複数のノードが相互作用した結果がグラフになります。LangGraphは、モジュール式かつ構成可能なAIシステムを構築するためのツールを提供する、より広範な<a href="https://js.langchain.com/docs/introduction/">LangChain</a>エコシステムの一部です。</p><p>LangGraphが有用である理由をより深く理解するために、LangGraphを使用して問題のある状況を解決してみましょう。</p><h2>ソリューションの概要</h2><p>ベンチャーキャピタル企業では、投資家は多くのフィルタリングオプションを備えた大規模なデータベースにアクセスできますが、基準を組み合わせたい場合には困難で時間がかかります。これにより、関連するスタートアップの一部が投資対象として見つからない可能性があります。その結果、最適な候補を見つけるために多くの時間を費やしたり、機会を逃したりすることになります。</p><p>LangGraphとElasticsearchを使用することで、自然言語を用いてフィルターで検索することが可能となり、ユーザーが手動で複雑なリクエストを何十ものフィルターで構築する必要がなくなります。柔軟性を高めるために、ワークフローはユーザーの入力に基づいて2つのクエリタイプを自動的に決定します。</p><ul><li><strong>投資に焦点を当てたクエリ</strong>：スタートアップ企業の財務および資金調達の側面を対象としており、<a href="https://www.investopedia.com/articles/personal-finance/102015/series-b-c-funding-what-it-all-means-and-how-it-works.asp">資金調達ラウンド</a>、バリュエーション、<a href="https://www.investopedia.com/terms/r/revenue.asp">収益</a>を含みます。<em>例：</em>「シリーズAまたはシリーズBの資金調達額が800万ドル～2,500万ドルで、月間収益が50万ドルを超えるスタートアップを探してください。」</li><li><strong>市場重視のクエリ</strong>：<a href="https://en.wikipedia.org/wiki/Vertical_market">業界分野</a>、<a href="https://en.wikipedia.org/wiki/Target_market">地理的市場</a>、<a href="https://www.investopedia.com/terms/b/businessmodel.asp">ビジネスモデル</a>に重点を置き、特定のセクターまたは地域での機会の特定に役立ちます。<em>例：</em>「サンフランシスコ、ニューヨーク、ボストンのフィンテックおよびヘルスケアのスタートアップ企業を探してください」</li></ul><p>クエリを強固に保つため、LLMに<a href="https://www.elastic.co/docs/solutions/search/search-templates">検索テンプレート</a>を構築させ、完全な<a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/querydsl">DSLクエリ</a>の代わりとします。このようにすれば、必要なクエリを常に取得でき、LLMは空白を埋めるだけで済み、毎回必要なクエリを構築する責任を負う必要がなくなります。</p><h2>始めるために必要なもの</h2><ul><li>Elasticsearch APIキー</li><li>OpenAPI APIキー</li><li>Node 18以降</li></ul><h2>ステップ別のガイド</h2><p>このセクションでは、アプリがどのように見えるかを見てみましょう。<a href="https://www.typescriptlang.org/">TypeScript</a>はJavaScriptのスーパーセットで、静的な型を追加することでコードの信頼性を高め、保守性を向上させ、エラーを早期に発見して安全性を高めます。既存のJavaScriptとの完全な互換性を保ちながら、これを実現します。</p><p>ノードのフローは次のようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b47d7f238c4964a6febc0de7fe5e68b186f539c3-363x555.png" style="width: 100%;" /><p>上記の画像はLangGraphによって生成されたもので、ノード間の実行順序と条件付きロジックを定義するワークフローを表しています。</p><ul><li><strong>decideStrategy：</strong>LLMを用いてユーザーのクエリを分析し、投資重視か市場重視の2つの専門的な検索戦略のどちらかを判断します。</li><li><strong>prepareInvestmentSearch：</strong>クエリからフィルター値を抽出し、財務および資金調達関連のパラメータを強調した定義済みテンプレートを構築します。</li><li><strong>prepareMarketSearch</strong> : フィルター値も抽出しますが、市場、業界、地理的コンテキストを重視したパラメータを動的に構築します。</li><li><strong>executeSearch：</strong>検索テンプレートを使用して構築されたクエリをElasticsearchに送信し、一致するスタートアップドキュメントを取得します。</li><li><strong>visualizeResults：</strong>最終結果を、資金、業界、収益などの主要なスタートアップ属性を示す明確で読みやすい要約にフォーマットします。</li></ul><p>このフローには「if」ステートメントとして機能する<a href="https://langchain-ai.github.io/langgraphjs/how-tos/branching/?h=conditional#how-to-create-branches-for-parallel-node-execution">条件分岐が</a>含まれており、ユーザーの入力に基づいて投資検索パスを使用するか、市場検索パスを使用するかを決定します。LLMにより駆動されるこの意思決定ロジックにより、ワークフローは適応的でコンテキストに応じたものになります。このメカニズムについては次のセクションで詳しく説明します。</p><h3>LangGraphの状態</h3><p>各ノードを個別に見る前に、ノードがどのように通信し、データを共有するかを理解する必要があります。そのために、LangGraphではワークフローの状態を定義することができます。これはノード間で共有される状態を定義します。</p><p>状態は、ワークフロー全体の中間データを保存する共有コンテナとして機能します。ユーザーの自然言語クエリから始まり、選択された検索戦略、Elasticsearch用に準備されたパラメータ、取得された検索結果、最後にフォーマットされた出力が保持されます。</p><p>この構造により、すべてのノードが状態を読み取って更新できるようになり、ユーザー入力から最終的な視覚化までの一貫した情報の流れが保証されます。</p><pre><code>const VCState = Annotation.Root({
  input: Annotation<string>(), // User's natural language query
  searchStrategy: Annotation<string>(), // Search strategy chosen by LLM
  searchParams: Annotation<any>(), // Prepared search parameters
  results: Annotation<any[]>(), // Search results
  final: Annotation<string>(), // Final formatted response
});</code></pre><h3>アプリケーションをセットアップする</h3><p>このセクションのすべてのコードは<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch">elasticsearch-labsリポジトリ</a>で見つけることができます。</p><p>アプリが置かれるフォルダーでターミナルを開き、以下のコマンドで Node.js アプリケーションを初期化します。</p><pre><code>npm init -y</code></pre><p>これで、このプロジェクトに必要な依存関係をインストールできます。</p><pre><code>npm install @elastic/elasticsearch @langchain/langgraph @langchain/openai @langchain/core dotenv zod && npm install --save-dev @types/node tsx typescript</code></pre><ul><li><strong><code>@elastic/elasticsearch</code></strong>: Elasticsearchのデータインジェストや検索などのリクエストを処理するのに役立ちます。</li><li><strong><code>@langchain/langgraph</code></strong>: すべてのLangGraphツールを提供するためのJS依存関係。</li><li><strong><code>@langchain/openai</code></strong>: LangChain用のOpenAI LLMクライアント。</li><li>@langchain/core：プロンプトテンプレートなど、LangChainアプリのコアとなる基本的な構成要素を提供します。</li><li><strong><code>dotenv</code></strong>:JavaScriptで環境変数を使用するために必要な依存関係。</li><li><strong><code>zod</code></strong>：型データへの依存関係。</li></ul><p><code>@types/node</code> <code>tsx</code> <code>typescript</code> により、TypeScriptコードを記述して実行できるようになります。</p><p>次に、以下のファイルを作成します。</p><ul><li><code>elasticsearchSetup<a href="http://ingest.ts/">.ts</a></code>: Elasticsearchのマッピングを作成し、JSONファイルからデータを取り込み、Elasticsearchにデータを取り込みます。</li><li><a href="http://main.ts/"><code>main.ts</code></a>: LangGraphアプリケーションが含まれます。</li><li><code>.env</code>：環境変数を格納するファイル</li></ul><p><code>.env</code>ファイルに以下の環境変数を追加します。</p><pre><code>ELASTICSEARCH_ENDPOINT="your-endpoint-here"
ELASTICSEARCH_API_KEY="your-key-here"
OPENAI_API_KEY="your-key-here"</code></pre><p>OpenAPI APIKeyはコード上で直接使用されることはなく、ライブラリ<code>@langchain/openai</code>によって内部的に使用されます。</p><p>マッピングの作成、検索テンプレートの作成、データセットのインジェストに関するすべてのロジックは、<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/elasticsearchSetup.ts"><code>elasticsearchSetup.ts</code></a>ファイルにあります。次のステップでは、<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/main.ts"><code>main.ts</code></a>ファイルに焦点を当てていきます。また、データセットをチェックして、 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/dataset.json"><code>dataset.json</code></a>でデータがどのように表示されるかをよりよく理解することもできます。</p><h3>LangGraphアプリ</h3><p><code>main.ts</code>ファイルで、LangGraphアプリを統合するために必要な依存関係をいくつかインポートしましょう。このファイルには、ノード関数と状態宣言も含める必要があります。グラフの宣言は、次のステップで <code>main</code> メソッドで行われます。<code>elasticsearchSetup.ts</code>ファイルには、以降のステップでノード内で使用する Elasticsearch ヘルパーが含まれます。</p><pre><code>import { writeFileSync } from "node:fs";
import { StateGraph, Annotation, START, END } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
import {
  esClient,
  ingestDocuments,
  createSearchTemplates,
  INDEX_NAME,
  INVESTMENT_FOCUSED_TEMPLATE,
  MARKET_FOCUSED_TEMPLATE,
  createIndex,
} from "./elasticsearchSetup.js";

const llm = new ChatOpenAI({ model: "gpt-4o-mini" });</code></pre><p>前述のように、LLMクライアントは、ユーザーの質問に基づいてElasticsearch検索テンプレートパラメーターを生成するために使用されます。</p><pre><code>async function saveGraphImage(app: any): Promise<void> {
  try {
    const drawableGraph = app.getGraph();
    const image = await drawableGraph.drawMermaidPng();
    const arrayBuffer = await image.arrayBuffer();

    const filePath = "./workflow_graph.png";
    writeFileSync(filePath, new Uint8Array(arrayBuffer));
    console.log(`📊 Workflow graph saved as: ${filePath}`);
  } catch (error: any) {
    console.log("⚠️  Could not save graph image:", error.message);
  }
}</code></pre><p>上記の方法はグラフ画像をpng形式で生成し、裏で<a href="https://mermaid.ink/">Mermaid.INK API</a>を利用しています。これは、スタイル設定された視覚化を使用してアプリノードがどのように相互作用するかを確認する場合に便利です。</p><h3>LangGraphノード</h3><p>次に、各ノードの詳細を見てみましょう。</p><h3>decideSearchStrategyノード</h3><p><code>decideSearchStrategy</code>ノードはユーザー入力を分析し、投資重視の検索を実行するか、市場重視の検索を実行するかを決定します。構造化された出力スキーマ（Zodで定義）を持つLLMを使用してクエリタイプを分類します。決定を下す前に、集計を使用してインデックスから利用可能なフィルターを取得し、モデルが業界、場所、資金調達データに関する最新のコンテキストを持っていることを確認します。</p><p>フィルタの可能な値を抽出してLLMに送信するために、<a href="https://www.elastic.co/docs/explore-analyze/query-filter/aggregations">集計</a>クエリを使ってElasticsearchインデックスから直接値を取得してみましょう。このロジックは<code>getAvailableFilters</code>というメソッドに割り当てられます。</p><pre><code>async function getAvailableFilters() {
  try {
    const response = await esClient.search({
      index: INDEX_NAME,
      size: 0,
      aggs: {
        industries: {
          terms: { field: "industry", size: 100 },
        },
        locations: {
          terms: { field: "location", size: 100 },
        },
        funding_stages: {
          terms: { field: "funding_stage", size: 20 },
        },
        business_models: {
          terms: { field: "business_model", size: 10 },
        },
        lead_investors: {
          terms: { field: "lead_investor", size: 100 },
        },
        funding_amount_stats: {
          stats: { field: "funding_amount" },
        },
      },
    });

    return response.aggregations;
  } catch (error) {
    console.error("❌ Error getting available filters:", error);
    return {};
  }
}</code></pre><p>上記の集約クエリを用いると、以下の結果が得られます。</p><pre><code>{
  "industries": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "logistics",
        "doc_count": 5
      },
      ...
    ]
  },
  "locations": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "San Francisco, CA",
        "doc_count": 4
      },
      {
        "key": "New York, NY",
        "doc_count": 3
      },
      ...
    ]
  },
  "funding_stages": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "Series A",
        "doc_count": 8
      },
      ...
    ]
  },
  "business_models": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "B2B",
        "doc_count": 13
      },
      ...
    ]
  },
  "lead_investors": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "Battery Ventures",
        "doc_count": 1
      },
      {
        "key": "Benchmark Capital",
        "doc_count": 1
      },
      ...
    ]
  },
  "funding_amount_stats": {
    "count": 20,
    "min": 4500000,
    "max": 35000000,
    "avg": 14075000,
    "sum": 281500000
  }
}</code></pre><p>すべての結果は<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/responses/aggregationsResponse.json">こちらで</a>ご覧いただけます。</p><p>両方の戦略において、ハイブリッド検索を行うことにより、質問の構造化された部分（フィルター）とより主観的な部分（セマンティック）の両方を検出します。以下は<a href="https://www.elastic.co/docs/solutions/search/search-templates">検索テンプレート</a>を使用した両方のクエリの例です。</p><pre><code>await esClient.putScript({
      id: INVESTMENT_FOCUSED_TEMPLATE,
      script: {
        lang: "mustache",
        source: `{
          "size": 5,
          "retriever": {
            "rrf": {
              "retrievers": [
                {
                  "standard": {
                    "query": {
                      "semantic": {
                        "field": "semantic_field",
                        "query": "{{query_text}}"
                      }
                    }
                  }
                },
                {
                  "standard": {
                    "query": {
                      "bool": {
                        "filter": [
                          {"terms": {"funding_stage": {{#join}}{{#toJson}}funding_stage{{/toJson}}{{/join}}}},
                          {"range": {"funding_amount": {"gte": {{funding_amount_gte}}{{#funding_amount_lte}},"lte": {{funding_amount_lte}}{{/funding_amount_lte}}}}},
                          {"terms": {"lead_investor": {{#join}}{{#toJson}}lead_investor{{/toJson}}{{/join}}}},
                          {"range": {"monthly_revenue": {"gte": {{monthly_revenue_gte}}{{#monthly_revenue_lte}},"lte": {{monthly_revenue_lte}}{{/monthly_revenue_lte}}}}}
                        ]
                      }
                    }
                  }
                }
              ],
              "rank_window_size": 100,
              "rank_constant": 20
            }
          }
        }`,
      },
    });</code></pre><p><a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/elasticsearchSetup.ts#L119"><code>elasticsearchSetup.ts</code></a>ファイルに詳細が記載されているクエリを確認します。次のノードでは、2つのクエリのどちらを使用するかが決定されます。</p><pre><code>// Node 1: Decide search strategy using LLM
async function decideSearchStrategy(state: typeof VCState.State) {
  // Zod schema for specialized search strategy decision
  const SearchDecisionSchema = z.object({
    search_type: z
      .enum(["investment_focused", "market_focused"])
      .describe("Type of specialized search strategy to use"),
    reasoning: z
      .string()
      .describe("Brief explanation of why this search strategy was chosen"),
  });

  const decisionLLM = llm.withStructuredOutput(SearchDecisionSchema);

  // Get dynamic filters from Elasticsearch
  const availableFilters = await getAvailableFilters();

  const prompt = `Query: "${state.input}"
    Available filters: ${JSON.stringify(availableFilters, null, 2)}

    Choose between two specialized search strategies:
    
    - investment_focused: For queries about funding stages, funding amounts, monthly revenue, lead investors, financial performance
    
    - market_focused: For queries about industries, locations, business models, market segments, geographic markets
    
    Analyze the query intent and choose the most appropriate strategy.
  `;

  try {
    const result = await decisionLLM.invoke(prompt);
    console.log(
      `🤔 Search strategy: ${result.search_type} - ${result.reasoning}`
    );

    return {
      searchStrategy: result.search_type,
    };
  } catch (error: any) {
    console.error("❌ Error in decideSearchStrategy:", error.message);
    return {
      searchStrategy: "investment_focused",
    };
  }
}</code></pre><h3>prepareInvestmentSearchノードとprepareMarketSearchノード</h3><p>どちらのノードも共有ヘルパー関数<code>extractFilterValues</code>を使用します。この関数はLLMを活用して、業界、場所、資金調達段階、ビジネスモデルなど、ユーザーの入力に記載されている関連フィルターを識別します。このスキーマを使用して<a href="https://www.elastic.co/docs/solutions/search/search-templates">検索テンプレート</a>を構築します。</p><pre><code>// Extract all possible filter values from user input
async function extractFilterValues(input: string) {
  const FilterValuesSchema = z.object({
    // Investment-focused filters
    funding_stage: z
      .array(z.string())
      .default([])
      .describe("Funding stage values mentioned in query"),
    funding_amount_gte: z
      .number()
      .default(0)
      .describe("Minimum funding amount in USD"),
    funding_amount_lte: z
      .number()
      .default(100000000)
      .describe("Maximum funding amount in USD"),
    lead_investor: z
      .array(z.string())
      .default([])
      .describe("Lead investor values mentioned in query"),
    monthly_revenue_gte: z
      .number()
      .default(0)
      .describe("Minimum monthly revenue in USD"),
    monthly_revenue_lte: z
      .number()
      .default(10000000)
      .describe("Maximum monthly revenue in USD"),
    industry: z
      .array(z.string())
      .default([])
      .describe("Industry values mentioned in query"),
    location: z
      .array(z.string())
      .default([])
      .describe("Location values mentioned in query"),
    business_model: z
      .array(z.string())
      .default([])
      .describe("Business model values mentioned in query"),
  });

  const extractorLLM = llm.withStructuredOutput(FilterValuesSchema);
  const availableFilters = await getAvailableFilters();

  const extractPrompt = `Extract ALL relevant filter values from: "${input}"
    Available options: ${JSON.stringify(availableFilters, null, 2)}
    Extract only values explicitly mentioned in the query. Leave fields empty if not mentioned.`;

  return await extractorLLM.invoke(extractPrompt);
}</code></pre><p>検出された意図に応じて、ワークフローは2つのパスのいずれかを選択します。</p><p><strong>prepareInvestmentSearch：</strong>資金調達段階、資金調達額、投資家、更新情報などの財務指向の検索パラメータを構築します。クエリ テンプレート全体は<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/elasticsearchSetup.ts"><code>elasticsearchSetup.ts</code></a>ファイルで確認できます。</p><pre><code>// Node 2A: Prepare Investment-Focused Search Parameters 
async function prepareInvestmentSearch(state: typeof VCState.State) {
  console.log(
    "💰 Preparing INVESTMENT-FOCUSED search parameters with financial emphasis..."
  );

  try {
    // Extract all filter values from input
    const values = await extractFilterValues(state.input);

    let searchParams: any = {
      template_id: INVESTMENT_FOCUSED_TEMPLATE,
      query_text: state.input,
      ...values,
    };

    return { searchParams };
  } catch (error) {
    console.error("❌ Error preparing investment-focused params:", error);
    return {
      searchParams: {},
    };
  }
}</code></pre><p><strong>prepareMarketSearch：</strong>業界、地域、ビジネスモデルに重点を置いた市場主導のパラメータを作成します。クエリ全文は<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/langgraph-js-elasticsearch/elasticsearchSetup.ts"><code>elasticsearchSetup.ts</code></a>ファイルをご覧ください。</p><pre><code>// Node 2B: Prepare Market-Focused Search Parameters
async function prepareMarketSearch(state: typeof VCState.State) {
  console.log(
    "🔍 Preparing MARKET-FOCUSED search parameters with market emphasis..."
  );

  try {
    // Extract all filter values from input
    const values = await extractFilterValues(state.input);

    let searchParams: any = {
      template_id: MARKET_FOCUSED_TEMPLATE,
      query_text: state.input,
      ...values,
    };

    return { searchParams };
  } catch (error) {
    console.error("❌ Error preparing market-focused params:", error);
    return {};
  }
}</code></pre><h3>executeSearchノード</h3><p>このノードは、生成された検索パラメータを状態から取得し、最初にElasticsearchに送信します。次に、<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-render-search-template">_render API</a>を使用してデバッグの目的でクエリを視覚化し、次に結果を取得するためのリクエストを送信します。</p><pre><code>// Node 3: Execute Search
async function executeSearch(state: typeof VCState.State) {
  const { searchParams } = state;

  try {
    // getting formed query from template for debugging
    const renderedTemplate = await esClient.renderSearchTemplate({
      id: searchParams.template_id,
      params: searchParams,
    });

    console.log(
      "📋 Complete query:",
      JSON.stringify(renderedTemplate.template_output, null, 2)
    );

    const results = await esClient.searchTemplate({
      index: INDEX_NAME,
      id: searchParams.template_id,
      params: searchParams,
    });

    return {
      results: results.hits.hits.map((hit: any) => hit._source),
    };
  } catch (error: any) {
    console.error(`❌ ${state.searchParams.search_type} search error:`, error);
    return { results: [] };
  }
}</code></pre><h3>visualizeResultsノード</h3><p>最後に、このnodeはElasticsearchの結果を表示します。</p><pre><code>// Node 4: Visualize results
async function visualizeResults(state: typeof VCState.State) {
  const results = state.results || [];

  let formattedResults = `🎯 Found ${results.length} startups matching your criteria:\n\n`;

  results.forEach((startup: any, index: number) => {
    formattedResults += `${index + 1}. **${startup.company_name}**\n`;
    formattedResults += `   📍 ${startup.location} | 🏢 ${startup.industry} | 💼 ${startup.business_model}\n`;
    formattedResults += `   💰 ${startup.funding_stage} - $${(
      startup.funding_amount / 1000000
    ).toFixed(1)}M\n`;
    formattedResults += `   👥 ${startup.employee_count} employees | 📈 $${(
      startup.monthly_revenue / 1000
    ).toFixed(0)}K MRR\n`;
    formattedResults += `   🏦 Lead: ${startup.lead_investor}\n`;
    formattedResults += `   📝 ${startup.description}\n\n`;
  });

  return {
    final: formattedResults,
  };
}</code></pre><p>プログラム的には、グラフ全体は次のようになります。</p><pre><code>  const workflow = new StateGraph(VCState)
    // Register nodes - these are the processing functions
    .addNode("decideStrategy", decideSearchStrategy)
    .addNode("prepareInvestment", prepareInvestmentSearch)
    .addNode("prepareMarket", prepareMarketSearch)
    .addNode("executeSearch", executeSearch)
    .addNode("visualizeResults", visualizeResults)
    // Define execution flow with conditional branching
    .addEdge(START, "decideStrategy") // Start with strategy decision
    .addConditionalEdges(
      "decideStrategy",
      (state: typeof VCState.State) => state.searchStrategy, // Conditional function
      {
        investment_focused: "prepareInvestment", // If investment focused -> RRF template preparation
        market_focused: "prepareMarket", // If market focused -> dynamic query preparation
      }
    )
    .addEdge("prepareInvestment", "executeSearch") // Investment prep -> execute
    .addEdge("prepareMarket", "executeSearch") // Market prep -> execute
    .addEdge("executeSearch", "visualizeResults") // Execute -> visualize
    .addEdge("visualizeResults", END); // End workflow</code></pre><p>ご覧のとおり、アプリが次にどの「パス」またはノードを実行するかを決定する条件付きエッジがあります。この特徴は、ワークフローに分岐ロジックが必要な場合、例えば複数のツールから選択する場合や、人間が関与するステップを含む場合に有用です。</p><p>LangGraph のコア機能を理解したら、コードが実行されるアプリケーションをセットアップできます。</p><p>すべてを<code>main</code>メソッドで組み合わせ、ここではすべての要素をワークフロー変数下のグラフとして宣言します。</p><pre><code>async function main() {
  await createIndex();
  await createSearchTemplates();
  await ingestDocuments();

  // Create the workflow graph with shared state
  const workflow = new StateGraph(VCState)
    // Register nodes - these are the processing functions
    .addNode("decideStrategy", decideSearchStrategy)
    .addNode("prepareInvestment", prepareInvestmentSearch)
    .addNode("prepareMarket", prepareMarketSearch)
    .addNode("executeSearch", executeSearch)
    .addNode("visualizeResults", visualizeResults)
    // Define execution flow with conditional branching
    .addEdge(START, "decideStrategy") // Start with strategy decision
    .addConditionalEdges(
      "decideStrategy",
      (state: typeof VCState.State) => state.searchStrategy, // Conditional function
      {
        investment_focused: "prepareInvestment", // If investment focused -> RRF template preparation
        market_focused: "prepareMarket", // If market focused -> dynamic query preparation
      }
    )
    .addEdge("prepareInvestment", "executeSearch") // Investment prep -> execute
    .addEdge("prepareMarket", "executeSearch") // Market prep -> execute
    .addEdge("executeSearch", "visualizeResults") // Execute -> visualize
    .addEdge("visualizeResults", END); // End workflow


  const app = workflow.compile();

  await saveGraphImage(app);

  const query =
    "Find startups with Series A or Series B funding between $8M-$25M and monthly revenue above $500K";

  const marketResult = await app.invoke({ input: query });
  console.log(marketResult.final);
}</code></pre><p>クエリ変数は、仮想の検索バーに入力されたユーザー入力をシミュレートします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e8d76909eb2bc1bb62f3ca9a8b3e4b85fcec2893-1600x164.png" style="width: 100%;" /><p>「シリーズAまたはシリーズBの資金調達額が800万ドル～2,500万ドルで、月間収益が50万ドルを超えるスタートアップを探してください。」という自然言語フレーズから、すべてのフィルターが抽出されます。</p><p>最後にmainメソッドを呼び出します。</p><pre><code>main().catch(console.error);</code></pre><h3>成果</h3><pre><code>🔍 Checking if index exists...
🏗️ Creating index...
✅ Index created successfully!
Ingesting documents...
✅ Documents ingested successfully!
✅ Investment-focused template created successfully!
✅ Market-focused template created successfully!

📊 Workflow graph saved as: ./workflow_graph.png

🔍 Query: "Find startups with Series A or Series B funding between $8M-$25M and monthly revenue above $500K"

🤔 Search strategy: investment_focused - The query specifically seeks profitable fintech startups with defined funding amounts and high monthly revenue, which aligns closely with financial performance metrics and investment-related criteria.

💰 Preparing INVESTMENT-FOCUSED search parameters with financial emphasis...

📋 Complete query: {
  "size": 5,
  "retriever": {
    "rrf": {
      "retrievers": [
        {
          "standard": {
            "query": {
              "semantic": {
                "field": "semantic_field",
                "query": "Find startups with Series A or Series B funding between $8M-$25M and monthly revenue above $500K"
              }
            }
          }
        },
        {
          "standard": {
            "query": {
              "bool": {
                "filter": [
                  {
                    "terms": {
                      "funding_stage": [
                        "Series A",
                        "Series B"
                      ]
                    }
                  },
                  {
                    "range": {
                      "funding_amount": {
                        "gte": 8000000,
                        "lte": 25000000
                      }
                    }
                  },
                  {
                    "terms": {
                      "lead_investor": []
                    }
                  },
                  {
                    "range": {
                      "monthly_revenue": {
                        "gte": 500000,
                        "lte": 0
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      ],
      "rank_window_size": 100,
      "rank_constant": 20
    }
  }
}
🎯 Found 5 startups matching your criteria:

1. **TechFlow**
   📍 San Francisco, CA | 🏢 logistics | 💼 B2B
   💰 Series A - $8.0M
   👥 45 employees | 📈 $500K MRR
   🏦 Lead: Sequoia Capital
   📝 TechFlow optimizes supply chain operations using AI-powered route optimization and real-time tracking. Founded in 2023, shows remarkable growth with $500K monthly revenue.

2. **DataViz**
   📍 New York, NY | 🏢 enterprise software | 💼 B2B
   💰 Series A - $10.0M
   👥 42 employees | 📈 $450K MRR
   🏦 Lead: Battery Ventures
   📝 DataViz creates intuitive data visualization tools for enterprise customers. No-code platform allows business users to create dashboards without technical expertise.

3. **FinanceAI**
   📍 San Francisco, CA | 🏢 fintech | 💼 B2C
   💰 Series C - $25.0M
   👥 120 employees | 📈 $1200K MRR
   🏦 Lead: Tiger Global Management
   📝 FinanceAI provides AI-powered investment advisory services to retail investors. Uses machine learning to analyze market trends with over 100,000 active users.

4. **UrbanMobility**
   📍 New York, NY | 🏢 logistics | 💼 B2B2C
   💰 Series B - $15.0M
   👥 78 employees | 📈 $750K MRR
   🏦 Lead: Kleiner Perkins
   📝 UrbanMobility revolutionizes urban transportation through autonomous delivery drones and smart logistics hubs. Partners with major retailers for same-day delivery across Manhattan and Brooklyn.

5. **HealthTech Solutions**
   📍 Boston, MA | 🏢 healthcare | 💼 B2B
   💰 Series B - $18.0M
   👥 95 employees | 📈 $900K MRR
   🏦 Lead: General Catalyst
   📝 HealthTech Solutions develops medical devices and software for remote patient monitoring. Comprehensive telehealth platform reducing hospital readmissions by 30%.

✨  Done in 18.80s.</code></pre><p>送信された入力に対して、アプリケーションは<strong>投資に重点を置いた</strong>パスを選択し、その結果、ユーザー入力から値と範囲を抽出するLangGraphワークフローによって生成されたElasticsearchクエリを確認できます。また、抽出された値が適用された状態でElasticsearchに送信されたクエリと、最後に<code>visualizeResults</code>ノードによって結果がフォーマットされた結果も確認できます。</p><p>次に、<strong>市場重視</strong>のノードを、クエリ「サンフランシスコ、ニューヨーク、ボストンのフィンテックおよびヘルスケアのスタートアップ企業を探してください」を使用してテストしてみましょう。</p><pre><code>...

🔍 Query: Find fintech and healthcare startups in San Francisco, New York, or Boston

🤔 Search strategy: market_focused - The query is focused on finding fintech startups in San Francisco that are disrupting traditional banking and payment systems, which pertains to specific industries (fintech) and locations (San Francisco). Thus, a market-focused strategy is more appropriate.

🔍 Preparing MARKET-FOCUSED search parameters with market emphasis...

📋 Complete query: {
  "size": 5,
  "retriever": {
    "rrf": {
      "retrievers": [
        {
          "standard": {
            "query": {
              "semantic": {
                "field": "semantic_field",
                "query": "Find fintech and healthcare startups in San Francisco, New York, or Boston"
              }
            }
          }
        },
        {
          "standard": {
            "query": {
              "bool": {
                "filter": [
                  {
                    "terms": {
                      "industry": [
                        "fintech",
                        "healthcare"
                      ]
                    }
                  },
                  {
                    "terms": {
                      "location": [
                        "San Francisco, CA",
                        "New York, NY",
                        "Boston, MA"
                      ]
                    }
                  },
                  {
                    "terms": {
                      "business_model": []
                    }
                  }
                ]
              }
            }
          }
        }
      ],
      "rank_window_size": 50,
      "rank_constant": 10
    }
  }
}
🎯 Found 5 startups matching your criteria:

1. **FinanceAI**
   📍 San Francisco, CA | 🏢 fintech | 💼 B2C
   💰 Series C - $25.0M
   👥 120 employees | 📈 $1200K MRR
   🏦 Lead: Tiger Global Management
   📝 FinanceAI provides AI-powered investment advisory services to retail investors. Uses machine learning to analyze market trends with over 100,000 active users.

2. **CryptoWallet**
   📍 Miami, FL | 🏢 fintech | 💼 B2C
   💰 Series B - $16.0M
   👥 73 employees | 📈 $820K MRR
   🏦 Lead: Coinbase Ventures
   📝 CryptoWallet provides secure digital wallet solutions for cryptocurrency trading and storage. Multi-chain support with enterprise-grade security features.

...

✨  Done in 7.41s.</code></pre><h2>学び</h2><p>執筆の過程で次のことを学びました。</p><ul><li>LLMにフィルターの正確な値を表示する必要があります。そうしないと、ユーザーが正確な値を入力することになります。カーディナリティが低い場合はこのアプローチで問題ありませんが、カーディナリティが高い場合は結果をフィルタリングする何らかのメカニズムが必要です。</li><li>検索テンプレートを使用すると、LLMにElasticsearchクエリを記述させるよりも結果の一貫性が大幅に向上し、速度も速くなります。</li><li>条件付きエッジは、複数のバリアントと分岐パスを持つアプリケーションを構築するための強力なメカニズムです。</li><li>構造化された出力は、予測可能でタイプセーフな応答を強制するため、LLMを使用して情報を生成する場合に非常に役立ちます。これにより、信頼性が向上し、プロンプトの誤解が減少します。</li></ul><p>ハイブリッド検索を通じてセマンティック検索と構造化検索を組み合わせることで、精度とコンテキスト理解のバランスを保ちながら、より適切で関連性の高い結果が生成されます。</p><h2>まとめ</h2><p>この例では、LangGraph.jsとElasticsearchを組み合わせて、自然言語クエリをElasticsearchで検索し、金融と市場のいずれかワークフローを焦点を当てた検索戦略をワークフローで決定できる動的なワークフローを作成します。このアプローチにより、手動クエリ作成の複雑さが軽減され、ベンチャーキャピタルアナリストの柔軟性と精度が向上します。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[変数コントロールによるKibanaダッシュボードのインタラクション性の向上]]></title>
            <description><![CDATA[Kibana 8.18+の変数コントロールを使用して、個々の可視化をフィルタリングし、時間間隔を調整し、Kibanaダッシュボード内の異なるフィールドでグループ化する方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/kibana-dashboard-interactivity-variable-controls-overview</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/kibana-dashboard-interactivity-variable-controls-overview</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Teresa Alvarez Soler]]></dc:creator>
            <pubDate>Thu, 04 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/59aa3adffc8c759e42b961ef7d63719ce232893a-1348x830.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>バージョン8.18以降および9.xシリーズすべての<strong>Kibanaダッシュボードで変数コントロールが利用できるようになりました</strong>。ダッシュボードユーザーから最も継続的にリクエストされていた追加機能の1つがついに導入されました 🎉 過去数か月間、変数コントロールの拡張と改良を続けてきたため、<a href="https://www.elastic.co/docs/explore-analyze/dashboards/add-controls#add-variable-control">変数コントロール</a>専用のブログ投稿を作成するのに絶好のタイミングとなりました。</p><h2>変数コントロールとは何ですか？</h2><p>Kibanaのダッシュボードを使用したことがある方なら、クラシックなダッシュボードコントロール（データの値を表示する便利なドロップダウン）をご存知でしょう。これにより、数回のクリックでフィルタリングできます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b82c1b25a0b38661e5ce4552f763be487d5074aa-1600x701.png" style="width: 100%;" /><p>変数コントロールは表面上では似ていますが、巧妙な工夫が施されています。ダッシュボード上のすべてのパネルを自動的にフィルター処理するのではなく、<a href="https://www.elastic.co/docs/explore-analyze/visualize/esorql">個々の可視化内の ES|QLクエリ</a>に直接プラグインすることができます。</p><p>つまり、各コントロールを適用する場所を<em>ユーザーが決定できる</em>ということです。さらに、時間間隔の調整、内訳フィールドの切り替え、可視化パラメーターの即時変更など、さまざまなクリエイティブなトリックに使用できます。基本的に、ダッシュボードに真のインタラクティブな体験を提供し、より速く、より簡単に洞察を得られるようにしています。</p><h2>変数コントロールのユースケース</h2><p>変数コントロールは便利そうですが、実際に何ができるのでしょうか？ダッシュボードをレベルアップさせる例をいくつかご紹介します。</p><h3>選択された可視化をフィルター</h3><p><em>一部の</em>可視化をフィルターし、他の可視化はそのままにしておきたい場合は、変数コントロールがまさに最適です。必要なパネルを選択して、可視化の背後にあるES|QLクエリでそれらを接続できます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/efa367363830b03bc67028aceafe78c4b44e578f-1440x562.gif" style="width: 100%;" /><h3>異なる時間間隔を選択</h3><p>ユーザーが「5分」、「1時間」、「1日」など、適切な時間枠を切り替えることができるようにします。事前定義された間隔で変数コントロールを構築し、それを時系列クエリに接続します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/62aa9f4e728036f8c70213b76b1cf131f36f5b4d-1440x606.gif" style="width: 100%;" /><h3>機能を変更</h3><p>各操作ごとに複数のチャートを作成する代わりに、ダッシュボードユーザーが最大値、平均値、異なるパーセンタイル、またはその他のアグリゲーターを選択できるようにします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f6a2b4c73dc35efe462c2924a153d7b3fa3a7922-1436x606.gif" style="width: 100%;" /><h3>異なるフィールドでグループ化</h3><p>調査中に、データをさまざまな次元で分類する必要がある場合があります。変数コントロールを使用すると、複数の「グループ化」フィールドを定義し、ダッシュボードユーザーが分析情報を明らかにするのに役立つフィールドを選択できるようにすることができます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fe8745a6fddccadba0666686b8ebc67fdaf64158-1438x606.gif" style="width: 100%;" /><h2>作成方法</h2><p>変数コントロールを作成する最も簡単な（そしておそらく最も楽しい）方法は、可視化の<strong>ES|QLクエリエディタから</strong>直接作成することです。クエリを入力し始め、オートコンプリートメニューを使用すれば、Kibanaが役立つコントロールを自動的に作成します。</p><p>ただし、変数自体から開始したい場合は<strong>「パネルを追加」→「コントロール」→「変数コントロール」</strong>に進み、コントロールを作成した後で変数を可視化に追加することもできます。</p><h3>例1：複数値選択によるフィルタリングコントロール</h3><p>1. ES|QLクエリを利用した可視化を選択し、WHERE句内の「コントロールを作成」をクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/46cb6f2a6775aee152d42eb5ee85170f1bdf26cb-1600x668.png" style="width: 100%;" /><p>2. 自動的に変数作成のポップアップにリダイレクトされます。ここでは「クエリからの値」タイプが自動で選択され、変数の名前も既に入力されています。可視化クエリで動作するよう、コントロールの名前は常に「?...」で始める必要があります。</p><p>通常、フィールドから値を取得し、ダッシュボードで選択した時間範囲に応じて値を更新するには、次のようなクエリが必要になります。</p><pre><code>FROM <datasource_name>
| WHERE @timestamp <=?_tend and @timestamp >?_tstart
| STATS BY <field_name></code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a2a72d4e3159923c6207908da9b4172e27cd5f81-1600x716.png" style="width: 100%;" /><p>3. コントロールを保存すると、それがダッシュボードのトップに表示され、可視化クエリが変数コントロール名で更新されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5fc434c8951889e9769652b675191711d126a685-1600x653.png" style="width: 100%;" /><p>4. コントロールに<a href="https://www.elastic.co/docs/explore-analyze/dashboards/add-controls#esql-multi-values-controls">多重選択</a>を追加したい場合は、ステップ2でクエリ内の <code>MV_CONTAINS</code> 関数を使用し、「複数選択を許可」を選択する必要があります（9.3以降で利用可能）。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1f237cea0a37cb25a7917a2a683707a269adae8e-1600x670.png" style="width: 100%;" /><h3>例 2: 時間間隔制御</h3><p>時系列を構築する場合は、日付ヒストグラム間隔に変数コントロールを簡単に追加できます。</p><p>1. 時系列のES|QLクエリを記述するときは、「コントロールを作成」をクリックします。変数を間隔用に作成する際は、<code>BUCKET</code> の代わりに <code>TBUCKET</code> を使用する方が良いです。これにより、「1 hour」、「1 day」などの見やすい間隔を受け入れることができます。<code>TBUCKET</code>には、時間範囲に自動的に適応できる自動オプションも近日中に導入される予定です。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b0ad53d790ff9bdd42db5e77477318319f423534-1600x664.png" style="width: 100%;" /><p>2. ドロップダウンメニューでオプションを入力する間隔を定義します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f3bd83f530cfa4698c1a3b1ae60d08d0414043b5-1600x757.png" style="width: 100%;" /><p>3. ドロップダウンメニューで異なる間隔を選択し、可視化がどのように変化するかを確認します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0f928d9c70929f64926e065059188d140cd48943-1600x671.png" style="width: 100%;" /><h3>例3：関数の変数</h3><ol><li>「静的値」タイプのコントロールを使用して変数を作成し、ドロップダウン値に関数名を追加します。関数を置き換えるには、「??...」で始まる変数名を使用することが重要です。</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/531444237b7e152d3c8a6f3ca7e464f954f9e856-1600x663.png" style="width: 100%;" /><p>2. ES|QLクエリに変数名を含めます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9858442abb26d8d266d464852871b139fde63b89-1600x665.png" style="width: 100%;" /><h3>例4：フィールドの変数</h3><ol><li>「静的値」タイプのコントロールを使用して、必要なフィールドの名前を書き留めることができます。フィールドで機能させるためには「??...」で始まる変数名を使用することが重要です。</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/33534c3df2fae024b25c28b4aed5d742e54202a2-1600x710.png" style="width: 100%;" /><p>2. 可視化クエリで任意の場所に変数を参照します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/71cdf3e9df72c59d957628a3aa6e4aa9bd60d6d5-1600x676.png" style="width: 100%;" /><h2>Discoverの変数コントロール</h2><p>変数コントロールは単なるダッシュボードの特徴ではなく、DiscoverのES|QLエディターでも直接利用可能です。Discoverでより高速なデータ探索エクスペリエンスを実現するコントロールを構築し、それをダッシュボードに表示したり、その逆を行ったりすることができます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fdddeec902d0bc746caed9276d01d7d48793dd85-1600x709.png" style="width: 100%;" /><h2>技術的な詳細</h2><p>ここまでで、変数コントロールには、クエリのどの部分を参照できるか、使用すべき命名プレフィックス（値の場合は「?...」、フィールドまたは関数の場合は「??...」）などのいくつかのルールがあることに気付かれたでしょう。これは、変数がクライアント上で行われる単純な文字列置換ではなく、実際にはクエリ言語自体の第一級オブジェクトであるためです（<a href="https://www.elastic.co/docs/solutions/search/agent-builder/tools/esql-tools#parameter-types">ES|QL ではパラメーター</a>と呼ばれます）。</p><p></p><p>この設計にはいくつかの大きな利点があります。1つは、Kibanaが各変数のコンテキストを理解できるため、設定を自動的に生成して事前入力できることです。また、この言語が変数の入力を厳密に検証し、悪意のある挿入を防ぎ、何かおかしい点があれば適切にエラーを出力するため、安全性もはるかに高くなります。さらに、複雑な検証とエラー処理をクライアントではなくサーバー側に移動することで、パフォーマンスと安定性が向上します。パフォーマンスに関する注意点として、ベストプラクティスは、高速クエリを含む変数を構築することです。これにより、ダッシュボードよりも先に読み込まれ、遅いクエリがダッシュボード全体のパフォーマンスに影響を与えることを防ぎます。</p><p>もちろん、このアーキテクチャーにも（現時点では）いくつかの<a href="https://www.elastic.co/docs/solutions/search/agent-builder/limitations-known-issues#esql-limitations">制限</a>があります。変数はまだフィルタリング用の「任意」オプションをサポートしておらず、現在、<code>LIKE</code><strong> </strong>や<code>FROM</code>（データソースの切り替え用）のような特定の演算子では使用できません。幸い、当社はこれらの機能を追加するために積極的に取り組んでいます。</p><h2>コントロールの今後</h2><p>この機能はまだ完成ではありません。以下のような改善を予定しています。</p><p>✨ ダッシュボードのどこにでもコントロールを配置できる機能</p><p>✨ コントロールの連鎖（一つのコントロールの出力が次のコントロールのインプットになるよう）</p><p>✨ 変数の「任意」選択のようなより良い選択オプション</p><p>✨ 新しいコントロールタイプ（検索するタイプのコントロールとデータソースの変数）</p><p>✨ さらに、ユーザーの皆様のご要望に応え、通常コントロールの事前フィルタリングなどの操作性の改善</p><p>アイデアやご意見があれば、ぜひお聞かせください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[NVIDIA cuVSでElasticsearchのベクトルインデキシングを最大12倍高速化：GPUアクセラレーション 第2章]]></title>
            <description><![CDATA[ElasticsearchがGPUアクセラレーションによるベクトルインデキシングとNVIDIA cuVSを使用してほぼ12倍のインデキシングスループットを達成する方法をご覧ください。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-gpu-accelerated-vector-indexing-nvidia</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-gpu-accelerated-vector-indexing-nvidia</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Chris Hegarty, Hemant Malik, Corey Nolet, Manas Singh, Mithun Radhakrishnan, Mayya Sharipova, Lorenzo Dematte, Ben Frederickson]]></dc:creator>
            <pubDate>Wed, 03 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/08f7469a4daaf67b7c5999585aae179b6680c78d-896x746.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>今年の初め、ElasticはNVIDIAとの<a href="https://ir.elastic.co/news/news-details/2025/Elastic-Brings-Enterprise-Data-to-NVIDIA-AI-Factories/default.aspx">協業</a>を発表し、ElasticsearchにGPUアクセラレーションをもたらすために<a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS</a>と統合しました。これは<a href="https://www.nvidia.com/en-us/on-demand/session/gtc25-S71286/">NVIDIA GTCのセッション</a>やさまざまな<a href="https://www.elastic.co/search-labs/blog/gpu-accelerated-vector-search-elasticsearch-nvidia">ブログ</a>で詳しく説明されています。この投稿は、NVIDIAのベクトル検索チームとの共同エンジニアリング作業の最新情報です。</p><h2>要約</h2><p>まず、現状をお伝えしましょう。Elasticsearchは、強力なベクトルデータベースとして確立され、大規模な類似性検索に対して豊富な特徴と強力なパフォーマンスを提供しています。スカラー量子化、Better Binary Quantization（<a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">BBQ</a>）、<a href="https://www.elastic.co/blog/accelerating-vector-search-simd-instructions">SIMD</a>ベクトル演算、<a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction">DiskBBQ</a>のようなよりディスク効率の高いアルゴリズムなどの機能により、すでにベクトルワークロードの管理に効率的かつ柔軟な選択肢を提供しています。</p><p>NVIDIA cuVSをベクトル検索タスク用の呼び出し可能なモジュールとして統合することで、ベクトルインデキシングのパフォーマンスと効率を大幅に向上させ、大規模なベクトルワークロードをより良くサポートすることを目指しています。</p><h2>課題</h2><p>高性能ベクトルデータベースを構築する上で最も困難な課題の一つは、ベクトルインデックス（<a href="https://arxiv.org/abs/1603.09320">HNSW</a>グラフ）を構築することです。インデックス構築は、すべてのベクトルが他の多数のベクトルと比較されるため、すぐに数百万、あるいは数十億の算術演算によって支配されるようになります。さらに、インデックスのライフサイクル操作、例えば圧縮やマージなどは、インデキシングの全体的な計算オーバーヘッドをさらに増加させる可能性があります。データ量と関連するベクトル埋め込みが指数関数的に増加するにつれ、大規模な並列処理と高スループットの数学演算用に構築された高速コンピューティングGPUは、これらのワークロードを処理するのに理想的な位置にあります。</p><h2>Elasticsearch-GPUプラグインの登場</h2><p><a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS</a>は、GPUによるベクトル検索とデータクラスタリングのためのオープンソースCUDA-Xライブラリであり、AIおよび推奨ワークロード向けの高速インデックス構築と埋め込み検索を可能にします。</p><p>Elasticsearchは<a href="https://mvnrepository.com/artifact/com.nvidia.cuvs/cuvs-java">cuVSをcuvs-javaを通じて使用して</a>います。cuvs-javaはコミュニティが開発し、NVIDIAが保守するオープンソースライブラリです。cuvs-javaライブラリは軽量で、<a href="https://docs.rapids.ai/api/cuvs/nightly/c_api/">cuVS C API</a>をベースに<a href="https://openjdk.org/projects/panama/">Panama</a> Foreign Functionを使用して、cuVSの特徴をJavaらしい方法で公開しつつ、モダンな高性能を維持しています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5f6daa1eb07f704a6707d9e6b7ccb81d0abaa8c9-566x419.png" style="width: 100%;" /><p>cuvs-javaライブラリは<a href="https://github.com/elastic/elasticsearch/pull/135545">新しいElasticsearchプラグイン</a>に統合されています。そのため、GPU上でのインデキシングを同じElasticsearchノードとプロセスで実行でき、外部のコードやハードウェアを提供する必要はありません。CUVsライブラリがインストールされていて、GPUが存在して構成されている場合、インデキシング中にElasticsearchはGPUを使用してベクターインデキシング処理を高速化します。ベクトルはGPUに提供され、GPUは<a href="https://arxiv.org/abs/2308.15136">CAGRA</a>グラフを構築します。その後、このグラフはHNSW形式に変換され、CPU上でのベクトル検索にすぐに利用可能になります。構築されたグラフの最終的な形式は、CPU上に構築されるものと同じです。これによりElasticsearchは、基盤となるハードウェアがサポートしている場合、GPUを活用して高スループットのベクトルインデックスを作成し、CPUのパワーを他のタスク（同時検索やデータ処理など）に解放することができます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3ea255bd9bfd7983f78143c5eba999d2149d72be-671x356.png" style="width: 100%;" /><h2>インデックス構築の加速</h2><p>ElasticsearchにGPUアクセラレーションを統合する一環として、cuvs-javaにいくつかの機能強化が行われ、効率的なデータのインプット/出力と関数呼び出しに焦点が当てられました。主要な機能強化は、<a href="https://github.com/rapidsai/cuvs/blob/2cf5fa7666d703dccbe655f8214656b0952bb69b/java/cuvs-java/src/main/java/com/nvidia/cuvs/CuVSMatrix.java">cuVSMatrix</a>を使用して、Javaヒープ、オフヒープ、またはGPUメモリに存在するベクトルを透過的にモデル化することです。これにより、データをメモリとGPU間で効率的に移動でき、潜在的に数十億のベクトルの不要なコピーを回避できます。</p><p>この基礎となるゼロコピー抽象化のおかげで、GPUメモリへの転送とグラフの取得の両方が直接実行できます。インデキシング中、ベクトルは最初にJavaヒープ上のメモリにバッファリングされ、その後GPUに送られてCagraグラフを構築します。その後、グラフはGPUから取得され、HNSW形式に変換され、ディスクに保存されます。</p><p>マージ時には、ベクトルはすでにディスクに格納されており、Javaヒープを完全にバイパスします。インデックスファイルはメモリマップされ、データは直接GPUメモリに転送されます。この設計は、float32やint8などのさまざまなビット幅にも簡単に対応し、他の量子化スキームにも自然に拡張できます。</p><h2>実際のパフォーマンス</h2><p>数字を見てみる前に、少し背景を説明しておきましょう。Elasticsearchのセグメントマージは通常、インデキシング中にバックグラウンドで自動的に実行されるため、分離してベンチマークをとることが難しくなります。再現可能な結果を得るために、制御された実験でforce-mergeを使用してセグメントのマージを明示的にトリガーしました。force-mergeはバックグラウンドマージと同じ基礎となるマージ操作を実行するので、実際のインデキシングワークロードでは正確な効果が異なる場合でも、そのパフォーマンスは期待される改善を示す有用な指標となります。</p><p>さて、数字を見てみましょう。</p><p>最初のベンチマーク結果は非常に有望です。ベンチマークは、ローカルに接続されたNVMeストレージを持つAWS <code>g6.4xlarge</code>インスタンスで実行しました。Elasticsearchのシングルノードは、デフォルトの最適なインデキシングスレッド数（各物理コアに1つずつの計8つ）を使用し、<a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/merge">マージスロットリング</a>（高速NVMeディスクではあまり適用されません）を無効にするように設定しました。</p><p>データセットには<a href="https://github.com/elastic/rally-tracks/blob/master/openai_vector/README.md">OpenAI Rallyベクトルトラック</a>から取得した1,536次元のベクトル260万個を<a href="https://github.com/elastic/elasticsearch/pull/137072">base64文字列</a>としてエンコードし、float32 <em>hnsw</em>としてインデックスして使用しました。すべてのシナリオにおいて、構築されたグラフは最大95%のリコールレベルを達成します。結果は以下となりました。</p><ul><li><strong>インデキシングのスループット：</strong>メモリ内バッファのフラッシュ中にグラフ構築を GPU に移動することで、スループットが約 12 倍向上します。</li><li><strong>強制マージ：</strong>インデキシングが完了した後、GPUはセグメントのマージを加速し続け、強制マージフェーズを約7倍高速化します。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f60ea9ee5996e456f393ffd195ee7eada6e5a7c2-948x387.png" style="width: 100%;" /><ul><li><strong>CPU使用率：</strong>グラフ構築をGPUにオフロードすると、平均およびピーク時のCPU使用率が大幅に削減されます。以下のグラフは、インデキシングとマージ中のCPU使用率を示しており、これらの操作をGPUで実行すると使用率がどれだけ低くなるかを強調しています。GPUインデキシング中のCPU使用率が低下すると、CPUサイクルが解放され、検索パフォーマンスの向上に向けることができます。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5e680a5fc41700a877f3d8b2e5ce18ebd3f37a0b-1600x562.png" style="width: 100%;" /><ul><li><strong>リコール：</strong>CPU実行とGPU実行の精度は実質的に同じですが、GPUで構築されたグラフのリコールはわずかに高くなります。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/48a2b7758606bd321712b7d8378cd2640e652a4e-1384x544.png" style="width: 100%;" /><h2>別の次元での比較：価格</h2><p>先ほどの比較では、意図的に同一のハードウェアが使用されており、唯一の違いはインデキシング中にGPUが使用されたかどうかでした。この設定は、生のコンピューティング効果を分離するのに役立ちますが、コストの観点から比較することもできます。</p><p>GPUアクセラレーション構成とほぼ同じ時間単価で、同等のCPUおよびメモリリソースの約2倍（32個のvCPU（AMD EPYC）と64GBのRAM）を備えたCPUのみのセットアップをプロビジョニングでき、インデキシングスレッドの数を2倍の16に増やすことができます。</p><p>比較を公平かつ一貫性のあるものにするために、このCPUのみの実験をAWS g6.8xlargeインスタンスで実行しました。GPUは明示的に無効になっています。これにより、GPUアクセラレーションとCPUのみのインデキシングのコストパフォーマンスのトレードオフを評価する際、他のすべてのハードウェア特性を一定に保つことができました。</p><p>予想どおり、より強力なCPUインスタンスでは、上記のセクションのベンチマークと比較してパフォーマンスが向上しています。しかし、このより強力なCPUインスタンスを元のGPUアクセラレーション結果と比較すると、GPUは、リコールレベル最大<strong>95%</strong>に達するグラフを構築しながら<strong>最大5倍</strong>のインデキシングスループット向上、<strong>最大6倍</strong>のフォースマージと、依然として大幅なパフォーマンス向上を提供します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8ffa58cae3ad175ef2932a351aeef4c34a1407b9-948x394.png" style="width: 100%;" /><h2>結論</h2><p>エンドツーエンドのシナリオでは、NVIDIA cuVSによるGPUアクセラレーションにより、インデキシングのスループットが約12倍向上し、force-mergeのレイテンシが7倍減少し、CPU使用率が大幅に低下します。これは、ベクトルインデキシングとマージワークロードがGPUアクセラレーションから大きな恩恵を受けることを示しています。コスト調整後の比較では、GPUアクセラレーションは引き続き大幅なパフォーマンス向上をもたらし、インデキシングのスループットは約5倍、force-merge操作は6倍高速化されます。</p><p>GPUアクセラレーションによるベクトルインデキシングは、現在Elasticsearch 9.3の技術プレビューで計画されており、2026年初頭にリリース予定です。</p><p>続報をお楽しみに。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ES|QL 9.2：スマートになったLookup Joinと時系列サポート]]></title>
            <description><![CDATA[Elasticsearch 9.2のES|QLでは、データ相関をより表現豊かにするためのLOOKUP JOIN強化、時系列分析用の新しいTSコマンド、集計用の柔軟なINLINE STATSコマンドの3つのアップデートを行いました。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/esql-elasticsearch-9-2-multi-field-joins-ts-command</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/esql-elasticsearch-9-2-multi-field-joins-ts-command</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Tyler Perkins, Kostas Krikellas, Julian Kiryakov]]></dc:creator>
            <pubDate>Tue, 02 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/415894e21e7758c907d6e60d4efc94230349beef-2012x1164.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>10月にリリースされたElasticsearch 9.2には、データの分析をこれまで以上に高速化、柔軟化、アクセスしやすくするための大きな進化が詰まっています。このリリースの中心となるのは、パイプクエリ言語であるES|QLの重要な機能強化で、エンドユーザーに直接さらに多くの価値をもたらすように設計されています。</p><p>ES|QLを使用してデータ分析ワークフローを変革するElasticsearch 9.2の特徴を見てみましょう。</p><h2>データ相関の革命：よりスマート高速、柔軟になったLookup Join</h2><p>ES|QLの<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join">LOOKUP JOIN</a>コマンドはElasticsearch 9.2で大幅に変更され、効率性と汎用性が飛躍的に向上しました。LOOKUP JOINは、ES|QLクエリ結果テーブルのデータを、指定されたルックアップモードインデックスの一致するレコードと結合します。結合フィールド内の一致する値に基づいて、ルックアップ インデックスのフィールドが新しい列として結果テーブルに追加されます。以前は、データの結合は単一のフィールドと単純な等式に制限されていましたが、これが改善されました。これらの機能強化により、複雑なデータ相関シナリオに簡単に対処できるようになります。</p><p><strong>Lookup Joinの主な機能強化は次のとおりです。</strong></p><ul><li><strong>複数フィールド結合：</strong>複数のフィールドを簡単に結合できます。例えば、<code>application_logs</code> を <code>service_name</code>、<code>environment</code> の <code>service_registry</code> と結合する場合 <code>version:</code></li></ul><pre><code>FROM application_logs
| LOOKUP JOIN service_registry ON service_name, environment, version</code></pre><ul><li><strong>式を使用して複雑なjoin述語を活用（テクニカルプレビュー）：</strong></li></ul><p>もはや、単純な等式に制限されることはありません。LOOKUP JOINでは、<strong>複数の条件</strong>を相関に指定し、==、!=、、&lt;=、&gt;= を含む <strong>二項演算子</strong>の範囲を組み込むことができます。つまり、非常に微妙な結合条件を作成できるようになり、データに対してより高度な質問をすることができるようになります。</p><p>例1：サービスごとのSLAしきい値を使用したアプリケーションメトリクスの検索</p><pre><code>FROM application_metrics
| LOOKUP JOIN sla_thresholds
      ON service_name == sla_service AND response_time > sla_response_time</code></pre><p>例2：このクエリは、時間とともに変化する地域の価格ポリシーに基づいて支払われるべき金額を計算します。複雑な日付範囲と等しい条件に基づく3つのデータセットを統合し、最終的に <code>due_amount</code>を算出します。2番目のルックアップ結合では、 <code>meter_readings</code>インデックスの<code>measurement_date</code>フィールドと<code>customers</code>インデックスの<code>region_id</code>フィールドを使用して<code>pricing_policies</code>インデックスに結合し、特定の<code>region</code>と<code>measurement_date</code>の正しい価格設定ポリシーを検索します。</p><pre><code>FROM meter_readings
| LOOKUP JOIN customers
      ON meter_id
| LOOKUP JOIN pricing_policies
      ON
        region_id == region AND
          measurement_date >= policy_begin_date AND
          measurement_date < policy_end_date
| EVAL due_amount = (kwh_consumed * rate_per_kwh + base_charge) * (1 + tax_rate)
| EVAL period = policy_name
| KEEP customer_name, period, due_amount, measurement_date, kwh_consumed,
    rate_per_kwh, base_charge, tax_rate
| SORT measurement_date</code></pre><ul><li><strong>フィルターされた結合のパフォーマンスが大幅に向上： </strong></li></ul><p>ルックアップテーブル条件を使用してフィルター処理される「拡張結合」のパフォーマンスが向上しました。拡張結合では、入力行ごとに複数の一致が生成され、大きな中間結果セットが作成されることがあります。これらの行の多くが後続のフィルターによって破棄されると、状況はさらに悪化します。9.2では、ルックアップデータにフィルターを適用するときに不要な行を除外することでこれらの結合を最適化し、破棄される行の処理を回避します。シナリオによっては、これらの結合は最大<strong>1000倍高速</strong>化される可能性があります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/002c014ee29b1aaf9ddeb8c554bb76efe3ed180c-1572x954.png" style="width: 100%;" /><p>この最適化は、ルックアップによって最初に多くの潜在的な一致が生成される可能性がある「拡張結合」を処理する場合に重要です。フィルターをインテリジェントにプッシュダウンすることで、関連するデータのみが処理され、クエリ実行時間が大幅に短縮され、膨大なデータセットでのリアルタイム分析が可能になります。つまり、非常に大規模または複雑な結合操作の場合でも、はるかに速く洞察を得ることができます。</p><p><strong>Lookup Joinクラスター横断検索（CCS）の互換性：</strong></p><p>8.19および9.1でLookup Joinが一般公開となった際、クロスクラスター検索（CCS）のサポートはありませんでした。複数のクラスターにまたがって運用している組織のため、LOOKUP JOINは9.2でCCSとシームレスに統合されるようになりました。ルックアップインデックスを結合したいすべてのリモートクラスターに配置するだけで、ES|QLは自動的にこれらのリモートルックアップインデックスを活用して、リモートデータと結合します。これにより、分散データ分析が簡素化され、Elasticsearch展開全体で一貫したエンリッチメントが保証されます。</p><p>これらの改善により、多様なデータセットを前例のない精度、速度、そして容易さで関連付けることができ、複雑な回避策や前処理のステップなしに、より深く、実用的な洞察を明らかにすることができます。</p><h2>データを簡単に充実化：ルックアップインデックスのためのKibana Discoverユーザーエクスペリエンス</h2><p>データのエンリッチメントはハードルではなく、シンプルであるべきです。KibanaのDiscoverに、ルックアップインデックスの作成と管理のための素晴らしい新しいユーザーエクスペリエンスを導入しました。</p><p><strong>直感的なワークフロー：</strong>Discoverの包括的なオートコンプリートがES|QLエディターでルックアップインデックスや結合フィールドを提案するため、アップロードされたデータを既存のインデックスと驚くほど簡単に結びつけることができます。存在しないルックアップインデックスの名前を入力し、ワンクリックでルックアップエディターに直接アクセスしてインデックスを作成できます。既存の検索インデックスの名前を入力すると、編集オプションを提案します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d36fd6ffd6bc0bf8d31067f6266445c68d15c71c-1400x184.png" style="width: 100%;" /><p><strong>インライン管理（CRUD）：</strong> Discoverで直接、参照データセットをインライン編集機能（作成、読み取り、更新、削除）で最新の状態に保ちます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a0e4aa66540b1f725c24ccb0519d978415073bb6-1453x842.png" style="width: 100%;" /><p><strong>簡単なファイルアップロード：</strong>CSVなどのファイルをDiscover内で直接アップロードし、 <code>LOOKUP JOIN</code>ですぐに使用できるようになりました。Kibanaのさまざまなエリアにジャンプしてコンテキストを切り替える必要はもうありません！</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0f5dc2c712af4c4cada50292a7c8b836eb02aa67-1600x748.png" style="width: 100%;" /><p>ユーザーIDを名前にマッピングしたり、ビジネスメタデータを追加したり、静的参照ファイルを結合したりする際、この特徴はデータのエンリッチメントを民主化し、結合のパワーをすべてのユーザーの手元に直接、迅速かつシンプルに、そして一箇所で提供します。</p><h2>コンテキストの保持：INLINE STATSのご紹介（テクニカルプレビュー）</h2><p>データの集約は重要ですが、時には集約を<em>元のデータと</em>並べて見る必要があることもあります。<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/inlinestats-by">INLINE STATSを</a><strong>テクニカルプレビュー</strong>機能としてご紹介できることを嬉しく思います。</p><p>入力フィールドを集約された出力に置き換える<code>STATS</code>コマンドとは異なり、 <code>INLINE STATS</code>元の入力フィールドをすべて保持し、新しい集約されたフィールドを追加します。これにより、集計<em>後に</em>元の入力フィールドに対してさらに操作を実行できるようになり、より継続的で柔軟な分析ワークフローが実現します。</p><p>例えば、個々のフライトの行を保持しながら平均飛行距離を計算する場合：</p><pre><code>FROM kibana_sample_data_flights
 | KEEP Carrier, Dest, DistanceMiles
 | INLINE STATS avgDist = ROUND(AVG(DistanceMiles))
       BY Dest
 | WHERE DistanceMiles > avgDist</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6eea943035e0ab371270084c504a06bb89f8b82b-1496x290.png" style="width: 100%;" /><p>このクエリでは、<code>avgDist</code>が各行に対応する<code>Dest</code>(ination)と共に追加され、さらに、フライト情報の列が残っているため、平均を超える距離のフライトに結果をフィルタリングできます。</p><h2>ES|QL における時系列サポート (技術プレビュー)</h2><p>Elasticsearchは、メトリックを格納するために<a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">時系列データストリーム</a>を使用します。<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code></a>ソースコマンドを通じて、ES|QLの時系列集計のサポートを追加しています。これは、Elastic Cloud Serverlessと9.2 basicでテクニカルプレビューとして利用できます。</p><p>時系列分析は主に、1つ以上のフィルタリングディメンションでスライスされた時間バケット全体のメトリック値を要約する集計クエリに基づいています。ほとんどの集計クエリは、(a) 時系列ごとに値を集計する内部集計関数と、(b) 時系列全体で (a) の結果を結合する外部集計関数という2段階の処理に依存しています。</p><p><a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code></a>ソースコマンドを<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/stats-by"><code>STATS</code></a>と組み合わせると、時系列にわたるこのようなクエリを簡潔かつ効果的に表現できるようになります。より具体的には、ホストおよび時間あたりのリクエストの合計レートを計算する次の例を考えてみましょう。</p><pre><code>TS my_metrics
| WHERE @timestamp > NOW() - 1 day
| STATS SUM(RATE(requests))
      BY host, TBUCKET(1h)</code></pre><p>この場合、時系列アグリゲーション関数 <code>RATE</code> はまず時系列と時間ごとに評価されます。生成された部分集計は、 <code>SUM</code>を使用して結合され、ホストおよび時間ごとの最終的な集計値が計算されます。</p><p>利用可能な時系列集計関数のリストは<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/time-series-aggregation-functions">こちらで</a>確認できます。カウンターを処理するための最も重要な集計関数の<a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds#time-series-metric">counter</a>レートがサポートされるようになりました。</p><p><code>TS</code>ソースコマンドは<code>STATS</code>と組み合わせて使用するように設計されており、時系列集計を効率的にサポートするように実行が調整されています。例えば、データは<code>STATS</code>に入る前にソートされます。処理コマンド（<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fork"><code>FORK</code></a>や<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/inlinestats-by"><code>INLINE STATS</code></a>など）によって時系列データを強化または変更したり、その順序を変更したりすることは、現在<code>TS</code>と<code>STATS</code>の間で許可されていません。この制限は将来解除される可能性があります。</p><p><code>STATS</code>表形式の出力は、適用可能なコマンドを使用してさらに処理できます。例えば、以下のクエリは、ホストごとの平均の <code>cpu_usage</code> と時間の比率とホストごとの最大値の比率を計算します。</p><pre><code>TS my_metrics
| STATS avg_usage = AVG(AVG_OVER_TIME(cpu_usage))
      BY host, time_bucket = TBUCKET(1h)
| INLINE STATS max_avg_usage = MAX(avg_usage)
      BY host
| EVAL ratio = avg_usage / max_avg_usage
| KEEP host, time_bucket, ratio
| SORT host, time_bucket DESC</code></pre><p>時系列データは、Luceneドキュメント値を利用した基盤となる列指向ストレージエンジンに保存されます。TSコマンドは、ES|QLコンピュートエンジンを介してベクトル化されたクエリの実行を追加します。クエリパフォーマンスは、同等の<a href="https://www.elastic.co/docs/reference/query-languages/querydsl">DSL</a>クエリと比較して、しばしば1桁以上の向上が見られ、確立されたメトリクス固有のシステムと同等になります。今後、詳細なアーキテクチャおよびパフォーマンス分析も提供する予定ですので、どうぞご期待ください。</p><h2>ツールキットの拡張：新しい ES|QL関数</h2><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><p><strong>文字列操作：</strong><a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/string-functions#esql-contains">INCLUDES</a>、<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/mv-functions#esql-mv_contains">MV_CONTAINS</a>、<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/string-functions#esql-url_encode">URL_ENCODE</a>、 <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/string-functions#esql-url_encode_component">URL_ENCODE_COMPONENT</a>、<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/string-functions#esql-url_decode">URL_DECODE</a>が追加され、より堅牢なテキストおよびURL処理を実現します。</p><p><strong>時系列と地理空間:</strong>柔軟な時間バケツ処理のための<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/grouping-functions#esql-tbucket">TBUCKET</a> 、ベクトル演算のための TO_DENSE_VECTOR、高度な位置ベースの分析のための<code>ST_GEOHASH</code> 、 <code>ST_GEOTILE</code> 、 <code>ST_GEOHEX</code> 、 <code>TO_GEOHASH</code> 、 <code>TO_GEOTILE</code> 、 <code>TO_GEOHEX</code>などの包括的な<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/spatial-functions">地理空間関数</a>のセット。</p><p><strong>日付のフォーマット：</strong><a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/date-time-functions#esql-day_name">DAY_NAME</a>、<a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/date-time-functions#esql-month_name">MONTH_NAME</a>は、より読みやすい日付表現のために使用されます。</p><p>これらの関数には、ES|QL内で直接データを操作および分析するための豊富なツールセットが用意されています。</p><h2>内部の改善：パフォーマンスと効率性の向上</h2><p>注目の機能以外にも、Elasticsearch 9.2にはES|QL全体にわたる数多くのパフォーマンス最適化が含まれています。関数が複数の類似したRLIKEクエリを置き換える場合に、プッシュダウンを使用して<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/where#like-and-rlike">RLIKE (LIST</a>) を高速化しました。<code>RLIKE</code> (LIST) を使用すると、これらのクエリを1つのオートマトンに結合し、複数ではなく1つのオートマトンを適用できます。また、インデックスの並べ替えによるキーワードフィールドの読み込みが高速化され、クエリ全般が最適化されました。これらの改善により、ES|QLクエリがこれまで以上に効率的に実行されるようになります。</p><h2>今すぐ始めましょう！</h2><p>Elasticsearch 9.2は、ES|QLを大きく飛躍させ、データ分析ワークフローにかつてないパワーと柔軟性をもたらします。ぜひこれらの新機能を試して、その違いを体験してください。</p><p>Elasticsearch 9.2のすべての変更点と機能強化の包括的なリストについては、<a href="https://www.elastic.co/guide/en/elasticsearch/reference/9.2/release-notes-9.2.0.html">公式リリースノート</a>を参照してください。楽しいクエリングを!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GitHubのイシューをElasticsearchでクエリするChatGPTコネクターの構築]]></title>
            <description><![CDATA[カスタムChatGPTコネクターの構築方法と、ハイブリッド検索して内部のGitHubイシューをクエリするElasticsearch MCPサーバーをデプロイする方法を学びましょう。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/chatgpt-connector-mcp-server-github-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/chatgpt-connector-mcp-server-github-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
            <pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>最近、OpenAIはPro/Business/EnterpriseおよびEduプラン向けに<a href="https://help.openai.com/en/articles/11487775-connectors-in-chatgpt">ChatGPT向けのカスタムコネクター</a>機能を発表しました。これは、Gmail、GitHub、Dropboxなどのデータを活用するためのすぐに使えるコネクターへの追加となります。MCPサーバーを使用してカスタムコネクターを作成できます。</p><p>カスタムコネクターを使用すると、既存のChatGPTコネクターをElasticsearchなどの追加のデータソースと組み合わせて、包括的な回答を得ることができます。</p><p>この記事では、内部のGitHubの課題とプルリクエストに関する情報を含むElasticsearchインデックスにChatGPTを接続する<a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP</a>サーバーを構築します。これにより、Elasticsearchデータを使用して自然言語クエリに回答できるようになります。</p><p>Google Colabの<a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a>とngrokを使ってMCPサーバーをデプロイし、ChatGPTが接続できる公開URLを取得し、複雑なインフラ構築の必要性を排除します。</p><p>MCPとそのエコシステムの包括的な概要については、<a href="https://www.elastic.co/search-labs/blog/mcp-current-state">MCPの現在の状態</a>をご参照ください。</p><h2>要件</h2><p>始める前に必要なものは次のとおりです。</p><ul><li>Elasticsearchクラスター（8.X以降）</li><li>インデックスへの読み取りアクセス権を持つ Elasticsearch APIキー</li><li>Googleアカウント（Google Colab用）</li><li>Ngrokアカウント（無料プランでも可）</li><li>Pro/Enterprise/BusinessまたはEduプランのChatGPTアカウント</li></ul><h2>ChatGPT MCPコネクターの要件を理解する</h2><p>ChatGPT MCPコネクターには、<code>search</code>と<code>fetch</code>の2つのツールを実装する必要があります。詳細については、<a href="https://platform.openai.com/docs/mcp#create-an-mcp-server">OpenAIドキュメント</a>をご覧ください。</p><h3><a href="https://platform.openai.com/docs/mcp#search-tool">検索ツール</a></h3><p>ユーザークエリに基づいて、Elasticsearchインデックスから関連する結果のリストを返します。</p><h4>受け取るもの：</h4><ul><li>ユーザーの自然言語クエリを含む単一の文字列。</li><li>例：「Elasticsearch移行に関連するイシューを見つけて」</li></ul><h4>返されるもの：</h4><ul><li>結果オブジェクトの配列を含む<code>result</code>キーを持つオブジェクト。各結果には以下が含まれます。<ul><li><code>id</code> - 一意の文書識別子</li><li><code>title</code> - イシューまたはPRタイトル</li><li><code>url</code> - イシュー/PRへのリンク</li></ul></li></ul><h4>実装内容：</h4><pre><code>return {
    "results": [
        {
            "id": "PR-612",
            "title": "Fix memory leak in WebSocket notification service",
            "url": "https://internal-git.techcorp.com/pulls/612"
        },
        # ... more results
    ]
}</code></pre><h3><a href="https://platform.openai.com/docs/mcp#fetch-tool">フェッチ・ツール</a></h3><p>特定の文書の完全な内容を取得します。</p><h4>受け取るもの：</h4><ul><li>検索結果からElasticsearch文書IDを入力する単一の文字列</li><li>例：「PR-578の詳細を教えてください。」</li></ul><h4>返されるもの：</h4><ul><li>以下を含む完全な文書オブジェクト：<ul><li><code>id</code> - 一意の文書識別子</li><li><code>title</code> - イシューまたはPRタイトル</li><li><code>text</code> - 完全なイシュー・PRの説明と詳細</li><li><code>url</code> - イシュー/PRへのリンク</li><li><code>type</code> - 文書の種類（issue, pull_request）</li><li><code>status</code> - 現在のステータス（open, in_progress, resolved）</li><li><code>priority</code> - 優先度レベル（low, medium, high, critical）</li><li><code>assignee</code> - イシュー/PRの担当者</li><li><code>created_date</code> - 作成された時期</li><li><code>resolved_date</code> - 解決された時期（該当する場合）</li><li><code>labels</code> 文書に関連するタグ</li><li><code>related_pr</code> - 関連するプルリクエストID</li></ul></li></ul><pre><code>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
}</code></pre><p><strong>注</strong>：この例では、すべてのフィールドがルートレベルにあるフラット構造を使用しています。OpenAIの要件は柔軟で、ネストされたメタデータオブジェクトもサポートしています。</p><h2>GitHubのデータセットとプルリクエストデータセット</h2><p>このチュートリアルでは、イシューとプルリクエストを含む内部GitHubデータセットを使用します。これは、ChatGPTを通じてプライベートな内部データをクエリするシナリオを表しています。</p><p>データセットは<a href="https://gist.github.com/TomasMurua/4e7bbdf7a7ebbdffaa663c43578d934a">こちら</a>からご覧いただけます。そして、<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk">Bulk APIを使って</a>データのインデックスを更新します。</p><p>このデータセットには以下が含まれます。</p><ul><li>説明、ステータス、優先順位、担当者に関する問題</li><li>コード変更、レビュー、導入情報を含むプルリクエスト</li><li>イシューとPRの関係（例：PR-578がISSUE-1889を修正）</li><li>ラベル、日付、その他のメタデータ</li></ul><h3>インデックスマッピング</h3><p>インデックスは、<a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-elser">ELSER</a>とのハイブリッド検索をサポートするために以下の<a href="https://www.elastic.co/docs/manage-data/data-store/mapping">マッピング</a>を使用します。<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">text_semantic</a>はセマンティック検索に使用され、他のフィールドはキーワード検索を可能にします。</p><pre><code>{
  "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"
      }
    }
  }
}</code></pre><h2>MCPサーバーを構築する</h2><p>当社のMCPサーバーは、OpenAI仕様に従って2つのツールを実装しています。ハイブリッド検索を使用してセマンティックマッチングとテキストマッチングを組み合わせることで、より良い結果が得られます。</p><h3>検索ツール</h3><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">RRF</a>（相互ランク融合）を用いたハイブリッド検索を使用し、セマンティック検索とテキストマッチングを組み合わせています。</p><pre><code>@mcp.tool()
    async def search(query: str) -> 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)}")</code></pre><h3>主なポイント：</h3><ul><li><strong>RRFを用いたハイブリッド検索：</strong> より良い結果を得るために、セマンティック検索（ELSER）とテキスト検索（BM25）を組み合わせます。</li><li><strong>複数一致クエリ：</strong>ブースティングを使用して<a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-multi-match-query">複数のフィールドを検索します</a>（title^3, text^2, assignee^2）。キャレット記号（^）は関連性スコアを乗算し、コンテンツよりもタイトルの一致を優先します。</li><li><strong>あいまい一致：</strong> <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/common-options#fuzziness"><code>fuzziness: AUTO</code></a> は近似一致を許可することでタイプミスやスペルミスを処理します。</li><li><strong>RRFのパラメーター調整：</strong><ul><li><code>rank_window_size: 50</code> - マージする前に、各リトリーバー（セマンティックとテキスト）からの上位結果をいくつ考慮するかを指定します。</li><li><code>rank_constant: 60</code> - この値は、個々の結果セット内の文書が最終的なランク付け結果にどの程度影響を与えるかを決定します。</li></ul></li><li><strong>必須フィールドのみを返す：</strong> <code>id</code>、<code>title</code>、<code>url</code>はOpenAIの仕様に従い、追加のフィールドを不必要に公開しないようにします。</li></ul><h3>フェッチ・ツール</h3><p>文書IDが存在する場合は、そのIDで文書の詳細を取得します。</p><pre><code>@mcp.tool()
    async def fetch(id: str) -> 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)}")</code></pre><h3>主なポイント：</h3><ul><li><strong>文書IDのフィールドで検索：</strong>カスタム <code>id</code>フィールドに用語クエリを使用します</li><li><strong>完全な文書を返す：</strong> すべてのコンテンツを含む完全な<code>text</code>フィールドが含まれます</li><li><strong>フラットな構造：</strong>すべてのフィールドがルートレベルにあり、Elasticsearchのドキュメント構造に一致します。</li></ul><h2>Google Colabにデプロイする</h2><p>Google Colabを使用してMCPサーバーを実行し、ngrokで公開することで、ChatGPTが接続できるようにします。</p><h3>ステップ1：Google Colabノートブックを開く</h3><p>事前設定されたノートブック<a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/elasticsearch-chatgpt-connector">Elasticsearch MCP for ChatGPT</a>にアクセスします。</p><h3>ステップ2：認証情報を設定する</h3><p>次の3つの情報が必要になります。</p><ul><li><strong>Elasticsearch URL：</strong>お客様の<a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-enterprise/connect-elasticsearch">ElasticsearchクラスタリングURL</a>。</li><li><strong>Elasticsearch API キー：</strong>インデックスへの読み取りアクセス権を持つ<a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">APIキー</a>。</li><li><strong>Ngrok認証トークン：</strong><a href="https://ngrok.com/">ngrok</a>からの無料トークン。ngrokを使ってMCPのURLをインターネットに公開し、ChatGPTが接続できるようにします。</li></ul><h4>ngrokトークンの取得</h4><ol><li><a href="https://ngrok.com/">ngrok</a>で無料アカウントに登録します。</li><li><a href="https://dashboard.ngrok.com/">ngrok</a>ダッシュボードにアクセスします。</li><li>認証トークンをコピーします。</li></ol><h4>Google Colabにシークレットを追加する</h4><p>Google Colabノートブック内で：</p><ol><li>左側のサイドバーにある<strong>キーアイコン</strong>をクリックして、<strong>シークレット</strong>を開きます。</li><li>次の3つのシークレットを追加します。</li></ol><pre><code>ELASTICSEARCH_URL=https://your-cluster.elastic.com:443
ELASTICSEARCH_API_KEY=your-api-key
NGROK_TOKEN=your-ngrok-token</code></pre><p>3. 各シークレットのノートブックアクセスを有効にします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5dd6ac19fe816a562c6351fdb0f11369da0e877-609x321.jpg" style="width: 100%;" /><h3>ステップ3：ノートブックを実行する</h3><ol><li><strong>ランタイム</strong>をクリックし、次に<strong>すべて実行</strong>をクリックして、すべてのセルを実行します。</li><li>サーバーの起動を待ちます（約30秒）。</li><li>公開ngrok URLを示す出力を探します。</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f185100e8869624bc9e1c7b2b4eb32785e2d89e7-1189x283.png" style="width: 100%;" /><p>4. 出力は次のようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e02e625e91ed9136454e4401b184575fb03a336e-1052x465.jpg" style="width: 100%;" /><h2>ChatGPTに接続する</h2><p>次に、MCPサーバーをあなたのChatGPTアカウントに接続します。</p><ol><li>ChatGPTを開き、<strong>設定</strong>に移動します。</li><li><strong>コネクター</strong>に移動します。Proアカウントを使用している場合は、コネクタで<a href="https://platform.openai.com/docs/guides/developer-mode">開発者モード</a>をオンにする必要があります。</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/32c02192912fc0e7e5a52e9399077ba7ae3b4901-739x715.png" style="width: 100%;" /><p><em>ChatGPT EnterpriseまたはBusinessを使用している場合は、コネクターを職場に公開する必要があります。</em></p><p>3.&nbsp; <strong>作成</strong>をクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/15c53e5ccc381108a9dc0052cca05bf0fc97679a-755x683.png" style="width: 100%;" /><p><em><strong>注</strong>：Business、Enterprise、Eduワークスペースでは、ワークスペースの所有者、管理者、およびそれぞれの設定が有効になっているユーザー（Enterprise/Eduの場合）のみがカスタムコネクターを追加できます。通常のメンバーロールのユーザーには、自分でカスタムコネクターを追加する権限がありません。</em></p><p><em>コネクターが所有者または管理者ユーザーによって追加され有効化されると、ワークスペースのすべてのメンバーが使用できるようになります。</em></p><p>4. 必要な情報と、<code>/sse/</code>で終わるngrokのURLを入力します。「sse」の後の「/」に注意してください。これがない場合、動作しません。</p><ul><li><strong>Name:</strong> Elasticsearch MCP</li><li><strong>Description: </strong>GitHubの内部情報を検索および取得するためのカスタムMCP。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/162a85705cc8ac48a3f2f665551d513e0719f93d-479x684.png" style="width: 100%;" /><p>5. <strong>作成</strong>を押してカスタムMCPを保存します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/97eb5fb0a32b86bfadfb35561f698616f217c049-913x629.png" style="width: 100%;" /><p>サーバーが稼働していれば、接続は瞬時に完了します。追加の認証は不要で、Elasticsearch APIキーはサーバー上で設定されています。</p><h2>MCPサーバーをテストする</h2><p>質問する前に、ChatGPTが使用するコネクターを選択する必要があります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" style="width: 100%;" /><h3>プロンプト1：イシューを検索する</h3><p><strong>「Elasticsearchの移行に関連するイシューを見つけて」</strong>と質問し、アクションツールの呼び出しを確認します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cfd781acbff8cd7c8095bbe29224f8b26d581f77-650x375.png" style="width: 100%;" /><p>ChatGPT はクエリを使用して<code>search</code>ツールを呼び出します。利用可能なツールを検索し、Elasticsearchツールを呼び出す準備をし、ツールに対して何らかのアクションを実行する前にユーザーに確認していることがわかります。</p><h4>ツール呼び出しリクエスト：</h4><pre><code>{
  "query": "Elasticsearch migration issues"
}</code></pre><h4>ツールの応答：</h4><pre><code>{
  "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
  ]
}</code></pre><p>ChatGPTは結果を処理し、自然で会話的な形式で提示します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9d5b6cff85c7e54ccc2584b8ae96d45495fae8c1-923x1352.png" style="width: 100%;" /><h3>仕組み</h3><h4>プロンプト：「Elasticsearch移行に関連するイシューを見つけて」</h4><p>1. ChatGPTの呼び出し <code>search(“Elasticsearch migration”)</code></p><p>2. Elasticsearchがハイブリッド検索を実行する</p><ul><li><strong>セマンティック検索は</strong>「アップグレード」や「<em>バージョン互換性」などの概念を理解します。</em></li><li><strong>テキスト検索</strong>で「<em>Elasticsearch</em>」と「migration」の完全一致を見つけます。</li><li><strong>RRF</strong>は両方のアプローチの結果を組み合わせてランク付けします。</li></ul><p>3. <code>id</code>、<code>title</code>を含むトップ10のマッチングイベントを返します。 <code>url</code></p><p>4. ChatGPTは「<em>ISSUE-1712: migrate from Elasticsearch 7.x to 8.x</em>」を最も関連性の高い結果として特定します。</p><h3>プロンプト2：完全な詳細を取得する</h3><p>質問：<strong><em>「ISSUE-1889の詳細を教えて」</em></strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5c0db5245535ce67a36056e61e135bddc97ce496-934x629.png" style="width: 100%;" /><p>ChatGPTは、あなたが特定のイシューに関する詳細な情報を求めていることを認識し、<code>fetch</code>ツールを呼び出し、ツールに対して何らかのアクションを起こす前にユーザーに確認します。</p><h4>ツール呼び出しリクエスト：</h4><pre><code>{
  "id": "ISSUE-1889"
}</code></pre><h4>ツールの応答：</h4><pre><code>{
  "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"
}</code></pre><p>ChatGPTは情報を統合し、明確に提示します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/410f19f213e94fc4e3c47eeef6e04b69e0c86159-602x462.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/54d8ffa117628a1e3afc317c3ab75d4f7731d7ab-767x1600.png" style="width: 100%;" /><h3>仕組み</h3><h4>プロンプト：「ISSUE-1889の詳細を教えて」</h4><ol><li>ChatGPT呼び出し <code>fetch(“ISSUE-1889”)</code></li><li>Elasticsearchが完全な文書を取得する</li><li>すべてのフィールドがルートレベルにある完全な文書を返す</li><li>ChatGPTは情報を統合し、適切な引用で回答する</li></ol><h2>まとめ</h2><p>この記事では、専用の<strong>検索</strong>および<strong>フェッチ</strong>MCPツールを使用してChatGPTをElasticsearchに接続するカスタムMCPサーバーを構築し、プライベートデータに対する自然言語クエリを可能にしました。</p><p>このMCPパターンは、自然言語を使用してクエリしたい任意のElasticsearchインデックス、ドキュメント、製品、ログ、またはその他のデータで機能します。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[LangChainとElasticsearchを使用したエージェントRAGアシスタントの開発]]></title>
            <description><![CDATA[LangChain と Elasticsearch を使用して、適応型ルーティングで記事に関するクエリに回答するエージェント型ラグニュース アシスタントを構築する方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/agentic-rag-news-assistant-langchain-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/agentic-rag-news-assistant-langchain-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Kirti Sodhi]]></dc:creator>
            <pubDate>Fri, 28 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d7213956309c6b5863627aca2ba95569b02fc4c7-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>このブログ記事では、エージェント RAG ワークフローについて詳しく説明し、その主な機能と一般的な設計パターンについて説明します。さらに、Elasticsearch をベクター ストアとして使用し、LangChain を使用してエージェント RAG フレームワークを構築する実践的な例を通じて、これらのワークフローを実装する方法を示します。最後に、この記事では、このようなアーキテクチャの設計と実装に関連するベスト プラクティスと課題について簡単に説明します。この<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/agentic-rag/agent_rag_news_assistant.ipynb">Jupyter ノートブック</a>に従って、シンプルなエージェント RAG パイプラインを作成できます。</p><h2>エージェントRAGの紹介</h2><p>検索拡張生成 ( <a href="https://www.elastic.co/docs/solutions/search/rag">RAG</a> ) は LLM ベースのアプリケーションの基礎となり、モデルがユーザーのクエリに基づいて関連するコンテキストを取得することで最適な回答を提供できるようになりました。RAG システムは、事前にトレーニングされた LLM 知識に限定されるのではなく、API またはデータ ストアからの外部情報を利用することで、LLM 応答の精度とコンテキストを強化します。一方、AI エージェントは自律的に動作し、指定された目的を達成するために意思決定とアクションを実行します。</p><p>Agentic RAG は、検索強化生成とエージェント推論の両方の長所を統合したフレームワークです。RAG をエージェントの意思決定プロセスに統合することで、システムはデータ ソースを動的に選択し、クエリを改良してコンテキスト検索を改善し、より正確な応答を生成し、フィードバック ループを適用して出力品質を継続的に向上できるようになります。</p><h2>エージェントRAGの主な特徴</h2><p>エージェント RAG フレームワークは、従来の RAG システムに比べて大きな進歩を遂げています。固定された検索プロセスに従うのではなく、結果をリアルタイムで計画、実行、最適化できる動的エージェントを活用します。</p><p>エージェント RAG パイプラインを区別する主な機能のいくつかを見てみましょう。</p><ul><li><strong>動的な意思決定</strong>: Agentic RAG は推論メカニズムを使用してユーザーの意図を理解し、各クエリを最も関連性の高いデータ ソースにルーティングして、正確でコンテキストに応じた応答を生成します。</li><li><strong>包括的なクエリ分析:</strong> Agentic RAG は、サブ質問とその全体的な意図を含むユーザークエリを詳細に分析します。クエリの複雑さを評価し、最も関連性の高いデータ ソースを動的に選択して情報を取得し、正確で完全な応答を保証します。</li><li><strong>多段階コラボレーション</strong>: このフレームワークは、専門エージェントのネットワークを通じて多段階コラボレーションを可能にします。各エージェントは、より大きな目標の特定の部分を担当し、一貫した結果を達成するために順次または同時に作業します。</li><li><strong>自己評価メカニズム</strong>: エージェント RAG パイプラインは、自己反映を使用して、取得したドキュメントと生成された応答を評価します。取得した情報がクエリに完全に対応しているかどうかを確認し、出力の正確性、完全性、事実の一貫性を確認できます。</li><li><strong>外部ツールとの統合</strong>: このワークフローは、外部 API、データベース、リアルタイム情報ソースと対話して、最新の情報を取り込み、進化するデータに動的に適応できます。</li></ul><h2>エージェントRAGのワークフローパターン</h2><p>ワークフロー パターンは、エージェント AI が LLM ベースのアプリケーションを信頼性と効率性をもって構築、管理、調整する方法を定義します。<a href="https://www.langchain.com/">LangChain</a> 、 <a href="https://www.langchain.com/langgraph">LangGraph</a> 、 <a href="https://www.crewai.com/">CrewAI</a> 、 <a href="https://www.llamaindex.ai/">LlamaIndex</a>などのいくつかのフレームワークとプラットフォームを使用して、これらのエージェント ワークフローを実装できます。</p><ol><li><strong>順次取得チェーン</strong>: 順次ワークフローは、複雑なタスクを単純な順序付けられたステップに分割します。各ステップで次のステップの入力が改善され、より良い結果が得られます。たとえば、顧客プロファイルを作成する場合、1 人のエージェントが CRM から基本的な詳細を取得し、別のエージェントがトランザクション データベースから購入履歴を取得し、最後のエージェントがこの情報を組み合わせて推奨事項やレポート用の完全なプロファイルを生成します。</li><li><strong>ルーティング取得チェーン</strong>: このワークフロー パターンでは、ルーター エージェントが入力を分析し、最も適切なプロセスまたはデータ ソースに送信します。このアプローチは、重複が最小限で複数の異なるデータ ソースが存在する場合に特に効果的です。たとえば、顧客サービス システムでは、ルータ エージェントが技術的な問題、払い戻し、苦情などの受信リクエストを分類し、適切な部門にルーティングして効率的に処理します。</li><li><strong>並列検索チェーン</strong>: このワークフロー パターンでは、複数の独立したサブタスクが同時に実行され、それらの出力が後で集約されて最終的な応答が生成されます。このアプローチにより、処理時間が大幅に短縮され、ワークフローの効率が向上します。たとえば、顧客サービスの並列ワークフローでは、1 人のエージェントが過去の同様のリクエストを取得し、別のエージェントが関連するナレッジベースの記事を参照します。アグリゲータはこれらの出力を組み合わせて包括的な解決策を生成します。</li><li><strong>オーケストレーター ワーカー チェーン</strong>: このワークフローは、独立したサブタスクを利用するため、並列化と類似点があります。ただし、重要な違いはオーケストレーター エージェントの統合にあります。このエージェントは、ユーザークエリを分析し、実行時にそれらをサブタスクに動的に分割し、正確な応答を作成するために必要な適切なプロセスまたはツールを識別する役割を担います。</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ece6fc2403f211556c93e99d5227bfb7053b0c31-1600x1047.png" style="width: 100%;" /><h2>エージェントRAGパイプラインをゼロから構築する</h2><p>エージェント RAG の原理を説明するために、LangChain と Elasticsearch を使用してワークフローを設計してみましょう。このワークフローはルーティングベースのアーキテクチャを採用しており、複数のエージェントが連携してクエリを分析し、関連情報を取得し、結果を評価し、一貫した応答を生成します。この例に従うには、この<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/agentic-rag/agent_rag_news_assistant.ipynb">Jupyter ノートブック</a>を参照してください。</p><p>ワークフローはルータ エージェントから開始され、ルータ エージェントはユーザーのクエリを分析して最適な取得方法 ( <code>vectorstore</code> 、 <code>websearch</code> 、または<code>composite</code>のいずれかのアプローチ) を選択します。ベクターストアは従来の RAG ベースのドキュメント検索を処理し、Web 検索はベクターストアに保存されていない最新の情報を取得し、複合アプローチは複数のソースからの情報が必要な場合に両方を組み合わせます。</p><p>ドキュメントが適切であると判断された場合、要約エージェントは明確で文脈に適した応答を生成します。ただし、ドキュメントが不十分または無関係な場合、クエリ書き換えエージェントはクエリを再作成して検索を改善します。この修正されたクエリによりルーティング プロセスが再開され、システムは検索を絞り込み、最終出力を強化できるようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ed8701a7f15558fbf2e967a884b3e770eccb826b-1256x1092.png" style="width: 100%;" /><h3>要件</h3><p>このワークフローは、例を効果的に実行するために次のコア コンポーネントに依存しています。</p><ul><li>Python 3.10</li><li><a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/agentic-rag/agent_rag_news_assistant.ipynb">Jupyterノートブック</a></li><li>Azure OpenAI</li><li>Elasticsearch</li><li>LangChain</li></ul><p>続行する前に、この例に必要な次の環境変数のセットを構成するように求められます。</p><pre><code>AZURE_OPENAI_ENDPOINT="Add your azure openai endpoint"
AZURE_OPENAI_KEY="Add your azure openai key"
AZURE_OPENAI_DEPLOYMENT="gpt-4.1"
AZURE_OPENAI_API_VERSION="Add your azure openai api version"

ES_ENDPOINT = "Add your Elasticsearch ENDPOINT"
ES_API_KEY = "Add your Elasticsearch API KEY"</code></pre><h3>データソース</h3><p>このワークフローは、AG ニュース データセットのサブセットを使用して説明されています。このデータセットは、国際、スポーツ、ビジネス、科学技術など、さまざまなカテゴリのニュース記事で構成されています。</p><pre><code>dataset = load_dataset("ag_news", split="train[:1000]")
docs = [
    Document(
        page_content=sample["text"],
        metadata={"category": sample["label"]}
    )
    for sample in dataset
]</code></pre><p><a href="https://python.langchain.com/docs/integrations/vectorstores/elasticsearch/">ElasticsearchStore モジュールは</a>、 <code>langchain_elasticsearch</code>からベクター ストアとして利用されます。検索には、Elastic 独自の埋め込みモデルである<a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-elser">ELSER</a>を採用した SparseVectorStrategy を実装します。ベクター ストアを開始する前に、ELSER モデルが Elasticsearch 環境に正しくインストールされ、デプロイされていることを確認することが重要です。</p><pre><code>elastic_vectorstore = ElasticsearchStore.from_documents(
    docs,
    es_url=ES_ENDPOINT,
    es_api_key=ES_API_KEY,
    index_name=index_name,
    strategy=SparseVectorStrategy(model_id=".elser_model_2"),
)

elastic_vectorstore.client.indices.refresh(index=index_name)</code></pre><p>Web 検索機能は、LangChain コミュニティ ツールの<a href="https://python.langchain.com/api_reference/community/tools/langchain_community.tools.ddg_search.tool.DuckDuckGoSearchRun.html">DuckDuckGoSearchRun</a>を使用して実装されており、これによりシステムは Web からライブ情報を効率的に取得できます。より関連性の高い結果を提供できる他の検索 API の使用も検討できます。このツールは、API キーを必要とせずに検索できるため選択されました。</p><pre><code>duckduckgo = DuckDuckGoSearchRun(description= "A custom DuckDuckGo search tool for finding latest news stories.", verbose=True)
def websearch_retriever(query):
    results = duckduckgo.run(f"{query}")
    return results</code></pre><p>複合リトリーバーは、ソースの組み合わせを必要とするクエリ用に設計されています。これは、Web からリアルタイム データを取得すると同時に、ベクター ストアから過去のニュースを参照することで、包括的かつ文脈的に正確な応答を提供するために使用されます。</p><pre><code>def composite_retriever(query):
    related_docs = vectorstore_retriever(query)
    related_docs += websearch_retriever(query)
    return related_docs</code></pre><h3>エージェントの設定</h3><p>次のステップでは、このワークフロー内で推論および意思決定機能を提供する LLM エージェントが定義されます。作成する LLM チェーンには、 <code>router_chain</code> 、 <code>grade_docs_chain</code> 、 <code>rewrite_query_chain</code> 、 <code>summary_chain</code>が含まれます。</p><p>ルータ エージェントは LLM アシスタントを使用して、実行時に特定のクエリに最適なデータ ソースを決定します。グレーディングエージェントは、取得したドキュメントの関連性を評価します。ドキュメントが関連していると判断された場合、それらは要約エージェントに渡され、要約が生成されます。それ以外の場合、書き換えクエリ エージェントはクエリを再作成し、別の取得を試行するためにルーティング プロセスに送り返します。すべてのエージェントの手順は、<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/agentic-rag/agent_rag_news_assistant.ipynb">ノートブック</a>の LLM チェーン セクションに記載されています。</p><pre><code>class RouteQuery(BaseModel):
    datasource: Literal["vectorstore", "websearch", "composite"] = Field(
        ...,
        description="Choose to route the query to web search, vectorstore or composite."
    )

router_prompt = ChatPromptTemplate.from_template("""You are an assistant that decides the best data source for questions based on news articles.
Choose one of the following options:
- 'vectorstore': for general, background, or historical news articles.
- 'websearch': for recent discoveries, 'latest', 'current', or '2025' type queries.
- 'composite': when the question needs both historical and current knowledge on news articles.

Question: {query}

Return one word: 'vectorstore', 'websearch', or 'composite'.
""")
router_structured = llm.with_structured_output(RouteQuery)
router_chain: RunnableSequence = router_prompt | router_structured</code></pre><p><code>llm.with_structured_output</code>は、モデルの出力を<code>RouteQuery</code>クラスの BaseModel によって定義された定義済みスキーマに従うように制限し、結果の一貫性を保証します。2 行目は、 <code>router_prompt</code>と<code>router_structured</code>を接続して<code>RunnableSequence</code>を構成し、入力プロンプトが言語モデルによって処理され、構造化されたスキーマ準拠の結果が生成されるパイプラインを形成します。</p><h3>グラフノードを定義する</h3><p>この部分では、システムのさまざまなコンポーネント間を流れるデータを表すグラフの状態を定義します。これらの状態を明確に指定することで、ワークフロー内の各ノードがアクセスおよび更新できる情報を確実に認識できるようになります。</p><pre><code>class RAGState(TypedDict):
    query: str
    docs: List[Document]
    router: str
    summary: str
    self_reflection: bool
    retry_count: int = 0</code></pre><p>状態が定義されたら、次のステップはグラフのノードを定義することです。ノードは、データに対して特定の操作を実行するグラフの機能単位のようなものです。パイプラインには 7 つの異なるノードがあります。</p><pre><code>def router(state: RAGState):
   router = router_chain.invoke({'query': state["query"]})
   logger.info(f"Router selected the datasource: {router.datasource}")
   logger.info(f"User query: {state['query']}")
   return {"router": router.datasource}

def vectorstore(state: RAGState):
   return {"docs": vectorstore_retriever(state["query"])}

def websearch(state: RAGState):
   return {"docs": websearch_retriever(state["query"])}

def composite(state: RAGState):
   return {"docs": composite_retriever(state["query"])}

def self_reflection(state: RAGState):
   evaluation = grade_docs_chain.invoke(
       {"query": state["query"], "docs": state["docs"]}
   )
   if evaluation.binary_score:
       logger.info(f"Self-reflection passed -- binary_score={evaluation.binary_score}")
   else:
       logger.info(f"Self-reflection failed -- binary_score={evaluation.binary_score}")

   return {
       "self_reflection": evaluation.binary_score,
   }

def query_rewriter(state: RAGState):
   retry_count = state.get("retry_count", 0) + 1
   new_query = rewrite_query_chain.invoke({"query": state["query"]})
   logger.info(f"Query rewritten: {new_query}, retry_count: {retry_count}")
   return {
       "query": new_query,
       "retry_count": retry_count,
   }

def summarize(state: RAGState):
   summary = summarize_chain.run(
       query=state["query"],
       docs=state["docs"],
   )
   return {"summary": summary}</code></pre><p><code>query_rewriter</code>ノードはワークフロー内で 2 つの目的を果たします。まず、自己反省エージェントによって評価された文書が不十分または無関係であると判断された場合に、 <code>rewrite_query_chain</code>を使用してユーザークエリを書き換え、検索を改善します。2 番目に、クエリが書き換えられた回数を追跡するカウンターとして機能します。</p><p>ノードが呼び出されるたびに、ワークフロー状態に格納されている<code>retry_count</code>が増加します。このメカニズムにより、ワークフローが無限ループに陥るのを防ぎます。<code>retry_count</code>が事前定義されたしきい値を超えた場合、システムはエラー状態、デフォルトの応答、または選択したその他の定義済み条件にフォールバックできます。</p><h3>グラフのコンパイル</h3><p>最後のステップは、グラフのエッジを定義し、コンパイルする前に必要な条件を追加することです。すべてのグラフは、ワークフローのエントリ ポイントとして機能する指定された開始ノードから開始する必要があります。グラフ内のエッジはノード間のデータの流れを表し、次の 2 つのタイプがあります。</p><ul><li>直線エッジ: 1 つのノードから別のノードへの直接的で無条件のフローを定義します。最初のノードがタスクを完了すると、ワークフローは直線に沿って次のノードに自動的に進みます。</li><li>条件付きエッジ: これにより、現在の状態またはノードの計算結果に基づいてワークフローを分岐できます。次のノードは、評価結果、ルーティングの決定、再試行回数などの条件に応じて動的に選択されます。</li></ul><pre><code>graph.add_edge(START, "router")

def after_router(state: RAGState):
   route = state.get("router", None)
   if route == "vectorstore":
       return "vectorstore"
   elif route == "websearch":
       return "websearch"
   else:
       return "composite"

def after_self_reflection(state: RAGState):
   if state["self_reflection"]:
           return "summarize"
   return "query_rewriter"

def after_query_rewriter(state: RAGState):
   while state['retry_count'] <= 3:
           return "router"
   raise RuntimeError("Maximum retries (3) reached -- evaluation failed.")

graph.add_conditional_edges(
   "router",
   after_router,
   {
       "vectorstore": "vectorstore",
       "websearch": "websearch",
       "composite": "composite"
   }
)

graph.add_edge("vectorstore", "self_reflection")
graph.add_edge("websearch", "self_reflection")
graph.add_edge("composite", "self_reflection")
graph.add_conditional_edges(
   "self_reflection",
   after_self_reflection,
   {
       "summarize": "summarize",
       "query_rewriter": "query_rewriter"
   }
)
graph.add_conditional_edges("query_rewriter", after_query_rewriter, {"router": "router"})
graph.add_edge("summarize", END)
agent=graph.compile()</code></pre><p>これで、最初のエージェント RAG パイプラインの準備が整い、コンパイルされたエージェントを使用してテストできるようになります。</p><pre><code>result = agent.invoke({"query": query1})
logger.info(f"\nFinal Summary:\n: {result['summary']}")</code></pre><h3>エージェントRAGパイプラインのテスト</h3><p>次に、以下の 3 つの異なるタイプのクエリを使用してこのパイプラインをテストします。結果は異なる場合があり、以下に示す例は潜在的な結果の 1 つを示しているにすぎないことに注意してください。</p><pre><code>query1="What are the latest AI models released this month?"
query2="What technological innovations are discussed in Sci/Tech news?"
query3="Compare a Sci/Tech article from the dataset with a current web article about AI trends."</code></pre><p>最初のクエリでは、ルータはデータ ソースとして<code>websearch</code>を選択します。クエリは自己反映評価に失敗し、出力に示されているように、その後クエリ書き換えステージにリダイレクトされます。</p><pre><code>INFO     | __main__:router:11 - Router selected the datasource: websearch
INFO     | __main__:router:12 - User query: What are the latest AI models released this month?
Latest Singapore news, including the city state's relationships with Malaysia and Mahathir, China and Xi Jinping, and the rest of Southeast Asia. 3 days ago · The latest military news, insights and analysis from China. All the latest news, opinions and analysis on Hong Kong, China, Asia and around the world Latest news, in-depth features and opinion on Malaysia, covering politics, economy, society and the Asean member-nation's relationships with China, Singapore, and other Southeast Asian ... Oct 12, 2025 · Brics (an acronym for Brazil, Russia, India, China and South Africa) refers to an association of 10 leading emerging markets. The other member states are Egypt, Ethiopia, ...
INFO     | __main__:self_reflection:31 - Self-reflection failed -- binary_score=False
INFO     | __main__:query_rewriter:40 - Query rewritten: query='Which AI models have been officially released in June 2024?', retry_count: 1
INFO     | __main__:router:11 - Router selected the datasource: websearch
INFO     | __main__:router:12 - User query: query='Which AI models have been officially released in June 2024?'
Dream Machine is a text-to-video model created by Luma Labs and launched in June 2024 . It generates video output based on user prompts or still images. Dream Machine has been noted for its ability to realistically capture motion... Released in June 2023. In June 2024 , Baidu announced Ernie 4.0 Turbo. In April 2025, Ernie 4.5 Turbo and X1 Turbo were released . These models are optimized for faster response times and lower operational costs.[28][29]. The meaning of QUERY is question, inquiry. How to use query in a sentence. Synonym Discussion of Query. QUERY definition: 1. a question, often expressing doubt about something or looking for an answer from an authority.... Learn more. Query definition: a question; an inquiry.. See examples of QUERY used in a sentence.
INFO     | __main__:self_reflection:29 - Self-reflection passed -- binary_score=True
INFO     | __main__:<module>:2 - 
Final Summary:
: In June 2024, two AI models were officially released: Dream Machine, a text-to-video model launched by Luma Labs, and Ernie 4.0 Turbo, announced by Baidu, which is optimized for faster response times and lower operational costs.</code></pre><p>次に、2 番目のクエリで示されているように、 <code>vectorstore</code>検索が使用される例を調べます。</p><pre><code>INFO     | __main__:router:11 - Router selected the datasource: vectorstore
INFO     | __main__:router:12 - User query: What technological innovations are discussed in Sci/Tech news?
INFO     | __main__:self_reflection:29 - Self-reflection passed -- binary_score=True
INFO     | __main__:<module>:2 - 
Final Summary:
: Recent Sci/Tech news highlights several technological innovations: NASA is collaborating with Silicon Valley firms to build a powerful Linux-based supercomputer to support theoretical research and shuttle engineering; new chromatin transfer techniques have enabled the cloning of cats; cybersecurity advancements are being discussed in relation to protecting personal technology; Princeton University scientists assert that existing technologies can be used immediately to stabilize global warming; and a set of GameBoy micro-games has been recognized for innovation in game design.</code></pre><p>最後のクエリは、ベクターストアと Web 検索の両方を活用する複合検索に向けられます。</p><pre><code>INFO     | __main__:router:11 - Router selected the datasource: composite
INFO     | __main__:router:12 - User query: Compare a Sci/Tech article from the dataset with a current web article about AI trends.
Atlas currently only available on macOS, built on Chromium with planned features like ad-blocking still in development. OpenAI's Atlas browser launched with bold promises of AI -powered web browsing, but early real-world testing reveals a different story. Career-long data are updated to end-of-2024 and single recent year data pertain to citations received during calendar year 2024. The selection is based on the top 100,000 scientists by c-score (with and without self-citations) or a percentile rank of 2% or above in the sub-field. In this article I list 45 AI tools across 21 different categories. After exploring all the available options in each category, I've carefully selected the best tools based on my personal experience. Reading a complex technical article ? Simply highlight confusing terminology and ask "what's this?" to receive instant explanations. compare browsers. Comparison showing traditional browser navigation versus OpenAI Atlas AI -powered workflows. After putting Gemini, ChatGPT, Grok, and DeepSeek through rigorous testing in October 2025, it's clear that there isn't one AI that reigns supreme across all categories.
INFO     | __main__:self_reflection:29 - Self-reflection passed -- binary_score=True
INFO     | __main__:<module>:2 - 
Final Summary:
: A Sci/Tech article from the dataset highlights NASA's development of robust artificial intelligence software for planetary rovers, aiming to make them more self-reliant and capable of decision-making during missions. In contrast, a current web article about AI trends focuses on the proliferation of AI-powered tools across various categories, including browsers like OpenAI Atlas, and compares leading models such as Gemini, ChatGPT, Grok, and DeepSeek, noting that no single AI currently excels in all areas. While the NASA article emphasizes specialized AI applications for autonomous robotics in space exploration, the current trends article showcases the broadening impact of AI across consumer and professional technologies, with ongoing competition and rapid innovation among major AI platforms.</code></pre><p>上記のワークフローでは、エージェント RAG は、ユーザー クエリの情報を取得するときに使用するデータ ソースをインテリジェントに決定し、応答の精度と関連性を向上させます。追加の例を作成してエージェントをテストし、出力を確認して興味深い結果が得られるかどうかを確認できます。</p><h2>エージェント型 RAG ワークフローを構築するためのベストプラクティス</h2><p>エージェント RAG の仕組みがわかったので、次にこれらのワークフローを構築するためのベスト プラクティスをいくつか見てみましょう。これらのガイドラインに従うことで、システムを効率的に維持し、保守を容易にすることができます。</p><ul><li><strong>フォールバックの準備</strong>: ワークフローのいずれかのステップが失敗するシナリオに備えて、事前にフォールバック戦略を計画します。これらには、デフォルトの回答を返すこと、エラー状態をトリガーすること、代替ツールを使用することなどが含まれます。これにより、全体的なワークフローを中断することなく、システムが障害を適切に処理できるようになります。</li><li><strong>包括的なログ記録を実装する</strong>: 再試行、生成された出力、ルーティングの選択、クエリの書き換えなど、ワークフローの各段階でログ記録を実装してみます。これらのログは、透明性の向上、デバッグの容易化、プロンプト、エージェントの動作、および取得戦略の継続的な改善に役立ちます。</li><li><strong>適切なワークフロー パターンを選択する</strong>: ユースケースを調べて、ニーズに最適なワークフロー パターンを選択します。ステップごとの推論には順次ワークフローを使用し、独立したデータ ソースには並列ワークフローを使用し、複数のツールや複雑なクエリにはオーケストレーター ワーカー パターンを使用します。</li><li><strong>評価戦略を組み込む</strong>: ワークフローのさまざまな段階で評価メカニズムを統合します。これらには、自己反省エージェント、取得したドキュメントの採点、自動品質チェックなどが含まれます。評価は、取得されたドキュメントが関連性があり、応答が正確であり、複雑なクエリのすべての部分が処理されていることを確認するのに役立ちます。</li></ul><h2>課題</h2><p>エージェント RAG システムは、適応性、精度、動的推論の点で大きな利点を提供しますが、設計および実装の段階で対処する必要がある特定の課題も伴います。主な課題は次のとおりです。</p><ul><li><strong>複雑なワークフロー</strong>: エージェントと決定ポイントが追加されるにつれて、全体的なワークフローはますます複雑になります。これにより、実行時にエラーや障害が発生する可能性が高くなります。可能な限り、冗長なエージェントと不要な意思決定ポイントを排除して、合理化されたワークフローを優先します。</li><li><strong>スケーラビリティ</strong>: 大規模なデータセットと大量のクエリを処理するためにエージェント RAG システムを拡張するのは難しい場合があります。効率的なインデックス作成、キャッシュ、分散処理戦略を組み込んで、大規模なパフォーマンスを維持します。</li><li><strong>オーケストレーションと計算オーバーヘッド</strong>: 複数のエージェントによるワークフローの実行には、高度なオーケストレーションが必要です。これには、ボトルネックや競合を防ぐための慎重なスケジュール設定、依存関係の管理、エージェントの調整が含まれますが、これらはすべてシステム全体の複雑さを増大させます。</li><li><strong>評価の複雑さ</strong>: これらのワークフローの評価には、各段階で異なる評価戦略が必要になるため、固有の課題が伴います。たとえば、RAG ステージでは、取得したドキュメントの関連性と完全性を評価する必要があり、生成された要約については品質と正確性をチェックする必要があります。同様に、クエリの再定式化の有効性には、書き換えられたクエリによって検索結果が改善されるかどうかを判断するための別の評価ロジックが必要です。</li></ul><h2>まとめ</h2><p>このブログ投稿では、エージェント RAG の概念を紹介し、エージェント AI の自律機能を組み込むことで従来の RAG フレームワークをどのように強化するかについて説明しました。私たちは、エージェント RAG のコア機能を探り、Elasticsearch をベクター ストアとして使用し、LangChain を使用してエージェント フレームワークを作成するニュース アシスタントを構築するという実践的な例を通じて、これらの機能を実演しました。</p><p>さらに、エージェント RAG パイプラインを設計および実装する際に考慮すべきベスト プラクティスと主な課題についても説明しました。これらの洞察は、検索、推論、意思決定を効果的に組み合わせた、堅牢でスケーラブルかつ効率的なエージェント システムを開発者が作成できるようにするためのものです。</p><h2>次のステップ</h2><p>私たちが構築したワークフローはシンプルで、改善と実験の余地が十分にあります。さまざまな埋め込みモデルを試し、検索戦略を改良することで、これを強化できます。さらに、検索されたドキュメントに優先順位を付ける再ランク付けエージェントを統合すると有益となる可能性があります。もう一つの調査領域は、エージェント フレームワークの評価戦略の開発、具体的にはさまざまな種類のフレームワークに適用可能な共通かつ再利用可能なアプローチの特定です。最後に、これらのフレームワークを大規模でより複雑なデータセットで実験します。</p><p>その間、同様の実験を共有していただける場合は、ぜひお聞かせください。お気軽にフィードバックをお寄せください。また、<a href="https://ela.st/slack">コミュニティの Slack チャンネル</a>や<a href="https://discuss.elastic.co/c/security">ディスカッション フォーラム</a>を通じてご連絡いただくこともできます。</p><h2>各種資料</h2><ul><li><a href="https://arxiv.org/abs/2310.11511">セルフRAG：自己反省を通して、情報の検索、生成、批評を学ぶ</a></li><li><a href="https://arxiv.org/abs/2501.09136">エージェント検索拡張生成：エージェントRAGの調査</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[面倒な手間を省いたハイブリッド検索：リトリーバーによるハイブリッド検索の簡素化]]></title>
            <description><![CDATA[線形および RRF リトリーバーのマルチフィールド クエリ形式を使用して Elasticsearch でのハイブリッド検索を簡素化する方法と、Elasticsearch インデックスに関する事前の知識なしでクエリを作成する方法について説明します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/hybrid-search-multi-field-query-retrievers-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/hybrid-search-multi-field-query-retrievers-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Mike Pellegrini]]></dc:creator>
            <pubDate>Thu, 27 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4494f98ae3958bbdbc6171df9677fc4d65ec5640-1536x1024.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><a href="https://www.elastic.co/what-is/hybrid-search">ハイブリッド検索は</a>、<a href="https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch#lexical-search---sparse-retrieval">語彙検索</a>の精度と速度と<a href="https://www.elastic.co/what-is/semantic-search">セマンティック検索</a>の自然言語機能を組み合わせた強力な検索アプローチとして広く認識されています。ただし、実際に適用するのは難しい場合があり、インデックスに関する深い知識と、単純ではない構成での詳細なクエリの構築が必要になることがよくあります。このブログでは、<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers#multi-field-query-format">リニア リトリーバーと RRF リトリーバーのマルチフィールド クエリ形式によって</a>ハイブリッド検索がよりシンプルで使いやすくなり、よくある問題点が解消され、より簡単にその全機能を活用できるようになる方法について説明します。また、マルチフィールド クエリ形式を使用すると、インデックスに関する事前の知識がなくてもハイブリッド検索クエリを実行できる方法についても説明します。</p><h2>スコア範囲の問題</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c8b49153c47d2cdc233c0d2e440db04711d48ca5-1600x1600.jpg" style="width: 100%;" /><p>まず最初に、ハイブリッド検索が困難になる主な理由の 1 つである、スコア範囲の多様性について確認しましょう。私たちの古い友人<a href="https://www.elastic.co/elasticon/conf/2016/sf/improved-text-scoring-with-bm25">BM25</a>は無制限のスコアを生成します。言い換えれば、BM25 は 0 に近い値から (理論的には) 無限大までの範囲のスコアを生成できます。対照的に、 <code>dense_vector</code>フィールドに対するクエリでは、0 から 1 の範囲のスコアが生成されます。この問題をさらに悪化させるのは、 <code>semantic_text</code>埋め込みのインデックス作成に使用されるフィールド タイプが難読化されるため、インデックスと推論エンドポイントの構成に関する詳細な知識がない限り、クエリのスコアの範囲がどうなるかを判断するのが難しい場合があることです。これは、語彙検索結果と意味検索結果をインターリーブしようとするときに、意味検索結果の関連性が高い場合でも、語彙検索結果が意味検索結果よりも優先される可能性があるため、問題が発生します。この問題に対する一般的に受け入れられている解決策は、結果をインターリーブする前にスコアを正規化することです。Elasticsearch には、これを実行するためのツールとして、<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/linear-retriever">線形リトリーバー</a>と<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/rrf-retriever">RRF</a>リトリーバーの 2 つがあります。<br/></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/264481268c8b6ac259e3c257b85431b513f16672-1077x586.png" style="width: 100%;" /><p><strong>RRF</strong>リトリーバーは、ドキュメントのランクを関連性の尺度として使用し、スコアを破棄して、 <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">RRF アルゴリズム</a>を適用します。スコアは考慮されないため、スコア範囲の不一致は問題になりません。</p><p><strong>線形</strong>リトリーバーは線形結合を使用してドキュメントの最終スコアを決定します。これには、ドキュメントの各コンポーネント クエリのスコアを取得し、それを正規化し、合計して合計スコアを生成することが含まれます。数学的には、この操作は次のように表現できます。</p><pre><code>Total Score = 𝚺(N(Sx))</code></pre><p>ここで、 <code>N</code>は正規化関数であり、SX はクエリ X のスコアです。ここで重要なのは正規化関数です。正規化関数は各クエリのスコアを同じ範囲を使用するように変換します。リニア リトリーバーの詳細については、<a href="https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search">こちらを</a>ご覧ください。</p><h2>詳しく見てみる</h2><p>ユーザーはこれらのツールを使用して効果的なハイブリッド検索を実装できますが、インデックスに関するある程度の知識が必要です。線形リトリーバーを使用して、2 つのフィールドを持つインデックスをクエリする例を見てみましょう。</p><pre><code>PUT linear_retriever_example
{
  "mappings": {
    "properties": {
      "semantic_text_field": { <1>
        "type": "semantic_text",
        "inference_id": ".multilingual-e5-small-elasticsearch"
      },
      "text_field": { <2>
        "type": "text"
      }
    }
  }
}</code></pre><p>1. <code>semantic_text_field</code>は、テキスト埋め込みモデルである<a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5">E5</a>を使用する<code>semantic_text</code>フィールドです。</p><p>2. <code>text_field</code>は標準の<code>text</code>フィールドです</p><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "standard": {
              "query": {
                "match": { <1>
                  "semantic_text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}</code></pre><p>1. <a href="https://www.elastic.co/search-labs/blog/semantic-search-match-knn-sparse-vector#we-made-match-happen-in-semantic-search!">Elasticsearch 8.18/9.0でサポートされた</a><code>semantic_text</code>フィールドで<code>match</code>クエリを使用します。</p><p><br/>クエリを構築するときは、 <code>semantic_text_field</code>テキスト埋め込みモデルを使用するため、このクエリでは 0 から 1 の間のスコアが生成されることに留意する必要があります。また、 <code>text_field</code>は標準の<code>text</code>フィールドであるため、これに対するクエリによって無制限のスコアが生成されることも知っておく必要があります。適切な関連性を持つ結果セットを作成するには、クエリ スコアを結合する前に正規化するリトリーバーを使用する必要があります。この例では、 <code>minmax</code>正規化を備えた線形リトリーバーを使用して、各クエリのスコアを 0 から 1 の間の値に正規化します。</p><p>この例のクエリ構築は、関係するフィールドが 2 つだけなので、非常に簡単です。ただし、さまざまなタイプのフィールドが追加されると、すぐに複雑になる可能性があります。これは、効果的なハイブリッド検索クエリを記述するには、クエリ対象のインデックスに関するより深い知識が必要になることが多く、組み合わせる前にコンポーネント クエリ スコアが適切に正規化される必要があることを示しています。これは、ハイブリッド検索のより広範な導入の障害となります。</p><h3>クエリのグループ化</h3><p>例を拡張してみましょう。1 つの<code>text</code>フィールドと 2 つの<code>semantic_text</code>フィールドをクエリしたい場合はどうなるでしょうか。次のようなクエリを作成できます。</p><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "semantic_text_field_1",
                  "query": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "semantic_text_field_2",
                  "query": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}</code></pre><p>表面的には良さそうですが、潜在的な問題があります。これで、 <code>semantic_text</code>フィールドの一致が合計スコアの 2/3 を占めることになります。</p><pre><code>Total Score = N(semantic_text_field_1 score) + N(semantic_text_field_2 score) + N(text_field score)</code></pre><p>これは、不均衡なスコアを作成するため、おそらく望ましい結果ではありません。この例のようにフィールドが 3 つしかない場合、影響はそれほど顕著ではないかもしれませんが、より多くのフィールドをクエリすると問題が生じます。例えば、ほとんどの索引には意味フィールド（つまり<code>dense_vector</code> 、 <code>sparse_vector</code> 、または<code>semantic_text</code> )。上記のパターンを使用して、9 つの語彙フィールドと 1 つの意味フィールドを持つインデックスをクエリするとどうなるでしょうか?語彙の一致がスコアの 90% を占めることになり、意味検索の有効性が鈍ってしまいます。</p><p>これに対処する一般的な方法は、クエリを語彙と意味のカテゴリにグループ化し、その 2 つに均等に重み付けすることです。これにより、どちらかのカテゴリーが合計スコアを支配することが防止されます。</p><p>それを実践してみましょう。この例では、線形リトリーバーを使用する場合、グループ化されたクエリのアプローチはどのようになるでしょうか?</p><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "linear": {
              "retrievers": [
                {
                  "retriever": {
                    "standard": {
                      "query": {
                        "semantic": {
                          "field": "semantic_text_field_1",
                          "query": "foo"
                        }
                      }
                    }
                  },
                  "normalizer": "minmax"
                },
                {
                  "retriever": {
                    "standard": {
                      "query": {
                        "semantic": {
                          "field": "semantic_text_field_2",
                          "query": "foo"
                        }
                      }
                    }
                  },
                  "normalizer": "minmax"
                }
              ]
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}</code></pre><p>うわー、これは冗長になってきましたね!クエリ全体を確認するには、上下に何度もスクロールする必要があったかもしれません。ここでは、2 つのレベルの正規化を使用してクエリ グループを作成します。数学的には次のように表現できます。</p><pre><code>Total Score = N(N(semantic_text_field_1 score) + N(semantic_text_field_2 score)) + N(text_field score)</code></pre><p>この 2 番目のレベルの正規化により、 <code>semantic_text</code>フィールドと<code>text</code>フィールドに対するクエリが均等に重み付けされるようになります。この例では、語彙フィールドが 1 つしかないため、 <code>text_field</code>の 2 番目のレベルの正規化を省略し、冗長性を<em>さらに</em>軽減していることに注意してください。</p><p>このクエリ構造はすでに扱いにくく、クエリするフィールドは 3 つだけです。より多くのフィールドをクエリするにつれて、熟練した検索実践者にとっても管理がますます困難になります。</p><h2>複数フィールドのクエリ形式</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/dd04e1686076a574b717c1460acfe4eb79299208-1600x1600.jpg" style="width: 100%;" /><p>これらすべてを簡素化するために、Elasticsearch 8.19、9.1、 サーバーレスの 線形および RRF リトリーバーに<a href="https://www.elastic.co/cloud/serverless"> マルチフィールド</a><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers#multi-field-query-format"> クエリ形式</a> を追加しました。次のようにするだけで、上記と同じクエリを実行できます。</p><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}</code></pre><p>これにより、クエリが 55 行から 9 行に短縮されます。Elasticsearch はインデックス マッピングを自動的に使用して次の処理を実行します。</p><ul><li>クエリされた各フィールドのタイプを決定する</li><li>各フィールドを語彙または意味のカテゴリにグループ化します</li><li>最終スコアでは各カテゴリーを均等に重み付けする</li></ul><p>これにより、使用されるインデックスや推論エンドポイントの詳細を知らなくても、誰でも効果的なハイブリッド検索クエリを実行できるようになります。</p><p>RRF を使用する場合、ランクは関連性の代理として使用されるため、 <code>normalizer</code>を省略できます。</p><pre><code>GET rrf_retriever_example/_search
{
  "retriever": {
    "rrf": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field" ],
      "query": "foo"
    }
  }
}</code></pre><h2>フィールドごとのブースティング</h2><p>リニア リトリーバーを使用する場合、フィールドごとにブーストを適用して、特定のフィールドでの一致の重要度を調整できます。たとえば、2 つの<code>semantic_text</code>フィールドと 2 つの<code>text</code>フィールドの 4 つのフィールドをクエリするとします。</p><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field_1", "text_field_2" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}</code></pre><p>デフォルトでは、各フィールドはグループ内（語彙または意味）で均等に重み付けされます。スコアの内訳は次のようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ba31c76189e3a1e5b1638437ccf0528aafec2598-1600x549.png" style="width: 100%;" /><p>つまり、各フィールドは合計スコアの 25% を占めます。</p><p><code>field^boost</code>構文を使用して、任意のフィールドにフィールドごとのブーストを追加できます。<code>semantic_text_field_1</code>と<code>text_field_1</code>に2のブーストを適用してみましょう。</p><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1^2", "semantic_text_field_2", "text_field_1^2", "text_field_2" ]
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}</code></pre><p>スコアの内訳は次のようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/de20e555d52f914bf483a048d056f54f4fece757-1600x549.png" style="width: 100%;" /><p>各クエリ グループの重みは均等ですが、グループ内のフィールドの重みは次のように変更されました。</p><ul><li><code>semantic_text_field_1</code> セマンティッククエリグループスコアの66％、合計スコアの33％</li><li><code>text_field_1</code> 語彙質問グループスコアの66％、総スコアの33％</li></ul>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ℹ️ フィールドごとのブーストを適用しても、合計スコアの範囲は変更されないことに注意してください。これはスコア正規化の意図された副作用であり、語彙クエリスコアと意味クエリスコアが互いに直接比較可能のままになることを保証します。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ℹ️ フィールドごとのブースティングは、Elasticsearch 9.2 以降の RRF リトリーバーでも使用できます。</td>
                    </tr>
              </tbody>
            </table>
          <h3>ワイルドカード解決</h3><p>複数のフィールドを一致させるには、 <code>fields</code>パラメータで<code>*</code>ワイルドカードを使用できます。上記の例を続けると、このクエリは機能的には<code>emantic_text_field_1</code> 、 <code>semantic_text_field_2</code> 、 <code>text_field_1</code>明示的にクエリすることと同等です。</p><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_*", "*_field_1" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}</code></pre><p>興味深いことに、 <code>*_field_1</code>パターンは<code>text_field_1</code>と<code>semantic_text_field_1</code>の両方に一致します。これは自動的に処理され、各フィールドが明示的にクエリされたかのようにクエリが実行されます。<code>semantic_text_field_1</code>が両方のパターンに一致することも問題ありません。すべてのフィールド名の一致は、クエリの実行前に重複が排除されます。</p><p>ワイルドカードはさまざまな方法で使用できます。</p><ul><li>プレフィックス一致（例： <code>*_text_field</code> ）</li><li>インラインマッチング（例： <code>semantic_*_field</code> ）</li><li>サフィックス一致（例： <code>semantic_text_field_*</code> ）</li></ul><p><code>*_text_field_*</code>のように、複数のワイルドカードを使用して上記の組み合わせを適用することもできます。</p><h3>デフォルトのクエリフィールド</h3><p>マルチフィールド クエリ形式を使用すると、何も知らないインデックスをクエリすることもできます。<code>fields</code>パラメータを省略すると、 <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules">index.query.default_field インデックス設定</a>で指定されたすべてのフィールドがクエリされます。</p><pre><code>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}</code></pre><p>デフォルトでは、 <code>index.query.default_field</code>は<code>*</code>に設定されています。このワイルドカードは、用語クエリをサポートするインデックス内のすべてのフィールド タイプ (ほとんど) に解決されます。例外は次のとおりです:</p><ul><li><code>dense_vector</code> フィールド</li><li><code>rank_vector</code> フィールド</li><li>ジオメトリフィールド: <code>geo_point</code> 、 <code>shape</code></li></ul><p>この機能は、サードパーティが提供するインデックスに対してハイブリッド検索クエリを実行する場合に特に便利です。マルチフィールド クエリ形式を使用すると、適切なクエリを簡単な方法で実行できます。<code>fields</code>パラメータを除外するだけで、該当するすべてのフィールドが照会されます。</p><h2>まとめ</h2><p>スコア範囲の問題により、特にクエリ対象のインデックスや使用中の推論エンドポイントに関する情報が限られている場合、効果的なハイブリッド検索の実装が困難になる可能性があります。リニア リトリーバーと RRF リトリーバーのマルチフィールド クエリ形式では、自動化されたクエリ グループ化ベースのハイブリッド検索アプローチをシンプルで使いやすい API にパッケージ化することで、この煩わしさを軽減します。フィールドごとのブースト、ワイルドカード解決、デフォルトのクエリ フィールドなどの追加機能により、機能が拡張され、多くのユース ケースをカバーできます。</p><h2>今すぐマルチフィールドクエリ形式をお試しください</h2><p>無料トライアル では、完全に管理された Elasticsearch<a href="https://www.elastic.co/cloud/serverless"> Serverless</a> プロジェクトで、マルチフィールド<a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/create-serverless-project"> クエリ形式を使用した線形リトリーバーと RRF</a> リトリーバーを試すことができます。8.19 および 9.1 以降のスタック バージョンでも利用できます。</p><p>1 つのコマンドでローカル環境で数分以内に開始できます。</p><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre><p></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elastic Agent Builder と GPT-OSS を使用した HR 向け AI エージェントの構築]]></title>
            <description><![CDATA[Elastic Agent Builder と GPT-OSS を使用して、従業員の HR データに関する自然言語クエリに回答できる AI エージェントを構築する方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/build-an-ai-agent-hr-elastic-agent-builder-gpt-oss</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/build-an-ai-agent-hr-elastic-agent-builder-gpt-oss</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
            <pubDate>Wed, 26 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/05d2d0513fff67d975f9223d75108aa9f50646bc-1600x914.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>はじめに</h2><p>この記事では<a href="https://openai.com/index/introducing-gpt-oss/">、GPT-OSS</a>と Elastic Agent Builder を使用して HR 向けの AI エージェントを構築する方法を説明します。エージェントは、OpenAI、Anthropic、その他の外部サービスにデータを送信せずに質問に答えることができます。</p><p>LM Studio を使用して GPT-OSS をローカルで提供し、Elastic Agent Builder に接続します。</p><p>この記事を読み終える頃には、情報とモデルを完全に制御しながら、従業員データに関する自然言語の質問に答えることができるカスタム AI エージェントが完成しているはずです。</p><h2>要件</h2><p>この記事には以下が必要です:</p><ul><li><a href="https://www.elastic.co/cloud">Elastic Cloud</a>ホスト 9.2、サーバーレスまたは<a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">ローカル</a>展開</li><li>32GB RAM搭載マシンを推奨（GPT-OSS 20Bの場合は最低16GB）</li><li><a href="https://lmstudio.ai/">LM Studio</a>がインストール済み</li><li><a href="https://www.docker.com/products/docker-desktop/">Dockerデスクトップ</a>がインストール済み</li></ul><h2>GPT-OSS を使用する理由は何ですか?</h2><p>ローカル LLM を使用すると、独自のインフラストラクチャに LLM を展開し、独自のニーズに合わせて微調整することができます。モデルと共有するデータの制御を維持しながら、これらすべてを実行できます。もちろん、外部プロバイダーにライセンス料を支払う必要はありません。</p><p>OpenAI は、オープン モデル エコシステムへの取り組みの一環として、2025 年 8 月 5 日に<a href="https://openai.com/index/introducing-gpt-oss/">GPT-OSS をリリースしました</a>。</p><p>20B パラメータ モデルは以下を提供します。</p><ul><li><strong>ツール使用能力</strong></li><li><strong>効率的な推論</strong></li><li><strong>OpenAI SDK対応</strong></li><li><strong>エージェントワークフローと互換性あり</strong></li></ul><p>ベンチマーク比較:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/29160e3345352088e8213297630882f252b00c47-1600x680.png" style="width: 100%;" /><h2>ソリューションアーキテクチャ</h2><p>アーキテクチャは完全にローカル マシン上で実行されます。Elastic (Docker で実行) は LM Studio を介してローカル LLM と直接通信し、Elastic Agent Builder はこの接続を使用して従業員データを照会できるカスタム AI エージェントを作成します。</p><p>詳細については、 こちらの<a href="https://www.elastic.co/docs/solutions/observability/connect-to-own-local-llm">ドキュメント</a>を参照してください。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a4a886750ff25fa8bb7aefc7448161e52cf73ed3-1600x896.png" style="width: 100%;" /><h2>HR向けAIエージェントの構築：手順</h2><p>実装は 5 つのステップに分けられます。</p><ol><li>ローカルモデルでLMスタジオを構成する</li><li>DockerでローカルElasticをデプロイする</li><li>ElasticでOpenAIコネクタを作成する</li><li>従業員データをElasticsearchにアップロードする</li><li>AIエージェントを構築してテストする</li></ol><h2>ステップ1：LM StudioをGPT-OSS 20Bで構成する</h2><p>LM Studio は、大規模な言語モデルをコンピュータ上でローカルに実行できるユーザーフレンドリーなアプリケーションです。OpenAI 互換の API サーバーを提供するため、複雑なセットアップ プロセスなしで Elastic などのツールと簡単に統合できます。詳細については、 <a href="https://lmstudio.ai/docs/app">LM Studio ドキュメント</a>を参照してください。</p><p>まず、公式サイトから<span class="unknown__pt__mark__51566e628851">LM Studioを</span>ダウンロードしてインストールします。インストールしたら、アプリケーションを開きます。</p><h3>LM Studio インターフェースの場合:</h3><ol><li>検索タブに移動して「GPT-OSS」を検索します。</li><li>OpenAIから<code>openai/gpt-oss-20b</code>を選択してください</li><li>ダウンロードをクリック</li></ol><p>このモデルのサイズは約<strong>12.10 GB</strong>になります。インターネット接続によっては、ダウンロードに数分かかる場合があります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5d01bc4dcb377b5259fc6b521fe2425a31b90ca4-1312x872.png" style="width: 100%;" /><h4>モデルをダウンロードしたら:</h4><ol><li>ローカルサーバータブに移動します</li><li>openai/gpt-oss-20bを選択します</li><li>デフォルトのポート1234を使用する</li><li>右側のパネルで、 <strong>「ロード」</strong>に移動し、コンテキストの長さを<strong>40K</strong>以上に設定します。</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e546033f916381647b876815b2c1f1ae2a08365f-326x337.png" style="width: 100%;" /><p>5. サーバーの開始をクリック</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/28ee78a3caa84d14e04db3d42f30acbe4d4d005a-1312x872.png" style="width: 100%;" /><p>サーバーが実行中の場合はこれが表示されます。</p><pre><code>[LM STUDIO SERVER] Success! HTTP server listening on port 1234
[LM STUDIO SERVER] Supported endpoints:
[LM STUDIO SERVER] ->	GET  http://localhost:1234/v1/models
[LM STUDIO SERVER] ->	POST http://localhost:1234/v1/responses
[LM STUDIO SERVER] ->	POST http://localhost:1234/v1/chat/completions
[LM STUDIO SERVER] ->	POST http://localhost:1234/v1/completions
[LM STUDIO SERVER] ->	POST http://localhost:1234/v1/embeddings
Server started.</code></pre><h2>ステップ2: DockerでローカルElasticをデプロイする</h2><p>ここで、Docker を使用して Elasticsearch と Kibana をローカルにセットアップします。Elastic は、セットアッププロセス全体を処理する便利なスクリプトを提供します。詳細については、<a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">公式ドキュメント</a>を参照してください。</p><h3>start-local スクリプトを実行する</h3><p>ターミナルで次のコマンドを実行します。</p><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre><p>このスクリプトは次のことを行います。</p><ul><li>ElasticsearchとKibanaをダウンロードして設定する</li><li>Docker Composeを使用して両方のサービスを開始します</li><li>30日間のプラチナトライアルライセンスを自動的に有効化</li></ul><h3>期待される出力</h3><p>次のメッセージが表示されるまで待ち、表示されるパスワードと API キーを保存します。これらは Kibana にアクセスするために必要になります。</p><pre><code>🎉 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</code></pre><h3>Kibanaにアクセスする</h3><p>ブラウザを開いて次の場所に移動します:</p><pre><code>http://localhost:5601</code></pre><p>ターミナル出力で取得した資格情報を使用してログインします。</p><h3>エージェントビルダーを有効にする</h3><p>Kibana にログインしたら、 <strong>[Management]</strong> &gt; <strong>[AI]</strong> &gt; <strong>[Agent Builder]</strong>に移動して、Agent Builder をアクティブ化します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/92e104cb846c20d875865ded8a3d37f5c7daae9b-1491x1528.png" style="width: 100%;" /><h2>ステップ3: ElasticでOpenAIコネクタを作成する</h2><p>ここで、ローカル LLM を使用するように Elastic を構成します。</p><h3>アクセスコネクタ</h3><ol><li>キバナで</li><li><strong>プロジェクト設定</strong>&gt;<strong>管理</strong>に移動します</li><li><strong>アラートとインサイトの</strong>下で、<strong>コネクタ</strong>を選択します。</li><li>コネクタの作成をクリック</li></ol><h3>コネクタを構成する</h3><p>コネクタのリストから<strong>OpenAI を</strong>選択します。LM Studio は OpenAI SDK を使用しているため、互換性があります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5ac87042e086c7a2bd47a8039e646ec831f0dcc6-923x974.png" style="width: 100%;" /><p>次の値をフィールドに入力します。</p><ul><li><strong>コネクタ名:</strong> LM Studio - GPT-OSS 20B</li><li><strong>OpenAIプロバイダーを選択:</strong>その他 (OpenAI互換サービス)</li><li><strong>URL: </strong><code>http://host.docker.internal:1234/v1/chat/completions</code></li><li><strong>デフォルトモデル:</strong> openai/gpt-oss-20b</li><li><strong>API キー:</strong> testkey-123 (LM Studio Server では認証が不要なので、任意のテキストを使用できます。)</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2084ac32fcf1fb810c8b54ecab1c85a1e3e8905b-672x1302.png" style="width: 100%;" /><p>設定を完了するには、 <strong>「保存してテスト」</strong>をクリックします。</p><p><strong>重要:</strong> 「<strong>ネイティブ関数の呼び出しを有効にする</strong>」をオンにします。これは、Agent Builder が正しく動作するために必要です。これを有効にしないと、 <strong><code>No tool calls found in the response</code></strong>エラーが発生します。</p><h3>接続をテストする</h3><p>Elastic は自動的に接続をテストするはずです。すべてが正しく構成されている場合、次のような成功メッセージが表示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f567d767f1969c4730c1daa92f651789dc3742ac-1042x812.png" style="width: 100%;" /><p>対応：</p><pre><code>{
  "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"
}</code></pre><h2>ステップ4: 従業員データをElasticsearchにアップロードする</h2><p>ここで、 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/gpt-oss-with-elasticsearch/hr-employees-bulk.json">HR 従業員データセット</a>をアップロードして、エージェントが機密データをどのように処理するかを説明します。私はこの構造を持つ架空のデータセットを生成しました。</p><h3>データセットの構造</h3><pre><code>{
  "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"
}</code></pre><h3>マッピングを使用してインデックスを作成する</h3><p>まず、適切なマッピングを使用してインデックスを作成します。一部のキー フィールドに<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">semantic_text</a>フィールドを使用していることに注意してください。これにより、インデックスのセマンティック検索機能が有効になります。</p><pre><code>​​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"
      }
    }
  }
}</code></pre><h3>Bulk APIを使用したインデックス</h3><p><a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/gpt-oss-with-elasticsearch/hr-employees-bulk.json">データセット</a>をコピーして Kibana の開発ツールに貼り付け、実行します。</p><pre><code>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": "...", ...}</code></pre><h3>データを検証する</h3><p>クエリを実行して確認します。</p><pre><code>GET hr-employees/_search</code></pre><h2>ステップ5: AIエージェントを構築してテストする</h2><p>すべての設定が完了したら、Elastic Agent Builder を使用してカスタム AI エージェントを構築します。詳細については、 <a href="https://www.elastic.co/docs/solutions/search/agent-builder/get-started">Elastic のドキュメント</a>を参照してください。</p><h3>コネクタを追加する</h3><p>新しいエージェントを作成する前に、デフォルトのコネクタは<a href="https://www.elastic.co/docs/reference/kibana/connectors-kibana/elastic-managed-llm">Elastic Managed LLM</a>であるため、 <code>LM Studio - GPT-OSS 20B</code>というカスタム コネクタを使用するようにエージェント ビルダーを設定する必要があります。そのためには、 <strong>「プロジェクト設定」</strong> &gt; <strong>「管理」</strong> &gt; <strong>「GenAI 設定」</strong>に移動し、作成した設定を選択して<strong>「保存」</strong>をクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/11e830c3e2fb4c298b020c928fa5422f3397ba08-1600x1152.png" style="width: 100%;" /><h3>アクセスエージェントビルダー</h3><ol><li><strong>エージェント</strong>へ</li><li><strong>「新しいエージェントを作成」</strong>をクリックします</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c1e60541563650163f972ac9088dc1ed1de759a7-1600x1054.png" style="width: 100%;" /><h3>エージェントを構成する</h3><p>新しいエージェントを作成するには、<strong>エージェント ID</strong> 、<strong>表示名</strong>、および<strong>表示手順</strong>が必須フィールドです。</p><p>ただし、システム プロンプトに似ていますが、カスタム エージェント用の、エージェントの動作やツールとの対話方法をガイドするカスタム インストラクションなど、さらに多くのカスタマイズ オプションがあります。ラベルは、エージェント、アバターの色、アバター シンボルを整理するのに役立ちます。</p><p>データセットに基づいてエージェント用に選択したものは次のとおりです。<br/><br/><strong>エージェントID:</strong> <code>hr_assistant</code></p><p><strong>カスタム指示:</strong></p><pre><code>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</code></pre><p><br/>ラベル: <code>Human Resources</code>および <code>GPT-OSS</code></p><p>表示名： <code>HR Analytics Assistant</code></p><p>表示の説明:</p><pre><code>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.</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f94bb2bf08497e5e756ca76b30a3a51f42927756-1424x1217.png" style="width: 100%;" /><p>すべてのデータが入力されたら、新しいエージェントの<strong>「保存」</strong>をクリックします。</p><h3>エージェントをテストする</h3><p>従業員データについて自然言語で質問できるようになり、GPT-OSS 20B が意図を理解して適切な応答を生成します。</p><h4>プロンプト：</h4><pre><code>Which employee is the one with the highest salary in the hr-employees index?</code></pre><h4>答え：</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/94ad19f80b96304028a59f60beca51dfc9aecc8a-899x631.png" style="width: 100%;" /><p>エージェントのプロセスは次のとおりです。</p><p>1. GPT-OSSコネクタを使用して質問を理解する</p><p>2. 適切なElasticsearchクエリを生成する（組み込みツールまたはカスタム<a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a>を使用）</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6f2961d0d1b97475f6dda300acee84da540938e6-844x466.png" style="width: 100%;" /><p>3. 一致する従業員レコードを取得する</p><p>4. 適切なフォーマットで自然言語で結果を提示する</p><p>従来の語彙検索とは異なり、GPT-OSS を搭載したエージェントは意図とコンテキストを理解するため、正確なフィールド名やクエリ構文を知らなくても情報を簡単に見つけることができます。エージェントの思考プロセスの詳細については、こちらの<a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance">記事</a>を参照してください。</p><h2>まとめ</h2><p>この記事では、Elastic の Agent Builder を使用してカスタム AI エージェントを構築し、ローカルで実行されている OpenAI GPT-OSS モデルに接続しました。このアーキテクチャでは、Elastic と LLM の両方をローカルマシンにデプロイすることで、外部サービスに情報を送信することなく、データに対する完全な制御を維持しながら生成 AI 機能を活用できます。</p><p>実験としてはGPT-OSS 20Bを使用しましたが、Elastic Agent Builderの公式推奨モデルは<a href="https://www.elastic.co/docs/solutions/search/agent-builder/models#recommended-models">こちらを</a>参考にしています。より高度な推論機能が必要な場合は、複雑なシナリオでより優れたパフォーマンスを発揮する<a href="https://huggingface.co/openai/gpt-oss-120b">120B パラメータ バリアント</a>もありますが、ローカルで実行するにはより高性能なマシンが必要です。詳細については、 <a href="https://openai.com/open-models/">OpenAI の公式ドキュメント</a>を参照してください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cal Hacks 12.0 で取り上げた Elastic Agent Builder のトッププロジェクトと学習内容]]></title>
            <description><![CDATA[Cal Hacks 12.0 のトップ Elastic Agent Builder プロジェクトを探索し、サーバーレス、ES|QL、エージェント アーキテクチャに関する技術的なポイントを詳しく調べます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/agent-builder-projects-learnings-cal-hacks-12-0</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/agent-builder-projects-learnings-cal-hacks-12-0</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[JD Armada]]></dc:creator>
            <pubDate>Tue, 25 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8ba034a6f19b50521f541b8131756a8acdb52975-1280x960.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<a href="https://www.youtube.com/watch/GUI0WXNZduo">Watch video on YouTube</a><p>数週間前、私たちは、世界中から 2,000 人を超える参加者が集まる最大規模の対面ハッカソンの 1 つである<a href="https://cal-hacks-12-0.devpost.com/">Cal Hacks 12.0 を</a>スポンサーするという素晴らしい機会を得ました。Elastic Agent Builder on Serverless の最も優れた活用方法に専用の賞品トラックを設けましたが、反響は驚くほど大きかったです。わずか 36 時間で、山火事インテリジェンス ツールの構築から StackOverflow バリデーターまで、Agent Builder を独創的な方法で使用した 29 件の応募を受け取りました。</p><p>Cal Hacks 12.0 での経験は、印象的なプロジェクト以外にも、同様に貴重なものをもたらしてくれました。それは、初めて当社のスタックに遭遇した開発者からの、迅速でフィルターされていないフィードバックです。ハッカソンは、厳しい期限、事前の知識ゼロ、そして予測不可能な障害（悪名高い WiFi の停止など）を伴う、ユニークなプレッシャーテストです。開発者エクスペリエンスが優れている点と、まだ改善が必要な点が正確に明らかになります。開発者が LLM 主導のワークフローを通じて新しい方法で Elastic Stack を操作することが増えているため、これは現在さらに重要になっています。このブログ投稿では、参加者が Agent Builder を使用して構築したものと、そのプロセスで学んだことについてさらに詳しく説明します。</p><h2>受賞プロジェクト</h2><h3>1位: AgentOverflow</h3><p>LLM およびエージェント時代に合わせて再構築された Stack Overflow。</p><p>AgentOverflow の詳細については、<a href="https://devpost.com/software/agentoverflow">こちらを</a>ご覧ください。</p><p>AgentOverflow は、ほとんどの AI 開発者が遭遇する問題、つまり LLM が幻覚を起こし、チャット履歴が消え、開発者が同じ問題を再度解決するのに時間を無駄にする問題に対処します。</p><p>AgentOverflow は実際の問題と解決策のペアをキャプチャ、検証、再表示するため、開発者は幻覚スパイラルを打破し、より早く製品を出荷できます。</p><h4>仕組み：</h4><p><strong>1. JSON（「ソリューション スキーマ」）を共有します。</strong></p><p>Claude の共有から 1 回クリックすると、次の内容を含む構造化形式である Share Solution JSON がスクレイピング、抽出、組み立てられます。</p><ul><li>問題</li><li>コンテクスト</li><li>コード</li><li>タグ</li><li>検証済みの解決手順。</li></ul><p>バリデーター (LAVA) が構造をチェックして強制し、ユーザーが追加のコンテキストの行を追加すると、Elasticsearch 内に保存されてインデックスが作成されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/45a3e96f4474050a855419628c2a7338bb12c706-1600x877.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2d65cb154d8ee32fc96ff17dfa5b0bf2636e3777-1600x1002.png" style="width: 100%;" /><p><strong>2. 解決策を見つける</strong></p><p>行き詰まったら、 <code>Find Solution</code>をクリックすると、AgentOverflow が現在の会話をスクレイピングし、それを使用してクエリを作成し、ハイブリッド Elasticsearch 検索を実行して次の内容を表示します。</p><ul><li>ランク付けされたコミュニティ検証済みの修正</li><li>当初問題を解決した正確なプロンプト</li></ul><p>これにより、開発者は現在のセッションをすばやくコピー、貼り付け、ブロック解除できます。</p><p><strong>3. MCP - LLMのコンテキスト注入</strong></p><p>MCP (モデル コンテキスト プロトコル) を介して Elasticsearch 内に保存された構造化ソリューションに接続することにより、LLM には実行時に余分なノイズなしで高度な信号コンテキスト (コード、ログ、構成、以前の修正) が供給されます。</p><p>AgentOverflow は、関連するコンテキストを LLM に挿入する構造化メモリ レイヤーとして、Elasticsearch を備えた Agent Builder を使用します。これにより、受動的なチャットボットからコンテキストを認識した問題解決者へと変化します。</p><h3>準優勝：マーケットマインド</h3><p>6 つの Elastic Agent を活用した、市場エネルギーのリアルタイムの解釈可能なビュー。</p><p>MarketMindの詳細については、<a href="https://devpost.com/software/marketmind-b6cy2q">こちらを</a>ご覧ください。</p><p>MarketMind は、初心者トレーダーに、断片化された市場データを明確でリアルタイムなシグナルに変換するプラットフォームを提供することで、その地位を獲得しました。MarketMind は、さまざまなツール間で価格変動、ファンダメンタルズ、センチメント、ボラティリティを調整する代わりに、これらすべての情報を 1 つのプラットフォームに統合し、トレーダーが実用的な洞察を得られるよう支援します。このプロジェクトでは、エージェントの構築時に複雑な ES|QL クエリも使用しました。</p><h4>仕組み：</h4><p><strong>1. リアルタイムの市場データを収集する</strong></p><p>MarketMind は、Yahoo Finance から価格動向、ファンダメンタルズ、センチメント、ボラティリティ、リスク指標を取得します。このデータは複数の Elasticsearch インデックスに取り込まれ、整理されます。</p><p><strong>2. 6人の専門エージェントが市場を分析</strong></p><p>Agent Builder で構築された各エージェントは、市場の異なる層に焦点を当てています。これらは Elasticsearch インデックスから読み取り、独自のドメイン固有のメトリックを計算し、スコアと推論を含む標準化された JSON 出力を生成します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7d9716cca06a047a2b3584378b5c7e592a785ba1-1284x878.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5aac6a833347c0d2e596c02049ec4b4d3aae5cd7-794x764.png" style="width: 100%;" /><p><strong>3. シグナルを統合した「市場エネルギー」モデルに集約する</strong></p><p>組み合わせた出力は各株の周囲に光るパルスとして表示され、勢いが高まっているのか、リスクが高まっているのか、感情が変化しているのかを示します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/46b3da8e3d528c5dd4e2829416c5446098acb3aa-744x718.png" style="width: 100%;" /><p><strong>4. 洞察を視覚化する</strong></p><p>フロントエンドは、TypeScript、SVG 物理ベースのビジュアル、ライブ ローソク足チャート用の<a href="https://github.com/chartjs"> Chart.js</a> を使用して、React と<a href="https://github.com/vercel/next.js"> Next.js で構築されました。</a>これにより、生の分析がリアルタイムで実用的なフィードバックに変換されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3f000c043117b77ed4127202be5a49c12e3682ba-1600x930.png" style="width: 100%;" /><h2>その他の興味深いプロジェクト:</h2><p>スタックのさまざまな部分で Elastic を使用した他の有力な候補を次に示します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/76c49a853426844f475cd6b2a74999e60af20e8c-926x1080.png" style="width: 100%;" /><p>私たちのトラックに提出されたプロジェクトの完全なリストは、<a href="https://cal-hacks-12-0.devpost.com/submissions/search?utf8=%E2%9C%93&amp;prize_filter%5Bprizes%5D%5B%5D=91882">こちらで</a>ご覧ください。</p><h2>開発者から学んだこと</h2><ul><li><strong>Agent Builder はユーザーフレンドリーです:</strong></li></ul><p>ほとんどのチームはこれまで Elastic を使用したことがありませんでしたが、それでもほとんどサポートなしでエージェントを迅速に構築できました。さらに詳しい指導が必要な人向けにワークショップを開催しましたが、ほとんどの人はデータを取り込み、そのデータに基づいてアクションを実行するエージェントを構築することができました。</p><ul><li>LLM は<strong><code>kNN</code></strong>クエリ<strong>に優れていますが、ES|QL の生成には依然としてガイダンスが必要です。</strong></li></ul><p>ChatGPT-5 に ES|QL クエリの生成を依頼すると、ES|QL と SQL が混在するなど、誤った情報が返されることがよくありました。LLM にマークダウン ファイルでドキュメントを供給することは、実行可能な修正であるように思われました。</p><ul><li><strong>スナップショット専用の ES|QL 関数がドキュメントに漏洩しました:</strong></li></ul><p>今後登場する<code>FIRST</code>および<code>LAST</code>集計関数が、意図せず ES|QL ドキュメントに紛れ込んでしまいました。これらのドキュメントを ChatGPT に渡したため、Serverless ではまだ利用できないにもかかわらず、モデルはこれらの関数を忠実に使用しました。グループからのフィードバックのおかげで、エンジニアリングはすぐに修正を公開し、マージして、公開されたドキュメントから関数を削除しました ( <a href="https://github.com/elastic/elasticsearch/pull/137341">PR #137341</a> )。</p><ul><li><strong>サーバーレス固有のガイダンスが不足しています:</strong></li></ul><p>チームは、ルックアップ モードで作成されなかったインデックスで<code>LOOKUP JOIN</code>有効にしようとしました。エラー メッセージにより、Serverless に存在しないコマンドが追跡されました。私たちはこれを製品チームに伝え、製品チームはすぐに Serverless 固有の実用的なメッセージの修正を開始しました。長期的には、再インデックスの複雑さを完全に隠すことがビジョンです (<a href="https://github.com/elastic/elasticsearch-serverless/issues/4838">問題 #4838</a> )。</p><ul><li><strong>対面イベントの価値:</strong></li></ul><p>オンライン ハッカソンは素晴らしいですが、ビルダーと肩を並べてデバッグしているときに得られる迅速なフィードバック ループに匹敵するものはありません。私たちは、チームがさまざまなユースケースにわたって Agent Builder を統合する様子を観察し、ES|QL を使用した開発者エクスペリエンスを改善できる部分を見つけ、非同期チャネルで解決するよりもはるかに迅速に問題を修正しました。</p><h2>まとめ</h2><p>Cal Hacks 12.0 では、素晴らしいデモを週末にわたって披露するだけでなく、新しい開発者が Elastic Stack とどのように関わっているかについても理解することができました。わずか 36 時間で、チームは Agent Builder を導入し、Elasticsearch にデータを取り込み、マルチエージェント システムを設計し、さまざまな方法で機能をテストするようになりました。このイベントは、対面イベントがなぜ重要なのかを私たちに思い出させてくれました。迅速なフィードバック ループ、実際の会話、実践的なデバッグにより、現在の開発者のニーズを理解することができました。私たちが学んだことをエンジニアリング チームに還元できることを嬉しく思います。次回のハッカソンでお会いしましょう。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch で A2A プロトコルと MCP を使用して LLM エージェント ニュースルームを作成する: パート II]]></title>
            <description><![CDATA[エージェントのコラボレーションに A2A プロトコルを使用し、Elasticsearch でのツール アクセスに MCP を使用して、特殊なハイブリッド LLM エージェント ニュースルームを構築する方法を説明します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/a2a-protocol-mcp-llm-agent-workflow-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/a2a-protocol-mcp-llm-agent-workflow-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Justin Castilla]]></dc:creator>
            <pubDate>Mon, 24 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f87330e5d4ca961593b3cfb861ca850a4cc34186-1519x1173.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>A2AとMCP：コードの動作</h2><p>これは、記事「Elasticsearch で A2A プロトコルと MCP を使用して LLM エージェント ニュースルームを作成する」の補足記事です。この記事では、同じエージェント内に A2A と MCP の両方のアーキテクチャを実装して、両方のフレームワークの独自のメリットを最大限に活用するメリットについて説明しました。自分でデモを実行したい場合、<a href="https://github.com/justincastilla/elastic-newsroom">リポジトリ</a>が利用可能です。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7196f004089127f83547b2e5dc3f663205cfcdce-1162x1600.png" style="width: 100%;" /><p>ニュースルームのエージェントが A2A と MCP の両方を使用して協力し、ニュース記事を作成する方法を見ていきましょう。エージェントの動作を確認するための付属リポジトリは、<a href="https://github.com/justincastilla/elastic-newsroom">ここに</a>あります。</p><h3>ステップ1：ストーリーの割り当て</h3><p><strong>ニュースチーフ</strong>（クライアントとして行動）がストーリーを割り当てます。</p><pre><code>{
  "message_type": "task_request",
  "sender": "news_chief",
  "receiver": "reporter_agent",
  "payload": {
    "task_id": "story_renewable_energy_2024",
    "assignment": {
      "topic": "Renewable Energy Adoption in Europe",
      "angle": "Policy changes driving solar and wind expansion",
      "target_length": 1200,
      "deadline": "2025-09-30T18:00:00Z"
    }
  }
}</code></pre><h3>ステップ2: 記者が調査を依頼する</h3><p><strong>レポーター エージェントは</strong>背景情報が必要であることを認識し、A2A を介して<strong>リサーチャー エージェント</strong>に委任します。</p><pre><code>{
  "message_type": "task_request",
  "sender": "reporter_agent",
  "receiver": "researcher_agent",
  "payload": {
    "task_id": "research_eu_renewable_2024",
    "parent_task_id": "story_renewable_energy_2024",
    "capability": "fact_gathering",
    "parameters": {
      "queries": [
        "EU renewable energy capacity 2024",
        "Solar installations growth Europe",
        "Wind energy policy changes 2024"
      ],
      "depth": "comprehensive"
    }
  }
}</code></pre><h3>ステップ3: 報告者がアーカイブエージェントに歴史的背景をリクエストする</h3><p><strong>レポーターエージェントは</strong>、歴史的背景が記事の内容を強めることを認識しています。A2A 経由で<strong>アーカイブエージェント</strong>( <a href="https://www.elastic.co/docs/solutions/search/elastic-agent-builder">Elastic の A2A エージェント</a>を搭載) に委任し、ニュースルームの Elasticsearch 搭載記事アーカイブを検索します。</p><pre><code>{
  "message_type": "task_request",
  "sender": "reporter_agent",
  "receiver": "archive_agent",
  "payload": {
    "task_id": "archive_search_renewable_2024",
    "parent_task_id": "story_renewable_energy_2024",
    "capability": "search_archive",
    "parameters": {
      "query": "European renewable energy policy changes and adoption trends over past 5 years",
      "focus_areas": ["solar", "wind", "policy", "Germany", "France"],
      "time_range": "2019-2024",
      "result_count": 10
    }
  }
}</code></pre><h3>ステップ4: アーカイブエージェントはMCPでElastic A2Aエージェントを使用する</h3><p><strong>アーカイブ エージェントは</strong>Elastic の A2A エージェントを使用し、A2A エージェントは MCP を使用して Elasticsearch ツールにアクセスします。これは、A2A がエージェントのコラボレーションを可能にし、MCP がツール アクセスを提供するハイブリッド アーキテクチャを示しています。</p><pre><code># Archive Agent using Elastic A2A Agent
async def search_historical_articles(self, query_params):
    # The Archive Agent sends a request to Elastic's A2A Agent
    elastic_response = await self.a2a_client.send_request(
        agent="elastic_agent",
        capability="search_and_analyze",
        parameters={
            "natural_language_query": query_params["query"],
            "index_pattern": "newsroom-articles-*",
            "filters": {
                "topics": query_params["focus_areas"],
                "date_range": query_params["time_range"]
            },
            "analysis_type": "trend_analysis"
        }
    )
    
    # Elastic's A2A Agent internally uses MCP tools:
    # - platform.core.search (to find relevant articles)
    # - platform.core.generate_esql (to analyze trends)
    # - platform.core.index_explorer (to identify relevant indices)
    
    return elastic_response</code></pre><p><strong>アーカイブエージェントは</strong>Elastic の A2A エージェントから包括的な履歴データを受信し、それをレポーターに返します。</p><pre><code>{
  "message_type": "task_response",
  "sender": "archive_agent",
  "receiver": "reporter_agent",
  "payload": {
    "task_id": "archive_search_renewable_2024",
    "status": "completed",
    "archive_data": {
      "historical_articles": [
        {
          "title": "Germany's Energiewende: Five Years of Solar Growth",
          "published": "2022-06-15",
          "key_points": [
            "Germany added 7 GW annually 2020-2022",
            "Policy subsidies drove 60% of growth"
          ],
          "relevance_score": 0.94
        },
        {
          "title": "France Balances Nuclear and Renewables",
          "published": "2023-03-20",
          "key_points": [
            "France increased renewable target to 40% by 2030",
            "Solar capacity doubled 2021-2023"
          ],
          "relevance_score": 0.89
        }
      ],
      "trend_analysis": {
        "coverage_frequency": "EU renewable stories increased 150% since 2019",
        "emerging_themes": ["policy incentives", "grid modernization", "battery storage"],
        "coverage_gaps": ["Small member states", "offshore wind permitting"]
      },
      "total_articles_found": 47,
      "search_confidence": 0.91
    }
  }
}</code></pre><p>このステップでは、Elastic の A2A エージェントがニュースルームのワークフローにどのように統合されるかを示します。Archive Agent（ニュースルーム固有のエージェント）は、Elastic の A2A Agent（サードパーティの専門家）と連携して、Elasticsearch の強力な検索および分析機能を活用します。Elastic のエージェントは内部的に MCP を使用して Elasticsearch ツールにアクセスし、エージェント調整 (A2A) とツール アクセス (MCP) を明確に分離します。</p><h3>ステップ5: 研究者はMCPサーバーを使用する</h3><p><strong>研究者エージェントは</strong>複数の MCP サーバーにアクセスして情報を収集します。</p><pre><code># Researcher Agent using MCP to access tools
async def gather_facts(self, queries):
    results = []
    
    # Use News API MCP Server
    news_data = await self.mcp_client.invoke_tool(
        server="news_api_mcp",
        tool="search_articles",
        parameters={
            "query": queries[0],
            "date_range": "2024-01-01 to 2024-09-30",
            "sources": ["reuters", "bloomberg", "euractiv"]
        }
    )
    
    # Use Statistics MCP Server
    stats_data = await self.mcp_client.invoke_tool(
        server="eurostat_mcp",
        tool="get_energy_statistics",
        parameters={
            "metrics": ["solar_capacity_mw", "wind_capacity_mw"],
            "countries": ["EU27"],
            "year": 2024
        }
    )
    
    # Use Fact-Checking MCP Server
    verified_facts = await self.mcp_client.invoke_tool(
        server="factcheck_mcp",
        tool="verify_claims",
        parameters={
            "claims": [
                "EU solar capacity increased 40% in 2024",
                "Germany leads in wind installations"
            ]
        }
    )
    
    return self._synthesize_research(news_data, stats_data, verified_facts)</code></pre><h3>ステップ6: 研究者が報告者にデータを返す</h3><p><strong>研究者エージェントは、</strong>包括的な研究を A2A 経由で返送します。</p><pre><code>{
  "message_type": "task_response",
  "sender": "researcher_agent",
  "receiver": "reporter_agent",
  "payload": {
    "task_id": "research_eu_renewable_2024",
    "status": "completed",
    "research_data": {
      "key_facts": [
        {
          "claim": "EU solar capacity reached 260 GW in 2024",
          "source": "Eurostat Q3 2024 Report",
          "confidence": 0.95,
          "verified": true
        },
        {
          "claim": "Germany installed 12 GW of wind capacity in 2024",
          "source": "German Federal Network Agency",
          "confidence": 0.92,
          "verified": true
        }
      ],
      "statistics": {
        "solar_growth_rate": "35%",
        "wind_growth_rate": "28%"
      },
      "sources_count": 15
    }
  }
}</code></pre><h3>ステップ7：記者が記事を書く</h3><p><strong>Reporter Agent は、</strong>調査データと独自の LLM 機能を使用して記事を作成します。書き込み中、Reporter はスタイルとテンプレートに MCP サーバーを使用します。</p><pre><code># Reporter Agent writing with MCP assistance
async def write_article(self, research_data, assignment):
    # Get style guidelines via MCP
    style_guide = await self.mcp_client.get_resource(
        server="newsroom_mcp",
        resource="style://editorial/ap_style_guide"
    )
    
    # Get article template via MCP
    template = await self.mcp_client.get_resource(
        server="newsroom_mcp",
        resource="template://articles/news_story"
    )
    
    # Generate article using LLM + research + style
    draft = await self.llm.generate(
        prompt=f"""
        Write a news article following these guidelines:
        {style_guide}
        
        Using this template:
        {template}
        
        Based on this research:
        {research_data}
        
        Assignment: {assignment}
        """
    )
    
    # Self-evaluate confidence in claims
    confidence_check = await self._evaluate_confidence(draft)
    
    return draft, confidence_check</code></pre><h3>ステップ8：自信が低い場合は再調査を促します</h3><p><strong>レポーター エージェントは</strong>下書きを評価し、1 つの主張の信頼性が低いことを発見しました。<strong>研究者エージェント</strong>に別のリクエストを送信します:</p><pre><code>{
  "message_type": "collaboration_request",
  "sender": "reporter_agent",
  "receiver": "researcher_agent",
  "payload": {
    "request_type": "fact_verification",
    "claims": [
      {
        "text": "France's nuclear phase-down contributed to 15% increase in renewable capacity",
        "context": "Discussing policy drivers for renewable growth",
        "current_confidence": 0.45,
        "required_confidence": 0.80
      }
    ],
    "urgency": "high"
  }
}</code></pre><p><strong>研究者は</strong>ファクトチェックMCPサーバーを使用して主張を検証し、更新された情報を返します。</p><pre><code>{
  "message_type": "collaboration_response",
  "sender": "researcher_agent",
  "receiver": "reporter_agent",
  "payload": {
    "verified_claims": [
      {
        "original_claim": "France's nuclear phase-down contributed to 15% increase...",
        "verified_claim": "France's renewable capacity increased 18% in 2024, partially offsetting reduced nuclear output",
        "confidence": 0.88,
        "corrections": "Percentage was 18%, not 15%; nuclear phase-down is gradual, not primary driver",
        "sources": ["RTE France", "French Energy Ministry Report 2024"]
      }
    ]
  }
}</code></pre><h3>ステップ9: 記者が修正して編集者に提出する</h3><p><strong>記者は</strong>検証された事実を組み込み、完成した原稿を A2A 経由で<strong>編集者エージェント</strong>に送信します。</p><pre><code>{
  "message_type": "task_request",
  "sender": "reporter_agent",
  "receiver": "editor_agent",
  "payload": {
    "task_id": "edit_renewable_story",
    "parent_task_id": "story_renewable_energy_2024",
    "content": {
      "headline": "Europe's Renewable Revolution: Solar and Wind Surge 30% in 2024",
      "body": "[Full article text...]",
      "word_count": 1185,
      "sources": [/* array of sources */]
    },
    "editing_requirements": {
      "check_style": true,
      "check_facts": true,
      "check_seo": true
    }
  }
}</code></pre><h3>ステップ10: MCPツールを使用した編集者のレビュー</h3><p><strong>エディター エージェントは</strong>複数の MCP サーバーを使用して記事をレビューします。</p><pre><code># Editor Agent using MCP for quality checks
async def review_article(self, content):
    # Grammar and style check
    grammar_issues = await self.mcp_client.invoke_tool(
        server="grammarly_mcp",
        tool="check_document",
        parameters={"text": content["body"]}
    )
    
    # SEO optimization check
    seo_analysis = await self.mcp_client.invoke_tool(
        server="seo_mcp",
        tool="analyze_content",
        parameters={
            "headline": content["headline"],
            "body": content["body"],
            "target_keywords": ["renewable energy", "Europe", "solar", "wind"]
        }
    )
    
    # Plagiarism check
    originality = await self.mcp_client.invoke_tool(
        server="plagiarism_mcp",
        tool="check_originality",
        parameters={"text": content["body"]}
    )
    
    # Generate editorial feedback
    feedback = await self._generate_feedback(
        grammar_issues, 
        seo_analysis, 
        originality
    )
    
    return feedback</code></pre><p><strong>編集者は</strong>記事を承認し、送信します。</p><pre><code>{
  "message_type": "task_response",
  "sender": "editor_agent",
  "receiver": "reporter_agent",
  "payload": {
    "status": "approved",
    "quality_score": 9.2,
    "minor_edits": [
      "Changed 'surge' to 'increased' in paragraph 3 for AP style consistency",
      "Added Oxford comma in list of countries"
    ],
    "approved_content": "[Final edited article]"
  }
}</code></pre><h3>ステップ11: パブリッシャーがCI/CD経由でパブリッシュする</h3><p>最後に、<strong>プリンター エージェントは</strong>、CMS および CI/CD パイプラインの MCP サーバーを使用して承認された記事を公開します。</p><pre><code># Publisher Agent publishing via MCP
async def publish_article(self, content, metadata):
    # Upload to CMS via MCP
    cms_result = await self.mcp_client.invoke_tool(
        server="wordpress_mcp",
        tool="create_post",
        parameters={
            "title": content["headline"],
            "body": content["body"],
            "status": "draft",
            "categories": metadata["categories"],
            "tags": metadata["tags"],
            "featured_image_url": metadata["image_url"]
        }
    )
    
    post_id = cms_result["post_id"]
    
    # Trigger CI/CD deployment via MCP
    deploy_result = await self.mcp_client.invoke_tool(
        server="cicd_mcp",
        tool="trigger_deployment",
        parameters={
            "pipeline": "publish_article",
            "environment": "production",
            "post_id": post_id,
            "schedule": "immediate"
        }
    )
    
    # Track analytics
    await self.mcp_client.invoke_tool(
        server="analytics_mcp",
        tool="register_publication",
        parameters={
            "post_id": post_id,
            "publish_time": datetime.now().isoformat(),
            "story_id": metadata["story_id"]
        }
    )
    
    return {
        "status": "published",
        "post_id": post_id,
        "url": f"https://newsroom.example.com/articles/{post_id}",
        "deployment_id": deploy_result["deployment_id"]
    }</code></pre><p><strong>出版社は</strong>A2Aを通じて出版を確認します。</p><pre><code>{
  "message_type": "task_complete",
  "sender": "printer_agent",
  "receiver": "news_chief",
  "payload": {
    "task_id": "story_renewable_energy_2024",
    "status": "published",
    "publication": {
      "url": "https://newsroom.example.com/articles/renewable-europe-2024",
      "published_at": "2025-09-30T17:45:00Z",
      "post_id": "12345"
    },
    "workflow_metrics": {
      "total_time_minutes": 45,
      "agents_involved": ["reporter", "researcher", "archive", "editor", "printer"],
      "iterations": 2,
      "mcp_calls": 12
    }
  }
}</code></pre><p>以下は、上記と同じエージェントを使用した付属のリポジトリ内の A2A ワークフローの完全なシーケンスです。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">#</td><td style="border: 1px solid #ccc; padding: 4px;">から</td><td style="border: 1px solid #ccc; padding: 4px;">に</td><td style="border: 1px solid #ccc; padding: 4px;">アクション</td><td style="border: 1px solid #ccc; padding: 4px;">プロトコル</td><td style="border: 1px solid #ccc; padding: 4px;">説明</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">1</td><td style="border: 1px solid #ccc; padding: 4px;">ユーザー</td><td style="border: 1px solid #ccc; padding: 4px;">ニュースチーフ</td><td style="border: 1px solid #ccc; padding: 4px;">ストーリーの割り当て</td><td style="border: 1px solid #ccc; padding: 4px;">HTTP ポスト</td><td style="border: 1px solid #ccc; padding: 4px;">ユーザーがストーリーのトピックと角度を提出する</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">2</td><td style="border: 1px solid #ccc; padding: 4px;">ニュースチーフ</td><td style="border: 1px solid #ccc; padding: 4px;">内部</td><td style="border: 1px solid #ccc; padding: 4px;">ストーリーを作成する</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">固有のIDを持つストーリーレコードを作成します</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">3</td><td style="border: 1px solid #ccc; padding: 4px;">ニュースチーフ</td><td style="border: 1px solid #ccc; padding: 4px;">記者</td><td style="border: 1px solid #ccc; padding: 4px;">委任の割り当て</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">A2Aプロトコル経由でストーリー割り当てを送信します</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">4</td><td style="border: 1px solid #ccc; padding: 4px;">記者</td><td style="border: 1px solid #ccc; padding: 4px;">内部</td><td style="border: 1px solid #ccc; padding: 4px;">割り当てを受け入れる</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">割り当てを内部に保存する</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">5</td><td style="border: 1px solid #ccc; padding: 4px;">記者</td><td style="border: 1px solid #ccc; padding: 4px;">MCP サーバー</td><td style="border: 1px solid #ccc; padding: 4px;">アウトラインを生成</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">記事のアウトラインと研究の質問を作成します</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">6a</td><td style="border: 1px solid #ccc; padding: 4px;">記者</td><td style="border: 1px solid #ccc; padding: 4px;">研究者</td><td style="border: 1px solid #ccc; padding: 4px;">調査依頼</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">質問を送信します（6bと並行）</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">6b</td><td style="border: 1px solid #ccc; padding: 4px;">記者</td><td style="border: 1px solid #ccc; padding: 4px;">アーキビスト</td><td style="border: 1px solid #ccc; padding: 4px;">アーカイブを検索</td><td style="border: 1px solid #ccc; padding: 4px;">A2A JSONRPC</td><td style="border: 1px solid #ccc; padding: 4px;">歴史的な記事を検索します（6aと並行）</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">7</td><td style="border: 1px solid #ccc; padding: 4px;">研究者</td><td style="border: 1px solid #ccc; padding: 4px;">MCP サーバー</td><td style="border: 1px solid #ccc; padding: 4px;">研究上の質問</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">MCP経由でAnthropicを使用して質問に答えます</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">8</td><td style="border: 1px solid #ccc; padding: 4px;">研究者</td><td style="border: 1px solid #ccc; padding: 4px;">記者</td><td style="border: 1px solid #ccc; padding: 4px;">リターンリサーチ</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">調査の回答を返す</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">9</td><td style="border: 1px solid #ccc; padding: 4px;">アーキビスト</td><td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">検索インデックス</td><td style="border: 1px solid #ccc; padding: 4px;">ES REST API</td><td style="border: 1px solid #ccc; padding: 4px;">news_archiveインデックスをクエリ</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">10</td><td style="border: 1px solid #ccc; padding: 4px;">アーキビスト</td><td style="border: 1px solid #ccc; padding: 4px;">記者</td><td style="border: 1px solid #ccc; padding: 4px;">アーカイブに戻る</td><td style="border: 1px solid #ccc; padding: 4px;">A2A JSONRPC</td><td style="border: 1px solid #ccc; padding: 4px;">過去の検索結果を返します</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">11</td><td style="border: 1px solid #ccc; padding: 4px;">記者</td><td style="border: 1px solid #ccc; padding: 4px;">MCP サーバー</td><td style="border: 1px solid #ccc; padding: 4px;">記事を生成する</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">研究/アーカイブの文脈で記事を作成する</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">12</td><td style="border: 1px solid #ccc; padding: 4px;">記者</td><td style="border: 1px solid #ccc; padding: 4px;">内部</td><td style="border: 1px solid #ccc; padding: 4px;">ストアドラフト</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">下書きを内部に保存</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">13</td><td style="border: 1px solid #ccc; padding: 4px;">記者</td><td style="border: 1px solid #ccc; padding: 4px;">ニュースチーフ</td><td style="border: 1px solid #ccc; padding: 4px;">下書きを送信</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">完成した草稿を提出する</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">14</td><td style="border: 1px solid #ccc; padding: 4px;">ニュースチーフ</td><td style="border: 1px solid #ccc; padding: 4px;">内部</td><td style="border: 1px solid #ccc; padding: 4px;">ストーリーを更新</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">下書きを保存し、ステータスを「draft_submitted」に更新します</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">15</td><td style="border: 1px solid #ccc; padding: 4px;">ニュースチーフ</td><td style="border: 1px solid #ccc; padding: 4px;">エディタ</td><td style="border: 1px solid #ccc; padding: 4px;">レビュー草稿</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">レビューのために編集者に自動ルーティング</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">16</td><td style="border: 1px solid #ccc; padding: 4px;">エディタ</td><td style="border: 1px solid #ccc; padding: 4px;">MCP サーバー</td><td style="border: 1px solid #ccc; padding: 4px;">総説</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">MCP経由でAnthropicを使用してコンテンツを分析します</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">17</td><td style="border: 1px solid #ccc; padding: 4px;">エディタ</td><td style="border: 1px solid #ccc; padding: 4px;">ニュースチーフ</td><td style="border: 1px solid #ccc; padding: 4px;">返品レビュー</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">編集上のフィードバックと提案を送信します</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">18</td><td style="border: 1px solid #ccc; padding: 4px;">ニュースチーフ</td><td style="border: 1px solid #ccc; padding: 4px;">内部</td><td style="border: 1px solid #ccc; padding: 4px;">ストアレビュー</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">編集者のフィードバックを保存</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">19</td><td style="border: 1px solid #ccc; padding: 4px;">ニュースチーフ</td><td style="border: 1px solid #ccc; padding: 4px;">記者</td><td style="border: 1px solid #ccc; padding: 4px;">編集を適用</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">レビューのフィードバックをレポーターに転送する</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">20</td><td style="border: 1px solid #ccc; padding: 4px;">記者</td><td style="border: 1px solid #ccc; padding: 4px;">MCP サーバー</td><td style="border: 1px solid #ccc; padding: 4px;">編集を適用</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">フィードバックに基づいて記事を修正する</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">21</td><td style="border: 1px solid #ccc; padding: 4px;">記者</td><td style="border: 1px solid #ccc; padding: 4px;">内部</td><td style="border: 1px solid #ccc; padding: 4px;">下書きの更新</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">修正を加えて下書きを更新する</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">22</td><td style="border: 1px solid #ccc; padding: 4px;">記者</td><td style="border: 1px solid #ccc; padding: 4px;">ニュースチーフ</td><td style="border: 1px solid #ccc; padding: 4px;">返品修正</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">修正された記事を返す</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">23</td><td style="border: 1px solid #ccc; padding: 4px;">ニュースチーフ</td><td style="border: 1px solid #ccc; padding: 4px;">内部</td><td style="border: 1px solid #ccc; padding: 4px;">ストーリーを更新</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">修正した下書きを保存し、ステータスを「修正済み」にする</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">24</td><td style="border: 1px solid #ccc; padding: 4px;">ニュースチーフ</td><td style="border: 1px solid #ccc; padding: 4px;">出版社</td><td style="border: 1px solid #ccc; padding: 4px;">記事を公開する</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">パブリッシャーへの自動ルーティング</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">25</td><td style="border: 1px solid #ccc; padding: 4px;">出版社</td><td style="border: 1px solid #ccc; padding: 4px;">MCP サーバー</td><td style="border: 1px solid #ccc; padding: 4px;">タグを生成する</td><td style="border: 1px solid #ccc; padding: 4px;">MCP/HTTP</td><td style="border: 1px solid #ccc; padding: 4px;">タグとカテゴリを作成する</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">26</td><td style="border: 1px solid #ccc; padding: 4px;">出版社</td><td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch</td><td style="border: 1px solid #ccc; padding: 4px;">インデックス記事</td><td style="border: 1px solid #ccc; padding: 4px;">ES REST API</td><td style="border: 1px solid #ccc; padding: 4px;">記事をnews_archiveインデックスにインデックスします</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">27</td><td style="border: 1px solid #ccc; padding: 4px;">出版社</td><td style="border: 1px solid #ccc; padding: 4px;">ファイルシステム</td><td style="border: 1px solid #ccc; padding: 4px;">マークダウンを保存</td><td style="border: 1px solid #ccc; padding: 4px;">ファイルI/O</td><td style="border: 1px solid #ccc; padding: 4px;">記事を.mdとして保存します/articles内のファイル</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">28</td><td style="border: 1px solid #ccc; padding: 4px;">出版社</td><td style="border: 1px solid #ccc; padding: 4px;">ニュースチーフ</td><td style="border: 1px solid #ccc; padding: 4px;">公開の確認</td><td style="border: 1px solid #ccc; padding: 4px;">A2A</td><td style="border: 1px solid #ccc; padding: 4px;">成功ステータスを返します</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">29</td><td style="border: 1px solid #ccc; padding: 4px;">ニュースチーフ</td><td style="border: 1px solid #ccc; padding: 4px;">内部</td><td style="border: 1px solid #ccc; padding: 4px;">ストーリーを更新</td><td style="border: 1px solid #ccc; padding: 4px;">-</td><td style="border: 1px solid #ccc; padding: 4px;">ストーリーのステータスを「公開済み」に更新します</td>
                    </tr>
              </tbody>
            </table>
          <h2>まとめ</h2><p>A2A と MCP はどちらも、現代の拡張 LLM インフラストラクチャ パラダイムにおいて重要な役割を果たします。A2A は複雑なマルチエージェント システムに柔軟性を提供しますが、移植性が低くなり、運用が複雑になる可能性があります。MCP は、マルチエージェント オーケストレーションを処理するようには設計されていませんが、実装と保守がより簡単なツール統合のための標準化されたアプローチを提供します。</p><p>選択は二者択一ではありません。私たちのニュースルームの例で示されているように、最も洗練され効果的な LLM 対応システムは、多くの場合、両方のアプローチを組み合わせています。つまり、エージェントは A2A プロトコルを通じて調整と専門化を行いながら、MCP サーバーを通じてツールやリソースにアクセスします。このハイブリッド アーキテクチャは、MCP の標準化とエコシステムの利点に加えて、マルチエージェント システムの組織上の利点も提供します。これは、選択する必要が全くないかもしれないことを示唆している。単に両方を標準的なアプローチとして使うだけでよい。</p><p>開発者またはアーキテクトとして、両方のソリューションの最適な組み合わせをテストして決定し、特定のユースケースに適した結果を生み出すのはあなた次第です。それぞれのアプローチの長所、制限、適切な適用を理解することで、より効果的で保守性と拡張性に優れた AI システムを構築できるようになります。</p><p>デジタル ニュースルーム、顧客サービス プラットフォーム、リサーチ アシスタント、またはその他の LLM を利用したアプリケーションを構築する場合でも、調整ニーズ (A2A) とツール アクセス要件 (MCP) を慎重に検討することで、成功への道が開かれます。</p><h2>参考資料</h2><ul><li><strong>Elasticsearch エージェントビルダー:</strong> <a href="https://www.elastic.co/docs/solutions/search/elastic-agent-builder">https://www.elastic.co/docs/solutions/search/elastic-agent-builder</a></li><li><strong>A2A仕様</strong>: <a href="https://a2a-protocol.org/latest/specification/">https://a2a-protocol.org/latest/specification/</a></li><li><strong>A2A と MCP の統合</strong>: <a href="https://a2a-protocol.org/latest/topics/a2a-and-mcp/">https://a2a-protocol.org/latest/topics/a2a-and-mcp/</a></li><li><strong>モデルコンテキストプロトコル</strong>: <a href="https://modelcontextprotocol.io/">https://modelcontextprotocol.io</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[コンテキストエンジニアリングにおけるハイブリッド検索の威力 - パート3]]></title>
            <description><![CDATA[コンテキスト エンジニアリングとハイブリッド検索を使用して、集計、RBAC、非コンテンツ シグナルによって AI 出力の精度を向上させる方法を説明します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/context-engineering-hybrid-search-agentic-ai-accuracy</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/context-engineering-hybrid-search-agentic-ai-accuracy</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Woody Walton]]></dc:creator>
            <pubDate>Thu, 20 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b82ff25242e4229cc20b218d9cc91c60cfd680bc-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>ハイブリッド検索 (<a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">パート I</a> ) とコンテキスト エンジニアリング (<a href="https://www.elastic.co/search-labs/blog/context-engineering-llm-evolution-agentic-ai">パート II</a> ) の両方について説明しました。次に、RAG およびエージェント AI 操作にターゲットを絞ったコンテキストを提供する上で、これらがどのように連携して最大の効果を発揮するかについて詳しく見ていきましょう。</p><h2>検索は死んでいない、ただ移動しただけだ</h2><p>そのため、主にテキスト ボックスでコンテキストを検索し、返された情報 (コンテキスト) を使用して自分で回答を構築するという方法から、自然言語を使用してエージェントに必要なものを伝え、エージェントが自動的に回答を調査してまとめる方法へと移行しました。テクノロジー業界の多くの人々は、この変化を指摘し、「検索は死んだ」と主張しています（まあ、SEO とアドワーズの世界は<a href="https://www.pewresearch.org/short-reads/2025/07/22/google-users-are-less-likely-to-click-on-links-when-an-ai-summary-appears-in-the-results/">確実に変化しています</a>。GEO<a href="https://www.wired.com/story/goodbye-seo-hello-geo-brandlight-openai/">は</a>どうですか？）。しかし、検索は依然として代理店の業務にとって絶対に不可欠です。ただ、現在では主にツールを介して目に見えない形で実行されているだけです。</p><p>以前は、主観的な関連性の主な判断者は人間でした。各ユーザーには検索を実行する独自の理由があり、個人的な経験が結果の相対的な正確性に影響を与えていました。エージェントが私たちと同じ（あるいはそれ以上の）結論に達することができると信頼するには、エージェントがアクセスできるコンテキスト情報が私たちの主観的な意図に可能な限り近いことを保証する必要があります。私たちはその目標に向けて、LLM に提供するコンテキストを設計する必要があります。</p><h2>ハイブリッド検索によるコンテキストの生成</h2><p>パート I でもう一度お伝えしましたが、Elastic のハイブリッド検索は、従来のキーワードベースの検索の強み (構文の柔軟性、キーワードの精度、関連性のスコアリング) とベクトル類似性検索の意味理解を組み合わせ、複数の再ランキング手法を提供します。この相乗効果（この言葉のより正確な使い方はこれまで見つかりませんでした！）クエリによってコンテンツをターゲットする方法がより細かく指定できるため、関連性の高い結果を得ることができます。主観的関連性を検索段階の<em>1 つ</em>として適用できるというだけでなく、実際には、第 1 段階の検索に関連性スコアリングを他のすべてのモードとともに一度に含めることができるのです。</p><h3>優れた精度と効率</h3><p>分散検索、取得、再ランク付け機能を備えたデータ プラットフォームを主要なコンテキスト検索エンジンとして使用することは、非常に理にかなっています。高度なクエリ構文を使用して、主観的な意図の欠落したコンポーネントを追加し、返されるコンテキスト情報の価値を損なったり不明瞭にしたりする可能性のあるコンテンツを除外できます。利用可能な個々の構文オプションから選択することも、モダリティを単一の検索に組み合わせて、各データの種類を最もよく理解できる方法でターゲットにし、それらを再ランク付けして組み合わせたり並べ替えたりすることもできます。不要なデータを除外し、必要なフィールド/値のみが含まれるように応答をフィルタリングできます。エージェントにとって、このターゲティングの柔軟性により、コンテキストを非常に正確に取得できるツールを構築できます。</p><h3>コンテキストの洗練（集約と非コンテンツシグナル）</h3><p>集約は、ツールがコンテキスト ウィンドウに配信するコンテンツを形成する際に特に役立ちます。集計により、返されるコンテキスト データの形状に関する数値ベースの事実が自然に提供されるため、LLM による推論がより容易かつ正確になります。集計は階層的にネストできるため、LLM に複数レベルの詳細を追加して、より微妙な理解を深めることが簡単にできます。集計はコンテキスト ウィンドウのサイズの管理にも役立ちます。10 万件のドキュメントのクエリ結果を、集約された分析情報の数百トークンに簡単に減らすことができます。</p><p>非コンテンツ シグナルは、データに内在する指標であり、見ているものの全体像を示します。つまり、人気、鮮度、地理的位置、カテゴリ、ホストの多様性、価格帯など、結果の追加特性です。これらの情報は、エージェントが受け取ったコンテキストの重要性をどのように評価するかをエージェントに通知するのに役立ちます。これを最もよく説明するために、いくつかの簡単な例を挙げます。</p><ul><li><strong>最近公開されたコンテンツや人気コンテンツの強化</strong>- 記事のナレッジ ベースがあると想像してください。ユーザーのクエリに関連する記事を見つけたいが、最近の記事であり、他のユーザーに役立つと判断された記事（「いいね」の数が多いなど）を優先したいとします。このシナリオでは、ハイブリッド検索を使用して関連する記事を見つけ、公開日と人気度の組み合わせに基づいて記事を再ランク付けすることができます。</li><li><strong>売上と在庫調整を伴う電子商取引の検索</strong>- 電子商取引の設定では、検索語に一致する製品を顧客に表示したいだけでなく、売れ行きがよく在庫がある製品を宣伝したいとも考えます。顧客の不満を避けるために、在庫が少ない商品のランクを下げることもできます。</li><li><strong>バグ トラッカーで重大度の高い問題を優先する</strong>- ソフトウェア開発チームにとって、問題を検索する際には、重大度が高く、優先度が高く、最近更新された問題を最初に表示することが重要です。「重要度」や「最も議論されている」などの非シグナルを使用して、さまざまな要素を個別に評価し、最も重要で活発に議論されている問題が最上位に表示されるようにすることができます。</li></ul><p>これらのサンプルクエリおよびその他の詳細は、付属の Elasticsearch Labs<a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/you-know-for-context/">コンテンツ ページ</a>にあります。</p><h3>セキュリティ強化</h3><p>コンテキストエンジニアリングに Elastic のような検索を活用したスピードレイヤーを活用する重要な利点は、セキュリティ フレームワークが組み込まれていることです。Elastic のプラットフォームは、きめ細かなロールベースのアクセス制御 (RBAC) と属性ベースのアクセス制御 (ABAC) を通じて、エージェントおよび生成 AI オペレーションに提供されるコンテキストが機密性の高い非公開情報を尊重して保護することを保証します。これは、クエリが効率的に処理されるだけでなく、エージェントまたはリクエストを開始したユーザーの特定の権限に応じて結果がフィルタリングされることを意味します。</p><p>エージェントは認証されたユーザーとして実行されるため、プラットフォームに組み込まれたセキュリティ機能を通じてセキュリティが暗黙的に適用されます。</p><ul><li><strong>きめ細かな権限:</strong>ドキュメント、フィールド、さらには用語レベルでアクセスを定義し、AI エージェントが表示を許可されているデータのみを受信するようにします。</li><li><strong>ロールベースのアクセス制御 (RBAC):</strong>エージェントまたはユーザーにロールを割り当て、定義された責任に基づいて特定のデータセットまたは機能へのアクセスを許可します。</li><li><strong>属性ベースのアクセス制御 (ABAC):</strong>データ、ユーザー、または環境の属性に基づいて動的なアクセス ポリシーを実装し、適応性の高いコンテキスト認識型のセキュリティを実現します。</li><li><strong>ドキュメント レベルのセキュリティ (DLS) とフィールド レベルのセキュリティ (FLS):</strong>これらの機能により、取得したドキュメント内でも許可された部分のみが表示されるようになり、機密情報の漏洩を防止できます。</li><li><strong>エンタープライズ セキュリティとの統合:</strong>既存の ID 管理システム (LDAP、SAML、OIDC など) とシームレスに統合し、組織全体で一貫したセキュリティ ポリシーを適用します。</li></ul><p>これらのセキュリティ対策をコンテキスト取得メカニズムに直接統合することで、Elastic は安全なゲートキーパーとして機能し、AI エージェントが定義されたデータ境界内で動作し、不正なデータ公開を防ぎ、データプライバシー規制へのコンプライアンスを維持できるようにします。これは、機密情報や独自情報を扱うエージェント AI システムへの信頼を構築する上で非常に重要です。</p><p>追加のボーナスとして、エンタープライズ データ ソース上で統合されたデータ スピード レイヤーを使用することで、エージェント ツールによって作成されるリポジトリでの予期しないアドホック クエリ負荷を軽減できます。ほぼリアルタイムであらゆるものを検索できる単一の場所と、セキュリティとガバナンスの制御を適用できる単一の場所が提供されます。</p><h2>ハイブリッド検索ベースのツール</h2><p>Elastic プラットフォームには、コンテキスト エンジニアリングの追求を加速させるコア機能がいくつかあります (<a href="https://www.elastic.co/blog/whats-new-elastic-9-2-0">今後もさらに増える予定</a>です)。ここで重要なのは、このプラットフォームが、AI エコシステムの進化に合わせて方法を適応、変更、拡張できる柔軟性を備え、さまざまな達成方法を提供していることです。</p><h3>エージェントビルダーの紹介</h3><p>Elastic <a href="https://www.elastic.co/elasticsearch/agent-builder">Agent Builder は</a>、Elastic にすでに保存されているデータと対話するために構築されたエージェント AI ツールの領域への最初の進出です。Agent Builder は、ユーザーが Kibana 内で独自のエージェントとツールを作成および管理できるようにするチャット インターフェースを提供します。組み込みの MCP および A2A サーバー、プログラム API、Elasticsearch インデックスのクエリと探索、および自然言語からの ES|QL クエリの生成用の一連の構築済みシステム ツールが付属しています。Agent Builder を使用すると、表現力豊かな<a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a>クエリ構文を通じてエージェントに返されるコンテキスト データをターゲットにして整形するカスタム ツールを作成できます。</p><p>ES|QL はハイブリッド検索をどのように実行するのでしょうか?コア機能は、 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">semantic_text</a>フィールド タイプと<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fork">FORK</a> / <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fuse">FUSE</a>コマンドの組み合わせによって実現されます (FUSE はデフォルトで<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">RRF</a>を使用して各フォークの結果をマージします)。架空の製品検索の簡単な例を次に示します。</p><pre><code>FROM products
| FORK
  (MATCH description "high performance gaming laptop" | EVAL search_type = "bm25"),
  (MATCH description_semantic "high performance gaming laptop" | EVAL search_type = "semantic")
| FUSE 
| LIMIT 20
| KEEP product_name, description, _score, search_type</code></pre><p>上記の例の各 FORK ブランチに含まれる<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval">EVAL</a>句は厳密には必須ではありません。これは、特定の結果がどの検索モダリティから返されたかを追跡する方法を示すためだけに含まれています。</p><h3>検索テンプレート</h3><p>独自の外部エージェントツールを Elastic デプロイメントにポイントするとします。また、ES|QL の代わりに、マルチステージ リトリーバーを使用したり、開発した既存の DSL 構文を再利用したり、クエリが受け入れる入力、検索を実行するために使用される構文、および出力で返されるフィールドを制御できるようにしたいと考えています。<a href="https://www.elastic.co/docs/solutions/search/search-templates">検索テンプレートを</a>使用すると、ユーザーは一般的な検索パターンの定義済み構造を定義できるため、データ取得の効率と一貫性が向上します。これは、定型コードの標準化と検索ロジックの高速な反復処理を可能にするため、検索 API と対話するエージェント ツールにとって特に有益です。そして、これらの要素のいずれかを調整する必要がある場合は、検索テンプレートを更新するだけで、変更が実装されます。エージェントツールで実際に実行される検索テンプレートの例を探している場合は、Elasticsearch Labs のブログ「 <a href="https://www.elastic.co/search-labs/blog/mcp-intelligent-search">MCP for intelligent search</a> 」をご覧ください。このブログでは、外部 MCP サーバーからのツール呼び出しの背後で検索テンプレートが使用されています。</p><h3>統合ワークフロー (最高!)</h3><p>新しいエージェント AI の世界で最も扱いにくいことの 1 つは、半自律型で自己指向的な「推論」エージェントの非決定論的な性質です。コンテキスト エンジニアリングは、エージェント AI にとって非常に重要な分野です。これは、エージェントが生成できる可能性のある結論を、私たちが知っている事実に絞り込むのに役立つ手法です。非常に正確で関連性の高いコンテキスト ウィンドウがあっても、(数値的事実の領域から外れると) エージェントの応答が完全に再現可能で信頼できるという安心感がまだ少し欠けています。</p><p>エージェントに対して同じリクエストを複数回実行すると、応答に わずかな違いがあるだけ <em>で、回答は 基本的に 同じになる可能性があります。</em>これは通常、単純なクエリでは問題なく、ほとんど気づかれない程度で、コンテキスト エンジニアリング手法を使用して出力を調整することができます。しかし、エージェントに要求するタスクが複雑になるにつれて、1 つ以上のサブタスクによって差異が生じ、最終結果がわずかに変わる可能性が高くなります。エージェント間のコミュニケーションにさらに依存するようになると、状況はさらに悪化し、差異が累積していくでしょう。これは、エージェントが対話するツールは、コンテキスト データを正確にターゲットにするために非常に柔軟かつ調整可能である必要があり、予期される出力形式で応答する必要があるという考えを再び示しています。また、多くのユースケースでは、エージェントとツールのやり取りを誘導する必要があることも示しています。ここでワークフローが登場します。</p><p>Elastic ではまもなく、プラットフォームの中核に完全にカスタマイズ可能なワークフローが組み込まれる予定です。これらのワークフローはエージェントやツールと双方向に操作できるため、ワークフローはエージェントやツールを呼び出すことができ、エージェントやツールはワークフローを呼び出すことができます。これらの機能が、すべてのデータが存在する同じ検索 AI プラットフォームに完全に統合されることで、ワークフローの可能性は大きく変化します。もうすぐ、もうすぐ登場です！</p><h3>統合メモリバンクとしてのElastic</h3><p>Elastic は、ほぼリアルタイムの検索向けに作られた分散データ プラットフォームであるため、エージェント AI システムの長期メモリ機能を自然に実行します。組み込みの Agent Builder チャット エクスペリエンスにより、短期記憶とチャット履歴の追跡と管理も行えます。また、プラットフォーム全体が API ファーストであるため、エージェントのコンテキスト ウィンドウを圧倒する可能性のあるツールのコンテキスト出力を永続化するためのプラットフォームとして Elastic を利用する（そして後で参照できるようにする）ことは非常に簡単です。この手法は、コンテキスト エンジニアリングの分野では「<a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents#:~:text=Agents%20can%20assemble%20understanding%20layer%20by%20layer%2C%20maintaining%20only%20what%27s%20necessary%20in%20working%20memory%20and%20leveraging%20note%2Dtaking%20strategies%20for%20additional%20persistence">メモを取る</a>」と呼ばれることもあります。</p><p>同じ検索プラットフォームに短期記憶と長期記憶の両方を持つことで、多くの本質的なメリットが生まれます。チャット履歴と永続的なコンテキスト応答を、将来のチャットのやり取りに対する意味的影響要因の一部として使用したり、脅威分析を実行したり、頻繁に繰り返されるツール呼び出しから自動的に生成される永続的なデータ製品を作成したりできるようになることを想像してみてください。可能性は無限です。</p><h2>まとめ</h2><p>大規模言語モデルの出現により、コンテンツを一致させる方法や、データを調査するために使用する手法が変化しました。私たちは、人間が自らの疑問に答えるために調査、状況の考慮、論理的推論を行う現在の世界から、それらのステップがエージェント AI によって大部分が自動化される世界へと急速に移行しつつあります。生成された回答を信頼するには、エージェントが応答を生成する際に<em> 最も関連性の高い</em> 情報（主観的関連性の要素を含む） をすべて 考慮したという保証が必要です。エージェント AI を信頼できるものにするための主な方法は、RAG とコンテキスト エンジニアリング技術を通じて追加のコンテキストを取得するツールを基盤化することですが、それらのツールが<em>最初の取得を</em>どのように実行するかが応答の精度に非常に重要になる場合があります。</p><p>Elastic Search AI プラットフォームは、ハイブリッド検索の柔軟性と利点に加えて、エージェント AI の精度、パフォーマンス、スケーラビリティを向上させるいくつかの組み込み機能を提供します。つまり、Elastic はコンテキスト エンジニアリングのさまざまな側面に対応する素晴らしいプラットフォームを実現します。検索プラットフォームを介したコンテキスト検索の標準化により、エージェントツールの操作がいくつかの面で簡素化されます。「速度を落としてスピードを上げる」という矛盾した表現と同様に、コンテキスト生成レイヤーの簡素化は、より高速で信頼性の高いエージェントAIを意味します。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[コンテキストのためのYou Know - パートII：エージェントAIとコンテキストエンジニアリングの必要性]]></title>
            <description><![CDATA[LLM がエージェント AI へと進化するにつれ、RAG コンテキストの制限とメモリ管理を解決するためのコンテキスト エンジニアリングの必要性がどのように高まるのかを学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/context-engineering-llm-evolution-agentic-ai</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/context-engineering-llm-evolution-agentic-ai</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Woody Walton]]></dc:creator>
            <pubDate>Tue, 18 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6153ce399e5498841ef2ffa5363a3e2d2dea726d-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>LLM が情報検索の基本的なプロセスをどのように変えてきたかについての (かなり広範囲にわたる)<a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">背景</a>を踏まえて、LLM がデータのクエリ方法をどのように変えてきたかを見てみましょう。</p><h2>データと対話する新しい方法</h2><p>ジェネレーティブ (genAI) AI とエージェント AI は、従来の検索とは異なる処理を行います。かつて私たちが情報を調べ始める方法は検索（「グーグルで検索してみます…」）でしたが、gen AI とエージェントの両方にとって、開始アクションは通常、チャット インターフェースに入力された自然言語を通じて行われます。チャット インターフェースは、意味理解を使用して質問を簡潔な回答、つまりあらゆる種類の情報に関する幅広い知識を持つ予言者から出されたような要約された応答に変換する LLM とのディスカッションです。本当に売れているのは、LLM が表面化した知識の断片をつなぎ合わせて首尾一貫した思慮深い文章を生成する能力です。たとえそれが不正確であったり完全に幻覚的であったりしても、そこには<a href="https://en.wikipedia.org/wiki/Truthiness">真実味</a>があります。</p><p>私たちが使い慣れている古い検索バーは<strong><em>、私たち自身が</em></strong>推論エージェントであったときに使用した RAG エンジンと考えることができます。現在では、インターネット検索エンジンでさえ、使い古された「ハント・アンド・ペック」という語彙検索エクスペリエンスを、クエリに対する結果の要約で答える AI 主導の概要へと変えつつあり、ユーザーがクリックして個々の結果を自分で評価する必要がないようにしています。</p><h2>生成AIとRAG</h2><p>生成 AI は、世界の意味理解を活用してチャット リクエストを通じて表明された主観的な意図を解析し、推論能力を使用して専門的な回答を即座に作成します。生成 AI インタラクションにはいくつかの部分があります。ユーザーの入力/クエリから始まり、チャット セッションでの以前の会話が追加のコンテキストとして使用でき、LLM に推論方法と応答の構築手順を指示する指示プロンプトがあります。プロンプトは、「5 歳児に説明するように説明してください」という単純なタイプのガイダンスから、リクエストを処理する方法の完全な詳細へと進化しました。これらの内訳には、AI のペルソナ/役割、生成前の推論/内部思考プロセス、客観的な基準、制約、出力形式、対象者、および期待される結果を示すのに役立つ例の詳細を説明する個別のセクションが含まれることがよくあります。</p><p>ユーザーのクエリとシステム プロンプトに加えて、検索拡張生成 (RAG) は、「コンテキスト ウィンドウ」と呼ばれる追加のコンテキスト情報を提供します。RAG はアーキテクチャへの重要な追加機能であり、世界の意味理解において欠落している部分を LLM に通知するために使用します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5b9671d5d07d4caefde372bb3188000754a91eed-1470x746.png" style="width: 100%;" /><p>コンテキスト ウィンドウは、何を、どこに、どれだけ与えるかという点では、かなり<a href="https://www.dbreunig.com/2025/06/22/how-contexts-fail-and-how-to-fix-them.html">細かい指定が</a>必要になる場合があります。もちろん、どのコンテキストが選択されるかは非常に重要ですが、提供されたコンテキストの信号対雑音比やウィンドウの長さも重要です。</p><h3>情報が少なすぎる</h3><p>クエリ、プロンプト、またはコンテキスト ウィンドウに提供される情報が少なすぎると、LLM が応答を生成するための正しいセマンティック コンテキストを正確に判断できないため、幻覚が発生する可能性があります。また、ドキュメント チャンク サイズのベクトル類似性にも問題があります。つまり、短くて単純な質問は、ベクトル化された知識ベースにある豊富で詳細なドキュメントと意味的に一致しない可能性があります。<a href="https://medium.com/data-science/how-to-use-hyde-for-better-llm-rag-retrieval-a0aa5d0e23e8">Hypothetical Document Embeddings (HyDE)</a>などのクエリ拡張手法が開発され、LLM を使用して、短いクエリよりも豊富で表現力豊かな仮説的な回答を生成します。もちろん、ここでの危険は、仮説文書自体が LLM を正しい文脈からさらに逸脱させる幻覚であるということです。</p><h3>情報が多すぎる</h3><p>私たち人間と同じように、コンテキスト ウィンドウに情報が多すぎると、LLM は重要な部分が何であるのかについて混乱し、圧倒されてしまう可能性があります。コンテキスト オーバーフロー (または「<a href="https://research.trychroma.com/context-rot">コンテキスト ロット</a>」) は、生成 AI 操作の品質とパフォーマンスに影響します。LLM の「注意予算」(作業メモリ) に大きな影響を与え、競合する多くのトークン間の関連性を薄めます。「コンテキスト腐敗」の概念には、LLM が<a href="https://alexandrabarr.beehiiv.com/p/context-windows">位置の偏り</a>を持つ傾向があるという観察も含まれます。つまり、LLM はコンテキスト ウィンドウの中央セクションのコンテンツよりも、コンテキスト ウィンドウの先頭または末尾のコンテンツを優先します。</p><h3>気が散ったり矛盾したりする情報</h3><p>コンテキスト ウィンドウが大きくなるほど、LLM が正しいコンテキストを選択して処理する妨げとなる余分な情報や矛盾した情報が含まれる可能性が高くなります。ある意味、これは「ガベージ イン/ガベージ アウト」の問題になります。つまり、ドキュメント結果セットをコンテキスト ウィンドウにダンプするだけで、LLM に処理すべき大量の情報が提供されます (多すぎる可能性があります)。ただし、コンテキストの選択方法によっては、矛盾した情報や無関係な情報が入り込む可能性が高くなります。</p><h2>エージェント型AI</h2><p>カバーすべき内容がたくさんあると言いましたが、ついにエージェント AI のトピックについて話すことができました。エージェント AI は、LLM チャット インターフェイスの非常にエキサイティングな新しい使用法であり、独自の知識とユーザーが提供するコンテキスト情報に基づいて応答を合成する生成 AI (すでに「レガシー」と呼んでもいいでしょうか?) の機能を拡張します。生成 AI が成熟するにつれて、当初は人間が簡単に確認/検証できる、面倒でリスクの低いアクティビティに限定されていた、一定レベルのタスク処理と自動化を LLM に実行させることができることに気付きました。短期間で、当初のスコープは拡大しました。LLM チャット ウィンドウは、AI エージェントが自律的に計画、実行し、指定された目標を達成するためにその計画を反復的に評価および適応させるきっかけとなることができるようになりました。エージェントは、LLM 自身の推論、チャット履歴、思考メモリ (現状のまま) にアクセスでき、その目標達成に向けて活用できる特定のツールも利用できます。また、トップレベルのエージェントが、それぞれ独自のロジック チェーン、命令セット、コンテキスト、ツールを持つ複数の<a href="https://www.philschmid.de/the-rise-of-subagents">サブエージェント</a>のオーケストレーターとして機能することを可能にするアーキテクチャも登場しています。</p><p>エージェントは、ほぼ自動化されたワークフローへのエントリ ポイントです。エージェントは自己主導型であり、ユーザーとチャットしてから「ロジック」を使用して、ユーザーの質問に答えるために使用できるツールを決定します。ツールは通常、エージェントに比べて受動的であると考えられており、1 種類のタスクを実行するために構築されています。ツールが実行できるタスクの<em>種類</em>はほぼ無限です (これは本当に素晴らしいことです!) が、ツールが実行する主なタスクは、エージェントがワークフローを実行する際に考慮するコンテキスト情報を収集することです。</p><p>技術としては、エージェント AI はまだ初期段階にあり、注意欠陥障害に相当する LLM になりがちです。つまり、指示されたことをすぐに忘れてしまい、指示にまったく含まれていない他の作業に走り出してしまうことがよくあります。一見魔法のように見えますが、LLM の「推論」機能は、シーケンス内で次に最も可能性の高いトークンを予測することに基づいています。推論（あるいは将来的には、汎用人工知能（AGI））が信頼できるものになるためには、正確で最新の情報が与えられたときに、私たちが期待する通りに推論してくれるか（そしておそらく、私たち自身では考えつかなかったようなちょっとした追加情報を提供してくれるか）を検証できなければなりません。これを実現するには、エージェント アーキテクチャに、明確に通信する機能 (プロトコル)、指定されたワークフローと制約を順守する機能 (ガードレール)、タスク内の位置を記憶する機能 (状態)、使用可能なメモリ領域を管理する機能、応答が正確でありタスクの基準を満たしていることを検証する機能が必要になります。</p><h2>私に理解できる言語で話してください</h2><p>新しい開発分野ではよくあることですが (特に LLM の世界ではそうです)、当初はエージェントとツール間の通信にはかなり多くのアプローチがありましたが、すぐに<a href="https://modelcontextprotocol.io/docs/getting-started/intro">モデル コンテキスト プロトコル (MCP) が</a>事実上の標準として採用されました。モデル コンテキスト プロトコルの定義はまさにその名前の通りで、<strong> モデルが コンテキスト</strong> 情報を要求および受信するために使用する<strong> プロトコル</strong> です。MCP は、LLM エージェントが外部ツールやデータ ソースに接続するためのユニバーサル アダプタとして機能し、さまざまな LLM フレームワークやツールが簡単に相互運用できるように API を簡素化および標準化します。そのため、MCP は、エージェントが目的を達成するために自律的に実行するために与えられるオーケストレーション ロジックとシステム プロンプトと、より分離された形式 (少なくとも開始エージェントに関しては分離された形式) で実行するためにツールに送信される操作との間の、一種のピボット ポイントになります。</p><p>このエコシステムは非常に新しいため、あらゆる方向への拡大が新たなフロンティアのように感じられます。エージェント間のインタラクション（もちろん<a href="https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/">Agent2Agent (A2A)</a> ）用の類似プロトコルのほか、エージェントの推論メモリを改善するプロジェクト（ <a href="https://venturebeat.com/ai/new-memory-framework-builds-ai-agents-that-can-handle-the-real-worlds">ReasoningBank</a> ）、手元のジョブに最適な MCP サーバーを選択するプロジェクト（ <a href="https://arxiv.org/abs/2505.03275">RAG-MCP</a> ）、ゼロショット分類や入力と出力のパターン検出などのセマンティック分析を<a href="https://openai.github.io/openai-guardrails-python/">ガードレール</a>として使用してエージェントが操作できる内容を制御するプロジェクトもあります。</p><p>これらの各プロジェクトの根本的な目的は、エージェント/genAI コンテキスト ウィンドウに返される情報の品質と制御を向上させることであることにお気づきでしょうか。エージェント AI エコシステムは、コンテキスト情報をより適切に処理する (制御、管理、操作する) 能力の開発を継続していますが、エージェントが処理するための<em>最も関連性の高い</em>コンテキスト情報を取得する必要性は常に存在します。</p><h2>コンテキストエンジニアリングへようこそ!</h2><p>生成 AI の用語に詳しい方なら、おそらく「プロンプト エンジニアリング」という言葉を聞いたことがあるでしょう。現時点では、プロンプト エンジニアリングはそれ自体がほぼ疑似科学となっています。プロンプト エンジニアリングは、LLM が応答を生成する際に使用する動作を積極的に記述するための最良かつ最も効率的な方法を見つけるために使用されます。「<a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">コンテキスト エンジニアリング</a>」は、「プロンプト エンジニアリング」の手法をエージェント側を超えて拡張し、MCP プロトコルのツール側で利用可能なコンテキスト ソースとシステムもカバーし、コンテキストの管理、処理、生成という幅広いトピックを扱います。</p><ul><li><strong>コンテキスト管理</strong>- 長時間実行される、またはより複雑なエージェント ワークフロー全体で状態とコンテキストの効率を維持することに関連します。エージェントの目標を達成するために、タスクとツールの呼び出しを繰り返し計画、追跡、オーケストレーションします。エージェントが動作しなければならない「注意予算」は限られているため、コンテキスト管理は主に、コンテキスト ウィンドウを絞り込んでコンテキストの最大限の範囲と最も重要な部分 (精度と再現率) の両方をキャプチャするのに役立つ手法に関係しています。技術には、圧縮、要約、前のステップまたはツール呼び出しからのコンテキストを永続化して、後続のステップで追加のコンテキストのために作業メモリ内にスペースを確保することが含まれます。</li><li><strong>コンテキスト処理</strong>- エージェントがすべてのコンテキストをある程度統一された方法で推論できるように、異なるソースから取得したコンテキストを統合、正規化、または調整するための論理的かつできればほとんどプログラム的な手順。基本的な作業は、すべてのソース (プロンプト、RAG、メモリなど) からのコンテキストを、エージェントが可能な限り効率的に使用できるようにすることです。 </li><li><strong>コンテキスト生成</strong>- コンテキスト処理が、取得したコンテキストをエージェントが使用できるようにすることであるならば、コンテキスト生成は、追加のコンテキスト情報を自由に、しかし制約付きで要求して受け取るための範囲をエージェントに提供します。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4a8240e1eb078b2294b8d981b9caa8593589cac4-1600x900.png" style="width: 100%;" /><p>LLM チャット アプリケーションのさまざまな一時的な機能は、コンテキスト エンジニアリングの高レベル機能に直接 (場合によっては重複して) マッピングされます。</p><ul><li><strong>指示 / システム プロンプト</strong>- プロンプトは、生成的 (またはエージェント的) AI アクティビティがユーザーの目標を達成するためにどのように思考を導くかを示す足場です。プロンプトはそれ自体がコンテキストです。単なる音声による指示ではなく、回答がユーザーの要求に完全に応えているかどうかを確認するために、応答する前に「段階的に考える」や「深呼吸する」などのタスク実行ロジックやルールも含まれることがよくあります。最近のテストでは、マークアップ言語はプロンプトのさまざまな部分を組み立てるのに非常に効果的であることが示されていますが、指示を曖昧になりすぎず、具体的になりすぎないように注意して調整する必要があります。LLM が適切なコンテキストを見つけるのに十分な指示を与える必要がありますが、予期しない洞察を見逃すほど規範的であってはなりません。</li><li><strong>短期記憶</strong>(状態/履歴) - 短期記憶は、基本的にユーザーと LLM 間のチャット セッションのやり取りです。これらはライブ セッションのコンテキストを絞り込むのに役立ち、将来の取得や続行のために保存できます。 </li><li><strong>長期記憶</strong>- 長期記憶は、複数のセッションにわたって役立つ情報で構成されている必要があります。また、RAG を通じてアクセスされるのはドメイン固有の知識ベースだけではありません。最近の研究では、以前のエージェント/生成 AI 要求の結果を使用して、現在のエージェントのやり取り内で学習および参照を行っています。長期記憶領域における最も興味深い革新のいくつかは、エージェントが中断したところから再開できるように、状態がどのように<a href="https://steve-yegge.medium.com/introducing-beads-a-coding-agent-memory-system-637d7d92514a">保存され、リンクされるかを</a>調整することに関係しています。 </li><li><strong>構造化された出力</strong>- 認知には努力が必要なので、推論能力があっても、LLM が (人間と同じように) 考えるときにあまり努力を費やしたくないのは当然です。また、定義された API やプロトコルがない場合、ツール呼び出しから返されたデータを読み取る方法のマップ (スキーマ) を持つことは非常に役立ちます。<a href="https://platform.openai.com/docs/guides/structured-outputs?lang=javascript">構造化出力</a>をエージェント フレームワークの一部として組み込むと、思考主導の解析の必要性が減り、マシン間のやり取りがより高速かつ信頼性が高くなるようになります。</li><li><strong>利用可能なツール</strong>- ツールは、追加情報の収集 (エンタープライズ データ リポジトリへの RAG クエリの発行、またはオンライン API 経由の RAG クエリの発行など) から、エージェントに代わって自動アクションを実行すること (エージェントからのリクエストの基準に基づいてホテルの部屋を予約するなど) まで、さまざまな処理を実行できます。ツールは、独自のエージェント処理チェーンを持つサブエージェントになることもできます。 </li><li><strong>検索拡張生成 (RAG)</strong> - RAG の「動的な知識統合」という説明がとても気に入っています。前述のように、RAG は LLM がトレーニング時にアクセスできなかった追加情報を提供するための手法であり、主観的なクエリに最も関連性の高い正しい答えを得るために最も重要だと考えられるアイデアを繰り返し述べたものです。</li></ul><h2>驚異的な宇宙のパワー、小さな居住空間！</h2><p>エージェント AI には、探索すべき魅力的でエキサイティングな新しい領域が数多くあります。解決すべき従来のデータ検索および処理の問題はまだたくさんありますが、LLM の新時代に初めて日の目を見るようになったまったく新しい種類の課題もあります。私たちが現在取り組んでいる差し迫った問題の多くは、コンテキスト エンジニアリング、つまり、LLM の限られた作業メモリ空間を圧迫することなく、必要な追加のコンテキスト情報を取得することに関係しています。</p><p>さまざまなツール (および他のエージェント) にアクセスできる半自律エージェントの柔軟性により、AI を実装するための非常に多くの新しいアイデアが生まれ、さまざまな方法でそれらを組み合わせることができるのかを推測するのは困難です。現在の研究のほとんどはコンテキスト エンジニアリングの分野に属し、大量のコンテキストを処理および追跡できるメモリ管理構造の構築に重点を置いています。これは、LLM に解決してほしい深い思考の問題には、記憶することが極めて重要となる、複雑さが増し、実行時間が長く、多段階の思考ステップが含まれるためです。</p><p>この分野で現在行われている多くの実験では、エージェントの口を満たすための最適なタスク管理とツール構成を見つけようとしています。エージェントの推論チェーンにおける各ツール呼び出しは、そのツールの機能を実行するための計算と、制限されたコンテキスト ウィンドウへの影響の両方の点で累積的なコストを発生させます。LLM<a href="https://venturebeat.com/ai/ace-prevents-context-collapse-with-evolving-playbooks-for-self-improving-ai"> </a>エージェントのコンテキストを管理する最新の技術の一部は、長時間実行されるタスクの蓄積されたコンテキストを圧縮/要約すると損失が<em> 大きくなりすぎる 「</em> コンテキストの崩壊 」などの意図しない連鎖効果を引き起こしています。望ましい結果は、貴重なコンテキスト ウィンドウのメモリ領域に余分な情報が漏れることなく、簡潔で正確なコンテキストを返すツールです。</p><h3>可能性が多すぎる</h3><p>私たちはツール/コンポーネントを再利用するための柔軟性を備えた職務の分離を望んでいるため、特定のデータ ソースに接続するための専用のエージェント ツールを作成することは完全に理にかなっています。各ツールは、1 つのタイプのリポジトリ、1 つのタイプのデータ ストリーム、または 1 つのユース ケースのクエリに特化できます。しかし、注意してください。時間や費用を節約し、何かが可能であると証明しようとすると、LLM をフェデレーション ツールとして使用する強い誘惑に駆られるでしょう... やめてください。私たちは以前にも<a href="https://www.elastic.co/pdf/elastic-distributed-not-federated-search.pdf">その道を歩ん</a>だことがあります。フェデレーション クエリは、受信したクエリをリモート リポジトリが理解できる構文に変換する「ユニバーサル トランスレータ」のように機能し、その後、複数のソースからの結果を何らかの方法で合理化して一貫した応答を生成する必要があります。技術としてのフェデレーションは小規模では <em>問題なく 機能します が、大規模で、特にデータがマルチモーダルである場合、フェデレーションは大きすぎるギャップを埋めようとします。</em></p><p>エージェントの世界では、エージェントがフェデレーターとなり、ツール (MCP 経由) がさまざまなリソースへの手動で定義された接続となります。専用のツールを使用して接続されていないデータ ソースにアクセスすることは、クエリごとにさまざまなデータ ストリームを動的に統合する強力な新しい方法のように思えるかもしれませんが、ツールを使用して複数のソースに同じ質問をすると、解決するよりも多くの問題が発生する可能性があります。これらのデータ ソースはそれぞれ、その下にある異なるタイプのリポジトリである可能性があり、それぞれが内部のデータを取得、ランク付け、保護するための独自の機能を備えています。もちろん、リポジトリ間のこうした差異、つまり「インピーダンスの不一致」により、処理負荷が増加します。また、矛盾する情報やシグナルが生じる可能性があり、スコアの不一致のように一見無害に見えるものでも、返されたコンテキストの重要性が大きく損なわれ、最終的に生成された応答の関連性に影響する可能性があります。</p><h3>コンテキストスイッチはコンピュータにとっても難しい</h3><p>エージェントを任務に送り出す場合、多くの場合、最初の任務はエージェントがアクセスできるすべての関連データを見つけることです。人間の場合と同様に、エージェントが接続する各データ ソースが類似していない分散した応答を返すと、取得したコンテンツから重要なコンテキスト ビットを抽出することに関連する認知負荷 (まったく同じ種類ではありませんが) が発生します。これには時間と計算がかかり、エージェントのロジック チェーンでは少しずつ蓄積されていきます。このことから、 <a href="https://blog.cloudflare.com/code-mode/">MCP</a>について議論されているように、ほとんどのエージェント ツールは、API (既知の入力と出力を持つ分離された関数で、さまざまな種類のエージェントのニーズをサポートするように調整された) のように動作する必要があるという結論に至ります。実際、 <a href="https://arxiv.org/html/2501.12372v5">LLM にはコンテキストのためのコンテキストが必要である</a>ことにも気づき始めています。特に、自然言語を構造化構文に翻訳するようなタスクでは、参照できるスキーマがあれば、LLM は意味の点と点を結びつけるのがはるかに上手です (まさに RTFM!)。</p><h2>7回裏ストレッチ！</h2><p>ここでは、 <a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">LLM がデータの取得とクエリに与えた影響</a>と、チャット ウィンドウがエージェント AI エクスペリエンスへとどのように成熟しているかについて説明しました。これら 2 つのトピックを組み合わせて、最新の検索機能と取得機能を使用してコンテキスト エンジニアリングの結果を改善する方法を見てみましょう。<a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-agentic-ai-accuracy">パート III へ進みます: コンテキスト エンジニアリングにおけるハイブリッド検索の威力</a>!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Azure AKS Automatic に Elasticsearch をデプロイする方法]]></title>
            <description><![CDATA[部分的に管理された Elasticsearch セットアップ構成のために、AKS Automatic と ECK を使用して Azure に Kibana とともに Elasticsearch をデプロイする方法を学習します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-azure-aks-automatic-deployment</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-azure-aks-automatic-deployment</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Eduard Martin]]></dc:creator>
            <pubDate>Fri, 14 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4d821659a606e04390b09215e9a0d32eb01f0d1b-854x489.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>この記事は、さまざまなインフラストラクチャを使用して Elasticsearch をインストールする方法を説明するシリーズの一部です。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0a65ca8b62fd8a42d7751b8f4bf057e33d877304-940x458.png" style="width: 100%;" /><p>ECK はマーケットプレイスベースの Elastic Cloud ソリューションよりも大幅に多くの労力を必要としますが、Kubernetes オペレーターがシステム オーケストレーションとノードのスケーリングを処理するため、VM を自分でデプロイするよりも自動化されています。</p><p>今回は、Automatic を使用して Azure Kubernetes Service (AKS) を操作します。他の記事では、 <a href="https://www.elastic.co/search-labs/blog/azure-elasticsearch-vm-deployment">Azure VM</a>と<a href="https://www.elastic.co/search-labs/blog/deploy-elasticsearch-azure-marketplace">Azure Marketplace の</a>使用方法について学習します。</p><h2>AKS Automatic とは何ですか?</h2><p><a href="https://learn.microsoft.com/en-us/azure/aks/intro-aks-automatic">Azure Kubernetes Service (AKS) は、クラスターのセットアップを自動的に</a>管理し、リソースを動的に割り当て、Kubernetes の柔軟性を維持しながらセキュリティのベスト プラクティスを統合するため、開発者はコンテナー イメージからデプロイされたアプリケーションまでを数分以内に実行できます。</p><p>AKS Automatic は、クラスター管理のオーバーヘッドの大部分を排除し、シンプルさと柔軟性のバランスを適切にとります。適切な選択はユースケースによって異なりますが、次のことを計画すると決定が容易になります。</p><ul><li><strong>テスト環境をデプロイする:</strong>デプロイは高速かつ簡単なので、簡単な実験や短期間のクラスターに最適です。</li><li><strong>厳密な VM、ストレージ、またはネットワーク要件なしで作業:</strong> AKS Automatic では定義済みのデフォルトが提供されるため、それがニーズに合えば、追加の構成を行う必要がなくなります。</li><li><strong>Kubernetes を初めて使用する場合:</strong> AKS Automatic はクラスターのセットアップの大部分を処理するため、学習曲線が短縮され、チームはアプリケーションに集中できるようになります。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2e4c09b8c5e0ce5e8ea9c369626a373b7030a5ba-854x489.png" style="width: 100%;" /><p>Elasticsearch では、Elastic Stack の Kubernetes デプロイメント オーケストレーションを簡素化する公式 Elastic Kubernetes オペレーターである<a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s">Elastic Cloud on Kubernetes</a> (ECK) を使用します。</p><h2>AKS Automaticの設定方法</h2><p>1. <a href="https://azure.microsoft.com/">Microsoft Azure ポータル</a>にログインします。</p><p>2.<strong>右上の Cloud Shell</strong>ボタンをクリックし、コンソールにアクセスしてそこから AKS クラスターをデプロイします。あるいは、 <a href="https://learn.microsoft.com/en-us/azure/cloud-shell/overview">Azure Cloud Shell を</a>使用することもできます。</p><p><strong><em>チュートリアル中にプロジェクト ID を自分のものに更新することを忘れないでください。</em></strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0aa60605777c0a6e3aef8faa4e54388c2cb582c8-624x495.png" style="width: 100%;" /><p><em>AKS を開くと、上のスクリーンショットのようになります。</em></p><p>3. aks-preview Azure CLI 拡張機能をインストールします。このプレビュー バージョンでは、クラスターの作成時に<code>--sku automatic</code>を選択できるようになり、AKS Automatic 機能が有効になります。</p><pre><code>az extension add --name aks-preview</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2e05ab67fc554c5fb5208683c179fdeaeadd95db-624x56.png" style="width: 100%;" /><p><em>このメッセージが表示された場合、AKS 拡張機能が正しくインストールされたことを意味します。</em></p><p>4. <code>az feature register</code>コマンドを使用して<a href="https://learn.microsoft.com/en-us/azure/azure-app-configuration/concept-feature-management">機能フラグ</a>を登録する</p><pre><code>az feature register --namespace Microsoft.ContainerService --name AutomaticSKUPreview</code></pre><p><em>作成した機能サブスクリプションの詳細が表示されます。</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3aca710c1f312ba91de461638e518386919ec722-801x138.png" style="width: 100%;" /><p>登録ステータスが「<strong><em>登録中</em></strong>」から「<strong><em>登録済み</em></strong>」に変わるまで確認します。登録が完了するまでに数分かかる場合があります。</p><pre><code>az feature show --namespace Microsoft.ContainerService --name AutomaticSKUPreview</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/186486b08ab8e1c372efaff50f10cbddeaf4e0cd-844x177.png" style="width: 100%;" /><p>変更を伝播するには<code>az provider register</code>を実行します。</p><pre><code>az provider register --namespace Microsoft.ContainerService</code></pre><p>5. リソースグループを作成する</p><p>リソース グループは、管理およびデプロイされる Azure リソースの論理グループです。</p><pre><code>az group create --name elastic-resource --location eastus</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d1952269e97d94f914020754bd02702f9eafd037-770x212.png" style="width: 100%;" /><p>6. Autopilot クラスターを作成します。これを<strong><em>myAKSAutomaticCluster に</em></strong>名前を付け、先ほど作成したリソース グループを使用します。AKS がリソースを割り当てるには、<a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dpsv5-series"> Standard_D4pds_v5</a> 、<a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dldsv5-series"> Standard_D4lds_v5</a> 、<a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dadsv5-series"> Standard_D4ads_v5</a> 、<a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/ddsv5-series"> Standard_D4ds_v5</a> 、 <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/ddv5-series">Standard_D4d_v5</a><a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/ddv4-series"> 、 Standard_D4d_v4</a> 、<a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dsv3-series"> Standard_DS3_v2</a> 、<a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/memory-optimized/dv2-dsv2-series-memory"> Standard_DS12_v2</a> のいずれかの VM サイズで<strong><em> 16 個の vCPU が使用可能であることを確認してください。</em></strong></p><pre><code>az aks create \
    --resource-group elastic-resource \
    --name myAKSAutomaticCluster \
    --sku automatic \
    --generate-ssh-keys</code></pre><p><em>*</em> <em><code>MissingSubscriptionRegistration</code>エラーが発生した場合は、不足しているサブスクリプションを使用して手順4に戻ってください。たとえば、</em> <em><code>The subscription is not registered to use namespace &#x27;</code></em> <strong><em><code>microsoft.insights</code></em></strong> <em><code>&#x27;</code>実行中のサブスクリプションが必要です。<code>az provider register --namespace Microsoft.Insights.</code></em></p><p>対話型ログインに従ってください:</p><p><em>「az login」の実行を求めるメッセージが表示されます。そのコマンドを実行してから待つ必要があります。</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/14c0238f755fe6347519e69d3cb28c0fa52ec044-775x203.png" style="width: 100%;" /><p>7. 準備ができるまで待ちます。作成には約10分かかります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/eb00c3ad18f884f47db6645b196808ebec07c1fc-797x177.png" style="width: 100%;" /><p>8. kubectl コマンドライン アクセスを構成します。</p><pre><code>az aks get-credentials --resource-group elastic-resource --name myAKSAutomaticCluster</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5136d72a5d455345b0b6205bb232c4bdf7762998-793x52.png" style="width: 100%;" /><p><em>インストールした拡張機能によって AKS Automatic が有効になっていることに注意してください。</em></p><p>9. ノードがデプロイされたことを確認します。</p><pre><code>kubectl get nodes</code></pre><p>禁止エラー メッセージが表示されます。エラーからユーザー ID をコピーします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d6c021fa54f4db00d2d795f5ba9b5a93376d03cd-818x47.png" style="width: 100%;" /><p>10. ユーザーを AKS アクセス制御に追加します。</p><p>AKS ID を取得します。コマンドからの出力をコピーします。</p><pre><code>az aks show --resource-group elastic-resource  --name myAKSAutomaticCluster --query id --output tsv</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/22a1cdc538bd379812a752c6a368a0651000abb8-810x36.png" style="width: 100%;" /><p>AKS ID とユーザーのプリンシパル ID を使用してロールの割り当てを作成します。</p><pre><code>az role assignment create --role "Azure Kubernetes Service RBAC Cluster Admin" --assignee <PRINCIPAL_ID> --scope <AKS_ID></code></pre><p>11. ノードが再度デプロイされたことを確認します。</p><pre><code>kubectl get nodes</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c9b3a5c1cc540ef732c3e7f60b0a973bdbd0b6fd-617x99.png" style="width: 100%;" /><p>12. Kubernetes (ECK) オペレーターに Elastic Cloud をインストールします。</p><pre><code># Install ECK Custom Resource Definitions
kubectl create -f https://download.elastic.co/downloads/eck/2.16.1/crds.yaml

# Install the ECK operator
kubectl apply -f https://download.elastic.co/downloads/eck/2.16.1/operator.yaml</code></pre><p>13. デフォルト値を使用して、単一ノードの Elasticsearch インスタンスを作成しましょう。</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 9.0.0
  nodeSets:
  - name: default
    count: 1
    config:
      node.store.allow_mmap: false
EOF</code></pre><p>デフォルトの AKS マシンの<code>vm.max_map_count</code>値が低すぎるため、 <code>nmap</code>を無効にしました。本番環境では無効にすることは推奨されませんが、 <code>vm.max_map_count</code>の値を増やすことは推奨されます。これを行う方法の詳細については、<a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/virtual-memory">ここを</a>参照してください。</p><p>14. Kibana シングルノード クラスターもデプロイしましょう。Kibana の場合は、ロード バランサーを追加します。これにより、デバイスから Kibana にアクセスするために使用できる外部 IP が提供されます。</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: quickstart
spec:
  version: 9.0.0
  http:
    service:
      spec:
        type: LoadBalancer
  count: 1
  elasticsearchRef:
    name: quickstart
EOF</code></pre><p>デフォルトでは、AKS Automatic はロード バランサーをパブリックとして構成します。メタデータ アノテーションを設定することで動作を変更できます。</p><p><code>service.beta.kubernetes.io/azure-load-balancer-internal: &quot;true&quot;</code></p><p>15. ポッドが実行中であることを確認します。</p><pre><code>kubectl get pods</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/213a897176c0af6cea19c7c777cfaf8734e3ee6e-616x84.png" style="width: 100%;" /><p>16.Elasticsearch のバージョン、ノード、健全性などのより具体的な統計情報を取得するには、 <code>kubectl get elasticsearch</code>と<code>kubectl get kibana</code>を実行することもできます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c84988e725ef892eddd8fb7e5a03d58c35a8f9d6-470x62.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/452dd03d314cd00c8a3c19e19862b968592a0435-415x62.png" style="width: 100%;" /><p>17. サービスにアクセスします。</p><pre><code>kubectl get svc</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b2d3e8f368be22b89aa2ed4d4d514f97dd6cbabd-624x115.png" style="width: 100%;" /><p>これにより、EXTERNAL-IP の下に Kibana の<a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/accessing-services">外部 URL</a>が表示されます。ロードバランサーのプロビジョニングには数分かかる場合があります。<strong><em>EXTERNAL-IP の値をコピーします。</em></strong></p><p>18. &#x27;elastic&#x27; ユーザーの Elasticsearch パスワードを取得します。</p><pre><code>kubectl get secret quickstart-es-elastic-user -o=jsonpath='{.data.elastic}' | base64 --decode</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bd74537f8c35c4e027c518913fdb0a0524621d56-624x31.png" style="width: 100%;" /><p>19. ブラウザから<strong>Kibana にアクセスします</strong>。</p><p>a. URL: https://&lt;EXTERNAL_IP&gt;:5601</p><p>b. ユーザー名:elastic</p><p>c. パスワード:c44A295CaEt44D6xIzN6Zs5m (前の手順から)</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a601112527d80721b292328ed8da58386d2837eb-463x503.png" style="width: 100%;" /><p>20.ブラウザから Elastic Cloud にアクセスすると、ようこそ画面が表示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f91e71fa961d215a8d886601d1a9fc5c452ce329-1999x1256.png" style="width: 100%;" /><p>ノードの変更やサイズ変更など、Elasticsearch クラスターの仕様を変更する場合は、新しい設定で YML マニフェストを再度適用できます。</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 9.0.0
  nodeSets:
    - name: default
      count: 2
      config:
        node.store.allow_mmap: false
      podTemplate:
        spec:
          containers:
            - name: elasticsearch
              resources:
                requests:
                  memory: 1.5Gi
                  cpu: 2
                limits:
                  memory: 1.5Gi
                  cpu: 2
EOF</code></pre><p>この例では、さらに 1 つのノードを追加し、RAM と CPU を変更します。ご覧のとおり、 <code>kubectl get elasticsearch</code>には 2 つのノードが表示されています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4b139a476b50933d45d99e09479112817964f76a-624x60.png" style="width: 100%;" /><p>Kibana にも同じことが当てはまります。</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: quickstart
spec:
  version: 9.0.0
  http:
    service:
      spec:
        type: LoadBalancer
  count: 1
  elasticsearchRef:
    name: quickstart
  podTemplate:
    spec:
      containers:
        - name: kibana
          env:
            - name: NODE_OPTIONS
              value: "--max-old-space-size=1024"
          resources:
            requests:
              memory: 0.5Gi
              cpu: 0.5
            limits:
              memory: 1Gi
              cpu: 1
EOF</code></pre><p>コンテナのCPU/RAMと<a href="https://nodejs.org/">Node.jsの</a>メモリ使用量（ <a href="https://nodejs.org/api/cli.html#--max-old-space-sizesize-in-mib">max-old-space-size</a> ）を調整できます。</p><p><a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/volume-claim-templates">既存のボリュームクレームを縮小することはできない</a>ことに留意してください。アップデートを適用した後、オペレーターは最小限の中断時間で変更を加えます。</p><p>不要なコストを避けるために、テストが完了したらクラスターを忘れずに削除してください。</p><pre><code>az aks delete --name myAKSAutomaticCluster --resource-group elastic-resource</code></pre><h2>まとめ</h2><p>Azure AKS Automatic を ECK と併用すると、Elasticsearch と Kibana をデプロイするためのバランスの取れたソリューションが提供されます。これにより、運用の複雑さが軽減され、スケーリングと更新が自動化され、Kubernetes の柔軟性が活用されます。このアプローチは、インフラストラクチャの詳細をすべて手動で管理することなく、信頼性が高く、繰り返し可能で、保守可能なデプロイメント プロセスを求めるチームに最適であり、テスト環境と本番環境の両方にとって実用的な選択肢となります。</p><h2>今後の見通し</h2><p>Kubernetes について詳しく知りたい場合は、次の公式ドキュメントをご覧ください。</p><ul><li><a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s">Kubernetes 上の Elastic Cloud | Elastic ドキュメント</a></li><li><a href="https://learn.microsoft.com/en-us/azure/aks/intro-aks-automatic">Azure Kubernetes Service (AKS) Automatic (プレビュー) の概要</a></li><li><a href="https://azure.github.io/AKS/2024/05/22/aks-automatic">AKS Automatic - AKSエンジニアリングブログ</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch で A2A プロトコルと MCP を使用して LLM エージェント ニュースルームを作成する: パート I]]></title>
            <description><![CDATA[専門の LLM エージェントが協力してニュース記事の調査、執筆、編集、公開を行う実践的なニュースルームの例で、A2A プロトコルと MCP の概念を探ります。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/a2a-protocol-mcp-llm-agent-newsroom-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/a2a-protocol-mcp-llm-agent-newsroom-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Justin Castilla]]></dc:creator>
            <pubDate>Thu, 13 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9f938d8e2f0fdf7509edf028816c48bdbc8b3fc7-1600x900.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>はじめに</h2><p>現在の LLM 対応システムは、単一モデルのアプリケーションから、専門のエージェントが連携して、現代のコンピューティングではこれまで不可能と思われていたタスクを達成する複雑なネットワークへと急速に進化しています。これらのシステムの複雑さが増すにつれて、エージェントの通信とツールへのアクセスを可能にするインフラストラクチャが開発の主な焦点になります。これらのニーズに対応するために、マルチエージェント調整用の<strong>Agent2Agent (A2A)</strong>プロトコルと、標準化されたツールおよびリソース アクセス用の<strong>Model Context Protocol (MCP) という</strong>2 つの補完的なアプローチが登場しました。</p><p>それぞれの機能をいつ、またいつ単独で、調和して使用するかを理解することは、アプリケーションのスケーラビリティ、保守性、および有効性に大きな影響を与える可能性があります。この記事では、専門の LLM エージェントが協力してニュース記事の調査、執筆、編集、公開を行うデジタル ニュースルームの実際の例を通して、 <strong>A2A</strong>の概念と実装について説明します。</p><p>付属のリポジトリは<a href="https://github.com/justincastilla/elastic-newsroom/tree/main">ここに</a>あります。セクション 5 の最後の方で、A2A の実際の動作の具体的な例を検討します。</p><h3>要件</h3><p><a href="https://github.com/justincastilla/elastic-newsroom/tree/main">リポジトリは</a>、A2A エージェントの Python ベースの実装で構成されています。Flask には API サーバーが用意されているほか、ログ記録や UI 更新のメッセージをルーティングする Event Hub というカスタム Python メッセージング サービスも用意されています。最後に、ニュースルームの機能をスタンドアロンで使用するための React UI が提供されます。実装を容易にするために、すべてが Docker イメージ内に含まれています。マシンで直接サービスを実行する場合は、次のテクノロジがインストールされていることを確認してください。</p><p>言語とランタイム</p><ul><li>Python 13.12 - コアバックエンド言語</li><li>Node.js 18+ - オプションのReact UI</li></ul><p>コアフレームワークと SDK:</p><ul><li>A2A SDK 0.3.8 - エージェントの調整と通信</li><li>Anthropic SDK - AI生成のためのClaude統合</li><li>Uvicorn - エージェントを実行するためのASGIサーバー</li><li>FastMCP 2.12.5+ - MCP サーバーの実装</li><li>React 18.2 - フロントエンドUIフレームワーク</li></ul><p>データと検索</p><ul><li>Elasticsearch 9.1.1 以上- 記事のインデックス作成と検索</li></ul><p>Docker のデプロイメント (オプションですが推奨)</p><ul><li>Docker 28.5.1 以上</li></ul><h2>セクション 1: Agent2Agent (A2A) とは何ですか?</h2><h3>定義とコアコンセプト</h3><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><p><strong>公式仕様</strong>: <a href="https://a2a-protocol.org/latest/specification/">https://a2a-protocol.org/latest/specification/</a></p><h3>起源と進化</h3><p>エージェント間通信、つまりマルチエージェント システムの概念は、<a href="https://en.wikipedia.org/wiki/Multi-agent_system">数十年</a>前に遡る分散システム、マイクロサービス、およびマルチエージェントの研究に根ざしています。分散型人工知能の初期の研究は、交渉、調整、共同作業ができるエージェントの基盤を築きました。これらの初期のシステムは、大規模な<a href="https://www.jasss.org/5/1/7.html">社会シミュレーション</a>、<a href="https://arxiv.org/html/2410.09403v1">学術研究</a>、<a href="https://www.researchgate.net/publication/334765661_Generation_Expansion_Planning_Considering_Investment_Dynamic_of_Market_Participants_Using_Multi-agent_System">電力網管理</a>に特化していました。</p><p>LLM が利用可能になり、運用コストが削減されたことで、Google や AI 研究コミュニティ全体の支援を受けて、マルチエージェント システムが「プロシューマー」市場で利用可能になりました。現在 Agent2Agent システムとして知られている A2A プロトコルの追加により、複数の大規模言語モデルが取り組みとタスクを調整する時代に合わせて特別に設計された最新の標準へと進化しました。</p><p>A2A プロトコルは、LLM が接続して通信するインタラクション ポイントに一貫した標準と原則を適用することで、エージェント間のシームレスな通信と調整を保証します。この標準化により、異なる開発者のエージェントが、異なる基盤モデルを使用して、効果的に連携できるようになります。</p><p>通信プロトコルは新しいものではなく、インターネット上で行われるほぼすべてのデジタル取引に広く定着しています。<a href="https://www.elastic.co/search-labs">https://www.elastic.co/search-labs</a>と入力した場合この記事にアクセスするためにブラウザにログインすると、TCP/IP、HTTP トランスポート、DNS ルックアップ プロトコルがすべて実行され、一貫したブラウジング エクスペリエンスが保証される可能性が高くなります。</p><h3>主な特徴</h3><p>A2A システムは、スムーズな通信を確保するためにいくつかの基本原則に基づいて構築されています。これらの原則に基づいて構築することで、異なる LLM、フレームワーク、プログラミング言語に基づくさまざまなエージェントがすべてシームレスに対話できるようになります。</p><p>主な原則は次の 4 つです。</p><ul><li><strong>メッセージパッシング</strong>: エージェントは、明確に定義されたプロパティとフォーマットを持つ構造化されたメッセージを通じて通信します。</li><li><strong>調整</strong>: エージェントは、他のエージェントをブロックすることなく、タスクを互いに委任し、依存関係を管理することで、複雑なワークフローを調整します。</li><li><strong>専門分野</strong>: 各エージェントは特定のドメインまたは機能に焦点を合わせ、その分野の専門家となり、そのスキルセットに基づいてタスクの完了を提供します。</li><li><strong>分散状態</strong>: 状態と知識は集中化されるのではなくエージェント間に分散され、エージェントはタスクの状態と部分的な戻り値(成果物)の進捗状況を相互に更新する機能を持ちます。</li></ul><h3>ニュースルーム：実例</h3><p>ジャーナリズムのさまざまな側面に特化した AI エージェントによって駆動されるデジタル ニュースルームを想像してみてください。</p><ul><li><strong>ニュースチーフ</strong>（コーディネーター/クライアント）：ストーリーを割り当て、ワークフローを監督する</li><li><strong>記者エージェント</strong>：調査やインタビューに基づいて記事を書く</li><li><strong>研究エージェント</strong>: 事実、統計、背景情報を収集します</li><li><strong>アーカイブエージェント</strong>: Elasticsearchを使用して過去の記事を検索し、傾向を特定します</li><li><strong>エディターエージェント</strong>: 記事の品質、スタイル、SEO最適化をレビューします</li><li><strong>パブリッシャーエージェント</strong>: 承認された記事をCI/CD経由でブログプラットフォームに公開します。</li></ul><p>これらのエージェントは単独では機能しません。ニュースチーフが<em>再生可能エネルギーの導入</em>についての記事を割り当てる場合、記者は統計を収集する研究者、草稿を確認する編集者、そして最終記事を公開する発行者を必要とします。この調整は A2A プロトコルを通じて行われます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cc0760dbd74c49b92fa00dafbb8c2e8740eb70b6-963x693.png" style="width: 100%;" /><h2>セクション2: A2Aアーキテクチャの理解</h2><h3>クライアントエージェントとリモートエージェントの役割</h3><p>A2A アーキテクチャでは、エージェントは主に 2 つの役割を担います。<strong>クライアント エージェントは</strong>、タスクを策定し、システム内の他のエージェントに伝達する役割を担います。リモート エージェントとその機能を識別し、この情報を使用してタスクの委任について十分な情報に基づいた決定を下します。クライアント エージェントはワークフロー全体を調整し、タスクが適切に分散され、システムが目標に向かって進行することを保証します。</p><p>対照的に、<strong>リモート エージェントは</strong>、クライアントによって委任されたタスクを実行します。リクエストに応じて情報を提供したり特定のアクションを実行したりしますが、独自にアクションを開始することはありません。リモート エージェントは、割り当てられた責任を果たすために必要に応じて他のリモート エージェントと通信し、特殊な機能の共同ネットワークを作成することもできます。</p><p>私たちのニュースルームでは、ニュースチーフがクライアントエージェントとして機能し、レポーター、リサーチャー、エディター、パブリッシャーはリクエストに応答し、互いに調整するリモートエージェントとして機能します。</p><h3>コアA2A機能</h3><p>A2A プロトコルは、マルチエージェントのコラボレーションを可能にするいくつかの機能を定義します。</p><h4>1. 発見</h4><p>A2A サーバーは、クライアントが特定のタスクにいつどのようにサーバーを利用できるかがわかるように、その機能をアナウンスする必要があります。これは、エージェントの能力、入力、出力を記述する JSON ドキュメントであるエージェント カードを通じて実現されます。エージェント カードは、一貫性のあるよく知られたエンドポイント (推奨される<code>/.well-known/agent-card.json</code>エンドポイントなど) で利用できるようになり、クライアントはコラボレーションを開始する前にエージェントの機能を検出して照会できるようになります。</p><p>以下は、Elastic のカスタム アーカイブ エージェント「Archie Archivist」のエージェント カードの例です。Elastic などのソフトウェア プロバイダーは A2A エージェントをホストし、アクセス用の URL を提供していることに注意してください。</p><pre><code>{
  "name": "Archie Archivist",
  "description": "Helps find historical news documents in the Elasticsearch Index of archived news articles and content.",
  "url": "https://xxxxxxxxxxxxx-abc123.kb.us-central1.gcp.elastic.cloud/api/agent_builder/a2a/archive-agent",
  "provider": {
    "organization": "Elastic",
    "url": "https://elastic.co"
  },
  "version": "0.1.0",
  "protocolVersion": "0.3.0",
  "preferred_transport": "JSONRPC",
  "documentationURL": "https://www.elastic.co/docs/solutions/search/agent-builder/a2a-server"
  "capabilities": {
    "streaming": false,
    "pushNotifications": false,
    "stateTransitionHistory": false
  },
  "skills": [
    {
      "id": "platform.core.search",
      "name": "platform.core.search",
      "description": "A powerful tool for searching and analyzing data within your Elasticsearch cluster.",
      "inputModes": ["text/plain", "application/json"],
      "outputModes": ["text/plain", "application/json"]
    },
    {
      "id": "platform.core.index_explorer",
      "name": "platform.core.index_explorer",
      "description": "List relevant indices, aliases and datastreams based on a natural language query.",
      "inputModes": ["text/plain", "application/json"],
      "outputModes": ["text/plain", "application/json"]
    }
  ],
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"]
}</code></pre><p>このエージェント カードでは、Elastic のアーカイブ エージェントのいくつかの重要な側面について説明します。エージェントは自身を「Archie Archivist」と名乗り、Elasticsearch インデックス内の過去のニュース文書の検索を支援するという目的を明確に述べています。カードはプロバイダー (Elastic) とプロトコル バージョン (0.3.0) を指定し、他の A2A 準拠エージェントとの互換性を確保します。最も重要なのは、 <code>skills</code>配列が、強力な検索機能やインテリジェントなインデックス探索など、このエージェントが提供する特定の機能を列挙していることです。各スキルはサポートする入力モードと出力モードを定義し、クライアントがこのエージェントと通信する方法を正確に理解できるようにします。このエージェントは Elastic の Agent Builder サービスから派生したもので、データ ストアからデータを取得するだけでなく、データ ストアと対話するためのネイティブ LLM 対応ツールと API エンドポイントのスイートを提供します。Elasticsearch の A2A エージェントへのアクセスについては、<a href="https://www.elastic.co/docs/solutions/search/agent-builder/a2a-server">こちらを</a>ご覧ください。</p><h4>2. 交渉</h4><p>クライアントとエージェントは、適切なユーザー インタラクションとデータ交換を確保するために、コミュニケーション方法 (インタラクションがテキスト、フォーム、iframe、またはオーディオ/ビデオを介して行われるかどうか) について合意する必要があります。このネゴシエーションはエージェントのコラボレーションの開始時に行われ、ワークフロー全体にわたるエージェントの相互作用を管理するプロトコルを確立します。たとえば、音声ベースのカスタマー サービス エージェントはオーディオ ストリーム経由での通信をネゴシエートする可能性がありますが、データ分析エージェントは構造化された JSON を好む可能性があります。交渉プロセスにより、両当事者がそれぞれの能力と現在のタスクの要件に適した形式で情報を効果的に交換できるようになります。</p><p>上記の JSON スニペットにリストされている機能にはすべて入力スキーマと出力スキーマがあり、これらによって、他のエージェントからこのエージェントと対話する方法の期待値が設定されます。</p><h4>3. タスクと状態の管理</h4><p>クライアントとエージェントには、タスク実行全体を通じてタスクのステータス、変更、依存関係を通信するためのメカニズムが必要です。これには、タスクの作成と割り当てから進捗状況の更新とステータスの変更までのタスクのライフサイクル全体の管理が含まれます。一般的なステータスには、保留中、進行中、完了、失敗などの状態が含まれます。また、システムは、依存タスクが開始する前に前提条件となる作業が完了していることを確認するために、タスク間の依存関係を追跡する必要があります。エラー処理と再試行ロジックも重要なコンポーネントであり、システムが障害から正常に回復し、主な目標に向かって前進し続けることを可能にします。</p><p>タスクメッセージの例:</p><pre><code>{
  "message_id": "msg_789xyz",
  "message_type": "task_request",
  "sender": "news_chief",
  "receiver": "researcher_agent",
  "timestamp": "2025-09-30T10:15:00Z",
  "payload": {
    "task_id": "task_456abc",
    "capability": "fact_gathering",
    "parameters": {
      "query": "renewable energy adoption rates in Europe 2024",
      "sources": ["eurostat", "iea", "ember"],
      "depth": "comprehensive"
    },
    "context": {
      "story_id": "story_123",
      "deadline": "2025-09-30T18:00:00Z",
      "priority": "high"
    }
  }
}</code></pre><p>このサンプル タスク メッセージは、A2A 通信のいくつかの重要な側面を示しています。</p><ul><li><strong>メッセージ</strong>構造には、一意のメッセージ識別子、送信されるメッセージの種類、送信者と受信者の識別、追跡およびデバッグ用のタイムスタンプなどのメタデータが含まれます。</li><li><strong>ペイロードには</strong>実際のタスク情報が含まれており、リモート エージェントで呼び出される機能を指定し、その機能を実行するために必要なパラメータを提供します。</li><li><strong>コンテキスト</strong>セクションでは、受信側エージェントが広範なワークフローを理解するのに役立つ追加情報が提供されます。これには、エージェントがリソースを割り当てて作業をスケジュールする方法を示す期限や優先度レベルなどが含まれます。</li></ul><h4>4. コラボレーション</h4><p>クライアントとエージェントは、動的かつ構造化されたインタラクションをサポートし、エージェントがクライアント、他のエージェント、またはユーザーに説明、情報、またはサブアクションを要求できるようにする<strong>必要があります</strong>。これにより、エージェントが最初の指示が曖昧な場合にフォローアップの質問をしたり、より適切な決定を下すために追加のコンテキストを要求したり、より適切な専門知識を持つ他のエージェントにサブタスクを委任したり、完全なタスクに進む前にフィードバック用の中間結果を提供したりできる共同作業環境が作成されます。この多方向のコミュニケーションにより、エージェントは孤立して作業するのではなく、継続的な対話に参加してより良い結果を得ることができます。</p><h3>分散型ピアツーピア通信</h3><p>A2A は、エージェントが異なる組織によってホストされ、一部のエージェントが社内で管理され、他のエージェントがサードパーティのサービスによって提供される分散通信を可能にします。これらのエージェントは、複数のクラウド プロバイダーまたはオンプレミスのデータ センターにまたがる可能性のある、さまざまなインフラストラクチャで実行できます。エージェントによっては、GPT モデルを活用したエージェント、Claude を活用したエージェント、オープンソースの代替手段を活用したエージェントなど、基盤となる LLM が異なる場合があります。エージェントは、データ主権の要件に準拠したり、待ち時間を削減したりするために、異なる地理的領域にまたがって動作する場合もあります。この多様性にもかかわらず、すべてのエージェントは情報を交換するための共通の通信プロトコルに同意し、実装の詳細に関係なく相互運用性を保証します。この分散アーキテクチャにより、システムの構築と展開に柔軟性が提供され、組織は特定のニーズに合わせて最適なエージェントとインフラストラクチャを組み合わせることができます。</p><p>これはニュースルーム アプリケーションの最終的なアーキテクチャです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/82e01a0d9746038eafd69d11177042b5390507ae-1600x838.png" style="width: 100%;" /><h2>セクション3: モデルコンテキストプロトコル (MCP)</h2><h3>定義と目的</h3><p>モデル コンテキスト プロトコル (MCP) は、Anthropic によって開発された標準化されたプロトコルであり、ユーザー定義のツール、リソース、プロンプト、その他の補足的なコードベースの追加機能を使用して個々の LLM を強化および強化します。MCP は、言語モデルと、タスクを効果的に完了するために必要な外部リソースとの間のユニバーサル インターフェイスを提供します。この<a href="https://www.elastic.co/search-labs/blog/mcp-current-state">記事では</a>、ユースケース、新たなトレンド、Elastic 独自の実装の例を挙げて、MCP の現状を概説します。</p><h3>MCPのコアコンセプト</h3><p>MCP は、次の 3 つの主要コンポーネントを持つクライアント サーバー アーキテクチャで動作します。</p><ul><li><strong>クライアント:</strong> MCP サーバーに接続してその機能にアクセスするアプリケーション (Claude Desktop やカスタム AI アプリケーションなど)。</li><li><strong>サーバー</strong>: 言語モデルにリソース、ツール、プロンプトを公開するアプリケーション。各サーバーは、特定の機能またはデータ ソースへのアクセスを提供することに特化しています。<ul><li><strong>ツール</strong>: モデルがデータベースの検索、外部APIの呼び出し、データに対する変換の実行などのアクションを実行するために呼び出すことができるユーザー定義関数</li><li><strong>リソース:</strong>モデルが読み取り可能なデータ ソース。動的または静的データが提供され、URI パターン (REST ルートに類似) 経由でアクセスされます。</li><li><strong>プロンプト:</strong>特定のタスクを実行するためにモデルをガイドする変数を含む再利用可能なプロンプト テンプレート。</li></ul></li></ul><h3>リクエスト・レスポンスパターン</h3><p>MCP は、REST API に似た、使い慣れた要求と応答の相互作用パターンに従います。クライアント (LLM) がリソースを要求するかツールを呼び出すと、MCP サーバーが要求を処理して結果を返します。LLM はこれを使用してタスクを続行します。周辺サーバーを備えたこの集中型モデルは、ピアツーピアのエージェント通信に比べて、よりシンプルな統合パターンを提供します。</p><h3>ニュースルームのMCP</h3><p>私たちのニュースルームの例では、個々のエージェントが MCP サーバーを使用して必要なツールとデータにアクセスします。</p><ul><li><strong>研究者エージェントは</strong>以下を使用します:<ul><li>ニュース API MCP サーバー (ニュース データベースへのアクセス)</li><li>ファクトチェックMCPサーバー（信頼できる情報源との照合による主張の検証）</li><li>学術データベース MCP サーバー (学術論文と研究)</li></ul></li><li><strong>レポーターエージェントは</strong>以下を使用します:<ul><li>スタイルガイド MCP サーバー (ニュースルームの執筆基準)</li><li>テンプレート MCP サーバー (記事テンプレートとフォーマット)</li><li>画像ライブラリ MCP サーバー (ストック写真とグラフィック)</li></ul></li><li><strong>エディターエージェントは</strong>以下を使用します:<ul><li>文法チェッカーMCPサーバー（言語品質ツール）</li><li>盗作検出MCPサーバー（独創性検証）</li><li>SEO分析MCPサーバー（見出しとキーワードの最適化）</li></ul></li><li><strong>Publisher Agent は</strong>以下を使用します:<ul><li>CMS MCP サーバー (コンテンツ管理システム API)</li><li>CI/CD MCP サーバー (デプロイメント パイプライン)</li><li>Analytics MCP サーバー (追跡と監視)</li></ul></li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b67311e3b58b27f9eb1b42a7b1dbad47ef3be4ad-808x535.png" style="width: 100%;" /><h2><br/>セクション4: アーキテクチャの比較</h2><h3>A2Aを使用する場合</h3><p>A2A アーキテクチャは<strong>、真のマルチエージェントコラボレーションを必要とするシナリオ</strong>に優れています。調整を必要とする複数ステップのワークフローでは、特にタスクに複数の順次または並列ステップが含まれる場合、反復と改良が必要なワークフロー、およびチェックポイントと検証のニーズがあるプロセスの場合に、A2A から大きなメリットが得られます。私たちのニュースルームの例では、ストーリーのワークフローでは記者が記事を書く必要がありますが、特定の事実に対する信頼性が低い場合は研究者に繰り返し報告し、その後編集者に進み、最終的に発行者に渡す必要がある場合があります。</p><p><strong>複数の領域にわたるドメイン固有の特化</strong>は、A2A のもう 1 つの強力な使用例です。より大きなタスクを達成するためにさまざまな分野の複数の専門家が必要であり、各エージェントがさまざまな側面に関する深いドメイン知識と専門的な推論機能を提供する場合、A2A はそれらの接続を行うために必要な調整フレームワークを提供します。ニュースルームはこれを完璧に例証しています。リサーチャーは情報収集、レポーターは執筆、編集者は品質管理を専門としており、それぞれが異なる専門知識を持っています。</p><p>自律的なエージェントの動作の必要性により、A2A は特に価値が高まります。<strong>独立した意思決定を行い、変化する状況に基づいて積極的な行動を示し、ワークフロー要件に動的に適応できる</strong>エージェントは、A2A アーキテクチャで成功します。特化された機能の水平スケーリングも重要な利点の 1 つです。単一の万能エージェントではなく、複数の特化エージェントが連携して動作し、同じエージェントの複数のインスタンスがサブタスクを非同期的に処理できます。たとえば、ニュースルームでニュース速報を取材しているとき、複数の記者エージェントが同時に同じニュースのさまざまな角度から取材することがあります。</p><p>最後に、真のマルチエージェントコラボレーションを必要とするタスクは A2A に最適です。これには<a href="https://arxiv.org/abs/2404.18796">、陪審員としての LLM 評価</a>メカニズム、合意形成および投票システム、および最善の結果に到達するために<strong>複数の視点が必要となる共同問題解決が</strong>含まれます。</p><h3>MCPを使用する場合</h3><p>モデル コンテキスト プロトコルは、単一の AI モデルの機能を拡張する場合に最適です。単一の AI モデルが複数のツールやデータ ソースにアクセスする必要がある場合、MCP は、集中型の推論と分散ツール、および簡単なツール統合を組み合わせた完璧なソリューションを提供します。私たちのニュースルームの例では、研究者エージェント (1 つのモデル) は、ニュース API、ファクトチェック サービス、学術データベースなど、標準化された MCP サーバーを介してアクセスされる複数のデータ ソースにアクセスする必要があります。</p><p>ツール統合の広範な共有と再利用性が重要になる場合は、標準化されたツール統合が優先されます。MCP は、一般的な統合の開発時間を大幅に短縮する、事前に構築された MCP サーバーのエコシステムを備えているため、この点で優れています。シンプルさと保守性が求められる場合、MCP の要求応答パターンは開発者に馴染みがあり、分散システムよりも理解やデバッグが容易で、運用上の複雑さも少なくなります。</p><p>最後に、MCP は、システムとのリモート通信を容易にするためにソフトウェア プロバイダーによって提供されることがよくあります。プロバイダーが提供するこれらの MCP サーバーは、独自のシステムへの標準化されたインターフェースを提供しながら、オンボーディングと開発時間を大幅に短縮し、カスタム API 開発よりも統合をはるかに簡単にします。</p><h3>両方を使用する場合 (A2A ❤️ の MCP)</h3><p><a href="https://a2a-protocol.org/latest/topics/a2a-and-mcp/">MCP 統合に関する A2A ドキュメント</a>に記載されているように、多くの高度なシステムは A2A と MCP を組み合わせることでメリットを得られます。調整と標準化の両方を必要とするシステムは、ハイブリッド アプローチに最適です。A2A はエージェントの調整とワークフロー オーケストレーションを処理し、MCP は個々のエージェントにツール アクセスを提供します。私たちのニュースルームの例では、エージェントは A2A を介して調整し、ワークフローは記者から研究者、編集者、そして発行者へと移行します。ただし、各エージェントは専用のツール用に MCP サーバーを使用するため、アーキテクチャが明確に分離されます。</p><p>ツール アクセスにそれぞれ MCP を使用する複数の特殊エージェントは、A2A によって処理されるエージェント調整レイヤーと、MCP によって管理されるツール アクセス レイヤーがある一般的なパターンを表します。このように関心事を明確に分離することで、システムの理解と保守が容易になります。</p><p>両方のアプローチを組み合わせることによる利点は非常に大きいです。特殊化、自律性、並列処理などのマルチエージェント システムの組織的な利点が得られると同時に、ツールの統合やリソース アクセスなどの MCP の標準化とエコシステムの利点も享受できます。エージェント調整 (A2A) とリソース アクセス (MCP) は明確に区別されており、重要なのは、API アクセスなどの小規模なタスクのみには A2A は必要ないことです。MCP は、マルチエージェント オーケストレーションのオーバーヘッドなしで、これらのタスクを効率的に処理します。</p><p><strong>FAQ: A2A vs. MCP - ユースケース</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">機能</td><td style="border: 1px solid #ccc; padding: 4px;">エージェント2エージェント（A2A）</td><td style="border: 1px solid #ccc; padding: 4px;">モデルコンテキストプロトコル（MCP）</td><td style="border: 1px solid #ccc; padding: 4px;">ハイブリッド（A2A + MCP）</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">主な目標</td><td style="border: 1px solid #ccc; padding: 4px;">マルチエージェント調整: 専門エージェントのチームが、複雑な複数ステップのワークフローで連携できるようにします。</td><td style="border: 1px solid #ccc; padding: 4px;">単一エージェントの拡張: 外部ツール、リソース、およびデータを使用して、単一の LLM/エージェントの機能を拡張します。</td><td style="border: 1px solid #ccc; padding: 4px;">組み合わせた強み: A2A がチームのワークフローを処理し、MCP が各チーム メンバーにツールを提供します。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ニュースルームチームの例</td><td style="border: 1px solid #ccc; padding: 4px;">ワークフロー チェーン: ニュース チーフ → レポーター → リサーチャー → 編集者 → 発行者。これは調整レイヤーです。</td><td style="border: 1px solid #ccc; padding: 4px;">個々のエージェントのツール: スタイル ガイド サーバーとテンプレート サーバーにアクセスする Reporter Agent (MCP 経由)。これはツール アクセス レイヤーです。</td><td style="border: 1px solid #ccc; padding: 4px;">完全なシステム: 記者は編集者 (A2A) と連携し、画像ライブラリ MCP サーバーを使用して記事のグラフィックを検索します。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">いつどれを使うか</td><td style="border: 1px solid #ccc; padding: 4px;">真のコラボレーション、反復、改良、または専門知識を複数のエージェントに分割する必要がある場合。</td><td style="border: 1px solid #ccc; padding: 4px;">1 つのエージェントが複数のツールやデータ ソースにアクセスする必要がある場合、または独自のシステムとの標準化された統合が必要な場合。</td><td style="border: 1px solid #ccc; padding: 4px;">マルチエージェント システムの組織的利点と、MCP の標準化およびエコシステムの利点が必要な場合。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">コアベネフィット</td><td style="border: 1px solid #ccc; padding: 4px;">自律性とスケーリング: エージェントは独立して決定を下すことができ、システムは特殊な機能の水平スケーリングを可能にします。</td><td style="border: 1px solid #ccc; padding: 4px;">シンプルさと標準化: 集中化された推論によりデバッグと保守が容易になり、リソースに対する汎用的なインターフェースが提供されます。</td><td style="border: 1px solid #ccc; padding: 4px;">関心事の明確な分離: システムを理解しやすくなります: A2A = チームワーク、MCP = ツール アクセス。</td>
                    </tr>
              </tbody>
            </table>
          <img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ddf6a29b1107ac6a63e94ecef703abc561a29e1e-986x656.png" style="width: 100%;" /><h2>まとめ</h2><p>これは、データとツールへのサポートと外部アクセスを提供するために MCP サーバーで強化された A2A ベースのエージェントの実装を扱った 2 部構成の最初のセクションです。次の部分では、実際のコードを調べて、オンライン ニュースルームのアクティビティをエミュレートするためにそれらが連携して動作する様子を示します。どちらのフレームワークも、それ自体で非常に有能で柔軟性に優れていますが、連携して動作することで、どれだけ互いを補完し合うかがわかります。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[コンテキストのためのYou Know - パート1：ハイブリッド検索とコンテキストエンジニアリングの進化]]></title>
            <description><![CDATA[ハイブリッド検索とコンテキスト エンジニアリングが語彙の基礎からどのように進化し、次世代のエージェント AI ワークフローを可能にしたかを探ります。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/context-engineering-hybrid-search-evolution-agentic-ai</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/context-engineering-hybrid-search-evolution-agentic-ai</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Woody Walton]]></dc:creator>
            <pubDate>Wed, 12 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7e59a0671aa9ea32d68188a693936a66ebf48625-1000x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>私たちの新しいエージェントAIの世界</h2><p>私たちの多くと同じように、私も AI の能力が進化している速さに興奮すると同時に驚いています。大規模言語モデル (LLM) とベクトル検索によって、キーワードで検索する必要がなくなったセマンティック革命が初めて実現しました。その後、LLM は、チャット インターフェースを使用して自然言語によるリクエストを応答に変換し、膨大な知識ベースから簡単に利用できる要約を抽出するなど、データと対話する新しい方法を教えてくれました。私たちは今（すでに！）「エージェント AI」ワークフローの形で自動化された LLM 駆動型ロジックが始まります。このワークフローは、受信したリクエストを意味的に理解し、実行する手順を推論し、利用可能なツールから選択してアクションを反復的に実行し、目標を達成します。</p><p>エージェント AI の可能性により、私たちは、主に「プロンプト エンジニアリング」を使用して生成 AI のインタラクションを形成することから、エージェント ツールが LLM が応答を生成する際に考慮する必要がある最も関連性の高い効率的な追加情報を取得できるようにする方法に重点を置くように進化することを余儀なくされています。つまり、「コンテキスト エンジニアリング」が次のフロンティアです。ハイブリッド検索は、関連するコンテキストを明らかにするための最も強力で柔軟な手段であり、Elastic の Search AI プラットフォームは、コンテキストエンジニアリングに役立つデータを活用するまったく新しい方法を実現します。この記事では、LLM が情報検索の世界をどのように変えたかを 2 つの角度から説明し、さらに、LLM がどのように連携してより優れた成果を上げることができるかについて説明します。カバーすべき領域はかなり広いです…</p><h2>パート1: LLMが検索に与えた影響</h2><p>まず、LLM が情報にアクセスし取得する方法をどのように変えたかという観点から始めましょう。</p><h3>私たちの語彙の遺産</h3><p>私たちは皆、長い間、ある程度制限された語彙検索の世界で（できるだけうまく）生きてきました。検索は、調査をしたり新しいプロジェクトを開始したりするときに最初に使用するツールであり、最近まで、語彙検索エンジンが理解できる方法でクエリを言い表すのは私たち次第でした。語彙検索は、コンテンツが構造化されているか非構造化されているかに関係なく、何らかの形式のクエリ用語をドキュメント コーパスで見つかったキーワードと一致させることに依存します。語彙検索で文書がヒットとして返されるためには、そのキーワードに一致している必要があります (または、概念的なつながりを作るために同義語リストや辞書などの制御された語彙が必要です)。</p><pre><code>POST my-index/_search
{
  "size": 10,
  "query": {
    "semantic": {
      "query": "machine learning applications",
      "field": "semantic-content-field"
    }
  }
}</code></pre><p><em>語彙の<a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-multi-match-query"> 複数一致</a> クエリの 例</em></p><p>少なくとも検索エンジンには、関連性スコア付きのヒットを返す機能があります。検索エンジンは、インデックスされたデータを効果的にターゲットするための豊富なクエリ構文オプションと、ユーザーのクエリ構文の意図に応じて結果をスコア付けする組み込みの関連性アルゴリズムを提供します。検索エンジンは、関連性ランキング アルゴリズムの数十年にわたる進歩の恩恵を受けており、クエリとの関連性に基づいてスコア付けされ、並べ替えられた結果を提供できる効率的なデータ検索プラットフォームとなっています。データを取得する主な方法として SQL を使用するデータベースやその他のシステムは、ここでは不利です。データベース クエリには関連性の概念がなく、せいぜいアルファベット順または数字順に結果を並べ替えることしかできないからです。良いニュースとしては、これらのキーワードでヒットするものがすべて得られる (リコール) ことですが、それらは必ずしも、検索を求めた<em>理由</em>に対して役立つ順序 (精度) になっているわけではありません。これは重要なポイントです。すぐにわかります…</p><h3>（意味論的）ドラゴンの登場</h3><p>キーワード検索の代替として情報のベクトル表現の可能性は<a href="https://www.elastic.co/search-labs/blog/introduction-to-vector-search">、かなり長い間</a>研究されてきました。ベクトルは、キーワードのみのコンテンツ一致モードから抜け出すことができるため、大きな可能性を秘めています。ベクトルは用語と重みの数値表現であるため、トレーニング領域で用語が互いにどのように関連しているかについての言語モデルの理解に基づいて、概念を数学的に近づけることができます。汎用ベクトル検索の長い遅延は、モデルが主に特定のドメインに限定されていたためであり、異なるコンテキスト内で用語が表す可能性のあるさまざまな概念を十分に理解できるほどモデルが大きくなかったのです。</p><p>ベクトル検索が実用的になったのは、数年前に大規模言語モデル (LLM) が登場し、<a href="https://en.wikipedia.org/wiki/Transformer_(deep_learning_architecture)">トランスフォーマー</a>と<a href="https://en.wikipedia.org/wiki/Attention_(machine_learning)">アテンション</a>を使用してはるかに大量のデータをトレーニングできるようになったときでした。LLM のサイズと深さにより、ベクトルは最終的に十分なニュアンスを保存できるようになり、実際に意味を捉えることができるようになりました。理解の深さが突然増加したことにより、LLM は、以前はロックされていた多数の自然言語処理 (NLP) 機能を提供できるようになり、おそらく最も影響力があるのは、これまでのシーケンスの内容に基づいて、シーケンス内で最も可能性の高い次の用語を推測する機能です。推論は、生成 AI に人間に近いテキスト生成能力を与えるプロセスです。AI によって生成されたテキストは、LLM がトレーニング データ内で用語がどのように関連しているかを理解した上で生成され、また、リクエストのフレーズを使用して、用語が出現する可能性のあるさまざまなコンテキスト間の曖昧さを解消します。</p><p>生成 AI は魔法のようですが、LLM には品質と精度のエラー (一般に幻覚と呼ばれる) を引き起こす制限<em>があります</em>。幻覚は、LLM が真実に基づいた回答をするための情報にアクセスできない (または正しいコンテキストに誘導されない) 場合に発生します。そのため、LLM は役に立とうとして、代わりに自信に満ちたもっともらしい応答をでっち上げで生成します。原因の一部は、LLM が多様な情報の大規模な領域内で言語の使用法を学習する一方で、ある時点でトレーニングを停止する必要があるため、理解に適時性の要素があることです。つまり、モデルはトレーニングを停止した時点までの正確さしか認識できないということです。幻覚を引き起こすもう 1 つの要因は、モデルが非公開データ (パブリック インターネットで利用できないデータ) を認識しないことです。これは、データに特定の用語や命名法が含まれている場合に特に重要です。</p><h3>ベクターデータベース</h3><p>LLM は、テキスト埋め込みと呼ばれる手法を使用してコンテンツをモデル空間にベクトル化します。テキスト<a href="https://www.elastic.co/search-labs/blog/hybrid-search-multiple-embeddings">埋め込み</a>とは、受信したトレーニングに基づいて、モデルの世界観内にコンテンツの意味を埋め込む、つまりマッピングすることを指します。埋め込み用のコンテンツを準備して処理するには、<a href="https://www.elastic.co/search-labs/blog/chunking-strategies-elasticsearch">チャンク化</a>とトークン化（および<a href="https://www.kaggle.com/code/danishmahdi/subword-tokenization-bpe-wordpiece-and-unigram">サブワードトークン化</a>）など、いくつかの手順が必要です。結果は通常、ベクトル空間内でのコンテンツ チャンクの意味に関するモデルの理解を表す密なベクトルのセットになります。チャンキングは、埋め込みを生成するためのモデルの処理制約の制限内にコンテンツを収めることを目的とした不正確なプロセスであり、文や段落のインジケーターなどのセマンティック構造を使用して関連するテキストをチャンクにグループ化しようとします。</p><p>チャンク化の必要性により、埋め込まれたドキュメントでは、個々のチャンクが同じドキュメントの他のチャンクと完全に関連付けられていないため、多少の意味的損失が生じる可能性があります。ニューラル ネットワークの本質的な不透明性により、この損失が悪化する可能性があります。LLM はまさに「ブラック ボックス」であり、トレーニング中に作成された用語と概念間の接続は非決定論的であり、人間が解釈することはできません。これにより、説明可能性、再現性、無意識の偏見に関する問題が発生し、信頼性と正確性が失われる可能性があります。それでも、クエリ時に特定のキーワードに縛られずにアイデアを意味的に結び付ける機能は非常に強力です。</p><pre><code>POST my-index/_search 
{
  "size": 10, 
  "query": {
    "semantic": {
      "query": "machine learning applications",
      "field": "semantic-content-field"
    }
  }
} </code></pre><p><em><a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-semantic-query">セマンティック</a> クエリの 例</em></p><p>ベクター データベースに関して考慮すべきもう 1 つの問題があります。ベクター データベースは検索エンジンではなく、データベースなのです。<a href="https://www.elastic.co/search-labs/blog/introduction-to-vector-search">ベクトル類似性検索を</a>実行すると、クエリ用語がエンコードされ、モデルのベクトル空間内の一連の (埋め込み) 座標が検索されます。これらの座標は、ブルズアイとして使用され、ブルズアイに「最も近い」近傍にあるドキュメントが検索されます。つまり、ドキュメントのランク (または結果の配置) は、クエリの座標からのそのドキュメントの座標の計算された類似<em>距離</em>によって決まります。ランキングはどの方向を優先すべきでしょうか、考えられるコンテキストのうちどれがユーザーの意図に最も近いでしょうか?私がこれを例えると、映画<a href="https://www.youtube.com/watch?v=x3h7xz558EY&amp;start=3&amp;end=86">「スターゲイト</a>」のワンシーンになります。そのシーンでは、交差する 6 つの座標点が目的地 (的) を示しますが、ユーザーの主観的な意図を表す出発点の座標である「7 番目のシンボル」を知らないとそこに到達できません。したがって、ベクトルの相対的なランキングが常に拡大し区別のない類似性の領域に基づくのではなく、表現構文と関連性スコアリングを通じてクエリの主観的な意図を考慮することによって、段階的な主観的関連性の<em>円筒</em>に似たものを得ることができます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1ddfaae0c1496d08d7d30419e6d2aeaeacfc0ea2-1600x544.png" style="width: 100%;" /><p>LLM の推論機能は、クエリ<em>に対して</em>最も可能性の高いコンテキストを識別するのに役立つ可能性がありますが、問題は<em>、支援がなければ、</em>着信クエリの座標はモデルが最初にトレーニングされた方法によって<em>のみ</em>決定できることです。</p><p>ある意味、ベクトル類似性は厳密なキーワード一致とは正反対の極限にあると言えます。その強みは用語の不一致の問題を克服する能力にありますが、それは<a href="https://medium.com/data-science/vector-embeddings-are-lossy-heres-what-to-do-about-it-4f9a8ee58bb7">ほとんど欠点で</a>もあります。LLM は関連する概念を区別するのではなく、統合する傾向があります。ベクトル類似性により、コンテンツを意味的に一致させる能力は向上しますが、モデルによって十分に明確にされていない正確なキーワードや具体的な詳細を見落とす可能性があるため、精度は保証されません。ベクトル類似性検索はそれ自体強力ですが、ベクトル データベースから取得した結果と他の取得方法の結果を相関させる方法が必要です。</p><h3>再ランキング手法</h3><p>ここで、結果セットを統一されたランク順に再スコアリングまたは正規化する、再ランク付けと呼ばれる一般的な手法について説明するのが良いでしょう。再ランク付けが必要になるのは、複数のソースからの結果や、ランク付け/スコアリング メカニズムが異なる (または SQL の場合はまったくメカニズムがない) 検索方法による場合です。また、非セマンティック ソースからの結果をユーザーのクエリに意味的に合わせるために再ランク付けを使用する場合もあります。再ランキングは第2段階の操作であり、何らかの<em>初期検索</em>方法（つまり、その後、検索クエリ (SQL、語彙検索、ベクトル検索) は、異なるスコアリング方法で並べ替えられます。</p><p>利用可能なアプローチはいくつかありますが、その中には<a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr">Learning-To-Rank (LTR)</a>や<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">Reciprocal Rank Fusion (RRF)</a>などがあります。LTR は、検索結果の特徴 (いいね、評価、クリックなど) をキャプチャし、それらを使用して結果にスコアを付けたり、結果をブーストしたり、バイアスをかけたりするのに役立ちます。RRFは、異なるクエリモダリティから返された結果をマージするのに最適です（例：語彙データベース検索とベクトルデータベース検索を 1 つの結果リストにまとめます。Elastic は、<a href="https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search">線形再ランキング</a>方式を使用してスコアを調整する柔軟性も提供します。</p><p>ただし、最も効果的な再ランキング手法の 1 つは、<a href="https://www.elastic.co/docs/solutions/search/ranking/semantic-reranking">セマンティック再ランキング</a>です。これは、LLM のセマンティック理解を使用して、クエリと結果の両方のベクトル埋め込みを分析し、関連性スコアリング/再スコアリングを適用して最終的な順序を決定します。もちろん、セマンティック再ランク付けには再ランク付けモデルへの接続が必要です 。Elasticsearch は、組み込みモデル (<a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-rerank"> Elastic Rerank</a> )、<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/eland/machine-learning"> インポートされた</a> サードパーティモデル、または<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-cohere"> Cohere</a> や<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-googlevertexai"> Google Vertex AI</a><strong> などの外部でホストされるサービスを活用する 再ランク付け</strong> エンドポイントを作成できる推論<a href="https://www.elastic.co/docs/api/doc/elasticsearch/group/endpoint-inference"> API</a> を提供します。次に、<a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">リトリーバー</a>クエリ抽象化構文を使用して再ランク付けを実行できます。</p><pre><code>POST my-index/_search 
{
  "size": 10,
  "retriever": {
    "text_similarity_reranker": {
      "retriever": {
        "rrf": {
          "retrievers": [
            {
              "standard": {
                "query": {
                  "multi_match": {
                    "query": "machine learning applications",
                    "fields": ["title", "content"]
                  }
                }
              }
            },
            {
              "knn": {
                "field": "semantic-content-field",
                "k": 10,
                "num_candidates": 100,
                "query_vector_builder": {
                  "text_embedding": {
                    "model_id": "my-text-embedding-model",
                    "model_text": "machine learning applications"
                  }
                }
              }
            }
          ],
          "rank_window_size": 50,
          "rank_constant": 20
        }
      }
    },
    "field": "content",
    "inference_id": "my-reranker",
    "inference_text": "machine learning applications",
    "rank_window_size": 20
  }
}</code></pre><p><em>多段階リトリーバー再ランキング操作の例</em></p><p>素晴らしいですね。さまざまなソースからの結果の再ランキングを実行し、あらゆる種類のコンテンツの意味的理解に近づくことができます。意味的再ランキングは、計算コストと必要な処理時間の両方でコストがかかる可能性があり、そのため、意味的再ランキングは限られた数の結果に対してのみ実行できます。つまり、最初の結果を<em>どのように</em>取得するかが重要になります。</p><h3>文脈検索方法が重要</h3><p>主観的な意図は、結果の正確性を判断し、関連性を評価する上で重要な要素です。クエリを実行するユーザーの意図（柔軟な構文または第 2 段階の再ランク付けによって表現される）を考慮する機能がなければ、モデル空間内にすでにエンコードされている既存のコンテキストから選択することしかできません。このコンテキストの欠如に対処する一般的な方法は、<a href="https://en.wikipedia.org/wiki/Retrieval-augmented_generation">検索拡張生成 (RAG)</a>などの手法を使用することです。RAG の仕組みは、文脈的に関連するデータの事前クエリから返された追加の関連用語を含めることで、クエリの座標を効果的にシフトすることです。そのため、追加のコンテキストを提供するエンジンと、検索を実行するため<em>の</em>初期方法が、コンテキストの正確さにとってさらに重要になります。</p><p>さまざまなコンテキスト取得方法と、それが RAG 操作にどのように役立つか、または悪影響を与えるかを確認しましょう。</p><ul><li><strong>検索エンジンを使用しないハイブリッド検索では、依然として主観的な関連性が欠けています。</strong>RAG を提供するプラットフォームが主に SQL ベースである場合 (ほとんどの「データ レイク」プラットフォームが含まれます)、最初の検索段階で関連性スコアリングが欠如しています。多くのデータ レイク プラットフォームは、独自のハイブリッド検索 (検索ではない) を提供しており、通常は SQL ベースの検索とベクター データベースの結果にセマンティック リランキングや RRF などの再ランキング手法を組み合わせています。単純なソートは主観的なランキング付けには明らかに不十分ですが、第 2 段階のセマンティック リランキング操作の基礎として使用した場合でも、第 1 段階の検索としての SQL では、セマンティック リランキングが「上位 k」のヒットに対してのみ実行される場合に問題が生じます。検索時に結果にスコアを付ける方法がなければ、<em>最善の</em>結果が実際に上位の結果にあるという保証はありません。</li><li><strong>ベクトルの類似性だけでは RAG には不十分です</strong>。これは実際には、一連の問題が複合的に絡み合った結果です。つまり、埋め込みの損失、単純なチャンク化方法、類似性の計算方法、そして主観的な意図という重要な要素が欠落しているという問題です。RAG の主な目標の 1 つは、生成 AI のインタラクションを客観的な真実に基づいて確立することです。これにより、幻覚を防ぐと同時に、トレーニング中に認識されなかったプライベート情報を LLM に通知します。RAG を通じて提供される追加のコンテキストを使用して、手近の質問に答えるために最も重要であることがわかっている接続と詳細を考慮するように LLM を制限および指示できます。そのためには、意味論的アプローチと語彙的アプローチの<em>両方</em>を使用する必要があります。</li><li><strong>ファイルベースの grep/regex RAG。</strong>エージェント AI の世界では、外部の検索プラットフォームではなく、RAG の grep と regex を介してローカル ファイルにアクセスする、大幅に拡大されたコンテキスト ウィンドウの使用を指摘する<a href="https://www.nicolasbustamante.com/p/the-rag-obituary-killed-by-agents">声</a>も上がっています。その考え方は、はるかに大きなコンテキスト ウィンドウを利用できることで、LLM が、関連情報を収集するために断片的な情報や複数の検索方法/プラットフォームに頼るのではなく、独自の思考空間内で概念的なつながりを構築できるようになるというものです。理論上は、文書全体があれば文書セグメントよりも完全な画像が得られるというのは本当ですが、これは小さなデータドメインでのみ機能します (または、たとえば、 <a href="https://en.wikipedia.org/wiki/Vibe_coding">vibecoding</a>にファイルを提供する場合)。また、その場合でも、最初の検索方法は、キーワードのみが一致するすべての文書をスキャンすることです。</li></ul><p><strong>検索は単なる回収以上のもの</strong></p><p>検索エンジンは、クエリを可能な限り高速かつ柔軟に実行することを目的として構築されています。内部的には、さまざまな種類のデータをそれらのデータ型に適した方法で保存および取得するための特殊なデータ構造を利用します。Elasticsearchは、非構造化/全文語彙検索（一致、フレーズ、近接、複数一致）、高速キーワード（完全一致）マッチングとフィルタリング、数値範囲、日付、IPアドレスなど、基本的にすべてのタイプのデータの最適化された保存とクエリを提供し、ドキュメント構造（例：ネストされたドキュメントやフラット化されたドキュメントなど)。Elasticsearch は、スパース ベクトル タイプと密ベクトル タイプの両方を保存およびクエリできるネイティブ ベクトル データベースでもあり、ベクトル化されたコンテンツに関連する速度、スケーラビリティ、コストを改善しながら検索の忠実度を維持するための革新的な方法 ( <a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Better Binary Quantization (BBQ)</a>や<a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction">DiskBBQ</a>など) を継続的に模索しています。Elasticsearch プラットフォームには、組み込みのデータ回復力と高可用性も備わっており、<a href="https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/searchable-snapshots">検索可能なスナップショット</a>などのデータライフサイクル管理機能も含まれています。検索可能なスナップショットを使用すると、アクセス頻度の低いデータや長期保存データをコスト効率の高いオブジェクトストレージに保存しながらも、完全に検索可能です。</p><h3>ハイブリッド検索はあらゆる面で最高です</h3><p><a href="https://www.elastic.co/what-is/hybrid-search">ハイブリッド検索</a>(単なるハイブリッド取得ではありません!)従来の語彙検索の長所と、LLM の意味理解およびベクトル類似性検索を組み合わせます。この相乗効果により、検索エンジンが提供する柔軟なクエリ構文オプション（意図主導型の構文オプションと関連性スコアリング、マルチモーダル データ検索、フィルタリング、集約、バイアスなど）を通じて、<em>検索</em>段階で関連性の高い結果をターゲットにすることができます。<a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a>やマルチステージ<a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">リトリーバー</a>などの検索構文を使用すると、従来の検索とセマンティック検索、フィルター、複数の再ランキング手法をすべて 1 つのリクエストで柔軟に組み合わせることができます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f25180cb430414b99ae553d3b8eb161dbccea4d4-1920x1080.png" style="width: 100%;" /><p>ハイブリッド検索の最大の利点の 1 つは、クエリで複数の異なるデータ タイプに同時に特殊な構文を使用できることです。これらのさまざまなクエリ構文は、結果の<em>検索</em>だけでなく、結果<em>の</em>フィルターや集計としても使用できます。たとえば、他の構文と頻繁に組み合わせられる最も一般的なクエリ タイプの 1 つは、<a href="https://www.elastic.co/docs/explore-analyze/geospatial-analysis">地理空間分析</a>です。特定のポイントから指定された距離内の地理座標を持つ結果をクエリしたり、地域別に結果の集計を要求したり、ゾーンへの出入りの動きを追跡してアラートを発する集計を実行したりできます。ハイブリッド検索を使用すると、構文を柔軟に組み合わせて、最も正確な方法で結果をターゲットにし、コンテキストに最も近いコンテンツを取得できます。</p><h2>休憩</h2><p>この最初の部分では、ベクトル検索によってデータの取得方法がどのように変化したかを説明し、LLM がデータの操作に使用するクエリ メカニズムにもたらした変化の基礎を説明します。LLM がコンテキストを失うことなく理解できるように、これを複数の部分に分割する必要があったと仮定します... ;-)<em>これがなぜ重要なのか</em>については<a href="https://www.elastic.co/search-labs/blog/context-engineering-llm-evolution-agentic-ai">、パート II: エージェント AI とコンテキスト エンジニアリングの必要性</a>で詳しく説明し、パート III ではハイブリッド検索について再び説明します。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch で構造化ドキュメントの再帰チャンクを構成する]]></title>
            <description><![CDATA[チャンク サイズ、セパレーター グループ、カスタム セパレーター リストを使用して Elasticsearch で再帰チャンクを設定し、構造化ドキュメントのインデックスを最適に作成する方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/recursive-chunking-structured-documents-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/recursive-chunking-structured-documents-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Daniel Rubinstein]]></dc:creator>
            <pubDate>Tue, 11 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/591872e31880768ca927507654a621addc0d124d-1600x960.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>8.16 以降、ユーザーは長いドキュメントをセマンティック テキスト フィールドに取り込むときに使用するチャンキング戦略を構成できるようになりました。9.1 / 8.19 では、正規表現のリストを使用してドキュメントをチャンク化する、新しい構成可能な再帰チャンク化戦略を導入しました。チャンク化の目的は、長いドキュメントを関連するコンテンツをカプセル化するセクションに分割することです。既存の戦略では、テキストを単語/文の粒度で分割しますが、構造化された形式 (例:Markdown では、区切り文字列で定義されたセクション内に関連コンテンツが含まれることがよくあります (例:ヘッダー)。このような種類のドキュメントでは、構造化ドキュメントの形式を活用してより適切なチャンクを作成するための再帰チャンキング戦略を導入しています。</p><h2>再帰チャンキングとは何ですか?</h2><p>再帰チャンク化では、指定されたセクション分離パターンのリストを反復処理して、必要な最大チャンク サイズを満たすまで、ドキュメントを段階的に小さなセグメントに分割します。</p><h3>再帰チャンクを構成するにはどうすればよいですか?</h3><p>以下は、再帰チャンク化に対してユーザーが指定できる構成可能な値です。</p><ul><li>(必須) <code>max_chunk_size</code> : チャンク内の最大単語数。</li><li>次のいずれか:<ul><li><code>separators</code>: ドキュメントをチャンクに分割するために使用される正規表現文字列パターンのリスト。</li><li><code>separator_group</code>: 特定の種類のドキュメントに使用するために Elastic によって定義された区切り文字のデフォルト リストにマップされる文字列。現在、 <code>markdown</code>と<code>plaintext</code>が利用可能です。</li></ul></li></ul><h3>再帰チャンキングはどのように機能しますか?</h3><p>入力ドキュメント、 <code>max_chunk_size</code> (単語単位で測定)、および区切り文字列のリストが与えられた場合の再帰チャンク化のプロセスは次のとおりです。</p><ol><li>入力ドキュメントがすでに最大チャンク サイズ内である場合は、入力全体にわたる単一のチャンクを返します。</li><li>区切り文字の出現に基づいてテキストを潜在的なチャンクに分割します。潜在的なチャンクごとに:<ol><li>潜在的なチャンクが最大チャンク サイズ内である場合は、ユーザーに返すチャンクのリストに追加します。</li><li>それ以外の場合は、潜在的なチャンクのテキストのみを使用して、リスト内の次のセパレーターを使用して分割し、手順 2 から繰り返します。試す区切り文字がもう残っていない場合は、文ベースのチャンクに戻ります。</li></ol></li></ol><h2>再帰チャンクの設定例</h2><p>チャンク サイズとは別に、再帰チャンク化の主な構成は、ドキュメントを分割するために使用するセパレーターを選択することです。どこから始めればよいかわからない場合は、Elasticsearch では一般的なユースケースに使用できるデフォルトのセパレーター グループがいくつか用意されています。</p><h3>セパレーターグループの活用</h3><p>セパレーター グループを利用するには、チャンク設定を構成するときに使用するグループの名前を指定するだけです。例えば：</p><pre><code>"chunking_settings": {
    "strategy": "recursive",
    "max_chunk_size": 25,
    "separator_group": "plaintext"
}</code></pre><p>これにより、区切りリスト<code>[&quot;(?&lt;!\\n)\\n\\n(?!\\n)&quot;, &quot;(?&lt;!\\n)\\n(?!\\n)&quot;)]</code>を利用する再帰的なチャンク化戦略が提供されます。これは、2 つの改行文字とそれに続く 1 つの改行文字で分割する、一般的なプレーン テキスト アプリケーションに適しています。</p><p>セパレーターリストを利用するセパレーターグループ<code>markdown</code>も提供しています。</p><pre><code>[
"\n# ",
       "\n## ",
       "\n### ",
       "\n#### ",
       "\n##### ",
       "\n###### ",
       "\n^(?!\\s*$).*\\n-{1,}\\n",
       "\n^(?!\\s*$).*\\n={1,}\\n"
]</code></pre><p>この区切りリストは、6 つの見出しレベルとセクション区切り文字のそれぞれに分割する一般的なマークダウンの使用例に適しています。</p><p>リソース (推論エンドポイント/セマンティック テキスト フィールド) を作成すると、その時点のセパレーター グループに対応するセパレーターのリストが構成に保存されます。セパレーター グループが後日更新されても、既に作成されたリソースの動作は変更されません。</p><h3>カスタム区切りリストの利用</h3><p>定義済みの区切り文字グループのいずれかが使用ケースに適していない場合は、ニーズに合った区切り文字のカスタム リストを定義できます。区切りリスト内に正規表現を指定できることに注意してください。以下は、カスタムセパレーターを使用して構成されたチャンク設定の例です。</p><pre><code>"chunking_settings": {
    "strategy": "recursive",
    "max_chunk_size": 25,
    "separators": ["\n\n", "\n", "<my-custom-separator>"]
}</code></pre><p>上記のチャンク化戦略では、 2 つの改行文字、続いて 1 つの改行文字、最後に文字列<code>“&lt;my-custom-separator&gt;”</code>で分割されます。</p><h2>再帰チャンキングの実際の例</h2><p>再帰チャンキングの実際の例を見てみましょう。この例では、上位 2 つのヘッダー レベルを使用してマークダウン ドキュメントを分割するセパレーターのカスタム リストとともに、次のチャンク設定を使用します。</p><pre><code>"chunking_settings": {
    "strategy": "recursive",
    "max_chunk_size": 25,
    "separators": ["\n# ", "\n## "]
}</code></pre><p>単純なチャンクなしの Markdown ドキュメントを見てみましょう。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3a5507f4a1288065097231548e5b18e240508785-1302x1446.png" style="width: 100%;" /><p>ここで、上で定義したチャンク設定を使用してドキュメントをチャンク化してみましょう。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a3313c4c40ff39b8dbcdd7c4878c723f088e6c1a-1600x1187.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/79a2921943191ea631df94c9d465818ec8d3e738-1600x1206.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/459e695cce7540267422396b9a62ff4ad35f61db-1600x1260.png" style="width: 100%;" /><p>注: 各チャンク (チャンク 3 を除く) の末尾の改行は強調表示されませんが、実際のチャンク境界内に含まれます。</p><h3>今すぐ再帰チャンキングを始めましょう!</h3><p>この機能の利用方法の詳細については、<span class="unknown__pt__mark__88e241676926">チャンク設定の構成</span>に関するドキュメントを参照してください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Kibana に Elasticsearch クエリルール UI を導入]]></title>
            <description><![CDATA[Elasticsearch クエリ ルール UI を使用して、オーガニック ランキングに影響を与えずに Kibana のカスタマイズ可能なルールセットを使用して検索クエリにドキュメントを追加または除外する方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-query-rules-ui-introduction</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-query-rules-ui-introduction</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jhon Guzmán]]></dc:creator>
            <pubDate>Fri, 07 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1fb10bd31c509cc9b9bb4f71f49970f140e6c36f-1600x945.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>検索エンジンの役割は、関連性のある結果を返すことです。ただし、セールの強調、季節商品の優先、スポンサー商品の展示など、それ以上のビジネスニーズがあり、開発者は検索クエリでこれを常に実行できるとは限りません。</p><p>さらに、これらのユースケースは通常、時間に敏感であり、一般的な開発段階 (コード ブランチを作成してから新しいリリースを待つ) を実行するのは時間のかかるプロセスです。</p><p>では、このプロセス全体を API 呼び出しだけで、あるいは Kibana で数回クリックするだけで実行できたらどうなるでしょうか?</p><h2>クエリルールUI</h2><p>Elasticsearch 8.10 では、<a href="https://www.elastic.co/blog/introducing-query-rules-elasticsearch-8-10"><strong>クエリ ルール</strong></a>と<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/rule-retriever"><strong>ルール リトリーバー</strong></a>が導入されました。これらは、ルールに基づいてオーガニック検索結果のランキングに影響を与えずに、<a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-pinned-query"><em>ピン留めされた結果を</em></a>クエリに挿入するように設計されたツールです。宣言的かつシンプルな方法で、結果の上にビジネス ロジックを追加するだけです。</p><p>クエリ ルールの一般的な使用例は次のとおりです。</p><ul><li><strong>プロモーション対象商品やセール品の強調表示</strong>: セール中の商品やスポンサー商品を上部に表示します。</li><li><strong>コンテキストまたは地理位置情報による除外</strong>: 地域の規制により表示が許可されていない場合は、特定のアイテムを非表示にします。</li><li><strong>主要な結果を優先する</strong>: オーガニックランキングに関係なく、人気のある検索や固定検索が常に上位に表示されるようにします。</li></ul><p>インターフェースにアクセスしてこれらのツールを操作するには、Kibana サイドメニューをクリックし、関連性の下にある<strong>クエリルール</strong>に移動する必要があります<strong>。</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/242e33e89d1a07ffa0e76009c46b3a9236722741-458x1010.png" style="width: 100%;" /><p>クエリ ルール メニューが表示されたら、<strong>最初のルール セットの作成をクリックします。</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/30b3a91bbbf243d314cf38298e01ca5cff784430-1600x945.png" style="width: 100%;" /><p>次に、ルールセットに名前を付ける必要があります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/26c5462f88678867776f933b5655ca0df0d72a16-708x446.png" style="width: 100%;" /><p>各ルールを定義するフォームには、次の 3 つの主要コンポーネントがあります。</p><ul><li><strong>基準</strong>: ルールを適用するために満たす必要がある条件。たとえば、「query_string フィールドに値<em>Christmas</em>が含まれている場合」や「country フィールドに<em>CO の場合」などです。</em></li><li><strong>アクション</strong>: これは、条件が満たされたときに発生する動作です。ピン留め（ドキュメントを上位の結果に固定する）したり、除外（ドキュメントを非表示にする）したりできます。</li><li><strong>メタデータ</strong>: これらはクエリの実行時にクエリに付随するフィールドです。これらには、ユーザーの情報 (場所や言語など) や検索データ (query_string) を含めることができます。これらは、ルールを適用するかどうかを決定するための基準で使用される値です。</li></ul><h2>例: 人気商品</h2><p>さまざまな商品を扱う電子商取引サイトがあると想像してみましょう。指標をチェックすると、コンソール カテゴリで最も売れているアイテムの 1 つが「DualShock 4 ワイヤレス コントローラー」であることがわかります。特に、ユーザーが「PS4」または「PlayStation 4」というキーワードを検索した場合に多く見られます。そこで、ユーザーがこれらのキーワードを検索するたびに、この製品を結果の最上位に表示することにしました。</p><p>まず、Bulk API リクエストを使用して各アイテムのドキュメントをインデックス化します。</p><pre><code>POST _bulk
{ "index": { "_index": "products", "_id": "1" } }
{ "id": "1", "name": "PlayStation 4 Slim 1TB", "category": "console", "brand": "Sony", "price": 1200 }
{ "index": { "_index": "products", "_id": "2" } }
{ "id": "2", "name": "DualShock 4 Wireless Controller", "category": "accessory", "brand": "Sony", "price": 250 }
{ "index": { "_index": "products", "_id": "3" } }
{ "id": "3", "name": "PlayStation 4 Camera", "category": "accessory", "brand": "Sony", "price": 200 }
{ "index": { "_index": "products", "_id": "4" } }
{ "id": "4", "name": "PlayStation 4 VR Headset", "category": "accessory", "brand": "Sony", "price": 900 }
{ "index": { "_index": "products", "_id": "5" } }
{ "id": "5", "name": "Charging Station for DualShock 4", "category": "accessory", "brand": "Sony", "price": 80 }</code></pre><p>クエリに介入しない場合、アイテムは通常 4 番目の場所に表示されます。クエリは次のとおりです。</p><pre><code>GET products/_search
{
 "query": {
   "match": {
     "name": "PlayStation 4"
   }
 }
}</code></pre><p>そして結果はこちらです</p><pre><code>{
 "took": 1,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 5,
     "relation": "eq"
   },
   "max_score": 0.6973252,
   "hits": [
     {
       "_index": "products",
       "_id": "3",
       "_score": 0.6973252,
       "_source": {
         "id": "3",
         "name": "PlayStation 4 Camera",
         "category": "accessory",
         "brand": "Sony",
         "price": 200
       }
     },
     {
       "_index": "products",
       "_id": "1",
       "_score": 0.6260078,
       "_source": {
         "id": "1",
         "name": "PlayStation 4 Slim 1TB",
         "category": "console",
         "brand": "Sony",
         "price": 1200
       }
     },
     {
       "_index": "products",
       "_id": "4",
       "_score": 0.6260078,
       "_source": {
         "id": "4",
         "name": "PlayStation 4 VR Headset",
         "category": "accessory",
         "brand": "Sony",
         "price": 900
       }
     },
     {
       "_index": "products",
       "_id": "2",
       "_score": 0.08701137,
       "_source": {
         "id": "2",
         "name": "DualShock 4 Wireless Controller",
         "category": "accessory",
         "brand": "Sony",
         "price": 250
       }
     },
     {
       "_index": "products",
       "_id": "5",
       "_score": 0.07893815,
       "_source": {
         "id": "5",
         "name": "Charging Station for DualShock 4",
         "category": "accessory",
         "brand": "Sony",
         "price": 80
       }
     }
   ]
 }
}</code></pre><p>これを変更するためのクエリ ルールを作成しましょう。まず、次のようにルールセットに追加しましょう。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fdc42646fb3e76a09bca7d19047a76efe343f7a2-1600x650.png" style="width: 100%;" /><p>または同等の<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-query-rules-put-ruleset">API リクエスト</a>:</p><pre><code>PUT _query_rules/my-rules
{
  "rules": [
    {
      "rule_id": "rule-1232",
      "type": "pinned",
      "criteria": [
        {
          "type": "exact",
          "metadata": "query_string",
          "values": [
            "PS4",
            "PlayStation 4"
          ]
        }
      ],
      "actions": {
        "docs": [
          {
            "_index": "products",
            "_id": "2"
          }
        ]
      }
    }
  ]
}</code></pre><p>クエリで<strong>ルールセット</strong>を使用するには、クエリ ルール タイプを使用する必要があります。この種のクエリは、主に次の 2 つの部分で構成されます。</p><pre><code>GET /products/_search
{
 "retriever": {
   "rule": {
     "retriever": {
       "standard": {
         "query": {
           "match": { "name": "PlayStation 4" }
         }
       }
     },
     "match_criteria": {
       "query_string": "PlayStation 4"
     },
     "ruleset_ids": ["my-rules"]
   }
 }
}</code></pre><ul><li><strong>match_criteria</strong> : ユーザーのクエリと比較するために使用されるメタデータです。この例では、query_string フィールドの値が「PlayStation 4」の場合にルールセットがアクティブになります。</li><li><strong>query</strong> : 検索してオーガニック検索結果を取得するために使用される実際のクエリ。</li></ul><p>この方法では、最初にオーガニッククエリを実行し、次に Elasticsearch がルールセットのルールを適用します。</p><pre><code>{
 "took": 17,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 5,
     "relation": "eq"
   },
   "max_score": 1.7014122e+38,
   "hits": [
     {
       "_index": "products",
       "_id": "2",
       "_score": 1.7014122e+38,
       "_source": {
         "id": "2",
         "name": "DualShock 4 Wireless Controller",
         "category": "accessory",
         "brand": "Sony",
         "price": 250
       }
     },
     {
       "_index": "products",
       "_id": "3",
       "_score": 0.6973252,
       "_source": {
         "id": "3",
         "name": "PlayStation 4 Camera",
         "category": "accessory",
         "brand": "Sony",
         "price": 200
       }
     },
     {
       "_index": "products",
       "_id": "1",
       "_score": 0.6260078,
       "_source": {
         "id": "1",
         "name": "PlayStation 4 Slim 1TB",
         "category": "console",
         "brand": "Sony",
         "price": 1200
       }
     },
     {
       "_index": "products",
       "_id": "4",
       "_score": 0.6260078,
       "_source": {
         "id": "4",
         "name": "PlayStation 4 VR Headset",
         "category": "accessory",
         "brand": "Sony",
         "price": 900
       }
     },
     {
       "_index": "products",
       "_id": "5",
       "_score": 0.07893815,
       "_source": {
         "id": "5",
         "name": "Charging Station for DualShock 4",
         "category": "accessory",
         "brand": "Sony",
         "price": 80
       }
     }
   ]
 }
}</code></pre><h2>例: ユーザーベースのメタデータ</h2><p>クエリ ルールのもう 1 つの興味深い応用は、メタデータを使用して、ユーザーまたは Web ページからのコンテキスト情報に基づいて特定のドキュメントを表示することです。</p><p>たとえば、数値として表されるユーザーのロイヤルティ レベルに基づいて、アイテムやカスタマイズされたセールを強調表示したいとします。</p><p>これを実現するには、このメタデータをクエリに直接取り込んで、その値が特定の基準を満たしたときにルールがアクティブになるようにします。</p><p>まず、ロイヤルティ レベルの高いユーザーだけが閲覧できるドキュメントをインデックスします。</p><pre><code>POST _bulk
{ "index": { "_index": "products", "_id": "6" } }
{ "id": "6", "name": "PlayStation Plus Deluxe Card - 12 months", "category": "membership", "brand": "Sony", "price": 300 }</code></pre><p>ここで、同じルールセット内に新しいルールを作成し、loyalty_level が 80 以上の場合にアイテムが結果の上部に表示されるようにします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/58de12dff93305440608f51465462fcc68653a08-1421x496.png" style="width: 100%;" /><p>ルールとルールセットを保存します。</p><p>同等の REST リクエストは次のとおりです。</p><pre><code>PUT _query_rules/my-rules
{
  "rules": [
    {
      "rule_id": "pin-premiun-user",
      "type": "pinned",
      "criteria": [
        {
          "type": "gte",
          "metadata": "loyalty_level",
          "values": [
            80
          ]
        }
      ],
      "actions": {
        "docs": [
          {
            "_index": "products",
            "_id": "6"
          }
        ]
      }
    }
  ]
}</code></pre><p>ここで、クエリを実行するときに、メタデータに新しいパラメータ<strong>royality_level</strong>を含める必要があります。ルールの条件が満たされると、新しいドキュメントが結果の上部に表示されます。</p><p>たとえば、loyalty_level が 80 のクエリを送信する場合:</p><pre><code>POST /products/_search
{
  "retriever": {
    "rule": {
      "retriever": {
        "standard": {
          "query": {
            "match": {
              "name": "PlayStation"
            }
          }
        }
      },
      "match_criteria": {
        "query_string": "PlayStation",
        "loyalty_level": 80
      },
      "ruleset_ids": ["my-rules"]
    }
  }
}</code></pre><p>結果の上部にロイヤルティ ドキュメントが表示されます。</p><pre><code>{
  "took": 31,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 4,
      "relation": "eq"
    },
    "max_score": 1.7014122e+38,
    "hits": [
      {
        "_index": "products",
        "_id": "6",
        "_score": 1.7014122e+38,
        "_source": {
          "id": "6",
          "name": "PlayStation Plus Deluxe Card - 12 months",
          "category": "membership",
          "brand": "Sony",
          "price": 300
        }
      },
      {
        "_index": "products",
        "_id": "3",
        "_score": 0.5054567,
        "_source": {
          "id": "3",
          "name": "PlayStation 4 Camera",
          "category": "accessory",
          "brand": "Sony",
          "price": 200
        }
      },
      {
        "_index": "products",
        "_id": "1",
        "_score": 0.45618832,
        "_source": {
          "id": "1",
          "name": "PlayStation 4 Slim 1TB",
          "category": "console",
          "brand": "Sony",
          "price": 1200
        }
      },
      {
        "_index": "products",
        "_id": "4",
        "_score": 0.45618832,
        "_source": {
          "id": "4",
          "name": "PlayStation 4 VR Headset",
          "category": "accessory",
          "brand": "Sony",
          "price": 900
        }
      }
    ]
  }
}</code></pre><p>以下の場合、ロイヤルティ レベルが 70 であるため、ルールは満たされず、アイテムは上部に表示されません。</p><pre><code>POST /products/_search
{
  "retriever": {
    "rule": {
      "retriever": {
        "standard": {
          "query": {
            "match": {
              "name": "PlayStation"
            }
          }
        }
      },
      "match_criteria": {
        "query_string": "PlayStation",
        "loyalty_level": 70
      },
      "ruleset_ids": ["my-rules"]
    }
  }
}</code></pre><p>結果は次のとおりです。</p><pre><code>{
  "took": 7,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 4,
      "relation": "eq"
    },
    "max_score": 0.5054567,
    "hits": [
      {
        "_index": "products",
        "_id": "3",
        "_score": 0.5054567,
        "_source": {
          "id": "3",
          "name": "PlayStation 4 Camera",
          "category": "accessory",
          "brand": "Sony",
          "price": 200
        }
      },
      {
        "_index": "products",
        "_id": "1",
        "_score": 0.45618832,
        "_source": {
          "id": "1",
          "name": "PlayStation 4 Slim 1TB",
          "category": "console",
          "brand": "Sony",
          "price": 1200
        }
      },
      {
        "_index": "products",
        "_id": "4",
        "_score": 0.45618832,
        "_source": {
          "id": "4",
          "name": "PlayStation 4 VR Headset",
          "category": "accessory",
          "brand": "Sony",
          "price": 900
        }
      },
      {
        "_index": "products",
        "_id": "6",
        "_score": 0.3817649,
        "_source": {
          "id": "6",
          "name": "PlayStation Plus Deluxe Card - 12 months",
          "category": "membership",
          "brand": "Sony",
          "price": 300
        }
      }
    ]
  }
}</code></pre><h2>例: 即時除外</h2><p><strong>DualShock 4 ワイヤレス コントローラー (ID 2)</strong>が一時的に入手できず、販売できないとします。そのため、ビジネス チームは、ドキュメントを手動で削除したり、何らかのデータ処理が開始されるのを待ったりする代わりに、当面は検索結果からドキュメントを削除することにしました。</p><p>先ほど人気アイテムに適用したのと同様のプロセスを使用しますが、今回は<em>[Pinned]</em>ではなく<em>[Exclude]</em>を選択します。このルールは一種のブラックリストとして機能します。クエリが実行されるたびに除外が機能するように、条件を<strong>「常時」</strong>に変更します。</p><p>ルールは次のようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f10971e4f1bc9520105111adfa3a476581a27130-1600x623.png" style="width: 100%;" /><p>変更を適用するには、ルールとルールセットを保存します。同等の REST リクエストは次のとおりです。</p><pre><code>PUT _query_rules/my-rules
{
  "rules": [
    {
      "rule_id": "rule-6358",
      "type": "pinned",
      "criteria": [
        {
          "type": "always"
        }
      ],
      "actions": {
        "docs": [
          {
            "_index": "products",
            "_id": "2"
          }
        ]
      }
    }
  ]
}</code></pre><p>ここで、クエリを再度実行すると、以前のルールではアイテムをピン留めするはずだったにもかかわらず、アイテムが結果に表示されなくなっていることがわかります。これは、<strong>除外がピン留め結果よりも優先される</strong>ためです。</p><pre><code>{
 "took": 6,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 4,
     "relation": "eq"
   },
   "max_score": 2.205655,
   "hits": [
     {
       "_index": "products",
       "_id": "3",
       "_score": 2.205655,
       "_source": {
         "id": "3",
         "name": "PlayStation 4 Camera",
         "category": "accessory",
         "brand": "Sony",
         "price": 200
       }
     },
     {
       "_index": "products",
       "_id": "1",
       "_score": 1.9738505,
       "_source": {
         "id": "1",
         "name": "PlayStation 4 Slim 1TB",
         "category": "console",
         "brand": "Sony",
         "price": 1200
       }
     },
     {
       "_index": "products",
       "_id": "4",
       "_score": 1.9738505,
       "_source": {
         "id": "4",
         "name": "PlayStation 4 VR Headset",
         "category": "accessory",
         "brand": "Sony",
         "price": 900
       }
     },
     {
       "_index": "products",
       "_id": "5",
       "_score": 0.69247496,
       "_source": {
         "id": "5",
         "name": "Charging Station for DualShock 4",
         "category": "accessory",
         "brand": "Sony",
         "price": 80
       }
     }
   ]
 }
}</code></pre><h2>まとめ</h2><p><strong>クエリ ルールを</strong>使用すると、コードを変更することなく関連性を簡単に調整できます。新しい<strong>Kibana</strong> <strong>UI</strong>では、<strong> </strong>これらの変更を数秒で行うことができるため、お客様とビジネス チームは検索結果をより細かく制御できるようになります。</p><p>クエリ ルールは、電子商取引以外にも、サポート ポータルでトラブルシューティング ガイドを強調表示したり、ナレッジ ベースで重要な社内ドキュメントを表示したり、ニュース サイトで最新ニュースを宣伝したり、期限切れの求人やコンテンツの一覧を除外したりするなど、さまざまなシナリオで活用できます。ユーザーの役割や地域によって制限されたコンテンツを非表示にするなど、コンプライアンス ルールを適用することもできます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[MastraとElasticsearchを使用してセマンティックリコールを備えた知識エージェントを構築する]]></title>
            <description><![CDATA[メモリと情報検索用のベクトル ストアとして Mastra と Elasticsearch を使用して、セマンティック リコールを備えたナレッジ エージェントを構築する方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/knowledge-agent-semantic-recall-mastra-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/knowledge-agent-semantic-recall-mastra-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[JD Armada]]></dc:creator>
            <pubDate>Thu, 06 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b8d51c2726d5573385c9246a7821d12ade4f1b0e-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">コンテキスト エンジニアリングは</a>、信頼性の高い AI エージェントとアーキテクチャの構築においてますます重要になっています。モデルがどんどん良くなるにつれて、その有効性と信頼性はトレーニングされたデータに依存するのではなく、適切なコンテキストにどれだけ適切に基づいているかに依存するようになります。最も関連性の高い情報を適切なタイミングで取得して適用できるエージェントは、正確で信頼できる出力を生成する可能性がはるかに高くなります。</p><p>このブログでは、 <a href="https://mastra.ai/">Mastra</a>を使用して、Elasticsearch をメモリおよび検索バックエンドとして使用し、ユーザーの発言を記憶し、後で関連情報を思い出すことができるナレッジ エージェントを構築します。これと同じ概念を実際のユースケースに簡単に拡張できます。サポート エージェントが過去の会話や解決策を記憶し、特定のユーザーへの応答をカスタマイズしたり、以前のコンテキストに基づいてより迅速に解決策を提示したりできると考えてください。</p><p>ここから手順に従って、ステップごとに構築する方法を確認してください。迷ってしまったり、完成した例を実行したいだけの場合は、<a href="https://github.com/jdarmada/getting-started-mastra-elastic/tree/main">ここにある</a>リポジトリを確認してください。</p><h2>マストラとは何ですか？</h2><p>Mastra は、推論、メモリ、ツールの交換可能なパーツを備えた AI エージェントを構築するためのオープンソースの TypeScript フレームワークです。<a href="https://mastra.ai/docs/memory/semantic-recall">セマンティック リコール</a>機能により、エージェントはメッセージをベクター データベースに埋め込みとして保存することで、過去のやり取りを記憶して取り出すことができます。これにより、エージェントは長期的な会話のコンテキストと継続性を維持できます。Elasticsearch は効率的な高密度ベクトル検索をサポートしているため、この機能を有効にするのに最適なベクトル ストアです。セマンティックリコールがトリガーされると、エージェントは関連する過去のメッセージをモデルのコンテキストウィンドウに引き出し、モデルが取得したコンテキストを推論と応答の基礎として使用できるようにします。</p><h2>始めるために必要なもの</h2><ul><li>ノード v18+</li><li>Elasticsearch（バージョン8.15以降）</li><li>Elasticsearch APIキー</li><li><a href="https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key">OpenAI APIキー</a></li></ul><p>注: デモでは OpenAI プロバイダーを使用するため、これが必要になりますが、Mastra は他の AI SDK とコミュニティ モデル プロバイダーをサポートしているため、設定に応じて簡単に交換できます。</p><h2>Mastraプロジェクトの構築</h2><p>プロジェクトの足場を提供するために、Mastra の組み込み CLI を使用します。次のコマンドを実行します。</p><pre><code>npm create mastra@latest</code></pre><p>次のような一連のプロンプトが表示されます。</p><p>1. プロジェクトに名前を付けます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2b9fe559e0276140dd539e24f916a73c60870405-620x84.png" style="width: 100%;" /><p>2. このデフォルト設定を維持することもできますし、空白のままにしておくこともできます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e04729eb03bce8499e973e18c28642402340d0e5-852x68.png" style="width: 100%;" /><p>3. このプロジェクトでは、OpenAI が提供するモデルを使用します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a86596a469a71bdf8bd99cbaf528d0f0cf7272c0-436x222.png" style="width: 100%;" /><p>4. すべての環境変数を、後のステップで設定する `.env` ファイルに保存するため、「今はスキップ」オプションを選択します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/02b19ccc34af0bdacf52fd94b519d036540ca2e6-426x114.png" style="width: 100%;" /><p>5. このオプションをスキップすることもできます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b3fe63d19d270bc2e0de1dd92033bf8b26750819-990x208.png" style="width: 100%;" /><p>初期化が完了したら、次のステップに進むことができます。</p><h3>依存関係のインストール</h3><p>次に、いくつかの依存関係をインストールする必要があります。</p><pre><code>npm install ai @ai-sdk/openai @elastic/elasticsearch dotenv</code></pre><ul><li><code>ai</code> - JavaScript/TypeScript で AI モデル、プロンプト、ワークフローを管理するためのツールを提供するコア AI SDK パッケージ。Mastra は Vercel の<a href="https://ai-sdk.dev/">AI SDK</a>上に構築されているため、エージェントとのモデルのインタラクションを有効にするにはこの依存関係が必要です。</li><li><code>@ai-sdk/openai</code> - AI SDK を OpenAI モデル (GPT-4、GPT-4o など) に接続し、OpenAI API キーを使用した API 呼び出しを可能にするプラグイン。</li><li><code>@elastic/elasticsearch</code> - <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/javascript">Node.js 用の公式 Elasticsearch クライアント</a>、インデックス作成、検索、ベクター操作のために Elastic Cloud またはローカル クラスターに接続するために使用されます。</li><li><code>dotenv</code> - .envから環境変数を読み込みますファイルをprocess.envにコピーし、API キーや Elasticsearch エンドポイントなどの資格情報を安全に挿入できるようになります。</li></ul><h3>環境変数の設定</h3><p>プロジェクトのルート ディレクトリに<code>.env</code>ファイルが存在しない場合は作成します。あるいは、<a href="https://github.com/jdarmada/getting-started-mastra-elastic/blob/main/.env.example">リポジトリ</a>に提供されている例<code>.env</code>をコピーして名前を変更することもできます。このファイルでは、次の変数を追加できます。</p><pre><code>ELASTICSEARCH_ENDPOINT="your-endpoint-here"
ELASTICSEARCH_API_KEY="your-key-here"
OPENAI_API_KEY="your-key-here"</code></pre><p>これで基本的な設定は完了です。ここから、エージェントの構築とオーケストレーションを開始できます。さらに一歩進んで、Elasticsearch をストアおよびベクター検索レイヤーとして追加します。</p><h2>ベクターストアとしてElasticsearchを追加する</h2><p><code>stores</code>という新しいフォルダーを作成し、その中にこの<a href="https://github.com/jdarmada/getting-started-mastra-elastic/blob/main/src/mastra/stores/elastic-store.ts">ファイル</a>を追加します。Mastra と Elastic が公式の Elasticsearch ベクター ストア統合を出荷する前に、 <a href="https://github.com/abhiaiyer91">Abhi Aiyer</a> (Mastra CTO) が<code>ElasticVector</code>と呼ばれるこの初期のプロトタイプ クラスを共有しました。簡単に言えば、Mastra のメモリ抽象化を Elasticsearch の高密度ベクトル機能に接続することで、開発者はエージェントのベクトル データベースとして Elasticsearch を導入できるようになります。</p><p>統合の重要な部分を詳しく見てみましょう。</p><h3>Elasticsearchクライアントの取り込み</h3><p>このセクションでは、 <code>ElasticVector</code>クラスを定義し、標準デプロイメントとサーバーレスデプロイメントの両方をサポートする Elasticsearch クライアント接続を設定します。</p><pre><code>export interface ElasticVectorConfig extends ClientOptions {
    /**
     * Explicitly specify if connecting to Elasticsearch Serverless.
     * If not provided, will be auto-detected on first use.
     */
    isServerless?: boolean;
    
    /**
     * Maximum documents to count accurately when describing indices.
     * Higher values provide accurate counts but may impact performance on large indices.
     * 
     * @default 10000
     */
    maxCountAccuracy?: number;
}

export class ElasticVector extends MastraVector {
    private client: Client;
    private isServerless: boolean | undefined;
    private deploymentChecked: boolean = false;
    private readonly maxCountAccuracy: number;

    constructor(config: ElasticVectorConfig) {
        super();
        this.client = new Client(config);
        this.isServerless = config.isServerless;
        this.maxCountAccuracy = config.maxCountAccuracy ?? 10000;
    }
}</code></pre><ul><li><code>ElasticVectorConfig extends ClientOptions</code>: これにより、すべての Elasticsearch クライアント オプション ( <code>node</code> 、 <code>auth</code> 、 <code>requestTimeout</code>など) を継承し、カスタム プロパティを追加する新しい構成インターフェースが作成されます。つまり、ユーザーは、サーバーレス固有のオプションとともに、有効な Elasticsearch 構成を渡すことができるということです。</li><li><code>extends MastraVector</code>: これにより、 <code>ElasticVector</code> 、すべてのベクター ストア統合が準拠する共通インターフェースである Mastra の基本<code>MastraVector</code>クラスから継承できるようになります。これにより、エージェントの観点から見ると、Elasticsearch は他の Mastra ベクター バックエンドと同じように動作するようになります。</li><li><code>private client: Client</code>: これは、Elasticsearch JavaScript クライアントのインスタンスを保持するプライベート プロパティです。これにより、クラスはクラスターと直接通信できるようになります。</li><li><code>isServerless</code> および<code>deploymentChecked</code> : これらのプロパティは連携して、サーバーレスまたは標準の Elasticsearch デプロイメントに接続されているかどうかを検出し、キャッシュします。この検出は最初の使用時に自動的に行われますが、明示的に構成することもできます。</li><li><code>constructor(config: ClientOptions)</code>: このコンストラクターは、構成オブジェクト (Elasticsearch の資格情報とオプションのサーバーレス設定を含む) を受け取り、それを使用して<code>this.client = new Client(config)</code>行でクライアントを初期化します。</li><li><code>super()</code>: これは Mastra の基本コンストラクターを呼び出すため、ログ記録、検証ヘルパー、およびその他の内部フックを継承します。</li></ul><p>この時点で、Mastraは新しいベクターストアがあることを知っています。 <code>ElasticVector</code></p><h3>展開タイプの検出</h3><p>インデックスを作成する前に、アダプターは標準の Elasticsearch を使用しているか Elasticsearch Serverless を使用しているかを自動的に検出します。サーバーレス デプロイメントでは手動でのシャード構成が許可されないため、これは重要です。</p><pre><code>private async detectServerless(): Promise<boolean> {
    // Return cached result if already detected
    if (this.deploymentChecked) {
        return this.isServerless ?? false;
    }

    // Use explicit configuration if provided
    if (this.isServerless !== undefined) {
        this.deploymentChecked = true;
        this.logger?.info(
            `Using explicit deployment type: ${this.isServerless ? 'Serverless' : 'Standard'}`
        );
        return this.isServerless;
    }

    try {
        const info = await this.client.info();
        
        // Primary detection: build flavor (most reliable)
        const isBuildFlavorServerless = info.version?.build_flavor === 'serverless';
        
        // Secondary detection: tagline (fallback)
        const isTaglineServerless = info.tagline?.toLowerCase().includes('serverless') ?? false;
        
        this.isServerless = isBuildFlavorServerless || isTaglineServerless;
        this.deploymentChecked = true;
        
        this.logger?.info(
            `Auto-detected ${this.isServerless ? 'Serverless' : 'Standard'} Elasticsearch deployment`,
            { 
                buildFlavor: info.version?.build_flavor, 
                version: info.version?.number,
                detectionMethod: isBuildFlavorServerless ? 'build_flavor' : 'tagline'
            }
        );
        
        return this.isServerless;
    } catch (error) {
        this.logger?.warn(
            'Could not auto-detect deployment type, assuming Standard Elasticsearch. ' +
            'Set isServerless: true explicitly in config if using Serverless.',
            { error: error instanceof Error ? error.message : String(error) }
        );
        this.isServerless = false;
        this.deploymentChecked = true;
        return false;
    }
}</code></pre><p>何が起こっていますか：</p><ul><li>まず、構成で明示的に<code>isServerless</code>が設定されているかどうかを確認します (自動検出をスキップします)</li><li>Elasticsearch の<code>info()</code> API を呼び出してクラスター情報を取得します</li><li><code>build_flavor field</code>をチェックします (サーバーレス デプロイメントは<code>serverless</code>を返します)</li><li>ビルドフレーバーが利用できない場合はタグラインをチェックする</li><li>結果をキャッシュして、API 呼び出しの繰り返しを回避します。</li><li>検出に失敗した場合は標準展開をデフォルトとする</li></ul><p> 使用例:</p><pre><code>// Option 1: Auto-detect (recommended)
const vector = new ElasticVector({
    node: 'https://your-cluster.es.cloud',
    auth: { apiKey: 'your-api-key' }
});
// Detection happens automatically on first index operation

// Option 2: Explicit configuration (faster startup)
const vector = new ElasticVector({
    node: 'https://your-serverless.es.cloud',
    auth: { apiKey: 'your-api-key' },
    isServerless: true  // Skips auto-detection
});</code></pre><h3>Elasticsearchに「メモリ」ストアを作成する</h3><p>以下の関数は、埋め込みを保存するための Elasticsearch インデックスを設定します。インデックスがすでに存在するかどうかを確認します。そうでない場合は、埋め込みとカスタム類似度メトリックを格納するための<code>dense_vector</code>フィールドを含む以下のマッピングを使用して作成します。</p><p>注意すべき点:</p><ul><li><code>dimension</code>パラメータは各埋め込みベクトルの長さであり、使用している埋め込みモデルによって異なります。私たちの場合、サイズ<code>1536</code>のベクトルを出力する OpenAI の<code>text-embedding-3-small</code>モデルを使用して埋め込みを生成します。これをデフォルト値として使用します。</li><li>以下のマッピングで使用される<code>similarity</code>変数は、ヘルパー関数 c <code>onst similarity = this.mapMetricToSimilarity(metric)</code>から定義されます。この関数は、 <code>metric</code>パラメータの値を受け取り、選択された距離メトリックの Elasticsearch 互換キーワードに変換します。<ul><li>たとえば、Mastra では、ベクトルの類似性を表すために<code>cosine</code> 、 <code>euclidean</code> 、 <code>dotproduct</code>などの一般的な用語を使用します。メトリック<code>euclidean</code> Elasticsearch マッピングに直接渡すと、Elasticsearch はキーワード<code>l2_norm</code>がユークリッド距離を表すと想定するため、エラーが発生します。</li></ul></li><li>サーバーレス互換性: サーバーレスデプロイメントのシャードとレプリカの設定は Elasticsearch Serverless によって自動的に管理されるため、コードでは自動的に省略されます。</li></ul><pre><code>async createIndex(params: CreateIndexParams): Promise<void> {
    const { indexName, dimension = 1536, metric = 'cosine' } = params;

    try {
        const exists = await this.client.indices.exists({ index: indexName });

        if (exists) {
            try {
                await this.validateExistingIndex(indexName, dimension, metric);
                this.logger?.info(`Index "${indexName}" already exists and is valid`);
                return;
            } catch (validationError) {
                throw new Error(
                    `Index "${indexName}" exists but does not match the required configuration: ${
                        validationError instanceof Error ? validationError.message : String(validationError)
                    }`
                );
            }
        }

        const isServerless = await this.detectServerless();
        const similarity = this.mapMetricToSimilarity(metric);

        const indexConfig: any = {
            index: indexName,
            mappings: {
                properties: {
                    vector: {
                        type: 'dense_vector',
                        dims: dimension,
                        index: true,
                        similarity: similarity,
                    },
                    metadata: {
                        type: 'object',
                        enabled: true,
                        dynamic: true, // Allows flexible metadata structures
                    },
                },
            },
        };

        // Only configure shards/replicas for non-serverless deployments
        // Serverless manages infrastructure automatically
        if (!isServerless) {
            indexConfig.settings = {
                number_of_shards: 1,
                number_of_replicas: 0, // Increase for production HA deployments
            };
        }

        await this.client.indices.create(indexConfig);

        this.logger?.info(
            `Created ${isServerless ? 'Serverless' : 'Standard'} Elasticsearch index "${indexName}"`,
            { dimension, metric, similarity }
        );
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        this.logger?.error(`Failed to create index "${indexName}": ${errorMessage}`);
        throw new Error(`Failed to create index "${indexName}": ${errorMessage}`);
    }
}</code></pre><h3>やりとりの後に新しい記憶やメモを保存する</h3><p>この関数は、各インタラクションの後に生成された新しい埋め込みをメタデータとともに取得し、Elastic の<code>bulk</code> API を使用してそれらをインデックスに挿入または更新します。<code>bulk</code> API は複数の書き込み操作を 1 つのリクエストにグループ化します。このインデックス作成パフォーマンスの向上により、エージェントのメモリが増加し続けても更新の効率が維持されます。</p><pre><code>async upsert(params: UpsertVectorParams): Promise<string[]> {
    const { indexName, vectors, metadata = [], ids } = params;

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

        const operations = vectors.flatMap((vec, index) => [
            { index: { _index: indexName, _id: vectorIds[index] } },
            {
                vector: vec,
                metadata: metadata[index] || {},
            },
        ]);

        const response = await this.client.bulk({
            refresh: true,
            operations,
        });

        if (response.errors) {
            const erroredItems = response.items.filter((item: any) => item.index?.error);
            const erroredIds = erroredItems.map((item: any) => item.index?._id);
            const errorDetails = erroredItems.slice(0, 3).map((item: any) => ({
                id: item.index?._id,
                error: item.index?.error?.reason || item.index?.error,
                type: item.index?.error?.type
            }));
            
            const errorMessage = `Failed to upsert ${erroredIds.length}/${vectors.length} vectors`;
            console.error(`${errorMessage}. Sample errors:`, JSON.stringify(errorDetails, null, 2));
            this.logger?.error(errorMessage, { 
                failedCount: erroredIds.length, 
                totalCount: vectors.length,
                sampleErrors: errorDetails 
            });
            
            // Still return successfully inserted IDs
            const successfulIds = vectorIds.filter((id, idx) => 
                !erroredIds.includes(id)
            );
            
            if (successfulIds.length === 0) {
                throw new Error(`${errorMessage}. All operations failed. See logs for details.`);
            }
            
            return successfulIds;
        }

        this.logger?.info(`Successfully upserted ${vectors.length} vectors to "${indexName}"`);
        return vectorIds;
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        this.logger?.error(`Failed to upsert vectors to "${indexName}": ${errorMessage}`);
        throw new Error(`Failed to upsert vectors to "${indexName}": ${errorMessage}`);
    }
}</code></pre><h3>意味的想起のための類似ベクトルのクエリ</h3><p>この機能は、セマンティック リコール機能の中核です。エージェントはベクトル検索を使用して、インデックス内に保存されている類似の埋め込みを見つけます。</p><pre><code>async query(params: QueryVectorParams<any>): Promise<QueryResult[]> {
    const { indexName, queryVector, topK = 10, filter, includeVector = false } = params;

    try {
        const knnQuery: any = {
            field: 'vector',
            query_vector: queryVector,
            k: topK,
            num_candidates: Math.max(topK * 10, 100), // Search more candidates for better recall
        };

        // Apply metadata filters if provided
        if (filter) {
            knnQuery.filter = this.buildElasticFilter(filter);
        }

        const sourceFields = ['metadata'];
        if (includeVector) {
            sourceFields.push('vector');
        }

        const response = await this.client.search({
            index: indexName,
            knn: knnQuery,
            size: topK,
            _source: sourceFields,
        });

        const results = response.hits.hits.map((hit: any) => ({
            id: hit._id,
            score: hit._score || 0,
            metadata: hit._source?.metadata || {},
            vector: includeVector ? hit._source?.vector : undefined,
        }));

        this.logger?.debug(`Query returned ${results.length} results from "${indexName}"`);
        return results;
    } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        this.logger?.error(`Failed to query vectors from "${indexName}": ${errorMessage}`);
        throw new Error(`Failed to query vectors from "${indexName}": ${errorMessage}`);
    }
}</code></pre><p>内部構造:</p><ul><li>Elasticsearch の<code>knn</code> API を使用して<a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN</a> (k 近傍法) クエリを実行します。</li><li>入力クエリ ベクトルに類似する上位 K 個のベクトルを取得します。</li><li>オプションでメタデータ フィルターを適用して結果を絞り込む (例: 特定のカテゴリまたは時間範囲内のみを検索する)</li><li>ドキュメント ID、類似度スコア、保存されたメタデータを含む構造化された結果を返します。</li></ul><h2>知識エージェントの作成</h2><p><code>ElasticVector</code>統合を通じて Mastra と Elasticsearch の接続を確認したので、次は Knowledge Agent 自体を作成しましょう。</p><p>フォルダー<code>agents</code>内に、 <code>knowledge-agent.ts</code>というファイルを作成します。まず、環境変数を接続し、Elasticsearch クライアントを初期化します。</p><pre><code>import { Agent } from '@mastra/core/agent';
import { Memory } from '@mastra/memory';
import { openai } from '@ai-sdk/openai';
import { Client } from '@elastic/elasticsearch';
import { ElasticVector } from '../stores/elastic-store';
import dotenv from "dotenv";

dotenv.config();

const ELASTICSEARCH_ENDPOINT = process.env.ELASTICSEARCH_ENDPOINT;
const ELASTICSEARCH_API_KEY = process.env.ELASTICSEARCH_API_KEY;

//Error check for undefined credentials
if (!ELASTICSEARCH_ENDPOINT || !ELASTICSEARCH_API_KEY) {
  throw new Error('Missing Elasticsearch credentials');
}

//Check to see if a connection can be established
const testClient = new Client({
  node: ELASTICSEARCH_ENDPOINT,
  auth: { 
    apiKey: ELASTICSEARCH_API_KEY 
  },
});

try {
  await testClient.ping();
  console.log('Connected to Elasticsearch successfully');
} catch (error: unknown) {
  if (error instanceof Error) {
    console.error('Failed to connect to Elasticsearch:', error.message);
  } else {
    console.error('Failed to connect to Elasticsearch:', error);
  }
  process.exit(1);
}
//Initialize the Elasticsearch vector store
const vectorStore = new ElasticVector({
  node: ELASTICSEARCH_ENDPOINT,
  auth: {
    apiKey: ELASTICSEARCH_API_KEY,
  },
//Optional: Explicitly set to true if using Elasticsearch Serverless to skip auto-detection and improve startup time
//isServerless: true,
});</code></pre><p>ここでは、次の操作を行います。</p><ul><li><code>dotenv</code>を使用して、 <code>.env</code>ファイルから変数を読み込みます。</li><li>Elasticsearch の資格情報が正しく挿入されているかどうかを確認し、クライアントへの接続を正常に確立できるかどうかを確認します。</li><li>Elasticsearch エンドポイントと API キーを<code>ElasticVector</code>コンストラクターに渡して、先ほど定義したベクター ストアのインスタンスを作成します。</li><li>Elasticsearch Serverless を使用している場合は、オプションで<code>isServerless: true</code>を指定します。これにより、自動検出手順がスキップされ、起動時間が短縮されます。省略した場合、アダプタは最初の使用時にデプロイメント タイプを自動的に検出します。</li></ul><p>次に、Mastra の<code>Agent</code>クラスを使用してエージェントを定義します。</p><pre><code>export const knowledgeAgent = new Agent({
    name: 'KnowledgeAgent',
    instructions: 'You are a helpful knowledge assistant.',
    model: openai('gpt-4o'),
    memory: new Memory({

        vector: vectorStore,

        //embedder used to create embeddings for each message
        embedder: 'openai/text-embedding-3-small',

        //set semantic recall options
        options: {
            semanticRecall: {
                topK: 3, // retrieve 3 similar messages
                messageRange: 2, // include 2 messages before/after each match
                scope: 'resource',
            },
        },
    }),
});</code></pre><p>定義できるフィールドは次のとおりです。</p><ul><li><code>name</code> そして<code>instructions</code> : アイデンティティと主な機能を与えます。</li><li><code>model</code>: <code>@ai-sdk/openai</code>パッケージを通じて OpenAI の<code>gpt-4o</code>を使用しています。</li><li><code>memory</code>:<ul><li><code>vector</code>: Elasticsearch ストアを指すので、埋め込みはそこから保存され、取得されます。</li><li><code>embedder</code>: 埋め込みを生成するためにどのモデルを使用するか</li><li><code>semanticRecall</code> オプションによってリコールの動作が決まります。<ul><li><code>topK</code>: 意味的に類似したメッセージを取得する数。</li><li><code>messageRange</code>: 各マッチにどの程度の会話を含めるか。</li><li><code>scope</code>: メモリの境界を定義します。</li></ul></li></ul></li></ul><p>もうすぐ終わりです。新しく作成したエージェントを Mastra 構成に追加するだけです。<a href="http://index.ts/"><code>index.ts</code></a>というファイルで、ナレッジ エージェントをインポートし、 <code>agents</code>フィールドに挿入します。</p><pre><code>export const mastra = new Mastra({
  agents: { knowledgeAgent },
  storage: new LibSQLStore({
    // stores observability, scores, ... into memory storage, if it needs to persist, change to file:../mastra.db
    url: ":memory:",
  }),
  logger: new PinoLogger({
    name: 'Mastra',
    level: 'info',
  }),
  telemetry: {
    // Telemetry is deprecated and will be removed in the Nov 4th release
    enabled: false, 
  },
  observability: {
    // Enables DefaultExporter and CloudExporter for AI tracing
    default: { enabled: true }, 
  },
});</code></pre><p>その他のフィールドには以下が含まれます。</p><ul><li><code>storage</code>: これは、実行履歴、観測性メトリック、スコア、キャッシュのための Mastra の内部データ ストアです。Mastra ストレージの詳細については、<a href="https://mastra.ai/docs/server-db/storage">こちらを</a>ご覧ください。</li><li><code>logger</code>: Mastra は、軽量の構造化 JSON ロガーである<a href="https://github.com/pinojs/pino">Pino</a>を使用します。エージェントの開始と停止、ツールの呼び出しと結果、エラー、LLM 応答時間などのイベントをキャプチャします。</li><li><code>observability</code>: エージェントの AI トレースおよび実行の可視性を制御します。追跡対象:<ul><li>各推論ステップの開始/終了。</li><li>使用されたモデルまたはツール。</li><li>入力と出力。</li><li>スコアと評価</li></ul></li></ul><h3>Mastra Studioでエージェントをテストする</h3><p>おめでとうございます！ここまで到達したら、このエージェントを実行し、そのセマンティックリコール能力をテストする準備が整いました。幸いなことに、Mastra には組み込みのチャット UI が用意されているため、独自に構築する必要はありません。</p><p>Mastra 開発サーバーを起動するには、ターミナルを開いて次のコマンドを実行します。</p><pre><code>npm run dev</code></pre><p>サーバーの初期バンドルと起動が完了すると、Playground のアドレスが提供されるはずです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8b045f70008d26aec4d2e6b59d61085555b9c5b2-686x116.png" style="width: 100%;" /><p>このアドレスをブラウザに貼り付けると、Mastra Studio が表示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/69bc80fe8486edd9e0cf91d87b39f465aeb23111-1600x438.png" style="width: 100%;" /><p><code>knowledgeAgent</code>のオプションを選択してチャットを開始してください。</p><p>すべてが正しく接続されているかどうかを確認するための簡単なテストでは、次のような情報を入力します。「チームは、主にエンタープライズ契約の更新により、10 月の売上実績が 12% 増加したと発表しました。次のステップは、中規模市場の顧客へのリーチを拡大することです。」次に、新しいチャットを開始して、「次に重点を置く必要があると言った顧客セグメントはどれですか?」などの質問をします。ナレッジエージェントは、最初のチャットで提供した情報を思い出せるはずです。次のような応答が表示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/da8ebddad89874023ed440a8f1ad2cb04ed043f4-1070x288.png" style="width: 100%;" /><p>このような応答が表示された場合、エージェントが以前のメッセージを Elasticsearch に埋め込みとして正常に保存し、後でベクトル検索を使用して取得したことを意味します。</p><h3>エージェントの長期記憶ストアの検査</h3><p>Mastra Studio のエージェント構成の<code>memory</code>タブに移動します。これにより、エージェントが時間の経過とともに何を学習したかを確認できます。Elasticsearch に埋め込まれて保存されるすべてのメッセージ、応答、およびやり取りは、この長期メモリの一部になります。過去のやり取りを意味的に検索して、エージェントが以前に学習した思い出の情報やコンテキストをすぐに見つけることができます。これは本質的には、エージェントがセマンティックリコール中に使用するメカニズムと同じものですが、ここではそれを直接検査できます。以下の例では、「sales」という用語を検索し、sales に関連する内容を含むすべてのインタラクションを取得しています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3decaa0c332d288c5ae0b11c25f592c7d50c2f0f-1104x1320.png" style="width: 100%;" /><h2>まとめ</h2><p>Mastra と Elasticsearch を接続することで、コンテキスト エンジニアリングの重要なレイヤーであるメモリをエージェントに付与できます。セマンティックリコールを使用すると、エージェントは時間の経過とともにコンテキストを構築し、学習した内容に基づいて応答することができます。つまり、より正確で信頼性が高く、自然なやりとりが可能になります。</p><p>この早期の統合は単なる出発点にすぎません。ここで同じパターンを使用すると、過去のチケットを記憶しているサポートエージェント、関連ドキュメントを取得する内部ボット、会話の途中で顧客の詳細を思い出すことができる AI アシスタントなどが可能になります。当社は公式の Mastra 統合にも取り組んでおり、近い将来この組み合わせがさらにシームレスになる予定です。</p><p>次に何を構築するのか楽しみにしています。ぜひ試してみて、 <a href="https://mastra.ai/">Mastra</a>とそのメモリ機能を調べ、発見したことをコミュニティと自由に共有してください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch と SigLIP-2 による山頂のマルチモーダル探索 ]]></title>
            <description><![CDATA[SigLIP-2 埋め込みと Elasticsearch kNN ベクトル検索を使用して、テキストから画像、画像から画像へのマルチモーダル検索を実装する方法を学びます。プロジェクトの焦点: エベレスト トレッキングでアマ ダブラム山の山頂の写真を探す。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/multimodal-search-siglip-2-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/multimodal-search-siglip-2-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Navneet Kumar]]></dc:creator>
            <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ffcf93358a7c5dadcea82faf3de460bf060d003c-1600x1200.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>写真アルバムを意味で検索したいと思ったことはありませんか?「青いジャケットを着てベンチに座っている写真を見せてください」「エベレストの写真を見せてください」「日本酒と寿司」などのクエリを試してみてください。コーヒー（またはお好みの飲み物）を飲みながら、読み続けてください。このブログでは、マルチモーダル ハイブリッド検索アプリケーションの構築方法を紹介します。マルチモーダルとは、アプリが単語だけでなく、テキスト、画像、音声などさまざまな種類の入力を理解して検索できることを意味します。ハイブリッドとは、キーワード マッチング、kNN ベクトル検索、ジオフェンシングなどの技術を組み合わせて、より鮮明な結果を提供することを意味します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0ec6bbb45013846b59ee00d2bf73ee2182ee7392-1920x1080.gif" style="width: 100%;" /><p>これを実現するために、Google の SigLIP-2 を使用して画像とテキストの両方のベクトル埋め込みを生成し、Elasticsearch ベクトル データベースに保存します。クエリ時に、検索入力、テキストまたは画像を埋め込みに変換し、高速 kNN ベクトル検索を実行して結果を取得します。この設定により、効率的なテキストから画像への検索、画像から画像への検索が可能になります。Streamlit UI は、テキストベースの検索を行ってアルバムから一致する写真を検索して表示するだけでなく、アップロードされた画像から山頂を識別し、フォトアルバムでその山の他の写真を表示できるフロントエンドを提供することで、このプロジェクトを実現します。また、検索精度を向上させるために実行した手順や、実用的なヒントやコツについても説明します。さらに詳しく調べるために、 <a href="https://github.com/navneet83/multimodal-mountain-peak-search">GitHub リポジトリ</a>と<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">Colab ノートブック</a>を提供しています。</p><h2>始まり</h2><p>このブログ投稿は、エベレストベースキャンプトレッキングで撮ったアマダブラム山の写真を全部見せてほしいと私に頼んだ10歳の子供からインスピレーションを受けたものです。写真アルバムを精査しながら、私は他のいくつかの山頂を特定するよう求められましたが、そのうちのいくつかは名前がわかりませんでした。</p><p>それで、これは楽しいコンピューター ビジョン プロジェクトになるかもしれないというアイデアが浮かびました。私たちが達成したかったこと:</p><ul><li>山頂の写真を名前で検索する</li><li>画像から山頂の名前を推測し、写真アルバムで似たような山頂を見つける</li><li>概念クエリを機能させる（<em>人</em>、<em>川</em>、<em>祈りの旗など）</em></li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e9d0d720a9b565d5b749bdc915068852d4f157ad-1200x1600.png" style="width: 100%;" /><h2>ドリームチームを結成: SigLIP-2、Elasticsearch、Streamlit</h2><p>これを機能させるには、テキスト (「Ama Dablam」) と画像 (私のアルバムの写真) の両方を、意味のある比較が可能なベクトル、つまり同じベクトル空間に変換する必要があることがすぐに明らかになりました。これを実行すると、検索は単に「最も近いものを見つける」だけになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/20e6f8b7d4fa48414f407ec200adbe00ee28d517-1536x1024.png" style="width: 100%;" /><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><p>最近 Google がリリースした<a href="https://huggingface.co/blog/siglip2"><strong>SigLIP-2</strong></a>はここによく当てはまります。タスク固有のトレーニング (<strong>ゼロショット</strong>設定) なしで埋め込みを生成でき、ラベルのない写真や異なる名前と言語を持つピークなど、私たちのユースケースに適しています。テキストと画像のマッチングがトレーニングされているため、クエリ言語やスペルが異なっていても、トレッキング中の山の写真と短いテキストプロンプトは埋め込みとして近いものになります。</p><p>SigLIP-2 は、品質と速度のバランスが優れており、複数の入力解像度をサポートし、CPU と GPU の両方で実行されます。SigLIP-2 は、オリジナルの CLIP などの以前のモデルと比較して、屋外での写真撮影に対してより堅牢になるように設計されています。私たちのテストでは、SigLIP-2 は一貫して信頼できる結果を生成しました。また、サポートも非常に充実しており、このプロジェクトに最適な選択肢となっています。</p><p>次に、埋め込みとパワー検索を保存するためのベクトル データベースが必要です。画像埋め込みに対するコサイン kNN 検索をサポートするだけでなく、単一のクエリでジオフェンスとテキスト フィルターを適用することもサポートする必要があります。Elasticsearch はここで最適です。ベクトル (dense_vector フィールドの HNSW kNN) を非常に適切に処理し、テキスト、ベクトル、地理クエリを組み合わせたハイブリッド検索をサポートし、フィルタリングと並べ替えをすぐに使用できます。また、水平方向にも拡張できるため、数枚の写真から数千枚の写真まで簡単に拡張できます。公式の<a href="https://www.elastic.co/docs/reference/elasticsearch/clients/python">Elasticsearch Python クライアントは</a>、配管をシンプルに保ち、プロジェクトときれいに統合します。最後に、検索クエリを入力して結果を表示できる軽量のフロントエンドが必要です。簡単な Python ベースのデモには、Streamlit が最適です。ファイルのアップロード、レスポンシブな画像グリッド、並べ替えとジオフェンシングのためのドロップダウン メニューなど、必要な基本的な機能を提供します。簡単にクローンを作成してローカルで実行でき、Colab ノートブックでも動作します。</p><h2>実装</h2><h3>Elasticsearchのインデックス設計とインデックス戦略</h3><p>このプロジェクトでは、 <code>peaks_catalog</code>と<code>photos</code>の 2 つのインデックスを使用します。</p><h4>Peaks_catalogインデックス</h4><p>この索引は、エベレストベースキャンプトレッキング中に見える主要な山頂のコンパクトなカタログとして機能します。このインデックス内の各ドキュメントは、エベレスト山などの単一の山頂に対応しています。各山頂ドキュメントには、名前/エイリアス、オプションの緯度経度座標、および SigLIP-2 テキストプロンプト (+ オプションの参照画像) を組み合わせて構築された単一のプロトタイプ ベクトルが保存されます。</p><p><strong>インデックスマッピング:</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">分野</td><td style="border: 1px solid #ccc; padding: 4px;">タイプ</td><td style="border: 1px solid #ccc; padding: 4px;">例</td><td style="border: 1px solid #ccc; padding: 4px;">目的/注意事項</td><td style="border: 1px solid #ccc; padding: 4px;">ベクトル/インデックス</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">id</td><td style="border: 1px solid #ccc; padding: 4px;">キーワード</td><td style="border: 1px solid #ccc; padding: 4px;">アマ・ダブラム</td><td style="border: 1px solid #ccc; padding: 4px;">安定したスラッグ/ID</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">名前</td><td style="border: 1px solid #ccc; padding: 4px;">テキスト + キーワードサブフィールド</td><td style="border: 1px solid #ccc; padding: 4px;">["アマ・ダブラム"、"アマダブラム"]</td><td style="border: 1px solid #ccc; padding: 4px;">エイリアス/多言語名; 正確なフィルターのためのnames.raw</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ラトロン</td><td style="border: 1px solid #ccc; padding: 4px;">ジオポイント</td><td style="border: 1px solid #ccc; padding: 4px;">{"lat":27.8617,"lon":86.8614}</td><td style="border: 1px solid #ccc; padding: 4px;">緯度/経度の組み合わせによるピーク GPS 座標 (オプション)</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">高度m</td><td style="border: 1px solid #ccc; padding: 4px;">整数</td><td style="border: 1px solid #ccc; padding: 4px;">6812</td><td style="border: 1px solid #ccc; padding: 4px;">標高（オプション）</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">テキスト埋め込み</td><td style="border: 1px solid #ccc; padding: 4px;">dense_vector</td><td style="border: 1px solid #ccc; padding: 4px;">768</td><td style="border: 1px solid #ccc; padding: 4px;">このピークのブレンドプロトタイプ（プロンプトとオプションで1～3枚の参照画像）</td><td style="border: 1px solid #ccc; padding: 4px;">index:true、類似度:"cosine"、index_options: {type:"hnsw", m:16, ef_construction:128}</td>
                    </tr>
              </tbody>
            </table>
          <p>このインデックスは主に、画像から山頂を識別するなど、画像間の検索に使用されます。このインデックスは、テキストから画像への検索結果を強化するためにも使用されます。</p><p>要約すると、 <code>peaks_catalog</code>は「これは何の山ですか？」という質問を焦点を絞った最近傍問題に変換し、概念的理解を画像データの複雑さから効果的に分離します。</p><p><strong>peaks_catalog インデックスのインデックス戦略:</strong> EBC トレッキング中に見える最も目立つ山頂のリストを作成することから始めます。各山頂の地理的位置、名前、同義語、標高を<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/data/peaks.yaml">yaml ファイル</a>に保存します。次のステップは、各ピークの<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L351">埋め込みを生成し</a>、それを<code>text_embed</code>フィールドに保存することです。堅牢な埋め込みを生成するために、次の手法を使用します。</p><ul><li>以下を使用してテキスト プロトタイプを作成します。<ul><li>山の名前</li><li><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L301">プロンプト アンサンブル</a>(複数の異なるプロンプトを使用して同じ質問に答える)、例:<ul><li>「ネパール、ヒマラヤ山脈の山頂{name}の自然写真」</li><li>「クンブ地域の{name}マーク的な山頂、高山の風景」</li><li>「 {name}山頂、雪、岩だらけの尾根」</li></ul></li><li>オプションの<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L333">反概念</a>（SigLIP-2 に一致しないものを指示する）: 「絵画、イラスト、ポスター、地図、ロゴ」の小さなベクトルを減算して、実際の写真に偏向させます。</li></ul></li><li>ピークの参照画像が提供されている場合は、オプションで<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L388C13-L388C29">画像プロトタイプを作成します</a>。</li></ul><p>次に、<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L392">テキストと画像のプロトタイプをブレンドして</a>、最終的な埋め込みを生成します。最後に、ドキュメントはすべての必須フィールドで<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L396">インデックス化され</a>ます。</p><pre><code>def l2norm(v: np.ndarray) -> np.ndarray:
    return v / (np.linalg.norm(v) + 1e-12)
def compute_blended_peak_vec(
        emb: Siglip2,
        names: List[str],
        peak_id: str,
        peaks_images_root: str,
        alpha_text: float = 0.5,
        max_images: int = 3,
) -> Tuple[np.ndarray, int, int, List[str]]:
    """
    Build blended vector for a single peak.

    Returns:
      vec           : np.ndarray (L2-normalized)
      found_count   : number of reference images discovered
      used_count    : number of references used (<= max_images)
      used_filenames: list of filenames used (for logging)
    """
    # 1) TEXT vector
    tv = embed_text_blend(emb, names)

    # 2) IMAGE refs: prefer folder by id; fallback to slug of the primary name
    root = Path(peaks_images_root)
    candidates = [root / peak_id]
    if names:
        candidates.append(root / slugify(names[0]))

    all_refs: List[Path] = []
    for c in candidates:
        if c.exists() and c.is_dir():
            all_refs = list_ref_images(c)
            if all_refs:
                break

    found = len(all_refs)
    used_list = all_refs[:max_images] if (max_images and found > max_images) else all_refs
    used = len(used_list)

    img_v = embed_image_mean(emb, used_list) if used_list else None

    # 3) Blend TEXT and IMAGE vectors, clamp alpha to [0,1]
    a = max(0.0, min(1.0, float(alpha_text)))
    vec = l2norm(tv if img_v is None else (a * tv + (1.0 - a) * img_v)).astype("float32")
    return vec, found, used, [p.name for p in used_list]</code></pre><p><code>peaks_catalog</code>インデックスからのサンプル ドキュメント:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bc05fbd0c4f8d721d5170c28a3884a9eda80bb7d-1210x1132.png" style="width: 100%;" /><h4>写真インデックス</h4><p>このプライマリ インデックスには、アルバム内のすべての写真に関する詳細情報が保存されます。各ドキュメントは 1 枚の写真を表し、次の情報が含まれています。</p><ul><li>フォトアルバム内の写真への相対パス。これを使用して、一致する画像を表示したり、検索 UI に画像を読み込んだりできます。</li><li>写真のGPSと時間情報。</li><li>SigLIP-2 によって生成された画像エンコーディング用の密なベクトル。</li><li><code>predicted_peaks</code> ピーク名でフィルタリングできます。<strong>インデックスマッピング</strong></li></ul>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">分野</td><td style="border: 1px solid #ccc; padding: 4px;">タイプ</td><td style="border: 1px solid #ccc; padding: 4px;">例</td><td style="border: 1px solid #ccc; padding: 4px;">目的/注意事項</td><td style="border: 1px solid #ccc; padding: 4px;">ベクター / インデックス</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">パス</td><td style="border: 1px solid #ccc; padding: 4px;">キーワード</td><td style="border: 1px solid #ccc; padding: 4px;">データ/画像/IMG_1234.HEIC</td><td style="border: 1px solid #ccc; padding: 4px;">UI でサムネイル/フル画像を開く方法</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">クリップ画像</td><td style="border: 1px solid #ccc; padding: 4px;">dense_vector</td><td style="border: 1px solid #ccc; padding: 4px;">768</td><td style="border: 1px solid #ccc; padding: 4px;">SigLIP-2画像埋め込み</td><td style="border: 1px solid #ccc; padding: 4px;">index:true、類似度:"cosine"、index_options: {type:"hnsw", m:16, ef_construction:128}</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">予測ピーク</td><td style="border: 1px solid #ccc; padding: 4px;">キーワード</td><td style="border: 1px solid #ccc; padding: 4px;">["ama-dablam","pumori"]</td><td style="border: 1px solid #ccc; padding: 4px;">インデックス時の上位Kの推測（安価なUXフィルター/ファセット）</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">GPS</td><td style="border: 1px solid #ccc; padding: 4px;">ジオポイント</td><td style="border: 1px solid #ccc; padding: 4px;">{"lat":27.96,"lon":86.83}</td><td style="border: 1px solid #ccc; padding: 4px;">地理フィルターを有効にする</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ショット時間</td><td style="border: 1px solid #ccc; padding: 4px;">date</td><td style="border: 1px solid #ccc; padding: 4px;">2023年10月18日09:41:00Z</td><td style="border: 1px solid #ccc; padding: 4px;">撮影時間: 並べ替え/フィルター</td><td style="border: 1px solid #ccc; padding: 4px;">—</td>
                    </tr>
              </tbody>
            </table>
          <p><strong>写真インデックスのインデックス戦略:</strong>アルバム内の写真ごとに、次の操作を実行します。<br/><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L526">画像メタデータから</a>画像<code>shot_time</code>と<code>gps</code>情報を抽出します。</p><ul><li><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L511">SigLIP-2 画像埋め込み</a>: 画像をモデルに渡し、ベクトルを L2 正規化します。埋め込みを<code>clip_image</code>フィールドに保存します。</li><li><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L519">ピークを予測し</a>、 <code>predicted_peaks</code>フィールドに保存します。これを行うには、まず前の手順で生成された写真の画像ベクトルを取得し、次に<code>peaks_catalog</code>インデックスの text_embed フィールドに対して簡単な kNN 検索を実行します。上位 3 ～ 4 つのピークを保持し、残りは無視します。</li><li>画像名とパスの<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L509">ハッシュ</a>を実行して<code>_id</code>フィールドを計算します。これにより、複数回実行した後に重複が発生しなくなります。</li></ul><p>写真のすべてのフィールドを決定したら、<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L530">一括</a>インデックスを使用して写真ドキュメントを一括でインデックスします。</p><pre><code>def bulk_index_photos(
        es: Elasticsearch,
        images_root: str,
        photos_index: str = "photos",
        peaks_index: str = "peaks_catalog",
        topk_predicted: int = 5,
        batch_size: int = 200,
        refresh: str = "false",
) -> None:
    """Walk a folder of images, embed + enrich, and bulk index to Elasticsearch."""
    root = Path(images_root)
    if not root.exists():
        raise SystemExit(f"Images root not found: {images_root}")

    emb = Siglip2()
    batch: List[Dict[str, Any]] = []
    n_indexed = 0

    for p in iter_images(root):
        rel = relpath_within(root, p)
        _id = id_for_path(rel)

        # 1) Image embedding (and reuse it for predicted_peaks)
        try:
            with Image.open(p) as im:
                ivec = emb.image_vec(im.convert("RGB")).astype("float32")
        except (UnidentifiedImageError, OSError) as e:
            print(f"[skip] {rel} — cannot embed: {e}")
            continue

        # 2) Predict top-k peak names
        try:
            top_names = predict_peaks(es, ivec.tolist(), peaks_index=peaks_index, k=topk_predicted)
        except Exception as e:
            print(f"[warn] predict_peaks failed for {rel}: {e}")
            top_names = []

        # 3) EXIF enrichment (safe)
        gps = get_gps_decimal(str(p))
        shot = get_shot_time(str(p))

        # 4) Build doc and stage for bulk
        doc = {"path": rel, "clip_image": ivec.tolist(), "predicted_peaks": top_names}
        if gps:
            doc["gps"] = gps
        if shot:
            doc["shot_time"] = shot

        batch.append(
            {"_op_type": "index", "_index": photos_index, "_id": _id, "_source": doc}
        )

        # 5) Periodic flush
        if len(batch) >= batch_size:
            helpers.bulk(es, batch, refresh=refresh)
            n_indexed += len(batch)
            print(f"[photos] indexed {n_indexed} (last: {rel})")
            batch.clear()

    # Final flush
    if batch:
        helpers.bulk(es, batch, refresh=refresh)
        n_indexed += len(batch)
        print(f"[photos] indexed {n_indexed} total.")

    print("[done] photos indexing")</code></pre><p>写真インデックスからのサンプルドキュメント:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1dc1406ac2a97440b6804838795b3c2205c4c6b2-1080x1234.png" style="width: 100%;" /><p>要約すると、写真のインデックスは、アルバム内のすべての写真を格納する、高速でフィルタリング可能な kNN 対応のストアです。マッピングは意図的に最小限に抑えられており、すばやく取得し、きれいに表示し、結果を空間と時間で分割するのに十分な構造になっています。このインデックスは、両方の検索ユースケースに対応します。両方のインデックスを作成するための Python スクリプトは<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/create_indices.py">ここに</a>あります。</p><p>以下の Kibana マップの視覚化では、写真アルバムのドキュメントが緑のドットで表示され、 <code>peaks_catalog</code>インデックスの山頂が赤い三角形で表示されています。緑のドットはエベレスト ベース キャンプのトレッキング トレイルとぴったり一致しています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1c75d0ed0ce53d28a94bf2f47a354e25581d2baf-1600x1402.png" style="width: 100%;" /><h2>検索ユースケース</h2><p><strong>名前による検索（テキストから画像へ）：</strong>この機能により、ユーザーはテキスト クエリを使用して山頂の写真（さらには「祈りの旗」のような抽象的な概念）を見つけることができます。これを実現するために、テキスト入力は SigLIP-2 を使用して<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L87C5-L87C20">テキスト ベクトルに変換されます</a>。堅牢なテキストベクトル生成のために、 インデックスでテキスト埋め込みを作成するために使用したのと同じ戦略を採用しています。つまり、テキスト入力を小さな<code>peaks_catalog</code><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L100"> プロンプトアンサンブル</a> と<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104"> 組み合わせ</a><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L103"> 、小さな 反概念ベクトル</a> を減算し、<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104"> L2正規化</a> を適用して最終的なクエリベクトルを生成します。次に、 <code>photos.clip_image</code>フィールドで kNN<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L140">クエリを</a>実行し、コサイン類似度に基づいて一致する上位のピークを取得して、最も近い画像を見つけます。オプションとして、クエリの一部として地理および日付<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L152">フィルター</a>、および/または<code>photos.predicted_peaks</code>用語フィルターを適用することで、検索結果の関連性を高めることができます (以下のクエリ例を参照)。これにより、トレッキング中に実際には見えていない、似たような山頂を除外することができます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b5fac28ffdbedb820505365ca07df125cd01b939-946x370.png" style="width: 100%;" /><p><strong>ジオフィルターを使用した Elasticsearch クエリ:</strong></p><pre><code>POST photos/_search
{
  "knn": {
    "field": "clip_image",
    "query_vector": [ ... ],
    "k": 60,
    "num_candidates": 2000
  },
  "query": {
    "bool": {
      "filter": [
        { "geo_bounding_box": { "gps": { "top_left": "...", "bottom_right": "..." } } }
      ]
    }
  },
  "_source": ["path","predicted_peaks","gps","shot_time"]
}

Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}</code></pre><p><strong>画像による検索 (画像間):</strong>この機能を使用すると、写真内の山を識別し、写真アルバム内で同じ山の他の画像を見つけることができます。画像がアップロードされると、SigLIP-2 画像エンコーダーによって処理され、<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L228">画像ベクトル</a>が生成されます。次に、 <code>peaks_catalog.text_embed</code>フィールドで<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L234">kNN 検索を</a>実行し、最も一致するピーク名を特定します。次に、これらの一致するピーク名から<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L257">テキスト ベクトルが生成され</a>、写真インデックスに対して別の<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L263">kNN 検索が</a>実行され、対応する写真が検索されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3a3d5635bee7a222b95529dd7f9fbee016381610-1226x550.png" style="width: 100%;" /><p><strong>Elasticsearchクエリ:</strong></p><p>ステップ1: 一致するピーク名を見つける</p><pre><code>GET peaks_catalog/_search
{
 "knn": {
   "field": "text_embed",
   "query_vector": [...image-vector... ],
   "k": 3,
   "num_candidates": 500
 },
 "_source": [
   "id",
   "names",
   "latlon",
   "text_embed"
 ]
}


Response (first two documents):
{
 "took": 2,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 3,
     "relation": "eq"
   },
   "max_score": 0.58039916,
   "hits": [
     {
       "_index": "peaks_catalog",
       "_id": "pumori",
       "_score": 0.58039916,
       "_source": {
         "id": "pumori",
         "names": [
           "Pumori",
           "Pumo Ri"
         ],
         "latlon": {
           "lat": 28.01472,
           "lon": 86.82806
         },
         "text_embed": [
                  ... embeddings...
         ]
       }
     },
     {
       "_index": "peaks_catalog",
       "_id": "kyajo-ri",
       "_score": 0.57942784,
       "_source": {
         "id": "kyajo-ri",
         "names": [
           "Kyajo Ri",
           "Kyazo Ri"
         ],
         "latlon": {
           "lat": 27.909167,
           "lon": 86.673611
         },
         "text_embed": [
           ... embeddings...
         ]
       }
     }
   ]
 }
}</code></pre><p>ステップ 2: <code>photos</code>インデックスで検索を実行し、一致する画像を見つけます (テキストから画像への検索ユース ケースに示されているのと同じクエリ)。</p><pre><code>POST photos/_search
{
 "knn": {
   "field": "clip_image",
   "query_vector": [ ...image-vector... ],
   "k": 30,
   "num_candidates": 2000
 },
 "_source": [
   "path",
   "gps",
   "shot_time",
   "predicted_peaks",
   "clip_image"
 ],
 "query": {
   "bool": {
     "filter": [
       {
         "term": {
           "predicted_peaks": "Pumori"
         }
       }
     ]
   }
 }
}


Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}</code></pre><h2>流線型のUI</h2><p>すべてをまとめるために、両方の検索ユースケースを実行できるシンプルな Streamlit UI を作成しました。左側のレールには、チェックボックスとミニマップ/ジオフィルターが付いた、スクロール可能なピークのリスト（ <code>photos.predicted_peaks</code>から集約）が表示されます。上部には、<strong>名前による検索</strong>ボックスと<strong>写真アップロードからの識別</strong>ボタンがあります。中央のペインには、kNN スコア、予測ピーク バッジ、キャプチャ時間を表示するレスポンシブなサムネイル グリッドがあります。各画像には、フル解像度のプレビューを表示するための<strong>画像表示</strong>ボタンが含まれています。</p><p><strong>画像をアップロードして検索:</strong>ピークを予測し、写真アルバムから一致するピークを見つけます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/dca540cbf5279e6d6102c5a0c0351ddd4ac91cda-1600x1112.png" style="width: 100%;" /><p><strong>テキスト検索</strong>: アルバム内の一致するピークをテキストから検索します</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b1e8618db746cd49ea4962d3dc73031387b975dd-1600x1166.png" style="width: 100%;" /><h2>まとめ</h2><p><strong><em>アマ・ダブラムの</em></strong> <em>写真 だけ見せてもらえませんか ？ というところから始まりました。</em>小規模で実用的な<strong>マルチモーダル検索</strong>システムになりました。私たちは生のトレッキング写真を撮影し、それを<strong>SigLIP-2 埋め込み</strong>に変換し、 <strong>Elasticsearch</strong>を使用してベクトルに対して高速<strong>kNN を</strong>実行し、さらに単純な地理/時間フィルターを使用して<em>意味</em>によって適切な画像を浮かび上がらせました。途中で、私たちは 2 つのインデックス、つまり混合プロトタイプの小さな<code>peaks_catalog</code> (識別用) と、画像ベクトルと EXIF のスケーラブルな<code>photos</code>インデックス (検索用) で関心を分離しました。実用的かつ再現性があり、拡張も簡単です。</p><p>調整したい場合は、いくつかの設定を試すことができます。</p><ul><li><strong>クエリ時間の設定:</strong> <code>k</code> (返す近隣の数) と<code>num_candidates</code> (最終スコアリングの前に検索する範囲)。これらの設定については、<a href="https://www.elastic.co/search-labs/blog/elasticsearch-knn-and-num-candidates-strategies">こちらの</a>ブログで説明されています。</li><li><strong>インデックス時間の設定:</strong> <code>m</code> (グラフの接続性) および<code>ef_construction</code> (ビルド時間の精度とメモリ)。クエリの場合は、 <code>ef_search</code>も試してください。値が大きいほど、通常は、レイテンシを多少トレードオフして、リコール率が向上します。これらの設定の詳細については、<a href="https://www.elastic.co/search-labs/blog/hnsw-graph">このブログ</a>を参照してください。</li></ul><p>今後、<strong>マルチモーダル</strong>および<strong>多言語</strong>検索用のネイティブモデル/リランカーがElasticエコシステムにまもなく導入される予定です。これにより、画像/テキスト検索とハイブリッドランキングがさらに強化されるはずです<a href="https://ir.elastic.co/news/news-details/2025/Elastic-Completes-Acquisition-of-Jina-AI-a-Leader-in-Frontier-Models-for-Multimodal-and-Multilingual-Search/default.aspx?utm_source=chatgpt.com">。ir.elastic.co+1</a></p><p>これを自分で試してみたい場合は:</p><ul><li><strong>GitHub リポジトリ:</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search"><em>https://github.com/navneet83/multimodal-mountain-peak-search</em></a></li><li><strong>Colab クイックスタート:</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb</a></li></ul><p>これで私たちの旅は終わり、帰る時間になりました。これが役に立つことを願っています。これを壊した場合（または改善した場合）、何を変更したかをお聞かせください。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d324d1e1472f1bfbd8f25747f57bdeeb9c7f16b2-1600x1200.png" style="width: 100%;" />]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ハイブリッド検索再ランキングによる多言語埋め込みモデルの関連性の向上]]></title>
            <description><![CDATA[Cohere の再ランカーと Elasticsearch のハイブリッド検索を使用して、E5 多言語埋め込みモデルの検索結果の関連性を向上させる方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/multilingual-embedding-model-hybrid-search-reranking</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/multilingual-embedding-model-hybrid-search-reranking</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Quynh Nguyen]]></dc:creator>
            <pubDate>Mon, 03 Nov 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d341b04acecc8eeec321f5404e1643447ecc8526-720x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>はじめに</h2><p><a href="https://www.elastic.co/search-labs/blog/multilingual-embedding-model-deployment-elasticsearch">このシリーズの最後の部分</a>では、Elastic の事前トレーニング済み E5 モデル (および Hugging Face の他の多言語テキスト埋め込みモデル) のデプロイについて説明し、Elasticsearch と Kibana を使用してテキスト データから高密度のベクトル埋め込みを生成する方法について詳しく説明しました。このブログでは、これらの埋め込みの結果を調べ、多言語モデルを活用することの大きな利点を強調します。</p><p>インデックス<code>coco_multilingual</code>が作成されたので、検索を実行すると、参照用の「en」フィールドを含む複数の言語のドキュメントが表示されます。</p><pre><code># GET coco_multilingual/_search
    {
       "_index": "coco_multilingual",
       "_id": "WAiXQJYBgf6odR9bLohZ",
       "_score": 1,
       "_source": {
         "description": "Ein Parkmeßgerät auf einer Straße mit Autos",
         "en": "A row of parked cars sitting next to parking meters.",
         "language": "de",
         "vector_description": {...}
       }
     },
     . . .</code></pre><h2>英語で検索する</h2><p>英語で検索を実行して、どれくらいうまくいくか確認してみましょう。</p><pre><code>GET coco_multi/_search
{
"size": 10,
"_source": [
  "description", "language", "en"
],
"knn": {
  "field": "vector_description.predicted_value",
  "k": 10,
  "num_candidates": 100,
  "query_vector_builder": {
    "text_embedding": {
      "model_id": ".multilingual-e5-small_linux-x86_64_search",
      "model_text": "query: kitty"
    }
  }
}
}</code></pre><pre><code>{
       "_index": "coco_multi",
       "_id": "JQiXQJYBgf6odR9b6Yz0",
       "_score": 0.9334303,
       "_source": {
         "description": "Eine Katze, die in einem kleinen, gepackten Koffer sitzt.",
         "en": "A brown and white cat is in a suitcase.",
         "language": "de"
       }
     },
      {
       "_index": "coco_multi",
       "_id": "3AiXQJYBgf6odR9bFod6",
       "_score": 0.9281012,
       "_source": {
         "description": "Una bambina che tiene un gattino vicino a una recinzione blu.",
         "en": "A little girl holding a kitten next to a blue fence.",
         "language": "it"
       }
     },
     . . .</code></pre><p>ここでは、クエリは一見単純に見えますが、内部的にはすべての言語のすべてのドキュメントにわたって「kitty」という単語の数値埋め込みを検索しています。また、ベクトル検索を実行しているため、「kitty」に関連する可能性のあるすべての単語を意味的に検索できます。「cat」、「kitten」、「feline」、「gatto」（イタリア語）、「mèo」（ベトナム語）、고양이（韓国語）、猫（中国語）などです。その結果、クエリが英語であっても、他のすべての言語でコンテンツを検索できるようになります。たとえば、「a kitty l <code>ying on something</code>を検索すると、イタリア語、オランダ語、ベトナム語のドキュメントも返されます。効率について話しましょう!</p><h2>他の言語でコンテンツを検索する</h2><pre><code>GET coco_multi/_search
{  
 "size": 100,
 "_source": [
   "description", "language", "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 50,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: kitty lying on something"
     }
   }
 }
}</code></pre><pre><code>{
 "description": "A black kitten lays on her side beside remote controls.",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "en"
},
{
 "description": "un gattino sdraiato su un letto accanto ad alcuni telefoni ",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "it"
},
{
 "description": "eine Katze legt sich auf ein ausgestopftes Tier",
 "en": "a cat lays down on a stuffed animal",
 "language": "de"
},
{
 "description": "Một chú mèo con màu đen nằm nghiêng bên cạnh điều khiển từ xa.",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "vi"
}
. . .</code></pre><p>同様に、韓国語で「cat」（「고양이」）のキーワード検索を実行しても、意味のある結果が返されます。驚くべきことに、このインデックスには韓国語の文書がまったくありません。</p><pre><code>GET coco_multi/_search
{
 "size": 100,
 "_source": [
   "description", "language", "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 50,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: 고양이"
     }
   }
 }
}</code></pre><pre><code> {
       {
         "description": "eine Katze legt sich auf ein ausgestopftes Tier",
         "en": "a cat lays down on a stuffed animal",
         "language": "de"
       }
     },
     {
       {
         "description": "Một con chó và con mèo đang ngủ với nhau trên một chiếc ghế dài màu cam.",
         "en": "A dog and cat lying  together on an orange couch. ",
         "language": "vi"
       }
     },</code></pre><p>これが機能するのは、埋め込みモデルが意味を共有セマンティック空間で表現し、インデックス付けされたキャプションとは異なる言語でのクエリでも関連する画像を取得できるためです。</p><h2>ハイブリッド検索と再ランキングによる関連性の高い検索結果の向上</h2><p>関連する結果が期待どおりに表示されたことを嬉しく思います。しかし、現実の世界では、たとえば、最も関連性の高い上位 5 ～ 10 件の結果に絞り込む必要がある e コマースや RAG アプリケーションでは、再ランク付けモデルを使用して最も関連性の高い結果を優先することができます。</p><p>ここで、ベトナム語で「猫の色は何色ですか？」と尋ねるクエリを実行すると、多くの結果が表示されますが、上位 1 つまたは 2 つが最も関連性が高いとは限りません。</p><pre><code>GET coco_multi/_search
{
 "size": 20,
 "_source": [
   "description",
   "language",
   "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 20,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: con mèo màu gì?"
     }
   }
 }
}</code></pre><p>結果にはすべて「猫」または何らかの形の色が言及されています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/33e1e887dbbdd1066cfedc7375f5e3b46538529e-859x847.png" style="width: 100%;" /><p>では、それを改善しましょう!<a href="https://cohere.com/blog/rerank-3pt5">Cohere</a>の多言語再ランク付けモデルを統合して、質問に対応する推論を改善しましょう。</p><pre><code>PUT _inference/rerank/cohere_rerank
{
 "service": "cohere",
 "service_settings": {
   "api_key": "your_api_key",
   "model_id": "rerank-v3.5"
 },
 "task_settings": {
   "top_n": 10,
   "return_documents": true
 }
}


GET coco_multi/_search
{
"size": 10,
"_source": [
  "description",
  "language",
  "en"
],
"retriever": {
  "text_similarity_reranker": {
    "retriever": {
      "rrf": {
        "retrievers": [
          {
            "knn": {
              "field": "vector_description.predicted_value",
              "k": 50,
              "num_candidates": 100,
              "query_vector_builder": {
                "text_embedding": {
                  "model_id": ".multilingual-e5-small_linux-x86_64_search",
                  "model_text": "query: con mèo màu gì?" // English: What color is the cat?
                }
              }
            }
          }
        ],
        "rank_window_size": 100,
        "rank_constant": 0
      }
    },
    "field": "description",
    "inference_id": "cohere_rerank",
    "inference_text": "con mèo màu gì?"
  }
}
}</code></pre><pre><code> {
       "_index": "coco_multi",
       "_id": "rQiYQJYBgf6odR9bBYyH",
       "_score": 1.5501487,
       "_source": {
         "description": "Hai cái điện thoại được đặt trên một cái chăn cạnh một con mèo con màu đen.",
         "en": "A black kitten lays on her side beside remote controls.",
         "language": "vi"
       }
     },
     {
       "_index": "coco_multi",
       "_id": "swiXQJYBgf6odR9b04uf",
       "_score": 1.5427427,
       "_source": {
         "description": "Một con mèo sọc nâu nhìn vào máy quay.", // Real translation: A brown striped cat looks at the camera 
         "en": "This cat is sitting on a porch near a tire.",
         "language": "vi"
       }
     },</code></pre><p>これで、上位の結果により、アプリケーションは子猫の色が黒か茶色で縞模様であると自信を持って答えることができます。ここでさらに興味深いのは、ベクトル検索によって、元のデータセットの英語のキャプションの欠落が実際に検出されたことです。参照の英語翻訳ではその詳細が抜けていたにもかかわらず、茶色の縞模様の猫を見つけることができます。これがベクトル検索の威力です。</p><h2>まとめ</h2><p>このブログでは、多言語埋め込みモデルの有用性と、Elasticsearch を活用してモデルを統合し埋め込みを生成する方法、ハイブリッド検索と再ランク付けによって関連性と精度を効果的に向上させる方法について説明しました。独自の<a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">クラウド クラスターを作成し、</a>選択した言語とデータセットで<a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5">すぐに使用できる E5 モデルを使用して、多言語セマンティック検索を</a>試すことができます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch に多言語埋め込みモデルをデプロイする]]></title>
            <description><![CDATA[Elasticsearch でベクトル検索とクロスリンガル検索を行うために、e5 多言語埋め込みモデルをデプロイする方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/multilingual-embedding-model-deployment-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/multilingual-embedding-model-deployment-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Quynh Nguyen]]></dc:creator>
            <pubDate>Wed, 22 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8f2aa7bebb6b2f701e274ba7282273f9ab4abed6-720x432.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>はじめに</h2><p>世界中のユーザーがいる世界では、言語間情報検索 (CLIR) が非常に重要です。CLIR を使用すると、検索を 1 つの言語に限定するのではなく、<em>あらゆる</em>言語で情報を検索できるため、ユーザー エクスペリエンスが向上し、操作が効率化されます。電子商取引の顧客が自分の言語で商品を検索でき、事前にデータをローカライズする必要なく、適切な結果が表示されるグローバル市場を想像してみてください。あるいは、情報源が別の言語であっても、学術研究者がニュアンスや複雑さを含めて母国語で論文を検索できる場所です。</p><p>多言語テキスト埋め込みモデルを使用すると、まさにそれが実現できます。埋め込みは、テキストの意味を数値ベクトルとして表現する方法です。これらのベクトルは、同様の意味を持つテキストが高次元空間内で互いに近く配置されるように設計されています。多言語テキスト埋め込みモデルは、特に、異なる言語間で同じ意味を持つ単語やフレーズを同様のベクトル空間にマッピングするように設計されています。</p><p>オープンソースの Multilingual E5 のようなモデルは、多くの場合、対照学習などの手法を使用して、大量のテキスト データでトレーニングされます。このアプローチでは、モデルは類似した意味を持つテキストのペア (肯定的なペア) と類似しない意味を持つテキストのペア (否定的なペア) を区別することを学習します。モデルは、正のペア間の類似性が最大化され、負のペア間の類似性が最小化されるように、生成するベクトルを調整するようにトレーニングされます。多言語モデルの場合、このトレーニング データには、相互に翻訳された異なる言語のテキスト ペアが含まれており、モデルが複数の言語の共有表現空間を学習できるようになります。結果として得られる埋め込みは、クエリの言語に関係なく、テキスト埋め込み間の類似性を使用して関連するドキュメントを検索するクロスリンガル検索を含むさまざまな NLP タスクに使用できます。</p><h2>多言語ベクター検索のメリット</h2><ul><li><strong>ニュアンス</strong>: ベクター検索は、キーワードのマッチングを超えて、意味を捉えることに優れています。これは、文脈や言語の微妙なニュアンスを理解する必要があるタスクにとって非常に重要です。</li><li><strong>クロスリンガル理解</strong>: クエリとドキュメントが異なる語彙を使用している場合でも、言語間で効果的な情報検索を可能にします。</li><li><strong>関連性</strong>: クエリとドキュメント間の概念的な類似性に焦点を当てることで、より関連性の高い結果を提供します。</li></ul><p>たとえば、さまざまな国における「ソーシャル メディアが政治的議論に与える影響」を研究している学術研究者を考えてみましょう。ベクトル検索を使用すると、「l&#x27;impatto dei social media sul discorso politico」（イタリア語）または「ảnh hưởng của mạng xã hội đối với diễn ngôn chính trị」（ベトナム語）などのクエリを入力し、英語、スペイン語などで関連する論文を見つけることができます。他のインデックス付き言語。これは、ベクトル検索では、正確なキーワードを含む論文だけでなく、ソーシャル メディアの政治への影響の<em>概念</em>について議論している論文も特定されるためです。これにより、研究の幅と深さが大幅に向上します。</p><h2>使い始める</h2><p>Elasticsearch を使用して CLIR を設定する方法 (すぐに使用できる E5 モデルを使用) を次に示します。複数の言語の画像キャプションを含む<a href="https://huggingface.co/datasets/romrawinjp/multilingual-coco">オープンソースの多言語 COCO データセットを</a>使用して、2 種類の検索を視覚化します。</p><ol><li>1つの英語データセット上の他の言語のクエリと検索用語、および</li><li>複数の言語のドキュメントを含むデータセットに対する複数の言語でのクエリ。</li></ol><p>次に、ハイブリッド検索と再ランキングの力を活用して、検索結果をさらに改善します。</p><h2>要件</h2><ul><li>Python 3.6以上</li><li>Elasticsearch 8以上</li><li>Elasticsearch Pythonクライアント: pip install elasticsearch</li></ul><h2>データセット</h2><p><a href="https://huggingface.co/datasets/romrawinjp/multilingual-coco">COCO データセット</a>は、大規模なキャプション データセットです。データセット内の各画像には複数の異なる言語でキャプションが付けられており、言語ごとに複数の翻訳が利用可能です。デモンストレーションの目的で、各翻訳を個別のドキュメントとしてインデックス化し、参照用に最初の利用可能な英語の翻訳もインデックス化します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d4f0632529c71a22fbdecf21c9f4f0bb64b8e69c-1600x567.png" style="width: 100%;" /><h3>ステップ1: 多言語COCOデータセットをダウンロードする</h3><p>ブログを簡素化し、理解しやすくするために、ここでは、単純な API 呼び出しを使用して、restval の最初の 100 行をローカル JSON ファイルに読み込みます。あるいは、HuggingFace のライブラリ データセットを使用して、完全なデータセットまたはデータセットのサブセットを読み込むこともできます。</p><pre><code>import requests
import json
import os
### Download multilingual coco dataset into a json file (for easy viewing)
### Here we are retrieving first 100 rows for this example
### Alternatively, you can use `datasets` library from Hugging Face
url = "https://datasets-server.huggingface.co/rows?dataset=romrawinjp%2Fmultilingual-coco&config=default&split=restval&offset=0&length=100"
response = requests.get(url)


if response.status_code == 200:
   data = response.json()
   output_file = "multilingual_coco_sample.json" 
   ### Loading the downloaded content into a json file locally
   with open(output_file, "w", encoding="utf-8") as f:
       json.dump(data, f, indent=4, ensure_ascii=False)
   print(f"Data successfully downloaded and saved to {output_file}")
else:
   print(f"Failed to download data: {response.status_code}")
   print(response.text)</code></pre><p>データが JSON ファイルに正常に読み込まれると、次のようなものが表示されます。</p><p><code>Data successfully downloaded and saved to multilingual_coco_sample.json</code></p><h3>ステップ2: (Elasticsearchを起動) Elasticsearchでデータをインデックスする</h3><p>a) ローカル Elasticsearch サーバーを起動します。</p><p>b) Elasticsearch クライアントを起動します。</p><pre><code>from elasticsearch import Elasticsearch
from getpass import getpass


# Initialize Elasticsearch client
es = Elasticsearch(getpass("Host: "), api_key=getpass("API Key: "))


index_name = "coco"


# Create the index if it doesn't exist
if not es.indices.exists(index=index_name):
   es.indices.create(index=index_name, body=mapping)</code></pre><p>c) インデックスデータ</p><pre><code># Load the JSON data
with open('./multilingual_coco_sample.json', 'r') as f:
   data = json.load(f)


rows = data["rows"]
# List of languages to process
languages = ["en", "es", "de", "it", "vi", "th"]


# For each image, we will process each individual caption as its own document
bulk_data = []
for data in rows:
   row = data["row"]
   image = row.get("image")
   image_url = image["src"]


   # Process each language
   for lang in languages:
       # Skip if language not present in this row
       if lang not in row:
           continue


       # Get all descriptions for this language
 # along with first available English caption for reference
       descriptions = row[lang]
       first_eng_caption = row["en"][0]


       # Prepare bulk indexing data
       for description in descriptions:
           if description == "":
               continue
           # Add index operation
           bulk_data.append(
               {"index": {"_index": index_name}}
           )
           # Add document
           bulk_data.append({
               "language": lang,
               "description": description,
               "en": first_eng_caption,
               "image_url": image_url,
           })


# Perform bulk indexing
if bulk_data:
   try:
       response = es.bulk(operations=bulk_data)
       if response["errors"]:
           print("Some documents failed to index")
       else:
           print(f"Successfully bulk indexed {len(bulk_data)} documents")
   except Exception as e:
       print(f"Error during bulk indexing: {str(e)}")


print("Indexing complete!")</code></pre><p>データがインデックスされると、次のようなものが表示されます。</p><p><code>Successfully bulk indexed 4840 documents</code></p><p><code>Indexing complete!</code></p><h3>ステップ3: E5トレーニング済みモデルをデプロイする</h3><p>Kibanaで、スタック管理 &gt;<strong>トレーニング済みモデル</strong>ページに移動し、.multilingual-e5-small_linux-x86_64の<strong>デプロイを</strong>クリックします。オプション。この E5 モデルは、linux-x86_64 向けに最適化された小型の多言語モデルで、そのまま使用できます。「デプロイ」をクリックすると、デプロイ設定または vCPU 構成を調整できる画面が表示されます。簡単にするために、デフォルト オプションを使用し、適応型リソースを選択します。これにより、使用状況に応じてデプロイメントが自動的にスケーリングされます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/95cd8f352425d1db2d04b00c3c88d1e71d1ef19a-1600x440.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1599d99949dda8267acc58f400a403a3af5373ef-1600x655.png" style="width: 100%;" /><p>オプションとして、他のテキスト埋め込みモデルを使用することもできます。たとえば、BGE-M3 を使用するには、 <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/eland/machine-learning#ml-nlp-pytorch">Elastic の Eland Python クライアント</a>を使用して HuggingFace からモデルをインポートできます。</p><pre><code>export MODEL_ID="bge-m3"
export HUB_MODEL_ID="BAAI/bge-m3"
export CLOUD_ID={{CLOUD_ID}}
export ES_API_KEY={{API_KEY}}
docker run -it --rm docker.elastic.co/eland/eland \
eland_import_hub_model --cloud-id $CLOUD_ID --es-api-key $ES_API_KEY --hub-model-id $HUB_MODEL_ID --es-model-id $MODEL_ID --task-type text_embedding --start</code></pre><p>次に、「トレーニング済みモデル」ページに移動し、インポートしたモデルを必要な構成でデプロイします。</p><h3>ステップ4: デプロイされたモデルを使用して元のデータをベクトル化または埋め込みを作成する</h3><p>埋め込みを作成するには、まずテキストを取得して推論テキスト埋め込みモデルに通すことができる取り込みパイプラインを作成する必要があります。これは、Kibana のユーザー インターフェースまたは Elasticsearch の API を通じて実行できます。</p><p><strong>Kibana インターフェース経由でこれを行うには</strong>、トレーニング済みモデルをデプロイした後、 <strong>[テスト]</strong>ボタンをクリックします。これにより、生成された埋め込みをテストおよびプレビューできるようになります。<code>coco</code>の新しいデータビューを作成します<strong> </strong>インデックスを作成し、データ ビューを新しく作成した coco データ ビューに設定し、フィールドを<code>description</code>に設定します。これは、埋め込みを生成するフィールドだからです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cfe189e13dc118d325e7fb90bdace0c912e29f51-1088x1600.png" style="width: 100%;" /><p>それは素晴らしいですね！これで、取り込みパイプラインの作成に進み、元のドキュメントのインデックスを再作成し、パイプラインに渡して、埋め込みを含む新しいインデックスを作成できます。これを実現するには、 <strong>「パイプラインの作成」を</strong>クリックします。これにより、埋め込みの作成に必要なプロセッサが自動的に入力され、パイプラインの作成プロセスがガイドされます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1e043c1c3279b66fbdf19c06b41e76e613043998-1600x1126.png" style="width: 100%;" /><p>ウィザードでは、データの取り込みと処理中に障害を処理するために必要なプロセッサを自動的に入力することもできます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8804d31b4f869078e3b2245040bbb0ab1720a94a-1600x1084.png" style="width: 100%;" /><p>それでは、取り込みパイプラインを作成しましょう。パイプラインに<code>coco_e5</code>という名前を付けます。パイプラインが正常に作成されたら、ウィザードで元のインデックス付きデータを新しいインデックスに再インデックスすることで、パイプラインをすぐに使用して埋め込みを生成できます。プロセスを開始するには、 <strong>「再インデックス」</strong>をクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e34b1b18f5b24420d4581fe4d657c569926c2023-1600x1126.png" style="width: 100%;" /><h2>より複雑な構成の場合は、Elasticsearch API を使用できます。</h2><p>一部のモデルでは、モデルのトレーニング方法により、埋め込みを生成する前に実際の入力の先頭または末尾に特定のテキストを追加する必要がある場合があります。そうしないと、パフォーマンスが低下します。</p><p>たとえば、e5 の場合、モデルは入力テキストが「passage: {content of passage} 」に続くことを想定します。これを実現するために、取り込みパイプラインを活用しましょう。新しい取り込みパイプライン<strong>vectorize_descriptions を</strong>作成します。このパイプラインでは、新しい一時的な<code>temp_desc</code>フィールドを作成し、 <code>description</code>テキストの先頭に「passage:」を追加し、モデルで<code>temp_desc</code>を実行してテキスト埋め込みを生成し、 <code>temp_desc</code>を削除します。</p><pre><code>PUT _ingest/pipeline/vectorize_descriptions
{
"description": "Pipeline to run the descriptions text_field through our inference text embedding model",
"processors": [
 {
   "set": {
     "field": "temp_desc",
     "value": "passage: {{description}}"
   }
 },
 {
   "inference": {     
"field_map": {
       "temp_desc": "text_field"
     },
     "model_id": ".multilingual-e5-small_linux-x86_64_search",
     "target_field": "vector_description"
   }
 },
 {
   "remove": {
     "field": "temp_desc"
   }
 }
]
}</code></pre><p>さらに、生成されたベクトルに使用する<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-quantization">量子化の種類</a>を指定することもできます。デフォルトでは、Elasticsearch は<code>int8_hnsw</code>を使用しますが、ここでは各次元を 1 ビットの精度に削減する<a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Better Binary Quantization</a> (または<code>bqq_hnsw</code> ) を使用します。これにより、精度は犠牲になりますが、メモリ フットプリントが 96% (または 32 倍) 削減されます。後で再ランク付けを使用して精度の低下を改善することが分かっているため、この量子化タイプを選択しています。</p><p>そのためには、 <strong>coco_multi</strong>という名前の新しいインデックスを作成し、マッピングを指定します。ここでの魔法は vector_description フィールドにあり、そこで<strong>index_options</strong>のタイプを<strong>bbq_hnsw</strong>に指定します。</p><pre><code>PUT coco_multi
{
 "mappings": {
   "properties": {
     "description": {
       "type": "text"
     },
     "en": {
       "type": "text"
     },
     "image_url": {
       "type": "keyword"
     },
     "language": {
       "type": "keyword"
     },
     "vector_description.predicted_value": {
       "type": "dense_vector",
       "dims": 384,
       "index": "true",
       "similarity": "cosine",
       "index_options": {
         "type": "bbq_hnsw" 
       }
     }
   }
 }
}</code></pre><p>これで、説明フィールドを「ベクトル化」または埋め込みを作成する取り込みパイプラインを使用して、元のドキュメントを新しいインデックスに再インデックスできます。</p><pre><code>POST _reindex?wait_for_completion=false
{
 "source": {
   "index": "coco"
 },
 "dest": {
   "index": "coco_multilingual",
   "pipeline": "vectorize_descriptions"
 }
}</code></pre><p>以上です！Elasticsearch と Kibana を使用して多言語モデルを正常にデプロイし、Kibana ユーザー インターフェースまたは Elasticsearch API を使用して Elastic でデータにベクトル埋め込みを作成する方法を段階的に学習しました。このシリーズの第 2 部では、多言語モデルを使用した場合の結果とニュアンスについて説明します。その間、独自の<a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">クラウド クラスターを作成し</a>、選択した言語とデータセットで<a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5">すぐに使用できる E5 モデルを使用して多言語セマンティック検索を</a>試すことができます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elastic MCP サーバー: あらゆる AI エージェントに Agent Builder ツールを公開]]></title>
            <description><![CDATA[Agent Builder に組み込まれている Elastic MCP サーバーを使用して、プライベート データやカスタム ツールにアクセスできる AI エージェントを安全に拡張する方法を説明します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elastic-mcp-server-agent-builder-tools</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elastic-mcp-server-agent-builder-tools</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jedr Blaszyk, Joe McElroy]]></dc:creator>
            <pubDate>Mon, 20 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ef5153551a1d14833c7f512fede554d1dfb31553-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elastic Agent Builder は、Elasticsearch 内の独自のデータと深く統合されたツールとエージェントを作成するためのプラットフォームです。たとえば、内部ドキュメントに対してセマンティック検索を実行したり、観測ログを分析したり、セキュリティアラートを照会したりするツールを作成できます。</p><p>しかし、本当の魔法は、これらのカスタマイズされたデータ対応ツールを、ほとんどの時間を費やす環境に導入できたときに起こります。コード エディター エージェントが組織のプライベート ナレッジ ベースに安全にアクセスできたらどうなるでしょうか?</p><p>ここで、<strong>モデル コンテキスト プロトコル (MCP)</strong>が登場します。Elastic Agent Builder には、プラットフォーム内のツールへのアクセスを提供する組み込みの MCP サーバーが付属しています。</p><h2>Elastic Agent Builder MCP サーバーを使用する理由は何ですか?</h2><p>AI エージェントは非常に強力ですが、その知識は通常、トレーニングに使用されたデータとパブリック インターネット上でアクティブに検索できる情報に限定されます。彼らは、会社の内部設計ドキュメント、チーム固有のデプロイメント ランブック、またはアプリケーション ログの独自の構造については知りません。</p><p>課題は、AI アシスタントに必要な特殊なコンテキストを提供することです。これはまさに、MCP が解決するために設計された問題です。<strong>MCP は、AI モデルまたはエージェントが外部ツールを検出して使用できるようにするオープン スタンダードです。</strong></p><p>これを実現するために、Elastic Agent Builder は組み込みの MCP サーバーを通じてカスタム ツールをネイティブに公開します。つまり、 <strong>Cursor</strong> 、 <strong>VS Code</strong> 、 <strong>Claude Desktop</strong>などの MCP 対応クライアントを、Elastic Agent Builder で構築した特殊なデータ対応ツールに簡単に接続できるということです。</p><h2>MCP を使用する場合 (および使用しない場合)</h2><p>Elastic Agent Builder には、さまざまな統合パターンをサポートするためのいくつかのプロトコルが含まれています。適切なものを選択することが、効果的な AI ワークフローを構築する鍵となります。</p><ul><li><strong><a href="https://www.elastic.co/docs/solutions/search/agent-builder/mcp-server">MCP を使用して</a></strong> 、専用のツールで AI エージェント (<strong> Cursor</strong> や<strong> VS Code</strong> など) を拡張します。これは「独自のツールを持ち込む」アプローチであり、すでに使用しているアシスタントを強化して、プライベート データに安全にアクセスできるようにします。MCP サーバーを通じて公開されるのはツールのみで、Elastic のエージェントはそれとは別です。</li><li><strong><a href="https://www.elastic.co/docs/solutions/search/agent-builder/a2a-server">A2A プロトコルを</a> 使用すると</strong> 、完全なカスタム Elastic Agent が他の自律エージェント (<a href="https://www.elastic.co/search-labs/blog/a2a-protocol-elastic-agent-builder-gemini-enterprise"><strong> Google の Gemini Enterprise</strong></a> など) と連携できるようになります。これはエージェント間の委任用であり、各エージェントは問題を解決するためにピアとして機能します。</li><li><strong><a href="https://www.elastic.co/docs/solutions/search/agent-builder/kibana-api">カスタム</a></strong> アプリケーションを最初から構築するときに、完全なプログラム制御を行うには Agent Builder API を 使用します 。</li></ul><p>IDE を離れずに社内ドキュメントから回答を得たい開発者にとって、MCP は最適です。</p><h2>例: Agent Builder MCP サーバーを使用した Cursor のカスタム ツール</h2><p>私が日常的に使用している実際の例を見てみましょう。まず、社内のエンジニアリング ドキュメントをクロールして、 <code>elastic-dev-docs</code>という Elasticsearch インデックスにインデックス付けしました。Agent Builder で使用できる汎用の組み込みツールを使用することもできますが、この特定のナレッジベースを照会するための独自のカスタム ツールを作成します。</p><p>カスタム ツールを構築する理由はシンプルです。<strong>制御と精度です</strong>。このアプローチにより、 <code>elastic-dev-docs</code>インデックスに対して高速でセマンティックなクエリを直接実行できるようになります。どのインデックスをターゲットにするか、データをどのように取得するかを完全に制御できます。</p><p>ここで、このカスタム ナレッジ ベースを Cursor のような AI 搭載コード エディターで使用する方法を説明します。</p><h3>ステップ1: Agent Builderでカスタムナレッジベースツールを作成する</h3><p>まず、Agent Builder で新しいツールを作成します。明確で具体的なツールの説明は重要です。なぜなら、それが内部 Elastic Agent であれ、MCP 経由で接続する Cursor などの外部ツールであれ、あらゆる AI エージェントが適切なタスクのためにツールを検出し選択する方法だからです。</p><p>強力な説明は明確である必要があります。たとえば、「elastic-dev-docs インデックスでセマンティック検索を実行して、社内のエンジニアリング ドキュメント、ランブック、リリース手順を検索します。」</p><p>これで、ツールは特定のインデックスに対してセマンティック検索を実行するように構成されます。保存すると、すぐに利用できるようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1eea079908fdf7cc72dbe81abd07ff51601a43d4-1472x1600.png" style="width: 100%;" /><p>外部に接続する前に、UI で直接テストできます。<strong>[テスト]</strong>ボタンをクリックするだけで、パラメータを手動で入力し、LLM の動作をエミュレートして、結果を検査し、すべてが正しく動作していることを確認します。</p><h3>ステップ2: CursorをElastic MCPサーバーに接続する</h3><p>Elastic Agent Builder は、安全な MCP エンドポイントを介して利用可能なすべてのツールを自動的に公開します。固有のサーバー URL は、Kibana 内のツール UI で見つけることができます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ba137be30f0eaa7f028b96bd8af4e2779c3f8a33-1600x589.png" style="width: 100%;" /><p>Cursor に接続するには、この URL と認証用の Elastic API キー ( <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">ES API キーの作成方法を参照</a>) を構成ファイルに追加するだけです。認証には API キーを使用します。これにより、すべてのアクセス制御ルールを尊重し、ツールは付与した権限でのみ実行されるようになります。</p><p>カーソルの<code>~/.cursor/mcp.json</code>内の MCP 構成は次のようになります。</p><pre><code>{
  "mcpServers": {
    "elastic-agent-builder": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://your-kibana.kb.company.io/api/agent_builder/mcp",
        "--header",
        "Authorization:${AUTH_HEADER}"
      ],
      "env": {
        "AUTH_HEADER": "ApiKey <ELASTIC_API_KEY>"
      }
    }
  }
}</code></pre><p>設定が保存されると、Cursor で Elastic Agent Builder MCP サーバー ツールが利用可能になります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d302c6d3609fbf14fd40e21b9e69e567bf12553f-1600x1002.png" style="width: 100%;" /><h3>ステップ 3: どんどん質問しましょう!</h3><p>接続が確立されると、カーソル エージェントはカスタム ツールを呼び出して質問に答えたり、コード生成プロセスをガイドしたりできるようになります。</p><p>具体的な質問をしてみましょう。</p><p><em>「Elastic Search org のエンジニアリング内部ドキュメントからクローラー サービスをリリースするための手順を参照する」</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/14f572730203c23615bb9dd38234bcb3b0f81155-1600x1468.png" style="width: 100%;" /><p>舞台裏では魔法が起こります:</p><ol><li>カーソルエージェントはあなたの質問に最もよく答える方法を決定し、 <code>engineering_documentation_internal_search</code></li><li>自然言語クエリでツールを呼び出す</li><li>このツールは、 <code>elastic-dev-docs</code>インデックスに対してセマンティック検索を実行し、最も関連性の高い最新の手順を返します。</li></ol><p>コード エディターを離れることなく、社内ドキュメントに基づいた正確で信頼できる回答が得られます。体験はシームレスかつ強力です。</p><h2>あなたの番です</h2><p>ここでは、Elastic Agent Builder に組み込まれている MCP サーバーを使用して、プライベート データへの安全なアクセスを備えた AI アシスタントを拡張する方法を説明しました。モデルを本当に役立つものにするためには、独自の情報に基づいてモデルを構築することが鍵となります。</p><p>要約すると、主要な手順について説明しました。</p><ul><li>ニーズに合った適切なプロトコルを選択する (MCP)。</li><li>カスタム ナレッジ ベース ツールを構築します。</li><li>そのツールを Cursor などの IDE アシスタントに接続します。</li></ul><p>エージェントとツールを最も重要なコンテキストから切り離す必要がなくなりました。このガイドがより効果的でデータを考慮したワークフローの作成に役立つことを願っています。楽しい建築を！</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ユーザー行動データに基づく判断リストを使用して Elasticsearch で LTR モデルをトレーニングする]]></title>
            <description><![CDATA[UBI データを使用して判断リストを作成し、Elasticsearch での Learning to Rank (LTR) モデルのトレーニングを自動化する方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/training-learning-to-rank-models-elasticsearch-ubi-data</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/training-learning-to-rank-models-elasticsearch-ubi-data</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Alexander Dávila]]></dc:creator>
            <pubDate>Wed, 15 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/762bf09c28829d626d42c2cfadc719e1dd618d1b-1536x1024.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr"><strong><em>ランク付け学習</em></strong></a>モデルを使用する際の大きな課題は、モデルをトレーニングするための高品質の<a href="https://www.elastic.co/search-labs/blog/judgment-lists"><strong><em>判断リスト</em></strong></a>を作成することです。従来、このプロセスでは、クエリとドキュメントの関連性を<strong><em>手動で</em></strong>評価し、それぞれにグレードを割り当てていました。これは、拡張性が低く、維持が困難な、時間のかかるプロセスです (数百のエントリを含むリストを手動で更新する必要があることを想像してください)。</p><p>さて、検索アプリケーションでの実際のユーザーインタラクションを使用してこのトレーニングデータを作成できたらどうなるでしょうか?<a href="https://www.elastic.co/search-labs/blog/elasticsearch-plugin-user-behavior-insights"><strong><em>UBI</em></strong></a>データを使用すると、まさにそれが実現できます。検索、クリック、その他のインタラクションをキャプチャして使用し、判断リストを生成できる自動システムを作成します。このプロセスは、手動による操作よりもはるかに簡単に拡張および繰り返すことができ、より良い結果が得られる傾向があります。このブログでは、Elasticsearch に保存されている UBI データをクエリして意味のある信号を計算し、 <a href="https://www.elastic.co/search-labs/blog/elasticsearch-learning-to-rank-introduction"><strong><em>LTR</em></strong></a>モデルのトレーニング データセットを生成する方法について説明します。</p><p><strong><em>完全な実験は<a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog.git"> ここで</a> ご覧いただけます 。</em></strong></p><h2>UBIデータがLTRモデルのトレーニングに役立つ理由</h2><p>UBI データには、手動による注釈に比べていくつかの利点があります。</p><ul><li><strong>量:</strong> UBI データは実際のやり取りから得られるため、手動で生成できるよりもはるかに多くのデータを収集できます。もちろん、このデータを生成するのに十分なトラフィックがあることを前提としています。</li><li><strong>実際のユーザーの意図:</strong>従来、手動の判断リストは、利用可能なデータの専門家による評価から作成されます。一方、UBI データは実際のユーザー行動を反映しています。これは、何が関連しているべきかという理論的な仮定ではなく、ユーザーが実際にコンテンツとやりとりして価値を見出す方法に基づいているため、検索システムの精度を向上させる、より優れたトレーニング データを生成できることを意味します。</li><li><strong>継続的な更新:</strong>判断リストは時間の経過とともに更新する必要があります。UBI データから作成すれば、最新のデータが得られ、判断リストが更新されます。</li><li><strong>コスト効率:</strong>判断リストを手動で作成するオーバーヘッドがないため、プロセスを何度でも効率的に繰り返すことができます。</li><li><strong>自然なクエリ分布</strong>: UBI データは実際のユーザー クエリを表し、より深い変化を促すことができます。たとえば、ユーザーはシステム内で検索する際に自然言語を使用しているでしょうか?もしそうなら、セマンティック検索またはハイブリッド検索アプローチを実装する必要があるかもしれません。</li></ul><p>ただし、いくつかの警告も伴います。</p><ul><li><strong>バイアスの増幅:</strong>人気のあるコンテンツは、露出が増えるため、クリックされる可能性が高くなります。そのため、人気のあるアイテムが強調され、より良い選択肢が埋もれてしまう可能性があります。</li><li><strong>カバレッジが不完全:</strong>新しいコンテンツにはインタラクションがないため、結果の上位に表示されることは難しい可能性があります。まれなクエリでは、意味のあるトレーニング データを作成するのに十分なデータ ポイントが不足している場合もあります。</li><li><strong>季節的な変動:</strong>ユーザーの行動が時間の経過とともに劇的に変化することが予想される場合、履歴データからは、どのような結果が適切であるかについてあまり情報が得られない可能性があります。</li><li><strong>タスクの曖昧さ:</strong>クリックしても、ユーザーが探していたものが見つかるとは限らない。</li></ul><h2>成績計算</h2><h3>LTRトレーニングのグレード</h3><p>LTR モデルをトレーニングするには、ドキュメントがクエリにどの程度関連しているかを数値で表現する必要があります。私たちの実装では、この数値は 0.0 から 5.0+ までの連続したスコアであり、スコアが高いほど関連性が高くなります。</p><p>この評価システムがどのように機能するかを示すために、手動で作成された次の例を考えてみましょう。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">クエリ</td><td style="border: 1px solid #ccc; padding: 4px;">文書の内容</td><td style="border: 1px solid #ccc; padding: 4px;">学年</td><td style="border: 1px solid #ccc; padding: 4px;">説明</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">「最高のピザレシピ」</td><td style="border: 1px solid #ccc; padding: 4px;">「本格的なイタリアンピザ生地のレシピ（写真付きステップバイステップ）」</td><td style="border: 1px solid #ccc; padding: 4px;">4.0</td><td style="border: 1px solid #ccc; padding: 4px;">関連性が高く、まさにユーザーが探しているもの</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">「最高のピザレシピ」</td><td style="border: 1px solid #ccc; padding: 4px;">「イタリアのピザの歴史」</td><td style="border: 1px solid #ccc; padding: 4px;">1.0</td><td style="border: 1px solid #ccc; padding: 4px;">ピザに関する内容ですが、レシピではありません</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">「最高のピザレシピ」</td><td style="border: 1px solid #ccc; padding: 4px;">「初心者向け15分でできる簡単ピザレシピ」</td><td style="border: 1px solid #ccc; padding: 4px;">3.0</td><td style="border: 1px solid #ccc; padding: 4px;">関連性があり、良い結果ですが、「最高」のレシピとは言えないかもしれません。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">「最高のピザレシピ」</td><td style="border: 1px solid #ccc; padding: 4px;">「車のメンテナンスガイド」</td><td style="border: 1px solid #ccc; padding: 4px;">0.0</td><td style="border: 1px solid #ccc; padding: 4px;">全く関係ありません。クエリとは全く関係ありません。</td>
                    </tr>
              </tbody>
            </table>
          <p>ここからわかるように、グレードは、ドキュメントが「最高のピザのレシピ」というサンプルクエリにどれだけ関連しているかを数値で表したものです。これらのスコアを使用して、LTR モデルはどのドキュメントを結果の上位に表示する必要があるかを学習できます。</p><p>成績の計算方法は、トレーニング データセットの中核です。これを行うには<a href="https://www.elastic.co/search-labs/blog/judgment-lists">複数のアプローチ</a>があり、それぞれに長所と短所があります。たとえば、関連性がある場合は 1、関連性がない場合は 0 というバイナリ スコアを割り当てたり、クエリごとに結果のドキュメントのクリック数をカウントしたりすることもできます。</p><p>このブログ投稿では、<strong><em>ユーザーの行動を入力として考慮し、グレード番号を出力として計算する、</em></strong>異なるアプローチを使用します。また、ドキュメントの関連性に関係なく、検索結果の上位の方がクリックされる傾向があるという事実から生じる可能性のあるバイアスも修正します。</p><h2>成績の計算 - COECアルゴリズム</h2><p>COEC ( <a href="https://www.wsdm-conference.org/2010/proceedings/docs/p351.pdf">Clicks over Expected Clicks</a> ) アルゴリズムは、ユーザーのクリックから判断グレードを計算する手法です。前述したように、ユーザーは、ドキュメントがクエリに最も関連していない場合でも、上位に表示された結果をクリックする傾向があります。これは、<a href="https://eugeneyan.com/writing/position-bias/">ポジション バイアス</a>と呼ばれます。COEC アルゴリズムを使用する際の基本的な考え方は、すべてのクリックが同等に重要であるわけではないということです。つまり、位置 10 のドキュメントをクリックすると、位置 1 のドキュメントをクリックするよりも、そのドキュメントがクエリとの関連性がはるかに高いことを示します。COEC アルゴリズムに関する研究論文 (上記リンク) を引用します。</p><p><em>「検索結果や広告のクリック率（CTR）は、検索結果の順位によって大幅に低下することがよく知られています。」</em></p><p>ポジションバイアスの詳細については、<a href="https://www.researchgate.net/publication/200110550_An_experimental_comparison_of_click_position-bias_models">こちらを</a>ご覧ください。</p><p>COEC アルゴリズムを使用してこれに対処するには、次の手順に従います。</p><p><strong>1. 位置のベースラインを確立する:</strong>検索位置ごとに 1 から 10 までのクリック率 (CTR) を計算します。つまり、通常、ユーザーの何パーセントが位置 1、位置 2 などをクリックするかを決定します。このステップでは、ユーザーの自然な位置の偏りを捉えます。CTR は次のように計算します。</p><div class="katex">CTRp=CpIp</div><p>どこ：</p><p>p = 位置。1から10まで<br/>Cp = すべてのクエリにおける位置pでの合計クリック数（任意のドキュメント）<br/> Ip = 総表示回数: すべてのクエリで、任意のドキュメントが位置 p に表示された回数</p><p>ここでは、上位の順位の方がクリック数が多くなると予想されます。</p><p><strong>2.予想クリック数（EC）を計算する</strong>:</p><p>この指標は、ドキュメントが表示された位置とその位置のCTRに基づいて、ドキュメントが「受け取るべき」クリック数を決定します。ECは次の方法で計算します。</p><div class="katex">EC (for a document) = qQdCTRpos(d,q)</div><p>どこ：</p><p>Qd = 文書dが出現したすべてのクエリ<br/>pos(d,q) = クエリqの結果における文書dの位置</p><p>3.<strong>実際のクリック数をカウントする:</strong>ドキュメントが表示されたすべてのクエリでドキュメントが受け取った実際の合計クリック数をカウントします。以降、 <strong>A(d) と呼びます。</strong></p><p>4. <strong>COECスコアを計算します。</strong>これは、実際のクリック数（A(d)）と予想クリック数（EC(d)）の比率です。</p><div class="katex">COEC = A(d)EC(d)</div><p>このメトリックは、次のように位置バイアスを正規化します。</p><ul><li>スコア 1.0 は、ドキュメントが表示された位置に応じて、期待どおりに実行されたことを意味します。</li><li>スコアが 1.0 を超える場合、ドキュメントの位置を見ると予想よりもパフォーマンスが優れていることを意味します。したがって、このドキュメントはクエリに対してより関連性があります。</li><li>スコアが 1.0 未満の場合、ドキュメントの位置から判断すると、予想よりもパフォーマンスが悪かったことを意味します。したがって、このドキュメントはクエリとの関連性が低くなります。</li></ul><p><strong><em>最終結果は、検索システムとの実際のやりとりから抽出された位置ベースの期待を考慮して、ユーザーが探しているものを捉えたグレード番号になります。</em></strong></p><h2>技術的な実装</h2><p>LTR モデルをトレーニングするための判断リストを作成するスクリプトを作成します。</p><p>このスクリプトの入力は、Elastic でインデックス化された UBI データ (クエリとイベント) です。</p><p>出力は、COEC アルゴリズムを使用してこれらの UBI ドキュメントから生成された CSV ファイル内の判断リストです。この判断リストを<a href="https://www.elastic.co/search-labs/blog/elasticsearch-learning-to-rank-introduction">Eland</a>で使用すると、関連する特徴を抽出し、LTR モデルをトレーニングできます。</p><h3>クイックスタート</h3><p>このブログのサンプル データから判断リストを生成するには、次の手順に従います。</p><p>1. リポジトリをクローンします。</p><pre><code>git clone https://github.com/Alex1795/elastic-ltr-judgement_list-blog.git  
cd elastic-ltr-judgement_list-blog</code></pre><p>2. 必要なライブラリをインストールする</p><p>このスクリプトには、次のライブラリが必要です。</p><ul><li><em>pandas</em> : 判定リストを保存する</li><li><em>elasticsearch</em> : ElasticデプロイメントからUBIデータを取得する</li></ul><p>Python 3.11も必要です</p><pre><code>pip install -r requirements.txt</code></pre><p>3. <a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog/blob/main/.env-example">.envファイル</a>でElasticデプロイメントの環境変数を更新します。</p><ul><li>ES_ホスト</li><li>API_キー</li></ul><p>環境変数を追加するには、次を使用します。</p><pre><code>source .env</code></pre><p>4. ubi_queries、ubi_events インデックスを作成し、サンプル データをアップロードします。setup.py ファイルを実行します。</p><pre><code>python setup.py</code></pre><p>5. Python スクリプトを実行します。</p><pre><code>python judgement_list-generator.py</code></pre><p>これらの手順に従うと、次のような judgement_list.csv という新しいファイルが表示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2531090131ac9fe3e4e1d79de9d156fc47a7825a-782x531.png" style="width: 100%;" /><p>このスクリプトは、以下に示す<strong>calculate_relevance_grade()</strong>関数を使用して、前に説明した COEC アルゴリズムを適用して成績を計算します。</p><h2>データアーキテクチャ</h2><h3>Ubiクエリ</h3><p>UBI クエリ インデックスには、検索システムで実行されたクエリに関する情報が含まれています。これはサンプルドキュメントです:</p><pre><code>{
          "client_id": "client_002",
          "query": "italian pasta recipes",
          "query_attributes": {
            "search_type": "recipe",
            "category": "food",
            "cuisine": "italian"
          },
          "query_id": "q002",
          "query_response_id": "qr002",
          "query_response_object_ids": [
            "doc_011",
            "doc_012",
            "doc_013",
            "doc_014",
            "doc_015",
            "doc_016",
            "doc_017",
            "doc_018",
            "doc_019",
            "doc_020"
          ],
          "timestamp": "2024-08-14T11:15:00Z",
          "user_query": "italian pasta recipes"
        }</code></pre><p>ここでは、ユーザー（client_id）、クエリの結果（query_response_object_ids）、クエリ自体（timestamp、user_query）のデータを見ることができます。</p><h3>Ubiクリックイベント</h3><p>ubi_events インデックスには、ユーザーが結果内のドキュメントをクリックするたびに収集されたデータが含まれています。これはサンプルドキュメントです:</p><pre><code>{
          "action_name": "click",
          "application": "recipe_search",
          "client_id": "client_001",
          "event_attributes": {
            "object": {
              "description": "Authentic Italian Pizza Dough Recipe with Step-by-Step Photos",
              "device": "desktop",
              "object_id": "doc_001",
              "position": {
                "ordinal": 1,
                "page_depth": 1
              },
              "user": {
                "city": "New York",
                "country": "USA",
                "ip": "192.168.1.100",
                "location": {
                  "lat": 40.7128,
                  "lon": -74.006
                },
                "region": "NY"
              }
            }
          },
          "message": "User clicked on document doc_001",
          "message_type": "click",
          "query_id": "q001",
          "timestamp": "2024-08-14T10:31:00Z",
          "user_query": "best pizza recipe"
        }</code></pre><h2>判定リスト生成スクリプト</h2><h3>スクリプトの一般的な概要</h3><p>このスクリプトは、Elasticsearch に保存されているクエリとクリック イベントからの UBI データを使用して、判断リストの生成を自動化します。次のタスクを実行します:</p><ul><li>Elasticsearch で UBI データを取得して処理します。</li><li>UBI イベントをそのクエリと関連付けます。</li><li>各位置の CTR を計算します。</li><li>各ドキュメントの予想クリック数 (EC) を計算します。</li><li>各ドキュメントの実際のクリック数をカウントします。</li><li>各クエリとドキュメントのペアの COEC スコアを計算します。</li><li>判定リストを生成し、CSVファイルに書き込みます。</li></ul><p>それぞれの機能を見ていきましょう。</p><h3>connect_to_elasticsearch()</h3><pre><code>def connect_to_elasticsearch(host, api_key):
    """Create and return Elasticsearch client"""
    try:
        es = Elasticsearch(
            hosts=[host],
            api_key=api_key,
            request_timeout=60
        )
        # Test the connection
        if es.ping():
            print(f"✓ Successfully connected to Elasticsearch at {host}")
            return es
        else:
            print("✗ Failed to connect to Elasticsearch")
            return None
    except Exception as e:
        print(f"✗ Error connecting to Elasticsearch: {e}")
        return None</code></pre><p>この関数は、ホストと API キーを使用して Elasticsearch クライアント オブジェクトを返します。</p><h3>fetch_ubi_data()</h3><pre><code>def fetch_ubi_data(es_client: Elasticsearch, queries_index: str, events_index: str,
                   size: int = 10000) -> Tuple[List[Dict], List[Dict]]:
    """
    Fetch UBI queries and events data from Elasticsearch indices.

    Args:
        es_client: Elasticsearch client
        queries_index: Name of the UBI queries index
        events_index: Name of the UBI events index
        size: Maximum number of documents to fetch

    Returns:
        Tuple of (queries_data, events_data)
    """
    logger.info(f"Fetching data from {queries_index} and {events_index}")

    # Fetch queries with error handling
    try:
        queries_response = es_client.search(
            index=queries_index,
            body={
                "query": {"match_all": {}},
                "size": size
            }
        )
        queries_data = [hit['_source'] for hit in queries_response['hits']['hits']]
        logger.info(f"Fetched {len(queries_data)} queries")

    except Exception as e:
        logger.error(f"Error fetching queries from {queries_index}: {e}")
        raise

    # Fetch events (only click events for now) with error handling
    try:
        events_response = es_client.search(
            index=events_index,
            body={
                "query": {
                    "term": {"message_type.keyword": "CLICK_THROUGH"}
                },
                "size": size
            }
        )
        events_data = [hit['_source'] for hit in events_response['hits']['hits']]
        logger.info(f"Fetched {len(events_data)} click events")

    except Exception as e:
        logger.error(f"Error fetching events from {events_index}: {e}")
        raise

    logger.info(f"Data fetch completed successfully - Queries: {len(queries_data)}, Events: {len(events_data)}")

    return queries_data, events_data</code></pre><p>この関数はデータ抽出レイヤーであり、Elasticsearch に接続して match_all クエリを使用して UBI クエリを取得し、UBI イベントをフィルタリングして &#x27;CLICK_THROUGH&#x27; イベントのみを取得します。</p><h3>プロセスubi_data()</h3><pre><code>def process_ubi_data(queries_data: List[Dict], events_data: List[Dict]) -> pd.DataFrame:
    """
    Process UBI data and generate judgment list.

    Args:
        queries_data: List of query documents from UBI queries index
        events_data: List of event documents from UBI events index

    Returns:
        DataFrame with judgment list (qid, docid, grade, keywords)
    """
    logger.info("Processing UBI data to generate judgment list")

    # Group events by query_id
    clicks_by_query = {}
    for event in events_data:
        query_id = event['query_id']
        if query_id not in clicks_by_query:
            clicks_by_query[query_id] = {}

        # Extract clicked document info
        object_id = event['event_attributes']['object']['object_id']
        position = event['event_attributes']['object']['position']['ordinal']

        clicks_by_query[query_id][object_id] = {
            'position': position,
            'timestamp': event['timestamp']
        }

    judgment_list = []

    # Process each query
    for query in queries_data:
        query_id = query['query_id']
        user_query = query['user_query']
        document_ids = query['query_response_object_ids']

        # Get clicks for this query
        query_clicks = clicks_by_query.get(query_id, {})

        # Generate judgment for each document shown
        for doc_id in document_ids:
            grade = calculate_relevance_grade(doc_id, query_clicks, document_ids, queries_data, events_data)

            judgment_list.append({
                'qid': query_id,
                'docid': doc_id,
                'grade': grade,
                'query': user_query
            })

    df = pd.DataFrame(judgment_list)
    logger.info(f"Generated {len(df)} judgment entries for {df['qid'].nunique()} unique queries")

    return df</code></pre><p>この関数は判定リストの生成を処理します。UBI イベントとクエリを関連付けることで、UBI データの処理を開始します。次に、ドキュメントとクエリのペアごとに calculate_relevance_grade() 関数を呼び出して、判断リストのエントリを取得します。最後に、結果のリストを pandas データフレームとして返します。</p><h3>関連性グレードを計算する()</h3><pre><code>def calculate_relevance_grade(document_id: str, clicks_data: Dict,
                              query_response_ids: List[str], all_queries_data: List[Dict] = None,
                              all_events_data: List[Dict] = None) -> float:
    """
    Calculate COEC (Click Over Expected Clicks) relevance score for a document.

    Args:
        document_id: ID of the document
        clicks_data: Dictionary of clicked documents with their positions for current query
        query_response_ids: List of document IDs shown in search results (ordered by position)
        all_queries_data: All queries data for calculating position CTR averages
        all_events_data: All events data for calculating position CTR averages

    Returns:
        COEC relevance score (continuous value, typically 0.0 to 5.0+)
    """

    # If no global data provided, fall back to simple position-based grading
    if all_queries_data is None or all_events_data is None:
        logger.warning("No global data provided, falling back to position-based grading")
        # Simple fallback logic
        if document_id in clicks_data:
            position = clicks_data[document_id]['position']
            if position > 3:
                return 4.0
            elif position >= 1 and position <= 3:
                return 3.0
        if document_id in query_response_ids:
            position = query_response_ids.index(document_id) + 1
            if position <= 5:
                return 2.0
            elif position >= 6 and position <= 10:
                return 1.0
        return 0.0

    # Calculate rank-aggregated click-through rates
    position_ctr_averages = {}
    position_impression_counts = {}
    position_click_counts = {}

    # Initialize counters
    for pos in range(1, 11):  # Positions 1-10
        position_impression_counts[pos] = 0
        position_click_counts[pos] = 0

    # Count impressions (every document shown contributes)
    for query in all_queries_data:
        for i, doc_id in enumerate(query['query_response_object_ids'][:10]):  # Top 10 positions
            position = i + 1
            position_impression_counts[position] += 1

    # Count clicks by position
    for event in all_events_data:
        if event.get('action_name') == 'click':
            position = event['event_attributes']['object']['position']['ordinal']
            if position <= 10:
                position_click_counts[position] += 1

    # Calculate average CTR per position
    for pos in range(1, 11):
        if position_impression_counts[pos] > 0:
            position_ctr_averages[pos] = position_click_counts[pos] / position_impression_counts[pos]
        else:
            position_ctr_averages[pos] = 0.0

    # Calculate expected clicks for this specific document
    expected_clicks = 0.0

    # Count how many times this document appeared at each position for any query
    for query in all_queries_data:
        if document_id in query['query_response_object_ids']:
            position = query['query_response_object_ids'].index(document_id) + 1
            if position <= 10:
                expected_clicks += position_ctr_averages[position]

    # Count total actual clicks for this document across all queries
    actual_clicks = 0
    for event in all_events_data:
        if (event.get('action_name') == 'click' and
                event['event_attributes']['object']['object_id'] == document_id):
            actual_clicks += 1

    # Calculate COEC score
    if expected_clicks > 0:
        coec_score = actual_clicks / expected_clicks
    else:
        coec_score = 0.0

    logger.debug(
        f"Document {document_id}: {actual_clicks} clicks / {expected_clicks:.3f} expected = {coec_score:.3f} COEC")

    return coec_score</code></pre><p>これは COEC アルゴリズムを実装する関数です。各位置の CTR を計算し、次にドキュメントとクエリのペアの実際のクリック数を比較し、最後にそれぞれの実際の COEC スコアを計算します。</p><h3>判断統計を生成する()</h3><pre><code>def generate_judgment_statistics(df: pd.DataFrame) -> Dict:
    """Generate statistics about the judgment list."""
    stats = {
        'total_judgments': len(df),
        'unique_queries': df['qid'].nunique(),
        'unique_documents': df['docid'].nunique(),
        'grade_distribution': df['grade'].value_counts().to_dict(),
        'avg_judgments_per_query': len(df) / df['qid'].nunique() if df['qid'].nunique() > 0 else 0,
        'queries_with_clicks': len(df[df['grade'] > 1]['qid'].unique()),
        'click_through_rate': len(df[df['grade'] > 1]) / len(df) if len(df) > 0 else 0
    }
    return stats</code></pre><p>判定リストから、合計クエリ数、合計ユニークドキュメント数、グレード分布などの有用な統計情報を生成します。これは純粋に情報提供であり、結果の判断リストは変更されません。</p><h2>結果と影響</h2><p>クイック スタート セクションの指示に従うと、320 エントリの判定リストを含む CSV ファイルが生成されます (リポジトリで<a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog/blob/main/judgment_list.csv">サンプル出力を</a>確認できます)。これらのフィールド:</p><ul><li>qid: クエリの一意のID</li><li>docid: 結果のドキュメントの一意の識別子</li><li>グレード: クエリとドキュメントのペアの計算されたグレード</li><li>クエリ: ユーザークエリ</li></ul><p> 「イタリア料理のレシピ」というクエリの結果を見てみましょう。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">クイド</td><td style="border: 1px solid #ccc; padding: 4px;">ドシド</td><td style="border: 1px solid #ccc; padding: 4px;">学年</td><td style="border: 1px solid #ccc; padding: 4px;">クエリ</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">q1-イタリア料理レシピ</td><td style="border: 1px solid #ccc; padding: 4px;">パスタの基本レシピ</td><td style="border: 1px solid #ccc; padding: 4px;">0.0</td><td style="border: 1px solid #ccc; padding: 4px;">イタリアのレシピ</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">q1-イタリア料理レシピ</td><td style="border: 1px solid #ccc; padding: 4px;">レシピ_ピザ_マルゲリータ</td><td style="border: 1px solid #ccc; padding: 4px;">3.333333</td><td style="border: 1px solid #ccc; padding: 4px;">イタリアのレシピ</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">q1-イタリア料理レシピ</td><td style="border: 1px solid #ccc; padding: 4px;">レシピ_リゾット_ガイド</td><td style="border: 1px solid #ccc; padding: 4px;">10.0</td><td style="border: 1px solid #ccc; padding: 4px;">イタリアのレシピ</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">q1-イタリア料理レシピ</td><td style="border: 1px solid #ccc; padding: 4px;">レシピ_フレンチ_クロワッサン</td><td style="border: 1px solid #ccc; padding: 4px;">0.0</td><td style="border: 1px solid #ccc; padding: 4px;">イタリアのレシピ</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">q1-イタリア料理レシピ</td><td style="border: 1px solid #ccc; padding: 4px;">レシピ_スペイン_パエリア</td><td style="border: 1px solid #ccc; padding: 4px;">0.0</td><td style="border: 1px solid #ccc; padding: 4px;">イタリアのレシピ</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">q1-イタリア料理レシピ</td><td style="border: 1px solid #ccc; padding: 4px;">ギリシャ風ムサカのレシピ</td><td style="border: 1px solid #ccc; padding: 4px;">1.875</td><td style="border: 1px solid #ccc; padding: 4px;">イタリアのレシピ</td>
                    </tr>
              </tbody>
            </table>
          <p>結果から、「イタリアのレシピ」というクエリに対して次のことがわかります。</p><ul><li>リゾットのレシピは間違いなくクエリに対する最高の結果であり、予想よりも10倍多くのクリックを獲得しています。</li><li>ピザ マルゲリータも素晴らしい出来栄えです。</li><li>ギリシャのムサカも（意外にも）良い結果であり、結果上の順位が示唆するよりも良い成績を残しています。これは、イタリア料理のレシピを探していた数人のユーザーが、代わりにこのレシピに興味を持ったことを意味します。おそらくこれらのユーザーは地中海料理全般に興味があるのでしょう。結局のところ、このことからわかるのは、これは上で説明した他の 2 つの「より良い」一致の下に表示される良い結果になる可能性があるということです。</li></ul><h2>まとめ</h2><p>UBI データを使用すると、LTR モデルのトレーニングを自動化し、独自のユーザーから高品質の判断リストを作成できます。UBI データは、検索システムがどのように使用されているかを反映する大きなデータセットを提供します。COEC アルゴリズムを使用して成績を生成することで、固有の偏りを考慮しながら、同時にユーザーがより良い結果と考えるものを反映します。ここで概説した方法は、実際のユースケースに適用でき、実際の使用傾向に合わせて進化する、より優れた検索エクスペリエンスを提供できます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AIエージェントの評価：Elasticによるエージェントフレームワークのテスト方法]]></title>
            <description><![CDATA[正確で検証可能な結果を確保するために、エージェントシステムへの変更を Elastic ユーザーにリリースする前に評価およびテストする方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/ai-agent-evaluation-elastic</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/ai-agent-evaluation-elastic</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Susan Chang, Abhimanyu Anand]]></dc:creator>
            <pubDate>Mon, 13 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e681aab6ee77dfef38dc95e36c8535dfe7bcc105-721x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<h2>はじめに</h2><p>Elastic Stack には、 <a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">Agent Builder</a>の近々リリースされる Elastic AI Agent (現在技術プレビュー) や<a href="https://www.elastic.co/docs/solutions/security/ai/attack-discovery">Attack Discovery</a> (8.18 および 9.0 以降で<a href="https://www.elastic.co/blog/whats-new-elastic-security-9-0-0">GA</a>提供) など、LLM を利用したエージェント アプリケーションが多数あり、さらに多くのアプリケーションが開発中です。開発中、そして展開後でも、次の質問に答えることが重要です。</p><ul><li>これらの AI アプリケーションの応答の品質をどのように評価するのでしょうか?</li><li>変更を加えた場合、その変更が本当に改善となり、ユーザー エクスペリエンスが低下しないことをどのように保証すればよいでしょうか。</li><li>これらの結果を繰り返し簡単にテストするにはどうすればよいでしょうか?</li></ul><p>従来のソフトウェア テストとは異なり、生成 AI アプリケーションの評価には、統計的手法、微妙な定性的なレビュー、ユーザーの目標の深い理解が必要になります。</p><p>この記事では、Elastic 開発チームが評価を実施し、展開前に変更の品質を確保し、システム パフォーマンスを監視するために採用しているプロセスについて詳しく説明します。私たちは、あらゆる変更が証拠によって裏付けられ、信頼できる検証可能な結果につながるようにすることを目指しています。このプロセスの一部は Kibana に直接統合されており、オープンソース精神の一環として透明性への取り組みを反映しています。評価データと指標の一部を公開することで、コミュニティの信頼を育み、AI エージェントを開発したり当社の製品を利用したりするすべての人にとって明確なフレームワークを提供することを目指しています。</p><h2>製品例</h2><p>このドキュメントで使用した方法は、Attack Discovery や Elastic AI Agent などのソリューションを反復して改善する方法の基礎となりました。それぞれ2つの簡単な紹介:</p><h3>Elastic Securityの攻撃検出</h3><p>Attack Discovery は LLM を使用して、Elastic 内の攻撃シーケンスを識別および要約します。特定の期間（デフォルトでは 24 時間）内の Elastic Security アラートに基づいて、Attack Discovery のエージェント ワークフローは、攻撃が発生したかどうかを自動的に検出するほか、どのホストまたはユーザーが侵害されたか、どのアラートが結論に寄与したかなどの重要な情報も検出します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/20fabb47642dad7b588daaaa8c3a98de860ad01d-1251x758.png" style="width: 100%;" /><p></p><p>目標は、LLM ベースのソリューションが少なくとも人間と同等の出力を生成することです。</p><h3>エラスティックAIエージェント</h3><p><strong>Elastic Agent Builder は、</strong>すべての検索機能を活用するコンテキスト認識型 AI エージェントを構築するための新しいプラットフォームです。この製品には、会話形式のやりとりを通じてユーザーがデータを理解し、データから回答を得られるよう設計された、あらかじめ構築された汎用エージェントである<strong>Elastic AI Agent</strong>が付属しています。</p><p>エージェントは、Elasticsearch または接続されたナレッジベース内の関連情報を自動的に識別し、事前に構築された一連のツールを活用してそれらと対話することでこれを実現します。これにより、Elastic AI Agent は、単一のドキュメントに関する単純な Q&amp;A から、複数のインデックスにわたる集約や単一または複数ステップの検索を必要とする複雑なリクエストまで、さまざまなユーザー クエリに応答できるようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d29dee100bb8a17bb623acd745773a5164a1df4f-1600x1014.png" style="width: 100%;" /><h2>実験による改善の測定</h2><p>AI エージェントのコンテキストでは、実験とは、明確に定義された次元 (有用性、正確性、遅延など) のパフォーマンスを向上させるように設計された、システムに対する構造化されたテスト可能な変更です。目標は、「この変更をマージした場合、それが真の改善であり、ユーザー エクスペリエンスを低下させないことを保証できますか?」という質問に明確に答えることです。</p><p>私たちが実施するほとんどの実験には、一般的に次のようなものが含まれます。</p><ul><li><strong>仮説:</strong>特定の、反証可能な主張。<em>例:</em> 「攻撃検出ツールへのアクセスを追加すると、セキュリティ関連のクエリの正確性が向上します。」</li><li><strong>成功基準:</strong> 「成功」の意味を定義する明確なしきい値。<em>例:</em> 「セキュリティ データセットの正確性スコアが 5% 向上し、他の部分では低下は見られません。」</li><li><strong>評価計画:</strong>成功の測定方法 (指標、データセット、比較方法)</li></ul><p>成功した実験は体系的な調査プロセスです。小さなプロンプトの調整から大規模なアーキテクチャの変更まで、すべての変更は次の 7 つの手順に従い、結果が有意義かつ実用的なものになるようにします。</p><ul><li>手順1：問題を特定する</li><li>ステップ2: 指標を定義する</li><li>ステップ3：明確な仮説を立てる</li><li>ステップ4: 評価データセットの準備</li><li>ステップ5: 実験を実行する</li><li>ステップ6: 結果の分析と反復</li><li>ステップ7：決定を下し、文書化する</li></ul><p>これらのステップの例を<em>図 1</em>に示します。次のサブセクションでは各ステップについて説明します。各ステップの技術的な詳細については、今後のドキュメントで詳しく説明します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3a9f5a3e92dd4922a795a19104c6e4ad8c98958d-2400x1352.png" style="width: 100%;" /><h2>実際の Elastic の例を使ったステップバイステップのウォークスルー</h2><h3>手順1：問題を特定する</h3><p><em>この変更が解決しようとしている問題は正確には何でしょうか?</em></p><p>攻撃検出の例: 概要が不完全な場合や、無害なアクティビティが誤って攻撃としてフラグ付けされる (誤検知) 場合があります。</p><p>Elastic AI エージェントの例: 特に分析クエリの場合、エージェントのツール選択は最適ではなく一貫性がなく、間違ったツールが選択されてしまうことがよくあります。これにより、トークンのコストとレイテンシが増加します。</p><h3>ステップ2: 指標を定義する</h3><p><em>問題を測定可能にして、変化を現在の状態と比較できるようにします。</em></p><p>一般的な指標には、<a href="https://developers.google.com/machine-learning/crash-course/classification/accuracy-precision-recall">精度と再現率</a>、<a href="https://en.wikipedia.org/wiki/Semantic_similarity">意味的類似性</a>、事実性などがあります。ユースケースに応じて、一致するアラート ID や正しく取得された URL などのメトリックを計算するためにコード チェックを使用したり、より自由形式の回答を得るために LLM-as-judge などの手法を使用したりします。</p><p>以下は、実験で使用されたメトリックの例です (<em>網羅的ではありません</em>)。</p><p><strong>攻撃の検出</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">メトリック</td><td style="border: 1px solid #ccc; padding: 4px;">説明</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">精度と再現率</td><td style="border: 1px solid #ccc; padding: 4px;">実際の出力と予想される出力の間でアラート ID を一致させて、検出精度を測定します。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">類似性</td><td style="border: 1px solid #ccc; padding: 4px;">BERTScore を使用して、応答テキストの意味的類似性を比較します。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">事実性</td><td style="border: 1px solid #ccc; padding: 4px;">重要な IOC (侵害の兆候) は存在しますか?MITRE 戦術 (攻撃の業界分類) は正しく反映されていますか?</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">攻撃チェーンの一貫性</td><td style="border: 1px solid #ccc; padding: 4px;">発見された数を比較して、攻撃の過剰報告または過少報告がないか確認します。</td>
                    </tr>
              </tbody>
            </table>
          <p><strong>エラスティックAIエージェント</strong></p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">メトリック</td><td style="border: 1px solid #ccc; padding: 4px;">説明</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">精度と再現率</td><td style="border: 1px solid #ccc; padding: 4px;">ユーザーのクエリに回答するためにエージェントによって取得されたドキュメント/情報と、クエリに回答するために必要な実際の情報またはドキュメントを照合して、情報取得の精度を測定します。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">事実性</td><td style="border: 1px solid #ccc; padding: 4px;">ユーザーのクエリに回答するために必要な主要な事実は存在しますか?事実は手続き上のクエリに対して正しい順序になっていますか?</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">回答の関連性</td><td style="border: 1px solid #ccc; padding: 4px;">応答には、ユーザーのクエリとは関連がない、または周辺的な情報が含まれていますか?</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">応答の完全性</td><td style="border: 1px solid #ccc; padding: 4px;">応答はユーザークエリのすべての部分に答えていますか?応答にはグラウンドトゥルースに存在するすべての情報が含まれていますか?</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ES|QL検証</td><td style="border: 1px solid #ccc; padding: 4px;">生成された ES|QL は構文的に正しいですか?機能的にはグラウンドトゥルース ES|QL と同一ですか?</td>
                    </tr>
              </tbody>
            </table>
          <h3>ステップ3：明確な仮説を立てる</h3><p><em>上記で定義した問題と指標を使用して、明確な成功基準を確立します。</em></p><p>Elastic AI エージェントの例:</p><ol><li><strong>relevance_search および nl_search ツールの説明に変更を加え、それぞれの機能と使用例を明確に定義します</strong>。</li><li><strong>ツールの呼び出し精度が</strong> <strong>25% 向上 する</strong> と予測しています。</li><li>他の指標に悪影響が及ばないことを保証し、これが純粋にプラスであることを確認します。<strong>事実性と完全性</strong>。</li><li><strong>正確なツールの説明により、エージェントがさまざまなクエリタイプに最も適した検索ツールをより正確に選択して適用できるようになり、誤った適用が減り、全体的な検索の有効性が向上するため、この方法が効果的であると考えています</strong>。</li></ol><h3>ステップ4: 評価データセットの準備</h3><p><em>システムのパフォーマンスを測定するために、現実世界のシナリオをキャプチャしたデータセットを使用します。</em></p><p>実施する評価の種類に応じて、LLMに供給される生データ（例：攻撃検出のための攻撃シナリオと予想される出力。アプリケーションがチャットボットの場合、入力はユーザークエリであり、出力は正しいチャットボット応答、取得されるべき正しいリンクなどになります。</p><p>攻撃検出の例:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">10の斬新な攻撃シナリオ</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Oh My Malware のエピソード 8 つ (ohmymalware.com)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">4 つのマルチ攻撃シナリオ (最初の 2 つのカテゴリの攻撃を組み合わせて作成)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">3つの良性のシナリオ</td>
                    </tr>
              </tbody>
            </table>
          <p>Elastic AI エージェント評価データセットの例 ( <a href="https://github.com/elastic/kibana/blob/main/x-pack/platform/packages/shared/onechat/kbn-evals-suite-onechat/evals/kb/kb.spec.ts">Kibana データセット リンク</a>):</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">オープンソース データセットを使用して KB 内の複数のソースをシミュレートする 14 のインデックス。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">5 つのクエリ タイプ (分析、テキスト検索、ハイブリッドなど)</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">7 つのクエリ意図タイプ（手続き型、事実型 - 分類型、調査型など）</td>
                    </tr>
              </tbody>
            </table>
          <h3>ステップ5: 実験を実行する</h3><p>評価データセットに対して既存のエージェントと修正バージョンの両方からの応答を生成して実験を実行します。事実性などの指標を計算します (手順 2 を参照)。</p><p>ステップ 2 で必要な指標に基づいて、さまざまな評価を組み合わせます。</p><ul><li>ルールベースの評価（例：Python/TypeScriptを使用して.jsonが有効かどうかを確認します)</li><li>LLM が裁判官となる（回答が原文と事実上一致しているかどうかを別の LLM に尋ねる）</li><li>ニュアンス品質チェックのための人間によるレビュー</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8648e75818d3291f0ac66f069438a500d42b8225-1600x1099.png" style="width: 100%;" /><h3>ステップ6: 結果の分析と反復</h3><p>指標が得られたので、結果を分析します。<em><span style="text-decoration:underline">結果がステップ 3 で定義された成功基準を満たしている場合でも、変更を本番環境にマージする前に人間によるレビューが行われます</span></em>。結果が基準を満たしていない場合は、問題を反復して修正してから、新しい変更に対して評価を実行します。</p><p>マージする前に、最適な変更を見つけるために数回の反復が必要になると予想されます。コミットをプッシュする前にローカル ソフトウェア テストを実行するのと同様に、オフライン評価はローカルの変更または複数の提案された変更で実行できます。分析を効率化するために、実験結果、複合スコア、視覚化の保存を自動化すると便利です。</p><h3>ステップ7：決定を下し、文書化する</h3><p>意思決定フレームワークと受け入れ基準に基づいて、変更のマージを決定し、実験を文書化します。意思決定は多面的であり、他のデータセットでの回帰シナリオの確認や、提案された変更の費用対効果の検討など、評価データセット以外の要素を考慮する場合があります。</p><p>例: いくつかの反復をテストして比較した後、最高スコアの変更を選択し、製品マネージャーやその他の関連する関係者に送信して承認を得ます。意思決定を支援するために、前の手順の結果を添付します。攻撃検出に関するその他の例については、 <a href="https://www.elastic.co/blog/elastic-security-generative-ai-features">「Elastic Security の生成 AI 機能の舞台裏」を</a>ご覧ください。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/74c80b8f34dce8ddd20873ecb2f553873587ed35-1600x618.png" style="width: 100%;" /><h2>まとめ</h2><p>このブログでは、実験ワークフローのエンドツーエンドのプロセスについて説明し、エージェントシステムの変更を Elastic ユーザーにリリースする前に評価およびテストする方法を説明しました。また、Elastic でのエージェントベースのワークフローの改善例もいくつか紹介しました。今後のブログ投稿では、適切なデータセットを作成する方法、信頼性の高いメトリックを設計する方法、複数のメトリックが関係する場合に意思決定を行う方法など、さまざまな手順の詳細を詳しく説明します。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A2Aプロトコルを介してElastic AgentsをGemini Enterpriseに接続する]]></title>
            <description><![CDATA[Agent Builder を使用して、A2A プロトコルを使用してカスタム Elastic Agent を Gemini Enterprise などの外部サービスに公開する方法を学習します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/a2a-protocol-elastic-agent-builder-gemini-enterprise</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/a2a-protocol-elastic-agent-builder-gemini-enterprise</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jedr Blaszyk, Valerio Arvizzigno, Joe McElroy]]></dc:creator>
            <pubDate>Thu, 09 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5be8a425fab55dca2f9717d2e50812b0450fa625-1440x840.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><strong>Elastic Agent Builder は、</strong> Elasticsearch で直接データ駆動型の AI エージェントを作成するための機能セットです。この<a href="https://www.elastic.co/search-labs/blog/series/context-aware-ai-agentic-workflows-with-elastic">シリーズ</a>の以前の投稿では、カスタム エージェントに複雑なタスクを実行するツールを装備し、エージェントの動作をガイドする一連のカスタム指示を提供する方法を説明しました。</p><p>しかし、すでに使用しているアプリケーションや生産性ツールでカスタムエージェントを使用したい場合はどうすればよいでしょうか?</p><p>ここで、<strong>エージェント間 (A2A) プロトコルが</strong>登場します。A2A は相互運用性のための<a href="https://github.com/a2aproject/A2A">オープン スタンダード</a>であり、異なるプラットフォームのエージェント間の通信と共同作業を可能にします。そして、これを Elastic Agent Builder に直接組み込みました。</p><p>今日は、構築したカスタム エージェントを他のサービス、具体的には<strong>Gemini Enterprise</strong> (旧称 Agentspace) に公開する方法を紹介します。</p><h2>オープンスタンダードの力：A2Aが重要な理由</h2><p>ブログ記事<a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">「初めての Elastic Agent」</a>では、市場データに安全にアクセスできる<em>Financial Assistant</em>エージェントなどのカスタムエージェントの構築方法を説明しました。しかし、作業を再構築せずに、Gemini Enterprise などの他の環境でその洞察を利用できない場合、その価値は限られます。</p><p>この相互運用性の課題が、エージェント AI の実現を妨げているのです。エージェントはプラットフォーム間で通信するために共通言語を必要としますが、これがまさに A2A プロトコルの役割です。標準の通信レイヤーを提供することで、エージェントと直接対話できるだけでなく、組織全体の専門エージェントが連携して洞察を共有できる未来が開かれます。</p><p>これを実現するために、Elastic Agent Builder は、すべてのエージェントに対して 2 つの標準エンドポイントを通じて A2A プロトコルをネイティブにサポートしています。</p><ol><li><strong>エージェント カード エンドポイント (</strong> <strong><code>GET {your-kibana-url}/api/agent_builder/a2a/{agentId}.json</code></strong> <strong>) -</strong>これはカスタム エージェントの名刺として機能します。エージェントに関するメタデータ (名前、説明、機能など) を A2A 互換サービスに提供します。</li><li><strong>A2A プロトコル エンドポイント (</strong> <strong><code>POST {your-kibana-url}/api/agent_builder/a2a/{agentId}</code></strong> <strong>)</strong> - これは通信チャネルです。他のエージェントはここにリクエストを送信し、エージェントはそれを処理して応答を返します。これらはすべて<a href="https://a2a-protocol.org/latest/specification/">A2A プロトコル仕様</a>に従って行われます。</li></ol><h2>A2Aインスペクターでエージェントをテストする</h2><p>エージェントを本番システムに接続する前に、正しく通信していることを確認することをお勧めします。これを行う最も簡単な方法は、A2A 統合のテストとデバッグ専用に設計されたツールである<strong>A2A Inspector を</strong>使用することです。</p><p>インスペクターを実行するのは簡単です。<a href="https://github.com/a2aproject/a2a-inspector">a2a-inspector</a>リポジトリのクローンを作成し、README の指示に従って<a href="https://github.com/a2aproject/a2a-inspector?tab=readme-ov-file#3-run-the-application">アプリケーションを実行でき</a>ます。起動すると、UI はデフォルトで<code>http://localhost:5001/</code>で使用できるようになります。</p><p>A2A インスペクターをエージェントに接続するには、次の 2 つの重要な情報を提供する必要があります。</p><ul><li>エージェント カード URL: これはエージェントを説明するエンドポイントです。<a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">前回の投稿の Financial Assistant エージェント</a>の場合、この URL は<code>{your-kibana-url}/api/agent_builder/a2a/financial_assistant.json</code>になります。</li><li>認証ヘッダー: 認証には標準の API キーを使用します。</li></ul><p>インスペクターの UI にこれらの詳細を入力すると、すぐにエージェントに接続してチャットを開始できます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7231c72bf30bed2a854f58658c1eca2843f43bfc-1600x1296.png" style="width: 100%;" /><p>この簡単な検証により、エージェントが正しく構成され、次のステップの準備ができていることが保証されます。</p><h2>ライブ配信しよう！Gemini Enterpriseのカスタムエージェント</h2><p>次は、エキサイティングな部分です。カスタム ファイナンシャル アドバイザー エージェントを Gemini Enterprise (旧 Agentspace) 内で実現します。この統合は<a href="https://console.cloud.google.com/marketplace/product/elastic-prod/elastic-ai-agent">、Google Cloud Marketplace で入手可能な Elastic AI Agent</a>によって実現されています。</p><p>接続されると、Gemini Enterprise は A2A プロトコルを使用してエージェントと直接通信します。ここで相互運用性の真の威力が発揮されます。ユーザーは使い慣れた環境を離れることなく、カスタム Elasticsearch エージェントから得られる詳細なデータ駆動型の分析情報にアクセスできるようになります。エージェント リストにカスタム Elastic Agent が表示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/37a39e92ebf3d72c6c8014397cd8e846336173a4-1600x834.png" style="width: 100%;" /><p>Gemini Enterprise のユーザーが次のように質問していると想像してください。</p><p><em>「市場のセンチメントが心配です。悪いニュースによって最もリスクが高い顧客は誰でしょうか？</em> 」</p><p>バックグラウンドでは、Gemini Enterprise がこのクエリを A2A プロトコル経由でカスタム Elastic Agent にルーティングします。エージェントは専用のツールを使用してデータを照会し、回答を作成して返送します。エンドユーザーにとって、エクスペリエンスはシームレスです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e5f126c1a27a51c6e69a767aa87c9f746b62e39c-1600x1044.png" style="width: 100%;" /><p>そして、ここで終わりではありません!Elasticエージェントで取得した回答は、別の専門エージェントをトリガーする可能性のある次の質問のコンテキストとして使用できるようになりました（例：上場企業へのエクスポージャーを調整するには、投資プラットフォーム エージェントにご相談ください。検索バーを離れることなくすべて行えます。</p><p>A2A を搭載した Gemini Enterprise に Elastic エージェントをデプロイすると、ユーザーがデータやツールにコンテキスト内でアクセスできる単一の UI が提供されるため、AI、検索、エンタープライズ システム間の摩擦をなくし、アクセス、オーケストレーション、ワークフローを統合できます。ユーザーにとって、これはツールの切り替えが減り、より直感的で有能な AI アシスタントが利用できるようになることを意味します。組織にとって、これは一貫したガバナンス、スケーラビリティ、相互運用性が組み込まれていることを意味します。</p><h2>あなたの番です</h2><p>これで、Elastic Agent をどこからでも利用できるようにするツールが手に入りました。オープン A2A プロトコルを活用することで、カスタムのデータ対応エージェントの範囲を拡大できます。</p><p>この投稿では、重要な手順について説明しました。</p><ul><li>A2A エージェント カードとプロトコル エンドポイントを介してエージェントを公開します。</li><li>A2A Inspector を使用して接続をテストします。</li><li>エージェントを Google の Gemini Enterprise などの外部サービスにライブで統合します。</li></ul><p>エージェントを分離する必要がなくなりました。皆さんが作り上げる、強力で相互接続されたシステムを見るのが待ちきれません。楽しい建築を！</p><p>始める最も簡単な方法は、 <a href="https://console.cloud.google.com/marketplace/product/elastic-prod/elastic-cloud?pli=1">Google Cloud Marketplace</a>で Elastic Cloud の無料トライアルを利用することです。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch向けAgentic AIツールの改善実験]]></title>
            <description><![CDATA[スケーラブルな RAG 最適化のために線形リトリーバー、ハイブリッド検索、および semantic_text を組み合わせることで、反復的な実験を通じて Elasticsearch の AI エージェント ワークフローをどのように改善したかを学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/ai-agent-builder-experiments-performance</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/ai-agent-builder-experiments-performance</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Sean Story]]></dc:creator>
            <pubDate>Mon, 06 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/42903c869e034674b30bb36013345aaa97f6608b-1184x864.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>最近の他社と同様に、Elastic ではチャット、エージェント、RAG に全力を注いでいます。検索部門では最近、エージェント ビルダーとツール レジストリに取り組んでおり、その目的は、Elasticsearch 内のデータとの「チャット」を簡単に行えるようにすることです。</p><p>この取り組みの「全体像」について詳しくは、<a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">ブログ「Elasticsearch を使用した AI エージェントワークフローの構築」</a>をお読みください。より実践的な入門書として<a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">、「初めての Elastic エージェント: 単一のクエリから AI を活用したチャットまで」もご覧ください</a>。</p><p>ただし、このブログでは、チャットを開始したときに最初に起こることの 1 つに焦点を絞り、最近行った改善点のいくつかについて説明します。</p><h2>ここで何が起こっているのですか?</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/25a24055a166d7d6ba81d80aa35cb97163662e23-1600x443.png" style="width: 100%;" /><p>Elasticsearch データとチャットする場合、デフォルトの AI エージェントが次の標準フローを実行します。</p><ol><li>プロンプトを検査します。</li><li>どのインデックスにそのプロンプトの回答が含まれている可能性があるかを特定します。</li><li>プロンプトに基づいて、そのインデックスのクエリを生成します。</li><li>そのクエリでそのインデックスを検索します。</li><li>結果を統合します。</li><li>結果はプロンプトに対応できますか?はいの場合は応答してください。そうでない場合は、別の方法を試しながら繰り返します。</li></ol><p>これはあまり目新しいものではないはずです。これは単に Retrieval Augmented Generation (RAG) です。そして当然のことですが、応答の質は最初の検索結果の関連性に大きく左右されます。そのため、応答品質の向上に取り組む中で、ステップ 3 で生成してステップ 4 で実行するクエリに細心の注意を払ってきました。そして、私たちは興味深いパターンに気づきました。</p><p>多くの場合、最初の応答が「悪い」場合、それは実行したクエリが悪かったからではありません。クエリを実行するために<em>間違ったインデックスを選択した</em>ためです。通常、ステップ 3 と 4 は問題ではありません。問題はステップ 2 です。</p><h2>私たちは何をしていたのでしょうか?</h2><p>当初の実装はシンプルでした。私たちは、 <code>_cat/indices</code>を効果的に実行して利用可能なすべてのインデックスをリストし、これらのインデックスのうちどれがユーザーのメッセージ/質問/プロンプトに最も一致するかを LLM に識別させるツール (index_explorer と呼ばれる) を構築しました。この<a href="https://github.com/elastic/kibana/blob/0cc78184957fcd12110dabae50353392ea937508/x-pack/platform/packages/shared/onechat/onechat-genai-utils/tools/index_explorer.ts#L98-L113">オリジナルの実装はここで</a>見ることができます。</p><pre><code>You are an AI assistant for the Elasticsearch company.
based on a natural language query from the user, your task is to select up to ${limit} most relevant indices from a list of indices.

*The natural language query is:* ${nlQuery}

*List of indices:*
${indices.map((index) => `- ${index.index}`).join('\n')}

Based on those information, please return most relevant indices with your reasoning.
Remember, you should select at maximum ${limit} indices.</code></pre><p>これはどれくらいうまく機能しましたか?よく分かりませんでした！うまく機能して<em>いない</em>明確な例はありましたが、私たちにとっての本当の最初の課題は、現状を定量化することでした。</p><h2>ベースラインの確立</h2><h3>それはデータから始まる</h3><p>私たちが必要としていたのは、ユーザーのプロンプトと既存のインデックス セットに基づいて適切なインデックスを選択するツールの有効性を測定するためのゴールデン データ セットでした。そして、手元にそのようなデータセットがなかったので、それを生成しました。</p><p>謝辞: これは「ベスト プラクティス」ではないことは承知しています。しかし、時には、自転車を捨てるよりも前進する方が良いこともあります。<a href="https://www.elastic.co/about/our-source-code#progress-perfection">進歩、シンプルな完璧さ</a>。</p><p><a href="https://gist.github.com/seanstory/a08db2e149897da656db3a1ca72e17ac">このプロンプト</a>を使用して、いくつかの異なるドメインのシードのインデックスを生成しました。次に、生成されたドメインごとに、<a href="https://gist.github.com/seanstory/a280a85d067e61bfeb5911bf2654e6e2">このプロンプト</a>を使用してさらにいくつかのインデックスを生成しました (ここでの目標は、ハードネガティブと分類が難しい例を使用して LLM に混乱を引き起こすことです)。次に、生成された各インデックスとその説明を手動で編集しました。最後に、<a href="https://gist.github.com/seanstory/44291b666c05a383136f6e36bb9106fa">このプロンプト</a>を使用してテストクエリを生成しました。次のようなサンプルデータが得られました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9d96d87e286eddbc012402b1ecccd57419a99253-1600x782.png" style="width: 100%;" /><p>そして次のようなテストケース:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4c2e9ad941d98d7e66033bbc08c9b8060ec19097-1600x797.png" style="width: 100%;" /><h3>テストハーネスの作成</h3><p>ここからのプロセスは非常に簡単でした。次の機能を備えたツールをスクリプト化します。</p><ol><li>ターゲット Elasticsearch クラスターを使用してクリーンな状態を確立します。</li><li>ターゲット データセットで定義されているすべてのインデックスを作成します。</li><li>各テスト シナリオに対して、 i <code>ndex_explorer</code>ツールを実行します (便利なことに、<a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-post-agent-builder-tools-execute">実行ツール API が</a>あります)。</li><li>結果のインデックスを予想インデックスと比較し、結果を取得します。</li><li>すべてのテストシナリオを終了したら、結果を表にまとめます。</li></ol><h3>調査によると…</h3><p>当初の結果は予想通り平凡なものでした。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9c10679bcd6291edfa2a9ba42e7dd922aa483f0b-1216x806.png" style="width: 100%;" /><p>全体として、正しいインデックスを識別する精度は 77.14% です。これは、すべてのインデックスに意味的に意味のある適切な名前が付けられている「最良のケース」のシナリオでした。`PUT test2/_doc/foo {...} ` を実行したことがある人なら、インデックスの名前が必ずしも意味のあるものではないことはご存じでしょう。</p><p>つまり、ベースラインがあり、改善の余地が十分にあることがわかります。さあ、科学の時間です！🧪</p><h2>実験</h2><h3>仮説1: マッピングは役立つ</h3><p>ここでの目標は、元のプロンプトに関連するデータが含まれるインデックスを識別することです。インデックスに含まれるデータを最もよく表す部分は、インデックスの<em>マッピング</em>です。インデックスの内容のサンプルを取得しなくても、インデックスに double 型の価格フィールドがあることがわかれば、そのデータは販売されるものを表していることがわかります。テキストタイプの著者フィールドは、何らかの非構造化言語データを意味します。これら 2 つを組み合わせると、データが書籍、物語、詩であることを意味する可能性があります。インデックスのプロパティを知るだけで、意味上の手がかりを数多く得ることができます。そこでローカルブランチで`.index_explorer`を調整しましたインデックスの完全なマッピング (およびその名前) を LLM に送信して決定を下すツール。 </p><p>結果（Kibana ログより）:</p><pre><code>[2025-09-05T11:01:21.552-05:00][ERROR][plugins.onechat] Error: Error calling connector: event: error
data: {"error":{"code":"request_entity_too_large","message":"Received a content too large status code for request from inference entity id [.rainbow-sprinkles-elastic] status [413]","type":"error"}}


    at createInferenceProviderError (errors.ts:90:10)
    at convertUpstreamError (convert_upstream_error.ts:39:38)
    at handle_connector_response.ts:26:33
    at Observable.init [as _subscribe] (/Users/seanstory/Desktop/Dev/kibana/node_modules/rxjs/src/internal/observable/throwError.ts:123:68)...</code></pre><p>ツールの最初の作成者はこれを予期していました。インデックスのマッピングは情報の宝庫ですが、非常に冗長な JSON ブロックでもあります。そして、多数のインデックス (評価データセットでは 20 個が定義されています) を比較する現実的なシナリオでは、これらの JSON BLOB が加算されます。したがって、LLM に、すべてのオプションのインデックス名だけでなく、それぞれの完全なマッピングほどではなく、決定のためのより多くのコンテキストを提供したいと考えています。</p><h3>仮説2: 妥協案としての「フラット化された」マッピング（フィールドリスト）</h3><p>私たちは、インデックス作成者が意味的に意味のあるインデックス名を使用するという前提から始めました。その仮定をフィールド名にも拡張するとどうなるでしょうか?前回の実験は、JSON のマッピングに大量の煩わしいメタデータと定型句が含まれているため失敗しました。</p><pre><code>     "description_text": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          },
          "copy_to": [
            "description_semantic"
          ]
        },</code></pre><p>たとえば、上記のブロックは 236 文字で、Elasticsearch マッピング内の 1 つのフィールドのみを定義します。一方、文字列「description_text」は 16 文字だけです。これは文字数が約 15 倍に増加していることを意味しますが、そのフィールドが利用可能なデータについて何を意味するかを説明する意味的な改善は見られません。すべてのインデックスのマッピングをフェッチしたが、それを LLM に送信する前に、フィールド名のリストだけに「フラット化」するとどうなるでしょうか?</p><p>試してみました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/112c2f447c11f154b5082725cd49b51d0a3c8a65-1214x804.png" style="width: 100%;" /><p>これは素晴らしいですね！全面的に改善されました。しかし、もっと良い方法はないでしょうか?</p><h3>仮説3: マッピング_meta内の説明</h3><p>追加のコンテキストのないフィールド名だけでこれほど大きな変化が生じたのであれば、実質的なコンテキストを追加すればさらに良くなると思われます。すべてのインデックスに説明を添付することが必ずしも慣例ではありませんが、マッピングの _meta オブジェクトにあらゆる種類のインデックス レベルのメタデータを追加することは可能です。生成されたインデックスに戻り、データセット内のすべてのインデックスに説明を追加しました。説明が極端に長くない限り、完全なマッピングよりも少ないトークンが使用され、インデックスに含まれるデータに関するはるかに優れた洞察が提供されるはずです。私たちの実験はこの仮説を検証しました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/32d2692ad4479d0e52d8ee723dcc5710a6ec90f3-1208x806.png" style="width: 100%;" /><p>若干の改善があり、現在では全体的に 90% を超える精度を実現しています。</p><h3>仮説4：全体は部分の合計よりも大きい</h3><p>フィールド名により結果が向上しました。説明により結果が向上しました。したがって、説明とフィールド名の<em>両方</em>を利用すると、さらに良い結果が得られるはずです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/114cbb408ff16b136251d2265416bd5270380fe5-1208x794.png" style="width: 100%;" /><p>データは「いいえ」（前回の実験から変化なし）を示しました。ここでの主な理論は、説明はそもそもインデックス フィールド/マッピングから生成されたため、これら 2 つのコンテキストの間には、組み合わせたときに何か「新しい」ものを追加するのに十分な情報がないというものでした。さらに、20 個のテスト インデックスに送信するペイロードもかなり大きくなっています。これまで私たちが辿ってきた考え方はスケーラブルではありません。実際、これまでの私たちの実験は、数百または数千のインデックスから選択できる Elasticsearch クラスターでは機能しないと考えられる十分な理由があります。インデックスの合計数が増加するにつれて、LLM に送信されるメッセージ サイズが直線的に増加するアプローチは、おそらく一般化可能な戦略にはなりません。</p><p>私たちに本当に必要なのは、多数の候補から最も関連性の高い選択肢だけを絞り込むのに役立つアプローチです...</p><p>ここで問題となるのは検索の問題です。</p><h3>仮説5：意味検索による選択</h3><p>インデックスの名前に意味がある場合は、ベクトルとして保存し、意味的に検索することができます。</p><p>インデックスのフィールド名に意味がある場合は、それらをベクトルとして保存し、意味的に検索することができます。</p><p>インデックスに意味を持つ記述がある場合は、それもベクトルとして保存し、意味的に検索することができます。</p><p>現在、Elasticsearch インデックスではこの情報を検索可能にしていません (検索可能にすべきかもしれませんが) が、そのギャップを回避できる<a href="https://github.com/elastic/connectors/pull/3638">ものをハックする</a>のは非常に簡単でした。Elastic のコネクタ フレームワークを使用して、クラスター内のすべてのインデックスのドキュメントを出力するコネクタを構築しました。出力ドキュメントは次のようになります。</p><pre><code> doc = {
                "_id": index_name,
                "index_name": index_name,
			"meta_description”: description,
"field_descriptions" = field_descriptions,
                "mapping": json.dumps(mapping),  
                "source_cluster": self.es_client.configured_host,
            }</code></pre><p>これらのドキュメントを、次のように手動でマッピングを定義した新しいインデックスに送信しました。</p><pre><code>{
   "mappings": {
       "properties": {
           "semantic_content": {
               "type": "semantic_text"
           },
           "index_name": {
               "type": "text",
               "copy_to": "semantic_content"
           },
           "mapping": {
               "type": "keyword",
               "copy_to": "semantic_content"
           },
           "source_cluster": {
               "type": "keyword"
           },
           "meta_description": {
               "type": "text",
               "copy_to": "semantic_content"
           },
           "field_descriptions": {
               "type": "text",
               "copy_to": "semantic_content"
           }
       }
   }
}</code></pre><p>これにより、単一の semantic_content フィールドが作成され、セマンティックな意味を持つ他のすべてのフィールドがチャンク化され、インデックスが作成されます。このインデックスの検索は、次のようにするだけで簡単になります。</p><pre><code>GET indexed-indices/_search
{
 "query": {
   "semantic": {
     "field": "semantic_content",
     "query": "$query"
   }
 }
}</code></pre><p>修正された<code>index_explorer</code>ツールは、LLM へのリクエストを行う必要がなくなり、代わりに指定されたクエリに対して単一の埋め込みをリクエストして効率的なベクトル検索操作を実行できるため、<em>大幅に</em>高速化されました。トップヒットを選択したインデックスとして取得すると、次の結果が得られました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/06ef5d78040d064d3444793f636d527d9e19a869-1214x800.png" style="width: 100%;" /><p>このアプローチはスケーラブルです。このアプローチは効率的です。しかし、このアプローチはベースラインよりわずかに優れているだけです。しかし、これは驚くことではありません。ここでの検索アプローチは信じられないほど単純です。ニュアンスがない。インデックスの名前と説明は、インデックスに含まれる任意のフィールド名よりも重視されるべきであるという認識がありません。正確な語彙の一致を同義語の一致よりも重視するアフォーダンスはありません。ただし、非常に微妙なニュアンスのあるクエリを構築するには、手元のデータについて多くのことを想定する必要があります。これまで、インデックス名とフィールド名には意味があるという大きな仮定をすでに立ててきましたが、さらに一歩進んで、インデックス名とフィールド名が<em>どの程度の</em>意味を持ち、互いにどのように関連しているかを仮定する必要があります。そうしないと、最上位の結果として最適な一致を確実に特定することはできないかもしれませんが、最上位 N 個の結果のどこかに最上位の一致があると言える可能性が高くなります。意味情報をそれが存在するコンテキスト内で消費し、意味的に異なる方法で自身を表現する別のエンティティと比較し、それらを判断できるものが必要です。LLM のようなものです。</p><h3>仮説6: 候補セットの削減</h3><p>他にも簡単に触れる実験はいくつかありましたが、重要な突破口となったのは、純粋にセマンティック検索から最適な一致を選択したいという欲求を捨て、代わりにセマンティック検索をフィルターとして活用して、LLM の検討対象から無関係なインデックスを除外したことです。<a href="https://gist.github.com/seanstory/d704443120e20f6c844db10e30066860">検索</a>では、リニア リトリーバー、RRF を使用したハイブリッド検索、 <code>semantic_text</code>を組み合わせて、一致する上位 5 つのインデックスに結果を制限しました。</p><p>次に、一致ごとに、インデックスの名前、説明、フィールド名を LLM のメッセージに追加しました。結果は素晴らしかったです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8fcabd78f591f90d6bc7c0e087d31317e4eef791-1206x804.png" style="width: 100%;" /><p>これまでのどの実験よりも最高の精度です!また、このアプローチではインデックスの合計数に比例してメッセージ サイズが増加しないため、このアプローチははるかにスケーラブルです。</p><h2>成果</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/04d630797213dbb8bf567da41d1cdd5c7b4586c9-1600x521.png" style="width: 100%;" /><p>最初の明らかな結果は、ベースラインを改善<em>できる</em>ということでした。振り返ってみるとこれは明らかなようですが、実験が始まる前に、 <code>index_explorer</code>ツールを完全に放棄して、ユーザーからの明示的な構成に依存して検索空間を制限すべきかどうかについて真剣な議論がありました。これはまだ実行可能かつ有効なオプションですが、この調査では、そのようなユーザー入力が利用できない場合にインデックス選択を自動化するための有望な道筋があることが示されています。</p><p>次の明らかな結果は、問題に対して説明文字をさらに追加するだけでは、効果は減少するということです。この調査を行う前、Elasticsearch の<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-field-meta">フィールドレベルのメタデータ</a>保存機能を拡張することに投資すべきかどうかについて議論していました。現在、これらの<code>meta</code>値は 50 文字に制限されており、フィールドの意味を理解できるようにするにはこの値を増やす必要があると想定されていました。これは明らかに事実ではなく、LLM はフィールド名だけでかなりうまく機能しているようです。これについては後でさらに調査するかもしれませんが、もはや緊急の問題ではないように思われます。</p><p>逆に言えば、これは「検索可能な」インデックス メタデータを持つことの重要性を明確に示しています。これらの実験のために、インデックスのインデックスをハッキングしました。しかし、これを Elasticsearch に直接組み込むか、管理するための API を構築するか、少なくとも規則を確立することを調査することはできます。私たちは選択肢を検討し、社内で議論する予定ですので、お楽しみに。</p><p>最後に、この取り組みにより、時間をかけて実験し、データに基づいた意思決定を行うことの価値が確認されました。実際、これにより、Agent Builder 製品には強力な製品内評価機能が必要になることが再確認されました。インデックスを選択するツール専用のテスト ハーネス全体を構築する必要がある場合、お客様は反復的な調整を行う際にカスタム ツールを定性的に評価する方法が絶対に必要になります。</p><p>私たちが何を構築するのか楽しみにしています。皆さんも楽しみにしていただければ幸いです。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AWS MarketplaceでElasticsearchをデプロイする方法]]></title>
            <description><![CDATA[このステップバイステップガイドでは、AWSマーケットプレイスでElastic Cloudサービスを使用してElasticsearchをセットアップし、実行する方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/aws-elasticsearch-service-set-up</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/aws-elasticsearch-service-set-up</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Eduard Martin]]></dc:creator>
            <pubDate>Fri, 03 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a80776d2ef85db26f850d932339fac2d26b90278-1086x620.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>この記事では、Marketplaceの提供を利用してAWS上でElasticsearchをデプロイする方法を学びます。</p><p>Elastic Cloud Service on AWSを使用します。これは、AWSのネイティブインフラストラクチャーを介してすべてのElastic Stackコンポーネントの導入とオーケストレーションを簡素化するオフィシャルのマネージドElasticsearch Serviceです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f15814051b53b50bec38f9a9f515a1e6dc08a56c-884x440.png" style="width: 100%;" /><p>AWS EC2でのElasticsearchのインストールと設定方法を学びたい方は、<a href="https://www.elastic.co/search-labs/blog/elasticsearch-on-aws-ec2-deployment-guide">このブログ</a>をご覧ください。<br/></p><h2>AWS Marketplaceとは？</h2><p><a href="https://aws.amazon.com/marketplace"><strong>Elastic on AWS Marketplace</strong></a>は、完全マネージド型の検索・分析エクスペリエンスを提供します。インフラの提供、security、スケーリングは AWS が処理し、開発者は検索アプリケーションの構築に集中できます。これにより、チームは搭載のAWS統合を使用して、エンタープライズグレードのElasticsearchクラスターを数分でデプロイできるようになります。</p><h2>Elastic on AWS Marketplaceを使用するのはどのような場合ですか？</h2><p>Elastic on AWS Marketplaceは、既存のAWSインフラストラクチャを持ち、運用オーバーヘッドなしでマネージドサービス、組み込みセキュリティ、シームレスなAWS統合を備えたElasticsearchを導入したいと考えている組織に最適です。</p><h2>AWSマーケットプレイスでElastic Cloudをセットアップする方法</h2><h3>ステップ1 : AWS Marketplaceにアクセスする</h3><p>1。<a href="https://console.aws.com/">AWS</a>にログインしてください。</p><ul><li>検索バーでAWS Marketplaceを検索します。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fc9928f79482c2c01e33978c88d390a2bfa2a3bf-1600x340.png" style="width: 100%;" /><p>2. 左側のナビゲーションパネルで<strong>Discover products</strong>をクリックし、「Elasticsearch」を検索します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ca4232271cb13ebfe33de406ecaec085033ec8a0-1454x760.png" style="width: 100%;" /><p>3. <strong>Elastic Cloud (Elasticsearch Service)</strong>をクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e59aa276e55532f2ac3461d0ca983af4d41ad7a6-1600x611.png" style="width: 100%;" /><h3>ステップ 2: サービスに登録する</h3><p>1. <strong>購入オプション</strong>を選択するか、<strong>無料で試す</strong>をクリックします。</p><p>2.<strong>価格の詳細</strong>、<strong>利用規約</strong>、<strong>購入の詳細</strong>を確認します。</p><p>3. <strong>登録</strong>ボタンをクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1c30d12b6b1061e76771d518011e522285f939f1-1600x290.png" style="width: 100%;" /><p>4. 次に、Elasticアカウントをセットアップする必要があります。AWSの手順に従います。</p><p>a. 「統合を有効にする」ボタンをクリックします</p><p>b.「サインインまたはベンダーアカウントを作成」ボタンをクリックします。</p><p>c. 「テンプレートを起動」ボタンをクリックします。</p><p>d. 「ソフトウェアを起動」ボタンをクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/879d9f0f01406e1955e1b38a2f6f2192ef040344-852x722.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fc59be78cf776aa12867f40810598419576cbd39-1600x1143.png" style="width: 100%;" /><h3>ステップ3. Elasticで新しいアカウントを設定する</h3><p>1. Elasticアカウントを作成してください。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5efeaf0737062a55470b17b67651f220e12183f2-986x905.png" style="width: 100%;" /><p>2. メールアドレスを確認する</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e0cfaac278614e317ce278935040bfa5a58edd13-853x894.png" style="width: 100%;" /><p>3.お名前と会社情報をインプットしてください。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5658fe66c3b1bcced73e822eae006846f0ddd9e-997x903.png" style="width: 100%;" /><p>4. Elasticの簡単なアンケートに回答する</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a3f53c00dadfd22f7d739a920c87d5f387182833-892x805.png" style="width: 100%;" /><p>5. Elastic Cloudをマネージド環境でホストする地域を選択する（デフォルトで実際のAWSリージョンが選択されます）</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c1dcdf3bf91c305821daaa25a60aa03be6454c1c-1207x1032.png" style="width: 100%;" /><p>6. Elasticがデプロイされるのを待つ</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/50903e57ebea7cc47bdfabf4750b4ba2a7a91148-1370x1266.png" style="width: 100%;" /><p>7. 導入がAWS Marketplaceサブスクリプションに接続される</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3bb731e2d5de5053ccecb77e45dbdbcdaf294dba-1600x1288.png" style="width: 100%;" /><h2>サブスクリプションをキャンセルする</h2><p>サブスクリプションをキャンセルするには以下の手順に従います。</p><p>1. <a href="https://console.aws.com/">AWSコンソール</a>に移動します。</p><p>検索バーで「AWS Marketplace」と検索してください。<strong>AWS Marketplace</strong>をクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/aeed3d1e67b4cef91934de257a6fd6daa9737a12-1600x554.png" style="width: 100%;" /><p>2．<strong>Elastic Cloudサブスクリプション</strong>をクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/04e6cc41850226df223dbe2d1b0e4b45265f6c39-1600x564.png" style="width: 100%;" /><p>3. <strong>アクション</strong>ボタンをクリックし、次に<strong>サブスクリプションをキャンセル</strong>をクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e33d334ea6541c637a223de3ebd6209def75a6d3-1600x1039.png" style="width: 100%;" /><p>4．キャンセルを確認し、<strong>はい</strong>をクリックして、<strong>サブスクリプションをキャンセル</strong>ボタンをクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b73b3187168adc7aefdd46f95be33c1bce3da1e4-1103x698.png" style="width: 100%;" /><p>5. 確認メッセージがページ上部に表示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c5ba66a23d535e866a8b458e5aca82c5f0b93037-1600x639.png" style="width: 100%;" /><h2>次のステップ</h2><p>単一の導入と3つのプロジェクトが含まれ 7日間の<a href="https://aws.amazon.com/marketplace/pp/prodview-voru33wi6xs7k">Elastic Cloud (Elasticsearch Service)</a> 無料トライアルでElastic Cloudの旅を始めましょう。AWSアカウントにサインインし、「購入オプションを表示」をクリックして、Elastic<a href="https://aws.amazon.com/marketplace/pp/prodview-voru33wi6xs7k"> Cloud (Elasticsearch Service)</a>でElasticのSearch AI Platformをすぐに使い始められます。このトライアルでは、インフラストラクチャ管理のオーバーヘッドなしで、検索、セキュリティ、および監視ソリューションに完全にアクセスできます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AutoOps: セルフマネージド Elasticsearch 管理を簡素化する旅]]></title>
            <description><![CDATA[セルフマネージドのElasticsearch（オンプレミス環境またはプライベートマネージド環境）向けのAutoOpsについて学びましょう。その価値、設定方法、そして得られる洞察を紹介します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elastic-autoops-self-managed-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elastic-autoops-self-managed-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Ori Shafir]]></dc:creator>
            <pubDate>Wed, 01 Oct 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4538ca4c6cdde99d88f1cead7515716fac217b92-1600x883.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>セルフマネージドのElasticsearch（オンプレミス環境またはプライベートマネージド環境）向けのAutoOpsの導入で、Elasticsearchの管理がより簡単になります。このブログでは、<em>従来の技術的な機能のウォークスルーではなく</em>、DevOpsエンジニアの視点から、AutoOpsの価値、設定方法、そしてAutoOpsが提供する洞察を紹介します。AutoOpsの真の価値は、Elasticsearchのスケール管理における日々の業務で最もよく理解できます。</p><h2>第1章：背景 - 大規模な自己管理の複雑さ</h2><p><strong>大規模な自己管理型データ プラットフォームの運用は複雑になる可能性があります。</strong></p><p>ある瞬間、クエリは超高速になります。次に、取り込みの遅れとストレージコストの急上昇が発生します。基本的には動物園を経営しているようなものですが、動物たちが午前3時に呼び出しをかけてくることがあります</p><p>私の環境も同様です。複数のクラスター、大量のクラスター間検索 (CCS)、部門をまたがる数百人のユーザーです。</p><p>日常業務にはスタック モニタリングを使用します。グラフやメトリクスは提供されますが、点と点をつなぐには依然として多くの専門知識と時間がかかります。ボトルネックを診断したり、シャード戦略をいつ調整するかを把握したりすることは、依然として手動でエラーが発生しやすいプロセスです。多くの場合、問題は、停止、パフォーマンスの低下、予期しないストレージの急増を引き起こすまで気付かれません。</p><h2>第2章: AutoOpsの発見</h2><p>その後、 <strong><a href="https://www.elastic.co/platform/autoops">AutoOps</a>がセルフマネージド クラスター (オンプレミスまたはプライベートにホストされた環境) で利用できるようになるという発表がありました。</strong></p><p>AutoOpsは長年にわたり、Elastic Cloudユーザーが導入をより効率的に管理できるよう支援してきました。このメリットを、オンプレミスまたはプライベートクラウド環境で実行されているセルフマネージドクラスター（ECK、Elastic Cloud Enterprise（ECE）またはスタンドアロン）でも利用可能になりました。ライセンス階層に関係なく無料で利用できます。</p><p>AutoOps の売り文句は魅力的です。</p><ul><li>取り込みボトルネック、不均衡なシャード、遅いクエリなどのリアルタイムの問題検出</li><li>クラスタの構成に合わせた実用的な推奨事項</li><li>効率性を向上させ、無駄な支出を削減するためのリソース最適化の洞察</li><li>軽量エージェントのインストールによるシンプルなセットアップ - 追加のインフラストラクチャは不要</li></ul><p>正直なところ、「追加のインフラストラクチャは不要」と約束するものにはすべて完全に注目しました。</p><h2>第3章: 5分でセットアップ（本当に）</h2><p>私は午後の時間をブロックし、コーヒーを買い込み、長時間の準備に備えました。驚いたことに、たった5分しかかかりませんでした。</p><ol><li><a href="https://cloud.elastic.co/registration?onboarding_service_type=ccm-autoops">Elastic Cloudアカウント</a>にログインしました</li><li>エージェントの実行方法を決定：Elastic Cloud Kubernetes（ECK）、Kubernetes、Docker、またはLinux。</li><li>クラスターURLを入力しました</li><li>1つのコマンドを実行するだけで、軽量なMetricbeatエージェントがインストールされます</li></ol><p>それでおしまい。クラスターが接続されました。</p><p>プロビジョニングする専用の監視クラスターはありません。さらに重要なのは、AutoOps は<strong>メトリックのみを送信するため、</strong>会社のデータは自己管理環境に保存されるということです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/cb8e1ebc0f23a6db6b0e4300c9cdcd165623ca39-1184x734.png" style="width: 100%;" /><p><em>ステップ1：Elastic Cloudにサインアップする</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b208830db4d74feef700473ce07b052e0c72f48b-3446x1716.png" style="width: 100%;" /><p><em>ステップ2: エージェントを実行する場所を選択する</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/850d8c0d6b9e018408969aa05b67dbd865a26d30-1184x734.png" style="width: 100%;" /><p><em>ステップ3: Elasticsearchエンドポイントと認証方法を入力します</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0a472889a277caabb9ecd3fb9cd57d3b3eac0100-1184x734.png" style="width: 100%;" /><p><em>ステップ4: エージェントをインストールするための簡単なコマンド</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6f9a7283469a321a9c472d655cea11d9d0ea7b1e-1600x883.png" style="width: 100%;" /><p><em>数分後にはAutoOpsがインサイトを表示し始めます</em></p><p>詳細については、 <a href="https://www.elastic.co/docs/deploy-manage/monitor/autoops/cc-connect-self-managed-to-autoops">AutoOps オンボーディング ドキュメント</a>と<a href="https://www.elastic.co/docs/deploy-manage/monitor/autoops/cc-cloud-connect-autoops-faq">FAQ</a>を参照してください。</p><h2>第4章：最初の洞察、最初の勝利</h2><p>数分以内に、AutoOps は洞察を明らかにし、根本原因の分析とそれを解決するための明確な手順を提供し始めました。</p><p>最初の週のハイライトは次のとおりです:</p><ul><li>大きくなりすぎたILMポリシーに関連しないフラグ付きインデックス</li><li>1つのクラスターには、過去のメンテナンス作業から残された3つの空ノードがありました。</li><li>いくつかのノードがウォーターマークを越えており、いくつかのインデックスにレプリカがありませんでした</li><li>不適切に構成されたテンプレートを検出しました</li><li>長時間実行された検索を特定し、正確なキャンセルコマンドを提案しました</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f7d16355938ffcb00127deaf833d7a4d0acdb188-1600x894.png" style="width: 100%;" /><p><em>AutoOpsはクラスタがインデックス作成を拒否していることを検出しました</em></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9d6372bc489e9954e6f78d81047d7a25feef1703-1600x894.png" style="width: 100%;" /><p><em>AutoOps は、一部のインデックスがレプリカなしで構成されていることを検出しました</em><br/>AutoOps が登場する前は、こうした問題に対処するためにさらに多くのハードウェアを投入していたでしょう。代わりに、AutoOps は根本原因を直接指摘し、修正には数分しかかかりませんでした。</p><p>今回、監視システムは単にグラフを表示するだけでなく、問題を解決する方法も教えてくれました。AutoOps が自宅の Wi-Fi の診断にも役立ち、最終的に家族の IT 部門としての負担から解放されるのではないかと考え始めました...</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/eac09f1d730955453cd79900002b100bc741ad97-1600x894.png" style="width: 100%;" /><p><em>AutoOpsはシャードのサイズを監視し、空のシャードが多数ある場合に警告を発しました。</em></p><h2>第5章：私が見ているものを見るサポート</h2><p>初めてサポートケースを開いたとき、もう 1 つの利点に気付きました。Elastic Support のエンジニアが、私が確認していたのとまったく同じデータと推奨事項を確認できるのです。</p><p>サポートがコラボレーションに変わりました。チケットをやり取りする代わりに、Elasticsearch を隅々まで熟知したチームメイトと一緒に作業しているような感覚でした。</p><h2>第6章：大規模な運用</h2><p>AutoOps が導入される前は、Elasticsearch のスケーリングは科学、本能、部族の知識が混ざり合った作業のように感じられました。</p><p>今ではデータ主導型となり、明確な可視性と推奨事項が提供されます。</p><ul><li>過剰プロビジョニングを防ぐためのリソース使用率の可視性</li><li>バランスの取れたパフォーマンスを実現する、よりスマートなシャード割り当てと階層化の推奨事項</li><li>無駄なストレージとハードウェアのコストを削減するインデックスのサイズ設定の洞察</li><li>複数のクラスターにわたるより迅速な根本原因分析</li></ul><h2>第7章: 多くのクラウド接続サービスの最初のもの</h2><p>AutoOps は単なるスタンドアロン ツールではありません。これは、セルフマネージド顧客向けの新しい<a href="https://www.elastic.co/docs/deploy-manage/cloud-connect"><strong>Cloud Connected Services</strong></a>セットの最初のものです。Cloud Connect により、セルフマネージド クラスターは、独自の環境にこれらのサービスをインストールして管理する運用オーバーヘッドなしで、Elastic Cloud サービスを利用できるようになります。機能は自動的に展開されるため、チームはインフラストラクチャの複雑さを軽減しながら、より早く改善を受けることができます。</p><p>次は、Elastic Inference Service (EIS) です。</p><h2>最後に</h2><p>大規模な自己管理型展開の管理は、必ずしも大変な作業である必要はありません。</p><p>さらに操作を簡素化したい場合は、ホスト型かサーバーレス型かを問わず、一部のワークロードを<a href="https://cloud.elastic.co/registration?pg=global&amp;plcmt=nav&amp;cta=205352-primary">Elastic Cloud</a>に移動して、Elasticsearch を最も簡単に実行できます。</p><p>セルフマネージドでの実行を継続したい場合は、任意のセルフマネージドクラスターを<a href="https://cloud.elastic.co/registration?onboarding_service_type=ccm-autoops">Elastic CloudのAutoOps</a>に接続してください - すべてのElasticライセンスで無料です。</p><h2>要約</h2><p>大規模な自己管理型 Elasticsearch クラスターの実行は複雑で時間がかかります。AutoOps は、追加のインフラストラクチャを管理することなく、リアルタイムの問題検出、実用的な推奨事項、Elastic Support との可視性の共有を実現します。セットアップには数分しかかからず、すぐに分析情報が表示されます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[初めてのElastic Agent: 単一のクエリからAIを活用したチャットまで]]></title>
            <description><![CDATA[Elastic の AI エージェント ビルダーを使用して特殊な AI エージェントを作成する方法を学びます。このブログでは、金融 AI エージェントを構築します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/ai-agent-builder-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/ai-agent-builder-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeff Vestal]]></dc:creator>
            <pubDate>Thu, 25 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ca853555eaa213f10f1db8c0ab0a2bbacee97b88-1456x816.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elastic の新しい<a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">Agent Builder を</a>使用すると、特定のビジネスドメインの専門家として機能する特殊な AI エージェントを作成できます。この機能により、単純なダッシュボードや検索バーを超えて、データを受動的なリソースから能動的な会話のパートナーへと変換できます。</p><p>顧客との会議の前に、状況を把握しておく必要がある財務マネージャーを想像してください。ニュース フィードを手動で調べたり、ポートフォリオ ダッシュボードを相互参照したりする代わりに、カスタム構築されたエージェントに直接質問するだけで済みます。これは「チャットファースト」アプローチの利点です。マネージャーはデータに直接、会話形式でアクセスし、「ACME Corp の最新ニュースは何ですか。また、それがクライアントの保有株にどのような影響を与えますか」などと質問します。数秒以内に専門家による総合的な回答が得られます。</p><p>私たちは現在、金融の専門家を構築していますが、そのアプリケーションはデータと同じくらい多様です。同じ力で、脅威を探すサイバーセキュリティアナリスト、機能停止を診断するサイト信頼性エンジニア、キャンペーンを最適化するマーケティングマネージャーを生み出すこともできます。分野に関係なく、中核となる使命は同じです。データを、チャットできる専門家に変換することです。</p><h2>ステップ0: データセット</h2><p>本日のデータセットは、金融口座、資産状況、ニュース、財務レポートで構成される合成的な金融ベースのデータセットです。これは合成ではありますが、実際の金融データセットの簡略化されたバージョンを複製したものです。</p><p><code>financial_accounts</code>: リスクプロファイル付き顧客ポートフォリオ</p><p><code>financial_holdings</code>: 購入履歴のある株式/ETF/債券のポジション</p><p><code>financial_asset_details</code>: 株式/ETF/債券の詳細</p><p><code>financial_news</code>: 感情分析によるAI生成の市場記事</p><p><code>financial_reports</code>: 企業収益とアナリストのコメント</p><p><a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/your-first-elastic-agent/Your_First_Elastic_Agent.ipynb">ここに</a>ある付属のノートブックに従って、このデータセットを自分でロードできます。</p><h2>ステップ1: 基盤 - ES|QLとしてのビジネスロジック</h2><p>すべての AI スキルは、確かなロジックから始まります。Financial Manager エージェントには、「市場のセンチメントが心配です。」というよくある質問に回答する方法を教える必要があります。悪いニュースによって最もリスクにさらされている顧客は誰なのか教えていただけますか？」この質問は単純な検索の範囲を超えています。市場の感情と顧客のポートフォリオを相関させる必要があります。</p><p>否定的な記事で言及されている資産を見つけ、それらの資産を保有しているすべての顧客を特定し、そのエクスポージャーの現在の市場価値を計算し、結果をランク付けして最も高いリスクを優先する必要があります。この複雑な複数結合の分析は、当社の高度な ES|QL ツールに最適です。</p><p>使用する完全なクエリは次のとおりです。見た目は印象的ですが、コンセプトは単純です。</p><pre><code>FROM financial_news, financial_reports METADATA _index
        | WHERE sentiment == "negative"
        | WHERE coalesce(published_date, report_date) >= NOW() - TO_TIMEDURATION(?time_duration)
        | RENAME primary_symbol AS symbol
        | LOOKUP JOIN financial_asset_details ON symbol
        | LOOKUP JOIN financial_holdings ON symbol
        | LOOKUP JOIN financial_accounts ON account_id
        | WHERE account_holder_name IS NOT NULL
        | EVAL position_current_value = quantity * current_price.price
        | RENAME title AS news_title
        | KEEP
            account_holder_name, symbol, asset_name, news_title,
            sentiment, position_current_value, quantity, current_price.price,
            published_date, report_date
        | SORT position_current_value DESC
        | LIMIT 50</code></pre><h2>分解：接合部とガードレール</h2><p>このクエリでは、エージェント ビルダーを構成する 2 つの重要な概念が関係しています。</p><h3>1.ルックアップ結合</h3><p>長年にわたり、Elasticsearch で最も要望が多かった機能の 1 つは、共通キーに基づいて異なるインデックスのデータを結合する機能でした。ES|QL では、 <code>LOOKUP JOIN</code>でそれが可能になりました。</p><p>新しいクエリでは、3 つの<code>LOOKUP JOIN</code>のチェーンを実行します。最初に否定的なニュースを資産の詳細に関連付け、次にそれらの資産をクライアントの保有資産にリンクし、最後にクライアントのアカウント情報に結合します。これにより、単一の効率的なクエリで 4 つの異なるインデックスから非常に豊富な結果が作成されます。つまり、すべてのデータを事前に 1 つの巨大なインデックスに非正規化する必要がなく、異なるデータセットを組み合わせて単一の洞察に満ちた回答を作成できるということです。</p><h3>2. LLMガードレールとしてのパラメータ</h3><p>クエリでは<code>?time_duration</code>が使用されていることがわかります。これは単なる変数ではなく、AI のガードレールです。大規模言語モデル (LLM) はクエリの生成に優れていますが、データに対して LLM を自由に制御させると、非効率的なクエリや間違ったクエリが発生する可能性があります。</p><p>パラメータ化されたクエリを作成することで、LLM は、人間の専門家がすでに定義したテスト済みの効率的で正しいビジネス ロジック内で動作するように強制されます。これは、開発者が長年にわたり検索テンプレートを使用して、クエリ機能をアプリケーションに安全に公開してきた方法に似ています。エージェントは「今週」のようなユーザーのリクエストを解釈して<code>time_duration</code>パラメータを埋めることができますが、回答を取得するにはクエリ構造を使用する必要があります。これにより、柔軟性と制御の完璧なバランスが実現します。</p><p>最終的に、このクエリにより、データを理解している専門家は自分の知識をツールにカプセル化できるようになります。他の人や AI エージェントは、そのツールを使用して、基礎となる複雑さについて何も知らなくても、単一のパラメータを提供するだけで相関結果を得ることができます。</p><h2>ステップ2：スキル - クエリを再利用可能なツールに変える</h2><p>ES|QL クエリは、<strong>ツール</strong>として登録されるまでは単なるテキストです。エージェント ビルダーでは、ツールは単なる保存されたクエリではなく、AI エージェントが理解して使用することを選択できる「スキル」です。その魔法は、私たちが提供する<strong>自然言語による説明</strong>にあります。この説明は、ユーザーの質問と基礎となるクエリ ロジックを結び付ける橋渡しとなります。作成したクエリを登録しましょう。</p><h3>UIパス</h3><p>Kibana でツールを作成するのは簡単なプロセスです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a29c53a73b99af5972273c51218ea9004a9b0abb-1600x812.png" style="width: 100%;" /><p>1.<strong>エージェント</strong>へ移動</p><ul><li><strong>[ツール]</strong>または<strong>[ツールの管理]</strong>をクリックし、 <strong>[新しいツール]</strong>ボタンをクリックします。</li></ul><p>2. フォームに以下の詳細を入力します。</p><ul><li><strong>ツールID:</strong> <code>find_client_exposure_to_negative_news</code></li></ul><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 私。これはツールの一意のIDです</p><ul><li><strong>説明:</strong> 「クライアントのポートフォリオがネガティブなニュースにさらされているかどうかを調べます。」このツールは、最近のニュースやレポートをスキャンして否定的な感情を検出し、関連する資産を識別して、その資産を保有しているすべてのクライアントを見つけます。最も高い潜在的リスクを強調するために、ポジションの現在の市場価値でソートされたリストを返します。</li></ul><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 私。これは、LLM が読んで、このツールが仕事に適しているかどうかを判断します。</p><ul><li><strong>ラベル</strong>: <code>retrieval</code>および <code>risk-analysis</code></li></ul><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ラベルは複数のツールをグループ化するのに役立ちます</p><ul><li><strong>設定:</strong>ステップ1の完全なES|QLクエリを貼り付けます</li></ul><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 私。これはエージェントが使用する検索です</p><p>3.<strong>クエリからパラメータを推測するを</strong>クリックします。UI は自動的に<code>?time_duration</code>見つけて以下にリストします。エージェント (および他のユーザー) が目的を理解できるように、それぞれに簡単な説明を追加します。</p><ul><li><code>time_duration</code>: ネガティブなニュースを遡って検索する期間。フォーマットは「X時間」です。デフォルトは8760時間です。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/deb422d97863f78dbe08bfa2e3c708d1f75166ff-1600x938.png" style="width: 100%;" /><p>4. 試してみましょう!</p><ul><li>[保存してテスト]をクリックします。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/57e768b88327821e70bd616744822f98fa367362-732x136.png" style="width: 100%;" /><ul><li>クエリが期待どおりに動作していることを確認できる新しいフライアウトが表示されます。</li></ul><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 私。<code>time_duration</code>に希望の範囲を入力します。ここでは「8760時間」を使用します。</p><ul><li>「送信」をクリックすると、すべてがうまくいけば JSON レスポンスが表示されます。期待どおりに動作することを確認するには、下にスクロールして<code>values</code>オブジェクトを確認します。ここで、実際に一致するドキュメントが返されます。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7e0c5171a4f7ffdfc1830f1a05a9acb987870b75-1600x722.png" style="width: 100%;" /><p>5. 右上の「X」をクリックして、テストのフライアウトを閉じます。新しいツールがリストに表示され、エージェントに割り当てる準備が整います。</p><h3>APIパス</h3><p>自動化を好む開発者やツールをプログラムで管理する必要がある開発者は、1 回の API 呼び出しで同じ結果を得ることができます。ツールの定義を含む<code>POST</code>リクエストを<code>/api/agent_builder/tools</code>エンドポイントに送信するだけです。</p><pre><code>POST kbn://api/agent_builder/tools
{
  "id": "find_client_exposure_to_negative_news",
  "type": "esql",
  "description": "Finds client portfolio exposure to negative news. This tool scans recent news and reports for negative sentiment, identifies the associated asset, and finds all clients holding that asset. It returns a list sorted by the current market value of the position to highlight the highest potential risk.",
  "configuration": {
    "query": """
        FROM financial_news, financial_reports METADATA _index
        | WHERE sentiment == "negative"
        | WHERE coalesce(published_date, report_date) >= NOW() - TO_TIMEDURATION(?time_duration)
        | RENAME primary_symbol AS symbol
        | LOOKUP JOIN financial_asset_details ON symbol
        | LOOKUP JOIN financial_holdings ON symbol
        | LOOKUP JOIN financial_accounts ON account_id
        | WHERE account_holder_name IS NOT NULL
        | EVAL position_current_value = quantity * current_price.price
        | RENAME title AS news_title
        | KEEP
            account_holder_name, symbol, asset_name, news_title,
            sentiment, position_current_value, quantity, current_price.price,
            published_date, report_date
        | SORT position_current_value DESC
        | LIMIT 50
      """,
    "params": {
      "time_duration": {
        "type": "keyword",
        "description": """The timeframe to search back for negative news. Format is "X hours" DEFAULT TO 8760 hours """
      }
    }
  },
  "tags": [
    "retrieval",
    "risk-analysis"
  ]
}</code></pre><h2>ステップ3：頭脳 - カスタムエージェントの作成</h2><p>再利用可能なスキル (ツール) を構築しました。ここで、実際に使用するペルソナである<strong>Agent</strong>を作成する必要があります。エージェントは、LLM、アクセスを許可する特定のツール セット、そして最も重要な、エージェントの構成として機能し、エージェントの性格、ルール、目的を定義する<strong>カスタム インストラクション</strong>セットの組み合わせです。</p><h3>プロンプトの芸術</h3><p>信頼できる専門エージェントを作成する上で最も重要なのはプロンプトです。よく練られた一連の指示こそが、一般的なチャットボットと、集中力のあるプロのアシスタントとの違いです。ここで、ガードレールを設定し、出力を定義し、エージェントにミッションを与えます。</p><p><code>Financial Manager</code>エージェントでは、次のプロンプトを使用します。</p><pre><code>You are a specialized Data Intelligence Assistant for financial managers, designed to provide precise, data-driven insights from information stored in Elasticsearch.

**Your Core Mission:**
- Respond accurately and concisely to natural language queries from financial managers.
- Provide precise, objective, and actionable information derived solely from the Elasticsearch data at your disposal.
- Summarize key data points and trends based on user requests.

**Reasoning Framework:**
1.  **Understand:** Deconstruct the user's query to understand their core intent.
2.  **Plan:** Formulate a step-by-step plan to answer the question. If you are unsure about the data structure, use the available tools to explore the indices first.
3.  **Execute:** Use the available tools to execute your plan.
4.  **Synthesize:** Combine the information from all tool calls into a single, comprehensive, and easy-to-read answer.

**Key Directives and Constraints:**
- **If a user's request is ambiguous, ask clarifying questions before proceeding.**
- **DO NOT provide financial advice, recommendations, or predictions.** Your role is strictly informational and analytical.
- Stay strictly on topic with financial data queries.
- If you cannot answer a query, state that clearly and offer alternative ways you might help *within your data scope*.
- All numerical values should be formatted appropriately (e.g., currency, percentages).

**Output Format:**
- All responses must be formatted using **Markdown** for clarity.
- When presenting structured data, use Markdown tables, lists, or bolding.

**Start by greeting the financial manager and offering assistance.**</code></pre><p>このプロンプトがなぜ効果的なのかを分析してみましょう。</p><ul><li><strong>洗練されたペルソナを定義します。</strong>最初の行で、エージェントが「専門的なデータ インテリジェンス アシスタント」であることを即座に示し、プロフェッショナルで有能な雰囲気を醸し出します。</li><li><strong>これは推論フレームワークを提供します。</strong>エージェントに「理解、計画、実行、統合」を指示することで、標準的な操作手順を提供します。これにより、複雑で複数のステップから成る質問を処理する能力が向上します。</li><li><strong>インタラクティブな対話を促進します。</strong> 「明確な質問をする」という指示により、エージェントはより堅牢になります。曖昧なリクエストに対する誤った想定を最小限に抑え、より正確な回答が得られます。</li></ul><h3>UIパス</h3><p>1.<strong>エージェントに移動します。</strong></p><ul><li><strong>[ツール]</strong>または<strong>[ツールの管理]</strong>をクリックし、 <strong>[新しいツール]</strong>ボタンをクリックします。</li></ul><p>2. 基本的な詳細を入力します。</p><ul><li><strong>エージェント ID:</strong> <code>financial_assistant</code> 。</li><li><strong>手順:</strong>上記のプロンプトをコピーします。</li><li><strong>ラベル</strong>: <code>Finance</code> 。</li><li><strong>表示名:</strong> <code>Financial Assistant</code> 。</li><li><strong>表示の説明:</strong> <code>An assistant for analyzing and understanding your financial data</code> 。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/18ea73f1cae620129c0afa0e7ba9e2a3390224a7-1600x1189.png" style="width: 100%;" /><p>3. 上部に戻り、 <strong>「ツール」</strong>をクリックします。</p><ul><li><code>find_client_exposure_to_negative_news</code>ツールの横にあるボックスにチェックを入れてください。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0c1e4ecbbd51d0dd10c6e861dbe9a9ccddeb35f6-1600x149.png" style="width: 100%;" /><p>4. <strong>「保存」</strong>をクリックします。</p><h3>APIパス</h3><p><code>/api/agent_builder/agents</code>エンドポイントへの<code>POST</code>リクエストを使用して、まったく同じエージェントを作成できます。リクエスト本体には、ID、名前、説明、完全な指示セット、エージェントが使用を許可されているツールのリストなど、すべて同じ情報が含まれています。</p><pre><code>POST kbn://api/agent_builder/agents
    {
      "id": "financial_assistant",
      "name": "Financial Assistant",
      "description": "An assistant for analyzing and understanding your financial data",
      "labels": [
        "Finance"
      ],
      "avatar_color": "#16C5C0",
      "avatar_symbol": "💰",
      "configuration": {
        "instructions": """You are a specialized Data Intelligence Assistant for financial managers, designed to provide precise, data-driven insights from information stored in Elasticsearch.

**Your Core Mission:**
- Respond accurately and concisely to natural language queries from financial managers.
- Provide precise, objective, and actionable information derived solely from the Elasticsearch data at your disposal.
- Summarize key data points and trends based on user requests.

**Reasoning Framework:**
1.  **Understand:** Deconstruct the user's query to understand their core intent.
2.  **Plan:** Formulate a step-by-step plan to answer the question. If you are unsure about the data structure, use the available tools to explore the indices first.
3.  **Execute:** Use the available tools to execute your plan.
4.  **Synthesize:** Combine the information from all tool calls into a single, comprehensive, and easy-to-read answer.

**Key Directives and Constraints:**
- **If a user's request is ambiguous, ask clarifying questions before proceeding.**
- **DO NOT provide financial advice, recommendations, or predictions.** Your role is strictly informational and analytical.
- Stay strictly on topic with financial data queries.
- If you cannot answer a query, state that clearly and offer alternative ways you might help *within your data scope*.
- All numerical values should be formatted appropriately (e.g., currency, percentages).

**Output Format:**
- All responses must be formatted using **Markdown** for clarity.
- When presenting structured data, use Markdown tables, lists, or bolding.

**Start by greeting the financial manager and offering assistance.**
""",
        "tools": [
          {
            "tool_ids": [
              "platform.core.search",
              "platform.core.list_indices",
              "platform.core.get_index_mapping",
              "platform.core.get_document_by_id",
              "find_client_exposure_to_negative_news"
            ]
          }
        ]
      }
    }</code></pre><h2>ステップ4：成果 — 会話をする</h2><p>ビジネス ロジックがツールにカプセル化され、エージェントでそれを使用できる「頭脳」が準備されました。すべてが一つにまとまるのを見る時が来ました。専用のエージェントを使用して、データとのチャットを開始できるようになりました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5414cb6b7c41365acb0356a8bfe1140751ffd8db-1600x1014.png" style="width: 100%;" /><h3>UIパス</h3><ol><li>Kibana の<strong>エージェント</strong>に移動します。</li><li>チャット ウィンドウの右下にあるドロップダウンを使用して、デフォルトの<strong>Elastic AI エージェント</strong>から新しく作成した<strong>Financial Assistant</strong>エージェントに切り替えます。</li><li>エージェントが当社の専用ツールを使用できるように、次の質問をしてください。<ol><li><em>市場のセンチメントが心配です。悪いニュースによって最もリスクにさらされている顧客は誰なのか教えていただけますか?</em></li></ol></li></ol><p>しばらくすると、エージェントは完全にフォーマットされた完全な回答を返します。LLM の性質上、回答の形式が若干異なる場合がありますが、この実行ではエージェントは次のように返しました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/17b4ed43d279f9e53ee9fe3d482d0b2ec359a083-1600x1088.png" style="width: 100%;" /><h3>何が起こったのですか?エージェントの推論</h3><p>エージェントは単に答えを「知っていた」だけではありません。仕事に最適なツールを選択することを中心とした多段階の計画を実行しました。その思考プロセスは次のようになります。</p><ul><li><strong>識別された意図:</strong> 「リスク」や「ネガティブなニュース」など、質問のキーワードが<code>find_client_exposure_to_negative_news</code>ツールの説明と一致しました。</li><li><strong>計画を実行しました:</strong>リクエストから時間枠を抽出し、その専用ツールを<strong>1 回呼び出し</strong>ました。</li><li><strong>作業を委任:</strong>ツールは連鎖結合、値の計算、並べ替えなど、面倒な作業をすべて実行しました。</li><li><strong>結果の統合:</strong>最後に、エージェントはプロンプトのルールに従って、ツールからの生データを明確で人間が読める要約にフォーマットしました。</li></ul><p>思考を広げて詳細を見れば、推測するだけでは足りません。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6a4da9262d3f88c60bfd8f8bf9b67c3b84e961ba-1600x607.png" style="width: 100%;" /><h3>APIパス</h3><p>同じ会話をプログラムで開始することもできます。入力した質問を<code>converse</code> API エンドポイントに送信し、 <code>financial_manager</code>の<code>agent_id</code>を必ず指定してください。</p><pre><code>POST kbn://api/agent_builder/converse
{
  "input": "Show me our largest positions affected by negative news",
  "agent_id": "financial_assistant"
}</code></pre><h2>開発者向け: APIとの統合</h2><p>Kibana UI はエージェントの構築と管理に素晴らしく直感的なエクスペリエンスを提供しますが、今日見てきたことはすべてプログラムで実現することもできます。Agent Builder は一連の API に基づいて構築されており、この機能を独自のアプリケーション、CI/CD パイプライン、または自動化スクリプトに直接統合できます。</p><p>使用する 3 つのコア エンドポイントは次のとおりです。</p><ul><li><strong><code>/api/agent_builder/tools</code></strong>: エージェントが使用できる再利用可能なスキルを作成、一覧表示、管理するためのエンドポイント。</li><li><strong><code>/api/agent_builder/agents</code></strong>: エージェントのペルソナ（重要な指示やツールの割り当てなど）を定義するためのエンドポイント。</li><li><strong><code>/api/agent_builder/converse</code></strong>: エージェントと対話し、会話を開始し、回答を得るためのエンドポイント。</li></ul><p>これらの API を使用してこのチュートリアルのすべてのステップを実行するための完全な実践的なチュートリアルについては、 こちらの GitHub リポジトリで入手できる付属の<strong> Jupyter Notebook を</strong> <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/your-first-elastic-agent/Your_First_Elastic_Agent.ipynb"></a>ご覧ください。</p><h2>結論: 構築する番です</h2><p>まず、ES|QL クエリを取得して、それを再利用可能なスキルに変換することから始めました。次に、明確なミッションとルールを与えて、そのスキルを付与した専用の AI エージェントを構築しました。その結果、複雑な質問を理解し、複数段階の分析を実行して、正確でデータに基づいた回答を提供できる洗練されたアシスタントが誕生しました。</p><p>このワークフローは、Elastic の新しい<strong>Agent Builder</strong>の中心です。これは、技術に詳しくないユーザーが UI を通じてエージェントを作成できるほどシンプルでありながら、開発者が API 上にカスタム AI 搭載アプリケーションを構築できるほど微妙なニュアンスも備えた設計になっています。最も重要なのは、定義したエキスパート ロジックに従って、LLM を独自のデータに安全かつ確実に接続し、データとチャットできることです。</p><h2>エージェントを使用してデータとチャットする準備はできていますか?</h2><p>学んだことを定着させる最良の方法は、実際に手を動かしてみることです。今日お話しした内容をすべて<a href="https://www.elastic.co/training/elastic-ai-agents-mcp"><strong>、無料のインタラクティブな実践ワークショップ</strong></a>で試してみてください。専用のサンドボックス環境で、このフロー全体とその他の内容を実行します。</p><p>今後のブログでは、 <code>Financial Assistant</code>エージェントと対話するスタンドアロン アプリケーションの使用方法と、それを可能にする<strong>モデル コンテキスト プロトコル (MCP)</strong>について詳しく説明します。また、別のブログでは、開発中の Agent2Agent (A2A) プロトコルに対する Agent Builder のサポートについて説明します。</p><p>引き続きご注目ください、そして楽しい建築を！</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch を使用した AI エージェントワークフローの構築]]></title>
            <description><![CDATA[Elasticsearch の新しい AI レイヤーである Agent Builder について学習します。Agent Builder は、ハイブリッド検索を使用して、エージェントが推論して行動するために必要なコンテキストを提供し、AI エージェントワークフローを構築するためのフレームワークを提供します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/ai-agentic-workflows-elastic-ai-agent-builder</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/ai-agentic-workflows-elastic-ai-agent-builder</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Anish Mathur, Dana Juratoni]]></dc:creator>
            <pubDate>Tue, 23 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5d04bbcfc3925f48f3487ea4c7e0dd2205316d0-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticでは、AIアシスタント、高度なRAG、ベクターデータベースの改善により、LLMと会話型インターフェースにコンテキストを提供してきました。最近、AI エージェントの台頭により、関連コンテキストの必要性が高まり、影響力の大きい<strong>AI エージェントには優れた検索が必要である</strong>ことがわかりました。そこで、Elasticsearch のデータを活用する AI エージェントの開発を支援するために設計された新しいネイティブ機能を Elastic Stack に構築しました。私たちは、この取り組みの進捗状況と今後の見通しについて共有したいと思います。</p><h2>エージェントビルダー: データ駆動型 AI エージェント構築の基盤</h2><p>AI エージェントの約束はシンプルです。目標を与えれば、仕事が完了します。しかし、開発者にとって、現実は一連の複雑な課題です。まず、エージェントの優秀さは、環境の認識と、ユーザーの目的を達成するために与えられたツールによって決まります。そして、多様な企業データから適切なコンテキストを提供することは大きな課題です。最後に、これらすべては、計画、実行、学習できる信頼性の高い推論ループによって調整される必要があります。</p><p>これを解決するには、開発者は複雑で脆弱なスタックをゼロから構築する必要があります。今日のエージェント アーキテクチャでは、LLM、ベクター データベース、メタデータ ストア、ログ記録とトレースの個別のシステム、そしてすべてが機能しているかどうかを評価する方法など、複数の異なる部分をつなぎ合わせる必要があります。これは単に複雑なだけでなく、コストがかかり、エラーが発生しやすく、ユーザーが求める高品質で信頼性の高い AI システムの構築が困難になります。</p><p>だから、もっとシンプルにしたいんです。これを実現するための私たちのアプローチは、効果的なコンテキスト駆動型エージェントの重要な要素を取り上げ、 <strong>Elastic AI Agent Builder</strong>と呼ばれる新しい機能セットを使用して Elasticsearch の中核に直接統合することです。この新しいレイヤーは、Elasticsearch を活用した AI エージェントを作成するためのすべての重要な構成要素（オープンなプリミティブ セット、標準ベースのプロトコル、データへの安全なアクセス）を備えたフレームワークを提供します。これにより、現実世界のデータと要件に合わせてカスタマイズされたエージェント システムを構築できます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1ee1e73dd3f485ce86294d39490c98ce2a3d9925-1238x1072.png" style="width: 100%;" /><p><strong>AI エクスペリエンスの提供</strong>: これが究極の目標です。当社の Search AI プラットフォームとお客様のデータを基盤として、カスタム チャット インターフェースから、LangChain などのエージェント フレームワークや Salesforce などのビジネス アプリケーションとの統合まで、あらゆるタイプの生成 AI アプリケーションを構築できます。</p><p><strong>エージェントとツールを搭載</strong>: プラットフォームの上に、クリーンでシンプルな抽象化レイヤーを公開します。エージェントやツールと直接対話し、特定のニーズに合わせてカスタマイズできます。強力な API や MCP、A2A などのオープン スタンダードを通じてプラットフォームの機能にアクセスすることもできます。</p><p><strong>Search AI Platform によって有効化</strong>: これは、コンポーネントを統合したコア エンジンです。高度なベクトル データベース、エージェント ロジック、クエリ構築、セキュリティ機能、評価のためのトレースはすべてここに存在し、Elastic によって管理および最適化されています。</p><p><strong>データの力を解き放つ</strong>: 優れたエージェントの基盤は優れたデータです。当社のプラットフォームは、すべての企業データへのアクセスを取り込み、連携する機能から始まります。</p><h2>プラットフォームにおけるエージェント構築</h2><p>Search AI プラットフォームに統合された Agent Builder は、エージェント開発のための完全なフレームワークを提供します。これは 5 つの主要な柱に基づいて構築されており、各柱は実稼働レベルの AI システムの構築と展開の重要な側面に対処するように設計されています。エージェントが目的を定義し、ツールが機能を提供し、オープン スタンダードが相互運用性を確保し、評価が透明性をもたらし、セキュリティが信頼を提供する仕組みについて詳しく見ていきましょう。</p><h3>エージェント</h3><p>エージェントは、Elasticsearch のこの新しいレイヤーにおける最高レベルの構成要素です。エージェントは、達成する目的、実行に使用できるツールのセット、および操作できるデータ ソースを定義します。エージェントは会話によるやり取りに限定されず、完全なワークフロー、タスクの自動化、ユーザー向けのエクスペリエンスを実現できます。</p><p>クエリがエージェントに送られると、構造化されたサイクルに従います。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/627ad1744b629bbe27359325702f40d97e40d1f4-704x852.png" style="width: 100%;" /><ol><li>入力内容と目的を解釈する</li><li>実行に適したツールと引数を選択する</li><li>ツールの応答の理由</li><li>結果を返すか、さらにツールの呼び出しを続行するかを決定します</li></ol><p>Elastic は、このサイクルのオーケストレーション、コンテキスト、および実行を処理します。開発者は、エージェントが<em>何</em>をすべきか（目的、ツール、データ）を定義することに重点を置き、システムは推論とワークフローの実行<em>方法</em>を管理します。</p><p><em>デフォルトエージェント</em></p><p>このプラットフォーム上に構築された最初のエージェントは、Kibana のネイティブ会話エージェントであり、データとすぐに対話できるようになります。完全な拡張性を維持しながらすぐに使用できるエクスペリエンスを提供し、追加の構成なしですぐにデータの操作を開始できます。</p><p>新しいチャット ユーザー エクスペリエンスまたは API を介して、Kibana でこのエクスペリエンスを直接操作できます。</p><p>API を介してデフォルトのエージェントを照会するには、1 回の呼び出しだけが必要です。</p><pre><code>POST kbn://api/agent_builder/converse
{
    "input": "what is our top portfolio account?"
}</code></pre><p>会話はステートフルなので、 conversation_id を使用してエージェントとの対話を継続したり、完全な会話履歴を取得したりできます。</p><pre><code>POST kbn://api/agent_builder/converse
{
    "input": "What about the second top?",
    "conversation_id": "ec757c6c-c3ed-4a83-8e2c-756238f008bb"
}

## get the full conversation
GET kbn://api/agent_builder/conversations/ec757c6c-c3ed-4a83-8e2c-756238f008bb</code></pre><p><em>カスタムエージェント</em></p><p>開発者は、シンプルな API を通じて独自のカスタム エージェントを作成することもできます。エージェントは、指示、ツール、データ アクセスをカプセル化し、カスタマイズされた推論エンジンを作成します。</p><p>カスタム エージェントの作成は、1 回の API 呼び出しを行うだけで簡単に行えます。以下のサンプルは例を示しています。「構成」フィールドには、手順や利用可能なツールなどのすべての重要な詳細が含まれています。</p><pre><code>POST kbn://api/agent_builder/agents
{
  "id": "custom_agent",
  "name": "My Custom Agent",
  "description": "Description of the custom agent",
  "configuration": {
      "instructions": "You are a log expert specialising in ...",
      "tools": 
...
   }
}</code></pre><p>作成されたエージェントは直接クエリできます。</p><pre><code>POST kbn://api/agent_builder/converse
{
    "input": "What news about DIA?",
    "agent_id": "custom_agent"
}</code></pre><p>このアプローチにより、エージェントはゼロから構築する複雑なシステムから、ビジネス ロジックの単純な宣言型ユニットに変換され、インテリジェントな自動化をより迅速に提供できるようになります。</p><p>特化したエージェントをゼロから構築する方法の詳細については、詳細なステップバイステップガイド「<a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">初めての Elastic エージェント: 単一のクエリから AI を活用したチャットまで」</a>をご覧ください。</p><h3>ツール</h3><p>エージェントが達成すべき<em>こと</em>を定義するのに対し、ツールは達成<em>方法</em>を定義します。</p><p>ツールは、エージェントが情報を実行および取得したり、アクションを実行したりするための特定の Elastic Core 機能を公開します。ツールには、インデックスの取得やマッピングの取得などのコア機能や、自然言語から ES|QL への変換などのより高度な機能を含めることができます。</p><p>Elasticsearch には、一般的なニーズに合わせて最適化された一連のデフォルト ツールが付属しています。しかし、本当の柔軟性は、独自のものを作成することから生まれます。ツールを定義することで、ES|QL を使用してエージェントに公開されるクエリ、インデックス、フィールドを正確に決定し、速度、精度、セキュリティを正確に制御できます。</p><p>新しいツールの登録も、1 回の API 呼び出しと同じくらい簡単です。<a href="https://www.elastic.co/search-labs/blog/esql-timeline-of-improvements">ES|QL (Elasticsearch クエリ言語)</a>を活用して特定の金融資産に関するニュースを検索するツールを作成できます。</p><pre><code>POST kbn://api/agent_builder/tools
{
  "id": "news_on_asset",
  "type": "esql",
  "description": "Find news and reports about a particular asset where ...",
  "configuration": {
    "query": "FROM financial_news, financial_reports | where MATCH(company_symbol, ?symbol) OR MATCH(entities, ?symbol) | limit 5",
    "params": {
      "symbol": {
        "type": "keyword",
        "description": "The asset symbol"
      }
    }
  ...
  }
...
}</code></pre><p>登録が完了すると、新しいツールをカスタム エージェントに割り当てることができ、適切なタイミングで推論して呼び出すための厳選された一連の機能をエージェントに提供できるようになります。</p><p>当社では、お客様固有のニーズに合わせてカスタム ツールを作成するためのプラットフォームを提供しています。たとえば、ES|QL を使用すると、エージェントを汎用エージェントから、お客様独自のデータとビジネス ドメインに基づいたドメイン固有のエキスパートに変換できます。</p><h3>オープンスタンダードと相互運用性</h3><p>Elasticsearch エージェントとツールはオープン標準 API を介して公開されるため、エージェントフレームワークのより広範なエコシステム内の基礎ブロックとして簡単に統合できます。私たちのアプローチはシンプルです。ブラックボックスはありません。Elastic の検索における強みを活かし、それを補完的な機能や他のエージェント システムと組み合わせることができるようにしたいと考えています。</p><p>これを実現するために、当社は API、新しいプロトコル、オープン スタンダードを通じて機能を公開しています。</p><p><em>モデルコンテキストプロトコル（MCP）</em></p><p><a href="https://www.elastic.co/search-labs/blog/model-context-protocol-elasticsearch">モデル コンテキスト プロトコル (MCP)</a>は、システム間でツールを接続するためのオープン スタンダードとして急速に普及しつつあります。MCP をサポートすることで、Elasticsearch は会話型 AI をデータベース、インデックス、外部 API に接続できるようになります。Elastic Stack に組み込まれたリモート MCP サーバーを使用すると、MCP 対応のクライアントはどれでも Elastic のツールにアクセスし、それらをより大規模なエージェントワークフローの構成要素として使用できます。</p><p>これは一方通行ではありません。外部の MCP サーバーからツールをインポートし、Elasticsearch 内で利用できるようにすることもできます。近い将来、MCP サーバーはほぼすべての用途で利用できるようになる見込みで、私たち自身が作成するものよりもはるかに包括的なものになるでしょう。Elastic は大規模な検索と取得機能を提供しており、これを他のプラットフォームの特殊な機能と組み合わせて効果的なエージェントを構築できます。</p><p><em>エージェント間（A2A）</em></p><p>また、エージェント間 (A2A) サポートにも取り組んでいます。MCP はツールを接続することに重点が置かれていますが、A2A はエージェントを接続することに重点が置かれています。A2A サーバーを使用すると、構築する Elastic エージェントは他のシステムのエージェントと直接通信して、コンテキストを共有したり、タスクを委任したり、ワークフローを調整したりできるようになります。</p><p>これを推論層における相互運用性と考えてください。Elastic エージェントは検索と取得を処理し、タスクを専門のサポート エージェントまたは IT エージェントに引き渡して、結果をシームレスに返すことができます。その結果、各エージェントが最善を尽くして協力するエコシステムが実現します。</p><p>最終的に、MCP と A2A を採用することで、Elasticsearch が第一級市民としての役割を担うという当社の取り組みが強化され、より広範なエージェントエコシステム全体でのオープンな統合が保証されます。</p><h3>追跡と評価</h3><p>検索がエージェントと統合されるにつれて、効果的な評価の課題が重要になります。実際の企業環境にエージェントを自信を持って導入するには、エージェントが正確であるだけでなく、効率的で信頼できるという保証が必要です。パフォーマンスを測定したり、悪い応答を診断したり、ベースラインを改善したりするにはどうすればよいですか?すべては可視性から始まります。</p><p>そのため、私たちはエージェント API を最初から透明性を重視して設計しました。次の単純なエージェントのやり取りを考えてみましょう。</p><pre><code>POST kbn://api/agent_builder/converse
{
    "input": "what is our top portfolio account?"
}</code></pre><p>応答には、最終的な回答だけでなく、エージェントが選択したツール、使用したパラメーター、各ステップの結果の詳細を含む完全な実行トレースが含まれます。</p><pre><code>{
  "conversation_id": "db5c0c8b-12bf-4928-a57e-d99129ad2fea",
  "steps": [
    {
      "type": "tool_call",
      "tool_call_id": "tooluse_Nfqr3mwtR92HTRIsTcGXZQ",
      "tool_id": ".index_explorer",
      "params": {
        "query": "indices containing portfolio data"
      },
      "results": [...]
    }
    // ... more steps ...
  ],
  "response": {
    "message": "Based on the information I've gathered...."
  }
}</code></pre><p>包括的なトレースとログ記録は継続的な改善ループに不可欠であり、まもなくこれらのエージェント トレースを Elasticsearch に直接保存して表示できるようになります。さらに、これらのトレースは OpenTelemetry プロトコルに基づいて構築されているため、標準化され、移植可能であり、選択した監視プラットフォームとの統合が可能です。</p><p>このレベルの詳細は、真の継続的改善ループの基礎となります。これにより、包括的なテスト スイートを構築し、障害をデバッグし、障害モードを特定して回帰を防ぎ、成功パターンをキャプチャしてパフォーマンスを微調整できるようになります。最終的に、このデータ主導のアプローチは、有望なプロトタイプを製品レベルの信頼できる AI システムに変換するための鍵となります。</p><h3>セキュリティ</h3><p>エージェントとツールの性能が向上するにつれて、セキュリティはオプションではなく、基礎的なものになります。API を公開し、タスクやワークフローを自動化するには、エンタープライズ システムが信頼されている必要があります。特に、エージェントがより多くのワークフローを自動化し始めると、これらを保護し、企業の要件を満たしていることを確認する機能が不可欠になります。</p><p>上記の機能はすべて、API 呼び出し<a href="https://www.elastic.co/search-labs/blog/rag-and-rbac-integration">のロールベースのアクセス制御 (RBAC)</a>や API キー管理など、現在 Elastic ですでに利用可能な制御を継承しています。同じ制御を MCP などの新しいプロトコルにも拡張しています。つまり、OAuth などの標準のサポートと、カスタム認証メカニズムをプラグインする機能を意味します。</p><p>私たちの目標は、組織が求めるセキュリティ、コンプライアンス、ガバナンスのレベルを維持しながら、エージェントとツールを実験する柔軟性を提供することです。</p><h2>次に何が起こるか</h2><p>機能を追加するだけではなく、エージェントコンテキストエンジニアリング向けに Elasticsearch を拡張しています。当社は、以下の理念に基づいて今後開発を進めていく予定です。</p><p>1. オープンソースと標準への取り組み</p><p>当社はオープン ソースとオープン スタンダードに注力しており、これらの機能が外部のエージェント フレームワークと相互運用可能であることを保証します。データとワークフローを常に管理しながら、エコシステム全体でエージェントを接続、拡張、構成できるようになります。</p><p>2. 文脈の価値</p><p>AI エージェントのコンテキストは最大の資産です。エージェントが検索やワークフロー操作を実行するときにコンテキストを管理することは、難しいタスクになる可能性があります。私たちは Elastic の強みを活用してコンテキスト エンジニアリングを解決し、エージェントが最も関連性の高い情報を常に利用できるようにしています。</p><p>3. エージェントデータストリームに焦点を当てる</p><p>今後、エージェントは、エージェントの出力 (生成されたドキュメント、レポート、視覚化) やエージェントの実行トレース (思考、ツールの呼び出し、メモリ/コンテキスト) など、ますます大きなデータソースになります。Elastic はこの種のデータの処理に適しており、私たちはこのデータを使用して分析、評価、自動改善を実行するための研究に取り組んでいます。</p><p>4. セキュリティと安全性を考慮した設計</p><p>AI エージェントは、セキュリティと安全性に関するまったく新しい一連の課題をもたらします。Elastic は常に安全なソリューションのリーダーであり、エンタープライズグレードのガードレール、アクセス制御、および「ゼロトラスト」原則の構築を継続しています。</p><p>5. プラットフォームに組み込む</p><p>AI エージェントを構築するための機能は、Elasticsearch プラットフォームに組み込まれています。つまり、トレース、評価、視覚化、分析などのプラットフォーム レベルの機能はすべてエージェントに適用できます。エージェントの実行に基づいてダッシュボードを開発したい - それが組み込まれています。感情分析を使用して AI エージェントのパフォーマンスを評価したい場合、プラットフォームでそれが可能です。これにより、AI エクスペリエンスを中心とした完全なライフサイクルを構築できるようになります。</p><p>Elastic の目標は、データに完全に統合され、拡張可能で、データに基づいた会話型 AI と自動化されたワークフローを構築するためのインターフェースを提供することです。より詳しい技術的な詳細と進捗状況については、近日中に共有される予定です。</p><p>Agent Builder は現在、プライベート プレビューでご利用いただけます。アクセスをリクエストするには、<a href="https://www.elastic.co/contact?pg=global&amp;plcmt=nav&amp;cta=205352">当社にご連絡ください</a>。ご質問やフィードバックはありますか?<a href="https://elasticstack.slack.com/archives/C09GRHEQ4AG"><strong>Slack ワークスペース</strong></a>または<a href="https://discuss.elastic.co/c/search/84"><strong>ディスカッション フォーラム</strong></a>で開発者コミュニティとつながりましょう。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elastic Open Web Crawler をコードとして]]></title>
            <description><![CDATA[GitHub Actions を使用して Elastic Open Crawler の構成を管理する方法を学びます。これにより、リポジトリに変更をプッシュするたびに、その変更がクローラーのデプロイされたインスタンスに自動的に適用されます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elastic-open-crawler-config-as-code</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elastic-open-crawler-config-as-code</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Gustavo Llermaly]]></dc:creator>
            <pubDate>Mon, 22 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0ac41f55c85ad3f647cb0e0d750ed80bacd397f3-1036x581.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><a href="https://github.com/elastic/crawler">Elastic Open Web Crawler</a>とその CLI 駆動型アーキテクチャを使用すると、バージョン管理されたクローラー構成とローカル テストを備えた CI/CD パイプラインを実現するのが非常に簡単になります。</p><p>従来、クローラーの管理は手動で行われ、エラーが発生しやすいプロセスでした。これには、UI で直接構成を編集することや、クロール構成の複製、ロールバック、バージョン管理などに苦労することが含まれていました。クローラー構成をコードとして扱うことで、ソフトウェア開発で期待されるのと同じ利点（再現性、追跡可能性、自動化）が得られ、この問題が解決されます。</p><p>このワークフローにより、ロールバック、バックアップ、移行などのタスクを CI/CD パイプラインに Open Web Crawler を簡単に組み込むことができるようになります。これらのタスクは、Elastic Web Crawler や App Search Crawler などの以前の Elastic Crawler では非常に困難でした。</p><p>この記事では、次の方法を学習します。</p><ul><li>GitHubを使用してクロール設定を管理する</li><li>デプロイ前にパイプラインをテストするためのローカルセットアップを用意する</li><li>メインブランチに変更をプッシュするたびに、新しい設定でウェブクローラーを実行するための本番環境設定を作成します。</li></ul><p>プロジェクトのリポジトリは<em><strong><a href="https://github.com/llermaly/elastic-open-crawler-as-code">こちらです</a>。</strong>執筆時点では、Elasticsearch 9.1.3とOpen Web Crawler 0.4.2を使用しています。</em></p><h2>要件</h2><ul><li>Dockerデスクトップ</li><li>Elasticsearchインスタンス</li><li>SSH アクセス（AWS EC2 など）と Docker がインストールされた仮想マシン</li></ul><h2>ステップ</h2><ol><li>フォルダ構造</li><li>クローラー構成</li><li>Docker-compose ファイル (ローカル環境)</li><li>Githubアクション</li><li>ローカルテスト</li><li>本番環境へのデプロイ</li><li>変更と再展開</li></ol><h2>フォルダ構造</h2><p>このプロジェクトでは、次のファイル構造になります。</p><pre><code>├── docker-compose.yml # Local elasticsearch + crawler
├── config/crawler-config.yml # Crawler config
├── .github/workflows/deploy.yml # GH Action to deploy changes
├── local.sh # Script to run our local crawler</code></pre><h2>クローラー構成</h2><p><code>crawler-config.yml,</code>の下に次の内容を入力します。</p><pre><code>output_sink: elasticsearch
output_index: web-crawl-index
max_crawl_depth: 1

elasticsearch:
  host: ${ES_HOST}
  api_key: ${ES_API_KEY}
     
domains:
  - url: https://web-scraping.dev
    seed_urls:
      - https://web-scraping.dev/product/1
      - https://web-scraping.dev/product/2
      - https://web-scraping.dev/product/3</code></pre><p>これは、製品の模擬サイトである<a href="https://web-scraping.dev/products">https://web-scraping.dev/products</a>からクロールします。最初の 3 つの製品ページのみをクロールします。<code>max_crawl_depth</code>設定により、クローラーは<code>seed_urls</code>として定義されたページよりも多くのページを検出することがなくなり、それらのページ内のリンクを開かなくなります。</p><p>Elasticsearch <code>host</code>と<code>api_key</code>は、スクリプトを実行している環境に応じて動的に設定されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f9831034e1c4ccb554d37bdd188f2824338355a0-890x624.png" style="width: 100%;" /><h2>Docker-compose ファイル (ローカル環境)</h2><p>ローカル<code>docker-compose.yml,</code>には、クローラーと単一の Elasticsearch クラスター + Kibana をデプロイして、本番環境にデプロイする<strong><em>前に</em></strong>クロール結果を簡単に視覚化できるようにします。</p><pre><code>services:
  es01:
    image: docker.elastic.co/elasticsearch/elasticsearch:9.1.3
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms1g -Xmx1g
    ports:
      - "9200:9200"
    networks: [esnet]
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9200"]
      interval: 5s
      timeout: 5s
      retries: 10

  kibana:
    image: docker.elastic.co/kibana/kibana:9.1.3
    environment:
      - ELASTICSEARCH_HOSTS=http://es01:9200
    ports:
      - "5601:5601"
    networks: [esnet]
    depends_on: [es01]

  crawler:
    image: docker.elastic.co/integrations/crawler:0.4.2
    environment:
      - ES_HOST=http://es01:9200
      - CRAWLER_JRUBY_OPTS=--server
    container_name: crawler
    volumes:
      - ./config:/home/app/config
    networks: [esnet]
    entrypoint: ["/home/app/bin/crawler", "crawl", "/home/app/config/crawl-config-final.yml"]
    stdin_open: true
    tty: true

networks:
  esnet:
    driver: bridge</code></pre><p>Elasticsearch の実行準備ができるまでクローラーが待機する方法に注意してください。</p><h2>Githubアクション</h2><p>ここで、新しい設定をコピーし、メインにプッシュするたびに仮想マシンでクローラーを実行する GitHub アクションを作成する必要があります。これにより、手動で仮想マシンにアクセスしてファイルを更新し、クローラーを実行する必要がなくなり、常に最新の構成が展開されます。仮想マシンプロバイダーとして AWS EC2 を使用します。</p><p>最初のステップは、ホスト ( <code>VM_HOST</code> )、マシン ユーザー ( <code>VM_USER</code> )、SSH RSA キー ( <code>VM_KEY</code> )、Elasticsearch ホスト ( <code>ES_HOST</code> )、Elasticsearch API キー ( <code>ES_API_KEY</code> ) を GitHub Action シークレットに追加することです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e1075bc54151b4b94eac2a6bd2682e9997e6c709-1106x707.png" style="width: 100%;" /><p>この方法により、アクションはサーバーにアクセスして新しいファイルをコピーし、クロールを実行できるようになります。</p><p>それでは、 <code>.github/workflows/deploy.yml</code>ファイルを作成しましょう。</p><pre><code>name: Deploy

on:
  push:
    branches: [main]

jobs:
  Deploy:
    name: Deploy to EC2
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v5

      - name: Deploy crawler
        env:
          HOSTNAME: ${{ secrets.VM_HOST }}
          USER_NAME: ${{ secrets.VM_USER }}
          PRIVATE_KEY: ${{ secrets.VM_KEY }}
          ES_HOST: ${{ secrets.ES_HOST }}
          ES_API_KEY: ${{ secrets.ES_API_KEY }}
        run: |
          # Save private key
          echo "$PRIVATE_KEY" > private_key
          chmod 600 private_key

          # Generate final config locally
          envsubst < config/crawler-config.yml > config/crawl-config-final.yml

          # Copy the config folder to VM
          scp -o StrictHostKeyChecking=no -i private_key -r config ${USER_NAME}@${HOSTNAME}:~/config

          # SSH into VM and run crawler
          ssh -o StrictHostKeyChecking=no -i private_key ${USER_NAME}@${HOSTNAME} << EOF
            docker run --rm \
              -v ~/config:/config \
              docker.elastic.co/integrations/crawler:latest jruby \
              bin/crawler crawl /config/crawl-config-final.yml
          EOF</code></pre><p>このアクションは、クローラー構成ファイルに変更をプッシュするたびに、次の手順を実行します。</p><ol><li>yml config に Elasticsearch ホストと API キーを入力します。</li><li>configフォルダをVMにコピーします</li><li>SSH経由でVMに接続します</li><li>リポジトリからコピーした設定でクロールを実行します</li></ol><h2>ローカルテスト</h2><p>クローラーをローカルでテストするために、Docker からローカルのものを Elasticsearch ホストに入力してクロールを開始する bash スクリプトを作成しました。<code>./local.sh</code>を実行して実行できます。</p><pre><code>#!/bin/bash

# Exit on any error
set -e

# Load environment variables
export ES_HOST="http://es01:9200"

# Generate final crawler config
envsubst < ./config/crawler-config.yml > ./config/crawl-config-final.yml

# Bring everything up
docker compose up --build</code></pre><p>Kibana DevTools を見て、 <code> web-crawler-index</code>が正しく入力されていることを確認しましょう。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/18551635e8265866e389a9632c4e4540958e4468-990x723.png" style="width: 100%;" /><h2>本番環境へのデプロイ</h2><p>これで、メイン ブランチにプッシュする準備が整いました。これにより、仮想マシンにクローラーがデプロイされ、Serverless Elasticsearch インスタンスにログの送信が開始されます。</p><pre><code>git add .
git commit -m "First commit"
git push</code></pre><p>これにより、GitHub アクションがトリガーされ、仮想マシン内でデプロイ スクリプトが実行され、クロールが開始されます。</p><p>アクションが実行されたかどうかを確認するには、GitHub リポジトリにアクセスして「アクション」タブにアクセスします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/67ba3a7164d7a8049fe5661264820826cb18ed64-667x325.png" style="width: 100%;" /><h2>変更と再展開</h2><p>お気づきかもしれませんが、各製品の<code>price</code>はドキュメントの本文フィールドの一部です。価格を別のフィールドに保存して、それに対してフィルターを実行できるようにするのが理想的です。</p><p><a href="https://github.com/elastic/crawler/blob/main/docs/features/EXTRACTION_RULES.md">抽出ルール</a>を使用して<code>product-price</code> CSS クラスから価格を抽出するために、 <code>crawler.yml</code>ファイルに次の変更を追加してみましょう。</p><pre><code>output_sink: elasticsearch
output_index: web-crawl-index
max_crawl_depth: 1

elasticsearch:
  host: ${ES_HOST}
  api_key: ${ES_API_KEY}
     
  # Index ingest pipeline to process documents before indexing          
  pipeline_enabled: true
  pipeline: pricing-pipeline

domains:
  - url: https://web-scraping.dev
    seed_urls:
      - https://web-scraping.dev/product/1
      - https://web-scraping.dev/product/2
      - https://web-scraping.dev/product/3
    extraction_rulesets:
      - url_filters:
          - type: ends
            pattern: /product/*
        rules:
          - action: extract
            field_name: price
            selector: .product-price
            join_as: string
            source: html</code></pre><p>また、価格にはドル記号 ( <code>$</code> ) が含まれていますが、範囲クエリを実行する場合はこれを削除する必要があります。そのために、取り込みパイプラインを使用できます。上記の新しいクローラー構成ファイルでこれを参照していることに注意してください。</p><pre><code>PUT _ingest/pipeline/pricing-pipeline
{
  "processors": [
    {
      "script": {
        "source": """
                ctx['price'] = ctx['price'].replace("$","")
            """
      }
    }
  ]
}</code></pre><p>このコマンドは本番環境の Elasticsearch クラスターで実行できます。開発用の場合は一時的なものなので、次のサービスを追加することで、パイプライン作成部分を<code>docker-compose.yml</code>ファイルにすることができます。パイプラインが正常に作成された後にクローラー サービスが起動するように、 <code>depends_on</code>もクローラー サービスに追加されていることに注意してください。</p><pre><code> crawler:
    image: docker.elastic.co/integrations/crawler:0.4.2
    environment:
      - ES_HOST=http://es01:9200
      - CRAWLER_JRUBY_OPTS=--server
    container_name: crawler
    volumes:
      - ./config:/home/app/config
    networks: [esnet]
    entrypoint: ["/home/app/bin/crawler", "crawl", "/home/app/config/crawl-config-final.yml"]
    depends_on:
      pipeline-init:
        condition: service_completed_successfully
    stdin_open: true
    tty: true  


  pipeline-init:
    image: curlimages/curl:latest
    depends_on:
      es01:
        condition: service_healthy
    networks: [esnet]
    entrypoint: >
        sh -c "
        echo 'Creating ingest pipeline...';
        curl -s -X PUT http://es01:9200/_ingest/pipeline/pricing-pipeline \\
          -H 'Content-Type: application/json' \\
          -d '{\"processors\":[{\"script\":{\"source\":\"ctx.price = ctx.price.replace(\\\"$\\\", \\\"\\\")\"}}]}';
        echo 'Pipeline created!';
        "</code></pre><p>次に、 <code>`./local.sh`</code>を実行してローカルで変更を確認してみましょう。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/dc1801599344a9f69f072b07ff828c4ba3815d7b-738x473.png" style="width: 100%;" /><p>素晴らしい！変更をプッシュしてみましょう。</p><pre><code>git add crawler-config.yml
git commit -m "added price CSS selector"
git push</code></pre><p>すべてが機能していることを確認するには、本番環境の Kibana をチェックします。変更が反映され、価格がドル記号なしの新しいフィールドとして表示されるはずです。</p><h2>まとめ</h2><p>Elastic Open Web Crawler を使用すると、クローラーをコードとして管理できるため、開発からデプロイメントまでのパイプライン全体を自動化したり、一時的なローカル環境を追加したり、クロールされたデータに対してプログラムでテストを実行したりすることができます。</p><p>公式リポジトリのクローンを作成し、このワークフローを使用して独自のデータのインデックス作成を開始してください。クローラーによって生成されたインデックスに対してセマンティック検索を実行する方法については、<a href="https://www.elastic.co/search-labs/blog/semantic-search-open-crawler">この記事</a>を読むこともできます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch の ES|QL エディターエクスペリエンスと OpenSearch の PPL イベントアナライザーの比較]]></title>
            <description><![CDATA[OpenSearch の PPL イベント アナライザーの手動アプローチと直接対照的に、ES|QL エディターの高度な機能がどのようにワークフローを加速するかをご覧ください。 
]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/opensearch-vs-elasticsearch-ppl-esql</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/opensearch-vs-elasticsearch-ppl-esql</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Libby Lin, George Kobar]]></dc:creator>
            <pubDate>Thu, 18 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f1ff4ff4a00b3e5b084d4116cea6cabc82a2d816-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>8.14 から一般公開されている<a href="https://www.elastic.co/blog/getting-started-elasticsearch-query-language">Elasticsearch クエリ言語</a>(ES|QL) では、検索、可観測性、セキュリティ調査用に設計された専用のクエリ言語とエンジンが導入されています。既存のパイプ言語から多くの部分を借用している OpenSearch のパイプ処理言語 (PPL) とは異なり、ES|QL は洗練性、使いやすさ、および Kibana プラットフォーム全体でのシームレスな統合に重点を置いてゼロから構築されました。</p><p>このブログでは、Elasticsearch 9.1 の ES|QL エディターの開発者エクスペリエンスを、OpenSearch 3.2 のイベント アナライザー (略して PPL) の PPL と比較しながら探っていきます。</p><p>違いはすぐに明らかになります。ES|QL エディターは、初心者ユーザーだけでなく、エキスパートレベルのユーザーにも力を与えるインテリジェントなオートコンプリート、コンテキスト ヘルプ、推奨クエリ、およびクラスター間クエリ サポートを提供します。ES|QL オーサリングの思慮深い設計は、たとえば最近のクエリを使用した Kibana ワークフローによる統合クエリ検査と総合的な統合にも反映されています。</p><p>対照的に、PPL にはオートコンプリート、コンテキスト ガイダンス、分散クエリに対する同等のサポートがないため、学習曲線が急峻になり、試行錯誤が増えます。</p><h2>ES|QL の学習と使用を容易にする</h2><p>新しいクエリ言語を使い始めると、圧倒されると感じることがよくあります。<strong>Kibana Discover</strong> に直接組み込まれた ES|QL エディターは<strong> 、</strong> クエリの作成とデバッグをサポートするだけでなく、言語に慣れて使いこなせるようになるまでの時間を短縮することで、そのプロセスを容易にするように設計されています。エディターは日常のタスクの摩擦を軽減するのに役立つため、構文や試行錯誤からソリューションの作成に焦点を移すことができます。これらの原則と、それをエディターにどのように統合したかの詳細については、<a href="https://www.elastic.co/search-labs/blog/improving-esql-editor-experience-in-kibana">こちらを</a>ご覧ください。</p><p>このエディター エクスペリエンスは Discover に限定されません。これは再利用可能なコード モジュールであり、ダッシュボード、Kibana アラート、Kibana マップなど、 <strong>Kibana の他の部分に統合する</strong>作業が進められています。</p><h3>インテリジェントなオートコンプリート: クエリ作成を高速化</h3><p>ES|QL エディターのオートコンプリートは包括的で、互換性のある関数、引数、リテラル、さらにはネストされた関数の提案を提供します。これは PPL には明らかに欠けている機能です。実際、<a href="https://www.elastic.co/search-labs/blog/esql-autocomplete-rebuilt">ここで</a>概説されているように、根本から再構築されました。</p><p><a href="https://www.elastic.co/search-labs/blog/improving-esql-editor-experience-in-kibana">ここで</a>説明されているように、検証はユーザーが入力すると実行され、フィールドを提案し、ユーザーにエラーを通知します。これにより、ユーザーの精神的負担が軽減され、クエリ作成プロセスの早い段階でエラーを防ぐことができます。</p><p>例: このネストでは、フィールドと互換性のある関数が提案されています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a4d7b2819c34fab31bced7873257b8932b623fba-1502x473.png" style="width: 100%;" /><p>PPL がサポートしていないもの:</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6f3a89b1255b8a3a762022a2704fdd1c2987e5f9-1013x335.png" style="width: 100%;" /><p>互換性のある関数、引数、ネストされた関数を案内するインテリジェントなオートコンプリート機能があっても、利用可能なオプションについてさらに深く理解したい場合があります。ここで、ES|QL エディターのコンテキスト ヘルプが非常に役立ち、エディター内で即時に支援が提供され、クエリの開発が明確化され、強化されます。</p><h3>指先で状況に応じたヘルプ</h3><p>オートコンプリートによって生成されたコマンドに関する追加情報は、Ctrl キーとスペース キーを押すことで表示されます。問題の関数、引数、またはフィールドの詳細を示すパネルがすぐに表示されます。この軽量なインタラクションにより、開発者はスムーズに作業を進めることができ、エディターを離れたり外部ドキュメントを検索したりすることなく、ジャストインタイムのガイダンスを得ることができます。これにより、構文の検索に費やす時間が削減され、よくある間違いを未然に防ぐことができます。</p><p>実際の動作は次のようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2797f9b5e002dbd83c46475c4ed4dcdc86144a01-1343x522.gif" style="width: 100%;" /><p>PPL にはこのレベルの組み込みガイダンスがないため、ユーザーは外部のドキュメントや試行錯誤に頼ることになります。その欠如は単に機能が欠けているというだけではなく、設計哲学におけるより広範な相違を浮き彫りにしています。ES|QL は、ユーザーのデータとワークフローに適応する、思慮深くコンテキストを意識したエクスペリエンスを優先します。この違いはクエリの複雑さが増すにつれて顕著になり、ES|QL エディターは学習と本番使用の両方においてより効率的で信頼性の高い環境になります。</p><h3>データのコンテキストを考慮した推奨クエリ</h3><p>ES|QL エディターは、ログなどの作業中のデータに合わせて自動的に調整される推奨クエリを提供します。空白のエディターを表示する代わりに、一般的なユースケースに最も関連性の高い開始点を表示します。推奨クエリを選択すると、すぐに使用できる標準クエリが生成され、必要に応じてさらに絞り込むことができます。このアプローチにより、特に完全な構文をまだ知らない新しいユーザーにとって、クエリの開発が加速されます。</p><p>以下は、ユーザーが「変化点の検出」クエリを選択する例です。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3284c9340d41298820fbf8c7702abad946b48248-925x370.gif" style="width: 100%;" /><p>これを PPL の経験と比較してみましょう。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5c3e59dadc6210aede3366bdd081887bcbae7a54-969x798.png" style="width: 100%;" /><p>対照的に、ここでの PPL は基本的なオートコンプリートのみを提供するため、コンテキストや構造なしでクエリを組み立てることになります。このガイダンスの欠如は、フラストレーションと試行錯誤につながる可能性があります。ES|QL エディターのデータ対応の推奨クエリを使用すると、日常的なタスクの構文を最初から作成したり、暗記したりする必要がなくなります。エディターは認知負荷を軽減し、エラーの防止に役立ち、クエリの構築に悩むのではなく、問題解決やクラスター間検索の実行などのより広範な目標に集中できるようにします。</p><h2>直感的なクラスター間クエリ</h2><p>ES|QL エディターのオートコンプリートは、 <a href="https://elastic.aiops.work/search-labs/blog/esql-cross-cluster-search">CCS を使用して</a>複数のリモート クラスターを操作する場合でも、優れた性能を維持します。理由は次のとおりです。</p><h3>ES|QL エディターは、クラスター間でもシームレスなオートコンプリートを提供します。</h3><p>ES|QL エディターのオートコンプリートは、クラスター名だけでなく、<strong>ローカル インデックスとリモート インデックスの</strong>両方をサポートします。<a href="https://www.elastic.co/search-labs/blog/esql-cross-cluster-search">ここで</a>説明されているように、これはコーディネーター ノード アーキテクチャのおかげで機能します。このアーキテクチャは、ローカル ノードに送信するクエリ プランを検証および生成し、クエリを実行して結果を集計してからユーザーに送り返すのに役立ちます。完全なリモート クラスター名を入力せずに「:」と入力すると、リモート インデックスの自動補完プロセスが開始されます。また、接頭辞に限定されるわけではありません。</p><p>これにより、命名規則を記憶したりコンテキストを切り替えたりすることなく、分散データセット全体の検出とクエリを簡単に実行できるようになります。</p><p>以下は、ユーザーが「clu:g」と入力してリモート インデックスを検索する例です。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bae1fbacb2320e4d07f41291ea57c9bcf15bf8a5-1092x523.gif" style="width: 100%;" /><p>対照的に、PPL はローカル インデックスに対して基本的な補完のみを提供し、提案はプレフィックスの一致に制限されています。リモート クラスターは手動で入力する必要があるため、エラーが発生する可能性が高まり、クエリの作成が遅くなります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e38793c0cc7c6cc7dc0fd4779a3e24ffbb6e0838-1094x263.gif" style="width: 100%;" /><p>PPL はローカル インデックスに対してのみ補完を提供し、提案はプレフィックスに制限されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5310f824942f94485cace2558ea72c56a0971e22-862x197.png" style="width: 100%;" /><p>ES|QL ではさらに、負の符号を使用して直接<a href="https://www.elastic.co/docs/solutions/search/cross-cluster-search#exclude-problematic-clusters">除外できる</a>ため、探索に参加するクラスターをきめ細かく制御できます。この機能は、クラスター間の調査中に特定のデータセットを含めたり省略したりする必要があるハイブリッド環境で作業する場合に特に役立ちます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f23ca17f58fbf8e6d27419c028274cb91f30a549-937x78.png" style="width: 100%;" /><p>これらの機能強化は、Elasticsearch がクラスター間検索における摩擦の軽減に重点を置いていることを反映しています。ES|QL エディターでは、分散クエリの構築と管理が容易になるため、アナリストや開発者は構文ではなく洞察に集中できます。一方、PPL ではその負担がユーザーに多く残ります。ES|QL エディターは、クラスター間クエリの作成を簡素化するだけでなく、それらのクエリの実行方法を検査するツールも提供し、複数のクラスターにわたる透明性とパフォーマンス監視を保証します。</p><h3>検査ツールを使用してクロスクラスター検索の詳細を分析する</h3><p>ES|QL エディターからアクセスできる検査ツールは、すべてのクラスターにわたるクエリ実行に関する明示的な情報をメタデータに提供するように設計されています。この機能は Kibana Discover で有効になっており、クエリ インスペクターから直接アクセスできるため、検索の進行状況と詳細を分析できます。これは<strong>、Cross-Cluster Search</strong> ( <a href="https://www.elastic.co/docs/reference/query-languages/esql/esql-cross-clusters">CCS</a> ) にとって特に重要です。この機能を使用すると、検索の進行状況を監視し、分散データセット全体でのクエリの実行方法を把握できます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5d1c204f70171526fff924c30ea8ad08121a0f8d-919x523.gif" style="width: 100%;" /><p>特に複雑な分散検索の場合、クエリ実行の詳細な可視性により、最適なパフォーマンスとトラブルシューティングが可能になります。</p><p>ES|QL エディターは、個々のクエリの仕組みを理解するだけでなく、Kibana プラットフォーム全体に重要な機能を深く組み込むことでユーザー ジャーニーをさらに強化し、シームレスで中断のないワークフローを促進します。</p><h2>ES|QLとKibanaによる統合クエリエクスペリエンス</h2><p>クエリ駆動型分析における最も一般的な摩擦の原因の 1 つは、コンテキストの切り替えです。すでに記述したクエリを思い出す必要がある場合がよくあります。中断されるたびに集中力が途切れ、調査が遅くなります。ES|QL エディターは、Kibana 全体のクエリ履歴を統合することでこの問題に対処します。</p><h3>最近のクエリ</h3><p>ES|QL エディターの<a href="https://www.elastic.co/search-labs/blog/esql-piped-query-language-goes-ga">最近のクエリ</a>機能を使用すると、過去の作業にすぐにアクセスできるようになり、作業の流れを維持できます。Discover の ES|QL エディターでは、過去 20 件のクエリを表示、再実行、スター付けすることができ、頻繁に使用するクエリや複雑なクエリを 1 回のクリックで実行できるようになります。保存されたクエリは Kibana 全体に引き継がれ、ダッシュボード、視覚化、アラート、マップと統合されるため、現在の画面を離れたり、コマンドを最初から再入力したりする必要はありません。これにより、反復的な作業が削減され、調査が高速化され、エラーのリスクが最小限に抑えられます。</p><p>たとえば、ユーザーは Discover の ES|QL エディターで最近のクエリを利用できます (そしてスターを付けることもできます)。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/eb0f9fe37b92dec421c394d31ae7d90afebe062e-1421x793.png" style="width: 100%;" /><p>最近のクエリはダッシュボードに統合されています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b84cd3a9bdec58812360d2aba4fc7713363ee3cc-1411x797.png" style="width: 100%;" /><p>PPL には同等の機能は用意されていないため、ユーザーはクエリを再利用するために手動でのコピー アンド ペーストや外部メモに頼ることになります。この違いは利便性だけではありません。これは、ES|QL を Kibana エコシステム内に真に統合された言語として構築するという Elastic の戦略を反映しています。ES|QL エディターは、最近のクエリなどの機能により、日常のワークフローを効率化するだけでなく、現在テクニカル プレビューで提供されているより高度な機能の基盤も構築し、エクスペリエンスの継続的な進化を保証します。</p><h2>まとめ</h2><p>ES|QL は単なる構文ではありません。ユーザーがデータを検索、探索、分析する方法を改善するという Elastic の戦略を反映しています。インテリジェントなオートコンプリート、コンテキスト認識型の推奨クエリ、エディター内ガイダンス、Inspect などのツールを備えた ES|QL エディターは、学習を加速し、エラーを削減し、クラスター間分析などの複雑なワークフローを簡素化します。Kibana 全体に統合されており、クエリをダッシュボード、アラート、視覚化にシームレスに接続して、中断のないワークフローを実現します。</p><p>要約すると、ES|QL は単なる別のパイプ言語ではありません。データとの対話方法を根本的に再定義する直感的な UI と組み合わせた、思慮深く設計されたクエリ エンジンであり、OpenSearch PPL の多くの場合シーケンシャルでガイドが少ない性質とは対照的に、統合されたインテリジェントで継続的に進化するエクスペリエンスを提供します。</p><h2>次は何？</h2><p>このブログは ES|QL の表面的な部分のみを取り上げています。今後の投稿では、OpenSearch PPL との比較をさらに深め、地理空間、視覚化、<a href="https://www.elastic.co/docs/explore-analyze/dashboards/add-controls">コントロール</a>(ダッシュボードで既に利用可能)、マルチデータ探索タブ、バックグラウンド検索、より豊富なクエリ履歴、FUSE などの今後のエディター機能について説明します。</p><h2>今すぐES|QLをお試しください</h2><p><a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/create-serverless-project">無料トライアル</a> で、完全に管理された Elasticsearch<a href="https://www.elastic.co/cloud/serverless"> Serverless</a> プロジェクトで ES|QL を試すことができます。8.11 以降のバージョンでも利用可能ですが、 <a href="https://www.elastic.co/blog/whats-new-elastic-9-1-0">8.19 および 9.1</a>で最も快適にご利用いただけます。</p><p>1 つのコマンドでローカル環境で数分以内に開始できます。</p><pre><code>curl -fsSL https://elastic.co/start-local | sh</code></pre>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch Ruby クライアント向け ES|QL クエリビルダーのご紹介]]></title>
            <description><![CDATA[Elasticsearch Ruby クライアント用に最近リリースされた ES|QL クエリ ビルダーの使用方法を学習します。Ruby コードを使用して ES|QL クエリをより簡単に構築するためのツール。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/esql-query-builder-elasticsearch-ruby-client</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/esql-query-builder-elasticsearch-ruby-client</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Fernando Briano]]></dc:creator>
            <pubDate>Wed, 17 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f8e1263ab53d356824a4fc539084151be80899db-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>最近、Apache 2 ライセンスに基づいて公開された Ruby gem、 <a href="https://github.com/elastic/esql-ruby/"><code>elastic-esql</code></a>をリリースしました。この gem を使用すると、Elastic の<a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql">ES|QL</a>クエリを慣用的な Ruby で構築し、ES|QL クエリ API で使用できるようになります。ES|QL を使用すると、開発者はクエリを介して Elasticsearch に保存されているデータをフィルタリング、変換、分析できます。「パイプ」（ <code>|</code> ）を使用して、データを段階的に処理します。代わりに gem は Ruby 関数を使用します。これを元のオブジェクトに連鎖して、より複雑なクエリを構築できます。</p><p><strong>ESQL:</strong></p><pre><code>FROM sample_data | LIMIT 2 | SORT @timestamp DESC</code></pre><p><strong>ルビー：</strong></p><pre><code>Elastic::ESQL.from('sample_data').limit(2).sort('@timestamp').descending</code></pre><h2>インストール</h2><p>この gem は、RubyGems から次のようにインストールできます。</p><pre><code>gem install elastic-esql</code></pre><p>または、プロジェクトの Gemfile に追加することもできます。</p><pre><code>gem 'elastic-esql'</code></pre><h2>使用法</h2><p>完全なクエリを一度に構築することも、 <code>from</code>や<code>row</code>などのソース コマンドを使用してクエリ オブジェクトを作成し、それに基づいて ES|QL メソッドを連鎖して構築することもできます。</p><pre><code>query = Elastic::ESQL.from('sample_data')
query.limit(2).sort('@timestamp')</code></pre><p>gem は<code>to_s</code>メソッドでコードを ES|QL に変換するので、印刷されるか文字列としてキャストされるときに ES|QL クエリを返します。</p><pre><code>query = Elastic::ESQL.from('sample_data').limit(2).sort('@timestamp').descending
query.to_s
# => "FROM sample_data | LIMIT 2 | SORT @timestamp DESC"</code></pre><p>各関数の<code>!</code>に相当するものを使用して、クエリ オブジェクトをインスタンス化し、その初期状態を変更できます。</p><pre><code>query = Elastic::ESQL.from('sample_data')
query.to_s
# => "FROM sample_data"
query.limit!(2).sort!('@timestamp')
query.to_s
# => "FROM sample_data | LIMIT 2 | SORT @timestamp"</code></pre><p>このツールは、 <code>enrich</code>や<code>sort</code>などの追加ステップを ES|QL 関数に連鎖させる便利な方法を提供します。<code>Elastic::ESQL</code>オブジェクトで<code>enrich</code>呼び出すと、それに<code>on</code>と<code>with</code>を連鎖できます。</p><pre><code>esql.enrich!('policy').on('a').with({ name: 'language_name' })</code></pre><p><code>sort</code>を使用した後、クエリに<code>desc</code> 、 <code>asc</code> 、 <code>nulls_first</code> 、 <code>nulls_last</code>を連鎖させることもできます。</p><pre><code>Elastic::ESQL.from('sample_data').sort('@timestamp').asc.to_s
# => 'FROM sample_data | SORT @timestamp ASC'

Elastic::ESQL.from('sample_data').sort('@timestamp').desc.nulls_first.to_s
# => 'FROM sample_data | SORT @timestamp DESC NULLS FIRST'</code></pre><p>また、ES|QL クエリを自分で記述する場合や、ライブラリにまだ追加されていない機能を使用する場合に備えて、カスタム文字列もサポートされています。<code>custom</code>クエリの末尾の文字列を結合します。パイプ文字を追加せずに、関数に送信されるとそれらが追加されます。これらはスペース文字によってクエリの残りの部分に結合されます。</p><pre><code>esql = Elastic::ESQL.from('sample_data')
esql.custom('| MY_VALUE = "test value"').to_s
# => 'FROM sample_data | MY_VALUE = "test value"'</code></pre><p><code>custom</code>関数を連鎖させることもできます:</p><pre><code>esql.custom('| MY_VALUE = "test value"').custom('| ANOTHER, VALUE')
'FROM sample_data | MY_VALUE = "test value" | ANOTHER, VALUE'</code></pre><h2>Ruby クライアントで ES|QL クエリビルダーを使用する</h2><p>クエリ オブジェクトを送信することで、 <a href="https://github.com/elastic/elasticsearch-ruby">elasticsearch-ruby</a>と<code>esql.query</code> API でクエリ ビルダーを直接使用できます。</p><pre><code>require 'elasticsearch'
require 'elastic/esql'

client = Elasticsearch::Client.new
index = 'sample_data'

query = Elastic::ESQL.from(index)
                     .sort('@timestamp')
                     .desc
                     .where('event_duration > 5000000')
                     .limit(3)
                     .eval({ duration_ms: 'ROUND(event_duration/1000000.0, 1)' })
client.esql.query(body: { query: query })</code></pre><p>Elasticsearch Ruby クライアントの ES|QL Helper と一緒に使用することもできます。<a href="https://www.elastic.co/search-labs/blog/esql-ruby-helper-elasticsearch">詳細については</a>、以下を参照してください。</p><pre><code>require 'elasticsearch/helpers/esql_helper'

Elasticsearch::Helpers::ESQLHelper.query(client, query)</code></pre><h2>スタンドアロンツールとして</h2><p>この gem は、ES|QL クエリを慣用的な方法で構築するためのスタンドアロン ツールとして設計されています。ランタイム依存関係がないため、公式の Elasticsearch Ruby クライアントと一緒に使用することも、単独で使用することもできます。</p><p>生成されたクエリは、アプリケーションが Elasticsearch API (Ruby かどうかに関係なく) と対話するあらゆる方法で<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-query"><code>esql.query</code></a> API で使用できます。<code>elastic-esql</code>を使用してクエリを構築すると、生成された文字列をリクエスト本文の<code>query</code>パラメータとして API に送信できます。 </p><p>以前、 <a href="https://www.elastic.co/search-labs/blog/elasticsearch-ruby-tools">Elasticsearch を一般的な Ruby ツールと併用する方法</a>について書きました。この gem は、一般的な Ruby ツールと組み合わせて使用し、ES|QL を使用して Elasticsearch をクエリできます。</p><h2>まとめ</h2><p>このライブラリは現在開発中であり、最終的な API はまだ完成していません。現在はテクニカルプレビューとしてリリースされています。現在の API または一般的な使用方法に関してフィードバックがある場合は、遠慮なく<a href="https://github.com/elastic/esql-ruby/issues">新しい問題を開いて</a>ください。Ruby ES|QL クエリ ビルダーの詳細については、 <a href="https://github.com/elastic/esql-ruby/?tab=readme-ov-file#ruby-esql-query-builder">README</a>を参照してください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ベクター検索フィルタリング: 関連性を保つ]]></title>
            <description><![CDATA[クエリに最も類似した結果を見つけるためにベクトル検索を実行するだけでは不十分です。検索結果を絞り込むには、フィルタリングが必要になることがよくあります。この記事では、Elasticsearch と Apache Lucene でのベクトル検索のフィルタリングの仕組みについて説明します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/vector-search-filtering</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/vector-search-filtering</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Carlos Delgado]]></dc:creator>
            <pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/03b1dd4c7bda4fbabd8e374bc2e4f12d5be6ef5f-1600x1150.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>関連する結果を見つけるには、ベクター検索だけでは不十分です。検索結果を絞り込み、無関係な結果を除外するのに役立つフィルタリング基準を使用することは非常に一般的です。</p><p>ベクトル検索でのフィルタリングの仕組みを理解すると、パフォーマンスとリコールのトレードオフのバランスをとるのに役立ちます。また、フィルタリングの使用時にベクトル検索のパフォーマンスを高めるために使用される最適化のいくつかを知ることもできます。</p><h2>なぜフィルタリングするのですか?</h2><p>ベクトル検索は、大規模なデータセット内で関連情報を検索する方法に革命をもたらし、クエリと意味的に類似する項目を発見できるようになりました。</p><p>ただし、類似アイテムを見つけるだけでは十分ではありません。多くの場合、特定の基準や属性に基づいて検索結果を絞り込む必要があります。</p><p>電子商取引ストアで商品を検索していると想像してください。純粋なベクター検索では視覚的に類似したアイテムが表示される場合がありますが、価格帯、ブランド、在庫状況、または顧客評価でフィルタリングすることもできます。フィルタリングがなければ、類似した製品が大量に表示され、探しているものを正確に見つけることが難しくなります。</p><p>フィルタリングにより、検索結果を正確に制御できるようになり、取得された項目が意味的に一致するだけでなく、必要な要件をすべて満たすことが保証されます。これにより、より正確で効率的、そしてユーザーフレンドリーな検索エクスペリエンスが実現します。</p><p>Elasticsearch と Apache Lucene が優れているのはこの点です。さまざまなデータ タイプにわたって効果的なフィルタリングを使用することが、他のベクター データベースとの主な違いの 1 つです。</p><h2>正確なベクトル検索のためのフィルタリング</h2><p>正確なベクトル検索を実行するには、主に 2 つの方法があります。</p><ul><li>dense_vector フィールドに<code>flat</code>インデックス タイプを使用します。これにより、 <code>knn</code>検索では近似検索ではなく正確な検索が使用されるようになります。</li><li>ベクトル関数を使用してスコアを計算する<a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-script-score-query#vector-functions">script_score クエリ</a>を使用します。これはどのインデックス タイプでも使用できます。</li></ul><p>正確なベクトル検索を実行すると、すべてのベクトルがクエリと比較されます。このシナリオでは、フィルターを通過するベクトルのみを比較する必要があるため、フィルタリングによってパフォーマンスが向上します。</p><p>いずれにしてもすべてのベクトルが考慮されるため、結果の品質には影響しません。興味のない結果を事前にフィルタリングするだけで、操作の数を減らすことができます。</p><p>これは非常に重要です。適用したフィルターによって少数のドキュメントが生成される場合、近似検索ではなく正確な検索を実行するとパフォーマンスが向上する可能性があるためです。</p><p>経験則としては、フィルターを通過するドキュメントが 10,000 個未満の場合は完全一致検索を使用します。<a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">BBQ</a>インデックスは比較が非常に高速なので、ベース インデックスが 10 万未満の場合は、完全一致検索を使用するのが合理的です。詳細については、<a href="https://www.elastic.co/search-labs/blog/knn-exact-vs-approximate-search">このブログ投稿</a>をご覧ください。</p><p>フィルターが常に非常に制限的である場合は、HNSW ベースのインデックス タイプではなく<code>flat</code>インデックス タイプを使用して、近似検索ではなく完全検索に重点を置いたインデックス作成を検討してください。詳細については、 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-params">index_options のプロパティ</a>を参照してください。</p><h2>近似ベクトル検索のためのフィルタリング</h2><p>近似ベクトル検索を実行する場合、結果の精度とパフォーマンスをトレードオフします。HNSW のようなベクトル検索データ構造は、数百万のベクトルのおおよその最近傍を効率的に検索します。計算コストのかかるベクトル比較を最小限に抑えて、最も類似したベクトルを取得することに重点を置いています。</p><p>つまり、他のフィルタリング属性はベクター データの一部ではないということです。さまざまなデータ タイプには、用語辞書、投稿リスト、ドキュメント値など、検索やフィルタリングに効率的な独自のインデックス構造があります。</p><p>これらのデータ構造はベクトル検索メカニズムとは別であるため、ベクトル検索にフィルタリングをどのように適用すればよいでしょうか?フィルターには、ベクター検索の後にフィルターを適用する (ポストフィルタリング) か、ベクター検索の前にフィルターを適用する (プレフィルタリング) という 2 つのオプションがあります。</p><p>それぞれの選択肢には長所と短所があります。詳しく見ていきましょう！</p><h3>ポストフィルタリング</h3><p>ポストフィルタリングは、ベクトル検索が完了した後にフィルターを適用します。これは、最も類似した上位 k 個のベクトル結果が見つかった後にフィルターが適用されることを意味します。</p><p>明らかに、結果にフィルターを適用した後、 k 件未満の結果が返される可能性があります。もちろん、ベクトル検索 (より高い k 値) からより多くの結果を取得できますが、フィルターを適用した後に k 以上の結果が得られるかどうかはわかりません。</p><p>ポストフィルタリングの利点は、ベクトル検索の実行時の動作を変更しないことです。つまり、ベクトル検索はフィルタリングを認識しません。ただし、取得される結果の最終的な数は変わります。</p><p>以下は、 <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query">knn クエリ</a>を使用したポストフィルタリングの例です。フィルタリング句が knn クエリとは別であることを確認します。</p><pre><code>{
  "query": {
    "bool": {
      "must": {
        "knn": {
          "field": "image-vector",
          "query_vector": [54, 10, -2],
          "k": 5,
          "num_candidates": 50
        }
      },
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}</code></pre><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/filter-search-results#post-filter">post-filter</a>を使用した knn 検索では、ポストフィルタリングも利用できます。</p><pre><code>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, 2],
    "k": 5,
    "num_candidates": 50
  },
  "post_filter": {
    "term": {
      "file-type": "png"
    }
  }
}</code></pre><p>knn 検索では明示的なポストフィルター セクションを使用する必要があることに注意してください。ポストフィルターを使用しない場合、knn 検索はポストフィルターを実行する代わりに<a href="https://www.elastic.co/docs/solutions/search/vector/knn#_combine_approximate_knn_with_other_features">、最近傍の結果を他のクエリまたはフィルターと組み合わせます</a>。</p><h3>プレフィルタリング</h3><p>ベクトル検索の前にフィルターを適用すると、まずフィルターを満たすドキュメントが取得され、次にその情報がベクトル検索に渡されます。</p><p>Lucene は<a href="https://github.com/apache/lucene/blob/7a60d7ce92392181e137361336e5196bd486cdd9/lucene/core/src/java/org/apache/lucene/util/BitSet.java">BitSets</a>を使用して、フィルター条件を満たすドキュメントを効率的に保存します。次に、ベクトル検索は条件を満たすドキュメントを考慮して、HNSW グラフを走査します。候補を結果に追加する前に、それが有効なドキュメントの BitSet に含まれているかどうかを確認します。</p><p>ただし、有効なドキュメントでない場合でも、候補を調査してクエリと比較する必要があります。HNSW の有効性は、グラフ内のベクトル間の接続に依存します。つまり、候補の探索を停止すると、その近傍もスキップされる可能性があることを意味します。</p><p>ガソリンスタンドに行くために車を運転するのを想像してください。ガソリンスタンドのない道路を無視すると、目的地にたどり着く可能性は低くなります。他の道はあなたにとって必要なものではないかもしれませんが、目的地まであなたを<em>繋いで</em>くれます。HNSW グラフ上のベクトルも同様です。</p><p>したがって、プレフィルタリングを適用すると、フィルタを適用しない場合よりもパフォーマンスが低下します。検索で訪れる<em>すべての</em>ベクトルに対して作業を実行し、フィルターに一致しないベクトルを破棄する必要があります。私たちは、トップ k の結果を得るために、より多くの作業とより多くの時間をかけています。</p><p>以下は、Elasticsearch クエリ DSL での事前フィルタリングの例です。フィルタリング句が knn セクションの一部になっていることを確認します。</p><pre><code>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, -2],
    "k": 5,
    "num_candidates": 50,
    "filter": {
      "term": {
        "file-type": "png"
      }
    }
  }
}</code></pre><p>事前フィルタリングは、 <a href="https://www.elastic.co/docs/solutions/search/vector/knn#knn-search-filter-example">knn 検索</a>と<a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query#knn-query-filtering">knn クエリの</a>両方で利用できます。</p><pre><code>{
  "query": {
    "knn": {
      "field": "image-vector",
      "query_vector": [-5, 9, -12],
      "k": 5,
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}</code></pre><h4>プレフィルタリングの最適化</h4><p>プレフィルタリングのパフォーマンスを確保するために適用できる最適化がいくつかあります。</p><p>フィルターの制限が厳しい場合は、完全一致検索に切り替えることができます。比較するベクトルが少ない場合は、フィルターを満たす少数のドキュメントに対して正確な検索を実行する方が高速です。</p><p>これは、 <a href="https://github.com/apache/lucene/blob/eb876b618da5d04c1ad14b04a48321638318493a/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L218">Lucene</a>および Elasticsearch で自動的に適用される最適化です。</p><p>別の最適化方法では、フィルターの条件を満たさないベクトルを無視します。代わりに、このメソッドは、フィルターを通過するフィルター処理されたベクトルの近傍をチェックします。このアプローチでは、フィルタリングされたベクトルは考慮されず、現在のパスに接続されたベクトルの探索が継続されるため、比較の回数が効果的に削減されます。</p><p>このアルゴリズムは ACORN-1 であり、そのプロセスについては<a href="https://www.elastic.co/search-labs/blog/filtered-hnsw-knn-search">こちらのブログ記事</a>で詳しく説明されています。</p><h2>ドキュメントレベルのセキュリティを使用したフィルタリング</h2><p><a href="https://www.elastic.co/docs/deploy-manage/users-roles/cluster-or-deployment-auth/controlling-access-at-document-field-level#document-level-security">ドキュメント レベル セキュリティ (DLS)</a>は、ユーザー ロールが取得できるドキュメントを指定する Elasticsearch 機能です。</p><p>DLS はクエリを使用して実行されます。ロールにはインデックスに関連付けられたクエリを持たせることができ、これにより、そのロールに属するユーザーがインデックスから取得できるドキュメントを効果的に制限できます。</p><p>ロール クエリは、<a href="https://github.com/elastic/elasticsearch/blob/c3a1cb34294e902a9f46d7e840ea09965019f456/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java#L92">それに一致するドキュメントを取得する</a>ためのフィルターとして使用され、BitSet としてキャッシュされます。この BitSet は、基盤となる Lucene リーダーをラップするために使用されるため、クエリから返されたドキュメントのみが<em>ライブで</em>あると見なされます。つまり、それらのドキュメントはインデックス上に存在し、削除されていません。</p><p>knn クエリを実行するために<a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L196">リーダーからライブ ドキュメントが取得される</a>ため、ユーザーが利用できるドキュメントのみが考慮されます。プレフィルターがある場合は、DLS ドキュメントが<a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L204">それに追加され</a>ます。</p><p>つまり、DLS フィルタリングは、近似ベクトル検索のプレフィルタとして機能し、同じパフォーマンスへの影響と最適化を実現します。</p><p>完全一致検索を使用した DLS には、任意のフィルターを適用する場合と同じ利点があります。つまり、DLS から取得されるドキュメントが少ないほど、完全一致検索のパフォーマンスが向上します。DLS によって返されるドキュメントの数も考慮してください。DLS ロールの制限が非常に厳しい場合は、近似検索ではなく完全検索の使用を検討してください。</p><h2>ベンチマーク</h2><p>Elasticsearch では、ベクトル検索フィルタリングが効率的であることを確認したいと考えています。当社には、さまざまな<a href="https://elasticsearch-benchmarks.elastic.co/#tracks/so_vector/nightly/default/90d">フィルタリングを使用して近似ベクトル検索を実行するベクトル フィルタリング用の特定のベンチマーク</a>があり、ベクトル検索で関連する結果を可能な限り高速に取得し続けることができるようにします。</p><p>ACORN-1 が導入されたときの<a href="https://elasticsearch-benchmark-analytics.elastic.co/app/dashboards#/view/43b63e80-5ba2-11ed-aede-a742809feed4?_g=(refreshInterval:(pause:!t,value:60000),time:(from:&#x27;2025-05-28T01:27:58.456Z&#x27;,to:&#x27;2025-06-30T13:53:26.430Z&#x27;))&amp;_a=()">改善点</a>を確認します。ベクトルの 2% のみがフィルターを通過するテストでは、クエリの待機時間は元の期間の 55% に短縮されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3eac3748a33376fc97d957364a5c1f5108d5c58b-1023x896.png" style="width: 100%;" /><h2>まとめ</h2><p>フィルタリングは検索の不可欠な部分です。ベクトル検索でフィルタリングのパフォーマンスを確保し、トレードオフと最適化を理解することが、効率的で正確な検索の成否を左右します。</p><p>フィルタリングはベクトル検索のパフォーマンスに影響します。</p><ul><li>フィルタリングを使用すると、正確な検索が高速になります。フィルタリングが十分に制限されている場合は、近似検索ではなく完全検索の使用を検討する必要があります。これは Elasticsearch での自動最適化です。</li><li>事前フィルタリングを使用すると近似検索が遅くなります。事前フィルタリングを使用すると、検索速度は遅くなりますが、フィルターに一致する上位 k 件の結果を取得できます。</li><li>ポストフィルタリングでは、フィルターの適用時にフィルターによってフィルタリングされる可能性があるため、必ずしも上位 k 件の結果が取得されるわけではありません。</li></ul><p>フィルタリングをお楽しみください！</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch のシャードとレプリカ: 実践ガイド]]></title>
            <description><![CDATA[Elasticsearch のシャードとレプリカの概念を習得し、それらを最適化する方法を学習します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-shards-and-replicas-guide</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-shards-and-replicas-guide</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Piotr Przybyl]]></dc:creator>
            <pubDate>Thu, 14 Aug 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7775c44f01f2516c4ff4cce6d6bbe9e7b2c38908-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch は、スケーラビリティとフォールト トレランスの問題に対処する分散システムを Lucene 上に構築することで、Lucene のパワーを強化します。また、JSON ベースの REST API も公開されているため、他のシステムとの相互運用性が非常に簡単になります。</p><p>Elasticsearch のような分散システムは非常に複雑になる可能性があり、パフォーマンスと安定性に影響を与える要因が多数あります。<strong>シャードは</strong>Elasticsearch の最も基本的な概念の 1 つであり、その仕組みを理解することで Elasticsearch クラスターを効果的に管理できるようになります。</p><p>この記事では、プライマリ シャードとレプリカ シャードとは何か、それらが Elasticsearch クラスターに与える影響、さまざまな需要に合わせてそれらを調整するためのツールについて説明します。</p><h2>破片を理解する</h2><p>Elasticsearch インデックス内のデータは膨大な量にまで増大する可能性があります。管理しやすいように、すべてのデータはインデックスに保存され、インデックスはいくつかの<strong>シャード</strong>に分割されます。各 Elasticsearch シャードは Apache Lucene インデックスであり、個々の Lucene インデックスには Elasticsearch インデックス内のドキュメントのサブセットが含まれています。このようにインデックスを分割すると、リソースの使用量を制御できます。Apache Lucene インデックスには、2,147,483,519 (2³¹ - 129) ドキュメントの制限があります。</p><p>場合によっては、再バランス調整のためにインデックスをノード間で移動する必要があります。このプロセスは時間とリソースの両方を大量に消費する可能性があるため、インデックスが大きくなりすぎないようにする必要があります。これにより、回復時間を管理しやすい状態に保つことができます。さらに、インデックスは常に結合する必要がある Lucene セグメントで構成されているため、セグメントが大きくなりすぎないことが重要です。これらの理由から、Elasticsearch はインデックス データを<strong>プライマリ シャード</strong>と呼ばれるより小さく管理しやすいチャンクに分割し、複数のマシン間でより簡単に分散できるようにします。<strong>レプリカ</strong>シャードは、対応するプライマリ シャードの正確なコピーであり、その機能についてはこの記事の後半で説明します。</p><p>適切な数のシャードを持つことはパフォーマンスにとって重要です。したがって、事前に計画を立てるのが賢明です。クエリが異なるシャード間で並列に実行されると、各シャードが異なるノードに配置され、クラスター内に十分なノードがある場合に限り、単一のシャードで構成されたインデックスよりも高速に実行されます。ただし同時に、シャードはインデックス化されたデータとクラスター メタデータの両方に関して、メモリとディスク領域を消費します。シャードが多すぎると（オーバーシャーディングとも呼ばれます）、クエリ、インデックス要求、および管理操作が遅くなる可能性があるため、適切なバランスを維持することが重要です。</p><p>プライマリ シャードの数は、<strong>特定のインデックス インスタンスの</strong>インデックス作成時に定義されます。後でプライマリ シャードの数を変更する必要がある場合は、<strong>サイズ変更 API を</strong>使用できます (<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-split">分割</a>(プライマリ シャードを増やす)、<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-shrink">縮小</a>(プライマリ シャードを減らす)、または<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-clone">複製</a>(レプリカの新しい設定でプライマリ シャードの数は同じ))。これらの操作は、Lucene セグメントをコピーし、<strong>すべてのドキュメントの完全な再インデックスを回避します</strong>。インデックスを作成するときに、インデックスの設定としてプライマリ シャードとレプリカ シャードの数を設定できます。</p><pre><code>PUT /sensor
{
   "settings" : {
       "index" : {
           "number_of_shards" : 6,
           "number_of_replicas" : 2
       }
   }
}</code></pre><p>(シャードまたはレプリカの数を指定しない場合、Elasticsearch 7.0 以降、両方のデフォルト値は 1 になります)。理想的なシャードの数は、インデックス内のデータ量に基づいて決定する必要があります。一般的に、<a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/size-shards">最適なシャードは 10 ～ 50 GB のデータを保持し、シャードあたりのドキュメント数は 2 億未満である必要があります</a>。たとえば、1 日に約 300 GB のアプリケーション ログが蓄積されると予想される場合、それらをホストするのに十分な数のノードがあれば、そのインデックスに約 10 個のシャードを持つことが妥当です。</p><p>シャードは、その存続期間中に、次のようなさまざまな状態を経る可能性があります。</p><ul><li><strong>初期化中:</strong>シャードが使用される前の初期状態。</li><li><strong>開始済み:</strong>シャードがアクティブでリクエストを受信できる状態。</li><li><strong>再配置中:</strong>シャードが別のノードに移動されている途中に発生する状態。これは、たとえば、ノードのディスク容量が不足している場合など、特定の状況下では必要になることがあります。</li><li><strong>未割り当て:</strong>割り当てに失敗したシャードの状態。これが発生すると理由が提供されます。たとえば、シャードをホストしているノードがクラスター内になくなった場合<em>(NODE_LEFT)</em> 、または閉じたインデックスに復元された場合<em>(EXISTING_INDEX_RESTORED) などです。</em></li></ul><p>すべてのシャード、その状態、およびその他のメタデータを表示するには、次のリクエストを使用できます。</p><pre><code>GET _cat/shards</code></pre><p>特定のインデックスのシャードを表示するには、URL にインデックスの名前を追加します (例: sensor)。</p><pre><code>GET _cat/shards/sensor</code></pre><p>このコマンドは、次の例のような出力を生成します。デフォルトでは、表示される列にはインデックスの名前、名前（つまりシャードの数、プライマリ シャードかレプリカか、シャードの状態、ドキュメント数、ディスク上のサイズ、シャードが配置されているノードの IP アドレスとノード ID が表示されます。</p><pre><code>sensor 5 p STARTED    0  283b 127.0.0.1 ziap
sensor 5 r UNASSIGNED                  
sensor 2 p STARTED    1 3.7kb 127.0.0.1 ziap
sensor 2 r UNASSIGNED                  
sensor 3 p STARTED    3 7.2kb 127.0.0.1 ziap
sensor 3 r UNASSIGNED                  
sensor 1 p STARTED    1 3.7kb 127.0.0.1 ziap
sensor 1 r UNASSIGNED                  
sensor 4 p STARTED    2 3.8kb 127.0.0.1 ziap
sensor 4 r UNASSIGNED                  
sensor 0 p STARTED    0  283b 127.0.0.1 ziap
sensor 0 r UNASSIGNED</code></pre><h2>レプリカを理解する</h2><p>各シャードにはデータのコピーが 1 つ含まれますが、インデックスにはシャードの複数のコピーが含まれる場合があります。したがって、シャードには、<strong>プライマリ シャード</strong>とコピー、または<strong>レプリカ</strong>の 2 種類があります。プライマリ シャードの各レプリカは常に異なるノードに配置されるため、ノード障害が発生した場合でもデータの高可用性が確保されます。冗長性とデータ損失やダウンタイムの防止の役割に加えて、レプリカはクエリをプライマリ シャードと並行して処理できるため、検索パフォーマンスが向上し、処理速度が向上します。</p><p>プライマリ シャードとレプリカ シャードの動作にはいくつかの重要な違いがあります。どちらもクエリを処理できますが、インデックスリクエスト（つまりインデックスにデータを追加するなどの処理は、レプリカ シャードに複製される前に、まずプライマリ シャードを通過する必要があります。前述のように、プライマリ シャードが使用できなくなった場合 (たとえば、ノードの切断やハードウェア障害などにより)、レプリカが昇格してその役割を引き継ぎます。</p><p>レプリカはノード障害の際に役立ちますが、インデックス作成時にメモリ、ディスク容量、計算能力を消費するため、レプリカを多くしすぎないことが重要です。プライマリ シャードとレプリカのもう 1 つの違いは、インデックスの作成後はプライマリ シャードの数を変更できないのに対し、レプリカの数はインデックス設定を更新することでいつでも動的に変更できることです。</p><p>レプリカに関して考慮すべきもう 1 つの要素は、利用可能なノードの数です。同じノードに同じデータのコピーが 2 つあると、ノードに障害が発生した場合に保護が提供されないため、レプリカは常にプライマリ シャードとは異なるノードに配置されます。その結果、システムが<em>n 個の</em>レプリカをサポートするには、クラスター内に少なくとも<em>n + 1 個の</em>ノードが必要になります。たとえば、クラスター内に 2 つのノードがあり、インデックスが 6 つのレプリカで構成されている場合、割り当てられるレプリカは 1 つだけです。一方、7 つのノードを持つシステムは、1 つのプライマリ シャードと 6 つのレプリカを完全に処理できます。</p><h2>シャードとレプリカの最適化</h2><p>プライマリ シャードとレプリカ シャードの適切なバランスを持つインデックスが作成された後でも、インデックスの周囲のダイナミクスは時間の経過とともに変化するため、これらを監視する必要があります。たとえば、時系列データを扱う場合、最近のデータを持つインデックスは、一般的に古いデータを持つインデックスよりもアクティブになります。これらのインデックスを調整しないと、要件が大きく異なるにもかかわらず、すべて同じ量のリソースを消費することになります。</p><p>ロールオーバー インデックス API を使用すると、新しいインデックスと古いインデックスを分離できます。特定のしきい値（ディスク上のインデックスのサイズ、ドキュメントの数、または年齢）に達すると、新しいインデックスを自動的に作成するように設定できます。この API は、シャードのサイズを制御するのにも役立ちます。インデックス作成後はシャードの数を簡単に変更できないため、ロールオーバー条件が満たされない限り、シャードにはデータが蓄積され続けます。アクセス頻度が低い古いインデックスの場合、インデックスの縮小と強制マージは、メモリとディスクのフットプリントを削減する 2 つの異なる方法です。前者はインデックス内のシャードの数を減らし、後者は Lucene セグメントの数を減らし、削除されたドキュメントによって使用されていたスペースを解放します。</p><h2>Elasticsearchの基盤となるプライマリシャードとレプリカシャード</h2><p>Elasticsearch は、膨大な量のデータのための分散ストレージ、検索、分析プラットフォームとして高い評価を得ています。しかし、このような規模で事業を展開する場合、必然的に課題が生じます。そのため、プライマリ シャードとレプリカ シャードの仕組みを理解することは、Elasticsearch にとって非常に重要かつ基本的なことであり、プラットフォームの信頼性とパフォーマンスを最適化するのに役立ちます。</p><p>これらがどのように機能し、どのように最適化するかを知ることは、より堅牢でパフォーマンスの高い Elasticsearch クラスターを実現するために重要です。クエリ応答が遅くなったり、頻繁に停止したりする場合は、この知識がこれらの障害を克服する鍵となる可能性があります。</p><p><a href="https://www.elastic.co/docs/deploy-manage/distributed-architecture/clusters-nodes-shards">クラスター、ノード、シャード、シャードの</a><a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/size-shards">サイズ設定方法</a>、<a href="https://www.elastic.co/docs/deploy-manage/distributed-architecture/shard-allocation-relocation-recovery">シャードの割り当てと回復の</a>詳細については、Elasticsearch の公式ドキュメントを参照してください。</p><p>このトピックは、 <a href="https://youtu.be/sAySPSyL2qE">Elastic コミュニティ YouTube チャンネルの入門コースとしてもご利用いただけます。</a></p><p>最後に、ノード、シャード、レプリカについて心配したくない場合は、 <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/serverless">Elastic Cloud Serverless を</a>試してみてください。この Elastic Cloud オファリングは Elastic によって完全に管理され、ワークロードに合わせて自動的に拡張されます。無料トライアルを利用すると、サーバーレス アプローチのその他の利点を理解するのに役立ちます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearchインデックスのフィールドを表示する方法]]></title>
            <description><![CDATA[_mapping および _search API、サブフィールド、合成 _source、およびランタイム フィールドを使用して Elasticsearch インデックスのフィールドを表示する方法を学習します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-index-show-fields</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-index-show-fields</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[JD Armada]]></dc:creator>
            <pubDate>Wed, 06 Aug 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/23b96aaa1a38b1f4747b4a87695d816f24c0cf70-720x421.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>この記事では、Elasticsearch インデックスのフィールドを表示する方法について説明します。これは、データの構造を理解し、特定のフィールドを識別し、問題をトラブルシューティングするのに役立ちます。以下のトピックを取り上げます。</p><ol><li><code>_mapping</code> API を使用してフィールド情報を取得する</li><li><code>_search</code> API を使用してフィールド値を表示する</li><li>サブフィールドの表示</li><li>Synthetic _source</li><li>ランタイムフィールド</li></ol><h2>1. _mapping APIを使用してフィールド情報を取得する</h2><p><code>_mapping</code> API を使用すると、1 つまたは複数のインデックスのマッピング定義を取得できます。これには、フィールド、そのデータ型、およびその他のプロパティに関する情報が含まれます。特定のインデックスのマッピングを取得するには、次のリクエストを使用します。</p><pre><code>GET /<index_name>/_mapping</code></pre><p>たとえば、 <code>my_index</code>という名前のインデックスがある場合、次のリクエストでそのマッピングを取得できます。</p><pre><code>GET /my_index/_mapping</code></pre><p>応答には、フィールドとそのプロパティに関する情報を含むインデックスのマッピング定義が含まれます。</p><p>特定のフィールドのマッピングを取得することもできます。これは、マッピングが非常に大きく、特定のフィールドにのみ焦点を当てたい場合に便利です。特定のフィールドのマッピングを取得するには、次のリクエストを使用します。</p><pre><code>GET /my_index/_mapping/field/my_field</code></pre><p>次のリクエストのように、フィールド名をコンマで区切ることで、複数のフィールドのマッピングを取得することもできます。</p><pre><code>GET /my_index/_mapping/field/my_field_1,my_field_2,my_field_3</code></pre><h2>2. _search APIを使用してフィールド値を表示する</h2><p>Elasticsearch インデックス内のフィールドの値を表示するには、 <code>_search</code> API を使用できます。<code>_search</code> API では、返されるフィールドを制御する方法が複数用意されています。主な方法は次の 2 つです。</p><ol><li><strong><code>_source</code></strong>: <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-source-field"><code>_source</code></a>フィールドには、取り込みパイプラインまたは前処理手順によって行われた変更も含め、インデックスが作成されたとおりの元の JSON ドキュメント本体が含まれます。ソース ドキュメントの特定のフィールドを表示するには、以下に示すようにソース フィルタリングを実装します。</li><li><strong><code>fields</code></strong>: <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrieve-selected-fields"><code>fields</code></a>パラメータを使用すると、インデックス マッピングに基づいて検索を実行するときにドキュメントから特定のフィールドを取得できます。<code>_source</code>とは異なり、 <code>fields</code> <code>_source</code>を参照せずに、保存されたフィールド、ドキュメント値、またはランタイム フィールドから値を返すこともできます。ただし、ドキュメント値や保存された設定のない標準フィールドの場合は、 <code>_source</code>にフォールバックします。これによって、後述するように、パフォーマンスなど多くの利点が得られます。</li></ol><h3>_source <code> </code>フィールドの使用</h3><p>デフォルトでは、 <code> _search</code> API は、インデックスが作成された元の JSON ドキュメントを含む<code>_source</code>フィールドを返します。特定のフィールドを表示するには、検索リクエストの<code>_source </code>パラメータにフィルターを追加できます。これはソース フィルタリングと呼ばれます。</p><p>以下は、 <code>my_index</code>インデックス内のドキュメントの<code>title </code>フィールドと<code>author</code>フィールドの値を返す検索要求の例です。</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "_source": ["title", "author"]
}</code></pre><p>この例では、 <code>_source</code>パラメータは返されるフィールドを指定します。</p><p>さらに詳細な制御が必要な場合は、 <code>_source</code>オブジェクトの<code>includes</code>プロパティと<code>excludes </code>プロパティを使用できます。たとえば、次のクエリは、トップレベルの<code>title</code>フィールドと、 <code>author.description</code>を除く<code>author</code>のすべてのサブフィールドを返します。</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "_source": {
     “includes”: [“title”, “author.*],
     “excludes”: [“author.description”]
  }
}</code></pre><p>この例では、 <code>author.* </code>パターンを使用して、 <code>author </code>オブジェクトのすべての直接サブフィールドを取得します。次に、 <code>author.description </code>明示的に除外して、他の著者フィールドのみが返されるようにします。ソース JSON を読み込んで解析する必要があるため、パフォーマンスは向上しませんが、ネットワーク経由で送信される応答のサイズは小さくなることに注意してください。</p><h3>フィールドパラメータの使用</h3><p><code>fields</code>パラメータを使用して、検索応答で返されるフィールドをフィルタリングできます。<code>_source</code>ではなく<code>fields</code>を使用すると、次のようないくつかの利点があります。</p><ul><li><strong>パフォーマンスの向上:</strong> <code>fields </code> 、 <code>_source</code>全体をロードせずに、<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-store">保存されたフィールド</a>または<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/doc-values">ドキュメント値</a>から直接値を返すことができるため、応答のペイロード サイズが小さくなります。</li><li><strong>フォーマットされた出力:</strong>標準フィールドの場合、 <code> fields</code>値を取得するために<code>_source</code>にフォールバックすることがありますが、インデックス マッピングを参照して、フォーマットされた日付などの出力を適切にフォーマットし、集計や並べ替えに使用されるものと一貫性を保ちます。</li><li><strong>ランタイム フィールドへのアクセス:</strong> <code>fields</code> 、元の<code>_source</code>には存在しないランタイム フィールドを返す場合があります。</li><li>さらに詳しい特典については、<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrieve-selected-fields#search-fields-param">こちらを</a>ご覧ください。</li></ul><p>たとえば、 <code>my_index</code>インデックス内の<code>title</code>フィールドと<code>author</code>フィールドのみを返すには、次の検索リクエストを使用できます。</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author"],
  "_source": false
}</code></pre><p>上記のクエリでは、 <code>_source </code>フィールドを false に設定して、ソース ドキュメントを返さないようにします。これにより、応答のペイロード サイズを大幅に最小化できますが、これが機能するのは、フィールド<code>title</code>と<code>author</code>が<code>keyword </code>フィールド タイプであり、デフォルトで<code>doc_values</code>有効になっている場合のみであることに注意してください。フィールドで<code>doc_values</code>有効になっておらず、 <code>_source</code>が false に設定されている場合、Elasticsearch はそれらを取得する方法がなく、応答でスキップされます。</p><p><code>fields</code>レスポンスでは、値が 1 つしかない場合でも、常に各フィールドの値の配列が返されることに注意してください。これは、Elasticsearch に専用の配列タイプがなく、どのフィールドも複数の値を持つ可能性があるためです。Elasticsearch の配列の詳細については、<a href="http://elastic.co/docs/reference/elasticsearch/mapping-reference/array">ここをクリック</a>してください。</p><h3>フィールドを取得する他の方法</h3><p><code>_source</code>または<code>fields</code>を使用してフィールドを取得する方法が推奨されますが、特定のユースケースでは次のような異なる方法も使用できます。</p><p><strong>ドキュメント値フィールド:</strong> <code>_source</code>完全に回避したい場合は、 <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrieve-selected-fields#docvalue-fields"><code>docvalue_fields</code></a> <code> </code>パラメータを使用して検索できます。Doc 値は<code>_source</code>と同じフィールド値を、並べ替えと集計に最適化されたディスク上のデータ構造で保存します。</p><p>これは<code>_source</code>で保存された値とは別であるため、 <code>_source</code>全体をロードせずに特定のフィールドを要求できます。これは、大きなドキュメントをクエリしているが、ドキュメント値をサポートするいくつかの小さなフィールドのみが必要な場合に便利です。<code>docvalue_fields </code>使用するもう 1 つのユース ケースは、以下の例に示すように、 <code>date</code>と<code>numeric</code>フィールドでカスタム フォーマットを使用する場合です。</p><p>これは、 <code>doc_values</code>有効にしたフィールド、または<code>keyword</code> 、 <code>date</code> 、数値型、 <code>boolean</code>など、デフォルトで有効になっているフィールド タイプに対してのみ機能し、 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/text"><code>text</code></a>または<a href="https://www.elastic.co/docs/reference/elasticsearch/plugins/mapper-annotated-text-usage"><code>annotated_text</code></a>に対しては機能しないことに注意してください。</p><p>この例では、 <code>docvalue_fields</code>パラメータを使用して、 <code>_source</code>ドキュメント全体をロードせずに<code>title</code> 、 <code>author</code> 、および<code>published</code>フィールドを取得します。</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "docvalue_fields": [
    "title",
    "author",
    {
      "field": "published",
      "format": "epoch_millis"
    }
  ],
  "_source": false
}</code></pre><p>このクエリを実行すると、Elasticsearch は各ドキュメントの<code>_source </code>を参照するのではなく、ディスク上の列ストアから直接値を取得します。クエリに指定された<code>format</code>パラメータにより、 <code>published</code>フィールドはデフォルトの形式ではなく<code>epoch_millis</code>形式で返されます。</p><p><strong>保存されたフィールド:</strong>特定のフィールドをマッピングに<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-store">保存されているもの</a>として明示的にマークした場合は、 <code>stored_fields</code>パラメータを使用してそれらのフィールドをフィルターできます。これは、特定のフィールドのみで簡単な応答が必要な場合や、後で検索するために意図的に保存したフィールドの場合に便利です。これは<code>_source</code>とは別に保存されるため、このメソッドは<code>_source</code>をロードする必要を回避するのにも役立ちます。</p><p>このオプションはデフォルトでオフになっており、通常は推奨されないことに注意することが重要です。代わりにソース フィルタリングを使用して、元のソース ドキュメントの特定のサブセットを返します。</p><p>以下のサンプルクエリでは、 <code>stored_fields</code>パラメータを使用して、インデックス マッピング構成が「 <code>store”: true</code> 」である<code>summary</code>フィールドを取得します。</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "stored_fields": ["summary"]
}</code></pre><p>このクエリが実行されると、Elasticsearch はこのフィールドが<code>”store”: true</code>でマークされているかどうかを確認し、見つからない場合はフィールド全体をスキップします。</p><h2>3. サブフィールドの表示</h2><p>インデックスにサブフィールドが含まれている場合は、ドット表記を使用して<code>fields</code>パラメータでフィールド パスを指定できます。サブフィールドは<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/nested">ネストされたフィールド タイプ</a>とは異なることに注意してください。たとえば、 <code>address.city</code>という名前のサブフィールドがある場合、次のように検索応答に含めることができます。</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author", "address.city"],
  "_source": false
}</code></pre><p>この例では、検索応答には<code>title</code> 、 <code>author</code> 、および<code>address.city</code>フィールドの値が含まれます。</p><h2>4. 合成_ソース</h2><p><code> _source</code>を使用する機能を維持しながらディスク領域を節約したい場合は、インデックス マッピングで合成<code>_source</code>を使用するオプションがあります。<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-source-field#synthetic-source">合成<code>_source</code></a>は、 <code>_source</code>が無効になっている場合でも、Elasticsearch が保存されたフィールドやドキュメント値などの既存のデータから<code>_source</code>を再構築できるようにする機能です。これにより、再構築がオンザフライで行われるため、クエリ時の速度が若干低下しますが、多くのストレージ スペースを節約できます。インデックス設定で以下の値を使用してこの機能を有効にします。</p><pre><code>PUT idx
{
  "settings": {
    "index": {
      "mapping": {
        "source": {
          "mode": "synthetic"
        }
      }
    }
  }
}</code></pre><p>合成<code>_source </code>を使用する利点としては、 <code>_search</code> API 使用時の完全なドキュメント表示、ソース フィルタリング、 <code>_source</code>が利用可能であると想定されている Kibana などの他の機能やツールとの互換性などが挙げられますが、これらはすべて、完全な<code>_source</code>ドキュメントを保存する必要性を回避しながら実現できます。</p><h2>5. ランタイムフィールド</h2><p><a href="https://www.elastic.co/docs/manage-data/data-store/mapping/runtime-fields">ランタイム フィールドを</a>使用すると、クエリ時またはランタイム ブロックの下のインデックス マッピングでスクリプト フィールドを定義できます。これらのフィールドにはインデックスが付けられないため、ランタイム フィールドを追加してもインデックス サイズは増加しませんが、 <code>_source</code>には表示されません。マッピングで定義されたランタイム フィールドは永続的であり、すべてのクエリで使用できますが、クエリ時に定義されたランタイム フィールドは一時的であり、その検索要求でのみ使用できます。</p><p>ランタイム フィールドを使用する主な利点は、ドキュメントを取り込んだ後にフィールドを追加できるため、マッピングの決定が簡素化されることです。ランタイム フィールドは、文字列の書式設定やスコアの計算など、元のドキュメントには存在しないがスクリプトを使用して生成された値でドキュメントを充実させるのにも最適です。</p><p>また、結果セット内のすべてのドキュメントに対してスクリプトを実行する必要があるため、ランタイム フィールドはパフォーマンスに悪影響を与える可能性があることにも注意してください。<a href="https://www.elastic.co/docs/manage-data/data-store/mapping/retrieve-runtime-field">ランタイム フィールドを取得する</a>には、 <code>_search</code> API の<code>fields</code>パラメータを使用することもできます。</p><h2>まとめ</h2><p>Elasticsearch インデックスのフィールドの表示は、インデックス マッピングまたは<code>_source</code>を使用して単純に値を取得する方法から、 <code>fields</code> 、 <code>docvalue_fields</code> 、またはランタイム フィールドを使用して制御と効率性を高めるより高度な方法まで多岐にわたります。さまざまな方法間のトレードオフを理解することが、検索エクスペリエンスを最適化する鍵となります。ペイロードを最適化したり、ドキュメントを充実させたり、合成<code>_source</code>を使用してストレージを節約したりする場合でも、Elasticsearch は必要なデータを必要な方法で見つけるための複数のツールと機能を提供します。これらの手法は、データの構造を理解し、特定のフィールドを識別し、問題のトラブルシューティングを行うのに役立ちます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AI搭載ダッシュボード：ビジョンからKibanaへ]]></title>
            <description><![CDATA[LLM を使用してイメージを処理し、Kibana ダッシュボードに変換してダッシュボードを生成します。
]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/ai-powered-dashboards</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/ai-powered-dashboards</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo, Tomás Murúa]]></dc:creator>
            <pubDate>Wed, 16 Jul 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/eb60ceb2fbc3941745b21ae3357cbb6ea8fab18c-1443x811.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><a href="https://www.elastic.co/kibana/kibana-lens">Kibana Lens を</a>使用するとダッシュボードのドラッグ アンド ドロップが非常に簡単になりますが、数十個のパネルが必要な場合はクリック回数が増えてしまいます。ダッシュボードをスケッチし、スクリーンショットを撮り、LLM にプロセス全体を任せることができたらどうでしょうか?</p><p>この記事では、それを実現します。ダッシュボードのイメージを取得し、マッピングを分析し、Kibana にまったく触れることなくダッシュボードを生成するアプリケーションを作成します。</p><p><strong>手順</strong>:</p><ol><li><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#background-&amp;-application-workflow">背景とアプリケーションのワークフロー</a></li><li><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#prepare-data">データを準備する</a></li><li><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#llm-configuration">LLM構成</a></li><li><a href="https://www.elastic.co/search-labs/blog/ai-powered-dashboards#application-functions">アプリケーション機能</a></li></ol><h2>背景とアプリケーションのワークフロー</h2><p>最初に思いついたのは、LLM に NDJSON 形式の Kibana<a href="https://www.elastic.co/docs/explore-analyze/find-and-organize/saved-objects">保存オブジェクト</a>全体を生成させて、それを Kibana にインポートさせることでした。</p><p>私たちはいくつかのモデルを試しました:</p><ul><li>ジェミニ 2.5 プロ</li><li>GPT o3 / o4-ミニハイ / 4.1</li><li>クロード 4つのソネット</li><li>グロク3</li><li>ディープシーク（ディープシンク R1）</li></ul><p>プロンプトについては、次のように単純なものから始めました。</p><pre><code>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</code></pre><p><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.">いくつかのショットの例</a>と、各視覚化の構築方法に関する詳細な説明を確認したにもかかわらず、うまくいきませんでした。この実験に興味がある方は、<a href="https://gist.github.com/TomasMurua/a78dc283e115624731beffc98984b70b">こちらで</a>詳細をご覧ください。</p><p>このアプローチの結果、LLM によって生成されたファイルを Kibana にアップロードしようとしたときに、次のメッセージが表示されました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2b599443b5613c9f0fc3235581614add5b4b3900-891x98.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d87ccfc033bc00ee8188c5cae18043fbca22784c-741x233.png" style="width: 100%;" /><p>これは、生成された JSON が無効であるか、形式が間違っていることを意味します。最も一般的な問題は、LLM が不完全な NDJSON を生成したり、パラメータを幻覚させたり、あるいは、どれだけ強制しようとしても NDJSON ではなく通常の JSON を返したりすることでした。</p><p><a href="https://www.elastic.co/search-labs/blog/llm-functions-elasticsearch-intelligent-query">この記事</a>（<a href="https://www.elastic.co/docs/solutions/search/search-templates">検索テンプレートが</a>LLM フリースタイルよりもうまく機能した）に触発され、完全な NDJSON ファイルを生成するように要求するのではなく、テンプレートを LLM に提供し、コード内で LLM によって提供されたパラメータを使用して適切な視覚化を作成することにしました。このアプローチは期待を裏切らず、予測可能で拡張可能です。LLM ではなくコードが重い処理を実行するようになったためです。</p><p>アプリケーションのワークフローは次のようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/52c587cf0cf3517fdd4ee7ab95581dd4f2bce030-725x668.png" style="width: 100%;" /><p></p><p><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"><strong>この</strong></a>ノートブックに記載されています。</em></p><h2>要件</h2><p>開発を始める前に、次のものが必要です。</p><ol><li>Python 3.8以上</li><li><a href="https://docs.python.org/3/library/venv.html">Venv</a> Python環境</li><li>実行中のElasticsearchインスタンス、そのエンドポイント、APIキー</li><li>環境変数名 OPENAI_API_KEY に保存された OpenAI API キー:</li></ol><pre><code>export OPENAI_API_KEY="your-openai-api-key"</code></pre><h2>データを準備する</h2><p>データについては、シンプルさを保ち、Elastic のサンプル Web ログを使用します。<a href="https://www.elastic.co/docs/manage-data/ingest/sample-data#add-sample-data-sets">ここで、</a>そのデータをクラスターにインポートする方法を学習できます。</p><p>各ドキュメントには、アプリケーションにリクエストを発行したホストの詳細と、リクエスト自体とその応答ステータスに関する情報が含まれています。以下にサンプル文書を示します。</p><pre><code>{
    "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
}</code></pre><p>ここで、先ほどロードしたインデックス<code>kibana_sample_data_logs</code>のマッピングを取得しましょう。</p><pre><code>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"]</code></pre><p>後で読み込むイメージと一緒にマッピングを渡します。</p><h2>LLM構成</h2><p><a href="https://python.langchain.com/docs/concepts/structured_outputs/">構造化出力</a>を使用して画像を入力し、JSON オブジェクトを生成するために関数に渡す必要がある情報を含む JSON を受け取るように LLM を構成しましょう。</p><p>依存関係をインストールします。</p><pre><code>pip install elasticsearch pydantic langchain langchain-openai -q</code></pre><p>Elasticsearch は<a href="https://www.elastic.co/docs/manage-data/data-store/mapping">インデックス マッピングの</a>取得に役立ちます。Pydantic を使用すると、Python でスキーマを定義して LLM に従うように要求することができ、 <a href="https://www.elastic.co/search-labs/integrations/langchain">LangChain は</a>LLM と AI ツールの呼び出しを容易にするフレームワークです。</p><p>LLM から必要な出力を定義するために、Pydantic スキーマを作成します。画像からわかる必要があるのは、グラフの種類、フィールド、視覚化タイトル、ダッシュボード タイトルです。</p><pre><code>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]</code></pre><p>画像入力には、先ほど描いたダッシュボードを送信します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/36441d7b5dc1f3ff2ac2a30710208d57ad41c716-1600x898.jpg" style="width: 100%;" /><p>ここで、LLM モデルの呼び出しとイメージの読み込みを宣言します。この関数は、Elasticsearch インデックスのマッピングと、生成するダッシュボードの画像を受け取ります。</p><p><code>with_structured_output</code>を使用すると、Pydantic <code>Dashboard</code>スキーマを LLM が生成する応答オブジェクトとして使用できます。<a href="https://docs.pydantic.dev/latest/">Pydantic</a>を使用すると、検証付きのデータ モデルを定義できるため、LLM 出力が期待される構造と一致することが保証されます。</p><p>画像を base64 に変換して入力として送信するには、<a href="https://www.base64-image.de/">オンライン コンバーターを</a>使用するか、<a href="https://www.geeksforgeeks.org/python-convert-image-to-string-and-vice-versa/">コードで</a>実行します。</p><pre><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)}")</code></pre><p>LLM にはすでに Kibana ダッシュボードに関するコンテキストがあるため、プロンプトですべてを説明する必要はなく、Elasticsearch と Kibana で動作していることを忘れないようにするための詳細のみを説明します。</p><p>プロンプトを分解してみましょう:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">セクション</td><td style="border: 1px solid #ccc; padding: 4px;">理由</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">あなたは、Kibana バージョン 9.0.0 の画像から Kibana ダッシュボードを分析するエキスパートです。</td><td style="border: 1px solid #ccc; padding: 4px;">これを Elasticsearch と Elasticsearch バージョンで強化することで、LLM が古い/無効なパラメータを幻覚する可能性を減らします。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ダッシュボード イメージと Elasticsearch インデックス マッピングが提供されます。</td><td style="border: 1px solid #ccc; padding: 4px;">LLM による誤った解釈を避けるために、この画像はダッシュボードに関するものであることを説明します。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">以下は、ダッシュボードのベースとなるインデックスのインデックス マッピングです。これを使用すると、使用可能なデータとフィールドを理解するのに役立ちます。インデックス マッピング: {index_mappings}</td><td style="border: 1px solid #ccc; padding: 4px;">LLM が有効なフィールドを動的に選択できるようにマッピングを提供することが重要です。そうしないと、ここでのマッピングをハードコードすることになり、厳しすぎることになります。あるいは、正しいフィールド名を含むイメージに依存することになりますが、これは信頼できません。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">画像に表示されている内容に基づいて、各視覚化に関連するフィールドのみを含めます。</td><td style="border: 1px solid #ccc; padding: 4px;">画像に関係のないフィールドを追加しようとすることがあるため、この強化を追加する必要がありました。</td>
                    </tr>
              </tbody>
            </table>
          <p>これにより、表示する視覚化の配列を含むオブジェクトが返されます。</p><pre><code>"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"")"
]</code></pre><h2>LLM応答の処理</h2><p>私たちは<strong> </strong>サンプルの 2x2 パネル ダッシュボードを作成し、 <a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-get-dashboards-dashboard">Get a dashboard API を</a>使用して JSON 形式でエクスポートしました。その後、パネルを視覚化テンプレート (円グラフ、棒グラフ、メトリック) として保存し、いくつかのパラメータを置き換えて、質問に応じて異なるフィールドを持つ新しい視覚化を作成できます。</p><p>テンプレート JSON ファイルは<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>ここで</strong></a>確認できます。後で置き換えたいオブジェクトの値を {<code>variable_name</code>} に変更したことに注意してください。<br/></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ec7e1ac0cd8b470df13e60940162b56778acb386-315x234.png" style="width: 100%;" /><p>LLM が提供した情報を使用して、どのテンプレートを使用し、どの値を置き換えるかを決定できます。</p><p><code>fill_template_with_analysis</code> 視覚化の JSON テンプレート、タイトル、フィールド、グリッド上の視覚化の座標など、単一のパネルのパラメータを受け取ります。</p><p>次に、テンプレートの値を置き換えて、最終的な JSON 視覚化を返します。</p><pre><code>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)</code></pre><p>簡単にするために、LLM が作成することを決定したパネルに割り当てる静的座標があり、上の画像のように 2x2 グリッド ダッシュボードが生成されます。</p><pre><code># 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</code></pre><p>LLM によって決定された視覚化タイプに応じて、JSON ファイル テンプレートを選択し、 <code>fill_template_with_analysis</code>を使用して関連情報を置き換え、後でダッシュボードを作成するために使用する配列に新しいパネルを追加します。</p><p>ダッシュボードの準備ができたら、<a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-post-dashboards-dashboard-id">ダッシュボードの作成 API</a>を使用して新しい JSON ファイルを Kibana にプッシュし、ダッシュボードを生成します。<br/></p><pre><code>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)}")</code></pre><p>スクリプトを実行してダッシュボードを生成するには、コンソールで次のコマンドを実行します。</p><pre><code>python <file_name>.py</code></pre><p>最終結果は次のようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e909afbf0e47d9a6e0f7bd07dfb2efcfa5cf06ac-921x715.png" style="width: 100%;" /><h2>まとめ</h2><p>LLM は、テキストをコード化したり、画像をコード化したりするときに、強力な視覚機能を発揮します。ダッシュボード API を使用すると、JSON ファイルをダッシュボードに変換することも可能で、LLM といくつかのコードを使用して、画像を Kibana ダッシュボードに変換することもできます。</p><p>次のステップは、さまざまなグリッド設定、ダッシュボードのサイズ、位置を使用して、ダッシュボードのビジュアルの柔軟性を向上させることです。また、より複雑な視覚化と視覚化タイプのサポートを提供することも、このアプリケーションにとって便利な追加機能となるでしょう。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ユニークなパターンを明らかにする: Elasticsearch における重要な用語の集約ガイド]]></title>
            <description><![CDATA[重要な用語の集計を使用してデータから洞察を得る方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/significant-terms-aggregation-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/significant-terms-aggregation-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Alexander Dávila]]></dc:creator>
            <pubDate>Mon, 07 Jul 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/13adbc901837835bb56abf15e377127b017cfac8-1536x1024.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch では、<a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significantterms-aggregation">重要な用語の集約</a>により<a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-terms-aggregation">、最も一般的な用語</a>を超えて、データセット内の統計的に異常な値を見つけます。これにより、貴重な洞察や明らかでないパターンを発見することができます。重要な用語の集約により、次の 2 つの便利なパラメータを含む応答が提供されます。</p><ul><li><strong>bg_count (背景カウント):</strong>親データセットで見つかったドキュメントの数</li><li><strong>doc_count:</strong>結果データセットで見つかったドキュメントの数</li></ul><p>たとえば、携帯電話の販売データセットでは、次のようにして iPhone 16 の販売に関する重要な用語を探すことができます。</p><pre><code>GET phone_sales_analysis/_search
{
 "size": 0,
 "query": {
   "term": {
     "phone_model": {
       "value": "iPhone 16"
     }
   }
 },
 "aggs": {
   "significant_cities": {
     "significant_terms": {
       "field": "city_region",
       "size": 1
     }
   }
 }
}</code></pre><p>すると、応答は次のようになります。</p><pre><code>{
 "aggregations": {
   "significant_cities": {
     "doc_count": 122,
     "bg_count": 424,
     "buckets": [
       {
         "key": "Houston",
         "doc_count": 12,
         "score": 0.1946481360617346,
         "bg_count": 14
       }

     ]
   }
 }
}</code></pre><p>ヒューストンは、データセット全体の都市のトップ 10 にも、iPhone 16 のトップ都市にも入っていません。しかし、重要な用語の集約により<strong><em>、この都市では他のデータと比較して iPhone 16 が不均衡に多く購入されている</em></strong>ことが示されました。数字を詳しく見てみましょう:</p><ul><li><strong>最上位レベル:</strong><ul><li><strong>doc_count: 122 —</strong>クエリは合計122の文書と一致しました</li><li><strong>bg_count: 424 —</strong>背景セット（すべての販売文書）には424の文書が含まれています</li></ul></li><li><strong>ヒューストンのバケット:</strong><ul><li><strong>doc_count: 12 —</strong>ヒューストンは122件の検索結果のうち12件に出現します</li><li><strong>bg_count: 14 —</strong>ヒューストンは、背景データセット内の合計424の文書のうち14に登場します。</li></ul></li></ul><p>これは、合計 424 件の購入のうち、ヒューストンで発生したのは 14 件のみであり、これは全購入の 3.3% であることを示しています。しかし、iPhone 16の販売だけに注目すると、122件中12件がヒューストンで発生しており、これは9.8%で、データセット全体の3倍にあたります。これは大きな数字です。</p><p>これを視覚的に表すと次のようになります: city_region ごとの総売上高。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f31335b0b7793650025f941820f238dd35bfb09f-1486x1066.png" style="width: 100%;" /><p>ヒューストンには 14 件の売上があり、データセット内で売上高が 14 番目に多い都市であることがわかります。</p><p>ここで、フィルターを適用して iPhone 16 の販売のみを調べると、ヒューストンでの販売数が 12 件となり、この特定のモデルの販売数が最も多い都市として 2 番目に多い都市になります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6516040db77e6c62af5541a74c723b18008ad3c6-1472x1038.png" style="width: 100%;" /><h2>重要な用語の理解</h2><p>Elasticのドキュメントによると、<a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significantterms-aggregation">重要な用語の集約は次のとおり</a>です。</p><p><em>「前景セットと背景セット間で測定された人気度に大きな変化があった用語を検索します。」</em></p><p>つまり、統計メトリックを使用して、データのサブセット (フォアグラウンド セット) 内の用語の頻度を、データの親セット (バックグラウンド セット) 内の同じ用語の頻度と比較します。この方法では、スコアリングはデータ内での用語の出現頻度ではなく、統計的有意性を反映します。</p><p>重要な用語の集約と通常の用語の集約の主な違いは次のとおりです。</p><ul><li>重要な用語はデータのサブセットを比較しますが、用語の集約はクエリの結果のデータセットに対してのみ機能します。</li><li>用語の集約から得られる結果はデータセット内で最も一般的な用語ですが、重要な用語から得られる結果はデータセットを一意にする要素を見つけるために一般的な用語を無視します。</li><li>用語の集約と同様に、メモリではなくディスクからデータを取得する必要があるため、重要な用語はパフォーマンスに大きな影響を与える可能性があります。</li></ul><h2>実践応用（消費者行動分析）</h2><h3>分析のためのデータの準備</h3><p>この分析のために、価格、携帯電話の仕様、購入者の人口統計、フィードバックを含む合成携帯電話販売データセットを生成しました。また、後でセマンティッククエリを実行できるように、ユーザーのフィードバックから埋め込みを生成しました。Elasticsearch ですぐに使用できる<a href="https://huggingface.co/intfloat/multilingual-e5-small">多言語 e5 小型モデル</a>を使用しました。</p><p></p><p>このデータセットを Elasticsearch で使用するには:</p><ol><li>Kibana の データファイルアップロード 機能を使用して、CSV ファイル<a href="https://www.elastic.co/docs/manage-data/ingest/upload-data-files"> (</a><a href="https://github.com/Alex1795/significant_terms_blog_dataset/blob/main/phone_sales_analysis_dataset.csv"> ここ</a> からダウンロード可能) をアップロードします。</li><li><a href="https://www.elastic.co/search-labs/blog/chat-with-pdf-elastic-playground#upload-pdfs-to-kibana">このブログ</a>で紹介されている「埋め込み」と呼ばれるセマンティックフィールドを設定します。 <code>multilingual-e5-small model</code></li><li>フィールド タイプのデフォルト ( <code>purchase_date</code>と<code>user_feedback)</code>を除くすべてのフィールドのキーワード) を使用してインポートを終了します。ここで提示されたクエリをそのまま実行できるようにするには、インデックス名<code>phone_sales_analysis</code>を必ず追加してください。</li></ol><p>この分析の主な焦点は、 <strong><em>「iPhone 16 の購入者と他の人口セグメントの違いは何か</em></strong>」を発見し、マーケティング目的で購入者をセグメント化することです。</p><p>これはデータセットからのサンプルドキュメントです。</p><pre><code>{
         "customer_type": "Returning",
         "user_feedback": "I have to say, quality is great for the price. The battery life is really good.",
         "upgrade_frequency": "2 years",
         "storage_capacity": "256GB",
         "occupation": "Technology & Data",
         "color": "Phantom Black",
         "gender": "Male",
         "price_paid": 899,
         "previous_brand_loyalty": "Mixed",
         "location_type": "Urban",
         "phone_model": "Samsung Galaxy S24",
         "city_region": "San Francisco Bay Area",
         "@timestamp": "2024-03-15T00:00:00.000-05:00",
         "income_bracket": "75000-100000",
         "purchase_channel": "Online",
         "feedback_sentiment": "positive",
         "education_level": "Bachelor",
         "embedding": "I have to say, quality is great for the price. The battery life is really good.",
         "customer_id": "C001",
         "purchase_date": "2024-03-15",
         "age": 34,
         "trade_in_model": "iPhone 13"
}</code></pre><h3>人口動態パターンの理解</h3><p>ここでは、一般人口を対象に分析を実行し、iPhone 16 ユーザーにとって重要な用語の集計から得られた興味深い結果と比較します。</p><h4>通常のパターン</h4><p>通常の購入パターンを理解するために、さまざまなフィールドにわたるすべてのドキュメントのデータを集計できます。簡単にするために、携帯電話を購入した人の職業に焦点を当てて調査します。Elasticsearch へのリクエストでこれを実行できます。</p><pre><code>GET phone_sales_analysis/_search
{
 "aggs": {
   "occupation_distribution": {
     "terms": {
       "size": 5,
       "field": "occupation"
     }
   }
 },
 "size": 0
}</code></pre><p>これにより、データセット内の主な職業（レコード数別）は次のようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/99136ddddd7abad5d74481158a04501b6915441b-1518x480.png" style="width: 100%;" /><h4>iPhone 16ユーザーのパターン</h4><p>iPhone 16 を購入した人々の違いを理解するために、次のように、クエリ内の人々を見つけるためのフィルターを使用して、同じフィールドで用語の集計を実行してみましょう。</p><pre><code>GET phone_sales_analysis/_search
{
  "query": {
    "term": {
      "phone_model": "iPhone 16"
    }
  },
  "aggs": {
    "occupation_distribution": {
      "terms": {
        "size": 5,
        "field": "occupation"
      }
    }
  },
  "size": 0
}</code></pre><p>つまり、iPhone 16ユーザーの主な職業は次のようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/36ce86475beb03810c6ad81d7c776d1eec736654-1500x484.png" style="width: 100%;" /><p>iPhone 16 ユーザーは、他の電話モデルのユーザーと比べて、使用パターンが異なっていることがわかります。Kibana を使用して結果を簡単に視覚化してみましょう。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b70ab05bc962a274e1617b6caf20575c489a62d8-1448x1128.png" style="width: 100%;" /><p></p><p>このグラフでは、iPhone 16 の傾向が全体の傾向と異なることがわかります。</p><p>この分析全体をスキップして、1つの重要な用語の集計を実行することで、iPhone 16ユーザーが一般ユーザーと何が違うのかを確認できます。</p><pre><code>GET phone_sales_analysis/_search
{
  "query": {
    "term": {
      "phone_model": "iPhone 16"
    }
  },
  "aggs": {
    "occupation_distribution": {
      "significant_terms": {
        "size": 5,
        "field": "occupation"
      }
    }
  },
  "size": 0
}</code></pre><p>つまり、次のような応答が得られます。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">iPhone 16の職業の価値</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント数</td><td style="border: 1px solid #ccc; padding: 4px;">bg_count</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">職業分布（最上位レベル）</td><td style="border: 1px solid #ccc; padding: 4px;">122</td><td style="border: 1px solid #ccc; padding: 4px;">424</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">医療・ヘルスケア分野</td><td style="border: 1px solid #ccc; padding: 4px;">45</td><td style="border: 1px solid #ccc; padding: 4px;">57</td>
                    </tr>
              </tbody>
            </table>
          <p>この回答は、iPhone 16 ユーザーが珍しい (つまり重大な) 状況にあることを明確に示しています。一般人口と比較した医療・ヘルスケア分野に従事する人の数。応答内の数字が何を意味するか見てみましょう。</p><ul><li><strong>最上位レベル:</strong><ul><li><strong>doc_count: 122 —</strong>クエリは合計122の文書と一致しました</li><li><strong>bg_count: 424 —</strong>背景セット（すべての販売文書）には424の文書が含まれています</li></ul></li><li><strong>医療・ヘルスケア分野:</strong><ul><li><strong>doc_count: 45 —</strong> 122件の検索結果のうち45件に「医療とヘルスケア」が出現</li><li><strong>bg_count: 57 —</strong>背景データセット内の合計424の文書のうち57に「医療とヘルスケア」が出現します</li></ul></li></ul><p>424 人の購入者のうち 57 人が医療・ヘルスケア分野で働いており、割合は 13.44% です。しかし、iPhone 16の購入者を見てみると、122人中45人が医療・ヘルスケア分野で働いており、その割合は36.88%です。つまり、iPhone 16 ユーザーの中に医療・ヘルスケアの分野で働く人がいる可能性が 2 倍になるということです。</p><p>同じ分析を他のフィールド（年齢、場所、収入層など）に適用すると、iPhone 16 ユーザーの独自性に関する詳細な情報を見つけることができます。</p><h3>消費者セグメンテーション</h3><p>重要な用語の集約を使用して、製品、カテゴリ、顧客セグメント間の関係性の洞察を抽出できます。このため、調査したいカテゴリの親集計を構築します。また、重要な用語と通常の用語のサブ集計を使用して、そのカテゴリに関する興味深い洞察を見つけ、その職業のほとんどの人が使用する用語と比較します。</p><p>たとえば、いくつかの職種の人々が何を好むかを見てみましょう。</p><ol><li>分析をより明確にするために、検索を3つの職種に限定してみましょう: [「管理・サポート」、「テクノロジー・データ」、「医療・ヘルスケア」]</li><li>集計側では、職業別の用語集計から始めます。</li><li>サブ集計を1つ追加します: 電話モデル別の用語 - 各分野のユーザーがどのモデルを購入しているかを調べます</li><li>2番目のサブ集計を追加します: 電話モデル別の重要な用語 - 各作業分野でどのモデルが特別であるかを見つけます</li></ol><pre><code>GET phone_sales_analysis/_search
{
 "query": {
   "terms": {
     "occupation": [
       "Administrative & Support",
       "Technology & Data",
       "Medical & Healthcare"
     ]
   }
 },
 "aggs": {
   "occupations": {
     "terms": {
       "size": 15,
       "field": "occupation"
     },
     "aggs": {
       "general_models": {
         "terms": {
           "field": "phone_model"
         }
       },
       "significant_models": {
         "significant_terms": {
           "field": "phone_model"
         }
       }
     }
   }
 },
 "size": 0
}</code></pre><p>集計結果を詳しく見てみましょう。</p><p><strong>職業</strong>：管理・サポート</p><p><strong>用語の集約</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a4ad519c9013867a3f4cee032160eadd8a47804a-1506x398.png" style="width: 100%;" /><p><strong>重要な用語の集約</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e5604fa8036667bdfe733576a5e7c6153760dd3a-306x220.png" style="width: 100%;" /><p>この表から、この職業の傾向と人口全体の傾向の間には大きな違いがないことが推測できます。</p><p><strong>職業</strong>：テクノロジー＆データ</p><p><strong>用語の集約</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/13b09039bb7d183276451007d2d69dc190b1d3c0-1508x836.png" style="width: 100%;" /><p></p><p><strong>重要な用語の集約</strong></p><p>合計文書数: 424</p><p>この職業に関する文書: 71</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">携帯電話のモデル</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント数 （この職業のこのモデル）</td><td style="border: 1px solid #ccc; padding: 4px;">bg_count （すべての文書でこのモデルを使用）</td><td style="border: 1px solid #ccc; padding: 4px;">すべての文書の%</td><td style="border: 1px solid #ccc; padding: 4px;">この職業における%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">グーグルピクセル8</td><td style="border: 1px solid #ccc; padding: 4px;">12</td><td style="border: 1px solid #ccc; padding: 4px;">22</td><td style="border: 1px solid #ccc; padding: 4px;">5.19%</td><td style="border: 1px solid #ccc; padding: 4px;">16.90%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ワンプラス11</td><td style="border: 1px solid #ccc; padding: 4px;">9</td><td style="border: 1px solid #ccc; padding: 4px;">14</td><td style="border: 1px solid #ccc; padding: 4px;">3.30%</td><td style="border: 1px solid #ccc; padding: 4px;">12.68%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ワンプラス 12 プロ</td><td style="border: 1px solid #ccc; padding: 4px;">3</td><td style="border: 1px solid #ccc; padding: 4px;">3</td><td style="border: 1px solid #ccc; padding: 4px;">0.71%</td><td style="border: 1px solid #ccc; padding: 4px;">4.23%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Google Pixel 8 Pro</td><td style="border: 1px solid #ccc; padding: 4px;">9</td><td style="border: 1px solid #ccc; padding: 4px;">21</td><td style="border: 1px solid #ccc; padding: 4px;">4.95%</td><td style="border: 1px solid #ccc; padding: 4px;">12.68%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">何もない電話2</td><td style="border: 1px solid #ccc; padding: 4px;">5</td><td style="border: 1px solid #ccc; padding: 4px;">8</td><td style="border: 1px solid #ccc; padding: 4px;">1.89%</td><td style="border: 1px solid #ccc; padding: 4px;">7.04%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">サムスン ギャラクシー Z フォールド5</td><td style="border: 1px solid #ccc; padding: 4px;">4</td><td style="border: 1px solid #ccc; padding: 4px;">6</td><td style="border: 1px solid #ccc; padding: 4px;">1.42%</td><td style="border: 1px solid #ccc; padding: 4px;">5.63%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ワンプラス12</td><td style="border: 1px solid #ccc; padding: 4px;">8</td><td style="border: 1px solid #ccc; padding: 4px;">20</td><td style="border: 1px solid #ccc; padding: 4px;">4.72%</td><td style="border: 1px solid #ccc; padding: 4px;">11.27%</td>
                    </tr>
              </tbody>
            </table>
          <p><strong>職業</strong>：医療・ヘルスケア</p><p><strong>用語の集約</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b008e996742fc0bb48dc6bacff17cfbc56cf0d73-1492x398.png" style="width: 100%;" /><p><strong>重要な用語の集約</strong></p><p>合計文書数: 424</p><p>この職業に関する文書: 57</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">携帯電話のモデル</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント数 （この職業のこのモデル）</td><td style="border: 1px solid #ccc; padding: 4px;">bg_count （すべての文書でこのモデルを使用）</td><td style="border: 1px solid #ccc; padding: 4px;">すべての文書の%</td><td style="border: 1px solid #ccc; padding: 4px;">この職業における%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">iPhone 16</td><td style="border: 1px solid #ccc; padding: 4px;">45</td><td style="border: 1px solid #ccc; padding: 4px;">122</td><td style="border: 1px solid #ccc; padding: 4px;">28.77%</td><td style="border: 1px solid #ccc; padding: 4px;">78.95%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">iPhone 15 Pro Max</td><td style="border: 1px solid #ccc; padding: 4px;">3</td><td style="border: 1px solid #ccc; padding: 4px;">13</td><td style="border: 1px solid #ccc; padding: 4px;">3.07%</td><td style="border: 1px solid #ccc; padding: 4px;">5.26%</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">iPhone 15</td><td style="border: 1px solid #ccc; padding: 4px;">7</td><td style="border: 1px solid #ccc; padding: 4px;">40</td><td style="border: 1px solid #ccc; padding: 4px;">9.43%</td><td style="border: 1px solid #ccc; padding: 4px;">12.28%</td>
                    </tr>
              </tbody>
            </table>
          <p>このデータが何を伝えているのか見てみましょう。</p><ul><li>医療およびヘルスケアの専門家は iPhone 16 を好み、一般的に Apple の携帯電話を使用する傾向が非常に強いです。</li><li>テクノロジーおよびデータの専門家はハイエンドの Android スマートフォンを好みますが、必ずしも Samsung ブランドを使用するわけではありません。このカテゴリーでは、iPhone にもかなりのトレンドが見られます。</li><li>管理およびサポート担当者は Samsung や Google の携帯電話を好みますが、明確な独自の傾向はありません。</li></ul><h3>重要語の集約とハイブリッド検索</h3><p>ハイブリッド検索は、テキスト検索とセマンティック結果を組み合わせて、検索エクスペリエンスを向上させます。この文脈において、重要な用語の集約は<strong>、「このデータセットは他のすべての文書と比べて何が特別なのか？」</strong>という問いに答えることで、コンテキスト認識検索の結果に関する洞察を提供することができます。この機能を示すために、ユーザーが優れたパフォーマンスについて語る際に、どのモデルが過剰に表現されているかを見てみましょう。 </p><ul><li>フィールド埋め込みよりも入力「良いパフォーマンス」に近いトップユーザーフィードバックを見つけるセマンティッククエリを構築してみましょう。</li><li>テキストフィールドuser_feedbackで同じ用語を使ったテキスト検索も使用します。</li><li>また、完全なデータセットよりもこれらの結果の中でより頻繁に見つかる電話モデルを見つけるために、重要な用語クエリを追加します。<br/></li></ul><pre><code>GET phone_sales_analysis/_search
{
 "retriever": {
   "rrf": {
     "retrievers": [
       {
         "standard": {
           "query": {
             "bool": {
               "must": [
                 {
                   "match": {
                     "user_feedback": {
                       "query": "good performance",
                       "operator": "and"
                     }
                   }
                 }
               ]
             }
           }
         }
       },
       {
         "standard": {
           "query": {
             "semantic": {
               "field": "embedding",
               "query": "good performance"
             }
           }
         }
       }
     ],
    "rank_window_size": 20
   }
 },
 "aggs": {
   "Models": {
     "significant_terms": {
       "field": "phone_model"
     }
   }
 }
}</code></pre><p>一致するドキュメントの例を見てみましょう。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4cb488097a382f0c28c21540db4f593d23633473-1600x162.png" style="width: 100%;" /><p>返ってくる応答は次のとおりです。</p><pre><code>{
  "took": 388,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 20,
      "relation": "eq"
    },
    "max_score": 0.016393442,
    "hits": [...]
  },
  "aggregations": {
    "Models": {
      "doc_count": 20,
      "bg_count": 424,
      "buckets": [
        {
          "key": "iPhone 15",
          "doc_count": 5,
          "score": 0.4125,
          "bg_count": 40
        }
      ]
    }
  }
}</code></pre><p></p><p>これは、iPhone 15 が合計 424 のドキュメントのうち 40 回 (ドキュメントの 9.4%) 出現している一方で、セマンティック検索「良好なパフォーマンス」に一致した 20 のドキュメント (ドキュメントの 25%) では 5 回見つかるということを示しています。したがって、次のような結論を導き出すことができます。優れたパフォーマンスについて話しているときに、偶然よりも iPhone 15 が見つかる可能性が 2.7 倍高くなります。</p><h2>まとめ</h2><p>重要な用語の集約により、データセットをドキュメント全体と比較することで、データセットの固有の詳細を明らかにすることができます。これにより、発生回数を超えて、データ内の予期しない関係が明らかになる可能性があります。さまざまなユースケースで重要な用語を適用して、非常に興味深い機能を実現できます。たとえば、次のようになります。</p><ul><li><a href="https://www.elastic.co/blog/significant-terms-aggregation#credit">不正行為検出に取り組む際にパターンを見つけ</a>、盗難されたクレジットカードの一般的な取引を識別します。</li><li>ユーザーレビューからのブランド品質の洞察 - 悪いレビューの数が不釣り合いに多いブランドを検出します。</li><li>誤分類されたドキュメントの<a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significantterms-aggregation#_use_on_free_text_fields">検出</a>- 説明にそのカテゴリに一般的でない単語を使用しているカテゴリ (用語フィルター) に属するドキュメントを検出します (重要な用語の集約)。</li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[JavaScript、Mastra、Elasticsearch を使用したエージェント型 RAG アシスタントの構築]]></title>
            <description><![CDATA[JavaScript エコシステムで AI エージェントを構築する方法を学ぶ]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/agentic-rag</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/agentic-rag</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[JD Armada]]></dc:creator>
            <pubDate>Tue, 01 Jul 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8132ed781c1ea5d46ca244182f421ed5c721f23b-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>このアイデアは、白熱したハイリスクなファンタジー バスケットボール リーグの最中に思いつきました。私はこう考えました。 <em>「毎週の対戦で優位に立つのに役立つ AI エージェントを構築できるだろうか？」 もちろんです!</em></p><p>この記事では、 <a href="https://mastra.ai/en/docs">Mastra</a>とそれと対話するための軽量 JavaScript Web アプリケーションを使用して、エージェント RAG アシスタントを構築する方法について説明します。このエージェントを Elasticsearch に接続することで、構造化されたプレイヤーデータへのアクセスとリアルタイムの統計集計の実行が可能になり、プレイヤー統計に基づいた推奨事項を提供できるようになります。GitHub<a href="https://github.com/jdarmada/nba-ai-assistant-js.git">リポジトリ</a>にアクセスして手順を確認してください。README<a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/README.md">に</a>は、アプリケーションを独自に複製して実行する方法が記載されています。 </p><p>すべてをまとめると次のようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1c73bd1dc1b5fe54f025c7a2b7c322acc9122f3a-1999x1393.png" style="width: 100%;" /><p>注: このブログ投稿は、「 <a href="https://www.elastic.co/search-labs/blog/ai-agents-ai-sdk-elasticsearch">AI SDK と Elastic を使用した AI エージェントの構築</a>」に基づいています。AI エージェント全般とその用途についてよく知らない場合は、まずそこから始めてください。<br/></p><h2><strong>アーキテクチャの概要</strong></h2><p>システムの中核となるのは、エージェントの推論エンジン（脳）として機能する大規模言語モデル（LLM）です。ユーザー入力を解釈し、呼び出すツールを決定し、関連する応答を生成するために必要な手順を調整します。</p><p>エージェント自体は、JavaScript エコシステムのエージェント フレームワークである Mastra によって構築されます。Mastra は、LLM をバックエンド インフラストラクチャでラップし、それを API エンドポイントとして公開し、ツール、システム プロンプト、エージェントの動作を定義するためのインターフェイスを提供します。</p><p>フロントエンドでは、 <a href="https://vite.dev/guide/">Vite</a>を使用して、エージェントにクエリを送信してその応答を受信するためのチャット インターフェイスを提供する React Web アプリケーションを迅速に構築します。</p><p>最後に、エージェントがクエリして集計できるプレーヤーの統計情報と対戦データを保存する Elasticsearch があります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/443bdc00d84ed1dd49e9f9e431e86ca4b0892563-1999x977.png" style="width: 100%;" /><h2><strong>背景</strong></h2><p>いくつかの基本的な概念を確認してみましょう。</p><h3><strong>エージェントRAGとは何ですか？</strong></h3><p>AI エージェントは他のシステムと対話し、独立して動作し、定義されたパラメータに基づいてアクションを実行できます。Agentic RAG は、AI エージェントの自律性と検索拡張生成の原理を組み合わせ、LLM が応答を生成するために呼び出すツールとコンテキストとして使用するデータを選択できるようにします。RAG の詳細については、<a href="https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag">こちらを</a>ご覧ください。</p><h3><strong>フレームワークを選択する場合、なぜ AI-SDK を超えるのでしょうか?</strong></h3><p>利用可能な AI エージェント フレームワークは数多くあり、 <a href="https://www.elastic.co/search-labs/blog/using-crewai-with-elasticsearch">CrewAI</a> 、 <a href="https://www.elastic.co/search-labs/blog/using-autogen-with-elasticsearch">AutoGen</a> 、 <a href="https://www.elastic.co/search-labs/blog/build-rag-workflow-langgraph-elasticsearch">LangGraph</a>などの人気のフレームワークについてはおそらく聞いたことがあるでしょう。これらのフレームワークのほとんどは、さまざまなモデルのサポート、ツールの使用、メモリ管理など、共通の機能セットを共有しています。</p><p>こちらは、Harrison Chase (LangChain CEO) によるフレームワーク<a href="https://docs.google.com/spreadsheets/d/1B37VxTBuGLeTSPVWtz7UMsCdtXrqV5hCjWkbHN8tfAo/edit?gid=0#gid=0">比較シート</a>です。</p><p>私が Mastra に興味を持ったのは、フルスタック開発者がエージェントをエコシステムに簡単に統合できるように構築された JavaScript ファーストのフレームワークであるという点です。Vercel の AI-SDK もこのほとんどを実行しますが、プロジェクトにさらに複雑なエージェント ワークフローが含まれている場合は、Mastra が真価を発揮します。Mastra は AI-SDK によって設定された基本パターンを強化しており、このプロジェクトではそれらを連携して使用します。</p><h3><strong>フレームワークとモデル選択の考慮事項</strong></h3><p>これらのフレームワークは AI エージェントを迅速に構築するのに役立ちますが、考慮すべき欠点もいくつかあります。たとえば、AI エージェントや一般的な抽象化レイヤー以外のフレームワークを使用する場合、制御が少し失われます。LLM がツールを正しく使用しなかったり、望ましくないことを実行したりする場合、抽象化によってデバッグが難しくなります。それでも、私の意見では、特にこれらのフレームワークは勢いを増しており、継続的に反復されているため、このトレードオフは、構築時に得られる容易さとスピードの価値があります。</p><p>繰り返しになりますが、これらのフレームワークはモデルに依存しません。つまり、さまざまなモデルをプラグ アンド プレイできます。モデルはトレーニングに使用されたデータ セットによって異なり、その結果、モデルが提供する応答も異なることに注意してください。一部のモデルではツールの呼び出しすらサポートされていません。したがって、さまざまなモデルを切り替えてテストし、どのモデルが最適な応答を返すかを確認することは可能ですが、それぞれのシステム プロンプトを書き換える必要がある可能性が高いことに注意してください。例えば、Llama3.3を使用する場合GPT-4o よりも、必要な応答を得るために、より多くのプロンプトと具体的な指示が必要になります。</p><h3><strong>NBAファンタジーバスケットボール</strong></h3><p>ファンタジー バスケットボールでは、友達のグループでリーグを開始し (グループの競争力に応じて、友情のステータスに影響する可能性があります)、通常はいくらかのお金が賭けられます。その後、各自が 10 人のプレイヤーでチームを編成し、毎週交互に他の友達の 10 人のプレイヤーと対戦します。全体のスコアに加算されるポイントは、特定の週に各プレイヤーが対戦相手に対して行ったパフォーマンスです。</p><p>チームの選手が負傷したり、出場停止になったりした場合は、チームに追加できるフリーエージェント選手のリストが表示されます。ファンタジー スポーツでは、選べる選手の数が限られており、誰もが常に最高の選手を選ぶために奔走しているため、ここで多くの難しい思考が生まれます。</p><p>これは、どの選手を選択するかをすぐに決定しなければならない状況で特に役立つ、NBA AI アシスタントの出番です。特定の対戦相手に対するプレーヤーのパフォーマンスを手動で調べる代わりに、アシスタントがそのデータをすばやく見つけて平均を比較し、情報に基づいた推奨事項を提供します。</p><p>エージェント RAG と NBA ファンタジー バスケットボールの基本がわかったので、実際に見てみましょう。</p><h2><strong>プロジェクトの構築</strong></h2><p>途中で行き詰まったり、最初から構築したくない場合は、<a href="https://github.com/jdarmada/nba-ai-assistant-js.git">リポジトリ</a>を参照してください。</p><h3><strong>取り上げる内容</strong></h3><ol><li><strong>プロジェクトの足場作り:</strong><ol><li><strong>バックエンド (Mastra):</strong> npx create mastra@latest を使用してバックエンドをスキャフォールディングし、エージェント ロジックを定義します。</li><li><strong>フロントエンド (Vite + React):</strong> npm create vite@latest を使用して、エージェントと対話するためのフロントエンド チャット インターフェイスを構築します。</li></ol></li><li><strong>環境変数の設定</strong><ol><li>環境変数を管理するには、dotenv をインストールします。</li><li>.envを作成するファイルを開き、必要な変数を指定します。</li></ol></li><li><strong>Elasticsearchの設定</strong><ol><li>Elasticsearch クラスターを起動します (ローカルまたはクラウド上)。</li><li>公式 Elasticsearch クライアントをインストールします。</li><li>環境変数にアクセスできることを確認します。</li><li>クライアントへの接続を確立します。</li></ol></li><li><strong>NBA データを Elasticsearch に一括取り込み</strong><ol><li>集計を有効にするには、適切なマッピングを使用してインデックスを作成します。</li><li>プレイヤーのゲーム統計を CSV ファイルから Elasticsearch インデックスに一括取り込みます。</li></ol></li><li><strong>Elasticsearchの集計を定義する</strong><ol><li>特定の対戦相手に対する過去の平均を計算するクエリ。</li><li>特定の対戦相手に対するシーズン平均を計算するクエリ。</li></ol></li><li><strong>プレーヤー比較ユーティリティファイル</strong><ol><li>ヘルパー関数と Elasticsearch 集計を統合します。</li></ol></li><li><strong>エージェントの構築</strong><ol><li>エージェント定義とシステム プロンプトを追加します。</li><li>zod をインストールし、ツールを定義します。</li><li>CORS を処理するためのミドルウェア設定を追加します。</li></ol></li><li><strong>フロントエンドの統合</strong><ol><li>AI-SDK の useChat を使用してエージェントと対話します。</li><li>適切にフォーマットされた会話を保持するための UI を作成します。</li></ol></li><li><strong>アプリケーションの実行</strong><ol><li>バックエンド (Mastra サーバー) とフロントエンド (React アプリ) の両方を起動します。</li><li>サンプルクエリと使用方法。</li></ol></li><li><strong>次はエージェントのさらなるインテリジェント化</strong><ol><li>セマンティック検索機能を追加して、より洞察力のある推奨を可能にします。</li><li>検索ロジックを Elasticsearch MCP (Model Context Protocol) サーバーに移動することで、動的クエリを有効にします。</li></ol></li></ol><h3><strong>要件</strong></h3><ul><li><strong>Node.js と npm</strong> : バックエンドとフロントエンドの両方が Node 上で実行されます。Node 18+ と npm v9+ (Node 18+ にバンドルされています) がインストールされていることを確認してください。</li><li><strong>Elasticsearch クラスター:</strong>ローカルまたはクラウド上のアクティブな Elasticsearch クラスター。</li><li><strong>OpenAI API キー</strong>: <a href="https://platform.openai.com/api-keys">OpenAI 開発者ポータルの</a>API キー ページで生成します。</li></ul><p></p><h3><strong>プロジェクト構造</strong></h3><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1c0bde11ad0eead523a95e03b9b905aa776e3fd1-1420x934.png" style="width: 100%;" /><h4><strong>ステップ1：プロジェクトの足場作り</strong></h4><ol><li>まず、nba-ai-assistant-js ディレクトリを作成し、次のコマンドを使用して内部に移動します。 </li></ol><pre><code>mkdir nba-ai-assistant-js && cd nba-ai-assistant-js</code></pre><p><strong>バックエンド:</strong></p><ol><li>次のコマンドで Mastra 作成ツールを使用します。 </li></ol><pre><code>npx create-mastra@latest</code></pre><p>2. ターミナルにいくつかのプロンプトが表示されます。最初のプロンプトでは、プロジェクトに backend という名前を付けます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/de2725031ed6837db99a979efcdd0ece1e197dbb-608x84.png" style="width: 100%;" /><p>3. 次に、Mastra ファイルを保存するためのデフォルトの構造を維持するため、 <code>src/</code>を入力します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/88919d9ff1852126395e1fcd700ecb1b59aac63c-866x116.png" style="width: 100%;" /><p>4. 次に、デフォルトの LLM プロバイダーとして OpenAI を選択します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2328761e769f3ded134e5a21e8a0bf8f41e88f68-404x210.png" style="width: 100%;" /><p>5. 最後に、OpenAI API キーの入力が求められます。ここでは、スキップするオプションを選択し、後で<code> .env</code>ファイルで提供します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0662de9bd28758e377e4c63df8d08b479068ce63-444x120.png" style="width: 100%;" /><p><strong>フロントエンド：</strong></p><ol><li>ルート ディレクトリに戻り、次のコマンドを使用して<a href="https://vite.dev/guide/">Vite 作成ツール</a>を実行します。 <code>npm create vite@latest frontend -- --template react</code></li></ol><p>これにより、React 専用のテンプレートを使用して、 <code>frontend</code>という名前の軽量 React アプリが作成されます。</p><p>すべてがうまくいけば、プロジェクト ディレクトリ内に、Mastra コードを保持するバックエンド ディレクトリと、React アプリを含む<code>frontend</code>ディレクトリが表示されるはずです。</p><p></p><h4><strong>ステップ2: 環境変数の設定</strong></h4><ol><li>機密キーを管理するために、 <code>dotenv</code>パッケージを使用して.envから環境変数を読み込みます。ファイル。バックエンドディレクトリに移動して<code>dotenv</code>をインストールします。</li></ol><pre><code>cd backend
npm install dotenv --save</code></pre><p>2. バックエンド ディレクトリでは、適切な変数を入力するための example.env ファイルが提供されます。独自に作成する場合は、次の変数を必ず含めてください。</p><pre><code># OpenAI Configuration
OPENAI_API_KEY=your_openai_api_key_here

# Elasticsearch Configuration
ELASTIC_ENDPOINT=your_elasticsearch_endpoint_here
ELASTIC_API_KEY=your_elasticsearch_api_key_here
</code></pre><p></p><p>注意: <code>.env</code> <code>.gitignore</code>に追加して、このファイルがバージョン管理から除外されていることを確認してください。</p><h4><strong>ステップ3: Elasticsearchの設定</strong></h4><p>まず、アクティブな Elasticsearch クラスターが必要です。次の 2 つのオプションがあります。</p><ul><li><strong>オプションA: Elasticsearch Cloudを使用する</strong><ul><li><a href="https://cloud.elastic.co/registration">Elastic Cloud</a>にサインアップ</li><li>新しいデプロイメントを作成する</li><li>エンドポイント URL と API キー（エンコード済み）を取得します</li></ul></li><li><strong>オプションB: Elasticsearchをローカルで実行する</strong><ul><li>Elasticsearchをローカルにインストールして実行する</li><li>エンドポイントとして http://localhost:9200 を使用します</li><li>APIキーを生成する</li></ul></li></ul><p></p><p><strong>バックエンドに Elasticsearch クライアントをインストールする:</strong></p><ol><li>まず、バックエンド ディレクトリに公式 Elasticsearch クライアントをインストールします。</li></ol><pre><code>npm install @elastic/elasticsearch</code></pre><p>2. 次に、再利用可能な関数を保持するディレクトリ lib を作成し、そこに移動します。</p><pre><code>mkdir lib && cd lib</code></pre><p>3. 内部に<a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/elasticClient.js">elasticClient.js</a>という新しいファイルを作成します。このファイルは Elasticsearch クライアントを初期化し、プロジェクト全体で使用できるように公開します。</p><p>4. ECMAScript モジュール (ESM) を使用しているため、 __dirname and __ファイル名は使用できません。環境変数が.envから正しく読み込まれていることを確認するにはバックエンド フォルダー内のファイルで、ファイルの先頭に次の設定を追加します。</p><pre><code>import { config } from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { Client } from '@elastic/elasticsearch';

// Grab current directory and load .env from backend folder
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const envPath = join(__dirname, '../.env');

// Load environment variables from the correct path
config({ path: envPath });</code></pre><p>5. 次に、環境変数を使用して Elasticsearch クライアントを初期化し、接続を確認します。</p><pre><code>//Elastic client Initialization, make sure environment variables are being loaded in correctly
const config= {
    node: `${process.env.ELASTIC_ENDPOINT}`,
    auth: {
        apiKey: `${process.env.ELASTIC_API_KEY}`,
    },
};

export const elasticClient = new Client(config);

//Check if the client is connected
async function checkConnection() { 
    try {
        const info = await elasticClient.info();
        console.log('Elasticsearch is connected:', info);
    } catch (error) {
        console.error('Elasticsearch connection error:', error);
    }
}

checkConnection();
</code></pre><p>これで、このクライアント インスタンスを、Elasticsearch クラスターと対話する必要がある任意のファイルにインポートできます。</p><p></p><h4><strong>ステップ4: NBAデータをElasticsearchに一括取り込み</strong></h4><p><strong>データセット:</strong></p><p>このプロジェクトでは、リポジトリの<a href="https://github.com/jdarmada/nba-ai-assistant-js/tree/main/backend">backend/data</a>ディレクトリにあるデータセットを参照します。当社の NBA アシスタントは、このデータを知識ベースとして使用し、統計的な比較を実行し、推奨事項を生成します。</p><ul><li><a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/data/sample_nba_data.csv">sample_player_game_stats.csv</a> - サンプルプレーヤーのゲーム統計 (例: NBA キャリア全体におけるプレーヤーごとのゲームごとのポイント、リバウンド、スティールなど)。このデータセットを使用して集計を実行します。(注: これはデモ用に事前に生成された模擬データであり、公式 NBA ソースから取得されたものではありません。)</li><li><a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/data/playerAndTeamInfo.js">playerAndTeamInfo.js</a> - 通常は API 呼び出しによって提供されるプレーヤーとチームのメタデータを置き換え、エージェントがプレーヤーとチームの名前を ID に一致できるようにします。サンプル データを使用しているため、外部 API から取得する際のオーバーヘッドを避け、エージェントが参照できるいくつかの値をハードコードしました。</li></ul><p></p><p><strong>実装：</strong></p><ol><li><code>backend/lib</code>ディレクトリで、 <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/playerDataIngestion.js">playerDataIngestion.js</a>という名前のファイルを作成します。</li><li>インポートを設定し、CSV ファイル パスを解決し、解析を設定します。ここでも、ESM を使用しているため、サンプル CSV へのパスを解決するには<code>__dirname</code>を再構築する必要があります。また、 <a href="http://node.js/">Node.js</a>の組み込みモジュール<code>fs</code>と<code>readline</code>を使用して、指定された CSV ファイルを行ごとに解析します。</li></ol><pre><code>import fs from 'fs';
import readline from 'readline';
import path from 'path';
import { fileURLToPath } from 'url';
import { elasticClient } from './elasticClient.js';

const indexName = 'sample-nba-player-data'; //Replace with your preferred index name

//Since we are using ES modules __dirname and __filename don't exist, so this is a workaround that allows us to use the absolute file path for our sample data.
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const filePath = path.resolve(__dirname, '../data/sample_nba_data.csv');</code></pre><p>これにより、一括取り込み手順で CSV を効率的に読み取って解析できるようになります。</p><p>3. 適切なマッピングを使用してインデックスを作成します。Elasticsearch は<a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dynamic">動的マッピングを</a>使用してフィールド タイプを自動的に推測できますが、ここでは各統計が数値フィールドとして扱われるように明示的に指定します。これらのフィールドは後で集計に使用するため、これは重要です。また、ポイントやリバウンドなどの統計情報には、小数値が含まれるようにするために、タイプ<code>float </code>を使用します。最後に、Elasticsearch が認識されないフィールドを動的にマッピングしないように、マッピング プロパティ<code>dynamic: &#x27;strict&#x27;</code>を追加します。<br/></p><pre><code>// Function to create an index with mappings
async function createIndex() {
    try {
        // Check if the index already exists
        const exists = await elasticClient.indices.exists({ index: indexName });

        if (exists) {
            console.log(`Index "${indexName}" already exists, deleting it now.`);
            await elasticClient.indices.delete({ index: indexName });
            console.log(`Deleted index "${indexName}".`);
        }
        // Create the index with mappings
        const response = await elasticClient.indices.create({
            index: indexName,
            body: {
                mappings: {
                    dynamic: 'strict', // Prevent dynamic mapping
                    properties: {
                        game_id: { type: 'integer' },
                        game_date: { type: 'date' },
                        player_id: { type: 'integer' },
                        player_full_name: { type: 'text' },
                        player_team_id: { type: 'integer' },
                        player_team_name: { type: 'text' },
                        home_team: { type: 'boolean' },
                        opponent_team_id: { type: 'integer' },
                        opponent_team_name: { type: 'text' },
                        points: { type: 'float' },
                        rebounds: { type: 'float' },
                        assists: { type: 'float' },
                        steals: { type: 'float' },
                        blocks: { type: 'float' },
                        fg_percentage: { type: 'float' },
                        minutes_played: { type: 'float' },
                    },
                },
            },
        });

        console.log('Index created:', response);
        return true;
    } catch (error) {
        console.error('Error creating index:', error);
        return false;
    }
}
</code></pre><p>4. CSV データを Elasticsearch インデックスに一括で取り込む機能を追加します。コード ブロック内では、ヘッダー行をスキップします。次に、各行項目をコンマで分割し、ドキュメント オブジェクトにプッシュします。このステップでは、それらをクリーンアップし、適切なタイプであることを確認します。次に、ドキュメントをインデックス情報とともに bulkBody 配列にプッシュします。これは、Elasticsearch への一括取り込みのペイロードとして機能します。</p><pre><code>async function bulkIngestCsv(filePath) {
    const readStream = fs.createReadStream(filePath);
    const rl = readline.createInterface({
        input: readStream,
        crlfDelay: Infinity,
    });

    const bulkBody = [];
    let lineNum = 0;

    //Skip the header line
    let headerLine = true;
    for await (const line of rl) {
        if (headerLine) {
            headerLine = false;
            continue;
        }
        lineNum++;

        // Split the line by comma and remove whitespace
        const [
            game_id,
            game_date,
            player_id,
            player_full_name,
            player_team_id,
            player_team_name,
            home_team,
            opponent_team_id,
            opponent_team_name,
            points,
            rebounds,
            assists,
            steals,
            blocks,
            fg_percentage,
            minutes_played,
        ] = line.split(',');

        // Create a document object
        const document = {
            game_id: parseInt(game_id),
            game_date: game_date.trim(),
            player_id: parseInt(player_id),
            player_full_name: player_full_name.trim(),
            player_team_id: parseInt(player_team_id),
            player_team_name: player_team_name.trim(),
            home_team: home_team.trim() === 'True', // Converts True/False into a boolean
            opponent_team_id: parseInt(opponent_team_id),
            opponent_team_name: opponent_team_name.trim(),
            points: parseFloat(points),
            rebounds: parseFloat(rebounds),
            assists: parseFloat(assists),
            steals: parseFloat(steals),
            blocks: parseFloat(blocks),
            fg_percentage: parseFloat(fg_percentage),
            minutes_played: parseFloat(minutes_played),
        };

        // Prepare the bulk operation format
        bulkBody.push({ index: { _index: indexName } });
        bulkBody.push(document);
    }

    console.log(`Parsed ${lineNum} lines from CSV`);
</code></pre><p>5.次に、 <code>elasticClient.bulk()</code>で Elasticsearch の<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk">Bulk API を</a>使用して、1 回のリクエストで複数のドキュメントを取り込むことができます。以下のエラー処理は、取り込みに失敗したドキュメントの数と、取り込みに成功したドキュメントの数を示すように構成されています。</p><pre><code>try {
        // Perform the bulk request
        const response = await elasticClient.bulk({ body: bulkBody });

        if (response.errors) {
            console.log('Bulk Ingestion had some hiccups:');

            // Count successful vs failed operations
            let successCount = 0;
            let errorCount = 0;
            const errorDetails = [];

            response.items.forEach((item, index) => {
                const operation = item.index || item.create || item.update || item.delete;
                if (operation.error) {
                    errorCount++;
                    errorDetails.push({
                        document: index + 1,
                        error: operation.error,
                    });
                } else {
                    successCount++;
                }
            });

            console.log(`Successfully indexed: ${successCount} documents`);
            console.log(`Failed to index: ${errorCount} documents, here are the details`, errorDetails);

        } else {
            console.log(`Bulk Ingestion fully successful!`);
        }

    } catch (error) {
        console.error('Error performing bulk ingestion:', error);
    }
}
</code></pre><p>6. 以下の<code>main()</code>関数を実行して、 <code>createIndex()</code>関数と<code>bulkIngestCsv()</code>関数を順番に実行します。</p><pre><code>// Run this function
async function main() {
    const result = await createIndex();
    if (!result) {
        console.error('Index setup failed. Aborting.');
        return;
    }

    await bulkIngestCsv(filePath);
    console.log('Bulk ingestion completed!');
}

main();
</code></pre><p>一括取り込みが成功したことを示すコンソール ログが表示された場合は、Elasticsearch インデックスを簡単にチェックして、ドキュメントが実際に正常に取り込まれたかどうかを確認します。</p><h4><strong>ステップ5: Elasticsearchの集計の定義と統合</strong></h4><p>これらは、プレイヤーの統計を相互に比較するために AI エージェントのツールを定義するときに使用される主な関数になります。</p><p>1. <code>backend/lib</code>ディレクトリに移動し、 <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/elasticAggs.js">elasticAggs.js</a>というファイルを作成します。</p><p>2. 特定の対戦相手に対するプレイヤーの過去の平均を計算するには、以下のクエリを追加します。このクエリでは、2 つの条件（1 つは<code>player_id</code>に一致し、もう 1 つは<code>opponent_team_id</code>に一致する）を持つ<code>bool</code><a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/full-text-search/filters">フィルター</a>を使用して、関連するゲームのみを取得します。ドキュメントを返す必要はなく、集計のみを対象とするため、 <code>size:0</code>を設定します。<code>aggs</code>ブロックでは、 <code>points, rebounds, assists, steals, blocks</code>や<code>fg_percentage</code>などのフィールドに対して複数のメトリック<a href="https://www.elastic.co/docs/explore-analyze/query-filter/aggregations">集計を</a>並行して実行し、平均値を計算します。LLM は計算で成功するか失敗するかのどちらかですが、このプロセスは Elasticsearch にオフロードされ、NBA AI アシスタントが正確なデータにアクセスできるようになります。</p><pre><code>export async function getHistoricalAveragesAgainstOpponent(player_id, opponent_team_id) {
    try {
        //Query for Historical Averages
        const historicalQuery = await elasticClient.search({
            index: 'sample-nba-player-data', 
            size: 0,
            query: {
                bool: {
                    must: [
                        {
                            term: {
                                player_id: {
                                    value: player_id,
                                },
                            },
                        },
                        {
                            term: {
                                opponent_team_id: {
                                    value: opponent_team_id,
                                },
                            },
                        },
                    ],
                },
            },
            aggs: {
                avg_points: { avg: { field: 'points' } },
                avg_rebounds: { avg: { field: 'rebounds' } },
                avg_assists: { avg: { field: 'assists' } },
                avg_steals: { avg: { field: 'steals' } },
                avg_blocks: { avg: { field: 'blocks' } },
             avg_fg_percentage: { avg: { field: 'fg_percentage' } },
            },
        });

        return {
            points: historicalQuery.aggregations.avg_points.value || 0,
            rebounds: historicalQuery.aggregations.avg_rebounds.value || 0,
            assists: historicalQuery.aggregations.avg_assists.value || 0,
            steals: historicalQuery.aggregations.avg_steals.value || 0,
            blocks: historicalQuery.aggregations.avg_blocks.value || 0,
            fgPercentage: historicalQuery.aggregations.avg_fg_percentage.value || 0,
        };
    } catch (error) {
        console.error('Query error from getHistoricalAveragesAgainstOpponent function:', error);
        return { error: 'Queries failed in getting historical averages against opponent.' };
    }
}
</code></pre><p>3. 特定の対戦相手に対するプレーヤーのシーズン平均を計算するには、履歴クエリとほぼ同じクエリを使用します。このクエリの唯一の違いは、 <code>bool</code>フィルターに<code>game_date</code>の追加条件があることです。フィールド<code>game_date</code>は、現在の NBA シーズンの範囲内に収まる必要があります。この場合、範囲は<code>2024-10-01</code>から<code>2025-06-30</code>の間になります。以下の追加条件により、後続の集計で今シーズンのゲームのみが分離されることが保証されます。<br/></p><pre><code>        {
                            range: {
                    //Range for this season, change to match current season
                                game_date: {
                                    gte: '2024-10-01',
                                    lte: '2025-06-30',
                                },
                            },
</code></pre><h4><strong>ステップ6: プレーヤー比較ユーティリティ</strong></h4><p>コードをモジュール化して保守しやすい状態に保つために、メタデータ ヘルパー関数と Elasticsearch 集計を統合するユーティリティ ファイルを作成します。これにより、エージェントが使用するメイン ツールが強化されます。詳細は後述します。</p><p>1. <code>backend/lib</code>ディレクトリに新しいファイル<a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/comparePlayers.js">comparePlayers.js</a>を作成します。</p><p>2. 以下の関数を追加して、メタデータ ヘルパーと Elasticsearch 集約ロジックを、エージェントが使用するメイン ツールを強化する単一の関数に統合します。<br/></p><pre><code>import { playersByName } from '../data/playerAndTeamInfo.js';
import { teamsByName } from '../data/playerAndTeamInfo.js';
import { upcomingMatchups } from '../data/playerAndTeamInfo.js';
import { getHistoricalAveragesAgainstOpponent } from './elasticAggs.js';
import { getSeasonAveragesAgainstOpponent } from './elasticAggs.js';

//Simple helper functions to simulate API calls for player and team metadata. These reference the hardcoded values from playerAndTeamInfo.js in the data directory
export function getPlayerInfo(playerFullName) {
    return playersByName[playerFullName];
}

export function getTeamID(teamFullName) {
    return teamsByName[teamFullName];
}

export function getUpcomingMatchups(teamId) {
    return upcomingMatchups[teamId];
}

//Main function used by the 'playerComparisonTool' agent tool
export async function comparePlayersForNextMatchup(player1Name, player2Name) {
    //Get Player Info
    const player1Info = getPlayerInfo(player1Name);
    const player2Info = getPlayerInfo(player2Name);

    //Get upcoming matchups
    const player1NextGame = getUpcomingMatchups(player1Info.team_id)[0];
    const player2NextGame = getUpcomingMatchups(player2Info.team_id)[0];

    //Get season and historical averages against next opponent for player 1
    const player1SeasonAverages = await getSeasonAveragesAgainstOpponent(
        player1Info.player_id,
        player1NextGame.opponent_team_id
    );
    const player1HistoricalAverages = await getHistoricalAveragesAgainstOpponent(
        player1Info.player_id,
        player1NextGame.opponent_team_id
    );

    //Get season and historical averages against next opponent for player 2
    const player2SeasonAverages = await getSeasonAveragesAgainstOpponent(
        player2Info.player_id,
        player2NextGame.opponent_team_id
    );
    const player2HistoricalAverages = await getHistoricalAveragesAgainstOpponent(
        player2Info.player_id,
        player2NextGame.opponent_team_id
    );

    const player1 = {
        name: player1Name,
        playerId: player1Info.player_id,
        teamId: player1Info.team_id,
        nextOpponent: {
            teamId: player1NextGame.opponent_team_id,
            teamName: player1NextGame.opponent_team_name,
            home: player1NextGame.home,
        },
        stats: {
            seasonAverages: player1SeasonAverages,
            historicalAverages: player1HistoricalAverages,
        },
    };

    const player2 = {
        name: player2Name,
        playerId: player2Info.player_id,
        teamId: player2Info.team_id,
        nextOpponent: {
            teamId: player2NextGame.opponent_team_id,
            teamName: player2NextGame.opponent_team_name,
            home: player2NextGame.home,
        },
        stats: {
            seasonAverages: player2SeasonAverages,
            historicalAverages: player2HistoricalAverages,
        },
    };

    return [player1, player2];
}
</code></pre><h4><strong>ステップ7: エージェントの構築</strong></h4><p>フロントエンドとバックエンドのスキャフォールディングを作成し、NBA ゲームデータを取り込み、Elasticsearch への接続を確立したので、すべてのピースをまとめてエージェントを構築し始めることができます。</p><p><strong>エージェントの定義</strong></p><p>1. <code>backend/src/mastra/agents</code>ディレクトリ内の<a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/src/mastra/agents/index.ts">index.ts</a>ファイルに移動し、エージェント定義を追加します。次のようなフィールドを指定できます。</p><ul><li><strong>名前:</strong>フロントエンドで呼び出されたときに参照として使用されるエージェントの名前を指定します。</li><li><strong>指示/システム プロンプト:</strong>システム プロンプトは、対話中に従うべき初期コンテキストとルールを LLM に提供します。これは、ユーザーがチャット ボックスを通じて送信するプロンプトに似ていますが、こちらはユーザー入力の前に表示されます。繰り返しになりますが、これは選択したモデルに応じて変わります。</li><li><strong>モデル:</strong>使用する LLM (Mastra は OpenAI、Anthropic、ローカル モデルなどをサポートしています)。</li><li><strong>ツール:</strong>エージェントが呼び出すことができるツール関数のリスト。</li><li><strong>メモリ:</strong> (オプション) エージェントに会話履歴などを記憶させたい場合。簡単にするために、Mastra は永続メモリをサポートしていますが、永続メモリなしで開始できます。</li></ul><p></p><pre><code>import { openai } from '@ai-sdk/openai';
import { Agent } from '@mastra/core/agent';
import { playerComparisonTool } from '../tools';

export const basketballAgent = new Agent({
    name: 'Basketball Agent',
    instructions: `
      You are a NBA Basketball expert.
      Your primary function is to compare two NBA players and recommend which one is the better fantasy pickup.

      Only compare players from the following list:
      - LeBron James
      - Stephen Curry
      - Jayson Tatum
      - Jaylen Brown
      - Nikola Jokic
      - Luka Doncic
      - Kyrie Irving
      - Anthony Davis
      - Kawhi Leonard
      - Russell Westbrook

      Input Handling Rules:
      - If the user asks about a player that is not on this list, respond with the list of available players for comparison.
      - If the user only inputs one player, ask the user to add another player from the list provided.
      - If the user inputs a player with the wrong spelling or capitalizations, infer from the list of available players provided.
      - IMPORTANT: If the user asks a question or asks you to generate a response about anything outside of basketball or the scope of this project, DO NOT answer and affirm you can only talk about basketball.

      Tool Usage:
      - Extract and standardize player names to match the list exactly.
      - Use the playerComparisonTool, passing both names as strings.
      - The tool will return an object with game information, stats, and analysis.

      Format your response using Markdown syntax. Use:

        Example output format:

       
        #### Next Game Info
        - ***LeBron James** vs Warriors, May 24 (Home)  
        - ***Stephen Curry** vs Lakers, May 24 (Away)


        #### Stats Comparison  
        \`\`\`  
        Stat                  LeBron James (vs Warriors)    Stephen Curry (vs Lakers)  
        --------------------  -----------------------------  ----------------------------  
        Historical Points     28.3                          30.3  
        Historical Assists    6.7                           8.7  
        Season Points         28.8                          23.3  
        Season Assists        6.2                           4.7  
        \`\`\`

        #### Fantasy Recommendation  
        Explain which player is the better fantasy pickup and why.
      
    `,
    model: openai('gpt-4o'),
    tools: { playerComparisonTool },
});
</code></pre><p><strong><br/>ツールの定義</strong></p><ol><li><code>backend/src/mastra/tools</code>ディレクトリ内の<a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/src/mastra/tools/index.ts">index.ts</a>ファイルに移動します。</li><li>次のコマンドを使用して Zod をインストールします。</li></ol><pre><code>npm install zod</code></pre><p>3. ツール定義を追加します。このツールを呼び出すときにエージェントが使用するメイン関数として、 <code>comparePlayers.js</code>ファイル内の関数をインポートすることに注意してください。Mastra の<code>createTool()</code>関数を使用して、 <code>playerComparisonTool</code>を登録します。フィールドには次のものが含まれます。</p><ul><li><code>id</code>: これは、エージェントがツールの機能を理解するのに役立つ自然言語による説明です。</li><li><code>input schema</code>: ツールの入力の形状を定義するために、Mastra は TypeScript スキーマ検証ライブラリである<a href="https://zod.dev/">Zod</a>スキーマを使用します。Zod は、エージェントが正しく構造化された入力を入力したことを確認し、入力構造が一致しない場合はツールが実行されないようにすることで役立ちます。</li><li><code>description</code>: これは、エージェントがいつ電話をかけてツールを使用するかを理解するのに役立つ自然言語による説明です。</li><li><code>execute</code>: ツールが呼び出されたときに実行されるロジック。私たちの場合、インポートしたヘルパー関数を使用してパフォーマンス統計を返します。</li></ul><pre><code>import { comparePlayersForNextMatchup } from '../../../lib/comparePlayers.js'
import { createTool } from "@mastra/core/tools";
import { z } from "zod";

export const playerComparisonTool = createTool({
    id: "Compare two NBA players",
    inputSchema: z.object({
        player1:z.string(),
        player2:z.string()
    }),
    description: "Use this tool to compare two players given in the user prompt.",
    execute: async ({ context: { player1, player2 } }) => {
        return await comparePlayersForNextMatchup(player1, player2);
      },
})</code></pre><p><strong>CORSを処理するミドルウェアの追加</strong></p><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS">CORS を</a>処理するために、Mastra サーバーにミドルウェアを追加します。人生には避けられないことが 3 つあると言われています。死、税金、そして Web 開発者にとっては CORS です。簡単に言うと、クロスオリジン リソース共有は、フロントエンドが別のドメインまたはポートで実行されているバックエンドにリクエストを送信するのをブロックするブラウザのセキュリティ機能です。バックエンドとフロントエンドの両方をローカルホストで実行しているにもかかわらず、それらは異なるポートを使用するため、CORS ポリシーがトリガーされます。バックエンドがフロントエンドからのリクエストを許可するように、 <a href="https://mastra.ai/en/docs/server-db/middleware">Mastra ドキュメント</a>で指定されているミドルウェアを追加する必要があります。</p><p>1. <code>backend/src/mastra</code>ディレクトリ内の<a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/src/mastra/index.ts">index.ts</a>ファイルに移動し、CORS の設定を追加します。</p><ul><li><code>origin: [&#x27;http://localhost:5173&#x27;]</code><ul><li>このアドレス（Vite のデフォルト アドレス）からのリクエストのみを許可します</li></ul></li><li><code>allowMethods: [&quot;GET&quot;, &quot;POST&quot;]</code><ul><li>許可される HTTP メソッド。ほとんどの場合、POST が使用されます。</li></ul></li><li><code>allowHeaders: [&quot;Content-Type&quot;, &quot;Authorization&quot;, &quot;x-mastra-client-type, &quot;x-highlight-request&quot;, &quot;traceparent&quot;],</code><ul><li>これらはリクエストで使用できるカスタムヘッダーを決定します</li></ul></li></ul><p></p><pre><code>import { Mastra } from '@mastra/core/mastra';
import { basketballAgent } from './agents';

console.log('Starting Mastra server...');

export const mastra = new Mastra({
  agents: { basketballAgent },
  server:{
    timeout: 10 * 60 * 1000, // 10 minutes
    cors: {
      origin: ['http://localhost:5173'],
      allowMethods: ["GET", "POST"],
      allowHeaders: [
        "Content-Type",
        "Authorization",
        "x-mastra-client-type",
        "x-highlight-request",
        "traceparent",
      ],
      exposeHeaders: ["Content-Length", "X-Requested-With"],
      credentials: false,
    },
  },

});

console.log('Mastra server configured.'); // Log after server configuration
</code></pre><h4><strong>ステップ8: フロントエンドの統合</strong></h4><p>この React コンポーネントは、 <code>@ai-sdk/react</code>の<a href="https://mastra.ai/en/docs/frameworks/agentic-uis/ai-sdk#using-the-usechat-hook">useChat()</a>フックを使用して Mastra AI エージェントに接続するシンプルなチャット インターフェースを提供します。このフックを使用して、トークンの使用状況やツールの呼び出しを表示したり、会話をレンダリングしたりします。上記のシステム プロンプトでは、エージェントに応答をマークダウンで出力するように要求しているため、 <code>react-markdown</code>を使用して応答を適切にフォーマットします。</p><p></p><p>1.フロントエンド ディレクトリにいる間に、useChat() フックを使用するために @ai-sdk/react パッケージをインストールします。</p><pre><code>npm install @ai-sdk/react</code></pre><p>2. 同じディレクトリで、React Markdown をインストールして、エージェントが生成する応答を適切にフォーマットできるようにします。</p><pre><code>npm install react-markdown</code></pre><p>3. <code>useChat()</code>を実装します。このフックは、フロントエンドと AI エージェントのバックエンド間のやり取りを管理します。メッセージの状態、ユーザー入力、ステータスを処理し、監視の目的でライフサイクル フックを提供します。渡すオプションは次のとおりです。</p><ul><li><code>api:</code> これは、Mastra AI エージェントのエンドポイントを定義します。デフォルトではポート 4111 に設定されており、ストリーミング応答をサポートするルートも追加する必要があります。</li><li><code>onToolCall</code>: これは、エージェントがツールを呼び出すたびに実行されます。エージェントがどのツールを呼び出しているかを追跡するために使用します。</li><li><code>onFinish</code>: エージェントが完全な応答を完了した後に実行されます。ストリーミングを有効にしても、 <code>onFinish</code>各チャンクの後ではなく、完全なメッセージが受信された後に実行されます。ここでは、トークンの使用状況を追跡するためにこれを使用しています。これは、LLM コストを監視して最適化するときに役立ちます。</li></ul><p>4. 最後に、 <code>frontend/components</code>ディレクトリの<a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/frontend/components/ChatUI.jsx">ChatUI.jsx</a>コンポーネントに移動して、会話を行うための UI を作成します。次に、エージェントからの応答を適切にフォーマットするために、応答を<code>ReactMarkdown</code>コンポーネントでラップします。</p><pre><code>import React, { useState } from 'react';
import { useChat } from '@ai-sdk/react';
import ReactMarkdown from 'react-markdown';

export default function ChatUI() {
    const [totalTokenUsage, setTotalTokenUsage] = useState(0);
    const [promptTokenUsage, setPromptTokenUsage] = useState(0);
    const [completionTokenUsage, setCompletionTokenUsage] = useState(0);
    const [toolsCalled, setToolsCalled] = useState([]);

    const { messages, input, handleInputChange, handleSubmit, status } = useChat({
        api: 'http://localhost:4111/api/agents/basketballAgent/stream', //Replace with your own endpoint for your agent
        id: 'my-chat-session',

        //Optional parameter to check agent tool calls
        onToolCall: ({ toolCall }) => {
            setToolsCalled((prev) => [...prev, toolCall.toolName]);
        },

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

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

    return (
        <div>
            <div className="agent-info">
                <h4 className="stats-title">What's My Agent Doing?</h4>

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

                    <div className="usage-stats">
                        <p>Prompt Token Usage: {promptTokenUsage}</p>
                        <p>Completion Token Usage: {completionTokenUsage}</p>
                        <p>Total Token Usage: {totalTokenUsage}</p>
                    </div>
                </div>
            </div>

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

            <form onSubmit={handleSubmit}>
                <input
                    type="text"
                    value={input}
                    onChange={handleInputChange}
                    placeholder="Input two players you want to compare."
                    className="input-box"
                />
                <button type="submit" disabled={status === 'streaming'}>
                    {status === 'streaming' ? 'Thinking...' : 'Send'}
                </button>
            </form>
        </div>
    );
}</code></pre><h4><strong>ステップ9: アプリケーションの実行</strong></h4><p>おめでとうございます！これでアプリケーションを実行する準備が整いました。バックエンドとフロントエンドの両方を起動するには、次の手順に従います。</p><ol><li>ターミナル ウィンドウで、ルート ディレクトリからバックエンド ディレクトリに移動し、Mastra サーバーを起動します。</li></ol><pre><code>cd backend

npm run dev</code></pre><p>2. 別のターミナル ウィンドウで、ルート ディレクトリからフロントエンド ディレクトリに移動し、React アプリを起動します。</p><p></p><pre><code>cd frontend

npm run dev</code></pre><p></p><p>3. ブラウザで次の場所に移動します。</p><p></p><p><a href="http://localhost:5173/">http://localhost:5173</a></p><p></p><p>チャット インターフェースが表示されるはずです。次のサンプルプロンプトを試してみてください。</p><ul><li>「レブロン・ジェームズとステフィン・カリーを比較」</li><li>「ジェイソン・テイタムとルカ・ドンチッチのどちらを選ぶべきでしょうか？」</li></ul><p></p><h3><strong>次はエージェントのさらなるインテリジェント化</strong></h3><p>アシスタントをよりエージェント的にし、推奨事項をより洞察力のあるものにするために、次のイテレーションでいくつかの重要なアップグレードを追加する予定です。</p><p></p><p><strong>NBAニュースのセマンティック検索</strong></p><p>プレーヤーのパフォーマンスに影響を与える要因は数多くありますが、その多くは生の統計には表示されません。負傷報告、ラインナップの変更、さらには試合後の分析などは、ニュース記事でしか見つけることができません。この追加のコンテキストを捉えるために、エージェントが関連する NBA の記事を取得し、その内容を推奨事項に組み込めるよう、セマンティック検索機能を追加します。</p><p></p><p><strong>Elasticsearch MCPサーバーによる動的検索</strong></p><p>MCP (モデル コンテキスト プロトコル) は、エージェントがデータ ソースに接続する方法の標準として急速に普及しつつあります。検索ロジックを Elasticsearch MCP サーバーに移行します。これにより、エージェントは、私たちが提供する定義済みの検索機能に頼るのではなく、動的にクエリを構築できるようになります。これにより、より自然な言語ワークフローを使用できるようになり、すべての検索クエリを手動で記述する必要性が軽減されます。Elasticsearch MCP サーバーとエコシステムの現在の状態の詳細については、<a href="https://www.elastic.co/search-labs/blog/mcp-current-state">こちらを</a>ご覧ください。</p><p></p><p>これらの変更はすでに進行中ですので、お楽しみに!</p><h3><strong>まとめ</strong></h3><p></p><p>このブログでは、JavaScript、Mastra、Elasticsearch を使用して、ファンタジー バスケットボール チームに合わせた推奨事項を提供するエージェント RAG アシスタントを構築しました。取り上げた内容:</p><ul><li><strong>エージェント RAG の基礎</strong>と、AI エージェントの自律性と RAG を効果的に使用するツールを組み合わせることで、より繊細で動的なエージェントを実現できる方法について説明します。</li><li><strong>Elasticsearch</strong>とそのデータ ストレージ機能および強力なネイティブ集約により、それが LLM のナレッジ ベースとして優れたパートナーとなる理由について説明します。</li><li><strong>Mastra</strong>フレームワークと、それが JavaScript エコシステムの開発者にとってこれらのエージェントの構築をどのように簡素化するかについて説明します。</li></ul><p>あなたがバスケットボールの熱狂的なファンであっても、AI エージェントの構築方法を検討している方であっても、あるいは私のようにその両方であっても、このブログが、始めるための基礎を提供できれば幸いです。完全なリポジトリは<a href="https://github.com/jdarmada/nba-ai-assistant-js">GitHub</a>で入手できます。自由にクローンして改良してください。さあ、ファンタジーリーグで優勝しましょう!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[LogstashでのRubyスクリプト]]></title>
            <description><![CDATA[Logstash パイプラインでの高度なデータ変換を実現する Logstash Ruby フィルター プラグインについて学習します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/ruby-scripting-logstash</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/ruby-scripting-logstash</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Dai Sugimori]]></dc:creator>
            <pubDate>Tue, 24 Jun 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b1bcd63c033ccbde102c20ba3085f165f9289a71-1600x1000.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Logstash は、複数のソースからデータを取り込み、変換して、選択した宛先に送信するデータ処理パイプラインです。フィルター プラグインはこのプロセスの鍵であり、データがパイプラインを通過するときにデータに対して特定の操作を実行します。</p><p>Logstash には、データの解析、拡充、変更などの一般的なタスク用のフィルターがいくつか組み込まれています。しかし、場合によっては、これらの標準フィルターが提供できる範囲を超えたカスタム ロジックが必要なシナリオに遭遇することがあります。ここで<a href="https://www.elastic.co/docs/reference/logstash/plugins/plugins-filters-ruby">Ruby フィルター プラグインが</a>役立ちます。</p><p><strong>Ruby フィルター プラグインを使用すると、Logstash パイプライン内でカスタム Ruby コードを直接実行できます。</strong>標準フィルターでは不十分な場合は、Ruby フィルターを使用すると、複雑なデータ変換を処理したり、カスタム ビジネス ロジックを実装したり、外部システムと統合したりできます。</p><p>このブログでは、Ruby フィルターの使い方を、基本的な使い方から高度な使い方まで解説します。</p><h2>Ruby フィルターはいつ使用すればよいですか?</h2><p>Elastic のコンサルティング アーキテクトとして、Logstash は現在最先端のデータ処理エンジンではないにもかかわらず、データ処理パイプラインに Logstash を使用している顧客をよく見かけます。複雑なデータ操作やカスタム ロジックに関しては、標準フィルターの制限に悩まされることがよくあります。このような場合、Ruby フィルターはこれらの課題を克服するのに役立ちます。</p><p>Ruby フィルターは、標準の Logstash フィルターが特定の要件を満たせない場合に役立ちます。一般的な使用例をいくつか紹介します。</p><ul><li><strong>深くネストされたデータ操作</strong>: 複雑な JSON 構造、配列内の配列を変更したり、コンテンツに基づいてデータを動的に再構築したりできます。</li><li><strong>高度な文字列処理</strong>: 非構造化テキストから構造化データを解析して抽出します</li><li><strong>複雑なビジネス ロジックの実装</strong>: 条件付きロジック、ループ、または複雑な計算を必要とするカスタム変換を作成します。</li></ul><h2>基本的な使い方</h2><p>Ruby フィルターがどのように機能するかを理解するために、簡単な例から始めましょう。</p><h3>Rubyフィルターの設定</h3><p>Logstash パイプラインを作成するときは、構成ファイルを<code>/etc/logstash/conf.d</code>ディレクトリに配置する必要があります。あるいは、Logstash を手動で起動するときに<code>-f</code>オプションを使用して構成ファイルへのパスを指定し、パイプラインを簡単に試すことができます。</p><pre><code>$ ./bin/logstash -f /path/to/your_pipeline.conf</code></pre><p>構成ファイルには<code>.conf</code>拡張子が必要です。</p><p>Ruby フィルターを使用するには、Logstash パイプライン構成 (*.conf) ファイルのフィルター セクションで<code>ruby</code>フィルターを定義します。基本的な例は次のとおりです。</p><pre><code>filter {
  ruby {
    code => "
      event.set('new_field', 'Hello from Ruby!')
    "
  }
}</code></pre><p>このインライン Ruby フィルターは、Logstash 構成内で Ruby フィルター インスタンスを定義します。<code>code</code>パラメータは、このフィルタによって処理される各イベントに対して Logstash が実行するインライン Ruby スクリプトを提供します。そのスクリプト内には、イベント自体を表す<code>event</code>変数があります。イベント オブジェクトには、Logstash に送信された元のデータと、Logstash のフィルター ステージ中に作成された追加フィールドが含まれます。これらのフィールドには、 <code>event.get()</code>や<code>event.set()</code>などの Logstash イベント API を介してアクセスできます。このサンプルコードでは、 <code>event.set(&#x27;new_field&#x27;, &#x27;Hello from Ruby!&#x27;)</code> <code>new_field</code>という名前の新しいフィールドを文字列値<code>Hello from Ruby!</code>に設定します。必要に応じて、この<code>code</code>ブロックに他のコードを追加できます。</p><p>この<code>event</code>オブジェクトはキーバリュー型のデータコンテナとして機能しますが、通常の Ruby のハッシュオブジェクトではないことに注意してください。イベント API の詳細については、<a href="https://www.elastic.co/docs/reference/logstash/event-api">この公式ドキュメント</a>をご覧ください。</p><h3>Rubyスクリプトを外部化する</h3><p>単純な変換には、インライン Ruby コードが便利です。ただし、複雑なロジックや再利用可能な関数の場合は、コードを外部の Ruby スクリプトに移動することをお勧めします。これにより保守性が向上し、Logstash パイプラインの構成がクリーンな状態に保たれます。</p><p>まず、Ruby スクリプトを作成し、 <code>my_ruby_script.rb</code>として保存します。スクリプトはイベントを処理する<code>filter</code>メソッドを定義する必要があります。処理中の現在のイベントを表すイベント オブジェクトを引数として受け取ります。<code>filter</code>メソッドは、発行するイベントの配列を返す必要があります。イベントを削除するには、空の配列を返します。</p><p>たとえば、次のスクリプトは<code>message</code>フィールドを読み取り、その長さを計算し、結果を<code>message_length</code>という新しいフィールドに格納します。</p><pre><code>def register(params)
  # This method is called when the plugin is loaded.
  # You can use it to initialize any instance variables or perform setup tasks.
end

def filter(event)
  message = event.get('message')

  if message
    event.set('message_length', message.length)
  end

  return [event]
end</code></pre><p>次に、 <code>path</code>オプションを使用してスクリプトを参照するように Ruby フィルター構成を設定します。これにより、Logstash は外部スクリプトを読み込んで実行します。外部スクリプトを使用する場合は、ファイルが存在し、適切な権限があることを確認してください。</p><pre><code>filter {
  ruby {
    path => "/path/to/my_ruby_script.rb"
  }
}</code></pre><p>これで、各イベントは<code>my_ruby_script.rb</code>の<code>filter</code>メソッドに渡され、処理されるようになります。</p><p>このアプローチにより、複雑なロジックをより効率的に管理できるようになり、Ruby コードのテスト、デバッグ、再利用が容易になります。</p><h2>高度な使用法</h2><p>このセクションでは、Logstash で Ruby フィルターを使用する高度な例をいくつか紹介します。これらの例では、Ruby を使用してデータ変換を実行し、イベントを強化し、カスタム ロジックを実装する方法を示します。</p><h3>ネストされたデータ構造の操作</h3><p>Logstash イベントは、Logstash が処理するコア データ構造です。配列やハッシュなどのネストされたデータ構造を含むさまざまなフィールドを含めることができます。Ruby フィルターを使用すると、これらのネストされた構造を簡単に操作できます。</p><p>Ruby フィルターはハッシュや配列などのネストされたデータ構造を処理できるため、これらの構造内のフィールドを変更したり追加したりできます。これは、JSON のような複雑なデータ形式を扱うときに便利です。</p><pre><code>input {
  generator {
    lines => [
      '{"nested": {"key1": "value1", "key2": "value2"}}'
    ]
    count => 1
    codec => "json"
    ecs_compatibility => "disabled"
  }
}

filter {
  ruby {
    code => "
      nested_data = event.get('nested')

      if nested_data.is_a?(Hash)
        nested_data['key3'] = 'value3'
        event.set('nested', nested_data)
      end
    "
  }
}

output {
  stdout { codec => rubydebug }
}</code></pre><p>この例では、入力データにネストされた JSON オブジェクトが含まれています。Ruby フィルターは、新しいキーと値のペアを追加してネストされたデータを変更します。ネストされたデータに対するこの種の操作は標準の Logstash フィルターでは不可能であるため、Ruby フィルターは複雑なデータ構造に便利なオプションになります。</p><h3>1つのイベントを複数のイベントに分割する</h3><p>Ruby フィルターを使用して、単一のイベントを複数のイベントに分割することもできます。これは、アイテムの配列を含む単一のイベントがあり、アイテムごとに個別のイベントを作成する場合に便利です。</p><p>Elasticsearch の取り込みパイプラインも Beats/Elastic Agent のプロセッサもイベントの分割をサポートしていないことに注意してください。これは Logstash の最も強力な使用例の 1 つです。</p><h4>分割フィルター付き</h4><p><code>split</code>フィルターを使用すると、指定されたフィールドに基づいてイベントを複数のイベントに分割できます。ただし、分割中に追加の変換やロジックを実行する必要がある場合は、分割フィルターと組み合わせて Ruby フィルターを使用できます。</p><p>次の例では、RSS フィードが 1 行の XML テキストとして存在します。複数の<code>&lt;item&gt;</code>要素が含まれています。Ruby フィルターは、XML から<code>&lt;item&gt;</code>要素を抽出し、 <code>items</code>という新しいフィールドに保存するために使用されます。次に、分割フィルターを使用して、 <code>items</code>フィールドに基づいてイベントを複数のイベントに分割します。</p><pre><code>input {
  generator {
    lines => [
      '<rss version="2.0"><channel><title>Sample RSS</title><item><title>Article 1</title><link>http://example.com/1</link><description>Desc 1</description></item><item><title>Article 2</title><link>http://example.com/2</link><description>Desc 2</description></item></channel></rss>'
    ]
    count => 1
    codec => "plain"
    ecs_compatibility => "disabled"
  }
}

filter {
  xml {
    source => "message"
    target => "rss"
    store_xml => true
    force_array => false
  }
  ruby {
    code => "event.set('items', event.get('[rss][channel][item]')) if event.get('[rss][channel][item]')"
  }
  split {
    field => "items"
  }
  ruby {
    code => "
      item = event.get('items')
      event.set('title', item['title']) if item['title']
      event.set('link', item['link']) if item['link']
      event.set('description', item['description']) if item['description']
    "
  }
  mutate {
    remove_field => ["@timestamp", "@version", "sequence", "host", "event", "message", "rss", "items"]
  }
}

output {
  stdout { codec => rubydebug }
}</code></pre><p>出力は次のようになります:</p><pre><code>{
          "title" => "Article 1",
           "link" => "http://example.com/1",
    "description" => "Desc 1"
}
{
          "title" => "Article 2",
           "link" => "http://example.com/2",
    "description" => "Desc 2"
}</code></pre><p>お気づきかもしれませんが、この場合、 <code>ruby</code>フィルターは必須ではありません。<code>split</code>フィルターを使用すると、 <code>items</code>フィールドに基づいてイベントを複数のイベントに分割できます。また、 <code>mutate</code>フィルターを使用すると、不要なフィールドを削除できます。ただし、分割中に追加の変換やロジックを実行する必要がある場合は、Ruby フィルターを使用できます。</p><h4>インラインRubyスクリプトを使用する</h4><p>インライン Ruby スクリプトを使用して、 <code>event.clone</code>メソッドと<code>new_event_block variable</code> ( <code>new_event_block.call(new_event)</code>など) を使用することで、単一のイベントを複数のイベントに分割することもできます。これにより、元のイベントのデータを保持しながら、元のイベントに基づいて新しいイベントを作成できます。</p><p>Ruby フィルターを使用して 1 つのイベントを複数のイベントに分割する方法の例を次に示します。入力と出力は前の例と同じです。</p><pre><code>filter {
  xml {
    source => "message"
    target => "rss"
    store_xml => true
    force_array => false
  }
  ruby {
    code => "
      items = event.get('[rss][channel][item]')
      if items.is_a?(Array)
        items.each do |item|
          new_event = event.clone
          new_event.set('title', item['title'])
          new_event.set('link', item['link'])
          new_event.set('description', item['description'])
          new_event_block.call new_event
        end
        event.cancel
      elsif items.is_a?(Hash)
        event.set('title', items['title'])
        event.set('link', items['link'])
        event.set('description', items['description'])
      end
    "
  }
  mutate {
    remove_field => ["@timestamp", "@version", "sequence", "host", "event", "message", "rss", "items"]
  }
}</code></pre><h4>外部Rubyスクリプトを使用する</h4><p>外部の Ruby スクリプトを使用して、単一のイベントを複数のイベントに分割することもできます。</p><p>設定ファイル:</p><pre><code>filter {
  xml {
    source => "message"
    target => "rss"
    store_xml => true
    force_array => false
  }
  ruby {
    path => "path/to/ruby/split_event.rb"
  }
  mutate {
    remove_field => ["@timestamp", "@version", "sequence", "host", "event", "message", "rss", "items"]
  }
}</code></pre><p>Ruby スクリプトは<code>split_event.rb</code>として外部化する必要があります:</p><pre><code>def filter(event)
  items = event.get('[rss][channel][item]')
  events = []
  if items.is_a?(Array)
    items.each do |item|
      new_event = event.clone
      new_event.set('title', item['title'])
      new_event.set('link', item['link'])
      new_event.set('description', item['description'])
      events << new_event
    end
    return events
  elsif items.is_a?(Hash)
    event.set('title', items['title'])
    event.set('link', items['link'])
    event.set('description', items['description'])
    return [event]
  else
    return []
  end
end</code></pre><p><code>filter</code>メソッドはイベントの配列を返す必要があることに注意してください。受信したイベント オブジェクトを複製して配列に追加することで複数のイベントを返すことも、1 つの要素を持つ配列として 1 つのイベントを返すこともできます。</p><pre><code>return events
# or
# return [event]</code></pre><p>これにより、単一のイベントを複数のイベントに分割できます。</p><h3>外部コマンドを実行し、その出力を解析する</h3><p>Logstash exec 入力プラグインを使用すると、外部コマンドを実行でき、その出力は Logstash のイベントになります。コマンドの出力はイベントの<code>message</code>フィールドに保存されます。</p><p>通常、システム コマンドの出力は人間が判読できますが、Logstash が簡単に解析できる JSON やその他の形式として構造化されていません。これを処理するには、Ruby フィルターを使用して出力を解析し、そこから情報を抽出します。</p><p>以下は、 <code>exec</code>入力プラグインを使用して、Unix 系システムで実行中のすべてのプロセスを一覧表示する<code>ps -ef</code>コマンドを実行する例です。出力は Ruby フィルターによって解析され、各プロセスに関する関連情報が抽出されます。</p><pre><code>input {
  exec {
    command => "ps -ef"
    interval => 60
  }
}

filter {
  ruby {
    code => '
      processes = []
      lines = event.get("message").split("\n")  
      lines.each_with_index do |line, index|
        # Skip header line and empty lines
        next if index == 0 || line.strip.empty?
        entry = nil
        
        # Use regex to match the ps -ef output format more flexibly
        # This pattern accounts for variable spacing and different time formats
        if line =~ /^\s*(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+([\d:]+\.?\d*)\s+(.+)$/
          uid, pid, ppid, c, stime, tty, time, cmd = $1, $2, $3, $4, $5, $6, $7, $8
          
          entry = {
            "UID" => uid,
            "PID" => pid,
            "PPID" => ppid,
            "C" => c,
            "STIME" => stime,
            "TTY" => tty,
            "TIME" => time,
            "CMD" => cmd.strip
          }
        elsif line =~ /^\s*(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(.+)$/
          # Fallback pattern for lines that might not match the exact format
          # Split the remaining part more carefully
          uid, pid, ppid, c, remainder = $1, $2, $3, $4, $5
          
          # Split remainder into STIME, TTY, TIME, CMD
          parts = remainder.strip.split(/\s+/, 4)
          if parts.length >= 4
            stime, tty, time, cmd = parts[0], parts[1], parts[2], parts[3]
            
            entry = {
              "UID" => uid,
              "PID" => pid,
              "PPID" => ppid,
              "C" => c,
              "STIME" => stime,
              "TTY" => tty,
              "TIME" => time,
              "CMD" => cmd
            }
          end
        end
        if entry && entry["UID"] == "0"
          original_line = line.strip
          entry["original_line"] = original_line if original_line.length > 0
          processes.push(entry)
        end
      end
      event.set("processes", processes)
      event.remove("message")
      event.remove("event")
    '
  }
}

output {
  stdout { codec => rubydebug }
}</code></pre><p>この例では、 <code>exec</code>入力プラグインを使用して、60 秒ごとに<code>ps -ef</code>コマンドを実行します。Ruby フィルターは出力を処理し、UID、PID、PPID、CPU 使用率 (C)、開始時刻 (STIME)、TTY、合計 CPU 時間 (TIME)、実行されたコマンド (CMD) などの関連フィールドを抽出します。私の macOS 環境では問題なく動作しますが、システムの<code>ps -ef</code>コマンドの出力形式に合わせて正規表現パターンを調整する必要がある場合があります。</p><h3>組み込みライブラリを使用する</h3><p>Ruby フィルター プラグインを使用すると、さまざまなタスクに非常に役立つ組み込みの Ruby ライブラリを使用できます。たとえば、 <code>json</code>ライブラリを使用して JSON 文字列を解析したり、 <code>date</code>ライブラリを使用して日付を操作したりできます。</p><p>以下は、 <code>json</code>ライブラリを使用して、フィールドに格納されている JSON 文字列を解析する例です。</p><pre><code>require 'json'

def filter(event)
  json_string = event.get('message')
  parsed_json = JSON.parse(json_string)
  event.set('parsed_json', parsed_json)
  return [event]
end</code></pre><p>毎回ライブラリを要求するのを避けるには、Ruby フィルター スクリプトの先頭で<code>require</code>ステートメントを使用できるように Ruby コードを外部化する必要があります。これにより、ライブラリが一度読み込まれ、スクリプトで使用できるようになります。</p><p>環境で使用できるライブラリを確認するには、Ruby フィルターで次のコードを実行して、組み込みライブラリを一覧表示できます。</p><pre><code>Gem.loaded_specs.sort_by { |name, _| name }.each do |name, spec|
  puts "#{name}: #{spec.version}"
end</code></pre><p><strong>注意:</strong>組み込みライブラリは Logstash では正式にサポートされていないため、その動作は変更される可能性があり、将来のバージョンでは利用できなくなる可能性があります。自己責任でご使用ください。</p><h2>まとめ</h2><p>Logstash Ruby フィルターを使用すると、Logstash パイプラインの機能をカスタマイズおよび拡張できます。この記事では、Ruby フィルターの基本的な使用方法を説明し、高度な使用例を示しました。</p><p>Ruby フィルターを活用することで、カスタム ロジックや高度な操作を必要とする複雑なデータ処理タスクを処理できます。ネストされたデータ構造を操作したり、イベントを分割したり、複雑または非構造化テキストを解析して構造化 JSON に変換したりする場合でも、Ruby フィルターは特定の要件を満たす柔軟性を提供します。</p><p>このガイドが、Logstash Ruby フィルターの可能性を最大限に引き出すための知識とインスピレーションを提供できたことを願っています。スクリプト作成を楽しんでください!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GCP GKE AutopilotでElasticsearchをデプロイする方法]]></title>
            <description><![CDATA[部分的に管理されたElasticsearchセットアップ構成のために、GKEオートパイロットとECKを使用してElasticsearchクラスターをGCPにデプロイする方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/eck-gke-autopilot</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/eck-gke-autopilot</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Eduard Martin]]></dc:creator>
            <pubDate>Thu, 19 Jun 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d9c462fe63011356671479ccfedd435eec1ede52-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>この記事では、Autopilotを使用してGoogle Cloud Kubernetes（GKE）にElasticsearchをデプロイする方法を学びます。</p><p>Elasticsearchでは、<a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s">Elastic Cloud on Kubernetes</a>（ECK）を使用します。これはElasticsearch Kubernetes公式オペレーターであり、すべてのElastic StackコンポーネントのKubernetes導入のオーケストレーションを簡素化します。</p><p>ElasticsearchクラスターをさまざまなGCPインフラストラクチャーにデプロイする方法の詳細は、<a href="https://www.elastic.co/search-labs/blog/elasticsearch-gpc-google-compute-engine">Google Cloud Compute</a>と<a href="https://www.elastic.co/search-labs/blog/deploy-elastic-gcp-marketplace">Google Cloud Marketplace</a>の入門記事をご覧ください。</p><h2>Elasticsearchの導入作業</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/56b54d718dcff9af9050873c41fdf738074851da-1428x582.png" style="width: 100%;" /><h3>GKE Autopilotとは何ですか？</h3><p><a href="https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-overview?hl=es-419"><strong>Google Kubernetes Engine（GKE）Autopilot</strong></a>は、Googleがクラスター設定、ノード管理、セキュリティ、スケーリングを処理し、開発者がアプリケーションのデプロイに集中できる、完全に管理されたKubernetesエクスペリエンスを提供します。これにより、チームは組み込まれたベストプラクティスを使用し、コードから本番環境まで数分で移行できます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/bbf6de63fa0a199326352f521cb22654818799f6-1600x958.png" style="width: 100%;" /><h2>Google CloudでECKを使用するタイミングは？</h2><p>Elastic Cloud on Kubernetes（ECK）は、既存のKubernetesインフラを持つ組織が、専用ノードロール、高可用性、自動化などの高度な機能を備えたElasticsearchをデプロイするのに最適です。</p><h2>Google CloudでECKを設定する方法は？</h2><p>1. <a href="https://console.cloud.google.com">Google Cloud Console</a>にログインします。</p><p>2. <strong>右上</strong>の<strong>Cloud Shell</strong>ボタンをクリックし、コンソールにアクセスして、そこから GKEクラスターをデプロイします。あるいは、<a href="https://cloud.google.com/cli">gcloud CLI</a>を使用することもできます。</p><p><strong><em>チュートリアルでは、必ずプロジェクトIDを自分のプロジェクトIDに更新してください。</em></strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/999b03861d4fe44f360ab4c7e2616e1dc10cf182-1558x1248.png" style="width: 100%;" /><p>3. <a href="https://console.cloud.google.com/flows/enableapi?apiid=container.googleapis.com">Google Kubernetes Engine API</a>を有効にします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/03827d3dc0e987c019e7747d33e7c01920047beb-911x246.png" style="width: 100%;" /><p><strong><em>[次へ]</em></strong> をクリックします。</p><p>これで、Kubernetes Engine APIを検索すると、Kubernetes Engine APIが有効になっていることが表示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5cd46f0333086bcb31b80cf9c08a469b449ec0f-640x250.png" style="width: 100%;" /><p>4. Cloud ShellでAutopilotクラスターを作成します。これをautopilot-cluster-1という名前にし、autopilot-testをプロジェクトのIDに置き換えます。</p><pre><code>gcloud beta container --project "autopilot-test-457216" clusters create-auto "autopilot-cluster-1" --region "us-central1" --release-channel "regular" --tier "standard" --enable-ip-access --no-enable-google-cloud-access --network "projects/autopilot-test-457216/global/networks/default" --subnetwork "projects/autopilot-test-457216/regions/us-central1/subnetworks/default" --cluster-ipv4-cidr "/17" --binauthz-evaluation-mode=DISABLED</code></pre><p>5. 準備ができるまで待ちます。作成には10分ほどかかります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/81fbc45ba56d0f16ba42724cb8ae45e60b327dbc-1581x258.png" style="width: 100%;" /><p>クラスターを正しく設定すると確認メッセージが表示されます。</p><p>6. kubectlコマンドラインアクセスを設定します。</p><pre><code>gcloud container clusters get-credentials autopilot-cluster-1 --region us-central1 --project autopilot-test-457216</code></pre><p>次のように表示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b0beba1ee00ce9029f586ee32693fc2aa58c7f65-3442x142.png" style="width: 100%;" /><p><em>autopilot-cluster-1用に生成されたkubeconfigエントリ。</em></p><p>7. <a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s">Elastic Cloud on Kubernetes</a>（ECK）オペレーターをインストールします。</p><pre><code># Install ECK Custom Resource Definitions
kubectl create -f https://download.elastic.co/downloads/eck/2.16.1/crds.yaml

# Install the ECK operator
kubectl apply -f https://download.elastic.co/downloads/eck/2.16.1/operator.yaml</code></pre><p>8. デフォルト値で単一ノードのElasticsearchインスタンスを作成しましょう。</p><p>異なる設定のレシピを確認したい場合は<a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/recipes">こちらのリンク</a>をご覧ください。</p><p><code>storageClass</code>を指定しない場合、ECKはデフォルトで設定されたものを使用します。GKEの場合は<code>standard-rwo</code>で、これは<a href="https://cloud.google.com/kubernetes-engine/docs/how-to/persistent-volumes/gce-pd-csi-driver?cloudshell=true">Compute Engine persistent disk CSI Driver</a>を使用して1GBのボリュームを作成します。</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 9.0.0
  nodeSets:
  - name: default
    count: 1
    config:
      node.store.allow_mmap: false
EOF</code></pre><p>デフォルトの GKE マシンの<code>vm.max_map_count</code>値が低すぎるため、 <code>nmap</code>を無効にしました。本番環境ではこれを無効にせず、 <code>vm.max_map_count</code>の値を増やすことが推奨されます。詳しくは<a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/virtual-memory">こちら</a>をご覧ください。</p><p>9. Kibanaのシングルノードクラスターもデプロイしましょう。Kibanaの場合、デバイスからKibanaにアクセスするために使用できる外部IPを提供するLoadBalancerを追加します。</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: quickstart
spec:
  version: 9.0.0
  http:
    service:
      metadata:
        annotations:
          cloud.google.com/l4-rbs: "enabled"
      spec:
        type: LoadBalancer
  count: 1
  elasticsearchRef:
    name: quickstart
EOF</code></pre><p>注釈に注意してください。 </p><p><code>cloud.google.com/l4-rbs: &quot;enabled&quot;</code></p><p><strong><em>これは、Autopilotにパブリック向けのLoadBalancerを提供するように指示するため、非常に重要です。設定しない場合、LoadBalancerは内部になります。</em></strong></p><p>10. ポッドが動作していることを確認します。</p><pre><code>kubectl get pods</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/99da1270581a137683770efdb9c6e1577ec9fc01-3150x442.png" style="width: 100%;" /><p>11. また、<code>run kubectl get elasticsearch</code>と<code>kubectl get kibana</code>することで、Elasticsearchのバージョン、ノード、健全性などのより具体的な統計情報を取得することもできます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/86234f307970fd5f78b8acd41496e8cc89ff82d3-3414x326.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/29160286ccc88928734c8ea11b1923db8e85d49d-3142x318.png" style="width: 100%;" /><p>12. サービスにアクセスします。</p><pre><code>kubectl get svc</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ed1a07314b84a99b4aa1fec3db4b9badeb9587ee-3446x610.png" style="width: 100%;" /><p>これにより、EXTERNAL-IPのKibanaの外部URLが表示されます。LoadBalancerの提供には数分かかる場合があります。<strong><em>EXTERNAL-IPの値をコピーします。</em></strong></p><p>13 「elastic」ユーザーのElasticsearchパスワードを取得します。</p><pre><code>kubectl get secret quickstart-es-elastic-user -o=jsonpath='{.data.elastic}' | base64 --decode</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ab5054219216ebc15fc0d96e27605aaf13b720c6-3448x210.png" style="width: 100%;" /><p>14. ブラウザから<strong>Kibana にアクセス</strong>します。</p><ul><li>URL: https://&lt;EXTERNAL_IP&gt;:5601</li><li>ユーザー名:elastic</li><li>パスワード:28Pao50lr2GpyguX470L2uj5（前のステップから）</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/47cbe88dc14db64db3a256f3f7504cc86a843475-463x503.png" style="width: 100%;" /><p>15. ブラウザからアクセスすると、ウェルカム画面が表示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a75c151c0144b7efe2b730698c0ed0156fa9b16a-1600x1005.png" style="width: 100%;" /><p>ノードの変更やサイズ変更など、Elasticsearchクラスターの仕様を変更したい場合は、新しい設定でymlマニフェストを再度適用できます。</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 9.0.0
  nodeSets:
    - name: default
      count: 2
      config:
        node.store.allow_mmap: false
      podTemplate:
        spec:
          containers:
            - name: elasticsearch
              resources:
                requests:
                  memory: 1.5Gi
                  cpu: 2
                limits:
                  memory: 1.5Gi
                  cpu: 2
EOF</code></pre><p>この例では、ノードをもう1つ追加して、RAMとCPUを変更します。ご覧のとおり、 <code>kubectl get elasticsearch</code>には2つのノードが表示されています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/48b475622cc48890bff8105d151f2cbde28d7021-3418x298.png" style="width: 100%;" /><p>Kibanaにも同じことが当てはまります。</p><pre><code>cat <<EOF | kubectl apply -f -
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: quickstart
spec:
  version: 9.0.0
  http:
    service:
      metadata:
        annotations:
          cloud.google.com/l4-rbs: "enabled"
      spec:
        type: LoadBalancer
  count: 1
  elasticsearchRef:
    name: quickstart
  podTemplate:
    spec:
      containers:
        - name: kibana
          env:
            - name: NODE_OPTIONS
              value: "--max-old-space-size=1024"
          resources:
            requests:
              memory: 0.5Gi
              cpu: 0.5
            limits:
              memory: 1Gi
              cpu: 1
EOF</code></pre><p>コンテナのCPU/RAMと<a href="https://nodejs.org/">Node.js</a>のメモリ使用量（<a href="https://nodejs.org/api/cli.html#--max-old-space-sizesize-in-mib">max-old-space-size</a>）を調整できます。</p><p><a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/volume-claim-templates">既存のボリュームクレームを縮小することはできない</a>ことに留意してください。アップデートを適用した後、オペレーターは最小限の中断時間で変更を加えます。</p><p>不要なコストを避けるために、テストが完了したらクラスターを忘れずに削除してください。</p><pre><code>gcloud container clusters delete autopilot-cluster-1</code></pre><h2>今後の見通し</h2><p>KubernetesとGoogle Kubernetes Engineについて詳しく知りたい場合は、次の記事をご覧ください。</p><ul><li><a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s">Elastic Cloud on Kubernetes | Elastic Docs</a></li><li><a href="https://cloud.google.com/blog/products/containers-kubernetes/introducing-gke-autopilot">GKE Autopilotのご紹介 | Google Cloudブログ</a></li><li><a href="https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-overview">Autopilotの概要 | Google Kubernetes Engine（GKE）</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Azure LLM Functions を Elasticsearch と併用してよりスマートなクエリ エクスペリエンスを実現する]]></title>
            <description><![CDATA[Azure Gen AI LLM 関数と Elasticsearch を使用して柔軟なハイブリッド検索結果を提供する不動産検索アプリの例を調べます。GitHub Codespaces でサンプル アプリを構成して実行する方法を段階的に説明します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/azure-llm-functions-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/azure-llm-functions-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jonathan Simon]]></dc:creator>
            <pubDate>Fri, 13 Jun 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1431b90c7e00de06574c1e33c44a2e89296c824e-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>精度。重要なときは、それは非常に重要なのです。何か特定のものを検索する場合、精度は非常に重要です。ただし、クエリが正確すぎると結果が返されない場合もあるため、クエリの範囲を広げて関連する可能性のある追加のデータを見つける柔軟性があると便利です。</p><p>このブログ記事では、Elasticsearch と Azure Open AI を使用して、非常に具体的な不動産物件を検索するときに正確な結果を見つける方法を示すサンプル アプリを作成する方法について説明します。同時に、特定の一致が見つからない場合でも関連性の高い結果を提供します。検索テンプレートとともに Elasticsearch インデックスを作成するために必要なすべての手順を説明します。次に、Azure OpenAI を使用してユーザー クエリを取得し、それを Elasticsearch 検索テンプレート クエリに変換して、驚くほどカスタマイズされた結果を生成できるアプリを作成するプロセス全体を説明します。</p><p>以下は、サンプルの不動産検索アプリを作成するために使用するすべてのリソースのリストです。</p><ul><li>Elasticsearch インデックスと検索テンプレート</li><li>Azure OpenAI</li><li>Azure マップ API</li><li><a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb">コードスペース Jupyter ノートブック</a></li><li>セマンティックカーネル</li><li>Blazor フロントエンドを使用した C# アプリ</li></ul><h2>スマートクエリワークフロー</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0c4a7c835e06c514f158c00ab1055a7ba719a35f-1600x765.png" style="width: 100%;" /><p>このワークフローは、LLM、LLM ツール、および検索を組み合わせて、自然言語クエリを構造化された関連性の高い検索結果に変換します。</p><ul><li><strong>LLM (大規模言語モデル)</strong> - 複雑なユーザークエリを解釈し、ツールを調整して検索意図を抽出し、コンテキストを強化します。</li><li><strong>LLM ツール</strong>- 各 LLM ツールは、この投稿用に作成した C# プログラムです。ツールは3つあります。<ul><li><em>パラメータ抽出ツール</em>: クエリから寝室、バスルーム、機能、価格などの主要な属性を取得します。</li><li><em>ジオコード ツール</em>: 空間フィルタリングのために場所の名前を緯度/経度に変換します。</li><li><em>検索ツール</em>: Elasticsearch 検索テンプレートにクエリパラメータを入力し、検索を実行します。<strong>ハイブリッド検索</strong>- 組み込みの ML 推論を使用してハイブリッド検索 (フルテキスト + 高密度ベクトル) を実行します。この階層化されたアプローチにより、エンドユーザーにとってよりスマートでコンテキストを意識したクエリ エクスペリエンスが保証されます。</li></ul></li></ul><h2>アプリケーションアーキテクチャ</h2><p>以下はサンプル アプリのシステム アーキテクチャ図です。Elastic Cloud とやり取りするために、Codespaces Jupyter Notebook を使用します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2880afee184cd9270c0eb4310e51418e2339784d-936x452.png" style="width: 100%;" /><h2>要件</h2><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><h2>Codespaces でサンプル アプリのリポジトリをクローンする</h2><p>まず、サンプル アプリケーションのコードを複製します。これは、アプリケーションのクローンを作成して実行する方法を提供する<a href="https://github.com/codespaces/">GitHub Codespaces</a>で実行できます。<strong>「新しいコードスペース」をクリックします。</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b89cbec491659b6c8a0bb9551ed2629f7a37f9fd-1600x427.png" style="width: 100%;" /><p>次に、<strong> リポジトリ</strong> ドロップダウンでリポジトリ<a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo"> jwilliams-elastic/msbuild-intelligent-query-demo</a><strong> を選択し、 コードスペースの作成を</strong> クリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/43ea377554527af9578400f16cd2342bf8fff3a2-1600x1049.png" style="width: 100%;" /><h2>.env を作成するファイル</h2><p>Elastic Cloud にアクセスして操作するには、Python Jupyter Notebook を使用します。これは、構成ファイルに保存されている構成値を使用して行われます。ノートブックの設定ファイルの名前は<strong><em>.env</em></strong>です。今すぐ作成してみましょう。</p><ol><li>GitHub Codespacesで、 <strong>「新規ファイル」</strong>ボタンをクリックし、 <strong><em>.env</em></strong>という名前のファイルを追加します。</li><li>新しく作成した<strong><em>.env</em></strong>に次の内容を追加します。ファイル</li></ol><pre><code>ELASTIC_URL=
ELASTIC_API_KEY=</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/84d4f327948858ba61db0001dd8cf780d42fe0a7-1600x875.gif" style="width: 100%;" /><p>ご覧のとおり、 <strong>ELASTIC_URL</strong>と<strong>ELASTIC_API_KEYという2つの値が欠落しており、</strong> <em>.env</em>に追加する必要があります。ファイル。サンプル アプリの検索機能を強化するバックエンドとして機能する Elasticsearch サーバーレス プロジェクトを作成して、これらを今すぐ取得しましょう。</p><h2>Elastic Serverlessプロジェクトを作成する</h2><ol><li><a href="http://cloud.elastic.co">cloud.elastic.co</a>にアクセスし、 <strong>「Create New Serverless project」</strong>をクリックします。</li><li><strong>Elasticsearch</strong> ソリューションの<strong> 「次へ」</strong> をクリックします</li><li><strong>ベクター用に最適化を</strong>選択</li><li><strong>クラウドプロバイダーをAzure</strong>に設定する</li><li><strong>サーバーレスプロジェクトの作成を</strong>クリック</li><li>メインナビゲーションメニューの<strong>「はじめ</strong>に」をクリックし、下にスクロールして<strong>接続の詳細</strong>をコピーします。</li><li><strong>コピー</strong> ボタンをクリックして、<strong> 接続の詳細</strong> から<strong> Elasticsearchエンドポイント をコピーします。</strong></li><li><strong><em>.env</em></strong>を更新する<strong>ELASTIC_URL</strong>をコピーした<strong>Elasticsearch エンドポイント</strong>に設定するファイル</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/77a35e55446d396066b68cfd132d1543a07b81cc-1600x875.gif" style="width: 100%;" /><h2>Elastic APIキーを作成する</h2><ol><li>Elasticsearchの<strong> Getting Started</strong> ページを開き、<strong> APIキーの追加</strong> セクションで<strong> 新規を クリックします。</strong></li><li>キー<strong>名</strong>を入力してください</li><li><strong>APIキーの作成を</strong>クリック</li><li>「コピー」ボタンをクリックしてAPIキーの値をコピーします</li><li><strong>Codespacesに戻ると、</strong> <strong><em>.env</em></strong>がある。<strong> </strong>ファイルを開いて編集し、コピーした値を貼り付けて<strong>ELASTIC_API_KEY</strong>を設定します。</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/20168cba493d8e2c0d9ae7704eb0ae707df58e4c-1600x875.gif" style="width: 100%;" /><h2>Codespacesノートブックを開き、ライブラリの依存関係をインストールする</h2><p>ファイル エクスプローラーで<strong><em>VectorDBSetup.ipynb</em></strong>ファイルを選択し、ノートブックを開きます。ノートブックが読み込まれたら、<a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L40-L52"><strong> 「ライブラリのインストール」</strong> というタイトルのノートブックのセクション を見つけます</a><strong> 。</strong>セクションの再生ボタンをクリックします。</p><p>GitHub Codespaces でノートブックを初めて実行する場合は、Codespaces カーネルを選択して Python 環境を構成するように求められます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2272f70615dfb9dcbeb91f39b6dd5076213e24a5-1600x875.gif" style="width: 100%;" /><h2>Codespaces Notebook を使用してインポートを定義し、環境変数をロードする</h2><p>ノートブックの次のセクション<a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L58-L104">「<strong>インポートの定義と環境変数の読み込み</strong></a>」に進みます。セクションの再生ボタンをクリックします。</p><p>このコードは、ノートブックで使用されるPythonライブラリをインポートし、 <em>.env</em>から環境変数を読み込みます。以前作成したもの。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/aa9f9198ff452ac0c4ce33b00f253731dbee22c5-1600x875.gif" style="width: 100%;" /><h2>Codespaces Notebook を使用して Elastic ML 推論エンドポイントを作成する</h2><p>次の<a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L112-L157"><strong> 「ML 推論エンドポイントの作成」</strong> というノートブックのセクション</a> に進みます。セクションの再生ボタンをクリックします。</p><p>これにより、Elasticsearch プロジェクトに新しい ML 推論エンドポイントが作成され、データからテキスト埋め込みを生成するために使用します。テキスト埋め込みは、セマンティック検索を強化するために Elasticsearch に保存されるテキストのベクトル表現です。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/97c1afab3e64027ee5ae77f377d56ba406ae1765-1600x875.gif" style="width: 100%;" /><h2>Codespaces Notebook を使用して Elasticsearch インデックスを作成する</h2><p>次の<a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L165-L224"><strong> 「Elasticsearch インデックスの作成」</strong> というノートブックのセクション</a> に進みます。セクションの再生ボタンをクリックします。</p><p>これにより、サンプル データと、ML 推論エンドポイントを介して生成された関連するベクトル データを保存する Elasticsearch インデックスが作成されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/23a66283ee41239e24fb8455c3cd95641982ca6b-1600x875.gif" style="width: 100%;" /><h2>Codespaces Notebook を使用して Elasticsearch 検索テンプレートを作成する</h2><p>次の<a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L232-L384"> <strong>「検索テンプレート」 というノートブックのセクション</strong></a> に進みます。セクションの再生ボタンをクリックします。</p><p>これにより、<a href="https://www.elastic.co/jp/docs/solutions/search/search-templates">検索テンプレート</a>が作成されます。これは、ユーザーの検索クエリから解析された単語が入力さたテンプレートとしてサンプル アプリで使用されるものです。これにより、Elasticsearch インデックス内のデータをクエリする際の詳細度を設定および制御できるようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4c001a3e4d1cca4cfb5c043fea92c7ccaf9cb64a-1600x875.gif" style="width: 100%;" /><h2>Codespaces Notebook を使用して Elasticsearch インデックスにデータを取り込む</h2><p>ノートブックの次のセクション<a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L392-L450">「</a> <a href="https://github.com/jwilliams-elastic/msbuild-intelligent-query-demo/blob/main/VectorDBSetup.ipynb?short_path=17c25d8#L392-L450"><strong>Ingest property data」</strong></a>に進みます。セクション実行ボタンをクリックします。</p><p>このコード セクションを実行すると、 <em>properties.jsonl</em>ファイルに含まれるサンプル データが一括ロードされます。数分後、プロセスが正常に完了したことを示す確認が表示されます。Elastic Cloud の<strong>インデックス管理</strong>セクションに移動すると、インデックスに予想されるレコードが含まれていることを確認できます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e9698c798541ccfc08143a939846597028e3c566-1600x875.gif" style="width: 100%;" /><h2>C# アプリを構成するための appsetting.json を作成する</h2><p>Elasticsearch インデックスが作成され、データが取り込まれたので、Elastic および Azure Cloud で動作するようにサンプル アプリを構成する準備が整いました。C# サンプル アプリは、 <em>appsettings.json</em>という名前のファイルを使用して、API キーなどのアクセス情報を保存および読み込みます。ここで、Codespaces のエディターを使用して<em>appsettings.json</em>ファイルを作成します。</p><p>1.<strong> HomeFinderApp</strong> フォルダに<em> appsettings.json を作成します。</em></p><p>2. 次のコードを<em>appsettings.json</em>ファイルに貼り付けます。</p><pre><code>{
 "ElasticSettings": {
   "Url": "",
   "ApiKey": "",
   "IndexName": "properties",
   "TemplateId": "properties-search-template"
 },
 "AzureOpenAISettings": {
   "Endpoint": "",
   "ApiKey": "",
   "DeploymentName": "gpt-4o"
 },
 "AzureMapsSettings": {
   "Url": "https://atlas.microsoft.com/geocode",
   "ApiKey": ""
 },
 "Logging": {
   "LogLevel": {
 	"Default": "Information",
 	"Microsoft.AspNetCore": "Warning"
   }
 },
 "AllowedHosts": "*"
}
</code></pre><p>3.<strong> ElasticSettings セクションの Url</strong> と<strong> ApiKey</strong> の 値を見つけます。<em>.env</em>で設定した値と同じ値に設定します前の手順でファイルを作成します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/00dad25bacdea2adcbd1e6eca7658867a49b0d8c-1600x875.gif" style="width: 100%;" /><h2>Azure OpenAI サービスを作成する</h2><p>サンプル アプリでは、Azure OpenAI を使用してアプリ ユーザーのクエリを解析し、検索テンプレートを入力して Elasticsearch にリクエストを送信し、ユーザーが検索している内容を柔軟に伝えようとします。</p><ol><li>新しいブラウザー タブを開き、Azure ポータルの<a href="https://portal.azure.com/#blade/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/OpenAI">AI Foundry | Azure OpenAI</a>に移動します。+<strong>作成を</strong>クリック</li><li>作成フォームで、<strong>リソース グループ</strong>を選択します。</li><li><strong>名前</strong>を入力してください</li><li><strong>価格帯</strong>を選択する</li><li><strong>次へを</strong>クリック</li><li><strong>ネットワーク</strong>タブで<strong>次へを</strong>クリックします</li><li><strong>「タグ」</strong>タブで<strong>「次へ」</strong>をクリックします。</li><li><strong>「確認と送信」</strong>タブで、 <strong>「作成」を</strong>クリックします。</li><li>作成が完了したら、 <strong>「リソースに移動」を</strong>クリックします。</li><li>左側のナビゲーションメニューから<strong>キーとエンドポイント</strong>を選択します</li><li><strong>エンドポイント</strong>をコピーし、Codespaces エディターが開いているブラウザー タブで作成した<em>appsettings.json</em>ファイルに貼り付けます。</li><li>次に、Azure OpenAI<strong>キーとエンドポイント ページ</strong>を含むブラウザー タブに戻ります。<strong>Key 1</strong>のコピー ボタンをクリックし、コピーした値を、Codespaces エディターが開いているブラウザー タブの<em>appsettings.json</em>ファイルに貼り付けます。</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6d49197302d110410dca0a53b6ae90237cf2dfd6-1600x875.gif" style="width: 100%;" /><h2>Azure Open AI サービスに gpt-4o モデルのデプロイメントを追加する</h2><p>素晴らしい。Azure OpenAI サービスが実行できるようになりました。ただし、サンプル アプリに必要な LLM 機能を提供するには、まだモデルのデプロイが必要です。選択できるモデルは多数あります。作成した <em>appsettings.json ファイルですでに指定されているので 、gpt-4o</em> をデプロイしましょう。</p><p></p><ol><li><a href="https://ai.azure.com/resource/playground">Azure AI Foundry</a>にアクセスし、 <strong>「デプロイの作成」を</strong>クリックします。</li><li><em>gpt-4o</em>を検索し、結果から選択します</li><li><strong>確認</strong>をクリックして選択します</li><li><strong>「デプロイ」</strong>をクリックしてモデルをデプロイします</li></ol><p><em>gpt-4o</em> モデルを正常にデプロイしたら、左側のナビゲーション<strong> </strong>メニューから[デプロイメント] を選択し、<strong><em> gpt-4o</em></strong> デプロイメントが[成功] の<strong> 状態</strong> でリストされていることを確認できます。<br/></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1b817ab67c05634e9c72777593b4d1a2c6c28191-1600x875.gif" style="width: 100%;" /><h2>Azure Maps アカウントを作成する</h2><p>私たちは、サンプル アプリのユーザーが特定のエリアの不動産物件を検索できるようにしたいと考えていますが、あまり具体的に検索する必要はありません。地元のファーマーズ マーケットの近くの物件を検索したい場合、Azure Maps は OpenAI LLM がマーケットの緯度と経度の座標を取得するために使用できるサービスです。その後、座標は、特定の場所と地理的距離を考慮したユーザークエリのために Elasticsearch に送信される検索テンプレートベースのリクエストに含めることができます。</p><ol><li><strong></strong><a href="https://portal.azure.com/#browse/Microsoft.Maps%2Faccounts">Azure Mapsアカウント で 作成を クリックします</a></li><li><strong>リソースグループ</strong>を選択</li><li><strong>名前</strong>を入力してください</li><li>ライセンスとプライバシーに関する声明に同意する</li><li><strong>「確認して作成」</strong>をクリック</li><li><strong>作成を</strong>クリック</li><li>アカウントの作成が完了したら、 <strong>「リソースに移動」を</strong>クリックします。</li><li>左側のナビゲーションメニューで<strong>「認証」を</strong>クリックします。</li><li><strong>主キーの</strong> 値をコピーし、Codespaces エディターを含むブラウザー タブに戻って、<em> appsettings.json</em> ファイルの<strong> AzureMapsSettings</strong> セクションの<strong> ApiKey</strong> の値として貼り付けます。</li></ol><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/90fadd48e366682e2bad91e32988f93c6354e126-1600x875.gif" style="width: 100%;" /><h2>サンプルアプリを試してみる</h2><p>さて、ここからが楽しい部分です。サンプルアプリを実行してみましょう。アプリを動かすために必要な Elastic Cloud および Azure Cloud リソースとともに、すべての構成の詳細が設定されました。</p><p>1. Codespaces エディターでターミナル ウィンドウを開きます。</p><p>2. 次のコマンドを使用して、アクティブ ディレクトリをサンプル アプリ フォルダーに変更します。<br/></p><pre><code>cd HomeFinderApp</code></pre><p>3. 次の<em>dotnet</em>コマンドを使用してアプリを実行します。</p><pre><code>dotnet run</code></pre><p>4. <strong>「ブラウザで開く</strong>」ボタンが表示されたらクリックします。</p><p>5. デフォルトの検索をテストしてから、独自のカスタム検索をいくつか試します。検索結果を生成するためにバックエンドで実行される内容の詳細を確認するには、「ツールの呼び出し」の横にある<strong>「表示」</strong>リンクをクリックします<strong>。</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/821fe7b9446de5ed646d938cc9484a7ddad21030-1600x875.gif" style="width: 100%;" /><p><strong>ボーナス:</strong> GPT-4o を実際にテストしたい場合は、次の検索を試してください:<em>フロリダ州ディズニー ワールドの近くで、ベッドルーム 30 室以上、バスルーム 20 室以上、プールとガレージがあり、ビーチに近い物件を 20 万ドル未満で探しています。</em>このクエリは、複数の検索ツールの呼び出し後に結果を返します。</p><h2>Elasticは検索AIのソリューションです</h2><p>実行中のアプリは、検索テンプレートを介して Elasticsearch を基礎データ ソースとして使用する Gen AI LLM ガイド付き検索の例です。自由にサンプル アプリを試してカスタマイズし、正確かつ柔軟な検索エクスペリエンスを作成して、ユーザーが探しているものを見つけられるようにしてください。</p><p>読んでいただきありがとうございます。<a href="https://cloud.elastic.co/registration">Elastic Cloud</a>をお試しください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[MCP（モデルコンテキストプロトコル）の現状]]></title>
            <description><![CDATA[MCP、プロジェクトの更新、機能、セキュリティ上の課題、新しいユースケース、Elastic の Elasticsearch MCP サーバーの操作方法について学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/mcp-current-state</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/mcp-current-state</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[JD Armada]]></dc:creator>
            <pubDate>Thu, 12 Jun 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/02b8c8672ffa129e0ed91a92d6cab612a01d27f2-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>最近、サンフランシスコで開催され<a href="https://mcpdevsummit.ai/">た MCP 開発者サミット</a>に出席しましたが、モデル コンテキスト プロトコル (MCP) が急速に AI エージェントとコンテキストリッチな AI アプリケーションの基礎となる構成要素になりつつあることは明らかでした。Elastic では、 <a href="https://www.elastic.co/jp/elasticsearch/agent-builder">Agent Builder</a>から MCP サーバーを直接公開することでこの方向に傾き、Elasticsearch をあらゆる MCP 互換エージェントにとって第一級のコンテキストおよびツール プロバイダーにしています。この記事では、イベントからの主な最新情報、新しいユースケース、MCP の今後の展望、Agent Builder を使用して MCP 経由でエージェントが Elasticsearch を利用できるようにする方法について説明します。</p><h2>モデルコンテキストプロトコル (MCP) とは何ですか?</h2><p>ご存じない方のために説明すると、<a href="https://modelcontextprotocol.io/introduction">モデル コンテキスト プロトコルは</a>、AI モデルをさまざまなデータ ソースやツールに接続するための構造化された双方向の方法を提供し、より関連性の高い情報に基づいた応答を生成できるようにするオープン スタンダードです。一般的に「 <a href="https://modelcontextprotocol.io/introduction">AI アプリケーション用の USB-C ポート</a>」と呼ばれています。</p><p>双方向の性質を強調したアーキテクチャ図を以下に示します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5eba1e59514eb58a5220bb92bb49e6328ee83cd7-674x466.png" style="width: 100%;" /><p>これは AI 実践者にとって大きな変化です。AI アプリケーションを拡張する際の主な課題の 1 つは、新しいデータ ソースごとにカスタム統合を構築する必要があることです。MCP は、モデルにコンテキストを管理および提供するための持続可能で再利用可能なアーキテクチャを提供します。モデルやサーバーに依存せず、完全にオープンソースです。</p><p>MCP は、アプリケーション間の統合を標準化することを目指す一連の API 仕様の最新版です。これまで、RESTful サービスには OpenAPI、データ クエリには GraphQL、マイクロサービス通信には gRPC を使用していました。MCP は、これらの古い仕様の構造化された厳密さを共有するだけでなく、それを生成 AI 設定に取り入れることで、カスタム コネクタなしでエージェントをさまざまなシステムに簡単に接続できるようになります。多くの点で、MCP は HTTP が Web に対して行ったことと同じことを AI エージェントに対して行うことを目指しています。HTTP がブラウザと Web サイト間の通信を標準化したのと同様に、MCP は AI エージェントが周囲のデータの世界と対話する方法を標準化することを目指しています。</p><h2>MCPと他のエージェントプロトコルの比較</h2><p>エージェント プロトコルの状況は急速に拡大しており、エージェントの相互作用方法を定義するために 12 を超える新しい標準が競合しています。LlamaIndex の<a href="https://x.com/seldo">Laurie Voss</a>氏は、ほとんどのプロトコルを 2 つのタイプに分類できると説明しています。エージェント同士の対話に重点を置くエージェント間プロトコルと、構造化されたコンテキストを LLM に提供することに重点を置く MCP などのコンテキスト指向プロトコルです。</p><p>Google の<a href="https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability/">A2A</a> (Agent to Agent)、Cisco と IBM の<a href="https://agentcommunicationprotocol.dev/introduction/welcome">ACP</a> (Agent Communication Protocol)、 <a href="https://agoraprotocol.org/">Agora</a>などの他の一般的なプロトコルは、エージェント間のネゴシエーション、連合の構築、さらには分散型 ID システムを可能にすることを目的としています。MCP は、エージェントがツールやデータにアクセスする方法に焦点を当てており、必ずしもエージェント同士が通信する方法に焦点を当てているわけではないため、もう少し実用的なアプローチを採用しています (ただし、MCP は将来的にさまざまな方法でそれを可能にすることもできます)。</p><p>現在、MCP が他と一線を画しているのは、その牽引力と勢いです。フロントエンド フレームワークの初期の React と同様に、MCP はニッチな問題から始まり、現在では実際に最も採用され、拡張可能なエージェント プロトコルの 1 つとなっています。</p><h2>サミットのまとめ: MCP の優先事項の進化</h2><p>サミットには、Anthropic、Okta、OpenAI、AWS、GitHub などの貢献者による講演者が登壇しました。講演では、コアプロトコルの強化から実際の実装まで幅広い話題が取り上げられ、短期的および長期的な優先事項が概説されました。これらの講演は、初期の実験や単純なツール呼び出しから、MCP を基盤として使用した信頼性が高く、スケーラブルでモジュール化された AI システムの構築への移行を反映していました。</p><p>何人かの講演者は、MCP が単なるプロトコル配管にとどまらず、AI ネイティブ Web の基盤となる将来を示唆しました。JavaScript によってユーザーが Web ページをクリックして操作できるのと同じように、MCP によってエージェントが私たちに代わって同じアクションを実行できるようになります。たとえば、電子商取引では、ユーザーが買い物をするために手動で Web サイトに移動するのではなく、エージェントにログインして特定の製品を見つけ、カートに追加してチェックアウトするように指示するだけで済みます。</p><p>これは単なる憶測や誇大宣伝ではありません。PayPal はサミットで、まさにこのエージェントによるコマース体験を可能にする新しいエージェント ツールキットと MCP サーバーを披露しました。MCP はツールやデータ ソースへの安全で信頼性の高いアクセスを提供するため、エージェントは Web を読み取るだけでなく、それに基づいて行動できるようになります。現在、MCP はすでに大きな勢いを持つ強力な標準であり、将来的には Web 全体で AI を活用したユーザー インタラクションの標準になる可能性があります。</p><h2>MCPプロジェクトの最新情報: トランスポート、抽出、構造化ツール</h2><p>MCP のコア貢献者である<a href="https://x.com/JeromeSwannack">Jerome Swannack 氏</a>が、過去 6 か月間のプロトコル仕様の更新をいくつか共有しました。これらの変更の主な目的は次のとおりです。</p><ol><li>ストリーミング可能なHTTPを追加してリモートMCPを有効にする</li><li>抽出とツール出力スキーマの追加により、より豊富なエージェントインタラクションモデルを可能にする</li></ol><p>MCP はオープンソースであるため、開発者は Streamable HTTP などの変更を実装できる状態になっています。抽出およびツール出力スキーマは現在リリースされておらず、ドラフト段階にあり、進化する可能性があります。</p><p><strong>ストリーミング可能な HTTP</strong> ( <a href="https://modelcontextprotocol.io/specification/2025-03-26/basic/transports">2025 年 3 月 26 日リリース</a>) <strong>:</strong>ストリーミング可能な HTTP が新しいトランスポート メカニズムとして導入されたことは、大きなインパクトのある技術更新でした。これにより、サーバー送信イベント (SSE) が、チャンク転送エンコーディングと単一の HTTP 接続を介したプログレッシブ メッセージ配信をサポートする、よりスケーラブルな双方向モデルに置き換えられます。これにより、AWS Lambda などのクラウド インフラストラクチャに MCP サーバーを展開し、長時間の接続やポーリングを必要とせずにエンタープライズ ネットワークの制約をサポートできるようになります。</p><p><strong>Elicitation</strong> ( <a href="https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation">2025 年 6 月 18 日リリース</a>) <strong>:</strong> Elicitation を使用すると、サーバーはクライアントからのコンテキストの構造化方法を指定するスキーマを定義できます。基本的に、サーバーは必要なものと期待する入力の種類を記述できます。これにはいくつかの意味があります。サーバービルダーにとっては、より複雑なエージェントのインタラクションを構築できます。クライアントビルダーは、これらのスキーマに適応する動的な UI を実装できます。ただし、ユーザーから機密情報や個人を特定できる情報を抽出するために、誘導法を使用するべきではありません。特に MCP が成熟するにつれて、開発者は<a href="https://modelcontextprotocol.io/specification/draft/client/elicitation#security-considerations">ベスト プラクティス</a>に従って、誘導プロンプトが安全かつ適切な状態を保つようにする必要があります。これは、この投稿の後半で説明する、より広範なセキュリティ上の懸念に関係しています。</p><p><strong>ツール出力スキーマ</strong>( <a href="https://modelcontextprotocol.io/specification/draft/server/tools#output-schema">2025 年 6 月 18 日リリース</a>) <strong>:</strong>このコンセプトにより、クライアントと LLM はツール出力の形状を事前に知ることができます。ツール出力スキーマを使用すると、開発者はツールが返すことが予想される内容を記述できます。これらのスキーマは、コンテキスト ウィンドウの非効率的な使用という、直接ツール呼び出しの主な制限の 1 つに対処します。コンテキスト ウィンドウは、LLM を操作するときに最も重要なリソースの 1 つと考えられており、ツールを直接呼び出すと、LLM のコンテキストに完全にプッシュされる生のコンテンツが返されます。ツール出力スキーマを使用すると、MCP サーバーが構造化データを提供できるようになるため、トークンとコンテキスト ウィンドウをより有効に活用できるようになります。ここでは、ツール全般に関する<a href="https://modelcontextprotocol.io/specification/draft/server/tools#security-considerations">ベストプラクティスを</a>いくつか紹介します。</p><p>これらの新しいアップデートと今後の追加により、MCP はよりモジュール化され、型付けされた、実稼働対応のエージェント プロトコルになります。</p><h2>あまり使われていない強力な機能：サンプリングとルート</h2><p>MCP 仕様では目新しいものではありませんが、基調講演ではサンプリングとルートの両方が強調されました。これら 2 つのプリミティブは現在見過ごされ、十分に調査されていませんが、エージェント間のより豊かで安全なインタラクションに大きく貢献する可能性があります。</p><p><strong>サンプリング - サーバーはクライアントからの補完を要求できます。</strong><a href="https://modelcontextprotocol.io/docs/concepts/sampling">サンプリング</a>により、MCP サーバーはクライアント側の LLM から補完を要求できます。これにより、プロトコルの双方向性が強化され、サーバーはリクエストに応答するだけでなく、クライアントのモデルにプロンプトを出して応答を生成するように要求できるようになります。これにより、クライアントはコスト、セキュリティ、MCP サーバーが使用するモデルを完全に制御できます。したがって、事前構成されたモデルを備えた外部 MCP サーバーを使用する場合、サーバーはクライアントにすでに接続されているモデルを要求するだけでよいため、独自の API キーを提供したり、そのモデルに対する独自のサブスクリプションを構成したりする必要はありません。これにより、より複雑でインタラクティブなエージェントの動作が可能になります。</p><p><strong>ルート - リソースへのスコープ アクセス:</strong><a href="https://modelcontextprotocol.io/docs/concepts/roots">ルートは</a>、クライアントが関連するリソースと焦点を当てるワークスペースについてサーバーに通知する方法を提供するために設計されました。これは、サーバーが動作する範囲を設定するのに強力です。ルートは「<a href="https://modelcontextprotocol.io/docs/concepts/roots#how-roots-work">情報提供のみを目的としており、厳密に強制するものではない</a>」ことに注意することが重要です。つまり、MCP サーバーまたはエージェントの権限やアクセス許可を定義しないということです。つまり、サーバーまたはエージェントが特定のツールを実行したり書き込みアクションを実行したりするのを防ぐために、ルートだけに頼ることはできません。ルートの場合も、ユーザー承認のメカニズムを使用して、権限はクライアント側で処理する必要があります。また、開発者は、ルートによって設定された境界を尊重し、<a href="https://modelcontextprotocol.io/docs/concepts/roots#best-practices">ベストプラクティス</a>を使用するように設計されたサーバーの使用にも注意する必要があります。</p><h2>エージェントの認証: OAuth 2.1 と保護されたメタデータ</h2><p>このセクションでは、安全でないフローを排除し、ベスト プラクティスを統合した OAuth 2.0 の最新バージョンである OAuth 2.1 に焦点を当てます。</p><p>OAuth サポートは、特にセキュリティとスケーラビリティが、MCP がエージェントをツールに接続するための標準となることを妨げる大きな障害であると考えられているため、非常に期待されていたトピックでした。<a href="https://x.com/aaronpk">Aaron Parecki 氏</a>(Okta の OAuth 2.1 編集者兼 ID 標準専門家) は、サーバー開発者の複雑さのほとんどを軽減する、クリーンかつスケーラブルな OAuth フローを MCP がどのように採用できるかについて説明しました。公式の OAuth 2.1 認証仕様は、 <a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization">2025 年 6 月 18 日</a>の最新プロトコル改訂版で最近公開されました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2ea159116fe5e03ff800f077adf16d6ca9f1c1d1-1594x1280.png" style="width: 100%;" /><p>この実装では、OAuth の責任を MCP クライアントとサーバーの間で分割できます。認証フローの大部分は MCP クライアントによって開始および処理され、最後にサーバーが関与するのは安全なトークンの受信と検証のみです。この分割により、開発者がすべての接続を構成する必要なく、多くのツール間で認証を行うという重要なスケーリングの問題が解決され、MCP サーバー開発者が OAuth の専門家になる必要がなくなります。</p><p>講演の2つの重要なハイライト:</p><ol><li><a href="https://datatracker.ietf.org/doc/rfc9728/"><strong>保護されたリソース メタデータ</strong></a>: MCP サーバーは、目的、エンドポイント、認証方法を記述した JSON ファイルを公開できます。これにより、クライアントはサーバー URL だけで OAuth フローを開始できるようになり、接続プロセスが簡素化されます。詳細: <a href="https://aaronparecki.com/2025/04/03/15/oauth-for-model-context-protocol">MCP で OAuth を修正しましょう</a></li><li><a href="https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13"><strong>IDP と SSO のサポート</strong></a>: 企業は ID プロバイダーを統合してアクセスを集中管理できます。これは、ユーザー エクスペリエンスとセキュリティの両方にとってメリットとなります。ユーザーは 10 個の異なる同意画面をクリックする必要がなくなり、セキュリティ チームは各接続を監視できるようになります。</li></ol><p>OAuth ロジックをクライアントにプッシュし、サーバーからのメタデータに依存することで、MCP エコシステムは大きなボトルネックを回避します。これにより、MCP は、今日の運用環境で最新の API が保護される方法とより密接に連携するようになります。</p><p>追加の参考資料: <a href="https://aaronparecki.com/oauth-2-simplified/">OAuth 2 Simplified</a> 。</p><h2>コンポーザブルエコシステムにおけるセキュリティの課題</h2><p>新たな開発には新たな攻撃対象領域も伴います。Cisco の Arjun Sambamoorthy 氏は、MCP 環境における主な脅威をいくつか挙げています。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">脅威</td><td style="border: 1px solid #ccc; padding: 4px;">説明</td><td style="border: 1px solid #ccc; padding: 4px;">修復とベストプラクティス</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">迅速な注射とツールの中毒</td><td style="border: 1px solid #ccc; padding: 4px;">LLM システムのコンテキストまたはツールの説明内に悪意のあるプロンプトを挿入し、LLM がファイルの読み取りやデータの漏洩などの意図しないアクションを実行するようにする方法。</td><td style="border: 1px solid #ccc; padding: 4px;">MCP Scan などのツールを使用して、ツールのメタデータのチェックを実行します。説明とパラメータをプロンプトに含める前に検証します。最後に、リスクの高いツールに対してユーザー承認を実装することを検討してください。詳細については、表の下の追加の読書リストにある OWASP プロンプト インジェクション ガイドを参照してください。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">サンプリング攻撃</td><td style="border: 1px solid #ccc; padding: 4px;">MCP のコンテキストでは、サンプリングにより、MCP サーバーが LLM に対してプロンプト インジェクション攻撃を実行できるようになります。</td><td style="border: 1px solid #ccc; padding: 4px;">信頼できないサーバーのサンプリングを無効にし、サンプリング要求に人間による承認を追加することを検討してください。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">悪意のあるMCPサーバー</td><td style="border: 1px solid #ccc; padding: 4px;">現在の MCP サーバーのコレクションでは、安全性を確保するために各サーバーを検査するのは困難です。不正なサーバーは密かにデータを収集し、悪意のある人物に公開する可能性があります。</td><td style="border: 1px solid #ccc; padding: 4px;">信頼できるレジストリまたは内部リストからのみ MCP サーバーに接続します。サンドボックス化されたコンテナ内でサードパーティのサーバーを実行します。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">悪意のあるMCPインストールツール</td><td style="border: 1px solid #ccc; padding: 4px;">コマンドライン インストーラーとスクリプトは、MCP サーバーまたはツールを迅速に実装するのに便利ですが、検証されていない侵害されたコードをインストールしてしまう可能性があります。</td><td style="border: 1px solid #ccc; padding: 4px;">サンドボックス環境にインストールし、パッケージ署名を検証します。検証されていないソースからの自動更新は行わないでください。</td>
                    </tr>
              </tbody>
            </table>
          <p>この問題にさらに対抗するために、Arjun は、信頼できる MCP レジストリを使用してすべての検証を処理すること (これは最重要トピックです。詳細については、以下の読書リストの上位 2 項目を参照してください) と、この<a href="https://github.com/slowmist/MCP-Security-Checklist">セキュリティ チェックリスト</a>の使用を提案しています。</p><p>追加の参考資料:</p><ul><li><a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices">公式MCPセキュリティベストプラクティス</a></li><li><a href="https://owasp.org/www-project-top-10-for-large-language-model-applications/">OWASP LLM アプリケーション トップ 10</a></li><li><a href="https://hiddenlayer.com/innovation-hub/">HiddenLayer脅威リサーチ</a></li><li><a href="https://github.com/invariantlabs-ai/mcp-scan">MCPスキャン</a></li><li><a href="https://genai.owasp.org/llmrisk/llm01-prompt-injection/">OWASP プロンプトインジェクションガイド</a></li></ul><h2>次はレジストリ、ガバナンス、エコシステム</h2><p>集中型の MCP レジストリが開発中であり、サミットで最も頻繁に議論されたトピックの 1 つでした。現在のサーバー エコシステムは、断片化、信頼性と発見可能性の低さに悩まされています。特にメタデータが不完全であったり偽装されたりする可能性がある分散型エコシステムでは、開発者が MCP サーバーを見つけ、その動作を検証し、安全にインストールすることは困難です。</p><p>集中型レジストリは、信頼できる真実のソースとして機能し、発見可能性を向上させ、サーバー メタデータの整合性を確保し、悪意のあるツールをインストールするリスクを軽減することで、これらの問題点に直接対処します。</p><p>MCP レジストリの目標は次のとおりです。</p><ul><li>サーバーのメタデータ（サーバーが何をするか、どのように認証するか、インストールして呼び出すか）に関する唯一の真実の情報源を提供します。</li><li>不完全なサードパーティのレジストリと断片化を排除して、サーバーを登録するときに、インターネット上の他のすべてのレジストリを更新する必要がなくなります。</li><li>CLI ツールと、前述のメタデータを含む server.json ファイルを含むサーバー登録フローを提供します。</li></ul><p>より広範な期待は、信頼できるレジストリがエコシステムを安全に拡張し、開発者が自信を持って新しいツールを構築して共有できるようにすることです。</p><p>ガバナンスは、Anthropic にとってもう一つの最重要課題でした。MCP はオープンかつコミュニティ主導であり続けるべきだと明言しましたが、ガバナンス モデルの拡張はまだ進行中です。彼らは現在、その分野での支援を求めており、オープンソース プロトコルのガバナンスの経験がある方は誰でも連絡を取るよう呼びかけています。これは私が言及したかったもう一つの話題につながります。イベント全体を通じて、講演者は、エコシステムはその内部の開発者の貢献によってのみ成長できると強調しました。MCP を新しい Web 標準にして、他の一般的なエージェント プロトコルと差別化するために、集中的な取り組みが必要です。</p><h2>現実世界におけるMCP：ケーススタディとデモ</h2><p>いくつかの組織は、MCP がすでに実際のアプリケーションでどのように使用されているかを共有しました。</p><ul><li><strong>PayPal - エージェンティックコマース向け MCP サーバー:</strong> PayPal は、ユーザーのショッピング体験を根本的に変えることができる新しい<a href="https://github.com/paypal/agent-toolkit/">エージェント ツールキット</a>と MCP サーバーを展示しました。ユーザーは、ソーシャル メディアで商品を探したり、価格を比較したり、チェックアウトしたりする代わりに、PayPal MCP サーバーに接続してそれらのすべてのアクションを処理するエージェントとチャットできます。<br/></li><li><strong>EpicAI.pro - Jarvis:</strong> MCP の開発により、現実世界の Jarvis タイプのアシスタントの実現にますます近づいています。アイアンマン映画をご存じない方のために説明すると、Jarvis は自然言語を使用し、マルチモーダル入力に応答し、応答時に遅延がなく、ユーザーのニーズを積極的に予測し、統合を自動的に管理し、デバイスと場所の間でコンテキストを切り替えることができる AI アシスタントです。Jarvis を物理的なロボット アシスタントとして想像すると、MCP は Jarvis に「手」、つまり複雑なタスクを処理する能力を与えます。<br/></li><li><strong>Postman -</strong> <strong><a href="https://www.postman.com/explore/mcp-generator">MCP サーバー ジェネレーター</a>:</strong> API リクエスト用のショッピング カート エクスペリエンスを提供します。さまざまな API リクエストを選択してバスケットに入れ、バスケット全体を MCP サーバーとしてダウンロードできます。<br/></li><li><strong>Bloomberg -</strong> Bloomberg は、エンタープライズ GenAI 開発における主要なボトルネックを解決しました。約 10,000 人のエンジニアを抱える同社では、チーム間でツールとエージェントを統合するための標準化された方法が必要でした。MCP を使用することで、社内ツールをモジュール式のリモートファースト コンポーネントに変換し、エージェントが統合インターフェースで簡単に呼び出すことができるようになりました。これにより、エンジニアは組織全体にツールを提供できるようになり、AI チームはカスタム統合ではなくエージェントの構築に集中できるようになりました。Bloomberg は現在、MCP エコシステムとの完全な相互運用性を実現する、スケーラブルで安全なエージェント ワークフローをサポートしています。ブルームバーグは公開リソースへのリンクを一切提供していないが、これはサミットで彼らが公開した内容である。<br/></li><li><strong>Block –</strong> Block は MCP を使用して、従業員がエンジニアリング、営業、マーケティングなどのタスクを自動化できるようにする社内 AI エージェントである<a href="https://github.com/block/goose?tab=readme-ov-file">Goose</a>を強化しています。同社は、Git、Snowflake、Jira、Google Workspace などのツール用に 60 台以上の MCP サーバーを構築し、日常的に使用するシステムとの自然言語によるやり取りを可能にしました。Block 社の従業員は現在、Goose を使用してデータのクエリ、不正行為の検出、インシデントの管理、内部プロセスのナビゲートなどを行っており、これらはすべてコードを書かずに実行できます。MCP は、Block がわずか 2 か月で多くの職務にわたって AI の導入を拡大できるよう支援しました。<br/></li><li><strong>AWS -</strong> <strong><a href="https://github.com/awslabs/mcp">AWS MCP サーバー</a>:</strong> AWS は、サイコロを振る動作をシミュレートし、過去のロールを追跡し、Streamable HTTP を使用して結果を返す、楽しいダンジョンズ アンド ドラゴンズをテーマにした MCP サーバーを発表しました。この軽量な例では、Lambda や Fargate などの AWS ツールとインフラストラクチャを使用して MCP サーバーを簡単に構築およびデプロイできることが強調されました。また、MCP サーバーと対話するマルチモーダル エージェントを構築するためのオープン ソース ツールキットである<a href="https://aws.amazon.com/blogs/opensource/introducing-strands-agents-an-open-source-ai-agents-sdk/">Strands SDK</a>も紹介されました。</li></ul><h2>Elastic Agent Builder での MCP サポート</h2><p><a href="https://www.elastic.co/jp/search-labs/blog/elastic-ai-agent-builder-context-engineering-introduction">Elastic Agent Builder を使用すると、</a>今すぐ MCP の実験を始めることができます。これは、データ上に直接エージェントを構築する最も簡単な方法です。Agent Builder を使用すると、Elasticsearch を利用したツールを MCP 対応エージェントに公開できます。また、次のような強力な組み込みツールがすでに付属しています。</p><ul><li><code>platform.core.search</code> - 完全なElasticsearchクエリDSLを使用して検索を実行します</li><li><code>platform.core.list_indices</code> - Elasticsearch 内で利用可能なすべてのインデックスを一覧表示します (エージェントがどのようなデータが存在するかを検出できるようにします)</li><li><code>platform.core.get_index_mapping</code> - 特定のインデックスのフィールド マッピングを取得します (エージェントがデータの形状と種類を理解するのに役立ちます)</li><li><code>platform.core.get_document_by_id</code> - IDで特定のドキュメントを取得します（正確な検索のため）</li></ul><p>これらのツールを使用するだけで、信頼性の高い AI エージェントを構築するための中核となるエンタープライズ レベルの検索と関連性をエージェントに装備できます。</p><p>Agent Builder をさらに強力にするのは、アプリケーションのニーズに合わせて独自のカスタム ツールを定義し、公開できる機能です。これは、毎回そのロジックを再検出することなく、エージェントが特定のインデックスに対して特定のタイプの検索を実行するようにしたい、意見が強いワークフローや繰り返し可能なワークフローに特に役立ちます。同じ結論に到達するために計画と推論にトークンを費やす代わりに、その意図をツールに直接エンコードすることで、エージェントの速度、信頼性、コスト効率を高めることができます。</p><p>Agent Builder UI 内で、ES|QL を使用するカスタム ツール定義の例を次に示します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c1f6405a374b707e8e6fa36b9e21db5f3c7cd127-1376x864.png" style="width: 100%;" /><p>カスタム ツールを定義したら、 <code>Manage MCP</code>のドロップダウンをクリックして MCP サーバー URL をコピーすることで、MCP を使用してカスタム ツール (および組み込みのネイティブ ツール) を公開できます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9f39588525ca2643475de557ea54a6bcf5c150f6-1282x616.png" style="width: 100%;" /><p>これで、この MCP エンドポイントを、MCP を使用する任意のクライアントにインポートして Agent Builder に接続し、利用可能なすべてのツールにアクセスできるようになります。詳細については、 <a href="https://www.elastic.co/jp/search-labs/blog/elastic-ai-agent-builder-context-engineering-introduction">Agent Builder</a>の紹介をお読みください。</p><h2>まとめ</h2><p>MCP Dev Summit では、MCP がこれらの AI エージェント同士、そして周囲のデータの世界と対話する方法を形作っていることが明らかになりました。エージェントをエンタープライズ データに接続する場合でも、完全に自律的なエージェントを設計する場合でも、MCP は標準化された構成可能な統合方法を提供し、大規模な環境ですぐに役立つようになります。トランスポート プロトコルやセキュリティ パターンからレジストリやガバナンスに至るまで、MCP エコシステムは急速に成熟しています。MCP は今後もオープンかつコミュニティ主導であり続けるため、今日の開発者には MCP の進化を形作るチャンスがあります。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ハイブリッド検索の再考: Elasticsearch の線形リトリーバーの導入!]]></title>
            <description><![CDATA[線形リトリーバーが加重スコアと MinMax 正規化を活用してハイブリッド検索を強化し、より正確で一貫性のあるランキングを実現する方法を確認し、その使用方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/linear-retriever-hybrid-search</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/linear-retriever-hybrid-search</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Wed, 28 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7a07e27e3095463ff93b4cb7f8a0cf3b8e44eab0-1777x1000.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><a href="https://www.elastic.co/jp/search-labs/blog/elasticsearch-retrievers-ga-8.16.0">前回のブログ</a>投稿では、複雑なランキング パイプラインの作成を可能にする、ゼロから再設計されたリトリーバー フレームワークを紹介しました。また、Reciprocal Rank Fusion (RRF) リトリーバーが、異なるクエリの結果を結合してハイブリッド検索を可能にする方法についても調査しました。RRF は簡単に実装できますが、実際のスコアを無視して相対的なランクのみに焦点を当てるという、顕著な制限があります。これにより、微調整と最適化が困難になります。</p><h2>リニアレトリーバーに会いましょう！</h2><p>この投稿では、ハイブリッド検索をサポートするために最近追加された<a href="https://www.elastic.co/jp/docs/solutions/search/retrievers-overview#retrievers-overview-types"><code>linear</code>リトリーバー</a>を紹介します。<code>rrf</code>とは異なり、 <code>linear</code>リトリーバーはドキュメントに一致したすべてのクエリの加重合計を計算します。このアプローチにより、結果セット内の各ドキュメントの相対的な重要度が維持され、各クエリが最終スコアに与える影響を正確に制御できるようになります。その結果、ハイブリッド検索を微調整するためのより直感的で柔軟な方法が提供されます。</p><p>最終スコアが次のように計算される線形リトリーバーを定義します。</p><div class="katex">score = 5 * knn + 1.5 * bm25</div><p>それは次のように簡単です:</p><pre><code>GET linear_retriever_blog/_search
{
   "retriever": {
       "linear": {
           "retrievers": [
               {
                   "retriever": {
                       "knn": {
                          ...
                        }
                    },
                   "weight": 5
               },
                  {
                   "retriever": {
                       "standard": {
                          ...
                        }
                    },
                   "weight": 1.5
               },


           ]
        }
     }
}</code></pre><p>いかにシンプルで直感的であるかに気づきましたか?（そして<code>rrf</code>と本当に似ています！）この構成により、相対的な順位のみに依存する<code>rrf</code>とは異なり、各クエリ タイプが最終的な順位にどの程度寄与するかを正確に制御できます。</p><p>注意点が 1 つあります: 使用される類似度メトリックに応じて、 <code>knn</code>スコアは厳密に制限される場合があります。たとえば、コサイン類似度または単位正規化ベクトルのドット積では、スコアは常に<code>[0, 1]</code>範囲内になります。対照的に、 <code>bm25</code>スコアは予測しにくく、明確に定義された境界がありません。</p><h2>スコアのスケーリング：kNN vs BM25</h2><p>ハイブリッド検索の課題の 1 つは、異なるリトリーバーが異なるスケールでスコアを生成することです。たとえば、次のシナリオを考えてみましょう。</p><p>クエリAのスコア:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント1</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント2</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント3</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント4</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">わかる</td><td style="border: 1px solid #ccc; padding: 4px;">0.347</td><td style="border: 1px solid #ccc; padding: 4px;">0.35</td><td style="border: 1px solid #ccc; padding: 4px;">0.348</td><td style="border: 1px solid #ccc; padding: 4px;">0.346</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">bm25</td><td style="border: 1px solid #ccc; padding: 4px;">100</td><td style="border: 1px solid #ccc; padding: 4px;">1.5</td><td style="border: 1px solid #ccc; padding: 4px;">1</td><td style="border: 1px solid #ccc; padding: 4px;">0.5</td>
                    </tr>
              </tbody>
            </table>
          <p>クエリBのスコア:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント1</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント2</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント3</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント4</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">わかる</td><td style="border: 1px solid #ccc; padding: 4px;">0.347</td><td style="border: 1px solid #ccc; padding: 4px;">0.35</td><td style="border: 1px solid #ccc; padding: 4px;">0.348</td><td style="border: 1px solid #ccc; padding: 4px;">0.346</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">bm25</td><td style="border: 1px solid #ccc; padding: 4px;">0.63</td><td style="border: 1px solid #ccc; padding: 4px;">0.01</td><td style="border: 1px solid #ccc; padding: 4px;">0.3</td><td style="border: 1px solid #ccc; padding: 4px;">0.4</td>
                    </tr>
              </tbody>
            </table>
          <p>上記の差異を見ると、 <code>kNN</code>スコアは 0 から 1 の範囲であるのに対し、 <code>bm25</code>スコアは大きく変動する可能性があります。この違いにより、結果を組み合わせるための静的な最適な重みを設定することが難しくなります。</p><h2>正規化による救済：MinMax正規化</h2><p>この問題を解決するために、次の数式を使用して、クエリごとに独立してスコアを<code>[0, 1]</code>範囲にスケーリングするオプションの<code>minmax</code>ノーマライザーを導入しました。</p><div class="katex">normalized_score = (score - min) / (max - min)</div><p>これにより、クエリの結果セット内の各ドキュメントの相対的な重要度が保持され、異なるリトリーバーからのスコアを簡単に組み合わせることができます。正規化すると、スコアは次のようになります。</p><p>クエリAのスコア:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント1</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント2</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント3</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント4</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">わかる</td><td style="border: 1px solid #ccc; padding: 4px;">0.347</td><td style="border: 1px solid #ccc; padding: 4px;">0.35</td><td style="border: 1px solid #ccc; padding: 4px;">0.348</td><td style="border: 1px solid #ccc; padding: 4px;">0.346</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">bm25</td><td style="border: 1px solid #ccc; padding: 4px;">1.00</td><td style="border: 1px solid #ccc; padding: 4px;">0.01</td><td style="border: 1px solid #ccc; padding: 4px;">0.005</td><td style="border: 1px solid #ccc; padding: 4px;">0.000</td>
                    </tr>
              </tbody>
            </table>
          <p>クエリBのスコア:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント1</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント2</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント3</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント4</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">わかる</td><td style="border: 1px solid #ccc; padding: 4px;">0.347</td><td style="border: 1px solid #ccc; padding: 4px;">0.35</td><td style="border: 1px solid #ccc; padding: 4px;">0.348</td><td style="border: 1px solid #ccc; padding: 4px;">0.346</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">bm25</td><td style="border: 1px solid #ccc; padding: 4px;">1.00</td><td style="border: 1px solid #ccc; padding: 4px;">0.000</td><td style="border: 1px solid #ccc; padding: 4px;">0.465</td><td style="border: 1px solid #ccc; padding: 4px;">0.645</td>
                    </tr>
              </tbody>
            </table>
          <p>すべてのスコアが<code>[0, 1]</code>範囲内に収まるようになり、絶対スコアではなく結果の (クエリに対する相対的な) 重要度を取得し、クエリ間で一貫性を維持できるようになったため、加重合計の最適化がはるかに簡単になりました。</p><h2>リニアリトリーバーの例 </h2><p>ここで例を見て、上記がどのようになっているか、また<code>linear</code>リトリーバーが<code>rrf</code>の欠点のいくつかをどのように解決しているかを見てみましょう。RRF は相対的なランクのみに依存し、実際のスコアの違いは考慮しません。たとえば、次のスコアがあるとします。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント1</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント2</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント3</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント4</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">わかる</td><td style="border: 1px solid #ccc; padding: 4px;">0.347</td><td style="border: 1px solid #ccc; padding: 4px;">0.35</td><td style="border: 1px solid #ccc; padding: 4px;">0.348</td><td style="border: 1px solid #ccc; padding: 4px;">0.346</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">bm25</td><td style="border: 1px solid #ccc; padding: 4px;">100</td><td style="border: 1px solid #ccc; padding: 4px;">1.5</td><td style="border: 1px solid #ccc; padding: 4px;">1</td><td style="border: 1px solid #ccc; padding: 4px;">0.5</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">rrfスコア</td><td style="border: 1px solid #ccc; padding: 4px;">0.03226</td><td style="border: 1px solid #ccc; padding: 4px;">0.03252</td><td style="border: 1px solid #ccc; padding: 4px;">0.03200</td><td style="border: 1px solid #ccc; padding: 4px;">0.03125</td>
                    </tr>
              </tbody>
            </table>
          <p>rrf はドキュメントを次のようにランク付けします。</p><div class="katex">doc2 > doc1 > doc3 > doc4</div><p>ただし、doc1 は他のものと比べて<code>bm25</code>スコアが大幅に高くなっていますが、 <code>rrf</code>相対的な順位のみを見ているため、これを捕捉できません。<code>linear</code>リトリーバーは正規化と組み合わせることで、スコアとその差異の両方を正しく考慮し、より意味のあるランキングを生成します。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント1</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント2</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント3</td><td style="border: 1px solid #ccc; padding: 4px;">ドキュメント4</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">わかる</td><td style="border: 1px solid #ccc; padding: 4px;">0.347</td><td style="border: 1px solid #ccc; padding: 4px;">0.35</td><td style="border: 1px solid #ccc; padding: 4px;">0.348</td><td style="border: 1px solid #ccc; padding: 4px;">0.346</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">bm25</td><td style="border: 1px solid #ccc; padding: 4px;">1</td><td style="border: 1px solid #ccc; padding: 4px;">0.01</td><td style="border: 1px solid #ccc; padding: 4px;">0.005</td><td style="border: 1px solid #ccc; padding: 4px;">0</td>
                    </tr>
              </tbody>
            </table>
          <p>上記のように、doc1 の優れたランキングと<code>bm25</code>の<code>score</code>が適切に考慮され、最終スコアに反映されています。さらに、すべてのスコアが<code>[0, 1]</code>範囲内にあるため、より直感的な方法でスコアを比較および組み合わせることができます (さらに、オフラインの最適化プロセスを構築することもできます)。</p><h2>すべてをまとめると</h2><p>正規化を伴う<code>linear</code>リトリーバーを最大限に活用するには、検索リクエストは次のようになります。</p><pre><code>GET linear_retriever_blog/_search
{
   "retriever": {
       "linear": {
           "retrievers": [
               {
                   "retriever": {
                       "knn": {
                          ...
                        }
                    },
                   "weight": 5
               },
                  {
                   "retriever": {
                       "standard": {
                          ...
                        }
                    },
                   "weight": 1.5,
                   "normalizer": "minmax"
               },


           ]
       }
   }
}</code></pre><p>このアプローチは、両方の長所を組み合わせたものです。つまり、 <code>linear</code>リトリーバーの柔軟性と直感的なスコアリングを維持しながら、MinMax 正規化による一貫したスコア スケーリングを保証します。</p><p>すべてのリトリーバーと同様に、 <code>linear</code>リトリーバーは、説明可能性、一致の強調表示、フィールドの折りたたみなどをサポートし、階層的なリトリーバー ツリーの任意のレベルに統合できます。</p><h2>リニアレトリーバーを選ぶべきタイミングとそれが重要な理由</h2><p><code>linear</code>レトリーバー:</p><ul><li>ランクだけでなく実際のスコアを活用して相対的な重要性を維持します。</li><li>さまざまなクエリからの重み付けされた寄与による微調整を可能にします。</li><li>正規化を使用して一貫性を強化し、ハイブリッド検索をより堅牢かつ予測可能にします。</li></ul><h2>まとめ</h2><p><code>linear</code>リトリーバーは、Elasticsearch Serverless、8.18 および 9.0 リリースですでに利用可能です。その他の例と構成パラメータについては、ドキュメントでも参照できます。ぜひお試しいただき、ハイブリッド検索エクスペリエンスがどのように改善されるかをご確認ください。皆様からのフィードバックをお待ちしております。楽しい検索を！</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Quepidでジャッジメントリストを作成する]]></title>
            <description><![CDATA[Quepidで人間の評価者が協力して判定リストを作成する方法、ベンチマークを使用して関連性を調整する方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/quepid-judgement-lists</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/quepid-judgement-lists</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Daniel Wrigley]]></dc:creator>
            <pubDate>Mon, 26 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/307c1774bd31f92bb4aa7b69e1a6796240465100-1600x914.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><a href="https://www.elastic.co/search-labs/blog/judgment-lists">判断リスト</a>の作成は、検索結果の品質を最適化する上で重要なステップですが、複雑で困難な作業になる場合があります。判断リストは、対応する結果の関連性評価と組み合わせた検索クエリの厳選されたセットであり、テスト コレクションとも呼ばれます。このリストを使用して計算されたメトリックは、検索エンジンのパフォーマンスを測定するためのベンチマークとして機能します。判断リストの作成プロセスを効率化するために、 <a href="https://opensourceconnections.com/">OpenSource Connections</a>チームは<a href="https://quepidapp.com/">Quepid を</a>開発しました。判断は明示的なものでも、ユーザーからの暗黙的なフィードバックに基づくものでも構いません。このブログでは、あらゆる判断リストの基礎となる明示的な判断を人間の評価者が効果的に行えるように、Quepid で共同作業環境を設定する方法について説明します。</p><p>Quepid は、検索品質評価プロセスにおいて検索チームをサポートします。</p><ul><li>クエリセットを構築する</li><li>判断リストを作成する</li><li>検索品質指標を計算する</li><li>計算された検索品質指標に基づいて、さまざまな検索アルゴリズム/ランカーを比較します</li></ul><p>私たちのブログでは、映画レンタル店を運営しており、検索結果の品質を向上させることを目標としていると仮定しましょう。</p><h2>要件</h2><p>このブログでは<a href="https://github.com/o19s/es-tmdb">、es-tmdb リポジトリ</a>のデータとマッピングを使用します。データは<a href="https://www.themoviedb.org/">The Movie Database</a>から取得されています。手順に沿って、マッピングを使用して tmdb というインデックスを設定し、データにインデックスを付けます。ローカルインスタンスをセットアップするか、Elastic Cloud デプロイメントを使用するかは問題ではありません。どちらでも問題なく動作します。このブログでは、Elastic Cloud のデプロイメントを想定しています。データのインデックス作成方法については<a href="https://github.com/o19s/es-tmdb/blob/master/README.md">、es-tmdb リポジトリの README を</a>参照してください。</p><p>検索するデータがあることを確認するには、 <code>rocky</code>のタイトル フィールドで単純な一致クエリを実行します。</p><pre><code>GET tmdb/_search
{
 "query": {
   "match": {
     "title": "rocky"
   }
 }
}</code></pre><p>8 件の結果が表示されます。</p><pre><code>{
 "took": 2,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 8,
     "relation": "eq"
   }
…
}</code></pre><h2>Quepidにログイン</h2><p><a href="https://github.com/o19s/quepid">Quepid は</a>、ユーザーが検索結果の品質を測定し、オフライン実験を実行して品質を向上できるようにするツールです。</p><p>Quepid は 2 つの方法で使用できます: <a href="https://app.quepid.com">https://app.quepid.com</a>で公開されている無料のホストバージョンを使用するか、または、アクセスできるマシンに Quepid をセットアップします。この投稿では、無料のホスト バージョンを使用していることを前提としています。ご使用の環境に Quepid インスタンスを設定する場合は、<a href="https://github.com/o19s/quepid/wiki/Installation-Guide">インストール ガイド</a>に従ってください。</p><p>どちらの設定を選択する場合でも、まだアカウントをお持ちでない場合はアカウントを作成する必要があります。</p><h2>Quepidケースの設定方法</h2><p>Quepid は「ケース」を中心に構成されています。ケースには、関連性調整設定と検索エンジンへの接続を確立する方法とともにクエリが保存されます。</p><ul><li>初めて使用する場合は、 <strong>「最初の関連性ケースを作成する」</strong>を選択します。</li><li>再度アクセスしたユーザーは、トップレベルのメニューから<strong>[関連性ケース]</strong>を選択し、 <strong>[+ ケースを作成] を</strong>クリックできます。</li></ul><p>ベースライン検索の測定と改善を開始するため、ケースに説明的な名前を付けます (例:「映画検索ベースライン」)。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8f9e480d9aae0d706cfc5371e41f19c706dd452a-594x251.png" style="width: 100%;" /><p><strong>[続行]</strong>を選択して名前を確認します。</p><p>次に、Quepid から検索エンジンへの接続を確立します。Quepid は、Elasticsearch を含むさまざまな検索エンジンに接続できます。</p><p>構成は、Elasticsearch と Quepid の設定によって異なります。Quepid を Elastic Cloud デプロイメントに接続するには、Elastic Cloud デプロイメントに対して CORS を有効にして構成し、API キーを用意する必要があります。詳細な手順は、 <a href="https://quepid-docs.dev.o19s.com/2/quepid/49/how-to-connect-quepid-to-elastic-cloud">Quepid ドキュメントの対応するハウツー</a>に記載されています。</p><p>Elasticsearch エンドポイント情報 ( <code>https://YOUR_ES_HOST:PORT/tmdb/_search</code> ) と接続に必要な追加情報 (<strong>詳細</strong>設定オプションの Elastic Cloud デプロイメントの場合は API キー) を入力し、 <strong>ping</strong>をクリックして接続をテストし、 <strong>[続行</strong>] を選択して次のステップに進みます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/267339dfaecae2740eb2ee2739bdc971608bdb5f-588x1169.png" style="width: 100%;" /><p>ここで、ケースに表示するフィールドを定義します。人間の評価者が後で特定のクエリに対するドキュメントの関連性を評価するのに役立つものをすべて選択します。</p><p><code>title</code><em>タイトル フィールド</em>として設定し、 <code>_id</code> <em>ID フィールド</em>のままにして、 <code>overview, tagline, cast, vote_average, thumb:poster_path</code><em>追加表示フィールド</em>として追加します。最後のエントリには、結果内の映画の小さなサムネイル画像が表示され、私たちと人間の評価者に視覚的にガイドします。</p><p><strong>[続行]</strong>ボタンを選択して表示設定を確認します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/02c58aae8c2ebb6d31f538b27462b4c65428fdc3-594x493.png" style="width: 100%;" /><p>最後のステップは、ケースに検索クエリを追加することです。入力フィールドから<em>「スターウォーズ」</em> 、 <em>「ハリソンフォード」</em> 、 <em>「ベストアクション映画」の</em>3 つのクエリを 1 つずつ追加し、 <strong>「続行」をクリックします</strong>。</p><p>理想的には、ケースには実際のユーザークエリを表し、さまざまな種類のクエリを示すクエリが含まれます。現時点では、<em>スターウォーズは</em>映画のタイトルのすべてのクエリを表すクエリ、<em>ハリソンフォードは</em>キャストメンバーのすべてのクエリを表すクエリ、<em>ベストアクション映画は</em>特定のジャンルの映画を検索するすべてのクエリを表すクエリであると想像できます。これは通常、クエリ セットと呼ばれます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/baccfe96766319aa7255e9bff08913ac87d1517f-595x326.png" style="width: 100%;" /><p>実稼働シナリオでは、<a href="https://opensourceconnections.com/blog/2022/10/13/how-to-succeed-with-explicit-relevance-evaluation-using-probability-proportional-to-size-sampling/">確率比例サイズサンプリング</a>などの統計手法を適用してイベント トラッキング データからクエリをサンプリングし、これらのサンプリングされたクエリを Quepid にインポートして、頻度に応じて先頭 (頻繁に発生するクエリ) と末尾 (発生頻度の低いクエリ) のクエリを含めます。つまり、まれなクエリを除外することなく、より頻度の高いクエリに偏向させるということです。</p><p>最後に、 <strong>「完了」</strong>を選択すると、定義された 3 つのクエリが表示されるケース インターフェイスに移動します。</p><h2>クエリと情報ニーズ</h2><p>判断リストという全体的な目標に到達するには、人間の評価者が特定のクエリに対する検索結果 (通常はドキュメント) を判断する必要があります。これはクエリ/ドキュメント ペアと呼ばれます。</p><p>場合によっては、クエリを見るとユーザーが何を望んでいたかが簡単にわかるようです。クエリ<code>harrison ford</code>の目的は、俳優のハリソン・フォードが主演する映画を見つけることです。クエリ<code>action</code>についてはどうでしょうか?ユーザーの意図はアクション ジャンルに属する映画を見つけることだと言いたくなるでしょう。でもどれですか？最新のもの、最も人気のあるもの、ユーザーの評価による最高のものはありますか?あるいは、ユーザーは「アクション」と呼ばれるすべての映画を見つけたいのでしょうか?<a href="https://www.themoviedb.org/search/movie?query=Action">映画データベースには「アクション」というタイトルの映画が少なくとも 12 本 (!) あり</a>、それらの名前は主にタイトルに含まれる感嘆符の数によって異なります。</p><p>意図が不明瞭なクエリの場合、2 人の評価者の間で解釈に違いが生じる可能性があります。情報ニーズの登場:<a href="https://en.wikipedia.org/wiki/Information_needs">情報ニーズ</a>とは、情報に対する意識的または無意識的な欲求のことです。情報ニーズを定義すると、人間の評価者がクエリに対して文書を判断するのに役立つため、判断リストを構築するプロセスで重要な役割を果たします。専門ユーザーまたは主題の専門家は、情報ニーズを指定するのに適しています。検索結果はユーザーのニーズを満たす必要があるため、ユーザーの視点から情報ニーズを定義することをお勧めします。</p><p>「映画検索ベースライン」のケースのクエリに必要な情報:</p><ol><li><strong>スターウォーズ</strong>: ユーザーはスターウォーズシリーズの映画や番組を見つけたいと考えています。関連性がある可能性があるのは、スターウォーズに関するドキュメンタリーです。</li><li><strong>ハリソン・フォード</strong>: ユーザーは俳優ハリソン・フォードが主演する映画を見つけたいと考えています。関連性がある可能性があるのは、ハリソン・フォードがナレーターなどの別の役割を担っている映画です。</li><li><strong>最高のアクション映画</strong>: ユーザーはアクション映画、できれば平均ユーザー投票数の多い映画を見つけたいと考えています。</li></ol><h2>Quepidで情報ニーズを定義する方法</h2><p>Quepid で情報ニーズを定義するには、ケース インターフェースにアクセスします。</p><p>1. クエリ (たとえば、<em>スターウォーズ</em>) を開き、 <em>[Toggle Notes] を選択します。</em></p><p>2. 最初のフィールドに情報ニーズを入力し、2 番目のフィールドに追加のメモを入力します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e01d3d5242a350d8797faa665eb3170039f5dfa2-1483x559.png" style="width: 100%;" /><p>3. <strong>「保存」</strong>をクリックします。</p><p>少数のクエリの場合、このプロセスは適切です。ただし、ケースを 3 クエリから 100 クエリに拡張する場合 (Quepid のケースでは、多くの場合、クエリは 50 ～ 100 クエリの範囲です)、Quepid の外部で (たとえば、スプレッドシートで) 情報ニーズを定義し、それを<strong>[インポート</strong>] からアップロードして<strong>[情報ニーズ] を選択する必要がある</strong>場合があります。</p><h2>Quepidでチームを作成し、ケースを共有する</h2><p>共同判断により関連性評価の品質が向上します。チームを設定するには:</p><p>1. 最上位メニューの<strong>「Teams」</strong>に移動します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/797706e8d130b474a95d30b6fa22ecaf36f98c03-613x58.png" style="width: 100%;" /><p>2. <strong>[+ 新規追加]</strong>をクリックし、チーム名 (例:「検索関連性評価者」) を入力して、 <strong>[作成] を</strong>クリックします。</p><p>3. メールアドレスを入力し、 <strong>「ユーザーの追加」</strong>をクリックしてメンバーを追加します。</p><p>4. ケースインターフェースで、 <strong>「ケースの共有」</strong>を選択します。</p><p>5. 適切なチームを選択して確認します。</p><h2>Quepidで判定ブックを作成する</h2><p>Quepid のブックでは、複数の評価者がクエリ/ドキュメントのペアを体系的に評価できます。作成するには:</p><p>1. ケースインターフェースの<strong>「判決」</strong>に移動し、 <strong>「+ ブックを作成」</strong>をクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6a077f26225961150b7414463d7db04f090b68d6-896x365.png" style="width: 100%;" /><p>2. ブックにわかりやすい名前を付けてチームに割り当て、採点方法 (DCG@10 など) を選択し、選択戦略 (単一または複数の評価者) を設定します。ブックには次の設定を使用します。</p><ul><li><strong>名称</strong>：「映画検索0-3スケール」</li><li><strong>この本を共有するチーム</strong>: 作成したチームのボックスにチェックを入れます</li><li><strong>得点者</strong>: DCG@10</li></ul><p>3. <strong>「ブックを作成」をクリックします。</strong></p><p>名前は説明的で、検索対象（「映画」）に関する情報と、評価のスケール（「0～3」）が含まれています。選択したスコアラー DCG@10 によって、検索メトリックの計算方法が決まります。「DCG」は<a href="https://en.wikipedia.org/wiki/Discounted_cumulative_gain">「Discounted Cumulative Gain」</a>の略で、「@10」は指標を計算する際に考慮される上位からの結果の数です。</p><p>この場合、情報ゲインを測定し、それを位置の重み付けと組み合わせるメトリックを使用しています。他にもユースケースに適した検索メトリックが存在する可能性があり、<a href="https://opensourceconnections.com/blog/2020/02/28/choosing-your-search-relevance-metric">適切なものを選択すること自体が課題となります</a>。</p><h2>クエリとドキュメントのペアをブックに入力する</h2><p>関連性評価のためにクエリ/ドキュメントのペアを追加するには、次の手順に従います。</p><p>1.ケース インターフェースで、「判決」に移動します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d717c5b06ae6cb42ed2b9e771486a12f738a9890-1041x218.png" style="width: 100%;" /><p>2. 作成したブックを選択します。</p><p>3. 「ブックに入力」をクリックし、「ブックのクエリ/ドキュメント ペアを更新」を選択して確認します。</p><p>このアクションにより、各クエリの上位の検索結果に基づいてペアが生成され、チームによる評価の準備が整います。</p><h2>人間の評価チームに判断させる </h2><p>これまでに完了した手順は、かなり技術的かつ管理的なものでした。必要な準備が完了したので、審査員チームに作業を任せることができます。本質的に、審査員の仕事は、与えられたクエリに対する特定の文書の関連性を評価することです。このプロセスの結果は、判断されたクエリ ドキュメント ペアのすべての関連性ラベルを含む判断リストです。次に、このプロセスとそのインターフェースについてさらに詳しく説明します。</p><h3>人間評価インターフェースの概要</h3><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/be3016091b49655dab3354d84e6dc638f3468390-1283x664.png" style="width: 100%;" /><p>Quepidの人間評価インターフェースは、効率的な評価ができるように設計されています。</p><ul><li><strong>クエリ:</strong>検索用語を表示します。</li><li><strong>情報ニーズ:</strong>ユーザーの意図を示します。</li><li><strong>採点ガイドライン:</strong>一貫した評価を行うための指示を提供します。</li><li><strong>ドキュメント メタデータ:</strong>ドキュメントに関する関連詳細を表示します。</li><li><strong>評価ボタン:</strong>評価者は対応するキーボード ショートカットを使用して判断を割り当てることができます。</li></ul><h3>人間評価インターフェースを使用する</h3><p>人間の評価者として、私は本の概要からインターフェースにアクセスします。</p><p>1. ケース インターフェイスに移動し、 <strong>[判決]</strong>をクリックします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d717c5b06ae6cb42ed2b9e771486a12f738a9890-1041x218.png" style="width: 100%;" /><p>2. <strong>「より多くの判断が必要です!」</strong>をクリックします。</p><p>システムはまだ評価されておらず、追加の判断が必要なクエリ/ドキュメントのペアを提示します。これは、ブックの選択戦略によって決まります。</p><ul><li><em>単一の評価者</em>: クエリ/ドキュメントのペアごとに 1 つの判断。</li><li><em>複数の評価者</em>: クエリ/ドキュメントのペアごとに最大 3 つの判断。</li></ul><h3>クエリとドキュメントのペアを評価する</h3><p>いくつかの例を見てみましょう。このガイドに従うと、さまざまな映画が表示される可能性が高くなります。ただし、評価の原則は変わりません。</p><p>最初の例は、映画「Heroes」でクエリ<em>「harrison ford」</em>を検索するものです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c1e14b0d8b04dd579471932dbe4ff72ae5692a02-981x571.png" style="width: 100%;" /><p>まずクエリを確認し、次に情報ニーズを確認し、最後に指定されたメタデータに基づいて映画を判断します。</p><p>この映画は、ハリドソン・フォードが出演しているため、私たちの検索に関連する結果です。私たちは主観的には最近の映画の方が関連性が高いと考えるかもしれませんが、これは私たちの情報ニーズには含まれません。したがって、この文書は当社の評価尺度で 3 に相当する「完璧」と評価されます。</p><p>次の例は、映画「フォードvsフェラーリ」のクエリ<em>「harrison ford」</em>です。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/052af7894506d7a765af156ba8e26ceec3559973-981x789.png" style="width: 100%;" /><p>同じ慣例に従い、クエリ、情報ニーズ、そしてドキュメントのメタデータが情報ニーズにどの程度一致しているかを見て、このクエリ/ドキュメントを判断します。</p><p>これは悪い結果です。この結果は、おそらく、クエリ用語の 1 つである「ford」がタイトルに一致していることを示していると思われます。しかし、ハリソン・フォードはこの映画でも他の役でも何の役も演じていない。したがって、この文書は「悪い」と評価され、これは当社の評価尺度では 0 に相当します。</p><p>3番目の例は、クエリ<em>「ベストアクション映画</em>」に対する映画「アクションジャクソン」です。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/247ab862fbc7435537709f8c96619cb331133d09-985x606.png" style="width: 100%;" /><p>これはアクション映画のように見えるので、情報ニーズは少なくとも部分的に満たされています。しかし、投票平均は10点満点中5.4点です。そのため、この映画はおそらく私たちのコレクションの中で最高のアクション映画ではないでしょう。したがって、審査員である私としては、この文書を「普通」と評価します。これは、当社の評価尺度では 1 です。</p><p>これらの例は、特に Quepid を使用してクエリ/ドキュメントのペアを評価するプロセスを、高レベルと全般にわたって示しています。</p><h2>人間評価者の最適な活用のヒント</h2><p>示された例を見ると、明確な判断に至るのは簡単そうに思えるかもしれません。しかし、信頼できる人間による評価プログラムを構築するのは簡単なことではありません。これは、データの品質を簡単に損なう可能性のある課題に満ちたプロセスです。</p><ul><li>人間の評価者は反復的な作業で疲れてしまうことがあります。</li><li>個人的な好みにより判断が歪む可能性があります。</li><li>分野の専門知識のレベルは裁判官によって異なります。</li><li>評価者は多くの場合、複数の責任を同時にこなします。</li><li>ドキュメントの認識された関連性は、クエリに対する実際の関連性と一致しない場合があります。</li></ul><p>これらの要因により、一貫性のない低品質の判断が生じる可能性があります。しかし、心配する必要はありません。これらの問題を最小限に抑え、より堅牢で信頼性の高い評価プロセスを構築するのに役立つ、実証済みのベスト プラクティスがあります。</p><ul><li><strong>一貫した評価:</strong>クエリ、情報ニーズ、ドキュメント メタデータを順番に確認します。</li><li><strong>ガイドラインを参照してください:</strong>一貫性を保つためにスコアリング ガイドラインを使用します。採点ガイドラインには、どのグレードをいつ適用するかの例など、審査プロセスを示すものが含まれます。最初の一連の判断の後に人間の評価者とチェックインすることは、困難なエッジケースや追加のサポートが必要な場所を知るための良い方法であることが証明されました。</li><li><strong>オプションを活用する:</strong>不明な場合は、「後で判断します」または「わかりません」を使用し、必要に応じて説明を加えます。</li><li><strong>休憩を取る:</strong>定期的に休憩を取ると判断力を維持するのに役立ちます。Quepid は、人間の評価者が一連の判定を終えるたびに紙吹雪を飛ばして、定期的な休憩を促します。</li></ul><p>これらの手順に従うことで、Quepid で判断リストを作成するための構造化された共同アプローチを確立し、検索関連性の最適化の取り組みの有効性を高めることができます。</p><h2>今後の見通し</h2><p>ここからどこへ行くのでしょうか?判断リストは、検索結果の品質を向上させるための基本的なステップの 1 つにすぎません。次の手順は次のとおりです。</p><h3>指標を計算して実験を始める</h3><p>判断リストが利用可能になると、その判断を活用して<a href="https://opensourceconnections.com/blog/2020/02/28/choosing-your-search-relevance-metric/">検索品質メトリック</a>を計算するのは自然な流れになります。Quepid は、判断が可能な場合、現在のケースに対して構成されたメトリックを自動的に計算します。メトリックは「スコアラー」として実装されており、サポートされているメトリックにお気に入りのメトリックが含まれていない場合は、独自のメトリックを提供できます。</p><p>ケース インターフェイスに移動し、 <strong>[スコアラーの選択]</strong>に移動して、 <em>DCG@10</em>を選択し、 <strong>[スコアラーの選択]</strong>をクリックして確認します。Quepid はクエリごとに DCG@10 を計算し、全体的なクエリの平均も計算して、ケースの検索結果の品質を定量化します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0ff3b9918342071744d681a43d542102e927abd3-1163x551.png" style="width: 100%;" /><p>検索結果の品質が定量化されたので、最初の実験を実行できます。実験は仮説を立てることから始まります。評価を行った後のスクリーンショットの 3 つのクエリを見ると、検索品質メトリックの点から見ると 3 つのクエリのパフォーマンスが大きく異なることが明らかです。 <em>「スターウォーズ」</em>のパフォーマンスはかなり良好で、 <em>「ハリソンフォード」</em>も悪くありませんが、最も大きな可能性を秘めているのは<em>「ベストアクション映画」です</em>。</p><p>このクエリを拡張すると、その結果が表示され、細かい詳細まで掘り下げて、ドキュメントが一致した理由やスコアに影響を与えるものを調べることができます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c45c6cae085a492198c0f8b7060a1a7204e3724e-1131x691.png" style="width: 100%;" /><p>「クエリの説明」をクリックして「解析」タブに入ると、クエリが<em>キャスト</em>、<em>概要</em>、<em>タイトルの</em>3つのフィールドを検索するDisjunctionMaxxQueryであることがわかります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4a1e1bb2a9cd28e9c48e0ba16357d17ed9d3a5cf-894x557.png" style="width: 100%;" /><p>通常、検索エンジニアとして私たちは、検索プラットフォームに関するドメイン固有の情報をある程度知っています。この場合、<em>ジャンル</em>フィールドがあることが分かります。これをクエリに追加して、検索品質が向上するかどうかを確認しましょう。</p><p><strong>ケース インターフェースで[関連性の調整]</strong> を選択すると開く<strong> クエリ サンドボックス</strong> を使用します。検索する<em>ジャンル</em>フィールドを追加して、これを探索してみましょう。</p><pre><code>{
  "query": {
    "multi_match": {
      "query": "#$query##",
      "type": "best_fields",
      "fields": [
        "title^10",
        "overview",
        "cast",
        "genres"
      ]
    }
  }
}</code></pre><p>「検索を再実行」をクリックしてください。そして結果を確認します。彼らは変わったのでしょうか？残念ながらそうではありません。現在、探索できるオプションは多数あり、基本的には Elasticsearch が提供するすべてのクエリ オプションがあります。</p><ul><li>ジャンルフィールドのフィールドウェイトを増やすことができます。</li><li>投票平均によってドキュメントをブーストする関数を追加できます。</li><li>ジャンルの一致が強い場合にのみ投票平均によってドキュメントをブーストする、より複雑なクエリを作成することもできます。</li><li>…</li></ul><p>これらすべてのオプションを Quepid で検討することの最大の利点は、改善しようとしている 1 つのクエリだけでなく、この場合のすべてのクエリへの影響を定量化できる点です。これにより、他のクエリの検索結果の品質を犠牲にして、パフォーマンスの低いクエリを改善することが防止されます。リスクなしで迅速かつ安価に反復して仮説の価値を検証できるため、オフライン実験はすべての検索チームの基本的な機能になります。</p><h3>評価者間の信頼性を測定する</h3><p>タスクの説明、情報のニーズ、そして Quepid が提供するような人間の評価者インターフェースがあっても、人間の評価者の間で意見の相違が生じる可能性があります。</p><p>意見の相違自体は悪いことではありません。むしろその逆です。意見の相違を測定することで、取り組むべき問題が明らかになることがあります。関連性は主観的である可能性があり、クエリはあいまいであり、データは不完全または不正確である可能性があります。<a href="https://en.wikipedia.org/wiki/Fleiss%27_kappa">Fleiss の Kappa</a>は評価者間の一致を測る統計的尺度であり、Quepid には使用できるサンプルノートブックがあります。これを見つけるには、最上位のナビゲーションで[ノートブック]<strong> </strong>を選択し、 例の<strong> フォルダーにあるノートブック Fleiss Kappa.ipynb</strong> <strong></strong>を選択します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f0669ae96371368ef4d84bb28669560ef09d755c-624x61.png" style="width: 100%;" /><h2>まとめ</h2><p>Quepid は、最も複雑な検索関連性の課題にも対処できるようにし、進化し続けています。<a href="https://github.com/o19s/quepid/blob/main/CHANGELOG.md#800----2024-02-14">バージョン 8 では、Quepid は AI 生成の判断をサポートしており</a>、これは判断生成プロセスを拡大したいチームにとって特に便利です。</p><p>Quepid ワークフローを使用すると、スケーラブルな判断リストを効率的に作成できるため、最終的にはユーザーのニーズを真に満たす検索結果が得られます。判断リストを確立すると、検索の関連性を測定し、改善を繰り返し、ユーザー エクスペリエンスを向上させるための強固な基盤が得られます。</p><p>先に進む際には、関連性の調整は継続的なプロセスであることを忘れないでください。判断リストを使用すると進捗状況を体系的に評価できますが、実験、メトリック分析、反復的な改善と組み合わせると最も強力になります。</p><h2>参考資料</h2><ul><li>Quepid ドキュメント:<ul><li><a href="https://quepid-docs.dev.o19s.com/2/quepid/32/relevancy-is-a-team-sport">関連性はチームスポーツ</a></li><li><a href="https://quepid-docs.dev.o19s.com/2/quepid/18/quepid-for-human-raters">人間の評価者のためのQuepid</a></li><li><a href="https://quepid-docs.dev.o19s.com/2/quepid/49/how-to-connect-quepid-to-elastic-cloud">QuepidをElastic Cloudに接続する方法</a></li></ul></li><li><a href="https://github.com/o19s/quepid">Quepid Githubリポジトリ</a></li><li><a href="https://opensourceconnections.com/blog/2020/07/07/meet-pete-the-e-commerce-search-product-manager/">電子商取引の検索を改善するブログシリーズ「Pete」</a></li><li><a href="https://opensourceconnections.com/slack">関連性Slack</a> ：#quepidチャンネルに参加する</li></ul><p><strong><a href="https://opensourceconnections.com/">Open Source Connections</a></strong> と提携して 検索機能と AI 機能を変革し、チームが継続的に進化できるようにします。当社の実績は世界中に広がっており、クライアントは一貫して検索品質、チーム能力、ビジネス パフォーマンスの劇的な改善を達成しています。詳細については、<a href="https://opensourceconnections.com/contact/">今すぐお問い合わせください</a>。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearchインデックスのフィールドを表示する]]></title>
            <description><![CDATA[Elasticsearch インデックス内のフィールドを表示するためのテクニックを探ります。
]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/displaying-fields-in-an-elasticsearch-index</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/displaying-fields-in-an-elasticsearch-index</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Kofi Bartlett]]></dc:creator>
            <pubDate>Mon, 26 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fa386d7bbaeab6855e62897ace8d7dca91a060b4-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>この記事では、Elasticsearch インデックスでフィールドを表示する方法について説明します。これは、データの構造を理解し、特定のフィールドを識別し、問題をトラブルシューティングするのに役立ちます。以下のトピックを取り上げます。</p><ol><li><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#1.-using-the--mapping-api-to-retrieve-field-information"><code>_mapping</code></a> API<a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#1.-using-the--mapping-api-to-retrieve-field-information">を使用してフィールド情報を取得する</a></li><li><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#2.-using-the--search-api-to-display-field-values"><code>_search</code></a> API<a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#2.-using-the--search-api-to-display-field-values">を使用してフィールド値を表示する</a></li><li><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#3.-filtering-fields-using-the-fields-parameter">パラメータ を使用してフィールドをフィルタリングする<code>fields</code></a></li><li><a href="https://www.elastic.co/search-labs/blog/displaying-fields-in-an-elasticsearch-index#4.-displaying-nested-fields">ネストされたフィールドの表示</a></li></ol><h2>1. _mapping APIを使用してフィールド情報を取得する</h2><p><code>_mapping</code> API を使用すると、1 つまたは複数の<a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-index/">インデックス</a>のマッピング定義を取得できます。これには、フィールド、そのデータ型、およびその他のプロパティに関する情報が含まれます。特定のインデックスのマッピングを取得するには、次のリクエストを使用します。</p><pre><code>GET /<index_name>/_mapping</code></pre><p>たとえば、 <code>my_index</code>という名前のインデックスがある場合、次のリクエストでそのマッピングを取得できます。</p><pre><code>GET /my_index/_mapping</code></pre><p>応答には、フィールドとそのプロパティに関する情報を含むインデックスのマッピング定義が含まれます。</p><p>特定のフィールドのマッピングを取得することもできます。これは、マッピングが非常に大きく、特定のフィールドにのみ焦点を当てたい場合に便利です。特定のフィールドのマッピングを取得するには、次のリクエストを使用します。</p><pre><code>GET /my_index/_mapping/field/my_field</code></pre><p>次のリクエストのように、フィールド名をコンマで区切ることで、複数のフィールドのマッピングを取得することもできます。</p><pre><code>GET /my_index/_mapping/field/my_field_1,my_field_2,my_field_3</code></pre><h2>2. _search APIを使用してフィールド値を表示する</h2><p>Elasticsearch インデックス内のフィールドの値を表示するには、 <code>_search</code> API を使用できます。デフォルトでは、 <code>_search</code> API は、インデックスが作成された元の JSON ドキュメントを含む<code>_source</code>フィールドを返します。特定のフィールドのみを表示するには、検索リクエストで<code>_source</code>パラメータを使用できます。</p><p>以下は、 <code>my_index</code>インデックス内のドキュメントの<code>title</code>フィールドと<code>author</code>フィールドの値を返す検索要求の例です。</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "_source": ["title", "author"]
}</code></pre><p>この例では、 <code>_source</code>パラメータは返されるフィールドを指定します。</p><h2>3. フィールドパラメータを使用してフィールドをフィルタリングする</h2><p><code>fields</code>パラメータを使用して、検索応答で返されるフィールドをフィルタリングすることもできます。これは、特定のフィールドのみが必要で、応答のサイズを縮小したい場合に役立ちます。<code>fields</code>パラメータは、フィールド名またはワイルドカード パターンの配列を受け入れます。</p><p>たとえば、 <code>my_index</code>インデックス内のドキュメントの<code>title</code>フィールドと<code>author</code>フィールドのみを返すには、次の検索リクエストを使用できます。</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author"],
  "_source": false
}</code></pre><p>ソース ドキュメントを返さないように、 <code>_source</code>パラメータは false に設定されていることに注意してください。</p><p><code>text</code>データ型のすべてのフィールドを返すには、次のようなワイルドカード パターンを使用できます。</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["*.text"],
  "_source": false
}</code></pre><h2>4. ネストされたフィールドの表示</h2><p>インデックスにネストされたフィールドが含まれている場合は、ドット表記を使用して、 <code>fields</code>パラメータでネストされたフィールド パスを指定できます。たとえば、 <code>address.city</code>という名前のネストされたフィールドがある場合、次のように検索応答に含めることができます。</p><pre><code>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author", "address.city"],
  "_source": false
}</code></pre><p>この例では、検索応答には<code>title</code> 、 <code>author</code> 、および<code>address.city</code>フィールドの値が含まれます。</p><h2>まとめ</h2><p>結論として、Elasticsearch インデックス内のフィールドを表示するには、 <code>_mapping</code> API を使用してフィールド情報を取得し、 <code>_search</code> API を使用してフィールド値を表示します。<code>_source</code>または<code>fields</code>パラメータを使用して検索応答で返されるフィールドをフィルタリングし、ドット表記を使用してネストされたフィールドを表示できます。これらの手法は、データの構造を理解し、特定のフィールドを識別し、問題のトラブルシューティングを行うのに役立ちます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Spring AIとElasticsearchをベクターデータベースとして]]></title>
            <description><![CDATA[Spring AIとElasticsearchを使用して本番環境に対応したRAGアプリを構築し、ベクトルデータベースを使用してLLMと独自データを統合する方法をご覧ください。
]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/spring-ai-elasticsearch-application</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/spring-ai-elasticsearch-application</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Philipp Krenn, Laura Trotta]]></dc:creator>
            <pubDate>Tue, 20 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0771fb5b3d9234697cb868cd7d9d1b840000bf29-1280x720.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><strong>Spring AI</strong>は現在一般公開されており、最初の<a href="https://spring.io/blog/2025/05/20/spring-ai-1-0-GA-released">安定版リリース 1.0</a>が<a href="https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-core">Maven Central</a>からダウンロードできます。早速、お気に入りの<a href="https://www.elastic.co/what-is/large-language-models">LLM</a>とお気に入りの<a href="https://www.elastic.co/elasticsearch/vector-database">ベクター データベース</a>を使用して、完全な AI アプリケーションを構築してみましょう。または、最終的なアプリケーションを使用して<a href="https://github.com/xeraa/rag-with-java-springai-elasticsearch">リポジトリ</a>に直接アクセスします。</p><a href="https://www.youtube.com/watch/ms2xRO8B1YU">Watch video on YouTube</a><h2>Spring AI とは何ですか?</h2><p>AI 分野の急速な進歩の影響を受けたかなりの開発期間を経て、Java での AI エンジニアリングのための包括的なソリューションである<strong>Spring AI 1.0 が</strong>リリースされました。このリリースには、AI エンジニアにとって重要な新機能が多数含まれています。</p><p>Java と Spring は、この AI の波に乗るのに最適な位置にあります。数多くの企業が Spring Boot 上で業務を遂行しており、既存の業務に AI を組み込むのが非常に簡単になります。基本的に、あまり手間をかけずにビジネス ロジックとデータを AI モデルに直接リンクできます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/328d7c51e1c145e94ea1e73ee9ff91836d3b180e-1600x773.png" style="width: 100%;" /><p>Spring AI は、次のような<a href="https://docs.spring.io/spring-ai/reference/api/index.html">さまざまな AI モデルとテクノロジー</a>のサポートを提供します。</p><ul><li><strong>画像モデル</strong>: テキストプロンプトに基づいて画像を生成します。</li><li><strong>転写モデル</strong>: オーディオ ソースを取得してテキストに変換します。</li><li><strong>埋め込みモデル:</strong>任意のデータを、意味的類似性検索に最適化されたデータ型である<a href="https://www.elastic.co/what-is/vector-embedding">ベクトル</a>に変換します。</li><li><strong>チャットモデル:</strong>これら<strong> </strong>馴染みがあるはずです！あなたもきっとどこかで簡単な会話をしたことがあるはずです。</li></ul><p>チャット モデルは AI 分野で最も注目を集めているようですが、その通り、素晴らしいものです。文書の修正や詩の作成を手伝ってもらうこともできます。（ただ、まだジョークを言うように頼まないでください。）素晴らしいですが、いくつか問題もあります。</p><h2>AIの課題に対するSpring AIソリューション</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/2ebd68a90ebc73847df6ef7325936d4d06b35c8c-1600x900.jpg" style="width: 100%;" /><p>これらの問題のいくつかと Spring AI での解決策を見てみましょう。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">問題</td><td style="border: 1px solid #ccc; padding: 4px;">ソリューション</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">一貫性</td><td style="border: 1px solid #ccc; padding: 4px;">チャットモデルはオープンマインドで、気が散りやすい</td><td style="border: 1px solid #ccc; padding: 4px;">全体的な形状と構造を制御するためのシステムプロンプトを与えることができます</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">メモリ</td><td style="border: 1px solid #ccc; padding: 4px;">AIモデルには記憶がないので、特定のユーザーからのメッセージを別のユーザーと関連付けることはできない。</td><td style="border: 1px solid #ccc; padding: 4px;">会話の関連部分を保存するメモリシステムを与えることができます</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">分離</td><td style="border: 1px solid #ccc; padding: 4px;">AIモデルは隔離された小さなサンドボックス内に存在しますが、ツール（必要だと判断したときに呼び出せる機能）へのアクセスを与えると、本当に素晴らしいことが可能になります。</td><td style="border: 1px solid #ccc; padding: 4px;">Spring AI はツール呼び出しをサポートしており、これにより AI モデルに環境内のツールを通知し、そのツールを呼び出すように要求できます。この複数ターンのインタラクションはすべて透過的に処理されます</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">個人データ</td><td style="border: 1px solid #ccc; padding: 4px;">AI モデルはスマートですが、全知ではありません。彼らはあなたの独自のデータベースに何が入っているか知りませんし、あなたもそれを知りたいとは思わないでしょう。</td><td style="border: 1px solid #ccc; padding: 4px;">基本的には、モデルが質問を確認する前に、強力な文字列連結演算子を使用してリクエストにテキストを挿入することで、プロンプトを詰め込んで応答を通知する必要があります。背景情報です。何を送信して何を送信しないかをどのように決定しますか?ベクター ストアを使用して、関連するデータのみを選択し、それを送信します。これは検索拡張生成、またはRAGと呼ばれます。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">幻覚</td><td style="border: 1px solid #ccc; padding: 4px;">AI チャット モデルは、チャットするのが大好きです。そして時には、自信過剰になり、嘘をつくこともある。</td><td style="border: 1px solid #ccc; padding: 4px;">妥当な結果を確認するには、評価（あるモデルを使用して別のモデルの出力を検証する）を使用する必要があります。</td>
                    </tr>
              </tbody>
            </table>
          <p></p><p>そしてもちろん、AI アプリケーションは孤立したものではありません。今日、最新の AI システムとサービスは、他のシステムやサービスと統合すると最も効果的に機能します。<strong><a href="https://modelcontextprotocol.io/introduction">モデルコンテキストプロトコル</a> </strong>(MCP) を使用すると、記述言語に関係なく、AI アプリケーションを他の MCP ベースのサービスに接続できるようになります。これらすべてを、より大きな目標に向かって進む<strong>エージェント</strong>ワークフローに組み込むことができます。</p><p>一番良かった点は？Spring Boot 開発者なら誰もが期待する使い慣れたイディオムと抽象化を基盤として、これらすべてを実現できます。基本的にすべてに対する便利なスターター依存関係が<a href="https://start.spring.io"><strong>Spring Initializr</strong></a>で利用できます<strong>。</strong></p><p>Spring AI は、既知の期待通りの、設定よりも規約を重視するセットアップを実現する便利な Spring Boot 自動設定を提供します。また、Spring AI は、Spring Boot の Actuator と Micrometer プロジェクトによって可観測性をサポートしています。GraalVM や仮想スレッドとも連携し、スケーラブルな超高速かつ効率的な AI アプリケーションを構築できます。</p><h2>Elasticsearchを選ぶ理由</h2><p>Elasticsearch は全文検索エンジンです。おそらくご存知でしょう。では、なぜこのプロジェクトでそれを使用するのでしょうか?まあ、ベクターストア<em>でも</em>あります！データが全文の隣に保存されるので、非常に便利です。その他の注目すべき利点:</p><ul><li>セットアップは超簡単</li><li>オープンソース</li><li>水平方向に拡張可能</li><li>組織の自由形式のデータのほとんどは、すでにElasticsearchクラスターに保存されているでしょう。</li><li>完全な検索エンジン機能を搭載</li><li><a href="https://docs.spring.io/spring-ai/reference/api/vectordbs/elasticsearch.html">Spring AI に完全に統合されています</a>!</li></ul><p>すべてを考慮すると、Elasticsearch は優れたベクター ストアに必要なすべての条件を満たしているので、セットアップしてアプリケーションの構築を開始しましょう。</p><h2>Elasticsearchを使い始める</h2><p>Elasticsearch と Kibana (データベースにホストされているデータを操作するために使用する UI コンソール) の両方が必要になります。</p><p>Docker イメージと<a href="http://elastic.co">Elastic.co ホームページの</a>おかげで、ローカル マシンですべてを試すことができます。そこに移動して、下にスクロールして<code>curl</code>コマンドを見つけ、それを実行してシェルに直接パイプします。</p><pre><code> curl -fsSL https://elastic.co/start-local | sh 
  ______ _           _   _      
 |  ____| |         | | (_)     
 | |__  | | __ _ ___| |_ _  ___ 
 |  __| | |/ _` / __| __| |/ __|
 | |____| | (_| \__ \ |_| | (__ 
 |______|_|\__,_|___/\__|_|\___|
-------------------------------------------------
🚀 Run Elasticsearch and Kibana for local testing
-------------------------------------------------
ℹ️  Do not use this script in a production environment
⌛️ Setting up Elasticsearch and Kibana v9.0.0...
- Generated random passwords
- Created the elastic-start-local folder containing the files:
  - .env, with settings
  - docker-compose.yml, for Docker services
  - start/stop/uninstall commands
- Running docker compose up --wait
[+] Running 25/26
 ✔ kibana_settings Pulled                                                 16.7s 
 ✔ kibana Pulled                                                          26.8s 
 ✔ elasticsearch Pulled                                                   17.4s                                                                     
[+] Running 6/6
 ✔ Network elastic-start-local_default             Created                 0.0s 
 ✔ Volume "elastic-start-local_dev-elasticsearch"  Created                 0.0s 
 ✔ Volume "elastic-start-local_dev-kibana"         Created                 0.0s 
 ✔ Container es-local-dev                          Healthy                12.9s 
 ✔ Container kibana_settings                       Exited                 11.9s 
 ✔ Container kibana-local-dev                      Healthy                21.8s 
🎉 Congrats, Elasticsearch and Kibana are installed and running in Docker!
🌐 Open your browser at http://localhost:5601
   Username: elastic
   Password: w1GB15uQ
🔌 Elasticsearch API endpoint: http://localhost:9200
🔑 API key: SERqaGlKWUJLNVJDODc1UGxjLWE6WFdxSTNvMU5SbVc5NDlKMEhpMzJmZw==
Learn more at https://github.com/elastic/start-local
➜  ~ </code></pre><p>これにより、Elasticsearch と Kibana の Docker イメージがプルされて構成され、数分後には接続資格情報も揃ってローカル マシン上で稼働するようになります。</p><p>Elasticsearch インスタンスと対話するために使用できる 2 つの異なる URL もあります。プロンプトの指示に従って、ブラウザで<a href="http://localhost:5601">http://localhost:5601</a>にアクセスします。</p><p>コンソールに出力されるユーザー名<code>elastic</code>とパスワードにも注意してください。これらはログイン時に必要になります (上記の出力例では、それぞれ<code>elastic</code>と<code>w1GB15uQ</code>です)。</p><p></p><h2>アプリをまとめる</h2><p><a href="https://start.spring.io">Spring Initializr</a>ページに移動し、次の依存関係を持つ新しい Spring AI プロジェクトを生成します。</p><ul><li><code>Elasticsearch Vector Store</code></li><li><code>Spring Boot Actuator</code></li><li><code>GraalVM</code></li><li><code>OpenAI</code></li><li><code>Web</code></li></ul><p>必ず最新の Java バージョン (理想的には、この記事の執筆時点では Java 24 以降) と好みのビルド ツールを選択してください。この例では Apache Maven を使用しています。</p><p><code>Generate</code>をクリックし、プロジェクトを解凍して、選択した IDE にインポートします。(IntelliJ IDEA を使用しています。)</p><p>まず最初に、Spring Boot アプリケーションの接続詳細を指定しましょう。<code>application.properties,</code>に次のように記述します。</p><pre><code>spring.elasticsearch.uris=http://localhost:9200
spring.elasticsearch.username=elastic
spring.elasticsearch.password=w1GB15uQ</code></pre><p>また、データ構造に関して Elasticsearch 側で必要なものをすべて初期化するための Spring AI のベクトル ストア機能も使用するので、次のように指定します。</p><pre><code>spring.ai.vectorstore.elasticsearch.initialize-schema=true</code></pre><p>このデモでは<strong>OpenAI</strong> 、具体的には<strong>埋め込みモデル</strong>と<strong>チャット モデル</strong>を使用します ( <a href="https://docs.spring.io/spring-ai/reference/api/embeddings.html#available-implementations">Spring AI がサポートしている</a>限り、お好みのサービスを自由に使用してください)。</p><p>埋め込みモデルは、データを Elasticsearch に格納する前にデータの埋め込みを作成するために必要です。OpenAI が動作するには、 <code>API key</code>を指定する必要があります。</p><pre><code>spring.ai.openai.api-key=...</code></pre><p>ソース コード内に資格情報を保存することを避けるために、 <code>SPRING_AI_OPENAI_API_KEY</code>のような環境変数として定義することができます。</p><p>ファイルをアップロードするので、サーブレット コンテナにアップロードできるデータの量を必ずカスタマイズしてください。</p><pre><code>spring.servlet.multipart.max-file-size=20MB
spring.servlet.multipart.max-request-size=20MB</code></pre><p>もうすぐ到着です！コードの記述に進む前に、これがどのように機能するかをプレビューしてみましょう。</p><p>私たちのマシンでは、<a href="https://images-cdn.fantasyflightgames.com/filer_public/9f/aa/9faa23a3-9f71-4c77-865f-bba4aac8a258/runewars-revised-_rulebook.pdf">次のファイル</a>(ボードゲームのルールのリスト) をダウンロードし、名前を<code>test.pdf</code>に変更して<code>~/Downloads/test.pdf</code>に配置しました。</p><p>ファイルは<code>/rag/ingest</code>エンドポイントに送信されます (パスをローカル設定に応じて置き換えてください)。</p><pre><code>http --form POST http://localhost:8080/rag/ingest path@/Users/jlong/Downloads/test.pdf</code></pre><p>数秒かかる場合があります…</p><p>舞台裏では、データが OpenAI に送信され、データの埋め込みが作成されます。その後、そのデータはベクトルと元のテキストの両方で Elasticsearch に書き込まれます。</p><p>そのデータと、そこに含まれるすべての埋め込みによって、魔法が起こるのです。その後、 <code>VectorStore</code>インターフェースを使用して Elasticsearch をクエリできます。</p><p>完全なフローは次のようになります。</p><ul><li>HTTP クライアントは、選択した PDF を Spring アプリケーションにアップロードします。</li><li>Spring AI は PDF からテキストを抽出し、各ページを 800 文字のチャンクに分割します。</li><li>OpenAI は各チャンクのベクトル表現を生成します。</li><li>チャンク化されたテキストと埋め込みの両方が Elasticsearch に保存されます。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9734adb2d7128e61c515d5855dfad6d3a326a4a1-1454x706.png" style="width: 100%;" /><p>最後に、クエリを発行します。</p><pre><code>http :8080/rag/query question=="where do you place the reward card after obtaining it?" </code></pre><p>そして、適切な答えが得られます。</p><pre><code>After obtaining a Reward card, you place it facedown under the Hero card of the hero who received it.
Found at page: 28 of the manual</code></pre><p>いいですね！これはどうやって動くんですか？</p><ul><li>HTTP クライアントは Spring アプリケーションに質問を送信します。</li><li>Spring AI は OpenAI から質問のベクトル表現を取得します。</li><li>この埋め込みにより、保存された Elasticsearch チャンク内で類似のドキュメントが検索され、最も類似したドキュメントが取得されます。</li><li>次に、Spring AI は質問と取得したコンテキストを OpenAI に送信し、LLM 回答を生成します。</li><li>最後に、生成された回答と取得されたコンテキストへの参照を返します。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3799de6e8cb13ce49b9e136cfe593263030231a8-1464x1050.png" style="width: 100%;" /><p>実際にどのように動作するかを確認するために、Java コードを調べてみましょう。</p><p>まず、 <strong>Main</strong>クラス: これは、あらゆる Spring Boot アプリケーションの標準的なメイン クラスです。</p><pre><code>@SpringBootApplication
public class DemoApplication {
 	public static void main(String[] args) { 
     		SpringApplication.run(DemoApplication.class, args);
 	}
}</code></pre><p>そこには何も見るものはありません。次に進みましょう…</p><p>次は、基本的な HTTP コントローラーです。</p><pre><code>@RestController
class RagController {

   private final RagService rag;

   RagController(RagService rag) {
       this.rag = rag;
   }

   @PostMapping("/rag/ingest")
   ResponseEntity<?> ingestPDF(@RequestBody MultipartFile path) {
       rag.ingest(path.getResource());
       return ResponseEntity.ok().body("Done!");
   }

   @GetMapping("/rag/query")
   ResponseEntity<?> query(@RequestParam String question) {
       String response = rag.directRag(question);
       return ResponseEntity.ok().body(response);
   }
}</code></pre><p>コントローラーは、ファイルの取り込みと Elasticsearch ベクター ストアへの書き込みを処理するために構築したサービスを呼び出し、同じベクター ストアに対するクエリを容易にするだけです。</p><p>サービスを見てみましょう:</p><pre><code>@Service
class RagService {

   private final ElasticsearchVectorStore vectorStore;

   private final ChatClient ai;

   RagService(ElasticsearchVectorStore vectorStore, ChatClient.Builder clientBuilder) {
       this.vectorStore = vectorStore;
       this.ai = clientBuilder.build();
   }

   void ingest(Resource path) {
       PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(path);
       List<Document> batch = new TokenTextSplitter().apply(pdfReader.read());
       vectorStore.add(batch);
   }

  // TBD
}</code></pre><p>このコードはすべての取り込みを処理します。バイトを囲むコンテナーである Spring Framework <code>Resource</code>が指定されると、Spring AI の<code>PagePdfDocumentReader</code>を使用して PDF データ ( <code>.PDF</code>ファイルであると想定されます。任意の入力を受け入れる前に必ず検証してください) を読み取り、次に Spring AI の<code>TokenTextSplitter</code>を使用してトークン化し、最後に結果の<code>List&lt;Document&gt;</code>を<code>VectorStore</code>実装<code>ElasticsearchVectorStore</code>に追加します。</p><p>Kibana を使用してこれを確認できます。ファイルを<code>/rag/ingest</code>エンドポイントに送信した後、ブラウザで<code>localhost:5601</code>を開き、左側のサイド メニューで<code>Dev Tools</code>に移動します。ここでクエリを発行して、Elasticsearch インスタンス内のデータと対話できます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c85e522f02f8b2da7462cd428dc7e952c9692542-1600x1040.png" style="width: 100%;" /><p>次のようなクエリを発行します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/00974a176cbce11e70fcab24fb4b3f9c6e205982-1600x1040.png" style="width: 100%;" /><p>さて、ここからが面白いところです。ユーザーのクエリに応じて、そのデータをどうやって再び取り出すのでしょうか?</p><p>以下は、 <code>directRag</code>と呼ばれるメソッドでのクエリの実装の最初の例です。</p><pre><code>String directRag(String question) {
   // Query the vector store for documents related to the question
   List<Document> vectorStoreResult =
           vectorStore.doSimilaritySearch(SearchRequest.builder().query(question).topK(5)
                   .similarityThreshold(0.7).build());

   // Merging the documents into a single string
   String documents = vectorStoreResult.stream()
           .map(Document::getText)
           .collect(Collectors.joining(System.lineSeparator()));

   // Exit if the vector search didn't find any results
   if (documents.isEmpty()) {
       return "No relevant context found. Please change your question.";
   }

   // Setting the prompt with the context
   String prompt = """
           You're assisting with providing the rules of the tabletop game Runewars.
           Use the information from the DOCUMENTS section to provide accurate answers to the
           question in the QUESTION section.
           If unsure, simply state that you don't know.
          
           DOCUMENTS:
           """ + documents
           + """
           QUESTION:
           """ + question;


   // Calling the chat model with the question
   String response = ai
           .prompt()
           .user(prompt)
           .call()
           .content();

   return response +
           System.lineSeparator() +
           "Found at page: " +
           // Retrieving the first ranked page number from the document metadata
           vectorStoreResult.getFirst().getMetadata().get(PagePdfDocumentReader.METADATA_START_PAGE_NUMBER) +
           " of the manual";

}</code></pre><p>コードは非常に単純ですが、複数のステップに分解してみましょう。</p><ol><li>類似検索を実行するには、 <code>VectorStore</code>を使用します。</li><li>すべての結果が与えられたら、基礎となる Spring AI <code>Document</code>を取得し、そのテキストを抽出して、すべてを 1 つの結果に連結します。</li><li><code>VectorStore</code>からの結果を、その結果の処理方法をモデルに指示するプロンプトとユーザーからの質問とともにモデルに送信します。応答を待って返します。</li></ol><p></p><p>これは<strong>RAG</strong> (検索拡張生成) です。これは、ベクトル ストアのデータを使用して、モデルによって実行される処理と分析に情報を提供するという考え方です。やり方がわかったので、もうそんなことをしなくて済むことを祈ります!とにかく、これは違います。Spring AI の<a href="https://docs.spring.io/spring-ai/reference/api/advisors.html">アドバイザーは</a>、このプロセスをさらに簡素化するために存在します。</p><p>アドバイザーを使用すると、アプリケーションとベクター ストアの間に抽象化レイヤーを提供するだけでなく、特定のモデルへのリクエストを前処理および後処理できます。ビルドに次の依存関係を追加します。<br/></p><pre><code><dependency>
   <groupId>org.springframework.ai</groupId>
   <artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency></code></pre><p>クラスに<code>advisedRag(String question)</code>という別のメソッドを追加します。</p><pre><code>String advisedRag(String question) {
   return this.ai
           .prompt()
           .user(question)
           .advisors(new QuestionAnswerAdvisor(vectorStore))
           .call()
           .content();
}</code></pre><p>すべての RAG パターン ロジックは<code>QuestionAnswerAdvisor</code>にカプセル化されます。その他は、 <code>ChatModel</code>へのリクエストと同じです。ニース！</p><p><a href="https://github.com/xeraa/rag-with-java-springai-elasticsearch">完全なコードは GitHub から入手</a>できます。</p><h2>まとめ</h2><p>このデモでは、Docker イメージを使用してすべてをローカル マシン上で実行しましたが、ここでの目標は、本番環境に適した AI システムとサービスを構築することです。それを実現するためにできることはいくつかあります。</p><p>まず、トークンの消費を監視するために<a href="https://docs.spring.io/spring-boot/reference/actuator/index.html#actuator">Spring Boot Actuator を</a>追加できます。トークンは、モデルに対する特定のリクエストの複雑さ (場合によってはドルとセント) のコストを表す代理です。</p><p>クラスパスにはすでに Spring Boot Actuator があるので、次のプロパティを指定するだけで、すべてのメトリック (素晴らしい<a href="http://micrometer.io">Micrometer.io</a>プロジェクトによってキャプチャされたもの) が表示されます。</p><pre><code>management.endpoints.web.exposure.include=*</code></pre><p>アプリケーションを再起動してください。クエリを作成して、 <a href="http://localhost:8080/actuator/metrics">http://localhost:8080/actuator/metrics</a>にアクセスします。「 <code>token</code> 」を検索すると、アプリケーションで使用されているトークンに関する情報が表示されます。これに注意してください。もちろん、Micrometer の<a href="https://docs.micrometer.io/micrometer/reference/implementations/elastic.html">Elasticsearch 統合を</a>使用してこれらのメトリックをプッシュし、Elasticsearch を好みの時系列データベースとして機能させることもできます。</p><p>次に、Elasticsearch などのデータストア、OpenAI、またはその他のネットワーク サービスにリクエストを送信するたびに IO が実行され、多くの場合、その IO によって実行中のスレッドがブロックされることを考慮する必要があります。Java 21 以降には、スケーラビリティを大幅に向上させる非ブロッキング<strong>仮想スレッド</strong>が搭載されています。有効にするには以下を実行します:<br/></p><pre><code>spring.threads.virtual.enabled=true</code></pre><p>そして最後に、アプリケーションとデータを、それが繁栄し、拡張できる場所でホストすることが必要になります。アプリケーションをどこで実行するかについてはおそらくすでにお考えだと思いますが、データはどこにホストするのでしょうか?<a href="https://cloud.elastic.co/">Elastic Cloud</a>をお勧めしますか?安全、プライベート、スケーラブル、そして機能が満載です。私たちのお気に入りの部分は？ご希望の場合は、Elastic がポケベルを装着し、お客様がポケベルを装着する Serverless エディションを入手できます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[JavaScript で Elasticsearch を正しく使う方法、パート II]]></title>
            <description><![CDATA[本番環境でのベストプラクティスと、コーディングエラーを減らすためにServerless環境でElasticsearch Node.jsクライアントを実行する方法についてご覧ください。 ]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/how-to-use-elasticsearch-in-javascript-part-ii</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/how-to-use-elasticsearch-in-javascript-part-ii</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
            <pubDate>Mon, 19 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/70fb0ff949f6db9ac9b8a28ecb4329ab915ebf46-720x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>これは、JavaScript での Elasticsearch シリーズの第 2 部です。<a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i">最初の部分では、</a>環境を正しくセットアップし、Node.js クライアントを構成し、データをインデックスして検索する方法を学びました。この第 2 部では、実稼働のベスト プラクティスを実装し、サーバーレス環境で Elasticsearch <a href="http://node.js">Node.js</a>クライアントを実行する方法を学習します。</p><p>以下を確認します:</p><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#production-best-practices">制作のベストプラクティス</a><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#error-handling">エラー処理</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#testing">テスト</a></li></ul></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#serverless-environments">サーバーレス環境</a><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#running-the-client-on-elastic-serverless">Elastic Serverlessでクライアントを実行する</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-ii#running-the-client-on-function-as-a-service-environment">Function-as-a-Service 環境でクライアントを実行する</a></li></ul></li></ul><p><em><a href="https://github.com/Delacrobix/JS-client-best-practices_article"><strong>ここで</strong></a> 例付きのソースコードを確認できます<strong> 。</strong></em></p><h2>制作のベストプラクティス</h2><h3>Elasticsearchにおけるエラー処理。</h3><p>Node.js の Elasticsearch クライアントの便利な機能は、Elasticsearch で発生する可能性のあるエラーのオブジェクトを公開し、さまざまな方法で検証して処理できることです。</p><p><a href="https://www.elastic.co/docs/reference/elasticsearch/clients/javascript/connecting#client-error-handling">すべてを表示する</a>には、次のコマンドを実行します。</p><pre><code>const { errors } = require('@elastic/elasticsearch')
console.log(errors)</code></pre><p>検索の例に戻り、起こりうるエラーのいくつかを処理してみましょう。</p><pre><code>app.get("/search/lexic", async (req, res) => {
 ....
  } catch (error) {
    if (error instanceof errors.ResponseError) {
      let errorMessage =
        "Response error!, query malformed or server down, contact the administrator!";

      if (error.body.error.type === "parsing_exception") {
        errorMessage = "Query malformed, make sure mappings are set correctly";
      }

      res.status(error.meta.statusCode).json({
        erroStatus: error.meta.statusCode,
        success: false,
        results: null,
        error: errorMessage,
      });
    }

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});</code></pre><p><code>ResponseError</code> 特に、回答が<code>4xx</code>または<code>5xx</code>場合に発生します。これは、要求が正しくないか、サーバーが利用できないことを意味します。</p><p>このタイプのエラーは<strong>、テキストタイプのフィールドで用語クエリを実行するなど、間違ったクエリを生成することでテストできます。</strong></p><p>デフォルトエラー:</p><pre><code> {
    "success": false,
    "results": null,
    "error": "parsing_exception\n\tRoot causes:\n\t\tparsing_exception: [terms] query does not support [visit_details]"
}</code></pre><p>カスタマイズされたエラー: </p><pre><code>{
    "erroStatus": 400,
    "success": false,
    "results": null,
    "error": "Response error!, query malformed or server down; contact the administrator!"
}</code></pre><p>各タイプのエラーを特定の方法でキャプチャして処理することもできます。たとえば、 <code>TimeoutError</code>に再試行ロジックを追加できます。</p><pre><code>app.get("/search/semantic", async (req, res) => {
    try {
  ...
  } catch (error) {
    if (error instanceof errors.TimeoutError) {


     // Retry logic...

      res.status(error.meta.statusCode).json({
        erroStatus: error.meta.statusCode,
        success: false,
        results: null,
        error:
          "The request took more than 10s after 3 retries. Try again later.",
      });
    }
  }
});</code></pre><h3>テスト</h3><p>テストはアプリの安定性を保証する上で重要です。Elasticsearch から分離された方法でコードをテストするには、クラスターを作成するときにライブラリ<a href="https://github.com/elastic/elasticsearch-js-mock">elasticsearch-js-mock を</a>使用できます。</p><p>このライブラリを使用すると、実際のクライアントと非常によく似たクライアントをインスタンス化できますが、クライアントの HTTP レイヤーのみをモックのレイヤーに置き換え、残りは元のレイヤーと同じにすることで、構成に応答します。</p><p>自動テスト用に、モック ライブラリと<a href="https://github.com/avajs/ava">AVA</a>をインストールします。</p><p><code>npm install @elastic/elasticsearch-mock</code></p><p><code>npm install --save-dev ava</code></p><p>テストを実行するために<code>package.json</code>ファイルを構成します。次のようになっていることを確認してください:</p><pre><code>"type": "module",
	"scripts": {
		"test": "ava"
	},
	"devDependencies": {
		"ava": "^5.0.0"
	}</code></pre><p>それでは、 <code>test.js</code>ファイルを作成し、モッククライアントをインストールしましょう。</p><pre><code>const { Client } = require('@elastic/elasticsearch')
const Mock = require('@elastic/elasticsearch-mock')

const mock = new Mock()
const client = new Client({
  node: 'http://localhost:9200',
  Connection: mock.getConnection()
})</code></pre><p>次に、セマンティック検索のモックを追加します。</p><pre><code>function createSemanticSearchMock(query, indexName) {
  mock.add(
    {
      method: "POST",
      path: `/${indexName}/_search`,
      body: {
        query: {
          semantic: {
            field: "semantic_field",
            query: query,
          },
        },
      },
    },
    () => {
      return {
        hits: {
          total: { value: 2, relation: "eq" },
          hits: [
            {
              _id: "1",
              _score: 0.9,
              _source: {
                owner_name: "Alice Johnson",
                pet_name: "Buddy",
                species: "Dog",
                breed: "Golden Retriever",
                vaccination_history: ["Rabies", "Parvovirus", "Distemper"],
                visit_details:
                  "Annual check-up and nail trimming. Healthy and active.",
              },
            },
            {
              _id: "2",
              _score: 0.7,
              _source: {
                owner_name: "Daniel Kim",
                pet_name: "Mochi",
                species: "Rabbit",
                breed: "Mixed",
                vaccination_history: [],
                visit_details:
                  "Nail trimming and general health check. No issues.",
              },
            },
          ],
        },
      };
    }
  );
}</code></pre><p>これで、コードのテストを作成し、Elasticsearch 部分が常に同じ結果を返すことを確認できるようになりました。</p><pre><code>import test from 'ava';

test("performSemanticSearch must return formatted results correctly", async (t) => {
  const indexName = "vet-visits";
  const query = "Which pets had nail trimming?";

  createSemanticSearchMock(query, indexName);

  async function performSemanticSearch(esClient, q, indexName = "vet-visits") {
    try {
      const result = await esClient.search({
        index: indexName,
        body: {
          query: {
            semantic: {
              field: "semantic_field",
              query: q,
            },
          },
        },
      });

      return {
        success: true,
        results: result.hits.hits,
      };
    } catch (error) {
      if (error instanceof errors.TimeoutError) {
        return {
          success: false,
          results: null,
          error: error.body.error.reason,
        };
      }

      return {
        success: false,
        results: null,
        error: error.message,
      };
    }
  }

  const result = await performSemanticSearch(esClient, query, indexName);

  t.true(result.success, "The search must be successful");
  t.true(Array.isArray(result.results), "The results must be an array");

  if (result.results.length > 0) {
    t.true(
      "_source" in result.results[0],
      "Each result must have a _source property"
    );
    t.true(
      "pet_name" in result.results[0]._source,
      "Results must include the pet_name field"
    );
    t.true(
      "visit_details" in result.results[0]._source,
      "Results must include the visit_details field"
    );
  }
});</code></pre><p>テストを実行してみましょう。</p><p><code>npm run test</code></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/42feae845ae8eae03c37ad7ad114e8db35984812-1186x302.png" style="width: 100%;" /><p>完了です！これからは、外部要因ではなくコードに100%重点を置いてアプリをテストできます。</p><h2>サーバーレス環境</h2><h3>Elastic Serverlessでクライアントを実行する方法</h3><p>クラウドまたはオンプレミスでの Elasticsearch の実行について説明しましたが、Node.js クライアントは<a href="https://www.elastic.co/guide/en/serverless/current/intro.html">Elastic Cloud Serverless</a>への接続もサポートしています。</p><p>Elastic Cloud Serverless を使用すると、Elastic がインフラストラクチャを内部で処理するためインフラストラクチャについて心配する必要がないプロジェクトを作成でき、インデックスを作成するデータとそのデータにアクセスする期間のみを考慮すれば済みます。</p><p>使用の観点から見ると、Serverless はコンピューティングとストレージを切り離し、<a href="https://www.elastic.co/search-labs/blog/elasticsearch-serverless-tier-autoscaling">検索</a>と<a href="https://www.elastic.co/search-labs/blog/elasticsearch-ingest-autoscaling">インデックス作成</a>の両方に自動スケーリング機能を提供します。これにより、実際に必要なリソースのみを増やすことができます。</p><p>クライアントは、Serverless に接続するために次の調整を行います。</p><ul><li>スニッフィングをオフにし、スニッフィング関連のオプションを無視します</li><li>最初のノードを除いて、config で渡されたすべてのノードを無視し、ノードのフィルタリングと選択のオプションも無視します。</li><li>圧縮と `TLSv1_2_method` を有効にします（Elastic Cloud 用に構成した場合と同じ）</li><li>すべてのリクエストに `elastic-api-version` HTTP ヘッダーを追加します</li><li>デフォルトでは `WeightedConnectionPool` ではなく `CloudConnectionPool` を使用します</li><li>標準の MIME タイプを優先して、ベンダーの `content-type` および `accept` ヘッダーをオフにします。</li></ul><p>サーバーレス プロジェクトを接続するには、パラメーター serverMode: serverless を使用する必要があります。</p><pre><code>const { Client } = require('@elastic/elasticsearch')
const client = new Client({
  node: 'ELASTICSEARCH_ENDPOINT',
  auth: { apiKey: 'ELASTICSEARCH_API_KEY' },
  serverMode: "serverless",
});</code></pre><h3>Function-as-a-Service環境でクライアントを実行する方法</h3><p>この例では Node.js サーバーを使用しましたが、AWS lambda、GCP Run などの機能を備えた Function-as-a-Service 環境を使用して接続することもできます。</p><pre><code>'use strict'

const { Client } = require('@elastic/elasticsearch')

const client = new Client({
  // client initialisation
})

exports.handler = async function (event, context) {
  // use the client
}</code></pre><p>もう 1 つの例は、同じくサーバーレスである Vercel などのサービスに接続することです。これを実行する方法の<a href="https://github.com/elastic/elasticsearch-js/blob/main/docs/examples/proxy/README.md">完全な例</a>を確認できますが、<a href="https://github.com/elastic/elasticsearch-js/blob/main/docs/examples/proxy/api/search.js">検索エンドポイント</a>の最も重要な部分は次のようになります。</p><pre><code>const response = await client.search(
  {
    index: INDEX,
    // You could directly send from the browser
    // the Elasticsearch's query DSL, but it will
    // expose you to the risk that a malicious user
    // could overload your cluster by crafting
    // expensive queries.
    query: {
      match: { field: req.body.text },
    },
  },
  {
    headers: {
      Authorization: `ApiKey ${token}`,
    },
  }
);</code></pre><p>このエンドポイントは /api フォルダーにあり、サーバー側から実行されるため、クライアントは検索用語に対応する「テキスト」パラメータのみを制御できます。</p><p>Function-as-a-Service を使用する意味は、24 時間 365 日稼働するサーバーとは異なり、関数は関数を実行するマシンのみを起動し、関数が終了するとマシンは休止モードになり、消費するリソースが少なくなることです。</p><p>この構成は、アプリケーションがあまり多くのリクエストを受け取らない場合には便利ですが、そうでない場合はコストが高くなる可能性があります。また、<a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html">関数のライフサイクル</a>と実行時間 (場合によっては数秒しかないこともあります) も考慮する必要があります。</p><h2>まとめ</h2><p>この記事では、実稼働環境で非常に重要なエラーの処理方法を学びました。また、Elasticsearch サービスをモックしながらアプリケーションをテストする方法についても説明しました。これにより、クラスターの状態に関係なく信頼性の高いテストが提供され、コードに集中できるようになります。</p><p>最後に、Elastic Cloud Serverless と Vercel アプリケーションの両方をプロビジョニングして、完全にサーバーレスなスタックを立ち上げる方法を示しました。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch のディスク容量と使用量を最適化する方法]]></title>
            <description><![CDATA[Elasticsearchのディスクの空き容量が極端に少ない場合（過度な利用）や、ディスク容量が十分に活用されていない場合を未然に防ぎ、対処する方法について知り、クラスターのコストを最適化します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/optimize-elasticsearch-disk-space-and-usage</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/optimize-elasticsearch-disk-space-and-usage</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Fri, 16 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5d41a078dd03e4480a0ff4e9591c8618b9bab4d0-720x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>ディスク管理はどのデータベースでも重要であり、Elasticsearch も例外ではありません。十分なディスク容量がない場合、Elasticsearch はノードへのシャードの割り当てを停止します。これにより、最終的にはクラスターにデータを書き込むことができなくなり、アプリケーションでデータが失われる潜在的なリスクが生じます。一方、ディスク容量が多すぎる場合は、必要以上のリソースに対して料金を支払うことになります。</p><h2>透かしの背景</h2><p>Elasticsearch クラスターには、使用可能なディスク容量を追跡するのに役立つさまざまな「ウォーターマーク」しきい値があります。ノード上のディスクがいっぱいになると、最初に超えるしきい値は「低ディスク ウォーターマーク」になります。2 番目のしきい値は、「高ディスク ウォーターマークしきい値」になります。最終的には、「ディスク洪水段階」に到達します。このしきい値を超えると、クラスターは、ウォーターマークを通過したノード上の 1 つのシャード (プライマリまたはレプリカ) を持つすべてのインデックスへの書き込みをブロックします。読み取り（検索）は引き続き可能です。</p><h2>ディスクがいっぱいになった場合（過剰使用）の防止と対処方法</h2><p>Elasticsearch ディスクがいっぱいになった場合の対処方法はいくつかあります。</p><ol><li><strong>古いデータ を削除する:</strong> 通常、データを無期限に保存しないでください。ディスクがいっぱいになるのを防ぎ、解決する 1 つの方法は、データが一定の期間に達したときに確実にアーカイブされ、削除されるようにすることです。これを行う 1 つの方法は、 <a href="https://www.elastic.co/docs/manage-data/lifecycle/index-lifecycle-management">ILM を</a>使用することです。</li><li><strong>ストレージ容量の追加:</strong>データを削除できない場合は、パフォーマンスに悪影響を与えずにすべてのデータを保持するために、データ ノードを追加するか、ディスク サイズを増やす必要がある場合があります。クラスターにストレージ容量を追加する必要がある場合は、ストレージ容量だけを追加するのか、それともストレージ容量に加えて RAM と CPU のリソースも比例して追加するのかを検討する必要があります (以下の<a href="https://www.elastic.co/search-labs/blog/optimize-elasticsearch-disk-space-and-usage#the-relationship-between-disk-size,-ram-and-cpu">ディスク サイズ、RAM、CPU の比率</a>に関するセクションを参照)。</li></ol><h2>Elasticsearch クラスターにストレージ容量を追加する方法</h2><ol><li><strong>データ ノードの数を増やします。</strong>新しいノードは既存のノードと同じサイズで、同じ Elasticsearch バージョンである必要があることに注意してください。</li><li><strong>既存のノードのサイズを増やす:</strong>クラウドベースの環境では、通常、既存のノードのディスク サイズと RAM/CPU を増やすのは簡単です。</li><li><strong>ディスク サイズのみを増やす:</strong>クラウドベースの環境では、ディスク サイズを増やすのは比較的簡単です。</li><li><a href="https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore"><strong>スナップショット</strong> <strong>そして</strong> <strong>復元</strong></a><strong>:</strong>要求に応じて古いデータを自動プロセスでバックアップから取得できるようにする場合は、古いインデックスのスナップショットを作成し、それらを削除して、要求に応じてスナップショットからデータを一時的に復元できます。</li><li><strong>シャードごとのレプリカの数を減らす:</strong>データを削減するもう 1 つのオプションは、各シャードのレプリカの数を減らすことです。高可用性を実現するには、シャードごとに 1 つのレプリカを用意する必要がありますが、データが古くなると、レプリカなしでも作業できる可能性があります。これは通常、データが永続的である場合、または必要に応じて復元できるバックアップがある場合に機能します。</li><li><strong>アラートを作成する:</strong>将来ディスクがいっぱいになるのを防ぎ、積極的に対処するには、ディスクの使用状況に基づいて、ディスクがいっぱいになり始めたときに通知するアラートを作成する必要があります。</li></ol><h2>ディスク容量が十分に活用されない場合の防止と対処方法</h2><p>ディスク容量が十分に活用されていない場合は、クラスター上のストレージ ボリュームを削減するさまざまなオプションがあります。</p><h3>Elasticsearch クラスターのストレージ容量を削減する方法</h3><p>クラスターのストレージ容量を削減する方法はさまざまです。</p><p><strong>1. データノードの数を減らす</strong></p><p>データストレージを削減し、RAM と CPU リソースも同じ割合で削減したい場合は、これが最も簡単な戦略です。不要なノードを廃止すると、最大のコスト削減が実現する可能性があります。</p><p>ノードを廃止する前に、次の操作を行う必要があります。</p><ul><li>廃止するノードが MASTER ノードとして必要ではないことを確認します。常に、MASTER ノード ロールを持つノードが少なくとも 3 つ必要です。</li><li>廃止するノードからデータ シャードを移行します。</li></ul><p><strong>2. 既存のノードを小さなノードに置き換える</strong></p><p>ノードの数をさらに減らすことができない場合 (通常、最小構成は 3 です)、既存のノードのサイズを縮小することが必要になる場合があります。シャードはノードあたりのシャード数に基づいてバランスが取られるため、すべてのデータ ノードが同じ RAM メモリとディスク サイズであることを確認することをお勧めします。</p><p>プロセスは次のようになります。</p><ul><li>クラスターに新しい小さなノードを追加する</li><li>廃止するノードからシャードを移行する</li><li>古いノードをシャットダウンする</li></ul><p><strong>3. ノード上のディスクサイズを減らす</strong></p><p>クラスターの全体的な RAM または CPU を変更せずに、ノード上のディスク サイズのみを削減したい場合は、各ノードのディスク サイズを削減できます。Elasticsearch ノード上のディスク サイズを縮小するのは簡単なプロセスではありません。</p><p>最も簡単な方法は通常次のようになります。</p><ul><li>ノードからシャードを移行する</li><li>ノードを停止する</li><li>適切なサイズの新しいデータボリュームをノードにマウントします</li><li>古いディスクボリュームから新しいボリュームにすべてのデータをコピーします</li><li>古いボリュームAを切り離す</li><li>ノードを起動し、シャードをノードに戻す</li></ul><p>これには、このプロセス中にノードからの追加シャードを一時的に保存するのに十分な容量が他のノードに必要です。多くの場合、このプロセスの管理にかかるコストは、ディスク使用量の潜在的な節約額を上回る可能性があります。このため、必要なディスク サイズを持つ新しいノードにノード全体を置き換える方が簡単な場合があります (上記の「既存のノードをより小さなノードに置き換える」を参照)。</p><p>不要なリソースに料金を支払う場合、リソースの使用率を最適化することでコストを削減できることは明らかです。</p><h2>ディスクサイズ、RAM、CPUの関係</h2><p>クラスター内のディスク容量と RAM の理想的な比率は、特定のユースケースによって異なります。このため、ストレージ容量の変更を検討する際には、現在のディスク/RAM/CPU の比率が適切にバランスされているかどうか、また、その結果として RAM/CPU を同じ比率で追加/削減する必要があるかどうかも考慮する必要があります。</p><p>RAM と CPU の要件は、<a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-indexing/">インデックス作成</a>アクティビティの量、クエリの数と種類、さらに検索および集約されるデータの量によって異なります。これは多くの場合、クラスターに保存されるデータの量に比例するため、ディスク サイズにも関連している必要があります。</p><p>ディスク容量と RAM の比率は、使用事例に応じて変わる可能性があります。ここでいくつかの例をご覧ください:</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">インデックスアクティビティ</td><td style="border: 1px solid #ccc; padding: 4px;">保持</td><td style="border: 1px solid #ccc; padding: 4px;">検索アクティビティ</td><td style="border: 1px solid #ccc; padding: 4px;">ディスク容量</td><td style="border: 1px solid #ccc; padding: 4px;">ラム</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">エンタープライズ検索アプリ</td><td style="border: 1px solid #ccc; padding: 4px;">中程度のログ摂取</td><td style="border: 1px solid #ccc; padding: 4px;">長さ</td><td style="border: 1px solid #ccc; padding: 4px;">ライト</td><td style="border: 1px solid #ccc; padding: 4px;">2TB</td><td style="border: 1px solid #ccc; padding: 4px;">32GB</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">アプリ監視</td><td style="border: 1px solid #ccc; padding: 4px;">集中的なログ取り込み</td><td style="border: 1px solid #ccc; padding: 4px;">短い</td><td style="border: 1px solid #ccc; padding: 4px;">ライト</td><td style="border: 1px solid #ccc; padding: 4px;">1TB</td><td style="border: 1px solid #ccc; padding: 4px;">32GB</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">電子商取引</td><td style="border: 1px solid #ccc; padding: 4px;">軽量データインデックス</td><td style="border: 1px solid #ccc; padding: 4px;">不定</td><td style="border: 1px solid #ccc; padding: 4px;">重い</td><td style="border: 1px solid #ccc; padding: 4px;">500GB</td><td style="border: 1px solid #ccc; padding: 4px;">32GB</td>
                    </tr>
              </tbody>
            </table>
          <p><em>ノード マシンの構成の変更は、ノードのダウンタイムが発生する可能性があり、すでに過剰に負荷がかかっている他のノードにシャードが移行しないようにする必要があるため、慎重に行う必要があることに注意してください。</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[JavaScript で Elasticsearch を正しく使う方法、パート 1]]></title>
            <description><![CDATA[JavaScript で本番環境対応の Elasticsearch バックエンドを作成する方法を説明します。  

ElasticsearchをJavaScriptで使用して、クライアント/サーバーのベストプラクティスに従ってElasticsearchドキュメントにクエリを実行するために、さまざまな検索エンドポイントを持つサーバーを作成する方法をご覧ください。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/how-to-use-elasticsearch-in-javascript-part-i</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/how-to-use-elasticsearch-in-javascript-part-i</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
            <pubDate>Thu, 15 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/72200540ed258779d87e53a72ea189f8a138540c-1600x901.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>これは、JavaScript で Elasticsearch を使用する方法を説明するシリーズの最初の記事です。このシリーズでは、JavaScript 環境で Elasticsearch を使用する方法の基本を学習し、検索アプリを作成するための最も関連性の高い機能とベスト プラクティスを確認します。最後には、JavaScript を使用して Elasticsearch を実行するために必要なすべてのことを理解できるようになります。</p><p>この最初の部分では、次の点を確認します。</p><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#environment">環境</a><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#frontend,-backend,-or-serverless?">フロントエンド、バックエンド、それともサーバーレス？</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#connecting-the-client">クライアントの接続</a></li></ul></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#indexing-documents">文書のインデックス作成</a><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#elasticsearch-client">Elasticsearchクライアント</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#semantic-mappings">セマンティックマッピング</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#bulk-helper">バルクヘルパー</a></li></ul></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#searching-data">データの検索</a><ul><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#lexical-query-(/search/lexic?q=%3Cquery-term%3E)">語彙クエリ</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#semantic-query-(/search/semantic?q=%3Cquery-term%3E)">セマンティッククエリ</a></li><li><a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i#hybrid-query-(/search/hybrid?q=%3Cquery-term%3E)">ハイブリッドクエリ</a></li></ul></li></ul><p><em><a href="https://github.com/Delacrobix/JS-client-best-practices_article"><strong>ここで</strong></a> 例付きのソースコードを確認できます<strong> 。</strong></em></p><h3>Elasticsearch Node.js クライアントとは何ですか?</h3><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html">Elasticsearch Node.js クライアント</a>は、Elasticsearch API からの HTTP REST 呼び出しを JavaScript に配置する JavaScript ライブラリです。これにより、処理が容易になり、ドキュメントのインデックス作成などのタスクを一括で簡素化するヘルパーが利用できるようになります。</p><h2>環境</h2><h3>フロントエンド、バックエンド、それともサーバーレス？</h3><p>JavaScript クライアントを使用して検索アプリを作成するには、Elasticsearch クラスターとクライアントを実行する JavaScript ランタイムという少なくとも 2 つのコンポーネントが必要です。</p><p>JavaScript クライアントはすべての Elasticsearch ソリューション (クラウド、オンプレミス、サーバーレス) をサポートしており、クライアントがすべてのバリエーションを内部で処理するため、ソリューション間に大きな違いはありません。そのため、どれを使用するかについて心配する必要はありません。</p><p>ただし、JavaScript ランタイムは<strong>ブラウザ</strong>から<strong>直接ではなく、サーバーから実行する必要があります。</strong></p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/92ce6cfd923c8008fa44f617a58193642d9d5879-661x410.png" style="width: 100%;" /><p>これは、ブラウザから Elasticsearch を呼び出すと、ユーザーがクラスター API キー、ホスト、クエリ自体などの機密情報を取得する可能性があるためです。Elasticsearch では<strong>、クラスターをインターネットに直接公開せず</strong>、このすべての情報を抽象化する中間層を使用して、ユーザーがパラメータのみを確認できるようにすることを推奨しています。このトピックの詳細については、<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/es-security-principles.html#security-protect-cluster-traffic">ここ を</a>ご覧ください。</p><p>次のようなスキーマを使用することをお勧めします。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a08769f08ec73fe57bf2e961cfdfbb1cdd57919d-972x429.png" style="width: 100%;" /><p>この場合、クライアントは検索用語とサーバーの認証キーのみを送信し、サーバーはクエリと Elasticsearch との通信を完全に制御します。</p><h3>クライアントの接続</h3><p>まず、<a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud">次の手順</a>に従って API キーを作成します。</p><p>前の例に従って、シンプルな Express サーバーを作成し、Node.JS サーバーからのクライアントを使用してそのサーバーに接続します。</p><p>NPM を使用してプロジェクトを初期化し、Elasticsearch クライアントと<a href="https://expressjs.com/">Express をインストールします。</a>後者は、Node.js でサーバーを起動するためのライブラリです。Express を使用すると、HTTP 経由でバックエンドと対話できます。</p><p>プロジェクトを初期化しましょう:</p><p><code>npm init -y</code></p><p>依存関係をインストールします:</p><p><code>npm install @elastic/elasticsearch express split2 dotenv</code></p><p>詳しく説明しましょう:</p><ul><li><a href="https://www.npmjs.com/package/@elastic/elasticsearch"><strong><em>@elastic/elasticsearch</em></strong></a> : 公式Node.jsクライアントです</li><li><a href="https://www.npmjs.com/package/express"><strong><em>express</em></strong></a> : 軽量なNode.jsサーバーを立ち上げてElasticsearchを公開できるようになります</li><li><a href="https://www.npmjs.com/package/split2"><strong><em>split2</em></strong></a> : テキスト行をストリームに分割します。ndjsonファイルを1行ずつ処理するのに便利です</li><li><a href="https://www.npmjs.com/package/dotenv"><strong><em>dotenv</em></strong></a> : .env を使用して環境変数を管理できるようにしますファイル</li></ul><p>.envを作成するプロジェクトのルートにあるファイルを作成し、次の行を追加します。</p><pre><code>ELASTICSEARCH_ENDPOINT="Your Elasticsearch endpoint"
ELASTICSEARCH_API_KEY="Your Elasticssearch API"</code></pre><p>この方法では、 <code>dotenv</code>パッケージを使用してこれらの変数をインポートできます。</p><p><code>server.js</code>ファイルを作成します:</p><pre><code>const express = require("express");
const bodyParser = require("body-parser");
const { Client } = require("@elastic/elasticsearch");
 
require("dotenv").config(); //environment variables setup

const ELASTICSEARCH_ENDPOINT = process.env.ELASTICSEARCH_ENDPOINT;
const ELASTICSEARCH_API_KEY = process.env.ELASTICSEARCH_API_KEY;
const PORT = 3000;


const app = express();

app.listen(PORT, () => {
  console.log("Server running on port", PORT);
});
app.use(bodyParser.json());


let esClient = new Client({
  node: ELASTICSEARCH_ENDPOINT,
  auth: { apiKey: ELASTICSEARCH_API_KEY },  
});

app.get("/ping", async (req, res) => {
  try {
    const result = await esClient.info();

    res.status(200).json({
      success: true,
      clusterInfo: result,
    });
  } catch (error) {
    console.error("Error getting Elasticsearch info:", error);

    res.status(500).json({
      success: false,
      clusterInfo: null,
      error: error.message,
    });
  }
});</code></pre><p>このコードは、ポート 3000 をリッスンし、認証用の API キーを使用して Elasticsearch クラスターに接続する基本的な Express.js サーバーをセットアップします。これには、GET リクエストを介してアクセスすると、Elasticsearch クライアントの<code>.info()</code>メソッドを使用して Elasticsearch クラスターに基本情報を照会する /ping エンドポイントが含まれています。</p><p>クエリが成功した場合は、クラスター情報が JSON 形式で返され、それ以外の場合はエラー メッセージが返されます。サーバーは、JSON リクエスト本体を処理するために body-parser ミドルウェアも使用します。</p><p>ファイルを実行してサーバーを起動します。</p><p><code>node server.js</code></p><p>答えは次のようになるはずです:</p><pre><code>Server running on port 3000</code></pre><p>それでは、エンドポイント<code>/ping</code>を参照して、Elasticsearch クラスターのステータスを確認しましょう。</p><pre><code>curl http://localhost:3000/ping
{
    "success": true,
    "clusterInfo": {
        "name": "instance-0000000000",
        "cluster_name": "61b7e19eec204d59855f5e019acd2689",
        "cluster_uuid": "BIfvfLM0RJWRK_bDCY5ldg",
        "version": {
            "number": "9.0.0",
            "build_flavor": "default",
            "build_type": "docker",
            "build_hash": "112859b85d50de2a7e63f73c8fc70b99eea24291",
            "build_date": "2025-04-08T15:13:46.049795831Z",
            "build_snapshot": false,
            "lucene_version": "10.1.0",
            "minimum_wire_compatibility_version": "8.18.0",
            "minimum_index_compatibility_version": "8.0.0"
        },
        "tagline": "You Know, for Search"
    }
}</code></pre><h2>文書のインデックス作成</h2><p>接続すると、セマンティック検索用の<a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">semantic_text</a>やフルテキストクエリ用の text などのマッピングを使用してドキュメントのインデックスを作成できます。これら 2 つのフィールド タイプを使用すると、<a href="https://www.elastic.co/what-is/hybrid-search">ハイブリッド検索</a>も実行できます。</p><p>マッピングを生成し、ドキュメントをアップロードするために、新しい<code>load.js</code>ファイルを作成します。</p><h3>Elasticsearchクライアント</h3><p>まずクライアントをインスタンス化して認証する必要があります。</p><pre><code>const { Client } = require("@elastic/elasticsearch");

const ELASTICSEARCH_ENDPOINT = "cluster/project_endpoint";
const ELASTICSEARCH_API_KEY = "apiKey";

const esClient = new Client({
  node: ELASTICSEARCH_ENDPOINT,
  auth: { apiKey: ELASTICSEARCH_API_KEY },
});</code></pre><h3>セマンティックマッピング</h3><p>動物病院に関するデータを含むインデックスを作成します。飼い主様、ペット様、訪問の詳細に関する情報を保存します。</p><p>名前や説明など、全文検索を実行するデータはテキストとして保存されます。動物の種や品種などのカテゴリのデータは、キーワードとして保存されます。</p><p>さらに、すべてのフィールドの値を semantic_text フィールドにコピーして、その情報に対してもセマンティック検索を実行できるようにします。</p><pre><code>const INDEX_NAME = "vet-visits";

const createMappings = async (indexName, mapping) => {
  try {
    const body = await esClient.indices.create({
      index: indexName,
      body: {
        mappings: mapping,
      },
    });

    console.log("Index created successfully:", body);
  } catch (error) {
    console.error("Error creating mapping:", error);
  }
};

await createMappings(INDEX_NAME, {
  properties: {
    owner_name: {
      type: "text",
      copy_to: "semantic_field",
    },
    pet_name: {
      type: "text",
      copy_to: "semantic_field",
    },
    species: {
      type: "keyword",
      copy_to: "semantic_field",
    },
    breed: {
      type: "keyword",
      copy_to: "semantic_field",
    },
    vaccination_history: {
      type: "keyword",
      copy_to: "semantic_field",
    },
    visit_details: {
      type: "text",
      copy_to: "semantic_field",
    },
    semantic_field: {
      type: "semantic_text",
    },
  },
});</code></pre><h3>バルクヘルパー</h3><p>クライアントのもう 1 つの利点は、<a href="https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/client-helpers.html#bulk-helper">一括ヘルパーを</a>使用してインデックスを一括で作成できることです。バルク ヘルパーを使用すると、同時実行、再試行、関数を通過して成功または失敗した各ドキュメントの処理などを簡単に処理できます。</p><p>このヘルパーの魅力的な機能は、ストリームを操作できることです。この機能を使用すると、ファイル全体をメモリに保存して Elasticsearch に一度に送信するのではなく、ファイルを 1 行ずつ送信できます。</p><p>Elasticsearch にデータをアップロードするには、プロジェクトのルートに data.ndjson というファイルを作成し、以下の情報を追加します (または、<a href="https://github.com/Delacrobix/JS-client-best-practices_article/blob/main/data.ndjson">ここ</a>からデータセットを含むファイルをダウンロードすることもできます)。</p><pre><code>{"owner_name":"Alice Johnson","pet_name":"Buddy","species":"Dog","breed":"Golden Retriever","vaccination_history":["Rabies","Parvovirus","Distemper"],"visit_details":"Annual check-up and nail trimming. Healthy and active."}
{"owner_name":"Marco Rivera","pet_name":"Milo","species":"Cat","breed":"Siamese","vaccination_history":["Rabies","Feline Leukemia"],"visit_details":"Slight eye irritation, prescribed eye drops."}
{"owner_name":"Sandra Lee","pet_name":"Pickles","species":"Guinea Pig","breed":"Mixed","vaccination_history":[],"visit_details":"Loss of appetite, recommended dietary changes."}
{"owner_name":"Jake Thompson","pet_name":"Luna","species":"Dog","breed":"Labrador Mix","vaccination_history":["Rabies","Bordetella"],"visit_details":"Mild ear infection, cleaning and antibiotics given."}
{"owner_name":"Emily Chen","pet_name":"Ziggy","species":"Cat","breed":"Mixed","vaccination_history":["Rabies","Feline Calicivirus"],"visit_details":"Vaccination update and routine physical."}
{"owner_name":"Tomás Herrera","pet_name":"Rex","species":"Dog","breed":"German Shepherd","vaccination_history":["Rabies","Parvovirus","Leptospirosis"],"visit_details":"Follow-up for previous leg strain, improving well."}
{"owner_name":"Nina Park","pet_name":"Coco","species":"Ferret","breed":"Mixed","vaccination_history":["Rabies"],"visit_details":"Slight weight loss; advised new diet."}
{"owner_name":"Leo Martínez","pet_name":"Simba","species":"Cat","breed":"Maine Coon","vaccination_history":["Rabies","Feline Panleukopenia"],"visit_details":"Dental cleaning. Minor tartar buildup removed."}
{"owner_name":"Rachel Green","pet_name":"Rocky","species":"Dog","breed":"Bulldog Mix","vaccination_history":["Rabies","Parvovirus"],"visit_details":"Skin rash, antihistamines prescribed."}
{"owner_name":"Daniel Kim","pet_name":"Mochi","species":"Rabbit","breed":"Mixed","vaccination_history":[],"visit_details":"Nail trimming and general health check. No issues."}</code></pre><p>バルク ヘルパーがファイル行を Elasticsearch に送信する間、split2 を使用してファイル行をストリーミングします。</p><pre><code>const { createReadStream } = require("fs");
const split = require("split2");
 
const indexData = async (filePath, indexName) => {
  try {
    console.log(`Indexing data from ${filePath} into ${indexName}...`);

    const result = await esClient.helpers.bulk({
      datasource: createReadStream(filePath).pipe(split()),

      onDocument: () => {
        return {
          index: { _index: indexName },
        };
      },
      onDrop(doc) {
        console.error("Error processing document:", doc);
      },
    });

    console.log("Bulk indexing successful elements:", result.items.length);
  } catch (error) {
    console.error("Error indexing data:", error);
    throw error;
  }
};

await indexData("./data.ndjson", INDEX_NAME);</code></pre><p>上記のコードは.ndjsonを読み取りますファイルを 1 行ずつ読み込み、 <code>helpers.bulk</code>メソッドを使用して各 JSON オブジェクトを指定された Elasticsearch インデックスに一括インデックスします。<code>createReadStream</code>と<code>split2</code>を使用してファイルをストリーミングし、各ドキュメントのインデックス メタデータを設定し、処理に失敗したドキュメントをログに記録します。完了すると、正常にインデックスが作成されたアイテムの数を記録します。</p><p><code>indexData</code>関数を使用する代わりに、Kibana を使用して UI 経由でファイルを直接アップロードし、<a href="https://www.elastic.co/docs/manage-data/ingest/upload-data-files">データ ファイルのアップロード UI を使用することもできます。</a></p><p>ファイルを実行して、ドキュメントを Elasticsearch クラスターにアップロードします。</p><p><code>node load.js</code></p><pre><code>Creating mappings for index vet-visits...
Index created successfully: { acknowledged: true, shards_acknowledged: true, index: 'vet-visits' }
Indexing data from ./data.ndjson into vet-visits...
Bulk indexing completed. Total documents: 10, Failed: 0</code></pre><h2>Elasticsearchでのデータ検索</h2><p><code>server.js</code>ファイルに戻って、語彙検索、セマンティック検索、ハイブリッド検索を実行するためのさまざまなエンドポイントを作成します。</p><p>簡単に言えば、これらのタイプの検索は相互に排他的ではありませんが、回答する必要がある質問の種類によって異なります。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">クエリタイプ</td><td style="border: 1px solid #ccc; padding: 4px;">使用事例</td><td style="border: 1px solid #ccc; padding: 4px;">例題</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">語彙クエリ</td><td style="border: 1px solid #ccc; padding: 4px;">質問内の単語または語根は、索引文書に表示される可能性があります。質問とドキュメント間のトークンの類似性。</td><td style="border: 1px solid #ccc; padding: 4px;">青いスポーツTシャツを探しています。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">セマンティッククエリ</td><td style="border: 1px solid #ccc; padding: 4px;">質問内の単語は文書には表示されない可能性があります。質問とドキュメント間の概念的な類似性。</td><td style="border: 1px solid #ccc; padding: 4px;">寒い季節用の服を探しています。</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ハイブリッド検索</td><td style="border: 1px solid #ccc; padding: 4px;">質問には語彙や意味の要素が含まれています。質問とドキュメント間のトークンと意味の類似性。</td><td style="border: 1px solid #ccc; padding: 4px;">ビーチでの結婚式用にSサイズのドレスを探しています。</td>
                    </tr>
              </tbody>
            </table>
          <p>質問の<strong><em>語彙</em></strong>部分はタイトルや説明、またはカテゴリ名の一部である可能性が高く、<strong><em>意味</em></strong>部分はそれらの分野に関連する概念です。<strong><em>青は</em></strong>おそらくカテゴリ名または説明の一部であり、<strong><em>ビーチウェディングは</em></strong>そうではないかもしれませんが、意味的にはリネンの衣服に関連している可能性があります。</p><h3>語彙クエリ (/search/lexic?q=&lt;query_term&gt;)</h3><p>語彙検索 (フルテキスト検索とも呼ばれる) とは、トークンの類似性に基づいて検索することを意味します。つまり、分析後、検索内のトークンを含むドキュメントが返されます。</p><p>語彙検索の実践チュートリアルは、<a href="https://www.elastic.co/demo-gallery/lexical-search">こちらで</a>ご覧いただけます。</p><pre><code>app.get("/search/lexic", async (req, res) => {
  const { q } = req.query;

  const INDEX_NAME = "vet-visits";

  try {
    const result = await esClient.search({
      index: INDEX_NAME,
      size: 5,
      body: {
        query: {
          multi_match: {
            query: q,
            fields: ["owner_name", "pet_name", "visit_details"],
          },
        },
      },
    });

    res.status(200).json({
      success: true,
      results: result.hits.hits
    });
  } catch (error) {
    console.error("Error performing search:", error);

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});</code></pre><p><strong><em>爪切り</em></strong>でテストする</p><pre><code>curl http://localhost:3000/search/lexic?q=nail%20trimming</code></pre><p>答え：</p><pre><code>{
    "success": true,
    "results": [
        {
            "_index": "vet-visits",
            "_id": "-RY6RJYBLe2GoFQ6-9n9",
            "_score": 2.7075968,
            "_source": {
                "pet_name": "Mochi",
                "owner_name": "Daniel Kim",
                "species": "Rabbit",
                "visit_details": "Nail trimming and general health check. No issues.",
                "breed": "Mixed",
                "vaccination_history": []
            }
        },
        {
            "_index": "vet-visits",
            "_id": "8BY6RJYBLe2GoFQ6-9n9",
            "_score": 2.560356,
            "_source": {
                "pet_name": "Buddy",
                "owner_name": "Alice Johnson",
                "species": "Dog",
                "visit_details": "Annual check-up and nail trimming. Healthy and active.",
                "breed": "Golden Retriever",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Distemper"
                ]
            }
        }
    ]
}</code></pre><h3>セマンティッククエリ (/search/semantic?q=&lt;query_term&gt;)</h3><p>セマンティック検索は、語彙検索とは異なり、ベクトル検索を通じて検索用語の意味に類似した結果を見つけます。</p><p>セマンティック検索の実践チュートリアルは、<a href="https://www.elastic.co/demo-gallery/semantic-search">こちらで</a>ご覧いただけます。</p><pre><code>app.get("/search/semantic", async (req, res) => {
  const { q } = req.query;

  const INDEX_NAME = "vet-visits";

  try {
    const result = await esClient.search({
      index: INDEX_NAME,
      size: 5,
      body: {
        query: {
          semantic: {
            field: "semantic_field",
            query: q
          },
        },
      },
    });

    res.status(200).json({
      success: true,
      results: result.hits.hits,
    });
  } catch (error) {
    console.error("Error performing search:", error);

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});</code></pre><p>テスト対象:<strong><em>誰がペディキュアをしましたか?</em></strong></p><pre><code>curl http://localhost:3000/search/semantic?q=Who%20got%20a%20pedicure?</code></pre><p>答え：</p><pre><code>{
    "success": true,
    "results": [
        {
            "_index": "vet-visits",
            "_id": "-RY6RJYBLe2GoFQ6-9n9",
            "_score": 4.861466,
            "_source": {
                "owner_name": "Daniel Kim",
                "pet_name": "Mochi",
                "species": "Rabbit",
                "breed": "Mixed",
                "vaccination_history": [],
                "visit_details": "Nail trimming and general health check. No issues."
            }
        },
        {
            "_index": "vet-visits",
            "_id": "8BY6RJYBLe2GoFQ6-9n9",
            "_score": 4.7152824,
            "_source": {
                "pet_name": "Buddy",
                "owner_name": "Alice Johnson",
                "species": "Dog",
                "visit_details": "Annual check-up and nail trimming. Healthy and active.",
                "breed": "Golden Retriever",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Distemper"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "9RY6RJYBLe2GoFQ6-9n9",
            "_score": 1.6717153,
            "_source": {
                "pet_name": "Rex",
                "owner_name": "Tomás Herrera",
                "species": "Dog",
                "visit_details": "Follow-up for previous leg strain, improving well.",
                "breed": "German Shepherd",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Leptospirosis"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "9xY6RJYBLe2GoFQ6-9n9",
            "_score": 1.5600781,
            "_source": {
                "pet_name": "Simba",
                "owner_name": "Leo Martínez",
                "species": "Cat",
                "visit_details": "Dental cleaning. Minor tartar buildup removed.",
                "breed": "Maine Coon",
                "vaccination_history": [
                    "Rabies",
                    "Feline Panleukopenia"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "-BY6RJYBLe2GoFQ6-9n9",
            "_score": 1.2696637,
            "_source": {
                "pet_name": "Rocky",
                "owner_name": "Rachel Green",
                "species": "Dog",
                "visit_details": "Skin rash, antihistamines prescribed.",
                "breed": "Bulldog Mix",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus"
                ]
            }
        }
    ]
}</code></pre><h3>ハイブリッド クエリ (/search/hybrid?q=&lt;query_term&gt;)</h3><p>ハイブリッド検索により、セマンティック検索と語彙検索を組み合わせることができるため、両方の長所を活用できます。つまり、トークンによる検索の精度と、セマンティック検索の意味の近似性の両方が得られます。</p><pre><code>app.get("/search/hybrid", async (req, res) => {
  const { q } = req.query;

  const INDEX_NAME = "vet-visits";

  try {
    const result = await esClient.search({
      index: INDEX_NAME,
      body: {
        retriever: {
          rrf: {
            retrievers: [
              {
                standard: {
                  query: {
                    bool: {
                      must: {
                         multi_match: {
             query: q,
            fields: ["owner_name", "pet_name", "visit_details"],
          },
                      },
                    },
                  },
                },
              },
              {
                standard: {
                  query: {
                    bool: {
                      must: {
                        semantic: {
                          field: "semantic_field",
                          query: q,
                        },
                      },
                    },
                  },
                },
              },
            ],
          },
        },
        size: 5,
      },
    });

    res.status(200).json({
      success: true,
      results: result.hits.hits,
    });
  } catch (error) {
    console.error("Error performing search:", error);

    res.status(500).json({
      success: false,
      results: null,
      error: error.message,
    });
  }
});</code></pre><p>「<strong><em>ペディキュアや歯科治療を受けた人はいますか？」</em></strong>という質問をしてテストします。</p><pre><code>curl http://localhost:3000/search/hybrid?q=who%20got%20a%20pedicure%20or%20dental%20treatment</code></pre><p>対応：</p><pre><code>{
    "success": true,
    "results": [
        {
            "_index": "vet-visits",
            "_id": "9xY6RJYBLe2GoFQ6-9n9",
            "_score": 0.032522473,
            "_source": {
                "pet_name": "Simba",
                "owner_name": "Leo Martínez",
                "species": "Cat",
                "visit_details": "Dental cleaning. Minor tartar buildup removed.",
                "breed": "Maine Coon",
                "vaccination_history": [
                    "Rabies",
                    "Feline Panleukopenia"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "-RY6RJYBLe2GoFQ6-9n9",
            "_score": 0.016393442,
            "_source": {
                "pet_name": "Mochi",
                "owner_name": "Daniel Kim",
                "species": "Rabbit",
                "visit_details": "Nail trimming and general health check. No issues.",
                "breed": "Mixed",
                "vaccination_history": []
            }
        },
        {
            "_index": "vet-visits",
            "_id": "8BY6RJYBLe2GoFQ6-9n9",
            "_score": 0.015873017,
            "_source": {
                "pet_name": "Buddy",
                "owner_name": "Alice Johnson",
                "species": "Dog",
                "visit_details": "Annual check-up and nail trimming. Healthy and active.",
                "breed": "Golden Retriever",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Distemper"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "9RY6RJYBLe2GoFQ6-9n9",
            "_score": 0.015625,
            "_source": {
                "pet_name": "Rex",
                "owner_name": "Tomás Herrera",
                "species": "Dog",
                "visit_details": "Follow-up for previous leg strain, improving well.",
                "breed": "German Shepherd",
                "vaccination_history": [
                    "Rabies",
                    "Parvovirus",
                    "Leptospirosis"
                ]
            }
        },
        {
            "_index": "vet-visits",
            "_id": "8xY6RJYBLe2GoFQ6-9n9",
            "_score": 0.015384615,
            "_source": {
                "pet_name": "Luna",
                "owner_name": "Jake Thompson",
                "species": "Dog",
                "visit_details": "Mild ear infection, cleaning and antibiotics given.",
                "breed": "Labrador Mix",
                "vaccination_history": [
                    "Rabies",
                    "Bordetella"
                ]
            }
        }
    ]
}</code></pre><h2>まとめ</h2><p>このシリーズの最初の部分では、クライアント/サーバーのベストプラクティスに従って、環境を設定し、さまざまな検索エンドポイントを持つサーバーを作成し、Elasticsearch ドキュメントをクエリする方法を説明しました。シリーズの<a href="https://www.elastic.co/search-labs/blog/how-to-use-elasticsearch-in-javascript-part-i">パート 2</a>では、本番環境のベスト プラクティスと、サーバーレス環境で Elasticsearch Node.js クライアントを実行する方法について学習します。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearchインデックスのレプリカ数の設定方法]]></title>
            <description><![CDATA[Elasticsearchインデックスでnumber_of_replicasを構成して、検索パフォーマンスを向上させ、ノード障害に対する耐性を高める方法を学びます。 
]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-index-number-of_replicas</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-index-number-of_replicas</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Wed, 14 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/23b96aaa1a38b1f4747b4a87695d816f24c0cf70-720x421.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch は、大量のデータを処理し、高い可用性を提供できる分散システムとして設計されています。これを可能にする重要な機能の 1 つは、 <code>number_of_replicas</code>設定によって制御されるインデックス レプリケーションの概念です。この記事では、この設定の詳細、その影響、および適切な構成方法について詳しく説明します。</p><h2>Elasticsearchにおけるレプリカの役割</h2><p>Elasticsearch では、インデックスは複数のプライマリ シャードに分割されたドキュメントのコレクションです。各プライマリ シャードは自己完結型の Apache Lucene インデックスであり、インデックス内のドキュメントはすべてのプライマリ シャードに分散されます。高可用性とデータの冗長性を確保するために、Elasticsearch では各シャードにレプリカと呼ばれる 1 つ以上のコピーを持たせることができます。<code>number_of_replicas</code>設定は、Elasticsearch がインデックス内の各プライマリ シャードに対して作成するレプリカ シャード (コピー) の数を制御します。デフォルトでは、Elasticsearch はプライマリ シャードごとに 1 つのレプリカを作成しますが、これはシステムの要件に応じて変更できます。</p><h2>number_of_replicas の設定</h2><p><code>number_of_replicas</code>設定は、インデックスの作成時に構成することも、後で更新することもできます。インデックス作成時に設定する方法は次のとおりです。</p><pre><code>PUT /my_index
{
  "settings": {
    "number_of_replicas": 2
  }
}</code></pre><p>この例では、Elasticsearch は<code>my_index</code>インデックス内のプライマリ シャードごとに 2 つのレプリカを作成します。</p><p>既存のインデックスの<code>number_of_replicas</code>設定を更新するには、 <code>_settings</code> API を使用できます。</p><pre><code>PUT /my_index/_settings
{
  "number_of_replicas": 3
}</code></pre><p>このコマンドは、 <code>my_index</code>インデックスを更新して、プライマリ シャードごとに 3 つのレプリカを作成します。</p><h2>number_of_replicas設定の影響</h2><p><code>number_of_replicas</code>設定は、Elasticsearch<a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-cluster/">クラスター</a>のパフォーマンスと復元力に大きな影響を与えます。考慮すべき重要なポイントは次のとおりです。</p><ol><li><strong>データの冗長性と可用性:</strong> <code>number_of_replicas</code>を増やすと、各シャードのコピーがさらに作成され、データの可用性が向上します。ノードに障害が発生した場合でも、Elasticsearch は残りの<a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-node/">ノード</a>上のレプリカ シャードからデータを提供できます。</li><li><strong>検索パフォーマンス:</strong>レプリカ シャードは読み取り要求を処理できるため、レプリカの数を増やすと、負荷がより多くのシャードに分散され、検索パフォーマンスが向上します。</li></ol><div style="display:none">Unknown block type "block", specify a component for it in the `components.types` option</div><ol><li><strong>ストレージ要件:</strong>レプリカが増えると、ストレージ容量も増えます。追加のレプリカを保存するのに十分な容量がクラスターにあることを確認する必要があります。</li><li><strong>ノード障害に対する耐性:</strong>クラスター内のノードの数を考慮して<code>number_of_replicas</code>を設定する必要があります。<code>number_of_replicas</code>がノード数以上である場合、クラスターはデータ損失なしで複数のノードの障害を許容できます。</li></ol><h2>number_of_replicas の設定に関するベストプラクティス</h2><p>最適な<code>number_of_replicas</code>設定は、システムの特定の要件によって異なります。ただし、一般的なベストプラクティスをいくつか示します。</p><ul><li>単一ノード クラスターの場合、レプリカを保持する他のノードがないため、 <code>number_of_replicas</code> 0 に設定する必要があります。</li><li>マルチノード クラスターの場合、データの冗長性と高可用性を確保するために、 <code>number_of_replicas</code>少なくとも 1 に設定する必要があります。</li><li>検索パフォーマンスを優先する場合は、 <code>number_of_replicas</code>を増やすことを検討してください。ただし、書き込みパフォーマンスとストレージ要件とのトレードオフに留意してください。</li><li>クラスターに追加のレプリカを保存するのに十分な容量があることを常に確認してください。</li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch フィールド タイプへの埋め込みのマッピング: semantic_text、dense_vector、sparse_vector]]></title>
            <description><![CDATA[semantic_text、dense_vector、sparse_vector をどのように、いつ使用するか、またそれらが埋め込み生成とどのように関係するかについて説明します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/mapping-embeddings-to-elasticsearch-field-types</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/mapping-embeddings-to-elasticsearch-field-types</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Tue, 13 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f98fdff837db55b466780c0bae672aa6f6c3a966-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>情報検索の関連性と精度を向上させるために埋め込みを使用することは、長年にわたって大幅に増加しています。Elasticsearch などのツールは、密ベクトル、疎ベクトル、セマンティック テキストなどの特殊なフィールド タイプを通じてこのタイプのデータをサポートするように進化してきました。ただし、良好な結果を得るには、利用可能な Elasticsearch フィールド タイプ ( <code>semantic_text</code> 、 <code>dense_vector</code> 、 <code>sparse_vector</code> ) に埋め込みを適切にマッピングする方法を理解することが重要です。</p><p>この記事では、これらのフィールド タイプ、それぞれのフィールド タイプをいつ使用するか、インデックス作成時とクエリ時の両方で埋め込み生成と使用戦略にどのように関連するかについて説明します。</p><h2>密ベクトル型</h2><p>Elasticsearch の<code>dense_vector</code>フィールド タイプは、ほぼすべての次元が関連する、テキスト、画像、音声などのデータの数値表現である密なベクトルを格納するために使用されます。これらのベクトルは、OpenAI、Cohere、Hugging Face などのプラットフォームによって提供される埋め込みモデルを使用して生成され、他のドキュメントと正確な用語を共有していない場合でも、データの全体的な意味をキャプチャするように設計されています。</p><p>Elasticsearch では、使用されるモデルに応じて、密なベクトルは最大 4096 次元を持つことができます。たとえば、all-MiniLM-L6-v2 モデルは 384 次元のベクトルを生成しますが、OpenAI の text-embedding-ada-002 は 1536 次元のベクトルを生成します。</p><p><code>dense_vector</code>フィールドは、事前に生成されたベクトルの使用、カスタム類似度関数の適用、外部モデルとの統合など、より高度な制御が必要な場合に、この種の埋め込みを格納するためのデフォルトのタイプとして一般的に採用されています。</p><h3>dense_vector 型をいつ、なぜ使用するのでしょうか?</h3><p>高密度ベクトルは、文、段落、またはドキュメント全体の間の意味的な類似性を捉えるのに最適です。同じ用語を共有していなくても、テキストの全体的な意味を比較することが目的の場合、非常に効果的です。</p><p>密ベクトル フィールドは、OpenAI、Cohere、Hugging Face などのプラットフォームによって提供されるモデルを使用した外部埋め込み生成パイプラインが既にあり、これらのベクトルを手動で保存およびクエリするだけの場合に最適です。このタイプのフィールドは、埋め込みモデルとの高い互換性と、生成およびクエリの完全な柔軟性を提供し、検索中にベクトルがどのように生成、インデックス付け、使用されるかを制御できます。</p><p>さらに、ランキングロジックを調整する必要がある場合には、k-NN や script_score などのクエリを使用して、さまざまな形式のセマンティック検索をサポートします。これらの可能性により、高密度ベクトルは、RAG (検索拡張生成)、推奨システム、類似性に基づくパーソナライズされた検索などのアプリケーションに最適です。</p><p>最後に、このフィールドでは、 <code>cosineSimilarity</code> 、 <code>dotProduct</code> 、 <code>l2norm</code>などの関数を使用して関連性ロジックをカスタマイズし、ユースケースのニーズに応じてランキングを調整できます。 </p><p>柔軟性、カスタマイズ性、および上記のような高度なユースケースとの互換性を必要とする人にとって、高密度ベクターは依然として最適なオプションです。</p><h3>密ベクター型のクエリを使用するにはどうすればいいですか?</h3><p><strong><code>dense_vector</code></strong>として定義されたフィールドの検索では、k 近傍クエリが使用されます。このクエリは、密なベクトルがクエリ ベクトルに最も近いドキュメントを見つける役割を果たします。以下は、密なベクトル フィールドに k-NN クエリを適用する方法の例です。</p><pre><code>{
  "knn": {
    "field": "my_dense_vector",
    "k": 10,
    "num_candidates": 50,
    "query_vector": [/* vector generated by model */]
  }
}</code></pre><p>k-NN クエリに加えて、ドキュメントのスコアリングをカスタマイズする必要がある場合は、script_score クエリを使用して、 <strong>cosineSimilarity、dotProduct、l2norm</strong>などのベクトル比較関数と組み合わせて、より制御された方法で関連性を計算することもできます。例を参照してください:</p><pre><code>{
"script_score": {
    "query": { "match_all": {} },
    "script": {
      "source": "cosineSimilarity(params.query_vector,
'my_dense_vector') + 1.0",
      "params": {
        "query_vector": [/* vector */]
      }
    }
  }
}</code></pre><p>さらに詳しく知りたい場合は、 <a href="https://www.elastic.co/jp/search-labs/blog/vector-search-set-up-elasticsearch">「Elasticsearch でベクトル検索を設定する方法」の記事を参照することをお勧めします。</a></p><p></p><h2>スパースベクトル型</h2><p><strong><code>sparse_vector</code></strong>フィールド タイプは、ほとんどの値がゼロで、少数の項のみに重要な重みがある数値表現であるスパース ベクトルを格納するために使用されます。このタイプのベクトルは、SPLADE や ELSER (Elastic Learned Sparse EncodeR) などの用語ベースのモデルで一般的です。</p><h3>スパースベクトル型をいつ、なぜ使用するのでしょうか?</h3><p>スパースベクトルは、意味的インテリジェンスを犠牲にすることなく、語彙的により正確な検索が必要な場合に最適です。テキストをトークン/値のペアとして表現し、関連する重みを持つ最も関連性の高い用語のみを強調表示することで、明確さ、制御性、効率性を実現します。</p><p>このタイプのフィールドは、テキスト内の相対的な重要度に基づいて各トークンに異なる重みを割り当てる ELSER モデルや SPLADE モデルなどの用語に基づいてベクトルを生成する場合に特に役立ちます。</p><p>クエリ内の特定の単語の影響を制御したい場合には、スパースベクター型を使用すると、用語の重みを手動で調整して、結果のランキングを最適化できます。</p><p>主な利点としては、ドキュメントが関連性があると判断された理由を明確に理解できるため検索の透明性が高く、また、すべての次元を保存する密なベクトルとは異なり、ゼロ以外の値を持つトークンのみが保存されるためストレージ効率が高くなる点が挙げられます。</p><p>さらに、スパース ベクトルはハイブリッド検索戦略の理想的な補完であり、稠密ベクトルと組み合わせて語彙の精度と意味の理解を組み合わせることもできます。</p><h3>スパースベクトル型のクエリを使用するにはどうすればいいですか?</h3><p><strong><code>sparse_vector</code></strong>クエリを使用すると、トークン/値形式のクエリ ベクトルに基づいてドキュメントを検索できます。以下のクエリの例を参照してください。</p><pre><code>{
  "query": {
    "sparse_vector": {
      "field": "field_sparse",
      "query_vector": {
        "token1": 0.6,
        "token2": 0.2,
        "token3": 0.9
      }
    }
  }
}</code></pre><p>トレーニング済みのモデルを使用する場合は、クエリ テキストをスパース ベクトルに自動的に変換する推論エンドポイントを使用できます。</p><pre><code>{
  "query": {
    "sparse_vector": {
      "field": "field_sparse",
      "inference_id": "the inference ID to produce the token/weights",
      "query": "search text"
    }
  }
}</code></pre><p>このトピックをさらに詳しく調べるには、 <a href="https://www.elastic.co/jp/search-labs/blog/sparse-vector-embedding">「トレーニング済み ML モデルによるスパース ベクトル埋め込みの理解」</a>を読むことをお勧めします。</p><h2>セマンティックテキストタイプ</h2><p><strong><code>semantic_text</code></strong>フィールド タイプは、Elasticsearch でセマンティック検索を使用する最もシンプルで簡単な方法です。推論エンドポイントを通じて、インデックス作成時とクエリ時の両方で埋め込み生成を自動的に処理します。つまり、ベクトルを手動で生成したり保存したりする手間がかかりません。</p><h3>セマンティックテキストはいつ、なぜ使用するのでしょうか?</h3><p><code>semantic_text</code>フィールドは、最小限の技術的労力で、ベクトルを手動で処理することなく開始したい場合に最適です。このフィールドは、埋め込み生成やベクトル検索マッピングなどの手順を自動化し、セットアップをより速く便利にします。</p><p><strong>シンプルさと抽象化</strong>を重視する場合は、<strong>マッピング、埋め込み生成、および取り込みパイプラインを手動で構成する複雑さが排除される</strong>ため、 <code>semantic_text</code>使用を検討する必要があります。推論モデルを選択するだけで、残りの作業は Elasticsearch が処理します。</p><p>主な利点としては、インデックス作成とクエリの両方で実行される<strong>自動埋め込み生成と</strong>、選択した推論モデルをサポートするように事前構成された、<strong>すぐに使用できるマッピング</strong>が挙げられます。</p><p>さらに、このフィールドは<strong>長いテキストの自動分割 (テキスト チャンク) をネイティブにサポート</strong>しており、大きなテキストをそれぞれ独自の埋め込みを持つ小さな段落に分割できるため、検索の精度が向上します。これにより、特にセマンティック検索の基礎となるエンジニアリングを扱うことなく価値を迅速に提供したいチームにとって、生産性が大幅に向上します。</p><p>ただし、 <code>semantic_text</code>スピードとシンプルさを提供しますが、このアプローチにはいくつかの制限があります。Elasticsearch の推論エンドポイントとして利用できる限り、市場標準モデルの使用が可能になります。ただし、 <code>dense_vector</code>フィールドの場合のように、<strong>外部で生成された埋め込みはサポートされません</strong>。</p><p>ベクトルの生成方法をより細かく制御する必要がある場合、独自の埋め込みを使用する場合、または高度な戦略のために複数のフィールドを組み合わせる必要がある場合は、 <code>dense_vector</code>フィールドと<code>sparse_vector</code>フィールドによって、よりカスタマイズされたシナリオやドメイン固有のシナリオに必要な柔軟性が得られます。</p><h3>セマンティックテキストタイプのクエリの使用方法</h3><p><strong><code>semantic_text</code></strong>より前は、埋め込みの種類 (密または疎) に応じて異なるクエリを使用する必要がありました。スパースフィールドには<code>sparse_vector</code>クエリが使用されましたが、 <code>dense_vector</code>フィールドには KNN クエリが必要でした。</p><p>セマンティック テキスト タイプでは、<a href="https://www.elastic.co/jp/docs/reference/query-languages/query-dsl/query-dsl-semantic-query">セマンティック クエリを使用して検索が実行されます。セマンティック クエリ</a>は、クエリ ベクトルを自動的に生成し、それをインデックス付けされたドキュメントの埋め込みと比較します。<strong><code>semantic_text</code></strong>タイプでは、クエリを埋め込むための推論エンドポイントを定義できますが、何も指定されていない場合は、インデックス作成時に使用されたのと同じエンドポイントがクエリに適用されます。</p><pre><code>{
  "query": {
    "semantic": {
      "field": "semantic_text_field",
      "query": "search text"
    }
  }
}</code></pre><p>詳細については、 <a href="https://www.elastic.co/jp/search-labs/blog/semantic-search-simplified-semantic-text">「Elasticsearch の新しい semantic_text マッピング: セマンティック検索の簡素化」という</a>記事を読むことをお勧めします。</p><h2>まとめ</h2><p>Elasticsearch で埋め込みをマッピングする方法を選択するときは、ベクトルを生成する方法とベクトルに対して必要な制御レベルを理解することが重要です。シンプルさを求める場合、セマンティック テキスト フィールドを使用すると、自動かつスケーラブルなセマンティック検索が可能になり、多くの初期使用ケースに最適です。より高度な制御、微調整されたパフォーマンス、またはカスタム モデルとの統合が必要な場合は、密ベクトル フィールドと疎ベクトル フィールドが必要な柔軟性を提供します。</p><p>理想的なフィールド タイプは、ユース ケース、利用可能なインフラストラクチャ、機械学習スタックの成熟度によって異なります。最も重要なのは、Elastic が最新の、適応性に優れた検索システムを構築するためのツールを提供していることです。</p><h2>参照資料</h2><ul><li><a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/current/semantic-text.html">セマンティックテキストフィールドタイプ</a></li><li><a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/current/sparse-vector.html">スパースベクトル場型</a></li><li><a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/current/dense-vector.html">密ベクトル場型</a></li><li><a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html">セマンティッククエリ</a></li><li><a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/current/query-dsl-sparse-vector-query.html">スパースベクトルクエリ</a></li><li><a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/current/knn-search.html">kNN検索</a></li><li><a href="https://www.elastic.co/jp/search-labs/blog/semantic-search-simplified-semantic-text">Elasticsearchの新しいsemantic_textマッピング：セマンティック検索の簡素化</a></li><li><a href="https://www.elastic.co/jp/search-labs/blog/sparse-vector-embedding">学習済み ML モデルによるスパースベクトル埋め込みの理解</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch フィールドをインデックスから除外する]]></title>
            <description><![CDATA[フィールドを除外するようにElasticsearchを設定する方法、インデキシングからフィールドを除外する主な理由、従うべきベストプラクティスを学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/excluding-elasticsearch-fields-from-indexing</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/excluding-elasticsearch-fields-from-indexing</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Mon, 12 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3aa0b481994d2445ba979d3c79fff64c5ee6676a-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch では、インデックス作成とは、データを簡単に検索できるように保存および整理するプロセスを指します。ドキュメント内のすべてのフィールドにインデックスを作成すると便利な場合もありますが、特定のフィールドをインデックス作成から除外したい場合もあります。これにより、パフォーマンスが向上し、ストレージ コストが削減され、Elasticsearch インデックスの全体的なサイズが最小限に抑えられます。</p><p>この記事では、インデックス作成からフィールドを除外する理由、特定のフィールドを除外するように Elasticsearch を構成する方法、およびそうする際に従うべきベスト プラクティスについて説明します。</p><h2>フィールドをインデックスから除外する理由</h2><ol><li><strong>パフォーマンス:</strong>ドキュメント内のすべてのフィールドにインデックスを作成すると、インデックス作成時間が長くなり、検索パフォーマンスが低下する可能性があります。検索や集計に必要のないフィールドを除外することで、Elasticsearch クラスターの全体的なパフォーマンスを向上させることができます。</li><li><strong>ストレージ:</strong>フィールドのインデックス作成により、ストレージ スペースが消費されます。検索や集計に必要のないフィールドを除外すると、Elasticsearch クラスターのストレージ要件を削減できます。</li><li><strong>インデックス サイズ:</strong> Elasticsearch インデックスのサイズは、インデックスが作成されるフィールドの数に直接関係します。不要なフィールドを除外することで、インデックスのサイズを最小限に抑えることができ、検索とインデックス作成のパフォーマンスが向上します。</li></ol><h2>フィールドを除外するようにElasticsearchを構成する</h2><p>Elasticsearch でフィールドをインデックスから除外するには、フィールドのマッピングで「index」プロパティを使用できます。「index」プロパティを「false」に設定すると、Elasticsearch はフィールドにインデックスを付けず、検索や集計に使用できなくなります。</p><p>Elasticsearch マッピングを使用してフィールドをインデックスから除外する方法の例を次に示します。</p><pre><code>PUT /my_index
{
  "mappings": {
    "properties": {
      "field_to_exclude": {
        "type": "text",
        "index": false
      }
    }
  }
}</code></pre><p>この例では、「field_to_exclude」という単一のフィールドを持つ「my_index」という新しいインデックスを作成しています。「index」プロパティを「false」に設定することで、Elasticsearch にこのフィールドをインデックスしないように指示します。ただし、フィールドはソース ドキュメントでは引き続き使用できます。</p><h2>インデックスからフィールドを除外するためのベストプラクティス</h2><ol><li><strong>データを分析する:</strong>フィールドをインデックスから除外する前に、データを分析し、検索と集計に必要なフィールドを理解することが重要です。これにより、どのフィールドを除外するかについて十分な情報に基づいた決定を下すことができます。</li><li><strong>変更をテストする:</strong>フィールドをインデックス作成から除外する場合は、検索機能と集計機能が期待どおりに動作することを確認するために、変更をテストすることが重要です。これにより、予期しない問題やパフォーマンスの問題を回避できます。</li><li><strong>パフォーマンスを監視する:</strong>フィールドをインデックスから除外した後、Elasticsearch クラスターのパフォーマンスを監視して、変更が目的の効果をもたらしたことを確認します。これにより、必要となる可能性のある追加の最適化を特定するのに役立ちます。</li><li><strong>ソース フィルタリングを使用する:</strong> Elasticsearch にフィールドを保存する必要があるが、検索可能にしたり集計に使用したりしたくない場合は、ソース フィルタリングの使用を検討してください。これにより、フィールドを _source フィールドに保存しながら、インデックスからは除外することができます。</li></ol><h2>まとめ</h2><p>Elasticsearch のインデックス作成からフィールドを除外すると、パフォーマンスの向上、ストレージ コストの削減、インデックスの全体的なサイズの最小化に役立ちます。データを慎重に分析し、検索と集計に必要なフィールドを理解することで、どのフィールドを除外するかについて十分な情報に基づいた決定を下すことができます。常に変更をテストし、Elasticsearch クラスターのパフォーマンスを監視して、最適化が期待どおりの効果をもたらすことを確認してください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch のドキュメントからフィールドを削除する]]></title>
            <description><![CDATA[Update API、スクリプト、または単一削除や一括削除のための再インデックスを使用して、Elasticsearchドキュメントからフィールドを削除する方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-delete-field-from-document</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-delete-field-from-document</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Fri, 09 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/89278eb7309b7f3018c61be2b514d1fd25b9564d-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch では、ドキュメントからフィールドを削除することが一般的な要件です。これは、インデックスから不要な情報や古い情報を削除する場合に役立ちます。この記事では、Elasticsearch 内のドキュメントからフィールドを削除するさまざまな方法を、例と手順とともに説明します。 </p><h2>方法1: Update APIを使用する</h2><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/update-document">Update API</a>を使用すると、ドキュメントのソースを変更するスクリプトを提供することでドキュメントを更新できます。このAPIを使用して、フィールドをnullに設定して、ドキュメントからフィールドを削除できます。以下はその手順のステップ別のガイドです。</p><p>1. 更新するドキュメントのインデックス、ドキュメント タイプ (Elasticsearch 6.x 以前を使用している場合)、およびドキュメント ID を特定します。</p><p>2. フィールドを null に設定するスクリプト、またはソース ドキュメントからフィールドを削除するスクリプトで Update API を使用します。次の例は、「my_index」インデックス内の ID「1」のドキュメントから「field_to_delete」フィールドを削除する方法を示しています。</p><pre><code>POST /my_index/_update/1
{
  "script": "ctx._source.remove('field_to_delete')"
}</code></pre><p>3. リクエストを実行します。成功した場合、Elasticsearch はドキュメントが更新されたことを示す応答を返します。</p><p>注: このメソッドは、指定されたドキュメントからフィールドのみを削除します。フィールドはマッピングおよびインデックス内の他のドキュメントに引き続き存在します。</p><h2>方法2：変更されたソースでの再インデックス</h2><p>インデックス内のすべてのドキュメントからフィールドを削除したい場合、<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-reindex">Reindex API</a>を使用して、変更されたソースで新しいインデックスを作成できます。方法は以下の通りです。</p><p>1. 元のインデックスと同じ設定とマッピングを持つ新しいインデックスを作成します。Get Index API を使用して、元のインデックスの設定とマッピングを取得できます。</p><p>2. Reindex API を使用して、ソースからフィールドを削除しながら、元のインデックスから新しいインデックスにドキュメントをコピーします。次の例は、「my_index」インデックス内のすべてのドキュメントから「field_to_delete」フィールドを削除する方法を示しています。</p><pre><code>POST /_reindex
{
  "source": {
    "index": "my_index"
  },
  "dest": {
    "index": "new_index"
  },
  "script": {
    "source": "ctx._source.remove('field_to_delete')"
  }
}</code></pre><p><br/>3. 新しいインデックスに、フィールドが削除された正しいドキュメントが含まれていることを確認します。</p><p>4. すべてが問題なければ、元のインデックスを削除し、必要に応じて、元のインデックス名を持つエイリアスを新しいインデックスに追加できます。</p><h2>方法3：マッピングの更新と再インデックス</h2><p>マッピングからフィールドとインデックス内のすべてのドキュメントを削除する場合は、マッピングを更新してからドキュメントのインデックスを再作成できます。やり方は次のとおりです:</p><p>1. 元のインデックスと同じ設定で新しいインデックスを作成します。</p><p>2. Get Mapping API を使用して、元のインデックスのマッピングを取得します。</p><p>3. 削除するフィールドを削除してマッピングを変更します。</p><p>4. Put Mapping API を使用して、変更したマッピングを新しいインデックスに適用します。</p><p>5. 方法 2 の説明に従って、Reindex API を使用して、元のインデックスから新しいインデックスにドキュメントをコピーします。</p><p>6. 新しいインデックスに、フィールドが削除された正しいドキュメントが含まれていること、およびマッピングにフィールドが存在しないことを確認します。</p><p>7. 問題がなければ元のインデックスを削除し、必要に応じて元のインデックス名でエイリアスを追加できます。</p><h2>まとめ</h2><p>この記事では、Elasticsearch のドキュメントからフィールドを削除する 3 つの方法 (Update API の使用、変更されたソースによる再インデックス、マッピングの更新と再インデックス) について説明しました。それぞれの方法には独自の使用例とトレードオフがあるため、要件に最も適したものを選択してください。変更を本番環境に適用する前に、必ず変更をテストし、結果を確認してください。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearchで2つのインデックスを結合する方法]]></title>
            <description><![CDATA[Elasticsearch で 2 つのインデックスを結合するための用語クエリ、Logstash elasticsearch フィルター、エンリッチ プロセッサ、ES|QL の使用方法を説明します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-join-two-indexes</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-join-two-indexes</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Carly Richmond]]></dc:creator>
            <pubDate>Wed, 07 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0d4736d10fa3e12e6233cd59993299c7bd48911b-680x450.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch では、2 つのインデックスを結合することは、従来の SQL リレーショナル データベースほど簡単ではありません。ただし、Elasticsearch が提供する特定のテクニックと機能を使用すれば、同様の結果を得ることは可能です。</p><p>歴史的に、多くの人々は、異なるインデックスを結合するメカニズムとして<a href="https://www.elastic.co/jp/docs/reference/elasticsearch/mapping-reference/nested"><code>nested</code>フィールド タイプ</a>を使用してきました。しかし、クエリのコストが高く、Kibana、特にLensの視覚化のサポートが不完全であるため、制限がありました。</p><p>この記事では、Elasticsearch で 2 つのインデックスを結合するプロセスを詳しく説明し、次のアプローチに焦点を当てます。 </p><ol><li><code>terms</code>クエリの使用</li><li>取り込みパイプラインで<code>enrich</code>プロセッサを使用する</li><li>Logstash <code>elasticsearch</code>フィルター プラグイン</li><li>ES|QL <code>ENRICH</code></li><li>ES|QL <code>LOOKUP JOIN</code></li></ol><h2>用語クエリの使用</h2><p><a href="https://www.elastic.co/jp/docs/reference/query-languages/query-dsl/query-dsl-terms-query">用語クエリは</a>、Elasticsearch で 2 つのインデックスを結合する最も効果的な方法の 1 つです。このクエリは、特定のフィールドに 1 つ以上の正確な用語を含むドキュメントを取得するために使用されます。ここでは、これを使用して 2 つのインデックスを結合する方法について説明します。</p><p>まず、最初のインデックスから必要なデータを取得する必要があります。これは、単純な GET リクエストを使用して<code>_source</code>属性から値を取得することで実行できます。</p><pre><code># Simple GET request
GET first_index/_search</code></pre><p>最初のインデックスからデータを取得したら、それを使用して 2 番目のインデックスをクエリできます。これは、一致させるフィールドと値を指定する<code>terms</code>クエリを使用して行われます。</p><p>次に例を示します。</p><pre><code>GET second_index/_search
{
  "query": {
    "terms": {
      "field_in_second_index": ["value1_from_first_index", "value2_from_first_index"]
    }
  }
}</code></pre><p><br/>この例では、 <code>field_in_second_index</code> 、最初のインデックスの値と一致させる 2 番目のインデックスのフィールドです。<code>value1_from_first_index</code>と<code>value2_from_first_index</code> 、2 番目のインデックスで一致させる最初のインデックスの値です。</p><p>用語クエリは<a href="https://www.elastic.co/jp/docs/reference/query-languages/query-dsl/query-dsl-terms-query#query-dsl-terms-lookup">、用語ルックアップ</a>と呼ばれる手法を使用して、上記の 2 つの手順を 1 回のショットで実行するためのサポートも提供します。Elasticsearch は、別のインデックスから一致する値を透過的に取得します。たとえば、プレーヤーのリストを含むチーム インデックスがある場合:</p><pre><code>PUT teams/_doc/team1
{
  "players":   ["john", "bill", "michael"]
}
PUT teams/_doc/team2
{
  "players":   ["aaron", "joe", "donald"]
}</code></pre><p>以下に示すように、team1 でプレイしているすべての人々の人インデックスをクエリすることができます。</p><pre><code>GET people/_search?pretty
{
  "query": {
    "terms": {
        "name" : {
            "index" : "teams",
            "id" : "team1",
            "path" : "players"
        }
    }
  }
}</code></pre><p>上記の例では、Elasticsearchはチームインデックス内のID team1 を持つドキュメントからプレーヤー名を透過的に取得します（つまり、たとえば、「john」、「bill」、「michael」など) を検索し、名前フィールドにこれらの値のいずれかを含む人物インデックス内のすべてのドキュメントを検索します。</p><p>興味がある方のために、同等の SQL クエリは次のようになります。</p><pre><code>SELECT p.* FROM people p
INNER JOIN teams t ON p.name = t.players</code></pre><h2>エンリッチプロセッサの使用</h2><p><a href="https://www.elastic.co/jp/docs/reference/enrich-processor/enrich-processor"><code>enrich</code>プロセッサは</a>、Elasticsearch 内の 2 つのインデックスを結合するために使用できるもう 1 つの強力なツールです。このプロセッサは、事前に定義されたエンリッチ インデックスからデータを追加することで、受信ドキュメントのデータをエンリッチします。</p><p>エンリッチ プロセッサを使用して 2 つのインデックスを結合する方法は次のとおりです。</p><p>1. まず、エンリッチポリシーを作成する必要があります。このポリシーは、エンリッチメントに使用するインデックス、一致させるフィールド、および受信ドキュメントのエンリッチメントに使用するフィールドを定義します。</p><p>次に例を示します。</p><pre><code>PUT _enrich/policy/my_enrich_policy
{
  "match": {
    "indices": "first_index",
    "match_field": "field_in_first_index",
    "enrich_fields": ["field_to_enrich"]
  }
}</code></pre><p>2. ポリシーが作成されたら、それを実行して、新しく作成されたポリシーからエンリッチ インデックスを作成する必要があります。</p><pre><code>PUT _enrich/policy/my_enrich_policy/_execute</code></pre><p>これにより、エンリッチメント中に使用される新しい非表示のエンリッチメント インデックスが構築されます。ソース インデックスのサイズによっては、この操作に時間がかかる場合があります。次のステップに進む前に、エンリッチポリシーが完全に構築されていることを確認してください。</p><p>3. エンリッチポリシーを構築したら、取り込みパイプラインでエンリッチプロセッサを使用して、受信ドキュメントのデータをエンリッチできます。</p><pre><code>PUT _ingest/pipeline/my_pipeline
{
  "processors": [
    {
      "enrich": {
        "policy_name": "my_enrich_policy",
        "field": "field_in_second_index",
        "target_field": "enriched_field"
      }
    }
  ]
}</code></pre><p>この例では、 <code>field_in_second_index</code> 、最初のインデックスの<code>match_field</code>と一致する必要がある 2 番目のインデックスのフィールドです。<code>enriched_field</code> 、最初のインデックスの<code>enrich_fields</code>から拡張されたデータを格納する、2 番目のインデックスの新しいフィールドです。</p><p>このアプローチの欠点の 1 つは、 <code>first_index</code>のデータが変更された場合、エンリッチ ポリシーを再実行する必要があることです。エンリッチされたインデックスは、その構築元となったソース インデックスから自動的に更新または同期されることはありません。ただし、 <code>first_index</code>が比較的安定している場合は、このアプローチはうまく機能します。</p><h2>Logstash elasticsearch フィルター プラグイン</h2><p>Logstash を使用する場合、上記の<code>enrich</code>プロセッサに似た別のオプションとして、 <code>elasticsearch</code>フィルター プラグインを使用して、指定されたクエリに基づいてイベントに関連フィールドを追加する方法があります。Logstash パイプラインの構成は、 <code>my-pipeline.conf</code>などの<code>.conf</code>ファイルに保存されます。</p><p>パイプラインが<a href="https://www.elastic.co/jp/docs/reference/logstash/plugins/plugins-inputs-elasticsearch"><code>elasticsearch</code>入力プラグイン</a>を使用して Elasticsearch からログを取得し、選択範囲を絞り込むクエリを実行しているとします。</p><pre><code>input {
  # Read all documents from Elasticsearch matching the given query
  elasticsearch {
    hosts => "localhost"
    query => '{ "query": { "match": { "statuscode": 200 } }, "sort": [ "_doc" ] }'
  }
}</code></pre><p>特定のインデックスからの情報を使用してこれらのメッセージを拡充したい場合は、 <code>filter</code>セクションの<a href="https://www.elastic.co/jp/docs/reference/logstash/plugins/plugins-filters-elasticsearch"><code>elasticsearch</code>フィルター プラグイン</a>を使用してログを拡充できます。</p><pre><code>filter {
   elasticsearch {
      hosts => ["localhost"]
      index => "index_name"
      query => "type:start AND operation:%{[opid]}"
      fields => { "@timestamp" => "started" }
   }
}</code></pre><p>上記のコードは、インデックス<code>index_name</code>から、 <code>type</code>が開始され、操作フィールドが指定された<code>opid</code>と一致するドキュメントを検索し、 <code>@timestamp</code>フィールドの値を<code>started</code>という名前の新しいフィールドにコピーします。</p><p>強化されたドキュメントは適切な出力ソース（この場合は<a href="https://www.elastic.co/jp/docs/reference/logstash/plugins/plugins-outputs-elasticsearch"><code>elasticsearch</code>出力プラグイン</a>を使用して Elasticsearch ）に送信されます。</p><pre><code>output {
    elasticsearch {
        hosts => "localhost"
        data_stream => "true"
    }
}</code></pre><p>すでに Logstash を使用している場合、このオプションは、エンリッチメント ロジックを 1 か所に統合し、新しいイベントが発生したときに処理するのに役立ちます。ただし、そうでない場合は、ソリューションが複雑になり、実行および保守する必要がある別のコンポーネントが追加されることになります。</p><h2>ES|QL エンリッチ</h2><p>バージョン 8.14 で GA となった<a href="https://www.elastic.co/jp/docs/explore-analyze/query-filter/languages/esql">ES|QL</a>は、Elasticsearch でサポートされるパイプ クエリ言語であり、データのフィルタリング、変換、分析を可能にします。ENRICH 処理コマンドを使用すると、エンリッチ ポリシーを使用して既存のインデックスからデータを追加できます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/03aadddc08afffff3f6526c9c052999c97fa09dd-1600x989.png" style="width: 100%;" /><p>元のエンリッチ プロセッサの例と同じポリシー<code>my_enrich_policy</code>を使用すると、ES|QL の例は次のようになります。</p><pre><code>FROM first_index
| WHERE field_in_first_index IS NOT NULL
| ENRICH my_enrich_policy</code></pre><p>一致フィールドとエンリッチメント フィールド (この例ではそれぞれ<code>field_in_first_index</code>と<code>field_to_enrich</code>をオーバーライドすることもできます。</p><pre><code>FROM first_index
| WHERE field_in_first_index IS NOT NULL
| ENRICH my_enrich_policy ON another_field_in_first_index WITH different_field_to_enrich</code></pre><p>明らかな制限は、最初にエンリッチポリシーを指定する必要があることですが、ES|QL では、必要に応じてフィールドを微調整できる柔軟性が提供されます。</p><h2>ES|QL ルックアップ結合</h2><p>Elasticsearch 8.18 では、Elasticsearch でインデックスを結合する新しい方法、つまり<code>LOOKUP JOIN</code>コマンドが導入されました。このコマンドは、結合の右側にある新しい<a href="https://www.elastic.co/jp/docs/reference/elasticsearch/index-settings/index-modules#index-mode-setting">ルックアップ インデックス モード</a>を使用して、SQL スタイルの LEFT OUTER JOIN として動作します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/1d73495979c4d6bb675c4c966ea86d9a72dc1c48-510x605.png" style="width: 100%;" /><p>前の例をもう一度見てみると、新しいクエリは次のようになります。ここで、 <code>match_field</code> <code>first_index</code>と<code>second_index</code>両方に存在する必要があります。</p><pre><code>FROM first_index
| WHERE field_in_first_index IS NOT NULL
| LOOKUP JOIN second_index ON match_field</code></pre><p>LOOKUP JOIN が他のアプローチよりも優れている点は、 <code>enrich</code>ポリシーが不要であり、したがってポリシーの設定に関連する追加の処理も必要ないことです。これは、この記事で説明した他のアプローチとは異なり、頻繁に変更されるエンリッチメント データを扱う場合に役立ちます。</p><h2>まとめ</h2><p>結論として、Elasticsearch は従来の結合操作をサポートしていませんが、同様の結果を実現するために使用できるさまざまな機能を提供しています。具体的には、以下を使用して結合操作を実現する方法について説明しました。</p><ol><li><code>terms</code>クエリ</li><li>取り込みパイプラインの<code>enrich</code>プロセッサ</li><li>Logstash <code>elasticsearch</code>フィルター プラグイン</li><li>ES|QL <code>ENRICH</code></li><li>ES|QL <code>LOOKUP JOIN</code></li></ol><p>これらの方法には限界があり、特定の要件とデータの性質に基づいて慎重に使用する必要があることに注意することが重要です。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch のスコアリングと Explain API を理解する]]></title>
            <description><![CDATA[Elasticsearch のスコアリングメカニズムと、Explain API を使用して検索する関連性を監査し、ドキュメントのランキングを向上させるための実用的なスコアリング機能について学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-scoring-and-explain-api</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-scoring-and-explain-api</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Mon, 05 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a70c5403064d5bbceff66a17373332362227f13c-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch は、インデックス内の各ドキュメントのスコアを計算して、高速かつ関連性の高い検索結果を提供する強力な検索エンジンです。このスコアは検索結果の順序を決定する上で重要な要素となります。この記事では、Elasticsearch のスコアリング メカニズムを詳しく説明し、スコアリング プロセスを理解するのに役立つ Explain API について説明します。</p><h2>Elasticsearchのスコアリングメカニズム</h2><p>Elasticsearch は、デフォルトで Practical Scoring Function (BM25) と呼ばれるスコアリング モデルを使用します。このモデルは確率的情報検索理論に基づいており、用語頻度、逆文書頻度、フィールド長の正規化などの要素を考慮に入れています。これらの要因について簡単に説明しましょう。</p><ol><li><strong>用語頻度 (TF):</strong>これは、ドキュメント内で用語が出現する回数を表します。用語の頻度が高いほど、用語とドキュメントの関係が強くなることを示します。</li><li><strong>逆文書頻度 (IDF):</strong>この要素は、文書コレクション全体における用語の重要度を測定します。多くのドキュメントに出現する用語は重要度が低いとみなされ、より少ないドキュメントに出現する用語は重要度が高いとみなされます。</li><li><strong>フィールド長の正規化</strong>: この要素は、用語が表示されるフィールドの長さを考慮します。短いフィールドでは用語がより重要であるとみなされるため、短いフィールドにはより大きな重みが与えられます。</li></ol><h2>Explain APIの使用</h2><p>Elasticsearch の Explain API は、スコアリング プロセスを理解するための貴重なツールです。特定のドキュメントのスコアがどのように計算されたかについての詳細な説明を提供します。Explain API を使用するには、次のエンドポイントに GET リクエストを送信する必要があります。</p><pre><code>GET /<index>/_explain/<document_id></code></pre><p>リクエスト本文には、スコアリングを理解したいクエリを指定する必要があります。次に例を示します。</p><pre><code>{
  "query": {
    "match": {
      "title": "elasticsearch"
    }
  }
}</code></pre><p>Explain API からの応答には、個々の要素 (TF、IDF、フィールド長の正規化) とそれらが最終スコアに与える影響など、スコアリング プロセスの詳細な内訳が含まれます。応答の例は次のとおりです。</p><pre><code>{
  "_index": "example_index",
  "_type": "_doc",
  "_id": "1",
  "matched": true,
  "explanation": {
    "value": 1.2,
    "description": "weight(title:elasticsearch in 0) [PerFieldSimilarity], result of:",
    "details": [
      {
        "value": 1.2,
        "description": "score(doc=0,freq=1.0 = termFreq=1.0\n), product of:",
        "details": [
          {
            "value": 2.2,
            "description": "idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:",
            "details": [
              {
                "value": 1,
                "description": "docFreq",
                "details": []
              },
              {
                "value": 1,
                "description": "docCount",
                "details": []
              }
            ]
          },
          {
            "value": 0.5,
            "description": "tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:",
            "details": [
              {
                "value": 1,
                "description": "termFreq=1.0",
                "details": []
              },
              {
                "value": 1.2,
                "description": "parameter k1",
                "details": []
              },
              {
                "value": 0.75,
                "description": "parameter b",
                "details": []
              },
              {
                "value": 1,
                "description": "avgFieldLength",
                "details": []
              },
              {
                "value": 1,
                "description": "fieldLength",
                "details": []
              }
            ]
          }
        ]
      }
    ]
  }
}</code></pre><p>この例では、応答は、スコア 1.2 が IDF 値 (2.2) と tfNorm 値 (0.5) の積であることを示しています。詳細な説明は、スコアに影響を与える要因を理解するのに役立ち、検索の関連性を微調整するのに役立ちます。</p><h2>まとめ</h2><p>Elasticsearch スコアリングは、関連性の高い検索結果を提供する上で重要な要素です。スコアリング メカニズムを理解し、Explain API を使用することで、検索結果に影響を与える要因についての洞察を得て、検索クエリを最適化し、関連性とパフォーマンスを向上させることができます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch のインデックステンプレート: 構成可能なテンプレートの使い方]]></title>
            <description><![CDATA[Elasticsearchでコンポーザブルおよびコンポーネントインデックステンプレートを作成する方法を探り、マッピングの一貫性を確保し、インデックス設定を自動化する方法を学びましょう。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/index-composable-templates</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/index-composable-templates</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Fri, 02 May 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/510750708df50bf79463586a1bbf35bf94acfa30-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch インデックスは、マッピング、設定、エイリアスを通じて構成できます。</p><ul><li>マッピング定義はデータ スキーマを指定します。</li><li>設定では、シャードのサイズと更新レートを設定します。</li><li>エイリアスは、インデックスに別の名前を付けるために使用されます。</li></ul><p>初めてドキュメントのインデックスを作成するとき、または Create Index API を使用して空のインデックスを作成するときは、データ スキーマとエイリアスのないデフォルト設定でインデックスが作成されます。これらのデフォルトは開発環境やテスト環境では適切に機能しますが、実稼働環境の場合はインデックスをカスタマイズする必要がある場合があります。</p><p>運用環境でデフォルトのマッピングと設定を使用すると、インデックス作成と検索のパフォーマンスが低下する可能性があります。インデックスを手動でインスタンス化するのは面倒で時間のかかるプロセスです。複雑なマッピング スキーマやカスタマイズされた設定およびエイリアスがある場合、このようなインデックスをすべての環境で再作成することは特に非現実的です。</p><p>幸いなことに、Elasticsearch には、<em>インデックステンプレートの形式でインデックスを作成するときに、事前定義された構成を自動的に適用するツールが用意されています。</em></p><h2>インデックステンプレート</h2><p>インデックス テンプレートを使用すると、ユーザー定義の構成でインデックスを作成できます。インデックスは、インスタンス化中に、これらのテンプレートから構成 (シャードとレプリカの数の設定、フィールド マッピングなど) を取得できます。テンプレートは、名前パターンといくつかの構成で定義されます。インデックスの名前がテンプレートの命名パターンと一致する場合、テンプレートで定義された構成で新しいインデックスが作成されます。</p><p>Elasticsearch はバージョン 7.8 で、コンポーザブル テンプレートを使用してテンプレート機能をアップグレードしました。この新しいバージョンでは、この記事で示されているように、より再利用可能なインデックス テンプレートが提供されます。</p><h3>インデックステンプレートの種類</h3><p>インデックス テンプレートは、次の 2 つのカテゴリに分類できます。</p><ul><li><strong>インデックス テンプレート (または構成可能なインデックス テンプレート)</strong> : 構成可能なインデックス テンプレートは、単独で存在することも、0 個以上のコンポーネント テンプレートで構成することもできます (2 番目のカテゴリを参照)。</li><li><strong>コンポーネント テンプレート:</strong>コンポーネント テンプレートは、必要な構成を定義する、それ自体が<em>再利用可能な</em>テンプレートです。通常、コンポーネント テンプレートはインデックス テンプレートに関連付けられることが想定されます。各コンポーネント テンプレートには、1 つまたは複数のインデックス テンプレートを添付できます。</li></ul><p>下の画像でわかるように、インデックス テンプレート A と B はコンポーネント テンプレート (この場合はテンプレート 3 の 1 つだけ) を共有しています。インデックス テンプレートは、0 個以上のコンポーネント テンプレートで構成でき、各コンポーネント テンプレートは、0 個以上のインデックス テンプレートに関連付けることができます。どちらのタイプのテンプレートも単独で存在できますが、コンポーネント テンプレートはインデックス テンプレートに添付されない限り役に立ちません。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/96c0aac29d3992e54a79be34e14cf909e0ca2ea9-1202x556.png" style="width: 100%;" /><p>基本的な考え方は、組織がさまざまなニーズに合わせて使用できるコンポーネント テンプレートのカタログを開発し (たとえば、個々の環境に合わせてさまざまなコンポーネント テンプレートを指定する)、それらを構成可能なインデックス テンプレートを介してさまざまなインデックスに添付することです。</p><h2>構成可能な（インデックス）テンプレートを作成する方法</h2><p>Elasticsearch は、インデックス テンプレートを管理するための _index_template エンドポイントを提供します。ユーザーは、このテンプレートでインデックス名パターンとともに、必要なすべてのマッピング、設定、エイリアスを指定します。注文生成ロジックを担当するマイクロサービス アプリケーション<em>customer-order-service</em>のテンプレートを作成する例を見てみましょう。</p><p>ワイルドカード *orders を含むパターンで表される顧客注文のテンプレートを作成する必要があるとします。このテンプレートには、order_date フィールド、シャード、レプリカ番号などの特定のマッピングと設定が含まれている必要があります。</p><p>作成中にこのテンプレートと一致するインデックスはすべて、このテンプレートで定義された構成を継承します。たとえば、black_friday_orders インデックスには order_date フィールドがあり、シャードは 5 に設定され、レプリカは 2 に設定されます。これに加えて、このテンプレートから作成された<em>すべての</em>インデックスも単一の<a href="https://opster.com/guides/elasticsearch/glossary/elasticsearch-alias/">エイリアス</a>名を継承します。*orders として定義されたインデックス パターンと、定義済みの日付形式 dd-MM-yyyy を持つ単一の oder_date フィールドで構成されるマッピング スキーマを使用して、この orders_template を作成しましょう。以下のコードは、このインデックス テンプレートを作成する方法を示しています。</p><pre><code>PUT _index_template/orders_template
{
  "index_patterns": ["*orders"],
  "priority": 300,
  "template": {
    "mappings": {
      "properties": {
        "order_date": {
          "type": "date",
          "format":"dd-MM-yyyy"
        }
      }
    },
    "settings":{
      "number_of_shards":5,
      "number_of_replicas":2
    },
    "aliases":{
      "all_orders":{}
    }
  }
}</code></pre><p>Kibana の DevTools でこのクエリを実行すると、事前定義されたマッピング、設定、エイリアスとともに、インデックス パターン *orders を使用してテンプレートが作成されます。index_patterns は一致パターンの配列です。このパターンに一致するインデックスはテンプレート構成を派生します。実行した内容を繰り返す永続テンプレートを取得するには、以下を実行します。</p><pre><code>GET _index_template/orders_template </code></pre><p>テンプレートに定義されたテンプレート属性を作成するときに定義される、正の数値の優先度もあります。すべてのテンプレートには優先度が定義されるため、異なるテンプレートからの競合する変更は、この値を使用して解決され、より高い優先度の値が優先されます。テンプレートの優先順位については、以下でさらに詳しく説明します。</p><h2>テンプレートを使用してインデックスを作成する</h2><p>これで、インデックスを作成するための設計図であるテンプレートができました。次のステップは、インデックスを作成することです。インデックスの名前が指定されたパターンと一致すると、テンプレート化された構成が自動的に適用されます。この点を証明するために、以下のコードに示すように、blackfriday_orders という名前の新しいインデックスを作成しましょう。</p><pre><code>PUT blackfriday_orders</code></pre><p>インデックスの名前（blackfriday_orders）はテンプレートで定義された命名パターンと一致します（つまり*orders) の場合、インデックスはテンプレートから派生したすべての構成を取得する必要があります。この新しく作成されたインデックスを取得し、次のコードを実行してこれが本当に当てはまるかどうかを確認しましょう。</p><pre><code>GET blackfriday_orders</code></pre><p>次のように返されます:</p><pre><code>{
  "blackfriday_orders" : {
    "aliases" : {
      "all_orders" : { }
    },
    "mappings" : {
      "properties" : {
        "order_date" : {
          "type" : "date",
          "format" : "dd-MM-yyyy"
        }
      }
    },
    "settings" : {
      "index" : {
         ...
        "number_of_shards" : "5",
        "number_of_replicas" : "2"
      }
    }
  }
}</code></pre><p>応答が示すように、blackfriday_orders の構成はテンプレートから継承されています。テンプレート構成を正常に継承するインデックスのさまざまな組み合わせを試すことができます。</p><pre><code>PUT blackfriday_orders
PUT americaorders
PUT cancelled--orders
PUT undefined101orders</code></pre><p>ただし、次のインデックスは名前がパターンと一致しないため、構成を継承しません。</p><pre><code>PUT blackfriday_orders2
PUT open_orders_
PUT allorders_total</code></pre><p>覚えておくべき重要な点の 1 つは、この場合、テンプレートから派生したすべてのインデックスが同じエイリアス (all_orders) を持つことです。このようなエイリアスを使用すると、複数のインデックスではなく、この単一のエイリアスに対してクエリを実行できるという利点があります。</p><pre><code>GET blackfriday_orders,americaorders,undefined101orders/_search
GET all_orders/_search 
{
  "query": {
    "range": {
      "order_date": {
        "gte": "01-12-2021",
        "lte": "31-12-2021"
      }
    }
  }
}</code></pre><p>*orders のテンプレートを作成する際、一致するインデックスはテンプレート構成を採用することが期待されます。通常、チームは、意識的か無意識的かを問わず、さまざまな理由でさらにいくつかのテンプレートを作成することがあります。つまり、インデックス名が 2 つの異なるテンプレート パターンと一致する場合があるということです。Elasticsearch は、これらのテンプレートからどの構成を適用する必要があるかを決定する必要があります。幸いなことに、このジレンマはテンプレートの優先順位を使用することで解決できます。</p><h2>コンポーネントテンプレートの作成方法</h2><p>この記事の前半でインデックス テンプレートについて学習しました。構成が組み込まれたテンプレートを作成することにはいくつかの欠点があります。その 1 つは、構成を他のテンプレートにエクスポートできないことです。たとえば顧客関連のテンプレート（*customers）に対して同様の構成を希望する場合は、テンプレート全体を再作成する必要がある場合があります。つまり、典型的な組織では数十個のコンポーネントが作成されることになります (環境によっては、さらに数個になることもあります)。</p><p>私たちは常に再利用性を重視しているため、Elasticsearch は再利用性を念頭に置いてテンプレートを再設計しました。コンポーネント テンプレートはまさにその要件を満たしています。DevOps の経験がある場合、環境ごとに事前設定された構成でインデックスを作成する必要がある可能性が高くなります。これらの各構成を手動で面倒に適用するのではなく、環境ごとにコンポーネント テンプレートを作成できます。</p><p>コンポーネント テンプレートは、より多くのインデックス テンプレートを作成するために使用できる再利用可能な構成ブロックです。コンポーネント テンプレートは、インデックス テンプレートと組み合わせない限り価値がないことに注意してください。これらは _component_template エンドポイントを介して公開されます。これらすべてがどのように組み合わさるかを見てみましょう。</p><h3>インデックステンプレート内の設定</h3><p>先ほどインデックス テンプレートで定義した設定を抽出し、そこからコンポーネント テンプレートを作成しましょう。settings_component_template には、プライマリ シャードごとに 2 つのレプリカを持つ 5 つのプライマリ シャードがあることが想定されています。最初のステップは、以下のコード リストに示すように、この構成でコンポーネント テンプレートを宣言して実行することです。</p><pre><code>PUT _component_template/settings_component_template
{
  "template":{
    "settings":{
      "number_of_shards":5,
      "number_of_replicas":2
    }
  }
}</code></pre><p>上記のコードが示すように、_component_template エンドポイントを使用してコンポーネント テンプレートを作成します。リクエストの本文には、テンプレート オブジェクト内のテンプレート情報が保持されます。これで、settings_component_template はインデックス テンプレートの他の場所でも使用できるようになりました。注目すべき違いの 1 つは、このテンプレートではインデックス パターンが定義されていないことです。これは、いくつかのプロパティを構成する単なるコード ブロックです。</p><h3>マッピングテンプレート</h3><p>同じように、もう一つテンプレートを作成しましょう。今回は、スタンドアロン インデックス テンプレートで以前に定義したマッピング スキーマを抽出しましょう。以下のコードはスクリプトを示しています。</p><pre><code>PUT _component_template/mappings_component_template
{
  "template": {
    "mappings": {
      "properties": {
        "order_date": {
          "type": "date",
          "format":"dd-MM-yyyy"
        }
      }
    }
  }
}</code></pre><h3>エイリアステンプレート</h3><p>同じフローで、エイリアス（2 つのエイリアス（all_orders と sales_orders））を持つコンポーネント テンプレートも作成できます。</p><pre><code>PUT _component_template/aliases_component_template
{
  "template": {
    "aliases": {
      "all_orders": {},
      "sales_orders":{}
    }
  }
}</code></pre><h3>構成可能なインデックステンプレート</h3><p>これで 3 つのコンポーネント テンプレートができたので、次のステップではそれらを使用します。これを実現するには、たとえば christmas_orders のインデックス テンプレートでこれを使用できるようにします。</p><pre><code>PUT _index_template/composed_orders_template
{
  "index_patterns": [
    "*orders"
  ],
  "priority": 500,
  "composed_of": [
    "settings_component_template",
    "mappings_component_template",
    "aliases_component_template"
  ]
}</code></pre><p>compose_of タグは、このテンプレートを構成するすべてのコンポーネント テンプレートのコレクションです。この場合は、設定、マッピング、エイリアスのコンポーネント テンプレートを選択します。また、このテンプレートが他のテンプレートよりも優先されるように、優先順位を上げています。テンプレートの準備が整うと、*orders パターンに一致するすべてのインデックスは、これら 3 つのコンポーネント テンプレートから構成を継承します。</p><p>そうは言っても、既存のテンプレート (settings_component_template) と新しく作成したエイリアス テンプレート (aliases_component_template – 以下を参照) のいずれか 1 つを使用して、新しいテンプレート (たとえば、顧客) を作成したい場合は、次のようにします。</p><pre><code>PUT _component_template/aliases_component_template2
{
  "template": {
    "aliases": {
      "all_customers": {}
    }
  }
}</code></pre><p>インデックス テンプレートは次のようになります。</p><pre><code>PUT _index_template/composed_customers_template
{
  "index_patterns": [
    "*customers*"
  ],
  "priority": 200,
  "composed_of": [
    "settings_component_template",
    "aliases_component_template2"
  ]
}</code></pre><p>settings_component_template が 2 つの異なるテンプレートで (再) 使用されていることに気付きましたか?それがコンポーネント テンプレートの力です。</p><h2>インデックステンプレートの優先度</h2><p>開発者が既存のストックを確認せずに複数のインデックス テンプレートを作成する可能性があります。これらのテンプレートそれぞれに優先順位を設定し、優先順位の高いテンプレートが使用されるようにすることが重要です。たとえば、次のコード スニペットでは、my_orders_template_1 が my_orders_template_2 をオーバーライドします。</p><pre><code>PUT _index_template/my_orders_template_1
{
  "index_patterns": ["*orders"],
  "priority": 1000,
  "template": { ... }
}
PUT _index_template/my_orders_template2
{
  "index_patterns": ["*orders"],
  "priority": 300,
  "template": { ... }
}</code></pre><p>作成中のインデックスに一致するテンプレートが複数ある場合、Elasticsearch は一致するすべてのテンプレートのすべての設定を適用しますが、優先度の高い設定は上書きします。</p><h2>テンプレートの優先順位</h2><p>最後に、テンプレートの優先順位について疑問に思われるかもしれません。コンポーネント テンプレートで定義された構成は、メイン インデックス テンプレート自体で定義された構成を上書きするのでしょうか。それとも逆でしょうか?まあ、いくつかルールがあります:</p><ul><li>明示的に構成を作成して作成したインデックスは、すべてに優先します。つまり、明示的に構成を作成してインデックスを作成した場合、テンプレートによってそのインデックスが上書きされることは想定しないでください。</li><li>レガシー テンプレート (バージョン 7.8 より前に作成されたテンプレート) は、コンポーザブル テンプレートよりも優先順位が低くなります。</li></ul><h2>まとめ</h2><ul><li>インデックスには、マッピング、設定、エイリアスが含まれます。マッピングはフィールド スキーマを定義し、設定はシャードやレプリカの数などのインデックス パラメータを設定し、エイリアスはインデックスに別名を与えます。</li><li>テンプレートを使用すると、事前定義された構成でインデックスを作成できます。特定のテンプレート内で定義されたインデックス パターンと一致する名前でインデックスに名前を付けると、そのインデックスはテンプレートに従って自動的に構成されます。</li><li>Elasticsearch はバージョン 7.8 で構成可能なインデックス テンプレートを導入しました。構成可能なインデックス テンプレートにより、テンプレートのモジュール化とバージョン管理が可能になります。</li><li>構成可能なテンプレートは、0 個以上のコンポーネント テンプレートで構成されます。</li><li>インデックス テンプレートにも独自の構成を定義することができます。</li><li>コンポーネント テンプレートは、コンポーザブル インデックス テンプレートと同様に、事前定義された構成を持つ再利用可能なテンプレートです。</li><li>ただし、コンポーネント テンプレートはインデックス テンプレートの一部であることが想定されており、インデックス テンプレートに「構成」されていない場合は役に立ちません。</li><li>コンポーネント テンプレートにはインデックス パターンが定義されていません。これが、コンポーネント テンプレートがインデックス テンプレートの一部であると「想定」されるもう 1 つの理由です。</li><li>各テンプレートには優先度（正の数字）があります。数値が大きいほど、そのテンプレートが適用される優先順位が高くなります。</li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[2つのフィールドによるElasticsearch検索]]></title>
            <description><![CDATA[複数一致クエリ、boolクエリ、クエリタイムフィールドブースティングなど、2つのフィールドで検索する技術を探ります。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-search-by-two-fields</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-search-by-two-fields</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Wed, 30 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d5d04bbcfc3925f48f3487ea4c7e0dd2205316d0-720x420.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch で複数のフィールドを検索することは、多くのアプリケーションで一般的な要件です。この記事では、複数一致クエリ、ブールクエリ、クエリ時のフィールドブースティングなど、2 つのフィールドで検索を実行するための高度な手法について説明します。これらのテクニックは、ユーザーにとってより正確で関連性の高い検索結果を作成するのに役立ちます。</p><h2>2つのフィールドで検索を実行する高度なテクニック</h2><h3>1. 複数一致クエリ</h3><p>複数一致クエリを使用すると、複数のフィールドにわたって単一のクエリ文字列を検索できます。これは、2 つのフィールドのいずれかに指定されたクエリ文字列を含むドキュメントを検索する場合に便利です。以下は、「title」または「description」フィールドで「example」という用語を検索する複数一致クエリの例です。</p><pre><code>{
  "query": {
    "multi_match": {
      "query": "example",
      "fields": ["title", "description"]
    }
  }
}</code></pre><h3>2. ブールクエリ</h3><p>bool クエリを使用すると、ブールロジックを使用して複数のクエリを組み合わせることができます。「should」句を使用すると、2 つのフィールドのいずれかでクエリに一致するドキュメントを検索できます。以下は、フィールド「title」と「description」で「example」という用語を検索するブールクエリの例です。</p><pre><code>{
  "query": {
    "bool": {
      "should": [
        {"match": {"title": "example"}},
        {"match": {"description": "example"}}
      ]
    }
  }
}</code></pre><h3>3. クエリ時のフィールドブースティング</h3><p>場合によっては、検索中にあるフィールドを他のフィールドよりも重視したいことがあります。これを実現するには、クエリ時にフィールドにブースト係数を適用します。ブースト値が高いほど、フィールドの重みが増し、最終的な検索スコアに影響を与える可能性が高くなります。以下は、「タイトル」フィールドにブースト係数を適用した複数一致クエリの例です。</p><pre><code>{
  "query": {
    "multi_match": {
      "query": "example",
      "fields": ["title^3", "description"]
    }
  }
}</code></pre><p>この例では、「タイトル」フィールドのブースト係数は 3 であり、検索スコアの決定において「説明」フィールドよりも 3 倍重要になります。</p><h3>4. 異なるブースト係数を持つクエリを組み合わせる</h3><p>bool クエリを使用して、異なるブースト係数を持つ複数のクエリを組み合わせることもできます。これにより、検索結果の各フィールドの重要性を微調整できます。以下は、「title」フィールドと「description」フィールドに異なるブースト係数を適用したブールクエリの例です。</p><pre><code>{
  "query": {
    "bool": {
      "should": [
        {"match": {"title": {"query": "example", "boost": 3}}},
        {"match": {"description": {"query": "example", "boost": 1}}}
      ]
    }
  }
}</code></pre><p>この例では、「タイトル」フィールドのブースト係数は 3 ですが、「説明」フィールドのブースト係数は 1 です。</p><h2>まとめ</h2><p>Elasticsearch での 2 つのフィールドによる検索は、マルチマッチ クエリ、ブール クエリ、クエリ時フィールド ブースティングなどの高度な手法を使用して実現できます。これらの技術を組み合わせることで、ユーザーにとってより正確で関連性の高い検索結果を作成できます。さまざまなクエリの組み合わせとブースト係数を試して、特定のユースケースに最適な検索構成を見つけます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[LangGraphとElasticsearchを使用したRAGワークフローの構築]]></title>
            <description><![CDATA[Elasticsearch を使用して LangGraph 検索エージェント テンプレートを構成およびカスタマイズし、効率的なデータ取得と AI 駆動型応答のための RAG ワークフローを構築する方法を学習します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/build-rag-workflow-langgraph-elasticsearch</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/build-rag-workflow-langgraph-elasticsearch</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Fri, 25 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/17b7e7b336f73e232375d1add582ae5f6c52a279-1440x840.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><a href="https://github.com/langchain-ai/retrieval-agent-template">LangGraph 検索エージェント テンプレートは、</a> LangGraph Studio で LangGraph を使用して検索ベースの質問応答システムの作成を容易にするために LangChain によって開発されたスターター プロジェクトです。このテンプレートは Elasticsearch とシームレスに統合するように事前構成されているため、開発者はドキュメントを効率的にインデックスして取得できるエージェントを迅速に構築できます。</p><p>このブログでは、LangGraph Studio と LangGraph CLI を使用して LangChain 検索エージェント テンプレートを実行およびカスタマイズすることに焦点を当てています。このテンプレートは、Elasticsearch などのさまざまな検索バックエンドを活用して、検索拡張生成 (RAG) アプリケーションを構築するためのフレームワークを提供します。</p><p>エージェントフローをカスタマイズしながら、Elastic を使用してテンプレートを効率的にセットアップ、環境の構成、実行する方法を説明します。</p><h2>要件</h2><p>続行する前に、以下がインストールされていることを確認してください。</p><ul><li>Elasticsearch Cloud デプロイメントまたはオンプレミス Elasticsearch デプロイメント (または Elastic Cloud で 14 日間の<a href="https://www.elastic.co/jp/cloud/cloud-trial-overview">無料トライアル</a>を作成) - バージョン 8.0.0 以上</li><li>Python 3.9以上</li><li><a href="https://cohere.com/">Cohere</a> （このガイドで使用）、 <a href="https://openai.com/">OpenAI</a> 、 <a href="https://www.anthropic.com/claude">Anthropic/Claude</a>などのLLMプロバイダーへのアクセス</li></ul><h2>LangGraphアプリの作成</h2><h3>1. LangGraph CLIをインストールする</h3><pre><code>pip install --upgrade "langgraph-cli[inmem]"</code></pre><h3>2. 検索エージェントテンプレートからLangGraphアプリを作成する</h3><pre><code>mkdir lg-agent-demo
cd lg-agent-demo
langgraph new lg-agent-demo </code></pre><p><em>利用可能なテンプレートのリストから選択できるインタラクティブ メニューが表示されます。</em>以下に示すように、取得エージェントの場合は 4、Python の場合は 1 を選択します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6a41a41f95c2477c67810adc7be46d91faf06878-1600x407.png" style="width: 100%;" /><ul><li><strong>トラブルシューティング</strong>: 「urllib.error.URLError: &lt;urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: cannot get local issuer certificate (_ssl.c:1000)&gt;」というエラーが発生した場合「</li></ul><p>問題を解決するには、以下に示すように、Python の証明書インストール コマンドを実行してください。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/83ec238136c41738457299fd42c83aff32eb5b97-1407x75.png" style="width: 100%;" /><h3>3. 依存関係をインストールする</h3><p>新しい LangGraph アプリのルートで仮想環境を作成し、依存関係を<code>edit</code>モードでインストールして、ローカルの変更がサーバーで使用されるようにします。</p><pre><code>#For Mac
python3 -m venv lg-demo
source lg-demo/bin/activate 
pip install -e .

#For Windows
python3 -m venv lg-demo
lg-demo\Scripts\activate 
pip install -e .</code></pre><h2>環境の設定</h2><h3>1. .environmentを作成するファイル</h3><p><code>.env</code>ファイルには、アプリが選択した LLM および取得プロバイダーに接続できるようにするための API キーと構成が保持されます。サンプル設定を複製して新しい<code>.env</code>ファイルを生成します。</p><pre><code>cp .env.example .env</code></pre><h3>2. .envを設定するファイル</h3><p><code>.env</code>ファイルには、デフォルトの構成のセットが付属しています。設定に応じて必要な API キーと値を追加することで更新できます。ユースケースに関係のないキーは、変更せずにそのままにしておくことも、削除することもできます。</p><pre><code># To separate your traces from other applications
LANGSMITH_PROJECT=retrieval-agent

# LLM choice (set the API key for your selected provider):
ANTHROPIC_API_KEY=your_anthropic_api_key
FIREWORKS_API_KEY=your_fireworks_api_key
OPENAI_API_KEY=your_openai_api_key

# Retrieval provider (configure based on your chosen service):

## Elastic Cloud:
ELASTICSEARCH_URL=https://your_elastic_cloud_url
ELASTICSEARCH_API_KEY=your_elastic_api_key

## Elastic Local:
ELASTICSEARCH_URL=http://host.docker.internal:9200
ELASTICSEARCH_USER=elastic
ELASTICSEARCH_PASSWORD=changeme

## Pinecone:
PINECONE_API_KEY=your_pinecone_api_key
PINECONE_INDEX_NAME=your_pinecone_index_name

## MongoDB Atlas:
MONGODB_URI=your_mongodb_connection_string

# Cohere API key:
COHERE_API_KEY=your_cohere_api_key</code></pre><ul><li>サンプル<code>.env</code>ファイル (Elastic Cloud と Cohere を使用)</li></ul><p>以下は、このブログで説明されているように、 <strong>Elastic Cloud を</strong>取得プロバイダーとして使用し、 <strong>Cohere を</strong>LLM として使用するためのサンプルの<code>.env</code>構成です。</p><pre><code># To separate your traces from other applications
LANGSMITH_PROJECT=retrieval-agent
#Retrieval Provider
# Elasticsearch configuration
ELASTICSEARCH_URL=elastic-url:443
ELASTICSEARCH_API_KEY=elastic_api_key
# Cohere API key
COHERE_API_KEY=cohere_api_key</code></pre><p><em>注: このガイドでは、レスポンス生成と埋め込みの両方にCohereを使用していますが、ユースケースに応じて、<strong> OpenAI</strong> 、<strong> Claude</strong> 、あるいはローカルLLMモデル などの他のLLMプロバイダーも自由に使用できます 。使用する予定の各キーが<code>.env</code> ファイル に存在し、正しく設定されていることを確認してください。</em></p><h3>3. 設定ファイル -configuration.py を更新する </h3><p>適切な API キーを使用して<code>.env</code>ファイルを設定したら、次の手順ではアプリケーションのデフォルトのモデル構成を更新します。構成を更新すると、システムは<code>.env</code>ファイルで指定したサービスとモデルを使用するようになります。</p><p>構成ファイルに移動します。</p><pre><code> cd src/retrieval_graph</code></pre><p><code>configuration.py</code>ファイルには、検索エージェントが 3 つの主なタスクに使用するデフォルトのモデル設定が含まれています。</p><ul><li><strong>埋め込みモデル</strong>– ドキュメントをベクトル表現に変換する</li><li><strong>クエリモデル</strong>– ユーザーのクエリをベクトルに変換する</li><li><strong>レスポンスモデル</strong>– 最終的なレスポンスを生成する</li></ul><p>デフォルトでは、コードは<strong>OpenAI</strong> (例: <code>openai/text-embedding-3-small</code> ) と<strong>Anthropic</strong> (例: <code>anthropic/claude-3-5-sonnet-20240620 and anthropic/claude-3-haiku-20240307</code> ) のモデルを使用します。このブログでは、Cohere モデルの使用に切り替えます。すでに OpenAI または Anthropic を使用している場合は、変更は必要ありません。</p><h4>変更例（Cohere を使用）:</h4><p><code>configuration.py</code>を開き、以下のようにモデルのデフォルトを変更します。</p><pre><code>…
 embedding_model: Annotated[
       str,
       {"__template_metadata__": {"kind": "embeddings"}},
   ] = field(
       default="cohere/embed-english-v3.0",
…
response_model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field(
       default="cohere/command-r-08-2024",
…
query_model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field(
       default="cohere/command-r-08-2024",
       metadata={</code></pre><h2>LangGraph CLI で取得エージェントを実行する</h2><h3>1. LangGraphサーバーを起動する</h3><pre><code>cd lg-agent-demo
langgraph dev</code></pre><p>これにより、LangGraph API サーバーがローカルで起動します。これが正常に実行されると、次のような画面が表示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e3c3344b24651067e2d0892d870feca505b3be35-1494x542.png" style="width: 100%;" /><p>Studio UI URL を開きます。</p><p>利用可能なグラフは 2 つあります。</p><ul><li><strong>取得グラフ</strong>: Elasticsearch からデータを取得し、LLM を使用してクエリに応答します。</li><li><strong>インデクサー グラフ</strong>: ドキュメントを Elasticsearch にインデックスし、LLM を使用して埋め込みを生成します。</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/99b579ca44b2d13f61da7e1de5886ea5b9a5e16f-1600x1009.png" style="max-width:100%; margin-bottom:1em;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/f54d182d2789b6a4d113b0c88063531ea0b6de5d-1600x1009.png" style="max-width:100%; margin-bottom:1em;" /><h3>2. インデクサーグラフの設定</h3><ul><li>インデクサー グラフを開きます。</li><li>アシスタントの管理をクリックします。<ul><li><strong>「新しいアシスタントを追加</strong>」をクリックし、指定どおりにユーザーの詳細を入力して、ウィンドウを閉じます。</li></ul></li></ul><pre><code>{"user_id": "101"}</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6f87958e40760ac9671639f4995962bcea090991-1600x1139.png" style="max-width:100%; margin-bottom:1em;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8f802f6b65b3f06d7d1a521c3f40385c6006413b-1422x1212.png" style="max-width:100%; margin-bottom:1em;" /><h3>3. サンプル文書のインデックス作成</h3><ul><li>NoveTech という組織の仮想的な四半期レポートを表す次のサンプル ドキュメントにインデックスを付けます。</li></ul><pre><code>[
  {    "page_content": "NoveTech Solutions Q1 2025 Report - Revenue: $120.5M, Net Profit: $18.2M, EPS: $2.15. Strong AI software launch and $50M government contract secured."
  },
  {
    "page_content": "NoveTech Solutions Business Highlights - AI-driven analytics software gained 15% market share. Expansion into Southeast Asia with two new offices. Cloud security contract secured."
  },
  {
    "page_content": "NoveTech Solutions Financial Overview - Operating expenses at $85.3M, Gross Margin 29.3%. Stock price rose from $72.5 to $78.3. Market Cap reached $5.2B."
  },
  {
    "page_content": "NoveTech Solutions Challenges - Rising supply chain costs impacting hardware production. Regulatory delays slowing European expansion. Competitive pressure in cybersecurity sector."
  },
  {
    "page_content": "NoveTech Solutions Future Outlook - Expected revenue for Q2 2025: $135M. New AI chatbot and blockchain security platform launch planned. Expansion into Latin America."
  },
  {
    "page_content": "NoveTech Solutions Market Performance - Year-over-Year growth at 12.7%. Stock price increase reflects investor confidence. Cybersecurity and AI sectors remain competitive."
  },
  {
    "page_content": "NoveTech Solutions Strategic Moves - Investing in R&D to enhance AI-driven automation. Strengthening partnerships with enterprise cloud providers. Focusing on data privacy solutions."
  },
  {
    "page_content": "NoveTech Solutions CEO Statement - 'NoveTech Solutions continues to innovate in AI and cybersecurity. Our growth strategy remains strong, and we foresee steady expansion in the coming quarters.'"
  }
]</code></pre><p>ドキュメントがインデックスされると、以下に示すように、スレッドに削除メッセージが表示されます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fd3a1efd64cb54d54ea56ef5055249dd066d5708-1600x854.png" style="width: 100%;" /><h3>4. 検索グラフの実行</h3><ul><li>検索グラフに切り替えます。</li><li>次の検索クエリを入力してください。</li></ul><pre><code>What was NovaTech Solutions total revenue in Q1 2025?</code></pre><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/eb4d8ddfe0effd7e1868fba921b8ef13f7baf27a-1600x755.png" style="width: 100%;" /><p>システムは関連するドキュメントを返し、インデックスされたデータに基づいて正確な回答を提供します。</p><h2>検索エージェントをカスタマイズする</h2><p>ユーザー エクスペリエンスを向上させるために、検索グラフにカスタマイズ ステップを導入し、ユーザーが次に尋ねる可能性のある 3 つの質問を予測します。この予測は以下に基づいています:</p><ul><li>取得した文書のコンテキスト</li><li>以前のユーザーインタラクション</li><li>最後のユーザークエリ</li></ul><p>クエリ予測機能を実装するには、次のコード変更が必要です。</p><h3>1. graph.pyを更新する</h3><ul><li><code>predict_query</code>関数を追加します:</li></ul><pre><code>async def predict_query(
   state: State, *, config: RunnableConfig
) -> dict[str, list[BaseMessage]]:
   logger.info(f"predict_query predict_querypredict_query predict_query predict_query predict_query")  # Log the query

   configuration = Configuration.from_runnable_config(config)
   prompt = ChatPromptTemplate.from_messages(
       [
           ("system", configuration.predict_next_question_prompt),
           ("placeholder", "{messages}"),
       ]
   )
   model = load_chat_model(configuration.response_model)
   user_query = state.queries[-1] if state.queries else "No prior query available"
   logger.info(f"user_query: {user_query}")
   logger.info(f"statemessage: {state.messages}")
   #human_messages = [msg for msg in state.message if isinstance(msg, HumanMessage)]

   message_value = await prompt.ainvoke(
       {
           "messages": state.messages,
           "user_query": user_query,  # Use the most recent query as primary input
           "system_time": datetime.now(tz=timezone.utc).isoformat(),
       },
       config,
   )

   next_question = await model.ainvoke(message_value, config)
   return {"next_question": [next_question]}</code></pre><ul><li><code>respond</code>関数を変更して、メッセージの代わりに<strong><code>response</code></strong>オブジェクトを返します。</li></ul><pre><code>async def respond(
   state: State, *, config: RunnableConfig
) -> dict[str, list[BaseMessage]]:
   """Call the LLM powering our "agent"."""
   configuration = Configuration.from_runnable_config(config)
   # Feel free to customize the prompt, model, and other logic!
   prompt = ChatPromptTemplate.from_messages(
       [
           ("system", configuration.response_system_prompt),
           ("placeholder", "{messages}"),
       ]
   )
   model = load_chat_model(configuration.response_model)

   retrieved_docs = format_docs(state.retrieved_docs)
   message_value = await prompt.ainvoke(
       {
           "messages": state.messages,
           "retrieved_docs": retrieved_docs,
           "system_time": datetime.now(tz=timezone.utc).isoformat(),
       },
       config,
   )
   response = await model.ainvoke(message_value, config)
   # We return a list, because this will get added to the existing list
   return {"response": [response]}</code></pre><ul><li>グラフ構造を更新して、predict_query に新しいノードとエッジを追加します。</li></ul><pre><code>builder.add_node(generate_query)
builder.add_node(retrieve)
builder.add_node(respond)
builder.add_node(predict_query)
builder.add_edge("__start__", "generate_query")
builder.add_edge("generate_query", "retrieve")
builder.add_edge("retrieve", "respond")
builder.add_edge("respond", "predict_query")</code></pre><h3>2. prompts.pyを更新する</h3><ul><li><code>prompts.py</code>でのクエリ予測のプロンプトを作成します:</li></ul><pre><code>PREDICT_NEXT_QUESTION_PROMPT = """Given the user query and the retrieved documents, suggest the most likely next question the user might ask.

**Context:**
- Previous Queries:
{previous_queries}

- Latest User Query: {user_query}

- Retrieved Documents:
{retrieved_docs}

**Guidelines:**
1. Do not suggest a question that has already been asked in previous queries.
2. Consider the retrieved documents when predicting the next logical question.
3. If the user's query is already fully answered, suggest a relevant follow-up question.
4. Keep the suggested question natural and conversational.
5. Suggest at least 3 question

System time: {system_time}"""</code></pre><h3>3. configuration.pyを更新する</h3><ul><li><code>predict_next_question_prompt</code>追加:</li></ul><pre><code>predict_next_question_prompt: str = field(
       default=prompts.PREDICT_NEXT_QUESTION_PROMPT,
       metadata={"description": "The system prompt used for generating responses."},
   )</code></pre><h3>4. state.pyを更新する</h3><ul><li>次の属性を追加します。</li></ul><pre><code>response: Annotated[Sequence[AnyMessage], add_messages]
next_question : Annotated[Sequence[AnyMessage], add_messages]</code></pre><h3>5. 検索グラフを再実行する</h3><ul><li>次の検索クエリをもう一度入力してください。</li></ul><pre><code>What was NovaTech Solutions total revenue in Q1 2025?</code></pre><p>システムは入力を処理し、以下に示すように、ユーザーが尋ねる可能性のある 3 つの関連する質問を予測します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/70e855a2e4edc0ba5a147588df0de30eb081d053-1600x777.png" style="width: 100%;" /><h2>まとめ</h2><p>LangGraph Studio と CLI 内に取得エージェント テンプレートを統合すると、いくつかの重要な利点が得られます。</p><ul><li><strong>開発の加速</strong>: テンプレートと視覚化ツールにより、検索ワークフローの作成とデバッグが効率化され、開発時間が短縮されます。</li><li><strong>シームレスな展開</strong>: API と自動スケーリングの組み込みサポートにより、環境間でのスムーズな展開が保証されます。</li><li><strong>簡単な更新:</strong>ワークフローの変更、新しい機能の追加、追加のノードの統合が簡単なので、検索プロセスの拡張と強化が容易になります。</li><li><strong>永続メモリ</strong>: システムはエージェントの状態と知識を保持し、一貫性と信頼性を向上させます。</li><li><strong>柔軟なワークフロー モデリング</strong>: 開発者は、特定のユース ケースに合わせて検索ロジックと通信ルールをカスタマイズできます。</li><li><strong>リアルタイムの対話とデバッグ</strong>: 実行中のエージェントと対話できるため、効率的なテストと問題解決が可能になります。</li></ul><p>これらの機能を活用することで、組織はデータのアクセシビリティとユーザー エクスペリエンスを向上させる強力で効率的かつスケーラブルな検索システムを構築できます。</p><p>このプロジェクトの完全なソースコードは<a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/langraph-retrieval-agent-template-demo">GitHub</a>で入手できます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ユースケースにBetter Binary Quantization (BBQ)を実装する方法]]></title>
            <description><![CDATA[ユースケースで Better Binary Quantization (BBQ) を実装する理由とその方法について説明します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/bbq-implementation-into-use-case</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/bbq-implementation-into-use-case</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Sachin Frayne]]></dc:creator>
            <pubDate>Wed, 23 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/66842055367cdd795532b01c167f2a4b03dc65e3-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>ベクトル検索は、テキストのセマンティック検索や、画像、ビデオ、オーディオの類似性検索を実装する際の基盤を提供します。ベクトル検索では、ベクトルはデータの数学的表現であり、そのサイズは膨大で、場合によっては遅くなることがあります。Better Binary Quantization (以下、BBQ と呼びます) は、ベクトルの圧縮方法として機能します。これにより、ベクトルを縮小して検索と処理を高速化しながら、適切な一致を見つけることができます。この記事では、BBQ と、ベクトルを自動的に再スコアリングする量子化インデックスにのみ使用可能なフィールドである rescore_vector について説明します。</p><p>この記事で説明したすべての完全なクエリと出力は<a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/how-and-why-bbq">、Elasticsearch Labs コード リポジトリ</a>で確認できます。</p><h2>ユースケースで Better Binary Quantization (BBQ) を実装する理由は何ですか?</h2><blockquote>注: BBQ の背後にある数学の仕組みを詳しく理解するには、以下の<a href="https://www.elastic.co/jp/search-labs/blog/bbq-implementation-into-use-case#further-learning">「さらに学ぶ」セクション</a>をご覧ください。このブログでは、実装に重点を置いています。</blockquote><p>数学は興味深いものですが、ベクトル検索がなぜ正確であり続けるのかを完全に理解したい場合には重要です。結局のところ、現在のベクトル検索アルゴリズムではデータの読み取り速度によって制限されることが判明しているため、これはすべて圧縮に関することです。したがって、そのデータすべてをメモリに収めることができれば、ストレージから読み取る場合と比べて速度が大幅に向上します (<a href="https://sre.google/static/pdf/rule-of-thumb-latency-numbers-letter.pdf">メモリは SSD よりも約 200 倍高速です</a>)。</p><p>いくつか留意すべき点があります:</p><ul><li><a href="https://arxiv.org/pdf/1603.09320">HNSW</a> (Hierarchical Navigable Small World) などのグラフベースのインデックスは、ベクター検索では最も高速です。<ul><li>HNSW: 効率的な高次元類似性検索を可能にする多層グラフ構造を構築する近似最近傍検索アルゴリズム。</li></ul></li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/590f3b3c72a76023a38a0436cd9ff90a9f80e936-1964x1262.png" style="width: 100%;" /><ul><li>HNSW の速度は、基本的にメモリ、または最悪の場合、ストレージからのデータ読み取り速度によって制限されます。<ul><li>理想的には、保存されているすべてのベクトルをメモリにロードできるようにする必要があります。</li></ul></li><li>埋め込みモデルは通常、浮動小数点数ごとに 4 バイトの float32 精度のベクトルを生成します。</li><li>そして最後に、ベクトルや次元の数によっては、すべてのベクトルを保存するためのメモリがすぐに不足する可能性があります。</li></ul><p>これを当然のこととして考えると、それぞれが数百、数千の次元を持つ可能性のある数百万、数十億のベクトルを取り込み始めると、すぐに問題が発生することがわかります。「<a href="https://www.elastic.co/jp/search-labs/blog/bbq-implementation-into-use-case#approximate-numbers-on-the-compression-ratios">圧縮率のおおよその数値</a>」というセクションでは、おおよその数値が示されています。</p><h2>始めるには何が必要ですか?</h2><p>始めるには、次のものが必要です。</p><ul><li>Elastic Cloud またはオンプレミスを使用している場合は、Elasticsearch のバージョン 8.18 以降が必要になります。BBQ は 8.16 で導入されましたが、この記事では 8.18 で導入された<code>vector_rescore</code>使用します。</li><li>さらに、クラスター内に<a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/8.18/ml-settings.html">機械学習 (ML) ノード</a>があることも確認する必要があります。(注: モデルをロードするには最低 4 GB の ML ノードが必要ですが、完全な本番環境のワークロードには、はるかに大きなノードが必要になる可能性があります。)</li><li>Serverless を使用している場合は、ベクターに最適化されたインスタンスを選択する必要があります。</li><li>また、ベクター データベースに関する基本的な知識も必要になります。Elastic のベクトル検索の概念にまだ慣れていない場合は、まず次のリソースを確認することをお勧めします。<ul><li><a href="https://www.elastic.co/jp/search-labs/blog/elastic-vector-database-practical-example">弾性ベクトルデータベースのナビゲート</a></li><li><a href="https://www.elastic.co/jp/blog/retrieval-augmented-generation-explained">検索拡張生成の背後にある大きなアイデア</a></li></ul></li></ul><h2>より良いバイナリ量子化（BBQ）実装</h2><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/4d388078495566f0527e931e0c2e38facdce83c6-1503x748.png" style="width: 100%;" /><p>このブログをシンプルに保つために、組み込み関数が利用可能な場合はそれを使用します。この場合、機械学習ノード上の Elasticsearch 内で直接実行される<a href="https://www.elastic.co/jp/guide/en/machine-learning/8.17/ml-nlp-e5.html"><code>.multilingual-e5-small</code></a>ベクトル埋め込みモデルがあります。<code>text_embedding</code>モデルを、任意の埋め込みツール ( <a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/8.18/infer-service-openai.html">OpenAI</a> 、 <a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/8.18/infer-service-google-ai-studio.html">Google AI Studio</a> 、 <a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/8.18/infer-service-cohere.html">Cohere</a>など) に置き換えることができることに注意してください。希望するモデルがまだ統合されていない場合は、<a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/8.18/bring-your-own-vectors.html">独自の密なベクトル埋め込みも使用</a>できます。</p><p>まず、特定のテキストのベクトルを生成するための推論エンドポイントを作成する必要があります。これらのコマンドはすべて、Kibana <a href="https://www.elastic.co/jp/guide/en/kibana/8.18/console-kibana.html">Dev Tools コンソール</a>から実行します。このコマンドは<code>.multilingual-e5-small</code>をダウンロードします。まだ存在しない場合はエンドポイントが設定されます。実行には 1 分ほどかかる場合があります。予想される出力は、Outputs フォルダー内のファイル<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/01-create-an-inference-endpoint-output.json">01-create-an-inference-endpoint-output.json</a>で確認できます。 </p><pre><code>PUT _inference/text_embedding/my_e5_model
{
  "service": "elasticsearch",
  "service_settings": {
    "num_threads": 1,
    "model_id": ".multilingual-e5-small",
    "adaptive_allocations": {
      "enabled": true,
      "min_number_of_allocations": 1
    }
  }
}</code></pre><p>これが返されると、モデルが設定され、次のコマンドを使用してモデルが期待どおりに動作するかどうかをテストできます。期待される出力は、Outputs フォルダー内のファイル<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/02-embed-text-output.json">02-embed-text-output.json</a>で確認できます。</p><pre><code>POST _inference/text_embedding/my_e5_model
{
  "input": "my awesome piece of text"
}</code></pre><p>トレーニング済みのモデルがどのノードにも割り当てられないという問題が発生した場合は、モデルを手動で起動する必要がある場合があります。</p><pre><code>POST _ml/trained_models/.multilingual-e5-small/deployment/_start</code></pre><p>ここで、埋め込みモデルからの出力と一致するように、標準テキスト フィールド ( <code>my_field</code> ) と 384 次元の密なベクトル フィールド ( <code>my_vector</code> ) の 2 つのプロパティを持つ新しいマッピングを作成しましょう。<code>index_options.type to bbq_hnsw</code>もオーバーライドします。予想される出力は、Outputs フォルダー内のファイル<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/03-create-byte-qauntized-index-output.json">03-create-byte-qauntized-index-output.json</a>で確認できます。</p><pre><code>PUT bbq-my-byte-quantized-index
{
  "mappings": {
    "properties": {
      "my_field": {
        "type": "text"
      },
      "my_vector": {
        "type": "dense_vector",
        "dims": 384,
        "index_options": {
          "type": "bbq_hnsw"
        }
      }
    }
  }
}</code></pre><p>Elasticsearch がベクトルを生成するようにするには、 <a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/8.18/ingest.html">Ingest Pipeline</a>を利用できます。このパイプラインには、エンドポイント ( <code>model_id</code> )、ベクトルを作成する<code>input_field</code> 、およびそれらのベクトルを格納する<code>output_field</code>の 3 つが必要です。以下の最初のコマンドは、内部で<a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/current/inference-apis.html">推論サービス</a>を使用する推論取り込みパイプラインを作成し、2 番目のコマンドはパイプラインが正しく動作していることをテストします。予想される出力は、Outputs フォルダー内のファイル<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/04-create-and-simulate-ingest-pipeline-output.json">04-create-and-simulate-ingest-pipeline-output.json</a>で確認できます。</p><pre><code>PUT _ingest/pipeline/my_inference_pipeline
{
  "processors": [
    {
      "inference": {
        "model_id": "my_e5_model",
        "input_output": [
          {
            "input_field": "my_field",
            "output_field": "my_vector"
          }
        ]
      }
    }
  ]
}

POST _ingest/pipeline/my_inference_pipeline/_simulate
{
  "docs": [
    {
      "_source": {
        "my_field": "my awesome text field"
      }
    }
  ]
}</code></pre><p>これで、以下の最初の 2 つのコマンドを使用してドキュメントを追加し、3 番目のコマンドで検索が機能することをテストする準備が整いました。予想される出力は、Outputs フォルダー内のファイル<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/05-bbq-index-output.json">05-bbq-index-output.json</a>で確認できます。</p><pre><code>PUT bbq-my-byte-quantized-index/_doc/1?pipeline=my_inference_pipeline
{
    "my_field": "my awesome text field"
}

PUT bbq-my-byte-quantized-index/_doc/2?pipeline=my_inference_pipeline
{
    "my_field": "some other sentence"
}

GET bbq-my-byte-quantized-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}</code></pre><p><a href="https://www.elastic.co/jp/search-labs/blog/better-binary-quantization-lucene-elasticsearch#lucene-benchmarking">この投稿</a>で推奨されているように、圧縮の利点を活用しながら高い再現精度を維持するのに役立つため、大量のデータに拡張する場合は、再スコアリングとオーバーサンプリングが推奨されます。Elasticsearch バージョン 8.18 以降では、 <a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/8.18/knn-search.html#dense-vector-knn-search-rescoring">rescore_vector</a>を使用してこの方法で実行できます。予想される出力は、Outputs フォルダー内のファイル<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/06-bbq-search-8-18-output.json">06-bbq-search-8-18-output.json</a>にあります。</p><pre><code>GET bbq-my-byte-quantized-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "rescore_vector": {
              "oversample": 3
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}</code></pre><p>これらのスコアは、生データで得られるスコアと比べてどうでしょうか?上記のすべてを<code>index_options.type: hnsw</code>を使ってもう一度実行すると、スコアが非常に似ていることがわかります。予想される出力は、Outputs フォルダーのファイル<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/07-raw-vector-output.json">07-raw-vector-output.json</a>で確認できます。</p><pre><code>PUT my-raw-vector-index
{
  "mappings": {
    "properties": {
      "my_field": {
        "type": "text"
      },
      "my_vector": {
        "type": "dense_vector",
        "dims": 384,
        "index_options": {
          "type": "hnsw"
        }
      }
    }
  }
}

PUT my-raw-vector-index/_doc/1?pipeline=my_inference_pipeline
{
    "my_field": "my awesome text field"
}

PUT my-raw-vector-index/_doc/2?pipeline=my_inference_pipeline
{
    "my_field": "some other sentence"
}

GET my-raw-vector-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}</code></pre><h2>圧縮比のおおよその数値</h2><p>ベクトル検索を使用する場合、ストレージとメモリの要件がすぐに大きな課題になる可能性があります。次の内訳は、さまざまな量子化手法によってベクター データのメモリ フットプリントがいかに劇的に削減されるかを示しています。</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ベクトル（V）</td><td style="border: 1px solid #ccc; padding: 4px;">寸法（D）</td><td style="border: 1px solid #ccc; padding: 4px;">生（V x D x 4）</td><td style="border: 1px solid #ccc; padding: 4px;">int8 (V x (D x 1 + 4))</td><td style="border: 1px solid #ccc; padding: 4px;">int4 (V x (D x 0.5 + 4))</td><td style="border: 1px solid #ccc; padding: 4px;">バーベキュー（V×（D×0.125+4））</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">10,000,000</td><td style="border: 1px solid #ccc; padding: 4px;">384</td><td style="border: 1px solid #ccc; padding: 4px;">14.31GB</td><td style="border: 1px solid #ccc; padding: 4px;">3.61GB</td><td style="border: 1px solid #ccc; padding: 4px;">1.83GB</td><td style="border: 1px solid #ccc; padding: 4px;">0.58GB</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">50,000,000</td><td style="border: 1px solid #ccc; padding: 4px;">384</td><td style="border: 1px solid #ccc; padding: 4px;">71.53GB</td><td style="border: 1px solid #ccc; padding: 4px;">18.07GB</td><td style="border: 1px solid #ccc; padding: 4px;">9.13GB</td><td style="border: 1px solid #ccc; padding: 4px;">2.89GB</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">1億</td><td style="border: 1px solid #ccc; padding: 4px;">384</td><td style="border: 1px solid #ccc; padding: 4px;">143.05GB</td><td style="border: 1px solid #ccc; padding: 4px;">36.14GB</td><td style="border: 1px solid #ccc; padding: 4px;">18.25GB</td><td style="border: 1px solid #ccc; padding: 4px;">5.77GB</td>
                    </tr>
              </tbody>
            </table>
          <h2>まとめ</h2><p>BBQ は、精度を犠牲にすることなくベクター データを圧縮するために適用できる最適化です。これはベクトルをビットに変換することで機能し、データを効果的に検索できるようにし、AI ワークフローを拡張して検索を高速化し、データ ストレージを最適化できるようにします。</p><h2>さらなる学習</h2><p>バーベキューについてさらに詳しく知りたい場合は、次のリソースをぜひチェックしてください。</p><ul><li><a href="https://www.elastic.co/jp/search-labs/blog/better-binary-quantization-lucene-elasticsearch">LuceneとElasticsearchにおけるバイナリ量子化（BBQ）</a></li><li><a href="https://www.elastic.co/jp/search-labs/blog/bit-vectors-elasticsearch-bbq-vs-pq">より良いバイナリ量子化（BBQ）と積量子化</a></li><li><a href="https://www.elastic.co/jp/search-labs/blog/optimized-scalar-quantization-elasticsearch">最適化されたスカラー量子化：さらに優れたバイナリ量子化</a></li><li><a href="https://www.youtube.com/watch?v=04NzMt2Nigc">より良いバイナリ量子化（BBQ）：バイトからBBQへ、より良いベクトル検索の秘密 by Ben Trent</a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch ヒープサイズの使用量と JVM ガベージコレクション]]></title>
            <description><![CDATA[Elasticsearch のヒープ サイズの使用と JVM ガベージ コレクションについて説明します。ベスト プラクティスや、ヒープ メモリの使用量が高すぎる場合や JVM のパフォーマンスが最適でない場合の問題を解決する方法も説明します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-heap-size-jvm-garbage-collection</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-heap-size-jvm-garbage-collection</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Tue, 22 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b162c28623b9070fd1980bcd891b9dd1e868f2f0-720x421.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>ヒープ サイズは、Elasticsearch ノードの Java 仮想マシンに割り当てられる RAM の量です。</p><p>バージョン 7.11 以降、Elasticsearch はデフォルトで、ノードのロールと合計メモリに基づいて JVM ヒープ サイズを自動的に設定します。ほとんどの運用環境では、デフォルトのサイズ設定を使用することをお勧めします。ただし、JVM ヒープ サイズを手動で設定する場合は、一般的なルールとして、-Xms と -Xmx を同じ値に設定する必要があります。これは、使用可能な RAM の合計の 50% で、最大 (約) 31 GB になります。</p><p>ヒープ サイズを大きくすると、インデックス作成と検索操作に使用できるメモリがノードに多く割り当てられます。ただし、ノードにはキャッシュ用のメモリも必要なので、50% を使用すると 2 つのメモリのバランスが適切に保たれます。同じ理由から、本番環境では、Elasticsearch と同じノードで他のメモリを大量に消費するプロセスを使用することは避けてください。</p><p>通常、ヒープ使用量は鋸歯状のパターンに従い、使用されている最大ヒープの約 30 ～ 70% の間を変動します。これは、ガベージ コレクション プロセスによってメモリが再び解放されるまで、JVM がヒープ使用率を着実に増加させるためです。ガベージ コレクション プロセスが追いつかない場合、ヒープ使用率が高くなります。ヒープ使用量が高いことを示す指標は、ガベージ コレクションでヒープ使用量を約 30% まで削減できない場合です。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/0a17a67cc589a3c1fbf9e918eadc119df7bd7619-858x278.png" style="width: 100%;" /><p>上の画像では、JVM ヒープの通常のノコギリ波を見ることができます。</p><p>また、ガベージ コレクションには、若い GC と古い GC の 2 種類があることもわかります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8df5c24c4894404de4617be7a13683c9027d607d-875x281.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/e01eb2537310b052580411153b8eddc187d97687-890x264.png" style="width: 100%;" /><p>正常な JVM では、ガベージ コレクションは理想的には次の条件を満たす必要があります。</p><ul><li>Young GC は迅速に処理されます (50 ミリ秒以内)。</li><li>Young GC は頻繁に実行されません (約 10 秒)。</li><li>古い GC はすぐに処理されます (1 秒以内)。</li><li>古い GC は頻繁に実行されません (10 分に 1 回以上)。</li></ul><h3><strong>ヒープメモリ使用量が高すぎる場合やJVMパフォーマンスが最適でない場合の解決方法</strong></h3><p>ヒープ メモリの使用量が増える理由はさまざまです。</p><h4><strong>オーバーシャーディング</strong></h4><p>オーバーシャーディングに関するドキュメントは<a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/size-shards#sizing-shard-guidelines">ここを</a>参照してください。</p><h4><strong>大規模な集約サイズ</strong></h4><p>集約サイズが大きくなるのを避けるには、クエリ内の集約バケットの数 (サイズ) を最小限に抑えます。</p><pre><code>GET /_search
{
   "aggs" : {
       "products" : {
           "terms" : {
               "field" : "product",
               "size" : 5
                          }
       }
   }
}</code></pre><p>低速クエリ ログ (スロー ログ) を使用し、次のように特定のインデックスに実装することができます。</p><pre><code>PUT /my_index/_settings
{
   "index.search.slowlog.threshold.query.warn": "10s",
   "index.search.slowlog.threshold.query.info": "5s",
   "index.search.slowlog.threshold.query.debug": "2s",
   "index.search.slowlog.threshold.query.trace": "500ms",
   "index.search.slowlog.threshold.fetch.warn": "1s",
   "index.search.slowlog.threshold.fetch.info": "800ms",
   "index.search.slowlog.threshold.fetch.debug": "500ms",
   "index.search.slowlog.threshold.fetch.trace": "200ms",
   "index.search.slowlog.level": "info"
}</code></pre><p>結果を返すのに長い時間がかかるクエリは、リソースを大量に消費するクエリである可能性が高くなります。</p><h4><strong>バルクインデックスのサイズが大きすぎる</strong></h4><p>大きなリクエストを送信する場合、ヒープ消費量が多くなる原因となる可能性があります。一括インデックス要求のサイズを小さくしてみてください。</p><h4><strong>マッピングの問題</strong></h4><p>特に、「fielddata: true」を使用する場合、これが JVM ヒープの主要なユーザーになる可能性があります。</p><h4><strong>ヒープサイズが正しく設定されていません</strong></h4><p>ヒープ サイズは次のように手動で定義できます。</p><p>環境変数の設定:</p><pre><code>ES_JAVA_OPTS="-Xms2g -Xmx2g"</code></pre><p>Elasticsearch 構成ディレクトリ内の jvm.options ファイルを編集します。</p><pre><code>-Xms2g
-Xmx2g</code></pre><p>環境変数の設定はファイルの設定よりも優先されます。</p><p>設定を有効にするにはノードを再起動する必要があります。</p><h4><strong>JVM の新しい比率が正しく設定されていません</strong></h4><p>Elasticsearch はデフォルトでこの値を設定するため、通常はこれを設定する必要はありません。このパラメータは、JVM 内の「新世代」オブジェクトと「旧世代」オブジェクトに使用可能なスペースの比率を定義します。</p><p>古い GC が非常に頻繁に発生していることがわかった場合は、Elasticsearch 構成ディレクトリの jvm.options ファイルでこの値を具体的に設定してみてください。</p><pre><code>-XX:NewRatio=3</code></pre><h3><strong>大規模な Elasticsearch クラスターでヒープサイズの使用量と JVM ガベージコレクションを管理するためのベストプラクティスは何ですか?</strong></h3><p>大規模な Elasticsearch クラスターでヒープ サイズの使用量と JVM ガベージ コレクションを管理するためのベスト プラクティスは、ヒープ サイズが使用可能な RAM の最大 50% に設定され、JVM ガベージ コレクション設定が特定のユース ケースに合わせて最適化されていることを確認することです。クラスターが最適に実行されていることを確認するには、ヒープ サイズとガベージ コレクション メトリックを監視することが重要です。具体的には、JVM ヒープ サイズ、ガベージ コレクション時間、ガベージ コレクションの一時停止を監視することが重要です。さらに、ガベージ コレクション サイクルの数とガベージ コレクションに費やされた時間を監視することも重要です。これらのメトリックを監視することで、ヒープ サイズやガベージ コレクションの設定に関する潜在的な問題を特定し、必要に応じて修正措置を講じることができます。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearchでプライマリシャード数を増やす方法]]></title>
            <description><![CDATA[最適なシャードスケーリングを実現するために、分割APIと再インデックスAPIを使用してElasticsearchのプライマリシャード数を増やす方法を学びます。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-increase-primary-shard-count</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-increase-primary-shard-count</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Thu, 17 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7034b76019a0cba52c25eda29fceb18afc96ed0b-720x420.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>既存のインデックスのプライマリ シャード数を増やすことはできません。つまり、プライマリ シャード数を増やす場合は、インデックスを再作成する必要があります。このような状況で一般的に使用される方法は 2 つあります。_reindex API と _split API です。</p><p>_split API は、多くの場合、_reindex API よりも高速な方法です。両方の操作の前に<strong>インデックス作成を停止する必要があります</strong>。そうしないと、source_index と target_index のドキュメント数が異なります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/aa0ae010c2f5691ca00440fb453ed6b47bacd24f-1200x628.png" style="width: 100%;" /><h2>方法1 – 分割APIを使用する</h2><p>分割 API は、設定をコピーし、既存のインデックスをマッピングすることで、必要な数のプライマリ シャードを持つ新しいインデックスを作成するために使用されます。作成時に必要なプライマリ シャードの数を設定できます。分割 API を実装する前に、次の設定を確認する必要があります。</p><ol><li>ソース インデックスは読み取り専用である必要があります。これは、インデックス作成プロセスを停止する必要があることを意味します。</li><li>ターゲット インデックス内のプライマリ シャードの数は、ソース インデックス内のプライマリ シャードの数の倍数である必要があります。たとえば、ソース インデックスに 5 つのプライマリ シャードがある場合、ターゲット インデックスのプライマリ シャードを 10、15、20 などに設定できます。</li></ol><p>注: プライマリ シャード番号のみを変更する必要がある場合は、再インデックス API よりもはるかに高速な分割 API が推奨されます。</p><h3>分割APIの実装</h3><p>テストインデックスを作成します。</p><pre><code>POST test_split_source/_doc
{
  "test": "test"
}</code></pre><p>分割するには、ソース インデックスが読み取り専用である必要があります。</p><pre><code>PUT test_split_source/_settings
{
  "index.blocks.write": true
}</code></pre><p>設定とマッピングはソース インデックスから自動的にコピーされます。</p><pre><code>POST /test_split_source/_split/test_split_target
{
  "settings": {
    "index.number_of_shards": 3
  }
}</code></pre><p>進捗状況は以下で確認できます:</p><pre><code>GET _cat/recovery/test_split_target?v&h=index,shard,time,stage,files_percent,files_total</code></pre><p>設定とマッピングはソース インデックスからコピーされるため、ターゲット インデックスは読み取り専用になります。ターゲット インデックスへの書き込み操作を有効にしましょう。</p><pre><code>PUT test_split_target/_settings
{
    "index.blocks.write": null
}</code></pre><p>元のインデックスを削除する前に、ソース インデックスとターゲット インデックスの docs.count を確認します。</p><pre><code>GET _cat/indices/test_split*?v&h=index,pri,rep,docs.count</code></pre><p>インデックス名とエイリアス名を同じにすることはできません。ソース インデックスを削除し、ソース インデックス名をターゲット インデックスのエイリアスとして追加する必要があります。</p><pre><code>DELETE test_split_source
PUT /test_split_target/_alias/test_split_source</code></pre><p><strong>test_split_source</strong>エイリアスを<strong>test_split_target</strong>インデックスに追加した後、次のようにテストする必要があります。</p><pre><code>GET test_split_source
POST test_split_source/_doc
{
  "test": "test"
}</code></pre><h2>方法2 – 再インデックスAPIを使用する</h2><p>Reindex API を使用して新しいインデックスを作成すると、任意の数のプライマリ シャード カウントを指定できます。意図した数のプライマリ シャードで新しいインデックスを作成した後、ソース インデックス内のすべてのデータをこの新しいインデックスに再インデックスできます。</p><p>分割 API 機能に加えて、再インデックス AP の ingest_pipeline を使用してデータを操作することもできます。取り込みパイプラインでは、フィルターに適合する指定されたフィールドのみがクエリを使用してターゲット インデックスにインデックス付けされます。データの内容は簡単なスクリプトを使用して変更でき、複数のインデックスを 1 つのインデックスにマージできます。</p><h3>再インデックスAPIの実装</h3><p>テストの再インデックスを作成します。</p><pre><code>POST test_reindex_source/_doc
{
    "test": "test"
}</code></pre><p>ソース インデックスから設定とマッピングをコピーします。</p><pre><code>GET test_reindex_source</code></pre><p>設定、マッピング、および必要なシャード数を使用してターゲット インデックスを作成します。</p><pre><code>PUT test_reindex_target
{
  "mappings" : {},
  "settings": {
    "number_of_shards": 10,
    "number_of_replicas": 0,
    "refresh_interval": -1
  }
}</code></pre><p>*注: number_of_replicas: 0 および refresh_interval: -1 を設定すると、再インデックスの速度が向上します。</p><p>再インデックスプロセスを開始します。requests_per_second=-1 および slices=auto を設定すると、再インデックス速度が調整されます。</p><pre><code>POST _reindex?requests_per_second=-1&slices=auto&wait_for_completion=false
{
  "source": {
    "index": "test_reindex_source"
  },
  "dest": {
    "index": "test_reindex_target"
  }
}</code></pre><p>再インデックス API を実行すると、task_id が表示されます。それをコピーして、_tasks API で確認します。</p><pre><code>GET _tasks/<task_id></code></pre><p>再インデックスが完了したら設定を更新します。</p><pre><code>PUT test_reindex_target/_settings
{
  "number_of_replicas": 1,
  "refresh_interval": "1s"
}</code></pre><p>元のインデックスを削除する前に、ソース インデックスとターゲット インデックスの docs.count が同じであることを確認します。</p><pre><code>GET _cat/indices/test_reindex_*?v&h=index,pri,rep,docs.count</code></pre><p>インデックス名とエイリアス名を同じにすることはできません。ソース インデックスを削除し、ソース インデックス名をターゲット インデックスのエイリアスとして追加します。</p><pre><code>DELETE test_reindex_source
PUT /test_reindex_target/_alias/test_reindex_source</code></pre><p>test_split_source エイリアスを test_split_target インデックスに追加した後、次のコマンドを使用してテストします。</p><pre><code>GET test_reindex_source</code></pre><h2>まとめ</h2><p>既存のインデックスのプライマリ シャード数を増やす場合は、新しいインデックスへの設定とマッピングを再作成する必要があります。これを行うには、主に reindex API と split API という 2 つの方法があります。どちらの方法を使用する前にも、アクティブなインデックス作成を停止する必要があります。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch BBQ vs. OpenSearch FAISS: ベクトル検索パフォーマンス比較]]></title>
            <description><![CDATA[Elasticsearch BBQ と OpenSearch FAISS のパフォーマンス比較。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-bbq-vs-opensearch-faiss</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-bbq-vs-opensearch-faiss</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Ugo Sangiorgi]]></dc:creator>
            <pubDate>Tue, 15 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/796de4829e29566f1f3efa2482f5c3e54b31b1d6-1536x1024.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p><strong>バイナリ量子化によるベクトル検索: BBQ を使用した Elasticsearch は、 FAISS を使用した OpenSearch よりも 5 倍高速です</strong>。Elastic はコミュニティから、特にセマンティック検索/ベクター検索の領域における Elasticsearch と OpenSearch のパフォーマンスの違いを明確にしてほしいというリクエストを受けており、明確でデータに基づいた比較を提供するためにこれらのパフォーマンス テストを実施しました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7f4f8d08f7bee188423e4e65f0caefc7e34f0355-1600x681.png" style="width: 100%;" /><h2>バイナリ量子化対決</h2><p>高次元ベクトルを元の形式で保存すると、メモリを大量に消費する可能性があります。量子化技術はこれらのベクトルをコンパクトな表現に圧縮し、メモリフットプリントを大幅に削減します。その後、検索は圧縮された空間で実行されるため、計算の複雑さが軽減され、特に大規模なデータセットでは検索が高速化されます。</p><p>Elastic は、Lucene を最高のパフォーマンスを発揮するベクター エンジンにすることに注力しています。Elasticsearch 8.16 では Lucene をベースに<a href="https://www.elastic.co/jp/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Better Binary Quantization</a> (BBQ) を導入し、8.18 および 9.0 でさらに進化させました。BBQ は、float32 次元をビットに削減する<a href="https://www.elastic.co/jp/search-labs/blog/optimized-scalar-quantization-elasticsearch">スカラー量子化</a>の新しいアプローチに基づいて構築されており、高いランキング品質を維持しながら約 95% のメモリ削減を実現します。</p><p>一方、OpenSearch は、nmslib (現在は非推奨)、Lucene、FAISS といった複数のベクター エンジンを使用します。<a href="https://www.elastic.co/jp/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison">以前のブログ</a>では、ベクトル検索について Elasticsearch と OpenSearch を比較しました。3 つの異なるデータセットを使用し、両方の製品でさまざまなエンジンと構成の組み合わせをテストしました。</p><p>このブログでは、現在両方の製品で利用可能なバイナリ量子化アルゴリズムに焦点を当てています。<a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector">openai_vector</a> Rally トラックを使用して、Elasticsearch と BBQ および<a href="https://opensearch.org/docs/latest/search-plugins/knn/knn-vector-quantization/#binary-quantization"> OpenSearch と FAISS のバイナリ量子化を テストしました。</a></p><p>主な目的は、同じレベルの再現率で両方のソリューションのパフォーマンスを評価することでした。<em>リコールと</em>はどういう意味ですか?リコールは、検索システムによって関連結果がどれだけ正常に取得されたかを測定する指標です。</p><p>この評価では、recall@k が特に重要です。ここで、 <em>k は</em>検討される上位の結果の数を表します。したがって、 <strong>Recall@10</strong> 、 <strong>Recall@50、および Recall@100 は、</strong>それぞれ上位 10、50、および 100 件の取得された項目内に表示される実際の関連結果の数を測定します。再現率は 0 ～ 1 (または 0% ～ 100% の精度) のスケールで表されます。これは重要なことです。なぜなら、ここでは再現率が常に 1 (100%) となる正確な KNN ではなく、近似 KNN (ANN) について話しているためです。</p><p><em>k</em>の各値に対して、最終的なランキングを適用する前に考慮される候補の数である<em>n</em>も指定しました。つまり、Recall@10、Recall@50、および Recall@100 の場合、システムは最初にバイナリ量子化アルゴリズムを使用して<em>n</em>個の候補を取得し、次にそれらをランク付けして、上位<em>k 個</em>の結果に期待される関連項目が含まれているかどうかを判断します。</p><p><em>n を</em>制御することで、効率と精度のトレードオフを分析できます。通常、 <em>n</em>が大きいほど、ランキングに使用できる候補が増えるためリコール<strong>は増加します</strong>が、レイテンシも<strong>増加し</strong>、スループット<strong>は低下します</strong>。逆に、 <em>n</em>を小さくすると検索速度は上がりますが、初期セットに含まれる関連候補が少なすぎるとリコール率が低下する可能性があります。</p><p>この比較では、Elasticsearch は同一の設定で OpenSearch よりも低いレイテンシと高いスループットを示しました。</p><h2>方法論</h2><p>完全な構成、Terraform スクリプト、Kubernetes マニフェスト、および特定の Rally トラックは、この<a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/tree/bbq">リポジトリ</a>の<a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/tree/bbq/rally-custom/custom_tracks/elasticsearch/openai_vector_bq"><em>openai_vector_bq</em></a>で入手できます。</p><p>以前のベンチマークと同様に、次のもので構成される Kubernetes クラスターを使用しました。</p><ul><li>Elasticsearch 9.0 用の 1 つのノードプール（3 台の<code>e2-standard-32</code>マシン、128 GB の RAM、32 個の CPU）</li><li>OpenSearch 2.19 用の 1 つのノード プール (3 台の<code>e2-standard-32</code>マシン (128 GB RAM、32 個の CPU))</li><li>Rally 用の 1 つのノードプール（2 台の<code>e2-standard-4</code>マシン、16 GB の RAM と 4 つの CPU）</li></ul><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/169bb6033f6eebfd1b177b3446bf916fde4ee5c5-1600x856.png" style="width: 100%;" /><p>Elasticsearch クラスター バージョン 9.0 を 1 つと OpenSearch クラスター バージョン 2.19 を 1 つセットアップしました。</p><p>Elasticsearch と OpenSearch は両方ともまったく同じ設定でテストされました。 つまり、OpenAI の<a href="https://openai.com/blog/new-and-improved-embedding-model"> text-embedding-ada-002 モデルを使用して生成された埋め込みが強化された</a><a href="https://huggingface.co/datasets/BeIR/nq"> NQ データ セットからの</a> 250 万のドキュメントを使用する、いくつかの変更 を<a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/commit/b97d5d95c22c8cf862f2030964524bdd156a5da3"> 加え た</a><a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector"> openai_vector</a> Rally トラック を使用しました。</p><pre><code>{
  "source-file": "open_ai_corpus-initial-indexing.json.bz2",
  "document-count": 2580961,
  "compressed-bytes": 32076749416,
  "uncompressed-bytes": 90263571686
}</code></pre><p>結果には、8 つの同時クライアントを使用して検索操作を実行し、さまざまなリコール レベル (recall@10、recall@50、recall@100) で測定されたレイテンシとスループットが報告されています。単一のシャードを使用し、レプリカは使用しませんでした。</p><p>我々は以下のkn-rescoreの組み合わせを実行した。10-2000-2000、または<em>k:10</em> 、 <em>n:2000</em> 、 <em>rescore:2000 は</em>、2000 件の結果に対して再スコアを適用して、n 件の候補 (2000) のうち上位 k 件 (10) を取得します (これは「オーバーサンプル係数」が 1 の場合に相当します)。各検索はウォームアップとして 1,000 回実行され、10,000 回実行されました。</p><p></p><p><strong><span style="text-decoration:underline">リコール@10</span></strong></p><ul><li>10-40-40</li><li>10-50-50</li><li>10-100-100</li><li>10-200-200</li><li>10-500-500</li><li>10-750-750</li><li>10-1000-1000</li><li>10-1500-1500</li><li>2000年10月</li></ul><p><strong><span style="text-decoration:underline">リコール@50</span></strong></p><ul><li>50-150-150</li><li>50-200-200</li><li>50-250-250</li><li>50-500-500</li><li>50-750-750</li><li>50-1000-1000</li><li>50-1200-1200</li><li>50-1500-1500</li><li>50-2000-2000</li></ul><p><strong><span style="text-decoration:underline">リコール@100</span></strong></p><ul><li>100-200-200</li><li>100-250-250</li><li>100-300-300</li><li>100-500-500</li><li>100-750-750</li><li>100-1000-1000</li><li>100-1200-1200</li><li>100-1500-1500</li><li>100-2000-2000</li></ul><p>ベンチマークを再現するために、rally-elasticsearch と rally-opensearch の両方の Kubernetes マニフェストには、関連するすべての変数が ConfigMap で外部化されており、<a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/bbq/k8s/rally-openai_vector-es-bq.yml">こちら</a>(ES) と<a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/bbq/k8s/rally-openai_vector-os-bq.yml">こちら</a>(OS) から入手できます。<em>search_ops</em>パラメータは、k、n、rescore の任意の組み合わせをテストするようにカスタマイズできます。</p><h3>OpenSearch Rallyの設定</h3><p><code>/k8s/rally-openai_vector-os-bq.yml</code></p><pre><code>apiVersion: v1
kind: ConfigMap
metadata:
  name: rally-params-os
  labels:
    app: rally-opensearch
data:
  user-tags.json: |
    {
      "product": "OpenSearch",
      "product-version": "OpenSearch-2.19.0",
      "product-label": "OpenSearch-2.19-faiss",
      "benchmark-run": "19-feb-recall@100"
    }
  track-params.json: |
    {
      "mapping_type": "vectors-only-mapping-with-docid",
      "standalone_search_clients": 8,
      "standalone_search_iterations": 5000,
      "ann_threshold": 0,
      "vector_mode": "on_disk",
      "compression_level": "32x",
      "vector_method_name": "hnsw",
      "vector_method_engine": "faiss",
      "search_ops": [
        [100, 200, 200],
        [100, 250, 250],
        [100, 300, 300],
        [100, 500, 500],
        [100, 750, 750],
        [100, 1000, 1000],
        [100, 1200, 1200],
        [100, 1500, 1500],
        [100, 2000, 2000]
      ]
    }</code></pre><h3>Opensearchインデックス設定</h3><p>ConfigMap の変数はインデックス構成で使用され、一部のパラメータは変更されません。OpenSearch における 1 ビット量子化は<a href="https://opensearch.org/docs/latest/search-plugins/knn/knn-vector-quantization/#binary-quantization">、圧縮レベルを「32x」に設定する</a>ことによって設定されます。</p><p><code>index-vectors-only-mapping-with-docid-mapping.json</code></p><pre><code>{
  "settings": {
    {% if preload_pagecache %}
    "index.store.preload": [
      "vec", "vex", "vem", "veq", "veqm", "veb", "vebm"
    ],
    {% endif %}
    "index.number_of_shards": {{ number_of_shards | default(1) }},
    "index.number_of_replicas": {{ number_of_replicas | default(0) }},
    "index.knn": true,
    "index.knn.advanced.approximate_threshold": {{ ann_threshold | default(15000) }}
  },
  "mappings": {
    "dynamic": false,
    "properties": {
      "docid": {
        "type": "keyword"
      },
      "emb": {
        "type": "knn_vector",
        "dimension": 1536,
        "space_type": "innerproduct",
        "data_type": "float",
        "mode": {{ vector_mode | default("in_memory") | tojson }},
        "compression_level": {{ compression_level | default("32x") | tojson }},
        "method": {
          "name": {{ vector_method_name | default("hnsw") | tojson }},
          "engine": {{ vector_method_engine | default("faiss") | tojson }},
          "parameters": {
            "ef_construction": 100,
            "m": 16
          }
        }
      }
    }
  }
}</code></pre><h3>Elasticsearch Rallyの設定</h3><p><code>/k8s/rally-openai_vector-es-bq.yml</code></p><pre><code>apiVersion: v1
kind: ConfigMap
metadata:
  name: rally-params-es
  labels:
    app: rally-elasticsearch
data:
  user-tags.json: |
    {
      "product": "Elasticsearch",
      "product-version": "Elasticsearch-9.0.0-ade01164",
      "product-label": "Elasticsearch-9.0-BBQ",
      "benchmark-run": "19-feb-recall@100"
    }
  track-params.json: |
    {
      "mapping_type": "vectors-only-mapping-with-docid",
      "standalone_search_clients": 8,
      "standalone_search_iterations": 5000,
      "vector_index_type": "bbq_hnsw",
      "search_ops": [
        [100, 200, 200],
        [100, 250, 250],
        [100, 300, 300],
        [100, 500, 500],
        [100, 750, 750],
        [100, 1000, 1000],
        [100, 1200, 1200],
        [100, 1500, 1500],
        [100, 2000, 2000]
      ]
    }</code></pre><h3>Elasticsearchインデックス設定</h3><p><code>index-vectors-only-mapping-with-docid-mapping.json</code></p><pre><code>{
  "settings": {
    {# non-serverless-index-settings-marker-start #}
    {%- if build_flavor != "serverless" or serverless_operator == true -%}
    {% if preload_pagecache %}
    "index.store.preload": [ "vec", "vex", "vem", "veq", "veqm", "veb", "vebm" ],
    {% endif %}
    "index.number_of_shards": {{ number_of_shards | default(1) }},
    "index.number_of_replicas": {{ number_of_replicas | default(0) }}
    {%- endif -%}
    {# non-serverless-index-settings-marker-end #}
  },
  "mappings": {
    "dynamic": false,
    "properties": {
      "docid": {
        "type": "keyword"
      },
      "emb": {
        "type": "dense_vector",
        "element_type": "float",
        "dims": 1536,
        "index": true,
        "similarity": "dot_product",
        "index_options": {
          "type": {{ vector_index_type | default("bbq_hnsw") | tojson }},
          "ef_construction": 100,
          "m": 16
        }
      }
    }
  }
}</code></pre><h2>成果</h2><p>結果を解釈する方法は複数あります。レイテンシとスループットの両方について、リコールの各レベルで簡略化されたグラフと詳細なグラフをプロットしました。各指標について「高いほど良い」と考えると、違いが簡単にわかります。ただし、レイテンシはマイナス（低い方が実際には良い）であり、スループットはプラスです。簡略化されたグラフでは、 <strong>(リコール / レイテンシ) * 10000</strong> (単に「速度」と呼びます) と<strong>リコール * スループット</strong>を使用しました。したがって、両方のメトリックは、速度とスループットが高いほど優れていることを意味します。始めましょう。</p><h3>10回想 - 簡略化</h3><p>この再現レベルでは、Elasticsearch BBQ は OpenSearch FAISS よりも最大<strong>5 倍高速</strong>(平均で 3.9 倍高速) で、平均で<strong>3.2 倍のスループット</strong>を実現します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d580ad53e8974bd3aa75957c413a0136c4e465c5-1600x681.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/18edce667fe36ab95033264ef8df6f352dda2425-2044x866.png" style="width: 100%;" /><h4>リコール@10 - 詳細</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a7459b87e679f4ad963d0e2f1685499b40f6f050-1600x799.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c6036f55a13377654d296eb3148c7199e1965475-1600x799.png" style="width: 100%;" />
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">タスク</td><td style="border: 1px solid #ccc; padding: 4px;">レイテンシ平均</td><td style="border: 1px solid #ccc; padding: 4px;">スループット平均</td><td style="border: 1px solid #ccc; padding: 4px;">平均再現率</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-100-100</td><td style="border: 1px solid #ccc; padding: 4px;">11.70</td><td style="border: 1px solid #ccc; padding: 4px;">513.58</td><td style="border: 1px solid #ccc; padding: 4px;">0.89</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-1000-100</td><td style="border: 1px solid #ccc; padding: 4px;">27.33</td><td style="border: 1px solid #ccc; padding: 4px;">250.55</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">35.93</td><td style="border: 1px solid #ccc; padding: 4px;">197.26</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">13.33</td><td style="border: 1px solid #ccc; padding: 4px;">456.16</td><td style="border: 1px solid #ccc; padding: 4px;">0.92</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">2000年10月</td><td style="border: 1px solid #ccc; padding: 4px;">44.27</td><td style="border: 1px solid #ccc; padding: 4px;">161.40</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-40-40</td><td style="border: 1px solid #ccc; padding: 4px;">10.97</td><td style="border: 1px solid #ccc; padding: 4px;">539.94</td><td style="border: 1px solid #ccc; padding: 4px;">0.84</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-50-50</td><td style="border: 1px solid #ccc; padding: 4px;">11時00分</td><td style="border: 1px solid #ccc; padding: 4px;">535.73</td><td style="border: 1px solid #ccc; padding: 4px;">0.85</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">19.52</td><td style="border: 1px solid #ccc; padding: 4px;">341.45</td><td style="border: 1px solid #ccc; padding: 4px;">0.93</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">10-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">22.94</td><td style="border: 1px solid #ccc; padding: 4px;">295.19</td><td style="border: 1px solid #ccc; padding: 4px;">0.94</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-100-100</td><td style="border: 1px solid #ccc; padding: 4px;">35.59</td><td style="border: 1px solid #ccc; padding: 4px;">200.61</td><td style="border: 1px solid #ccc; padding: 4px;">0.94</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-1000-1000</td><td style="border: 1px solid #ccc; padding: 4px;">156.81</td><td style="border: 1px solid #ccc; padding: 4px;">58.30</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">181.79</td><td style="border: 1px solid #ccc; padding: 4px;">42.97</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">47.91</td><td style="border: 1px solid #ccc; padding: 4px;">155.16</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">2000年10月</td><td style="border: 1px solid #ccc; padding: 4px;">232.14</td><td style="border: 1px solid #ccc; padding: 4px;">31.84</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-40-40</td><td style="border: 1px solid #ccc; padding: 4px;">27.55</td><td style="border: 1px solid #ccc; padding: 4px;">249.25</td><td style="border: 1px solid #ccc; padding: 4px;">0.92</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-50-50</td><td style="border: 1px solid #ccc; padding: 4px;">28.78</td><td style="border: 1px solid #ccc; padding: 4px;">245.14</td><td style="border: 1px solid #ccc; padding: 4px;">0.92</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">79.44</td><td style="border: 1px solid #ccc; padding: 4px;">97.06</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">10-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">104.19</td><td style="border: 1px solid #ccc; padding: 4px;">75.49</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
              </tbody>
            </table>
          <h3>50歳の思い出 - 簡略化</h3><p>この再現率では、Elasticsearch BBQは<strong>最大5倍（平均4.2倍）高速で</strong>、平均<strong>3.9倍のスループット</strong>を実現しています。<strong> </strong>OpenSearch FAISSよりも。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/3189ffb330948b35854eeea9ae317d4846c14972-1600x681.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/607383d674dcf0b8f94bfb1a450063f52fcbeb15-2060x876.png" style="width: 100%;" /><h4>詳細な結果 - 50 回のリコール</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ebfffe0b776aad31dd03d315cfbf5aa098b41226-1600x789.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/ae20cf870a65c400a2112bbad62eb56e244f549a-1600x799.png" style="width: 100%;" />
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">タスク</td><td style="border: 1px solid #ccc; padding: 4px;">レイテンシ平均</td><td style="border: 1px solid #ccc; padding: 4px;">スループット平均</td><td style="border: 1px solid #ccc; padding: 4px;">平均再現率</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-1000-1000</td><td style="border: 1px solid #ccc; padding: 4px;">25.71</td><td style="border: 1px solid #ccc; padding: 4px;">246.44</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-1200-1200</td><td style="border: 1px solid #ccc; padding: 4px;">28.81</td><td style="border: 1px solid #ccc; padding: 4px;">227.85</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-150-150</td><td style="border: 1px solid #ccc; padding: 4px;">13.43</td><td style="border: 1px solid #ccc; padding: 4px;">362.90</td><td style="border: 1px solid #ccc; padding: 4px;">0.90</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">33.38</td><td style="border: 1px solid #ccc; padding: 4px;">202.37</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">12.99</td><td style="border: 1px solid #ccc; padding: 4px;">406.30</td><td style="border: 1px solid #ccc; padding: 4px;">0.91</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-2000-2000</td><td style="border: 1px solid #ccc; padding: 4px;">42.63</td><td style="border: 1px solid #ccc; padding: 4px;">163.68</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-250-250</td><td style="border: 1px solid #ccc; padding: 4px;">14.41</td><td style="border: 1px solid #ccc; padding: 4px;">373.21</td><td style="border: 1px solid #ccc; padding: 4px;">0.92</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">17.15</td><td style="border: 1px solid #ccc; padding: 4px;">341.04</td><td style="border: 1px solid #ccc; padding: 4px;">0.93</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">50-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">31.25</td><td style="border: 1px solid #ccc; padding: 4px;">248.60</td><td style="border: 1px solid #ccc; padding: 4px;">0.94</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-1000-1000</td><td style="border: 1px solid #ccc; padding: 4px;">125.35</td><td style="border: 1px solid #ccc; padding: 4px;">62.53</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-1200-1200</td><td style="border: 1px solid #ccc; padding: 4px;">143.87</td><td style="border: 1px solid #ccc; padding: 4px;">54.75</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-150-150</td><td style="border: 1px solid #ccc; padding: 4px;">43.64</td><td style="border: 1px solid #ccc; padding: 4px;">130.01</td><td style="border: 1px solid #ccc; padding: 4px;">0.89</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">169.45</td><td style="border: 1px solid #ccc; padding: 4px;">46.35</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">48.05</td><td style="border: 1px solid #ccc; padding: 4px;">156.07</td><td style="border: 1px solid #ccc; padding: 4px;">0.91</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-2000-2000</td><td style="border: 1px solid #ccc; padding: 4px;">216.73</td><td style="border: 1px solid #ccc; padding: 4px;">36.38</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-250-250</td><td style="border: 1px solid #ccc; padding: 4px;">53.52</td><td style="border: 1px solid #ccc; padding: 4px;">142.44</td><td style="border: 1px solid #ccc; padding: 4px;">0.93</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">78.98</td><td style="border: 1px solid #ccc; padding: 4px;">97.82</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">50-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">103.20</td><td style="border: 1px solid #ccc; padding: 4px;">75.86</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
              </tbody>
            </table>
          <h3>リコール@100</h3><p>この再現レベルでは、Elasticsearch BBQ は OpenSearch FAISS よりも<strong>最大 5 倍高速</strong>(平均 4.6 倍高速) で、平均で<strong>3.9 倍のスループット</strong>を実現します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d3f29db64cbde9956de1fa3ae64a75f15141a2bb-1600x681.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/10aff7f8c61fdac895b9ba9c5342baf239ba3ffc-2072x864.png" style="width: 100%;" /><h4>詳細な結果 - 再現率 100</h4><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8790fdf9512b850447f6875fb69969f6f1d4da5f-1600x799.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d47032fac6c288cd4eedd9f25001e417b2fa9d65-1600x787.png" style="width: 100%;" />
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">タスク</td><td style="border: 1px solid #ccc; padding: 4px;">レイテンシ平均</td><td style="border: 1px solid #ccc; padding: 4px;">スループット平均</td><td style="border: 1px solid #ccc; padding: 4px;">平均再現率</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-1000-1000</td><td style="border: 1px solid #ccc; padding: 4px;">27.82</td><td style="border: 1px solid #ccc; padding: 4px;">243.22</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-1200-1200</td><td style="border: 1px solid #ccc; padding: 4px;">31.14</td><td style="border: 1px solid #ccc; padding: 4px;">224.04</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">35.98</td><td style="border: 1px solid #ccc; padding: 4px;">193.99</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">14.18</td><td style="border: 1px solid #ccc; padding: 4px;">403.86</td><td style="border: 1px solid #ccc; padding: 4px;">0.88</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-2000-2000</td><td style="border: 1px solid #ccc; padding: 4px;">45.36</td><td style="border: 1px solid #ccc; padding: 4px;">159.88</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-250-250</td><td style="border: 1px solid #ccc; padding: 4px;">14.77</td><td style="border: 1px solid #ccc; padding: 4px;">433.06</td><td style="border: 1px solid #ccc; padding: 4px;">0.90</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-300-300</td><td style="border: 1px solid #ccc; padding: 4px;">14.61</td><td style="border: 1px solid #ccc; padding: 4px;">375.54</td><td style="border: 1px solid #ccc; padding: 4px;">0.91</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">18.88</td><td style="border: 1px solid #ccc; padding: 4px;">340.37</td><td style="border: 1px solid #ccc; padding: 4px;">0.93</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">Elasticsearch-9.0-BBQ</td><td style="border: 1px solid #ccc; padding: 4px;">100-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">23.59</td><td style="border: 1px solid #ccc; padding: 4px;">285.79</td><td style="border: 1px solid #ccc; padding: 4px;">0.94</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-1000-1000</td><td style="border: 1px solid #ccc; padding: 4px;">142.90</td><td style="border: 1px solid #ccc; padding: 4px;">58.48</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-1200-1200</td><td style="border: 1px solid #ccc; padding: 4px;">153.03</td><td style="border: 1px solid #ccc; padding: 4px;">51.04</td><td style="border: 1px solid #ccc; padding: 4px;">0.95</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-1500-1500</td><td style="border: 1px solid #ccc; padding: 4px;">181.79</td><td style="border: 1px solid #ccc; padding: 4px;">43.20</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-200-200</td><td style="border: 1px solid #ccc; padding: 4px;">50.94</td><td style="border: 1px solid #ccc; padding: 4px;">131.62</td><td style="border: 1px solid #ccc; padding: 4px;">0.83</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-2000-2000</td><td style="border: 1px solid #ccc; padding: 4px;">232.53</td><td style="border: 1px solid #ccc; padding: 4px;">33.67</td><td style="border: 1px solid #ccc; padding: 4px;">0.96</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-250-250</td><td style="border: 1px solid #ccc; padding: 4px;">57.08</td><td style="border: 1px solid #ccc; padding: 4px;">131.23</td><td style="border: 1px solid #ccc; padding: 4px;">0.87</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-300-300</td><td style="border: 1px solid #ccc; padding: 4px;">62.76</td><td style="border: 1px solid #ccc; padding: 4px;">120.10</td><td style="border: 1px solid #ccc; padding: 4px;">0.89</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-500-500</td><td style="border: 1px solid #ccc; padding: 4px;">84.36</td><td style="border: 1px solid #ccc; padding: 4px;">91.54</td><td style="border: 1px solid #ccc; padding: 4px;">0.93</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">OpenSearch-2.19-faiss</td><td style="border: 1px solid #ccc; padding: 4px;">100-750-750</td><td style="border: 1px solid #ccc; padding: 4px;">111.33</td><td style="border: 1px solid #ccc; padding: 4px;">69.95</td><td style="border: 1px solid #ccc; padding: 4px;">0.94</td>
                    </tr>
              </tbody>
            </table>
          <h2>バーベキューの改良</h2><p>BBQ は最初のリリース以来長い道のりを歩んできました。Elasticsearch 8.16 では、比較のために、8.16 のベンチマーク実行を現在のものと並べて含めており、それ以降、リコールとレイテンシーがどのように改善されたかを確認できます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/45a0acfe5985bff28ccada76ec4eca190fe65f72-1600x799.png" style="width: 100%;" /><p>Elasticsearch 8.18 および 9.0 では、ベクトルを量子化するためのコア アルゴリズムを書き直しました。つまり、8.16 の BBQ は良かったのですが、最新バージョンはさらに良くなっています。それについては<a href="https://www.elastic.co/jp/search-labs/blog/optimized-scalar-quantization-elasticsearch">ここ</a>と<a href="https://www.elastic.co/jp/search-labs/blog/scalar-quantization-optimization">ここで</a>読むことができます。つまり、すべてのベクトルは、最適化されたスカラー分位数を通じて個別に量子化されます。その結果、ユーザーはパフォーマンスを犠牲にすることなくベクトル検索の精度向上の恩恵を受けることができ、Elasticsearch のベクトル検索はさらに強力になります。</p><h2>まとめ</h2><p>Elasticsearch BBQ と OpenSearch FAISS のパフォーマンス比較では、Elasticsearch はベクトル検索において OpenSearch を大幅に上回り、さまざまなレベルのリコールで平均最大 5 倍のクエリ速度と 3.9 倍のスループットを達成しています。</p><p>主な調査結果は次のとおりです。</p><ul><li><strong>Recall@10</strong> : Elasticsearch BBQ は、OpenSearch FAISS と比較して最大 5 倍高速 (平均 3.9 倍高速) で、平均 3.2 倍のスループットを実現します。</li><li><strong>Recall@50</strong> : Elasticsearch BBQ は、OpenSearch FAISS と比較して最大 5 倍高速 (平均で 4.2 倍高速) で、平均で 3.9 倍のスループットを実現します。</li><li><strong>Recall@100</strong> : Elasticsearch BBQ は、OpenSearch FAISS と比較して最大 5 倍高速 (平均で 4.6 倍高速) で、平均で 3.9 倍のスループットを実現します。</li></ul><p>これらの結果は、特に高次元ベクトル検索シナリオにおける Elasticsearch BBQ の効率性とパフォーマンスの利点を強調しています。Elasticsearch 8.16 で導入された Better Binary Quantization (BBQ) 技術は、高いランキング品質を維持しながらメモリを大幅に削減 (約 95%) するため、大規模なベクトル検索アプリケーションに最適です。</p><p>Elastic では、RAG (Retrieval Augmented Generation) を含む検索および取得ユースケースに最適なベクター データベースを提供するために、Apache Lucene と Elasticsearch を改良するための革新を絶えず続けています。<a href="https://www.elastic.co/jp/search-labs/blog/optimized-scalar-quantization-elasticsearch">最近の進歩</a>によりパフォーマンスが劇的に向上し、Lucene 10 から得た成果を基にして、ベクター検索が以前よりも高速かつスペース効率が高くなりました。このブログは、その革新のもう一つの例です。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Elasticsearch の異なるバージョン間およびクラスター間でデータを移行する方法]]></title>
            <description><![CDATA[Elasticsearch のバージョンとクラスター間でデータを転送する方法を検討します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-migrate-data-versions-clusters</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-migrate-data-versions-clusters</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Mon, 14 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/01fde3b1d714f12bf8673140c9f2f940d443de31-1440x823.jpg?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticsearch クラスターをアップグレードする場合、新しい別のクラスターを作成し、古いクラスターから新しいクラスターにデータを転送する方が簡単な場合があります。これにより、ユーザーは、ダウンタイムやデータ損失のリスクなしに、すべてのアプリケーションを使用して新しいクラスター上のすべてのデータと構成をテストできるという利点が得られます。</p><p>このアプローチの欠点は、ハードウェアの重複が必要となり、すべてのデータをスムーズに転送および同期する際に困難が生じる可能性があることです。</p><p>アプリケーションをあるデータ センターから別のデータ センターに移行する必要がある場合にも、同様の手順を実行する必要がある場合があります。</p><p>この記事では、Elasticsearch クラスター間でデータを転送する 3 つの方法について詳しく説明します。</p><p><strong>Elasticsearch クラスター間でデータを移行するにはどうすればよいですか?</strong></p><p>Elasticsearch クラスター間でデータを転送する方法は 3 つあります。</p><ol><li><a href="https://www.elastic.co/jp/search-labs/blog/elasticsearch-migrate-data-versions-clusters#1.-reindexing-data-from-a-remote-cluster">リモートクラスタからの再インデックス</a></li><li><a href="https://www.elastic.co/jp/search-labs/blog/elasticsearch-migrate-data-versions-clusters#2.-transferring-data-using-snapshots">スナップショットを使用したデータ転送</a></li><li><a href="https://www.elastic.co/jp/search-labs/blog/elasticsearch-migrate-data-versions-clusters#3.-transferring-data-using-logstash">Logstash を使用したデータ転送</a></li></ol><p>通常、スナップショットを使用するのが、データを転送する最も高速かつ信頼性の高い方法です。ただし、スナップショットは同等以上のバージョンのクラスターにのみ復元でき、メジャー バージョンが 1 つ以上異なるクラスターには復元できないことに注意してください。つまり、6.x スナップショットを 7.x クラスターに復元することはできますが、8.x クラスターには復元できません。</p><p>メジャー バージョンを 1 つ以上増やす必要がある場合は、インデックスを再作成するか、Logstash を使用する必要があります。</p><p>ここで、Elasticsearch クラスター間でデータを転送するための 3 つのオプションをそれぞれ詳しく見ていきましょう。</p><h2>1. リモートクラスタからのデータの再インデックス</h2><p>再インデックスを開始する前に、新しいクラスター上のすべてのインデックスに対して適切なマッピングを設定する必要があることに注意してください。そのためには、適切なマッピングを使用してインデックスを直接作成するか、インデックス テンプレートを使用する必要があります。</p><h3>リモートからの再インデックス - 設定が必要</h3><p>リモートから再インデックスを行うには、データを受信しているクラスターの elasticseearch.yml ファイルに以下の構成を追加する必要があります。Linux システムでは、このファイルは、通常、/etc/elasticsearch/elasticsearch.yml にあります。追加する構成は次のとおりです。</p><pre><code>reindex.remote.whitelist: "192.168.1.11:9200"</code></pre><p>SSL を使用している場合は、各ノードに CA 証明書を追加し、elasticsearch.yml 内の各ノードのコマンドに以下を含める必要があります。</p><pre><code>reindex.ssl.certificate_authorities: “/path/to/ca.pem”</code></pre><p>あるいは、SSL 検証を無効にするために、すべての Elasticsearch ノードに以下の行を追加することもできます。ただし、このアプローチは前のオプションほど安全ではないため、あまりお勧めできません。</p><pre><code>reindex.remote.whitelist: "192.168.1.11:9200"
reindex.ssl.verification_mode: none
systemctl restart elasticsearch service </code></pre><p>すべてのノードでこれらの変更を行い、ローリング再起動を実行する必要があります。その方法の詳細については、<a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/8.17/restart-cluster.html#restart-cluster-rolling">ガイド</a>をご覧ください。</p><h3>再インデックスコマンド</h3><p>elasticsearch.yml ファイルでリモート ホストを定義し、必要に応じて SSL 証明書を追加したら、以下のコマンドでデータの再インデックスを開始できます。</p><pre><code>POST _reindex
{
  "source": {
    "remote": {
      "host": "http://192.168.1.11:9200",
      "username": "elastic",
      "password": "123456",
     "socket_timeout": "1m",
      "connect_timeout": "1m"

    },
    "index": "companydatabase"
  },
  "dest": {
    "index": "my-new-index-000001"
  }
}</code></pre><p>その際、タイムアウト エラーが発生する可能性があるため、デフォルトに頼るのではなく、タイムアウトに余裕を持った値を設定すると便利な場合があります。</p><p>ここで、リモートから再インデックスするときに発生する可能性のあるその他の一般的なエラーを見てみましょう。</p><h3>リモートからの再インデックス時によくあるエラー</h3><h4>1. 再インデックスがホワイトリストに登録されていない</h4><pre><code>{
  "error": {
    "root_cause": [
      {
        "type": "illegal_argument_exception",
        "reason": "[192.168.1.11:9200] not whitelisted in reindex.remote.whitelist"
      }
    ],
    "type": "illegal_argument_exception",
    "reason": "[192.168.1.11:9200] not whitelisted in reindex.remote.whitelist"
  },
  "status": 400
}</code></pre><p>このエラーが発生した場合は、上記のように Elasticsearch でリモート ホストの IP アドレスまたはノード名 DNS を定義しなかったか、Elasticsearch サービスの再起動を忘れたことを示しています。</p><p>Elasticsearch クラスターでこの問題を修正するには、リモート ホストをすべての Elasticsearch ノードに追加し、Elasticsearch サービスを再起動する必要があります。</p><h4>2. SSLハンドシェイク例外</h4><pre><code>{
  "error": {
    "root_cause": [
      {
        "type": "s_s_l_handshake_exception",
        "reason": "PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target"
      }
    ],
    "type": "s_s_l_handshake_exception",
    "reason": "PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target",
    "caused_by": {
      "type": "validator_exception",
      "reason": "PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target",
      "caused_by": {
        "type": "sun_cert_path_builder_exception",
        "reason": "unable to find valid certification path to requested target"
      }
    }
  },
  "status": 500
}</code></pre><p>このエラーは、上記のように elasticsearch.yml に reindex.ssl.certificate_authorities を追加するのを忘れたことを意味します。追加するには:</p><pre><code>#elasticsearch.yml
reindex.ssl.certificate_authorities: "/path/to/ca.pem"</code></pre><h2>2. スナップショットを使用したデータ転送</h2><p>前述のように、スナップショットは同等以上のバージョンのクラスタにのみ復元でき、1つ以上のメジャーバージョンの違いがあるクラスタには復元できないことに注意してください。</p><p>メジャー バージョンを 1 つ以上増やす必要がある場合は、インデックスを再作成するか、Logstash を使用する必要があります。</p><p>スナップショットを介してデータを転送するには、次の手順が必要です。</p><p>ステップ 1. 最初の Elasticsearch クラスターにリポジトリ プラグインを追加する – スナップショットを介してクラスター間でデータを転送するには、新しいクラスターと古いクラスターの両方からリポジトリにアクセスできることを確認する必要があります。通常、AWS、Google、Azure などのクラウド ストレージ リポジトリがこれに最適です。スナップショットを撮るには、<a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/current/snapshot-restore.html">ガイド</a>を参照して、そこに記載されている手順に従ってください。</p><p>ステップ 2. Elasticsearch サービスを再起動します (ローリング再起動)。</p><p>ステップ 3. 最初の Elasticsearch クラスターのリポジトリを作成します。</p><p>ステップ 4 - リポジトリ プラグインを 2 番目の Elasticsearch クラスターに追加します。</p><p>ステップ 5 - リポジトリを 2 番目の Elasticsearch クラスターに読み取り専用として追加する - 最初の Elasticsearch クラスターを作成するときに実行したのと同じ手順を繰り返して、リポジトリを追加する必要があります。</p><p>重要な注意: 2 番目の Elasticsearch クラスターを同じ AWS S3 リポジトリに接続する場合は、リポジトリを読み取り専用リポジトリとして定義する必要があります。</p><pre><code>PUT _snapshot/my_s3_repository
{
  "type": "s3",
  "settings": {
    "bucket": "my-analytic-data",
    "endpoint": "s3.eu-de.cloud-object-storage.appdomain.cloud",
    "readonly": "true"
  }
}</code></pre><p>これは、同じスナップショットリポジトリ内で Elasticsearch のバージョンが混在するリスクを回避するために重要です。</p><p>ステップ 6 - 2 番目の Elasticsearch クラスターへのデータの復元 - 上記の手順を実行した後、データを復元して新しいクラスターに転送できます。新しいクラスターにデータを復元するには、<a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/current/snapshot-restore.html">この記事</a>に記載されている手順に従ってください。</p><h2>3. Logstash を使用したデータ転送</h2><p>logstash を使用してデータの転送を開始する前に、新しいクラスター上のすべてのインデックスに対して適切なマッピングを設定する必要があることに注意してください。そのためには、インデックスを直接作成するか、インデックス テンプレートを使用する必要があります。</p><p>2 つの Elasticsearch クラスター間でデータを転送するには、一時的な Logstash サーバーをセットアップし、それを使用して 2 つのクラスター間でデータを転送できます。小規模なクラスターの場合、2GB の RAM インスタンスで十分です。より大規模なクラスターの場合は、8GB の RAM を搭載した 4 コア CPU を使用できます。</p><p>Logstash のインストールに関するガイダンスについては、<a href="https://www.elastic.co/jp/guide/en/logstash/current/installing-logstash.html">こちらをご覧</a>ください。</p><h3>あるクラスターから別のクラスターにデータを転送するための Logstash 構成</h3><p>クラスター A からクラスター B に単一のインデックスをコピーするための基本構成は次のとおりです。</p><pre><code>iinput
{
elasticsearch
      {
        hosts => ["192.168.1.11:9200"]
        index => "index_name"
       docinfo => true      
      }
}

output 
{
  elasticsearch {
        hosts => "https://192.168.1.12:9200"
        index => "index_name"
        
  }
}</code></pre><p>セキュアな elasticsearch の場合は、以下の構成を使用できます。</p><pre><code>input
{
  elasticsearch
      {
        hosts => ["192.168.1.11:9200"]
        index => "index_name"
        docinfo => true 
        user => "elastic"
        password => "elastic_password"
        ssl => true
        ssl_certificate_verification => false
            
      }
}

output 
{
  elasticsearch {
        hosts => "https://192.168.1.12:9200"
        index => "index_name"
        user => "elastic"
        password => "elastic_password"
        ssl => true
        ssl_certificate_verification => false
  }
}</code></pre><h3>インデックスメタデータ</h3><p>上記のコマンドは、単一の名前付きインデックスに書き込みます。複数のインデックスを転送し、インデックス名を保持する場合は、Logstash 出力に次の行を追加する必要があります。</p><pre><code>index => "%{[@metadata][_index]}"</code></pre><p>また、ドキュメントの元の ID を保持したい場合は、以下を追加する必要があります。</p><pre><code>document_id => "%{[@metadata][_id]}"</code></pre><p>ドキュメント ID を設定するとデータ転送速度が大幅に低下することに注意してください。必要な場合にのみ元の ID を保持してください。</p><h2>更新の同期</h2><p>上記の方法はすべて比較的長い時間がかかり、プロセスが完了するまでに元のクラスターのデータが更新されている場合があります。</p><p>データ転送プロセス中に発生した可能性のある更新を同期できるようにするにはさまざまな戦略があり、そのプロセスを開始する前にこれらの問題について検討する必要があります。特に、次の点を考慮する必要があります。</p><ul><li>データ転送プロセスの開始以降に更新/追加されたデータを識別する方法は何ですか (例: データ内の「last_update_time」フィールド)?</li><li>最後のデータを転送するにはどのような方法を使用できますか?</li><li>記録が重複するリスクはありますか?通常は、使用している方法によって再インデックス中にドキュメント ID が既知の値に設定されない限り、存在します。</li></ul><p>更新の同期を有効にするさまざまな方法について以下に説明します。</p><h3>1. 待ち行列システムの使用</h3><p>一部の取り込み/更新システムでは、過去 x 日間に受信したデータの変更を「再生」できるキューを使用します。これにより、実行された変更を同期する手段が提供される場合があります。 </p><h3>2. リモートから再インデックス</h3><p>「last_update_time」が x 日前を超えるすべてのアイテムに対して、再インデックス処理を繰り返します。これを行うには、再インデックス リクエストに「クエリ」パラメータを追加します。</p><h3>3. ログスタッシュ</h3><p>Logstash 入力では、「last_update_time」が x 日前を超えるすべての項目をフィルターするクエリを追加できます。ただし、document_id を設定していない限り、このプロセスでは非時系列データに重複が発生します。</p><h3>4. スナップショット</h3><p>インデックスの一部だけを復元することはできないため、データ転送プロセスの実行以降に行われた変更を更新するには、上で説明した他のデータ転送方法のいずれか (またはスクリプト) を使用する必要があります。</p><p>ただし、スナップショットの復元は再インデックス/Logstash よりもはるかに高速なプロセスであるため、スナップショットの転送中に更新を短時間一時停止して、問題を完全に回避できる可能性があります。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Google Cloud の Vertex AI Platform のネイティブ基盤となる Elasticsearch ベクトル データベース]]></title>
            <description><![CDATA[Google Cloud の Vertex AI 向けの初のサードパーティ製ネイティブ グラウンディング エンジンである Elasticsearch を使用して、Gemini モデルをエンタープライズ データにグラウンディングすることで、カスタム GenAI エクスペリエンスを構築する方法をご覧ください。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/elasticsearch-google-cloud-vertex-ai-native-grounding</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/elasticsearch-google-cloud-vertex-ai-native-grounding</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Valerio Arvizzigno]]></dc:creator>
            <pubDate>Wed, 09 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/d1f1c8860f1f0b989ad698a882f869de7284ab78-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>Elasticは、Elasticsearchベクターデータベースがネイティブサポートされた情報検索エンジンとしてGoogle CloudのVertex AIプラットフォームに統合され、ユーザーがElasticsearchの高度なAI搭載セマンティックおよびハイブリッド検索機能を使用してGoogleのGeminiモデルのマルチモーダルな強みを活用できるようになることを発表します。</p><p>開発者は、統一されたジャーニー内で RAG アプリケーションを作成し、ローコードで柔軟な方法でプライベート データに基づいたチャット エクスペリエンスを実現できるようになりました。顧客や社内従業員向けの AI エージェントを構築する場合でも、ソフトウェア内で LLM 生成を活用する場合でも、Vertex AI プラットフォームでは、最小限の構成で Elasticsearch の関連性を簡単に利用できます。この統合により、本番環境のユースケースで Gemini モデルをより簡単に、より迅速に導入できるようになり、GenAI を PoC から実際のシナリオへと推進できます。</p><p>このブログでは、Elasticsearch を Google Cloud の Vertex AI プラットフォームと統合して、シームレスなデータグラウンディングと完全にカスタマイズ可能な GenAI アプリケーションの構築を実現する方法について説明します。どのようにするか見てみましょう。</p><h2>Google Cloud の Vertex AI と Gemini モデルは Elasticsearch によってデータに基づいています</h2><p>GenAI アプリケーションを作成するために Vertex AI サービスとツールを活用するユーザーは、新しい「Grounding」オプションにアクセスして、プライベート データを会話のやり取りに自動的に取り入れることができるようになりました。Elasticsearch は現在この機能の一部となっており、両方から使用できます。</p><ul><li>Vertex AI <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference">LLM API は</a>、生成時に Google の Gemini モデルを直接強化します (推奨)。</li><li><a href="https://cloud.google.com/generative-ai-app-builder/docs/grounded-gen">Grounded Generation API は</a>、Vertex AI Agent Builder エコシステムでエージェント エクスペリエンスを構築する代わりに使用されます。</li></ul><p>この統合により、最もダウンロードされ、導入されている<a href="https://www.elastic.co/jp/elasticsearch/vector-database">ベクターデータベースで</a>ある Elasticsearch が、社内のエンド カスタマー対応チャットで必要な場所に適切なエンタープライズ データを提供します。これは、GenAI をビジネス プロセスに実際に導入する上で非常に重要です。</p><p>前述の API により、開発者はこの新しい提携機能をコードに採用できるようになります。ただし、迅速なエンジニアリングとテストは、アプリケーション開発において依然として重要なステップであり、初期の発見の場として機能します。これをサポートするために、Elasticsearch は Vertex AI Studio コンソール ツール内でユーザーが簡単に評価できるように設計されています。</p><p>以下に示すように、UI の「Grounding のカスタマイズ」タブ内で、必要なパラメータ (検索するインデックス、取得するドキュメントの数、必要な検索テンプレート) を使用して Elastic エンドポイントを構成するには、いくつかの簡単な手順を実行するだけです (これを機能させるには、UI と以下のコード例に「ApiKey」という単語を含む API キーを入力する必要があることに注意してください)。これで、プライベートな知識を使って生成する準備が整いました。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/69b3d979d18fa90742d9397f57975c586edf5d9f-1003x710.gif" style="width: 100%;" /><h2>GenAIアプリケーションを簡単に本番環境に対応</h2><p>Elastic と Google Cloud は、開発者中心で包括的かつ楽しいエクスペリエンスを提供するために連携しています。LLM と Grounding Generation API の両方で Elastic にネイティブに接続することで、Vertex AI 上で GAI アプリケーションを構築する際の複雑さとオーバーヘッドが軽減され、不要な追加 API やデータ オーケストレーションが回避され、1 回の統合呼び出しでグラウンディングが可能になります。</p><p>両方のシナリオでどのように機能するかを見てみましょう。</p><p>最初の例は LLM API を使用して実行されます。</p><pre><code>curl -X POST \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \https://us-central1-aiplatform.googleapis.com/v1beta1/projects/<PROJECT_ID>/locations/us-central1/publishers/google/models/gemini-2.0-flash-001:generateContent \
  -d '
{
  "contents": [
    {
      "role": "user",
      "parts": [
        {
          "text": "What's my company car policy?"
        }
      ]
    }
  ],
  "tools": [{
    "retrieval": {
      "externalApi": {
        "api_spec": "ELASTIC_SEARCH",
    "endpoint": "https://<my-elastic-cluster>.gcp.elastic-cloud.com:9243",
    "apiAuth": {
      "apiKeyConfig": {
            "apiKeyString": "ApiKey <API_KEY>"
      }
    },
    "elasticSearchParams": {
      "index": "<my-index>",
      "searchTemplate": "<my-search-template>"
    }
      }
    }
  }]
}</code></pre><p>上記の例では、Gemini 2.0 Flash へのコンテンツ生成を要求する API の<code>retrieval</code>フィールドを使用して、要求の検索エンジンをコンテキストに応じて設定できます。<code>api_spec</code>を「ELASTIC_SEARCH」に設定すると、API キーやクラスターエンドポイント (Elastic クラスターへのリクエストをルーティングするために必要)、データを取得するインデックス、検索ロジックに使用する検索テンプレートなどの追加の構成パラメータが使用できるようになります。</p><p>同様に、Grounding Generation API を使用して<code>groundingSpec</code>パラメータを設定することで、同じ結果を得ることができます。</p><pre><code>curl -X POST -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Content-Type: application/json" https://us-discoveryengine.googleapis.com/v1alpha/projects/<PROJECT_ID>/locations/global:generateGroundedContent -d '
{
  "contents": [{
    "role": "user",
    "parts": [{
      "text": "What do I need to patch a hole in my drywall?"
    }]
  }],
  "groundingSpec": {
    "groundingSources": [{
      "elasticSource": {
        "endpoint": "https://<my-elastic-cluster>.gcp.elastic-cloud.com:9243",
        "index": "<my-index>",
        "searchTemplate": "<my-search-template",
        "apiKey": "projects/<PROJECT_ID>/secrets/api-key/versions/latest"
      }
    }]
  }
}
'</code></pre><p>どちらのアプローチでも、レスポンスには、クエリをサポートするために、Elasticsearch で見つかった最も関連性の高いプライベート ドキュメントと、関連する接続されたデータ ソースが含まれた回答が提供されます。</p><p>ただし、シンプルであることは、特定のニーズやユースケースを満たすためのパーソナライゼーションの欠如と混同しないでください。これを念頭に置いて、検索構成をシナリオに完全に適応させることができるように設計しました。</p><h2>指先で完全にカスタマイズ可能な検索:検索テンプレート</h2><p>検索シナリオを最大限にカスタマイズするために、Google Cloud と連携して、当社の有名な<a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/current/search-template.html">検索テンプレート</a>をベースにしたエクスペリエンスを構築しました。Elasticsearch 検索テンプレートは、動的で再利用可能かつ保守可能な検索クエリを作成するための優れたツールです。クエリ構造を事前に定義して再利用することができます。これらは、開発時間を節約し、エラーの可能性を減らすため、異なるパラメータを使用して同様のクエリを実行する場合に特に役立ちます。テンプレートには変数のプレースホルダーを含めることができるため、クエリは動的になり、さまざまな検索要件に適応できるようになります。</p><p>Vertex AI API と Elasticsearch をグラウンディングに使用する場合は、上記のコード スニペットに示すように、検索ロジックが実装され Elasticsearch にプッシュダウンされる目的の検索テンプレートを参照する必要があります。Elastic のパワー ユーザーは、検索アプローチを非同期的に管理、構成、更新し、特定のインデックス、モデル、データに合わせてカスタマイズできます。その操作は、Vertex AI ユーザー、ウェブ アプリ開発者、AI エンジニアにとって完全に透過的な方法で実行でき、グラウンディング API でテンプレートの名前を指定するだけで済みます。</p><p>この設計により完全なカスタマイズが可能になり、Elasticsearch の広範な検索機能を Google Cloud AI 環境で利用できるようになり、モジュール性、透明性、そして Elastic に精通していない開発者にとっても使いやすさが確保されます。</p><p>BM25 検索、セマンティック検索、または 2 つの間のハイブリッド アプローチが必要なときはいつでも (<a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/current/retrievers-overview.html">リトリーバー</a>をすでに調べましたか?)単一の検索 API 呼び出しで構成可能な検索テクニックを使用すれば、検索テンプレートでカスタム ロジックを定義でき、Vertex AI はそれを自動的に活用できます。</p><p>これは、ベクトルと結果を管理するために選択した埋め込みおよび再ランク付けモデルにも適用されます。ユースケースに応じて、Elastic の ML ノードでモデルをホストしたり、推論 API を介してサードパーティのサービスエンドポイントを使用したり、オンプレミスでローカルモデルを実行したりすることができます。これは検索テンプレートを介して実行可能であり、次のセクションでその仕組みを見ていきます。</p><h2>参照テンプレートから始めて、独自のテンプレートを作成しましょう</h2><p>すぐに使い始められるように、初期リファレンスとして使用できる互換性のある検索テンプレートのサンプル セットを用意しました。その後、それを基にしてカスタムの検索テンプレートを変更および構築できます。</p><ul><li>ELSER モデルによるセマンティック検索 (スパースベクトルとチャンキング)</li><li>e5多言語モデルによるセマンティック検索（密ベクトルとチャンキング）</li><li>Vertex AI テキスト埋め込みモデルを使用したハイブリッド検索</li></ul><p>これらは、この<a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/Cloud-Vertex-AI/search-templates">GitHub リポジトリ</a>で見つけることができます。</p><p>1 つの例を見てみましょう。製品カタログに Google Cloud の Vertex AI API を使用して埋め込みを作成します。まず、以下に示すように Elasticsearch で検索テンプレートを作成する必要があります。</p><pre><code>PUT _scripts/google-template-knn
{
  "script": {
    "lang": "mustache",
    "source": {
      "_source": {
        "excludes": [ "title_embedding", "description_embedding", "images" ]
      },
        "size": "{{num_hits}}",
          "knn" : [
          { 
            "field": "description_embedding",
            "k": 5,
            "num_candidates": 10,
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "googlevertexai_embeddings_004",
                "model_text": "{{query}}"
              }
            },
            "boost": 0.4
          },
          {
            "field": "title_embedding",
            "k": 5,
            "num_candidates": 10,
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "googlevertexai_embeddings_004",
                "model_text": "{{query}}"
            }
          },
          "boost": 0.6
          }
          ]
    }  
  }
}</code></pre><p>この例では、1 回の検索内で 2 つのフィールド ( <code>title_embedding</code> – 製品の名前を含むベクトル フィールド – と<code>description_embedding</code> – 製品の説明の表現を含むベクトル フィールド) に対して KNN 検索を実行します。</p><p><code>excludes</code>構文を利用すると、LLM に不要なフィールドが返されることを回避できます。不要なフィールドが返されると、処理中にノイズが発生し、最終的な回答の品質に影響を及ぼす可能性があります。この例では、ベクターと画像の URL を含むフィールドを除外しました。</p><p>ベクトルは、以前は次のように定義されていた Vertex AI 埋め込み API <code>googlevertexai_embeddings_004</code>への推論エンドポイントを介して送信された入力に対して、クエリ時にオンザフライで作成されます。</p><pre><code>PUT /_inference/text_embedding/googlevertexai_embeddings_004
{
    "service": "googlevertexai",
    "service_settings": {
        "service_account_json": "<your_service_account_key>",
        "model_id": "text-embedding-004",
        "location": "us-central1",
        "project_id": "<your_gcp_project>"
    }
}</code></pre><p>Elastic の推論 API の使用方法に関する追加情報については、<a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/current/inference-apis.html">こちらを</a>ご覧ください。</p><p>これで、テンプレート検索をテストする準備が整いました。</p><pre><code>GET product-catalog-with-embeddings/_search/template
{
  "id": "google-template-knn",
  "params": {
    "query": "What do I need to patch a hole in my drywall?",
    "index_name": "product-catalog-with-embeddings",
    "num_hits": 3
  }
}</code></pre><p><code>params</code>フィールドは、テンプレート スクリプトで二重中括弧内に設定した変数を置き換えます。現在、Vertex AI LLM および Grounded Generation API は、次の入力変数を Elastic に送信できます。</p><ul><li>「クエリ」 - 検索対象となるユーザークエリ</li><li>「index_name」 - 検索するインデックスの名前</li><li>「num_hits」 - 最終出力で取得したいドキュメントの数</li></ul><p>出力例は次のとおりです。</p><pre><code>{
        "_index": "product-catalog-with-embeddings",
        "_id": "9ZQCm5IBcrGI1ivqV-f_",
        "_score": 0.4925191,
        "_ignored": [
          "description.keyword",
          "images.keyword"
        ],
        "_source": {
          "description": "DAP Eclipse Rapid Wall Repair Patch is a new, revolutionary product solution for repairing drywall damage. No more waiting for spackling to dry or messy sanding. DAP Eclipse allows you to patch drywall damage and paint immediately, allowing you to finish your project faster. This all-in-1, mess free solution not only provides a permanent, long-lasting repair but also superior impact resistance for areas that may see reoccurring impact, such as behind a door.",
          "availability": "InStock",
          "model_id": "googlevertexai_embeddings_004",
          "title": "4 in. Eclipse Wall Repair Patch (2-Pack)",
          "url": "https://www.myDIYwebsite.com/p/DAP-4-in-Eclipse-Wall-Repair-Patch-2-Pack-7079809164/317967195",
          "price": 23.96,
          "product_id": 317967195,
          "currency": "USD",
          "brand": "DAP"
        }</code></pre><p>上記のクエリは、Google Cloud の Vertex AI が、以前に作成した検索テンプレートを参照するときに、バックグラウンドで Elasticsearch 上で実行するクエリとまったく同じです。Gemini モデルは、出力ドキュメントを使用して回答を根拠付けます。「乾式壁の補修には何が必要ですか?」と質問すると、一般的な提案ではなく、チャット エージェントが具体的な製品を提供します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/8c8b1374da7e9b2c17df8758acb448eed5b74e2d-1473x913.png" style="width: 100%;" /><h2>ElasticとGoogle CloudによるエンドツーエンドのGenAIジャーニー</h2><p>Elastic は Google Cloud と提携して、本番環境対応のエンドツーエンドの GenAI エクスペリエンスとソリューションを作成します。先ほど見てきたように、Elastic は Vertex AI プラットフォームの UI と SDK に直接統合された最初の ISV であり、シームレスで基盤のある Gemini モデル プロンプトとエージェントが当社のベクトル検索機能を使用して実行できるようになります。さらに、Elastic は<a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/current/infer-service-google-vertex-ai.html">Vertex AI</a>および<a href="https://www.elastic.co/jp/guide/en/elasticsearch/reference/current/infer-service-google-ai-studio.html">Google AI Studio</a>の埋め込み、再ランク付け、補完モデルと統合し、Google Cloud 環境を離れることなくベクトルを作成およびランク付けして、<a href="https://cloud.google.com/responsible-ai?hl=en">責任ある AI の</a>原則を保証します。マルチモーダルアプローチをサポートすることで、さまざまなデータ形式にわたるアプリケーションを共同で促進します。</p><p><a href="https://www.elastic.co/jp/search-labs/blog/vertex-ai-elasticsearch-playground-fast-rag-apps">Playground</a>を介して GenAI 検索コードを調整、テスト、エクスポートできます。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/b1fc48dba1713cd01c4d7f3296587d0c4e6e7e0c-862x651.png" style="width: 100%;" /><p>しかし、検索アプリの構築だけではありません。ElasticはGeminiモデルを活用して、 <a href="https://www.elastic.co/jp/blog/elastic-google-vertex-ai-integration">Elastic AIアシスタント、攻撃検出、自動インポート機能</a>などのIT運用を強化し、セキュリティアナリストやSREの低価値タスクによる日々の疲労を軽減し、ビジネスの改善に集中できるようにします。Elastic では、 <a href="https://www.elastic.co/jp/guide/en/integrations/current/gcp_vertexai.html">Vertex AI の使用状況を包括的に監視し</a>、応答時間、トークン、リソースなどのメトリックとログを追跡して、最適なパフォーマンスを確保することもできます。私たちは協力して、データの取り込みと埋め込み生成からハイブリッド検索によるグラウンディングまで、GenAI のライフサイクル全体を管理し、LLM を活用したアクションによって GenAI ツールの堅牢な観測性とセキュリティを確保します。</p><h2>さらに詳しく調べて試してみてください!</h2><p>これを試してみませんか？この機能は現在、Google Cloud プロジェクトで GA になっています。</p><p>Elastic Search AI Platform を使い始めて、その機能を試す最も簡単な方法の一つは、まだお試しいただいていない場合、 <a href="https://cloud.elastic.co/registration">Elastic Cloud の無料トライアル</a>を利用するか、 <a href="https://console.cloud.google.com/marketplace/product/elastic-prod/elastic-cloud?pli=1">Google Cloud Marketplace</a>からサブスクライブすることです。</p><p><em>この投稿で説明されている機能のリリースとタイミングは、Elastic の独自の裁量により決定されます。現在利用できない機能は、予定どおりに提供されないか、まったく提供されない可能性があります。Elastic、Elasticsearch および関連するマークは、米国およびその他の国における Elasticsearch NV の商標、ロゴ、または登録商標です。その他すべての会社名および製品名は、それぞれの所有者の商標、ロゴ、または登録商標です。</em></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[HNSWグラフのマージを高速化する]]></title>
            <description><![CDATA[複数の HNSW グラフを構築する際のオーバーヘッドを削減するために、特にグラフのマージにかかるコストを削減するために私たちが行ってきた作業について説明します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/hnsw-graphs-speed-up-merging</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/hnsw-graphs-speed-up-merging</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <dc:creator><![CDATA[Mayya Sharipova]]></dc:creator>
            <pubDate>Mon, 07 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/49a6c880f5dedd0fa502ece5be124824ee218cc0-1792x1024.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<p>以前、複数の<a href="https://www.elastic.co/jp/search-labs/blog/hnsw-graph"> HNSW グラフを</a> 検索する必要がある場合に生じるいくつかの課題と、その課題をどのように軽減できるか<a href="https://www.elastic.co/jp/search-labs/blog/multi-graph-vector-search"> について説明しました 。</a>当時、私たちは計画していたさらなる改善点についても触れました。この投稿はその作業の集大成です。</p><p>なぜ複数のグラフを使用するのかと疑問に思うかもしれません。これは、Lucene のアーキテクチャ上の選択である不変セグメントによる副作用です。ほとんどのアーキテクチャ上の選択と同様に、長所と短所があります。たとえば、最近 Serverless Elasticsearch が GA になりました。この文脈において、私たちは不変セグメントから、効率的なインデックス レプリケーションや、インデックスとクエリの計算を分離して個別に自動スケーリングする機能など、非常に大きなメリットを得ています。ベクトル量子化の場合、セグメントのマージにより、パラメータを更新してデータ特性に適合させることができます。これに沿って、データ特性を測定し、インデックスの選択を再検討する機会を持つことで得られる他の利点もあると考えています。</p><p>この投稿では、複数の HNSW グラフを構築する際のオーバーヘッドを大幅に削減し、特にグラフのマージにかかるコストを削減するために私たちが行ってきた作業について説明します。</p><h3>背景</h3><p>管理可能な数のセグメントを維持するために、Lucene はセグメントをマージする必要があるかどうかを定期的に確認します。これは、現在のセグメント数が、基本セグメント サイズとマージ ポリシーによって決定されるターゲット セグメント数を超えているかどうかを確認することになります。カウントを超過すると、Lucene は制約に違反しながらセグメントのグループを結合します。このプロセスについては<a href="https://blog.mikemccandless.com/2011/02/visualizing-lucenes-segment-merges.html">他の場所で</a>詳しく説明されています。</p><p>Lucene は、書き込み増幅の対数的増加を実現するため、同様のサイズのセグメントを結合することを選択します。ベクトル インデックスの場合、書き込み増幅はベクトルがグラフに挿入される回数です。Luceneはセグメントを約10個のグループにまとめ、マージしようとします。その結果、ベクトルはグラフに約$1+\frac {9} {10} \log_ {10} \left(\frac {n} {n_0} \right)$挿入されます。<em> </em>ここで、 $n$はインデックス ベクトル数、 $n_0 は$予想される基本セグメント ベクトル数です。対数増加のため、巨大なインデックスの場合でも書き込み増幅は 1 桁になります。ただし、グラフのマージに費やされる合計時間は、書き込み増幅に直線的に比例します。</p><p>HNSW グラフをマージする際には、最大セグメントのグラフを保持し、他のセグメントのベクトルをそのグラフに挿入するという、小さな最適化をすでに行っています。これが上記の 9/10 要因の理由です。以下では、マージするすべてのグラフの情報を使用することで、どのように大幅に改善できるかを示します。</p><h3>HNSW グラフのマージ</h3><p>これまでは、最大のグラフを保持し、他のグラフからベクトルを挿入し、それらを含むグラフは無視していました。以下で利用する重要な洞察は、破棄する各 HNSW グラフには、それに含まれるベクトルに関する重要な近接情報が含まれているということです。この情報を使用して、少なくとも一部のベクトルの挿入を高速化したいと考えています。</p><p>これは任意のマージポリシーを構築するために使用できるアトミック操作であるため、小さなグラフ$G_s=(V_s,E_s)$を大きなグラフ$G_l=(V_l,E_l)$に挿入する問題に焦点を当てます。</p><p>戦略は、 $J\subset V_s$の頂点のサブセットを見つけて、大きなグラフに挿入することです。次に、小さなグラフ内のこれらの頂点の接続性を使用して、残りの頂点$V_s \setminus J$の挿入を高速化します。以下では、小さいグラフと大きいグラフの頂点$ u$ の隣接点をそれぞれ$ N_s(u)$ と$ N_l(u)$ で表します。図式的にプロセスは次のとおりです。</p><p><code>MERGE-HNSW</code></p><p><code>Inputs $G_s$ and $G_l$</code></p><p><code>1$\;\;$Find $J\subset V_s$ to insert into $G_l$ using COMPUTE-JOIN-SET</code><br/><code>2$\;\;$Insert each vertex $u\in J$ into $G_l$</code><br/><code>3$\;\;$for $u\in V_s \setminus J$ do</code><br/><code>4$\;\;\;\;J_u\leftarrow J\cap N_s(u)$</code><br/><code>5$\;\;\;\;E_u\leftarrow \cup_{v\in J_u} N_l(u)$</code><br/><code>6$\;\;\;\;W\leftarrow\,$FAST-SEARCH-LAYER$(J_u, E_u)$</code><br/><code>7$\;\;\;\;neighbors \leftarrow\,$SELECT-NEIGHBORS-HEURISTIC$(u, W)$</code><br/><code>8$\;\;\;\;J\leftarrow J \cup \{u\}$</code></p><p>以下で説明する手順 (1 行目) を使用して、セット$J$を計算します。次に、標準の HNSW 挿入手順 (2 行目) を使用して、 $J$内のすべての頂点を大きなグラフに挿入します。挿入していない各頂点については、挿入した隣接頂点と、大きなグラフ内でのその隣接頂点を見つけます (4 行目と 5 行目)。このセットでシードされた<code>FAST-SEARCH-LAYER</code>手順 (6 行目) を使用して、HNSW<a href="https://arxiv.org/pdf/1603.09320">論文</a>から<code>SELECT-NEIGHBORS-HEURISTIC</code>の候補を検索します (7 行目)。実際には、 <code>SEARCH-LAYER</code> <code>INSERT</code>メソッド (論文のアルゴリズム 1) の候補セットを見つけるために置き換えていますが、それ以外は変更されていません。最後に、先ほど挿入した頂点を$J$ (行 8) に追加します。</p><p>これが機能するためには、 $V_s \setminus J$内のすべての頂点が$J$内に少なくとも 1 つの隣接頂点を持つ必要があることは明らかです。実際、 $u\in V_s \setminus J$内のすべての頂点に対して、 $|J\cap N_s(u) |\geq k_u$ (ある$k_u&lt;M$に対して) が最大のレイヤー接続性であることが必要です。実際の HNSW グラフでは、頂点次数がかなり広がっていることがわかります。下の図は、Lucene HNSW グラフの最下層の頂点次数の典型的な累積密度関数を示しています。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/44f001b3a1bc6627e172fed5c52a02cfdcb4cd66-1324x898.png" style="width: 100%;" /><p>$k_u$に固定値を使用することと、それを頂点次数の関数にすることを検討しました。この2番目の選択肢は、グラフの品質への影響を最小限に抑えながら大幅なスピードアップにつながるため、次の方法を採用しました。</p><div class="katex">k_u=\max\left(2,\frac{1}{4}|N_s(u)|\right)</div><p>定義により、 $|N_s(u)$ | は小さなグラフの頂点$u$の次数に等しいことに注意してください。下限が 2 の場合、次数が 2 未満のすべての頂点が挿入されます。</p><p>単純な計算の議論によれば、 $Jを$慎重に選択すれば、約$\frac {1} {5} |V_s|をG_l$に直接挿入するだけでよいことがわかります。具体的には、グラフの端の頂点の 1 つを$J$に挿入すると、グラフのエッジに色を付けます。すると、$ V_s \setminus J 内のすべての頂点が J$ 内に少なくとも$ k_u$ 個の 隣接頂点を持つためには、少なくとも$ \sum_{u\in V_s\setminus J} k_u$ 個の 辺を色付けする必要があることがわかります。さらに、我々は</p><div class="katex">\sum_{u\in V_s\setminus J} k_u\approx \left(|V_s|-|J|\right)\frac{1}{4}\mathbb{E}_U\left[|N_s(U)|\right]</div><p>ここで、 $\mathbb {E} _U\left[N_s(U)|\right]は$小さなグラフ内の平均頂点次数です。J内の各頂点$u\in J$に対して、最大で$|N_s(u)|$の辺を色付けします。したがって、色付けすると予想されるエッジの総数は最大で$|J|\, \mathbb {E} _U\left[|N_s(U)|\right]$となります。$Jを$慎重に選択することで、この辺の数に近い色を塗ることができると期待しており、すべての頂点をカバーするには$|J|が$満たす必要がある。</p><div class="katex">|J|\, \mathbb{E}_U\left[|N_s(U)|\right] =\left(|V_s|-|J|\right)\frac{1}{4}\mathbb{E}_U\left[|N_s(U)|\right]</div><p>これは、 $|J|=\frac {1} {4} (|V_s|-|J|)=\frac {4} {5} \frac {1} {4} |V_s|=\frac {1} {5} |V_s|$を意味します。</p><p><code>SEARCH-LAYER</code>が実行時間を支配すると、マージ時間を最大$5 倍$高速化できる可能性があります。書き込み増幅の対数的増加を考慮すると、非常に大きなインデックスの場合でも、通常は 1 つのグラフを構築する場合と比べて構築時間が 2 倍になるだけです。</p><p>この戦略のリスクは、グラフの品質が損なわれることです。最初は何も実行しない<code>FAST-SEARCH-LAYER</code>を試しました。これにより、特に単一のセグメントにマージするときに、レイテンシの関数としてのリコールが影響を受ける程度にグラフの品質が低下することがわかりました。次に、グラフの限定的な検索を使用して、さまざまな代替案を検討しました。結局、最も効果的な選択は最も単純なものでした。<code>SEARCH-LAYER</code>を使用しますが、 <code>ef_construction</code>は低くします。このパラメータ化により、優れた品質のグラフを実現しながらも、マージ時間を平均で 30% 強短縮することができました。</p><h3>結合セットの計算</h3><p>適切な結合セットを見つけることは、HNSW グラフ カバー問題として定式化できます。貪欲ヒューリスティックは、最適なグラフカバーを近似するためのシンプルで効果的なヒューリスティックです。私たちが採用するアプローチでは、ゲインが減少する順に頂点を 1 つずつ選択して$J$に追加します。ゲインは次のように定義されます。</p><div class="katex">Gain(v)=\max(k_v-c(v),0)+\sum_{u\in N_s(v)\setminus J} 1\left\{c(u)<k_u\right\}</div><p>ここで、$ c(v)は J$ におけるベクトル$ v$ の近傍の数を表し、$ 1\{\cdot\}$ は 指示関数である。ゲインには、 $J$に追加した頂点の数の変化、つまり$\max(k_v-c(v),0)$が含まれます。これは、カバーされていない頂点を追加することで目標に近づくためです。中央のオレンジ色の頂点のゲイン計算を下の図に示します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/fe11cd55a7d94d9e0b0f4d47ea309a99075c355d-540x474.png" style="width: 100%;" /><p>各頂点$v$に対して次の状態を維持します。</p><ol><li>古くなっても、</li><li>そのゲイン$Gain(v)$ 、</li><li>$J$内の隣接頂点の数は$c(v)$と表され、</li><li>タイブレークに使用される範囲 [0,1] 内の乱数。</li></ol><p>結合セットを計算するための疑似コードは次のとおりです。</p><p><code>COMPUTE-JOIN-SET</code></p><p><code>Inputs $G_s$</code></p><p><code>1$\;\;C\leftarrow\emptyset$</code><br/><code>2$\;\;Gain_{exit}\leftarrow 0$</code><br/><code>3$\;\;$for $u\in G_s$ do</code><br/><code>4$\;\;\;\;C\leftarrow C \cup \{(\text{false}, k_u+deg(u), 0, \text{rand in }[0,1])\}$</code><br/><code>5$\;\;\;\;Gain_{exit}\leftarrow Gain_{exit}+k_u$</code><br/><code>6$\;\;Gain_{tot}\leftarrow 0$</code><br/><code>7$\;\;$while $Gain_{tot}&lt;Gain_{exit}$ do<br/>8$\;\;\;\;v^*\leftarrow\,$ maximum gain vertex in $C$</code><br/><code>9$\;\;\;\;$Remove the state for $v^*$ from $C$</code><br/><code>10$\;\;\;$if $v^*$ is not stale then</code><br/><code>11$\;\;\;\;\;J\leftarrow J\cup\{v^*\}$</code><br/><code>12$\;\;\;\;\;Gain_{tot}\leftarrow Gain_{tot}+Gain(v^*)$</code><br/><code>13$\;\;\;\;\;$for $u \in N_s(v^*)$ do</code><br/><code>14$\;\;\;\;\;\;\;$mark $u$ as stale if $c(v^*)&lt;k_{v^*}$</code><br/><code>15$\;\;\;\;\;\;\;$mark neighbors of $u$ stale if $c(u)=k_u-1$<br/>16$\;\;\;\;\;\;\;c(u)\leftarrow c(u)+1$<br/>17$\;\;\;$else<br/>18$\;\;\;\;\;Gain(v^*)\leftarrow \max(k_v-c(v^*),0)+\sum_{u\in N_s(v^*)\setminus J} 1\left\{c(u)&lt;k_u\right\}$<br/>19$\;\;\;\;\;$if $Gain(v^*)&gt;0$ then<br/>20$\;\;\;\;\;\;\;C\leftarrow C \cup \{(\text{false},Gain(v^*),c(v^*),\text{copy rand})\}$<br/>21$\;\;$return $J$</code></p><p>まず 1 行目から 5 行目で状態を初期化します。</p><p>メイン ループの各反復では、最初に最大ゲイン頂点 (行 8) を抽出し、同点の場合はランダムに決定します。変更を加える前に、頂点のゲインが古くなっているかどうかを確認する必要があります。特に、 $J$に頂点を追加するたびに、他の頂点のゲインに影響を与えます。</p><ol><li>すべての隣接ノードは$J$に追加の隣接ノードを持つため、ゲインは変化する可能性がある（14行目）</li><li>いずれかの隣接セルが完全にカバーされている場合、その隣接セルのゲインはすべて変化する可能性があります（14～16行目）</li></ol><p>ゲインは遅延方式で再計算されるため、頂点を$J$に挿入する場合にのみ、その頂点のゲインを再計算します (18 ～ 20 行目)。ゲインは常に減少するだけなので、挿入すべき頂点を見逃すことは決してありません。</p><p>いつ終了するかを決定するには、 $J$に追加した頂点の合計増加を追跡するだけでよいことに注意してください。さらに、 $Gain_ {tot} &lt;Gain_ {exit}$の場合、少なくとも 1 つの頂点のゲインはゼロではないため、常に進歩します。</p><h3>成果</h3><p>私たちは、サポートされている 3 つの距離メトリック (ユークリッド、コサイン、内積) をカバーする 4 つのデータセットで実験を実行しました。</p><ol><li>quora-E5-small: 522931文書、384次元、コサイン類似度を使用、</li><li>cohere-wikipedia-v2: 100万文書、768次元、コサイン類似度を使用。</li><li>要点: 100万文書、960次元、ユークリッド距離を使用、</li><li>cohere-wikipedia-v3: 100 万件のドキュメント、1024 次元、最大内積を使用します。</li></ol><p>各データセットに対して、2 つの量子化レベルを評価します。</p><ol><li>int8 – 次元ごとに1バイトの整数を使用し、</li><li>BBQ – 次元ごとに 1 ビットを使用します。</li></ol><p>最後に、各実験について、2 つの検索深度で検索品質を評価し、インデックスを構築した後と、単一のセグメントに強制的にマージした後を調べます。</p><p>要約すると、グラフの品質を維持しながら、インデックス作成とマージの速度を一貫して大幅に向上させ、すべてのケースで検索パフォーマンスを実現できます。</p><h4>実験1: int8量子化</h4><p>ベースラインから候補、つまり提案された変更までの平均的な高速化は次のとおりです。</p><p>インデックス時間の高速化: <strong>1.28</strong> $<strong>\times</strong>$</p><p>強制マージの高速化: <strong>1.72$倍$</strong></p><p>これは実行時間の次の内訳に対応する。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5f9f6d487b4c74c4998aa47465912c1a9743e928-734x479.png" style="width: 100%;" /><p>完全性のために正確な時間は</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">索引</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">マージ</td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">データセット</td><td style="border: 1px solid #ccc; padding: 4px;">ベースライン</td><td style="border: 1px solid #ccc; padding: 4px;">候補者</td><td style="border: 1px solid #ccc; padding: 4px;">構築</td><td style="border: 1px solid #ccc; padding: 4px;">候補者</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">quora-E5-small</td><td style="border: 1px solid #ccc; padding: 4px;">112.41秒</td><td style="border: 1px solid #ccc; padding: 4px;">81.55秒</td><td style="border: 1px solid #ccc; padding: 4px;">113.81秒</td><td style="border: 1px solid #ccc; padding: 4px;">70.87秒</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ウィキコヒアv2</td><td style="border: 1px solid #ccc; padding: 4px;">158.1秒</td><td style="border: 1px solid #ccc; padding: 4px;">122.95秒</td><td style="border: 1px solid #ccc; padding: 4px;">425.20秒</td><td style="border: 1px solid #ccc; padding: 4px;">239.28秒</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">要旨</td><td style="border: 1px solid #ccc; padding: 4px;">141.82秒</td><td style="border: 1px solid #ccc; padding: 4px;">119.26秒</td><td style="border: 1px solid #ccc; padding: 4px;">536.07秒</td><td style="border: 1px solid #ccc; padding: 4px;">279.05秒</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ウィキコヒアv3</td><td style="border: 1px solid #ccc; padding: 4px;">211.86秒</td><td style="border: 1px solid #ccc; padding: 4px;">168.22秒</td><td style="border: 1px solid #ccc; padding: 4px;">654.97秒</td><td style="border: 1px solid #ccc; padding: 4px;">414.12秒</td>
                    </tr>
              </tbody>
            </table>
          <p>以下に、複数のセグメントを持つインデックス（すべてのベクトルをインデックスした後のデフォルトのマージ戦略の最終結果）と単一セグメントへの強制マージ後のインデックスの、2 つの検索深度（recall@10 と recall@100）と、単一セグメントへの強制マージ後の、候補（破線）とベースラインを比較したリコール対レイテンシのグラフを示します。曲線が高く、左に寄っているほど良好であり、これは、より低いレイテンシでより高い再現性を意味します。</p><p>ご覧のとおり、複数のセグメント インデックスの場合、候補は Cohere v3 データセットの方が優れており、他のすべてのデータセットではわずかに劣りますが、ほぼ同等です。単一セグメントに統合した後、リコール曲線はすべてのケースでほぼ同じになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/9195297f19f90b172d832ba56bdada7b0d8a768b-985x392.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c866f32d579ae2b841e92ca733dd29c0e214ac6b-986x386.png" style="width: 100%;" /><h4>実験2：BBQ量子化</h4><p>ベースラインから候補までの平均的な高速化は次のとおりです。</p><p>インデックス時間の高速化: <strong>1.33</strong> $<strong>\times</strong>$</p><p>強制マージの高速化: <strong>1.34$倍$</strong></p><p>これは実行時間の次の内訳に対応する。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/11be0cf6b63480dc703409329357ea4847b55051-740x415.png" style="width: 100%;" /><p>完全性のために正確な時間は</p>
            <table style="border-collapse: collapse; margin: 20px 0;">
              <tbody>
                
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">索引</td><td style="border: 1px solid #ccc; padding: 4px;"></td><td style="border: 1px solid #ccc; padding: 4px;">マージ</td><td style="border: 1px solid #ccc; padding: 4px;"></td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">データセット</td><td style="border: 1px solid #ccc; padding: 4px;">ベースライン</td><td style="border: 1px solid #ccc; padding: 4px;">候補者</td><td style="border: 1px solid #ccc; padding: 4px;">構築</td><td style="border: 1px solid #ccc; padding: 4px;">候補者</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">quora-E5-small</td><td style="border: 1px solid #ccc; padding: 4px;">70.71秒</td><td style="border: 1px solid #ccc; padding: 4px;">58.25秒</td><td style="border: 1px solid #ccc; padding: 4px;">59.38秒</td><td style="border: 1px solid #ccc; padding: 4px;">40.15秒</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ウィキコヒアv2</td><td style="border: 1px solid #ccc; padding: 4px;">203.08秒</td><td style="border: 1px solid #ccc; padding: 4px;">142.27秒</td><td style="border: 1px solid #ccc; padding: 4px;">107.27秒</td><td style="border: 1px solid #ccc; padding: 4px;">85.68秒</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">要旨</td><td style="border: 1px solid #ccc; padding: 4px;">110.35秒</td><td style="border: 1px solid #ccc; padding: 4px;">105.52秒</td><td style="border: 1px solid #ccc; padding: 4px;">323.66秒</td><td style="border: 1px solid #ccc; padding: 4px;">202.2秒</td>
                    </tr>
                    <tr>
                      <td style="border: 1px solid #ccc; padding: 4px;">ウィキコヒアv3</td><td style="border: 1px solid #ccc; padding: 4px;">313.43秒</td><td style="border: 1px solid #ccc; padding: 4px;">190.63秒</td><td style="border: 1px solid #ccc; padding: 4px;">165.98秒</td><td style="border: 1px solid #ccc; padding: 4px;">159.95秒</td>
                    </tr>
              </tbody>
            </table>
          <p>複数のセグメント インデックスの場合、ベースラインがわずかに優れている cohere v2 を除き、ほぼすべてのデータセットで候補の方が優れています。単一セグメントインデックスの場合、リコール曲線はすべてのケースでほぼ同じです。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/7da1be27bab5a7f5f9c9a1882ea35761a4a0ba5c-973x383.png" style="width: 100%;" /><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/a26af30a56c55a12addee7e4fb7e8f606f366668-979x386.png" style="width: 100%;" /><h3>まとめ</h3><p>このブログで説明したアルゴリズムは、今後リリースされる Lucene 10.2 およびそれに基づく Elasticsearch リリースで利用できるようになります。ユーザーは、これらの新しいバージョンで改善されたマージ パフォーマンスと短縮されたインデックス構築時間を活用できるようになります。この変更は、Lucene と Elasticsearch をベクター検索とハイブリッド検索に高速かつ効率的にするための継続的な取り組みの一環です。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[MLを使用したフィルターとファセットの生成]]></title>
            <description><![CDATA[ML モデルを使用した検索エクスペリエンスにおけるフィルターとファセットの作成を自動化することと、従来のハードコードされたアプローチを比較して、長所と短所を検討します。]]></description>
            <link>https://www.elastic.co/search-labs/jp/blog/filters-facets-using-ml</link>
            <guid isPermaLink="true">https://www.elastic.co/search-labs/jp/blog/filters-facets-using-ml</guid>
            <category><![CDATA[elasticsearch-labs]]></category>
            <pubDate>Thu, 03 Apr 2025 00:00:00 GMT</pubDate>
            <enclosure url="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/6f196643d573614fe5124705c7e4db9bfce004b0-1200x628.png?fm=webp&amp;w=1440&amp;h=754&amp;q=90&amp;fit=crop" length="0" type="image/webp"/>
            <content:encoded><![CDATA[<a href="https://www.youtube.com/watch/zrD1Q_KKxUw">Watch video on YouTube</a><p>フィルターとファセットは、検索結果を絞り込むために使用されるメカニズムであり、ユーザーが関連するコンテンツや製品をより迅速に見つけるのに役立ちます。従来のアプローチでは、ルールは手動で定義されます。たとえば、映画カタログでは、フィルターやファセットで使用するためにジャンルなどの属性が事前定義されています。一方、AI モデルを使用すると、映画の特徴から新しい属性を自動的に抽出できるため、プロセスはより動的かつパーソナライズされたものになります。このブログでは、それぞれの方法の長所と短所を検討し、その応用と課題に焦点を当てます。</p><h2>フィルターとファセットの違い</h2><p>始める前に、フィルターとファセットとは何かを定義しましょう。<strong>フィルターは</strong>、結果セットを制限するために使用される定義済みの属性です。たとえば、マーケットプレイスでは、検索を実行する前でもフィルターを利用できます。ユーザーは、<strong> 「PS5」</strong> を検索する前に<strong> 「ビデオゲーム」</strong> などのカテゴリを選択し、データベース全体ではなく、より具体的なサブセットに検索を絞り込むことができます。これにより、より関連性の高い結果を得られる可能性が大幅に高まります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/5ed8868fa5017d034e1273e35c884a5430afdf3c-1600x937.png" style="width: 100%;" /><p><strong>ファセットは</strong>フィルターと同様に機能しますが、検索を実行した後にのみ使用できます。つまり、検索によって結果が返され、それに基づいて絞り込みオプションの新しいリストが生成されます。たとえば、PS5 コンソールを検索する場合、ストレージ<strong>容量</strong>、<strong>送料</strong>、<strong>色</strong>などの側面が表示され、ユーザーが理想的な製品を選択できるようになります。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/c5633fcc5b6fbb916110faf32144d8d43572e33a-1600x937.png" style="width: 100%;" /><p>フィルターとファセットを定義したので、次に、従来のアプローチと機械学習 (ML) ベースのアプローチがそれらの実装と使用に与える影響について説明しましょう。それぞれの方法には、検索効率に影響する利点と課題があります。</p><h2>フィルターとファセットに対する従来のアプローチ</h2><p>このアプローチでは、フィルターとファセットは事前定義されたルールに基づいて手動で定義されます。つまり、検索を絞り込むために使用できる属性は、カタログ構造とユーザーのニーズを考慮して事前に固定され、計画されています。</p><p>たとえば、マーケットプレイスでは、「エレクトロニクス」や「ファッション」などのカテゴリに、ブランド、フォーマット、価格帯などの特定のフィルターがある場合があります。これらのルールは静的に作成されるため、検索エクスペリエンスの一貫性は確保されますが、新しい製品やカテゴリが登場するたびに手動で調整する必要があります。</p><p>このアプローチでは、表示されるフィルターとファセットの予測可能性と制御が可能になりますが、動的な改良を必要とする新しいトレンドが生まれた場合には制限される可能性があります。</p><p><strong>長所:</strong></p><ul><li><strong>予測可能性と制御:</strong>フィルターとファセットは手動で定義されるため、管理が容易になります。</li><li><strong>複雑さが低い:</strong>モデルをトレーニングする必要はありません。</li><li><strong>メンテナンスの容易さ:</strong>ルールが事前に定義されているため、調整や修正を迅速に行うことができます。</li></ul><p><strong>短所</strong>:</p><ul><li><strong>新しいフィルターには再インデックスが必要です:</strong>新しい属性をフィルターとして使用する必要がある場合は、ドキュメントにこの情報が含まれていることを確認するために、データセット全体のインデックスを再作成する必要があります。</li><li><strong>動的適応の欠如:</strong>フィルターは静的であり、ユーザーの行動の変化に自動的に適応しません。</li></ul><h3>フィルター/ファセットの実装 – 古典的なアプローチ</h3><p><strong>開発ツールの Kibana</strong>では、<strong>従来のアプローチ</strong>を使用してフィルター/ファセットのデモを作成します。</p><p>まず、インデックスを構造化するためのマッピングを定義します。</p><pre><code>PUT videogames
{
  "mappings": {
    "properties": {
      "name": { "type": "text" },
      "brand": { "type": "keyword" },
      "storage": { "type": "keyword" },
      "price": { "type": "float" },
      "description": { "type": "text" }
    }
  }
}</code></pre><p><strong>ブランド</strong>および<strong>ストレージ</strong>フィールドは<strong>キーワード</strong>として設定されており、集計 (<strong>ファセット</strong>) で直接使用できます。<strong>価格</strong>フィールドは<strong>float</strong>型であり、<strong>価格範囲</strong>の作成を可能にします。</p><p>次のステップでは、製品データがインデックス化されます。</p><pre><code>POST videogames/_bulk
{ "index": { "_id": 1 } }
{ "name": "Play Station 5", "brand": "Sony", "storage": "1TB", "price": 499.99, "description": "Stunning Gaming: Marvel at stunning graphics and experience the features of the new PS5. Breathtaking Immersion: Discover a deeper gaming experience with support for haptic feedback, adaptive triggers, and 3D Audio technology. Slim Design: With the PS5 Digital Edition, gamers get powerful gaming technology in a sleek, compact design. 1TB of Storage: Have your favorite games ready and waiting for you to play with 1TB of built-in SSD storage. Backward Compatibility and Game Boost: The PS5 console can play over 4,000 PS4 games. With Game Boost, you can even enjoy faster, smoother frame rates in some of the best PS4 console games." }
{ "index": { "_id": 2 } }
{ "name": "Xbox Series X", "brand": "Microsoft", "storage": "1TB", "price": 499.99, "description": "Fastest, most powerful Xbox console ever. Play thousands of titles: Every game looks and plays better on Xbox Series X. At the heart of Series X is the Xbox Velocity. Architecture, which combines a custom SSD and built-in software to significantly reduce load times in and out of game. Switch between multiple games in an instant with Quick Resume. Explore new worlds and experience the action like never before with an unparalleled 12 teraflops of graphics processing power. Enjoy 4K gaming at up to 120 frames per second, premium advanced 3D sound, and more. 4K at 120 FPS: requires compatible content and display X version - with disc drive" }
{ "index": { "_id": 3 } }
{ "name": "Nintendo Switch", "brand": "Nintendo", "storage": "512GB", "price": 299.99, "description": "SHARPER, VIBRANT VISUALS. The new 7-inch screen on the Nintendo Switch OLED takes your gaming to the next level: vibrant colors with sharp contrasts for every moment. INTEGRATED GAMEPLAY. Enjoy the console's many multiplayer modes and connect with other players. Online or locally, the fun on the Nintendo Switch is guaranteed. ENJOY IMMERSION FOR LONGER. In addition to delivering an unparalleled experience, thanks to its improved audio, the Nintendo Switch has a rechargeable battery while you play. From 4.5 hours to 9 hours of battery life. INCLUDES SUPER MARIO BROS. WONDER. Transform your world with the phenomenal flowers in this new Mario game, full of amazing adventures, power-ups and new abilities. NINTENDO SWITCH ONLINE SUBSCRIPTION. Access online games, play with friends and enjoy the exclusive benefits of the Nintendo Switch Online subscription." }
{ "index": { "_id": 4 } }
{ "name": "Steam Deck", "brand": "Valve", "storage": "512GB", "price": 399.99, "description": "You can save games, apps, photos and videos without worrying about space. High-Level Performance: The 4-core processor and graphics ensure a dynamic experience and fast responses. High-Definition Images: Smooth transitions and sharp images provide complete immersion in the game. Wireless Connectivity: Wi-Fi technology allows you to play wherever you want, without wires or cables limiting your fun" }
{ "index": { "_id": 5 } }
{ "name": "Nintendo Switch Lite", "brand": "Nintendo", "storage": "512GB", "price": 299.99, "description": "MADE TO BE PORTABLE. Nintendo Switch Lite is designed specifically for portable gaming. The console lets you jump into your favorite games wherever you are. COMPACT AND LIGHTWEIGHT. With its sleek, lightweight design, this console is ready to hit the road wherever you are. COMPATIBLE GAMES. The Nintendo Switch Lite system plays the library of Nintendo Switch games that work in handheld mode. A WORLD OF COLOR TO CHOOSE FROM. Available in a variety of vibrant and unique colors, Nintendo Switch Lite lets you bring even more personality wherever you go." }</code></pre><p>ここで、ブランド、ストレージ、価格帯別に結果をグループ化して、クラシック ファセットを取得してみましょう。クエリでは、size:0 が定義されました。このシナリオでは、クエリに対応するドキュメントを含めずに、集計結果のみを取得することが目標です。</p><pre><code>POST videogames/_search
{
  "size": 0,
  "aggs": {
    "brands": {
      "terms": { "field": "brand" }
    },
    "storage_sizes": {
      "terms": { "field": "storage" }
    },
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 300 },   
          { "from": 300, "to": 500 },  
          { "from": 500 }  
        ]
      }
    }
  }
}</code></pre><p>応答には、 <strong>Brand</strong> 、 <strong>Storage</strong> 、 <strong>Price</strong>のカウントが含まれ、フィルターとファセットの作成に役立ちます。</p><pre><code>"aggregations": {
   "brands": {
     "doc_count_error_upper_bound": 0,
     "sum_other_doc_count": 0,
     "buckets": [
       {
         "key": "Microsoft",
         "doc_count": 1
       },
       {
         "key": "Nintendo",
         "doc_count": 1
       },
       {
         "key": "Sony",
         "doc_count": 1
       },
       {
         "key": "Valve",
         "doc_count": 1
       }
     ]
   },
   "storage_sizes": {
     "doc_count_error_upper_bound": 0,
     "sum_other_doc_count": 0,
     "buckets": [
       {
         "key": "1TB",
         "doc_count": 2
       },
       {
         "key": "512GB",
         "doc_count": 2
       }
     ]
   },
   "price_ranges": {
     "buckets": [
       {
         "key": "*-300.0",
         "to": 300,
         "doc_count": 1
       },
       {
         "key": "300.0-500.0",
         "from": 300,
         "to": 500,
         "doc_count": 3
       },
       {
         "key": "500.0-*",
         "from": 500,
         "doc_count": 0
       }
     ]
   }
 }</code></pre><h2>フィルターとファセットに対する機械学習/AIベースのアプローチ</h2><p>このアプローチでは、人工知能 (AI) 技術を含む機械学習 (ML) モデルがデータ属性を分析して、関連するフィルターとファセットを生成します。ML/AI は、事前定義されたルールに依存するのではなく、インデックス化されたデータ特性を活用します。これにより、新しいファセットとフィルターを動的に検出できるようになります。</p><p><strong>長所</strong>:</p><ul><li><strong>自動更新:</strong>新しいフィルターとファセットは自動的に生成され、手動で調整する必要はありません。</li><li><strong>新しい属性の検出:これまで考慮されていなかった</strong>データ特性をフィルターとして識別し、検索エクスペリエンスを充実させることができます。</li><li><strong>手作業の削減:</strong> AI が利用可能なデータから学習するため、チームはフィルタリング ルールを継続的に定義および更新する必要がありません。</li></ul><p><strong>短所:</strong></p><ul><li><strong>メンテナンスの複雑さ:</strong>モデルを使用する場合、生成されたフィルターの一貫性を確保するために事前検証が必要になる場合があります。</li><li><strong>ML と AI の専門知識が必要:</strong>このソリューションでは、モデルのパフォーマンスを微調整および監視する資格のある専門家が必要です。</li><li><strong>無関係なフィルターのリスク:</strong>モデルが適切に調整されていない場合、ユーザーにとって役に立たないファセットが生成される場合があります。</li><li><strong>コスト:</strong> ML および AI の使用にはサードパーティのサービスが必要になる場合があり、運用コストが増加する可能性があります。</li></ul><p>適切に調整されたモデルと適切に作成されたプロンプトを使用しても、生成されたファセットはレビュー手順を経る必要があることに注意してください。この検証は手動で行うことも、モデレーション ルールに基づいて行うこともできます。これにより、コンテンツが適切かつ安全であることが保証されます。必ずしも欠点ではありませんが、ファセットをユーザーに提供する前に、その品質と適合性を確認することは重要な考慮事項です。</p><h3>フィルター/ファセットの実装 - AIアプローチ</h3><p>このデモでは、AI モデルを使用して製品の特性を自動的に分析し、関連する属性を提案します。適切に構造化されたプロンプトを使用して、カタログから情報を抽出し、それをフィルターとファセットに変換します。以下に、プロセスの各ステップを紹介します。</p><p>最初に、 <strong>Inference API を</strong>使用して、ML サービスとの統合のためのエンドポイントを登録します。以下は<strong>OpenAI のサービス</strong>との統合の例です。</p><pre><code>PUT _inference/completion/generate_filter_ia
{
   "service": "openai",
   "service_settings": {
       "api_key": "your-key",
       "model_id": "gpt-4o-mini"
   }
}</code></pre><p>ここで、プロンプトを実行し、モデルによって生成された新しいフィルターを取得するためのパイプラインを定義します。</p><pre><code>PUT /_ingest/pipeline/generate_filter_ai
{
   "processors": [
     {
       "script": {
         "source": """ctx.prompt = "You are an expert in data organization for search and product categorization. Your task is to analyze the following product and identify the best dynamic facets that can be used in an e-commerce search experience. Product: " + ctx.name + "description: " + ctx.description + "Instructions: - Analyze the product name and description. - Extract only the dynamic facets (technological features or product characteristics that can be inferred from the description, try to create max 3 facets by characteristics found). Put the values into an array. Using key and value, e.g. dynamic_facets: [{ \"name\": \"Gaming Experience\", \"value\": \"Haptic Feedback\" },{ \"name\": \"Gaming Experience\", \"value\": \"Adaptive Triggers\" } - Return only a JSON."
         """
       }
     },
     {
       "inference": {
         "model_id": "generate_filter_ia",
         "input_output": {
           "input_field": "prompt",
           "output_field": "result"
         }
       }
     },
     {
       "gsub": {
         "field": "result",
         "pattern": "```json",
         "replacement": ""
       }
     },
     {
       "json" : {
         "field" : "result",
         "strict_json_parsing": false,
         "add_to_root" : true
       }
     },
     {
       "remove": {
         "field": "result"
       }
     },
     {
       "remove": {
         "field": "prompt"
       }
     }
   ]
}</code></pre><p>「PlayStation 5」製品用にこのパイプラインのシミュレーションを実行すると、次のようになります。</p><p><em>驚異的なゲーム体験: 驚異的なグラフィックスに驚嘆し、新しい PS5 の機能を体験してください。</em></p><p><em>息を呑むような没入感: 触覚フィードバック、アダプティブ トリガー、3D オーディオ テクノロジーのサポートにより、より奥深いゲーム体験を体験できます。</em></p><p><em>スリムなデザイン: PS5 デジタル エディションでは、洗練されたコンパクトなデザインで強力なゲーミング テクノロジーをゲーマーに提供します。</em></p><p><em>1TB のストレージ: 1TB の内蔵 SSD ストレージで、お気に入りのゲームをいつでもプレイできます。</em></p><p><em>下位互換性とゲームブースト: PS5 コンソールは 4,000 以上の PS4 ゲームをプレイできます。Game Boost を使用すると、最高の PS4 コンソール ゲームの一部で、より高速でスムーズなフレーム レートを楽しむことができます。</em></p><p>このシミュレーションから生成されたプロンプト出力を観察してみましょう。</p><pre><code>{
 "docs": [
   {
     "doc": {
       "_index": "index",
       "_version": "-3",
       "_id": "1",
       "_source": {
         "name": "Play Station 5",
         "result": """```json
{
 "dynamic_facets": [
   { "name": "Storage Capacity", "value": "1TB SSD" },
   { "name": "Graphics Technology", "value": "Stunning Graphics" },
   { "name": "Audio Technology", "value": "3D Audio" }
 ]
}
```""",
         "description": "Stunning Gaming: Marvel at stunning graphics and experience the features of the new PS5. Breathtaking Immersion: Discover a deeper gaming experience with support for haptic feedback, adaptive triggers, and 3D Audio technology. Slim Design: With the PS5 Digital Edition, gamers get powerful gaming technology in a sleek, compact design. 1TB of Storage: Have your favorite games ready and waiting for you to play with 1TB of built-in SSD storage. Backward Compatibility and Game Boost: The PS5 console can play over 4,000 PS4 games. With Game Boost, you can even enjoy faster, smoother frame rates in some of the best PS4 console games.",
         "model_id": "generate_filter_ia",
         "prompt": """You are an expert in data organization for search and product categorization. Your task is to analyze the following product and identify the best dynamic facets that can be used in an e-commerce search experience. Product: Play Station 5description: Stunning Gaming: Marvel at stunning graphics and experience the features of the new PS5. Breathtaking Immersion: Discover a deeper gaming experience with support for haptic feedback, adaptive triggers, and 3D Audio technology. Slim Design: With the PS5 Digital Edition, gamers get powerful gaming technology in a sleek, compact design. 1TB of Storage: Have your favorite games ready and waiting for you to play with 1TB of built-in SSD storage. Backward Compatibility and Game Boost: The PS5 console can play over 4,000 PS4 games. With Game Boost, you can even enjoy faster, smoother frame rates in some of the best PS4 console games.Instructions: - Analyze the product name and description. - Extract only the dynamic facets (technological features or product characteristics that can be inferred from the description, try create max 3 facets by characteristics found). Put the values like arrays. Using key and value, e.g. dynamic_facets: [{ "name": "Gaming Experience", "value": "Haptic Feedback" },{ "name": "Gaming Experience", "value": "Adaptive Triggers" } - Return only a JSON."""
       },
       "_ingest": {
         "timestamp": "2025-03-19T22:14:32.0161803Z"
       }
     }
   }
 ]
}</code></pre><p>これで、AI によって生成されたファセットを保存するための新しいフィールド<strong>dynamic_facets</strong>が新しいインデックスに追加されます。</p><pre><code>PUT videogames_1
{
 "mappings": {
   "properties": {
     "name": { "type": "text" },
     "brand": { "type": "keyword" },
     "storage": { "type": "keyword" },
     "price": { "type": "float" },
     "description": { "type": "text" },
     "dynamic_facets": { "type": "nested",
     "properties": { "name": { "type": "keyword" },
                     "value": { "type": "keyword" } } }
   }
 }
}</code></pre><p><strong>Reindex API</strong> を使用して、プロセス中に<strong> generate_filter_ai</strong> パイプラインを適用し、<strong> videogames</strong> インデックスを<strong> videogames_1</strong> に再インデックスします。このパイプラインは、インデックス作成中に動的ファセットを自動的に生成します。</p><pre><code>POST _reindex?wait_for_completion=false
{
 "source": {
   "index": "videogames"
 },
 "dest": {
   "index": "videogames_1",
   "pipeline": "generate_filter_ai"
 }
}</code></pre><p>ここで、検索を実行して新しいフィルターを取得します。</p><pre><code>GET videogames_1/_search
{
 "size": 0,
 "query": {
   "match": {
     "name": "nintendo"
   }
 },
 "aggs": {
   "dynamic_facets": {
     "nested": {
       "path": "dynamic_facets"
     },
     "aggs": {
       "facets": {
         "terms": {
           "field": "dynamic_facets.name"
         },
         "aggs": {
           "facets": {
             "terms": {
               "field": "dynamic_facets.value"
             }
           }
         }
       }
     }
   }
 }
}</code></pre><p>結果：</p><pre><code>"aggregations": {
   "dynamic_facets": {
     "doc_count": 3,
     "facets": {
       "doc_count_error_upper_bound": 0,
       "sum_other_doc_count": 0,
       "buckets": [
         {
           "key": "Frame Rate",
           "doc_count": 1,
           "facets": {
             "doc_count_error_upper_bound": 0,
             "sum_other_doc_count": 0,
             "buckets": [
               {
                 "key": "120 FPS",
                 "doc_count": 1
               }
             ]
           }
         },
         {
           "key": "Gaming Resolution",
           "doc_count": 1,
           "facets": {
             "doc_count_error_upper_bound": 0,
             "sum_other_doc_count": 0,
             "buckets": [
               {
                 "key": "4K",
                 "doc_count": 1
               }
             ]
           }
         },
         {
           "key": "Graphics Processing Power",
           "doc_count": 1,
           "facets": {
             "doc_count_error_upper_bound": 0,
             "sum_other_doc_count": 0,
             "buckets": [
               {
                 "key": "12 Teraflops",
                 "doc_count": 1
               }
             ]
           }
         }
       ]
     }
   }
 }</code></pre><p>ファセットの実装を象徴するシンプルなフロントエンドを以下に示します。</p><img src="https://cdn.sanity.io/images/me0ej585/search-labs-import-testing/12b6d9d4f4d0985848d92841545fd22b7253ae6d-1600x1288.png" style="width: 100%;" /><p>提示された UI コードは<a href="https://gist.github.com/andreluiz1987/06d9ec1b381e942e9def0e969bd811a0">ここに</a>あります。</p><h2>まとめ</h2><p>フィルターとファセットを作成する両方のアプローチには、利点と注意点があります。手動ルールに基づく従来のアプローチでは、制御が可能になりコストも削減されますが、継続的な更新が必要となり、新しい製品や機能に動的に適応することができません。</p><p>一方、AI と機械学習ベースのアプローチでは、ファセット抽出が自動化されるため、検索がより柔軟になり、手動による介入なしに新しい属性を発見できるようになります。ただし、このアプローチは実装と維持が複雑になる可能性があり、一貫した結果を確保するために調整が必要になります。</p><p>従来のアプローチと AI ベースのアプローチのどちらを選択するかは、ビジネスのニーズと複雑さによって異なります。データ属性が安定していて予測可能な、より単純なシナリオでは、従来のアプローチの方が効率的で保守が容易になり、インフラストラクチャと AI モデルによる不必要なコストを回避できます。一方、ML/AI を使用してファセットを抽出すると、検索エクスペリエンスが向上し、フィルタリングがよりインテリジェントになるため、大きな価値が付加されます。</p><p>重要なのは、自動化が投資を正当化するかどうか、あるいはより従来型のソリューションがすでにビジネス ニーズを効果的に満たしているかどうかを評価することです。</p>]]></content:encoded>
        </item>
    </channel>
</rss>