synth-ai 0.2.8.dev4__py3-none-any.whl → 0.2.23.dev3__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.
- examples/README.md +1 -0
- examples/__init__.py +16 -0
- examples/analyze_semantic_words.sh +17 -0
- examples/baseline/banking77_baseline.py +243 -0
- examples/baseline/banking77_pipeline_baseline.py +294 -0
- examples/baseline/crafter_baseline.py +407 -0
- examples/baseline/pokemon_red_baseline.py +326 -0
- examples/baseline/simple_baseline.py +56 -0
- examples/baseline/warming_up_to_rl_baseline.py +239 -0
- examples/blog_posts/gepa/README.md +355 -0
- examples/blog_posts/gepa/configs/banking77_gepa_local.toml +95 -0
- examples/blog_posts/gepa/configs/banking77_gepa_test.toml +80 -0
- examples/blog_posts/gepa/configs/banking77_mipro_local.toml +50 -0
- examples/blog_posts/gepa/configs/banking77_pipeline_gepa_local.toml +101 -0
- examples/blog_posts/gepa/configs/banking77_pipeline_gepa_test.toml +96 -0
- examples/blog_posts/gepa/configs/hotpotqa_gepa_local.toml +57 -0
- examples/blog_posts/gepa/configs/hotpotqa_gepa_qwen.toml +35 -0
- examples/blog_posts/gepa/configs/hotpotqa_mipro_local.toml +51 -0
- examples/blog_posts/gepa/configs/hover_gepa_local.toml +57 -0
- examples/blog_posts/gepa/configs/hover_gepa_qwen.toml +35 -0
- examples/blog_posts/gepa/configs/hover_mipro_local.toml +51 -0
- examples/blog_posts/gepa/configs/ifbench_gepa_local.toml +57 -0
- examples/blog_posts/gepa/configs/ifbench_gepa_qwen.toml +35 -0
- examples/blog_posts/gepa/configs/ifbench_mipro_local.toml +51 -0
- examples/blog_posts/gepa/configs/pupa_gepa_local.toml +58 -0
- examples/blog_posts/gepa/configs/pupa_mipro_local.toml +52 -0
- examples/blog_posts/gepa/deploy_banking77_task_app.sh +54 -0
- examples/blog_posts/gepa/gepa_baseline.py +204 -0
- examples/blog_posts/gepa/query_prompts_example.py +97 -0
- examples/blog_posts/gepa/run_gepa_banking77.sh +112 -0
- examples/blog_posts/gepa/run_gepa_banking77_pipeline.sh +163 -0
- examples/blog_posts/gepa/task_apps.py +105 -0
- examples/blog_posts/gepa/test_gepa_local.sh +67 -0
- examples/blog_posts/gepa/verify_banking77_setup.sh +123 -0
- examples/blog_posts/mipro/README.md +415 -0
- examples/blog_posts/mipro/configs/banking77_mipro_local.toml +91 -0
- examples/blog_posts/mipro/configs/banking77_mipro_test.toml +87 -0
- examples/blog_posts/mipro/configs/banking77_pipeline_mipro_gemini_flash_lite_local.toml +98 -0
- examples/blog_posts/mipro/configs/banking77_pipeline_mipro_gpt41mini_local.toml +96 -0
- examples/blog_posts/mipro/configs/banking77_pipeline_mipro_local.toml +94 -0
- examples/blog_posts/mipro/configs/banking77_pipeline_mipro_test.toml +170 -0
- examples/blog_posts/mipro/deploy_banking77_pipeline_task_app.sh +59 -0
- examples/blog_posts/mipro/deploy_banking77_task_app.sh +41 -0
- examples/blog_posts/mipro/multi_step.md +79 -0
- examples/blog_posts/mipro/run_mipro_banking77.sh +191 -0
- examples/blog_posts/mipro/run_mipro_banking77_pipeline.sh +171 -0
- examples/blog_posts/mipro/run_mipro_banking77_pipeline_gemini_flash_lite.sh +177 -0
- examples/blog_posts/mipro/run_mipro_banking77_pipeline_gpt41mini.sh +173 -0
- examples/blog_posts/mipro/verify_banking77_setup.sh +117 -0
- examples/blog_posts/pokemon_vl/README.md +98 -0
- examples/blog_posts/pokemon_vl/configs/eval_gpt5nano.toml +26 -0
- examples/blog_posts/pokemon_vl/configs/eval_qwen3_vl.toml +27 -0
- examples/blog_posts/pokemon_vl/configs/eval_rl_final.toml +24 -0
- examples/blog_posts/pokemon_vl/configs/filter_high_reward.toml +10 -0
- examples/blog_posts/pokemon_vl/configs/train_rl_from_sft.toml +43 -0
- examples/blog_posts/pokemon_vl/configs/train_sft_qwen4b_vl.toml +40 -0
- examples/blog_posts/pokemon_vl/extract_images.py +239 -0
- examples/blog_posts/pokemon_vl/pokemon_vl_baseline.py +326 -0
- examples/blog_posts/pokemon_vl/run_eval_extract_images.py +209 -0
- examples/blog_posts/pokemon_vl/run_qwen_eval_extract_images.py +212 -0
- examples/blog_posts/pokemon_vl/text_box_analysis.md +106 -0
- examples/blog_posts/warming_up_to_rl/ARCHITECTURE.md +195 -0
- examples/blog_posts/warming_up_to_rl/FINAL_TEST_RESULTS.md +127 -0
- examples/blog_posts/warming_up_to_rl/INFERENCE_SUCCESS.md +132 -0
- examples/blog_posts/warming_up_to_rl/README.md +158 -0
- examples/blog_posts/warming_up_to_rl/SMOKE_TESTING.md +164 -0
- examples/blog_posts/warming_up_to_rl/SMOKE_TEST_COMPLETE.md +253 -0
- examples/blog_posts/warming_up_to_rl/configs/eval_baseline_qwen32b_10x20.toml +25 -0
- examples/blog_posts/warming_up_to_rl/configs/eval_ft_qwen4b.toml +25 -0
- examples/blog_posts/warming_up_to_rl/configs/eval_ft_qwen4b_10x20.toml +26 -0
- examples/blog_posts/warming_up_to_rl/configs/eval_groq_qwen32b.toml +25 -0
- examples/blog_posts/warming_up_to_rl/configs/eval_openai_gpt_oss_120b.toml +29 -0
- examples/blog_posts/warming_up_to_rl/configs/filter_high_reward_dataset.toml +10 -0
- examples/blog_posts/warming_up_to_rl/configs/smoke_test.toml +75 -0
- examples/blog_posts/warming_up_to_rl/configs/train_rl_from_sft.toml +91 -0
- examples/blog_posts/warming_up_to_rl/configs/train_sft_qwen4b.toml +40 -0
- examples/blog_posts/warming_up_to_rl/warming_up_to_rl_baseline.py +187 -0
- examples/crafter_debug_render.py +186 -0
- examples/dev/qwen3_32b_qlora_4xh100.toml +45 -0
- examples/gepa/banking77_pipeline_gepa.toml +96 -0
- examples/gepa/multi_stage_gepa_example.toml +84 -0
- examples/gepa/run_gepa_banking77_pipeline.sh +157 -0
- examples/multi_step/SFT_README.md +147 -0
- examples/multi_step/configs/README_verilog_rl.md +77 -0
- examples/multi_step/configs/VERILOG_REWARDS.md +103 -0
- examples/multi_step/configs/VERILOG_RL_CHECKLIST.md +196 -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_outcome.toml +75 -0
- examples/multi_step/configs/crafter_rl_stepwise_hosted_judge.toml +145 -0
- examples/multi_step/configs/crafter_rl_stepwise_shaped.toml +84 -0
- examples/multi_step/configs/crafter_rl_stepwise_simple.toml +79 -0
- examples/multi_step/configs/crafter_rl_stepwise_simple_NEW_FORMAT.toml +105 -0
- examples/multi_step/configs/crafter_sft_qwen30b_lora.toml +62 -0
- 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 +147 -0
- examples/multi_step/convert_traces_to_sft.py +84 -0
- examples/multi_step/crafter_rl_lora.md +70 -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/run_sft_qwen30b.sh +45 -0
- examples/multi_step/sse_metrics_streaming_notes.md +357 -0
- examples/multi_step/task_app_config_notes.md +494 -0
- examples/multi_step/verilog_rl_lora.md +218 -0
- examples/qwen_coder/README.md +102 -0
- examples/qwen_coder/_shared.py +113 -0
- examples/qwen_coder/configs/coder_lora_30b.toml +60 -0
- examples/qwen_coder/configs/coder_lora_4b.toml +61 -0
- examples/qwen_coder/configs/coder_lora_small.toml +57 -0
- examples/qwen_coder/generate_dataset.py +98 -0
- examples/qwen_coder/infer_ft_smoke.py +65 -0
- examples/qwen_coder/infer_prod_proxy.py +73 -0
- examples/qwen_coder/infer_via_synth.py +87 -0
- examples/qwen_coder/scripts/infer_coder.sh +19 -0
- examples/qwen_coder/scripts/train_coder_30b.sh +22 -0
- examples/qwen_coder/sft_full_17b.py +103 -0
- examples/qwen_coder/sft_lora_30b.py +110 -0
- examples/qwen_coder/subset_jsonl.py +39 -0
- examples/qwen_coder/todos.md +38 -0
- examples/qwen_coder/validate_jsonl.py +60 -0
- examples/qwen_vl/BUGS_AND_FIXES.md +232 -0
- examples/qwen_vl/IMAGE_VALIDATION_COMPLETE.md +271 -0
- examples/qwen_vl/IMAGE_VALIDATION_SUMMARY.md +260 -0
- examples/qwen_vl/INFERENCE_SFT_TESTS.md +412 -0
- examples/qwen_vl/NEXT_STEPS_2B.md +325 -0
- examples/qwen_vl/QUICKSTART.md +327 -0
- examples/qwen_vl/QUICKSTART_RL_VISION.md +110 -0
- examples/qwen_vl/README.md +152 -0
- examples/qwen_vl/RL_VISION_COMPLETE.md +475 -0
- examples/qwen_vl/RL_VISION_TESTING.md +333 -0
- examples/qwen_vl/SDK_VISION_INTEGRATION.md +328 -0
- examples/qwen_vl/SETUP_COMPLETE.md +274 -0
- examples/qwen_vl/VISION_TESTS_COMPLETE.md +489 -0
- examples/qwen_vl/VLM_PIPELINE_COMPLETE.md +242 -0
- examples/qwen_vl/__init__.py +2 -0
- examples/qwen_vl/collect_data_via_cli.md +415 -0
- examples/qwen_vl/collect_vision_traces.py +368 -0
- examples/qwen_vl/configs/crafter_rl_vision_qwen3vl4b.toml +110 -0
- examples/qwen_vl/configs/crafter_vlm_sft_example.toml +59 -0
- examples/qwen_vl/configs/eval_gpt4o_mini_vision.toml +26 -0
- examples/qwen_vl/configs/eval_gpt4o_vision_proper.toml +29 -0
- examples/qwen_vl/configs/eval_gpt5nano_vision.toml +26 -0
- examples/qwen_vl/configs/eval_qwen3vl_vision.toml +26 -0
- examples/qwen_vl/configs/filter_qwen3vl_sft.toml +49 -0
- examples/qwen_vl/configs/filter_vision_sft.toml +52 -0
- examples/qwen_vl/configs/filter_vision_test.toml +8 -0
- examples/qwen_vl/configs/sft_qwen3_vl_2b_test.toml +54 -0
- examples/qwen_vl/crafter_gpt5nano_agent.py +308 -0
- examples/qwen_vl/crafter_qwen_vl_agent.py +300 -0
- examples/qwen_vl/run_vision_comparison.sh +61 -0
- examples/qwen_vl/run_vision_sft_pipeline.sh +175 -0
- examples/qwen_vl/test_image_validation.py +201 -0
- examples/qwen_vl/test_sft_vision_data.py +110 -0
- examples/rl/README.md +169 -0
- examples/rl/configs/eval_base_qwen.toml +17 -0
- examples/rl/configs/eval_rl_qwen.toml +13 -0
- examples/rl/configs/rl_from_base_qwen.toml +62 -0
- examples/rl/configs/rl_from_base_qwen17.toml +80 -0
- examples/rl/configs/rl_from_ft_qwen.toml +37 -0
- examples/rl/download_dataset.py +80 -0
- examples/rl/run_eval.py +436 -0
- examples/rl/run_rl_and_save.py +111 -0
- examples/rl/task_app/README.md +21 -0
- examples/rl/task_app/math_single_step.py +990 -0
- examples/rl/task_app/math_task_app.py +111 -0
- examples/run_crafter_demo.sh +10 -0
- examples/sdk_prompt_learning_example.py +55 -0
- examples/sft/README.md +139 -0
- examples/sft/configs/crafter_fft_qwen0p6b.toml +49 -0
- examples/sft/configs/crafter_lora_qwen0p6b.toml +49 -0
- examples/sft/evaluate.py +117 -0
- examples/sft/export_dataset.py +120 -0
- examples/sft/generate_traces.py +164 -0
- examples/swe/__init__.py +12 -0
- examples/swe/task_app/README.md +135 -0
- examples/swe/task_app/__init__.py +2 -0
- examples/swe/task_app/grpo_swe_mini.py +604 -0
- examples/swe/task_app/grpo_swe_mini_task_app.py +124 -0
- examples/swe/task_app/hosted/README.md +173 -0
- examples/swe/task_app/hosted/__init__.py +5 -0
- examples/swe/task_app/hosted/branching.py +143 -0
- examples/swe/task_app/hosted/environment_routes.py +1289 -0
- examples/swe/task_app/hosted/envs/__init__.py +1 -0
- examples/swe/task_app/hosted/envs/crafter/__init__.py +6 -0
- examples/swe/task_app/hosted/envs/crafter/app.py +1 -0
- examples/swe/task_app/hosted/envs/crafter/environment.py +522 -0
- examples/swe/task_app/hosted/envs/crafter/policy.py +478 -0
- examples/swe/task_app/hosted/envs/crafter/react_agent.py +108 -0
- examples/swe/task_app/hosted/envs/crafter/shared.py +305 -0
- examples/swe/task_app/hosted/envs/crafter/tools.py +47 -0
- examples/swe/task_app/hosted/envs/mini_swe/__init__.py +8 -0
- examples/swe/task_app/hosted/envs/mini_swe/environment.py +1191 -0
- examples/swe/task_app/hosted/envs/mini_swe/policy.py +355 -0
- examples/swe/task_app/hosted/envs/mini_swe/shared.py +83 -0
- examples/swe/task_app/hosted/envs/mini_swe/tools.py +96 -0
- examples/swe/task_app/hosted/hosted_app.py +204 -0
- examples/swe/task_app/hosted/inference/__init__.py +5 -0
- examples/swe/task_app/hosted/inference/openai_client.py +584 -0
- examples/swe/task_app/hosted/main.py +100 -0
- examples/swe/task_app/hosted/policy_routes.py +1094 -0
- examples/swe/task_app/hosted/registry.py +195 -0
- examples/swe/task_app/hosted/rollout.py +1905 -0
- examples/swe/task_app/hosted/storage/__init__.py +5 -0
- examples/swe/task_app/hosted/storage/volume.py +211 -0
- examples/swe/task_app/hosted/test_agents.py +161 -0
- examples/swe/task_app/hosted/test_service.py +136 -0
- examples/swe/task_app/hosted/utils.py +62 -0
- examples/swe/task_app/morph_backend.py +178 -0
- examples/task_apps/IMAGE_ONLY_EVAL_QUICKSTART.md +258 -0
- examples/task_apps/TESTING.md +275 -0
- examples/task_apps/banking77/__init__.py +6 -0
- examples/task_apps/banking77/banking77_task_app.py +912 -0
- examples/task_apps/banking77/deploy_wrapper.py +46 -0
- examples/task_apps/banking77_pipeline/__init__.py +6 -0
- examples/task_apps/banking77_pipeline/banking77_pipeline_task_app.py +489 -0
- examples/task_apps/banking77_pipeline/deploy_wrapper.py +50 -0
- examples/task_apps/crafter/CREATE_SFT_DATASET.md +286 -0
- examples/task_apps/crafter/EVAL_IMAGE_ONLY_RESULTS.md +152 -0
- examples/task_apps/crafter/FILTER_COMMAND_STATUS.md +187 -0
- examples/task_apps/crafter/FILTER_COMMAND_SUCCESS.md +281 -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/README.md +42 -0
- examples/task_apps/crafter/task_app/__init__.py +5 -0
- examples/task_apps/crafter/task_app/grpo_crafter.py +1055 -0
- examples/task_apps/crafter/task_app/grpo_crafter_task_app.py +146 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/README.md +173 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/__init__.py +5 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/branching.py +143 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/environment_routes.py +1226 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/__init__.py +1 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/__init__.py +6 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/app.py +1 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/environment.py +532 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/policy.py +583 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/react_agent.py +122 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/shared.py +305 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/tools.py +47 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/hosted_app.py +253 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/inference/__init__.py +5 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/inference/openai_client.py +999 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/main.py +100 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/policy_routes.py +1252 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/registry.py +195 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/rollout.py +2233 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/storage/__init__.py +5 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/storage/volume.py +211 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/test_agents.py +161 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/test_service.py +136 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/utils.py +411 -0
- examples/task_apps/dev/pokemon_emerald/__init__.py +2 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/README.md +811 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/__init__.py +120 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/action.py +160 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/memory.py +155 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/perception.py +69 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/planning.py +96 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/simple.py +1502 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/agent/system_prompt.py +4 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/grab_map.py +68 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/manual.py +216 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/__init__.py +35 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/emerald_utils.py +631 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/emulator.py +1544 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/enums.py +1428 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/memory_reader.py +4848 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/types.py +41 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pokemon_env/utils.py +298 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/pyproject.toml +95 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/run.py +204 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/app.py +2152 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/client.py +429 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server/frame_server.py +155 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/README.md +78 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/run_tests.py +122 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_agent_direct.py +76 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_agent_prompts.py +413 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_battle_state_formatting.py +204 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_dialogue_detection.py +133 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_dialogue_detection_comprehensive.py +229 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_direct_agent_emulator.py +300 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_fps_adjustment_pytest.py +205 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_house_to_outside_direct.py +200 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_house_to_outside_transition.py +284 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_map_ground_truth_comparison.py +468 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_memory_map.py +575 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_server_map_validation.py +311 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests/test_torchic_state.py +259 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/anticheat.py +372 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/checkpoint.py +296 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/error_handler.py +275 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/get_local_ip.py +22 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/helpers.py +44 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/llm_logger.py +514 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_formatter.py +415 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_stitcher.py +1763 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_stitcher_singleton.py +33 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_trimmer.py +106 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/map_visualizer.py +334 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/ocr_dialogue.py +1020 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/recording.py +188 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/state_formatter.py +1481 -0
- examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils/vlm.py +862 -0
- examples/task_apps/dev/pokemon_emerald/modal_app.py +114 -0
- examples/task_apps/dev/pokemon_emerald/task_app/README.md +81 -0
- examples/task_apps/dev/pokemon_emerald/task_app/__init__.py +6 -0
- examples/task_apps/dev/pokemon_emerald/task_app/pokemon_emerald.py +685 -0
- examples/task_apps/enron/__init__.py +2 -0
- examples/task_apps/enron/eval_groq_qwen32.toml +16 -0
- examples/task_apps/enron/filter_sft.toml +5 -0
- examples/task_apps/enron/task_app/README.md +14 -0
- examples/task_apps/enron/task_app/__init__.py +1 -0
- examples/task_apps/enron/task_app/grpo_enron.py +906 -0
- examples/task_apps/enron/task_app/grpo_enron_task_app.py +146 -0
- examples/task_apps/enron/tests/__init__.py +4 -0
- examples/task_apps/enron/tests/conftest.py +115 -0
- examples/task_apps/enron/tests/integration/__init__.py +4 -0
- examples/task_apps/enron/tests/integration/test_enron_eval.py +179 -0
- examples/task_apps/enron/tests/integration/test_enron_rollout.py +135 -0
- examples/task_apps/enron/tests/unit/__init__.py +4 -0
- examples/task_apps/enron/tests/unit/test_enron_environment.py +126 -0
- examples/task_apps/gepa_benchmarks/__init__.py +7 -0
- examples/task_apps/gepa_benchmarks/common.py +260 -0
- examples/task_apps/gepa_benchmarks/hotpotqa_task_app.py +507 -0
- examples/task_apps/gepa_benchmarks/hover_task_app.py +436 -0
- examples/task_apps/gepa_benchmarks/ifbench_task_app.py +563 -0
- examples/task_apps/gepa_benchmarks/pupa_task_app.py +460 -0
- examples/task_apps/math/README.md +21 -0
- examples/task_apps/math/math_single_step.py +1000 -0
- examples/task_apps/math/math_task_app.py +115 -0
- examples/task_apps/pokemon_battle/__init__.py +2 -0
- examples/task_apps/pokemon_battle/modal_app.py +104 -0
- examples/task_apps/pokemon_battle/task_app/README.md +68 -0
- examples/task_apps/pokemon_battle/task_app/__init__.py +6 -0
- examples/task_apps/pokemon_battle/task_app/pokemon_showdown.py +932 -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.md +356 -0
- examples/task_apps/pokemon_red/README_IMAGE_ONLY_EVAL.md +428 -0
- examples/task_apps/pokemon_red/__init__.py +3 -0
- examples/task_apps/pokemon_red/eval_image_only_gpt4o.toml +30 -0
- examples/task_apps/pokemon_red/eval_pokemon_red_policy.py +224 -0
- examples/task_apps/pokemon_red/pallet_town_rl_config.toml +75 -0
- examples/task_apps/pokemon_red/task_app.py +1048 -0
- examples/task_apps/pokemon_red/test_pallet_town_rewards.py +193 -0
- examples/task_apps/sokoban/README.md +306 -0
- examples/task_apps/sokoban/__init__.py +3 -0
- examples/task_apps/sokoban/eval_groq_qwen32.toml +16 -0
- examples/task_apps/sokoban/eval_openai_gpt5.toml +16 -0
- examples/task_apps/sokoban/filter_sft.toml +5 -0
- examples/task_apps/sokoban/task_app.py +1058 -0
- examples/task_apps/sokoban/tests/__init__.py +4 -0
- examples/task_apps/sokoban/tests/conftest.py +113 -0
- examples/task_apps/sokoban/tests/integration/__init__.py +4 -0
- examples/task_apps/sokoban/tests/integration/test_sokoban_eval.py +57 -0
- examples/task_apps/sokoban/tests/integration/test_sokoban_rollout.py +198 -0
- examples/task_apps/sokoban/tests/unit/__init__.py +4 -0
- examples/task_apps/sokoban/tests/unit/test_sokoban_environment.py +114 -0
- examples/task_apps/verilog/__init__.py +1 -0
- examples/task_apps/verilog/eval_groq_qwen32b.toml +22 -0
- examples/task_apps/verilog/filter_sft.toml +5 -0
- examples/task_apps/verilog/task_app/README.md +12 -0
- examples/task_apps/verilog/task_app/__init__.py +1 -0
- examples/task_apps/verilog/task_app/grpo_verilog.py +1166 -0
- examples/task_apps/verilog/task_app/grpo_verilog_task_app.py +145 -0
- examples/task_apps/verilog/tests/__init__.py +4 -0
- examples/task_apps/verilog/tests/conftest.py +115 -0
- examples/task_apps/verilog/tests/integration/__init__.py +4 -0
- examples/task_apps/verilog/tests/integration/test_verilog_eval.py +181 -0
- examples/task_apps/verilog/tests/integration/test_verilog_rollout.py +55 -0
- examples/task_apps/verilog/tests/unit/__init__.py +4 -0
- examples/task_apps/verilog/tests/unit/test_verilog_scoring.py +118 -0
- examples/tunnel_gepa_banking77/README.md +106 -0
- examples/tunnel_gepa_banking77/banking77_gepa_tunnel.toml +95 -0
- examples/tunnel_gepa_banking77/keep_tunnel_running.py +60 -0
- examples/tunnel_gepa_banking77/run_gepa_with_tunnel.sh +226 -0
- examples/vlm/PROPOSAL.md +53 -0
- examples/vlm/README.md +68 -0
- examples/vlm/configs/crafter_vlm_gpt4o.toml +49 -0
- examples/vlm/crafter_image_only_agent.py +207 -0
- examples/vlm/crafter_openai_vlm_agent.py +275 -0
- examples/vlm/filter_image_rows.py +63 -0
- examples/vlm/run_crafter_vlm_benchmark.py +316 -0
- examples/warming_up_to_rl/_utils.py +92 -0
- examples/warming_up_to_rl/analyze_trace_db.py +422 -0
- examples/warming_up_to_rl/configs/crafter_fft.toml +53 -0
- examples/warming_up_to_rl/configs/crafter_fft_4b.toml +54 -0
- examples/warming_up_to_rl/configs/eval_fft_qwen4b.toml +22 -0
- examples/warming_up_to_rl/configs/eval_groq_qwen32b.toml +15 -0
- examples/warming_up_to_rl/configs/eval_modal_qwen4b.toml +24 -0
- examples/warming_up_to_rl/configs/eval_stepwise_complex.toml +35 -0
- examples/warming_up_to_rl/configs/eval_stepwise_consistent.toml +26 -0
- examples/warming_up_to_rl/configs/eval_stepwise_per_achievement.toml +36 -0
- examples/warming_up_to_rl/configs/eval_stepwise_simple.toml +32 -0
- examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +85 -0
- examples/warming_up_to_rl/configs/rl_from_ft.toml +58 -0
- examples/warming_up_to_rl/export_trace_sft.py +837 -0
- examples/warming_up_to_rl/groq_test.py +97 -0
- examples/warming_up_to_rl/manage_secrets.py +131 -0
- examples/warming_up_to_rl/old/event_rewards.md +234 -0
- examples/warming_up_to_rl/old/notes.md +73 -0
- examples/warming_up_to_rl/readme.md +110 -0
- examples/warming_up_to_rl/run_eval.py +736 -0
- examples/warming_up_to_rl/run_fft_and_save.py +380 -0
- examples/warming_up_to_rl/run_local_rollout.py +239 -0
- examples/warming_up_to_rl/run_local_rollout_modal.py +248 -0
- examples/warming_up_to_rl/run_local_rollout_parallel.py +405 -0
- examples/warming_up_to_rl/run_local_rollout_traced.py +477 -0
- examples/warming_up_to_rl/run_rl_and_save.py +124 -0
- examples/warming_up_to_rl/run_rollout_remote.py +156 -0
- examples/warming_up_to_rl/task_app/README.md +42 -0
- examples/warming_up_to_rl/task_app/grpo_crafter.py +876 -0
- examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +135 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/README.md +173 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/__init__.py +5 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/branching.py +143 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/environment_routes.py +1226 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/__init__.py +1 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/__init__.py +6 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/app.py +1 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/environment.py +522 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +454 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +108 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/shared.py +305 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/tools.py +47 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/hosted_app.py +253 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/__init__.py +5 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +729 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/main.py +100 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +1114 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/registry.py +195 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +1891 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/__init__.py +5 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/volume.py +211 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/test_agents.py +161 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/test_service.py +137 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/utils.py +129 -0
- examples/workflows/math_rl/configs/eval_base_qwen.toml +15 -0
- examples/workflows/math_rl/configs/eval_rl_qwen.toml +11 -0
- examples/workflows/math_rl/configs/rl_from_base_qwen.toml +62 -0
- examples/workflows/math_rl/configs/rl_from_base_qwen17.toml +80 -0
- examples/workflows/math_rl/configs/rl_from_ft_qwen.toml +35 -0
- examples/workflows/math_rl/download_dataset.py +80 -0
- examples/workflows/math_rl/run_eval.py +436 -0
- examples/workflows/math_rl/run_rl_and_save.py +111 -0
- synth_ai/__init__.py +47 -23
- synth_ai/_utils/__init__.py +47 -0
- synth_ai/_utils/base_url.py +10 -0
- synth_ai/_utils/http.py +10 -0
- synth_ai/_utils/prompts.py +10 -0
- synth_ai/_utils/task_app_state.py +12 -0
- synth_ai/_utils/user_config.py +10 -0
- synth_ai/api/models/supported.py +514 -0
- synth_ai/api/train/__init__.py +63 -0
- synth_ai/api/train/builders.py +473 -0
- synth_ai/api/train/cli.py +1185 -0
- synth_ai/api/train/config_finder.py +246 -0
- synth_ai/api/train/configs/__init__.py +65 -0
- synth_ai/api/train/configs/prompt_learning.py +496 -0
- synth_ai/api/train/configs/rl.py +188 -0
- synth_ai/api/train/configs/sft.py +99 -0
- synth_ai/api/train/configs/shared.py +81 -0
- synth_ai/api/train/env_resolver.py +352 -0
- synth_ai/api/train/pollers.py +91 -0
- synth_ai/api/train/prompt_learning.py +425 -0
- synth_ai/api/train/sft.py +390 -0
- synth_ai/api/train/supported_algos.py +147 -0
- synth_ai/api/train/task_app.py +195 -0
- synth_ai/api/train/utils.py +244 -0
- synth_ai/api/train/validators.py +1117 -0
- synth_ai/api/tunnel.py +49 -0
- synth_ai/auth/credentials.py +94 -0
- synth_ai/baseline/__init__.py +25 -0
- synth_ai/baseline/config.py +209 -0
- synth_ai/baseline/discovery.py +214 -0
- synth_ai/baseline/execution.py +146 -0
- synth_ai/cfgs.py +227 -0
- synth_ai/cli/__init__.py +90 -45
- synth_ai/cli/_modal_wrapper.py +31 -0
- synth_ai/cli/_storage.py +20 -0
- synth_ai/cli/_typer_patch.py +47 -0
- synth_ai/cli/_validate_task_app.py +29 -0
- synth_ai/cli/balance.py +16 -4
- synth_ai/cli/calc.py +36 -21
- synth_ai/cli/claude.py +70 -0
- synth_ai/cli/codex.py +267 -0
- synth_ai/cli/commands/__init__.py +18 -0
- synth_ai/cli/commands/baseline/__init__.py +12 -0
- synth_ai/cli/commands/baseline/core.py +637 -0
- synth_ai/cli/commands/baseline/list.py +93 -0
- synth_ai/cli/commands/demo/__init__.py +6 -0
- synth_ai/cli/commands/demo/core.py +163 -0
- synth_ai/cli/commands/eval/__init__.py +19 -0
- synth_ai/cli/commands/eval/core.py +1112 -0
- synth_ai/cli/commands/eval/errors.py +81 -0
- synth_ai/cli/commands/eval/validation.py +133 -0
- synth_ai/cli/commands/filter/__init__.py +12 -0
- synth_ai/cli/commands/filter/core.py +424 -0
- synth_ai/cli/commands/filter/errors.py +55 -0
- synth_ai/cli/commands/filter/validation.py +77 -0
- synth_ai/cli/commands/help/__init__.py +185 -0
- synth_ai/cli/commands/help/core.py +72 -0
- synth_ai/cli/commands/smoke/__init__.py +7 -0
- synth_ai/cli/commands/smoke/core.py +1437 -0
- synth_ai/cli/commands/status/__init__.py +66 -0
- synth_ai/cli/commands/status/client.py +192 -0
- synth_ai/cli/commands/status/config.py +92 -0
- synth_ai/cli/commands/status/errors.py +20 -0
- synth_ai/cli/commands/status/formatters.py +164 -0
- synth_ai/cli/commands/status/subcommands/__init__.py +9 -0
- synth_ai/cli/commands/status/subcommands/files.py +79 -0
- synth_ai/cli/commands/status/subcommands/jobs.py +334 -0
- synth_ai/cli/commands/status/subcommands/models.py +79 -0
- synth_ai/cli/commands/status/subcommands/pricing.py +22 -0
- synth_ai/cli/commands/status/subcommands/runs.py +81 -0
- synth_ai/cli/commands/status/subcommands/session.py +183 -0
- synth_ai/cli/commands/status/subcommands/summary.py +47 -0
- synth_ai/cli/commands/status/subcommands/usage.py +203 -0
- synth_ai/cli/commands/status/utils.py +114 -0
- synth_ai/cli/commands/train/__init__.py +53 -0
- synth_ai/cli/commands/train/core.py +21 -0
- synth_ai/cli/commands/train/errors.py +117 -0
- synth_ai/cli/commands/train/judge_schemas.py +200 -0
- synth_ai/cli/commands/train/judge_validation.py +305 -0
- synth_ai/cli/commands/train/validation.py +386 -0
- synth_ai/cli/demo.py +32 -140
- synth_ai/cli/deploy.py +233 -0
- synth_ai/cli/eval/__init__.py +36 -0
- synth_ai/cli/eval/core.py +5 -0
- synth_ai/cli/eval/errors.py +31 -0
- synth_ai/cli/eval/validation.py +5 -0
- synth_ai/cli/filter/__init__.py +28 -0
- synth_ai/cli/filter/core.py +5 -0
- synth_ai/cli/filter/errors.py +23 -0
- synth_ai/cli/filter/validation.py +5 -0
- synth_ai/cli/legacy_root_backup.py +28 -22
- synth_ai/cli/lib/__init__.py +10 -0
- synth_ai/cli/lib/task_app_discovery.py +7 -0
- synth_ai/cli/lib/task_app_env.py +518 -0
- synth_ai/cli/mcp.py +34 -0
- synth_ai/cli/modal_serve/__init__.py +12 -0
- synth_ai/cli/modal_serve/core.py +14 -0
- synth_ai/cli/modal_serve/errors.py +8 -0
- synth_ai/cli/modal_serve/validation.py +11 -0
- synth_ai/cli/opencode.py +256 -0
- synth_ai/cli/recent.py +13 -7
- synth_ai/cli/rl_demo.py +166 -114
- synth_ai/cli/root.py +143 -112
- synth_ai/cli/serve/__init__.py +12 -0
- synth_ai/cli/serve/core.py +14 -0
- synth_ai/cli/serve/errors.py +8 -0
- synth_ai/cli/serve/validation.py +11 -0
- synth_ai/cli/setup.py +49 -0
- synth_ai/cli/status.py +7 -125
- synth_ai/cli/task_app_deploy.py +7 -0
- synth_ai/cli/task_app_list.py +25 -0
- synth_ai/cli/task_app_modal_serve.py +11 -0
- synth_ai/cli/task_app_serve.py +11 -0
- synth_ai/cli/task_apps.py +3134 -0
- synth_ai/cli/traces.py +9 -5
- synth_ai/cli/train/__init__.py +12 -0
- synth_ai/cli/train/core.py +21 -0
- synth_ai/cli/train/errors.py +8 -0
- synth_ai/cli/train/validation.py +24 -0
- synth_ai/cli/train.py +5 -0
- synth_ai/cli/turso.py +73 -0
- synth_ai/cli/watch.py +13 -18
- synth_ai/demos/__init__.py +10 -0
- synth_ai/demos/core/__init__.py +28 -1
- synth_ai/demos/core/cli.py +745 -416
- synth_ai/demos/crafter/__init__.py +1 -0
- synth_ai/demos/crafter/crafter_fft_4b.toml +55 -0
- synth_ai/demos/crafter/grpo_crafter_task_app.py +185 -0
- synth_ai/demos/crafter/rl_from_base_qwen4b.toml +74 -0
- synth_ai/demos/demo_registry.py +176 -0
- synth_ai/demos/demo_task_apps/__init__.py +7 -1
- synth_ai/demos/demo_task_apps/core.py +75 -37
- synth_ai/demos/demo_task_apps/crafter/__init__.py +1 -0
- synth_ai/demos/demo_task_apps/crafter/configs/crafter_fft_4b.toml +53 -0
- synth_ai/demos/demo_task_apps/crafter/configs/rl_from_base_qwen4b.toml +73 -0
- synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +184 -0
- synth_ai/demos/demo_task_apps/math/_common.py +1 -2
- synth_ai/demos/demo_task_apps/math/app.py +2 -1
- synth_ai/demos/demo_task_apps/math/config.toml +55 -110
- synth_ai/demos/demo_task_apps/math/deploy_modal.py +3 -6
- synth_ai/demos/demo_task_apps/math/modal_task_app.py +491 -166
- synth_ai/demos/demo_task_apps/math/task_app_entry.py +37 -0
- synth_ai/demos/math/__init__.py +1 -0
- synth_ai/demos/math/_common.py +16 -0
- synth_ai/demos/math/app.py +38 -0
- synth_ai/demos/math/config.toml +76 -0
- synth_ai/demos/math/deploy_modal.py +54 -0
- synth_ai/demos/math/modal_task_app.py +703 -0
- synth_ai/demos/math/task_app_entry.py +51 -0
- synth_ai/environments/environment/core.py +7 -1
- synth_ai/environments/examples/bandit/engine.py +12 -5
- synth_ai/environments/examples/bandit/environment.py +0 -1
- synth_ai/environments/examples/bandit/taskset.py +4 -4
- synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +7 -4
- synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +9 -5
- synth_ai/environments/examples/crafter_classic/environment.py +93 -2
- synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +4 -3
- synth_ai/environments/examples/enron/engine.py +7 -2
- synth_ai/environments/examples/enron/environment.py +68 -0
- synth_ai/environments/examples/red/engine.py +60 -12
- synth_ai/environments/examples/red/engine_helpers/memory_map.py +7 -0
- synth_ai/environments/examples/red/engine_helpers/reward_components.py +151 -179
- synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_progression.py +477 -0
- synth_ai/environments/examples/red/engine_helpers/state_extraction.py +32 -0
- synth_ai/environments/examples/red/environment.py +86 -0
- synth_ai/environments/examples/red/trace_hooks_v3.py +168 -0
- synth_ai/environments/examples/sokoban/taskset.py +116 -0
- synth_ai/environments/examples/verilog/engine.py +104 -12
- synth_ai/environments/examples/wordle/environment.py +0 -1
- synth_ai/environments/reproducibility/tree.py +5 -6
- synth_ai/environments/service/app.py +11 -12
- synth_ai/environments/service/core_routes.py +10 -9
- synth_ai/environments/stateful/engine.py +1 -1
- synth_ai/environments/tasks/core.py +1 -0
- synth_ai/environments/tasks/filters.py +5 -6
- synth_ai/environments/tasks/utils.py +4 -5
- synth_ai/evals/__init__.py +15 -0
- synth_ai/evals/base.py +14 -5
- synth_ai/evals/client.py +82 -0
- synth_ai/evals/types.py +42 -0
- synth_ai/http.py +8 -22
- synth_ai/http_client.py +45 -12
- synth_ai/inference/__init__.py +0 -2
- synth_ai/inference/client.py +21 -7
- synth_ai/jobs/client.py +129 -80
- synth_ai/judge_schemas.py +127 -0
- synth_ai/learning/__init__.py +51 -6
- synth_ai/learning/algorithms.py +14 -0
- synth_ai/learning/client.py +122 -30
- synth_ai/learning/config.py +2 -40
- synth_ai/learning/constants.py +0 -2
- synth_ai/learning/ft_client.py +4 -56
- synth_ai/learning/health.py +14 -8
- synth_ai/learning/jobs.py +43 -47
- synth_ai/learning/prompt_learning_client.py +276 -0
- synth_ai/learning/prompt_learning_types.py +185 -0
- synth_ai/{rl → learning/rl}/__init__.py +14 -5
- synth_ai/learning/rl/client.py +269 -0
- synth_ai/learning/rl/config.py +31 -0
- synth_ai/{rl → learning/rl}/contracts.py +5 -10
- synth_ai/{rl → learning/rl}/env_keys.py +45 -16
- synth_ai/learning/rl/secrets.py +13 -0
- synth_ai/learning/rl_client.py +2 -253
- synth_ai/learning/sft/__init__.py +29 -0
- synth_ai/learning/sft/client.py +68 -0
- synth_ai/learning/sft/config.py +270 -0
- synth_ai/learning/sft/data.py +698 -0
- synth_ai/learning/sse.py +25 -26
- synth_ai/learning/validators.py +29 -25
- synth_ai/mcp/__init__.py +5 -0
- synth_ai/mcp/__main__.py +8 -0
- synth_ai/mcp/main.py +254 -0
- synth_ai/mcp/setup.py +100 -0
- synth_ai/modal.py +257 -0
- synth_ai/pricing/__init__.py +3 -0
- synth_ai/pricing/model_pricing.py +64 -0
- synth_ai/session/__init__.py +75 -0
- synth_ai/session/client.py +383 -0
- synth_ai/session/constants.py +63 -0
- synth_ai/session/exceptions.py +105 -0
- synth_ai/session/manager.py +139 -0
- synth_ai/session/models.py +89 -0
- synth_ai/session/query.py +110 -0
- synth_ai/spec/__init__.py +46 -0
- synth_ai/spec/dataclasses.py +149 -0
- synth_ai/spec/loader.py +144 -0
- synth_ai/spec/serializer.py +199 -0
- synth_ai/spec/validation.py +250 -0
- synth_ai/streaming/__init__.py +29 -0
- synth_ai/streaming/config.py +94 -0
- synth_ai/streaming/handlers.py +589 -0
- synth_ai/streaming/streamer.py +320 -0
- synth_ai/streaming/types.py +95 -0
- synth_ai/task/__init__.py +116 -3
- synth_ai/task/apps/__init__.py +132 -0
- synth_ai/task/auth.py +165 -0
- synth_ai/task/client.py +167 -0
- synth_ai/task/config.py +261 -0
- synth_ai/task/contracts.py +173 -57
- synth_ai/task/datasets.py +108 -0
- synth_ai/task/errors.py +50 -0
- synth_ai/task/health.py +17 -11
- synth_ai/task/inference_api.py +101 -0
- synth_ai/task/json.py +111 -0
- synth_ai/task/proxy.py +251 -0
- synth_ai/task/rubrics/__init__.py +55 -0
- synth_ai/task/rubrics/loaders.py +156 -0
- synth_ai/task/rubrics/models.py +57 -0
- synth_ai/task/rubrics/scoring.py +116 -0
- synth_ai/task/rubrics/strict.py +149 -0
- synth_ai/task/rubrics.py +219 -0
- synth_ai/task/server.py +432 -0
- synth_ai/task/trace_correlation_helpers.py +328 -0
- synth_ai/task/tracing_utils.py +95 -0
- synth_ai/task/validators.py +449 -6
- synth_ai/task/vendors.py +59 -0
- synth_ai/tracing_v3/__init__.py +4 -0
- synth_ai/tracing_v3/abstractions.py +21 -4
- synth_ai/tracing_v3/config.py +167 -22
- synth_ai/tracing_v3/constants.py +21 -0
- synth_ai/tracing_v3/db_config.py +42 -29
- synth_ai/tracing_v3/decorators.py +80 -45
- synth_ai/tracing_v3/examples/basic_usage.py +15 -9
- synth_ai/tracing_v3/hooks.py +6 -4
- synth_ai/tracing_v3/llm_call_record_helpers.py +161 -61
- synth_ai/tracing_v3/migration_helper.py +1 -2
- synth_ai/tracing_v3/replica_sync.py +12 -7
- synth_ai/tracing_v3/serialization.py +130 -0
- synth_ai/tracing_v3/session_tracer.py +86 -21
- synth_ai/tracing_v3/storage/base.py +98 -12
- synth_ai/tracing_v3/storage/config.py +63 -16
- synth_ai/tracing_v3/storage/factory.py +11 -9
- synth_ai/tracing_v3/storage/utils.py +15 -11
- synth_ai/tracing_v3/trace_utils.py +317 -0
- synth_ai/tracing_v3/turso/__init__.py +8 -21
- synth_ai/tracing_v3/turso/daemon.py +123 -15
- synth_ai/tracing_v3/turso/models.py +5 -2
- synth_ai/tracing_v3/turso/native_manager.py +1293 -0
- synth_ai/tracing_v3/utils.py +5 -4
- synth_ai/tunnel.py +143 -0
- synth_ai/tunnel_deploy.py +278 -0
- synth_ai/types.py +8 -0
- synth_ai/urls.py +11 -0
- synth_ai/utils/__init__.py +166 -0
- synth_ai/utils/agents.py +74 -0
- synth_ai/utils/apps.py +152 -0
- synth_ai/utils/base_url.py +94 -0
- synth_ai/utils/bin.py +39 -0
- synth_ai/utils/claude.py +36 -0
- synth_ai/utils/cli.py +284 -0
- synth_ai/utils/config.py +81 -0
- synth_ai/utils/env.py +346 -0
- synth_ai/utils/errors.py +85 -0
- synth_ai/utils/http.py +172 -0
- synth_ai/utils/json.py +72 -0
- synth_ai/utils/log_filter.py +99 -0
- synth_ai/utils/logging.py +198 -0
- synth_ai/utils/modal.py +299 -0
- synth_ai/utils/paths.py +95 -0
- synth_ai/utils/process.py +233 -0
- synth_ai/utils/prompts.py +39 -0
- synth_ai/utils/sqld.py +122 -0
- synth_ai/utils/ssl.py +25 -0
- synth_ai/utils/task_app_discovery.py +882 -0
- synth_ai/utils/task_app_env.py +186 -0
- synth_ai/utils/task_app_state.py +318 -0
- synth_ai/utils/tunnel/__init__.py +12 -0
- synth_ai/utils/tunnel/config.py +55 -0
- synth_ai/utils/user_config.py +137 -0
- synth_ai/uvicorn.py +77 -0
- synth_ai-0.2.23.dev3.dist-info/METADATA +357 -0
- synth_ai-0.2.23.dev3.dist-info/RECORD +983 -0
- {synth_ai-0.2.8.dev4.dist-info → synth_ai-0.2.23.dev3.dist-info}/entry_points.txt +0 -1
- {synth_ai-0.2.8.dev4.dist-info → synth_ai-0.2.23.dev3.dist-info}/top_level.txt +1 -0
- synth_ai/cli/man.py +0 -106
- synth_ai/core/experiment.py +0 -15
- synth_ai/core/system.py +0 -15
- synth_ai/environments/examples/sokoban/units/astar_common.py +0 -95
- synth_ai/experimental/synth_oss.py +0 -446
- synth_ai/handshake.py +0 -63
- synth_ai/install_sqld.sh +0 -40
- synth_ai/learning/offline/dpo.py +0 -0
- synth_ai/learning/offline/providers.py +0 -7
- synth_ai/learning/offline/sft.py +0 -0
- synth_ai/learning/offline/shared.py +0 -0
- synth_ai/learning/online/grpo.py +0 -0
- synth_ai/learning/online/irft.py +0 -0
- synth_ai/learning/prompts/banking77_injection_eval.py +0 -168
- synth_ai/learning/prompts/gepa.py +0 -0
- synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +0 -213
- synth_ai/learning/prompts/mipro.py +0 -289
- synth_ai/learning/prompts/random_search.py +0 -246
- synth_ai/learning/prompts/run_mipro_banking77.py +0 -172
- synth_ai/learning/prompts/run_random_search_banking77.py +0 -324
- synth_ai/lm/__init__.py +0 -51
- synth_ai/lm/caching/constants.py +0 -6
- synth_ai/lm/caching/dbs.py +0 -0
- synth_ai/lm/caching/ephemeral.py +0 -102
- synth_ai/lm/caching/handler.py +0 -137
- synth_ai/lm/caching/initialize.py +0 -11
- synth_ai/lm/caching/persistent.py +0 -114
- synth_ai/lm/config.py +0 -110
- synth_ai/lm/constants.py +0 -32
- synth_ai/lm/core/__init__.py +0 -8
- synth_ai/lm/core/all.py +0 -73
- synth_ai/lm/core/exceptions.py +0 -7
- synth_ai/lm/core/main.py +0 -319
- synth_ai/lm/core/main_v3.py +0 -594
- synth_ai/lm/core/synth_models.py +0 -48
- synth_ai/lm/core/vendor_clients.py +0 -188
- synth_ai/lm/cost/monitor.py +0 -1
- synth_ai/lm/cost/statefulness.py +0 -1
- synth_ai/lm/injection.py +0 -80
- synth_ai/lm/overrides.py +0 -206
- synth_ai/lm/provider_support/__init__.py +0 -8
- synth_ai/lm/provider_support/anthropic.py +0 -972
- synth_ai/lm/provider_support/openai.py +0 -1139
- synth_ai/lm/provider_support/suppress_logging.py +0 -31
- synth_ai/lm/structured_outputs/handler.py +0 -440
- synth_ai/lm/structured_outputs/inject.py +0 -297
- synth_ai/lm/structured_outputs/rehabilitate.py +0 -185
- synth_ai/lm/tools/__init__.py +0 -3
- synth_ai/lm/tools/base.py +0 -172
- synth_ai/lm/unified_interface.py +0 -202
- synth_ai/lm/vendors/base.py +0 -81
- synth_ai/lm/vendors/core/anthropic_api.py +0 -387
- synth_ai/lm/vendors/core/gemini_api.py +0 -292
- synth_ai/lm/vendors/core/mistral_api.py +0 -322
- synth_ai/lm/vendors/core/openai_api.py +0 -225
- synth_ai/lm/vendors/core/synth_dev_api.py +0 -0
- synth_ai/lm/vendors/local/ollama.py +0 -0
- synth_ai/lm/vendors/openai_standard.py +0 -780
- synth_ai/lm/vendors/openai_standard_responses.py +0 -256
- synth_ai/lm/vendors/retries.py +0 -22
- synth_ai/lm/vendors/supported/custom_endpoint.py +0 -417
- synth_ai/lm/vendors/supported/deepseek.py +0 -69
- synth_ai/lm/vendors/supported/grok.py +0 -75
- synth_ai/lm/vendors/supported/groq.py +0 -16
- synth_ai/lm/vendors/supported/ollama.py +0 -15
- synth_ai/lm/vendors/supported/openrouter.py +0 -74
- synth_ai/lm/vendors/supported/together.py +0 -11
- synth_ai/lm/vendors/synth_client.py +0 -808
- synth_ai/lm/warmup.py +0 -186
- synth_ai/rl/secrets.py +0 -19
- synth_ai/scripts/verify_rewards.py +0 -100
- synth_ai/tracing/__init__.py +0 -30
- synth_ai/tracing_v1/__init__.py +0 -33
- synth_ai/tracing_v3/turso/manager.py +0 -760
- synth_ai/v0/tracing/abstractions.py +0 -224
- synth_ai/v0/tracing/base_client.py +0 -91
- synth_ai/v0/tracing/client_manager.py +0 -131
- synth_ai/v0/tracing/config.py +0 -142
- synth_ai/v0/tracing/context.py +0 -146
- synth_ai/v0/tracing/decorators.py +0 -682
- synth_ai/v0/tracing/events/__init__.py +0 -0
- synth_ai/v0/tracing/events/manage.py +0 -147
- synth_ai/v0/tracing/events/scope.py +0 -86
- synth_ai/v0/tracing/events/store.py +0 -228
- synth_ai/v0/tracing/immediate_client.py +0 -151
- synth_ai/v0/tracing/local.py +0 -18
- synth_ai/v0/tracing/log_client_base.py +0 -73
- synth_ai/v0/tracing/retry_queue.py +0 -186
- synth_ai/v0/tracing/trackers.py +0 -515
- synth_ai/v0/tracing/upload.py +0 -512
- synth_ai/v0/tracing/utils.py +0 -9
- synth_ai/v0/tracing_v1/__init__.py +0 -16
- synth_ai/v0/tracing_v1/abstractions.py +0 -224
- synth_ai/v0/tracing_v1/base_client.py +0 -91
- synth_ai/v0/tracing_v1/client_manager.py +0 -131
- synth_ai/v0/tracing_v1/config.py +0 -142
- synth_ai/v0/tracing_v1/context.py +0 -146
- synth_ai/v0/tracing_v1/decorators.py +0 -703
- synth_ai/v0/tracing_v1/events/__init__.py +0 -0
- synth_ai/v0/tracing_v1/events/manage.py +0 -147
- synth_ai/v0/tracing_v1/events/scope.py +0 -86
- synth_ai/v0/tracing_v1/events/store.py +0 -228
- synth_ai/v0/tracing_v1/immediate_client.py +0 -151
- synth_ai/v0/tracing_v1/local.py +0 -18
- synth_ai/v0/tracing_v1/log_client_base.py +0 -73
- synth_ai/v0/tracing_v1/retry_queue.py +0 -186
- synth_ai/v0/tracing_v1/trackers.py +0 -515
- synth_ai/v0/tracing_v1/upload.py +0 -527
- synth_ai/v0/tracing_v1/utils.py +0 -9
- synth_ai/zyk/__init__.py +0 -30
- synth_ai-0.2.8.dev4.dist-info/METADATA +0 -129
- synth_ai-0.2.8.dev4.dist-info/RECORD +0 -420
- {synth_ai/lm/caching → examples/task_apps}/__init__.py +0 -0
- {synth_ai/lm/cost → examples/task_apps/crafter}/__init__.py +0 -0
- {synth_ai/lm/structured_outputs → examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/server}/__init__.py +0 -0
- {synth_ai/lm/vendors → examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/tests}/__init__.py +0 -0
- {synth_ai/lm/vendors/core → examples/task_apps/dev/pokemon_emerald/external/pokeagent-speedrun/utils}/__init__.py +0 -0
- {synth_ai/lm/vendors/local → examples/task_apps/math}/__init__.py +0 -0
- {synth_ai/lm/vendors/supported → examples/workflows}/__init__.py +0 -0
- {synth_ai/v0/tracing → examples/workflows/math_rl}/__init__.py +0 -0
- /synth_ai/{compound/cais.py → cli/__main__.py} +0 -0
- /synth_ai/{learning/filtering.py → py.typed} +0 -0
- {synth_ai-0.2.8.dev4.dist-info → synth_ai-0.2.23.dev3.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.8.dev4.dist-info → synth_ai-0.2.23.dev3.dist-info}/licenses/LICENSE +0 -0
synth_ai/demos/core/cli.py
CHANGED
|
@@ -1,19 +1,38 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import contextlib
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
|
-
import sys
|
|
7
|
-
import time
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from typing import Any, Dict, Callable
|
|
10
6
|
import shutil
|
|
11
7
|
import stat
|
|
8
|
+
import sys
|
|
12
9
|
import textwrap
|
|
10
|
+
import time
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
13
14
|
|
|
15
|
+
from synth_ai.demo_registry import (
|
|
16
|
+
DemoTemplate,
|
|
17
|
+
get_demo_template,
|
|
18
|
+
list_demo_templates,
|
|
19
|
+
)
|
|
14
20
|
from synth_ai.demos.demo_task_apps import core as demo_core
|
|
15
|
-
from synth_ai.
|
|
16
|
-
from synth_ai.
|
|
21
|
+
from synth_ai.demos.demo_task_apps.core import DEFAULT_TASK_APP_SECRET_NAME, DemoEnv
|
|
22
|
+
from synth_ai.handshake import HandshakeError, run_handshake
|
|
23
|
+
from synth_ai.utils.process import get_subprocess_env, should_filter_log_line
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _key_preview(value: str, label: str) -> str:
|
|
27
|
+
"""Return a short descriptor for a secret without leaking the full value."""
|
|
28
|
+
try:
|
|
29
|
+
text = value or ""
|
|
30
|
+
length = len(text)
|
|
31
|
+
prefix = text[:6] if length >= 6 else text
|
|
32
|
+
suffix = text[-5:] if length >= 5 else text
|
|
33
|
+
return f"{label} len={length} prefix={prefix} last5={suffix}"
|
|
34
|
+
except Exception:
|
|
35
|
+
return f"{label} len=0"
|
|
17
36
|
|
|
18
37
|
|
|
19
38
|
def _is_modal_public_url(u: str) -> bool:
|
|
@@ -26,35 +45,71 @@ def _is_modal_public_url(u: str) -> bool:
|
|
|
26
45
|
return False
|
|
27
46
|
|
|
28
47
|
|
|
29
|
-
def
|
|
30
|
-
#
|
|
48
|
+
def setup() -> int:
|
|
49
|
+
# Change to demo directory if stored
|
|
50
|
+
demo_dir = demo_core.load_demo_dir()
|
|
51
|
+
if demo_dir and os.path.isdir(demo_dir):
|
|
52
|
+
os.chdir(demo_dir)
|
|
53
|
+
print(f"Using demo directory: {demo_dir}")
|
|
54
|
+
|
|
55
|
+
# 1) Try to fetch keys from frontend; fall back to manual input if fetch fails
|
|
56
|
+
synth_key = ""
|
|
57
|
+
rl_env_key = ""
|
|
58
|
+
org_name = "this organization"
|
|
59
|
+
|
|
31
60
|
try:
|
|
32
61
|
print("\n⏳ Connecting SDK to your browser session…")
|
|
33
62
|
res = run_handshake()
|
|
34
|
-
user = res.get("user") or {}
|
|
35
63
|
org = res.get("org") or {}
|
|
36
64
|
keys = res.get("keys") or {}
|
|
37
65
|
synth_key = str(keys.get("synth") or "").strip()
|
|
38
66
|
rl_env_key = str(keys.get("rl_env") or "").strip()
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
67
|
+
org_name = org.get("name") or "this organization"
|
|
68
|
+
print(f"✅ Connected to {org_name}!")
|
|
69
|
+
except (HandshakeError, Exception) as e:
|
|
70
|
+
print(f"⚠️ Failed to fetch keys from frontend: {e}")
|
|
71
|
+
print("Falling back to manual entry...")
|
|
72
|
+
|
|
73
|
+
# Prompt for manual input if any key is missing
|
|
74
|
+
if not synth_key:
|
|
75
|
+
try:
|
|
76
|
+
synth_key = input(
|
|
77
|
+
"Failed to fetch your Synth API key. Please enter your Synth API key here:\n> "
|
|
78
|
+
).strip()
|
|
79
|
+
except (EOFError, KeyboardInterrupt):
|
|
80
|
+
print("\nSetup cancelled.")
|
|
81
|
+
return 1
|
|
82
|
+
if not synth_key:
|
|
83
|
+
print("Synth API key is required.")
|
|
84
|
+
return 1
|
|
85
|
+
|
|
86
|
+
if not rl_env_key:
|
|
87
|
+
try:
|
|
88
|
+
rl_env_key = input(
|
|
89
|
+
"Failed to fetch your RL Environment API key. Please enter your RL Environment API key here:\n> "
|
|
90
|
+
).strip()
|
|
91
|
+
except (EOFError, KeyboardInterrupt):
|
|
92
|
+
print("\nSetup cancelled.")
|
|
93
|
+
return 1
|
|
94
|
+
if not rl_env_key:
|
|
95
|
+
print("RL Environment API key is required.")
|
|
96
|
+
return 1
|
|
97
|
+
|
|
98
|
+
# Persist both keys to .env
|
|
99
|
+
dotenv_path = demo_core.persist_dotenv_values(
|
|
100
|
+
{
|
|
43
101
|
"SYNTH_API_KEY": synth_key,
|
|
44
102
|
"ENVIRONMENT_API_KEY": rl_env_key,
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return 1
|
|
51
|
-
except Exception as e:
|
|
52
|
-
print(f"Unexpected handshake error: {e}")
|
|
53
|
-
return 1
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Store .env path for subsequent commands
|
|
107
|
+
demo_core.persist_env_file_path(dotenv_path)
|
|
54
108
|
|
|
55
109
|
# 2) Reload env after handshake to pick up values from .env (suppress env prints)
|
|
56
|
-
import io
|
|
57
110
|
import contextlib
|
|
111
|
+
import io
|
|
112
|
+
|
|
58
113
|
_buf = io.StringIO()
|
|
59
114
|
with contextlib.redirect_stdout(_buf):
|
|
60
115
|
env = demo_core.load_env()
|
|
@@ -71,22 +126,22 @@ def cmd_setup(_args: argparse.Namespace) -> int:
|
|
|
71
126
|
return
|
|
72
127
|
current = env.task_app_base_url
|
|
73
128
|
needs_lookup = False
|
|
74
|
-
if not current:
|
|
75
|
-
needs_lookup = True
|
|
76
|
-
elif not _is_modal_public_url(current):
|
|
129
|
+
if not current or not _is_modal_public_url(current):
|
|
77
130
|
needs_lookup = True
|
|
78
131
|
if not needs_lookup:
|
|
79
132
|
return
|
|
80
|
-
code, out = _popen_capture(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
133
|
+
code, out = _popen_capture(
|
|
134
|
+
[
|
|
135
|
+
"uv",
|
|
136
|
+
"run",
|
|
137
|
+
"python",
|
|
138
|
+
"-m",
|
|
139
|
+
"modal",
|
|
140
|
+
"app",
|
|
141
|
+
"url",
|
|
142
|
+
env.task_app_name,
|
|
143
|
+
]
|
|
144
|
+
)
|
|
90
145
|
if code != 0 or not out:
|
|
91
146
|
return
|
|
92
147
|
new_url = ""
|
|
@@ -100,7 +155,6 @@ def cmd_setup(_args: argparse.Namespace) -> int:
|
|
|
100
155
|
dotenv_values = {
|
|
101
156
|
"TASK_APP_BASE_URL": new_url,
|
|
102
157
|
"TASK_APP_NAME": env.task_app_name,
|
|
103
|
-
"TASK_APP_SECRET_NAME": env.task_app_secret_name or f"{env.task_app_name}-secret",
|
|
104
158
|
}
|
|
105
159
|
demo_core.persist_dotenv_values(dotenv_values)
|
|
106
160
|
os.environ["TASK_APP_BASE_URL"] = new_url
|
|
@@ -117,15 +171,16 @@ def cmd_setup(_args: argparse.Namespace) -> int:
|
|
|
117
171
|
|
|
118
172
|
_maybe_fix_task_url()
|
|
119
173
|
|
|
120
|
-
ok_backend = False
|
|
121
|
-
ok_task = False
|
|
122
174
|
if env.dev_backend_url:
|
|
123
|
-
api = env.dev_backend_url.rstrip("/") + (
|
|
124
|
-
|
|
175
|
+
api = env.dev_backend_url.rstrip("/") + (
|
|
176
|
+
"" if env.dev_backend_url.endswith("/api") else "/api"
|
|
177
|
+
)
|
|
178
|
+
demo_core.assert_http_ok(api + "/health", method="GET")
|
|
125
179
|
# Intentionally suppress backend health print for concise output
|
|
126
180
|
if env.task_app_base_url:
|
|
127
|
-
|
|
128
|
-
|
|
181
|
+
demo_core.assert_http_ok(
|
|
182
|
+
env.task_app_base_url.rstrip("/") + "/health", method="GET"
|
|
183
|
+
) or demo_core.assert_http_ok(env.task_app_base_url.rstrip("/"), method="GET")
|
|
129
184
|
# Intentionally suppress task app health print
|
|
130
185
|
else:
|
|
131
186
|
print("\nSet your task app URL by running:\nuvx synth-ai rl_demo deploy\n")
|
|
@@ -133,13 +188,19 @@ def cmd_setup(_args: argparse.Namespace) -> int:
|
|
|
133
188
|
# Omit uv version print to keep output concise
|
|
134
189
|
|
|
135
190
|
# Keep exit code neutral; not all checks are critical for pairing
|
|
191
|
+
print(f"\nKeys saved to: {dotenv_path}")
|
|
136
192
|
return 0
|
|
137
193
|
|
|
138
194
|
|
|
139
|
-
def _popen_capture(
|
|
195
|
+
def _popen_capture(
|
|
196
|
+
cmd: list[str], cwd: str | None = None, env: dict | None = None
|
|
197
|
+
) -> tuple[int, str]:
|
|
140
198
|
import subprocess
|
|
199
|
+
|
|
141
200
|
try:
|
|
142
|
-
proc = subprocess.Popen(
|
|
201
|
+
proc = subprocess.Popen(
|
|
202
|
+
cmd, cwd=cwd, env=get_subprocess_env(env), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
|
|
203
|
+
)
|
|
143
204
|
out, _ = proc.communicate()
|
|
144
205
|
return int(proc.returncode or 0), out or ""
|
|
145
206
|
except Exception as e:
|
|
@@ -156,7 +217,7 @@ def _popen_stream(cmd: list[str], cwd: str | None = None, env: dict | None = Non
|
|
|
156
217
|
proc = subprocess.Popen(
|
|
157
218
|
cmd,
|
|
158
219
|
cwd=cwd,
|
|
159
|
-
env=env,
|
|
220
|
+
env=get_subprocess_env(env),
|
|
160
221
|
stdout=subprocess.PIPE,
|
|
161
222
|
stderr=subprocess.STDOUT,
|
|
162
223
|
text=True,
|
|
@@ -169,7 +230,8 @@ def _popen_stream(cmd: list[str], cwd: str | None = None, env: dict | None = Non
|
|
|
169
230
|
def _pump(stdout) -> None:
|
|
170
231
|
try:
|
|
171
232
|
for line in stdout:
|
|
172
|
-
|
|
233
|
+
if not should_filter_log_line(line):
|
|
234
|
+
print(line.rstrip())
|
|
173
235
|
except Exception:
|
|
174
236
|
pass
|
|
175
237
|
|
|
@@ -183,7 +245,9 @@ def _popen_stream(cmd: list[str], cwd: str | None = None, env: dict | None = Non
|
|
|
183
245
|
return int(proc.returncode or 0)
|
|
184
246
|
|
|
185
247
|
|
|
186
|
-
def _popen_stream_capture(
|
|
248
|
+
def _popen_stream_capture(
|
|
249
|
+
cmd: list[str], cwd: str | None = None, env: dict | None = None
|
|
250
|
+
) -> tuple[int, str]:
|
|
187
251
|
"""Stream subprocess output to stdout and also capture it into a buffer."""
|
|
188
252
|
import subprocess
|
|
189
253
|
import threading
|
|
@@ -193,7 +257,7 @@ def _popen_stream_capture(cmd: list[str], cwd: str | None = None, env: dict | No
|
|
|
193
257
|
proc = subprocess.Popen(
|
|
194
258
|
cmd,
|
|
195
259
|
cwd=cwd,
|
|
196
|
-
env=env,
|
|
260
|
+
env=get_subprocess_env(env),
|
|
197
261
|
stdout=subprocess.PIPE,
|
|
198
262
|
stderr=subprocess.STDOUT,
|
|
199
263
|
text=True,
|
|
@@ -207,8 +271,9 @@ def _popen_stream_capture(cmd: list[str], cwd: str | None = None, env: dict | No
|
|
|
207
271
|
try:
|
|
208
272
|
for line in stdout:
|
|
209
273
|
line = line.rstrip()
|
|
210
|
-
|
|
211
|
-
|
|
274
|
+
if not should_filter_log_line(line):
|
|
275
|
+
print(line)
|
|
276
|
+
buf_lines.append(line)
|
|
212
277
|
except Exception:
|
|
213
278
|
pass
|
|
214
279
|
|
|
@@ -222,55 +287,6 @@ def _popen_stream_capture(cmd: list[str], cwd: str | None = None, env: dict | No
|
|
|
222
287
|
return int(proc.returncode or 0), "\n".join(buf_lines)
|
|
223
288
|
|
|
224
289
|
|
|
225
|
-
def _mask_secret_args(args: list[str]) -> list[str]:
|
|
226
|
-
masked: list[str] = []
|
|
227
|
-
for a in args:
|
|
228
|
-
if "=" in a and any(a.startswith(prefix) for prefix in ("ENVIRONMENT_API_KEY=", "OPENAI_API_KEY=", "SYNTH_API_KEY=")):
|
|
229
|
-
try:
|
|
230
|
-
key, value = a.split("=", 1)
|
|
231
|
-
tail = value[-5:] if len(value) >= 5 else value
|
|
232
|
-
masked.append(f"{key}=***{tail}")
|
|
233
|
-
except Exception:
|
|
234
|
-
masked.append("<masked>")
|
|
235
|
-
else:
|
|
236
|
-
masked.append(a)
|
|
237
|
-
return masked
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def _ensure_modal_secret(
|
|
241
|
-
secret_name: str,
|
|
242
|
-
*,
|
|
243
|
-
values: dict[str, str],
|
|
244
|
-
label: str = "deploy",
|
|
245
|
-
replace: bool = False,
|
|
246
|
-
) -> bool:
|
|
247
|
-
prefix = f"[{label}]"
|
|
248
|
-
if not secret_name.strip():
|
|
249
|
-
raise RuntimeError("Secret name is required")
|
|
250
|
-
|
|
251
|
-
if not values:
|
|
252
|
-
raise RuntimeError("No values provided to create Modal secret")
|
|
253
|
-
|
|
254
|
-
create_args = [f"{k}={v}" for k, v in values.items()]
|
|
255
|
-
create_cmd = ["uv", "run", "modal", "secret", "create", secret_name, *create_args]
|
|
256
|
-
|
|
257
|
-
if replace:
|
|
258
|
-
print(f"{prefix} Removing Modal secret '{secret_name}' (if present)…")
|
|
259
|
-
delete_cmd = ["bash", "-lc", f"printf 'y\\n' | uv run modal secret delete {secret_name}"]
|
|
260
|
-
print(f"{prefix} Command:", " ".join(delete_cmd))
|
|
261
|
-
delete_code = _popen_stream(delete_cmd)
|
|
262
|
-
if delete_code != 0:
|
|
263
|
-
print(f"{prefix} Warning: delete command exited with {delete_code}; continuing to create")
|
|
264
|
-
|
|
265
|
-
print(f"\n{prefix} Creating Modal secret '{secret_name}'…")
|
|
266
|
-
print(f"{prefix} Command:", " ".join(_mask_secret_args(create_cmd)))
|
|
267
|
-
code = _popen_stream(create_cmd)
|
|
268
|
-
if code != 0:
|
|
269
|
-
raise RuntimeError("Failed to provision Modal secret (see logs above)")
|
|
270
|
-
|
|
271
|
-
return True
|
|
272
|
-
|
|
273
|
-
|
|
274
290
|
def _fmt_float(value: float) -> str:
|
|
275
291
|
return f"{value:.10g}"
|
|
276
292
|
|
|
@@ -283,7 +299,19 @@ def _find_asgi_apps(root: Path) -> list[Path]:
|
|
|
283
299
|
- "@modal.asgi_app()"
|
|
284
300
|
"""
|
|
285
301
|
results: list[Path] = []
|
|
286
|
-
skip_dirs = {
|
|
302
|
+
skip_dirs = {
|
|
303
|
+
".git",
|
|
304
|
+
".hg",
|
|
305
|
+
".svn",
|
|
306
|
+
"node_modules",
|
|
307
|
+
"dist",
|
|
308
|
+
"build",
|
|
309
|
+
"__pycache__",
|
|
310
|
+
".ruff_cache",
|
|
311
|
+
".mypy_cache",
|
|
312
|
+
"venv",
|
|
313
|
+
".venv",
|
|
314
|
+
}
|
|
287
315
|
for dirpath, dirnames, filenames in os.walk(root):
|
|
288
316
|
dirnames[:] = [d for d in dirnames if d not in skip_dirs]
|
|
289
317
|
for name in filenames:
|
|
@@ -297,16 +325,20 @@ def _find_asgi_apps(root: Path) -> list[Path]:
|
|
|
297
325
|
results.append(path)
|
|
298
326
|
except Exception:
|
|
299
327
|
continue
|
|
328
|
+
|
|
300
329
|
# Stable order: prioritize files under synth_demo/ first, then alphabetical
|
|
301
330
|
def _priority(p: Path) -> tuple[int, str]:
|
|
302
331
|
rel = str(p.resolve())
|
|
303
332
|
in_demo = "/synth_demo/" in rel or rel.endswith("/synth_demo/task_app.py")
|
|
304
333
|
return (0 if in_demo else 1, rel)
|
|
334
|
+
|
|
305
335
|
results.sort(key=_priority)
|
|
306
336
|
return results
|
|
307
337
|
|
|
308
338
|
|
|
309
|
-
def _prompt_value(
|
|
339
|
+
def _prompt_value(
|
|
340
|
+
label: str, default: str | int | float, cast: Callable[[str], Any] | None = None
|
|
341
|
+
) -> Any:
|
|
310
342
|
prompt = f"{label} [{default}]: "
|
|
311
343
|
try:
|
|
312
344
|
raw = input(prompt).strip()
|
|
@@ -325,7 +357,19 @@ def _prompt_value(label: str, default: str | int | float, cast: Callable[[str],
|
|
|
325
357
|
|
|
326
358
|
def _find_vllm_tomls(root: Path) -> list[Path]:
|
|
327
359
|
results: list[Path] = []
|
|
328
|
-
skip_dirs = {
|
|
360
|
+
skip_dirs = {
|
|
361
|
+
".git",
|
|
362
|
+
".hg",
|
|
363
|
+
".svn",
|
|
364
|
+
"node_modules",
|
|
365
|
+
"dist",
|
|
366
|
+
"build",
|
|
367
|
+
"__pycache__",
|
|
368
|
+
".ruff_cache",
|
|
369
|
+
".mypy_cache",
|
|
370
|
+
"venv",
|
|
371
|
+
".venv",
|
|
372
|
+
}
|
|
329
373
|
for dirpath, dirnames, filenames in os.walk(root):
|
|
330
374
|
dirnames[:] = [d for d in dirnames if d not in skip_dirs]
|
|
331
375
|
for name in filenames:
|
|
@@ -345,7 +389,9 @@ def _create_new_config(env: DemoEnv) -> str:
|
|
|
345
389
|
default_path = os.path.join(os.getcwd(), "demo_config.toml")
|
|
346
390
|
while True:
|
|
347
391
|
try:
|
|
348
|
-
destination =
|
|
392
|
+
destination = (
|
|
393
|
+
input(f"Path to save new config [{default_path}]: ").strip() or default_path
|
|
394
|
+
)
|
|
349
395
|
except Exception:
|
|
350
396
|
destination = default_path
|
|
351
397
|
destination = os.path.abspath(destination)
|
|
@@ -354,7 +400,9 @@ def _create_new_config(env: DemoEnv) -> str:
|
|
|
354
400
|
continue
|
|
355
401
|
if os.path.exists(destination):
|
|
356
402
|
try:
|
|
357
|
-
overwrite =
|
|
403
|
+
overwrite = (
|
|
404
|
+
input(f"{destination} exists. Overwrite? [y/N]: ").strip().lower() or "n"
|
|
405
|
+
)
|
|
358
406
|
except Exception:
|
|
359
407
|
overwrite = "n"
|
|
360
408
|
if not overwrite.startswith("y"):
|
|
@@ -366,7 +414,9 @@ def _create_new_config(env: DemoEnv) -> str:
|
|
|
366
414
|
model_name = _prompt_value("Model name", "Qwen/Qwen3-0.6B")
|
|
367
415
|
compute_gpu_type = _prompt_value("Compute GPU type", "H100")
|
|
368
416
|
compute_gpu_count = _prompt_value("Compute GPU count", 4, int)
|
|
369
|
-
topology_gpu_type = _prompt_value(
|
|
417
|
+
topology_gpu_type = _prompt_value(
|
|
418
|
+
"Topology GPU type", f"{compute_gpu_type}:{compute_gpu_count}"
|
|
419
|
+
)
|
|
370
420
|
gpus_for_vllm = _prompt_value("Topology gpus_for_vllm", 2, int)
|
|
371
421
|
gpus_for_training = _prompt_value("Topology gpus_for_training", 1, int)
|
|
372
422
|
tensor_parallel = _prompt_value("Topology tensor_parallel", 2, int)
|
|
@@ -384,8 +434,9 @@ def _create_new_config(env: DemoEnv) -> str:
|
|
|
384
434
|
task_url_default = env.task_app_base_url or ""
|
|
385
435
|
services_task_url = _prompt_value("services.task_url", task_url_default)
|
|
386
436
|
|
|
387
|
-
template =
|
|
388
|
-
|
|
437
|
+
template = (
|
|
438
|
+
textwrap.dedent(
|
|
439
|
+
f"""\
|
|
389
440
|
# Crafter online RL training configuration (research local copy)
|
|
390
441
|
|
|
391
442
|
[model]
|
|
@@ -527,7 +578,9 @@ def _create_new_config(env: DemoEnv) -> str:
|
|
|
527
578
|
[services]
|
|
528
579
|
task_url = \"{services_task_url}\"
|
|
529
580
|
"""
|
|
530
|
-
|
|
581
|
+
).strip()
|
|
582
|
+
+ "\n"
|
|
583
|
+
)
|
|
531
584
|
|
|
532
585
|
with open(destination, "w", encoding="utf-8") as fh:
|
|
533
586
|
fh.write(template)
|
|
@@ -546,7 +599,11 @@ def _select_or_create_config(explicit: str | None, env: DemoEnv) -> str:
|
|
|
546
599
|
discovered = _find_vllm_tomls(search_root)
|
|
547
600
|
|
|
548
601
|
extras: list[Path] = []
|
|
549
|
-
packaged = Path(
|
|
602
|
+
packaged = Path(
|
|
603
|
+
os.path.abspath(
|
|
604
|
+
os.path.join(os.path.dirname(__file__), "..", "demo_task_apps", "math", "config.toml")
|
|
605
|
+
)
|
|
606
|
+
)
|
|
550
607
|
extras.append(packaged)
|
|
551
608
|
home_cfg = Path(os.path.expanduser("~/.synth-ai/demo_config.toml"))
|
|
552
609
|
extras.append(home_cfg)
|
|
@@ -592,29 +649,36 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
592
649
|
|
|
593
650
|
env_key = (env.env_api_key or "").strip()
|
|
594
651
|
if not env_key:
|
|
595
|
-
raise RuntimeError(
|
|
652
|
+
raise RuntimeError(
|
|
653
|
+
f"[{label}] ENVIRONMENT_API_KEY missing. Run `uvx synth-ai rl_demo deploy` first."
|
|
654
|
+
)
|
|
596
655
|
|
|
597
656
|
task_url = env.task_app_base_url
|
|
598
657
|
if not task_url or not _is_modal_public_url(task_url):
|
|
599
658
|
resolved = ""
|
|
600
659
|
if env.task_app_name:
|
|
601
660
|
try:
|
|
602
|
-
choice =
|
|
603
|
-
f"Resolve URL from Modal for app '{env.task_app_name}'? [Y/n]: "
|
|
604
|
-
|
|
661
|
+
choice = (
|
|
662
|
+
input(f"Resolve URL from Modal for app '{env.task_app_name}'? [Y/n]: ")
|
|
663
|
+
.strip()
|
|
664
|
+
.lower()
|
|
665
|
+
or "y"
|
|
666
|
+
)
|
|
605
667
|
except Exception:
|
|
606
668
|
choice = "y"
|
|
607
669
|
if choice.startswith("y"):
|
|
608
|
-
code, out = _popen_capture(
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
670
|
+
code, out = _popen_capture(
|
|
671
|
+
[
|
|
672
|
+
"uv",
|
|
673
|
+
"run",
|
|
674
|
+
"python",
|
|
675
|
+
"-m",
|
|
676
|
+
"modal",
|
|
677
|
+
"app",
|
|
678
|
+
"url",
|
|
679
|
+
env.task_app_name,
|
|
680
|
+
]
|
|
681
|
+
)
|
|
618
682
|
if code == 0 and out:
|
|
619
683
|
for tok in out.split():
|
|
620
684
|
if _is_modal_public_url(tok):
|
|
@@ -623,7 +687,9 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
623
687
|
if not resolved:
|
|
624
688
|
print(f"[{label}] Task app URL not configured or not a valid Modal public URL.")
|
|
625
689
|
print("Examples: https://<app-name>-fastapi-app.modal.run")
|
|
626
|
-
entered = input(
|
|
690
|
+
entered = input(
|
|
691
|
+
"Enter Task App base URL (must contain '.modal.run'), or press Enter to abort: "
|
|
692
|
+
).strip()
|
|
627
693
|
if not entered or not _is_modal_public_url(entered):
|
|
628
694
|
raise RuntimeError(f"[{label}] Valid Task App URL is required.")
|
|
629
695
|
task_url = entered.rstrip("/")
|
|
@@ -639,30 +705,26 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
639
705
|
app_name = fallback
|
|
640
706
|
demo_core.persist_task_url(task_url, name=app_name)
|
|
641
707
|
|
|
642
|
-
secret_name = env.task_app_secret_name.strip() or f"{app_name}-secret"
|
|
643
708
|
demo_core.persist_task_url(task_url, name=app_name)
|
|
644
|
-
demo_core.persist_dotenv_values(
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
709
|
+
demo_core.persist_dotenv_values(
|
|
710
|
+
{
|
|
711
|
+
"TASK_APP_BASE_URL": task_url,
|
|
712
|
+
"TASK_APP_NAME": app_name,
|
|
713
|
+
"TASK_APP_SECRET_NAME": DEFAULT_TASK_APP_SECRET_NAME,
|
|
714
|
+
}
|
|
715
|
+
)
|
|
649
716
|
|
|
650
|
-
openai_key = (os.environ.get("OPENAI_API_KEY") or local_env.get("OPENAI_API_KEY") or "").strip()
|
|
651
|
-
secret_values: dict[str, str] = {"ENVIRONMENT_API_KEY": env_key}
|
|
652
|
-
if openai_key:
|
|
653
|
-
secret_values["OPENAI_API_KEY"] = openai_key
|
|
654
717
|
if synth_key:
|
|
655
|
-
|
|
718
|
+
os.environ["SYNTH_API_KEY"] = synth_key
|
|
656
719
|
|
|
657
|
-
|
|
720
|
+
openai_key = (os.environ.get("OPENAI_API_KEY") or local_env.get("OPENAI_API_KEY") or "").strip()
|
|
721
|
+
if openai_key:
|
|
722
|
+
os.environ["OPENAI_API_KEY"] = openai_key
|
|
658
723
|
|
|
659
|
-
rollout_url = task_url.rstrip("/") + "/health/rollout"
|
|
660
724
|
print(f"[{label}] Verifying rollout health:")
|
|
661
725
|
try:
|
|
662
726
|
ek = (env_key or "").strip()
|
|
663
|
-
|
|
664
|
-
ek_tail = ek[-5:] if ek_len >= 5 else ek
|
|
665
|
-
print(f"[{label}] Using ENVIRONMENT_API_KEY len={ek_len} last5={ek_tail}")
|
|
727
|
+
print(f"[{label}] {_key_preview(ek, 'ENVIRONMENT_API_KEY')}")
|
|
666
728
|
except Exception:
|
|
667
729
|
pass
|
|
668
730
|
health_base = task_url.rstrip("/")
|
|
@@ -673,7 +735,6 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
673
735
|
print(f"[{label}] GET", h)
|
|
674
736
|
rc, body = _http("GET", h, headers={"X-API-Key": env_key})
|
|
675
737
|
if rc == 200:
|
|
676
|
-
rollout_url = h
|
|
677
738
|
break
|
|
678
739
|
print(f"[{label}] status: {rc}")
|
|
679
740
|
try:
|
|
@@ -685,41 +746,64 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
685
746
|
print(f"[{label}] body:", preview)
|
|
686
747
|
if rc != 200:
|
|
687
748
|
print(f"[{label}] Warning: rollout health check failed ({rc}). Response: {body}")
|
|
749
|
+
with contextlib.suppress(Exception):
|
|
750
|
+
print(f"[{label}] Sent header X-API-Key → {_key_preview(env_key, 'X-API-Key')}")
|
|
688
751
|
else:
|
|
689
752
|
print(f"[{label}] Task app rollout health check OK.")
|
|
690
753
|
|
|
691
754
|
os.environ["TASK_APP_BASE_URL"] = task_url
|
|
692
755
|
os.environ["ENVIRONMENT_API_KEY"] = env_key
|
|
756
|
+
os.environ["TASK_APP_SECRET_NAME"] = DEFAULT_TASK_APP_SECRET_NAME
|
|
693
757
|
updated_env = demo_core.load_env()
|
|
694
758
|
updated_env.env_api_key = env_key
|
|
695
759
|
updated_env.task_app_base_url = task_url
|
|
696
760
|
updated_env.task_app_name = app_name
|
|
697
|
-
updated_env.task_app_secret_name =
|
|
761
|
+
updated_env.task_app_secret_name = DEFAULT_TASK_APP_SECRET_NAME
|
|
698
762
|
return updated_env
|
|
699
763
|
|
|
700
764
|
|
|
701
|
-
def
|
|
765
|
+
def deploy(
|
|
766
|
+
local: bool = False, app: str | None = None, name: str | None = None, script: str | None = None
|
|
767
|
+
) -> int:
|
|
768
|
+
# Change to demo directory if stored
|
|
769
|
+
demo_dir = demo_core.load_demo_dir()
|
|
770
|
+
if demo_dir and os.path.isdir(demo_dir):
|
|
771
|
+
os.chdir(demo_dir)
|
|
772
|
+
print(f"Using demo directory: {demo_dir}")
|
|
773
|
+
|
|
702
774
|
env = demo_core.load_env()
|
|
775
|
+
os.environ["TASK_APP_SECRET_NAME"] = DEFAULT_TASK_APP_SECRET_NAME
|
|
703
776
|
cwd_env_path = os.path.join(os.getcwd(), ".env")
|
|
704
777
|
local_env = demo_core.load_dotenv_file(cwd_env_path)
|
|
705
778
|
url = ""
|
|
706
779
|
app_name = env.task_app_name or ""
|
|
707
780
|
try:
|
|
708
|
-
if
|
|
781
|
+
if local:
|
|
709
782
|
print("Starting local Task App…")
|
|
710
783
|
import subprocess
|
|
711
|
-
|
|
712
|
-
|
|
784
|
+
|
|
785
|
+
subprocess.Popen(
|
|
786
|
+
[
|
|
787
|
+
sys.executable,
|
|
788
|
+
"-c",
|
|
789
|
+
"from synth_ai.demos.demo_task_apps.math.app import run; run()",
|
|
790
|
+
],
|
|
791
|
+
env=get_subprocess_env(),
|
|
792
|
+
stdout=sys.stdout,
|
|
793
|
+
stderr=sys.stderr,
|
|
794
|
+
)
|
|
713
795
|
target = "http://127.0.0.1:8080"
|
|
714
796
|
app_name = ""
|
|
715
797
|
for _ in range(30):
|
|
716
|
-
if demo_core.assert_http_ok(
|
|
798
|
+
if demo_core.assert_http_ok(
|
|
799
|
+
target + "/health", method="GET"
|
|
800
|
+
) or demo_core.assert_http_ok(target, method="GET"):
|
|
717
801
|
url = target
|
|
718
802
|
break
|
|
719
803
|
time.sleep(1)
|
|
720
804
|
else:
|
|
721
805
|
# Auto-detect app path if not supplied; prompt interactively from discovered ASGI apps
|
|
722
|
-
app_path = os.path.abspath(
|
|
806
|
+
app_path = os.path.abspath(app) if app else None
|
|
723
807
|
if not app_path or not os.path.isfile(app_path):
|
|
724
808
|
# First pass: look for known common filenames
|
|
725
809
|
candidates = [
|
|
@@ -738,7 +822,9 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
738
822
|
rel = os.path.relpath(str(pth), os.getcwd())
|
|
739
823
|
print(f" [{idx}] {rel}")
|
|
740
824
|
try:
|
|
741
|
-
sel =
|
|
825
|
+
sel = (
|
|
826
|
+
input(f"Enter choice [1-{len(found)}] (default 1): ").strip() or "1"
|
|
827
|
+
)
|
|
742
828
|
except Exception:
|
|
743
829
|
sel = "1"
|
|
744
830
|
try:
|
|
@@ -747,12 +833,13 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
747
833
|
choice = 1
|
|
748
834
|
choice = max(1, min(choice, len(found)))
|
|
749
835
|
app_path = str(found[choice - 1].resolve())
|
|
750
|
-
if not app_path and
|
|
836
|
+
if not app_path and script:
|
|
751
837
|
# Legacy script fallback if user supplied --script explicitly
|
|
752
838
|
from synth_ai.demos.demo_task_apps.math.deploy_modal import deploy as modal_deploy
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
839
|
+
|
|
840
|
+
url = modal_deploy(script_path=script, env_api_key=env.env_api_key)
|
|
841
|
+
if name:
|
|
842
|
+
app_name = name
|
|
756
843
|
else:
|
|
757
844
|
if not app_path:
|
|
758
845
|
entered = input("Path to Modal app.py (e.g., ./task_app.py): ").strip()
|
|
@@ -763,7 +850,10 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
763
850
|
raise FileNotFoundError(f"App file not found: {app_path}")
|
|
764
851
|
# Surface the app path before asking for the name
|
|
765
852
|
print(f"Using task app: {app_path}")
|
|
766
|
-
|
|
853
|
+
existing_name = (name or env.task_app_name or "").strip()
|
|
854
|
+
if not existing_name:
|
|
855
|
+
existing_name = f"synth-{os.path.splitext(os.path.basename(app_path))[0]}"
|
|
856
|
+
suggested_name = existing_name
|
|
767
857
|
name_in = input(f"Modal app name [{suggested_name}]: ").strip() or suggested_name
|
|
768
858
|
app_name = name_in
|
|
769
859
|
print("\nAbout to deploy with:")
|
|
@@ -774,21 +864,23 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
774
864
|
print("Aborted by user.")
|
|
775
865
|
return 1
|
|
776
866
|
|
|
777
|
-
secret_name = (env.task_app_secret_name or "").strip() or f"{name_in}-secret"
|
|
778
867
|
existing_env_key = (env.env_api_key or "").strip()
|
|
779
868
|
env_key: str | None = existing_env_key or None
|
|
780
869
|
if existing_env_key:
|
|
781
870
|
try:
|
|
782
|
-
reuse_choice =
|
|
783
|
-
"Use existing ENVIRONMENT_API_KEY from state/.env? [Y/n]: "
|
|
784
|
-
|
|
871
|
+
reuse_choice = (
|
|
872
|
+
input("Use existing ENVIRONMENT_API_KEY from state/.env? [Y/n]: ")
|
|
873
|
+
.strip()
|
|
874
|
+
.lower()
|
|
875
|
+
or "y"
|
|
876
|
+
)
|
|
785
877
|
except Exception:
|
|
786
878
|
reuse_choice = "y"
|
|
787
879
|
if not reuse_choice.startswith("y"):
|
|
788
880
|
env_key = None
|
|
789
881
|
|
|
790
882
|
if env_key is None:
|
|
791
|
-
from synth_ai.rl.secrets import mint_environment_api_key
|
|
883
|
+
from synth_ai.learning.rl.secrets import mint_environment_api_key
|
|
792
884
|
|
|
793
885
|
env_key = mint_environment_api_key()
|
|
794
886
|
demo_core.persist_env_api_key(env_key)
|
|
@@ -797,69 +889,90 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
797
889
|
env.env_api_key = env_key
|
|
798
890
|
local_env["ENVIRONMENT_API_KEY"] = env_key
|
|
799
891
|
print("[deploy] Minted new ENVIRONMENT_API_KEY")
|
|
800
|
-
|
|
892
|
+
elif env_key:
|
|
893
|
+
os.environ["ENVIRONMENT_API_KEY"] = env_key
|
|
894
|
+
|
|
801
895
|
# Optionally upload the new key to the backend using sealed box helper
|
|
802
|
-
backend_base = env.dev_backend_url or ""
|
|
803
|
-
synth_key = (
|
|
896
|
+
backend_base = (env.dev_backend_url or "").rstrip("/")
|
|
897
|
+
synth_key = (
|
|
898
|
+
env.synth_api_key
|
|
899
|
+
or os.environ.get("SYNTH_API_KEY")
|
|
900
|
+
or local_env.get("SYNTH_API_KEY")
|
|
901
|
+
or ""
|
|
902
|
+
).strip()
|
|
804
903
|
if backend_base and synth_key:
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
904
|
+
# Pass a base WITHOUT trailing /api to setup_environment_api_key,
|
|
905
|
+
# since it appends /api/v1/... internally.
|
|
906
|
+
non_api_base = (
|
|
907
|
+
backend_base[:-4] if backend_base.endswith("/api") else backend_base
|
|
908
|
+
)
|
|
909
|
+
try:
|
|
910
|
+
choice = (
|
|
911
|
+
input(f"Upload ENVIRONMENT_API_KEY to backend {non_api_base}? [Y/n]: ")
|
|
912
|
+
.strip()
|
|
913
|
+
.lower()
|
|
914
|
+
or "y"
|
|
915
|
+
)
|
|
916
|
+
except Exception:
|
|
917
|
+
choice = "y"
|
|
918
|
+
if choice.startswith("y"):
|
|
808
919
|
try:
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
from synth_ai.rl.env_keys import setup_environment_api_key
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
synth_key = (env.synth_api_key or os.environ.get("SYNTH_API_KEY") or local_env.get("SYNTH_API_KEY") or "").strip()
|
|
920
|
+
print(f"[deploy] Uploading ENVIRONMENT_API_KEY to {non_api_base} …")
|
|
921
|
+
from synth_ai.learning.rl.env_keys import setup_environment_api_key
|
|
922
|
+
|
|
923
|
+
setup_environment_api_key(non_api_base, synth_key, token=env_key)
|
|
924
|
+
print("[deploy] Backend sealed-box upload complete.")
|
|
925
|
+
except Exception as upload_err:
|
|
926
|
+
print(f"[deploy] Failed to upload ENVIRONMENT_API_KEY: {upload_err}")
|
|
927
|
+
print(
|
|
928
|
+
'Hint: run `uvx python -c "from synth_ai.learning.rl.env_keys import setup_environment_api_key as s;'
|
|
929
|
+
" s('<backend>', '<synth_api_key>')\"` once the backend is reachable."
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
synth_key = (
|
|
933
|
+
env.synth_api_key
|
|
934
|
+
or os.environ.get("SYNTH_API_KEY")
|
|
935
|
+
or local_env.get("SYNTH_API_KEY")
|
|
936
|
+
or ""
|
|
937
|
+
).strip()
|
|
829
938
|
if not synth_key:
|
|
830
|
-
synth_key = input("Enter SYNTH_API_KEY for
|
|
939
|
+
synth_key = input("Enter SYNTH_API_KEY for deployment (required): ").strip()
|
|
831
940
|
if not synth_key:
|
|
832
|
-
print("SYNTH_API_KEY is required
|
|
941
|
+
print("SYNTH_API_KEY is required for deployment.")
|
|
833
942
|
return 1
|
|
834
943
|
demo_core.persist_api_key(synth_key)
|
|
835
944
|
demo_core.persist_dotenv_values({"SYNTH_API_KEY": synth_key})
|
|
836
945
|
env.synth_api_key = synth_key
|
|
946
|
+
os.environ["SYNTH_API_KEY"] = synth_key
|
|
837
947
|
|
|
838
|
-
openai_key = (
|
|
948
|
+
openai_key = (
|
|
949
|
+
os.environ.get("OPENAI_API_KEY") or local_env.get("OPENAI_API_KEY") or ""
|
|
950
|
+
).strip()
|
|
839
951
|
if not openai_key:
|
|
840
952
|
openai_key = input(
|
|
841
953
|
"Enter your OpenAI API key, found at https://platform.openai.com/api-keys\n> "
|
|
842
954
|
).strip()
|
|
843
955
|
if not openai_key:
|
|
844
|
-
print("OPENAI_API_KEY is required
|
|
956
|
+
print("OPENAI_API_KEY is required for deployment.")
|
|
845
957
|
return 1
|
|
846
958
|
demo_core.persist_dotenv_values({"OPENAI_API_KEY": openai_key})
|
|
847
959
|
local_env["OPENAI_API_KEY"] = openai_key
|
|
960
|
+
os.environ["OPENAI_API_KEY"] = openai_key
|
|
848
961
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
962
|
+
deploy_cmd = [
|
|
963
|
+
"uv",
|
|
964
|
+
"run",
|
|
965
|
+
"python",
|
|
966
|
+
"-m",
|
|
967
|
+
"modal",
|
|
968
|
+
"deploy",
|
|
969
|
+
"--name",
|
|
970
|
+
name_in,
|
|
971
|
+
app_path,
|
|
972
|
+
]
|
|
973
|
+
print(
|
|
974
|
+
"\nStreaming Modal build/deploy logs (this can take several minutes on first run)…\n"
|
|
975
|
+
)
|
|
863
976
|
code, deploy_logs = _popen_stream_capture(deploy_cmd)
|
|
864
977
|
if code != 0:
|
|
865
978
|
raise RuntimeError(f"modal deploy failed (exit {code})")
|
|
@@ -867,6 +980,7 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
867
980
|
if not url:
|
|
868
981
|
try:
|
|
869
982
|
import re as _re
|
|
983
|
+
|
|
870
984
|
m_all = _re.findall(r"https?://[^\s]+\.modal\.run", deploy_logs or "")
|
|
871
985
|
if m_all:
|
|
872
986
|
url = m_all[-1].strip().rstrip("/")
|
|
@@ -881,7 +995,9 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
881
995
|
break
|
|
882
996
|
# Fallback: try reading recent Modal logs for the app to find a URL line
|
|
883
997
|
if not url:
|
|
884
|
-
code3, out3 = _popen_capture(
|
|
998
|
+
code3, out3 = _popen_capture(
|
|
999
|
+
["uv", "run", "python", "-m", "modal", "app", "list"]
|
|
1000
|
+
)
|
|
885
1001
|
if code3 == 0 and out3:
|
|
886
1002
|
for line in out3.splitlines():
|
|
887
1003
|
if name_in in line:
|
|
@@ -894,7 +1010,9 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
894
1010
|
# Prompt user if still no valid URL
|
|
895
1011
|
if not url:
|
|
896
1012
|
print("\nCould not auto-detect a public Modal URL for the app.")
|
|
897
|
-
entered = input(
|
|
1013
|
+
entered = input(
|
|
1014
|
+
"Enter the Modal public URL (must contain '.modal.run'), or press Enter to abort: "
|
|
1015
|
+
).strip()
|
|
898
1016
|
if entered and _is_modal_public_url(entered):
|
|
899
1017
|
url = entered.rstrip("/")
|
|
900
1018
|
if not url:
|
|
@@ -906,7 +1024,7 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
906
1024
|
dotenv_values = {"TASK_APP_BASE_URL": url}
|
|
907
1025
|
if app_name:
|
|
908
1026
|
dotenv_values["TASK_APP_NAME"] = app_name
|
|
909
|
-
|
|
1027
|
+
dotenv_values["TASK_APP_SECRET_NAME"] = DEFAULT_TASK_APP_SECRET_NAME
|
|
910
1028
|
dotenv_path = demo_core.persist_dotenv_values(dotenv_values)
|
|
911
1029
|
print(f"TASK_APP_BASE_URL={url}")
|
|
912
1030
|
if app_name:
|
|
@@ -915,16 +1033,16 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
915
1033
|
print(f" export TASK_APP_BASE_URL={url}")
|
|
916
1034
|
if app_name:
|
|
917
1035
|
print(f" export TASK_APP_NAME={app_name}")
|
|
918
|
-
print(f" export TASK_APP_SECRET_NAME={app_name}-secret")
|
|
919
1036
|
print(f"Persisted to {dotenv_path}")
|
|
920
|
-
print("
|
|
1037
|
+
print("\nNext step:\n$ uvx synth-ai run")
|
|
921
1038
|
return 0
|
|
922
1039
|
except Exception as e:
|
|
923
1040
|
print(f"Deploy error: {e}")
|
|
924
1041
|
return 2
|
|
925
1042
|
|
|
926
|
-
|
|
927
|
-
|
|
1043
|
+
print(
|
|
1044
|
+
"`rl_demo configure` prepares environment and secrets; `synth-ai run` now handles launches."
|
|
1045
|
+
)
|
|
928
1046
|
env = demo_core.load_env()
|
|
929
1047
|
synth_key = (env.synth_api_key or "").strip()
|
|
930
1048
|
if not synth_key:
|
|
@@ -956,133 +1074,314 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
956
1074
|
return 0
|
|
957
1075
|
|
|
958
1076
|
|
|
959
|
-
def
|
|
960
|
-
"""
|
|
1077
|
+
def _ensure_modal_installed() -> None:
|
|
1078
|
+
"""Install the modal package if it is not already available and check authentication."""
|
|
961
1079
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
"""
|
|
1080
|
+
# Check if modal is installed
|
|
1081
|
+
modal_installed = False
|
|
965
1082
|
try:
|
|
966
|
-
|
|
967
|
-
def _has_modal() -> bool:
|
|
968
|
-
try:
|
|
969
|
-
import importlib.util as _iu
|
|
970
|
-
return _iu.find_spec("modal") is not None
|
|
971
|
-
except Exception:
|
|
972
|
-
return False
|
|
1083
|
+
import importlib.util as _iu
|
|
973
1084
|
|
|
974
|
-
if not
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
print(
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
dst_deploy = os.path.join(demo_dir, "deploy_task_app.sh")
|
|
1001
|
-
env_path = os.path.join(demo_dir, ".env")
|
|
1002
|
-
dst_cfg = os.path.join(demo_dir, "demo_config.toml")
|
|
1003
|
-
|
|
1004
|
-
# Copy packaged math modal task app into synth_demo/task_app.py
|
|
1005
|
-
src_modal = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "demo_task_apps", "math", "modal_task_app.py"))
|
|
1006
|
-
if not os.path.isfile(src_modal):
|
|
1007
|
-
print("Init failed: packaged math modal task app not found.")
|
|
1008
|
-
print(f"Looked for: {src_modal}")
|
|
1009
|
-
return 1
|
|
1010
|
-
if os.path.exists(dst_task_py) and not getattr(args, "force", False):
|
|
1011
|
-
print(f"Refusing to overwrite existing file: {dst_task_py} (use --force)")
|
|
1012
|
-
return 1
|
|
1013
|
-
shutil.copy2(src_modal, dst_task_py)
|
|
1014
|
-
|
|
1015
|
-
# Create deploy script in synth_demo/
|
|
1016
|
-
deploy_text = r"""#!/usr/bin/env bash
|
|
1017
|
-
set -euo pipefail
|
|
1018
|
-
|
|
1019
|
-
HERE=$(cd "$(dirname "$0")" && pwd)
|
|
1020
|
-
APP="$HERE/task_app.py"
|
|
1021
|
-
if [ -f "$HERE/.env" ]; then
|
|
1022
|
-
# shellcheck disable=SC2046
|
|
1023
|
-
export $(grep -v '^#' "$HERE/.env" | xargs -I{} echo {})
|
|
1024
|
-
fi
|
|
1025
|
-
uv run modal deploy "$APP" | tee "$HERE/.last_deploy.log"
|
|
1026
|
-
URL=$(grep -Eo 'https://[^ ]+\.modal\.run' "$HERE/.last_deploy.log" | tail -1 || true)
|
|
1027
|
-
if [ -n "$URL" ]; then
|
|
1028
|
-
if grep -q '^TASK_APP_BASE_URL=' "$HERE/.env" 2>/dev/null; then
|
|
1029
|
-
sed -i.bak "s#^TASK_APP_BASE_URL=.*#TASK_APP_BASE_URL=$URL#" "$HERE/.env" || true
|
|
1030
|
-
else
|
|
1031
|
-
echo "TASK_APP_BASE_URL=$URL" >> "$HERE/.env"
|
|
1032
|
-
fi
|
|
1033
|
-
echo "Saved TASK_APP_BASE_URL to $HERE/.env"
|
|
1034
|
-
fi
|
|
1035
|
-
"""
|
|
1036
|
-
_write_text(dst_deploy, deploy_text)
|
|
1085
|
+
if _iu.find_spec("modal") is not None:
|
|
1086
|
+
modal_installed = True
|
|
1087
|
+
except Exception:
|
|
1088
|
+
pass
|
|
1089
|
+
|
|
1090
|
+
# Install modal if needed
|
|
1091
|
+
if not modal_installed:
|
|
1092
|
+
print("modal not found; installing…")
|
|
1093
|
+
try:
|
|
1094
|
+
if shutil.which("uv"):
|
|
1095
|
+
code, out = _popen_capture(["uv", "pip", "install", "modal>=1.1.4"])
|
|
1096
|
+
else:
|
|
1097
|
+
code, out = _popen_capture([sys.executable, "-m", "pip", "install", "modal>=1.1.4"])
|
|
1098
|
+
if code != 0:
|
|
1099
|
+
print(out)
|
|
1100
|
+
print("Failed to install modal; continuing may fail.")
|
|
1101
|
+
return
|
|
1102
|
+
else:
|
|
1103
|
+
print("✓ modal installed successfully")
|
|
1104
|
+
modal_installed = True
|
|
1105
|
+
except Exception as exc:
|
|
1106
|
+
print(f"modal install error: {exc}")
|
|
1107
|
+
return
|
|
1108
|
+
|
|
1109
|
+
# Verify modal is importable
|
|
1110
|
+
if modal_installed:
|
|
1037
1111
|
try:
|
|
1038
|
-
|
|
1039
|
-
|
|
1112
|
+
import importlib.util as _iu
|
|
1113
|
+
|
|
1114
|
+
if _iu.find_spec("modal") is None:
|
|
1115
|
+
print("Warning: modal is still not importable after install attempt.")
|
|
1116
|
+
return
|
|
1040
1117
|
except Exception:
|
|
1041
|
-
|
|
1118
|
+
print("Warning: unable to verify modal installation.")
|
|
1119
|
+
return
|
|
1120
|
+
|
|
1121
|
+
# Check modal authentication status
|
|
1122
|
+
auth_ok, auth_msg = demo_core.modal_auth_status()
|
|
1123
|
+
if auth_ok:
|
|
1124
|
+
print(f"✓ Modal authenticated: {auth_msg}")
|
|
1125
|
+
else:
|
|
1126
|
+
print("\n⚠️ Modal authentication required")
|
|
1127
|
+
print(f" Status: {auth_msg}")
|
|
1128
|
+
print("\n To authenticate Modal, run:")
|
|
1129
|
+
print(" modal setup")
|
|
1130
|
+
print("\n Or set environment variables:")
|
|
1131
|
+
print(" export MODAL_TOKEN_ID=your-token-id")
|
|
1132
|
+
print(" export MODAL_TOKEN_SECRET=your-token-secret")
|
|
1133
|
+
print("\n You can deploy later after authenticating.\n")
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
def init(template: str | None = None, dest: str | None = None, force: bool = False) -> int:
|
|
1137
|
+
"""Materialise a demo task app template into the current directory."""
|
|
1138
|
+
|
|
1139
|
+
templates = list(list_demo_templates())
|
|
1140
|
+
if not templates:
|
|
1141
|
+
print("No demo templates registered. Update synth_ai/demo_registry.py to add entries.")
|
|
1142
|
+
return 1
|
|
1143
|
+
|
|
1144
|
+
selected: DemoTemplate | None = None
|
|
1145
|
+
if template:
|
|
1146
|
+
selected = get_demo_template(template)
|
|
1147
|
+
if selected is None:
|
|
1148
|
+
available = ", ".join(t.template_id for t in templates)
|
|
1149
|
+
print(f"Unknown template '{template}'. Available: {available}")
|
|
1150
|
+
return 1
|
|
1151
|
+
else:
|
|
1152
|
+
if force:
|
|
1153
|
+
selected = templates[0]
|
|
1154
|
+
print(
|
|
1155
|
+
f"Using default template: {selected.name} ({selected.template_id}) "
|
|
1156
|
+
f"(pass --template to choose another)"
|
|
1157
|
+
)
|
|
1158
|
+
else:
|
|
1159
|
+
print("Select a demo template:" + "\n")
|
|
1160
|
+
for idx, tpl in enumerate(templates, start=1):
|
|
1161
|
+
print(f" [{idx}] {tpl.name} ({tpl.template_id})")
|
|
1162
|
+
print(f" {tpl.description}")
|
|
1163
|
+
try:
|
|
1164
|
+
choice_raw = input(f"Enter choice [1-{len(templates)}] (default 1): ").strip() or "1"
|
|
1165
|
+
except Exception:
|
|
1166
|
+
choice_raw = "1"
|
|
1167
|
+
if not choice_raw.isdigit():
|
|
1168
|
+
print("Selection must be a number.")
|
|
1169
|
+
return 1
|
|
1170
|
+
choice_idx = int(choice_raw)
|
|
1171
|
+
if not 1 <= choice_idx <= len(templates):
|
|
1172
|
+
print("Selection out of range.")
|
|
1173
|
+
return 1
|
|
1174
|
+
selected = templates[choice_idx - 1]
|
|
1175
|
+
|
|
1176
|
+
assert selected is not None
|
|
1177
|
+
|
|
1178
|
+
default_subdir = selected.default_subdir or selected.template_id
|
|
1179
|
+
|
|
1180
|
+
# Check if default destination is already occupied and switch to local_demos/ if needed
|
|
1181
|
+
if dest:
|
|
1182
|
+
default_dest = Path(dest).expanduser().resolve()
|
|
1183
|
+
else:
|
|
1184
|
+
primary_dest = Path.cwd() / default_subdir
|
|
1185
|
+
if primary_dest.exists() and any(primary_dest.iterdir()):
|
|
1186
|
+
# Switch to local_demos/ automatically if primary location is occupied
|
|
1187
|
+
default_dest = (Path.cwd() / "local_demos" / default_subdir).resolve()
|
|
1188
|
+
else:
|
|
1189
|
+
default_dest = primary_dest.resolve()
|
|
1042
1190
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
"# Required for task app auth to environment service",
|
|
1047
|
-
"ENVIRONMENT_API_KEY=",
|
|
1048
|
-
"",
|
|
1049
|
-
"# Optional: for CLI job submission and proxying OpenAI models",
|
|
1050
|
-
"SYNTH_API_KEY=",
|
|
1051
|
-
"OPENAI_API_KEY=",
|
|
1052
|
-
"",
|
|
1053
|
-
"# Optional: set to 'prod' to use production names",
|
|
1054
|
-
"ENVIRONMENT=",
|
|
1055
|
-
]) + "\n")
|
|
1056
|
-
|
|
1057
|
-
# Seed demo_config.toml from packaged default if not present (or overwrite with --force)
|
|
1058
|
-
packaged_cfg = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "demo_task_apps", "math", "config.toml"))
|
|
1191
|
+
if force:
|
|
1192
|
+
dest_input = ""
|
|
1193
|
+
else:
|
|
1059
1194
|
try:
|
|
1060
|
-
|
|
1061
|
-
if not os.path.exists(dst_cfg) or getattr(args, "force", False):
|
|
1062
|
-
shutil.copy2(packaged_cfg, dst_cfg)
|
|
1195
|
+
dest_input = input(f"Destination directory [{default_dest}]: ").strip()
|
|
1063
1196
|
except Exception:
|
|
1064
|
-
|
|
1197
|
+
dest_input = ""
|
|
1198
|
+
destination = Path(dest_input).expanduser().resolve() if dest_input else default_dest
|
|
1199
|
+
|
|
1200
|
+
# Track whether we should skip individual file prompts (if we already cleared the directory)
|
|
1201
|
+
directory_cleared = False
|
|
1202
|
+
|
|
1203
|
+
if destination.exists():
|
|
1204
|
+
if destination.is_file():
|
|
1205
|
+
print(f"Destination {destination} is a file. Provide a directory path.")
|
|
1206
|
+
return 1
|
|
1207
|
+
if any(destination.iterdir()):
|
|
1208
|
+
if force:
|
|
1209
|
+
response = "y"
|
|
1210
|
+
else:
|
|
1211
|
+
try:
|
|
1212
|
+
response = (
|
|
1213
|
+
input(f"Destination {destination} is not empty. Overwrite? [y/N]: ")
|
|
1214
|
+
.strip()
|
|
1215
|
+
.lower()
|
|
1216
|
+
)
|
|
1217
|
+
except (EOFError, KeyboardInterrupt):
|
|
1218
|
+
print("\nCancelled.")
|
|
1219
|
+
return 1
|
|
1220
|
+
if response not in ("y", "yes"):
|
|
1221
|
+
print("Cancelled. Choose another directory or delete the existing one.")
|
|
1222
|
+
return 1
|
|
1223
|
+
# User agreed to overwrite - clear the entire directory including hidden files
|
|
1224
|
+
print(f"Clearing {destination}...")
|
|
1225
|
+
try:
|
|
1226
|
+
# Remove all contents including hidden files (.env, .git, etc.)
|
|
1227
|
+
shutil.rmtree(destination)
|
|
1228
|
+
except Exception as e:
|
|
1229
|
+
print(f"Error clearing directory: {e}")
|
|
1230
|
+
print("Please manually remove the directory and try again.")
|
|
1231
|
+
return 1
|
|
1232
|
+
# Recreate empty directory
|
|
1233
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
1234
|
+
# Verify it's actually empty
|
|
1235
|
+
if any(destination.iterdir()):
|
|
1236
|
+
print(f"Warning: Directory {destination} still contains files after clearing.")
|
|
1237
|
+
print("Some files may not have been removed. Please check manually.")
|
|
1238
|
+
return 1
|
|
1239
|
+
directory_cleared = True
|
|
1240
|
+
else:
|
|
1241
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
1242
|
+
|
|
1243
|
+
if selected.requires_modal:
|
|
1244
|
+
_ensure_modal_installed()
|
|
1245
|
+
|
|
1246
|
+
try:
|
|
1247
|
+
for spec in selected.iter_copy_specs():
|
|
1248
|
+
src_path = spec.absolute_source()
|
|
1249
|
+
if not src_path.exists():
|
|
1250
|
+
print(f"Template source missing: {src_path}")
|
|
1251
|
+
return 1
|
|
1252
|
+
dest_path = (destination / spec.destination).resolve()
|
|
1253
|
+
|
|
1254
|
+
# Handle directory copying
|
|
1255
|
+
if src_path.is_dir():
|
|
1256
|
+
if dest_path.exists() and not directory_cleared:
|
|
1257
|
+
if force:
|
|
1258
|
+
response = "y"
|
|
1259
|
+
else:
|
|
1260
|
+
try:
|
|
1261
|
+
response = (
|
|
1262
|
+
input(f"Directory {dest_path.name} exists. Overwrite? [y/N]: ")
|
|
1263
|
+
.strip()
|
|
1264
|
+
.lower()
|
|
1265
|
+
)
|
|
1266
|
+
except (EOFError, KeyboardInterrupt):
|
|
1267
|
+
print("\nCancelled.")
|
|
1268
|
+
return 1
|
|
1269
|
+
if response not in ("y", "yes"):
|
|
1270
|
+
print(f"Skipping {dest_path.name}")
|
|
1271
|
+
continue
|
|
1272
|
+
shutil.rmtree(dest_path)
|
|
1273
|
+
elif dest_path.exists() and directory_cleared:
|
|
1274
|
+
shutil.rmtree(dest_path)
|
|
1275
|
+
shutil.copytree(src_path, dest_path)
|
|
1276
|
+
else:
|
|
1277
|
+
# Handle file copying
|
|
1278
|
+
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1279
|
+
if dest_path.exists() and not directory_cleared:
|
|
1280
|
+
if force:
|
|
1281
|
+
response = "y"
|
|
1282
|
+
else:
|
|
1283
|
+
try:
|
|
1284
|
+
response = (
|
|
1285
|
+
input(f"File {dest_path.name} exists. Overwrite? [y/N]: ")
|
|
1286
|
+
.strip()
|
|
1287
|
+
.lower()
|
|
1288
|
+
)
|
|
1289
|
+
except (EOFError, KeyboardInterrupt):
|
|
1290
|
+
print("\nCancelled.")
|
|
1291
|
+
return 1
|
|
1292
|
+
if response not in ("y", "yes"):
|
|
1293
|
+
print(f"Skipping {dest_path.name}")
|
|
1294
|
+
continue
|
|
1295
|
+
shutil.copy2(src_path, dest_path)
|
|
1296
|
+
if spec.make_executable:
|
|
1297
|
+
try:
|
|
1298
|
+
st = os.stat(dest_path)
|
|
1299
|
+
os.chmod(dest_path, st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
1300
|
+
except Exception:
|
|
1301
|
+
pass
|
|
1065
1302
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1303
|
+
if selected.env_lines:
|
|
1304
|
+
env_path = destination / ".env"
|
|
1305
|
+
should_write = True
|
|
1306
|
+
if env_path.exists() and not directory_cleared:
|
|
1307
|
+
if force:
|
|
1308
|
+
response = "y"
|
|
1309
|
+
else:
|
|
1310
|
+
try:
|
|
1311
|
+
response = input("File .env exists. Overwrite? [y/N]: ").strip().lower()
|
|
1312
|
+
except (EOFError, KeyboardInterrupt):
|
|
1313
|
+
print("\nCancelled.")
|
|
1314
|
+
return 1
|
|
1315
|
+
should_write = response in ("y", "yes")
|
|
1316
|
+
if should_write:
|
|
1317
|
+
_write_text(env_path, "\n".join(selected.env_lines) + "\n")
|
|
1318
|
+
elif not directory_cleared:
|
|
1319
|
+
print("Skipping .env")
|
|
1320
|
+
|
|
1321
|
+
config_src = selected.config_source_path()
|
|
1322
|
+
if config_src and config_src.exists():
|
|
1323
|
+
cfg_dst = (destination / selected.config_destination).resolve()
|
|
1324
|
+
should_copy = True
|
|
1325
|
+
if cfg_dst.exists() and not directory_cleared:
|
|
1326
|
+
if force:
|
|
1327
|
+
response = "y"
|
|
1328
|
+
else:
|
|
1329
|
+
try:
|
|
1330
|
+
response = (
|
|
1331
|
+
input(f"File {cfg_dst.name} exists. Overwrite? [y/N]: ").strip().lower()
|
|
1332
|
+
)
|
|
1333
|
+
except (EOFError, KeyboardInterrupt):
|
|
1334
|
+
print("\nCancelled.")
|
|
1335
|
+
return 1
|
|
1336
|
+
should_copy = response in ("y", "yes")
|
|
1337
|
+
if should_copy:
|
|
1338
|
+
cfg_dst.parent.mkdir(parents=True, exist_ok=True)
|
|
1339
|
+
shutil.copy2(config_src, cfg_dst)
|
|
1340
|
+
elif not directory_cleared:
|
|
1341
|
+
print(f"Skipping {cfg_dst.name}")
|
|
1342
|
+
|
|
1343
|
+
if selected.post_copy is not None:
|
|
1344
|
+
try:
|
|
1345
|
+
selected.post_copy(destination)
|
|
1346
|
+
except Exception as post_exc:
|
|
1347
|
+
print(f"Post-processing failed: {post_exc}")
|
|
1348
|
+
return 1
|
|
1349
|
+
|
|
1350
|
+
# Store demo directory for subsequent commands
|
|
1351
|
+
demo_core.persist_demo_dir(str(destination))
|
|
1352
|
+
|
|
1353
|
+
# Store .env path if it was created
|
|
1354
|
+
env_file = destination / ".env"
|
|
1355
|
+
if env_file.exists():
|
|
1356
|
+
demo_core.persist_env_file_path(str(env_file))
|
|
1357
|
+
|
|
1358
|
+
print(f"Demo template '{selected.name}' materialised at {destination}.")
|
|
1359
|
+
print("Files created:")
|
|
1360
|
+
for spec in selected.iter_copy_specs():
|
|
1361
|
+
print(f" - {spec.destination}")
|
|
1362
|
+
if selected.env_lines:
|
|
1363
|
+
print(" - .env")
|
|
1364
|
+
if selected.config_source_path():
|
|
1365
|
+
print(f" - {selected.config_destination}")
|
|
1366
|
+
print("\nDemo directory stored. Subsequent commands will use this directory automatically.")
|
|
1367
|
+
print("Review the files, edit .env, and run any provided deploy scripts when ready.")
|
|
1078
1368
|
return 0
|
|
1079
|
-
except
|
|
1080
|
-
print(
|
|
1081
|
-
return
|
|
1369
|
+
except KeyboardInterrupt:
|
|
1370
|
+
print("Aborted")
|
|
1371
|
+
return 1
|
|
1372
|
+
except Exception as exc:
|
|
1373
|
+
print(f"Init failed: {exc}")
|
|
1374
|
+
return 1
|
|
1082
1375
|
|
|
1083
1376
|
|
|
1084
|
-
def _http(
|
|
1085
|
-
|
|
1377
|
+
def _http(
|
|
1378
|
+
method: str, url: str, headers: dict[str, str] | None = None, body: dict[str, Any] | None = None
|
|
1379
|
+
) -> tuple[int, dict[str, Any] | str]:
|
|
1380
|
+
import json as _json
|
|
1381
|
+
import ssl
|
|
1382
|
+
import urllib.error
|
|
1383
|
+
import urllib.request
|
|
1384
|
+
|
|
1086
1385
|
data = None
|
|
1087
1386
|
if body is not None:
|
|
1088
1387
|
data = _json.dumps(body).encode("utf-8")
|
|
@@ -1119,10 +1418,23 @@ def _write_text(path: str, content: str) -> None:
|
|
|
1119
1418
|
# Note: `prepare` command has been removed; configuration now prepares TOML
|
|
1120
1419
|
|
|
1121
1420
|
|
|
1122
|
-
def
|
|
1421
|
+
def run(
|
|
1422
|
+
config: str | None = None,
|
|
1423
|
+
batch_size: int | None = None,
|
|
1424
|
+
group_size: int | None = None,
|
|
1425
|
+
model: str | None = None,
|
|
1426
|
+
timeout: int = 600,
|
|
1427
|
+
dry_run: bool = False,
|
|
1428
|
+
) -> int:
|
|
1429
|
+
# Change to demo directory if stored
|
|
1430
|
+
demo_dir = demo_core.load_demo_dir()
|
|
1431
|
+
if demo_dir and os.path.isdir(demo_dir):
|
|
1432
|
+
os.chdir(demo_dir)
|
|
1433
|
+
print(f"Using demo directory: {demo_dir}")
|
|
1434
|
+
|
|
1123
1435
|
env = demo_core.load_env()
|
|
1124
1436
|
cwd_env_path = os.path.join(os.getcwd(), ".env")
|
|
1125
|
-
|
|
1437
|
+
demo_core.load_dotenv_file(cwd_env_path)
|
|
1126
1438
|
|
|
1127
1439
|
synth_key = (env.synth_api_key or "").strip()
|
|
1128
1440
|
if not synth_key:
|
|
@@ -1154,7 +1466,7 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1154
1466
|
import tomllib
|
|
1155
1467
|
|
|
1156
1468
|
try:
|
|
1157
|
-
cfg_path = _select_or_create_config(
|
|
1469
|
+
cfg_path = _select_or_create_config(config, env)
|
|
1158
1470
|
except FileNotFoundError as exc:
|
|
1159
1471
|
print(exc)
|
|
1160
1472
|
return 1
|
|
@@ -1162,7 +1474,11 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1162
1474
|
# Detect monorepo launcher and delegate if available (aligns with run_clustered.sh which works)
|
|
1163
1475
|
launcher = "/Users/joshpurtell/Documents/GitHub/monorepo/tests/applications/math/rl/start_math_clustered.py"
|
|
1164
1476
|
if os.path.isfile(launcher):
|
|
1165
|
-
backend_base =
|
|
1477
|
+
backend_base = (
|
|
1478
|
+
env.dev_backend_url[:-4]
|
|
1479
|
+
if env.dev_backend_url.endswith("/api")
|
|
1480
|
+
else env.dev_backend_url
|
|
1481
|
+
)
|
|
1166
1482
|
run_env = os.environ.copy()
|
|
1167
1483
|
run_env["BACKEND_URL"] = backend_base
|
|
1168
1484
|
run_env["SYNTH_API_KEY"] = env.synth_api_key
|
|
@@ -1172,12 +1488,12 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1172
1488
|
# Optional: TRAINER_START_URL passthrough if already set in environment
|
|
1173
1489
|
run_env["TRAINER_START_URL"] = run_env.get("TRAINER_START_URL", "")
|
|
1174
1490
|
# Forward convenience knobs
|
|
1175
|
-
if
|
|
1176
|
-
run_env["RL_BATCH_SIZE"] = str(int(
|
|
1177
|
-
if
|
|
1178
|
-
run_env["RL_GROUP_SIZE"] = str(int(
|
|
1179
|
-
if
|
|
1180
|
-
run_env["RL_MODEL"] =
|
|
1491
|
+
if batch_size is not None:
|
|
1492
|
+
run_env["RL_BATCH_SIZE"] = str(int(batch_size))
|
|
1493
|
+
if group_size is not None:
|
|
1494
|
+
run_env["RL_GROUP_SIZE"] = str(int(group_size))
|
|
1495
|
+
if model:
|
|
1496
|
+
run_env["RL_MODEL"] = model
|
|
1181
1497
|
cmd = ["uv", "run", "python", launcher]
|
|
1182
1498
|
print(f"Launching monorepo clustered runner: {' '.join(cmd)}")
|
|
1183
1499
|
code = _popen_stream(cmd, env=run_env)
|
|
@@ -1192,33 +1508,33 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1192
1508
|
ek = (env.env_api_key or "").strip()
|
|
1193
1509
|
print("Hint: If backend responded 401, verify SYNTH_API_KEY for:", base_url)
|
|
1194
1510
|
if sk:
|
|
1195
|
-
print(f"
|
|
1511
|
+
print(f" {_key_preview(sk, 'SYNTH_API_KEY')}")
|
|
1196
1512
|
if ek:
|
|
1197
|
-
print(f"
|
|
1198
|
-
print(
|
|
1513
|
+
print(f" {_key_preview(ek, 'ENVIRONMENT_API_KEY')}")
|
|
1514
|
+
print(
|
|
1515
|
+
"Ensure the ENVIRONMENT_API_KEY you deployed with matches the task app and remains exported."
|
|
1516
|
+
)
|
|
1199
1517
|
return code
|
|
1200
1518
|
|
|
1201
1519
|
# Fallback: legacy jobs API flow
|
|
1202
1520
|
with open(cfg_path, "rb") as fh:
|
|
1203
1521
|
inline_cfg = tomllib.load(fh)
|
|
1204
|
-
with open(cfg_path
|
|
1522
|
+
with open(cfg_path) as fh2:
|
|
1205
1523
|
toml_text = fh2.read()
|
|
1206
|
-
if
|
|
1207
|
-
inline_cfg.setdefault("training", {})["batch_size"] = int(
|
|
1208
|
-
if
|
|
1209
|
-
inline_cfg.setdefault("training", {})["group_size"] = int(
|
|
1210
|
-
model_name =
|
|
1524
|
+
if batch_size is not None:
|
|
1525
|
+
inline_cfg.setdefault("training", {})["batch_size"] = int(batch_size)
|
|
1526
|
+
if group_size is not None:
|
|
1527
|
+
inline_cfg.setdefault("training", {})["group_size"] = int(group_size)
|
|
1528
|
+
model_name = model or (inline_cfg.get("model", {}) or {}).get("name", "Qwen/Qwen3-0.6B")
|
|
1211
1529
|
api = env.dev_backend_url.rstrip("/") + ("" if env.dev_backend_url.endswith("/api") else "/api")
|
|
1212
1530
|
# Print backend and key preview before request for clearer diagnostics
|
|
1213
1531
|
try:
|
|
1214
1532
|
sk = (env.synth_api_key or "").strip()
|
|
1215
|
-
sk_len = len(sk)
|
|
1216
|
-
sk_tail = sk[-5:] if sk_len >= 5 else sk
|
|
1217
1533
|
print(f"[run] Backend API: {api}")
|
|
1218
|
-
print(f"[run]
|
|
1534
|
+
print(f"[run] {_key_preview(sk, 'SYNTH_API_KEY')}")
|
|
1219
1535
|
except Exception:
|
|
1220
1536
|
pass
|
|
1221
|
-
data_fragment:
|
|
1537
|
+
data_fragment: dict[str, Any] = {
|
|
1222
1538
|
"model": model_name,
|
|
1223
1539
|
"endpoint_base_url": env.task_app_base_url,
|
|
1224
1540
|
"config": inline_cfg,
|
|
@@ -1236,23 +1552,28 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1236
1552
|
if inline_cfg["compute"].get("gpu_type"):
|
|
1237
1553
|
compute["gpu_type"] = str(inline_cfg["compute"]["gpu_type"]).upper()
|
|
1238
1554
|
if inline_cfg["compute"].get("gpu_count"):
|
|
1239
|
-
compute["gpu_count"] = int(inline_cfg["compute"]["gpu_count"])
|
|
1555
|
+
compute["gpu_count"] = int(inline_cfg["compute"]["gpu_count"])
|
|
1240
1556
|
if not compute:
|
|
1241
1557
|
topo = inline_cfg.get("topology") or {}
|
|
1242
1558
|
gshape = str(topo.get("gpu_type") or "")
|
|
1243
1559
|
if ":" in gshape:
|
|
1244
1560
|
t, c = gshape.split(":", 1)
|
|
1245
1561
|
compute = {"gpu_type": t.upper(), "gpu_count": int(c)}
|
|
1246
|
-
body:
|
|
1562
|
+
body: dict[str, Any] = {
|
|
1247
1563
|
"job_type": "rl",
|
|
1248
1564
|
"data": data_fragment,
|
|
1249
1565
|
}
|
|
1250
1566
|
if compute:
|
|
1251
1567
|
body["compute"] = compute
|
|
1252
|
-
code, js = _http(
|
|
1253
|
-
"
|
|
1254
|
-
|
|
1255
|
-
|
|
1568
|
+
code, js = _http(
|
|
1569
|
+
"POST",
|
|
1570
|
+
api + "/rl/jobs",
|
|
1571
|
+
headers={
|
|
1572
|
+
"Content-Type": "application/json",
|
|
1573
|
+
"Authorization": f"Bearer {env.synth_api_key}",
|
|
1574
|
+
},
|
|
1575
|
+
body=body,
|
|
1576
|
+
)
|
|
1256
1577
|
if code not in (200, 201) or not isinstance(js, dict):
|
|
1257
1578
|
print("Job create failed:", code)
|
|
1258
1579
|
print(f"Backend: {api}")
|
|
@@ -1264,15 +1585,77 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1264
1585
|
except Exception:
|
|
1265
1586
|
print(str(js))
|
|
1266
1587
|
print("Request body was:\n" + json.dumps(body, indent=2))
|
|
1588
|
+
try:
|
|
1589
|
+
auth_preview = _key_preview(env.synth_api_key or "", "SYNTH_API_KEY (auth)")
|
|
1590
|
+
print(f"[run] {auth_preview}")
|
|
1591
|
+
except Exception:
|
|
1592
|
+
pass
|
|
1593
|
+
try:
|
|
1594
|
+
data_block = body.get("data") if isinstance(body, dict) else None
|
|
1595
|
+
env_key_body = ""
|
|
1596
|
+
if isinstance(data_block, dict):
|
|
1597
|
+
env_key_body = str(data_block.get("environment_api_key") or "")
|
|
1598
|
+
if env_key_body:
|
|
1599
|
+
print(f"[run] {_key_preview(env_key_body, 'environment_api_key (body)')}")
|
|
1600
|
+
except Exception:
|
|
1601
|
+
pass
|
|
1602
|
+
try:
|
|
1603
|
+
current_env_key = env.env_api_key or ""
|
|
1604
|
+
if current_env_key:
|
|
1605
|
+
print(f"[run] {_key_preview(current_env_key, 'ENVIRONMENT_API_KEY (current)')}")
|
|
1606
|
+
except Exception:
|
|
1607
|
+
pass
|
|
1608
|
+
if isinstance(js, dict):
|
|
1609
|
+
detail = js.get("detail")
|
|
1610
|
+
if isinstance(detail, dict):
|
|
1611
|
+
try:
|
|
1612
|
+
sent_key = detail.get("sent_key")
|
|
1613
|
+
if isinstance(sent_key, str):
|
|
1614
|
+
print(
|
|
1615
|
+
f"[run] Backend detail.sent_key {_key_preview(sent_key, 'detail.sent_key')}"
|
|
1616
|
+
)
|
|
1617
|
+
except Exception:
|
|
1618
|
+
pass
|
|
1619
|
+
try:
|
|
1620
|
+
sent_keys = detail.get("sent_keys")
|
|
1621
|
+
if isinstance(sent_keys, list | tuple):
|
|
1622
|
+
previews = []
|
|
1623
|
+
for idx, val in enumerate(sent_keys):
|
|
1624
|
+
if isinstance(val, str):
|
|
1625
|
+
previews.append(_key_preview(val, f"detail.sent_keys[{idx}]"))
|
|
1626
|
+
if previews:
|
|
1627
|
+
joined = "; ".join(previews)
|
|
1628
|
+
print(f"[run] Backend detail.sent_keys previews: {joined}")
|
|
1629
|
+
except Exception:
|
|
1630
|
+
pass
|
|
1631
|
+
try:
|
|
1632
|
+
key_prefix = detail.get("sent_key_prefix")
|
|
1633
|
+
if isinstance(key_prefix, str):
|
|
1634
|
+
print(f"[run] Backend detail.sent_key_prefix={key_prefix}")
|
|
1635
|
+
except Exception:
|
|
1636
|
+
pass
|
|
1637
|
+
try:
|
|
1638
|
+
health_url = detail.get("health_url")
|
|
1639
|
+
if isinstance(health_url, str):
|
|
1640
|
+
print(f"[run] Backend detail.health_url={health_url}")
|
|
1641
|
+
except Exception:
|
|
1642
|
+
pass
|
|
1267
1643
|
# Extra hints for auth failures
|
|
1268
1644
|
try:
|
|
1269
1645
|
sk = (env.synth_api_key or "").strip()
|
|
1270
|
-
if int(code) == 401 or (
|
|
1646
|
+
if int(code) == 401 or (
|
|
1647
|
+
isinstance(js, dict)
|
|
1648
|
+
and any(isinstance(v, str) and "Invalid API key" in v for v in js.values())
|
|
1649
|
+
):
|
|
1271
1650
|
base_url = env.dev_backend_url
|
|
1272
|
-
print(
|
|
1651
|
+
print(
|
|
1652
|
+
"Hint: HTTP 401 Unauthorized from backend. Verify SYNTH_API_KEY for:", base_url
|
|
1653
|
+
)
|
|
1273
1654
|
if sk:
|
|
1274
|
-
print(f"
|
|
1275
|
-
print(
|
|
1655
|
+
print(f" {_key_preview(sk, 'SYNTH_API_KEY')}")
|
|
1656
|
+
print(
|
|
1657
|
+
"Ensure the ENVIRONMENT_API_KEY and OPENAI_API_KEY used for deployment remain valid."
|
|
1658
|
+
)
|
|
1276
1659
|
except Exception:
|
|
1277
1660
|
pass
|
|
1278
1661
|
return 2
|
|
@@ -1324,9 +1707,7 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1324
1707
|
"rl.performance.metrics",
|
|
1325
1708
|
):
|
|
1326
1709
|
print(f"[{seq}] {typ}: {msg}")
|
|
1327
|
-
mc, mj = _http(
|
|
1328
|
-
"GET", api + f"/learning/jobs/{job_id}/metrics?after_step=-1&limit=50"
|
|
1329
|
-
)
|
|
1710
|
+
mc, mj = _http("GET", api + f"/learning/jobs/{job_id}/metrics?after_step=-1&limit=50")
|
|
1330
1711
|
if mc == 200 and isinstance(mj, dict):
|
|
1331
1712
|
pts = mj.get("points") or []
|
|
1332
1713
|
for p in pts:
|
|
@@ -1334,60 +1715,8 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1334
1715
|
if name == "eval.reward_mean":
|
|
1335
1716
|
print(f"metric eval.reward_mean step={p.get('step')} value={p.get('value')}")
|
|
1336
1717
|
break
|
|
1337
|
-
if time.time() - start_t > (
|
|
1718
|
+
if time.time() - start_t > (timeout or 600):
|
|
1338
1719
|
print("Timeout waiting for terminal state.")
|
|
1339
1720
|
break
|
|
1340
1721
|
time.sleep(2)
|
|
1341
1722
|
return 0
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
def main(argv: list[str] | None = None) -> int:
|
|
1345
|
-
p = argparse.ArgumentParser(prog="synth-ai")
|
|
1346
|
-
sub = p.add_subparsers(dest="cmd")
|
|
1347
|
-
|
|
1348
|
-
def _add_parser(names: list[str], *, configure: Callable[[argparse.ArgumentParser], None]) -> None:
|
|
1349
|
-
for name in names:
|
|
1350
|
-
parser = sub.add_parser(name)
|
|
1351
|
-
configure(parser)
|
|
1352
|
-
|
|
1353
|
-
_add_parser(["rl_demo.setup", "demo.setup"], configure=lambda parser: parser.set_defaults(func=cmd_setup))
|
|
1354
|
-
|
|
1355
|
-
def _init_opts(parser):
|
|
1356
|
-
parser.add_argument("--force", action="store_true", help="Overwrite existing files in CWD")
|
|
1357
|
-
parser.set_defaults(func=cmd_init)
|
|
1358
|
-
|
|
1359
|
-
_add_parser(["rl_demo.init", "demo.init"], configure=_init_opts)
|
|
1360
|
-
|
|
1361
|
-
# (prepare command removed)
|
|
1362
|
-
|
|
1363
|
-
def _deploy_opts(parser):
|
|
1364
|
-
parser.add_argument("--local", action="store_true", help="Run local FastAPI instead of Modal deploy")
|
|
1365
|
-
parser.add_argument("--app", type=str, default=None, help="Path to Modal app.py for uv run modal deploy")
|
|
1366
|
-
parser.add_argument("--name", type=str, default="synth-math-demo", help="Modal app name")
|
|
1367
|
-
parser.add_argument("--script", type=str, default=None, help="Path to deploy_task_app.sh (optional legacy)")
|
|
1368
|
-
parser.set_defaults(func=cmd_deploy)
|
|
1369
|
-
|
|
1370
|
-
_add_parser(["rl_demo.deploy", "demo.deploy"], configure=_deploy_opts)
|
|
1371
|
-
|
|
1372
|
-
_add_parser(["rl_demo.configure", "demo.configure"], configure=lambda parser: parser.set_defaults(func=cmd_run))
|
|
1373
|
-
|
|
1374
|
-
def _run_opts(parser):
|
|
1375
|
-
parser.add_argument("--config", type=str, default=None, help="Path to TOML config (skip prompt)")
|
|
1376
|
-
parser.add_argument("--batch-size", type=int, default=None)
|
|
1377
|
-
parser.add_argument("--group-size", type=int, default=None)
|
|
1378
|
-
parser.add_argument("--model", type=str, default=None)
|
|
1379
|
-
parser.add_argument("--timeout", type=int, default=600)
|
|
1380
|
-
parser.add_argument("--dry-run", action="store_true", help="Print request body and exit")
|
|
1381
|
-
parser.set_defaults(func=cmd_run)
|
|
1382
|
-
|
|
1383
|
-
_add_parser(["run", "rl_demo.run", "demo.run"], configure=_run_opts)
|
|
1384
|
-
|
|
1385
|
-
args = p.parse_args(argv)
|
|
1386
|
-
if not hasattr(args, "func"):
|
|
1387
|
-
p.print_help()
|
|
1388
|
-
return 1
|
|
1389
|
-
return int(args.func(args) or 0)
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
if __name__ == "__main__":
|
|
1393
|
-
sys.exit(main())
|