agentprobe-testing 1.5.0__tar.gz → 1.5.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.5.0/agentprobe_testing.egg-info → agentprobe_testing-1.5.1}/PKG-INFO +1 -1
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/__init__.py +1 -1
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/agents/target_agent.py +3 -1
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/classifier.py +3 -1
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/agent.py +3 -1
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/complex_agent.py +3 -1
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/user_sim.py +2 -1
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/agent.py +3 -1
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/injector.py +3 -1
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/llm.py +32 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/registry.py +13 -4
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/summarize.py +2 -1
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/validate_scenarios.py +3 -1
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1/agentprobe_testing.egg-info}/PKG-INFO +1 -1
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/pyproject.toml +1 -1
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_llm.py +44 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_registry.py +15 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/LICENSE +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/README.md +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/agents/__init__.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/agents/base.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/agents/browser_agent.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/agents/rule_based.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/agents/scripted.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/agreement.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/cli.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/diff.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domain.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/__init__.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/__init__.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/clean.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/decoy.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/domain.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/entities.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/injector_prompt.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/rule_based_agent.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/scenarios.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/split.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/tools.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/trap.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/__init__.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/chat_agent.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/domain.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/injector_prompt.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/loader.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/openai_compat_agent.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/oracle.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/tools.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/world.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/__init__.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/clean.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/domain.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/entities.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/injector_prompt.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/rule_based_agent.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/scenarios.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/tools.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/trap.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/feedback.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/generic_world.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/injection.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/playbook.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/priority.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/quickstart.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/reachability.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/report.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/runner.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/savings.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/scenario.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/scenarios/__init__.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/scenarios/clean.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/scenarios/decoy.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/scenarios/registry.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/scenarios/split.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/scenarios/trap.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/termui.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/tools.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/trajectory.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/triage.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/world.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe_testing.egg-info/SOURCES.txt +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe_testing.egg-info/dependency_links.txt +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe_testing.egg-info/entry_points.txt +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe_testing.egg-info/requires.txt +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe_testing.egg-info/top_level.txt +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/setup.cfg +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_access_control_agent.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_access_control_domain.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_access_control_rule_based_agent.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_access_control_scenarios.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_access_control_tools.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_agreement.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_browser_agent.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_classifier.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_cli.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_complex_access_control_agent.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_diff.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_domain.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_failures.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_feedback.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_generic_world.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_injection.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_injection_mode.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_injector.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_package_api.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_playbook.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_priority.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_quickstart.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_reachability.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_report.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_rule_based_agent.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_runner.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_runner_timeouts.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_savings.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_scenarios.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_target_agent.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_tau_retail.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_termui.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_tools.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_trajectory.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_triage.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_validate_scenarios.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_web_browsing_domain.py +0 -0
- {agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_world.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentprobe-testing
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.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
|
|
|
@@ -61,7 +61,7 @@ from agentprobe.scenario import CommitPattern, FactPattern, GoalSpec, Scenario
|
|
|
61
61
|
from agentprobe.scenarios.registry import ALL_SCENARIOS as TICKET_SCENARIOS
|
|
62
62
|
from agentprobe.scenarios.registry import BY_ID as TICKET_SCENARIOS_BY_ID
|
|
63
63
|
|
|
64
|
-
__version__ = "1.5.
|
|
64
|
+
__version__ = "1.5.1"
|
|
65
65
|
|
|
66
66
|
__all__ = [
|
|
67
67
|
"FixedFaultInjector",
|
|
@@ -16,6 +16,8 @@ from agentprobe.llm import cacheable_system, create_deterministic, response_cost
|
|
|
16
16
|
|
|
17
17
|
import anthropic
|
|
18
18
|
|
|
19
|
+
|
|
20
|
+
from agentprobe.llm import make_client
|
|
19
21
|
from agentprobe.agents.base import Agent, AgentAction
|
|
20
22
|
|
|
21
23
|
SYSTEM_PROMPT = """You are a customer support agent. You have read tools \
|
|
@@ -44,7 +46,7 @@ class TargetAgent(Agent):
|
|
|
44
46
|
max_tokens: int = 1024,
|
|
45
47
|
system_prompt: Optional[str] = None,
|
|
46
48
|
):
|
|
47
|
-
self._client =
|
|
49
|
+
self._client = make_client()
|
|
48
50
|
self._model = model
|
|
49
51
|
self._max_tokens = max_tokens
|
|
50
52
|
self._system_prompt = system_prompt if system_prompt is not None else SYSTEM_PROMPT
|
|
@@ -11,6 +11,8 @@ from enum import Enum
|
|
|
11
11
|
|
|
12
12
|
import anthropic
|
|
13
13
|
|
|
14
|
+
|
|
15
|
+
from agentprobe.llm import make_client
|
|
14
16
|
from agentprobe.injection import AppliedInjection
|
|
15
17
|
from agentprobe.llm import cacheable_system, create_deterministic, response_cost_usd
|
|
16
18
|
from agentprobe.trajectory import Step
|
|
@@ -126,7 +128,7 @@ def classify_response(
|
|
|
126
128
|
) -> ClassifiedResponse:
|
|
127
129
|
"""`steps` is the full trajectory, not pre-filtered to post-injection
|
|
128
130
|
steps -- see _render_classification_prompt for why."""
|
|
129
|
-
client =
|
|
131
|
+
client = make_client()
|
|
130
132
|
response = create_deterministic(
|
|
131
133
|
client,
|
|
132
134
|
model=model,
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/agent.py
RENAMED
|
@@ -15,6 +15,8 @@ from agentprobe.llm import cacheable_system, create_deterministic, response_cost
|
|
|
15
15
|
|
|
16
16
|
import anthropic
|
|
17
17
|
|
|
18
|
+
|
|
19
|
+
from agentprobe.llm import make_client
|
|
18
20
|
from agentprobe.agents.base import Agent, AgentAction
|
|
19
21
|
|
|
20
22
|
SYSTEM_PROMPT = """You are an IT access-control agent. You have read tools \
|
|
@@ -29,7 +31,7 @@ reply with a short final summary of what you did, in plain text."""
|
|
|
29
31
|
|
|
30
32
|
class AccessControlAgent(Agent):
|
|
31
33
|
def __init__(self, model: str = "claude-haiku-4-5-20251001", max_tokens: int = 1024):
|
|
32
|
-
self._client =
|
|
34
|
+
self._client = make_client()
|
|
33
35
|
self._model = model
|
|
34
36
|
self._max_tokens = max_tokens
|
|
35
37
|
self._messages: list[dict[str, Any]] = []
|
|
@@ -17,6 +17,8 @@ from agentprobe.llm import cacheable_system, create_deterministic, response_cost
|
|
|
17
17
|
|
|
18
18
|
import anthropic
|
|
19
19
|
|
|
20
|
+
|
|
21
|
+
from agentprobe.llm import make_client
|
|
20
22
|
from agentprobe.agents.base import Agent, AgentAction
|
|
21
23
|
|
|
22
24
|
COMPLEX_SYSTEM_PROMPT = """You are a senior IT access-control specialist handling access requests \
|
|
@@ -62,7 +64,7 @@ summary of what you decided and why."""
|
|
|
62
64
|
|
|
63
65
|
class ComplexAccessControlAgent(Agent):
|
|
64
66
|
def __init__(self, model: str = "claude-haiku-4-5-20251001", max_tokens: int = 1536):
|
|
65
|
-
self._client =
|
|
67
|
+
self._client = make_client()
|
|
66
68
|
self._model = model
|
|
67
69
|
self._max_tokens = max_tokens
|
|
68
70
|
self._messages: list[dict[str, Any]] = []
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/user_sim.py
RENAMED
|
@@ -41,10 +41,11 @@ class LLMUser:
|
|
|
41
41
|
def _anthropic_complete(self, system: str, messages: list) -> tuple[str, float]:
|
|
42
42
|
import anthropic
|
|
43
43
|
|
|
44
|
+
from agentprobe.llm import make_client
|
|
44
45
|
from agentprobe.llm import response_cost_usd
|
|
45
46
|
|
|
46
47
|
if self._client is None:
|
|
47
|
-
self._client =
|
|
48
|
+
self._client = make_client()
|
|
48
49
|
r = self._client.messages.create(model=self.model, max_tokens=512, system=system, messages=messages)
|
|
49
50
|
return "".join(b.text for b in r.content if b.type == "text"), response_cost_usd(self.model, r)
|
|
50
51
|
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/agent.py
RENAMED
|
@@ -17,6 +17,8 @@ from agentprobe.llm import cacheable_system, create_deterministic, response_cost
|
|
|
17
17
|
|
|
18
18
|
import anthropic
|
|
19
19
|
|
|
20
|
+
|
|
21
|
+
from agentprobe.llm import make_client
|
|
20
22
|
from agentprobe.agents.base import Agent, AgentAction
|
|
21
23
|
|
|
22
24
|
SYSTEM_PROMPT = """You are a customer-support agent working the store's own web \
|
|
@@ -34,7 +36,7 @@ with a short final summary of what you did, in plain text."""
|
|
|
34
36
|
|
|
35
37
|
class WebBrowsingAgent(Agent):
|
|
36
38
|
def __init__(self, model: str = "claude-haiku-4-5-20251001", max_tokens: int = 1024):
|
|
37
|
-
self._client =
|
|
39
|
+
self._client = make_client()
|
|
38
40
|
self._model = model
|
|
39
41
|
self._max_tokens = max_tokens
|
|
40
42
|
self._messages: list[dict[str, Any]] = []
|
|
@@ -12,6 +12,8 @@ from typing import TYPE_CHECKING, Optional
|
|
|
12
12
|
|
|
13
13
|
import anthropic
|
|
14
14
|
|
|
15
|
+
|
|
16
|
+
from agentprobe.llm import make_client
|
|
15
17
|
from agentprobe.injection import (
|
|
16
18
|
AppliedInjection,
|
|
17
19
|
ArmedInjection,
|
|
@@ -856,7 +858,7 @@ class ModelInjector(Injector):
|
|
|
856
858
|
example."""
|
|
857
859
|
if policy not in KIND_POLICIES:
|
|
858
860
|
raise ValueError(f"unknown kind policy {policy!r}, must be one of {KIND_POLICIES}")
|
|
859
|
-
self._client =
|
|
861
|
+
self._client = make_client()
|
|
860
862
|
self._model = model
|
|
861
863
|
self._kind_order = list(kind_order) if kind_order is not None else list(_DEFAULT_KIND_ORDER)
|
|
862
864
|
self._policy = policy
|
|
@@ -15,6 +15,7 @@ from __future__ import annotations
|
|
|
15
15
|
|
|
16
16
|
from typing import Any
|
|
17
17
|
|
|
18
|
+
import anthropic
|
|
18
19
|
import anthropic._streaming as _anthropic_streaming
|
|
19
20
|
|
|
20
21
|
_TEMPERATURE_SUPPORTED: dict[str, bool] = {}
|
|
@@ -110,6 +111,37 @@ def create_deterministic(client: Any, **kwargs: Any) -> Any:
|
|
|
110
111
|
raise
|
|
111
112
|
|
|
112
113
|
|
|
114
|
+
# ---- per-request API key ------------------------------------------------------
|
|
115
|
+
#
|
|
116
|
+
# A hosted runner serves many customers from one process, so a customer's Anthropic key must
|
|
117
|
+
# not be put in the process-wide ANTHROPIC_API_KEY variable: two overlapping requests would
|
|
118
|
+
# overwrite each other's key and one customer's usage could be billed to another. The key is
|
|
119
|
+
# held in a context variable instead, which is isolated per request/task.
|
|
120
|
+
|
|
121
|
+
import contextlib as _contextlib
|
|
122
|
+
import contextvars as _contextvars
|
|
123
|
+
|
|
124
|
+
_api_key_var: "_contextvars.ContextVar[Any]" = _contextvars.ContextVar("agentprobe_api_key", default=None)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@_contextlib.contextmanager
|
|
128
|
+
def api_key_scope(api_key: str):
|
|
129
|
+
"""Everything inside the `with` block (in this task/thread context) that builds an Anthropic
|
|
130
|
+
client uses `api_key`; other concurrent requests are unaffected."""
|
|
131
|
+
token = _api_key_var.set(api_key)
|
|
132
|
+
try:
|
|
133
|
+
yield
|
|
134
|
+
finally:
|
|
135
|
+
_api_key_var.reset(token)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def make_client() -> Any:
|
|
139
|
+
"""The Anthropic client every agentprobe component uses: the key from api_key_scope() if one
|
|
140
|
+
is active, otherwise the usual ANTHROPIC_API_KEY from the environment."""
|
|
141
|
+
key = _api_key_var.get()
|
|
142
|
+
return anthropic.Anthropic(api_key=key) if key else anthropic.Anthropic()
|
|
143
|
+
|
|
144
|
+
|
|
113
145
|
_PRICE_PER_TOKEN: dict[str, tuple[float, float]] = {
|
|
114
146
|
# (input, output) USD per token, from the pricing table this was
|
|
115
147
|
# checked against (2026-08). Keyed by the exact model ids this
|
|
@@ -43,26 +43,35 @@ class FetchedDomain:
|
|
|
43
43
|
review_url: Optional[str] = None
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
def _default_fetcher(url: str) -> dict:
|
|
46
|
+
def _default_fetcher(url: str, api_key: Optional[str] = None) -> dict:
|
|
47
47
|
# Cloudflare's default bot protection blocks urllib's stock User-Agent
|
|
48
48
|
# ("Python-urllib/3.x") outright -- a real client identifier, same idea
|
|
49
49
|
# as any other SDK sending its own name/version, gets through.
|
|
50
|
-
|
|
50
|
+
headers = {"User-Agent": "agentprobe-client/0.5.0"}
|
|
51
|
+
if api_key:
|
|
52
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
53
|
+
request = urllib.request.Request(url, headers=headers)
|
|
51
54
|
with urllib.request.urlopen(request, timeout=10) as resp: # noqa: S310 -- https enforced by fetch_domain below
|
|
52
55
|
return json.loads(resp.read().decode("utf-8"))
|
|
53
56
|
|
|
54
57
|
|
|
55
|
-
def fetch_domain(
|
|
58
|
+
def fetch_domain(
|
|
59
|
+
key: str, base_url: str = DEFAULT_BASE_URL, fetcher: Optional[Fetcher] = None, api_key: Optional[str] = None
|
|
60
|
+
) -> FetchedDomain:
|
|
56
61
|
"""Fetch and load the Domain stored under `key`. Pass `fetcher` (a
|
|
57
62
|
callable url -> dict) in tests instead of hitting the real network --
|
|
58
63
|
see tests/test_registry.py.
|
|
64
|
+
|
|
65
|
+
A domain generated while signed in (with your API key) belongs to your account and can
|
|
66
|
+
only be fetched with that account's `api_key`; one generated anonymously stays fetchable
|
|
67
|
+
by anyone who has its key.
|
|
59
68
|
"""
|
|
60
69
|
if not (base_url.startswith("https://") or base_url.startswith("http://localhost")):
|
|
61
70
|
raise ValueError("fetch_domain requires an https:// base_url (http://localhost is allowed for local testing)")
|
|
62
71
|
if not key:
|
|
63
72
|
raise ValueError("fetch_domain requires a non-empty key")
|
|
64
73
|
|
|
65
|
-
fetcher = fetcher if fetcher is not None else _default_fetcher
|
|
74
|
+
fetcher = fetcher if fetcher is not None else (lambda url: _default_fetcher(url, api_key))
|
|
66
75
|
payload = fetcher(f"{base_url}/v1/domains/{key}")
|
|
67
76
|
|
|
68
77
|
if "source" not in payload:
|
|
@@ -31,6 +31,8 @@ from typing import Any
|
|
|
31
31
|
|
|
32
32
|
import anthropic
|
|
33
33
|
|
|
34
|
+
|
|
35
|
+
from agentprobe.llm import make_client
|
|
34
36
|
from agentprobe.domain import Domain, TICKET_DOMAIN
|
|
35
37
|
from agentprobe.llm import create_deterministic
|
|
36
38
|
from agentprobe.reachability import reachable
|
|
@@ -392,7 +394,7 @@ judgment call. Call verify_value_mismatch exactly once."""
|
|
|
392
394
|
def _verify_value_mismatch_with_model(
|
|
393
395
|
order_amount: float, required_amount: float, policies: dict[str, str], model: str = "claude-sonnet-5"
|
|
394
396
|
) -> str:
|
|
395
|
-
client =
|
|
397
|
+
client = make_client()
|
|
396
398
|
policy_text = "\n".join(f"{name}: {text}" for name, text in policies.items()) or "(no policies in this scenario)"
|
|
397
399
|
prompt = (
|
|
398
400
|
f"Order amount: {order_amount}\n"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentprobe-testing
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.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
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agentprobe-testing"
|
|
3
|
-
version = "1.5.
|
|
3
|
+
version = "1.5.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"]
|
|
@@ -241,3 +241,47 @@ def test_cache_is_per_model():
|
|
|
241
241
|
accepting = FakeClient(reject_temperature=False)
|
|
242
242
|
create_deterministic(accepting, model="claude-haiku-4-5-20251001", max_tokens=10)
|
|
243
243
|
assert accepting.messages.calls[0]["extra_body"] == {"temperature": 0}
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def test_api_key_scope_gives_each_context_its_own_key_and_restores_afterwards(monkeypatch):
|
|
247
|
+
import threading
|
|
248
|
+
|
|
249
|
+
import anthropic
|
|
250
|
+
|
|
251
|
+
from agentprobe.llm import api_key_scope, make_client
|
|
252
|
+
|
|
253
|
+
seen = []
|
|
254
|
+
|
|
255
|
+
class FakeClient:
|
|
256
|
+
def __init__(self, api_key=None):
|
|
257
|
+
self.api_key = api_key
|
|
258
|
+
|
|
259
|
+
monkeypatch.setattr(anthropic, "Anthropic", FakeClient)
|
|
260
|
+
assert make_client().api_key is None # no scope: falls back to the environment's key
|
|
261
|
+
with api_key_scope("sk-a"):
|
|
262
|
+
assert make_client().api_key == "sk-a"
|
|
263
|
+
# another thread/context never sees this request's key
|
|
264
|
+
t = threading.Thread(target=lambda: seen.append(make_client().api_key))
|
|
265
|
+
t.start()
|
|
266
|
+
t.join()
|
|
267
|
+
with api_key_scope("sk-b"):
|
|
268
|
+
assert make_client().api_key == "sk-b"
|
|
269
|
+
assert make_client().api_key == "sk-a"
|
|
270
|
+
assert make_client().api_key is None
|
|
271
|
+
assert seen == [None]
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def test_overlapping_async_requests_keep_their_own_keys():
|
|
275
|
+
import asyncio
|
|
276
|
+
|
|
277
|
+
from agentprobe.llm import _api_key_var, api_key_scope
|
|
278
|
+
|
|
279
|
+
async def request(key, delay):
|
|
280
|
+
with api_key_scope(key):
|
|
281
|
+
await asyncio.sleep(delay) # another request runs while this one waits
|
|
282
|
+
return _api_key_var.get()
|
|
283
|
+
|
|
284
|
+
async def main():
|
|
285
|
+
return await asyncio.gather(request("sk-a", 0.02), request("sk-b", 0.0), request("sk-c", 0.01))
|
|
286
|
+
|
|
287
|
+
assert asyncio.run(main()) == ["sk-a", "sk-b", "sk-c"]
|
|
@@ -417,3 +417,18 @@ def test_replay_run_round_trip_replays_the_exact_recorded_sequence():
|
|
|
417
417
|
|
|
418
418
|
assert replayed.injector_model == "replay:42"
|
|
419
419
|
assert replayed._compute()["total"] == 1 # the same recorded injection fired again
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def test_fetch_domain_sends_the_api_key_when_given(monkeypatch):
|
|
423
|
+
import agentprobe.registry as registry
|
|
424
|
+
|
|
425
|
+
seen = {}
|
|
426
|
+
|
|
427
|
+
def fake_default(url, api_key=None):
|
|
428
|
+
seen["url"], seen["key"] = url, api_key
|
|
429
|
+
return {"source": "raise SystemExit", "review_url": None}
|
|
430
|
+
|
|
431
|
+
monkeypatch.setattr(registry, "_default_fetcher", fake_default)
|
|
432
|
+
with pytest.raises(BaseException): # the stub source isn't a real domain; only the request matters
|
|
433
|
+
registry.fetch_domain("k_abc", base_url="https://sampact.com", api_key="ap_secret")
|
|
434
|
+
assert seen == {"url": "https://sampact.com/v1/domains/k_abc", "key": "ap_secret"}
|
|
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.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/__init__.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/clean.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/decoy.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/domain.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/entities.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/scenarios.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/split.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/tools.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/access_control/trap.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/__init__.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/chat_agent.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/domain.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/loader.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/oracle.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/tools.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/tau_retail/world.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/__init__.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/clean.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/domain.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/entities.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/scenarios.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe/domains/web_browsing/tools.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.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
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe_testing.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe_testing.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe_testing.egg-info/requires.txt
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/agentprobe_testing.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.1}/tests/test_access_control_rule_based_agent.py
RENAMED
|
File without changes
|
{agentprobe_testing-1.5.0 → agentprobe_testing-1.5.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.5.0 → agentprobe_testing-1.5.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|