Elasticsearchは、業界をリードする生成AIツールやプロバイダーとネイティブに統合されています。RAG応用編やElasticベクトルデータベースで本番環境対応のアプリを構築する方法についてのウェビナーをご覧ください。
ユースケースに最適な検索ソリューションを構築するには、無料のクラウドトライアルを始めるか、ローカルマシンでElasticを試してみてください。
このアイデアは、白熱したハイリスクなファンタジー バスケットボール リーグの最中に思いつきました。私はこう考えました。 「毎週の対戦で優位に立つのに役立つ AI エージェントを構築できるだろうか?」 もちろんです!
この記事では、 Mastraとそれと対話するための軽量 JavaScript Web アプリケーションを使用して、エージェント RAG アシスタントを構築する方法について説明します。このエージェントを Elasticsearch に接続することで、構造化されたプレイヤーデータへのアクセスとリアルタイムの統計集計の実行が可能になり、プレイヤー統計に基づいた推奨事項を提供できるようになります。GitHubリポジトリにアクセスして手順を確認してください。READMEには、アプリケーションを独自に複製して実行する方法が記載されています。
すべてをまとめると次のようになります。

注: このブログ投稿は、「 AI SDK と Elastic を使用した AI エージェントの構築」に基づいています。AI エージェント全般とその用途についてよく知らない場合は、まずそこから始めてください。
アーキテクチャの概要
システムの中核となるのは、エージェントの推論エンジン(脳)として機能する大規模言語モデル(LLM)です。ユーザー入力を解釈し、呼び出すツールを決定し、関連する応答を生成するために必要な手順を調整します。
エージェント自体は、JavaScript エコシステムのエージェント フレームワークである Mastra によって構築されます。Mastra は、LLM をバックエンド インフラストラクチャでラップし、それを API エンドポイントとして公開し、ツール、システム プロンプト、エージェントの動作を定義するためのインターフェイスを提供します。
フロントエンドでは、 Viteを使用して、エージェントにクエリを送信してその応答を受信するためのチャット インターフェイスを提供する React Web アプリケーションを迅速に構築します。
最後に、エージェントがクエリして集計できるプレーヤーの統計情報と対戦データを保存する Elasticsearch があります。

