agentprobe-testing 1.3.0__tar.gz → 1.4.1__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-1.3.0 → agentprobe_testing-1.4.1}/LICENSE +1 -1
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/PKG-INFO +2 -2
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/README.md +4 -4
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/__init__.py +1 -1
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/quickstart.py +21 -1
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/registry.py +2 -1
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/report.py +14 -2
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe_testing.egg-info/PKG-INFO +2 -2
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe_testing.egg-info/SOURCES.txt +1 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/pyproject.toml +1 -1
- agentprobe_testing-1.4.1/tests/test_injection_mode.py +80 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_registry.py +1 -1
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/agents/__init__.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/agents/base.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/agents/browser_agent.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/agents/rule_based.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/agents/scripted.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/agents/target_agent.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/agreement.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/classifier.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/cli.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/diff.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domain.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/__init__.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/__init__.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/agent.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/clean.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/complex_agent.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/decoy.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/domain.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/entities.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/injector_prompt.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/rule_based_agent.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/scenarios.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/split.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/tools.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/trap.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/__init__.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/agent.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/clean.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/domain.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/entities.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/injector_prompt.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/rule_based_agent.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/scenarios.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/tools.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/trap.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/feedback.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/generic_world.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/injection.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/injector.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/llm.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/playbook.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/priority.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/reachability.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/runner.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/scenario.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/scenarios/__init__.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/scenarios/clean.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/scenarios/decoy.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/scenarios/registry.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/scenarios/split.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/scenarios/trap.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/summarize.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/termui.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/tools.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/trajectory.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/triage.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/validate_scenarios.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/world.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe_testing.egg-info/dependency_links.txt +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe_testing.egg-info/entry_points.txt +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe_testing.egg-info/requires.txt +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe_testing.egg-info/top_level.txt +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/setup.cfg +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_access_control_agent.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_access_control_domain.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_access_control_rule_based_agent.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_access_control_scenarios.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_access_control_tools.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_agreement.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_browser_agent.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_classifier.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_cli.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_complex_access_control_agent.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_diff.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_domain.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_failures.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_feedback.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_generic_world.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_injection.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_injector.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_llm.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_package_api.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_playbook.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_priority.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_quickstart.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_reachability.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_report.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_rule_based_agent.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_runner.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_scenarios.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_target_agent.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_termui.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_tools.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_trajectory.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_triage.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_validate_scenarios.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_web_browsing_domain.py +0 -0
- {agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_world.py +0 -0
|
@@ -16,7 +16,7 @@ Additional Use Grant: You may use, copy, modify, and make production use of
|
|
|
16
16
|
limitation, hosted domain generation, hosted
|
|
17
17
|
dashboards, or hosted test-run storage substantially
|
|
18
18
|
similar to those offered at
|
|
19
|
-
https://
|
|
19
|
+
https://sampact.com).
|
|
20
20
|
Change Date: 2030-01-01
|
|
21
21
|
Change License: Apache License, Version 2.0
|
|
22
22
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentprobe-testing
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.1
|
|
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
|
|
|
@@ -20,7 +20,7 @@ License: Business Source License 1.1
|
|
|
20
20
|
limitation, hosted domain generation, hosted
|
|
21
21
|
dashboards, or hosted test-run storage substantially
|
|
22
22
|
similar to those offered at
|
|
23
|
-
https://
|
|
23
|
+
https://sampact.com).
|
|
24
24
|
Change Date: 2030-01-01
|
|
25
25
|
Change License: Apache License, Version 2.0
|
|
26
26
|
|
|
@@ -183,10 +183,10 @@ own world — the engine underneath is already generic.
|
|
|
183
183
|
|
|
184
184
|
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
|
-
rather not write it yourself, [
|
|
186
|
+
rather not write it yourself, [sampact.com](https://sampact.com)
|
|
187
187
|
can generate one from your existing tool schemas plus a plain-English
|
|
188
188
|
description of your business rules, and hand you back a key — either via
|
|
189
|
-
[/generate](https://
|
|
189
|
+
[/generate](https://sampact.com/generate) on the
|
|
190
190
|
site (sign in, fill in a form, get the key and the generated source back
|
|
191
191
|
right on the page — no `curl` needed) or the raw API:
|
|
192
192
|
|
|
@@ -215,7 +215,7 @@ rule as never installing a package from someone you don't trust.
|
|
|
215
215
|
|
|
216
216
|
## The website: sign in and run a real test in your browser
|
|
217
217
|
|
|
218
|
-
[
|
|
218
|
+
[sampact.com](https://sampact.com)
|
|
219
219
|
is the real site, not just the API — sign in with GitHub and you get:
|
|
220
220
|
|
|
221
221
|
- **`/run`** — one flow, describing to running: describe your business in
|
|
@@ -246,7 +246,7 @@ Attribute a generated domain to your account by passing the key as a
|
|
|
246
246
|
bearer token:
|
|
247
247
|
|
|
248
248
|
```bash
|
|
249
|
-
curl -X POST https://
|
|
249
|
+
curl -X POST https://sampact.com/v1/generate \
|
|
250
250
|
-H "Authorization: Bearer ap_live_..." \
|
|
251
251
|
-H "content-type: application/json" \
|
|
252
252
|
-d '{"name": "...", "tool_schemas": [...], "business_description": "..."}'
|
|
@@ -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__ = "1.
|
|
63
|
+
__version__ = "1.4.1"
|
|
64
64
|
|
|
65
65
|
__all__ = [
|
|
66
66
|
"Agent",
|
|
@@ -17,7 +17,7 @@ from typing import Any, Callable, Optional
|
|
|
17
17
|
from agentprobe.agents.base import Agent, AgentAction
|
|
18
18
|
from agentprobe.domain import Domain, TICKET_DOMAIN
|
|
19
19
|
from agentprobe.injection import injection_was_triggered
|
|
20
|
-
from agentprobe.injector import Injector, NullInjector, RecordingInjector, serialize_armed_injection
|
|
20
|
+
from agentprobe.injector import Injector, ModelInjector, NullInjector, RecordingInjector, ReplayInjector, serialize_armed_injection
|
|
21
21
|
from agentprobe.priority import clean_overrides
|
|
22
22
|
from agentprobe.report import InjectionRecord, Report
|
|
23
23
|
from agentprobe.runner import run_recovery, run_robustness_pair
|
|
@@ -186,6 +186,7 @@ def quick_test(
|
|
|
186
186
|
injector = injector if injector is not None else NullInjector()
|
|
187
187
|
target_factory = agent if _looks_like_factory(agent) else wrap_agent(agent)
|
|
188
188
|
resolved_injector_model = injector_model if injector_model is not None else type(injector).__name__
|
|
189
|
+
injection_mode = _injection_mode(injector)
|
|
189
190
|
|
|
190
191
|
recording: Optional[RecordingInjector] = None
|
|
191
192
|
if record_injections:
|
|
@@ -217,6 +218,7 @@ def quick_test(
|
|
|
217
218
|
injection_records=injection_records,
|
|
218
219
|
injector_cost_usd=_injector_cost_usd(injector),
|
|
219
220
|
priority_overrides=clean_overrides(priority_overrides),
|
|
221
|
+
injection_mode=injection_mode,
|
|
220
222
|
)
|
|
221
223
|
if recording is not None:
|
|
222
224
|
# Stashed here (not a Report field -- Report is a general-purpose
|
|
@@ -300,6 +302,7 @@ def quick_test_all(
|
|
|
300
302
|
all_records: list[InjectionRecord] = []
|
|
301
303
|
total_injector_cost = 0.0
|
|
302
304
|
combined_injector_model: Optional[str] = None
|
|
305
|
+
combined_injection_mode: Optional[str] = None
|
|
303
306
|
start = time.monotonic()
|
|
304
307
|
scenarios_run = 0
|
|
305
308
|
|
|
@@ -311,6 +314,7 @@ def quick_test_all(
|
|
|
311
314
|
resolved_injector_model = injector_model if injector_model is not None else type(injector).__name__
|
|
312
315
|
if combined_injector_model is None:
|
|
313
316
|
combined_injector_model = resolved_injector_model
|
|
317
|
+
combined_injection_mode = _injection_mode(injector)
|
|
314
318
|
|
|
315
319
|
recording: Optional[RecordingInjector] = None
|
|
316
320
|
if record_injections:
|
|
@@ -357,6 +361,7 @@ def quick_test_all(
|
|
|
357
361
|
injection_records=all_records,
|
|
358
362
|
injector_cost_usd=total_injector_cost,
|
|
359
363
|
priority_overrides=clean_overrides(priority_overrides),
|
|
364
|
+
injection_mode=combined_injection_mode,
|
|
360
365
|
)
|
|
361
366
|
report._scenarios_run = len(all_chaos)
|
|
362
367
|
if summarize_failures:
|
|
@@ -396,6 +401,21 @@ def assert_passes(report: Report, save_html_on_failure: Optional[str] = None) ->
|
|
|
396
401
|
raise AssertionError("agentprobe: " + "; ".join(failures))
|
|
397
402
|
|
|
398
403
|
|
|
404
|
+
def _injection_mode(injector: Injector) -> Optional[str]:
|
|
405
|
+
"""How this run's failures were chosen, for labeling its report: an
|
|
406
|
+
adaptive ModelInjector decides afresh each time ("exploration" -- finds new
|
|
407
|
+
weaknesses, but two runs aren't directly comparable), a ReplayInjector
|
|
408
|
+
re-fires a recorded sequence ("pinned" -- the same failures every time, so
|
|
409
|
+
scores compare). Anything else (none, hardcoded) isn't labeled. Looks
|
|
410
|
+
through a RecordingInjector's wrapping."""
|
|
411
|
+
inner = getattr(injector, "_inner", injector)
|
|
412
|
+
if isinstance(inner, ReplayInjector):
|
|
413
|
+
return "pinned"
|
|
414
|
+
if isinstance(inner, ModelInjector):
|
|
415
|
+
return "exploration"
|
|
416
|
+
return None
|
|
417
|
+
|
|
418
|
+
|
|
399
419
|
def _injector_cost_usd(injector: Injector) -> float:
|
|
400
420
|
"""ModelInjector is the only injector type with real API cost (none/
|
|
401
421
|
hardcoded/replay never call a model) -- same logic as cli.py's version,
|
|
@@ -29,7 +29,7 @@ from agentprobe.scenario import Scenario
|
|
|
29
29
|
if TYPE_CHECKING:
|
|
30
30
|
from agentprobe.report import Report
|
|
31
31
|
|
|
32
|
-
DEFAULT_BASE_URL = "https://
|
|
32
|
+
DEFAULT_BASE_URL = "https://sampact.com"
|
|
33
33
|
|
|
34
34
|
Fetcher = Callable[[str], dict]
|
|
35
35
|
Poster = Callable[[str, dict, str], dict] # (url, json_body, api_key) -> response dict
|
|
@@ -175,6 +175,7 @@ def upload_run(
|
|
|
175
175
|
# caller's explicit consent to pool them, anonymously, into
|
|
176
176
|
# aggregate benchmarks; False (the default) keeps them private.
|
|
177
177
|
"findings": report.findings(),
|
|
178
|
+
"injection_mode": report.injection_mode,
|
|
178
179
|
"share_anonymized": bool(share_anonymized),
|
|
179
180
|
}
|
|
180
181
|
if recorded_injections is None:
|
|
@@ -306,6 +306,11 @@ class Report:
|
|
|
306
306
|
holds Trajectories/InjectionRecords, not the Injector instances
|
|
307
307
|
themselves. 0.0 (and omitted from output) for none/hardcoded/replay
|
|
308
308
|
injectors, which never call a model at all."""
|
|
309
|
+
injection_mode: Optional[str] = None
|
|
310
|
+
"""How this run's failures were chosen: "exploration" (an adaptive injector
|
|
311
|
+
decided them -- finds new weaknesses, but two runs aren't directly
|
|
312
|
+
comparable) or "pinned" (a recorded sequence was replayed -- the same
|
|
313
|
+
failures every time, so scores compare). None when unknown/unlabeled."""
|
|
309
314
|
priority_overrides: dict[str, str] = field(default_factory=dict)
|
|
310
315
|
"""Tiers (High/Medium/Low) the reader has set for particular failure kinds,
|
|
311
316
|
e.g. {"FALSE_SUCCESS": "High"}. They replace the automatic estimate in
|
|
@@ -412,6 +417,13 @@ class Report:
|
|
|
412
417
|
)
|
|
413
418
|
return out
|
|
414
419
|
|
|
420
|
+
def injection_mode_label(self) -> Optional[str]:
|
|
421
|
+
"""Plain-language form of injection_mode for report headers."""
|
|
422
|
+
return {
|
|
423
|
+
"exploration": "adaptive failures (exploration: not directly comparable with other runs)",
|
|
424
|
+
"pinned": "replayed failures (pinned: comparable with other replays of the same recording)",
|
|
425
|
+
}.get(self.injection_mode or "")
|
|
426
|
+
|
|
415
427
|
def priorities(self) -> list[dict]:
|
|
416
428
|
"""The run's issues ranked highest to lowest priority, with any
|
|
417
429
|
`priority_overrides` applied. See priority.py for how it's scored."""
|
|
@@ -475,7 +487,7 @@ class Report:
|
|
|
475
487
|
lines = [
|
|
476
488
|
blue(
|
|
477
489
|
f"scenarios: {c['n']} mode: {self.mode} injector: {self.injector_model} "
|
|
478
|
-
f"target: {self.target_model}"
|
|
490
|
+
f"target: {self.target_model}" + (f" failures: {self.injection_mode_label()}" if self.injection_mode_label() else "")
|
|
479
491
|
),
|
|
480
492
|
]
|
|
481
493
|
if c["clean_passed"] is not None:
|
|
@@ -811,7 +823,7 @@ class Report:
|
|
|
811
823
|
<div class="page">
|
|
812
824
|
<header>
|
|
813
825
|
<div class="title-row"><span class="health-dot health-dot--{health}"></span><h1>AgentProbe report</h1></div>
|
|
814
|
-
<div class="subtitle">mode: {esc(self.mode)} · injector: {esc(self.injector_model)} · target: {esc(self.target_model)} · {esc(c['n'])} scenarios</div>
|
|
826
|
+
<div class="subtitle">mode: {esc(self.mode)} · injector: {esc(self.injector_model)} · target: {esc(self.target_model)} · {esc(c['n'])} scenarios{(' · ' + esc(self.injection_mode_label())) if self.injection_mode_label() else ''}</div>
|
|
815
827
|
</header>
|
|
816
828
|
{injector_error_banner}
|
|
817
829
|
<div class="stats">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentprobe-testing
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.1
|
|
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
|
|
|
@@ -20,7 +20,7 @@ License: Business Source License 1.1
|
|
|
20
20
|
limitation, hosted domain generation, hosted
|
|
21
21
|
dashboards, or hosted test-run storage substantially
|
|
22
22
|
similar to those offered at
|
|
23
|
-
https://
|
|
23
|
+
https://sampact.com).
|
|
24
24
|
Change Date: 2030-01-01
|
|
25
25
|
Change License: Apache License, Version 2.0
|
|
26
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agentprobe-testing"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.4.1"
|
|
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"]
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Labeling a run exploration (adaptive injector) or pinned (replayed recording)."""
|
|
2
|
+
|
|
3
|
+
from agentprobe.agents.scripted import ScriptedAgent
|
|
4
|
+
from agentprobe.injection import ArmedInjection, Injection, InjectionKind, ResponseSignature, Trigger
|
|
5
|
+
from agentprobe.injector import HardcodedToolErrorInjector, ModelInjector, NullInjector, RecordingInjector, ReplayInjector, serialize_armed_injection, deserialize_armed_injection
|
|
6
|
+
from agentprobe.quickstart import _injection_mode, quick_test, quick_test_all
|
|
7
|
+
from agentprobe.registry import upload_run
|
|
8
|
+
from agentprobe.scenarios.registry import BY_ID
|
|
9
|
+
|
|
10
|
+
AGENT = lambda: ScriptedAgent(script=[("get_ticket", {"id": "T-1001"})])
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _armed(step=1):
|
|
14
|
+
return ArmedInjection(
|
|
15
|
+
injection=Injection(kind=InjectionKind.TOOL_ERROR, payload={"tool_name": "close_ticket", "count": 1}, intent="i",
|
|
16
|
+
expected_signature=ResponseSignature("s"), rationale="r"),
|
|
17
|
+
trigger=Trigger.on_next_action(),
|
|
18
|
+
armed_at_step=step,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_injection_mode_reads_the_injector_type_through_a_recording_wrapper():
|
|
23
|
+
adaptive = ModelInjector.__new__(ModelInjector) # only its type matters here; no client needed
|
|
24
|
+
replay = ReplayInjector([_armed()])
|
|
25
|
+
assert _injection_mode(adaptive) == "exploration"
|
|
26
|
+
assert _injection_mode(replay) == "pinned"
|
|
27
|
+
assert _injection_mode(RecordingInjector(adaptive)) == "exploration"
|
|
28
|
+
assert _injection_mode(RecordingInjector(replay)) == "pinned"
|
|
29
|
+
assert _injection_mode(NullInjector()) is None
|
|
30
|
+
assert _injection_mode(HardcodedToolErrorInjector()) is None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_a_replayed_run_is_labeled_pinned_in_the_report():
|
|
34
|
+
report = quick_test(BY_ID["clean-1"], AGENT, injector=ReplayInjector([]), mode="recovery")
|
|
35
|
+
assert report.injection_mode == "pinned"
|
|
36
|
+
assert "replayed failures (pinned: comparable with other replays of the same recording)" in report.render_html()
|
|
37
|
+
assert "failures: replayed failures (pinned" in report.render()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_recording_a_replay_still_labels_it_pinned():
|
|
41
|
+
report = quick_test(BY_ID["clean-1"], AGENT, injector=ReplayInjector([]), mode="recovery", record_injections=True)
|
|
42
|
+
assert report.injection_mode == "pinned"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_an_unlabeled_injector_adds_no_mode_text():
|
|
46
|
+
report = quick_test(BY_ID["clean-1"], AGENT, injector=NullInjector(), mode="recovery")
|
|
47
|
+
assert report.injection_mode is None and report.injection_mode_label() is None
|
|
48
|
+
assert "pinned" not in report.render_html() and "exploration" not in report.render_html()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_the_exploration_label_says_runs_are_not_directly_comparable():
|
|
52
|
+
from agentprobe.report import Report
|
|
53
|
+
|
|
54
|
+
r = Report("robustness", "m", "t", [], [], injection_mode="exploration")
|
|
55
|
+
assert "adaptive failures (exploration: not directly comparable with other runs)" in r.render_html()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_quick_test_all_labels_the_combined_report_from_its_injectors():
|
|
59
|
+
scenarios = {k: BY_ID[k] for k in ("clean-1", "clean-2")}
|
|
60
|
+
report = quick_test_all(scenarios, AGENT, injector_factory=lambda: ReplayInjector([]), mode="recovery")
|
|
61
|
+
assert report.injection_mode == "pinned"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_upload_run_sends_the_mode_so_the_website_can_label_it():
|
|
65
|
+
sent = {}
|
|
66
|
+
report = quick_test(BY_ID["clean-1"], AGENT, injector=ReplayInjector([]), mode="recovery")
|
|
67
|
+
upload_run(report, api_key="k", poster=lambda url, body, key: sent.update(body))
|
|
68
|
+
assert sent["injection_mode"] == "pinned"
|
|
69
|
+
report2 = quick_test(BY_ID["clean-1"], AGENT, injector=NullInjector(), mode="recovery")
|
|
70
|
+
upload_run(report2, api_key="k", poster=lambda url, body, key: sent.update(body))
|
|
71
|
+
assert sent["injection_mode"] is None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_a_serialized_recording_survives_the_round_trip_the_website_relies_on():
|
|
75
|
+
# The website stores serialize_armed_injection() dicts and the runner rebuilds them with
|
|
76
|
+
# deserialize_armed_injection() before replaying -- this is the contract between the two.
|
|
77
|
+
original = [_armed(2), _armed(5)]
|
|
78
|
+
rebuilt = ReplayInjector([deserialize_armed_injection(serialize_armed_injection(a)) for a in original])
|
|
79
|
+
assert sorted(rebuilt._by_step) == [2, 5]
|
|
80
|
+
assert rebuilt._by_step[2].injection.kind == InjectionKind.TOOL_ERROR
|
|
@@ -161,7 +161,7 @@ def test_upload_run_posts_the_report_to_v1_runs():
|
|
|
161
161
|
|
|
162
162
|
assert len(poster.calls) == 1
|
|
163
163
|
call = poster.calls[0]
|
|
164
|
-
assert call["url"] == "https://
|
|
164
|
+
assert call["url"] == "https://sampact.com/v1/runs"
|
|
165
165
|
assert call["api_key"] == "ap_live_test123"
|
|
166
166
|
assert call["body"]["scenario_id"] == "clean-1"
|
|
167
167
|
assert call["body"]["mode"] == "robustness"
|
|
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-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/__init__.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/agent.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/clean.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/decoy.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/domain.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/entities.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/scenarios.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/split.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/tools.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/access_control/trap.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/__init__.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/agent.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/clean.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/domain.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/entities.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/scenarios.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/tools.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe/domains/web_browsing/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe_testing.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe_testing.egg-info/requires.txt
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/agentprobe_testing.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_access_control_rule_based_agent.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/tests/test_access_control_scenarios.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentprobe_testing-1.3.0 → agentprobe_testing-1.4.1}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|