自然言語的なバリデーションを生成AIに判別させ、プルリクを自動更新する機能を実装しました

## これはなに？

先日、以下の記事を投稿しました。

https://qiita.com/ysk91_engineer/items/b047c879602d4fb8e62d

公開後、沢山のいいねやストックを頂いてとても嬉しかったです。

この記事を応用し、**生成AIに誹謗中傷の判別をさせてバリデーションする**という機能を実装しました。
さらに、従来通りNGワードのリストをymlで管理し、**AIが新たにNGだと判断したワードをymlに自動で追加してプルリクにプッシュする**機能も実装しました。

課題が残る実装ではありますが、興味と実験の結果を報告したいと思います。


### 解決した課題

一般的に誹謗中傷を禁止するバリデーションを実装する場合は、NGワードリストを作成し、入力がそのリストに部分一致するかどうかでバリデーションの判別をするかと思います。
しかし、この世にある誹謗中傷ワードのすべてをリストに含めることは至難の業です。
（すべてというのは言葉の通り「すべて」で、言語や時代を超えた誹謗中傷ワードです。）

NGリスト外の誹謗中傷ワードをキャッチするために、生成AIの出番です。

このモジュールは、生成AIによって誹謗中傷キーワードを判別させ、その結果をbool値で出力します。
その結果を受けてバリデーションのOK, NGを分岐させます。

![AIモジュールを組み込んだアプリの可能性.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2688241/a070d814-379b-2a8f-5950-5f9255cd0d38.png)

生成AIのAPI使用料を削減するため、従来通りのNGワードリストも使用します。
このキーワード集は、生成AIが新たにNGと判断したキーワードが次々に追加されていきます。
これは、アプリが**自ら成長している**と言えるのではないでしょうか？

## 作ったもの

https://github.com/ysk91/mva-demo/blob/main/foods.py

本機能は、入力された文章の中に日本食が含まれているかどうかをチェックします。
実運用では誹謗中傷をバリデーション対象にすることが多くなりそうですが、テスト実装でそれをおこなうとymlファイルを見るたびに悲しい気持ちになるので日本食にしました。

なので、入力に対するバリデーションとしては

- 日本食が含まれない → 通ってよし
- 日本食が含まれる → ここは通さねぇぜ（俺が美味しく食べるから）

と読み替えていただければよいと思います。

新規のNGワードは下記PRにpushされます。
https://github.com/ysk91/mva-demo/pull/42
このPRはPRコメントにあるように、ymlファイルを定期的にmainブランチにマージする運用を想定しています。
（GitHub Actionsとかで自動化できたら尚良し）


### 挙動

実際の挙動をお見せします。

#### 日本食じゃない場合（バリデーションOKの場合）

```
mva-demo % python foods.py
文章を入力してください: 皆のユートピア、ミラノ風ドリア
入力された文章に日本食は含まれていません。
```

この処理では、入力はymlファイル内のキーワードに部分一致せず、かつAIによる判別も通るため日本食ではないと判断されます。

#### 日本食で、リストに入っている場合（バリデーションNG、APIコスト削減）

```
mva-demo % python foods.py
文章を入力してください: ちらし寿司食べたい
入力された文章に日本食が含まれています。
```

この処理では、入力がymlファイル内の`寿司`に部分一致するため、日本食であると判別されます。

#### 日本食で、リストに入っていない場合（バリデーションNG）

```
mva-demo % python foods.py 
文章を入力してください: 屋台の焼きそばって美味しく感じるよね
入力された文章に日本食が含まれています。
Changes pushed to master/add_japanese_food
```

この処理では、ymlファイルのキーワードに部分一致はしませんがAIが日本食であると判断したため、日本食がふくまれている結果が出力されています。

さらに、`焼きそば`というキーワードを新たにymlファイルに追加してPRにプッシュする処理もおこなっています。

作成されたコミット

https://github.com/ysk91/mva-demo/pull/42/commits/00e9ac86da842102f96243dab44dbb4a0585a565


## 技術ポイント

### 生成AIによるバリデーション判別をbool値で返す

今回もOpenAI APIのJSONモードを使用しています。
JSONモードでは、出力をbool値に指定することも可能です。
試しに、下記のスクリプトを実行してみます。

```python:test.py
import modules.classification_foods as cf

food_name = input("食べ物の名前を入力してください: ")

response = cf.classify_food(food_name)
print(response)
is_japanese = response["is_japanese"]
print(isinstance(is_japanese, bool))
```

結果は以下のようになります。

```shell
mva-demo % python test.py
食べ物の名前を入力してください: 焼きそば
{'is_japanese': True, 'japanese_keyword': '焼きそば'}
True

mva-demo % python test.py
食べ物の名前を入力してください: コーヒー
{'is_japanese': False, 'japanese_keyword': ''}
True
```

`is_japanese`の出力がstring型ではなく、bool型であることが分かります。
また、is_japaneseがFalseのとき、`japanese_keyword`は空欄を返します。


### 成長するプロダクト（課題あり）

生成AIがNGと判断したキーワードは、NGワードをリスト化したymlファイルに追加され、指定したPRにプッシュされます。

![AIモジュールを組み込んだアプリの可能性 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2688241/20d3e3c1-88ee-3dbf-7a78-8b1c7bdc1994.png)

こんな風に、単語が次々とコミットされます。

![スクリーンショット 2024-10-20 9.08.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2688241/50d47138-1ce9-5081-8e09-1d29dd4eb0f9.png)

これはまるで、アプリが開発者の手から離れ、自身の力で成長しているように見えます。
こんな風に、ユーザーの利用によってアプリ自体の精度が向上したらと考えると、未来を感じませんか？

:::note warn
しかし現在の実装には改善が必要です。

稼働中の環境を一瞬他ブランチに移動させる処理を採用しており、危険性が伴います。

自分のトライとしては、[`specific_file_checkout`](https://github.com/ysk91/mva-demo/blob/main/modules/github_api.py)という処理を考えました。
しかしこれだとブランチを移動せず、指定のPRにpushすることができないため不採用としました。

このあたり、良いアイデアがある方はコメントで教えて頂けると幸いです。
:::

## まとめ

最近、[IMONを創る](https://www.ishiharashobo.jp/index/imon)という本を読みました。
この本は1989年に初出された、ぼのぼのの作者による長編エッセイです。

この本の中で「パソコンは生き物である」と書かれています。

私が前回の記事から続いて実装してきたものは、

- ユーザーの入力をもとに**アプリ自身が**次の処理を判断する
- ユーザーの入力の正否を**アプリ自身が**判別する
- **アプリ自身が**自らの機能を向上させる（issue, PRを作る）

という機能です。
AIを活用することで、ソフトウェアを少しだけ人間に近づけることができたのかもしれません。