🍄

Minecraftプラグインで実現するOpenAI APIを活用した自然言語取引アシスタント

に公開

Minecraftプラグインで実現するOpenAI APIを活用した自然言語取引アシスタント

はじめに

OpenAI APIを使用して、自作のマーケットプラグインを自然言語で取引できるようにしました。
自然言語によるリクエストをAPIで処理して、コマンドを覚えなくても複雑な注文や取引を行えるようになります。

アシスタント機能の概要

Man10Marketのアシスタント機能は、プレイヤーが自然言語で市場操作を行えるようにするものです。例えば:

  • 「ダイヤモンドの価格を教えて」
  • 「鉄インゴットを10個買いたい」
  • 「エメラルドを5個、1個あたり100円で売りたい」
  • 「市場の動向を分析して」

といった自然な会話形式のリクエストを解釈し、適切な市場操作を実行します。

実際の動作デモ

以下の動画では、Man10Marketのアシスタント機能を実際に使用している様子を確認できます

https://youtu.be/zE-LEzmGUPM?si=e--KbY6YnPUf4TXU

処理フロー

  1. プレイヤーからのリクエストを受け取る
  2. OpenAI APIを使ってリクエストをタスクに分解
  3. タスクを実行
  4. 実行結果を元に残りのタスクを再評価
  5. 全てのタスクが完了するまで3と4を繰り返す
  6. 実行結果をOpenAI APIで自然言語に変換して返答

OpenAI APIによる自然言語処理

リクエストの分解

まずOpenAI APIを使用して、プレイヤーからの自然言語リクエストを具体的なタスクに分解しています。

private fun requestToTasks(player: Player, request: String): TaskList? {
    val prompt = """
        下記のリクエストを以下のJSONフォーマットに従って分割してください。
        各タスクには適切なタイプを指定し、必要なパラメータを含めてください。
        
        リクエスト:
        ```$request```
        
        タスクタイプ:
        - info_gathering: 情報収集タスク(例:アイテム価格の取得)
        - condition_check: 条件チェックタスク(例:価格が指定値以下か確認)
        - trade_execution: 取引実行タスク(例:アイテムの購入・売却)
        - result_report: 結果レポートタスク
        
        パラメータ詳細:
        1. info_gathering (情報収集)
           - {"item": "アイテム名"} - 特定アイテムの情報を取得
           - {} - 全アイテムの情報を取得
        
        2. condition_check (条件チェック)
           - {"item": "アイテム名", "price": 100} - アイテムの価格が指定値と比較
           - {"item": "アイテム名"} - アイテムの存在確認
        
        3. trade_execution (取引実行) - 必ず「action」パラメータを含めてください
           - 成行買い: {"action": "market_buy", "item": "アイテム名", "amount": 10}
           - 成行売り: {"action": "market_sell", "item": "アイテム名", "amount": 10}
           - 指値買い: {"action": "order_buy", "item": "アイテム名", "amount": 10, "price": 100}
           - 指値売り: {"action": "order_sell", "item": "アイテム名", "amount": 10, "price": 100}
        
        JSONフォーマット:
        ```json
        {
            "tasks": [
                {
                    "task": "タスク1の説明",
                    "type": "info_gathering",
                    "parameters": {"item": "ダイヤモンド"}
                },
                {
                    "task": "タスク2の説明",
                    "type": "condition_check",
                    "parameters": {"item": "ダイヤモンド", "price": 100}
                },
                ...
            ]
        }
        ```
        
        JSONのみを返してください。他の説明は不要です。
    """.trimIndent()
    
    // AIにリクエストを送信
    val response = sendRequest(player, prompt, false)
    
    try {
        // JSONレスポンスを抽出
        val jsonPattern = "\\{\\s*\"tasks\"\\s*:\\s*\\[.*?\\]\\s*\\}".toRegex(RegexOption.DOT_MATCHES_ALL)
        val jsonMatch = jsonPattern.find(response)
        
        if (jsonMatch != null) {
            val jsonStr = jsonMatch.value
            return TaskList.fromJson(jsonStr)
        } else {
            // 完全なJSONオブジェクトとしてパースを試みる
            return TaskList.fromJson(response)
        }
    } catch (e: Exception) {
        plugin.logger.warning("Failed to parse tasks from response: ${e.message}")
        return null
    }
}

プロンプトにリクエスト埋め込み、指定したフォーマットでタスク分解するようAIに指示を出します。

複雑なリクエストの処理

例えば、「ダイヤモンドが1000円以下なら10個買って、そうでなければ現在の価格を教えて」というような複雑なリクエストも処理できます。

