synth-ai 0.2.9.dev0__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
- {synth_ai/task/apps → examples/rl/task_app}/math_single_step.py +188 -50
- 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 +60 -2
- synth_ai/api/train/builders.py +347 -39
- synth_ai/api/train/cli.py +895 -160
- synth_ai/api/train/config_finder.py +103 -25
- 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 +70 -20
- synth_ai/api/train/pollers.py +29 -4
- 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 +6 -4
- synth_ai/api/train/utils.py +64 -52
- 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 +85 -63
- 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 +156 -116
- synth_ai/cli/root.py +131 -132
- 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 +2284 -257
- 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 +579 -291
- 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 +3 -3
- synth_ai/demos/demo_task_apps/core.py +64 -28
- 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/deploy_modal.py +3 -6
- synth_ai/demos/demo_task_apps/math/modal_task_app.py +185 -83
- synth_ai/demos/demo_task_apps/math/task_app_entry.py +0 -2
- 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 +50 -30
- synth_ai/task/apps/__init__.py +63 -19
- synth_ai/task/auth.py +35 -23
- synth_ai/task/client.py +15 -13
- synth_ai/task/config.py +261 -0
- synth_ai/task/contracts.py +165 -64
- synth_ai/task/datasets.py +9 -6
- synth_ai/task/errors.py +11 -10
- synth_ai/task/health.py +17 -11
- synth_ai/task/inference_api.py +101 -0
- synth_ai/task/json.py +58 -24
- synth_ai/task/proxy.py +59 -66
- 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 +22 -15
- synth_ai/task/server.py +65 -31
- synth_ai/task/trace_correlation_helpers.py +328 -0
- synth_ai/task/tracing_utils.py +44 -28
- synth_ai/task/validators.py +449 -6
- synth_ai/task/vendors.py +5 -7
- 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 +73 -16
- synth_ai/tracing_v3/storage/base.py +89 -1
- 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.9.dev0.dist-info → synth_ai-0.2.23.dev3.dist-info}/entry_points.txt +0 -1
- {synth_ai-0.2.9.dev0.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/demo_registry.py +0 -258
- synth_ai/environments/examples/sokoban/units/astar_common.py +0 -95
- synth_ai/experimental/synth_oss.py +0 -446
- synth_ai/handshake.py +0 -107
- 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/task/apps/grpo_crafter.py +0 -438
- synth_ai/tracing/__init__.py +0 -30
- synth_ai/tracing_v1/__init__.py +0 -33
- synth_ai/tracing_v3/turso/manager.py +0 -774
- 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.9.dev0.dist-info/METADATA +0 -131
- synth_ai-0.2.9.dev0.dist-info/RECORD +0 -444
- {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.9.dev0.dist-info → synth_ai-0.2.23.dev3.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.9.dev0.dist-info → synth_ai-0.2.23.dev3.dist-info}/licenses/LICENSE +0 -0
synth_ai/demos/core/cli.py
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
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
|
|
|
14
|
-
from synth_ai.demos.demo_task_apps import core as demo_core
|
|
15
|
-
from synth_ai.demos.demo_task_apps.core import DemoEnv, DEFAULT_TASK_APP_SECRET_NAME
|
|
16
15
|
from synth_ai.demo_registry import (
|
|
17
|
-
CopySpec,
|
|
18
16
|
DemoTemplate,
|
|
19
17
|
get_demo_template,
|
|
20
18
|
list_demo_templates,
|
|
21
19
|
)
|
|
22
|
-
from synth_ai.
|
|
20
|
+
from synth_ai.demos.demo_task_apps import core as demo_core
|
|
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
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
def _key_preview(value: str, label: str) -> str:
|
|
@@ -44,35 +45,71 @@ def _is_modal_public_url(u: str) -> bool:
|
|
|
44
45
|
return False
|
|
45
46
|
|
|
46
47
|
|
|
47
|
-
def
|
|
48
|
-
#
|
|
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
|
+
|
|
49
60
|
try:
|
|
50
61
|
print("\n⏳ Connecting SDK to your browser session…")
|
|
51
62
|
res = run_handshake()
|
|
52
|
-
user = res.get("user") or {}
|
|
53
63
|
org = res.get("org") or {}
|
|
54
64
|
keys = res.get("keys") or {}
|
|
55
65
|
synth_key = str(keys.get("synth") or "").strip()
|
|
56
66
|
rl_env_key = str(keys.get("rl_env") or "").strip()
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
+
{
|
|
61
101
|
"SYNTH_API_KEY": synth_key,
|
|
62
102
|
"ENVIRONMENT_API_KEY": rl_env_key,
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return 1
|
|
69
|
-
except Exception as e:
|
|
70
|
-
print(f"Unexpected handshake error: {e}")
|
|
71
|
-
return 1
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Store .env path for subsequent commands
|
|
107
|
+
demo_core.persist_env_file_path(dotenv_path)
|
|
72
108
|
|
|
73
109
|
# 2) Reload env after handshake to pick up values from .env (suppress env prints)
|
|
74
|
-
import io
|
|
75
110
|
import contextlib
|
|
111
|
+
import io
|
|
112
|
+
|
|
76
113
|
_buf = io.StringIO()
|
|
77
114
|
with contextlib.redirect_stdout(_buf):
|
|
78
115
|
env = demo_core.load_env()
|
|
@@ -89,22 +126,22 @@ def cmd_setup(_args: argparse.Namespace) -> int:
|
|
|
89
126
|
return
|
|
90
127
|
current = env.task_app_base_url
|
|
91
128
|
needs_lookup = False
|
|
92
|
-
if not current:
|
|
93
|
-
needs_lookup = True
|
|
94
|
-
elif not _is_modal_public_url(current):
|
|
129
|
+
if not current or not _is_modal_public_url(current):
|
|
95
130
|
needs_lookup = True
|
|
96
131
|
if not needs_lookup:
|
|
97
132
|
return
|
|
98
|
-
code, out = _popen_capture(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
+
)
|
|
108
145
|
if code != 0 or not out:
|
|
109
146
|
return
|
|
110
147
|
new_url = ""
|
|
@@ -134,15 +171,16 @@ def cmd_setup(_args: argparse.Namespace) -> int:
|
|
|
134
171
|
|
|
135
172
|
_maybe_fix_task_url()
|
|
136
173
|
|
|
137
|
-
ok_backend = False
|
|
138
|
-
ok_task = False
|
|
139
174
|
if env.dev_backend_url:
|
|
140
|
-
api = env.dev_backend_url.rstrip("/") + (
|
|
141
|
-
|
|
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")
|
|
142
179
|
# Intentionally suppress backend health print for concise output
|
|
143
180
|
if env.task_app_base_url:
|
|
144
|
-
|
|
145
|
-
|
|
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")
|
|
146
184
|
# Intentionally suppress task app health print
|
|
147
185
|
else:
|
|
148
186
|
print("\nSet your task app URL by running:\nuvx synth-ai rl_demo deploy\n")
|
|
@@ -150,13 +188,19 @@ def cmd_setup(_args: argparse.Namespace) -> int:
|
|
|
150
188
|
# Omit uv version print to keep output concise
|
|
151
189
|
|
|
152
190
|
# Keep exit code neutral; not all checks are critical for pairing
|
|
191
|
+
print(f"\nKeys saved to: {dotenv_path}")
|
|
153
192
|
return 0
|
|
154
193
|
|
|
155
194
|
|
|
156
|
-
def _popen_capture(
|
|
195
|
+
def _popen_capture(
|
|
196
|
+
cmd: list[str], cwd: str | None = None, env: dict | None = None
|
|
197
|
+
) -> tuple[int, str]:
|
|
157
198
|
import subprocess
|
|
199
|
+
|
|
158
200
|
try:
|
|
159
|
-
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
|
+
)
|
|
160
204
|
out, _ = proc.communicate()
|
|
161
205
|
return int(proc.returncode or 0), out or ""
|
|
162
206
|
except Exception as e:
|
|
@@ -173,7 +217,7 @@ def _popen_stream(cmd: list[str], cwd: str | None = None, env: dict | None = Non
|
|
|
173
217
|
proc = subprocess.Popen(
|
|
174
218
|
cmd,
|
|
175
219
|
cwd=cwd,
|
|
176
|
-
env=env,
|
|
220
|
+
env=get_subprocess_env(env),
|
|
177
221
|
stdout=subprocess.PIPE,
|
|
178
222
|
stderr=subprocess.STDOUT,
|
|
179
223
|
text=True,
|
|
@@ -186,7 +230,8 @@ def _popen_stream(cmd: list[str], cwd: str | None = None, env: dict | None = Non
|
|
|
186
230
|
def _pump(stdout) -> None:
|
|
187
231
|
try:
|
|
188
232
|
for line in stdout:
|
|
189
|
-
|
|
233
|
+
if not should_filter_log_line(line):
|
|
234
|
+
print(line.rstrip())
|
|
190
235
|
except Exception:
|
|
191
236
|
pass
|
|
192
237
|
|
|
@@ -200,7 +245,9 @@ def _popen_stream(cmd: list[str], cwd: str | None = None, env: dict | None = Non
|
|
|
200
245
|
return int(proc.returncode or 0)
|
|
201
246
|
|
|
202
247
|
|
|
203
|
-
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]:
|
|
204
251
|
"""Stream subprocess output to stdout and also capture it into a buffer."""
|
|
205
252
|
import subprocess
|
|
206
253
|
import threading
|
|
@@ -210,7 +257,7 @@ def _popen_stream_capture(cmd: list[str], cwd: str | None = None, env: dict | No
|
|
|
210
257
|
proc = subprocess.Popen(
|
|
211
258
|
cmd,
|
|
212
259
|
cwd=cwd,
|
|
213
|
-
env=env,
|
|
260
|
+
env=get_subprocess_env(env),
|
|
214
261
|
stdout=subprocess.PIPE,
|
|
215
262
|
stderr=subprocess.STDOUT,
|
|
216
263
|
text=True,
|
|
@@ -224,8 +271,9 @@ def _popen_stream_capture(cmd: list[str], cwd: str | None = None, env: dict | No
|
|
|
224
271
|
try:
|
|
225
272
|
for line in stdout:
|
|
226
273
|
line = line.rstrip()
|
|
227
|
-
|
|
228
|
-
|
|
274
|
+
if not should_filter_log_line(line):
|
|
275
|
+
print(line)
|
|
276
|
+
buf_lines.append(line)
|
|
229
277
|
except Exception:
|
|
230
278
|
pass
|
|
231
279
|
|
|
@@ -251,7 +299,19 @@ def _find_asgi_apps(root: Path) -> list[Path]:
|
|
|
251
299
|
- "@modal.asgi_app()"
|
|
252
300
|
"""
|
|
253
301
|
results: list[Path] = []
|
|
254
|
-
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
|
+
}
|
|
255
315
|
for dirpath, dirnames, filenames in os.walk(root):
|
|
256
316
|
dirnames[:] = [d for d in dirnames if d not in skip_dirs]
|
|
257
317
|
for name in filenames:
|
|
@@ -265,16 +325,20 @@ def _find_asgi_apps(root: Path) -> list[Path]:
|
|
|
265
325
|
results.append(path)
|
|
266
326
|
except Exception:
|
|
267
327
|
continue
|
|
328
|
+
|
|
268
329
|
# Stable order: prioritize files under synth_demo/ first, then alphabetical
|
|
269
330
|
def _priority(p: Path) -> tuple[int, str]:
|
|
270
331
|
rel = str(p.resolve())
|
|
271
332
|
in_demo = "/synth_demo/" in rel or rel.endswith("/synth_demo/task_app.py")
|
|
272
333
|
return (0 if in_demo else 1, rel)
|
|
334
|
+
|
|
273
335
|
results.sort(key=_priority)
|
|
274
336
|
return results
|
|
275
337
|
|
|
276
338
|
|
|
277
|
-
def _prompt_value(
|
|
339
|
+
def _prompt_value(
|
|
340
|
+
label: str, default: str | int | float, cast: Callable[[str], Any] | None = None
|
|
341
|
+
) -> Any:
|
|
278
342
|
prompt = f"{label} [{default}]: "
|
|
279
343
|
try:
|
|
280
344
|
raw = input(prompt).strip()
|
|
@@ -293,7 +357,19 @@ def _prompt_value(label: str, default: str | int | float, cast: Callable[[str],
|
|
|
293
357
|
|
|
294
358
|
def _find_vllm_tomls(root: Path) -> list[Path]:
|
|
295
359
|
results: list[Path] = []
|
|
296
|
-
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
|
+
}
|
|
297
373
|
for dirpath, dirnames, filenames in os.walk(root):
|
|
298
374
|
dirnames[:] = [d for d in dirnames if d not in skip_dirs]
|
|
299
375
|
for name in filenames:
|
|
@@ -313,7 +389,9 @@ def _create_new_config(env: DemoEnv) -> str:
|
|
|
313
389
|
default_path = os.path.join(os.getcwd(), "demo_config.toml")
|
|
314
390
|
while True:
|
|
315
391
|
try:
|
|
316
|
-
destination =
|
|
392
|
+
destination = (
|
|
393
|
+
input(f"Path to save new config [{default_path}]: ").strip() or default_path
|
|
394
|
+
)
|
|
317
395
|
except Exception:
|
|
318
396
|
destination = default_path
|
|
319
397
|
destination = os.path.abspath(destination)
|
|
@@ -322,7 +400,9 @@ def _create_new_config(env: DemoEnv) -> str:
|
|
|
322
400
|
continue
|
|
323
401
|
if os.path.exists(destination):
|
|
324
402
|
try:
|
|
325
|
-
overwrite =
|
|
403
|
+
overwrite = (
|
|
404
|
+
input(f"{destination} exists. Overwrite? [y/N]: ").strip().lower() or "n"
|
|
405
|
+
)
|
|
326
406
|
except Exception:
|
|
327
407
|
overwrite = "n"
|
|
328
408
|
if not overwrite.startswith("y"):
|
|
@@ -334,7 +414,9 @@ def _create_new_config(env: DemoEnv) -> str:
|
|
|
334
414
|
model_name = _prompt_value("Model name", "Qwen/Qwen3-0.6B")
|
|
335
415
|
compute_gpu_type = _prompt_value("Compute GPU type", "H100")
|
|
336
416
|
compute_gpu_count = _prompt_value("Compute GPU count", 4, int)
|
|
337
|
-
topology_gpu_type = _prompt_value(
|
|
417
|
+
topology_gpu_type = _prompt_value(
|
|
418
|
+
"Topology GPU type", f"{compute_gpu_type}:{compute_gpu_count}"
|
|
419
|
+
)
|
|
338
420
|
gpus_for_vllm = _prompt_value("Topology gpus_for_vllm", 2, int)
|
|
339
421
|
gpus_for_training = _prompt_value("Topology gpus_for_training", 1, int)
|
|
340
422
|
tensor_parallel = _prompt_value("Topology tensor_parallel", 2, int)
|
|
@@ -352,8 +434,9 @@ def _create_new_config(env: DemoEnv) -> str:
|
|
|
352
434
|
task_url_default = env.task_app_base_url or ""
|
|
353
435
|
services_task_url = _prompt_value("services.task_url", task_url_default)
|
|
354
436
|
|
|
355
|
-
template =
|
|
356
|
-
|
|
437
|
+
template = (
|
|
438
|
+
textwrap.dedent(
|
|
439
|
+
f"""\
|
|
357
440
|
# Crafter online RL training configuration (research local copy)
|
|
358
441
|
|
|
359
442
|
[model]
|
|
@@ -495,7 +578,9 @@ def _create_new_config(env: DemoEnv) -> str:
|
|
|
495
578
|
[services]
|
|
496
579
|
task_url = \"{services_task_url}\"
|
|
497
580
|
"""
|
|
498
|
-
|
|
581
|
+
).strip()
|
|
582
|
+
+ "\n"
|
|
583
|
+
)
|
|
499
584
|
|
|
500
585
|
with open(destination, "w", encoding="utf-8") as fh:
|
|
501
586
|
fh.write(template)
|
|
@@ -514,7 +599,11 @@ def _select_or_create_config(explicit: str | None, env: DemoEnv) -> str:
|
|
|
514
599
|
discovered = _find_vllm_tomls(search_root)
|
|
515
600
|
|
|
516
601
|
extras: list[Path] = []
|
|
517
|
-
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
|
+
)
|
|
518
607
|
extras.append(packaged)
|
|
519
608
|
home_cfg = Path(os.path.expanduser("~/.synth-ai/demo_config.toml"))
|
|
520
609
|
extras.append(home_cfg)
|
|
@@ -560,29 +649,36 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
560
649
|
|
|
561
650
|
env_key = (env.env_api_key or "").strip()
|
|
562
651
|
if not env_key:
|
|
563
|
-
raise RuntimeError(
|
|
652
|
+
raise RuntimeError(
|
|
653
|
+
f"[{label}] ENVIRONMENT_API_KEY missing. Run `uvx synth-ai rl_demo deploy` first."
|
|
654
|
+
)
|
|
564
655
|
|
|
565
656
|
task_url = env.task_app_base_url
|
|
566
657
|
if not task_url or not _is_modal_public_url(task_url):
|
|
567
658
|
resolved = ""
|
|
568
659
|
if env.task_app_name:
|
|
569
660
|
try:
|
|
570
|
-
choice =
|
|
571
|
-
f"Resolve URL from Modal for app '{env.task_app_name}'? [Y/n]: "
|
|
572
|
-
|
|
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
|
+
)
|
|
573
667
|
except Exception:
|
|
574
668
|
choice = "y"
|
|
575
669
|
if choice.startswith("y"):
|
|
576
|
-
code, out = _popen_capture(
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
+
)
|
|
586
682
|
if code == 0 and out:
|
|
587
683
|
for tok in out.split():
|
|
588
684
|
if _is_modal_public_url(tok):
|
|
@@ -591,7 +687,9 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
591
687
|
if not resolved:
|
|
592
688
|
print(f"[{label}] Task app URL not configured or not a valid Modal public URL.")
|
|
593
689
|
print("Examples: https://<app-name>-fastapi-app.modal.run")
|
|
594
|
-
entered = input(
|
|
690
|
+
entered = input(
|
|
691
|
+
"Enter Task App base URL (must contain '.modal.run'), or press Enter to abort: "
|
|
692
|
+
).strip()
|
|
595
693
|
if not entered or not _is_modal_public_url(entered):
|
|
596
694
|
raise RuntimeError(f"[{label}] Valid Task App URL is required.")
|
|
597
695
|
task_url = entered.rstrip("/")
|
|
@@ -608,11 +706,13 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
608
706
|
demo_core.persist_task_url(task_url, name=app_name)
|
|
609
707
|
|
|
610
708
|
demo_core.persist_task_url(task_url, name=app_name)
|
|
611
|
-
demo_core.persist_dotenv_values(
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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
|
+
)
|
|
616
716
|
|
|
617
717
|
if synth_key:
|
|
618
718
|
os.environ["SYNTH_API_KEY"] = synth_key
|
|
@@ -621,7 +721,6 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
621
721
|
if openai_key:
|
|
622
722
|
os.environ["OPENAI_API_KEY"] = openai_key
|
|
623
723
|
|
|
624
|
-
rollout_url = task_url.rstrip("/") + "/health/rollout"
|
|
625
724
|
print(f"[{label}] Verifying rollout health:")
|
|
626
725
|
try:
|
|
627
726
|
ek = (env_key or "").strip()
|
|
@@ -636,7 +735,6 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
636
735
|
print(f"[{label}] GET", h)
|
|
637
736
|
rc, body = _http("GET", h, headers={"X-API-Key": env_key})
|
|
638
737
|
if rc == 200:
|
|
639
|
-
rollout_url = h
|
|
640
738
|
break
|
|
641
739
|
print(f"[{label}] status: {rc}")
|
|
642
740
|
try:
|
|
@@ -648,10 +746,8 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
648
746
|
print(f"[{label}] body:", preview)
|
|
649
747
|
if rc != 200:
|
|
650
748
|
print(f"[{label}] Warning: rollout health check failed ({rc}). Response: {body}")
|
|
651
|
-
|
|
749
|
+
with contextlib.suppress(Exception):
|
|
652
750
|
print(f"[{label}] Sent header X-API-Key → {_key_preview(env_key, 'X-API-Key')}")
|
|
653
|
-
except Exception:
|
|
654
|
-
pass
|
|
655
751
|
else:
|
|
656
752
|
print(f"[{label}] Task app rollout health check OK.")
|
|
657
753
|
|
|
@@ -666,7 +762,15 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
666
762
|
return updated_env
|
|
667
763
|
|
|
668
764
|
|
|
669
|
-
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
|
+
|
|
670
774
|
env = demo_core.load_env()
|
|
671
775
|
os.environ["TASK_APP_SECRET_NAME"] = DEFAULT_TASK_APP_SECRET_NAME
|
|
672
776
|
cwd_env_path = os.path.join(os.getcwd(), ".env")
|
|
@@ -674,21 +778,32 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
674
778
|
url = ""
|
|
675
779
|
app_name = env.task_app_name or ""
|
|
676
780
|
try:
|
|
677
|
-
if
|
|
781
|
+
if local:
|
|
678
782
|
print("Starting local Task App…")
|
|
679
783
|
import subprocess
|
|
680
|
-
|
|
681
|
-
|
|
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
|
+
)
|
|
682
795
|
target = "http://127.0.0.1:8080"
|
|
683
796
|
app_name = ""
|
|
684
797
|
for _ in range(30):
|
|
685
|
-
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"):
|
|
686
801
|
url = target
|
|
687
802
|
break
|
|
688
803
|
time.sleep(1)
|
|
689
804
|
else:
|
|
690
805
|
# Auto-detect app path if not supplied; prompt interactively from discovered ASGI apps
|
|
691
|
-
app_path = os.path.abspath(
|
|
806
|
+
app_path = os.path.abspath(app) if app else None
|
|
692
807
|
if not app_path or not os.path.isfile(app_path):
|
|
693
808
|
# First pass: look for known common filenames
|
|
694
809
|
candidates = [
|
|
@@ -707,7 +822,9 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
707
822
|
rel = os.path.relpath(str(pth), os.getcwd())
|
|
708
823
|
print(f" [{idx}] {rel}")
|
|
709
824
|
try:
|
|
710
|
-
sel =
|
|
825
|
+
sel = (
|
|
826
|
+
input(f"Enter choice [1-{len(found)}] (default 1): ").strip() or "1"
|
|
827
|
+
)
|
|
711
828
|
except Exception:
|
|
712
829
|
sel = "1"
|
|
713
830
|
try:
|
|
@@ -716,12 +833,13 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
716
833
|
choice = 1
|
|
717
834
|
choice = max(1, min(choice, len(found)))
|
|
718
835
|
app_path = str(found[choice - 1].resolve())
|
|
719
|
-
if not app_path and
|
|
836
|
+
if not app_path and script:
|
|
720
837
|
# Legacy script fallback if user supplied --script explicitly
|
|
721
838
|
from synth_ai.demos.demo_task_apps.math.deploy_modal import deploy as modal_deploy
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
839
|
+
|
|
840
|
+
url = modal_deploy(script_path=script, env_api_key=env.env_api_key)
|
|
841
|
+
if name:
|
|
842
|
+
app_name = name
|
|
725
843
|
else:
|
|
726
844
|
if not app_path:
|
|
727
845
|
entered = input("Path to Modal app.py (e.g., ./task_app.py): ").strip()
|
|
@@ -732,7 +850,7 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
732
850
|
raise FileNotFoundError(f"App file not found: {app_path}")
|
|
733
851
|
# Surface the app path before asking for the name
|
|
734
852
|
print(f"Using task app: {app_path}")
|
|
735
|
-
existing_name = (
|
|
853
|
+
existing_name = (name or env.task_app_name or "").strip()
|
|
736
854
|
if not existing_name:
|
|
737
855
|
existing_name = f"synth-{os.path.splitext(os.path.basename(app_path))[0]}"
|
|
738
856
|
suggested_name = existing_name
|
|
@@ -750,16 +868,19 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
750
868
|
env_key: str | None = existing_env_key or None
|
|
751
869
|
if existing_env_key:
|
|
752
870
|
try:
|
|
753
|
-
reuse_choice =
|
|
754
|
-
"Use existing ENVIRONMENT_API_KEY from state/.env? [Y/n]: "
|
|
755
|
-
|
|
871
|
+
reuse_choice = (
|
|
872
|
+
input("Use existing ENVIRONMENT_API_KEY from state/.env? [Y/n]: ")
|
|
873
|
+
.strip()
|
|
874
|
+
.lower()
|
|
875
|
+
or "y"
|
|
876
|
+
)
|
|
756
877
|
except Exception:
|
|
757
878
|
reuse_choice = "y"
|
|
758
879
|
if not reuse_choice.startswith("y"):
|
|
759
880
|
env_key = None
|
|
760
881
|
|
|
761
882
|
if env_key is None:
|
|
762
|
-
from synth_ai.rl.secrets import mint_environment_api_key
|
|
883
|
+
from synth_ai.learning.rl.secrets import mint_environment_api_key
|
|
763
884
|
|
|
764
885
|
env_key = mint_environment_api_key()
|
|
765
886
|
demo_core.persist_env_api_key(env_key)
|
|
@@ -770,35 +891,50 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
770
891
|
print("[deploy] Minted new ENVIRONMENT_API_KEY")
|
|
771
892
|
elif env_key:
|
|
772
893
|
os.environ["ENVIRONMENT_API_KEY"] = env_key
|
|
773
|
-
|
|
894
|
+
|
|
774
895
|
# Optionally upload the new key to the backend using sealed box helper
|
|
775
896
|
backend_base = (env.dev_backend_url or "").rstrip("/")
|
|
776
|
-
synth_key = (
|
|
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()
|
|
777
903
|
if backend_base and synth_key:
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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"):
|
|
781
919
|
try:
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
from synth_ai.rl.env_keys import setup_environment_api_key
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
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()
|
|
802
938
|
if not synth_key:
|
|
803
939
|
synth_key = input("Enter SYNTH_API_KEY for deployment (required): ").strip()
|
|
804
940
|
if not synth_key:
|
|
@@ -809,7 +945,9 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
809
945
|
env.synth_api_key = synth_key
|
|
810
946
|
os.environ["SYNTH_API_KEY"] = synth_key
|
|
811
947
|
|
|
812
|
-
openai_key = (
|
|
948
|
+
openai_key = (
|
|
949
|
+
os.environ.get("OPENAI_API_KEY") or local_env.get("OPENAI_API_KEY") or ""
|
|
950
|
+
).strip()
|
|
813
951
|
if not openai_key:
|
|
814
952
|
openai_key = input(
|
|
815
953
|
"Enter your OpenAI API key, found at https://platform.openai.com/api-keys\n> "
|
|
@@ -821,8 +959,20 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
821
959
|
local_env["OPENAI_API_KEY"] = openai_key
|
|
822
960
|
os.environ["OPENAI_API_KEY"] = openai_key
|
|
823
961
|
|
|
824
|
-
deploy_cmd = [
|
|
825
|
-
|
|
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
|
+
)
|
|
826
976
|
code, deploy_logs = _popen_stream_capture(deploy_cmd)
|
|
827
977
|
if code != 0:
|
|
828
978
|
raise RuntimeError(f"modal deploy failed (exit {code})")
|
|
@@ -830,6 +980,7 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
830
980
|
if not url:
|
|
831
981
|
try:
|
|
832
982
|
import re as _re
|
|
983
|
+
|
|
833
984
|
m_all = _re.findall(r"https?://[^\s]+\.modal\.run", deploy_logs or "")
|
|
834
985
|
if m_all:
|
|
835
986
|
url = m_all[-1].strip().rstrip("/")
|
|
@@ -844,7 +995,9 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
844
995
|
break
|
|
845
996
|
# Fallback: try reading recent Modal logs for the app to find a URL line
|
|
846
997
|
if not url:
|
|
847
|
-
code3, out3 = _popen_capture(
|
|
998
|
+
code3, out3 = _popen_capture(
|
|
999
|
+
["uv", "run", "python", "-m", "modal", "app", "list"]
|
|
1000
|
+
)
|
|
848
1001
|
if code3 == 0 and out3:
|
|
849
1002
|
for line in out3.splitlines():
|
|
850
1003
|
if name_in in line:
|
|
@@ -857,7 +1010,9 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
857
1010
|
# Prompt user if still no valid URL
|
|
858
1011
|
if not url:
|
|
859
1012
|
print("\nCould not auto-detect a public Modal URL for the app.")
|
|
860
|
-
entered = input(
|
|
1013
|
+
entered = input(
|
|
1014
|
+
"Enter the Modal public URL (must contain '.modal.run'), or press Enter to abort: "
|
|
1015
|
+
).strip()
|
|
861
1016
|
if entered and _is_modal_public_url(entered):
|
|
862
1017
|
url = entered.rstrip("/")
|
|
863
1018
|
if not url:
|
|
@@ -885,8 +1040,9 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
885
1040
|
print(f"Deploy error: {e}")
|
|
886
1041
|
return 2
|
|
887
1042
|
|
|
888
|
-
|
|
889
|
-
|
|
1043
|
+
print(
|
|
1044
|
+
"`rl_demo configure` prepares environment and secrets; `synth-ai run` now handles launches."
|
|
1045
|
+
)
|
|
890
1046
|
env = demo_core.load_env()
|
|
891
1047
|
synth_key = (env.synth_api_key or "").strip()
|
|
892
1048
|
if not synth_key:
|
|
@@ -919,43 +1075,65 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
919
1075
|
|
|
920
1076
|
|
|
921
1077
|
def _ensure_modal_installed() -> None:
|
|
922
|
-
"""Install the modal package if it is not already available."""
|
|
1078
|
+
"""Install the modal package if it is not already available and check authentication."""
|
|
923
1079
|
|
|
1080
|
+
# Check if modal is installed
|
|
1081
|
+
modal_installed = False
|
|
924
1082
|
try:
|
|
925
1083
|
import importlib.util as _iu
|
|
926
1084
|
|
|
927
1085
|
if _iu.find_spec("modal") is not None:
|
|
928
|
-
|
|
929
|
-
return
|
|
1086
|
+
modal_installed = True
|
|
930
1087
|
except Exception:
|
|
931
1088
|
pass
|
|
932
1089
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
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
|
|
946
1108
|
|
|
947
|
-
|
|
948
|
-
|
|
1109
|
+
# Verify modal is importable
|
|
1110
|
+
if modal_installed:
|
|
1111
|
+
try:
|
|
1112
|
+
import importlib.util as _iu
|
|
949
1113
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1114
|
+
if _iu.find_spec("modal") is None:
|
|
1115
|
+
print("Warning: modal is still not importable after install attempt.")
|
|
1116
|
+
return
|
|
1117
|
+
except Exception:
|
|
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")
|
|
956
1134
|
|
|
957
1135
|
|
|
958
|
-
def
|
|
1136
|
+
def init(template: str | None = None, dest: str | None = None, force: bool = False) -> int:
|
|
959
1137
|
"""Materialise a demo task app template into the current directory."""
|
|
960
1138
|
|
|
961
1139
|
templates = list(list_demo_templates())
|
|
@@ -964,47 +1142,101 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
964
1142
|
return 1
|
|
965
1143
|
|
|
966
1144
|
selected: DemoTemplate | None = None
|
|
967
|
-
if
|
|
968
|
-
selected = get_demo_template(
|
|
1145
|
+
if template:
|
|
1146
|
+
selected = get_demo_template(template)
|
|
969
1147
|
if selected is None:
|
|
970
1148
|
available = ", ".join(t.template_id for t in templates)
|
|
971
|
-
print(f"Unknown template '{
|
|
1149
|
+
print(f"Unknown template '{template}'. Available: {available}")
|
|
972
1150
|
return 1
|
|
973
1151
|
else:
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
print(
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
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]
|
|
990
1175
|
|
|
991
1176
|
assert selected is not None
|
|
992
1177
|
|
|
993
1178
|
default_subdir = selected.default_subdir or selected.template_id
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
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()
|
|
1190
|
+
|
|
1191
|
+
if force:
|
|
998
1192
|
dest_input = ""
|
|
1193
|
+
else:
|
|
1194
|
+
try:
|
|
1195
|
+
dest_input = input(f"Destination directory [{default_dest}]: ").strip()
|
|
1196
|
+
except Exception:
|
|
1197
|
+
dest_input = ""
|
|
999
1198
|
destination = Path(dest_input).expanduser().resolve() if dest_input else default_dest
|
|
1000
1199
|
|
|
1200
|
+
# Track whether we should skip individual file prompts (if we already cleared the directory)
|
|
1201
|
+
directory_cleared = False
|
|
1202
|
+
|
|
1001
1203
|
if destination.exists():
|
|
1002
1204
|
if destination.is_file():
|
|
1003
1205
|
print(f"Destination {destination} is a file. Provide a directory path.")
|
|
1004
1206
|
return 1
|
|
1005
|
-
if
|
|
1006
|
-
|
|
1007
|
-
|
|
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
|
|
1008
1240
|
else:
|
|
1009
1241
|
destination.mkdir(parents=True, exist_ok=True)
|
|
1010
1242
|
|
|
@@ -1018,29 +1250,95 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
1018
1250
|
print(f"Template source missing: {src_path}")
|
|
1019
1251
|
return 1
|
|
1020
1252
|
dest_path = (destination / spec.destination).resolve()
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
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
|
|
1032
1302
|
|
|
1033
1303
|
if selected.env_lines:
|
|
1034
1304
|
env_path = destination / ".env"
|
|
1035
|
-
|
|
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:
|
|
1036
1317
|
_write_text(env_path, "\n".join(selected.env_lines) + "\n")
|
|
1318
|
+
elif not directory_cleared:
|
|
1319
|
+
print("Skipping .env")
|
|
1037
1320
|
|
|
1038
1321
|
config_src = selected.config_source_path()
|
|
1039
1322
|
if config_src and config_src.exists():
|
|
1040
1323
|
cfg_dst = (destination / selected.config_destination).resolve()
|
|
1041
|
-
|
|
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:
|
|
1042
1338
|
cfg_dst.parent.mkdir(parents=True, exist_ok=True)
|
|
1043
1339
|
shutil.copy2(config_src, cfg_dst)
|
|
1340
|
+
elif not directory_cleared:
|
|
1341
|
+
print(f"Skipping {cfg_dst.name}")
|
|
1044
1342
|
|
|
1045
1343
|
if selected.post_copy is not None:
|
|
1046
1344
|
try:
|
|
@@ -1049,6 +1347,14 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
1049
1347
|
print(f"Post-processing failed: {post_exc}")
|
|
1050
1348
|
return 1
|
|
1051
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
|
+
|
|
1052
1358
|
print(f"Demo template '{selected.name}' materialised at {destination}.")
|
|
1053
1359
|
print("Files created:")
|
|
1054
1360
|
for spec in selected.iter_copy_specs():
|
|
@@ -1057,6 +1363,7 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
1057
1363
|
print(" - .env")
|
|
1058
1364
|
if selected.config_source_path():
|
|
1059
1365
|
print(f" - {selected.config_destination}")
|
|
1366
|
+
print("\nDemo directory stored. Subsequent commands will use this directory automatically.")
|
|
1060
1367
|
print("Review the files, edit .env, and run any provided deploy scripts when ready.")
|
|
1061
1368
|
return 0
|
|
1062
1369
|
except KeyboardInterrupt:
|
|
@@ -1067,8 +1374,14 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
1067
1374
|
return 1
|
|
1068
1375
|
|
|
1069
1376
|
|
|
1070
|
-
def _http(
|
|
1071
|
-
|
|
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
|
+
|
|
1072
1385
|
data = None
|
|
1073
1386
|
if body is not None:
|
|
1074
1387
|
data = _json.dumps(body).encode("utf-8")
|
|
@@ -1105,10 +1418,23 @@ def _write_text(path: str, content: str) -> None:
|
|
|
1105
1418
|
# Note: `prepare` command has been removed; configuration now prepares TOML
|
|
1106
1419
|
|
|
1107
1420
|
|
|
1108
|
-
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
|
+
|
|
1109
1435
|
env = demo_core.load_env()
|
|
1110
1436
|
cwd_env_path = os.path.join(os.getcwd(), ".env")
|
|
1111
|
-
|
|
1437
|
+
demo_core.load_dotenv_file(cwd_env_path)
|
|
1112
1438
|
|
|
1113
1439
|
synth_key = (env.synth_api_key or "").strip()
|
|
1114
1440
|
if not synth_key:
|
|
@@ -1140,7 +1466,7 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1140
1466
|
import tomllib
|
|
1141
1467
|
|
|
1142
1468
|
try:
|
|
1143
|
-
cfg_path = _select_or_create_config(
|
|
1469
|
+
cfg_path = _select_or_create_config(config, env)
|
|
1144
1470
|
except FileNotFoundError as exc:
|
|
1145
1471
|
print(exc)
|
|
1146
1472
|
return 1
|
|
@@ -1148,7 +1474,11 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1148
1474
|
# Detect monorepo launcher and delegate if available (aligns with run_clustered.sh which works)
|
|
1149
1475
|
launcher = "/Users/joshpurtell/Documents/GitHub/monorepo/tests/applications/math/rl/start_math_clustered.py"
|
|
1150
1476
|
if os.path.isfile(launcher):
|
|
1151
|
-
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
|
+
)
|
|
1152
1482
|
run_env = os.environ.copy()
|
|
1153
1483
|
run_env["BACKEND_URL"] = backend_base
|
|
1154
1484
|
run_env["SYNTH_API_KEY"] = env.synth_api_key
|
|
@@ -1158,12 +1488,12 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1158
1488
|
# Optional: TRAINER_START_URL passthrough if already set in environment
|
|
1159
1489
|
run_env["TRAINER_START_URL"] = run_env.get("TRAINER_START_URL", "")
|
|
1160
1490
|
# Forward convenience knobs
|
|
1161
|
-
if
|
|
1162
|
-
run_env["RL_BATCH_SIZE"] = str(int(
|
|
1163
|
-
if
|
|
1164
|
-
run_env["RL_GROUP_SIZE"] = str(int(
|
|
1165
|
-
if
|
|
1166
|
-
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
|
|
1167
1497
|
cmd = ["uv", "run", "python", launcher]
|
|
1168
1498
|
print(f"Launching monorepo clustered runner: {' '.join(cmd)}")
|
|
1169
1499
|
code = _popen_stream(cmd, env=run_env)
|
|
@@ -1181,30 +1511,30 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1181
1511
|
print(f" {_key_preview(sk, 'SYNTH_API_KEY')}")
|
|
1182
1512
|
if ek:
|
|
1183
1513
|
print(f" {_key_preview(ek, 'ENVIRONMENT_API_KEY')}")
|
|
1184
|
-
print(
|
|
1514
|
+
print(
|
|
1515
|
+
"Ensure the ENVIRONMENT_API_KEY you deployed with matches the task app and remains exported."
|
|
1516
|
+
)
|
|
1185
1517
|
return code
|
|
1186
1518
|
|
|
1187
1519
|
# Fallback: legacy jobs API flow
|
|
1188
1520
|
with open(cfg_path, "rb") as fh:
|
|
1189
1521
|
inline_cfg = tomllib.load(fh)
|
|
1190
|
-
with open(cfg_path
|
|
1522
|
+
with open(cfg_path) as fh2:
|
|
1191
1523
|
toml_text = fh2.read()
|
|
1192
|
-
if
|
|
1193
|
-
inline_cfg.setdefault("training", {})["batch_size"] = int(
|
|
1194
|
-
if
|
|
1195
|
-
inline_cfg.setdefault("training", {})["group_size"] = int(
|
|
1196
|
-
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")
|
|
1197
1529
|
api = env.dev_backend_url.rstrip("/") + ("" if env.dev_backend_url.endswith("/api") else "/api")
|
|
1198
1530
|
# Print backend and key preview before request for clearer diagnostics
|
|
1199
1531
|
try:
|
|
1200
1532
|
sk = (env.synth_api_key or "").strip()
|
|
1201
|
-
sk_len = len(sk)
|
|
1202
|
-
sk_tail = sk[-5:] if sk_len >= 5 else sk
|
|
1203
1533
|
print(f"[run] Backend API: {api}")
|
|
1204
1534
|
print(f"[run] {_key_preview(sk, 'SYNTH_API_KEY')}")
|
|
1205
1535
|
except Exception:
|
|
1206
1536
|
pass
|
|
1207
|
-
data_fragment:
|
|
1537
|
+
data_fragment: dict[str, Any] = {
|
|
1208
1538
|
"model": model_name,
|
|
1209
1539
|
"endpoint_base_url": env.task_app_base_url,
|
|
1210
1540
|
"config": inline_cfg,
|
|
@@ -1222,23 +1552,28 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1222
1552
|
if inline_cfg["compute"].get("gpu_type"):
|
|
1223
1553
|
compute["gpu_type"] = str(inline_cfg["compute"]["gpu_type"]).upper()
|
|
1224
1554
|
if inline_cfg["compute"].get("gpu_count"):
|
|
1225
|
-
compute["gpu_count"] = int(inline_cfg["compute"]["gpu_count"])
|
|
1555
|
+
compute["gpu_count"] = int(inline_cfg["compute"]["gpu_count"])
|
|
1226
1556
|
if not compute:
|
|
1227
1557
|
topo = inline_cfg.get("topology") or {}
|
|
1228
1558
|
gshape = str(topo.get("gpu_type") or "")
|
|
1229
1559
|
if ":" in gshape:
|
|
1230
1560
|
t, c = gshape.split(":", 1)
|
|
1231
1561
|
compute = {"gpu_type": t.upper(), "gpu_count": int(c)}
|
|
1232
|
-
body:
|
|
1562
|
+
body: dict[str, Any] = {
|
|
1233
1563
|
"job_type": "rl",
|
|
1234
1564
|
"data": data_fragment,
|
|
1235
1565
|
}
|
|
1236
1566
|
if compute:
|
|
1237
1567
|
body["compute"] = compute
|
|
1238
|
-
code, js = _http(
|
|
1239
|
-
"
|
|
1240
|
-
|
|
1241
|
-
|
|
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
|
+
)
|
|
1242
1577
|
if code not in (200, 201) or not isinstance(js, dict):
|
|
1243
1578
|
print("Job create failed:", code)
|
|
1244
1579
|
print(f"Backend: {api}")
|
|
@@ -1276,12 +1611,14 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1276
1611
|
try:
|
|
1277
1612
|
sent_key = detail.get("sent_key")
|
|
1278
1613
|
if isinstance(sent_key, str):
|
|
1279
|
-
print(
|
|
1614
|
+
print(
|
|
1615
|
+
f"[run] Backend detail.sent_key {_key_preview(sent_key, 'detail.sent_key')}"
|
|
1616
|
+
)
|
|
1280
1617
|
except Exception:
|
|
1281
1618
|
pass
|
|
1282
1619
|
try:
|
|
1283
1620
|
sent_keys = detail.get("sent_keys")
|
|
1284
|
-
if isinstance(sent_keys,
|
|
1621
|
+
if isinstance(sent_keys, list | tuple):
|
|
1285
1622
|
previews = []
|
|
1286
1623
|
for idx, val in enumerate(sent_keys):
|
|
1287
1624
|
if isinstance(val, str):
|
|
@@ -1306,12 +1643,19 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1306
1643
|
# Extra hints for auth failures
|
|
1307
1644
|
try:
|
|
1308
1645
|
sk = (env.synth_api_key or "").strip()
|
|
1309
|
-
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
|
+
):
|
|
1310
1650
|
base_url = env.dev_backend_url
|
|
1311
|
-
print(
|
|
1651
|
+
print(
|
|
1652
|
+
"Hint: HTTP 401 Unauthorized from backend. Verify SYNTH_API_KEY for:", base_url
|
|
1653
|
+
)
|
|
1312
1654
|
if sk:
|
|
1313
1655
|
print(f" {_key_preview(sk, 'SYNTH_API_KEY')}")
|
|
1314
|
-
print(
|
|
1656
|
+
print(
|
|
1657
|
+
"Ensure the ENVIRONMENT_API_KEY and OPENAI_API_KEY used for deployment remain valid."
|
|
1658
|
+
)
|
|
1315
1659
|
except Exception:
|
|
1316
1660
|
pass
|
|
1317
1661
|
return 2
|
|
@@ -1363,9 +1707,7 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1363
1707
|
"rl.performance.metrics",
|
|
1364
1708
|
):
|
|
1365
1709
|
print(f"[{seq}] {typ}: {msg}")
|
|
1366
|
-
mc, mj = _http(
|
|
1367
|
-
"GET", api + f"/learning/jobs/{job_id}/metrics?after_step=-1&limit=50"
|
|
1368
|
-
)
|
|
1710
|
+
mc, mj = _http("GET", api + f"/learning/jobs/{job_id}/metrics?after_step=-1&limit=50")
|
|
1369
1711
|
if mc == 200 and isinstance(mj, dict):
|
|
1370
1712
|
pts = mj.get("points") or []
|
|
1371
1713
|
for p in pts:
|
|
@@ -1373,62 +1715,8 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1373
1715
|
if name == "eval.reward_mean":
|
|
1374
1716
|
print(f"metric eval.reward_mean step={p.get('step')} value={p.get('value')}")
|
|
1375
1717
|
break
|
|
1376
|
-
if time.time() - start_t > (
|
|
1718
|
+
if time.time() - start_t > (timeout or 600):
|
|
1377
1719
|
print("Timeout waiting for terminal state.")
|
|
1378
1720
|
break
|
|
1379
1721
|
time.sleep(2)
|
|
1380
1722
|
return 0
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
def main(argv: list[str] | None = None) -> int:
|
|
1384
|
-
p = argparse.ArgumentParser(prog="synth-ai")
|
|
1385
|
-
sub = p.add_subparsers(dest="cmd")
|
|
1386
|
-
|
|
1387
|
-
def _add_parser(names: list[str], *, configure: Callable[[argparse.ArgumentParser], None]) -> None:
|
|
1388
|
-
for name in names:
|
|
1389
|
-
parser = sub.add_parser(name)
|
|
1390
|
-
configure(parser)
|
|
1391
|
-
|
|
1392
|
-
_add_parser(["rl_demo.setup", "demo.setup"], configure=lambda parser: parser.set_defaults(func=cmd_setup))
|
|
1393
|
-
|
|
1394
|
-
def _init_opts(parser):
|
|
1395
|
-
parser.add_argument("--template", type=str, default=None, help="Template id to instantiate")
|
|
1396
|
-
parser.add_argument("--dest", type=str, default=None, help="Destination directory for files")
|
|
1397
|
-
parser.add_argument("--force", action="store_true", help="Overwrite existing files in destination")
|
|
1398
|
-
parser.set_defaults(func=cmd_init)
|
|
1399
|
-
|
|
1400
|
-
_add_parser(["rl_demo.init", "demo.init"], configure=_init_opts)
|
|
1401
|
-
|
|
1402
|
-
# (prepare command removed)
|
|
1403
|
-
|
|
1404
|
-
def _deploy_opts(parser):
|
|
1405
|
-
parser.add_argument("--local", action="store_true", help="Run local FastAPI instead of Modal deploy")
|
|
1406
|
-
parser.add_argument("--app", type=str, default=None, help="Path to Modal app.py for uv run modal deploy")
|
|
1407
|
-
parser.add_argument("--name", type=str, default=None, help="Modal app name")
|
|
1408
|
-
parser.add_argument("--script", type=str, default=None, help="Path to deploy_task_app.sh (optional legacy)")
|
|
1409
|
-
parser.set_defaults(func=cmd_deploy)
|
|
1410
|
-
|
|
1411
|
-
_add_parser(["rl_demo.deploy", "demo.deploy"], configure=_deploy_opts)
|
|
1412
|
-
|
|
1413
|
-
_add_parser(["rl_demo.configure", "demo.configure"], configure=lambda parser: parser.set_defaults(func=cmd_run))
|
|
1414
|
-
|
|
1415
|
-
def _run_opts(parser):
|
|
1416
|
-
parser.add_argument("--config", type=str, default=None, help="Path to TOML config (skip prompt)")
|
|
1417
|
-
parser.add_argument("--batch-size", type=int, default=None)
|
|
1418
|
-
parser.add_argument("--group-size", type=int, default=None)
|
|
1419
|
-
parser.add_argument("--model", type=str, default=None)
|
|
1420
|
-
parser.add_argument("--timeout", type=int, default=600)
|
|
1421
|
-
parser.add_argument("--dry-run", action="store_true", help="Print request body and exit")
|
|
1422
|
-
parser.set_defaults(func=cmd_run)
|
|
1423
|
-
|
|
1424
|
-
_add_parser(["run", "rl_demo.run", "demo.run"], configure=_run_opts)
|
|
1425
|
-
|
|
1426
|
-
args = p.parse_args(argv)
|
|
1427
|
-
if not hasattr(args, "func"):
|
|
1428
|
-
p.print_help()
|
|
1429
|
-
return 1
|
|
1430
|
-
return int(args.func(args) or 0)
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
if __name__ == "__main__":
|
|
1434
|
-
sys.exit(main())
|