synth-ai 0.2.4.dev6__py3-none-any.whl → 0.2.4.dev8__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 (256) hide show
  1. synth_ai/__init__.py +18 -9
  2. synth_ai/cli/__init__.py +10 -5
  3. synth_ai/cli/balance.py +25 -32
  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 +47 -2
  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/http.py +102 -0
  123. synth_ai/inference/__init__.py +7 -0
  124. synth_ai/inference/client.py +20 -0
  125. synth_ai/jobs/client.py +246 -0
  126. synth_ai/learning/__init__.py +24 -0
  127. synth_ai/learning/client.py +149 -0
  128. synth_ai/learning/config.py +43 -0
  129. synth_ai/learning/constants.py +29 -0
  130. synth_ai/learning/ft_client.py +59 -0
  131. synth_ai/learning/gateway.py +1 -3
  132. synth_ai/learning/health.py +43 -0
  133. synth_ai/learning/jobs.py +205 -0
  134. synth_ai/learning/prompts/banking77_injection_eval.py +15 -10
  135. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +26 -14
  136. synth_ai/learning/prompts/mipro.py +61 -52
  137. synth_ai/learning/prompts/random_search.py +42 -43
  138. synth_ai/learning/prompts/run_mipro_banking77.py +32 -20
  139. synth_ai/learning/prompts/run_random_search_banking77.py +71 -52
  140. synth_ai/learning/rl_client.py +256 -0
  141. synth_ai/learning/sse.py +58 -0
  142. synth_ai/learning/validators.py +48 -0
  143. synth_ai/lm/__init__.py +5 -5
  144. synth_ai/lm/caching/ephemeral.py +9 -9
  145. synth_ai/lm/caching/handler.py +20 -20
  146. synth_ai/lm/caching/persistent.py +10 -10
  147. synth_ai/lm/config.py +3 -3
  148. synth_ai/lm/constants.py +7 -7
  149. synth_ai/lm/core/all.py +17 -3
  150. synth_ai/lm/core/exceptions.py +0 -2
  151. synth_ai/lm/core/main.py +26 -41
  152. synth_ai/lm/core/main_v3.py +33 -10
  153. synth_ai/lm/core/synth_models.py +48 -0
  154. synth_ai/lm/core/vendor_clients.py +26 -22
  155. synth_ai/lm/injection.py +7 -8
  156. synth_ai/lm/overrides.py +21 -19
  157. synth_ai/lm/provider_support/__init__.py +1 -1
  158. synth_ai/lm/provider_support/anthropic.py +15 -15
  159. synth_ai/lm/provider_support/openai.py +23 -21
  160. synth_ai/lm/structured_outputs/handler.py +34 -32
  161. synth_ai/lm/structured_outputs/inject.py +24 -27
  162. synth_ai/lm/structured_outputs/rehabilitate.py +19 -15
  163. synth_ai/lm/tools/base.py +17 -16
  164. synth_ai/lm/unified_interface.py +17 -18
  165. synth_ai/lm/vendors/base.py +20 -18
  166. synth_ai/lm/vendors/core/anthropic_api.py +36 -27
  167. synth_ai/lm/vendors/core/gemini_api.py +31 -36
  168. synth_ai/lm/vendors/core/mistral_api.py +19 -19
  169. synth_ai/lm/vendors/core/openai_api.py +42 -13
  170. synth_ai/lm/vendors/openai_standard.py +158 -101
  171. synth_ai/lm/vendors/openai_standard_responses.py +74 -61
  172. synth_ai/lm/vendors/retries.py +9 -1
  173. synth_ai/lm/vendors/supported/custom_endpoint.py +38 -28
  174. synth_ai/lm/vendors/supported/deepseek.py +10 -10
  175. synth_ai/lm/vendors/supported/grok.py +8 -8
  176. synth_ai/lm/vendors/supported/ollama.py +2 -1
  177. synth_ai/lm/vendors/supported/openrouter.py +11 -9
  178. synth_ai/lm/vendors/synth_client.py +425 -75
  179. synth_ai/lm/warmup.py +8 -7
  180. synth_ai/rl/__init__.py +30 -0
  181. synth_ai/rl/contracts.py +32 -0
  182. synth_ai/rl/env_keys.py +137 -0
  183. synth_ai/rl/secrets.py +19 -0
  184. synth_ai/scripts/verify_rewards.py +100 -0
  185. synth_ai/task/__init__.py +10 -0
  186. synth_ai/task/contracts.py +120 -0
  187. synth_ai/task/health.py +28 -0
  188. synth_ai/task/validators.py +12 -0
  189. synth_ai/tracing/__init__.py +22 -10
  190. synth_ai/tracing_v1/__init__.py +22 -20
  191. synth_ai/tracing_v3/__init__.py +7 -7
  192. synth_ai/tracing_v3/abstractions.py +56 -52
  193. synth_ai/tracing_v3/config.py +4 -2
  194. synth_ai/tracing_v3/db_config.py +6 -8
  195. synth_ai/tracing_v3/decorators.py +29 -30
  196. synth_ai/tracing_v3/examples/basic_usage.py +12 -12
  197. synth_ai/tracing_v3/hooks.py +24 -22
  198. synth_ai/tracing_v3/llm_call_record_helpers.py +85 -98
  199. synth_ai/tracing_v3/lm_call_record_abstractions.py +2 -4
  200. synth_ai/tracing_v3/migration_helper.py +3 -5
  201. synth_ai/tracing_v3/replica_sync.py +30 -32
  202. synth_ai/tracing_v3/session_tracer.py +158 -31
  203. synth_ai/tracing_v3/storage/__init__.py +1 -1
  204. synth_ai/tracing_v3/storage/base.py +8 -7
  205. synth_ai/tracing_v3/storage/config.py +4 -4
  206. synth_ai/tracing_v3/storage/factory.py +4 -4
  207. synth_ai/tracing_v3/storage/utils.py +9 -9
  208. synth_ai/tracing_v3/turso/__init__.py +3 -3
  209. synth_ai/tracing_v3/turso/daemon.py +9 -9
  210. synth_ai/tracing_v3/turso/manager.py +278 -48
  211. synth_ai/tracing_v3/turso/models.py +77 -19
  212. synth_ai/tracing_v3/utils.py +5 -5
  213. synth_ai/v0/tracing/abstractions.py +28 -28
  214. synth_ai/v0/tracing/base_client.py +9 -9
  215. synth_ai/v0/tracing/client_manager.py +7 -7
  216. synth_ai/v0/tracing/config.py +7 -7
  217. synth_ai/v0/tracing/context.py +6 -6
  218. synth_ai/v0/tracing/decorators.py +6 -5
  219. synth_ai/v0/tracing/events/manage.py +1 -1
  220. synth_ai/v0/tracing/events/store.py +5 -4
  221. synth_ai/v0/tracing/immediate_client.py +4 -5
  222. synth_ai/v0/tracing/local.py +3 -3
  223. synth_ai/v0/tracing/log_client_base.py +4 -5
  224. synth_ai/v0/tracing/retry_queue.py +5 -6
  225. synth_ai/v0/tracing/trackers.py +25 -25
  226. synth_ai/v0/tracing/upload.py +6 -0
  227. synth_ai/v0/tracing_v1/__init__.py +1 -1
  228. synth_ai/v0/tracing_v1/abstractions.py +28 -28
  229. synth_ai/v0/tracing_v1/base_client.py +9 -9
  230. synth_ai/v0/tracing_v1/client_manager.py +7 -7
  231. synth_ai/v0/tracing_v1/config.py +7 -7
  232. synth_ai/v0/tracing_v1/context.py +6 -6
  233. synth_ai/v0/tracing_v1/decorators.py +7 -6
  234. synth_ai/v0/tracing_v1/events/manage.py +1 -1
  235. synth_ai/v0/tracing_v1/events/store.py +5 -4
  236. synth_ai/v0/tracing_v1/immediate_client.py +4 -5
  237. synth_ai/v0/tracing_v1/local.py +3 -3
  238. synth_ai/v0/tracing_v1/log_client_base.py +4 -5
  239. synth_ai/v0/tracing_v1/retry_queue.py +5 -6
  240. synth_ai/v0/tracing_v1/trackers.py +25 -25
  241. synth_ai/v0/tracing_v1/upload.py +25 -24
  242. synth_ai/zyk/__init__.py +1 -0
  243. synth_ai-0.2.4.dev8.dist-info/METADATA +635 -0
  244. synth_ai-0.2.4.dev8.dist-info/RECORD +317 -0
  245. synth_ai/tui/__init__.py +0 -1
  246. synth_ai/tui/__main__.py +0 -13
  247. synth_ai/tui/cli/__init__.py +0 -1
  248. synth_ai/tui/cli/query_experiments.py +0 -165
  249. synth_ai/tui/cli/query_experiments_v3.py +0 -165
  250. synth_ai/tui/dashboard.py +0 -329
  251. synth_ai-0.2.4.dev6.dist-info/METADATA +0 -203
  252. synth_ai-0.2.4.dev6.dist-info/RECORD +0 -299
  253. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev8.dist-info}/WHEEL +0 -0
  254. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev8.dist-info}/entry_points.txt +0 -0
  255. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev8.dist-info}/licenses/LICENSE +0 -0
  256. {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev8.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,6 @@ of the application run, useful for avoiding redundant API calls within a session
7
7
 
8
8
  import os
9
9
  from dataclasses import dataclass
10
- from typing import Optional, Union
11
10
 
12
11
  from diskcache import Cache
13
12
  from pydantic import BaseModel
@@ -20,24 +19,25 @@ from synth_ai.lm.vendors.base import BaseLMResponse
20
19
  class EphemeralCache:
21
20
  """
22
21
  Ephemeral cache implementation using diskcache.
23
-
22
+
24
23
  This cache stores LM responses temporarily on disk with a size limit.
25
24
  The cache is cleared when the application restarts.
26
25
  """
26
+
27
27
  def __init__(self, fast_cache_dir: str = ".cache/ephemeral_cache"):
28
28
  os.makedirs(fast_cache_dir, exist_ok=True)
29
29
  self.fast_cache = Cache(fast_cache_dir, size_limit=DISKCACHE_SIZE_LIMIT)
30
30
 
31
31
  def hit_cache(
32
- self, key: str, response_model: Optional[BaseModel] = None
33
- ) -> Optional[BaseLMResponse]:
32
+ self, key: str, response_model: BaseModel | None = None
33
+ ) -> BaseLMResponse | None:
34
34
  """
35
35
  Check if a response exists in cache for the given key.
36
-
36
+
37
37
  Args:
38
38
  key: Cache key to look up
39
39
  response_model: Optional Pydantic model to reconstruct structured output
40
-
40
+
41
41
  Returns:
42
42
  BaseLMResponse if found in cache, None otherwise
43
43
  """
