GPT-4でSES営業メール（Gmail）を自動分類してみる

# 概要
こんにちは！
GPT君は文脈を読み取る力みたいなのが非常に優れているので、「React歴２年以上の案件メールだけ確認したい」みたいなことができるのは。と思ったのでSpringBootで試してみました。

# 前提
* Googleアカウント開設済み
* OpenAIのAPI keysを取得済み

# Gmail APIの設定
GmailのAPIを利用するため、以下の手順が必要になります。
* Google Cloudでプロジェクトの作成
* Gmail APIの有効化
* OAuth 2.0 クライアントの設定

## Google Cloudでプロジェクトを作成する
https://console.cloud.google.com/cloud-resource-manager にログインし、プロジェクトを作成します。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838672/c6f5d692-27c1-8c07-42ea-de0769e5db91.png)
プロジェクト名は適当にgmail-aiとかにしました。

## Gmail APIの有効化
https://console.developers.google.com/apis/library にアクセスし、「Gmail API」で検索→Gmail-apiを有効化する。
![キャプチャ2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838672/3734a5d5-5feb-ae8b-c5c1-aad3e3819396.png)

## OAuth 2.0 クライアントの作成
https://console.cloud.google.com/apis/credentials にアクセスし、認証情報を作成→OAuthクライアントIDを選択する。
![キャプチャ3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838672/217992ab-55b4-131b-8658-9355c09a1317.png)
ウェブアプリケーションを選択し、名前は適当に「gmailai」、承認済みのリダイレクトURIに「http://localhost:5555/Callback 」を入力→保存する。
![キャプチャ4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838672/569fac9d-5974-c123-b727-478eb623ab55.png)
秘密鍵をJSONでダウンロード。後に実装で使います。
![キャプチャ5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838672/df0cca02-a82a-0656-7385-8597764b0266.png)





# 実装
構成はこんな感じ
先ほど取得した秘密鍵JSONは`/resources`に配置。OpenAiのAPI_Keyはopen-api-secret.propatiesに書いています。
![キャプチャ6.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838672/555c776c-f492-7ce2-4912-0ca913eb37fd.png)

まずはbuild.gradleにTheoKanning/openai-javaを追加
```gradle
implementation group: 'com.theokanning.openai-gpt3-java', name: 'service', version: '0.12.0'
```
ちなみに
```gradle
implementation group: 'com.theokanning.openai-gpt3-java', name: 'client', version: '0.12.0'
```
もありますが、後に`OpenAiServiceクラス`を使うためにはサービスモジュールの必要があるため前者の方にして下さい。

今回はGMailのAPIを使うので以下も追加します。
```gradle
implementation 'com.google.apis:google-api-services-gmail:v1-rev20220404-1.32.1'
implementation group: 'com.google.api-client', name: 'google-api-client', version: '1.32.1'
implementation group: 'com.google.oauth-client', name: 'google-oauth-client-jetty', version: '1.32.1'
```
## 認証
認証のためのコーディングをしていきます。大体のコードは以下記事を参考にしています。

https://qiita.com/hsn/items/5779fc8b41d4aecaeb83
```java:/service/GmailAiService.java
// Json処理
    JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
    // スコープ
    List<String> SCOPES = Collections.singletonList(GmailScopes.GMAIL_READONLY);
    // 認証周りの設定ファイルの格納フォルダ
    String CREDENTIALS_FOLDER = "credentials";
    
    String path = "/client-secret.json";
    try (InputStream is = getClass().getResourceAsStream(path);
    InputStreamReader streamReader = new InputStreamReader(is, StandardCharsets.UTF_8);
    BufferedReader br = new BufferedReader(streamReader);
    ) {
      // HTTP通信
      NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
      System.out.println(HTTP_TRANSPORT.toString());
      GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, streamReader);
      GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY,
        clientSecrets, SCOPES)
                .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(CREDENTIALS_FOLDER)))
                .setAccessType("offline").build();
      // 認証を行う
      LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(5555).build();
      Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
      // Gmailのサービスを作成
      // アプリケーション名
      String APPLICATION_NAME = "gmailai";
      // Gmailにアクセス
      Gmail service = new Gmail.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential).setApplicationName(APPLICATION_NAME)
              .build();
    }
```
> まずは定数の説明
JSON_FACTORY : JSONを処理するためのオブジェクト。APIの返ってくるデータはJSON形式なので。
SCOPES : APIをつかってGmailにどこまでのアクセスを許可するか。GmailScopesクラスに定数で全部あるはずなのでそれから適切なものを選んでつかいましょう。今回やるようなことはGMAIL_READONLYを選んでおけば全部できるはずなので、それを選んでおきましょう。
CLIENT_SECRET_DIR : 秘密鍵のファイルのパス。今回は直下に「client_secret.json」という名前でいれたので、"client_secret.json"でOKです。
CREDENTIALS_FOLDER : 秘密鍵を使って取得した認証まわりのファイルを保存するフォルダのパス。上で作ったフォルダを設定。
HTTP_TRANSPORT : HTTP通信をするためのオブジェクト。

