synth-ai 0.2.8.dev2__py3-none-any.whl → 0.4.3__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.
Files changed (740) hide show
  1. synth_ai/__init__.py +44 -24
  2. synth_ai/__main__.py +30 -3
  3. synth_ai/cli/__init__.py +103 -48
  4. synth_ai/cli/__main__.py +42 -0
  5. synth_ai/cli/_internal/__init__.py +5 -0
  6. synth_ai/cli/_internal/modal_wrapper.py +31 -0
  7. synth_ai/cli/_internal/storage.py +20 -0
  8. synth_ai/cli/_internal/typer_patch.py +47 -0
  9. synth_ai/cli/_internal/validate_task_app.py +29 -0
  10. synth_ai/cli/agents/__init__.py +17 -0
  11. synth_ai/cli/agents/claude.py +77 -0
  12. synth_ai/cli/agents/codex.py +265 -0
  13. synth_ai/cli/agents/opencode.py +253 -0
  14. synth_ai/cli/commands/__init__.py +18 -0
  15. synth_ai/cli/commands/artifacts/__init__.py +13 -0
  16. synth_ai/cli/commands/artifacts/client.py +119 -0
  17. synth_ai/cli/commands/artifacts/config.py +57 -0
  18. synth_ai/cli/commands/artifacts/core.py +24 -0
  19. synth_ai/cli/commands/artifacts/download.py +188 -0
  20. synth_ai/cli/commands/artifacts/export.py +186 -0
  21. synth_ai/cli/commands/artifacts/list.py +156 -0
  22. synth_ai/cli/commands/artifacts/parsing.py +250 -0
  23. synth_ai/cli/commands/artifacts/show.py +336 -0
  24. synth_ai/cli/commands/demo/__init__.py +3 -0
  25. synth_ai/cli/commands/demo/core.py +153 -0
  26. synth_ai/cli/commands/eval/__init__.py +10 -0
  27. synth_ai/cli/commands/eval/config.py +338 -0
  28. synth_ai/cli/commands/eval/core.py +256 -0
  29. synth_ai/cli/commands/eval/runner.py +704 -0
  30. synth_ai/cli/commands/eval/validation.py +60 -0
  31. synth_ai/cli/commands/filter/__init__.py +12 -0
  32. synth_ai/cli/commands/filter/core.py +424 -0
  33. synth_ai/cli/commands/filter/errors.py +55 -0
  34. synth_ai/cli/commands/filter/validation.py +77 -0
  35. synth_ai/cli/commands/help/__init__.py +185 -0
  36. synth_ai/cli/commands/help/core.py +72 -0
  37. synth_ai/cli/commands/scan/__init__.py +19 -0
  38. synth_ai/cli/commands/scan/cloudflare_scanner.py +403 -0
  39. synth_ai/cli/commands/scan/core.py +344 -0
  40. synth_ai/cli/commands/scan/health_checker.py +242 -0
  41. synth_ai/cli/commands/scan/local_scanner.py +278 -0
  42. synth_ai/cli/commands/scan/models.py +83 -0
  43. synth_ai/cli/commands/smoke/__init__.py +7 -0
  44. synth_ai/cli/commands/smoke/core.py +1428 -0
  45. synth_ai/cli/commands/status/__init__.py +3 -0
  46. synth_ai/cli/commands/status/client.py +91 -0
  47. synth_ai/cli/commands/status/config.py +12 -0
  48. synth_ai/cli/commands/status/errors.py +11 -0
  49. synth_ai/cli/commands/status/subcommands/__init__.py +3 -0
  50. synth_ai/cli/commands/status/subcommands/config.py +13 -0
  51. synth_ai/cli/commands/status/subcommands/files.py +34 -0
  52. synth_ai/cli/commands/status/subcommands/jobs.py +51 -0
  53. synth_ai/cli/commands/status/subcommands/models.py +35 -0
  54. synth_ai/cli/commands/status/subcommands/runs.py +34 -0
  55. synth_ai/cli/commands/status/subcommands/session.py +77 -0
  56. synth_ai/cli/commands/status/subcommands/summary.py +39 -0
  57. synth_ai/cli/commands/status/subcommands/utils.py +41 -0
  58. synth_ai/cli/commands/status/utils.py +23 -0
  59. synth_ai/cli/commands/train/__init__.py +53 -0
  60. synth_ai/cli/commands/train/core.py +22 -0
  61. synth_ai/cli/commands/train/errors.py +117 -0
  62. synth_ai/cli/commands/train/judge_schemas.py +201 -0
  63. synth_ai/cli/commands/train/judge_validation.py +305 -0
  64. synth_ai/cli/commands/train/prompt_learning_validation.py +633 -0
  65. synth_ai/cli/commands/train/validation.py +392 -0
  66. synth_ai/cli/demo_apps/__init__.py +10 -0
  67. synth_ai/cli/demo_apps/core/__init__.py +28 -0
  68. synth_ai/{demos → cli/demo_apps}/core/cli.py +783 -441
  69. synth_ai/cli/demo_apps/crafter/__init__.py +1 -0
  70. synth_ai/cli/demo_apps/crafter/crafter_fft_4b.toml +55 -0
  71. synth_ai/cli/demo_apps/crafter/grpo_crafter_task_app.py +186 -0
  72. synth_ai/cli/demo_apps/crafter/rl_from_base_qwen4b.toml +74 -0
  73. synth_ai/cli/demo_apps/demo_registry.py +176 -0
  74. synth_ai/cli/demo_apps/demo_task_apps/__init__.py +7 -0
  75. synth_ai/{demos → cli/demo_apps}/demo_task_apps/core.py +75 -37
  76. synth_ai/cli/demo_apps/demo_task_apps/crafter/__init__.py +1 -0
  77. synth_ai/cli/demo_apps/demo_task_apps/crafter/configs/crafter_fft_4b.toml +53 -0
  78. synth_ai/cli/demo_apps/demo_task_apps/crafter/configs/rl_from_base_qwen4b.toml +73 -0
  79. synth_ai/cli/demo_apps/demo_task_apps/crafter/grpo_crafter_task_app.py +185 -0
  80. synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/_common.py +1 -2
  81. synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/app.py +2 -1
  82. synth_ai/cli/demo_apps/demo_task_apps/math/config.toml +73 -0
  83. synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/deploy_modal.py +3 -6
  84. synth_ai/cli/demo_apps/demo_task_apps/math/modal_task_app.py +738 -0
  85. synth_ai/cli/demo_apps/demo_task_apps/math/task_app_entry.py +39 -0
  86. synth_ai/cli/demo_apps/math/__init__.py +1 -0
  87. synth_ai/cli/demo_apps/math/_common.py +16 -0
  88. synth_ai/cli/demo_apps/math/app.py +38 -0
  89. synth_ai/cli/demo_apps/math/config.toml +75 -0
  90. synth_ai/cli/demo_apps/math/deploy_modal.py +54 -0
  91. synth_ai/cli/demo_apps/math/modal_task_app.py +698 -0
  92. synth_ai/cli/demo_apps/math/task_app_entry.py +53 -0
  93. synth_ai/cli/demo_apps/mipro/main.py +271 -0
  94. synth_ai/cli/demo_apps/mipro/task_app.py +922 -0
  95. synth_ai/cli/demo_apps/mipro/train_cfg.toml +92 -0
  96. synth_ai/cli/demos/__init__.py +12 -0
  97. synth_ai/cli/demos/demo.py +32 -0
  98. synth_ai/cli/demos/rl_demo.py +254 -0
  99. synth_ai/cli/deploy.py +216 -0
  100. synth_ai/cli/infra/__init__.py +14 -0
  101. synth_ai/cli/{balance.py → infra/balance.py} +16 -4
  102. synth_ai/cli/infra/mcp.py +35 -0
  103. synth_ai/cli/infra/modal_app.py +36 -0
  104. synth_ai/cli/infra/setup.py +69 -0
  105. synth_ai/cli/infra/status.py +16 -0
  106. synth_ai/cli/infra/turso.py +77 -0
  107. synth_ai/cli/lib/__init__.py +10 -0
  108. synth_ai/cli/lib/agents.py +76 -0
  109. synth_ai/cli/lib/apps/modal_app.py +101 -0
  110. synth_ai/cli/lib/apps/task_app.py +642 -0
  111. synth_ai/cli/lib/bin.py +39 -0
  112. synth_ai/cli/lib/env.py +375 -0
  113. synth_ai/cli/lib/errors.py +85 -0
  114. synth_ai/cli/lib/modal.py +315 -0
  115. synth_ai/cli/lib/plotting.py +126 -0
  116. synth_ai/cli/lib/prompt_args.py +39 -0
  117. synth_ai/cli/lib/prompts.py +284 -0
  118. synth_ai/cli/lib/sqld.py +122 -0
  119. synth_ai/cli/lib/task_app_discovery.py +884 -0
  120. synth_ai/cli/lib/task_app_env.py +295 -0
  121. synth_ai/cli/lib/train_cfgs.py +300 -0
  122. synth_ai/cli/lib/tunnel_records.py +207 -0
  123. synth_ai/cli/local/__init__.py +14 -0
  124. synth_ai/cli/local/experiment_queue/__init__.py +72 -0
  125. synth_ai/cli/local/experiment_queue/api_schemas.py +221 -0
  126. synth_ai/cli/local/experiment_queue/celery_app.py +208 -0
  127. synth_ai/cli/local/experiment_queue/config.py +128 -0
  128. synth_ai/cli/local/experiment_queue/config_utils.py +272 -0
  129. synth_ai/cli/local/experiment_queue/database.py +175 -0
  130. synth_ai/cli/local/experiment_queue/dispatcher.py +119 -0
  131. synth_ai/cli/local/experiment_queue/models.py +231 -0
  132. synth_ai/cli/local/experiment_queue/progress_info.py +160 -0
  133. synth_ai/cli/local/experiment_queue/results.py +373 -0
  134. synth_ai/cli/local/experiment_queue/schemas.py +131 -0
  135. synth_ai/cli/local/experiment_queue/service.py +344 -0
  136. synth_ai/cli/local/experiment_queue/status.py +372 -0
  137. synth_ai/cli/local/experiment_queue/status_tracker.py +360 -0
  138. synth_ai/cli/local/experiment_queue/tasks.py +1984 -0
  139. synth_ai/cli/local/experiment_queue/trace_storage.py +65 -0
  140. synth_ai/cli/local/experiment_queue/validation.py +157 -0
  141. synth_ai/cli/local/session/__init__.py +92 -0
  142. synth_ai/cli/local/session/client.py +383 -0
  143. synth_ai/cli/local/session/constants.py +63 -0
  144. synth_ai/cli/local/session/exceptions.py +105 -0
  145. synth_ai/cli/local/session/manager.py +139 -0
  146. synth_ai/cli/local/session/models.py +89 -0
  147. synth_ai/cli/local/session/query.py +110 -0
  148. synth_ai/cli/root.py +150 -108
  149. synth_ai/cli/task_apps/__init__.py +37 -0
  150. synth_ai/cli/task_apps/commands.py +3145 -0
  151. synth_ai/cli/task_apps/deploy.py +7 -0
  152. synth_ai/cli/task_apps/list.py +26 -0
  153. synth_ai/cli/task_apps/main.py +36 -0
  154. synth_ai/cli/task_apps/modal_serve.py +11 -0
  155. synth_ai/cli/task_apps/serve.py +11 -0
  156. synth_ai/cli/training/__init__.py +8 -0
  157. synth_ai/cli/training/train.py +5 -0
  158. synth_ai/cli/training/train_cfg.py +34 -0
  159. synth_ai/cli/{watch.py → training/watch.py} +13 -18
  160. synth_ai/cli/turso.py +52 -0
  161. synth_ai/cli/utils/__init__.py +8 -0
  162. synth_ai/cli/utils/experiments.py +235 -0
  163. synth_ai/cli/utils/queue.py +504 -0
  164. synth_ai/cli/{recent.py → utils/recent.py} +13 -7
  165. synth_ai/cli/{traces.py → utils/traces.py} +9 -5
  166. synth_ai/contracts/__init__.py +67 -0
  167. synth_ai/core/__init__.py +100 -0
  168. synth_ai/core/_utils/__init__.py +54 -0
  169. synth_ai/core/_utils/base_url.py +10 -0
  170. synth_ai/core/_utils/http.py +10 -0
  171. synth_ai/core/_utils/prompts.py +14 -0
  172. synth_ai/core/_utils/task_app_state.py +12 -0
  173. synth_ai/core/_utils/user_config.py +10 -0
  174. synth_ai/core/apps/common.py +116 -0
  175. synth_ai/core/auth.py +95 -0
  176. synth_ai/core/cfgs.py +240 -0
  177. synth_ai/core/config/__init__.py +16 -0
  178. synth_ai/core/config/base.py +168 -0
  179. synth_ai/core/config/resolver.py +89 -0
  180. synth_ai/core/env.py +231 -0
  181. synth_ai/core/errors.py +126 -0
  182. synth_ai/core/http.py +230 -0
  183. synth_ai/core/integrations/__init__.py +11 -0
  184. synth_ai/core/integrations/cloudflare.py +1710 -0
  185. synth_ai/core/integrations/mcp/__init__.py +6 -0
  186. synth_ai/core/integrations/mcp/__main__.py +8 -0
  187. synth_ai/core/integrations/mcp/claude.py +36 -0
  188. synth_ai/core/integrations/mcp/main.py +254 -0
  189. synth_ai/core/integrations/mcp/setup.py +100 -0
  190. synth_ai/core/integrations/modal.py +277 -0
  191. synth_ai/core/json.py +72 -0
  192. synth_ai/core/log_filter.py +99 -0
  193. synth_ai/core/logging.py +82 -0
  194. synth_ai/core/paths.py +107 -0
  195. synth_ai/core/pricing.py +109 -0
  196. synth_ai/core/process.py +233 -0
  197. synth_ai/core/ssl.py +25 -0
  198. synth_ai/core/storage/__init__.py +71 -0
  199. synth_ai/core/task_app_state.py +318 -0
  200. synth_ai/core/telemetry.py +282 -0
  201. synth_ai/{tracing_v3 → core/tracing_v3}/__init__.py +5 -1
  202. synth_ai/{tracing_v3 → core/tracing_v3}/abstractions.py +21 -4
  203. synth_ai/core/tracing_v3/config.py +229 -0
  204. synth_ai/core/tracing_v3/constants.py +21 -0
  205. synth_ai/{tracing_v3 → core/tracing_v3}/db_config.py +42 -29
  206. synth_ai/{tracing_v3 → core/tracing_v3}/decorators.py +80 -45
  207. synth_ai/{tracing_v3 → core/tracing_v3}/examples/basic_usage.py +15 -9
  208. synth_ai/{tracing_v3 → core/tracing_v3}/hooks.py +6 -4
  209. synth_ai/{tracing_v3 → core/tracing_v3}/llm_call_record_helpers.py +161 -61
  210. synth_ai/{tracing_v3 → core/tracing_v3}/migration_helper.py +1 -2
  211. synth_ai/{tracing_v3 → core/tracing_v3}/replica_sync.py +12 -7
  212. synth_ai/core/tracing_v3/serialization.py +130 -0
  213. synth_ai/{tracing_v3 → core/tracing_v3}/session_tracer.py +88 -21
  214. synth_ai/{tracing_v3 → core/tracing_v3}/storage/base.py +99 -12
  215. synth_ai/core/tracing_v3/storage/config.py +109 -0
  216. synth_ai/{tracing_v3 → core/tracing_v3}/storage/factory.py +11 -9
  217. synth_ai/{tracing_v3 → core/tracing_v3}/storage/utils.py +15 -11
  218. synth_ai/core/tracing_v3/trace_utils.py +326 -0
  219. synth_ai/core/tracing_v3/turso/__init__.py +12 -0
  220. synth_ai/core/tracing_v3/turso/daemon.py +278 -0
  221. synth_ai/{tracing_v3 → core/tracing_v3}/turso/models.py +7 -3
  222. synth_ai/core/tracing_v3/turso/native_manager.py +1385 -0
  223. synth_ai/{tracing_v3 → core/tracing_v3}/utils.py +5 -4
  224. synth_ai/core/urls.py +18 -0
  225. synth_ai/core/user_config.py +137 -0
  226. synth_ai/core/uvicorn.py +222 -0
  227. synth_ai/data/__init__.py +83 -0
  228. synth_ai/data/enums.py +123 -0
  229. synth_ai/data/rewards.py +152 -0
  230. synth_ai/data/traces.py +35 -0
  231. synth_ai/products/__init__.py +6 -0
  232. synth_ai/products/graph_evolve/__init__.py +46 -0
  233. synth_ai/products/graph_evolve/client.py +226 -0
  234. synth_ai/products/graph_evolve/config.py +591 -0
  235. synth_ai/products/graph_evolve/converters/__init__.py +42 -0
  236. synth_ai/products/graph_evolve/converters/openai_sft.py +484 -0
  237. synth_ai/products/graph_evolve/examples/hotpotqa/config.toml +109 -0
  238. synth_ai/products/graph_evolve/run.py +222 -0
  239. synth_ai/products/graph_gepa/__init__.py +23 -0
  240. synth_ai/products/graph_gepa/converters/__init__.py +19 -0
  241. synth_ai/products/graph_gepa/converters/openai_sft.py +29 -0
  242. synth_ai/sdk/__init__.py +123 -0
  243. synth_ai/sdk/api/__init__.py +1 -0
  244. synth_ai/sdk/api/models/supported.py +514 -0
  245. synth_ai/sdk/api/research_agent/__init__.py +296 -0
  246. synth_ai/sdk/api/train/__init__.py +85 -0
  247. synth_ai/sdk/api/train/builders.py +895 -0
  248. synth_ai/sdk/api/train/cli.py +2199 -0
  249. synth_ai/sdk/api/train/config_finder.py +267 -0
  250. synth_ai/sdk/api/train/configs/__init__.py +65 -0
  251. synth_ai/sdk/api/train/configs/prompt_learning.py +1706 -0
  252. synth_ai/sdk/api/train/configs/rl.py +187 -0
  253. synth_ai/sdk/api/train/configs/sft.py +99 -0
  254. synth_ai/sdk/api/train/configs/shared.py +81 -0
  255. synth_ai/sdk/api/train/context_learning.py +312 -0
  256. synth_ai/sdk/api/train/env_resolver.py +418 -0
  257. synth_ai/sdk/api/train/graph_validators.py +216 -0
  258. synth_ai/sdk/api/train/graphgen.py +984 -0
  259. synth_ai/sdk/api/train/graphgen_models.py +823 -0
  260. synth_ai/sdk/api/train/graphgen_validators.py +109 -0
  261. synth_ai/sdk/api/train/local_api.py +10 -0
  262. synth_ai/sdk/api/train/pollers.py +124 -0
  263. synth_ai/sdk/api/train/progress/__init__.py +97 -0
  264. synth_ai/sdk/api/train/progress/dataclasses.py +569 -0
  265. synth_ai/sdk/api/train/progress/events.py +326 -0
  266. synth_ai/sdk/api/train/progress/results.py +428 -0
  267. synth_ai/sdk/api/train/progress/tracker.py +641 -0
  268. synth_ai/sdk/api/train/prompt_learning.py +469 -0
  269. synth_ai/sdk/api/train/rl.py +441 -0
  270. synth_ai/sdk/api/train/sft.py +396 -0
  271. synth_ai/sdk/api/train/summary.py +522 -0
  272. synth_ai/sdk/api/train/supported_algos.py +147 -0
  273. synth_ai/sdk/api/train/task_app.py +351 -0
  274. synth_ai/sdk/api/train/utils.py +279 -0
  275. synth_ai/sdk/api/train/validators.py +2424 -0
  276. synth_ai/sdk/graphs/__init__.py +15 -0
  277. synth_ai/sdk/graphs/completions.py +570 -0
  278. synth_ai/{inference → sdk/inference}/__init__.py +0 -1
  279. synth_ai/sdk/inference/client.py +128 -0
  280. synth_ai/sdk/jobs/__init__.py +16 -0
  281. synth_ai/sdk/jobs/client.py +371 -0
  282. synth_ai/sdk/judging/__init__.py +14 -0
  283. synth_ai/sdk/judging/base.py +24 -0
  284. synth_ai/sdk/judging/client.py +40 -0
  285. synth_ai/sdk/judging/schemas.py +222 -0
  286. synth_ai/sdk/judging/types.py +42 -0
  287. synth_ai/sdk/learning/__init__.py +99 -0
  288. synth_ai/sdk/learning/algorithms.py +14 -0
  289. synth_ai/{learning → sdk/learning}/client.py +121 -30
  290. synth_ai/sdk/learning/config.py +5 -0
  291. synth_ai/{learning → sdk/learning}/constants.py +0 -2
  292. synth_ai/sdk/learning/context_learning_client.py +531 -0
  293. synth_ai/sdk/learning/context_learning_types.py +292 -0
  294. synth_ai/sdk/learning/ft_client.py +7 -0
  295. synth_ai/{learning → sdk/learning}/health.py +15 -9
  296. synth_ai/{learning → sdk/learning}/jobs.py +44 -47
  297. synth_ai/sdk/learning/prompt_extraction.py +334 -0
  298. synth_ai/sdk/learning/prompt_learning_client.py +455 -0
  299. synth_ai/sdk/learning/prompt_learning_types.py +186 -0
  300. synth_ai/{rl → sdk/learning/rl}/__init__.py +13 -8
  301. synth_ai/{learning/rl_client.py → sdk/learning/rl/client.py} +89 -77
  302. synth_ai/sdk/learning/rl/config.py +31 -0
  303. synth_ai/{rl → sdk/learning/rl}/contracts.py +5 -14
  304. synth_ai/{rl → sdk/learning/rl}/env_keys.py +45 -16
  305. synth_ai/sdk/learning/rl/secrets.py +13 -0
  306. synth_ai/sdk/learning/rl_client.py +5 -0
  307. synth_ai/sdk/learning/sft/__init__.py +29 -0
  308. synth_ai/sdk/learning/sft/client.py +95 -0
  309. synth_ai/sdk/learning/sft/config.py +270 -0
  310. synth_ai/sdk/learning/sft/data.py +698 -0
  311. synth_ai/sdk/learning/sse.py +57 -0
  312. synth_ai/sdk/learning/validators.py +52 -0
  313. synth_ai/sdk/localapi/__init__.py +40 -0
  314. synth_ai/sdk/localapi/apps/__init__.py +28 -0
  315. synth_ai/sdk/localapi/client.py +10 -0
  316. synth_ai/sdk/localapi/contracts.py +10 -0
  317. synth_ai/sdk/localapi/helpers.py +519 -0
  318. synth_ai/sdk/localapi/rollouts.py +87 -0
  319. synth_ai/sdk/localapi/server.py +29 -0
  320. synth_ai/sdk/localapi/template.py +70 -0
  321. synth_ai/sdk/streaming/__init__.py +35 -0
  322. synth_ai/sdk/streaming/config.py +94 -0
  323. synth_ai/sdk/streaming/handlers.py +1997 -0
  324. synth_ai/sdk/streaming/streamer.py +713 -0
  325. synth_ai/sdk/streaming/types.py +112 -0
  326. synth_ai/sdk/task/__init__.py +164 -0
  327. synth_ai/sdk/task/apps/__init__.py +169 -0
  328. synth_ai/sdk/task/auth.py +165 -0
  329. synth_ai/sdk/task/client.py +175 -0
  330. synth_ai/sdk/task/config.py +257 -0
  331. synth_ai/sdk/task/contracts.py +219 -0
  332. synth_ai/sdk/task/datasets.py +108 -0
  333. synth_ai/sdk/task/errors.py +50 -0
  334. synth_ai/sdk/task/health.py +34 -0
  335. synth_ai/sdk/task/in_process.py +1190 -0
  336. synth_ai/sdk/task/in_process_runner.py +314 -0
  337. synth_ai/sdk/task/inference_api.py +299 -0
  338. synth_ai/sdk/task/json.py +111 -0
  339. synth_ai/sdk/task/proxy.py +287 -0
  340. synth_ai/sdk/task/rubrics/__init__.py +55 -0
  341. synth_ai/sdk/task/rubrics/loaders.py +156 -0
  342. synth_ai/sdk/task/rubrics/models.py +57 -0
  343. synth_ai/sdk/task/rubrics/scoring.py +116 -0
  344. synth_ai/sdk/task/rubrics/strict.py +149 -0
  345. synth_ai/sdk/task/rubrics.py +219 -0
  346. synth_ai/sdk/task/server.py +631 -0
  347. synth_ai/sdk/task/trace_correlation_helpers.py +539 -0
  348. synth_ai/sdk/task/tracing_utils.py +95 -0
  349. synth_ai/sdk/task/validators.py +441 -0
  350. synth_ai/sdk/task/vendors.py +59 -0
  351. synth_ai/sdk/training/__init__.py +102 -0
  352. synth_ai/sdk/tunnels/__init__.py +83 -0
  353. synth_ai/sdk/tunnels/cleanup.py +83 -0
  354. synth_ai/sdk/tunnels/ports.py +120 -0
  355. synth_ai/utils/__init__.py +213 -0
  356. synth_ai-0.4.3.dist-info/METADATA +262 -0
  357. synth_ai-0.4.3.dist-info/RECORD +370 -0
  358. {synth_ai-0.2.8.dev2.dist-info → synth_ai-0.4.3.dist-info}/entry_points.txt +0 -1
  359. synth_ai/cli/calc.py +0 -69
  360. synth_ai/cli/demo.py +0 -144
  361. synth_ai/cli/legacy_root_backup.py +0 -470
  362. synth_ai/cli/man.py +0 -106
  363. synth_ai/cli/rl_demo.py +0 -202
  364. synth_ai/cli/status.py +0 -133
  365. synth_ai/config/base_url.py +0 -107
  366. synth_ai/core/experiment.py +0 -15
  367. synth_ai/core/system.py +0 -15
  368. synth_ai/demos/core/__init__.py +0 -1
  369. synth_ai/demos/demo_task_apps/__init__.py +0 -1
  370. synth_ai/demos/demo_task_apps/math/config.toml +0 -129
  371. synth_ai/demos/demo_task_apps/math/deploy_task_app.sh +0 -22
  372. synth_ai/demos/demo_task_apps/math/modal_task_app.py +0 -415
  373. synth_ai/environments/__init__.py +0 -31
  374. synth_ai/environments/environment/__init__.py +0 -1
  375. synth_ai/environments/environment/artifacts/__init__.py +0 -1
  376. synth_ai/environments/environment/artifacts/base.py +0 -52
  377. synth_ai/environments/environment/core.py +0 -67
  378. synth_ai/environments/environment/db/__init__.py +0 -1
  379. synth_ai/environments/environment/db/sqlite.py +0 -45
  380. synth_ai/environments/environment/registry.py +0 -233
  381. synth_ai/environments/environment/resources/sqlite.py +0 -45
  382. synth_ai/environments/environment/results.py +0 -1
  383. synth_ai/environments/environment/rewards/__init__.py +0 -1
  384. synth_ai/environments/environment/rewards/core.py +0 -29
  385. synth_ai/environments/environment/shared_engine.py +0 -26
  386. synth_ai/environments/environment/tools/__init__.py +0 -200
  387. synth_ai/environments/examples/__init__.py +0 -1
  388. synth_ai/environments/examples/bandit/__init__.py +0 -33
  389. synth_ai/environments/examples/bandit/engine.py +0 -294
  390. synth_ai/environments/examples/bandit/environment.py +0 -194
  391. synth_ai/environments/examples/bandit/taskset.py +0 -200
  392. synth_ai/environments/examples/crafter_classic/__init__.py +0 -8
  393. synth_ai/environments/examples/crafter_classic/agent_demos/analyze_semantic_words_markdown.py +0 -250
  394. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_comprehensive_evaluation.py +0 -59
  395. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_browser.py +0 -152
  396. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_config.toml +0 -24
  397. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_framework.py +0 -1194
  398. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/crafter_synth_config.toml +0 -56
  399. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_config_modal.toml +0 -32
  400. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_traces_sft_turso.py +0 -738
  401. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/kick_off_ft_modal.py +0 -384
  402. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_action_results.py +0 -53
  403. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_agent_actions.py +0 -178
  404. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_latest_run.py +0 -222
  405. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_lm_traces.py +0 -183
  406. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_no_rewards.py +0 -210
  407. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/analyze_trace_issue.py +0 -206
  408. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/check_db_schema.py +0 -49
  409. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/check_latest_results.py +0 -64
  410. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/debug_agent_responses.py +0 -88
  411. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/old/quick_trace_check.py +0 -77
  412. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/compare_experiments.py +0 -324
  413. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/filter_traces_sft_turso.py +0 -580
  414. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/kick_off_ft_oai.py +0 -362
  415. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/multi_model_config.toml +0 -49
  416. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_enhanced_hooks.py +0 -332
  417. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_hook_events.py +0 -97
  418. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/analyze_hook_results.py +0 -217
  419. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/check_hook_storage.py +0 -87
  420. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/check_seeds.py +0 -88
  421. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/compare_seed_performance.py +0 -195
  422. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/custom_eval_pipelines.py +0 -400
  423. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/plot_hook_frequency.py +0 -195
  424. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/old/seed_analysis_summary.py +0 -56
  425. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/run_rollouts_for_models_and_compare_v3.py +0 -858
  426. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_quick_evaluation.py +0 -52
  427. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_react_agent.py +0 -874
  428. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +0 -1412
  429. synth_ai/environments/examples/crafter_classic/agent_demos/example_v3_usage.py +0 -216
  430. synth_ai/environments/examples/crafter_classic/agent_demos/old/compare_traces.py +0 -296
  431. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_comprehensive_evaluation.py +0 -58
  432. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_env_serialization.py +0 -464
  433. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_evaluation_browser.py +0 -152
  434. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_quick_evaluation.py +0 -51
  435. synth_ai/environments/examples/crafter_classic/agent_demos/old/crafter_trace_evaluation.py +0 -1412
  436. synth_ai/environments/examples/crafter_classic/agent_demos/old/debug_player_loss.py +0 -112
  437. synth_ai/environments/examples/crafter_classic/agent_demos/old/diagnose_service.py +0 -203
  438. synth_ai/environments/examples/crafter_classic/agent_demos/old/diagnose_slowness.py +0 -305
  439. synth_ai/environments/examples/crafter_classic/agent_demos/old/eval_by_difficulty.py +0 -126
  440. synth_ai/environments/examples/crafter_classic/agent_demos/old/eval_example.py +0 -94
  441. synth_ai/environments/examples/crafter_classic/agent_demos/old/explore_saved_states.py +0 -142
  442. synth_ai/environments/examples/crafter_classic/agent_demos/old/filter_traces_sft.py +0 -26
  443. synth_ai/environments/examples/crafter_classic/agent_demos/old/filter_traces_sft_OLD.py +0 -984
  444. synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_data_gemini.py +0 -724
  445. synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_data_modal.py +0 -386
  446. synth_ai/environments/examples/crafter_classic/agent_demos/old/generate_ft_metadata.py +0 -205
  447. synth_ai/environments/examples/crafter_classic/agent_demos/old/kick_off_ft_gemini.py +0 -150
  448. synth_ai/environments/examples/crafter_classic/agent_demos/old/kick_off_ft_modal.py +0 -283
  449. synth_ai/environments/examples/crafter_classic/agent_demos/old/prepare_vertex_ft.py +0 -280
  450. synth_ai/environments/examples/crafter_classic/agent_demos/old/profile_env_slowness.py +0 -456
  451. synth_ai/environments/examples/crafter_classic/agent_demos/old/replicate_issue.py +0 -166
  452. synth_ai/environments/examples/crafter_classic/agent_demos/old/run_and_eval.py +0 -102
  453. synth_ai/environments/examples/crafter_classic/agent_demos/old/run_comparison.py +0 -128
  454. synth_ai/environments/examples/crafter_classic/agent_demos/old/run_qwen_rollouts.py +0 -655
  455. synth_ai/environments/examples/crafter_classic/agent_demos/old/trace_eval_OLD.py +0 -202
  456. synth_ai/environments/examples/crafter_classic/agent_demos/old/validate_openai_format.py +0 -166
  457. synth_ai/environments/examples/crafter_classic/config_logging.py +0 -111
  458. synth_ai/environments/examples/crafter_classic/debug_translation.py +0 -0
  459. synth_ai/environments/examples/crafter_classic/engine.py +0 -579
  460. synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +0 -64
  461. synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +0 -6
  462. synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +0 -75
  463. synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +0 -267
  464. synth_ai/environments/examples/crafter_classic/environment.py +0 -404
  465. synth_ai/environments/examples/crafter_classic/taskset.py +0 -233
  466. synth_ai/environments/examples/crafter_classic/trace_hooks_v3.py +0 -228
  467. synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +0 -299
  468. synth_ai/environments/examples/crafter_custom/__init__.py +0 -4
  469. synth_ai/environments/examples/crafter_custom/agent_demos/__init__.py +0 -1
  470. synth_ai/environments/examples/crafter_custom/agent_demos/trace_eval.py +0 -202
  471. synth_ai/environments/examples/crafter_custom/crafter/__init__.py +0 -7
  472. synth_ai/environments/examples/crafter_custom/crafter/config.py +0 -182
  473. synth_ai/environments/examples/crafter_custom/crafter/constants.py +0 -8
  474. synth_ai/environments/examples/crafter_custom/crafter/engine.py +0 -269
  475. synth_ai/environments/examples/crafter_custom/crafter/env.py +0 -262
  476. synth_ai/environments/examples/crafter_custom/crafter/objects.py +0 -417
  477. synth_ai/environments/examples/crafter_custom/crafter/recorder.py +0 -187
  478. synth_ai/environments/examples/crafter_custom/crafter/worldgen.py +0 -118
  479. synth_ai/environments/examples/crafter_custom/dataset_builder.py +0 -373
  480. synth_ai/environments/examples/crafter_custom/environment.py +0 -312
  481. synth_ai/environments/examples/crafter_custom/old/analyze_diamond_issue.py +0 -159
  482. synth_ai/environments/examples/crafter_custom/old/analyze_diamond_spawning.py +0 -158
  483. synth_ai/environments/examples/crafter_custom/old/compare_worlds.py +0 -71
  484. synth_ai/environments/examples/crafter_custom/old/dataset_stats.py +0 -105
  485. synth_ai/environments/examples/crafter_custom/old/diamond_spawning_summary.py +0 -119
  486. synth_ai/environments/examples/crafter_custom/old/example_dataset_usage.py +0 -52
  487. synth_ai/environments/examples/crafter_custom/run_dataset.py +0 -305
  488. synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +0 -156
  489. synth_ai/environments/examples/enron/art_helpers/local_email_db.py +0 -281
  490. synth_ai/environments/examples/enron/art_helpers/types_enron.py +0 -25
  491. synth_ai/environments/examples/enron/engine.py +0 -295
  492. synth_ai/environments/examples/enron/environment.py +0 -166
  493. synth_ai/environments/examples/enron/taskset.py +0 -112
  494. synth_ai/environments/examples/enron/units/keyword_stats.py +0 -112
  495. synth_ai/environments/examples/minigrid/__init__.py +0 -48
  496. synth_ai/environments/examples/minigrid/agent_demos/minigrid_evaluation_framework.py +0 -1188
  497. synth_ai/environments/examples/minigrid/agent_demos/minigrid_quick_evaluation.py +0 -48
  498. synth_ai/environments/examples/minigrid/agent_demos/minigrid_react_agent.py +0 -562
  499. synth_ai/environments/examples/minigrid/agent_demos/minigrid_trace_evaluation.py +0 -221
  500. synth_ai/environments/examples/minigrid/engine.py +0 -589
  501. synth_ai/environments/examples/minigrid/environment.py +0 -274
  502. synth_ai/environments/examples/minigrid/environment_mapping.py +0 -242
  503. synth_ai/environments/examples/minigrid/puzzle_loader.py +0 -417
  504. synth_ai/environments/examples/minigrid/taskset.py +0 -583
  505. synth_ai/environments/examples/nethack/__init__.py +0 -7
  506. synth_ai/environments/examples/nethack/achievements.py +0 -337
  507. synth_ai/environments/examples/nethack/agent_demos/nethack_evaluation_framework.py +0 -981
  508. synth_ai/environments/examples/nethack/agent_demos/nethack_quick_evaluation.py +0 -74
  509. synth_ai/environments/examples/nethack/agent_demos/nethack_react_agent.py +0 -831
  510. synth_ai/environments/examples/nethack/engine.py +0 -739
  511. synth_ai/environments/examples/nethack/environment.py +0 -256
  512. synth_ai/environments/examples/nethack/helpers/__init__.py +0 -41
  513. synth_ai/environments/examples/nethack/helpers/action_mapping.py +0 -301
  514. synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +0 -402
  515. synth_ai/environments/examples/nethack/helpers/observation_utils.py +0 -433
  516. synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +0 -200
  517. synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +0 -269
  518. synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +0 -308
  519. synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +0 -431
  520. synth_ai/environments/examples/nethack/taskset.py +0 -323
  521. synth_ai/environments/examples/red/__init__.py +0 -7
  522. synth_ai/environments/examples/red/agent_demos/__init__.py +0 -1
  523. synth_ai/environments/examples/red/config_logging.py +0 -110
  524. synth_ai/environments/examples/red/engine.py +0 -694
  525. synth_ai/environments/examples/red/engine_helpers/__init__.py +0 -1
  526. synth_ai/environments/examples/red/engine_helpers/memory_map.py +0 -28
  527. synth_ai/environments/examples/red/engine_helpers/reward_components.py +0 -276
  528. synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +0 -142
  529. synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +0 -57
  530. synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +0 -284
  531. synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +0 -150
  532. synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +0 -138
  533. synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +0 -57
  534. synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +0 -331
  535. synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +0 -121
  536. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +0 -559
  537. synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +0 -313
  538. synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +0 -148
  539. synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +0 -247
  540. synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +0 -368
  541. synth_ai/environments/examples/red/engine_helpers/state_extraction.py +0 -140
  542. synth_ai/environments/examples/red/environment.py +0 -238
  543. synth_ai/environments/examples/red/taskset.py +0 -79
  544. synth_ai/environments/examples/red/units/__init__.py +0 -1
  545. synth_ai/environments/examples/sokoban/__init__.py +0 -1
  546. synth_ai/environments/examples/sokoban/agent_demos/sokoban_full_eval.py +0 -899
  547. synth_ai/environments/examples/sokoban/engine.py +0 -678
  548. synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +0 -1
  549. synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +0 -657
  550. synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +0 -18
  551. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +0 -3
  552. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +0 -131
  553. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +0 -370
  554. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +0 -332
  555. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +0 -306
  556. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +0 -67
  557. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +0 -115
  558. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +0 -123
  559. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +0 -394
  560. synth_ai/environments/examples/sokoban/environment.py +0 -229
  561. synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +0 -440
  562. synth_ai/environments/examples/sokoban/puzzle_loader.py +0 -312
  563. synth_ai/environments/examples/sokoban/taskset.py +0 -428
  564. synth_ai/environments/examples/sokoban/units/astar_common.py +0 -95
  565. synth_ai/environments/examples/tictactoe/__init__.py +0 -1
  566. synth_ai/environments/examples/tictactoe/engine.py +0 -368
  567. synth_ai/environments/examples/tictactoe/environment.py +0 -240
  568. synth_ai/environments/examples/tictactoe/taskset.py +0 -215
  569. synth_ai/environments/examples/verilog/__init__.py +0 -10
  570. synth_ai/environments/examples/verilog/engine.py +0 -329
  571. synth_ai/environments/examples/verilog/environment.py +0 -350
  572. synth_ai/environments/examples/verilog/taskset.py +0 -420
  573. synth_ai/environments/examples/wordle/__init__.py +0 -29
  574. synth_ai/environments/examples/wordle/engine.py +0 -398
  575. synth_ai/environments/examples/wordle/environment.py +0 -159
  576. synth_ai/environments/examples/wordle/helpers/generate_instances_wordfreq.py +0 -75
  577. synth_ai/environments/examples/wordle/taskset.py +0 -230
  578. synth_ai/environments/reproducibility/core.py +0 -42
  579. synth_ai/environments/reproducibility/helpers.py +0 -0
  580. synth_ai/environments/reproducibility/tree.py +0 -364
  581. synth_ai/environments/service/app.py +0 -98
  582. synth_ai/environments/service/core_routes.py +0 -1020
  583. synth_ai/environments/service/external_registry.py +0 -56
  584. synth_ai/environments/service/registry.py +0 -9
  585. synth_ai/environments/stateful/__init__.py +0 -1
  586. synth_ai/environments/stateful/core.py +0 -163
  587. synth_ai/environments/stateful/engine.py +0 -21
  588. synth_ai/environments/stateful/state.py +0 -7
  589. synth_ai/environments/tasks/api.py +0 -19
  590. synth_ai/environments/tasks/core.py +0 -80
  591. synth_ai/environments/tasks/filters.py +0 -41
  592. synth_ai/environments/tasks/utils.py +0 -91
  593. synth_ai/environments/v0_observability/history.py +0 -3
  594. synth_ai/environments/v0_observability/log.py +0 -2
  595. synth_ai/evals/base.py +0 -15
  596. synth_ai/experimental/synth_oss.py +0 -446
  597. synth_ai/handshake.py +0 -63
  598. synth_ai/http.py +0 -26
  599. synth_ai/http_client.py +0 -104
  600. synth_ai/inference/client.py +0 -20
  601. synth_ai/install_sqld.sh +0 -40
  602. synth_ai/jobs/client.py +0 -246
  603. synth_ai/learning/__init__.py +0 -24
  604. synth_ai/learning/config.py +0 -43
  605. synth_ai/learning/filtering.py +0 -0
  606. synth_ai/learning/ft_client.py +0 -59
  607. synth_ai/learning/offline/dpo.py +0 -0
  608. synth_ai/learning/offline/providers.py +0 -7
  609. synth_ai/learning/offline/sft.py +0 -0
  610. synth_ai/learning/offline/shared.py +0 -0
  611. synth_ai/learning/online/grpo.py +0 -0
  612. synth_ai/learning/online/irft.py +0 -0
  613. synth_ai/learning/prompts/banking77_injection_eval.py +0 -168
  614. synth_ai/learning/prompts/gepa.py +0 -0
  615. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +0 -213
  616. synth_ai/learning/prompts/mipro.py +0 -289
  617. synth_ai/learning/prompts/random_search.py +0 -246
  618. synth_ai/learning/prompts/run_mipro_banking77.py +0 -172
  619. synth_ai/learning/prompts/run_random_search_banking77.py +0 -324
  620. synth_ai/learning/sse.py +0 -58
  621. synth_ai/learning/validators.py +0 -48
  622. synth_ai/lm/__init__.py +0 -51
  623. synth_ai/lm/caching/constants.py +0 -6
  624. synth_ai/lm/caching/dbs.py +0 -0
  625. synth_ai/lm/caching/ephemeral.py +0 -102
  626. synth_ai/lm/caching/handler.py +0 -137
  627. synth_ai/lm/caching/initialize.py +0 -11
  628. synth_ai/lm/caching/persistent.py +0 -114
  629. synth_ai/lm/config.py +0 -110
  630. synth_ai/lm/constants.py +0 -32
  631. synth_ai/lm/core/__init__.py +0 -8
  632. synth_ai/lm/core/all.py +0 -73
  633. synth_ai/lm/core/exceptions.py +0 -7
  634. synth_ai/lm/core/main.py +0 -319
  635. synth_ai/lm/core/main_v3.py +0 -594
  636. synth_ai/lm/core/synth_models.py +0 -48
  637. synth_ai/lm/core/vendor_clients.py +0 -188
  638. synth_ai/lm/cost/__init__.py +0 -0
  639. synth_ai/lm/cost/monitor.py +0 -1
  640. synth_ai/lm/cost/statefulness.py +0 -1
  641. synth_ai/lm/injection.py +0 -80
  642. synth_ai/lm/overrides.py +0 -206
  643. synth_ai/lm/provider_support/__init__.py +0 -8
  644. synth_ai/lm/provider_support/anthropic.py +0 -972
  645. synth_ai/lm/provider_support/openai.py +0 -1139
  646. synth_ai/lm/provider_support/suppress_logging.py +0 -31
  647. synth_ai/lm/structured_outputs/__init__.py +0 -0
  648. synth_ai/lm/structured_outputs/handler.py +0 -440
  649. synth_ai/lm/structured_outputs/inject.py +0 -297
  650. synth_ai/lm/structured_outputs/rehabilitate.py +0 -185
  651. synth_ai/lm/tools/__init__.py +0 -3
  652. synth_ai/lm/tools/base.py +0 -172
  653. synth_ai/lm/unified_interface.py +0 -202
  654. synth_ai/lm/vendors/__init__.py +0 -0
  655. synth_ai/lm/vendors/base.py +0 -81
  656. synth_ai/lm/vendors/core/__init__.py +0 -0
  657. synth_ai/lm/vendors/core/anthropic_api.py +0 -387
  658. synth_ai/lm/vendors/core/gemini_api.py +0 -292
  659. synth_ai/lm/vendors/core/mistral_api.py +0 -322
  660. synth_ai/lm/vendors/core/openai_api.py +0 -225
  661. synth_ai/lm/vendors/core/synth_dev_api.py +0 -0
  662. synth_ai/lm/vendors/local/__init__.py +0 -0
  663. synth_ai/lm/vendors/local/ollama.py +0 -0
  664. synth_ai/lm/vendors/openai_standard.py +0 -780
  665. synth_ai/lm/vendors/openai_standard_responses.py +0 -256
  666. synth_ai/lm/vendors/retries.py +0 -22
  667. synth_ai/lm/vendors/supported/__init__.py +0 -0
  668. synth_ai/lm/vendors/supported/custom_endpoint.py +0 -417
  669. synth_ai/lm/vendors/supported/deepseek.py +0 -69
  670. synth_ai/lm/vendors/supported/grok.py +0 -75
  671. synth_ai/lm/vendors/supported/groq.py +0 -16
  672. synth_ai/lm/vendors/supported/ollama.py +0 -15
  673. synth_ai/lm/vendors/supported/openrouter.py +0 -74
  674. synth_ai/lm/vendors/supported/together.py +0 -11
  675. synth_ai/lm/vendors/synth_client.py +0 -808
  676. synth_ai/lm/warmup.py +0 -186
  677. synth_ai/rl/secrets.py +0 -19
  678. synth_ai/scripts/verify_rewards.py +0 -100
  679. synth_ai/task/__init__.py +0 -10
  680. synth_ai/task/contracts.py +0 -120
  681. synth_ai/task/health.py +0 -28
  682. synth_ai/task/validators.py +0 -12
  683. synth_ai/tracing/__init__.py +0 -30
  684. synth_ai/tracing_v1/__init__.py +0 -33
  685. synth_ai/tracing_v3/config.py +0 -84
  686. synth_ai/tracing_v3/storage/config.py +0 -62
  687. synth_ai/tracing_v3/turso/__init__.py +0 -25
  688. synth_ai/tracing_v3/turso/daemon.py +0 -144
  689. synth_ai/tracing_v3/turso/manager.py +0 -760
  690. synth_ai/v0/tracing/__init__.py +0 -0
  691. synth_ai/v0/tracing/abstractions.py +0 -224
  692. synth_ai/v0/tracing/base_client.py +0 -91
  693. synth_ai/v0/tracing/client_manager.py +0 -131
  694. synth_ai/v0/tracing/config.py +0 -142
  695. synth_ai/v0/tracing/context.py +0 -146
  696. synth_ai/v0/tracing/decorators.py +0 -682
  697. synth_ai/v0/tracing/events/__init__.py +0 -0
  698. synth_ai/v0/tracing/events/manage.py +0 -147
  699. synth_ai/v0/tracing/events/scope.py +0 -86
  700. synth_ai/v0/tracing/events/store.py +0 -228
  701. synth_ai/v0/tracing/immediate_client.py +0 -151
  702. synth_ai/v0/tracing/local.py +0 -18
  703. synth_ai/v0/tracing/log_client_base.py +0 -73
  704. synth_ai/v0/tracing/retry_queue.py +0 -186
  705. synth_ai/v0/tracing/trackers.py +0 -515
  706. synth_ai/v0/tracing/upload.py +0 -512
  707. synth_ai/v0/tracing/utils.py +0 -9
  708. synth_ai/v0/tracing_v1/__init__.py +0 -16
  709. synth_ai/v0/tracing_v1/abstractions.py +0 -224
  710. synth_ai/v0/tracing_v1/base_client.py +0 -91
  711. synth_ai/v0/tracing_v1/client_manager.py +0 -131
  712. synth_ai/v0/tracing_v1/config.py +0 -142
  713. synth_ai/v0/tracing_v1/context.py +0 -146
  714. synth_ai/v0/tracing_v1/decorators.py +0 -703
  715. synth_ai/v0/tracing_v1/events/__init__.py +0 -0
  716. synth_ai/v0/tracing_v1/events/manage.py +0 -147
  717. synth_ai/v0/tracing_v1/events/scope.py +0 -86
  718. synth_ai/v0/tracing_v1/events/store.py +0 -228
  719. synth_ai/v0/tracing_v1/immediate_client.py +0 -151
  720. synth_ai/v0/tracing_v1/local.py +0 -18
  721. synth_ai/v0/tracing_v1/log_client_base.py +0 -73
  722. synth_ai/v0/tracing_v1/retry_queue.py +0 -186
  723. synth_ai/v0/tracing_v1/trackers.py +0 -515
  724. synth_ai/v0/tracing_v1/upload.py +0 -527
  725. synth_ai/v0/tracing_v1/utils.py +0 -9
  726. synth_ai/zyk/__init__.py +0 -30
  727. synth_ai-0.2.8.dev2.dist-info/METADATA +0 -129
  728. synth_ai-0.2.8.dev2.dist-info/RECORD +0 -420
  729. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/__init__.py +0 -0
  730. /synth_ai/{lm/caching → core/apps}/__init__.py +0 -0
  731. /synth_ai/{tracing_v3 → core/tracing_v3}/lm_call_record_abstractions.py +0 -0
  732. /synth_ai/{tracing_v3 → core/tracing_v3}/storage/__init__.py +0 -0
  733. /synth_ai/{tracing_v3 → core/tracing_v3}/storage/exceptions.py +0 -0
  734. /synth_ai/{tracing_v3 → core/tracing_v3}/storage/types.py +0 -0
  735. /synth_ai/{compound/cais.py → py.typed} +0 -0
  736. /synth_ai/{learning → sdk/learning}/core.py +0 -0
  737. /synth_ai/{learning → sdk/learning}/gateway.py +0 -0
  738. {synth_ai-0.2.8.dev2.dist-info → synth_ai-0.4.3.dist-info}/WHEEL +0 -0
  739. {synth_ai-0.2.8.dev2.dist-info → synth_ai-0.4.3.dist-info}/licenses/LICENSE +0 -0
  740. {synth_ai-0.2.8.dev2.dist-info → synth_ai-0.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,884 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ import contextlib
5
+ import hashlib
6
+ import importlib
7
+ import importlib.util
8
+ import inspect
9
+ import os
10
+ import sys
11
+ import types
12
+ from collections.abc import Callable, Iterable, Iterator, Sequence
13
+ from dataclasses import dataclass
14
+ from datetime import datetime
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ import click
19
+ from click.exceptions import Abort
20
+
21
+ try:
22
+ _task_apps_module = importlib.import_module("synth_ai.sdk.task.apps")
23
+ ModalDeploymentConfig = _task_apps_module.ModalDeploymentConfig
24
+ TaskAppConfig = _task_apps_module.TaskAppConfig
25
+ TaskAppEntry = _task_apps_module.TaskAppEntry
26
+ registry = _task_apps_module.registry
27
+ except Exception:
28
+ class _UnavailableTaskAppType: # pragma: no cover - used when optional deps missing
29
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
30
+ raise RuntimeError("Task app registry is unavailable in this environment")
31
+
32
+ ModalDeploymentConfig = TaskAppConfig = TaskAppEntry = _UnavailableTaskAppType # type: ignore[assignment]
33
+ registry: dict[str, Any] = {}
34
+
35
+ REPO_ROOT = Path(__file__).resolve().parents[3]
36
+
37
+ DEFAULT_IGNORE_DIRS = {
38
+ ".git",
39
+ "__pycache__",
40
+ "node_modules",
41
+ "venv",
42
+ ".venv",
43
+ "build",
44
+ "dist",
45
+ ".mypy_cache",
46
+ ".pytest_cache",
47
+ }
48
+
49
+ DEFAULT_SEARCH_RELATIVE = (
50
+ Path("."),
51
+ Path("examples"),
52
+ Path("synth_ai"),
53
+ )
54
+
55
+
56
+ @dataclass
57
+ class AppChoice:
58
+ app_id: str
59
+ label: str
60
+ path: Path
61
+ source: str
62
+ description: str | None = None
63
+ aliases: tuple[str, ...] = ()
64
+ entry: TaskAppEntry | None = None
65
+ entry_loader: Callable[[], TaskAppEntry] | None = None
66
+ modal_script: Path | None = None
67
+ lineno: int | None = None
68
+
69
+ def ensure_entry(self) -> TaskAppEntry:
70
+ if self.entry is not None:
71
+ return self.entry
72
+ if self.entry_loader is None:
73
+ raise click.ClickException(f"Unable to load task app '{self.app_id}' from {self.path}")
74
+ entry = self.entry_loader()
75
+ self.entry = entry
76
+ return entry
77
+
78
+
79
+ def _temporary_sys_path(paths: Sequence[Path]):
80
+ """Context manager to prepend entries to sys.path temporarily."""
81
+
82
+ @contextlib.contextmanager
83
+ def _manager() -> Iterator[None]:
84
+ added: list[str] = []
85
+ for p in paths:
86
+ try:
87
+ resolved = str(p.resolve())
88
+ except Exception:
89
+ continue
90
+ if resolved in sys.path:
91
+ continue
92
+ sys.path.insert(0, resolved)
93
+ added.append(resolved)
94
+ try:
95
+ yield None
96
+ finally:
97
+ for entry in added:
98
+ with contextlib.suppress(ValueError):
99
+ sys.path.remove(entry)
100
+
101
+ return _manager()
102
+
103
+
104
+ def _possible_module_names(path: Path, module_search_roots: Sequence[Path]) -> list[tuple[str, Path]]:
105
+ candidates: list[tuple[str, Path]] = []
106
+ for root in module_search_roots:
107
+ try:
108
+ resolved_root = root.resolve()
109
+ except Exception:
110
+ continue
111
+ if not resolved_root.exists():
112
+ continue
113
+ with contextlib.suppress(ValueError):
114
+ relative = path.resolve().relative_to(resolved_root)
115
+ stem = relative.with_suffix("")
116
+ parts = list(stem.parts)
117
+ if not parts:
118
+ continue
119
+ module_name = ".".join(parts)
120
+ if module_name:
121
+ candidates.append((module_name, resolved_root))
122
+ return candidates
123
+
124
+
125
+ def _ensure_parent_namespace(module_name: str, search_root: Path) -> None:
126
+ """Ensure namespace packages exist for dotted module names."""
127
+
128
+ parts = module_name.split(".")
129
+ for depth in range(1, len(parts)):
130
+ parent_name = ".".join(parts[:depth])
131
+ if parent_name in sys.modules:
132
+ continue
133
+ parent_module = types.ModuleType(parent_name)
134
+ candidate_dir = search_root.joinpath(*parts[:depth])
135
+ try:
136
+ resolved = candidate_dir.resolve()
137
+ except Exception:
138
+ resolved = search_root.resolve()
139
+ parent_module.__path__ = [str(resolved)] # type: ignore[attr-defined]
140
+ sys.modules[parent_name] = parent_module
141
+
142
+
143
+ def _should_ignore_path(path: Path) -> bool:
144
+ return any(part in DEFAULT_IGNORE_DIRS for part in path.parts)
145
+
146
+
147
+ def _candidate_search_roots() -> list[Path]:
148
+ roots: list[Path] = []
149
+
150
+ try:
151
+ demo_module = importlib.import_module("synth_ai.cli.demo_apps.demo_task_apps.core")
152
+ except Exception:
153
+ demo_module = None
154
+ if demo_module:
155
+ load_demo_dir = getattr(demo_module, "load_demo_dir", None)
156
+ if callable(load_demo_dir):
157
+ try:
158
+ demo_dir = load_demo_dir()
159
+ except Exception:
160
+ demo_dir = None
161
+ if demo_dir:
162
+ demo_path = Path(demo_dir)
163
+ if demo_path.exists() and demo_path.is_dir():
164
+ roots.append(demo_path.resolve())
165
+
166
+ env_paths = os.environ.get("SYNTH_TASK_APP_SEARCH_PATH")
167
+ if env_paths:
168
+ for chunk in env_paths.split(os.pathsep):
169
+ if chunk:
170
+ roots.append(Path(chunk).expanduser())
171
+
172
+ cwd = Path.cwd().resolve()
173
+ roots.append(cwd)
174
+
175
+ for rel in DEFAULT_SEARCH_RELATIVE:
176
+ try:
177
+ candidate = (cwd / rel).resolve()
178
+ except Exception:
179
+ continue
180
+ roots.append(candidate)
181
+
182
+ seen: set[Path] = set()
183
+ ordered: list[Path] = []
184
+ for root in roots:
185
+ try:
186
+ resolved = root.resolve()
187
+ except Exception:
188
+ continue
189
+ if resolved in seen or not resolved.exists():
190
+ continue
191
+ seen.add(resolved)
192
+ ordered.append(resolved)
193
+ return ordered
194
+
195
+
196
+ def _eval_config_sort_key(path: Path) -> tuple[int, int, int, str]:
197
+ name = path.name.lower()
198
+ parent_names = {p.name.lower() for p in path.parents}
199
+ in_configs = 0 if "configs" in parent_names else 1
200
+ in_examples = 0 if "examples" in parent_names else 1
201
+ starts_eval = 0 if name.startswith("eval") else 1
202
+ return (in_configs, in_examples, starts_eval, str(path))
203
+
204
+
205
+ def discover_eval_config_paths() -> list[Path]:
206
+ candidates: list[Path] = []
207
+ seen: set[Path] = set()
208
+ for root in _candidate_search_roots():
209
+ if not root.exists() or not root.is_dir():
210
+ continue
211
+ try:
212
+ root = root.resolve()
213
+ except Exception:
214
+ continue
215
+ for path in root.rglob("*.toml"):
216
+ if not path.is_file() or _should_ignore_path(path):
217
+ continue
218
+ name_lower = path.name.lower()
219
+ if "eval" not in name_lower and "evaluation" not in name_lower:
220
+ continue
221
+ try:
222
+ resolved = path.resolve()
223
+ except Exception:
224
+ continue
225
+ if resolved in seen:
226
+ continue
227
+ seen.add(resolved)
228
+ candidates.append(resolved)
229
+ candidates.sort(key=_eval_config_sort_key)
230
+ return candidates
231
+
232
+
233
+ class _TaskAppConfigVisitor(ast.NodeVisitor):
234
+ def __init__(self) -> None:
235
+ self.matches: list[tuple[str, int]] = []
236
+
237
+ def visit_Call(self, node: ast.Call) -> None: # noqa: D401
238
+ if _is_task_app_config_call(node):
239
+ app_id = _extract_app_id(node)
240
+ if app_id:
241
+ self.matches.append((app_id, getattr(node, "lineno", 0)))
242
+ elif _is_register_task_app_call(node):
243
+ app_id = _extract_register_app_id(node)
244
+ if app_id:
245
+ self.matches.append((app_id, getattr(node, "lineno", 0)))
246
+ self.generic_visit(node)
247
+
248
+
249
+ class _ModalAppVisitor(ast.NodeVisitor):
250
+ def __init__(self) -> None:
251
+ self.app_aliases: set[str] = set()
252
+ self.modal_aliases: set[str] = set()
253
+ self.matches: list[tuple[str, int]] = []
254
+
255
+ def visit_ImportFrom(self, node: ast.ImportFrom) -> None: # noqa: D401
256
+ if node.module == "modal":
257
+ for alias in node.names:
258
+ if alias.name == "App":
259
+ self.app_aliases.add(alias.asname or alias.name)
260
+ self.generic_visit(node)
261
+
262
+ def visit_Import(self, node: ast.Import) -> None: # noqa: D401
263
+ for alias in node.names:
264
+ if alias.name == "modal":
265
+ self.modal_aliases.add(alias.asname or alias.name)
266
+ self.generic_visit(node)
267
+
268
+ def visit_Call(self, node: ast.Call) -> None: # noqa: D401
269
+ func = node.func
270
+ if isinstance(func, ast.Name) and func.id in self.app_aliases:
271
+ name = _extract_modal_app_name(node)
272
+ if name:
273
+ self.matches.append((name, getattr(node, "lineno", 0)))
274
+ elif isinstance(func, ast.Attribute):
275
+ if (
276
+ isinstance(func.value, ast.Name)
277
+ and func.value.id in self.modal_aliases
278
+ and func.attr == "App"
279
+ ):
280
+ name = _extract_modal_app_name(node)
281
+ if name:
282
+ self.matches.append((name, getattr(node, "lineno", 0)))
283
+ self.generic_visit(node)
284
+
285
+
286
+ def _is_task_app_config_call(node: ast.Call) -> bool:
287
+ func = node.func
288
+ return (isinstance(func, ast.Name) and func.id == "TaskAppConfig") or (
289
+ isinstance(func, ast.Attribute) and func.attr == "TaskAppConfig"
290
+ )
291
+
292
+
293
+ def _extract_app_id(node: ast.Call) -> str | None:
294
+ for kw in node.keywords:
295
+ if (
296
+ kw.arg == "app_id"
297
+ and isinstance(kw.value, ast.Constant)
298
+ and isinstance(kw.value.value, str)
299
+ ):
300
+ return kw.value.value
301
+ if node.args:
302
+ first = node.args[0]
303
+ if isinstance(first, ast.Constant) and isinstance(first.value, str):
304
+ return first.value
305
+ return None
306
+
307
+
308
+ def _is_register_task_app_call(node: ast.Call) -> bool:
309
+ func = node.func
310
+ return (isinstance(func, ast.Name) and func.id in {"register_task_app", "register_local_api"}) or (
311
+ isinstance(func, ast.Attribute) and func.attr in {"register_task_app", "register_local_api"}
312
+ )
313
+
314
+
315
+ def _extract_register_app_id(node: ast.Call) -> str | None:
316
+ for kw in node.keywords:
317
+ if kw.arg == "entry" and isinstance(kw.value, ast.Call):
318
+ entry_call = kw.value
319
+ if isinstance(entry_call.func, ast.Name) and entry_call.func.id in {"TaskAppEntry", "LocalAPIEntry"}:
320
+ for entry_kw in entry_call.keywords:
321
+ if (
322
+ entry_kw.arg in {"app_id", "api_id"}
323
+ and isinstance(entry_kw.value, ast.Constant)
324
+ and isinstance(entry_kw.value.value, str)
325
+ ):
326
+ return entry_kw.value.value
327
+ return None
328
+
329
+
330
+ def _extract_modal_app_name(node: ast.Call) -> str | None:
331
+ if node.args:
332
+ first = node.args[0]
333
+ if isinstance(first, ast.Constant) and isinstance(first.value, str):
334
+ return first.value
335
+ for kw in node.keywords:
336
+ if kw.arg == "name" and isinstance(kw.value, ast.Constant) and isinstance(kw.value.value, str):
337
+ return kw.value.value
338
+ return None
339
+
340
+
341
+ def _collect_registered_choices() -> list[AppChoice]:
342
+ result: list[AppChoice] = []
343
+ for entry in registry.list(): # type: ignore[attr-defined]
344
+ module_name = entry.config_factory.__module__
345
+ module = sys.modules.get(module_name)
346
+ if module is None:
347
+ module = importlib.import_module(module_name)
348
+ module_file = getattr(module, "__file__", None)
349
+ path = Path(module_file).resolve() if module_file else REPO_ROOT
350
+ result.append(
351
+ AppChoice(
352
+ app_id=entry.app_id,
353
+ label=entry.app_id,
354
+ path=path,
355
+ source="registered",
356
+ description=entry.description,
357
+ aliases=tuple(entry.aliases),
358
+ entry=entry,
359
+ )
360
+ )
361
+ return result
362
+
363
+
364
+ def _collect_scanned_task_configs() -> list[AppChoice]:
365
+ results: list[AppChoice] = []
366
+ seen: set[tuple[str, Path]] = set()
367
+ for root in _candidate_search_roots():
368
+ if not root.exists() or not root.is_dir():
369
+ continue
370
+ try:
371
+ root_resolved = root.resolve()
372
+ except Exception:
373
+ continue
374
+ for path in root.rglob("*.py"):
375
+ if not path.is_file() or _should_ignore_path(path):
376
+ continue
377
+ try:
378
+ source = path.read_text(encoding="utf-8")
379
+ tree = ast.parse(source, filename=str(path))
380
+ except Exception:
381
+ continue
382
+ visitor = _TaskAppConfigVisitor()
383
+ visitor.visit(tree)
384
+ for app_id, lineno in visitor.matches:
385
+ key = (app_id, path.resolve())
386
+ if key in seen:
387
+ continue
388
+ seen.add(key)
389
+
390
+ def _loader(p: Path = path.resolve(), a: str = app_id, roots: tuple[Path, ...] = (root_resolved,)):
391
+ return _load_entry_from_path(p, a, module_search_roots=roots)
392
+
393
+ results.append(
394
+ AppChoice(
395
+ app_id=app_id,
396
+ label=app_id,
397
+ path=path.resolve(),
398
+ source="discovered",
399
+ description=f"TaskAppConfig in {path.name} (line {lineno})",
400
+ entry_loader=_loader,
401
+ lineno=lineno,
402
+ )
403
+ )
404
+ return results
405
+
406
+
407
+ def _collect_modal_scripts() -> list[AppChoice]:
408
+ results: list[AppChoice] = []
409
+ seen: set[tuple[str, Path]] = set()
410
+ for root in _candidate_search_roots():
411
+ if not root.exists() or not root.is_dir():
412
+ continue
413
+ for path in root.rglob("*.py"):
414
+ if not path.is_file() or _should_ignore_path(path):
415
+ continue
416
+ try:
417
+ source = path.read_text(encoding="utf-8")
418
+ tree = ast.parse(source, filename=str(path))
419
+ except Exception:
420
+ continue
421
+ visitor = _ModalAppVisitor()
422
+ visitor.visit(tree)
423
+ for app_name, lineno in visitor.matches:
424
+ key = (app_name, path.resolve())
425
+ if key in seen:
426
+ continue
427
+ seen.add(key)
428
+ results.append(
429
+ AppChoice(
430
+ app_id=app_name,
431
+ label=app_name,
432
+ path=path.resolve(),
433
+ source="modal-script",
434
+ description=f"Modal App '{app_name}' in {path.name} (line {lineno})",
435
+ modal_script=path.resolve(),
436
+ lineno=lineno,
437
+ )
438
+ )
439
+ return results
440
+
441
+
442
+ def _app_choice_sort_key(choice: AppChoice) -> tuple[int, int, int, int, int, str, str]:
443
+ demo_rank = 1
444
+ try:
445
+ demo_module = importlib.import_module("synth_ai.cli.demo_apps.demo_task_apps.core")
446
+ except Exception:
447
+ demo_module = None
448
+ if demo_module:
449
+ load_demo_dir = getattr(demo_module, "load_demo_dir", None)
450
+ if callable(load_demo_dir):
451
+ try:
452
+ demo_dir = load_demo_dir()
453
+ except Exception:
454
+ demo_dir = None
455
+ if demo_dir:
456
+ demo_path = Path(demo_dir).resolve()
457
+ if choice.path.is_relative_to(demo_path):
458
+ demo_rank = 0
459
+
460
+ cwd_rank = 1
461
+ try:
462
+ cwd = Path.cwd().resolve()
463
+ if choice.path.is_relative_to(cwd):
464
+ try:
465
+ rel_path = choice.path.relative_to(cwd)
466
+ if len(rel_path.parts) <= 2:
467
+ cwd_rank = 0
468
+ except Exception:
469
+ pass
470
+ except Exception:
471
+ pass
472
+
473
+ modal_rank = 1 if choice.modal_script else 0
474
+ name = choice.path.name.lower()
475
+ if name.endswith("_task_app.py") or name.endswith("task_app.py"):
476
+ file_rank = 0
477
+ elif name.endswith("_app.py") or "task_app" in name:
478
+ file_rank = 1
479
+ elif name.endswith(".py"):
480
+ file_rank = 2
481
+ else:
482
+ file_rank = 3
483
+
484
+ directory_rank = 0 if choice.path.parent.name.lower() in {"task_app", "task_apps"} else 1
485
+
486
+ return (
487
+ demo_rank,
488
+ cwd_rank,
489
+ modal_rank,
490
+ file_rank,
491
+ directory_rank,
492
+ choice.app_id,
493
+ str(choice.path),
494
+ )
495
+
496
+
497
+ def _choice_matches_identifier(choice: AppChoice, identifier: str) -> bool:
498
+ ident = identifier.strip()
499
+ if not ident:
500
+ return False
501
+ return ident == choice.app_id or ident == choice.label or ident in choice.aliases
502
+
503
+
504
+ def _choice_has_modal_support(choice: AppChoice) -> bool:
505
+ if choice.modal_script:
506
+ return True
507
+ try:
508
+ entry = choice.ensure_entry()
509
+ except click.ClickException:
510
+ return _has_modal_support_in_file(choice.path)
511
+ return entry.modal is not None # type: ignore[attr-defined]
512
+
513
+
514
+ def _choice_has_local_support(choice: AppChoice) -> bool:
515
+ if choice.modal_script:
516
+ return False
517
+ try:
518
+ choice.ensure_entry()
519
+ except click.ClickException:
520
+ return False
521
+ return True
522
+
523
+
524
+ def _has_modal_support_in_file(path: Path) -> bool:
525
+ try:
526
+ source = path.read_text(encoding="utf-8")
527
+ tree = ast.parse(source, filename=str(path))
528
+ except Exception:
529
+ return False
530
+
531
+ for node in ast.walk(tree):
532
+ if isinstance(node, ast.Call) and _is_register_task_app_call(node):
533
+ for kw in node.keywords:
534
+ if kw.arg == "entry" and isinstance(kw.value, ast.Call):
535
+ entry_call = kw.value
536
+ if (
537
+ isinstance(entry_call.func, ast.Name)
538
+ and entry_call.func.id in {"TaskAppEntry", "LocalAPIEntry"}
539
+ ):
540
+ for entry_kw in entry_call.keywords:
541
+ if entry_kw.arg == "modal" and isinstance(entry_kw.value, ast.Call):
542
+ modal_call = entry_kw.value
543
+ if (
544
+ isinstance(modal_call.func, ast.Name)
545
+ and modal_call.func.id == "ModalDeploymentConfig"
546
+ ):
547
+ return True
548
+ return False
549
+
550
+
551
+ def _extract_modal_config_from_file(path: Path) -> ModalDeploymentConfig | None:
552
+ try:
553
+ source = path.read_text(encoding="utf-8")
554
+ tree = ast.parse(source, filename=str(path))
555
+ except Exception:
556
+ return None
557
+
558
+ for node in ast.walk(tree):
559
+ if isinstance(node, ast.Call) and _is_register_task_app_call(node):
560
+ for kw in node.keywords:
561
+ if kw.arg == "entry" and isinstance(kw.value, ast.Call):
562
+ entry_call = kw.value
563
+ if (
564
+ isinstance(entry_call.func, ast.Name)
565
+ and entry_call.func.id in {"TaskAppEntry", "LocalAPIEntry"}
566
+ ):
567
+ for entry_kw in entry_call.keywords:
568
+ if entry_kw.arg == "modal" and isinstance(entry_kw.value, ast.Call):
569
+ modal_call = entry_kw.value
570
+ if (
571
+ isinstance(modal_call.func, ast.Name)
572
+ and modal_call.func.id == "ModalDeploymentConfig"
573
+ ):
574
+ return _build_modal_config_from_ast(modal_call)
575
+ return None
576
+
577
+
578
+ def _build_modal_config_from_ast(modal_call: ast.Call) -> ModalDeploymentConfig | None:
579
+ try:
580
+ kwargs = {}
581
+ for kw in modal_call.keywords:
582
+ if kw.arg and isinstance(kw.value, ast.Constant):
583
+ kwargs[kw.arg] = kw.value.value
584
+ elif kw.arg == "pip_packages" and isinstance(kw.value, ast.List | ast.Tuple):
585
+ packages = []
586
+ for elt in kw.value.elts:
587
+ if isinstance(elt, ast.Constant):
588
+ packages.append(elt.value)
589
+ kwargs[kw.arg] = tuple(packages)
590
+ elif kw.arg == "extra_local_dirs" and isinstance(kw.value, ast.List | ast.Tuple):
591
+ dirs = []
592
+ for elt in kw.value.elts:
593
+ if isinstance(elt, ast.List | ast.Tuple) and len(elt.elts) == 2:
594
+ src = elt.elts[0].value if isinstance(elt.elts[0], ast.Constant) else None
595
+ dst = elt.elts[1].value if isinstance(elt.elts[1], ast.Constant) else None
596
+ if src and dst:
597
+ dirs.append((src, dst))
598
+ kwargs[kw.arg] = tuple(dirs)
599
+ elif kw.arg == "secret_names" and isinstance(kw.value, ast.List | ast.Tuple):
600
+ secrets = []
601
+ for elt in kw.value.elts:
602
+ if isinstance(elt, ast.Constant):
603
+ secrets.append(elt.value)
604
+ kwargs[kw.arg] = tuple(secrets)
605
+ elif kw.arg == "volume_mounts" and isinstance(kw.value, ast.List | ast.Tuple):
606
+ mounts = []
607
+ for elt in kw.value.elts:
608
+ if isinstance(elt, ast.List | ast.Tuple) and len(elt.elts) == 2:
609
+ name = elt.elts[0].value if isinstance(elt.elts[0], ast.Constant) else None
610
+ mount = elt.elts[1].value if isinstance(elt.elts[1], ast.Constant) else None
611
+ if name and mount:
612
+ mounts.append((name, mount))
613
+ kwargs[kw.arg] = tuple(mounts)
614
+ return ModalDeploymentConfig(**kwargs)
615
+ except Exception:
616
+ return None
617
+
618
+
619
+ def _format_choice(choice: AppChoice, index: int | None = None) -> str:
620
+ prefix = f"[{index}] " if index is not None else ""
621
+ try:
622
+ mtime = choice.path.stat().st_mtime
623
+ modified_str = datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M:%S")
624
+ details = f"Modified: {modified_str}"
625
+ except Exception:
626
+ details = choice.description or "No timestamp available"
627
+ return f"{prefix}{choice.app_id} ({choice.source}) – {details}"
628
+
629
+
630
+ def _prompt_user_for_choice(choices: list[AppChoice]) -> AppChoice:
631
+ click.echo("Select a task app:")
632
+ for idx, choice in enumerate(choices, start=1):
633
+ click.echo(_format_choice(choice, idx))
634
+ try:
635
+ response = click.prompt("Enter choice", default="1", type=str).strip() or "1"
636
+ except (Abort, EOFError, KeyboardInterrupt) as exc:
637
+ raise click.ClickException("Task app selection cancelled by user") from exc
638
+ if not response.isdigit():
639
+ raise click.ClickException("Selection must be a number")
640
+ index = int(response)
641
+ if not 1 <= index <= len(choices):
642
+ raise click.ClickException("Selection out of range")
643
+ return choices[index - 1]
644
+
645
+
646
+ def _collect_task_app_choices() -> list[AppChoice]:
647
+ registry.clear()
648
+ choices: list[AppChoice] = []
649
+ with contextlib.suppress(Exception):
650
+ importlib.import_module("synth_ai.cli.demo_apps.demo_task_apps")
651
+ choices.extend(_collect_registered_choices())
652
+ choices.extend(_collect_scanned_task_configs())
653
+ choices.extend(_collect_modal_scripts())
654
+
655
+ unique: dict[tuple[str, Path], AppChoice] = {}
656
+ ordered: list[AppChoice] = []
657
+ for choice in choices:
658
+ key = (choice.app_id, choice.path.resolve())
659
+ if key in unique:
660
+ existing = unique[key]
661
+ if existing.source == "registered" and choice.source != "registered":
662
+ continue
663
+ if choice.source == "registered" and existing.source != "registered":
664
+ unique[key] = choice
665
+ idx = ordered.index(existing)
666
+ ordered[idx] = choice
667
+ continue
668
+ unique[key] = choice
669
+ ordered.append(choice)
670
+ ordered.sort(key=_app_choice_sort_key)
671
+ return ordered
672
+
673
+
674
+ def select_app_choice(app_id: str | None, purpose: str) -> AppChoice:
675
+ choices = _collect_task_app_choices()
676
+ if purpose in {"serve", "eval"}:
677
+ filtered = [c for c in choices if _choice_has_local_support(c)]
678
+ elif purpose in {"deploy", "modal-serve"}:
679
+ filtered = [c for c in choices if _choice_has_modal_support(c)]
680
+ else:
681
+ filtered = choices
682
+
683
+ if not filtered:
684
+ raise click.ClickException("No task apps discovered for this command.")
685
+
686
+ if app_id:
687
+ matches = [c for c in filtered if _choice_matches_identifier(c, app_id)]
688
+ if not matches:
689
+ available = ", ".join(sorted({c.app_id for c in filtered}))
690
+ raise click.ClickException(f"Task app '{app_id}' not found. Available: {available}")
691
+ if len(matches) == 1:
692
+ return matches[0]
693
+ if purpose in {"deploy", "modal-serve"}:
694
+ modal_matches = [c for c in matches if _choice_has_modal_support(c)]
695
+ if len(modal_matches) == 1:
696
+ return modal_matches[0]
697
+ if modal_matches:
698
+ matches = modal_matches
699
+ filtered = matches
700
+
701
+ filtered.sort(key=_app_choice_sort_key)
702
+ if len(filtered) == 1:
703
+ choice = filtered[0]
704
+ click.echo(_format_choice(choice))
705
+ return choice
706
+ return _prompt_user_for_choice(filtered)
707
+
708
+
709
+ def _import_task_app_module(
710
+ resolved: Path,
711
+ module_name: str,
712
+ *,
713
+ namespace_root: Path | None,
714
+ sys_path_roots: Sequence[Path],
715
+ ensure_namespace: bool = True,
716
+ ) -> types.ModuleType:
717
+ spec = importlib.util.spec_from_file_location(module_name, str(resolved))
718
+ if spec is None or spec.loader is None:
719
+ raise click.ClickException(f"Unable to load Python module from {resolved}")
720
+
721
+ module = importlib.util.module_from_spec(spec)
722
+ sys.modules[module_name] = module
723
+
724
+ with _temporary_sys_path(sys_path_roots):
725
+ if ensure_namespace and namespace_root is not None and "." in module_name:
726
+ _ensure_parent_namespace(module_name, namespace_root)
727
+
728
+ registry.clear()
729
+
730
+ try:
731
+ spec.loader.exec_module(module)
732
+ except Exception:
733
+ sys.modules.pop(module_name, None)
734
+ raise
735
+
736
+ return module
737
+
738
+
739
+ def _load_entry_from_path(
740
+ path: Path, app_id: str, module_search_roots: Sequence[Path] | None = None
741
+ ) -> TaskAppEntry:
742
+ resolved = path.resolve()
743
+ search_roots: list[Path] = []
744
+ seen_roots: set[Path] = set()
745
+
746
+ def _append_root(candidate: Path) -> None:
747
+ try:
748
+ resolved_root = candidate.resolve()
749
+ except Exception:
750
+ return
751
+ if resolved_root in seen_roots:
752
+ return
753
+ seen_roots.add(resolved_root)
754
+ search_roots.append(resolved_root)
755
+
756
+ for root in module_search_roots or []:
757
+ _append_root(root)
758
+ _append_root(resolved.parent)
759
+ _append_root(REPO_ROOT)
760
+
761
+ last_error: Exception | None = None
762
+ module: types.ModuleType | None = None
763
+
764
+ for module_name, namespace_root in _possible_module_names(resolved, search_roots):
765
+ try:
766
+ module = _import_task_app_module(
767
+ resolved,
768
+ module_name,
769
+ namespace_root=namespace_root,
770
+ sys_path_roots=search_roots,
771
+ ensure_namespace=True,
772
+ )
773
+ break
774
+ except Exception as exc:
775
+ last_error = exc
776
+ continue
777
+
778
+ if module is None:
779
+ hashed_name = f"_synth_task_app_{hashlib.md5(str(resolved).encode(), usedforsecurity=False).hexdigest()}"
780
+ try:
781
+ module = _import_task_app_module(
782
+ resolved,
783
+ hashed_name,
784
+ namespace_root=None,
785
+ sys_path_roots=search_roots,
786
+ ensure_namespace=False,
787
+ )
788
+ except Exception as exc:
789
+ detail = last_error or exc
790
+ raise click.ClickException(f"Failed to import {resolved}: {detail}") from detail
791
+
792
+ config_obj: TaskAppConfig | None = None
793
+ factory_callable: Callable[[], TaskAppConfig] | None = None
794
+
795
+ for attr_name in dir(module):
796
+ try:
797
+ attr = getattr(module, attr_name)
798
+ except Exception:
799
+ continue
800
+ if isinstance(attr, TaskAppConfig) and attr.app_id == app_id:
801
+
802
+ def _return_config(cfg: TaskAppConfig = attr) -> TaskAppConfig:
803
+ return cfg
804
+
805
+ factory_callable = _return_config
806
+ config_obj = attr
807
+ break
808
+
809
+ if factory_callable is None:
810
+ for attr_name in dir(module):
811
+ if attr_name.startswith("_"):
812
+ continue
813
+ try:
814
+ attr = getattr(module, attr_name)
815
+ except Exception:
816
+ continue
817
+ if not callable(attr):
818
+ continue
819
+ try:
820
+ sig = inspect.signature(attr)
821
+ except (TypeError, ValueError):
822
+ continue
823
+ has_required = any(
824
+ param.kind
825
+ in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD)
826
+ and param.default is inspect._empty
827
+ for param in sig.parameters.values()
828
+ )
829
+ if has_required:
830
+ continue
831
+ try:
832
+ result = attr()
833
+ except Exception:
834
+ continue
835
+ if isinstance(result, TaskAppConfig) and result.app_id == app_id:
836
+
837
+ def _factory_noargs(func: Callable[[], TaskAppConfig] = attr) -> TaskAppConfig:
838
+ return func()
839
+
840
+ factory_callable = _factory_noargs
841
+ config_obj = result
842
+ break
843
+
844
+ if factory_callable is None or config_obj is None:
845
+ try:
846
+ entry = registry.get(app_id)
847
+ if entry is None:
848
+ raise KeyError(f"TaskApp '{app_id}' not found in registry")
849
+ return entry
850
+ except KeyError as exc:
851
+ raise click.ClickException(
852
+ f"Could not locate TaskAppConfig for '{app_id}' in {resolved}."
853
+ ) from exc
854
+
855
+ modal_cfg: ModalDeploymentConfig | None = None
856
+ for attr_name in dir(module):
857
+ try:
858
+ attr = getattr(module, attr_name)
859
+ except Exception:
860
+ continue
861
+ if isinstance(attr, ModalDeploymentConfig):
862
+ modal_cfg = attr
863
+ break
864
+
865
+ if modal_cfg is None:
866
+ modal_cfg = _extract_modal_config_from_file(resolved)
867
+
868
+ env_files: Iterable[str] = getattr(module, "ENV_FILES", ()) # type: ignore[arg-type]
869
+
870
+ return TaskAppEntry(
871
+ app_id=app_id,
872
+ description=inspect.getdoc(module) or f"Discovered task app in {resolved.name}",
873
+ config_factory=factory_callable,
874
+ aliases=(),
875
+ env_files=tuple(str(Path(p)) for p in env_files if p),
876
+ modal=modal_cfg,
877
+ )
878
+
879
+
880
+ __all__ = [
881
+ "AppChoice",
882
+ "discover_eval_config_paths",
883
+ "select_app_choice",
884
+ ]