synth-ai 0.2.9.dev7__py3-none-any.whl → 0.2.9.dev8__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 (327) hide show
  1. examples/__init__.py +16 -0
  2. examples/crafter_debug_render.py +8 -11
  3. examples/qwen_coder/README.md +102 -0
  4. examples/qwen_coder/_shared.py +113 -0
  5. examples/qwen_coder/configs/coder_lora_30b.toml +61 -0
  6. examples/qwen_coder/configs/coder_lora_4b.toml +57 -0
  7. examples/qwen_coder/configs/coder_lora_small.toml +58 -0
  8. examples/qwen_coder/generate_dataset.py +98 -0
  9. examples/qwen_coder/infer_ft_smoke.py +64 -0
  10. examples/qwen_coder/infer_prod_proxy.py +73 -0
  11. examples/qwen_coder/infer_via_synth.py +87 -0
  12. examples/qwen_coder/scripts/infer_coder.sh +18 -0
  13. examples/qwen_coder/scripts/train_coder_30b.sh +21 -0
  14. examples/qwen_coder/sft_full_17b.py +103 -0
  15. examples/qwen_coder/sft_lora_30b.py +110 -0
  16. examples/qwen_coder/subset_jsonl.py +38 -0
  17. examples/qwen_coder/validate_jsonl.py +59 -0
  18. examples/rl/run_eval.py +36 -37
  19. examples/rl/run_rl_and_save.py +5 -5
  20. examples/rl/task_app/math_single_step.py +65 -43
  21. examples/rl/task_app/math_task_app.py +3 -3
  22. examples/sft/README.md +139 -0
  23. examples/sft/configs/crafter_fft_qwen0p6b.toml +44 -0
  24. examples/sft/configs/crafter_lora_qwen0p6b.toml +45 -0
  25. examples/sft/evaluate.py +117 -0
  26. examples/sft/export_dataset.py +117 -0
  27. examples/sft/generate_traces.py +162 -0
  28. examples/swe/__init__.py +12 -0
  29. examples/swe/task_app/README.md +105 -0
  30. examples/swe/task_app/__init__.py +2 -0
  31. examples/swe/task_app/grpo_swe_mini.py +571 -0
  32. examples/swe/task_app/grpo_swe_mini_task_app.py +136 -0
  33. examples/swe/task_app/hosted/README.md +173 -0
  34. examples/swe/task_app/hosted/__init__.py +5 -0
  35. examples/swe/task_app/hosted/branching.py +143 -0
  36. examples/swe/task_app/hosted/environment_routes.py +1289 -0
  37. examples/swe/task_app/hosted/envs/__init__.py +1 -0
  38. examples/swe/task_app/hosted/envs/crafter/__init__.py +6 -0
  39. examples/swe/task_app/hosted/envs/crafter/app.py +1 -0
  40. examples/swe/task_app/hosted/envs/crafter/environment.py +522 -0
  41. examples/swe/task_app/hosted/envs/crafter/policy.py +478 -0
  42. examples/swe/task_app/hosted/envs/crafter/react_agent.py +108 -0
  43. examples/swe/task_app/hosted/envs/crafter/shared.py +305 -0
  44. examples/swe/task_app/hosted/envs/crafter/tools.py +47 -0
  45. examples/swe/task_app/hosted/envs/mini_swe/__init__.py +8 -0
  46. examples/swe/task_app/hosted/envs/mini_swe/environment.py +1164 -0
  47. examples/swe/task_app/hosted/envs/mini_swe/policy.py +355 -0
  48. examples/swe/task_app/hosted/envs/mini_swe/shared.py +83 -0
  49. examples/swe/task_app/hosted/envs/mini_swe/tools.py +96 -0
  50. examples/swe/task_app/hosted/hosted_app.py +204 -0
  51. examples/swe/task_app/hosted/inference/__init__.py +5 -0
  52. examples/swe/task_app/hosted/inference/openai_client.py +618 -0
  53. examples/swe/task_app/hosted/main.py +100 -0
  54. examples/swe/task_app/hosted/policy_routes.py +1079 -0
  55. examples/swe/task_app/hosted/registry.py +195 -0
  56. examples/swe/task_app/hosted/rollout.py +1869 -0
  57. examples/swe/task_app/hosted/storage/__init__.py +5 -0
  58. examples/swe/task_app/hosted/storage/volume.py +211 -0
  59. examples/swe/task_app/hosted/test_agents.py +161 -0
  60. examples/swe/task_app/hosted/test_service.py +137 -0
  61. examples/swe/task_app/hosted/utils.py +62 -0
  62. examples/vlm/README.md +68 -0
  63. examples/vlm/configs/crafter_vlm_gpt4o.toml +44 -0
  64. examples/vlm/crafter_image_only_agent.py +207 -0
  65. examples/vlm/crafter_openai_vlm_agent.py +277 -0
  66. examples/vlm/filter_image_rows.py +63 -0
  67. examples/vlm/run_crafter_vlm_benchmark.py +316 -0
  68. examples/warming_up_to_rl/analyze_trace_db.py +5 -5
  69. examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +11 -1
  70. examples/warming_up_to_rl/export_trace_sft.py +78 -21
  71. examples/warming_up_to_rl/groq_test.py +4 -4
  72. examples/warming_up_to_rl/manage_secrets.py +13 -18
  73. examples/warming_up_to_rl/run_eval.py +42 -44
  74. examples/warming_up_to_rl/run_fft_and_save.py +11 -16
  75. examples/warming_up_to_rl/run_local_rollout.py +1 -3
  76. examples/warming_up_to_rl/run_local_rollout_modal.py +2 -4
  77. examples/warming_up_to_rl/run_local_rollout_parallel.py +1 -4
  78. examples/warming_up_to_rl/run_local_rollout_traced.py +3 -5
  79. examples/warming_up_to_rl/run_rl_and_save.py +5 -6
  80. examples/warming_up_to_rl/run_rollout_remote.py +8 -10
  81. examples/warming_up_to_rl/task_app/README.md +6 -2
  82. examples/warming_up_to_rl/task_app/grpo_crafter.py +234 -35
  83. examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +2 -3
  84. examples/warming_up_to_rl/task_app/synth_envs_hosted/__init__.py +1 -1
  85. examples/warming_up_to_rl/task_app/synth_envs_hosted/branching.py +9 -11
  86. examples/warming_up_to_rl/task_app/synth_envs_hosted/environment_routes.py +131 -114
  87. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/environment.py +101 -41
  88. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +73 -51
  89. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +14 -6
  90. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/shared.py +16 -16
  91. examples/warming_up_to_rl/task_app/synth_envs_hosted/hosted_app.py +32 -34
  92. examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +94 -31
  93. examples/warming_up_to_rl/task_app/synth_envs_hosted/main.py +0 -2
  94. examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +303 -203
  95. examples/warming_up_to_rl/task_app/synth_envs_hosted/registry.py +21 -23
  96. examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +328 -225
  97. examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/volume.py +13 -13
  98. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_agents.py +1 -0
  99. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_service.py +1 -0
  100. examples/warming_up_to_rl/task_app/synth_envs_hosted/utils.py +4 -3
  101. synth/__init__.py +14 -0
  102. synth_ai/__init__.py +26 -4
  103. synth_ai/api/models/supported.py +376 -0
  104. synth_ai/api/train/builders.py +128 -21
  105. synth_ai/api/train/cli.py +80 -64
  106. synth_ai/api/train/config_finder.py +7 -2
  107. synth_ai/api/train/env_resolver.py +1 -1
  108. synth_ai/api/train/pollers.py +2 -1
  109. synth_ai/api/train/supported_algos.py +139 -0
  110. synth_ai/api/train/task_app.py +1 -2
  111. synth_ai/api/train/utils.py +13 -44
  112. synth_ai/cli/__init__.py +8 -0
  113. synth_ai/cli/_modal_wrapper.py +28 -0
  114. synth_ai/cli/_typer_patch.py +49 -0
  115. synth_ai/cli/balance.py +1 -2
  116. synth_ai/cli/calc.py +1 -1
  117. synth_ai/cli/demo.py +2 -1
  118. synth_ai/cli/recent.py +2 -2
  119. synth_ai/cli/rl_demo.py +2 -1
  120. synth_ai/cli/root.py +11 -13
  121. synth_ai/cli/status.py +2 -2
  122. synth_ai/cli/task_apps.py +529 -179
  123. synth_ai/cli/traces.py +6 -4
  124. synth_ai/cli/watch.py +12 -18
  125. synth_ai/demo_registry.py +1 -1
  126. synth_ai/demos/core/cli.py +36 -43
  127. synth_ai/demos/demo_task_apps/__init__.py +3 -3
  128. synth_ai/demos/demo_task_apps/core.py +17 -25
  129. synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +3 -4
  130. synth_ai/demos/demo_task_apps/math/app.py +2 -1
  131. synth_ai/demos/demo_task_apps/math/deploy_modal.py +3 -4
  132. synth_ai/demos/demo_task_apps/math/modal_task_app.py +16 -18
  133. synth_ai/demos/demo_task_apps/math/task_app_entry.py +0 -1
  134. synth_ai/environments/examples/crafter_classic/environment.py +76 -1
  135. synth_ai/environments/reproducibility/tree.py +2 -5
  136. synth_ai/environments/service/app.py +11 -12
  137. synth_ai/environments/service/core_routes.py +4 -7
  138. synth_ai/environments/stateful/engine.py +1 -1
  139. synth_ai/environments/tasks/core.py +1 -0
  140. synth_ai/environments/tasks/filters.py +5 -6
  141. synth_ai/environments/tasks/utils.py +4 -5
  142. synth_ai/handshake.py +9 -9
  143. synth_ai/http.py +1 -1
  144. synth_ai/http_client.py +18 -10
  145. synth_ai/inference/client.py +15 -5
  146. synth_ai/jobs/client.py +78 -83
  147. synth_ai/learning/__init__.py +41 -6
  148. synth_ai/learning/algorithms.py +14 -0
  149. synth_ai/learning/client.py +91 -24
  150. synth_ai/learning/config.py +2 -38
  151. synth_ai/learning/ft_client.py +4 -59
  152. synth_ai/learning/health.py +5 -6
  153. synth_ai/learning/jobs.py +31 -47
  154. synth_ai/{rl → learning/rl}/__init__.py +14 -4
  155. synth_ai/learning/rl/client.py +267 -0
  156. synth_ai/learning/rl/config.py +31 -0
  157. synth_ai/{rl → learning/rl}/contracts.py +5 -8
  158. synth_ai/{rl → learning/rl}/env_keys.py +39 -15
  159. synth_ai/learning/rl/secrets.py +13 -0
  160. synth_ai/learning/rl_client.py +2 -281
  161. synth_ai/learning/sft/__init__.py +29 -0
  162. synth_ai/learning/sft/client.py +68 -0
  163. synth_ai/learning/sft/config.py +270 -0
  164. synth_ai/learning/sft/data.py +295 -0
  165. synth_ai/learning/sse.py +25 -24
  166. synth_ai/learning/validators.py +25 -28
  167. synth_ai/lm/__init__.py +21 -47
  168. synth_ai/main.py +4 -0
  169. synth_ai/task/__init__.py +25 -27
  170. synth_ai/task/apps/__init__.py +7 -8
  171. synth_ai/task/auth.py +8 -8
  172. synth_ai/task/client.py +14 -14
  173. synth_ai/task/contracts.py +36 -35
  174. synth_ai/task/datasets.py +6 -5
  175. synth_ai/task/errors.py +10 -10
  176. synth_ai/task/health.py +17 -9
  177. synth_ai/task/json.py +58 -23
  178. synth_ai/task/proxy.py +13 -9
  179. synth_ai/task/rubrics.py +16 -15
  180. synth_ai/task/server.py +12 -12
  181. synth_ai/task/tracing_utils.py +4 -4
  182. synth_ai/task/vendors.py +5 -6
  183. synth_ai/tracing_v3/__init__.py +2 -0
  184. synth_ai/tracing_v3/abstractions.py +21 -4
  185. synth_ai/tracing_v3/decorators.py +18 -16
  186. synth_ai/tracing_v3/hooks.py +5 -5
  187. synth_ai/tracing_v3/llm_call_record_helpers.py +6 -6
  188. synth_ai/tracing_v3/session_tracer.py +40 -14
  189. synth_ai/tracing_v3/storage/base.py +85 -0
  190. synth_ai/tracing_v3/storage/config.py +21 -8
  191. synth_ai/tracing_v3/storage/factory.py +10 -7
  192. synth_ai/tracing_v3/storage/utils.py +4 -2
  193. synth_ai/tracing_v3/turso/daemon.py +7 -2
  194. synth_ai/tracing_v3/turso/models.py +2 -2
  195. synth_ai/tracing_v3/turso/native_manager.py +1173 -0
  196. synth_ai/tracing_v3/utils.py +4 -4
  197. synth_ai/v0/api/__init__.py +8 -0
  198. synth_ai/v0/api/models/__init__.py +8 -0
  199. synth_ai/v0/api/models/supported.py +8 -0
  200. synth_ai/v0/config/__init__.py +15 -0
  201. synth_ai/v0/config/base_url.py +12 -0
  202. synth_ai/v0/lm/__init__.py +51 -0
  203. synth_ai/{lm → v0/lm}/caching/ephemeral.py +2 -2
  204. synth_ai/{lm → v0/lm}/caching/handler.py +4 -4
  205. synth_ai/{lm → v0/lm}/caching/initialize.py +1 -1
  206. synth_ai/{lm → v0/lm}/caching/persistent.py +1 -1
  207. synth_ai/{lm → v0/lm}/config.py +6 -1
  208. synth_ai/{lm → v0/lm}/core/all.py +9 -9
  209. synth_ai/{lm → v0/lm}/core/main.py +6 -6
  210. synth_ai/{lm → v0/lm}/core/main_v3.py +10 -10
  211. synth_ai/{lm → v0/lm}/core/synth_models.py +2 -14
  212. synth_ai/{lm → v0/lm}/core/vendor_clients.py +2 -2
  213. synth_ai/{lm → v0/lm}/overrides.py +2 -2
  214. synth_ai/{lm → v0/lm}/provider_support/anthropic.py +4 -4
  215. synth_ai/{lm → v0/lm}/provider_support/openai.py +5 -5
  216. synth_ai/{lm → v0/lm}/structured_outputs/handler.py +5 -5
  217. synth_ai/{lm → v0/lm}/structured_outputs/rehabilitate.py +1 -1
  218. synth_ai/{lm → v0/lm}/vendors/core/anthropic_api.py +9 -9
  219. synth_ai/{lm → v0/lm}/vendors/core/gemini_api.py +5 -5
  220. synth_ai/{lm → v0/lm}/vendors/core/mistral_api.py +5 -5
  221. synth_ai/{lm → v0/lm}/vendors/core/openai_api.py +10 -10
  222. synth_ai/{lm → v0/lm}/vendors/openai_standard.py +8 -8
  223. synth_ai/{lm → v0/lm}/vendors/openai_standard_responses.py +2 -2
  224. synth_ai/{lm → v0/lm}/vendors/supported/custom_endpoint.py +3 -3
  225. synth_ai/{lm → v0/lm}/vendors/supported/deepseek.py +2 -2
  226. synth_ai/{lm → v0/lm}/vendors/supported/grok.py +2 -2
  227. synth_ai/{lm → v0/lm}/vendors/supported/groq.py +1 -1
  228. synth_ai/{lm → v0/lm}/vendors/supported/ollama.py +1 -1
  229. synth_ai/{lm → v0/lm}/vendors/supported/openrouter.py +3 -3
  230. synth_ai/{lm → v0/lm}/vendors/supported/together.py +1 -1
  231. synth_ai/{lm → v0/lm}/vendors/synth_client.py +1 -1
  232. synth_ai/v0/tracing_v3/__init__.py +10 -0
  233. synth_ai/v0/tracing_v3/abstractions.py +3 -0
  234. synth_ai/v0/tracing_v3/decorators.py +3 -0
  235. synth_ai/v0/tracing_v3/llm_call_record_helpers.py +3 -0
  236. synth_ai/v0/tracing_v3/session_tracer.py +3 -0
  237. synth_ai-0.2.9.dev8.dist-info/METADATA +191 -0
  238. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.9.dev8.dist-info}/RECORD +268 -238
  239. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.9.dev8.dist-info}/top_level.txt +1 -0
  240. examples/common_old/backend.py +0 -20
  241. examples/evals_old/README.md +0 -98
  242. examples/evals_old/__init__.py +0 -6
  243. examples/evals_old/compare_models.py +0 -1038
  244. examples/evals_old/example_log.md +0 -145
  245. examples/evals_old/run_demo.sh +0 -126
  246. examples/evals_old/trace_analysis.py +0 -270
  247. examples/finetuning_old/_backup_synth_qwen/config.toml +0 -29
  248. examples/finetuning_old/_backup_synth_qwen/example_log.md +0 -324
  249. examples/finetuning_old/_backup_synth_qwen/filter_traces.py +0 -60
  250. examples/finetuning_old/_backup_synth_qwen/filter_traces_achievements.py +0 -243
  251. examples/finetuning_old/_backup_synth_qwen/purge_v3_traces.py +0 -109
  252. examples/finetuning_old/_backup_synth_qwen/react_agent_lm.py +0 -1924
  253. examples/finetuning_old/_backup_synth_qwen/readme.md +0 -49
  254. examples/finetuning_old/_backup_synth_qwen/run_crafter_qwen4b.py +0 -114
  255. examples/finetuning_old/_backup_synth_qwen/run_demo.sh +0 -195
  256. examples/finetuning_old/_backup_synth_qwen/sft_kickoff.py +0 -119
  257. examples/finetuning_old/synth_qwen_v1/README.md +0 -68
  258. examples/finetuning_old/synth_qwen_v1/filter_traces.py +0 -60
  259. examples/finetuning_old/synth_qwen_v1/filter_traces_achievements.py +0 -243
  260. examples/finetuning_old/synth_qwen_v1/finetune.py +0 -46
  261. examples/finetuning_old/synth_qwen_v1/hello_ft_model.py +0 -71
  262. examples/finetuning_old/synth_qwen_v1/infer.py +0 -36
  263. examples/finetuning_old/synth_qwen_v1/poll.py +0 -46
  264. examples/finetuning_old/synth_qwen_v1/prepare_data.py +0 -35
  265. examples/finetuning_old/synth_qwen_v1/purge_v3_traces.py +0 -109
  266. examples/finetuning_old/synth_qwen_v1/react_agent_lm.py +0 -1933
  267. examples/finetuning_old/synth_qwen_v1/run_crafter_sft_job.py +0 -210
  268. examples/finetuning_old/synth_qwen_v1/run_ft_job.py +0 -237
  269. examples/finetuning_old/synth_qwen_v1/upload_data.py +0 -34
  270. examples/finetuning_old/synth_qwen_v1/util.py +0 -152
  271. examples/rl_old/task_app.py +0 -1131
  272. examples/warming_up_to_rl/old/event_rewards.md +0 -234
  273. examples/warming_up_to_rl/old/notes.md +0 -73
  274. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_traces_sft_turso.py +0 -738
  275. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/filter_traces_sft_turso.py +0 -580
  276. synth_ai/experimental/synth_oss.py +0 -445
  277. synth_ai/learning/filtering.py +0 -0
  278. synth_ai/learning/offline/dpo.py +0 -0
  279. synth_ai/learning/offline/providers.py +0 -7
  280. synth_ai/learning/offline/sft.py +0 -0
  281. synth_ai/learning/offline/shared.py +0 -0
  282. synth_ai/learning/online/grpo.py +0 -0
  283. synth_ai/learning/online/irft.py +0 -0
  284. synth_ai/learning/prompts/banking77_injection_eval.py +0 -168
  285. synth_ai/learning/prompts/gepa.py +0 -0
  286. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +0 -211
  287. synth_ai/learning/prompts/mipro.py +0 -289
  288. synth_ai/learning/prompts/random_search.py +0 -249
  289. synth_ai/learning/prompts/run_mipro_banking77.py +0 -172
  290. synth_ai/learning/prompts/run_random_search_banking77.py +0 -329
  291. synth_ai/rl/secrets.py +0 -19
  292. synth_ai/scripts/verify_rewards.py +0 -100
  293. synth_ai/tracing/__init__.py +0 -30
  294. synth_ai/tracing_v1/__init__.py +0 -33
  295. synth_ai/tracing_v3/turso/__init__.py +0 -25
  296. synth_ai/tracing_v3/turso/manager.py +0 -838
  297. synth_ai/zyk/__init__.py +0 -30
  298. synth_ai-0.2.9.dev7.dist-info/METADATA +0 -131
  299. /synth_ai/{lm → v0/lm}/caching/__init__.py +0 -0
  300. /synth_ai/{lm → v0/lm}/caching/constants.py +0 -0
  301. /synth_ai/{lm → v0/lm}/caching/dbs.py +0 -0
  302. /synth_ai/{lm → v0/lm}/constants.py +0 -0
  303. /synth_ai/{lm → v0/lm}/core/__init__.py +0 -0
  304. /synth_ai/{lm → v0/lm}/core/exceptions.py +0 -0
  305. /synth_ai/{lm → v0/lm}/cost/__init__.py +0 -0
  306. /synth_ai/{lm → v0/lm}/cost/monitor.py +0 -0
  307. /synth_ai/{lm → v0/lm}/cost/statefulness.py +0 -0
  308. /synth_ai/{lm → v0/lm}/injection.py +0 -0
  309. /synth_ai/{lm → v0/lm}/provider_support/__init__.py +0 -0
  310. /synth_ai/{lm → v0/lm}/provider_support/suppress_logging.py +0 -0
  311. /synth_ai/{lm → v0/lm}/structured_outputs/__init__.py +0 -0
  312. /synth_ai/{lm → v0/lm}/structured_outputs/inject.py +0 -0
  313. /synth_ai/{lm → v0/lm}/tools/__init__.py +0 -0
  314. /synth_ai/{lm → v0/lm}/tools/base.py +0 -0
  315. /synth_ai/{lm → v0/lm}/unified_interface.py +0 -0
  316. /synth_ai/{lm → v0/lm}/vendors/__init__.py +0 -0
  317. /synth_ai/{lm → v0/lm}/vendors/base.py +0 -0
  318. /synth_ai/{lm → v0/lm}/vendors/core/__init__.py +0 -0
  319. /synth_ai/{lm → v0/lm}/vendors/core/synth_dev_api.py +0 -0
  320. /synth_ai/{lm → v0/lm}/vendors/local/__init__.py +0 -0
  321. /synth_ai/{lm → v0/lm}/vendors/local/ollama.py +0 -0
  322. /synth_ai/{lm → v0/lm}/vendors/retries.py +0 -0
  323. /synth_ai/{lm → v0/lm}/vendors/supported/__init__.py +0 -0
  324. /synth_ai/{lm → v0/lm}/warmup.py +0 -0
  325. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.9.dev8.dist-info}/WHEEL +0 -0
  326. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.9.dev8.dist-info}/entry_points.txt +0 -0
  327. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.9.dev8.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,139 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from dataclasses import dataclass
