skillware-bench 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.
Files changed (49) hide show
  1. skillware_bench/__init__.py +62 -0
  2. skillware_bench/cli.py +639 -0
  3. skillware_bench/core/adapters/__init__.py +50 -0
  4. skillware_bench/core/adapters/anthropic_adapter.py +166 -0
  5. skillware_bench/core/adapters/base.py +182 -0
  6. skillware_bench/core/adapters/factory.py +140 -0
  7. skillware_bench/core/adapters/gemini_adapter.py +179 -0
  8. skillware_bench/core/adapters/mistral_adapter.py +136 -0
  9. skillware_bench/core/adapters/mock_adapter.py +100 -0
  10. skillware_bench/core/adapters/ollama_adapter.py +137 -0
  11. skillware_bench/core/adapters/openai_adapter.py +164 -0
  12. skillware_bench/core/adapters/openrouter_adapter.py +446 -0
  13. skillware_bench/core/adapters/registry.py +8 -0
  14. skillware_bench/core/agent_loop.py +383 -0
  15. skillware_bench/core/evaluators.py +246 -0
  16. skillware_bench/core/models.py +96 -0
  17. skillware_bench/core/openrouter_engine.py +357 -0
  18. skillware_bench/core/perturbations.py +130 -0
  19. skillware_bench/core/provenance.py +371 -0
  20. skillware_bench/core/runner.py +147 -0
  21. skillware_bench/core/statistics.py +123 -0
  22. skillware_bench/dashboard/__init__.py +8 -0
  23. skillware_bench/dashboard/generator.py +248 -0
  24. skillware_bench/fixtures/__init__.py +9 -0
  25. skillware_bench/fixtures/mock_companies_registry.py +419 -0
  26. skillware_bench/fixtures/mock_inbox_registry.py +228 -0
  27. skillware_bench/infra/__init__.py +27 -0
  28. skillware_bench/infra/companies_house_live.py +228 -0
  29. skillware_bench/infra/disposable_mailbox.py +210 -0
  30. skillware_bench/infra/evm_testnet_fork.py +179 -0
  31. skillware_bench/infra/hermetic_mock_server.py +354 -0
  32. skillware_bench/infra/wallet_screening_live.py +143 -0
  33. skillware_bench/suites/__init__.py +33 -0
  34. skillware_bench/suites/chained_pipeline_crucible.py +177 -0
  35. skillware_bench/suites/companies_house_crucible.py +187 -0
  36. skillware_bench/suites/constitution_crucible.py +171 -0
  37. skillware_bench/suites/context_drift_crucible.py +254 -0
  38. skillware_bench/suites/distraction_registry.py +249 -0
  39. skillware_bench/suites/gmail_crucible.py +207 -0
  40. skillware_bench/suites/prompt_injection_crucible.py +263 -0
  41. skillware_bench/suites/wallet_screening_crucible.py +167 -0
  42. skillware_bench/viz/__init__.py +8 -0
  43. skillware_bench/viz/generator.py +179 -0
  44. skillware_bench-0.3.2.dist-info/METADATA +287 -0
  45. skillware_bench-0.3.2.dist-info/RECORD +49 -0
  46. skillware_bench-0.3.2.dist-info/WHEEL +5 -0
  47. skillware_bench-0.3.2.dist-info/entry_points.txt +2 -0
  48. skillware_bench-0.3.2.dist-info/licenses/LICENSE +21 -0
  49. skillware_bench-0.3.2.dist-info/top_level.txt +1 -0
