生成AIをモジュール化したらアプリの新しい可能性が見えてきた

## この記事は何？

最近Pythonを勉強しはじめたので、学習がてらAIを組み込んだアプリを作ってみました。

その結果、技術的に面白いものができたので共有します。

結論として、AIをモジュール化することで、

- **ユーザーの自由度の高い入力に対して後続処理を出し分ける**
- **エラー発生時にエラー分析と改善コードを生成してGitHub issueに出力する**
- **要件を元にコードを生成し、GitHub issueに出力する**

を実現することができました。


## 今回実装したもの

今回実装したリポジトリは以下になります。

https://github.com/ysk91/mva-demo

ローカルで動かす場合はREADME.mdを参考に環境構築をしてください。

:::note
本記事はAIをモジュール化することの可能性を紹介することが目的です。
著者はPython初心者ですので、**コードの書き方**の評価はPRにてお待ちしています。
:::

機能を紹介する前に、一般的なLLMアプリと生成AIをモジュール化したアプリの違いを説明します。

### 一般的なLLMアプリ

![AIモジュールを組み込んだアプリの可能性 (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2688241/9be3925f-14eb-cf1a-1064-0c1dcb1ad6ff.png)

現在目にする生成AIプロダクト（LLMアプリ）は、ユーザーからの入力をプロンプトとして使用し、そのプロンプトをもとに生成した生成AIの出力をそのままユーザーに提供するものが多いです。
しかし、生成AIの出力は自由度が高く不安定です。
そのため、以下の可能性を危惧する必要があります。

- 処理を設計側でハンドリングできない
- プロンプトインジェクションの危険性
- 自由度の高い出力に対して責任を負う必要がある

この問題を解決するために、生成AIをモジュール化したアプリを作ってみました。

### 生成AIをモジュール化したアプリ

![AIモジュールを組み込んだアプリの可能性.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2688241/cfe8a887-3bc8-9b20-2924-aa4f23977628.png)


「生成AIをモジュール化する」とは、生成AIを処理全体における機能のひとつとして実装することで全体の処理を実現する方法です。


モジュール化した生成AIをアプリに組み込むことで、プログラムがその後の処理をハンドリングできるようになります。
これにより、ユーザーからの自由度の高い入力に対しても安全に対応することが可能となります。


## 機能紹介

それでは、本アプリに実装した機能を1つずつ見ていきましょう。

### ユーザーの入力をAIが判別し、処理を出し分ける

![AIモジュールを組み込んだアプリの可能性 (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2688241/73542fa2-af9c-c383-4fe3-154cbd502af8.png)

実装内容

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


#### 実際の挙動

`emo.py`を実行します。

このプログラムは、ユーザーが入力した文章をAIが感情分析します。
感情分析の結果に基づき、その後の処理をif文で出し分けるものになっています。

```zsh
mva-demo % python emo.py
あなたの感情を表現する文を入力してください: プリンが美味しかった
あなたは嬉しい気持ちですね！

mva-demo % python emo.py
あなたの感情を表現する文を入力してください: 夏の終わりかと思ったらやっぱり暑いやんけ
あなたは怒っていますね！
```

今は結果を文字列として出力させるだけですが、以下を実現することも可能です。

- 呼び出す関数・モデルを出し分ける
- データ操作するテーブルを出し分ける
- データ作成時に特定の項目に値を付与する
  - `status: todo`, `priority: High` など
- 自然言語的バリデーションを設定する（誹謗・中傷など）
  - 返り値を True / False にする


#### 技術的ポイント

OpenAI APIには、出力をJSONに固定させる**JSONモード**という機能があります。
これを活用することで、AIの不安定な出力をある程度制限することができます。

:::note
JSONモードを使用するには、プロンプト内にJSONで出力するように指示する必要があります。
詳細は下記の`Hack`を御覧ください。
https://github.com/ysk91/mva-demo/blob/main/modules/openai_api.py
:::


### エラー発生時に原因を特定し、解決策をissueに残す

![AIモジュールを組み込んだアプリの可能性 (7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2688241/0e51c1b7-c08b-909e-132d-e009f84c1ae8.png)

実装内容

https://github.com/ysk91/mva-demo/blob/feature/emo.py/modules/issue.py

このモジュールは、エラーハンドリング時にエラーオブジェクトと該当スクリプトをAIに渡し、分析結果をGitHub issueに残します。

例えば、`emo.py`を実行し、ゼロ除算をする指示を入力します。
このプログラムは、ユーザーからゼロ除算の指示を受けると、`100 / 0`を実行します。
その結果 `ZeroDivisionError: division by zero` が発生するので、以下 `expect`節が走ります。

```python
import modules.issue as issue #  モジュール呼び出し

try:
#  正常時の処理
except Exception as e:
    issue.rescue(e)
```

issueモジュール内の`rescue`関数は、

- 発生したエラー内容
- エラーが発生したスクリプト
- その他モジュールのスクリプト

を受け取り、エラーを解決するためのコードを生成します。
その結果を、GitHub issueに作成します。

実際に作成されたissue

https://github.com/ysk91/mva-demo/issues/34

#### 技術的ポイント

エラーを分析させるプロンプトには、エラーメッセージと現在のスクリプトの他に、モジュールの全てを渡しています。
アプリにとってモデルは、計算処理やデータ操作をする役割を持ちます。
エラーを解決するために修正が必要になる箇所をAIに特定させ、それを元にissueを作成します。

#### 今後実装したいこと

実はこの機能はまだ完全なものではありません。
今後実装したい内容を残しておきます。

- 非同期での実行
  - AIの処理に時間がかかるため
- 提案コードの精度向上
    - o1モデルを使用するのが良さそう
- PRまで全自動で作成する
    - 新規ブランチを切る
    - スクリプトを修正する
    - GitHubにpushする
    - PR作成
    - CodeRabbitでコメント、レビュー作成
      - https://qiita.com/ysk91_engineer/items/47cff12d0625f003d50d


### 要件を伝えると実装方法をissueに残す

![AIモジュールを組み込んだアプリの可能性 (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2688241/4f461bcc-0d66-4d8e-7607-26e1e4d7cfcf.png)

実装内容

- スクリプト
  - https://github.com/ysk91/mva-demo/blob/main/mva.py
- モジュール
  - https://github.com/ysk91/mva-demo/blob/main/modules/issue.py

`mva.py`を実行します。
このスクリプトを実行すると、要件、データ、対象スクリプトのパスの入力を要求されます。
この機能はレベルが2つに分けられます。

**単体処理のコード作成**

まずは、単体処理のコード作成です。

要件とデータを入力し、スクリプトを入力しないとアプリに依存しない形で要件を満たすコードを生成します。
コード生成後にコードを実行するかどうかを聞かれるので、`y`と答えると作成したコードの実行結果を見ることができます。
それを元にissueを作成するかどうかを聞かれるので、`y`と答えるとissueが作成されます。

実際の挙動は以下です。

https://github.com/ysk91/mva-demo/issues/20

**既存のコードに対する修正**

要件と、それを適用させるスクリプトのパスを入力します（データの入力は任意）。
すると、スクリプトに対して要件を満たすためのコードを生成します。
issueを作成するかどうかを聞かれるので、`y`と答えるとissueが作成されます。

実際の挙動結果は以下です。

与えたスクリプト: https://github.com/ysk91/mva-demo/blob/feature/add_coderabbit/main.py

実際に作成されたissue:

https://github.com/ysk91/mva-demo/issues/24

## これって何なの？

さて、本アプリの機能を紹介したところで、ひとつの問いが浮かびました。
**これって何なの？**

- ユーザーの入力に対し、アプリ自身が処理を考えて出し分ける
- エラーや要件によってコードと共にissueを立て、自ら成長する

まるで生き物のようなアプリケーション。
**これって、、何なの？**

頭をよぎったのは、MVAです。

MVAとは、現在普及しているMVCアーキテクチャのControllerをAIに置き換えたものです。
ただし、MVAはモデルをFunctionCallingでRAG的に選択するので、今回の実装はMVAではなさそうです。

どなたかこの手法に名前を付けてください。

## (10/11追記)登壇依頼お待ちしてます

本件について、登壇させて頂けるイベントを探しています。
実際にアプリが動く様子も見せられます。

人前に立つことや、初対面の方と話すことには慣れています。
イベント運営している方は是非下記からお声がけ頂けると幸いです！
https://twitter.com/ysk91_engineer

## （10/19追記）エラー発生時にAIが分析・改善提案・issue作成をしてくれるAissue gemを作りました

記事内で紹介したエラー処理機能をRuby gemにして公開しました！
よかったら試してみてください！

https://qiita.com/ysk91_engineer/items/18127af3d2bd25645593

## 参考

- https://platform.openai.com/docs/guides/structured-outputs/json-mode
- https://platform.openai.com/docs/guides/reasoning
- https://zenn.dev/headwaters/articles/1fe12f38e95056
- https://tech-blog.cloud-config.jp/2024-02-22-microsoft-ai-tour-mva-architecture