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,22 +1,19 @@
1
1
  import json
2
+ import warnings
2
3
  from typing import (
3
4
  Any,
4
- Dict,
5
- List,
5
+ Literal,
6
6
  Optional,
7
- Tuple,
8
- Type,
9
- get_type_hints,
7
+ Union,
10
8
  get_args,
11
9
  get_origin,
12
- Union,
13
- Literal,
10
+ get_type_hints,
14
11
  )
12
+
15
13
  from pydantic import BaseModel
16
- import warnings
17
14
 
18
15
 
19
- def generate_type_map() -> Dict[Any, str]:
16
+ def generate_type_map() -> dict[Any, str]:
20
17
  base_types = {
21
18
  int: "int",
22
19
  float: "float",
@@ -26,8 +23,8 @@ def generate_type_map() -> Dict[Any, str]:
26
23
  }
27
24
 
28
25
  collection_types = {
29
- List: "List",
30
- Dict: "Dict",
26
+ list: "List",
27
+ dict: "Dict",
31
28
  Optional: "Optional",
32
29
  }
33
30
 
@@ -37,19 +34,19 @@ def generate_type_map() -> Dict[Any, str]:
37
34
  for collection, collection_name in collection_types.items():
38
35
  if collection is Optional:
39
36
  type_map[Optional[base_type]] = name
40
- elif collection is Dict:
37
+ elif collection is dict:
41
38
  # Handle generic Dict type
42
- type_map[Dict] = "Dict[Any,Any]"
39
+ type_map[dict] = "Dict[Any,Any]"
43
40
  # Provide both key and value types for Dict
44
- type_map[Dict[base_type, base_type]] = f"{collection_name}[{name},{name}]"
41
+ type_map[dict[base_type, base_type]] = f"{collection_name}[{name},{name}]"
45
42
  # Handle Dict[Any, Any] explicitly
46
- type_map[Dict[Any, Any]] = "Dict[Any,Any]"
43
+ type_map[dict[Any, Any]] = "Dict[Any,Any]"
47
44
  else:
48
45
  type_map[collection[base_type]] = f"{collection_name}[{name}]"
49
46
  return type_map
50
47
 
51
48
 
52
- def generate_example_dict() -> Dict[str, Any]:
49
+ def generate_example_dict() -> dict[str, Any]:
53
50
  example_values = {
54
51
  "str": "<Your type-str response here>",
55
52
  "int": "<Your type-int response here>",
@@ -101,10 +98,10 @@ def get_type_string(type_hint):
101
98
  return f"{type_hint.__name__}({', '.join(f'{k}: {v}' for k, v in field_types.items())})"
102
99
  else:
103
100
  return base_type_examples.get(type_hint, ("Unknown", "unknown"))[0]
104
- elif origin in (list, List):
101
+ elif origin in (list, list):
105
102
  elem_type = get_type_string(args[0])
106
103
  return f"List[{elem_type}]"
107
- elif origin in (dict, Dict):
104
+ elif origin in (dict, dict):
108
105
  key_type = get_type_string(args[0])
109
106
  value_type = get_type_string(args[1])
110
107
  return f"Dict[{key_type}, {value_type}]"
@@ -167,10 +164,10 @@ def get_example_value(type_hint):
167
164
  return example, union_docs
168
165
  else:
169
166
  return base_type_examples.get(type_hint, ("Unknown", "unknown"))[1], []
170
- elif origin in (list, List):
167
+ elif origin in (list, list):
171
168
  value, docs = get_example_value(args[0])
172
169
  return [value], docs
173
- elif origin in (dict, Dict):
170
+ elif origin in (dict, dict):
174
171
  if not args or len(args) < 2:
175
172
  warnings.warn(
176
173
  f"Dictionary type hint {type_hint} missing type arguments. "
@@ -224,9 +221,9 @@ def get_example_value(type_hint):
224
221
  def add_json_instructions_to_messages(
225
222
  system_message,
226
223
  user_message,
227
- response_model: Optional[Type[BaseModel]] = None,
228
- previously_failed_error_messages: List[str] = [],
229
- ) -> Tuple[str, str]:
224
+ response_model: type[BaseModel] | None = None,
225
+ previously_failed_error_messages: list[str] = [],
226
+ ) -> tuple[str, str]:
230
227
  if response_model:
231
228
  type_hints = get_type_hints(response_model)
232
229
  # print("Type hints", type_hints)
@@ -283,10 +280,10 @@ Here are some error traces from previous attempts:
283
280
 
284
281
 
285
282
  def inject_structured_output_instructions(
286
- messages: List[Dict[str, str]],
287
- response_model: Optional[Type[BaseModel]] = None,
288
- previously_failed_error_messages: List[str] = [],
289
- ) -> List[Dict[str, str]]:
283
+ messages: list[dict[str, str]],
284
+ response_model: type[BaseModel] | None = None,
285
+ previously_failed_error_messages: list[str] = [],
286
+ ) -> list[dict[str, str]]:
290
287
  prev_system_message_content = messages[0]["content"]
