synth-ai 0.2.9.dev7__py3-none-any.whl → 0.2.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of synth-ai might be problematic. Click here for more details.

Files changed (323) hide show
  1. examples/__init__.py +16 -0
  2. examples/crafter_debug_render.py +8 -11
  3. examples/dev/qwen3_32b_qlora_4xh100.toml +40 -0
  4. examples/multi_step/crafter_rl_lora.md +29 -0
  5. examples/qwen_coder/README.md +102 -0
  6. examples/qwen_coder/_shared.py +113 -0
  7. examples/qwen_coder/configs/coder_lora_30b.toml +61 -0
  8. examples/qwen_coder/configs/coder_lora_4b.toml +57 -0
  9. examples/qwen_coder/configs/coder_lora_small.toml +58 -0
  10. examples/qwen_coder/generate_dataset.py +98 -0
  11. examples/qwen_coder/infer_ft_smoke.py +65 -0
  12. examples/qwen_coder/infer_prod_proxy.py +73 -0
  13. examples/qwen_coder/infer_via_synth.py +87 -0
  14. examples/qwen_coder/scripts/infer_coder.sh +19 -0
  15. examples/qwen_coder/scripts/train_coder_30b.sh +22 -0
  16. examples/qwen_coder/sft_full_17b.py +103 -0
  17. examples/qwen_coder/sft_lora_30b.py +110 -0
  18. examples/qwen_coder/subset_jsonl.py +39 -0
  19. examples/qwen_coder/todos.md +38 -0
  20. examples/qwen_coder/validate_jsonl.py +60 -0
  21. examples/rl/run_eval.py +36 -37
  22. examples/rl/run_rl_and_save.py +5 -5
  23. examples/rl/task_app/math_single_step.py +65 -43
  24. examples/rl/task_app/math_task_app.py +3 -3
  25. examples/sft/README.md +139 -0
  26. examples/sft/configs/crafter_fft_qwen0p6b.toml +44 -0
  27. examples/sft/configs/crafter_lora_qwen0p6b.toml +45 -0
  28. examples/sft/evaluate.py +117 -0
  29. examples/sft/export_dataset.py +117 -0
  30. examples/sft/generate_traces.py +162 -0
  31. examples/swe/__init__.py +12 -0
  32. examples/swe/task_app/README.md +105 -0
  33. examples/swe/task_app/__init__.py +2 -0
  34. examples/swe/task_app/grpo_swe_mini.py +571 -0
  35. examples/swe/task_app/grpo_swe_mini_task_app.py +136 -0
  36. examples/swe/task_app/hosted/README.md +173 -0
  37. examples/swe/task_app/hosted/__init__.py +5 -0
  38. examples/swe/task_app/hosted/branching.py +143 -0
  39. examples/swe/task_app/hosted/environment_routes.py +1289 -0
  40. examples/swe/task_app/hosted/envs/__init__.py +1 -0
  41. examples/swe/task_app/hosted/envs/crafter/__init__.py +6 -0
  42. examples/swe/task_app/hosted/envs/crafter/app.py +1 -0
  43. examples/swe/task_app/hosted/envs/crafter/environment.py +522 -0
  44. examples/swe/task_app/hosted/envs/crafter/policy.py +478 -0
  45. examples/swe/task_app/hosted/envs/crafter/react_agent.py +108 -0
  46. examples/swe/task_app/hosted/envs/crafter/shared.py +305 -0
  47. examples/swe/task_app/hosted/envs/crafter/tools.py +47 -0
  48. examples/swe/task_app/hosted/envs/mini_swe/__init__.py +8 -0
  49. examples/swe/task_app/hosted/envs/mini_swe/environment.py +1164 -0
  50. examples/swe/task_app/hosted/envs/mini_swe/policy.py +355 -0
  51. examples/swe/task_app/hosted/envs/mini_swe/shared.py +83 -0
  52. examples/swe/task_app/hosted/envs/mini_swe/tools.py +96 -0
  53. examples/swe/task_app/hosted/hosted_app.py +204 -0
  54. examples/swe/task_app/hosted/inference/__init__.py +5 -0
  55. examples/swe/task_app/hosted/inference/openai_client.py +618 -0
  56. examples/swe/task_app/hosted/main.py +100 -0
  57. examples/swe/task_app/hosted/policy_routes.py +1079 -0
  58. examples/swe/task_app/hosted/registry.py +195 -0
  59. examples/swe/task_app/hosted/rollout.py +1869 -0
  60. examples/swe/task_app/hosted/storage/__init__.py +5 -0
  61. examples/swe/task_app/hosted/storage/volume.py +211 -0
  62. examples/swe/task_app/hosted/test_agents.py +161 -0
  63. examples/swe/task_app/hosted/test_service.py +137 -0
  64. examples/swe/task_app/hosted/utils.py +62 -0
  65. examples/vlm/PROPOSAL.md +53 -0
  66. examples/vlm/README.md +68 -0
  67. examples/vlm/configs/crafter_vlm_gpt4o.toml +44 -0
  68. examples/vlm/crafter_image_only_agent.py +207 -0
  69. examples/vlm/crafter_openai_vlm_agent.py +277 -0
  70. examples/vlm/filter_image_rows.py +63 -0
  71. examples/vlm/run_crafter_vlm_benchmark.py +316 -0
  72. examples/warming_up_to_rl/analyze_trace_db.py +5 -5
  73. examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +11 -1
  74. examples/warming_up_to_rl/export_trace_sft.py +78 -21
  75. examples/warming_up_to_rl/groq_test.py +4 -4
  76. examples/warming_up_to_rl/manage_secrets.py +13 -18
  77. examples/warming_up_to_rl/run_eval.py +42 -44
  78. examples/warming_up_to_rl/run_fft_and_save.py +11 -16
  79. examples/warming_up_to_rl/run_local_rollout.py +1 -3
  80. examples/warming_up_to_rl/run_local_rollout_modal.py +2 -4
  81. examples/warming_up_to_rl/run_local_rollout_parallel.py +1 -4
  82. examples/warming_up_to_rl/run_local_rollout_traced.py +3 -5
  83. examples/warming_up_to_rl/run_rl_and_save.py +5 -6
  84. examples/warming_up_to_rl/run_rollout_remote.py +8 -10
  85. examples/warming_up_to_rl/task_app/README.md +6 -2
  86. examples/warming_up_to_rl/task_app/grpo_crafter.py +234 -35
  87. examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +2 -3
  88. examples/warming_up_to_rl/task_app/synth_envs_hosted/__init__.py +1 -1
  89. examples/warming_up_to_rl/task_app/synth_envs_hosted/branching.py +9 -11
  90. examples/warming_up_to_rl/task_app/synth_envs_hosted/environment_routes.py +131 -114
  91. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/environment.py +101 -41
  92. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +73 -51
  93. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +14 -6
  94. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/shared.py +16 -16
  95. examples/warming_up_to_rl/task_app/synth_envs_hosted/hosted_app.py +32 -34
  96. examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +94 -31
  97. examples/warming_up_to_rl/task_app/synth_envs_hosted/main.py +0 -2
  98. examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +303 -203
  99. examples/warming_up_to_rl/task_app/synth_envs_hosted/registry.py +21 -23
  100. examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +328 -225
  101. examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/volume.py +13 -13
  102. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_agents.py +1 -0
  103. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_service.py +1 -0
  104. examples/warming_up_to_rl/task_app/synth_envs_hosted/utils.py +4 -3
  105. synth_ai/api/models/supported.py +376 -0
  106. synth_ai/api/train/builders.py +128 -21
  107. synth_ai/api/train/cli.py +80 -64
  108. synth_ai/api/train/config_finder.py +7 -2
  109. synth_ai/api/train/env_resolver.py +1 -1
  110. synth_ai/api/train/pollers.py +2 -1
  111. synth_ai/api/train/supported_algos.py +139 -0
  112. synth_ai/api/train/task_app.py +1 -2
  113. synth_ai/api/train/utils.py +13 -44
  114. synth_ai/cli/__init__.py +8 -0
  115. synth_ai/cli/_modal_wrapper.py +28 -0
  116. synth_ai/cli/_typer_patch.py +49 -0
  117. synth_ai/cli/balance.py +1 -2
  118. synth_ai/cli/calc.py +1 -1
  119. synth_ai/cli/demo.py +2 -1
  120. synth_ai/cli/recent.py +2 -2
  121. synth_ai/cli/rl_demo.py +2 -1
  122. synth_ai/cli/root.py +11 -13
  123. synth_ai/cli/status.py +2 -2
  124. synth_ai/cli/task_apps.py +529 -179
  125. synth_ai/cli/traces.py +6 -4
  126. synth_ai/cli/watch.py +12 -18
  127. synth_ai/demo_registry.py +1 -1
  128. synth_ai/demos/core/cli.py +36 -43
  129. synth_ai/demos/demo_task_apps/__init__.py +3 -3
  130. synth_ai/demos/demo_task_apps/core.py +17 -25
  131. synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +3 -4
  132. synth_ai/demos/demo_task_apps/math/app.py +2 -1
  133. synth_ai/demos/demo_task_apps/math/deploy_modal.py +3 -4
  134. synth_ai/demos/demo_task_apps/math/modal_task_app.py +16 -18
  135. synth_ai/demos/demo_task_apps/math/task_app_entry.py +0 -1
  136. synth_ai/environments/examples/crafter_classic/environment.py +76 -1
  137. synth_ai/environments/reproducibility/tree.py +2 -5
  138. synth_ai/environments/service/app.py +11 -12
  139. synth_ai/environments/service/core_routes.py +4 -7
  140. synth_ai/environments/stateful/engine.py +1 -1
  141. synth_ai/environments/tasks/core.py +1 -0
  142. synth_ai/environments/tasks/filters.py +5 -6
  143. synth_ai/environments/tasks/utils.py +4 -5
  144. synth_ai/handshake.py +9 -9
  145. synth_ai/http.py +1 -1
  146. synth_ai/http_client.py +18 -10
  147. synth_ai/inference/client.py +15 -5
  148. synth_ai/jobs/client.py +78 -83
  149. synth_ai/learning/__init__.py +41 -6
  150. synth_ai/learning/algorithms.py +14 -0
  151. synth_ai/learning/client.py +91 -24
  152. synth_ai/learning/config.py +2 -38
  153. synth_ai/learning/ft_client.py +4 -59
  154. synth_ai/learning/health.py +5 -6
  155. synth_ai/learning/jobs.py +31 -47
  156. synth_ai/{rl → learning/rl}/__init__.py +14 -4
  157. synth_ai/learning/rl/client.py +267 -0
  158. synth_ai/learning/rl/config.py +31 -0
  159. synth_ai/{rl → learning/rl}/contracts.py +5 -8
  160. synth_ai/{rl → learning/rl}/env_keys.py +39 -15
  161. synth_ai/learning/rl/secrets.py +13 -0
  162. synth_ai/learning/rl_client.py +2 -281
  163. synth_ai/learning/sft/__init__.py +29 -0
  164. synth_ai/learning/sft/client.py +68 -0
  165. synth_ai/learning/sft/config.py +270 -0
  166. synth_ai/learning/sft/data.py +295 -0
  167. synth_ai/learning/sse.py +25 -24
  168. synth_ai/learning/validators.py +25 -28
  169. synth_ai/lm/__init__.py +21 -47
  170. synth_ai/task/__init__.py +25 -27
  171. synth_ai/task/apps/__init__.py +7 -8
  172. synth_ai/task/auth.py +8 -8
  173. synth_ai/task/client.py +14 -14
  174. synth_ai/task/contracts.py +36 -35
  175. synth_ai/task/datasets.py +6 -5
  176. synth_ai/task/errors.py +10 -10
  177. synth_ai/task/health.py +17 -9
  178. synth_ai/task/json.py +58 -23
  179. synth_ai/task/proxy.py +13 -9
  180. synth_ai/task/rubrics.py +16 -15
  181. synth_ai/task/server.py +12 -12
  182. synth_ai/task/tracing_utils.py +4 -4
  183. synth_ai/task/vendors.py +5 -6
  184. synth_ai/tracing_v3/__init__.py +2 -0
  185. synth_ai/tracing_v3/abstractions.py +21 -4
  186. synth_ai/tracing_v3/decorators.py +18 -16
  187. synth_ai/tracing_v3/hooks.py +5 -5
  188. synth_ai/tracing_v3/llm_call_record_helpers.py +6 -6
  189. synth_ai/tracing_v3/session_tracer.py +40 -14
  190. synth_ai/tracing_v3/storage/base.py +85 -0
  191. synth_ai/tracing_v3/storage/config.py +21 -8
  192. synth_ai/tracing_v3/storage/factory.py +10 -7
  193. synth_ai/tracing_v3/storage/utils.py +4 -2
  194. synth_ai/tracing_v3/turso/daemon.py +7 -2
  195. synth_ai/tracing_v3/turso/models.py +2 -2
  196. synth_ai/tracing_v3/turso/native_manager.py +1173 -0
  197. synth_ai/tracing_v3/utils.py +4 -4
  198. synth_ai/v0/api/__init__.py +8 -0
  199. synth_ai/v0/api/models/__init__.py +8 -0
  200. synth_ai/v0/api/models/supported.py +8 -0
  201. synth_ai/v0/config/__init__.py +15 -0
  202. synth_ai/v0/config/base_url.py +12 -0
  203. synth_ai/v0/lm/__init__.py +51 -0
  204. synth_ai/{lm → v0/lm}/caching/ephemeral.py +2 -2
  205. synth_ai/{lm → v0/lm}/caching/handler.py +4 -4
  206. synth_ai/{lm → v0/lm}/caching/initialize.py +1 -1
  207. synth_ai/{lm → v0/lm}/caching/persistent.py +1 -1
  208. synth_ai/{lm → v0/lm}/config.py +6 -1
  209. synth_ai/{lm → v0/lm}/core/all.py +9 -9
  210. synth_ai/{lm → v0/lm}/core/main.py +6 -6
  211. synth_ai/{lm → v0/lm}/core/main_v3.py +10 -10
  212. synth_ai/{lm → v0/lm}/core/synth_models.py +2 -14
  213. synth_ai/{lm → v0/lm}/core/vendor_clients.py +2 -2
  214. synth_ai/{lm → v0/lm}/overrides.py +2 -2
  215. synth_ai/{lm → v0/lm}/provider_support/anthropic.py +4 -4
  216. synth_ai/{lm → v0/lm}/provider_support/openai.py +5 -5
  217. synth_ai/{lm → v0/lm}/structured_outputs/handler.py +5 -5
  218. synth_ai/{lm → v0/lm}/structured_outputs/rehabilitate.py +1 -1
  219. synth_ai/{lm → v0/lm}/vendors/core/anthropic_api.py +9 -9
  220. synth_ai/{lm → v0/lm}/vendors/core/gemini_api.py +5 -5
  221. synth_ai/{lm → v0/lm}/vendors/core/mistral_api.py +5 -5
  222. synth_ai/{lm → v0/lm}/vendors/core/openai_api.py +10 -10
  223. synth_ai/{lm → v0/lm}/vendors/openai_standard.py +8 -8
  224. synth_ai/{lm → v0/lm}/vendors/openai_standard_responses.py +2 -2
  225. synth_ai/{lm → v0/lm}/vendors/supported/custom_endpoint.py +3 -3
  226. synth_ai/{lm → v0/lm}/vendors/supported/deepseek.py +2 -2
  227. synth_ai/{lm → v0/lm}/vendors/supported/grok.py +2 -2
  228. synth_ai/{lm → v0/lm}/vendors/supported/groq.py +1 -1
  229. synth_ai/{lm → v0/lm}/vendors/supported/ollama.py +1 -1
  230. synth_ai/{lm → v0/lm}/vendors/supported/openrouter.py +3 -3
  231. synth_ai/{lm → v0/lm}/vendors/supported/together.py +1 -1
  232. synth_ai/{lm → v0/lm}/vendors/synth_client.py +1 -1
  233. synth_ai/v0/tracing_v3/__init__.py +10 -0
  234. synth_ai/v0/tracing_v3/abstractions.py +3 -0
  235. synth_ai/v0/tracing_v3/decorators.py +3 -0
  236. synth_ai/v0/tracing_v3/llm_call_record_helpers.py +3 -0
  237. synth_ai/v0/tracing_v3/session_tracer.py +3 -0
  238. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.10.dist-info}/METADATA +10 -7
  239. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.10.dist-info}/RECORD +269 -233
  240. examples/common_old/backend.py +0 -20
  241. examples/evals_old/README.md +0 -98
  242. examples/evals_old/__init__.py +0 -6
  243. examples/evals_old/compare_models.py +0 -1038
  244. examples/evals_old/example_log.md +0 -145
  245. examples/evals_old/run_demo.sh +0 -126
  246. examples/evals_old/trace_analysis.py +0 -270
  247. examples/finetuning_old/_backup_synth_qwen/config.toml +0 -29
  248. examples/finetuning_old/_backup_synth_qwen/example_log.md +0 -324
  249. examples/finetuning_old/_backup_synth_qwen/filter_traces.py +0 -60
  250. examples/finetuning_old/_backup_synth_qwen/filter_traces_achievements.py +0 -243
  251. examples/finetuning_old/_backup_synth_qwen/purge_v3_traces.py +0 -109
  252. examples/finetuning_old/_backup_synth_qwen/react_agent_lm.py +0 -1924
  253. examples/finetuning_old/_backup_synth_qwen/readme.md +0 -49
  254. examples/finetuning_old/_backup_synth_qwen/run_crafter_qwen4b.py +0 -114
  255. examples/finetuning_old/_backup_synth_qwen/run_demo.sh +0 -195
  256. examples/finetuning_old/_backup_synth_qwen/sft_kickoff.py +0 -119
  257. examples/finetuning_old/synth_qwen_v1/README.md +0 -68
  258. examples/finetuning_old/synth_qwen_v1/filter_traces.py +0 -60
  259. examples/finetuning_old/synth_qwen_v1/filter_traces_achievements.py +0 -243
  260. examples/finetuning_old/synth_qwen_v1/finetune.py +0 -46
  261. examples/finetuning_old/synth_qwen_v1/hello_ft_model.py +0 -71
  262. examples/finetuning_old/synth_qwen_v1/infer.py +0 -36
  263. examples/finetuning_old/synth_qwen_v1/poll.py +0 -46
  264. examples/finetuning_old/synth_qwen_v1/prepare_data.py +0 -35
  265. examples/finetuning_old/synth_qwen_v1/purge_v3_traces.py +0 -109
  266. examples/finetuning_old/synth_qwen_v1/react_agent_lm.py +0 -1933
  267. examples/finetuning_old/synth_qwen_v1/run_crafter_sft_job.py +0 -210
  268. examples/finetuning_old/synth_qwen_v1/run_ft_job.py +0 -237
  269. examples/finetuning_old/synth_qwen_v1/upload_data.py +0 -34
  270. examples/finetuning_old/synth_qwen_v1/util.py +0 -152
  271. examples/rl_old/task_app.py +0 -1131
  272. synth_ai/experimental/synth_oss.py +0 -445
  273. synth_ai/learning/filtering.py +0 -0
  274. synth_ai/learning/offline/dpo.py +0 -0
  275. synth_ai/learning/offline/providers.py +0 -7
  276. synth_ai/learning/offline/sft.py +0 -0
  277. synth_ai/learning/offline/shared.py +0 -0
  278. synth_ai/learning/online/grpo.py +0 -0
  279. synth_ai/learning/online/irft.py +0 -0
  280. synth_ai/learning/prompts/banking77_injection_eval.py +0 -168
  281. synth_ai/learning/prompts/gepa.py +0 -0
  282. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +0 -211
  283. synth_ai/learning/prompts/mipro.py +0 -289
  284. synth_ai/learning/prompts/random_search.py +0 -249
  285. synth_ai/learning/prompts/run_mipro_banking77.py +0 -172
  286. synth_ai/learning/prompts/run_random_search_banking77.py +0 -329
  287. synth_ai/rl/secrets.py +0 -19
  288. synth_ai/scripts/verify_rewards.py +0 -100
  289. synth_ai/tracing/__init__.py +0 -30
  290. synth_ai/tracing_v1/__init__.py +0 -33
  291. synth_ai/tracing_v3/turso/__init__.py +0 -25
  292. synth_ai/tracing_v3/turso/manager.py +0 -838
  293. synth_ai/zyk/__init__.py +0 -30
  294. /synth_ai/{lm → v0/lm}/caching/__init__.py +0 -0
  295. /synth_ai/{lm → v0/lm}/caching/constants.py +0 -0
  296. /synth_ai/{lm → v0/lm}/caching/dbs.py +0 -0
  297. /synth_ai/{lm → v0/lm}/constants.py +0 -0
  298. /synth_ai/{lm → v0/lm}/core/__init__.py +0 -0
  299. /synth_ai/{lm → v0/lm}/core/exceptions.py +0 -0
  300. /synth_ai/{lm → v0/lm}/cost/__init__.py +0 -0
  301. /synth_ai/{lm → v0/lm}/cost/monitor.py +0 -0
  302. /synth_ai/{lm → v0/lm}/cost/statefulness.py +0 -0
  303. /synth_ai/{lm → v0/lm}/injection.py +0 -0
  304. /synth_ai/{lm → v0/lm}/provider_support/__init__.py +0 -0
  305. /synth_ai/{lm → v0/lm}/provider_support/suppress_logging.py +0 -0
  306. /synth_ai/{lm → v0/lm}/structured_outputs/__init__.py +0 -0
  307. /synth_ai/{lm → v0/lm}/structured_outputs/inject.py +0 -0
  308. /synth_ai/{lm → v0/lm}/tools/__init__.py +0 -0
  309. /synth_ai/{lm → v0/lm}/tools/base.py +0 -0
  310. /synth_ai/{lm → v0/lm}/unified_interface.py +0 -0
  311. /synth_ai/{lm → v0/lm}/vendors/__init__.py +0 -0
  312. /synth_ai/{lm → v0/lm}/vendors/base.py +0 -0
  313. /synth_ai/{lm → v0/lm}/vendors/core/__init__.py +0 -0
  314. /synth_ai/{lm → v0/lm}/vendors/core/synth_dev_api.py +0 -0
  315. /synth_ai/{lm → v0/lm}/vendors/local/__init__.py +0 -0
  316. /synth_ai/{lm → v0/lm}/vendors/local/ollama.py +0 -0
  317. /synth_ai/{lm → v0/lm}/vendors/retries.py +0 -0
  318. /synth_ai/{lm → v0/lm}/vendors/supported/__init__.py +0 -0
  319. /synth_ai/{lm → v0/lm}/warmup.py +0 -0
  320. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.10.dist-info}/WHEEL +0 -0
  321. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.10.dist-info}/entry_points.txt +0 -0
  322. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.10.dist-info}/licenses/LICENSE +0 -0
  323. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.10.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,17 @@