このようなリクエストは以下のようなタスクに分解されます:

  1. 情報収集タスク: ダイヤモンドの価格を取得
  2. 条件チェックタスク: 価格が1000円以下かチェック
  3. 取引実行タスク: 条件が満たされれば購入を実行

タスクの動的な再評価

タスクの実行->実行結果をもとに残りのタスクを再評価という流れをタスク完了時に毎回行うことで、条件によって変わるようなタスクも実行可能にしています。
単一のリクエストに答えるAIを作るのは簡単ですが、複数の条件が関わるリクエストに対しては、このようにタスクの実行と再評価、という形をとることで実装が可能になります。

private fun reevaluateTasks(player: Player, originalRequest: String, completedResults: List<Map<String, Any>>, remainingTasks: List<TaskInfo>): List<TaskInfo>? {
    val prompt = """
        以下のリクエストと実行結果に基づいて、残りのタスクを再評価してください。
        必要に応じてタスクの追加、削除、変更を行ってください。
        
        元のリクエスト:
        ```$originalRequest```
        
        完了したタスクの結果:
        ${gson.toJson(completedResults)}
        
        残りのタスク:
        ${gson.toJson(remainingTasks)}
        
        残りのタスクを再評価し、以下のJSON形式で返してください。
        必要な変更がない場合は、そのまま残りのタスクを返してください。
        
        ```json
        {
            "tasks": [
                {
                    "task": "タスクの説明",
                    "type": "info_gathering",
                    "parameters": {"item": "アイテム名"}
                },
                ...
            ]
        }
        ```
        
        JSONのみを返してください。他の説明は不要です。
    """.trimIndent()
    
    // AIにリクエストを送信
    val response = sendRequest(player, prompt, false)
    
    try {
        // JSONレスポンスを抽出
        val jsonPattern = "\\{\\s*\"tasks\"\\s*:\\s*\\[.*?\\]\\s*\\}".toRegex(RegexOption.DOT_MATCHES_ALL)
        val jsonMatch = jsonPattern.find(response)
        
        if (jsonMatch != null) {
            val jsonStr = jsonMatch.value
            val taskList = TaskList.fromJson(jsonStr)
            return taskList?.tasks
        } else {
            // 完全なJSONオブジェクトとしてパースを試みる
            val taskList = TaskList.fromJson(response)
            return taskList?.tasks
        }
    } catch (e: Exception) {
        plugin.logger.warning("Failed to parse updated tasks from response: ${e.message}")
        return null
    }
}

この機能により、例えば「ダイヤモンドが安ければ買って、高ければ売る」といった条件分岐を含むリクエストも処理できます。

実際の使用例

例1: 価格確認

プレイヤー: 「ダイヤモンドの価格を教えて」

処理:

  1. リクエストを「ダイヤモンドの価格情報を取得する」タスクに変換
  2. 市場システムからダイヤモンドの価格情報を取得
  3. 結果を自然言語で返答

応答: 「ダイヤモンドの現在の価格は1個あたり500円です。売り注文の最安値は520円、買い注文の最高値は480円です。」

例2: 条件付き取引

プレイヤー: 「ダイヤモンドが400円以下なら20個買って、そうでなければ現在の価格を教えて」

処理:

  1. リクエストを複数のタスクに分解
    • ダイヤモンドの価格情報を取得
    • 価格が400円以下かチェック
    • 条件に応じて購入または情報提供
  2. タスクを順番に実行
  3. 結果を自然言語で返答

応答: 「ダイヤモンドの現在価格は500円で、指定された条件(400円以下)を満たしていないため、購入は行いませんでした。」

例3: 複数アイテムの取引

プレイヤー: 「ダイヤモンドを5個買って、鉄インゴットを10個売って」

処理:

  1. リクエストを複数のタスクに分解
    • ダイヤモンドを5個購入するタスク
    • 鉄インゴットを10個売却するタスク
  2. タスクを順番に実行
  3. 結果を自然言語で返答

応答: 「ダイヤモンドを5個、市場価格(500円/個)で購入しました。鉄インゴットを10個、市場価格(50円/個)で売却しました。」

まとめ

Man10Marketのアシスタント機能は、OpenAI APIを活用して自然言語によるMinecraft内での取引を可能にしています。この実装の特徴は:

  1. 自然言語のリクエストを具体的なタスクに分解
  2. 条件分岐を含む複雑なリクエストの処理
  3. 実行結果に基づくタスクの動的な再評価

これにより、プレイヤーは複雑なコマンドを覚える必要なく、自然な会話形式で市場を利用できるようになります。

※この記事で紹介したコードは、実際のMan10Marketプラグインの一部を抜粋・簡略化したものです。完全な実装については、以下のGitHubリポジトリを参照してください。

Discussion