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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. synth_ai/__init__.py +18 -9
  2. synth_ai/cli/__init__.py +10 -5
  3. synth_ai/cli/balance.py +22 -17
  4. synth_ai/cli/calc.py +2 -3
  5. synth_ai/cli/demo.py +3 -5
  6. synth_ai/cli/legacy_root_backup.py +58 -32
  7. synth_ai/cli/man.py +22 -19
  8. synth_ai/cli/recent.py +9 -8
  9. synth_ai/cli/root.py +58 -13
  10. synth_ai/cli/status.py +13 -6
  11. synth_ai/cli/traces.py +45 -21
  12. synth_ai/cli/watch.py +40 -37
  13. synth_ai/config/base_url.py +1 -3
  14. synth_ai/core/experiment.py +1 -2
  15. synth_ai/environments/__init__.py +2 -6
  16. synth_ai/environments/environment/artifacts/base.py +3 -1
  17. synth_ai/environments/environment/db/sqlite.py +1 -1
  18. synth_ai/environments/environment/registry.py +19 -20
  19. synth_ai/environments/environment/resources/sqlite.py +2 -3
  20. synth_ai/environments/environment/rewards/core.py +3 -2
  21. synth_ai/environments/environment/tools/__init__.py +6 -4
  22. synth_ai/environments/examples/crafter_classic/__init__.py +1 -1
  23. synth_ai/environments/examples/crafter_classic/engine.py +13 -13
  24. synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +1 -0
  25. synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +2 -1
  26. synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +2 -1
  27. synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +3 -2
  28. synth_ai/environments/examples/crafter_classic/environment.py +16 -15
  29. synth_ai/environments/examples/crafter_classic/taskset.py +2 -2
  30. synth_ai/environments/examples/crafter_classic/trace_hooks_v3.py +2 -3
  31. synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +2 -1
  32. synth_ai/environments/examples/crafter_custom/crafter/__init__.py +2 -2
  33. synth_ai/environments/examples/crafter_custom/crafter/config.py +2 -2
  34. synth_ai/environments/examples/crafter_custom/crafter/env.py +1 -5
  35. synth_ai/environments/examples/crafter_custom/crafter/objects.py +1 -2
  36. synth_ai/environments/examples/crafter_custom/crafter/worldgen.py +1 -2
  37. synth_ai/environments/examples/crafter_custom/dataset_builder.py +5 -5
  38. synth_ai/environments/examples/crafter_custom/environment.py +13 -13
  39. synth_ai/environments/examples/crafter_custom/run_dataset.py +5 -5
  40. synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +2 -2
  41. synth_ai/environments/examples/enron/art_helpers/local_email_db.py +5 -4
  42. synth_ai/environments/examples/enron/art_helpers/types_enron.py +2 -1
  43. synth_ai/environments/examples/enron/engine.py +18 -14
  44. synth_ai/environments/examples/enron/environment.py +12 -11
  45. synth_ai/environments/examples/enron/taskset.py +7 -7
  46. synth_ai/environments/examples/minigrid/__init__.py +6 -6
  47. synth_ai/environments/examples/minigrid/engine.py +6 -6
  48. synth_ai/environments/examples/minigrid/environment.py +6 -6
  49. synth_ai/environments/examples/minigrid/puzzle_loader.py +3 -2
  50. synth_ai/environments/examples/minigrid/taskset.py +13 -13
  51. synth_ai/environments/examples/nethack/achievements.py +1 -1
  52. synth_ai/environments/examples/nethack/engine.py +8 -7
  53. synth_ai/environments/examples/nethack/environment.py +10 -9
  54. synth_ai/environments/examples/nethack/helpers/__init__.py +8 -9
  55. synth_ai/environments/examples/nethack/helpers/action_mapping.py +1 -1
  56. synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +2 -1
  57. synth_ai/environments/examples/nethack/helpers/observation_utils.py +1 -1
  58. synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +3 -4
  59. synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +6 -5
  60. synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +5 -5
  61. synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +7 -6
  62. synth_ai/environments/examples/nethack/taskset.py +5 -5
  63. synth_ai/environments/examples/red/engine.py +9 -8
  64. synth_ai/environments/examples/red/engine_helpers/reward_components.py +2 -1
  65. synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +7 -7
  66. synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +2 -1
  67. synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +2 -1
  68. synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +2 -1
  69. synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +2 -1
  70. synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +2 -1
  71. synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +2 -1
  72. synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +2 -1
  73. synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +2 -1
  74. synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +2 -1
  75. synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +2 -1
  76. synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +2 -1
  77. synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +3 -2
  78. synth_ai/environments/examples/red/engine_helpers/state_extraction.py +2 -1
  79. synth_ai/environments/examples/red/environment.py +18 -15
  80. synth_ai/environments/examples/red/taskset.py +5 -3
  81. synth_ai/environments/examples/sokoban/engine.py +16 -13
  82. synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +3 -2
  83. synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +2 -1
  84. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +1 -1
  85. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +7 -5
  86. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +1 -1
  87. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +2 -1
  88. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +5 -4
  89. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +3 -2
  90. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +2 -1
  91. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +5 -4
  92. synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +1 -1
  93. synth_ai/environments/examples/sokoban/environment.py +15 -14
  94. synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +5 -3
  95. synth_ai/environments/examples/sokoban/puzzle_loader.py +3 -2
  96. synth_ai/environments/examples/sokoban/taskset.py +13 -10
  97. synth_ai/environments/examples/tictactoe/engine.py +6 -6
  98. synth_ai/environments/examples/tictactoe/environment.py +8 -7
  99. synth_ai/environments/examples/tictactoe/taskset.py +6 -5
  100. synth_ai/environments/examples/verilog/engine.py +4 -3
  101. synth_ai/environments/examples/verilog/environment.py +11 -10
  102. synth_ai/environments/examples/verilog/taskset.py +14 -12
  103. synth_ai/environments/examples/wordle/__init__.py +5 -5
  104. synth_ai/environments/examples/wordle/engine.py +32 -25
  105. synth_ai/environments/examples/wordle/environment.py +21 -16
  106. synth_ai/environments/examples/wordle/helpers/generate_instances_wordfreq.py +6 -6
  107. synth_ai/environments/examples/wordle/taskset.py +20 -12
  108. synth_ai/environments/reproducibility/core.py +1 -1
  109. synth_ai/environments/reproducibility/tree.py +21 -21
  110. synth_ai/environments/service/app.py +3 -2
  111. synth_ai/environments/service/core_routes.py +104 -110
  112. synth_ai/environments/service/external_registry.py +1 -2
  113. synth_ai/environments/service/registry.py +1 -1
  114. synth_ai/environments/stateful/core.py +1 -2
  115. synth_ai/environments/stateful/engine.py +1 -1
  116. synth_ai/environments/tasks/api.py +4 -4
  117. synth_ai/environments/tasks/core.py +14 -12
  118. synth_ai/environments/tasks/filters.py +6 -4
  119. synth_ai/environments/tasks/utils.py +13 -11
  120. synth_ai/evals/base.py +2 -3
  121. synth_ai/experimental/synth_oss.py +4 -4
  122. synth_ai/learning/gateway.py +1 -3
  123. synth_ai/learning/prompts/banking77_injection_eval.py +15 -10
  124. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +26 -14
  125. synth_ai/learning/prompts/mipro.py +61 -52
  126. synth_ai/learning/prompts/random_search.py +42 -43
  127. synth_ai/learning/prompts/run_mipro_banking77.py +32 -20
  128. synth_ai/learning/prompts/run_random_search_banking77.py +71 -52
  129. synth_ai/lm/__init__.py +5 -5
  130. synth_ai/lm/caching/ephemeral.py +9 -9
  131. synth_ai/lm/caching/handler.py +20 -20
  132. synth_ai/lm/caching/persistent.py +10 -10
  133. synth_ai/lm/config.py +3 -3
  134. synth_ai/lm/constants.py +7 -7
  135. synth_ai/lm/core/all.py +17 -3
  136. synth_ai/lm/core/exceptions.py +0 -2
  137. synth_ai/lm/core/main.py +26 -41
  138. synth_ai/lm/core/main_v3.py +20 -10
  139. synth_ai/lm/core/vendor_clients.py +18 -17
  140. synth_ai/lm/injection.py +7 -8
  141. synth_ai/lm/overrides.py +21 -19
  142. synth_ai/lm/provider_support/__init__.py +1 -1
  143. synth_ai/lm/provider_support/anthropic.py +15 -15
  144. synth_ai/lm/provider_support/openai.py +23 -21
  145. synth_ai/lm/structured_outputs/handler.py +34 -32
  146. synth_ai/lm/structured_outputs/inject.py +24 -27
  147. synth_ai/lm/structured_outputs/rehabilitate.py +19 -15
  148. synth_ai/lm/tools/base.py +17 -16
  149. synth_ai/lm/unified_interface.py +17 -18
  150. synth_ai/lm/vendors/base.py +20 -18
  151. synth_ai/lm/vendors/core/anthropic_api.py +36 -27
  152. synth_ai/lm/vendors/core/gemini_api.py +31 -36
  153. synth_ai/lm/vendors/core/mistral_api.py +19 -19
  154. synth_ai/lm/vendors/core/openai_api.py +11 -10
  155. synth_ai/lm/vendors/openai_standard.py +113 -87
  156. synth_ai/lm/vendors/openai_standard_responses.py +74 -61
  157. synth_ai/lm/vendors/retries.py +9 -1
  158. synth_ai/lm/vendors/supported/custom_endpoint.py +26 -26
  159. synth_ai/lm/vendors/supported/deepseek.py +10 -10
  160. synth_ai/lm/vendors/supported/grok.py +8 -8
  161. synth_ai/lm/vendors/supported/ollama.py +2 -1
  162. synth_ai/lm/vendors/supported/openrouter.py +11 -9
  163. synth_ai/lm/vendors/synth_client.py +69 -63
  164. synth_ai/lm/warmup.py +8 -7
  165. synth_ai/tracing/__init__.py +22 -10
  166. synth_ai/tracing_v1/__init__.py +22 -20
  167. synth_ai/tracing_v3/__init__.py +7 -7
  168. synth_ai/tracing_v3/abstractions.py +56 -52
  169. synth_ai/tracing_v3/config.py +4 -2
  170. synth_ai/tracing_v3/db_config.py +6 -8
  171. synth_ai/tracing_v3/decorators.py +29 -30
  172. synth_ai/tracing_v3/examples/basic_usage.py +12 -12
  173. synth_ai/tracing_v3/hooks.py +21 -21
  174. synth_ai/tracing_v3/llm_call_record_helpers.py +85 -98
  175. synth_ai/tracing_v3/lm_call_record_abstractions.py +2 -4
  176. synth_ai/tracing_v3/migration_helper.py +3 -5
  177. synth_ai/tracing_v3/replica_sync.py +30 -32
  178. synth_ai/tracing_v3/session_tracer.py +35 -29
  179. synth_ai/tracing_v3/storage/__init__.py +1 -1
  180. synth_ai/tracing_v3/storage/base.py +8 -7
  181. synth_ai/tracing_v3/storage/config.py +4 -4
  182. synth_ai/tracing_v3/storage/factory.py +4 -4
  183. synth_ai/tracing_v3/storage/utils.py +9 -9
  184. synth_ai/tracing_v3/turso/__init__.py +3 -3
  185. synth_ai/tracing_v3/turso/daemon.py +9 -9
  186. synth_ai/tracing_v3/turso/manager.py +60 -48
  187. synth_ai/tracing_v3/turso/models.py +24 -19
  188. synth_ai/tracing_v3/utils.py +5 -5
  189. synth_ai/tui/__main__.py +1 -1
  190. synth_ai/tui/cli/query_experiments.py +2 -3
  191. synth_ai/tui/cli/query_experiments_v3.py +2 -3
  192. synth_ai/tui/dashboard.py +97 -86
  193. synth_ai/v0/tracing/abstractions.py +28 -28
  194. synth_ai/v0/tracing/base_client.py +9 -9
  195. synth_ai/v0/tracing/client_manager.py +7 -7
  196. synth_ai/v0/tracing/config.py +7 -7
  197. synth_ai/v0/tracing/context.py +6 -6
  198. synth_ai/v0/tracing/decorators.py +6 -5
  199. synth_ai/v0/tracing/events/manage.py +1 -1
  200. synth_ai/v0/tracing/events/store.py +5 -4
  201. synth_ai/v0/tracing/immediate_client.py +4 -5
  202. synth_ai/v0/tracing/local.py +3 -3
  203. synth_ai/v0/tracing/log_client_base.py +4 -5
  204. synth_ai/v0/tracing/retry_queue.py +5 -6
  205. synth_ai/v0/tracing/trackers.py +25 -25
  206. synth_ai/v0/tracing/upload.py +6 -0
  207. synth_ai/v0/tracing_v1/__init__.py +1 -1
  208. synth_ai/v0/tracing_v1/abstractions.py +28 -28
  209. synth_ai/v0/tracing_v1/base_client.py +9 -9
  210. synth_ai/v0/tracing_v1/client_manager.py +7 -7
  211. synth_ai/v0/tracing_v1/config.py +7 -7
  212. synth_ai/v0/tracing_v1/context.py +6 -6
  213. synth_ai/v0/tracing_v1/decorators.py +7 -6
  214. synth_ai/v0/tracing_v1/events/manage.py +1 -1
  215. synth_ai/v0/tracing_v1/events/store.py +5 -4
  216. synth_ai/v0/tracing_v1/immediate_client.py +4 -5
  217. synth_ai/v0/tracing_v1/local.py +3 -3
  218. synth_ai/v0/tracing_v1/log_client_base.py +4 -5
  219. synth_ai/v0/tracing_v1/retry_queue.py +5 -6
  220. synth_ai/v0/tracing_v1/trackers.py +25 -25
  221. synth_ai/v0/tracing_v1/upload.py +25 -24
  222. synth_ai/zyk/__init__.py +1 -0
  223. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev7.dist-info}/METADATA +1 -11
  224. synth_ai-0.2.4.dev7.dist-info/RECORD +299 -0
  225. synth_ai-0.2.4.dev6.dist-info/RECORD +0 -299
  226. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev7.dist-info}/WHEEL +0 -0
  227. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev7.dist-info}/entry_points.txt +0 -0
  228. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev7.dist-info}/licenses/LICENSE +0 -0
  229. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev7.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  import hashlib
