はじめに
こんにちは!今回は、DartとGitHubを使ってAIがコードレビューしてくれるBotを開発する方法を解説します。このBotは、GitHubのプルリクエストに対してGeminiというAIモデルを使ってコードレビューを自動的に行ってくれます。しかも、利用は無料です!
この記事では、Botの中身の構造を詳しく説明し、初心者でも完璧に内容を理解できるようにしています。章や箇条書き、コードブロックを多用して可読性を高め、適宜嚙み砕いた丁寧な解説を挟んでいます。さらに、コードブロックには適宜コメントを付与して、どの機能がどこにあるのかわかりやすくしています。
それでは、さっそくBotの開発に取り掛かりましょう!
プロジェクトの構成
まずは、プロジェクトのディレクトリ構成を見ていきましょう。
OS: nt
Directory: C:\Prj\code_sensei_base
├─ routes/
│ ├─ index.dart
├─ analysis_options.yaml
├─ pubspec.yaml
├─ README.md
routes/
: HTTP リクエストのハンドラーが格納されているディレクトリindex.dart
: メインのリクエストハンドラー
analysis_options.yaml
: Dart Analyzer の設定ファイルpubspec.yaml
: プロジェクトの依存関係やメタデータを管理するファイルREADME.md
: プロジェクトの説明を記述するファイル
設定ファイル
次に、各設定ファイルの中身を見ていきましょう。
analysis_options.yaml
include: package:very_good_analysis/analysis_options.5.1.0.yaml
analyzer:
exclude:
- build/**
linter:
rules:
file_names: false
このファイルでは、Dart Analyzer の設定を行っています。very_good_analysis
パッケージの設定を include し、build/
ディレクトリを除外しています。また、file_names
ルールを無効にしています。
pubspec.yaml
name: code_sensei_base
description: An new Dart Frog application
version: 1.0.0+1
publish_to: none
environment:
sdk: ">=3.0.0 <4.0.0"
dependencies:
dart_frog: ^1.1.0
dart_jsonwebtoken: ^2.14.0
dotenv: ^4.2.0
github: ^9.24.0
google_generative_ai: ^0.4.1
http: ^1.2.1
dev_dependencies:
mocktail: ^1.0.0
test: ^1.19.2
very_good_analysis: ^5.1.0
このファイルでは、プロジェクトの依存関係やメタデータを管理しています。主な依存パッケージは以下の通りです。
dart_frog
: Dart Frog フレームワークdart_jsonwebtoken
: JSON Web Token の生成と検証dotenv
: 環境変数の読み込みgithub
: GitHub API クライアントgoogle_generative_ai
: Google の Generative AI API クライアントhttp
: HTTP リクエストの送信
開発時の依存パッケージとして、mocktail
(モックライブラリ)、test
(テストフレームワーク)、very_good_analysis
(静的解析設定)が使用されています。
README.md
# code_sensei_base
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
[![License: MIT][license_badge]][license_link]
[![Powered by Dart Frog](https://img.shields.io/endpoint?url=https://tinyurl.com/dartfrog-badge)](https://dartfrog.vgv.dev)
An example application built with dart_frog
[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
[license_link]: https://opensource.org/licenses/MIT
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
このファイルは、プロジェクトの説明を記述するためのものです。ライセンスやスタイルのバッジが表示されています。
メインのリクエストハンドラー
プロジェクトの中心となるのが、routes/index.dart
ファイルです。このファイルでは、GitHub からのWebhookリクエストを処理しています。
Future<Response> onRequest(RequestContext context) async {
// 環境変数の読み込み
final env = DotEnv(includePlatformEnvironment: true)..load();
// GitHub App の秘密鍵(Base64エンコード)
final base64Pem = env['PEM_BASE64'];
// GitHub App ID
final githubAppId = env['GITHUB_APP_ID'];
// Gemini API キー
final geminiApiKey = env['GEMINI_API_KEY'];
// リクエストボディのパース
final body = await context.request.body();
final json = jsonDecode(body) as Map<String, dynamic>;
// GitHub App のインストールID
final installationId = json['installation']['id'] as int;
// アクセストークンの取得
final token = await accessToken(
installationId,
base64Pem,
githubAppId,
);
// GitHub APIクライアントの作成
final github = GitHub(auth: Authentication.withToken(token));
// プルリクエストのアクション
final action = json['action'];
// リポジトリの所有者とリポジトリ名
final fullName = json['repository']['full_name'] as String;
// プルリクエストのデータ
final pullRequest = json['pull_request'] as Map<String, dynamic>?;
if ((action == 'opened' || action == 'reopened') && pullRequest != null) {
// プルリクエストの番号
final issueNumber = pullRequest['number'] as int?;
// プルリクエストのファイル差分を取得
final diffApiUrl =
'https://api.github.com/repos/$fullName/pulls/$issueNumber/files';
final diffResponse = await http.get(
Uri.parse(diffApiUrl),
headers: {
'Authorization': 'token $token',
'Accept': 'application/vnd.github.v3+json',
},
);
if (diffResponse.statusCode == 200) {
final files = jsonDecode(diffResponse.body) as List<dynamic>;
for (final file in files) {
// ファイルの情報を取得
final filename = file['filename'];
final status = file['status'];
final additions = file['additions'];
final deletions = file['deletions'];
final changes = file['changes'];
final patch = file['patch'];
final diffInfo = {
'filename': filename,
'status': status,
'additions': additions,
'deletions': deletions,
'changes': changes,
'patch': patch,
};
// Gemini APIクライアントの作成
final model = GenerativeModel(
model: 'gemini-1.5-flash-latest',
apiKey: geminiApiKey,
);
// コードレビューのプロンプトを作成
final prompt = '次のコードを日本語でレビューして: $diffInfo';
final content = [Content.text(prompt)];
// Gemini APIを呼び出してコードレビューを生成
final response = await model.generateContent(content);
// コードレビューのコメントを作成
final comments = <PullRequestReviewComment>[
PullRequestReviewComment(
path: diffInfo['filename'].toString(),
position: 1,
body: response.text,
),
];
// リポジトリの所有者とリポジトリ名を取得
final owner = fullName.split('/')[0];
final repo = fullName.split('/')[1];
// プルリクエストレビューを作成
final review = CreatePullRequestReview(
owner,
repo,
issueNumber,
'COMMENT',
comments: comments,
);
// GitHub APIを呼び出してプルリクエストレビューを投稿
await github.pullRequests.createReview(
RepositorySlug(owner, repo),
review,
);
}
}
}
return Response(body: 'Success');
}
このハンドラーでは、以下の処理を行っています。
- 環境変数から必要な情報(GitHub App の秘密鍵、ID、Gemini API キー)を読み込みます。
- リクエストボディをパースして、プルリクエストの情報を取得します。
- GitHub App のインストールIDを使って、アクセストークンを取得します。
- プルリクエストのファイル差分を取得します。
- 各ファイルについて、以下の処理を行います。
- ファイルの情報(ファイル名、変更内容など)を取得します。
- Gemini APIを使ってコードレビューを生成します。
- コードレビューのコメントを作成します。
- GitHub APIを使ってプルリクエストレビューを投稿します。
以上が、このBotの主な処理の流れです。
まとめ
この記事では、DartとGitHubを使ってAIがコードレビューしてくれるBotの開発方法を解説しました。プロジェクトの構成や設定ファイル、メインのリクエストハンドラーの処理の流れを詳しく説明し、初心者でも完璧に内容を理解できるようにしました。
このBotを使えば、プルリクエストに対して自動的にコードレビューを行ってくれるので、開発の効率化につながります。しかも、利用は無料なので、ぜひ試してみてください!
Dartを使った開発に興味がある方は、ぜひこのプロジェクトをカスタマイズしてみてください。GitHubとの連携やAIを活用した開発など、面白い機能を追加できるはずです。
Happy coding!
参考サイト
GitHubでAI(Gemini)がコードレビューしてくれるbotをDartで開発しました。利用は無料です。https://t.co/EemHS5Ehmx pic.twitter.com/jGgoLZiZVL
— Masahiro Aoki (@ma_freud) May 24, 2024
コメント