synth-ai 0.2.13.dev2__py3-none-any.whl → 0.2.14__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.
Potentially problematic release.
This version of synth-ai might be problematic. Click here for more details.
- examples/multi_step/configs/README_verilog_rl.md +77 -0
- examples/multi_step/configs/VERILOG_REWARDS.md +90 -0
- examples/multi_step/configs/VERILOG_RL_CHECKLIST.md +183 -0
- examples/multi_step/configs/crafter_eval_synth_qwen4b.toml +35 -0
- examples/multi_step/configs/crafter_eval_text_only_groq_qwen32b.toml +36 -0
- examples/multi_step/configs/crafter_rl_stepwise_hosted_judge.toml +5 -4
- examples/multi_step/configs/crafter_synth_backend.md +40 -0
- examples/multi_step/configs/verilog_eval_groq_qwen32b.toml +31 -0
- examples/multi_step/configs/verilog_eval_synth_qwen8b.toml +33 -0
- examples/multi_step/configs/verilog_rl_lora.toml +190 -0
- examples/multi_step/judges/crafter_backend_judge.py +220 -0
- examples/multi_step/judges/verilog_backend_judge.py +234 -0
- examples/multi_step/readme.md +48 -0
- examples/multi_step/verilog_rl_lora.md +218 -0
- examples/qwen_coder/configs/coder_lora_30b.toml +1 -1
- examples/sft/evaluate.py +2 -0
- examples/sft/generate_traces.py +2 -0
- examples/swe/task_app/grpo_swe_mini.py +1 -0
- examples/swe/task_app/hosted/rollout.py +2 -0
- examples/task_apps/IMAGE_ONLY_EVAL_QUICKSTART.md +258 -0
- examples/task_apps/crafter/CREATE_SFT_DATASET.md +273 -0
- examples/task_apps/crafter/EVAL_IMAGE_ONLY_RESULTS.md +152 -0
- examples/task_apps/crafter/FILTER_COMMAND_STATUS.md +174 -0
- examples/task_apps/crafter/FILTER_COMMAND_SUCCESS.md +268 -0
- examples/task_apps/crafter/QUERY_EXAMPLES.md +203 -0
- examples/task_apps/crafter/README_IMAGE_ONLY_EVAL.md +316 -0
- examples/task_apps/crafter/eval_image_only_gpt4o.toml +28 -0
- examples/task_apps/crafter/eval_text_only_groq_llama.toml +36 -0
- examples/task_apps/crafter/filter_sft_dataset.toml +16 -0
- examples/task_apps/crafter/task_app/__init__.py +3 -0
- examples/task_apps/crafter/task_app/grpo_crafter.py +306 -8
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/environment.py +10 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/policy.py +16 -3
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/react_agent.py +17 -2
- examples/task_apps/crafter/task_app/synth_envs_hosted/inference/openai_client.py +25 -3
- examples/task_apps/crafter/task_app/synth_envs_hosted/policy_routes.py +52 -1
- examples/task_apps/crafter/task_app/synth_envs_hosted/rollout.py +111 -13
- examples/task_apps/crafter/task_app/synth_envs_hosted/utils.py +156 -0
- examples/task_apps/enron/filter_sft.toml +5 -0
- examples/task_apps/enron/tests/__init__.py +2 -0
- examples/task_apps/enron/tests/integration/__init__.py +2 -0
- examples/task_apps/enron/tests/integration/test_enron_eval.py +2 -0
- examples/task_apps/enron/tests/unit/__init__.py +2 -0
- examples/task_apps/pokemon_red/EVAL_IMAGE_ONLY_COMPLETE.md +283 -0
- examples/task_apps/pokemon_red/EVAL_IMAGE_ONLY_STATUS.md +155 -0
- examples/task_apps/pokemon_red/README_IMAGE_ONLY_EVAL.md +415 -0
- examples/task_apps/pokemon_red/eval_image_only_gpt4o.toml +29 -0
- examples/task_apps/pokemon_red/pallet_town_rl_config.toml +2 -0
- examples/task_apps/pokemon_red/task_app.py +199 -6
- examples/task_apps/pokemon_red/test_pallet_town_rewards.py +2 -0
- examples/task_apps/sokoban/filter_sft.toml +5 -0
- examples/task_apps/sokoban/tests/__init__.py +2 -0
- examples/task_apps/sokoban/tests/integration/__init__.py +2 -0
- examples/task_apps/sokoban/tests/unit/__init__.py +2 -0
- examples/task_apps/verilog/eval_groq_qwen32b.toml +8 -4
- examples/task_apps/verilog/filter_sft.toml +5 -0
- examples/task_apps/verilog/task_app/grpo_verilog.py +258 -23
- examples/task_apps/verilog/tests/__init__.py +2 -0
- examples/task_apps/verilog/tests/integration/__init__.py +2 -0
- examples/task_apps/verilog/tests/integration/test_verilog_eval.py +2 -0
- examples/task_apps/verilog/tests/unit/__init__.py +2 -0
- examples/warming_up_to_rl/groq_test.py +2 -0
- examples/warming_up_to_rl/run_local_rollout.py +2 -0
- examples/warming_up_to_rl/run_local_rollout_modal.py +2 -0
- examples/warming_up_to_rl/run_local_rollout_parallel.py +2 -0
- examples/warming_up_to_rl/run_local_rollout_traced.py +2 -0
- examples/warming_up_to_rl/run_rollout_remote.py +2 -0
- synth_ai/api/models/supported.py +1 -0
- synth_ai/cli/__init__.py +46 -13
- synth_ai/cli/_modal_wrapper.py +3 -2
- synth_ai/cli/recent.py +1 -1
- synth_ai/cli/status.py +1 -1
- synth_ai/cli/task_apps.py +354 -143
- synth_ai/cli/traces.py +1 -1
- synth_ai/cli/tui.py +57 -0
- synth_ai/cli/turso.py +1 -1
- synth_ai/cli/watch.py +1 -1
- synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +1 -1
- synth_ai/environments/examples/crafter_classic/environment.py +1 -1
- synth_ai/environments/examples/verilog/engine.py +76 -10
- synth_ai/judge_schemas.py +8 -8
- synth_ai/task/__init__.py +11 -1
- synth_ai/task/apps/__init__.py +1 -0
- synth_ai/task/config.py +257 -0
- synth_ai/task/contracts.py +15 -2
- synth_ai/task/rubrics/__init__.py +3 -0
- synth_ai/task/rubrics/loaders.py +22 -3
- synth_ai/task/rubrics/scoring.py +3 -0
- synth_ai/task/trace_correlation_helpers.py +315 -0
- synth_ai/task/validators.py +144 -0
- synth_ai/tracing_v3/abstractions.py +3 -3
- synth_ai/tracing_v3/llm_call_record_helpers.py +5 -5
- synth_ai/tracing_v3/session_tracer.py +16 -6
- synth_ai/tracing_v3/storage/base.py +29 -29
- synth_ai/tracing_v3/storage/config.py +3 -3
- synth_ai/tracing_v3/turso/daemon.py +8 -7
- synth_ai/tracing_v3/turso/native_manager.py +63 -40
- synth_ai/tracing_v3/utils.py +3 -3
- synth_ai/tui/__init__.py +5 -0
- synth_ai/tui/__main__.py +13 -0
- synth_ai/tui/cli/__init__.py +1 -0
- synth_ai/tui/cli/query_experiments.py +164 -0
- synth_ai/tui/cli/query_experiments_v3.py +164 -0
- synth_ai/tui/dashboard.py +906 -0
- {synth_ai-0.2.13.dev2.dist-info → synth_ai-0.2.14.dist-info}/METADATA +1 -1
- {synth_ai-0.2.13.dev2.dist-info → synth_ai-0.2.14.dist-info}/RECORD +110 -71
- {synth_ai-0.2.13.dev2.dist-info → synth_ai-0.2.14.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.13.dev2.dist-info → synth_ai-0.2.14.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.13.dev2.dist-info → synth_ai-0.2.14.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.2.13.dev2.dist-info → synth_ai-0.2.14.dist-info}/top_level.txt +0 -0
|
@@ -34,6 +34,7 @@ from synth_ai.task.contracts import (
|
|
|
34
34
|
from synth_ai.task.datasets import TaskDatasetRegistry, TaskDatasetSpec
|
|
35
35
|
from synth_ai.task.rubrics import load_rubric
|
|
36
36
|
from synth_ai.task.server import ProxyConfig, RubricBundle, TaskAppConfig
|
|
37
|
+
from synth_ai.task.validators import normalize_inference_url
|
|
37
38
|
from synth_ai.task.tracing_utils import (
|
|
38
39
|
build_tracer_factory,
|
|
39
40
|
resolve_sft_output_dir,
|
|
@@ -45,7 +46,36 @@ from synth_ai.tracing_v3.session_tracer import SessionTracer
|
|
|
45
46
|
logger = logging.getLogger(__name__)
|
|
46
47
|
|
|
47
48
|
_HERE = Path(__file__).resolve()
|
|
48
|
-
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _resolve_repo_root() -> Path:
|
|
52
|
+
"""Find synth-ai repo root, checking env var and parent traversal."""
|
|
53
|
+
candidates: list[Path] = []
|
|
54
|
+
env_root = os.getenv("SYNTH_AI_REPO_ROOT")
|
|
55
|
+
if env_root:
|
|
56
|
+
candidates.append(Path(env_root).expanduser())
|
|
57
|
+
|
|
58
|
+
# Try Modal mount point
|
|
59
|
+
candidates.append(Path("/opt/synth_ai_repo"))
|
|
60
|
+
|
|
61
|
+
# Traverse up from current file
|
|
62
|
+
current = _HERE
|
|
63
|
+
for _ in range(6):
|
|
64
|
+
current = current.parent
|
|
65
|
+
candidates.append(current)
|
|
66
|
+
if (current / "synth_ai").is_dir() and (current / "examples").is_dir():
|
|
67
|
+
return current
|
|
68
|
+
|
|
69
|
+
# Return first existing candidate
|
|
70
|
+
for candidate in candidates:
|
|
71
|
+
if candidate.is_dir() and (candidate / "synth_ai").exists():
|
|
72
|
+
return candidate
|
|
73
|
+
|
|
74
|
+
# Fallback to current parent structure (may not work in Modal)
|
|
75
|
+
return _HERE.parent.parent.parent.parent
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
REPO_ROOT = _resolve_repo_root()
|
|
49
79
|
|
|
50
80
|
DATASET_SPEC = TaskDatasetSpec(
|
|
51
81
|
id="verilog_eval_v2",
|
|
@@ -161,23 +191,6 @@ def _base_task_info(dataset: VerilogDataset) -> TaskInfo:
|
|
|
161
191
|
)
|
|
162
192
|
|
|
163
193
|
|
|
164
|
-
def _normalize_inference_url(url: str | None) -> str:
|
|
165
|
-
candidate = (url or DEFAULT_INFERENCE_URL).strip()
|
|
166
|
-
if not candidate:
|
|
167
|
-
candidate = DEFAULT_INFERENCE_URL
|
|
168
|
-
if candidate.endswith("/v1/chat/completions"):
|
|
169
|
-
return candidate
|
|
170
|
-
if candidate.endswith("/chat/completions"):
|
|
171
|
-
return candidate
|
|
172
|
-
if candidate.endswith("/v1"):
|
|
173
|
-
return f"{candidate.rstrip('/')}/chat/completions"
|
|
174
|
-
if candidate.endswith("/v1/"):
|
|
175
|
-
return f"{candidate.rstrip('/')}/chat/completions"
|
|
176
|
-
if candidate.endswith("/chat"):
|
|
177
|
-
return f"{candidate.rstrip('/')}/completions"
|
|
178
|
-
if candidate.endswith("/chat/"):
|
|
179
|
-
return f"{candidate.rstrip('/')}/completions"
|
|
180
|
-
return f"{candidate.rstrip('/')}/v1/chat/completions"
|
|
181
194
|
|
|
182
195
|
|
|
183
196
|
def _format_file_previews(files: dict[str, str]) -> str:
|
|
@@ -336,7 +349,7 @@ class VerilogLLMAgent:
|
|
|
336
349
|
max_tokens: int,
|
|
337
350
|
) -> None:
|
|
338
351
|
self.instructions = instructions.strip()
|
|
339
|
-
self.inference_url =
|
|
352
|
+
self.inference_url = normalize_inference_url(inference_url, default=DEFAULT_INFERENCE_URL)
|
|
340
353
|
self.model = model or DEFAULT_MODEL
|
|
341
354
|
self.temperature = temperature
|
|
342
355
|
self.max_tokens = max_tokens
|
|
@@ -349,7 +362,16 @@ class VerilogLLMAgent:
|
|
|
349
362
|
if not api_key:
|
|
350
363
|
raise RuntimeError("GROQ_API_KEY is not configured for Verilog inference.")
|
|
351
364
|
self.headers["Authorization"] = f"Bearer {api_key.strip()}"
|
|
352
|
-
|
|
365
|
+
# If target is Synth backend (any deployment), use SYNTH_API_KEY
|
|
366
|
+
elif any(pattern in lowered for pattern in [
|
|
367
|
+
"synth-backend", "synth.run", "agent-learning",
|
|
368
|
+
"localhost:8000", "127.0.0.1:8000"
|
|
369
|
+
]):
|
|
370
|
+
api_key = os.getenv("SYNTH_API_KEY")
|
|
371
|
+
if not api_key:
|
|
372
|
+
raise RuntimeError("SYNTH_API_KEY is not configured for Verilog inference with Synth backend.")
|
|
373
|
+
self.headers["Authorization"] = f"Bearer {api_key.strip()}"
|
|
374
|
+
elif "openai" in lowered or "api.openai.com" in lowered:
|
|
353
375
|
api_key = os.getenv("OPENAI_API_KEY")
|
|
354
376
|
if not api_key:
|
|
355
377
|
raise RuntimeError("OPENAI_API_KEY is not configured for Verilog inference.")
|
|
@@ -574,6 +596,21 @@ async def rollout_executor(
|
|
|
574
596
|
total_reward = 0.0
|
|
575
597
|
final_observation: dict[str, Any] | None = None
|
|
576
598
|
truncated_due_to_limit = False
|
|
599
|
+
|
|
600
|
+
# Log episode start
|
|
601
|
+
problem_id = getattr(instance, "problem_id", "unknown")
|
|
602
|
+
logger.info("=" * 80)
|
|
603
|
+
logger.info(f"[EPISODE START] run_id={request.run_id}")
|
|
604
|
+
logger.info(f" Problem ID: {problem_id}")
|
|
605
|
+
logger.info(f" Policy: {policy_id}")
|
|
606
|
+
logger.info(f" Model: {policy_model}")
|
|
607
|
+
logger.info(f" Max steps: {max_steps}")
|
|
608
|
+
logger.info(f" Temperature: {temperature}")
|
|
609
|
+
logger.info(f" Max tokens: {max_tokens}")
|
|
610
|
+
if instructions:
|
|
611
|
+
instructions_preview = instructions[:150] + "..." if len(instructions) > 150 else instructions
|
|
612
|
+
logger.info(f" Instructions: {instructions_preview}")
|
|
613
|
+
logger.info("=" * 80)
|
|
577
614
|
code_dirty = False
|
|
578
615
|
last_compile_success = False
|
|
579
616
|
simulate_since_last_compile = False
|
|
@@ -648,7 +685,7 @@ async def rollout_executor(
|
|
|
648
685
|
and not code_dirty
|
|
649
686
|
)
|
|
650
687
|
if skip_env_step:
|
|
651
|
-
reward_last =
|
|
688
|
+
reward_last = 0.0 # No reward for blocked operations
|
|
652
689
|
total_reward += reward_last
|
|
653
690
|
current_observation = dict(current_observation)
|
|
654
691
|
current_observation["reward_last"] = reward_last
|
|
@@ -669,6 +706,23 @@ async def rollout_executor(
|
|
|
669
706
|
or current_observation.get("task_completed")
|
|
670
707
|
)
|
|
671
708
|
truncated_flag = bool(current_observation.get("truncated"))
|
|
709
|
+
|
|
710
|
+
# Log what the environment returned
|
|
711
|
+
print(f"\n{'='*80}")
|
|
712
|
+
print(f"[STEP {step_index}] TOOL CALL:")
|
|
713
|
+
print(f" Tool: {env_call.tool}")
|
|
714
|
+
print(f" Args: {env_call.args}")
|
|
715
|
+
print(f"\n[STEP {step_index}] ENVIRONMENT RESPONSE:")
|
|
716
|
+
print(f" Reward: {reward_last:.4f} (cumulative: {total_reward:.4f})")
|
|
717
|
+
print(f" Task completed: {step_observation.get('task_completed')}")
|
|
718
|
+
print(f" Done: {done_flag} | Truncated: {truncated_flag}")
|
|
719
|
+
if 'compile_status' in step_observation and step_observation.get('compile_status'):
|
|
720
|
+
print(f" Compile status:\n{step_observation.get('compile_status')}")
|
|
721
|
+
if 'simulate_status' in step_observation and step_observation.get('simulate_status'):
|
|
722
|
+
print(f" Simulate status:\n{step_observation.get('simulate_status')}")
|
|
723
|
+
if 'files' in step_observation:
|
|
724
|
+
print(f" Files: {list(step_observation.get('files', {}).keys())}")
|
|
725
|
+
print(f"{'='*80}\n")
|
|
672
726
|
|
|
673
727
|
executed_tool_name = str(primary_call["tool"])
|
|
674
728
|
normalized_executed_tool = executed_tool_name.strip().lower()
|
|
@@ -698,10 +752,40 @@ async def rollout_executor(
|
|
|
698
752
|
{"tool_name": call["tool"], "arguments": call["args"]}
|
|
699
753
|
for call in tool_calls
|
|
700
754
|
]
|
|
755
|
+
|
|
756
|
+
# Print tool calls for debugging
|
|
757
|
+
logger.info(f"[STEP {step_index}] Tool calls executed:")
|
|
758
|
+
for call in tool_calls:
|
|
759
|
+
tool_name = call["tool"]
|
|
760
|
+
args = call["args"]
|
|
761
|
+
# Truncate long arguments for readability
|
|
762
|
+
if "code" in args or "content" in args:
|
|
763
|
+
args_preview = {k: (v[:100] + "..." if isinstance(v, str) and len(v) > 100 else v)
|
|
764
|
+
for k, v in args.items()}
|
|
765
|
+
else:
|
|
766
|
+
args_preview = args
|
|
767
|
+
logger.info(f" └─ {tool_name}({args_preview})")
|
|
768
|
+
|
|
769
|
+
# Log reward details for debugging
|
|
770
|
+
logger.info(f"[STEP {step_index}] Reward details:")
|
|
771
|
+
logger.info(f" └─ reward_last: {reward_last:.4f}")
|
|
772
|
+
logger.info(f" └─ total_reward: {total_reward:.4f}")
|
|
773
|
+
logger.info(f" └─ skip_env_step: {skip_env_step}")
|
|
774
|
+
if not skip_env_step:
|
|
775
|
+
logger.info(f" └─ obs.task_completed: {current_observation.get('task_completed', False)}")
|
|
776
|
+
logger.info(f" └─ obs.compile_status: {current_observation.get('compile_status', 'N/A')}")
|
|
777
|
+
logger.info(f" └─ obs.simulate_status: {current_observation.get('simulate_status', 'N/A')}")
|
|
778
|
+
logger.info(f" └─ obs.terminated: {current_observation.get('terminated', False)}")
|
|
779
|
+
else:
|
|
780
|
+
logger.info(f" └─ (blocked operation - no env step)")
|
|
781
|
+
|
|
701
782
|
step_info = {
|
|
702
783
|
"assistant_message": assistant_text,
|
|
703
784
|
"model_response": raw_response,
|
|
704
785
|
"llm_request": request_payload,
|
|
786
|
+
"meta": {
|
|
787
|
+
"inference_url": policy_config.get("inference_url") or resolved_inference, # CRITICAL: Required by RL trainer for trace extraction (must have ?cid=...)
|
|
788
|
+
},
|
|
705
789
|
}
|
|
706
790
|
if override_info:
|
|
707
791
|
step_info["auto_override"] = override_info
|
|
@@ -756,6 +840,9 @@ async def rollout_executor(
|
|
|
756
840
|
"model_response": raw_response,
|
|
757
841
|
"llm_request": request_payload,
|
|
758
842
|
"error": error_text,
|
|
843
|
+
"meta": {
|
|
844
|
+
"inference_url": policy_config.get("inference_url") or resolved_inference, # CRITICAL: Required by RL trainer
|
|
845
|
+
},
|
|
759
846
|
}
|
|
760
847
|
steps.append(
|
|
761
848
|
RolloutStep(
|
|
@@ -797,6 +884,25 @@ async def rollout_executor(
|
|
|
797
884
|
},
|
|
798
885
|
)
|
|
799
886
|
|
|
887
|
+
# Extract inference_url from policy config (REQUIRED for RL trace correlation)
|
|
888
|
+
# The trainer injects this with ?cid=trace_xxxxx parameter for trace linking
|
|
889
|
+
final_inference_url = policy_config.get("inference_url")
|
|
890
|
+
if not isinstance(final_inference_url, str) or not final_inference_url.strip():
|
|
891
|
+
# Fallback to agent's inference_url if not in policy config
|
|
892
|
+
final_inference_url = agent.inference_url
|
|
893
|
+
logger.warning(
|
|
894
|
+
"VERILOG_ROLLOUT: inference_url not found in policy_config, using agent.inference_url run_id=%s url=%s",
|
|
895
|
+
request.run_id,
|
|
896
|
+
final_inference_url,
|
|
897
|
+
)
|
|
898
|
+
else:
|
|
899
|
+
logger.info(
|
|
900
|
+
"VERILOG_ROLLOUT: using inference_url from policy_config run_id=%s url=%s has_cid=%s",
|
|
901
|
+
request.run_id,
|
|
902
|
+
final_inference_url,
|
|
903
|
+
"?cid=" in final_inference_url,
|
|
904
|
+
)
|
|
905
|
+
|
|
800
906
|
trajectory = RolloutTrajectory(
|
|
801
907
|
env_id=str(env_id),
|
|
802
908
|
policy_id=str(policy_id),
|
|
@@ -810,11 +916,11 @@ async def rollout_executor(
|
|
|
810
916
|
"total_reward": final_total_reward,
|
|
811
917
|
"task_completed": bool(final_observation.get("task_completed")),
|
|
812
918
|
"policy_model": policy_model,
|
|
813
|
-
"inference_url":
|
|
919
|
+
"inference_url": final_inference_url,
|
|
814
920
|
},
|
|
815
921
|
},
|
|
816
922
|
length=len(steps),
|
|
817
|
-
inference_url=
|
|
923
|
+
inference_url=final_inference_url, # CRITICAL: Must contain ?cid=... for trace correlation
|
|
818
924
|
decision_samples=None,
|
|
819
925
|
)
|
|
820
926
|
|
|
@@ -836,6 +942,133 @@ async def rollout_executor(
|
|
|
836
942
|
}
|
|
837
943
|
}
|
|
838
944
|
|
|
945
|
+
# Build pipeline_metadata (required for RL training)
|
|
946
|
+
pipeline_metadata = {
|
|
947
|
+
"reward_score": final_total_reward,
|
|
948
|
+
"policy_id": policy_id,
|
|
949
|
+
"inference_url": final_inference_url, # CRITICAL: Must be at top level for RL trainer (expects ?cid=...)
|
|
950
|
+
"inference": {
|
|
951
|
+
"provider": "groq",
|
|
952
|
+
"model": policy_model,
|
|
953
|
+
"url": final_inference_url, # Use final_inference_url (has ?cid=...)
|
|
954
|
+
},
|
|
955
|
+
"env_name": env_id,
|
|
956
|
+
"task_id": getattr(instance, "problem_id", None),
|
|
957
|
+
"task_split": getattr(instance, "split", "val"),
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
# Log episode summary with reward breakdown
|
|
961
|
+
compile_status = final_observation.get("compile_status", "N/A")
|
|
962
|
+
simulate_status = final_observation.get("simulate_status", "N/A")
|
|
963
|
+
task_completed = bool(final_observation.get("task_completed", False))
|
|
964
|
+
|
|
965
|
+
logger.info("=" * 80)
|
|
966
|
+
logger.info(f"[EPISODE COMPLETE] run_id={request.run_id}")
|
|
967
|
+
logger.info(f" Steps taken: {len(steps)}")
|
|
968
|
+
logger.info(f" Total reward: {final_total_reward:.3f}")
|
|
969
|
+
logger.info(f" Task completed: {task_completed}")
|
|
970
|
+
logger.info(f" Compile status: {compile_status}")
|
|
971
|
+
logger.info(f" Simulate status: {simulate_status}")
|
|
972
|
+
logger.info(f" Done/Truncated: {final_done}/{final_truncated}")
|
|
973
|
+
logger.info(f" Problem ID: {getattr(instance, 'problem_id', 'N/A')}")
|
|
974
|
+
|
|
975
|
+
# DEBUG: Log each step's reward for RL debugging
|
|
976
|
+
print(f"\n[REWARD DEBUG] Step-by-step breakdown:")
|
|
977
|
+
for idx, step in enumerate(steps):
|
|
978
|
+
print(f" Step {idx}: reward={step.reward:.4f} tool_calls={[tc.get('tool_name') for tc in step.tool_calls]}")
|
|
979
|
+
print(f"[REWARD DEBUG] Final observation keys: {list(final_observation.keys())}")
|
|
980
|
+
print(f"[REWARD DEBUG] Final obs total_reward: {final_observation.get('total_reward')}")
|
|
981
|
+
print(f"[REWARD DEBUG] Metrics outcome_score: {metrics.outcome_score}")
|
|
982
|
+
print(f"[REWARD DEBUG] Metrics mean_return: {metrics.mean_return}")
|
|
983
|
+
|
|
984
|
+
# Reward breakdown for debugging
|
|
985
|
+
logger.info("\n[REWARD BREAKDOWN]")
|
|
986
|
+
compile_count = sum(1 for s in steps if any(tc.get("tool_name") == "compile" for tc in s.tool_calls))
|
|
987
|
+
simulate_count = sum(1 for s in steps if any(tc.get("tool_name") == "simulate" for tc in s.tool_calls))
|
|
988
|
+
submit_count = sum(1 for s in steps if any(tc.get("tool_name") == "submit" for tc in s.tool_calls))
|
|
989
|
+
write_count = sum(1 for s in steps if any(tc.get("tool_name") == "write_file" for tc in s.tool_calls))
|
|
990
|
+
|
|
991
|
+
logger.info(f" Tool usage: write_file={write_count}, compile={compile_count}, simulate={simulate_count}, submit={submit_count}")
|
|
992
|
+
|
|
993
|
+
# Show per-step rewards
|
|
994
|
+
step_rewards = [s.reward for s in steps]
|
|
995
|
+
nonzero_rewards = [r for r in step_rewards if r != 0.0]
|
|
996
|
+
logger.info(f" Step rewards: {step_rewards}")
|
|
997
|
+
if nonzero_rewards:
|
|
998
|
+
logger.info(f" Non-zero rewards: {nonzero_rewards}")
|
|
999
|
+
else:
|
|
1000
|
+
logger.info(f" ⚠️ ALL REWARDS ZERO! Possible reasons:")
|
|
1001
|
+
logger.info(f" - No successful compiles (compile reward = 0.01)")
|
|
1002
|
+
logger.info(f" - No successful simulations (simulate reward = 0.1)")
|
|
1003
|
+
logger.info(f" - No successful submits (submit reward = 1.0)")
|
|
1004
|
+
logger.info(f" - Check if task_completed={task_completed}")
|
|
1005
|
+
logger.info(f" - Check compile_status='{compile_status}'")
|
|
1006
|
+
logger.info(f" - Check simulate_status='{simulate_status}'")
|
|
1007
|
+
logger.info("=" * 80)
|
|
1008
|
+
|
|
1009
|
+
# Log for debugging RL training
|
|
1010
|
+
logger.info(
|
|
1011
|
+
"VERILOG_ROLLOUT: pipeline_metadata run_id=%s reward=%.3f inference_url=%s",
|
|
1012
|
+
request.run_id,
|
|
1013
|
+
final_total_reward,
|
|
1014
|
+
final_inference_url,
|
|
1015
|
+
)
|
|
1016
|
+
|
|
1017
|
+
# DEBUG: Log what we're returning to the RL trainer
|
|
1018
|
+
print(f"\n[RETURN DEBUG] Trajectory structure being returned:")
|
|
1019
|
+
print(f" trajectory.steps count: {len(steps)}")
|
|
1020
|
+
print(f" trajectory.final.reward: {trajectory.final.get('reward') if trajectory.final else 'None'}")
|
|
1021
|
+
print(f" trajectory.length: {trajectory.length}")
|
|
1022
|
+
print(f" metrics.outcome_score: {metrics.outcome_score}")
|
|
1023
|
+
print(f" metrics.mean_return: {metrics.mean_return}")
|
|
1024
|
+
print(f" metrics.episode_returns: {metrics.episode_returns}")
|
|
1025
|
+
print(f" pipeline_metadata.reward_score: {pipeline_metadata.get('reward_score')}")
|
|
1026
|
+
|
|
1027
|
+
# ASSERTIONS: Validate RL-required fields before returning
|
|
1028
|
+
# These catch structural issues early (before they reach the backend trainer)
|
|
1029
|
+
# Only enforce for RL mode, not EVAL mode
|
|
1030
|
+
is_rl_mode = hasattr(request, 'mode') and str(getattr(request, 'mode', '')).lower() == 'rl'
|
|
1031
|
+
|
|
1032
|
+
assert isinstance(pipeline_metadata, dict), (
|
|
1033
|
+
f"VERILOG_ROLLOUT_VALIDATION: pipeline_metadata must be dict, got {type(pipeline_metadata).__name__}"
|
|
1034
|
+
)
|
|
1035
|
+
assert "inference_url" in pipeline_metadata, (
|
|
1036
|
+
f"VERILOG_ROLLOUT_VALIDATION: pipeline_metadata missing 'inference_url' (REQUIRED for RL training)"
|
|
1037
|
+
)
|
|
1038
|
+
assert isinstance(pipeline_metadata["inference_url"], str), (
|
|
1039
|
+
f"VERILOG_ROLLOUT_VALIDATION: pipeline_metadata['inference_url'] must be string, got {type(pipeline_metadata['inference_url']).__name__}"
|
|
1040
|
+
)
|
|
1041
|
+
# Only require ?cid= for RL mode (not needed for EVAL)
|
|
1042
|
+
if is_rl_mode:
|
|
1043
|
+
assert "?cid=" in pipeline_metadata["inference_url"], (
|
|
1044
|
+
f"VERILOG_ROLLOUT_VALIDATION: pipeline_metadata['inference_url'] must contain '?cid=' for trace correlation in RL mode. "
|
|
1045
|
+
f"Got: {pipeline_metadata['inference_url'][:100]}"
|
|
1046
|
+
)
|
|
1047
|
+
|
|
1048
|
+
# Validate each step has meta.inference_url (backend expects this nested structure)
|
|
1049
|
+
for step_idx, step in enumerate(steps):
|
|
1050
|
+
step_dict = step if isinstance(step, dict) else (step.model_dump() if hasattr(step, "model_dump") else {})
|
|
1051
|
+
step_info = step_dict.get("info", {})
|
|
1052
|
+
assert isinstance(step_info, dict), (
|
|
1053
|
+
f"VERILOG_ROLLOUT_VALIDATION: step[{step_idx}].info must be dict, got {type(step_info).__name__}"
|
|
1054
|
+
)
|
|
1055
|
+
step_meta = step_info.get("meta", {})
|
|
1056
|
+
assert isinstance(step_meta, dict), (
|
|
1057
|
+
f"VERILOG_ROLLOUT_VALIDATION: step[{step_idx}].info.meta must be dict, got {type(step_meta).__name__}"
|
|
1058
|
+
)
|
|
1059
|
+
assert "inference_url" in step_meta, (
|
|
1060
|
+
f"VERILOG_ROLLOUT_VALIDATION: step[{step_idx}].info.meta missing 'inference_url' (REQUIRED for RL training)"
|
|
1061
|
+
)
|
|
1062
|
+
assert isinstance(step_meta["inference_url"], str), (
|
|
1063
|
+
f"VERILOG_ROLLOUT_VALIDATION: step[{step_idx}].info.meta['inference_url'] must be string, got {type(step_meta['inference_url']).__name__}"
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
logger.info(
|
|
1067
|
+
"VERILOG_ROLLOUT_VALIDATION: ✓ All RL-required fields present run_id=%s steps=%d",
|
|
1068
|
+
request.run_id,
|
|
1069
|
+
len(steps),
|
|
1070
|
+
)
|
|
1071
|
+
|
|
839
1072
|
return RolloutResponse(
|
|
840
1073
|
run_id=request.run_id,
|
|
841
1074
|
trajectories=[trajectory],
|
|
@@ -844,6 +1077,7 @@ async def rollout_executor(
|
|
|
844
1077
|
aborted=False,
|
|
845
1078
|
ops_executed=len(steps),
|
|
846
1079
|
trace=trace_payload,
|
|
1080
|
+
pipeline_metadata=pipeline_metadata,
|
|
847
1081
|
)
|
|
848
1082
|
|
|
849
1083
|
|
|
@@ -917,6 +1151,7 @@ register_task_app(
|
|
|
917
1151
|
"python-dotenv>=1.0.1",
|
|
918
1152
|
"datasets>=2.10.0",
|
|
919
1153
|
),
|
|
1154
|
+
apt_packages=("iverilog",), # Icarus Verilog compiler and simulator (provides iverilog and vvp)
|
|
920
1155
|
extra_local_dirs=(
|
|
921
1156
|
(str(REPO_ROOT), "/opt/synth_ai_repo"),
|
|
922
1157
|
(str(REPO_ROOT / "synth_ai"), "/opt/synth_ai_repo/synth_ai"),
|
|
@@ -47,8 +47,10 @@ async def run(args: argparse.Namespace) -> None:
|
|
|
47
47
|
|
|
48
48
|
inference_url = args.inference_url or f"{args.base_url.rstrip('/')}/proxy/groq"
|
|
49
49
|
|
|
50
|
+
from synth_ai.task.contracts import RolloutMode
|
|
50
51
|
request = RolloutRequest(
|
|
51
52
|
run_id=args.run_id,
|
|
53
|
+
mode=RolloutMode.EVAL,
|
|
52
54
|
env=RolloutEnvSpec(env_name="crafter", seed=args.seed, config={"seed": args.seed}),
|
|
53
55
|
policy=RolloutPolicySpec(
|
|
54
56
|
policy_name="groq-smoke",
|
|
@@ -42,8 +42,10 @@ def build_rollout_request(
|
|
|
42
42
|
trace_format=trace_format,
|
|
43
43
|
return_trace=return_trace,
|
|
44
44
|
)
|
|
45
|
+
from synth_ai.task.contracts import RolloutMode
|
|
45
46
|
return RolloutRequest(
|
|
46
47
|
run_id=run_id,
|
|
48
|
+
mode=RolloutMode.EVAL,
|
|
47
49
|
env=RolloutEnvSpec(env_name="crafter", seed=seed, config={}),
|
|
48
50
|
policy=RolloutPolicySpec(policy_name="crafter-react", config=policy_config),
|
|
49
51
|
ops=ops,
|
|
@@ -33,12 +33,14 @@ def build_rollout_request(
|
|
|
33
33
|
"Authorization": f"Bearer {api_key}",
|
|
34
34
|
},
|
|
35
35
|
}
|
|
36
|
+
from synth_ai.task.contracts import RolloutMode
|
|
36
37
|
return RolloutRequest(
|
|
37
38
|
run_id=run_id,
|
|
38
39
|
env=RolloutEnvSpec(env_name="crafter", seed=seed, config={}),
|
|
39
40
|
policy=RolloutPolicySpec(policy_name="crafter-react", config=policy_config),
|
|
40
41
|
ops=ops,
|
|
41
42
|
record=RolloutRecordConfig(trajectories=True),
|
|
43
|
+
mode=RolloutMode.EVAL,
|
|
42
44
|
on_done="reset",
|
|
43
45
|
safety=RolloutSafetyConfig(),
|
|
44
46
|
)
|
|
@@ -46,12 +46,14 @@ def build_rollout_request(
|
|
|
46
46
|
trace_format=trace_format,
|
|
47
47
|
return_trace=return_trace,
|
|
48
48
|
)
|
|
49
|
+
from synth_ai.task.contracts import RolloutMode
|
|
49
50
|
return RolloutRequest(
|
|
50
51
|
run_id=run_id,
|
|
51
52
|
env=RolloutEnvSpec(env_name="crafter", seed=seed, config={}),
|
|
52
53
|
policy=RolloutPolicySpec(policy_name="crafter-react", config=policy_config),
|
|
53
54
|
ops=ops,
|
|
54
55
|
record=record_cfg,
|
|
56
|
+
mode=RolloutMode.EVAL,
|
|
55
57
|
on_done="reset",
|
|
56
58
|
safety=RolloutSafetyConfig(),
|
|
57
59
|
)
|
|
@@ -53,12 +53,14 @@ def build_rollout_request(
|
|
|
53
53
|
trace_format=trace_format,
|
|
54
54
|
)
|
|
55
55
|
|
|
56
|
+
from synth_ai.task.contracts import RolloutMode
|
|
56
57
|
return RolloutRequest(
|
|
57
58
|
run_id=run_id,
|
|
58
59
|
env=RolloutEnvSpec(env_name="crafter", seed=seed, config={}),
|
|
59
60
|
policy=RolloutPolicySpec(policy_name="crafter-react", config=policy_config),
|
|
60
61
|
ops=ops,
|
|
61
62
|
record=record,
|
|
63
|
+
mode=RolloutMode.EVAL,
|
|
62
64
|
on_done="reset",
|
|
63
65
|
safety=RolloutSafetyConfig(),
|
|
64
66
|
)
|
|
@@ -60,12 +60,14 @@ def build_request(
|
|
|
60
60
|
for _ in range(max(llm_calls, 1)):
|
|
61
61
|
ops.extend(["agent", "env"])
|
|
62
62
|
|
|
63
|
+
from synth_ai.task.contracts import RolloutMode
|
|
63
64
|
return RolloutRequest(
|
|
64
65
|
run_id=run_id,
|
|
65
66
|
env=RolloutEnvSpec(env_name="crafter", seed=seed, config={}),
|
|
66
67
|
policy=RolloutPolicySpec(policy_name="crafter-react", config=policy_config),
|
|
67
68
|
ops=ops,
|
|
68
69
|
record=RolloutRecordConfig(trajectories=True),
|
|
70
|
+
mode=RolloutMode.EVAL,
|
|
69
71
|
on_done="reset",
|
|
70
72
|
safety=RolloutSafetyConfig(),
|
|
71
73
|
)
|
synth_ai/api/models/supported.py
CHANGED
synth_ai/cli/__init__.py
CHANGED
|
@@ -22,26 +22,26 @@ except Exception:
|
|
|
22
22
|
pass
|
|
23
23
|
|
|
24
24
|
try:
|
|
25
|
-
from ._typer_patch import patch_typer_make_metavar
|
|
25
|
+
from synth_ai.cli._typer_patch import patch_typer_make_metavar
|
|
26
26
|
|
|
27
27
|
patch_typer_make_metavar()
|
|
28
28
|
except Exception:
|
|
29
29
|
pass
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
from .root import cli # new canonical CLI entrypoint
|
|
32
|
+
from synth_ai.cli.root import cli # new canonical CLI entrypoint
|
|
33
33
|
|
|
34
34
|
# Register subcommands from this package onto the group
|
|
35
35
|
# Deprecated/legacy commands intentionally not registered: watch/experiments, balance, calc,
|
|
36
36
|
# man, recent, status, traces
|
|
37
37
|
try:
|
|
38
|
-
from . import demo as _demo
|
|
38
|
+
from synth_ai.cli import demo as _demo
|
|
39
39
|
|
|
40
40
|
_demo.register(cli)
|
|
41
41
|
except Exception:
|
|
42
42
|
pass
|
|
43
43
|
try:
|
|
44
|
-
from . import turso as _turso
|
|
44
|
+
from synth_ai.cli import turso as _turso
|
|
45
45
|
|
|
46
46
|
_turso.register(cli)
|
|
47
47
|
except Exception:
|
|
@@ -54,20 +54,53 @@ except Exception:
|
|
|
54
54
|
pass
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
cli.
|
|
57
|
+
# Import task_app_group conditionally
|
|
58
|
+
try:
|
|
59
|
+
from synth_ai.cli.task_apps import task_app_group
|
|
60
|
+
cli.add_command(task_app_group, name="task-app")
|
|
61
|
+
except Exception:
|
|
62
|
+
# Task app functionality not available
|
|
63
|
+
pass
|
|
60
64
|
|
|
61
65
|
|
|
62
66
|
try:
|
|
63
|
-
|
|
67
|
+
# Make task_apps import more robust to handle missing optional dependencies
|
|
68
|
+
import importlib
|
|
69
|
+
task_apps_module = importlib.import_module('synth_ai.cli.task_apps')
|
|
70
|
+
task_apps_module.register(cli)
|
|
71
|
+
except (ImportError, ModuleNotFoundError, TypeError, RuntimeError) as e:
|
|
72
|
+
# Task apps module not available (missing optional dependencies)
|
|
73
|
+
# This is expected - silently skip
|
|
74
|
+
pass
|
|
64
75
|
|
|
65
|
-
|
|
76
|
+
# Register TUI command - make import completely isolated
|
|
77
|
+
def _register_tui_command():
|
|
78
|
+
"""Register TUI command only when called, not during CLI startup."""
|
|
79
|
+
try:
|
|
80
|
+
# Import TUI only when the command is actually used
|
|
81
|
+
from synth_ai.cli.tui import register as tui_register
|
|
82
|
+
tui_register(cli)
|
|
83
|
+
except Exception:
|
|
84
|
+
# TUI not available - this is expected if dependencies are missing
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
# Add TUI command as a lazy-registered command
|
|
88
|
+
try:
|
|
89
|
+
# Try to import and register immediately for normal cases
|
|
90
|
+
from synth_ai.cli.tui import register as tui_register
|
|
91
|
+
tui_register(cli)
|
|
66
92
|
except Exception:
|
|
93
|
+
# If that fails, add a lazy registration that will only happen when called
|
|
94
|
+
# For now, just skip - the command won't be available but CLI won't crash
|
|
67
95
|
pass
|
|
68
96
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
cli.add_command(task_app_group.commands["
|
|
97
|
+
# Add task app commands if available
|
|
98
|
+
try:
|
|
99
|
+
if 'task_app_group' in locals() and hasattr(task_app_group, 'commands'):
|
|
100
|
+
cli.add_command(task_app_group.commands["serve"], name="serve")
|
|
101
|
+
cli.add_command(task_app_group.commands["deploy"], name="deploy")
|
|
102
|
+
cli.add_command(task_app_group.commands["modal-serve"], name="modal-serve")
|
|
103
|
+
except Exception:
|
|
104
|
+
# Task app commands not available
|
|
105
|
+
pass
|
|
73
106
|
# Top-level 'info' alias removed; use `synth-ai task-app info` instead
|
synth_ai/cli/_modal_wrapper.py
CHANGED
|
@@ -6,7 +6,7 @@ import sys
|
|
|
6
6
|
def main() -> int:
|
|
7
7
|
# Apply Typer compatibility patch before Modal CLI bootstraps Click/Typer internals.
|
|
8
8
|
try:
|
|
9
|
-
from ._typer_patch import patch_typer_make_metavar
|
|
9
|
+
from synth_ai.cli._typer_patch import patch_typer_make_metavar
|
|
10
10
|
|
|
11
11
|
patch_typer_make_metavar()
|
|
12
12
|
except Exception:
|
|
@@ -20,7 +20,8 @@ def main() -> int:
|
|
|
20
20
|
else:
|
|
21
21
|
sys.argv = ["modal"]
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
result = modal_main()
|
|
24
|
+
return result if result is not None else 0
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
if __name__ == "__main__":
|
synth_ai/cli/recent.py
CHANGED
synth_ai/cli/status.py
CHANGED