synth-ai 0.2.4.dev6__py3-none-any.whl → 0.2.4.dev8__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 +25 -32
- 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 +47 -2
- 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 +13 -13
- 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 +5 -5
- synth_ai/environments/examples/wordle/engine.py +32 -25
- synth_ai/environments/examples/wordle/environment.py +21 -16
- synth_ai/environments/examples/wordle/helpers/generate_instances_wordfreq.py +6 -6
- synth_ai/environments/examples/wordle/taskset.py +20 -12
- synth_ai/environments/reproducibility/core.py +1 -1
- synth_ai/environments/reproducibility/tree.py +21 -21
- synth_ai/environments/service/app.py +3 -2
- synth_ai/environments/service/core_routes.py +104 -110
- 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/http.py +102 -0
- synth_ai/inference/__init__.py +7 -0
- synth_ai/inference/client.py +20 -0
- synth_ai/jobs/client.py +246 -0
- synth_ai/learning/__init__.py +24 -0
- synth_ai/learning/client.py +149 -0
- synth_ai/learning/config.py +43 -0
- synth_ai/learning/constants.py +29 -0
- synth_ai/learning/ft_client.py +59 -0
- synth_ai/learning/gateway.py +1 -3
- synth_ai/learning/health.py +43 -0
- synth_ai/learning/jobs.py +205 -0
- synth_ai/learning/prompts/banking77_injection_eval.py +15 -10
- synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +26 -14
- synth_ai/learning/prompts/mipro.py +61 -52
- synth_ai/learning/prompts/random_search.py +42 -43
- synth_ai/learning/prompts/run_mipro_banking77.py +32 -20
- synth_ai/learning/prompts/run_random_search_banking77.py +71 -52
- synth_ai/learning/rl_client.py +256 -0
- synth_ai/learning/sse.py +58 -0
- synth_ai/learning/validators.py +48 -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 +33 -10
- synth_ai/lm/core/synth_models.py +48 -0
- synth_ai/lm/core/vendor_clients.py +26 -22
- synth_ai/lm/injection.py +7 -8
- synth_ai/lm/overrides.py +21 -19
- synth_ai/lm/provider_support/__init__.py +1 -1
- synth_ai/lm/provider_support/anthropic.py +15 -15
- synth_ai/lm/provider_support/openai.py +23 -21
- 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 +36 -27
- 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 +42 -13
- synth_ai/lm/vendors/openai_standard.py +158 -101
- 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 +38 -28
- 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 +425 -75
- synth_ai/lm/warmup.py +8 -7
- synth_ai/rl/__init__.py +30 -0
- synth_ai/rl/contracts.py +32 -0
- synth_ai/rl/env_keys.py +137 -0
- synth_ai/rl/secrets.py +19 -0
- synth_ai/scripts/verify_rewards.py +100 -0
- synth_ai/task/__init__.py +10 -0
- synth_ai/task/contracts.py +120 -0
- synth_ai/task/health.py +28 -0
- synth_ai/task/validators.py +12 -0
- 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 +24 -22
- 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 +158 -31
- 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 +278 -48
- synth_ai/tracing_v3/turso/models.py +77 -19
- synth_ai/tracing_v3/utils.py +5 -5
- 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.dev8.dist-info/METADATA +635 -0
- synth_ai-0.2.4.dev8.dist-info/RECORD +317 -0
- synth_ai/tui/__init__.py +0 -1
- synth_ai/tui/__main__.py +0 -13
- synth_ai/tui/cli/__init__.py +0 -1
- synth_ai/tui/cli/query_experiments.py +0 -165
- synth_ai/tui/cli/query_experiments_v3.py +0 -165
- synth_ai/tui/dashboard.py +0 -329
- synth_ai-0.2.4.dev6.dist-info/METADATA +0 -203
- synth_ai-0.2.4.dev6.dist-info/RECORD +0 -299
- {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev8.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev8.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev8.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev8.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
|
"""
|
@@ -101,6 +107,10 @@ class SessionTracer:
|
|
101
107
|
if self.auto_save and self.db is None:
|
102
108
|
await self.initialize()
|
103
109
|
|
110
|
+
# Ensure session row exists for incremental writes
|
111
|
+
if self.db:
|
112
|
+
await self.db.ensure_session(session_id, created_at=self._current_trace.created_at, metadata=metadata or {})
|
113
|
+
|
104
114
|
# Trigger hooks
|
105
115
|
await self.hooks.trigger(
|
106
116
|
"session_start", session_id=session_id, metadata=metadata or {}
|
@@ -111,8 +121,8 @@ class SessionTracer:
|
|
111
121
|
async def start_timestep(
|
112
122
|
self,
|
113
123
|
step_id: str,
|
114
|
-
turn_number:
|
115
|
-
metadata:
|
124
|
+
turn_number: int | None = None,
|
125
|
+
metadata: dict[str, Any] | None = None,
|
116
126
|
) -> SessionTimeStep:
|
117
127
|
"""Start a new timestep.
|
118
128
|
|
@@ -146,9 +156,20 @@ class SessionTracer:
|
|
146
156
|
"timestep_start", step=step, session_id=self._current_trace.session_id
|
147
157
|
)
|
148
158
|
|
159
|
+
# Ensure timestep row exists in DB for incremental linkage
|
160
|
+
if self.db:
|
161
|
+
await self.db.ensure_timestep(
|
162
|
+
self._current_trace.session_id,
|
163
|
+
step_id=step.step_id,
|
164
|
+
step_index=step.step_index,
|
165
|
+
turn_number=turn_number,
|
166
|
+
started_at=step.timestamp,
|
167
|
+
metadata=metadata or {},
|
168
|
+
)
|
169
|
+
|
149
170
|
return step
|
150
171
|
|
151
|
-
async def end_timestep(self, step_id:
|
172
|
+
async def end_timestep(self, step_id: str | None = None):
|
152
173
|
"""End the current or specified timestep."""
|
153
174
|
if self._current_trace is None:
|
154
175
|
raise RuntimeError("No active session")
|
@@ -174,7 +195,7 @@ class SessionTracer:
|
|
174
195
|
if step == self._current_step:
|
175
196
|
self._current_step = None
|
176
197
|
|
177
|
-
async def record_event(self, event: BaseEvent):
|
198
|
+
async def record_event(self, event: BaseEvent) -> int | None:
|
178
199
|
"""Record an event.
|
179
200
|
|
180
201
|
Args:
|
@@ -195,14 +216,54 @@ class SessionTracer:
|
|
195
216
|
if self._current_step:
|
196
217
|
self._current_step.events.append(event)
|
197
218
|
|
219
|
+
# Persist incrementally if DB is available; return DB event id
|
220
|
+
if self.db:
|
221
|
+
timestep_db_id = None
|
222
|
+
if self._current_step:
|
223
|
+
# ensure timestep exists and get id
|
224
|
+
timestep_db_id = await self.db.ensure_timestep(
|
225
|
+
self._current_trace.session_id,
|
226
|
+
step_id=self._current_step.step_id,
|
227
|
+
step_index=self._current_step.step_index,
|
228
|
+
turn_number=self._current_step.turn_number,
|
229
|
+
started_at=self._current_step.timestamp,
|
230
|
+
completed_at=self._current_step.completed_at,
|
231
|
+
metadata=self._current_step.step_metadata,
|
232
|
+
)
|
233
|
+
event_id = await self.db.insert_event_row(
|
234
|
+
self._current_trace.session_id,
|
235
|
+
timestep_db_id=timestep_db_id,
|
236
|
+
event=event,
|
237
|
+
)
|
238
|
+
# Auto-insert an event reward if EnvironmentEvent carries reward
|
239
|
+
try:
|
240
|
+
from .abstractions import EnvironmentEvent # local import to avoid cycles
|
241
|
+
|
242
|
+
if isinstance(event, EnvironmentEvent) and event.reward is not None:
|
243
|
+
await self.record_event_reward(
|
244
|
+
event_id=event_id,
|
245
|
+
message_id=None,
|
246
|
+
turn_number=self._current_step.turn_number if self._current_step else None,
|
247
|
+
reward_value=float(event.reward),
|
248
|
+
reward_type="sparse",
|
249
|
+
key=None,
|
250
|
+
annotation=getattr(event, "event_metadata", None),
|
251
|
+
source="environment",
|
252
|
+
)
|
253
|
+
except Exception:
|
254
|
+
# Do not fail tracing if reward recording fails
|
255
|
+
pass
|
256
|
+
return event_id
|
257
|
+
return None
|
258
|
+
|
198
259
|
async def record_message(
|
199
260
|
self,
|
200
261
|
content: str,
|
201
262
|
message_type: str,
|
202
|
-
event_time:
|
203
|
-
message_time:
|
204
|
-
metadata:
|
205
|
-
):
|
263
|
+
event_time: float | None = None,
|
264
|
+
message_time: int | None = None,
|
265
|
+
metadata: dict[str, Any] | None = None,
|
266
|
+
) -> int | None:
|
206
267
|
"""Record a message.
|
207
268
|
|
208
269
|
Args:
|
@@ -236,6 +297,31 @@ class SessionTracer:
|
|
236
297
|
if self._current_step:
|
237
298
|
self._current_step.markov_blanket_messages.append(msg)
|
238
299
|
|
300
|
+
# Persist incrementally and return DB message id
|
301
|
+
if self.db:
|
302
|
+
timestep_db_id = None
|
303
|
+
if self._current_step:
|
304
|
+
timestep_db_id = await self.db.ensure_timestep(
|
305
|
+
self._current_trace.session_id,
|
306
|
+
step_id=self._current_step.step_id,
|
307
|
+
step_index=self._current_step.step_index,
|
308
|
+
turn_number=self._current_step.turn_number,
|
309
|
+
started_at=self._current_step.timestamp,
|
310
|
+
completed_at=self._current_step.completed_at,
|
311
|
+
metadata=self._current_step.step_metadata,
|
312
|
+
)
|
313
|
+
message_id = await self.db.insert_message_row(
|
314
|
+
self._current_trace.session_id,
|
315
|
+
timestep_db_id=timestep_db_id,
|
316
|
+
message_type=message_type,
|
317
|
+
content=content,
|
318
|
+
event_time=msg.time_record.event_time,
|
319
|
+
message_time=msg.time_record.message_time,
|
320
|
+
metadata=msg.metadata,
|
321
|
+
)
|
322
|
+
return message_id
|
323
|
+
return None
|
324
|
+
|
239
325
|
async def end_session(self, save: bool = None) -> SessionTrace:
|
240
326
|
"""End the current session.
|
241
327
|
|
@@ -281,8 +367,8 @@ class SessionTracer:
|
|
281
367
|
@asynccontextmanager
|
282
368
|
async def session(
|
283
369
|
self,
|
284
|
-
session_id:
|
285
|
-
metadata:
|
370
|
+
session_id: str | None = None,
|
371
|
+
metadata: dict[str, Any] | None = None,
|
286
372
|
save: bool = None,
|
287
373
|
):
|
288
374
|
"""Context manager for a session.
|
@@ -302,8 +388,8 @@ class SessionTracer:
|
|
302
388
|
async def timestep(
|
303
389
|
self,
|
304
390
|
step_id: str,
|
305
|
-
turn_number:
|
306
|
-
metadata:
|
391
|
+
turn_number: int | None = None,
|
392
|
+
metadata: dict[str, Any] | None = None,
|
307
393
|
):
|
308
394
|
"""Context manager for a timestep.
|
309
395
|
|
@@ -318,7 +404,7 @@ class SessionTracer:
|
|
318
404
|
finally:
|
319
405
|
await self.end_timestep(step_id)
|
320
406
|
|
321
|
-
async def get_session_history(self, limit:
|
407
|
+
async def get_session_history(self, limit: int | None = None) -> list[dict[str, Any]]:
|
322
408
|
"""Get recent session history from database."""
|
323
409
|
if self.db is None:
|
324
410
|
await self.initialize()
|
@@ -335,3 +421,44 @@ class SessionTracer:
|
|
335
421
|
if self.db:
|
336
422
|
await self.db.close()
|
337
423
|
self.db = None
|
424
|
+
|
425
|
+
# -------------------------------
|
426
|
+
# Reward recording helpers
|
427
|
+
# -------------------------------
|
428
|
+
|
429
|
+
async def record_outcome_reward(self, *, total_reward: int, achievements_count: int, total_steps: int) -> int | None:
|
430
|
+
"""Record an episode-level outcome reward for the current session."""
|
431
|
+
if self._current_trace is None:
|
432
|
+
raise RuntimeError("No active session")
|
433
|
+
if self.db is None:
|
434
|
+
await self.initialize()
|
435
|
+
if self.db:
|
436
|
+
return await self.db.insert_outcome_reward(
|
437
|
+
self._current_trace.session_id,
|
438
|
+
total_reward=total_reward,
|
439
|
+
achievements_count=achievements_count,
|
440
|
+
total_steps=total_steps,
|
441
|
+
)
|
442
|
+
return None
|
443
|
+
|
444
|
+
# StepMetrics removed in favor of event_rewards; use record_event_reward for per-turn shaped values
|
445
|
+
|
446
|
+
async def record_event_reward(self, *, event_id: int, message_id: int | None = None, turn_number: int | None = None, reward_value: float = 0.0, reward_type: str | None = None, key: str | None = None, annotation: dict[str, Any] | None = None, source: str | None = None) -> int | None:
|
447
|
+
"""Record a first-class event-level reward with optional annotations."""
|
448
|
+
if self._current_trace is None:
|
449
|
+
raise RuntimeError("No active session")
|
450
|
+
if self.db is None:
|
451
|
+
await self.initialize()
|
452
|
+
if self.db:
|
453
|
+
return await self.db.insert_event_reward(
|
454
|
+
self._current_trace.session_id,
|
455
|
+
event_id=event_id,
|
456
|
+
message_id=message_id,
|
457
|
+
turn_number=turn_number,
|
458
|
+
reward_value=reward_value,
|
459
|
+
reward_type=reward_type,
|
460
|
+
key=key,
|
461
|
+
annotation=annotation,
|
462
|
+
source=source,
|
463
|
+
)
|
464
|
+
return None
|
@@ -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
|