agent-ablation 0.3.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.
- agent_ablation-0.3.0/LICENSE +21 -0
- agent_ablation-0.3.0/PKG-INFO +275 -0
- agent_ablation-0.3.0/README.md +251 -0
- agent_ablation-0.3.0/agent_ablation/__init__.py +76 -0
- agent_ablation-0.3.0/agent_ablation/adapters/__init__.py +16 -0
- agent_ablation-0.3.0/agent_ablation/adapters/autogen.py +73 -0
- agent_ablation-0.3.0/agent_ablation/adapters/crewai.py +111 -0
- agent_ablation-0.3.0/agent_ablation/adapters/langgraph.py +109 -0
- agent_ablation-0.3.0/agent_ablation/adapters/vercel.py +96 -0
- agent_ablation-0.3.0/agent_ablation/core.py +583 -0
- agent_ablation-0.3.0/agent_ablation/models.py +162 -0
- agent_ablation-0.3.0/agent_ablation/reporters/__init__.py +10 -0
- agent_ablation-0.3.0/agent_ablation/reporters/formatters.py +130 -0
- agent_ablation-0.3.0/agent_ablation.egg-info/PKG-INFO +275 -0
- agent_ablation-0.3.0/agent_ablation.egg-info/SOURCES.txt +25 -0
- agent_ablation-0.3.0/agent_ablation.egg-info/dependency_links.txt +1 -0
- agent_ablation-0.3.0/agent_ablation.egg-info/requires.txt +3 -0
- agent_ablation-0.3.0/agent_ablation.egg-info/top_level.txt +1 -0
- agent_ablation-0.3.0/pyproject.toml +45 -0
- agent_ablation-0.3.0/setup.cfg +4 -0
- agent_ablation-0.3.0/setup.py +7 -0
- agent_ablation-0.3.0/tests/test_ablation.py +246 -0
- agent_ablation-0.3.0/tests/test_adapters.py +202 -0
- agent_ablation-0.3.0/tests/test_async_ablation.py +106 -0
- agent_ablation-0.3.0/tests/test_backward_elimination.py +56 -0
- agent_ablation-0.3.0/tests/test_new_adapters_and_reporters.py +117 -0
- agent_ablation-0.3.0/tests/test_roi_and_accuracy.py +86 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ayush Verma
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-ablation
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Leave-one-out ablation testing, backward elimination, and ROI evaluation for multi-agent decision systems — find out which agents' findings actually change the outcome.
|
|
5
|
+
Author-email: Ayush Verma <ayushv3533e@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: multi-agent,ai-agents,ablation,explainability,llm-agents,agent-orchestration,interpretability
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Classifier: Topic :: Software Development :: Testing
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# agent-ablation (Python)
|
|
26
|
+
|
|
27
|
+
[](https://github.com/AyushCipher/agent-ablation/actions/workflows/ci.yml)
|
|
28
|
+
[](https://pypi.org/project/agent-ablation/)
|
|
29
|
+
[](https://pypi.org/project/agent-ablation/)
|
|
30
|
+
|
|
31
|
+
Leave-one-out ablation testing, backward elimination, and ROI evaluation for multi-agent systems in Python. You have a set of per-agent findings (scores, confidences, telemetry) and a function or model that turns those findings into a verdict. `agent-ablation` answers the key production questions:
|
|
32
|
+
|
|
33
|
+
1. **Load-Bearing Influence:** Which agents' findings actually changed the verdict, and which were along for the ride?
|
|
34
|
+
2. **Cost & Token ROI:** How many dollars and tokens did each specialist burn per verdict flip? Does that small accuracy bump justify the API bill?
|
|
35
|
+
3. **Correlated Agents & Pruning:** Which redundant agents can be safely pruned via greedy backward elimination without breaking the final verdict?
|
|
36
|
+
4. **Protective vs. Harmful Signals:** When ground truth is provided, did an agent's presence prevent an error (protective), or did it cause a hallucination/false positive (harmful)?
|
|
37
|
+
5. **Stochastic & Async LLM Judges:** Handles async decisions and repeated sampling with majority voting to filter out LLM temperature variance.
|
|
38
|
+
|
|
39
|
+
Zero runtime dependencies. Pure Python 3.9+.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install agent-ablation
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Core Features & Usage
|
|
52
|
+
|
|
53
|
+
### 1. Basic Leave-One-Out Ablation
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from typing import List
|
|
57
|
+
from agent_ablation import Finding, run_ablation
|
|
58
|
+
|
|
59
|
+
def decide(findings: List[Finding]) -> str:
|
|
60
|
+
# Noisy-OR combination
|
|
61
|
+
survival = 1.0
|
|
62
|
+
for f in findings:
|
|
63
|
+
survival *= (1.0 - f.score / 100.0)
|
|
64
|
+
risk = 1.0 - survival
|
|
65
|
+
|
|
66
|
+
if risk >= 0.7:
|
|
67
|
+
return "decline"
|
|
68
|
+
if risk <= 0.3:
|
|
69
|
+
return "approve"
|
|
70
|
+
return "escalate"
|
|
71
|
+
|
|
72
|
+
findings = [
|
|
73
|
+
Finding(agent_id="transaction_pattern", score=25),
|
|
74
|
+
Finding(agent_id="identity_signal", score=90),
|
|
75
|
+
Finding(agent_id="network_analysis", score=20),
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
result = run_ablation(findings, decide)
|
|
79
|
+
|
|
80
|
+
print(result.baseline) # "decline"
|
|
81
|
+
print(result.load_bearing_ratio) # 0.33 (1 out of 3 agents flipped the outcome)
|
|
82
|
+
for p in result.per_agent:
|
|
83
|
+
print(p.removed_agent_id, "->", p.verdict_without, "(load-bearing)" if p.changed else "")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### 2. Cost & Token ROI Analysis ("Cost per Verdict Flip")
|
|
89
|
+
|
|
90
|
+
Pass telemetry (`cost`, `tokens`, `latency_ms`) inside your findings. `batch_ablation` computes the exact ROI metrics and identifies expensive agents with low decision impact:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from agent_ablation import Finding, batch_ablation, format_markdown_report
|
|
94
|
+
|
|
95
|
+
cases = [
|
|
96
|
+
[
|
|
97
|
+
Finding(agent_id="expensive_reasoner", score=90, cost=0.15, tokens=3000),
|
|
98
|
+
Finding(agent_id="cheap_heuristic", score=10, cost=0.002, tokens=50),
|
|
99
|
+
],
|
|
100
|
+
[
|
|
101
|
+
Finding(agent_id="expensive_reasoner", score=20, cost=0.15, tokens=3000),
|
|
102
|
+
Finding(agent_id="cheap_heuristic", score=85, cost=0.002, tokens=50),
|
|
103
|
+
],
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
results, summary = batch_ablation(cases, decide)
|
|
107
|
+
|
|
108
|
+
print(summary.roi.agents["expensive_reasoner"].cost_per_verdict_flip) # Cost per decision flip
|
|
109
|
+
print(summary.roi.recommendations) # Automated pruning/downgrade advice
|
|
110
|
+
|
|
111
|
+
# Format into a Markdown report for PRs or documentation
|
|
112
|
+
print(format_markdown_report(summary))
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### 3. Async & Stochastic Decision Functions (LLM-as-a-Judge)
|
|
118
|
+
|
|
119
|
+
When your decision step is an async LLM call with temperature, use `run_ablation_async` or `batch_ablation_async`. Set `samples=k` to take a majority-vote consensus across runs to eliminate sampling noise:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
import asyncio
|
|
123
|
+
from agent_ablation import Finding, run_ablation_async
|
|
124
|
+
|
|
125
|
+
async def llm_supervisor_decide(findings: List[Finding]) -> str:
|
|
126
|
+
res = await call_llm_judge(findings)
|
|
127
|
+
return res["verdict"]
|
|
128
|
+
|
|
129
|
+
result = await run_ablation_async(
|
|
130
|
+
findings,
|
|
131
|
+
llm_supervisor_decide,
|
|
132
|
+
samples=5, # Runs 5 samples per ablation to filter out temperature noise
|
|
133
|
+
)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### 4. Greedy Backward Elimination & Minimal Viable Panel
|
|
139
|
+
|
|
140
|
+
If you have correlated or redundant agents (e.g., three critics looking at the same context), simple leave-one-out might mark all of them as not load-bearing because the others compensate.
|
|
141
|
+
|
|
142
|
+
`run_backward_elimination` iteratively eliminates agents one-by-one until removing any further agent flips the verdict, revealing the **minimal viable panel**:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from agent_ablation import run_backward_elimination
|
|
146
|
+
|
|
147
|
+
result = run_backward_elimination(all_specialists, decide)
|
|
148
|
+
|
|
149
|
+
print(result.minimal_agent_ids) # ["critic_1", "security_auditor"]
|
|
150
|
+
print(result.eliminated_agent_ids) # ["critic_2", "critic_3", "scout_noisy"]
|
|
151
|
+
print(result.steps) # Step-by-step elimination trace
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
For detecting 2nd-order joint dependencies, `run_pairwise_ablation(findings, decide)` evaluates all pairs $(A, B)$ to catch cases where neither agent alone is load-bearing, but removing both together flips the outcome.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
### 5. Ground-Truth & Net Accuracy Impact ("Protective vs. Harmful")
|
|
159
|
+
|
|
160
|
+
Supply ground truth labels in `batch_ablation` to measure whether an agent's load-bearing presence actually **improved** accuracy or **injected errors / hallucinations**:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
results, summary = batch_ablation(
|
|
164
|
+
cases,
|
|
165
|
+
decide,
|
|
166
|
+
ground_truth=["approve", "decline", "approve", "escalate"],
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Per-agent stats:
|
|
170
|
+
# - Protective: removing the agent caused a correct verdict to become incorrect
|
|
171
|
+
# - Harmful: removing the agent fixed an incorrect verdict
|
|
172
|
+
print(summary.per_agent_stats["hallucinating_agent"].role) # "Harmful"
|
|
173
|
+
print(summary.per_agent_stats["hallucinating_agent"].net_accuracy_impact) # -0.25
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Framework Adapters
|
|
179
|
+
|
|
180
|
+
Zero-dependency adapters to map telemetry and messages from popular agent frameworks directly into `Finding`:
|
|
181
|
+
|
|
182
|
+
### LangGraph / LangChain
|
|
183
|
+
```python
|
|
184
|
+
from agent_ablation import from_langgraph_messages
|
|
185
|
+
|
|
186
|
+
findings = from_langgraph_messages(
|
|
187
|
+
state["messages"],
|
|
188
|
+
score_of=lambda msg: msg["content"]["score"],
|
|
189
|
+
confidence_of=lambda msg: msg["content"].get("confidence"),
|
|
190
|
+
)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### CrewAI
|
|
194
|
+
```python
|
|
195
|
+
from agent_ablation import from_crewai_tasks
|
|
196
|
+
|
|
197
|
+
findings = from_crewai_tasks(
|
|
198
|
+
crew_output.tasks_output,
|
|
199
|
+
score_of=lambda task: task.json_dict.get("score", 0),
|
|
200
|
+
)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### AutoGen
|
|
204
|
+
```python
|
|
205
|
+
from agent_ablation import from_autogen_messages
|
|
206
|
+
|
|
207
|
+
findings = from_autogen_messages(
|
|
208
|
+
chat_history,
|
|
209
|
+
score_of=lambda msg: msg["content"]["risk_score"],
|
|
210
|
+
)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Vercel AI SDK / Trace Steps
|
|
214
|
+
```python
|
|
215
|
+
from agent_ablation import from_ai_sdk_steps
|
|
216
|
+
|
|
217
|
+
findings = from_ai_sdk_steps(
|
|
218
|
+
steps,
|
|
219
|
+
score_of=lambda step: step["result"]["score"],
|
|
220
|
+
)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Generic Custom Records
|
|
224
|
+
```python
|
|
225
|
+
from agent_ablation import from_records
|
|
226
|
+
|
|
227
|
+
findings = from_records(
|
|
228
|
+
custom_audit_records,
|
|
229
|
+
agent_id=lambda r, idx: r.specialist_id,
|
|
230
|
+
score_of=lambda r, idx: r.risk_score,
|
|
231
|
+
confidence_of=lambda r, idx: r.confidence_level,
|
|
232
|
+
)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Worked Example: SentryMesh 33% Multi-Signal Finding
|
|
238
|
+
|
|
239
|
+
[SentryMesh](https://github.com/AyushCipher/Sentry-Mesh) is a four-specialist multi-agent fraud investigation system. Its eval harness runs an ablation over its 23-case bank and reports: of 9 cases auto-resolved without human escalation, **only 3 survive removal of their single loudest specialist — 6 collapse to `escalate`.**
|
|
240
|
+
|
|
241
|
+
`tests/test_ablation.py` reproduces all 6 cases verbatim with `agent-ablation`.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Architectural Note: Leaf Ablation vs. DAG Subgraph Replay
|
|
246
|
+
|
|
247
|
+
* **Leaf Finding Ablation (This Package):** Best for **parallel / fan-out / fan-in** panels where specialists independently produce findings that feed a decision gate. Because findings are generated independently, dropping an item at `decide()` measures causal weight with **zero LLM re-invocation cost**.
|
|
248
|
+
* **Sequential DAG Replay:** If your pipeline is sequential (Agent A feeds intermediate prompt context to Agent B), removing Agent A at the final decision gate misses that Agent B's output already reflects Agent A. Measuring sequential pipelines requires replaying downstream subgraphs or injecting mock messages into the trace.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## API Summary
|
|
253
|
+
|
|
254
|
+
| Function | Description |
|
|
255
|
+
| :--- | :--- |
|
|
256
|
+
| `run_ablation(findings, decide, equals=None)` | Synchronous leave-one-out ablation for a single case. |
|
|
257
|
+
| `run_ablation_async(findings, decide, equals=None, samples=1, aggregate_samples=None)` | Async leave-one-out ablation with optional $K$-sampling / majority voting. |
|
|
258
|
+
| `batch_ablation(cases, decide, equals=None, ground_truth=None)` | Batch ablation with telemetry ROI and ground-truth metrics. |
|
|
259
|
+
| `batch_ablation_async(cases, decide, equals=None, samples=1, aggregate_samples=None, ground_truth=None)` | Async batch ablation. |
|
|
260
|
+
| `run_backward_elimination(findings, decide, equals=None)` | Greedy backward elimination to find the minimal agent panel. |
|
|
261
|
+
| `run_backward_elimination_async(findings, decide, ...)` | Async greedy backward elimination. |
|
|
262
|
+
| `run_pairwise_ablation(findings, decide, equals=None)` | Evaluates all 2-agent pairs to detect interaction/redundancy effects. |
|
|
263
|
+
| `format_markdown_report(summary, title=..., include_roi=True, include_recommendations=True)` | Formats summary into a GitHub/Dev.to markdown report with ROI tables. |
|
|
264
|
+
| `format_ascii_table(summary)` | Formats summary into a clean terminal ASCII table. |
|
|
265
|
+
| `from_langgraph_messages(...)` | Adapter for LangGraph message arrays. |
|
|
266
|
+
| `from_crewai_tasks(...)` | Adapter for CrewAI task outputs. |
|
|
267
|
+
| `from_autogen_messages(...)` | Adapter for AutoGen chat histories. |
|
|
268
|
+
| `from_ai_sdk_steps(...)` | Adapter for AI SDK / tool execution traces. |
|
|
269
|
+
| `from_records(...)` | Generic record mapper. |
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## License
|
|
274
|
+
|
|
275
|
+
MIT © Ayush Verma — ayushv3533e@gmail.com
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# agent-ablation (Python)
|
|
2
|
+
|
|
3
|
+
[](https://github.com/AyushCipher/agent-ablation/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/agent-ablation/)
|
|
5
|
+
[](https://pypi.org/project/agent-ablation/)
|
|
6
|
+
|
|
7
|
+
Leave-one-out ablation testing, backward elimination, and ROI evaluation for multi-agent systems in Python. You have a set of per-agent findings (scores, confidences, telemetry) and a function or model that turns those findings into a verdict. `agent-ablation` answers the key production questions:
|
|
8
|
+
|
|
9
|
+
1. **Load-Bearing Influence:** Which agents' findings actually changed the verdict, and which were along for the ride?
|
|
10
|
+
2. **Cost & Token ROI:** How many dollars and tokens did each specialist burn per verdict flip? Does that small accuracy bump justify the API bill?
|
|
11
|
+
3. **Correlated Agents & Pruning:** Which redundant agents can be safely pruned via greedy backward elimination without breaking the final verdict?
|
|
12
|
+
4. **Protective vs. Harmful Signals:** When ground truth is provided, did an agent's presence prevent an error (protective), or did it cause a hallucination/false positive (harmful)?
|
|
13
|
+
5. **Stochastic & Async LLM Judges:** Handles async decisions and repeated sampling with majority voting to filter out LLM temperature variance.
|
|
14
|
+
|
|
15
|
+
Zero runtime dependencies. Pure Python 3.9+.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install agent-ablation
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Core Features & Usage
|
|
28
|
+
|
|
29
|
+
### 1. Basic Leave-One-Out Ablation
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from typing import List
|
|
33
|
+
from agent_ablation import Finding, run_ablation
|
|
34
|
+
|
|
35
|
+
def decide(findings: List[Finding]) -> str:
|
|
36
|
+
# Noisy-OR combination
|
|
37
|
+
survival = 1.0
|
|
38
|
+
for f in findings:
|
|
39
|
+
survival *= (1.0 - f.score / 100.0)
|
|
40
|
+
risk = 1.0 - survival
|
|
41
|
+
|
|
42
|
+
if risk >= 0.7:
|
|
43
|
+
return "decline"
|
|
44
|
+
if risk <= 0.3:
|
|
45
|
+
return "approve"
|
|
46
|
+
return "escalate"
|
|
47
|
+
|
|
48
|
+
findings = [
|
|
49
|
+
Finding(agent_id="transaction_pattern", score=25),
|
|
50
|
+
Finding(agent_id="identity_signal", score=90),
|
|
51
|
+
Finding(agent_id="network_analysis", score=20),
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
result = run_ablation(findings, decide)
|
|
55
|
+
|
|
56
|
+
print(result.baseline) # "decline"
|
|
57
|
+
print(result.load_bearing_ratio) # 0.33 (1 out of 3 agents flipped the outcome)
|
|
58
|
+
for p in result.per_agent:
|
|
59
|
+
print(p.removed_agent_id, "->", p.verdict_without, "(load-bearing)" if p.changed else "")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
### 2. Cost & Token ROI Analysis ("Cost per Verdict Flip")
|
|
65
|
+
|
|
66
|
+
Pass telemetry (`cost`, `tokens`, `latency_ms`) inside your findings. `batch_ablation` computes the exact ROI metrics and identifies expensive agents with low decision impact:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from agent_ablation import Finding, batch_ablation, format_markdown_report
|
|
70
|
+
|
|
71
|
+
cases = [
|
|
72
|
+
[
|
|
73
|
+
Finding(agent_id="expensive_reasoner", score=90, cost=0.15, tokens=3000),
|
|
74
|
+
Finding(agent_id="cheap_heuristic", score=10, cost=0.002, tokens=50),
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
Finding(agent_id="expensive_reasoner", score=20, cost=0.15, tokens=3000),
|
|
78
|
+
Finding(agent_id="cheap_heuristic", score=85, cost=0.002, tokens=50),
|
|
79
|
+
],
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
results, summary = batch_ablation(cases, decide)
|
|
83
|
+
|
|
84
|
+
print(summary.roi.agents["expensive_reasoner"].cost_per_verdict_flip) # Cost per decision flip
|
|
85
|
+
print(summary.roi.recommendations) # Automated pruning/downgrade advice
|
|
86
|
+
|
|
87
|
+
# Format into a Markdown report for PRs or documentation
|
|
88
|
+
print(format_markdown_report(summary))
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### 3. Async & Stochastic Decision Functions (LLM-as-a-Judge)
|
|
94
|
+
|
|
95
|
+
When your decision step is an async LLM call with temperature, use `run_ablation_async` or `batch_ablation_async`. Set `samples=k` to take a majority-vote consensus across runs to eliminate sampling noise:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
import asyncio
|
|
99
|
+
from agent_ablation import Finding, run_ablation_async
|
|
100
|
+
|
|
101
|
+
async def llm_supervisor_decide(findings: List[Finding]) -> str:
|
|
102
|
+
res = await call_llm_judge(findings)
|
|
103
|
+
return res["verdict"]
|
|
104
|
+
|
|
105
|
+
result = await run_ablation_async(
|
|
106
|
+
findings,
|
|
107
|
+
llm_supervisor_decide,
|
|
108
|
+
samples=5, # Runs 5 samples per ablation to filter out temperature noise
|
|
109
|
+
)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### 4. Greedy Backward Elimination & Minimal Viable Panel
|
|
115
|
+
|
|
116
|
+
If you have correlated or redundant agents (e.g., three critics looking at the same context), simple leave-one-out might mark all of them as not load-bearing because the others compensate.
|
|
117
|
+
|
|
118
|
+
`run_backward_elimination` iteratively eliminates agents one-by-one until removing any further agent flips the verdict, revealing the **minimal viable panel**:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from agent_ablation import run_backward_elimination
|
|
122
|
+
|
|
123
|
+
result = run_backward_elimination(all_specialists, decide)
|
|
124
|
+
|
|
125
|
+
print(result.minimal_agent_ids) # ["critic_1", "security_auditor"]
|
|
126
|
+
print(result.eliminated_agent_ids) # ["critic_2", "critic_3", "scout_noisy"]
|
|
127
|
+
print(result.steps) # Step-by-step elimination trace
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
For detecting 2nd-order joint dependencies, `run_pairwise_ablation(findings, decide)` evaluates all pairs $(A, B)$ to catch cases where neither agent alone is load-bearing, but removing both together flips the outcome.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### 5. Ground-Truth & Net Accuracy Impact ("Protective vs. Harmful")
|
|
135
|
+
|
|
136
|
+
Supply ground truth labels in `batch_ablation` to measure whether an agent's load-bearing presence actually **improved** accuracy or **injected errors / hallucinations**:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
results, summary = batch_ablation(
|
|
140
|
+
cases,
|
|
141
|
+
decide,
|
|
142
|
+
ground_truth=["approve", "decline", "approve", "escalate"],
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Per-agent stats:
|
|
146
|
+
# - Protective: removing the agent caused a correct verdict to become incorrect
|
|
147
|
+
# - Harmful: removing the agent fixed an incorrect verdict
|
|
148
|
+
print(summary.per_agent_stats["hallucinating_agent"].role) # "Harmful"
|
|
149
|
+
print(summary.per_agent_stats["hallucinating_agent"].net_accuracy_impact) # -0.25
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Framework Adapters
|
|
155
|
+
|
|
156
|
+
Zero-dependency adapters to map telemetry and messages from popular agent frameworks directly into `Finding`:
|
|
157
|
+
|
|
158
|
+
### LangGraph / LangChain
|
|
159
|
+
```python
|
|
160
|
+
from agent_ablation import from_langgraph_messages
|
|
161
|
+
|
|
162
|
+
findings = from_langgraph_messages(
|
|
163
|
+
state["messages"],
|
|
164
|
+
score_of=lambda msg: msg["content"]["score"],
|
|
165
|
+
confidence_of=lambda msg: msg["content"].get("confidence"),
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### CrewAI
|
|
170
|
+
```python
|
|
171
|
+
from agent_ablation import from_crewai_tasks
|
|
172
|
+
|
|
173
|
+
findings = from_crewai_tasks(
|
|
174
|
+
crew_output.tasks_output,
|
|
175
|
+
score_of=lambda task: task.json_dict.get("score", 0),
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### AutoGen
|
|
180
|
+
```python
|
|
181
|
+
from agent_ablation import from_autogen_messages
|
|
182
|
+
|
|
183
|
+
findings = from_autogen_messages(
|
|
184
|
+
chat_history,
|
|
185
|
+
score_of=lambda msg: msg["content"]["risk_score"],
|
|
186
|
+
)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Vercel AI SDK / Trace Steps
|
|
190
|
+
```python
|
|
191
|
+
from agent_ablation import from_ai_sdk_steps
|
|
192
|
+
|
|
193
|
+
findings = from_ai_sdk_steps(
|
|
194
|
+
steps,
|
|
195
|
+
score_of=lambda step: step["result"]["score"],
|
|
196
|
+
)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Generic Custom Records
|
|
200
|
+
```python
|
|
201
|
+
from agent_ablation import from_records
|
|
202
|
+
|
|
203
|
+
findings = from_records(
|
|
204
|
+
custom_audit_records,
|
|
205
|
+
agent_id=lambda r, idx: r.specialist_id,
|
|
206
|
+
score_of=lambda r, idx: r.risk_score,
|
|
207
|
+
confidence_of=lambda r, idx: r.confidence_level,
|
|
208
|
+
)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Worked Example: SentryMesh 33% Multi-Signal Finding
|
|
214
|
+
|
|
215
|
+
[SentryMesh](https://github.com/AyushCipher/Sentry-Mesh) is a four-specialist multi-agent fraud investigation system. Its eval harness runs an ablation over its 23-case bank and reports: of 9 cases auto-resolved without human escalation, **only 3 survive removal of their single loudest specialist — 6 collapse to `escalate`.**
|
|
216
|
+
|
|
217
|
+
`tests/test_ablation.py` reproduces all 6 cases verbatim with `agent-ablation`.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Architectural Note: Leaf Ablation vs. DAG Subgraph Replay
|
|
222
|
+
|
|
223
|
+
* **Leaf Finding Ablation (This Package):** Best for **parallel / fan-out / fan-in** panels where specialists independently produce findings that feed a decision gate. Because findings are generated independently, dropping an item at `decide()` measures causal weight with **zero LLM re-invocation cost**.
|
|
224
|
+
* **Sequential DAG Replay:** If your pipeline is sequential (Agent A feeds intermediate prompt context to Agent B), removing Agent A at the final decision gate misses that Agent B's output already reflects Agent A. Measuring sequential pipelines requires replaying downstream subgraphs or injecting mock messages into the trace.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## API Summary
|
|
229
|
+
|
|
230
|
+
| Function | Description |
|
|
231
|
+
| :--- | :--- |
|
|
232
|
+
| `run_ablation(findings, decide, equals=None)` | Synchronous leave-one-out ablation for a single case. |
|
|
233
|
+
| `run_ablation_async(findings, decide, equals=None, samples=1, aggregate_samples=None)` | Async leave-one-out ablation with optional $K$-sampling / majority voting. |
|
|
234
|
+
| `batch_ablation(cases, decide, equals=None, ground_truth=None)` | Batch ablation with telemetry ROI and ground-truth metrics. |
|
|
235
|
+
| `batch_ablation_async(cases, decide, equals=None, samples=1, aggregate_samples=None, ground_truth=None)` | Async batch ablation. |
|
|
236
|
+
| `run_backward_elimination(findings, decide, equals=None)` | Greedy backward elimination to find the minimal agent panel. |
|
|
237
|
+
| `run_backward_elimination_async(findings, decide, ...)` | Async greedy backward elimination. |
|
|
238
|
+
| `run_pairwise_ablation(findings, decide, equals=None)` | Evaluates all 2-agent pairs to detect interaction/redundancy effects. |
|
|
239
|
+
| `format_markdown_report(summary, title=..., include_roi=True, include_recommendations=True)` | Formats summary into a GitHub/Dev.to markdown report with ROI tables. |
|
|
240
|
+
| `format_ascii_table(summary)` | Formats summary into a clean terminal ASCII table. |
|
|
241
|
+
| `from_langgraph_messages(...)` | Adapter for LangGraph message arrays. |
|
|
242
|
+
| `from_crewai_tasks(...)` | Adapter for CrewAI task outputs. |
|
|
243
|
+
| `from_autogen_messages(...)` | Adapter for AutoGen chat histories. |
|
|
244
|
+
| `from_ai_sdk_steps(...)` | Adapter for AI SDK / tool execution traces. |
|
|
245
|
+
| `from_records(...)` | Generic record mapper. |
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
|
|
251
|
+
MIT © Ayush Verma — ayushv3533e@gmail.com
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agent-ablation: Leave-one-out ablation testing, backward elimination, and ROI evaluation for multi-agent systems.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from agent_ablation.adapters.autogen import from_autogen_messages
|
|
6
|
+
from agent_ablation.adapters.crewai import from_crewai_tasks
|
|
7
|
+
from agent_ablation.adapters.langgraph import from_langgraph_messages, from_records
|
|
8
|
+
from agent_ablation.adapters.vercel import from_ai_sdk_steps
|
|
9
|
+
from agent_ablation.core import (
|
|
10
|
+
aggregate_batch_results,
|
|
11
|
+
batch_ablation,
|
|
12
|
+
batch_ablation_async,
|
|
13
|
+
majority_vote,
|
|
14
|
+
run_ablation,
|
|
15
|
+
run_ablation_async,
|
|
16
|
+
run_backward_elimination,
|
|
17
|
+
run_backward_elimination_async,
|
|
18
|
+
run_pairwise_ablation,
|
|
19
|
+
)
|
|
20
|
+
from agent_ablation.models import (
|
|
21
|
+
AblationResult,
|
|
22
|
+
AgentRoiMetrics,
|
|
23
|
+
BackwardEliminationResult,
|
|
24
|
+
BackwardEliminationStep,
|
|
25
|
+
BatchAblationSummary,
|
|
26
|
+
Finding,
|
|
27
|
+
GroundTruthSummary,
|
|
28
|
+
PairwiseAblationItem,
|
|
29
|
+
PairwiseAblationResult,
|
|
30
|
+
PerAgentAblation,
|
|
31
|
+
PerAgentStats,
|
|
32
|
+
PruningRecommendation,
|
|
33
|
+
RoiSummary,
|
|
34
|
+
)
|
|
35
|
+
from agent_ablation.reporters.formatters import (
|
|
36
|
+
format_ascii_table,
|
|
37
|
+
format_markdown_report,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
__version__ = "0.3.0"
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
# Models
|
|
44
|
+
"Finding",
|
|
45
|
+
"PerAgentAblation",
|
|
46
|
+
"AblationResult",
|
|
47
|
+
"AgentRoiMetrics",
|
|
48
|
+
"PruningRecommendation",
|
|
49
|
+
"RoiSummary",
|
|
50
|
+
"GroundTruthSummary",
|
|
51
|
+
"PerAgentStats",
|
|
52
|
+
"BatchAblationSummary",
|
|
53
|
+
"BackwardEliminationStep",
|
|
54
|
+
"BackwardEliminationResult",
|
|
55
|
+
"PairwiseAblationItem",
|
|
56
|
+
"PairwiseAblationResult",
|
|
57
|
+
# Core Functions
|
|
58
|
+
"run_ablation",
|
|
59
|
+
"run_ablation_async",
|
|
60
|
+
"batch_ablation",
|
|
61
|
+
"batch_ablation_async",
|
|
62
|
+
"aggregate_batch_results",
|
|
63
|
+
"run_backward_elimination",
|
|
64
|
+
"run_backward_elimination_async",
|
|
65
|
+
"run_pairwise_ablation",
|
|
66
|
+
"majority_vote",
|
|
67
|
+
# Adapters
|
|
68
|
+
"from_langgraph_messages",
|
|
69
|
+
"from_records",
|
|
70
|
+
"from_crewai_tasks",
|
|
71
|
+
"from_autogen_messages",
|
|
72
|
+
"from_ai_sdk_steps",
|
|
73
|
+
# Reporters
|
|
74
|
+
"format_markdown_report",
|
|
75
|
+
"format_ascii_table",
|
|
76
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Framework adapters for agent-ablation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from agent_ablation.adapters.autogen import from_autogen_messages
|
|
6
|
+
from agent_ablation.adapters.crewai import from_crewai_tasks
|
|
7
|
+
from agent_ablation.adapters.langgraph import from_langgraph_messages, from_records
|
|
8
|
+
from agent_ablation.adapters.vercel import from_ai_sdk_steps
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"from_langgraph_messages",
|
|
12
|
+
"from_records",
|
|
13
|
+
"from_crewai_tasks",
|
|
14
|
+
"from_autogen_messages",
|
|
15
|
+
"from_ai_sdk_steps",
|
|
16
|
+
]
|