【Mastra入門】Claudeとペアプロしてみた その２（Agents・Tools総復習編）

## はじめに
こんにちは、いのせです。
前回に引き続き、ClaudeとペアプロしながらMastraを学んでみました。
今回はAgentsとToolsの復習になります。（備忘録に近い）

前回の記事↓

https://qiita.com/i-inose/items/0c9c066a4c78b101d83f

Claudeが優秀すぎてリアルタイムで超高速コード生成している姿を見ると、自分のコーディングスピードの遅さと比較して萎える日々ですね...笑

ClaudeCodeも最近は触り始めたので、Mastraの基礎知識が入ったら、今度はClaudeCodeで開発してみたシリーズを執筆したいなと思ってます。

:::note info
### Claudeとペアプロってなに？
Claudeに「ハンズオン形式でMastraの〇〇の箇所を学ぶことができる資料を作成して」というプロンプトを投げて出力された資料を参考にしながら勉強を進め、わからない箇所が出たらClaudeに質問を投げるという勉強方法です。
※僕が勝手に生み出しました。
:::

では、早速総復習としてそれぞれの概念からハンズオン形式の開発の詳細について書いていきます。

## 目次
- [MastraにおけるAgentsとToolsの概念](#mastraにおけるagentsとtoolsの概念)
- [MastraのTools機能を理解する](#mastraのtools機能を理解する)
- [MastraのAgent機能を理解する](#mastraのagents機能を理解する)
- [Mastraインスタンスによる統合管理](#mastraインスタンスによる統合管理)
- [Mastraのテスト・デバッグ機能](#mastraのテストデバッグ機能)
- [最後に](#最後に)

## MastraにおけるAgentsとToolsの概念

### MastraのAgentとは？
MastraのAgentsは、以下の要素を組み合わせてAIアシスタントを作成するクラスです。

```typescript
new Agent({
  name: "エージェント名",           // エージェントの識別名
  instructions: "行動指針",       // AIの人格と行動ルール
  model: "使用するLLM",           // OpenAI、Anthropic等
  tools: [使用可能なツール],       // エージェントが実行できる機能
})
```

### Mastraの**Tools**とは？
MastraのToolは、エージェントが実行できる具体的な機能を定義するオブジェクトです。

```typescript
createTool({
  id: "ツールの識別子",           // 一意な名前
  description: "ツールの説明",    // AIが理解するための説明
  parameters: "入力パラメータ",   // Zodスキーマで型定義
  execute: "実行関数"           // 実際の処理ロジック
})
```

### MastraのTools-Agents連携の仕組み

1. **エージェント**がユーザーからのリクエストを解析
2. **必要な機能**があると判断した場合、対応する**Tool**を呼び出し
3. **Tool**が実際の処理（API呼び出し、計算等）を実行
4. **エージェント**が結果を受け取り、ユーザーに返答

## MastraのTools機能を理解する

### createTool関数の詳細

MastraのcreateToolは、カスタムツールを作成するためのコア機能です。

`src/mastra/tools/weather.ts` ファイルを作成

```src/mastra/tools/weather.ts
import { createTool } from "@mastra/core";
import { z } from "zod";

// Mastraツールの定義
export const weatherTool = createTool({
  id: "get_weather",
  description: "指定された都市の現在の天気情報を取得します",
  
  inputSchema: z.object({
  city: z.string().describe("天気を取得したい都市名（例: 東京、大阪）"),
  }),

  
  execute: async (input: any) => {
    // inputオブジェクトのcontextプロパティから都市名を取得
    const city = input.context?.city || input.city;
    
    // デバッグ用ログ
    console.log("🔍 Received input:", JSON.stringify(input, null, 2));
    console.log("🏙️ City:", city);
    
    // 入力チェック
    if (!city) {
      return {
        success: false,
        error: "都市名が指定されていません",
      };
    }
    
    try {
      console.log(`🌍 ${city}の天気情報を取得中...`);
      
      // Step 1: 都市名から座標を取得
      const geocodeUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1&language=ja&format=json`;
      const geocodeResponse = await fetch(geocodeUrl);
      
      if (!geocodeResponse.ok) {
        throw new Error(`Geocoding API error: ${geocodeResponse.status}`);
      }
      
      const geocodeData = await geocodeResponse.json();
      console.log("📍 Geocode results:", geocodeData);
      
      if (!geocodeData.results || geocodeData.results.length === 0) {
        return {
          success: false,
          error: `「${city}」という都市が見つかりませんでした。別の都市名で試してください。`,
        };
      }
      
      const location = geocodeData.results[0];
      const { latitude, longitude, name, country } = location;
      console.log(`📍 Found: ${name}, ${country} (${latitude}, ${longitude})`);
      
      // Step 2: 座標から天気情報を取得
      const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current_weather=true&timezone=auto`;
      const weatherResponse = await fetch(weatherUrl);
      
      if (!weatherResponse.ok) {
        throw new Error(`Weather API error: ${weatherResponse.status}`);
      }
      
      const weatherData = await weatherResponse.json();
      console.log("🌤️ Weather data:", weatherData.current_weather);
      
      const currentWeather = weatherData.current_weather;
      
      // 天気コードから説明を取得
      const getWeatherDescription = (code: number): string => {
        const weatherCodes: Record<number, string> = {
          0: "快晴",
          1: "晴れ", 
          2: "一部曇り",
          3: "曇り",
          45: "霧",
          48: "霧氷",
          51: "霧雨（弱）",
          53: "霧雨",
          55: "霧雨（強）",
          56: "着氷性の霧雨（弱）",
          57: "着氷性の霧雨（強）",
          61: "雨（弱）",
          63: "雨",
          65: "雨（強）",
          66: "着氷性の雨（弱）",
          67: "着氷性の雨（強）",
          71: "雪（弱）",
          73: "雪",
          75: "雪（強）",
          77: "雪粒",
          80: "にわか雨（弱）",
          81: "にわか雨",
          82: "にわか雨（強）",
          85: "にわか雪（弱）",
          86: "にわか雪（強）",
          95: "雷雨",
          96: "雷雨と小さなひょう",
          99: "雷雨と大きなひょう",
        };
        return weatherCodes[code] || `天気コード: ${code}`;
      };
      
      // レスポンスの作成
      const result = {
        success: true,
        data: {
          location: `${name}, ${country}`,
          temperature: `${currentWeather.temperature}°C`,
          weather: getWeatherDescription(currentWeather.weathercode),
          windSpeed: `${currentWeather.windspeed} km/h`,
          windDirection: `${currentWeather.winddirection}°`,
          time: new Date(currentWeather.time).toLocaleString('ja-JP', {
            year: 'numeric',
            month: '2-digit',
            day: '2-digit',
            hour: '2-digit',
            minute: '2-digit'
          }),
        }
      };
      
      console.log("✅ 天気情報取得成功:", result);
      return result;
      
    } catch (error) {
      console.error("❌ エラー発生:", error);
      return {
        success: false,
        error: error instanceof Error ? error.message : "天気情報の取得中にエラーが発生しました。",
      };
    }
  },
});
```

### **MastraのcreateTool機能の重要ポイント**

#### **1. Mastraの型安全性システム**
```typescript
inputSchema: z.object({
  city: z.string().describe("天気を取得したい都市名（例: 東京、大阪）"),
  }),
```

**Mastraの特徴**
- **Zodスキーマ**でツールの入力を厳密に定義
- **実行時の型チェック**で不正なデータを事前に検出
- **describe**でAIエージェントに文脈を提供
- TypeScriptの型安全性を実行時まで拡張

#### **2. Mastraのツール実行コンテキスト**
```typescript
execute: async (input: any) => {
    // inputオブジェクトのcontextプロパティから都市名を取得
}
```

**contextで利用可能な情報**
- エージェントの実行状況
- セッション情報
- ログ出力機能
- Mastraの内部状態

#### **3. Mastraの統一レスポンス形式**
```typescript
// エラー時
return {
  success: false,
  error: "エラーメッセージ"
};
```

**この形式の利点**
- エージェントが結果を適切に解釈可能
- エラーハンドリングの統一化
- デバッグ時の問題特定が容易

## MastraのAgents機能を理解する

### Agentクラスの詳細機能

MastraのAgentクラスは、LLMとツールを統合してインテリジェントなアシスタントを作成するコア機能です。

`src/mastra/agents/weather.ts` ファイルを作成

```src/mastra/agents/weather.ts
import { Agent } from "@mastra/core";
import { openai } from "@ai-sdk/openai";
import { weatherTool } from "../tools/weather";

export const weatherAgent = new Agent({
  // Mastraエージェントの基本設定
  name: "Weather Agent",
  
  // Mastraのinstructions機能：エージェントの行動指針
  instructions: `
    あなたは親切で知識豊富な天気予報士です。
    
    ## Mastraツールの使用方針
    - 天気情報が必要な場合は、必ずweatherToolを使用する
    - ツールから取得したデータを基に正確な情報を提供する
    - ツールのエラー時は、適切にユーザーに説明する
    
    ## 応答スタイル
    1. 丁寧で親しみやすい口調
    2. 絵文字を適度に使用
    3. 情報を構造化して見やすく表示
    4. 必要に応じて追加のアドバイスも提供
    
    ## 注意事項
    - 推測や古い情報は提供しない
    - weatherToolの結果のみを信頼する
    - エラー時は再試行を促す
  `,
  
  // Mastraのモデル統合：Vercel AI SDK経由
  model: openai("gpt-4o-mini"),
  
  // Mastraのツール統合システム
  tools: {weatherTool},
});
```

### **MastraのAgent機能の重要ポイント**

#### **1. Mastraのinstructions機能**
```typescript
instructions: `エージェントの行動指針...`
```

**Mastraにおけるinstructionsの役割**
- **システムプロンプト**として動作
- **ツール使用判断**の基準を提供
- **応答品質**の一貫性を保証
- **エラー処理**の方針を定義

**効果的なinstructionsの書き方**
```typescript
instructions: `
  ## ツール使用方針
  - いつツールを使うか明確に指示
  
  ## 応答フォーマット
  - どのように情報を整理するか
  
  ## エラー対応
  - 問題発生時の対処法
`
```

#### **2. MastraのModel統合機能**
```typescript
model: openai("gpt-4o-mini"),
```

**MastraのModel統合の特徴**
- **Vercel AI SDK**との完全統合
- **複数プロバイダー**への統一インターface
- **ストリーミング応答**の自動対応
- **関数呼び出し**の最適化

**利用可能なモデル例**：
```typescript
// OpenAI
model: openai("gpt-4o-mini")
model: openai("gpt-4o")

// Anthropic
model: anthropic("claude-3-sonnet-20240229")

// Google
model: google("gemini-pro")
```

#### **3. Mastraのツール統合システム**
```typescript
tools: [weatherTool],
```

**ツール統合の仕組み**
1. **自動関数呼び出し**：AIが必要に応じてツールを選択
2. **型安全な実行**：Zodスキーマによる入力検証
3. **結果の自動統合**：ツールの戻り値をAIが理解
4. **エラーの適切な処理**：失敗時の自動対応


## Mastraインスタンスによる統合管理

### Mastraクラスの中央管理機能

Mastraクラスはアプリケーション全体のエージェントとツールを統合管理します。

`src/mastra/index.ts` を更新

```src/mastra/index.ts
import { Mastra } from "@mastra/core";
// Mastraエージェントのインポート
import { weatherAgent } from "./agents/weather";
// Mastraツールのインポート
import { weatherTool } from "./tools/weather";

// Mastraインスタンス：アプリケーションの中央管理
export const mastra = new Mastra({
  // Mastraのエージェント管理機能
  agents: {
    weatherAgent,
  },
  
  // Mastraのツール管理機能
  tools: {
    weatherTool,
  }
});
```

### **Mastraインスタンスの重要機能**

#### **1. Mastraの中央管理システム**
```typescript
export const mastra = new Mastra({
  agents: { /* エージェント一覧 */ },
  tools: { /* ツール一覧 */ },
});
```

**中央管理の利点**
- **名前空間の管理**：重複を防ぐ
- **依存関係の解決**：エージェント↔ツールの関連付け
- **設定の統一化**：ログ、ストレージ等の共通設定
- **デプロイの簡素化**：1つのインスタンスで全機能を管理

#### **2. Mastraの実行時システム**
```typescript
// エージェントの取得と実行
const agent = mastra.getAgent("weatherAgent");
const result = await agent.generate("東京の天気は？");

// ツールの直接実行
const tool = mastra.getTool("get_weather");
const result = await tool.execute({ city: "東京" });
```

#### **3. Mastraの拡張性**
```typescript
export const mastra = new Mastra({
  agents: { /* 既存 + 新規エージェント */ },
  tools: { /* 既存 + 新規ツール */ },
  
  // 高度な機能
  storage: new LibSQLStore({}),  // データ永続化
  logger: new PinoLogger({}),    // 構造化ログ
  workflows: { /* ワークフロー */ },
});
```

## Mastraのテスト・デバッグ機能

### Mastra Dev Server

Mastraの開発サーバーは、エージェントとツールをテストするための統合環境です。

```bash
npm run dev
```

### **Mastra Dev Serverの機能**

#### **1. Mastra Playground**
ブラウザで `http://localhost:4111` にアクセス

:::note info
このPlayground機能は個人的に、サクッと結果確認ができてとても便利です。
もちろんですが、Nextやexpressと一緒に用いてアプリケーションにすることも可能です。

:::


**主な機能**
- **Agents**：エージェントとの対話テスト
- **Tools**：ツールの単体テスト
- **Logs**：実行ログの確認
- **Workflows**：ワークフローのテスト

#### **2. Mastraのログシステム**
```typescript
// ツール内でのログ出力
console.log(`🌍 Mastraツール実行: ${city}の天気取得開始`);
console.log(`✅ Mastraツール完了:`, result.data);
console.error(`❌ Mastraツールエラー:`, error);
```

**ログの確認方法**
- **ターミナル**：サーバー実行中のコンソール
- **Playground**：「Logs」タブ
- **構造化ログ**：JSON形式での詳細情報

#### **3. Mastraのエラー追跡**
```typescript
// エージェント実行時のエラー
try {
  const result = await agent.generate("テスト入力");
} catch (error) {
  console.error("Mastraエージェントエラー:", error);
}
```

**よくあるMastraエラーと解決法**

```typescript
// エラー1: ツールが見つからない
"Tool 'get_weather' not found"
// 解決：mastraインスタンスのtools設定を確認

// エラー2: エージェントが見つからない
"Agent 'weatherAgent' not found"  
// 解決：mastraインスタンスのagents設定を確認

// エラー3: パラメータ型エラー
"Invalid parameters for tool"
// 解決：Zodスキーマの定義を確認
```

## 最後に
今回は、ハンズオン形式で実際のコードを出力してもらった後に細かい説明をするように頼み、コードや仕組みを確認しながら総復習として進めてみました。
実際の実行結果は下記の記事とほぼ一緒なので今回は省きました。

https://qiita.com/i-inose/items/34ffa9872cde26ab7787

次回はWorkflowsについて同じようにClaudeとペアプロしながら学んでいこうと思います。