5
+
6
+ from synth_ai.api.models.supported import (
7
+ RL_SUPPORTED_MODELS,
8
+ SFT_SUPPORTED_MODELS,
9
+ training_modes_for_model,
10
+ )
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class AlgorithmSpec:
15
+ algo_type: str
16
+ method: str
17
+ variety: str
18
+ label: str # Human readable identifier (e.g. "RL / GSPO")
19
+ required_training_mode: str # Expected training mode on the model record (e.g. "rl", "sft")
20
+
21
+
22
+ def _normalize(value: object) -> str:
23
+ if value is None:
24
+ return ""
25
+ return str(value).strip().lower()
26
+
27
+ RL_ALGORITHM_MODEL_IDS: tuple[str, ...] = tuple(sorted(RL_SUPPORTED_MODELS))
28
+ RL_ALGORITHM_MODEL_SET: frozenset[str] = frozenset(RL_ALGORITHM_MODEL_IDS)
29
+
30
+ SFT_ALGORITHM_MODEL_IDS: tuple[str, ...] = tuple(sorted(SFT_SUPPORTED_MODELS))
31
+ SFT_ALGORITHM_MODEL_SET: frozenset[str] = frozenset(SFT_ALGORITHM_MODEL_IDS)
32
+
33
+
34
+ SUPPORTED_ALGORITHMS: tuple[AlgorithmSpec, ...] = (
35
+ AlgorithmSpec(
36
+ "online", "policy_gradient", "gspo", "online policy_gradient / gspo", "rl"
37
+ ),
38
+ AlgorithmSpec(
39
+ "offline", "supervised_finetune", "fft", "offline supervised_finetune / fft", "sft"
40
+ ),
41
+ AlgorithmSpec("offline", "sft", "fft", "offline sft / fft", "sft"),
42
+ # Accept explicit LoRA variety for SFT as a first-class alias of FFT SFT.
43
+ # This allows configs to declare intent with variety="lora" while still
44
+ # using SFT training mode; actual adapter selection is driven by [training].
45
+ AlgorithmSpec("offline", "sft", "lora", "offline sft / lora", "sft"),
46
+ )
47
+
48
+ _SUPPORTED_LOOKUP = {
49
+ (spec.algo_type, spec.method, spec.variety): spec for spec in SUPPORTED_ALGORITHMS
50
+ }
51
+
52
+
53
+ class AlgorithmValidationError(ValueError):
54
+ """Raised when an algorithm block contains unsupported combinations."""
55
+
56
+
57
+ def validate_algorithm_config(
58
+ algorithm_block: Mapping[str, object] | None,
59
+ *,
60
+ expected_family: str | None = None,
61
+ ) -> AlgorithmSpec:
62
+ """Validate the [algorithm] section of a training config.
63
+
64
+ Args:
65
+ algorithm_block: Parsed mapping from the TOML config.
66
+ expected_family: Optional expected family label ("rl" or "sft").
67
+
68
+ Returns:
69
+ The matched AlgorithmSpec describing the supported combination.
70
+
71
+ Raises:
72
+ AlgorithmValidationError: if the combination is missing or unsupported.
73
+ """
74
+
75
+ if algorithm_block is None:
76
+ raise AlgorithmValidationError("Missing required [algorithm] section in config.")
77
+ if not isinstance(algorithm_block, Mapping):
78
+ raise AlgorithmValidationError("[algorithm] section must be a mapping/object.")
79
+
80
+ algo_type = _normalize(algorithm_block.get("type"))
81
+ method = _normalize(algorithm_block.get("method"))
82
+ variety = _normalize(algorithm_block.get("variety"))
83
+
84
+ key = (algo_type, method, variety)
85
+ spec = _SUPPORTED_LOOKUP.get(key)
86
+ if spec is None:
87
+ supported = "; ".join(
88
+ f"type='{entry.algo_type}', method='{entry.method}', variety='{entry.variety}'"
89
+ for entry in SUPPORTED_ALGORITHMS
90
+ )
91
+ raise AlgorithmValidationError(
92
+ "Unsupported algorithm configuration:\n"
93
+ f" type={algo_type!r}, method={method!r}, variety={variety!r}\n"
94
+ f"Supported combinations are: {supported}"
95
+ )
96
+
97
+ if expected_family:
98
+ expected_family = expected_family.lower()
99
+ family_map = {
100
+ ("online", "policy_gradient", "gspo"): "rl",
101
+ ("offline", "supervised_finetune", "fft"): "sft",
102
+ ("offline", "sft", "fft"): "sft",
103
+ ("offline", "sft", "lora"): "sft",
104
+ }
105
+ family = family_map.get(key)
106
+ if family != expected_family:
107
+ raise AlgorithmValidationError(
108
+ f"Config contains algorithm {spec.label!r}, "
109
+ f"but the current command expects {expected_family.upper()}."
110
+ )
111
+
112
+ return spec
113
+
114
+
115
+ def ensure_model_supported_for_algorithm(model_id: str, spec: AlgorithmSpec) -> None:
116
+ """Ensure that the given model supports the training mode required by *spec*."""
117
+
118
+ modes = {mode.lower() for mode in training_modes_for_model(model_id)}
119
+ required = spec.required_training_mode.lower()
120
+ if required not in modes:
121
+ if required == "rl":
122
+ allowed = ", ".join(RL_ALGORITHM_MODEL_IDS)
123
+ else:
124
+ allowed = ", ".join(SFT_ALGORITHM_MODEL_IDS)
125
+ raise AlgorithmValidationError(
126
+ f"Model '{model_id}' does not support {spec.label} workloads "
127
+ f"(missing training mode '{required}'). Supported model IDs: {allowed}"
128
+ )
129
+
130
+
131
+ __all__ = [
132
+ "AlgorithmSpec",
133
+ "AlgorithmValidationError",
134
+ "RL_ALGORITHM_MODEL_IDS",
135
+ "SFT_ALGORITHM_MODEL_IDS",
136
+ "SUPPORTED_ALGORITHMS",
137
+ "validate_algorithm_config",
138
+ "ensure_model_supported_for_algorithm",
139
+ ]
@@ -1,8 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- import json
3
+ from collections.abc import Iterable
4
4
  from dataclasses import dataclass