2
- from typing import Any, Dict, List, Optional, Type
2
+ from typing import Any
3
3
 
4
4
  from pydantic import BaseModel
5
5
 
@@ -17,11 +17,11 @@ logger = logging.getLogger(__name__)
17
17
 
18
18
 
19
19
  def map_params_to_key(
20
- messages: List[Dict],
20
+ messages: list[dict],
21
21
  model: str,
22
22
  temperature: float,
23
- response_model: Optional[Type[BaseModel]],
24
- tools: Optional[List[BaseTool]] = None,
23
+ response_model: type[BaseModel] | None,
24
+ tools: list[BaseTool] | None = None,
25
25
  reasoning_effort: str = "low",
26
26
  ) -> str:
27
27
  if any(m is None for m in messages):
@@ -76,37 +76,37 @@ class CacheHandler:
76
76
  self.use_persistent_store = use_persistent_store
77
77
  self.use_ephemeral_store = use_ephemeral_store
78
78
 
79
- def _validate_messages(self, messages: List[Dict[str, Any]]) -> None:
79
+ def _validate_messages(self, messages: list[dict[str, Any]]) -> None:
80
80
  """Validate that messages are in the correct format."""
81
- assert all([type(msg["content"]) == str for msg in messages]), (
81
+ assert all(isinstance(msg["content"], str) for msg in messages), (
82
82
  "All message contents must be strings"
83
83
  )
84
84
 
85
85
  def hit_managed_cache(
86
86
  self,
87
87
  model: str,
88
- messages: List[Dict[str, Any]],
89
- lm_config: Dict[str, Any],
90
- tools: Optional[List[BaseTool]] = None,
91
- ) -> Optional[BaseLMResponse]:
88
+ messages: list[dict[str, Any]],
89
+ lm_config: dict[str, Any],
90
+ tools: list[BaseTool] | None = None,
91
+ ) -> BaseLMResponse | None:
92
92
  """Hit the cache with the given key."""
