synth-ai 0.2.9.dev7__py3-none-any.whl → 0.2.10__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 (323) hide show
  1. examples/__init__.py +16 -0
  2. examples/crafter_debug_render.py +8 -11
  3. examples/dev/qwen3_32b_qlora_4xh100.toml +40 -0
  4. examples/multi_step/crafter_rl_lora.md +29 -0
  5. examples/qwen_coder/README.md +102 -0
  6. examples/qwen_coder/_shared.py +113 -0
  7. examples/qwen_coder/configs/coder_lora_30b.toml +61 -0
  8. examples/qwen_coder/configs/coder_lora_4b.toml +57 -0
  9. examples/qwen_coder/configs/coder_lora_small.toml +58 -0
  10. examples/qwen_coder/generate_dataset.py +98 -0
  11. examples/qwen_coder/infer_ft_smoke.py +65 -0
  12. examples/qwen_coder/infer_prod_proxy.py +73 -0
  13. examples/qwen_coder/infer_via_synth.py +87 -0
  14. examples/qwen_coder/scripts/infer_coder.sh +19 -0
  15. examples/qwen_coder/scripts/train_coder_30b.sh +22 -0
  16. examples/qwen_coder/sft_full_17b.py +103 -0
  17. examples/qwen_coder/sft_lora_30b.py +110 -0
  18. examples/qwen_coder/subset_jsonl.py +39 -0
  19. examples/qwen_coder/todos.md +38 -0
  20. examples/qwen_coder/validate_jsonl.py +60 -0
  21. examples/rl/run_eval.py +36 -37
  22. examples/rl/run_rl_and_save.py +5 -5
  23. examples/rl/task_app/math_single_step.py +65 -43
  24. examples/rl/task_app/math_task_app.py +3 -3
  25. examples/sft/README.md +139 -0
  26. examples/sft/configs/crafter_fft_qwen0p6b.toml +44 -0
  27. examples/sft/configs/crafter_lora_qwen0p6b.toml +45 -0
  28. examples/sft/evaluate.py +117 -0
  29. examples/sft/export_dataset.py +117 -0
  30. examples/sft/generate_traces.py +162 -0
  31. examples/swe/__init__.py +12 -0
  32. examples/swe/task_app/README.md +105 -0
  33. examples/swe/task_app/__init__.py +2 -0
  34. examples/swe/task_app/grpo_swe_mini.py +571 -0
  35. examples/swe/task_app/grpo_swe_mini_task_app.py +136 -0
  36. examples/swe/task_app/hosted/README.md +173 -0
  37. examples/swe/task_app/hosted/__init__.py +5 -0
  38. examples/swe/task_app/hosted/branching.py +143 -0
  39. examples/swe/task_app/hosted/environment_routes.py +1289 -0
  40. examples/swe/task_app/hosted/envs/__init__.py +1 -0
  41. examples/swe/task_app/hosted/envs/crafter/__init__.py +6 -0
  42. examples/swe/task_app/hosted/envs/crafter/app.py +1 -0
  43. examples/swe/task_app/hosted/envs/crafter/environment.py +522 -0
  44. examples/swe/task_app/hosted/envs/crafter/policy.py +478 -0
  45. examples/swe/task_app/hosted/envs/crafter/react_agent.py +108 -0
  46. examples/swe/task_app/hosted/envs/crafter/shared.py +305 -0
  47. examples/swe/task_app/hosted/envs/crafter/tools.py +47 -0
  48. examples/swe/task_app/hosted/envs/mini_swe/__init__.py +8 -0
  49. examples/swe/task_app/hosted/envs/mini_swe/environment.py +1164 -0
  50. examples/swe/task_app/hosted/envs/mini_swe/policy.py +355 -0
  51. examples/swe/task_app/hosted/envs/mini_swe/shared.py +83 -0
  52. examples/swe/task_app/hosted/envs/mini_swe/tools.py +96 -0
  53. examples/swe/task_app/hosted/hosted_app.py +204 -0
  54. examples/swe/task_app/hosted/inference/__init__.py +5 -0
  55. examples/swe/task_app/hosted/inference/openai_client.py +618 -0
  56. examples/swe/task_app/hosted/main.py +100 -0
  57. examples/swe/task_app/hosted/policy_routes.py +1079 -0
  58. examples/swe/task_app/hosted/registry.py +195 -0
  59. examples/swe/task_app/hosted/rollout.py +1869 -0
  60. examples/swe/task_app/hosted/storage/__init__.py +5 -0
  61. examples/swe/task_app/hosted/storage/volume.py +211 -0
  62. examples/swe/task_app/hosted/test_agents.py +161 -0
  63. examples/swe/task_app/hosted/test_service.py +137 -0
  64. examples/swe/task_app/hosted/utils.py +62 -0
  65. examples/vlm/PROPOSAL.md +53 -0
  66. examples/vlm/README.md +68 -0
  67. examples/vlm/configs/crafter_vlm_gpt4o.toml +44 -0
  68. examples/vlm/crafter_image_only_agent.py +207 -0
  69. examples/vlm/crafter_openai_vlm_agent.py +277 -0
  70. examples/vlm/filter_image_rows.py +63 -0
  71. examples/vlm/run_crafter_vlm_benchmark.py +316 -0
  72. examples/warming_up_to_rl/analyze_trace_db.py +5 -5
  73. examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +11 -1
  74. examples/warming_up_to_rl/export_trace_sft.py +78 -21
  75. examples/warming_up_to_rl/groq_test.py +4 -4
  76. examples/warming_up_to_rl/manage_secrets.py +13 -18
  77. examples/warming_up_to_rl/run_eval.py +42 -44
  78. examples/warming_up_to_rl/run_fft_and_save.py +11 -16
  79. examples/warming_up_to_rl/run_local_rollout.py +1 -3
  80. examples/warming_up_to_rl/run_local_rollout_modal.py +2 -4
  81. examples/warming_up_to_rl/run_local_rollout_parallel.py +1 -4
  82. examples/warming_up_to_rl/run_local_rollout_traced.py +3 -5
  83. examples/warming_up_to_rl/run_rl_and_save.py +5 -6
  84. examples/warming_up_to_rl/run_rollout_remote.py +8 -10
  85. examples/warming_up_to_rl/task_app/README.md +6 -2
  86. examples/warming_up_to_rl/task_app/grpo_crafter.py +234 -35
  87. examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +2 -3
  88. examples/warming_up_to_rl/task_app/synth_envs_hosted/__init__.py +1 -1
  89. examples/warming_up_to_rl/task_app/synth_envs_hosted/branching.py +9 -11
  90. examples/warming_up_to_rl/task_app/synth_envs_hosted/environment_routes.py +131 -114
  91. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/environment.py +101 -41
  92. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +73 -51
  93. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +14 -6
  94. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/shared.py +16 -16
  95. examples/warming_up_to_rl/task_app/synth_envs_hosted/hosted_app.py +32 -34
  96. examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +94 -31
  97. examples/warming_up_to_rl/task_app/synth_envs_hosted/main.py +0 -2
  98. examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +303 -203
  99. examples/warming_up_to_rl/task_app/synth_envs_hosted/registry.py +21 -23
  100. examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +328 -225
  101. examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/volume.py +13 -13
  102. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_agents.py +1 -0
  103. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_service.py +1 -0
  104. examples/warming_up_to_rl/task_app/synth_envs_hosted/utils.py +4 -3
  105. synth_ai/api/models/supported.py +376 -0
  106. synth_ai/api/train/builders.py +128 -21
  107. synth_ai/api/train/cli.py +80 -64
  108. synth_ai/api/train/config_finder.py +7 -2
  109. synth_ai/api/train/env_resolver.py +1 -1
  110. synth_ai/api/train/pollers.py +2 -1
  111. synth_ai/api/train/supported_algos.py +139 -0
  112. synth_ai/api/train/task_app.py +1 -2
  113. synth_ai/api/train/utils.py +13 -44
  114. synth_ai/cli/__init__.py +8 -0
  115. synth_ai/cli/_modal_wrapper.py +28 -0
  116. synth_ai/cli/_typer_patch.py +49 -0
  117. synth_ai/cli/balance.py +1 -2
  118. synth_ai/cli/calc.py +1 -1
  119. synth_ai/cli/demo.py +2 -1
  120. synth_ai/cli/recent.py +2 -2
  121. synth_ai/cli/rl_demo.py +2 -1
  122. synth_ai/cli/root.py +11 -13
  123. synth_ai/cli/status.py +2 -2
  124. synth_ai/cli/task_apps.py +529 -179
  125. synth_ai/cli/traces.py +6 -4
  126. synth_ai/cli/watch.py +12 -18
  127. synth_ai/demo_registry.py +1 -1
  128. synth_ai/demos/core/cli.py +36 -43
  129. synth_ai/demos/demo_task_apps/__init__.py +3 -3
  130. synth_ai/demos/demo_task_apps/core.py +17 -25
  131. synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +3 -4
  132. synth_ai/demos/demo_task_apps/math/app.py +2 -1
  133. synth_ai/demos/demo_task_apps/math/deploy_modal.py +3 -4
  134. synth_ai/demos/demo_task_apps/math/modal_task_app.py +16 -18
  135. synth_ai/demos/demo_task_apps/math/task_app_entry.py +0 -1
  136. synth_ai/environments/examples/crafter_classic/environment.py +76 -1
  137. synth_ai/environments/reproducibility/tree.py +2 -5
  138. synth_ai/environments/service/app.py +11 -12
  139. synth_ai/environments/service/core_routes.py +4 -7
  140. synth_ai/environments/stateful/engine.py +1 -1
  141. synth_ai/environments/tasks/core.py +1 -0
  142. synth_ai/environments/tasks/filters.py +5 -6
  143. synth_ai/environments/tasks/utils.py +4 -5
  144. synth_ai/handshake.py +9 -9
  145. synth_ai/http.py +1 -1
  146. synth_ai/http_client.py +18 -10
  147. synth_ai/inference/client.py +15 -5
  148. synth_ai/jobs/client.py +78 -83
  149. synth_ai/learning/__init__.py +41 -6
  150. synth_ai/learning/algorithms.py +14 -0
  151. synth_ai/learning/client.py +91 -24
  152. synth_ai/learning/config.py +2 -38
  153. synth_ai/learning/ft_client.py +4 -59
  154. synth_ai/learning/health.py +5 -6
  155. synth_ai/learning/jobs.py +31 -47
  156. synth_ai/{rl → learning/rl}/__init__.py +14 -4
  157. synth_ai/learning/rl/client.py +267 -0
  158. synth_ai/learning/rl/config.py +31 -0
  159. synth_ai/{rl → learning/rl}/contracts.py +5 -8
  160. synth_ai/{rl → learning/rl}/env_keys.py +39 -15
  161. synth_ai/learning/rl/secrets.py +13 -0
  162. synth_ai/learning/rl_client.py +2 -281
  163. synth_ai/learning/sft/__init__.py +29 -0
  164. synth_ai/learning/sft/client.py +68 -0
  165. synth_ai/learning/sft/config.py +270 -0
  166. synth_ai/learning/sft/data.py +295 -0
  167. synth_ai/learning/sse.py +25 -24
  168. synth_ai/learning/validators.py +25 -28
  169. synth_ai/lm/__init__.py +21 -47
  170. synth_ai/task/__init__.py +25 -27
  171. synth_ai/task/apps/__init__.py +7 -8
  172. synth_ai/task/auth.py +8 -8
  173. synth_ai/task/client.py +14 -14
  174. synth_ai/task/contracts.py +36 -35
  175. synth_ai/task/datasets.py +6 -5
  176. synth_ai/task/errors.py +10 -10
  177. synth_ai/task/health.py +17 -9
  178. synth_ai/task/json.py +58 -23
  179. synth_ai/task/proxy.py +13 -9
  180. synth_ai/task/rubrics.py +16 -15
  181. synth_ai/task/server.py +12 -12
  182. synth_ai/task/tracing_utils.py +4 -4
  183. synth_ai/task/vendors.py +5 -6
  184. synth_ai/tracing_v3/__init__.py +2 -0
  185. synth_ai/tracing_v3/abstractions.py +21 -4
  186. synth_ai/tracing_v3/decorators.py +18 -16
  187. synth_ai/tracing_v3/hooks.py +5 -5
  188. synth_ai/tracing_v3/llm_call_record_helpers.py +6 -6
  189. synth_ai/tracing_v3/session_tracer.py +40 -14
  190. synth_ai/tracing_v3/storage/base.py +85 -0
  191. synth_ai/tracing_v3/storage/config.py +21 -8
  192. synth_ai/tracing_v3/storage/factory.py +10 -7
  193. synth_ai/tracing_v3/storage/utils.py +4 -2
  194. synth_ai/tracing_v3/turso/daemon.py +7 -2
  195. synth_ai/tracing_v3/turso/models.py +2 -2
  196. synth_ai/tracing_v3/turso/native_manager.py +1173 -0
  197. synth_ai/tracing_v3/utils.py +4 -4
  198. synth_ai/v0/api/__init__.py +8 -0
  199. synth_ai/v0/api/models/__init__.py +8 -0
  200. synth_ai/v0/api/models/supported.py +8 -0
  201. synth_ai/v0/config/__init__.py +15 -0
  202. synth_ai/v0/config/base_url.py +12 -0
  203. synth_ai/v0/lm/__init__.py +51 -0
  204. synth_ai/{lm → v0/lm}/caching/ephemeral.py +2 -2
  205. synth_ai/{lm → v0/lm}/caching/handler.py +4 -4
  206. synth_ai/{lm → v0/lm}/caching/initialize.py +1 -1
  207. synth_ai/{lm → v0/lm}/caching/persistent.py +1 -1
  208. synth_ai/{lm → v0/lm}/config.py +6 -1
  209. synth_ai/{lm → v0/lm}/core/all.py +9 -9
  210. synth_ai/{lm → v0/lm}/core/main.py +6 -6
  211. synth_ai/{lm → v0/lm}/core/main_v3.py +10 -10
  212. synth_ai/{lm → v0/lm}/core/synth_models.py +2 -14
  213. synth_ai/{lm → v0/lm}/core/vendor_clients.py +2 -2
  214. synth_ai/{lm → v0/lm}/overrides.py +2 -2
  215. synth_ai/{lm → v0/lm}/provider_support/anthropic.py +4 -4
  216. synth_ai/{lm → v0/lm}/provider_support/openai.py +5 -5
  217. synth_ai/{lm → v0/lm}/structured_outputs/handler.py +5 -5
  218. synth_ai/{lm → v0/lm}/structured_outputs/rehabilitate.py +1 -1
  219. synth_ai/{lm → v0/lm}/vendors/core/anthropic_api.py +9 -9
  220. synth_ai/{lm → v0/lm}/vendors/core/gemini_api.py +5 -5
  221. synth_ai/{lm → v0/lm}/vendors/core/mistral_api.py +5 -5
  222. synth_ai/{lm → v0/lm}/vendors/core/openai_api.py +10 -10
  223. synth_ai/{lm → v0/lm}/vendors/openai_standard.py +8 -8
  224. synth_ai/{lm → v0/lm}/vendors/openai_standard_responses.py +2 -2
  225. synth_ai/{lm → v0/lm}/vendors/supported/custom_endpoint.py +3 -3
  226. synth_ai/{lm → v0/lm}/vendors/supported/deepseek.py +2 -2
  227. synth_ai/{lm → v0/lm}/vendors/supported/grok.py +2 -2
  228. synth_ai/{lm → v0/lm}/vendors/supported/groq.py +1 -1
  229. synth_ai/{lm → v0/lm}/vendors/supported/ollama.py +1 -1
  230. synth_ai/{lm → v0/lm}/vendors/supported/openrouter.py +3 -3
  231. synth_ai/{lm → v0/lm}/vendors/supported/together.py +1 -1
  232. synth_ai/{lm → v0/lm}/vendors/synth_client.py +1 -1
  233. synth_ai/v0/tracing_v3/__init__.py +10 -0
  234. synth_ai/v0/tracing_v3/abstractions.py +3 -0
  235. synth_ai/v0/tracing_v3/decorators.py +3 -0
  236. synth_ai/v0/tracing_v3/llm_call_record_helpers.py +3 -0
  237. synth_ai/v0/tracing_v3/session_tracer.py +3 -0
  238. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.10.dist-info}/METADATA +10 -7
  239. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.10.dist-info}/RECORD +269 -233
  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. synth_ai/experimental/synth_oss.py +0 -445
  273. synth_ai/learning/filtering.py +0 -0
  274. synth_ai/learning/offline/dpo.py +0 -0
  275. synth_ai/learning/offline/providers.py +0 -7
  276. synth_ai/learning/offline/sft.py +0 -0
  277. synth_ai/learning/offline/shared.py +0 -0
  278. synth_ai/learning/online/grpo.py +0 -0
  279. synth_ai/learning/online/irft.py +0 -0
  280. synth_ai/learning/prompts/banking77_injection_eval.py +0 -168
  281. synth_ai/learning/prompts/gepa.py +0 -0
  282. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +0 -211
  283. synth_ai/learning/prompts/mipro.py +0 -289
  284. synth_ai/learning/prompts/random_search.py +0 -249
  285. synth_ai/learning/prompts/run_mipro_banking77.py +0 -172
  286. synth_ai/learning/prompts/run_random_search_banking77.py +0 -329
  287. synth_ai/rl/secrets.py +0 -19
  288. synth_ai/scripts/verify_rewards.py +0 -100
  289. synth_ai/tracing/__init__.py +0 -30
  290. synth_ai/tracing_v1/__init__.py +0 -33
  291. synth_ai/tracing_v3/turso/__init__.py +0 -25
  292. synth_ai/tracing_v3/turso/manager.py +0 -838
  293. synth_ai/zyk/__init__.py +0 -30
  294. /synth_ai/{lm → v0/lm}/caching/__init__.py +0 -0
  295. /synth_ai/{lm → v0/lm}/caching/constants.py +0 -0
  296. /synth_ai/{lm → v0/lm}/caching/dbs.py +0 -0
  297. /synth_ai/{lm → v0/lm}/constants.py +0 -0
  298. /synth_ai/{lm → v0/lm}/core/__init__.py +0 -0
  299. /synth_ai/{lm → v0/lm}/core/exceptions.py +0 -0
  300. /synth_ai/{lm → v0/lm}/cost/__init__.py +0 -0
  301. /synth_ai/{lm → v0/lm}/cost/monitor.py +0 -0
  302. /synth_ai/{lm → v0/lm}/cost/statefulness.py +0 -0
  303. /synth_ai/{lm → v0/lm}/injection.py +0 -0
  304. /synth_ai/{lm → v0/lm}/provider_support/__init__.py +0 -0
  305. /synth_ai/{lm → v0/lm}/provider_support/suppress_logging.py +0 -0
  306. /synth_ai/{lm → v0/lm}/structured_outputs/__init__.py +0 -0
  307. /synth_ai/{lm → v0/lm}/structured_outputs/inject.py +0 -0
  308. /synth_ai/{lm → v0/lm}/tools/__init__.py +0 -0
  309. /synth_ai/{lm → v0/lm}/tools/base.py +0 -0
  310. /synth_ai/{lm → v0/lm}/unified_interface.py +0 -0
  311. /synth_ai/{lm → v0/lm}/vendors/__init__.py +0 -0
  312. /synth_ai/{lm → v0/lm}/vendors/base.py +0 -0
  313. /synth_ai/{lm → v0/lm}/vendors/core/__init__.py +0 -0
  314. /synth_ai/{lm → v0/lm}/vendors/core/synth_dev_api.py +0 -0
  315. /synth_ai/{lm → v0/lm}/vendors/local/__init__.py +0 -0
  316. /synth_ai/{lm → v0/lm}/vendors/local/ollama.py +0 -0
  317. /synth_ai/{lm → v0/lm}/vendors/retries.py +0 -0
  318. /synth_ai/{lm → v0/lm}/vendors/supported/__init__.py +0 -0
  319. /synth_ai/{lm → v0/lm}/warmup.py +0 -0
  320. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.10.dist-info}/WHEEL +0 -0
  321. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.10.dist-info}/entry_points.txt +0 -0
  322. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.10.dist-info}/licenses/LICENSE +0 -0
  323. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.10.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,7 @@ import tarfile