@@ -65,14 +65,14 @@ class EphemeralCache:
65
65
  tool_calls=tool_calls,
66
66
  )
67
67
 
68
- def add_to_cache(self, key: str, response: Union[BaseLMResponse, str]) -> None:
68
+ def add_to_cache(self, key: str, response: BaseLMResponse | str) -> None:
69
69
  """
70
70
  Add a response to the cache.
71
-
71
+
72
72
  Args:
73
73
  key: Cache key to store under
74
74
  response: Either a BaseLMResponse object or raw string response
75
-
75
+
76
76
  Raises:
77
77
  ValueError: If response type is not supported
78
78
  """
@@ -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
  {
@@ -115,7 +117,11 @@ class LM:
115
117
  if enable_v2_tracing is not None:
116
118
  enable_v3_tracing = enable_v2_tracing
117
119
 
120
+ # Debug logging
121
+ print(f"🔍 LM __init__: provider={provider}, vendor={vendor}, model={model}")
122
+
118
123
  # If vendor not provided, infer from model name
124
+ # But only if no explicit provider was given
119
125
  if vendor is None and model is not None:
120
126
  # Import vendor detection logic
121
127
  from synth_ai.lm.core.vendor_clients import (
@@ -154,6 +160,7 @@ class LM:
154
160
 
155
161
  self.vendor = vendor
156
162
  self.model = model
163
+ print(f"🔍 LM final: vendor={self.vendor}, model={self.model}")
157
164
  self.is_structured = is_structured
158
165
  self.structured_outputs_vendor = structured_outputs_vendor
159
166
  self.response_format = response_format
@@ -163,7 +170,7 @@ class LM:
163
170
  self.system_id = system_id or f"lm_{self.vendor or 'unknown'}_{self.model or 'unknown'}"
164
171
  self.enable_v3_tracing = enable_v3_tracing
165
172
  self.additional_params = additional_params
166
-
173
+
167
174
  # Initialize vendor wrapper early, before any potential usage
168
175
  # (e.g., within StructuredOutputHandler initialization below)
169
176
  self._vendor_wrapper = None
@@ -221,11 +228,14 @@ class LM:
221
228
  """Determine if Responses API should be used."""
222
229
  if self.use_responses_api is not None:
223
230
  return self.use_responses_api
224
-
231
+
225
232
  # Auto-detect based on model
226
233
  responses_models = {
227
- "o4-mini", "o3", "o3-mini", # Supported Synth-hosted models
228
- "gpt-oss-120b", "gpt-oss-20b" # OSS models via Synth
234
+ "o4-mini",
235
+ "o3",
236
+ "o3-mini", # Supported Synth-hosted models
237
+ "gpt-oss-120b",
238
+ "gpt-oss-20b", # OSS models via Synth
229
239
  }
230
240
  return self.model in responses_models or (self.model and self.model in reasoning_models)
231
241
 
@@ -332,6 +342,14 @@ class LM:
332
342
  if hasattr(vendor_wrapper, "_hit_api_async"):
333
343
  # OpenAIStandard expects lm_config
334
344
  lm_config = {"temperature": self.temperature, **self.additional_params, **kwargs}
345
+ # Map convenience enable_thinking => thinking_mode unless explicitly set
346
+ if "enable_thinking" in lm_config and "thinking_mode" not in lm_config:
347
+ try:
348
+ et = lm_config.get("enable_thinking")
349
+ if isinstance(et, bool):
350
+ lm_config["thinking_mode"] = "think" if et else "no_think"
351
+ except Exception:
352
+ pass
335
353
  if self.json_mode:
336
354
  lm_config["response_format"] = {"type": "json_object"}
337
355
 
@@ -377,11 +395,15 @@ class LM:
377
395
  raise AttributeError(
378
396
  f"Vendor wrapper {type(vendor_wrapper).__name__} has no suitable response method"
379
397
  )
380
- if not hasattr(response, 'api_type'):
398
+ if not hasattr(response, "api_type"):
381
399
  response.api_type = "chat"
382
400
 
383
401
  # Update stored response ID if auto-storing
384
- if self.auto_store_responses and hasattr(response, 'response_id') and response.response_id:
402
+ if (
403
+ self.auto_store_responses
404
+ and hasattr(response, "response_id")
405
+ and response.response_id
406
+ ):
385
407
  self._last_response_id = response.response_id
386
408
 
387
409
  except Exception as e:
@@ -397,12 +419,13 @@ class LM:
397
419
  and hasattr(self.session_tracer, "current_session")
398
420
  ):
399
421
  latency_ms = int((time.time() - start_time) * 1000)
400
-
422
+
401
423
  # Create LLMCallRecord from the response
402
424
  from datetime import datetime
425
+
403
426
  started_at = datetime.utcnow()
404
427
  completed_at = datetime.utcnow()
405
-
428
+
406
429
  call_record = create_llm_call_record_from_response(
407
430
  response=response,
408
431
  model_name=self.model or self.vendor,
@@ -415,7 +438,7 @@ class LM:
415
438
  completed_at=completed_at,
416
439
  latency_ms=latency_ms,
417
440
  )
418
-
441
+
419
442
  # Compute aggregates from the call record
420
443
  aggregates = compute_aggregates_from_call_records([call_record])
421
444
 
@@ -0,0 +1,48 @@
1
+ """
2
+ Synth-supported models registry.
3
+
4
+ This module defines the specific models that are supported by Synth's infrastructure.
5
+ Models are organized by family and size for easy maintenance and extension.
6
+
7
+ MAINTENANCE GUIDE:
8
+ 1. Add new model families to the appropriate lists (QWEN_MODELS, OTHER_SYNTH_MODELS)
9
+ 2. Fine-tuned models (ft:) are automatically detected by regex
10
+ 3. Update SYNTH_SUPPORTED_MODELS set when adding new models
11
+ 4. Test changes with: pytest tests/lms/test_qwen_chat_completions.py
12
+
13
+ WHY THIS EXISTS:
14
+ - The previous regex (^.*\/.*$) was too broad and caught unintended models
15
+ - This provides explicit control over which models use Synth infrastructure
16
+ - Easier to maintain and debug model routing issues
17
+ """
18
+
19
+ from typing import List, Set
20
+
21
+ # Qwen3 model families supported by Synth
22
+ QWEN3_MODELS: List[str] = [
23
+ # Qwen3 base models
24
+ "Qwen/Qwen3-0.6B",
25
+ "Qwen/Qwen3-1.7B",
26
+ "Qwen/Qwen3-4B",
27
+ "Qwen/Qwen3-8B",
28
+ "Qwen/Qwen3-14B",
29
+ "Qwen/Qwen3-32B",
30
+
31
+ # Qwen3 specialized variants
32
+ "Qwen/Qwen3-4B-Instruct-2507",
33
+ "Qwen/Qwen3-4B-Thinking-2507",
34
+ ]
35
+
36
+ # Fine-tuned models pattern - any model starting with "ft:" is considered Synth-compatible
37
+ # These are dynamically detected, but we can add specific known ones here
38
+ FINE_TUNED_MODELS: List[str] = [
39
+ # Add specific fine-tuned models that are known to work with Synth
40
+ # Examples:
41
+ # "ft:Qwen/Qwen3-4B-Instruct-2507:ftjob-22",
42
+ ]
43
+
44
+ # Combine all Synth-supported models
45
+ SYNTH_SUPPORTED_MODELS: Set[str] = set(QWEN3_MODELS + FINE_TUNED_MODELS)
46
+
47
+ # Export the main set for easy import
48
+ __all__ = ["SYNTH_SUPPORTED_MODELS", "QWEN3_MODELS", "FINE_TUNED_MODELS"]