291
288
  prev_user_message_content = messages[1]["content"]
292
289
  system_message, user_message = add_json_instructions_to_messages(
@@ -2,15 +2,13 @@ import ast
2
2
  import json
3
3
  import logging
4
4
  import re
5
- from typing import Dict, List, Type, Union
6
5
 
7
6
  from pydantic import BaseModel
8
7
 
9
- from synth_ai.lm.vendors.base import VendorBase
10
8
  from synth_ai.lm.vendors.core.openai_api import OpenAIStructuredOutputClient
11
9
 
12
10
 
13
- def pull_out_structured_output(response_raw: str, response_model: Type[BaseModel]) -> BaseModel:
11
+ def pull_out_structured_output(response_raw: str, response_model: type[BaseModel]) -> BaseModel:
14
12
  logger = logging.getLogger(__name__)
15
13
  # logger.debug(f"Raw response received: {response_raw}")
16
14
 
@@ -36,7 +34,7 @@ def pull_out_structured_output(response_raw: str, response_model: Type[BaseModel
36
34
  try:
37
35
  response = json.loads(response_prepared)
38
36
  final = response_model(**response)
39
- except json.JSONDecodeError as e:
37
+ except json.JSONDecodeError:
40
38
  # Attempt to parse using ast.literal_eval as a fallback
41
39
  response_prepared = response_prepared.replace("\n", "").replace("\\n", "")
42
40
  response_prepared = response_prepared.replace('\\"', '"')
@@ -46,18 +44,22 @@ def pull_out_structured_output(response_raw: str, response_model: Type[BaseModel
46
44
  except Exception as inner_e:
47
45
  raise ValueError(
48
46
  f"Failed to parse response as {response_model}: {inner_e} - {response_prepared}"
49
- )
47
+ ) from inner_e
50
48
  except Exception as e:
51
- raise ValueError(f"Failed to parse response as {response_model}: {e} - {response_prepared}")
49
+ raise ValueError(
50
+ f"Failed to parse response as {response_model}: {e} - {response_prepared}"
51
+ ) from e
52
52
  assert isinstance(final, BaseModel), "Structured output must be a Pydantic model"
53
53
  return final
54
54
 
55
55
 
56
56
  def fix_errant_stringified_json_sync(
57
57
  response_raw: str,
58
- response_model: Type[BaseModel],
59
- models: List[str] = ["gpt-4o-mini", "gpt-4o"],
58
+ response_model: type[BaseModel],
59
+ models: list[str] | None = None,
60
60
  ) -> BaseModel:
61
+ if models is None:
62
+ models = ["gpt-4o-mini", "gpt-4o"]
61
63
  try:
62
64
  return pull_out_structured_output(response_raw, response_model)
63
65
  except ValueError as e:
@@ -85,14 +87,16 @@ def fix_errant_stringified_json_sync(
85
87
  return pull_out_structured_output(fixed_response, response_model)
86
88
  except Exception as e:
87
89
  pass
88
- raise ValueError("Failed to fix response using any model")
90
+ raise ValueError("Failed to fix response using any model") from None
89
91
 
90
92
 
91
93
  async def fix_errant_stringified_json_async(
92
94
  response_raw: str,
93
- response_model: Type[BaseModel],
94
- models: List[str] = ["gpt-4o-mini", "gpt-4o"],
95
+ response_model: type[BaseModel],
96
+ models: list[str] | None = None,
95
97
  ) -> BaseModel:
98
+ if models is None:
99
+ models = ["gpt-4o-mini", "gpt-4o"]
96
100
  try:
97
101
  return pull_out_structured_output(response_raw, response_model)
98
102
  except ValueError as e:
@@ -119,13 +123,13 @@ async def fix_errant_stringified_json_async(
119
123
  return pull_out_structured_output(fixed_response, response_model)
120
124
  except Exception as e:
121
125
  pass
122
- raise ValueError("Failed to fix response using any model")
126
+ raise ValueError("Failed to fix response using any model") from None
123
127
 
124
128
 
125
129
  async def fix_errant_forced_async(
126
- messages: List[Dict],
130
+ messages: list[dict],
127
131
  response_raw: str,
128
- response_model: Type[BaseModel],
132
+ response_model: type[BaseModel],
129
133
  model: str,
130
134
  ) -> BaseModel:
131
135
  try:
@@ -157,7 +161,7 @@ async def fix_errant_forced_async(
157
161
 
158
162
  def fix_errant_forced_sync(
159
163
  response_raw: str,
160
- response_model: Type[BaseModel],
164
+ response_model: type[BaseModel],
161
165
  model: str,
162
166
  ) -> BaseModel:
163
167
  client = OpenAIStructuredOutputClient()
synth_ai/lm/tools/base.py CHANGED
@@ -4,7 +4,7 @@ Base class for LM tools.
4
4
  This module provides the base class for defining tools that can be used with language models.
5
5
  """
6
6
 
7
- from typing import Type, Dict, Any
7
+ from typing import Any
8
8
 
9
9
  from pydantic import BaseModel
10
10
 
@@ -12,25 +12,26 @@ from pydantic import BaseModel
12
12
  class BaseTool(BaseModel):
13
13
  """
14
14
  Base class for defining tools that can be used with language models.
15
-
15
+
16
16
  Attributes:
17
17
  name: The name of the tool
18
18
  arguments: Pydantic model defining the tool's arguments
19
19
  description: Human-readable description of what the tool does
20
20
  strict: Whether to enforce strict schema validation (default True)
21
21
  """
22
+
22
23
  name: str
23
- arguments: Type[BaseModel]
24
+ arguments: type[BaseModel]
24
25
  description: str = ""
25
26
  strict: bool = True
26
27
 
27
- def to_openai_tool(self) -> Dict[str, Any]:
28
+ def to_openai_tool(self) -> dict[str, Any]:
28
29
  """
29
30
  Convert the tool to OpenAI's tool format.
30
-
31
+
31
32
  Returns:
32
33
  dict: Tool definition in OpenAI's expected format
33
-
34
+
34
35
  Note:
35
36
  - Ensures additionalProperties is False for strict validation
36
37
  - Fixes array items that lack explicit types
@@ -40,7 +41,7 @@ class BaseTool(BaseModel):
40
41
  schema["additionalProperties"] = False
41
42
 
42
43
  if "properties" in schema:
43
- for prop_name, prop_schema in schema["properties"].items():
44
+ for _prop_name, prop_schema in schema["properties"].items():
44
45
  if prop_schema.get("type") == "array":
45
46
  items_schema = prop_schema.get("items", {})
46
47
  if not isinstance(items_schema, dict) or not items_schema.get("type"):
@@ -63,13 +64,13 @@ class BaseTool(BaseModel):
63
64
  },
64
65
  }
65
66
 
66
- def to_anthropic_tool(self) -> Dict[str, Any]:
67
+ def to_anthropic_tool(self) -> dict[str, Any]:
67
68
  """
68
69
  Convert the tool to Anthropic's tool format.
69
-
70
+
70
71
  Returns:
71
72
  dict: Tool definition in Anthropic's expected format
72
-
73
+
73
74
  Note:
74
75
  Anthropic uses a different format with input_schema instead of parameters.
75
76
  """
@@ -86,13 +87,13 @@ class BaseTool(BaseModel):
86
87
  },
87
88
  }
88
89
 
89
- def to_mistral_tool(self) -> Dict[str, Any]:
90
+ def to_mistral_tool(self) -> dict[str, Any]:
90
91
  """
91
92
  Convert the tool to Mistral's tool format.
92
-
93
+
93
94
  Returns:
94
95
  dict: Tool definition in Mistral's expected format
95
-
96
+
96
97
  Note:
97
98
  Mistral requires explicit handling of array types and enum values.
98
99
  """
@@ -130,13 +131,13 @@ class BaseTool(BaseModel):
130
131
  },
131
132
  }
132
133
 
133
- def to_gemini_tool(self) -> Dict[str, Any]:
134
+ def to_gemini_tool(self) -> dict[str, Any]:
134
135
  """
135
136
  Convert the tool to Gemini's tool format.
136
-
137
+
137
138
  Returns:
138
139
  dict: Tool definition in Gemini's expected format
139
-
140
+
140
141
  Note:
141
142
  Gemini uses a simpler format without the nested "function" key.
142
143
  """
@@ -3,12 +3,11 @@ Unified interface for LM providers.
3
3
  Provides a consistent API for OpenAI and Synth backends.
4
4
  """
5
5
 
6
- import os
7
6
  import logging
8
7
  from abc import ABC, abstractmethod
9
- from typing import List, Dict, Any, Optional, Union
8
+ from typing import Any
10
9
 
11
- from .config import SynthConfig, OpenAIConfig
10
+ from .config import OpenAIConfig, SynthConfig
12
11
 
13
12
  logger = logging.getLogger(__name__)
14
13
 
@@ -18,8 +17,8 @@ class UnifiedLMProvider(ABC):
18
17
 
19
18
  @abstractmethod
20
19
  async def create_chat_completion(
21
- self, model: str, messages: List[Dict[str, Any]], **kwargs
22
- ) -> Dict[str, Any]:
20
+ self, model: str, messages: list[dict[str, Any]], **kwargs
21
+ ) -> dict[str, Any]:
23
22
  """Create a chat completion."""
24
23
  pass
25
24
 
@@ -37,7 +36,7 @@ class UnifiedLMProvider(ABC):
37
36
  class OpenAIProvider(UnifiedLMProvider):
38
37
  """OpenAI provider implementation."""
39
38
 
40
- def __init__(self, api_key: Optional[str] = None, **kwargs):
39
+ def __init__(self, api_key: str | None = None, **kwargs):
41
40
  """
42
41
  Initialize OpenAI provider.
43
42
 
@@ -47,8 +46,8 @@ class OpenAIProvider(UnifiedLMProvider):
47
46
  """
48
47
  try:
49
48
  from openai import AsyncOpenAI
50
- except ImportError:
51
- raise ImportError("OpenAI package not installed. Run: pip install openai")
49
+ except ImportError as err:
50
+ raise ImportError("OpenAI package not installed. Run: pip install openai") from err
52
51
 
53
52
  # Use provided key or load from environment
54
53
  if api_key is None:
@@ -59,8 +58,8 @@ class OpenAIProvider(UnifiedLMProvider):
59
58
  logger.info("Initialized OpenAI provider")
60
59
 
61
60
  async def create_chat_completion(
62
- self, model: str, messages: List[Dict[str, Any]], **kwargs
63
- ) -> Dict[str, Any]:
61
+ self, model: str, messages: list[dict[str, Any]], **kwargs
62
+ ) -> dict[str, Any]:
64
63
  """Create a chat completion using OpenAI."""
65
64
  response = await self.client.chat.completions.create(
66
65
  model=model, messages=messages, **kwargs
@@ -82,7 +81,7 @@ class OpenAIProvider(UnifiedLMProvider):
82
81
  class SynthProvider(UnifiedLMProvider):
83
82
  """Synth provider implementation."""
84
83
 
85
- def __init__(self, config: Optional[SynthConfig] = None, **kwargs):
84
+ def __init__(self, config: SynthConfig | None = None, **kwargs):
86
85
  """
87
86
  Initialize Synth provider.
88
87
 
@@ -96,8 +95,8 @@ class SynthProvider(UnifiedLMProvider):
96
95
  self.client = AsyncSynthClient(self.config)
97
96
 
98
97
  async def create_chat_completion(
99
- self, model: str, messages: List[Dict[str, Any]], **kwargs
100
- ) -> Dict[str, Any]:
98
+ self, model: str, messages: list[dict[str, Any]], **kwargs
99
+ ) -> dict[str, Any]:
101
100
  """Create a chat completion using Synth."""