5
- from typing import Iterable
6
5
 
7
6
  import click
8
7
  import requests
@@ -4,15 +4,16 @@ import json
4
4
  import os
5
5
  import re
6
6
  import subprocess
7
- import sys
8
7
  import tempfile
9
8
  import time
9
+ import tomllib
10
+ from collections.abc import Iterable, Mapping
10
11
  from dataclasses import dataclass
11
12
  from pathlib import Path
12
- from typing import Any, Iterable, Mapping
13
+ from typing import Any
13
14
 
14
15
  import requests
15
- import tomllib
16
+ from synth_ai.learning.sft import collect_sft_jsonl_errors
16
17
 
17
18
  REPO_ROOT = Path(__file__).resolve().parents[3]
18
19
 
@@ -139,49 +140,17 @@ def fmt_duration(seconds: float) -> str:
139
140
 
140
141
 
141
142
  def validate_sft_jsonl(path: Path, *, max_errors: int = 20) -> None:
142
- errors: list[str] = []
143
- try:
144
- fh = path.open("r", encoding="utf-8")
145
- except FileNotFoundError as exc: # pragma: no cover - upstream ensures existence
146
- raise TrainError(f"Dataset not found: {path}") from exc
147
-
148
- with fh:
149
- for idx, line in enumerate(fh, start=1):
150
- stripped = line.strip()
151
- if not stripped:
152
- continue
153
- try:
154
- record = json.loads(stripped)
155
- except json.JSONDecodeError as exc:
156
- errors.append(f"Line {idx}: invalid JSON ({exc.msg})")
157
- if len(errors) >= max_errors:
158
- break
159
- continue
160
-
161
- messages = record.get("messages")
162
- if not isinstance(messages, list) or not messages:
163
- errors.append(f"Line {idx}: missing or empty 'messages' list")
164
- if len(errors) >= max_errors:
165
- break
166
- continue
143
+ if not path.exists():
144
+ raise TrainError(f"Dataset not found: {path}")
167
145
 
