<?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[JD Armada - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[JD Armada - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/jp/search-labs/author/jd-armada</link>
    </image>
    <link>https://www.elastic.co/jp/search-labs/author/jd-armada</link>
    <atom:link href="https://www.elastic.co/jp/search-labs/rss/author/jd-armada.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[jp]]></language>
    <lastBuildDate>Mon, 21 Sep 2026 19:46:55 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Cal Hacks 12.0 で取り上げた Elastic Agent Builder のトッププロジェクトと学習内容]]></title>
    <description><![CDATA[Cal Hacks 12.0 のトップ Elastic Agent Builder プロジェクトを探索し、サーバーレス、ES|QL、エージェント アーキテクチャに関する技術的なポイントを詳しく調べます。]]></description>
    <content:encoded><![CDATA[<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><p>問題</p></li><li><p>コンテクスト</p></li><li><p>コード</p></li><li><p>タグ</p></li><li><p>検証済みの解決手順。</p></li></ul><p>バリデーター (LAVA) が構造をチェックして強制し、ユーザーが追加のコンテキストの行を追加すると、Elasticsearch 内に保存されてインデックスが作成されます。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte7bc35b6d54921e8/6a17f0176df73162760a0fe6/45a3e96f4474050a855419628c2a7338bb12c706-1600x877.png" alt="「ソリューションを共有」をクリックすると、現在のセッションと関連するメタデータがスクレイピングされます。" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9967f52007fff99e/6a17f019ec0f8987c45a6701/2d65cb154d8ee32fc96ff17dfa5b0bf2636e3777-1600x1002.png" alt="ユーザーはWebフロントエンドを通じて追加のコンテキストを提供し、JSONはElasticsearchでインデックス化されます。" /><p><strong>2. 解決策を見つける</strong></p><p>行き詰まったら、 <code>Find Solution</code>をクリックすると、AgentOverflow が現在の会話をスクレイピングし、それを使用してクエリを作成し、ハイブリッド Elasticsearch 検索を実行して次の内容を表示します。</p><ul><li><p>ランク付けされたコミュニティ検証済みの修正</p></li><li><p>当初問題を解決した正確なプロンプト</p></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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4ba582f9872b65b/6a17f01b7f6f15c2d8c09c1c/7d9716cca06a047a2b3584378b5c7e592a785ba1-1284x878.png" alt="市場を分析する6つの専門的GOOGL AIエージェント" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd86ed3bfe4b8bd2b/6a17f01c5ea30f868164b6ba/5aac6a833347c0d2e596c02049ec4b4d3aae5cd7-794x764.png" alt="GOOGL特化エージェントのボリューム異常と大惨事検出分析機能" /><p><strong>3. シグナルを統合した「市場エネルギー」モデルに集約する</strong></p><p>組み合わせた出力は各株の周囲に光るパルスとして表示され、勢いが高まっているのか、リスクが高まっているのか、感情が変化しているのかを示します。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5af7c7c838308275/6a17f01e42022917b629f6ca/46b3da8e3d528c5dd4e2829416c5446098acb3aa-744x718.png" alt="GOOGL特化エージェントの統一「市場エネルギー」モデル" /><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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt775e1880aa7afacc/6a17f01f1d1b83ce1f93e528/3f000c043117b77ed4127202be5a49c12e3682ba-1600x930.png" alt="GOOGL特化エージェント分析の洞察を視覚化する方法" /><h2>その他の興味深いプロジェクト:</h2><p>スタックのさまざまな部分で Elastic を使用した他の有力な候補を次に示します。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltffe292009e446a70/6a17f0216df731068c0a0fea/76c49a853426844f475cd6b2a74999e60af20e8c-926x1080.png" alt="" /><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><p><strong>Agent Builder はユーザーフレンドリーです:</strong></p></li></ul><p>ほとんどのチームはこれまで Elastic を使用したことがありませんでしたが、それでもほとんどサポートなしでエージェントを迅速に構築できました。さらに詳しい指導が必要な人向けにワークショップを開催しましたが、ほとんどの人はデータを取り込み、そのデータに基づいてアクションを実行するエージェントを構築することができました。</p><ul><li><p>LLM は<strong><code>kNN</code></strong>クエリ<strong>に優れています</strong><strong>が、ES|QL の生成には依然としてガイダンスが必要です。</strong></p></li></ul><p>ChatGPT-5 に ES|QL クエリの生成を依頼すると、ES|QL と SQL が混在するなど、誤った情報が返されることがよくありました。LLM にマークダウン ファイルでドキュメントを供給することは、実行可能な修正であるように思われました。</p><ul><li><p><strong>スナップショット専用の ES|QL 関数がドキュメントに漏洩しました:</strong></p></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><p><strong>サーバーレス固有のガイダンスが不足しています:</strong></p></li></ul><p>チームは、ルックアップ モードで作成されなかったインデックスで<code>LOOKUP JOIN</code>有効にしようとしました。エラー メッセージにより、Serverless に存在しないコマンドが追跡されました。私たちはこれを製品チームに伝え、製品チームはすぐに Serverless 固有の実用的なメッセージの修正を開始しました。長期的には、再インデックスの複雑さを完全に隠すことがビジョンです (<a href="https://github.com/elastic/elasticsearch-serverless/issues/4838">問題 #4838</a> )。</p><ul><li><p><strong>対面イベントの価値:</strong></p></li></ul><p>オンライン ハッカソンは素晴らしいですが、ビルダーと肩を並べてデバッグしているときに得られる迅速なフィードバック ループに匹敵するものはありません。私たちは、チームがさまざまなユースケースにわたって Agent Builder を統合する様子を観察し、ES|QL を使用した開発者エクスペリエンスを改善できる部分を見つけ、非同期チャネルで解決するよりもはるかに迅速に問題を修正しました。</p><h2>まとめ</h2><p>Cal Hacks 12.0 では、素晴らしいデモを週末にわたって披露するだけでなく、新しい開発者が Elastic Stack とどのように関わっているかについても理解することができました。わずか 36 時間で、チームは Agent Builder を導入し、Elasticsearch にデータを取り込み、マルチエージェント システムを設計し、さまざまな方法で機能をテストするようになりました。このイベントは、対面イベントがなぜ重要なのかを私たちに思い出させてくれました。迅速なフィードバック ループ、実際の会話、実践的なデバッグにより、現在の開発者のニーズを理解することができました。私たちが学んだことをエンジニアリング チームに還元できることを嬉しく思います。次回のハッカソンでお会いしましょう。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/agent-builder-projects-learnings-cal-hacks-12-0</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/agent-builder-projects-learnings-cal-hacks-12-0</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[エージェント型AI]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0f079179be9832d4/6a17f023631730a69c585b6d/8ba034a6f19b50521f541b8131756a8acdb52975-1280x960.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 25 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[MastraとElasticsearchを使用してセマンティックリコールを備えた知識エージェントを構築する]]></title>
    <description><![CDATA[メモリと情報検索用のベクトル ストアとして Mastra と Elasticsearch を使用して、セマンティック リコールを備えたナレッジ エージェントを構築する方法を学びます。]]></description>
    <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><p>ノード v18+</p></li><li><p>Elasticsearch（バージョン8.15以降）</p></li><li><p>Elasticsearch APIキー</p></li><li><p><a href="https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key">OpenAI APIキー</a></p></li></ul><p>注: デモでは OpenAI プロバイダーを使用するため、これが必要になりますが、Mastra は他の AI SDK とコミュニティ モデル プロバイダーをサポートしているため、設定に応じて簡単に交換できます。</p><h2>Mastraプロジェクトの構築</h2><p>プロジェクトの足場を提供するために、Mastra の組み込み CLI を使用します。次のコマンドを実行します。</p>npm create mastra@latest<p>次のような一連のプロンプトが表示されます。</p><p>1. プロジェクトに名前を付けます。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt87f941f654d03827/6a16f7af67045b214d45bfa1/2b9fe559e0276140dd539e24f916a73c60870405-620x84.png" alt="Mastraアプリでプロンプトに名前を付ける" /><p>2. このデフォルト設定を維持することもできますし、空白のままにしておくこともできます。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7dbb3d4f27435cac/6a16f7b0cdacbf29497d27de/e04729eb03bce8499e973e18c28642402340d0e5-852x68.png" alt="プロンプトファイルを保存する場所をmastraに伝える" /><p>3. このプロジェクトでは、OpenAI が提供するモデルを使用します。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1f654f6cb9397e94/6a16f7b2964cea899a08b942/a86596a469a71bdf8bd99cbaf528d0f0cf7272c0-436x222.png" alt="MastraでOpenAIが提供するモデルを選択する" /><p>4. すべての環境変数を、後のステップで設定する `.env` ファイルに保存するため、「今はスキップ」オプションを選択します。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltff106117521a3519/6a16f7b3c1e8a5031af880d8/02b19ccc34af0bdacf52fd94b519d036540ca2e6-426x114.png" alt="OpenAIキーについては今はスキップを選択" /><p>5. このオプションをスキップすることもできます。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcda7d9c51c3878d7/6a16f7b450916809dbe1b892/b3fe63d19d270bc2e0de1dd92033bf8b26750819-990x208.png" alt="" /><p>初期化が完了したら、次のステップに進むことができます。</p><h3>依存関係のインストール</h3><p>次に、いくつかの依存関係をインストールする必要があります。</p>npm install ai @ai-sdk/openai @elastic/elasticsearch dotenv<ul><li><p><code>ai</code> - JavaScript/TypeScript で AI モデル、プロンプト、ワークフローを管理するためのツールを提供するコア AI SDK パッケージ。Mastra は Vercel の<a href="https://ai-sdk.dev/">AI SDK</a>上に構築されているため、エージェントとのモデルのインタラクションを有効にするにはこの依存関係が必要です。</p></li><li><p><code>@ai-sdk/openai</code> - AI SDK を OpenAI モデル (GPT-4、GPT-4o など) に接続し、OpenAI API キーを使用した API 呼び出しを可能にするプラグイン。</p></li><li><p><code>@elastic/elasticsearch</code> - <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/javascript">Node.js 用の公式 Elasticsearch クライアント</a>、インデックス作成、検索、ベクター操作のために Elastic Cloud またはローカル クラスターに接続するために使用されます。</p></li><li><p><code>dotenv</code> - .envから環境変数を読み込みますファイルをprocess.envにコピーし、API キーや Elasticsearch エンドポイントなどの資格情報を安全に挿入できるようになります。</p></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>ELASTICSEARCH_ENDPOINT="your-endpoint-here"
ELASTICSEARCH_API_KEY="your-key-here"
OPENAI_API_KEY="your-key-here"<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>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;
    }
}<ul><li><p><code>ElasticVectorConfig extends ClientOptions</code>: これにより、すべての Elasticsearch クライアント オプション ( <code>node</code> 、 <code>auth</code> 、 <code>requestTimeout</code>など) を継承し、カスタム プロパティを追加する新しい構成インターフェースが作成されます。つまり、ユーザーは、サーバーレス固有のオプションとともに、有効な Elasticsearch 構成を渡すことができるということです。</p></li><li><p><code>extends MastraVector</code>: これにより、 <code>ElasticVector</code> 、すべてのベクター ストア統合が準拠する共通インターフェースである Mastra の基本<code>MastraVector</code>クラスから継承できるようになります。これにより、エージェントの観点から見ると、Elasticsearch は他の Mastra ベクター バックエンドと同じように動作するようになります。</p></li><li><p><code>private client: Client</code>: これは、Elasticsearch JavaScript クライアントのインスタンスを保持するプライベート プロパティです。これにより、クラスはクラスターと直接通信できるようになります。</p></li><li><p><code>isServerless</code> および<code>deploymentChecked</code> : これらのプロパティは連携して、サーバーレスまたは標準の Elasticsearch デプロイメントに接続されているかどうかを検出し、キャッシュします。この検出は最初の使用時に自動的に行われますが、明示的に構成することもできます。</p></li><li><p><code>constructor(config: ClientOptions)</code>: このコンストラクターは、構成オブジェクト (Elasticsearch の資格情報とオプションのサーバーレス設定を含む) を受け取り、それを使用して<code>this.client = new Client(config)</code>行でクライアントを初期化します。</p></li><li><p><code>super()</code>: これは Mastra の基本コンストラクターを呼び出すため、ログ記録、検証ヘルパー、およびその他の内部フックを継承します。</p></li></ul><p>この時点で、Mastraは新しいベクターストアがあることを知っています。 <code>ElasticVector</code></p><h3>展開タイプの検出</h3><p>インデックスを作成する前に、アダプターは標準の Elasticsearch を使用しているか Elasticsearch Serverless を使用しているかを自動的に検出します。サーバーレス デプロイメントでは手動でのシャード構成が許可されないため、これは重要です。</p>private async detectServerless(): Promise&lt;boolean&gt; {
    // 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;
    }
}<p>何が起こっていますか：</p><ul><li><p>まず、構成で明示的に<code>isServerless</code>が設定されているかどうかを確認します (自動検出をスキップします)</p></li><li><p>Elasticsearch の<code>info()</code> API を呼び出してクラスター情報を取得します</p></li><li><p><code>build_flavor field</code>をチェックします (サーバーレス デプロイメントは<code>serverless</code>を返します)</p></li><li><p>ビルドフレーバーが利用できない場合はタグラインをチェックする</p></li><li><p>結果をキャッシュして、API 呼び出しの繰り返しを回避します。</p></li><li><p>検出に失敗した場合は標準展開をデフォルトとする</p></li></ul><p> 使用例:</p>// 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
});<h3>Elasticsearchに「メモリ」ストアを作成する</h3><p>以下の関数は、埋め込みを保存するための Elasticsearch インデックスを設定します。インデックスがすでに存在するかどうかを確認します。そうでない場合は、埋め込みとカスタム類似度メトリックを格納するための<code>dense_vector</code>フィールドを含む以下のマッピングを使用して作成します。</p><p>注意すべき点:</p><ul><li><p><code>dimension</code>パラメータは各埋め込みベクトルの長さであり、使用している埋め込みモデルによって異なります。私たちの場合、サイズ<code>1536</code>のベクトルを出力する OpenAI の<code>text-embedding-3-small</code>モデルを使用して埋め込みを生成します。これをデフォルト値として使用します。</p></li><li><p>以下のマッピングで使用される<code>similarity</code>変数は、ヘルパー関数 c <code>onst similarity = this.mapMetricToSimilarity(metric)</code>から定義されます。この関数は、 <code>metric</code>パラメータの値を受け取り、選択された距離メトリックの Elasticsearch 互換キーワードに変換します。</p><ul><li><p>たとえば、Mastra では、ベクトルの類似性を表すために<code>cosine</code> 、 <code>euclidean</code> 、 <code>dotproduct</code>などの一般的な用語を使用します。メトリック<code>euclidean</code> Elasticsearch マッピングに直接渡すと、Elasticsearch はキーワード<code>l2_norm</code>がユークリッド距離を表すと想定するため、エラーが発生します。</p></li></ul></li><li><p>サーバーレス互換性: サーバーレスデプロイメントのシャードとレプリカの設定は Elasticsearch Serverless によって自動的に管理されるため、コードでは自動的に省略されます。</p></li></ul>async createIndex(params: CreateIndexParams): Promise&lt;void&gt; {
    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}`);
    }
}<h3>やりとりの後に新しい記憶やメモを保存する</h3><p>この関数は、各インタラクションの後に生成された新しい埋め込みをメタデータとともに取得し、Elastic の<code>bulk</code> API を使用してそれらをインデックスに挿入または更新します。<code>bulk</code> API は複数の書き込み操作を 1 つのリクエストにグループ化します。このインデックス作成パフォーマンスの向上により、エージェントのメモリが増加し続けても更新の効率が維持されます。</p>async upsert(params: UpsertVectorParams): Promise&lt;string[]&gt; {
    const { indexName, vectors, metadata = [], ids } = params;

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

        const operations = vectors.flatMap((vec, index) =&gt; [
            { 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) =&gt; item.index?.error);
            const erroredIds = erroredItems.map((item: any) =&gt; item.index?._id);
            const errorDetails = erroredItems.slice(0, 3).map((item: any) =&gt; ({
                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) =&gt; 
                !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}`);
    }
}<h3>意味的想起のための類似ベクトルのクエリ</h3><p>この機能は、セマンティック リコール機能の中核です。エージェントはベクトル検索を使用して、インデックス内に保存されている類似の埋め込みを見つけます。</p>async query(params: QueryVectorParams&lt;any&gt;): Promise&lt;QueryResult[]&gt; {
    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) =&gt; ({
            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}`);
    }
}<p>内部構造:</p><ul><li><p>Elasticsearch の<code>knn</code> API を使用して<a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN</a> (k 近傍法) クエリを実行します。</p></li><li><p>入力クエリ ベクトルに類似する上位 K 個のベクトルを取得します。</p></li><li><p>オプションでメタデータ フィルターを適用して結果を絞り込む (例: 特定のカテゴリまたは時間範囲内のみを検索する)</p></li><li><p>ドキュメント ID、類似度スコア、保存されたメタデータを含む構造化された結果を返します。</p></li></ul><h2>知識エージェントの作成</h2><p><code>ElasticVector</code>統合を通じて Mastra と Elasticsearch の接続を確認したので、次は Knowledge Agent 自体を作成しましょう。</p><p>フォルダー<code>agents</code>内に、 <code>knowledge-agent.ts</code>というファイルを作成します。まず、環境変数を接続し、Elasticsearch クライアントを初期化します。</p>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,
});<p>ここでは、次の操作を行います。</p><ul><li><p><code>dotenv</code>を使用して、 <code>.env</code>ファイルから変数を読み込みます。</p></li><li><p>Elasticsearch の資格情報が正しく挿入されているかどうかを確認し、クライアントへの接続を正常に確立できるかどうかを確認します。</p></li><li><p>Elasticsearch エンドポイントと API キーを<code>ElasticVector</code>コンストラクターに渡して、先ほど定義したベクター ストアのインスタンスを作成します。</p></li><li><p>Elasticsearch Serverless を使用している場合は、オプションで<code>isServerless: true</code>を指定します。これにより、自動検出手順がスキップされ、起動時間が短縮されます。省略した場合、アダプタは最初の使用時にデプロイメント タイプを自動的に検出します。</p></li></ul><p>次に、Mastra の<code>Agent</code>クラスを使用してエージェントを定義します。</p>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',
            },
        },
    }),
});<p>定義できるフィールドは次のとおりです。</p><ul><li><p><code>name</code> そして<code>instructions</code> : アイデンティティと主な機能を与えます。</p></li><li><p><code>model</code>: <code>@ai-sdk/openai</code>パッケージを通じて OpenAI の<code>gpt-4o</code>を使用しています。</p></li><li><p><code>memory</code>:</p><ul><li><p><code>vector</code>: Elasticsearch ストアを指すので、埋め込みはそこから保存され、取得されます。</p></li><li><p><code>embedder</code>: 埋め込みを生成するためにどのモデルを使用するか</p></li><li><p><code>semanticRecall</code> オプションによってリコールの動作が決まります。</p><ul><li><p><code>topK</code>: 意味的に類似したメッセージを取得する数。</p></li><li><p><code>messageRange</code>: 各マッチにどの程度の会話を含めるか。</p></li><li><p><code>scope</code>: メモリの境界を定義します。</p></li></ul></li></ul></li></ul><p>もうすぐ終わりです。新しく作成したエージェントを Mastra 構成に追加するだけです。<a href="http://index.ts/"><code>index.ts</code></a>というファイルで、ナレッジ エージェントをインポートし、 <code>agents</code>フィールドに挿入します。</p>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 }, 
  },
});<p>その他のフィールドには以下が含まれます。</p><ul><li><p><code>storage</code>: これは、実行履歴、観測性メトリック、スコア、キャッシュのための Mastra の内部データ ストアです。Mastra ストレージの詳細については、<a href="https://mastra.ai/docs/server-db/storage">こちらを</a>ご覧ください。</p></li><li><p><code>logger</code>: Mastra は、軽量の構造化 JSON ロガーである<a href="https://github.com/pinojs/pino">Pino</a>を使用します。エージェントの開始と停止、ツールの呼び出しと結果、エラー、LLM 応答時間などのイベントをキャプチャします。</p></li><li><p><code>observability</code>: エージェントの AI トレースおよび実行の可視性を制御します。追跡対象:</p><ul><li><p>各推論ステップの開始/終了。</p></li><li><p>使用されたモデルまたはツール。</p></li><li><p>入力と出力。</p></li><li><p>スコアと評価</p></li></ul></li></ul><h3>Mastra Studioでエージェントをテストする</h3><p>おめでとうございます！ここまで到達したら、このエージェントを実行し、そのセマンティックリコール能力をテストする準備が整いました。幸いなことに、Mastra には組み込みのチャット UI が用意されているため、独自に構築する必要はありません。</p><p>Mastra 開発サーバーを起動するには、ターミナルを開いて次のコマンドを実行します。</p>npm run dev<p>サーバーの初期バンドルと起動が完了すると、Playground のアドレスが提供されるはずです。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5f857fddc74ffc9/6a16f7b6a6c2b995d5e794c0/8b045f70008d26aec4d2e6b59d61085555b9c5b2-686x116.png" alt="プレイグラウンドのサーバーアドレス" /><p>このアドレスをブラウザに貼り付けると、Mastra Studio が表示されます。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc7fdda6ce46ce068/6a16f7b7b0367d4f7672bacf/69bc80fe8486edd9e0cf91d87b39f465aeb23111-1600x438.png" alt="プレイグラウンドのアドレスを貼り付けてMastra Studioにアクセスする" /><p><code>knowledgeAgent</code>のオプションを選択してチャットを開始してください。</p><p>すべてが正しく接続されているかどうかを確認するための簡単なテストでは、次のような情報を入力します。「チームは、主にエンタープライズ契約の更新により、10 月の売上実績が 12% 増加したと発表しました。次のステップは、中規模市場の顧客へのリーチを拡大することです。」次に、新しいチャットを開始して、「次に重点を置く必要があると言った顧客セグメントはどれですか?」などの質問をします。ナレッジエージェントは、最初のチャットで提供した情報を思い出せるはずです。次のような応答が表示されます。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfec3266e81a7213b/6a16f7b92b835f6f70f4afe2/da8ebddad89874023ed440a8f1ad2cb04ed043f4-1070x288.png" alt="Mastra Studioでナレッジエージェントとチャット - エージェントは情報を思い出すことができます" /><p>このような応答が表示された場合、エージェントが以前のメッセージを Elasticsearch に埋め込みとして正常に保存し、後でベクトル検索を使用して取得したことを意味します。</p><h3>エージェントの長期記憶ストアの検査</h3><p>Mastra Studio のエージェント構成の<code>memory</code>タブに移動します。これにより、エージェントが時間の経過とともに何を学習したかを確認できます。Elasticsearch に埋め込まれて保存されるすべてのメッセージ、応答、およびやり取りは、この長期メモリの一部になります。過去のやり取りを意味的に検索して、エージェントが以前に学習した思い出の情報やコンテキストをすぐに見つけることができます。これは本質的には、エージェントがセマンティックリコール中に使用するメカニズムと同じものですが、ここではそれを直接検査できます。以下の例では、「sales」という用語を検索し、sales に関連する内容を含むすべてのインタラクションを取得しています。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte428134d7bf2a43a/6a16f7bbb0367d185872bad3/3decaa0c332d288c5ae0b11c25f592c7d50c2f0f-1104x1320.png" alt="知識エージェントの長期記憶ストアを検査する方法" /><h2>まとめ</h2><p>Mastra と Elasticsearch を接続することで、コンテキスト エンジニアリングの重要なレイヤーであるメモリをエージェントに付与できます。セマンティックリコールを使用すると、エージェントは時間の経過とともにコンテキストを構築し、学習した内容に基づいて応答することができます。つまり、より正確で信頼性が高く、自然なやりとりが可能になります。</p><p>この早期の統合は単なる出発点にすぎません。ここで同じパターンを使用すると、過去のチケットを記憶しているサポートエージェント、関連ドキュメントを取得する内部ボット、会話の途中で顧客の詳細を思い出すことができる AI アシスタントなどが可能になります。当社は公式の Mastra 統合にも取り組んでおり、近い将来この組み合わせがさらにシームレスになる予定です。</p><p>次に何を構築するのか楽しみにしています。ぜひ試してみて、 <a href="https://mastra.ai/">Mastra</a>とそのメモリ機能を調べ、発見したことをコミュニティと自由に共有してください。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/knowledge-agent-semantic-recall-mastra-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/knowledge-agent-semantic-recall-mastra-elasticsearch</guid>
    <category><![CDATA[エージェント型AI]]></category>
    <category><![CDATA[開発者エクスペリエンス]]></category>
    <category><![CDATA[統合]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt09afdbff05603865/6a16f7bd839dfabbf2dcfcb5/b8d51c2726d5573385c9246a7821d12ade4f1b0e-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 06 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearchインデックスのフィールドを表示する方法]]></title>
    <description><![CDATA[_mapping および _search API、サブフィールド、合成 _source、およびランタイム フィールドを使用して Elasticsearch インデックスのフィールドを表示する方法を学習します。]]></description>
    <content:encoded><![CDATA[<p>この記事では、Elasticsearch インデックスのフィールドを表示する方法について説明します。これは、データの構造を理解し、特定のフィールドを識別し、問題をトラブルシューティングするのに役立ちます。以下のトピックを取り上げます。</p><ol><li><p><code>_mapping</code> API を使用してフィールド情報を取得する</p></li><li><p><code>_search</code> API を使用してフィールド値を表示する</p></li><li><p>サブフィールドの表示</p></li><li><p>Synthetic _source</p></li><li><p>ランタイムフィールド</p></li></ol><h2>1. _mapping APIを使用してフィールド情報を取得する</h2><p><code>_mapping</code> API を使用すると、1 つまたは複数のインデックスのマッピング定義を取得できます。これには、フィールド、そのデータ型、およびその他のプロパティに関する情報が含まれます。特定のインデックスのマッピングを取得するには、次のリクエストを使用します。</p>GET /&lt;index_name&gt;/_mapping<p>たとえば、 <code>my_index</code>という名前のインデックスがある場合、次のリクエストでそのマッピングを取得できます。</p>GET /my_index/_mapping<p>応答には、フィールドとそのプロパティに関する情報を含むインデックスのマッピング定義が含まれます。</p><p>特定のフィールドのマッピングを取得することもできます。これは、マッピングが非常に大きく、特定のフィールドにのみ焦点を当てたい場合に便利です。特定のフィールドのマッピングを取得するには、次のリクエストを使用します。</p>GET /my_index/_mapping/field/my_field<p>次のリクエストのように、フィールド名をコンマで区切ることで、複数のフィールドのマッピングを取得することもできます。</p>GET /my_index/_mapping/field/my_field_1,my_field_2,my_field_3<h2>2. _search APIを使用してフィールド値を表示する</h2><p>Elasticsearch インデックス内のフィールドの値を表示するには、 <code>_search</code> API を使用できます。<code>_search</code> API では、返されるフィールドを制御する方法が複数用意されています。主な方法は次の 2 つです。</p><ol><li><p><strong><code>_source</code></strong>: <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-source-field"><code>_source</code></a>フィールドには、取り込みパイプラインまたは前処理手順によって行われた変更も含め、インデックスが作成されたとおりの元の JSON ドキュメント本体が含まれます。ソース ドキュメントの特定のフィールドを表示するには、以下に示すようにソース フィルタリングを実装します。</p></li><li><p><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>にフォールバックします。これによって、後述するように、パフォーマンスなど多くの利点が得られます。</p></li></ol><h3>_source フィールドの使用</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>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "_source": ["title", "author"]
}<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>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "_source": {
     “includes”: [“title”, “author.*],
     “excludes”: [“author.description”]
  }
}<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><p><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>から直接値を返すことができるため、応答のペイロード サイズが小さくなります。</p></li><li><p><strong>フォーマットされた出力:</strong>標準フィールドの場合、 <code> fields</code>値を取得するために<code>_source</code>にフォールバックすることがありますが、インデックス マッピングを参照して、フォーマットされた日付などの出力を適切にフォーマットし、集計や並べ替えに使用されるものと一貫性を保ちます。</p></li><li><p><strong>ランタイム フィールドへのアクセス:</strong> <code>fields</code> 、元の<code>_source</code>には存在しないランタイム フィールドを返す場合があります。</p></li><li><p>さらに詳しい特典については、<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrieve-selected-fields#search-fields-param">こちらを</a>ご覧ください。</p></li></ul><p>たとえば、 <code>my_index</code>インデックス内の<code>title</code>フィールドと<code>author</code>フィールドのみを返すには、次の検索リクエストを使用できます。</p>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author"],
  "_source": false
}<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> パラメータを使用して検索できます。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>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "docvalue_fields": [
    "title",
    "author",
    {
      "field": "published",
      "format": "epoch_millis"
    }
  ],
  "_source": false
}<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>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "stored_fields": ["summary"]
}<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>GET /my_index/_search
{
  "query": {
    "match_all": {}
  },
  "fields": ["title", "author", "address.city"],
  "_source": false
}<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">合成</a><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>PUT idx
{
  "settings": {
    "index": {
      "mapping": {
        "source": {
          "mode": "synthetic"
        }
      }
    }
  }
}<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>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-index-show-fields</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-index-show-fields</guid>
    <category><![CDATA[データのインデキシング]]></category>
    <category><![CDATA[マッピング]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd041e871a8935448/6a17de320b0bedf404dd34ab/23b96aaa1a38b1f4747b4a87695d816f24c0cf70-720x421.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 06 Aug 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[JavaScript、Mastra、Elasticsearch を使用したエージェント型 RAG アシスタントの構築]]></title>
    <description><![CDATA[JavaScript エコシステムで AI エージェントを構築する方法を学ぶ]]></description>
    <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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt63ea3e7a09306fbf/6a17f1d97f6f150e22c09c50/1c73bd1dc1b5fe54f025c7a2b7c322acc9122f3a-1999x1393.png" alt="" /><p>注: このブログ投稿は、「 <a href="https://www.elastic.co/search-labs/blog/ai-agents-ai-sdk-elasticsearch">AI SDK と Elastic を使用した AI エージェントの構築</a>」に基づいています。AI エージェント全般とその用途についてよく知らない場合は、まずそこから始めてください。
</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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte13f09493f217047/6a17f1db1d1b83178d93e546/443bdc00d84ed1dd49e9f9e431e86ca4b0892563-1999x977.png" alt="" /><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><p><strong>プロジェクトの足場作り:</strong></p><ol><li><p><strong>バックエンド (Mastra):</strong> npx create mastra@latest を使用してバックエンドをスキャフォールディングし、エージェント ロジックを定義します。</p></li><li><p><strong>フロントエンド (Vite + React):</strong> npm create vite@latest を使用して、エージェントと対話するためのフロントエンド チャット インターフェイスを構築します。</p></li></ol></li><li><p><strong>環境変数の設定</strong></p><ol><li><p>環境変数を管理するには、dotenv をインストールします。</p></li><li><p>.envを作成するファイルを開き、必要な変数を指定します。</p></li></ol></li><li><p><strong>Elasticsearchの設定</strong></p><ol><li><p>Elasticsearch クラスターを起動します (ローカルまたはクラウド上)。</p></li><li><p>公式 Elasticsearch クライアントをインストールします。</p></li><li><p>環境変数にアクセスできることを確認します。</p></li><li><p>クライアントへの接続を確立します。</p></li></ol></li><li><p><strong>NBA データを Elasticsearch に一括取り込み</strong></p><ol><li><p>集計を有効にするには、適切なマッピングを使用してインデックスを作成します。</p></li><li><p>プレイヤーのゲーム統計を CSV ファイルから Elasticsearch インデックスに一括取り込みます。</p></li></ol></li><li><p><strong>Elasticsearchの集計を定義する</strong></p><ol><li><p>特定の対戦相手に対する過去の平均を計算するクエリ。</p></li><li><p>特定の対戦相手に対するシーズン平均を計算するクエリ。</p></li></ol></li><li><p><strong>プレーヤー比較ユーティリティファイル</strong></p><ol><li><p>ヘルパー関数と Elasticsearch 集計を統合します。</p></li></ol></li><li><p><strong>エージェントの構築</strong></p><ol><li><p>エージェント定義とシステム プロンプトを追加します。</p></li><li><p>zod をインストールし、ツールを定義します。</p></li><li><p>CORS を処理するためのミドルウェア設定を追加します。</p></li></ol></li><li><p><strong>フロントエンドの統合</strong></p><ol><li><p>AI-SDK の useChat を使用してエージェントと対話します。</p></li><li><p>適切にフォーマットされた会話を保持するための UI を作成します。</p></li></ol></li><li><p><strong>アプリケーションの実行</strong></p><ol><li><p>バックエンド (Mastra サーバー) とフロントエンド (React アプリ) の両方を起動します。</p></li><li><p>サンプルクエリと使用方法。</p></li></ol></li><li><p><strong>次はエージェントのさらなるインテリジェント化</strong></p><ol><li><p>セマンティック検索機能を追加して、より洞察力のある推奨を可能にします。</p></li><li><p>検索ロジックを Elasticsearch MCP (Model Context Protocol) サーバーに移動することで、動的クエリを有効にします。</p></li></ol></li></ol><h3><strong>要件</strong></h3><ul><li><p><strong>Node.js と npm</strong> : バックエンドとフロントエンドの両方が Node 上で実行されます。Node 18+ と npm v9+ (Node 18+ にバンドルされています) がインストールされていることを確認してください。</p></li><li><p><strong>Elasticsearch クラスター:</strong>ローカルまたはクラウド上のアクティブな Elasticsearch クラスター。</p></li><li><p><strong>OpenAI API キー</strong>: <a href="https://platform.openai.com/api-keys">OpenAI 開発者ポータルの</a>API キー ページで生成します。</p></li></ul><p></p><h3><strong>プロジェクト構造</strong></h3><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt749baa120552e4ab/6a17f1dd1d1b83bfe993e54a/1c0bde11ad0eead523a95e03b9b905aa776e3fd1-1420x934.png" alt="" /><h4><strong>ステップ1：プロジェクトの足場作り</strong></h4><ol><li><p>まず、nba-ai-assistant-js ディレクトリを作成し、次のコマンドを使用して内部に移動します。 </p></li></ol>mkdir nba-ai-assistant-js &amp;&amp; cd nba-ai-assistant-js<p><strong>バックエンド:</strong></p><ol><li><p>次のコマンドで Mastra 作成ツールを使用します。 </p></li></ol>npx create-mastra@latest<p>2. ターミナルにいくつかのプロンプトが表示されます。最初のプロンプトでは、プロジェクトに backend という名前を付けます。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt65abf68fe588e968/6a17f1de63baff2814741d5b/de2725031ed6837db99a979efcdd0ece1e197dbb-608x84.png" alt="" /><p>3. 次に、Mastra ファイルを保存するためのデフォルトの構造を維持するため、 <code>src/</code>を入力します。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt89bd829fcf0ae6b9/6a17f1e04b055dd30e432302/88919d9ff1852126395e1fcd700ecb1b59aac63c-866x116.png" alt="" /><p>4. 次に、デフォルトの LLM プロバイダーとして OpenAI を選択します。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfd167cc77a40b9a8/6a17f1e11480099e29b48863/2328761e769f3ded134e5a21e8a0bf8f41e88f68-404x210.png" alt="" /><p>5. 最後に、OpenAI API キーの入力が求められます。ここでは、スキップするオプションを選択し、後で<code> .env</code>ファイルで提供します。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt12654151ed495370/6a17f1e22f4a5c0f84fa89f9/0662de9bd28758e377e4c63df8d08b479068ce63-444x120.png" alt="" /><p><strong>フロントエンド：</strong></p><ol><li><p>ルート ディレクトリに戻り、次のコマンドを使用して<a href="https://vite.dev/guide/">Vite 作成ツール</a>を実行します。 <code>npm create vite@latest frontend -- --template react</code></p></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><p>機密キーを管理するために、 <code>dotenv</code>パッケージを使用して.envから環境変数を読み込みます。ファイル。バックエンドディレクトリに移動して<code>dotenv</code>をインストールします。</p></li></ol>cd backend
npm install dotenv --save<p>2. バックエンド ディレクトリでは、適切な変数を入力するための example.env ファイルが提供されます。独自に作成する場合は、次の変数を必ず含めてください。</p># 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
<p></p><p>注意: <code>.env</code> <code>.gitignore</code>に追加して、このファイルがバージョン管理から除外されていることを確認してください。</p><h4><strong>ステップ3: Elasticsearchの設定</strong></h4><p>まず、アクティブな Elasticsearch クラスターが必要です。次の 2 つのオプションがあります。</p><ul><li><p><strong>オプションA: Elasticsearch Cloudを使用する</strong></p><ul><li><p><a href="https://cloud.elastic.co/registration">Elastic Cloud</a>にサインアップ</p></li><li><p>新しいデプロイメントを作成する</p></li><li><p>エンドポイント URL と API キー（エンコード済み）を取得します</p></li></ul></li><li><p><strong>オプションB: Elasticsearchをローカルで実行する</strong></p><ul><li><p>Elasticsearchをローカルにインストールして実行する</p></li><li><p>エンドポイントとして http://localhost:9200 を使用します</p></li><li><p>APIキーを生成する</p></li></ul></li></ul><p></p><p><strong>バックエンドに Elasticsearch クライアントをインストールする:</strong></p><ol><li><p>まず、バックエンド ディレクトリに公式 Elasticsearch クライアントをインストールします。</p></li></ol>npm install @elastic/elasticsearch<p>2. 次に、再利用可能な関数を保持するディレクトリ lib を作成し、そこに移動します。</p>mkdir lib &amp;&amp; cd lib<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>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 });<p>5. 次に、環境変数を使用して Elasticsearch クライアントを初期化し、接続を確認します。</p>//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();
<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><p><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 ソースから取得されたものではありません。)</p></li><li><p><a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/data/playerAndTeamInfo.js">playerAndTeamInfo.js</a> - 通常は API 呼び出しによって提供されるプレーヤーとチームのメタデータを置き換え、エージェントがプレーヤーとチームの名前を ID に一致できるようにします。サンプル データを使用しているため、外部 API から取得する際のオーバーヘッドを避け、エージェントが参照できるいくつかの値をハードコードしました。</p></li></ul><p></p><p><strong>実装：</strong></p><ol><li><p><code>backend/lib</code>ディレクトリで、 <a href="https://github.com/jdarmada/nba-ai-assistant-js/blob/main/backend/lib/playerDataIngestion.js">playerDataIngestion.js</a>という名前のファイルを作成します。</p></li><li><p>インポートを設定し、CSV ファイル パスを解決し、解析を設定します。ここでも、ESM を使用しているため、サンプル CSV へのパスを解決するには<code>__dirname</code>を再構築する必要があります。また、 <a href="http://node.js/">Node.js</a>の組み込みモジュール<code>fs</code>と<code>readline</code>を使用して、指定された CSV ファイルを行ごとに解析します。</p></li></ol>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');<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: 'strict'</code>を追加します。
</p>// 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;
    }
}
<p>4. CSV データを Elasticsearch インデックスに一括で取り込む機能を追加します。コード ブロック内では、ヘッダー行をスキップします。次に、各行項目をコンマで分割し、ドキュメント オブジェクトにプッシュします。このステップでは、それらをクリーンアップし、適切なタイプであることを確認します。次に、ドキュメントをインデックス情報とともに bulkBody 配列にプッシュします。これは、Elasticsearch への一括取り込みのペイロードとして機能します。</p>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`);
<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>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) =&gt; {
                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);
    }
}
<p>6. 以下の<code>main()</code>関数を実行して、 <code>createIndex()</code>関数と<code>bulkIngestCsv()</code>関数を順番に実行します。</p>// 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();
<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>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.' };
    }
}
<p>3. 特定の対戦相手に対するプレーヤーのシーズン平均を計算するには、履歴クエリとほぼ同じクエリを使用します。このクエリの唯一の違いは、 <code>bool</code>フィルターに<code>game_date</code>の追加条件があることです。フィールド<code>game_date</code>は、現在の NBA シーズンの範囲内に収まる必要があります。この場合、範囲は<code>2024-10-01</code>から<code>2025-06-30</code>の間になります。以下の追加条件により、後続の集計で今シーズンのゲームのみが分離されることが保証されます。
</p>        {
                            range: {
                    //Range for this season, change to match current season
                                game_date: {
                                    gte: '2024-10-01',
                                    lte: '2025-06-30',
                                },
                            },
<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 集約ロジックを、エージェントが使用するメイン ツールを強化する単一の関数に統合します。
</p>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];
}
<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><p><strong>名前:</strong>フロントエンドで呼び出されたときに参照として使用されるエージェントの名前を指定します。</p></li><li><p><strong>指示/システム プロンプト:</strong>システム プロンプトは、対話中に従うべき初期コンテキストとルールを LLM に提供します。これは、ユーザーがチャット ボックスを通じて送信するプロンプトに似ていますが、こちらはユーザー入力の前に表示されます。繰り返しになりますが、これは選択したモデルに応じて変わります。</p></li><li><p><strong>モデル:</strong>使用する LLM (Mastra は OpenAI、Anthropic、ローカル モデルなどをサポートしています)。</p></li><li><p><strong>ツール:</strong>エージェントが呼び出すことができるツール関数のリスト。</p></li><li><p><strong>メモリ:</strong> (オプション) エージェントに会話履歴などを記憶させたい場合。簡単にするために、Mastra は永続メモリをサポートしていますが、永続メモリなしで開始できます。</p></li></ul><p></p>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 },
});
<p><strong>
ツールの定義</strong></p><ol><li><p><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>ファイルに移動します。</p></li><li><p>次のコマンドを使用して Zod をインストールします。</p></li></ol>npm install zod<p>3. ツール定義を追加します。このツールを呼び出すときにエージェントが使用するメイン関数として、 <code>comparePlayers.js</code>ファイル内の関数をインポートすることに注意してください。Mastra の<code>createTool()</code>関数を使用して、 <code>playerComparisonTool</code>を登録します。フィールドには次のものが含まれます。</p><ul><li><p><code>id</code>: これは、エージェントがツールの機能を理解するのに役立つ自然言語による説明です。</p></li><li><p><code>input schema</code>: ツールの入力の形状を定義するために、Mastra は TypeScript スキーマ検証ライブラリである<a href="https://zod.dev/">Zod</a>スキーマを使用します。Zod は、エージェントが正しく構造化された入力を入力したことを確認し、入力構造が一致しない場合はツールが実行されないようにすることで役立ちます。</p></li><li><p><code>description</code>: これは、エージェントがいつ電話をかけてツールを使用するかを理解するのに役立つ自然言語による説明です。</p></li><li><p><code>execute</code>: ツールが呼び出されたときに実行されるロジック。私たちの場合、インポートしたヘルパー関数を使用してパフォーマンス統計を返します。</p></li></ul>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 } }) =&gt; {
        return await comparePlayersForNextMatchup(player1, player2);
      },
})<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><p><code>origin: ['http://localhost:5173']</code></p><ul><li><p>このアドレス（Vite のデフォルト アドレス）からのリクエストのみを許可します</p></li></ul></li><li><p><code>allowMethods: ["GET", "POST"]</code></p><ul><li><p>許可される HTTP メソッド。ほとんどの場合、POST が使用されます。</p></li></ul></li><li><p><code>allowHeaders: ["Content-Type", "Authorization", "x-mastra-client-type, "x-highlight-request", "traceparent"],</code></p><ul><li><p>これらはリクエストで使用できるカスタムヘッダーを決定します</p></li></ul></li></ul><p></p>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
<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>npm install @ai-sdk/react<p>2. 同じディレクトリで、React Markdown をインストールして、エージェントが生成する応答を適切にフォーマットできるようにします。</p>npm install react-markdown<p>3. <code>useChat()</code>を実装します。このフックは、フロントエンドと AI エージェントのバックエンド間のやり取りを管理します。メッセージの状態、ユーザー入力、ステータスを処理し、監視の目的でライフサイクル フックを提供します。渡すオプションは次のとおりです。</p><ul><li><p><code>api:</code> これは、Mastra AI エージェントのエンドポイントを定義します。デフォルトではポート 4111 に設定されており、ストリーミング応答をサポートするルートも追加する必要があります。</p></li><li><p><code>onToolCall</code>: これは、エージェントがツールを呼び出すたびに実行されます。エージェントがどのツールを呼び出しているかを追跡するために使用します。</p></li><li><p><code>onFinish</code>: エージェントが完全な応答を完了した後に実行されます。ストリーミングを有効にしても、 <code>onFinish</code>各チャンクの後ではなく、完全なメッセージが受信された後に実行されます。ここでは、トークンの使用状況を追跡するためにこれを使用しています。これは、LLM コストを監視して最適化するときに役立ちます。</p></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>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 }) =&gt; {
            setToolsCalled((prev) =&gt; [...prev, toolCall.toolName]);
        },

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

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

    return (
        &lt;div&gt;
            &lt;div className="agent-info"&gt;
                &lt;h4 className="stats-title"&gt;What's My Agent Doing?&lt;/h4&gt;

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

                    &lt;div className="usage-stats"&gt;
                        &lt;p&gt;Prompt Token Usage: {promptTokenUsage}&lt;/p&gt;
                        &lt;p&gt;Completion Token Usage: {completionTokenUsage}&lt;/p&gt;
                        &lt;p&gt;Total Token Usage: {totalTokenUsage}&lt;/p&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;

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

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

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

npm run dev<p></p><p>3. ブラウザで次の場所に移動します。</p><p></p><p><a href="http://localhost:5173/">http://localhost:5173</a></p><p></p><p>チャット インターフェースが表示されるはずです。次のサンプルプロンプトを試してみてください。</p><ul><li><p>「レブロン・ジェームズとステフィン・カリーを比較」</p></li><li><p>「ジェイソン・テイタムとルカ・ドンチッチのどちらを選ぶべきでしょうか？」</p></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><p><strong>エージェント RAG の基礎</strong>と、AI エージェントの自律性と RAG を効果的に使用するツールを組み合わせることで、より繊細で動的なエージェントを実現できる方法について説明します。</p></li><li><p><strong>Elasticsearch</strong>とそのデータ ストレージ機能および強力なネイティブ集約により、それが LLM のナレッジ ベースとして優れたパートナーとなる理由について説明します。</p></li><li><p><strong>Mastra</strong>フレームワークと、それが JavaScript エコシステムの開発者にとってこれらのエージェントの構築をどのように簡素化するかについて説明します。</p></li></ul><p>あなたがバスケットボールの熱狂的なファンであっても、AI エージェントの構築方法を検討している方であっても、あるいは私のようにその両方であっても、このブログが、始めるための基礎を提供できれば幸いです。完全なリポジトリは<a href="https://github.com/jdarmada/nba-ai-assistant-js">GitHub</a>で入手できます。自由にクローンして改良してください。さあ、ファンタジーリーグで優勝しましょう!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/agentic-rag</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/agentic-rag</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[エージェント型AI]]></category>
    <category><![CDATA[JavaScript]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ffd561836a4cb20/6a17f1e47b54f978588b39e4/8132ed781c1ea5d46ca244182f421ed5c721f23b-1200x628.png" length="0" type="image/png"/>
    <pubDate>Tue, 01 Jul 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[MCP（モデルコンテキストプロトコル）の現状]]></title>
    <description><![CDATA[MCP、プロジェクトの更新、機能、セキュリティ上の課題、新しいユースケース、Elastic の Elasticsearch MCP サーバーの操作方法について学びます。]]></description>
    <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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ff0e141b5dfda29/6a17e7ffe8fbcee5263a1946/5eba1e59514eb58a5220bb92bb49e6328ee83cd7-674x466.png" alt="モデルコンテキストプロトコル（MCP）アーキテクチャ" /><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><p>ストリーミング可能なHTTPを追加してリモートMCPを有効にする</p></li><li><p>抽出とツール出力スキーマの追加により、より豊富なエージェントインタラクションモデルを可能にする</p></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://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4d53e7bb091b4f43/6a17e80163baff80dc741c56/2ea159116fe5e03ff800f077adf16d6ca9f1c1d1-1594x1280.png" alt="エージェント向けMCP認証" /><p>この実装では、OAuth の責任を MCP クライアントとサーバーの間で分割できます。認証フローの大部分は MCP クライアントによって開始および処理され、最後にサーバーが関与するのは安全なトークンの受信と検証のみです。この分割により、開発者がすべての接続を構成する必要なく、多くのツール間で認証を行うという重要なスケーリングの問題が解決され、MCP サーバー開発者が OAuth の専門家になる必要がなくなります。</p><p>講演の2つの重要なハイライト:</p><ol><li><p><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></p></li><li><p><a href="https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13"><strong>IDP と SSO のサポート</strong></a>: 企業は ID プロバイダーを統合してアクセスを集中管理できます。これは、ユーザー エクスペリエンスとセキュリティの両方にとってメリットとなります。ユーザーは 10 個の異なる同意画面をクリックする必要がなくなり、セキュリティ チームは各接続を監視できるようになります。</p></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><p>脅威</p><p>説明</p><p>修復とベストプラクティス</p><p>迅速な注射とツールの中毒</p><p>LLM システムのコンテキストまたはツールの説明内に悪意のあるプロンプトを挿入し、LLM がファイルの読み取りやデータの漏洩などの意図しないアクションを実行するようにする方法。</p><p>MCP Scan などのツールを使用して、ツールのメタデータのチェックを実行します。説明とパラメータをプロンプトに含める前に検証します。最後に、リスクの高いツールに対してユーザー承認を実装することを検討してください。詳細については、表の下の追加の読書リストにある OWASP プロンプト インジェクション ガイドを参照してください。</p><p>サンプリング攻撃</p><p>MCP のコンテキストでは、サンプリングにより、MCP サーバーが LLM に対してプロンプト インジェクション攻撃を実行できるようになります。</p><p>信頼できないサーバーのサンプリングを無効にし、サンプリング要求に人間による承認を追加することを検討してください。</p><p>悪意のあるMCPサーバー</p><p>現在の MCP サーバーのコレクションでは、安全性を確保するために各サーバーを検査するのは困難です。不正なサーバーは密かにデータを収集し、悪意のある人物に公開する可能性があります。</p><p>信頼できるレジストリまたは内部リストからのみ MCP サーバーに接続します。サンドボックス化されたコンテナ内でサードパーティのサーバーを実行します。</p><p>悪意のあるMCPインストールツール</p><p>コマンドライン インストーラーとスクリプトは、MCP サーバーまたはツールを迅速に実装するのに便利ですが、検証されていない侵害されたコードをインストールしてしまう可能性があります。</p><p>サンドボックス環境にインストールし、パッケージ署名を検証します。検証されていないソースからの自動更新は行わないでください。</p><p>この問題にさらに対抗するために、Arjun は、信頼できる MCP レジストリを使用してすべての検証を処理すること (これは最重要トピックです。詳細については、以下の読書リストの上位 2 項目を参照してください) と、この<a href="https://github.com/slowmist/MCP-Security-Checklist">セキュリティ チェックリスト</a>の使用を提案しています。</p><p>追加の参考資料:</p><ul><li><p><a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices">公式MCPセキュリティベストプラクティス</a></p></li><li><p><a href="https://owasp.org/www-project-top-10-for-large-language-model-applications/">OWASP LLM アプリケーション トップ 10</a></p></li><li><p><a href="https://hiddenlayer.com/innovation-hub/">HiddenLayer脅威リサーチ</a></p></li><li><p><a href="https://github.com/invariantlabs-ai/mcp-scan">MCPスキャン</a></p></li><li><p><a href="https://genai.owasp.org/llmrisk/llm01-prompt-injection/">OWASP プロンプトインジェクションガイド</a></p></li></ul><h2>次はレジストリ、ガバナンス、エコシステム</h2><p>集中型の MCP レジストリが開発中であり、サミットで最も頻繁に議論されたトピックの 1 つでした。現在のサーバー エコシステムは、断片化、信頼性と発見可能性の低さに悩まされています。特にメタデータが不完全であったり偽装されたりする可能性がある分散型エコシステムでは、開発者が MCP サーバーを見つけ、その動作を検証し、安全にインストールすることは困難です。</p><p>集中型レジストリは、信頼できる真実のソースとして機能し、発見可能性を向上させ、サーバー メタデータの整合性を確保し、悪意のあるツールをインストールするリスクを軽減することで、これらの問題点に直接対処します。</p><p>MCP レジストリの目標は次のとおりです。</p><ul><li><p>サーバーのメタデータ（サーバーが何をするか、どのように認証するか、インストールして呼び出すか）に関する唯一の真実の情報源を提供します。</p></li><li><p>不完全なサードパーティのレジストリと断片化を排除して、サーバーを登録するときに、インターネット上の他のすべてのレジストリを更新する必要がなくなります。</p></li><li><p>CLI ツールと、前述のメタデータを含む server.json ファイルを含むサーバー登録フローを提供します。</p></li></ul><p>より広範な期待は、信頼できるレジストリがエコシステムを安全に拡張し、開発者が自信を持って新しいツールを構築して共有できるようにすることです。</p><p>ガバナンスは、Anthropic にとってもう一つの最重要課題でした。MCP はオープンかつコミュニティ主導であり続けるべきだと明言しましたが、ガバナンス モデルの拡張はまだ進行中です。彼らは現在、その分野での支援を求めており、オープンソース プロトコルのガバナンスの経験がある方は誰でも連絡を取るよう呼びかけています。これは私が言及したかったもう一つの話題につながります。イベント全体を通じて、講演者は、エコシステムはその内部の開発者の貢献によってのみ成長できると強調しました。MCP を新しい Web 標準にして、他の一般的なエージェント プロトコルと差別化するために、集中的な取り組みが必要です。</p><h2>現実世界におけるMCP：ケーススタディとデモ</h2><p>いくつかの組織は、MCP がすでに実際のアプリケーションでどのように使用されているかを共有しました。</p><ul><li><p><strong>PayPal - エージェンティックコマース向け MCP サーバー:</strong> PayPal は、ユーザーのショッピング体験を根本的に変えることができる新しい<a href="https://github.com/paypal/agent-toolkit/">エージェント ツールキット</a>と MCP サーバーを展示しました。ユーザーは、ソーシャル メディアで商品を探したり、価格を比較したり、チェックアウトしたりする代わりに、PayPal MCP サーバーに接続してそれらのすべてのアクションを処理するエージェントとチャットできます。
</p></li><li><p><strong>EpicAI.pro - Jarvis:</strong> MCP の開発により、現実世界の Jarvis タイプのアシスタントの実現にますます近づいています。アイアンマン映画をご存じない方のために説明すると、Jarvis は自然言語を使用し、マルチモーダル入力に応答し、応答時に遅延がなく、ユーザーのニーズを積極的に予測し、統合を自動的に管理し、デバイスと場所の間でコンテキストを切り替えることができる AI アシスタントです。Jarvis を物理的なロボット アシスタントとして想像すると、MCP は Jarvis に「手」、つまり複雑なタスクを処理する能力を与えます。
</p></li><li><p><strong>Postman -</strong> <a href="https://www.postman.com/explore/mcp-generator"><strong>MCP サーバー ジェネレーター</strong></a><strong>:</strong> API リクエスト用のショッピング カート エクスペリエンスを提供します。さまざまな API リクエストを選択してバスケットに入れ、バスケット全体を MCP サーバーとしてダウンロードできます。
</p></li><li><p><strong>Bloomberg -</strong> Bloomberg は、エンタープライズ GenAI 開発における主要なボトルネックを解決しました。約 10,000 人のエンジニアを抱える同社では、チーム間でツールとエージェントを統合するための標準化された方法が必要でした。MCP を使用することで、社内ツールをモジュール式のリモートファースト コンポーネントに変換し、エージェントが統合インターフェースで簡単に呼び出すことができるようになりました。これにより、エンジニアは組織全体にツールを提供できるようになり、AI チームはカスタム統合ではなくエージェントの構築に集中できるようになりました。Bloomberg は現在、MCP エコシステムとの完全な相互運用性を実現する、スケーラブルで安全なエージェント ワークフローをサポートしています。ブルームバーグは公開リソースへのリンクを一切提供していないが、これはサミットで彼らが公開した内容である。
</p></li><li><p><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 の導入を拡大できるよう支援しました。
</p></li><li><p><strong>AWS -</strong> <a href="https://github.com/awslabs/mcp"><strong>AWS MCP サーバー</strong></a><strong>:</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>も紹介されました。</p></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><p><code>platform.core.search</code> - 完全なElasticsearchクエリDSLを使用して検索を実行します</p></li><li><p><code>platform.core.list_indices</code> - Elasticsearch 内で利用可能なすべてのインデックスを一覧表示します (エージェントがどのようなデータが存在するかを検出できるようにします)</p></li><li><p><code>platform.core.get_index_mapping</code> - 特定のインデックスのフィールド マッピングを取得します (エージェントがデータの形状と種類を理解するのに役立ちます)</p></li><li><p><code>platform.core.get_document_by_id</code> - IDで特定のドキュメントを取得します（正確な検索のため）</p></li></ul><p>これらのツールを使用するだけで、信頼性の高い AI エージェントを構築するための中核となるエンタープライズ レベルの検索と関連性をエージェントに装備できます。</p><p>Agent Builder をさらに強力にするのは、アプリケーションのニーズに合わせて独自のカスタム ツールを定義し、公開できる機能です。これは、毎回そのロジックを再検出することなく、エージェントが特定のインデックスに対して特定のタイプの検索を実行するようにしたい、意見が強いワークフローや繰り返し可能なワークフローに特に役立ちます。同じ結論に到達するために計画と推論にトークンを費やす代わりに、その意図をツールに直接エンコードすることで、エージェントの速度、信頼性、コスト効率を高めることができます。</p><p>Agent Builder UI 内で、ES|QL を使用するカスタム ツール定義の例を次に示します。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltca9e3d0a4e7031c0/6a17e803faa913d8c393c897/c1f6405a374b707e8e6fa36b9e21db5f3c7cd127-1376x864.png" alt="エージェントビルダーUI" /><p>カスタム ツールを定義したら、 <code>Manage MCP</code>のドロップダウンをクリックして MCP サーバー URL をコピーすることで、MCP を使用してカスタム ツール (および組み込みのネイティブ ツール) を公開できます。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf7d8b29b06c08f94/6a17e805033c8d07f06bb1b6/9f39588525ca2643475de557ea54a6bcf5c150f6-1282x616.png" alt="MCPツール" /><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>
    <link>https://www.elastic.co/search-labs/blog/mcp-current-state</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/mcp-current-state</guid>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[JD Armada]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2f63f23bbecd2a18/6a17e8066317302039585aa7/02b8c8672ffa129e0ed91a92d6cab612a01d27f2-1200x628.png" length="0" type="image/png"/>
    <pubDate>Thu, 12 Jun 2025 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>