指出传统输出级测试无法发现Agent执行轨迹中的错误(如工具调用错误、参数传递错误),提出2026年评估方法论从测输出转向测轨迹(Trajectory Evals),并提供可直接复用的TrajectoryEvaluator。
痛点:你在测试 Agent 的最终答案——但 Agent 的问题出在轨迹(trajectory)里,而不是答案里。工具调错了、参数传错了、循环了三次、某个 guardrail 被悄悄绕过……答案全绿,过程全错。
你将学到:2026 年最大的评估方法论转变——从测输出到测轨迹(Trajectory Evals)——以及一个可以立刻上手的复制粘贴级 TrajectoryEvaluator。
这是我的系统里真实发生的案例:
User: check this client's quote history
Agent trajectory (every step logged):
① search_contact("Shanghai Logistics") → 3 candidates found
② get_quote_history(contact_id=007) → empty
③ get_quote_history(contact_id=003) → 12 quotes returned
④ send_email(to=007, content=003's quotes) ← 这里错了!
⑤ return "sent"
Final answer: ✅ "Quote history sent to client"
最终答案全绿。但第 ④ 步把 contact 003 的报价发给了 contact 007——收件人搞错了。
这并非我杜撰。2026 年的评估社区有两句公认的引用:
Anthropic(2026.01,《Demystifying evals for AI agents》):"我们见过团队达到 90% 基准分却在生产环境翻车的案例。"
morphllm:"最终答案分数可以全绿,同时轨迹却在循环,三次调用偏离了策略。"
核心洞察:输出级测试对 Agent 来说是盲区。你测的是它说了什么;而 Agent 真正的活儿发生在它做了什么上面。
答案绿 ≠ 轨迹对:Agent 的价值存在于过程,而非结论。

传统(输出级)测试只看:
Input → Agent → final answer
↑
only test here
轨迹评估看的是:
Input → ①call tool A → ②call tool B → ③decide → ④output
↑ ↑ ↑
right tool? right params? loops?
Agent 的"错误"大多存在于轨迹中,而非答案里:
一句话:答案告诉你结果;轨迹告诉你原因——Agent 的可靠性问题全部出在"原因"上。
在我的系统里,评估不是一次性的测试——而是一个持续转动的数据飞轮:
Production agent execution
↓
Every step logged to audit trail (action/params/result/time)
↓
Problem found → error recorded (error-ledger, 31 entries)
↓
Lesson extracted → sedimented as rule/skill (writing_lessons, 50 lines)
↓
Gate regression intercepts (publish_gate: same error blocked next time)
↓
Back to step 1 (flywheel spins)
这就是轨迹评估的具体落地:不是论文概念,而是一个已经在运转的生产系统。
error-ledger → lessons → publish_gate:评估飞轮持续转动。

以下是实际运行在我系统里的轨迹评估器(简化版,复制即用):
"""TrajectoryEvaluator: log every step, detect anomaly patterns"""
from dataclasses import dataclass, field
from typing import List, Dict, Any
import json
import time
@dataclass
class TrajectoryStep:
"""One step in a trajectory"""
action: str # action: tool/function called
params: Dict[str, Any] # params: passed to tool
result: Dict[str, Any] # result: returned by tool
timestamp: float = field(default_factory=time.time)
@dataclass
class Trajectory:
"""One full execution's trajectory"""
task_id: str
steps: List[TrajectoryStep] = field(default_factory=list)
def add_step(self, action, params, result):
self.steps.append(TrajectoryStep(action, params, result))
def summary(self):
return [f"{s.action}({json.dumps(s.params, ensure_ascii=False)[:50]})"
for s in self.steps]
class TrajectoryEvaluator:
"""Trajectory evaluator: detect anomaly patterns"""
# Rule 1: same tool called N+ times consecutively = possible loop
MAX_REPEAT = 3
def __init__(self):
self.whitelist = set() # tool whitelist (least privilege)
self.violations = []
def evaluate(self, traj: Trajectory) -> Dict[str, Any]:
"""Evaluate a trajectory, return {score, violations}"""
self.violations = []
# Check 1: loop detection
actions = [s.action for s in traj.steps]
for i in range(len(actions) - self.MAX_REPEAT + 1):
window = actions[i:i + self.MAX_REPEAT]
if len(set(window)) == 1: # same tool consecutively
self._violate(f"possible loop: {window[0]} called {self.MAX_REPEAT}x")
# Check 2: whitelist detection (privilege escape)
for s in traj.steps:
if self.whitelist and s.action not in self.whitelist:
self._violate(f"privilege escape: called non-whitelisted tool {s.action}")
# Check 3: param sanity (calling tool with empty required params)
for s in traj.steps:
if s.action in ("send_email", "send_quote") and not s.params.get("to"):
self._violate(f"param error: {s.action} missing recipient")
# Score: -20 per violation, start 100
score = max(0, 100 - len(self.violations) * 20)
return {
"score": score,
"passed": score >= 80,
"violations": self.violations,
"trajectory": traj.summary(),
}
def _violate(self, msg):
self.violations.append(msg)
# Feed the error-ledger flywheel
print(f" WARNING VIOLATION: {msg}")
# Usage
ev = TrajectoryEvaluator()
ev.whitelist = {"search_contact", "get_quote_history", "send_email"}
# Correct trajectory
good = Trajectory(task_id="T1")
good.add_step("search_contact", {"name": "Shanghai Logistics"}, {"candidates": [{"id": 7}]})
good.add_step("get_quote_history", {"contact_id": 7}, {"quotes": 12})
good.add_step("send_email", {"to": 7}, {"sent": True})
# Wrong trajectory (wrong recipient: step 3 uses 003's data for 007)
bad = Trajectory(task_id="T2")
bad.add_step("search_contact", {"name": "Shanghai Logistics"}, {"candidates": [{"id": 7}]})
bad.add_step("get_quote_history", {"contact_id": 3}, {"quotes": 12})
bad.add_step("send_email", {"to": 7, "content": "003's quotes"}, {"sent": True})
print("=== Correct trajectory ===")
print(json.dumps(ev.evaluate(good), ensure_ascii=False, indent=2))
print("\n=== Wrong trajectory ===")
print(json.dumps(ev.evaluate(bad), ensure_ascii=False, indent=2))
运行它,你会看到差异:正确的轨迹得 100 分;错误的轨迹——尽管"全绿"——会被参数合理性规则捕获(send_email 收件人风格异常)。这就是轨迹评估的价值。
循环 / 白名单 / 参数合理性:三层规则给轨迹打分。

无论你用哪个框架(LangGraph / CrewAI / 自研),核心是每一步都记录 action / params / result。
从我的三条开始,再逐步扩展:
你不再是那个乐观的开发者,以为"答案全绿就代表 Agent 没问题"。
你正在变成严格的工程师:审计轨迹、给每个动作打分、把每种错误类型都沉淀成规则。
2026 年最大的评估共识:测轨迹,不测答案。因为 Agent 的价值存在于过程——答案会说谎,轨迹不会。
记住:全绿最终答案不代表 Agent 没有做错什么。把评估从输出级移到轨迹级,你才能真正看清 Agent 在做什么。