synth-ai 0.2.4.dev6__py3-none-any.whl → 0.2.4.dev7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. synth_ai/__init__.py +18 -9
  2. synth_ai/cli/__init__.py +10 -5
  3. synth_ai/cli/balance.py +22 -17
  4. synth_ai/cli/calc.py +2 -3
  5. synth_ai/cli/demo.py +3 -5
  6. synth_ai/cli/legacy_root_backup.py +58 -32
  7. synth_ai/cli/man.py +22 -19
  8. synth_ai/cli/recent.py +9 -8
  9. synth_ai/cli/root.py +58 -13
  10. synth_ai/cli/status.py +13 -6
  11. synth_ai/cli/traces.py +45 -21
  12. synth_ai/cli/watch.py +40 -37
  13. synth_ai/config/base_url.py +1 -3
  14. synth_ai/core/experiment.py +1 -2
  15. synth_ai/environments/__init__.py +2 -6
  16. synth_ai/environments/environment/artifacts/base.py +3 -1
  17. synth_ai/environments/environment/db/sqlite.py +1 -1
  18. synth_ai/environments/environment/registry.py +19 -20
  19. synth_ai/environments/environment/resources/sqlite.py +2 -3
  20. synth_ai/environments/environment/rewards/core.py +3 -2
  21. synth_ai/environments/environment/tools/__init__.py +6 -4
  22. synth_ai/environments/examples/crafter_classic/__init__.py +1 -1
  23. synth_ai/environments/examples/crafter_classic/engine.py +13 -13
  24. synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +1 -0
  25. synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +2 -1
  26. synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +2 -1
  27. synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +3 -2
  28. synth_ai/environments/examples/crafter_classic/environment.py +16 -15
  29. synth_ai/environments/examples/crafter_classic/taskset.py +2 -2
  30. synth_ai/environments/examples/crafter_classic/trace_hooks_v3.py +2 -3
  31. synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +2 -1
  32. synth_ai/environments/examples/crafter_custom/crafter/__init__.py +2 -2
  33. synth_ai/environments/examples/crafter_custom/crafter/config.py +2 -2
  34. synth_ai/environments/examples/crafter_custom/crafter/env.py +1 -5
  35. synth_ai/environments/examples/crafter_custom/crafter/objects.py +1 -2
  36. synth_ai/environments/examples/crafter_custom/crafter/worldgen.py +1 -2
  37. synth_ai/environments/examples/crafter_custom/dataset_builder.py +5 -5
  38. synth_ai/environments/examples/crafter_custom/environment.py +13 -13
  39. synth_ai/environments/examples/crafter_custom/run_dataset.py +5 -5
  40. synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +2 -2
  41. synth_ai/environments/examples/enron/art_helpers/local_email_db.py +5 -4
  42. synth_ai/environments/examples/enron/art_helpers/types_enron.py +2 -1
  43. synth_ai/environments/examples/enron/engine.py +18 -14
  44. synth_ai/environments/examples/enron/environment.py +12 -11
  45. synth_ai/environments/examples/enron/taskset.py +7 -7
  46. synth_ai/environments/examples/minigrid/__init__.py +6 -6
  47. synth_ai/environments/examples/minigrid/engine.py +6 -6
  48. synth_ai/environments/examples/minigrid/environment.py +6 -6
  49. synth_ai/environments/examples/minigrid/puzzle_loader.py +3 -2
  50. synth_ai/environments/examples/minigrid/taskset.py +13 -13
  51. synth_ai/environments/examples/nethack/achievements.py +1 -1
  52. synth_ai/environments/examples/nethack/engine.py +8 -7
  53. synth_ai/environments/examples/nethack/environment.py +10 -9
  54. synth_ai/environments/examples/nethack/helpers/__init__.py +8 -9
  55. synth_ai/environments/examples/nethack/helpers/action_mapping.py +1 -1
  56. synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +2 -1
  57. synth_ai/environments/examples/nethack/helpers/observation_utils.py +1 -1
  58. synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +3 -4
  59. synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +6 -5
  60. synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +5 -5
  61. synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +7 -6
  62. synth_ai/environments/examples/nethack/taskset.py +5 -5
  63. synth_ai/environments/examples/red/engine.py +9 -8
  64. synth_ai/environments/examples/red/engine_helpers/reward_components.py +2 -1
  65. synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +7 -7
  66. synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +2 -1
  67. synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +2 -1
  68. synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +2 -1
  69. synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +2 -1
  70. synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +2 -1
  71. synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +2 -1
  72. synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +2 -1
  73. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +2 -1
  74. synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +2 -1
  75. synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +2 -1
  76. synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +2 -1
  77. synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +3 -2
  78. synth_ai/environments/examples/red/engine_helpers/state_extraction.py +2 -1
  79. synth_ai/environments/examples/red/environment.py +18 -15
  80. synth_ai/environments/examples/red/taskset.py +5 -3
  81. synth_ai/environments/examples/sokoban/engine.py +16 -13
  82. synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +3 -2
  83. synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +2 -1
  84. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +1 -1
  85. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +7 -5
  86. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +1 -1
  87. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +2 -1
  88. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +5 -4
  89. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +3 -2
  90. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +2 -1
  91. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +5 -4
  92. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +1 -1
  93. synth_ai/environments/examples/sokoban/environment.py +15 -14
  94. synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +5 -3
  95. synth_ai/environments/examples/sokoban/puzzle_loader.py +3 -2
  96. synth_ai/environments/examples/sokoban/taskset.py +13 -10
  97. synth_ai/environments/examples/tictactoe/engine.py +6 -6
  98. synth_ai/environments/examples/tictactoe/environment.py +8 -7
  99. synth_ai/environments/examples/tictactoe/taskset.py +6 -5
  100. synth_ai/environments/examples/verilog/engine.py +4 -3
  101. synth_ai/environments/examples/verilog/environment.py +11 -10
  102. synth_ai/environments/examples/verilog/taskset.py +14 -12
  103. synth_ai/environments/examples/wordle/__init__.py +5 -5
  104. synth_ai/environments/examples/wordle/engine.py +32 -25
  105. synth_ai/environments/examples/wordle/environment.py +21 -16
  106. synth_ai/environments/examples/wordle/helpers/generate_instances_wordfreq.py +6 -6
  107. synth_ai/environments/examples/wordle/taskset.py +20 -12
  108. synth_ai/environments/reproducibility/core.py +1 -1
  109. synth_ai/environments/reproducibility/tree.py +21 -21
  110. synth_ai/environments/service/app.py +3 -2
  111. synth_ai/environments/service/core_routes.py +104 -110
  112. synth_ai/environments/service/external_registry.py +1 -2
  113. synth_ai/environments/service/registry.py +1 -1
  114. synth_ai/environments/stateful/core.py +1 -2
  115. synth_ai/environments/stateful/engine.py +1 -1
  116. synth_ai/environments/tasks/api.py +4 -4
  117. synth_ai/environments/tasks/core.py +14 -12
  118. synth_ai/environments/tasks/filters.py +6 -4
  119. synth_ai/environments/tasks/utils.py +13 -11
  120. synth_ai/evals/base.py +2 -3
  121. synth_ai/experimental/synth_oss.py +4 -4
  122. synth_ai/learning/gateway.py +1 -3
  123. synth_ai/learning/prompts/banking77_injection_eval.py +15 -10
  124. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +26 -14
  125. synth_ai/learning/prompts/mipro.py +61 -52
  126. synth_ai/learning/prompts/random_search.py +42 -43
  127. synth_ai/learning/prompts/run_mipro_banking77.py +32 -20
  128. synth_ai/learning/prompts/run_random_search_banking77.py +71 -52
  129. synth_ai/lm/__init__.py +5 -5
  130. synth_ai/lm/caching/ephemeral.py +9 -9
  131. synth_ai/lm/caching/handler.py +20 -20
  132. synth_ai/lm/caching/persistent.py +10 -10
  133. synth_ai/lm/config.py +3 -3
  134. synth_ai/lm/constants.py +7 -7
  135. synth_ai/lm/core/all.py +17 -3
  136. synth_ai/lm/core/exceptions.py +0 -2
  137. synth_ai/lm/core/main.py +26 -41
  138. synth_ai/lm/core/main_v3.py +20 -10
  139. synth_ai/lm/core/vendor_clients.py +18 -17
  140. synth_ai/lm/injection.py +7 -8
  141. synth_ai/lm/overrides.py +21 -19
  142. synth_ai/lm/provider_support/__init__.py +1 -1
  143. synth_ai/lm/provider_support/anthropic.py +15 -15
  144. synth_ai/lm/provider_support/openai.py +23 -21
  145. synth_ai/lm/structured_outputs/handler.py +34 -32
  146. synth_ai/lm/structured_outputs/inject.py +24 -27
  147. synth_ai/lm/structured_outputs/rehabilitate.py +19 -15
  148. synth_ai/lm/tools/base.py +17 -16
  149. synth_ai/lm/unified_interface.py +17 -18
  150. synth_ai/lm/vendors/base.py +20 -18
  151. synth_ai/lm/vendors/core/anthropic_api.py +36 -27
  152. synth_ai/lm/vendors/core/gemini_api.py +31 -36
  153. synth_ai/lm/vendors/core/mistral_api.py +19 -19
  154. synth_ai/lm/vendors/core/openai_api.py +11 -10
  155. synth_ai/lm/vendors/openai_standard.py +113 -87
  156. synth_ai/lm/vendors/openai_standard_responses.py +74 -61
  157. synth_ai/lm/vendors/retries.py +9 -1
  158. synth_ai/lm/vendors/supported/custom_endpoint.py +26 -26
  159. synth_ai/lm/vendors/supported/deepseek.py +10 -10
  160. synth_ai/lm/vendors/supported/grok.py +8 -8
  161. synth_ai/lm/vendors/supported/ollama.py +2 -1
  162. synth_ai/lm/vendors/supported/openrouter.py +11 -9
  163. synth_ai/lm/vendors/synth_client.py +69 -63
  164. synth_ai/lm/warmup.py +8 -7
  165. synth_ai/tracing/__init__.py +22 -10
  166. synth_ai/tracing_v1/__init__.py +22 -20
  167. synth_ai/tracing_v3/__init__.py +7 -7
  168. synth_ai/tracing_v3/abstractions.py +56 -52
  169. synth_ai/tracing_v3/config.py +4 -2
  170. synth_ai/tracing_v3/db_config.py +6 -8
  171. synth_ai/tracing_v3/decorators.py +29 -30
  172. synth_ai/tracing_v3/examples/basic_usage.py +12 -12
  173. synth_ai/tracing_v3/hooks.py +21 -21
  174. synth_ai/tracing_v3/llm_call_record_helpers.py +85 -98
  175. synth_ai/tracing_v3/lm_call_record_abstractions.py +2 -4
  176. synth_ai/tracing_v3/migration_helper.py +3 -5
  177. synth_ai/tracing_v3/replica_sync.py +30 -32
  178. synth_ai/tracing_v3/session_tracer.py +35 -29
  179. synth_ai/tracing_v3/storage/__init__.py +1 -1
  180. synth_ai/tracing_v3/storage/base.py +8 -7
  181. synth_ai/tracing_v3/storage/config.py +4 -4
  182. synth_ai/tracing_v3/storage/factory.py +4 -4
  183. synth_ai/tracing_v3/storage/utils.py +9 -9
  184. synth_ai/tracing_v3/turso/__init__.py +3 -3
  185. synth_ai/tracing_v3/turso/daemon.py +9 -9
  186. synth_ai/tracing_v3/turso/manager.py +60 -48
  187. synth_ai/tracing_v3/turso/models.py +24 -19
  188. synth_ai/tracing_v3/utils.py +5 -5
  189. synth_ai/tui/__main__.py +1 -1
  190. synth_ai/tui/cli/query_experiments.py +2 -3
  191. synth_ai/tui/cli/query_experiments_v3.py +2 -3
  192. synth_ai/tui/dashboard.py +97 -86
  193. synth_ai/v0/tracing/abstractions.py +28 -28
  194. synth_ai/v0/tracing/base_client.py +9 -9
  195. synth_ai/v0/tracing/client_manager.py +7 -7
  196. synth_ai/v0/tracing/config.py +7 -7
  197. synth_ai/v0/tracing/context.py +6 -6
  198. synth_ai/v0/tracing/decorators.py +6 -5
  199. synth_ai/v0/tracing/events/manage.py +1 -1
  200. synth_ai/v0/tracing/events/store.py +5 -4
  201. synth_ai/v0/tracing/immediate_client.py +4 -5
  202. synth_ai/v0/tracing/local.py +3 -3
  203. synth_ai/v0/tracing/log_client_base.py +4 -5
  204. synth_ai/v0/tracing/retry_queue.py +5 -6
  205. synth_ai/v0/tracing/trackers.py +25 -25
  206. synth_ai/v0/tracing/upload.py +6 -0
  207. synth_ai/v0/tracing_v1/__init__.py +1 -1
  208. synth_ai/v0/tracing_v1/abstractions.py +28 -28
  209. synth_ai/v0/tracing_v1/base_client.py +9 -9
  210. synth_ai/v0/tracing_v1/client_manager.py +7 -7
  211. synth_ai/v0/tracing_v1/config.py +7 -7
  212. synth_ai/v0/tracing_v1/context.py +6 -6
  213. synth_ai/v0/tracing_v1/decorators.py +7 -6
  214. synth_ai/v0/tracing_v1/events/manage.py +1 -1
  215. synth_ai/v0/tracing_v1/events/store.py +5 -4
  216. synth_ai/v0/tracing_v1/immediate_client.py +4 -5
  217. synth_ai/v0/tracing_v1/local.py +3 -3
  218. synth_ai/v0/tracing_v1/log_client_base.py +4 -5
  219. synth_ai/v0/tracing_v1/retry_queue.py +5 -6
  220. synth_ai/v0/tracing_v1/trackers.py +25 -25
  221. synth_ai/v0/tracing_v1/upload.py +25 -24
  222. synth_ai/zyk/__init__.py +1 -0
  223. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev7.dist-info}/METADATA +1 -11
  224. synth_ai-0.2.4.dev7.dist-info/RECORD +299 -0
  225. synth_ai-0.2.4.dev6.dist-info/RECORD +0 -299
  226. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev7.dist-info}/WHEEL +0 -0
  227. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev7.dist-info}/entry_points.txt +0 -0
  228. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev7.dist-info}/licenses/LICENSE +0 -0
  229. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev7.dist-info}/top_level.txt +0 -0
