DrewWasem/agentic_testing_framework
GitHub: DrewWasem/agentic_testing_framework
面向 AI agent 的对抗性测试框架,通过多层级审判庭机制(确定性检查、多视角评审、裁决编排)提供可审计的评估结果,替代脆弱的字符串匹配和宽松的单模型评判。
Stars: 0 | Forks: 0
# Agentic Testing 框架
**一个用于 AI agent 的对抗性测试工具,配备可审计的评估审判庭。**
[](https://github.com/DrewWasem/agentic_testing_framework/actions/workflows/ci.yml)
[](https://www.python.org/)
[](LICENSE)
[](#status)
[](https://github.com/astral-sh/ruff)
[](https://mypy-lang.org/)
大多数测试 agent 的方法都检查错了对象。它们断言的是底层管道逻辑 ——
API 是否返回了 200,是否出现了某个确切的字符串 —— 并且一旦措辞发生改变,它们就会失效。真正重要的是那些更难确定的东西:*给定一个任务,agent 的结果是否做到了它应该做的事,以及当你向它抛出复杂的输入时,它表现如何?*
Agentic Testing Framework 回答了这个问题。你只需用简单的英语描述一个好的结果应该是什么样。系统会生成测试输入(包括对抗性输入),在被测 agent 上运行它们,然后通过一个多层次的**审判庭 (tribunal)** 来评判结果 —— 包括确定性检查、逐步审查的 reviewer、拥有不同视角的 reviewer 议会,以及一个将完整记录权衡为最终裁决的 orchestrator,并附带书面且引用证据的理由。
它是**与 agent 无关的 (agent-agnostic)**。通过一个轻量级的 adapter,它可以驱动任何 agent —— 一个 prompt、一个 HTTP endpoint、一个 CLI、一个本地函数 —— 而不仅仅是用这个框架构建的 agent。只要你能调用它,你就可以测试它。
## 为什么开发它
我不断看到两种 agent 的“测试”:一种是瞬间失效的脆弱的字符串匹配,另一种是询问单个 LLM“这个好吗?” —— 这很宽松、不一致且无法审计。两者都不能告诉你一个不是你构建的 agent 是否能在压力下支撑得住。
我想要一种像严肃的审查过程一样对待评估的方法:首先确立硬事实,允许多个视角存在分歧,并且最终裁决必须*展示其推理过程*。这就是审判庭。我还希望它能生成自己的压力测试,这样问题就从“它是否通过了我的正常路径 (happy-path) 示例”转变为“它实际上在哪里崩溃了”。
## 范围与负责任的使用
这是一个用于**对你拥有或获得明确授权测试的 agent 进行红队测试 (red-teaming)** 的工具 —— 以便在它们进入生产环境之前发现弱点。对抗性生成器在众所周知的鲁棒性类别(如 prompt 注入、范围蔓延 (scope-creep)、矛盾指令、边缘情况)层面上工作,以便你可以加固你自己的系统。它不是为了对你无法控制的系统产生攻击而构建的,并且它有意避免了逐步的漏洞利用指令。请本着这种精神使用它。
## 它的不同之处
**确定性检查为 LLM 提供了事实基础 —— 它们不会与 LLM 同时运行。**
语言模型在那些简单的 Python 完美擅长的事情上恰恰不可靠:计算单词数、测量句子长度、验证 URL 格式是否正确。因此,这些检查会首先运行,它们的硬事实会被注入到每个 reviewer 的 prompt 中。推理层绝不会去猜测确定性层已经知道的事情。
**一个共享的证据账本 (evidence ledger)。** 每一次检查和每一位 reviewer 都会将一个结构化的*发现 (finding)* —— 包括来源、严重程度、消息、引用的证据 —— 写入同一个账本。没有任何东西会在下游仅仅传递一个单纯的通过/失败结果。最终的裁决可以准确指出是哪些发现促成了它。这种可审计性才是真正的意义所在。
**orchestrator 进行裁决;它从不进行平均计算。** 对议会的分数进行平均计算会丢弃最有用的信号 —— *它们在哪里存在分歧*。orchestrator 的工作是找到分歧,权衡谁的证据更有力,并解决它,就像主审法官在审议中做出裁决一样。
**它严肃对待 LLM 作为评判者 (LLM-judge) 的弱点。** reviewer 被指示仅根据陈述的期望进行评分,要严格,并引用证据。该设计假设评判者容易犯错,并构建结构来控制这一点,而不是信任一个单一的、自信的裁决。
**它通过架构设计实现了低成本。** 确定性检查是免费的,并且会首先运行。一个失败的硬性关卡 (hard gate) 会在花费哪怕一个 token 之前就使案例短路终止。流水线根据模型层级进行路由 —— 将一个小巧、廉价的模型分配给 reviewer,将前沿模型分配给 orchestrator,这是唯一一个深度推理物有所值的地方。(离线默认运行时在每个阶段运行一个 mock。)
## 横向对比
大多数评估工具要么进行字符串匹配(脆弱),要么询问单个 LLM“这个好吗?”(宽松且不可审计)。审判庭做出了不同的设计选择 —— 其中两个在所调查的工具中似乎是独一无二的:
| | promptfoo | DeepEval | Ragas | Inspect AI | **ATF** |
|---|:---:|:---:|:---:|:---:|:---:|
| 确定性检查**确立**评判者的事实基础(作为事实注入) | – | – | – | – | **✓** |
| 单个共享的**可引用证据账本** → 书面理由 | 按断言 | – | traces | – | **✓** |
| orchestrator **进行裁决,从不平均** | – | – | vote | majority | **✓** |
| 独特视角的**评审团**(不是自集成) | – | – | – | – | **✓** |
| 硬性关卡**在任何 token 花费之前短路终止** | partial | partial | – | – | **✓** |
| 离线 + **仅使用标准库,零依赖的核心** | – | – | – | – | **✓** |
这是一个能力概览,而不是基准测试 —— 这些工具中的每一个都做了很多本工具没有做到的事情(托管数据集、大型指标库、仪表板)。重点在于评估的*形态*,而不是功能数量。
## 组装方式
```
[Generator] ── invents (input, expectation) ──┐
spec-driven / adversarial / mutation │
▼
[Target: the agent under test]
│ produces output
▼
┌─────────────────── TRIBUNAL ───────────────────┐
│ Clerk deterministic checks → findings │ gate fails? stop, $0
│ Reviewer step-by-step → findings │ grounded by clerk
│ Council N lenses → findings │ grounded by clerk+reviewer
│ Orchestrator adjudicates the evidence ledger │
└────────────────────────┬──────────────────────────┘
▼
Verdict + written rationale
│
└─ (optional) feedback to the generator: target weak spots
```
默认的议会有四个视角 —— **准确性、完整性、清晰度以及一个对抗性的怀疑者** —— 并且它们全都可以被重写。
两个微小的接缝保持了一切的解耦。**provider** 是系统与评判者或生成器模型通信的方式(`complete(system, prompt) -> text`)。**target** 是它驱动被测 agent 的方式(`run(input) -> output`)。更换评判者后端或将测试工具指向不同的 agent 只需要修改单个文件。
## 报告与 CI
裁决只有具备可检查性才有用,因此运行可以生成两种产出物:
```
atf run --example --html report.html --junit results.xml --gate
```
`--html` 会写入一个自包含的 HTML 页面 —— 包含裁决及其理由、orchestrator 引用的发现,以及按来源分组并按严重程度排序的完整证据账本,因此整个推理链都可以在一个文件中进行审计。
`--junit` 会写入任何 CI 运行器都能理解的 JUnit XML;非 PASS 的裁决会变成一个携带了理由和所引用发现的 ``。`--gate` 使 `atf` 在非 PASS 时以状态码 1 退出,因此被测 agent 中的回归会导致作业失败并阻止合并。一份可直接复制到 `.github/workflows/` 的配置方法见 [`examples/github-actions-eval.yml`](examples/github-actions-eval.yml)。
`--show-cost` 会打印每个阶段的汇总 —— 调用次数、延迟以及按默认层级目录价格估算的美元成本 —— 因此通过架构设计实现的低成本是可见的,而不是声称的:clerk 是免费的,reviewer 和议会按廉价层级计价,只有 orchestrator 按前沿层级计价,而硬性关卡会以 0 美元短路终止。`--cache DIR` 增加了模型响应的基于内容寻址的磁盘缓存,因此在未更改的 target 上重新运行测试套件时会从磁盘重放 —— 缓存命中仍然算作案例所需的调用,但成本为 0 美元,并且几乎没有延迟增加,这会在汇总中直接显示。美元数字是一个估计值(token 根据实际文本近似计算),而不是账单。
## 指标
审判庭是核心评估;指标库是一组**命名的 LLM 评判视角**,当你需要熟悉的单轴分数时,可以随之一同运行。每个指标都要求模型对输出的某一个维度进行评分,并将结构化的发现写入与其他每个阶段**相同的证据账本**中 —— 数字分数位于发现的 `metadata` 中,带有引用的证据和通过/失败状态,因此指标结果和议会的发现一样可审计,而不是一个单纯的数字。
| 指标 | 询问内容 | 方向 |
|---|---|:---:|
| **`g_eval`** | 从期望中推导评分标准,然后填表打分 | ↑ 越高越好 |
| `faithfulness` | 是否每个声明都有提供的上下文支持? | ↑ 越高越好 |
| `answer_relevancy` | 输出是否针对了被要求回答的输入? | ↑ 越高越好 |
| `hallucination` | 存在多少捏造/无支持的内容? | 反向 |
| `toxicity` | 输出的有害或滥用程度如何? | 反向 |
**G-Eval 是旗舰指标。** 它不是根据固定的评分标准进行打分,而是让模型首先从任务自身的期望和标准中*推导*出评估步骤(思维链),然后按照这些步骤填表打出 1-5 的分数。推导出的步骤被记录在发现的 `metadata["steps"]` 中,因此它所依据的评分标准是记录的一部分,而不是隐藏的。反向指标报告的是坏事物的*数量*,并进行了归一化,因此每个分数的读取方向都是一致的:一个干净的输出在各个方面都会接近 1.0。
```
from agentic_testing_framework import Case, MockProvider, run_metrics
report = run_metrics(case, MockProvider(), metrics=["g_eval", "faithfulness", "toxicity"])
print(report.scores) # {"g_eval": 1.0, "faithfulness": 1.0, "toxicity": 1.0}
print(report.mean, report.passed)
```
或者从 CLI 中,使用 `--metrics` 开启:
```
atf run --example --metrics g_eval,faithfulness,toxicity
```
`run_metrics` 将每个指标的分数汇总为一个平均值和整体的通过/失败 —— 这是唯一发生平均计算的地方,刻意只在这里进行,而绝不会在 orchestrator 中进行,因为 orchestrator 的工作是权衡分歧,而不是消除分歧。这些指标是**可选择开启且独立的**:它们不会在默认的流水线中运行,因此接入它们永远不会改变流水线的成本。它们**是对确定性 clerk 和审判庭的补充,而不是替代** —— 一个指标是一个模型对某一个轴的看法;审判庭是经过裁决的判决。这里的所有内容都可以像框架的其余部分一样,通过 mock 离线免费运行。
## Prompt 版本控制与回归测试
Prompt 是最有可能改变裁决的因素,因此框架将 prompt 视为代码。每个 judge、reviewer、议会视角、orchestrator、generator 和 metric 的系统 prompt 都位于同一个注册表中,具有稳定的 `id`、整数 `version` 以及每次修改对应一行记录的 `changelog`;各个阶段从那里获取文本,而不是持有私有的字符串。评判案例所使用的版本随后会被**标记到裁决上** —— `verdict.prompt_versions` 会记录 `{stage: version}` —— 因此一项裁决会声明是哪些 prompt 版本生成了它。这就是应用于 prompt 自身的可审计性论点:在编辑 prompt 后发生的行为变化可以归因于特定的版本号提升,而不是一个谜团。
**黄金集 (golden set)** 将其转化为一个关卡。它是一个小型的 JSON 基准案例集,每个案例都与审判庭预期达到的 `Outcome` 配对:
```
atf regression --golden examples/golden.json --max-drift 0.0
```
运行器重新运行每个案例,并将不再与基准匹配的裁决(即**翻转 (flips)**)报告为 `drift` 分数;当漂移超过预算时,命令会以非零状态退出,因此悄悄破坏了裁决的 prompt 或模型更改会使 CI 失败,而不是蒙混过关。比较是基于裁决的 `Outcome` **属性,绝不基于理由文本**:一个模型合理地重写其推理过程绝不能被记为漂移。当更改是有意为之的,`--update-baseline` 会重新运行并将预期结果重写为新的契约。像其他一切一样,黄金集是仅使用标准库的 JSON,并通过 mock 离线运行。
```
from agentic_testing_framework import build_pipeline, load_golden, run_regression
report = run_regression(load_golden("examples/golden.json"), build_pipeline())
print(report.drift, report.passed) # 0.0 True
```
## 一个完整的端到端测试案例
你可以将一个已经存在的结果交给审判庭:
```
from agentic_testing_framework import Case
Case(
input="Write a SQL query for total revenue per region in 2025.",
output="SELECT region, SUM(amount) AS revenue FROM orders WHERE year=2025 GROUP BY region;",
expectation="A correct, runnable SQL query answering the question.",
criteria=[ # optional — graded one by one
"Groups by region",
"Sums a revenue/amount column",
"Filters to the year 2025",
"Is syntactically valid SQL",
],
)
```
……或者让 generator 发明输入和需要跨越的门槛,在你的 agent 上运行它们,并报告它在哪里坚持住了,在哪里崩溃了。
## 状态
**v0.1.0 —— 整个架构现在就可以运行,离线且免费。** 完整的审判庭(确定性 clerk → 有事实基础的 reviewer → 多视角议会 → 进行裁决的 orchestrator)以及 generator(规范驱动、对抗性、突变以及可选的自适应循环)全部针对 mock 后端进行端到端运行,**无需 API key**:
```
git clone https://github.com/DrewWasem/agentic_testing_framework
cd agentic_testing_framework
pip install -e ".[dev]" # the core has zero required deps; SDKs are optional extras
atf run --example # grade the SQL example through the full tribunal, offline
```
最后一条命令针对 mock 后端端到端地裁决了 SQL 示例 —— 无需 API key,无需网络 —— 并打印出裁决,其中每一个被引用的发现都可以在证据账本中追踪:
```
VERDICT: PASS
Rationale: Offline mock adjudication: deterministic checks passed and no finding failed.
Cited findings: clerk:word_count#0, clerk:sentence_length#1, clerk:url_validity#2
Model calls: 6 (gated=False)
Evidence ledger:
[clerk:word_count#0] clerk:word_count (info): Output word count = 12.
[clerk:sentence_length#1] clerk:sentence_length (info): Average sentence length = 12.0 words across 1 sentence(s).
[clerk:url_validity#2] clerk:url_validity (info): No URLs found in output.
```
以同样的方式运行整个测试套件 ——pytest`,全部离线进行,无需 API key。
带有标签的 **PyPI** 版本正在路上;一旦发布,`pip install agentic-testing-framework` 将是你唯一需要做的。
通过传递 `AnthropicProvider` 来接入真实的评判者(SDK 是一个可选的额外组件,延迟加载 —— 核心保持了其零依赖的承诺)。Live-API 路径已经实现,但并未被测试套件执行;每个测试都在 mock 上运行。
## 路线图
1. **确定性基础** —— 证据账本和最初的检查(字数统计、句子长度、URL 有效性)。 ✅ *已发布。*
2. **Target adapter** —— 驱动任何 agent:一个 Claude prompt、一个 endpoint、一个 CLI、一个函数。首先是单轮交互,设计上可扩展为多轮。 ✅ *已发布。*
3. **有事实基础的 reviewer** —— reviewer 消费账本并逐步走查标准。 ✅ *已发布。*
4. **议会** —— 具有独特视角的多个 reviewer;表面化分歧而不是将其平均掉。 ✅ *已发布。*
5. **Orchestrator + 流水线** —— 裁决成为最终判决和理由;门控和模型分层端到端连接。 ✅ *已发布。*
6. **Generator** —— 规范驱动,然后是对抗性模板,接着是突变;最后是一个可选的自适应循环,将裁决反馈回去以针对 agent 的弱点。 ✅ *已发布。*
## 设计原则
- **核心零必需依赖。** 它在标准库上运行;模型 SDK 是可选的,并且会延迟加载。通过 mock 后端,一切都是可运行和可测试的,并且是免费离线的。
- **每个子系统都是独立发布的。** 审判庭在没有 generator 的情况下也能工作;generator 在没有自适应循环的情况下也能工作。组合,而不是纠缠。
- **展示推理过程。** 一个你无法追踪的裁决没有多大价值;每一次裁决都会引用其背后的发现。
## 这是如何构建的
这个仓库本身就是一项 agentic 工程的成果:它的设计、构建、审计和加固,都是通过指导 AI 编程 agent 针对一组固定的不变量来实现的,在每个阶段都有一个怀疑态度的 reviewer agent,并留下了清晰的 pull request 记录。完整的故事 —— 包括 reviewer 发现的一个真实的账本损坏 bug(在 Python 中 `bool("false")` 是 `True`) —— 位于 **[BUILD.md](BUILD.md)** 中。
## 贡献
欢迎提 issue 和讨论 —— 特别是关于议会视角设计、generator 的对抗性类别以及额外的确定性检查。如果你尝试针对真实的 agent 运行它,我很乐意听到它在哪里让你感到惊讶。
## License
MIT
标签:AI智能体, DLL 劫持, Python, 多模态安全, 大语言模型, 对抗性测试, 无后门, 评估框架, 逆向工具