Navigam commited on
Commit
78c259c
·
1 Parent(s): 97b9312
README.md CHANGED
@@ -100,12 +100,12 @@ See [`docs/lightning_hf_runbook.md`](docs/lightning_hf_runbook.md) for the short
100
  Evaluate all model stages through the same environment:
101
 
102
  ```powershell
103
- uv run python eval.py --policy scripted_weak --label baseline --output results/baseline_eval.jsonl
104
- uv run python eval.py --policy oracle --label oracle --output results/oracle_eval.jsonl
105
- uv run python plot_results.py --inputs results/baseline_eval.jsonl results/oracle_eval.jsonl --output-dir results
106
  ```
107
 
108
- For trained adapters on a GPU box, use `eval.py --policy hf --model <base_model> --adapter <adapter_path>`.
109
 
110
  ## OpenEnv validation
111
 
 
100
  Evaluate all model stages through the same environment:
101
 
102
  ```powershell
103
+ uv run python eval.py --policy scripted_weak --label baseline
104
+ uv run python eval.py --policy oracle --label oracle
105
+ uv run python plot_results.py --inputs results/runs --output-dir results
106
  ```
107
 
108
+ For trained adapters on a GPU box, use `eval.py --policy hf --model <base_model> --adapter <adapter_path>`. If `--output` is omitted, eval files are saved under `results/runs/<model-adapter-label>/` with a `metadata.json` summary.
109
 
110
  ## OpenEnv validation
111
 
eval.py CHANGED
@@ -1,8 +1,8 @@
1
  """Evaluate CORP-ENV policies through the real OpenEnv environment.
2
 
3
  Examples:
4
- uv run python eval.py --policy scripted_weak --output results/baseline_eval.jsonl
5
- uv run python eval.py --policy oracle --output results/oracle_eval.jsonl
6
  uv run python eval.py --policy openai --model Qwen/Qwen2.5-7B-Instruct
7
  uv run python eval.py --policy hf --model outputs/sft_adapter --adapter outputs/grpo_adapter
8
  """
@@ -12,6 +12,7 @@ from __future__ import annotations
12
  import argparse
13
  import json
14
  import os
 
15
  import sys
16
  from pathlib import Path
17
  from typing import Any, Dict, List, Optional
@@ -241,6 +242,60 @@ def summarize(rows: List[Dict[str, Any]], label: str) -> None:
241
  )
242
 
243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  def main() -> None:
245
  parser = argparse.ArgumentParser(description="Evaluate CORP-ENV model stages.")
246
  parser.add_argument("--policy", choices=["scripted_weak", "oracle", "openai", "hf"], default="scripted_weak")
@@ -251,7 +306,12 @@ def main() -> None:
251
  parser.add_argument("--episodes", type=int, default=1)
252
  parser.add_argument("--max-steps", type=int, default=30)
253
  parser.add_argument("--max-new-tokens", type=int, default=1536)
254
- parser.add_argument("--output", default="results/eval.jsonl")
 
 
 
 
 
255
  args = parser.parse_args()
256
 
257
  os.environ.setdefault("CORP_STUB_WORKERS", "1")
@@ -278,14 +338,17 @@ def main() -> None:
278
  row = run_model_episode(task_id=task_id, policy=policy, max_steps=max_steps)
279
  row["episode_index"] = ep
280
  row["model_stage"] = args.label or args.policy
 
281
  row["model"] = args.model
282
  row["adapter"] = args.adapter
283
  rows.append(row)
284
 
285
- out = Path(args.output)
286
  write_jsonl(out, rows)
 
287
  summarize(rows, args.label or args.policy)
288
  print(f"\nWrote {out}")
 
289
 
290
 
291
  if __name__ == "__main__":
 
1
  """Evaluate CORP-ENV policies through the real OpenEnv environment.
2
 
3
  Examples:
4
+ uv run python eval.py --policy scripted_weak --label baseline
5
+ uv run python eval.py --policy oracle --label oracle
6
  uv run python eval.py --policy openai --model Qwen/Qwen2.5-7B-Instruct
7
  uv run python eval.py --policy hf --model outputs/sft_adapter --adapter outputs/grpo_adapter
8
  """
 
12
  import argparse
13
  import json
14
  import os
15
+ import re
16
  import sys
17
  from pathlib import Path
