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.
- synth_ai/__init__.py +18 -9
- synth_ai/cli/__init__.py +10 -5
- synth_ai/cli/balance.py +22 -17
- synth_ai/cli/calc.py +2 -3
- synth_ai/cli/demo.py +3 -5
- synth_ai/cli/legacy_root_backup.py +58 -32
- synth_ai/cli/man.py +22 -19
- synth_ai/cli/recent.py +9 -8
- synth_ai/cli/root.py +58 -13
- synth_ai/cli/status.py +13 -6
- synth_ai/cli/traces.py +45 -21
- synth_ai/cli/watch.py +40 -37
- synth_ai/config/base_url.py +1 -3
- synth_ai/core/experiment.py +1 -2
- synth_ai/environments/__init__.py +2 -6
- synth_ai/environments/environment/artifacts/base.py +3 -1
- synth_ai/environments/environment/db/sqlite.py +1 -1
- synth_ai/environments/environment/registry.py +19 -20
- synth_ai/environments/environment/resources/sqlite.py +2 -3
- synth_ai/environments/environment/rewards/core.py +3 -2
- synth_ai/environments/environment/tools/__init__.py +6 -4
- synth_ai/environments/examples/crafter_classic/__init__.py +1 -1
- synth_ai/environments/examples/crafter_classic/engine.py +21 -17
- synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +1 -0
- synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +2 -1
- synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +2 -1
- synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +3 -2
- synth_ai/environments/examples/crafter_classic/environment.py +16 -15
- synth_ai/environments/examples/crafter_classic/taskset.py +2 -2
- synth_ai/environments/examples/crafter_classic/trace_hooks_v3.py +2 -3
- synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +2 -1
- synth_ai/environments/examples/crafter_custom/crafter/__init__.py +2 -2
- synth_ai/environments/examples/crafter_custom/crafter/config.py +2 -2
- synth_ai/environments/examples/crafter_custom/crafter/env.py +1 -5
- synth_ai/environments/examples/crafter_custom/crafter/objects.py +1 -2
- synth_ai/environments/examples/crafter_custom/crafter/worldgen.py +1 -2
- synth_ai/environments/examples/crafter_custom/dataset_builder.py +5 -5
- synth_ai/environments/examples/crafter_custom/environment.py +13 -13
- synth_ai/environments/examples/crafter_custom/run_dataset.py +5 -5
- synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +2 -2
- synth_ai/environments/examples/enron/art_helpers/local_email_db.py +5 -4
- synth_ai/environments/examples/enron/art_helpers/types_enron.py +2 -1
- synth_ai/environments/examples/enron/engine.py +18 -14
- synth_ai/environments/examples/enron/environment.py +12 -11
- synth_ai/environments/examples/enron/taskset.py +7 -7
- synth_ai/environments/examples/minigrid/__init__.py +6 -6
- synth_ai/environments/examples/minigrid/engine.py +6 -6
- synth_ai/environments/examples/minigrid/environment.py +6 -6
- synth_ai/environments/examples/minigrid/puzzle_loader.py +3 -2
- synth_ai/environments/examples/minigrid/taskset.py +13 -13
- synth_ai/environments/examples/nethack/achievements.py +1 -1
- synth_ai/environments/examples/nethack/engine.py +8 -7
- synth_ai/environments/examples/nethack/environment.py +10 -9
- synth_ai/environments/examples/nethack/helpers/__init__.py +8 -9
- synth_ai/environments/examples/nethack/helpers/action_mapping.py +1 -1
- synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +2 -1
- synth_ai/environments/examples/nethack/helpers/observation_utils.py +1 -1
- synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +3 -4
- synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +6 -5
- synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +5 -5
- synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +7 -6
- synth_ai/environments/examples/nethack/taskset.py +5 -5
- synth_ai/environments/examples/red/engine.py +9 -8
- synth_ai/environments/examples/red/engine_helpers/reward_components.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +7 -7
- synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +3 -2
- synth_ai/environments/examples/red/engine_helpers/state_extraction.py +2 -1
- synth_ai/environments/examples/red/environment.py +18 -15
- synth_ai/environments/examples/red/taskset.py +5 -3
- synth_ai/environments/examples/sokoban/engine.py +16 -13
- synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +3 -2
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +2 -1
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +1 -1
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +7 -5
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +1 -1
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +2 -1
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +5 -4
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +3 -2
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +2 -1
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +5 -4
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +1 -1
- synth_ai/environments/examples/sokoban/environment.py +15 -14
- synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +5 -3
- synth_ai/environments/examples/sokoban/puzzle_loader.py +3 -2
- synth_ai/environments/examples/sokoban/taskset.py +13 -10
- synth_ai/environments/examples/tictactoe/engine.py +6 -6
- synth_ai/environments/examples/tictactoe/environment.py +8 -7
- synth_ai/environments/examples/tictactoe/taskset.py +6 -5
- synth_ai/environments/examples/verilog/engine.py +4 -3
- synth_ai/environments/examples/verilog/environment.py +11 -10
- synth_ai/environments/examples/verilog/taskset.py +14 -12
- synth_ai/environments/examples/wordle/__init__.py +29 -0
- synth_ai/environments/examples/wordle/engine.py +398 -0
- synth_ai/environments/examples/wordle/environment.py +159 -0
- synth_ai/environments/examples/wordle/helpers/generate_instances_wordfreq.py +75 -0
- synth_ai/environments/examples/wordle/taskset.py +230 -0
- synth_ai/environments/reproducibility/core.py +1 -1
- synth_ai/environments/reproducibility/tree.py +21 -21
- synth_ai/environments/service/app.py +11 -2
- synth_ai/environments/service/core_routes.py +137 -105
- synth_ai/environments/service/external_registry.py +1 -2
- synth_ai/environments/service/registry.py +1 -1
- synth_ai/environments/stateful/core.py +1 -2
- synth_ai/environments/stateful/engine.py +1 -1
- synth_ai/environments/tasks/api.py +4 -4
- synth_ai/environments/tasks/core.py +14 -12
- synth_ai/environments/tasks/filters.py +6 -4
- synth_ai/environments/tasks/utils.py +13 -11
- synth_ai/evals/base.py +2 -3
- synth_ai/experimental/synth_oss.py +4 -4
- synth_ai/learning/gateway.py +1 -3
- synth_ai/learning/prompts/banking77_injection_eval.py +168 -0
- synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +213 -0
- synth_ai/learning/prompts/mipro.py +282 -1
- synth_ai/learning/prompts/random_search.py +246 -0
- synth_ai/learning/prompts/run_mipro_banking77.py +172 -0
- synth_ai/learning/prompts/run_random_search_banking77.py +324 -0
- synth_ai/lm/__init__.py +5 -5
- synth_ai/lm/caching/ephemeral.py +9 -9
- synth_ai/lm/caching/handler.py +20 -20
- synth_ai/lm/caching/persistent.py +10 -10
- synth_ai/lm/config.py +3 -3
- synth_ai/lm/constants.py +7 -7
- synth_ai/lm/core/all.py +17 -3
- synth_ai/lm/core/exceptions.py +0 -2
- synth_ai/lm/core/main.py +26 -41
- synth_ai/lm/core/main_v3.py +20 -10
- synth_ai/lm/core/vendor_clients.py +18 -17
- synth_ai/lm/injection.py +80 -0
- synth_ai/lm/overrides.py +206 -0
- synth_ai/lm/provider_support/__init__.py +1 -1
- synth_ai/lm/provider_support/anthropic.py +51 -24
- synth_ai/lm/provider_support/openai.py +51 -22
- synth_ai/lm/structured_outputs/handler.py +34 -32
- synth_ai/lm/structured_outputs/inject.py +24 -27
- synth_ai/lm/structured_outputs/rehabilitate.py +19 -15
- synth_ai/lm/tools/base.py +17 -16
- synth_ai/lm/unified_interface.py +17 -18
- synth_ai/lm/vendors/base.py +20 -18
- synth_ai/lm/vendors/core/anthropic_api.py +50 -25
- synth_ai/lm/vendors/core/gemini_api.py +31 -36
- synth_ai/lm/vendors/core/mistral_api.py +19 -19
- synth_ai/lm/vendors/core/openai_api.py +11 -10
- synth_ai/lm/vendors/openai_standard.py +144 -88
- synth_ai/lm/vendors/openai_standard_responses.py +74 -61
- synth_ai/lm/vendors/retries.py +9 -1
- synth_ai/lm/vendors/supported/custom_endpoint.py +26 -26
- synth_ai/lm/vendors/supported/deepseek.py +10 -10
- synth_ai/lm/vendors/supported/grok.py +8 -8
- synth_ai/lm/vendors/supported/ollama.py +2 -1
- synth_ai/lm/vendors/supported/openrouter.py +11 -9
- synth_ai/lm/vendors/synth_client.py +69 -63
- synth_ai/lm/warmup.py +8 -7
- synth_ai/tracing/__init__.py +22 -10
- synth_ai/tracing_v1/__init__.py +22 -20
- synth_ai/tracing_v3/__init__.py +7 -7
- synth_ai/tracing_v3/abstractions.py +56 -52
- synth_ai/tracing_v3/config.py +4 -2
- synth_ai/tracing_v3/db_config.py +6 -8
- synth_ai/tracing_v3/decorators.py +29 -30
- synth_ai/tracing_v3/examples/basic_usage.py +12 -12
- synth_ai/tracing_v3/hooks.py +21 -21
- synth_ai/tracing_v3/llm_call_record_helpers.py +85 -98
- synth_ai/tracing_v3/lm_call_record_abstractions.py +2 -4
- synth_ai/tracing_v3/migration_helper.py +3 -5
- synth_ai/tracing_v3/replica_sync.py +30 -32
- synth_ai/tracing_v3/session_tracer.py +35 -29
- synth_ai/tracing_v3/storage/__init__.py +1 -1
- synth_ai/tracing_v3/storage/base.py +8 -7
- synth_ai/tracing_v3/storage/config.py +4 -4
- synth_ai/tracing_v3/storage/factory.py +4 -4
- synth_ai/tracing_v3/storage/utils.py +9 -9
- synth_ai/tracing_v3/turso/__init__.py +3 -3
- synth_ai/tracing_v3/turso/daemon.py +9 -9
- synth_ai/tracing_v3/turso/manager.py +60 -48
- synth_ai/tracing_v3/turso/models.py +24 -19
- synth_ai/tracing_v3/utils.py +5 -5
- synth_ai/tui/__main__.py +1 -1
- synth_ai/tui/cli/query_experiments.py +2 -3
- synth_ai/tui/cli/query_experiments_v3.py +2 -3
- synth_ai/tui/dashboard.py +97 -86
- synth_ai/v0/tracing/abstractions.py +28 -28
- synth_ai/v0/tracing/base_client.py +9 -9
- synth_ai/v0/tracing/client_manager.py +7 -7
- synth_ai/v0/tracing/config.py +7 -7
- synth_ai/v0/tracing/context.py +6 -6
- synth_ai/v0/tracing/decorators.py +6 -5
- synth_ai/v0/tracing/events/manage.py +1 -1
- synth_ai/v0/tracing/events/store.py +5 -4
- synth_ai/v0/tracing/immediate_client.py +4 -5
- synth_ai/v0/tracing/local.py +3 -3
- synth_ai/v0/tracing/log_client_base.py +4 -5
- synth_ai/v0/tracing/retry_queue.py +5 -6
- synth_ai/v0/tracing/trackers.py +25 -25
- synth_ai/v0/tracing/upload.py +6 -0
- synth_ai/v0/tracing_v1/__init__.py +1 -1
- synth_ai/v0/tracing_v1/abstractions.py +28 -28
- synth_ai/v0/tracing_v1/base_client.py +9 -9
- synth_ai/v0/tracing_v1/client_manager.py +7 -7
- synth_ai/v0/tracing_v1/config.py +7 -7
- synth_ai/v0/tracing_v1/context.py +6 -6
- synth_ai/v0/tracing_v1/decorators.py +7 -6
- synth_ai/v0/tracing_v1/events/manage.py +1 -1
- synth_ai/v0/tracing_v1/events/store.py +5 -4
- synth_ai/v0/tracing_v1/immediate_client.py +4 -5
- synth_ai/v0/tracing_v1/local.py +3 -3
- synth_ai/v0/tracing_v1/log_client_base.py +4 -5
- synth_ai/v0/tracing_v1/retry_queue.py +5 -6
- synth_ai/v0/tracing_v1/trackers.py +25 -25
- synth_ai/v0/tracing_v1/upload.py +25 -24
- synth_ai/zyk/__init__.py +1 -0
- {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/METADATA +2 -11
- synth_ai-0.2.4.dev7.dist-info/RECORD +299 -0
- synth_ai-0.2.4.dev5.dist-info/RECORD +0 -287
- {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/licenses/LICENSE +0 -0
- {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
|
9
|
-
|
10
|
-
from .
|
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 .
|
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:
|
22
|
-
db_url:
|
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:
|
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:
|
42
|
+
self.db: AsyncSQLTraceManager | None = None
|
37
43
|
self.auto_save = auto_save
|
38
|
-
self._current_step:
|
44
|
+
self._current_step: SessionTimeStep | None = None
|
39
45
|
|
40
46
|
@property
|
41
|
-
def current_session(self) ->
|
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) ->
|
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:
|
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:
|
115
|
-
metadata:
|
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:
|
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:
|
203
|
-
message_time:
|
204
|
-
metadata:
|
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:
|
285
|
-
metadata:
|
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:
|
306
|
-
metadata:
|
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:
|
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
|
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) ->
|
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:
|
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:
|
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
|
-
) ->
|
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:
|
113
|
-
) ->
|
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:
|
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) ->
|
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
|
-
|
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:
|
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:
|
47
|
-
) ->
|
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,
|
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,
|
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:
|
123
|
+
self.operations: dict[str, dict[str, Any]] = {}
|
124
124
|
|
125
125
|
def record_operation(
|
126
|
-
self, operation: str, duration: float, success: bool, size:
|
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:
|
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, {})
|
@@ -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
|
5
|
+
import subprocess
|
7
6
|
import time
|
7
|
+
|
8
8
|
import requests
|
9
|
-
|
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:
|
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
|
-
|
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:
|
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() ->
|
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
|
27
|
+
from typing import Any
|
28
|
+
|
27
29
|
import pandas as pd
|
28
|
-
from sqlalchemy import select,
|
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
|
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 ..
|
42
|
+
from ..config import CONFIG
|
45
43
|
from .models import (
|
46
44
|
Base,
|
47
|
-
|
48
|
-
|
45
|
+
analytics_views,
|
46
|
+
)
|
47
|
+
from .models import (
|
49
48
|
Event as DBEvent,
|
50
|
-
|
49
|
+
)
|
50
|
+
from .models import (
|
51
51
|
Experiment as DBExperiment,
|
52
|
-
|
53
|
-
|
54
|
-
|
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:
|
80
|
+
def __init__(self, db_url: str | None = None):
|
75
81
|
self.db_url = db_url or CONFIG.db_url
|
76
|
-
self.engine:
|
77
|
-
self.SessionLocal:
|
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:
|
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:
|
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) ->
|
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(
|
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,
|
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:
|
418
|
-
configuration:
|
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:
|
444
|
-
) ->
|
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:
|
474
|
-
) ->
|
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
|
"""
|