8
8
  import tempfile
9
9
  from datetime import datetime
10
10
  from pathlib import Path
11
- from typing import Any, Dict, Optional
11
+ from typing import Any
12
12
 
13
13
 
14
14
  class VolumeStorage:
@@ -57,8 +57,8 @@ class VolumeStorage:
57
57
 
58
58
  def create_archive(
59
59
  self,
60
- state_dict: Dict[str, Any],
61
- meta: Dict[str, Any],
60
+ state_dict: dict[str, Any],
61
+ meta: dict[str, Any],
62
62
  ) -> bytes:
63
63
  """Create a tar.gz archive with state and metadata."""
64
64
  with tempfile.TemporaryDirectory() as tmpdir:
@@ -88,7 +88,7 @@ class VolumeStorage:
88
88
 
89
89
  return compressed
90
90
 
91
- def extract_archive(self, archive_bytes: bytes) -> tuple[Dict[str, Any], Dict[str, Any]]:
91
+ def extract_archive(self, archive_bytes: bytes) -> tuple[dict[str, Any], dict[str, Any]]:
92
92
  """Extract state and metadata from a tar.gz archive."""
93
93
  # Decompress
94
94
  tar_bytes = gzip.decompress(archive_bytes)
@@ -106,10 +106,10 @@ class VolumeStorage:
106
106
  tar.extractall(tmppath)
