エラー発生時にAIが分析・改善提案・issue作成をしてくれるAissue gemを作りました

## この記事はなに？

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

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

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

僕は普段Rubyで開発しているので、記事で紹介した機能の中から

- エラー発生時にエラー分析と改善コードを生成してGitHub issueに出力する
- 要件を元にコードを生成し、GitHub issueに出力する

をRuby gemにしました。

## 作ったもの

GitHubリポジトリ

https://github.com/ysk91/aissue

Ruby Gem

https://rubygems.org/gems/aissue

## README和訳

機能についてはREADMEが分かりやすいと思うので、和訳をそのまま載せます。
（記事公開当時のREADMEです。）

:::note

# Aissue

Aissueは、生成AIを利用してコード作成のCLIを提供したり、エラーの原因と解決策をissueに記録する機能を持つRuby gemです。

Aissue gemを使用することで、次の2つの利点を得られます：

- エラー発生時に記録されたissueで原因と解決策を確認することで、不具合解決の処理速度を向上させます
- 自動生成されたコードを参照することで、開発速度を向上させます

## インストール

### Aissue gemのインストール

次のコマンドを実行してgemをインストールし、アプリケーションのGemfileに追加します：

```
$ bundle add aissue
```

Bundlerを使って依存関係を管理していない場合、次のコマンドを実行してgemをインストールしてください：

```
$ gem install aissue
```

### .envの作成

プロジェクトのルートに`.env`ファイルを作成し、以下を追加します：

```
OPENAI_API_KEY=sk-xxx
GPT_MODEL=gpt-4o-mini
GITHUB_TOKEN=ghp_xxx
GITHUB_OWNER=your-github-username
REPOSITORY=your-repository-name
AISSUE_LANG=if-you-want-to-specify-the-language(default: ja)
```

APIキーを以下から取得してください：
- OPENAI_API_KEY
  - https://platform.openai.com/settings/profile?tab=api-keys
- GITHUB_TOKEN
  - https://github.com/settings/tokens

## 使用方法

### エラー時のissue作成

begin/rescue構文のrescueブロックに `Aissue::Issue.rescue(e)` を挿入することで、エラーに基づいて原因と解決策を分析し、issueを作成できます。

例えば、次のように使用できます：

```ruby
begin
  100 / 0 # エラーが発生する可能性がある処理
rescue => e
  Aissue::Issue.rescue(e)
end
```

`rescue(e)` メソッドはissueのURLを返り値として出力します。
例えばSlack通知と組み合わせることでエラーに気付きやすくなるでしょう。

#### ToDo

現在、ここで提案されるコードは、発生したエラーに対して効果的な対処法とは言えません。
この問題は、今後使用可能になる予定のo1モデルを使用することで解決されることを期待しています。

### CLIツールによるコード生成

コンソールで`aissue start` を実行すると、CLIツールが起動します。
最初に、次の3つの入力を求められます：

- 作成するコードの要件
- データ（オプション）
- 対象スクリプトのパス（オプション）

対象スクリプトのパスが入力されていない（またはGitHubリポジトリに見つからない）場合、リポジトリに依存しない独立したコードを生成します。
コード生成後に、生成したコードを実行するか尋ねられます。
この時、**コードにリスクがなければ**、`y` を入力して結果を確認できます。

対象スクリプトへのパスが入力された場合は、そのスクリプトの修正コードが提案されます。

最後に、生成したコードをissueとして記録するか尋ねられます。`y` を入力すると、issueを作成し、URLを表示します。

#### 例

**対象スクリプトなしの場合**

```shell
% aissue start
Please enter your requirements: 配列の中身をすべて大文字にし、実行するたびに順番をシャッフルする関数を作りたい
Please enter the relevant data: ['dog', 'cat', 'bird']
Please enter the path of the target script:
def shuffle_and_upcase(array)
  array.map(&:upcase).shuffle
end

result = shuffle_and_upcase(['dog', 'cat', 'bird'])
result
Do you want to run this code?(y/n): y
[WARNING] Attempted to create command "shuffle_and_upcase" without usage or description. Call desc if you want this method to be available as command or declare it inside a no_commands{} block. Invoked from "/Users/yusukesonoki/work_mine/aissue/lib/aissue/cli.rb:57:in `start'".
["BIRD", "CAT", "DOG"]
Would you like to record this code in a GitHub Issue?(y/n): y
https://github.com/ysk91/aissue/issues/10
```

作成されたコードを実行すると、`[WARNING] Attempted to create command`で始まるメッセージが表示されることがあります。
これはdisc内で宣言されていない関数を使用した場合に発生する、Thorに依存した警告文です。
機能には影響しませんが、より良いアプローチをご存知の場合はプルリクエストを通じてご提案を歓迎します。

**対象スクリプトがある場合**

```shell
aissue_test % aissue start
Please enter your requirements: クラス化し、内部の処理を関数化。引数を分母にする
Please enter the relevant data: 
Please enter the path of the target script: test.rb
class Division
  def initialize(numerator)
    @numerator = numerator
  end

  def divide(denominator)
    puts 'ゼロ除算します'
    begin
      @numerator / denominator
    rescue ZeroDivisionError => e
      handle_error(e)
    end
  end

  private

  def handle_error(exception)
    # エラーハンドリングの処理をここに記述
    "エラーが発生しました: #{exception.message}"
  end