168
- for msg_idx, msg in enumerate(messages):
169
- if not isinstance(msg, dict):
170
- errors.append(f"Line {idx}: message {msg_idx} is not an object")
171
- break
172
- if "role" not in msg or "content" not in msg:
173
- errors.append(f"Line {idx}: message {msg_idx} missing 'role' or 'content'")
174
- break
175
- if not isinstance(msg["role"], str) or not isinstance(msg["content"], str):
176
- errors.append(f"Line {idx}: message {msg_idx} has non-string role/content")
177
- break
178
- if len(errors) >= max_errors:
179
- break
146
+ issues = collect_sft_jsonl_errors(path, min_messages=1, max_errors=max_errors)
147
+ if not issues:
148
+ return
180
149
 
181
- if errors:
182
- suffix = "" if len(errors) < max_errors else f" (showing first {max_errors} issues)"
183
- details = "\n - ".join(errors)
184
- raise TrainError(f"Dataset validation failed{suffix}:\n - {details}")
150
+ truncated = max_errors is not None and len(issues) >= max_errors
151
+ suffix = "" if not truncated else f" (showing first {max_errors} issues)"
152
+ details = "\n - ".join(issues)
153
+ raise TrainError(f"{path}: Dataset validation failed{suffix}:\n - {details}")
185
154
 
