fusionkit 0.3.1__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: fusionkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Command line interface for fusionkit: local response-level model fusion.
|
|
5
|
-
Requires-Dist: fusionkit-core==0.
|
|
6
|
-
Requires-Dist: fusionkit-evals==0.
|
|
7
|
-
Requires-Dist: fusionkit-mlx==0.
|
|
8
|
-
Requires-Dist: fusionkit-server==0.
|
|
5
|
+
Requires-Dist: fusionkit-core==0.5.0
|
|
6
|
+
Requires-Dist: fusionkit-evals==0.5.0
|
|
7
|
+
Requires-Dist: fusionkit-mlx==0.5.0
|
|
8
|
+
Requires-Dist: fusionkit-server==0.5.0
|
|
9
9
|
Requires-Dist: typer>=0.20.0
|
|
10
10
|
Requires-Python: >=3.11
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fusionkit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.5.0"
|
|
4
4
|
description = "Command line interface for fusionkit: local response-level model fusion."
|
|
5
5
|
requires-python = ">=3.11"
|
|
6
6
|
dependencies = [
|
|
7
|
-
"fusionkit-core==0.
|
|
8
|
-
"fusionkit-evals==0.
|
|
9
|
-
"fusionkit-mlx==0.
|
|
10
|
-
"fusionkit-server==0.
|
|
7
|
+
"fusionkit-core==0.5.0",
|
|
8
|
+
"fusionkit-evals==0.5.0",
|
|
9
|
+
"fusionkit-mlx==0.5.0",
|
|
10
|
+
"fusionkit-server==0.5.0",
|
|
11
11
|
"typer>=0.20.0",
|
|
12
12
|
]
|
|
13
13
|
|
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import shlex
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Annotated, cast
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
import uvicorn
|
|
12
|
+
from fusionkit_core.clients import build_clients
|
|
13
|
+
from fusionkit_core.config import FusionMode, load_config
|
|
14
|
+
from fusionkit_core.fusion import FusionEngine
|
|
15
|
+
from fusionkit_core.prompts import SYSTEM_PROMPT_DEFAULTS
|
|
16
|
+
from fusionkit_evals.bench_history import BenchRunRecord, append_run, drift_vs_previous
|
|
17
|
+
from fusionkit_evals.benchmark import BenchmarkRunner, load_jsonl_samples, write_jsonl_results
|
|
18
|
+
from fusionkit_evals.benchmark_panel import get_benchmark_panel
|
|
19
|
+
from fusionkit_evals.candidate_bank import (
|
|
20
|
+
PreparedTask,
|
|
21
|
+
bank_signature,
|
|
22
|
+
build_candidate_bank,
|
|
23
|
+
load_bank,
|
|
24
|
+
save_bank,
|
|
25
|
+
)
|
|
26
|
+
from fusionkit_evals.fusion_bench import (
|
|
27
|
+
CommandHandoffKitExecutor,
|
|
28
|
+
FusionBenchReport,
|
|
29
|
+
FusionBenchRunner,
|
|
30
|
+
build_fusion_bench_report,
|
|
31
|
+
load_benchmark_tasks,
|
|
32
|
+
load_fusion_bench_jsonl,
|
|
33
|
+
write_fusion_bench_jsonl,
|
|
34
|
+
)
|
|
35
|
+
from fusionkit_evals.fusion_reports import (
|
|
36
|
+
write_fusion_bench_html_report,
|
|
37
|
+
write_fusion_bench_markdown_report,
|
|
38
|
+
write_fusion_bench_report_jsonl,
|
|
39
|
+
)
|
|
40
|
+
from fusionkit_evals.livecodebench_data import (
|
|
41
|
+
LCB_PROMPT_SUFFIX,
|
|
42
|
+
load_manifest,
|
|
43
|
+
load_problems,
|
|
44
|
+
prepare_tasks,
|
|
45
|
+
)
|
|
46
|
+
from fusionkit_evals.pareto import load_points, write_pareto_report
|
|
47
|
+
from fusionkit_evals.prompt_tuning import (
|
|
48
|
+
LLMProposer,
|
|
49
|
+
TunableRole,
|
|
50
|
+
TunerRuntime,
|
|
51
|
+
TuningResult,
|
|
52
|
+
optimize,
|
|
53
|
+
select_decision_tasks,
|
|
54
|
+
split_dev_val,
|
|
55
|
+
)
|
|
56
|
+
from fusionkit_evals.public_bench import (
|
|
57
|
+
PUBLIC_BENCHMARK_INFO,
|
|
58
|
+
PUBLIC_BENCHMARK_SUITES,
|
|
59
|
+
CommandExternalBenchmarkExecutor,
|
|
60
|
+
ExternalBenchmarkRequest,
|
|
61
|
+
PublicBenchmarkSuite,
|
|
62
|
+
baselines_for,
|
|
63
|
+
run_public_benchmark,
|
|
64
|
+
write_external_runs_jsonl,
|
|
65
|
+
)
|
|
66
|
+
from fusionkit_evals.public_bench_report import (
|
|
67
|
+
build_benchmark_comparison,
|
|
68
|
+
write_benchmark_comparison_markdown,
|
|
69
|
+
)
|
|
70
|
+
from fusionkit_evals.sandbox import SandboxConfig, build_sandbox
|
|
71
|
+
from fusionkit_evals.tiny import (
|
|
72
|
+
load_tiny_tasks,
|
|
73
|
+
run_tiny_benchmark,
|
|
74
|
+
write_tiny_benchmark_report,
|
|
75
|
+
write_tiny_jsonl,
|
|
76
|
+
)
|
|
77
|
+
from fusionkit_server.app import create_app
|
|
78
|
+
from fusionkit_server.openai_endpoint import build_endpoint, serve_single_endpoint
|
|
79
|
+
|
|
80
|
+
app = typer.Typer(help="Local model fusion toolkit.")
|
|
81
|
+
|
|
82
|
+
prompts_app = typer.Typer(help="Inspect and export the built-in fusion prompts.")
|
|
83
|
+
app.add_typer(prompts_app, name="prompts")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@prompts_app.command("dump")
|
|
87
|
+
def prompts_dump(
|
|
88
|
+
dir: Annotated[
|
|
89
|
+
Path | None,
|
|
90
|
+
typer.Option("--dir", help="write each default prompt to <dir>/<id>.md instead of stdout"),
|
|
91
|
+
] = None,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""Emit the built-in system prompts so a consumer can scaffold editable overrides.
|
|
94
|
+
|
|
95
|
+
With no options this prints a JSON object mapping each prompt id (e.g.
|
|
96
|
+
``judge``, ``trajectory-step``) to its default text. With ``--dir`` it writes
|
|
97
|
+
one ``<id>.md`` file per prompt. This keeps the CLI's scaffolded
|
|
98
|
+
``.fusionkit/prompts`` defaults in lockstep with this package's source.
|
|
99
|
+
"""
|
|
100
|
+
if dir is not None:
|
|
101
|
+
dir.mkdir(parents=True, exist_ok=True)
|
|
102
|
+
for prompt_id, text in SYSTEM_PROMPT_DEFAULTS.items():
|
|
103
|
+
(dir / f"{prompt_id}.md").write_text(text + "\n")
|
|
104
|
+
typer.echo(json.dumps({"dir": str(dir), "count": len(SYSTEM_PROMPT_DEFAULTS)}))
|
|
105
|
+
return
|
|
106
|
+
typer.echo(json.dumps(SYSTEM_PROMPT_DEFAULTS, indent=2))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@app.command()
|
|
110
|
+
def serve(
|
|
111
|
+
config: Annotated[Path, typer.Option("--config", "-c")],
|
|
112
|
+
host: str = "127.0.0.1",
|
|
113
|
+
port: int = 8080,
|
|
114
|
+
) -> None:
|
|
115
|
+
fusion_config = load_config(config)
|
|
116
|
+
api = create_app(fusion_config)
|
|
117
|
+
uvicorn.run(api, host=host, port=port)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@app.command("serve-endpoint")
|
|
121
|
+
def serve_endpoint(
|
|
122
|
+
id: Annotated[str, typer.Option("--id", help="endpoint id exposed via /v1/models")],
|
|
123
|
+
model: Annotated[str, typer.Option("--model", help="provider model name (e.g. gpt-5.5)")],
|
|
124
|
+
port: Annotated[int, typer.Option("--port")],
|
|
125
|
+
provider: Annotated[str, typer.Option("--provider")] = "openai",
|
|
126
|
+
base_url: Annotated[
|
|
127
|
+
str | None, typer.Option("--base-url", help="override the provider base URL")
|
|
128
|
+
] = None,
|
|
129
|
+
api_key_env: Annotated[
|
|
130
|
+
str | None, typer.Option("--api-key-env", help="env var holding the API key")
|
|
131
|
+
] = None,
|
|
132
|
+
timeout_s: Annotated[float, typer.Option("--timeout-s")] = 120.0,
|
|
133
|
+
host: Annotated[str, typer.Option("--host")] = "127.0.0.1",
|
|
134
|
+
) -> None:
|
|
135
|
+
"""Front a single provider model as an OpenAI Chat Completions endpoint."""
|
|
136
|
+
endpoint = build_endpoint(
|
|
137
|
+
id=id,
|
|
138
|
+
model=model,
|
|
139
|
+
provider=provider,
|
|
140
|
+
base_url=base_url,
|
|
141
|
+
api_key_env=api_key_env,
|
|
142
|
+
timeout_s=timeout_s,
|
|
143
|
+
)
|
|
144
|
+
serve_single_endpoint(endpoint, host=host, port=port)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@app.command("eval")
|
|
148
|
+
def run_eval(
|
|
149
|
+
config: Annotated[Path, typer.Option("--config", "-c")],
|
|
150
|
+
samples: Annotated[Path, typer.Option("--samples", "-s")],
|
|
151
|
+
output: Annotated[Path, typer.Option("--output", "-o")],
|
|
152
|
+
mode: FusionMode = "single",
|
|
153
|
+
config_id: str = "local",
|
|
154
|
+
) -> None:
|
|
155
|
+
fusion_config = load_config(config)
|
|
156
|
+
clients = build_clients(fusion_config)
|
|
157
|
+
engine = FusionEngine(config=fusion_config, clients=clients)
|
|
158
|
+
runner = BenchmarkRunner(engine)
|
|
159
|
+
results = asyncio.run(runner.run_samples(load_jsonl_samples(samples), config_id, mode))
|
|
160
|
+
write_jsonl_results(output, results)
|
|
161
|
+
typer.echo(json.dumps({"results": len(results), "output": str(output)}))
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@app.command()
|
|
165
|
+
def pareto(
|
|
166
|
+
points: Annotated[Path, typer.Option("--points", "-p")],
|
|
167
|
+
output: Annotated[Path, typer.Option("--output", "-o")],
|
|
168
|
+
) -> None:
|
|
169
|
+
write_pareto_report(output, load_points(points))
|
|
170
|
+
typer.echo(json.dumps({"output": str(output)}))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@app.command("tiny-bench")
|
|
174
|
+
def tiny_bench(
|
|
175
|
+
config: Annotated[Path, typer.Option("--config", "-c")],
|
|
176
|
+
output: Annotated[Path, typer.Option("--output", "-o")],
|
|
177
|
+
report: Annotated[Path | None, typer.Option("--report", "-r")] = None,
|
|
178
|
+
mode: FusionMode = "panel",
|
|
179
|
+
config_id: str = "local",
|
|
180
|
+
) -> None:
|
|
181
|
+
fusion_config = load_config(config)
|
|
182
|
+
clients = build_clients(fusion_config)
|
|
183
|
+
engine = FusionEngine(config=fusion_config, clients=clients)
|
|
184
|
+
results = asyncio.run(
|
|
185
|
+
run_tiny_benchmark(
|
|
186
|
+
engine,
|
|
187
|
+
config_id=config_id,
|
|
188
|
+
mode=mode,
|
|
189
|
+
tasks=load_tiny_tasks(),
|
|
190
|
+
model_versions={endpoint.id: endpoint.model for endpoint in fusion_config.endpoints},
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
write_tiny_jsonl(output, results)
|
|
194
|
+
response = {"results": len(results), "output": str(output)}
|
|
195
|
+
if report is not None:
|
|
196
|
+
write_tiny_benchmark_report(report, results)
|
|
197
|
+
response["report"] = str(report)
|
|
198
|
+
typer.echo(json.dumps(response))
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@app.command("fusion-bench")
|
|
202
|
+
def fusion_bench(
|
|
203
|
+
config: Annotated[Path, typer.Option("--config", "-c")],
|
|
204
|
+
output: Annotated[Path, typer.Option("--output", "-o")],
|
|
205
|
+
manifest: Annotated[Path | None, typer.Option("--manifest", "-m")] = None,
|
|
206
|
+
run_root: Annotated[Path, typer.Option("--run-root")] = Path(".fusionkit/fusion-bench"),
|
|
207
|
+
report_jsonl: Annotated[Path | None, typer.Option("--report-jsonl")] = None,
|
|
208
|
+
report_markdown: Annotated[
|
|
209
|
+
Path | None,
|
|
210
|
+
typer.Option("--report", "-r", "--report-markdown"),
|
|
211
|
+
] = None,
|
|
212
|
+
report_html: Annotated[Path | None, typer.Option("--report-html")] = None,
|
|
213
|
+
handoff_command: Annotated[
|
|
214
|
+
str | None,
|
|
215
|
+
typer.Option(
|
|
216
|
+
"--handoff-command",
|
|
217
|
+
help=(
|
|
218
|
+
"Optional HandoffKit-compatible command. It receives task JSON on stdin "
|
|
219
|
+
"and emits model-fusion contract records on stdout."
|
|
220
|
+
),
|
|
221
|
+
),
|
|
222
|
+
] = None,
|
|
223
|
+
handoff_timeout_s: Annotated[
|
|
224
|
+
float,
|
|
225
|
+
typer.Option("--handoff-timeout-s", min=1.0),
|
|
226
|
+
] = 300.0,
|
|
227
|
+
mode: FusionMode = "panel",
|
|
228
|
+
config_id: str = "local",
|
|
229
|
+
) -> None:
|
|
230
|
+
fusion_config = load_config(config)
|
|
231
|
+
clients = build_clients(fusion_config)
|
|
232
|
+
engine = FusionEngine(config=fusion_config, clients=clients)
|
|
233
|
+
runner = FusionBenchRunner(
|
|
234
|
+
engine,
|
|
235
|
+
run_root=run_root,
|
|
236
|
+
config_id=config_id,
|
|
237
|
+
mode=mode,
|
|
238
|
+
model_versions={endpoint.id: endpoint.model for endpoint in fusion_config.endpoints},
|
|
239
|
+
handoff_executor=(
|
|
240
|
+
CommandHandoffKitExecutor(
|
|
241
|
+
shlex.split(handoff_command),
|
|
242
|
+
timeout_s=handoff_timeout_s,
|
|
243
|
+
)
|
|
244
|
+
if handoff_command is not None
|
|
245
|
+
else None
|
|
246
|
+
),
|
|
247
|
+
)
|
|
248
|
+
tasks = load_benchmark_tasks(manifest) if manifest else load_benchmark_tasks()
|
|
249
|
+
rows = asyncio.run(runner.run_tasks(tasks))
|
|
250
|
+
write_fusion_bench_jsonl(output, rows)
|
|
251
|
+
response: dict[str, int | str] = {"rows": len(rows), "output": str(output)}
|
|
252
|
+
response.update(
|
|
253
|
+
_write_fusion_bench_reports(
|
|
254
|
+
build_fusion_bench_report(rows),
|
|
255
|
+
report_jsonl=report_jsonl,
|
|
256
|
+
report_markdown=report_markdown,
|
|
257
|
+
report_html=report_html,
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
typer.echo(json.dumps(response))
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@app.command("fusion-bench-report")
|
|
264
|
+
def fusion_bench_report(
|
|
265
|
+
input_path: Annotated[Path, typer.Option("--input", "-i")],
|
|
266
|
+
report_jsonl: Annotated[Path | None, typer.Option("--jsonl")] = None,
|
|
267
|
+
report_markdown: Annotated[Path | None, typer.Option("--markdown", "-m")] = None,
|
|
268
|
+
report_html: Annotated[Path | None, typer.Option("--html")] = None,
|
|
269
|
+
) -> None:
|
|
270
|
+
rows = load_fusion_bench_jsonl(input_path)
|
|
271
|
+
report = build_fusion_bench_report(rows)
|
|
272
|
+
response: dict[str, int | str] = {
|
|
273
|
+
"rows": len(rows),
|
|
274
|
+
"tasks": report.aggregate.total_tasks,
|
|
275
|
+
"skipped": report.aggregate.skipped_tasks,
|
|
276
|
+
"failed": report.aggregate.failed_tasks,
|
|
277
|
+
}
|
|
278
|
+
response.update(
|
|
279
|
+
_write_fusion_bench_reports(
|
|
280
|
+
report,
|
|
281
|
+
report_jsonl=report_jsonl,
|
|
282
|
+
report_markdown=report_markdown,
|
|
283
|
+
report_html=report_html,
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
typer.echo(json.dumps(response))
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@app.command("public-bench")
|
|
290
|
+
def public_bench(
|
|
291
|
+
suite: Annotated[str, typer.Option("--suite", help="public benchmark suite to run")],
|
|
292
|
+
panel: Annotated[
|
|
293
|
+
str,
|
|
294
|
+
typer.Option("--panel", help="benchmark panel id (e.g. decorrelated-peers)"),
|
|
295
|
+
] = "decorrelated-peers",
|
|
296
|
+
gateway_base_url: Annotated[
|
|
297
|
+
str, typer.Option("--gateway-base-url")
|
|
298
|
+
] = "http://127.0.0.1:8080",
|
|
299
|
+
gateway_model: Annotated[
|
|
300
|
+
str | None,
|
|
301
|
+
typer.Option("--gateway-model", help="gateway model alias (defaults per suite)"),
|
|
302
|
+
] = None,
|
|
303
|
+
runner_command: Annotated[
|
|
304
|
+
str | None,
|
|
305
|
+
typer.Option(
|
|
306
|
+
"--runner-command",
|
|
307
|
+
help=(
|
|
308
|
+
"External runner adapter. Receives the request JSON on stdin and emits a "
|
|
309
|
+
"normalized run envelope on stdout. Omit to produce an 'unavailable' report."
|
|
310
|
+
),
|
|
311
|
+
),
|
|
312
|
+
] = None,
|
|
313
|
+
subset: Annotated[
|
|
314
|
+
int | None,
|
|
315
|
+
typer.Option("--subset", min=1, help="run only the first N tasks (subset-first)"),
|
|
316
|
+
] = None,
|
|
317
|
+
runner_timeout_s: Annotated[float, typer.Option("--runner-timeout-s", min=1.0)] = 1800.0,
|
|
318
|
+
output: Annotated[Path | None, typer.Option("--output", "-o")] = None,
|
|
319
|
+
report_markdown: Annotated[Path | None, typer.Option("--report", "-r")] = None,
|
|
320
|
+
ledger: Annotated[
|
|
321
|
+
Path | None,
|
|
322
|
+
typer.Option("--ledger", help="append the run to a history ledger and report drift"),
|
|
323
|
+
] = None,
|
|
324
|
+
) -> None:
|
|
325
|
+
"""Run a public coding benchmark against the gateway and compare to leaderboards."""
|
|
326
|
+
|
|
327
|
+
resolved_suite = _resolve_public_suite(suite)
|
|
328
|
+
info = PUBLIC_BENCHMARK_INFO[resolved_suite]
|
|
329
|
+
benchmark_panel = get_benchmark_panel(panel)
|
|
330
|
+
request = ExternalBenchmarkRequest(
|
|
331
|
+
suite=resolved_suite,
|
|
332
|
+
mount_mode=info.mount_mode,
|
|
333
|
+
gateway_base_url=gateway_base_url,
|
|
334
|
+
gateway_model=gateway_model or info.default_gateway_model,
|
|
335
|
+
panel_id=benchmark_panel.panel_id,
|
|
336
|
+
subset=subset,
|
|
337
|
+
)
|
|
338
|
+
executor = (
|
|
339
|
+
CommandExternalBenchmarkExecutor(
|
|
340
|
+
shlex.split(runner_command),
|
|
341
|
+
timeout_s=runner_timeout_s,
|
|
342
|
+
)
|
|
343
|
+
if runner_command is not None
|
|
344
|
+
else None
|
|
345
|
+
)
|
|
346
|
+
run = asyncio.run(run_public_benchmark(request, executor))
|
|
347
|
+
comparison = build_benchmark_comparison(run, benchmark_panel)
|
|
348
|
+
response: dict[str, int | str | float | None] = {
|
|
349
|
+
"suite": run.suite,
|
|
350
|
+
"panel": run.panel_id,
|
|
351
|
+
"availability": run.availability,
|
|
352
|
+
"fusion_score": comparison.fusion_score,
|
|
353
|
+
"fusion_ci": _fmt_ci(comparison.fusion_ci_low, comparison.fusion_ci_high),
|
|
354
|
+
"measured_oracle": comparison.measured_oracle,
|
|
355
|
+
"measured_regret": comparison.measured_regret,
|
|
356
|
+
"oracle_headroom": comparison.oracle_headroom,
|
|
357
|
+
"lopsided_panel": int(comparison.lopsided),
|
|
358
|
+
"infra_error_tasks": run.infra_error_tasks,
|
|
359
|
+
"excluded_tasks": run.excluded_tasks,
|
|
360
|
+
}
|
|
361
|
+
if output is not None:
|
|
362
|
+
write_external_runs_jsonl(output, [run])
|
|
363
|
+
response["output"] = str(output)
|
|
364
|
+
if report_markdown is not None:
|
|
365
|
+
write_benchmark_comparison_markdown(report_markdown, comparison)
|
|
366
|
+
response["report"] = str(report_markdown)
|
|
367
|
+
if ledger is not None:
|
|
368
|
+
record = BenchRunRecord(
|
|
369
|
+
suite=run.suite,
|
|
370
|
+
panel_id=run.panel_id,
|
|
371
|
+
resolved_tasks=run.resolved_tasks,
|
|
372
|
+
score=comparison.fusion_score,
|
|
373
|
+
ci_low=comparison.fusion_ci_low,
|
|
374
|
+
ci_high=comparison.fusion_ci_high,
|
|
375
|
+
cache_signature=_optional_str(run.raw_metadata.get("cache_signature")),
|
|
376
|
+
repo_sha=_optional_str(run.provenance.get("repo_sha")),
|
|
377
|
+
)
|
|
378
|
+
drift = drift_vs_previous(ledger, record)
|
|
379
|
+
append_run(ledger, record)
|
|
380
|
+
response["ledger"] = str(ledger)
|
|
381
|
+
if drift is not None:
|
|
382
|
+
response["drift_delta"] = drift.delta
|
|
383
|
+
response["regressed"] = int(drift.regressed)
|
|
384
|
+
typer.echo(json.dumps(response))
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _fmt_ci(low: float | None, high: float | None) -> str | None:
|
|
388
|
+
if low is None or high is None:
|
|
389
|
+
return None
|
|
390
|
+
return f"[{low:.4f}, {high:.4f}]"
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _optional_str(value: object) -> str | None:
|
|
394
|
+
return value if isinstance(value, str) and value else None
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@app.command("public-bench-baselines")
|
|
398
|
+
def public_bench_baselines(
|
|
399
|
+
suite: Annotated[
|
|
400
|
+
str | None,
|
|
401
|
+
typer.Option("--suite", help="limit to one suite; omit for all suites"),
|
|
402
|
+
] = None,
|
|
403
|
+
) -> None:
|
|
404
|
+
"""Print the published leaderboard baselines used for comparison."""
|
|
405
|
+
|
|
406
|
+
suites = (PUBLIC_BENCHMARK_SUITES if suite is None else (_resolve_public_suite(suite),))
|
|
407
|
+
payload = {
|
|
408
|
+
target: [baseline.model_dump(mode="json") for baseline in baselines_for(target)]
|
|
409
|
+
for target in suites
|
|
410
|
+
}
|
|
411
|
+
typer.echo(json.dumps(payload, indent=2))
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
_ROLE_PROMPT_FILE = {
|
|
415
|
+
"judge_system": "judge.md",
|
|
416
|
+
"synthesizer_system": "synthesizer.md",
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@app.command("tune-prompts")
|
|
421
|
+
def tune_prompts(
|
|
422
|
+
config: Annotated[Path, typer.Option("--config", "-c", help="panel FusionConfig YAML")],
|
|
423
|
+
bank: Annotated[
|
|
424
|
+
Path, typer.Option("--bank", help="candidate bank path (built if missing)")
|
|
425
|
+
] = Path(".fusionkit/tuning/bank.json"),
|
|
426
|
+
role: Annotated[str, typer.Option("--role", help="prompt role to tune")] = "synthesizer_system",
|
|
427
|
+
subset: Annotated[int, typer.Option("--subset", min=1)] = 40,
|
|
428
|
+
bank_max_tests: Annotated[
|
|
429
|
+
int, typer.Option("--bank-max-tests", help="cap tests/task when building the bank (0=all)")
|
|
430
|
+
] = 0,
|
|
431
|
+
optimizer_model: Annotated[
|
|
432
|
+
str | None, typer.Option("--optimizer-model", help="endpoint id (default: judge)")
|
|
433
|
+
] = None,
|
|
434
|
+
max_iterations: Annotated[int, typer.Option("--max-iterations", min=1)] = 8,
|
|
435
|
+
patience: Annotated[int, typer.Option("--patience", min=1)] = 3,
|
|
436
|
+
val_fraction: Annotated[float, typer.Option("--val-fraction", min=0.1, max=0.9)] = 0.4,
|
|
437
|
+
seed: Annotated[int, typer.Option("--seed")] = 0,
|
|
438
|
+
test_timeout_s: Annotated[float, typer.Option("--test-timeout-s", min=1.0)] = 8.0,
|
|
439
|
+
concurrency: Annotated[int, typer.Option("--concurrency", min=1)] = 4,
|
|
440
|
+
prompts_out: Annotated[Path, typer.Option("--prompts-out")] = Path(".fusionkit/prompts"),
|
|
441
|
+
cache_dir: Annotated[Path, typer.Option("--cache-dir")] = Path(".fusionkit/tuning/cache"),
|
|
442
|
+
report: Annotated[Path | None, typer.Option("--report", "-r")] = None,
|
|
443
|
+
ledger: Annotated[Path | None, typer.Option("--ledger")] = None,
|
|
444
|
+
) -> None:
|
|
445
|
+
"""Automated LLM-driven tuning of judge/synth prompts over a frozen bank."""
|
|
446
|
+
|
|
447
|
+
if role not in _ROLE_PROMPT_FILE:
|
|
448
|
+
raise typer.BadParameter(f"role must be one of {sorted(_ROLE_PROMPT_FILE)}")
|
|
449
|
+
resolved_role = cast(TunableRole, role)
|
|
450
|
+
fusion_config = load_config(config)
|
|
451
|
+
clients = build_clients(fusion_config)
|
|
452
|
+
engine = FusionEngine(config=fusion_config, clients=clients)
|
|
453
|
+
sandbox = build_sandbox(SandboxConfig(backend=os.environ.get("BENCH_SANDBOX", "local")))
|
|
454
|
+
|
|
455
|
+
if bank.exists():
|
|
456
|
+
candidate_bank = load_bank(bank)
|
|
457
|
+
else:
|
|
458
|
+
problems = load_problems(
|
|
459
|
+
subset,
|
|
460
|
+
version=os.environ.get("LCB_VERSION", "release_v6"),
|
|
461
|
+
min_date=os.environ.get("LCB_MIN_DATE", "2025-01-01"),
|
|
462
|
+
manifest=load_manifest(os.environ.get("LCB_MANIFEST")),
|
|
463
|
+
)
|
|
464
|
+
prepared = [
|
|
465
|
+
PreparedTask(**task) for task in prepare_tasks(problems, max_tests=bank_max_tests)
|
|
466
|
+
]
|
|
467
|
+
signature = bank_signature(engine, prompt_suffix=LCB_PROMPT_SUFFIX)
|
|
468
|
+
candidate_bank = asyncio.run(
|
|
469
|
+
build_candidate_bank(
|
|
470
|
+
engine,
|
|
471
|
+
sandbox,
|
|
472
|
+
prepared,
|
|
473
|
+
signature=signature,
|
|
474
|
+
test_timeout_s=test_timeout_s,
|
|
475
|
+
concurrency=concurrency,
|
|
476
|
+
)
|
|
477
|
+
)
|
|
478
|
+
save_bank(bank, candidate_bank)
|
|
479
|
+
|
|
480
|
+
decision = select_decision_tasks(candidate_bank)
|
|
481
|
+
if len(decision) < 2:
|
|
482
|
+
typer.echo(
|
|
483
|
+
json.dumps(
|
|
484
|
+
{
|
|
485
|
+
"error": "not enough decision tasks to tune",
|
|
486
|
+
"decision_tasks": len(decision),
|
|
487
|
+
"total_tasks": len(candidate_bank.tasks),
|
|
488
|
+
}
|
|
489
|
+
)
|
|
490
|
+
)
|
|
491
|
+
raise typer.Exit(code=1)
|
|
492
|
+
split = split_dev_val(decision, val_fraction=val_fraction, seed=seed)
|
|
493
|
+
by_id = {task.task_id: task for task in candidate_bank.tasks}
|
|
494
|
+
dev_tasks = [by_id[task_id] for task_id in split.dev]
|
|
495
|
+
val_tasks = [by_id[task_id] for task_id in split.val]
|
|
496
|
+
|
|
497
|
+
runtime = TunerRuntime(
|
|
498
|
+
clients=clients,
|
|
499
|
+
judge_id=fusion_config.resolved_judge_model,
|
|
500
|
+
synth_id=fusion_config.resolved_synthesizer_model,
|
|
501
|
+
bank_signature=candidate_bank.signature,
|
|
502
|
+
sandbox=sandbox,
|
|
503
|
+
cache_dir=cache_dir,
|
|
504
|
+
judge_sampling=fusion_config.sampling.model_copy(update={"temperature": 0.0}),
|
|
505
|
+
synth_sampling=fusion_config.sampling,
|
|
506
|
+
test_timeout_s=test_timeout_s,
|
|
507
|
+
concurrency=concurrency,
|
|
508
|
+
)
|
|
509
|
+
proposer = LLMProposer(
|
|
510
|
+
clients[optimizer_model or fusion_config.resolved_judge_model],
|
|
511
|
+
fusion_config.sampling,
|
|
512
|
+
)
|
|
513
|
+
result = asyncio.run(
|
|
514
|
+
optimize(
|
|
515
|
+
runtime,
|
|
516
|
+
dev_tasks=dev_tasks,
|
|
517
|
+
val_tasks=val_tasks,
|
|
518
|
+
proposer=proposer,
|
|
519
|
+
role=resolved_role,
|
|
520
|
+
max_iterations=max_iterations,
|
|
521
|
+
patience=patience,
|
|
522
|
+
)
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
tuned_prompt = result.best_variant.role_text(resolved_role)
|
|
526
|
+
response: dict[str, object] = {
|
|
527
|
+
"role": result.role,
|
|
528
|
+
"decision_tasks": len(decision),
|
|
529
|
+
"dev": len(dev_tasks),
|
|
530
|
+
"val": len(val_tasks),
|
|
531
|
+
"baseline_dev": result.baseline_dev.score,
|
|
532
|
+
"best_dev": result.best_dev.score,
|
|
533
|
+
"baseline_val": result.baseline_val.score,
|
|
534
|
+
"best_val": result.best_val.score,
|
|
535
|
+
"val_ci": _fmt_ci(result.best_val.ci_low, result.best_val.ci_high),
|
|
536
|
+
"accepted_trials": sum(1 for trial in result.trials if trial.accepted),
|
|
537
|
+
"trials": len(result.trials),
|
|
538
|
+
"improved": result.best_val.score > result.baseline_val.score,
|
|
539
|
+
}
|
|
540
|
+
if tuned_prompt is not None and result.best_val.score > result.baseline_val.score:
|
|
541
|
+
prompts_out.mkdir(parents=True, exist_ok=True)
|
|
542
|
+
out_file = prompts_out / _ROLE_PROMPT_FILE[role]
|
|
543
|
+
out_file.write_text(tuned_prompt + "\n", encoding="utf-8")
|
|
544
|
+
response["prompts_out"] = str(out_file)
|
|
545
|
+
if report is not None:
|
|
546
|
+
report.parent.mkdir(parents=True, exist_ok=True)
|
|
547
|
+
report.write_text(_format_tuning_report(result), encoding="utf-8")
|
|
548
|
+
response["report"] = str(report)
|
|
549
|
+
if ledger is not None:
|
|
550
|
+
append_run(
|
|
551
|
+
ledger,
|
|
552
|
+
BenchRunRecord(
|
|
553
|
+
suite="prompt-tuning",
|
|
554
|
+
panel_id=result.role,
|
|
555
|
+
resolved_tasks=len(val_tasks),
|
|
556
|
+
score=result.best_val.score,
|
|
557
|
+
ci_low=result.best_val.ci_low,
|
|
558
|
+
ci_high=result.best_val.ci_high,
|
|
559
|
+
),
|
|
560
|
+
)
|
|
561
|
+
response["ledger"] = str(ledger)
|
|
562
|
+
typer.echo(json.dumps(response))
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def _format_tuning_report(result: TuningResult) -> str:
|
|
566
|
+
lines = [
|
|
567
|
+
"# Prompt Tuning Report",
|
|
568
|
+
"",
|
|
569
|
+
f"- Role: {result.role}",
|
|
570
|
+
f"- Baseline dev: {result.baseline_dev.score:.4f} -> best dev: {result.best_dev.score:.4f}",
|
|
571
|
+
f"- Baseline val: {result.baseline_val.score:.4f} -> best val: {result.best_val.score:.4f} "
|
|
572
|
+
f"(95% CI [{result.best_val.ci_low:.4f}, {result.best_val.ci_high:.4f}])",
|
|
573
|
+
f"- Promoted: {result.best_val.score > result.baseline_val.score}",
|
|
574
|
+
"",
|
|
575
|
+
"## Trials (dev)",
|
|
576
|
+
"",
|
|
577
|
+
"| Iter | dev score | wins | losses | accepted |",
|
|
578
|
+
"| ---: | ---: | ---: | ---: | :--: |",
|
|
579
|
+
]
|
|
580
|
+
for trial in result.trials:
|
|
581
|
+
lines.append(
|
|
582
|
+
f"| {trial.iteration} | {trial.dev_score:.4f} | {trial.wins} | {trial.losses} | "
|
|
583
|
+
f"{'yes' if trial.accepted else 'no'} |"
|
|
584
|
+
)
|
|
585
|
+
lines.append("")
|
|
586
|
+
return "\n".join(lines)
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def _resolve_public_suite(suite: str) -> PublicBenchmarkSuite:
|
|
590
|
+
if suite not in PUBLIC_BENCHMARK_SUITES:
|
|
591
|
+
known = ", ".join(PUBLIC_BENCHMARK_SUITES)
|
|
592
|
+
raise typer.BadParameter(f"unknown suite {suite!r}; choose one of: {known}")
|
|
593
|
+
return cast(PublicBenchmarkSuite, suite)
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def _write_fusion_bench_reports(
|
|
597
|
+
report: FusionBenchReport,
|
|
598
|
+
*,
|
|
599
|
+
report_jsonl: Path | None,
|
|
600
|
+
report_markdown: Path | None,
|
|
601
|
+
report_html: Path | None,
|
|
602
|
+
) -> dict[str, str]:
|
|
603
|
+
outputs = {}
|
|
604
|
+
if report_jsonl is not None:
|
|
605
|
+
write_fusion_bench_report_jsonl(report_jsonl, report)
|
|
606
|
+
outputs["report_jsonl"] = str(report_jsonl)
|
|
607
|
+
if report_markdown is not None:
|
|
608
|
+
write_fusion_bench_markdown_report(report_markdown, report)
|
|
609
|
+
outputs["report_markdown"] = str(report_markdown)
|
|
610
|
+
if report_html is not None:
|
|
611
|
+
write_fusion_bench_html_report(report_html, report)
|
|
612
|
+
outputs["report_html"] = str(report_html)
|
|
613
|
+
return outputs
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import asyncio
|
|
4
|
-
import json
|
|
5
|
-
import shlex
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Annotated
|
|
8
|
-
|
|
9
|
-
import typer
|
|
10
|
-
import uvicorn
|
|
11
|
-
from fusionkit_core.clients import build_clients
|
|
12
|
-
from fusionkit_core.config import FusionMode, load_config
|
|
13
|
-
from fusionkit_core.fusion import FusionEngine
|
|
14
|
-
from fusionkit_core.prompts import SYSTEM_PROMPT_DEFAULTS
|
|
15
|
-
from fusionkit_evals.benchmark import BenchmarkRunner, load_jsonl_samples, write_jsonl_results
|
|
16
|
-
from fusionkit_evals.fusion_bench import (
|
|
17
|
-
CommandHandoffKitExecutor,
|
|
18
|
-
FusionBenchReport,
|
|
19
|
-
FusionBenchRunner,
|
|
20
|
-
build_fusion_bench_report,
|
|
21
|
-
load_benchmark_tasks,
|
|
22
|
-
load_fusion_bench_jsonl,
|
|
23
|
-
write_fusion_bench_jsonl,
|
|
24
|
-
)
|
|
25
|
-
from fusionkit_evals.fusion_reports import (
|
|
26
|
-
write_fusion_bench_html_report,
|
|
27
|
-
write_fusion_bench_markdown_report,
|
|
28
|
-
write_fusion_bench_report_jsonl,
|
|
29
|
-
)
|
|
30
|
-
from fusionkit_evals.pareto import load_points, write_pareto_report
|
|
31
|
-
from fusionkit_evals.tiny import (
|
|
32
|
-
load_tiny_tasks,
|
|
33
|
-
run_tiny_benchmark,
|
|
34
|
-
write_tiny_benchmark_report,
|
|
35
|
-
write_tiny_jsonl,
|
|
36
|
-
)
|
|
37
|
-
from fusionkit_server.app import create_app
|
|
38
|
-
from fusionkit_server.openai_endpoint import build_endpoint, serve_single_endpoint
|
|
39
|
-
|
|
40
|
-
app = typer.Typer(help="Local model fusion toolkit.")
|
|
41
|
-
|
|
42
|
-
prompts_app = typer.Typer(help="Inspect and export the built-in fusion prompts.")
|
|
43
|
-
app.add_typer(prompts_app, name="prompts")
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
@prompts_app.command("dump")
|
|
47
|
-
def prompts_dump(
|
|
48
|
-
dir: Annotated[
|
|
49
|
-
Path | None,
|
|
50
|
-
typer.Option("--dir", help="write each default prompt to <dir>/<id>.md instead of stdout"),
|
|
51
|
-
] = None,
|
|
52
|
-
) -> None:
|
|
53
|
-
"""Emit the built-in system prompts so a consumer can scaffold editable overrides.
|
|
54
|
-
|
|
55
|
-
With no options this prints a JSON object mapping each prompt id (e.g.
|
|
56
|
-
``judge``, ``trajectory-step``) to its default text. With ``--dir`` it writes
|
|
57
|
-
one ``<id>.md`` file per prompt. This keeps the CLI's scaffolded
|
|
58
|
-
``.fusionkit/prompts`` defaults in lockstep with this package's source.
|
|
59
|
-
"""
|
|
60
|
-
if dir is not None:
|
|
61
|
-
dir.mkdir(parents=True, exist_ok=True)
|
|
62
|
-
for prompt_id, text in SYSTEM_PROMPT_DEFAULTS.items():
|
|
63
|
-
(dir / f"{prompt_id}.md").write_text(text + "\n")
|
|
64
|
-
typer.echo(json.dumps({"dir": str(dir), "count": len(SYSTEM_PROMPT_DEFAULTS)}))
|
|
65
|
-
return
|
|
66
|
-
typer.echo(json.dumps(SYSTEM_PROMPT_DEFAULTS, indent=2))
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@app.command()
|
|
70
|
-
def serve(
|
|
71
|
-
config: Annotated[Path, typer.Option("--config", "-c")],
|
|
72
|
-
host: str = "127.0.0.1",
|
|
73
|
-
port: int = 8080,
|
|
74
|
-
) -> None:
|
|
75
|
-
fusion_config = load_config(config)
|
|
76
|
-
api = create_app(fusion_config)
|
|
77
|
-
uvicorn.run(api, host=host, port=port)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
@app.command("serve-endpoint")
|
|
81
|
-
def serve_endpoint(
|
|
82
|
-
id: Annotated[str, typer.Option("--id", help="endpoint id exposed via /v1/models")],
|
|
83
|
-
model: Annotated[str, typer.Option("--model", help="provider model name (e.g. gpt-5.5)")],
|
|
84
|
-
port: Annotated[int, typer.Option("--port")],
|
|
85
|
-
provider: Annotated[str, typer.Option("--provider")] = "openai",
|
|
86
|
-
base_url: Annotated[
|
|
87
|
-
str | None, typer.Option("--base-url", help="override the provider base URL")
|
|
88
|
-
] = None,
|
|
89
|
-
api_key_env: Annotated[
|
|
90
|
-
str | None, typer.Option("--api-key-env", help="env var holding the API key")
|
|
91
|
-
] = None,
|
|
92
|
-
timeout_s: Annotated[float, typer.Option("--timeout-s")] = 120.0,
|
|
93
|
-
host: Annotated[str, typer.Option("--host")] = "127.0.0.1",
|
|
94
|
-
) -> None:
|
|
95
|
-
"""Front a single provider model as an OpenAI Chat Completions endpoint."""
|
|
96
|
-
endpoint = build_endpoint(
|
|
97
|
-
id=id,
|
|
98
|
-
model=model,
|
|
99
|
-
provider=provider,
|
|
100
|
-
base_url=base_url,
|
|
101
|
-
api_key_env=api_key_env,
|
|
102
|
-
timeout_s=timeout_s,
|
|
103
|
-
)
|
|
104
|
-
serve_single_endpoint(endpoint, host=host, port=port)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
@app.command("eval")
|
|
108
|
-
def run_eval(
|
|
109
|
-
config: Annotated[Path, typer.Option("--config", "-c")],
|
|
110
|
-
samples: Annotated[Path, typer.Option("--samples", "-s")],
|
|
111
|
-
output: Annotated[Path, typer.Option("--output", "-o")],
|
|
112
|
-
mode: FusionMode = "single",
|
|
113
|
-
config_id: str = "local",
|
|
114
|
-
) -> None:
|
|
115
|
-
fusion_config = load_config(config)
|
|
116
|
-
clients = build_clients(fusion_config)
|
|
117
|
-
engine = FusionEngine(config=fusion_config, clients=clients)
|
|
118
|
-
runner = BenchmarkRunner(engine)
|
|
119
|
-
results = asyncio.run(runner.run_samples(load_jsonl_samples(samples), config_id, mode))
|
|
120
|
-
write_jsonl_results(output, results)
|
|
121
|
-
typer.echo(json.dumps({"results": len(results), "output": str(output)}))
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@app.command()
|
|
125
|
-
def pareto(
|
|
126
|
-
points: Annotated[Path, typer.Option("--points", "-p")],
|
|
127
|
-
output: Annotated[Path, typer.Option("--output", "-o")],
|
|
128
|
-
) -> None:
|
|
129
|
-
write_pareto_report(output, load_points(points))
|
|
130
|
-
typer.echo(json.dumps({"output": str(output)}))
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
@app.command("tiny-bench")
|
|
134
|
-
def tiny_bench(
|
|
135
|
-
config: Annotated[Path, typer.Option("--config", "-c")],
|
|
136
|
-
output: Annotated[Path, typer.Option("--output", "-o")],
|
|
137
|
-
report: Annotated[Path | None, typer.Option("--report", "-r")] = None,
|
|
138
|
-
mode: FusionMode = "panel",
|
|
139
|
-
config_id: str = "local",
|
|
140
|
-
) -> None:
|
|
141
|
-
fusion_config = load_config(config)
|
|
142
|
-
clients = build_clients(fusion_config)
|
|
143
|
-
engine = FusionEngine(config=fusion_config, clients=clients)
|
|
144
|
-
results = asyncio.run(
|
|
145
|
-
run_tiny_benchmark(
|
|
146
|
-
engine,
|
|
147
|
-
config_id=config_id,
|
|
148
|
-
mode=mode,
|
|
149
|
-
tasks=load_tiny_tasks(),
|
|
150
|
-
model_versions={endpoint.id: endpoint.model for endpoint in fusion_config.endpoints},
|
|
151
|
-
)
|
|
152
|
-
)
|
|
153
|
-
write_tiny_jsonl(output, results)
|
|
154
|
-
response = {"results": len(results), "output": str(output)}
|
|
155
|
-
if report is not None:
|
|
156
|
-
write_tiny_benchmark_report(report, results)
|
|
157
|
-
response["report"] = str(report)
|
|
158
|
-
typer.echo(json.dumps(response))
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
@app.command("fusion-bench")
|
|
162
|
-
def fusion_bench(
|
|
163
|
-
config: Annotated[Path, typer.Option("--config", "-c")],
|
|
164
|
-
output: Annotated[Path, typer.Option("--output", "-o")],
|
|
165
|
-
manifest: Annotated[Path | None, typer.Option("--manifest", "-m")] = None,
|
|
166
|
-
run_root: Annotated[Path, typer.Option("--run-root")] = Path(".fusionkit/fusion-bench"),
|
|
167
|
-
report_jsonl: Annotated[Path | None, typer.Option("--report-jsonl")] = None,
|
|
168
|
-
report_markdown: Annotated[
|
|
169
|
-
Path | None,
|
|
170
|
-
typer.Option("--report", "-r", "--report-markdown"),
|
|
171
|
-
] = None,
|
|
172
|
-
report_html: Annotated[Path | None, typer.Option("--report-html")] = None,
|
|
173
|
-
handoff_command: Annotated[
|
|
174
|
-
str | None,
|
|
175
|
-
typer.Option(
|
|
176
|
-
"--handoff-command",
|
|
177
|
-
help=(
|
|
178
|
-
"Optional HandoffKit-compatible command. It receives task JSON on stdin "
|
|
179
|
-
"and emits model-fusion contract records on stdout."
|
|
180
|
-
),
|
|
181
|
-
),
|
|
182
|
-
] = None,
|
|
183
|
-
handoff_timeout_s: Annotated[
|
|
184
|
-
float,
|
|
185
|
-
typer.Option("--handoff-timeout-s", min=1.0),
|
|
186
|
-
] = 300.0,
|
|
187
|
-
mode: FusionMode = "panel",
|
|
188
|
-
config_id: str = "local",
|
|
189
|
-
) -> None:
|
|
190
|
-
fusion_config = load_config(config)
|
|
191
|
-
clients = build_clients(fusion_config)
|
|
192
|
-
engine = FusionEngine(config=fusion_config, clients=clients)
|
|
193
|
-
runner = FusionBenchRunner(
|
|
194
|
-
engine,
|
|
195
|
-
run_root=run_root,
|
|
196
|
-
config_id=config_id,
|
|
197
|
-
mode=mode,
|
|
198
|
-
model_versions={endpoint.id: endpoint.model for endpoint in fusion_config.endpoints},
|
|
199
|
-
handoff_executor=(
|
|
200
|
-
CommandHandoffKitExecutor(
|
|
201
|
-
shlex.split(handoff_command),
|
|
202
|
-
timeout_s=handoff_timeout_s,
|
|
203
|
-
)
|
|
204
|
-
if handoff_command is not None
|
|
205
|
-
else None
|
|
206
|
-
),
|
|
207
|
-
)
|
|
208
|
-
tasks = load_benchmark_tasks(manifest) if manifest else load_benchmark_tasks()
|
|
209
|
-
rows = asyncio.run(runner.run_tasks(tasks))
|
|
210
|
-
write_fusion_bench_jsonl(output, rows)
|
|
211
|
-
response: dict[str, int | str] = {"rows": len(rows), "output": str(output)}
|
|
212
|
-
response.update(
|
|
213
|
-
_write_fusion_bench_reports(
|
|
214
|
-
build_fusion_bench_report(rows),
|
|
215
|
-
report_jsonl=report_jsonl,
|
|
216
|
-
report_markdown=report_markdown,
|
|
217
|
-
report_html=report_html,
|
|
218
|
-
)
|
|
219
|
-
)
|
|
220
|
-
typer.echo(json.dumps(response))
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
@app.command("fusion-bench-report")
|
|
224
|
-
def fusion_bench_report(
|
|
225
|
-
input_path: Annotated[Path, typer.Option("--input", "-i")],
|
|
226
|
-
report_jsonl: Annotated[Path | None, typer.Option("--jsonl")] = None,
|
|
227
|
-
report_markdown: Annotated[Path | None, typer.Option("--markdown", "-m")] = None,
|
|
228
|
-
report_html: Annotated[Path | None, typer.Option("--html")] = None,
|
|
229
|
-
) -> None:
|
|
230
|
-
rows = load_fusion_bench_jsonl(input_path)
|
|
231
|
-
report = build_fusion_bench_report(rows)
|
|
232
|
-
response: dict[str, int | str] = {
|
|
233
|
-
"rows": len(rows),
|
|
234
|
-
"tasks": report.aggregate.total_tasks,
|
|
235
|
-
"skipped": report.aggregate.skipped_tasks,
|
|
236
|
-
"failed": report.aggregate.failed_tasks,
|
|
237
|
-
}
|
|
238
|
-
response.update(
|
|
239
|
-
_write_fusion_bench_reports(
|
|
240
|
-
report,
|
|
241
|
-
report_jsonl=report_jsonl,
|
|
242
|
-
report_markdown=report_markdown,
|
|
243
|
-
report_html=report_html,
|
|
244
|
-
)
|
|
245
|
-
)
|
|
246
|
-
typer.echo(json.dumps(response))
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
def _write_fusion_bench_reports(
|
|
250
|
-
report: FusionBenchReport,
|
|
251
|
-
*,
|
|
252
|
-
report_jsonl: Path | None,
|
|
253
|
-
report_markdown: Path | None,
|
|
254
|
-
report_html: Path | None,
|
|
255
|
-
) -> dict[str, str]:
|
|
256
|
-
outputs = {}
|
|
257
|
-
if report_jsonl is not None:
|
|
258
|
-
write_fusion_bench_report_jsonl(report_jsonl, report)
|
|
259
|
-
outputs["report_jsonl"] = str(report_jsonl)
|
|
260
|
-
if report_markdown is not None:
|
|
261
|
-
write_fusion_bench_markdown_report(report_markdown, report)
|
|
262
|
-
outputs["report_markdown"] = str(report_markdown)
|
|
263
|
-
if report_html is not None:
|
|
264
|
-
write_fusion_bench_html_report(report_html, report)
|
|
265
|
-
outputs["report_html"] = str(report_html)
|
|
266
|
-
return outputs
|
|
File without changes
|