面向人工智能智能体的评估驱动开发:契约、轨迹与评判器
将评估视为代理程序的 CI/CD 流程:将版本提示视为合同,利用 OpenTelemetry 和 Tempo 追踪运行过程,再通过确定性检查及大型语言模型评判器进行测试。
来源: **Eval-Driven Development: A Software Engineering Approach to Production-Grade AI Agents**
大多数开发智能体AI的团队都会早早陷入框架陷阱。寻找完美的协调器看似是显而易见的第一步,但那些复杂抽象的框架其实并非解决方案,而且有些框架在多次发布后会迅速性能下降。一个完全不依赖任何框架构建的代码审查自动化工具恰恰证明了其实并不需要那么多复杂的东西。所欠缺的其实是从普通软件工程中借鉴来的理念:评估机制,它类似于针对智能体的CI/CD流程。
评估机制并非千篇一律,也未必非得以大语言模型作为评判标准。能否正确设计评估机制,决定了系统是会变得脆弱不堪,还是能成为可投入生产的成熟系统。
这里的框架刻意采用开源形式,不依赖任何特定供应商的管理服务。Pydantic AI 是代理框架,一个轻量级且以模式定义为主的库,它不会干扰正常流程,并将评估功能视为核心特性。Grafana Tempo 配合 S3 存储来保存追踪数据,以此替代传统的管理型可观测性平台,而由 AI 生成的代码则会对各类评估对应的追踪数据进行分析和筛选。
为何大多数代理无法在生产环境中存活
一个无法长期运行的代理系统的最明显迹象并非模型、框架或提示语的巧妙程度,而是缺乏基础的工程实践。如果一个代理没有结构化的追踪机制、没有评估功能、没有提示语版本控制,也没有任何防护措施,那它只不过是一个基于概念验证的产物,极有可能失败。
传统软件通过 CI/CD 流水线、单元测试、集成测试和回归测试来确保持续的质量。在具有不确定性的智能体世界中,常见的致命缺陷在于缺乏自动评估框架以及运营防护机制。要构建成熟的智能体,就必须回归基本原则:采用开放式的基础设施,以及能够扩展的以评估驱动的开发(EDD)工作流程。
生产流水线
1. 合同:单独为提示词版本控制
提示词及其相关内容是智能体系统中最重要的部分,因此不应被嵌入智能体代码之中。代码只是系统主体,相对容易实现稳定和版本控制,而随着模型不断改进,提示词也会持续演变。
该流程始于一个带版本号的合约,其中明确了应当运行的组件:代理、提示词、模型以及评估策略。
[
{
"agent_id": "financial_market_headlines",
"version": 1,
"agent_model": "openai:gpt-4",
"prompt": "What are today's major financial market headlines?",
"eval": {
"contains": ["market"],
"max_model_requests": 3,
"min_tool_calls": 1,
"max_tool_calls": 5,
"judge_rubric": "The answer should be a useful response to the user's financial markets question. It should summarize market-relevant information, avoid obviously unrelated content, avoid investment advice, and avoid claiming certainty beyond what the retrieved information supports."
}
}
]
此处提示语和评分标准刻意设计得简短,仅用于演示目的,因为实际使用的提示语通常会很长且非常详细。该合约将提示语与作为评估者的LLM提示语相关联,同时也与非LLM检测机制相关联,比如使用“contains”进行关键词匹配,以及设定工具调用的最小值和最大值限制。
2. 运行时:执行引擎
智能体运行时负责执行该合约。它会调用模型,在需要时通过MCP调用外部工具,并完成实际工作。
选择时主要在LangGraph和Pydantic AI之间权衡。Python并非唯一的选择,甚至Rust在Rig中也拥有相关库。但在LLM推理的等待时间以及与MCP工具的网络调用时间面前,纯语言处理速度几乎无关紧要,因此Python庞大且成熟的AI生态系统更具优势。Pydantic AI则因其对评估功能的一流支持而获得青睐。
在实际生产环境中,运行时通常基于 Kubernetes 构建。虽然有将每个智能体都建模为自定义资源的冲动,试图过度设计,但针对快速变化的提示语所设计的同步循环会降低系统处理效率。因此应保持运行时的简洁性。
3. 可观测性层:杜绝盲区
运行时会生成追踪数据、日志、使用计数、模型与工具调用记录、延迟数值以及错误信息,这些数据均通过 OpenTelemetry 进行采集,并存储在 Tempo、Loki 和 Prometheus 等系统中。Grafana 则用于展示系统在生产环境中的运行状况。
4. 评估层:AI 的 CI/CD 流程
这就是各部分整合的地方。来自可观测性层的执行追踪数据会直接进入评估环节,在此处会针对真实追踪数据运行两类测试:确定性规则测试与非确定性判断测试。
这个循环永无止境:定义、执行、观察、评估、改进、重复。
将确定性强加于非确定性系统
评估方式可分为两类。确定性评估可通过普通代码进行精确测试。一个极其简单的例子就是让大型语言模型计算两个数的和,然后通过单元测试验证结果。
对于这种用例,提示词要求获取当天的金融市场新闻,这意味着模型至少需要调用一次提供的网络搜索工具。这一部分具有确定性,因此可以作为有效的测试依据。如果输出结果必须包含某些特定字符串,可以通过“包含”检查来验证。
第一层:实时确定性测试
该实时测试套件会使用已版本化的合约来运行当天的智能体。Pydantic Evals自带MaxToolCalls限制以及文本“包含”检查功能。像MinToolCalls这样的缺失功能则通过简单的封装函数加以实现。
uv run python tests/unittest_eval_agent.py
评估人员是按案件单独组建的:
evaluators=[
*[Contains(value, case_sensitive=False) for value in case.eval.contains],
MaxModelRequests(case.eval.max_model_requests),
MinToolCalls(case.eval.min_tool_calls),
MaxToolCalls(case.eval.max_tool_calls),
]
由于模型具有非确定性,每次运行都会重复多次。使用完全相同的提示词和设置时,同一个模型在某次运行中可能会调用该工具,而在另一次则不会。这种情况在成本较低、性能较弱的模型中更为明显;如果十次运行中有任何一次失败,那就说明提示词需要更详细的描述。包含三次重复的运行如下所示:
(financial-agent2) alex@pop-os:/ssd/ai_works/financial_agent2$ uv run python tests/unittest_eval_agent.py
Running each eval case 3 time(s)
Evaluating task ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00
Evaluation Summary: task
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Case ID ┃ Metrics ┃ Assertions ┃ Duration ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━┩
│ financial_market_headlines_v1 [1/3] │ tool_calls: 1 │ ✔✔✔✔ │ 18.8s │
│ │ requests: 2 │ │ │
│ │ input_tokens: 1,325 │ │ │
│ │ output_tokens: 278 │ │ │
│ │ cost: 0.0564 │ │ │
├─────────────────────────────────────┼───────────────────────┼────────────┼──────────┤
│ financial_market_headlines_v1 [2/3] │ tool_calls: 1 │ ✔✔✔✔ │ 8.8s │
│ │ requests: 2 │ │ │
│ │ input_tokens: 1,195 │ │ │
│ │ output_tokens: 82 │ │ │
│ │ cost: 0.0408 │ │ │
├─────────────────────────────────────┼───────────────────────┼────────────┼──────────┤
│ financial_market_headlines_v1 [3/3] │ tool_calls: 1 │ ✔✔✔✔ │ 13.5s │
│ │ requests: 2 │ │ │
│ │ input_tokens: 1,292 │ │ │
│ │ output_tokens: 388 │ │ │
│ │ cost: 0.0620 │ │ │
├─────────────────────────────────────┼───────────────────────┼────────────┼──────────┤
│ Averages │ requests: 2.00 │ 100.0% ✔ │ 13.7s │
│ │ output_tokens: 249.3 │ │ │
│ │ tool_calls: 1.00 │ │ │
│ │ cost: 0.0531 │ │ │
│ │ input_tokens: 1,270.7 │ │ │
└─────────────────────────────────────┴───────────────────────┴────────────┴──────────┘
第二层:基于轨迹的确定性回归
第二套方法不会再次运行智能体,而是从Tempo中读取已生成的轨迹:
uv run python tests/regression_eval_traces.py
它会输出最终结果,统计模型和工具的跨度,找到已记录的合约,并应用与之前相同的确定性规则:包含关系,以及工具至少被调用一次。已记录的运行结果可能会显示:
Trace: ff5a53e3392dc26cd0a2890782be70ec
Eval case: financial_market_headlines v1
Status: PASS
Model requests: 2
Tool calls: 1
contains('market'): PASS
MaxModelRequests: PASS
MaxToolCalls: PASS
这样做成本低且可重复,因为它会重新评估已收集到的证据。
第三层:由大语言模型法官执行的非确定性测试
该合同还包含了用于测试提示语的评估标准。依旧是那份简短的合同:
[
{
"agent_id": "financial_market_headlines",
"version": 1,
"agent_model": "openai:gpt-4",
"prompt": "What are today's major financial market headlines?",
"eval": {
"contains": ["market"],
"max_model_requests": 3,
"min_tool_calls": 1,
"max_tool_calls": 5,
"judge_rubric": "The answer should be a useful response to the user's financial markets question. It should summarize market-relevant information, avoid obviously unrelated content, avoid investment advice, and avoid claiming certainty beyond what the retrieved information supports."
}
}
]
由于该代理本身基于GPT-4运行,因此需要更强大的模型,而GPT-5便承担了这一角色。它负责判断回复是否有用且相关、是否夸大了现有资料所能支持的内容,以及是否涉及投资建议。
将评估标准拆分为多个层级也是另一种方法。简单的自然语言处理类检查可以由较弱的模型甚至小型自托管模型来完成,通过多组此类简单测试就能大致实现上述更高级别的判断。哪种方式更好取决于具体应用场景以及测试的设计方式。
若想深入了解,可阅读调研论文《“作为裁判的大型语言模型综述”》(arXiv 2411.15594)。根据应用场景的不同,最终可能需要借助人类反馈或其他手段来对裁判系统本身进行评估。
该套件会加载相同的已存储最终结果,并根据合约中的评分标准运行 Pydantic Evals 的 LLMJudge:
LLMJudge(
rubric=case.eval_case.eval.judge_rubric,
model=args.judge_model,
include_input=True,
score={"evaluation_name": "judge_score", "include_reason": True},
assertion={"evaluation_name": "judge_pass", "include_reason": True},
)
这是法官发现的典型失败案例,起因是提示语过于简短且模糊。模型没有调用工具并总结结果,而是回复了一条信息,让用户去查看财经新闻媒体。
uv run python tests/regression_llm_judge_traces.py --sample-percent 50
Trace selection: fetched=1 sampled=1 judging=1 lookback_minutes=1440 sample_percent=50 max_traces=5
Trace case: trace_id=3891f0b8432c9bbcb00e5e8adce1600b agent_id=financial_market_headlines prompt_version=1 answer_chars=207
Judging 1 trace(s) with openai:gpt-5.4
Evaluating task ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00
Evaluation Summary: task
┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Case ID ┃ Inputs ┃ Outputs ┃ Scores ┃ Assertions ┃ Duration ┃
┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
│ financial_market_he… │ {'trace_id': │ Today's major │ judge_score: 0.000 │ judge_pass: ✗ │ 469µs │
│ │ '3891f0b8432c9bbcb00 │ financial market │ Reason: The │ Reason: The │ │
│ │ e5e8adce1600b', │ headlines can be │ response does not │ response does not │ │
│ │ 'agent_id': │ found on major │ summarize any actual │ summarize any │ │
│ │ 'financial_market_he │ business and finance │ market headlines or │ actual market │ │
│ │ adlines', │ news outlets │ provide │ headlines or │ │
│ │ 'prompt_version': 1, │ including CNBC, │ market-relevant │ provide │ │
│ │ 'agent_model': │ Yahoo Finance, │ information; it only │ market-relevant │ │
│ │ 'openai:gpt-4', │ Reuters, and │ redirects the user │ information; it │ │
│ │ 'judge_model': │ Bloomberg. For more │ to news websites. It │ only redirects the │ │
│ │ 'openai:gpt-5.4', │ specific stories, │ avoids investment │ user to news │ │
│ │ 'prompt': "What are │ please visit their │ advice, but it is │ websites. It avoids │ │
│ │ today's major │ websites. │ not a useful answer │ investment advice, │ │
│ │ financial market │ │ to the user's │ but it is not a │ │
│ │ headlines?"} │ │ question. │ useful answer to │ │
│ │ │ │ │ the user's │ │
│ │ │ │ │ question. │ │
│ │ │ │ │ │ │
│ │ │ │ │ │ │
├──────────────────────┼──────────────────────┼──────────────────────┼──────────────────────┼─────────────────────┼──────────┤
│ Averages │ │ │ judge_score: 0.000 │ 0.0% ✔ │ 469µs │
└──────────────────────┴──────────────────────┴──────────────────────┴──────────────────────┴─────────────────────┴──────────┘
下文中是第二个失败案例。在这种情况中,可能是法官判断有误,或是评分标准过于模糊,因为该模型确实使用了搜索MCP并获得了不错的结果。
uv run python tests/regression_llm_judge_traces.py --sample-percent 10 --lookback-minutes 30
Trace selection: fetched=1 sampled=1 judging=1 lookback_minutes=30 sample_percent=10 max_traces=5
Trace case: trace_id=38fa3a54134d8818c7d7a5afbf50967e agent_id=financial_market_headlines prompt_version=1 answer_chars=654
Judging 1 trace(s) with openai:gpt-5.4
Evaluating task ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00
Evaluation Summary: task
┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Case ID ┃ Inputs ┃ Outputs ┃ Scores ┃ Assertions ┃ Duration ┃
┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
│ financial_market_… │ {'trace_id': │ Here are today's │ judge_score: 0.450 │ judge_pass: ✗ │ 441µs │
│ │ '38fa3a54134d8818c │ major financial │ Reason: The │ Reason: The │ │
│ │ 7d7a5afbf50967e', │ market headlines: │ response is │ response is │ │
│ │ 'agent_id': │ │ market-related and │ market-related │ │
│ │ 'financial_market_ │ 1. Wall Street's │ avoids investment │ and avoids │ │
│ │ headlines', │ riskiest trades │ advice, but it │ investment │ │
│ │ 'prompt_version': │ are suddenly back │ mostly lists │ advice, but it │ │
│ │ 1, 'agent_model': │ on top: Chart of │ article headlines │ mostly lists │ │
│ │ 'openai:gpt-4', │ the Day - Yahoo │ and links rather │ article headlines │ │
│ │ 'judge_model': │ Finance │ than providing a │ and links rather │ │
│ │ 'openai:gpt-5.4', │ [Link](https://fi │ useful summary of │ than providing a │ │
│ │ 'prompt': "What │ nance.yahoo.com/) │ the key financial │ useful summary of │ │
│ │ are today's major │ 2. S&P 500 │ market │ the key financial │ │
│ │ financial market │ notches │ developments. │ market │ │
│ │ headlines?"} │ record-high close │ │ developments. │ │
│ │ │ as rate-hike │ │ │ │
│ │ │ worries ease - │ │ │ │
│ │ │ Reuters │ │ │ │
│ │ │ [Link](https://ww │ │ │ │
│ │ │ w.reuters.com/mar │ │ │ │
│ │ │ kets/us/) │ │ │ │
│ │ │ 3. Treasury │ │ │ │
│ │ │ yields rise as │ │ │ │
│ │ │ U.S. threatens │ │ │ │
│ │ │ Iran with more │ │ │ │
│ │ │ economic │ │ │ │
│ │ │ sanctions - CNBC │ │ │ │
│ │ │ [Link](https://ww │ │ │ │
│ │ │ w.cnbc.com/) │ │ │ │
│ │ │ 4. Latest stock │ │ │ │
│ │ │ market, financial │ │ │ │
│ │ │ and business news │ │ │ │
│ │ │ - MarketWatch │ │ │ │
│ │ │ [Link](https://ww │ │ │ │
│ │ │ w.marketwatch.com │ │ │ │
│ │ │ /) │ │ │ │
│ │ │ 5. Latest finance │ │ │ │
│ │ │ and stock market │ │ │ │
│ │ │ news covering the │ │ │ │
│ │ │ Dow, S&P 500, │ │ │ │
│ │ │ banking, │ │ │ │
│ │ │ investing and │ │ │ │
│ │ │ regulation - WSJ │ │ │ │
│ │ │ [Link](https://ww │ │ │ │
│ │ │ w.wsj.com/finance │ │ │ │
│ │ │ ) │ │ │ │
├────────────────────┼────────────────────┼───────────────────┼────────────────────┼───────────────────┼──────────┤
│ Averages │ │ │ judge_score: 0.450 │ 0.0% ✔ │ 441µs │
└────────────────────┴────────────────────┴───────────────────┴────────────────────┴───────────────────┴──────────┘mar
对每个追踪记录都进行此类测试会耗费过高成本且速度极慢,随机抽样是更好的选择。如果产品提供了收集用户反馈的途径,追踪ID可以直接定位到流程中出问题的组件,只需对这些组件进行测试即可。像LangSmith和Arize Phoenix这样的商业产品已具备此功能,而S3后端加上这套技术栈也能实现相同效果,此外还有对应的docker compose配置。
在完成初步的单元测试后,很明显提示内容和评分标准都需要更新,于是该契约升级到了版本2:
{
"id": "financial_market_headlines",
"version": 2,
"agent": "websearch",
"prompt": "Use the web-search tool to find and verify today's major financial-market headlines. Report the 3 to 5 most consequential developments across equities, rates, currencies, commodities, or macroeconomic policy. For each item, state what happened, identify the affected market or region, explain briefly why it matters, and name the source with a link when available. Include the relevant date and units for numerical claims. Cross-check any surprising index level, percentage move, policy decision, or economic release against a second reliable source; if it cannot be verified, omit it or clearly label it as unconfirmed. State when the information was current, distinguish facts from developing reports or interpretation, and say when reliable current information is insufficient. Do not invent facts, present stale information as today's news, or give personalized investment advice.",
"contains": ["market"],
"max_model_requests": 4,
"min_tool_calls": 1,
"max_tool_calls": 7,
"judge_rubric": "The response should provide 3 to 5 current, consequential financial-market developments based on web research. Each item should identify what happened, the affected market or region, why it matters, and its source, preferably with a link. Numerical claims should include meaningful dates and units; surprising figures should be corroborated by a second reliable source or explicitly marked unconfirmed. The answer should state when the information was current, distinguish verified facts from developing reports or interpretation, and acknowledge insufficient evidence rather than inventing details. It must stay relevant, avoid stale news presented as current, avoid unsupported certainty, and avoid personalized investment advice. A polished but uncited answer containing an implausible or unverifiable market figure should fail."
},
从模式到发布流程
当每条追踪记录都能追溯到其对应的契约时,发布流程便能在每个阶段收集不同类型的证据,这比单一的通过率更能反映实际情况。发布质量应从多个维度进行评估:
- 任务是否成功
- 输出结果在语义层面的质量
- 工具是否被正确使用
- 安全性与合规性
- 请求量和代币成本
- 延迟与可靠性
延伸阅读
Anthropic的工程博客有一篇关于AI智能体评估方式的文章,地址为https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents,其中阐述了更广泛的思考视角。
相关阅读
- 利用AI编码智能体实现规范驱动开发:实用指南 —— 如何让AI编码智能体始终遵循规范:四阶段工作流程、AGENTS.md与SPEC.md文件、多智能体角色划分、成熟度等级以及意图偏移问题。
- 分层式的智能体工程方法:从规范与并行智能体到规则与监督机制 —— Karpathy提出了超越传统编码方式的转型思路。了解智能体工程的五层结构:规范层、协调层、编码规则层、人工审核与可观测性层,以及相应的周计划。