synth-ai 0.2.0__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 +35 -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-0.2.0.dist-info → synth_ai-0.2.1.dev0.dist-info}/WHEEL +1 -1
  245. synth_ai/zyk/lms/caching/constants.py +0 -1
  246. synth_ai/zyk/lms/cost/monitor.py +0 -1
  247. synth_ai/zyk/lms/cost/statefulness.py +0 -1
  248. synth_ai-0.2.0.dist-info/METADATA +0 -36
  249. synth_ai-0.2.0.dist-info/RECORD +0 -50
  250. /synth_ai/{zyk/lms/__init__.py → environments/reproducibility/helpers.py} +0 -0
  251. /synth_ai/{zyk/lms/caching → lm}/__init__.py +0 -0
  252. /synth_ai/{zyk/lms/core → lm/caching}/__init__.py +0 -0
  253. /synth_ai/{zyk/lms → lm}/caching/dbs.py +0 -0
  254. /synth_ai/{zyk/lms/cost → lm/core}/__init__.py +0 -0
  255. /synth_ai/{zyk/lms → lm}/core/exceptions.py +0 -0
  256. /synth_ai/{zyk/lms/structured_outputs → lm/cost}/__init__.py +0 -0
  257. /synth_ai/{zyk/lms/vendors → lm/structured_outputs}/__init__.py +0 -0
  258. /synth_ai/{zyk/lms → lm}/tools/__init__.py +0 -0
  259. /synth_ai/{zyk/lms → lm}/tools/base.py +0 -0
  260. /synth_ai/{zyk/lms/vendors/core → lm/vendors}/__init__.py +0 -0
  261. /synth_ai/{zyk/lms → lm}/vendors/base.py +0 -0
  262. /synth_ai/{zyk/lms/vendors/local → lm/vendors/core}/__init__.py +0 -0
  263. /synth_ai/{zyk/lms/vendors/supported → lm/vendors/local}/__init__.py +0 -0
  264. /synth_ai/{zyk/lms → lm}/vendors/local/ollama.py +0 -0
  265. {synth_ai-0.2.0.dist-info → synth_ai-0.2.1.dev0.dist-info/licenses}/LICENSE +0 -0
  266. {synth_ai-0.2.0.dist-info → synth_ai-0.2.1.dev0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,147 @@
1
+ import time
2
+ from typing import Literal, Optional
3
+
4
+ from synth_ai.tracing.abstractions import Event
5
+ from synth_ai.tracing.events.store import event_store
6
+ from synth_ai.tracing.local import _local, logger
7
+
8
+
9
+ def get_current_event(event_type: str) -> "Event":
10
+ """
11
+ Get the current active event of the specified type.
12
+ Raises ValueError if no such event exists.
13
+ """
14
+ events = getattr(_local, "active_events", {})
15
+ if event_type not in events:
16
+ raise ValueError(f"No active event of type '{event_type}' found")
17
+ return events[event_type]
18
+
19
+
20
+ def set_current_event(event: Optional["Event"], decorator_type: Literal["sync", "async"] = None):
21
+ """
22
+ Set the current event, ending any existing events of the same type.
23
+ If event is None, it clears the current event of that type.
24
+ """
25
+ if event is None:
26
+ raise ValueError("Event cannot be None when setting current event.")
27
+
28
+ # Check if we're in an async context
29
+ try:
30
+ import asyncio
31
+
32
+ asyncio.get_running_loop()
33
+ is_async = True
34
+ except RuntimeError:
35
+ is_async = False
36
+
37
+ if decorator_type == "sync" or not is_async:
38
+ # Original thread-local storage logic
39
+ if not hasattr(_local, "active_events"):
40
+ _local.active_events = {}
41
+ logger.debug("Initialized active_events in thread local storage")
42
+
43
+ # If there's an existing event of the same type, end it
44
+ if event.event_type in _local.active_events:
45
+ if (
46
+ _local.active_events[event.event_type].system_instance_id
47
+ == event.system_instance_id
48
+ ):
49
+ logger.debug(f"Found existing event of type {event.event_type}")
50
+ existing_event = _local.active_events[event.event_type]
51
+ existing_event.closed = time.time()
52
+ logger.debug(
53
+ f"Closed existing event of type {event.event_type} at {existing_event.closed}"
54
+ )
55
+
56
+ # Store the closed event if system_instance_id is present
57
+ if hasattr(_local, "system_instance_id"):
58
+ logger.debug(f"Storing closed event for system {_local.system_instance_id}")
59
+ try:
60
+ event_store.add_event(
61
+ _local.system_name,
62
+ _local.system_id,
63
+ _local.system_instance_id,
64
+ existing_event,
65
+ )
66
+ logger.debug("Successfully stored closed event")
67
+ except Exception as e:
68
+ logger.error(f"Failed to store closed event: {str(e)}")
69
+ raise
70
+
71
+ # Set the new event with both keys
72
+ _local.active_events[event.event_type] = event # Plain key
73
+ if is_async:
74
+ unique_key = f"{event.event_type}-{time.time()}"
75
+ _local.active_events[unique_key] = event # Unique key for async
76
+
77
+ else:
78
+ from synth_ai.tracing.local import (
79
+ active_events_var,
80
+ system_id_var,
81
+ system_instance_id_var,
82
+ system_name_var,
83
+ )
84
+
85
+ # Get current active events from context var
86
+ active_events = active_events_var.get()
87
+ # print("Active events:", active_events)
88
+ # If there's an existing event of the same type, end it
89
+ if event and event.event_type in active_events:
90
+ existing_event = active_events[event.event_type]
91
+
92
+ # Check that the active event has the same system_instance_id as the one we're settting
93
+ if existing_event.system_instance_id == event.system_instance_id:
94
+ logger.debug(f"Found existing event of type {event.event_type}")
95
+ existing_event.closed = time.time()
96
+ logger.debug(
97
+ f"Closed existing event of type {event.event_type} at {existing_event.closed}"
98
+ )
99
+
100
+ # Store the closed event if system_instance_id is present
101
+ system_instance_id = system_instance_id_var.get()
102
+ if system_instance_id:
103
+ logger.debug(f"Storing closed event for system {system_instance_id}")
104
+ try:
105
+ event_store.add_event(
106
+ system_name_var.get(),
107
+ system_id_var.get(),
108
+ system_instance_id,
109
+ existing_event,
110
+ )
111
+ logger.debug("Successfully stored closed event")
112
+ except Exception as e:
113
+ logger.error(f"Failed to store closed event: {str(e)}")
114
+ raise
115
+ elif not event:
116
+ logger.debug(f"No event found for type {event.event_type}")
117
+ # Set the new event with both keys
118
+ active_events[event.event_type] = event # Plain key
119
+ unique_key = f"{event.event_type}-{time.time()}"
120
+ active_events[unique_key] = event # Unique key for async
121
+ active_events_var.set(active_events)
122
+ logger.debug(
123
+ f"New event set as current in context vars with keys '{event.event_type}' and '{unique_key}'"
124
+ )
125
+
126
+
127
+ def clear_current_event(event_type: str):
128
+ if hasattr(_local, "active_events"):
129
+ _local.active_events.pop(event_type, None)
130
+ logger.debug(f"Cleared current event of type {event_type}")
131
+
132
+
133
+ def end_event(event_type: str) -> Optional[Event]:
134
+ """End the current event and store it."""
135
+ current_event = get_current_event(event_type)
136
+ if current_event:
137
+ current_event.closed = time.time()
138
+ # Store the event
139
+ if hasattr(_local, "system_instance_id"):
140
+ event_store.add_event(
141
+ _local.system_name,
142
+ _local.system_id,
143
+ _local.system_instance_id,
144
+ current_event,
145
+ )
146
+ clear_current_event(event_type)
147
+ return current_event
@@ -0,0 +1,86 @@
1
+ import time
2
+ from contextlib import contextmanager
3
+
4
+ from synth_ai.tracing.abstractions import Event
5
+ from synth_ai.tracing.config import LoggingMode
6
+ from synth_ai.tracing.decorators import (
7
+ _local,
8
+ clear_current_event,
9
+ get_tracing_config,
10
+ set_current_event,
11
+ )
12
+ from synth_ai.tracing.events.store import event_store
13
+ from synth_ai.tracing.immediate_client import (
14
+ AsyncImmediateLogClient,
15
+ ImmediateLogClient,
16
+ )
17
+ from synth_ai.tracing.local import (
18
+ system_id_var,
19
+ system_instance_id_var,
20
+ system_name_var,
21
+ )
22
+
23
+
24
+ @contextmanager
25
+ def event_scope(event_type: str):
26
+ """
27
+ Context manager for creating and managing events.
28
+
29
+ Usage:
30
+ with event_scope("my_event_type"):
31
+ # do stuff
32
+ """
33
+ # Check if we're in an async context
34
+ try:
35
+ import asyncio
36
+
37
+ asyncio.get_running_loop()
38
+ is_async = True
39
+ except RuntimeError:
40
+ is_async = False
41
+
42
+ # Get system_instance_id from appropriate source
43
+ system_instance_id = (
44
+ system_instance_id_var.get() if is_async else getattr(_local, "system_instance_id", None)
45
+ )
46
+ system_id = system_id_var.get() if is_async else getattr(_local, "system_id", None)
47
+ system_name = system_name_var.get() if is_async else getattr(_local, "system_name", None)
48
+
49
+ event = Event(
50
+ system_instance_id=system_instance_id,
51
+ event_type=event_type,
52
+ opened=time.time(),
53
+ closed=None,
54
+ partition_index=0,
55
+ agent_compute_step=None,
56
+ environment_compute_steps=[],
57
+ )
58
+ set_current_event(event)
59
+
60
+ try:
61
+ yield event
62
+ finally:
63
+ event.closed = time.time()
64
+ clear_current_event(event_type)
65
+
66
+ # Get the config to determine logging mode
67
+ config = get_tracing_config()
68
+
69
+ # If immediate logging is enabled and we have system info, send the event now
70
+ if config.mode == LoggingMode.INSTANT and system_instance_id:
71
+ system_info = {
72
+ "system_name": system_name,
73
+ "system_id": system_id,
74
+ "system_instance_id": system_instance_id,
75
+ }
76
+ if is_async:
77
+ client = AsyncImmediateLogClient(config)
78
+ # Note: Since we can't use await in a finally block,
79
+ # we'll have to rely on the event store as primary storage in async context
80
+ else:
81
+ client = ImmediateLogClient(config)
82
+ client.send_event(event, system_info)
83
+
84
+ # Always store in event_store
85
+ if system_instance_id:
86
+ event_store.add_event(system_name, system_id, system_instance_id, event)
@@ -0,0 +1,227 @@
1
+ import json
2
+ import logging
3
+ import time
4
+ from threading import RLock # Change this import
5
+ from typing import Any, Dict, List
6
+
7
+ from synth_ai.tracing.abstractions import Event, EventPartitionElement, SystemTrace
8
+ from synth_ai.tracing.local import ( # Import context variables
9
+ _local,
10
+ active_events_var,
11
+ )
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class EventStore:
17
+ def __init__(self) -> None:
18
+ self._traces: Dict[str, SystemTrace] = {}
19
+ self._lock = RLock() # Use RLock instead of Lock
20
+ self.logger = logging.getLogger(__name__)
21
+
22
+ def get_or_create_system_trace(
23
+ self,
24
+ system_name: str,
25
+ system_id: str,
26
+ system_instance_id: str,
27
+ _already_locked: bool = False,
28
+ ) -> SystemTrace:
29
+ """Get or create a SystemTrace for the given system_instance_id."""
30
+ logger = logging.getLogger(__name__)
31
+ # logger.debug(f"Starting get_or_create_system_trace for {system_instance_id}")
32
+
33
+ def _get_or_create():
34
+ # logger.debug("Inside _get_or_create")
35
+ if system_instance_id not in self._traces:
36
+ # Get system_instance_metadata from context if available
37
+ system_instance_metadata = {}
38
+ try:
39
+ from synth_ai.tracing.context import get_current_context
40
+
41
+ context = get_current_context()
42
+ if (
43
+ "system_instance_metadata" in context
44
+ and context["system_instance_metadata"]
45
+ ):
46
+ system_instance_metadata = context["system_instance_metadata"]
47
+ except Exception as e:
48
+ self.logger.warning(
49
+ f"Failed to get system_instance_metadata from context: {str(e)}"
50
+ )
51
+
52
+ # logger.debug(f"Creating new system trace for {system_instance_id}")
53
+ self._traces[system_instance_id] = SystemTrace(
54
+ system_name=system_name,
55
+ system_id=system_id,
56
+ system_instance_id=system_instance_id,
57
+ metadata={},
58
+ partition=[], # EventPartitionElement(partition_index=0, events=[])
59
+ current_partition_index=0,
60
+ instance_metadata=system_instance_metadata,
61
+ )
62
+ # logger.debug("Returning system trace")
63
+ return self._traces[system_instance_id]
64
+
65
+ if _already_locked:
66
+ return _get_or_create()
67
+ else:
68
+ with self._lock:
69
+ # logger.debug("Lock acquired in get_or_create_system_trace")
70
+ return _get_or_create()
71
+
72
+ def increment_partition(self, system_name: str, system_id: str, system_instance_id: str) -> int:
73
+ """Increment the partition index for a system and create new partition element."""
74
+ logger = logging.getLogger(__name__)
75
+ # logger.debug(f"Starting increment_partition for system {system_instance_id}")
76
+
77
+ with self._lock:
78
+ # logger.debug("Lock acquired in increment_partition")
79
+ system_trace = self.get_or_create_system_trace(
80
+ system_name, system_id, system_instance_id, _already_locked=True
81
+ )
82
+ # logger.debug(
83
+ # f"Got system trace, current index: {system_trace.current_partition_index}"
84
+ # )
85
+
86
+ system_trace.current_partition_index += 1
87
+ # logger.debug(
88
+ # f"Incremented index to: {system_trace.current_partition_index}"
89
+ # )
90
+
91
+ system_trace.partition.append(
92
+ EventPartitionElement(
93
+ partition_index=system_trace.current_partition_index, events=[]
94
+ )
95
+ )
96
+ # logger.debug("Added new partition element")
97
+
98
+ return system_trace.current_partition_index
99
+
100
+ def add_event(self, system_name: str, system_id: str, system_instance_id: str, event: Event):
101
+ """Add an event to the appropriate partition of the system trace."""
102
+ # self.#logger.debug(f"Adding event type {event.event_type} to system {system_instance_id}")
103
+ # self.#logger.debug(
104
+ # f"Event details: opened={event.opened}, closed={event.closed}, partition={event.partition_index}"
105
+ # )
106
+ # print("Adding event: ", event)
107
+
108
+ # try:
109
+ if not self._lock.acquire(timeout=5):
110
+ self.logger.error("Failed to acquire lock within timeout period")
111
+ return
112
+
113
+ try:
114
+ system_trace = self.get_or_create_system_trace(
115
+ system_name, system_id, system_instance_id, _already_locked=True
116
+ )
117
+ # self.#logger.debug(
118
+ # f"Got system trace with {len(system_trace.partition)} partitions"
119
+ # )
120
+
121
+ current_partition = next(
122
+ (p for p in system_trace.partition if p.partition_index == event.partition_index),
123
+ None,
124
+ )
125
+
126
+ if current_partition is None:
127
+ self.logger.error(
128
+ f"No partition found for index {event.partition_index} - existing partitions: {set([p.partition_index for p in system_trace.partition])}"
129
+ )
130
+ raise ValueError(f"No partition found for index {event.partition_index}")
131
+
132
+ current_partition.events.append(event)
133
+ finally:
134
+ self._lock.release()
135
+ # except Exception as e:
136
+ # self.logger.error(f"Error in add_event: {str(e)}", exc_info=True)
137
+ # raise
138
+
139
+ def get_system_traces(self) -> List[SystemTrace]:
140
+ """Get all system traces."""
141
+ with self._lock:
142
+ self.end_all_active_events()
143
+
144
+ return list(self._traces.values())
145
+
146
+ def end_all_active_events(self):
147
+ """End all active events and store them."""
148
+ # self.#logger.debug("Ending all active events")
149
+
150
+ # For synchronous code
151
+ if hasattr(_local, "active_events"):
152
+ for _, event in list(_local.active_events.items()):
153
+ if event.closed is None:
154
+ event.closed = time.time()
155
+ self.add_event(
156
+ event.system_name,
157
+ event.system_id,
158
+ event.system_instance_id,
159
+ event,
160
+ )
161
+ _local.active_events.clear()
162
+
163
+ else:
164
+ # For asynchronous code
165
+ active_events_async = active_events_var.get()
166
+ if active_events_async:
167
+ for _, event in list(active_events_async.items()):
168
+ if event.closed is None:
169
+ event.closed = time.time()
170
+ self.add_event(
171
+ event.system_name,
172
+ event.system_id,
173
+ event.system_instance_id,
174
+ event,
175
+ )
176
+ active_events_var.set({})
177
+
178
+ def get_system_traces_json(self) -> str:
179
+ """Get all system traces as JSON."""
180
+ with self._lock:
181
+ return json.dumps(
182
+ [
183
+ {
184
+ "system_instance_id": trace.system_instance_id,
185
+ "current_partition_index": trace.current_partition_index,
186
+ "partition": [
187
+ {
188
+ "partition_index": p.partition_index,
189
+ "events": [self._event_to_dict(event) for event in p.events],
190
+ }
191
+ for p in trace.partition
192
+ ],
193
+ }
194
+ for trace in self._traces.values()
195
+ ],
196
+ default=str,
197
+ )
198
+
199
+ def _event_to_dict(self, event: Event) -> Dict[str, Any]:
200
+ """Convert an Event object to a dictionary."""
201
+ return {
202
+ "event_type": event.event_type,
203
+ "opened": event.opened,
204
+ "closed": event.closed,
205
+ "partition_index": event.partition_index,
206
+ "agent_compute_step": {
207
+ "event_order": event.agent_compute_step.event_order,
208
+ "compute_began": event.agent_compute_step.compute_began,
209
+ "compute_ended": event.agent_compute_step.compute_ended,
210
+ "compute_input": event.agent_compute_step.compute_input,
211
+ "compute_output": event.agent_compute_step.compute_output,
212
+ },
213
+ "environment_compute_steps": [
214
+ {
215
+ "event_order": step.event_order,
216
+ "compute_began": step.compute_began,
217
+ "compute_ended": step.compute_ended,
218
+ "compute_input": step.compute_input,
219
+ "compute_output": step.compute_output,
220
+ }
221
+ for step in event.environment_compute_steps
222
+ ],
223
+ }
224
+
225
+
226
+ # Global event store instance
227
+ event_store = EventStore()
@@ -0,0 +1,152 @@
1
+ import asyncio
2
+ import logging
3
+ import time
4
+ from typing import Dict
5
+
6
+ import httpx
7
+
8
+ from synth_ai.tracing.abstractions import Event
9
+ from synth_ai.tracing.client_manager import ClientManager
10
+ from synth_ai.tracing.config import TracingConfig
11
+ from synth_ai.tracing.log_client_base import BaseAsyncLogClient, BaseLogClient
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class ImmediateLogClient(BaseLogClient):
17
+ """Synchronous client for immediate logging of events"""
18
+
19
+ def __init__(self, config: TracingConfig):
20
+ super().__init__(config)
21
+ self.client_manager = ClientManager.initialize(config)
22
+
23
+ def send_event(self, event: Event, system_info: Dict[str, str]) -> bool:
24
+ """Send a single event with retries and fallback"""
25
+ from synth_ai.tracing.retry_queue import (
26
+ retry_queue, # Import here to avoid circular import
27
+ )
28
+
29
+ payload = self._prepare_payload(event, system_info)
30
+ headers = {"Authorization": f"Bearer {self.config.api_key}"}
31
+ last_exception = None
32
+
33
+ for attempt in range(self.config.max_retries):
34
+ try:
35
+ response = self.client_manager.sync_client.post(
36
+ f"{self.config.base_url}/v1/uploads/stream",
37
+ json=payload,
38
+ headers=headers,
39
+ timeout=self.config.timeout,
40
+ )
41
+ logger.info(f"Event upload response status: {response.status_code}")
42
+ response.raise_for_status()
43
+ response_data = response.json()
44
+ event.id = response_data.get("event_id") # Store the event_id
45
+ self._handle_success()
46
+ return True
47
+ except Exception as e:
48
+ last_exception = e
49
+ status_code = getattr(e, "response", None)
50
+ if status_code:
51
+ status_code = status_code.status_code
52
+
53
+ if not self._should_retry(attempt, status_code):
54
+ break
55
+
56
+ backoff = self.client_manager.calculate_backoff(attempt)
57
+ time.sleep(backoff)
58
+
59
+ # If we get here, all immediate retries failed
60
+ self._handle_failure(event, system_info, last_exception)
61
+
62
+ # Add to retry queue for later processing
63
+ retry_queue.add_failed_event(event, system_info)
64
+ return False
65
+
66
+
67
+ class AsyncImmediateLogClient(BaseAsyncLogClient):
68
+ """Asynchronous client for immediate logging of events"""
69
+
70
+ def __init__(self, config: TracingConfig):
71
+ super().__init__(config)
72
+ self.client_manager = ClientManager.initialize(config)
73
+
74
+ async def send_event(self, event: Event, system_info: Dict[str, str]) -> bool:
75
+ """Send a single event with retries and fallback (async version)"""
76
+ from synth_ai.tracing.retry_queue import retry_queue
77
+
78
+ if not self.config.api_key:
79
+ logger.error("No API key provided")
80
+ return False
81
+
82
+ # First get JWT token
83
+ async with httpx.AsyncClient(timeout=self.config.timeout) as client:
84
+ try:
85
+ auth_response = await client.get(
86
+ f"{self.config.base_url}/v1/auth/token",
87
+ headers={"customer_specific_api_key": self.config.api_key.strip()},
88
+ )
89
+ auth_response.raise_for_status()
90
+ auth_data = auth_response.json() # This is synchronous in httpx
91
+ logger.debug(f"Auth response data: {auth_data}")
92
+
93
+ token = auth_data.get("access_token")
94
+ if not token:
95
+ logger.error(
96
+ f"No access token received from auth endpoint. Response data: {auth_data}"
97
+ )
98
+ return False
99
+ except Exception as e:
100
+ # logger.error(f"Failed to get auth token: {e}")
101
+ return False
102
+
103
+ # Now send the event with the JWT token
104
+ payload = self._prepare_payload(event, system_info)
105
+ headers = {
106
+ "Authorization": f"Bearer {token}",
107
+ "Content-Type": "application/json",
108
+ "Accept": "application/json",
109
+ }
110
+
111
+ logger.debug(f"Request URL: {self.config.base_url}/v1/uploads/stream")
112
+ logger.debug("Using JWT token for authentication")
113
+ logger.debug(f"Headers: {headers}")
114
+ logger.debug(f"Payload size: {len(str(payload))} bytes")
115
+
116
+ async with httpx.AsyncClient(timeout=self.config.timeout) as client:
117
+ for attempt in range(self.config.max_retries + 1):
118
+ try:
119
+ response = await client.post(
120
+ f"{self.config.base_url}/v1/uploads/stream",
121
+ json=payload,
122
+ headers=headers,
123
+ timeout=self.config.timeout,
124
+ )
125
+ logger.info(f"Event upload response status: {response.status_code}")
126
+
127
+ if response.status_code >= 500:
128
+ error_body = response.text
129
+ logger.error(f"Server error. Response: {error_body}")
130
+ response.raise_for_status()
131
+
132
+ if response.status_code == 401:
133
+ error_body = response.text
134
+ logger.error(f"Authentication failed. Response: {error_body}")
135
+ response.raise_for_status()
136
+
137
+ response_data = response.json() # This is synchronous in httpx
138
+ event.id = response_data.get("event_id")
139
+ return True
140
+
141
+ except Exception as e:
142
+ # last_exception = e
143
+ # logger.error(f"Upload attempt {attempt + 1} failed: {str(e)}")
144
+ if attempt < self.config.max_retries:
145
+ backoff = self.client_manager.calculate_backoff(attempt)
146
+ await asyncio.sleep(backoff)
147
+
148
+ # logger.error(
149
+ # f"All upload attempts failed. Last error: {last_exception}"
150
+ # )
151
+ retry_queue.add_failed_event(event, system_info)
152
+ return False
@@ -0,0 +1,18 @@
1
+ import logging
2
+ import threading
3
+ from contextvars import ContextVar
4
+ from typing import Any, Dict
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ # Thread-local storage for active events and system_instance_id
9
+ # Used for synchronous tracing
10
+ _local = threading.local()
11
+ # Used for asynchronous tracing
12
+ system_name_var: ContextVar[str] = ContextVar("system_name", default=None)
13
+ system_id_var: ContextVar[str] = ContextVar("system_id", default=None)
14
+ system_instance_id_var: ContextVar[str] = ContextVar("system_instance_id", default=None)
15
+ system_instance_metadata_var: ContextVar[Dict[str, Any]] = ContextVar(
16
+ "system_instance_metadata", default={}
17
+ )
18
+ active_events_var: ContextVar[Dict[str, Any]] = ContextVar("active_events", default={})