「ChatDev」でソフトウェア開発をお願いしてみた

# 1. ChatDevとは
ChatDevは、ChatGPTを利用してCEO、CTO、プログラマ、テスターなど、さまざまな役割を持つさまざまなインテリジェントエージェントを仮想的に再現したソフトウェア開発のツールです。
現実世界のソフトウェア開発のように、エージェントにそれぞれ役割を分担させ、コミュニケーションを取りながらソフトウェア開発のステップを実施することにより高精度なソフトウェア開発を実現することができます。
ユーザが作成したいアプリケーションの概要をChatDevに投げると、後はChatDev内のエージェントが設計やコーディング、テスト、ドキュメント作成などを自動で行ってくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/cc3d6e95-e2a9-8d50-7e17-de31a460f47e.png)
※ 画像は論文より引用

https://arxiv.org/abs/2307.07924

https://github.com/OpenBMB/ChatDev

# 2. ChatDevの使い方
ここではWindows環境でのChatDevの実行方法について紹介します。
## 2.1. Gitリポジトリのクローン
```
$ git clone https://github.com/OpenBMB/ChatDev.git
```
## 2.2. Python環境のセットアップ
Python 3.9以上のバージョンが必要です。
```
# 仮想環境作成
$ conda create -n ChatDev_conda_env python=3.9 -y
# 仮想環境の起動
$ conda activate ChatDev_conda_env
```
## 2.3. 依存ライブラリのインストール
```
$ cd ChatDev
$ pip install -r requirements.txt
```
## 2.4. OpenAI API キーの設定
```
# <your_OpenAI_API_key>には自身のOpenAI API Keyを設定
$ set OPENAI_API_KEY=<your_OpenAI_API_key>
```
## 2.5. ソフトウェアの開発
```
# <ソフトウェアのアイデア>には作成してほしいソフトウェアのアイデアを英語で入力
# <プロジェクト名>にはこのプロジェクトの名前を英語で入力
$ python3 run.py --task "<ソフトウェアのアイデア>" --name "<プロジェクト名>"
```
ソフトウェア開発のコマンドには主に以下のオプションが存在します。
- `--model "GPT_4"` : デフォルトは`gpt-3.5-turbo-16k-0613`ですが、このオプションに`GPT_4`を指定することで`gpt-4`を利用できます。
- `--config "Art"` : 開発するソフトウェアのUIなどで利用する画像やアイコンを生成し、より高度なUIを持ったソフトウェアを開発することが可能です。

# 3. ChatDevでお絵描きアプリを作ってみた
インストールが完了したので早速アプリを開発してもらおうと思います。
作ってもらうのはなんとなくお絵描きアプリにしました。
`gpt-4`に普通に指示を投げる場合と比較してどのようなアプリができるのか見ていこうと思います。
比較の都合上、言語は明示的にPythonで統一させます。

## 3.1. `gpt-4`にそのまま指示を投げて作成したお絵描きアプリ
以下のプロンプトを直接`gpt-4`に投げました。
```
prompt="Please develop a feature-rich drawing app in Python."
```
生成してもらったソースコードはこちらです。
```python:main.py
from tkinter import *
from tkinter import ttk, colorchooser, messagebox, filedialog

class DrawingApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Drawing App")
        self.root.geometry("800x600")

        # Variables
        self.color = "black"
        self.pen_size = 2

        # Canvas
        self.canvas = Canvas(self.root, bg="white")
        self.canvas.pack(fill=BOTH, expand=True)

        # Toolbar
        self.toolbar = Frame(self.root)
        self.toolbar.pack(pady=5)

        # Color Picker
        self.color_button = Button(self.toolbar, text="Pick Color", command=self.pick_color)
        self.color_button.pack(side=LEFT, padx=5)

        # Brush Size
        self.size_scale = ttk.Scale(self.toolbar, from_=1, to=10, value=self.pen_size, command=self.change_size, orient=HORIZONTAL)
        self.size_scale.pack(side=LEFT, padx=5)

        # Clear Canvas
        self.clear_button = Button(self.toolbar, text="Clear", command=self.clear_canvas)
        self.clear_button.pack(side=LEFT, padx=5)

        # Save Canvas
        self.save_button = Button(self.toolbar, text="Save", command=self.save_canvas)
        self.save_button.pack(side=LEFT, padx=5)

        # Binding mouse events
        self.canvas.bind("<B1-Motion>", self.draw)
        self.canvas.bind("<ButtonRelease-1>", self.reset)

    def pick_color(self):
        color = colorchooser.askcolor(initialcolor=self.color)
        self.color = color[1]

    def change_size(self, event):
        self.pen_size = event

    def draw(self, event):
        x1, y1 = (event.x - self.pen_size), (event.y - self.pen_size)
        x2, y2 = (event.x + self.pen_size), (event.y + self.pen_size)
        self.canvas.create_oval(x1, y1, x2, y2, fill=self.color, outline=self.color)

    def reset(self, event):
        self.canvas.delete("all")

    def clear_canvas(self):
        self.canvas.delete("all")

    def save_canvas(self):
        file_path = filedialog.asksaveasfilename(defaultextension='.png')
        if file_path:
            self.canvas.postscript(file=file_path, colormode='color')
            messagebox.showinfo("Success", "Canvas saved successfully!")

root = Tk()
drawing_app = DrawingApp(root)
root.mainloop()
```
このプログラムを実行すると以下のような画面になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/d3ff537d-5171-5149-efd6-c64bc9a950a6.png)
色変更の画面はこちらです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/3e0887df-efe8-ed14-21f2-b685e8c5a74a.png)