18
  from typing import Any, Dict, List, Optional
 
242
  )
243
 
244
 
245
+ def slugify(value: str, fallback: str = "unknown") -> str:
246
+ value = (value or "").strip()
247
+ if not value:
248
+ return fallback
249
+ value = value.replace("\\", "/").rstrip("/")
250
+ value = value.split("/")[-1] if "/" in value else value
251
+ value = re.sub(r"[^A-Za-z0-9._-]+", "-", value)
252
+ value = value.strip("-._")
253
+ return value or fallback
254
+
255
+
256
+ def default_output_path(args: argparse.Namespace) -> Path:
257
+ stage = slugify(args.label or args.policy, "eval")
258
+ model_slug = slugify(args.model, args.policy)
259
+ adapter_slug = slugify(args.adapter, "no-adapter")
260
+ if args.adapter:
261
+ run_slug = f"{model_slug}__{adapter_slug}__{stage}"
262
+ else:
263
+ run_slug = f"{model_slug}__{stage}"
264
+ return Path(args.results_root) / "runs" / run_slug / f"{stage}_eval.jsonl"
265
+
266
+
267
+ def write_run_metadata(path: Path, args: argparse.Namespace, rows: List[Dict[str, Any]]) -> None:
268
+ by_task: Dict[str, List[Dict[str, Any]]] = {}
269
+ for row in rows:
270
+ by_task.setdefault(row["task_id"], []).append(row)
271
+ summary = {
272
+ "model_stage": args.label or args.policy,
273
+ "policy": args.policy,
274
+ "model": args.model,
275
+ "adapter": args.adapter,
276
+ "tasks": [t.strip() for t in args.tasks.split(",") if t.strip()],
277
+ "episodes": args.episodes,
278
+ "max_steps": args.max_steps,
279
+ "max_new_tokens": args.max_new_tokens,
280
+ "eval_file": str(path),
281
+ "metrics_by_task": {},
282
+ }
283
+ for task_id, task_rows in by_task.items():
284
+ summary["metrics_by_task"][task_id] = {
285
+ "avg_terminal_reward": round(
286
+ sum(r["terminal_reward"] for r in task_rows) / len(task_rows), 6
287
+ ),
288
+ "avg_verifier_pass_rate": round(
289
+ sum(r["verifier_pass_rate"] for r in task_rows) / len(task_rows), 6
290
+ ),
291
+ "success_rate": round(
292
+ sum(1 for r in task_rows if r["success"]) / len(task_rows), 6
293
+ ),
294
+ }
295
+ metadata_path = path.with_name("metadata.json")
296
+ metadata_path.write_text(json.dumps(summary, indent=2, ensure_ascii=False), encoding="utf-8")
297
+
298
+
299
  def main() -> None:
300
  parser = argparse.ArgumentParser(description="Evaluate CORP-ENV model stages.")
301
  parser.add_argument("--policy", choices=["scripted_weak", "oracle", "openai", "hf"], default="scripted_weak")
 
306
  parser.add_argument("--episodes", type=int, default=1)
307
  parser.add_argument("--max-steps", type=int, default=30)
308
  parser.add_argument("--max-new-tokens", type=int, default=1536)
309
+ parser.add_argument("--results-root", default="results", help="Root for auto-organized eval output.")
310
+ parser.add_argument(
311
+ "--output",
312
+ default="",
313
+ help="Explicit JSONL path. If omitted, writes under results/runs/<model-adapter-label>/.",
314
+ )
315
  args = parser.parse_args()
316
 
317
  os.environ.setdefault("CORP_STUB_WORKERS", "1")
 
338
  row = run_model_episode(task_id=task_id, policy=policy, max_steps=max_steps)
339
  row["episode_index"] = ep
340
  row["model_stage"] = args.label or args.policy
341
+ row["policy"] = args.policy
342
  row["model"] = args.model
343
  row["adapter"] = args.adapter
344
  rows.append(row)
345
 
346
+ out = Path(args.output) if args.output else default_output_path(args)
347
  write_jsonl(out, rows)
348
+ write_run_metadata(out, args, rows)
349
  summarize(rows, args.label or args.policy)
350
  print(f"\nWrote {out}")
351
+ print(f"Wrote {out.with_name('metadata.json')}")
352
 
353
 
354
  if __name__ == "__main__":