93
93
  self._validate_messages(messages)
94
- assert type(lm_config) == dict, "lm_config must be a dictionary"
94
+ assert isinstance(lm_config, dict), "lm_config must be a dictionary"
95
95
  key = map_params_to_key(
96
96
  messages,
97
97
  model,
98
98
  lm_config.get("temperature", 0.0),
99
- lm_config.get("response_model", None),
99
+ lm_config.get("response_model"),
100
100
  tools,
101
101
  lm_config.get("reasoning_effort", "low"),
102
102
  )
103
103
  if self.use_persistent_store:
104
104
  return persistent_cache.hit_cache(
105
- key=key, response_model=lm_config.get("response_model", None)
105
+ key=key, response_model=lm_config.get("response_model")
106
106
  )
107
107
  elif self.use_ephemeral_store:
108
108
  return ephemeral_cache.hit_cache(
109
- key=key, response_model=lm_config.get("response_model", None)
109
+ key=key, response_model=lm_config.get("response_model")
110
110
  )
111
111
  else:
112
112
  return None
@@ -114,20 +114,20 @@ class CacheHandler:
114
114
  def add_to_managed_cache(
115
115
  self,
116
116
  model: str,
117
- messages: List[Dict[str, Any]],
118
- lm_config: Dict[str, Any],
117
+ messages: list[dict[str, Any]],
118
+ lm_config: dict[str, Any],
119
119
  output: BaseLMResponse,
120
- tools: Optional[List[BaseTool]] = None,
120
+ tools: list[BaseTool] | None = None,
121
121
  ) -> None:
