ChatGPTにRedmineの起票を任せてみた

## 概要

開発PJやシステム運用PJにおいて、チームメンバ間のやりとりは、基本的にはチャットツールを利用しているのではないかと思います。また、タスク管理ツールを用意して、チャットの中で出た課題はチケットとして起票するという体制としているPJも多いと思います。

このとき、起票するメンバは以下の課題(課題とまではいかないものの面倒くささ)を抱えているかもしれません。
* チャットに記載されている文章をあらためて全部追うのが面倒くさい
* その文章を要約(＝必要なものをとりだし、不要なものを削除する)するのが面倒くさい
* 要約にプラスして、やるべきことを別途整理するのが面倒くさい

チケットの起票作業は、チャットに記載された内容をそのままコピペというわけにもいかないので、自動化することはできずにいました。しかし、ChatGPTの登場により、この課題を解消できるかもしれないので、使用感の確認も兼ねて、今回はChatGPTにRedmineの起票を任せる仕組みを作成してみました。本記事では、仕組みの構築方法・使ってみた結果をまとめます。

## 想定読者

* ChatGPTのAPIを利用してツール間連携を試してみたい方
* ChatGPTの要約スキルを確認してみたい方

## 目次

※構築手順についてChatGPT以外の部分についても詳細に記載しているので分量が多くなっています。ChatGPT部分の設定方法については`2-4.Python+Flask構築`をご確認ください。使用感について知りたい、という方は`3.実際に使ってみた`をご確認ください。

* 1.全体構成図
* 2.構築手順
  * 2-1.OpenAI API取得
  * 2-2.Redmine設定
  * 2-3.Mattermost設定
  * 2-4.Python+Flask構築
  * 2-5.Mattermost設定2
  * 2-6.動作確認
* 3.実際に使ってみた
  * 3-1.ケース1
  * 3-2.ケース2
  * 3-3.ケース3
* 4.まとめ

## 1.全体構成図

![構成図.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420547/12d0e70b-a6ec-516d-03c3-99cced117eeb.png)

以下のフローでRedmineへの起票処理を行います。
* ①Mattermostの特定のチャネル上に、要約したいスレッドの情報を記載すると、それをトリガーにPythonの処理が実行される。
* ②PythonはMattermostから要約したいスレッドの情報を受け取る。その情報をChatGPTに入力し、Redmineに起票するための要約を実行してもらう。
* ③Pythonは要約してもらった内容をRedmineに起票する。
* ④Pythonは起票したRedmineの情報をMattermostに投稿する。

構成図の通り、ChatGPTを除く全てのツールをDocker環境上にコンテナで立ち上げています。ツール間の内向き・外向きWebhookが通り、APIが叩けるのであればコンテナでなくても問題ないと思います。参考情報として、今回使用したMattermost,Redmineのバージョン情報を記載します。

```
Mattermost: 7.9.1
Redmine: 5.0.5.stable
```

## 2.構築手順
### 2-1.OpenAI APIキー取得

https://openai.com/api/ にアクセスし、`GET STARTED`を押下すると、OpenAIアカウント登録画面に遷移します。必須項目を記入してアカウントを作成した後、ユーザアイコンの`View API keys`を押下することでChatGPT APIを実行するためのキーを取得できます。Pythonプログラムの変数で使用するため、この値を控えます(①)。

### 2-2.Redmine設定

Redmineの構築手順については割愛します。ツール間連携に必要な設定方法を記載します。

#### 2-2-1.構成情報を確認する

