Agent 框架评测实验室系列:如何测出企业场景中的真实强弱
“LangGraph、OpenAI Agents、PydanticAI、Google ADK,到底哪个更强?”
这句话听起来像一个排行榜问题,实际上至少藏着六个不同问题:谁更容易写出第一个 demo,谁在同一模型下更省 token,谁能安全暂停审批,谁能在进程崩溃后恢复,谁的原生抽象更适合复杂工作流,谁的运行证据足以通过企业审计。
如果不先限定业务场景和风险边界,“最强框架”没有可验证的含义。
我在开发 Agent Framework Lab 的过程中,逐步把框架测验从“同一个 prompt 跑一遍”改造成一条证据链:冻结任务、提示词、工具、模型和价格;交错重复执行;注入审批、取消、工具故障、worker 崩溃和进程重启;保存不可变原始结果;最后才按自定义权重生成比较结果。
这个系列整理 Lab 的核心理念、方法、代码、失败经验和阶段记录。它既是项目复盘,也是未来开发其他 Agent 测验项目时可以复用的设计手册。
系列文章
- 别急着问哪个 Agent 框架最好:先把比较问题定义对
- 把企业需求写成可执行的 Agent 评测契约
- 不注入故障,就测不出 Agent 框架的生产能力
- 从原始运行到排行榜:硬门、权重与统计陷阱
- Agent Framework Lab 开发实录:从比较 demo 到企业就绪 Alpha
此前发布的《Agent 框架开发与评测踩坑实录》是本系列的代码型伴读,集中记录事件账本、审批绑定、幂等键、价格表和 ground truth 等十二类工程坑。
一张图理解 Lab
场景 / Prompt / 工具 / 模型 / 价格 / 限额
│ 冻结并版本化
▼
Framework Adapter Matrix
┌──────────┴──────────┐
│ 公平公共赛道 │ 原生上限赛道
│ 同输入、同工具 │ 原生图、handoff、checkpoint
└──────────┬──────────┘
▼
正常运行 + 固定故障注入
▼
Raw Result / Events / Artifacts
▼
硬性 No-Go → 加权能力画像
▼
Go / Conditional-Go / No-Go
这套方法最重要的变化,是不再把框架看成一个只负责“调用模型”的 SDK。企业应用真正关心的是一整组能力:任务质量、安全治理、故障恢复、审计、成本、延迟、可运维性、开发体验和迁移成本。
Agent 框架开发与评测踩坑实录:从能跑到可审计、可比较
很多 Agent demo 的成功标准是:“模型能调用工具,最后返回一个答案。”
本文现作为《Agent 框架评测实验室系列》的代码型伴读;系列正文进一步讨论公平赛道、企业场景、故障证据、评分统计和项目演进。
但只要把它放进真实系统,问题马上变成另一组问题:任务执行到一半进程崩了怎么办?审批通过的到底是哪一次工具调用?工具重试会不会重复扣款?两个框架比较时,是否真的使用了同一份提示词和工具?模型价格变化后,旧实验的排名还能不能复算?
我在 Agent Framework Lab V2 RC 中把这些问题做成了一个可以运行、恢复、审计和比较的实验台。本文不讲“Agent 很有前景”,只记录实现过程中最容易被低估的坑、最终采用的设计,以及可以直接复制的代码片段。
先给结论:Agent 评测是一条证据链
可信的评测结果必须能回答:这次运行用了哪个框架、模型、provider、提示词和工具?模型调用、工具调用、审批、checkpoint 的顺序是什么?崩溃或重启后哪些动作被恢复,哪些动作绝不能重放?结果怎么评分?换一组权重后能否只重算排名?排名是否因为 ground truth 写错了?
因此数据流不是:
prompt -> model -> score
而是:
任务契约 + 模型契约 + 工具契约
↓
append-only 运行事件
↓
不可变 RunResult 原始证据
↓
可重算的 ScoreProfile
↓
排名、图表和审计报告
坑一:把“任务成功”和“评分高”混成一件事
一次运行可能在运行时层面成功完成,但答案质量只有 60 分;反过来,一个答案看起来不错,也不能掩盖未授权工具调用、预算超限或事件账本损坏。
Lab 将两者拆开:
class RunResult(BaseModel):
run_id: str
matrix_id: str
framework: str
case_id: str
status: Literal["completed", "failed", "blocked_gateway_contract", "unavailable"]
final_output: str = ""
tool_calls: list[ToolCallRecord] = []
model_calls: list[ModelCallRecord] = []
usage: Usage = Usage()
score: Score | None = None
trace_id: str | None = None
metadata: dict[str, Any] = {}
status == completed 只表示执行器完成生命周期;score.total 是评测器对结果的判断。模型返回错误 JSON 时,运行可以成功但结构化输出为 0;预算结算失败时,即使模型输出正确,也必须判为运行时失败;评分规则改变时,可以重算分数,但不能重写原始执行状态。