122
122
  """Add the given output to the cache."""
123
123
  self._validate_messages(messages)
124
- assert type(output) == BaseLMResponse, "output must be a BaseLMResponse"
125
- assert type(lm_config) == dict, "lm_config must be a dictionary"
124
+ assert isinstance(output, BaseLMResponse), "output must be a BaseLMResponse"
125
+ assert isinstance(lm_config, dict), "lm_config must be a dictionary"
126
126
  key = map_params_to_key(
127
127
  messages,
128
128
  model,
129
129
  lm_config.get("temperature", 0.0),
130
- lm_config.get("response_model", None),
130
+ lm_config.get("response_model"),
131
131
  tools,
132
132
  lm_config.get("reasoning_effort", "low"),
133
133
  )
@@ -9,7 +9,6 @@ import json
9
9
  import os
10
10
  import sqlite3
11
11
  from dataclasses import dataclass
12
- from typing import Optional, Type, Union
13
12
 
14
13
  from pydantic import BaseModel
15
14
 
@@ -20,10 +19,11 @@ from synth_ai.lm.vendors.base import BaseLMResponse
20
19
  class PersistentCache:
21
20
  """
22
21
  Persistent cache implementation using SQLite.
23
-
22
+
24
23
  This cache stores LM responses in a SQLite database that persists
25
24
  across application restarts.
26
25
  """
26
+
27
27
  def __init__(self, db_path: str = ".cache/persistent_cache.db"):
28
28
  os.makedirs(os.path.dirname(db_path), exist_ok=True)
29
29
  self.conn = sqlite3.connect(db_path)
@@ -33,15 +33,15 @@ class PersistentCache:
33
33
  self.conn.commit()
34
34
 
35
35
  def hit_cache(
36
- self, key: str, response_model: Optional[Type[BaseModel]] = None
37
- ) -> Optional[BaseLMResponse]:
36
+ self, key: str, response_model: type[BaseModel] | None = None
37
+ ) -> BaseLMResponse | None:
38
38
  """
39
39
  Check if a response exists in cache for the given key.
40
-
40
+
41
41
  Args:
42
42
  key: Cache key to look up
43
43
  response_model: Optional Pydantic model class to reconstruct structured output
44
-
44
+
45
45
  Returns:
46
46
  BaseLMResponse if found in cache, None otherwise
47
47
  """
@@ -72,17 +72,17 @@ class PersistentCache:
72
72
  tool_calls=tool_calls,
73
73
  )
74
74
 
75
- def add_to_cache(self, key: str, response: Union[BaseLMResponse, str]) -> None:
75
+ def add_to_cache(self, key: str, response: BaseLMResponse | str) -> None:
76
76
  """
77
77
  Add a response to the cache.
78
-
78
+
79
79
  Args:
80
80
  key: Cache key to store under
81
81
  response: Either a BaseLMResponse object or raw string response
82
-
82
+
83
83
  Raises:
84
84
  ValueError: If response type is not supported
85
-
85
+
86
86
  Note:
87
87
  Uses INSERT OR REPLACE to update existing cache entries.
88
88
  """