186
155
 
187
156
  def limit_jsonl_examples(src: Path, limit: int) -> Path:
synth_ai/cli/__init__.py CHANGED
@@ -17,6 +17,13 @@ except Exception:
17
17
  # dotenv is optional at runtime; proceed if unavailable
18
18
  pass
19
19
 
20
+ try:
21
+ from ._typer_patch import patch_typer_make_metavar
22
+
23
+ patch_typer_make_metavar()
24
+ except Exception:
25
+ pass
26
+
20
27
 
21
28
  from .root import cli # new canonical CLI entrypoint
22
29
 
@@ -105,3 +112,4 @@ cli.add_command(task_app_group.commands["serve"], name="serve")
105
112
  cli.add_command(task_app_group.commands["deploy"], name="deploy")
106
113
 
107
114
  cli.add_command(task_app_group.commands["modal-serve"], name="modal-serve")
115
+ cli.add_command(task_app_group.commands["info"], name="info")
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+
5
+
6
+ def main() -> int:
7
+ # Apply Typer compatibility patch before Modal CLI bootstraps Click/Typer internals.
8
+ try:
9
+ from ._typer_patch import patch_typer_make_metavar
10
+
11
+ patch_typer_make_metavar()
12
+ except Exception:
13
+ pass
14
+
15
+ from modal.__main__ import main as modal_main
16
+
17
+ # Present ourselves as the upstream `modal` CLI so Typer/Click parsing stays intact.
18
+ if sys.argv:
19
+ sys.argv[0] = "modal"
20
+ else:
21
+ sys.argv = ["modal"]
22
+
23
+ return modal_main()
24
+
25
+
26
+ if __name__ == "__main__":
27
+ sys.exit(main())
28
+
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ from click import Parameter
4
+
5
+
6
+ def patch_typer_make_metavar() -> None:
7
+ try:
8
+ from typer.main import TyperArgument, TyperOption
9
+ except Exception:
10
+ return
11
+
12
+ def _call_get_metavar(param_type: object, param: Parameter) -> str | None:
13
+ getter = getattr(param_type, "get_metavar", None)
14
+ if getter is None:
15
+ return None
16
+ try:
17
+ return getter(param)
18
+ except TypeError:
19
+ try:
20
+ return getter(param, None)
21
+ except TypeError:
22
+ return None
23
+
24
+ def _patched_argument_make_metavar(self, ctx=None) -> str:
25
+ if self.metavar is not None:
26
+ return self.metavar
27
+ var = (self.name or "").upper()
28
+ if not self.required:
29
+ var = f"[{var}]"
30
+ type_var = _call_get_metavar(self.type, self)
31
+ if type_var:
32
+ var += f":{type_var}"
33
+ if self.nargs != 1:
34
+ var += "..."
35
+ return var
36
+
37
+ def _patched_option_make_metavar(self, ctx=None) -> str:
38
+ if self.metavar is not None:
39
+ return self.metavar
40
+ metavar = _call_get_metavar(self.type, self)
41
+ if not metavar:
42
+ name = getattr(self.type, "name", "") or ""
43
+ metavar = name.upper()
44
+ if self.nargs != 1:
45
+ metavar += "..."
46
+ return metavar
47
+
48
+ TyperArgument.make_metavar = _patched_argument_make_metavar # type: ignore[assignment]
49
+ TyperOption.make_metavar = _patched_option_make_metavar # type: ignore[assignment]
synth_ai/cli/balance.py CHANGED
@@ -6,7 +6,6 @@ CLI: check remaining credit balance from Synth backend.
6
6
  from __future__ import annotations
