agentprobe-testing 0.5.1__tar.gz → 0.5.3__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.1 → agentprobe_testing-0.5.3}/PKG-INFO +1 -1
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/README.md +32 -14
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/__init__.py +1 -1
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/quickstart.py +22 -2
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/registry.py +6 -6
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/report.py +4 -4
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe_testing.egg-info/PKG-INFO +1 -1
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/pyproject.toml +1 -1
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_quickstart.py +23 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_registry.py +2 -2
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/LICENSE +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/agents/__init__.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/agents/base.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/agents/rule_based.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/agents/scripted.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/agents/target_agent.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/agreement.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/classifier.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/cli.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/diff.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domain.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/__init__.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/__init__.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/agent.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/clean.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/complex_agent.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/decoy.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/domain.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/entities.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/injector_prompt.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/rule_based_agent.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/scenarios.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/split.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/tools.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/trap.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/feedback.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/generic_world.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/injection.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/injector.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/llm.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/playbook.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/reachability.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/runner.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/scenario.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/scenarios/__init__.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/scenarios/clean.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/scenarios/decoy.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/scenarios/registry.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/scenarios/split.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/scenarios/trap.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/termui.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/tools.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/trajectory.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/triage.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/validate_scenarios.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/world.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe_testing.egg-info/SOURCES.txt +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe_testing.egg-info/dependency_links.txt +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe_testing.egg-info/entry_points.txt +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe_testing.egg-info/requires.txt +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe_testing.egg-info/top_level.txt +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/setup.cfg +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_access_control_agent.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_access_control_domain.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_access_control_rule_based_agent.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_access_control_scenarios.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_access_control_tools.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_agreement.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_classifier.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_cli.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_complex_access_control_agent.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_diff.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_domain.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_feedback.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_generic_world.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_injection.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_injector.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_llm.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_package_api.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_playbook.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_reachability.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_report.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_rule_based_agent.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_runner.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_scenarios.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_target_agent.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_termui.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_tools.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_trajectory.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_triage.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_validate_scenarios.py +0 -0
- {agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_world.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentprobe-testing
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
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
5
|
License: Business Source License 1.1
|
|
6
6
|
|
|
@@ -185,15 +185,18 @@ Writing `agentprobe/domain.py`'s `Domain` bundle by hand (entities, tools,
|
|
|
185
185
|
business rules) is real, unavoidable work — see "Domains" above. If you'd
|
|
186
186
|
rather not write it yourself, [agentprobe-api.agentprobe.workers.dev](https://agentprobe-api.agentprobe.workers.dev)
|
|
187
187
|
can generate one from your existing tool schemas plus a plain-English
|
|
188
|
-
description of your business rules, and hand you back a key
|
|
188
|
+
description of your business rules, and hand you back a key — either via
|
|
189
|
+
[/generate](https://agentprobe-api.agentprobe.workers.dev/generate) on the
|
|
190
|
+
site (sign in, fill in a form, get the key and the generated source back
|
|
191
|
+
right on the page — no `curl` needed) or the raw API:
|
|
189
192
|
|
|
190
193
|
```python
|
|
191
194
|
import agentprobe as ap
|
|
192
195
|
|
|
193
|
-
fetched = ap.fetch_domain("
|
|
196
|
+
fetched = ap.fetch_domain("meridian-ai-agents_bd0725d86767c7481581fc41")
|
|
194
197
|
report = ap.quick_test(
|
|
195
198
|
fetched.scenarios["wrong-order-among-several"],
|
|
196
|
-
lambda: ap.TargetAgent(system_prompt="You are
|
|
199
|
+
lambda: ap.TargetAgent(system_prompt="You are Meridian's order support agent."),
|
|
197
200
|
domain=fetched.domain,
|
|
198
201
|
)
|
|
199
202
|
print(report.render())
|
|
@@ -210,19 +213,34 @@ it just makes your test pass without proving anything real about your
|
|
|
210
213
|
agent. Only fetch a key you generated for your own business, the same
|
|
211
214
|
rule as never installing a package from someone you don't trust.
|
|
212
215
|
|
|
213
|
-
## The website:
|
|
216
|
+
## The website: sign in and run a real test in your browser
|
|
214
217
|
|
|
215
218
|
[agentprobe-api.agentprobe.workers.dev](https://agentprobe-api.agentprobe.workers.dev)
|
|
216
219
|
is the real site, not just the API — sign in with GitHub and you get:
|
|
217
220
|
|
|
218
|
-
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
221
|
+
- **`/run`** — one flow, describing to running: describe your business in
|
|
222
|
+
plain English (same generation as `POST /v1/generate`, as a web form
|
|
223
|
+
instead of `curl`), and it writes a real test domain for it, then falls
|
|
224
|
+
straight into picking a scenario from a real dropdown (or "All
|
|
225
|
+
scenarios" to run every one, combined into a single report), naming the
|
|
226
|
+
run, and pointing at your agent's HTTP endpoint plus your own Anthropic
|
|
227
|
+
API key. It runs in the background — the page comes back immediately so
|
|
228
|
+
you can go do something else on the site while it finishes, and it
|
|
229
|
+
shows up under Your Runs once it does. Free to use — you're billed by
|
|
230
|
+
Anthropic for your own API usage, the same as running it yourself. See
|
|
231
|
+
[`runner/README.md`](runner/README.md) for how this actually executes server-side (the real `agentprobe-testing`
|
|
232
|
+
package, not a reimplementation).
|
|
233
|
+
- **Your Runs** — a history of test runs you've uploaded or executed,
|
|
234
|
+
each with its full report viewable in the browser, and a Rerun link
|
|
235
|
+
that jumps straight back into `/run`'s scenario/agent step for any run
|
|
236
|
+
that was started from the site.
|
|
237
|
+
|
|
238
|
+
There's no separate domain-key page on the site anymore — a domain
|
|
239
|
+
generated through `/run` is only ever referenced internally, not
|
|
240
|
+
something you need to copy around. A personal API key
|
|
241
|
+
(`ap_live_...`) for scripting the CLI/library against your account, if
|
|
242
|
+
your account has one, is issued separately from the website (there's no
|
|
243
|
+
self-serve "create a key" page).
|
|
226
244
|
|
|
227
245
|
Attribute a generated domain to your account by passing the key as a
|
|
228
246
|
bearer token:
|
|
@@ -240,7 +258,7 @@ same key to `quick_test()`:
|
|
|
240
258
|
```python
|
|
241
259
|
report = ap.quick_test(
|
|
242
260
|
scenario, my_agent, domain=fetched.domain,
|
|
243
|
-
upload_api_key="ap_live_...", #
|
|
261
|
+
upload_api_key="ap_live_...", # a personal key issued for your account
|
|
244
262
|
domain_key=fetched_key, # optional -- links the run back to its domain
|
|
245
263
|
)
|
|
246
264
|
```
|
|
@@ -288,7 +306,7 @@ The website/API server (`server/`) has its own separate suite, same rule:
|
|
|
288
306
|
cd server && node --test test/
|
|
289
307
|
```
|
|
290
308
|
|
|
291
|
-
|
|
309
|
+
160+ tests against fake D1/KV/fetch — no real Cloudflare account, network
|
|
292
310
|
access, or secrets needed for either suite. Both run in CI on every push.
|
|
293
311
|
|
|
294
312
|
See [CHANGELOG.md](CHANGELOG.md) for what's new in each version.
|
|
@@ -60,7 +60,7 @@ from agentprobe.scenario import CommitPattern, FactPattern, GoalSpec, Scenario
|
|
|
60
60
|
from agentprobe.scenarios.registry import ALL_SCENARIOS as TICKET_SCENARIOS
|
|
61
61
|
from agentprobe.scenarios.registry import BY_ID as TICKET_SCENARIOS_BY_ID
|
|
62
62
|
|
|
63
|
-
__version__ = "0.5.
|
|
63
|
+
__version__ = "0.5.3"
|
|
64
64
|
|
|
65
65
|
__all__ = [
|
|
66
66
|
"Agent",
|
|
@@ -10,6 +10,7 @@ what to do next, test it against one scenario."
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
|
+
import time
|
|
13
14
|
import urllib.request
|
|
14
15
|
from typing import Any, Callable, Optional
|
|
15
16
|
|
|
@@ -135,7 +136,7 @@ def quick_test(
|
|
|
135
136
|
or a plain decide_fn(task, history) -> AgentAction, which gets wrap_agent()
|
|
136
137
|
applied automatically -- either way, nothing else here changes.
|
|
137
138
|
|
|
138
|
-
Pass `upload_api_key` (a personal key
|
|
139
|
+
Pass `upload_api_key` (a personal key issued for your account) to also upload the
|
|
139
140
|
finished report to your AgentProbe account, so it shows up under Your
|
|
140
141
|
Runs -- see registry.upload_run for what this does and its failure
|
|
141
142
|
behavior. Pass `domain_key` too if `domain` came from fetch_domain(), so
|
|
@@ -209,6 +210,7 @@ def quick_test_all(
|
|
|
209
210
|
upload_api_key: Optional[str] = None,
|
|
210
211
|
domain_key: Optional[str] = None,
|
|
211
212
|
record_injections: bool = False,
|
|
213
|
+
max_duration_seconds: Optional[float] = None,
|
|
212
214
|
) -> Report:
|
|
213
215
|
"""Run every scenario in `scenarios` (a dict[str, Scenario] -- e.g.
|
|
214
216
|
TICKET_SCENARIOS or a FetchedDomain's .scenarios -- or a plain list of
|
|
@@ -228,6 +230,16 @@ def quick_test_all(
|
|
|
228
230
|
run (so Your Runs shows them individually, the same granularity as
|
|
229
231
|
calling quick_test() per scenario and uploading each) -- the Report
|
|
230
232
|
this function returns is still the combined summary across all of them.
|
|
233
|
+
|
|
234
|
+
`max_duration_seconds`, if given, stops starting new scenarios once
|
|
235
|
+
that much wall-clock time has passed since the call began -- for a
|
|
236
|
+
caller with its own hard execution ceiling (e.g. a serverless platform
|
|
237
|
+
that kills a job outright past some limit, which can't be caught or
|
|
238
|
+
cleaned up after), a report covering fewer scenarios than asked for is
|
|
239
|
+
far better than getting killed mid-scenario with nothing to show for
|
|
240
|
+
it. The returned Report's `_scenarios_run`/`_scenarios_total`
|
|
241
|
+
attributes say whether this happened, so a caller can tell the
|
|
242
|
+
difference between "ran everything" and "stopped early."
|
|
231
243
|
"""
|
|
232
244
|
items = list(scenarios.items()) if isinstance(scenarios, dict) else [(s.id, s) for s in scenarios]
|
|
233
245
|
if not items:
|
|
@@ -246,8 +258,13 @@ def quick_test_all(
|
|
|
246
258
|
all_records: list[InjectionRecord] = []
|
|
247
259
|
total_injector_cost = 0.0
|
|
248
260
|
combined_injector_model: Optional[str] = None
|
|
261
|
+
start = time.monotonic()
|
|
262
|
+
scenarios_run = 0
|
|
249
263
|
|
|
250
264
|
for _scenario_id, scenario in items:
|
|
265
|
+
if max_duration_seconds is not None and scenarios_run > 0 and (time.monotonic() - start) >= max_duration_seconds:
|
|
266
|
+
break # already have at least one scenario's worth of real results -- stop here, not mid-scenario
|
|
267
|
+
scenarios_run += 1
|
|
251
268
|
injector = make_injector()
|
|
252
269
|
resolved_injector_model = injector_model if injector_model is not None else type(injector).__name__
|
|
253
270
|
if combined_injector_model is None:
|
|
@@ -289,7 +306,7 @@ def quick_test_all(
|
|
|
289
306
|
recorded = [serialize_armed_injection(a) for a in recording.recorded] if recording is not None else None
|
|
290
307
|
upload_run(scenario_report, api_key=upload_api_key, domain_key=domain_key, recorded_injections=recorded)
|
|
291
308
|
|
|
292
|
-
|
|
309
|
+
report = Report(
|
|
293
310
|
mode=mode,
|
|
294
311
|
injector_model=combined_injector_model or "none",
|
|
295
312
|
target_model=target_model,
|
|
@@ -298,6 +315,9 @@ def quick_test_all(
|
|
|
298
315
|
injection_records=all_records,
|
|
299
316
|
injector_cost_usd=total_injector_cost,
|
|
300
317
|
)
|
|
318
|
+
report._scenarios_run = len(all_chaos)
|
|
319
|
+
report._scenarios_total = len(items)
|
|
320
|
+
return report
|
|
301
321
|
|
|
302
322
|
|
|
303
323
|
def assert_passes(report: Report, save_html_on_failure: Optional[str] = None) -> None:
|
|
@@ -125,11 +125,11 @@ def upload_run(
|
|
|
125
125
|
recorded_injections: Optional[list[dict]] = None,
|
|
126
126
|
) -> None:
|
|
127
127
|
"""Upload a finished quick_test() report to your AgentProbe account (POST
|
|
128
|
-
/v1/runs) so it shows up under Your Runs on the
|
|
129
|
-
personal key
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
128
|
+
/v1/runs) so it shows up under Your Runs on the website. `api_key` is a
|
|
129
|
+
personal key issued for your account -- it's never shown again after
|
|
130
|
+
issuance so save it somewhere real (an env var, not a committed file).
|
|
131
|
+
`report` must cover exactly one scenario, same as every report
|
|
132
|
+
quick_test() itself produces.
|
|
133
133
|
|
|
134
134
|
Pass `recorded_injections` (a list of agentprobe.injector.
|
|
135
135
|
serialize_armed_injection() dicts) to make the uploaded run replayable
|
|
@@ -274,7 +274,7 @@ def replay_run(
|
|
|
274
274
|
target_model: str = "custom",
|
|
275
275
|
) -> "Report":
|
|
276
276
|
"""Replay the exact chaos sequence recorded for `run_id` (an id from
|
|
277
|
-
|
|
277
|
+
the website's Your Runs table, or a RegressionResult.previous_run_id)
|
|
278
278
|
against a new agent or model version, instead of paying for a fresh
|
|
279
279
|
ModelInjector decision sequence -- only the Target's own calls repeat.
|
|
280
280
|
|
|
@@ -564,14 +564,14 @@ class Report:
|
|
|
564
564
|
{classification_html}
|
|
565
565
|
</section>
|
|
566
566
|
<section>
|
|
567
|
-
<h2>By injection kind
|
|
567
|
+
<h2>By injection kind: handled rate</h2>
|
|
568
568
|
{kind_table}
|
|
569
569
|
</section>
|
|
570
570
|
<section>
|
|
571
|
-
<h2>Scenario detail
|
|
571
|
+
<h2>Scenario detail: step by step</h2>
|
|
572
572
|
{scenario_detail_html}
|
|
573
573
|
</section>
|
|
574
|
-
<footer>Generated by AgentProbe
|
|
574
|
+
<footer>Generated by AgentProbe. Adaptive chaos-testing for LLM agents.</footer>
|
|
575
575
|
</div>
|
|
576
576
|
</body></html>"""
|
|
577
577
|
|
|
@@ -621,7 +621,7 @@ class Report:
|
|
|
621
621
|
notes = ""
|
|
622
622
|
for a in traj.injections:
|
|
623
623
|
note_class = "injection-note" if a.valid else "injection-note injection-note--invalid"
|
|
624
|
-
validity = "" if a.valid else "
|
|
624
|
+
validity = "" if a.valid else " (<strong>INVALID</strong>)"
|
|
625
625
|
notes += (
|
|
626
626
|
f"<div class='{note_class}'>⚡ injection fired: <strong>{esc(a.injection.kind.value)}</strong> "
|
|
627
627
|
f"@ step {a.fired_at_step} (trigger: <code>{esc(a.trigger_kind)}</code>){validity}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentprobe-testing
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
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
5
|
License: Business Source License 1.1
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agentprobe-testing"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.3"
|
|
4
4
|
description = "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
5
|
requires-python = ">=3.11"
|
|
6
6
|
dependencies = ["anthropic>=1.0.0", "python-dotenv>=1.0.0"]
|
|
@@ -273,6 +273,29 @@ def test_quick_test_all_recovery_mode_has_no_clean_trajectories():
|
|
|
273
273
|
assert len(report.chaos_trajectories) == 2
|
|
274
274
|
|
|
275
275
|
|
|
276
|
+
def test_quick_test_all_reports_scenarios_run_and_total_when_nothing_stops_it_early():
|
|
277
|
+
scenarios = {"clean-1": BY_ID["clean-1"], "clean-2": BY_ID["clean-2"]}
|
|
278
|
+
report = quick_test_all(scenarios, lambda: RuleBasedAgent())
|
|
279
|
+
assert report._scenarios_run == 2
|
|
280
|
+
assert report._scenarios_total == 2
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def test_quick_test_all_stops_early_past_max_duration_seconds_but_always_runs_at_least_one():
|
|
284
|
+
# A caller with its own hard execution ceiling (e.g. a serverless
|
|
285
|
+
# platform that kills the whole job outright past some limit, with no
|
|
286
|
+
# chance to clean up) needs a report covering fewer scenarios instead
|
|
287
|
+
# of getting killed mid-run with nothing to show. max_duration_seconds=0
|
|
288
|
+
# means "already over budget" as soon as any real time has passed --
|
|
289
|
+
# deterministic regardless of how fast RuleBasedAgent actually runs,
|
|
290
|
+
# since real wall-clock time (time.monotonic()) always advances by a
|
|
291
|
+
# nonzero amount between the loop's start and its first elapsed check.
|
|
292
|
+
scenarios = {"clean-1": BY_ID["clean-1"], "clean-2": BY_ID["clean-2"], "clean-1-again": BY_ID["clean-1"]}
|
|
293
|
+
report = quick_test_all(scenarios, lambda: RuleBasedAgent(), max_duration_seconds=0)
|
|
294
|
+
assert report._scenarios_run == 1 # never zero -- see the "always run at least one" guard
|
|
295
|
+
assert report._scenarios_total == 3
|
|
296
|
+
assert report._compute()["n"] == 1 # the returned Report itself only covers what actually ran
|
|
297
|
+
|
|
298
|
+
|
|
276
299
|
def test_quick_test_all_uploads_each_scenario_as_its_own_run():
|
|
277
300
|
import agentprobe.registry as registry_module
|
|
278
301
|
|
|
@@ -175,9 +175,9 @@ def test_upload_run_includes_domain_key_when_given():
|
|
|
175
175
|
report = quick_test(scenario, _clean1_decide)
|
|
176
176
|
poster = make_poster()
|
|
177
177
|
|
|
178
|
-
upload_run(report, api_key="ap_live_test123", domain_key="
|
|
178
|
+
upload_run(report, api_key="ap_live_test123", domain_key="meridian_1234", poster=poster)
|
|
179
179
|
|
|
180
|
-
assert poster.calls[0]["body"]["domain_key"] == "
|
|
180
|
+
assert poster.calls[0]["body"]["domain_key"] == "meridian_1234"
|
|
181
181
|
|
|
182
182
|
|
|
183
183
|
def test_upload_run_rejects_a_report_covering_more_or_fewer_than_one_scenario():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/__init__.py
RENAMED
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/agent.py
RENAMED
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/clean.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/decoy.py
RENAMED
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/domain.py
RENAMED
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/entities.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/scenarios.py
RENAMED
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/split.py
RENAMED
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/tools.py
RENAMED
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe/domains/access_control/trap.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe_testing.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe_testing.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe_testing.egg-info/requires.txt
RENAMED
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/agentprobe_testing.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_access_control_rule_based_agent.py
RENAMED
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_access_control_scenarios.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentprobe_testing-0.5.1 → agentprobe_testing-0.5.3}/tests/test_complex_access_control_agent.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|