作りながら学ぶAIエージェント

第4回Evalでエージェントの品質を改善しよう
~計測→分析→改善→再計測⁠Evalsで応答品質を定量化する

Mastra StudioのExperiments画面 — スコアラー別にベースラインと改善後のスコアを比較できる

はじめに~Mastra Evalsで評価機能を実装する

ここまで3回にわたって、メール返信エージェントを段階的に構築してきました。第2回でAgent/Tools/Slack連携/Workflow/HITLの基本形を作り、第3回でMemoryによるパーソナライズとGuardrailsによる安全性を追加しました。機能面では、かなり実用的なエージェントに仕上がっています。

しかし、ここで一歩立ち止まって考えてみましょう。

「このエージェントは、本当に良い返信を書けているのだろうか?」

この問いに、現状では答えることができません。メモリが効いているように「見える」し、ガードレールが危険を「防いでいるように見える⁠⁠。しかし、それは定性的な印象に過ぎません。

この問いに答えるための一般的な概念・仕組みが、LLM/AIエージェントの分野でEvals(評価)と呼ばれているものです。今回はまずその一般概念を整理し、その上で具体例としてMastra Evalsでの実装を見ていきます。これまでの回と同様、概念中心で進め、コードの細部は公開リポジトリをご覧ください。

AIエージェントの評価(Evals)とは

なぜ AI エージェントの評価は難しいのか

通常のソフトウェアであれば、テストは「特定の入力に対して特定の出力が返る」ことをexpect(f(2)) === 4のように検証すれば足ります。ところがLLMベースのエージェントには次のような特性があり、従来のテスト手法がそのままでは通用しません。

  • 出力が確率的:同じ入力でも毎回違う応答が返る[1]
  • 正解が一つではない「丁寧な返信」⁠相手の要望を拾った返信」のように、評価軸自体が文脈依存
  • 挙動がプロンプトやモデルに強く左右される:一文書き換えただけ、あるいはLLMのモデルやバージョンを変えただけで挙動が変わる

このため、LLM/AIエージェントの世界では「応答品質を数値スコアで継続的に観測する」という独自の評価アプローチが発展してきました。これはMastra固有のものではなく、OpenAI Evals/LangSmithなど主要なエージェント開発ツールが共通して提供している考え方です。

評価関数(Scorer/Evaluator)という基本道具

このアプローチの中心にあるのが評価関数です。フレームワークによってScorer・Evaluatorなど呼び名は違いますが、本質は同じで、入力と出力を受け取り、それに対して0~1(あるいは0~100)の数値スコアを返す関数として定義されます。

たとえば「相手が指定した言語で返信されているか」⁠相手の要望をすべて拾えているか」といった観点を、それぞれ1つの評価関数として定義します。複数の評価関数を束ねて運用することで、エージェントの応答を多面的かつ定量的に観測できるようになります。

評価関数の2つの方式⁠コードベースvsLLMベース

評価関数の中身の作り方には、大きく2つの方式があります。Mastraをはじめ主要なEvalsツールで共通して採用されている分類です。

  • コードベース(Code-based scorer):厳密なマッチングによる決定論的な評価
  • LLMベース(LLM-based scorer):AIを用いた意味的(セマンティック)な評価

実プロジェクトではどちらか一方を選ぶのではなく、低コストなコードベースで広く網を張り、判定が難しい観点だけLLMベースで補うという組み合わせ運用が定石です。

ここまでが「AIエージェントの評価」の一般像です。ここからは、これを実際にMastraでどう実装するかを見ていきます。

Mastraでの実装⁠スコアラー