102
101
  return await self.client.chat_completions_create(model=model, messages=messages, **kwargs)
103
102
 
@@ -156,9 +155,9 @@ class UnifiedLMClient:
156
155
  default_provider: Default provider to use ("openai" or "synth")
157
156
  """
158
157
  self.default_provider = default_provider
159
- self._providers: Dict[str, UnifiedLMProvider] = {}
158
+ self._providers: dict[str, UnifiedLMProvider] = {}
160
159
 
161
- async def _get_provider(self, provider: Optional[str] = None) -> UnifiedLMProvider:
160
+ async def _get_provider(self, provider: str | None = None) -> UnifiedLMProvider:
162
161
  """Get or create a provider instance."""
163
162
  provider_name = provider or self.default_provider
164
163
 
@@ -168,8 +167,8 @@ class UnifiedLMClient:
168
167
  return self._providers[provider_name]
169
168
 
170
169
  async def create_chat_completion(
171
- self, model: str, messages: List[Dict[str, Any]], provider: Optional[str] = None, **kwargs
172
- ) -> Dict[str, Any]:
170
+ self, model: str, messages: list[dict[str, Any]], provider: str | None = None, **kwargs
171
+ ) -> dict[str, Any]:
173
172
  """
174
173
  Create a chat completion using specified or default provider.