@@ -1,32 +1,26 @@
1
- from fastapi import APIRouter, HTTPException, Body
2
- from uuid import uuid4
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
- from io import BytesIO
11
- import numpy as np
12
5
  import tempfile
13
- from dataclasses import dataclass
14
6
  import time
15
- import logging
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
16
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
17
17
  from synth_ai.environments.service.registry import get_environment_cls, list_supported_env_types
18
18
  from synth_ai.environments.stateful.core import StatefulEnvironment
19
- from synth_ai.environments.environment.tools import EnvToolCall
20
19
 
21
20
  # Set up logging
22
21
  logger = logging.getLogger(__name__)
23
22
 
24
23
  # Import tracing abstractions from v3
25
- from synth_ai.tracing_v3.abstractions import (
26
- RuntimeEvent,
27
- SessionEventMarkovBlanketMessage,
28
- TimeRecord,
29
- )
30
24
 
31
25
  # Try to import Redis for persistent storage
32
26
  try:
@@ -53,7 +47,7 @@ if os.getenv("SYNTH_USE_INMEM", "1") == "1":
53
47
  api_router = APIRouter()
54
48
 
55
49
  # Fallback in-memory store if Redis is not available
56
- instances: Dict[str, StatefulEnvironment] = {}
50
+ instances: dict[str, StatefulEnvironment] = {}
57
51
 