7
7
 
8
8
  import os
9
- from urllib.parse import urlparse
10
9
 
11
10
  import click
12
11
  import requests
@@ -15,7 +14,7 @@ from rich import box
15
14
  from rich.console import Console
16
15
  from rich.table import Table
17
16
 
18
- from synth_ai.config.base_url import get_backend_from_env, PROD_BASE_URL_DEFAULT
17
+ from synth_ai.config.base_url import PROD_BASE_URL_DEFAULT, get_backend_from_env
19
18
 
20
19
  PROD_BACKEND_BASE = f"{PROD_BASE_URL_DEFAULT.rstrip('/')}/api/v1"
21
20
 
synth_ai/cli/calc.py CHANGED
@@ -30,7 +30,7 @@ def _safe_eval(expr: str) -> float:
30
30
  def _eval(n):
31
31
  if isinstance(n, ast.Expression):
32
32
  return _eval(n.body)
33
- if isinstance(n, ast.Num): # 3.8 and earlier
33
+ if hasattr(ast, "Num") and isinstance(n, ast.Num): # 3.8 and earlier
34
34
  return n.n
35
35
  if isinstance(n, ast.Constant): # 3.8+
36
36
  if isinstance(n.value, int | float):
synth_ai/cli/demo.py CHANGED
@@ -101,7 +101,8 @@ def register(cli):
101
101
  # (prepare command removed; configure now prepares baseline TOML)
