mas-lab-component-bench 0.2.0__py3-none-any.whl
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.
- mas/lab/benchmark/README.md +323 -0
- mas/lab/benchmark/__init__.py +65 -0
- mas/lab/benchmark/analysis.py +199 -0
- mas/lab/benchmark/cache/__init__.py +28 -0
- mas/lab/benchmark/cache/key_scheme.py +102 -0
- mas/lab/benchmark/cache/store.py +198 -0
- mas/lab/benchmark/cache/trace_store.py +530 -0
- mas/lab/benchmark/cli/__init__.py +41 -0
- mas/lab/benchmark/cli/analyze.py +114 -0
- mas/lab/benchmark/cli/artifacts.py +156 -0
- mas/lab/benchmark/cli/clean.py +171 -0
- mas/lab/benchmark/cli/common.py +38 -0
- mas/lab/benchmark/cli/declared.py +269 -0
- mas/lab/benchmark/cli/export_import.py +1020 -0
- mas/lab/benchmark/cli/list.py +109 -0
- mas/lab/benchmark/cli/manage.py +175 -0
- mas/lab/benchmark/cli/show.py +319 -0
- mas/lab/benchmark/cli/steps.py +217 -0
- mas/lab/benchmark/cli/tree.py +295 -0
- mas/lab/benchmark/codecs/__init__.py +94 -0
- mas/lab/benchmark/codecs/base.py +79 -0
- mas/lab/benchmark/dataset.py +267 -0
- mas/lab/benchmark/dataset_source.py +231 -0
- mas/lab/benchmark/engine.py +196 -0
- mas/lab/benchmark/execution/__init__.py +16 -0
- mas/lab/benchmark/execution/experiment_overlay.py +157 -0
- mas/lab/benchmark/execution/overrides.py +50 -0
- mas/lab/benchmark/execution/pipeline_attach.py +96 -0
- mas/lab/benchmark/execution/plan.py +173 -0
- mas/lab/benchmark/experiment.py +648 -0
- mas/lab/benchmark/golden/__init__.py +20 -0
- mas/lab/benchmark/golden/cache_backup.py +96 -0
- mas/lab/benchmark/golden/events.py +149 -0
- mas/lab/benchmark/golden/labs.py +132 -0
- mas/lab/benchmark/lock.py +224 -0
- mas/lab/benchmark/metadata.py +456 -0
- mas/lab/benchmark/metadata_integration.py +157 -0
- mas/lab/benchmark/migrate.py +207 -0
- mas/lab/benchmark/pipeline/__init__.py +101 -0
- mas/lab/benchmark/pipeline/adapter.py +52 -0
- mas/lab/benchmark/pipeline/artifact.py +312 -0
- mas/lab/benchmark/pipeline/artifact_paths.py +567 -0
- mas/lab/benchmark/pipeline/cache.py +304 -0
- mas/lab/benchmark/pipeline/channel.py +458 -0
- mas/lab/benchmark/pipeline/cli.py +264 -0
- mas/lab/benchmark/pipeline/config.py +30 -0
- mas/lab/benchmark/pipeline/core.py +495 -0
- mas/lab/benchmark/pipeline/counters.py +44 -0
- mas/lab/benchmark/pipeline/digest.py +39 -0
- mas/lab/benchmark/pipeline/executor.py +1044 -0
- mas/lab/benchmark/pipeline/expand.py +61 -0
- mas/lab/benchmark/pipeline/fanout.py +197 -0
- mas/lab/benchmark/pipeline/gather.py +45 -0
- mas/lab/benchmark/pipeline/incremental.py +115 -0
- mas/lab/benchmark/pipeline/keys.py +51 -0
- mas/lab/benchmark/pipeline/kinds.py +57 -0
- mas/lab/benchmark/pipeline/lib/__init__.py +7 -0
- mas/lab/benchmark/pipeline/lib/data_source.py +201 -0
- mas/lab/benchmark/pipeline/lib/plot_lib.py +615 -0
- mas/lab/benchmark/pipeline/lib/plot_specs/ablation_heatmap.yaml +51 -0
- mas/lab/benchmark/pipeline/lib/plot_specs/shapley_bars.yaml +62 -0
- mas/lab/benchmark/pipeline/lib/plot_specs/trajectory_gantt.yaml +68 -0
- mas/lab/benchmark/pipeline/lifecycle.py +83 -0
- mas/lab/benchmark/pipeline/live_sink.py +20 -0
- mas/lab/benchmark/pipeline/models.py +161 -0
- mas/lab/benchmark/pipeline/persist.py +60 -0
- mas/lab/benchmark/pipeline/resolver.py +198 -0
- mas/lab/benchmark/pipeline/resources.py +502 -0
- mas/lab/benchmark/pipeline/run_artifacts.py +183 -0
- mas/lab/benchmark/pipeline/scheduler.py +345 -0
- mas/lab/benchmark/pipeline/schema_validation.py +90 -0
- mas/lab/benchmark/pipeline/store.py +122 -0
- mas/lab/benchmark/pipeline/types.py +68 -0
- mas/lab/benchmark/pipeline/ui_bridge.py +221 -0
- mas/lab/benchmark/pipeline/ws.py +179 -0
- mas/lab/benchmark/plugins/__init__.py +3 -0
- mas/lab/benchmark/plugins/mas.py +62 -0
- mas/lab/benchmark/progress.py +256 -0
- mas/lab/benchmark/reporting.py +248 -0
- mas/lab/benchmark/reproducibility.py +243 -0
- mas/lab/benchmark/results.py +346 -0
- mas/lab/benchmark/run_manager/__init__.py +17 -0
- mas/lab/benchmark/run_manager/discovery.py +58 -0
- mas/lab/benchmark/run_manager/display.py +222 -0
- mas/lab/benchmark/run_manager/manager.py +507 -0
- mas/lab/benchmark/run_manager/models.py +65 -0
- mas/lab/benchmark/run_manager/persistence.py +39 -0
- mas/lab/benchmark/run_manager/pointer.py +20 -0
- mas/lab/benchmark/run_manager/stats.py +84 -0
- mas/lab/benchmark/runner.py +186 -0
- mas/lab/benchmark/runners/__init__.py +3 -0
- mas/lab/benchmark/schedule/__init__.py +13 -0
- mas/lab/benchmark/schedule/metadata.py +87 -0
- mas/lab/benchmark/schedule/pipeline.py +749 -0
- mas/lab/benchmark/schedule/pipeline_resolve.py +137 -0
- mas/lab/benchmark/schedule/resume.py +70 -0
- mas/lab/benchmark/schedule/run_batch/__init__.py +114 -0
- mas/lab/benchmark/schedule/run_batch/execute.py +810 -0
- mas/lab/benchmark/schedule/run_batch/finalize.py +183 -0
- mas/lab/benchmark/schedule/run_batch/load.py +381 -0
- mas/lab/benchmark/schedule/run_batch/pre_hooks.py +123 -0
- mas/lab/benchmark/schedule/run_batch/prepare.py +364 -0
- mas/lab/benchmark/schedule/run_discovery.py +157 -0
- mas/lab/benchmark/schema/__init__.py +37 -0
- mas/lab/benchmark/schema/normalized.py +210 -0
- mas/lab/benchmark/schema/validation.py +198 -0
- mas/lab/benchmark/service_manager.py +658 -0
- mas/lab/benchmark/stale_cleanup.py +269 -0
- mas/lab/benchmark/state.py +267 -0
- mas/lab/benchmark/storage.py +181 -0
- mas/lab/benchmark/worker.py +198 -0
- mas/lab/deprecations.py +103 -0
- mas/lab/flavour/load.py +20 -0
- mas/lab/flavour/resolve.py +51 -0
- mas/lab/infra/README.md +26 -0
- mas/lab/infra/datastore.py +51 -0
- mas/lab/infra/stores.py +63 -0
- mas/lab/inputs/__init__.py +264 -0
- mas/lab/inputs/expectations.py +45 -0
- mas/lab/inputs/fixtures.py +55 -0
- mas/lab/inputs/legacy.py +75 -0
- mas/lab/inputs/refs.py +100 -0
- mas/lab/inputs/schemas.py +82 -0
- mas/lab/lab/__init__.py +39 -0
- mas/lab/lab/config/__init__.py +163 -0
- mas/lab/lab/config/artifact_types.py +205 -0
- mas/lab/lab/config/execution.py +562 -0
- mas/lab/lab/config/experiment_base.py +596 -0
- mas/lab/lab/config/lab_config.py +52 -0
- mas/lab/lab/config/lab_context.py +179 -0
- mas/lab/lab/config/mas_experiment.py +216 -0
- mas/lab/lab/config/pipeline.py +360 -0
- mas/lab/lab/config/scenario.py +224 -0
- mas/lab/lab/config/scenario_loading.py +558 -0
- mas/lab/lab/config/ui.py +39 -0
- mas/lab/labs.py +693 -0
- mas/lab/manifest/README.md +14 -0
- mas/lab/manifest/__init__.py +29 -0
- mas/lab/manifest/load.py +303 -0
- mas/lab/manifests/__init__.py +26 -0
- mas/lab/manifests/composed_manifest.py +276 -0
- mas/lab/manifests/loader.py +49 -0
- mas/lab/manifests/ref_checks.py +271 -0
- mas/lab/manifests/schema/README.md +10 -0
- mas/lab/manifests/validator.py +320 -0
- mas/lab/manifests/versions.py +51 -0
- mas/lab/mas_validator.py +612 -0
- mas/lab/plots/__init__.py +69 -0
- mas/lab/plots/_trajectory_validator.py +318 -0
- mas/lab/plots/_viz_adapters.py +122 -0
- mas/lab/plots/assets/context_provenance.html +675 -0
- mas/lab/plots/assets/d3.min.js +2 -0
- mas/lab/plots/assets/live.html +135 -0
- mas/lab/plots/assets/mas.js +519 -0
- mas/lab/plots/assets/multilevel.html +3053 -0
- mas/lab/plots/communication_flow.py +375 -0
- mas/lab/plots/communication_flow_processor.yaml +17 -0
- mas/lab/plots/context_provenance.py +521 -0
- mas/lab/plots/html_demo.py +186 -0
- mas/lab/plots/kg_adapter.py +971 -0
- mas/lab/plots/kg_widget/__init__.py +208 -0
- mas/lab/plots/kg_widget/kg_composer.css +532 -0
- mas/lab/plots/kg_widget/kg_composer.js +1097 -0
- mas/lab/plots/kg_widget/kg_widget.css +398 -0
- mas/lab/plots/kg_widget/kg_widget.js +1423 -0
- mas/lab/plots/live_html.py +73 -0
- mas/lab/plots/loaders.py +78 -0
- mas/lab/plots/mas_plot.py +500 -0
- mas/lab/plots/message_graph/__init__.py +60 -0
- mas/lab/plots/message_graph/constants.py +50 -0
- mas/lab/plots/message_graph/extract.py +93 -0
- mas/lab/plots/message_graph/helpers.py +36 -0
- mas/lab/plots/message_graph/layout.py +49 -0
- mas/lab/plots/message_graph/plot.py +410 -0
- mas/lab/plots/message_graph/processor.py +71 -0
- mas/lab/plots/message_graph/theme.py +282 -0
- mas/lab/plots/multilevel_context_provenance.py +619 -0
- mas/lab/plots/multilevel_trajectory/__init__.py +118 -0
- mas/lab/plots/multilevel_trajectory/annotations.py +584 -0
- mas/lab/plots/multilevel_trajectory/chart_data.py +568 -0
- mas/lab/plots/multilevel_trajectory/constants.py +156 -0
- mas/lab/plots/multilevel_trajectory/dag.py +2357 -0
- mas/lab/plots/multilevel_trajectory/governance.py +263 -0
- mas/lab/plots/multilevel_trajectory/layout.py +81 -0
- mas/lab/plots/multilevel_trajectory/models.py +225 -0
- mas/lab/plots/multilevel_trajectory/plot.py +218 -0
- mas/lab/plots/multilevel_trajectory/records.py +991 -0
- mas/lab/plots/multilevel_trajectory/svg.py +419 -0
- mas/lab/plots/multilevel_trajectory/test_regression_verifier.py +149 -0
- mas/lab/plots/multilevel_trajectory/tree.py +910 -0
- mas/lab/plots/multilevel_trajectory_processor.py +164 -0
- mas/lab/plots/multilevel_trajectory_processor.yaml +17 -0
- mas/lab/plots/palette.py +84 -0
- mas/lab/plots/pipeline_diagram.py +504 -0
- mas/lab/plots/providers.py +138 -0
- mas/lab/plots/topology.py +463 -0
- mas/lab/plots/trajectory/__init__.py +11 -0
- mas/lab/plots/trajectory/extract.py +213 -0
- mas/lab/plots/trajectory/highlights.py +54 -0
- mas/lab/plots/trajectory/html.py +185 -0
- mas/lab/plots/trajectory/loading.py +77 -0
- mas/lab/plots/trajectory/mermaid.py +99 -0
- mas/lab/plots/trajectory/plot.py +115 -0
- mas/lab/plots/trajectory/svg.py +311 -0
- mas/lab/processors/__init__.py +63 -0
- mas/lab/processors/multilevel_trajectory_plotter.py +105 -0
- mas/lab/processors/multilevel_trajectory_plotter.yaml +23 -0
- mas/lab/processors/trajectory_annotator.py +86 -0
- mas/lab/processors/trajectory_annotator.yaml +16 -0
- mas/lab/processors/trajectory_loader.py +86 -0
- mas/lab/processors/trajectory_loader.yaml +16 -0
- mas/lab/processors/trajectory_plotter.py +129 -0
- mas/lab/processors/trajectory_plotter.yaml +25 -0
- mas/lab/processors/trajectory_plotter_native.py +116 -0
- mas/lab/processors/trajectory_plotter_native.yaml +22 -0
- mas/lab/verification/__init__.py +39 -0
- mas/lab/verification/contracts/__init__.py +33 -0
- mas/lab/verification/contracts/executor.py +423 -0
- mas/lab/verification/contracts/generator.py +216 -0
- mas/lab/verification/contracts/loader.py +133 -0
- mas/lab/verification/contracts/reporter.py +202 -0
- mas/lab/workspace.py +77 -0
- mas_lab_component_bench-0.2.0.dist-info/METADATA +19 -0
- mas_lab_component_bench-0.2.0.dist-info/RECORD +226 -0
- mas_lab_component_bench-0.2.0.dist-info/WHEEL +4 -0
- mas_lab_component_bench-0.2.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
|
|
3
|
+
SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
-->
|
|
5
|
+
# Benchmark Framework - Reusable Components
|
|
6
|
+
|
|
7
|
+
**Composable primitives for agent evaluation.**
|
|
8
|
+
|
|
9
|
+
## Philosophy
|
|
10
|
+
|
|
11
|
+
This framework follows these principles:
|
|
12
|
+
|
|
13
|
+
1. **Declarative over imperative** - Configure via YAML/JSON, not code
|
|
14
|
+
2. **Incremental over monolithic** - Add runs without rerunning everything
|
|
15
|
+
3. **Composable over all-in-one** - Mix and match components
|
|
16
|
+
4. **Human-readable over optimized** - Folder names you can understand
|
|
17
|
+
5. **DataFrame-native over custom formats** - Leverage pandas/polars ecosystem
|
|
18
|
+
|
|
19
|
+
## Components
|
|
20
|
+
|
|
21
|
+
### 1. Dataset (`dataset.py`)
|
|
22
|
+
|
|
23
|
+
Loads and manipulates evaluation datasets.
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from mas.lab.benchmark import Dataset
|
|
27
|
+
|
|
28
|
+
# Load from JSON
|
|
29
|
+
dataset = Dataset.from_json("math_problems.json")
|
|
30
|
+
|
|
31
|
+
# Filter by category
|
|
32
|
+
arithmetic = dataset.filter(category="arithmetic")
|
|
33
|
+
|
|
34
|
+
# Iterate
|
|
35
|
+
for item in dataset:
|
|
36
|
+
print(item.id, item.prompt)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**JSON Format:**
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"name": "dataset_name",
|
|
43
|
+
"version": "v1",
|
|
44
|
+
"items": [
|
|
45
|
+
{
|
|
46
|
+
"id": "001",
|
|
47
|
+
"prompt": "Question text",
|
|
48
|
+
"category": "category_name",
|
|
49
|
+
"expected_answer": "Answer",
|
|
50
|
+
"metadata_key": "metadata_value"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. ResultStorage (`storage.py`)
|
|
57
|
+
|
|
58
|
+
Manages human-readable folder structure.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from mas.lab.benchmark import ResultStorage, RunMetadata
|
|
62
|
+
|
|
63
|
+
storage = ResultStorage("./results")
|
|
64
|
+
|
|
65
|
+
# Generate run ID
|
|
66
|
+
run_id = storage.generate_run_id("cot", run_number=1)
|
|
67
|
+
# → "cot_run_001_20260218_143022"
|
|
68
|
+
|
|
69
|
+
# Save run
|
|
70
|
+
metadata = RunMetadata(
|
|
71
|
+
dataset_name="math",
|
|
72
|
+
item_id="001",
|
|
73
|
+
run_id=run_id,
|
|
74
|
+
pattern="cot",
|
|
75
|
+
timestamp="2026-02-18T14:30:22",
|
|
76
|
+
config={"model": "gpt-4"},
|
|
77
|
+
success=True,
|
|
78
|
+
latency_ms=1234.5
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
storage.save_run(
|
|
82
|
+
dataset_name="math",
|
|
83
|
+
item_id="001",
|
|
84
|
+
run_id=run_id,
|
|
85
|
+
metadata=metadata,
|
|
86
|
+
events_file=Path("events.jsonl"),
|
|
87
|
+
artifacts={"log": Path("agent.log")}
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# List all runs
|
|
91
|
+
runs = storage.list_runs("math")
|
|
92
|
+
# → [("math", "001", "cot_run_001_20260218_143022"), ...]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Folder Structure:**
|
|
96
|
+
```
|
|
97
|
+
results/
|
|
98
|
+
{dataset_name}/
|
|
99
|
+
{item_id}/
|
|
100
|
+
{run_id}/
|
|
101
|
+
metadata.json
|
|
102
|
+
events.jsonl
|
|
103
|
+
artifacts/
|
|
104
|
+
consolidated.parquet
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 3. MultiRunOrchestrator (`runner.py`)
|
|
108
|
+
|
|
109
|
+
Coordinates N runs per item across patterns.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from mas.lab.benchmark import MultiRunOrchestrator
|
|
113
|
+
|
|
114
|
+
orchestrator = MultiRunOrchestrator(
|
|
115
|
+
storage=storage,
|
|
116
|
+
n_runs=3,
|
|
117
|
+
pause_between_runs=1.0
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Define agent factory
|
|
121
|
+
async def agent_factory(pattern_name, pattern_config):
|
|
122
|
+
# Your agent creation logic
|
|
123
|
+
return agent
|
|
124
|
+
|
|
125
|
+
# Run benchmark
|
|
126
|
+
summary = await orchestrator.run_dataset(
|
|
127
|
+
dataset=dataset,
|
|
128
|
+
patterns={
|
|
129
|
+
"cot": {"manifest": "cot.yaml"},
|
|
130
|
+
"react": {"manifest": "react.yaml"}
|
|
131
|
+
},
|
|
132
|
+
agent_factory=agent_factory
|
|
133
|
+
)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 4. ResultAnalyzer (`analysis.py`)
|
|
137
|
+
|
|
138
|
+
Consolidates results into DataFrame.
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from mas.lab.benchmark import ResultAnalyzer
|
|
142
|
+
|
|
143
|
+
analyzer = ResultAnalyzer(storage)
|
|
144
|
+
|
|
145
|
+
# Consolidate all runs
|
|
146
|
+
df = analyzer.consolidate_results(
|
|
147
|
+
dataset_name="math",
|
|
148
|
+
include_events=True, # Parse event logs
|
|
149
|
+
cache=True # Save as Parquet
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Compute statistics
|
|
153
|
+
stats = analyzer.compute_statistics(df)
|
|
154
|
+
# pattern latency_ms_mean success_rate mean_tokens
|
|
155
|
+
# 0 cot 1234.5 0.95 567
|
|
156
|
+
# 1 react 890.2 0.92 423
|
|
157
|
+
|
|
158
|
+
# Compare patterns
|
|
159
|
+
comparison = analyzer.compare_patterns(df, ["cot", "react"], "latency_ms")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**DataFrame Schema:**
|
|
163
|
+
```python
|
|
164
|
+
df.columns
|
|
165
|
+
# ['dataset_name', 'item_id', 'run_id', 'pattern',
|
|
166
|
+
# 'timestamp', 'success', 'latency_ms', 'error',
|
|
167
|
+
# 'total_tokens', 'prompt_tokens', 'completion_tokens',
|
|
168
|
+
# 'tool_calls', 'llm_calls',
|
|
169
|
+
# 'config_*']
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 5. Pipeline plots (`plotnine` / `plot` steps)
|
|
173
|
+
|
|
174
|
+
Paper figures use **`type: plotnine`** in `experiment.yaml` experiment-level `post:`.
|
|
175
|
+
Ad-hoc charts from step data use **`type: plot`** with **`config.spec`** (plot library).
|
|
176
|
+
|
|
177
|
+
See [pipeline-steps.md](../../../../../../docs/pipeline-steps.md) and `labs/*/experiment.yaml` for examples.
|
|
178
|
+
|
|
179
|
+
## Complete Example
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
import asyncio
|
|
183
|
+
from pathlib import Path
|
|
184
|
+
from mas.lab.benchmark import (
|
|
185
|
+
Dataset, ResultStorage, MultiRunOrchestrator,
|
|
186
|
+
ResultAnalyzer,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
async def my_agent_factory(pattern_name, pattern_config):
|
|
190
|
+
# Your agent creation logic
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
async def main():
|
|
194
|
+
# 1. Load dataset
|
|
195
|
+
dataset = Dataset.from_yaml("dataset.yaml")
|
|
196
|
+
|
|
197
|
+
# 2. Setup storage
|
|
198
|
+
storage = ResultStorage("./results")
|
|
199
|
+
|
|
200
|
+
# 3. Run benchmark
|
|
201
|
+
orchestrator = MultiRunOrchestrator(storage, n_runs=3)
|
|
202
|
+
await orchestrator.run_dataset(
|
|
203
|
+
dataset,
|
|
204
|
+
patterns={"cot": {}, "react": {}},
|
|
205
|
+
agent_factory=my_agent_factory
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# 4. Analyze
|
|
209
|
+
analyzer = ResultAnalyzer(storage)
|
|
210
|
+
df = analyzer.consolidate_results(dataset.name)
|
|
211
|
+
stats = analyzer.compute_statistics(df)
|
|
212
|
+
print(stats)
|
|
213
|
+
# Plots: declare plotnine / plot steps in experiment.yaml post:
|
|
214
|
+
|
|
215
|
+
asyncio.run(main())
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Incremental Updates
|
|
219
|
+
|
|
220
|
+
### Add Items
|
|
221
|
+
```python
|
|
222
|
+
# Load existing dataset
|
|
223
|
+
dataset = Dataset.from_json("dataset.json")
|
|
224
|
+
|
|
225
|
+
# Add new items
|
|
226
|
+
dataset.items.append(DatasetItem(id="006", prompt="New question"))
|
|
227
|
+
|
|
228
|
+
# Save
|
|
229
|
+
dataset.to_json("dataset.json")
|
|
230
|
+
|
|
231
|
+
# Run benchmark (only new items executed)
|
|
232
|
+
await orchestrator.run_dataset(dataset, patterns, agent_factory)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Add Runs
|
|
236
|
+
```python
|
|
237
|
+
# Increase n_runs
|
|
238
|
+
orchestrator = MultiRunOrchestrator(storage, n_runs=5) # Was 3
|
|
239
|
+
|
|
240
|
+
# Run again - adds run_004, run_005 to existing items
|
|
241
|
+
await orchestrator.run_dataset(dataset, patterns, agent_factory)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Add Patterns
|
|
245
|
+
```python
|
|
246
|
+
# Add new pattern
|
|
247
|
+
patterns = {
|
|
248
|
+
"cot": {...},
|
|
249
|
+
"react": {...},
|
|
250
|
+
"planner": {...} # NEW
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# Run again - only planner executed on all items
|
|
254
|
+
await orchestrator.run_dataset(dataset, patterns, agent_factory)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Regenerate Plots
|
|
258
|
+
```python
|
|
259
|
+
# Re-run pipeline post steps only (content-addressed trace cache skips LLM calls)
|
|
260
|
+
# mas-lab benchmark run experiment.yaml --force-post
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Advanced Usage
|
|
264
|
+
|
|
265
|
+
### Custom Metrics
|
|
266
|
+
|
|
267
|
+
Extend `ResultAnalyzer._extract_event_metrics()`:
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
def _extract_event_metrics(self, events_file: Path) -> Dict[str, Any]:
|
|
271
|
+
metrics = super()._extract_event_metrics(events_file)
|
|
272
|
+
|
|
273
|
+
# Add custom metrics
|
|
274
|
+
metrics["my_custom_metric"] = ...
|
|
275
|
+
|
|
276
|
+
return metrics
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Custom Plots
|
|
280
|
+
|
|
281
|
+
Add a `plotnine` step in experiment-level `post:` or a plot-library YAML under
|
|
282
|
+
`pipeline/steps/plot_library/`. See `labs/design-space.lab/01-design-patterns/experiment.yaml`.
|
|
283
|
+
|
|
284
|
+
### Parallel Execution
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
# Run patterns in parallel
|
|
288
|
+
import asyncio
|
|
289
|
+
|
|
290
|
+
async def run_pattern(pattern_name, pattern_config):
|
|
291
|
+
await orchestrator.run_dataset(
|
|
292
|
+
dataset,
|
|
293
|
+
patterns={pattern_name: pattern_config},
|
|
294
|
+
agent_factory=agent_factory
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
await asyncio.gather(
|
|
298
|
+
run_pattern("cot", {...}),
|
|
299
|
+
run_pattern("react", {...}),
|
|
300
|
+
)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Dependencies
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
pip install pandas pyarrow plotnine pyyaml
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Testing
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
cd mas-lab
|
|
313
|
+
pytest tests/benchmark/
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Contributing
|
|
317
|
+
|
|
318
|
+
When adding new components:
|
|
319
|
+
|
|
320
|
+
1. Follow existing patterns (DataClass for data, Class for behavior)
|
|
321
|
+
2. Add docstrings with examples
|
|
322
|
+
3. Write tests in `tests/benchmark/`
|
|
323
|
+
4. Update this documentation
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""
|
|
4
|
+
Reusable benchmark infrastructure for MAS evaluations.
|
|
5
|
+
|
|
6
|
+
Provides composable primitives for:
|
|
7
|
+
- Dataset management
|
|
8
|
+
- Multi-run orchestration
|
|
9
|
+
- Incremental results storage
|
|
10
|
+
- DataFrame consolidation
|
|
11
|
+
- Declarative evaluation configuration
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from mas.lab.benchmark.dataset import Dataset, DatasetItem
|
|
15
|
+
from mas.lab.benchmark.runner import MultiRunOrchestrator
|
|
16
|
+
from mas.lab.benchmark.storage import ResultStorage, RunMetadata
|
|
17
|
+
from mas.lab.benchmark.metadata import (
|
|
18
|
+
BenchmarkMetadata,
|
|
19
|
+
BenchmarkState,
|
|
20
|
+
BenchmarkStatus,
|
|
21
|
+
ScenarioResult,
|
|
22
|
+
ScenarioState,
|
|
23
|
+
)
|
|
24
|
+
from mas.lab.benchmark.state import EnhancedBenchmarkState
|
|
25
|
+
from mas.lab.benchmark.lock import BenchmarkLock, LockInfo
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"Dataset",
|
|
29
|
+
"DatasetItem",
|
|
30
|
+
"MultiRunOrchestrator",
|
|
31
|
+
"ResultStorage",
|
|
32
|
+
"RunMetadata",
|
|
33
|
+
"ResultAnalyzer",
|
|
34
|
+
"BenchmarkMetadata",
|
|
35
|
+
"BenchmarkState",
|
|
36
|
+
"BenchmarkStatus",
|
|
37
|
+
"ScenarioResult",
|
|
38
|
+
"ScenarioState",
|
|
39
|
+
"EnhancedBenchmarkState",
|
|
40
|
+
"BenchmarkLock",
|
|
41
|
+
"LockInfo",
|
|
42
|
+
"ExperimentResults",
|
|
43
|
+
"ScenarioView",
|
|
44
|
+
"ItemView",
|
|
45
|
+
"RunView",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def __getattr__(name: str):
|
|
50
|
+
# Defer pandas imports so coverage tracing of pipeline subpackages does not
|
|
51
|
+
# reload numpy during test collection (pytest-cov / coverage.py on Py 3.11+).
|
|
52
|
+
if name == "ResultAnalyzer":
|
|
53
|
+
from mas.lab.benchmark.analysis import ResultAnalyzer
|
|
54
|
+
|
|
55
|
+
return ResultAnalyzer
|
|
56
|
+
if name in ("ExperimentResults", "ScenarioView", "ItemView", "RunView"):
|
|
57
|
+
from mas.lab.benchmark.results import (
|
|
58
|
+
ExperimentResults,
|
|
59
|
+
ItemView,
|
|
60
|
+
RunView,
|
|
61
|
+
ScenarioView,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return locals()[name]
|
|
65
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""
|
|
4
|
+
Result analysis and DataFrame consolidation.
|
|
5
|
+
|
|
6
|
+
Loads all run metadata and creates consolidated DataFrames for analysis.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, Any, List, Optional
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
14
|
+
from mas.lab.benchmark.storage import ResultStorage
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ResultAnalyzer:
|
|
18
|
+
"""
|
|
19
|
+
Analyze benchmark results.
|
|
20
|
+
|
|
21
|
+
Features:
|
|
22
|
+
- Consolidate all runs into DataFrame
|
|
23
|
+
- Compute statistics per pattern
|
|
24
|
+
- Extract metrics from event logs
|
|
25
|
+
- Cache results as Parquet
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, storage: ResultStorage):
|
|
29
|
+
self.storage = storage
|
|
30
|
+
|
|
31
|
+
def consolidate_results(
|
|
32
|
+
self,
|
|
33
|
+
dataset_name: str,
|
|
34
|
+
include_events: bool = False,
|
|
35
|
+
cache: bool = True
|
|
36
|
+
) -> pd.DataFrame:
|
|
37
|
+
"""
|
|
38
|
+
Consolidate all runs into DataFrame.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
dataset_name: Dataset name
|
|
42
|
+
include_events: Parse event logs for detailed metrics
|
|
43
|
+
cache: Save as Parquet for incremental updates
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
DataFrame with columns:
|
|
47
|
+
- dataset_name, item_id, run_id, pattern
|
|
48
|
+
- timestamp, success, latency_ms, error
|
|
49
|
+
- (if include_events) total_tokens, tool_calls, etc.
|
|
50
|
+
"""
|
|
51
|
+
runs = self.storage.list_runs(dataset_name)
|
|
52
|
+
|
|
53
|
+
if not runs:
|
|
54
|
+
return pd.DataFrame()
|
|
55
|
+
|
|
56
|
+
rows = []
|
|
57
|
+
for ds_name, item_id, run_id in runs:
|
|
58
|
+
metadata = self.storage.load_run_metadata(ds_name, item_id, run_id)
|
|
59
|
+
if not metadata:
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
row = {
|
|
63
|
+
"dataset_name": metadata.dataset_name,
|
|
64
|
+
"item_id": metadata.item_id,
|
|
65
|
+
"run_id": metadata.run_id,
|
|
66
|
+
"pattern": metadata.pattern,
|
|
67
|
+
"timestamp": metadata.timestamp,
|
|
68
|
+
"success": metadata.success,
|
|
69
|
+
"latency_ms": metadata.latency_ms,
|
|
70
|
+
"error": metadata.error,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Add config fields
|
|
74
|
+
for key, value in metadata.config.items():
|
|
75
|
+
row[f"config_{key}"] = value
|
|
76
|
+
|
|
77
|
+
# Parse events if requested
|
|
78
|
+
if include_events:
|
|
79
|
+
run_dir = self.storage.get_run_dir(ds_name, item_id, run_id)
|
|
80
|
+
events_file = run_dir / "events.jsonl"
|
|
81
|
+
if events_file.exists():
|
|
82
|
+
event_metrics = self._extract_event_metrics(events_file)
|
|
83
|
+
row.update(event_metrics)
|
|
84
|
+
|
|
85
|
+
rows.append(row)
|
|
86
|
+
|
|
87
|
+
df = pd.DataFrame(rows)
|
|
88
|
+
|
|
89
|
+
# Cache as Parquet
|
|
90
|
+
if cache and not df.empty:
|
|
91
|
+
cache_path = self.storage.get_consolidated_path(dataset_name)
|
|
92
|
+
df.to_parquet(cache_path, index=False)
|
|
93
|
+
|
|
94
|
+
return df
|
|
95
|
+
|
|
96
|
+
def load_cached(self, dataset_name: str) -> Optional[pd.DataFrame]:
|
|
97
|
+
"""Load cached consolidated DataFrame."""
|
|
98
|
+
cache_path = self.storage.get_consolidated_path(dataset_name)
|
|
99
|
+
if not cache_path.exists():
|
|
100
|
+
return None
|
|
101
|
+
return pd.read_parquet(cache_path)
|
|
102
|
+
|
|
103
|
+
def compute_statistics(self, df: pd.DataFrame) -> pd.DataFrame:
|
|
104
|
+
"""
|
|
105
|
+
Compute aggregate statistics per pattern.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
DataFrame with pattern-level statistics:
|
|
109
|
+
- mean_latency_ms, std_latency_ms
|
|
110
|
+
- success_rate
|
|
111
|
+
- mean_tokens, mean_tool_calls (if available)
|
|
112
|
+
"""
|
|
113
|
+
if df.empty:
|
|
114
|
+
return pd.DataFrame()
|
|
115
|
+
|
|
116
|
+
# Basic statistics
|
|
117
|
+
stats = df.groupby("pattern").agg({
|
|
118
|
+
"latency_ms": ["mean", "std", "min", "max"],
|
|
119
|
+
"success": "mean",
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
# Flatten column names
|
|
123
|
+
stats.columns = ["_".join(col).strip("_") for col in stats.columns]
|
|
124
|
+
stats = stats.rename(columns={"success_mean": "success_rate"})
|
|
125
|
+
|
|
126
|
+
# Token and tool metrics if available
|
|
127
|
+
if "total_tokens" in df.columns:
|
|
128
|
+
token_stats = df.groupby("pattern")["total_tokens"].agg(["mean", "std"])
|
|
129
|
+
token_stats.columns = ["mean_tokens", "std_tokens"]
|
|
130
|
+
stats = stats.join(token_stats)
|
|
131
|
+
|
|
132
|
+
if "tool_calls" in df.columns:
|
|
133
|
+
tool_stats = df.groupby("pattern")["tool_calls"].agg(["mean", "std"])
|
|
134
|
+
tool_stats.columns = ["mean_tool_calls", "std_tool_calls"]
|
|
135
|
+
stats = stats.join(tool_stats)
|
|
136
|
+
|
|
137
|
+
return stats.reset_index()
|
|
138
|
+
|
|
139
|
+
def _extract_event_metrics(self, events_file: Path) -> Dict[str, Any]:
|
|
140
|
+
"""Extract metrics from event log."""
|
|
141
|
+
metrics = {
|
|
142
|
+
"total_tokens": 0,
|
|
143
|
+
"prompt_tokens": 0,
|
|
144
|
+
"completion_tokens": 0,
|
|
145
|
+
"tool_calls": 0,
|
|
146
|
+
"llm_calls": 0,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
with open(events_file) as f:
|
|
150
|
+
for line in f:
|
|
151
|
+
if not line.strip():
|
|
152
|
+
continue
|
|
153
|
+
try:
|
|
154
|
+
event = json.loads(line)
|
|
155
|
+
|
|
156
|
+
# Count LLM calls
|
|
157
|
+
if event.get("kind") == "llm_response":
|
|
158
|
+
metrics["llm_calls"] += 1
|
|
159
|
+
usage = event.get("usage", {})
|
|
160
|
+
metrics["total_tokens"] += usage.get("total_tokens", 0)
|
|
161
|
+
metrics["prompt_tokens"] += usage.get("prompt_tokens", 0)
|
|
162
|
+
metrics["completion_tokens"] += usage.get("completion_tokens", 0)
|
|
163
|
+
|
|
164
|
+
# Count tool calls
|
|
165
|
+
elif event.get("kind") == "tool_start":
|
|
166
|
+
metrics["tool_calls"] += 1
|
|
167
|
+
|
|
168
|
+
except json.JSONDecodeError:
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
return metrics
|
|
172
|
+
|
|
173
|
+
def get_item_runs(self, df: pd.DataFrame, item_id: str) -> pd.DataFrame:
|
|
174
|
+
"""Get all runs for specific item."""
|
|
175
|
+
return df[df["item_id"] == item_id].copy()
|
|
176
|
+
|
|
177
|
+
def get_pattern_runs(self, df: pd.DataFrame, pattern: str) -> pd.DataFrame:
|
|
178
|
+
"""Get all runs for specific pattern."""
|
|
179
|
+
return df[df["pattern"] == pattern].copy()
|
|
180
|
+
|
|
181
|
+
def compare_patterns(
|
|
182
|
+
self,
|
|
183
|
+
df: pd.DataFrame,
|
|
184
|
+
patterns: List[str],
|
|
185
|
+
metric: str = "latency_ms"
|
|
186
|
+
) -> pd.DataFrame:
|
|
187
|
+
"""
|
|
188
|
+
Compare patterns on specific metric.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
DataFrame with item_id, pattern, metric columns
|
|
192
|
+
"""
|
|
193
|
+
filtered = df[df["pattern"].isin(patterns)]
|
|
194
|
+
return filtered.pivot_table(
|
|
195
|
+
index="item_id",
|
|
196
|
+
columns="pattern",
|
|
197
|
+
values=metric,
|
|
198
|
+
aggfunc="mean"
|
|
199
|
+
).reset_index()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Content-addressed trace cache helpers."""
|
|
4
|
+
from mas.lab.benchmark.cache.trace_store import (
|
|
5
|
+
compute_run_hash,
|
|
6
|
+
resolve_run_events_path,
|
|
7
|
+
trace_cache_roots,
|
|
8
|
+
extract_flavour_info,
|
|
9
|
+
get_trace_cache_dir,
|
|
10
|
+
link_trace_to_cache_entry,
|
|
11
|
+
materialize_config,
|
|
12
|
+
write_cache_inputs,
|
|
13
|
+
write_run_info,
|
|
14
|
+
write_run_result,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"compute_run_hash",
|
|
19
|
+
"extract_flavour_info",
|
|
20
|
+
"get_trace_cache_dir",
|
|
21
|
+
"link_trace_to_cache_entry",
|
|
22
|
+
"materialize_config",
|
|
23
|
+
"resolve_run_events_path",
|
|
24
|
+
"trace_cache_roots",
|
|
25
|
+
"write_cache_inputs",
|
|
26
|
+
"write_run_info",
|
|
27
|
+
"write_run_result",
|
|
28
|
+
]
|