synth-ai 0.1.9__py3-none-any.whl → 0.2.1.dev0__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 (266) hide show
  1. synth_ai/__init__.py +28 -2
  2. synth_ai/core/system.py +4 -0
  3. synth_ai/environments/__init__.py +35 -0
  4. synth_ai/environments/environment/__init__.py +1 -0
  5. synth_ai/environments/environment/artifacts/__init__.py +1 -0
  6. synth_ai/environments/environment/artifacts/base.py +50 -0
  7. synth_ai/environments/environment/core.py +22 -0
  8. synth_ai/environments/environment/db/__init__.py +1 -0
  9. synth_ai/environments/environment/db/sqlite.py +45 -0
  10. synth_ai/environments/environment/registry.py +24 -0
  11. synth_ai/environments/environment/resources/sqlite.py +46 -0
  12. synth_ai/environments/environment/results.py +1 -0
  13. synth_ai/environments/environment/rewards/__init__.py +1 -0
  14. synth_ai/environments/environment/rewards/core.py +28 -0
  15. synth_ai/environments/environment/shared_engine.py +26 -0
  16. synth_ai/environments/environment/tools/__init__.py +34 -0
  17. synth_ai/environments/examples/__init__.py +1 -0
  18. synth_ai/environments/examples/crafter_classic/__init__.py +8 -0
  19. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_comprehensive_evaluation.py +58 -0
  20. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_browser.py +152 -0
  21. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_framework.py +1194 -0
  22. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_quick_evaluation.py +51 -0
  23. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_react_agent.py +872 -0
  24. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +1412 -0
  25. synth_ai/environments/examples/crafter_classic/agent_demos/test_crafter_react_agent.py +1110 -0
  26. synth_ai/environments/examples/crafter_classic/config_logging.py +111 -0
  27. synth_ai/environments/examples/crafter_classic/engine.py +502 -0
  28. synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +63 -0
  29. synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +5 -0
  30. synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +74 -0
  31. synth_ai/environments/examples/crafter_classic/environment.py +255 -0
  32. synth_ai/environments/examples/crafter_classic/taskset.py +228 -0
  33. synth_ai/environments/examples/enron/agent_demos/test_synth_react.py +535 -0
  34. synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +156 -0
  35. synth_ai/environments/examples/enron/art_helpers/local_email_db.py +280 -0
  36. synth_ai/environments/examples/enron/art_helpers/types_enron.py +24 -0
  37. synth_ai/environments/examples/enron/engine.py +291 -0
  38. synth_ai/environments/examples/enron/environment.py +165 -0
  39. synth_ai/environments/examples/enron/taskset.py +112 -0
  40. synth_ai/environments/examples/enron/units/keyword_stats.py +111 -0
  41. synth_ai/environments/examples/enron/units/test_email_index.py +8 -0
  42. synth_ai/environments/examples/minigrid/__init__.py +48 -0
  43. synth_ai/environments/examples/minigrid/agent_demos/minigrid_evaluation_framework.py +1188 -0
  44. synth_ai/environments/examples/minigrid/agent_demos/minigrid_quick_evaluation.py +47 -0
  45. synth_ai/environments/examples/minigrid/agent_demos/minigrid_react_agent.py +562 -0
  46. synth_ai/environments/examples/minigrid/agent_demos/minigrid_trace_evaluation.py +220 -0
  47. synth_ai/environments/examples/minigrid/agent_demos/test_minigrid_react_agent.py +393 -0
  48. synth_ai/environments/examples/minigrid/engine.py +589 -0
  49. synth_ai/environments/examples/minigrid/environment.py +274 -0
  50. synth_ai/environments/examples/minigrid/environment_mapping.py +242 -0
  51. synth_ai/environments/examples/minigrid/puzzle_loader.py +416 -0
  52. synth_ai/environments/examples/minigrid/taskset.py +583 -0
  53. synth_ai/environments/examples/minigrid/units/test_action_behavior.py +226 -0
  54. synth_ai/environments/examples/minigrid/units/test_debug_messages.py +83 -0
  55. synth_ai/environments/examples/minigrid/units/test_exploration.py +120 -0
  56. synth_ai/environments/examples/minigrid/units/test_minigrid_engine.py +214 -0
  57. synth_ai/environments/examples/minigrid/units/test_minigrid_environment.py +238 -0
  58. synth_ai/environments/examples/minigrid/units/test_minigrid_environment_mapping.py +301 -0
  59. synth_ai/environments/examples/minigrid/units/test_minigrid_taskset.py +210 -0
  60. synth_ai/environments/examples/nethack/__init__.py +7 -0
  61. synth_ai/environments/examples/nethack/achievements.py +337 -0
  62. synth_ai/environments/examples/nethack/agent_demos/nethack_evaluation_framework.py +981 -0
  63. synth_ai/environments/examples/nethack/agent_demos/nethack_quick_evaluation.py +74 -0
  64. synth_ai/environments/examples/nethack/agent_demos/nethack_react_agent.py +832 -0
  65. synth_ai/environments/examples/nethack/agent_demos/test_nethack_react_agent.py +1112 -0
  66. synth_ai/environments/examples/nethack/engine.py +738 -0
  67. synth_ai/environments/examples/nethack/environment.py +255 -0
  68. synth_ai/environments/examples/nethack/helpers/__init__.py +42 -0
  69. synth_ai/environments/examples/nethack/helpers/action_mapping.py +301 -0
  70. synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +401 -0
  71. synth_ai/environments/examples/nethack/helpers/observation_utils.py +433 -0
  72. synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +201 -0
  73. synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +268 -0
  74. synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +308 -0
  75. synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +430 -0
  76. synth_ai/environments/examples/nethack/taskset.py +323 -0
  77. synth_ai/environments/examples/nethack/units/test_nethack_engine.py +277 -0
  78. synth_ai/environments/examples/nethack/units/test_nethack_environment.py +281 -0
  79. synth_ai/environments/examples/nethack/units/test_nethack_taskset.py +213 -0
  80. synth_ai/environments/examples/nethack/units/test_recording.py +307 -0
  81. synth_ai/environments/examples/red/__init__.py +7 -0
  82. synth_ai/environments/examples/red/agent_demos/__init__.py +1 -0
  83. synth_ai/environments/examples/red/agent_demos/test_synth_react.py +1471 -0
  84. synth_ai/environments/examples/red/config_logging.py +110 -0
  85. synth_ai/environments/examples/red/engine.py +693 -0
  86. synth_ai/environments/examples/red/engine_helpers/__init__.py +1 -0
  87. synth_ai/environments/examples/red/engine_helpers/memory_map.py +28 -0
  88. synth_ai/environments/examples/red/engine_helpers/reward_components.py +275 -0
  89. synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +142 -0
  90. synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +56 -0
  91. synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +283 -0
  92. synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +149 -0
  93. synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +137 -0
  94. synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +56 -0
  95. synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +330 -0
  96. synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +120 -0
  97. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +558 -0
  98. synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +312 -0
  99. synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +147 -0
  100. synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +246 -0
  101. synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +367 -0
  102. synth_ai/environments/examples/red/engine_helpers/state_extraction.py +139 -0
  103. synth_ai/environments/examples/red/environment.py +235 -0
  104. synth_ai/environments/examples/red/taskset.py +77 -0
  105. synth_ai/environments/examples/red/test_fixes.py +125 -0
  106. synth_ai/environments/examples/red/test_fixes_mock.py +148 -0
  107. synth_ai/environments/examples/red/units/__init__.py +1 -0
  108. synth_ai/environments/examples/red/units/test_basic_functionality.py +97 -0
  109. synth_ai/environments/examples/red/units/test_button_press_requirements.py +217 -0
  110. synth_ai/environments/examples/red/units/test_engine.py +192 -0
  111. synth_ai/environments/examples/red/units/test_environment.py +455 -0
  112. synth_ai/environments/examples/red/units/test_exploration_strategy.py +227 -0
  113. synth_ai/environments/examples/red/units/test_integration.py +217 -0
  114. synth_ai/environments/examples/red/units/test_memory_extraction.py +111 -0
  115. synth_ai/environments/examples/red/units/test_menu_bug_reproduction.py +1100 -0
  116. synth_ai/environments/examples/red/units/test_movement_debug.py +255 -0
  117. synth_ai/environments/examples/red/units/test_pokemon_mcts_debug.py +163 -0
  118. synth_ai/environments/examples/red/units/test_pokemon_mcts_verbose.py +117 -0
  119. synth_ai/environments/examples/red/units/test_red_basic.py +145 -0
  120. synth_ai/environments/examples/red/units/test_red_comprehensive.py +323 -0
  121. synth_ai/environments/examples/red/units/test_retry_movement.py +195 -0
  122. synth_ai/environments/examples/red/units/test_reward_components.py +186 -0
  123. synth_ai/environments/examples/red/units/test_rom_integration.py +260 -0
  124. synth_ai/environments/examples/red/units/test_taskset.py +116 -0
  125. synth_ai/environments/examples/red/units/test_tree.py +448 -0
  126. synth_ai/environments/examples/sokoban/__init__.py +1 -0
  127. synth_ai/environments/examples/sokoban/agent_demos/sokoban_full_eval.py +900 -0
  128. synth_ai/environments/examples/sokoban/agent_demos/test_dspy_react.py +1 -0
  129. synth_ai/environments/examples/sokoban/agent_demos/test_sokoban_react_agent.py +498 -0
  130. synth_ai/environments/examples/sokoban/agent_demos/test_synth_lats.py +1 -0
  131. synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_locally.py +748 -0
  132. synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_service.py +296 -0
  133. synth_ai/environments/examples/sokoban/engine.py +675 -0
  134. synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +1 -0
  135. synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +656 -0
  136. synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +17 -0
  137. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +3 -0
  138. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +129 -0
  139. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +370 -0
  140. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +331 -0
  141. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +305 -0
  142. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +66 -0
  143. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +114 -0
  144. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +122 -0
  145. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +394 -0
  146. synth_ai/environments/examples/sokoban/environment.py +228 -0
  147. synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +438 -0
  148. synth_ai/environments/examples/sokoban/puzzle_loader.py +311 -0
  149. synth_ai/environments/examples/sokoban/taskset.py +425 -0
  150. synth_ai/environments/examples/sokoban/units/astar_common.py +94 -0
  151. synth_ai/environments/examples/sokoban/units/test_building_task_set.py +49 -0
  152. synth_ai/environments/examples/sokoban/units/test_false_positive.py +120 -0
  153. synth_ai/environments/examples/sokoban/units/test_simple_run_through_environment.py +119 -0
  154. synth_ai/environments/examples/sokoban/units/test_sokoban_environment.py +98 -0
  155. synth_ai/environments/examples/sokoban/units/test_tree.py +364 -0
  156. synth_ai/environments/examples/tictactoe/__init__.py +1 -0
  157. synth_ai/environments/examples/tictactoe/agent_demos/test_synth_react.py +266 -0
  158. synth_ai/environments/examples/tictactoe/agent_demos/test_tictactoe_react_agent.py +470 -0
  159. synth_ai/environments/examples/tictactoe/engine.py +368 -0
  160. synth_ai/environments/examples/tictactoe/environment.py +239 -0
  161. synth_ai/environments/examples/tictactoe/taskset.py +214 -0
  162. synth_ai/environments/examples/tictactoe/units/test_tictactoe_engine.py +393 -0
  163. synth_ai/environments/examples/tictactoe/units/test_tictactoe_environment.py +493 -0
  164. synth_ai/environments/examples/tictactoe/units/test_tictactoe_taskset.py +191 -0
  165. synth_ai/environments/examples/verilog/__init__.py +10 -0
  166. synth_ai/environments/examples/verilog/agent_demos/test_synth_react.py +520 -0
  167. synth_ai/environments/examples/verilog/engine.py +328 -0
  168. synth_ai/environments/examples/verilog/environment.py +349 -0
  169. synth_ai/environments/examples/verilog/taskset.py +418 -0
  170. synth_ai/environments/examples/verilog/units/test_verilog_engine.py +466 -0
  171. synth_ai/environments/examples/verilog/units/test_verilog_environment.py +585 -0
  172. synth_ai/environments/examples/verilog/units/test_verilog_integration.py +383 -0
  173. synth_ai/environments/examples/verilog/units/test_verilog_taskset.py +457 -0
  174. synth_ai/environments/reproducibility/core.py +42 -0
  175. synth_ai/environments/reproducibility/tree.py +364 -0
  176. synth_ai/environments/service/app.py +78 -0
  177. synth_ai/environments/service/core_routes.py +775 -0
  178. synth_ai/environments/service/external_registry.py +57 -0
  179. synth_ai/environments/service/registry.py +9 -0
  180. synth_ai/environments/stateful/__init__.py +1 -0
  181. synth_ai/environments/stateful/core.py +28 -0
  182. synth_ai/environments/stateful/engine.py +21 -0
  183. synth_ai/environments/stateful/state.py +7 -0
  184. synth_ai/environments/tasks/api.py +19 -0
  185. synth_ai/environments/tasks/core.py +78 -0
  186. synth_ai/environments/tasks/filters.py +39 -0
  187. synth_ai/environments/tasks/utils.py +89 -0
  188. synth_ai/environments/v0_observability/history.py +3 -0
  189. synth_ai/environments/v0_observability/log.py +2 -0
  190. synth_ai/lm/caching/constants.py +1 -0
  191. synth_ai/{zyk/lms → lm}/caching/ephemeral.py +4 -8
  192. synth_ai/{zyk/lms → lm}/caching/handler.py +15 -15
  193. synth_ai/{zyk/lms → lm}/caching/initialize.py +2 -4
  194. synth_ai/{zyk/lms → lm}/caching/persistent.py +4 -10
  195. synth_ai/{zyk/lms → lm}/config.py +2 -1
  196. synth_ai/{zyk/lms → lm}/constants.py +2 -2
  197. synth_ai/{zyk/lms → lm}/core/all.py +10 -10
  198. synth_ai/{zyk/lms → lm}/core/main.py +57 -33
  199. synth_ai/{zyk/lms → lm}/core/vendor_clients.py +12 -10
  200. synth_ai/lm/cost/monitor.py +1 -0
  201. synth_ai/lm/cost/statefulness.py +1 -0
  202. synth_ai/lm/provider_support/__init__.py +8 -0
  203. synth_ai/lm/provider_support/anthropic.py +945 -0
  204. synth_ai/lm/provider_support/openai.py +1115 -0
  205. synth_ai/lm/provider_support/suppress_logging.py +31 -0
  206. synth_ai/{zyk/lms → lm}/structured_outputs/handler.py +58 -80
  207. synth_ai/{zyk/lms → lm}/structured_outputs/inject.py +6 -20
  208. synth_ai/{zyk/lms → lm}/structured_outputs/rehabilitate.py +6 -12
  209. synth_ai/{zyk/lms → lm}/vendors/core/anthropic_api.py +21 -30
  210. synth_ai/{zyk/lms → lm}/vendors/core/gemini_api.py +37 -32
  211. synth_ai/{zyk/lms → lm}/vendors/core/mistral_api.py +19 -28
  212. synth_ai/{zyk/lms → lm}/vendors/core/openai_api.py +26 -36
  213. synth_ai/{zyk/lms → lm}/vendors/openai_standard.py +29 -33
  214. synth_ai/{zyk/lms → lm}/vendors/retries.py +1 -1
  215. synth_ai/lm/vendors/supported/__init__.py +0 -0
  216. synth_ai/{zyk/lms → lm}/vendors/supported/custom_endpoint.py +131 -118
  217. synth_ai/{zyk/lms → lm}/vendors/supported/deepseek.py +4 -8
  218. synth_ai/{zyk/lms → lm}/vendors/supported/grok.py +6 -8
  219. synth_ai/{zyk/lms → lm}/vendors/supported/groq.py +1 -1
  220. synth_ai/{zyk/lms → lm}/vendors/supported/ollama.py +2 -2
  221. synth_ai/{zyk/lms → lm}/vendors/supported/openrouter.py +18 -16
  222. synth_ai/{zyk/lms → lm}/vendors/supported/together.py +1 -1
  223. synth_ai/tracing/__init__.py +0 -0
  224. synth_ai/tracing/abstractions.py +224 -0
  225. synth_ai/tracing/base_client.py +91 -0
  226. synth_ai/tracing/client_manager.py +131 -0
  227. synth_ai/tracing/config.py +140 -0
  228. synth_ai/tracing/context.py +146 -0
  229. synth_ai/tracing/decorators.py +679 -0
  230. synth_ai/tracing/events/__init__.py +0 -0
  231. synth_ai/tracing/events/manage.py +147 -0
  232. synth_ai/tracing/events/scope.py +86 -0
  233. synth_ai/tracing/events/store.py +227 -0
  234. synth_ai/tracing/immediate_client.py +152 -0
  235. synth_ai/tracing/local.py +18 -0
  236. synth_ai/tracing/log_client_base.py +74 -0
  237. synth_ai/tracing/retry_queue.py +187 -0
  238. synth_ai/tracing/trackers.py +515 -0
  239. synth_ai/tracing/upload.py +504 -0
  240. synth_ai/tracing/utils.py +9 -0
  241. synth_ai/zyk/__init__.py +28 -2
  242. synth_ai-0.2.1.dev0.dist-info/METADATA +349 -0
  243. synth_ai-0.2.1.dev0.dist-info/RECORD +261 -0
  244. synth_ai/zyk/lms/caching/constants.py +0 -1
  245. synth_ai/zyk/lms/cost/monitor.py +0 -1
  246. synth_ai/zyk/lms/cost/statefulness.py +0 -1
  247. synth_ai-0.1.9.dist-info/METADATA +0 -37
  248. synth_ai-0.1.9.dist-info/RECORD +0 -50
  249. /synth_ai/{zyk/lms/__init__.py → environments/reproducibility/helpers.py} +0 -0
  250. /synth_ai/{zyk/lms/caching → lm}/__init__.py +0 -0
  251. /synth_ai/{zyk/lms/core → lm/caching}/__init__.py +0 -0
  252. /synth_ai/{zyk/lms → lm}/caching/dbs.py +0 -0
  253. /synth_ai/{zyk/lms/cost → lm/core}/__init__.py +0 -0
  254. /synth_ai/{zyk/lms → lm}/core/exceptions.py +0 -0
  255. /synth_ai/{zyk/lms/structured_outputs → lm/cost}/__init__.py +0 -0
  256. /synth_ai/{zyk/lms/vendors → lm/structured_outputs}/__init__.py +0 -0
  257. /synth_ai/{zyk/lms → lm}/tools/__init__.py +0 -0
  258. /synth_ai/{zyk/lms → lm}/tools/base.py +0 -0
  259. /synth_ai/{zyk/lms/vendors/core → lm/vendors}/__init__.py +0 -0
  260. /synth_ai/{zyk/lms → lm}/vendors/base.py +0 -0
  261. /synth_ai/{zyk/lms/vendors/local → lm/vendors/core}/__init__.py +0 -0
  262. /synth_ai/{zyk/lms/vendors/supported → lm/vendors/local}/__init__.py +0 -0
  263. /synth_ai/{zyk/lms → lm}/vendors/local/ollama.py +0 -0
  264. {synth_ai-0.1.9.dist-info → synth_ai-0.2.1.dev0.dist-info}/WHEEL +0 -0
  265. {synth_ai-0.1.9.dist-info → synth_ai-0.2.1.dev0.dist-info}/licenses/LICENSE +0 -0
  266. {synth_ai-0.1.9.dist-info → synth_ai-0.2.1.dev0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,74 @@
1
+ import importlib
2
+ import numpy as np
3
+ from typing import Any, Dict
4
+
5
+ # Minimal attributes to serialize per object type
6
+ BASIC_ATTRS: Dict[str, list] = {
7
+ "Player": [
8
+ "pos",
9
+ "facing",
10
+ "health",
11
+ "inventory",
12
+ "achievements",
13
+ "action",
14
+ "sleeping",
15
+ "_last_health",
16
+ "_hunger",
17
+ "_thirst",
18
+ "_fatigue",
19
+ "_recover",
20
+ ],
21
+ "Cow": ["pos", "health"],
22
+ "Zombie": ["pos", "health", "cooldown"],
23
+ "Skeleton": ["pos", "health", "reload"],
24
+ "Arrow": ["pos", "facing"],
25
+ "Plant": ["pos", "health", "grown"],
26
+ "Stone": ["pos"],
27
+ "Table": ["pos"],
28
+ "Furnace": ["pos"],
29
+ # Add other types as needed
30
+ }
31
+
32
+
33
+ def serialize_world_object(obj: Any) -> Dict[str, Any]:
34
+ """Convert a crafter object into a JSON-friendly dict."""
35
+ cls_name = obj.__class__.__name__
36
+ fields = BASIC_ATTRS.get(cls_name, ["pos"])
37
+ payload: Dict[str, Any] = {}
38
+ for field in fields:
39
+ val = getattr(obj, field)
40
+ if isinstance(val, np.ndarray):
41
+ payload[field] = val.tolist()
42
+ else:
43
+ payload[field] = val
44
+ return {
45
+ "type": f"{obj.__class__.__module__}.{cls_name}",
46
+ "state": payload,
47
+ }
48
+
49
+
50
+ def deserialize_world_object(blob: Dict[str, Any], world: Any) -> Any:
51
+ """Reconstruct a crafter object from its serialized dict."""
52
+ type_str = blob.get("type", "")
53
+ module_name, cls_name = type_str.rsplit(".", 1)
54
+ module = importlib.import_module(module_name)
55
+ cls = getattr(module, cls_name)
56
+ # Bypass __init__; create empty instance
57
+ state = blob.get("state", {})
58
+ obj = cls.__new__(cls)
59
+ # Initialize required base attributes
60
+ obj.world = world
61
+ obj.random = world.random
62
+ obj.removed = False
63
+ # Ensure inventory exists for health setter
64
+ obj.inventory = {"health": 0}
65
+ # Set attributes from state
66
+ for field, value in state.items():
67
+ if field == "pos":
68
+ # restore position as numpy array
69
+ obj.pos = np.array(value)
70
+ else:
71
+ # restore other attributes (including property setters)
72
+ # convert lists back to arrays only for known ndarray fields
73
+ setattr(obj, field, value)
74
+ return obj
@@ -0,0 +1,255 @@
1
+ """CrafterClassicEnvironment — thin wrapper exposing CrafterEngine via StatefulEnvironment API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import List, Optional, Any, Dict, Union
6
+ import dataclasses
7
+ import logging
8
+
9
+ # Import logging configuration to suppress JAX debug messages
10
+ from .config_logging import safe_compare
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ from synth_ai.environments.examples.crafter_classic.engine import (
15
+ CrafterEngine,
16
+ CrafterPrivateState,
17
+ CrafterPublicState,
18
+ CrafterEngineSnapshot,
19
+ )
20
+ from synth_ai.environments.examples.crafter_classic.taskset import CrafterTaskInstance
21
+ from synth_ai.environments.environment.shared_engine import (
22
+ GetObservationCallable,
23
+ InternalObservation,
24
+ )
25
+ from synth_ai.environments.reproducibility.core import ReproducibleEnvironment
26
+ from synth_ai.environments.stateful.core import StatefulEnvironment
27
+ from synth_ai.environments.environment.tools import (
28
+ AbstractTool,
29
+ EnvToolCall,
30
+ ToolResult,
31
+ TOOL_REGISTRY,
32
+ register_tool,
33
+ )
34
+ from pydantic import BaseModel, Field
35
+
36
+
37
+ # --- Tool Definition ---
38
+ class CrafterActionInput(BaseModel):
39
+ action: int = Field(..., description="Integer action for the Crafter environment.")
40
+
41
+
42
+ class CrafterInteractTool(AbstractTool):
43
+ name = "interact"
44
+ description = "Performs an action in the Crafter environment."
45
+ call_schema = CrafterActionInput
46
+ result_schema = ToolResult
47
+
48
+ def __init__(self, engine: CrafterEngine):
49
+ self.engine = engine
50
+
51
+ async def __call__(self, call: EnvToolCall) -> ToolResult:
52
+ try:
53
+ validated_args = self.call_schema(**call.args)
54
+ action_to_pass = self.engine._validate_action_engine(validated_args.action)
55
+ priv_state, pub_state = await self.engine._step_engine(action_to_pass)
56
+ return ToolResult(
57
+ ok=True,
58
+ payload={
59
+ "public_state": pub_state,
60
+ "private_state": priv_state,
61
+ },
62
+ )
63
+ except Exception as e:
64
+ pub_state_on_error = self.engine._get_public_state_from_env() # Use engine helper
65
+ # Get a safe private state for error cases
66
+ health_dead = safe_compare(0, self.engine.env._player.health, ">=")
67
+ step_exceeded = safe_compare(self.engine.env._length, self.engine.env._step, "<=")
68
+ priv_state_on_error = self.engine._get_private_state_from_env(
69
+ 0, health_dead, step_exceeded
70
+ )
71
+ return ToolResult(
72
+ ok=False,
73
+ error=str(e),
74
+ payload={
75
+ "public_state": pub_state_on_error,
76
+ "private_state": priv_state_on_error,
77
+ },
78
+ )
79
+
80
+
81
+ # Default observation callable (can be customized via __init__)
82
+ class SynthCrafterObservationCallable(GetObservationCallable):
83
+ async def get_observation(
84
+ self, pub: CrafterPublicState, priv: CrafterPrivateState
85
+ ) -> InternalObservation:
86
+ # Example: return a dictionary combining public and selected private info
87
+ # Actual observation structure depends on agent's needs.
88
+ obs_dict: Dict[str, Any] = dataclasses.asdict(pub) # type: ignore
89
+ obs_dict["reward_last_step"] = priv.reward_last_step
90
+ obs_dict["total_reward_episode"] = priv.total_reward_episode
91
+ obs_dict["terminated"] = priv.terminated
92
+ obs_dict["truncated"] = priv.truncated
93
+ if pub.error_info:
94
+ obs_dict["tool_error"] = pub.error_info
95
+ return obs_dict
96
+
97
+
98
+ class CrafterClassicEnvironment(StatefulEnvironment, ReproducibleEnvironment[CrafterEngine]):
99
+ """Environment wrapper bridging agent tool‑calls to `crafter.Env` dynamics."""
100
+
101
+ def __init__(
102
+ self,
103
+ task_instance: "CrafterTaskInstance",
104
+ custom_step_obs: Optional[GetObservationCallable] = None,
105
+ custom_ckpt_obs: Optional[GetObservationCallable] = None,
106
+ ) -> None:
107
+ self.name = "CrafterClassic"
108
+ self.task_instance = task_instance
109
+ self.custom_step_observation_callable = custom_step_obs or SynthCrafterObservationCallable()
110
+ self.custom_checkpoint_observation_callable = (
111
+ custom_ckpt_obs or SynthCrafterObservationCallable()
112
+ )
113
+ self.engine = CrafterEngine(task_instance)
114
+
115
+ self._interact_tool = CrafterInteractTool(self.engine)
116
+ if self._interact_tool.name not in TOOL_REGISTRY:
117
+ register_tool(self._interact_tool)
118
+
119
+ # ────────────────────────────────────────────────────────────────────
120
+ # Lifecycle helpers
121
+ # ────────────────────────────────────────────────────────────────────
122
+
123
+ async def initialize(self) -> InternalObservation: # type: ignore[override]
124
+ priv, pub = await self.engine._reset_engine()
125
+ return await self._to_observation(priv, pub, self.custom_step_observation_callable)
126
+
127
+ async def terminate(self) -> InternalObservation: # type: ignore[override]
128
+ pub = self.engine._get_public_state_from_env()
129
+ priv = self.engine._get_private_state_from_env(0, True, False) # Terminated state
130
+ priv.terminated = True
131
+ obs_dict = {"status": "Environment terminated."}
132
+ return await self._to_observation(
133
+ priv, pub, self.custom_step_observation_callable, extra_obs=obs_dict
134
+ )
135
+
136
+ # ────────────────────────────────────────────────────────────────────
137
+ # Step + checkpoint
138
+ # ────────────────────────────────────────────────────────────────────
139
+
140
+ def validate_tool_calls(
141
+ self, tool_calls: Union[EnvToolCall, List[EnvToolCall], List[List[EnvToolCall]]]
142
+ ) -> EnvToolCall:
143
+ # Normalize and validate to a single EnvToolCall (same as Sokoban)
144
+ if isinstance(tool_calls, list):
145
+ if not tool_calls:
146
+ raise ValueError("Received empty list of tool calls.")
147
+ if isinstance(tool_calls[0], list):
148
+ if not tool_calls[0]:
149
+ raise ValueError("Received empty inner list of tool calls.")
150
+ agent_call = tool_calls[0][0]
151
+ else:
152
+ agent_call = tool_calls[0]
153
+ elif isinstance(tool_calls, EnvToolCall):
154
+ agent_call = tool_calls
155
+ else:
156
+ raise TypeError(f"Unexpected type for tool_calls: {type(tool_calls)}")
157
+
158
+ if not isinstance(agent_call, EnvToolCall):
159
+ raise TypeError(f"Processed call is not EnvToolCall: {type(agent_call)}")
160
+ if agent_call.tool != "interact":
161
+ raise ValueError(f"Unknown tool: {agent_call.tool}. Expected 'interact'.")
162
+ return agent_call
163
+
164
+ async def step(
165
+ self, tool_calls: Union[EnvToolCall, List[EnvToolCall], List[List[EnvToolCall]]]
166
+ ) -> InternalObservation: # type: ignore[override]
167
+ agent_call = self.validate_tool_calls(tool_calls)
168
+ tool_result: ToolResult = await self._interact_tool(agent_call)
169
+
170
+ payload_dict = tool_result.payload
171
+ pub_state: CrafterPublicState
172
+ priv_state: CrafterPrivateState
173
+
174
+ if tool_result.ok:
175
+ # payload contains the actual state objects from the interact tool
176
+ priv_state = payload_dict.get("private_state")
177
+ pub_state = payload_dict.get("public_state")
178
+
179
+ # Validate we got the expected state objects
180
+ if not isinstance(priv_state, CrafterPrivateState) or not isinstance(
181
+ pub_state, CrafterPublicState
182
+ ):
183
+ logger.error(
184
+ f"Invalid state types in payload: priv={type(priv_state)}, pub={type(pub_state)}"
185
+ )
186
+ # Fall back to getting current state
187
+ pub_state = self.engine._get_public_state_from_env()
188
+ health_dead = safe_compare(0, self.engine.env._player.health, ">=")
189
+ step_exceeded = safe_compare(self.engine.env._length, self.engine.env._step, "<=")
190
+ priv_state = self.engine._get_private_state_from_env(0, health_dead, step_exceeded)
191
+ pub_state.error_info = "Invalid state types in tool result"
192
+ else:
193
+ # Tool call failed, use states from payload if available, otherwise get current state
194
+ priv_state = payload_dict.get("private_state")
195
+ pub_state = payload_dict.get("public_state")
196
+
197
+ if not isinstance(priv_state, CrafterPrivateState) or not isinstance(
198
+ pub_state, CrafterPublicState
199
+ ):
200
+ # Fall back to getting current state
201
+ pub_state = self.engine._get_public_state_from_env()
202
+ health_dead = safe_compare(0, self.engine.env._player.health, ">=")
203
+ step_exceeded = safe_compare(self.engine.env._length, self.engine.env._step, "<=")
204
+ priv_state = self.engine._get_private_state_from_env(0, health_dead, step_exceeded)
205
+
206
+ if tool_result.error:
207
+ pub_state.error_info = tool_result.error
208
+
209
+ return await self._to_observation(
210
+ priv_state, pub_state, self.custom_step_observation_callable
211
+ )
212
+
213
+ async def checkpoint(self) -> InternalObservation: # type: ignore[override]
214
+ engine_snapshot: CrafterEngineSnapshot = await self.engine._serialize_engine()
215
+ priv = self.engine._get_private_state_from_env(0, False, False) # Get current state for obs
216
+ pub = self.engine._get_public_state_from_env()
217
+ obs_data = await self._to_observation(
218
+ priv, pub, self.custom_checkpoint_observation_callable
219
+ )
220
+ if isinstance(obs_data, dict):
221
+ obs_data["engine_snapshot_data"] = engine_snapshot.model_dump()
222
+ return obs_data
223
+
224
+ # ────────────────────────────────────────────────────────────────────
225
+ # Helpers
226
+ # ────────────────────────────────────────────────────────────────────
227
+
228
+ async def _to_observation(
229
+ self,
230
+ priv: CrafterPrivateState,
231
+ pub: CrafterPublicState,
232
+ obs_cb: Optional[GetObservationCallable],
233
+ extra_obs: Optional[Dict[str, Any]] = None,
234
+ ) -> InternalObservation:
235
+ active_obs_cb = obs_cb or SynthCrafterObservationCallable()
236
+ observation = await active_obs_cb.get_observation(pub, priv)
237
+ if extra_obs and isinstance(observation, dict):
238
+ observation.update(extra_obs)
239
+ return observation
240
+
241
+ # ────────────────────────────────────────────────────────────────────
242
+ # ReproducibleEnvironment plumbing
243
+ # ────────────────────────────────────────────────────────────────────
244
+
245
+ async def _serialize_engine(self) -> CrafterEngineSnapshot:
246
+ return await self.engine._serialize_engine()
247
+
248
+ @classmethod
249
+ async def _deserialize_engine(
250
+ cls, snapshot: CrafterEngineSnapshot, task_instance: "CrafterTaskInstance"
251
+ ) -> "CrafterClassicEnvironment":
252
+ eng = await CrafterEngine._deserialize_engine(snapshot, task_instance)
253
+ env = cls(task_instance)
254
+ env.engine = eng
255
+ return env
@@ -0,0 +1,228 @@
1
+ """Procedural Crafter taskset generation with seed filtering by world traits.
2
+ Run this to build a TaskInstanceSet with reproducible initial snapshots.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import asyncio
8
+ import random
9
+ from dataclasses import dataclass, asdict, fields
10
+ from typing import Dict, List
11
+ from uuid import UUID, uuid4
12
+
13
+ import numpy as np
14
+ import crafter
15
+ from crafter import objects
16
+
17
+ from synth_ai.environments.tasks.core import (
18
+ Impetus,
19
+ Intent,
20
+ SplitInfo,
21
+ Task,
22
+ TaskInstance,
23
+ TaskInstanceMetadata,
24
+ TaskInstanceSet,
25
+ )
26
+
27
+ # ──────────────────────────────────────────────────────────────────────────────
28
+ # Config
29
+ # ──────────────────────────────────────────────────────────────────────────────
30
+ TASK = Task(
31
+ global_premises="Procedural Crafter seed generation",
32
+ global_constraints="",
33
+ global_objectives="Survive and unlock achievements.",
34
+ shared_env_params={},
35
+ )
36
+
37
+ AREA = (64, 64)
38
+ LEN = 10000
39
+ RADIUS = 10 # Manhattan distance for local trait count
40
+ SEED_START = 0
41
+ NUM_INSTANCES = 50
42
+
43
+ # Desired trait ranges per difficulty tier
44
+ TRAIT_BOUNDS = {
45
+ "easy": {
46
+ "min_trees": 4,
47
+ "max_hostiles": 0,
48
+ },
49
+ "medium": {
50
+ "min_trees": 2,
51
+ "max_hostiles": 2,
52
+ },
53
+ "hard": {
54
+ "min_trees": 0,
55
+ "max_hostiles": 5,
56
+ },
57
+ }
58
+
59
+ # ──────────────────────────────────────────────────────────────────────────────
60
+ # Metadata + instance helpers
61
+ # ──────────────────────────────────────────────────────────────────────────────
62
+
63
+
64
+ @dataclass
65
+ class CrafterTaskInstanceMetadata(TaskInstanceMetadata):
66
+ difficulty: str
67
+ seed: int
68
+ num_trees_radius: int
69
+ num_cows_radius: int
70
+ num_hostiles_radius: int
71
+
72
+
73
+ @dataclass
74
+ class CrafterTaskInstance(TaskInstance):
75
+ async def serialize(self) -> dict: # identical to Sokoban pattern
76
+ data = asdict(self)
77
+ if isinstance(data.get("id"), UUID):
78
+ data["id"] = str(data["id"])
79
+ if "intent" in data and data["intent"] is not None:
80
+ data["intent"]["deterministic_eval_functions"] = []
81
+ return data
82
+
83
+ @classmethod
84
+ async def deserialize(cls, data: dict) -> "CrafterTaskInstance":
85
+ if "id" in data:
86
+ try:
87
+ data["id"] = UUID(str(data["id"]))
88
+ except Exception:
89
+ pass
90
+ if "impetus" in data and isinstance(data["impetus"], dict):
91
+ impetus_data = data["impetus"]
92
+ # Ensure instructions field exists with default if missing
93
+ if "instructions" not in impetus_data:
94
+ impetus_data["instructions"] = "Survive and unlock achievements"
95
+ data["impetus"] = Impetus(**impetus_data)
96
+ if "intent" in data and isinstance(data["intent"], dict):
97
+ intent_data = data["intent"]
98
+ # Ensure required fields exist with defaults if missing
99
+ if "rubric" not in intent_data:
100
+ intent_data["rubric"] = {"goal": "Unlock achievements"}
101
+ if "gold_trajectories" not in intent_data:
102
+ intent_data["gold_trajectories"] = None
103
+ if "gold_state_diff" not in intent_data:
104
+ intent_data["gold_state_diff"] = {}
105
+ intent_data["deterministic_eval_functions"] = []
106
+ data["intent"] = Intent(**intent_data)
107
+ if "metadata" in data and isinstance(data["metadata"], dict):
108
+ metadata_data = data["metadata"]
109
+ # Ensure required fields exist with defaults if missing
110
+ if "difficulty" not in metadata_data:
111
+ metadata_data["difficulty"] = "medium"
112
+ if "seed" not in metadata_data:
113
+ metadata_data["seed"] = 0
114
+ if "num_trees_radius" not in metadata_data:
115
+ metadata_data["num_trees_radius"] = 0
116
+ if "num_cows_radius" not in metadata_data:
117
+ metadata_data["num_cows_radius"] = 0
118
+ if "num_hostiles_radius" not in metadata_data:
119
+ metadata_data["num_hostiles_radius"] = 0
120
+ data["metadata"] = CrafterTaskInstanceMetadata(**metadata_data)
121
+ keep = {f.name for f in fields(cls)}
122
+ return cls(**{k: v for k, v in data.items() if k in keep})
123
+
124
+
125
+ # ──────────────────────────────────────────────────────────────────────────────
126
+ # Trait extraction util
127
+ # ──────────────────────────────────────────────────────────────────────────────
128
+
129
+
130
+ def world_traits(env: crafter.Env, radius: int = RADIUS) -> Dict[str, int]:
131
+ player = env._player # type: ignore[attr-defined]
132
+ pos = np.array(player.pos)
133
+ counts = {"trees": 0, "cows": 0, "hostiles": 0}
134
+ for obj in env._world._objects: # type: ignore[attr-defined]
135
+ if obj is None or obj is player:
136
+ continue
137
+ if np.abs(obj.pos - pos).sum() > radius:
138
+ continue
139
+ if isinstance(obj, objects.Plant) and getattr(obj, "kind", "") == "tree":
140
+ counts["trees"] += 1
141
+ elif isinstance(obj, objects.Cow):
142
+ counts["cows"] += 1
143
+ elif isinstance(obj, (objects.Zombie, objects.Skeleton)):
144
+ counts["hostiles"] += 1
145
+ return counts
146
+
147
+
148
+ # ──────────────────────────────────────────────────────────────────────────────
149
+ # Main generator
150
+ # ──────────────────────────────────────────────────────────────────────────────
151
+
152
+
153
+ async def create_crafter_taskset(num_instances: int = NUM_INSTANCES) -> TaskInstanceSet:
154
+ instances: List[CrafterTaskInstance] = []
155
+ seed = SEED_START
156
+ while len(instances) < num_instances:
157
+ env = crafter.Env(area=AREA, length=LEN, seed=seed)
158
+ _ = env.reset()
159
+ traits = world_traits(env)
160
+ # assign difficulty tier first match
161
+ difficulty: str | None = None
162
+ for diff, bounds in TRAIT_BOUNDS.items():
163
+ if (
164
+ traits["trees"] >= bounds["min_trees"]
165
+ and traits["hostiles"] <= bounds["max_hostiles"]
166
+ ):
167
+ difficulty = diff
168
+ break
169
+ if difficulty is None:
170
+ seed += 1
171
+ continue
172
+ # build instance
173
+ impetus = Impetus(instructions=f"Survive and unlock achievements. Difficulty={difficulty}.")
174
+ intent = Intent(
175
+ rubric={"goal": "Unlock as many achievements as possible."},
176
+ gold_trajectories=None,
177
+ gold_state_diff={},
178
+ )
179
+ metadata = CrafterTaskInstanceMetadata(
180
+ difficulty=difficulty,
181
+ seed=seed,
182
+ num_trees_radius=traits["trees"],
183
+ num_cows_radius=traits["cows"],
184
+ num_hostiles_radius=traits["hostiles"],
185
+ )
186
+ instance = CrafterTaskInstance(
187
+ id=uuid4(),
188
+ impetus=impetus,
189
+ intent=intent,
190
+ metadata=metadata,
191
+ is_reproducible=True,
192
+ initial_engine_snapshot=None, # will be filled lazily when env starts
193
+ )
194
+ instances.append(instance)
195
+ seed += 1
196
+
197
+ # simple random split 80/10/10
198
+ random.shuffle(instances)
199
+ n = len(instances)
200
+ val_ids = {inst.id for inst in instances[int(0.8 * n) : int(0.9 * n)]}
201
+ test_ids = {inst.id for inst in instances[int(0.9 * n) :]}
202
+ split = SplitInfo(val_instance_ids=val_ids, test_instance_ids=test_ids, _is_split_defined=True)
203
+
204
+ return TaskInstanceSet(
205
+ name="Crafter Procedural TaskSet",
206
+ description="Crafter seeds filtered by local world traits around spawn.",
207
+ instances=instances,
208
+ split_info=split,
209
+ )
210
+
211
+
212
+ # ──────────────────────────────────────────────────────────────────────────────
213
+ # CLI example
214
+ # ──────────────────────────────────────────────────────────────────────────────
215
+
216
+ if __name__ == "__main__":
217
+ import json
218
+ import pathlib
219
+
220
+ async def _main():
221
+ ts = await create_crafter_taskset(30)
222
+ serial = await asyncio.gather(*(inst.serialize() for inst in ts.instances))
223
+ out = pathlib.Path("dataset/crafter_instances.json")
224
+ out.parent.mkdir(parents=True, exist_ok=True)
225
+ out.write_text(json.dumps(serial, indent=2))
226
+ print(f"Saved {len(serial)} instances → {out}")
227
+
228
+ asyncio.run(_main())