🦔

Mastra Agent Network を試してみた 〜0から構築するAgent Network〜

に公開

こんにちは、オートロの代表をしております福田です。今回は、弊社のインターンの方が書いた記事を紹介いたします。Mastra を使って、Agent Network がどんな風に使えそうかを検討したものです。

===

こんにちは。オートロ株式会社でインターンをしております釜土です。
今回は、Mastraを用いてTypeScriptでAIエージェントの開発に挑戦した体験を、ひとつの記事としてまとめました。
はじめてのエージェント開発ということもあり、設計・実装・テストの各ステップで多くの発見と試行錯誤がありました。
この記事では、実際に作成したエージェントの構成や、開発中に得られた気づき・工夫したポイントなどを中心にご紹介します。

はじめに

AIチャットボットを業務に活用する例は増えてきましたが、単体では限界があり、「複数のエージェントを連携させて、ひとつの目的を果たす」ような構成が必要になってきています。Mastraでは、これを "Workflow" と "Network" の2つのアプローチで実現できます。

この記事では、特に Agent自身がシナリオ(流れ)を考える という新しいアプローチを試し、その可能性と課題についてまとめました。


Agentとは何か?

Agentとは、目的を持ち、自律的にタスクを遂行するAIプログラムのことです。人間のアシスタントのように、与えられたゴールに向かって情報収集・判断・出力を繰り返します。LLM(大規模言語モデル)をベースに、外部APIやツールとも連携して動作するのが一般的です。


Toolとは何か?

Toolとは、Agentが特定の処理を行うために呼び出す“外部機能”や“補助的な構成機能”のことです。たとえば、Web検索、PDF読み取り、社内データベースへの問い合わせなど、LLM単体では扱えない処理を実現するための手段です。

