synth-ai 0.2.4.dev5__py3-none-any.whl → 0.2.4.dev7__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 (229) hide show
  1. synth_ai/__init__.py +18 -9
  2. synth_ai/cli/__init__.py +10 -5
  3. synth_ai/cli/balance.py +22 -17
  4. synth_ai/cli/calc.py +2 -3
  5. synth_ai/cli/demo.py +3 -5
  6. synth_ai/cli/legacy_root_backup.py +58 -32
  7. synth_ai/cli/man.py +22 -19
  8. synth_ai/cli/recent.py +9 -8
  9. synth_ai/cli/root.py +58 -13
  10. synth_ai/cli/status.py +13 -6
  11. synth_ai/cli/traces.py +45 -21
  12. synth_ai/cli/watch.py +40 -37
  13. synth_ai/config/base_url.py +1 -3
  14. synth_ai/core/experiment.py +1 -2
  15. synth_ai/environments/__init__.py +2 -6
  16. synth_ai/environments/environment/artifacts/base.py +3 -1
  17. synth_ai/environments/environment/db/sqlite.py +1 -1
  18. synth_ai/environments/environment/registry.py +19 -20
  19. synth_ai/environments/environment/resources/sqlite.py +2 -3
  20. synth_ai/environments/environment/rewards/core.py +3 -2
  21. synth_ai/environments/environment/tools/__init__.py +6 -4
  22. synth_ai/environments/examples/crafter_classic/__init__.py +1 -1
  23. synth_ai/environments/examples/crafter_classic/engine.py +21 -17
  24. synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +1 -0
  25. synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +2 -1
  26. synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +2 -1
  27. synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +3 -2
  28. synth_ai/environments/examples/crafter_classic/environment.py +16 -15
  29. synth_ai/environments/examples/crafter_classic/taskset.py +2 -2
  30. synth_ai/environments/examples/crafter_classic/trace_hooks_v3.py +2 -3
  31. synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +2 -1
  32. synth_ai/environments/examples/crafter_custom/crafter/__init__.py +2 -2
  33. synth_ai/environments/examples/crafter_custom/crafter/config.py +2 -2
  34. synth_ai/environments/examples/crafter_custom/crafter/env.py +1 -5
  35. synth_ai/environments/examples/crafter_custom/crafter/objects.py +1 -2
  36. synth_ai/environments/examples/crafter_custom/crafter/worldgen.py +1 -2
  37. synth_ai/environments/examples/crafter_custom/dataset_builder.py +5 -5
  38. synth_ai/environments/examples/crafter_custom/environment.py +13 -13
  39. synth_ai/environments/examples/crafter_custom/run_dataset.py +5 -5
  40. synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +2 -2
  41. synth_ai/environments/examples/enron/art_helpers/local_email_db.py +5 -4
  42. synth_ai/environments/examples/enron/art_helpers/types_enron.py +2 -1
  43. synth_ai/environments/examples/enron/engine.py +18 -14
  44. synth_ai/environments/examples/enron/environment.py +12 -11
  45. synth_ai/environments/examples/enron/taskset.py +7 -7
  46. synth_ai/environments/examples/minigrid/__init__.py +6 -6
  47. synth_ai/environments/examples/minigrid/engine.py +6 -6
  48. synth_ai/environments/examples/minigrid/environment.py +6 -6
  49. synth_ai/environments/examples/minigrid/puzzle_loader.py +3 -2
  50. synth_ai/environments/examples/minigrid/taskset.py +13 -13
  51. synth_ai/environments/examples/nethack/achievements.py +1 -1
  52. synth_ai/environments/examples/nethack/engine.py +8 -7
  53. synth_ai/environments/examples/nethack/environment.py +10 -9
  54. synth_ai/environments/examples/nethack/helpers/__init__.py +8 -9
  55. synth_ai/environments/examples/nethack/helpers/action_mapping.py +1 -1
  56. synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +2 -1
  57. synth_ai/environments/examples/nethack/helpers/observation_utils.py +1 -1
  58. synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +3 -4
  59. synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +6 -5
  60. synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +5 -5
  61. synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +7 -6
  62. synth_ai/environments/examples/nethack/taskset.py +5 -5
  63. synth_ai/environments/examples/red/engine.py +9 -8
  64. synth_ai/environments/examples/red/engine_helpers/reward_components.py +2 -1
  65. synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +7 -7
  66. synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +2 -1
  67. synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +2 -1
  68. synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +2 -1
  69. synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +2 -1
  70. synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +2 -1
  71. synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +2 -1
  72. synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +2 -1
  73. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +2 -1
  74. synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +2 -1
  75. synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +2 -1
  76. synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +2 -1
  77. synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +3 -2
  78. synth_ai/environments/examples/red/engine_helpers/state_extraction.py +2 -1
  79. synth_ai/environments/examples/red/environment.py +18 -15
  80. synth_ai/environments/examples/red/taskset.py +5 -3
  81. synth_ai/environments/examples/sokoban/engine.py +16 -13
  82. synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +3 -2
  83. synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +2 -1
  84. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +1 -1
  85. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +7 -5
  86. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +1 -1
  87. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +2 -1
  88. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +5 -4
  89. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +3 -2
  90. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +2 -1
  91. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +5 -4
  92. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +1 -1
  93. synth_ai/environments/examples/sokoban/environment.py +15 -14
  94. synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +5 -3
  95. synth_ai/environments/examples/sokoban/puzzle_loader.py +3 -2
  96. synth_ai/environments/examples/sokoban/taskset.py +13 -10
  97. synth_ai/environments/examples/tictactoe/engine.py +6 -6
  98. synth_ai/environments/examples/tictactoe/environment.py +8 -7
  99. synth_ai/environments/examples/tictactoe/taskset.py +6 -5
  100. synth_ai/environments/examples/verilog/engine.py +4 -3
  101. synth_ai/environments/examples/verilog/environment.py +11 -10
  102. synth_ai/environments/examples/verilog/taskset.py +14 -12
  103. synth_ai/environments/examples/wordle/__init__.py +29 -0
  104. synth_ai/environments/examples/wordle/engine.py +398 -0
  105. synth_ai/environments/examples/wordle/environment.py +159 -0
  106. synth_ai/environments/examples/wordle/helpers/generate_instances_wordfreq.py +75 -0
  107. synth_ai/environments/examples/wordle/taskset.py +230 -0
  108. synth_ai/environments/reproducibility/core.py +1 -1
  109. synth_ai/environments/reproducibility/tree.py +21 -21
  110. synth_ai/environments/service/app.py +11 -2
  111. synth_ai/environments/service/core_routes.py +137 -105
  112. synth_ai/environments/service/external_registry.py +1 -2
  113. synth_ai/environments/service/registry.py +1 -1
  114. synth_ai/environments/stateful/core.py +1 -2
  115. synth_ai/environments/stateful/engine.py +1 -1
  116. synth_ai/environments/tasks/api.py +4 -4
  117. synth_ai/environments/tasks/core.py +14 -12
  118. synth_ai/environments/tasks/filters.py +6 -4
  119. synth_ai/environments/tasks/utils.py +13 -11
  120. synth_ai/evals/base.py +2 -3
  121. synth_ai/experimental/synth_oss.py +4 -4
  122. synth_ai/learning/gateway.py +1 -3
  123. synth_ai/learning/prompts/banking77_injection_eval.py +168 -0
  124. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +213 -0
  125. synth_ai/learning/prompts/mipro.py +282 -1
  126. synth_ai/learning/prompts/random_search.py +246 -0
  127. synth_ai/learning/prompts/run_mipro_banking77.py +172 -0
  128. synth_ai/learning/prompts/run_random_search_banking77.py +324 -0
  129. synth_ai/lm/__init__.py +5 -5
  130. synth_ai/lm/caching/ephemeral.py +9 -9
  131. synth_ai/lm/caching/handler.py +20 -20
  132. synth_ai/lm/caching/persistent.py +10 -10
  133. synth_ai/lm/config.py +3 -3
  134. synth_ai/lm/constants.py +7 -7
  135. synth_ai/lm/core/all.py +17 -3
  136. synth_ai/lm/core/exceptions.py +0 -2
  137. synth_ai/lm/core/main.py +26 -41
  138. synth_ai/lm/core/main_v3.py +20 -10
  139. synth_ai/lm/core/vendor_clients.py +18 -17
  140. synth_ai/lm/injection.py +80 -0
  141. synth_ai/lm/overrides.py +206 -0
  142. synth_ai/lm/provider_support/__init__.py +1 -1
  143. synth_ai/lm/provider_support/anthropic.py +51 -24
  144. synth_ai/lm/provider_support/openai.py +51 -22
  145. synth_ai/lm/structured_outputs/handler.py +34 -32
  146. synth_ai/lm/structured_outputs/inject.py +24 -27
  147. synth_ai/lm/structured_outputs/rehabilitate.py +19 -15
  148. synth_ai/lm/tools/base.py +17 -16
  149. synth_ai/lm/unified_interface.py +17 -18
  150. synth_ai/lm/vendors/base.py +20 -18
  151. synth_ai/lm/vendors/core/anthropic_api.py +50 -25
  152. synth_ai/lm/vendors/core/gemini_api.py +31 -36
  153. synth_ai/lm/vendors/core/mistral_api.py +19 -19
  154. synth_ai/lm/vendors/core/openai_api.py +11 -10
  155. synth_ai/lm/vendors/openai_standard.py +144 -88
  156. synth_ai/lm/vendors/openai_standard_responses.py +74 -61
  157. synth_ai/lm/vendors/retries.py +9 -1
  158. synth_ai/lm/vendors/supported/custom_endpoint.py +26 -26
  159. synth_ai/lm/vendors/supported/deepseek.py +10 -10
  160. synth_ai/lm/vendors/supported/grok.py +8 -8
  161. synth_ai/lm/vendors/supported/ollama.py +2 -1
  162. synth_ai/lm/vendors/supported/openrouter.py +11 -9
  163. synth_ai/lm/vendors/synth_client.py +69 -63
  164. synth_ai/lm/warmup.py +8 -7
  165. synth_ai/tracing/__init__.py +22 -10
  166. synth_ai/tracing_v1/__init__.py +22 -20
  167. synth_ai/tracing_v3/__init__.py +7 -7
  168. synth_ai/tracing_v3/abstractions.py +56 -52
  169. synth_ai/tracing_v3/config.py +4 -2
  170. synth_ai/tracing_v3/db_config.py +6 -8
  171. synth_ai/tracing_v3/decorators.py +29 -30
  172. synth_ai/tracing_v3/examples/basic_usage.py +12 -12
  173. synth_ai/tracing_v3/hooks.py +21 -21
  174. synth_ai/tracing_v3/llm_call_record_helpers.py +85 -98
  175. synth_ai/tracing_v3/lm_call_record_abstractions.py +2 -4
  176. synth_ai/tracing_v3/migration_helper.py +3 -5
  177. synth_ai/tracing_v3/replica_sync.py +30 -32
  178. synth_ai/tracing_v3/session_tracer.py +35 -29
  179. synth_ai/tracing_v3/storage/__init__.py +1 -1
  180. synth_ai/tracing_v3/storage/base.py +8 -7
  181. synth_ai/tracing_v3/storage/config.py +4 -4
  182. synth_ai/tracing_v3/storage/factory.py +4 -4
  183. synth_ai/tracing_v3/storage/utils.py +9 -9
  184. synth_ai/tracing_v3/turso/__init__.py +3 -3
  185. synth_ai/tracing_v3/turso/daemon.py +9 -9
  186. synth_ai/tracing_v3/turso/manager.py +60 -48
  187. synth_ai/tracing_v3/turso/models.py +24 -19
  188. synth_ai/tracing_v3/utils.py +5 -5
  189. synth_ai/tui/__main__.py +1 -1
  190. synth_ai/tui/cli/query_experiments.py +2 -3
  191. synth_ai/tui/cli/query_experiments_v3.py +2 -3
  192. synth_ai/tui/dashboard.py +97 -86
  193. synth_ai/v0/tracing/abstractions.py +28 -28
  194. synth_ai/v0/tracing/base_client.py +9 -9
  195. synth_ai/v0/tracing/client_manager.py +7 -7
  196. synth_ai/v0/tracing/config.py +7 -7
  197. synth_ai/v0/tracing/context.py +6 -6
  198. synth_ai/v0/tracing/decorators.py +6 -5
  199. synth_ai/v0/tracing/events/manage.py +1 -1
  200. synth_ai/v0/tracing/events/store.py +5 -4
  201. synth_ai/v0/tracing/immediate_client.py +4 -5
  202. synth_ai/v0/tracing/local.py +3 -3
  203. synth_ai/v0/tracing/log_client_base.py +4 -5
  204. synth_ai/v0/tracing/retry_queue.py +5 -6
  205. synth_ai/v0/tracing/trackers.py +25 -25
  206. synth_ai/v0/tracing/upload.py +6 -0
  207. synth_ai/v0/tracing_v1/__init__.py +1 -1
  208. synth_ai/v0/tracing_v1/abstractions.py +28 -28
  209. synth_ai/v0/tracing_v1/base_client.py +9 -9
  210. synth_ai/v0/tracing_v1/client_manager.py +7 -7
  211. synth_ai/v0/tracing_v1/config.py +7 -7
  212. synth_ai/v0/tracing_v1/context.py +6 -6
  213. synth_ai/v0/tracing_v1/decorators.py +7 -6
  214. synth_ai/v0/tracing_v1/events/manage.py +1 -1
  215. synth_ai/v0/tracing_v1/events/store.py +5 -4
  216. synth_ai/v0/tracing_v1/immediate_client.py +4 -5
  217. synth_ai/v0/tracing_v1/local.py +3 -3
  218. synth_ai/v0/tracing_v1/log_client_base.py +4 -5
  219. synth_ai/v0/tracing_v1/retry_queue.py +5 -6
  220. synth_ai/v0/tracing_v1/trackers.py +25 -25
  221. synth_ai/v0/tracing_v1/upload.py +25 -24
  222. synth_ai/zyk/__init__.py +1 -0
  223. {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/METADATA +2 -11
  224. synth_ai-0.2.4.dev7.dist-info/RECORD +299 -0
  225. synth_ai-0.2.4.dev5.dist-info/RECORD +0 -287
  226. {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/WHEEL +0 -0
  227. {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/entry_points.txt +0 -0
  228. {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/licenses/LICENSE +0 -0
  229. {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,21 @@
1
1
  """Main SessionTracer class for tracing v3."""
2
2
 
3
3
  import asyncio
4
- from datetime import datetime
5
- from typing import Dict, List, Optional, Any, Union
6
4
  from contextlib import asynccontextmanager
7
-
8
- from .abstractions import SessionTrace, SessionTimeStep, BaseEvent, SessionEventMarkovBlanketMessage, TimeRecord
9
- from .decorators import set_session_id, set_turn_number, set_session_tracer, SessionContext
10
- from .turso.manager import AsyncSQLTraceManager
5
+ from datetime import datetime
6
+ from typing import Any
7
+
8
+ from .abstractions import (
9
+ BaseEvent,
10
+ SessionEventMarkovBlanketMessage,
11
+ SessionTimeStep,
12
+ SessionTrace,
13
+ TimeRecord,
14
+ )
11
15
  from .config import CONFIG
12
- from .hooks import HookManager, GLOBAL_HOOKS
16
+ from .decorators import set_session_id, set_session_tracer, set_turn_number
17
+ from .hooks import GLOBAL_HOOKS, HookManager
18
+ from .turso.manager import AsyncSQLTraceManager
13
19
  from .utils import generate_session_id
14
20
 
15
21
 
@@ -18,8 +24,8 @@ class SessionTracer:
18
24
 
19
25
  def __init__(
20
26
  self,
21
- hooks: Optional[HookManager] = None,
22
- db_url: Optional[str] = None,
27
+ hooks: HookManager | None = None,
28
+ db_url: str | None = None,
23
29
  auto_save: bool = True,
24
30
  ):
25
31
  """Initialize session tracer.
@@ -30,20 +36,20 @@ class SessionTracer:
30
36
  auto_save: Whether to automatically save sessions on end
31
37
  """
32
38
  self.hooks = hooks or GLOBAL_HOOKS
33
- self._current_trace: Optional[SessionTrace] = None
39
+ self._current_trace: SessionTrace | None = None
34
40
  self._lock = asyncio.Lock()
35
41
  self.db_url = db_url or CONFIG.db_url
36
- self.db: Optional[AsyncSQLTraceManager] = None
42
+ self.db: AsyncSQLTraceManager | None = None
37
43
  self.auto_save = auto_save
38
- self._current_step: Optional[SessionTimeStep] = None
44
+ self._current_step: SessionTimeStep | None = None
39
45
 
40
46
  @property
41
- def current_session(self) -> Optional[SessionTrace]:
47
+ def current_session(self) -> SessionTrace | None:
42
48
  """Get the current session trace."""
43
49
  return self._current_trace
44
50
 
45
51
  @property
46
- def current_step(self) -> Optional[SessionTimeStep]:
52
+ def current_step(self) -> SessionTimeStep | None:
47
53
  """Get the current timestep."""
48
54
  return self._current_step
49
55
 
@@ -54,14 +60,14 @@ class SessionTracer:
54
60
  await self.db.initialize()
55
61
 
56
62
  async def start_session(
57
- self, session_id: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None
63
+ self, session_id: str | None = None, metadata: dict[str, Any] | None = None
58
64
  ) -> str:
59
65
  """Start a new session.
60
-
66
+
61
67
  Creates a new tracing session and sets up the necessary context variables.
62
68
  This method is thread-safe and will raise an error if a session is already
63
69
  active to prevent accidental session mixing.
64
-
70
+
65
71
  The session ID is propagated through asyncio context variables, allowing
66
72
  nested async functions to access the tracer without explicit passing.
67
73
 
@@ -76,7 +82,7 @@ class SessionTracer:
76
82
 
77
83
  Returns:
78
84
  The session ID (either provided or generated)
79
-
85
+
80
86
  Raises:
81
87
  RuntimeError: If a session is already active
82
88
  """
@@ -111,8 +117,8 @@ class SessionTracer:
111
117
  async def start_timestep(
112
118
  self,
113
119
  step_id: str,
114
- turn_number: Optional[int] = None,
115
- metadata: Optional[Dict[str, Any]] = None,
120
+ turn_number: int | None = None,
121
+ metadata: dict[str, Any] | None = None,
116
122
  ) -> SessionTimeStep:
117
123
  """Start a new timestep.
118
124
 
@@ -148,7 +154,7 @@ class SessionTracer:
148
154
 
149
155
  return step
150
156
 
151
- async def end_timestep(self, step_id: Optional[str] = None):
157
+ async def end_timestep(self, step_id: str | None = None):
152
158
  """End the current or specified timestep."""
153
159
  if self._current_trace is None:
154
160
  raise RuntimeError("No active session")
@@ -199,9 +205,9 @@ class SessionTracer:
199
205
  self,
200
206
  content: str,
201
207
  message_type: str,
202
- event_time: Optional[float] = None,
203
- message_time: Optional[int] = None,
204
- metadata: Optional[Dict[str, Any]] = None,
208
+ event_time: float | None = None,
209
+ message_time: int | None = None,
210
+ metadata: dict[str, Any] | None = None,
205
211
  ):
206
212
  """Record a message.
207
213
 
@@ -281,8 +287,8 @@ class SessionTracer:
281
287
  @asynccontextmanager
282
288
  async def session(
283
289
  self,
284
- session_id: Optional[str] = None,
285
- metadata: Optional[Dict[str, Any]] = None,
290
+ session_id: str | None = None,
291
+ metadata: dict[str, Any] | None = None,
286
292
  save: bool = None,
287
293
  ):
288
294
  """Context manager for a session.
@@ -302,8 +308,8 @@ class SessionTracer:
302
308
  async def timestep(
303
309
  self,
304
310
  step_id: str,
305
- turn_number: Optional[int] = None,
306
- metadata: Optional[Dict[str, Any]] = None,
311
+ turn_number: int | None = None,
312
+ metadata: dict[str, Any] | None = None,
307
313
  ):
308
314
  """Context manager for a timestep.
309
315
 
@@ -318,7 +324,7 @@ class SessionTracer:
318
324
  finally:
319
325
  await self.end_timestep(step_id)
320
326
 
321
- async def get_session_history(self, limit: Optional[int] = None) -> List[Dict[str, Any]]:
327
+ async def get_session_history(self, limit: int | None = None) -> list[dict[str, Any]]:
322
328
  """Get recent session history from database."""
323
329
  if self.db is None:
324
330
  await self.initialize()
@@ -1,8 +1,8 @@
1
1
  """Storage abstraction layer for tracing v3."""
2
2
 
3
3
  from .base import TraceStorage
4
- from .factory import create_storage
5
4
  from .config import StorageConfig
5
+ from .factory import create_storage
6
6
  from .types import EventType, MessageType, Provider
7
7
 
8
8
  __all__ = [
@@ -2,7 +2,8 @@
2
2
 
3
3
  from abc import ABC, abstractmethod
4
4
  from datetime import datetime
5
- from typing import Any, Dict, List, Optional
5
+ from typing import Any
6
+
6
7
  import pandas as pd
7
8
 
8
9
  from ..abstractions import SessionTrace
@@ -29,7 +30,7 @@ class TraceStorage(ABC):
29
30
  pass
30
31
 
31
32
  @abstractmethod
32
- async def get_session_trace(self, session_id: str) -> Optional[Dict[str, Any]]:
33
+ async def get_session_trace(self, session_id: str) -> dict[str, Any] | None:
33
34
  """Retrieve a session trace by ID.
34
35
 
35
36
  Args:
@@ -41,7 +42,7 @@ class TraceStorage(ABC):
41
42
  pass
42
43
 
43
44
  @abstractmethod
44
- async def query_traces(self, query: str, params: Dict[str, Any] = None) -> pd.DataFrame:
45
+ async def query_traces(self, query: str, params: dict[str, Any] = None) -> pd.DataFrame:
45
46
  """Execute a query and return results as DataFrame.
46
47
 
47
48
  Args:
@@ -92,7 +93,7 @@ class TraceStorage(ABC):
92
93
  experiment_id: str,
93
94
  name: str,
94
95
  description: str = None,
95
- configuration: Dict[str, Any] = None,
96
+ configuration: dict[str, Any] = None,
96
97
  ) -> str:
97
98
  """Create a new experiment."""
98
99
  raise NotImplementedError("Experiment management not supported by this backend")
@@ -103,14 +104,14 @@ class TraceStorage(ABC):
103
104
 
104
105
  async def get_sessions_by_experiment(
105
106
  self, experiment_id: str, limit: int = None
106
- ) -> List[Dict[str, Any]]:
107
+ ) -> list[dict[str, Any]]:
107
108
  """Get all sessions for an experiment."""
108
109
  raise NotImplementedError("Experiment management not supported by this backend")
109
110
 
110
111
  # Batch operations
111
112
  async def batch_insert_sessions(
112
- self, traces: List[SessionTrace], batch_size: int = 1000
113
- ) -> List[str]:
113
+ self, traces: list[SessionTrace], batch_size: int = 1000
114
+ ) -> list[str]:
114
115
  """Batch insert multiple session traces.
115
116
 
116
117
  Default implementation calls insert_session_trace for each trace.
@@ -1,9 +1,9 @@
1
1
  """Storage configuration for tracing v3."""
2
2
 
3
- from dataclasses import dataclass
4
- from typing import Optional, Dict, Any
5
3
  import os
4
+ from dataclasses import dataclass
6
5
  from enum import Enum
6
+ from typing import Any
7
7
 
8
8
 
9
9
  class StorageBackend(str, Enum):
@@ -19,7 +19,7 @@ class StorageConfig:
19
19
  """Configuration for storage backend."""
20
20
 
21
21
  backend: StorageBackend = StorageBackend.TURSO
22
- connection_string: Optional[str] = None
22
+ connection_string: str | None = None
23
23
 
24
24
  # Turso-specific settings
25
25
  turso_url: str = os.getenv("TURSO_DATABASE_URL", "sqlite+libsql://http://127.0.0.1:8080")
@@ -48,7 +48,7 @@ class StorageConfig:
48
48
  else:
49
49
  raise ValueError(f"Unknown backend: {self.backend}")
50
50
 
51
- def get_backend_config(self) -> Dict[str, Any]:
51
+ def get_backend_config(self) -> dict[str, Any]:
52
52
  """Get backend-specific configuration."""
53
53
  if self.backend == StorageBackend.TURSO:
54
54
  config = {}
@@ -1,12 +1,12 @@
1
1
  """Factory for creating storage instances."""
2
2
 
3
- from typing import Optional
4
- from .base import TraceStorage
5
- from .config import StorageConfig, StorageBackend
3
+
6
4
  from ..turso.manager import AsyncSQLTraceManager
5
+ from .base import TraceStorage
6
+ from .config import StorageBackend, StorageConfig
7
7
 
8
8
 
9
- def create_storage(config: Optional[StorageConfig] = None) -> TraceStorage:
9
+ def create_storage(config: StorageConfig | None = None) -> TraceStorage:
10
10
  """Create a storage instance based on configuration.
11
11
 
12
12
  Args:
@@ -1,10 +1,10 @@
1
1
  """Utility functions for storage layer."""
2
2
 
3
3
  import asyncio
4
- from typing import Any, Dict, List, Optional, TypeVar, Callable
5
4
  import functools
6
5
  import time
7
-
6
+ from collections.abc import Callable
7
+ from typing import Any, TypeVar
8
8
 
9
9
  T = TypeVar("T")
10
10
 
@@ -43,8 +43,8 @@ def retry_async(max_attempts: int = 3, delay: float = 1.0, backoff: float = 2.0)
43
43
 
44
44
 
45
45
  async def batch_process(
46
- items: List[Any], processor: Callable, batch_size: int = 100, max_concurrent: int = 5
47
- ) -> List[Any]:
46
+ items: list[Any], processor: Callable, batch_size: int = 100, max_concurrent: int = 5
47
+ ) -> list[Any]:
48
48
  """Process items in batches with concurrency control.
49
49
 
50
50
  Args:
@@ -88,7 +88,7 @@ def sanitize_json(data: Any) -> Any:
88
88
  return {k: sanitize_json(v) for k, v in data.items()}
89
89
  elif isinstance(data, list):
90
90
  return [sanitize_json(item) for item in data]
91
- elif isinstance(data, (str, int, float, bool, type(None))):
91
+ elif isinstance(data, str | int | float | bool | type(None)):
92
92
  return data
93
93
  else:
94
94
  return str(data)
@@ -101,7 +101,7 @@ def estimate_size(obj: Any) -> int:
101
101
  """
102
102
  if isinstance(obj, str):
103
103
  return len(obj.encode("utf-8"))
104
- elif isinstance(obj, (int, float)):
104
+ elif isinstance(obj, int | float):
105
105
  return 8
106
106
  elif isinstance(obj, bool):
107
107
  return 1
@@ -120,10 +120,10 @@ class StorageMetrics:
120
120
  """Track storage operation metrics."""
121
121
 
122
122
  def __init__(self):
123
- self.operations: Dict[str, Dict[str, Any]] = {}
123
+ self.operations: dict[str, dict[str, Any]] = {}
124
124
 
125
125
  def record_operation(
126
- self, operation: str, duration: float, success: bool, size: Optional[int] = None
126
+ self, operation: str, duration: float, success: bool, size: int | None = None
127
127
  ):
128
128
  """Record a storage operation."""
129
129
  if operation not in self.operations:
@@ -142,7 +142,7 @@ class StorageMetrics:
142
142
  if size:
143
143
  stats["total_size"] += size
144
144
 
145
- def get_stats(self, operation: Optional[str] = None) -> Dict[str, Any]:
145
+ def get_stats(self, operation: str | None = None) -> dict[str, Any]:
146
146
  """Get statistics for operations."""
147
147
  if operation:
148
148
  stats = self.operations.get(operation, {})
@@ -3,11 +3,11 @@
3
3
  from .manager import AsyncSQLTraceManager
4
4
  from .models import (
5
5
  Base,
6
- SessionTrace,
7
- SessionTimestep,
8
6
  Event,
9
- Message,
10
7
  Experiment,
8
+ Message,
9
+ SessionTimestep,
10
+ SessionTrace,
11
11
  System,
12
12
  SystemVersion,
13
13
  )
@@ -1,12 +1,12 @@
1
1
  """sqld daemon management utilities."""
2
2
 
3
- import subprocess
4
3
  import pathlib
5
4
  import shutil
6
- import sys
5
+ import subprocess
7
6
  import time
7
+
8
8
  import requests
9
- from typing import Optional
9
+
10
10
  from ..config import CONFIG
11
11
 
12
12
 
@@ -15,23 +15,23 @@ class SqldDaemon:
15
15
 
16
16
  def __init__(self, db_path: str = None, http_port: int = None, binary_path: str = None):
17
17
  """Initialize sqld daemon manager.
18
-
18
+
19
19
  Args:
20
20
  db_path: Path to database file (uses config default if not provided)
21
- http_port: HTTP port for daemon (uses config default if not provided)
21
+ http_port: HTTP port for daemon (uses config default if not provided)
22
22
  binary_path: Path to sqld binary (auto-detected if not provided)
23
23
  """
24
24
  self.db_path = db_path or CONFIG.sqld_db_path
25
25
  self.http_port = http_port or CONFIG.sqld_http_port
26
26
  self.binary_path = binary_path or self._find_binary()
27
- self.process: Optional[subprocess.Popen] = None
27
+ self.process: subprocess.Popen | None = None
28
28
 
29
29
  def _find_binary(self) -> str:
30
30
  """Find sqld binary in PATH."""
31
31
  binary = shutil.which(CONFIG.sqld_binary) or shutil.which("libsql-server")
32
32
  if not binary:
33
33
  raise RuntimeError(
34
- f"sqld binary not found in PATH. Install with: brew install turso-tech/tools/sqld"
34
+ "sqld binary not found in PATH. Install with: brew install turso-tech/tools/sqld"
35
35
  )
36
36
  return binary
37
37
 
@@ -117,7 +117,7 @@ class SqldDaemon:
117
117
 
118
118
 
119
119
  # Convenience functions
120
- _daemon: Optional[SqldDaemon] = None
120
+ _daemon: SqldDaemon | None = None
121
121
 
122
122
 
123
123
  def start_sqld(db_path: str = None, port: int = None) -> SqldDaemon:
@@ -139,6 +139,6 @@ def stop_sqld():
139
139
  _daemon = None
140
140
 
141
141
 
142
- def get_daemon() -> Optional[SqldDaemon]:
142
+ def get_daemon() -> SqldDaemon | None:
143
143
  """Get the global daemon instance."""
144
144
  return _daemon
@@ -21,37 +21,43 @@ Performance Considerations:
21
21
  """
22
22
 
23
23
  import asyncio
24
+ import logging
24
25
  from contextlib import asynccontextmanager
25
26
  from datetime import datetime
26
- from typing import Any, Dict, List, Optional, Sequence, Union
27
+ from typing import Any
28
+
27
29
  import pandas as pd
28
- from sqlalchemy import select, insert, update, delete, text, and_, or_, func
29
- from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine, AsyncSession
30
- from sqlalchemy.orm import sessionmaker, selectinload, joinedload
31
- from sqlalchemy.pool import NullPool
30
+ from sqlalchemy import select, text, update
32
31
  from sqlalchemy.exc import IntegrityError
33
- import logging
32
+ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
33
+ from sqlalchemy.orm import selectinload, sessionmaker
34
+ from sqlalchemy.pool import NullPool
34
35
 
35
- from ..config import CONFIG
36
36
  from ..abstractions import (
37
- SessionTrace,
38
- SessionTimeStep,
39
- BaseEvent,
40
- LMCAISEvent,
41
37
  EnvironmentEvent,
38
+ LMCAISEvent,
42
39
  RuntimeEvent,
40
+ SessionTrace,
43
41
  )
44
- from ..utils import json_dumps
42
+ from ..config import CONFIG
45
43
  from .models import (
46
44
  Base,
47
- SessionTrace as DBSessionTrace,
48
- SessionTimestep as DBSessionTimestep,
45
+ analytics_views,
46
+ )
47
+ from .models import (
49
48
  Event as DBEvent,
50
- Message as DBMessage,
49
+ )
50
+ from .models import (
51
51
  Experiment as DBExperiment,
52
- System as DBSystem,
53
- SystemVersion as DBSystemVersion,
54
- analytics_views,
52
+ )
53
+ from .models import (
54
+ Message as DBMessage,
55
+ )
56
+ from .models import (
57
+ SessionTimestep as DBSessionTimestep,
58
+ )
59
+ from .models import (
60
+ SessionTrace as DBSessionTrace,
55
61
  )
56
62
 
57
63
  logger = logging.getLogger(__name__)
@@ -59,10 +65,10 @@ logger = logging.getLogger(__name__)
59
65
 
60
66
  class AsyncSQLTraceManager:
61
67
  """Async trace storage manager using SQLAlchemy and Turso/sqld.
62
-
68
+
63
69
  Handles all database operations for the tracing system. Designed to work
64
70
  with both local SQLite (via aiosqlite) and remote Turso databases.
65
-
71
+
66
72
  The manager handles:
67
73
  - Connection lifecycle management
68
74
  - Schema creation and verification
@@ -71,22 +77,22 @@ class AsyncSQLTraceManager:
71
77
  - Analytics view creation
72
78
  """
73
79
 
74
- def __init__(self, db_url: Optional[str] = None):
80
+ def __init__(self, db_url: str | None = None):
75
81
  self.db_url = db_url or CONFIG.db_url
76
- self.engine: Optional[AsyncEngine] = None
77
- self.SessionLocal: Optional[sessionmaker] = None
82
+ self.engine: AsyncEngine | None = None
83
+ self.SessionLocal: sessionmaker | None = None
78
84
  self._schema_lock = asyncio.Lock()
79
85
  self._schema_ready = False
80
86
 
81
87
  async def initialize(self):
82
88
  """Initialize the database connection and schema.
83
-
89
+
84
90
  This method is idempotent and thread-safe. It:
85
91
  1. Creates the async engine with appropriate settings
86
92
  2. Verifies database file exists (for SQLite)
87
93
  3. Creates schema if needed
88
94
  4. Sets up analytics views
89
-
95
+
90
96
  The schema lock ensures only one worker creates the schema in
91
97
  concurrent scenarios.
92
98
  """
@@ -136,7 +142,7 @@ class AsyncSQLTraceManager:
136
142
 
137
143
  async def _ensure_schema(self):
138
144
  """Ensure database schema is created.
139
-
145
+
140
146
  Uses a lock to prevent race conditions when multiple workers start
141
147
  simultaneously. The checkfirst=True parameter handles cases where
142
148
  another worker already created the schema.
@@ -154,7 +160,7 @@ class AsyncSQLTraceManager:
154
160
  await conn.run_sync(
155
161
  lambda sync_conn: Base.metadata.create_all(sync_conn, checkfirst=True)
156
162
  )
157
- #logger.info("✅ Database schema created/verified successfully")
163
+ # logger.info("✅ Database schema created/verified successfully")
158
164
  except Exception as e:
159
165
  # If tables already exist, that's fine - another worker created them
160
166
  if "already exists" not in str(e):
@@ -183,7 +189,7 @@ class AsyncSQLTraceManager:
183
189
  logger.warning(f"Could not create view {view_name}: {e}")
184
190
 
185
191
  self._schema_ready = True
186
- #logger.debug("🎯 Database ready for use!")
192
+ # logger.debug("🎯 Database ready for use!")
187
193
 
188
194
  @asynccontextmanager
189
195
  async def session(self):
@@ -195,18 +201,18 @@ class AsyncSQLTraceManager:
195
201
 
196
202
  async def insert_session_trace(self, trace: SessionTrace) -> str:
197
203
  """Insert a complete session trace.
198
-
204
+
199
205
  This method handles the complex task of inserting a complete session
200
206
  with all its timesteps, events, and messages. It uses a single
201
207
  transaction for atomicity and flushes after timesteps to get their
202
208
  auto-generated IDs for foreign keys.
203
-
209
+
204
210
  Args:
205
211
  trace: The complete session trace to store
206
-
212
+
207
213
  Returns:
208
214
  The session ID
209
-
215
+
210
216
  Raises:
211
217
  IntegrityError: If session ID already exists (handled gracefully)
212
218
  """
@@ -214,7 +220,7 @@ class AsyncSQLTraceManager:
214
220
  try:
215
221
  # Convert to cents for cost storage - avoids floating point
216
222
  # precision issues and allows for integer arithmetic
217
- def to_cents(cost: Optional[float]) -> Optional[int]:
223
+ def to_cents(cost: float | None) -> int | None:
218
224
  return int(cost * 100) if cost is not None else None
219
225
 
220
226
  # Insert session
@@ -230,7 +236,7 @@ class AsyncSQLTraceManager:
230
236
 
231
237
  # Track timestep IDs for foreign keys - we need these to link
232
238
  # events and messages to their respective timesteps
233
- step_id_map: Dict[str, int] = {}
239
+ step_id_map: dict[str, int] = {}
234
240
 
235
241
  # Insert timesteps
236
242
  for step in trace.session_time_steps:
@@ -270,8 +276,9 @@ class AsyncSQLTraceManager:
270
276
  call_records_data = None
271
277
  if event.call_records:
272
278
  from dataclasses import asdict
279
+
273
280
  call_records_data = [asdict(record) for record in event.call_records]
274
-
281
+
275
282
  event_data.update(
276
283
  {
277
284
  "event_type": "cais",
@@ -340,7 +347,7 @@ class AsyncSQLTraceManager:
340
347
  return trace.session_id # Return existing ID
341
348
  raise
342
349
 
343
- async def get_session_trace(self, session_id: str) -> Optional[Dict[str, Any]]:
350
+ async def get_session_trace(self, session_id: str) -> dict[str, Any] | None:
344
351
  """Retrieve a session trace by ID."""
345
352
  async with self.session() as sess:
346
353
  result = await sess.execute(
@@ -377,7 +384,9 @@ class AsyncSQLTraceManager:
377
384
  ],
378
385
  }
379
386
 
380
- async def query_traces(self, query: str, params: Optional[Dict[str, Any]] = None) -> pd.DataFrame:
387
+ async def query_traces(
388
+ self, query: str, params: dict[str, Any] | None = None
389
+ ) -> pd.DataFrame:
381
390
  """Execute a query and return results as DataFrame."""
382
391
  async with self.session() as sess:
383
392
  result = await sess.execute(text(query), params or {})
@@ -385,7 +394,10 @@ class AsyncSQLTraceManager:
385
394
  return pd.DataFrame(rows)
386
395
 
387
396
  async def get_model_usage(
388
- self, start_date: Optional[datetime] = None, end_date: Optional[datetime] = None, model_name: Optional[str] = None
397
+ self,
398
+ start_date: datetime | None = None,
399
+ end_date: datetime | None = None,
400
+ model_name: str | None = None,
389
401
  ) -> pd.DataFrame:
390
402
  """Get model usage statistics."""
391
403
  query = """
@@ -414,8 +426,8 @@ class AsyncSQLTraceManager:
414
426
  self,
415
427
  experiment_id: str,
416
428
  name: str,
417
- description: Optional[str] = None,
418
- configuration: Optional[Dict[str, Any]] = None,
429
+ description: str | None = None,
430
+ configuration: dict[str, Any] | None = None,
419
431
  ) -> str:
420
432
  """Create a new experiment."""
421
433
  async with self.session() as sess:
@@ -440,18 +452,18 @@ class AsyncSQLTraceManager:
440
452
  await sess.commit()
441
453
 
442
454
  async def batch_insert_sessions(
443
- self, traces: List[SessionTrace], batch_size: Optional[int] = None
444
- ) -> List[str]:
455
+ self, traces: list[SessionTrace], batch_size: int | None = None
456
+ ) -> list[str]:
445
457
  """Batch insert multiple session traces.
446
-
458
+
447
459
  Processes traces in batches to balance memory usage and performance.
448
460
  Each batch is inserted in a separate transaction to avoid holding
449
461
  locks for too long.
450
-
462
+
451
463
  Args:
452
464
  traces: List of session traces to insert
453
465
  batch_size: Number of traces per batch (defaults to config)
454
-
466
+
455
467
  Returns:
456
468
  List of inserted session IDs
457
469
  """
@@ -470,8 +482,8 @@ class AsyncSQLTraceManager:
470
482
  return inserted_ids
471
483
 
472
484
  async def get_sessions_by_experiment(
473
- self, experiment_id: str, limit: Optional[int] = None
474
- ) -> List[Dict[str, Any]]:
485
+ self, experiment_id: str, limit: int | None = None
486
+ ) -> list[dict[str, Any]]:
475
487
  """Get all sessions for an experiment."""
476
488
  async with self.session() as sess:
477
489
  query = (
@@ -515,7 +527,7 @@ class AsyncSQLTraceManager:
515
527
 
516
528
  async def close(self):
517
529
  """Close the database connection.
518
-
530
+
519
531
  Properly disposes of the engine and all connections. This is important
520
532
  for cleanup, especially with SQLite which can leave lock files.
521
533
  """