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,31 +1,26 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
from typing import Dict, Any, List, Optional
|
4
|
-
from types import SimpleNamespace
|
5
|
-
from pydantic import BaseModel
|
1
|
+
import base64
|
2
|
+
import logging
|
6
3
|
import os
|
7
|
-
import json
|
8
4
|
import pickle
|
9
|
-
import base64
|
10
|
-
import numpy as np
|
11
5
|
import tempfile
|
12
|
-
from dataclasses import dataclass
|
13
6
|
import time
|
14
|
-
import
|
7
|
+
from dataclasses import dataclass
|
8
|
+
from io import BytesIO
|
9
|
+
from types import SimpleNamespace
|
10
|
+
from typing import Any
|
11
|
+
from uuid import uuid4
|
15
12
|
|
13
|
+
import numpy as np
|
14
|
+
from fastapi import APIRouter, Body, HTTPException
|
15
|
+
from pydantic import BaseModel
|
16
|
+
from synth_ai.environments.environment.tools import EnvToolCall
|
16
17
|
from synth_ai.environments.service.registry import get_environment_cls, list_supported_env_types
|
17
18
|
from synth_ai.environments.stateful.core import StatefulEnvironment
|
18
|
-
from synth_ai.environments.environment.tools import EnvToolCall
|
19
19
|
|
20
20
|
# Set up logging
|
21
21
|
logger = logging.getLogger(__name__)
|
22
22
|
|
23
23
|
# Import tracing abstractions from v3
|
24
|
-
from synth_ai.tracing_v3.abstractions import (
|
25
|
-
RuntimeEvent,
|
26
|
-
SessionEventMarkovBlanketMessage,
|
27
|
-
TimeRecord,
|
28
|
-
)
|
29
24
|
|
30
25
|
# Try to import Redis for persistent storage
|
31
26
|
try:
|
@@ -52,7 +47,7 @@ if os.getenv("SYNTH_USE_INMEM", "1") == "1":
|
|
52
47
|
api_router = APIRouter()
|
53
48
|
|
54
49
|
# Fallback in-memory store if Redis is not available
|
55
|
-
instances:
|
50
|
+
instances: dict[str, StatefulEnvironment] = {}
|
56
51
|
|
57
52
|
|
58
53
|
# Environment-specific task instance creation
|
@@ -67,9 +62,9 @@ class MinimalTaskInstanceMetadata:
|
|
67
62
|
class MinimalIntent:
|
68
63
|
"""Minimal intent for environments that need it."""
|
69
64
|
|
70
|
-
rubric:
|
71
|
-
gold_trajectories:
|
72
|
-
gold_state_diff:
|
65
|
+
rubric: dict[str, Any]
|
66
|
+
gold_trajectories: Any | None = None
|
67
|
+
gold_state_diff: dict = None
|
73
68
|
deterministic_eval_functions: list = None
|
74
69
|
|
75
70
|
def __post_init__(self):
|
@@ -88,8 +83,8 @@ class MinimalImpetus:
|
|
88
83
|
|
89
84
|
def create_task_instance_for_environment(
|
90
85
|
env_name: str,
|
91
|
-
initial_state:
|
92
|
-
config:
|
86
|
+
initial_state: dict[str, Any] | None = None,
|
87
|
+
config: dict[str, Any] | None = None,
|
93
88
|
) -> Any:
|
94
89
|
"""Create appropriate task instance for different environments."""
|
95
90
|
|
@@ -172,7 +167,7 @@ def create_task_instance_for_environment(
|
|
172
167
|
|
173
168
|
|
174
169
|
async def reconstruct_task_instance_from_serialized(
|
175
|
-
env_name: str, serialized_data:
|
170
|
+
env_name: str, serialized_data: dict[str, Any]
|
176
171
|
) -> Any:
|
177
172
|
"""Reconstruct a task instance from serialized data for specific environment types."""
|
178
173
|
|
@@ -218,9 +213,9 @@ async def reconstruct_task_instance_from_serialized(
|
|
218
213
|
|
219
214
|
elif env_name == "Verilog":
|
220
215
|
# Verilog needs special handling with snapshot_dir
|
216
|
+
import tempfile
|
221
217
|
from types import SimpleNamespace
|
222
218
|
from uuid import UUID
|
223
|
-
import tempfile
|
224
219
|
|
225
220
|
task = SimpleNamespace()
|
226
221
|
task.id = UUID(serialized_data.get("id", str(uuid4())))
|
@@ -248,10 +243,11 @@ async def reconstruct_task_instance_from_serialized(
|
|
248
243
|
|
249
244
|
elif env_name == "NetHack":
|
250
245
|
# NetHack needs proper TaskInstance structure with NetHackTaskInstanceMetadata
|
251
|
-
from synth_ai.environments.examples.nethack.taskset import NetHackTaskInstanceMetadata
|
252
246
|
from types import SimpleNamespace
|
253
247
|
from uuid import UUID
|
254
248
|
|
249
|
+
from synth_ai.environments.examples.nethack.taskset import NetHackTaskInstanceMetadata
|
250
|
+
|
255
251
|
# Extract metadata from serialized data
|
256
252
|
metadata_data = serialized_data.get("metadata", {})
|
257
253
|
metadata = NetHackTaskInstanceMetadata(
|
@@ -381,7 +377,7 @@ class InstanceStorage:
|
|
381
377
|
else:
|
382
378
|
print(f"✅ Stored environment {env_id} in-memory (Redis not available)")
|
383
379
|
|
384
|
-
async def get(self, env_id: str) ->
|
380
|
+
async def get(self, env_id: str) -> StatefulEnvironment | None:
|
385
381
|
"""Retrieve an environment instance"""
|
386
382
|
# Try in-memory first (most reliable)
|
387
383
|
if env_id in instances:
|
@@ -405,7 +401,7 @@ class InstanceStorage:
|
|
405
401
|
print(f"❌ Environment {env_id} not found in either store")
|
406
402
|
return None
|
407
403
|
|
408
|
-
async def remove(self, env_id: str) ->
|
404
|
+
async def remove(self, env_id: str) -> StatefulEnvironment | None:
|
409
405
|
"""Remove and return an environment instance"""
|
410
406
|
# Get the environment first
|
411
407
|
env = await self.get(env_id)
|
@@ -432,7 +428,6 @@ storage = InstanceStorage()
|
|
432
428
|
|
433
429
|
def convert_numpy_types(obj):
|
434
430
|
"""Convert numpy types to native Python types for JSON serialization"""
|
435
|
-
import numpy as np
|
436
431
|
from dataclasses import is_dataclass
|
437
432
|
|
438
433
|
if isinstance(obj, dict):
|
@@ -484,15 +479,15 @@ def convert_numpy_types(obj):
|
|
484
479
|
|
485
480
|
# Request/Response models for better API documentation
|
486
481
|
class InitializeRequest(BaseModel):
|
487
|
-
initial_state:
|
488
|
-
config:
|
489
|
-
task_instance:
|
482
|
+
initial_state: dict[str, Any] | None = None
|
483
|
+
config: dict[str, Any] | None = None
|
484
|
+
task_instance: dict[str, Any] | None = None # Add task_instance field
|
490
485
|
|
491
486
|
|
492
487
|
class StepRequest(BaseModel):
|
493
488
|
env_id: str
|
494
|
-
request_id:
|
495
|
-
action:
|
489
|
+
request_id: str | None = None
|
490
|
+
action: dict[str, Any]
|
496
491
|
|
497
492
|
|
498
493
|
class TerminateRequest(BaseModel):
|
@@ -505,7 +500,7 @@ async def get_health():
|
|
505
500
|
|
506
501
|
|
507
502
|
@api_router.post("/env/{env_name}/initialize")
|
508
|
-
async def initialize_env(env_name: str, request: InitializeRequest = Body(...)) ->
|
503
|
+
async def initialize_env(env_name: str, request: InitializeRequest = Body(...)) -> dict[str, Any]:
|
509
504
|
"""Initialize a new environment instance."""
|
510
505
|
import traceback
|
511
506
|
|
@@ -517,11 +512,11 @@ async def initialize_env(env_name: str, request: InitializeRequest = Body(...))
|
|
517
512
|
|
518
513
|
# Handle task_instance parameter - use it if provided, otherwise create a new one
|
519
514
|
if request.task_instance:
|
520
|
-
print(
|
515
|
+
print("🔍 Using provided task_instance...")
|
521
516
|
task = await reconstruct_task_instance_from_serialized(env_name, request.task_instance)
|
522
517
|
print(f"✅ Reconstructed task instance: {type(task)}")
|
523
518
|
else:
|
524
|
-
print(
|
519
|
+
print("🔍 Creating new task instance...")
|
525
520
|
# Create environment-specific task instance
|
526
521
|
task = create_task_instance_for_environment(
|
527
522
|
env_name, request.initial_state, request.config
|
@@ -529,28 +524,28 @@ async def initialize_env(env_name: str, request: InitializeRequest = Body(...))
|
|
529
524
|
print(f"✅ Created task instance: {type(task)}")
|
530
525
|
|
531
526
|
# This is where recursion might happen for Sokoban
|
532
|
-
print(
|
527
|
+
print("🔍 Creating environment instance...")
|
533
528
|
env = cls(task)
|
534
|
-
print(
|
529
|
+
print("✅ Created environment instance")
|
535
530
|
|
536
531
|
# Generate unique environment ID
|
537
532
|
env_id = str(uuid4())
|
538
533
|
print(f"✅ Generated env_id: {env_id}")
|
539
534
|
|
540
535
|
# Initialize and get first observation - this might also cause recursion
|
541
|
-
print(
|
536
|
+
print("🔍 Calling env.initialize()...")
|
542
537
|
obs = await env.initialize()
|
543
538
|
print(f"✅ Environment initialized, observation type: {type(obs)}")
|
544
539
|
|
545
540
|
# Store the fully initialized environment (fixes Redis initialization bug)
|
546
|
-
print(
|
541
|
+
print("🔍 Storing environment...")
|
547
542
|
await storage.store(env_id, env)
|
548
|
-
print(
|
543
|
+
print("✅ Environment stored")
|
549
544
|
|
550
545
|
# Convert numpy types to Python types for JSON serialization
|
551
|
-
print(
|
546
|
+
print("🔍 Converting numpy types...")
|
552
547
|
obs_serializable = convert_numpy_types(obs)
|
553
|
-
print(
|
548
|
+
print("✅ Numpy types converted")
|
554
549
|
|
555
550
|
return {"env_id": env_id, "observation": obs_serializable, "done": False, "info": {}}
|
556
551
|
|
@@ -561,7 +556,7 @@ async def initialize_env(env_name: str, request: InitializeRequest = Body(...))
|
|
561
556
|
print(stack_trace)
|
562
557
|
raise HTTPException(
|
563
558
|
status_code=400, detail=f"Recursion error during {env_name} initialization: {str(e)}"
|
564
|
-
)
|
559
|
+
) from e
|
565
560
|
|
566
561
|
except Exception as e:
|
567
562
|
# Capture all other errors
|
@@ -570,14 +565,14 @@ async def initialize_env(env_name: str, request: InitializeRequest = Body(...))
|
|
570
565
|
print(stack_trace)
|
571
566
|
raise HTTPException(
|
572
567
|
status_code=400, detail=f"Error during {env_name} initialization: {str(e)}"
|
573
|
-
)
|
568
|
+
) from e
|
574
569
|
|
575
570
|
|
576
571
|
@api_router.post("/env/{env_name}/step")
|
577
|
-
async def step_env(env_name: str, request: StepRequest = Body(...)) ->
|
572
|
+
async def step_env(env_name: str, request: StepRequest = Body(...)) -> dict[str, Any]:
|
578
573
|
"""Execute a step in the environment."""
|
579
|
-
import uuid as uuid_module
|
580
574
|
import sys
|
575
|
+
import uuid as uuid_module
|
581
576
|
|
582
577
|
# Use provided request_id or generate one
|
583
578
|
request_id = request.request_id or str(uuid_module.uuid4())[:8]
|
@@ -700,11 +695,11 @@ async def step_env(env_name: str, request: StepRequest = Body(...)) -> Dict[str,
|
|
700
695
|
logger.error(
|
701
696
|
f"🌐 [{request_id}] STEP FAILED - env: {env_name}, time: {elapsed_time:.3f}s, error: {type(e).__name__} - {e}"
|
702
697
|
)
|
703
|
-
raise HTTPException(status_code=400, detail=str(e))
|
698
|
+
raise HTTPException(status_code=400, detail=str(e)) from e
|
704
699
|
|
705
700
|
|
706
701
|
@api_router.post("/env/{env_name}/terminate")
|
707
|
-
async def terminate_env(env_name: str, request: TerminateRequest = Body(...)) ->
|
702
|
+
async def terminate_env(env_name: str, request: TerminateRequest = Body(...)) -> dict[str, Any]:
|
708
703
|
"""Terminate an environment instance."""
|
709
704
|
logger.info(f"🚪 Terminating environment: {env_name}, env_id: {request.env_id}")
|
710
705
|
env = await storage.remove(request.env_id)
|
@@ -724,11 +719,53 @@ async def terminate_env(env_name: str, request: TerminateRequest = Body(...)) ->
|
|
724
719
|
"private": {"instance_id": request.env_id},
|
725
720
|
}
|
726
721
|
except Exception as e:
|
727
|
-
raise HTTPException(status_code=400, detail=str(e))
|
722
|
+
raise HTTPException(status_code=400, detail=str(e)) from e
|
723
|
+
|
724
|
+
|
725
|
+
@api_router.get("/env/{env_name}/frame")
|
726
|
+
async def get_env_frame(env_name: str, env_id: str) -> dict[str, Any]:
|
727
|
+
"""Return the current rendered frame of the environment as base64 PNG.
|
728
|
+
|
729
|
+
This provides a lightweight way for clients to capture before/after snapshots
|
730
|
+
around steps without modifying core step responses.
|
731
|
+
"""
|
732
|
+
env = await storage.get(env_id)
|
733
|
+
if not env:
|
734
|
+
raise HTTPException(status_code=404, detail=f"Environment instance {env_id} not found")
|
735
|
+
|
736
|
+
try:
|
737
|
+
# For CrafterClassic, underlying engine exposes env.render() -> RGB ndarray
|
738
|
+
if (
|
739
|
+
hasattr(env, "engine")
|
740
|
+
and hasattr(env.engine, "env")
|
741
|
+
and hasattr(env.engine.env, "render")
|
742
|
+
):
|
743
|
+
rgb = env.engine.env.render()
|
744
|
+
else:
|
745
|
+
raise RuntimeError("Environment does not support render()")
|
746
|
+
|
747
|
+
if rgb is None:
|
748
|
+
raise RuntimeError("render() returned None")
|
749
|
+
|
750
|
+
# Encode to PNG base64
|
751
|
+
try:
|
752
|
+
from PIL import Image # type: ignore
|
753
|
+
|
754
|
+
img = Image.fromarray(rgb.astype("uint8"), "RGB")
|
755
|
+
buf = BytesIO()
|
756
|
+
img.save(buf, format="PNG")
|
757
|
+
b64 = base64.b64encode(buf.getvalue()).decode("ascii")
|
758
|
+
except Exception as e:
|
759
|
+
raise RuntimeError(f"failed to encode frame: {e}") from e
|
760
|
+
|
761
|
+
return {"env_id": env_id, "image_base64": b64}
|
762
|
+
except Exception as e:
|
763
|
+
logger.error(f"Error rendering frame for {env_id}: {e}")
|
764
|
+
raise HTTPException(status_code=500, detail=str(e)) from e
|
728
765
|
|
729
766
|
|
730
767
|
@api_router.get("/env/{env_name}/metadata")
|
731
|
-
async def get_env_metadata(env_name: str, env_id: str) ->
|
768
|
+
async def get_env_metadata(env_name: str, env_id: str) -> dict[str, Any]:
|
732
769
|
"""Get metadata about an environment instance."""
|
733
770
|
env = await storage.get(env_id)
|
734
771
|
if not env:
|
@@ -769,16 +806,16 @@ async def get_env_metadata(env_name: str, env_id: str) -> Dict[str, Any]:
|
|
769
806
|
return metadata
|
770
807
|
except Exception as e:
|
771
808
|
logger.error(f"Error getting metadata for environment {env_id}: {e}")
|
772
|
-
raise HTTPException(status_code=500, detail=str(e))
|
809
|
+
raise HTTPException(status_code=500, detail=str(e)) from e
|
773
810
|
|
774
811
|
|
775
812
|
# Keep backward compatibility endpoints but mark as deprecated
|
776
813
|
@api_router.post("/{env_type}/create", deprecated=True)
|
777
814
|
async def create_env_legacy(
|
778
815
|
env_type: str,
|
779
|
-
config:
|
780
|
-
initial_state:
|
781
|
-
) ->
|
816
|
+
config: dict[str, Any] | None = None,
|
817
|
+
initial_state: dict[str, Any] | None = None,
|
818
|
+
) -> dict[str, str]:
|
782
819
|
"""[DEPRECATED] Use /env/{env_name}/initialize instead."""
|
783
820
|
cls = get_environment_cls(env_type)
|
784
821
|
task = create_task_instance_for_environment(env_type, initial_state, config)
|
@@ -793,8 +830,8 @@ async def create_env_legacy(
|
|
793
830
|
|
794
831
|
@api_router.post("/{env_type}/{instance_id}/reset", deprecated=True)
|
795
832
|
async def reset_env_legacy(
|
796
|
-
env_type: str, instance_id: str, seed:
|
797
|
-
) ->
|
833
|
+
env_type: str, instance_id: str, seed: int | None = None
|
834
|
+
) -> dict[str, Any]:
|
798
835
|
"""[DEPRECATED] Use /env/{env_name}/initialize instead."""
|
799
836
|
env = await storage.get(instance_id)
|
800
837
|
if not env:
|
@@ -805,7 +842,7 @@ async def reset_env_legacy(
|
|
805
842
|
|
806
843
|
|
807
844
|
@api_router.post("/{env_type}/{instance_id}/step", deprecated=True)
|
808
|
-
async def step_env_legacy(env_type: str, instance_id: str, calls:
|
845
|
+
async def step_env_legacy(env_type: str, instance_id: str, calls: list[Any]) -> dict[str, Any]:
|
809
846
|
"""[DEPRECATED] Use /env/{env_name}/step instead."""
|
810
847
|
env = await storage.get(instance_id)
|
811
848
|
if not env:
|
@@ -827,7 +864,7 @@ async def terminate_env_legacy(env_type: str, instance_id: str) -> Any:
|
|
827
864
|
|
828
865
|
|
829
866
|
@api_router.get("/{env_type}/{instance_id}/checkpoint")
|
830
|
-
async def checkpoint_env(env_type: str, instance_id: str) ->
|
867
|
+
async def checkpoint_env(env_type: str, instance_id: str) -> dict[str, Any]:
|
831
868
|
"""Get a checkpoint of the environment state."""
|
832
869
|
env = await storage.get(instance_id)
|
833
870
|
if not env:
|
@@ -839,11 +876,12 @@ async def checkpoint_env(env_type: str, instance_id: str) -> Dict[str, Any]:
|
|
839
876
|
|
840
877
|
# ===== Dynamic Environment Registration API =====
|
841
878
|
|
879
|
+
|
842
880
|
class RegisterEnvironmentRequest(BaseModel):
|
843
881
|
name: str
|
844
882
|
module_path: str
|
845
883
|
class_name: str
|
846
|
-
description:
|
884
|
+
description: str | None = None
|
847
885
|
|
848
886
|
|
849
887
|
class UnregisterEnvironmentRequest(BaseModel):
|
@@ -851,13 +889,13 @@ class UnregisterEnvironmentRequest(BaseModel):
|
|
851
889
|
|
852
890
|
|
853
891
|
@api_router.post("/registry/environments")
|
854
|
-
async def register_environment_api(request: RegisterEnvironmentRequest) ->
|
892
|
+
async def register_environment_api(request: RegisterEnvironmentRequest) -> dict[str, Any]:
|
855
893
|
"""
|
856
894
|
Dynamically register a new environment at runtime.
|
857
|
-
|
895
|
+
|
858
896
|
This endpoint allows third-party packages to register environments without
|
859
897
|
restarting the service. The environment class will be imported and validated.
|
860
|
-
|
898
|
+
|
861
899
|
Example:
|
862
900
|
POST /registry/environments
|
863
901
|
{
|
@@ -870,119 +908,113 @@ async def register_environment_api(request: RegisterEnvironmentRequest) -> Dict[
|
|
870
908
|
try:
|
871
909
|
# Import the module
|
872
910
|
import importlib
|
911
|
+
|
873
912
|
module = importlib.import_module(request.module_path)
|
874
|
-
|
913
|
+
|
875
914
|
# Get the class from the module
|
876
915
|
if not hasattr(module, request.class_name):
|
877
916
|
raise HTTPException(
|
878
917
|
status_code=400,
|
879
|
-
detail=f"Class '{request.class_name}' not found in module '{request.module_path}'"
|
918
|
+
detail=f"Class '{request.class_name}' not found in module '{request.module_path}'",
|
880
919
|
)
|
881
|
-
|
920
|
+
|
882
921
|
env_cls = getattr(module, request.class_name)
|
883
|
-
|
922
|
+
|
884
923
|
# Validate that it's a StatefulEnvironment subclass
|
885
924
|
from synth_ai.environments.stateful.core import StatefulEnvironment
|
925
|
+
|
886
926
|
if not issubclass(env_cls, StatefulEnvironment):
|
887
927
|
raise HTTPException(
|
888
928
|
status_code=400,
|
889
|
-
detail=f"Class '{request.class_name}' is not a subclass of StatefulEnvironment"
|
929
|
+
detail=f"Class '{request.class_name}' is not a subclass of StatefulEnvironment",
|
890
930
|
)
|
891
|
-
|
931
|
+
|
892
932
|
# Register the environment
|
893
933
|
from synth_ai.environments.environment.registry import register_environment
|
934
|
+
|
894
935
|
register_environment(request.name, env_cls)
|
895
|
-
|
936
|
+
|
896
937
|
logger.info(f"Dynamically registered environment: {request.name}")
|
897
|
-
|
938
|
+
|
898
939
|
return {
|
899
940
|
"success": True,
|
900
941
|
"message": f"Environment '{request.name}' registered successfully",
|
901
942
|
"name": request.name,
|
902
943
|
"module_path": request.module_path,
|
903
944
|
"class_name": request.class_name,
|
904
|
-
"description": request.description
|
945
|
+
"description": request.description,
|
905
946
|
}
|
906
|
-
|
947
|
+
|
907
948
|
except ImportError as e:
|
908
949
|
raise HTTPException(
|
909
|
-
status_code=400,
|
910
|
-
|
911
|
-
)
|
950
|
+
status_code=400, detail=f"Failed to import module '{request.module_path}': {str(e)}"
|
951
|
+
) from e
|
912
952
|
except Exception as e:
|
913
953
|
logger.error(f"Failed to register environment {request.name}: {e}")
|
914
|
-
raise HTTPException(
|
915
|
-
status_code=500,
|
916
|
-
detail=f"Failed to register environment: {str(e)}"
|
917
|
-
)
|
954
|
+
raise HTTPException(status_code=500, detail=f"Failed to register environment: {str(e)}") from e
|
918
955
|
|
919
956
|
|
920
957
|
@api_router.delete("/registry/environments/{env_name}")
|
921
|
-
async def unregister_environment_api(env_name: str) ->
|
958
|
+
async def unregister_environment_api(env_name: str) -> dict[str, Any]:
|
922
959
|
"""
|
923
960
|
Unregister an environment from the registry.
|
924
|
-
|
961
|
+
|
925
962
|
This removes the environment from the in-memory registry, making it
|
926
963
|
unavailable for new instances. Existing instances are not affected.
|
927
964
|
"""
|
928
965
|
try:
|
929
966
|
from synth_ai.environments.environment.registry import ENV_REGISTRY
|
930
|
-
|
967
|
+
|
931
968
|
if env_name not in ENV_REGISTRY:
|
932
969
|
raise HTTPException(
|
933
|
-
status_code=404,
|
934
|
-
detail=f"Environment '{env_name}' not found in registry"
|
970
|
+
status_code=404, detail=f"Environment '{env_name}' not found in registry"
|
935
971
|
)
|
936
|
-
|
972
|
+
|
937
973
|
# Remove from registry
|
938
974
|
removed_cls = ENV_REGISTRY.pop(env_name)
|
939
|
-
|
975
|
+
|
940
976
|
logger.info(f"Unregistered environment: {env_name}")
|
941
|
-
|
977
|
+
|
942
978
|
return {
|
943
979
|
"success": True,
|
944
980
|
"message": f"Environment '{env_name}' unregistered successfully",
|
945
981
|
"name": env_name,
|
946
|
-
"class_name": removed_cls.__name__
|
982
|
+
"class_name": removed_cls.__name__,
|
947
983
|
}
|
948
|
-
|
984
|
+
|
949
985
|
except Exception as e:
|
950
986
|
logger.error(f"Failed to unregister environment {env_name}: {e}")
|
951
|
-
raise HTTPException(
|
952
|
-
status_code=500,
|
953
|
-
detail=f"Failed to unregister environment: {str(e)}"
|
954
|
-
)
|
987
|
+
raise HTTPException(status_code=500, detail=f"Failed to unregister environment: {str(e)}") from e
|
955
988
|
|
956
989
|
|
957
990
|
@api_router.get("/registry/environments")
|
958
|
-
async def list_registered_environments() ->
|
991
|
+
async def list_registered_environments() -> dict[str, Any]:
|
959
992
|
"""
|
960
993
|
List all registered environments with their details.
|
961
|
-
|
994
|
+
|
962
995
|
Returns information about all available environments in the registry,
|
963
996
|
including both built-in and dynamically registered environments.
|
964
997
|
"""
|
965
998
|
try:
|
966
999
|
from synth_ai.environments.environment.registry import ENV_REGISTRY
|
967
|
-
|
1000
|
+
|
968
1001
|
environments = []
|
969
1002
|
for name, env_cls in ENV_REGISTRY.items():
|
970
1003
|
env_info = {
|
971
1004
|
"name": name,
|
972
1005
|
"class_name": env_cls.__name__,
|
973
1006
|
"module": env_cls.__module__,
|
974
|
-
"description": getattr(env_cls, "__doc__", "").split("\n")[0]
|
1007
|
+
"description": getattr(env_cls, "__doc__", "").split("\n")[0]
|
1008
|
+
if env_cls.__doc__
|
1009
|
+
else None,
|
975
1010
|
}
|
976
1011
|
environments.append(env_info)
|
977
|
-
|
1012
|
+
|
978
1013
|
return {
|
979
1014
|
"environments": sorted(environments, key=lambda x: x["name"]),
|
980
|
-
"total_count": len(environments)
|
1015
|
+
"total_count": len(environments),
|
981
1016
|
}
|
982
|
-
|
1017
|
+
|
983
1018
|
except Exception as e:
|
984
1019
|
logger.error(f"Failed to list environments: {e}")
|
985
|
-
raise HTTPException(
|
986
|
-
status_code=500,
|
987
|
-
detail=f"Failed to list environments: {str(e)}"
|
988
|
-
)
|
1020
|
+
raise HTTPException(status_code=500, detail=f"Failed to list environments: {str(e)}") from e
|
@@ -6,7 +6,6 @@ This module provides functionality to register environments from external packag
|
|
6
6
|
|
7
7
|
import importlib
|
8
8
|
import logging
|
9
|
-
from typing import List, Dict
|
10
9
|
|
11
10
|
logger = logging.getLogger(__name__)
|
12
11
|
|
@@ -14,7 +13,7 @@ logger = logging.getLogger(__name__)
|
|
14
13
|
class ExternalRegistryConfig:
|
15
14
|
"""Configuration for external environment registries."""
|
16
15
|
|
17
|
-
def __init__(self, external_environments:
|
16
|
+
def __init__(self, external_environments: list[dict[str, str]] = None):
|
18
17
|
self.external_environments = external_environments or []
|
19
18
|
|
20
19
|
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# This file re-exports the actual registry functions from synth_ai.environments.environment.registry
|
2
2
|
# to be used by the service layer, maintaining a clean separation if needed.
|
3
3
|
from synth_ai.environments.environment.registry import (
|
4
|
-
register_environment,
|
5
4
|
get_environment_cls,
|
6
5
|
list_supported_env_types,
|
6
|
+
register_environment,
|
7
7
|
)
|
8
8
|
|
9
9
|
__all__ = ["register_environment", "get_environment_cls", "list_supported_env_types"]
|
@@ -1,5 +1,4 @@
|
|
1
1
|
from abc import abstractmethod
|
2
|
-
from typing import List
|
3
2
|
|
4
3
|
from synth_ai.environments.environment.shared_engine import Engine, InternalObservation
|
5
4
|
from synth_ai.environments.environment.tools import EnvToolCall
|
@@ -108,7 +107,7 @@ class StatefulEnvironment(Engine):
|
|
108
107
|
pass
|
109
108
|
|
110
109
|
@abstractmethod
|
111
|
-
async def step(self, tool_calls:
|
110
|
+
async def step(self, tool_calls: list[EnvToolCall]) -> InternalObservation:
|
112
111
|
"""
|
113
112
|
Execute tool calls and return the resulting observation.
|
114
113
|
|
@@ -1,8 +1,10 @@
|
|
1
|
-
from typing import Optional, Dict, List, Callable, Set, Any
|
2
|
-
from synth_ai.environments.v0_observability.history import SynthGlobalTrajectory
|
3
|
-
from uuid import UUID
|
4
1
|
from abc import abstractmethod
|
2
|
+
from collections.abc import Callable
|
5
3
|
from dataclasses import dataclass, field
|
4
|
+
from typing import Any, Optional
|
5
|
+
from uuid import UUID
|
6
|
+
|
7
|
+
from synth_ai.environments.v0_observability.history import SynthGlobalTrajectory
|
6
8
|
|
7
9
|
|
8
10
|
@dataclass
|
@@ -11,7 +13,7 @@ class Task:
|
|
11
13
|
global_constraints: str
|
12
14
|
global_objectives: str
|
13
15
|
|
14
|
-
shared_env_params:
|
16
|
+
shared_env_params: dict | None
|
15
17
|
|
16
18
|
|
17
19
|
@dataclass
|
@@ -21,10 +23,10 @@ class TaskInstanceMetadata:
|
|
21
23
|
|
22
24
|
@dataclass
|
23
25
|
class Intent:
|
24
|
-
rubric:
|
25
|
-
gold_trajectories:
|
26
|
-
gold_state_diff:
|
27
|
-
deterministic_eval_functions:
|
26
|
+
rubric: dict[str, Any]
|
27
|
+
gold_trajectories: SynthGlobalTrajectory | None
|
28
|
+
gold_state_diff: dict
|
29
|
+
deterministic_eval_functions: list[Callable] = field(default_factory=list)
|
28
30
|
|
29
31
|
|
30
32
|
@dataclass
|
@@ -44,7 +46,7 @@ class TaskInstance:
|
|
44
46
|
initial_engine_snapshot: Optional["StatefulEngineSnapshot"]
|
45
47
|
|
46
48
|
@abstractmethod
|
47
|
-
async def serialize(self) ->
|
49
|
+
async def serialize(self) -> dict:
|
48
50
|
pass
|
49
51
|
|
50
52
|
@abstractmethod
|
@@ -65,8 +67,8 @@ class TaskInstanceMetadataFilter:
|
|
65
67
|
|
66
68
|
@dataclass
|
67
69
|
class SplitInfo:
|
68
|
-
val_instance_ids:
|
69
|
-
test_instance_ids:
|
70
|
+
val_instance_ids: set[str]
|
71
|
+
test_instance_ids: set[str]
|
70
72
|
_is_split_defined: bool
|
71
73
|
|
72
74
|
|
@@ -74,5 +76,5 @@ class SplitInfo:
|
|
74
76
|
class TaskInstanceSet:
|
75
77
|
name: str
|
76
78
|
description: str
|
77
|
-
instances:
|
79
|
+
instances: list[TaskInstance]
|
78
80
|
split_info: SplitInfo
|