102
102
 
103
103
  # Help pyright understand dynamic Click group attributes
104
- from typing import Any, cast as _cast
104
+ from typing import Any
105
+ from typing import cast as _cast
105
106
 
106
107
  _dg = _cast(Any, demo)
107
108
 
synth_ai/cli/recent.py CHANGED
@@ -34,11 +34,11 @@ def _fmt_time(v) -> str:
34
34
 
35
35
 
36
36
  async def _fetch_recent(db_url: str, hours: float):
37
- from synth_ai.tracing_v3.turso.manager import AsyncSQLTraceManager
37
+ from synth_ai.tracing_v3.storage.factory import create_storage, StorageConfig
38
38
 
39
39
  start_time = datetime.now() - timedelta(hours=hours)
40
40
 
41
- db = AsyncSQLTraceManager(db_url)
41
+ db = create_storage(StorageConfig(connection_string=db_url))
42
42
  await db.initialize()
43
43
  try:
44
44
  query = """
synth_ai/cli/rl_demo.py CHANGED
@@ -37,7 +37,8 @@ def register(cli):
37
37
  """RL Demo commands (separate from legacy demo)."""
38
38
 
39
39
  # Help pyright understand dynamic Click group attributes
40
- from typing import Any, cast as _cast
40
+ from typing import Any
41
+ from typing import cast as _cast
41
42
 
42
43
  _rlg = _cast(Any, rl_demo)
43
44
 
synth_ai/cli/root.py CHANGED
@@ -5,6 +5,7 @@ Canonical CLI entrypoint for Synth AI (moved from synth_ai/cli.py).
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ import contextlib
8
9
  import logging
9
10
  import os
10
11
  import shutil
@@ -18,7 +19,8 @@ import time
18
19
  import click
19
20
 
20
21
  try:
21
- from importlib.metadata import PackageNotFoundError, version as _pkg_version
22
+ from importlib.metadata import PackageNotFoundError
23
+ from importlib.metadata import version as _pkg_version
22
24
 
23
25
  try:
24
26
  __pkg_version__ = _pkg_version("synth-ai")
@@ -91,9 +93,8 @@ def install_sqld() -> str:
91
93
 
92
94
  click.echo("📥 Downloading sqld via 'turso dev' (this may take a few seconds)…")
93
95
 
94
- temp_db = tempfile.NamedTemporaryFile(prefix="synth_sqld_", suffix=".db", delete=False)
95
- temp_db_path = temp_db.name
96
- temp_db.close()
96
+ with tempfile.NamedTemporaryFile(prefix="synth_sqld_", suffix=".db", delete=False) as temp_db:
97
+ temp_db_path = temp_db.name
97
98
 
98
99
  env = os.environ.copy()
99
100
  env.setdefault("TURSO_NONINTERACTIVE", "1")
@@ -129,15 +130,12 @@ def install_sqld() -> str:
129
130
  proc.kill()
130
131
  stdout_data, stderr_data = proc.communicate()
131
132
  finally:
132
- if proc and proc.returncode not in (0, None):
133
- if stdout_data or stderr_data:
134
- logging.getLogger(__name__).debug(
135
- "turso dev stdout: %s\nstderr: %s", stdout_data, stderr_data
136
- )
137
- try:
133
+ if proc and proc.returncode not in (0, None) and (stdout_data or stderr_data):
134
+ logging.getLogger(__name__).debug(
135
+ "turso dev stdout: %s\nstderr: %s", stdout_data, stderr_data
136
+ )
137
+ with contextlib.suppress(OSError):
138
138
  os.unlink(temp_db_path)
139
- except OSError:
140
- pass
141
139
 
142
140
  sqld_path = find_sqld_binary()
143
141
  if sqld_path:
@@ -171,7 +169,7 @@ def _forward_to_demo(args: list[str]) -> None:
171
169
  except Exception as e: # pragma: no cover
172
170
  click.echo(f"Failed to import demo CLI: {e}")
173
171
  sys.exit(1)
174
- rc = int(getattr(demo_cli, "main")(args) or 0) # type: ignore[attr-defined]
172
+ rc = int(demo_cli.main(args) or 0) # type: ignore[attr-defined]
175
173
  if rc != 0:
176
174
  sys.exit(rc)
177
175
 
synth_ai/cli/status.py CHANGED
@@ -14,9 +14,9 @@ from rich.table import Table
14
14
 
15
15
 
16
16
  async def _db_stats(db_url: str) -> dict:
17
- from synth_ai.tracing_v3.turso.manager import AsyncSQLTraceManager
17
+ from synth_ai.tracing_v3.storage.factory import StorageConfig, create_storage
18
18
 
19
- db = AsyncSQLTraceManager(db_url)
19
+ db = create_storage(StorageConfig(connection_string=db_url))
20
20
  await db.initialize()
21
21
  try:
22
22
  out: dict = {}