175
174
 
@@ -185,7 +184,7 @@ class UnifiedLMClient:
185
184
  provider_instance = await self._get_provider(provider)
186
185
  return await provider_instance.create_chat_completion(model, messages, **kwargs)
187
186
 
188
- async def warmup(self, model: str, provider: Optional[str] = None, **kwargs) -> bool:
187
+ async def warmup(self, model: str, provider: str | None = None, **kwargs) -> bool:
189
188
  """Warm up a model on specified provider."""
190
189
  provider_instance = await self._get_provider(provider)
191
190
  return await provider_instance.warmup(model, **kwargs)
@@ -5,7 +5,7 @@ This module provides abstract base classes for implementing language model vendo
5
5
  """
6
6
 
7
7
  from abc import ABC, abstractmethod
8
- from typing import Any, Dict, List, Optional
8
+ from typing import Any
9
9
 
10
10
  from pydantic import BaseModel
11
11
 
@@ -13,7 +13,7 @@ from pydantic import BaseModel
13
13
  class BaseLMResponse(BaseModel):
14
14
  """
15
15
  Standard response format from language model API calls.
16
-
16
+
17
17
  Attributes:
18
18
  raw_response: The raw text response from the model
19
19
  structured_output: Optional parsed Pydantic model if structured output was requested
