tuneprompt 1.0.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.
- promptforge/__init__.py +15 -0
- promptforge/__main__.py +6 -0
- promptforge/analyzer.py +27 -0
- promptforge/cli.py +461 -0
- promptforge/comparison.py +101 -0
- promptforge/config.py +173 -0
- promptforge/data/__init__.py +33 -0
- promptforge/data/generate.py +198 -0
- promptforge/data/optimizer_generate.py +1683 -0
- promptforge/data/prepare.py +109 -0
- promptforge/evaluation.py +227 -0
- promptforge/hub.py +118 -0
- promptforge/local_paths.py +138 -0
- promptforge/models/__init__.py +3 -0
- promptforge/models/quality.py +180 -0
- promptforge/optimizer.py +113 -0
- promptforge/optimizer_chat.py +139 -0
- promptforge/optimizer_validation.py +106 -0
- promptforge/pipeline.py +202 -0
- promptforge/py.typed +0 -0
- promptforge/scorer.py +124 -0
- promptforge/training/__init__.py +20 -0
- promptforge/training/metrics.py +77 -0
- promptforge/training/train_optimizer.py +355 -0
- promptforge/training/train_quality.py +196 -0
- promptforge/training/trainer.py +36 -0
- promptforge/utils/__init__.py +17 -0
- promptforge/utils/device.py +72 -0
- tuneprompt-1.0.0.dist-info/METADATA +316 -0
- tuneprompt-1.0.0.dist-info/RECORD +34 -0
- tuneprompt-1.0.0.dist-info/WHEEL +5 -0
- tuneprompt-1.0.0.dist-info/entry_points.txt +3 -0
- tuneprompt-1.0.0.dist-info/licenses/LICENSE +21 -0
- tuneprompt-1.0.0.dist-info/top_level.txt +1 -0
promptforge/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""PromptForge — local prompt quality scoring and optimization."""
|
|
2
|
+
|
|
3
|
+
from promptforge.analyzer import PromptAnalyzer
|
|
4
|
+
from promptforge.optimizer import PromptOptimizer
|
|
5
|
+
from promptforge.pipeline import PromptForge
|
|
6
|
+
from promptforge.scorer import PromptQualityScorer
|
|
7
|
+
|
|
8
|
+
__version__ = "1.0.0"
|
|
9
|
+
__all__ = [
|
|
10
|
+
"PromptAnalyzer",
|
|
11
|
+
"PromptForge",
|
|
12
|
+
"PromptOptimizer",
|
|
13
|
+
"PromptQualityScorer",
|
|
14
|
+
"__version__",
|
|
15
|
+
]
|
promptforge/__main__.py
ADDED
promptforge/analyzer.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from promptforge.scorer import PromptQualityScorer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PromptAnalyzer:
|
|
9
|
+
"""
|
|
10
|
+
Thin analysis helper (PRD `analyzer.py`).
|
|
11
|
+
|
|
12
|
+
Wraps the Phase-1 scorer and normalizes the structured contract used by
|
|
13
|
+
the optimizer and Phase-3 pipeline.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, scorer: PromptQualityScorer) -> None:
|
|
17
|
+
self.scorer = scorer
|
|
18
|
+
|
|
19
|
+
def analyze(self, prompt: str) -> dict[str, Any]:
|
|
20
|
+
result = self.scorer.analyze(prompt)
|
|
21
|
+
return {
|
|
22
|
+
"prompt": prompt,
|
|
23
|
+
"quality_score": result["quality_score"],
|
|
24
|
+
"dimensions": result["dimensions"],
|
|
25
|
+
"issues": result.get("issues", []),
|
|
26
|
+
"missing_information": result.get("missing_information", []),
|
|
27
|
+
}
|
promptforge/cli.py
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _read_prompt(prompt: str | None, file: str | None) -> str:
|
|
10
|
+
if file:
|
|
11
|
+
return Path(file).read_text(encoding="utf-8").strip()
|
|
12
|
+
if prompt == "-":
|
|
13
|
+
return sys.stdin.read().strip()
|
|
14
|
+
if prompt:
|
|
15
|
+
return prompt
|
|
16
|
+
raise SystemExit("Provide a prompt argument, --file, or '-' for stdin.")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _cmd_version(_: argparse.Namespace) -> int:
|
|
20
|
+
from promptforge import __version__
|
|
21
|
+
|
|
22
|
+
print(__version__)
|
|
23
|
+
return 0
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _cmd_init(args: argparse.Namespace) -> int:
|
|
27
|
+
from promptforge.local_paths import (
|
|
28
|
+
ensure_dirs,
|
|
29
|
+
load_user_config,
|
|
30
|
+
save_user_config,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
paths = ensure_dirs()
|
|
34
|
+
cfg = load_user_config()
|
|
35
|
+
cfg.setdefault("prefer_gpu", not args.cpu)
|
|
36
|
+
if args.quality_model:
|
|
37
|
+
cfg["quality_model_path"] = args.quality_model
|
|
38
|
+
if args.optimizer_model:
|
|
39
|
+
cfg["optimizer_model_path"] = args.optimizer_model
|
|
40
|
+
saved = save_user_config(cfg)
|
|
41
|
+
print("PromptForge home:", paths["home"])
|
|
42
|
+
print("Models dir: ", paths["models"])
|
|
43
|
+
print("Config: ", saved)
|
|
44
|
+
return 0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _cmd_doctor(_: argparse.Namespace) -> int:
|
|
48
|
+
from promptforge.hub import doctor_report
|
|
49
|
+
|
|
50
|
+
report = doctor_report()
|
|
51
|
+
print("PromptForge doctor")
|
|
52
|
+
print("─" * 40)
|
|
53
|
+
for key, value in report.items():
|
|
54
|
+
print(f"{key:24s}: {value}")
|
|
55
|
+
|
|
56
|
+
ok = True
|
|
57
|
+
if not report["quality_model"]:
|
|
58
|
+
print("\nMissing quality model → run: promptforge download --quality")
|
|
59
|
+
ok = False
|
|
60
|
+
if not report["optimizer_model"]:
|
|
61
|
+
print("Missing optimizer model → run: promptforge download --optimizer")
|
|
62
|
+
ok = False
|
|
63
|
+
if report["prefer_gpu"] and not report["cuda_available"]:
|
|
64
|
+
print("\nWARNING: prefer_gpu=true but CUDA is unavailable (will use CPU).")
|
|
65
|
+
return 0 if ok else 1
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _cmd_download(args: argparse.Namespace) -> int:
|
|
69
|
+
from promptforge.hub import (
|
|
70
|
+
DEFAULT_OPTIMIZER_REPO,
|
|
71
|
+
DEFAULT_QUALITY_REPO,
|
|
72
|
+
download_defaults,
|
|
73
|
+
download_optimizer_model,
|
|
74
|
+
download_quality_model,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
quality_repo = args.quality_repo or DEFAULT_QUALITY_REPO
|
|
78
|
+
optimizer_repo = args.optimizer_repo or DEFAULT_OPTIMIZER_REPO
|
|
79
|
+
|
|
80
|
+
if args.quality and not args.optimizer:
|
|
81
|
+
path = download_quality_model(repo_id=quality_repo, token=args.token)
|
|
82
|
+
print("Downloaded quality →", path)
|
|
83
|
+
return 0
|
|
84
|
+
if args.optimizer and not args.quality:
|
|
85
|
+
path = download_optimizer_model(repo_id=optimizer_repo, token=args.token)
|
|
86
|
+
print("Downloaded optimizer →", path)
|
|
87
|
+
return 0
|
|
88
|
+
|
|
89
|
+
paths = download_defaults(
|
|
90
|
+
quality_repo=quality_repo,
|
|
91
|
+
optimizer_repo=optimizer_repo,
|
|
92
|
+
token=args.token,
|
|
93
|
+
update_config=not args.no_config,
|
|
94
|
+
)
|
|
95
|
+
print("Downloaded quality →", paths["quality"])
|
|
96
|
+
print("Downloaded optimizer →", paths["optimizer"])
|
|
97
|
+
if not args.no_config:
|
|
98
|
+
print("Updated ~/.promptforge/config.yaml")
|
|
99
|
+
return 0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _cmd_analyze(args: argparse.Namespace) -> int:
|
|
103
|
+
from promptforge import PromptForge
|
|
104
|
+
|
|
105
|
+
prompt = _read_prompt(args.prompt, args.file)
|
|
106
|
+
pf = PromptForge(
|
|
107
|
+
quality_model_path=args.model,
|
|
108
|
+
prefer_gpu=not args.cpu,
|
|
109
|
+
require_quality=True,
|
|
110
|
+
)
|
|
111
|
+
result = pf.analyze(prompt)
|
|
112
|
+
if args.json:
|
|
113
|
+
print(json.dumps(result, indent=2))
|
|
114
|
+
return 0
|
|
115
|
+
|
|
116
|
+
print(f"Prompt Quality: {result['quality_score']}/100\n")
|
|
117
|
+
print("Dimensions:")
|
|
118
|
+
for key, value in result["dimensions"].items():
|
|
119
|
+
print(f" {key:20s}: {value}")
|
|
120
|
+
if result["issues"]:
|
|
121
|
+
print("\nProblems:")
|
|
122
|
+
for issue in result["issues"]:
|
|
123
|
+
print(f" ✗ {issue}")
|
|
124
|
+
if result["missing_information"]:
|
|
125
|
+
print("\nMissing information:")
|
|
126
|
+
for item in result["missing_information"]:
|
|
127
|
+
print(f" - {item}")
|
|
128
|
+
print("\nRun:\n promptforge optimize \"...\"")
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _cmd_optimize(args: argparse.Namespace) -> int:
|
|
133
|
+
from promptforge import PromptForge
|
|
134
|
+
|
|
135
|
+
prompt = _read_prompt(args.prompt, args.file)
|
|
136
|
+
pf = PromptForge(
|
|
137
|
+
quality_model_path=args.quality_model,
|
|
138
|
+
optimizer_model_path=args.model,
|
|
139
|
+
prefer_gpu=not args.cpu,
|
|
140
|
+
require_optimizer=True,
|
|
141
|
+
)
|
|
142
|
+
analysis = None
|
|
143
|
+
if pf.scorer is not None and not args.no_analysis:
|
|
144
|
+
analysis = pf.analyze(prompt)
|
|
145
|
+
|
|
146
|
+
result = pf.optimize(
|
|
147
|
+
prompt,
|
|
148
|
+
analysis=analysis,
|
|
149
|
+
task_type=args.task_type,
|
|
150
|
+
use_scorer_analysis=False,
|
|
151
|
+
)
|
|
152
|
+
if args.json:
|
|
153
|
+
print(json.dumps(result, indent=2))
|
|
154
|
+
return 0
|
|
155
|
+
|
|
156
|
+
print("Optimized Prompt")
|
|
157
|
+
print("─" * 32)
|
|
158
|
+
print(result["optimized_prompt"])
|
|
159
|
+
return 0
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _cmd_run(args: argparse.Namespace) -> int:
|
|
163
|
+
from promptforge import PromptForge
|
|
164
|
+
|
|
165
|
+
prompt = _read_prompt(args.prompt, args.file)
|
|
166
|
+
pf = PromptForge(
|
|
167
|
+
quality_model_path=args.quality_model,
|
|
168
|
+
optimizer_model_path=args.optimizer_model,
|
|
169
|
+
prefer_gpu=not args.cpu,
|
|
170
|
+
require_quality=True,
|
|
171
|
+
require_optimizer=True,
|
|
172
|
+
)
|
|
173
|
+
result = pf.run(
|
|
174
|
+
prompt,
|
|
175
|
+
task_type=args.task_type,
|
|
176
|
+
rescore_optimized=not args.no_rescore,
|
|
177
|
+
)
|
|
178
|
+
if args.output:
|
|
179
|
+
pf.export_result(result, args.output)
|
|
180
|
+
|
|
181
|
+
if args.json:
|
|
182
|
+
print(json.dumps(result, indent=2))
|
|
183
|
+
return 0
|
|
184
|
+
|
|
185
|
+
before = result["before"]["quality_score"]
|
|
186
|
+
after = result["after"].get("quality_score", "n/a")
|
|
187
|
+
print(f"Prompt Quality: {before} -> {after} (delta {result['delta'].get('quality_score', 'n/a')})")
|
|
188
|
+
if result.get("used_fallback"):
|
|
189
|
+
model_val = result.get("model_validation") or {}
|
|
190
|
+
issues = model_val.get("issues") or []
|
|
191
|
+
print(
|
|
192
|
+
"\nNote: Model output failed validation; showing intent-preserving fallback."
|
|
193
|
+
)
|
|
194
|
+
if issues:
|
|
195
|
+
print(f"Rejection reasons: {', '.join(issues)}")
|
|
196
|
+
if model_val.get("instruction_preservation") is not None:
|
|
197
|
+
print(
|
|
198
|
+
f"Intent preservation: {model_val['instruction_preservation']:.2%} "
|
|
199
|
+
f"(min 8%)"
|
|
200
|
+
)
|
|
201
|
+
if model_val.get("repetitive"):
|
|
202
|
+
print("Detected repetitive output.")
|
|
203
|
+
raw = (result.get("raw_output") or "").strip()
|
|
204
|
+
if raw:
|
|
205
|
+
print("\nModel output (rejected):")
|
|
206
|
+
print("-" * 32)
|
|
207
|
+
preview = raw if len(raw) <= 600 else raw[:600] + "..."
|
|
208
|
+
print(preview)
|
|
209
|
+
print(
|
|
210
|
+
"\nFor real model rewrites, retrain: "
|
|
211
|
+
"python scripts/train_optimizer.py --require-gpu --fast --regenerate"
|
|
212
|
+
)
|
|
213
|
+
elif not result.get("trustworthy_improvement"):
|
|
214
|
+
print("\nWarning: Score delta may not reflect a trustworthy improvement.")
|
|
215
|
+
if result["before"].get("issues"):
|
|
216
|
+
print("\nProblems:")
|
|
217
|
+
for issue in result["before"]["issues"]:
|
|
218
|
+
print(f" - {issue}")
|
|
219
|
+
print("\nChanges:")
|
|
220
|
+
for change in result.get("changes", []):
|
|
221
|
+
print(f" + {change}")
|
|
222
|
+
print("\nOptimized Prompt")
|
|
223
|
+
print("-" * 32)
|
|
224
|
+
print(result["optimized_prompt"])
|
|
225
|
+
return 0
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _cmd_eval(args: argparse.Namespace) -> int:
|
|
229
|
+
from promptforge import PromptForge
|
|
230
|
+
from promptforge.evaluation import (
|
|
231
|
+
run_downstream_proxy_eval,
|
|
232
|
+
run_pipeline_evaluation,
|
|
233
|
+
save_eval_report,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
pf = PromptForge(
|
|
237
|
+
quality_model_path=args.quality_model,
|
|
238
|
+
optimizer_model_path=args.optimizer_model,
|
|
239
|
+
prefer_gpu=not args.cpu,
|
|
240
|
+
require_quality=True,
|
|
241
|
+
require_optimizer=True,
|
|
242
|
+
)
|
|
243
|
+
report = run_pipeline_evaluation(pf, task_type=args.task_type)
|
|
244
|
+
downstream = run_downstream_proxy_eval(pf, task_type=args.task_type)
|
|
245
|
+
payload = {"pipeline": report, "downstream_proxy": downstream}
|
|
246
|
+
|
|
247
|
+
out = args.output or "outputs/phase3_eval/pipeline_report.json"
|
|
248
|
+
save_eval_report(payload, out)
|
|
249
|
+
print(json.dumps(payload["pipeline"]["summary"], indent=2))
|
|
250
|
+
print("\nDownstream proxy:")
|
|
251
|
+
print(json.dumps(payload["downstream_proxy"]["summary"], indent=2))
|
|
252
|
+
print(f"\nWrote {out}")
|
|
253
|
+
return 0
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _cmd_space(args: argparse.Namespace) -> int:
|
|
257
|
+
import importlib.util
|
|
258
|
+
from pathlib import Path
|
|
259
|
+
|
|
260
|
+
from promptforge.local_paths import resolve_model_path
|
|
261
|
+
|
|
262
|
+
app_path = Path(__file__).resolve().parents[2] / "demo" / "app.py"
|
|
263
|
+
if not app_path.exists():
|
|
264
|
+
app_path = Path.cwd() / "demo" / "app.py"
|
|
265
|
+
spec = importlib.util.spec_from_file_location("promptforge_demo_app", app_path)
|
|
266
|
+
if spec is None or spec.loader is None:
|
|
267
|
+
raise FileNotFoundError(f"Could not load Gradio app at {app_path}")
|
|
268
|
+
mod = importlib.util.module_from_spec(spec)
|
|
269
|
+
spec.loader.exec_module(mod)
|
|
270
|
+
|
|
271
|
+
quality = resolve_model_path(args.quality_model, kind="quality")
|
|
272
|
+
optimizer = resolve_model_path(args.optimizer_model, kind="optimizer")
|
|
273
|
+
if not quality or not optimizer:
|
|
274
|
+
raise SystemExit("Models missing. Run: promptforge download")
|
|
275
|
+
|
|
276
|
+
demo = mod.build_demo(
|
|
277
|
+
quality_model_path=quality,
|
|
278
|
+
optimizer_model_path=optimizer,
|
|
279
|
+
prefer_gpu=not args.cpu,
|
|
280
|
+
)
|
|
281
|
+
demo.launch(server_name=args.host, server_port=args.port, share=args.share)
|
|
282
|
+
return 0
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _cmd_train_quality(args: argparse.Namespace) -> int:
|
|
286
|
+
from promptforge.config import QualityScorerConfig
|
|
287
|
+
from promptforge.training.train_quality import train_quality_scorer
|
|
288
|
+
|
|
289
|
+
config = (
|
|
290
|
+
QualityScorerConfig.from_yaml(args.config)
|
|
291
|
+
if args.config
|
|
292
|
+
else QualityScorerConfig()
|
|
293
|
+
)
|
|
294
|
+
if args.num_examples is not None:
|
|
295
|
+
config.num_examples = args.num_examples
|
|
296
|
+
if args.output_dir:
|
|
297
|
+
config.output_dir = args.output_dir
|
|
298
|
+
if args.final_model_dir:
|
|
299
|
+
config.final_model_dir = args.final_model_dir
|
|
300
|
+
if args.dataset_path:
|
|
301
|
+
config.dataset_path = args.dataset_path
|
|
302
|
+
if args.epochs is not None:
|
|
303
|
+
config.num_train_epochs = args.epochs
|
|
304
|
+
if args.batch_size is not None:
|
|
305
|
+
config.per_device_train_batch_size = args.batch_size
|
|
306
|
+
if args.cpu:
|
|
307
|
+
config.prefer_gpu = False
|
|
308
|
+
config.use_fp16 = False
|
|
309
|
+
config.use_bf16 = False
|
|
310
|
+
|
|
311
|
+
train_quality_scorer(config, regenerate_dataset=args.regenerate)
|
|
312
|
+
return 0
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _cmd_train_optimizer(args: argparse.Namespace) -> int:
|
|
316
|
+
from promptforge.config import OptimizerConfig
|
|
317
|
+
from promptforge.training.train_optimizer import train_optimizer
|
|
318
|
+
|
|
319
|
+
config = (
|
|
320
|
+
OptimizerConfig.from_yaml(args.config)
|
|
321
|
+
if args.config
|
|
322
|
+
else OptimizerConfig()
|
|
323
|
+
)
|
|
324
|
+
if args.num_examples is not None:
|
|
325
|
+
config.num_examples = args.num_examples
|
|
326
|
+
if args.output_dir:
|
|
327
|
+
config.output_dir = args.output_dir
|
|
328
|
+
if args.final_model_dir:
|
|
329
|
+
config.final_model_dir = args.final_model_dir
|
|
330
|
+
if args.dataset_path:
|
|
331
|
+
config.dataset_path = args.dataset_path
|
|
332
|
+
if args.epochs is not None:
|
|
333
|
+
config.num_train_epochs = args.epochs
|
|
334
|
+
if args.batch_size is not None:
|
|
335
|
+
config.per_device_train_batch_size = args.batch_size
|
|
336
|
+
if args.base_model:
|
|
337
|
+
config.base_model_name = args.base_model
|
|
338
|
+
if args.cpu:
|
|
339
|
+
config.prefer_gpu = False
|
|
340
|
+
config.use_fp16 = False
|
|
341
|
+
config.use_bf16 = False
|
|
342
|
+
if args.load_in_4bit:
|
|
343
|
+
config.load_in_4bit = True
|
|
344
|
+
|
|
345
|
+
train_optimizer(config, regenerate_dataset=args.regenerate)
|
|
346
|
+
return 0
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
350
|
+
parser = argparse.ArgumentParser(
|
|
351
|
+
prog="promptforge",
|
|
352
|
+
description="PromptForge — local prompt quality scoring & optimization",
|
|
353
|
+
)
|
|
354
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
355
|
+
|
|
356
|
+
ver = sub.add_parser("version", help="Print package version")
|
|
357
|
+
ver.set_defaults(func=_cmd_version)
|
|
358
|
+
|
|
359
|
+
init = sub.add_parser("init", help="Create ~/.promptforge config + models dirs")
|
|
360
|
+
init.add_argument("--quality-model", type=str, default=None)
|
|
361
|
+
init.add_argument("--optimizer-model", type=str, default=None)
|
|
362
|
+
init.add_argument("--cpu", action="store_true")
|
|
363
|
+
init.set_defaults(func=_cmd_init)
|
|
364
|
+
|
|
365
|
+
doctor = sub.add_parser("doctor", help="Check local install, GPU, and model paths")
|
|
366
|
+
doctor.set_defaults(func=_cmd_doctor)
|
|
367
|
+
|
|
368
|
+
dl = sub.add_parser("download", help="Download models from Hugging Face Hub")
|
|
369
|
+
dl.add_argument("--quality", action="store_true", help="Download quality model only")
|
|
370
|
+
dl.add_argument("--optimizer", action="store_true", help="Download optimizer only")
|
|
371
|
+
dl.add_argument("--quality-repo", type=str, default=None)
|
|
372
|
+
dl.add_argument("--optimizer-repo", type=str, default=None)
|
|
373
|
+
dl.add_argument("--token", type=str, default=None)
|
|
374
|
+
dl.add_argument("--no-config", action="store_true")
|
|
375
|
+
dl.set_defaults(func=_cmd_download)
|
|
376
|
+
|
|
377
|
+
analyze = sub.add_parser("analyze", help="Score a prompt")
|
|
378
|
+
analyze.add_argument("prompt", nargs="?", default=None)
|
|
379
|
+
analyze.add_argument("--file", type=str, default=None)
|
|
380
|
+
analyze.add_argument("--model", type=str, default=None)
|
|
381
|
+
analyze.add_argument("--json", action="store_true")
|
|
382
|
+
analyze.add_argument("--cpu", action="store_true")
|
|
383
|
+
analyze.set_defaults(func=_cmd_analyze)
|
|
384
|
+
|
|
385
|
+
optimize = sub.add_parser("optimize", help="Optimize a prompt")
|
|
386
|
+
optimize.add_argument("prompt", nargs="?", default=None)
|
|
387
|
+
optimize.add_argument("--file", type=str, default=None)
|
|
388
|
+
optimize.add_argument("--model", type=str, default=None)
|
|
389
|
+
optimize.add_argument("--quality-model", type=str, default=None)
|
|
390
|
+
optimize.add_argument("--task-type", type=str, default="general")
|
|
391
|
+
optimize.add_argument("--no-analysis", action="store_true")
|
|
392
|
+
optimize.add_argument("--json", action="store_true")
|
|
393
|
+
optimize.add_argument("--cpu", action="store_true")
|
|
394
|
+
optimize.set_defaults(func=_cmd_optimize)
|
|
395
|
+
|
|
396
|
+
run = sub.add_parser("run", help="Analyze + optimize + compare")
|
|
397
|
+
run.add_argument("prompt", nargs="?", default=None)
|
|
398
|
+
run.add_argument("--file", type=str, default=None)
|
|
399
|
+
run.add_argument("--quality-model", type=str, default=None)
|
|
400
|
+
run.add_argument("--optimizer-model", type=str, default=None)
|
|
401
|
+
run.add_argument("--task-type", type=str, default="general")
|
|
402
|
+
run.add_argument("--no-rescore", action="store_true")
|
|
403
|
+
run.add_argument("--output", type=str, default=None)
|
|
404
|
+
run.add_argument("--json", action="store_true")
|
|
405
|
+
run.add_argument("--cpu", action="store_true")
|
|
406
|
+
run.set_defaults(func=_cmd_run)
|
|
407
|
+
|
|
408
|
+
ev = sub.add_parser("eval", help="Run evaluation reports")
|
|
409
|
+
ev.add_argument("--quality-model", type=str, default=None)
|
|
410
|
+
ev.add_argument("--optimizer-model", type=str, default=None)
|
|
411
|
+
ev.add_argument("--task-type", type=str, default="general")
|
|
412
|
+
ev.add_argument("--output", type=str, default="outputs/phase3_eval/pipeline_report.json")
|
|
413
|
+
ev.add_argument("--cpu", action="store_true")
|
|
414
|
+
ev.set_defaults(func=_cmd_eval)
|
|
415
|
+
|
|
416
|
+
space = sub.add_parser("space", help="Launch Gradio demo")
|
|
417
|
+
space.add_argument("--quality-model", type=str, default=None)
|
|
418
|
+
space.add_argument("--optimizer-model", type=str, default=None)
|
|
419
|
+
space.add_argument("--host", type=str, default="0.0.0.0")
|
|
420
|
+
space.add_argument("--port", type=int, default=7860)
|
|
421
|
+
space.add_argument("--share", action="store_true")
|
|
422
|
+
space.add_argument("--cpu", action="store_true")
|
|
423
|
+
space.set_defaults(func=_cmd_space)
|
|
424
|
+
|
|
425
|
+
train_q = sub.add_parser("train-quality", help="Train Phase-1 quality scorer")
|
|
426
|
+
train_q.add_argument("--config", type=str, default="configs/quality_scorer.yaml")
|
|
427
|
+
train_q.add_argument("--num-examples", type=int, default=None)
|
|
428
|
+
train_q.add_argument("--epochs", type=int, default=None)
|
|
429
|
+
train_q.add_argument("--batch-size", type=int, default=None)
|
|
430
|
+
train_q.add_argument("--output-dir", type=str, default=None)
|
|
431
|
+
train_q.add_argument("--final-model-dir", type=str, default=None)
|
|
432
|
+
train_q.add_argument("--dataset-path", type=str, default=None)
|
|
433
|
+
train_q.add_argument("--regenerate", action="store_true")
|
|
434
|
+
train_q.add_argument("--cpu", action="store_true")
|
|
435
|
+
train_q.set_defaults(func=_cmd_train_quality)
|
|
436
|
+
|
|
437
|
+
train_o = sub.add_parser("train-optimizer", help="Train Phase-2 LoRA optimizer")
|
|
438
|
+
train_o.add_argument("--config", type=str, default="configs/optimizer.yaml")
|
|
439
|
+
train_o.add_argument("--num-examples", type=int, default=None)
|
|
440
|
+
train_o.add_argument("--epochs", type=int, default=None)
|
|
441
|
+
train_o.add_argument("--batch-size", type=int, default=None)
|
|
442
|
+
train_o.add_argument("--base-model", type=str, default=None)
|
|
443
|
+
train_o.add_argument("--output-dir", type=str, default=None)
|
|
444
|
+
train_o.add_argument("--final-model-dir", type=str, default=None)
|
|
445
|
+
train_o.add_argument("--dataset-path", type=str, default=None)
|
|
446
|
+
train_o.add_argument("--regenerate", action="store_true")
|
|
447
|
+
train_o.add_argument("--load-in-4bit", action="store_true")
|
|
448
|
+
train_o.add_argument("--cpu", action="store_true")
|
|
449
|
+
train_o.set_defaults(func=_cmd_train_optimizer)
|
|
450
|
+
|
|
451
|
+
return parser
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def main(argv: list[str] | None = None) -> int:
|
|
455
|
+
parser = build_parser()
|
|
456
|
+
args = parser.parse_args(argv)
|
|
457
|
+
return int(args.func(args))
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
if __name__ == "__main__":
|
|
461
|
+
sys.exit(main())
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def diff_dimensions(
|
|
7
|
+
before: dict[str, float],
|
|
8
|
+
after: dict[str, float],
|
|
9
|
+
) -> dict[str, float]:
|
|
10
|
+
keys = sorted(set(before) | set(after))
|
|
11
|
+
return {
|
|
12
|
+
key: round(float(after.get(key, 0.0) - before.get(key, 0.0)), 2)
|
|
13
|
+
for key in keys
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def infer_changes(
|
|
18
|
+
original: str,
|
|
19
|
+
optimized: str,
|
|
20
|
+
before: dict[str, Any] | None = None,
|
|
21
|
+
after: dict[str, Any] | None = None,
|
|
22
|
+
) -> list[str]:
|
|
23
|
+
"""Heuristic change summary for before/after UI."""
|
|
24
|
+
changes: list[str] = []
|
|
25
|
+
original_l = original.lower()
|
|
26
|
+
optimized_l = optimized.lower()
|
|
27
|
+
|
|
28
|
+
markers = [
|
|
29
|
+
("audience", ["for ", "targeting", "audience", "developers", "users"]),
|
|
30
|
+
("objective", ["build", "create", "write", "implement", "produce"]),
|
|
31
|
+
("constraints", ["requirements", "constraints", "must", "use "]),
|
|
32
|
+
("output format", ["return", "output", "json", "markdown", "numbered"]),
|
|
33
|
+
("technical requirements", ["python", "react", "fastapi", "typescript", "postgresql"]),
|
|
34
|
+
("structure", ["1.", "2.", "-", "requirements:"]),
|
|
35
|
+
("context", ["this is for", "university", "production", "startup", "prototype"]),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
for label, needles in markers:
|
|
39
|
+
if any(n in optimized_l for n in needles) and not any(n in original_l for n in needles):
|
|
40
|
+
changes.append(f"Added {label}")
|
|
41
|
+
|
|
42
|
+
if len(optimized.split()) > len(original.split()) * 1.8:
|
|
43
|
+
changes.append("Expanded specificity")
|
|
44
|
+
|
|
45
|
+
if before and after:
|
|
46
|
+
b_dims = before.get("dimensions", {})
|
|
47
|
+
a_dims = after.get("dimensions", {})
|
|
48
|
+
for key, delta in diff_dimensions(b_dims, a_dims).items():
|
|
49
|
+
if delta >= 15:
|
|
50
|
+
changes.append(f"Improved {key} (+{delta})")
|
|
51
|
+
|
|
52
|
+
# Deduplicate while preserving order
|
|
53
|
+
seen: set[str] = set()
|
|
54
|
+
unique: list[str] = []
|
|
55
|
+
for item in changes:
|
|
56
|
+
if item not in seen:
|
|
57
|
+
seen.add(item)
|
|
58
|
+
unique.append(item)
|
|
59
|
+
return unique or ["Rewrote prompt for clarity and completeness"]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def build_comparison_payload(
|
|
63
|
+
original_prompt: str,
|
|
64
|
+
optimized_prompt: str,
|
|
65
|
+
before_analysis: dict[str, Any],
|
|
66
|
+
after_analysis: dict[str, Any] | None = None,
|
|
67
|
+
task_type: str = "general",
|
|
68
|
+
) -> dict[str, Any]:
|
|
69
|
+
after_analysis = after_analysis or {}
|
|
70
|
+
before_score = float(before_analysis.get("quality_score", 0.0))
|
|
71
|
+
after_score = float(after_analysis.get("quality_score", before_score))
|
|
72
|
+
before_dims = before_analysis.get("dimensions", {})
|
|
73
|
+
after_dims = after_analysis.get("dimensions", {})
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
"original_prompt": original_prompt,
|
|
77
|
+
"optimized_prompt": optimized_prompt,
|
|
78
|
+
"task_type": task_type,
|
|
79
|
+
"before": {
|
|
80
|
+
"quality_score": before_score,
|
|
81
|
+
"dimensions": before_dims,
|
|
82
|
+
"issues": before_analysis.get("issues", []),
|
|
83
|
+
"missing_information": before_analysis.get("missing_information", []),
|
|
84
|
+
},
|
|
85
|
+
"after": {
|
|
86
|
+
"quality_score": after_score,
|
|
87
|
+
"dimensions": after_dims,
|
|
88
|
+
"issues": after_analysis.get("issues", []),
|
|
89
|
+
"missing_information": after_analysis.get("missing_information", []),
|
|
90
|
+
},
|
|
91
|
+
"delta": {
|
|
92
|
+
"quality_score": round(after_score - before_score, 2),
|
|
93
|
+
"dimensions": diff_dimensions(before_dims, after_dims),
|
|
94
|
+
},
|
|
95
|
+
"changes": infer_changes(
|
|
96
|
+
original_prompt,
|
|
97
|
+
optimized_prompt,
|
|
98
|
+
before_analysis,
|
|
99
|
+
after_analysis,
|
|
100
|
+
),
|
|
101
|
+
}
|