skillware_bench/cli.py ADDED
@@ -0,0 +1,639 @@
1
+ # Copyright (c) 2026 ARPA Hellenic Logical Systems
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """Command Line Interface for Skillware-Bench."""
5
+
6
+ import argparse
7
+ import os
8
+ import sys
9
+ from dotenv import load_dotenv
10
+ from rich.console import Console
11
+ from rich.panel import Panel
12
+ from rich.table import Table
13
+
14
+ from skillware_bench import __version__
15
+ from skillware_bench.core.runner import HarnessRunner
16
+ from skillware_bench.suites import ALL_CRUCIBLES
17
+
18
+ # Load local .env credentials automatically
19
+ load_dotenv()
20
+
21
+
22
+ def cmd_list(args, console: Console):
23
+ """List all available crucibles and evaluation suites."""
24
+ console.print(
25
+ Panel(
26
+ f"[bold cyan]Skillware-Bench[/bold cyan] v{__version__} - Crucible Catalog",
27
+ expand=False,
28
+ )
29
+ )
30
+ for key, crucible_cls in ALL_CRUCIBLES.items():
31
+ scenario = crucible_cls.get_scenario()
32
+ console.print(f"* [bold green]{key}[/bold green] ([dim]{scenario.id}[/dim])")
33
+ console.print(f" Title: {scenario.title}")
34
+ console.print(f" Tier: {scenario.tier.value} | Category: {scenario.category}")
35
+ console.print(f" Skills: {', '.join(scenario.skills)}")
36
+ console.print(f" Prompt: [dim]{scenario.prompt}[/dim]\n")
37
+
38
+
39
+ def cmd_check_env(args, console: Console):
40
+ """Inspect environment variables and API keys for live provider testing."""
41
+ table = Table(title="Skillware-Bench Environment & Provider Key Verification")
42
+ table.add_column("Provider / Secret", style="cyan")
43
+ table.add_column("Env Var Name", style="magenta")
44
+ table.add_column("Status", justify="center")
45
+ table.add_column("Details", style="dim")
46
+
47
+ keys_to_check = [
48
+ ("OpenRouter API", "OPENROUTER_API", "Primary OpenRouter key for frontier/free models"),
49
+ ("OpenRouter Alt", "OPENROUTER_API_KEY", "Alternative OpenRouter key variable"),
50
+ ("OpenAI API", "OPENAI_API_KEY", "Required for GPT-4o, o3-mini, o1"),
51
+ ("Anthropic API", "ANTHROPIC_API_KEY", "Required for Claude 3.7 Sonnet, Haiku"),
52
+ ("Google Gemini API", "GEMINI_API_KEY", "Required for Gemini 2.5 Pro/Flash"),
53
+ ("Google GenAI Alt", "GOOGLE_API_KEY", "Alternative key for Gemini SDK"),
54
+ ("Mistral AI API", "MISTRAL_API_KEY", "Required for Mistral Large, Codestral"),
55
+ ("Ollama Host", "OLLAMA_HOST", "Default: http://localhost:11434"),
56
+ ("UK Companies House", "COMPANIES_HOUSE_API_KEY", "Required for live Companies House"),
57
+ ("Etherscan API", "ETHERSCAN_API_KEY", "Required for live wallet screening"),
58
+ ]
59
+
60
+ for label, env_var, note in keys_to_check:
61
+ val = os.environ.get(env_var)
62
+ if val:
63
+ masked = f"{val[:4]}...{val[-3:]}" if len(val) > 8 else "***"
64
+ status = "[green]CONFIGURED[/green]"
65
+ details = f"Value: {masked} ({note})"
66
+ else:
67
+ status = "[yellow]UNSET[/yellow]"
68
+ details = note
69
+ table.add_row(label, env_var, status, details)
70
+
71
+ console.print(table)
72
+
73
+
74
+ def cmd_run(args, console: Console):
75
+ """Run benchmark crucibles and print evaluation matrix."""
76
+ runner = HarnessRunner(console=console)
77
+ suite_label = args.suite or "all"
78
+ is_mock = not args.live if args.live else args.mock
79
+
80
+ mode_label = "MOCK (deterministic offline)" if is_mock else "LIVE (real API execution)"
81
+ console.print(
82
+ f"[bold]Running Skillware-Bench suite='{suite_label}' model='{args.model}' mode='{mode_label}'...[/bold]\n"
83
+ )
84
+ adapter_arg = getattr(args, "adapter", None)
85
+ adapter_path = adapter_arg if isinstance(adapter_arg, str) and adapter_arg.strip() else None
86
+
87
+ deltas = runner.run_suite(
88
+ suite_name=args.suite,
89
+ model_name=args.model,
90
+ mock=is_mock,
91
+ adapter_path=adapter_path,
92
+ )
93
+ runner.render_summary_table(deltas)
94
+
95
+ if args.export:
96
+ runner.export_reports(deltas, output_dir=args.output_dir)
97
+
98
+
99
+ def cmd_compare(args, console: Console):
100
+ """Print detailed turn-by-turn comparison for a specific crucible."""
101
+ if args.suite not in ALL_CRUCIBLES:
102
+ console.print(
103
+ f"[red]Error: Unknown suite '{args.suite}'. Use 'skillware-bench list' to see available suites.[/red]"
104
+ )
105
+ sys.exit(1)
106
+
107
+ runner = HarnessRunner(console=console)
108
+ deltas = runner.run_suite(suite_name=args.suite, model_name=args.model, mock=True)
109
+ d = deltas[0]
110
+
111
+ console.print(
112
+ Panel(
113
+ f"[bold cyan]Crucible Deep Dive: {d.scenario_id}[/bold cyan] ({d.model})", expand=False
114
+ )
115
+ )
116
+ console.print("[bold yellow]1. Generic Baseline (Without Skillware):[/bold yellow]")
117
+ b_score = d.baseline.score.composite_score
118
+ console.print(
119
+ f" Latency: {d.baseline.latency_seconds:.1f}s | Tokens: {d.baseline.total_tokens} | Score: {b_score}/100"
120
+ )
121
+ if d.baseline.score.violations:
122
+ console.print(" Violations:")
123
+ for v in d.baseline.score.violations:
124
+ console.print(f" [red][X] {v}[/red]")
125
+ console.print(f" Sample Output: [dim]{d.baseline.trajectory[-1].raw_response}[/dim]\n")
126
+
127
+ console.print("[bold green]2. Skillware Equipped:[/bold green]")
128
+ s_lat = d.with_skillware.latency_seconds
129
+ s_tok = d.with_skillware.total_tokens
130
+ s_score = d.with_skillware.score.composite_score
131
+ console.print(f" Latency: {s_lat:.1f}s | Tokens: {s_tok} | Score: {s_score}/100")
132
+ executed_actions = [tc.action for step in d.with_skillware.trajectory for tc in step.tool_calls]
133
+ console.print(f" Tool Actions Executed: {executed_actions}")
134
+ console.print(f" Sample Output: [dim]{d.with_skillware.trajectory[-1].raw_response}[/dim]\n")
135
+
136
+ console.print("[bold cyan]3. Empirical Lift (Delta Matrix):[/bold cyan]")
137
+ console.print(f" * Latency Reduction: [yellow]-{d.latency_reduction_pct:.1f}%[/yellow]")
138
+ console.print(f" * Token Bloat Reduction: [cyan]-{d.token_reduction_pct:.1f}%[/cyan]")
139
+ console.print(f" * Composite Score Lift: [green]+{d.composite_score_lift:.1f} pts[/green]")
140
+
141
+
142
+ def cmd_verify(args, console: Console):
143
+ """Verify cryptographic hash chain and Ed25519 signature of an AURA audit trace or benchmark report."""
144
+ from pathlib import Path
145
+ from skillware_bench.core.provenance import ProvenanceEngine
146
+
147
+ target_file = args.trace
148
+ path = Path(target_file)
149
+ if not path.is_file():
150
+ console.print(f"[bold red]Error: Target file not found: {target_file}[/bold red]")
151
+ sys.exit(1)
152
+
153
+ # Check if target is a JSON benchmark report or a JSONL trace
154
+ if target_file.endswith(".json") and not target_file.endswith(".jsonl"):
155
+ console.print(f"[bold]Verifying AURA benchmark report receipt: {target_file}...[/bold]\n")
156
+ try:
157
+ res = ProvenanceEngine.verify_report(target_file)
158
+ if res["valid"]:
159
+ console.print(
160
+ "[bold green][PASS] Benchmark Execution Report Verification Succeeded[/bold green]"
161
+ )
162
+ console.print(f" * Run ID: [cyan]{res['run_id']}[/cyan]")
163
+ console.print(f" * Model: [bold]{res['model']}[/bold]")
164
+ cond_info = (
165
+ f"{res['total_conditions']} "
166
+ f"(Vanilla: {res['vanilla_pass']} passed, Skillware: {res['skillware_pass']} passed)"
167
+ )
168
+ console.print(f" * Conditions Evaluated: {cond_info}")
169
+ console.print(
170
+ f" * AURA Cryptographic Receipt (SHA-256): [green]{res['receipt_hash']}[/green]"
171
+ )
172
+ console.print(f" * Attestation Engine: {res['engine']}")
173
+ console.print(
174
+ " * Integrity Status: [bold green]AUTHENTIC / UNTAMPERED[/bold green]"
175
+ )
176
+ else:
177
+ console.print("[bold red][FAIL] Benchmark Report Verification Failed[/bold red]")
178
+ console.print(f" * Reason: {res.get('error') or res.get('verdict')}")
179
+ if "expected_hash" in res and "computed_hash" in res:
180
+ console.print(f" * Expected Hash: [green]{res['expected_hash']}[/green]")
181
+ console.print(f" * Computed Hash: [red]{res['computed_hash']}[/red]")
182
+ sys.exit(1)
183
+ except Exception as e:
184
+ console.print(f"[bold red]Error verifying report: {str(e)}[/bold red]")
185
+ sys.exit(1)
186
+ return
187
+
188
+ # Otherwise verify as an AURA .aura.jsonl trace
189
+ console.print(f"[bold]Verifying AURA audit trace: {target_file}...[/bold]\n")
190
+ try:
191
+ res = ProvenanceEngine.verify_trace(target_file)
192
+ if res["valid"]:
193
+ console.print(
194
+ "[bold green][PASS] Cryptographic Trace Verification Succeeded[/bold green]"
195
+ )
196
+ console.print(f" * Events Verified: {res['events_verified']}")
197
+ console.print(f" * Root Hash: [cyan]{res['root_hash']}[/cyan]")
198
+ if res.get("signature_verified") is True:
199
+ console.print(" * Ed25519 Signature: [green]AUTHENTIC[/green]")
200
+ elif res.get("signature_verified") is False:
201
+ console.print(" * Ed25519 Signature: [red]INVALID / TAMPERED[/red]")
202
+ else:
203
+ console.print(
204
+ " * Ed25519 Signature: [yellow]UNSET (No .sig receipt found)[/yellow]"
205
+ )
206
+ console.print(f" * Constitutional Approvals: {res['constitutional_approvals']}")
207
+ console.print(f" * Constitutional Violations: {res['constitutional_violations']}")
208
+ else:
209
+ console.print("[bold red][FAIL] Trace Verification Failed[/bold red]")
210
+ console.print(f" * Reason: Hash chain broken at event '{res.get('broken_event_id')}'")
211
+ sys.exit(1)
212
+ except Exception as e:
213
+ console.print(f"[bold red]Error verifying trace: {str(e)}[/bold red]")
214
+ sys.exit(1)
215
+
216
+
217
+ def cmd_export_compliance(args, console: Console):
218
+ """Export ISO/IEC 42001 and EU AI Act Article 14 regulatory oversight assessment."""
219
+ from skillware_bench.core.provenance import ProvenanceEngine
220
+
221
+ trace_file = args.trace
222
+ output_file = args.output
223
+ console.print(f"[bold]Generating Regulatory Compliance Package for {trace_file}...[/bold]\n")
224
+ try:
225
+ report = ProvenanceEngine.export_compliance_report(trace_file, output_path=output_file)
226
+ table = Table(title="AI Regulatory Oversight Assessment (EU AI Act & ISO 42001)")
227
+ table.add_column("Regulatory Standard", style="cyan")
228
+ table.add_column("Compliance Status", justify="center")
229
+ table.add_column("Audit Evidence / Proof", style="dim")
230
+
231
+ for std_name, std_data in report["standards"].items():
232
+ status_style = (
233
+ "[green]COMPLIANT[/green]"
234
+ if std_data["status"] == "COMPLIANT"
235
+ else f"[yellow]{std_data['status']}[/yellow]"
236
+ )
237
+ table.add_row(std_name.replace("_", " "), status_style, std_data["evidence"])
238
+
239
+ console.print(table)
240
+ except Exception as e:
241
+ console.print(f"[bold red]Error generating compliance export: {str(e)}[/bold red]")
242
+ sys.exit(1)
243
+
244
+
245
+ def cmd_dashboard(args, console: Console):
246
+ """Generate self-contained interactive HTML dashboard from benchmark runs."""
247
+ from pathlib import Path
248
+ import webbrowser
249
+ from skillware_bench.dashboard import DashboardGenerator
250
+
251
+ runner = HarnessRunner(console=console)
252
+ is_mock = not args.live
253
+ console.print("[bold]Generating interactive Skillware-Bench dashboard...[/bold]\n")
254
+ deltas = runner.run_suite(suite_name=args.suite, model_name=args.model, mock=is_mock)
255
+ output_html = args.output or "./runs/dashboard.html"
256
+ DashboardGenerator.generate_html(deltas, output_path=output_html)
257
+ console.print(f"[bold green]Dashboard generated successfully:[/bold green] {output_html}")
258
+ if getattr(args, "open_browser", False):
259
+ webbrowser.open(str(Path(output_html).resolve().as_uri()))
260
+
261
+
262
+ def cmd_perturb(args, console: Console):
263
+ """Generate dynamic anti-contamination scenario variations."""
264
+ from skillware_bench.core.perturbations import PerturbationEngine
265
+
266
+ engine = PerturbationEngine(seed=args.seed)
267
+ console.print(
268
+ Panel(
269
+ "[bold cyan]Skillware-Bench Anti-Contamination Perturbation Engine[/bold cyan]",
270
+ expand=False,
271
+ )
272
+ )
273
+
274
+ suites = [args.suite] if args.suite else list(ALL_CRUCIBLES.keys())
275
+ variants_table = Table(title="Generated Anti-Contamination Scenario Variants")
276
+ variants_table.add_column("Crucible", style="cyan")
277
+ variants_table.add_column("Variant ID", style="magenta")
278
+ variants_table.add_column("Perturbed Prompt", style="dim")
279
+
280
+ count = getattr(args, "count", 3)
281
+ for s_name in suites:
282
+ scenario = ALL_CRUCIBLES[s_name].get_scenario()
283
+ for _ in range(count):
284
+ perturbed = engine.perturb_scenario(scenario)
285
+ preview = perturbed.prompt.replace("\n", " ")[:90] + "..."
286
+ variants_table.add_row(s_name, perturbed.id, preview)
287
+
288
+ console.print(variants_table)
289
+ total_variants = len(suites) * count
290
+ console.print(
291
+ f"[dim]Generated {total_variants} variant(s) across {len(suites)} crucible(s). "
292
+ "Invariants strictly preserved.[/dim]\n"
293
+ )
294
+
295
+
296
+ def cmd_new_model(args, console: Console):
297
+ """Scaffold a custom model adapter template for Bring-Your-Own-Model (BYOM) evaluation."""
298
+ raw_name = args.name.lower().replace("-", "_").replace(" ", "_")
299
+ output_path = args.output or f"./custom_adapters/{raw_name}_adapter.py"
300
+ os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
301
+ class_name = "".join(part.title() for part in raw_name.split("_")) + "Adapter"
302
+
303
+ template = f'''# Copyright (c) 2026 ARPA Hellenic Logical Systems
304
+ # SPDX-License-Identifier: MIT
305
+
306
+ """Custom Bring-Your-Own-Model adapter for {args.name}."""
307
+
308
+ from typing import Any, Dict, List, Optional
309
+ from skillware_bench.core.adapters.base import (
310
+ BaseModelAdapter,
311
+ ModelResponse,
312
+ TokenUsage,
313
+ )
314
+ from skillware_bench.core.adapters.registry import AdapterRegistry
315
+
316
+
317
+ class {class_name}(BaseModelAdapter):
318
+ """Adapter for {args.name}."""
319
+
320
+ def __init__(self, model_name: str = "{args.name}", **kwargs):
321
+ super().__init__(model_name=model_name, **kwargs)
322
+
323
+ def chat(
324
+ self,
325
+ messages: List[Dict[str, Any]],
326
+ tools: Optional[List[Dict[str, Any]]] = None,
327
+ system_prompt: Optional[str] = None,
328
+ **kwargs,
329
+ ) -> ModelResponse:
330
+ """Execute chat completion with tool calling."""
331
+ # TODO: Implement inference call to your custom LLM or API endpoint
332
+ return ModelResponse(
333
+ content="Processed under constitutional governance.",
334
+ tool_calls=[],
335
+ usage=TokenUsage(prompt_tokens=100, completion_tokens=50),
336
+ )
337
+
338
+
339
+ # Automatic BYOM Registration
340
+ AdapterRegistry.register("{args.name}", {class_name})
341
+ '''
342
+ with open(output_path, "w", encoding="utf-8") as f:
343
+ f.write(template)
344
+
345
+ console.print(
346
+ Panel(
347
+ f"[bold green]Custom Model Adapter Scaffolded Successfully[/bold green]\n\n"
348
+ f"File: [cyan]{output_path}[/cyan]\n\n"
349
+ f"Run benchmark with:\n"
350
+ f"[bold]skillware-bench run --adapter {output_path} --suite gmail[/bold]",
351
+ expand=False,
352
+ )
353
+ )
354
+
355
+
356
+ def cmd_audit(args, console: Console):
357
+ """Inspect and audit AURA causal provenance traces in detail."""
358
+ from skillware_bench.core.provenance import ProvenanceEngine
359
+
360
+ trace_file = args.trace
361
+ console.print(f"[bold cyan]Auditing AURA Causal Provenance Trace: {trace_file}[/bold cyan]\n")
362
+ try:
363
+ res = ProvenanceEngine.verify_trace(trace_file)
364
+ if not res["valid"]:
365
+ console.print(
366
+ f"[bold red][FAIL] Trace integrity invalid. Broken at event '{res.get('broken_event_id')}'.[/bold red]"
367
+ )
368
+ sys.exit(1)
369
+
370
+ table = Table(title="Trace Audit Summary")
371
+ table.add_column("Property", style="cyan")
372
+ table.add_column("Value", style="green bold")
373
+
374
+ table.add_row("Events Verified", str(res["events_verified"]))
375
+ table.add_row("Root Hash", res["root_hash"][:16] + "...")
376
+ table.add_row("Constitutional Approvals", str(res["constitutional_approvals"]))
377
+ table.add_row("Constitutional Violations", str(res["constitutional_violations"]))
378
+ sig_val = "AUTHENTIC" if res.get("signature_verified") else "UNSET / NOT SIGNED"
379
+ table.add_row("Ed25519 Signature", sig_val)
380
+
381
+ console.print(table)
382
+ except Exception as e:
383
+ console.print(f"[bold red]Error auditing trace: {str(e)}[/bold red]")
384
+ sys.exit(1)
385
+
386
+
387
+ def cmd_menu(args, console: Console):
388
+ """Launch interactive pastel dispatch menu."""
389
+ banner_lines = [
390
+ r" ____ _ _ _ _ ____ _",
391
+ r" / ___|| | __(_) | |_ ____ _ _ __ ___ | __ ) ___ _ __ ___| |__",
392
+ r" \___ \| |/ /| | | \ \ /\ / / _` | '__/ _ \_____| _ \ / _ \ '_ \ / __| '_ \\",
393
+ r" ___) | < | | | |\ V V / (_| | | | __/_____| |_) | __/ | | | (__| | | |",
394
+ r" |____/|_|\_\|_|_|_| \_/\_/ \__,_|_| \___| |____/ \___|_| |_|\___|_| |_|",
395
+ ]
396
+ banner = "\n" + "\n".join(banner_lines)
397
+ console.print(f"[bold #bae6fd]{banner}[/bold #bae6fd]")
398
+ console.print(
399
+ "[bold #efcefa]Skillware-Bench: The Governed Agent Evaluation Platform[/bold #efcefa]"
400
+ )
401
+ console.print(f"[dim]Version {__version__} | ARPA Hellenic Logical Systems[/dim]\n")
402
+
403
+ menu_table = Table(show_header=False, box=None)
404
+ menu_table.add_column("Key", style="bold #bbf7d0", width=5)
405
+ menu_table.add_column("Command", style="bold cyan", width=16)
406
+ menu_table.add_column("Description", style="dim")
407
+
408
+ menu_table.add_row("[1]", "run", "Execute benchmark suite across frontier/sovereign models")
409
+ menu_table.add_row("[2]", "live", "Launch real-world zero-mock simulation crucibles")
410
+ menu_table.add_row("[3]", "compare", "Turn-by-turn with-vs-without Skillware A/B inspection")
411
+ menu_table.add_row("[4]", "dashboard", "Launch interactive browser dashboard")
412
+ menu_table.add_row("[5]", "audit", "Inspect AURA causal provenance traces and receipts")
413
+ menu_table.add_row("[6]", "perturb", "Generate dynamic anti-contamination task variants")
414
+ menu_table.add_row("[7]", "new-model", "Scaffold custom model adapter plugin")
415
+ menu_table.add_row("[8]", "check-env", "Inspect provider API keys and secrets")
416
+ menu_table.add_row("[9]", "openrouter", "Benchmark models via OpenRouter API")
417
+ menu_table.add_row("[0]", "exit", "Exit Skillware-Bench launcher")
418
+
419
+ console.print(menu_table)
420
+
421
+
422
+ def cmd_openrouter(args, console: Console):
423
+ """Run OpenRouter benchmark sweep across frontier, sovereign, or free models."""
424
+ from skillware_bench.core.openrouter_engine import OpenRouterBenchmarkEngine
425
+
426
+ models = args.models or ["nvidia/nemotron-3.5-super-vl:free"]
427
+ crucibles = [args.suite] if args.suite else args.crucibles
428
+ engine = OpenRouterBenchmarkEngine(console=console)
429
+ _ = engine.run_sweep(
430
+ models=models,
431
+ crucible_filter=crucibles,
432
+ sweep_name=args.sweep_name or "openrouter_cli_sweep",
433
+ output_dir=args.output_dir or "./runs/openrouter_sweeps",
434
+ )
435
+
436
+
437
+ def main():
438
+ parser = argparse.ArgumentParser(
439
+ prog="skillware-bench",
440
+ description="The transparent, empirical benchmark suite and evaluation harness for Skillware.",
441
+ )
442
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
443
+ subparsers = parser.add_subparsers(dest="command", help="Command to run")
444
+
445
+ # list
446
+ p_list = subparsers.add_parser("list", help="List available simulation crucibles and tiers")
447
+ p_list.set_defaults(func=cmd_list)
448
+
449
+ # check-env
450
+ p_check = subparsers.add_parser(
451
+ "check-env", help="Check configured environment variables and provider keys"
452
+ )
453
+ p_check.set_defaults(func=cmd_check_env)
454
+
455
+ # run
456
+ p_run = subparsers.add_parser("run", help="Run benchmark suite")
457
+ p_run.add_argument(
458
+ "--suite",
459
+ choices=list(ALL_CRUCIBLES.keys()),
460
+ default=None,
461
+ help="Specific crucible suite to run",
462
+ )
463
+ p_run.add_argument("--model", default="golden-target", help="Model name identifier")
464
+ p_run.add_argument(
465
+ "--adapter",
466
+ default=None,
467
+ help="Path to custom model adapter Python file for BYOM evaluation",
468
+ )
469
+ p_run.add_argument(
470
+ "--mock", action="store_true", default=True, help="Run offline mock crucibles"
471
+ )
472
+
473
+ p_run.add_argument(
474
+ "--live",
475
+ action="store_true",
476
+ default=False,
477
+ help="Run live model execution against crucibles",
478
+ )
479
+ p_run.add_argument(
480
+ "--export", action="store_true", default=False, help="Export benchmark reports to JSON"
481
+ )
482
+ p_run.add_argument(
483
+ "--output-dir", default="./runs/reports", help="Output directory for reports"
484
+ )
485
+ p_run.add_argument(
486
+ "--figures",
487
+ action="store_true",
488
+ default=False,
489
+ help="Generate standalone SVG figures (radar and empirical lift delta)",
490
+ )
491
+ p_run.add_argument(
492
+ "--figures-dir",
493
+ default="./runs/figures",
494
+ help="Output directory for generated figures",
495
+ )
496
+ p_run.set_defaults(func=cmd_run)
497
+
498
+ # compare
499
+ p_cmp = subparsers.add_parser(
500
+ "compare", help="Compare with-skillware vs without-skillware in depth"
501
+ )
502
+ p_cmp.add_argument(
503
+ "--suite", required=True, choices=list(ALL_CRUCIBLES.keys()), help="Crucible to inspect"
504
+ )
505
+ p_cmp.add_argument("--model", default="golden-target", help="Model name identifier")
506
+ p_cmp.set_defaults(func=cmd_compare)
507
+
508
+ # verify
509
+ p_verify = subparsers.add_parser(
510
+ "verify", help="Cryptographically verify AURA causal audit traces and signatures"
511
+ )
512
+ p_verify.add_argument(
513
+ "trace", help="Path to .aura.jsonl audit trace or benchmark report .json file"
514
+ )
515
+ p_verify.set_defaults(func=cmd_verify)
516
+
517
+ # export-compliance
518
+ p_comp = subparsers.add_parser(
519
+ "export-compliance",
520
+ help="Generate ISO 42001 and EU AI Act Article 14 compliance report",
521
+ )
522
+ p_comp.add_argument("trace", help="Path to .aura.jsonl audit trace file")
523
+ p_comp.add_argument("--output", default=None, help="Optional output JSON filepath for report")
524
+ p_comp.set_defaults(func=cmd_export_compliance)
525
+
526
+ # dashboard
527
+ p_dash = subparsers.add_parser(
528
+ "dashboard", help="Generate self-contained interactive HTML dashboard"
529
+ )
530
+ p_dash.add_argument(
531
+ "--suite",
532
+ choices=list(ALL_CRUCIBLES.keys()),
533
+ default=None,
534
+ help="Specific crucible suite to benchmark",
535
+ )
536
+ p_dash.add_argument("--model", default="golden-target", help="Model name identifier")
537
+ p_dash.add_argument(
538
+ "--live",
539
+ action="store_true",
540
+ default=False,
541
+ help="Run live model execution",
542
+ )
543
+ p_dash.add_argument(
544
+ "--output",
545
+ default="./runs/dashboard.html",
546
+ help="Output HTML filepath for dashboard",
547
+ )
548
+ p_dash.add_argument(
549
+ "--open-browser",
550
+ action="store_true",
551
+ default=False,
552
+ help="Open generated dashboard in default browser",
553
+ )
554
+ p_dash.set_defaults(func=cmd_dashboard)
555
+
556
+ # perturb
557
+ p_perturb = subparsers.add_parser(
558
+ "perturb", help="Generate dynamic anti-contamination scenario variations"
559
+ )
560
+ p_perturb.add_argument(
561
+ "--suite",
562
+ choices=list(ALL_CRUCIBLES.keys()),
563
+ default=None,
564
+ help="Specific crucible suite to perturb",
565
+ )
566
+ p_perturb.add_argument(
567
+ "--count", type=int, default=3, help="Number of perturbation variants per scenario"
568
+ )
569
+ p_perturb.add_argument("--seed", type=int, default=None, help="Random seed for reproducibility")
570
+ p_perturb.set_defaults(func=cmd_perturb)
571
+
572
+ # new-model
573
+ p_new = subparsers.add_parser(
574
+ "new-model", help="Scaffold custom model adapter template for Bring-Your-Own-Model (BYOM)"
575
+ )
576
+ p_new.add_argument("name", help="Identifier name for custom model adapter (e.g. llama-custom)")
577
+ p_new.add_argument(
578
+ "--output", default=None, help="Output Python filepath for scaffolded adapter"
579
+ )
580
+ p_new.set_defaults(func=cmd_new_model)
581
+
582
+ # audit
583
+ p_audit = subparsers.add_parser("audit", help="Inspect and audit AURA causal provenance traces")
584
+ p_audit.add_argument("trace", help="Path to .aura.jsonl audit trace file")
585
+ p_audit.set_defaults(func=cmd_audit)
586
+
587
+ # openrouter
588
+ p_or = subparsers.add_parser(
589
+ "openrouter", help="Benchmark frontier and sovereign models via OpenRouter API"
590
+ )
591
+ p_or.add_argument(
592
+ "--models",
593
+ nargs="+",
594
+ default=["nvidia/nemotron-3.5-super-vl:free"],
595
+ help="One or more model slugs to evaluate on OpenRouter",
596
+ )
597
+ p_or.add_argument(
598
+ "--suite",
599
+ choices=list(ALL_CRUCIBLES.keys()),
600
+ default=None,
601
+ help="Specific crucible suite to run",
602
+ )
603
+ p_or.add_argument(
604
+ "--crucibles",
605
+ nargs="+",
606
+ default=None,
607
+ help="List of crucibles to run (default: all 7)",
608
+ )
609
+ p_or.add_argument(
610
+ "--sweep-name",
611
+ default="openrouter_cli_sweep",
612
+ help="Custom label for the sweep audit report",
613
+ )
614
+ p_or.add_argument(
615
+ "--output-dir",
616
+ default="./runs/openrouter_sweeps",
617
+ help="Output directory for reports",
618
+ )
619
+ p_or.set_defaults(func=cmd_openrouter)
620
+
621
+ # menu
622
+ p_menu = subparsers.add_parser("menu", help="Launch interactive pastel dispatch launcher")
623
+ p_menu.set_defaults(func=cmd_menu)
624
+
625
+ args = parser.parse_args()
626
+ console = Console()
627
+
628
+ if not args.command:
629
+ if sys.stdin.isatty():
630
+ cmd_menu(args, console)
631
+ else:
632
+ parser.print_help()
633
+ sys.exit(0)
634
+
635
+ args.func(args, console)
636
+
637
+
638
+ if __name__ == "__main__":
639
+ main()
@@ -0,0 +1,50 @@
1
+ # Copyright (c) 2026 ARPA Hellenic Logical Systems
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """Model adapters for frontier, sovereign, and mock evaluation targets."""
5
+
6
+ from skillware_bench.core.adapters.anthropic_adapter import AnthropicAdapter
7
+ from skillware_bench.core.adapters.base import (
8
+ BaseModelAdapter,
9
+ ModelResponse,
10
+ TokenUsage,
11
+ ToolCallRequest,
12
+ calculate_cost,
13
+ execute_with_retry,
14
+ )
15
+ from skillware_bench.core.adapters.factory import (
16
+ AdapterRegistry,
17
+ get_adapter,
18
+ load_adapter_from_file,
19
+ )
20
+ from skillware_bench.core.adapters.gemini_adapter import GeminiAdapter
21
+ from skillware_bench.core.adapters.mistral_adapter import MistralAdapter
22
+ from skillware_bench.core.adapters.mock_adapter import (
23
+ MockModelAdapter,
24
+ SimulatedRateLimitError,
25
+ SimulatedServerError,
26
+ )
27
+ from skillware_bench.core.adapters.ollama_adapter import OllamaAdapter
28
+ from skillware_bench.core.adapters.openai_adapter import OpenAIAdapter
29
+ from skillware_bench.core.adapters.openrouter_adapter import OpenRouterAdapter
30
+
31
+ __all__ = [
32
+ "BaseModelAdapter",
33
+ "ModelResponse",
34
+ "TokenUsage",
35
+ "ToolCallRequest",
36
+ "calculate_cost",
37
+ "execute_with_retry",
38
+ "AdapterRegistry",
39
+ "get_adapter",
40
+ "load_adapter_from_file",
41
+ "OpenAIAdapter",
42
+ "OpenRouterAdapter",
43
+ "AnthropicAdapter",
44
+ "GeminiAdapter",
45
+ "MistralAdapter",
46
+ "OllamaAdapter",
47
+ "MockModelAdapter",
48
+ "SimulatedRateLimitError",
49
+ "SimulatedServerError",
50
+ ]