@@ -22,39 +22,41 @@ class BaseLMResponse(BaseModel):
22
22
  reasoning: Optional reasoning trace from the model (o1 models)
23
23
  api_type: Optional API type used ("chat", "responses", or "harmony")
24
24
  """
25
+
25
26
  raw_response: str
26
- structured_output: Optional[BaseModel] = None
27
- tool_calls: Optional[List[Dict]] = None
28
- response_id: Optional[str] = None
29
- reasoning: Optional[str] = None
30
- api_type: Optional[str] = None
31
- usage: Optional[Dict[str, Any]] = None
27
+ structured_output: BaseModel | None = None
28
+ tool_calls: list[dict] | None = None
29
+ response_id: str | None = None
30
+ reasoning: str | None = None
31
+ api_type: str | None = None
32
+ usage: dict[str, Any] | None = None
32
33
 
33
34
 
34
35
  class VendorBase(ABC):
35
36
  """
36
37
  Abstract base class for language model vendor implementations.
37
-
38
+
38
39
  Attributes:
39
40
  used_for_structured_outputs: Whether this vendor supports structured outputs
40
41
  exceptions_to_retry: List of exceptions that should trigger retries
41
42
  """
43
+
42
44
  used_for_structured_outputs: bool = False
43
- exceptions_to_retry: List[Exception] = []
45
+ exceptions_to_retry: list[Exception] = []
44
46
 
45
47
  @abstractmethod
46
48
  async def _hit_api_async(
47
49
  self,
48
- messages: List[Dict[str, Any]],
49
- response_model_override: Optional[BaseModel] = None,
50
+ messages: list[dict[str, Any]],
51
+ response_model_override: BaseModel | None = None,
50
52
  ) -> str:
51
53
  """