plot_results.py CHANGED
@@ -1,4 +1,4 @@
1
- """Create hackathon result plots from CORP-ENV eval JSONL files."""
2
 
3
  from __future__ import annotations
4
 
@@ -9,15 +9,39 @@ from pathlib import Path
9
  from typing import Any, Dict, Iterable, List
10
 
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  def read_rows(paths: Iterable[str]) -> List[Dict[str, Any]]:
13
  rows: List[Dict[str, Any]] = []
14
- for path_s in paths:
15
- path = Path(path_s)
16
  with path.open("r", encoding="utf-8") as f:
17
  for line in f:
18
  if line.strip():
19
  row = json.loads(line)
20
  row.setdefault("model_stage", path.stem.replace("_eval", ""))
 
 
21
  steps = max(float(row.get("steps", 0) or 0), 1.0)
22
  row["invalid_action_rate"] = float(row.get("invalid_action_count", 0) or 0) / steps
23
  rows.append(row)
@@ -95,7 +119,7 @@ def plot_reward_curve(rows: List[Dict[str, Any]], output: Path) -> None:
95
 
96
  def main() -> None:
97
  parser = argparse.ArgumentParser(description="Plot CORP-ENV eval results.")
98
- parser.add_argument("--inputs", nargs="+", required=True, help="Eval JSONL files.")
99
  parser.add_argument("--output-dir", default="results")
100
  args = parser.parse_args()
101
 
 
1
+ """Create hackathon result plots from CORP-ENV eval JSONL files or run folders."""
2
 
3
  from __future__ import annotations
4
 
 
9
  from typing import Any, Dict, Iterable, List
10
 
11
 
12
+ def expand_inputs(inputs: Iterable[str]) -> List[Path]:
13
+ paths: List[Path] = []
14
+ for raw in inputs:
15
+ path = Path(raw)
16
+ if path.is_dir():
17
+ paths.extend(sorted(path.rglob("*_eval.jsonl")))
18
+ paths.extend(sorted(path.rglob("eval.jsonl")))
19
+ elif path.exists():
20
+ paths.append(path)
21
+ else:
22
+ matches = sorted(Path().glob(raw))
23
+ paths.extend([m for m in matches if m.is_file()])
24
+ # Preserve order but remove duplicates.
25
+ seen = set()
26
+ out: List[Path] = []
27
+ for path in paths:
28
+ key = str(path.resolve())
29
+ if key not in seen:
30
+ seen.add(key)
31
+ out.append(path)
32
+ return out
33
+
34
+
35
  def read_rows(paths: Iterable[str]) -> List[Dict[str, Any]]:
36
  rows: List[Dict[str, Any]] = []
37
+ for path in expand_inputs(paths):
 
38
  with path.open("r", encoding="utf-8") as f:
39
  for line in f:
40
  if line.strip():
41
  row = json.loads(line)
42
  row.setdefault("model_stage", path.stem.replace("_eval", ""))
43
+ if "run_id" not in row:
44
+ row["run_id"] = path.parent.name
45
  steps = max(float(row.get("steps", 0) or 0), 1.0)
46
  row["invalid_action_rate"] = float(row.get("invalid_action_count", 0) or 0) / steps
47
  rows.append(row)
 
119
 
120
  def main() -> None:
121
  parser = argparse.ArgumentParser(description="Plot CORP-ENV eval results.")
122
+ parser.add_argument("--inputs", nargs="+", required=True, help="Eval JSONL files, folders, or glob patterns.")
123
  parser.add_argument("--output-dir", default="results")
124
  args = parser.parse_args()
125
 
results/invalid_action_rate.png CHANGED

Git LFS Details

  • SHA256: 03d21c81f8a40e1907831bc64539f7de5ad694fe35674f4fefcd9164bb4bb32d
  • Pointer size: 130 Bytes
  • Size of remote file: 54.9 kB

Git LFS Details

  • SHA256: 1ee2e6de45d10a797d32b08ce00d30382b3b664d639f7a25c5279f912b2d7e0d
  • Pointer size: 130 Bytes
  • Size of remote file: 54.2 kB
results/model_comparison.png CHANGED

Git LFS Details

  • SHA256: 67cbe74d2105fe08af4fb611fb3592418e9bc378a033be27e043df5ffddec472
  • Pointer size: 130 Bytes
  • Size of remote file: 53.6 kB

