synth-ai 0.2.9.dev7__py3-none-any.whl → 0.2.9.dev9__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.

Potentially problematic release.


This version of synth-ai might be problematic. Click here for more details.

Files changed (327) hide show
  1. examples/__init__.py +16 -0
  2. examples/crafter_debug_render.py +8 -11
  3. examples/qwen_coder/README.md +102 -0
  4. examples/qwen_coder/_shared.py +113 -0
  5. examples/qwen_coder/configs/coder_lora_30b.toml +61 -0
  6. examples/qwen_coder/configs/coder_lora_4b.toml +57 -0
  7. examples/qwen_coder/configs/coder_lora_small.toml +58 -0
  8. examples/qwen_coder/generate_dataset.py +98 -0
  9. examples/qwen_coder/infer_ft_smoke.py +64 -0
  10. examples/qwen_coder/infer_prod_proxy.py +73 -0
  11. examples/qwen_coder/infer_via_synth.py +87 -0
  12. examples/qwen_coder/scripts/infer_coder.sh +18 -0
  13. examples/qwen_coder/scripts/train_coder_30b.sh +21 -0
  14. examples/qwen_coder/sft_full_17b.py +103 -0
  15. examples/qwen_coder/sft_lora_30b.py +110 -0
  16. examples/qwen_coder/subset_jsonl.py +38 -0
  17. examples/qwen_coder/validate_jsonl.py +59 -0
  18. examples/rl/run_eval.py +36 -37
  19. examples/rl/run_rl_and_save.py +5 -5
  20. examples/rl/task_app/math_single_step.py +65 -43
  21. examples/rl/task_app/math_task_app.py +3 -3
  22. examples/sft/README.md +139 -0
  23. examples/sft/configs/crafter_fft_qwen0p6b.toml +44 -0
  24. examples/sft/configs/crafter_lora_qwen0p6b.toml +45 -0
  25. examples/sft/evaluate.py +117 -0
  26. examples/sft/export_dataset.py +117 -0
  27. examples/sft/generate_traces.py +162 -0
  28. examples/swe/__init__.py +12 -0
  29. examples/swe/task_app/README.md +105 -0
  30. examples/swe/task_app/__init__.py +2 -0
  31. examples/swe/task_app/grpo_swe_mini.py +571 -0
  32. examples/swe/task_app/grpo_swe_mini_task_app.py +136 -0
  33. examples/swe/task_app/hosted/README.md +173 -0
  34. examples/swe/task_app/hosted/__init__.py +5 -0
  35. examples/swe/task_app/hosted/branching.py +143 -0
  36. examples/swe/task_app/hosted/environment_routes.py +1289 -0
  37. examples/swe/task_app/hosted/envs/__init__.py +1 -0
  38. examples/swe/task_app/hosted/envs/crafter/__init__.py +6 -0
  39. examples/swe/task_app/hosted/envs/crafter/app.py +1 -0
  40. examples/swe/task_app/hosted/envs/crafter/environment.py +522 -0
  41. examples/swe/task_app/hosted/envs/crafter/policy.py +478 -0
  42. examples/swe/task_app/hosted/envs/crafter/react_agent.py +108 -0
  43. examples/swe/task_app/hosted/envs/crafter/shared.py +305 -0
  44. examples/swe/task_app/hosted/envs/crafter/tools.py +47 -0
  45. examples/swe/task_app/hosted/envs/mini_swe/__init__.py +8 -0
  46. examples/swe/task_app/hosted/envs/mini_swe/environment.py +1164 -0
  47. examples/swe/task_app/hosted/envs/mini_swe/policy.py +355 -0
  48. examples/swe/task_app/hosted/envs/mini_swe/shared.py +83 -0
  49. examples/swe/task_app/hosted/envs/mini_swe/tools.py +96 -0
  50. examples/swe/task_app/hosted/hosted_app.py +204 -0
  51. examples/swe/task_app/hosted/inference/__init__.py +5 -0
  52. examples/swe/task_app/hosted/inference/openai_client.py +618 -0
  53. examples/swe/task_app/hosted/main.py +100 -0
  54. examples/swe/task_app/hosted/policy_routes.py +1079 -0
  55. examples/swe/task_app/hosted/registry.py +195 -0
  56. examples/swe/task_app/hosted/rollout.py +1869 -0
  57. examples/swe/task_app/hosted/storage/__init__.py +5 -0
  58. examples/swe/task_app/hosted/storage/volume.py +211 -0
  59. examples/swe/task_app/hosted/test_agents.py +161 -0
  60. examples/swe/task_app/hosted/test_service.py +137 -0
  61. examples/swe/task_app/hosted/utils.py +62 -0
  62. examples/vlm/README.md +68 -0
  63. examples/vlm/configs/crafter_vlm_gpt4o.toml +44 -0
  64. examples/vlm/crafter_image_only_agent.py +207 -0
  65. examples/vlm/crafter_openai_vlm_agent.py +277 -0
  66. examples/vlm/filter_image_rows.py +63 -0
  67. examples/vlm/run_crafter_vlm_benchmark.py +316 -0
  68. examples/warming_up_to_rl/analyze_trace_db.py +5 -5
  69. examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +11 -1
  70. examples/warming_up_to_rl/export_trace_sft.py +78 -21
  71. examples/warming_up_to_rl/groq_test.py +4 -4
  72. examples/warming_up_to_rl/manage_secrets.py +13 -18
  73. examples/warming_up_to_rl/run_eval.py +42 -44
  74. examples/warming_up_to_rl/run_fft_and_save.py +11 -16
  75. examples/warming_up_to_rl/run_local_rollout.py +1 -3
  76. examples/warming_up_to_rl/run_local_rollout_modal.py +2 -4
  77. examples/warming_up_to_rl/run_local_rollout_parallel.py +1 -4
  78. examples/warming_up_to_rl/run_local_rollout_traced.py +3 -5
  79. examples/warming_up_to_rl/run_rl_and_save.py +5 -6
  80. examples/warming_up_to_rl/run_rollout_remote.py +8 -10
  81. examples/warming_up_to_rl/task_app/README.md +6 -2
  82. examples/warming_up_to_rl/task_app/grpo_crafter.py +234 -35
  83. examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +2 -3
  84. examples/warming_up_to_rl/task_app/synth_envs_hosted/__init__.py +1 -1
  85. examples/warming_up_to_rl/task_app/synth_envs_hosted/branching.py +9 -11
  86. examples/warming_up_to_rl/task_app/synth_envs_hosted/environment_routes.py +131 -114
  87. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/environment.py +101 -41
  88. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +73 -51
  89. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +14 -6
  90. examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/shared.py +16 -16
  91. examples/warming_up_to_rl/task_app/synth_envs_hosted/hosted_app.py +32 -34
  92. examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +94 -31
  93. examples/warming_up_to_rl/task_app/synth_envs_hosted/main.py +0 -2
  94. examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +303 -203
  95. examples/warming_up_to_rl/task_app/synth_envs_hosted/registry.py +21 -23
  96. examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +328 -225
  97. examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/volume.py +13 -13
  98. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_agents.py +1 -0
  99. examples/warming_up_to_rl/task_app/synth_envs_hosted/test_service.py +1 -0
  100. examples/warming_up_to_rl/task_app/synth_envs_hosted/utils.py +4 -3
  101. synth/__init__.py +14 -0
  102. synth_ai/__init__.py +26 -4
  103. synth_ai/api/models/supported.py +376 -0
  104. synth_ai/api/train/builders.py +128 -21
  105. synth_ai/api/train/cli.py +80 -64
  106. synth_ai/api/train/config_finder.py +7 -2
  107. synth_ai/api/train/env_resolver.py +1 -1
  108. synth_ai/api/train/pollers.py +2 -1
  109. synth_ai/api/train/supported_algos.py +139 -0
  110. synth_ai/api/train/task_app.py +1 -2
  111. synth_ai/api/train/utils.py +13 -44
  112. synth_ai/cli/__init__.py +8 -0
  113. synth_ai/cli/_modal_wrapper.py +28 -0
  114. synth_ai/cli/_typer_patch.py +49 -0
  115. synth_ai/cli/balance.py +1 -2
  116. synth_ai/cli/calc.py +1 -1
  117. synth_ai/cli/demo.py +2 -1
  118. synth_ai/cli/recent.py +2 -2
  119. synth_ai/cli/rl_demo.py +2 -1
  120. synth_ai/cli/root.py +11 -13
  121. synth_ai/cli/status.py +2 -2
  122. synth_ai/cli/task_apps.py +529 -179
  123. synth_ai/cli/traces.py +6 -4
  124. synth_ai/cli/watch.py +12 -18
  125. synth_ai/demo_registry.py +1 -1
  126. synth_ai/demos/core/cli.py +36 -43
  127. synth_ai/demos/demo_task_apps/__init__.py +3 -3
  128. synth_ai/demos/demo_task_apps/core.py +17 -25
  129. synth_ai/demos/demo_task_apps/crafter/grpo_crafter_task_app.py +3 -4
  130. synth_ai/demos/demo_task_apps/math/app.py +2 -1
  131. synth_ai/demos/demo_task_apps/math/deploy_modal.py +3 -4
  132. synth_ai/demos/demo_task_apps/math/modal_task_app.py +16 -18
  133. synth_ai/demos/demo_task_apps/math/task_app_entry.py +0 -1
  134. synth_ai/environments/examples/crafter_classic/environment.py +76 -1
  135. synth_ai/environments/reproducibility/tree.py +2 -5
  136. synth_ai/environments/service/app.py +11 -12
  137. synth_ai/environments/service/core_routes.py +4 -7
  138. synth_ai/environments/stateful/engine.py +1 -1
  139. synth_ai/environments/tasks/core.py +1 -0
  140. synth_ai/environments/tasks/filters.py +5 -6
  141. synth_ai/environments/tasks/utils.py +4 -5
  142. synth_ai/handshake.py +9 -9
  143. synth_ai/http.py +1 -1
  144. synth_ai/http_client.py +18 -10
  145. synth_ai/inference/client.py +15 -5
  146. synth_ai/jobs/client.py +78 -83
  147. synth_ai/learning/__init__.py +41 -6
  148. synth_ai/learning/algorithms.py +14 -0
  149. synth_ai/learning/client.py +91 -24
  150. synth_ai/learning/config.py +2 -38
  151. synth_ai/learning/ft_client.py +4 -59
  152. synth_ai/learning/health.py +5 -6
  153. synth_ai/learning/jobs.py +31 -47
  154. synth_ai/{rl → learning/rl}/__init__.py +14 -4
  155. synth_ai/learning/rl/client.py +267 -0
  156. synth_ai/learning/rl/config.py +31 -0
  157. synth_ai/{rl → learning/rl}/contracts.py +5 -8
  158. synth_ai/{rl → learning/rl}/env_keys.py +39 -15
  159. synth_ai/learning/rl/secrets.py +13 -0
  160. synth_ai/learning/rl_client.py +2 -281
  161. synth_ai/learning/sft/__init__.py +29 -0
  162. synth_ai/learning/sft/client.py +68 -0
  163. synth_ai/learning/sft/config.py +270 -0
  164. synth_ai/learning/sft/data.py +295 -0
  165. synth_ai/learning/sse.py +25 -24
  166. synth_ai/learning/validators.py +25 -28
  167. synth_ai/lm/__init__.py +21 -47
  168. synth_ai/main.py +6 -0
  169. synth_ai/task/__init__.py +25 -27
  170. synth_ai/task/apps/__init__.py +7 -8
  171. synth_ai/task/auth.py +8 -8
  172. synth_ai/task/client.py +14 -14
  173. synth_ai/task/contracts.py +36 -35
  174. synth_ai/task/datasets.py +6 -5
  175. synth_ai/task/errors.py +10 -10
  176. synth_ai/task/health.py +17 -9
  177. synth_ai/task/json.py +58 -23
  178. synth_ai/task/proxy.py +13 -9
  179. synth_ai/task/rubrics.py +16 -15
  180. synth_ai/task/server.py +12 -12
  181. synth_ai/task/tracing_utils.py +4 -4
  182. synth_ai/task/vendors.py +5 -6
  183. synth_ai/tracing_v3/__init__.py +2 -0
  184. synth_ai/tracing_v3/abstractions.py +21 -4
  185. synth_ai/tracing_v3/decorators.py +18 -16
  186. synth_ai/tracing_v3/hooks.py +5 -5
  187. synth_ai/tracing_v3/llm_call_record_helpers.py +6 -6
  188. synth_ai/tracing_v3/session_tracer.py +40 -14
  189. synth_ai/tracing_v3/storage/base.py +85 -0
  190. synth_ai/tracing_v3/storage/config.py +21 -8
  191. synth_ai/tracing_v3/storage/factory.py +10 -7
  192. synth_ai/tracing_v3/storage/utils.py +4 -2
  193. synth_ai/tracing_v3/turso/daemon.py +7 -2
  194. synth_ai/tracing_v3/turso/models.py +2 -2
  195. synth_ai/tracing_v3/turso/native_manager.py +1173 -0
  196. synth_ai/tracing_v3/utils.py +4 -4
  197. synth_ai/v0/api/__init__.py +8 -0
  198. synth_ai/v0/api/models/__init__.py +8 -0
  199. synth_ai/v0/api/models/supported.py +8 -0
  200. synth_ai/v0/config/__init__.py +15 -0
  201. synth_ai/v0/config/base_url.py +12 -0
  202. synth_ai/v0/lm/__init__.py +51 -0
  203. synth_ai/{lm → v0/lm}/caching/ephemeral.py +2 -2
  204. synth_ai/{lm → v0/lm}/caching/handler.py +4 -4
  205. synth_ai/{lm → v0/lm}/caching/initialize.py +1 -1
  206. synth_ai/{lm → v0/lm}/caching/persistent.py +1 -1
  207. synth_ai/{lm → v0/lm}/config.py +6 -1
  208. synth_ai/{lm → v0/lm}/core/all.py +9 -9
  209. synth_ai/{lm → v0/lm}/core/main.py +6 -6
  210. synth_ai/{lm → v0/lm}/core/main_v3.py +10 -10
  211. synth_ai/{lm → v0/lm}/core/synth_models.py +2 -14
  212. synth_ai/{lm → v0/lm}/core/vendor_clients.py +2 -2
  213. synth_ai/{lm → v0/lm}/overrides.py +2 -2
  214. synth_ai/{lm → v0/lm}/provider_support/anthropic.py +4 -4
  215. synth_ai/{lm → v0/lm}/provider_support/openai.py +5 -5
  216. synth_ai/{lm → v0/lm}/structured_outputs/handler.py +5 -5
  217. synth_ai/{lm → v0/lm}/structured_outputs/rehabilitate.py +1 -1
  218. synth_ai/{lm → v0/lm}/vendors/core/anthropic_api.py +9 -9
  219. synth_ai/{lm → v0/lm}/vendors/core/gemini_api.py +5 -5
  220. synth_ai/{lm → v0/lm}/vendors/core/mistral_api.py +5 -5
  221. synth_ai/{lm → v0/lm}/vendors/core/openai_api.py +10 -10
  222. synth_ai/{lm → v0/lm}/vendors/openai_standard.py +8 -8
  223. synth_ai/{lm → v0/lm}/vendors/openai_standard_responses.py +2 -2
  224. synth_ai/{lm → v0/lm}/vendors/supported/custom_endpoint.py +3 -3
  225. synth_ai/{lm → v0/lm}/vendors/supported/deepseek.py +2 -2
  226. synth_ai/{lm → v0/lm}/vendors/supported/grok.py +2 -2
  227. synth_ai/{lm → v0/lm}/vendors/supported/groq.py +1 -1
  228. synth_ai/{lm → v0/lm}/vendors/supported/ollama.py +1 -1
  229. synth_ai/{lm → v0/lm}/vendors/supported/openrouter.py +3 -3
  230. synth_ai/{lm → v0/lm}/vendors/supported/together.py +1 -1
  231. synth_ai/{lm → v0/lm}/vendors/synth_client.py +1 -1
  232. synth_ai/v0/tracing_v3/__init__.py +10 -0
  233. synth_ai/v0/tracing_v3/abstractions.py +3 -0
  234. synth_ai/v0/tracing_v3/decorators.py +3 -0
  235. synth_ai/v0/tracing_v3/llm_call_record_helpers.py +3 -0
  236. synth_ai/v0/tracing_v3/session_tracer.py +3 -0
  237. synth_ai-0.2.9.dev9.dist-info/METADATA +191 -0
  238. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.9.dev9.dist-info}/RECORD +268 -238
  239. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.9.dev9.dist-info}/top_level.txt +1 -0
  240. examples/common_old/backend.py +0 -20
  241. examples/evals_old/README.md +0 -98
  242. examples/evals_old/__init__.py +0 -6
  243. examples/evals_old/compare_models.py +0 -1038
  244. examples/evals_old/example_log.md +0 -145
  245. examples/evals_old/run_demo.sh +0 -126
  246. examples/evals_old/trace_analysis.py +0 -270
  247. examples/finetuning_old/_backup_synth_qwen/config.toml +0 -29
  248. examples/finetuning_old/_backup_synth_qwen/example_log.md +0 -324
  249. examples/finetuning_old/_backup_synth_qwen/filter_traces.py +0 -60
  250. examples/finetuning_old/_backup_synth_qwen/filter_traces_achievements.py +0 -243
  251. examples/finetuning_old/_backup_synth_qwen/purge_v3_traces.py +0 -109
  252. examples/finetuning_old/_backup_synth_qwen/react_agent_lm.py +0 -1924
  253. examples/finetuning_old/_backup_synth_qwen/readme.md +0 -49
  254. examples/finetuning_old/_backup_synth_qwen/run_crafter_qwen4b.py +0 -114
  255. examples/finetuning_old/_backup_synth_qwen/run_demo.sh +0 -195
  256. examples/finetuning_old/_backup_synth_qwen/sft_kickoff.py +0 -119
  257. examples/finetuning_old/synth_qwen_v1/README.md +0 -68
  258. examples/finetuning_old/synth_qwen_v1/filter_traces.py +0 -60
  259. examples/finetuning_old/synth_qwen_v1/filter_traces_achievements.py +0 -243
  260. examples/finetuning_old/synth_qwen_v1/finetune.py +0 -46
  261. examples/finetuning_old/synth_qwen_v1/hello_ft_model.py +0 -71
  262. examples/finetuning_old/synth_qwen_v1/infer.py +0 -36
  263. examples/finetuning_old/synth_qwen_v1/poll.py +0 -46
  264. examples/finetuning_old/synth_qwen_v1/prepare_data.py +0 -35
  265. examples/finetuning_old/synth_qwen_v1/purge_v3_traces.py +0 -109
  266. examples/finetuning_old/synth_qwen_v1/react_agent_lm.py +0 -1933
  267. examples/finetuning_old/synth_qwen_v1/run_crafter_sft_job.py +0 -210
  268. examples/finetuning_old/synth_qwen_v1/run_ft_job.py +0 -237
  269. examples/finetuning_old/synth_qwen_v1/upload_data.py +0 -34
  270. examples/finetuning_old/synth_qwen_v1/util.py +0 -152
  271. examples/rl_old/task_app.py +0 -1131
  272. examples/warming_up_to_rl/old/event_rewards.md +0 -234
  273. examples/warming_up_to_rl/old/notes.md +0 -73
  274. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_traces_sft_turso.py +0 -738
  275. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/filter_traces_sft_turso.py +0 -580
  276. synth_ai/experimental/synth_oss.py +0 -445
  277. synth_ai/learning/filtering.py +0 -0
  278. synth_ai/learning/offline/dpo.py +0 -0
  279. synth_ai/learning/offline/providers.py +0 -7
  280. synth_ai/learning/offline/sft.py +0 -0
  281. synth_ai/learning/offline/shared.py +0 -0
  282. synth_ai/learning/online/grpo.py +0 -0
  283. synth_ai/learning/online/irft.py +0 -0
  284. synth_ai/learning/prompts/banking77_injection_eval.py +0 -168
  285. synth_ai/learning/prompts/gepa.py +0 -0
  286. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +0 -211
  287. synth_ai/learning/prompts/mipro.py +0 -289
  288. synth_ai/learning/prompts/random_search.py +0 -249
  289. synth_ai/learning/prompts/run_mipro_banking77.py +0 -172
  290. synth_ai/learning/prompts/run_random_search_banking77.py +0 -329
  291. synth_ai/rl/secrets.py +0 -19
  292. synth_ai/scripts/verify_rewards.py +0 -100
  293. synth_ai/tracing/__init__.py +0 -30
  294. synth_ai/tracing_v1/__init__.py +0 -33
  295. synth_ai/tracing_v3/turso/__init__.py +0 -25
  296. synth_ai/tracing_v3/turso/manager.py +0 -838
  297. synth_ai/zyk/__init__.py +0 -30
  298. synth_ai-0.2.9.dev7.dist-info/METADATA +0 -131
  299. /synth_ai/{lm → v0/lm}/caching/__init__.py +0 -0
  300. /synth_ai/{lm → v0/lm}/caching/constants.py +0 -0
  301. /synth_ai/{lm → v0/lm}/caching/dbs.py +0 -0
  302. /synth_ai/{lm → v0/lm}/constants.py +0 -0
  303. /synth_ai/{lm → v0/lm}/core/__init__.py +0 -0
  304. /synth_ai/{lm → v0/lm}/core/exceptions.py +0 -0
  305. /synth_ai/{lm → v0/lm}/cost/__init__.py +0 -0
  306. /synth_ai/{lm → v0/lm}/cost/monitor.py +0 -0
  307. /synth_ai/{lm → v0/lm}/cost/statefulness.py +0 -0
  308. /synth_ai/{lm → v0/lm}/injection.py +0 -0
  309. /synth_ai/{lm → v0/lm}/provider_support/__init__.py +0 -0
  310. /synth_ai/{lm → v0/lm}/provider_support/suppress_logging.py +0 -0
  311. /synth_ai/{lm → v0/lm}/structured_outputs/__init__.py +0 -0
  312. /synth_ai/{lm → v0/lm}/structured_outputs/inject.py +0 -0
  313. /synth_ai/{lm → v0/lm}/tools/__init__.py +0 -0
  314. /synth_ai/{lm → v0/lm}/tools/base.py +0 -0
  315. /synth_ai/{lm → v0/lm}/unified_interface.py +0 -0
  316. /synth_ai/{lm → v0/lm}/vendors/__init__.py +0 -0
  317. /synth_ai/{lm → v0/lm}/vendors/base.py +0 -0
  318. /synth_ai/{lm → v0/lm}/vendors/core/__init__.py +0 -0
  319. /synth_ai/{lm → v0/lm}/vendors/core/synth_dev_api.py +0 -0
  320. /synth_ai/{lm → v0/lm}/vendors/local/__init__.py +0 -0
  321. /synth_ai/{lm → v0/lm}/vendors/local/ollama.py +0 -0
  322. /synth_ai/{lm → v0/lm}/vendors/retries.py +0 -0
  323. /synth_ai/{lm → v0/lm}/vendors/supported/__init__.py +0 -0
  324. /synth_ai/{lm → v0/lm}/warmup.py +0 -0
  325. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.9.dev9.dist-info}/WHEEL +0 -0
  326. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.9.dev9.dist-info}/entry_points.txt +0 -0
  327. {synth_ai-0.2.9.dev7.dist-info → synth_ai-0.2.9.dev9.dist-info}/licenses/LICENSE +0 -0