## メール本文の取得
先程取得したGmailのサービスのオブジェクトをもとに、以下の雑な処理でメール本文を取得し、String型に変換していきます。
```java:sharedService/GetGmail.java
public String getGMail(Gmail service) {
    // ユーザ
    String USER = "me";
    // メールIDを全て取得
    ListMessagesResponse messagesResponse = getMessagesResponse(service, USER);
    List<Message> messageList = messagesResponse.getMessages();

    // 今回は最新のメールだけ取得
    Message targetMessage = getFullMessage(service, USER, messageList.get(0));

    // 本文の取得(Base64)
    String bodyBase64 = targetMessage.getPayload().getParts().get(0).getBody().get("data").toString();
    // Base64からデコード(byte列)
    byte[] bodyBytes = BaseEncoding.base64Url().decode(bodyBase64);
    // byte列からStringに変換
    try {
      String body = new String(bodyBytes, "UTF-8");
      // 本文を返却
      return body;
    } catch (Exception e) {
      e.printStackTrace();
      return "";
    }
}
```
ちなみに取得した本文サンプルはこんな感じ。かなり適当です。
```text
突然のメール失礼いたします。
株式会社ホゲホゲ営業担当の斎藤と申します。

貴社の公式サイトを拝見し、SESの人員を募集しているとのことでしたので、ご紹介したいエンジニアがございます。
【名前】
田中　太郎
【経歴】
2018年　株式会社banに入社　プログラマーとして勤務
2022年　Boo株式会社に転職  プロジェクトマネージャーとして従事

【スキル】
Java歴　3年
React歴　1年
Vue歴　2年
Rust歴　1年

いずれも実務での経験年数になります

ご興味がありましたら、こちらのメールアドレス（abc＠mail .com）にご返信ください。
```

## GPT君の出番
やっとGPT君の出番が回ってきました。
今回はメール本文から、田中太郎さんのスキルを抽出してもらいましょう。
```java:GptSharedService.java
public void summaryForGpt(String mailBody) { // 先程取得したメール本文
    ResourceBundle config = ResourceBundle.getBundle("open-api-secret"); // resources/open-api-secret.properties
    String token = config.getString("token");
    String prompt = "次の文章は営業メールです。文章の内容を読み取って、紹介されている人物の持っているスキルを全て抽出して下さい。\n" + mailBody;
    OpenAiService service = new OpenAiService(token);
    ChatCompletionRequest completionRequest = ChatCompletionRequest.builder()
    .messages(Arrays.asList(new ChatMessage("system", prompt)))
    .model("gpt-4")
    .maxTokens(256)
    .stream(false)
    .build();
    List<ChatCompletionChoice> completionResult = service.createChatCompletion(completionRequest).getChoices();
    for (ChatCompletionChoice choice : completionResult) {
      System.out.println(choice.getMessage().getContent());
    }
  }
```
```:出力結果
Java、React、Vue、Rust
```
ちなみにメール本文の`【スキル】`を`【技術】`とかにしてもちゃんと取得してくれました。

# できそうなこと
メールの受信時にメール本文のGPTに読み込ませる→任意の項目を抽出（スキルや職種など）→DBに保存。みたいなことすれば、高度なメールフィルター機能をもつサービスとか作れそうだな～と思いました。
あとは、メールの内容的にスパムっぽいやつは全自動でゴミ箱へシュートする機能とかもできそうな気がします。

他にもこんなことできそう!みたいな案がありましたら、コメント頂けると嬉しいです!!

# 参考文献

https://qiita.com/hsn/items/5779fc8b41d4aecaeb83

https://github.com/TheoKanning/openai-java