107
107
 
108
108
  # Read state and meta
109
- with open(tmppath / "state.json", "r") as f:
109
+ with open(tmppath / "state.json") as f:
110
110
  state = json.load(f)
111
111
 
112
- with open(tmppath / "meta.json", "r") as f:
112
+ with open(tmppath / "meta.json") as f:
113
113
  meta = json.load(f)
114
114
 
115
115
  return state, meta
@@ -122,9 +122,9 @@ class VolumeStorage:
122
122
  self,
123
123
  rl_run_id: str,
124
124
  kind: str,
125
- state_dict: Dict[str, Any],
126
- config: Optional[Dict[str, Any]] = None,
127
- parent_snapshot_id: Optional[str] = None,
125
+ state_dict: dict[str, Any],
126
+ config: dict[str, Any] | None = None,
127
+ parent_snapshot_id: str | None = None,
128
128
  ) -> tuple[str, str, int]:
129
129
  """Save a snapshot and return (snapshot_id, path, size)."""
130
130
  # Build metadata
@@ -166,7 +166,7 @@ class VolumeStorage:
166
166
  rl_run_id: str,
167
167
  kind: str,
168
168
  snapshot_id: str,
169
- ) -> tuple[Dict[str, Any], Dict[str, Any]]:
169
+ ) -> tuple[dict[str, Any], dict[str, Any]]:
170
170
  """Load a snapshot and return (state_dict, meta)."""