Git LFS Details

  • SHA256: 362f759890867432df05a7995a0e0eaa2f18369b4e71f7bd476d2eb55dc7b21c
  • Pointer size: 130 Bytes
  • Size of remote file: 52.8 kB
results/reward_curve.png CHANGED

Git LFS Details

  • SHA256: 6127942095fe2a01ba86e0687d0a8b52ad32463aaba0c0635092f67035f0c7fa
  • Pointer size: 130 Bytes
  • Size of remote file: 86.1 kB

Git LFS Details

  • SHA256: c980cbae417a6504471e124c444ffed8eec828a7a89dc71c2e103bb5a4dd3eeb
  • Pointer size: 131 Bytes
  • Size of remote file: 145 kB
results/sft_eval.jsonl ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {"task_id": "e1_launch_readiness", "policy_kind": "model", "steps": 3, "total_reward": 1.39, "terminal_reward": 0.91, "reward_trace": [0.29000000000000004, 0.19, 0.9099999999999999], "verifier_pass_rate": 1.0, "passed_checks": ["qa_report_present", "final_rec_valid", "no_missed_milestones"], "failed_checks": [], "milestones_total": 2, "milestones_complete": 2, "milestones_missed": 0, "invalid_action_count": 0, "env_error_count": 0, "errors": [], "final_swd_version": 4, "success": true, "episode_index": 0, "model_stage": "sft", "model": "Qwen/Qwen2.5-7B-Instruct", "adapter": "Navigam/corp-gym-sft-adapter"}
2
+ {"task_id": "m1_budget_reallocation", "policy_kind": "model", "steps": 8, "total_reward": 1.815, "terminal_reward": 0.685, "reward_trace": [0.09000000000000001, 0.29000000000000004, 0.19, 0.19, -0.01, 0.39, -0.01, 0.685], "verifier_pass_rate": 0.833333, "passed_checks": ["required_agents_consulted", "conflict_logged", "conflict_resolved", "budget_constraint_acknowledged", "reasoning_documented"], "failed_checks": ["phased_plan"], "milestones_total": 3, "milestones_complete": 2, "milestones_missed": 0, "invalid_action_count": 2, "env_error_count": 2, "errors": ["invalid_action: Expecting value: line 1 column 58 (char 57)", "invalid_action: Expecting value: line 1 column 58 (char 57)"], "final_swd_version": 7, "success": false, "episode_index": 0, "model_stage": "sft", "model": "Qwen/Qwen2.5-7B-Instruct", "adapter": "Navigam/corp-gym-sft-adapter"}
3
+ {"task_id": "e1_launch_readiness", "policy_kind": "model", "steps": 3, "total_reward": 1.39, "terminal_reward": 0.91, "reward_trace": [0.29000000000000004, 0.19, 0.9099999999999999], "verifier_pass_rate": 1.0, "passed_checks": ["qa_report_present", "final_rec_valid", "no_missed_milestones"], "failed_checks": [], "milestones_total": 2, "milestones_complete": 2, "milestones_missed": 0, "invalid_action_count": 0, "env_error_count": 0, "errors": [], "final_swd_version": 4, "success": true, "episode_index": 1, "model_stage": "sft", "model": "Qwen/Qwen2.5-7B-Instruct", "adapter": "Navigam/corp-gym-sft-adapter"}
4
+ {"task_id": "m1_budget_reallocation", "policy_kind": "model", "steps": 5, "total_reward": 1.766667, "terminal_reward": 0.806667, "reward_trace": [0.09000000000000001, 0.29000000000000004, 0.19, 0.39, 0.8066666666666666], "verifier_pass_rate": 0.666667, "passed_checks": ["required_agents_consulted", "conflict_logged", "conflict_resolved", "phased_plan"], "failed_checks": ["budget_constraint_acknowledged", "reasoning_documented"], "milestones_total": 3, "milestones_complete": 3, "milestones_missed": 0, "invalid_action_count": 0, "env_error_count": 0, "errors": [], "final_swd_version": 6, "success": false, "episode_index": 1, "model_stage": "sft", "model": "Qwen/Qwen2.5-7B-Instruct", "adapter": "Navigam/corp-gym-sft-adapter"}
5
+ {"task_id": "e1_launch_readiness", "policy_kind": "model", "steps": 3, "total_reward": 1.39, "terminal_reward": 0.91, "reward_trace": [0.29000000000000004, 0.19, 0.9099999999999999], "verifier_pass_rate": 1.0, "passed_checks": ["qa_report_present", "final_rec_valid", "no_missed_milestones"], "failed_checks": [], "milestones_total": 2, "milestones_complete": 2, "milestones_missed": 0, "invalid_action_count": 0, "env_error_count": 0, "errors": [], "final_swd_version": 4, "success": true, "episode_index": 2, "model_stage": "sft", "model": "Qwen/Qwen2.5-7B-Instruct", "adapter": "Navigam/corp-gym-sft-adapter"}
6
+ {"task_id": "m1_budget_reallocation", "policy_kind": "model", "steps": 6, "total_reward": 1.776667, "terminal_reward": 0.626667, "reward_trace": [0.09000000000000001, 0.29000000000000004, 0.19, 0.19, 0.39, 0.6266666666666666], "verifier_pass_rate": 0.666667, "passed_checks": ["required_agents_consulted", "conflict_logged", "conflict_resolved", "reasoning_documented"], "failed_checks": ["phased_plan", "budget_constraint_acknowledged"], "milestones_total": 3, "milestones_complete": 2, "milestones_missed": 0, "invalid_action_count": 0, "env_error_count": 0, "errors": [], "final_swd_version": 7, "success": false, "episode_index": 2, "model_stage": "sft", "model": "Qwen/Qwen2.5-7B-Instruct", "adapter": "Navigam/corp-gym-sft-adapter"}
results/success_by_task.png CHANGED

