0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OpenAIのJavaSDKを使って、マインクラフトJava版のサーバーSpigotでChatGPTが使えるプラグインを作る

Last updated at Posted at 2025-06-15

はじめに

自分のマイクラサーバーでChatGPTやりたくて作りました。

OpenAIのJavaSDKを使って、Spigot用プラグインを作成します。

Java版のマイクラからこのように使えます。

2025-06-15_20.54.42.png

間違った回答を出してますけどね。

また、Function Callingを使って、ワールドの情報についても答えられるようにしました。

2025-06-15_21.37.10.png

自分のマイクラサーバー

はいってもすぐには使えません。

APIの利用料がかかるので、権限を付与されたプレイヤーしか使えません。

苦労した点

Java版マニュアルない!PythonとかJS版のマニュアル見て推測する!

使い方をChatGPTに聞いても古いのしか出てこない!

ポイント

マイクラ内からChatGPTと対話する。

最新のResponseAPIを使う。

OpenAIのJavaSDKを使う。

実装

Gradle

Gradleを使います。

build.gradle
dependencies {
    compileOnly 'org.spigotmc:spigot-api:1.21.4-R0.1-SNAPSHOT'
    implementation 'com.openai:openai-java:1.4.1'
}

リクエスト準備

クライアントを作成します。

OpenAIClient client = OpenAIOkHttpClient
        .builder()
        .apiKey("your-api-key")
        .build();

ResponseAPI用のパラメータを作成。

long maxTokens = 500;
String systemPrompt = "あなたはマインクラフトの相談役ですホニャラララ";
String prompt = "プレイヤーが入力した内容";
ResponseCreateParams.Builder builder = ResponseCreateParams.builder()
        .instructions(systemPrompt)
        .input(prompt)
        .model(ChatModel.GPT_4_1_MINI)
        .maxOutputTokens(maxTokens);

次に、会話の続きであるならば、前回のやりとりのIDを渡します。

if (lastChatId) {
    builder.previousResponseId(lastChatId);
}

Function Calling

Function Calling用の関数を作成し、パラメータに追加します。ここに挙げた関数は、ワールド名を引数にとり、ワールドの説明文を返すものになります。

builder.addTool(createTool());

public Tool createTool() {
    // WorldHelpはenum
    var worldList = WorldHelp.values();
    var worldListText = Arrays.stream(worldList).map(Enum::name).collect(Collectors.joining(", "));
    var param = FunctionTool
            .Parameters
            .builder()
            .putAdditionalProperty("type", JsonValue.from("object"))
            .putAdditionalProperty(
                    "properties",
                    JsonValue.from(Map.of("world_name", Map.of("type", "string", "enum", worldList))))
            .putAdditionalProperty("required", JsonValue.from(List.of("world_name")))
            .putAdditionalProperty("additionalProperties", JsonValue.from(false))
            .build();
    var func = FunctionTool
            .builder()
            .name(name)
            .description("Get world help. Available world is: " + worldListText)
            .strict(true)
            .parameters(param)
            .build();

    return Tool.ofFunction(func);
}

リクエスト送信

マイクラはシングルスレッドなので、別スレッドで以降のリクエスト処理を行います。

plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
    // 以降の内容
});

リクエストを送信します。

    var response = client.responses().create(builder.build());

トークン使用量などを取得します。ここでは載せませんが、これを保存して使用料制限などに使います。

    long inputTokens = response
            .usage()
            .stream()
            .mapToLong(ResponseUsage::inputTokens)
            .sum();
    long outputTokens = response
            .usage()
            .stream()
            .mapToLong(ResponseUsage::outputTokens)
            .sum();

ここで、Function Callingの呼び出しがあれば、それを実行します。

    var functionResults = response
            .output()
            .stream()
            .flatMap(item -> item.functionCall().stream())
            .map(toolCall -> /* ここで指定された関数を実行する */)
            .map(ResponseInputItem::ofFunctionCallOutput)
            .toList();

実行結果がもしあれば、再度リクエストを送信します。ここでもトークン使用量を取得します。

    if (!functionResults.isEmpty()) {
        builder.inputOfResponse(functionResults);
        builder.previousResponseId(response.id());
        response = client.responses().create(builder.build());

        inputTokens = response
                .usage()
                .stream()
                .mapToLong(ResponseUsage::inputTokens)
                .sum();
        outputTokens = response
                .usage()
                .stream()
                .mapToLong(ResponseUsage::outputTokens)
                .sum()
    }

そして、AIの返答を取得します。

    String result = response
            .output()
            .stream()
            .flatMap(item -> item.message().stream())
            .flatMap(message -> message.content().stream())
            .flatMap(content -> content.outputText().stream())
            .map(ResponseOutputText::text)
            .collect(Collectors.joining("\n"));

最後に、メインスレッドに処理を戻して、プレイヤーにAIの返答を表示します。

    plugin.getServer().getScheduler().runTask(plugin, () -> {
        sender.sendMessage(ChatColor.AQUA + "[AI] " + ChatColor.RESET + result);
    });

これで以上になります。

おわりに

苦労はしましたが、けっこう簡潔に作れました。

自分でAPIにリクエスト飛ばすよりかなり簡単だと思います。

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?