171
171
  path = self.get_snapshot_path(rl_run_id, kind, snapshot_id)
172
172
 
@@ -182,7 +182,7 @@ class VolumeStorage:
182
182
  def append_to_index(
183
183
  self,
184
184
  rl_run_id: str,
185
- meta: Dict[str, Any],
185
+ meta: dict[str, Any],
186
186
  ) -> None:
187
187
  """Append metadata to the run's index file."""
188
188
  index_path = self.get_index_path(rl_run_id)
@@ -191,7 +191,7 @@ class VolumeStorage:
191
191
  with open(index_path, "a") as f:
192
192
  f.write(json.dumps(meta) + "\n")
193
193
 
194
- def read_index(self, rl_run_id: str) -> list[Dict[str, Any]]:
194
+ def read_index(self, rl_run_id: str) -> list[dict[str, Any]]:
195
195
  """Read all entries from a run's index file."""
196
196
  index_path = self.get_index_path(rl_run_id)
197
197
 
@@ -199,7 +199,7 @@ class VolumeStorage:
199
199
  return []
200
200
 
201
201
  entries = []
202
- with open(index_path, "r") as f:
202
+ with open(index_path) as f:
203
203
  for line in f:
204
204
  if line.strip():
205
205
  entries.append(json.loads(line))