Redmineに接続できるURLを確認します(ex. http://redmine.example.com )。Pythonプログラムの変数で使用するため、この値を控えます(②)。

#### 2-2-2.APIを有効化する

`管理` -> `設定`に遷移し、`API`タブから`RESTによるWebサービスを有効にする`にチェックを入れます。

#### 2-2-3.API tokenを確認する

右上の`個人設定`を押下し、右の`APIアクセスキー`の表示を押下すると、token情報を確認できます。Pythonプログラムの変数で使用するため、この値を控えます(③)。

#### 2-2-4.チケット起票先プロジェクトを作成する

`プロジェクト` -> `新しいプロジェクト`を押下し、以下情報を記載し`作成`を押下します。
* 名称: 任意のプロジェクト名を記載する
* 識別子: 任意のIDを記載する。Pythonプログラムの変数で使用するため、この値を控えます(④)。

### 2-3.Mattermost設定

Mattermostの構築手順については割愛します。ツール間連携に必要な設定方法を記載します。

#### 2-3-1.構成情報を確認する

Mattermostに接続できるURLを確認します(ex. http://mattermost.example.com:8065 )。
Pythonプログラムの変数で使用するため、ホスト名(mattermost.example.com)(⑤),スキーム(http)(⑥),ポート番号(8065)(⑦)を控えます。

#### 2-3-2.API実行用Botアカウントを作成する

まず、Bot Accountの作成を有効化します。

左上メニューから`System Console`を押下し、`INTEGRSTIONS` -> `Bot Accounts`を押下します。`Enable Bot Account Creation:`をtrueにし、下部のSaveを押下します。

次にBot Accountを作成します。

左上メニューから`integations`を押下し、`Bot Accounts`を選択します。右上の`Add Bot Account`を押下し、以下情報を記載し、`Create Bot Account`を押下します。
* Username: 任意の名前を記載する
* Role: System Adminを選択

Bot Account一覧から、作成したBotのTokenIDが表示されているので、控えます(⑧)。
また、アカウント作成時にToken情報が表示されるので控えます(⑨)。

#### 2-3-3.実行トリガー用チャネルを作成する

左タブの「+」ボタンから`Create New Channel`を押下し、任意のチャネル名を記載後、`Create channel`を押下します。

作成したチャネルを開き、上部の「i」マークを押下すると、projectのIDが表示されますので控えます(⑩)。

### 2-4.Python+Flask構築

今回はFlaskを使用して、PythonのWebアプリケーションを構築します。Docker環境上でコンテナとして起動します。

[こちらのGitHubリポジトリ](https://github.com/IhiraShunya/summarize-python) に今回作成したソースコードを格納しています(例外処理など作り込みが甘いので、改善要素は多くあります)。まずはこちらをcloneします。

環境変数の設定をします。`.env.sample`を参考に、`.env`ファイルを新規作成します。前節で控えた①〜⑩の値をファイル内に記載します。

```
OPENAI_API_KEY=XXX ①

REDMINE_HOST=http://X.X.X.X:YYYY ②
REDMINE_API_TOKEN=XXX ③
REDMINE_PROJECT_ID=XXX ④

MATTERMOST_HOST=X.X.X.X ⑤
MATTERMOST_SCHEME=[http or https] ⑥
MATTERMOST_PORT=YYYY ⑦
MATTERMOST_LOGIN_ID=XXX ⑧
MATTERMOST_TOKEN=XXX ⑨
MATTERMOST_CHANNEL_ID=XXX ⑩
```

コンテナイメージのビルドとコンテナのデプロイを行います。cloneしたディレクトリ上で実行します。

```
docker image build -t flask .
docker run -p 5001:80 -d --env-file .env flask
```

コンテナが起動すると、`http://[サーバIP]:5001/matter`でプログラムを実行できます(ブラウザで接続しても動作はしません)。このURLを後続のMattermost設定時に使用します。

#### 補足.今回設定したChatGPTの入力スクリプトについて

作成したソースコードから、ChatGPTへのsystem入力値についてのコードを抽出しました。

※system入力値は、ChatGPTにどういった出力をしてもらうかの設定のことを指します。

* ① Mattermost上の特定スレッド内の文章をuser入力値として、要約を出力させるコード
  * Redmineに起票するための要約なので、`現在の状況`,`やるべきこと`をそれぞれまとめるということを明示的に指示しています。要約なので、最大50字という制約もつけました。

    ```
        system = """与えられた会話の要点を、以下の観点でそれぞれ最大50字でまとめ、日本語で出力してください。```
        ・現在の状況
        ・やるべきこと
        ```"""
    ```

* ② ①で出力された要約をuser入力値として、さらに20字程度に要約した文を出力させるコード
  * Redmineのチケットタイトル用に、さらに要約した文章も作成してもらっています。

    ```
        system = """与えられた文章の要点を最大20字でまとめ、日本語で出力してください。"""
    ```

### 2-5.Mattermost設定2
#### 2-5-1.OutgoingWebhook(Mattermost -> Python)の作成

左上メニューから`integrations`を押下し、`Outgoing Webhooks`を選択します。
右上の`Add Outgoing Webhook`を押下し、以下情報を記載し、`Save`を押下します。

* Title: 任意の名前を記載する
* Description: 任意の説明文を記載する
* Content Type: application/json を選択
* Channel: 上記で作成したチャネル名を選択
* Trigger Words(One Per Line): http と記載
* Trigger When: First word starts with a trigger word を選択
* Callback URLs(One Per Line): 構築されたPythonに接続するURLを記載

デフォルトではセキュリティ設定でWebhookが正常に動作しないため、セキュリティ設定を変更し、MattermostからPythonへの通信を許可します。

左上メニューから`System Console`を押下し、`ENVIRONMENT` -> `Developer`を押下します。`Allow untrusted internal connections to:`にPythonに接続するホストIPを記載し、下部のSaveを押下します。

### 2-6.動作確認

Mattermost上でどのチャネルでも良いのでスレッドを作成し色々と文章を書きます(画像のチャット内容はフィクションです)。

![check1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420547/2f5b84bb-c4a4-9367-58c8-2ab486f73029.png)

スレッド内のどの投稿でも良いので「…」から`Copy Link`を押下し、その投稿のリンクを取得します。

2-3で作成した実行トリガー用チャネル上で、先ほど取得したリンクを貼り付けて投稿します。

しばらく待つと、2-3で作成したBotから「チケットを作成しました。」という投稿とともにRedmineのリンクが送られてきます。

![check2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420547/9a7431cf-2cdc-493c-72a7-867fe711f371.png)

リンクを押下すると、新規作成されたチケットが参照できると思いますので、スレッドの情報が要約されて起票されているか確認してください。

![check3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420547/74990932-78c4-f8f8-0cc2-1487ce1b887b.png)

## 3.実際に使ってみた
### 3-1.ケース1

まずは、ノイズも少なめで分量としても多くないスレッドで試してみました(ここでいうノイズは、チケットの起票に関係のない文章(挨拶、誤情報など)を指しています)。

![case1-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420547/b2dbb5c0-488b-fe4c-fee6-195d8f3d592e.png)

結果は以下となりました。

![case1-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420547/7ffff313-ccda-190a-13b0-e1eaf3f21fee.png)

ノイズを除去し、要点が整理できているように見えます。それだけでなく、チャットには記載していない「再発防止策を検討する」という文言まで追加されているのが気になりました。これは、「運用者としてやるべきことを自ら適切に記載してくれた。すごい。」と捉えるべきか、「勝手に追記しやがって」と捉えるべきか微妙なところですが、大きく外すことはなく整理できているということは間違いないと思いました。

### 3-2.ケース2

次に、分量は多くないがかなりノイズがあり、チケットとして残すべき情報もそんなにないスレッドで試してみました。内容を見て分かる通り、挨拶や感情表現が多くを占めた会話です。

![case2-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420547/e2fab7f2-bfdd-41cb-ca20-b4ac38c2bdad.png)

結果は以下となりました。

![case2-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420547/28107ac9-df30-9c72-e651-061025604871.png)

全体の大部分を占めるノイズをほぼ完全に除去しているのはすごいと思いました。「11時すぎ」という情報が抜けていたりするなどの課題はあるものの、適切な要約ができているように思いました。

### 3-3.ケース3

最後に、トラシューで試行錯誤したような分量の多いスレッドを要約してもらいました。「トラシューで試行錯誤するスレッド」自体もChatGPTに作ってもらいました。

それなりに分量が多いので畳んでいます。

<details><summary>作成したスレッドの文章</summary><div>

```
A: 障害が発生したようだね。
B: そうだね。どこから始めようかな？
A: 今回の場合、サーバーAへのSSH接続ができないというのだから、サーバーAの動作状況をチェックしてみようかな。
B: はい。サーバーAをリモートから確認してみるかな。
A: SSHで接続しようとしたけど、接続できなかったよ。
B: そうか。サーバーA上で動作しているサービスを確認してみようかな。
A: 確認してみたけど、サーバーAではサービスが正常に動作しているようだ。しかしSSH接続ができない状態だったので、ネットワーク環境を確認してみようかな。
A: ネットワーク環境を確認してみたけど、問題なさそうだよ。
B: そうか。他のサーバーと接続ができるか確認してみるかな。
A: はい。サーバーAから別のサーバーへのSSH接続を試してみたけど、接続できなかったよ。
B: そうだね。サーバーAのSSHの設定を確認してみようかな。
A: そうか。SSH設定を確認しようか。
B: えっと、SSH設定ってどこ？
A: SSH設定はサーバーAの設定ファイル内にあるよ。
B: ああ、そうなんだ。サーバーAの設定ファイルを開いてSSH設定を確認してみようかな。
A: はい。設定ファイルを開いてみたけど、SSH設定は正常になっているようだ。
B: そうか。サーバーAのファイアウォール設定を確認してみようかな。
A: ファイアウォールの設定を確認したけど、SSH接続を許可しているようだ。
B: そうか。それなら他の原因が考えられるな。
A: 仮想環境のネットワーク設定を確認してみようかな。
B: はい。仮想環境の設定を確認してみるかな。
A: 仮想環境の設定を確認したけど、特に問題はなさそうだよ。
B: そうなんだ。残念だけど、原因がわからないかな。他のサーバーと比較してみたらどうかな。
A: 他のサーバーと比較してみるのはいいかもしれないね。
B: はい。他のサーバーのSSH設定を確認してみようかな。
A: 他のサーバーのSSH設定を確認したけど、サーバーAとは異なる点はなかったよ。
B: そうなんだ。それなら、サーバーA上で問題が発生している可能性があるね。
A: そうだね。サーバーA上で動作しているプロセスを確認してみようかな。
B: はい。プロセスを確認してみるかな。
```

</div></details>

結果は以下となりました。

![case3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2420547/1dbb9922-e60c-d0e2-2a4c-3dc8badd1985.png)

ノイズの除去もできているし、調査した内容について要点は抑えられていると思います。「やるべきこと」の項目に、すでにやったことが記載されてしまっているため、「現在の状況」と「やるべきこと」の区別は難しかったのかもしれません。もう少し具体的に指定をするなどのカスタマイズしろがあるかなと思いました。

## 4.まとめ

予想以上の要約精度で驚きでした。一部落としてしまった要素など課題がありましたが、除去してほしくない要素については除去しないようChatGPTへの指示をカスタマイズすれば、さらに実用的になると感じました(時系列は要素に入れる・ログ系は別途整理する など)。

今回はツール間の連携はPythonを書いて実装しましたが、そのうち「Mattermostからスレッド取り出して、要約して、Redmineに起票しといて」といった依頼でもAIがこなしてしまうようになっていくのかな(そういったコード書いて、と依頼したら作ってくれそうなので半自動化は実現している？)、と思いました。引き続き調査・検証していこうと思います。