実際に線を引いてみると線ではなく点がぽつぽつと現れるだけです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/a81650bf-5fa8-4178-8b42-f11b88a2c0cb.png)

色変更や太さ変更、保存機能などがありますが、線(点？)はマウスから手を離すと消えてしまいます。また太さ変更でエラーが起きて実行できませんでした。さらには保存した画像ファイルは壊れていて開けないという、お絵描きアプリとしてはよろしくないような結果となりました。(気になる方はコードをコピペして動かしてみてください)

## 3.2. ChatDevで作成したお絵描きアプリ
以下のコマンドでお絵描きアプリを作成してもらいます。
```
$ python run.py --task "Please develop a feature-rich drawing app in Python." --name "chatdev_test" --model "GPT_4"
```
:::note warn
`gpt-4`だとトークン数の上限に引っかかってしまいましたがソースコードの出力までは完了できました。
:::

コマンドを実行すると`WareHouse`フォルダにプロジェクトのフォルダが作成され、最終的な成果物はここに格納されます。
生成してもらったソースコードはこちらです。
```python:main.py
'''
main.py
'''
import tkinter as tk
from tkinter import colorchooser
from tkinter import ttk
from tkinter import Frame, Label, Menu, Canvas, BOTH
class DrawingApp:
    def __init__(self, master):
        '''
        Initialize the DrawingApp with a tk.Tk() instance.
        '''
        self.master = master
        self.color_fg = 'black'
        self.color_bg = 'white'
        self.old_x = None
        self.old_y = None
        self.penwidth = 5
        self.drawWidgets()
        self.c.bind('<B1-Motion>',self.paint) #drwaing the line 
        self.c.bind('<ButtonRelease-1>',self.reset)
    def paint(self,e):
        '''
        Draw on the canvas.
        '''
        if self.old_x and self.old_y:
            self.c.create_line(self.old_x,self.old_y,e.x,e.y,width=self.penwidth,fill=self.color_fg,capstyle=tk.ROUND,smooth=True)
        self.old_x = e.x
        self.old_y = e.y
    def reset(self,e):    
        '''
        Reset the line when the mouse button is released.
        '''
        self.old_x = None
        self.old_y = None      
    def changeW(self,e): 
        '''
        Change the width of the pen.
        '''
        self.penwidth = e
    def clear(self):
        '''
        Clear the canvas.
        '''
        self.c.delete(tk.ALL)
    def change_fg(self):  
        '''
        Change the foreground color.
        '''
        self.color_fg=colorchooser.askcolor(color=self.color_fg)[1]
        self.slider['foreground'] = self.color_fg  # reflect the brush color change on the slider
    def change_bg(self):  
        '''
        Change the background color.
        '''
        self.color_bg=colorchooser.askcolor(color=self.color_bg)[1]
        self.c['bg'] = self.color_bg
        self.c['highlightbackground'] = self.color_bg  # reflect the background color change on the canvas border
    def drawWidgets(self):
        '''
        Setup the widgets on the canvas.
        '''
        self.controls = Frame(self.master,padx = 5,pady = 5)
        Label(self.controls, text='Pen Width:',font=('arial 18')).grid(row=0,column=0)
        self.slider = ttk.Scale(self.controls,from_= 5, to = 100, command=self.changeW,orient=tk.HORIZONTAL)
        self.slider.set(self.penwidth)
        self.slider.grid(row=0,column=1,ipadx=30)
        self.controls.pack()
        self.c = Canvas(self.master,width=500,height=400,bg=self.color_bg,)
        self.c.pack(fill=BOTH,expand=True)
        menu = Menu(self.master)
        self.master.config(menu=menu)
        filemenu = Menu(menu)
        menu.add_cascade(label='Colors',menu=filemenu)
        filemenu.add_command(label='Brush Color',command=self.change_fg)
        filemenu.add_command(label='Background Color',command=self.change_bg)
        optionmenu = Menu(menu)
        menu.add_cascade(label='Options',menu=optionmenu)
        optionmenu.add_command(label='Clear Canvas',command=self.clear)
        optionmenu.add_command(label='Exit',command=self.master.destroy) 
if __name__ == '__main__':
    root = tk.Tk()
    DrawingApp(root)
    root.mainloop()
```

このプログラムを実行すると以下のような画面になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/9515526d-d8e5-88fd-9843-1ab674f0e647.png)
ペンの色や背景色の変更ができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/f8205359-34d3-9827-23ad-4358970d3de1.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/f161ff16-d518-2543-1601-07d5ac018c4a.png)