end

division = Division.new(100)
result = division.divide(0)
result
Would you like to record this code in a GitHub Issue?(y/n): y
https://github.com/ysk91/aissue_test/issues/4
```

プログラミングにAIを使用している皆さんは、生成されたコードをそのまま受け入れるのはリスクがあることをご存知かもしれません。
実装する前に、コードを十分にレビューすることをお勧めします。 

## 開発

リポジトリをチェックアウトした後、`bin/setup` を実行して依存関係をインストールします。それから `rake spec` を実行してテストを実行します。また、`bin/console` を実行して、対話式のプロンプトで試行錯誤することもできます。

このgemをローカルマシンにインストールするには、`bundle exec rake install` を実行します。新しいバージョンをリリースするには、`version.rb` のバージョン番号を更新してから、`bundle exec rake release` を実行します。これにより、バージョンのgitタグが作成され、gitコミットと作成されたタグがプッシュされ、`.gem` ファイルが [rubygems.org](https://rubygems.org) にプッシュされます。

## コントリビューティング

バグ報告やプルリクエストはGitHubの https://github.com/ysk91/aissue で歓迎されています。このプロジェクトは、安全で歓迎されるコラボレーションの場を目指しており、貢献者は [行動規範](https://github.com/[USERNAME]/aissue/blob/main/CODE_OF_CONDUCT.md) に従うことが求められます。

## ライセンス

このgemは、[MIT License](https://opensource.org/licenses/MIT) の条件の下でオープンソースとして利用可能です。

## 行動規範

Aissueプロジェクトのコードベース、課題トラッカー、チャットルーム、およびメーリングリストでやり取りするすべての人は、[行動規範](https://github.com/ysk91/aissue/blob/main/CODE_OF_CONDUCT.md) に従うことが期待されています。
:::

## 確認

テスト用のリポジトリを作成し、gemをインストールして実際に使用してみました。
すると、以下のようにissueを作成してくれました🎉
https://github.com/ysk91/aissue_test/issues

## 作ってみた感想

### CLIの呼び出しにつまづいた

`rake build`、`rake install`すると作成したgemがローカルで使用できるはずです。
しかし、いくら`% aissue start`しても起動しませんでした。
理由は2つありました。

- exeディレクトリに起動ファイルがなかった
  - デフォルトでは`bin`フォルダが存在し、そこに`issue`ファイルを作成していた
    - gemspecファイルをみると`spec.bindir = "exe"`とあり、起動ファイルは`exe`ディレクトリ配下に配置する必要があった
- 実行権限がなかった
  - 検証環境で`chmod +x exe/*`をする必要があった
  - これをすることで`rake release`したあとにgemをインストールしたリポジトリに実行権限が付与されるっぽい


### テストを作るのが難しい

本gemは部分的にAIの出力を使用しているため、テスト設計に悩みました。
というか、今もテストとしては不足していると思います。

というのも、以下の2つの問題があるからです。

- （今回実装したものは）AI出力の再現性がない
- テストの度にOpenAI APIを叩くと費用がかかる

AI関連のプロダクトを開発している方はどのようなテストを作っているのでしょうか？🤔


### Rubyの勉強になった

- `Aissue::Helper`のモジュール化
- CLIツールの開発方法

など、Rubyの基礎を改めて勉強することができました。
もしあなたが僕と同じくらいの歴でしたら、Railsを使用せずRubyだけで開発してみるのをおすすめします。

## コントリビュートお待ちしています。

正直自分でも実装に不安な点が多いです。
機能としては実用性が高いgemだと思っているので、みなさんからのPRやissueをお待ちしています。

https://github.com/ysk91/aissue

## 参考

- https://qiita.com/harashoo/items/1284fd5362c1a4f6c8a8
- https://github.com/mikik0/omochi