@@ -1,14 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
- import logging
4
- from typing import Any, Dict, List, Optional
3
+ import contextlib
5
4
  import json
5
+ import logging
6
+ from typing import Any
7
+ from uuid import uuid4
6
8
 
7
9
  from fastapi import APIRouter, HTTPException
8
10
  from pydantic import BaseModel
9
11
 
10
- from uuid import uuid4
11
-
12
12
  # Import the actual classes from synth-ai
13
13
  from synth_ai.environments.examples.crafter_classic.environment import (
14
14
  CrafterClassicEnvironment,
@@ -98,41 +98,41 @@ async def validate_environment_observation(observation: Any, context: str) -> No
98
98
 
99
99
  class EnvCreateRequest(BaseModel):
100
100
  env_name: str
101
- config: Dict[str, Any] = {}
102
- seed: Optional[int] = None
103
- parent_env_id: Optional[str] = None
101
+ config: dict[str, Any] = {}
102
+ seed: int | None = None
103
+ parent_env_id: str | None = None
104
104
  rl_run_id: str
105
105
 
106
106
 
107
107
  class EnvCreateResponse(BaseModel):
108
108
  env_id: str
109
- observation: Dict[str, Any]
110
- info: Optional[Dict[str, Any]] = None
109
+ observation: dict[str, Any]
110
+ info: dict[str, Any] | None = None
111
111
  step_idx: int
112
112
 
113
113
 
114
114
  class EnvResetRequest(BaseModel):
115
115
  env_id: str
116
- seed: Optional[int] = None
116
+ seed: int | None = None
117
117
 
118
118
 
119
119
  class EnvResetResponse(BaseModel):
120
- observation: Dict[str, Any]
121
- info: Optional[Dict[str, Any]] = None
120
+ observation: dict[str, Any]
121
+ info: dict[str, Any] | None = None
122
122
  step_idx: int
123
123
 
124
124
 
125
125
  class EnvStepRequest(BaseModel):
126
126
  env_id: str
127
- tool_calls: List[Dict[str, Any]]
127
+ tool_calls: list[dict[str, Any]]
128
128
 
129
129
 
130
130
  class EnvStepResponse(BaseModel):
131
- observation: Dict[str, Any]
131
+ observation: dict[str, Any]
132
132
  done: bool
133
- info: Optional[Dict[str, Any]] = None
134
- reward: Optional[float] = None
135
- truncated: Optional[bool] = None
133
+ info: dict[str, Any] | None = None
134
+ reward: float | None = None
135
+ truncated: bool | None = None
136
136
  step_idx: int
137
137
 
138
138
 
@@ -153,8 +153,8 @@ class EnvRestoreRequest(BaseModel):
153
153
 
154
154
  class EnvRestoreResponse(BaseModel):
155
155
  env_id: str
156
- observation: Dict[str, Any]
157
- info: Optional[Dict[str, Any]] = None
156
+ observation: dict[str, Any]
157
+ info: dict[str, Any] | None = None
158
158
  step_idx: int
159
159
 
160
160
 
@@ -213,7 +213,8 @@ async def create_environment(request: EnvCreateRequest) -> EnvCreateResponse:
213
213
  # Log a world signature for sanity: seed + starting public state hash
214
214
  try:
215
215
  pub_state = base_env.engine._get_public_state_from_env() # type: ignore[attr-defined]
216
- import hashlib, json as _json
216
+ import hashlib
217
+ import json as _json
217
218
 
218
219
  sig_src = {
219
220
  "player_position": list(pub_state.player_position),
@@ -268,23 +269,27 @@ async def create_environment(request: EnvCreateRequest) -> EnvCreateResponse:
268
269
  elif env_name_lower == "wordle":
269
270
  # Defer imports to avoid hard dependency when not used
270
271
  try:
272
+ from synth_ai.environments.examples.wordle.environment import (
273
+ WordleEnvironment,
274
+ )
271
275
  from synth_ai.environments.examples.wordle.taskset import (
272
276
  WordleTaskInstance,
273
277
  WordleTaskInstanceMetadata,
274
278
  )
275
- from synth_ai.environments.examples.wordle.environment import (
276
- WordleEnvironment,
277
- )
278
279
  except Exception as e:
279
- raise HTTPException(status_code=500, detail=f"Wordle modules unavailable: {e}")
280
+ raise HTTPException(
281
+ status_code=500, detail=f"Wordle modules unavailable: {e}"
282
+ ) from e
280
283
 
281
284
  # Lazy import of wrapper within branch
282
285
  try:
283
- from .envs.wordle.environment import (
284
- WordleEnvironmentWrapper as _WordleWrapper,
285
- )
286
+ from .envs.wordle.environment import WordleEnvironmentWrapper
286
287
  except Exception as e:
287
- raise HTTPException(status_code=500, detail=f"Wordle wrapper unavailable: {e}")
288
+ raise HTTPException(
289
+ status_code=500, detail=f"Wordle wrapper unavailable: {e}"
290
+ ) from e
291
+ else:
292
+ wordle_wrapper_cls = WordleEnvironmentWrapper
288
293
 
289
294
  cfg = request.config or {}
290
295
  word_length = int(cfg.get("word_length", 5))
@@ -312,7 +317,7 @@ async def create_environment(request: EnvCreateRequest) -> EnvCreateResponse:
312
317
  # Try to preserve the exact puzzle snapshot for reproducibility
313
318
  init_snap = getattr(instance, "initial_engine_snapshot", None)
314
319
 
315
- wrapper = _WordleWrapper(
320
+ wrapper = wordle_wrapper_cls(
316
321
  env=base_env,
317
322
  seed=request.seed,
318
323
  word_length=word_length,
@@ -356,23 +361,25 @@ async def create_environment(request: EnvCreateRequest) -> EnvCreateResponse:
356
361
 
357
362
  elif env_name_lower == "sokoban":
358
363
  try:
364
+ from synth_ai.environments.examples.sokoban.environment import (
365
+ SokobanEnvironment,
366
+ )
359
367
  from synth_ai.environments.examples.sokoban.taskset import (
360
368
  SokobanTaskInstance,
361
369
  SokobanTaskInstanceMetadata,
362
370
  )
363
- from synth_ai.environments.examples.sokoban.environment import (
364
- SokobanEnvironment,
365
- )
366
371
  except Exception as e:
367
- raise HTTPException(status_code=500, detail=f"Sokoban modules unavailable: {e}")
372
+ raise HTTPException(
373
+ status_code=500, detail=f"Sokoban modules unavailable: {e}"
374
+ ) from e
368
375
 
369
376
  # Lazy import of wrapper within branch
370
377
  try:
371
- from .envs.sokoban.environment import (
372
- SokobanEnvironmentWrapper as _SokobanWrapper,
373
- )
378
+ from .envs.sokoban.environment import SokobanEnvironmentWrapper
374
379
  except Exception as e:
375
- raise HTTPException(status_code=500, detail=f"Sokoban wrapper unavailable: {e}")
380
+ raise HTTPException(
381
+ status_code=500, detail=f"Sokoban wrapper unavailable: {e}"
382
+ ) from e
376
383
 
377
384
  cfg = request.config or {}
378
385
  difficulty = cfg.get("difficulty", "easy")
@@ -395,7 +402,7 @@ async def create_environment(request: EnvCreateRequest) -> EnvCreateResponse:
395
402
  )
396
403
  base_env = SokobanEnvironment(task_instance=instance)
397
404
 
398
- wrapper = _SokobanWrapper(env=base_env, seed=request.seed, config=cfg)
405
+ wrapper = SokobanEnvironmentWrapper(env=base_env, seed=request.seed, config=cfg)
399
406
  result = await wrapper.initialize()
400
407
 
401
408
  # Handle the observation structure consistently for Sokoban
@@ -431,13 +438,11 @@ async def create_environment(request: EnvCreateRequest) -> EnvCreateResponse:
431
438
  cfg = request.config or {}
432
439
  # Lazy import of wrapper within branch
433
440
  try:
434
- from .envs.math.environment import (
435
- MathEnvironmentWrapper as _MathWrapper,
436
- )
441
+ from .envs.math.environment import MathEnvironmentWrapper
437
442
  except Exception as e:
438
- raise HTTPException(status_code=500, detail=f"Math wrapper unavailable: {e}")
443
+ raise HTTPException(status_code=500, detail=f"Math wrapper unavailable: {e}") from e
439
444
 
440
- wrapper = _MathWrapper(
445
+ wrapper = MathEnvironmentWrapper(
441
446
  seed=request.seed,
442
447
  problem_id=cfg.get("problem_id"),
443
448
  problem_text=cfg.get("problem_text"),
@@ -477,7 +482,7 @@ async def create_environment(request: EnvCreateRequest) -> EnvCreateResponse:
477
482
 
478
483
  except Exception as e:
479
484
  logger.error(f"Failed to create environment: {e}")
480
- raise HTTPException(status_code=500, detail=str(e))
485
+ raise HTTPException(status_code=500, detail=str(e)) from e
481
486
 
482
487
 
483
488
  # --- Compatibility routes for existing eval scripts that expect CrafterClassic paths ---
@@ -572,27 +577,28 @@ async def reset_environment(request: EnvResetRequest) -> EnvResetResponse:
572
577
 
573
578
  elif True:
574
579
  # Try to dynamically import Wordle wrapper and check instance safely
575
- try:
576
- from .envs.wordle.environment import (
577
- WordleEnvironmentWrapper as _WordleWrapper,
578
- )
579
- except Exception:
580
- _WordleWrapper = None # type: ignore
580
+ wordle_wrapper_cls = None
581
+ with contextlib.suppress(Exception):
582
+ from .envs.wordle.environment import WordleEnvironmentWrapper
581
583
 
582
- if _WordleWrapper is not None and isinstance(wrapper, _WordleWrapper):
584
+ wordle_wrapper_cls = WordleEnvironmentWrapper # type: ignore[assignment]
585
+
586
+ if wordle_wrapper_cls is not None and isinstance(wrapper, wordle_wrapper_cls):
583
587
  # Rebuild Wordle env with the same configuration; if we have a preserved
584
588
  # initial_engine_snapshot, prefer constructing the instance directly.
585
589
  try:
590
+ from synth_ai.environments.examples.wordle.environment import (
591
+ WordleEnvironment,
592
+ )
586
593
  from synth_ai.environments.examples.wordle.taskset import (
587
- create_wordle_taskset,
588
594
  WordleTaskInstance,
589
595
  WordleTaskInstanceMetadata,
590
- )
591
- from synth_ai.environments.examples.wordle.environment import (
592
- WordleEnvironment,
596
+ create_wordle_taskset,
593
597
  )
594
598
  except Exception as e:
595
- raise HTTPException(status_code=500, detail=f"Wordle modules unavailable: {e}")
599
+ raise HTTPException(
600
+ status_code=500, detail=f"Wordle modules unavailable: {e}"
601
+ ) from e
596
602
 
597
603
  init_snap = getattr(wrapper, "initial_engine_snapshot", None)
598
604
  if init_snap is not None:
@@ -630,16 +636,18 @@ async def reset_environment(request: EnvResetRequest) -> EnvResetResponse:
630
636
  # Rebuild Wordle env with the same configuration; if we have a preserved
631
637
  # initial_engine_snapshot, prefer constructing the instance directly.
632
638
  try:
639
+ from synth_ai.environments.examples.wordle.environment import (
640
+ WordleEnvironment,
641
+ )
633
642
  from synth_ai.environments.examples.wordle.taskset import (
634
- create_wordle_taskset,
635
643
  WordleTaskInstance,
636
644
  WordleTaskInstanceMetadata,
637
- )
638
- from synth_ai.environments.examples.wordle.environment import (
639
- WordleEnvironment,
645
+ create_wordle_taskset,
640
646
  )
641
647
  except Exception as e:
642
- raise HTTPException(status_code=500, detail=f"Wordle modules unavailable: {e}")
648
+ raise HTTPException(
649
+ status_code=500, detail=f"Wordle modules unavailable: {e}"
650
+ ) from e
643
651
 
644
652
  init_snap = getattr(wrapper, "initial_engine_snapshot", None)
645
653
  if init_snap is not None:
@@ -675,25 +683,26 @@ async def reset_environment(request: EnvResetRequest) -> EnvResetResponse:
675
683
 
676
684
  elif True:
677
685
  # Try to dynamically import Sokoban wrapper and check instance safely
678
- try:
679
- from .envs.sokoban.environment import (
680
- SokobanEnvironmentWrapper as _SokobanWrapper,
681
- )
682
- except Exception:
683
- _SokobanWrapper = None # type: ignore
686
+ sokoban_wrapper_cls = None
687
+ with contextlib.suppress(Exception):
688
+ from .envs.sokoban.environment import SokobanEnvironmentWrapper
684
689
 
685
- if _SokobanWrapper is not None and isinstance(wrapper, _SokobanWrapper):
690
+ sokoban_wrapper_cls = SokobanEnvironmentWrapper # type: ignore[assignment]
691
+
692
+ if sokoban_wrapper_cls is not None and isinstance(wrapper, sokoban_wrapper_cls):
686
693
  # Rebuild Sokoban env using stored config snapshot
687
694
  try:
695
+ from synth_ai.environments.examples.sokoban.environment import (
696
+ SokobanEnvironment,
697
+ )
688
698
  from synth_ai.environments.examples.sokoban.taskset import (
689
699
  SokobanTaskInstance,
690
700
  SokobanTaskInstanceMetadata,
691
701
  )
692
- from synth_ai.environments.examples.sokoban.environment import (
693
- SokobanEnvironment,
694
- )
695
702
  except Exception as e:
696
- raise HTTPException(status_code=500, detail=f"Sokoban modules unavailable: {e}")
703
+ raise HTTPException(
704
+ status_code=500, detail=f"Sokoban modules unavailable: {e}"
705
+ ) from e
697
706
 
698
707
  cfg = dict(wrapper.config or {})
699
708
  metadata = SokobanTaskInstanceMetadata(
@@ -718,15 +727,17 @@ async def reset_environment(request: EnvResetRequest) -> EnvResetResponse:
718
727
  pass
719
728
  # Rebuild Sokoban env using stored config snapshot
720
729
  try:
730
+ from synth_ai.environments.examples.sokoban.environment import (
731
+ SokobanEnvironment,
732
+ )
721
733
  from synth_ai.environments.examples.sokoban.taskset import (
722
734
  SokobanTaskInstance,
723
735
  SokobanTaskInstanceMetadata,
724
736
  )
725
- from synth_ai.environments.examples.sokoban.environment import (
726
- SokobanEnvironment,
727
- )
728
737
  except Exception as e:
729
- raise HTTPException(status_code=500, detail=f"Sokoban modules unavailable: {e}")
738
+ raise HTTPException(
739
+ status_code=500, detail=f"Sokoban modules unavailable: {e}"
740
+ ) from e
730
741
 
731
742
  cfg = dict(wrapper.config or {})
732
743
  metadata = SokobanTaskInstanceMetadata(
@@ -753,7 +764,8 @@ async def reset_environment(request: EnvResetRequest) -> EnvResetResponse:
753
764
  try:
754
765
  base_env = handle.env.env # type: ignore[attr-defined]
755
766
  pub_state = base_env.engine._get_public_state_from_env() # type: ignore[attr-defined]
756
- import hashlib, json as _json
767
+ import hashlib
768
+ import json as _json
757
769
 
758
770
  sig_src = {
759
771
  "player_position": list(pub_state.player_position),
@@ -786,7 +798,7 @@ async def reset_environment(request: EnvResetRequest) -> EnvResetResponse:
786
798
 
787
799
  except Exception as e:
788
800
  logger.error(f"Failed to reset environment {request.env_id}: {e}")
789
- raise HTTPException(status_code=500, detail=str(e))
801
+ raise HTTPException(status_code=500, detail=str(e)) from e
790
802
 
791
803
 
792
804
  @router.post("/step", response_model=EnvStepResponse)
@@ -799,16 +811,15 @@ async def step_environment(request: EnvStepRequest) -> EnvStepResponse:
799
811
  try:
800
812
  # Execute the step, pre-normalizing invalid Wordle guesses to avoid hard failures
801
813
  wrapper = handle.env
802
- try:
803
- from .envs.wordle.environment import (
804
- WordleEnvironmentWrapper as _WordleWrapper,
805
- )
806
- except Exception:
807
- _WordleWrapper = None # type: ignore
814
+ wordle_wrapper_cls = None
815
+ with contextlib.suppress(Exception):
816
+ from .envs.wordle.environment import WordleEnvironmentWrapper
808
817
 
809
- if _WordleWrapper is not None and isinstance(wrapper, _WordleWrapper):
818
+ wordle_wrapper_cls = WordleEnvironmentWrapper # type: ignore[assignment]
819
+
820
+ if wordle_wrapper_cls is not None and isinstance(wrapper, wordle_wrapper_cls):
810
821
  expected_len = int(getattr(wrapper, "word_length", 5))
811
- normalized: List[Dict[str, Any]] = []
822
+ normalized: list[dict[str, Any]] = []
812
823
  for tc in request.tool_calls or []:
813
824
  tool = tc.get("tool") or tc.get("tool_name") or tc.get("name") or "interact"
814
825
  args = tc.get("arguments") or tc.get("args") or {}
@@ -880,14 +891,16 @@ async def step_environment(request: EnvStepRequest) -> EnvStepResponse:
880
891
  logger.error(f"Failed to step environment {request.env_id}: {e}")
881
892
  # Fallback for Wordle: convert invalid guesses into 'invalid_guess' tool calls and retry once
882
893
  try:
883
- from .envs.wordle.environment import (
884
- WordleEnvironmentWrapper as _WordleWrapper,
885
- )
894
+ wordle_wrapper_cls = None
895
+ with contextlib.suppress(Exception):
896
+ from .envs.wordle.environment import WordleEnvironmentWrapper
897
+
898
+ wordle_wrapper_cls = WordleEnvironmentWrapper # type: ignore[assignment]
886
899
 
887
900
  wrapper = handle.env
888
- if isinstance(wrapper, _WordleWrapper):
901
+ if wordle_wrapper_cls is not None and isinstance(wrapper, wordle_wrapper_cls):
889
902
  expected_len = int(getattr(wrapper, "word_length", 5))
890
- normalized: List[Dict[str, Any]] = []
903
+ normalized: list[dict[str, Any]] = []
891
904
  for tc in request.tool_calls or []:
892
905
  tool = tc.get("tool") or tc.get("tool_name") or tc.get("name") or "interact"
893
906
  args = tc.get("arguments") or tc.get("args") or {}
@@ -941,7 +954,7 @@ async def step_environment(request: EnvStepRequest) -> EnvStepResponse:
941
954
  # Ignore fallback errors; fall through to generic error
942
955
  pass
943
956
 
944
- raise HTTPException(status_code=500, detail=f"{type(e).__name__}: {e}")
957
+ raise HTTPException(status_code=500, detail=f"{type(e).__name__}: {e}") from e
945
958
 
946
959
 
947
960
  @router.post("/snapshot", response_model=EnvSnapshotResponse)
@@ -980,7 +993,7 @@ async def snapshot_environment(request: EnvSnapshotRequest) -> EnvSnapshotRespon
980
993
 
981
994
  except Exception as e:
982
995
  logger.error(f"Failed to snapshot environment {request.env_id}: {e}")
983
- raise HTTPException(status_code=500, detail=str(e))
996
+ raise HTTPException(status_code=500, detail=str(e)) from e
984
997
 
985
998
 
986
999
  @router.post("/restore", response_model=EnvRestoreResponse)
@@ -1060,16 +1073,18 @@ async def restore_environment(request: EnvRestoreRequest) -> EnvRestoreResponse:
1060
1073
  )
1061
1074
  elif name_lower == "wordle":
1062
1075
  try:
1076
+ from synth_ai.environments.examples.wordle.environment import (
1077
+ WordleEnvironment,
1078
+ )
1063
1079
  from synth_ai.environments.examples.wordle.taskset import (
1064
- create_wordle_taskset,
1065
1080
  WordleTaskInstance,
1066
1081
  WordleTaskInstanceMetadata,
1067
- )
1068
- from synth_ai.environments.examples.wordle.environment import (
1069
- WordleEnvironment,
1082
+ create_wordle_taskset,
1070
1083
  )
1071
1084
  except Exception as e:
1072
- raise HTTPException(status_code=500, detail=f"Wordle modules unavailable: {e}")
1085
+ raise HTTPException(
1086
+ status_code=500, detail=f"Wordle modules unavailable: {e}"
1087
+ ) from e
1073
1088
 
1074
1089
  cfg = state_dict.get("config", {}) or {}
1075
1090
  word_length = int(cfg.get("word_length", 5))
@@ -1100,12 +1115,12 @@ async def restore_environment(request: EnvRestoreRequest) -> EnvRestoreResponse:
1100
1115
  base_env = WordleEnvironment(task_instance=instance)
1101
1116
  # Lazy import of wrapper only when needed
1102
1117
  try:
1103
- from .envs.wordle.environment import (
1104
- WordleEnvironmentWrapper as _WordleWrapper,
1105
- )
1118
+ from .envs.wordle.environment import WordleEnvironmentWrapper
1106
1119
  except Exception as e:
1107
- raise HTTPException(status_code=500, detail=f"Wordle wrapper unavailable: {e}")
1108
- wrapper = await _WordleWrapper.deserialize(payload=state_dict, env=base_env)
1120
+ raise HTTPException(
1121
+ status_code=500, detail=f"Wordle wrapper unavailable: {e}"
1122
+ ) from e
1123
+ wrapper = await WordleEnvironmentWrapper.deserialize(payload=state_dict, env=base_env)
1109
1124
 
1110
1125
  env_id = registry.register_env(
1111
1126
  env=wrapper,
@@ -1126,15 +1141,17 @@ async def restore_environment(request: EnvRestoreRequest) -> EnvRestoreResponse:
1126
1141
 
1127
1142
  elif name_lower == "sokoban":
1128
1143
  try:
1144
+ from synth_ai.environments.examples.sokoban.environment import (
1145
+ SokobanEnvironment,
1146
+ )
1129
1147
  from synth_ai.environments.examples.sokoban.taskset import (
1130
1148
  SokobanTaskInstance,
1131
1149
  SokobanTaskInstanceMetadata,
1132
1150
  )
1133
- from synth_ai.environments.examples.sokoban.environment import (
1134
- SokobanEnvironment,
1135
- )
1136
1151
  except Exception as e:
1137
- raise HTTPException(status_code=500, detail=f"Sokoban modules unavailable: {e}")
1152
+ raise HTTPException(
1153
+ status_code=500, detail=f"Sokoban modules unavailable: {e}"
1154
+ ) from e
1138
1155
 
1139
1156
  cfg = state_dict.get("config", {}) or {}
1140
1157
  metadata = SokobanTaskInstanceMetadata(difficulty=cfg.get("difficulty", "easy"))
@@ -1153,12 +1170,12 @@ async def restore_environment(request: EnvRestoreRequest) -> EnvRestoreResponse:
1153
1170
  base_env = SokobanEnvironment(task_instance=instance)
1154
1171
  # Lazy import of wrapper only when needed
1155
1172
  try:
1156
- from .envs.sokoban.environment import (
1157
- SokobanEnvironmentWrapper as _SokobanWrapper,
1158
- )
1173
+ from .envs.sokoban.environment import SokobanEnvironmentWrapper
1159
1174
  except Exception as e:
1160
- raise HTTPException(status_code=500, detail=f"Sokoban wrapper unavailable: {e}")
1161
- wrapper = await _SokobanWrapper.deserialize(payload=state_dict, env=base_env)
1175
+ raise HTTPException(
1176
+ status_code=500, detail=f"Sokoban wrapper unavailable: {e}"
1177
+ ) from e
1178
+ wrapper = await SokobanEnvironmentWrapper.deserialize(payload=state_dict, env=base_env)
1162
1179
 
1163
1180
  env_id = registry.register_env(
1164
1181
  env=wrapper,
@@ -1185,7 +1202,7 @@ async def restore_environment(request: EnvRestoreRequest) -> EnvRestoreResponse:
1185
1202
 
1186
1203
  except Exception as e:
1187
1204
  logger.error(f"Failed to restore environment from snapshot {request.snapshot_id}: {e}")
1188
- raise HTTPException(status_code=500, detail=str(e))
1205
+ raise HTTPException(status_code=500, detail=str(e)) from e
1189
1206
 
1190
1207
 
1191
1208
  @router.post("/terminate", response_model=EnvTerminateResponse)
@@ -1206,4 +1223,4 @@ async def terminate_environment(request: EnvTerminateRequest) -> EnvTerminateRes
1206
1223
 
1207
1224
  except Exception as e:
1208
1225
  logger.error(f"Failed to terminate environment {request.env_id}: {e}")
1209
- raise HTTPException(status_code=500, detail=str(e))
1226
+ raise HTTPException(status_code=500, detail=str(e)) from e