Git LFS Details

  • SHA256: 1e9eafbd58b3c1cf65ca43d5d8cfc191592c5e4570ea680e44885a5109901ec2
  • Pointer size: 130 Bytes
  • Size of remote file: 52.3 kB

Git LFS Details

  • SHA256: 42f02290c7517c43dd544f0607f202ca38028cfa78669e856808144e326ac5f1
  • Pointer size: 130 Bytes
  • Size of remote file: 50 kB
training/train_sft.py CHANGED
@@ -57,9 +57,10 @@ def main() -> None:
57
  args = parser.parse_args()
58
 
59
  try:
 
 
60
  from datasets import Dataset
61
  from trl import SFTConfig, SFTTrainer
62
- from unsloth import FastLanguageModel
63
  except ImportError as exc:
64
  raise SystemExit(
65
  "SFT training requires datasets, trl, and unsloth. On Lightning AI, install with:\n"
@@ -72,6 +73,11 @@ def main() -> None:
72
  dtype=None,
73
  load_in_4bit=True,
74
  )
 
 
 
 
 
75
  model = FastLanguageModel.get_peft_model(
76
  model,
77
  r=32,
@@ -95,7 +101,7 @@ def main() -> None:
95
  config = SFTConfig(
96
  output_dir=args.output,
97
  dataset_text_field="text",
98
- max_seq_length=args.max_seq_length,
99
  per_device_train_batch_size=args.batch_size,
100
  gradient_accumulation_steps=args.grad_accum,
101
  num_train_epochs=args.epochs,
 
57
  args = parser.parse_args()
58
 
59
  try:
60
+ # Unsloth must load before trl/transformers/peft for full optimizations.
61
+ from unsloth import FastLanguageModel
62
  from datasets import Dataset
63
  from trl import SFTConfig, SFTTrainer
 
64
  except ImportError as exc:
65
  raise SystemExit(
66
  "SFT training requires datasets, trl, and unsloth. On Lightning AI, install with:\n"
 
73
  dtype=None,
74
  load_in_4bit=True,
75
  )
76
+ if getattr(tokenizer, "pad_token", None) is None and getattr(
77
+ tokenizer, "eos_token", None
78
+ ) is not None:
79
+ tokenizer.pad_token = tokenizer.eos_token
80
+
81
  model = FastLanguageModel.get_peft_model(
82
  model,
83
  r=32,
 
101
  config = SFTConfig(
102
  output_dir=args.output,
103
  dataset_text_field="text",
104
+ max_length=args.max_seq_length,
105
  per_device_train_batch_size=args.batch_size,
106
  gradient_accumulation_steps=args.grad_accum,
107
  num_train_epochs=args.epochs,