Github issueをアサインしたらコード修正&PR出すAI Agentをつくる

2024.03.21

Introduction

先日Devinが発表されました。
課題を与えると、自律的に情報収集・コーディング・デバッグ等
システム構築をやってくれるすごいAIだそうです。

ここまでやらなくても、Agent用アカウントにGihubでissueをassignしたら
自動で実装してpull requestだしてくれたら便利では、と思い
実装してみました。
今回はAmazon Bedrock(Claude3)をつかって実装してみたので
それについて解説します。

Environment

  • MacBook Pro (13-inch, M1, 2020)
  • OS : MacOS 14.3.1
  • Node : v20.8.1
  • Git : 2.43.2

AWSアカウントはセットアップ済みとします。

Setup

Agent用Githubアカウントの準備

適当なメールアドレスを用意し、Githubアカウントを作成しましょう。
アカウント作成後、Access Tokenを発行します。
ここでトークンを発行しましょう。
基本的にはFine-grained personal access tokensでいいかと思うのですが、
リポジトリによってはPersonal access tokens (classic)でないとダメかもしれません。

私の環境ではFine-grainedのトークンだとPR作成時にエラーになってしまったので、
classicのトークンを使いました。

Amazon Bedrockの準備

BedrockでClaude 3を使うので、このへんを参考にモデルを設定します。

AgentProgram

では作成していきます。

概要

ざっくりとした動きはこんな感じです。
NodeでAgentを起動した後ポーリングで自分宛てのopenなissueを取得し、
必要に応じてリポジトリのclone/updateを行います。
issue用ブランチを作成してBedrockにissue内容を問い合わせ、
結果をパースしてcommit→push→pull requestとやっていきます。

使用したライブラリなど

実装はNodeでtypescriptを使って実装いました。
GithubへのアクセスはOctakitを使い、 ローカルでのGit操作はsimple-gitを使います。
あとはaws-sdkでBedrockにアクセスしています。

各実装ポイント

Githubからissueリストをもってくる処理は下記のような感じです。
Octokitで簡単にできます。

let octokit: Octokit = new Octokit({ auth: "your githubToken" });
const response = await this.octokit.rest.issues.listForAuthenticatedUser({
    filter: 'assigned',
    state: 'open',
});

console.dir(response.data);

simple-gitを使ったGitのcloneは下記。
こちらも簡単です。

import simpleGit, { SimpleGit, SimpleGitOptions } from 'simple-git';

let git: SimpleGit = simpleGit({});
await git.clone("clone url", "your repo pash");

Bedrockへアクセス

AWS SDKでのBedrockアクセスもここのような感じで実装します。
モデルはClaude 3 Sonnetを指定します。

const client = new BedrockRuntimeClient({ region: "us-east-1" });
const modelId = "anthropic.claude-3-sonnet-20240229-v1:0";

const payload = {
    "anthropic_version": "bedrock-2023-05-31",
    "max_tokens": 1024,//必要に応じてトークン数を調整
    "messages": [
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "your prompt"
                }
            ]
        }
    ],
    "temperature": 0.5,
    "top_p": 0.9
};
const command = new InvokeModelCommand({
    body: JSON.stringify(payload),
    contentType: "application/json",
    accept: "application/json",
    modelId,
});
const response = await client.send(command);
const decodedResponseBody = new TextDecoder().decode(response.body);
/** @type {ResponseBody} */
const responseBody = JSON.parse(decodedResponseBody);
console.dir(responseBody);

プロンプトの作り方

issue内容に対してどのように修正すればよいかをAIに返してもらいます。
プロンプトは下記のような感じでJSON SCHEMAとcodebase全体のコードを渡して
修正内容を提案してもらいました。

下記に幾つかの要求のポリシーを示します。これに従って回答してください。

- 回答は下記JSON SCHEMAに合致するJSON形式で実施してください。

```
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "hoge": {
      "type": "string"
    },
    "fuga": {
      "type": "string"
    }
  }
}
```

- 上記JSON SCHEMAのプロパティの意味は下記です
    - ここに各プロパティ内容や仕様を記述

以下の<issue/>が依頼する内容です。
<codebase/>は依頼する内容に関連するコード内容です。

--------------------------------------------------------

<codebase>
//codebase全体の情報
</codebase>

<issue>
//issue内容
</issue>

codebase全体のコードはcode2prompt
ファイル出力したものをloadしてます。
AIからの出力はJSON SCHEMAを渡してその形式で返してもらいます。
こうしておけば自分で定義したclassにJSON.parseでそのまま変換できます。

ファイル修正〜pull request

修正すべき内容が取得できたので、それを適用していきます。
cloneしたcodebaseに対してファイルのupdateを適用していきます。
修正できたらsimple-gitでadd,commit,push。

let git: SimpleGit = simpleGit({});

await git.add("file path");
await git.commit("commitMessage");
await git.push('origin', "branch name");

pushできたらPRを作成します。

const { data } = await octokit.pulls.create({
    owner:"リポジトリオーナー名",
    repo: "リポジトリ名",
    head: `Pull Requestを作成するブランチ名`,
    base: 'mainとかのbaseになるブランチ',
    title: "PR タイトル",
    body: "PR 内容",
});

console.dir(data);

テスト用リポジトリで適当なコードをmainブランチに用意し、
コラボレータとしてAI Agentをinviteします。
そして、↓のようなissueを作成してAgentをアサインします。

作成されたPRは↓みたいな感じです。

「CSSファイル作成してhtmlに適用して」というissueを作成し
実際にマージして動作確認したら、ちゃんとCSSが適用されてました。
今回はシンプルな例でしたが、プロンプトやissue内容を調整すれば
けっこういけるはず。

Summary

今回はGithubでコラボレータとして追加されたAgentに対し、
issueをアサインしたらコード修正〜Pull Requestまで実行する処理を実装してみました。
いくつissueを同時にアサインしても、(tokenが許す限り)並行で作業できるのは便利。

また、一度作ってしまえば、モデルをバージョンアップしただけで
生成されるコードの品質が高くなり、対応できるissueの幅が広がるのもよいです。
シンプルなissueには単価が安いモデルのAgentをassignし、
複雑/重要なissueには高単価のハイスペックなモデルを、なども可能ですね。

PRをレビューしてくるAgentなどもどこかで見ましたが、
AIが生成したコードをAIがレビューして修正する、なども普通にありそうです。

ちなみに、今回はポーリングでissueチェックしましたが、
Guthubのwebhookを使えばissue登録など
任意のタイミングでリクエストをpostできるので、 それのほうがよいかもしれません。
あとはBedrockアクセス〜PR作成まで、MQとか使って処理したほうがよいかも。

References