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,21 +1,20 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from dataclasses import dataclass
|
4
|
-
from typing import Any, Dict, List, Optional, Tuple
|
5
|
-
from collections import Counter
|
6
3
|
import random
|
7
4
|
import string
|
5
|
+
from collections import Counter
|
6
|
+
from dataclasses import dataclass
|
7
|
+
from typing import Any
|
8
8
|
|
9
|
-
from synth_ai.environments.
|
10
|
-
from synth_ai.environments.reproducibility.core import IReproducibleEngine
|
11
|
-
from synth_ai.environments.environment.rewards.core import RewardStack, RewardComponent
|
9
|
+
from synth_ai.environments.environment.rewards.core import RewardComponent, RewardStack
|
12
10
|
from synth_ai.environments.environment.shared_engine import (
|
13
11
|
GetObservationCallable,
|
14
12
|
InternalObservation,
|
15
13
|
)
|
14
|
+
from synth_ai.environments.reproducibility.core import IReproducibleEngine
|
15
|
+
from synth_ai.environments.stateful.engine import StatefulEngine, StatefulEngineSnapshot
|
16
16
|
from synth_ai.environments.tasks.core import TaskInstance
|
17
17
|
|
18
|
-
|
19
18
|
DEFAULT_SOLUTIONS = [
|
20
19
|
"cigar",
|
21
20
|
"rebut",
|
@@ -98,10 +97,10 @@ class WordlePublicState:
|
|
98
97
|
word_length: int
|
99
98
|
remaining_guesses: int
|
100
99
|
max_guesses: int
|
101
|
-
guesses:
|
102
|
-
feedback:
|
103
|
-
last_feedback:
|
104
|
-
last_guess:
|
100
|
+
guesses: list[str]
|
101
|
+
feedback: list[str] # Parallel to guesses; strings of 'G/Y/B'
|
102
|
+
last_feedback: str | None
|
103
|
+
last_guess: str | None
|
105
104
|
terminated: bool
|
106
105
|
status: str # "in_progress" | "won" | "lost"
|
107
106
|
|
@@ -110,7 +109,7 @@ class WordlePublicState:
|
|
110
109
|
if not self.guesses:
|
111
110
|
return "(no guesses yet)"
|
112
111
|
lines = []
|
113
|
-
for g, fb in zip(self.guesses, self.feedback):
|
112
|
+
for g, fb in zip(self.guesses, self.feedback, strict=False):
|
114
113
|
spaced = " ".join(list(fb))
|
115
114
|
lines.append(f"{g.upper()} | {spaced}")
|
116
115
|
return "\n".join(lines)
|
@@ -126,8 +125,8 @@ class WordlePrivateState:
|
|
126
125
|
|
127
126
|
@dataclass
|
128
127
|
class WordleEngineSnapshot(StatefulEngineSnapshot):
|
129
|
-
task_instance_dict:
|
130
|
-
engine_snapshot:
|
128
|
+
task_instance_dict: dict
|
129
|
+
engine_snapshot: dict
|
131
130
|
|
132
131
|
|
133
132
|
class WordleWinComponent(RewardComponent):
|
@@ -156,20 +155,26 @@ class WordleEngine(StatefulEngine, IReproducibleEngine):
|
|
156
155
|
self.max_guesses: int = getattr(md, "max_guesses", 6) if md else 6
|
157
156
|
self.enforce_wordlist: bool = getattr(md, "enforce_wordlist", False) if md else False
|
158
157
|
# Toggle: whether invalid actions consume a turn (default True)
|
159
|
-
self.consume_invalid_attempts: bool =
|
158
|
+
self.consume_invalid_attempts: bool = (
|
159
|
+
getattr(md, "consume_invalid_attempts", True) if md else True
|
160
|
+
)
|
160
161
|
|
161
|
-
self.base_word_list:
|
162
|
+
self.base_word_list: list[str] = [
|
162
163
|
w for w in DEFAULT_SOLUTIONS if len(w) == self.word_length
|
163
164
|
] or [w for w in DEFAULT_SOLUTIONS if len(w) == 5]
|
164
165
|
|
165
166
|
# Target selection: prefer explicit target_word in metadata; else pick deterministically by seed
|
166
|
-
self.fixed_target:
|
167
|
-
|
167
|
+
self.fixed_target: str | None = (
|
168
|
+
_sanitize(getattr(md, "target_word", ""))
|
169
|
+
if md and getattr(md, "target_word", None)
|
170
|
+
else None
|
171
|
+
)
|
172
|
+
self.seed: int | None = getattr(md, "seed", None) if md else None
|
168
173
|
|
169
174
|
# Runtime state
|
170
|
-
self.target:
|
171
|
-
self.guesses:
|
172
|
-
self.feedback:
|
175
|
+
self.target: str | None = None
|
176
|
+
self.guesses: list[str] = []
|
177
|
+
self.feedback: list[str] = []
|
173
178
|
self.remaining_guesses: int = self.max_guesses
|
174
179
|
self.status: str = "in_progress"
|
175
180
|
self.terminated: bool = False
|
@@ -179,7 +184,9 @@ class WordleEngine(StatefulEngine, IReproducibleEngine):
|
|
179
184
|
self.invalid_component = WordleInvalidGuessComponent()
|
180
185
|
self.reward_stack = RewardStack([WordleWinComponent(), self.invalid_component])
|
181
186
|
|
182
|
-
async def _reset_engine(
|
187
|
+
async def _reset_engine(
|
188
|
+
self, *, seed: int | None = None
|
189
|
+
) -> tuple[WordlePrivateState, WordlePublicState]:
|
183
190
|
if seed is None:
|
184
191
|
seed = self.seed
|
185
192
|
if seed is not None and self.fixed_target is None:
|
@@ -211,7 +218,7 @@ class WordleEngine(StatefulEngine, IReproducibleEngine):
|
|
211
218
|
)
|
212
219
|
return priv, pub
|
213
220
|
|
214
|
-
async def _step_engine(self, action: str) ->
|
221
|
+
async def _step_engine(self, action: str) -> tuple[WordlePrivateState, WordlePublicState]:
|
215
222
|
assert self.target is not None
|
216
223
|
guess = _sanitize(action)
|
217
224
|
|
@@ -307,7 +314,7 @@ class WordleEngine(StatefulEngine, IReproducibleEngine):
|
|
307
314
|
)
|
308
315
|
|
309
316
|
@classmethod
|
310
|
-
async def _deserialize_engine(cls, snapshot: WordleEngineSnapshot) ->
|
317
|
+
async def _deserialize_engine(cls, snapshot: WordleEngineSnapshot) -> WordleEngine:
|
311
318
|
task_instance = await TaskInstance.deserialize(snapshot.task_instance_dict)
|
312
319
|
engine = cls(task_instance)
|
313
320
|
s = snapshot.engine_snapshot
|
@@ -327,7 +334,7 @@ class WordleEngine(StatefulEngine, IReproducibleEngine):
|
|
327
334
|
engine.total_reward = s.get("total_reward", 0.0)
|
328
335
|
return engine
|
329
336
|
|
330
|
-
def get_current_states_for_observation(self) ->
|
337
|
+
def get_current_states_for_observation(self) -> tuple[WordlePrivateState, WordlePublicState]:
|
331
338
|
pub = WordlePublicState(
|
332
339
|
word_length=self.word_length,
|
333
340
|
remaining_guesses=self.remaining_guesses,
|
@@ -1,10 +1,9 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from typing import
|
3
|
+
from typing import Any
|
4
|
+
|
4
5
|
from pydantic import BaseModel, Field
|
5
6
|
|
6
|
-
from synth_ai.environments.stateful.core import StatefulEnvironment
|
7
|
-
from synth_ai.environments.reproducibility.core import ReproducibleEnvironment
|
8
7
|
from synth_ai.environments.environment.shared_engine import (
|
9
8
|
GetObservationCallable,
|
10
9
|
InternalObservation,
|
@@ -14,15 +13,17 @@ from synth_ai.environments.environment.tools import (
|
|
14
13
|
EnvToolCall,
|
15
14
|
ToolResult,
|
16
15
|
)
|
16
|
+
from synth_ai.environments.reproducibility.core import ReproducibleEnvironment
|
17
|
+
from synth_ai.environments.stateful.core import StatefulEnvironment
|
17
18
|
from synth_ai.environments.tasks.core import TaskInstance
|
18
19
|
|
19
20
|
from .engine import (
|
21
|
+
SynthWordleCheckpointObservationCallable,
|
22
|
+
SynthWordleObservationCallable,
|
20
23
|
WordleEngine,
|
21
|
-
WordlePublicState,
|
22
|
-
WordlePrivateState,
|
23
24
|
WordleEngineSnapshot,
|
24
|
-
|
25
|
-
|
25
|
+
WordlePrivateState,
|
26
|
+
WordlePublicState,
|
26
27
|
)
|
27
28
|
|
28
29
|
|
@@ -47,15 +48,17 @@ class WordleInteractTool(AbstractTool):
|
|
47
48
|
except Exception as e:
|
48
49
|
# Return current state with error message
|
49
50
|
priv, pub = self.engine.get_current_states_for_observation()
|
50
|
-
return ToolResult(
|
51
|
+
return ToolResult(
|
52
|
+
ok=False, error=str(e), payload={"public_state": pub, "private_state": priv}
|
53
|
+
)
|
51
54
|
|
52
55
|
|
53
56
|
class WordleEnvironment(StatefulEnvironment, ReproducibleEnvironment[WordleEngine]):
|
54
57
|
def __init__(
|
55
58
|
self,
|
56
59
|
task_instance: TaskInstance,
|
57
|
-
custom_step_obs:
|
58
|
-
custom_ckpt_obs:
|
60
|
+
custom_step_obs: GetObservationCallable | None = None,
|
61
|
+
custom_ckpt_obs: GetObservationCallable | None = None,
|
59
62
|
) -> None:
|
60
63
|
self.name = "Wordle"
|
61
64
|
self.task_instance = task_instance
|
@@ -101,10 +104,13 @@ class WordleEnvironment(StatefulEnvironment, ReproducibleEnvironment[WordleEngin
|
|
101
104
|
if "tool" in tool_calls:
|
102
105
|
validated = EnvToolCall(tool=tool_calls["tool"], args=tool_calls.get("args", {}))
|
103
106
|
elif "name" in tool_calls:
|
104
|
-
validated = EnvToolCall(
|
107
|
+
validated = EnvToolCall(
|
108
|
+
tool=tool_calls["name"], args=tool_calls.get("parameters", {})
|
109
|
+
)
|
105
110
|
elif "function" in tool_calls:
|
106
111
|
validated = EnvToolCall(
|
107
|
-
tool=tool_calls["function"]["name"],
|
112
|
+
tool=tool_calls["function"]["name"],
|
113
|
+
args=tool_calls["function"].get("arguments", {}),
|
108
114
|
)
|
109
115
|
else:
|
110
116
|
# Treat remaining keys as args; default tool name
|
@@ -129,8 +135,8 @@ class WordleEnvironment(StatefulEnvironment, ReproducibleEnvironment[WordleEngin
|
|
129
135
|
self,
|
130
136
|
priv: WordlePrivateState,
|
131
137
|
pub: WordlePublicState,
|
132
|
-
obs_cb:
|
133
|
-
extra_obs:
|
138
|
+
obs_cb: GetObservationCallable | None,
|
139
|
+
extra_obs: dict[str, Any] | None = None,
|
134
140
|
) -> InternalObservation:
|
135
141
|
if obs_cb:
|
136
142
|
obs = await obs_cb.get_observation(pub, priv)
|
@@ -146,9 +152,8 @@ class WordleEnvironment(StatefulEnvironment, ReproducibleEnvironment[WordleEngin
|
|
146
152
|
@classmethod
|
147
153
|
async def _deserialize_engine(
|
148
154
|
cls, snapshot: WordleEngineSnapshot, task_instance: TaskInstance
|
149
|
-
) ->
|
155
|
+
) -> WordleEnvironment:
|
150
156
|
env = cls(task_instance)
|
151
157
|
env.engine = await WordleEngine._deserialize_engine(snapshot)
|
152
158
|
env._interact_tool = WordleInteractTool(env.engine)
|
153
159
|
return env
|
154
|
-
|
@@ -10,24 +10,24 @@ Usage:
|
|
10
10
|
This script writes a deterministic list of 5-letter English words ranked by frequency.
|
11
11
|
Commit the resulting instances.json to remove runtime dependencies.
|
12
12
|
"""
|
13
|
+
|
13
14
|
from __future__ import annotations
|
14
15
|
|
15
16
|
import argparse
|
16
17
|
import json
|
17
18
|
import re
|
18
|
-
from typing import List
|
19
19
|
|
20
|
-
from wordfreq import
|
20
|
+
from wordfreq import top_n_list, zipf_frequency
|
21
21
|
|
22
22
|
|
23
|
-
def build_word_list(count: int, length: int, min_zipf: float, wordlist: str = "large") ->
|
24
|
-
|
25
|
-
cands = [w.lower() for w in top_n_list("en",
|
23
|
+
def build_word_list(count: int, length: int, min_zipf: float, wordlist: str = "large") -> list[str]:
|
24
|
+
n_candidates = max(count * 20, 5000)
|
25
|
+
cands = [w.lower() for w in top_n_list("en", n_candidates, wordlist=wordlist)]
|
26
26
|
cands = [w for w in cands if len(w) == length and re.fullmatch(r"[a-z]+", w)]
|
27
27
|
scored = [(w, zipf_frequency(w, "en")) for w in cands]
|
28
28
|
scored = [p for p in scored if p[1] >= float(min_zipf)]
|
29
29
|
scored.sort(key=lambda t: (-t[1], t[0]))
|
30
|
-
out:
|
30
|
+
out: list[str] = []
|
31
31
|
seen = set()
|
32
32
|
for w, _ in scored:
|
33
33
|
if w in seen:
|
@@ -1,19 +1,17 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import json
|
4
|
-
import random
|
5
4
|
from dataclasses import dataclass
|
6
5
|
from pathlib import Path
|
7
|
-
from
|
8
|
-
from uuid import uuid4, UUID
|
6
|
+
from uuid import UUID
|
9
7
|
|
10
8
|
from synth_ai.environments.tasks.core import (
|
11
|
-
TaskInstance,
|
12
|
-
TaskInstanceMetadata,
|
13
|
-
TaskInstanceSet,
|
14
9
|
Impetus,
|
15
10
|
Intent,
|
16
11
|
SplitInfo,
|
12
|
+
TaskInstance,
|
13
|
+
TaskInstanceMetadata,
|
14
|
+
TaskInstanceSet,
|
17
15
|
)
|
18
16
|
|
19
17
|
from .engine import DEFAULT_SOLUTIONS
|
@@ -53,7 +51,7 @@ class WordleTaskInstance(TaskInstance):
|
|
53
51
|
}
|
54
52
|
|
55
53
|
@classmethod
|
56
|
-
async def deserialize(cls, data: dict) ->
|
54
|
+
async def deserialize(cls, data: dict) -> WordleTaskInstance:
|
57
55
|
from uuid import UUID
|
58
56
|
|
59
57
|
metadata = WordleTaskInstanceMetadata(
|
@@ -81,16 +79,18 @@ class WordleTaskInstance(TaskInstance):
|
|
81
79
|
|
82
80
|
def _stable_uuid_for_instance(idx: int, target: str) -> UUID:
|
83
81
|
import uuid
|
82
|
+
|
84
83
|
return uuid.uuid5(uuid.NAMESPACE_URL, f"wordle-fixed-v1:{idx}:{target}")
|
85
84
|
|
86
85
|
|
87
|
-
def _load_fixed_instances_json() -> tuple[
|
86
|
+
def _load_fixed_instances_json() -> tuple[list[dict], dict]:
|
88
87
|
"""Load fixed instances definition from instances.json (if present).
|
89
88
|
|
90
89
|
Returns a tuple (instances, defaults) where instances is a list of dicts with at least
|
91
90
|
target_word fields, and defaults contains default params.
|
92
91
|
"""
|
93
92
|
import os
|
93
|
+
|
94
94
|
# Allow override via env var
|
95
95
|
override = os.getenv("WORDLE_INSTANCES_JSON")
|
96
96
|
p = Path(override) if override else Path(__file__).with_name("instances.json")
|
@@ -126,11 +126,17 @@ async def create_wordle_taskset(
|
|
126
126
|
|
127
127
|
json_insts, json_defaults = _load_fixed_instances_json()
|
128
128
|
|
129
|
-
instances:
|
129
|
+
instances: list[WordleTaskInstance] = []
|
130
130
|
# Assemble fixed targets from JSON only (no runtime generation)
|
131
|
-
fixed_targets:
|
131
|
+
fixed_targets: list[str] = []
|
132
132
|
if json_insts:
|
133
|
-
fixed_targets.extend(
|
133
|
+
fixed_targets.extend(
|
134
|
+
[
|
135
|
+
str(r.get("target_word", "")).strip().lower()
|
136
|
+
for r in json_insts
|
137
|
+
if r.get("target_word")
|
138
|
+
]
|
139
|
+
)
|
134
140
|
|
135
141
|
if fixed_targets:
|
136
142
|
# Use fixed_targets, honoring defaults and slicing by sample_size
|
@@ -167,7 +173,9 @@ async def create_wordle_taskset(
|
|
167
173
|
instances.append(inst)
|
168
174
|
else:
|
169
175
|
# Procedural fallback: stable ordering from DEFAULT_SOLUTIONS
|
170
|
-
pool = [w for w in DEFAULT_SOLUTIONS if len(w) == word_length] or [
|
176
|
+
pool = [w for w in DEFAULT_SOLUTIONS if len(w) == word_length] or [
|
177
|
+
w for w in DEFAULT_SOLUTIONS if len(w) == 5
|
178
|
+
]
|
171
179
|
sample = pool[:sample_size]
|
172
180
|
for i, target in enumerate(sample):
|
173
181
|
seed = i
|
@@ -13,13 +13,14 @@ big “backend.production” code-base.
|
|
13
13
|
|
14
14
|
from __future__ import annotations
|
15
15
|
|
16
|
-
import json
|
17
|
-
import sqlite3
|
18
16
|
import gzip
|
19
|
-
import
|
17
|
+
import json
|
20
18
|
import logging
|
19
|
+
import pickle
|
20
|
+
import sqlite3
|
21
|
+
from collections.abc import Iterable
|
21
22
|
from pathlib import Path
|
22
|
-
from typing import Any
|
23
|
+
from typing import Any
|
23
24
|
|
24
25
|
import networkx as nx
|
25
26
|
|
@@ -31,10 +32,9 @@ log = logging.getLogger(__name__)
|
|
31
32
|
# --------------------------------------------------------------------------- #
|
32
33
|
# lightweight metadata record #
|
33
34
|
# --------------------------------------------------------------------------- #
|
34
|
-
import os
|
35
35
|
import hashlib
|
36
36
|
import logging
|
37
|
-
|
37
|
+
import os
|
38
38
|
|
39
39
|
log = logging.getLogger(__name__)
|
40
40
|
|
@@ -51,7 +51,7 @@ class FilesystemSnapshotStore:
|
|
51
51
|
is the SHA-256 hash of its compressed content.
|
52
52
|
"""
|
53
53
|
|
54
|
-
def __init__(self, base_dir:
|
54
|
+
def __init__(self, base_dir: str | Path = DEFAULT_SNAPSHOT_DIR):
|
55
55
|
self.base_dir = Path(base_dir)
|
56
56
|
try:
|
57
57
|
self.base_dir.mkdir(parents=True, exist_ok=True)
|
@@ -69,7 +69,7 @@ class FilesystemSnapshotStore:
|
|
69
69
|
filename = f"{key}.snapshot.gz"
|
70
70
|
return self.base_dir / filename
|
71
71
|
|
72
|
-
def write(self, blob:
|
72
|
+
def write(self, blob: bytes | dict[str, Any]) -> str:
|
73
73
|
"""
|
74
74
|
Stores a snapshot blob (bytes or dict) and returns its SHA-256 key.
|
75
75
|
|
@@ -95,7 +95,7 @@ class FilesystemSnapshotStore:
|
|
95
95
|
log.error(f"Failed to write snapshot: {e}", exc_info=True)
|
96
96
|
raise
|
97
97
|
|
98
|
-
def read(self, key: str) ->
|
98
|
+
def read(self, key: str) -> bytes | None:
|
99
99
|
"""
|
100
100
|
Retrieves the raw *compressed* snapshot bytes for a given key.
|
101
101
|
|
@@ -143,12 +143,12 @@ class TrajectorySnapshot:
|
|
143
143
|
def __init__(
|
144
144
|
self,
|
145
145
|
snap_id: str,
|
146
|
-
parent_id:
|
146
|
+
parent_id: str | None,
|
147
147
|
depth: int,
|
148
|
-
action:
|
148
|
+
action: Any | None,
|
149
149
|
reward: float = 0.0,
|
150
150
|
terminated: bool = False,
|
151
|
-
info:
|
151
|
+
info: dict[str, Any] | None = None,
|
152
152
|
):
|
153
153
|
self.snap_id = snap_id
|
154
154
|
self.parent_id = parent_id
|
@@ -186,9 +186,9 @@ class TrajectoryTreeStore:
|
|
186
186
|
|
187
187
|
def __init__(
|
188
188
|
self,
|
189
|
-
snapshot_store:
|
189
|
+
snapshot_store: FilesystemSnapshotStore | None = None,
|
190
190
|
*,
|
191
|
-
db_path:
|
191
|
+
db_path: Path | str | None = None,
|
192
192
|
):
|
193
193
|
self.snap_store = snapshot_store or FilesystemSnapshotStore()
|
194
194
|
self.graph: nx.DiGraph = nx.DiGraph()
|
@@ -202,7 +202,7 @@ class TrajectoryTreeStore:
|
|
202
202
|
|
203
203
|
# insertion -------------------------------------------------------------
|
204
204
|
|
205
|
-
def add_root(self, snapshot_blob: bytes, *, info:
|
205
|
+
def add_root(self, snapshot_blob: bytes, *, info: dict[str, Any] | None = None) -> str:
|
206
206
|
"""Insert the very first node and return its content-hash key."""
|
207
207
|
snap_id = self.snap_store.write(snapshot_blob)
|
208
208
|
self._add_node(TrajectorySnapshot(snap_id, None, 0, None, 0.0, False, info))
|
@@ -216,7 +216,7 @@ class TrajectoryTreeStore:
|
|
216
216
|
action: Any,
|
217
217
|
reward: float,
|
218
218
|
terminated: bool = False,
|
219
|
-
info:
|
219
|
+
info: dict[str, Any] | None = None,
|
220
220
|
) -> str:
|
221
221
|
"""Attach `snapshot_blob` as a child reached by `action` from *parent_id*."""
|
222
222
|
if parent_id not in self.graph:
|
@@ -230,10 +230,10 @@ class TrajectoryTreeStore:
|
|
230
230
|
|
231
231
|
# read-side helpers -----------------------------------------------------
|
232
232
|
|
233
|
-
def get_children(self, snap_id: str) ->
|
233
|
+
def get_children(self, snap_id: str) -> tuple[str, ...]:
|
234
234
|
return tuple(self.graph.successors(snap_id))
|
235
235
|
|
236
|
-
def get_parent(self, snap_id: str) ->
|
236
|
+
def get_parent(self, snap_id: str) -> str | None:
|
237
237
|
preds = tuple(self.graph.predecessors(snap_id))
|
238
238
|
return preds[0] if preds else None
|
239
239
|
|
@@ -246,17 +246,17 @@ class TrajectoryTreeStore:
|
|
246
246
|
"""Yield snapshot-ids that currently have no children."""
|
247
247
|
return (n for n in self.graph.nodes if self.is_leaf(n))
|
248
248
|
|
249
|
-
def path_to_root(self, snap_id: str) ->
|
249
|
+
def path_to_root(self, snap_id: str) -> tuple[str, ...]:
|
250
250
|
"""Return (snap_id, …, root_id)"""
|
251
251
|
path = [snap_id]
|
252
252
|
while (p := self.get_parent(path[-1])) is not None:
|
253
253
|
path.append(p)
|
254
254
|
return tuple(path)
|
255
255
|
|
256
|
-
def reconstruct_actions(self, snap_id: str) ->
|
256
|
+
def reconstruct_actions(self, snap_id: str) -> tuple[Any, ...]:
|
257
257
|
"""Return the sequence of *actions* from the root → `snap_id`."""
|
258
258
|
actions = []
|
259
|
-
for child, parent in zip(self.path_to_root(snap_id)[:-1], self.path_to_root(snap_id)[1:]):
|
259
|
+
for child, parent in zip(self.path_to_root(snap_id)[:-1], self.path_to_root(snap_id)[1:], strict=False):
|
260
260
|
actions.append(self.graph.edges[parent, child]["action"])
|
261
261
|
return tuple(reversed(actions))
|
262
262
|
|
@@ -1,5 +1,5 @@
|
|
1
|
-
import sys
|
2
1
|
import os # Added to ensure os is available before use
|
2
|
+
import sys
|
3
3
|
|
4
4
|
# Ensure local 'src' directory is on PYTHONPATH for dev installs
|
5
5
|
# Current file: <repo>/src/synth_env/service/app.py
|
@@ -12,12 +12,12 @@ print(f"SYS.PATH IN APP.PY: {sys.path}")
|
|
12
12
|
import logging
|
13
13
|
|
14
14
|
from fastapi import FastAPI
|
15
|
-
from synth_ai.environments.service.registry import list_supported_env_types, register_environment
|
16
15
|
from synth_ai.environments.service.core_routes import api_router
|
17
16
|
from synth_ai.environments.service.external_registry import (
|
18
17
|
ExternalRegistryConfig,
|
19
18
|
load_external_environments,
|
20
19
|
)
|
20
|
+
from synth_ai.environments.service.registry import list_supported_env_types, register_environment
|
21
21
|
|
22
22
|
# Configure logging with more detail
|
23
23
|
logging.basicConfig(
|
@@ -41,6 +41,7 @@ register_environment("CrafterCustom", ccustom.CrafterCustomEnvironment)
|
|
41
41
|
# Register Wordle example environment
|
42
42
|
try:
|
43
43
|
import synth_ai.environments.examples.wordle.environment as wordle_mod
|
44
|
+
|
44
45
|
register_environment("Wordle", wordle_mod.WordleEnvironment)
|
45
46
|
except Exception as _e:
|
46
47
|
# Keep service robust even if example env import fails
|