synth_ai/lm/config.py CHANGED
@@ -4,8 +4,8 @@ Loads sensitive configuration from environment variables.
4
4
  """
5
5
 
6
6
  import os
7
- from typing import Optional
8
7
  from dataclasses import dataclass
8
+
9
9
  from dotenv import load_dotenv
10
10
 
11
11
  # Load environment variables from .env file
@@ -15,10 +15,10 @@ load_dotenv()
15
15
  def should_use_cache() -> bool:
16
16
  """
17
17
  Check if caching should be enabled based on environment variable.
18
-
18
+
19
19
  Returns:
20
20
  bool: True if caching is enabled (default), False if explicitly disabled.
21
-
21
+
22
22
  Note:
23
23
  Caching is controlled by the USE_ZYK_CACHE environment variable.
24
24
  Set to "false", "0", or "no" to disable caching.
synth_ai/lm/constants.py CHANGED
@@ -13,20 +13,20 @@ GEMINI_REASONING_MODELS = ["gemini-2.5-flash", "gemini-2.5-pro"]
13
13
  # Gemini models that support thinking
14
14
  GEMINI_REASONING_MODELS = ["gemini-2.5-flash", "gemini-2.5-pro"]
15
15
  GEMINI_THINKING_BUDGETS = {
16
- "high": 10000, # High thinking budget for complex reasoning
17
- "medium": 5000, # Medium thinking budget for standard reasoning
18
- "low": 2500, # Low thinking budget for simple reasoning
16
+ "high": 10000, # High thinking budget for complex reasoning
17
+ "medium": 5000, # Medium thinking budget for standard reasoning
18
+ "low": 2500, # Low thinking budget for simple reasoning
19
19
  }
20
20
 
21
21
  # Anthropic Sonnet 3.7 budgets
22
22
  SONNET_37_BUDGETS = {
23
- "high": 8192, # High budget for complex tasks
24
- "medium": 4096, # Medium budget for standard tasks
25
- "low": 2048, # Low budget for simple tasks
23
+ "high": 8192, # High budget for complex tasks
24
+ "medium": 4096, # Medium budget for standard tasks
25
+ "low": 2048, # Low budget for simple tasks
26
26
  }
27
27
 
28
28
  # Combined list of all reasoning models
29
29
  REASONING_MODELS = OPENAI_REASONING_MODELS + CLAUDE_REASONING_MODELS + GEMINI_REASONING_MODELS
30
30
 
31
31
  # Special base temperatures for reasoning models (all set to 1.0)
32
- SPECIAL_BASE_TEMPS = {model: 1 for model in REASONING_MODELS}
32
+ SPECIAL_BASE_TEMPS = dict.fromkeys(REASONING_MODELS, 1)
synth_ai/lm/core/all.py CHANGED
@@ -4,12 +4,12 @@ from synth_ai.lm.vendors.core.openai_api import (
4
4
  OpenAIPrivate,
5
5
  OpenAIStructuredOutputClient,
6
6
  )
7
+ from synth_ai.lm.vendors.supported.custom_endpoint import CustomEndpointAPI
7
8
  from synth_ai.lm.vendors.supported.deepseek import DeepSeekAPI
8
- from synth_ai.lm.vendors.supported.together import TogetherAPI
9
- from synth_ai.lm.vendors.supported.groq import GroqAPI
10
9
  from synth_ai.lm.vendors.supported.grok import GrokAPI
11
- from synth_ai.lm.vendors.supported.custom_endpoint import CustomEndpointAPI
10
+ from synth_ai.lm.vendors.supported.groq import GroqAPI
12
11
  from synth_ai.lm.vendors.supported.openrouter import OpenRouterAPI
12
+ from synth_ai.lm.vendors.supported.together import TogetherAPI
13
13
 
14
14
 
15
15
  class OpenAIClient(OpenAIPrivate):
@@ -57,3 +57,17 @@ class CustomEndpointClient(CustomEndpointAPI):
57
57
  class OpenRouterClient(OpenRouterAPI):
58
58
  def __init__(self):
59
59
  super().__init__()
60
+
61
+
62
+ __all__ = [
63
+ "OpenAIClient",
64
+ "AnthropicClient",
65
+ "GeminiClient",
66
+ "DeepSeekClient",
67
+ "TogetherClient",
68
+ "GroqClient",
69
+ "GrokClient",
70
+ "CustomEndpointClient",
71
+ "OpenRouterClient",
72
+ "OpenAIStructuredOutputClient",
73
+ ]
@@ -1,5 +1,3 @@
1
- from abc import ABC, abstractmethod
2
- from typing import Any, Callable, Dict, List, Literal, Optional, Union
3
1
 
4
2
 
5
3
  class StructuredOutputCoercionFailureException(Exception):
synth_ai/lm/core/main.py CHANGED
@@ -1,8 +1,9 @@
1
- from typing import Any, Dict, List, Literal, Optional, Union
2
1
  import os
2
+ from typing import Any, Literal
3
3
 
4
4
  from pydantic import BaseModel, Field
5
5
 