背景
いくつかの基本的な概念を確認してみましょう。
エージェントRAGとは何ですか?
AI エージェントは他のシステムと対話し、独立して動作し、定義されたパラメータに基づいてアクションを実行できます。Agentic RAG は、AI エージェントの自律性と検索拡張生成の原理を組み合わせ、LLM が応答を生成するために呼び出すツールとコンテキストとして使用するデータを選択できるようにします。RAG の詳細については、こちらをご覧ください。
フレームワークを選択する場合、なぜ AI-SDK を超えるのでしょうか?
利用可能な AI エージェント フレームワークは数多くあり、 CrewAI 、 AutoGen 、 LangGraphなどの人気のフレームワークについてはおそらく聞いたことがあるでしょう。これらのフレームワークのほとんどは、さまざまなモデルのサポート、ツールの使用、メモリ管理など、共通の機能セットを共有しています。
こちらは、Harrison Chase (LangChain CEO) によるフレームワーク比較シートです。
私が Mastra に興味を持ったのは、フルスタック開発者がエージェントをエコシステムに簡単に統合できるように構築された JavaScript ファーストのフレームワークであるという点です。Vercel の AI-SDK もこのほとんどを実行しますが、プロジェクトにさらに複雑なエージェント ワークフローが含まれている場合は、Mastra が真価を発揮します。Mastra は AI-SDK によって設定された基本パターンを強化しており、このプロジェクトではそれらを連携して使用します。
フレームワークとモデル選択の考慮事項
これらのフレームワークは AI エージェントを迅速に構築するのに役立ちますが、考慮すべき欠点もいくつかあります。たとえば、AI エージェントや一般的な抽象化レイヤー以外のフレームワークを使用する場合、制御が少し失われます。LLM がツールを正しく使用しなかったり、望ましくないことを実行したりする場合、抽象化によってデバッグが難しくなります。それでも、私の意見では、特にこれらのフレームワークは勢いを増しており、継続的に反復されているため、このトレードオフは、構築時に得られる容易さとスピードの価値があります。
繰り返しになりますが、これらのフレームワークはモデルに依存しません。つまり、さまざまなモデルをプラグ アンド プレイできます。モデルはトレーニングに使用されたデータ セットによって異なり、その結果、モデルが提供する応答も異なることに注意してください。一部のモデルではツールの呼び出しすらサポートされていません。したがって、さまざまなモデルを切り替えてテストし、どのモデルが最適な応答を返すかを確認することは可能ですが、それぞれのシステム プロンプトを書き換える必要がある可能性が高いことに注意してください。例えば、Llama3.3を使用する場合GPT-4o よりも、必要な応答を得るために、より多くのプロンプトと具体的な指示が必要になります。
NBAファンタジーバスケットボール
ファンタジー バスケットボールでは、友達のグループでリーグを開始し (グループの競争力に応じて、友情のステータスに影響する可能性があります)、通常はいくらかのお金が賭けられます。その後、各自が 10 人のプレイヤーでチームを編成し、毎週交互に他の友達の 10 人のプレイヤーと対戦します。全体のスコアに加算されるポイントは、特定の週に各プレイヤーが対戦相手に対して行ったパフォーマンスです。
チームの選手が負傷したり、出場停止になったりした場合は、チームに追加できるフリーエージェント選手のリストが表示されます。ファンタジー スポーツでは、選べる選手の数が限られており、誰もが常に最高の選手を選ぶために奔走しているため、ここで多くの難しい思考が生まれます。
これは、どの選手を選択するかをすぐに決定しなければならない状況で特に役立つ、NBA AI アシスタントの出番です。特定の対戦相手に対するプレーヤーのパフォーマンスを手動で調べる代わりに、アシスタントがそのデータをすばやく見つけて平均を比較し、情報に基づいた推奨事項を提供します。
エージェント RAG と NBA ファンタジー バスケットボールの基本がわかったので、実際に見てみましょう。
プロジェクトの構築
途中で行き詰まったり、最初から構築したくない場合は、リポジトリを参照してください。
取り上げる内容
- プロジェクトの足場作り:
- バックエンド (Mastra): npx create mastra@latest を使用してバックエンドをスキャフォールディングし、エージェント ロジックを定義します。
- フロントエンド (Vite + React): npm create vite@latest を使用して、エージェントと対話するためのフロントエンド チャット インターフェイスを構築します。
- 環境変数の設定
- 環境変数を管理するには、dotenv をインストールします。
- .envを作成するファイルを開き、必要な変数を指定します。
- Elasticsearchの設定
- Elasticsearch クラスターを起動します (ローカルまたはクラウド上)。
- 公式 Elasticsearch クライアントをインストールします。
- 環境変数にアクセスできることを確認します。
- クライアントへの接続を確立します。
- NBA データを Elasticsearch に一括取り込み
- 集計を有効にするには、適切なマッピングを使用してインデックスを作成します。
- プレイヤーのゲーム統計を CSV ファイルから Elasticsearch インデックスに一括取り込みます。
- Elasticsearchの集計を定義する
- 特定の対戦相手に対する過去の平均を計算するクエリ。
- 特定の対戦相手に対するシーズン平均を計算するクエリ。
- プレーヤー比較ユーティリティファイル
- ヘルパー関数と Elasticsearch 集計を統合します。
- エージェントの構築
- エージェント定義とシステム プロンプトを追加します。
- zod をインストールし、ツールを定義します。
- CORS を処理するためのミドルウェア設定を追加します。
- フロントエンドの統合
- AI-SDK の useChat を使用してエージェントと対話します。
- 適切にフォーマットされた会話を保持するための UI を作成します。
- アプリケーションの実行
- バックエンド (Mastra サーバー) とフロントエンド (React アプリ) の両方を起動します。
- サンプルクエリと使用方法。
- 次はエージェントのさらなるインテリジェント化
- セマンティック検索機能を追加して、より洞察力のある推奨を可能にします。
- 検索ロジックを Elasticsearch MCP (Model Context Protocol) サーバーに移動することで、動的クエリを有効にします。
要件
- Node.js と npm : バックエンドとフロントエンドの両方が Node 上で実行されます。Node 18+ と npm v9+ (Node 18+ にバンドルされています) がインストールされていることを確認してください。
- Elasticsearch クラスター:ローカルまたはクラウド上のアクティブな Elasticsearch クラスター。
- OpenAI API キー: OpenAI 開発者ポータルのAPI キー ページで生成します。
プロジェクト構造

