エンジニアにエージェントの故障モードを挙げてもらおうとすると、幻覚、誤ったツール呼び出し、悪いJSONといった答えが返ってくるでしょう。時間について尋ねると、肩をすくめるだけです。しかし、本番環境のエージェントが問題を起こす際に最もよく起こることは、派手に失敗することではなく、ただ時間がかかりすぎることです。ループしたり、不安定なツールを再試行したり、最初のトークンをストリーミングしないモデル呼び出しを待機したりします。そして、事後的に実際に出力された内容に対して実行される評価スイートは、それを緑色で評価します。

これが盲点です。私たちはレイテンシをSREダッシュボードの問題として扱い、正しさから切り離しています。しかし、エージェントにとって、デッドラインの逸脱は正しさの失敗そのものです。90秒遅れて到着した要約は、要約がない場合よりも悪いことがよくあります — ユーザーはすでに離れ、下流のジョブはすでにタイムアウトし、再試行はすでに二重課金しています。時間は評価に属し、スタックの最下層に属します。

時間はTier 1の証拠

agent-evalの背後にあるティア教義に従ってきたなら、エビデンスは独立性の軸でランク付けされることをご存知でしょう — エージェントが偽造できない証拠から、エージェントが共有する基盤を持つ意見まで — コストの軸ではありません。3つのティア:

  • Tier 1 — エージェントが偽造できない外部観測可能な証明:有効なJSON、ファイルが存在する、コードがコンパイルされた、テストが通った、デッドライン内に完了した、出力が空でない。
  • Tier 2 — エージェントが作成していないベースラインに対する統計的シグナル:タスクに対する埋め込み類似度、長さと繰り返し、diffが実際に何かを変更したかどうか。
  • Tier 3 — モデル・アズ・ジャッジ:共有基盤の意見。シグナルであり、決して評決ではありません。

「タイムアウト内に完了した」がどこに位置するかに注目してください。有効なJSONのすぐ隣のTier 1です。これは偶然ではありません。壁時計のデッドラインは最も独立したシグナルです — エージェントはストップウォッチに反論できず、それを推論し越えることができず、それを偽造できません。物理学がこれを評価します。評価スイート全体の中で、「時計がタイムアウトした」よりも不正操作できない真実はありません。

そして重要なのは、Tier 1+2がリアルタイムゲートであることです:決定論的で、ほぼ$0で、ホットパスに配置して実行をブロックできるほど高速です。タイムアウトチェックは、その最も純粋な表現です — 定義上、すでにホットパスに存在しています。Tier 3、ジャッジは逆です:従量課金で、遅く、非決定論的で、オフライン専用です。レスポンスが十分に速かったかどうかを決定するために、モデル・アズ・ジャッジをクリティカルパスに配置することは決してありません。ジャッジは時計を見ることさえできません。

「緑色で評価」されたものが実際にどのようなものか

ここに落とし穴があります。ほとんどの評価ハーネスは(input, output)を受け取り、出力をスコアリングします。outputはエージェントが最終的に返したものであり、したがって、評価が開始される前にタイミング情報はすでに破棄されています。評価は実行がデッドラインを破ったことを文字通り見ることができません。なぜなら、実行が終了した後にのみ存在するからです。

評価しなければならないのは戻り値ではなく、実行です:

type RunResult<T> =
  | { status: "ok"; value: T; elapsedMs: number }
  | { status: "timeout"; elapsedMs: number; deadlineMs: number }
  | { status: "error"; elapsedMs: number; error: string };

async function withDeadline<T>(
  work: (signal: AbortSignal) => Promise<T>,
  deadlineMs: number,
): Promise<RunResult<T>> {
  const ctrl = new AbortController();
  const started = Date.now();
  const timer = setTimeout(() => ctrl.abort(), deadlineMs);
  try {
    const value = await work(ctrl.signal);
    return { status: "ok", value, elapsedMs: Date.now() - started };
  } catch (err) {
    const elapsedMs = Date.now() - started;
    if (ctrl.signal.aborted) {
      return { status: "timeout", elapsedMs, deadlineMs };
    }
    return { status: "error", elapsedMs, error: String(err) };
  } finally {
    clearTimeout(timer);
  }
}

Enter fullscreen mode Exit fullscreen mode

これで評価ゲートは自明で決定論的になりました — マイクロ秒で実行されるTier 1チェックです:

function tier1TimingGate(run: RunResult<unknown>): { pass: boolean; reason?: string } {
  if (run.status === "timeout") {
    return { pass: false, reason: `deadline miss: ${run.elapsedMs}ms > ${run.deadlineMs}ms` };
  }
  if (run.status === "error") {
    return { pass: false, reason: `crashed after ${run.elapsedMs}ms` };
  }
  return { pass: true };
}

Enter fullscreen mode Exit fullscreen mode

タイムアウトした実行はTier 2または3に到達することはありません。埋め込むべきものはなく、判断すべきものはありません — 遅すぎて到着した正しい答えは、正しい答えではありません。ショートサーキットし、実行をブロックし、フォールバックします。意見が必要でなかったため、モデルに意見を求められることはありませんでした。

デッドラインは予算ごとであって、エージェントごとではない

次に見る間違いは、単一のグローバルタイムアウトです。デッドラインは文脈依存です:バックグラウンドの再インデックスジョブは10分かかることがありますが、インタラクティブなチャットターンでは、ユーザーが停滞を感じるまでに3秒しかないかもしれません。デッドラインは呼び出し元のプロパティであって、エージェントのものではありません。タスクコントラクトの一部としてスレッドし、すべてのツールステップに共有予算を継承・減算させましょう — そうすれば、予算の80%を消費するツールは、モデルが実際に応答する余地を残さず、それも評価可能なイベントとなります。

記録しなかった時間を評価することはできない

これらすべては、ステップごとの経過時間を実際にキャプチャしたことを前提としています — ここで、評価側の話はもう片方の半分を必要とします。agent-evalは出力をスコアリングしてゲートしますが、何かがタイミングを記録していなければTier 1のタイミングゲートを強制することはできません。それがトレースキャプチャの仕事です。AgentLensはエージェントの軌跡を計測します — 解決された入力、生の出力、開始/停止タイムスタンプ付きのすべてのモデル呼び出しとツールステップ — したがって、「デッドライン内に完了した」は、エージェントが作成できなかった偽造不可能なトレースから読み取れる事実であり、誰かがログに残したことを期待する数字ではありません。

このペアリングがまさに要点です。トレースはTier 1+2が評価する基盤です:AgentLensはエージェントが主張することとは独立して、各ステップがいつ開始および停止したかを記録するため、タイミングゲートには本物の根拠となる真実があります。エージェントは「素早く応答した」と幻覚することができます。しかし、自分が書かなかったトレースのタイムスタンプを編集することはできません。それが、Tier 1+2で退屈な失敗の80% — 古くなった、クラッシュした、空の、遅すぎる — を決定論的にキャッチする評価と、1分遅れでゴールラインを這いずってきたすべての実行を静かに隠す緑のチェックマークのダッシュボードとの違いです。

主観的な20% — トーン、役立ち度、「実際に質問に答えたか」 — にはジャッジを予約し、それを証拠ではなく意見とラベル付けしてください。しかし、ストップウォッチは?それは証拠です。最初に置いてください。