@@ -16,6 +16,7 @@ This script will:
16
16
 
17
17
  import asyncio
18
18
  import os
19
+
19
20
  import httpx
20
21
 
21
22
  BASE_URL = os.environ.get("SYNTH_ENVS_HOSTED_URL", "http://localhost:8000")
@@ -8,6 +8,7 @@ Run this after starting the service with:
8
8
 
9
9
  import asyncio
10
10
  import json
11
+
11
12
  import httpx
12
13
 
13
14
 
@@ -1,7 +1,8 @@
1
1
  """Utility functions for the task service."""
2
2
 
3
+ from typing import Any
4
+
3
5
  import numpy as np
4
- from typing import Any, Dict, List, Union
5
6
 
6
7
 
7
8
  def convert_numpy_to_python(obj: Any) -> Any:
@@ -22,13 +23,13 @@ def convert_numpy_to_python(obj: Any) -> Any:
22
23
  return obj.tolist()
23
24
  elif isinstance(obj, dict):
24
25
  return {key: convert_numpy_to_python(value) for key, value in obj.items()}
25
- elif isinstance(obj, (list, tuple)):
26
+ elif isinstance(obj, list | tuple):
26
27
  return [convert_numpy_to_python(item) for item in obj]
27
28
  else:
28
29
  return obj
29
30
 
30
31
 