ステップ1:プロジェクトの足場作り
- まず、nba-ai-assistant-js ディレクトリを作成し、次のコマンドを使用して内部に移動します。
バックエンド:
- 次のコマンドで Mastra 作成ツールを使用します。
2. ターミナルにいくつかのプロンプトが表示されます。最初のプロンプトでは、プロジェクトに backend という名前を付けます。

3. 次に、Mastra ファイルを保存するためのデフォルトの構造を維持するため、 src/を入力します。

4. 次に、デフォルトの LLM プロバイダーとして OpenAI を選択します。

5. 最後に、OpenAI API キーの入力が求められます。ここでは、スキップするオプションを選択し、後で .envファイルで提供します。

フロントエンド:
- ルート ディレクトリに戻り、次のコマンドを使用してVite 作成ツールを実行します。
npm create vite@latest frontend -- --template react
これにより、React 専用のテンプレートを使用して、 frontendという名前の軽量 React アプリが作成されます。
すべてがうまくいけば、プロジェクト ディレクトリ内に、Mastra コードを保持するバックエンド ディレクトリと、React アプリを含むfrontendディレクトリが表示されるはずです。
ステップ2: 環境変数の設定
- 機密キーを管理するために、
dotenvパッケージを使用して.envから環境変数を読み込みます。ファイル。バックエンドディレクトリに移動してdotenvをインストールします。
2. バックエンド ディレクトリでは、適切な変数を入力するための example.env ファイルが提供されます。独自に作成する場合は、次の変数を必ず含めてください。
注意: .env .gitignoreに追加して、このファイルがバージョン管理から除外されていることを確認してください。
ステップ3: Elasticsearchの設定
まず、アクティブな Elasticsearch クラスターが必要です。次の 2 つのオプションがあります。
- オプションA: Elasticsearch Cloudを使用する
- Elastic Cloudにサインアップ
- 新しいデプロイメントを作成する
- エンドポイント URL と API キー(エンコード済み)を取得します
- オプションB: Elasticsearchをローカルで実行する
- Elasticsearchをローカルにインストールして実行する
- エンドポイントとして http://localhost:9200 を使用します
- APIキーを生成する
バックエンドに Elasticsearch クライアントをインストールする:
- まず、バックエンド ディレクトリに公式 Elasticsearch クライアントをインストールします。
2. 次に、再利用可能な関数を保持するディレクトリ lib を作成し、そこに移動します。
3. 内部にelasticClient.jsという新しいファイルを作成します。このファイルは Elasticsearch クライアントを初期化し、プロジェクト全体で使用できるように公開します。
4. ECMAScript モジュール (ESM) を使用しているため、 __dirname and __ファイル名は使用できません。環境変数が.envから正しく読み込まれていることを確認するにはバックエンド フォルダー内のファイルで、ファイルの先頭に次の設定を追加します。
5. 次に、環境変数を使用して Elasticsearch クライアントを初期化し、接続を確認します。
これで、このクライアント インスタンスを、Elasticsearch クラスターと対話する必要がある任意のファイルにインポートできます。
ステップ4: NBAデータをElasticsearchに一括取り込み
データセット:
このプロジェクトでは、リポジトリのbackend/dataディレクトリにあるデータセットを参照します。当社の NBA アシスタントは、このデータを知識ベースとして使用し、統計的な比較を実行し、推奨事項を生成します。
- sample_player_game_stats.csv - サンプルプレーヤーのゲーム統計 (例: NBA キャリア全体におけるプレーヤーごとのゲームごとのポイント、リバウンド、スティールなど)。このデータセットを使用して集計を実行します。(注: これはデモ用に事前に生成された模擬データであり、公式 NBA ソースから取得されたものではありません。)
- playerAndTeamInfo.js - 通常は API 呼び出しによって提供されるプレーヤーとチームのメタデータを置き換え、エージェントがプレーヤーとチームの名前を ID に一致できるようにします。サンプル データを使用しているため、外部 API から取得する際のオーバーヘッドを避け、エージェントが参照できるいくつかの値をハードコードしました。
実装:
backend/libディレクトリで、 playerDataIngestion.jsという名前のファイルを作成します。- インポートを設定し、CSV ファイル パスを解決し、解析を設定します。ここでも、ESM を使用しているため、サンプル CSV へのパスを解決するには
__dirnameを再構築する必要があります。また、 Node.jsの組み込みモジュールfsとreadlineを使用して、指定された CSV ファイルを行ごとに解析します。
これにより、一括取り込み手順で CSV を効率的に読み取って解析できるようになります。
3. 適切なマッピングを使用してインデックスを作成します。Elasticsearch は動的マッピングを使用してフィールド タイプを自動的に推測できますが、ここでは各統計が数値フィールドとして扱われるように明示的に指定します。これらのフィールドは後で集計に使用するため、これは重要です。また、ポイントやリバウンドなどの統計情報には、小数値が含まれるようにするために、タイプfloat を使用します。最後に、Elasticsearch が認識されないフィールドを動的にマッピングしないように、マッピング プロパティdynamic: 'strict'を追加します。
4. CSV データを Elasticsearch インデックスに一括で取り込む機能を追加します。コード ブロック内では、ヘッダー行をスキップします。次に、各行項目をコンマで分割し、ドキュメント オブジェクトにプッシュします。このステップでは、それらをクリーンアップし、適切なタイプであることを確認します。次に、ドキュメントをインデックス情報とともに bulkBody 配列にプッシュします。これは、Elasticsearch への一括取り込みのペイロードとして機能します。
5.次に、 elasticClient.bulk()で Elasticsearch のBulk API を使用して、1 回のリクエストで複数のドキュメントを取り込むことができます。以下のエラー処理は、取り込みに失敗したドキュメントの数と、取り込みに成功したドキュメントの数を示すように構成されています。
6. 以下のmain()関数を実行して、 createIndex()関数とbulkIngestCsv()関数を順番に実行します。
一括取り込みが成功したことを示すコンソール ログが表示された場合は、Elasticsearch インデックスを簡単にチェックして、ドキュメントが実際に正常に取り込まれたかどうかを確認します。
ステップ5: Elasticsearchの集計の定義と統合
これらは、プレイヤーの統計を相互に比較するために AI エージェントのツールを定義するときに使用される主な関数になります。
1. backend/libディレクトリに移動し、 elasticAggs.jsというファイルを作成します。
2. 特定の対戦相手に対するプレイヤーの過去の平均を計算するには、以下のクエリを追加します。このクエリでは、2 つの条件(1 つはplayer_idに一致し、もう 1 つはopponent_team_idに一致する)を持つboolフィルターを使用して、関連するゲームのみを取得します。ドキュメントを返す必要はなく、集計のみを対象とするため、 size:0を設定します。aggsブロックでは、 points, rebounds, assists, steals, blocksやfg_percentageなどのフィールドに対して複数のメトリック集計を並行して実行し、平均値を計算します。LLM は計算で成功するか失敗するかのどちらかですが、このプロセスは Elasticsearch にオフロードされ、NBA AI アシスタントが正確なデータにアクセスできるようになります。
3. 特定の対戦相手に対するプレーヤーのシーズン平均を計算するには、履歴クエリとほぼ同じクエリを使用します。このクエリの唯一の違いは、 boolフィルターにgame_dateの追加条件があることです。フィールドgame_dateは、現在の NBA シーズンの範囲内に収まる必要があります。この場合、範囲は2024-10-01から2025-06-30の間になります。以下の追加条件により、後続の集計で今シーズンのゲームのみが分離されることが保証されます。
ステップ6: プレーヤー比較ユーティリティ
コードをモジュール化して保守しやすい状態に保つために、メタデータ ヘルパー関数と Elasticsearch 集計を統合するユーティリティ ファイルを作成します。これにより、エージェントが使用するメイン ツールが強化されます。詳細は後述します。
1. backend/libディレクトリに新しいファイルcomparePlayers.jsを作成します。
2. 以下の関数を追加して、メタデータ ヘルパーと Elasticsearch 集約ロジックを、エージェントが使用するメイン ツールを強化する単一の関数に統合します。
ステップ7: エージェントの構築
フロントエンドとバックエンドのスキャフォールディングを作成し、NBA ゲームデータを取り込み、Elasticsearch への接続を確立したので、すべてのピースをまとめてエージェントを構築し始めることができます。
エージェントの定義
1. backend/src/mastra/agentsディレクトリ内のindex.tsファイルに移動し、エージェント定義を追加します。次のようなフィールドを指定できます。
- 名前:フロントエンドで呼び出されたときに参照として使用されるエージェントの名前を指定します。
- 指示/システム プロンプト:システム プロンプトは、対話中に従うべき初期コンテキストとルールを LLM に提供します。これは、ユーザーがチャット ボックスを通じて送信するプロンプトに似ていますが、こちらはユーザー入力の前に表示されます。繰り返しになりますが、これは選択したモデルに応じて変わります。
- モデル:使用する LLM (Mastra は OpenAI、Anthropic、ローカル モデルなどをサポートしています)。
- ツール:エージェントが呼び出すことができるツール関数のリスト。
- メモリ: (オプション) エージェントに会話履歴などを記憶させたい場合。簡単にするために、Mastra は永続メモリをサポートしていますが、永続メモリなしで開始できます。
ツールの定義
backend/src/mastra/toolsディレクトリ内のindex.tsファイルに移動します。- 次のコマンドを使用して Zod をインストールします。
3. ツール定義を追加します。このツールを呼び出すときにエージェントが使用するメイン関数として、 comparePlayers.jsファイル内の関数をインポートすることに注意してください。Mastra のcreateTool()関数を使用して、 playerComparisonToolを登録します。フィールドには次のものが含まれます。
id: これは、エージェントがツールの機能を理解するのに役立つ自然言語による説明です。input schema: ツールの入力の形状を定義するために、Mastra は TypeScript スキーマ検証ライブラリであるZodスキーマを使用します。Zod は、エージェントが正しく構造化された入力を入力したことを確認し、入力構造が一致しない場合はツールが実行されないようにすることで役立ちます。description: これは、エージェントがいつ電話をかけてツールを使用するかを理解するのに役立つ自然言語による説明です。execute: ツールが呼び出されたときに実行されるロジック。私たちの場合、インポートしたヘルパー関数を使用してパフォーマンス統計を返します。
CORSを処理するミドルウェアの追加
CORS を処理するために、Mastra サーバーにミドルウェアを追加します。人生には避けられないことが 3 つあると言われています。死、税金、そして Web 開発者にとっては CORS です。簡単に言うと、クロスオリジン リソース共有は、フロントエンドが別のドメインまたはポートで実行されているバックエンドにリクエストを送信するのをブロックするブラウザのセキュリティ機能です。バックエンドとフロントエンドの両方をローカルホストで実行しているにもかかわらず、それらは異なるポートを使用するため、CORS ポリシーがトリガーされます。バックエンドがフロントエンドからのリクエストを許可するように、 Mastra ドキュメントで指定されているミドルウェアを追加する必要があります。
1. backend/src/mastraディレクトリ内のindex.tsファイルに移動し、CORS の設定を追加します。
origin: ['http://localhost:5173']- このアドレス(Vite のデフォルト アドレス)からのリクエストのみを許可します
allowMethods: ["GET", "POST"]- 許可される HTTP メソッド。ほとんどの場合、POST が使用されます。
allowHeaders: ["Content-Type", "Authorization", "x-mastra-client-type, "x-highlight-request", "traceparent"],- これらはリクエストで使用できるカスタムヘッダーを決定します
ステップ8: フロントエンドの統合
この React コンポーネントは、 @ai-sdk/reactのuseChat()フックを使用して Mastra AI エージェントに接続するシンプルなチャット インターフェースを提供します。このフックを使用して、トークンの使用状況やツールの呼び出しを表示したり、会話をレンダリングしたりします。上記のシステム プロンプトでは、エージェントに応答をマークダウンで出力するように要求しているため、 react-markdownを使用して応答を適切にフォーマットします。
1.フロントエンド ディレクトリにいる間に、useChat() フックを使用するために @ai-sdk/react パッケージをインストールします。
2. 同じディレクトリで、React Markdown をインストールして、エージェントが生成する応答を適切にフォーマットできるようにします。
3. useChat()を実装します。このフックは、フロントエンドと AI エージェントのバックエンド間のやり取りを管理します。メッセージの状態、ユーザー入力、ステータスを処理し、監視の目的でライフサイクル フックを提供します。渡すオプションは次のとおりです。
api:これは、Mastra AI エージェントのエンドポイントを定義します。デフォルトではポート 4111 に設定されており、ストリーミング応答をサポートするルートも追加する必要があります。onToolCall: これは、エージェントがツールを呼び出すたびに実行されます。エージェントがどのツールを呼び出しているかを追跡するために使用します。onFinish: エージェントが完全な応答を完了した後に実行されます。ストリーミングを有効にしても、onFinish各チャンクの後ではなく、完全なメッセージが受信された後に実行されます。ここでは、トークンの使用状況を追跡するためにこれを使用しています。これは、LLM コストを監視して最適化するときに役立ちます。
4. 最後に、 frontend/componentsディレクトリのChatUI.jsxコンポーネントに移動して、会話を行うための UI を作成します。次に、エージェントからの応答を適切にフォーマットするために、応答をReactMarkdownコンポーネントでラップします。
ステップ9: アプリケーションの実行
おめでとうございます!これでアプリケーションを実行する準備が整いました。バックエンドとフロントエンドの両方を起動するには、次の手順に従います。
- ターミナル ウィンドウで、ルート ディレクトリからバックエンド ディレクトリに移動し、Mastra サーバーを起動します。
2. 別のターミナル ウィンドウで、ルート ディレクトリからフロントエンド ディレクトリに移動し、React アプリを起動します。
3. ブラウザで次の場所に移動します。
チャット インターフェースが表示されるはずです。次のサンプルプロンプトを試してみてください。
- 「レブロン・ジェームズとステフィン・カリーを比較」
- 「ジェイソン・テイタムとルカ・ドンチッチのどちらを選ぶべきでしょうか?」
次はエージェントのさらなるインテリジェント化
アシスタントをよりエージェント的にし、推奨事項をより洞察力のあるものにするために、次のイテレーションでいくつかの重要なアップグレードを追加する予定です。
NBAニュースのセマンティック検索
プレーヤーのパフォーマンスに影響を与える要因は数多くありますが、その多くは生の統計には表示されません。負傷報告、ラインナップの変更、さらには試合後の分析などは、ニュース記事でしか見つけることができません。この追加のコンテキストを捉えるために、エージェントが関連する NBA の記事を取得し、その内容を推奨事項に組み込めるよう、セマンティック検索機能を追加します。
Elasticsearch MCPサーバーによる動的検索
MCP (モデル コンテキスト プロトコル) は、エージェントがデータ ソースに接続する方法の標準として急速に普及しつつあります。検索ロジックを Elasticsearch MCP サーバーに移行します。これにより、エージェントは、私たちが提供する定義済みの検索機能に頼るのではなく、動的にクエリを構築できるようになります。これにより、より自然な言語ワークフローを使用できるようになり、すべての検索クエリを手動で記述する必要性が軽減されます。Elasticsearch MCP サーバーとエコシステムの現在の状態の詳細については、こちらをご覧ください。
これらの変更はすでに進行中ですので、お楽しみに!
まとめ
このブログでは、JavaScript、Mastra、Elasticsearch を使用して、ファンタジー バスケットボール チームに合わせた推奨事項を提供するエージェント RAG アシスタントを構築しました。取り上げた内容:
- エージェント RAG の基礎と、AI エージェントの自律性と RAG を効果的に使用するツールを組み合わせることで、より繊細で動的なエージェントを実現できる方法について説明します。
- Elasticsearchとそのデータ ストレージ機能および強力なネイティブ集約により、それが LLM のナレッジ ベースとして優れたパートナーとなる理由について説明します。
- Mastraフレームワークと、それが JavaScript エコシステムの開発者にとってこれらのエージェントの構築をどのように簡素化するかについて説明します。
あなたがバスケットボールの熱狂的なファンであっても、AI エージェントの構築方法を検討している方であっても、あるいは私のようにその両方であっても、このブログが、始めるための基礎を提供できれば幸いです。完全なリポジトリはGitHubで入手できます。自由にクローンして改良してください。さあ、ファンタジーリーグで優勝しましょう!