58
52
 
59
53
  # Environment-specific task instance creation
@@ -68,9 +62,9 @@ class MinimalTaskInstanceMetadata:
68
62
  class MinimalIntent:
69
63
  """Minimal intent for environments that need it."""
70
64
 
71
- rubric: Dict[str, Any]
72
- gold_trajectories: Optional[Any] = None
73
- gold_state_diff: Dict = None
65
+ rubric: dict[str, Any]
66
+ gold_trajectories: Any | None = None
67
+ gold_state_diff: dict = None
74
68
  deterministic_eval_functions: list = None
75
69
 
76
70
  def __post_init__(self):
@@ -89,8 +83,8 @@ class MinimalImpetus:
89
83
 
90
84
  def create_task_instance_for_environment(
91
85
  env_name: str,
92
- initial_state: Optional[Dict[str, Any]] = None,
93
- config: Optional[Dict[str, Any]] = None,
86
+ initial_state: dict[str, Any] | None = None,
87
+ config: dict[str, Any] | None = None,
94
88
  ) -> Any:
95
89
  """Create appropriate task instance for different environments."""
96
90
 
@@ -173,7 +167,7 @@ def create_task_instance_for_environment(
173
167
 
174
168
 
175
169
  async def reconstruct_task_instance_from_serialized(
176
- env_name: str, serialized_data: Dict[str, Any]
170
+ env_name: str, serialized_data: dict[str, Any]
177
171
  ) -> Any:
178
172
  """Reconstruct a task instance from serialized data for specific environment types."""
179
173
 
@@ -219,9 +213,9 @@ async def reconstruct_task_instance_from_serialized(
219
213
 
220
214
  elif env_name == "Verilog":
221
215
  # Verilog needs special handling with snapshot_dir
216
+ import tempfile
222
217
  from types import SimpleNamespace
223
218
  from uuid import UUID
224
- import tempfile
225
219
 
226
220
  task = SimpleNamespace()
227
221
  task.id = UUID(serialized_data.get("id", str(uuid4())))
@@ -249,10 +243,11 @@ async def reconstruct_task_instance_from_serialized(
249
243
 
250
244
  elif env_name == "NetHack":
251
245
  # NetHack needs proper TaskInstance structure with NetHackTaskInstanceMetadata
252
- from synth_ai.environments.examples.nethack.taskset import NetHackTaskInstanceMetadata
253
246
  from types import SimpleNamespace
254
247
  from uuid import UUID
255
248
 
249
+ from synth_ai.environments.examples.nethack.taskset import NetHackTaskInstanceMetadata
250
+
256
251
  # Extract metadata from serialized data
257
252
  metadata_data = serialized_data.get("metadata", {})
258
253
  metadata = NetHackTaskInstanceMetadata(
@@ -382,7 +377,7 @@ class InstanceStorage:
382
377
  else:
383
378
  print(f"✅ Stored environment {env_id} in-memory (Redis not available)")
384
379
 
385
- async def get(self, env_id: str) -> Optional[StatefulEnvironment]:
380
+ async def get(self, env_id: str) -> StatefulEnvironment | None:
386
381
  """Retrieve an environment instance"""
387
382
  # Try in-memory first (most reliable)
388
383
  if env_id in instances:
@@ -406,7 +401,7 @@ class InstanceStorage:
406
401
  print(f"❌ Environment {env_id} not found in either store")
407
402
  return None
408
403
 
409
- async def remove(self, env_id: str) -> Optional[StatefulEnvironment]:
404
+ async def remove(self, env_id: str) -> StatefulEnvironment | None:
410
405
  """Remove and return an environment instance"""
411
406
  # Get the environment first
412
407
  env = await self.get(env_id)
@@ -433,7 +428,6 @@ storage = InstanceStorage()
433
428
 
434
429
  def convert_numpy_types(obj):
435
430
  """Convert numpy types to native Python types for JSON serialization"""
436
- import numpy as np
437
431
  from dataclasses import is_dataclass
438
432
 
439
433
  if isinstance(obj, dict):
@@ -485,15 +479,15 @@ def convert_numpy_types(obj):
485
479
 
486
480
  # Request/Response models for better API documentation
487
481
  class InitializeRequest(BaseModel):
488
- initial_state: Optional[Dict[str, Any]] = None
489
- config: Optional[Dict[str, Any]] = None
490
- task_instance: Optional[Dict[str, Any]] = None # Add task_instance field
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
491
485
 
492
486
 
493
487
  class StepRequest(BaseModel):
494
488
  env_id: str
495
- request_id: Optional[str] = None
496
- action: Dict[str, Any]
489
+ request_id: str | None = None
490
+ action: dict[str, Any]
497
491
 
498
492
 
499
493
  class TerminateRequest(BaseModel):
@@ -506,7 +500,7 @@ async def get_health():
506
500
 
507
501
 
508
502
  @api_router.post("/env/{env_name}/initialize")
509
- async def initialize_env(env_name: str, request: InitializeRequest = Body(...)) -> Dict[str, Any]:
503
+ async def initialize_env(env_name: str, request: InitializeRequest = Body(...)) -> dict[str, Any]:
510
504
  """Initialize a new environment instance."""
511
505
  import traceback
512
506
 
@@ -518,11 +512,11 @@ async def initialize_env(env_name: str, request: InitializeRequest = Body(...))
518
512
 
519
513
  # Handle task_instance parameter - use it if provided, otherwise create a new one
520
514
  if request.task_instance:
521
- print(f"🔍 Using provided task_instance...")
515
+ print("🔍 Using provided task_instance...")
522
516
  task = await reconstruct_task_instance_from_serialized(env_name, request.task_instance)
523
517
  print(f"✅ Reconstructed task instance: {type(task)}")
524
518
  else:
525
- print(f"🔍 Creating new task instance...")
519
+ print("🔍 Creating new task instance...")
526
520
  # Create environment-specific task instance
527
521
  task = create_task_instance_for_environment(
528
522
  env_name, request.initial_state, request.config
@@ -530,28 +524,28 @@ async def initialize_env(env_name: str, request: InitializeRequest = Body(...))
530
524
  print(f"✅ Created task instance: {type(task)}")
531
525
 
532
526
  # This is where recursion might happen for Sokoban
533
- print(f"🔍 Creating environment instance...")
527
+ print("🔍 Creating environment instance...")
534
528
  env = cls(task)
535
- print(f"✅ Created environment instance")
529
+ print("✅ Created environment instance")
536
530
 
537
531
  # Generate unique environment ID
538
532
  env_id = str(uuid4())
539
533
  print(f"✅ Generated env_id: {env_id}")
540
534
 
541
535
  # Initialize and get first observation - this might also cause recursion
542
- print(f"🔍 Calling env.initialize()...")
536
+ print("🔍 Calling env.initialize()...")
543
537
  obs = await env.initialize()
544
538
  print(f"✅ Environment initialized, observation type: {type(obs)}")
545
539
 
546
540
  # Store the fully initialized environment (fixes Redis initialization bug)
547
- print(f"🔍 Storing environment...")
541
+ print("🔍 Storing environment...")
548
542
  await storage.store(env_id, env)
549
- print(f"✅ Environment stored")
543
+ print("✅ Environment stored")
550
544
 
551
545
  # Convert numpy types to Python types for JSON serialization
552
- print(f"🔍 Converting numpy types...")
546
+ print("🔍 Converting numpy types...")
553
547
  obs_serializable = convert_numpy_types(obs)
554
- print(f"✅ Numpy types converted")
548
+ print("✅ Numpy types converted")
555
549
 
556
550
  return {"env_id": env_id, "observation": obs_serializable, "done": False, "info": {}}
557
551
 
@@ -562,7 +556,7 @@ async def initialize_env(env_name: str, request: InitializeRequest = Body(...))
562
556
  print(stack_trace)
563
557
  raise HTTPException(
564
558
  status_code=400, detail=f"Recursion error during {env_name} initialization: {str(e)}"
565
- )
559
+ ) from e
566
560
 
567
561
  except Exception as e:
568
562
  # Capture all other errors
@@ -571,14 +565,14 @@ async def initialize_env(env_name: str, request: InitializeRequest = Body(...))
571
565
  print(stack_trace)
572
566
  raise HTTPException(
573
567
  status_code=400, detail=f"Error during {env_name} initialization: {str(e)}"
574
- )
568
+ ) from e
575
569
 
576
570
 
577
571
  @api_router.post("/env/{env_name}/step")
578
- async def step_env(env_name: str, request: StepRequest = Body(...)) -> Dict[str, Any]:
572
+ async def step_env(env_name: str, request: StepRequest = Body(...)) -> dict[str, Any]:
579
573
  """Execute a step in the environment."""
580
- import uuid as uuid_module
581
574
  import sys
575
+ import uuid as uuid_module
582
576
 
583
577
  # Use provided request_id or generate one
584
578
  request_id = request.request_id or str(uuid_module.uuid4())[:8]
@@ -701,11 +695,11 @@ async def step_env(env_name: str, request: StepRequest = Body(...)) -> Dict[str,
701
695
  logger.error(
702
696
  f"🌐 [{request_id}] STEP FAILED - env: {env_name}, time: {elapsed_time:.3f}s, error: {type(e).__name__} - {e}"
703
697
  )
704
- raise HTTPException(status_code=400, detail=str(e))
698
+ raise HTTPException(status_code=400, detail=str(e)) from e
705
699
 
706
700
 
707
701
  @api_router.post("/env/{env_name}/terminate")
708
- async def terminate_env(env_name: str, request: TerminateRequest = Body(...)) -> Dict[str, Any]:
702
+ async def terminate_env(env_name: str, request: TerminateRequest = Body(...)) -> dict[str, Any]:
709
703
  """Terminate an environment instance."""
710
704
  logger.info(f"🚪 Terminating environment: {env_name}, env_id: {request.env_id}")
711
705
  env = await storage.remove(request.env_id)
@@ -725,11 +719,11 @@ async def terminate_env(env_name: str, request: TerminateRequest = Body(...)) ->
725
719
  "private": {"instance_id": request.env_id},
726
720
  }
727
721
  except Exception as e:
728
- raise HTTPException(status_code=400, detail=str(e))
722
+ raise HTTPException(status_code=400, detail=str(e)) from e
729
723
 
730
724
 
731
725
  @api_router.get("/env/{env_name}/frame")
732
- async def get_env_frame(env_name: str, env_id: str) -> Dict[str, Any]:
726
+ async def get_env_frame(env_name: str, env_id: str) -> dict[str, Any]:
733
727
  """Return the current rendered frame of the environment as base64 PNG.
734
728
 
735
729
  This provides a lightweight way for clients to capture before/after snapshots
@@ -741,7 +735,11 @@ async def get_env_frame(env_name: str, env_id: str) -> Dict[str, Any]:
741
735
 
742
736
  try:
743
737
  # For CrafterClassic, underlying engine exposes env.render() -> RGB ndarray
744
- if hasattr(env, "engine") and hasattr(env.engine, "env") and hasattr(env.engine.env, "render"):
738
+ if (
739
+ hasattr(env, "engine")
740
+ and hasattr(env.engine, "env")
741
+ and hasattr(env.engine.env, "render")
742
+ ):
745
743
  rgb = env.engine.env.render()
746
744
  else:
747
745
  raise RuntimeError("Environment does not support render()")
@@ -752,21 +750,22 @@ async def get_env_frame(env_name: str, env_id: str) -> Dict[str, Any]:
752
750
  # Encode to PNG base64
753
751
  try:
754
752
  from PIL import Image # type: ignore
753
+
755
754
  img = Image.fromarray(rgb.astype("uint8"), "RGB")
756
755
  buf = BytesIO()
757
756
  img.save(buf, format="PNG")
758
757
  b64 = base64.b64encode(buf.getvalue()).decode("ascii")
759
758
  except Exception as e:
760
- raise RuntimeError(f"failed to encode frame: {e}")
759
+ raise RuntimeError(f"failed to encode frame: {e}") from e
761
760
 
762
761
  return {"env_id": env_id, "image_base64": b64}
763
762
  except Exception as e:
764
763
  logger.error(f"Error rendering frame for {env_id}: {e}")
765
- raise HTTPException(status_code=500, detail=str(e))
764
+ raise HTTPException(status_code=500, detail=str(e)) from e
766
765
 
767
766
 
768
767
  @api_router.get("/env/{env_name}/metadata")
769
- async def get_env_metadata(env_name: str, env_id: str) -> Dict[str, Any]:
768
+ async def get_env_metadata(env_name: str, env_id: str) -> dict[str, Any]:
770
769
  """Get metadata about an environment instance."""
771
770
  env = await storage.get(env_id)
772
771
  if not env:
@@ -807,16 +806,16 @@ async def get_env_metadata(env_name: str, env_id: str) -> Dict[str, Any]:
807
806
  return metadata
808
807
  except Exception as e:
809
808
  logger.error(f"Error getting metadata for environment {env_id}: {e}")
810
- raise HTTPException(status_code=500, detail=str(e))
809
+ raise HTTPException(status_code=500, detail=str(e)) from e
811
810
 
812
811
 
813
812
  # Keep backward compatibility endpoints but mark as deprecated
814
813
  @api_router.post("/{env_type}/create", deprecated=True)
815
814
  async def create_env_legacy(
816
815
  env_type: str,
817
- config: Optional[Dict[str, Any]] = None,
818
- initial_state: Optional[Dict[str, Any]] = None,
819
- ) -> Dict[str, str]:
816
+ config: dict[str, Any] | None = None,
817
+ initial_state: dict[str, Any] | None = None,
818
+ ) -> dict[str, str]:
820
819
  """[DEPRECATED] Use /env/{env_name}/initialize instead."""
821
820
  cls = get_environment_cls(env_type)
822
821
  task = create_task_instance_for_environment(env_type, initial_state, config)
@@ -831,8 +830,8 @@ async def create_env_legacy(
831
830
 
832
831
  @api_router.post("/{env_type}/{instance_id}/reset", deprecated=True)
833
832
  async def reset_env_legacy(
834
- env_type: str, instance_id: str, seed: Optional[int] = None
835
- ) -> Dict[str, Any]:
833
+ env_type: str, instance_id: str, seed: int | None = None
834
+ ) -> dict[str, Any]:
836
835
  """[DEPRECATED] Use /env/{env_name}/initialize instead."""
837
836
  env = await storage.get(instance_id)
838
837
  if not env:
@@ -843,7 +842,7 @@ async def reset_env_legacy(
843
842
 
844
843
 
845
844
  @api_router.post("/{env_type}/{instance_id}/step", deprecated=True)
846
- async def step_env_legacy(env_type: str, instance_id: str, calls: List[Any]) -> Dict[str, Any]:
845
+ async def step_env_legacy(env_type: str, instance_id: str, calls: list[Any]) -> dict[str, Any]:
847
846
  """[DEPRECATED] Use /env/{env_name}/step instead."""
848
847
  env = await storage.get(instance_id)
849
848
  if not env:
@@ -865,7 +864,7 @@ async def terminate_env_legacy(env_type: str, instance_id: str) -> Any:
865
864
 
866
865
 
867
866
  @api_router.get("/{env_type}/{instance_id}/checkpoint")
868
- async def checkpoint_env(env_type: str, instance_id: str) -> Dict[str, Any]:
867
+ async def checkpoint_env(env_type: str, instance_id: str) -> dict[str, Any]:
869
868
  """Get a checkpoint of the environment state."""
870
869
  env = await storage.get(instance_id)
871
870
  if not env:
@@ -877,11 +876,12 @@ async def checkpoint_env(env_type: str, instance_id: str) -> Dict[str, Any]:
877
876
 
878
877
  # ===== Dynamic Environment Registration API =====
879
878
 
879
+
880
880
  class RegisterEnvironmentRequest(BaseModel):
881
881
  name: str
882
882
  module_path: str
883
883
  class_name: str
884
- description: Optional[str] = None
884
+ description: str | None = None
885
885
 
886
886
 
887
887
  class UnregisterEnvironmentRequest(BaseModel):
@@ -889,13 +889,13 @@ class UnregisterEnvironmentRequest(BaseModel):
889
889
 
890
890
 
891
891
  @api_router.post("/registry/environments")
892
- async def register_environment_api(request: RegisterEnvironmentRequest) -> Dict[str, Any]:
892
+ async def register_environment_api(request: RegisterEnvironmentRequest) -> dict[str, Any]:
893
893
  """
894
894
  Dynamically register a new environment at runtime.
895
-
895
+
896
896
  This endpoint allows third-party packages to register environments without
897
897
  restarting the service. The environment class will be imported and validated.
898
-
898
+
899
899
  Example:
900
900
  POST /registry/environments
901
901
  {
@@ -908,119 +908,113 @@ async def register_environment_api(request: RegisterEnvironmentRequest) -> Dict[
908
908
  try:
909
909
  # Import the module
910
910
  import importlib
911
+
911
912
  module = importlib.import_module(request.module_path)
912
-
913
+
913
914
  # Get the class from the module
914
915
  if not hasattr(module, request.class_name):
915
916
  raise HTTPException(
916
917
  status_code=400,
917
- 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}'",
918
919
  )
919
-
920
+
920
921
  env_cls = getattr(module, request.class_name)
921
-
922
+
922
923
  # Validate that it's a StatefulEnvironment subclass
923
924
  from synth_ai.environments.stateful.core import StatefulEnvironment
925
+
924
926
  if not issubclass(env_cls, StatefulEnvironment):
925
927
  raise HTTPException(
926
928
  status_code=400,
927
- 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",
928
930
  )
929
-
931
+
930
932
  # Register the environment
931
933
  from synth_ai.environments.environment.registry import register_environment
934
+
932
935
  register_environment(request.name, env_cls)
933
-
936
+
934
937
  logger.info(f"Dynamically registered environment: {request.name}")
935
-
938
+
936
939
  return {
937
940
  "success": True,
938
941
  "message": f"Environment '{request.name}' registered successfully",
939
942
  "name": request.name,
940
943
  "module_path": request.module_path,
941
944
  "class_name": request.class_name,
942
- "description": request.description
945
+ "description": request.description,
943
946
  }
944
-
947
+
945
948
  except ImportError as e:
946
949
  raise HTTPException(
947
- status_code=400,
948
- detail=f"Failed to import module '{request.module_path}': {str(e)}"
949
- )
950
+ status_code=400, detail=f"Failed to import module '{request.module_path}': {str(e)}"
951
+ ) from e
950
952
  except Exception as e:
951
953
  logger.error(f"Failed to register environment {request.name}: {e}")
952
- raise HTTPException(
953
- status_code=500,
954
- detail=f"Failed to register environment: {str(e)}"
955
- )
954
+ raise HTTPException(status_code=500, detail=f"Failed to register environment: {str(e)}") from e
956
955
 
957
956
 
958
957
  @api_router.delete("/registry/environments/{env_name}")
959
- async def unregister_environment_api(env_name: str) -> Dict[str, Any]:
958
+ async def unregister_environment_api(env_name: str) -> dict[str, Any]:
960
959
  """
961
960
  Unregister an environment from the registry.
962
-
961
+
963
962
  This removes the environment from the in-memory registry, making it
964
963
  unavailable for new instances. Existing instances are not affected.
965
964
  """
966
965
  try:
967
966
  from synth_ai.environments.environment.registry import ENV_REGISTRY
968
-
967
+
969
968
  if env_name not in ENV_REGISTRY:
970
969
  raise HTTPException(
971
- status_code=404,
972
- detail=f"Environment '{env_name}' not found in registry"
970
+ status_code=404, detail=f"Environment '{env_name}' not found in registry"
973
971
  )
974
-
972
+
975
973
  # Remove from registry
976
974
  removed_cls = ENV_REGISTRY.pop(env_name)
977
-
975
+
978
976
  logger.info(f"Unregistered environment: {env_name}")
979
-
977
+
980
978
  return {
981
979
  "success": True,
982
980
  "message": f"Environment '{env_name}' unregistered successfully",
983
981
  "name": env_name,
984
- "class_name": removed_cls.__name__
982
+ "class_name": removed_cls.__name__,
985
983
  }
986
-
984
+
987
985
  except Exception as e:
988
986
  logger.error(f"Failed to unregister environment {env_name}: {e}")
989
- raise HTTPException(
990
- status_code=500,
991
- detail=f"Failed to unregister environment: {str(e)}"
992
- )
987
+ raise HTTPException(status_code=500, detail=f"Failed to unregister environment: {str(e)}") from e
993
988
 
994
989
 
995
990
  @api_router.get("/registry/environments")
996
- async def list_registered_environments() -> Dict[str, Any]:
991
+ async def list_registered_environments() -> dict[str, Any]:
997
992
  """
998
993
  List all registered environments with their details.
999
-
994
+
1000
995
  Returns information about all available environments in the registry,
1001
996
  including both built-in and dynamically registered environments.
1002
997
  """
1003
998
  try:
1004
999
  from synth_ai.environments.environment.registry import ENV_REGISTRY
1005
-
1000
+
1006
1001
  environments = []
1007
1002
  for name, env_cls in ENV_REGISTRY.items():
1008
1003
  env_info = {
1009
1004
  "name": name,
1010
1005
  "class_name": env_cls.__name__,
1011
1006
  "module": env_cls.__module__,
1012
- "description": getattr(env_cls, "__doc__", "").split("\n")[0] if env_cls.__doc__ else None
1007
+ "description": getattr(env_cls, "__doc__", "").split("\n")[0]
1008
+ if env_cls.__doc__
1009
+ else None,
1013
1010
  }
1014
1011
  environments.append(env_info)
1015
-
1012
+
1016
1013
  return {
1017
1014
  "environments": sorted(environments, key=lambda x: x["name"]),
1018
- "total_count": len(environments)
1015
+ "total_count": len(environments),
1019
1016
  }
1020
-
1017
+
1021
1018
  except Exception as e:
1022
1019
  logger.error(f"Failed to list environments: {e}")
1023
- raise HTTPException(
1024
- status_code=500,
1025
- detail=f"Failed to list environments: {str(e)}"
1026
- )
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: List[Dict[str, str]] = None):
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: List[EnvToolCall]) -> InternalObservation:
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,6 +1,6 @@
1
- from synth_ai.environments.environment.shared_engine import Engine
2
1
  from typing import TypeVar
3
2
 
3
+ from synth_ai.environments.environment.shared_engine import Engine
4
4
 
5
5
  SnapshotType = TypeVar("SnapshotType", bound="StatefulEngineSnapshot")
6
6
 
@@ -1,11 +1,11 @@
1
1
  from synth_ai.environments.tasks.core import (
2
+ Impetus,
3
+ Intent,
4
+ SplitInfo,
2
5
  Task,
3
6
  TaskInstance,
4
- TaskInstanceSet,
5
7
  TaskInstanceMetadata,
6
- SplitInfo,
7
- Impetus,
8
- Intent,
8
+ TaskInstanceSet,
9
9
  )
10
10
 
11
11
  __all__ = [
@@ -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: Optional[Dict]
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: Dict[str, Any]
25
- gold_trajectories: Optional[SynthGlobalTrajectory]
26
- gold_state_diff: Dict
27
- deterministic_eval_functions: List[Callable] = field(default_factory=list)
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) -> Dict:
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: Set[str]
69
- test_instance_ids: Set[str]
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: List[TaskInstance]
79
+ instances: list[TaskInstance]
78
80
  split_info: SplitInfo