Mastraでは評価関数をスコアラー(Scorer)と呼び、createScorerで定義します。前節の「コードベースvsLLMベース」という方式の軸に加えて、ライブラリ提供のものを使うか(組み込み⁠⁠、自分で書くか(カスタム)という便宜的な区別もあります。組み込みにはCompletenessScorer(コードベース)やAnswerRelevancyScorer(LLMベース)などがあります。

本連載のメール返信エージェントでは、この組み合わせの中から次の3種類を使い分けています。手軽な「組み込み」から順に紹介していきます。

種類 スコアラー名 評価する観点
組み込み・コードベース completenessScorer 入力情報を応答に網羅できているか
カスタム・コードベース replyLengthScorer 返信の長さが指定した文字数に収まっているか
カスタム・LLMベース requirementCoverageScorer 相手の具体要望を拾えているか

組み込みスコアラーから始める

最も手軽な選択肢が、Mastraが標準で提供する組み込みスコアラーです。@mastra/evals/scorers/prebuiltからインポートし、関数を呼び出すだけで使えます。

import { createCompletenessScorer } from "@mastra/evals/scorers/prebuilt";

export const completenessScorer = createCompletenessScorer();

createCompletenessScorerは内部で compromise というNLPライブラリで名詞・動詞・固有表現を抽出し、入力と出力の重複をスコア化する決定論的なアルゴリズムでLLMを呼ばないため低コストです。他のスコアラーを含めて目的に合う組み込みがあれば、まずそれらを試すのが手軽な選択になります。

カスタムスコアラー⁠コードベース

組み込みにない観点(例:「返信の長さが目標レンジに収まっているか⁠⁠)を測りたいときは、createScorerで自作します。最もシンプルな例として返信メールの長さを評価するreplyLengthScorerの実装例を見てみましょう。

export const replyLengthScorer = createScorer({
  id: "reply-length-scorer",
  name: "Reply Length",
  type: "agent",
}).generateScore(({ run }) => {
  // 返信本文の文字数を取得
  const length = getReplyLength(run);
  // 目標レンジ(80〜800字)に収まっていれば満点
  if (length >= 80 && length <= 800) return 1;
  // レンジ外は外れ具合に応じて 0〜1 のスコアを返す
  return calcLengthScore(length);
});

文字数をカウントしているだけなのでLLMを呼ばず、決定論的かつ低コストで、CIで高速に回せる実装になっています。

カスタムスコアラー⁠LLMベース

コードでは評価しづらい「相手の要望を拾えているか」のような観点は、LLM に判定させます。入出力テキストを抽出→LLMに判定させる→0~1のスコアに正規化の3ステップで構成するのが基本形です。

export const requirementCoverageScorer = createScorer({
  id: "requirement-coverage-scorer",
  type: "agent",
  // ジャッジに使う LLM と判定基準を渡す
  judge: { model: "google/gemini-2.5-flash-lite", instructions: "..." },
})
  // 1) 入出力テキストを取り出す
  .preprocess(({ run }) => ({
    userText: getUserText(run),
    assistantText: getAssistantText(run),
  }))
  // 2) LLM に「要望の総数」と「拾えた数」を判定させる
  .analyze({
    outputSchema: requirementCountSchema,
    createPrompt: ({ results }) => buildJudgePrompt(results),
  })
  // 3) 拾えた数 ÷ 要望の総数 で 0〜1 に正規化
  .generateScore(({ results }) => calcCoverage(results));

同じ3ステップ構成で、相手が指定した言語(例:英語)で返信できているかを判定するtranslationScorerも用意しています。スコアラーの実装全文はsrc/mastra/scorers/を参照してください。

評価のタイミング

スコアラーの作り方がわかったので、次は「いつ評価するか」です。これもフレームワーク非依存の一般的な整理から入りましょう。

一般概念⁠3つの評価タイミング

LLMアプリケーションの評価は、運用ライフサイクルのどこで実行するかによって3つのタイミングに分けて考えるのが定石です。

タイミング 目的 性質
①ライブ評価(Online) 運用中のエージェントを継続監視し、デグレを検知する 本番トラフィックに同居・サンプリング前提
②CI 評価(Pre-merge) プロンプトやモデル変更 PR が品質を下げていないかをマージ前にゲートする 少数のテストケースで高速判定
③バッチ評価(Offline / Experiments) 改善前後を腰を据えて比較し、施策の効果を定量確認する 多数のケースを一括実行・結果を永続化

これら3つは目的が異なるため、実務では評価関数を3つのタイミングで使い分けるのが良いでしょう。

Mastraでの実装⁠3つのAPI

Mastra Evalsでも、この3タイミングそれぞれに対応するAPIが用意されています。①ライブ評価はAgentのscorersフィールド、②CI評価はrunEvals、③バッチ評価はdatasets+startExperimentを使います。順に見ていきましょう。

①ライブ評価―サンプリングと運用監視

エージェント定義のscorersフィールドにスコアラーとサンプリング率を登録すると、本番/開発中の実行に非同期でスコアリングが走ります。レスポンスをブロックしないため、本番トラフィックに仕込んだまま運用監視として使えます。

export const mailReplyAgent = new Agent({
  /* ... */
  scorers: {
    replyLength: { scorer: replyLengthScorer, sampling: { type: "ratio", rate: 1 } },     // 全件
    requirementCoverage: { scorer: requirementCoverageScorer, sampling: { type: "ratio", rate: 0.1 } }, // 10%
  },
});

サンプリング率はコストに応じて調整します。

たとえば、変更直後で挙動を細かく見たい場合や低コストなコードベース・スコアラーであればrate: 0.5などで高めに設定し、運用が安定した状況下やAPIコストがかかるLLMベース・スコアラーはrate: 0.01等で抑える、といった具合にバランスを取ると良いでしょう。スコアはMastraのストレージに自動的に蓄積され、Studioの管理画面から閲覧できます。

Mastra StudioのScorers一覧画面

②CI評価―PRの品質ゲート

プロンプトを変更したPRで「品質が下がっていないこと」を確かめたいときは、VitestやJestからrunEvalsを呼びます。

const result = await runEvals({
  data: [
    { input: "見積書の送付をお願いしたいのですが" },
    { input: "先日のお見積もりの件、検討の結果お願いしたいです" },
  ],
  target: mailReplyAgent,
  scorers: [replyLengthScorer, completenessScorer],
});

// スコアが閾値を超えているかを判定
expect(result.scores["reply-length-scorer"]).toBeGreaterThan(0.8);

runEvalsはデータセット登録なしで、インラインのケース配列で完結します。expectで閾値判定するので、スコアが下がったPRはCIが落ち、マージ前に気づけます。

③Datasets+Experiments―腰を据えた改善サイクル

「プロンプト改善の前後でスコアがどう変わったか」を本格的に追跡するには、DatasetsExperimentsを使います。

  • Datasets:複数のテストケースをMastraのストレージに保存する仕組み。アイテムの追加・更新のたびにデータセットのバージョンが自動付与され、過去のデータセットバージョンに対しても再実験できる
  • Experiments:データセットに対してテストを走らせ、結果をまとめて保存する仕組み。Studioで一覧・個別に確認できる
補足⁠なぜDatasetsを使うのか?
TypeScriptの配列で書いてGit管理するだけでもrunEvalsで同じことができます(前節がそれです⁠⁠。Datasets を使う主なメリットとして、Studioの管理画面から非エンジニアでもプロンプトやテストケースを手軽に調整できること、実装コードを書き換えずに、複数のデータに対して調整結果を一括で確認できること、アイテムの変更履歴が自動でバージョン管理されること、実験結果と紐付けて永続化・比較できることなどが挙げられます。これら以外にもメリットがあるため、CI品質ゲートのように手軽さ重視の場面ではrunEvals、腰を据えた改善サイクルや非エンジニアとの協業ではDatasets、と用途に合わせて使い分けると良いでしょう。

データセットの定義やExperimentsの実行は、コードからも、Mastra Studioの管理画面からも行えます。実際の利用例は次節で紹介します。

改善サイクルの実践

ここまで揃ったスコアラーと評価タイミングを使って、実際に改善サイクルを1周回してみましょう。本連載リポジトリには7件のテストケースを持つデータセットmail-reply-casesが登録されているので、これを使ってbaseline計測→プロンプト調整→再計測→結果比較の流れを順に進めていきます。

Step 1⁠baseline の計測

まずは現状のエージェントに同じデータセットを通し、改善前のスコアを取ります。Studioから「Run Experiment」を押すか、CLIから実行できます。

EXP_NAME=baseline pnpm experiment

Step 2⁠分析

実行が終わると、各テストケースのスコアが並びます。

items: 7/7 ok, 0 failed
  [12462f43...] Reply Length=0.67
  [3cd38397...] Reply Length=0.67
  [19d9aef6...] Reply Length=0.81
  [c15fe082...] Reply Length=0.91
  ...

スコアが低いアイテムから「返信が短すぎる/長すぎる」ケースが具体的に見えてきます。StudioのExperiments画面から個別のアイテムを開けば、入力メール・エージェントの応答・スコアラーの判定理由まで辿れます。

Step 3⁠プロンプトを調整

分析を踏まえて、エージェントの指示文に返信の長さルールを追加してみます。

// 改善前
instructions: `あなたはメール返信の担当者です。
受信メールの内容を正確に理解し、適切な返信を作成してください。`;

// 改善後(返信ルールを追加)
instructions: `あなたはメール返信の担当者です。
受信メールの内容を正確に理解し、適切な返信を作成してください。

【返信のルール】
- 返信本文は200~400字前後に収めること`;

Step 4⁠改善版で再計測

同じデータセットでもう一度実験を走らせます。

EXP_NAME=improved-v1 pnpm experiment

実行後はStudioのExperimentsタブにbaselineとimproved-v1が並んで保存されます。

データセット詳細のExperimentsタブ — baselineとimproved-v1が並んで保存されている

Step 5⁠結果の比較

2つの実験のスコアを並べると、改善効果が一目で確認できます。

スコアラー baseline improved-v1 差分
reply-length-scorer 0.866 1.000 +0.134

ベースラインとなる改善前の平均スコアは0.866で、個々のケースを見るとスコアが0.67~0.91とバラついていました。

baseline実験のSummary画面(reply-length-scorer 平均0.866)
baseline実験の各アイテム内訳 — 個別ケースのスコアにバラつきが残っている

改善後は7件すべてが目標レンジに収まり、平均スコアは1.000まで上がりました。指示文に「200~400字前後に収める」というルールを1行足しただけで、ここまで揃えられます。

improved-v1実験のSummary画面(reply-length-scorer 平均 1.000)

「感覚」ではなく「数値」で改善を確認できたこと、そしてその数値をバージョン付きで永続化して後から再現できること――これがEvalsを使った改善サイクルの価値です。

連載のまとめ

全4回を通じて、メール返信エージェントを段階的に構築しながらAIエージェント開発の主要要素を学んできました。第1回ではAIとAIエージェントの違いを整理し、教材としてMastraを選んだ理由と完成形のデモを紹介。第2回ではAgent/Tools/Structured Output/Slack連携/HITL/Workflowを組み合わせて、動くエージェントの基本形を組み立てました。第3回ではMemoryでパーソナライズの「賢さ」を、Guardrailsで「安全性」を追加し、第4回(本記事)ではEvalsでその品質を「数値で観測しながら育てる」サイクルまで到達しました。

本連載で扱ったAgent/Tools/Memory/Guardrails/Evalsという枠組みは、Mastraに限らずどのフレームワークでも通用する普遍的なものです。AIエージェント開発のエコシステムは日々進化していますが、この土台があれば新しいツールや手法にもスムーズに乗り換えていけるはずです。

AIエージェントは「作って終わり」ではなく「育てていく」ものです。ぜひこの知識を土台に、皆さん自身のAIエージェントを作ってみてください。全4回にわたりお読みいただき、ありがとうございました。

おすすめ記事

記事・ニュース一覧