agentprobe-testing 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentprobe_testing-0.5.0/LICENSE +109 -0
- agentprobe_testing-0.5.0/PKG-INFO +127 -0
- agentprobe_testing-0.5.0/README.md +275 -0
- agentprobe_testing-0.5.0/agentprobe/__init__.py +104 -0
- agentprobe_testing-0.5.0/agentprobe/agents/__init__.py +0 -0
- agentprobe_testing-0.5.0/agentprobe/agents/base.py +32 -0
- agentprobe_testing-0.5.0/agentprobe/agents/rule_based.py +336 -0
- agentprobe_testing-0.5.0/agentprobe/agents/scripted.py +30 -0
- agentprobe_testing-0.5.0/agentprobe/agents/target_agent.py +106 -0
- agentprobe_testing-0.5.0/agentprobe/agreement.py +80 -0
- agentprobe_testing-0.5.0/agentprobe/classifier.py +159 -0
- agentprobe_testing-0.5.0/agentprobe/cli.py +684 -0
- agentprobe_testing-0.5.0/agentprobe/diff.py +150 -0
- agentprobe_testing-0.5.0/agentprobe/domain.py +121 -0
- agentprobe_testing-0.5.0/agentprobe/domains/__init__.py +0 -0
- agentprobe_testing-0.5.0/agentprobe/domains/access_control/__init__.py +0 -0
- agentprobe_testing-0.5.0/agentprobe/domains/access_control/agent.py +90 -0
- agentprobe_testing-0.5.0/agentprobe/domains/access_control/clean.py +154 -0
- agentprobe_testing-0.5.0/agentprobe/domains/access_control/complex_agent.py +123 -0
- agentprobe_testing-0.5.0/agentprobe/domains/access_control/decoy.py +124 -0
- agentprobe_testing-0.5.0/agentprobe/domains/access_control/domain.py +35 -0
- agentprobe_testing-0.5.0/agentprobe/domains/access_control/entities.py +43 -0
- agentprobe_testing-0.5.0/agentprobe/domains/access_control/injector_prompt.py +196 -0
- agentprobe_testing-0.5.0/agentprobe/domains/access_control/rule_based_agent.py +263 -0
- agentprobe_testing-0.5.0/agentprobe/domains/access_control/scenarios.py +17 -0
- agentprobe_testing-0.5.0/agentprobe/domains/access_control/split.py +96 -0
- agentprobe_testing-0.5.0/agentprobe/domains/access_control/tools.py +235 -0
- agentprobe_testing-0.5.0/agentprobe/domains/access_control/trap.py +100 -0
- agentprobe_testing-0.5.0/agentprobe/feedback.py +121 -0
- agentprobe_testing-0.5.0/agentprobe/generic_world.py +99 -0
- agentprobe_testing-0.5.0/agentprobe/injection.py +475 -0
- agentprobe_testing-0.5.0/agentprobe/injector.py +810 -0
- agentprobe_testing-0.5.0/agentprobe/llm.py +123 -0
- agentprobe_testing-0.5.0/agentprobe/playbook.py +211 -0
- agentprobe_testing-0.5.0/agentprobe/quickstart.py +295 -0
- agentprobe_testing-0.5.0/agentprobe/reachability.py +196 -0
- agentprobe_testing-0.5.0/agentprobe/registry.py +313 -0
- agentprobe_testing-0.5.0/agentprobe/report.py +666 -0
- agentprobe_testing-0.5.0/agentprobe/runner.py +317 -0
- agentprobe_testing-0.5.0/agentprobe/scenario.py +75 -0
- agentprobe_testing-0.5.0/agentprobe/scenarios/__init__.py +0 -0
- agentprobe_testing-0.5.0/agentprobe/scenarios/clean.py +194 -0
- agentprobe_testing-0.5.0/agentprobe/scenarios/decoy.py +272 -0
- agentprobe_testing-0.5.0/agentprobe/scenarios/registry.py +16 -0
- agentprobe_testing-0.5.0/agentprobe/scenarios/split.py +203 -0
- agentprobe_testing-0.5.0/agentprobe/scenarios/trap.py +215 -0
- agentprobe_testing-0.5.0/agentprobe/termui.py +154 -0
- agentprobe_testing-0.5.0/agentprobe/tools.py +275 -0
- agentprobe_testing-0.5.0/agentprobe/trajectory.py +107 -0
- agentprobe_testing-0.5.0/agentprobe/triage.py +153 -0
- agentprobe_testing-0.5.0/agentprobe/validate_scenarios.py +489 -0
- agentprobe_testing-0.5.0/agentprobe/world.py +189 -0
- agentprobe_testing-0.5.0/agentprobe_testing.egg-info/PKG-INFO +127 -0
- agentprobe_testing-0.5.0/agentprobe_testing.egg-info/SOURCES.txt +90 -0
- agentprobe_testing-0.5.0/agentprobe_testing.egg-info/dependency_links.txt +1 -0
- agentprobe_testing-0.5.0/agentprobe_testing.egg-info/entry_points.txt +4 -0
- agentprobe_testing-0.5.0/agentprobe_testing.egg-info/requires.txt +6 -0
- agentprobe_testing-0.5.0/agentprobe_testing.egg-info/top_level.txt +1 -0
- agentprobe_testing-0.5.0/pyproject.toml +39 -0
- agentprobe_testing-0.5.0/setup.cfg +4 -0
- agentprobe_testing-0.5.0/tests/test_access_control_agent.py +142 -0
- agentprobe_testing-0.5.0/tests/test_access_control_domain.py +270 -0
- agentprobe_testing-0.5.0/tests/test_access_control_rule_based_agent.py +331 -0
- agentprobe_testing-0.5.0/tests/test_access_control_scenarios.py +267 -0
- agentprobe_testing-0.5.0/tests/test_access_control_tools.py +232 -0
- agentprobe_testing-0.5.0/tests/test_agreement.py +88 -0
- agentprobe_testing-0.5.0/tests/test_classifier.py +133 -0
- agentprobe_testing-0.5.0/tests/test_cli.py +720 -0
- agentprobe_testing-0.5.0/tests/test_complex_access_control_agent.py +84 -0
- agentprobe_testing-0.5.0/tests/test_diff.py +135 -0
- agentprobe_testing-0.5.0/tests/test_domain.py +93 -0
- agentprobe_testing-0.5.0/tests/test_feedback.py +133 -0
- agentprobe_testing-0.5.0/tests/test_generic_world.py +88 -0
- agentprobe_testing-0.5.0/tests/test_injection.py +544 -0
- agentprobe_testing-0.5.0/tests/test_injector.py +1128 -0
- agentprobe_testing-0.5.0/tests/test_llm.py +163 -0
- agentprobe_testing-0.5.0/tests/test_package_api.py +47 -0
- agentprobe_testing-0.5.0/tests/test_playbook.py +291 -0
- agentprobe_testing-0.5.0/tests/test_quickstart.py +260 -0
- agentprobe_testing-0.5.0/tests/test_reachability.py +210 -0
- agentprobe_testing-0.5.0/tests/test_registry.py +419 -0
- agentprobe_testing-0.5.0/tests/test_report.py +604 -0
- agentprobe_testing-0.5.0/tests/test_rule_based_agent.py +329 -0
- agentprobe_testing-0.5.0/tests/test_runner.py +699 -0
- agentprobe_testing-0.5.0/tests/test_scenarios.py +69 -0
- agentprobe_testing-0.5.0/tests/test_target_agent.py +136 -0
- agentprobe_testing-0.5.0/tests/test_termui.py +74 -0
- agentprobe_testing-0.5.0/tests/test_tools.py +194 -0
- agentprobe_testing-0.5.0/tests/test_trajectory.py +28 -0
- agentprobe_testing-0.5.0/tests/test_triage.py +238 -0
- agentprobe_testing-0.5.0/tests/test_validate_scenarios.py +540 -0
- agentprobe_testing-0.5.0/tests/test_world.py +136 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Business Source License 1.1
|
|
2
|
+
|
|
3
|
+
Parameters
|
|
4
|
+
|
|
5
|
+
Licensor: Agent-Probe
|
|
6
|
+
Licensed Work: AgentProbe
|
|
7
|
+
The Licensed Work is (c) 2026 Agent-Probe
|
|
8
|
+
Additional Use Grant: You may use, copy, modify, and make production use of
|
|
9
|
+
the Licensed Work for any purpose -- including testing,
|
|
10
|
+
developing, and operating your own software agents --
|
|
11
|
+
free of charge, provided that you do not offer the
|
|
12
|
+
Licensed Work, or a modified or derivative version of
|
|
13
|
+
it, to third parties as part of a hosted, managed, or
|
|
14
|
+
embedded service that competes with a paid product or
|
|
15
|
+
service offered by the Licensor (including, without
|
|
16
|
+
limitation, hosted domain generation, hosted
|
|
17
|
+
dashboards, or hosted test-run storage substantially
|
|
18
|
+
similar to those offered at
|
|
19
|
+
https://agentprobe-api.agentprobe.workers.dev).
|
|
20
|
+
Change Date: 2030-01-01
|
|
21
|
+
Change License: Apache License, Version 2.0
|
|
22
|
+
|
|
23
|
+
For information about alternative licensing arrangements for the Licensed
|
|
24
|
+
Work, please open an issue at https://github.com/Agent-Probe/agentprobe.
|
|
25
|
+
|
|
26
|
+
Notice
|
|
27
|
+
|
|
28
|
+
The Business Source License (this document, or the "License") is not an
|
|
29
|
+
Open Source license. However, the Licensed Work will eventually be made
|
|
30
|
+
available under an Open Source License, as stated in this License.
|
|
31
|
+
|
|
32
|
+
License text copyright (c) 2017 MariaDB Corporation Ab, All Rights
|
|
33
|
+
Reserved. "Business Source License" is a trademark of MariaDB Corporation
|
|
34
|
+
Ab.
|
|
35
|
+
|
|
36
|
+
-----------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
Business Source License 1.1
|
|
39
|
+
|
|
40
|
+
Terms
|
|
41
|
+
|
|
42
|
+
The Licensor hereby grants you the right to copy, modify, create
|
|
43
|
+
derivative works, redistribute, and make non-production use of the
|
|
44
|
+
Licensed Work. The Licensor may make an Additional Use Grant, above,
|
|
45
|
+
permitting limited production use.
|
|
46
|
+
|
|
47
|
+
Effective on the Change Date, or the fourth anniversary of the first
|
|
48
|
+
publicly available distribution of a specific version of the Licensed
|
|
49
|
+
Work under this License, whichever comes first, the Licensor hereby
|
|
50
|
+
grants you rights under the terms of the Change License, and the rights
|
|
51
|
+
granted in the paragraph above terminate.
|
|
52
|
+
|
|
53
|
+
If your use of the Licensed Work does not comply with the requirements
|
|
54
|
+
currently in effect as described in this License, you must purchase a
|
|
55
|
+
commercial license from the Licensor, its affiliated entities, or
|
|
56
|
+
authorized resellers, or you must refrain from using the Licensed Work.
|
|
57
|
+
|
|
58
|
+
All copies of the original and modified Licensed Work, and derivative
|
|
59
|
+
works of the Licensed Work, are subject to this License. This License
|
|
60
|
+
applies separately for each version of the Licensed Work and the Change
|
|
61
|
+
Date may vary for each version of the Licensed Work released by
|
|
62
|
+
Licensor.
|
|
63
|
+
|
|
64
|
+
You must conspicuously display this License on each original or
|
|
65
|
+
modified copy of the Licensed Work. If you receive the Licensed Work in
|
|
66
|
+
original or modified form from a third party, the terms and conditions
|
|
67
|
+
set forth in this License apply to your use of that work.
|
|
68
|
+
|
|
69
|
+
Any use of the Licensed Work in violation of this License will
|
|
70
|
+
automatically terminate your rights under this License for the current
|
|
71
|
+
and all other versions of the Licensed Work.
|
|
72
|
+
|
|
73
|
+
This License does not grant you any right in any trademark or logo of
|
|
74
|
+
Licensor or its affiliates (provided that you may use a trademark or
|
|
75
|
+
logo of Licensor as expressly required by this License).
|
|
76
|
+
|
|
77
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS
|
|
78
|
+
PROVIDED ON AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES
|
|
79
|
+
AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION)
|
|
80
|
+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
|
|
81
|
+
NON-INFRINGEMENT, AND TITLE.
|
|
82
|
+
|
|
83
|
+
MariaDB hereby grants you permission to use this License's text to
|
|
84
|
+
license your works, and to refer to it using the trademark "Business
|
|
85
|
+
Source License", as long as you comply with the Covenants of Licensor
|
|
86
|
+
below.
|
|
87
|
+
|
|
88
|
+
Covenants of Licensor
|
|
89
|
+
|
|
90
|
+
In consideration of the right to use this License's text and the
|
|
91
|
+
"Business Source License" name and trademark, Licensor covenants to
|
|
92
|
+
MariaDB, and to all other recipients of the licensed work to be
|
|
93
|
+
provided by Licensor:
|
|
94
|
+
|
|
95
|
+
1. To specify as the Change License the GPL Version 2.0 or any later
|
|
96
|
+
version, or a license that is compatible with GPL Version 2.0 or a
|
|
97
|
+
later version, where "compatible" means that software provided
|
|
98
|
+
under the Change License can be included in a program with software
|
|
99
|
+
provided under GPL Version 2.0 or a later version. Licensor may
|
|
100
|
+
specify additional Change Licenses without limitation.
|
|
101
|
+
|
|
102
|
+
2. To either: (a) specify an additional grant of rights to use that
|
|
103
|
+
does not impose any additional restriction on the right granted in
|
|
104
|
+
this License, as the Additional Use Grant, or (b) insert the text
|
|
105
|
+
"None".
|
|
106
|
+
|
|
107
|
+
3. To specify a Change Date.
|
|
108
|
+
|
|
109
|
+
4. Not to modify this License in any other way.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentprobe-testing
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Adaptive chaos-testing for LLM agents: a live model-driven Injector that reads an agent's real trajectory and decides where to break something, instead of scripting perturbations in advance.
|
|
5
|
+
License: Business Source License 1.1
|
|
6
|
+
|
|
7
|
+
Parameters
|
|
8
|
+
|
|
9
|
+
Licensor: Agent-Probe
|
|
10
|
+
Licensed Work: AgentProbe
|
|
11
|
+
The Licensed Work is (c) 2026 Agent-Probe
|
|
12
|
+
Additional Use Grant: You may use, copy, modify, and make production use of
|
|
13
|
+
the Licensed Work for any purpose -- including testing,
|
|
14
|
+
developing, and operating your own software agents --
|
|
15
|
+
free of charge, provided that you do not offer the
|
|
16
|
+
Licensed Work, or a modified or derivative version of
|
|
17
|
+
it, to third parties as part of a hosted, managed, or
|
|
18
|
+
embedded service that competes with a paid product or
|
|
19
|
+
service offered by the Licensor (including, without
|
|
20
|
+
limitation, hosted domain generation, hosted
|
|
21
|
+
dashboards, or hosted test-run storage substantially
|
|
22
|
+
similar to those offered at
|
|
23
|
+
https://agentprobe-api.agentprobe.workers.dev).
|
|
24
|
+
Change Date: 2030-01-01
|
|
25
|
+
Change License: Apache License, Version 2.0
|
|
26
|
+
|
|
27
|
+
For information about alternative licensing arrangements for the Licensed
|
|
28
|
+
Work, please open an issue at https://github.com/Agent-Probe/agentprobe.
|
|
29
|
+
|
|
30
|
+
Notice
|
|
31
|
+
|
|
32
|
+
The Business Source License (this document, or the "License") is not an
|
|
33
|
+
Open Source license. However, the Licensed Work will eventually be made
|
|
34
|
+
available under an Open Source License, as stated in this License.
|
|
35
|
+
|
|
36
|
+
License text copyright (c) 2017 MariaDB Corporation Ab, All Rights
|
|
37
|
+
Reserved. "Business Source License" is a trademark of MariaDB Corporation
|
|
38
|
+
Ab.
|
|
39
|
+
|
|
40
|
+
-----------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
Business Source License 1.1
|
|
43
|
+
|
|
44
|
+
Terms
|
|
45
|
+
|
|
46
|
+
The Licensor hereby grants you the right to copy, modify, create
|
|
47
|
+
derivative works, redistribute, and make non-production use of the
|
|
48
|
+
Licensed Work. The Licensor may make an Additional Use Grant, above,
|
|
49
|
+
permitting limited production use.
|
|
50
|
+
|
|
51
|
+
Effective on the Change Date, or the fourth anniversary of the first
|
|
52
|
+
publicly available distribution of a specific version of the Licensed
|
|
53
|
+
Work under this License, whichever comes first, the Licensor hereby
|
|
54
|
+
grants you rights under the terms of the Change License, and the rights
|
|
55
|
+
granted in the paragraph above terminate.
|
|
56
|
+
|
|
57
|
+
If your use of the Licensed Work does not comply with the requirements
|
|
58
|
+
currently in effect as described in this License, you must purchase a
|
|
59
|
+
commercial license from the Licensor, its affiliated entities, or
|
|
60
|
+
authorized resellers, or you must refrain from using the Licensed Work.
|
|
61
|
+
|
|
62
|
+
All copies of the original and modified Licensed Work, and derivative
|
|
63
|
+
works of the Licensed Work, are subject to this License. This License
|
|
64
|
+
applies separately for each version of the Licensed Work and the Change
|
|
65
|
+
Date may vary for each version of the Licensed Work released by
|
|
66
|
+
Licensor.
|
|
67
|
+
|
|
68
|
+
You must conspicuously display this License on each original or
|
|
69
|
+
modified copy of the Licensed Work. If you receive the Licensed Work in
|
|
70
|
+
original or modified form from a third party, the terms and conditions
|
|
71
|
+
set forth in this License apply to your use of that work.
|
|
72
|
+
|
|
73
|
+
Any use of the Licensed Work in violation of this License will
|
|
74
|
+
automatically terminate your rights under this License for the current
|
|
75
|
+
and all other versions of the Licensed Work.
|
|
76
|
+
|
|
77
|
+
This License does not grant you any right in any trademark or logo of
|
|
78
|
+
Licensor or its affiliates (provided that you may use a trademark or
|
|
79
|
+
logo of Licensor as expressly required by this License).
|
|
80
|
+
|
|
81
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS
|
|
82
|
+
PROVIDED ON AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES
|
|
83
|
+
AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION)
|
|
84
|
+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
|
|
85
|
+
NON-INFRINGEMENT, AND TITLE.
|
|
86
|
+
|
|
87
|
+
MariaDB hereby grants you permission to use this License's text to
|
|
88
|
+
license your works, and to refer to it using the trademark "Business
|
|
89
|
+
Source License", as long as you comply with the Covenants of Licensor
|
|
90
|
+
below.
|
|
91
|
+
|
|
92
|
+
Covenants of Licensor
|
|
93
|
+
|
|
94
|
+
In consideration of the right to use this License's text and the
|
|
95
|
+
"Business Source License" name and trademark, Licensor covenants to
|
|
96
|
+
MariaDB, and to all other recipients of the licensed work to be
|
|
97
|
+
provided by Licensor:
|
|
98
|
+
|
|
99
|
+
1. To specify as the Change License the GPL Version 2.0 or any later
|
|
100
|
+
version, or a license that is compatible with GPL Version 2.0 or a
|
|
101
|
+
later version, where "compatible" means that software provided
|
|
102
|
+
under the Change License can be included in a program with software
|
|
103
|
+
provided under GPL Version 2.0 or a later version. Licensor may
|
|
104
|
+
specify additional Change Licenses without limitation.
|
|
105
|
+
|
|
106
|
+
2. To either: (a) specify an additional grant of rights to use that
|
|
107
|
+
does not impose any additional restriction on the right granted in
|
|
108
|
+
this License, as the Additional Use Grant, or (b) insert the text
|
|
109
|
+
"None".
|
|
110
|
+
|
|
111
|
+
3. To specify a Change Date.
|
|
112
|
+
|
|
113
|
+
4. Not to modify this License in any other way.
|
|
114
|
+
|
|
115
|
+
Project-URL: Repository, https://github.com/Agent-Probe/agentprobe
|
|
116
|
+
Project-URL: Changelog, https://github.com/Agent-Probe/agentprobe/blob/main/CHANGELOG.md
|
|
117
|
+
Classifier: Development Status :: 4 - Beta
|
|
118
|
+
Classifier: Intended Audience :: Developers
|
|
119
|
+
Classifier: Programming Language :: Python :: 3
|
|
120
|
+
Requires-Python: >=3.11
|
|
121
|
+
License-File: LICENSE
|
|
122
|
+
Requires-Dist: anthropic>=1.0.0
|
|
123
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
124
|
+
Provides-Extra: dev
|
|
125
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
126
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
127
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# AgentProbe
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Agent-Probe/agentprobe/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
Chaos-testing for LLM agents: a second agent (the **Injector**) watches
|
|
6
|
+
your agent's live trajectory — its actual tool calls, its actual
|
|
7
|
+
commitments — and adaptively decides where to break something, instead of
|
|
8
|
+
firing perturbations on a fixed schedule authored in advance. See
|
|
9
|
+
[PRIOR_ART.md](PRIOR_ART.md) for how this differs from τ²-bench, Gaia2,
|
|
10
|
+
and STAGE-Claw, all of which script their perturbations offline.
|
|
11
|
+
|
|
12
|
+
Six injection kinds: a failed tool call (`TOOL_ERROR`), a record that
|
|
13
|
+
silently changes after your agent already read it (`STALE_READ`), a new
|
|
14
|
+
fact that contradicts an earlier one (`CONTRADICTION`), information that
|
|
15
|
+
only matters after an irreversible action already happened (`LATE_INFO`),
|
|
16
|
+
a second equally-valid candidate record appearing (`AMBIGUITY`), and
|
|
17
|
+
adversarial content disguised as ordinary data (`PROMPT_INJECTION`).
|
|
18
|
+
|
|
19
|
+
## Quickstart
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install -e .
|
|
23
|
+
python examples/free_zero_cost_demo.py # no API key needed, no network calls at all
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
That runs a real (deterministic, not scripted) decision-tree agent
|
|
27
|
+
against a hardcoded chaos injector across both domains this repo ships
|
|
28
|
+
with, prints a pass/fail report, and saves a step-by-step HTML report
|
|
29
|
+
next to itself. Nothing here costs money.
|
|
30
|
+
|
|
31
|
+
To see it with real Claude agents on both sides (Target reasoning live,
|
|
32
|
+
Injector deciding live), across both domains this repo ships with, which
|
|
33
|
+
does cost a small amount of real API usage:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
python examples/live_llm_test.py # needs ANTHROPIC_API_KEY
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For a harder test subject than the baseline `AccessControlAgent` --
|
|
40
|
+
`ComplexAccessControlAgent` follows an explicit multi-factor decision
|
|
41
|
+
framework (sensitivity vs. clearance vs. justification quality, required
|
|
42
|
+
policy lookups before granting anything high-sensitivity, a mandatory
|
|
43
|
+
re-check right before committing, and explicit prompt-injection-defense
|
|
44
|
+
instructions) instead of a short generic prompt. Same cost profile as
|
|
45
|
+
`live_llm_test.py`, scoped to one scenario per class (clean/split/decoy/
|
|
46
|
+
trap) so it's cheap to run and still exercises everything that framework
|
|
47
|
+
claims:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
python examples/test_complex_agent.py # needs ANTHROPIC_API_KEY
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## As a library
|
|
54
|
+
|
|
55
|
+
The fastest way to test your own agent — write one function, get a report:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import agentprobe as ap
|
|
59
|
+
|
|
60
|
+
def my_agent(task: str, history: list[dict]) -> ap.AgentAction:
|
|
61
|
+
# call your real agent here; history is everything observed so far
|
|
62
|
+
# this run, as {"tool_name", "tool_args", "result", "ok"} dicts
|
|
63
|
+
...
|
|
64
|
+
return ap.AgentAction(kind="tool_call", tool_name="...", tool_args={...})
|
|
65
|
+
# or: return ap.AgentAction(kind="final_answer", text="...")
|
|
66
|
+
|
|
67
|
+
report = ap.quick_test(
|
|
68
|
+
ap.TICKET_SCENARIOS_BY_ID["clean-1"],
|
|
69
|
+
my_agent,
|
|
70
|
+
injector=ap.HardcodedToolErrorInjector(offsets=[0], tool_name="close_ticket"),
|
|
71
|
+
)
|
|
72
|
+
print(report.render())
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
No class to write, no manual state tracking — `quick_test` calls `my_agent`
|
|
76
|
+
fresh for both the clean and chaos runs, so there's nothing to reset
|
|
77
|
+
between them either. This only tests *ticket-support* scenarios though
|
|
78
|
+
(`ap.TICKET_SCENARIOS_BY_ID`) — meaningfully testing a different business's
|
|
79
|
+
agent means authoring a new [`Domain`](agentprobe/domain.py) for it first
|
|
80
|
+
(see "Domains" below); `quick_test` removes the harness boilerplate,
|
|
81
|
+
not the need to describe what your agent's tools and correct answers are.
|
|
82
|
+
|
|
83
|
+
For full control (multi-scenario runs, custom Report fields, manual state
|
|
84
|
+
in your agent), the same thing without the convenience wrapper:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import agentprobe as ap
|
|
88
|
+
|
|
89
|
+
injector = ap.HardcodedToolErrorInjector(offsets=[0], tool_name="close_ticket")
|
|
90
|
+
clean, chaos = ap.run_robustness_pair(
|
|
91
|
+
ap.TICKET_SCENARIOS_BY_ID["clean-1"],
|
|
92
|
+
target_factory=lambda: ap.RuleBasedAgent(), # zero API cost
|
|
93
|
+
injector=injector,
|
|
94
|
+
)
|
|
95
|
+
print(ap.Report(
|
|
96
|
+
mode="robustness", injector_model="hardcoded", target_model="rule_based",
|
|
97
|
+
clean_trajectories=[clean], chaos_trajectories=[chaos],
|
|
98
|
+
).render())
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`import agentprobe` re-exports the pieces most programs need directly —
|
|
102
|
+
see [`agentprobe/__init__.py`](agentprobe/__init__.py) for the full
|
|
103
|
+
surface. The `access_control` domain lives at
|
|
104
|
+
`agentprobe.domains.access_control` rather than being re-exported at the
|
|
105
|
+
top level, to keep this surface to the one domain most people reach for
|
|
106
|
+
first.
|
|
107
|
+
|
|
108
|
+
Beyond one-scenario `quick_test()`, see [USAGE.md](USAGE.md) for: running
|
|
109
|
+
a whole domain at once (`quick_test_all`), a one-line pytest integration
|
|
110
|
+
(`assert_passes`), catching regressions between uploaded runs
|
|
111
|
+
(`check_regression`), and replaying a specific past run's exact chaos
|
|
112
|
+
sequence against a new agent version (`replay_run`) — plus a worked
|
|
113
|
+
end-to-end CI regression-gate example combining all four.
|
|
114
|
+
|
|
115
|
+
## CLI
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
agentprobe run [options]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Key options (`agentprobe run --help` for the full list):
|
|
122
|
+
|
|
123
|
+
| Flag | What it controls |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `--domain {ticket,access_control}` | Which business domain's world/tools/scenarios to run against |
|
|
126
|
+
| `--target {claude,scripted,rule_based}` | `claude`: real Claude tool-calling loop. `scripted`: no-op stub for exercising the harness. `rule_based`: a real deterministic decision tree, zero API cost |
|
|
127
|
+
| `--injector {none,hardcoded,model,replay}` | `hardcoded`: fires one kind on a named tool at a fixed offset, zero cost. `model`: a live Claude Injector reasoning about the trajectory. `replay`: re-fires a previously recorded sequence for free |
|
|
128
|
+
| `--mode {robustness,recovery}` | `robustness`: a clean control run + a chaos run from the same seed. `recovery`: one run, chaos mid-trajectory, no control |
|
|
129
|
+
| `--scenario ID` | Run a single scenario instead of the whole registry |
|
|
130
|
+
| `--list-scenarios` | Print every scenario in `--domain` as JSON and exit without running anything -- for building a CI test matrix |
|
|
131
|
+
| `--classify` | Run the response classifier (HANDLED/IGNORED/MISREAD/OVERREACTED/LOOPED) on every valid injection |
|
|
132
|
+
| `--max-total-cost-usd N` | Stop starting new scenarios once combined Target + Injector spend for this invocation reaches `N` -- a whole-run safety net, separate from each scenario's own much smaller `max_cost_usd` |
|
|
133
|
+
| `--detailed` | Print the full scenario-by-scenario, step-by-step breakdown to the terminal |
|
|
134
|
+
| `--html-out PATH` | Save a self-contained HTML report (always includes the step-by-step detail, collapsed per scenario) |
|
|
135
|
+
| `--json-out PATH` | Save the full trajectory data, for `agentprobe diff` later |
|
|
136
|
+
| `--use-playbook` | Learn from and contribute to a local cross-run history of which triggers actually fire (see "Data moat" below) |
|
|
137
|
+
|
|
138
|
+
Other subcommands: `agentprobe diff before.json after.json
|
|
139
|
+
--fail-on-regression` (CI gate), `agentprobe score-agreement
|
|
140
|
+
labeled.json` (classifier hand-labeling agreement), `agentprobe
|
|
141
|
+
apply-feedback feedback.json` (fold human feedback into the playbook).
|
|
142
|
+
|
|
143
|
+
## Domains
|
|
144
|
+
|
|
145
|
+
A **domain** is a business world (entities + tools + business rules) the
|
|
146
|
+
Injector and Target both operate on. This repo ships two:
|
|
147
|
+
|
|
148
|
+
- **ticket** (default): customer support tickets — refund/close/reply/
|
|
149
|
+
escalate over tickets, orders, customers.
|
|
150
|
+
- **access_control**: employees requesting access to internal systems —
|
|
151
|
+
grant/close/reply/escalate over requests, resources, employees.
|
|
152
|
+
|
|
153
|
+
The actual chaos-injection engine (trigger timing, the six injection
|
|
154
|
+
kinds, the reachability checker, the Playbook) doesn't know or care which
|
|
155
|
+
domain it's running — see [`agentprobe/domain.py`](agentprobe/domain.py)
|
|
156
|
+
for the `Domain` bundle that's the seam between them, and
|
|
157
|
+
[`agentprobe/domains/access_control/`](agentprobe/domains/access_control/)
|
|
158
|
+
for a complete worked second example (entities, toolkit, scenarios, a
|
|
159
|
+
live Claude agent, a free rule-based agent, and its own Injector system
|
|
160
|
+
prompt). Adding a third domain means writing those same pieces for your
|
|
161
|
+
own world — the engine underneath is already generic.
|
|
162
|
+
|
|
163
|
+
## Generating a domain instead of writing one
|
|
164
|
+
|
|
165
|
+
Writing `agentprobe/domain.py`'s `Domain` bundle by hand (entities, tools,
|
|
166
|
+
business rules) is real, unavoidable work — see "Domains" above. If you'd
|
|
167
|
+
rather not write it yourself, [agentprobe-api.agentprobe.workers.dev](https://agentprobe-api.agentprobe.workers.dev)
|
|
168
|
+
can generate one from your existing tool schemas plus a plain-English
|
|
169
|
+
description of your business rules, and hand you back a key:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
import agentprobe as ap
|
|
173
|
+
|
|
174
|
+
fetched = ap.fetch_domain("joeblow-ai-agents_bd0725d86767c7481581fc41")
|
|
175
|
+
report = ap.quick_test(
|
|
176
|
+
fetched.scenarios["wrong-order-among-several"],
|
|
177
|
+
lambda: ap.TargetAgent(system_prompt="You are JoeBlow's order support agent."),
|
|
178
|
+
domain=fetched.domain,
|
|
179
|
+
)
|
|
180
|
+
print(report.render())
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Trust disclaimer — read this before using `fetch_domain`:** the domain
|
|
184
|
+
behind a key is Python code an LLM generated from your description, not
|
|
185
|
+
code AgentProbe's authors reviewed. `fetch_domain` downloads and *executes*
|
|
186
|
+
that code locally, and prints a warning with a `review_url` every time it
|
|
187
|
+
runs — open that link and skim the generated business logic (what counts
|
|
188
|
+
as "already refunded," what's irreversible, what a precondition checks)
|
|
189
|
+
before trusting any report it produces. A subtly wrong rule doesn't error;
|
|
190
|
+
it just makes your test pass without proving anything real about your
|
|
191
|
+
agent. Only fetch a key you generated for your own business, the same
|
|
192
|
+
rule as never installing a package from someone you don't trust.
|
|
193
|
+
|
|
194
|
+
## The website: accounts, personal API keys, and a dashboard
|
|
195
|
+
|
|
196
|
+
[agentprobe-api.agentprobe.workers.dev](https://agentprobe-api.agentprobe.workers.dev)
|
|
197
|
+
is the real site, not just the API — sign in with GitHub and you get:
|
|
198
|
+
|
|
199
|
+
- **Your Keys** — every domain you've generated (via `/v1/generate` with
|
|
200
|
+
your API key attached), click-to-copy, so you don't have to keep your
|
|
201
|
+
own notes of which key was which business.
|
|
202
|
+
- **Your Runs** — a history of test runs you've uploaded, each with its
|
|
203
|
+
full report viewable in the browser.
|
|
204
|
+
- **API access** (on the Dashboard) — create a personal key
|
|
205
|
+
(`ap_live_...`, shown once at creation) to authenticate the two things
|
|
206
|
+
above from your own scripts.
|
|
207
|
+
|
|
208
|
+
Attribute a generated domain to your account by passing the key as a
|
|
209
|
+
bearer token:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
curl -X POST https://agentprobe-api.agentprobe.workers.dev/v1/generate \
|
|
213
|
+
-H "Authorization: Bearer ap_live_..." \
|
|
214
|
+
-H "content-type: application/json" \
|
|
215
|
+
-d '{"name": "...", "tool_schemas": [...], "business_description": "..."}'
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
And upload a finished run so it shows up under Your Runs by passing the
|
|
219
|
+
same key to `quick_test()`:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
report = ap.quick_test(
|
|
223
|
+
scenario, my_agent, domain=fetched.domain,
|
|
224
|
+
upload_api_key="ap_live_...", # from Your Dashboard
|
|
225
|
+
domain_key=fetched_key, # optional -- links the run back to its domain
|
|
226
|
+
)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Both are entirely optional — omit `upload_api_key` (the default) and
|
|
230
|
+
`quick_test()` stays fully local, same as always. See
|
|
231
|
+
[`server/README.md`](server/README.md) for how the site itself is built
|
|
232
|
+
and deployed (Cloudflare Worker + D1 + GitHub OAuth).
|
|
233
|
+
|
|
234
|
+
## Cost
|
|
235
|
+
|
|
236
|
+
Nothing about this harness requires API spend to develop or test against:
|
|
237
|
+
`--target scripted` / `--target rule_based` and `--injector none` /
|
|
238
|
+
`--injector hardcoded` make zero network calls between them. Live testing
|
|
239
|
+
(`--target claude`, `--injector model`, `--classify`) uses the real
|
|
240
|
+
Anthropic API and costs real money — check `agentprobe/llm.py`'s pricing
|
|
241
|
+
table for current per-model rates, and a run's own report always prints
|
|
242
|
+
its actual cost breakdown (target/injector/classifier/total).
|
|
243
|
+
|
|
244
|
+
## Data moat
|
|
245
|
+
|
|
246
|
+
`--use-playbook` accumulates cross-run outcome data (which trigger
|
|
247
|
+
actually fires for a given injection kind and scenario shape) into a
|
|
248
|
+
local JSON file, surfaced back into the Injector's prompt as an advisory
|
|
249
|
+
hint on future runs — see [`agentprobe/playbook.py`](agentprobe/playbook.py).
|
|
250
|
+
`--export-for-feedback` / `agentprobe apply-feedback` let a human add
|
|
251
|
+
free-text judgment (what was good, what should change, what was missing)
|
|
252
|
+
about a run's injections into the same file — see
|
|
253
|
+
[`agentprobe/feedback.py`](agentprobe/feedback.py). Both are keyed by
|
|
254
|
+
structural scenario shape, not by scenario/customer content, so they
|
|
255
|
+
don't touch a BYOK customer's actual data isolation.
|
|
256
|
+
|
|
257
|
+
## Testing
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
pytest tests/ -q
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
600+ tests, all free — no test in this suite makes a live API call
|
|
264
|
+
(anything that would is mocked). Contributions should keep it that way.
|
|
265
|
+
|
|
266
|
+
The website/API server (`server/`) has its own separate suite, same rule:
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
cd server && node --test test/
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
90+ tests against fake D1/KV/fetch — no real Cloudflare account, network
|
|
273
|
+
access, or secrets needed for either suite. Both run in CI on every push.
|
|
274
|
+
|
|
275
|
+
See [CHANGELOG.md](CHANGELOG.md) for what's new in each version.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""AgentProbe: chaos-testing for LLM agents, via a second agent (the
|
|
2
|
+
Injector) that watches a Target's live trajectory and adaptively decides
|
|
3
|
+
where to break something -- see README.md for the full pitch.
|
|
4
|
+
|
|
5
|
+
This re-exports the pieces most programs actually need so a simple case
|
|
6
|
+
doesn't require knowing the submodule layout:
|
|
7
|
+
|
|
8
|
+
import agentprobe as ap
|
|
9
|
+
|
|
10
|
+
injector = ap.HardcodedToolErrorInjector(offsets=[0], tool_name="close_ticket")
|
|
11
|
+
clean, chaos = ap.run_robustness_pair(
|
|
12
|
+
ap.TICKET_SCENARIOS_BY_ID["clean-1"],
|
|
13
|
+
target_factory=lambda: ap.RuleBasedAgent(), # zero API cost
|
|
14
|
+
injector=injector,
|
|
15
|
+
)
|
|
16
|
+
print(ap.Report(
|
|
17
|
+
mode="robustness", injector_model="hardcoded", target_model="rule_based",
|
|
18
|
+
clean_trajectories=[clean], chaos_trajectories=[chaos],
|
|
19
|
+
).render())
|
|
20
|
+
|
|
21
|
+
Everything here is free (RuleBasedAgent + HardcodedToolErrorInjector make
|
|
22
|
+
no network calls at all). Swap in TargetAgent/ModelInjector for the real,
|
|
23
|
+
billed thing.
|
|
24
|
+
|
|
25
|
+
A second domain (access-control) ships in agentprobe.domains.access_control
|
|
26
|
+
rather than being re-exported here, to keep this top-level surface to the
|
|
27
|
+
one domain most people reach for first -- see domain.py for the seam that
|
|
28
|
+
lets you plug in a third.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from agentprobe.agents.base import Agent, AgentAction
|
|
34
|
+
from agentprobe.agents.rule_based import RuleBasedAgent
|
|
35
|
+
from agentprobe.agents.scripted import ScriptedAgent
|
|
36
|
+
from agentprobe.agents.target_agent import TargetAgent
|
|
37
|
+
from agentprobe.domain import Domain, TICKET_DOMAIN
|
|
38
|
+
from agentprobe.injection import AppliedInjection, Injection, InjectionKind, injection_was_triggered
|
|
39
|
+
from agentprobe.injector import (
|
|
40
|
+
HardcodedToolErrorInjector,
|
|
41
|
+
Injector,
|
|
42
|
+
ModelInjector,
|
|
43
|
+
NullInjector,
|
|
44
|
+
RecordingInjector,
|
|
45
|
+
ReplayInjector,
|
|
46
|
+
)
|
|
47
|
+
from agentprobe.quickstart import assert_passes, quick_test, quick_test_all, wrap_agent
|
|
48
|
+
from agentprobe.registry import (
|
|
49
|
+
FetchedDomain,
|
|
50
|
+
RegressionResult,
|
|
51
|
+
check_regression,
|
|
52
|
+
fetch_domain,
|
|
53
|
+
get_latest_run,
|
|
54
|
+
replay_run,
|
|
55
|
+
upload_run,
|
|
56
|
+
)
|
|
57
|
+
from agentprobe.report import InjectionRecord, Report
|
|
58
|
+
from agentprobe.runner import run_recovery, run_robustness_pair
|
|
59
|
+
from agentprobe.scenario import CommitPattern, FactPattern, GoalSpec, Scenario
|
|
60
|
+
from agentprobe.scenarios.registry import ALL_SCENARIOS as TICKET_SCENARIOS
|
|
61
|
+
from agentprobe.scenarios.registry import BY_ID as TICKET_SCENARIOS_BY_ID
|
|
62
|
+
|
|
63
|
+
__version__ = "0.5.0"
|
|
64
|
+
|
|
65
|
+
__all__ = [
|
|
66
|
+
"Agent",
|
|
67
|
+
"AgentAction",
|
|
68
|
+
"AppliedInjection",
|
|
69
|
+
"CommitPattern",
|
|
70
|
+
"Domain",
|
|
71
|
+
"FactPattern",
|
|
72
|
+
"FetchedDomain",
|
|
73
|
+
"GoalSpec",
|
|
74
|
+
"HardcodedToolErrorInjector",
|
|
75
|
+
"Injection",
|
|
76
|
+
"InjectionKind",
|
|
77
|
+
"InjectionRecord",
|
|
78
|
+
"Injector",
|
|
79
|
+
"ModelInjector",
|
|
80
|
+
"NullInjector",
|
|
81
|
+
"RecordingInjector",
|
|
82
|
+
"RegressionResult",
|
|
83
|
+
"ReplayInjector",
|
|
84
|
+
"Report",
|
|
85
|
+
"RuleBasedAgent",
|
|
86
|
+
"Scenario",
|
|
87
|
+
"ScriptedAgent",
|
|
88
|
+
"TICKET_DOMAIN",
|
|
89
|
+
"TICKET_SCENARIOS",
|
|
90
|
+
"TICKET_SCENARIOS_BY_ID",
|
|
91
|
+
"TargetAgent",
|
|
92
|
+
"assert_passes",
|
|
93
|
+
"check_regression",
|
|
94
|
+
"fetch_domain",
|
|
95
|
+
"get_latest_run",
|
|
96
|
+
"injection_was_triggered",
|
|
97
|
+
"quick_test",
|
|
98
|
+
"quick_test_all",
|
|
99
|
+
"replay_run",
|
|
100
|
+
"run_recovery",
|
|
101
|
+
"run_robustness_pair",
|
|
102
|
+
"upload_run",
|
|
103
|
+
"wrap_agent",
|
|
104
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Agent interface the runner drives. An agent is a small state machine:
|
|
2
|
+
start a task, propose an action, observe the result, repeat."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Literal, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class AgentAction:
|
|
13
|
+
kind: Literal["tool_call", "final_answer"]
|
|
14
|
+
tool_name: Optional[str] = None
|
|
15
|
+
tool_args: Optional[dict[str, Any]] = None
|
|
16
|
+
text: Optional[str] = None
|
|
17
|
+
latency_s: float = 0.0
|
|
18
|
+
cost_usd: float = 0.0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Agent(ABC):
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def start(self, task: str, tool_schemas: list[dict]) -> None:
|
|
24
|
+
"""Reset internal state for a new scenario."""
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def next_action(self) -> AgentAction:
|
|
28
|
+
"""Propose the next step: a tool call or a final answer."""
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def observe(self, tool_name: str, tool_args: dict, result: Any, ok: bool) -> None:
|
|
32
|
+
"""Feed back the result of the tool call just executed."""
|