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.

Files changed (299) hide show
  1. examples/analyze_semantic_words.sh +2 -2
  2. examples/baseline/banking77_baseline.py +204 -0
  3. examples/baseline/crafter_baseline.py +407 -0
  4. examples/baseline/pokemon_red_baseline.py +326 -0
  5. examples/baseline/simple_baseline.py +56 -0
  6. examples/baseline/warming_up_to_rl_baseline.py +239 -0
  7. examples/blog_posts/gepa/README.md +355 -0
  8. examples/blog_posts/gepa/configs/banking77_gepa_local.toml +95 -0
  9. examples/blog_posts/gepa/configs/banking77_gepa_test.toml +82 -0
  10. examples/blog_posts/gepa/configs/banking77_mipro_local.toml +52 -0
  11. examples/blog_posts/gepa/configs/hotpotqa_gepa_local.toml +59 -0
  12. examples/blog_posts/gepa/configs/hotpotqa_gepa_qwen.toml +36 -0
  13. examples/blog_posts/gepa/configs/hotpotqa_mipro_local.toml +53 -0
  14. examples/blog_posts/gepa/configs/hover_gepa_local.toml +59 -0
  15. examples/blog_posts/gepa/configs/hover_gepa_qwen.toml +36 -0
  16. examples/blog_posts/gepa/configs/hover_mipro_local.toml +53 -0
  17. examples/blog_posts/gepa/configs/ifbench_gepa_local.toml +59 -0
  18. examples/blog_posts/gepa/configs/ifbench_gepa_qwen.toml +36 -0
  19. examples/blog_posts/gepa/configs/ifbench_mipro_local.toml +53 -0
  20. examples/blog_posts/gepa/configs/pupa_gepa_local.toml +60 -0
  21. examples/blog_posts/gepa/configs/pupa_mipro_local.toml +54 -0
  22. examples/blog_posts/gepa/deploy_banking77_task_app.sh +41 -0
  23. examples/blog_posts/gepa/gepa_baseline.py +204 -0
  24. examples/blog_posts/gepa/query_prompts_example.py +97 -0
  25. examples/blog_posts/gepa/run_gepa_banking77.sh +87 -0
  26. examples/blog_posts/gepa/task_apps.py +105 -0
  27. examples/blog_posts/gepa/test_gepa_local.sh +67 -0
  28. examples/blog_posts/gepa/verify_banking77_setup.sh +123 -0
  29. examples/blog_posts/pokemon_vl/README.md +98 -0
  30. examples/blog_posts/pokemon_vl/configs/eval_gpt5nano.toml +26 -0
  31. examples/blog_posts/pokemon_vl/configs/eval_qwen3_vl.toml +27 -0
  32. examples/blog_posts/pokemon_vl/configs/eval_rl_final.toml +24 -0
  33. examples/blog_posts/pokemon_vl/configs/filter_high_reward.toml +10 -0
  34. examples/blog_posts/pokemon_vl/configs/train_rl_from_sft.toml +43 -0
  35. examples/blog_posts/pokemon_vl/configs/train_sft_qwen4b_vl.toml +40 -0
  36. examples/blog_posts/pokemon_vl/extract_images.py +239 -0
  37. examples/blog_posts/pokemon_vl/pokemon_vl_baseline.py +326 -0
  38. examples/blog_posts/pokemon_vl/run_eval_extract_images.py +209 -0
  39. examples/blog_posts/pokemon_vl/run_qwen_eval_extract_images.py +212 -0
  40. examples/blog_posts/pokemon_vl/text_box_analysis.md +106 -0
  41. examples/blog_posts/warming_up_to_rl/ARCHITECTURE.md +195 -0
  42. examples/blog_posts/warming_up_to_rl/FINAL_TEST_RESULTS.md +127 -0
  43. examples/blog_posts/warming_up_to_rl/INFERENCE_SUCCESS.md +132 -0
  44. examples/blog_posts/warming_up_to_rl/README.md +158 -0
  45. examples/blog_posts/warming_up_to_rl/SMOKE_TESTING.md +164 -0
  46. examples/blog_posts/warming_up_to_rl/SMOKE_TEST_COMPLETE.md +253 -0
  47. examples/blog_posts/warming_up_to_rl/configs/eval_baseline_qwen32b_10x20.toml +25 -0
  48. examples/blog_posts/warming_up_to_rl/configs/eval_ft_qwen4b.toml +25 -0
  49. examples/blog_posts/warming_up_to_rl/configs/eval_ft_qwen4b_10x20.toml +26 -0
  50. examples/blog_posts/warming_up_to_rl/configs/eval_groq_qwen32b.toml +25 -0
  51. examples/blog_posts/warming_up_to_rl/configs/eval_openai_gpt_oss_120b.toml +29 -0
  52. examples/blog_posts/warming_up_to_rl/configs/filter_high_reward_dataset.toml +10 -0
  53. examples/blog_posts/warming_up_to_rl/configs/smoke_test.toml +75 -0
  54. examples/blog_posts/warming_up_to_rl/configs/train_rl_from_sft.toml +91 -0
  55. examples/blog_posts/warming_up_to_rl/configs/train_sft_qwen4b.toml +40 -0
  56. examples/blog_posts/warming_up_to_rl/warming_up_to_rl_baseline.py +187 -0
  57. examples/dev/qwen3_32b_qlora_4xh100.toml +5 -0
  58. examples/multi_step/configs/VERILOG_REWARDS.md +4 -0
  59. examples/multi_step/configs/VERILOG_RL_CHECKLIST.md +4 -0
  60. examples/multi_step/configs/crafter_rl_outcome.toml +2 -1
  61. examples/multi_step/configs/crafter_rl_stepwise_hosted_judge.toml +65 -107
  62. examples/multi_step/configs/crafter_rl_stepwise_shaped.toml +2 -1
  63. examples/multi_step/configs/crafter_rl_stepwise_simple.toml +2 -1
  64. examples/multi_step/configs/crafter_rl_stepwise_simple_NEW_FORMAT.toml +105 -0
  65. examples/multi_step/configs/verilog_rl_lora.toml +80 -123
  66. examples/qwen_coder/configs/coder_lora_30b.toml +1 -3
  67. examples/qwen_coder/configs/coder_lora_4b.toml +4 -1
  68. examples/qwen_coder/configs/coder_lora_small.toml +1 -3
  69. examples/qwen_vl/README.md +10 -12
  70. examples/qwen_vl/SETUP_COMPLETE.md +7 -8
  71. examples/qwen_vl/VISION_TESTS_COMPLETE.md +2 -3
  72. examples/qwen_vl/collect_data_via_cli.md +76 -84
  73. examples/qwen_vl/collect_vision_traces.py +4 -4
  74. examples/qwen_vl/configs/crafter_rl_vision_qwen3vl4b.toml +40 -57
  75. examples/qwen_vl/configs/crafter_vlm_sft_example.toml +1 -2
  76. examples/qwen_vl/configs/eval_gpt4o_mini_vision.toml +20 -37
  77. examples/qwen_vl/configs/eval_gpt5nano_vision.toml +21 -40
  78. examples/qwen_vl/configs/eval_qwen3vl_vision.toml +26 -0
  79. examples/qwen_vl/configs/{filter_qwen2vl_sft.toml → filter_qwen3vl_sft.toml} +4 -5
  80. examples/qwen_vl/configs/filter_vision_sft.toml +2 -3
  81. examples/qwen_vl/crafter_qwen_vl_agent.py +5 -5
  82. examples/qwen_vl/run_vision_comparison.sh +6 -7
  83. examples/rl/README.md +5 -5
  84. examples/rl/configs/rl_from_base_qwen.toml +26 -1
  85. examples/rl/configs/rl_from_base_qwen17.toml +6 -2
  86. examples/rl/task_app/README.md +1 -2
  87. examples/rl/task_app/math_single_step.py +2 -2
  88. examples/run_crafter_demo.sh +2 -2
  89. examples/sft/README.md +1 -1
  90. examples/sft/configs/crafter_fft_qwen0p6b.toml +4 -1
  91. examples/sft/configs/crafter_lora_qwen0p6b.toml +4 -1
  92. examples/swe/task_app/README.md +32 -2
  93. examples/swe/task_app/grpo_swe_mini.py +4 -0
  94. examples/swe/task_app/hosted/envs/crafter/react_agent.py +1 -1
  95. examples/swe/task_app/hosted/envs/mini_swe/environment.py +37 -10
  96. examples/swe/task_app/hosted/inference/openai_client.py +4 -38
  97. examples/swe/task_app/hosted/policy_routes.py +17 -0
  98. examples/swe/task_app/hosted/rollout.py +4 -2
  99. examples/swe/task_app/morph_backend.py +178 -0
  100. examples/task_apps/banking77/__init__.py +6 -0
  101. examples/task_apps/banking77/banking77_task_app.py +841 -0
  102. examples/task_apps/banking77/deploy_wrapper.py +46 -0
  103. examples/task_apps/crafter/CREATE_SFT_DATASET.md +4 -0
  104. examples/task_apps/crafter/FILTER_COMMAND_STATUS.md +4 -0
  105. examples/task_apps/crafter/FILTER_COMMAND_SUCCESS.md +4 -0
  106. examples/task_apps/crafter/task_app/README.md +1 -1
  107. examples/task_apps/crafter/task_app/grpo_crafter.py +90 -5
  108. examples/task_apps/crafter/task_app/grpo_crafter_task_app.py +1 -1
  109. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/policy.py +4 -26
  110. examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/react_agent.py +1 -2
  111. examples/task_apps/crafter/task_app/synth_envs_hosted/hosted_app.py +49 -0
  112. examples/task_apps/crafter/task_app/synth_envs_hosted/inference/openai_client.py +372 -107
  113. examples/task_apps/crafter/task_app/synth_envs_hosted/policy_routes.py +81 -12
  114. examples/task_apps/crafter/task_app/synth_envs_hosted/rollout.py +82 -11
  115. examples/task_apps/crafter/task_app/synth_envs_hosted/utils.py +194 -1
  116. examples/task_apps/enron/task_app/grpo_enron_task_app.py +1 -1
  117. examples/task_apps/gepa_benchmarks/__init__.py +7 -0
  118. examples/task_apps/gepa_benchmarks/common.py +260 -0
  119. examples/task_apps/gepa_benchmarks/hotpotqa_task_app.py +507 -0
  120. examples/task_apps/gepa_benchmarks/hover_task_app.py +436 -0
  121. examples/task_apps/gepa_benchmarks/ifbench_task_app.py +563 -0
  122. examples/task_apps/gepa_benchmarks/pupa_task_app.py +460 -0
  123. examples/task_apps/math/README.md +1 -2
  124. examples/task_apps/pokemon_red/README.md +3 -4
  125. examples/task_apps/pokemon_red/README_IMAGE_ONLY_EVAL.md +4 -0
  126. examples/task_apps/pokemon_red/eval_image_only_gpt4o.toml +6 -5
  127. examples/task_apps/pokemon_red/eval_pokemon_red_policy.py +1 -2
  128. examples/task_apps/pokemon_red/task_app.py +288 -39
  129. examples/task_apps/sokoban/README.md +2 -3
  130. examples/task_apps/verilog/eval_groq_qwen32b.toml +12 -14
  131. examples/task_apps/verilog/task_app/grpo_verilog_task_app.py +1 -1
  132. examples/vlm/configs/crafter_vlm_gpt4o.toml +4 -1
  133. examples/warming_up_to_rl/configs/crafter_fft.toml +4 -1
  134. examples/warming_up_to_rl/configs/crafter_fft_4b.toml +0 -2
  135. examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +3 -2
  136. examples/warming_up_to_rl/run_local_rollout_traced.py +1 -1
  137. examples/warming_up_to_rl/task_app/README.md +1 -1
  138. examples/warming_up_to_rl/task_app/grpo_crafter.py +185 -5
  139. examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +1 -1
  140. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +3 -27
  141. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +1 -1
  142. examples/warming_up_to_rl/task_app/synth_envs_hosted/hosted_app.py +49 -0
  143. examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +156 -45
  144. examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +37 -4
  145. examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +33 -3
  146. examples/warming_up_to_rl/task_app/synth_envs_hosted/utils.py +67 -0
  147. examples/workflows/math_rl/configs/rl_from_base_qwen.toml +27 -0
  148. examples/workflows/math_rl/configs/rl_from_base_qwen17.toml +6 -0
  149. synth_ai/api/train/builders.py +99 -4
  150. synth_ai/api/train/cli.py +516 -26
  151. synth_ai/api/train/config_finder.py +13 -2
  152. synth_ai/api/train/configs/__init__.py +23 -2
  153. synth_ai/api/train/configs/prompt_learning.py +442 -0
  154. synth_ai/api/train/configs/rl.py +61 -7
  155. synth_ai/api/train/configs/sft.py +6 -2
  156. synth_ai/api/train/configs/shared.py +59 -2
  157. synth_ai/api/train/task_app.py +1 -1
  158. synth_ai/api/train/validators.py +277 -0
  159. synth_ai/auth/credentials.py +119 -0
  160. synth_ai/baseline/__init__.py +25 -0
  161. synth_ai/baseline/config.py +209 -0
  162. synth_ai/baseline/discovery.py +214 -0
  163. synth_ai/baseline/execution.py +146 -0
  164. synth_ai/cli/__init__.py +94 -18
  165. synth_ai/cli/__main__.py +0 -0
  166. synth_ai/cli/claude.py +70 -0
  167. synth_ai/cli/codex.py +84 -0
  168. synth_ai/cli/commands/__init__.py +18 -0
  169. synth_ai/cli/commands/baseline/__init__.py +12 -0
  170. synth_ai/cli/commands/baseline/core.py +637 -0
  171. synth_ai/cli/commands/baseline/list.py +93 -0
  172. synth_ai/cli/commands/demo/__init__.py +6 -0
  173. synth_ai/cli/commands/demo/core.py +163 -0
  174. synth_ai/cli/commands/eval/__init__.py +19 -0
  175. synth_ai/cli/commands/eval/core.py +1112 -0
  176. synth_ai/cli/commands/eval/errors.py +81 -0
  177. synth_ai/cli/commands/eval/validation.py +133 -0
  178. synth_ai/cli/commands/filter/__init__.py +12 -0
  179. synth_ai/cli/commands/filter/core.py +424 -0
  180. synth_ai/cli/commands/filter/errors.py +55 -0
  181. synth_ai/cli/commands/filter/validation.py +77 -0
  182. synth_ai/cli/commands/help/__init__.py +177 -0
  183. synth_ai/cli/commands/help/core.py +72 -0
  184. synth_ai/cli/commands/smoke/__init__.py +7 -0
  185. synth_ai/cli/commands/smoke/core.py +1436 -0
  186. synth_ai/cli/commands/status/__init__.py +64 -0
  187. synth_ai/cli/commands/status/client.py +192 -0
  188. synth_ai/cli/commands/status/config.py +92 -0
  189. synth_ai/cli/commands/status/errors.py +20 -0
  190. synth_ai/cli/commands/status/formatters.py +164 -0
  191. synth_ai/cli/commands/status/subcommands/__init__.py +9 -0
  192. synth_ai/cli/commands/status/subcommands/files.py +79 -0
  193. synth_ai/cli/commands/status/subcommands/jobs.py +334 -0
  194. synth_ai/cli/commands/status/subcommands/models.py +79 -0
  195. synth_ai/cli/commands/status/subcommands/pricing.py +22 -0
  196. synth_ai/cli/commands/status/subcommands/runs.py +81 -0
  197. synth_ai/cli/commands/status/subcommands/summary.py +47 -0
  198. synth_ai/cli/commands/status/subcommands/usage.py +203 -0
  199. synth_ai/cli/commands/status/utils.py +114 -0
  200. synth_ai/cli/commands/train/__init__.py +53 -0
  201. synth_ai/cli/commands/train/core.py +21 -0
  202. synth_ai/cli/commands/train/errors.py +117 -0
  203. synth_ai/cli/commands/train/judge_schemas.py +200 -0
  204. synth_ai/cli/commands/train/judge_validation.py +305 -0
  205. synth_ai/cli/commands/train/validation.py +386 -0
  206. synth_ai/cli/demo.py +30 -158
  207. synth_ai/cli/deploy/__init__.py +43 -0
  208. synth_ai/cli/deploy.py +162 -0
  209. synth_ai/cli/eval/__init__.py +36 -0
  210. synth_ai/cli/eval/core.py +5 -0
  211. synth_ai/cli/eval/errors.py +31 -0
  212. synth_ai/cli/eval/validation.py +5 -0
  213. synth_ai/cli/filter/__init__.py +28 -0
  214. synth_ai/cli/filter/core.py +5 -0
  215. synth_ai/cli/filter/errors.py +23 -0
  216. synth_ai/cli/filter/validation.py +5 -0
  217. synth_ai/cli/legacy_root_backup.py +14 -8
  218. synth_ai/cli/modal_serve/__init__.py +12 -0
  219. synth_ai/cli/modal_serve/core.py +14 -0
  220. synth_ai/cli/modal_serve/errors.py +8 -0
  221. synth_ai/cli/modal_serve/validation.py +11 -0
  222. synth_ai/cli/opencode.py +107 -0
  223. synth_ai/cli/root.py +9 -5
  224. synth_ai/cli/serve/__init__.py +12 -0
  225. synth_ai/cli/serve/core.py +14 -0
  226. synth_ai/cli/serve/errors.py +8 -0
  227. synth_ai/cli/serve/validation.py +11 -0
  228. synth_ai/cli/setup.py +20 -265
  229. synth_ai/cli/status.py +7 -126
  230. synth_ai/cli/task_app_deploy.py +1 -10
  231. synth_ai/cli/task_app_modal_serve.py +4 -9
  232. synth_ai/cli/task_app_serve.py +4 -11
  233. synth_ai/cli/task_apps.py +51 -1480
  234. synth_ai/cli/train/__init__.py +12 -0
  235. synth_ai/cli/train/core.py +21 -0
  236. synth_ai/cli/train/errors.py +8 -0
  237. synth_ai/cli/train/validation.py +24 -0
  238. synth_ai/cli/train.py +1 -14
  239. synth_ai/demos/crafter/grpo_crafter_task_app.py +1 -1
  240. synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +1 -1
  241. synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +7 -4
  242. synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +9 -5
  243. synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +4 -3
  244. synth_ai/environments/examples/red/engine.py +33 -12
  245. synth_ai/environments/examples/red/engine_helpers/reward_components.py +151 -179
  246. synth_ai/environments/examples/red/environment.py +26 -0
  247. synth_ai/environments/examples/red/trace_hooks_v3.py +168 -0
  248. synth_ai/http.py +12 -0
  249. synth_ai/judge_schemas.py +10 -10
  250. synth_ai/learning/__init__.py +10 -0
  251. synth_ai/learning/prompt_learning_client.py +276 -0
  252. synth_ai/learning/prompt_learning_types.py +184 -0
  253. synth_ai/learning/rl/client.py +3 -1
  254. synth_ai/pricing/__init__.py +2 -0
  255. synth_ai/pricing/model_pricing.py +57 -0
  256. synth_ai/streaming/__init__.py +29 -0
  257. synth_ai/streaming/config.py +94 -0
  258. synth_ai/streaming/handlers.py +518 -0
  259. synth_ai/streaming/streamer.py +320 -0
  260. synth_ai/streaming/types.py +95 -0
  261. synth_ai/task/apps/__init__.py +1 -0
  262. synth_ai/task/config.py +2 -0
  263. synth_ai/task/tracing_utils.py +25 -25
  264. synth_ai/task/validators.py +45 -9
  265. synth_ai/task_app_cfgs.py +21 -0
  266. synth_ai/tracing_v3/config.py +162 -19
  267. synth_ai/tracing_v3/constants.py +1 -1
  268. synth_ai/tracing_v3/db_config.py +24 -38
  269. synth_ai/tracing_v3/migration_helper.py +1 -2
  270. synth_ai/tracing_v3/storage/config.py +47 -13
  271. synth_ai/tracing_v3/storage/factory.py +3 -3
  272. synth_ai/tracing_v3/turso/daemon.py +113 -11
  273. synth_ai/tracing_v3/turso/native_manager.py +92 -16
  274. synth_ai/types.py +8 -0
  275. synth_ai/urls.py +11 -0
  276. synth_ai/utils/__init__.py +30 -1
  277. synth_ai/utils/agents.py +74 -0
  278. synth_ai/utils/bin.py +39 -0
  279. synth_ai/utils/cli.py +149 -5
  280. synth_ai/utils/env.py +40 -33
  281. synth_ai/utils/http.py +4 -1
  282. synth_ai/utils/json.py +72 -0
  283. synth_ai/utils/modal.py +285 -3
  284. synth_ai/utils/paths.py +48 -0
  285. synth_ai/utils/uvicorn.py +113 -0
  286. {synth_ai-0.2.16.dist-info → synth_ai-0.2.19.dist-info}/METADATA +109 -6
  287. {synth_ai-0.2.16.dist-info → synth_ai-0.2.19.dist-info}/RECORD +291 -142
  288. examples/qwen_vl/configs/eval_qwen2vl_vision.toml +0 -44
  289. synth_ai/cli/tui.py +0 -62
  290. synth_ai/tui/__init__.py +0 -5
  291. synth_ai/tui/__main__.py +0 -13
  292. synth_ai/tui/cli/__init__.py +0 -1
  293. synth_ai/tui/cli/query_experiments.py +0 -164
  294. synth_ai/tui/cli/query_experiments_v3.py +0 -164
  295. synth_ai/tui/dashboard.py +0 -911
  296. {synth_ai-0.2.16.dist-info → synth_ai-0.2.19.dist-info}/WHEEL +0 -0
  297. {synth_ai-0.2.16.dist-info → synth_ai-0.2.19.dist-info}/entry_points.txt +0 -0
  298. {synth_ai-0.2.16.dist-info → synth_ai-0.2.19.dist-info}/licenses/LICENSE +0 -0
  299. {synth_ai-0.2.16.dist-info → synth_ai-0.2.19.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,334 @@
1
+ """`synth jobs` command group implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from typing import Any
7
+
8
+ import click
9
+
10
+ from ..client import StatusAPIClient
11
+ from ..errors import StatusAPIError
12
+ from ..formatters import (
13
+ console,
14
+ events_panel,
15
+ job_panel,
16
+ jobs_table,
17
+ metrics_table,
18
+ print_json,
19
+ runs_table,
20
+ )
21
+ from ..utils import bail, common_options, parse_relative_time, resolve_context_config
22
+
23
+
24
+ @click.group("jobs", help="Manage training jobs.")
25
+ @click.pass_context
26
+ def jobs_group(ctx: click.Context) -> None: # pragma: no cover - Click wiring
27
+ ctx.ensure_object(dict)
28
+
29
+
30
+ def _print_or_json(items: Any, output_json: bool) -> None:
31
+ if output_json:
32
+ print_json(items)
33
+ elif isinstance(items, list):
34
+ console.print(jobs_table(items))
35
+ else:
36
+ console.print(job_panel(items))
37
+
38
+
39
+ @jobs_group.command("list")
40
+ @common_options()
41
+ @click.option(
42
+ "--status",
43
+ type=click.Choice(["queued", "running", "succeeded", "failed", "cancelled"]),
44
+ help="Filter by job status.",
45
+ )
46
+ @click.option(
47
+ "--type",
48
+ "job_type",
49
+ type=click.Choice(["sft_offline", "sft_online", "rl_online", "dpo", "sft"]),
50
+ help="Filter by training job type.",
51
+ )
52
+ @click.option("--created-after", help="Filter by creation date (ISO8601 or relative like '24h').")
53
+ @click.option("--limit", default=20, show_default=True, type=int)
54
+ @click.option("--json", "output_json", is_flag=True, help="Emit raw JSON.")
55
+ @click.pass_context
56
+ def list_jobs(
57
+ ctx: click.Context,
58
+ base_url: str | None,
59
+ api_key: str | None,
60
+ timeout: float,
61
+ status: str | None,
62
+ job_type: str | None,
63
+ created_after: str | None,
64
+ limit: int,
65
+ output_json: bool,
66
+ ) -> None:
67
+ cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
68
+ created_filter = parse_relative_time(created_after)
69
+
70
+ async def _run() -> None:
71
+ try:
72
+ async with StatusAPIClient(cfg) as client:
73
+ jobs = await client.list_jobs(
74
+ status=status,
75
+ job_type=job_type,
76
+ created_after=created_filter,
77
+ limit=limit,
78
+ )
79
+ _print_or_json(jobs, output_json)
80
+ except StatusAPIError as exc:
81
+ bail(f"Backend error: {exc}")
82
+
83
+ asyncio.run(_run())
84
+
85
+
86
+ @jobs_group.command("get")
87
+ @common_options()
88
+ @click.argument("job_id")
89
+ @click.option("--json", "output_json", is_flag=True)
90
+ @click.pass_context
91
+ def get_job(
92
+ ctx: click.Context,
93
+ base_url: str | None,
94
+ api_key: str | None,
95
+ timeout: float,
96
+ job_id: str,
97
+ output_json: bool,
98
+ ) -> None:
99
+ cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
100
+
101
+ async def _run() -> None:
102
+ try:
103
+ async with StatusAPIClient(cfg) as client:
104
+ job = await client.get_job(job_id)
105
+ _print_or_json(job, output_json)
106
+ except StatusAPIError as exc:
107
+ bail(f"Backend error: {exc}")
108
+
109
+ asyncio.run(_run())
110
+
111
+
112
+ @jobs_group.command("history")
113
+ @common_options()
114
+ @click.argument("job_id")
115
+ @click.option("--json", "output_json", is_flag=True)
116
+ @click.pass_context
117
+ def job_history(
118
+ ctx: click.Context,
119
+ base_url: str | None,
120
+ api_key: str | None,
121
+ timeout: float,
122
+ job_id: str,
123
+ output_json: bool,
124
+ ) -> None:
125
+ cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
126
+
127
+ async def _run() -> None:
128
+ try:
129
+ async with StatusAPIClient(cfg) as client:
130
+ runs = await client.list_job_runs(job_id)
131
+ if output_json:
132
+ print_json(runs)
133
+ else:
134
+ console.print(runs_table(runs))
135
+ except StatusAPIError as exc:
136
+ bail(f"Backend error: {exc}")
137
+
138
+ asyncio.run(_run())
139
+
140
+
141
+ @jobs_group.command("timeline")
142
+ @common_options()
143
+ @click.argument("job_id")
144
+ @click.option("--json", "output_json", is_flag=True)
145
+ @click.pass_context
146
+ def job_timeline(
147
+ ctx: click.Context,
148
+ base_url: str | None,
149
+ api_key: str | None,
150
+ timeout: float,
151
+ job_id: str,
152
+ output_json: bool,
153
+ ) -> None:
154
+ cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
155
+
156
+ async def _run() -> None:
157
+ try:
158
+ async with StatusAPIClient(cfg) as client:
159
+ timeline = await client.get_job_timeline(job_id)
160
+ if output_json:
161
+ print_json(timeline)
162
+ else:
163
+ console.print(events_panel(timeline))
164
+ except StatusAPIError as exc:
165
+ bail(f"Backend error: {exc}")
166
+
167
+ asyncio.run(_run())
168
+
169
+
170
+ @jobs_group.command("metrics")
171
+ @common_options()
172
+ @click.argument("job_id")
173
+ @click.option("--json", "output_json", is_flag=True)
174
+ @click.pass_context
175
+ def job_metrics(
176
+ ctx: click.Context,
177
+ base_url: str | None,
178
+ api_key: str | None,
179
+ timeout: float,
180
+ job_id: str,
181
+ output_json: bool,
182
+ ) -> None:
183
+ cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
184
+
185
+ async def _run() -> None:
186
+ try:
187
+ async with StatusAPIClient(cfg) as client:
188
+ metrics = await client.get_job_metrics(job_id)
189
+ if output_json:
190
+ print_json(metrics)
191
+ else:
192
+ console.print(metrics_table(metrics))
193
+ except StatusAPIError as exc:
194
+ bail(f"Backend error: {exc}")
195
+
196
+ asyncio.run(_run())
197
+
198
+
199
+ @jobs_group.command("config")
200
+ @common_options()
201
+ @click.argument("job_id")
202
+ @click.option("--json", "output_json", is_flag=True)
203
+ @click.pass_context
204
+ def job_config(
205
+ ctx: click.Context,
206
+ base_url: str | None,
207
+ api_key: str | None,
208
+ timeout: float,
209
+ job_id: str,
210
+ output_json: bool,
211
+ ) -> None:
212
+ cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
213
+
214
+ async def _run() -> None:
215
+ try:
216
+ async with StatusAPIClient(cfg) as client:
217
+ config = await client.get_job_config(job_id)
218
+ if output_json:
219
+ print_json(config)
220
+ else:
221
+ console.print(job_panel({"job_id": job_id, "config": config}))
222
+ except StatusAPIError as exc:
223
+ bail(f"Backend error: {exc}")
224
+
225
+ asyncio.run(_run())
226
+
227
+
228
+ @jobs_group.command("status")
229
+ @common_options()
230
+ @click.argument("job_id")
231
+ @click.option("--json", "output_json", is_flag=True)
232
+ @click.pass_context
233
+ def job_status(
234
+ ctx: click.Context,
235
+ base_url: str | None,
236
+ api_key: str | None,
237
+ timeout: float,
238
+ job_id: str,
239
+ output_json: bool,
240
+ ) -> None:
241
+ cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
242
+
243
+ async def _run() -> None:
244
+ try:
245
+ async with StatusAPIClient(cfg) as client:
246
+ status = await client.get_job_status(job_id)
247
+ if output_json:
248
+ print_json(status)
249
+ else:
250
+ console.print(f"[bold]{job_id}[/bold]: {status.get('status', 'unknown')}")
251
+ except StatusAPIError as exc:
252
+ bail(f"Backend error: {exc}")
253
+
254
+ asyncio.run(_run())
255
+
256
+
257
+ @jobs_group.command("cancel")
258
+ @common_options()
259
+ @click.argument("job_id")
260
+ @click.pass_context
261
+ def cancel_job(
262
+ ctx: click.Context,
263
+ base_url: str | None,
264
+ api_key: str | None,
265
+ timeout: float,
266
+ job_id: str,
267
+ ) -> None:
268
+ cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
269
+
270
+ async def _run() -> None:
271
+ try:
272
+ async with StatusAPIClient(cfg) as client:
273
+ resp = await client.cancel_job(job_id)
274
+ console.print(resp.get("message") or f"[yellow]Cancellation requested for {job_id}[/yellow]")
275
+ except StatusAPIError as exc:
276
+ bail(f"Backend error: {exc}")
277
+
278
+ asyncio.run(_run())
279
+
280
+
281
+ @jobs_group.command("logs")
282
+ @common_options()
283
+ @click.argument("job_id")
284
+ @click.option("--since", help="Only show events emitted after the provided timestamp/relative offset.")
285
+ @click.option("--tail", type=int, help="Show only the last N events.")
286
+ @click.option("--follow/--no-follow", default=False, help="Poll for new events.")
287
+ @click.option("--json", "output_json", is_flag=True)
288
+ @click.pass_context
289
+ def job_logs(
290
+ ctx: click.Context,
291
+ base_url: str | None,
292
+ api_key: str | None,
293
+ timeout: float,
294
+ job_id: str,
295
+ since: str | None,
296
+ tail: int | None,
297
+ follow: bool,
298
+ output_json: bool,
299
+ ) -> None:
300
+ cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
301
+ since_filter = parse_relative_time(since)
302
+
303
+ async def _loop() -> None:
304
+ seen_ids: set[str] = set()
305
+ cursor: str | None = None
306
+ try:
307
+ async with StatusAPIClient(cfg) as client:
308
+ while True:
309
+ events = await client.get_job_events(
310
+ job_id,
311
+ since=cursor or since_filter,
312
+ limit=tail,
313
+ after=cursor,
314
+ )
315
+ new_events: list[dict[str, Any]] = []
316
+ for event in events:
317
+ event_id = str(event.get("event_id") or event.get("id") or event.get("timestamp"))
318
+ if event_id in seen_ids:
319
+ continue
320
+ seen_ids.add(event_id)
321
+ new_events.append(event)
322
+ if new_events:
323
+ cursor = str(new_events[-1].get("event_id") or new_events[-1].get("id") or "")
324
+ if output_json:
325
+ print_json(new_events)
326
+ else:
327
+ console.print(events_panel(new_events))
328
+ if not follow:
329
+ break
330
+ await asyncio.sleep(2.0)
331
+ except StatusAPIError as exc:
332
+ bail(f"Backend error: {exc}")
333
+
334
+ asyncio.run(_loop())
@@ -0,0 +1,79 @@
1
+ """`synth models` command group."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+
7
+ import click
8
+ from rich.json import JSON
9
+
10
+ from ..client import StatusAPIClient
11
+ from ..errors import StatusAPIError
12
+ from ..formatters import console, models_table, print_json
13
+ from ..utils import bail, common_options, resolve_context_config
14
+
15
+
16
+ @click.group("models", help="Inspect fine-tuned models.")
17
+ @click.pass_context
18
+ def models_group(ctx: click.Context) -> None: # pragma: no cover - Click wiring
19
+ ctx.ensure_object(dict)
20
+
21
+
22
+ @models_group.command("list")
23
+ @common_options()
24
+ @click.option("--limit", type=int, default=None, help="Maximum number of models to return.")
25
+ @click.option("--type", "model_type", type=click.Choice(["rl", "sft"]), default=None, help="Filter by model type.")
26
+ @click.option("--json", "output_json", is_flag=True)
27
+ @click.pass_context
28
+ def list_models(
29
+ ctx: click.Context,
30
+ base_url: str | None,
31
+ api_key: str | None,
32
+ timeout: float,
33
+ limit: int | None,
34
+ model_type: str | None,
35
+ output_json: bool,
36
+ ) -> None:
37
+ cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
38
+
39
+ async def _run() -> None:
40
+ try:
41
+ async with StatusAPIClient(cfg) as client:
42
+ models = await client.list_models(limit=limit, model_type=model_type)
43
+ if output_json:
44
+ print_json(models)
45
+ else:
46
+ console.print(models_table(models))
47
+ except StatusAPIError as exc:
48
+ bail(f"Backend error: {exc}")
49
+
50
+ asyncio.run(_run())
51
+
52
+
53
+ @models_group.command("get")
54
+ @common_options()
55
+ @click.argument("model_id")
56
+ @click.option("--json", "output_json", is_flag=True)
57
+ @click.pass_context
58
+ def get_model(
59
+ ctx: click.Context,
60
+ base_url: str | None,
61
+ api_key: str | None,
62
+ timeout: float,
63
+ model_id: str,
64
+ output_json: bool,
65
+ ) -> None:
66
+ cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
67
+
68
+ async def _run() -> None:
69
+ try:
70
+ async with StatusAPIClient(cfg) as client:
71
+ model = await client.get_model(model_id)
72
+ if output_json:
73
+ print_json(model)
74
+ else:
75
+ console.print(JSON.from_data(model))
76
+ except StatusAPIError as exc:
77
+ bail(f"Backend error: {exc}")
78
+
79
+ asyncio.run(_run())
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ import click
4
+ from rich.table import Table
5
+ from synth_ai.pricing.model_pricing import MODEL_PRICES
6
+
7
+ from ..formatters import console
8
+
9
+
10
+ @click.command("pricing", help="List supported provider/model rates (SDK static table).")
11
+ def pricing_command() -> None:
12
+ table = Table(title="Supported Models and Rates (USD/token)")
13
+ table.add_column("Provider", style="cyan", no_wrap=True)
14
+ table.add_column("Model", style="magenta")
15
+ table.add_column("Input USD", justify="right")
16
+ table.add_column("Output USD", justify="right")
17
+ for provider, models in MODEL_PRICES.items():
18
+ for model, rates in models.items():
19
+ table.add_row(provider, model, f"{rates.input_usd:.9f}", f"{rates.output_usd:.9f}")
20
+ console.print(table)
21
+
22
+
@@ -0,0 +1,81 @@
1
+ """`synth runs` command group."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+
7
+ import click
8
+
9
+ from ..client import StatusAPIClient
10
+ from ..errors import StatusAPIError
11
+ from ..formatters import console, events_panel, print_json, runs_table
12
+ from ..utils import bail, common_options, parse_relative_time, resolve_context_config
13
+
14
+
15
+ @click.group("runs", help="Inspect individual job runs/attempts.")
16
+ @click.pass_context
17
+ def runs_group(ctx: click.Context) -> None: # pragma: no cover - Click wiring
18
+ ctx.ensure_object(dict)
19
+
20
+
21
+ @runs_group.command("list")
22
+ @common_options()
23
+ @click.argument("job_id")
24
+ @click.option("--json", "output_json", is_flag=True)
25
+ @click.pass_context
26
+ def list_runs(
27
+ ctx: click.Context,
28
+ base_url: str | None,
29
+ api_key: str | None,
30
+ timeout: float,
31
+ job_id: str,
32
+ output_json: bool,
33
+ ) -> None:
34
+ cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
35
+
36
+ async def _run() -> None:
37
+ try:
38
+ async with StatusAPIClient(cfg) as client:
39
+ runs = await client.list_job_runs(job_id)
40
+ if output_json:
41
+ print_json(runs)
42
+ else:
43
+ console.print(runs_table(runs))
44
+ except StatusAPIError as exc:
45
+ bail(f"Backend error: {exc}")
46
+
47
+ asyncio.run(_run())
48
+
49
+
50
+ @runs_group.command("logs")
51
+ @common_options()
52
+ @click.argument("job_id")
53
+ @click.option("--run", "run_id", required=True, help="Run identifier (number or ID) to inspect.")
54
+ @click.option("--since", help="Filter events after the supplied timestamp/relative offset.")
55
+ @click.option("--json", "output_json", is_flag=True)
56
+ @click.pass_context
57
+ def run_logs(
58
+ ctx: click.Context,
59
+ base_url: str | None,
60
+ api_key: str | None,
61
+ timeout: float,
62
+ job_id: str,
63
+ run_id: str,
64
+ since: str | None,
65
+ output_json: bool,
66
+ ) -> None:
67
+ cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
68
+ since_filter = parse_relative_time(since)
69
+
70
+ async def _run() -> None:
71
+ try:
72
+ async with StatusAPIClient(cfg) as client:
73
+ events = await client.get_job_events(job_id, since=since_filter, run_id=run_id)
74
+ if output_json:
75
+ print_json(events)
76
+ else:
77
+ console.print(events_panel(events))
78
+ except StatusAPIError as exc:
79
+ bail(f"Backend error: {exc}")
80
+
81
+ asyncio.run(_run())
@@ -0,0 +1,47 @@
1
+ """`synth status summary` command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+
7
+ import click
8
+
9
+ from ..client import StatusAPIClient
10
+ from ..errors import StatusAPIError
11
+ from ..formatters import console, files_table, jobs_table, models_table
12
+ from ..utils import common_options, resolve_context_config
13
+
14
+
15
+ @click.command("summary", help="Show a condensed overview of recent jobs, models, and files.")
16
+ @common_options()
17
+ @click.option("--limit", default=5, show_default=True, type=int, help="Rows per section.")
18
+ @click.pass_context
19
+ def summary_command(
20
+ ctx: click.Context,
21
+ base_url: str | None,
22
+ api_key: str | None,
23
+ timeout: float,
24
+ limit: int,
25
+ ) -> None:
26
+ cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
27
+
28
+ async def _run() -> tuple[list[dict[str, object]], list[dict[str, object]], list[dict[str, object]]]:
29
+ async with StatusAPIClient(cfg) as client:
30
+ try:
31
+ jobs = await client.list_jobs(limit=limit)
32
+ except StatusAPIError:
33
+ jobs = []
34
+ try:
35
+ models = await client.list_models(limit=limit)
36
+ except StatusAPIError:
37
+ models = []
38
+ try:
39
+ files = await client.list_files(limit=limit)
40
+ except StatusAPIError:
41
+ files = []
42
+ return jobs, models, files
43
+
44
+ jobs, models, files = asyncio.run(_run())
45
+ console.print(jobs_table(jobs[:limit]))
46
+ console.print(models_table(models[:limit]))
47
+ console.print(files_table(files[:limit]))