52
54
  Make an asynchronous API call to the language model.
53
-
55
+
54
56
  Args:
55
57
  messages: List of message dictionaries with role and content
56
58
  response_model_override: Optional Pydantic model for structured output
57
-
59
+
58
60
  Returns:
59
61
  str: The model's response
60
62
  """
@@ -63,16 +65,16 @@ class VendorBase(ABC):
63
65
  @abstractmethod
64
66
  def _hit_api_sync(
65
67
  self,
66
- messages: List[Dict[str, Any]],
67
- response_model_override: Optional[BaseModel] = None,
68
+ messages: list[dict[str, Any]],
69
+ response_model_override: BaseModel | None = None,
68
70
  ) -> str:
69
71
  """
70
72
  Make a synchronous API call to the language model.
71
-
73
+
72
74
  Args:
73
75
  messages: List of message dictionaries with role and content
74
76
  response_model_override: Optional Pydantic model for structured output
75
-
77
+
76
78
  Returns:
77
79
  str: The model's response
78
80
  """
@@ -1,5 +1,5 @@
1
1
  import json
2
- from typing import Any, Dict, List, Optional, Tuple, Type
2
+ from typing import Any
3
3
 
4
4
  import anthropic
5
5
  import pydantic
@@ -8,27 +8,32 @@ from pydantic import BaseModel
8
8
  from synth_ai.lm.caching.initialize import (
9
9
  get_cache_handler,
10
10
  )
11
+ from synth_ai.lm.constants import CLAUDE_REASONING_MODELS, SONNET_37_BUDGETS, SPECIAL_BASE_TEMPS
12
+ from synth_ai.lm.overrides import (
13
+ apply_injection as apply_injection_overrides,
14
+ )
15
+ from synth_ai.lm.overrides import (
16
+ apply_param_overrides,
17
+ use_overrides_for_messages,
18
+ )
11
19
  from synth_ai.lm.tools.base import BaseTool
12
20
  from synth_ai.lm.vendors.base import BaseLMResponse, VendorBase
13
- from synth_ai.lm.constants import SPECIAL_BASE_TEMPS, CLAUDE_REASONING_MODELS, SONNET_37_BUDGETS
14
21
  from synth_ai.lm.vendors.core.openai_api import OpenAIStructuredOutputClient
15
- from synth_ai.lm.overrides import use_overrides_for_messages, apply_injection as apply_injection_overrides, apply_param_overrides
16
- from synth_ai.lm.injection import apply_injection
17
22
 
18
- ANTHROPIC_EXCEPTIONS_TO_RETRY: Tuple[Type[Exception], ...] = (anthropic.APIError,)
23
+ ANTHROPIC_EXCEPTIONS_TO_RETRY: tuple[type[Exception], ...] = (anthropic.APIError,)
19
24
 
20
25
 
21
26
  class AnthropicAPI(VendorBase):
22
27
  used_for_structured_outputs: bool = True
23
- exceptions_to_retry: Tuple = ANTHROPIC_EXCEPTIONS_TO_RETRY
28
+ exceptions_to_retry: tuple = ANTHROPIC_EXCEPTIONS_TO_RETRY
24
29
  sync_client: Any
25
30
  async_client: Any
26
31
 
27
32
  def __init__(
28
33
  self,
29
- exceptions_to_retry: Tuple[Type[Exception], ...] = ANTHROPIC_EXCEPTIONS_TO_RETRY,
34
+ exceptions_to_retry: tuple[type[Exception], ...] = ANTHROPIC_EXCEPTIONS_TO_RETRY,
30
35
  used_for_structured_outputs: bool = False,
31
- reasoning_effort: Optional[str] = "high",
36
+ reasoning_effort: str | None = "high",
32
37
  ):
33
38
  self.sync_client = anthropic.Anthropic()
34
39
  self.async_client = anthropic.AsyncAnthropic()
@@ -46,14 +51,14 @@ class AnthropicAPI(VendorBase):
46
51
  async def _hit_api_async(
47
52
  self,
48
53
  model: str,
49
- messages: List[Dict[str, Any]],
50
- lm_config: Dict[str, Any],
54
+ messages: list[dict[str, Any]],
55
+ lm_config: dict[str, Any],
51
56
  use_ephemeral_cache_only: bool = False,
52
57
  reasoning_effort: str = "high",
53
- tools: Optional[List[BaseTool]] = None,
54
- **vendor_params: Dict[str, Any],
58
+ tools: list[BaseTool] | None = None,
59
+ **vendor_params: dict[str, Any],
55
60
  ) -> BaseLMResponse:
56
- assert lm_config.get("response_model", None) is None, (
61
+ assert lm_config.get("response_model") is None, (
57
62
  "response_model is not supported for standard calls"
58
63
  )
59
64
  used_cache_handler = get_cache_handler(use_ephemeral_cache_only)
@@ -77,6 +82,7 @@ class AnthropicAPI(VendorBase):
77
82
  }
78
83
  with use_overrides_for_messages(messages):
79
84
  from synth_ai.lm.overrides import apply_tool_overrides
85
+
80
86
  api_params = apply_tool_overrides(api_params)
81
87
  api_params = apply_param_overrides(api_params)
82
88
 
@@ -89,8 +95,11 @@ class AnthropicAPI(VendorBase):
89
95
  import inspect
90
96
 
91
97
  create_sig = inspect.signature(self.async_client.messages.create)
92
- if "thinking" in create_sig.parameters and model in CLAUDE_REASONING_MODELS:
93
- if reasoning_effort in ["high", "medium"]:
98
+ if (
99
+ "thinking" in create_sig.parameters
100
+ and model in CLAUDE_REASONING_MODELS
101
+ and reasoning_effort in ["high", "medium"]
102
+ ):
94
103
  budget = SONNET_37_BUDGETS[reasoning_effort]
95
104
  api_params["thinking"] = {
96
105
  "type": "enabled",
@@ -144,14 +153,14 @@ class AnthropicAPI(VendorBase):
144
153
  def _hit_api_sync(
145
154
  self,
146
155
  model: str,
147
- messages: List[Dict[str, Any]],
148
- lm_config: Dict[str, Any],
156
+ messages: list[dict[str, Any]],
157
+ lm_config: dict[str, Any],
149
158
  use_ephemeral_cache_only: bool = False,
150
159
  reasoning_effort: str = "high",
151
- tools: Optional[List[BaseTool]] = None,
152
- **vendor_params: Dict[str, Any],
160
+ tools: list[BaseTool] | None = None,
161
+ **vendor_params: dict[str, Any],
153
162
  ) -> BaseLMResponse:
154
- assert lm_config.get("response_model", None) is None, (
163
+ assert lm_config.get("response_model") is None, (
155
164
  "response_model is not supported for standard calls"
156
165
  )
157
166
  used_cache_handler = get_cache_handler(use_ephemeral_cache_only=use_ephemeral_cache_only)
@@ -175,6 +184,7 @@ class AnthropicAPI(VendorBase):
175
184
  }
176
185
  with use_overrides_for_messages(messages):
177
186
  from synth_ai.lm.overrides import apply_tool_overrides
187
+
178
188
  api_params = apply_tool_overrides(api_params)
179
189
  api_params = apply_param_overrides(api_params)
180
190
 
@@ -238,12 +248,12 @@ class AnthropicAPI(VendorBase):
238
248
  async def _hit_api_async_structured_output(
239
249
  self,
240
250
  model: str,
241
- messages: List[Dict[str, Any]],
251
+ messages: list[dict[str, Any]],
242
252
  response_model: BaseModel,
243
253
  temperature: float,
244
254
  use_ephemeral_cache_only: bool = False,
245
255
  reasoning_effort: str = "high",
246
- **vendor_params: Dict[str, Any],
256
+ **vendor_params: dict[str, Any],
247
257
  ) -> BaseLMResponse:
248
258
  try:
249
259
  # First try with Anthropic
@@ -297,17 +307,16 @@ class AnthropicAPI(VendorBase):
297
307
  def _hit_api_sync_structured_output(
298
308
  self,
299
309
  model: str,
300
- messages: List[Dict[str, Any]],
310
+ messages: list[dict[str, Any]],
301
311
  response_model: BaseModel,
302
312
  temperature: float,
303
313
  use_ephemeral_cache_only: bool = False,
304
314
  reasoning_effort: str = "high",
305
- **vendor_params: Dict[str, Any],
315
+ **vendor_params: dict[str, Any],
306
316
  ) -> BaseLMResponse:
307
317
  try:
308
318
  # First try with Anthropic
309
319
  reasoning_effort = vendor_params.get("reasoning_effort", reasoning_effort)
310
- import time
311
320
 
312
321
  if model in CLAUDE_REASONING_MODELS:
313
322
  if reasoning_effort in ["high", "medium"]:
@@ -359,13 +368,13 @@ class AnthropicAPI(VendorBase):
359
368
 
360
369
  async def _process_call_async(
361
370
  self,
362
- messages: List[Dict[str, Any]],
371
+ messages: list[dict[str, Any]],
363
372
  model: str,
364
373
  response_model: BaseModel,
365
374
  api_call_method,
366
375
  temperature: float = 0.0,
367
376
  use_ephemeral_cache_only: bool = False,
368
- vendor_params: Dict[str, Any] = None,
377
+ vendor_params: dict[str, Any] = None,
369
378
  ) -> BaseModel:
370
379
  vendor_params = vendor_params or {}
371
380
  # Each vendor can filter parameters they support