mechbench-schema 0.13.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.
- mechbench_schema-0.13.0/PKG-INFO +157 -0
- mechbench_schema-0.13.0/README.md +133 -0
- mechbench_schema-0.13.0/pyproject.toml +36 -0
- mechbench_schema-0.13.0/setup.cfg +4 -0
- mechbench_schema-0.13.0/src/mechbench_schema/__init__.py +213 -0
- mechbench_schema-0.13.0/src/mechbench_schema/attention_trace.py +81 -0
- mechbench_schema-0.13.0/src/mechbench_schema/cluster_data.py +136 -0
- mechbench_schema-0.13.0/src/mechbench_schema/codec_cbor.py +127 -0
- mechbench_schema-0.13.0/src/mechbench_schema/document_data.py +238 -0
- mechbench_schema-0.13.0/src/mechbench_schema/identity.py +334 -0
- mechbench_schema-0.13.0/src/mechbench_schema/metric_data.py +52 -0
- mechbench_schema-0.13.0/src/mechbench_schema/per_head_data.py +103 -0
- mechbench_schema-0.13.0/src/mechbench_schema/per_layer_data.py +175 -0
- mechbench_schema-0.13.0/src/mechbench_schema/per_layer_per_position_data.py +166 -0
- mechbench_schema-0.13.0/src/mechbench_schema/provenance.py +142 -0
- mechbench_schema-0.13.0/src/mechbench_schema/vector_data.py +171 -0
- mechbench_schema-0.13.0/src/mechbench_schema.egg-info/PKG-INFO +157 -0
- mechbench_schema-0.13.0/src/mechbench_schema.egg-info/SOURCES.txt +20 -0
- mechbench_schema-0.13.0/src/mechbench_schema.egg-info/dependency_links.txt +1 -0
- mechbench_schema-0.13.0/src/mechbench_schema.egg-info/requires.txt +9 -0
- mechbench_schema-0.13.0/src/mechbench_schema.egg-info/top_level.txt +1 -0
- mechbench_schema-0.13.0/tests/test_codec_cbor.py +64 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mechbench-schema
|
|
3
|
+
Version: 0.13.0
|
|
4
|
+
Summary: Typed emission contract for the mechbench family. Pydantic models as single source of truth; generates TypeScript bindings for mechbench-ui.
|
|
5
|
+
Author-email: Benji Smith <benji@shaxpir.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mechbench/mechbench-schema
|
|
8
|
+
Project-URL: Website, https://mechbench.ai
|
|
9
|
+
Keywords: interpretability,schema,pydantic,mechbench
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: pydantic>=2.6
|
|
18
|
+
Requires-Dist: cbor2>=5.6
|
|
19
|
+
Provides-Extra: codegen
|
|
20
|
+
Requires-Dist: datamodel-code-generator[typescript]>=0.25; extra == "codegen"
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
23
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
24
|
+
|
|
25
|
+
# mechbench-schema
|
|
26
|
+
|
|
27
|
+
The typed emission contract for the [mechbench](https://mechbench.ai) family.
|
|
28
|
+
|
|
29
|
+
**Pydantic models are the single source of truth.** TypeScript bindings are generated from them; both halves are published from this one repo to two package registries.
|
|
30
|
+
|
|
31
|
+
| Consumer | Registry | Package |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| Python (core, agent, remote, experiments) | PyPI | `pip install mechbench-schema` |
|
|
34
|
+
| TypeScript (ui, skills) | npm | `npm install mechbench-schema` |
|
|
35
|
+
|
|
36
|
+
The two packages share a name and a version. CI regenerates the TS bindings and fails on drift, so the two halves cannot disagree.
|
|
37
|
+
|
|
38
|
+
## Organization: by domain axis, not by consumer
|
|
39
|
+
|
|
40
|
+
Modules are named after the **indexing axis** of the records they carry, not after who renders them. This keeps the schema from becoming "chart data" just because charts are the current biggest consumer; the same records drive CSV exports, agent tool surfaces, the memoization cache, and anything else downstream.
|
|
41
|
+
|
|
42
|
+
| module | category | archetypal records |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `per_layer_data` | indexed by `layer` | ablation damage, DLA diffs, convergence summaries |
|
|
45
|
+
| `per_head_data` | indexed by `(layer, head)` | per-head DLA, OV rank-0 singular values |
|
|
46
|
+
| `attention_trace` | indexed by `(layer, head, pos_from, pos_to)` | attention patterns |
|
|
47
|
+
| `per_layer_per_position_data` | indexed by `(layer, position)` | logit-lens trajectories, causal-trace grids |
|
|
48
|
+
| `vector_data` | atomic: one direction in residual space | captured / steering / probe / centroid vectors |
|
|
49
|
+
| `cluster_data` | collection of vectors with aggregate stats | named clusters, cross-cluster statistics |
|
|
50
|
+
| `identity` | path grammar | `MechbenchPath` validated-string type |
|
|
51
|
+
|
|
52
|
+
Every payload model is part of a discriminated union keyed by `kind` (or `origin` for vectors), so the UI can dispatch rendering on the tag and the DAG solver can reason about compatible outputs.
|
|
53
|
+
|
|
54
|
+
## Repo layout
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
mechbench-schema/
|
|
58
|
+
├── src/mechbench_schema/ # Pydantic models — the source of truth
|
|
59
|
+
│ ├── __init__.py # __all__ + __schema_all__
|
|
60
|
+
│ ├── attention_trace.py
|
|
61
|
+
│ ├── cluster_data.py
|
|
62
|
+
│ ├── identity.py
|
|
63
|
+
│ ├── per_head_data.py
|
|
64
|
+
│ ├── per_layer_data.py
|
|
65
|
+
│ ├── per_layer_per_position_data.py
|
|
66
|
+
│ └── vector_data.py
|
|
67
|
+
├── ts/ # published to npm as "mechbench-schema"
|
|
68
|
+
│ ├── package.json
|
|
69
|
+
│ ├── tsconfig.json
|
|
70
|
+
│ └── src/
|
|
71
|
+
│ ├── index.ts
|
|
72
|
+
│ ├── generated.ts # codegen output — DO NOT EDIT
|
|
73
|
+
│ └── schema.json # consolidated JSON Schema — codegen output
|
|
74
|
+
├── scripts/
|
|
75
|
+
│ └── codegen.py # Pydantic → JSON Schema → TypeScript
|
|
76
|
+
├── pyproject.toml # published to PyPI as "mechbench-schema"
|
|
77
|
+
└── README.md
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Editing the contract
|
|
81
|
+
|
|
82
|
+
1. Edit an existing module under `src/mechbench_schema/`, or add a new module (following the domain-axis convention).
|
|
83
|
+
2. Export the new type from `src/mechbench_schema/__init__.py`. Add it to `__all__` for Python consumers and to `__schema_all__` for codegen (wire types only).
|
|
84
|
+
3. Run `python scripts/codegen.py`. This writes `ts/src/schema.json` and `ts/src/generated.ts`.
|
|
85
|
+
4. Commit everything in one PR — Python source, generated JSON Schema, generated TypeScript.
|
|
86
|
+
|
|
87
|
+
CI runs codegen and `git diff --exit-code ts/src/schema.json ts/src/generated.ts`. A drifted PR cannot land.
|
|
88
|
+
|
|
89
|
+
The codegen uses `json-schema-to-typescript` (via `npx`) for the TS emission. Python → JSON Schema comes from Pydantic's built-in `model_json_schema()` (for `BaseModel` subclasses) and `TypeAdapter(...).json_schema()` (for type aliases like the discriminated unions).
|
|
90
|
+
|
|
91
|
+
## Install (Python)
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pip install mechbench-schema
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from mechbench_schema import LayerAblationPayload, DlaSweepPayload
|
|
99
|
+
|
|
100
|
+
payload = LayerAblationPayload(
|
|
101
|
+
experiment="step_02_layer_ablation",
|
|
102
|
+
description="Per-layer ablation damage on FACTUAL_15.",
|
|
103
|
+
model="mlx-community/gemma-4-E4B-it-bf16",
|
|
104
|
+
n_layers=42,
|
|
105
|
+
global_layers=[5, 11, 17, 23, 29, 35, 41],
|
|
106
|
+
prompts=[...],
|
|
107
|
+
aggregates=LayerAggregates(mean=[...], median=[...]),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Emit to JSON — mode="json" canonicalizes datetimes, bytes, etc.
|
|
111
|
+
import json
|
|
112
|
+
path.write_text(json.dumps(payload.model_dump(mode="json"), indent=2))
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Install (TypeScript)
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npm install mechbench-schema
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import type { LayerAblationPayload, Vector, MechbenchPath } from "mechbench-schema";
|
|
123
|
+
|
|
124
|
+
const payload: LayerAblationPayload = JSON.parse(text);
|
|
125
|
+
// All fields, discriminated unions, and nested types come from the generated bindings.
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Identity grammar
|
|
129
|
+
|
|
130
|
+
`MechbenchPath` is the type used to address every object in the mechbench family — charts, articles, experiments, corpuses, probes, cached DAG intermediates. Five categories:
|
|
131
|
+
|
|
132
|
+
- `<owner>/<project>/<folders>/<leaf>` — user-named
|
|
133
|
+
- `~canonical/<area>/<path>/<leaf>` — human-readable aliases for globally-shared content
|
|
134
|
+
- `~system/<area>/<path>/<leaf>` — platform / first-party
|
|
135
|
+
- `~hash/<algo>:<digest>` — global content-hashed (deduplicated across users)
|
|
136
|
+
- `<owner>/<project>/~hash/<algo>:<digest>` — workspace-scoped content-hashed
|
|
137
|
+
|
|
138
|
+
Full spec in the meta repo's IDENTITY_AND_NAMESPACING.md. The Python side (`mechbench_schema.identity`) owns the authoritative validator; a mirror implementation lives in `mechbench-ui/src/lib/mechbenchPath.ts` for UI-side early failure.
|
|
139
|
+
|
|
140
|
+
## Why one repo, two publications
|
|
141
|
+
|
|
142
|
+
Schemas that live in two repos drift. The only invariant that matters — "Python and TS agree on the shape" — is enforced by keeping the source in one place and generating the target. Consumers never need the other language's toolchain to install; `pip` and `npm` each resolve to a clean single-language package.
|
|
143
|
+
|
|
144
|
+
See [the family overview](https://mechbench.ai) for the rationale behind this and other multi-repo decisions.
|
|
145
|
+
|
|
146
|
+
## Status
|
|
147
|
+
|
|
148
|
+
Version 0.8.0. Seven modules in active use; the legacy `records.py` holding pen was retired when the domain-axis reorg completed. `mechbench-experiments`' two exporters (`step_02_layer_ablation`, `step_33_dla_factual_sweep`) emit via Pydantic models; `mechbench-ui`'s chart interfaces are one-line aliases over the generated TS types.
|
|
149
|
+
|
|
150
|
+
Open work lives in the meta repo's `tasks/mechbench-schema/` directory. The two notable open epics downstream of this repo:
|
|
151
|
+
|
|
152
|
+
- **`000161`** — compact binary formats for records at rest and in transit (safetensors for tensor-bulk, parquet for record-collections).
|
|
153
|
+
- **`000163`** — the identity-and-namespacing epic that produced `MechbenchPath`; Phase 4 (content-addressing grammar) remains open and coordinates with `000162` (the DAG-solver epic in `mechbench-compute`).
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# mechbench-schema
|
|
2
|
+
|
|
3
|
+
The typed emission contract for the [mechbench](https://mechbench.ai) family.
|
|
4
|
+
|
|
5
|
+
**Pydantic models are the single source of truth.** TypeScript bindings are generated from them; both halves are published from this one repo to two package registries.
|
|
6
|
+
|
|
7
|
+
| Consumer | Registry | Package |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| Python (core, agent, remote, experiments) | PyPI | `pip install mechbench-schema` |
|
|
10
|
+
| TypeScript (ui, skills) | npm | `npm install mechbench-schema` |
|
|
11
|
+
|
|
12
|
+
The two packages share a name and a version. CI regenerates the TS bindings and fails on drift, so the two halves cannot disagree.
|
|
13
|
+
|
|
14
|
+
## Organization: by domain axis, not by consumer
|
|
15
|
+
|
|
16
|
+
Modules are named after the **indexing axis** of the records they carry, not after who renders them. This keeps the schema from becoming "chart data" just because charts are the current biggest consumer; the same records drive CSV exports, agent tool surfaces, the memoization cache, and anything else downstream.
|
|
17
|
+
|
|
18
|
+
| module | category | archetypal records |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `per_layer_data` | indexed by `layer` | ablation damage, DLA diffs, convergence summaries |
|
|
21
|
+
| `per_head_data` | indexed by `(layer, head)` | per-head DLA, OV rank-0 singular values |
|
|
22
|
+
| `attention_trace` | indexed by `(layer, head, pos_from, pos_to)` | attention patterns |
|
|
23
|
+
| `per_layer_per_position_data` | indexed by `(layer, position)` | logit-lens trajectories, causal-trace grids |
|
|
24
|
+
| `vector_data` | atomic: one direction in residual space | captured / steering / probe / centroid vectors |
|
|
25
|
+
| `cluster_data` | collection of vectors with aggregate stats | named clusters, cross-cluster statistics |
|
|
26
|
+
| `identity` | path grammar | `MechbenchPath` validated-string type |
|
|
27
|
+
|
|
28
|
+
Every payload model is part of a discriminated union keyed by `kind` (or `origin` for vectors), so the UI can dispatch rendering on the tag and the DAG solver can reason about compatible outputs.
|
|
29
|
+
|
|
30
|
+
## Repo layout
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
mechbench-schema/
|
|
34
|
+
├── src/mechbench_schema/ # Pydantic models — the source of truth
|
|
35
|
+
│ ├── __init__.py # __all__ + __schema_all__
|
|
36
|
+
│ ├── attention_trace.py
|
|
37
|
+
│ ├── cluster_data.py
|
|
38
|
+
│ ├── identity.py
|
|
39
|
+
│ ├── per_head_data.py
|
|
40
|
+
│ ├── per_layer_data.py
|
|
41
|
+
│ ├── per_layer_per_position_data.py
|
|
42
|
+
│ └── vector_data.py
|
|
43
|
+
├── ts/ # published to npm as "mechbench-schema"
|
|
44
|
+
│ ├── package.json
|
|
45
|
+
│ ├── tsconfig.json
|
|
46
|
+
│ └── src/
|
|
47
|
+
│ ├── index.ts
|
|
48
|
+
│ ├── generated.ts # codegen output — DO NOT EDIT
|
|
49
|
+
│ └── schema.json # consolidated JSON Schema — codegen output
|
|
50
|
+
├── scripts/
|
|
51
|
+
│ └── codegen.py # Pydantic → JSON Schema → TypeScript
|
|
52
|
+
├── pyproject.toml # published to PyPI as "mechbench-schema"
|
|
53
|
+
└── README.md
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Editing the contract
|
|
57
|
+
|
|
58
|
+
1. Edit an existing module under `src/mechbench_schema/`, or add a new module (following the domain-axis convention).
|
|
59
|
+
2. Export the new type from `src/mechbench_schema/__init__.py`. Add it to `__all__` for Python consumers and to `__schema_all__` for codegen (wire types only).
|
|
60
|
+
3. Run `python scripts/codegen.py`. This writes `ts/src/schema.json` and `ts/src/generated.ts`.
|
|
61
|
+
4. Commit everything in one PR — Python source, generated JSON Schema, generated TypeScript.
|
|
62
|
+
|
|
63
|
+
CI runs codegen and `git diff --exit-code ts/src/schema.json ts/src/generated.ts`. A drifted PR cannot land.
|
|
64
|
+
|
|
65
|
+
The codegen uses `json-schema-to-typescript` (via `npx`) for the TS emission. Python → JSON Schema comes from Pydantic's built-in `model_json_schema()` (for `BaseModel` subclasses) and `TypeAdapter(...).json_schema()` (for type aliases like the discriminated unions).
|
|
66
|
+
|
|
67
|
+
## Install (Python)
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install mechbench-schema
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from mechbench_schema import LayerAblationPayload, DlaSweepPayload
|
|
75
|
+
|
|
76
|
+
payload = LayerAblationPayload(
|
|
77
|
+
experiment="step_02_layer_ablation",
|
|
78
|
+
description="Per-layer ablation damage on FACTUAL_15.",
|
|
79
|
+
model="mlx-community/gemma-4-E4B-it-bf16",
|
|
80
|
+
n_layers=42,
|
|
81
|
+
global_layers=[5, 11, 17, 23, 29, 35, 41],
|
|
82
|
+
prompts=[...],
|
|
83
|
+
aggregates=LayerAggregates(mean=[...], median=[...]),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Emit to JSON — mode="json" canonicalizes datetimes, bytes, etc.
|
|
87
|
+
import json
|
|
88
|
+
path.write_text(json.dumps(payload.model_dump(mode="json"), indent=2))
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Install (TypeScript)
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npm install mechbench-schema
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import type { LayerAblationPayload, Vector, MechbenchPath } from "mechbench-schema";
|
|
99
|
+
|
|
100
|
+
const payload: LayerAblationPayload = JSON.parse(text);
|
|
101
|
+
// All fields, discriminated unions, and nested types come from the generated bindings.
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Identity grammar
|
|
105
|
+
|
|
106
|
+
`MechbenchPath` is the type used to address every object in the mechbench family — charts, articles, experiments, corpuses, probes, cached DAG intermediates. Five categories:
|
|
107
|
+
|
|
108
|
+
- `<owner>/<project>/<folders>/<leaf>` — user-named
|
|
109
|
+
- `~canonical/<area>/<path>/<leaf>` — human-readable aliases for globally-shared content
|
|
110
|
+
- `~system/<area>/<path>/<leaf>` — platform / first-party
|
|
111
|
+
- `~hash/<algo>:<digest>` — global content-hashed (deduplicated across users)
|
|
112
|
+
- `<owner>/<project>/~hash/<algo>:<digest>` — workspace-scoped content-hashed
|
|
113
|
+
|
|
114
|
+
Full spec in the meta repo's IDENTITY_AND_NAMESPACING.md. The Python side (`mechbench_schema.identity`) owns the authoritative validator; a mirror implementation lives in `mechbench-ui/src/lib/mechbenchPath.ts` for UI-side early failure.
|
|
115
|
+
|
|
116
|
+
## Why one repo, two publications
|
|
117
|
+
|
|
118
|
+
Schemas that live in two repos drift. The only invariant that matters — "Python and TS agree on the shape" — is enforced by keeping the source in one place and generating the target. Consumers never need the other language's toolchain to install; `pip` and `npm` each resolve to a clean single-language package.
|
|
119
|
+
|
|
120
|
+
See [the family overview](https://mechbench.ai) for the rationale behind this and other multi-repo decisions.
|
|
121
|
+
|
|
122
|
+
## Status
|
|
123
|
+
|
|
124
|
+
Version 0.8.0. Seven modules in active use; the legacy `records.py` holding pen was retired when the domain-axis reorg completed. `mechbench-experiments`' two exporters (`step_02_layer_ablation`, `step_33_dla_factual_sweep`) emit via Pydantic models; `mechbench-ui`'s chart interfaces are one-line aliases over the generated TS types.
|
|
125
|
+
|
|
126
|
+
Open work lives in the meta repo's `tasks/mechbench-schema/` directory. The two notable open epics downstream of this repo:
|
|
127
|
+
|
|
128
|
+
- **`000161`** — compact binary formats for records at rest and in transit (safetensors for tensor-bulk, parquet for record-collections).
|
|
129
|
+
- **`000163`** — the identity-and-namespacing epic that produced `MechbenchPath`; Phase 4 (content-addressing grammar) remains open and coordinates with `000162` (the DAG-solver epic in `mechbench-compute`).
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mechbench-schema"
|
|
7
|
+
version = "0.13.0"
|
|
8
|
+
description = "Typed emission contract for the mechbench family. Pydantic models as single source of truth; generates TypeScript bindings for mechbench-ui."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Benji Smith", email = "benji@shaxpir.com" }]
|
|
13
|
+
keywords = ["interpretability", "schema", "pydantic", "mechbench"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Topic :: Software Development :: Libraries",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"pydantic>=2.6",
|
|
23
|
+
"cbor2>=5.6",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
codegen = ["datamodel-code-generator[typescript]>=0.25"]
|
|
28
|
+
dev = ["pytest>=8", "ruff>=0.5"]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/mechbench/mechbench-schema"
|
|
32
|
+
Website = "https://mechbench.ai"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
36
|
+
include = ["mechbench_schema*"]
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""mechbench-schema — typed emission contract for the mechbench family.
|
|
2
|
+
|
|
3
|
+
This package is the single source of truth for every interpretability-record
|
|
4
|
+
shape that crosses a repo boundary. Python consumers (mechbench-compute,
|
|
5
|
+
mechbench-runner, mechbench-experiments) import types from
|
|
6
|
+
here; TypeScript consumers (mechbench-ui) import types from the parallel
|
|
7
|
+
mechbench-schema npm package, which is generated from these Pydantic models
|
|
8
|
+
via scripts/codegen.py.
|
|
9
|
+
|
|
10
|
+
Rule of thumb: if the shape is ever serialized to disk, sent over the wire,
|
|
11
|
+
or rendered by a non-Python consumer, it belongs here. If the shape is only
|
|
12
|
+
used as an in-memory container within a single repo, it does not.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from .attention_trace import (
|
|
16
|
+
AttentionPattern,
|
|
17
|
+
AttentionTraceData,
|
|
18
|
+
)
|
|
19
|
+
from .codec_cbor import (
|
|
20
|
+
dump_canonical,
|
|
21
|
+
load_canonical,
|
|
22
|
+
load_raw,
|
|
23
|
+
)
|
|
24
|
+
from .identity import (
|
|
25
|
+
InvalidPathError,
|
|
26
|
+
MechbenchPath,
|
|
27
|
+
ParsedPath,
|
|
28
|
+
PathCategory,
|
|
29
|
+
make_canonical_path,
|
|
30
|
+
make_global_hash_path,
|
|
31
|
+
make_platform_path,
|
|
32
|
+
make_scoped_hash_path,
|
|
33
|
+
make_user_path,
|
|
34
|
+
parse_path,
|
|
35
|
+
)
|
|
36
|
+
from .cluster_data import (
|
|
37
|
+
Cluster,
|
|
38
|
+
ClusterSet,
|
|
39
|
+
)
|
|
40
|
+
from .per_head_data import (
|
|
41
|
+
PerHeadBase,
|
|
42
|
+
PerHeadData,
|
|
43
|
+
PerHeadScalarGrid,
|
|
44
|
+
)
|
|
45
|
+
from .document_data import (
|
|
46
|
+
AnnotationAnchor,
|
|
47
|
+
AnnotationLayer,
|
|
48
|
+
AnnotationValue,
|
|
49
|
+
BASE_KIND_ANNOTATED_TOKENS,
|
|
50
|
+
BASE_KIND_CONVERSATION,
|
|
51
|
+
BASE_KIND_TEXT,
|
|
52
|
+
DocumentCollection,
|
|
53
|
+
DocumentItem,
|
|
54
|
+
DocumentPayload,
|
|
55
|
+
GenerationSpan,
|
|
56
|
+
KindManifest,
|
|
57
|
+
RendererBinding,
|
|
58
|
+
Segment,
|
|
59
|
+
Segmentation,
|
|
60
|
+
Trace,
|
|
61
|
+
Turn,
|
|
62
|
+
)
|
|
63
|
+
from .metric_data import (
|
|
64
|
+
MetricColumn,
|
|
65
|
+
MetricTable,
|
|
66
|
+
)
|
|
67
|
+
from .provenance import (
|
|
68
|
+
Emitted,
|
|
69
|
+
Fidelity,
|
|
70
|
+
Provenance,
|
|
71
|
+
ToolInfo,
|
|
72
|
+
fidelity_satisfies,
|
|
73
|
+
fingerprint_params,
|
|
74
|
+
)
|
|
75
|
+
from .per_layer_data import (
|
|
76
|
+
AblationPrompt,
|
|
77
|
+
ConvergencePayload,
|
|
78
|
+
ConvergenceRow,
|
|
79
|
+
DlaPrompt,
|
|
80
|
+
DlaSweepPayload,
|
|
81
|
+
LayerAblationPayload,
|
|
82
|
+
LayerAggregates,
|
|
83
|
+
PerLayerBase,
|
|
84
|
+
PerLayerData,
|
|
85
|
+
)
|
|
86
|
+
from .per_layer_per_position_data import (
|
|
87
|
+
LogitLensTrajectory,
|
|
88
|
+
PerLayerPerPositionBase,
|
|
89
|
+
PerLayerPerPositionData,
|
|
90
|
+
)
|
|
91
|
+
from .vector_data import (
|
|
92
|
+
CapturedVector,
|
|
93
|
+
CentroidVector,
|
|
94
|
+
HookKind,
|
|
95
|
+
ProbeVector,
|
|
96
|
+
SteeringVector,
|
|
97
|
+
Vector,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
__version__ = "0.12.0"
|
|
101
|
+
|
|
102
|
+
__all__ = [
|
|
103
|
+
# Document collections + annotations + kind manifests (task 000239)
|
|
104
|
+
"DocumentCollection",
|
|
105
|
+
"DocumentItem",
|
|
106
|
+
"DocumentPayload",
|
|
107
|
+
"Trace",
|
|
108
|
+
"GenerationSpan",
|
|
109
|
+
"Segmentation",
|
|
110
|
+
"Segment",
|
|
111
|
+
"Turn",
|
|
112
|
+
"AnnotationLayer",
|
|
113
|
+
"AnnotationValue",
|
|
114
|
+
"AnnotationAnchor",
|
|
115
|
+
"KindManifest",
|
|
116
|
+
"RendererBinding",
|
|
117
|
+
"BASE_KIND_TEXT",
|
|
118
|
+
"BASE_KIND_CONVERSATION",
|
|
119
|
+
"BASE_KIND_ANNOTATED_TOKENS",
|
|
120
|
+
# Metric tables (task 000244)
|
|
121
|
+
"MetricTable",
|
|
122
|
+
"MetricColumn",
|
|
123
|
+
# Provenance + fidelity (the emission envelope, task 000237)
|
|
124
|
+
"Provenance",
|
|
125
|
+
"ToolInfo",
|
|
126
|
+
"Emitted",
|
|
127
|
+
"Fidelity",
|
|
128
|
+
"fidelity_satisfies",
|
|
129
|
+
"fingerprint_params",
|
|
130
|
+
# Per-layer data (records indexed by transformer layer)
|
|
131
|
+
"PerLayerBase",
|
|
132
|
+
"LayerAggregates",
|
|
133
|
+
"AblationPrompt",
|
|
134
|
+
"LayerAblationPayload",
|
|
135
|
+
"DlaPrompt",
|
|
136
|
+
"DlaSweepPayload",
|
|
137
|
+
"ConvergenceRow",
|
|
138
|
+
"ConvergencePayload",
|
|
139
|
+
"PerLayerData",
|
|
140
|
+
# Per-head data (records indexed by (layer, head))
|
|
141
|
+
"PerHeadBase",
|
|
142
|
+
"PerHeadScalarGrid",
|
|
143
|
+
"PerHeadData",
|
|
144
|
+
# Attention-trace data (records indexed by (layer, head, position, position))
|
|
145
|
+
"AttentionPattern",
|
|
146
|
+
"AttentionTraceData",
|
|
147
|
+
# Vector data (atomic directions in residual-stream space)
|
|
148
|
+
"CapturedVector",
|
|
149
|
+
"SteeringVector",
|
|
150
|
+
"ProbeVector",
|
|
151
|
+
"CentroidVector",
|
|
152
|
+
"HookKind",
|
|
153
|
+
"Vector",
|
|
154
|
+
# Cluster data (collections of vectors with aggregate stats)
|
|
155
|
+
"Cluster",
|
|
156
|
+
"ClusterSet",
|
|
157
|
+
# Per-(layer, position) data (residual-stream-axis records)
|
|
158
|
+
"PerLayerPerPositionBase",
|
|
159
|
+
"LogitLensTrajectory",
|
|
160
|
+
"PerLayerPerPositionData",
|
|
161
|
+
# Identity / namespacing (see docs/IDENTITY_AND_NAMESPACING.md)
|
|
162
|
+
"MechbenchPath",
|
|
163
|
+
"ParsedPath",
|
|
164
|
+
"PathCategory",
|
|
165
|
+
"InvalidPathError",
|
|
166
|
+
"parse_path",
|
|
167
|
+
"make_user_path",
|
|
168
|
+
"make_canonical_path",
|
|
169
|
+
"make_platform_path",
|
|
170
|
+
"make_global_hash_path",
|
|
171
|
+
"make_scoped_hash_path",
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
# Names that should be emitted to TS via codegen. Subset of __all__ — excludes
|
|
175
|
+
# Python-side utilities (functions, exceptions, non-Pydantic dataclasses)
|
|
176
|
+
# that don't cross the wire.
|
|
177
|
+
__schema_all__ = [
|
|
178
|
+
# Per-layer data
|
|
179
|
+
"PerLayerBase",
|
|
180
|
+
"LayerAggregates",
|
|
181
|
+
"AblationPrompt",
|
|
182
|
+
"LayerAblationPayload",
|
|
183
|
+
"DlaPrompt",
|
|
184
|
+
"DlaSweepPayload",
|
|
185
|
+
"ConvergenceRow",
|
|
186
|
+
"ConvergencePayload",
|
|
187
|
+
"PerLayerData",
|
|
188
|
+
# Per-head data
|
|
189
|
+
"PerHeadBase",
|
|
190
|
+
"PerHeadScalarGrid",
|
|
191
|
+
"PerHeadData",
|
|
192
|
+
# Attention-trace data
|
|
193
|
+
"AttentionPattern",
|
|
194
|
+
"AttentionTraceData",
|
|
195
|
+
# Vector data
|
|
196
|
+
"CapturedVector",
|
|
197
|
+
"SteeringVector",
|
|
198
|
+
"ProbeVector",
|
|
199
|
+
"CentroidVector",
|
|
200
|
+
"HookKind",
|
|
201
|
+
"Vector",
|
|
202
|
+
# Cluster data
|
|
203
|
+
"Cluster",
|
|
204
|
+
"ClusterSet",
|
|
205
|
+
# Per-(layer, position) data
|
|
206
|
+
"PerLayerPerPositionBase",
|
|
207
|
+
"LogitLensTrajectory",
|
|
208
|
+
"PerLayerPerPositionData",
|
|
209
|
+
# Identity (only the validated-string type crosses the wire; parsers and
|
|
210
|
+
# helpers are Python-side-only)
|
|
211
|
+
"MechbenchPath",
|
|
212
|
+
"PathCategory",
|
|
213
|
+
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Attention-trace data shapes.
|
|
2
|
+
|
|
3
|
+
Records indexed by `(layer, head, position_from, position_to)` — the
|
|
4
|
+
attention patterns that heads compute during a forward pass. An
|
|
5
|
+
attention trace for a single prompt over a full model is a collection
|
|
6
|
+
of `(layer, head)` weight matrices, each `[seq_len × seq_len]` for
|
|
7
|
+
self-attention.
|
|
8
|
+
|
|
9
|
+
This module organizes the attention-pattern axis as a first-class
|
|
10
|
+
category, parallel to per_layer_data and per_head_data. A downstream
|
|
11
|
+
consumer can render a single (layer, head) matrix as a heatmap, a
|
|
12
|
+
small-multiples grid over (layer, head), a per-head attention-entropy
|
|
13
|
+
summary, etc.
|
|
14
|
+
|
|
15
|
+
The kinds today:
|
|
16
|
+
|
|
17
|
+
attention_pattern — one (layer, head) attention matrix, possibly
|
|
18
|
+
bundled with token labels for axis rendering.
|
|
19
|
+
This is the atomic unit; a full trace is a
|
|
20
|
+
collection of these.
|
|
21
|
+
|
|
22
|
+
Future kinds (file when a real consumer needs them):
|
|
23
|
+
- attention_entropy — per-(layer, head, position) scalar summary.
|
|
24
|
+
- attention_topk — per-(layer, head, position) top-k target
|
|
25
|
+
positions by weight.
|
|
26
|
+
- attention_trace — a sweep containing many attention_patterns plus
|
|
27
|
+
shared metadata (prompt_id, token_labels). May
|
|
28
|
+
want a `PerHeadOf[AttentionPattern]`-style
|
|
29
|
+
container when task 000157's generic work lands.
|
|
30
|
+
|
|
31
|
+
Migrated from the old records.py as part of the domain-axis reorg
|
|
32
|
+
(task 000156).
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
from typing import Annotated, Literal, Union
|
|
38
|
+
|
|
39
|
+
from pydantic import BaseModel, Field
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AttentionPattern(BaseModel):
|
|
43
|
+
"""Post-softmax attention weights for one (layer, head) over a sequence.
|
|
44
|
+
|
|
45
|
+
The weights matrix is stored as a flat row-major `[n_queries * n_keys]`
|
|
46
|
+
list. Consumers reshape on ingest; this keeps the wire format
|
|
47
|
+
JSON-native and trivially diffable. `token_labels`, when present,
|
|
48
|
+
labels both axes (self-attention) — for cross-attention the caller
|
|
49
|
+
should split into query_labels / key_labels (not yet modeled;
|
|
50
|
+
add when needed).
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
kind: Literal["attention_pattern"] = "attention_pattern"
|
|
54
|
+
layer: int = Field(..., ge=0, description="0-indexed transformer layer.")
|
|
55
|
+
head: int = Field(..., ge=0, description="0-indexed attention head within the layer.")
|
|
56
|
+
n_queries: int = Field(..., ge=1, description="Query-side sequence length.")
|
|
57
|
+
n_keys: int = Field(..., ge=1, description="Key-side sequence length.")
|
|
58
|
+
weights: list[float] = Field(
|
|
59
|
+
...,
|
|
60
|
+
description=(
|
|
61
|
+
"Row-major flattened post-softmax weights, shape "
|
|
62
|
+
"[n_queries * n_keys]. weights[q * n_keys + k] is the attention "
|
|
63
|
+
"weight from query q to key k."
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
token_labels: list[str] | None = Field(
|
|
67
|
+
None,
|
|
68
|
+
description=(
|
|
69
|
+
"Optional per-position decoded tokens, for axis labelling. "
|
|
70
|
+
"Assumes self-attention (same labels on both axes); if the "
|
|
71
|
+
"shape is asymmetric, model the split explicitly."
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# --- Discriminated union over all attention-trace kinds ---------------------
|
|
77
|
+
|
|
78
|
+
AttentionTraceData = Annotated[
|
|
79
|
+
Union[AttentionPattern],
|
|
80
|
+
Field(discriminator="kind"),
|
|
81
|
+
]
|