synth-ai 0.2.6.dev1__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 (738) 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/cli/demo_apps/core/cli.py +1735 -0
  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 +117 -51
  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/cli/demo_apps/demo_task_apps/math/_common.py +16 -0
  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} +21 -3
  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 -102
  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.6.dev1.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 -131
  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 -137
  364. synth_ai/cli/status.py +0 -133
  365. synth_ai/config/base_url.py +0 -98
  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/core/cli.py +0 -685
  370. synth_ai/demos/demo_task_apps/__init__.py +0 -1
  371. synth_ai/demos/demo_task_apps/math/config.toml +0 -44
  372. synth_ai/demos/demo_task_apps/math/deploy_task_app.sh +0 -22
  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 -724
  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 -91
  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/http.py +0 -102
  598. synth_ai/inference/client.py +0 -20
  599. synth_ai/install_sqld.sh +0 -40
  600. synth_ai/jobs/client.py +0 -246
  601. synth_ai/learning/__init__.py +0 -24
  602. synth_ai/learning/config.py +0 -43
  603. synth_ai/learning/filtering.py +0 -0
  604. synth_ai/learning/ft_client.py +0 -59
  605. synth_ai/learning/offline/dpo.py +0 -0
  606. synth_ai/learning/offline/providers.py +0 -7
  607. synth_ai/learning/offline/sft.py +0 -0
  608. synth_ai/learning/offline/shared.py +0 -0
  609. synth_ai/learning/online/grpo.py +0 -0
  610. synth_ai/learning/online/irft.py +0 -0
  611. synth_ai/learning/prompts/banking77_injection_eval.py +0 -168
  612. synth_ai/learning/prompts/gepa.py +0 -0
  613. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +0 -213
  614. synth_ai/learning/prompts/mipro.py +0 -289
  615. synth_ai/learning/prompts/random_search.py +0 -246
  616. synth_ai/learning/prompts/run_mipro_banking77.py +0 -172
  617. synth_ai/learning/prompts/run_random_search_banking77.py +0 -324
  618. synth_ai/learning/sse.py +0 -58
  619. synth_ai/learning/validators.py +0 -48
  620. synth_ai/lm/__init__.py +0 -51
  621. synth_ai/lm/caching/constants.py +0 -6
  622. synth_ai/lm/caching/dbs.py +0 -0
  623. synth_ai/lm/caching/ephemeral.py +0 -102
  624. synth_ai/lm/caching/handler.py +0 -137
  625. synth_ai/lm/caching/initialize.py +0 -11
  626. synth_ai/lm/caching/persistent.py +0 -114
  627. synth_ai/lm/config.py +0 -110
  628. synth_ai/lm/constants.py +0 -32
  629. synth_ai/lm/core/__init__.py +0 -8
  630. synth_ai/lm/core/all.py +0 -73
  631. synth_ai/lm/core/exceptions.py +0 -7
  632. synth_ai/lm/core/main.py +0 -319
  633. synth_ai/lm/core/main_v3.py +0 -594
  634. synth_ai/lm/core/synth_models.py +0 -48
  635. synth_ai/lm/core/vendor_clients.py +0 -188
  636. synth_ai/lm/cost/__init__.py +0 -0
  637. synth_ai/lm/cost/monitor.py +0 -1
  638. synth_ai/lm/cost/statefulness.py +0 -1
  639. synth_ai/lm/injection.py +0 -80
  640. synth_ai/lm/overrides.py +0 -206
  641. synth_ai/lm/provider_support/__init__.py +0 -8
  642. synth_ai/lm/provider_support/anthropic.py +0 -972
  643. synth_ai/lm/provider_support/openai.py +0 -1139
  644. synth_ai/lm/provider_support/suppress_logging.py +0 -31
  645. synth_ai/lm/structured_outputs/__init__.py +0 -0
  646. synth_ai/lm/structured_outputs/handler.py +0 -440
  647. synth_ai/lm/structured_outputs/inject.py +0 -297
  648. synth_ai/lm/structured_outputs/rehabilitate.py +0 -185
  649. synth_ai/lm/tools/__init__.py +0 -3
  650. synth_ai/lm/tools/base.py +0 -172
  651. synth_ai/lm/unified_interface.py +0 -202
  652. synth_ai/lm/vendors/__init__.py +0 -0
  653. synth_ai/lm/vendors/base.py +0 -81
  654. synth_ai/lm/vendors/core/__init__.py +0 -0
  655. synth_ai/lm/vendors/core/anthropic_api.py +0 -387
  656. synth_ai/lm/vendors/core/gemini_api.py +0 -292
  657. synth_ai/lm/vendors/core/mistral_api.py +0 -322
  658. synth_ai/lm/vendors/core/openai_api.py +0 -220
  659. synth_ai/lm/vendors/core/synth_dev_api.py +0 -0
  660. synth_ai/lm/vendors/local/__init__.py +0 -0
  661. synth_ai/lm/vendors/local/ollama.py +0 -0
  662. synth_ai/lm/vendors/openai_standard.py +0 -780
  663. synth_ai/lm/vendors/openai_standard_responses.py +0 -256
  664. synth_ai/lm/vendors/retries.py +0 -22
  665. synth_ai/lm/vendors/supported/__init__.py +0 -0
  666. synth_ai/lm/vendors/supported/custom_endpoint.py +0 -417
  667. synth_ai/lm/vendors/supported/deepseek.py +0 -69
  668. synth_ai/lm/vendors/supported/grok.py +0 -75
  669. synth_ai/lm/vendors/supported/groq.py +0 -16
  670. synth_ai/lm/vendors/supported/ollama.py +0 -15
  671. synth_ai/lm/vendors/supported/openrouter.py +0 -74
  672. synth_ai/lm/vendors/supported/together.py +0 -11
  673. synth_ai/lm/vendors/synth_client.py +0 -808
  674. synth_ai/lm/warmup.py +0 -186
  675. synth_ai/rl/secrets.py +0 -19
  676. synth_ai/scripts/verify_rewards.py +0 -100
  677. synth_ai/task/__init__.py +0 -10
  678. synth_ai/task/contracts.py +0 -120
  679. synth_ai/task/health.py +0 -28
  680. synth_ai/task/validators.py +0 -12
  681. synth_ai/tracing/__init__.py +0 -30
  682. synth_ai/tracing_v1/__init__.py +0 -33
  683. synth_ai/tracing_v3/config.py +0 -84
  684. synth_ai/tracing_v3/storage/config.py +0 -62
  685. synth_ai/tracing_v3/turso/__init__.py +0 -25
  686. synth_ai/tracing_v3/turso/daemon.py +0 -144
  687. synth_ai/tracing_v3/turso/manager.py +0 -760
  688. synth_ai/v0/tracing/__init__.py +0 -0
  689. synth_ai/v0/tracing/abstractions.py +0 -224
  690. synth_ai/v0/tracing/base_client.py +0 -91
  691. synth_ai/v0/tracing/client_manager.py +0 -131
  692. synth_ai/v0/tracing/config.py +0 -140
  693. synth_ai/v0/tracing/context.py +0 -146
  694. synth_ai/v0/tracing/decorators.py +0 -680
  695. synth_ai/v0/tracing/events/__init__.py +0 -0
  696. synth_ai/v0/tracing/events/manage.py +0 -147
  697. synth_ai/v0/tracing/events/scope.py +0 -86
  698. synth_ai/v0/tracing/events/store.py +0 -228
  699. synth_ai/v0/tracing/immediate_client.py +0 -151
  700. synth_ai/v0/tracing/local.py +0 -18
  701. synth_ai/v0/tracing/log_client_base.py +0 -73
  702. synth_ai/v0/tracing/retry_queue.py +0 -186
  703. synth_ai/v0/tracing/trackers.py +0 -515
  704. synth_ai/v0/tracing/upload.py +0 -510
  705. synth_ai/v0/tracing/utils.py +0 -9
  706. synth_ai/v0/tracing_v1/__init__.py +0 -16
  707. synth_ai/v0/tracing_v1/abstractions.py +0 -224
  708. synth_ai/v0/tracing_v1/base_client.py +0 -91
  709. synth_ai/v0/tracing_v1/client_manager.py +0 -131
  710. synth_ai/v0/tracing_v1/config.py +0 -140
  711. synth_ai/v0/tracing_v1/context.py +0 -146
  712. synth_ai/v0/tracing_v1/decorators.py +0 -701
  713. synth_ai/v0/tracing_v1/events/__init__.py +0 -0
  714. synth_ai/v0/tracing_v1/events/manage.py +0 -147
  715. synth_ai/v0/tracing_v1/events/scope.py +0 -86
  716. synth_ai/v0/tracing_v1/events/store.py +0 -228
  717. synth_ai/v0/tracing_v1/immediate_client.py +0 -151
  718. synth_ai/v0/tracing_v1/local.py +0 -18
  719. synth_ai/v0/tracing_v1/log_client_base.py +0 -73
  720. synth_ai/v0/tracing_v1/retry_queue.py +0 -186
  721. synth_ai/v0/tracing_v1/trackers.py +0 -515
  722. synth_ai/v0/tracing_v1/upload.py +0 -525
  723. synth_ai/v0/tracing_v1/utils.py +0 -9
  724. synth_ai/zyk/__init__.py +0 -30
  725. synth_ai-0.2.6.dev1.dist-info/METADATA +0 -106
  726. synth_ai-0.2.6.dev1.dist-info/RECORD +0 -416
  727. /synth_ai/{demos → cli/demo_apps}/demo_task_apps/math/__init__.py +0 -0
  728. /synth_ai/{lm/caching → core/apps}/__init__.py +0 -0
  729. /synth_ai/{tracing_v3 → core/tracing_v3}/lm_call_record_abstractions.py +0 -0
  730. /synth_ai/{tracing_v3 → core/tracing_v3}/storage/__init__.py +0 -0
  731. /synth_ai/{tracing_v3 → core/tracing_v3}/storage/exceptions.py +0 -0
  732. /synth_ai/{tracing_v3 → core/tracing_v3}/storage/types.py +0 -0
  733. /synth_ai/{compound/cais.py → py.typed} +0 -0
  734. /synth_ai/{learning → sdk/learning}/core.py +0 -0
  735. /synth_ai/{learning → sdk/learning}/gateway.py +0 -0
  736. {synth_ai-0.2.6.dev1.dist-info → synth_ai-0.4.3.dist-info}/WHEEL +0 -0
  737. {synth_ai-0.2.6.dev1.dist-info → synth_ai-0.4.3.dist-info}/licenses/LICENSE +0 -0
  738. {synth_ai-0.2.6.dev1.dist-info → synth_ai-0.4.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,713 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import contextlib
5
+ import json
6
+ import logging
7
+ import random
8
+ import sys
9
+ import time
10
+ from dataclasses import dataclass
11
+ from typing import Any, AsyncIterator, Iterable, Sequence
12
+
13
+ import aiohttp
14
+
15
+ from synth_ai.core.http import AsyncHttpClient, sleep
16
+ from synth_ai.core.telemetry import log_info
17
+
18
+ from .config import StreamConfig
19
+ from .handlers import StreamHandler
20
+ from .types import StreamMessage, StreamType
21
+
22
+ TERMINAL_STATUSES = {"succeeded", "failed", "cancelled", "canceled", "completed"}
23
+ TERMINAL_EVENT_SUCCESS = {
24
+ "sft.job.completed",
25
+ "rl.train.completed",
26
+ "rl.job.completed",
27
+ "context.learning.job.completed",
28
+ "workflow.completed",
29
+ "training.completed",
30
+ # GraphGen / Graph Evolve events
31
+ "optimization_completed",
32
+ "graphgen.completed",
33
+ "graph_evolve.completed",
34
+ # GEPA / Prompt Learning events
35
+ "prompt.learning.gepa.complete",
36
+ "prompt.learning.mipro.complete",
37
+ "gepa.complete",
38
+ "mipro.complete",
39
+ }
40
+ TERMINAL_EVENT_FAILURE = {
41
+ "sft.job.failed",
42
+ "rl.train.failed",
43
+ "rl.job.failed",
44
+ "context.learning.job.failed",
45
+ "workflow.failed",
46
+ "training.failed",
47
+ # GraphGen / Graph Evolve events
48
+ "optimization_failed",
49
+ "graphgen.failed",
50
+ "graph_evolve.failed",
51
+ # GEPA / Prompt Learning events
52
+ "prompt.learning.gepa.failed",
53
+ "prompt.learning.mipro.failed",
54
+ "gepa.failed",
55
+ "mipro.failed",
56
+ }
57
+
58
+
59
+ @dataclass(slots=True)
60
+ class StreamEndpoints:
61
+ """Collection of endpoint paths (with optional fallbacks) to poll for a job."""
62
+
63
+ status: str | None
64
+ events: str | None = None
65
+ metrics: str | None = None
66
+ timeline: str | None = None
67
+ status_fallbacks: tuple[str, ...] = ()
68
+ event_fallbacks: tuple[str, ...] = ()
69
+ metric_fallbacks: tuple[str, ...] = ()
70
+ timeline_fallbacks: tuple[str, ...] = ()
71
+
72
+ @classmethod
73
+ def learning(cls, job_id: str) -> StreamEndpoints:
74
+ base = f"/learning/jobs/{job_id}"
75
+ return cls(
76
+ status=base,
77
+ events=f"{base}/events",
78
+ metrics=f"{base}/metrics",
79
+ timeline=f"{base}/timeline",
80
+ )
81
+
82
+ @classmethod
83
+ def prompt_learning(cls, job_id: str) -> StreamEndpoints:
84
+ """Endpoints for prompt learning jobs (MIPRO/GEPA)."""
85
+ base = f"/prompt-learning/online/jobs/{job_id}"
86
+ return cls(
87
+ status=base,
88
+ events=f"{base}/events",
89
+ metrics=f"{base}/metrics",
90
+ timeline=None,
91
+ status_fallbacks=(
92
+ f"/learning/jobs/{job_id}",
93
+ f"/orchestration/jobs/{job_id}",
94
+ ),
95
+ event_fallbacks=(
96
+ f"/learning/jobs/{job_id}/events",
97
+ f"/orchestration/jobs/{job_id}/events",
98
+ ),
99
+ )
100
+
101
+ @classmethod
102
+ def context_learning(cls, job_id: str) -> StreamEndpoints:
103
+ """Endpoints for context learning jobs."""
104
+ base = f"/context-learning/jobs/{job_id}"
105
+ return cls(
106
+ status=base,
107
+ events=f"{base}/events",
108
+ metrics=f"{base}/metrics",
109
+ timeline=None,
110
+ )
111
+
112
+ @property
113
+ def events_stream_url(self) -> str | None:
114
+ """Get the SSE streaming URL for events if available."""
115
+ if self.events:
116
+ if self.events.endswith("/events"):
117
+ return f"{self.events}/stream"
118
+ return None
119
+
120
+ @classmethod
121
+ def rl(cls, job_id: str) -> StreamEndpoints:
122
+ base = f"/rl/jobs/{job_id}"
123
+ return cls(
124
+ status=base,
125
+ events=f"{base}/events",
126
+ metrics=f"{base}/metrics",
127
+ timeline=f"{base}/timeline",
128
+ status_fallbacks=(
129
+ f"/learning/jobs/{job_id}",
130
+ f"/orchestration/jobs/{job_id}",
131
+ ),
132
+ event_fallbacks=(
133
+ f"/learning/jobs/{job_id}/events",
134
+ f"/orchestration/jobs/{job_id}/events",
135
+ ),
136
+ metric_fallbacks=(
137
+ f"/learning/jobs/{job_id}/metrics",
138
+ ),
139
+ timeline_fallbacks=(
140
+ f"/learning/jobs/{job_id}/timeline",
141
+ ),
142
+ )
143
+
144
+ @classmethod
145
+ def graphgen(cls, job_id: str) -> StreamEndpoints:
146
+ """Endpoints for GraphGen workflow optimization jobs.
147
+
148
+ GraphGen jobs use /api/graphgen/jobs/{job_id} endpoints.
149
+ The backend handles GraphGen -> graph_evolve job ID resolution internally using job_relationships.
150
+ No fallbacks needed - GraphGen endpoints resolve everything.
151
+ """
152
+ base = f"/graphgen/jobs/{job_id}"
153
+ return cls(
154
+ status=base,
155
+ events=f"{base}/events",
156
+ metrics=f"{base}/metrics",
157
+ timeline=None,
158
+ )
159
+
160
+ @classmethod
161
+ def adas(cls, job_id: str) -> StreamEndpoints:
162
+ """Legacy alias for GraphGen workflow optimization jobs."""
163
+ return cls.graphgen(job_id)
164
+
165
+
166
+ class JobStreamer:
167
+ """Poll job endpoints and dispatch messages to configured handlers."""
168
+
169
+ def __init__(
170
+ self,
171
+ *,
172
+ base_url: str,
173
+ api_key: str,
174
+ job_id: str,
175
+ endpoints: StreamEndpoints | None = None,
176
+ config: StreamConfig | None = None,
177
+ handlers: Sequence[StreamHandler] | None = None,
178
+ interval_seconds: float = 2.0,
179
+ timeout_seconds: float | None = None,
180
+ http_timeout: float = 60.0,
181
+ http_client: AsyncHttpClient | None = None,
182
+ sleep_fn= sleep,
183
+ ) -> None:
184
+ self.base_url = base_url.rstrip("/")
185
+ self.api_key = api_key
186
+ self.job_id = job_id
187
+ self.endpoints = endpoints or StreamEndpoints.learning(job_id)
188
+ self.config = config or StreamConfig.default()
189
+ self.handlers: list[StreamHandler] = list(handlers or [])
190
+ self.interval_seconds = interval_seconds
191
+ self.timeout_seconds = timeout_seconds
192
+ self.http_timeout = http_timeout
193
+ self._http = http_client
194
+ self._sleep = sleep_fn
195
+
196
+ status_sources: list[str | None] = [self.endpoints.status]
197
+ status_sources.extend(self.endpoints.status_fallbacks)
198
+ self._status_paths = [p for p in status_sources if p]
199
+
200
+ event_sources: list[str | None] = [self.endpoints.events]
201
+ event_sources.extend(self.endpoints.event_fallbacks)
202
+ self._event_paths = [p for p in event_sources if p]
203
+
204
+ metric_sources: list[str | None] = [self.endpoints.metrics]
205
+ metric_sources.extend(self.endpoints.metric_fallbacks)
206
+ self._metric_paths = [p for p in metric_sources if p]
207
+
208
+ timeline_sources: list[str | None] = [self.endpoints.timeline]
209
+ timeline_sources.extend(self.endpoints.timeline_fallbacks)
210
+ self._timeline_paths = [p for p in timeline_sources if p]
211
+
212
+ self._last_seq_by_stream: dict[str, int] = {}
213
+ self._metric_cursors: dict[str, tuple[int | None, str]] = {}
214
+ self._seen_messages: set[str] = set()
215
+ self._last_status_payload: dict[str, Any] | None = None
216
+ self._last_status_value: str | None = None
217
+ self._terminal_seen = False
218
+ self._terminal_event_status: str | None = None
219
+
220
+ if not self.handlers:
221
+ from .handlers import CLIHandler
222
+
223
+ self.handlers = [CLIHandler()]
224
+
225
+ async def stream_until_terminal(self) -> dict[str, Any]:
226
+ """Stream configured endpoints until the job reaches a terminal state."""
227
+ ctx: dict[str, Any] = {"job_id": self.job_id, "base_url": self.base_url}
228
+ log_info("JobStreamer.stream_until_terminal invoked", ctx=ctx)
229
+ http_cm = self._http or AsyncHttpClient(self.base_url, self.api_key, timeout=self.http_timeout)
230
+ async with http_cm as http:
231
+ # Use SSE streaming exclusively for events (prompt learning jobs)
232
+ # SSE provides real-time event delivery from Redis, avoiding empty polling responses
233
+ sse_url = self.endpoints.events_stream_url
234
+ if sse_url and StreamType.EVENTS in self.config.enabled_streams:
235
+ # SSE streaming - real-time event delivery, with concurrent status polling
236
+
237
+ # Create a queue to receive events from SSE stream
238
+ event_queue: asyncio.Queue = asyncio.Queue()
239
+ sse_done = asyncio.Event()
240
+
241
+ async def sse_reader():
242
+ """Read SSE events and put them in the queue."""
243
+ try:
244
+ async for event_msg in self._stream_events_sse(sse_url):
245
+ await event_queue.put(event_msg)
246
+ if self._terminal_seen:
247
+ break
248
+ except Exception as e:
249
+ await event_queue.put(e) # Put exception in queue
250
+ finally:
251
+ sse_done.set()
252
+
253
+ async def status_poller():
254
+ """Periodically poll status while SSE stream is active."""
255
+ while not sse_done.is_set() and not self._terminal_seen:
256
+ await asyncio.sleep(2.0) # Check every 2 seconds
257
+ if self._terminal_seen or sse_done.is_set():
258
+ break
259
+
260
+ status = await self._refresh_status(http)
261
+
262
+ metric_messages = await self._poll_metrics(http)
263
+ timeline_messages = await self._poll_timeline(http)
264
+ self._dispatch(metric_messages + timeline_messages)
265
+
266
+ # Check for terminal status
267
+ if status and status.lower() in TERMINAL_STATUSES:
268
+ self._terminal_seen = True
269
+ break
270
+
271
+ # Start both tasks concurrently
272
+ sse_task = asyncio.create_task(sse_reader())
273
+ status_task = asyncio.create_task(status_poller())
274
+
275
+ try:
276
+ # Process events from queue
277
+ while not self._terminal_seen:
278
+ # Wait for event or timeout
279
+ try:
280
+ item = await asyncio.wait_for(event_queue.get(), timeout=1.0)
281
+ except asyncio.TimeoutError:
282
+ # No event received, check if SSE is done or terminal
283
+ if sse_done.is_set() or self._terminal_seen:
284
+ break
285
+ continue
286
+
287
+ # Handle exception from SSE reader
288
+ if isinstance(item, Exception):
289
+ raise item
290
+
291
+ # Process event
292
+ self._dispatch([item])
293
+
294
+ # Poll metrics/timeline after each event
295
+ metric_messages = await self._poll_metrics(http)
296
+ timeline_messages = await self._poll_timeline(http)
297
+ self._dispatch(metric_messages + timeline_messages)
298
+
299
+ # Check for terminal status
300
+ if self._terminal_seen:
301
+ break
302
+ finally:
303
+ # Cancel tasks
304
+ sse_task.cancel()
305
+ status_task.cancel()
306
+ try:
307
+ await asyncio.gather(sse_task, status_task, return_exceptions=True)
308
+ except Exception:
309
+ pass
310
+ else:
311
+ # No SSE endpoint available - use polling for events
312
+ while True:
313
+ status = await self._refresh_status(http)
314
+
315
+ # Check status FIRST before polling events/metrics
316
+ if status and status.lower() in TERMINAL_STATUSES:
317
+ self._terminal_seen = True
318
+ break
319
+ if self._terminal_seen:
320
+ break
321
+
322
+ event_messages = await self._poll_events(http)
323
+ metric_messages = await self._poll_metrics(http)
324
+ timeline_messages = await self._poll_timeline(http)
325
+
326
+ self._dispatch(event_messages + metric_messages + timeline_messages)
327
+
328
+ # Check again after polling (terminal events might have been received)
329
+ if self._terminal_seen:
330
+ break
331
+ if status and status.lower() in TERMINAL_STATUSES:
332
+ self._terminal_seen = True
333
+ break
334
+
335
+ await self._sleep(self.interval_seconds)
336
+
337
+ for handler in self.handlers:
338
+ with contextlib.suppress(Exception):
339
+ handler.flush()
340
+
341
+ final_status = self._terminal_event_status or self._last_status_value or "unknown"
342
+ if self._last_status_payload:
343
+ self._last_status_payload["status"] = final_status
344
+ return self._last_status_payload
345
+ return {"job_id": self.job_id, "status": final_status}
346
+
347
+ async def _stream_events_sse(self, sse_url: str) -> AsyncIterator[StreamMessage]:
348
+ """Stream events via Server-Sent Events (SSE)."""
349
+ url = f"{self.base_url.rstrip('/')}/{sse_url.lstrip('/')}"
350
+ headers = {
351
+ "Accept": "text/event-stream",
352
+ "Cache-Control": "no-cache",
353
+ "authorization": f"Bearer {self.api_key}",
354
+ }
355
+
356
+ # Create a separate session for SSE (long-lived connection)
357
+ timeout = aiohttp.ClientTimeout(total=None) # No timeout for SSE
358
+ async with aiohttp.ClientSession(headers=headers, timeout=timeout) as session:
359
+ async with session.get(url) as resp:
360
+ if resp.status != 200:
361
+ raise Exception(f"SSE endpoint returned {resp.status}: {await resp.text()}")
362
+
363
+ print(f"[DEBUG] SSE stream connected to {url}, status={resp.status}", file=sys.stderr)
364
+ buffer = ""
365
+ event_count = 0
366
+ last_event_time = time.time()
367
+ no_events_warning_printed = False
368
+
369
+ # Read SSE stream in chunks and parse events
370
+ async for chunk in resp.content.iter_chunked(8192):
371
+ current_time = time.time()
372
+
373
+ # Warn if no events received for 10 seconds (events should be streaming)
374
+ if event_count == 1 and current_time - last_event_time > 10 and not no_events_warning_printed:
375
+ print(f"[DEBUG] WARNING: No events received via SSE for 10s after connection. Backend may not be publishing to Redis (check SSE_USE_REDIS env var).", file=sys.stderr)
376
+ no_events_warning_printed = True
377
+
378
+ buffer += chunk.decode("utf-8", errors="ignore")
379
+
380
+ # SSE events are separated by double newlines
381
+ while "\n\n" in buffer:
382
+ event_block, buffer = buffer.split("\n\n", 1)
383
+ event_block = event_block.strip()
384
+
385
+ if not event_block:
386
+ continue
387
+
388
+ event_data = {}
389
+ event_id = None
390
+ event_type_line = None
391
+
392
+ # Parse SSE event block line by line
393
+ for event_line in event_block.split("\n"):
394
+ event_line = event_line.strip()
395
+ if not event_line or event_line.startswith(":"):
396
+ continue # Skip comments/empty lines
397
+ if event_line.startswith("id:"):
398
+ event_id = event_line[3:].strip()
399
+ elif event_line.startswith("event:"):
400
+ event_type_line = event_line[6:].strip()
401
+ elif event_line.startswith("data:"):
402
+ data_str = event_line[5:].strip()
403
+ try:
404
+ event_data = json.loads(data_str)
405
+ except json.JSONDecodeError as e:
406
+ print(f"[DEBUG] Failed to parse SSE data: {e}, data={data_str[:200]}", file=sys.stderr)
407
+ continue
408
+
409
+ # Debug: log what we parsed
410
+ if event_data:
411
+ event_count += 1
412
+ last_event_time = time.time()
413
+ print(f"[DEBUG] Parsed SSE event #{event_count}: type={event_data.get('type')}, seq={event_data.get('seq')}", file=sys.stderr)
414
+
415
+ if event_data and "type" in event_data:
416
+ # Convert SSE event to StreamMessage
417
+ event_job_id = event_data.get("job_id") or self.job_id
418
+ msg = StreamMessage.from_event(event_job_id, event_data)
419
+
420
+ # Update sequence tracking
421
+ seq = event_data.get("seq")
422
+ if seq is not None:
423
+ try:
424
+ seq_int = int(seq)
425
+ if sse_url not in self._last_seq_by_stream or seq_int > self._last_seq_by_stream[sse_url]:
426
+ self._last_seq_by_stream[sse_url] = seq_int
427
+ except (TypeError, ValueError):
428
+ pass
429
+
430
+ # Check for terminal events
431
+ event_type = str(event_data.get("type", "")).lower()
432
+ if event_type in TERMINAL_EVENT_SUCCESS:
433
+ self._terminal_seen = True
434
+ self._terminal_event_status = "succeeded"
435
+ elif event_type in TERMINAL_EVENT_FAILURE:
436
+ self._terminal_seen = True
437
+ self._terminal_event_status = "failed"
438
+
439
+ yield msg
440
+
441
+ async def _refresh_status(self, http: AsyncHttpClient) -> str:
442
+ status_payload = await self._poll_status(http)
443
+ if status_payload:
444
+ self._last_status_payload = status_payload
445
+ status = str(status_payload.get("status") or status_payload.get("state") or "").lower()
446
+ if status:
447
+ self._last_status_value = status
448
+ if status in TERMINAL_STATUSES:
449
+ self._terminal_seen = True
450
+ print(f"[SDK] Terminal status detected: {status}", flush=True)
451
+ return status
452
+ return self._last_status_value or ""
453
+
454
+ async def _poll_status(self, http: AsyncHttpClient) -> dict[str, Any] | None:
455
+ if StreamType.STATUS not in self.config.enabled_streams or not self._status_paths:
456
+ return None
457
+
458
+ last_error: Exception | None = None
459
+ for path in self._status_paths:
460
+ try:
461
+ # Add cache-busting query param to ensure fresh status
462
+ # Use a timestamp to prevent any caching
463
+ params = {"_t": int(time.time() * 1000)}
464
+ data = await http.get(path, params=params)
465
+ except Exception as exc:
466
+ last_error = exc
467
+ # Try next fallback path
468
+ continue
469
+ if isinstance(data, dict):
470
+ message = StreamMessage.from_status(self.job_id, data)
471
+ self._dispatch([message])
472
+ return data
473
+
474
+ # If all paths failed, log the error for debugging
475
+ if last_error is not None:
476
+ logger = logging.getLogger(__name__)
477
+ logger.debug(f"Status polling failed for all paths: {last_error}")
478
+ return None
479
+
480
+ async def _poll_events(self, http: AsyncHttpClient) -> list[StreamMessage]:
481
+ if StreamType.EVENTS not in self.config.enabled_streams or not self._event_paths:
482
+ return []
483
+ messages: list[StreamMessage] = []
484
+ total = 0
485
+ for path in self._event_paths:
486
+ since = self._last_seq_by_stream.get(path, 0)
487
+ # Increase limit to capture more events per poll
488
+ limit = 1000 if self.config.max_events_per_poll and self.config.max_events_per_poll > 200 else 200
489
+ params = {"since_seq": since, "limit": limit}
490
+ try:
491
+ data = await http.get(path, params=params)
492
+ # Debug: Always log what we got from API
493
+ print(f"[DEBUG] Polling {path} with since_seq={since}, limit={limit}", file=sys.stderr)
494
+ print(f"[DEBUG] Got response from {path}, type={type(data).__name__}, keys={list(data.keys()) if isinstance(data, dict) else 'not dict'}", file=sys.stderr)
495
+ if isinstance(data, dict):
496
+ # Check for next_seq to see if we should update our tracking
497
+ if "next_seq" in data:
498
+ print(f"[DEBUG] Response has next_seq={data.get('next_seq')}, current since={since}", file=sys.stderr)
499
+ # Show what keys are in the response
500
+ for key in data.keys():
501
+ val = data[key]
502
+ if isinstance(val, list):
503
+ print(f"[DEBUG] Response[{key}] is list with {len(val)} items", file=sys.stderr)
504
+ if len(val) > 0:
505
+ print(f"[DEBUG] First item in {key}: {list(val[0].keys()) if isinstance(val[0], dict) else type(val[0])}", file=sys.stderr)
506
+ elif isinstance(val, dict):
507
+ print(f"[DEBUG] Response[{key}] is dict with keys: {list(val.keys())[:5]}", file=sys.stderr)
508
+ except Exception as e:
509
+ error_str = str(e)
510
+ print(f"[DEBUG] Error polling {path}: {e}", file=sys.stderr)
511
+ # Fail fast if we get 404 on both ADAS and fallback endpoints (indicates job ID mapping issue)
512
+ if "404" in error_str and (
513
+ "graphgen" in path.lower()
514
+ or "adas" in path.lower()
515
+ or "prompt-learning" in path.lower()
516
+ ):
517
+ # Check if this is the last fallback path - if so, raise to fail fast
518
+ if path == self._event_paths[-1]: # Last fallback path
519
+ raise RuntimeError(
520
+ f"Failed to poll events: All endpoints returned 404. "
521
+ f"This likely indicates a job ID mapping issue. "
522
+ f"GraphGen endpoints need the GraphGen job ID; GEPA fallback endpoints need the GEPA job ID. "
523
+ f"Last error: {error_str}"
524
+ )
525
+ continue
526
+ raw_events = _extract_list(data, "events")
527
+ # Debug: Always log what we extracted
528
+ print(f"[DEBUG] Extracted {len(raw_events)} events from {path} using _extract_list", file=sys.stderr)
529
+ # Update last_seq using next_seq if available
530
+ if isinstance(data, dict) and "next_seq" in data:
531
+ next_seq = data.get("next_seq")
532
+ if next_seq is not None:
533
+ try:
534
+ next_seq_int = int(next_seq)
535
+ if next_seq_int > since:
536
+ self._last_seq_by_stream[path] = next_seq_int
537
+ print(f"[DEBUG] Updated last_seq for {path} to {next_seq_int}", file=sys.stderr)
538
+ except (TypeError, ValueError):
539
+ pass
540
+ if raw_events and len(raw_events) > 0:
541
+ # Log first event type for debugging
542
+ first_event_type = raw_events[0].get("type", "unknown")
543
+ print(f"[DEBUG] First event type: {first_event_type}", file=sys.stderr)
544
+ for event in raw_events:
545
+ seq_raw = event.get("seq")
546
+ try:
547
+ seq_value = int(seq_raw) # type: ignore[arg-type]
548
+ except (TypeError, ValueError):
549
+ seq_value = None
550
+ last_seq = self._last_seq_by_stream.get(path, 0)
551
+ seq = seq_value if seq_value is not None else last_seq + 1
552
+ if seq <= last_seq:
553
+ continue
554
+ if seq_value is None:
555
+ event["seq"] = seq
556
+ self._last_seq_by_stream[path] = seq
557
+ # Bypass filtering - show ALL events
558
+ # if not self.config.should_include_event(event):
559
+ # continue
560
+ event_job_id = event.get("job_id") or self.job_id
561
+ event_message = StreamMessage.from_event(event_job_id, event)
562
+ event_type = str(event.get("type") or "").lower()
563
+ if event_type in TERMINAL_EVENT_SUCCESS:
564
+ self._terminal_seen = True
565
+ self._terminal_event_status = "succeeded"
566
+ elif event_type in TERMINAL_EVENT_FAILURE:
567
+ self._terminal_seen = True
568
+ self._terminal_event_status = "failed"
569
+ messages.append(event_message)
570
+ total += 1
571
+ if self.config.max_events_per_poll and total >= self.config.max_events_per_poll:
572
+ return messages
573
+ return messages
574
+
575
+ async def _poll_metrics(self, http: AsyncHttpClient) -> list[StreamMessage]:
576
+ if StreamType.METRICS not in self.config.enabled_streams or not self._metric_paths:
577
+ return []
578
+ messages: list[StreamMessage] = []
579
+ for path in self._metric_paths:
580
+ params = {"limit": 200}
581
+ try:
582
+ data = await http.get(path, params=params)
583
+ except Exception:
584
+ continue
585
+ points = _extract_list(data, "points")
586
+ for point in points:
587
+ name = str(point.get("name") or "")
588
+ if not name:
589
+ continue
590
+ step, fingerprint = _metric_cursor(point)
591
+ last_step, last_fingerprint = self._metric_cursors.get(name, (None, ""))
592
+ if step is not None:
593
+ if last_step is not None and step <= last_step:
594
+ continue
595
+ elif fingerprint and fingerprint == last_fingerprint:
596
+ continue
597
+ self._metric_cursors[name] = (step, fingerprint)
598
+ if not self.config.should_include_metric(point):
599
+ continue
600
+ metric_job_id = point.get("job_id") or self.job_id
601
+ messages.append(StreamMessage.from_metric(metric_job_id, point))
602
+ return messages
603
+
604
+ async def _poll_timeline(self, http: AsyncHttpClient) -> list[StreamMessage]:
605
+ if StreamType.TIMELINE not in self.config.enabled_streams or not self._timeline_paths:
606
+ return []
607
+ messages: list[StreamMessage] = []
608
+ for path in self._timeline_paths:
609
+ try:
610
+ data = await http.get(path)
611
+ except Exception:
612
+ continue
613
+
614
+ timeline_entries = _extract_list(data, "events")
615
+ for entry in timeline_entries:
616
+ if not self.config.should_include_timeline(entry):
617
+ continue
618
+ timeline_job_id = entry.get("job_id") or self.job_id
619
+ phase = str(entry.get("phase") or "").lower()
620
+ if phase in TERMINAL_STATUSES:
621
+ self._terminal_seen = True
622
+ if phase in {"failed", "cancelled", "canceled"}:
623
+ self._terminal_event_status = "failed"
624
+ elif phase:
625
+ self._terminal_event_status = "succeeded"
626
+ messages.append(StreamMessage.from_timeline(timeline_job_id, entry))
627
+ return messages
628
+
629
+ def _dispatch(self, messages: Iterable[StreamMessage]) -> None:
630
+ message_list = list(messages)
631
+ for message in message_list:
632
+ if self.config.deduplicate and message.key in self._seen_messages:
633
+ continue
634
+ if self.config.sample_rate < 1.0 and random.random() > self.config.sample_rate:
635
+ continue
636
+ if self.config.deduplicate:
637
+ self._seen_messages.add(message.key)
638
+
639
+ # Check for terminal events in dispatch (belt-and-suspenders)
640
+ if message.stream_type == StreamType.EVENTS and message.data:
641
+ event_type = str(message.data.get("type", "")).lower()
642
+ if event_type in TERMINAL_EVENT_SUCCESS:
643
+ self._terminal_seen = True
644
+ self._terminal_event_status = "succeeded"
645
+ elif event_type in TERMINAL_EVENT_FAILURE:
646
+ self._terminal_seen = True
647
+ self._terminal_event_status = "failed"
648
+
649
+ for handler in self.handlers:
650
+ try:
651
+ if handler.should_handle(message):
652
+ handler.handle(message)
653
+ except Exception:
654
+ pass
655
+
656
+
657
+ def _metric_cursor(point: dict[str, Any]) -> tuple[int | None, str]:
658
+ raw_step = point.get("step")
659
+ step_value: int | None = None
660
+ if raw_step is not None:
661
+ try:
662
+ step_value = int(raw_step) # type: ignore[arg-type]
663
+ except (TypeError, ValueError):
664
+ step_value = None
665
+
666
+ fingerprint = ""
667
+ for key in ("created_at", "updated_at", "timestamp"):
668
+ ts_val = point.get(key)
669
+ if ts_val is not None and ts_val != "":
670
+ fingerprint = str(ts_val)
671
+ break
672
+ if not fingerprint:
673
+ try:
674
+ fingerprint = json.dumps(point, sort_keys=True, default=str)
675
+ except Exception:
676
+ fingerprint = repr(point)
677
+ return step_value, fingerprint
678
+
679
+
680
+ def _extract_list(data: Any, field: str) -> list[dict[str, Any]]:
681
+ results: list[dict[str, Any]] = []
682
+ seen_items: set[int] = set()
683
+ stack: list[Any] = [data]
684
+ seen_containers: set[int] = set()
685
+
686
+ fallback_keys = {"data", "result", "results", "items", "payload", "records", "entries", "values"}
687
+
688
+ while stack:
689
+ current = stack.pop()
690
+ if current is None:
691
+ continue
692
+ current_id = id(current)
693
+ if current_id in seen_containers:
694
+ continue
695
+ seen_containers.add(current_id)
696
+
697
+ if isinstance(current, list):
698
+ for item in current:
699
+ if isinstance(item, dict):
700
+ item_id = id(item)
701
+ if item_id not in seen_items:
702
+ seen_items.add(item_id)
703
+ results.append(item)
704
+ elif isinstance(current, dict):
705
+ if field in current:
706
+ stack.append(current[field])
707
+ for key in fallback_keys:
708
+ if key in current:
709
+ stack.append(current[key])
710
+ return results
711
+
712
+
713
+ __all__ = ["JobStreamer", "StreamEndpoints"]