:::note warn
ペンの色変更を実行するとソースコードの51行目でエラーが出ました。`ttk.Scale`の引数と`tk.Scale`の引数と間違えているようです。エラーは出るものの問題なくペンの色を変えてお絵描きはできます。
:::

キャンバスのクリアや終了機能も問題なく動作します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/882c64c0-daaa-f224-14e5-93a398510627.png)

ソースコードとしては1ヵ所ミスがありましたが、`gpt-4`へ指示を投げるだけのものよりもきちんと動くお絵描きアプリを作成してくれました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/9114c9fa-3ca7-50f6-1328-2d6252f84bb8.png)

# 4. その他の機能
ChatDevでソフトウェア開発をしてもらうと`WareHouse`フォルダにプロジェクトのログファイルが出力されます。
このログファイルはWeb上で再生することができ、開発やコミュニケーションの内容を見ることができます。
以下のコマンドを実行するとFlaskのWebアプリケーションが立ち上がります。
```
$ python online_log/app.py
please visit http://127.0.0.1:8000/ for demo
 * Serving Flask app 'app'
 * Debug mode: off
```
ブラウザで`http://127.0.0.1:8000/`にアクセスするとこんな画面になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/99343500-5ddb-3f83-d19f-8626d59caa33.png)
`ChatReplay`を選択し、ログファイルをアップロードして再生すると以下のように仮想の従業員たちが仕事をしている様子やコミュニケーションの内容を確認できます。
![replay.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323087/9054bba2-9020-3b88-ff90-207ce42c9516.gif)
※ 動画は[Wiki](https://github.com/OpenBMB/ChatDev/blob/main/wiki.md)より引用

ちなみに、今回のお絵描きアプリの開発ではエージェント間で次のようなコミュニケーションが行われていました。
<details open>
  <summary>コミュニケーションのおおまかな流れ</summary><br>
  最高経営責任者：「（最高製品責任者へ）ユーザから○○という依頼が来ました。実現可能な製品にするために、どのような製品モダリティにするべきでしょうか？」<br><br>
  最高製品責任者：「（最高経営責任者へ）アプリケーションです。」<br><br>
  最高経営責任者：「（最高技術責任者へ）ユーザの要求に従ってタスクを完了させたい。最高技術責任者として具体的なプログラミング言語を提案してください。」<br><br>
  最高技術責任者：「（最高経営責任者へ）Pythonです。」<br><br>
  最高技術責任者：「（プログラマへ）Pythonによって実装された複数ファイルからなる実行可能なソフトウェアを作成しユーザの要求を満たします。まずは必要となるクラス、関数、メソッドの名前と、その目的についての簡単なコメントを並べ、それから、完全なコードを含む各ファイルの内容を出力してください。」<br><br>
  プログラマ：「（レビュアーへ）コードを書きました！レビューをお願いします。」<br><br>
  レビュアー：「（プログラマへ）レビューの結果、プログラムの改善点は○○です。△△のようにコードを修正することをおすすめします。」<br><br>
  プログラマ：「（レビュアーへ）コードを修正しました。レビューをお願いします。」<br><br>
  (複数回レビューを繰り返す)<br><br>
  プログラマ：「（テストエンジニアへ）コードが完成したのでテストをお願いします。」<br><br>
  テストエンジニア：「（プログラマへ）テストの結果、コードの××はエラーとなるでしょう。原因は▲▲です。修正するには□□のようにコードを変更する必要があります。」<br><br>
  （複数回テストを繰り返す）<br><br>
  最高技術責任者：「（プログラマへ）ソフトウェアのrequirements.txtを作成してください。」  <br><br>
  プログラマ：「（最高技術責任者へ）ソフトウェアのrequirements.txtを作成しました。」<br><br>
  最高経営責任者：「（最高製品責任者へ）ソフトウェアを使用するための詳細なユーザーマニュアルであるmanual.mdを記述してください。」<br><br>
  最高製品責任者：「（最高製品責任者へ）マニュアルを作成しました。内容はこの通りです。」<br><br>
  （終了）
  
</details>


# 5. 注意点
ChatDevを実行するうえでいくつか注意点があります。
- 商用利用不可
- トークンをかなり使うので使いすぎるとChatGPTの課金額が増えます。
- `gpt-4`のレート制限に引っかかる場合があります。リクエストとリクエストの間にsleep関数などでインターバルを入れると回避できます。
- `gpt-4`のトークン上限に引っかかる場合があります。コンフィグでコードレビューの回数を下げると上限を越えなくなる可能性があります。

# 6. まとめ
ChatDevは異なる役割を持った複数のエージェント同士を組み合わせ、現実世界のソフトウェア開発のステップを仮想的に再現させることで簡単に比較的質の高いソースコードを作成してくれるツールです。
ChatDevはソフトウェア開発に焦点を当てていますが、同様に現実世界のフローを仮想のエージェントたちに実行させることで様々なタスクでのアウトプットの質の向上が期待できそうですね。

# 7. 参考

https://arxiv.org/abs/2307.07924

https://github.com/OpenBMB/ChatDev

https://qiita.com/taiwa_tky/items/4ab7962f6e7354824cae