6
+ from synth_ai.lm.config import reasoning_models
6
7
  from synth_ai.lm.core.exceptions import StructuredOutputCoercionFailureException
7
8
  from synth_ai.lm.core.vendor_clients import (
8
9
  anthropic_naming_regexes,
@@ -10,29 +11,28 @@ from synth_ai.lm.core.vendor_clients import (
10
11
  openai_naming_regexes,
11
12
  )
12
13
  from synth_ai.lm.structured_outputs.handler import StructuredOutputHandler
13
- from synth_ai.lm.vendors.base import VendorBase
14
14
  from synth_ai.lm.tools.base import BaseTool
15
- from synth_ai.lm.config import reasoning_models
15
+ from synth_ai.lm.vendors.base import VendorBase
16
16
 
17
17
 
18
18
  def build_messages(
19
19
  sys_msg: str,
20
20
  user_msg: str,
21
- images_bytes: List[bytes] = [],
22
- model_name: Optional[str] = None,
23
- ) -> List[Dict]:
21
+ images_bytes: list[bytes] = [],
22
+ model_name: str | None = None,
23
+ ) -> list[dict]:
24
24
  """
25
25
  Build a messages list for API calls, handling image formatting based on the model provider.
26
-
26
+
27
27
  Args:
28
28
  sys_msg: System message content
29
29
  user_msg: User message content
30
30
  images_bytes: List of base64-encoded image bytes
31
31
  model_name: Model name to determine proper image format (OpenAI vs Anthropic)
32
-
32
+
33
33
  Returns:
34
34
  List[Dict]: Formatted messages list ready for API calls
35
-
35
+
36
36
  Note:
37
37
  Different providers require different image formats:
38
38
  - OpenAI: Uses "image_url" with data URL format
@@ -102,7 +102,7 @@ class LM:
102
102
  # if str
103
103
  model_name: str
104
104
  client: VendorBase
105
- lm_config: Dict[str, Any]
105
+ lm_config: dict[str, Any]
106
106
  structured_output_handler: StructuredOutputHandler
107
107
 
108
108
  def __init__(
@@ -113,23 +113,8 @@ class LM:
113
113
  max_retries: Literal["None", "Few", "Many"] = "Few",
114
114
  structured_output_mode: Literal["stringified_json", "forced_json"] = "stringified_json",
115
115
  synth_logging: bool = True,
116
- provider: Optional[
117
- Union[
118
- Literal[
119
- "openai",
120
- "anthropic",
121
- "groq",
122
- "gemini",
123
- "deepseek",
124
- "grok",
125
- "mistral",
126
- "openrouter",
127
- "together",
128
- ],
129
- str,
130
- ]
131
- ] = None,
132
- enable_thinking: Optional[bool] = None,
116
+ provider: Literal["openai", "anthropic", "groq", "gemini", "deepseek", "grok", "mistral", "openrouter", "together"] | str | None = None,
117
+ enable_thinking: bool | None = None,
133
118
  ):
134
119
  # print("Structured output mode", structured_output_mode)
135
120
  # Check for environment variable if provider is not specified
@@ -170,13 +155,13 @@ class LM:
170
155
 
171
156
  def respond_sync(
172
157
  self,
173
- system_message: Optional[str] = None,
174
- user_message: Optional[str] = None,
175
- messages: Optional[List[Dict]] = None,
176
- images_as_bytes: List[bytes] = [],
177
- response_model: Optional[BaseModel] = None,
158
+ system_message: str | None = None,
159
+ user_message: str | None = None,
160
+ messages: list[dict] | None = None,
161
+ images_as_bytes: list[bytes] = [],
162
+ response_model: BaseModel | None = None,
178
163
  use_ephemeral_cache_only: bool = False,
179
- tools: Optional[List[BaseTool]] = None,
164
+ tools: list[BaseTool] | None = None,
180
165
  reasoning_effort: str = "low",
181
166
  ):
182
167
  assert (system_message is None) == (user_message is None), (
@@ -231,13 +216,13 @@ class LM:
231
216
 
232
217
  async def respond_async(
233
218
  self,
234
- system_message: Optional[str] = None,
235
- user_message: Optional[str] = None,
236
- messages: Optional[List[Dict]] = None,
237
- images_as_bytes: List[bytes] = [],
238
- response_model: Optional[BaseModel] = None,
219
+ system_message: str | None = None,
220
+ user_message: str | None = None,
221
+ messages: list[dict] | None = None,
222
+ images_as_bytes: list[bytes] = [],
223
+ response_model: BaseModel | None = None,
239
224
  use_ephemeral_cache_only: bool = False,
240
- tools: Optional[List[BaseTool]] = None,
225
+ tools: list[BaseTool] | None = None,
241
226
  reasoning_effort: str = "low",
242
227
  ):
243
228
  # "In respond_async")
@@ -300,8 +285,8 @@ if __name__ == "__main__":
300
285
 
301
286
  # Update json instructions to handle nested pydantic?
302
287
  class Thought(BaseModel):
303
- argument_keys: List[str] = Field(description="The keys of the arguments")
304
- argument_values: List[str] = Field(
288
+ argument_keys: list[str] = Field(description="The keys of the arguments")
289
+ argument_values: list[str] = Field(
305
290
  description="Stringified JSON for the values of the arguments"
306
291
  )
307
292
 
@@ -53,7 +53,9 @@ def build_messages(
53
53
  ],
54
54
  },
55
55
  ]
56
- elif len(images_bytes) > 0 and any(regex.match(model_name) for regex in anthropic_naming_regexes):
56
+ elif len(images_bytes) > 0 and any(
57
+ regex.match(model_name) for regex in anthropic_naming_regexes
58
+ ):
57
59
  return [
58
60
  {"role": "system", "content": sys_msg},
59
61
  {
@@ -163,7 +165,7 @@ class LM:
163
165
  self.system_id = system_id or f"lm_{self.vendor or 'unknown'}_{self.model or 'unknown'}"
164
166
  self.enable_v3_tracing = enable_v3_tracing
165
167
  self.additional_params = additional_params
166
-
168
+
167
169
  # Initialize vendor wrapper early, before any potential usage
168
170
  # (e.g., within StructuredOutputHandler initialization below)
169
171
  self._vendor_wrapper = None
@@ -221,11 +223,14 @@ class LM:
221
223
  """Determine if Responses API should be used."""
222
224
  if self.use_responses_api is not None:
223
225
  return self.use_responses_api
224
-
226
+
225
227
  # Auto-detect based on model
226
228
  responses_models = {
227
- "o4-mini", "o3", "o3-mini", # Supported Synth-hosted models
228
- "gpt-oss-120b", "gpt-oss-20b" # OSS models via Synth
229
+ "o4-mini",
230
+ "o3",
231
+ "o3-mini", # Supported Synth-hosted models
232
+ "gpt-oss-120b",
233
+ "gpt-oss-20b", # OSS models via Synth
229
234
  }
230
235
  return self.model in responses_models or (self.model and self.model in reasoning_models)
231
236
 
@@ -377,11 +382,15 @@ class LM:
377
382
  raise AttributeError(
378
383
  f"Vendor wrapper {type(vendor_wrapper).__name__} has no suitable response method"
379
384
  )
380
- if not hasattr(response, 'api_type'):
385
+ if not hasattr(response, "api_type"):
381
386
  response.api_type = "chat"
382
387
 
383
388
  # Update stored response ID if auto-storing
384
- if self.auto_store_responses and hasattr(response, 'response_id') and response.response_id:
389
+ if (
390
+ self.auto_store_responses
391
+ and hasattr(response, "response_id")
392
+ and response.response_id
393
+ ):
385
394
  self._last_response_id = response.response_id
386
395
 
387
396
  except Exception as e:
@@ -397,12 +406,13 @@ class LM:
397
406
  and hasattr(self.session_tracer, "current_session")
398
407
  ):
399
408
  latency_ms = int((time.time() - start_time) * 1000)
400
-
409
+
401
410
  # Create LLMCallRecord from the response
402
411
  from datetime import datetime
412
+
403
413
  started_at = datetime.utcnow()
404
414
  completed_at = datetime.utcnow()
405
-
415
+
406
416
  call_record = create_llm_call_record_from_response(
407
417
  response=response,
408
418
  model_name=self.model or self.vendor,
@@ -415,7 +425,7 @@ class LM:
415
425
  completed_at=completed_at,
416
426
  latency_ms=latency_ms,
417
427
  )
418
-
428
+
419
429
  # Compute aggregates from the call record
420
430
  aggregates = compute_aggregates_from_call_records([call_record])
421
431
 
@@ -6,43 +6,44 @@ based on model names or explicit provider specifications.
6
6
  """
