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
synth_ai/cli/traces.py CHANGED
@@ -26,7 +26,7 @@ def register(cli):
26
26
  console = Console()
27
27
 
28
28
  async def _run():
29
- from synth_ai.tracing_v3.turso.manager import AsyncSQLTraceManager
29
+ from synth_ai.tracing_v3.storage.factory import StorageConfig, create_storage
30
30
 
31
31
  # Discover DBs under ./synth_ai.db/dbs (or override via env)
32
32
  root = os.getenv("SYNTH_TRACES_ROOT", "./synth_ai.db/dbs")
@@ -58,7 +58,7 @@ def register(cli):
58
58
 
59
59
  async def db_counts(db_dir: str) -> tuple[int, dict[str, int], int, str | None, int]:
60
60
  data_file = os.path.join(db_dir, "data")
61
- mgr = AsyncSQLTraceManager(f"sqlite+aiosqlite:///{data_file}")
61
+ mgr = create_storage(StorageConfig(connection_string=f"sqlite+aiosqlite:///{data_file}"))
62
62
  await mgr.initialize()
63
63
  try:
64
64
  traces_df = await mgr.query_traces("SELECT COUNT(*) AS c FROM session_traces")
@@ -74,9 +74,11 @@ def register(cli):
74
74
  system_counts = (
75
75
  {
76
76
  str(r["system_type"] or "-"): int(r["c"] or 0)
77
- for _, r in (systems_df or []).iterrows()
77
+ for _, r in systems_df.iterrows()
78
78
  }
79
- if systems_df is not None and not systems_df.empty
79
+ if systems_df is not None
80
+ and hasattr(systems_df, "iterrows")
81
+ and not systems_df.empty
80
82
  else {}
81
83
  )
82
84
  except Exception:
synth_ai/cli/watch.py CHANGED
@@ -16,6 +16,12 @@ from rich.console import Console, Group
16
16
  from rich.panel import Panel
17
17
  from rich.table import Table
18
18
 
19
+ from synth_ai.tracing_v3.storage.factory import StorageConfig, create_storage
20
+
21
+
22
+ def _open_db(db_url: str):
23
+ return create_storage(StorageConfig(connection_string=db_url))
24
+
19
25
 
20
26
  class _State:
21
27
  def __init__(self):
@@ -48,9 +54,7 @@ def _format_int(value: Any) -> str:
48
54
 
49
55
 
50
56
  async def _fetch_experiments(db_url: str):
51
- from synth_ai.tracing_v3.turso.manager import AsyncSQLTraceManager
52
-
53
- db = AsyncSQLTraceManager(db_url)
57
+ db = _open_db(db_url)
54
58
  await db.initialize()
55
59
  try:
56
60
  df = await db.query_traces(
@@ -114,9 +118,7 @@ def _experiments_table(df, limit: int | None = None) -> Table:
114
118
 
115
119
 
116
120
  async def _experiment_detail(db_url: str, experiment_id: str) -> dict[str, Any]:
117
- from synth_ai.tracing_v3.turso.manager import AsyncSQLTraceManager
118
-
119
- db = AsyncSQLTraceManager(db_url)
121
+ db = _open_db(db_url)
120
122
  await db.initialize()
121
123
  try:
122
124
  exp_df = await db.query_traces(
@@ -253,9 +255,7 @@ def register(cli):
253
255
  console = Console()
254
256
 
255
257
  async def _run():
256
- from synth_ai.tracing_v3.turso.manager import AsyncSQLTraceManager
257
-
258
- db = AsyncSQLTraceManager(db_url)
258
+ db = _open_db(db_url)
259
259
  await db.initialize()
260
260
  try:
261
261
  df = await db.get_model_usage(model_name=model_name)
@@ -396,9 +396,7 @@ def _parse_hours(args: list[str]) -> float | None:
396
396
 
397
397
 
398
398
  async def _usage_table(db_url: str, model_name: str | None):
399
- from synth_ai.tracing_v3.turso.manager import AsyncSQLTraceManager
400
-
401
- db = AsyncSQLTraceManager(db_url)
399
+ db = _open_db(db_url)
402
400
  await db.initialize()
403
401
  try:
404
402
  df = await db.get_model_usage(model_name=model_name)
@@ -418,9 +416,7 @@ async def _usage_table(db_url: str, model_name: str | None):
418
416
 
419
417
 
420
418
  async def _traces_table(db_url: str, limit: int):
421
- from synth_ai.tracing_v3.turso.manager import AsyncSQLTraceManager
422
-
423
- db = AsyncSQLTraceManager(db_url)
419
+ db = _open_db(db_url)
424
420
  await db.initialize()
425
421
  try:
426
422
  df = await db.query_traces("SELECT * FROM session_summary ORDER BY created_at DESC")
@@ -453,10 +449,8 @@ async def _recent_table(db_url: str, hours: float, limit: int):
453
449
  # Inline the recent query to avoid cross-module coupling
454
450
  from datetime import timedelta
455
451
 
456
- from synth_ai.tracing_v3.turso.manager import AsyncSQLTraceManager
457
-
458
452
  start_time = datetime.now() - timedelta(hours=hours)
459
- db = AsyncSQLTraceManager(db_url)
453
+ db = _open_db(db_url)
460
454
  await db.initialize()
461
455
  try:
462
456
  query = """
synth_ai/demo_registry.py CHANGED
@@ -3,9 +3,9 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import textwrap
6
+ from collections.abc import Callable, Iterable
6
7
  from dataclasses import dataclass
7
8
  from pathlib import Path
8
- from typing import Callable, Iterable
9
9
 
10
10
  REPO_ROOT = Path(__file__).resolve().parents[1]
11
11
 
@@ -1,25 +1,26 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import argparse
4
+ import contextlib
4
5
  import json
5
6
  import os
6
- import sys
7
- import time
8
- from pathlib import Path
9
- from typing import Any, Dict, Callable
10
7
  import shutil
11
8
  import stat
9
+ import sys
12
10
  import textwrap
11
+ import time
12
+ from collections.abc import Callable
13
+ from pathlib import Path
14
+ from typing import Any
13
15
 
14
- from synth_ai.demos.demo_task_apps import core as demo_core
15
- from synth_ai.demos.demo_task_apps.core import DemoEnv, DEFAULT_TASK_APP_SECRET_NAME
16
16
  from synth_ai.demo_registry import (
17
- CopySpec,
18
17
  DemoTemplate,
19
18
  get_demo_template,
20
19
  list_demo_templates,
21
20
  )
22
- from synth_ai.handshake import run_handshake, HandshakeError
21
+ from synth_ai.demos.demo_task_apps import core as demo_core
22
+ from synth_ai.demos.demo_task_apps.core import DEFAULT_TASK_APP_SECRET_NAME, DemoEnv
23
+ from synth_ai.handshake import HandshakeError, run_handshake
23
24
 
24
25
 
25
26
  def _key_preview(value: str, label: str) -> str:
@@ -59,7 +60,6 @@ def cmd_setup(_args: argparse.Namespace) -> int:
59
60
  try:
60
61
  print("\n⏳ Connecting SDK to your browser session…")
61
62
  res = run_handshake()
62
- user = res.get("user") or {}
63
63
  org = res.get("org") or {}
64
64
  keys = res.get("keys") or {}
65
65
  synth_key = str(keys.get("synth") or "").strip()
@@ -107,8 +107,8 @@ def cmd_setup(_args: argparse.Namespace) -> int:
107
107
  demo_core.persist_env_file_path(dotenv_path)
108
108
 
109
109
  # 2) Reload env after handshake to pick up values from .env (suppress env prints)
110
- import io
111
110
  import contextlib
111
+ import io
112
112
 
113
113
  _buf = io.StringIO()
114
114
  with contextlib.redirect_stdout(_buf):
@@ -126,9 +126,7 @@ def cmd_setup(_args: argparse.Namespace) -> int:
126
126
  return
127
127
  current = env.task_app_base_url
128
128
  needs_lookup = False
129
- if not current:
130
- needs_lookup = True
131
- elif not _is_modal_public_url(current):
129
+ if not current or not _is_modal_public_url(current):
132
130
  needs_lookup = True
133
131
  if not needs_lookup:
134
132
  return
@@ -173,16 +171,14 @@ def cmd_setup(_args: argparse.Namespace) -> int:
173
171
 
174
172
  _maybe_fix_task_url()
175
173
 
176
- ok_backend = False
177
- ok_task = False
178
174
  if env.dev_backend_url:
179
175
  api = env.dev_backend_url.rstrip("/") + (
180
176
  "" if env.dev_backend_url.endswith("/api") else "/api"
181
177
  )
182
- ok_backend = demo_core.assert_http_ok(api + "/health", method="GET")
178
+ demo_core.assert_http_ok(api + "/health", method="GET")
183
179
  # Intentionally suppress backend health print for concise output
184
180
  if env.task_app_base_url:
185
- ok_task = demo_core.assert_http_ok(
181
+ demo_core.assert_http_ok(
186
182
  env.task_app_base_url.rstrip("/") + "/health", method="GET"
187
183
  ) or demo_core.assert_http_ok(env.task_app_base_url.rstrip("/"), method="GET")
188
184
  # Intentionally suppress task app health print
@@ -723,7 +719,6 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
723
719
  if openai_key:
724
720
  os.environ["OPENAI_API_KEY"] = openai_key
725
721
 
726
- rollout_url = task_url.rstrip("/") + "/health/rollout"
727
722
  print(f"[{label}] Verifying rollout health:")
728
723
  try:
729
724
  ek = (env_key or "").strip()
@@ -738,7 +733,6 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
738
733
  print(f"[{label}] GET", h)
739
734
  rc, body = _http("GET", h, headers={"X-API-Key": env_key})
740
735
  if rc == 200:
741
- rollout_url = h
742
736
  break
743
737
  print(f"[{label}] status: {rc}")
744
738
  try:
@@ -750,10 +744,8 @@ def _ensure_task_app_ready(env: DemoEnv, synth_key: str, *, label: str) -> DemoE
750
744
  print(f"[{label}] body:", preview)
751
745
  if rc != 200:
752
746
  print(f"[{label}] Warning: rollout health check failed ({rc}). Response: {body}")
753
- try:
747
+ with contextlib.suppress(Exception):
754
748
  print(f"[{label}] Sent header X-API-Key → {_key_preview(env_key, 'X-API-Key')}")
755
- except Exception:
756
- pass
757
749
  else:
758
750
  print(f"[{label}] Task app rollout health check OK.")
759
751
 
@@ -883,7 +875,7 @@ def cmd_deploy(args: argparse.Namespace) -> int:
883
875
  env_key = None
884
876
 
885
877
  if env_key is None:
886
- from synth_ai.rl.secrets import mint_environment_api_key
878
+ from synth_ai.learning.rl.secrets import mint_environment_api_key
887
879
 
888
880
  env_key = mint_environment_api_key()
889
881
  demo_core.persist_env_api_key(env_key)
@@ -921,14 +913,14 @@ def cmd_deploy(args: argparse.Namespace) -> int:
921
913
  if choice.startswith("y"):
922
914
  try:
923
915
  print(f"[deploy] Uploading ENVIRONMENT_API_KEY to {non_api_base} …")
924
- from synth_ai.rl.env_keys import setup_environment_api_key
916
+ from synth_ai.learning.rl.env_keys import setup_environment_api_key
925
917
 
926
918
  setup_environment_api_key(non_api_base, synth_key, token=env_key)
927
919
  print("[deploy] Backend sealed-box upload complete.")
928
920
  except Exception as upload_err:
929
921
  print(f"[deploy] Failed to upload ENVIRONMENT_API_KEY: {upload_err}")
930
922
  print(
931
- 'Hint: run `uvx python -c "from synth_ai.rl.env_keys import setup_environment_api_key as s;'
923
+ 'Hint: run `uvx python -c "from synth_ai.learning.rl.env_keys import setup_environment_api_key as s;'
932
924
  " s('<backend>', '<synth_api_key>')\"` once the backend is reachable."
933
925
  )
934
926
 
@@ -1126,14 +1118,14 @@ def _ensure_modal_installed() -> None:
1126
1118
  if auth_ok:
1127
1119
  print(f"✓ Modal authenticated: {auth_msg}")
1128
1120
  else:
1129
- print(f"\n⚠️ Modal authentication required")
1121
+ print("\n⚠️ Modal authentication required")
1130
1122
  print(f" Status: {auth_msg}")
1131
- print(f"\n To authenticate Modal, run:")
1132
- print(f" modal setup")
1133
- print(f"\n Or set environment variables:")
1134
- print(f" export MODAL_TOKEN_ID=your-token-id")
1135
- print(f" export MODAL_TOKEN_SECRET=your-token-secret")
1136
- print(f"\n You can deploy later after authenticating.\n")
1123
+ print("\n To authenticate Modal, run:")
1124
+ print(" modal setup")
1125
+ print("\n Or set environment variables:")
1126
+ print(" export MODAL_TOKEN_ID=your-token-id")
1127
+ print(" export MODAL_TOKEN_SECRET=your-token-secret")
1128
+ print("\n You can deploy later after authenticating.\n")
1137
1129
 
1138
1130
 
1139
1131
  def cmd_init(args: argparse.Namespace) -> int:
@@ -1289,7 +1281,7 @@ def cmd_init(args: argparse.Namespace) -> int:
1289
1281
  should_write = True
1290
1282
  if env_path.exists() and not directory_cleared:
1291
1283
  try:
1292
- response = input(f"File .env exists. Overwrite? [y/N]: ").strip().lower()
1284
+ response = input("File .env exists. Overwrite? [y/N]: ").strip().lower()
1293
1285
  except (EOFError, KeyboardInterrupt):
1294
1286
  print("\nCancelled.")
1295
1287
  return 1
@@ -1353,9 +1345,12 @@ def cmd_init(args: argparse.Namespace) -> int:
1353
1345
 
1354
1346
 
1355
1347
  def _http(
1356
- method: str, url: str, headers: Dict[str, str] | None = None, body: Dict[str, Any] | None = None
1357
- ) -> tuple[int, Dict[str, Any] | str]:
1358
- import urllib.request, urllib.error, json as _json, ssl
1348
+ method: str, url: str, headers: dict[str, str] | None = None, body: dict[str, Any] | None = None
1349
+ ) -> tuple[int, dict[str, Any] | str]:
1350
+ import json as _json
1351
+ import ssl
1352
+ import urllib.error
1353
+ import urllib.request
1359
1354
 
1360
1355
  data = None
1361
1356
  if body is not None:
@@ -1402,7 +1397,7 @@ def cmd_run(args: argparse.Namespace) -> int:
1402
1397
 
1403
1398
  env = demo_core.load_env()
1404
1399
  cwd_env_path = os.path.join(os.getcwd(), ".env")
1405
- local_env = demo_core.load_dotenv_file(cwd_env_path)
1400
+ demo_core.load_dotenv_file(cwd_env_path)
1406
1401
 
1407
1402
  synth_key = (env.synth_api_key or "").strip()
1408
1403
  if not synth_key:
@@ -1487,7 +1482,7 @@ def cmd_run(args: argparse.Namespace) -> int:
1487
1482
  # Fallback: legacy jobs API flow
1488
1483
  with open(cfg_path, "rb") as fh:
1489
1484
  inline_cfg = tomllib.load(fh)
1490
- with open(cfg_path, "r") as fh2:
1485
+ with open(cfg_path) as fh2:
1491
1486
  toml_text = fh2.read()
1492
1487
  if args.batch_size is not None:
1493
1488
  inline_cfg.setdefault("training", {})["batch_size"] = int(args.batch_size)
@@ -1498,13 +1493,11 @@ def cmd_run(args: argparse.Namespace) -> int:
1498
1493
  # Print backend and key preview before request for clearer diagnostics
1499
1494
  try:
1500
1495
  sk = (env.synth_api_key or "").strip()
1501
- sk_len = len(sk)
1502
- sk_tail = sk[-5:] if sk_len >= 5 else sk
1503
1496
  print(f"[run] Backend API: {api}")
1504
1497
  print(f"[run] {_key_preview(sk, 'SYNTH_API_KEY')}")
1505
1498
  except Exception:
1506
1499
  pass
1507
- data_fragment: Dict[str, Any] = {
1500
+ data_fragment: dict[str, Any] = {
1508
1501
  "model": model_name,
1509
1502
  "endpoint_base_url": env.task_app_base_url,
1510
1503
  "config": inline_cfg,
@@ -1529,7 +1522,7 @@ def cmd_run(args: argparse.Namespace) -> int:
1529
1522
  if ":" in gshape:
1530
1523
  t, c = gshape.split(":", 1)
1531
1524
  compute = {"gpu_type": t.upper(), "gpu_count": int(c)}
1532
- body: Dict[str, Any] = {
1525
+ body: dict[str, Any] = {
1533
1526
  "job_type": "rl",
1534
1527
  "data": data_fragment,
1535
1528
  }
@@ -1588,7 +1581,7 @@ def cmd_run(args: argparse.Namespace) -> int:
1588
1581
  pass
1589
1582
  try:
1590
1583
  sent_keys = detail.get("sent_keys")
1591
- if isinstance(sent_keys, (list, tuple)):
1584
+ if isinstance(sent_keys, list | tuple):
1592
1585
  previews = []
1593
1586
  for idx, val in enumerate(sent_keys):
1594
1587
  if isinstance(val, str):
@@ -1,7 +1,7 @@
1
1
  """Namespace for demo task apps (math, etc.)."""
2
2
 
3
+ import contextlib
4
+
3
5
  # Ensure registry entries are loaded for CLI discovery.
4
- try: # pragma: no cover - optional on downstream installs
6
+ with contextlib.suppress(Exception): # pragma: no cover - optional on downstream installs
5
7
  from .math import task_app_entry # noqa: F401
6
- except Exception:
7
- pass
@@ -3,15 +3,12 @@ from __future__ import annotations
3
3
  import json
4
4
  import os
5
5
  import subprocess
6
- import sys
7
- from dataclasses import dataclass
8
- from typing import Any, Dict, Optional, Tuple
9
-
10
6
  import urllib.request
7
+ from dataclasses import dataclass
8
+ from typing import Any
11
9
 
12
10
  from synth_ai.config.base_url import PROD_BASE_URL_DEFAULT
13
11
 
14
-
15
12
  DEFAULT_TASK_APP_SECRET_NAME = "hendrycks-math-task-app-secret"
16
13
 
17
14
 
@@ -35,7 +32,7 @@ def _state_path() -> str:
35
32
  return os.path.expanduser("~/.synth-ai/demo.json")
36
33
 
37
34
 
38
- def _read_state() -> Dict[str, Any]:
35
+ def _read_state() -> dict[str, Any]:
39
36
  try:
40
37
  path = _state_path()
41
38
  if os.path.isfile(path):
@@ -47,7 +44,7 @@ def _read_state() -> Dict[str, Any]:
47
44
  return {}
48
45
 
49
46
 
50
- def _write_state(data: Dict[str, Any]) -> None:
47
+ def _write_state(data: dict[str, Any]) -> None:
51
48
  try:
52
49
  path = _state_path()
53
50
  os.makedirs(os.path.dirname(path), exist_ok=True)
@@ -57,8 +54,8 @@ def _write_state(data: Dict[str, Any]) -> None:
57
54
  pass
58
55
 
59
56
 
60
- def load_dotenv_file(path: str) -> Dict[str, str]:
61
- out: Dict[str, str] = {}
57
+ def load_dotenv_file(path: str) -> dict[str, str]:
58
+ out: dict[str, str] = {}
62
59
  try:
63
60
  with open(path) as fh:
64
61
  for raw in fh:
@@ -72,7 +69,7 @@ def load_dotenv_file(path: str) -> Dict[str, str]:
72
69
  return out
73
70
 
74
71
 
75
- def _persist_dotenv_values(path: str, values: Dict[str, str]) -> None:
72
+ def _persist_dotenv_values(path: str, values: dict[str, str]) -> None:
76
73
  """Ensure ``values`` are present in ``path`` (.env style)."""
77
74
 
78
75
  try:
@@ -82,7 +79,7 @@ def _persist_dotenv_values(path: str, values: Dict[str, str]) -> None:
82
79
  existing_lines = fh.read().splitlines()
83
80
  else:
84
81
  os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
85
- mapping: Dict[str, str] = {}
82
+ mapping: dict[str, str] = {}
86
83
  order: list[str] = []
87
84
  for line in existing_lines:
88
85
  if not line or line.startswith("#") or "=" not in line:
@@ -110,7 +107,7 @@ def _persist_dotenv_values(path: str, values: Dict[str, str]) -> None:
110
107
  pass
111
108
 
112
109
 
113
- def persist_dotenv_values(values: Dict[str, str], *, cwd: str | None = None) -> str:
110
+ def persist_dotenv_values(values: dict[str, str], *, cwd: str | None = None) -> str:
114
111
  path = os.path.join(cwd or os.getcwd(), ".env")
115
112
  _persist_dotenv_values(path, values)
116
113
  return path
@@ -148,14 +145,15 @@ def load_env_file_path() -> str | None:
148
145
  return data.get("ENV_FILE_PATH")
149
146
 
150
147
 
151
- def modal_auth_status() -> Tuple[bool, str]:
148
+ def modal_auth_status() -> tuple[bool, str]:
152
149
  """Return (ok, message) describing Modal CLI credential status."""
153
150
 
154
151
  env_token_id = (os.environ.get("MODAL_TOKEN_ID") or "").strip()
155
152
  env_token_secret = (os.environ.get("MODAL_TOKEN_SECRET") or "").strip()
156
153
 
157
154
  try:
158
- from modal.config import config as modal_config, user_config_path
155
+ from modal.config import config as modal_config
156
+ from modal.config import user_config_path
159
157
  except Exception as exc: # pragma: no cover - modal optional in some envs
160
158
  return False, f"Modal client unavailable ({exc})"
161
159
 
@@ -210,7 +208,7 @@ def load_env() -> DemoEnv:
210
208
  """
211
209
  env = DemoEnv()
212
210
 
213
- os_env: Dict[str, str] = dict(os.environ)
211
+ os_env: dict[str, str] = dict(os.environ)
214
212
 
215
213
  # CWD .env
216
214
  cwd_env_path = os.path.join(os.getcwd(), ".env")
@@ -245,15 +243,12 @@ def load_env() -> DemoEnv:
245
243
  or pkg_env.get("DEV_BACKEND_URL")
246
244
  or ""
247
245
  ).strip()
248
- use_dev = False
249
246
  if backend_override:
250
247
  dev_url = backend_override
251
- use_dev = True
252
248
  elif dev_env:
253
249
  lower = dev_env.lower()
254
250
  if "localhost" in lower or "127.0.0.1" in lower or lower.endswith(":8000"):
255
251
  dev_url = dev_env
256
- use_dev = True
257
252
  else:
258
253
  dev_url = prod_default
259
254
  else:
@@ -426,16 +421,13 @@ def run_job(
426
421
  env: DemoEnv,
427
422
  config_toml_path: str,
428
423
  *,
429
- batch_size: Optional[int] = None,
430
- group_size: Optional[int] = None,
431
- model: Optional[str] = None,
424
+ batch_size: int | None = None,
425
+ group_size: int | None = None,
426
+ model: str | None = None,
432
427
  ) -> None:
433
428
  """Create and stream a short RL job using the backend API (placeholder: prints cURL to execute)."""
434
429
  backend = env.dev_backend_url.rstrip("/")
435
- if backend.endswith("/api"):
436
- api_base = backend
437
- else:
438
- api_base = backend + "/api"
430
+ api_base = backend if backend.endswith("/api") else backend + "/api"
439
431
  print("\nTo create an RL job, run:")
440
432
  print(
441
433
  'curl -s -X POST "' + api_base + '/rl/jobs" '
@@ -9,16 +9,15 @@ Prefer using `uvx synth-ai serve grpo-crafter` for local development and testing
9
9
  from __future__ import annotations
10
10
 
11
11
  import argparse
12
+ import importlib.util
12
13
  from pathlib import Path
13
14
 
14
15
  from fastapi.exceptions import RequestValidationError
15
16
  from fastapi.responses import JSONResponse
16
17
  from starlette.requests import Request
17
-
18
18
  from synth_ai.task.apps import ModalDeploymentConfig, registry
19
19
  from synth_ai.task.auth import is_api_key_header_authorized, normalize_environment_api_key
20
20
  from synth_ai.task.server import TaskAppConfig, create_task_app, run_task_app
21
- import importlib.util
22
21
 
23
22
 
24
23
  def _load_build_config():
@@ -40,7 +39,7 @@ def _load_build_config():
40
39
  raise ImportError(f"Could not load task app module at {module_path}")
41
40
  module = importlib.util.module_from_spec(spec)
42
41
  spec.loader.exec_module(module)
43
- return getattr(module, "build_config")
42
+ return module.build_config
44
43
 
45
44
 
46
45
  build_config = _load_build_config()
@@ -130,7 +129,7 @@ def fastapi_app():
130
129
  try:
131
130
  hdr = request.headers
132
131
  snapshot = {
133
- "path": str(getattr(request, "url").path),
132
+ "path": str(request.url.path),
134
133
  "have_x_api_key": bool(hdr.get("x-api-key")),
135
134
  "have_x_api_keys": bool(hdr.get("x-api-keys")),
136
135
  "have_authorization": bool(hdr.get("authorization")),
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import os
4
+
4
5
  from fastapi import FastAPI
5
6
  from starlette.middleware.cors import CORSMiddleware
6
7
 
@@ -11,7 +12,7 @@ except Exception: # fallback path when imported from repo root
11
12
  try:
12
13
  from examples.rl.task_app import make_app as make_rl_app # type: ignore
13
14
  except Exception as e: # pragma: no cover
14
- raise ImportError(f"Unable to import RL task app: {e}")
15
+ raise ImportError(f"Unable to import RL task app: {e}") from e
15
16
 
16
17
 
17
18
  def create_app() -> FastAPI:
@@ -2,10 +2,9 @@ from __future__ import annotations
2
2
 
3
3
  import os
4
4
  import subprocess
5
- from typing import Optional
6
5
 
7
6
 
8
- def _parse_public_url_from_log(log_path: str) -> Optional[str]:
7
+ def _parse_public_url_from_log(log_path: str) -> str | None:
9
8
  try:
10
9
  with open(log_path) as fh:
11
10
  for line in fh:
@@ -16,7 +15,7 @@ def _parse_public_url_from_log(log_path: str) -> Optional[str]:
16
15
  return None
17
16
 
18
17
 
19
- def deploy(script_path: Optional[str] = None, *, env_api_key: Optional[str] = None) -> str:
18
+ def deploy(script_path: str | None = None, *, env_api_key: str | None = None) -> str:
20
19
  """
21
20
  Deploy the Math Task App to Modal and return the public URL.
22
21
 
@@ -55,4 +54,4 @@ def deploy(script_path: Optional[str] = None, *, env_api_key: Optional[str] = No
55
54
  raise RuntimeError(
56
55
  f"No deploy script provided and Python-based deploy failed: {e}. "
57
56
  "Pass --script /path/to/deploy_task_app.sh to demo.deploy."
58
- )
57
+ ) from e