Open Libertyで試すChatGPT連携

## はじめに
23年は生成AIが大きなトレンドとなっています。流行のChatGPTも業務アプリケーションとシームレスに繋げて使えると可能性が広がるのではないでしょうか。
ChatGPTはチャットウィンドウだけではなくAPI経由で呼び出すことが可能となっています。
提供元のOpenAIでは開発者向けサンプルは主にPythonベースとなっており、Javaサンプルは提供されていないため「Javaからでは利用できないよトホホ」と思っていませんか？
WebAPIは言語を問わず利用可能ですので、勿論Javaからでも呼び出し可能です。
ここでは、Javaを使用したChatAPI呼び出しについて、Open Libertyでの実装例を見ていきます。

## 今回の内容
[Open LibertyでつくるHelloWorldタイムアタック](https://qiita.com/mi-ta/items/d63b8c169d07cd15431d)で作成したHelloWorldプログラムを、
**ChatGPTに「ランダムに選んだ人間の言語で Hello World に相当する言葉を、言語の名前とともに教えてください。」と聞いた結果を返す**
ように変更します。

## OpenAIのChat機能について
OpenAIのサイトで実装に関連する情報は次のあたりです。
[Guide](https://platform.openai.com/docs/guides/chat)
[API reference](https://platform.openai.com/docs/api-reference/chat)

「あなたはXXです」と前置きするためのsystemロールはGPT3.5であまり有効ではないと記載されています。
実際試してみたところあまり結果に反映されていなかったので、指示はuserロールのメッセージに集約したほうがよさそうです。

API呼び出しのためには、OpenAIに登録のうえAPIキーを取得する必要があります。
APIキーの取得手順は[こちら](https://qiita.com/kotattsu3/items/b936a65d173a5d39dad0)の投稿を参考にしていただくとよろしいかと思います。

API呼び出しは従量制となっています。料金表は[こちら](https://openai.com/pricing)です。
23年5月時点で $0.002/1Kトークンなので検証で扱うレベルでは特に気にならないかもしれませんが、本番環境で利用する場合には見積もりを忘れずに。

## つくってみる
#### 全体的な構成について
JAX-RSを使用してAPI呼び出しを行います。
[別投稿](https://qiita.com/mi-ta/items/d63b8c169d07cd15431d)で作成したHelloWorldアプリに、ChatAPI呼出しのクラスを追加していきます。

```
src\main\java\jp\sample\rest
  ChatConsumer.java    API呼び出しの主ロジック
  ChatRequest.java    リクエストメッセージ
  ChatResponse.java    レスポンスメッセージ
  ChatResponseChoice.java    レスポンスメッセージの子要素
  Hello.java
```
#### ライブラリ追加
サンプルコードをシンプルにするためにLombokを使用します。
pom.xmlのdependenciesセクション内に、次の定義を追加します。
```
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>
```

#### メッセージクラス
リクエストとレスポンスの内容を格納するクラスを定義します。
アクセッサメソッドはLombokが補完します。
```java:ChatRequest.java
package jp.sample.rest;

import java.util.HashMap;
import java.util.ArrayList;

@lombok.Data
public class ChatRequest {
    private String model;
    private ArrayList<HashMap<String, String>> messages;
}
```
```java:ChatResponse.java
package jp.sample.rest;

import java.util.ArrayList;

@lombok.Data
public class ChatResponse {
    private String id;
    private String object;
    private String created;
    private ArrayList<ChatResponseChoice> choices;
}
```
```java:ChatResponseChoice.java
package jp.sample.rest;

import java.util.HashMap;

@lombok.Data
public class ChatResponseChoice {
    private int index;
    private HashMap<String, String> message;
    private String finishReason;
}
```

#### RESTクライアントクラス
問合せに必要な内容をリクエストメッセージに詰め込み、API呼び出しを行います。
結果はレスポンスメッセージに展開されるため、ここから必要な情報を取り出します。
```java:ChatConsumer.java
package jp.sample.rest;

import java.util.HashMap;
import java.util.ArrayList;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;

public class ChatConsumer {

    public static String chatReq() {

        try(Client client = ClientBuilder.newClient();) {

            String targetURL = "https://api.openai.com/v1/chat/completions";
            ChatRequest req = new ChatRequest();
            req.setModel("gpt-3.5-turbo");

            HashMap<String, String> msg = new HashMap<String, String>();
            msg.put("role", "user");
            msg.put("content", "ランダムに選んだ人間の言語で Hello World に相当する言葉を、言語の名前とともに教えてください。");
            ArrayList<HashMap<String, String>> msgs = new ArrayList<HashMap<String, String>>();
            msgs.add(msg);
            req.setMessages(msgs);

            ChatResponse res = client
                .target(targetURL)
                .request(MediaType.APPLICATION_JSON)
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + "あなたが取得したAPIキー")
                .post(Entity.entity(req, MediaType.APPLICATION_JSON), ChatResponse.class);

            return res.getChoices().get(0).getMessage().get("content");

        } catch (Exception e) {
            e.printStackTrace();
            return "エラーが発生しました。";
        }
    }

}
```
#### ブラウザからのリクエスト受け取り
固定文字列を返していた部分を、上記クラスで取得した結果を返すように修正します。
```java:Hello.java
package jp.sample.rest;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@Path("hello")
public class Hello {

    @GET
    public String helloResponse() {
        //return "Hello World!!";
        return ChatConsumer.chatReq();
    }
    
}
```

## 実行してみる
例えば、次のような結果が返ってきます。世界は広いですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536604/0396ee70-02c1-6705-4b62-4cb2839ea6c4.png)

## まとめ
ChatのAPI呼び出しがJavaでも実行できることが確認できました。
Javaの業務アプリケーションにAIを組み込む可能性、を少しでも見出していただけたら幸いです。

業務で利用するためには、企業内に蓄積されたデータと組み合わせて精度の高い結果を返す工夫も必要です。
IBMのWatson製品と組み合わせることで、企業内データとシームレスに連携することも可能です。
業務品質のAI活用を想定されている方は、こちらも検討してみてください。