agent-attrib 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.
- agent_attrib-0.1.0/PKG-INFO +181 -0
- agent_attrib-0.1.0/README.md +152 -0
- agent_attrib-0.1.0/pyproject.toml +97 -0
- agent_attrib-0.1.0/pyproject.toml.orig +76 -0
- agent_attrib-0.1.0/src/agent_attrib/__init__.py +124 -0
- agent_attrib-0.1.0/src/agent_attrib/cli.py +250 -0
- agent_attrib-0.1.0/src/agent_attrib/design.py +214 -0
- agent_attrib-0.1.0/src/agent_attrib/model.py +262 -0
- agent_attrib-0.1.0/src/agent_attrib/power.py +65 -0
- agent_attrib-0.1.0/src/agent_attrib/py.typed +0 -0
- agent_attrib-0.1.0/src/agent_attrib/report.py +181 -0
- agent_attrib-0.1.0/src/agent_attrib/schema.py +183 -0
- agent_attrib-0.1.0/src/agent_attrib/simulate.py +138 -0
- agent_attrib-0.1.0/src/agent_attrib/verdict.py +54 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: agent-attrib
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Multi-factor attribution for agent release changes using factorial design of experiments
|
|
5
|
+
Keywords: agents,llm,attribution,factorial,design-of-experiments,statistics
|
|
6
|
+
Author: agent-attrib contributors
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
18
|
+
Requires-Dist: numpy>=1.26
|
|
19
|
+
Requires-Dist: pandas>=2.1
|
|
20
|
+
Requires-Dist: scipy>=1.11
|
|
21
|
+
Requires-Dist: statsmodels>=0.14
|
|
22
|
+
Requires-Dist: pydantic>=2.5
|
|
23
|
+
Requires-Dist: typer>=0.9
|
|
24
|
+
Requires-Dist: rich>=13.7
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Project-URL: Homepage, https://github.com/agent-attrib/agent-attrib
|
|
27
|
+
Project-URL: Issues, https://github.com/agent-attrib/agent-attrib/issues
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# agent-attrib
|
|
31
|
+
|
|
32
|
+
A Python package for attributing metric changes across simultaneous agent release changes using factorial design of experiments.
|
|
33
|
+
|
|
34
|
+
**The problem:** You shipped a release that changed the prompt, the model, and the retriever at once. Your eval pass rate moved. Which change caused it, and by how much?
|
|
35
|
+
|
|
36
|
+
**The solution:** Design a factorial experiment, run your agent on the configurations it specifies, and let `agent-attrib` decompose the effects with confidence intervals.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install agent-attrib
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 60-second demo
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
agent-attrib demo
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
No LLM calls, no cost. Generates synthetic runs with planted effects and reports which factors are responsible.
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
### 1. Design the experiment
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
agent-attrib plan --factors prompt,model,retriever --tasks 30 --trials 2 -o design.csv
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
Design type : 2^3 full factorial
|
|
60
|
+
Resolution : full
|
|
61
|
+
Factors : prompt, model, retriever
|
|
62
|
+
Tasks : 30
|
|
63
|
+
Trials/config: 2
|
|
64
|
+
Configurations: 8
|
|
65
|
+
Total runs : 480
|
|
66
|
+
Seed : 42
|
|
67
|
+
MDE (success, α=0.05, power=0.8): 0.1283
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For 5-7 factors, a fractional factorial is used automatically (resolution IV by default).
|
|
71
|
+
|
|
72
|
+
### 2. Run your agent
|
|
73
|
+
|
|
74
|
+
For each row in `design.csv`, set each factor to the specified level (`baseline` or `candidate`) and run your agent on the task. Write one line per run:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{"run_id": "r001", "task_id": "t0001", "trial": 1,
|
|
78
|
+
"factors": {"prompt": "candidate", "model": "baseline", "retriever": "candidate"},
|
|
79
|
+
"metrics": {"success": 1, "cost_usd": 0.042, "latency_s": 11.3}}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 3. Analyze
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
agent-attrib analyze runs.jsonl --metric success --metric cost_usd --margin 0.05
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Python API
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from agent_attrib import plan, analyze, load_runs
|
|
92
|
+
|
|
93
|
+
design = plan(factors=["prompt", "model", "retriever"], tasks=30, trials=2, seed=7)
|
|
94
|
+
print(design.summary())
|
|
95
|
+
|
|
96
|
+
runset = load_runs("runs.jsonl")
|
|
97
|
+
result = analyze(runset, metrics=["success", "cost_usd"], margin=0.05)
|
|
98
|
+
print(result.to_markdown())
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Input format
|
|
102
|
+
|
|
103
|
+
**JSONL** (one object per run):
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{"run_id": "r001", "task_id": "t07", "trial": 1,
|
|
107
|
+
"factors": {"prompt": "candidate", "model": "baseline", "retriever": "candidate"},
|
|
108
|
+
"metrics": {"success": 1, "cost_usd": 0.042, "latency_s": 11.3}}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**CSV** with columns `run_id, task_id, trial, f_<factor>..., m_<metric>...`:
|
|
112
|
+
|
|
113
|
+
```csv
|
|
114
|
+
run_id,task_id,trial,f_prompt,f_model,m_success,m_cost_usd
|
|
115
|
+
r001,t07,1,candidate,baseline,1,0.042
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Factor levels must be exactly `baseline` or `candidate`. Metrics must be numeric.
|
|
119
|
+
|
|
120
|
+
## Method
|
|
121
|
+
|
|
122
|
+
For each metric, `analyze` fits OLS with task fixed effects:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
metric ~ task fixed effects + main effects + estimable two-factor interactions
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- **Binary `success`:** linear probability model. Effects are in percentage points.
|
|
129
|
+
Not logistic regression: effects stay directly interpretable as probability differences.
|
|
130
|
+
- **Cost and latency:** `log(metric)` model. Effects reported as percent change.
|
|
131
|
+
- **Standard errors:** cluster-robust, clustered by task.
|
|
132
|
+
- **Multiple testing:** Holm step-down correction across all effects within a metric.
|
|
133
|
+
- **Verdicts** (given an equivalence margin `m`):
|
|
134
|
+
- `IMPROVEMENT` -- 95% CI sits entirely above +m
|
|
135
|
+
- `REGRESSION` -- 95% CI sits entirely below -m
|
|
136
|
+
- `EQUIVALENT` -- 95% CI lies within +/-m
|
|
137
|
+
- `INCONCLUSIVE` -- CI straddles a boundary; includes approximate tasks needed to resolve
|
|
138
|
+
- Effect convention: switching a factor from baseline to candidate (= 2*beta under +/-1 coding).
|
|
139
|
+
|
|
140
|
+
## Designs
|
|
141
|
+
|
|
142
|
+
| Factors | Design | Runs (30 tasks, 2 trials) |
|
|
143
|
+
|---------|--------|--------------------------|
|
|
144
|
+
| 1-4 | 2^k full factorial | 60-480 |
|
|
145
|
+
| 5 | 2^(5-1) res IV | 960 |
|
|
146
|
+
| 6 | 2^(6-2) res IV | 960 |
|
|
147
|
+
| 7 | 2^(7-3) res IV | 960 |
|
|
148
|
+
|
|
149
|
+
Fractional designs print their alias structure. Use `--resolution V` to request resolution V (more runs, no 2FI aliasing).
|
|
150
|
+
|
|
151
|
+
## Limitations
|
|
152
|
+
|
|
153
|
+
- Results apply to this task suite and model versions only.
|
|
154
|
+
- Attribution requires designed runs -- not observational production logs.
|
|
155
|
+
- Maximum 7 factors in v0.1.
|
|
156
|
+
- Two levels per factor only (baseline vs candidate).
|
|
157
|
+
- No LLM-as-judge; `agent-attrib` never calls any LLM.
|
|
158
|
+
|
|
159
|
+
## Prior art
|
|
160
|
+
|
|
161
|
+
- **agentstats, AgentAssay, deltagate:** proper A/B statistics for a single pair. Do not decompose changes across multiple simultaneous factors.
|
|
162
|
+
- **ablate-cli:** per-section ablation of `CLAUDE.md` files, for coding agents, runs Claude Code itself. Borrowed two ideas: four-way verdicts and printing the minimum detectable effect before any spend.
|
|
163
|
+
- **Research papers:** factorial ablations over agent components exist as one-off scripts.
|
|
164
|
+
|
|
165
|
+
`agent-attrib`'s niche: framework-agnostic, multi-factor, interaction-aware attribution for arbitrary release changes, decoupled from execution.
|
|
166
|
+
|
|
167
|
+
## Development
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
git clone https://github.com/agent-attrib/agent-attrib
|
|
171
|
+
cd agent-attrib
|
|
172
|
+
uv sync --all-groups
|
|
173
|
+
uv run pytest -m "not slow" # fast tests (~seconds)
|
|
174
|
+
uv run pytest # all tests including calibration (~minutes)
|
|
175
|
+
uv run ruff check src tests
|
|
176
|
+
uv run mypy src
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
MIT
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# agent-attrib
|
|
2
|
+
|
|
3
|
+
A Python package for attributing metric changes across simultaneous agent release changes using factorial design of experiments.
|
|
4
|
+
|
|
5
|
+
**The problem:** You shipped a release that changed the prompt, the model, and the retriever at once. Your eval pass rate moved. Which change caused it, and by how much?
|
|
6
|
+
|
|
7
|
+
**The solution:** Design a factorial experiment, run your agent on the configurations it specifies, and let `agent-attrib` decompose the effects with confidence intervals.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install agent-attrib
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 60-second demo
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
agent-attrib demo
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
No LLM calls, no cost. Generates synthetic runs with planted effects and reports which factors are responsible.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### 1. Design the experiment
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
agent-attrib plan --factors prompt,model,retriever --tasks 30 --trials 2 -o design.csv
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
Design type : 2^3 full factorial
|
|
31
|
+
Resolution : full
|
|
32
|
+
Factors : prompt, model, retriever
|
|
33
|
+
Tasks : 30
|
|
34
|
+
Trials/config: 2
|
|
35
|
+
Configurations: 8
|
|
36
|
+
Total runs : 480
|
|
37
|
+
Seed : 42
|
|
38
|
+
MDE (success, α=0.05, power=0.8): 0.1283
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
For 5-7 factors, a fractional factorial is used automatically (resolution IV by default).
|
|
42
|
+
|
|
43
|
+
### 2. Run your agent
|
|
44
|
+
|
|
45
|
+
For each row in `design.csv`, set each factor to the specified level (`baseline` or `candidate`) and run your agent on the task. Write one line per run:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{"run_id": "r001", "task_id": "t0001", "trial": 1,
|
|
49
|
+
"factors": {"prompt": "candidate", "model": "baseline", "retriever": "candidate"},
|
|
50
|
+
"metrics": {"success": 1, "cost_usd": 0.042, "latency_s": 11.3}}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 3. Analyze
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
agent-attrib analyze runs.jsonl --metric success --metric cost_usd --margin 0.05
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Python API
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from agent_attrib import plan, analyze, load_runs
|
|
63
|
+
|
|
64
|
+
design = plan(factors=["prompt", "model", "retriever"], tasks=30, trials=2, seed=7)
|
|
65
|
+
print(design.summary())
|
|
66
|
+
|
|
67
|
+
runset = load_runs("runs.jsonl")
|
|
68
|
+
result = analyze(runset, metrics=["success", "cost_usd"], margin=0.05)
|
|
69
|
+
print(result.to_markdown())
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Input format
|
|
73
|
+
|
|
74
|
+
**JSONL** (one object per run):
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{"run_id": "r001", "task_id": "t07", "trial": 1,
|
|
78
|
+
"factors": {"prompt": "candidate", "model": "baseline", "retriever": "candidate"},
|
|
79
|
+
"metrics": {"success": 1, "cost_usd": 0.042, "latency_s": 11.3}}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**CSV** with columns `run_id, task_id, trial, f_<factor>..., m_<metric>...`:
|
|
83
|
+
|
|
84
|
+
```csv
|
|
85
|
+
run_id,task_id,trial,f_prompt,f_model,m_success,m_cost_usd
|
|
86
|
+
r001,t07,1,candidate,baseline,1,0.042
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Factor levels must be exactly `baseline` or `candidate`. Metrics must be numeric.
|
|
90
|
+
|
|
91
|
+
## Method
|
|
92
|
+
|
|
93
|
+
For each metric, `analyze` fits OLS with task fixed effects:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
metric ~ task fixed effects + main effects + estimable two-factor interactions
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- **Binary `success`:** linear probability model. Effects are in percentage points.
|
|
100
|
+
Not logistic regression: effects stay directly interpretable as probability differences.
|
|
101
|
+
- **Cost and latency:** `log(metric)` model. Effects reported as percent change.
|
|
102
|
+
- **Standard errors:** cluster-robust, clustered by task.
|
|
103
|
+
- **Multiple testing:** Holm step-down correction across all effects within a metric.
|
|
104
|
+
- **Verdicts** (given an equivalence margin `m`):
|
|
105
|
+
- `IMPROVEMENT` -- 95% CI sits entirely above +m
|
|
106
|
+
- `REGRESSION` -- 95% CI sits entirely below -m
|
|
107
|
+
- `EQUIVALENT` -- 95% CI lies within +/-m
|
|
108
|
+
- `INCONCLUSIVE` -- CI straddles a boundary; includes approximate tasks needed to resolve
|
|
109
|
+
- Effect convention: switching a factor from baseline to candidate (= 2*beta under +/-1 coding).
|
|
110
|
+
|
|
111
|
+
## Designs
|
|
112
|
+
|
|
113
|
+
| Factors | Design | Runs (30 tasks, 2 trials) |
|
|
114
|
+
|---------|--------|--------------------------|
|
|
115
|
+
| 1-4 | 2^k full factorial | 60-480 |
|
|
116
|
+
| 5 | 2^(5-1) res IV | 960 |
|
|
117
|
+
| 6 | 2^(6-2) res IV | 960 |
|
|
118
|
+
| 7 | 2^(7-3) res IV | 960 |
|
|
119
|
+
|
|
120
|
+
Fractional designs print their alias structure. Use `--resolution V` to request resolution V (more runs, no 2FI aliasing).
|
|
121
|
+
|
|
122
|
+
## Limitations
|
|
123
|
+
|
|
124
|
+
- Results apply to this task suite and model versions only.
|
|
125
|
+
- Attribution requires designed runs -- not observational production logs.
|
|
126
|
+
- Maximum 7 factors in v0.1.
|
|
127
|
+
- Two levels per factor only (baseline vs candidate).
|
|
128
|
+
- No LLM-as-judge; `agent-attrib` never calls any LLM.
|
|
129
|
+
|
|
130
|
+
## Prior art
|
|
131
|
+
|
|
132
|
+
- **agentstats, AgentAssay, deltagate:** proper A/B statistics for a single pair. Do not decompose changes across multiple simultaneous factors.
|
|
133
|
+
- **ablate-cli:** per-section ablation of `CLAUDE.md` files, for coding agents, runs Claude Code itself. Borrowed two ideas: four-way verdicts and printing the minimum detectable effect before any spend.
|
|
134
|
+
- **Research papers:** factorial ablations over agent components exist as one-off scripts.
|
|
135
|
+
|
|
136
|
+
`agent-attrib`'s niche: framework-agnostic, multi-factor, interaction-aware attribution for arbitrary release changes, decoupled from execution.
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
git clone https://github.com/agent-attrib/agent-attrib
|
|
142
|
+
cd agent-attrib
|
|
143
|
+
uv sync --all-groups
|
|
144
|
+
uv run pytest -m "not slow" # fast tests (~seconds)
|
|
145
|
+
uv run pytest # all tests including calibration (~minutes)
|
|
146
|
+
uv run ruff check src tests
|
|
147
|
+
uv run mypy src
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "agent-attrib"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Multi-factor attribution for agent release changes using factorial design of experiments"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
keywords = [
|
|
8
|
+
"agents",
|
|
9
|
+
"llm",
|
|
10
|
+
"attribution",
|
|
11
|
+
"factorial",
|
|
12
|
+
"design-of-experiments",
|
|
13
|
+
"statistics",
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"numpy>=1.26",
|
|
29
|
+
"pandas>=2.1",
|
|
30
|
+
"scipy>=1.11",
|
|
31
|
+
"statsmodels>=0.14",
|
|
32
|
+
"pydantic>=2.5",
|
|
33
|
+
"typer>=0.9",
|
|
34
|
+
"rich>=13.7",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.license]
|
|
38
|
+
text = "MIT"
|
|
39
|
+
|
|
40
|
+
[[project.authors]]
|
|
41
|
+
name = "agent-attrib contributors"
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
agent-attrib = "agent_attrib.cli:app"
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Homepage = "https://github.com/agent-attrib/agent-attrib"
|
|
48
|
+
Issues = "https://github.com/agent-attrib/agent-attrib/issues"
|
|
49
|
+
|
|
50
|
+
[build-system]
|
|
51
|
+
requires = ["uv_build>=0.13.7,<0.14.0"]
|
|
52
|
+
build-backend = "uv_build"
|
|
53
|
+
|
|
54
|
+
[dependency-groups]
|
|
55
|
+
dev = [
|
|
56
|
+
"pytest>=8.0",
|
|
57
|
+
"pytest-cov>=5.0",
|
|
58
|
+
"hypothesis>=6.100",
|
|
59
|
+
"ruff>=0.4",
|
|
60
|
+
"mypy>=1.10",
|
|
61
|
+
"pandas-stubs>=2.1",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
line-length = 100
|
|
66
|
+
target-version = "py311"
|
|
67
|
+
|
|
68
|
+
[tool.ruff.lint]
|
|
69
|
+
select = [
|
|
70
|
+
"E",
|
|
71
|
+
"F",
|
|
72
|
+
"I",
|
|
73
|
+
"UP",
|
|
74
|
+
"B",
|
|
75
|
+
"SIM",
|
|
76
|
+
]
|
|
77
|
+
ignore = ["E501"]
|
|
78
|
+
|
|
79
|
+
[tool.mypy]
|
|
80
|
+
python_version = "3.11"
|
|
81
|
+
strict = true
|
|
82
|
+
ignore_missing_imports = true
|
|
83
|
+
|
|
84
|
+
[tool.pytest.ini_options]
|
|
85
|
+
testpaths = ["tests"]
|
|
86
|
+
markers = ["slow: marks tests as slow (deselect with -m 'not slow')"]
|
|
87
|
+
addopts = "--tb=short"
|
|
88
|
+
|
|
89
|
+
[tool.coverage.run]
|
|
90
|
+
source = ["src/agent_attrib"]
|
|
91
|
+
omit = ["*/cli.py"]
|
|
92
|
+
|
|
93
|
+
[tool.coverage.report]
|
|
94
|
+
exclude_lines = [
|
|
95
|
+
"pragma: no cover",
|
|
96
|
+
"if TYPE_CHECKING:",
|
|
97
|
+
]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "agent-attrib"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Multi-factor attribution for agent release changes using factorial design of experiments"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
authors = [{ name = "agent-attrib contributors" }]
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
keywords = ["agents", "llm", "attribution", "factorial", "design-of-experiments", "statistics"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 3 - Alpha",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"Intended Audience :: Science/Research",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"numpy>=1.26",
|
|
24
|
+
"pandas>=2.1",
|
|
25
|
+
"scipy>=1.11",
|
|
26
|
+
"statsmodels>=0.14",
|
|
27
|
+
"pydantic>=2.5",
|
|
28
|
+
"typer>=0.9",
|
|
29
|
+
"rich>=13.7",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
agent-attrib = "agent_attrib.cli:app"
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/agent-attrib/agent-attrib"
|
|
37
|
+
Issues = "https://github.com/agent-attrib/agent-attrib/issues"
|
|
38
|
+
|
|
39
|
+
[build-system]
|
|
40
|
+
requires = ["uv_build>=0.13.7,<0.14.0"]
|
|
41
|
+
build-backend = "uv_build"
|
|
42
|
+
|
|
43
|
+
[dependency-groups]
|
|
44
|
+
dev = [
|
|
45
|
+
"pytest>=8.0",
|
|
46
|
+
"pytest-cov>=5.0",
|
|
47
|
+
"hypothesis>=6.100",
|
|
48
|
+
"ruff>=0.4",
|
|
49
|
+
"mypy>=1.10",
|
|
50
|
+
"pandas-stubs>=2.1",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[tool.ruff]
|
|
54
|
+
line-length = 100
|
|
55
|
+
target-version = "py311"
|
|
56
|
+
|
|
57
|
+
[tool.ruff.lint]
|
|
58
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
59
|
+
ignore = ["E501"]
|
|
60
|
+
|
|
61
|
+
[tool.mypy]
|
|
62
|
+
python_version = "3.11"
|
|
63
|
+
strict = true
|
|
64
|
+
ignore_missing_imports = true
|
|
65
|
+
|
|
66
|
+
[tool.pytest.ini_options]
|
|
67
|
+
testpaths = ["tests"]
|
|
68
|
+
markers = ["slow: marks tests as slow (deselect with -m 'not slow')"]
|
|
69
|
+
addopts = "--tb=short"
|
|
70
|
+
|
|
71
|
+
[tool.coverage.run]
|
|
72
|
+
source = ["src/agent_attrib"]
|
|
73
|
+
omit = ["*/cli.py"]
|
|
74
|
+
|
|
75
|
+
[tool.coverage.report]
|
|
76
|
+
exclude_lines = ["pragma: no cover", "if TYPE_CHECKING:"]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""agent-attrib: multi-factor attribution for agent release changes."""
|
|
2
|
+
|
|
3
|
+
from .model import MetricResult, analyze_runset
|
|
4
|
+
from .schema import Design, Run, RunSet, load_runs
|
|
5
|
+
|
|
6
|
+
__version__ = "0.1.0"
|
|
7
|
+
__all__ = [
|
|
8
|
+
"plan",
|
|
9
|
+
"analyze",
|
|
10
|
+
"load_runs",
|
|
11
|
+
"RunSet",
|
|
12
|
+
"Run",
|
|
13
|
+
"Design",
|
|
14
|
+
"MetricResult",
|
|
15
|
+
"__version__",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def plan(
|
|
20
|
+
factors: list[str],
|
|
21
|
+
tasks: int = 30,
|
|
22
|
+
trials: int = 2,
|
|
23
|
+
seed: int = 42,
|
|
24
|
+
resolution: str = "IV",
|
|
25
|
+
sd: float | None = None,
|
|
26
|
+
) -> "Design":
|
|
27
|
+
"""Generate a factorial experiment design.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
factors: list of factor names.
|
|
31
|
+
tasks: number of distinct tasks.
|
|
32
|
+
trials: trials per configuration per task.
|
|
33
|
+
seed: random seed for run-order randomization.
|
|
34
|
+
resolution: minimum design resolution for fractional designs ("IV" or "V").
|
|
35
|
+
sd: assumed SD for MDE calculation on continuous metrics.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Design object with run order and summary statistics.
|
|
39
|
+
"""
|
|
40
|
+
from .design import build_design, generate_run_order
|
|
41
|
+
from .power import mde_binary, mde_continuous
|
|
42
|
+
from .schema import Design, DesignRow
|
|
43
|
+
|
|
44
|
+
dm_matrix, dm = build_design(factors, tasks=tasks, trials=trials, seed=seed, resolution=resolution)
|
|
45
|
+
task_ids = [f"t{i:04d}" for i in range(tasks)]
|
|
46
|
+
df = generate_run_order(dm_matrix, factors, task_ids, trials, seed)
|
|
47
|
+
|
|
48
|
+
n_configs = dm_matrix.shape[0]
|
|
49
|
+
n_total = n_configs * tasks * trials
|
|
50
|
+
|
|
51
|
+
mde_val = mde_continuous(n_configs, tasks, trials, sd=sd) if sd else mde_binary(n_configs, tasks, trials)
|
|
52
|
+
|
|
53
|
+
rows: list[DesignRow] = []
|
|
54
|
+
for _, row in df.iterrows():
|
|
55
|
+
factor_levels = {f: str(row[f"f_{f}"]) for f in factors}
|
|
56
|
+
rows.append(
|
|
57
|
+
DesignRow(
|
|
58
|
+
config_id=str(row["config_id"]),
|
|
59
|
+
task_id=str(row["task_id"]),
|
|
60
|
+
trial=int(row["trial"]),
|
|
61
|
+
run_order=int(row["run_order"]),
|
|
62
|
+
factors=factor_levels, # type: ignore[arg-type]
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return Design(
|
|
67
|
+
factors=factors,
|
|
68
|
+
tasks=tasks,
|
|
69
|
+
trials=trials,
|
|
70
|
+
seed=seed,
|
|
71
|
+
design_type=dm.design_type,
|
|
72
|
+
resolution=dm.resolution,
|
|
73
|
+
alias_structure=dm.alias_structure,
|
|
74
|
+
runs=rows,
|
|
75
|
+
n_configs=n_configs,
|
|
76
|
+
n_total=n_total,
|
|
77
|
+
mde=mde_val,
|
|
78
|
+
mde_metric="success (binary, p=0.5)" if sd is None else "continuous",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def analyze(
|
|
83
|
+
runset: "RunSet",
|
|
84
|
+
metrics: list[str] | None = None,
|
|
85
|
+
margin: float = 0.05,
|
|
86
|
+
binary_metrics: list[str] | None = None,
|
|
87
|
+
) -> "AnalysisResult":
|
|
88
|
+
"""Analyze a RunSet and return an AnalysisResult.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
runset: loaded runs from load_runs().
|
|
92
|
+
metrics: metrics to analyze (default: all in runset).
|
|
93
|
+
margin: equivalence margin for EQUIVALENT/INCONCLUSIVE verdicts.
|
|
94
|
+
binary_metrics: metric names to treat as binary (default: ["success"]).
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
AnalysisResult with per-metric results and reporting methods.
|
|
98
|
+
"""
|
|
99
|
+
metrics = metrics or runset.metrics
|
|
100
|
+
results = analyze_runset(runset, metrics=metrics, margin=margin, binary_metrics=binary_metrics)
|
|
101
|
+
return AnalysisResult(results)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class AnalysisResult:
|
|
105
|
+
"""Container for per-metric analysis results with reporting methods."""
|
|
106
|
+
|
|
107
|
+
def __init__(self, results: list["MetricResult"]) -> None:
|
|
108
|
+
self.results = results
|
|
109
|
+
|
|
110
|
+
def to_markdown(self) -> str:
|
|
111
|
+
from .report import to_markdown
|
|
112
|
+
return to_markdown(self.results)
|
|
113
|
+
|
|
114
|
+
def to_json(self) -> str:
|
|
115
|
+
from .report import to_json
|
|
116
|
+
return to_json(self.results)
|
|
117
|
+
|
|
118
|
+
def print(self) -> None:
|
|
119
|
+
from .report import print_rich
|
|
120
|
+
print_rich(self.results)
|
|
121
|
+
|
|
122
|
+
def __repr__(self) -> str:
|
|
123
|
+
metrics = [r.metric for r in self.results]
|
|
124
|
+
return f"AnalysisResult(metrics={metrics})"
|