synth-ai 0.2.16__py3-none-any.whl → 0.2.19__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of synth-ai might be problematic. Click here for more details.
- examples/analyze_semantic_words.sh +2 -2
- examples/baseline/banking77_baseline.py +204 -0
- examples/baseline/crafter_baseline.py +407 -0
- examples/baseline/pokemon_red_baseline.py +326 -0
- examples/baseline/simple_baseline.py +56 -0
- examples/baseline/warming_up_to_rl_baseline.py +239 -0
- examples/blog_posts/gepa/README.md +355 -0
- examples/blog_posts/gepa/configs/banking77_gepa_local.toml +95 -0
- examples/blog_posts/gepa/configs/banking77_gepa_test.toml +82 -0
- examples/blog_posts/gepa/configs/banking77_mipro_local.toml +52 -0
- examples/blog_posts/gepa/configs/hotpotqa_gepa_local.toml +59 -0
- examples/blog_posts/gepa/configs/hotpotqa_gepa_qwen.toml +36 -0
- examples/blog_posts/gepa/configs/hotpotqa_mipro_local.toml +53 -0
- examples/blog_posts/gepa/configs/hover_gepa_local.toml +59 -0
- examples/blog_posts/gepa/configs/hover_gepa_qwen.toml +36 -0
- examples/blog_posts/gepa/configs/hover_mipro_local.toml +53 -0
- examples/blog_posts/gepa/configs/ifbench_gepa_local.toml +59 -0
- examples/blog_posts/gepa/configs/ifbench_gepa_qwen.toml +36 -0
- examples/blog_posts/gepa/configs/ifbench_mipro_local.toml +53 -0
- examples/blog_posts/gepa/configs/pupa_gepa_local.toml +60 -0
- examples/blog_posts/gepa/configs/pupa_mipro_local.toml +54 -0
- examples/blog_posts/gepa/deploy_banking77_task_app.sh +41 -0
- examples/blog_posts/gepa/gepa_baseline.py +204 -0
- examples/blog_posts/gepa/query_prompts_example.py +97 -0
- examples/blog_posts/gepa/run_gepa_banking77.sh +87 -0
- examples/blog_posts/gepa/task_apps.py +105 -0
- examples/blog_posts/gepa/test_gepa_local.sh +67 -0
- examples/blog_posts/gepa/verify_banking77_setup.sh +123 -0
- examples/blog_posts/pokemon_vl/README.md +98 -0
- examples/blog_posts/pokemon_vl/configs/eval_gpt5nano.toml +26 -0
- examples/blog_posts/pokemon_vl/configs/eval_qwen3_vl.toml +27 -0
- examples/blog_posts/pokemon_vl/configs/eval_rl_final.toml +24 -0
- examples/blog_posts/pokemon_vl/configs/filter_high_reward.toml +10 -0
- examples/blog_posts/pokemon_vl/configs/train_rl_from_sft.toml +43 -0
- examples/blog_posts/pokemon_vl/configs/train_sft_qwen4b_vl.toml +40 -0
- examples/blog_posts/pokemon_vl/extract_images.py +239 -0
- examples/blog_posts/pokemon_vl/pokemon_vl_baseline.py +326 -0
- examples/blog_posts/pokemon_vl/run_eval_extract_images.py +209 -0
- examples/blog_posts/pokemon_vl/run_qwen_eval_extract_images.py +212 -0
- examples/blog_posts/pokemon_vl/text_box_analysis.md +106 -0
- examples/blog_posts/warming_up_to_rl/ARCHITECTURE.md +195 -0
- examples/blog_posts/warming_up_to_rl/FINAL_TEST_RESULTS.md +127 -0
- examples/blog_posts/warming_up_to_rl/INFERENCE_SUCCESS.md +132 -0
- examples/blog_posts/warming_up_to_rl/README.md +158 -0
- examples/blog_posts/warming_up_to_rl/SMOKE_TESTING.md +164 -0
- examples/blog_posts/warming_up_to_rl/SMOKE_TEST_COMPLETE.md +253 -0
- examples/blog_posts/warming_up_to_rl/configs/eval_baseline_qwen32b_10x20.toml +25 -0
- examples/blog_posts/warming_up_to_rl/configs/eval_ft_qwen4b.toml +25 -0
- examples/blog_posts/warming_up_to_rl/configs/eval_ft_qwen4b_10x20.toml +26 -0
- examples/blog_posts/warming_up_to_rl/configs/eval_groq_qwen32b.toml +25 -0
- examples/blog_posts/warming_up_to_rl/configs/eval_openai_gpt_oss_120b.toml +29 -0
- examples/blog_posts/warming_up_to_rl/configs/filter_high_reward_dataset.toml +10 -0
- examples/blog_posts/warming_up_to_rl/configs/smoke_test.toml +75 -0
- examples/blog_posts/warming_up_to_rl/configs/train_rl_from_sft.toml +91 -0
- examples/blog_posts/warming_up_to_rl/configs/train_sft_qwen4b.toml +40 -0
- examples/blog_posts/warming_up_to_rl/warming_up_to_rl_baseline.py +187 -0
- examples/dev/qwen3_32b_qlora_4xh100.toml +5 -0
- examples/multi_step/configs/VERILOG_REWARDS.md +4 -0
- examples/multi_step/configs/VERILOG_RL_CHECKLIST.md +4 -0
- examples/multi_step/configs/crafter_rl_outcome.toml +2 -1
- examples/multi_step/configs/crafter_rl_stepwise_hosted_judge.toml +65 -107
- examples/multi_step/configs/crafter_rl_stepwise_shaped.toml +2 -1
- examples/multi_step/configs/crafter_rl_stepwise_simple.toml +2 -1
- examples/multi_step/configs/crafter_rl_stepwise_simple_NEW_FORMAT.toml +105 -0
- examples/multi_step/configs/verilog_rl_lora.toml +80 -123
- examples/qwen_coder/configs/coder_lora_30b.toml +1 -3
- examples/qwen_coder/configs/coder_lora_4b.toml +4 -1
- examples/qwen_coder/configs/coder_lora_small.toml +1 -3
- examples/qwen_vl/README.md +10 -12
- examples/qwen_vl/SETUP_COMPLETE.md +7 -8
- examples/qwen_vl/VISION_TESTS_COMPLETE.md +2 -3
- examples/qwen_vl/collect_data_via_cli.md +76 -84
- examples/qwen_vl/collect_vision_traces.py +4 -4
- examples/qwen_vl/configs/crafter_rl_vision_qwen3vl4b.toml +40 -57
- examples/qwen_vl/configs/crafter_vlm_sft_example.toml +1 -2
- examples/qwen_vl/configs/eval_gpt4o_mini_vision.toml +20 -37
- examples/qwen_vl/configs/eval_gpt5nano_vision.toml +21 -40
- examples/qwen_vl/configs/eval_qwen3vl_vision.toml +26 -0
- examples/qwen_vl/configs/{filter_qwen2vl_sft.toml → filter_qwen3vl_sft.toml} +4 -5
- examples/qwen_vl/configs/filter_vision_sft.toml +2 -3
- examples/qwen_vl/crafter_qwen_vl_agent.py +5 -5
- examples/qwen_vl/run_vision_comparison.sh +6 -7
- examples/rl/README.md +5 -5
- examples/rl/configs/rl_from_base_qwen.toml +26 -1
- examples/rl/configs/rl_from_base_qwen17.toml +6 -2
- examples/rl/task_app/README.md +1 -2
- examples/rl/task_app/math_single_step.py +2 -2
- examples/run_crafter_demo.sh +2 -2
- examples/sft/README.md +1 -1
- examples/sft/configs/crafter_fft_qwen0p6b.toml +4 -1
- examples/sft/configs/crafter_lora_qwen0p6b.toml +4 -1
- examples/swe/task_app/README.md +32 -2
- examples/swe/task_app/grpo_swe_mini.py +4 -0
- examples/swe/task_app/hosted/envs/crafter/react_agent.py +1 -1
- examples/swe/task_app/hosted/envs/mini_swe/environment.py +37 -10
- examples/swe/task_app/hosted/inference/openai_client.py +4 -38
- examples/swe/task_app/hosted/policy_routes.py +17 -0
- examples/swe/task_app/hosted/rollout.py +4 -2
- examples/swe/task_app/morph_backend.py +178 -0
- examples/task_apps/banking77/__init__.py +6 -0
- examples/task_apps/banking77/banking77_task_app.py +841 -0
- examples/task_apps/banking77/deploy_wrapper.py +46 -0
- examples/task_apps/crafter/CREATE_SFT_DATASET.md +4 -0
- examples/task_apps/crafter/FILTER_COMMAND_STATUS.md +4 -0
- examples/task_apps/crafter/FILTER_COMMAND_SUCCESS.md +4 -0
- examples/task_apps/crafter/task_app/README.md +1 -1
- examples/task_apps/crafter/task_app/grpo_crafter.py +90 -5
- examples/task_apps/crafter/task_app/grpo_crafter_task_app.py +1 -1
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/policy.py +4 -26
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/react_agent.py +1 -2
- examples/task_apps/crafter/task_app/synth_envs_hosted/hosted_app.py +49 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/inference/openai_client.py +372 -107
- examples/task_apps/crafter/task_app/synth_envs_hosted/policy_routes.py +81 -12
- examples/task_apps/crafter/task_app/synth_envs_hosted/rollout.py +82 -11
- examples/task_apps/crafter/task_app/synth_envs_hosted/utils.py +194 -1
- examples/task_apps/enron/task_app/grpo_enron_task_app.py +1 -1
- examples/task_apps/gepa_benchmarks/__init__.py +7 -0
- examples/task_apps/gepa_benchmarks/common.py +260 -0
- examples/task_apps/gepa_benchmarks/hotpotqa_task_app.py +507 -0
- examples/task_apps/gepa_benchmarks/hover_task_app.py +436 -0
- examples/task_apps/gepa_benchmarks/ifbench_task_app.py +563 -0
- examples/task_apps/gepa_benchmarks/pupa_task_app.py +460 -0
- examples/task_apps/math/README.md +1 -2
- examples/task_apps/pokemon_red/README.md +3 -4
- examples/task_apps/pokemon_red/README_IMAGE_ONLY_EVAL.md +4 -0
- examples/task_apps/pokemon_red/eval_image_only_gpt4o.toml +6 -5
- examples/task_apps/pokemon_red/eval_pokemon_red_policy.py +1 -2
- examples/task_apps/pokemon_red/task_app.py +288 -39
- examples/task_apps/sokoban/README.md +2 -3
- examples/task_apps/verilog/eval_groq_qwen32b.toml +12 -14
- examples/task_apps/verilog/task_app/grpo_verilog_task_app.py +1 -1
- examples/vlm/configs/crafter_vlm_gpt4o.toml +4 -1
- examples/warming_up_to_rl/configs/crafter_fft.toml +4 -1
- examples/warming_up_to_rl/configs/crafter_fft_4b.toml +0 -2
- examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +3 -2
- examples/warming_up_to_rl/run_local_rollout_traced.py +1 -1
- examples/warming_up_to_rl/task_app/README.md +1 -1
- examples/warming_up_to_rl/task_app/grpo_crafter.py +185 -5
- examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +1 -1
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +3 -27
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +1 -1
- examples/warming_up_to_rl/task_app/synth_envs_hosted/hosted_app.py +49 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +156 -45
- examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +37 -4
- examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +33 -3
- examples/warming_up_to_rl/task_app/synth_envs_hosted/utils.py +67 -0
- examples/workflows/math_rl/configs/rl_from_base_qwen.toml +27 -0
- examples/workflows/math_rl/configs/rl_from_base_qwen17.toml +6 -0
- synth_ai/api/train/builders.py +99 -4
- synth_ai/api/train/cli.py +516 -26
- synth_ai/api/train/config_finder.py +13 -2
- synth_ai/api/train/configs/__init__.py +23 -2
- synth_ai/api/train/configs/prompt_learning.py +442 -0
- synth_ai/api/train/configs/rl.py +61 -7
- synth_ai/api/train/configs/sft.py +6 -2
- synth_ai/api/train/configs/shared.py +59 -2
- synth_ai/api/train/task_app.py +1 -1
- synth_ai/api/train/validators.py +277 -0
- synth_ai/auth/credentials.py +119 -0
- synth_ai/baseline/__init__.py +25 -0
- synth_ai/baseline/config.py +209 -0
- synth_ai/baseline/discovery.py +214 -0
- synth_ai/baseline/execution.py +146 -0
- synth_ai/cli/__init__.py +94 -18
- synth_ai/cli/__main__.py +0 -0
- synth_ai/cli/claude.py +70 -0
- synth_ai/cli/codex.py +84 -0
- synth_ai/cli/commands/__init__.py +18 -0
- synth_ai/cli/commands/baseline/__init__.py +12 -0
- synth_ai/cli/commands/baseline/core.py +637 -0
- synth_ai/cli/commands/baseline/list.py +93 -0
- synth_ai/cli/commands/demo/__init__.py +6 -0
- synth_ai/cli/commands/demo/core.py +163 -0
- synth_ai/cli/commands/eval/__init__.py +19 -0
- synth_ai/cli/commands/eval/core.py +1112 -0
- synth_ai/cli/commands/eval/errors.py +81 -0
- synth_ai/cli/commands/eval/validation.py +133 -0
- synth_ai/cli/commands/filter/__init__.py +12 -0
- synth_ai/cli/commands/filter/core.py +424 -0
- synth_ai/cli/commands/filter/errors.py +55 -0
- synth_ai/cli/commands/filter/validation.py +77 -0
- synth_ai/cli/commands/help/__init__.py +177 -0
- synth_ai/cli/commands/help/core.py +72 -0
- synth_ai/cli/commands/smoke/__init__.py +7 -0
- synth_ai/cli/commands/smoke/core.py +1436 -0
- synth_ai/cli/commands/status/__init__.py +64 -0
- synth_ai/cli/commands/status/client.py +192 -0
- synth_ai/cli/commands/status/config.py +92 -0
- synth_ai/cli/commands/status/errors.py +20 -0
- synth_ai/cli/commands/status/formatters.py +164 -0
- synth_ai/cli/commands/status/subcommands/__init__.py +9 -0
- synth_ai/cli/commands/status/subcommands/files.py +79 -0
- synth_ai/cli/commands/status/subcommands/jobs.py +334 -0
- synth_ai/cli/commands/status/subcommands/models.py +79 -0
- synth_ai/cli/commands/status/subcommands/pricing.py +22 -0
- synth_ai/cli/commands/status/subcommands/runs.py +81 -0
- synth_ai/cli/commands/status/subcommands/summary.py +47 -0
- synth_ai/cli/commands/status/subcommands/usage.py +203 -0
- synth_ai/cli/commands/status/utils.py +114 -0
- synth_ai/cli/commands/train/__init__.py +53 -0
- synth_ai/cli/commands/train/core.py +21 -0
- synth_ai/cli/commands/train/errors.py +117 -0
- synth_ai/cli/commands/train/judge_schemas.py +200 -0
- synth_ai/cli/commands/train/judge_validation.py +305 -0
- synth_ai/cli/commands/train/validation.py +386 -0
- synth_ai/cli/demo.py +30 -158
- synth_ai/cli/deploy/__init__.py +43 -0
- synth_ai/cli/deploy.py +162 -0
- synth_ai/cli/eval/__init__.py +36 -0
- synth_ai/cli/eval/core.py +5 -0
- synth_ai/cli/eval/errors.py +31 -0
- synth_ai/cli/eval/validation.py +5 -0
- synth_ai/cli/filter/__init__.py +28 -0
- synth_ai/cli/filter/core.py +5 -0
- synth_ai/cli/filter/errors.py +23 -0
- synth_ai/cli/filter/validation.py +5 -0
- synth_ai/cli/legacy_root_backup.py +14 -8
- synth_ai/cli/modal_serve/__init__.py +12 -0
- synth_ai/cli/modal_serve/core.py +14 -0
- synth_ai/cli/modal_serve/errors.py +8 -0
- synth_ai/cli/modal_serve/validation.py +11 -0
- synth_ai/cli/opencode.py +107 -0
- synth_ai/cli/root.py +9 -5
- synth_ai/cli/serve/__init__.py +12 -0
- synth_ai/cli/serve/core.py +14 -0
- synth_ai/cli/serve/errors.py +8 -0
- synth_ai/cli/serve/validation.py +11 -0
- synth_ai/cli/setup.py +20 -265
- synth_ai/cli/status.py +7 -126
- synth_ai/cli/task_app_deploy.py +1 -10
- synth_ai/cli/task_app_modal_serve.py +4 -9
- synth_ai/cli/task_app_serve.py +4 -11
- synth_ai/cli/task_apps.py +51 -1480
- synth_ai/cli/train/__init__.py +12 -0
- synth_ai/cli/train/core.py +21 -0
- synth_ai/cli/train/errors.py +8 -0
- synth_ai/cli/train/validation.py +24 -0
- synth_ai/cli/train.py +1 -14
- synth_ai/demos/crafter/grpo_crafter_task_app.py +1 -1
- synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +1 -1
- synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +7 -4
- synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +9 -5
- synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +4 -3
- synth_ai/environments/examples/red/engine.py +33 -12
- synth_ai/environments/examples/red/engine_helpers/reward_components.py +151 -179
- synth_ai/environments/examples/red/environment.py +26 -0
- synth_ai/environments/examples/red/trace_hooks_v3.py +168 -0
- synth_ai/http.py +12 -0
- synth_ai/judge_schemas.py +10 -10
- synth_ai/learning/__init__.py +10 -0
- synth_ai/learning/prompt_learning_client.py +276 -0
- synth_ai/learning/prompt_learning_types.py +184 -0
- synth_ai/learning/rl/client.py +3 -1
- synth_ai/pricing/__init__.py +2 -0
- synth_ai/pricing/model_pricing.py +57 -0
- synth_ai/streaming/__init__.py +29 -0
- synth_ai/streaming/config.py +94 -0
- synth_ai/streaming/handlers.py +518 -0
- synth_ai/streaming/streamer.py +320 -0
- synth_ai/streaming/types.py +95 -0
- synth_ai/task/apps/__init__.py +1 -0
- synth_ai/task/config.py +2 -0
- synth_ai/task/tracing_utils.py +25 -25
- synth_ai/task/validators.py +45 -9
- synth_ai/task_app_cfgs.py +21 -0
- synth_ai/tracing_v3/config.py +162 -19
- synth_ai/tracing_v3/constants.py +1 -1
- synth_ai/tracing_v3/db_config.py +24 -38
- synth_ai/tracing_v3/migration_helper.py +1 -2
- synth_ai/tracing_v3/storage/config.py +47 -13
- synth_ai/tracing_v3/storage/factory.py +3 -3
- synth_ai/tracing_v3/turso/daemon.py +113 -11
- synth_ai/tracing_v3/turso/native_manager.py +92 -16
- synth_ai/types.py +8 -0
- synth_ai/urls.py +11 -0
- synth_ai/utils/__init__.py +30 -1
- synth_ai/utils/agents.py +74 -0
- synth_ai/utils/bin.py +39 -0
- synth_ai/utils/cli.py +149 -5
- synth_ai/utils/env.py +40 -33
- synth_ai/utils/http.py +4 -1
- synth_ai/utils/json.py +72 -0
- synth_ai/utils/modal.py +285 -3
- synth_ai/utils/paths.py +48 -0
- synth_ai/utils/uvicorn.py +113 -0
- {synth_ai-0.2.16.dist-info → synth_ai-0.2.19.dist-info}/METADATA +109 -6
- {synth_ai-0.2.16.dist-info → synth_ai-0.2.19.dist-info}/RECORD +291 -142
- examples/qwen_vl/configs/eval_qwen2vl_vision.toml +0 -44
- synth_ai/cli/tui.py +0 -62
- synth_ai/tui/__init__.py +0 -5
- synth_ai/tui/__main__.py +0 -13
- synth_ai/tui/cli/__init__.py +0 -1
- synth_ai/tui/cli/query_experiments.py +0 -164
- synth_ai/tui/cli/query_experiments_v3.py +0 -164
- synth_ai/tui/dashboard.py +0 -911
- {synth_ai-0.2.16.dist-info → synth_ai-0.2.19.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.16.dist-info → synth_ai-0.2.19.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.16.dist-info → synth_ai-0.2.19.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.2.16.dist-info → synth_ai-0.2.19.dist-info}/top_level.txt +0 -0
synth_ai/cli/setup.py
CHANGED
|
@@ -1,266 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class HandshakeError(Exception):
|
|
22
|
-
pass
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def _get_canonical_origin() -> str:
|
|
26
|
-
"""Resolve the dashboard origin for the browser handshake.
|
|
27
|
-
|
|
28
|
-
Priority order:
|
|
29
|
-
1. Explicit ``SYNTH_CANONICAL_ORIGIN`` override.
|
|
30
|
-
2. Development flag ``SYNTH_CANONICAL_DEV`` (case-insensitive truthy) → localhost.
|
|
31
|
-
3. Production dashboard at ``https://www.usesynth.ai/dashboard``.
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
override = (os.getenv("SYNTH_CANONICAL_ORIGIN") or "").strip()
|
|
35
|
-
if override:
|
|
36
|
-
return override.rstrip("/")
|
|
37
|
-
|
|
38
|
-
dev_flag = (os.getenv("SYNTH_CANONICAL_DEV") or "").strip().lower()
|
|
39
|
-
if dev_flag in { "1", "true", "yes", "on" }:
|
|
40
|
-
print("USING DEV ORIGIN")
|
|
41
|
-
return "http://localhost:3000"
|
|
42
|
-
|
|
43
|
-
return "https://www.usesynth.ai/dashboard"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def _split_origin(origin: str) -> tuple[str, str]:
|
|
47
|
-
parsed = urlsplit(origin)
|
|
48
|
-
bare = cast(str, urlunsplit((parsed.scheme, parsed.netloc, "", "", "")))
|
|
49
|
-
path = parsed.path.rstrip("/")
|
|
50
|
-
return bare, path
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _ensure_verification_uri(data: dict[str, Any], base_with_path: str) -> None:
|
|
54
|
-
uri = data.get("verification_uri")
|
|
55
|
-
if not isinstance(uri, str) or not uri:
|
|
56
|
-
return
|
|
57
|
-
if uri.startswith("http://") or uri.startswith("https://"):
|
|
58
|
-
return
|
|
59
|
-
data["verification_uri"] = urljoin(base_with_path.rstrip("/") + "/", uri.lstrip("/"))
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def _start_handshake_session(origin: str | None = None) -> tuple[str, str, int, int]:
|
|
63
|
-
base = (origin or _get_canonical_origin()).rstrip("/")
|
|
64
|
-
api_origin, _ = _split_origin(base)
|
|
65
|
-
url = urljoin(api_origin.rstrip("/") + "/", "api/sdk/handshake/init")
|
|
66
|
-
r = requests.post(url, timeout=10)
|
|
67
|
-
if r.status_code != 200:
|
|
68
|
-
raise HandshakeError(f"init failed: {r.status_code} {r.text}")
|
|
69
|
-
try:
|
|
70
|
-
data = r.json()
|
|
71
|
-
except ValueError as exc: # pragma: no cover - network dependent
|
|
72
|
-
raise HandshakeError(f"init returned malformed JSON: {exc}") from exc
|
|
73
|
-
_ensure_verification_uri(data, base)
|
|
74
|
-
return (
|
|
75
|
-
str(data.get("device_code")),
|
|
76
|
-
str(data.get("verification_uri")),
|
|
77
|
-
int(data.get("expires_in", 600)),
|
|
78
|
-
int(data.get("interval", 3)),
|
|
1
|
+
"""Instructions on Docs → https://usesynth.ai/cli-cmds/setup"""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from synth_ai.auth.credentials import fetch_credentials_from_web_browser_session
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.command("setup")
|
|
8
|
+
@click.option(
|
|
9
|
+
"--local",
|
|
10
|
+
is_flag=True,
|
|
11
|
+
help="Load your credentials from your local machine"
|
|
12
|
+
)
|
|
13
|
+
@click.option(
|
|
14
|
+
"--dev",
|
|
15
|
+
is_flag=True
|
|
16
|
+
)
|
|
17
|
+
def setup_cmd(local: bool, dev: bool) -> None:
|
|
18
|
+
fetch_credentials_from_web_browser_session(
|
|
19
|
+
browser=not local,
|
|
20
|
+
prod=not dev
|
|
79
21
|
)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def _poll_handshake_token(
|
|
83
|
-
device_code: str, origin: str | None = None, *, timeout_s: int | None = None
|
|
84
|
-
) -> dict[str, Any]:
|
|
85
|
-
base = (origin or _get_canonical_origin()).rstrip("/")
|
|
86
|
-
api_origin, _ = _split_origin(base)
|
|
87
|
-
url = urljoin(api_origin.rstrip("/") + "/", "api/sdk/handshake/token")
|
|
88
|
-
deadline = time.time() + (timeout_s or 600)
|
|
89
|
-
while True:
|
|
90
|
-
if time.time() > deadline:
|
|
91
|
-
raise HandshakeError("handshake timed out")
|
|
92
|
-
try:
|
|
93
|
-
r = requests.post(url, json={"device_code": device_code}, timeout=10)
|
|
94
|
-
except Exception:
|
|
95
|
-
time.sleep(2)
|
|
96
|
-
continue
|
|
97
|
-
if r.status_code == 200:
|
|
98
|
-
try:
|
|
99
|
-
data = r.json()
|
|
100
|
-
except ValueError as exc: # pragma: no cover - network dependent
|
|
101
|
-
raise HandshakeError(f"token returned malformed JSON: {exc}") from exc
|
|
102
|
-
_ensure_verification_uri(data, base)
|
|
103
|
-
return data
|
|
104
|
-
elif r.status_code in (404, 410):
|
|
105
|
-
raise HandshakeError(f"handshake failed: {r.status_code}")
|
|
106
|
-
# 428 authorization_pending or others → wait and retry
|
|
107
|
-
time.sleep(2)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def _run_handshake(origin: str | None = None) -> dict[str, Any]:
|
|
111
|
-
device_code, verification_uri, expires_in, interval = _start_handshake_session(origin)
|
|
112
|
-
with contextlib.suppress(Exception):
|
|
113
|
-
webbrowser.open(verification_uri)
|
|
114
|
-
return _poll_handshake_token(device_code, origin, timeout_s=expires_in)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def setup() -> int:
|
|
119
|
-
# Prefer the demo directory provided in the current shell session, then fall back to persisted state
|
|
120
|
-
demo_dir_env = (os.environ.get("DEMO_DIR") or "").strip()
|
|
121
|
-
demo_dir: str | None = None
|
|
122
|
-
if demo_dir_env:
|
|
123
|
-
candidate = Path(demo_dir_env).expanduser()
|
|
124
|
-
if candidate.is_dir():
|
|
125
|
-
demo_dir = str(candidate.resolve())
|
|
126
|
-
else:
|
|
127
|
-
print(f"Warning: DEMO_DIR={demo_dir_env} does not exist; falling back to stored demo directory.")
|
|
128
|
-
|
|
129
|
-
if demo_dir is None:
|
|
130
|
-
loaded = demo_core.load_demo_dir()
|
|
131
|
-
if loaded:
|
|
132
|
-
demo_dir = loaded
|
|
133
|
-
|
|
134
|
-
if demo_dir and os.path.isdir(demo_dir):
|
|
135
|
-
os.chdir(demo_dir)
|
|
136
|
-
print(f"Using demo directory: {demo_dir}")
|
|
137
|
-
|
|
138
|
-
synth_key = ""
|
|
139
|
-
rl_env_key = ""
|
|
140
|
-
org_name = ""
|
|
141
|
-
|
|
142
|
-
try:
|
|
143
|
-
print("\n⏳ Connecting to your browser session…")
|
|
144
|
-
res = _run_handshake()
|
|
145
|
-
org = res.get("org") or {}
|
|
146
|
-
keys = res.get("keys") or {}
|
|
147
|
-
synth_key = str(keys.get("synth") or "").strip()
|
|
148
|
-
rl_env_key = str(keys.get("rl_env") or "").strip()
|
|
149
|
-
org_name = org.get("name") or "Unamed Organization ™️"
|
|
150
|
-
print(f"✅ Connected to {org_name}!")
|
|
151
|
-
except (HandshakeError, Exception) as exc:
|
|
152
|
-
print(f"⚠️ Failed to fetch keys from frontend: {exc}")
|
|
153
|
-
print("Falling back to manual entry...")
|
|
154
|
-
|
|
155
|
-
if not synth_key:
|
|
156
|
-
try:
|
|
157
|
-
synth_key = input(
|
|
158
|
-
"Failed to fetch your Synth API key. Please enter your Synth API key here:\n> "
|
|
159
|
-
).strip()
|
|
160
|
-
except (EOFError, KeyboardInterrupt):
|
|
161
|
-
print("\nSetup cancelled.")
|
|
162
|
-
return 1
|
|
163
|
-
if not synth_key:
|
|
164
|
-
print("Synth API key is required.")
|
|
165
|
-
return 1
|
|
166
|
-
|
|
167
|
-
if not rl_env_key:
|
|
168
|
-
try:
|
|
169
|
-
rl_env_key = input(
|
|
170
|
-
"Failed to fetch your Environment API key. Please enter your Environment API key here:\n> "
|
|
171
|
-
).strip()
|
|
172
|
-
except (EOFError, KeyboardInterrupt):
|
|
173
|
-
print("\nSetup cancelled.")
|
|
174
|
-
return 1
|
|
175
|
-
if not rl_env_key:
|
|
176
|
-
print("Environment API key is required.")
|
|
177
|
-
return 1
|
|
178
|
-
|
|
179
|
-
# Persist keys to user config
|
|
180
|
-
config_updates = {
|
|
181
|
-
"SYNTH_API_KEY": synth_key,
|
|
182
|
-
"ENVIRONMENT_API_KEY": rl_env_key,
|
|
183
|
-
}
|
|
184
|
-
update_user_config(config_updates)
|
|
185
|
-
|
|
186
|
-
os.environ["SYNTH_API_KEY"] = synth_key
|
|
187
|
-
os.environ["ENVIRONMENT_API_KEY"] = rl_env_key
|
|
188
|
-
|
|
189
|
-
env = demo_core.load_env()
|
|
190
|
-
|
|
191
|
-
def _refresh_env() -> None:
|
|
192
|
-
nonlocal env
|
|
193
|
-
env = demo_core.load_env()
|
|
194
|
-
|
|
195
|
-
def _maybe_fix_task_url() -> None:
|
|
196
|
-
if not env.task_app_name:
|
|
197
|
-
return
|
|
198
|
-
current = env.task_app_base_url
|
|
199
|
-
needs_lookup = not current or not is_modal_public_url(current)
|
|
200
|
-
if not needs_lookup:
|
|
201
|
-
return
|
|
202
|
-
code, out = popen_capture(
|
|
203
|
-
[
|
|
204
|
-
"uv",
|
|
205
|
-
"run",
|
|
206
|
-
"python",
|
|
207
|
-
"-m",
|
|
208
|
-
"modal",
|
|
209
|
-
"app",
|
|
210
|
-
"url",
|
|
211
|
-
env.task_app_name,
|
|
212
|
-
]
|
|
213
|
-
)
|
|
214
|
-
if code != 0 or not out:
|
|
215
|
-
return
|
|
216
|
-
new_url = ""
|
|
217
|
-
for token in out.split():
|
|
218
|
-
if is_modal_public_url(token):
|
|
219
|
-
new_url = token.strip().rstrip("/")
|
|
220
|
-
break
|
|
221
|
-
if new_url and new_url != current:
|
|
222
|
-
print(f"Updating TASK_APP_BASE_URL from Modal CLI → {new_url}")
|
|
223
|
-
persist_path = demo_dir or os.getcwd()
|
|
224
|
-
demo_core.persist_task_url(new_url, name=env.task_app_name, path=persist_path)
|
|
225
|
-
os.environ["TASK_APP_BASE_URL"] = new_url
|
|
226
|
-
_refresh_env()
|
|
227
|
-
|
|
228
|
-
modal_ok, modal_msg = demo_core.modal_auth_status()
|
|
229
|
-
if modal_ok:
|
|
230
|
-
print(f"✓ Modal authenticated: {modal_msg}")
|
|
231
|
-
else:
|
|
232
|
-
print(f"[setup] Modal authentication status: {modal_msg}")
|
|
233
|
-
|
|
234
|
-
_maybe_fix_task_url()
|
|
235
|
-
|
|
236
|
-
if env.dev_backend_url:
|
|
237
|
-
api = env.dev_backend_url.rstrip("/") + (
|
|
238
|
-
"" if env.dev_backend_url.endswith("/api") else "/api"
|
|
239
|
-
)
|
|
240
|
-
demo_core.assert_http_ok(api + "/health", method="GET")
|
|
241
|
-
if env.task_app_base_url:
|
|
242
|
-
base = env.task_app_base_url.rstrip("/")
|
|
243
|
-
demo_core.assert_http_ok(
|
|
244
|
-
base + "/health", method="GET"
|
|
245
|
-
) or demo_core.assert_http_ok(
|
|
246
|
-
base, method="GET"
|
|
247
|
-
)
|
|
248
|
-
print("\nSaved keys:")
|
|
249
|
-
print(f" SYNTH_API_KEY={mask_str(synth_key)}")
|
|
250
|
-
print(f" ENVIRONMENT_API_KEY={mask_str(rl_env_key)}")
|
|
251
|
-
if env.task_app_base_url:
|
|
252
|
-
print(f" TASK_APP_BASE_URL={env.task_app_base_url}")
|
|
253
|
-
print(f"Configuration persisted to: {USER_CONFIG_PATH}")
|
|
254
|
-
|
|
255
|
-
demo_core.persist_demo_dir(os.getcwd())
|
|
256
|
-
|
|
257
|
-
print_next_step("deploy our task app", ["uvx synth-ai deploy"])
|
|
258
|
-
return 0
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def register(group):
|
|
262
|
-
@group.command("setup")
|
|
263
|
-
def demo_setup():
|
|
264
|
-
code = setup()
|
|
265
|
-
if code:
|
|
266
|
-
raise Exit(code)
|
synth_ai/cli/status.py
CHANGED
|
@@ -1,134 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
CLI: status of agent runs/versions and environment service.
|
|
4
|
-
"""
|
|
2
|
+
"""Compatibility wrapper for legacy status imports."""
|
|
5
3
|
|
|
6
|
-
import
|
|
4
|
+
from __future__ import annotations
|
|
7
5
|
|
|
8
6
|
import click
|
|
9
|
-
import
|
|
10
|
-
from rich import box
|
|
11
|
-
from rich.console import Console
|
|
12
|
-
from rich.panel import Panel
|
|
13
|
-
from rich.table import Table
|
|
7
|
+
from synth_ai.cli.commands.status import register as _register_status
|
|
14
8
|
|
|
15
|
-
from ._storage import load_storage
|
|
16
9
|
|
|
10
|
+
def register(cli: click.Group) -> None:
|
|
11
|
+
"""Register status subcommands on the provided CLI group."""
|
|
12
|
+
_register_status(cli)
|
|
17
13
|
|
|
18
|
-
async def _db_stats(db_url: str) -> dict:
|
|
19
|
-
create_storage, storage_config = load_storage()
|
|
20
|
-
db = create_storage(storage_config(connection_string=db_url))
|
|
21
|
-
await db.initialize()
|
|
22
|
-
try:
|
|
23
|
-
out: dict = {}
|
|
24
|
-
# Totals
|
|
25
|
-
totals = await db.query_traces(
|
|
26
|
-
"""
|
|
27
|
-
SELECT
|
|
28
|
-
(SELECT COUNT(*) FROM session_traces) AS sessions,
|
|
29
|
-
(SELECT COUNT(*) FROM experiments) AS experiments,
|
|
30
|
-
(SELECT COUNT(*) FROM events) AS events,
|
|
31
|
-
(SELECT COUNT(*) FROM messages) AS messages,
|
|
32
|
-
(SELECT COALESCE(SUM(CASE WHEN event_type='cais' THEN cost_usd ELSE 0 END),0)/100.0 FROM events) AS total_cost_usd,
|
|
33
|
-
(SELECT COALESCE(SUM(CASE WHEN event_type='cais' THEN total_tokens ELSE 0 END),0) FROM events) AS total_tokens
|
|
34
|
-
"""
|
|
35
|
-
)
|
|
36
|
-
if not totals.empty:
|
|
37
|
-
out["totals"] = totals.iloc[0].to_dict()
|
|
38
|
-
else:
|
|
39
|
-
out["totals"] = {}
|
|
40
14
|
|
|
41
|
-
|
|
42
|
-
systems = await db.query_traces(
|
|
43
|
-
"""
|
|
44
|
-
SELECT system_type, COUNT(*) as count FROM systems GROUP BY system_type
|
|
45
|
-
"""
|
|
46
|
-
)
|
|
47
|
-
out["systems"] = systems
|
|
48
|
-
|
|
49
|
-
versions = await db.query_traces(
|
|
50
|
-
"""
|
|
51
|
-
SELECT COUNT(*) as version_count FROM system_versions
|
|
52
|
-
"""
|
|
53
|
-
)
|
|
54
|
-
if not versions.empty:
|
|
55
|
-
out["version_count"] = int(versions.iloc[0]["version_count"])
|
|
56
|
-
else:
|
|
57
|
-
out["version_count"] = 0
|
|
58
|
-
return out
|
|
59
|
-
finally:
|
|
60
|
-
await db.close()
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def register(cli):
|
|
64
|
-
@cli.command()
|
|
65
|
-
@click.option(
|
|
66
|
-
"--url",
|
|
67
|
-
"db_url",
|
|
68
|
-
default="sqlite+aiosqlite:///./synth_ai.db/dbs/default/data",
|
|
69
|
-
help="Database URL",
|
|
70
|
-
)
|
|
71
|
-
@click.option("--service-url", default="http://127.0.0.1:8901", help="Environment service URL")
|
|
72
|
-
def status(db_url: str, service_url: str):
|
|
73
|
-
"""Show DB stats, agent/environment system counts, and env service health."""
|
|
74
|
-
console = Console()
|
|
75
|
-
|
|
76
|
-
async def _run():
|
|
77
|
-
# DB
|
|
78
|
-
stats = await _db_stats(db_url)
|
|
79
|
-
|
|
80
|
-
# Env service
|
|
81
|
-
health_text = "[red]unreachable[/red]"
|
|
82
|
-
envs_list = []
|
|
83
|
-
try:
|
|
84
|
-
r = requests.get(f"{service_url}/health", timeout=2)
|
|
85
|
-
if r.ok:
|
|
86
|
-
data = r.json()
|
|
87
|
-
health_text = "[green]ok[/green]"
|
|
88
|
-
envs_list = data.get("supported_environments", [])
|
|
89
|
-
else:
|
|
90
|
-
health_text = f"[red]{r.status_code}[/red]"
|
|
91
|
-
except Exception:
|
|
92
|
-
pass
|
|
93
|
-
|
|
94
|
-
# Render
|
|
95
|
-
totals = stats.get("totals", {})
|
|
96
|
-
lines = []
|
|
97
|
-
lines.append(f"DB: [dim]{db_url}[/dim]")
|
|
98
|
-
lines.append(
|
|
99
|
-
f"Experiments: {int(totals.get('experiments', 0)):,} "
|
|
100
|
-
f"Sessions: {int(totals.get('sessions', 0)):,} "
|
|
101
|
-
f"Events: {int(totals.get('events', 0)):,} "
|
|
102
|
-
f"Messages: {int(totals.get('messages', 0)):,}"
|
|
103
|
-
)
|
|
104
|
-
lines.append(
|
|
105
|
-
f"Cost: ${float(totals.get('total_cost_usd', 0.0) or 0.0):.4f} "
|
|
106
|
-
f"Tokens: {int(totals.get('total_tokens', 0)):,}"
|
|
107
|
-
)
|
|
108
|
-
lines.append("")
|
|
109
|
-
lines.append(f"Env Service: {health_text} [dim]{service_url}[/dim]")
|
|
110
|
-
if envs_list:
|
|
111
|
-
lines.append(
|
|
112
|
-
"Environments: "
|
|
113
|
-
+ ", ".join(sorted(envs_list)[:10])
|
|
114
|
-
+ (" ..." if len(envs_list) > 10 else "")
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
panel_main = Panel("\n".join(lines), title="Synth AI Status", border_style="cyan")
|
|
118
|
-
console.print(panel_main)
|
|
119
|
-
|
|
120
|
-
# Systems table
|
|
121
|
-
sys_df = stats.get("systems")
|
|
122
|
-
if sys_df is not None and not sys_df.empty:
|
|
123
|
-
tbl = Table(
|
|
124
|
-
title=f"Systems (versions: {stats.get('version_count', 0)})",
|
|
125
|
-
box=box.SIMPLE,
|
|
126
|
-
header_style="bold",
|
|
127
|
-
)
|
|
128
|
-
tbl.add_column("Type")
|
|
129
|
-
tbl.add_column("Count", justify="right")
|
|
130
|
-
for _, r in sys_df.iterrows():
|
|
131
|
-
tbl.add_row(str(r.get("system_type", "-")), f"{int(r.get('count', 0)):,}")
|
|
132
|
-
console.print(tbl)
|
|
133
|
-
|
|
134
|
-
asyncio.run(_run())
|
|
15
|
+
__all__ = ["register"]
|
synth_ai/cli/task_app_deploy.py
CHANGED
|
@@ -2,15 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
from .task_apps import task_app_group
|
|
8
|
-
|
|
9
|
-
_deploy = task_app_group.commands.get("deploy")
|
|
10
|
-
|
|
11
|
-
if _deploy is None:
|
|
12
|
-
raise RuntimeError("task_app_group does not define a 'deploy' command")
|
|
13
|
-
|
|
14
|
-
deploy_command: click.Command = _deploy
|
|
5
|
+
from synth_ai.cli.deploy import deploy_cmd as deploy_command # type: ignore[attr-defined]
|
|
15
6
|
|
|
16
7
|
__all__ = ["deploy_command"]
|
|
@@ -2,15 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
from synth_ai.cli.task_apps import task_app_group
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if _modal_serve is None:
|
|
12
|
-
raise RuntimeError("task_app_group does not define a 'modal-serve' command")
|
|
13
|
-
|
|
14
|
-
modal_serve_command: click.Command = _modal_serve
|
|
7
|
+
modal_serve_command = task_app_group.commands.get("modal-serve")
|
|
8
|
+
if modal_serve_command is None: # pragma: no cover - defensive guard
|
|
9
|
+
raise RuntimeError("Modal-serve command is not registered on task_app_group")
|
|
15
10
|
|
|
16
11
|
__all__ = ["modal_serve_command"]
|
synth_ai/cli/task_app_serve.py
CHANGED
|
@@ -2,17 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
from synth_ai.cli.task_apps import serve_command, task_app_group
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
serve_command = task_app_serve_command
|
|
11
|
-
|
|
12
|
-
_group_serve = task_app_group.commands.get("serve")
|
|
13
|
-
if _group_serve is None:
|
|
14
|
-
raise RuntimeError("task_app_group does not define a 'serve' command")
|
|
15
|
-
|
|
16
|
-
serve_task_group: click.Command = _group_serve
|
|
7
|
+
serve_task_group = task_app_group.commands.get("serve")
|
|
8
|
+
if serve_task_group is None: # pragma: no cover - defensive guard
|
|
9
|
+
raise RuntimeError("Serve command is not registered on task_app_group")
|
|
17
10
|
|
|
18
11
|
__all__ = ["serve_command", "serve_task_group"]
|