
Mastraで作る実用的なAIエージェント:GitHubリポジトリ分析ボット
はじめに
AIエージェントの開発は、もはや特別なものではなくなりました。しかし、プロダクションレベルのAIエージェントを効率的に構築するには、適切なフレームワークの選択が重要です。今回は、Gatsby.jsの開発チームが新たに生み出したMastraを使って、実用的なGitHubリポジトリ分析AIエージェントを実装する方法をご紹介します。
Mastraとは?
Mastraは、AIアプリケーションやフィーチャーを素早く構築するための、TypeScriptフレームワークです。以下のような特徴があります:
- TypeScriptネイティブ: 型安全性と優れた開発体験
- バッテリー同梱: ワークフロー、エージェント、RAG、統合、評価機能が最初から利用可能
- 柔軟なデプロイメント: ローカル環境からサーバーレスクラウドまで対応
- 統一されたLLMインターフェース: Vercel AI SDKを使用し、OpenAI、Anthropic、Google Geminiなど任意のLLMプロバイダーと統合
プロジェクトのセットアップ
まずは、Mastraプロジェクトを作成しましょう:
npm create mastra@latest github-analyzer \ --components agents,tools,workflows \ --llm openai \ --example cd github-analyzer
必要な環境変数を設定します:
# .env OPENAI_API_KEY=your-api-key-here GITHUB_TOKEN=your-github-token-here # GitHub APIアクセス用
GitHubリポジトリ分析エージェントの実装
1. ツールの定義
まず、GitHubからデータを取得するツールを作成します:
// src/mastra/tools/github.tools.ts import { createTool } from '@mastra/core'; import { z } from 'zod'; import { Octokit } from '@octokit/rest'; // GitHubクライアントの初期化 const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN, }); // リポジトリ情報取得ツール export const getRepositoryInfo = createTool({ id: 'get-repository-info', description: 'GitHubリポジトリの詳細情報を取得します', inputSchema: z.object({ owner: z.string().describe('リポジトリのオーナー名'), repo: z.string().describe('リポジトリ名'), }), execute: async ({ owner, repo }) => { try { const { data } = await octokit.repos.get({ owner, repo }); return { name: data.name, description: data.description, stars: data.stargazers_count, forks: data.forks_count, language: data.language, openIssues: data.open_issues_count, createdAt: data.created_at, updatedAt: data.updated_at, topics: data.topics, }; } catch (error) { throw new Error(`リポジトリ情報の取得に失敗しました: ${error.message}`); } }, }); // 最近のコミット取得ツール export const getRecentCommits = createTool({ id: 'get-recent-commits', description: '最近のコミット履歴を取得します', inputSchema: z.object({ owner: z.string().describe('リポジトリのオーナー名'), repo: z.string().describe('リポジトリ名'), limit: z.number().default(10).describe('取得するコミット数'), }), execute: async ({ owner, repo, limit }) => { try { const { data } = await octokit.repos.listCommits({ owner, repo, per_page: limit, }); return data.map(commit => ({ sha: commit.sha, message: commit.commit.message, author: commit.commit.author?.name, date: commit.commit.author?.date, })); } catch (error) { throw new Error(`コミット履歴の取得に失敗しました: ${error.message}`); } }, }); // Issue統計取得ツール export const getIssueStats = createTool({ id: 'get-issue-stats', description: 'Issueの統計情報を取得します', inputSchema: z.object({ owner: z.string().describe('リポジトリのオーナー名'), repo: z.string().describe('リポジトリ名'), }), execute: async ({ owner, repo }) => { try { // オープンなIssue const openIssues = await octokit.issues.listForRepo({ owner, repo, state: 'open', per_page: 100, }); // クローズされたIssue const closedIssues = await octokit.issues.listForRepo({ owner, repo, state: 'closed', per_page: 100, }); // ラベル別の集計 const labelCounts: Record<string, number> = {}; openIssues.data.forEach(issue => { issue.labels.forEach(label => { if (typeof label !== 'string' && label.name) { labelCounts[label.name] = (labelCounts[label.name] || 0) + 1; } }); }); return { open: openIssues.data.length, closed: closedIssues.data.length, total: openIssues.data.length + closedIssues.data.length, labelDistribution: labelCounts, averageCloseTime: calculateAverageCloseTime(closedIssues.data), }; } catch (error) { throw new Error(`Issue統計の取得に失敗しました: ${error.message}`); } }, }); // ヘルパー関数 function calculateAverageCloseTime(issues: any[]): string { if (issues.length === 0) return 'N/A'; const closeTimes = issues .filter(issue => issue.closed_at) .map(issue => { const created = new Date(issue.created_at); const closed = new Date(issue.closed_at); return closed.getTime() - created.getTime(); }); if (closeTimes.length === 0) return 'N/A'; const average = closeTimes.reduce((a, b) => a + b, 0) / closeTimes.length; const days = Math.floor(average / (1000 * 60 * 60 * 24)); return `${days}日`; }
2. ワークフローの定義
複雑な分析を行うワークフローを作成します:
// src/mastra/workflows/analyze-repository.workflow.ts import { Workflow, Step, createWorkflow } from '@mastra/core'; import { z } from 'zod'; import { getRepositoryInfo, getRecentCommits, getIssueStats } from '../tools/github.tools'; // ワークフローの入力スキーマ const inputSchema = z.object({ owner: z.string(), repo: z.string(), analysisDepth: z.enum(['basic', 'detailed']).default('detailed'), }); // ワークフローの定義 export const analyzeRepositoryWorkflow = createWorkflow({ id: 'analyze-repository', description: 'GitHubリポジトリの包括的な分析を実行', inputSchema, execute: async (input, { runStep }) => { // Step 1: 基本情報の取得 const repoInfo = await runStep('get-basic-info', async () => { return await getRepositoryInfo.execute({ owner: input.owner, repo: input.repo, }); }); // Step 2: 詳細分析(analysisDepthがdetailedの場合) let detailedAnalysis = null; if (input.analysisDepth === 'detailed') { detailedAnalysis = await runStep('get-detailed-analysis', async () => { const [commits, issueStats] = await Promise.all([ getRecentCommits.execute({ owner: input.owner, repo: input.repo, limit: 20, }), getIssueStats.execute({ owner: input.owner, repo: input.repo, }), ]); return { recentActivity: analyzeCommitActivity(commits), issueHealth: analyzeIssueHealth(issueStats), overallScore: calculateHealthScore(repoInfo, commits, issueStats), }; }); } // Step 3: レポートの生成 const report = await runStep('generate-report', async () => { return { repository: `${input.owner}/${input.repo}`, summary: { ...repoInfo, healthStatus: detailedAnalysis?.overallScore || 'N/A', }, detailedAnalysis, recommendations: generateRecommendations(repoInfo, detailedAnalysis), generatedAt: new Date().toISOString(), }; }); return report; }, }); // 分析ヘルパー関数 function analyzeCommitActivity(commits: any[]) { if (commits.length === 0) return { status: 'inactive', description: 'コミット履歴なし' }; const latestCommit = new Date(commits[0].date); const daysSinceLastCommit = Math.floor( (Date.now() - latestCommit.getTime()) / (1000 * 60 * 60 * 24) ); if (daysSinceLastCommit < 7) { return { status: 'very-active', description: '非常に活発(1週間以内にコミット)' }; } else if (daysSinceLastCommit < 30) { return { status: 'active', description: '活発(1ヶ月以内にコミット)' }; } else if (daysSinceLastCommit < 90) { return { status: 'moderate', description: '中程度(3ヶ月以内にコミット)' }; } else { return { status: 'inactive', description: '非活発(3ヶ月以上コミットなし)' }; } } function analyzeIssueHealth(stats: any) { const openRatio = stats.total > 0 ? stats.open / stats.total : 0; if (openRatio < 0.2) { return { status: 'excellent', description: '優秀(Issue対応率80%以上)' }; } else if (openRatio < 0.4) { return { status: 'good', description: '良好(Issue対応率60%以上)' }; } else if (openRatio < 0.6) { return { status: 'fair', description: '普通(Issue対応率40%以上)' }; } else { return { status: 'poor', description: '要改善(Issue対応率40%未満)' }; } } function calculateHealthScore(repoInfo: any, commits: any[], issueStats: any): string { let score = 0; // スター数による評価 if (repoInfo.stars > 1000) score += 30; else if (repoInfo.stars > 100) score += 20; else if (repoInfo.stars > 10) score += 10; // アクティビティによる評価 const commitActivity = analyzeCommitActivity(commits); if (commitActivity.status === 'very-active') score += 30; else if (commitActivity.status === 'active') score += 20; else if (commitActivity.status === 'moderate') score += 10; // Issue管理による評価 const issueHealth = analyzeIssueHealth(issueStats); if (issueHealth.status === 'excellent') score += 40; else if (issueHealth.status === 'good') score += 30; else if (issueHealth.status === 'fair') score += 20; else if (issueHealth.status === 'poor') score += 10; if (score >= 80) return '優秀'; if (score >= 60) return '良好'; if (score >= 40) return '普通'; return '要改善'; } function generateRecommendations(repoInfo: any, detailedAnalysis: any): string[] { const recommendations = []; if (!repoInfo.description) { recommendations.push('リポジトリに説明を追加することをお勧めします'); } if (repoInfo.openIssues > 50) { recommendations.push('未解決のIssueが多いため、優先順位を付けて対応することをお勧めします'); } if (detailedAnalysis?.recentActivity.status === 'inactive') { recommendations.push('最近の活動が少ないため、メンテナンス計画を立てることをお勧めします'); } if (repoInfo.topics?.length === 0) { recommendations.push('検索性を高めるため、トピックタグを追加することをお勧めします'); } return recommendations; }
3. エージェントの実装
最後に、これらのツールとワークフローを活用するエージェントを作成します:
// src/mastra/agents/github-analyzer.agent.ts import { Agent, createAgent } from '@mastra/core'; import { createOpenAI } from '@ai-sdk/openai'; import { getRepositoryInfo, getRecentCommits, getIssueStats } from '../tools/github.tools'; import { analyzeRepositoryWorkflow } from '../workflows/analyze-repository.workflow'; // OpenAIクライアントの作成 const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY, }); // GitHubアナライザーエージェント export const githubAnalyzerAgent = createAgent({ id: 'github-analyzer', name: 'GitHub Repository Analyzer', description: 'GitHubリポジトリの分析と洞察を提供するAIエージェント', model: openai('gpt-4o-mini'), // エージェントへの指示 instructions: ` あなたはGitHubリポジトリ分析の専門家です。 ユーザーからリポジトリに関する質問を受けたら、適切なツールを使用して情報を収集し、 分かりやすく有益な洞察を提供してください。 以下の点に注意してください: - 常に正確なデータに基づいて回答する - 技術的な詳細と一般的な理解のバランスを取る - 改善提案は建設的で実行可能なものにする - 日本語で丁寧に回答する `, // 利用可能なツール tools: { getRepositoryInfo, getRecentCommits, getIssueStats, }, // 利用可能なワークフロー workflows: { analyzeRepository: analyzeRepositoryWorkflow, }, // メモリ設定(オプション) memory: { provider: 'local', // または 'redis' for production config: { maxMessages: 100, }, }, }); // エージェントのエクスポート export default githubAnalyzerAgent;
4. Mastraインスタンスの設定
すべてのコンポーネントを統合します:
// src/mastra/index.ts import { Mastra } from '@mastra/core'; import { githubAnalyzerAgent } from './agents/github-analyzer.agent'; // Mastraインスタンスの作成 export const mastra = new Mastra({ agents: { githubAnalyzer: githubAnalyzerAgent, }, // オプション:観測性の設定 observability: { provider: 'console', // または 'datadog', 'honeycomb' など }, // オプション:評価の設定 evals: { enabled: true, runOnDeploy: false, }, }); // サーバーの起動(開発環境) if (process.env.NODE_ENV !== 'production') { mastra.startServer({ port: 4111, playground: true, // ローカルプレイグラウンドを有効化 }); }
5. 実際の使用例
エージェントは複数の方法で使用できます:
a) プログラマティックな使用
// src/examples/analyze-repo.ts import { mastra } from '../mastra'; async function analyzeRepository() { const agent = await mastra.getAgent('githubAnalyzer'); // シンプルな質問 const response1 = await agent.generate( 'vercel/next.jsリポジトリの現在の状態を教えてください' ); console.log(response1.text); // 詳細な分析のリクエスト const response2 = await agent.generate( 'facebook/reactリポジトリの健全性を詳しく分析し、改善点を提案してください' ); console.log(response2.text); // 特定のワークフローの実行 const workflowResult = await agent.runWorkflow('analyzeRepository', { owner: 'microsoft', repo: 'typescript', analysisDepth: 'detailed', }); console.log(JSON.stringify(workflowResult, null, 2)); } analyzeRepository().catch(console.error);
b) REST APIとしての使用
# エージェントに質問 curl -X POST http://localhost:4111/api/agents/githubAnalyzer/generate \ -H "Content-Type: application/json" \ -d '{ "messages": ["tailwindlabs/tailwindcssの最新の開発状況を教えてください"] }' # ワークフローの実行 curl -X POST http://localhost:4111/api/workflows/analyze-repository/run \ -H "Content-Type: application/json" \ -d '{ "input": { "owner": "openai", "repo": "whisper", "analysisDepth": "detailed" } }'
c) Webアプリケーションへの統合
// src/app/api/analyze/route.ts (Next.js App Router example) import { NextRequest, NextResponse } from 'next/server'; import { mastra } from '@/mastra'; export async function POST(request: NextRequest) { try { const { repository } = await request.json(); const [owner, repo] = repository.split('/'); const agent = await mastra.getAgent('githubAnalyzer'); const result = await agent.runWorkflow('analyzeRepository', { owner, repo, analysisDepth: 'detailed', }); return NextResponse.json(result); } catch (error) { return NextResponse.json( { error: error.message }, { status: 500 } ); } }
6. 評価(Evals)の実装
エージェントの品質を確保するための評価を追加:
// src/mastra/evals/github-analyzer.eval.ts import { createEval } from '@mastra/core'; import { githubAnalyzerAgent } from '../agents/github-analyzer.agent'; export const accuracyEval = createEval({ id: 'github-analyzer-accuracy', description: 'GitHubアナライザーの精度評価', dataset: [ { input: 'microsoft/vscodeのスター数を教えてください', expectedOutput: { containsNumber: true, minValue: 100000, // VSCodeは10万スター以上 }, }, { input: 'torvalds/linuxの主要言語は何ですか?', expectedOutput: { contains: ['C'], }, }, ], scoringFunction: async (input, output, expected) => { // カスタムスコアリングロジック let score = 0; if (expected.containsNumber) { const numbers = output.match(/\d+/g); if (numbers && numbers.some(n => parseInt(n) >= expected.minValue)) { score += 0.5; } } if (expected.contains) { const containsAll = expected.contains.every(item => output.toLowerCase().includes(item.toLowerCase()) ); if (containsAll) score += 0.5; } return score; }, }); // 評価の実行 export async function runEvaluations() { const results = await mastra.runEvals([accuracyEval]); console.log('評価結果:', results); }
プロダクションへのデプロイ
Vercelへのデプロイ
// src/api/mastra/[...path].ts import { mastra } from '@/mastra'; export default mastra.createVercelHandler();
# デプロイ vercel --prod
Dockerでのデプロイ
# Dockerfile FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build EXPOSE 4111 CMD ["npm", "start"]
まとめ
Mastraを使用することで、以下のような利点が得られます:
- 開発速度の向上: TypeScriptの型安全性とMastraの高レベル抽象化により、AIエージェントを迅速に開発できます
- プロダクション対応: 組み込みの観測性、エラーハンドリング、評価機能により、本番環境でも安心して使用できます
- 柔軟性: ローカル開発からサーバーレスデプロイまで、様々な環境に対応
- 統合の容易さ: REST API、プログラマティックAPI、各種クラウドプラットフォームとの統合が簡単
このGitHubアナライザーエージェントは、実際のプロジェクトでリポジトリの健全性モニタリング、自動レポート生成、開発チームへの洞察提供などに活用できます。
Mastraの持つシンプルさと強力な機能を活かして、皆さんも独自のAIエージェントを構築してみてはいかがでしょうか。
参考リンク
この記事で紹介したコードは、MITライセンスの下で自由に使用できます。質問やフィードバックがありましたら、お気軽にコメントください。
投稿されたコメントは管理者による承認後に表示される場合があります。 不適切な内容は削除される可能性があります。
コメント (0件)
関連記事

TypeScriptパッケージを0から自作して公開しよう!
2025年の最新ベストプラクティスに基づいて、実際に手を動かしながらTypeScriptパッケージを作成し、NPMに公開するまでの全工程を解説します。

Apple Silicon Macへのasdfインストール・設定手順【2025年版】
Apple Silicon Macでのasdfのインストールからセットアップまでをステップバイステップで解説します。

2025年のAIサービス開発トレンド
2025年のAIサービス開発のトレンドについて、できるだけ分かりやすく説明します。