nasde-toolkit 0.3.2__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.
- nasde_toolkit/__init__.py +5 -0
- nasde_toolkit/_bundled_skills/nasde-benchmark-creator/SKILL.md +321 -0
- nasde_toolkit/_bundled_skills/nasde-benchmark-from-history/SKILL.md +298 -0
- nasde_toolkit/_bundled_skills/nasde-benchmark-from-public-repos/SKILL.md +303 -0
- nasde_toolkit/_bundled_skills/nasde-benchmark-runner/SKILL.md +388 -0
- nasde_toolkit/_version.py +24 -0
- nasde_toolkit/agents/__init__.py +1 -0
- nasde_toolkit/agents/configurable_claude.py +121 -0
- nasde_toolkit/agents/configurable_codex.py +101 -0
- nasde_toolkit/agents/configurable_gemini.py +194 -0
- nasde_toolkit/banner.py +82 -0
- nasde_toolkit/cli.py +513 -0
- nasde_toolkit/config.py +206 -0
- nasde_toolkit/docker.py +221 -0
- nasde_toolkit/evaluator.py +663 -0
- nasde_toolkit/evaluator_backends/__init__.py +21 -0
- nasde_toolkit/evaluator_backends/claude_subprocess.py +172 -0
- nasde_toolkit/evaluator_backends/codex_subprocess.py +152 -0
- nasde_toolkit/evaluator_backends/protocol.py +30 -0
- nasde_toolkit/runner.py +739 -0
- nasde_toolkit/scaffold/__init__.py +161 -0
- nasde_toolkit/skills_installer.py +83 -0
- nasde_toolkit/update_check.py +166 -0
- nasde_toolkit-0.3.2.dist-info/METADATA +701 -0
- nasde_toolkit-0.3.2.dist-info/RECORD +28 -0
- nasde_toolkit-0.3.2.dist-info/WHEEL +4 -0
- nasde_toolkit-0.3.2.dist-info/entry_points.txt +2 -0
- nasde_toolkit-0.3.2.dist-info/licenses/LICENSE +21 -0
nasde_toolkit/cli.py
ADDED
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
"""CLI entry point for nasde.
|
|
2
|
+
|
|
3
|
+
Core commands (run, eval, init) use Harbor/Opik Python APIs directly.
|
|
4
|
+
Pass-through commands (harbor, opik) delegate to the respective CLIs.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
import typer
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.panel import Panel
|
|
17
|
+
from typer.core import TyperGroup
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from nasde_toolkit.config import ProjectConfig
|
|
21
|
+
|
|
22
|
+
console = Console()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class _BannerGroup(TyperGroup):
|
|
26
|
+
def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
|
27
|
+
from nasde_toolkit.banner import print_banner
|
|
28
|
+
|
|
29
|
+
print_banner(console)
|
|
30
|
+
super().format_help(ctx, formatter)
|
|
31
|
+
|
|
32
|
+
def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None:
|
|
33
|
+
cmd = super().get_command(ctx, cmd_name)
|
|
34
|
+
if cmd is not None:
|
|
35
|
+
original_format_help = cmd.format_help
|
|
36
|
+
|
|
37
|
+
def patched_format_help(ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
|
38
|
+
from nasde_toolkit.banner import print_banner
|
|
39
|
+
|
|
40
|
+
print_banner(console)
|
|
41
|
+
original_format_help(ctx, formatter)
|
|
42
|
+
|
|
43
|
+
cmd.format_help = patched_format_help # type: ignore[assignment]
|
|
44
|
+
return cmd
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
app = typer.Typer(
|
|
48
|
+
name="nasde",
|
|
49
|
+
help="Noesis Agentic Software Development Evals Toolkit",
|
|
50
|
+
rich_markup_mode="rich",
|
|
51
|
+
cls=_BannerGroup,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _version_callback(value: bool) -> None:
|
|
56
|
+
if value:
|
|
57
|
+
from nasde_toolkit.banner import print_banner
|
|
58
|
+
|
|
59
|
+
print_banner(console)
|
|
60
|
+
raise typer.Exit()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _no_banner_callback(value: bool) -> None:
|
|
64
|
+
if value:
|
|
65
|
+
from nasde_toolkit.banner import suppress_banner
|
|
66
|
+
|
|
67
|
+
suppress_banner()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@app.callback(invoke_without_command=True)
|
|
71
|
+
def main(
|
|
72
|
+
ctx: typer.Context,
|
|
73
|
+
version: bool = typer.Option(
|
|
74
|
+
False,
|
|
75
|
+
"--version",
|
|
76
|
+
"-v",
|
|
77
|
+
help="Show version and exit.",
|
|
78
|
+
callback=_version_callback,
|
|
79
|
+
is_eager=True,
|
|
80
|
+
),
|
|
81
|
+
no_banner: bool = typer.Option(
|
|
82
|
+
False,
|
|
83
|
+
"--no-banner",
|
|
84
|
+
help="Suppress ASCII banner. Also: NASDE_NO_BANNER=1.",
|
|
85
|
+
callback=_no_banner_callback,
|
|
86
|
+
is_eager=True,
|
|
87
|
+
),
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Noesis Agentic Software Development Evals Toolkit."""
|
|
90
|
+
from nasde_toolkit._version import __version__
|
|
91
|
+
from nasde_toolkit.update_check import maybe_notify_update
|
|
92
|
+
|
|
93
|
+
maybe_notify_update(console, current_version=__version__)
|
|
94
|
+
if ctx.invoked_subcommand is None:
|
|
95
|
+
console.print(ctx.get_help())
|
|
96
|
+
raise typer.Exit()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@app.command()
|
|
100
|
+
def init(
|
|
101
|
+
project_dir: Path = typer.Argument(
|
|
102
|
+
Path("."),
|
|
103
|
+
help="Directory to scaffold the evaluation project in.",
|
|
104
|
+
),
|
|
105
|
+
name: str = typer.Option(
|
|
106
|
+
"",
|
|
107
|
+
"--name",
|
|
108
|
+
"-n",
|
|
109
|
+
help="Project name (defaults to directory name).",
|
|
110
|
+
),
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Scaffold a new evaluation project."""
|
|
113
|
+
from nasde_toolkit.banner import print_banner
|
|
114
|
+
from nasde_toolkit.scaffold import create_project
|
|
115
|
+
|
|
116
|
+
print_banner(console)
|
|
117
|
+
project_name = name or project_dir.resolve().name
|
|
118
|
+
create_project(project_dir.resolve(), project_name)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@app.command(name="install-skills")
|
|
122
|
+
def install_skills(
|
|
123
|
+
scope: str = typer.Option(
|
|
124
|
+
"user",
|
|
125
|
+
"--scope",
|
|
126
|
+
help="Where to install: 'user' (~/.claude/skills) or 'project' (./.claude/skills).",
|
|
127
|
+
),
|
|
128
|
+
target_dir: Path | None = typer.Option(
|
|
129
|
+
None,
|
|
130
|
+
"--target-dir",
|
|
131
|
+
help="Custom skills directory (overrides --scope).",
|
|
132
|
+
),
|
|
133
|
+
force: bool = typer.Option(
|
|
134
|
+
False,
|
|
135
|
+
"--force",
|
|
136
|
+
"-f",
|
|
137
|
+
help="Overwrite existing skill files.",
|
|
138
|
+
),
|
|
139
|
+
) -> None:
|
|
140
|
+
"""Install NASDE authoring skills into a Claude Code skills directory."""
|
|
141
|
+
from nasde_toolkit.banner import print_banner
|
|
142
|
+
from nasde_toolkit.skills_installer import install_bundled_skills
|
|
143
|
+
|
|
144
|
+
print_banner(console)
|
|
145
|
+
install_bundled_skills(console=console, scope=scope, target_dir=target_dir, force=force)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@app.command()
|
|
149
|
+
def run(
|
|
150
|
+
variant: str | None = typer.Option(
|
|
151
|
+
None,
|
|
152
|
+
"--variant",
|
|
153
|
+
help="Variant to run (defaults to config default).",
|
|
154
|
+
),
|
|
155
|
+
all_variants: bool = typer.Option(
|
|
156
|
+
False,
|
|
157
|
+
"--all-variants",
|
|
158
|
+
help="Run all available variants (Cartesian product with tasks).",
|
|
159
|
+
),
|
|
160
|
+
tasks: str | None = typer.Option(
|
|
161
|
+
None,
|
|
162
|
+
"--tasks",
|
|
163
|
+
help="Comma-separated task names to run.",
|
|
164
|
+
),
|
|
165
|
+
model: str | None = typer.Option(
|
|
166
|
+
None,
|
|
167
|
+
"--model",
|
|
168
|
+
help="Model override.",
|
|
169
|
+
),
|
|
170
|
+
timeout: int | None = typer.Option(
|
|
171
|
+
None,
|
|
172
|
+
"--timeout",
|
|
173
|
+
help="Agent timeout in seconds.",
|
|
174
|
+
),
|
|
175
|
+
with_opik: bool = typer.Option(
|
|
176
|
+
False,
|
|
177
|
+
"--with-opik",
|
|
178
|
+
help="Enable Opik tracing.",
|
|
179
|
+
),
|
|
180
|
+
attempts: int = typer.Option(
|
|
181
|
+
1,
|
|
182
|
+
"--attempts",
|
|
183
|
+
"-n",
|
|
184
|
+
help="Number of independent attempts per task (Harbor n_attempts).",
|
|
185
|
+
),
|
|
186
|
+
without_eval: bool = typer.Option(
|
|
187
|
+
False,
|
|
188
|
+
"--without-eval",
|
|
189
|
+
help="Skip assessment evaluation after benchmark.",
|
|
190
|
+
),
|
|
191
|
+
job_suffix: str | None = typer.Option(
|
|
192
|
+
None,
|
|
193
|
+
"--job-suffix",
|
|
194
|
+
help="Custom suffix for job directory name (default: random 6-char hex).",
|
|
195
|
+
),
|
|
196
|
+
max_concurrent_eval: int = typer.Option(
|
|
197
|
+
10,
|
|
198
|
+
"--max-concurrent-eval",
|
|
199
|
+
help="Max concurrent assessment evaluations (default: 10).",
|
|
200
|
+
),
|
|
201
|
+
harbor_env: str | None = typer.Option(
|
|
202
|
+
None,
|
|
203
|
+
"--harbor-env",
|
|
204
|
+
help="Harbor execution environment (docker, daytona, modal, e2b, runloop, gke). Default: docker.",
|
|
205
|
+
),
|
|
206
|
+
project_dir: Path = typer.Option(
|
|
207
|
+
Path("."),
|
|
208
|
+
"--project-dir",
|
|
209
|
+
"-C",
|
|
210
|
+
help="Path to evaluation project.",
|
|
211
|
+
),
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Run benchmark tasks via Harbor."""
|
|
214
|
+
if variant and all_variants:
|
|
215
|
+
console.print("[red]ERROR: --variant and --all-variants are mutually exclusive.[/red]")
|
|
216
|
+
raise typer.Exit(1)
|
|
217
|
+
|
|
218
|
+
if not variant and not all_variants:
|
|
219
|
+
console.print("[red]ERROR: Specify --variant NAME or --all-variants.[/red]")
|
|
220
|
+
raise typer.Exit(1)
|
|
221
|
+
|
|
222
|
+
from nasde_toolkit.config import load_project_config
|
|
223
|
+
from nasde_toolkit.runner import collect_available_variants, run_benchmark
|
|
224
|
+
|
|
225
|
+
config = load_project_config(project_dir.resolve())
|
|
226
|
+
tasks_filter = [t.strip() for t in tasks.split(",")] if tasks else None
|
|
227
|
+
resolved_harbor_env = harbor_env or config.default_harbor_env
|
|
228
|
+
resolved_model = model
|
|
229
|
+
resolved_timeout = timeout
|
|
230
|
+
|
|
231
|
+
if all_variants:
|
|
232
|
+
variants_list = collect_available_variants(config.project_dir)
|
|
233
|
+
if not variants_list:
|
|
234
|
+
console.print("[red]ERROR: No variants found.[/red]")
|
|
235
|
+
raise typer.Exit(1)
|
|
236
|
+
|
|
237
|
+
_confirm_multi_variant_run(
|
|
238
|
+
variants=variants_list,
|
|
239
|
+
config=config,
|
|
240
|
+
tasks_filter=tasks_filter,
|
|
241
|
+
attempts=attempts,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# TODO: Parallel variant execution is possible but requires refactoring
|
|
245
|
+
# _run_job() to eliminate os.chdir() and isolate Opik tracking.
|
|
246
|
+
asyncio.run(
|
|
247
|
+
_run_all_variants(
|
|
248
|
+
config=config,
|
|
249
|
+
variants=variants_list,
|
|
250
|
+
model=resolved_model,
|
|
251
|
+
timeout_sec=resolved_timeout,
|
|
252
|
+
tasks_filter=tasks_filter,
|
|
253
|
+
with_opik=with_opik,
|
|
254
|
+
with_eval=not without_eval,
|
|
255
|
+
harbor_env=resolved_harbor_env,
|
|
256
|
+
n_attempts=attempts,
|
|
257
|
+
job_suffix=job_suffix,
|
|
258
|
+
max_concurrent_eval=max_concurrent_eval,
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
else:
|
|
262
|
+
assert variant is not None
|
|
263
|
+
resolved_variant = variant
|
|
264
|
+
from nasde_toolkit.runner import _resolve_model, load_variant_agent_type, resolve_variant_dir
|
|
265
|
+
|
|
266
|
+
variant_dir = resolve_variant_dir(config.project_dir, resolved_variant)
|
|
267
|
+
agent_type = load_variant_agent_type(variant_dir)
|
|
268
|
+
display_model = _resolve_model(resolved_model, variant_dir, config)
|
|
269
|
+
|
|
270
|
+
_print_run_header(
|
|
271
|
+
variant=resolved_variant,
|
|
272
|
+
model=display_model,
|
|
273
|
+
timeout=resolved_timeout,
|
|
274
|
+
tasks_filter=tasks_filter,
|
|
275
|
+
with_opik=with_opik,
|
|
276
|
+
with_eval=not without_eval,
|
|
277
|
+
harbor_env=resolved_harbor_env,
|
|
278
|
+
attempts=attempts,
|
|
279
|
+
agent_type=agent_type,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
asyncio.run(
|
|
283
|
+
run_benchmark(
|
|
284
|
+
config=config,
|
|
285
|
+
variant=resolved_variant,
|
|
286
|
+
model=resolved_model,
|
|
287
|
+
timeout_sec=resolved_timeout,
|
|
288
|
+
tasks_filter=tasks_filter,
|
|
289
|
+
with_opik=with_opik,
|
|
290
|
+
with_eval=not without_eval,
|
|
291
|
+
harbor_env=resolved_harbor_env,
|
|
292
|
+
n_attempts=attempts,
|
|
293
|
+
job_suffix=job_suffix,
|
|
294
|
+
max_concurrent_eval=max_concurrent_eval,
|
|
295
|
+
)
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@app.command(name="eval")
|
|
300
|
+
def eval_command(
|
|
301
|
+
job_dir: Path = typer.Argument(
|
|
302
|
+
...,
|
|
303
|
+
help="Path to job directory to evaluate.",
|
|
304
|
+
),
|
|
305
|
+
with_opik: bool = typer.Option(
|
|
306
|
+
False,
|
|
307
|
+
"--with-opik",
|
|
308
|
+
help="Upload scores to Opik.",
|
|
309
|
+
),
|
|
310
|
+
max_concurrent_eval: int = typer.Option(
|
|
311
|
+
10,
|
|
312
|
+
"--max-concurrent-eval",
|
|
313
|
+
help="Max concurrent assessment evaluations (default: 10).",
|
|
314
|
+
),
|
|
315
|
+
project_dir: Path = typer.Option(
|
|
316
|
+
Path("."),
|
|
317
|
+
"--project-dir",
|
|
318
|
+
"-C",
|
|
319
|
+
help="Path to evaluation project.",
|
|
320
|
+
),
|
|
321
|
+
) -> None:
|
|
322
|
+
"""Run post-hoc assessment of trial artifacts."""
|
|
323
|
+
from nasde_toolkit.config import load_project_config
|
|
324
|
+
from nasde_toolkit.evaluator import evaluate_job
|
|
325
|
+
|
|
326
|
+
config = load_project_config(project_dir.resolve())
|
|
327
|
+
|
|
328
|
+
from nasde_toolkit.banner import print_banner
|
|
329
|
+
|
|
330
|
+
print_banner(console)
|
|
331
|
+
console.print(
|
|
332
|
+
Panel(
|
|
333
|
+
f"[bold]Assessment Evaluation[/bold]\nJob: {job_dir}\nOpik: {'enabled' if with_opik else 'disabled'}",
|
|
334
|
+
title="nasde",
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
asyncio.run(
|
|
339
|
+
evaluate_job(
|
|
340
|
+
job_dir=job_dir.resolve(),
|
|
341
|
+
project_root=config.project_dir,
|
|
342
|
+
project_name=config.reporting.project_name,
|
|
343
|
+
with_opik=with_opik,
|
|
344
|
+
max_concurrent=max_concurrent_eval,
|
|
345
|
+
eval_config=config.evaluation,
|
|
346
|
+
)
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
# ---------------------------------------------------------------------------
|
|
351
|
+
# Harbor pass-through (Typer → Typer)
|
|
352
|
+
# ---------------------------------------------------------------------------
|
|
353
|
+
|
|
354
|
+
from harbor.cli.main import app as harbor_app
|
|
355
|
+
|
|
356
|
+
app.add_typer(harbor_app, name="harbor", help="Harbor CLI (pass-through).")
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
# ---------------------------------------------------------------------------
|
|
360
|
+
# Opik pass-through (Click → Typer via ctx.args)
|
|
361
|
+
# ---------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
@app.command(
|
|
365
|
+
name="opik",
|
|
366
|
+
context_settings={"allow_extra_args": True, "allow_interspersed_args": False},
|
|
367
|
+
)
|
|
368
|
+
def opik_passthrough(ctx: typer.Context) -> None:
|
|
369
|
+
"""Opik CLI commands (pass-through)."""
|
|
370
|
+
from opik.cli.main import cli as opik_cli
|
|
371
|
+
|
|
372
|
+
opik_cli(ctx.args, standalone_mode=False)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
# ---------------------------------------------------------------------------
|
|
376
|
+
# Display helpers
|
|
377
|
+
# ---------------------------------------------------------------------------
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _print_run_header(
|
|
381
|
+
variant: str,
|
|
382
|
+
model: str | None,
|
|
383
|
+
timeout: int | None,
|
|
384
|
+
tasks_filter: list[str] | None,
|
|
385
|
+
with_opik: bool,
|
|
386
|
+
with_eval: bool,
|
|
387
|
+
harbor_env: str | None = None,
|
|
388
|
+
attempts: int = 1,
|
|
389
|
+
agent_type: str = "claude",
|
|
390
|
+
) -> None:
|
|
391
|
+
from nasde_toolkit.banner import print_banner
|
|
392
|
+
|
|
393
|
+
print_banner(console)
|
|
394
|
+
|
|
395
|
+
tasks_str = ", ".join(tasks_filter) if tasks_filter else "all"
|
|
396
|
+
eval_str = "enabled" if with_eval else "[yellow]disabled[/yellow]"
|
|
397
|
+
env_str = harbor_env or "docker"
|
|
398
|
+
attempts_str = f"{attempts}" if attempts > 1 else "1"
|
|
399
|
+
timeout_str = f"{timeout}s (override)" if timeout is not None else "per task.toml"
|
|
400
|
+
agent_labels = {"codex": "Codex (OpenAI)", "gemini": "Gemini CLI (Google)"}
|
|
401
|
+
agent_label = agent_labels.get(agent_type, "Claude Code")
|
|
402
|
+
console.print(
|
|
403
|
+
Panel(
|
|
404
|
+
f"[bold]Benchmark Runner[/bold]\n"
|
|
405
|
+
f"Agent: {agent_label}\n"
|
|
406
|
+
f"Variant: {variant}\n"
|
|
407
|
+
f"Model: {model}\n"
|
|
408
|
+
f"Timeout: {timeout_str}\n"
|
|
409
|
+
f"Tasks: {tasks_str}\n"
|
|
410
|
+
f"Attempts: {attempts_str}\n"
|
|
411
|
+
f"Environment: {env_str}\n"
|
|
412
|
+
f"Opik: {'enabled' if with_opik else 'disabled'}\n"
|
|
413
|
+
f"Assessment: {eval_str}",
|
|
414
|
+
title="nasde",
|
|
415
|
+
)
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _confirm_multi_variant_run(
|
|
420
|
+
variants: list[str],
|
|
421
|
+
config: ProjectConfig,
|
|
422
|
+
tasks_filter: list[str] | None,
|
|
423
|
+
attempts: int,
|
|
424
|
+
) -> None:
|
|
425
|
+
from rich.table import Table
|
|
426
|
+
|
|
427
|
+
from nasde_toolkit.banner import print_banner
|
|
428
|
+
|
|
429
|
+
print_banner(console)
|
|
430
|
+
|
|
431
|
+
task_names = [t.name for t in config.tasks]
|
|
432
|
+
if tasks_filter:
|
|
433
|
+
task_names = [n for n in task_names if n in set(tasks_filter)]
|
|
434
|
+
|
|
435
|
+
n_variants = len(variants)
|
|
436
|
+
n_tasks = len(task_names)
|
|
437
|
+
total_trials = n_variants * n_tasks * attempts
|
|
438
|
+
|
|
439
|
+
table = Table(title="Multi-Variant Run")
|
|
440
|
+
table.add_column("Variants", style="cyan")
|
|
441
|
+
table.add_column("Tasks", style="green")
|
|
442
|
+
table.add_column("Attempts", justify="right")
|
|
443
|
+
table.add_column("Total trials", justify="right", style="bold")
|
|
444
|
+
table.add_row(
|
|
445
|
+
", ".join(variants),
|
|
446
|
+
", ".join(task_names) if task_names else "(none)",
|
|
447
|
+
str(attempts),
|
|
448
|
+
str(total_trials),
|
|
449
|
+
)
|
|
450
|
+
console.print(table)
|
|
451
|
+
|
|
452
|
+
typer.confirm(
|
|
453
|
+
f"Run {total_trials} trials ({n_variants} variants x {n_tasks} tasks x {attempts} attempts)?",
|
|
454
|
+
abort=True,
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
async def _run_all_variants(
|
|
459
|
+
config: ProjectConfig,
|
|
460
|
+
variants: list[str],
|
|
461
|
+
model: str | None,
|
|
462
|
+
timeout_sec: int | None,
|
|
463
|
+
tasks_filter: list[str] | None,
|
|
464
|
+
with_opik: bool,
|
|
465
|
+
with_eval: bool,
|
|
466
|
+
harbor_env: str | None,
|
|
467
|
+
n_attempts: int,
|
|
468
|
+
job_suffix: str | None = None,
|
|
469
|
+
max_concurrent_eval: int = 10,
|
|
470
|
+
) -> None:
|
|
471
|
+
|
|
472
|
+
from nasde_toolkit.runner import run_benchmark
|
|
473
|
+
|
|
474
|
+
results: list[tuple[str, str, str]] = []
|
|
475
|
+
|
|
476
|
+
for i, variant_name in enumerate(variants, 1):
|
|
477
|
+
console.print(f"\n[bold]=== Variant {i}/{len(variants)}: {variant_name} ===[/bold]\n")
|
|
478
|
+
try:
|
|
479
|
+
await run_benchmark(
|
|
480
|
+
config=config,
|
|
481
|
+
variant=variant_name,
|
|
482
|
+
model=model,
|
|
483
|
+
timeout_sec=timeout_sec,
|
|
484
|
+
tasks_filter=tasks_filter,
|
|
485
|
+
with_opik=with_opik,
|
|
486
|
+
with_eval=with_eval,
|
|
487
|
+
harbor_env=harbor_env,
|
|
488
|
+
n_attempts=n_attempts,
|
|
489
|
+
job_suffix=job_suffix,
|
|
490
|
+
max_concurrent_eval=max_concurrent_eval,
|
|
491
|
+
)
|
|
492
|
+
results.append((variant_name, "[green]OK[/green]", ""))
|
|
493
|
+
except SystemExit:
|
|
494
|
+
results.append((variant_name, "[red]FAILED[/red]", "system exit"))
|
|
495
|
+
except Exception as exc:
|
|
496
|
+
console.print(f"[red]ERROR running variant '{variant_name}': {exc}[/red]")
|
|
497
|
+
results.append((variant_name, "[red]FAILED[/red]", str(exc)))
|
|
498
|
+
|
|
499
|
+
_print_multi_variant_summary(results)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def _print_multi_variant_summary(
|
|
503
|
+
results: list[tuple[str, str, str]],
|
|
504
|
+
) -> None:
|
|
505
|
+
from rich.table import Table
|
|
506
|
+
|
|
507
|
+
table = Table(title="Multi-Variant Summary")
|
|
508
|
+
table.add_column("Variant", style="cyan")
|
|
509
|
+
table.add_column("Status")
|
|
510
|
+
table.add_column("Error")
|
|
511
|
+
for variant_name, status, error in results:
|
|
512
|
+
table.add_row(variant_name, status, error)
|
|
513
|
+
console.print(table)
|