synth-ai 0.2.8.dev2__py3-none-any.whl → 0.4.3__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.
- synth_ai/__init__.py +44 -24
- synth_ai/__main__.py +30 -3
- synth_ai/cli/__init__.py +103 -48
- synth_ai/cli/__main__.py +42 -0
- synth_ai/cli/_internal/__init__.py +5 -0
- synth_ai/cli/_internal/modal_wrapper.py +31 -0
- synth_ai/cli/_internal/storage.py +20 -0
- synth_ai/cli/_internal/typer_patch.py +47 -0
- synth_ai/cli/_internal/validate_task_app.py +29 -0
- synth_ai/cli/agents/__init__.py +17 -0
- synth_ai/cli/agents/claude.py +77 -0
- synth_ai/cli/agents/codex.py +265 -0
- synth_ai/cli/agents/opencode.py +253 -0
- synth_ai/cli/commands/__init__.py +18 -0
- synth_ai/cli/commands/artifacts/__init__.py +13 -0
- synth_ai/cli/commands/artifacts/client.py +119 -0
- synth_ai/cli/commands/artifacts/config.py +57 -0
- synth_ai/cli/commands/artifacts/core.py +24 -0
- synth_ai/cli/commands/artifacts/download.py +188 -0
- synth_ai/cli/commands/artifacts/export.py +186 -0
- synth_ai/cli/commands/artifacts/list.py +156 -0
- synth_ai/cli/commands/artifacts/parsing.py +250 -0
- synth_ai/cli/commands/artifacts/show.py +336 -0
- synth_ai/cli/commands/demo/__init__.py +3 -0
- synth_ai/cli/commands/demo/core.py +153 -0
- synth_ai/cli/commands/eval/__init__.py +10 -0
- synth_ai/cli/commands/eval/config.py +338 -0
- synth_ai/cli/commands/eval/core.py +256 -0
- synth_ai/cli/commands/eval/runner.py +704 -0
- synth_ai/cli/commands/eval/validation.py +60 -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/scan/__init__.py +19 -0
- synth_ai/cli/commands/scan/cloudflare_scanner.py +403 -0
- synth_ai/cli/commands/scan/core.py +344 -0
- synth_ai/cli/commands/scan/health_checker.py +242 -0
- synth_ai/cli/commands/scan/local_scanner.py +278 -0
- synth_ai/cli/commands/scan/models.py +83 -0
- synth_ai/cli/commands/smoke/__init__.py +7 -0
- synth_ai/cli/commands/smoke/core.py +1428 -0
- synth_ai/cli/commands/status/__init__.py +3 -0
- synth_ai/cli/commands/status/client.py +91 -0
- synth_ai/cli/commands/status/config.py +12 -0
- synth_ai/cli/commands/status/errors.py +11 -0
- synth_ai/cli/commands/status/subcommands/__init__.py +3 -0
- synth_ai/cli/commands/status/subcommands/config.py +13 -0
- synth_ai/cli/commands/status/subcommands/files.py +34 -0
- synth_ai/cli/commands/status/subcommands/jobs.py +51 -0
- synth_ai/cli/commands/status/subcommands/models.py +35 -0
- synth_ai/cli/commands/status/subcommands/runs.py +34 -0
- synth_ai/cli/commands/status/subcommands/session.py +77 -0
- synth_ai/cli/commands/status/subcommands/summary.py +39 -0
- synth_ai/cli/commands/status/subcommands/utils.py +41 -0
- synth_ai/cli/commands/status/utils.py +23 -0
- synth_ai/cli/commands/train/__init__.py +53 -0
- synth_ai/cli/commands/train/core.py +22 -0
- synth_ai/cli/commands/train/errors.py +117 -0
- synth_ai/cli/commands/train/judge_schemas.py +201 -0
- synth_ai/cli/commands/train/judge_validation.py +305 -0
- synth_ai/cli/commands/train/prompt_learning_validation.py +633 -0
- synth_ai/cli/commands/train/validation.py +392 -0
- synth_ai/cli/demo_apps/__init__.py +10 -0
- synth_ai/cli/demo_apps/core/__init__.py +28 -0
- synth_ai/{demos → cli/demo_apps}/core/cli.py +783 -441
- synth_ai/cli/demo_apps/crafter/__init__.py +1 -0
- synth_ai/cli/demo_apps/crafter/crafter_fft_4b.toml +55 -0
- synth_ai/cli/demo_apps/crafter/grpo_crafter_task_app.py +186 -0
- synth_ai/cli/demo_apps/crafter/rl_from_base_qwen4b.toml +74 -0
- synth_ai/cli/demo_apps/demo_registry.py +176 -0
- synth_ai/cli/demo_apps/demo_task_apps/__init__.py +7 -0
- synth_ai/{demos → cli/demo_apps}/demo_task_apps/core.py +75 -37
- synth_ai/cli/demo_apps/demo_task_apps/crafter/__init__.py +1 -0
- synth_ai/cli/demo_apps/demo_task_apps/crafter/configs/crafter_fft_4b.toml +53 -0
- synth_ai/cli/demo_apps/demo_task_apps/crafter/configs/rl_from_base_qwen4b.toml +73 -0
- synth_ai/cli/demo_apps/demo_task_apps/crafter/grpo_crafter_task_app.py +185 -0
- synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/_common.py +1 -2
- synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/app.py +2 -1
- synth_ai/cli/demo_apps/demo_task_apps/math/config.toml +73 -0
- synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/deploy_modal.py +3 -6
- synth_ai/cli/demo_apps/demo_task_apps/math/modal_task_app.py +738 -0
- synth_ai/cli/demo_apps/demo_task_apps/math/task_app_entry.py +39 -0
- synth_ai/cli/demo_apps/math/__init__.py +1 -0
- synth_ai/cli/demo_apps/math/_common.py +16 -0
- synth_ai/cli/demo_apps/math/app.py +38 -0
- synth_ai/cli/demo_apps/math/config.toml +75 -0
- synth_ai/cli/demo_apps/math/deploy_modal.py +54 -0
- synth_ai/cli/demo_apps/math/modal_task_app.py +698 -0
- synth_ai/cli/demo_apps/math/task_app_entry.py +53 -0
- synth_ai/cli/demo_apps/mipro/main.py +271 -0
- synth_ai/cli/demo_apps/mipro/task_app.py +922 -0
- synth_ai/cli/demo_apps/mipro/train_cfg.toml +92 -0
- synth_ai/cli/demos/__init__.py +12 -0
- synth_ai/cli/demos/demo.py +32 -0
- synth_ai/cli/demos/rl_demo.py +254 -0
- synth_ai/cli/deploy.py +216 -0
- synth_ai/cli/infra/__init__.py +14 -0
- synth_ai/cli/{balance.py → infra/balance.py} +16 -4
- synth_ai/cli/infra/mcp.py +35 -0
- synth_ai/cli/infra/modal_app.py +36 -0
- synth_ai/cli/infra/setup.py +69 -0
- synth_ai/cli/infra/status.py +16 -0
- synth_ai/cli/infra/turso.py +77 -0
- synth_ai/cli/lib/__init__.py +10 -0
- synth_ai/cli/lib/agents.py +76 -0
- synth_ai/cli/lib/apps/modal_app.py +101 -0
- synth_ai/cli/lib/apps/task_app.py +642 -0
- synth_ai/cli/lib/bin.py +39 -0
- synth_ai/cli/lib/env.py +375 -0
- synth_ai/cli/lib/errors.py +85 -0
- synth_ai/cli/lib/modal.py +315 -0
- synth_ai/cli/lib/plotting.py +126 -0
- synth_ai/cli/lib/prompt_args.py +39 -0
- synth_ai/cli/lib/prompts.py +284 -0
- synth_ai/cli/lib/sqld.py +122 -0
- synth_ai/cli/lib/task_app_discovery.py +884 -0
- synth_ai/cli/lib/task_app_env.py +295 -0
- synth_ai/cli/lib/train_cfgs.py +300 -0
- synth_ai/cli/lib/tunnel_records.py +207 -0
- synth_ai/cli/local/__init__.py +14 -0
- synth_ai/cli/local/experiment_queue/__init__.py +72 -0
- synth_ai/cli/local/experiment_queue/api_schemas.py +221 -0
- synth_ai/cli/local/experiment_queue/celery_app.py +208 -0
- synth_ai/cli/local/experiment_queue/config.py +128 -0
- synth_ai/cli/local/experiment_queue/config_utils.py +272 -0
- synth_ai/cli/local/experiment_queue/database.py +175 -0
- synth_ai/cli/local/experiment_queue/dispatcher.py +119 -0
- synth_ai/cli/local/experiment_queue/models.py +231 -0
- synth_ai/cli/local/experiment_queue/progress_info.py +160 -0
- synth_ai/cli/local/experiment_queue/results.py +373 -0
- synth_ai/cli/local/experiment_queue/schemas.py +131 -0
- synth_ai/cli/local/experiment_queue/service.py +344 -0
- synth_ai/cli/local/experiment_queue/status.py +372 -0
- synth_ai/cli/local/experiment_queue/status_tracker.py +360 -0
- synth_ai/cli/local/experiment_queue/tasks.py +1984 -0
- synth_ai/cli/local/experiment_queue/trace_storage.py +65 -0
- synth_ai/cli/local/experiment_queue/validation.py +157 -0
- synth_ai/cli/local/session/__init__.py +92 -0
- synth_ai/cli/local/session/client.py +383 -0
- synth_ai/cli/local/session/constants.py +63 -0
- synth_ai/cli/local/session/exceptions.py +105 -0
- synth_ai/cli/local/session/manager.py +139 -0
- synth_ai/cli/local/session/models.py +89 -0
- synth_ai/cli/local/session/query.py +110 -0
- synth_ai/cli/root.py +150 -108
- synth_ai/cli/task_apps/__init__.py +37 -0
- synth_ai/cli/task_apps/commands.py +3145 -0
- synth_ai/cli/task_apps/deploy.py +7 -0
- synth_ai/cli/task_apps/list.py +26 -0
- synth_ai/cli/task_apps/main.py +36 -0
- synth_ai/cli/task_apps/modal_serve.py +11 -0
- synth_ai/cli/task_apps/serve.py +11 -0
- synth_ai/cli/training/__init__.py +8 -0
- synth_ai/cli/training/train.py +5 -0
- synth_ai/cli/training/train_cfg.py +34 -0
- synth_ai/cli/{watch.py → training/watch.py} +13 -18
- synth_ai/cli/turso.py +52 -0
- synth_ai/cli/utils/__init__.py +8 -0
- synth_ai/cli/utils/experiments.py +235 -0
- synth_ai/cli/utils/queue.py +504 -0
- synth_ai/cli/{recent.py → utils/recent.py} +13 -7
- synth_ai/cli/{traces.py → utils/traces.py} +9 -5
- synth_ai/contracts/__init__.py +67 -0
- synth_ai/core/__init__.py +100 -0
- synth_ai/core/_utils/__init__.py +54 -0
- synth_ai/core/_utils/base_url.py +10 -0
- synth_ai/core/_utils/http.py +10 -0
- synth_ai/core/_utils/prompts.py +14 -0
- synth_ai/core/_utils/task_app_state.py +12 -0
- synth_ai/core/_utils/user_config.py +10 -0
- synth_ai/core/apps/common.py +116 -0
- synth_ai/core/auth.py +95 -0
- synth_ai/core/cfgs.py +240 -0
- synth_ai/core/config/__init__.py +16 -0
- synth_ai/core/config/base.py +168 -0
- synth_ai/core/config/resolver.py +89 -0
- synth_ai/core/env.py +231 -0
- synth_ai/core/errors.py +126 -0
- synth_ai/core/http.py +230 -0
- synth_ai/core/integrations/__init__.py +11 -0
- synth_ai/core/integrations/cloudflare.py +1710 -0
- synth_ai/core/integrations/mcp/__init__.py +6 -0
- synth_ai/core/integrations/mcp/__main__.py +8 -0
- synth_ai/core/integrations/mcp/claude.py +36 -0
- synth_ai/core/integrations/mcp/main.py +254 -0
- synth_ai/core/integrations/mcp/setup.py +100 -0
- synth_ai/core/integrations/modal.py +277 -0
- synth_ai/core/json.py +72 -0
- synth_ai/core/log_filter.py +99 -0
- synth_ai/core/logging.py +82 -0
- synth_ai/core/paths.py +107 -0
- synth_ai/core/pricing.py +109 -0
- synth_ai/core/process.py +233 -0
- synth_ai/core/ssl.py +25 -0
- synth_ai/core/storage/__init__.py +71 -0
- synth_ai/core/task_app_state.py +318 -0
- synth_ai/core/telemetry.py +282 -0
- synth_ai/{tracing_v3 → core/tracing_v3}/__init__.py +5 -1
- synth_ai/{tracing_v3 → core/tracing_v3}/abstractions.py +21 -4
- synth_ai/core/tracing_v3/config.py +229 -0
- synth_ai/core/tracing_v3/constants.py +21 -0
- synth_ai/{tracing_v3 → core/tracing_v3}/db_config.py +42 -29
- synth_ai/{tracing_v3 → core/tracing_v3}/decorators.py +80 -45
- synth_ai/{tracing_v3 → core/tracing_v3}/examples/basic_usage.py +15 -9
- synth_ai/{tracing_v3 → core/tracing_v3}/hooks.py +6 -4
- synth_ai/{tracing_v3 → core/tracing_v3}/llm_call_record_helpers.py +161 -61
- synth_ai/{tracing_v3 → core/tracing_v3}/migration_helper.py +1 -2
- synth_ai/{tracing_v3 → core/tracing_v3}/replica_sync.py +12 -7
- synth_ai/core/tracing_v3/serialization.py +130 -0
- synth_ai/{tracing_v3 → core/tracing_v3}/session_tracer.py +88 -21
- synth_ai/{tracing_v3 → core/tracing_v3}/storage/base.py +99 -12
- synth_ai/core/tracing_v3/storage/config.py +109 -0
- synth_ai/{tracing_v3 → core/tracing_v3}/storage/factory.py +11 -9
- synth_ai/{tracing_v3 → core/tracing_v3}/storage/utils.py +15 -11
- synth_ai/core/tracing_v3/trace_utils.py +326 -0
- synth_ai/core/tracing_v3/turso/__init__.py +12 -0
- synth_ai/core/tracing_v3/turso/daemon.py +278 -0
- synth_ai/{tracing_v3 → core/tracing_v3}/turso/models.py +7 -3
- synth_ai/core/tracing_v3/turso/native_manager.py +1385 -0
- synth_ai/{tracing_v3 → core/tracing_v3}/utils.py +5 -4
- synth_ai/core/urls.py +18 -0
- synth_ai/core/user_config.py +137 -0
- synth_ai/core/uvicorn.py +222 -0
- synth_ai/data/__init__.py +83 -0
- synth_ai/data/enums.py +123 -0
- synth_ai/data/rewards.py +152 -0
- synth_ai/data/traces.py +35 -0
- synth_ai/products/__init__.py +6 -0
- synth_ai/products/graph_evolve/__init__.py +46 -0
- synth_ai/products/graph_evolve/client.py +226 -0
- synth_ai/products/graph_evolve/config.py +591 -0
- synth_ai/products/graph_evolve/converters/__init__.py +42 -0
- synth_ai/products/graph_evolve/converters/openai_sft.py +484 -0
- synth_ai/products/graph_evolve/examples/hotpotqa/config.toml +109 -0
- synth_ai/products/graph_evolve/run.py +222 -0
- synth_ai/products/graph_gepa/__init__.py +23 -0
- synth_ai/products/graph_gepa/converters/__init__.py +19 -0
- synth_ai/products/graph_gepa/converters/openai_sft.py +29 -0
- synth_ai/sdk/__init__.py +123 -0
- synth_ai/sdk/api/__init__.py +1 -0
- synth_ai/sdk/api/models/supported.py +514 -0
- synth_ai/sdk/api/research_agent/__init__.py +296 -0
- synth_ai/sdk/api/train/__init__.py +85 -0
- synth_ai/sdk/api/train/builders.py +895 -0
- synth_ai/sdk/api/train/cli.py +2199 -0
- synth_ai/sdk/api/train/config_finder.py +267 -0
- synth_ai/sdk/api/train/configs/__init__.py +65 -0
- synth_ai/sdk/api/train/configs/prompt_learning.py +1706 -0
- synth_ai/sdk/api/train/configs/rl.py +187 -0
- synth_ai/sdk/api/train/configs/sft.py +99 -0
- synth_ai/sdk/api/train/configs/shared.py +81 -0
- synth_ai/sdk/api/train/context_learning.py +312 -0
- synth_ai/sdk/api/train/env_resolver.py +418 -0
- synth_ai/sdk/api/train/graph_validators.py +216 -0
- synth_ai/sdk/api/train/graphgen.py +984 -0
- synth_ai/sdk/api/train/graphgen_models.py +823 -0
- synth_ai/sdk/api/train/graphgen_validators.py +109 -0
- synth_ai/sdk/api/train/local_api.py +10 -0
- synth_ai/sdk/api/train/pollers.py +124 -0
- synth_ai/sdk/api/train/progress/__init__.py +97 -0
- synth_ai/sdk/api/train/progress/dataclasses.py +569 -0
- synth_ai/sdk/api/train/progress/events.py +326 -0
- synth_ai/sdk/api/train/progress/results.py +428 -0
- synth_ai/sdk/api/train/progress/tracker.py +641 -0
- synth_ai/sdk/api/train/prompt_learning.py +469 -0
- synth_ai/sdk/api/train/rl.py +441 -0
- synth_ai/sdk/api/train/sft.py +396 -0
- synth_ai/sdk/api/train/summary.py +522 -0
- synth_ai/sdk/api/train/supported_algos.py +147 -0
- synth_ai/sdk/api/train/task_app.py +351 -0
- synth_ai/sdk/api/train/utils.py +279 -0
- synth_ai/sdk/api/train/validators.py +2424 -0
- synth_ai/sdk/graphs/__init__.py +15 -0
- synth_ai/sdk/graphs/completions.py +570 -0
- synth_ai/{inference → sdk/inference}/__init__.py +0 -1
- synth_ai/sdk/inference/client.py +128 -0
- synth_ai/sdk/jobs/__init__.py +16 -0
- synth_ai/sdk/jobs/client.py +371 -0
- synth_ai/sdk/judging/__init__.py +14 -0
- synth_ai/sdk/judging/base.py +24 -0
- synth_ai/sdk/judging/client.py +40 -0
- synth_ai/sdk/judging/schemas.py +222 -0
- synth_ai/sdk/judging/types.py +42 -0
- synth_ai/sdk/learning/__init__.py +99 -0
- synth_ai/sdk/learning/algorithms.py +14 -0
- synth_ai/{learning → sdk/learning}/client.py +121 -30
- synth_ai/sdk/learning/config.py +5 -0
- synth_ai/{learning → sdk/learning}/constants.py +0 -2
- synth_ai/sdk/learning/context_learning_client.py +531 -0
- synth_ai/sdk/learning/context_learning_types.py +292 -0
- synth_ai/sdk/learning/ft_client.py +7 -0
- synth_ai/{learning → sdk/learning}/health.py +15 -9
- synth_ai/{learning → sdk/learning}/jobs.py +44 -47
- synth_ai/sdk/learning/prompt_extraction.py +334 -0
- synth_ai/sdk/learning/prompt_learning_client.py +455 -0
- synth_ai/sdk/learning/prompt_learning_types.py +186 -0
- synth_ai/{rl → sdk/learning/rl}/__init__.py +13 -8
- synth_ai/{learning/rl_client.py → sdk/learning/rl/client.py} +89 -77
- synth_ai/sdk/learning/rl/config.py +31 -0
- synth_ai/{rl → sdk/learning/rl}/contracts.py +5 -14
- synth_ai/{rl → sdk/learning/rl}/env_keys.py +45 -16
- synth_ai/sdk/learning/rl/secrets.py +13 -0
- synth_ai/sdk/learning/rl_client.py +5 -0
- synth_ai/sdk/learning/sft/__init__.py +29 -0
- synth_ai/sdk/learning/sft/client.py +95 -0
- synth_ai/sdk/learning/sft/config.py +270 -0
- synth_ai/sdk/learning/sft/data.py +698 -0
- synth_ai/sdk/learning/sse.py +57 -0
- synth_ai/sdk/learning/validators.py +52 -0
- synth_ai/sdk/localapi/__init__.py +40 -0
- synth_ai/sdk/localapi/apps/__init__.py +28 -0
- synth_ai/sdk/localapi/client.py +10 -0
- synth_ai/sdk/localapi/contracts.py +10 -0
- synth_ai/sdk/localapi/helpers.py +519 -0
- synth_ai/sdk/localapi/rollouts.py +87 -0
- synth_ai/sdk/localapi/server.py +29 -0
- synth_ai/sdk/localapi/template.py +70 -0
- synth_ai/sdk/streaming/__init__.py +35 -0
- synth_ai/sdk/streaming/config.py +94 -0
- synth_ai/sdk/streaming/handlers.py +1997 -0
- synth_ai/sdk/streaming/streamer.py +713 -0
- synth_ai/sdk/streaming/types.py +112 -0
- synth_ai/sdk/task/__init__.py +164 -0
- synth_ai/sdk/task/apps/__init__.py +169 -0
- synth_ai/sdk/task/auth.py +165 -0
- synth_ai/sdk/task/client.py +175 -0
- synth_ai/sdk/task/config.py +257 -0
- synth_ai/sdk/task/contracts.py +219 -0
- synth_ai/sdk/task/datasets.py +108 -0
- synth_ai/sdk/task/errors.py +50 -0
- synth_ai/sdk/task/health.py +34 -0
- synth_ai/sdk/task/in_process.py +1190 -0
- synth_ai/sdk/task/in_process_runner.py +314 -0
- synth_ai/sdk/task/inference_api.py +299 -0
- synth_ai/sdk/task/json.py +111 -0
- synth_ai/sdk/task/proxy.py +287 -0
- synth_ai/sdk/task/rubrics/__init__.py +55 -0
- synth_ai/sdk/task/rubrics/loaders.py +156 -0
- synth_ai/sdk/task/rubrics/models.py +57 -0
- synth_ai/sdk/task/rubrics/scoring.py +116 -0
- synth_ai/sdk/task/rubrics/strict.py +149 -0
- synth_ai/sdk/task/rubrics.py +219 -0
- synth_ai/sdk/task/server.py +631 -0
- synth_ai/sdk/task/trace_correlation_helpers.py +539 -0
- synth_ai/sdk/task/tracing_utils.py +95 -0
- synth_ai/sdk/task/validators.py +441 -0
- synth_ai/sdk/task/vendors.py +59 -0
- synth_ai/sdk/training/__init__.py +102 -0
- synth_ai/sdk/tunnels/__init__.py +83 -0
- synth_ai/sdk/tunnels/cleanup.py +83 -0
- synth_ai/sdk/tunnels/ports.py +120 -0
- synth_ai/utils/__init__.py +213 -0
- synth_ai-0.4.3.dist-info/METADATA +262 -0
- synth_ai-0.4.3.dist-info/RECORD +370 -0
- {synth_ai-0.2.8.dev2.dist-info → synth_ai-0.4.3.dist-info}/entry_points.txt +0 -1
- synth_ai/cli/calc.py +0 -69
- synth_ai/cli/demo.py +0 -144
- synth_ai/cli/legacy_root_backup.py +0 -470
- synth_ai/cli/man.py +0 -106
- synth_ai/cli/rl_demo.py +0 -202
- synth_ai/cli/status.py +0 -133
- synth_ai/config/base_url.py +0 -107
- synth_ai/core/experiment.py +0 -15
- synth_ai/core/system.py +0 -15
- synth_ai/demos/core/__init__.py +0 -1
- synth_ai/demos/demo_task_apps/__init__.py +0 -1
- synth_ai/demos/demo_task_apps/math/config.toml +0 -129
- synth_ai/demos/demo_task_apps/math/deploy_task_app.sh +0 -22
- synth_ai/demos/demo_task_apps/math/modal_task_app.py +0 -415
- synth_ai/environments/__init__.py +0 -31
- synth_ai/environments/environment/__init__.py +0 -1
- synth_ai/environments/environment/artifacts/__init__.py +0 -1
- synth_ai/environments/environment/artifacts/base.py +0 -52
- synth_ai/environments/environment/core.py +0 -67
- synth_ai/environments/environment/db/__init__.py +0 -1
- synth_ai/environments/environment/db/sqlite.py +0 -45
- synth_ai/environments/environment/registry.py +0 -233
- synth_ai/environments/environment/resources/sqlite.py +0 -45
- synth_ai/environments/environment/results.py +0 -1
- synth_ai/environments/environment/rewards/__init__.py +0 -1
- synth_ai/environments/environment/rewards/core.py +0 -29
- synth_ai/environments/environment/shared_engine.py +0 -26
- synth_ai/environments/environment/tools/__init__.py +0 -200
- synth_ai/environments/examples/__init__.py +0 -1
- synth_ai/environments/examples/bandit/__init__.py +0 -33
- synth_ai/environments/examples/bandit/engine.py +0 -294
- synth_ai/environments/examples/bandit/environment.py +0 -194
- synth_ai/environments/examples/bandit/taskset.py +0 -200
- synth_ai/environments/examples/crafter_classic/__init__.py +0 -8
- synth_ai/environments/examples/crafter_classic/agent_demos/analyze_semantic_words_markdown.py +0 -250
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_comprehensive_evaluation.py +0 -59
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_browser.py +0 -152
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_config.toml +0 -24
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_framework.py +0 -1194
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/crafter_synth_config.toml +0 -56
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_config_modal.toml +0 -32
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_traces_sft_turso.py +0 -738
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/kick_off_ft_modal.py +0 -384
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_action_results.py +0 -53
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_agent_actions.py +0 -178
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_latest_run.py +0 -222
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_lm_traces.py +0 -183
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_no_rewards.py +0 -210
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_trace_issue.py +0 -206
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/check_db_schema.py +0 -49
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/check_latest_results.py +0 -64
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/debug_agent_responses.py +0 -88
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/quick_trace_check.py +0 -77
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/compare_experiments.py +0 -324
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/filter_traces_sft_turso.py +0 -580
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/kick_off_ft_oai.py +0 -362
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/multi_model_config.toml +0 -49
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_enhanced_hooks.py +0 -332
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_hook_events.py +0 -97
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_hook_results.py +0 -217
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/check_hook_storage.py +0 -87
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/check_seeds.py +0 -88
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/compare_seed_performance.py +0 -195
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/custom_eval_pipelines.py +0 -400
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/plot_hook_frequency.py +0 -195
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/seed_analysis_summary.py +0 -56
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/run_rollouts_for_models_and_compare_v3.py +0 -858
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_quick_evaluation.py +0 -52
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_react_agent.py +0 -874
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +0 -1412
- synth_ai/environments/examples/crafter_classic/agent_demos/example_v3_usage.py +0 -216
- synth_ai/environments/examples/crafter_classic/agent_demos/old/compare_traces.py +0 -296
- synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_comprehensive_evaluation.py +0 -58
- synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_env_serialization.py +0 -464
- synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_evaluation_browser.py +0 -152
- synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_quick_evaluation.py +0 -51
- synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_trace_evaluation.py +0 -1412
- synth_ai/environments/examples/crafter_classic/agent_demos/old/debug_player_loss.py +0 -112
- synth_ai/environments/examples/crafter_classic/agent_demos/old/diagnose_service.py +0 -203
- synth_ai/environments/examples/crafter_classic/agent_demos/old/diagnose_slowness.py +0 -305
- synth_ai/environments/examples/crafter_classic/agent_demos/old/eval_by_difficulty.py +0 -126
- synth_ai/environments/examples/crafter_classic/agent_demos/old/eval_example.py +0 -94
- synth_ai/environments/examples/crafter_classic/agent_demos/old/explore_saved_states.py +0 -142
- synth_ai/environments/examples/crafter_classic/agent_demos/old/filter_traces_sft.py +0 -26
- synth_ai/environments/examples/crafter_classic/agent_demos/old/filter_traces_sft_OLD.py +0 -984
- synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_data_gemini.py +0 -724
- synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_data_modal.py +0 -386
- synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_metadata.py +0 -205
- synth_ai/environments/examples/crafter_classic/agent_demos/old/kick_off_ft_gemini.py +0 -150
- synth_ai/environments/examples/crafter_classic/agent_demos/old/kick_off_ft_modal.py +0 -283
- synth_ai/environments/examples/crafter_classic/agent_demos/old/prepare_vertex_ft.py +0 -280
- synth_ai/environments/examples/crafter_classic/agent_demos/old/profile_env_slowness.py +0 -456
- synth_ai/environments/examples/crafter_classic/agent_demos/old/replicate_issue.py +0 -166
- synth_ai/environments/examples/crafter_classic/agent_demos/old/run_and_eval.py +0 -102
- synth_ai/environments/examples/crafter_classic/agent_demos/old/run_comparison.py +0 -128
- synth_ai/environments/examples/crafter_classic/agent_demos/old/run_qwen_rollouts.py +0 -655
- synth_ai/environments/examples/crafter_classic/agent_demos/old/trace_eval_OLD.py +0 -202
- synth_ai/environments/examples/crafter_classic/agent_demos/old/validate_openai_format.py +0 -166
- synth_ai/environments/examples/crafter_classic/config_logging.py +0 -111
- synth_ai/environments/examples/crafter_classic/debug_translation.py +0 -0
- synth_ai/environments/examples/crafter_classic/engine.py +0 -579
- synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +0 -64
- synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +0 -6
- synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +0 -75
- synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +0 -267
- synth_ai/environments/examples/crafter_classic/environment.py +0 -404
- synth_ai/environments/examples/crafter_classic/taskset.py +0 -233
- synth_ai/environments/examples/crafter_classic/trace_hooks_v3.py +0 -228
- synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +0 -299
- synth_ai/environments/examples/crafter_custom/__init__.py +0 -4
- synth_ai/environments/examples/crafter_custom/agent_demos/__init__.py +0 -1
- synth_ai/environments/examples/crafter_custom/agent_demos/trace_eval.py +0 -202
- synth_ai/environments/examples/crafter_custom/crafter/__init__.py +0 -7
- synth_ai/environments/examples/crafter_custom/crafter/config.py +0 -182
- synth_ai/environments/examples/crafter_custom/crafter/constants.py +0 -8
- synth_ai/environments/examples/crafter_custom/crafter/engine.py +0 -269
- synth_ai/environments/examples/crafter_custom/crafter/env.py +0 -262
- synth_ai/environments/examples/crafter_custom/crafter/objects.py +0 -417
- synth_ai/environments/examples/crafter_custom/crafter/recorder.py +0 -187
- synth_ai/environments/examples/crafter_custom/crafter/worldgen.py +0 -118
- synth_ai/environments/examples/crafter_custom/dataset_builder.py +0 -373
- synth_ai/environments/examples/crafter_custom/environment.py +0 -312
- synth_ai/environments/examples/crafter_custom/old/analyze_diamond_issue.py +0 -159
- synth_ai/environments/examples/crafter_custom/old/analyze_diamond_spawning.py +0 -158
- synth_ai/environments/examples/crafter_custom/old/compare_worlds.py +0 -71
- synth_ai/environments/examples/crafter_custom/old/dataset_stats.py +0 -105
- synth_ai/environments/examples/crafter_custom/old/diamond_spawning_summary.py +0 -119
- synth_ai/environments/examples/crafter_custom/old/example_dataset_usage.py +0 -52
- synth_ai/environments/examples/crafter_custom/run_dataset.py +0 -305
- synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +0 -156
- synth_ai/environments/examples/enron/art_helpers/local_email_db.py +0 -281
- synth_ai/environments/examples/enron/art_helpers/types_enron.py +0 -25
- synth_ai/environments/examples/enron/engine.py +0 -295
- synth_ai/environments/examples/enron/environment.py +0 -166
- synth_ai/environments/examples/enron/taskset.py +0 -112
- synth_ai/environments/examples/enron/units/keyword_stats.py +0 -112
- synth_ai/environments/examples/minigrid/__init__.py +0 -48
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_evaluation_framework.py +0 -1188
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_quick_evaluation.py +0 -48
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_react_agent.py +0 -562
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_trace_evaluation.py +0 -221
- synth_ai/environments/examples/minigrid/engine.py +0 -589
- synth_ai/environments/examples/minigrid/environment.py +0 -274
- synth_ai/environments/examples/minigrid/environment_mapping.py +0 -242
- synth_ai/environments/examples/minigrid/puzzle_loader.py +0 -417
- synth_ai/environments/examples/minigrid/taskset.py +0 -583
- synth_ai/environments/examples/nethack/__init__.py +0 -7
- synth_ai/environments/examples/nethack/achievements.py +0 -337
- synth_ai/environments/examples/nethack/agent_demos/nethack_evaluation_framework.py +0 -981
- synth_ai/environments/examples/nethack/agent_demos/nethack_quick_evaluation.py +0 -74
- synth_ai/environments/examples/nethack/agent_demos/nethack_react_agent.py +0 -831
- synth_ai/environments/examples/nethack/engine.py +0 -739
- synth_ai/environments/examples/nethack/environment.py +0 -256
- synth_ai/environments/examples/nethack/helpers/__init__.py +0 -41
- synth_ai/environments/examples/nethack/helpers/action_mapping.py +0 -301
- synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +0 -402
- synth_ai/environments/examples/nethack/helpers/observation_utils.py +0 -433
- synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +0 -200
- synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +0 -269
- synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +0 -308
- synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +0 -431
- synth_ai/environments/examples/nethack/taskset.py +0 -323
- synth_ai/environments/examples/red/__init__.py +0 -7
- synth_ai/environments/examples/red/agent_demos/__init__.py +0 -1
- synth_ai/environments/examples/red/config_logging.py +0 -110
- synth_ai/environments/examples/red/engine.py +0 -694
- synth_ai/environments/examples/red/engine_helpers/__init__.py +0 -1
- synth_ai/environments/examples/red/engine_helpers/memory_map.py +0 -28
- synth_ai/environments/examples/red/engine_helpers/reward_components.py +0 -276
- synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +0 -142
- synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +0 -57
- synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +0 -284
- synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +0 -150
- synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +0 -138
- synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +0 -57
- synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +0 -331
- synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +0 -121
- synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +0 -559
- synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +0 -313
- synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +0 -148
- synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +0 -247
- synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +0 -368
- synth_ai/environments/examples/red/engine_helpers/state_extraction.py +0 -140
- synth_ai/environments/examples/red/environment.py +0 -238
- synth_ai/environments/examples/red/taskset.py +0 -79
- synth_ai/environments/examples/red/units/__init__.py +0 -1
- synth_ai/environments/examples/sokoban/__init__.py +0 -1
- synth_ai/environments/examples/sokoban/agent_demos/sokoban_full_eval.py +0 -899
- synth_ai/environments/examples/sokoban/engine.py +0 -678
- synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +0 -1
- synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +0 -657
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +0 -18
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +0 -3
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +0 -131
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +0 -370
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +0 -332
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +0 -306
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +0 -67
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +0 -115
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +0 -123
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +0 -394
- synth_ai/environments/examples/sokoban/environment.py +0 -229
- synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +0 -440
- synth_ai/environments/examples/sokoban/puzzle_loader.py +0 -312
- synth_ai/environments/examples/sokoban/taskset.py +0 -428
- synth_ai/environments/examples/sokoban/units/astar_common.py +0 -95
- synth_ai/environments/examples/tictactoe/__init__.py +0 -1
- synth_ai/environments/examples/tictactoe/engine.py +0 -368
- synth_ai/environments/examples/tictactoe/environment.py +0 -240
- synth_ai/environments/examples/tictactoe/taskset.py +0 -215
- synth_ai/environments/examples/verilog/__init__.py +0 -10
- synth_ai/environments/examples/verilog/engine.py +0 -329
- synth_ai/environments/examples/verilog/environment.py +0 -350
- synth_ai/environments/examples/verilog/taskset.py +0 -420
- synth_ai/environments/examples/wordle/__init__.py +0 -29
- synth_ai/environments/examples/wordle/engine.py +0 -398
- synth_ai/environments/examples/wordle/environment.py +0 -159
- synth_ai/environments/examples/wordle/helpers/generate_instances_wordfreq.py +0 -75
- synth_ai/environments/examples/wordle/taskset.py +0 -230
- synth_ai/environments/reproducibility/core.py +0 -42
- synth_ai/environments/reproducibility/helpers.py +0 -0
- synth_ai/environments/reproducibility/tree.py +0 -364
- synth_ai/environments/service/app.py +0 -98
- synth_ai/environments/service/core_routes.py +0 -1020
- synth_ai/environments/service/external_registry.py +0 -56
- synth_ai/environments/service/registry.py +0 -9
- synth_ai/environments/stateful/__init__.py +0 -1
- synth_ai/environments/stateful/core.py +0 -163
- synth_ai/environments/stateful/engine.py +0 -21
- synth_ai/environments/stateful/state.py +0 -7
- synth_ai/environments/tasks/api.py +0 -19
- synth_ai/environments/tasks/core.py +0 -80
- synth_ai/environments/tasks/filters.py +0 -41
- synth_ai/environments/tasks/utils.py +0 -91
- synth_ai/environments/v0_observability/history.py +0 -3
- synth_ai/environments/v0_observability/log.py +0 -2
- synth_ai/evals/base.py +0 -15
- synth_ai/experimental/synth_oss.py +0 -446
- synth_ai/handshake.py +0 -63
- synth_ai/http.py +0 -26
- synth_ai/http_client.py +0 -104
- synth_ai/inference/client.py +0 -20
- synth_ai/install_sqld.sh +0 -40
- synth_ai/jobs/client.py +0 -246
- synth_ai/learning/__init__.py +0 -24
- synth_ai/learning/config.py +0 -43
- synth_ai/learning/filtering.py +0 -0
- synth_ai/learning/ft_client.py +0 -59
- 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/learning/sse.py +0 -58
- synth_ai/learning/validators.py +0 -48
- 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/__init__.py +0 -0
- 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/__init__.py +0 -0
- 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/__init__.py +0 -0
- synth_ai/lm/vendors/base.py +0 -81
- synth_ai/lm/vendors/core/__init__.py +0 -0
- 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/__init__.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/__init__.py +0 -0
- 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/__init__.py +0 -10
- synth_ai/task/contracts.py +0 -120
- synth_ai/task/health.py +0 -28
- synth_ai/task/validators.py +0 -12
- synth_ai/tracing/__init__.py +0 -30
- synth_ai/tracing_v1/__init__.py +0 -33
- synth_ai/tracing_v3/config.py +0 -84
- synth_ai/tracing_v3/storage/config.py +0 -62
- synth_ai/tracing_v3/turso/__init__.py +0 -25
- synth_ai/tracing_v3/turso/daemon.py +0 -144
- synth_ai/tracing_v3/turso/manager.py +0 -760
- synth_ai/v0/tracing/__init__.py +0 -0
- synth_ai/v0/tracing/abstractions.py +0 -224
- synth_ai/v0/tracing/base_client.py +0 -91
- synth_ai/v0/tracing/client_manager.py +0 -131
- synth_ai/v0/tracing/config.py +0 -142
- synth_ai/v0/tracing/context.py +0 -146
- synth_ai/v0/tracing/decorators.py +0 -682
- synth_ai/v0/tracing/events/__init__.py +0 -0
- synth_ai/v0/tracing/events/manage.py +0 -147
- synth_ai/v0/tracing/events/scope.py +0 -86
- synth_ai/v0/tracing/events/store.py +0 -228
- synth_ai/v0/tracing/immediate_client.py +0 -151
- synth_ai/v0/tracing/local.py +0 -18
- synth_ai/v0/tracing/log_client_base.py +0 -73
- synth_ai/v0/tracing/retry_queue.py +0 -186
- synth_ai/v0/tracing/trackers.py +0 -515
- synth_ai/v0/tracing/upload.py +0 -512
- synth_ai/v0/tracing/utils.py +0 -9
- synth_ai/v0/tracing_v1/__init__.py +0 -16
- synth_ai/v0/tracing_v1/abstractions.py +0 -224
- synth_ai/v0/tracing_v1/base_client.py +0 -91
- synth_ai/v0/tracing_v1/client_manager.py +0 -131
- synth_ai/v0/tracing_v1/config.py +0 -142
- synth_ai/v0/tracing_v1/context.py +0 -146
- synth_ai/v0/tracing_v1/decorators.py +0 -703
- synth_ai/v0/tracing_v1/events/__init__.py +0 -0
- synth_ai/v0/tracing_v1/events/manage.py +0 -147
- synth_ai/v0/tracing_v1/events/scope.py +0 -86
- synth_ai/v0/tracing_v1/events/store.py +0 -228
- synth_ai/v0/tracing_v1/immediate_client.py +0 -151
- synth_ai/v0/tracing_v1/local.py +0 -18
- synth_ai/v0/tracing_v1/log_client_base.py +0 -73
- synth_ai/v0/tracing_v1/retry_queue.py +0 -186
- synth_ai/v0/tracing_v1/trackers.py +0 -515
- synth_ai/v0/tracing_v1/upload.py +0 -527
- synth_ai/v0/tracing_v1/utils.py +0 -9
- synth_ai/zyk/__init__.py +0 -30
- synth_ai-0.2.8.dev2.dist-info/METADATA +0 -129
- synth_ai-0.2.8.dev2.dist-info/RECORD +0 -420
- /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/__init__.py +0 -0
- /synth_ai/{lm/caching → core/apps}/__init__.py +0 -0
- /synth_ai/{tracing_v3 → core/tracing_v3}/lm_call_record_abstractions.py +0 -0
- /synth_ai/{tracing_v3 → core/tracing_v3}/storage/__init__.py +0 -0
- /synth_ai/{tracing_v3 → core/tracing_v3}/storage/exceptions.py +0 -0
- /synth_ai/{tracing_v3 → core/tracing_v3}/storage/types.py +0 -0
- /synth_ai/{compound/cais.py → py.typed} +0 -0
- /synth_ai/{learning → sdk/learning}/core.py +0 -0
- /synth_ai/{learning → sdk/learning}/gateway.py +0 -0
- {synth_ai-0.2.8.dev2.dist-info → synth_ai-0.4.3.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.8.dev2.dist-info → synth_ai-0.4.3.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.2.8.dev2.dist-info → synth_ai-0.4.3.dist-info}/top_level.txt +0 -0
|
@@ -1,19 +1,45 @@
|
|
|
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
|
|
14
|
+
|
|
15
|
+
from synth_ai.cli.demo_apps.demo_registry import (
|
|
16
|
+
DemoTemplate,
|
|
17
|
+
get_demo_template,
|
|
18
|
+
list_demo_templates,
|
|
19
|
+
)
|
|
20
|
+
from synth_ai.cli.demo_apps.demo_task_apps import core as demo_core
|
|
21
|
+
from synth_ai.cli.demo_apps.demo_task_apps.core import DEFAULT_TASK_APP_SECRET_NAME, DemoEnv
|
|
22
|
+
from synth_ai.core.process import get_subprocess_env, should_filter_log_line
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
from synth_ai.handshake import HandshakeError, run_handshake # type: ignore[import-untyped]
|
|
26
|
+
except ImportError:
|
|
27
|
+
# handshake module may not exist in all environments
|
|
28
|
+
HandshakeError = Exception # type: ignore[assignment,misc]
|
|
29
|
+
def run_handshake() -> None: # type: ignore[misc]
|
|
30
|
+
pass
|
|
31
|
+
|
|
13
32
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
33
|
+
def _key_preview(value: str, label: str) -> str:
|
|
34
|
+
"""Return a short descriptor for a secret without leaking the full value."""
|
|
35
|
+
try:
|
|
36
|
+
text = value or ""
|
|
37
|
+
length = len(text)
|
|
38
|
+
prefix = text[:6] if length >= 6 else text
|
|
39
|
+
suffix = text[-5:] if length >= 5 else text
|
|
40
|
+
return f"{label} len={length} prefix={prefix} last5={suffix}"
|
|
41
|
+
except Exception:
|
|
42
|
+
return f"{label} len=0"
|
|
17
43
|
|
|
18
44
|
|
|
19
45
|
def _is_modal_public_url(u: str) -> bool:
|
|
@@ -26,35 +52,75 @@ def _is_modal_public_url(u: str) -> bool:
|
|
|
26
52
|
return False
|
|
27
53
|
|
|
28
54
|
|
|
29
|
-
def
|
|
30
|
-
#
|
|
55
|
+
def setup() -> int:
|
|
56
|
+
# Change to demo directory if stored
|
|
57
|
+
demo_dir = demo_core.load_demo_dir()
|
|
58
|
+
if demo_dir and os.path.isdir(demo_dir):
|
|
59
|
+
os.chdir(demo_dir)
|
|
60
|
+
print(f"Using demo directory: {demo_dir}")
|
|
61
|
+
|
|
62
|
+
# 1) Try to fetch keys from frontend; fall back to manual input if fetch fails
|
|
63
|
+
synth_key = ""
|
|
64
|
+
rl_env_key = ""
|
|
65
|
+
org_name = "this organization"
|
|
66
|
+
|
|
31
67
|
try:
|
|
32
68
|
print("\n⏳ Connecting SDK to your browser session…")
|
|
33
69
|
res = run_handshake()
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
70
|
+
# Type narrowing for dict access - run_handshake returns dict[str, Any] | None
|
|
71
|
+
res_dict: dict[str, Any] = res if isinstance(res, dict) else {}
|
|
72
|
+
org_val = res_dict.get("org") # type: ignore[misc]
|
|
73
|
+
org: dict[str, Any] = org_val if isinstance(org_val, dict) else {}
|
|
74
|
+
keys_val = res_dict.get("keys") # type: ignore[misc]
|
|
75
|
+
keys: dict[str, Any] = keys_val if isinstance(keys_val, dict) else {}
|
|
76
|
+
synth_key = str(keys.get("synth") or "").strip() # type: ignore[misc]
|
|
77
|
+
rl_env_key = str(keys.get("rl_env") or "").strip() # type: ignore[misc]
|
|
78
|
+
org_name = org.get("name") or "this organization" # type: ignore[misc]
|
|
79
|
+
print(f"✅ Connected to {org_name}!")
|
|
80
|
+
except (HandshakeError, Exception) as e:
|
|
81
|
+
print(f"⚠️ Failed to fetch keys from frontend: {e}")
|
|
82
|
+
print("Falling back to manual entry...")
|
|
83
|
+
|
|
84
|
+
# Prompt for manual input if any key is missing
|
|
85
|
+
if not synth_key:
|
|
86
|
+
try:
|
|
87
|
+
synth_key = input(
|
|
88
|
+
"Failed to fetch your Synth API key. Please enter your Synth API key here:\n> "
|
|
89
|
+
).strip()
|
|
90
|
+
except (EOFError, KeyboardInterrupt):
|
|
91
|
+
print("\nSetup cancelled.")
|
|
92
|
+
return 1
|
|
93
|
+
if not synth_key:
|
|
94
|
+
print("Synth API key is required.")
|
|
95
|
+
return 1
|
|
96
|
+
|
|
97
|
+
if not rl_env_key:
|
|
98
|
+
try:
|
|
99
|
+
rl_env_key = input(
|
|
100
|
+
"Failed to fetch your RL Environment API key. Please enter your RL Environment API key here:\n> "
|
|
101
|
+
).strip()
|
|
102
|
+
except (EOFError, KeyboardInterrupt):
|
|
103
|
+
print("\nSetup cancelled.")
|
|
104
|
+
return 1
|
|
105
|
+
if not rl_env_key:
|
|
106
|
+
print("RL Environment API key is required.")
|
|
107
|
+
return 1
|
|
108
|
+
|
|
109
|
+
# Persist both keys to .env
|
|
110
|
+
dotenv_path = demo_core.persist_dotenv_values(
|
|
111
|
+
{
|
|
43
112
|
"SYNTH_API_KEY": synth_key,
|
|
44
113
|
"ENVIRONMENT_API_KEY": rl_env_key,
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return 1
|
|
51
|
-
except Exception as e:
|
|
52
|
-
print(f"Unexpected handshake error: {e}")
|
|
53
|
-
return 1
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Store .env path for subsequent commands
|
|
118
|
+
demo_core.persist_env_file_path(dotenv_path)
|
|
54
119
|
|
|
55
120
|
# 2) Reload env after handshake to pick up values from .env (suppress env prints)
|
|
56
|
-
import io
|
|
57
121
|
import contextlib
|
|
122
|
+
import io
|
|
123
|
+
|
|
58
124
|
_buf = io.StringIO()
|
|
59
125
|
with contextlib.redirect_stdout(_buf):
|
|
60
126
|
env = demo_core.load_env()
|
|
@@ -71,22 +137,22 @@ def cmd_setup(_args: argparse.Namespace) -> int:
|
|
|
71
137
|
return
|
|
72
138
|
current = env.task_app_base_url
|
|
73
139
|
needs_lookup = False
|
|
74
|
-
if not current:
|
|
75
|
-
needs_lookup = True
|
|
76
|
-
elif not _is_modal_public_url(current):
|
|
140
|
+
if not current or not _is_modal_public_url(current):
|
|
77
141
|
needs_lookup = True
|
|
78
142
|
if not needs_lookup:
|
|
79
143
|
return
|
|
80
|
-
code, out = _popen_capture(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
144
|
+
code, out = _popen_capture(
|
|
145
|
+
[
|
|
146
|
+
"uv",
|
|
147
|
+
"run",
|
|
148
|
+
"python",
|
|
149
|
+
"-m",
|
|
150
|
+
"modal",
|
|
151
|
+
"app",
|
|
152
|
+
"url",
|
|
153
|
+
env.task_app_name,
|
|
154
|
+
]
|
|
155
|
+
)
|
|
90
156
|
if code != 0 or not out:
|
|
91
157
|
return
|
|
92
158
|
new_url = ""
|
|
@@ -100,7 +166,6 @@ def cmd_setup(_args: argparse.Namespace) -> int:
|
|
|
100
166
|
dotenv_values = {
|
|
101
167
|
"TASK_APP_BASE_URL": new_url,
|
|
102
168
|
"TASK_APP_NAME": env.task_app_name,
|
|
103
|
-
"TASK_APP_SECRET_NAME": env.task_app_secret_name or f"{env.task_app_name}-secret",
|
|
104
169
|
}
|
|
105
170
|
demo_core.persist_dotenv_values(dotenv_values)
|
|
106
171
|
os.environ["TASK_APP_BASE_URL"] = new_url
|
|
@@ -108,7 +173,7 @@ def cmd_setup(_args: argparse.Namespace) -> int:
|
|
|
108
173
|
|
|
109
174
|
# Keys have been written already via handshake; avoid any interactive prompts
|
|
110
175
|
synth_key = env.synth_api_key.strip()
|
|
111
|
-
if not local_env.get("SYNTH_API_KEY") and synth_key:
|
|
176
|
+
if not local_env.get("SYNTH_API_KEY") and synth_key: # type: ignore[misc]
|
|
112
177
|
demo_core.persist_dotenv_values({"SYNTH_API_KEY": synth_key})
|
|
113
178
|
_refresh_env()
|
|
114
179
|
|
|
@@ -117,15 +182,16 @@ def cmd_setup(_args: argparse.Namespace) -> int:
|
|
|
117
182
|
|
|
118
183
|
_maybe_fix_task_url()
|
|
119
184
|
|
|
120
|
-
ok_backend = False
|
|
121
|
-
ok_task = False
|
|
122
185
|
if env.dev_backend_url:
|
|
123
|
-
api = env.dev_backend_url.rstrip("/") + (
|
|
124
|
-
|
|
186
|
+
api = env.dev_backend_url.rstrip("/") + (
|
|
187
|
+
"" if env.dev_backend_url.endswith("/api") else "/api"
|
|
188
|
+
)
|
|
189
|
+
demo_core.assert_http_ok(api + "/health", method="GET")
|
|
125
190
|
# Intentionally suppress backend health print for concise output
|
|
126
191
|
if env.task_app_base_url:
|
|
127
|
-
|
|
128
|
-
|
|
192
|
+
demo_core.assert_http_ok(
|
|
193
|
+
env.task_app_base_url.rstrip("/") + "/health", method="GET"
|
|
194
|
+
) or demo_core.assert_http_ok(env.task_app_base_url.rstrip("/"), method="GET")
|
|
129
195
|
# Intentionally suppress task app health print
|
|
130
196
|
else:
|
|
131
197
|
print("\nSet your task app URL by running:\nuvx synth-ai rl_demo deploy\n")
|
|
@@ -133,13 +199,19 @@ def cmd_setup(_args: argparse.Namespace) -> int:
|
|
|
133
199
|
# Omit uv version print to keep output concise
|
|
134
200
|
|
|
135
201
|
# Keep exit code neutral; not all checks are critical for pairing
|
|
202
|
+
print(f"\nKeys saved to: {dotenv_path}")
|
|
136
203
|
return 0
|
|
137
204
|
|
|
138
205
|
|
|
139
|
-
def _popen_capture(
|
|
206
|
+
def _popen_capture(
|
|
207
|
+
cmd: list[str], cwd: str | None = None, env: dict | None = None
|
|
208
|
+
) -> tuple[int, str]:
|
|
140
209
|
import subprocess
|
|
210
|
+
|
|
141
211
|
try:
|
|
142
|
-
proc = subprocess.Popen(
|
|
212
|
+
proc = subprocess.Popen(
|
|
213
|
+
cmd, cwd=cwd, env=get_subprocess_env(env), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
|
|
214
|
+
)
|
|
143
215
|
out, _ = proc.communicate()
|
|
144
216
|
return int(proc.returncode or 0), out or ""
|
|
145
217
|
except Exception as e:
|
|
@@ -156,7 +228,7 @@ def _popen_stream(cmd: list[str], cwd: str | None = None, env: dict | None = Non
|
|
|
156
228
|
proc = subprocess.Popen(
|
|
157
229
|
cmd,
|
|
158
230
|
cwd=cwd,
|
|
159
|
-
env=env,
|
|
231
|
+
env=get_subprocess_env(env),
|
|
160
232
|
stdout=subprocess.PIPE,
|
|
161
233
|
stderr=subprocess.STDOUT,
|
|
162
234
|
text=True,
|
|
@@ -169,7 +241,8 @@ def _popen_stream(cmd: list[str], cwd: str | None = None, env: dict | None = Non
|
|
|
169
241
|
def _pump(stdout) -> None:
|
|
170
242
|
try:
|
|
171
243
|
for line in stdout:
|
|
172
|
-
|
|
244
|
+
if not should_filter_log_line(line):
|
|
245
|
+
print(line.rstrip())
|
|
173
246
|
except Exception:
|
|
174
247
|
pass
|
|
175
248
|
|
|
@@ -183,7 +256,9 @@ def _popen_stream(cmd: list[str], cwd: str | None = None, env: dict | None = Non
|
|
|
183
256
|
return int(proc.returncode or 0)
|
|
184
257
|
|
|
185
258
|
|
|
186
|
-
def _popen_stream_capture(
|
|
259
|
+
def _popen_stream_capture(
|
|
260
|
+
cmd: list[str], cwd: str | None = None, env: dict | None = None
|
|
261
|
+
) -> tuple[int, str]:
|
|
187
262
|
"""Stream subprocess output to stdout and also capture it into a buffer."""
|
|
188
263
|
import subprocess
|
|
189
264
|
import threading
|
|
@@ -193,7 +268,7 @@ def _popen_stream_capture(cmd: list[str], cwd: str | None = None, env: dict | No
|
|
|
193
268
|
proc = subprocess.Popen(
|
|
194
269
|
cmd,
|
|
195
270
|
cwd=cwd,
|
|
196
|
-
env=env,
|
|
271
|
+
env=get_subprocess_env(env),
|
|
197
272
|
stdout=subprocess.PIPE,
|
|
198
273
|
stderr=subprocess.STDOUT,
|
|
199
274
|
text=True,
|
|
@@ -207,8 +282,9 @@ def _popen_stream_capture(cmd: list[str], cwd: str | None = None, env: dict | No
|
|
|
207
282
|
try:
|
|
208
283
|
for line in stdout:
|
|
209
284
|
line = line.rstrip()
|
|
210
|
-
|
|
211
|
-
|
|
285
|
+
if not should_filter_log_line(line):
|
|
286
|
+
print(line)
|
|
287
|
+
buf_lines.append(line)
|
|
212
288
|
except Exception:
|
|
213
289
|
pass
|
|
214
290
|
|
|
@@ -222,55 +298,6 @@ def _popen_stream_capture(cmd: list[str], cwd: str | None = None, env: dict | No
|
|
|
222
298
|
return int(proc.returncode or 0), "\n".join(buf_lines)
|
|
223
299
|
|
|
224
300
|
|
|
225
|
-
def _mask_secret_args(args: list[str]) -> list[str]:
|
|
226
|
-
masked: list[str] = []
|
|
227
|
-
for a in args:
|
|
228
|
-
if "=" in a and any(a.startswith(prefix) for prefix in ("ENVIRONMENT_API_KEY=", "OPENAI_API_KEY=", "SYNTH_API_KEY=")):
|
|
229
|
-
try:
|
|
230
|
-
key, value = a.split("=", 1)
|
|
231
|
-
tail = value[-5:] if len(value) >= 5 else value
|
|
232
|
-
masked.append(f"{key}=***{tail}")
|
|
233
|
-
except Exception:
|
|
234
|
-
masked.append("<masked>")
|
|
235
|
-
else:
|
|
236
|
-
masked.append(a)
|
|
237
|
-
return masked
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def _ensure_modal_secret(
|
|
241
|
-
secret_name: str,
|
|
242
|
-
*,
|
|
243
|
-
values: dict[str, str],
|
|
244
|
-
label: str = "deploy",
|
|
245
|
-
replace: bool = False,
|
|
246
|
-
) -> bool:
|
|
247
|
-
prefix = f"[{label}]"
|
|
248
|
-
if not secret_name.strip():
|
|
249
|
-
raise RuntimeError("Secret name is required")
|
|
250
|
-
|
|
251
|
-
if not values:
|
|
252
|
-
raise RuntimeError("No values provided to create Modal secret")
|
|
253
|
-
|
|
254
|
-
create_args = [f"{k}={v}" for k, v in values.items()]
|
|
255
|
-
create_cmd = ["uv", "run", "modal", "secret", "create", secret_name, *create_args]
|
|
256
|
-
|
|
257
|
-
if replace:
|
|
258
|
-
print(f"{prefix} Removing Modal secret '{secret_name}' (if present)…")
|
|
259
|
-
delete_cmd = ["bash", "-lc", f"printf 'y\\n' | uv run modal secret delete {secret_name}"]
|
|
260
|
-
print(f"{prefix} Command:", " ".join(delete_cmd))
|
|
261
|
-
delete_code = _popen_stream(delete_cmd)
|
|
262
|
-
if delete_code != 0:
|
|
263
|
-
print(f"{prefix} Warning: delete command exited with {delete_code}; continuing to create")
|
|
264
|
-
|
|
265
|
-
print(f"\n{prefix} Creating Modal secret '{secret_name}'…")
|
|
266
|
-
print(f"{prefix} Command:", " ".join(_mask_secret_args(create_cmd)))
|
|
267
|
-
code = _popen_stream(create_cmd)
|
|
268
|
-
if code != 0:
|
|
269
|
-
raise RuntimeError("Failed to provision Modal secret (see logs above)")
|
|
270
|
-
|
|
271
|
-
return True
|
|
272
|
-
|
|
273
|
-
|
|
274
301
|
def _fmt_float(value: float) -> str:
|
|
275
302
|
return f"{value:.10g}"
|
|
276
303
|
|
|
@@ -283,11 +310,23 @@ def _find_asgi_apps(root: Path) -> list[Path]:
|
|
|
283
310
|
- "@modal.asgi_app()"
|
|
284
311
|
"""
|
|
285
312
|
results: list[Path] = []
|
|
286
|
-
skip_dirs = {
|
|
313
|
+
skip_dirs = {
|
|
314
|
+
".git",
|
|
315
|
+
".hg",
|
|
316
|
+
".svn",
|
|
317
|
+
"node_modules",
|
|
318
|
+
"dist",
|
|
319
|
+
"build",
|
|
320
|
+
"__pycache__",
|
|
321
|
+
".ruff_cache",
|
|
322
|
+
".mypy_cache",
|
|
323
|
+
"venv",
|
|
324
|
+
".venv",
|
|
325
|
+
}
|
|
287
326
|
for dirpath, dirnames, filenames in os.walk(root):
|
|
288
327
|
dirnames[:] = [d for d in dirnames if d not in skip_dirs]
|
|
289
328
|
for name in filenames:
|
|
290
|
-
if not name.endswith(".py"):
|
|
329
|
+
if not str(name).endswith(".py"):
|
|
291
330
|
continue
|
|
292
331
|
path = Path(dirpath) / name
|
|
293
332
|
try:
|
|
@@ -297,16 +336,20 @@ def _find_asgi_apps(root: Path) -> list[Path]:
|
|
|
297
336
|
results.append(path)
|
|
298
337
|
except Exception:
|
|
299
338
|
continue
|
|
339
|
+
|
|
300
340
|
# Stable order: prioritize files under synth_demo/ first, then alphabetical
|
|
301
341
|
def _priority(p: Path) -> tuple[int, str]:
|
|
302
342
|
rel = str(p.resolve())
|
|
303
343
|
in_demo = "/synth_demo/" in rel or rel.endswith("/synth_demo/task_app.py")
|
|
304
344
|
return (0 if in_demo else 1, rel)
|
|
345
|
+
|
|
305
346
|
results.sort(key=_priority)
|
|
306
347
|
return results
|
|
307
348
|
|
|
308
349
|
|
|
309
|
-
def _prompt_value(
|
|
350
|
+
def _prompt_value(
|
|
351
|
+
label: str, default: str | int | float, cast: Callable[[str], Any] | None = None
|
|
352
|
+
) -> Any:
|
|
310
353
|
prompt = f"{label} [{default}]: "
|
|
311
354
|
try:
|
|
312
355
|
raw = input(prompt).strip()
|
|
@@ -325,11 +368,23 @@ def _prompt_value(label: str, default: str | int | float, cast: Callable[[str],
|
|
|
325
368
|
|
|
326
369
|
def _find_vllm_tomls(root: Path) -> list[Path]:
|
|
327
370
|
results: list[Path] = []
|
|
328
|
-
skip_dirs = {
|
|
371
|
+
skip_dirs = {
|
|
372
|
+
".git",
|
|
373
|
+
".hg",
|
|
374
|
+
".svn",
|
|
375
|
+
"node_modules",
|
|
376
|
+
"dist",
|
|
377
|
+
"build",
|
|
378
|
+
"__pycache__",
|
|
379
|
+
".ruff_cache",
|
|
380
|
+
".mypy_cache",
|
|
381
|
+
"venv",
|
|
382
|
+
".venv",
|
|
383
|
+
}
|
|
329
384
|
for dirpath, dirnames, filenames in os.walk(root):
|
|
330
385
|
dirnames[:] = [d for d in dirnames if d not in skip_dirs]
|
|
331
386
|
for name in filenames:
|
|
332
|
-
if not name.endswith(".toml"):
|
|
387
|
+
if not str(name).endswith(".toml"):
|
|
333
388
|
continue
|
|
334
389
|
path = Path(dirpath) / name
|
|
335
390
|
try:
|
|
@@ -345,7 +400,9 @@ def _create_new_config(env: DemoEnv) -> str:
|
|
|
345
400
|
default_path = os.path.join(os.getcwd(), "demo_config.toml")
|
|
346
401
|
while True:
|
|
347
402
|
try:
|
|
348
|
-
destination =
|
|
403
|
+
destination = (
|
|
404
|
+
input(f"Path to save new config [{default_path}]: ").strip() or default_path
|
|
405
|
+
)
|
|
349
406
|
except Exception:
|
|
350
407
|
destination = default_path
|
|
351
408
|
destination = os.path.abspath(destination)
|
|
@@ -354,7 +411,9 @@ def _create_new_config(env: DemoEnv) -> str:
|
|
|
354
411
|
continue
|
|
355
412
|
if os.path.exists(destination):
|
|
356
413
|
try:
|
|
357
|
-
overwrite =
|
|
414
|
+
overwrite = (
|
|
415
|
+
input(f"{destination} exists. Overwrite? [y/N]: ").strip().lower() or "n"
|
|
416
|
+
)
|
|
358
417
|
except Exception:
|
|
359
418
|
overwrite = "n"
|
|
360
419
|
if not overwrite.startswith("y"):
|
|
@@ -366,7 +425,9 @@ def _create_new_config(env: DemoEnv) -> str:
|
|
|
366
425
|
model_name = _prompt_value("Model name", "Qwen/Qwen3-0.6B")
|
|
367
426
|
compute_gpu_type = _prompt_value("Compute GPU type", "H100")
|
|
368
427
|
compute_gpu_count = _prompt_value("Compute GPU count", 4, int)
|
|
369
|
-
topology_gpu_type = _prompt_value(
|
|
428
|
+
topology_gpu_type = _prompt_value(
|
|
429
|
+
"Topology GPU type", f"{compute_gpu_type}:{compute_gpu_count}"
|
|
430
|
+
)
|
|
370
431
|
gpus_for_vllm = _prompt_value("Topology gpus_for_vllm", 2, int)
|
|
371
432
|
gpus_for_training = _prompt_value("Topology gpus_for_training", 1, int)
|
|
372
433
|
tensor_parallel = _prompt_value("Topology tensor_parallel", 2, int)
|
|
@@ -384,8 +445,9 @@ def _create_new_config(env: DemoEnv) -> str:
|
|
|
384
445
|
task_url_default = env.task_app_base_url or ""
|
|
385
446
|
services_task_url = _prompt_value("services.task_url", task_url_default)
|
|
386
447
|
|
|
387
|
-
template =
|
|
388
|
-
|
|
448
|
+
template = (
|
|
449
|
+
textwrap.dedent(
|
|
450
|
+
f"""\
|
|
389
451
|
# Crafter online RL training configuration (research local copy)
|
|
390
452
|
|
|
391
453
|
[model]
|
|
@@ -527,7 +589,9 @@ def _create_new_config(env: DemoEnv) -> str:
|
|
|
527
589
|
[services]
|
|
528
590
|
task_url = \"{services_task_url}\"
|
|
529
591
|
"""
|
|
530
|
-
|
|
592
|
+
).strip()
|
|
593
|
+
+ "\n"
|
|
594
|
+
)
|
|
531
595
|
|
|
532
596
|
with open(destination, "w", encoding="utf-8") as fh:
|
|
533
597
|
fh.write(template)
|
|
@@ -546,7 +610,11 @@ def _select_or_create_config(explicit: str | None, env: DemoEnv) -> str:
|
|
|
546
610
|
discovered = _find_vllm_tomls(search_root)
|
|
547
611
|
|
|
548
612
|
extras: list[Path] = []
|
|
549
|
-
packaged = Path(
|
|
613
|
+
packaged = Path(
|
|
614
|
+
os.path.abspath(
|
|
615
|
+
os.path.join(os.path.dirname(__file__), "..", "demo_task_apps", "math", "config.toml")
|
|
616
|
+
)
|
|
617
|
+
)
|
|
550
618
|
extras.append(packaged)
|
|
551
619
|
home_cfg = Path(os.path.expanduser("~/.synth-ai/demo_config.toml"))
|
|
552
620
|
extras.append(home_cfg)
|
|
@@ -592,29 +660,36 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
592
660
|
|
|
593
661
|
env_key = (env.env_api_key or "").strip()
|
|
594
662
|
if not env_key:
|
|
595
|
-
raise RuntimeError(
|
|
663
|
+
raise RuntimeError(
|
|
664
|
+
f"[{label}] ENVIRONMENT_API_KEY missing. Run `uvx synth-ai rl_demo deploy` first."
|
|
665
|
+
)
|
|
596
666
|
|
|
597
667
|
task_url = env.task_app_base_url
|
|
598
668
|
if not task_url or not _is_modal_public_url(task_url):
|
|
599
669
|
resolved = ""
|
|
600
670
|
if env.task_app_name:
|
|
601
671
|
try:
|
|
602
|
-
choice =
|
|
603
|
-
f"Resolve URL from Modal for app '{env.task_app_name}'? [Y/n]: "
|
|
604
|
-
|
|
672
|
+
choice = (
|
|
673
|
+
input(f"Resolve URL from Modal for app '{env.task_app_name}'? [Y/n]: ")
|
|
674
|
+
.strip()
|
|
675
|
+
.lower()
|
|
676
|
+
or "y"
|
|
677
|
+
)
|
|
605
678
|
except Exception:
|
|
606
679
|
choice = "y"
|
|
607
680
|
if choice.startswith("y"):
|
|
608
|
-
code, out = _popen_capture(
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
681
|
+
code, out = _popen_capture(
|
|
682
|
+
[
|
|
683
|
+
"uv",
|
|
684
|
+
"run",
|
|
685
|
+
"python",
|
|
686
|
+
"-m",
|
|
687
|
+
"modal",
|
|
688
|
+
"app",
|
|
689
|
+
"url",
|
|
690
|
+
env.task_app_name,
|
|
691
|
+
]
|
|
692
|
+
)
|
|
618
693
|
if code == 0 and out:
|
|
619
694
|
for tok in out.split():
|
|
620
695
|
if _is_modal_public_url(tok):
|
|
@@ -623,7 +698,9 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
623
698
|
if not resolved:
|
|
624
699
|
print(f"[{label}] Task app URL not configured or not a valid Modal public URL.")
|
|
625
700
|
print("Examples: https://<app-name>-fastapi-app.modal.run")
|
|
626
|
-
entered = input(
|
|
701
|
+
entered = input(
|
|
702
|
+
"Enter Task App base URL (must contain '.modal.run'), or press Enter to abort: "
|
|
703
|
+
).strip()
|
|
627
704
|
if not entered or not _is_modal_public_url(entered):
|
|
628
705
|
raise RuntimeError(f"[{label}] Valid Task App URL is required.")
|
|
629
706
|
task_url = entered.rstrip("/")
|
|
@@ -639,30 +716,26 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
639
716
|
app_name = fallback
|
|
640
717
|
demo_core.persist_task_url(task_url, name=app_name)
|
|
641
718
|
|
|
642
|
-
secret_name = env.task_app_secret_name.strip() or f"{app_name}-secret"
|
|
643
719
|
demo_core.persist_task_url(task_url, name=app_name)
|
|
644
|
-
demo_core.persist_dotenv_values(
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
if openai_key:
|
|
653
|
-
secret_values["OPENAI_API_KEY"] = openai_key
|
|
720
|
+
demo_core.persist_dotenv_values(
|
|
721
|
+
{
|
|
722
|
+
"TASK_APP_BASE_URL": task_url,
|
|
723
|
+
"TASK_APP_NAME": app_name,
|
|
724
|
+
"TASK_APP_SECRET_NAME": DEFAULT_TASK_APP_SECRET_NAME,
|
|
725
|
+
}
|
|
726
|
+
)
|
|
727
|
+
|
|
654
728
|
if synth_key:
|
|
655
|
-
|
|
729
|
+
os.environ["SYNTH_API_KEY"] = synth_key
|
|
656
730
|
|
|
657
|
-
|
|
731
|
+
openai_key = (os.environ.get("OPENAI_API_KEY") or local_env.get("OPENAI_API_KEY") or "").strip() # type: ignore[misc]
|
|
732
|
+
if openai_key:
|
|
733
|
+
os.environ["OPENAI_API_KEY"] = openai_key
|
|
658
734
|
|
|
659
|
-
rollout_url = task_url.rstrip("/") + "/health/rollout"
|
|
660
735
|
print(f"[{label}] Verifying rollout health:")
|
|
661
736
|
try:
|
|
662
737
|
ek = (env_key or "").strip()
|
|
663
|
-
|
|
664
|
-
ek_tail = ek[-5:] if ek_len >= 5 else ek
|
|
665
|
-
print(f"[{label}] Using ENVIRONMENT_API_KEY len={ek_len} last5={ek_tail}")
|
|
738
|
+
print(f"[{label}] {_key_preview(ek, 'ENVIRONMENT_API_KEY')}")
|
|
666
739
|
except Exception:
|
|
667
740
|
pass
|
|
668
741
|
health_base = task_url.rstrip("/")
|
|
@@ -673,7 +746,6 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
673
746
|
print(f"[{label}] GET", h)
|
|
674
747
|
rc, body = _http("GET", h, headers={"X-API-Key": env_key})
|
|
675
748
|
if rc == 200:
|
|
676
|
-
rollout_url = h
|
|
677
749
|
break
|
|
678
750
|
print(f"[{label}] status: {rc}")
|
|
679
751
|
try:
|
|
@@ -685,41 +757,64 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
|
|
|
685
757
|
print(f"[{label}] body:", preview)
|
|
686
758
|
if rc != 200:
|
|
687
759
|
print(f"[{label}] Warning: rollout health check failed ({rc}). Response: {body}")
|
|
760
|
+
with contextlib.suppress(Exception):
|
|
761
|
+
print(f"[{label}] Sent header X-API-Key → {_key_preview(env_key, 'X-API-Key')}")
|
|
688
762
|
else:
|
|
689
763
|
print(f"[{label}] Task app rollout health check OK.")
|
|
690
764
|
|
|
691
765
|
os.environ["TASK_APP_BASE_URL"] = task_url
|
|
692
766
|
os.environ["ENVIRONMENT_API_KEY"] = env_key
|
|
767
|
+
os.environ["TASK_APP_SECRET_NAME"] = DEFAULT_TASK_APP_SECRET_NAME
|
|
693
768
|
updated_env = demo_core.load_env()
|
|
694
769
|
updated_env.env_api_key = env_key
|
|
695
770
|
updated_env.task_app_base_url = task_url
|
|
696
771
|
updated_env.task_app_name = app_name
|
|
697
|
-
updated_env.task_app_secret_name =
|
|
772
|
+
updated_env.task_app_secret_name = DEFAULT_TASK_APP_SECRET_NAME
|
|
698
773
|
return updated_env
|
|
699
774
|
|
|
700
775
|
|
|
701
|
-
def
|
|
776
|
+
def deploy(
|
|
777
|
+
local: bool = False, app: str | None = None, name: str | None = None, script: str | None = None
|
|
778
|
+
) -> int:
|
|
779
|
+
# Change to demo directory if stored
|
|
780
|
+
demo_dir = demo_core.load_demo_dir()
|
|
781
|
+
if demo_dir and os.path.isdir(demo_dir):
|
|
782
|
+
os.chdir(demo_dir)
|
|
783
|
+
print(f"Using demo directory: {demo_dir}")
|
|
784
|
+
|
|
702
785
|
env = demo_core.load_env()
|
|
786
|
+
os.environ["TASK_APP_SECRET_NAME"] = DEFAULT_TASK_APP_SECRET_NAME
|
|
703
787
|
cwd_env_path = os.path.join(os.getcwd(), ".env")
|
|
704
788
|
local_env = demo_core.load_dotenv_file(cwd_env_path)
|
|
705
789
|
url = ""
|
|
706
790
|
app_name = env.task_app_name or ""
|
|
707
791
|
try:
|
|
708
|
-
if
|
|
792
|
+
if local:
|
|
709
793
|
print("Starting local Task App…")
|
|
710
794
|
import subprocess
|
|
711
|
-
|
|
712
|
-
|
|
795
|
+
|
|
796
|
+
subprocess.Popen(
|
|
797
|
+
[
|
|
798
|
+
sys.executable,
|
|
799
|
+
"-c",
|
|
800
|
+
"from synth_ai.cli.demo_apps.demo_task_apps.math.app import run; run()",
|
|
801
|
+
],
|
|
802
|
+
env=get_subprocess_env(),
|
|
803
|
+
stdout=sys.stdout,
|
|
804
|
+
stderr=sys.stderr,
|
|
805
|
+
)
|
|
713
806
|
target = "http://127.0.0.1:8080"
|
|
714
807
|
app_name = ""
|
|
715
808
|
for _ in range(30):
|
|
716
|
-
if demo_core.assert_http_ok(
|
|
809
|
+
if demo_core.assert_http_ok(
|
|
810
|
+
target + "/health", method="GET"
|
|
811
|
+
) or demo_core.assert_http_ok(target, method="GET"):
|
|
717
812
|
url = target
|
|
718
813
|
break
|
|
719
814
|
time.sleep(1)
|
|
720
815
|
else:
|
|
721
816
|
# Auto-detect app path if not supplied; prompt interactively from discovered ASGI apps
|
|
722
|
-
app_path = os.path.abspath(
|
|
817
|
+
app_path = os.path.abspath(app) if app else None
|
|
723
818
|
if not app_path or not os.path.isfile(app_path):
|
|
724
819
|
# First pass: look for known common filenames
|
|
725
820
|
candidates = [
|
|
@@ -738,7 +833,9 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
738
833
|
rel = os.path.relpath(str(pth), os.getcwd())
|
|
739
834
|
print(f" [{idx}] {rel}")
|
|
740
835
|
try:
|
|
741
|
-
sel =
|
|
836
|
+
sel = (
|
|
837
|
+
input(f"Enter choice [1-{len(found)}] (default 1): ").strip() or "1"
|
|
838
|
+
)
|
|
742
839
|
except Exception:
|
|
743
840
|
sel = "1"
|
|
744
841
|
try:
|
|
@@ -747,12 +844,15 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
747
844
|
choice = 1
|
|
748
845
|
choice = max(1, min(choice, len(found)))
|
|
749
846
|
app_path = str(found[choice - 1].resolve())
|
|
750
|
-
if not app_path and
|
|
847
|
+
if not app_path and script:
|
|
751
848
|
# Legacy script fallback if user supplied --script explicitly
|
|
752
|
-
from synth_ai.
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
849
|
+
from synth_ai.cli.demo_apps.demo_task_apps.math.deploy_modal import (
|
|
850
|
+
deploy as modal_deploy,
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
url = modal_deploy(script_path=script, env_api_key=env.env_api_key)
|
|
854
|
+
if name:
|
|
855
|
+
app_name = name
|
|
756
856
|
else:
|
|
757
857
|
if not app_path:
|
|
758
858
|
entered = input("Path to Modal app.py (e.g., ./task_app.py): ").strip()
|
|
@@ -763,7 +863,10 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
763
863
|
raise FileNotFoundError(f"App file not found: {app_path}")
|
|
764
864
|
# Surface the app path before asking for the name
|
|
765
865
|
print(f"Using task app: {app_path}")
|
|
766
|
-
|
|
866
|
+
existing_name = (name or env.task_app_name or "").strip()
|
|
867
|
+
if not existing_name:
|
|
868
|
+
existing_name = f"synth-{os.path.splitext(os.path.basename(app_path))[0]}"
|
|
869
|
+
suggested_name = existing_name
|
|
767
870
|
name_in = input(f"Modal app name [{suggested_name}]: ").strip() or suggested_name
|
|
768
871
|
app_name = name_in
|
|
769
872
|
print("\nAbout to deploy with:")
|
|
@@ -774,21 +877,23 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
774
877
|
print("Aborted by user.")
|
|
775
878
|
return 1
|
|
776
879
|
|
|
777
|
-
secret_name = (env.task_app_secret_name or "").strip() or f"{name_in}-secret"
|
|
778
880
|
existing_env_key = (env.env_api_key or "").strip()
|
|
779
881
|
env_key: str | None = existing_env_key or None
|
|
780
882
|
if existing_env_key:
|
|
781
883
|
try:
|
|
782
|
-
reuse_choice =
|
|
783
|
-
"Use existing ENVIRONMENT_API_KEY from state/.env? [Y/n]: "
|
|
784
|
-
|
|
884
|
+
reuse_choice = (
|
|
885
|
+
input("Use existing ENVIRONMENT_API_KEY from state/.env? [Y/n]: ")
|
|
886
|
+
.strip()
|
|
887
|
+
.lower()
|
|
888
|
+
or "y"
|
|
889
|
+
)
|
|
785
890
|
except Exception:
|
|
786
891
|
reuse_choice = "y"
|
|
787
892
|
if not reuse_choice.startswith("y"):
|
|
788
893
|
env_key = None
|
|
789
894
|
|
|
790
895
|
if env_key is None:
|
|
791
|
-
from synth_ai.rl.secrets import mint_environment_api_key
|
|
896
|
+
from synth_ai.sdk.learning.rl.secrets import mint_environment_api_key
|
|
792
897
|
|
|
793
898
|
env_key = mint_environment_api_key()
|
|
794
899
|
demo_core.persist_env_api_key(env_key)
|
|
@@ -797,69 +902,90 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
797
902
|
env.env_api_key = env_key
|
|
798
903
|
local_env["ENVIRONMENT_API_KEY"] = env_key
|
|
799
904
|
print("[deploy] Minted new ENVIRONMENT_API_KEY")
|
|
800
|
-
|
|
905
|
+
elif env_key:
|
|
906
|
+
os.environ["ENVIRONMENT_API_KEY"] = env_key
|
|
907
|
+
|
|
801
908
|
# Optionally upload the new key to the backend using sealed box helper
|
|
802
|
-
backend_base = env.dev_backend_url or ""
|
|
803
|
-
synth_key = (
|
|
909
|
+
backend_base = (env.dev_backend_url or "").rstrip("/")
|
|
910
|
+
synth_key = (
|
|
911
|
+
env.synth_api_key
|
|
912
|
+
or os.environ.get("SYNTH_API_KEY") # type: ignore[misc]
|
|
913
|
+
or local_env.get("SYNTH_API_KEY") # type: ignore[misc]
|
|
914
|
+
or ""
|
|
915
|
+
).strip()
|
|
804
916
|
if backend_base and synth_key:
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
917
|
+
# Pass a base WITHOUT trailing /api to setup_environment_api_key,
|
|
918
|
+
# since it appends /api/v1/... internally.
|
|
919
|
+
non_api_base = (
|
|
920
|
+
backend_base[:-4] if backend_base.endswith("/api") else backend_base
|
|
921
|
+
)
|
|
922
|
+
try:
|
|
923
|
+
choice = (
|
|
924
|
+
input(f"Upload ENVIRONMENT_API_KEY to backend {non_api_base}? [Y/n]: ")
|
|
925
|
+
.strip()
|
|
926
|
+
.lower()
|
|
927
|
+
or "y"
|
|
928
|
+
)
|
|
929
|
+
except Exception:
|
|
930
|
+
choice = "y"
|
|
931
|
+
if choice.startswith("y"):
|
|
808
932
|
try:
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
from synth_ai.rl.env_keys import setup_environment_api_key
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
synth_key = (env.synth_api_key or os.environ.get("SYNTH_API_KEY") or local_env.get("SYNTH_API_KEY") or "").strip()
|
|
933
|
+
print(f"[deploy] Uploading ENVIRONMENT_API_KEY to {non_api_base} …")
|
|
934
|
+
from synth_ai.sdk.learning.rl.env_keys import setup_environment_api_key
|
|
935
|
+
|
|
936
|
+
setup_environment_api_key(non_api_base, synth_key, token=env_key)
|
|
937
|
+
print("[deploy] Backend sealed-box upload complete.")
|
|
938
|
+
except Exception as upload_err:
|
|
939
|
+
print(f"[deploy] Failed to upload ENVIRONMENT_API_KEY: {upload_err}")
|
|
940
|
+
print(
|
|
941
|
+
'Hint: run `uvx python -c "from synth_ai.sdk.learning.rl.env_keys import setup_environment_api_key as s;'
|
|
942
|
+
" s('<backend>', '<synth_api_key>')\"` once the backend is reachable."
|
|
943
|
+
)
|
|
944
|
+
|
|
945
|
+
synth_key = (
|
|
946
|
+
env.synth_api_key
|
|
947
|
+
or os.environ.get("SYNTH_API_KEY") # type: ignore[misc]
|
|
948
|
+
or local_env.get("SYNTH_API_KEY") # type: ignore[misc]
|
|
949
|
+
or ""
|
|
950
|
+
).strip()
|
|
829
951
|
if not synth_key:
|
|
830
|
-
synth_key = input("Enter SYNTH_API_KEY for
|
|
952
|
+
synth_key = input("Enter SYNTH_API_KEY for deployment (required): ").strip()
|
|
831
953
|
if not synth_key:
|
|
832
|
-
print("SYNTH_API_KEY is required
|
|
954
|
+
print("SYNTH_API_KEY is required for deployment.")
|
|
833
955
|
return 1
|
|
834
956
|
demo_core.persist_api_key(synth_key)
|
|
835
957
|
demo_core.persist_dotenv_values({"SYNTH_API_KEY": synth_key})
|
|
836
958
|
env.synth_api_key = synth_key
|
|
959
|
+
os.environ["SYNTH_API_KEY"] = synth_key
|
|
837
960
|
|
|
838
|
-
openai_key = (
|
|
961
|
+
openai_key = (
|
|
962
|
+
os.environ.get("OPENAI_API_KEY") or local_env.get("OPENAI_API_KEY") or ""
|
|
963
|
+
).strip()
|
|
839
964
|
if not openai_key:
|
|
840
965
|
openai_key = input(
|
|
841
966
|
"Enter your OpenAI API key, found at https://platform.openai.com/api-keys\n> "
|
|
842
967
|
).strip()
|
|
843
968
|
if not openai_key:
|
|
844
|
-
print("OPENAI_API_KEY is required
|
|
969
|
+
print("OPENAI_API_KEY is required for deployment.")
|
|
845
970
|
return 1
|
|
846
971
|
demo_core.persist_dotenv_values({"OPENAI_API_KEY": openai_key})
|
|
847
972
|
local_env["OPENAI_API_KEY"] = openai_key
|
|
973
|
+
os.environ["OPENAI_API_KEY"] = openai_key
|
|
848
974
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
975
|
+
deploy_cmd = [
|
|
976
|
+
"uv",
|
|
977
|
+
"run",
|
|
978
|
+
"python",
|
|
979
|
+
"-m",
|
|
980
|
+
"modal",
|
|
981
|
+
"deploy",
|
|
982
|
+
"--name",
|
|
983
|
+
name_in,
|
|
984
|
+
app_path,
|
|
985
|
+
]
|
|
986
|
+
print(
|
|
987
|
+
"\nStreaming Modal build/deploy logs (this can take several minutes on first run)…\n"
|
|
988
|
+
)
|
|
863
989
|
code, deploy_logs = _popen_stream_capture(deploy_cmd)
|
|
864
990
|
if code != 0:
|
|
865
991
|
raise RuntimeError(f"modal deploy failed (exit {code})")
|
|
@@ -867,6 +993,7 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
867
993
|
if not url:
|
|
868
994
|
try:
|
|
869
995
|
import re as _re
|
|
996
|
+
|
|
870
997
|
m_all = _re.findall(r"https?://[^\s]+\.modal\.run", deploy_logs or "")
|
|
871
998
|
if m_all:
|
|
872
999
|
url = m_all[-1].strip().rstrip("/")
|
|
@@ -881,7 +1008,9 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
881
1008
|
break
|
|
882
1009
|
# Fallback: try reading recent Modal logs for the app to find a URL line
|
|
883
1010
|
if not url:
|
|
884
|
-
code3, out3 = _popen_capture(
|
|
1011
|
+
code3, out3 = _popen_capture(
|
|
1012
|
+
["uv", "run", "python", "-m", "modal", "app", "list"]
|
|
1013
|
+
)
|
|
885
1014
|
if code3 == 0 and out3:
|
|
886
1015
|
for line in out3.splitlines():
|
|
887
1016
|
if name_in in line:
|
|
@@ -894,7 +1023,9 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
894
1023
|
# Prompt user if still no valid URL
|
|
895
1024
|
if not url:
|
|
896
1025
|
print("\nCould not auto-detect a public Modal URL for the app.")
|
|
897
|
-
entered = input(
|
|
1026
|
+
entered = input(
|
|
1027
|
+
"Enter the Modal public URL (must contain '.modal.run'), or press Enter to abort: "
|
|
1028
|
+
).strip()
|
|
898
1029
|
if entered and _is_modal_public_url(entered):
|
|
899
1030
|
url = entered.rstrip("/")
|
|
900
1031
|
if not url:
|
|
@@ -906,7 +1037,7 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
906
1037
|
dotenv_values = {"TASK_APP_BASE_URL": url}
|
|
907
1038
|
if app_name:
|
|
908
1039
|
dotenv_values["TASK_APP_NAME"] = app_name
|
|
909
|
-
|
|
1040
|
+
dotenv_values["TASK_APP_SECRET_NAME"] = DEFAULT_TASK_APP_SECRET_NAME
|
|
910
1041
|
dotenv_path = demo_core.persist_dotenv_values(dotenv_values)
|
|
911
1042
|
print(f"TASK_APP_BASE_URL={url}")
|
|
912
1043
|
if app_name:
|
|
@@ -915,16 +1046,16 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
915
1046
|
print(f" export TASK_APP_BASE_URL={url}")
|
|
916
1047
|
if app_name:
|
|
917
1048
|
print(f" export TASK_APP_NAME={app_name}")
|
|
918
|
-
print(f" export TASK_APP_SECRET_NAME={app_name}-secret")
|
|
919
1049
|
print(f"Persisted to {dotenv_path}")
|
|
920
|
-
print("
|
|
1050
|
+
print("\nNext step:\n$ uvx synth-ai run")
|
|
921
1051
|
return 0
|
|
922
1052
|
except Exception as e:
|
|
923
1053
|
print(f"Deploy error: {e}")
|
|
924
1054
|
return 2
|
|
925
1055
|
|
|
926
|
-
|
|
927
|
-
|
|
1056
|
+
print(
|
|
1057
|
+
"`rl_demo configure` prepares environment and secrets; `synth-ai run` now handles launches."
|
|
1058
|
+
)
|
|
928
1059
|
env = demo_core.load_env()
|
|
929
1060
|
synth_key = (env.synth_api_key or "").strip()
|
|
930
1061
|
if not synth_key:
|
|
@@ -956,133 +1087,314 @@ def cmd_deploy(args: argparse.Namespace) -> int:
|
|
|
956
1087
|
return 0
|
|
957
1088
|
|
|
958
1089
|
|
|
959
|
-
def
|
|
960
|
-
"""
|
|
1090
|
+
def _ensure_modal_installed() -> None:
|
|
1091
|
+
"""Install the modal package if it is not already available and check authentication."""
|
|
961
1092
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
"""
|
|
1093
|
+
# Check if modal is installed
|
|
1094
|
+
modal_installed = False
|
|
965
1095
|
try:
|
|
966
|
-
|
|
967
|
-
def _has_modal() -> bool:
|
|
968
|
-
try:
|
|
969
|
-
import importlib.util as _iu
|
|
970
|
-
return _iu.find_spec("modal") is not None
|
|
971
|
-
except Exception:
|
|
972
|
-
return False
|
|
1096
|
+
import importlib.util as _iu
|
|
973
1097
|
|
|
974
|
-
if not
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
print(
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
dst_deploy = os.path.join(demo_dir, "deploy_task_app.sh")
|
|
1001
|
-
env_path = os.path.join(demo_dir, ".env")
|
|
1002
|
-
dst_cfg = os.path.join(demo_dir, "demo_config.toml")
|
|
1003
|
-
|
|
1004
|
-
# Copy packaged math modal task app into synth_demo/task_app.py
|
|
1005
|
-
src_modal = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "demo_task_apps", "math", "modal_task_app.py"))
|
|
1006
|
-
if not os.path.isfile(src_modal):
|
|
1007
|
-
print("Init failed: packaged math modal task app not found.")
|
|
1008
|
-
print(f"Looked for: {src_modal}")
|
|
1009
|
-
return 1
|
|
1010
|
-
if os.path.exists(dst_task_py) and not getattr(args, "force", False):
|
|
1011
|
-
print(f"Refusing to overwrite existing file: {dst_task_py} (use --force)")
|
|
1012
|
-
return 1
|
|
1013
|
-
shutil.copy2(src_modal, dst_task_py)
|
|
1014
|
-
|
|
1015
|
-
# Create deploy script in synth_demo/
|
|
1016
|
-
deploy_text = r"""#!/usr/bin/env bash
|
|
1017
|
-
set -euo pipefail
|
|
1018
|
-
|
|
1019
|
-
HERE=$(cd "$(dirname "$0")" && pwd)
|
|
1020
|
-
APP="$HERE/task_app.py"
|
|
1021
|
-
if [ -f "$HERE/.env" ]; then
|
|
1022
|
-
# shellcheck disable=SC2046
|
|
1023
|
-
export $(grep -v '^#' "$HERE/.env" | xargs -I{} echo {})
|
|
1024
|
-
fi
|
|
1025
|
-
uv run modal deploy "$APP" | tee "$HERE/.last_deploy.log"
|
|
1026
|
-
URL=$(grep -Eo 'https://[^ ]+\.modal\.run' "$HERE/.last_deploy.log" | tail -1 || true)
|
|
1027
|
-
if [ -n "$URL" ]; then
|
|
1028
|
-
if grep -q '^TASK_APP_BASE_URL=' "$HERE/.env" 2>/dev/null; then
|
|
1029
|
-
sed -i.bak "s#^TASK_APP_BASE_URL=.*#TASK_APP_BASE_URL=$URL#" "$HERE/.env" || true
|
|
1030
|
-
else
|
|
1031
|
-
echo "TASK_APP_BASE_URL=$URL" >> "$HERE/.env"
|
|
1032
|
-
fi
|
|
1033
|
-
echo "Saved TASK_APP_BASE_URL to $HERE/.env"
|
|
1034
|
-
fi
|
|
1035
|
-
"""
|
|
1036
|
-
_write_text(dst_deploy, deploy_text)
|
|
1098
|
+
if _iu.find_spec("modal") is not None:
|
|
1099
|
+
modal_installed = True
|
|
1100
|
+
except Exception:
|
|
1101
|
+
pass
|
|
1102
|
+
|
|
1103
|
+
# Install modal if needed
|
|
1104
|
+
if not modal_installed:
|
|
1105
|
+
print("modal not found; installing…")
|
|
1106
|
+
try:
|
|
1107
|
+
if shutil.which("uv"):
|
|
1108
|
+
code, out = _popen_capture(["uv", "pip", "install", "modal>=1.1.4"])
|
|
1109
|
+
else:
|
|
1110
|
+
code, out = _popen_capture([sys.executable, "-m", "pip", "install", "modal>=1.1.4"])
|
|
1111
|
+
if code != 0:
|
|
1112
|
+
print(out)
|
|
1113
|
+
print("Failed to install modal; continuing may fail.")
|
|
1114
|
+
return
|
|
1115
|
+
else:
|
|
1116
|
+
print("✓ modal installed successfully")
|
|
1117
|
+
modal_installed = True
|
|
1118
|
+
except Exception as exc:
|
|
1119
|
+
print(f"modal install error: {exc}")
|
|
1120
|
+
return
|
|
1121
|
+
|
|
1122
|
+
# Verify modal is importable
|
|
1123
|
+
if modal_installed:
|
|
1037
1124
|
try:
|
|
1038
|
-
|
|
1039
|
-
|
|
1125
|
+
import importlib.util as _iu
|
|
1126
|
+
|
|
1127
|
+
if _iu.find_spec("modal") is None:
|
|
1128
|
+
print("Warning: modal is still not importable after install attempt.")
|
|
1129
|
+
return
|
|
1040
1130
|
except Exception:
|
|
1041
|
-
|
|
1131
|
+
print("Warning: unable to verify modal installation.")
|
|
1132
|
+
return
|
|
1133
|
+
|
|
1134
|
+
# Check modal authentication status
|
|
1135
|
+
auth_ok, auth_msg = demo_core.modal_auth_status()
|
|
1136
|
+
if auth_ok:
|
|
1137
|
+
print(f"✓ Modal authenticated: {auth_msg}")
|
|
1138
|
+
else:
|
|
1139
|
+
print("\n⚠️ Modal authentication required")
|
|
1140
|
+
print(f" Status: {auth_msg}")
|
|
1141
|
+
print("\n To authenticate Modal, run:")
|
|
1142
|
+
print(" modal setup")
|
|
1143
|
+
print("\n Or set environment variables:")
|
|
1144
|
+
print(" export MODAL_TOKEN_ID=your-token-id")
|
|
1145
|
+
print(" export MODAL_TOKEN_SECRET=your-token-secret")
|
|
1146
|
+
print("\n You can deploy later after authenticating.\n")
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
def init(template: str | None = None, dest: str | None = None, force: bool = False) -> int:
|
|
1150
|
+
"""Materialise a demo task app template into the current directory."""
|
|
1151
|
+
|
|
1152
|
+
templates = list(list_demo_templates())
|
|
1153
|
+
if not templates:
|
|
1154
|
+
print("No demo templates registered. Update synth_ai/demo_registry.py to add entries.")
|
|
1155
|
+
return 1
|
|
1156
|
+
|
|
1157
|
+
selected: DemoTemplate | None = None
|
|
1158
|
+
if template:
|
|
1159
|
+
selected = get_demo_template(template)
|
|
1160
|
+
if selected is None:
|
|
1161
|
+
available = ", ".join(t.template_id for t in templates)
|
|
1162
|
+
print(f"Unknown template '{template}'. Available: {available}")
|
|
1163
|
+
return 1
|
|
1164
|
+
else:
|
|
1165
|
+
if force:
|
|
1166
|
+
selected = templates[0]
|
|
1167
|
+
print(
|
|
1168
|
+
f"Using default template: {selected.name} ({selected.template_id}) "
|
|
1169
|
+
f"(pass --template to choose another)"
|
|
1170
|
+
)
|
|
1171
|
+
else:
|
|
1172
|
+
print("Select a demo template:" + "\n")
|
|
1173
|
+
for idx, tpl in enumerate(templates, start=1):
|
|
1174
|
+
print(f" [{idx}] {tpl.name} ({tpl.template_id})")
|
|
1175
|
+
print(f" {tpl.description}")
|
|
1176
|
+
try:
|
|
1177
|
+
choice_raw = input(f"Enter choice [1-{len(templates)}] (default 1): ").strip() or "1"
|
|
1178
|
+
except Exception:
|
|
1179
|
+
choice_raw = "1"
|
|
1180
|
+
if not choice_raw.isdigit():
|
|
1181
|
+
print("Selection must be a number.")
|
|
1182
|
+
return 1
|
|
1183
|
+
choice_idx = int(choice_raw)
|
|
1184
|
+
if not 1 <= choice_idx <= len(templates):
|
|
1185
|
+
print("Selection out of range.")
|
|
1186
|
+
return 1
|
|
1187
|
+
selected = templates[choice_idx - 1]
|
|
1188
|
+
|
|
1189
|
+
assert selected is not None
|
|
1190
|
+
|
|
1191
|
+
default_subdir = selected.default_subdir or selected.template_id
|
|
1192
|
+
|
|
1193
|
+
# Check if default destination is already occupied and switch to local_demos/ if needed
|
|
1194
|
+
if dest:
|
|
1195
|
+
default_dest = Path(dest).expanduser().resolve()
|
|
1196
|
+
else:
|
|
1197
|
+
primary_dest = Path.cwd() / default_subdir
|
|
1198
|
+
if primary_dest.exists() and any(primary_dest.iterdir()):
|
|
1199
|
+
# Switch to local_demos/ automatically if primary location is occupied
|
|
1200
|
+
default_dest = (Path.cwd() / "local_demos" / default_subdir).resolve()
|
|
1201
|
+
else:
|
|
1202
|
+
default_dest = primary_dest.resolve()
|
|
1042
1203
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
"# Required for task app auth to environment service",
|
|
1047
|
-
"ENVIRONMENT_API_KEY=",
|
|
1048
|
-
"",
|
|
1049
|
-
"# Optional: for CLI job submission and proxying OpenAI models",
|
|
1050
|
-
"SYNTH_API_KEY=",
|
|
1051
|
-
"OPENAI_API_KEY=",
|
|
1052
|
-
"",
|
|
1053
|
-
"# Optional: set to 'prod' to use production names",
|
|
1054
|
-
"ENVIRONMENT=",
|
|
1055
|
-
]) + "\n")
|
|
1056
|
-
|
|
1057
|
-
# Seed demo_config.toml from packaged default if not present (or overwrite with --force)
|
|
1058
|
-
packaged_cfg = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "demo_task_apps", "math", "config.toml"))
|
|
1204
|
+
if force:
|
|
1205
|
+
dest_input = ""
|
|
1206
|
+
else:
|
|
1059
1207
|
try:
|
|
1060
|
-
|
|
1061
|
-
if not os.path.exists(dst_cfg) or getattr(args, "force", False):
|
|
1062
|
-
shutil.copy2(packaged_cfg, dst_cfg)
|
|
1208
|
+
dest_input = input(f"Destination directory [{default_dest}]: ").strip()
|
|
1063
1209
|
except Exception:
|
|
1064
|
-
|
|
1210
|
+
dest_input = ""
|
|
1211
|
+
destination = Path(dest_input).expanduser().resolve() if dest_input else default_dest
|
|
1212
|
+
|
|
1213
|
+
# Track whether we should skip individual file prompts (if we already cleared the directory)
|
|
1214
|
+
directory_cleared = False
|
|
1215
|
+
|
|
1216
|
+
if destination.exists():
|
|
1217
|
+
if destination.is_file():
|
|
1218
|
+
print(f"Destination {destination} is a file. Provide a directory path.")
|
|
1219
|
+
return 1
|
|
1220
|
+
if any(destination.iterdir()):
|
|
1221
|
+
if force:
|
|
1222
|
+
response = "y"
|
|
1223
|
+
else:
|
|
1224
|
+
try:
|
|
1225
|
+
response = (
|
|
1226
|
+
input(f"Destination {destination} is not empty. Overwrite? [y/N]: ")
|
|
1227
|
+
.strip()
|
|
1228
|
+
.lower()
|
|
1229
|
+
)
|
|
1230
|
+
except (EOFError, KeyboardInterrupt):
|
|
1231
|
+
print("\nCancelled.")
|
|
1232
|
+
return 1
|
|
1233
|
+
if response not in ("y", "yes"):
|
|
1234
|
+
print("Cancelled. Choose another directory or delete the existing one.")
|
|
1235
|
+
return 1
|
|
1236
|
+
# User agreed to overwrite - clear the entire directory including hidden files
|
|
1237
|
+
print(f"Clearing {destination}...")
|
|
1238
|
+
try:
|
|
1239
|
+
# Remove all contents including hidden files (.env, .git, etc.)
|
|
1240
|
+
shutil.rmtree(destination)
|
|
1241
|
+
except Exception as e:
|
|
1242
|
+
print(f"Error clearing directory: {e}")
|
|
1243
|
+
print("Please manually remove the directory and try again.")
|
|
1244
|
+
return 1
|
|
1245
|
+
# Recreate empty directory
|
|
1246
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
1247
|
+
# Verify it's actually empty
|
|
1248
|
+
if any(destination.iterdir()):
|
|
1249
|
+
print(f"Warning: Directory {destination} still contains files after clearing.")
|
|
1250
|
+
print("Some files may not have been removed. Please check manually.")
|
|
1251
|
+
return 1
|
|
1252
|
+
directory_cleared = True
|
|
1253
|
+
else:
|
|
1254
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
1255
|
+
|
|
1256
|
+
if selected.requires_modal:
|
|
1257
|
+
_ensure_modal_installed()
|
|
1258
|
+
|
|
1259
|
+
try:
|
|
1260
|
+
for spec in selected.iter_copy_specs():
|
|
1261
|
+
src_path = spec.absolute_source()
|
|
1262
|
+
if not src_path.exists():
|
|
1263
|
+
print(f"Template source missing: {src_path}")
|
|
1264
|
+
return 1
|
|
1265
|
+
dest_path = (destination / spec.destination).resolve()
|
|
1266
|
+
|
|
1267
|
+
# Handle directory copying
|
|
1268
|
+
if src_path.is_dir():
|
|
1269
|
+
if dest_path.exists() and not directory_cleared:
|
|
1270
|
+
if force:
|
|
1271
|
+
response = "y"
|
|
1272
|
+
else:
|
|
1273
|
+
try:
|
|
1274
|
+
response = (
|
|
1275
|
+
input(f"Directory {dest_path.name} exists. Overwrite? [y/N]: ")
|
|
1276
|
+
.strip()
|
|
1277
|
+
.lower()
|
|
1278
|
+
)
|
|
1279
|
+
except (EOFError, KeyboardInterrupt):
|
|
1280
|
+
print("\nCancelled.")
|
|
1281
|
+
return 1
|
|
1282
|
+
if response not in ("y", "yes"):
|
|
1283
|
+
print(f"Skipping {dest_path.name}")
|
|
1284
|
+
continue
|
|
1285
|
+
shutil.rmtree(dest_path)
|
|
1286
|
+
elif dest_path.exists() and directory_cleared:
|
|
1287
|
+
shutil.rmtree(dest_path)
|
|
1288
|
+
shutil.copytree(src_path, dest_path)
|
|
1289
|
+
else:
|
|
1290
|
+
# Handle file copying
|
|
1291
|
+
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1292
|
+
if dest_path.exists() and not directory_cleared:
|
|
1293
|
+
if force:
|
|
1294
|
+
response = "y"
|
|
1295
|
+
else:
|
|
1296
|
+
try:
|
|
1297
|
+
response = (
|
|
1298
|
+
input(f"File {dest_path.name} exists. Overwrite? [y/N]: ")
|
|
1299
|
+
.strip()
|
|
1300
|
+
.lower()
|
|
1301
|
+
)
|
|
1302
|
+
except (EOFError, KeyboardInterrupt):
|
|
1303
|
+
print("\nCancelled.")
|
|
1304
|
+
return 1
|
|
1305
|
+
if response not in ("y", "yes"):
|
|
1306
|
+
print(f"Skipping {dest_path.name}")
|
|
1307
|
+
continue
|
|
1308
|
+
shutil.copy2(src_path, dest_path)
|
|
1309
|
+
if spec.make_executable:
|
|
1310
|
+
try:
|
|
1311
|
+
st = os.stat(dest_path)
|
|
1312
|
+
os.chmod(dest_path, st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
1313
|
+
except Exception:
|
|
1314
|
+
pass
|
|
1065
1315
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1316
|
+
if selected.env_lines:
|
|
1317
|
+
env_path = destination / ".env"
|
|
1318
|
+
should_write = True
|
|
1319
|
+
if env_path.exists() and not directory_cleared:
|
|
1320
|
+
if force:
|
|
1321
|
+
response = "y"
|
|
1322
|
+
else:
|
|
1323
|
+
try:
|
|
1324
|
+
response = input("File .env exists. Overwrite? [y/N]: ").strip().lower()
|
|
1325
|
+
except (EOFError, KeyboardInterrupt):
|
|
1326
|
+
print("\nCancelled.")
|
|
1327
|
+
return 1
|
|
1328
|
+
should_write = response in ("y", "yes")
|
|
1329
|
+
if should_write:
|
|
1330
|
+
_write_text(str(env_path), "\n".join(selected.env_lines) + "\n")
|
|
1331
|
+
elif not directory_cleared:
|
|
1332
|
+
print("Skipping .env")
|
|
1333
|
+
|
|
1334
|
+
config_src = selected.config_source_path()
|
|
1335
|
+
if config_src and config_src.exists():
|
|
1336
|
+
cfg_dst = (destination / selected.config_destination).resolve()
|
|
1337
|
+
should_copy = True
|
|
1338
|
+
if cfg_dst.exists() and not directory_cleared:
|
|
1339
|
+
if force:
|
|
1340
|
+
response = "y"
|
|
1341
|
+
else:
|
|
1342
|
+
try:
|
|
1343
|
+
response = (
|
|
1344
|
+
input(f"File {cfg_dst.name} exists. Overwrite? [y/N]: ").strip().lower()
|
|
1345
|
+
)
|
|
1346
|
+
except (EOFError, KeyboardInterrupt):
|
|
1347
|
+
print("\nCancelled.")
|
|
1348
|
+
return 1
|
|
1349
|
+
should_copy = response in ("y", "yes")
|
|
1350
|
+
if should_copy:
|
|
1351
|
+
cfg_dst.parent.mkdir(parents=True, exist_ok=True)
|
|
1352
|
+
shutil.copy2(config_src, cfg_dst)
|
|
1353
|
+
elif not directory_cleared:
|
|
1354
|
+
print(f"Skipping {cfg_dst.name}")
|
|
1355
|
+
|
|
1356
|
+
if selected.post_copy is not None:
|
|
1357
|
+
try:
|
|
1358
|
+
selected.post_copy(destination)
|
|
1359
|
+
except Exception as post_exc:
|
|
1360
|
+
print(f"Post-processing failed: {post_exc}")
|
|
1361
|
+
return 1
|
|
1362
|
+
|
|
1363
|
+
# Store demo directory for subsequent commands
|
|
1364
|
+
demo_core.persist_demo_dir(str(destination))
|
|
1365
|
+
|
|
1366
|
+
# Store .env path if it was created
|
|
1367
|
+
env_file = destination / ".env"
|
|
1368
|
+
if env_file.exists():
|
|
1369
|
+
demo_core.persist_env_file_path(str(env_file))
|
|
1370
|
+
|
|
1371
|
+
print(f"Demo template '{selected.name}' materialised at {destination}.")
|
|
1372
|
+
print("Files created:")
|
|
1373
|
+
for spec in selected.iter_copy_specs():
|
|
1374
|
+
print(f" - {spec.destination}")
|
|
1375
|
+
if selected.env_lines:
|
|
1376
|
+
print(" - .env")
|
|
1377
|
+
if selected.config_source_path():
|
|
1378
|
+
print(f" - {selected.config_destination}")
|
|
1379
|
+
print("\nDemo directory stored. Subsequent commands will use this directory automatically.")
|
|
1380
|
+
print("Review the files, edit .env, and run any provided deploy scripts when ready.")
|
|
1078
1381
|
return 0
|
|
1079
|
-
except
|
|
1080
|
-
print(
|
|
1081
|
-
return
|
|
1382
|
+
except KeyboardInterrupt:
|
|
1383
|
+
print("Aborted")
|
|
1384
|
+
return 1
|
|
1385
|
+
except Exception as exc:
|
|
1386
|
+
print(f"Init failed: {exc}")
|
|
1387
|
+
return 1
|
|
1388
|
+
|
|
1082
1389
|
|
|
1390
|
+
def _http(
|
|
1391
|
+
method: str, url: str, headers: dict[str, str] | None = None, body: dict[str, Any] | None = None
|
|
1392
|
+
) -> tuple[int, dict[str, Any] | str]:
|
|
1393
|
+
import json as _json
|
|
1394
|
+
import ssl
|
|
1395
|
+
import urllib.error
|
|
1396
|
+
import urllib.request
|
|
1083
1397
|
|
|
1084
|
-
def _http(method: str, url: str, headers: Dict[str, str] | None = None, body: Dict[str, Any] | None = None) -> tuple[int, Dict[str, Any] | str]:
|
|
1085
|
-
import urllib.request, urllib.error, json as _json, ssl
|
|
1086
1398
|
data = None
|
|
1087
1399
|
if body is not None:
|
|
1088
1400
|
data = _json.dumps(body).encode("utf-8")
|
|
@@ -1119,10 +1431,23 @@ def _write_text(path: str, content: str) -> None:
|
|
|
1119
1431
|
# Note: `prepare` command has been removed; configuration now prepares TOML
|
|
1120
1432
|
|
|
1121
1433
|
|
|
1122
|
-
def
|
|
1434
|
+
def run(
|
|
1435
|
+
config: str | None = None,
|
|
1436
|
+
batch_size: int | None = None,
|
|
1437
|
+
group_size: int | None = None,
|
|
1438
|
+
model: str | None = None,
|
|
1439
|
+
timeout: int = 600,
|
|
1440
|
+
dry_run: bool = False,
|
|
1441
|
+
) -> int:
|
|
1442
|
+
# Change to demo directory if stored
|
|
1443
|
+
demo_dir = demo_core.load_demo_dir()
|
|
1444
|
+
if demo_dir and os.path.isdir(demo_dir):
|
|
1445
|
+
os.chdir(demo_dir)
|
|
1446
|
+
print(f"Using demo directory: {demo_dir}")
|
|
1447
|
+
|
|
1123
1448
|
env = demo_core.load_env()
|
|
1124
1449
|
cwd_env_path = os.path.join(os.getcwd(), ".env")
|
|
1125
|
-
|
|
1450
|
+
demo_core.load_dotenv_file(cwd_env_path)
|
|
1126
1451
|
|
|
1127
1452
|
synth_key = (env.synth_api_key or "").strip()
|
|
1128
1453
|
if not synth_key:
|
|
@@ -1154,7 +1479,7 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1154
1479
|
import tomllib
|
|
1155
1480
|
|
|
1156
1481
|
try:
|
|
1157
|
-
cfg_path = _select_or_create_config(
|
|
1482
|
+
cfg_path = _select_or_create_config(config, env)
|
|
1158
1483
|
except FileNotFoundError as exc:
|
|
1159
1484
|
print(exc)
|
|
1160
1485
|
return 1
|
|
@@ -1162,7 +1487,11 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1162
1487
|
# Detect monorepo launcher and delegate if available (aligns with run_clustered.sh which works)
|
|
1163
1488
|
launcher = "/Users/joshpurtell/Documents/GitHub/monorepo/tests/applications/math/rl/start_math_clustered.py"
|
|
1164
1489
|
if os.path.isfile(launcher):
|
|
1165
|
-
backend_base =
|
|
1490
|
+
backend_base = (
|
|
1491
|
+
env.dev_backend_url[:-4]
|
|
1492
|
+
if env.dev_backend_url.endswith("/api")
|
|
1493
|
+
else env.dev_backend_url
|
|
1494
|
+
)
|
|
1166
1495
|
run_env = os.environ.copy()
|
|
1167
1496
|
run_env["BACKEND_URL"] = backend_base
|
|
1168
1497
|
run_env["SYNTH_API_KEY"] = env.synth_api_key
|
|
@@ -1170,14 +1499,14 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1170
1499
|
run_env["ENVIRONMENT_API_KEY"] = env.env_api_key
|
|
1171
1500
|
run_env["RL_CONFIG_PATH"] = cfg_path
|
|
1172
1501
|
# Optional: TRAINER_START_URL passthrough if already set in environment
|
|
1173
|
-
run_env["TRAINER_START_URL"] = run_env.get("TRAINER_START_URL", "")
|
|
1502
|
+
run_env["TRAINER_START_URL"] = run_env.get("TRAINER_START_URL", "") # type: ignore[misc]
|
|
1174
1503
|
# Forward convenience knobs
|
|
1175
|
-
if
|
|
1176
|
-
run_env["RL_BATCH_SIZE"] = str(int(
|
|
1177
|
-
if
|
|
1178
|
-
run_env["RL_GROUP_SIZE"] = str(int(
|
|
1179
|
-
if
|
|
1180
|
-
run_env["RL_MODEL"] =
|
|
1504
|
+
if batch_size is not None:
|
|
1505
|
+
run_env["RL_BATCH_SIZE"] = str(int(batch_size))
|
|
1506
|
+
if group_size is not None:
|
|
1507
|
+
run_env["RL_GROUP_SIZE"] = str(int(group_size))
|
|
1508
|
+
if model:
|
|
1509
|
+
run_env["RL_MODEL"] = model
|
|
1181
1510
|
cmd = ["uv", "run", "python", launcher]
|
|
1182
1511
|
print(f"Launching monorepo clustered runner: {' '.join(cmd)}")
|
|
1183
1512
|
code = _popen_stream(cmd, env=run_env)
|
|
@@ -1192,33 +1521,33 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1192
1521
|
ek = (env.env_api_key or "").strip()
|
|
1193
1522
|
print("Hint: If backend responded 401, verify SYNTH_API_KEY for:", base_url)
|
|
1194
1523
|
if sk:
|
|
1195
|
-
print(f"
|
|
1524
|
+
print(f" {_key_preview(sk, 'SYNTH_API_KEY')}")
|
|
1196
1525
|
if ek:
|
|
1197
|
-
print(f"
|
|
1198
|
-
print(
|
|
1526
|
+
print(f" {_key_preview(ek, 'ENVIRONMENT_API_KEY')}")
|
|
1527
|
+
print(
|
|
1528
|
+
"Ensure the ENVIRONMENT_API_KEY you deployed with matches the task app and remains exported."
|
|
1529
|
+
)
|
|
1199
1530
|
return code
|
|
1200
1531
|
|
|
1201
1532
|
# Fallback: legacy jobs API flow
|
|
1202
1533
|
with open(cfg_path, "rb") as fh:
|
|
1203
1534
|
inline_cfg = tomllib.load(fh)
|
|
1204
|
-
with open(cfg_path
|
|
1535
|
+
with open(cfg_path) as fh2:
|
|
1205
1536
|
toml_text = fh2.read()
|
|
1206
|
-
if
|
|
1207
|
-
inline_cfg.setdefault("training", {})["batch_size"] = int(
|
|
1208
|
-
if
|
|
1209
|
-
inline_cfg.setdefault("training", {})["group_size"] = int(
|
|
1210
|
-
model_name =
|
|
1537
|
+
if batch_size is not None:
|
|
1538
|
+
inline_cfg.setdefault("training", {})["batch_size"] = int(batch_size)
|
|
1539
|
+
if group_size is not None:
|
|
1540
|
+
inline_cfg.setdefault("training", {})["group_size"] = int(group_size)
|
|
1541
|
+
model_name = model or (inline_cfg.get("model", {}) or {}).get("name", "Qwen/Qwen3-0.6B") # type: ignore[misc]
|
|
1211
1542
|
api = env.dev_backend_url.rstrip("/") + ("" if env.dev_backend_url.endswith("/api") else "/api")
|
|
1212
1543
|
# Print backend and key preview before request for clearer diagnostics
|
|
1213
1544
|
try:
|
|
1214
1545
|
sk = (env.synth_api_key or "").strip()
|
|
1215
|
-
sk_len = len(sk)
|
|
1216
|
-
sk_tail = sk[-5:] if sk_len >= 5 else sk
|
|
1217
1546
|
print(f"[run] Backend API: {api}")
|
|
1218
|
-
print(f"[run]
|
|
1547
|
+
print(f"[run] {_key_preview(sk, 'SYNTH_API_KEY')}")
|
|
1219
1548
|
except Exception:
|
|
1220
1549
|
pass
|
|
1221
|
-
data_fragment:
|
|
1550
|
+
data_fragment: dict[str, Any] = {
|
|
1222
1551
|
"model": model_name,
|
|
1223
1552
|
"endpoint_base_url": env.task_app_base_url,
|
|
1224
1553
|
"config": inline_cfg,
|
|
@@ -1229,30 +1558,35 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1229
1558
|
if env.env_api_key:
|
|
1230
1559
|
data_fragment["environment_api_key"] = env.env_api_key
|
|
1231
1560
|
for k in ("training", "evaluation", "rollout", "topology", "vllm"):
|
|
1232
|
-
if isinstance(inline_cfg.get(k), dict):
|
|
1561
|
+
if isinstance(inline_cfg.get(k), dict): # type: ignore[misc]
|
|
1233
1562
|
data_fragment[k] = inline_cfg[k]
|
|
1234
1563
|
compute = {}
|
|
1235
|
-
if isinstance(inline_cfg.get("compute"), dict):
|
|
1236
|
-
if inline_cfg["compute"].get("gpu_type"):
|
|
1564
|
+
if isinstance(inline_cfg.get("compute"), dict): # type: ignore[misc]
|
|
1565
|
+
if inline_cfg["compute"].get("gpu_type"): # type: ignore[misc]
|
|
1237
1566
|
compute["gpu_type"] = str(inline_cfg["compute"]["gpu_type"]).upper()
|
|
1238
|
-
if inline_cfg["compute"].get("gpu_count"):
|
|
1239
|
-
compute["gpu_count"] = int(inline_cfg["compute"]["gpu_count"])
|
|
1567
|
+
if inline_cfg["compute"].get("gpu_count"): # type: ignore[misc]
|
|
1568
|
+
compute["gpu_count"] = int(inline_cfg["compute"]["gpu_count"])
|
|
1240
1569
|
if not compute:
|
|
1241
|
-
topo = inline_cfg.get("topology") or {}
|
|
1242
|
-
gshape = str(topo.get("gpu_type") or "")
|
|
1570
|
+
topo = inline_cfg.get("topology") or {} # type: ignore[misc]
|
|
1571
|
+
gshape = str(topo.get("gpu_type") or "") # type: ignore[misc]
|
|
1243
1572
|
if ":" in gshape:
|
|
1244
1573
|
t, c = gshape.split(":", 1)
|
|
1245
1574
|
compute = {"gpu_type": t.upper(), "gpu_count": int(c)}
|
|
1246
|
-
body:
|
|
1575
|
+
body: dict[str, Any] = {
|
|
1247
1576
|
"job_type": "rl",
|
|
1248
1577
|
"data": data_fragment,
|
|
1249
1578
|
}
|
|
1250
1579
|
if compute:
|
|
1251
1580
|
body["compute"] = compute
|
|
1252
|
-
code, js = _http(
|
|
1253
|
-
"
|
|
1254
|
-
|
|
1255
|
-
|
|
1581
|
+
code, js = _http(
|
|
1582
|
+
"POST",
|
|
1583
|
+
api + "/rl/jobs",
|
|
1584
|
+
headers={
|
|
1585
|
+
"Content-Type": "application/json",
|
|
1586
|
+
"Authorization": f"Bearer {env.synth_api_key}",
|
|
1587
|
+
},
|
|
1588
|
+
body=body,
|
|
1589
|
+
)
|
|
1256
1590
|
if code not in (200, 201) or not isinstance(js, dict):
|
|
1257
1591
|
print("Job create failed:", code)
|
|
1258
1592
|
print(f"Backend: {api}")
|
|
@@ -1264,19 +1598,81 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1264
1598
|
except Exception:
|
|
1265
1599
|
print(str(js))
|
|
1266
1600
|
print("Request body was:\n" + json.dumps(body, indent=2))
|
|
1601
|
+
try:
|
|
1602
|
+
auth_preview = _key_preview(env.synth_api_key or "", "SYNTH_API_KEY (auth)")
|
|
1603
|
+
print(f"[run] {auth_preview}")
|
|
1604
|
+
except Exception:
|
|
1605
|
+
pass
|
|
1606
|
+
try:
|
|
1607
|
+
data_block = body.get("data") if isinstance(body, dict) else None # type: ignore[misc]
|
|
1608
|
+
env_key_body = ""
|
|
1609
|
+
if isinstance(data_block, dict):
|
|
1610
|
+
env_key_body = str(data_block.get("environment_api_key") or "") # type: ignore[misc]
|
|
1611
|
+
if env_key_body:
|
|
1612
|
+
print(f"[run] {_key_preview(env_key_body, 'environment_api_key (body)')}")
|
|
1613
|
+
except Exception:
|
|
1614
|
+
pass
|
|
1615
|
+
try:
|
|
1616
|
+
current_env_key = env.env_api_key or ""
|
|
1617
|
+
if current_env_key:
|
|
1618
|
+
print(f"[run] {_key_preview(current_env_key, 'ENVIRONMENT_API_KEY (current)')}")
|
|
1619
|
+
except Exception:
|
|
1620
|
+
pass
|
|
1621
|
+
if isinstance(js, dict):
|
|
1622
|
+
detail = js.get("detail") # type: ignore[misc]
|
|
1623
|
+
if isinstance(detail, dict):
|
|
1624
|
+
try:
|
|
1625
|
+
sent_key = detail.get("sent_key") # type: ignore[misc]
|
|
1626
|
+
if isinstance(sent_key, str):
|
|
1627
|
+
print(
|
|
1628
|
+
f"[run] Backend detail.sent_key {_key_preview(sent_key, 'detail.sent_key')}"
|
|
1629
|
+
)
|
|
1630
|
+
except Exception:
|
|
1631
|
+
pass
|
|
1632
|
+
try:
|
|
1633
|
+
sent_keys = detail.get("sent_keys") # type: ignore[misc]
|
|
1634
|
+
if isinstance(sent_keys, list | tuple):
|
|
1635
|
+
previews = []
|
|
1636
|
+
for idx, val in enumerate(sent_keys):
|
|
1637
|
+
if isinstance(val, str):
|
|
1638
|
+
previews.append(_key_preview(val, f"detail.sent_keys[{idx}]"))
|
|
1639
|
+
if previews:
|
|
1640
|
+
joined = "; ".join(previews)
|
|
1641
|
+
print(f"[run] Backend detail.sent_keys previews: {joined}")
|
|
1642
|
+
except Exception:
|
|
1643
|
+
pass
|
|
1644
|
+
try:
|
|
1645
|
+
key_prefix = detail.get("sent_key_prefix") # type: ignore[misc]
|
|
1646
|
+
if isinstance(key_prefix, str):
|
|
1647
|
+
print(f"[run] Backend detail.sent_key_prefix={key_prefix}")
|
|
1648
|
+
except Exception:
|
|
1649
|
+
pass
|
|
1650
|
+
try:
|
|
1651
|
+
health_url = detail.get("health_url")
|
|
1652
|
+
if isinstance(health_url, str):
|
|
1653
|
+
print(f"[run] Backend detail.health_url={health_url}")
|
|
1654
|
+
except Exception:
|
|
1655
|
+
pass
|
|
1267
1656
|
# Extra hints for auth failures
|
|
1268
1657
|
try:
|
|
1269
1658
|
sk = (env.synth_api_key or "").strip()
|
|
1270
|
-
if int(code) == 401 or (
|
|
1659
|
+
if int(code) == 401 or (
|
|
1660
|
+
isinstance(js, dict)
|
|
1661
|
+
and any(isinstance(v, str) and "Invalid API key" in v for v in js.values())
|
|
1662
|
+
):
|
|
1271
1663
|
base_url = env.dev_backend_url
|
|
1272
|
-
print(
|
|
1664
|
+
print(
|
|
1665
|
+
"Hint: HTTP 401 Unauthorized from backend. Verify SYNTH_API_KEY for:", base_url
|
|
1666
|
+
)
|
|
1273
1667
|
if sk:
|
|
1274
|
-
print(f"
|
|
1275
|
-
print(
|
|
1668
|
+
print(f" {_key_preview(sk, 'SYNTH_API_KEY')}")
|
|
1669
|
+
print(
|
|
1670
|
+
"Ensure the ENVIRONMENT_API_KEY and OPENAI_API_KEY used for deployment remain valid."
|
|
1671
|
+
)
|
|
1276
1672
|
except Exception:
|
|
1277
1673
|
pass
|
|
1278
1674
|
return 2
|
|
1279
|
-
job_id = js.get("job_id") or js.get("id") or ""
|
|
1675
|
+
job_id = js.get("job_id") or js.get("id") or "" # type: ignore[misc]
|
|
1280
1676
|
if not job_id:
|
|
1281
1677
|
print("Job id missing in response:", js)
|
|
1282
1678
|
print("Request body was:\n" + json.dumps(body, indent=2))
|
|
@@ -1296,7 +1692,7 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1296
1692
|
start_t = time.time()
|
|
1297
1693
|
while True:
|
|
1298
1694
|
sc, sj = _http("GET", api + f"/learning/jobs/{job_id}")
|
|
1299
|
-
status = (sj.get("status") if isinstance(sj, dict) else "") if sc == 200 else ""
|
|
1695
|
+
status = (sj.get("status") if isinstance(sj, dict) else "") if sc == 200 else "" # type: ignore[misc]
|
|
1300
1696
|
if status and status != last_status:
|
|
1301
1697
|
last_status = status
|
|
1302
1698
|
print("status →", status)
|
|
@@ -1308,14 +1704,14 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1308
1704
|
api + f"/orchestration/jobs/{job_id}/events?since_seq={since}&limit=200",
|
|
1309
1705
|
)
|
|
1310
1706
|
if ec == 200 and isinstance(ej, dict):
|
|
1311
|
-
events = ej.get("events") or ej.get("data") or []
|
|
1707
|
+
events = ej.get("events") or ej.get("data") or [] # type: ignore[misc]
|
|
1312
1708
|
for e in events:
|
|
1313
1709
|
seq = int(e.get("seq") or 0)
|
|
1314
1710
|
if seq <= since:
|
|
1315
1711
|
continue
|
|
1316
1712
|
since = seq
|
|
1317
|
-
typ = str(e.get("type") or e.get("event_type") or "").lower()
|
|
1318
|
-
msg = e.get("message") or e.get("msg") or ""
|
|
1713
|
+
typ = str(e.get("type") or e.get("event_type") or "").lower() # type: ignore[misc]
|
|
1714
|
+
msg = e.get("message") or e.get("msg") or "" # type: ignore[misc]
|
|
1319
1715
|
if typ in (
|
|
1320
1716
|
"rl.eval.started",
|
|
1321
1717
|
"rl.eval.summary",
|
|
@@ -1324,70 +1720,16 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
1324
1720
|
"rl.performance.metrics",
|
|
1325
1721
|
):
|
|
1326
1722
|
print(f"[{seq}] {typ}: {msg}")
|
|
1327
|
-
mc, mj = _http(
|
|
1328
|
-
"GET", api + f"/learning/jobs/{job_id}/metrics?after_step=-1&limit=50"
|
|
1329
|
-
)
|
|
1723
|
+
mc, mj = _http("GET", api + f"/learning/jobs/{job_id}/metrics?after_step=-1&limit=50")
|
|
1330
1724
|
if mc == 200 and isinstance(mj, dict):
|
|
1331
|
-
pts = mj.get("points") or []
|
|
1725
|
+
pts = mj.get("points") or [] # type: ignore[misc]
|
|
1332
1726
|
for p in pts:
|
|
1333
|
-
name = p.get("name")
|
|
1727
|
+
name = p.get("name") # type: ignore[misc]
|
|
1334
1728
|
if name == "eval.reward_mean":
|
|
1335
|
-
print(f"metric eval.reward_mean step={p.get('step')} value={p.get('value')}")
|
|
1729
|
+
print(f"metric eval.reward_mean step={p.get('step')} value={p.get('value')}") # type: ignore[misc]
|
|
1336
1730
|
break
|
|
1337
|
-
if time.time() - start_t > (
|
|
1731
|
+
if time.time() - start_t > (timeout or 600):
|
|
1338
1732
|
print("Timeout waiting for terminal state.")
|
|
1339
1733
|
break
|
|
1340
1734
|
time.sleep(2)
|
|
1341
1735
|
return 0
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
def main(argv: list[str] | None = None) -> int:
|
|
1345
|
-
p = argparse.ArgumentParser(prog="synth-ai")
|
|
1346
|
-
sub = p.add_subparsers(dest="cmd")
|
|
1347
|
-
|
|
1348
|
-
def _add_parser(names: list[str], *, configure: Callable[[argparse.ArgumentParser], None]) -> None:
|
|
1349
|
-
for name in names:
|
|
1350
|
-
parser = sub.add_parser(name)
|
|
1351
|
-
configure(parser)
|
|
1352
|
-
|
|
1353
|
-
_add_parser(["rl_demo.setup", "demo.setup"], configure=lambda parser: parser.set_defaults(func=cmd_setup))
|
|
1354
|
-
|
|
1355
|
-
def _init_opts(parser):
|
|
1356
|
-
parser.add_argument("--force", action="store_true", help="Overwrite existing files in CWD")
|
|
1357
|
-
parser.set_defaults(func=cmd_init)
|
|
1358
|
-
|
|
1359
|
-
_add_parser(["rl_demo.init", "demo.init"], configure=_init_opts)
|
|
1360
|
-
|
|
1361
|
-
# (prepare command removed)
|
|
1362
|
-
|
|
1363
|
-
def _deploy_opts(parser):
|
|
1364
|
-
parser.add_argument("--local", action="store_true", help="Run local FastAPI instead of Modal deploy")
|
|
1365
|
-
parser.add_argument("--app", type=str, default=None, help="Path to Modal app.py for uv run modal deploy")
|
|
1366
|
-
parser.add_argument("--name", type=str, default="synth-math-demo", help="Modal app name")
|
|
1367
|
-
parser.add_argument("--script", type=str, default=None, help="Path to deploy_task_app.sh (optional legacy)")
|
|
1368
|
-
parser.set_defaults(func=cmd_deploy)
|
|
1369
|
-
|
|
1370
|
-
_add_parser(["rl_demo.deploy", "demo.deploy"], configure=_deploy_opts)
|
|
1371
|
-
|
|
1372
|
-
_add_parser(["rl_demo.configure", "demo.configure"], configure=lambda parser: parser.set_defaults(func=cmd_run))
|
|
1373
|
-
|
|
1374
|
-
def _run_opts(parser):
|
|
1375
|
-
parser.add_argument("--config", type=str, default=None, help="Path to TOML config (skip prompt)")
|
|
1376
|
-
parser.add_argument("--batch-size", type=int, default=None)
|
|
1377
|
-
parser.add_argument("--group-size", type=int, default=None)
|
|
1378
|
-
parser.add_argument("--model", type=str, default=None)
|
|
1379
|
-
parser.add_argument("--timeout", type=int, default=600)
|
|
1380
|
-
parser.add_argument("--dry-run", action="store_true", help="Print request body and exit")
|
|
1381
|
-
parser.set_defaults(func=cmd_run)
|
|
1382
|
-
|
|
1383
|
-
_add_parser(["run", "rl_demo.run", "demo.run"], configure=_run_opts)
|
|
1384
|
-
|
|
1385
|
-
args = p.parse_args(argv)
|
|
1386
|
-
if not hasattr(args, "func"):
|
|
1387
|
-
p.print_help()
|
|
1388
|
-
return 1
|
|
1389
|
-
return int(args.func(args) or 0)
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
if __name__ == "__main__":
|
|
1393
|
-
sys.exit(main())
|