31
- def sanitize_observation(observation: Dict[str, Any]) -> Dict[str, Any]:
32
+ def sanitize_observation(observation: dict[str, Any]) -> dict[str, Any]:
32
33
  """
33
34
  Sanitize observation data for JSON serialization.
34
35
 
@@ -0,0 +1,376 @@
1
+ """Catalog of Synth-hosted base models and helpers (core vs experimental)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import warnings
7
+ from collections.abc import Iterable, Iterator, Sequence
8
+ from dataclasses import dataclass
9
+
10
+ # ------------------------------------------------------------------------------
11
+ # Model families
12
+ # ------------------------------------------------------------------------------
13
+
14
+ QWEN3_MODELS: list[str] = [
15
+ # Core Qwen3 base models
16
+ "Qwen/Qwen3-0.6B",
17
+ "Qwen/Qwen3-1.7B",
18
+ "Qwen/Qwen3-4B",
19
+ "Qwen/Qwen3-8B",
20
+ "Qwen/Qwen3-14B",
21
+ "Qwen/Qwen3-30B-A3B",
22
+ "Qwen/Qwen3-32B",
23
+ # Include 4B-2507 and Thinking variants used in RL
24
+ "Qwen/Qwen3-4B-Thinking-2507",
25
+ "Qwen/Qwen3-30B-A3B-Thinking-2507",
26
+ "Qwen/Qwen3-235B-A22B-Thinking-2507",
27
+ ]
28
+
29
+ # Qwen3 Coder family (backend-supported); text-only, SFT/inference
30
+ QWEN3_CODER_MODELS: list[str] = [
31
+ # Instruct variants used for coding tasks
32
+ "Qwen/Qwen3-Coder-30B-A3B-Instruct",
33
+ "Qwen/Qwen3-Coder-480B-A35B-Instruct",
34
+ ]
35
+
36
+ # Training support sets
37
+ RL_SUPPORTED_MODELS: frozenset[str] = frozenset(
38
+ {
39
+ "Qwen/Qwen3-1.7B",
40
+ "Qwen/Qwen3-4B",
41
+ "Qwen/Qwen3-4B-Thinking-2507",
42
+ "Qwen/Qwen3-8B",
43
+ "Qwen/Qwen3-14B",
44
+ "Qwen/Qwen3-30B-A3B",
45
+ "Qwen/Qwen3-30B-A3B-Thinking-2507",
46
+ }
47
+ )
48
+
49
+ # SFT allowlist includes core Qwen3 plus Coder family
50
+ SFT_SUPPORTED_MODELS: frozenset[str] = frozenset([*QWEN3_MODELS, *QWEN3_CODER_MODELS])
51
+
52
+ # ------------------------------------------------------------------------------
53
+ # Lifecycle classification (core vs experimental)
54
+ # ------------------------------------------------------------------------------
55
+
56
+ # Which base models are considered "experimental" by default.
57
+ _EXPERIMENTAL_DEFAULTS: frozenset[str] = frozenset(
58
+ {
59
+ # Larger (>= 64B) or bleeding-edge variants are experimental by default.
60
+ "Qwen/Qwen3-235B-A22B-Thinking-2507",
61
+ "Qwen/Qwen3-Coder-480B-A35B-Instruct",
62
+ # Thinking variants can fluctuate more rapidly.
63
+ "Qwen/Qwen3-30B-A3B-Thinking-2507",
64
+ "Qwen/Qwen3-4B-Thinking-2507",
65
+ }
66
+ )
67
+
68
+
69
+ def _parse_experimental_env() -> frozenset[str]:
70
+ raw = os.getenv("SDK_EXPERIMENTAL_MODELS", "").strip()
71
+ if not raw:
72
+ return frozenset()
73
+ return frozenset(s.strip() for s in raw.split(",") if s.strip())
74
+
75
+
76
+ # Final experimental set (defaults ∪ optional env override)
77
+ EXPERIMENTAL_MODELS: frozenset[str] = frozenset(_EXPERIMENTAL_DEFAULTS | _parse_experimental_env())
78
+
79
+ # Build catalog entries for both core and coder families under unified "Qwen3"
80
+ _ALL_QWEN3_IDS: list[str] = [*QWEN3_MODELS, *QWEN3_CODER_MODELS]
81
+
82
+ CORE_MODELS: frozenset[str] = frozenset(m for m in _ALL_QWEN3_IDS if m not in EXPERIMENTAL_MODELS)
83
+
84
+ # ------------------------------------------------------------------------------
85
+ # Experimental gating / warnings
86
+ # ------------------------------------------------------------------------------
87
+
88
+
89
+ class ExperimentalWarning(UserWarning):
90
+ """Warning for usage of experimental SDK models/APIs."""
91
+
92
+
93
+ def _experimental_enabled() -> bool:
94
+ # Global toggle to permit experimental usage
95
+ return os.getenv("SDK_EXPERIMENTAL", "0") == "1"
96
+
97
+
98
+ def _warn_if_experimental(model_id: str) -> None:
99
+ if model_id in EXPERIMENTAL_MODELS:
100
+ warnings.warn(
101
+ f"Model '{model_id}' is experimental and may change or be removed.",
102
+ category=ExperimentalWarning,
103
+ stacklevel=2,
104
+ )
105
+
106
+
107
+ # ------------------------------------------------------------------------------
108
+ # Model metadata + catalog
109
+ # ------------------------------------------------------------------------------
110
+
111
+
112
+ @dataclass(frozen=True, slots=True)
113
+ class SupportedModel:
114
+ """Metadata describing a supported base model."""
115
+
116
+ model_id: str
117
+ family: str
118
+ provider: str
119
+ modalities: tuple[str, ...] = ()
120
+ training_modes: tuple[str, ...] = ()
121
+ lifecycle: str = "core" # "core" | "experimental"
122
+
123
+ def as_dict(self) -> dict[str, object]:
124
+ data: dict[str, object] = {
125
+ "model_id": self.model_id,
126
+ "family": self.family,
127
+ "provider": self.provider,
128
+ "lifecycle": self.lifecycle,
129
+ }
130
+ if self.modalities:
131
+ data["modalities"] = list(self.modalities)
132
+ if self.training_modes:
133
+ data["training_modes"] = list(self.training_modes)
134
+ return data
135
+
136
+
137
+ SUPPORTED_MODELS: tuple[SupportedModel, ...] = tuple(
138
+ SupportedModel(
139
+ model_id=model,
140
+ family="Qwen3",
141
+ provider="Qwen",
142
+ modalities=("text",),
143
+ training_modes=tuple(
144
+ sorted(
145
+ {
146
+ *(("sft",) if model in SFT_SUPPORTED_MODELS else ()),
147
+ *(("rl",) if model in RL_SUPPORTED_MODELS else ()),
148
+ }
149
+ )
150
+ ),
151
+ lifecycle=("experimental" if model in EXPERIMENTAL_MODELS else "core"),
152
+ )
153
+ for model in _ALL_QWEN3_IDS
154
+ )
155
+
156
+ _BASE_LOOKUP = {model.model_id.lower(): model.model_id for model in SUPPORTED_MODELS}
157
+ SUPPORTED_BASE_MODEL_IDS: frozenset[str] = frozenset(_BASE_LOOKUP.values())
158
+ FINE_TUNED_PREFIXES: tuple[str, ...] = ("ft:", "fft:", "qft:", "rl:")
159
+ _MODEL_BY_ID = {model.model_id: model for model in SUPPORTED_MODELS}
160
+
161
+ # ------------------------------------------------------------------------------
162
+ # Public API
163
+ # ------------------------------------------------------------------------------
164
+
165
+
166
+ class UnsupportedModelError(ValueError):
167
+ """Raised when a model identifier is not supported by Synth."""
168
+
169
+
170
+ def _extract_base_model(candidate: str, *, allow_finetuned_prefixes: bool) -> str | None:
171
+ cleaned = candidate.strip()
172
+ lowered = cleaned.lower()
173
+ base = _BASE_LOOKUP.get(lowered)
174
+ if base:
175
+ return base
176
+ if not allow_finetuned_prefixes or ":" not in cleaned:
177
+ return None
178
+
179
+ segments = cleaned.split(":")
180
+ for segment in segments[1:]:
181
+ candidate_base = segment.strip()
182
+ if not candidate_base:
183
+ continue
184
+ base = _BASE_LOOKUP.get(candidate_base.lower())
185
+ if base:
186
+ return base
187
+ return None
188
+
189
+
190
+ def ensure_supported_model(
191
+ model_id: str,
192
+ *,
193
+ allow_finetuned_prefixes: bool = True,
194
+ ) -> str:
195
+ """Validate that *model_id* resolves to a supported base model (no lifecycle gate)."""
196
+ candidate = (model_id or "").strip()
197
+ if not candidate:
198
+ raise UnsupportedModelError("Model identifier is empty")
199
+
200
+ base = _extract_base_model(candidate, allow_finetuned_prefixes=allow_finetuned_prefixes)
201
+ if base:
202
+ return base
203
+
204
+ raise UnsupportedModelError(
205
+ f"Model '{candidate}' is not supported. Call supported_model_ids() for available base models."
206
+ )
207
+
208
+
209
+ def ensure_allowed_model(
210
+ model_id: str,
211
+ *,
212
+ allow_finetuned_prefixes: bool = True,
213
+ allow_experimental: bool | None = None,
214
+ ) -> str:
215
+ """Validate support + lifecycle; gate experimental unless enabled."""
216
+ base = ensure_supported_model(model_id, allow_finetuned_prefixes=allow_finetuned_prefixes)
217
+ is_exp = base in EXPERIMENTAL_MODELS
218
+ allow_exp = allow_experimental if allow_experimental is not None else _experimental_enabled()
219
+ if is_exp and not allow_exp:
220
+ raise UnsupportedModelError(
221
+ f"Model '{base}' is experimental and disabled. "
222
+ "Set SDK_EXPERIMENTAL=1 or pass allow_experimental=True."
223
+ )
224
+ if is_exp:
225
+ _warn_if_experimental(base)
226
+ return base
227
+
228
+
229
+ def normalize_model_identifier(
230
+ model_id: str,
231
+ *,
232
+ allow_finetuned_prefixes: bool = True,
233
+ ) -> str:
234
+ """Return a cleaned model identifier suitable for job payloads (no lifecycle gate)."""
235
+ canonical = ensure_supported_model(model_id, allow_finetuned_prefixes=allow_finetuned_prefixes)
236
+ cleaned = (model_id or "").strip()
237
+ if not cleaned:
238
+ return canonical
239
+ if cleaned.lower() in _BASE_LOOKUP:
240
+ return canonical
241
+ return cleaned
242
+
243
+
244
+ def is_supported_model(model_id: str, *, allow_finetuned_prefixes: bool = True) -> bool:
245
+ """Return True if *model_id* resolves to a supported base model (ignores lifecycle)."""
246
+ try:
247
+ ensure_supported_model(model_id, allow_finetuned_prefixes=allow_finetuned_prefixes)
248
+ except UnsupportedModelError:
249
+ return False
250
+ return True
251
+
252
+
253
+ def is_experimental_model(model_id: str) -> bool:
254
+ """Return True if *model_id* is marked experimental."""
255
+ try:
256
+ base = ensure_supported_model(model_id, allow_finetuned_prefixes=True)
257
+ except UnsupportedModelError:
258
+ return False
259
+ return base in EXPERIMENTAL_MODELS
260
+
261
+
262
+ def is_core_model(model_id: str) -> bool:
263
+ """Return True if *model_id* is marked core."""
264
+ try:
265
+ base = ensure_supported_model(model_id, allow_finetuned_prefixes=True)
266
+ except UnsupportedModelError:
267
+ return False
268
+ return base in CORE_MODELS
269
+
270
+
271
+ def iter_supported_models(
272
+ *,
273
+ families: Sequence[str] | None = None,
274
+ include: Sequence[str] | None = None,
275
+ exclude: Sequence[str] | None = None,
276
+ ) -> Iterator[SupportedModel]:
277
+ """Yield supported models, optionally filtered by family and lifecycle."""
278
+ include_set = {s.lower() for s in include} if include else None
279
+ exclude_set = {s.lower() for s in exclude} if exclude else None
280
+ fam_set = {f.lower() for f in families} if families else None
281
+
282
+ for m in SUPPORTED_MODELS:
283
+ if fam_set is not None and m.family.lower() not in fam_set:
284
+ continue
285
+ if include_set is not None and m.lifecycle.lower() not in include_set:
286
+ continue
287
+ if exclude_set is not None and m.lifecycle.lower() in exclude_set:
288
+ continue
289
+ yield m
290
+
291
+
292
+ def list_supported_models(
293
+ *,
294
+ families: Sequence[str] | None = None,
295
+ include: Sequence[str] | None = None,
296
+ exclude: Sequence[str] | None = None,
297
+ ) -> list[SupportedModel]:
298
+ """Return supported models as a list for easier consumption."""
299
+ return list(iter_supported_models(families=families, include=include, exclude=exclude))
300
+
301
+
302
+ def supported_model_ids(
303
+ *,
304
+ families: Sequence[str] | None = None,
305
+ include: Sequence[str] | None = None,
306
+ exclude: Sequence[str] | None = None,
307
+ ) -> list[str]:
308
+ """Return just the model identifiers for supported models."""
309
+ return [m.model_id for m in iter_supported_models(families=families, include=include, exclude=exclude)]
310
+
311
+
312
+ def experimental_model_ids(*, families: Sequence[str] | None = None) -> list[str]:
313
+ """Return identifiers for experimental supported models."""
314
+ return supported_model_ids(families=families, include=("experimental",))
315
+
316
+
317
+ def core_model_ids(*, families: Sequence[str] | None = None) -> list[str]:
318
+ """Return identifiers for core supported models."""
319
+ return supported_model_ids(families=families, include=("core",))
320
+
321
+
322
+ def format_supported_models(
323
+ *,
324
+ families: Sequence[str] | None = None,
325
+ include: Sequence[str] | None = None,
326
+ exclude: Sequence[str] | None = None,
327
+ ) -> str:
328
+ """Produce a human readable table of supported models."""
329
+ rows: Iterable[SupportedModel] = iter_supported_models(families=families, include=include, exclude=exclude)
330
+ lines = ["model_id | family | provider | lifecycle | modalities | training_modes", "-" * 96]
331
+ for model in rows:
332
+ modalities = ",".join(model.modalities) or "-"
333
+ training = ",".join(model.training_modes) or "-"
334
+ lines.append(
335
+ f"{model.model_id} | {model.family} | {model.provider} | {model.lifecycle} | {modalities} | {training}"
336
+ )
337
+ return "\n".join(lines)
338
+
339
+
340
+ def training_modes_for_model(model_id: str) -> tuple[str, ...]:
341
+ """Return the supported training modes (e.g., ('sft','rl')) for the given base model."""
342
+ canonical = ensure_supported_model(model_id, allow_finetuned_prefixes=True)
343
+ model = _MODEL_BY_ID.get(canonical)
344
+ if not model:
345
+ raise UnsupportedModelError(f"Model '{model_id}' is not registered as supported.")
346
+ return model.training_modes
347
+
348
+
349
+ __all__ = [
350
+ "QWEN3_MODELS",
351
+ "QWEN3_CODER_MODELS",
352
+ "RL_SUPPORTED_MODELS",
353
+ "SFT_SUPPORTED_MODELS",
354
+ "EXPERIMENTAL_MODELS",
355
+ "CORE_MODELS",
356
+ "ExperimentalWarning",
357
+ "SupportedModel",
358
+ "SUPPORTED_MODELS",
359
+ "SUPPORTED_BASE_MODEL_IDS",
360
+ "FINE_TUNED_PREFIXES",
361
+ "UnsupportedModelError",
362
+ "ensure_supported_model",
363
+ "ensure_allowed_model",
364
+ "normalize_model_identifier",
365
+ "is_supported_model",
366
+ "is_experimental_model",
367
+ "is_core_model",
368
+ "iter_supported_models",
369
+ "list_supported_models",
370
+ "supported_model_ids",
371
+ "experimental_model_ids",
372
+ "core_model_ids",
373
+ "format_supported_models",
374
+ "training_modes_for_model",
375
+ ]
376
+