1
- from __future__ import annotations
2
-
3
1
  """Main SessionTracer class for tracing v3."""
4
2
 
3
+ from __future__ import annotations
4
+
5
5
  import asyncio
6
+ import json
6
7
  from contextlib import asynccontextmanager
7
- from datetime import datetime
8
+ from datetime import UTC, datetime
8
9
  from typing import Any
9
10
 
10
11
  from .abstractions import (
11
12
  BaseEvent,
12
13
  SessionEventMarkovBlanketMessage,
14
+ SessionMessageContent,
13
15
  SessionTimeStep,
14
16
  SessionTrace,
15
17
  TimeRecord,
@@ -17,7 +19,9 @@ from .abstractions import (
17
19
  from .config import CONFIG
18
20
  from .decorators import set_session_id, set_session_tracer, set_turn_number
19
21
  from .hooks import GLOBAL_HOOKS, HookManager
20
- from .turso.manager import AsyncSQLTraceManager
22
+ from .storage.base import TraceStorage
23
+ from .storage.config import StorageConfig
24
+ from .storage.factory import create_storage
21
25
  from .utils import generate_session_id
22
26
 
23
27
 
@@ -29,6 +33,8 @@ class SessionTracer:
29
33
  hooks: HookManager | None = None,
30
34
  db_url: str | None = None,
31
35
  auto_save: bool = True,
36
+ storage: TraceStorage | None = None,
37
+ storage_config: StorageConfig | None = None,
32
38
  ):
33
39
  """Initialize session tracer.
34
40
 
@@ -41,7 +47,8 @@ class SessionTracer:
41
47
  self._current_trace: SessionTrace | None = None
42
48
  self._lock = asyncio.Lock()
43
49
  self.db_url = db_url or CONFIG.db_url
44
- self.db: AsyncSQLTraceManager | None = None
50
+ self._storage_config = storage_config
51
+ self.db: TraceStorage | None = storage
45
52
  self.auto_save = auto_save
46
53
  self._current_step: SessionTimeStep | None = None
47
54
 
@@ -58,7 +65,8 @@ class SessionTracer:
58
65
  async def initialize(self):
59
66
  """Initialize the database connection."""
60
67
  if self.db is None:
61
- self.db = AsyncSQLTraceManager(self.db_url)
68
+ config = self._storage_config or StorageConfig(connection_string=self.db_url)
69
+ self.db = create_storage(config)
62
70
  await self.db.initialize()
63
71
 
64
72
  async def start_session(
@@ -98,7 +106,7 @@ class SessionTracer:
98
106
 
99
107
  self._current_trace = SessionTrace(
100
108
  session_id=session_id,
101
- created_at=datetime.utcnow(),
109
+ created_at=datetime.now(UTC),
102
110
  session_time_steps=[],
103
111
  event_history=[],
104
112
  markov_blanket_message_history=[],
@@ -144,7 +152,7 @@ class SessionTracer:
144
152
  step = SessionTimeStep(
145
153
  step_id=step_id,
146
154
  step_index=len(self._current_trace.session_time_steps),
147
- timestamp=datetime.utcnow(),
155
+ timestamp=datetime.now(UTC),
148
156
  turn_number=turn_number,
149
157
  step_metadata=metadata or {},
150
158
  )
@@ -189,7 +197,7 @@ class SessionTracer:
189
197
  step = self._current_step
190
198
 
191
199
  if step and step.completed_at is None:
192
- step.completed_at = datetime.utcnow()
200
+ step.completed_at = datetime.now(UTC)
193
201
 
194
202
  # Trigger hooks
195
203
  await self.hooks.trigger(
@@ -262,7 +270,7 @@ class SessionTracer:
262
270
 
263
271
  async def record_message(
264
272
  self,
265
- content: str,
273
+ content: Any,
266
274
  message_type: str,
267
275
  event_time: float | None = None,
268
276
  message_time: int | None = None,
@@ -280,11 +288,13 @@ class SessionTracer:
280
288
  if self._current_trace is None:
281
289
  raise RuntimeError("No active session")
282
290
 
291
+ normalised_content, content_str = self._normalise_message_content(content)
292
+
283
293
  msg = SessionEventMarkovBlanketMessage(
284
- content=content,
294
+ content=normalised_content,
285
295
  message_type=message_type,
286
296
  time_record=TimeRecord(
287
- event_time=event_time or datetime.utcnow().timestamp(), message_time=message_time
297
+ event_time=event_time or datetime.now(UTC).timestamp(), message_time=message_time
288
298
  ),
289
299
  metadata=metadata or {},
290
300
  )
@@ -318,7 +328,7 @@ class SessionTracer:
318
328
  self._current_trace.session_id,
319
329
  timestep_db_id=timestep_db_id,
320
330
  message_type=message_type,
321
- content=content,
331
+ content=content_str,
322
332
  event_time=msg.time_record.event_time,
323
333
  message_time=msg.time_record.message_time,
324
334
  metadata=msg.metadata,
@@ -326,6 +336,22 @@ class SessionTracer:
326
336
  return message_id
327
337
  return None
328
338
 
339
+ @staticmethod
340
+ def _normalise_message_content(content: Any) -> tuple[SessionMessageContent, str]:
341
+ if isinstance(content, SessionMessageContent):
342
+ return content, content.as_text()
343
+ if isinstance(content, str):
344
+ payload = SessionMessageContent(text=content)
345
+ return payload, payload.as_text()
346
+ try:
347
+ serialized = json.dumps(content, ensure_ascii=False)
348
+ payload = SessionMessageContent(json_payload=serialized)
349
+ return payload, serialized
350
+ except (TypeError, ValueError):
351
+ text = str(content)
352
+ payload = SessionMessageContent(text=text)
353
+ return payload, text
354
+
329
355
  async def end_session(self, save: bool | None = None) -> SessionTrace:
330
356
  """End the current session.
331
357
 
@@ -342,7 +368,7 @@ class SessionTracer:
342
368
  # End any open timesteps
343
369
  for step in self._current_trace.session_time_steps:
344
370
  if step.completed_at is None:
345
- step.completed_at = datetime.utcnow()
371
+ step.completed_at = datetime.now(UTC)
346
372
 
347
373
  # Trigger pre-save hooks
348
374
  await self.hooks.trigger("before_save", session=self._current_trace)
@@ -88,6 +88,91 @@ class TraceStorage(ABC):
88
88
  """Close the storage connection."""
89
89
  pass
90
90
 
91
+ # Incremental helpers -------------------------------------------------
92
+
93
+ @abstractmethod
94
+ async def ensure_session(
95
+ self,
96
+ session_id: str,
97
+ *,
98
+ created_at: datetime | None = None,
99
+ metadata: dict[str, Any] | None = None,
100
+ ) -> None:
101
+ """Ensure a session row exists for the given session id."""
102
+ pass
103
+
104
+ @abstractmethod
105
+ async def ensure_timestep(
106
+ self,
107
+ session_id: str,
108
+ *,
109
+ step_id: str,
110
+ step_index: int,
111
+ turn_number: int | None = None,
112
+ started_at: datetime | None = None,
113
+ completed_at: datetime | None = None,
114
+ metadata: dict[str, Any] | None = None,
115
+ ) -> int:
116
+ """Ensure a timestep row exists and return its database id."""
117
+ pass
118
+
119
+ @abstractmethod
120
+ async def insert_event_row(
121
+ self,
122
+ session_id: str,
123
+ *,
124
+ timestep_db_id: int | None,
125
+ event: Any,
126
+ metadata_override: dict[str, Any] | None = None,
127
+ ) -> int:
128
+ """Insert an event and return its database id."""
129
+ pass
130
+
131
+ @abstractmethod
132
+ async def insert_message_row(
133
+ self,
134
+ session_id: str,
135
+ *,
136
+ timestep_db_id: int | None,
137
+ message_type: str,
138
+ content: Any,
139
+ event_time: float | None = None,
140
+ message_time: int | None = None,
141
+ metadata: dict[str, Any] | None = None,
142
+ ) -> int:
143
+ """Insert a message row linked to a session/timestep."""
144
+ pass
145
+
146
+ @abstractmethod
147
+ async def insert_outcome_reward(
148
+ self,
149
+ session_id: str,
150
+ *,
151
+ total_reward: int,
152
+ achievements_count: int,
153
+ total_steps: int,
154
+ reward_metadata: dict | None = None,
155
+ ) -> int:
156
+ """Record an outcome reward for a session."""
157
+ pass
158
+
159
+ @abstractmethod
160
+ async def insert_event_reward(
161
+ self,
162
+ session_id: str,
163
+ *,
164
+ event_id: int,
165
+ message_id: int | None = None,
166
+ turn_number: int | None = None,
167
+ reward_value: float = 0.0,
168
+ reward_type: str | None = None,
169
+ key: str | None = None,
170
+ annotation: dict[str, Any] | None = None,
171
+ source: str | None = None,
172
+ ) -> int:
173
+ """Record a reward tied to a specific event."""
174
+ pass
175
+
91
176
  # Optional experiment management methods
92
177
  async def create_experiment(
93
178
  self,
@@ -9,16 +9,22 @@ from typing import Any
9
9
  class StorageBackend(str, Enum):
10
10
  """Supported storage backends."""
11
11
 
12
- TURSO = "turso"
12
+ TURSO_NATIVE = "turso_native"
13
13
  SQLITE = "sqlite"
14
14
  POSTGRES = "postgres" # Future support
15
15
 
16
16
 
17
+ def _is_enabled(value: str | None) -> bool:
18
+ if value is None:
19
+ return False
20
+ return value.lower() in {"1", "true", "yes", "on"}
21
+
22
+
17
23
  @dataclass
18
24
  class StorageConfig:
19
25
  """Configuration for storage backend."""
20
26
 
21
- backend: StorageBackend = StorageBackend.TURSO
27
+ backend: StorageBackend = StorageBackend.TURSO_NATIVE
22
28
  connection_string: str | None = None
23
29
 
24
30
  # Turso-specific settings
@@ -34,23 +40,30 @@ class StorageConfig:
34
40
  enable_compression: bool = os.getenv("STORAGE_COMPRESSION", "false").lower() == "true"
35
41
  max_content_length: int = int(os.getenv("STORAGE_MAX_CONTENT_LENGTH", "1000000")) # 1MB
36
42
 
43
+ def __post_init__(self):
44
+ # Allow legacy override while keeping compatibility with existing TURSO_NATIVE env flag
45
+ native_env = os.getenv("TURSO_NATIVE")
46
+ native_flag = _is_enabled(native_env) if native_env is not None else None
47
+
48
+ if native_flag is False:
49
+ self.backend = StorageBackend.SQLITE
50
+
37
51
  def get_connection_string(self) -> str:
38
52
  """Get the appropriate connection string for the backend."""
39
53
  if self.connection_string:
40
54
  return self.connection_string
41
55
 
42
- if self.backend == StorageBackend.TURSO:
56
+ if self.backend == StorageBackend.TURSO_NATIVE:
43
57
  return self.turso_url
44
- elif self.backend == StorageBackend.SQLITE:
58
+ if self.backend == StorageBackend.SQLITE:
45
59
  return "sqlite+aiosqlite:///traces.db"
46
- elif self.backend == StorageBackend.POSTGRES:
60
+ if self.backend == StorageBackend.POSTGRES:
47
61
  return os.getenv("POSTGRES_URL", "postgresql+asyncpg://localhost/traces")
48
- else:
49
- raise ValueError(f"Unknown backend: {self.backend}")
62
+ raise ValueError(f"Unknown backend: {self.backend}")
50
63
 
51
64
  def get_backend_config(self) -> dict[str, Any]:
52
65
  """Get backend-specific configuration."""
53
- if self.backend == StorageBackend.TURSO:
66
+ if self.backend == StorageBackend.TURSO_NATIVE:
54
67
  config = {}
55
68
  if self.turso_auth_token:
56
69
  config["auth_token"] = self.turso_auth_token
@@ -1,6 +1,6 @@
1
1
  """Factory for creating storage instances."""
2
2
 
3
- from ..turso.manager import AsyncSQLTraceManager
3
+ from ..turso.native_manager import NativeLibsqlTraceManager
4
4
  from .base import TraceStorage
5
5
  from .config import StorageBackend, StorageConfig
6
6
 
@@ -22,13 +22,16 @@ def create_storage(config: StorageConfig | None = None) -> TraceStorage:
22
22
 
23
23
  config = STORAGE_CONFIG
24
24
 
25
- if config.backend == StorageBackend.TURSO:
26
- # Turso uses the AsyncSQLTraceManager
27
- return AsyncSQLTraceManager(db_url=config.get_connection_string())
25
+ connection_string = config.get_connection_string()
26
+
27
+ if config.backend == StorageBackend.TURSO_NATIVE:
28
+ backend_config = config.get_backend_config()
29
+ return NativeLibsqlTraceManager(
30
+ db_url=connection_string,
31
+ auth_token=backend_config.get("auth_token"),
32
+ )
28
33
  elif config.backend == StorageBackend.SQLITE:
29
- # For pure SQLite, we can still use AsyncSQLTraceManager
30
- # but with a file-based URL
31
- return AsyncSQLTraceManager(db_url=config.get_connection_string())
34
+ return NativeLibsqlTraceManager(db_url=connection_string)
32
35
  elif config.backend == StorageBackend.POSTGRES:
33
36
  # Future: PostgreSQL implementation
34
37
  raise NotImplementedError("PostgreSQL backend not yet implemented")
@@ -21,7 +21,7 @@ def retry_async(max_attempts: int = 3, delay: float = 1.0, backoff: float = 2.0)
21
21
  def decorator(func: Callable[..., T]) -> Callable[..., T]:
22
22
  @functools.wraps(func)
23
23
  async def wrapper(*args, **kwargs):
24
- last_exception = None
24
+ last_exception: Exception | None = None
25
25
  current_delay = delay
26
26
 
27
27
  for attempt in range(max_attempts):
@@ -35,7 +35,9 @@ def retry_async(max_attempts: int = 3, delay: float = 1.0, backoff: float = 2.0)
35
35
  else:
36
36
  raise
37
37
 
38
- raise last_exception
38
+ if last_exception:
39
+ raise last_exception
40
+ raise RuntimeError("Retry logic failed without exception")
39
41
 
40
42
  return wrapper
41
43
 
@@ -13,7 +13,12 @@ from ..config import CONFIG
13
13
  class SqldDaemon:
14
14
  """Manages local sqld daemon lifecycle."""
15
15
 
16
- def __init__(self, db_path: str = None, http_port: int = None, binary_path: str = None):
16
+ def __init__(
17
+ self,
18
+ db_path: str | None = None,
19
+ http_port: int | None = None,
20
+ binary_path: str | None = None,
21
+ ):
17
22
  """Initialize sqld daemon manager.
18
23
 
19
24
  Args:
@@ -120,7 +125,7 @@ class SqldDaemon:
120
125
  _daemon: SqldDaemon | None = None
121
126
 
122
127
 
123
- def start_sqld(db_path: str = None, port: int = None) -> SqldDaemon:
128
+ def start_sqld(db_path: str | None = None, port: int | None = None) -> SqldDaemon:
124
129
  """Start a global sqld daemon instance."""
125
130
  global _daemon
126
131
  if _daemon and _daemon.is_running():
@@ -1,7 +1,7 @@
1
- from __future__ import annotations
2
-
3
1
  """SQLAlchemy declarative models for tracing v3."""
4
2
 
3
+ from __future__ import annotations
4
+
5
5
  import json
6
6
 
7
7
  from sqlalchemy import (