mas-lab-component-core 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/artifacts.py +282 -0
- mas/lab/cli/__init__.py +115 -0
- mas/lab/cli/commands/__init__.py +3 -0
- mas/lab/cli/commands/benchmark/__init__.py +73 -0
- mas/lab/cli/commands/benchmark/analyze.py +25 -0
- mas/lab/cli/commands/benchmark/artifacts.py +57 -0
- mas/lab/cli/commands/benchmark/clean.py +56 -0
- mas/lab/cli/commands/benchmark/daemon.py +137 -0
- mas/lab/cli/commands/benchmark/export_cmd.py +49 -0
- mas/lab/cli/commands/benchmark/follow.py +121 -0
- mas/lab/cli/commands/benchmark/import_cmd.py +52 -0
- mas/lab/cli/commands/benchmark/import_hf.py +96 -0
- mas/lab/cli/commands/benchmark/list_cmd.py +29 -0
- mas/lab/cli/commands/benchmark/migrate.py +65 -0
- mas/lab/cli/commands/benchmark/pipeline.py +133 -0
- mas/lab/cli/commands/benchmark/publish_hf.py +88 -0
- mas/lab/cli/commands/benchmark/rename.py +44 -0
- mas/lab/cli/commands/benchmark/run.py +205 -0
- mas/lab/cli/commands/benchmark/show.py +101 -0
- mas/lab/cli/commands/benchmark/step.py +51 -0
- mas/lab/cli/commands/benchmark/step_info.py +116 -0
- mas/lab/cli/commands/benchmark/update.py +28 -0
- mas/lab/cli/commands/check.py +107 -0
- mas/lab/cli/commands/config.py +187 -0
- mas/lab/cli/commands/dataset.py +99 -0
- mas/lab/cli/commands/init.py +347 -0
- mas/lab/cli/commands/pipe.py +410 -0
- mas/lab/cli/commands/plot/__init__.py +51 -0
- mas/lab/cli/commands/plot/_common.py +38 -0
- mas/lab/cli/commands/plot/communication_flow.py +77 -0
- mas/lab/cli/commands/plot/context_provenance.py +136 -0
- mas/lab/cli/commands/plot/kg.py +97 -0
- mas/lab/cli/commands/plot/list_cmd.py +81 -0
- mas/lab/cli/commands/plot/message_graph.py +179 -0
- mas/lab/cli/commands/plot/multilevel_trajectory.py +90 -0
- mas/lab/cli/commands/plot/topology.py +58 -0
- mas/lab/cli/commands/plot/trajectory.py +93 -0
- mas/lab/cli/commands/run.py +473 -0
- mas/lab/cli/commands/serve.py +89 -0
- mas/lab/cli/commands/telemetry/__init__.py +30 -0
- mas/lab/cli/commands/telemetry/_clickhouse.py +123 -0
- mas/lab/cli/commands/telemetry/apps.py +132 -0
- mas/lab/cli/commands/telemetry/dump.py +232 -0
- mas/lab/cli/commands/telemetry/push.py +101 -0
- mas/lab/cli/commands/telemetry/sessions.py +92 -0
- mas/lab/cli/commands/telemetry/show.py +125 -0
- mas/lab/cli/commands/telemetry/traces.py +80 -0
- mas/lab/cli/commands/telemetry/verify.py +146 -0
- mas/lab/cli/commands/validate.py +138 -0
- mas/lab/cli/extensions.py +77 -0
- mas/lab/cli/manpage.py +729 -0
- mas/lab/cli/pipeline_shortcuts.py +205 -0
- mas/lab/cli.py +11 -0
- mas/lab/components/__init__.py +3 -0
- mas/lab/components/common/__init__.py +3 -0
- mas/lab/components/common/hooks.py +171 -0
- mas/lab/components/common/telemetry_feed.py +12 -0
- mas/lab/components/common/telemetry_schema.json +12 -0
- mas/lab/components/controller/backends.py +216 -0
- mas/lab/components/evaluation/__init__.py +28 -0
- mas/lab/components/evaluation/basic.py +35 -0
- mas/lab/components/evaluation/deepeval_wrapper.py +163 -0
- mas/lab/components/evaluation/interface.py +24 -0
- mas/lab/components/infrastructure/README.md +94 -0
- mas/lab/components/infrastructure/__init__.py +53 -0
- mas/lab/components/infrastructure/emulator.py +115 -0
- mas/lab/components/metrics/__init__.py +3 -0
- mas/lab/components/metrics/server.py +92 -0
- mas/lab/components/observability/otlp_http_local.py +56 -0
- mas/lab/components/package/__init__.py +116 -0
- mas/lab/components/ui/app.js +5210 -0
- mas/lab/components/ui/favicon.ico +1 -0
- mas/lab/components/ui/index.html +532 -0
- mas/lab/components/ui/js/plugin-system.js +179 -0
- mas/lab/components/ui/js/plugins/events.js +337 -0
- mas/lab/components/ui/js/plugins/hitl.js +123 -0
- mas/lab/components/ui/js/plugins/metrics.js +153 -0
- mas/lab/components/ui/js/plugins/observability.js +93 -0
- mas/lab/components/ui/js/plugins/plugins.css +133 -0
- mas/lab/components/ui/styles.css +2480 -0
- mas/lab/config_hygiene.py +197 -0
- mas/lab/connections.py +362 -0
- mas/lab/contracts/__init__.py +52 -0
- mas/lab/contracts/controller.py +115 -0
- mas/lab/contracts/executor.py +423 -0
- mas/lab/contracts/generator.py +216 -0
- mas/lab/contracts/loader.py +133 -0
- mas/lab/contracts/reporter.py +202 -0
- mas/lab/controllers/__init__.py +3 -0
- mas/lab/controllers/local_inproc.py +68 -0
- mas/lab/dataset/extractor.py +304 -0
- mas/lab/emulation_resolver.py +109 -0
- mas/lab/evaluation/__init__.py +20 -0
- mas/lab/evaluation/eval_contract.py +194 -0
- mas/lab/evaluation/events.py +70 -0
- mas/lab/evaluation/obs_event.py +4 -0
- mas/lab/graph/__init__.py +100 -0
- mas/lab/paths.py +395 -0
- mas/lab/pipe.py +462 -0
- mas/lab/processor.py +311 -0
- mas/lab/runners/__init__.py +35 -0
- mas/lab/runners/artifacts.py +84 -0
- mas/lab/runners/constants.py +12 -0
- mas/lab/runners/context.py +64 -0
- mas/lab/runners/factory.py +41 -0
- mas/lab/runners/infer.py +106 -0
- mas/lab/runners/invoke.py +16 -0
- mas/lab/runners/protocol.py +78 -0
- mas/lab/runners/registry.py +70 -0
- mas/lab/schemas/__init__.py +21 -0
- mas/lab/schemas/_schemas/artefacts/metrics.schema.json +114 -0
- mas/lab/schemas/_schemas/artefacts/run_info.schema.json +67 -0
- mas/lab/schemas/_schemas/dataset.schema.yaml +222 -0
- mas/lab/schemas/_schemas/experiment.schema.yaml +988 -0
- mas/lab/schemas/_schemas/fragments/memory-seed-entry.schema.yaml +26 -0
- mas/lab/schemas/_schemas/fragments/message.schema.yaml +16 -0
- mas/lab/schemas/_schemas/fragments/ref.schema.yaml +25 -0
- mas/lab/schemas/_schemas/fragments/tool-fixture-mapping.schema.yaml +46 -0
- mas/lab/schemas/_schemas/lab-config.schema.yaml +282 -0
- mas/lab/schemas/_schemas/lab.schema.yaml +65 -0
- mas/lab/schemas/_schemas/pipeline-manifest.schema.json +116 -0
- mas/lab/schemas/_schemas/pipeline.schema.yaml +230 -0
- mas/lab/schemas/_schemas/run-input.schema.yaml +155 -0
- mas/lab/schemas/paths.py +50 -0
- mas/lab/schemas/ui/agent-manifest.schema.json +286 -0
- mas/lab/schemas/ui/mas-manifest.schema.json +340 -0
- mas/lab/schemas/validate.py +89 -0
- mas/lab/schemas/workflow.py +63 -0
- mas/lab/telemetry/__init__.py +24 -0
- mas/lab/telemetry/otlp_push.py +10 -0
- mas/lab/templates/infra/llmprovider.yaml +19 -0
- mas/lab/tools/__init__.py +3 -0
- mas/lab/tools/experiment_designer_tools.py +301 -0
- mas/lab/utils/__init__.py +7 -0
- mas/lab/utils/output_formatter.py +191 -0
- mas_lab_component_core-0.2.0.dist-info/METADATA +12 -0
- mas_lab_component_core-0.2.0.dist-info/RECORD +139 -0
- mas_lab_component_core-0.2.0.dist-info/WHEEL +4 -0
- mas_lab_component_core-0.2.0.dist-info/entry_points.txt +5 -0
mas/lab/artifacts.py
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Typed artifact value objects for the mas-lab processor/pipeline system.
|
|
4
|
+
|
|
5
|
+
Artifacts are the strongly-typed values that flow between :class:`Processor`
|
|
6
|
+
instances and pipeline steps. Every artifact has:
|
|
7
|
+
|
|
8
|
+
* a ``kind`` class-variable — the canonical type name used in YAML / CLI
|
|
9
|
+
* an optional ``path`` — when the artifact is disk-backed (file)
|
|
10
|
+
* an optional ``data`` — when the artifact is in-memory only
|
|
11
|
+
* a ``meta`` dict — provenance, run_id, timestamps, …
|
|
12
|
+
|
|
13
|
+
Hierarchy::
|
|
14
|
+
|
|
15
|
+
Artifact
|
|
16
|
+
├── Trajectory ← raw JSONL event trace
|
|
17
|
+
│ └── AnnotatedTrajectory ← + highlights + analyst notes
|
|
18
|
+
├── PlotFile ← rendered HTML / SVG / PNG
|
|
19
|
+
├── NormalizedTrace ← normalized manifold trace (JSONL)
|
|
20
|
+
└── KnowledgeGraph ← JSON-LD graph file
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any, ClassVar, Dict, List, Optional, Type
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Base
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class Artifact:
|
|
36
|
+
"""Base value object for all typed artifacts.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
path:
|
|
41
|
+
File path when the artifact lives on disk.
|
|
42
|
+
data:
|
|
43
|
+
In-memory payload (list, dict, str, …) when the artifact is not
|
|
44
|
+
backed by a file, or has already been loaded from one.
|
|
45
|
+
meta:
|
|
46
|
+
Arbitrary provenance metadata (run_id, timestamps, labels …).
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
kind: ClassVar[str] = "artifact"
|
|
50
|
+
|
|
51
|
+
path: Optional[Path] = None
|
|
52
|
+
data: Any = None
|
|
53
|
+
meta: Dict[str, Any] = field(default_factory=dict)
|
|
54
|
+
|
|
55
|
+
def is_file(self) -> bool:
|
|
56
|
+
"""True iff ``path`` points to an existing file."""
|
|
57
|
+
return self.path is not None and Path(self.path).exists()
|
|
58
|
+
|
|
59
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
60
|
+
return {
|
|
61
|
+
"kind": self.kind,
|
|
62
|
+
"path": str(self.path) if self.path else None,
|
|
63
|
+
"meta": self.meta,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
def __repr__(self) -> str:
|
|
67
|
+
loc = f"path={self.path}" if self.path else "in-memory"
|
|
68
|
+
return f"<{self.__class__.__name__} {loc}>"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
# Trajectory family
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class Trajectory(Artifact):
|
|
77
|
+
"""Raw MAS execution trace — a list of OTEL-style event dicts.
|
|
78
|
+
|
|
79
|
+
The ``events`` list is populated lazily: calling :meth:`load` reads the
|
|
80
|
+
JSONL file at ``path`` if ``events`` is empty.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
kind: ClassVar[str] = "trajectory"
|
|
84
|
+
|
|
85
|
+
events: List[Dict[str, Any]] = field(default_factory=list)
|
|
86
|
+
run_id: str = ""
|
|
87
|
+
|
|
88
|
+
def load(self) -> "Trajectory":
|
|
89
|
+
"""Load events from ``path`` (or ``run_id``) into ``events`` (idempotent)."""
|
|
90
|
+
if not self.events:
|
|
91
|
+
source = self.path or (self.run_id or None)
|
|
92
|
+
if source:
|
|
93
|
+
from mas.lab.plots.trajectory import load_trace # lazy import
|
|
94
|
+
self.events = load_trace(source)
|
|
95
|
+
return self
|
|
96
|
+
|
|
97
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
98
|
+
d = super().as_dict()
|
|
99
|
+
d["run_id"] = self.run_id
|
|
100
|
+
return d
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class AnnotatedTrajectory(Trajectory):
|
|
105
|
+
"""Trajectory enriched with analyst highlights and free-form notes.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
highlights:
|
|
110
|
+
Each entry is a correlation-id prefix (e.g. ``"f19445b6"``) **or** a
|
|
111
|
+
1-based delegation index (e.g. ``"3"``).
|
|
112
|
+
notes:
|
|
113
|
+
List of ``{"index": int, "note": str}`` dicts — one per highlighted
|
|
114
|
+
delegation. Rendered as tooltips or annotations in plots.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
kind: ClassVar[str] = "annotated_trajectory"
|
|
118
|
+
|
|
119
|
+
highlights: List[str] = field(default_factory=list)
|
|
120
|
+
notes: List[Dict[str, Any]] = field(default_factory=list)
|
|
121
|
+
|
|
122
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
123
|
+
d = super().as_dict()
|
|
124
|
+
d["highlights"] = self.highlights
|
|
125
|
+
d["notes"] = self.notes
|
|
126
|
+
return d
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# Plot artifacts
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
@dataclass
|
|
134
|
+
class PlotFile(Artifact):
|
|
135
|
+
"""A rendered visualisation written to disk (HTML, SVG, PNG …)."""
|
|
136
|
+
|
|
137
|
+
kind: ClassVar[str] = "plot_file"
|
|
138
|
+
|
|
139
|
+
format: str = "html"
|
|
140
|
+
"""One of: html, svg, mermaid, png."""
|
|
141
|
+
|
|
142
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
143
|
+
d = super().as_dict()
|
|
144
|
+
d["format"] = self.format
|
|
145
|
+
return d
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# ---------------------------------------------------------------------------
|
|
149
|
+
# Knowledge-graph family
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
@dataclass
|
|
153
|
+
class NormalizedTrace(Artifact):
|
|
154
|
+
"""Normalized manifold trace (JSONL) ready for KG construction."""
|
|
155
|
+
|
|
156
|
+
kind: ClassVar[str] = "normalized_trace"
|
|
157
|
+
|
|
158
|
+
run_id: str = ""
|
|
159
|
+
|
|
160
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
161
|
+
d = super().as_dict()
|
|
162
|
+
d["run_id"] = self.run_id
|
|
163
|
+
return d
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@dataclass
|
|
167
|
+
class KnowledgeGraph(Artifact):
|
|
168
|
+
"""JSON-LD knowledge graph produced by the manifold normalizer."""
|
|
169
|
+
|
|
170
|
+
kind: ClassVar[str] = "knowledge_graph"
|
|
171
|
+
|
|
172
|
+
run_id: str = ""
|
|
173
|
+
|
|
174
|
+
def load_json(self) -> Any:
|
|
175
|
+
"""Read and return the JSON-LD dict from ``path``."""
|
|
176
|
+
if self.path:
|
|
177
|
+
return json.loads(Path(self.path).read_text(encoding="utf-8"))
|
|
178
|
+
return self.data
|
|
179
|
+
|
|
180
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
181
|
+
d = super().as_dict()
|
|
182
|
+
d["run_id"] = self.run_id
|
|
183
|
+
return d
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# ---------------------------------------------------------------------------
|
|
187
|
+
# MCE session-metrics artefacts
|
|
188
|
+
# ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
@dataclass
|
|
191
|
+
class SessionMetrics(Artifact):
|
|
192
|
+
"""MCE metric scores for one MAS session.
|
|
193
|
+
|
|
194
|
+
Written as ``metrics.json`` next to ``run_info.json`` in each run folder
|
|
195
|
+
(``item{N}/r{K}/metrics.json``) by :class:`EvalMceBatchStep`.
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
item_id:
|
|
200
|
+
Dataset item identifier (e.g. ``"1"``).
|
|
201
|
+
scenario:
|
|
202
|
+
Benchmark scenario name (e.g. ``"baseline"``).
|
|
203
|
+
session:
|
|
204
|
+
Session-level scores keyed by metric_id.
|
|
205
|
+
Each value is ``{"value": float|None, "reasoning": str, "error": str|None}``.
|
|
206
|
+
agents:
|
|
207
|
+
Per-agent scores (empty in schema_version="1").
|
|
208
|
+
Reserved for future extension.
|
|
209
|
+
schema_version:
|
|
210
|
+
Artefact format version. Increment on breaking changes.
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
kind: ClassVar[str] = "session_metrics"
|
|
214
|
+
|
|
215
|
+
item_id: str = ""
|
|
216
|
+
scenario: str = ""
|
|
217
|
+
session: Dict[str, Any] = field(default_factory=dict)
|
|
218
|
+
agents: Dict[str, Any] = field(default_factory=dict)
|
|
219
|
+
schema_version: str = "1"
|
|
220
|
+
|
|
221
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
222
|
+
d = super().as_dict()
|
|
223
|
+
d.update({
|
|
224
|
+
"schema_version": self.schema_version,
|
|
225
|
+
"item_id": self.item_id,
|
|
226
|
+
"scenario": self.scenario,
|
|
227
|
+
"session": self.session,
|
|
228
|
+
"agents": self.agents,
|
|
229
|
+
})
|
|
230
|
+
return d
|
|
231
|
+
|
|
232
|
+
@classmethod
|
|
233
|
+
def from_json(cls, path: Path) -> "SessionMetrics":
|
|
234
|
+
"""Load a ``metrics.json`` file from *path*."""
|
|
235
|
+
doc = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
236
|
+
return cls(
|
|
237
|
+
path=path,
|
|
238
|
+
item_id=doc.get("item_id", ""),
|
|
239
|
+
scenario=doc.get("scenario", ""),
|
|
240
|
+
session=doc.get("session", {}),
|
|
241
|
+
agents=doc.get("agents", {}),
|
|
242
|
+
schema_version=doc.get("schema_version", "1"),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# ---------------------------------------------------------------------------
|
|
247
|
+
# Registry
|
|
248
|
+
# ---------------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
_ARTIFACT_REGISTRY: Dict[str, Type[Artifact]] = {
|
|
251
|
+
"artifact": Artifact,
|
|
252
|
+
"trajectory": Trajectory,
|
|
253
|
+
"annotated_trajectory": AnnotatedTrajectory,
|
|
254
|
+
"plot_file": PlotFile,
|
|
255
|
+
"normalized_trace": NormalizedTrace,
|
|
256
|
+
"knowledge_graph": KnowledgeGraph,
|
|
257
|
+
"session_metrics": SessionMetrics,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def register_artifact(cls: Type[Artifact]) -> Type[Artifact]:
|
|
262
|
+
"""Class decorator — register a custom Artifact subclass by its ``kind``."""
|
|
263
|
+
_ARTIFACT_REGISTRY[cls.kind] = cls
|
|
264
|
+
return cls
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def artifact_from_dict(d: Dict[str, Any]) -> Artifact:
|
|
268
|
+
"""Reconstruct an Artifact from a dict produced by :meth:`Artifact.as_dict`."""
|
|
269
|
+
cls = _ARTIFACT_REGISTRY.get(d.get("kind", "artifact"), Artifact)
|
|
270
|
+
path = Path(d["path"]) if d.get("path") else None
|
|
271
|
+
inst = cls.__new__(cls)
|
|
272
|
+
# call dataclass __init__ with just the base fields; subclass extras default
|
|
273
|
+
Artifact.__init__(inst, path=path, meta=d.get("meta", {}))
|
|
274
|
+
if hasattr(inst, "run_id") and "run_id" in d:
|
|
275
|
+
inst.run_id = d["run_id"]
|
|
276
|
+
if hasattr(inst, "highlights") and "highlights" in d:
|
|
277
|
+
inst.highlights = d["highlights"]
|
|
278
|
+
if hasattr(inst, "notes") and "notes" in d:
|
|
279
|
+
inst.notes = d["notes"]
|
|
280
|
+
if hasattr(inst, "format") and "format" in d:
|
|
281
|
+
inst.format = d["format"]
|
|
282
|
+
return inst
|
mas/lab/cli/__init__.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""mas-lab click CLI — ``app`` is the single entry point.
|
|
4
|
+
|
|
5
|
+
Import and use programmatically::
|
|
6
|
+
|
|
7
|
+
from mas.lab.cli import app
|
|
8
|
+
from click.testing import CliRunner
|
|
9
|
+
|
|
10
|
+
result = CliRunner().invoke(app, ["benchmark", "list"])
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import importlib
|
|
15
|
+
import logging
|
|
16
|
+
|
|
17
|
+
import click
|
|
18
|
+
import mas.third_party_otel # noqa: F401
|
|
19
|
+
from dotenv import find_dotenv, load_dotenv
|
|
20
|
+
from mas.lab.cli.commands import check
|
|
21
|
+
from mas.lab.cli.commands import config as _config_mod
|
|
22
|
+
from mas.lab.cli.commands import init as _init_mod
|
|
23
|
+
from mas.lab.cli.commands import validate as _validate_mod
|
|
24
|
+
from mas.lab.cli.extensions import register_extension_components
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.group()
|
|
28
|
+
@click.option("-v", "--verbose", count=True, default=0,
|
|
29
|
+
help="Verbosity: -v exchange log, -vv debug, -vvv full")
|
|
30
|
+
@click.pass_context
|
|
31
|
+
def app(ctx: click.Context, verbose: int) -> None:
|
|
32
|
+
"""MAS Lab — evaluation, experimentation, and benchmarking tools.
|
|
33
|
+
|
|
34
|
+
\b
|
|
35
|
+
Command groups:
|
|
36
|
+
benchmark Run experiments, list results, manage steps
|
|
37
|
+
control Start/stop the controller daemon (UI + workers)
|
|
38
|
+
worker List and manage background workers
|
|
39
|
+
plot Generate trajectory and communication diagrams
|
|
40
|
+
run Execute processors and pipelines
|
|
41
|
+
pipe Stream-oriented processing
|
|
42
|
+
|
|
43
|
+
\b
|
|
44
|
+
Utilities:
|
|
45
|
+
check Validate a MAS configuration
|
|
46
|
+
config Show resolved paths and environment configuration
|
|
47
|
+
serve Start the benchmark UI server
|
|
48
|
+
telemetry Convert, verify, push, and dump traces (library-telemetry)
|
|
49
|
+
kg Normalize, validate, push, and dump graphs (library-kg)
|
|
50
|
+
eval Score traces with MCE metrics (library-eval)
|
|
51
|
+
validate Validate YAML manifests
|
|
52
|
+
|
|
53
|
+
\b
|
|
54
|
+
Optional extensions are discovered via the ``mas.lab.cli.components``
|
|
55
|
+
entry-point group and loaded automatically when their packages are installed.
|
|
56
|
+
|
|
57
|
+
\b
|
|
58
|
+
Global options:
|
|
59
|
+
-v Show exchange log
|
|
60
|
+
-vv Debug output
|
|
61
|
+
-vvv Full trace output
|
|
62
|
+
"""
|
|
63
|
+
ctx.ensure_object(dict)
|
|
64
|
+
ctx.obj["verbose"] = verbose
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
from mas.ctl.logging_setup import setup_logging
|
|
68
|
+
setup_logging(verbosity=verbose)
|
|
69
|
+
except ImportError:
|
|
70
|
+
level = (logging.DEBUG if verbose >= 2
|
|
71
|
+
else logging.INFO if verbose >= 1
|
|
72
|
+
else logging.WARNING)
|
|
73
|
+
logging.basicConfig(level=level, format="%(message)s")
|
|
74
|
+
|
|
75
|
+
# Workspace config — loaded once, shared via ctx.obj
|
|
76
|
+
try:
|
|
77
|
+
from mas.lab.workspace import WorkspaceConfig
|
|
78
|
+
ctx.obj["workspace"] = WorkspaceConfig.load()
|
|
79
|
+
except ImportError:
|
|
80
|
+
ctx.obj["workspace"] = None
|
|
81
|
+
|
|
82
|
+
load_dotenv(find_dotenv(usecwd=True))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _add_if_importable(name: str, module: str, attr: str) -> None:
|
|
86
|
+
"""Attach a command that lives in an optional lab component."""
|
|
87
|
+
try:
|
|
88
|
+
mod = importlib.import_module(module)
|
|
89
|
+
except ImportError:
|
|
90
|
+
return
|
|
91
|
+
app.add_command(getattr(mod, attr), name=name)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
app.add_command(check.check_cmd, name="check")
|
|
95
|
+
app.add_command(check.check_config_cmd, name="check-config")
|
|
96
|
+
app.add_command(_config_mod.config_cmd, name="config")
|
|
97
|
+
app.add_command(_init_mod.init_cmd, name="init")
|
|
98
|
+
app.add_command(_validate_mod.validate_cmd, name="validate")
|
|
99
|
+
_add_if_importable("serve", "mas.lab.cli.commands.serve", "serve_cmd")
|
|
100
|
+
_add_if_importable("benchmark", "mas.lab.cli.commands.benchmark", "group")
|
|
101
|
+
_add_if_importable("plot", "mas.lab.cli.commands.plot", "plot_group")
|
|
102
|
+
_add_if_importable("telemetry", "mas.lab.cli.commands.telemetry", "telemetry_group")
|
|
103
|
+
_add_if_importable("run", "mas.lab.cli.commands.run", "run_group")
|
|
104
|
+
_add_if_importable("pipe", "mas.lab.cli.commands.pipe", "pipe_group")
|
|
105
|
+
|
|
106
|
+
# Register optional command groups from installed extension packages.
|
|
107
|
+
register_extension_components(app)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def main() -> None:
|
|
111
|
+
app()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
if __name__ == "__main__":
|
|
115
|
+
app()
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Click wrappers for ``mas-lab benchmark`` subcommands."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from .analyze import analyze_cmd
|
|
10
|
+
from .artifacts import artifact_group
|
|
11
|
+
from .clean import clean_cmd
|
|
12
|
+
from .daemon import daemon_group
|
|
13
|
+
from .export_cmd import export_cmd
|
|
14
|
+
from .follow import follow_cmd
|
|
15
|
+
from .import_cmd import import_cmd
|
|
16
|
+
from .import_hf import import_hf_cmd
|
|
17
|
+
from .list_cmd import list_cmd
|
|
18
|
+
from .migrate import migrate_cmd
|
|
19
|
+
from .pipeline import pipeline_group
|
|
20
|
+
from .publish_hf import publish_hf_cmd
|
|
21
|
+
from .rename import rename_cmd
|
|
22
|
+
from .run import run_cmd
|
|
23
|
+
from .show import show_cmd
|
|
24
|
+
from .step import step_group
|
|
25
|
+
from .step_info import step_info_cmd
|
|
26
|
+
from .update import update_cmd
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@click.group("benchmark")
|
|
30
|
+
def group() -> None:
|
|
31
|
+
"""Benchmark management — run, list, show, update, analyze, step."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
group.add_command(run_cmd)
|
|
35
|
+
group.add_command(list_cmd)
|
|
36
|
+
group.add_command(show_cmd)
|
|
37
|
+
group.add_command(artifact_group)
|
|
38
|
+
group.add_command(update_cmd)
|
|
39
|
+
group.add_command(rename_cmd)
|
|
40
|
+
group.add_command(migrate_cmd)
|
|
41
|
+
group.add_command(analyze_cmd)
|
|
42
|
+
group.add_command(export_cmd)
|
|
43
|
+
group.add_command(import_cmd)
|
|
44
|
+
group.add_command(publish_hf_cmd)
|
|
45
|
+
group.add_command(import_hf_cmd)
|
|
46
|
+
group.add_command(follow_cmd)
|
|
47
|
+
group.add_command(clean_cmd)
|
|
48
|
+
group.add_command(step_info_cmd)
|
|
49
|
+
group.add_command(step_group)
|
|
50
|
+
group.add_command(pipeline_group)
|
|
51
|
+
group.add_command(daemon_group)
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"group",
|
|
55
|
+
"run_cmd",
|
|
56
|
+
"list_cmd",
|
|
57
|
+
"show_cmd",
|
|
58
|
+
"artifact_group",
|
|
59
|
+
"update_cmd",
|
|
60
|
+
"rename_cmd",
|
|
61
|
+
"migrate_cmd",
|
|
62
|
+
"analyze_cmd",
|
|
63
|
+
"export_cmd",
|
|
64
|
+
"import_cmd",
|
|
65
|
+
"publish_hf_cmd",
|
|
66
|
+
"import_hf_cmd",
|
|
67
|
+
"follow_cmd",
|
|
68
|
+
"clean_cmd",
|
|
69
|
+
"step_info_cmd",
|
|
70
|
+
"step_group",
|
|
71
|
+
"pipeline_group",
|
|
72
|
+
"daemon_group",
|
|
73
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""``mas-lab benchmark analyze`` command."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from types import SimpleNamespace
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command("analyze")
|
|
13
|
+
@click.argument("benchmark_id")
|
|
14
|
+
@click.option("--experiment-yaml", type=Path, default=None,
|
|
15
|
+
help="Path to experiment.yaml (auto-detected if omitted).")
|
|
16
|
+
@click.option("-o", "--output-dir", type=Path, default=None)
|
|
17
|
+
def analyze_cmd(benchmark_id: str, experiment_yaml: Path | None,
|
|
18
|
+
output_dir: Path | None) -> None:
|
|
19
|
+
"""Regenerate statistics and plots from existing results."""
|
|
20
|
+
from mas.lab.benchmark.cli import analyze_command
|
|
21
|
+
|
|
22
|
+
args = SimpleNamespace(benchmark_id=benchmark_id,
|
|
23
|
+
experiment_yaml=experiment_yaml,
|
|
24
|
+
output_dir=output_dir)
|
|
25
|
+
raise SystemExit(analyze_command(args))
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""``mas-lab benchmark artifact`` commands."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from types import SimpleNamespace
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group("artifact")
|
|
12
|
+
def artifact_group() -> None:
|
|
13
|
+
"""List and show artifacts for an experiment."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
_LEVEL = click.Choice(["application", "scenario", "test", "run"], case_sensitive=False)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@artifact_group.command("list")
|
|
20
|
+
@click.argument("target")
|
|
21
|
+
@click.option("-t", "--type", "artifact_type", default=None, metavar="TYPE",
|
|
22
|
+
help="Filter by artifact type.")
|
|
23
|
+
@click.option("-l", "--level", "level", type=_LEVEL, default=None,
|
|
24
|
+
help="Filter to one hierarchy level.")
|
|
25
|
+
@click.option("-v", "--verbose", is_flag=True, default=False,
|
|
26
|
+
help="Include the type description.")
|
|
27
|
+
def artifact_list_cmd(
|
|
28
|
+
target: str,
|
|
29
|
+
artifact_type: str | None,
|
|
30
|
+
level: str | None,
|
|
31
|
+
verbose: bool,
|
|
32
|
+
) -> None:
|
|
33
|
+
"""List artifacts declared in the experiment, and whether each was generated.
|
|
34
|
+
|
|
35
|
+
TARGET is ``last``, a benchmark id, or an experiment YAML. The run directory
|
|
36
|
+
is resolved from the last-run pointer or the experiment identity — not passed
|
|
37
|
+
as an argument.
|
|
38
|
+
"""
|
|
39
|
+
from mas.lab.benchmark.cli import artifact_list_command
|
|
40
|
+
|
|
41
|
+
raise SystemExit(artifact_list_command(SimpleNamespace(
|
|
42
|
+
target=target,
|
|
43
|
+
artifact_type=artifact_type,
|
|
44
|
+
level=level,
|
|
45
|
+
verbose=verbose,
|
|
46
|
+
)))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@artifact_group.command("show")
|
|
50
|
+
@click.argument("artifact_id")
|
|
51
|
+
def artifact_show_cmd(artifact_id: str) -> None:
|
|
52
|
+
"""Show one generated artifact by its 8-character id."""
|
|
53
|
+
from mas.lab.benchmark.cli import show_artifact_by_id_command
|
|
54
|
+
|
|
55
|
+
raise SystemExit(show_artifact_by_id_command(SimpleNamespace(
|
|
56
|
+
artifact_id=artifact_id,
|
|
57
|
+
)))
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""``mas-lab benchmark clean`` command."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from types import SimpleNamespace
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command("clean")
|
|
13
|
+
@click.argument("experiment_yaml", type=Path, metavar="EXPERIMENT_YAML")
|
|
14
|
+
@click.option("--scenario", "scenarios", multiple=True, metavar="SCENARIO_ID",
|
|
15
|
+
help="Scenario ID(s) to clean. Repeatable. If omitted, ALL scenarios are cleaned.")
|
|
16
|
+
@click.option("--dry-run", is_flag=True, default=False,
|
|
17
|
+
help="Print what would be deleted without deleting anything.")
|
|
18
|
+
@click.option("--keep-traces", is_flag=True, default=False,
|
|
19
|
+
help="Delete only the scenario run dirs; keep the global trace-cache entries.")
|
|
20
|
+
@click.option("-o", "--output-dir", type=Path, default=None,
|
|
21
|
+
help="Override the output dir directly (use when experiment.yaml is unavailable; pass '-' as EXPERIMENT_YAML).") # noqa: E501
|
|
22
|
+
def clean_cmd(
|
|
23
|
+
experiment_yaml: Path,
|
|
24
|
+
scenarios: tuple[str, ...],
|
|
25
|
+
dry_run: bool,
|
|
26
|
+
keep_traces: bool,
|
|
27
|
+
output_dir: Path | None,
|
|
28
|
+
) -> None:
|
|
29
|
+
"""Delete cached runs for selected scenarios so they are re-executed on the next run.
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
|
|
33
|
+
\b
|
|
34
|
+
# Clean only the ToT scenario
|
|
35
|
+
mas-lab benchmark clean labs/design-space.lab/01-design-patterns/experiment.yaml \\
|
|
36
|
+
--scenario pattern-tree-of-thoughts
|
|
37
|
+
|
|
38
|
+
\b
|
|
39
|
+
# Dry-run: see what would be removed
|
|
40
|
+
mas-lab benchmark clean labs/design-space.lab/01-design-patterns/experiment.yaml \\
|
|
41
|
+
--scenario pattern-tree-of-thoughts --dry-run
|
|
42
|
+
|
|
43
|
+
\b
|
|
44
|
+
# Clean all scenarios in an experiment
|
|
45
|
+
mas-lab benchmark clean labs/design-space.lab/01-design-patterns/experiment.yaml
|
|
46
|
+
"""
|
|
47
|
+
from mas.lab.benchmark.cli import clean_command
|
|
48
|
+
|
|
49
|
+
args = SimpleNamespace(
|
|
50
|
+
experiment_yaml=experiment_yaml,
|
|
51
|
+
scenarios=list(scenarios) if scenarios else None,
|
|
52
|
+
dry_run=dry_run,
|
|
53
|
+
keep_traces=keep_traces,
|
|
54
|
+
output_dir=output_dir,
|
|
55
|
+
)
|
|
56
|
+
raise SystemExit(clean_command(args))
|