7
7
 
8
8
  import re
9
- from typing import Any, List, Pattern, Optional, Dict
9
+ from re import Pattern
10
+ from typing import Any
10
11
 
11
12
  from synth_ai.lm.core.all import (
12
13
  AnthropicClient,
14
+ CustomEndpointClient,
13
15
  DeepSeekClient,
14
16
  GeminiClient,
15
- GroqClient,
16
17
  GrokClient,
18
+ GroqClient,
17
19
  # OpenAIClient,
18
20
  OpenAIStructuredOutputClient,
19
- TogetherClient,
20
- CustomEndpointClient,
21
21
  OpenRouterClient,
22
+ TogetherClient,
22
23
  )
23
24
 
24
25
  # Regular expressions to match model names to their respective providers
25
- openai_naming_regexes: List[Pattern] = [
26
+ openai_naming_regexes: list[Pattern] = [
26
27
  re.compile(r"^(ft:)?(o[1,3,4](-.*)?|gpt-.*)$"),
27
28
  ]
28
- openai_formatting_model_regexes: List[Pattern] = [
29
+ openai_formatting_model_regexes: list[Pattern] = [
29
30
  re.compile(r"^(ft:)?gpt-4o(-.*)?$"),
30
31
  ]
31
- anthropic_naming_regexes: List[Pattern] = [
32
+ anthropic_naming_regexes: list[Pattern] = [
32
33
  re.compile(r"^claude-.*$"),
33
34
  ]
34
- gemini_naming_regexes: List[Pattern] = [
35
+ gemini_naming_regexes: list[Pattern] = [
35
36
  re.compile(r"^gemini-.*$"),
36
37
  re.compile(r"^gemma[2-9].*$"),
37
38
  ]
38
- deepseek_naming_regexes: List[Pattern] = [
39
+ deepseek_naming_regexes: list[Pattern] = [
39
40
  re.compile(r"^deepseek-.*$"),
40
41
  ]
41
- together_naming_regexes: List[Pattern] = [
42
+ together_naming_regexes: list[Pattern] = [
42
43
  re.compile(r"^.*\/.*$"),
43
44
  ]
44
45
 
45
- groq_naming_regexes: List[Pattern] = [
46
+ groq_naming_regexes: list[Pattern] = [
46
47
  re.compile(r"^llama-3.3-70b-versatile$"),
47
48
  re.compile(r"^llama-3.1-8b-instant$"),
48
49
  re.compile(r"^qwen-2.5-32b$"),
@@ -60,7 +61,7 @@ groq_naming_regexes: List[Pattern] = [
60
61
  re.compile(r"^moonshotai/kimi-k2-instruct$"),
61
62
  ]
62
63
 
63
- grok_naming_regexes: List[Pattern] = [
64
+ grok_naming_regexes: list[Pattern] = [
64
65
  re.compile(r"^grok-3-beta$"),
65
66
  re.compile(r"^grok-3-mini-beta$"),
66
67
  re.compile(r"^grok-beta$"),
@@ -68,16 +69,16 @@ grok_naming_regexes: List[Pattern] = [
68
69
  ]
69
70
 
70
71
 
71
- openrouter_naming_regexes: List[Pattern] = [
72
+ openrouter_naming_regexes: list[Pattern] = [
72
73
  re.compile(r"^openrouter/.*$"), # openrouter/model-name pattern
73
74
  ]
74
75
 
75
- openrouter_naming_regexes: List[Pattern] = [
76
+ openrouter_naming_regexes: list[Pattern] = [
76
77
  re.compile(r"^openrouter/.*$"), # openrouter/model-name pattern
77
78
  ]
78
79
 
79
80
  # Custom endpoint patterns - check these before generic patterns
80
- custom_endpoint_naming_regexes: List[Pattern] = [
81
+ custom_endpoint_naming_regexes: list[Pattern] = [
81
82
  # Modal endpoints: org--app.modal.run
82
83
  re.compile(r"^[a-zA-Z0-9\-]+--[a-zA-Z0-9\-]+\.modal\.run$"),
83
84
  # Generic domain patterns for custom endpoints
@@ -86,7 +87,7 @@ custom_endpoint_naming_regexes: List[Pattern] = [
86
87
  ]
87
88
 
88
89
  # Provider mapping for explicit provider overrides
89
- PROVIDER_MAP: Dict[str, Any] = {
90
+ PROVIDER_MAP: dict[str, Any] = {
90
91
  "openai": OpenAIStructuredOutputClient,
91
92
  "anthropic": AnthropicClient,
92
93
  "groq": GroqClient,
@@ -104,7 +105,7 @@ def get_client(
104
105
  model_name: str,
105
106
  with_formatting: bool = False,
106
107
  synth_logging: bool = True,
107
- provider: Optional[str] = None,
108
+ provider: str | None = None,
108
109
  ) -> Any:
109
110
  """
110
111
  Get a vendor client for the specified model.
synth_ai/lm/injection.py CHANGED
@@ -2,16 +2,16 @@ from __future__ import annotations
2
2
 
3
3
  import contextvars
4
4
  from contextlib import contextmanager
5
- from typing import Any, Dict, List, Optional
5
+ from typing import Any
6
6
 
7
- Rule = Dict[str, Any]
7
+ Rule = dict[str, Any]
8
8
 
9
- _rules_ctx: contextvars.ContextVar[Optional[List[Rule]]] = contextvars.ContextVar(
9
+ _rules_ctx: contextvars.ContextVar[list[Rule] | None] = contextvars.ContextVar(
10
10
  "injection_rules", default=None
11
11
  )
12
12
 
13
13
 
14
- def set_injection_rules(rules: List[Rule]):
14
+ def set_injection_rules(rules: list[Rule]):
15
15
  """Set prompt-injection rules for the current context and return a reset token.
16
16
 
17
17
  Each rule must be a dict with at least keys: "find" and "replace" (strings).
@@ -24,7 +24,7 @@ def set_injection_rules(rules: List[Rule]):
24
24
  return _rules_ctx.set(rules)
25
25
 
26
26
 
27
- def get_injection_rules() -> Optional[List[Rule]]:
27
+ def get_injection_rules() -> list[Rule] | None:
28
28
  """Get the current context's injection rules, if any."""
29
29
  return _rules_ctx.get()
30
30
 
@@ -34,7 +34,7 @@ def clear_injection_rules(token) -> None:
34
34
  _rules_ctx.reset(token)
35
35
 
36
36
 
37
- def apply_injection(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
37
+ def apply_injection(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
38
38
  """Apply ordered substring replacements to text parts of messages in place.
39
39
 
40
40
  - Only modifies `str` content or list parts where `part["type"] == "text"`.
@@ -71,11 +71,10 @@ def apply_injection(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
71
71
 
72
72
 
73
73
  @contextmanager
74
- def injection_rules_ctx(rules: List[Rule]):
74
+ def injection_rules_ctx(rules: list[Rule]):
75
75
  """Context manager to temporarily apply injection rules within the block."""
76
76
  tok = set_injection_rules(rules)
77
77
  try:
78
78
  yield
79
79
  finally:
80
80
  clear_injection_rules(tok)
81
-