entropy-ai 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. entropy_ai-0.1.0/PKG-INFO +34 -0
  2. entropy_ai-0.1.0/README.md +125 -0
  3. entropy_ai-0.1.0/entropy/__init__.py +52 -0
  4. entropy_ai-0.1.0/entropy/benchmarks/__init__.py +10 -0
  5. entropy_ai-0.1.0/entropy/benchmarks/bench.py +59 -0
  6. entropy_ai-0.1.0/entropy/chaos/__init__.py +7 -0
  7. entropy_ai-0.1.0/entropy/chaos/faults.py +101 -0
  8. entropy_ai-0.1.0/entropy/chaos/runner.py +40 -0
  9. entropy_ai-0.1.0/entropy/cli/__init__.py +6 -0
  10. entropy_ai-0.1.0/entropy/cli/__main__.py +6 -0
  11. entropy_ai-0.1.0/entropy/cli/main.py +332 -0
  12. entropy_ai-0.1.0/entropy/core/__init__.py +20 -0
  13. entropy_ai-0.1.0/entropy/core/exporters.py +104 -0
  14. entropy_ai-0.1.0/entropy/core/models.py +122 -0
  15. entropy_ai-0.1.0/entropy/core/suite.py +141 -0
  16. entropy_ai-0.1.0/entropy/core/tracing.py +86 -0
  17. entropy_ai-0.1.0/entropy/dashboard/__init__.py +6 -0
  18. entropy_ai-0.1.0/entropy/dashboard/app.py +123 -0
  19. entropy_ai-0.1.0/entropy/datasets/__init__.py +12 -0
  20. entropy_ai-0.1.0/entropy/datasets/cases.py +83 -0
  21. entropy_ai-0.1.0/entropy/datasets/loaders.py +66 -0
  22. entropy_ai-0.1.0/entropy/integrations/__init__.py +17 -0
  23. entropy_ai-0.1.0/entropy/integrations/autogen.py +31 -0
  24. entropy_ai-0.1.0/entropy/integrations/base.py +49 -0
  25. entropy_ai-0.1.0/entropy/integrations/crewai.py +27 -0
  26. entropy_ai-0.1.0/entropy/integrations/custom.py +28 -0
  27. entropy_ai-0.1.0/entropy/integrations/google_adk.py +32 -0
  28. entropy_ai-0.1.0/entropy/integrations/langchain.py +39 -0
  29. entropy_ai-0.1.0/entropy/integrations/langgraph.py +28 -0
  30. entropy_ai-0.1.0/entropy/integrations/mcp.py +30 -0
  31. entropy_ai-0.1.0/entropy/integrations/openai.py +28 -0
  32. entropy_ai-0.1.0/entropy/integrations/pydanticai.py +27 -0
  33. entropy_ai-0.1.0/entropy/integrations/wire.py +49 -0
  34. entropy_ai-0.1.0/entropy/metrics/__init__.py +25 -0
  35. entropy_ai-0.1.0/entropy/metrics/behavioral.py +77 -0
  36. entropy_ai-0.1.0/entropy/metrics/drift.py +61 -0
  37. entropy_ai-0.1.0/entropy/metrics/entropy_.py +84 -0
  38. entropy_ai-0.1.0/entropy/metrics/execution.py +90 -0
  39. entropy_ai-0.1.0/entropy/metrics/layers.py +34 -0
  40. entropy_ai-0.1.0/entropy/metrics/memory.py +57 -0
  41. entropy_ai-0.1.0/entropy/metrics/multiagent.py +52 -0
  42. entropy_ai-0.1.0/entropy/metrics/reasoning.py +59 -0
  43. entropy_ai-0.1.0/entropy/metrics/registry.py +186 -0
  44. entropy_ai-0.1.0/entropy/metrics/safety.py +64 -0
  45. entropy_ai-0.1.0/entropy/metrics/statistical.py +85 -0
  46. entropy_ai-0.1.0/entropy/metrics/tool.py +85 -0
  47. entropy_ai-0.1.0/entropy/observability/__init__.py +6 -0
  48. entropy_ai-0.1.0/entropy/observability/watch.py +102 -0
  49. entropy_ai-0.1.0/entropy/plugins/__init__.py +8 -0
  50. entropy_ai-0.1.0/entropy/plugins/manager.py +51 -0
  51. entropy_ai-0.1.0/entropy/py.typed +0 -0
  52. entropy_ai-0.1.0/entropy/regression/__init__.py +6 -0
  53. entropy_ai-0.1.0/entropy/regression/test.py +42 -0
  54. entropy_ai-0.1.0/entropy/reports/__init__.py +8 -0
  55. entropy_ai-0.1.0/entropy/reports/exporters.py +88 -0
  56. entropy_ai-0.1.0/entropy/simulation/__init__.py +13 -0
  57. entropy_ai-0.1.0/entropy/simulation/adversarial.py +42 -0
  58. entropy_ai-0.1.0/entropy/simulation/environment.py +52 -0
  59. entropy_ai-0.1.0/entropy/simulation/user.py +72 -0
  60. entropy_ai-0.1.0/entropy_ai.egg-info/PKG-INFO +34 -0
  61. entropy_ai-0.1.0/entropy_ai.egg-info/SOURCES.txt +71 -0
  62. entropy_ai-0.1.0/entropy_ai.egg-info/dependency_links.txt +1 -0
  63. entropy_ai-0.1.0/entropy_ai.egg-info/entry_points.txt +2 -0
  64. entropy_ai-0.1.0/entropy_ai.egg-info/requires.txt +39 -0
  65. entropy_ai-0.1.0/entropy_ai.egg-info/top_level.txt +1 -0
  66. entropy_ai-0.1.0/pyproject.toml +38 -0
  67. entropy_ai-0.1.0/setup.cfg +4 -0
  68. entropy_ai-0.1.0/tests/test_basic.py +71 -0
  69. entropy_ai-0.1.0/tests/test_langchain_adapter.py +30 -0
  70. entropy_ai-0.1.0/tests/test_phase1.py +130 -0
  71. entropy_ai-0.1.0/tests/test_phase3.py +100 -0
  72. entropy_ai-0.1.0/tests/test_phase4.py +92 -0
  73. entropy_ai-0.1.0/tests/test_vision_metrics.py +48 -0
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: entropy-ai
3
+ Version: 0.1.0
4
+ Summary: Behavioral evaluation framework for non-deterministic AI agents
5
+ Author-email: Satyam Singh <satyamsingh7734@gmail.com>
6
+ Requires-Python: >=3.9
7
+ Requires-Dist: pandas
8
+ Requires-Dist: matplotlib
9
+ Requires-Dist: jinja2
10
+ Requires-Dist: rich
11
+ Requires-Dist: pyyaml
12
+ Provides-Extra: pdf
13
+ Requires-Dist: reportlab; extra == "pdf"
14
+ Provides-Extra: jupyter
15
+ Requires-Dist: nbformat; extra == "jupyter"
16
+ Provides-Extra: hf
17
+ Requires-Dist: datasets; extra == "hf"
18
+ Provides-Extra: langchain
19
+ Requires-Dist: langchain; extra == "langchain"
20
+ Provides-Extra: langgraph
21
+ Requires-Dist: langgraph; extra == "langgraph"
22
+ Provides-Extra: openai
23
+ Requires-Dist: openai; extra == "openai"
24
+ Requires-Dist: openai-agents; extra == "openai"
25
+ Provides-Extra: crewai
26
+ Requires-Dist: crewai; extra == "crewai"
27
+ Provides-Extra: pydanticai
28
+ Requires-Dist: pydantic-ai; extra == "pydanticai"
29
+ Provides-Extra: autogen
30
+ Requires-Dist: autogen; extra == "autogen"
31
+ Provides-Extra: google-adk
32
+ Requires-Dist: google-adk; extra == "google-adk"
33
+ Provides-Extra: mcp
34
+ Requires-Dist: mcp; extra == "mcp"
@@ -0,0 +1,125 @@
1
+ # EntroPy
2
+
3
+ Behavioral evaluation framework for non-deterministic AI agents.
4
+
5
+ Measures what other frameworks miss: **uncertainty, drift, stochastic
6
+ behavior, and emergent failures** — via Monte Carlo evaluation over repeated
7
+ runs and entropy-based metrics. Framework-agnostic, local-first, zero vendor
8
+ lock-in.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pip install -e . # core (pandas, matplotlib, jinja2, rich, pyyaml)
14
+ pip install "entropy-ai[langchain]" # optional: a framework adapter
15
+ pip install "entropy-ai[pdf,jupyter]" # optional: PDF / Jupyter reports
16
+ ```
17
+
18
+ ## Quick start
19
+
20
+ ```python
21
+ from entropy import Suite, Dataset, Case
22
+
23
+ def my_agent(inp): ...
24
+ # return an output, an AgentRun, or a dict with events
25
+
26
+ dataset = Dataset([Case(input="q1", expected="correct answer")])
27
+ results = Suite(seed=42).run(my_agent, dataset, trials=100)
28
+ print(results) # success_rate, behavioral_entropy, drift_score, ...
29
+ ```
30
+
31
+ The canonical output (spec §9 / §21) includes: `success_rate`, `reliability`,
32
+ `variance`, `robustness`, `entropy`, `confidence_interval`, `behavioral_entropy`,
33
+ `behavioral_variance`, `trajectory_entropy`, `trajectory_diversity`, `tool_reliability`,
34
+ `drift_score`, `goal_stability`, `recovery_score`, `emergent_behavior`, and more.
35
+ 56 metrics total; `entropy.default_metrics()` lists the canonical set.
36
+
37
+ ## Tracing
38
+
39
+ ```python
40
+ from entropy import trace, event, instrument
41
+ with trace() as t:
42
+ event("action", "tool:search")
43
+ # or auto-wrap any agent:
44
+ wrapped = instrument(my_agent) # returns an AgentRun per call
45
+ ```
46
+
47
+ ## Custom metrics (plugin system)
48
+
49
+ ```python
50
+ from entropy import metric
51
+ @metric("my_metric")
52
+ class MyMetric:
53
+ def compute(self, batch): ... # batch has outputs/events/successes/costs/...
54
+ ```
55
+
56
+ ## Simulation, Chaos, Observability
57
+
58
+ ```python
59
+ from entropy import AdversarialSimulator, ChaosRunner, Watcher
60
+ adv = AdversarialSimulator(seed=0)
61
+ for v in adv.iterate("book a flight"):
62
+ print(v) # typo / injection / distraction / leak variants
63
+
64
+ ChaosRunner(seed=0).run(agent, dataset, ["api_fail", "timeout"]) # fault injection
65
+ Watcher(agent, dataset, trials=20).watch(rounds=10) # live drift + anomalies
66
+ ```
67
+
68
+ ## Framework adapters (lazy)
69
+
70
+ ```python
71
+ from entropy import from_langchain, from_langgraph, from_openai, from_custom
72
+ runnable = from_langchain(my_langchain_agent) # maps framework runs -> AgentRun
73
+ ```
74
+
75
+ All 9 adapters (LangChain, LangGraph, OpenAI Agents, CrewAI, PydanticAI,
76
+ AutoGen, Google ADK, MCP, custom) import their framework **lazily**, so
77
+ `import entropy` works with none of them installed.
78
+
79
+ ## CLI
80
+
81
+ ```bash
82
+ entropy init # scaffold a project
83
+ entropy run --agent m:a --dataset d.json --trials 100
84
+ entropy test --agent m:a --dataset d.json --baseline base.json [--update]
85
+ entropy benchmark --agents m:a m:b --dataset d.json
86
+ entropy chaos --agent m:a --dataset d.json --faults api_fail,timeout
87
+ entropy simulate --kind adversarial --text "book a flight"
88
+ entropy ui --agent m:a --dataset d.json --serve
89
+ entropy watch --agent m:a --dataset d.json --rounds 10
90
+ entropy report --results r.json --format html
91
+ entropy dataset --path d.json
92
+ entropy doctor # check optional deps
93
+ ```
94
+
95
+ ## Reports
96
+
97
+ ```python
98
+ from entropy import export_report
99
+ export_report(results, "html", "report.html") # also: json, markdown, pdf, jupyter
100
+ ```
101
+
102
+ ## Layout
103
+
104
+ `core/` (models, tracing, exporters, suite) · `metrics/` (registry + all
105
+ categories) · `datasets/` · `simulation/` · `chaos/` · `regression/` ·
106
+ `benchmarks/` · `observability/` · `reports/` · `dashboard/` · `integrations/`
107
+ (adapters) · `cli/` · `plugins/`.
108
+
109
+ Tests: `python -m pytest tests/`.
110
+
111
+ ## Documentation
112
+
113
+ The static documentation site lives in [`docs/`](docs/) — plain
114
+ HTML/CSS/JS with no build step, deployable to GitHub Pages.
115
+
116
+ - Source: `docs/index.html`, `docs/guide/*.html`, `docs/api.html`,
117
+ `docs/examples.html`, plus `docs/assets/` (CSS, JS, logo, search index).
118
+ - Local preview: serve the folder, e.g. `python -m http.server -d docs 8000`
119
+ then open `http://localhost:8000`.
120
+ - Deploy: push to `main`; the
121
+ [`.github/workflows/pages.yml`](.github/workflows/pages.yml) workflow
122
+ uploads `docs/` and publishes it via GitHub Pages. Alternatively, set
123
+ **Settings → Pages → Build and deployment → Source** to *Deploy from a
124
+ branch* and choose the `main` branch `/docs` folder.
125
+
@@ -0,0 +1,52 @@
1
+ """EntroPy — behavioral evaluation for non-deterministic AI agents.
2
+
3
+ Measure uncertainty, drift, stochastic behavior, and emergent failures via
4
+ Monte Carlo evaluation and entropy-based metrics.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from .core import (
9
+ AgentRun, Event, Trace, Node, Edge,
10
+ ActionEvent, ReasoningEvent, ToolCallEvent, ObservationEvent,
11
+ MemoryReadEvent, MemoryWriteEvent, ErrorEvent, StateTransitionEvent,
12
+ instrument, trace, event, current_trace,
13
+ Suite, assert_stable,
14
+ )
15
+ from .metrics import metric, Metric, Batch, default_metrics, layers
16
+ from .datasets import (
17
+ Dataset, Case, GoldenCase, Scenario, BehaviorCase, FailureCase, AdversarialCase,
18
+ )
19
+ from .simulation import (
20
+ UserSimulator, ScriptedUserSimulator, RandomUserSimulator, LLMUserSimulator,
21
+ Environment, StatefulEnv, GridWorld, AdversarialSimulator,
22
+ )
23
+ from .chaos import ChaosRunner, inject as chaos_inject, available as chaos_available
24
+ from .observability import Watcher, render_metric_heatmap
25
+ from .dashboard import build_dashboard, serve as serve_dashboard
26
+ from .reports import export as export_report
27
+ from .integrations import (
28
+ adapter, find_adapter, list_adapters,
29
+ from_langchain, from_langgraph, from_openai, from_crewai,
30
+ from_pydanticai, from_autogen, from_google_adk, from_mcp, from_custom,
31
+ )
32
+ from .plugins import exporter, discover
33
+
34
+ __all__ = [
35
+ "Suite", "Dataset", "Case", "GoldenCase", "Scenario", "BehaviorCase",
36
+ "FailureCase", "AdversarialCase",
37
+ "UserSimulator", "ScriptedUserSimulator", "RandomUserSimulator", "LLMUserSimulator",
38
+ "Environment", "StatefulEnv", "GridWorld", "AdversarialSimulator",
39
+ "ChaosRunner", "chaos_inject", "chaos_available",
40
+ "Watcher", "render_metric_heatmap", "build_dashboard", "serve_dashboard",
41
+ "export_report",
42
+ "AgentRun", "Event", "Trace", "Node", "Edge",
43
+ "ActionEvent", "ReasoningEvent", "ToolCallEvent", "ObservationEvent",
44
+ "MemoryReadEvent", "MemoryWriteEvent", "ErrorEvent", "StateTransitionEvent",
45
+ "instrument", "trace", "event", "current_trace",
46
+ "metric", "Batch", "default_metrics", "layers",
47
+ "adapter", "find_adapter", "list_adapters",
48
+ "from_langchain", "from_langgraph", "from_openai", "from_crewai",
49
+ "from_pydanticai", "from_autogen", "from_google_adk", "from_mcp", "from_custom",
50
+ "exporter", "discover",
51
+ "assert_stable",
52
+ ]
@@ -0,0 +1,10 @@
1
+ """Benchmarks package."""
2
+ from __future__ import annotations
3
+
4
+ from .bench import (
5
+ load_ref, load_dataset, benchmark_variants, benchmark_refs, to_markdown,
6
+ )
7
+
8
+ __all__ = [
9
+ "load_ref", "load_dataset", "benchmark_variants", "benchmark_refs", "to_markdown",
10
+ ]
@@ -0,0 +1,59 @@
1
+ """Benchmarking (spec §14): compare models/prompts/frameworks/agents/versions."""
2
+ from __future__ import annotations
3
+
4
+ import importlib
5
+ from typing import Any, Callable, Dict, List, Tuple
6
+
7
+ from ..core.suite import Suite
8
+ from ..datasets.loaders import Dataset
9
+
10
+
11
+ def load_ref(ref: str):
12
+ """Load ``module:attr`` (agent/function) or ``module:attr`` callable."""
13
+ mod_name, attr = ref.split(":")
14
+ mod = importlib.import_module(mod_name)
15
+ return getattr(mod, attr)
16
+
17
+
18
+ def load_dataset(path: str) -> Dataset:
19
+ p = path.lower()
20
+ if p.endswith(".jsonl"):
21
+ return Dataset.from_jsonl(path)
22
+ if p.endswith((".yaml", ".yml")):
23
+ return Dataset.from_yaml(path)
24
+ if p.endswith(".csv"):
25
+ return Dataset.from_csv(path)
26
+ return Dataset.from_json(path)
27
+
28
+
29
+ def benchmark_variants(
30
+ variants: List[Tuple[str, Callable]], dataset, trials: int = 100, seed: int = 0,
31
+ ) -> List[Tuple[str, dict]]:
32
+ """Run the suite for each (name, agent) variant and return rows."""
33
+ rows = []
34
+ for name, agent in variants:
35
+ res = Suite(seed=seed).run(agent, dataset, trials=trials)
36
+ rows.append((name, res))
37
+ return rows
38
+
39
+
40
+ def benchmark_refs(
41
+ refs: List[str], dataset_path: str, trials: int = 100, seed: int = 0,
42
+ ) -> List[Tuple[str, dict]]:
43
+ ds = load_dataset(dataset_path)
44
+ variants = [(r, load_ref(r)) for r in refs]
45
+ return benchmark_variants(variants, ds, trials=trials, seed=seed)
46
+
47
+
48
+ def to_markdown(rows: List[Tuple[str, dict]], metrics: List[str] | None = None) -> str:
49
+ if not rows:
50
+ return "(no results)"
51
+ if metrics is None:
52
+ metrics = [k for k, v in rows[0][1].items() if isinstance(v, (int, float))]
53
+ header = ["variant"] + metrics
54
+ lines = ["| " + " | ".join(header) + " |",
55
+ "| " + " | ".join("---" for _ in header) + " |"]
56
+ for name, res in rows:
57
+ cells = [name] + [f"{res.get(m, 0):.4f}" for m in metrics]
58
+ lines.append("| " + " | ".join(cells) + " |")
59
+ return "\n".join(lines)
@@ -0,0 +1,7 @@
1
+ """Chaos package (spec §10)."""
2
+ from __future__ import annotations
3
+
4
+ from .faults import FAULTS, inject, available
5
+ from .runner import ChaosRunner
6
+
7
+ __all__ = ["FAULTS", "inject", "available", "ChaosRunner"]
@@ -0,0 +1,101 @@
1
+ """Fault injection (spec §10): 8 fault types as agent wrappers."""
2
+ from __future__ import annotations
3
+
4
+ import random
5
+ from typing import Callable, Dict, List
6
+
7
+ from ..core.models import AgentRun, ErrorEvent, ToolCallEvent, MemoryWriteEvent
8
+
9
+
10
+ def _wrap(agent: Callable, fault_fn, p: float, rng: random.Random) -> Callable:
11
+ def wrapped(inp):
12
+ if rng.random() < p:
13
+ return fault_fn(agent, inp)
14
+ return agent(inp)
15
+ wrapped.__name__ = getattr(fault_fn, "__name__", "fault")
16
+ return wrapped
17
+
18
+
19
+ # --- individual faults ----------------------------------------------------
20
+ def api_fail(agent, inp):
21
+ raise RuntimeError("CHAOS: simulated API failure")
22
+
23
+
24
+ def timeout(agent, inp):
25
+ raise TimeoutError("CHAOS: simulated timeout")
26
+
27
+
28
+ def rate_limit(agent, inp):
29
+ raise RuntimeError("CHAOS: 429 rate limited")
30
+
31
+
32
+ def network_delay(agent, inp):
33
+ raise ConnectionError("CHAOS: simulated network delay/reset")
34
+
35
+
36
+ def tool_crash(agent, inp):
37
+ res = agent(inp)
38
+ if isinstance(res, AgentRun):
39
+ events = [ToolCallEvent("__crashed__") if e.type == "tool" else e
40
+ for e in res.events]
41
+ return AgentRun(run_id=res.run_id, input=inp, output=res.output,
42
+ events=events, cost=res.cost,
43
+ metadata={**res.metadata, "error": "tool_crash"})
44
+ return AgentRun(run_id="", input=inp, output=res,
45
+ events=[ToolCallEvent("__crashed__")], metadata={"error": "tool_crash"})
46
+
47
+
48
+ def memory_corrupt(agent, inp):
49
+ res = agent(inp)
50
+ if isinstance(res, AgentRun):
51
+ events = [MemoryWriteEvent(e.name, {**e.data, "polluting": True})
52
+ if e.type == "memory_write" else e for e in res.events]
53
+ return AgentRun(run_id=res.run_id, input=inp, output=res.output,
54
+ events=events, cost=res.cost, metadata=res.metadata)
55
+ return res
56
+
57
+
58
+ def token_limit(agent, inp, limit: int = 16):
59
+ res = agent(inp)
60
+ out = res.output if isinstance(res, AgentRun) else res
61
+ text = out if isinstance(out, str) else str(out)
62
+ if len(text) > limit:
63
+ truncated = text[:limit]
64
+ if isinstance(res, AgentRun):
65
+ return AgentRun(run_id=res.run_id, input=inp, output=truncated,
66
+ events=res.events, cost=res.cost,
67
+ metadata={**res.metadata, "error": "token_limit"})
68
+ return AgentRun(run_id="", input=inp, output=truncated, metadata={"error": "token_limit"})
69
+ return res
70
+
71
+
72
+ def malformed_output(agent, inp):
73
+ res = agent(inp)
74
+ if isinstance(res, AgentRun):
75
+ return AgentRun(run_id=res.run_id, input=inp, output="[[MALFORMED]]",
76
+ events=res.events, cost=res.cost,
77
+ metadata={**res.metadata, "error": "malformed"})
78
+ return AgentRun(run_id="", input=inp, output="[[MALFORMED]]", metadata={"error": "malformed"})
79
+
80
+
81
+ FAULTS: Dict[str, Callable] = {
82
+ "api_fail": api_fail,
83
+ "timeout": timeout,
84
+ "rate_limit": rate_limit,
85
+ "network_delay": network_delay,
86
+ "tool_crash": tool_crash,
87
+ "memory_corrupt": memory_corrupt,
88
+ "token_limit": token_limit,
89
+ "malformed_output": malformed_output,
90
+ }
91
+
92
+
93
+ def inject(agent: Callable, fault: str, p: float = 1.0, seed: int = 0) -> Callable:
94
+ """Wrap ``agent`` so fault ``fault`` fires with probability ``p``."""
95
+ if fault not in FAULTS:
96
+ raise ValueError(f"unknown fault {fault!r}; have {sorted(FAULTS)}")
97
+ return _wrap(agent, FAULTS[fault], p, random.Random(seed))
98
+
99
+
100
+ def available() -> List[str]:
101
+ return list(FAULTS)
@@ -0,0 +1,40 @@
1
+ """Chaos runner (spec §10): run the suite under each fault and compare."""
2
+ from __future__ import annotations
3
+
4
+ import random
5
+ from typing import Callable, Iterable, List
6
+
7
+ from ..core.suite import Suite
8
+ from .faults import FAULTS, inject
9
+
10
+
11
+ class ChaosRunner:
12
+ """Run an agent under baseline and each requested fault."""
13
+
14
+ def __init__(self, seed: int = 0):
15
+ self.seed = seed
16
+
17
+ def run(self, agent: Callable, dataset, faults: List[str] | str,
18
+ trials: int = 50) -> dict:
19
+ if isinstance(faults, str):
20
+ faults = [faults]
21
+ unknown = [f for f in faults if f not in FAULTS]
22
+ if unknown:
23
+ raise ValueError(f"unknown faults: {unknown}")
24
+ rng = random.Random(self.seed)
25
+ baseline = Suite(seed=self.seed).run(agent, dataset, trials=trials)
26
+ out = {"baseline": baseline, "faults": {}}
27
+ for f in faults:
28
+ wrapped = inject(agent, f, p=1.0, seed=self.seed)
29
+ out["faults"][f] = Suite(seed=self.seed).run(wrapped, dataset, trials=trials)
30
+ return out
31
+
32
+ def summary(self, result: dict) -> str:
33
+ lines = [f"{'fault':16} {'success':>9} {'fail':>9} {'drift':>9}"]
34
+ b = result["baseline"]
35
+ lines.append(f"{'(baseline)':16} {b['success_rate']:9.4f} "
36
+ f"{1 - b['success_rate']:9.4f} {b['drift_score']:9.4f}")
37
+ for f, r in result["faults"].items():
38
+ lines.append(f"{f:16} {r['success_rate']:9.4f} "
39
+ f"{1 - r['success_rate']:9.4f} {r['drift_score']:9.4f}")
40
+ return "\n".join(lines)
@@ -0,0 +1,6 @@
1
+ """Entropy CLI package."""
2
+ from __future__ import annotations
3
+
4
+ from .main import build_parser, main
5
+
6
+ __all__ = ["build_parser", "main"]
@@ -0,0 +1,6 @@
1
+ import sys
2
+
3
+ from .main import main
4
+
5
+ if __name__ == "__main__":
6
+ sys.exit(main())