Toolは以下のような特性を持ちます:

  • Agentが明示的に「使う」と判断して呼び出す(例:tool('search')
  • 一般的に、決まった入力→決まった出力の構造を持つ
  • 外部API(Google Search, Notion, Slack など)とつながっていることが多い

Mastraでは、ToolをTypeScriptで関数として定義し、Agentのプロンプト内から呼び出すことができます。


Agent Workflowとは何か?

Workflowは、エージェントの動作や手順を事前に明確に定義したプロセスです。ファーストフード店などで従業員が従うマニュアル(何をやるか、そしてその手順が書かれている)を想像していただけるとわかりやすいかもしれません。

例:

  1. 履歴書を受け取る
  2. スキルマッチを評価する
  3. コメントを生成する

これは「固定的な」流れで、プロセスを設計した人間がすべて決める方式です。

Workflowを構築していて感じたこととして、Workflowはツールやエージェント同士の相互作用を固定するようなイメージでした。


Agent Networkとは何か?

Agent Networkは、複数のエージェントが“ネットワーク”として連携し、タスクを分担・協力して進める構成です。Agent同士が結果を共有し、部分的な判断を担いながらタスクを進行します。

特徴的なのは、「中心にいるAgent(マスターエージェント)」が、他のAgentを“必要に応じて呼び出す”ことで、柔軟な処理が実現できることです。


Workflow と Network の違い

項目 Workflow Agent Network
制御方法 事前定義されたステップ エージェント間のやり取りに委ねる
柔軟性 低い(定型処理に向く) 高い(状況に応じた分岐が可能)
実装の難易度 比較的低い 設計と評価が複雑
シナリオ生成 人間が定義 一部、エージェント自身が生成


Mastra とは何か?

Mastraは、エージェント開発・管理プラットフォームの一つです。TypeScriptベースでエージェントを構築し、LLMや外部API、ツールを自由に組み合わせて、業務自動化のシナリオを構築できます。2025年5月26日時点では最新バージョンは0.10で、今回のプロジェクトはこのバージョンを使用しました。

Mastraの実装・使用方法は公式サイトに記載されています。


実装サンプル

今回は、「採用候補者レビュー・チーム」をAgent Networkとして実装してみました。Agentに履歴書と募集要項を渡すことで、候補者のスキルと要求項目が一致しているかを評価してくれて、一致率(%)や候補者の強みや懸念点をレポートにまとめることで書類選考をサポートしてくれるものです。

登場エージェント

  • 履歴書分析エージェント
    • スキルマッチ評価エージェントが読み取れるSchemaに履歴書の情報を抽出するエージェント
  • スキルマッチ評価エージェント
    • 募集要項から、求める人物像を読み取って候補者の適正度を評価するエージェント
  • コメント下書き生成エージェント
    • スキルマッチ評価エージェントから評価内容を受け取って、ユーザーに渡すレポートを作成するエージェント

Master Review Agent の役割

  • 人事担当者からの入力(職務要件、候補者情報)を受け取り、上記の各エージェントに適切に処理を分配


技術的な詳細と実装の深掘り

本セクションでは、「採用候補者レビュー・チーム」エージェントネットワークをMastra上で構築するにあたり、中核となった技術的要素と、実装の過程で直面した課題や工夫について具体的に解説します。

1. スキーマ定義 (Zod): エージェント間連携の「契約」

複数のエージェント(またはツール)が連携するシステムにおいて、それぞれの入出力を厳密に定義することは、システムの安定性と予測可能性を高める上で不可欠です。本プロジェクトでは、データ構造の定義とバリデーションのためにZodを採用しました。

a. スキーマの役割:

  • 型安全性: TypeScript環境において、エージェントやツール間で受け渡されるデータの型をコンパイル時にチェックし、実行時エラーを未然に防ぎます。
  • LLMへの指示の明確化: Zodスキーマの構造(特に各フィールドの describe)は、LLMに対して期待するJSON出力形式を具体的に指示する際の基盤となります。これにより、LLMがより正確な構造で情報を生成するよう促すことができます。
  • バリデーション: LLMからの出力が期待通りの形式であるかを実行時に検証し、予期せぬデータ形式による後続処理のエラーを防ぎます。

b. スキーマ定義の具体例と工夫 (schemas.ts):

例えば、履歴書分析の結果を格納する ResumeAnalysisOutputSchema は以下のように定義しました。

// src/mastra/schemas.ts (抜粋)
import { z } from 'zod';

export const EducationEntrySchema = z.object({
  institution: z.string().describe('教育機関の正式名称。'),
  degree: z.string().optional().nullable().describe('取得した学位の名称。'),
  fieldOfStudy: z.string().optional().nullable().describe('専攻分野や学部・学科名。'),
  graduationYear: z.string().optional().nullable().describe('卒業年または卒業予定年。'),
  details: z.string().optional().nullable().describe('学歴に関するその他の特筆すべき事項。'),
});

export const ExperienceEntrySchema = z.object({
  companyName: z.string().describe('勤務した会社の正式名称。'),
  jobTitle: z.string().optional().nullable().describe('その会社での役職名。不明な場合は省略またはnull。'),
  startDate: z.string().optional().nullable().describe('勤務開始年月。'),
  // ... 他のフィールド ...
  responsibilities: z.array(z.string()).optional().nullable().describe('主要な職務内容、実績、プロジェクトなどのリスト。'),
});

export const ResumeAnalysisOutputSchema = z.object({
  extractedName: z.string().optional().nullable().describe("履歴書から抽出された候補者の氏名。"),
  contactInformation: z.object({
    email: z.string().optional().nullable().describe("候補者のメールアドレス。有効なメールアドレス形式であるべきです。"),
    phone: z.string().optional().nullable(),
    linkedin: z.string().optional().nullable().describe("候補者のLinkedInプロフィールのURL。有効なURLであるべきです。"),
  }).optional().nullable(),
  summaryOrObjective: z.string().optional().nullable(),
  education: z.array(EducationEntrySchema).optional().nullable(),
  professionalExperience: z.array(ExperienceEntrySchema).optional().nullable(),
  skills: z.array(z.string()).optional().nullable().describe('履歴書全体から抽出/推論されたスキル名のリスト。情報がない場合は空配列またはnull。'),
  certificationsAndLicenses: z.array(z.string()).optional().nullable(),
  // ...
}).describe('履歴書分析ツールによって抽出・構造化された情報。');

工夫した点:

  • 日本語での describe: 各フィールドの説明を日本語で記述することで、LLMへの指示(特にシステムプロンプト内でスキーマ構造を示す際)がより自然になり、LLMが各フィールドの意味を理解しやすくなることを期待しました。
  • optional().nullable() の活用: LLMの出力は必ずしも安定的ではなく、特定の情報を見つけられない場合にキー自体を省略したり、値として null を返したりすることがあります。スキーマ側でこれを許容することで、些細な出力の揺れによるバリデーションエラーを減らし、システムの堅牢性を高めました。例えば、jobTitle が履歴書に明記されていないケースを考慮し、オプショナルかつnull許容としました。
  • enum の適切な使用: CommentGenerationOutputSchemarecommendationLevel のように、取りうる値が限定的なフィールドは z.enum([...]) を使用し、LLMが返す値を特定の選択肢に制限するようにしました。これには、LLMへの指示で選択肢を明示することも重要です。

当初、Zodの .url().email() といったフォーマット指定子を使用していましたが、これらがGoogle Gemini APIのFunction Calling機能が期待するスキーマの format と互換性がなく、APIエラー(400 Bad Request)を引き起こすことが判明しました。そのため、これらの指定子を削除し、代わりに .describe() で期待する形式(例:「有効なURL文字列であるべきです」)を記述するアプローチに変更しました。これにより、APIとの互換性問題を解消できました。

2. Tool (createTool) の実装: 専門機能のカプセル化

各専門エージェントのコア機能は、Mastraの createTool を使ってツールとして実装しました。これにより、Master Agentからの呼び出しや、ワークフロー内でのステップとしての利用が容易になります。

a. resumeAnalysisTool の実装例 (tools/recruitment_tools.ts より抜粋):

// tools/recruitment_tools.ts
import { createTool, ToolExecutionContext } from '@mastra/core/tools';
import { z } from 'zod';
import { google, generateText } from '@ai-sdk/google';
import { ResumeAnalysisInputSchema, ResumeAnalysisOutputSchema } from '../schemas';

function getResumeAnalysisSchemaDescriptionForLLM(): string {
    // LLMに期待するJSON構造をテキストで説明 (前回の回答を参照)
    return `{ /* ... スキーマ構造の説明 ... */ }`;
}

export const resumeAnalysisTool = createTool({
    id: 'analyze-resume-details',
    description: '履歴書テキストを分析し、学歴、職歴、スキルなどの構造化情報を抽出します。特に資格情報から関連スキルを推論します。',
    inputSchema: ResumeAnalysisInputSchema,
    outputSchema: ResumeAnalysisOutputSchema,
    execute: async ({ context, runtimeContext }: ToolExecutionContext<z.infer<typeof ResumeAnalysisInputSchema>>) => {
        const { resumeText } = context;
        const logger = runtimeContext?.logger || console;
        logger.info(`[ResumeAnalysisTool] Received resume text (length: ${resumeText.length}).`);

        const schemaDescriptionForLLM = getResumeAnalysisSchemaDescriptionForLLM();
        const systemPrompt = `あなたは、提供された履歴書テキストから情報を正確に構造化して抽出する、経験豊富な日本語対応のHR分析エキスパートです。
以下の指示に従って、履歴書の内容を分析し、指定されたJSON形式で日本語の値を維持したまま応答してください。ただし、JSONのキー名は英語である必要があります。
JSONオブジェクトの構造は以下の通りです。この構造に厳密に従ってください。
\${schemaDescriptionForLLM}
重要な注意点:
- 応答は、上記の構造に合致する有効なJSONオブジェクトのみとしてください。
- 履歴書に記載されている情報を忠実に抽出してください。
- オプショナルなフィールドや配列で該当情報がない場合は、キー自体を省略するか空配列 [] を使用してください。'.nullable()' がスキーマで許可されていない限り 'null' は避けてください。
- 「skills」フィールドには、履歴書全体から読み取れるスキルに加え、「certificationsAndLicenses」からも関連スキルを推論して含めてください。`;

        const userPromptContent = \`以下の履歴書テキストを分析し、指示されたJSON形式で情報を抽出してください:\\n\\n---履歴書テキスト開始---\\n\${resumeText}\\n---履歴書テキスト終了---`;

        try {
            const model = google('gemini-1.5-pro-latest'); // 複雑な抽出にはProを推奨
            logger.info('[ResumeAnalysisTool] Sending request to LLM for resume analysis...');
            const { text } = await generateText({
                model: model,
                system: systemPrompt,
                prompt: userPromptContent,
            });
            logger.info('[ResumeAnalysisTool] Raw LLM response received.');

            let cleanedText = text.trim().replace(/^```(?:json)?\\s*|```\\s*$/g, '').trim();
            if (!cleanedText) throw new Error('LLM returned an empty response.');
            
            let parsedJson = JSON.parse(cleanedText);
            const validation = ResumeAnalysisOutputSchema.safeParse(parsedJson);

            if (validation.success) {
                logger.info('[ResumeAnalysisTool] Parsed and validated resume analysis successfully.');
                return validation.data;
            } else {
                logger.error('[ResumeAnalysisTool] Zod validation failed:', JSON.stringify(validation.error.flatten().fieldErrors, null, 2));
                logger.error('[ResumeAnalysisTool] Parsed JSON that failed validation:', JSON.stringify(parsedJson, null, 2));
                throw new Error(`LLM output schema validation failed.`);
            }
        } catch (error) { /* ... エラー処理 ... */ }
    },
});

工夫した点:

  • LLMプロンプトへのスキーマ構造の埋め込み: getResumeAnalysisSchemaDescriptionForLLM() のようなヘルパー関数を用意し、期待するJSON構造をテキストでLLMに明確に伝えることで、出力形式の安定性を高めました。
  • LLMの役割と指示の具体化: システムプロンプト内で、LLMのペルソナ(例: 「経験豊富なHRアナリスト」)、タスク、出力形式、注意点を詳細に指示しました。特に、「資格からスキルを推論する」といった具体的な処理を明記することで、より高度な情報抽出を試みました。
  • JSON出力の安定化:
    • LLMに対して「JSONオブジェクトのみを返す」ことを強く指示。
    • 応答テキストからMarkdownのコードブロック指示子(```json ... ```)を除去する処理を追加。
    • それでも発生するJSONパースエラーやZodバリデーションエラーに対しては、エラーログにLLMの生に近い出力やパース後のJSONオブジェクトを含めることで、デバッグを容易にしました。
  • モデルの選択: 履歴書のような自由形式の長文テキストから複雑な構造を抽出するには、gemini-2.5-flash-preview-04-17 のような高性能なモデルが適していると判断しました。Flashモデルでは指示の遵守度や抽出精度が低下する可能性がありました。
  • runtimeContext.logger の利用: Mastraの実行コンテキストから提供されるロガー(または console へのフォールバック)を利用し、ツールの動作状況を記録するようにしました。

3. Master Agent のオーケストレーションロジック (agents/MasterReviewAgent.ts)

Master Agentは、ユーザーとの対話、情報収集、そして上記で定義した専門ツール群の逐次的な呼び出しと結果の統合を担当します。このオーケストレーションは、Agentの instructionsに記述されます。

// agents/Recruiter.ts

import { AgentNetwork } from "@mastra/core/network";
import { google } from "@ai-sdk/google";
import { commentGeneratorAgent } from "../agents/comment-generator";
import { resumeAnalyzerAgent } from "../agents/ResumeAnalyzer";
import { skillMatchEvaluatorAgent } from "../agents/SkillMatchEvaluatorAgent";
import { Memory } from "@mastra/memory";
import { LibSQLStore, LibSQLVector } from "@mastra/libsql";
import { fastembed } from "@mastra/fastembed";

const memory = new Memory({
    embedder: fastembed, // Configure the embedder
    storage: new LibSQLStore({
        url: 'file:../../memory.db' // relative path from bundled .mastra/output dir
    }),
    vector: new LibSQLVector({
        connectionUrl: 'file:../../memory.db' // relative path from bundled .mastra/output dir
    }),

    // Memory configuration options
    options: {
        // Number of recent messages to include
        lastMessages: 20,

        // Semantic search configuration
        semanticRecall: {
            topK: 3, // Number of similar messages to retrieve
            messageRange: {
                // Messages to include around each result
                before: 2,
                after: 1,
            },

        },
        threads: {
            generateTitle: false
        },
        // Working memory configuration
        workingMemory: {
            enabled: true,
            use: "tool-call",
            template: `
                You are a highly skilled recruiting orchestrator, coordinating multiple specialized AI agents to efficiently manage the evaluation process of job candidates.
                Your task is to ensure that all relevant information is collected and passed between agents seamlessly, leading to a comprehensive evaluation summary.
                You must not stop until the final evaluation summary is delivered to the user.
                Always refer to the conversation history or other agents' stored information if any information is missing from the user.
                If still insufficient, inform the user and ask for the necessary details.`,
        },
    },
});

export const RecruitingNetwork = new AgentNetwork({
    name: "Recruiting Network",
    instructions: `あなたは、複数の専門AIエージェントを統括し、採用候補者の書類選考プロセス全体を効率的かつ正確に管理する、極めて高度なHR採用オーケストレーターです。
あなたの使命は、ユーザー(人事担当者)から提供される候補者の履歴書と募集要項に基づき、一連の専門エージェントを連携させ、最終的に包括的でプロフェッショナルな「採用候補者評価レポート」を生成し、ユーザーに提示することです。

**あなたの可能な行動の一つは以下の厳密な思考プロセスと実行手順です。途中でユーザーに追加の確認を求めたり、処理を中断したりすることなく、最終報告まで一気通貫で実行することを最優先とします。**
プロセス中はユーザーに常に情報を提供し、必要なデータを収集し、各エージェントの結果を正確に伝達してください。

**利用可能な専門エージェントとその呼び出し名:**
* **履歴書分析エージェント**: 'Resume Analyzer Agent'
* **スキルマッチ度評価エージェント**: 'Skill Match Evaluator Agent'
* **コメント下書き生成エージェント**: 'Comment Generator Agent'

**思考プロセスと実行手順:**

    1.  **初期情報収集**:
        - ユーザーから「候補者の履歴書テキスト」、「職務要件」の各情報(職務タイトル、必須スキルリストなど)を収集します。
        - 収集した「職務要件」は、内部的に **'currentJobRequirements'** というデータとして保持してください。
        - 収集した「履歴書テキスト」は、内部的に **'currentResumeText'** というデータとして保持してください。
        - 候補者名も取得できれば **'currentCandidateName'** として保持してください。
        - 全ての必須情報が揃ったら、ユーザーに「分析を開始します。少々お待ちください。」と一度だけ伝え、直ちにステップ2に進んでください。

    2.  **履歴書分析の実行**:
        - 保持している **'currentResumeText'** を使って、'Resume Analyzer Agent' を呼び出し、分析を依頼します。
        - 'Resume Analyzer Agent' への入力文字列は以下の形式です:
          \`"以下の候補者の履歴書テキストを分析し、構造化された情報を抽出してください: \n\n\${currentResumeText}"\`
        - 'Resume Analyzer Agent' から返された構造化された「履歴書分析結果」オブジェクトを、内部的に **'analyzedResumeData'** というデータとして正確に記録し、保持してください。
        - **もしこのステップでエラーが発生した場合は、ユーザーに「履歴書の分析中にエラーが発生しました: [エラー内容]。申し訳ありませんが、これ以上の処理は続行できません。」と伝え、プロセスを終了してください。** エラーがなければ結果を保持し、次のステップに進みます。

    3.  **スキルマッチ評価の実行**:
        - ステップ1でユーザーから得た「募集職種名」「必須スキル」「歓迎スキル」「最低経験年数」「職務内容詳細」「企業文化キーワード」を元に、\`JobRequirementsSchema\` に準拠した「職務要件オブジェクト」を内部で正確に構築してください。
        - **重要:'minimumExperienceYears' については、ユーザーが「未経験者歓迎」や「経験不問」と述べた場合、または年数指定がなかった場合は、必ず数値の \`0\` を設定してください。具体的な年数指定があればその数値を設定します。絶対に \`null\` や \`undefined\` をこのフィールドに設定しないでください。**
        - ステップ2で得られた「履歴書分析結果」と、上記で構築した「職務要件オブジェクト」を準備します。
        - **\`Skill Match Evaluator Agent\`** (ツール名: \`evaluateSkillMatchWithRequirements\`)を呼び出し、スキルマッチ評価を依頼します。
        - このエージェントへの入力 (input) は、JSON形式で「職務要件オブジェクト」と「履歴書分析結果オブジェクト」の両方を含む文字列とするべきです。
        - 'Skill Match Evaluator Agent' から返された「スキルマッチ評価結果」オブジェクトを、内部的に **'skillMatchResultData'** というデータとして正確に記録し、保持してください。
        - **もしこのステップでエラーが発生した場合は、ユーザーに「スキルマッチング評価中にエラーが発生しました: [エラー内容]。履歴書分析の結果のみに基づいてご判断いただくか、後ほど再試行しますか?」と尋ね、指示を仰いでください。この後のコメント生成は行いません。** エラーがなければ結果を保持し、次のステップに進みます。

    4.  **評価コメント生成の実行**:
        - 内部に保持している **'currentCandidateName'**(もしあれば)、**'currentJobRequirements'** の中の「職務タイトル」、**'analyzedResumeData'**、そして **'skillMatchResultData'** を準備します。
        - 'Comment Generator Agent' を呼び出し、評価コメントのドラフト作成を依頼します。
        - 'Comment Generator Agent' への入力文字列は、以下のような形式で生成してください:
          \`"以下の情報を総合的に判断し、書類選考を通過させて最初の面接に進めるべきかどうかの観点から、指定されたJSON形式で評価コメントのドラフトを作成してください。\n\n候補者名: \${currentCandidateName || '情報なし'}\n職務タイトル: \${currentJobRequirements.jobTitle}\n履歴書分析結果:\n\${JSON.stringify(analyzedResumeData)}\nスキルマッチ評価結果:\n\${JSON.stringify(skillMatchResultData)}"\`
        - 'Comment Generator Agent' から返された「コメントドラフト」オブジェクトを、内部的に **'commentDraftData'** というデータとして正確に記録し、保持してください。
        - **もしこのステップでエラーが発生した場合は、ユーザーに「評価コメントの生成中にエラーが発生しました: [エラー内容]。履歴書分析とスキルマッチ評価の結果はございますので、そちらを参考にしてください。」と伝え、プロセスを終了します。**

5.  **最終報告書の生成と提示**:   
    - 内部に保持している **'commentDraftData'**、**'skillMatchResultData'**、**'analyzedResumeData'**、そして **'currentJobRequirements'** の情報を使って、指示されたマークダウン形式のレポートを生成し、ユーザーに提示してください。
    * 以下のマークダウン形式のテンプレートに従って、ユーザーに最終的な「採用候補者評価レポート」を**一度の応答でまとめて**提示してください。
    * 各プレースホルダー '[プレースホルダー]' には、対応する情報を正確に埋め込んでください。
    * 情報が存在しないオプショナルな項目については、「該当なし」と記述するか、その項目やセクション自体を適切に省略して、レポートが冗長にならないようにしてください。

    markdown
📄 **採用候補者評価レポート**

**候補者氏名:** [候補者名 例: 山田 太郎]
**評価対象職務:** [職務名 例: 店舗運営職(正社員)]
**評価日:** [評価日 YYYY年MM月DD日]

---

### 🔑 **評価ハイライト**

* **総合推薦レベル:** **[コメントドラフトの "recommendationLevel"]**
* **全体的な適合度:** [スキルマッチ評価結果の "overallMatchPercentage"]%
* **特筆すべき強み:** [コメントドラフトの "keyStrengths" から主要なものを1~2点、簡潔に記述。または「該当なし」]
* **主な確認事項 (面接時):** [コメントドラフトの "areasForInterviewClarification" から主要なものを1~2点、簡潔に記述。または「特になし」]

---

### 📝 **評価サマリー**

[コメントドラフトの "overallAssessment" をここに記述。候補者の全体的な適合性、強み、および面接で確認すべき点を簡潔にまとめる。推薦レベルの根拠もここに含める。]

---

### 🛠️ **スキル・経験 詳細評価**

#### マッチング概要
[スキルマッチ評価結果の "matchSummary" をここに記述。]

#### 必須スキル
[スキルマッチ評価結果の "requiredSkillsAnalysis" 配列の各要素について、以下のように箇条書きで表示]
* **[スキル名1]:** [評価レベル (例: 高い/中程度/低い/該当なし/評価不能)] - [簡単な根拠やコメント]
* **[スキル名2]:** [評価レベル] - [簡単な根拠やコメント]
    * ...

#### 歓迎スキル
[スキルマッチ評価結果の "preferredSkillsAnalysis" 配列の各要素について、同様に表示。該当がなければ「特になし」。]
* **[スキル名1]:** [評価レベル] - [簡単な根拠やコメント]
    * ...

#### 経験年数
* **要求:** [職務要件の "minimumExperienceYears"]年 (未経験歓迎の場合は「0年(未経験者歓迎)」など)
* **候補者(推定):** [スキルマッチ評価結果の "experienceYearsAnalysis.foundCandidateYears"]
* **評価:** [スキルマッチ評価結果の "experienceYearsAnalysis.matchStatus"]

---

### 📄 **候補者情報サマリー (履歴書より)**

* **最終学歴:** [履歴書分析結果の education 配列の最新情報から機関名、学位/学科、卒業年を記述]
* **直近の職歴:** [履歴書分析結果の professionalExperience 配列の最新情報から会社名、役職、期間を記述]
* **本人希望/自己PRの要点:** [履歴書分析結果の summaryOrObjective を簡潔に記述]
* **保有資格/免許:** [履歴書分析結果の "certificationsAndLicenses" リストをカンマ区切り、または箇条書きで簡潔に表示]
* **抽出スキル:** [履歴書分析結果の "skills" リストから主要なものを数点ピックアップしてカンマ区切りで表示]

---

### 🎤 **面接での推奨質問例**
[コメントドラフトの "suggestedInterviewQuestions" 配列の各要素を箇条書きで表示。該当がなければ「特になし」。]
* 質問1
* ...

---

### 🚀 **次のステップ提案**
[コメントドラフトの "suggestedNextSteps" を記述。]

---

    **次のステップ提案 (もしあれば):**
    [コメントドラフトの "suggestedNextSteps" を記述。このセクション自体がオプショナル。]

    - 最終報告の後、「この評価レポートに関して、さらに詳しく知りたい項目はございますか?」と尋ね、ユーザーの追加の質問に対応してください。

**あなたの応答に関する一般的なガイドライン:**
- あなたはあくまでオーケストレーターであり、実際の分析や評価は専門エージェントが行います。その結果を正確に伝達することに集中してください。
- ユーザーが提供した情報(履歴書、職務要件)を勝手に変更したり、個人的な意見を加えたりしないでください。`,
    model: google('gemini-2.5-flash-preview-04-17'),
    agents: [commentGeneratorAgent, skillMatchEvaluatorAgent, resumeAnalyzerAgent],
});

工夫した点:

  • 逐次処理の明確な指示: 各専門ツールをどの順番で、どのような入力で呼び出すかを具体的に指示しました。
  • エラーハンドリングの指示: 各ツール呼び出しでエラーが発生した場合の基本的な対応(ユーザーへの通知とプロセス終了)を指示に含めました。
  • ユーザーへの応答タイミング: 途中のステップではユーザーへの確認を挟まず、全ての分析が完了してから最終報告を行うように指示することで、Mastraの会話ターン管理エラーを避けることを意図しました。

4. デバッグとエラーハンドリングの具体例

開発中、特にLLMの出力を扱う部分では、予期せぬ形式の応答によるエラーが頻発しました。

  • Zodバリデーションエラー:
    • 現象: LLMがスキーマ定義と異なるキー名でJSONを返したり、enum にない値を返したり、必須配列が空で返されたりする。
    • ログ: [ToolName] Failed to validate LLM JSON output against SchemaName: { "field": ["Error message"] }[ToolName] Parsed JSON that failed validation: { ... } を出力。
    • 対処:
      1. LLMの生に近い応答(cleanedText)とパース後のJSON(parsedJson)をログに出力し、具体的な不一致箇所を特定。
      2. スキーマ定義をより寛容にする(例: optional().nullable() の追加、enum 値の見直し、配列の .min(1) 削除)。
      3. LLMへのシステムプロンプトを修正し、期待するJSON構造、キー名、値の形式(特に enum や空配列の扱い)をより厳密に指示。
  • Google APIからの500/503エラー:
    • 現象: RetryError [AI_RetryError]: Failed after 3 attempts. Last error: An internal error has occurred / The model is overloaded.
    • 対処: 時間をおいての再試行が基本。頻発する場合は、より安定している可能性のあるモデル(例: gemini-1.5-pro-latest)への変更を検討。リクエストペイロード(プロンプト長など)が原因でないか確認。
  • Mastraの会話ターンエラー:
    • 現象: Error executing tool ...: Please ensure that function call turn comes immediately after a user turn or after a function response turn.

対処: Master Agentの指示を見直し、一連のツール呼び出しが完了するまでユーザーへのテキスト応答を控えるか、明確に「ユーザーの応答を待たずに次のツールを呼び出す」と指示。

コードの全文は本記事の一番下のリンクから閲覧できます。


評価

想定シナリオ

大手総合商社の募集要項をChat GPTで生成してもらい、それに応じて架空の就活生の履歴書を分析・評価してもらいました。

職務要件

募集職種名

総合職(General Career Track) 〇〇(会社名)は、全社的な視点で事業戦略・投資・事業経営を担う中核人材として、グローバルにキャリアを展開するポジションです。配属部門は入社後の面談や適性により決定されます(例:事業開発部門、投資管理部門、マーケティング部門、経営企画、人事、法務など)。

必須スキル・資格

2026年3月までに国内外の四年制大学または大学院を卒業・修了(見込み)の方 学部・学科不問 ビジネスレベルの日本語(母語またはJLPT N1相当)および英語(目安:TOEIC850点以上) 多文化環境での協働・チームワークへの柔軟性 主体的に課題を見出し、行動を起こせる思考力と実行力

歓迎スキル・経験

海外大学・留学経験または海外インターン経験 高度な英語運用能力(TOEFL100点、IELTS7.0以上など) 第二外国語スキル(中国語、スペイン語、フランス語など) 金融・経済・経営に関する基礎知識や専攻 リーダーシップを発揮した学生団体・プロジェクト経験 各種ビジネスコンテストや論文コンテスト等での入賞経験 総合商社やグローバル企業でのインターンシップ参加経験

最低限必要な経験年数

新卒採用のため 実務経験は不問。ただし、実践的な経験(長期インターンや海外活動)は高く評価されます。

初任給

  • 大学卒:月給330,000円
  • 大学院卒:月給365,000円

勤務地

  • 【雇入れ直後】東京本社または国内の支社・支店・関係会社等。
  • 【変更の範囲】国内・海外全域。

勤務時間・制度

  • フレックスタイム制度(休憩60分/日)。
  • 所定時間外労働:有。
  • テレワーク制度:有。
  • 短時間勤務制度:有(育児・介護)。

この募集要項に加え、候補者Aの履歴書を入力して分析してもらう

入力と結果

ChatGPTに3つの履歴書を生成してもらいました。3つの履歴書は、「あまり優秀でない大学生」、「一般的な大学生」、「優秀な大学生」という想定で作成してもらいました。

履歴書1

基本情報

ふりがな:やまだ たいち

氏名:山田 太一

性別:男

生年月日:2002年4月5日(満22歳)

現住所:〒123-4567 東京都足立区西新井1-2-3

電話番号:090-1234-5678

E-mail:taichi.yamada@example.com

学歴・職歴

  • 2018年4月 東京都立墨田工業高等学校 入学
  • 2021年3月 東京都立墨田工業高等学校 卒業
  • 2021年4月 私立東京経営短期大学 総合ビジネス学科 入学
  • 2023年3月 私立東京経営短期大学 卒業
  • 2023年4月 株式会社 Amazon アルバイト(倉庫仕分け業務)勤務開始
  • 2025年3月 株式会社 Amazon アルバイト退職予定

免許・資格

  • 2021年8月 普通自動車運転免許

本人希望記入欄

特に希望はございませんが、早朝勤務や力仕事にも対応可能です。正社員として長期的に働き、仕事を通じて成長していきたいと考えております。

履歴書1の分析結果の総合レポート

📄 採用候補者評価レポート

候補者氏名: 山田 太一 評価対象職務: 総合職(General Career Track) 評価日: 2024年05月18日


🔑 評価ハイライト

  • 総合推薦レベル: 今回は見送り推奨
  • 全体的な適合度: 5%
  • 特筆すべき強み: 体力, 正確性
  • 主な確認事項 (面接時): 特になし

📝 評価サマリー

候補者は必須要件である四年制大学/大学院卒、ビジネスレベルの英語力、および職務内容に関連する経験・スキルをほぼ満たしていません。履歴書から読み取れる経験は総合職の職務内容との関連性が極めて低く、現時点での職務適合度は非常に低いと評価されます。


🛠️ スキル・経験 詳細評価

マッチング概要

候補者は必須要件である四年制大学/大学院卒、ビジネスレベルの英語力、および職務内容に関連する経験・スキルをほぼ満たしていません。歓迎スキルも見られず、職務内容との適合度は非常に低いと評価されます。

必須スキル

  • 2026年3月までに国内外の四年制大学または大学院を卒業・修了(見込み)の方: 低い - 候補者は2023年3月に短期大学を卒業しています。
  • ビジネスレベルの日本語(母語またはJLPT N1相当): 評価不能 - 母語話者である可能性は高いですが、ビジネスレベルであることを示す具体的な情報(職務経験や資格)は履歴書に記載されていません。
  • ビジネスレベルの英語(目安:TOEIC850点以上): 低い - 履歴書に英語力に関する記載がありません。
  • 多文化環境での協働・チームワークへの柔軟性: 評価不能 - 履歴書に多文化環境での経験やチームワークに関する具体的な記載がありません。
  • 主体的に課題を見出し、行動を起こせる思考力と実行力: 評価不能 - 履歴書に主体性や課題解決能力を示す具体的なエピソードの記載がありません。

歓迎スキル

  • 海外大学・留学経験または海外インターン経験: 低い - 該当なし
  • 高度な英語運用能力(TOEFL100点、IELTS7.0以上など): 低い - 該当なし
  • 第二外国語スキル(中国語、スペイン語、フランス語など): 低い - 該当なし
  • 金融・経済・経営に関する基礎知識や専攻: 評価不能 - 短期大学の総合ビジネス学科卒ですが、具体的な知識レベルは不明です。
  • リーダーシップを発揮した学生団体・プロジェクト経験: 低い - 該当なし
  • 各種ビジネスコンテストや論文コンテスト等での入賞経験: 低い - 該当なし
  • 総合商社やグローバル企業でのインターンシップ参加経験: 低い - 該当なし

経験年数

  • 要求: 0年(未経験者歓迎)
  • 候補者(推定): 約0年
  • 評価: 不足

📄 候補者情報サマリー (履歴書より)

  • 最終学歴: 私立東京経営短期大学 総合ビジネス学科 卒業 (2023年3月)
  • 直近の職歴: 株式会社 Amazon アルバイト(倉庫仕分け業務) (2023年4月 - 2025年3月予定)
  • 本人希望/自己PRの要点: 特に希望はございませんが、早朝勤務や力仕事にも対応可能です。正社員として長期的に働き、仕事を通じて成長していきたいと考えております。
  • 保有資格/免許: 普通自動車運転免許
  • 抽出スキル: 体力, 正確性, 時間管理, 勤勉さ, 成長意欲

🎤 面接での推奨質問例

特になし


🚀 次のステップ提案

必須要件を満たしておらず、職務適合度が極めて低いため、書類選考にて見送りとします。

履歴書2

基本情報

ふりがな:さとう はな

氏名:佐藤 花

性別:女

生年月日:2001年9月12日(満22歳)

現住所:〒234-5678 神奈川県横浜市港北区綱島西2-4-5

電話番号:080-2345-6789

E-mail:hana.sato@example.com

学歴・職歴

  • 2017年4月 神奈川県立港北高等学校 入学
  • 2020年3月 神奈川県立港北高等学校 卒業
  • 2020年4月 私立東日本大学 経済学部 経済学科 入学
  • 2024年3月 私立東日本大学 卒業(予定)
  • 2022年6月 株式会社Deliカフェ アルバイト(接客業務)開始
  • 2024年2月 株式会社Deliカフェ アルバイト退職

免許・資格

  • 2018年6月 実用英語技能検定 準2級
  • 2023年4月 MOS Word・Excel(2023年取得)

本人希望記入欄

人と接する仕事に興味があり、サービス業や営業職にて自分の対人能力を活かしたいと考えております。勤務地には特にこだわりはありません。

履歴書2の分析結果の総合レポート

📄 採用候補者評価レポート

候補者氏名: 佐藤 花 評価対象職務: 総合職(General Career Track) 評価日: 2024年06月07日


🔑 評価ハイライト

  • 総合推薦レベル: 今回は見送り推奨
  • 全体的な適合度: 30%
  • 特筆すべき強み: 四年制大学卒業要件を満たしている, 経済学部での専攻経験がある
  • 主な確認事項 (面接時): ビジネスレベルの英語力(TOEIC850点以上)があるか, 多文化環境での協働経験や適応力があるか

📝 評価サマリー

候補者は四年制大学卒業および経済学部専攻という学歴要件は満たしていますが、総合職に必須とされるビジネスレベルの英語力、多文化環境での協働経験、主体性・実行力といった主要スキルが履歴書からは確認できません。また、職務内容への興味関心も不明であり、全体的な職務適合度は低いと評価されます。


🛠️ スキル・経験 詳細評価

マッチング概要

候補者は四年制大学卒業および経済学部専攻という点で一部要件を満たしていますが、ビジネスレベルの英語力、多文化環境での協働経験、主体性・実行力といった総合職に必須とされる主要スキルについて、履歴書からは確認できません。また、候補者の興味関心が職務内容(事業戦略・投資・経営)と合致しているか不明であり、全体的な適合度は低いと評価されます。

必須スキル

  • 国内外の四年制大学または大学院を卒業・修了(見込み): 高い - 私立東日本大学 経済学部 経済学科 2024年3月卒業
  • ビジネスレベルの日本語(母語またはJLPT N1相当): 高い - 履歴書が日本語で記述されており、日本人名であることから母語話者と推定
  • ビジネスレベルの英語(目安:TOEIC850点以上): 評価不能 -
  • 多文化環境での協働・チームワークへの柔軟性: 低い - アルバイト経験はあるが、多文化環境での経験は不明
  • 主体的に課題を見出し、行動を起こせる思考力と実行力: 評価不能 - 履歴書に具体的なエピソードの記載なし

歓迎スキル

  • 海外大学・留学経験または海外インターン経験: 該当なし -
  • 高度な英語運用能力(TOEFL100点、IELTS7.0以上など): 該当なし -
  • 第二外国語スキル(中国語、スペイン語、フランス語など): 該当なし -
  • 金融・経済・経営に関する基礎知識や専攻: 高い - 経済学部経済学科卒業
  • リーダーシップを発揮した学生団体・プロジェクト経験: 該当なし -
  • 各種ビジネスコンテストや論文コンテスト等での入賞経験: 該当なし -
  • 総合商社やグローバル企業でのインターンシップ参加経験: 該当なし -

経験年数

  • 要求: 0年(未経験者歓迎)
  • 候補者(推定): 0年
  • 評価: 満たしている

📄 候補者情報サマリー (履歴書より)

  • 最終学歴: 私立東日本大学 経済学部 経済学科 2024年3月卒業(予定)
  • 直近の職歴: 株式会社Deliカフェ アルバイト(接客業務) 2022年6月 - 2024年2月
  • 本人希望/自己PRの要点: 人と接する仕事に興味があり、サービス業や営業職にて自分の対人能力を活かしたいと考えております。勤務地には特にこだわりはありません。
  • 保有資格/免許: 実用英語技能検定 準2級, MOS Word・Excel(2023年取得)
  • 抽出スキル: 該当なし

🎤 面接での推奨質問例

  • ビジネスレベルの英語力(TOEIC850点以上)があるか
  • 多文化環境での協働経験や適応力があるか
  • 主体的に課題を見出し、解決に向けて行動した具体的な経験があるか
  • 総合職の職務内容(事業戦略、投資、経営など)への興味や理解度
  • 履歴書に記載されていないその他のスキルや経験(歓迎スキル含む)

🚀 次のステップ提案

今回は見送り推奨

履歴書3

基本情報

ふりがな:さとう はな

氏名:佐藤 花

性別:女

生年月日:2001年9月12日(満22歳)

現住所:〒234-5678 神奈川県横浜市港北区綱島西2-4-5

電話番号:080-2345-6789

E-mail:hana.sato@example.com

学歴・職歴

  • 2017年4月 神奈川県立港北高等学校 入学
  • 2020年3月 神奈川県立港北高等学校 卒業
  • 2020年4月 私立東日本大学 経済学部 経済学科 入学
  • 2024年3月 私立東日本大学 卒業(予定)
  • 2022年6月 株式会社Deliカフェ アルバイト(接客業務)開始
  • 2024年2月 株式会社Deliカフェ アルバイト退職

免許・資格

  • 2018年6月 実用英語技能検定 準2級
  • 2023年4月 MOS Word・Excel(2023年取得)

本人希望記入欄

人と接する仕事に興味があり、サービス業や営業職にて自分の対人能力を活かしたいと考えております。勤務地には特にこだわりはありません。

履歴書3の分析結果の総合レポート

📄 採用候補者評価レポート

候補者氏名: 佐々木 陸 評価対象職務: 総合職(General Career Track) 評価日: 2024年05月20日


🔑 評価ハイライト

  • 総合推薦レベル: 推薦する
  • 全体的な適合度: 90%
  • 特筆すべき強み: 高い学歴と専門知識、卓越した英語力、総合商社・投資銀行でのインターン経験
  • 主な確認事項 (面接時): 主体性や協働性に関する具体的な経験、リーダーシップ経験

📝 評価サマリー

候補者は高い学歴と専門知識、卓越した英語力を有しており、総合職としての高いポテンシャルを示しています。特にグローバルビジネスやM&Aへの強い関心は職務内容と合致しており、インターン経験も評価できます。ただし、主体性や協働性といった行動特性については面接での確認が必要です。


🛠️ スキル・経験 詳細評価

マッチング概要

必須要件を概ね満たしており、特に学歴、専門知識、英語力、関連インターン経験において高い適合性が見られます。グローバルな事業開発やM&Aへの強い関心も職務内容と合致しており、ポテンシャルは非常に高い候補者です。一部、履歴書からは判断しきれない必須スキル(協働性、主体性)や歓迎スキルの有無については、面接での確認が必要です。

必須スキル

  • 2026年3月までに国内外の四年制大学または大学院を卒業・修了(見込み): 高い - 東京大学大学院に在籍/進学予定のため合致の可能性が高い
  • 学部・学科不問: 高い - 東京大学経済学部・経済学研究科に在籍しており要件を満たす
  • ビジネスレベルの日本語(母語またはJLPT N1相当): 高い - 日本の大学・大学院に在籍しており母語話者と推定
  • ビジネスレベルの英語(目安:TOEIC850点以上): 高い - スキルに「ビジネスレベル英語」「高度な英語力」と記載あり
  • 多文化環境での協働・チームワークへの柔軟性: 中程度 - グローバルビジネスや海外駐在への関心、インターン経験から期待できる
  • 主体的に課題を見出し、行動を起こせる思考力と実行力: 評価不能 - 履歴書からは具体的なエピソードが不明

歓迎スキル

  • 海外大学・留学経験または海外インターン経験: 見つからず - 該当なし
  • 高度な英語運用能力(TOEFL100点、IELTS7.0以上など): 高い - TOEFL iBT 109点, IELTS 7.5を取得
  • 第二外国語スキル(中国語、スペイン語、フランス語など): 見つからず - 該当なし
  • 金融・経済・経営に関する基礎知識や専攻: 高い - 東京大学経済学部・経済学研究科に在籍、スキルに「経済学」「経営学」「簿記」「会計知識」と記載あり
  • リーダーシップを発揮した学生団体・プロジェクト経験: 見つからず - 該当なし
  • 各種ビジネスコンテストや論文コンテスト等での入賞経験: 見つからず - 該当なし
  • 総合商社やグローバル企業でのインターンシップ参加経験: 高い - 三井物産、ゴールドマン・サックスでのインターン経験あり

経験年数

  • 要求: 0年(未経験者歓迎)
  • 候補者(推定): 該当なし
  • 評価: 満たしている

📄 候補者情報サマリー (履歴書より)

  • 最終学歴: 東京大学大学院 経済学研究科 (在学中)
  • 直近の職歴: ゴールドマン・サックス サマーインターン (投資銀行部門)
  • 本人希望/自己PRの要点: 新興国事業開発やM&A戦略に携わり、グローバルに価値を創出する商社パーソンとして貢献したい。特にアジア市場を対象とした投資案件や事業経営に強い関心。将来的には海外駐在を希望。
  • 保有資格/免許: TOEFL iBT 109点, 日商簿記2級, IELTS 7.5, 経営学検定 中級
  • 抽出スキル: 経済学, 経営学, 市場分析, 投資銀行業務, M&A戦略, グローバルビジネス, 高度な英語力

🎤 面接での推奨質問例

  • これまでの経験で、主体的に課題を見つけ、解決に向けて行動した具体的なエピソードを教えてください。
  • チームや多文化環境で協働した経験について、特に困難だった点や工夫した点を教えてください。
  • 学生時代のリーダーシップ経験や、学業以外で最も力を入れた活動について詳しく教えてください。

🚀 次のステップ提案

一次面接に進め、履歴書からは読み取れない行動特性や経験について深掘りする。

アウトプットの画面

期待値との比較

自分の想定よりも評価は厳しかったように思えます。最初は書類選考段階の通過の可否ではなく採用の可否を考えているからだと思い、System Promptを繰り返し変更したり、4つほど違う履歴書を生成して評価にかけたのですが、最終的なプロダクトもやや辛口の評価だったように感じます。

とはいえ採用の可否に理論値のようなものは存在しないので、新卒の採用を行ったことのない身としては評価の妥当性は測れないというのが正直なところです。Agent Networkの定番である書類選考のエージェントを作成したのですが、期待値との評価を行うのであれば別のトピックを選べばよかったと思います。

Mastra Agent Networkを使ってみた感想

まず練習でWorkflowを作成してからAgent Networkに着手したのですが、Agent Networkの作成の途中に「これ Workflow でよくないか?」と思いました。Agent Networkはある意味 Master Agent が自らWorkflowを作成するのが特徴で、より柔軟で自律的にタスクをこなせるのが強みなのですが、結局は Agent Network の作成者が一連の流れを、ある程度頭の中で思い描きながら個々の Agent を作成しているのが実際のところなので Agent Network が自ら作成した Workflow もそれに類似したものになってしまうのがオチではないでしょうか。

Agentを大量に作成すれば、多様なタスクを人間の固定観念に捉われることなく柔軟かつ自律的にこなせるかもしれませんが、大量のAgentを管理できるほどのスペックが現状で Agent Network と Master Agent にあるのか、そしてトークンのコストを考えるとコストパフォーマンスが妥当なのかは悩ましいなと Mastra を扱いながら思いました。

Agent NetworkはWorkflowと違ってデバッグの多くはツールの System Prompt や、Agent の Instructionsをいじることが多かったです。LLMの内部処理にエラーの原因があるとき、デバッグはかなり面倒でした。指示の細かい表現を変えたり、特異的なバグに対する対処法を書き加えたりする必要があり、そこまで複雑な処理を行うAgentを作成したわけでもないのに指示文はかなりの分量になってしまいました。

Agent Networkは2025年5月26日現在では実験的に実装されている機能なので、今後のアップデート次第では画期的なシステムになる可能性は大いに秘めていると感じています。Workflowによる安定した処理と、Networkによる動的な判断を組み合わせることで、より実務に適したAI活用が見込めるのではないでしょうか。いつの日か、Agent Networkが有効利用されることを期待しています。

📝 まとめ 〜Mastra Agent Networkを0から構築してみて〜

本記事では、Mastra上で「Agent Network」を構築し、採用候補者の書類選考を補助するAIエージェントチームを試作・検証しました。エージェント同士が協調して履歴書を分析し、評価コメントを生成する構成により、人事業務の一部を自動化できる可能性を確認できました。

特に印象的だったのは、WorkflowとAgent Networkの違い。前者が「人間が定めた手順」に基づいて動作するのに対し、後者ではマスターエージェントが各サブエージェントを動的に呼び出すことで、より柔軟なプロセスが実現されます。とはいえ、現時点では依然として開発者が流れを設計する必要があり、完全にAIがシナリオを構築しているとは言い難い点もありました。

また、評価レポートでは意図よりも「辛口」な結果が出る傾向があり、システムプロンプトの微調整や複数の履歴書による検証が必要でした。これにより、期待と実際の出力とのギャップをどう評価すべきかという、AI活用における普遍的な課題にも直面しました。

実装を通して、Agent Networkは今後の進化が非常に楽しみな領域である一方、現時点ではデバッグの難しさ、プロンプト管理の煩雑さ、運用コストの懸念といった課題も明確になりました。とはいえ、こうした実験的な取り組みからこそ、将来の業務AI活用の突破口が見えてくると感じています。

今後のアップデートによって、より自律的かつスケーラブルなAgent Networkが誕生することを期待しつつ、実務活用への応用可能性を引き続き探っていきたいと思います。

オープン開発チーム

Discussion