現実的なAI並列実装
はじめに
- 最近並列実装がXで話題になっていると思うのですが、具体的に、コマンドレベルでどうやるのかわからなかったので、まとめてみました。
- また、github 上に大量のブランチが作成されることは避けたいので、並列実装したものの取捨選択は全てローカル上で行います。
- 並列実装にもいろいろな種類があると思うのですが、この記事では以下のように、一つの実装を複数のAIに実装させ、良いものを人間が選ぶコンペ形式を紹介します。
他の並列実装の方式として、AIがAIの出力を評価する(良いものを選ぶ)といった形式があるかと思います。
本当はこれがやりたいのですが、現状かなり難しい気がしています。\
git worktree
新しいworktreeの作成
git worktree は、一つのPC上で、同時に複数のブランチを操作するための機能です。例えば、以下のようなディレクトリ、ブランチ構成だと仮定します。
.
└── vitest-setup # (main,develop,add-button,add-button-1 ブランチが存在)
ここで、以下のコマンドを実行すると、
# git worktree <新しく作るディレクトリへのパス> <ディレクトリへ移すブランチ>
git worktree add ../vitest-setup-1 add-button-1
以下のようなディレクトリ、ブランチ構成になります。
.
├── vitest-setup # (main,develop,add-button ブランチが存在)
└── vitest-setup-1 # (add-button-1 ブランチが存在)
vitest-setup-1 という新しいブランチが作成され、その中で、add-button-1ブランチが操作できます。逆に、vitest-setupディレクトリの中ではadd-button-1ブランチは操作できません。
この機能を活用すると、複数のブランチで同時にコードがいじれるようになります。並列実装ととても相性が良いです。
worktree の操作
git worktree list
で確認できます。
paddy@fatrice vitest-setup % git worktree list
/Users/paddy/Desktop/git-managed/git-worktree-practice/vitest-setup e6a5700 [develop]
/Users/paddy/Desktop/git-managed/git-worktree-practice/vitest-setup-1 e6a5700 [add-button-1]
また、git worktree remove <削除したいディレクトリ名>
でworktree を削除することができ、元のブランチが元のディレクトリで操作できるようになります。
tmux
tmux とはターミナルを分割したり、バックグラウンドで実行したりするためのツールです。今回の記事での並列実装では、ターミナルの分割機能を使います。
まず、tmuxにおける用語を解説していきます。
用語 | 説明 |
---|---|
セッション(Session) | 一つ以上のウィンドウの集合 |
ウィンドウ(Window) | 一つ以上のペインの集合 |
ペイン(Pane) | ターミナル画面です。これがtmuxにおける最小単位で、コマンドを打ったりできるところです。 |
なぜtmux?
tmux がターミナルを分割するものと聞くと、「普通にターミナルを複数起動すればよいのでは?」と思うかもしれません。しかし、tmuxを使うと複数のターミナルに一回の入力で複数のターミナル(ペイン)にコマンドを送信することができます。
他にも良いところは色々ありますが、私がtmuxを使うメインの理由はこれです。人間がAIをマネジメントするケースにおいてはこれがとても有用です。やり方は後で解説します。
tmuxのインストール
brew install tmux
tmux の基本
tmux のコマンド以下の二通りの実行方法があります。
- Ctrl + b を押した後、別のキーを入力する。
- 普通にコマンドを入力する。
対応関係の表は以下です。
操作内容 | Ctrl + b の後に押すキー | コマンド |
---|---|---|
新しいセッションを作成して接続する | (なし) | tmux new-session |
既存のセッションに再接続する | (なし) | tmux attach -t <セッション名> |
セッション一覧を表示する | (なし) | tmux ls |
セッションからデタッチする | d |
tmux detach-client |
セッションを完全に終了する | (なし) | tmux kill-session -t <セッション名> |
ペインを左右に分割する(縦分割) | % |
tmux split-window -h |
ペインを上下に分割する(横分割) | " |
tmux split-window -v |
すべてのペインに同時入力を有効化する |
: → setw synchronize-panes on
|
tmux setw synchronize-panes on |
同期入力を無効化する |
: → setw synchronize-panes off
|
tmux setw synchronize-panes off |
アクティブなペインにコマンドを送信する |
: → send-keys "echo hello" C-m
|
tmux send-keys "echo hello" C-m |
ペイン間を上に移動する | ↑ |
tmux select-pane -U |
ペイン間を下に移動する | ↓ |
tmux select-pane -D |
ペイン間を左に移動する | ← |
tmux select-pane -L |
ペイン間を右に移動する | → |
tmux select-pane -R |
ペイン一覧を表示する |
: → list-panes
|
tmux list-panes |
アクティブなペインを削除する |
exit または Ctrl + d
|
tmux kill-pane |
全ペインに gemini を送信する |
: → send-keys "gemini" C-m
|
tmux send-keys "gemini" C-m |
実際にコマンドを実行してみましょう。セッションを作成し、パネルを作成してみます。
tmux new-session
tmux split-window -h
ここで、コマンドとCltr + b の後にキーを入力する方式のどちらを使ったらいいの?と思うかもしれませんが、個人的には以下のような使い分けをしています。
- claude code や gemini code を使う場合は Cltr + b の後にキーを入力する方式(対話形式で入力が進んでいくため、普通のコマンドを直接実行することがそもそもできない)
- それ以外はコマンドそのものを入力する方式(mise などのショートカットツールが使えるので、コマンドが使えるときはコマンドを使いたい)
tmux のコマンドはアルファベットの数が多いので、mise などのショートカットツールがおすすめです。以下のサイトがとてもわかりやすいです。
configファイルの作成
~/.tmux.conf
を作成します。
# クリックでペインを操作できるようになります。
set -g mouse on
# tmux を操作する場合、Ctrl + b をクリックしたあと、キーを入力することによって操作をするのですが、Ctrl + f を押すことによって操作できるようにする
unbind C-b
set-option -g prefix C-f
# 複数のペインに同じ内容を同時に入力するコマンドのショートカットを作成
bind-key e set-window-option synchronize-panes \;\
display-message "ペイン同期: #{?pane_synchronized,ON,OFF}"
- 最初の設定項目は、ペインをクリックで選択できるようにするためです。いちいちコマンドを打ってペインを切り替えるのは面倒ですよね。
- 次の設定項目は、Ctrl + b を入力した後にキーを入力するのではなく、 Ctrl + f を押したあとにキーを入力できる形に変更しています。これは、bよりもfの方が押しやすく、f が空いていたのでこのような設定にしました。自分が好きなキーに変えても問題ないです。
- 最後の項目は、最初のgifでお見せした、同期入力のon off を簡潔に実行するための設定です。上の表を見ると、同期入力のonには
~synchronize-panes on~
のようなコマンドが必要になるのですが、長いです。面倒です。なので、Ctrl + f の後にeを入力するだけで同期入力のon off ができるようにしました。
セッションの作成
tmux new-session -s mysession
上のコマンドを実行することにより、以下のような階層構造が出来上がります。(先ほど実行したtmux new-session
というコマンドだと名前が自動で振られるのですが、上の例だと名前を明示的に指定できます。)
作業環境(名前がmysession)
├── ウィンドウ-0
│ ├── パネル-0
次にパネルを増やしてみましょう。ペインの中で、以下のコマンドを実行すると右にターミナルが出現し、
tmux split-window -h # 横に分割(左右)
以下のような構造になります。パネルが増えています。
作業環境(名前がmysession)
├── ウィンドウ-0
│ ├── パネル-0
│ ├── パネル-1
ここで、それぞれのパネル上で、以下を実行すると、
tmux split-window -v
最終的に以下のような画面を作ることができます。この画面ひとつひとつ上でgemini code を動かしていきます。
実際どうやる?
方針はとしては以下のような感じです。
- 元のブランチ(git worktree でブランチを切るときの元のブランチ)を、派生先のブランチ(git worktree で作ったブランチ)に同期させる。
git reset <元のブランチ名>
を実行し、派生先のブランチの内容を元のブランチの内容に完全に同期させる。 - 元のブランチ内で、実装したい内容の計画書を作成させる。計画書は人間がレビューする
- 計画書ができたら、tmuxで管理しているターミナルに行き、以下を実行
- Ctrl + f を押したあと、eを押し、同期モードon
-
gemini
コマンドでgemini code を起動(同期モードonなので、全部のペインに入力される) - 計画書をコピペ
- Ctrl + f を押したあと、eを押し、同期モードoff
- アプリケーションを起動。それぞれのworktree ないで、
npm run dev
などを起動 - AIの出力が終わったら、レビューをする。ブラウザで見た目を確認してもいし、cursor などのエディターでdiffを確認するのもいい感じだと思います。
- 三つのAIの実行結果の中で一番いいものを選ぶ。一番いいブランチの中でコミットを行い、元のブランチで
git merge <一番よいブランチ> --no-ff --no-commit && git reset
を実行する。これにより、元のブランチでは、コミット履歴を汚さずに変更を取り込むことができる。 - 1~6を繰り返す。
リポジトリを格納するディレクトリを作成
mkdir git-worktree-practice
cd git-worktree-practice
git clone git@github.com:FatRicePaddyyyy/vitest-setup.git
# next.jsでvitestのセットアップが終わっているプロジェクト。ここはなんでもいいです。
- 今回の例だと、
git-worktree-practice
ディレクトリの中でリポジトリのコード管理することにします。
ブランチの作成
git-worktree-practice/vitest-setup/
ディレクトリで実行
cd vitest-setup
git branch add-buton # githubにpushするよう。以下の四つのブランチのうち、いいものをここにマージしていく
git branch add-buton-1 # gemini code が動くブランチ
git branch add-buton-2 # gemini code が動くブランチ
git branch add-buton-3 # gemini code が動くブランチ
-
add-button
ブランチが、github でも管理する、機能を実装していくブランチです。
worktreeの作成
git-worktree-practice/vitest-setup/
ディレクトリで実行
git worktree add ../vitest-setup-1 add-button-1
git worktree add ../vitest-setup-2 add-button-2
git worktree add ../vitest-setup-3 add-button-3
tmux で複数のペイン作成
git-worktree-practic/
ディレクトリで実行
tmux kill-session -t mysession # 練習で作ったセッションを削除
tmux new-session -s mysession # 新しいセッション作成
tmux split-window -h # 現在のペインを左右に分割
tmux split-window -h # 現在のペインを左右に分割
- Ctrl + f の後に spaceキーを押すことで、レイアウト変更できます。好きなレイアウトが来るまで連打してみてください。
- 次にそれぞれのワークツリーに対応するブランチに移動し, Ctrl + f を押した後にeを押して同期モードをonにしたあと、
gemini
を実行してください。
cursor などのエディタ起動
- cursor じゃなくてもいいです。
git-worktree-practice/
ディレクトリで
cursor .
を実行したあと、普通にターミナルを複数起動し、それぞれのworktreeでアプリケーションを起動。
cd vitest-setup && npm run dev
cd vitest-setup-1 && npm run dev
cd vitest-setup-2 && npm run dev
cd vitest-setup-3 && npm run dev
- 最後に、vitest-setupの中でgeminiを起動し計画書を書いてもらい、tmuxで管理しているターミナルにコピペします。
- 私の場合は以下の手順でAIの出力をレビューします。
- http://localhost:300X に行き、見た目を確認
- diff を確認
開発tips
- 「t-wada方式」という圧縮プロンプトを使うといい感じにテスト駆動開発してくれるらしい
- AI の出力を一つ選択したら、
/clear
で履歴を消した方がいいかもしれない。というのも、一番いいものが決まったら、元のブランチにマージし、派生先のブランチと同期させる際に、AIが認識しているファイル構成と実際のファイル構成が異なってしまうため。一度、存在しないファイルをずっと参照し続け、止まってしまうことがあった。
Discussion