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
@@ -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
@@ -2,22 +2,21 @@ import json
2
2
  import logging
3
3
  import os
4
4
  import warnings
5
- from typing import Any, Dict, List, Optional, Tuple, Type
5
+ from typing import Any
6
6
 
7
7
  import google.genai as genai
8
8
  from google.api_core.exceptions import ResourceExhausted
9
9
  from google.genai import types
10
+
10
11
  from synth_ai.lm.caching.initialize import get_cache_handler
11
- from synth_ai.lm.tools.base import BaseTool
12
- from synth_ai.lm.vendors.base import BaseLMResponse, VendorBase
13
12
  from synth_ai.lm.constants import (
14
- SPECIAL_BASE_TEMPS,
15
13
  GEMINI_REASONING_MODELS,
16
14
  GEMINI_THINKING_BUDGETS,
15
+ SPECIAL_BASE_TEMPS,
17
16
  )
17
+ from synth_ai.lm.tools.base import BaseTool
18
+ from synth_ai.lm.vendors.base import BaseLMResponse, VendorBase
18
19
  from synth_ai.lm.vendors.retries import BACKOFF_TOLERANCE, MAX_BACKOFF, backoff
19
- import logging
20
-
21
20
 
22
21
  ALIASES = {
23
22
  "gemini-2.5-flash": "gemini-2.5-flash-preview-04-17",
@@ -25,7 +24,7 @@ ALIASES = {
25
24
 
26
25
  logger = logging.getLogger(__name__)
27
26
  _CLIENT = None # Initialize lazily when needed
28
- GEMINI_EXCEPTIONS_TO_RETRY: Tuple[Type[Exception], ...] = (ResourceExhausted,)
27
+ GEMINI_EXCEPTIONS_TO_RETRY: tuple[type[Exception], ...] = (ResourceExhausted,)
29
28
  logging.getLogger("google.genai").setLevel(logging.ERROR)
30
29
  os.environ["GRPC_VERBOSITY"] = "ERROR"
31
30
  os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
@@ -49,11 +48,11 @@ def _get_client():
49
48
 
50
49
  class GeminiAPI(VendorBase):
51
50
  used_for_structured_outputs: bool = True
52
- exceptions_to_retry: Tuple[Type[Exception], ...] = GEMINI_EXCEPTIONS_TO_RETRY
51
+ exceptions_to_retry: tuple[type[Exception], ...] = GEMINI_EXCEPTIONS_TO_RETRY
53
52
 
54
53
  def __init__(
55
54
  self,
56
- exceptions_to_retry: Tuple[Type[Exception], ...] = GEMINI_EXCEPTIONS_TO_RETRY,
55
+ exceptions_to_retry: tuple[type[Exception], ...] = GEMINI_EXCEPTIONS_TO_RETRY,
57
56
  used_for_structured_outputs: bool = False,
58
57
  ):
59
58
  self.used_for_structured_outputs = used_for_structured_outputs
@@ -65,7 +64,7 @@ class GeminiAPI(VendorBase):
65
64
  return model_name
66
65
 
67
66
  @staticmethod
68
- def _msg_to_contents(messages: List[Dict[str, Any]]) -> List[types.Content]:
67
+ def _msg_to_contents(messages: list[dict[str, Any]]) -> list[types.Content]:
69
68
  # contents, sys_instr = [], None
70
69
  contents = []
71
70
  for m in messages:
@@ -82,16 +81,12 @@ class GeminiAPI(VendorBase):
82
81
  return contents
83
82
 
84
83
  @staticmethod
85
- def _tools_to_genai(tools: List[BaseTool]) -> List[types.Tool]:
84
+ def _tools_to_genai(tools: list[BaseTool]) -> list[types.Tool]:
86
85
  """Convert internal BaseTool → genai Tool."""
87
- out: List[types.Tool] = []
86
+ out: list[types.Tool] = []
88
87
  for t in tools:
89
88
  # Assume t.to_gemini_tool() now correctly returns a FunctionDeclaration
90
- # func_decl = t.to_gemini_tool()
91
- if isinstance(t, dict):
92
- func_decl = t
93
- else:
94
- func_decl = t.to_gemini_tool()
89
+ func_decl = t if isinstance(t, dict) else t.to_gemini_tool()
95
90
  if not isinstance(func_decl, types.FunctionDeclaration):
96
91
  # Or fetch schema parts if to_gemini_tool still returns dict
97
92
  # This depends on BaseTool.to_gemini_tool implementation
@@ -106,15 +101,15 @@ class GeminiAPI(VendorBase):
106
101
 
107
102
  async def _gen_content_async(
108
103
  self,
109
- messages: List[Dict],
104
+ messages: list[dict],
110
105
  temperature: float,
111
106
  model_name: str,
112
107
  reasoning_effort: str,
113
- tools: Optional[List[BaseTool]],
114
- lm_config: Optional[Dict[str, Any]],
115
- ) -> Tuple[str, Optional[List[Dict]]]:
108
+ tools: list[BaseTool] | None,
109
+ lm_config: dict[str, Any] | None,
110
+ ) -> tuple[str, list[dict] | None]:
116
111
  model_name = self.get_aliased_model_name(model_name)
117
- cfg_kwargs: Dict[str, Any] = {"temperature": temperature}
112
+ cfg_kwargs: dict[str, Any] = {"temperature": temperature}
118
113
  if model_name in GEMINI_REASONING_MODELS and reasoning_effort in GEMINI_THINKING_BUDGETS:
119
114
  cfg_kwargs["thinking_config"] = types.ThinkingConfig(
120
115
  thinking_budget=GEMINI_THINKING_BUDGETS[reasoning_effort]
@@ -141,15 +136,15 @@ class GeminiAPI(VendorBase):
141
136
 
142
137
  def _gen_content_sync(
143
138
  self,
144
- messages: List[Dict],
139
+ messages: list[dict],
145
140
  temperature: float,
146
141
  model_name: str,
147
142
  reasoning_effort: str,
148
- tools: Optional[List[BaseTool]],
149
- lm_config: Optional[Dict[str, Any]],
150
- ) -> Tuple[str, Optional[List[Dict]]]:
143
+ tools: list[BaseTool] | None,
144
+ lm_config: dict[str, Any] | None,
145
+ ) -> tuple[str, list[dict] | None]:
151
146
  model_name = self.get_aliased_model_name(model_name)
152
- cfg_kwargs: Dict[str, Any] = {"temperature": temperature}
147
+ cfg_kwargs: dict[str, Any] = {"temperature": temperature}
153
148
  if model_name in GEMINI_REASONING_MODELS and reasoning_effort in GEMINI_THINKING_BUDGETS:
154
149
  cfg_kwargs["thinking_config"] = types.ThinkingConfig(
155
150
  thinking_budget=GEMINI_THINKING_BUDGETS[reasoning_effort]
@@ -174,7 +169,7 @@ class GeminiAPI(VendorBase):
174
169
  return self._extract(resp)
175
170
 
176
171
  @staticmethod
177
- def _extract(response) -> Tuple[str, Optional[List[Dict]]]:
172
+ def _extract(response) -> tuple[str, list[dict] | None]:
178
173
  # Extract text, handling cases where it might be missing
179
174
  try:
180
175
  text = response.text
@@ -208,13 +203,13 @@ class GeminiAPI(VendorBase):
208
203
  async def _hit_api_async(
209
204
  self,
210
205
  model: str,
211
- messages: List[Dict[str, Any]],
212
- lm_config: Dict[str, Any],
206
+ messages: list[dict[str, Any]],
207
+ lm_config: dict[str, Any],
213
208
  use_ephemeral_cache_only: bool = False,
214
209
  reasoning_effort: str = "high",
215
- tools: Optional[List[BaseTool]] = None,
210
+ tools: list[BaseTool] | None = None,
216
211
  ) -> BaseLMResponse:
217
- assert lm_config.get("response_model", None) is None, (
212
+ assert lm_config.get("response_model") is None, (
218
213
  "response_model is not supported for standard calls"
219
214
  )
220
215
  used_cache_handler = get_cache_handler(use_ephemeral_cache_only)
@@ -257,13 +252,13 @@ class GeminiAPI(VendorBase):
257
252
  def _hit_api_sync(
258
253
  self,
259
254
  model: str,
260
- messages: List[Dict[str, Any]],
261
- lm_config: Dict[str, Any],
255
+ messages: list[dict[str, Any]],
256
+ lm_config: dict[str, Any],
262
257
  use_ephemeral_cache_only: bool = False,
263
258
  reasoning_effort: str = "high",
264
- tools: Optional[List[BaseTool]] = None,
259
+ tools: list[BaseTool] | None = None,
265
260
  ) -> BaseLMResponse:
266
- assert lm_config.get("response_model", None) is None, (
261
+ assert lm_config.get("response_model") is None, (
267
262
  "response_model is not supported for standard calls"
268
263
  )
269
264
  used_cache_handler = get_cache_handler(use_ephemeral_cache_only=use_ephemeral_cache_only)
@@ -1,30 +1,30 @@
1
1
  import json
2
2
  import os
3
- from typing import Any, Dict, List, Optional, Tuple, Type
3
+ from typing import Any
4
4
 
5
5
  import pydantic
6
6
  from mistralai import Mistral # use Mistral as both sync and async client
7
7
  from pydantic import BaseModel
8
8
 
9
9
  from synth_ai.lm.caching.initialize import get_cache_handler
10
+ from synth_ai.lm.constants import SPECIAL_BASE_TEMPS
10
11
  from synth_ai.lm.tools.base import BaseTool
11
12
  from synth_ai.lm.vendors.base import BaseLMResponse, VendorBase
12
- from synth_ai.lm.constants import SPECIAL_BASE_TEMPS
13
13
  from synth_ai.lm.vendors.core.openai_api import OpenAIStructuredOutputClient
14
14
 
15
15
  # Since the mistralai package doesn't expose an exceptions module,
16
16
  # we fallback to catching all Exceptions for retry.
17
- MISTRAL_EXCEPTIONS_TO_RETRY: Tuple[Type[Exception], ...] = (Exception,)
17
+ MISTRAL_EXCEPTIONS_TO_RETRY: tuple[type[Exception], ...] = (Exception,)
18
18
 
19
19
 
20
20
  class MistralAPI(VendorBase):
21
21
  used_for_structured_outputs: bool = True
22
- exceptions_to_retry: Tuple = MISTRAL_EXCEPTIONS_TO_RETRY
22
+ exceptions_to_retry: tuple = MISTRAL_EXCEPTIONS_TO_RETRY
23
23
  _openai_fallback: Any
24
24
 
25
25
  def __init__(
26
26
  self,
27
- exceptions_to_retry: Tuple[Type[Exception], ...] = MISTRAL_EXCEPTIONS_TO_RETRY,
27
+ exceptions_to_retry: tuple[type[Exception], ...] = MISTRAL_EXCEPTIONS_TO_RETRY,
28
28
  used_for_structured_outputs: bool = False,
29
29
  ):
30
30
  self.used_for_structured_outputs = used_for_structured_outputs
@@ -40,14 +40,14 @@ class MistralAPI(VendorBase):
40
40
  async def _hit_api_async(
41
41
  self,
42
42
  model: str,
43
- messages: List[Dict[str, Any]],
44
- lm_config: Dict[str, Any],
45
- response_model: Optional[BaseModel] = None,
43
+ messages: list[dict[str, Any]],
44
+ lm_config: dict[str, Any],
45
+ response_model: BaseModel | None = None,
46
46
  use_ephemeral_cache_only: bool = False,
47
47
  reasoning_effort: str = "high",
48
- tools: Optional[List[BaseTool]] = None,
48
+ tools: list[BaseTool] | None = None,
49
49
  ) -> BaseLMResponse:
50
- assert lm_config.get("response_model", None) is None, (
50
+ assert lm_config.get("response_model") is None, (
51
51
  "response_model is not supported for standard calls"
52
52
  )
53
53
  assert not (response_model and tools), "Cannot provide both response_model and tools"
@@ -63,7 +63,7 @@ class MistralAPI(VendorBase):
63
63
  ], f"Expected BaseLMResponse or str, got {type(cache_result)}"
64
64
  return (
65
65
  cache_result
66
- if type(cache_result) == BaseLMResponse
66
+ if isinstance(cache_result, BaseLMResponse)
67
67
  else BaseLMResponse(
68
68
  raw_response=cache_result, structured_output=None, tool_calls=None
69
69
  )
@@ -130,14 +130,14 @@ class MistralAPI(VendorBase):
130
130
  def _hit_api_sync(
131
131
  self,
132
132
  model: str,
133
- messages: List[Dict[str, Any]],
134
- lm_config: Dict[str, Any],
135
- response_model: Optional[BaseModel] = None,
133
+ messages: list[dict[str, Any]],
134
+ lm_config: dict[str, Any],
135
+ response_model: BaseModel | None = None,
136
136
  use_ephemeral_cache_only: bool = False,
137
137
  reasoning_effort: str = "high",
138
- tools: Optional[List[BaseTool]] = None,
138
+ tools: list[BaseTool] | None = None,
139
139
  ) -> BaseLMResponse:
140
- assert lm_config.get("response_model", None) is None, (
140
+ assert lm_config.get("response_model") is None, (
141
141
  "response_model is not supported for standard calls"
142
142
  )
143
143
  assert not (response_model and tools), "Cannot provide both response_model and tools"
@@ -154,7 +154,7 @@ class MistralAPI(VendorBase):
154
154
  ], f"Expected BaseLMResponse or str, got {type(cache_result)}"
155
155
  return (
156
156
  cache_result
157
- if type(cache_result) == BaseLMResponse
157
+ if isinstance(cache_result, BaseLMResponse)
158
158
  else BaseLMResponse(
159
159
  raw_response=cache_result, structured_output=None, tool_calls=None
160
160
  )
@@ -217,7 +217,7 @@ class MistralAPI(VendorBase):
217
217
  async def _hit_api_async_structured_output(
218
218
  self,
219
219
  model: str,
220
- messages: List[Dict[str, Any]],
220
+ messages: list[dict[str, Any]],
221
221
  response_model: BaseModel,
222
222
  temperature: float,
223
223
  use_ephemeral_cache_only: bool = False,
@@ -256,7 +256,7 @@ class MistralAPI(VendorBase):
256
256
  def _hit_api_sync_structured_output(
257
257
  self,
258
258
  model: str,
259
- messages: List[Dict[str, Any]],
259
+ messages: list[dict[str, Any]],
260
260
  response_model: BaseModel,
261
261
  temperature: float,
262
262
  use_ephemeral_cache_only: bool = False,
@@ -6,7 +6,8 @@ supporting both standard and structured output modes.
6
6
  """
7
7
 
8
8
  import json
9
- from typing import Any, Dict, List, Optional, Tuple, Type
9
+ import os
10
+ from typing import Any
10
11
 
11
12
  import openai
12
13
  import pydantic_core
@@ -15,13 +16,13 @@ import pydantic_core
15
16
  from pydantic import BaseModel
16
17
 
17
18
  from synth_ai.lm.caching.initialize import get_cache_handler
19
+ from synth_ai.lm.constants import OPENAI_REASONING_MODELS, SPECIAL_BASE_TEMPS
18
20
  from synth_ai.lm.tools.base import BaseTool
19
21
  from synth_ai.lm.vendors.base import BaseLMResponse
20
- from synth_ai.lm.constants import SPECIAL_BASE_TEMPS, OPENAI_REASONING_MODELS
21
22
  from synth_ai.lm.vendors.openai_standard import OpenAIStandard
22
23
 
23
24
  # Exceptions that should trigger retry logic for OpenAI API calls
24
- OPENAI_EXCEPTIONS_TO_RETRY: Tuple[Type[Exception], ...] = (
25
+ OPENAI_EXCEPTIONS_TO_RETRY: tuple[type[Exception], ...] = (
25
26
  pydantic_core._pydantic_core.ValidationError,
26
27
  openai.OpenAIError,
27
28
  openai.APIConnectionError,
@@ -36,33 +37,61 @@ OPENAI_EXCEPTIONS_TO_RETRY: Tuple[Type[Exception], ...] = (
36
37
  class OpenAIStructuredOutputClient(OpenAIStandard):
37
38
  """
38
39
  OpenAI client with support for structured outputs.
39
-
40
+
40
41
  This client extends the standard OpenAI client to support structured outputs
41
42
  using OpenAI's native structured output feature or response format parameter.
42
43
  """
44
+
43
45
  def __init__(self, synth_logging: bool = True):
44
- if synth_logging:
46
+ # Check if we should use Synth clients instead of OpenAI
47
+ openai_base = os.getenv("OPENAI_API_BASE", "")
48
+ use_synth = (openai_base.startswith("https://synth") or
49
+ openai_base.startswith("https://agent-learning") or
50
+ os.getenv("SYNTH_BASE_URL") or os.getenv("MODAL_BASE_URL"))
51
+
52
+ if use_synth:
53
+ # Use Synth clients for Synth endpoints
54
+ from synth_ai.lm.vendors.synth_client import AsyncSynthClient, SyncSynthClient
55
+ from synth_ai.lm.config import SynthConfig
56
+
57
+ # Create config from OPENAI_* environment variables if available
58
+ openai_base = os.getenv("OPENAI_API_BASE")
59
+ openai_key = os.getenv("OPENAI_API_KEY")
60
+
61
+ if openai_base and openai_key:
62
+ config = SynthConfig(base_url=openai_base, api_key=openai_key)
63
+ sync_client = SyncSynthClient(config)
64
+ async_client = AsyncSynthClient(config)
65
+ else:
66
+ # Fall back to default config loading
67
+ sync_client = SyncSynthClient()
68
+ async_client = AsyncSynthClient()
69
+ elif synth_logging:
45
70
  # print("Using synth logging - OpenAIStructuredOutputClient")
46
71
  from synth_ai.lm.provider_support.openai import AsyncOpenAI, OpenAI
72
+ sync_client = OpenAI()
73
+ async_client = AsyncOpenAI()
47
74
  else:
48
75
  # print("Not using synth logging - OpenAIStructuredOutputClient")
49
76
  from openai import AsyncOpenAI, OpenAI
77
+ sync_client = OpenAI()
78
+ async_client = AsyncOpenAI()
50
79
 
51
80
  super().__init__(
52
81
  used_for_structured_outputs=True,
53
82
  exceptions_to_retry=OPENAI_EXCEPTIONS_TO_RETRY,
54
- sync_client=OpenAI(),
55
- async_client=AsyncOpenAI(),
83
+ sync_client=sync_client,
84
+ async_client=async_client,
56
85
  )
57
86
 
58
87
  async def _hit_api_async_structured_output(
59
88
  self,
60
89
  model: str,
61
- messages: List[Dict[str, Any]],
90
+ messages: list[dict[str, Any]],
62
91
  response_model: BaseModel,
63
92
  temperature: float,
64
93
  use_ephemeral_cache_only: bool = False,
65
- tools: Optional[List[BaseTool]] = None,
94
+ tools: list[BaseTool] | None = None,
66
95
  reasoning_effort: str = "high",
67
96
  ) -> str:
68
97
  if tools:
@@ -81,7 +110,7 @@ class OpenAIStructuredOutputClient(OpenAIStandard):
81
110
  dict,
82
111
  BaseLMResponse,
83
112
  ], f"Expected dict or BaseLMResponse, got {type(cache_result)}"
84
- return cache_result["response"] if type(cache_result) == dict else cache_result
113
+ return cache_result["response"] if isinstance(cache_result, dict) else cache_result
85
114
  if model in OPENAI_REASONING_MODELS:
86
115
  output = await self.async_client.beta.chat.completions.parse(
87
116
  model=model,
@@ -109,11 +138,11 @@ class OpenAIStructuredOutputClient(OpenAIStandard):
109
138
  def _hit_api_sync_structured_output(
110
139
  self,
111
140
  model: str,
112
- messages: List[Dict[str, Any]],
141
+ messages: list[dict[str, Any]],
113
142
  response_model: BaseModel,
114
143
  temperature: float,
115
144
  use_ephemeral_cache_only: bool = False,
116
- tools: Optional[List[BaseTool]] = None,
145
+ tools: list[BaseTool] | None = None,
117
146
  reasoning_effort: str = "high",
118
147
  ) -> str:
119
148
  if tools:
@@ -130,7 +159,7 @@ class OpenAIStructuredOutputClient(OpenAIStandard):
130
159
  dict,
131
160
  BaseLMResponse,
132
161
  ], f"Expected dict or BaseLMResponse, got {type(cache_result)}"
133
- return cache_result["response"] if type(cache_result) == dict else cache_result
162
+ return cache_result["response"] if isinstance(cache_result, dict) else cache_result
134
163
  if model in OPENAI_REASONING_MODELS:
135
164
  output = self.sync_client.beta.chat.completions.parse(
136
165
  model=model,