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
@@ -1,24 +1,28 @@
1
- from typing import Any, Dict, List, Optional, Union
2
1
  import asyncio
2
+ import os
3
3
  import time
4
+ from typing import Any
4
5
 
6
+ import backoff
5
7
  import groq
6
8
  import openai
7
- import os
8
9
  import pydantic_core
9
10
  from pydantic import BaseModel
10
11
 
11
12
  from synth_ai.lm.caching.initialize import (
12
13
  get_cache_handler,
13
14
  )
15
+ from synth_ai.lm.constants import SPECIAL_BASE_TEMPS
16
+ from synth_ai.lm.injection import apply_injection
17
+ from synth_ai.lm.overrides import (
18
+ apply_param_overrides,
19
+ apply_tool_overrides,
20
+ use_overrides_for_messages,
21
+ )
14
22
  from synth_ai.lm.tools.base import BaseTool
15
23
  from synth_ai.lm.vendors.base import BaseLMResponse, VendorBase
16
- from synth_ai.lm.injection import apply_injection
17
- from synth_ai.lm.overrides import use_overrides_for_messages, apply_param_overrides, apply_tool_overrides
18
- from synth_ai.lm.constants import SPECIAL_BASE_TEMPS
19
- from synth_ai.lm.vendors.retries import MAX_BACKOFF
20
24
  from synth_ai.lm.vendors.openai_standard_responses import OpenAIResponsesAPIMixin
21
- import backoff
25
+ from synth_ai.lm.vendors.retries import MAX_BACKOFF
22
26
 
23
27
  DEFAULT_EXCEPTIONS_TO_RETRY = (
24
28
  pydantic_core._pydantic_core.ValidationError,
@@ -30,14 +34,14 @@ DEFAULT_EXCEPTIONS_TO_RETRY = (
30
34
  )
31
35
 
32
36
 
33
- def special_orion_transform(model: str, messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
37
+ def special_orion_transform(model: str, messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
34
38
  """
35
39
  Transform messages for O1 series models which don't support system messages.
36
-
40
+
37
41
  Args:
38
42
  model: Model name to check
39
43
  messages: Original messages list
40
-
44
+
41
45
  Returns:
42
46
  Transformed messages list with system content merged into user message
43
47
  """
@@ -59,18 +63,19 @@ def _silent_backoff_handler(_details):
59
63
  class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
60
64
  """
61
65
  Standard OpenAI-compatible vendor implementation.
62
-
66
+
63
67
  This class provides a standard implementation for OpenAI-compatible APIs,
64
68
  including proper retry logic, caching, and support for various model features.
65
-
69
+
66
70
  Attributes:
67
71
  used_for_structured_outputs: Whether this client supports structured outputs
68
72
  exceptions_to_retry: List of exceptions that trigger automatic retries
69
73
  sync_client: Synchronous API client
70
74
  async_client: Asynchronous API client
71
75
  """
76
+
72
77
  used_for_structured_outputs: bool = True
73
- exceptions_to_retry: List = DEFAULT_EXCEPTIONS_TO_RETRY
78
+ exceptions_to_retry: list = DEFAULT_EXCEPTIONS_TO_RETRY
74
79
  sync_client: Any
75
80
  async_client: Any
76
81
 
@@ -78,19 +83,20 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
78
83
  self,
79
84
  sync_client: Any,
80
85
  async_client: Any,
81
- exceptions_to_retry: List[Exception] = DEFAULT_EXCEPTIONS_TO_RETRY,
86
+ exceptions_to_retry: list[Exception] = DEFAULT_EXCEPTIONS_TO_RETRY,
82
87
  used_for_structured_outputs: bool = False,
83
88
  ):
84
89
  self.sync_client = sync_client
85
90
  self.async_client = async_client
86
91
  self.used_for_structured_outputs = used_for_structured_outputs
87
92
  self.exceptions_to_retry = exceptions_to_retry
88
-
93
+
89
94
  # Initialize Harmony support for OSS models
90
95
  self.harmony_available = False
91
96
  self.harmony_enc = None
92
97
  try:
93
- from openai_harmony import load_harmony_encoding, HarmonyEncodingName
98
+ from openai_harmony import HarmonyEncodingName, load_harmony_encoding
99
+
94
100
  self.harmony_available = True
95
101
  self.harmony_enc = load_harmony_encoding(HarmonyEncodingName.HARMONY_GPT_OSS)
96
102
  except ImportError:
@@ -106,24 +112,24 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
106
112
  async def _hit_api_async(
107
113
  self,
108
114
  model: str,
109
- messages: List[Dict[str, Any]],
110
- lm_config: Dict[str, Any],
115
+ messages: list[dict[str, Any]],
116
+ lm_config: dict[str, Any],
111
117
  use_ephemeral_cache_only: bool = False,
112
118
  reasoning_effort: str = "high",
113
- tools: Optional[List[BaseTool]] = None,
119
+ tools: list[BaseTool] | None = None,
114
120
  ) -> BaseLMResponse:
115
- assert lm_config.get("response_model", None) is None, (
121
+ assert lm_config.get("response_model") is None, (
116
122
  "response_model is not supported for standard calls"
117
123
  )
118
-
119
- DEBUG = os.getenv("SYNTH_OPENAI_DEBUG") == "1"
120
- if DEBUG:
121
- print(f"🔍 OPENAI DEBUG: _hit_api_async called with:")
124
+
125
+ debug = os.getenv("SYNTH_OPENAI_DEBUG") == "1"
126
+ if debug:
127
+ print("🔍 OPENAI DEBUG: _hit_api_async called with:")
122
128
  print(f" Model: {model}")
123
- print(f" Messages: {len(messages)} messages")
129
+ print(f" Messages: {len(messages)} messages")
124
130
  print(f" Tools: {len(tools) if tools else 0} tools")
125
131
  print(f" LM config: {lm_config}")
126
-
132
+
127
133
  messages = special_orion_transform(model, messages)
128
134
  # Apply context-scoped overrides and prompt injection just before building API params
129
135
  with use_overrides_for_messages(messages):
@@ -133,15 +139,14 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
133
139
  cache_result = used_cache_handler.hit_managed_cache(
134
140
  model, messages, lm_config=lm_config, tools=tools
135
141
  )
136
- if cache_result:
137
- if DEBUG:
138
- print(f"🔍 OPENAI DEBUG: Cache hit! Returning cached result")
139
- print(f" Cache result type: {type(cache_result)}")
140
- print(f"🔍 OPENAI DEBUG: DISABLING CACHE FOR DEBUGGING - forcing API call")
141
- # return cache_result # Commented out to force API call
142
-
143
- if DEBUG:
144
- print(f"🔍 OPENAI DEBUG: Cache miss, making actual API call")
142
+ if cache_result and debug:
143
+ print("🔍 OPENAI DEBUG: Cache hit! Returning cached result")
144
+ print(f" Cache result type: {type(cache_result)}")
145
+ print("🔍 OPENAI DEBUG: DISABLING CACHE FOR DEBUGGING - forcing API call")
146
+ # return cache_result # Commented out intentionally when debug is on
147
+
148
+ if debug:
149
+ print("🔍 OPENAI DEBUG: Cache miss, making actual API call")
145
150
 
146
151
  # Common API call params
147
152
  api_params = {
@@ -202,7 +207,22 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
202
207
  api_params = apply_tool_overrides(api_params)
203
208
  api_params = apply_param_overrides(api_params)
204
209
 
205
- # Forward Qwen3 chat template kwargs via extra_body when requested
210
+ # Thinking controls: route via extra_body.chat_template_kwargs for compatibility
211
+ thinking_mode_val = lm_config.get("thinking_mode")
212
+ thinking_budget_val = lm_config.get("thinking_budget")
213
+ if thinking_mode_val is not None or thinking_budget_val is not None:
214
+ api_params["extra_body"] = api_params.get("extra_body", {})
215
+ ctk = api_params["extra_body"].get("chat_template_kwargs", {})
216
+ if thinking_mode_val is not None:
217
+ ctk["thinking_mode"] = thinking_mode_val
218
+ if thinking_budget_val is not None:
219
+ try:
220
+ ctk["thinking_budget"] = int(thinking_budget_val)
221
+ except Exception:
222
+ ctk["thinking_budget"] = thinking_budget_val
223
+ api_params["extra_body"]["chat_template_kwargs"] = ctk
224
+
225
+ # Backward-compatible: forward legacy enable_thinking only via extra_body for callers still using it
206
226
  if lm_config.get("enable_thinking") is not None:
207
227
  api_params["extra_body"] = api_params.get("extra_body", {})
208
228
  ctk = api_params["extra_body"].get("chat_template_kwargs", {})
@@ -211,8 +231,11 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
211
231
  # Forward arbitrary extra_body from lm_config if provided (merge)
212
232
  if lm_config.get("extra_body") is not None:
213
233
  # Shallow-merge top-level keys; nested keys (like chat_template_kwargs) should be provided whole
214
- api_params["extra_body"] = {**api_params.get("extra_body", {}), **(lm_config.get("extra_body") or {})}
215
- # Forward Qwen3 chat template kwargs via extra_body when requested
234
+ api_params["extra_body"] = {
235
+ **api_params.get("extra_body", {}),
236
+ **(lm_config.get("extra_body") or {}),
237
+ }
238
+ # Ensure legacy extra_body flag remains merged (do not override top-level fields)
216
239
  if lm_config.get("enable_thinking") is not None:
217
240
  api_params["extra_body"] = api_params.get("extra_body", {})
218
241
  ctk = api_params["extra_body"].get("chat_template_kwargs", {})
@@ -233,9 +256,7 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
233
256
  except Exception:
234
257
  base_url_str = ""
235
258
 
236
- is_external_provider = (
237
- "openai.com" in base_url_str or "api.groq.com" in base_url_str
238
- )
259
+ is_external_provider = "openai.com" in base_url_str or "api.groq.com" in base_url_str
239
260
 
240
261
  if is_external_provider:
241
262
  # Remove extra_body entirely; this is Synth-specific plumbing
@@ -257,18 +278,18 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
257
278
  api_params.pop("temperature", None)
258
279
 
259
280
  # Call API with better auth error reporting
260
- #try:
261
- if DEBUG:
262
- print(f"🔍 OPENAI DEBUG: Making request with params:")
281
+ # try:
282
+ if debug:
283
+ print("🔍 OPENAI DEBUG: Making request with params:")
263
284
  print(f" Model: {api_params.get('model')}")
264
285
  print(f" Messages: {len(api_params.get('messages', []))} messages")
265
286
  print(f" Tools: {len(api_params.get('tools', []))} tools")
266
287
  print(f" Max tokens: {api_params.get('max_tokens', 'NOT SET')}")
267
288
  print(f" Temperature: {api_params.get('temperature', 'NOT SET')}")
268
- if 'tools' in api_params:
289
+ if "tools" in api_params:
269
290
  print(f" First tool: {api_params['tools'][0]}")
270
291
  print(f" FULL API PARAMS: {api_params}")
271
-
292
+
272
293
  # Quiet targeted retry for OpenAI 400 tool_use_failed during tool-calling
273
294
  try:
274
295
  max_attempts_for_tool_use = int(os.getenv("SYNTH_TOOL_USE_RETRIES", "5"))
@@ -294,7 +315,9 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
294
315
  err_obj = body.get("error") if isinstance(body.get("error"), dict) else {}
295
316
  code_val = err_obj.get("code")
296
317
  msg_val = err_obj.get("message")
297
- if code_val == "tool_use_failed" or (isinstance(msg_val, str) and "Failed to call a function" in msg_val):
318
+ if code_val == "tool_use_failed" or (
319
+ isinstance(msg_val, str) and "Failed to call a function" in msg_val
320
+ ):
298
321
  should_retry = True
299
322
  except Exception:
300
323
  pass
@@ -308,7 +331,10 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
308
331
  err_obj = j.get("error") if isinstance(j.get("error"), dict) else {}
309
332
  code_val = err_obj.get("code")
310
333
  msg_val = err_obj.get("message")
311
- if code_val == "tool_use_failed" or (isinstance(msg_val, str) and "Failed to call a function" in msg_val):
334
+ if code_val == "tool_use_failed" or (
335
+ isinstance(msg_val, str)
336
+ and "Failed to call a function" in msg_val
337
+ ):
312
338
  should_retry = True
313
339
  except Exception:
314
340
  pass
@@ -323,33 +349,37 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
323
349
  attempt_index += 1
324
350
  continue
325
351
  raise
326
-
327
- if DEBUG:
328
- print(f"🔍 OPENAI DEBUG: Response received:")
352
+
353
+ if debug:
354
+ print("🔍 OPENAI DEBUG: Response received:")
329
355
  print(f" Type: {type(output)}")
330
356
  print(f" Choices: {len(output.choices) if hasattr(output, 'choices') else 'N/A'}")
331
- if hasattr(output, 'choices') and output.choices:
357
+ if hasattr(output, "choices") and output.choices:
332
358
  choice = output.choices[0]
333
359
  print(f" Choice type: {type(choice)}")
334
- if hasattr(choice, 'message'):
360
+ if hasattr(choice, "message"):
335
361
  message = choice.message
336
362
  print(f" Message type: {type(message)}")
337
363
  print(f" Has tool_calls: {hasattr(message, 'tool_calls')}")
338
- if hasattr(message, 'tool_calls'):
364
+ if hasattr(message, "tool_calls"):
339
365
  print(f" Tool calls: {message.tool_calls}")
340
- print(f" Content: {message.content[:200] if hasattr(message, 'content') and message.content else 'None'}...")
366
+ print(
367
+ f" Content: {message.content[:200] if hasattr(message, 'content') and message.content else 'None'}..."
368
+ )
341
369
  # Show finish_reason and usage if available
342
370
  try:
343
371
  print(f" finish_reason: {getattr(choice, 'finish_reason', None)}")
344
- usage = getattr(output, 'usage', None)
372
+ usage = getattr(output, "usage", None)
345
373
  if usage:
346
- print(f" usage: prompt_tokens={getattr(usage, 'prompt_tokens', None)}, completion_tokens={getattr(usage, 'completion_tokens', None)}, total_tokens={getattr(usage, 'total_tokens', None)}")
374
+ print(
375
+ f" usage: prompt_tokens={getattr(usage, 'prompt_tokens', None)}, completion_tokens={getattr(usage, 'completion_tokens', None)}, total_tokens={getattr(usage, 'total_tokens', None)}"
376
+ )
347
377
  except Exception:
348
378
  pass
349
-
350
- if DEBUG:
351
- print(f"🔍 OPENAI DEBUG: FULL RAW RESPONSE:")
352
- if hasattr(output.choices[0].message, 'content') and output.choices[0].message.content:
379
+
380
+ if debug:
381
+ print("🔍 OPENAI DEBUG: FULL RAW RESPONSE:")
382
+ if hasattr(output.choices[0].message, "content") and output.choices[0].message.content:
353
383
  print(f" FULL CONTENT:\n{output.choices[0].message.content}")
354
384
  print(f" Raw choice: {choice}")
355
385
  print(f" Raw message: {message}")
@@ -372,30 +402,46 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
372
402
  # raise
373
403
  message = output.choices[0].message
374
404
 
375
- # Convert tool calls to dict format
405
+ # Convert tool calls to dict format, preferring dict-shaped entries first
376
406
  tool_calls = None
377
407
  if message.tool_calls:
378
- tool_calls = [
379
- {
380
- "id": tc.id,
381
- "type": tc.type,
382
- "function": {
383
- "name": tc.function.name,
384
- "arguments": tc.function.arguments,
385
- },
386
- }
387
- for tc in message.tool_calls
388
- ]
408
+ converted: list[dict] = []
409
+ for tc in message.tool_calls:
410
+ if isinstance(tc, dict):
411
+ fn = tc.get("function") or {}
412
+ converted.append(
413
+ {
414
+ "id": tc.get("id"),
415
+ "type": tc.get("type", "function"),
416
+ "function": {
417
+ "name": fn.get("name") or tc.get("name"),
418
+ "arguments": fn.get("arguments") or tc.get("arguments"),
419
+ },
420
+ }
421
+ )
422
+ else:
423
+ # SDK object path
424
+ converted.append(
425
+ {
426
+ "id": getattr(tc, "id", None),
427
+ "type": getattr(tc, "type", "function"),
428
+ "function": {
429
+ "name": getattr(getattr(tc, "function", None), "name", None),
430
+ "arguments": getattr(getattr(tc, "function", None), "arguments", None),
431
+ },
432
+ }
433
+ )
434
+ tool_calls = converted or None
389
435
 
390
436
  # Attach basic usage if available
391
437
  usage_dict = None
392
438
  try:
393
- usage_obj = getattr(output, 'usage', None)
439
+ usage_obj = getattr(output, "usage", None)
394
440
  if usage_obj is not None:
395
441
  usage_dict = {
396
- "prompt_tokens": getattr(usage_obj, 'prompt_tokens', None),
397
- "completion_tokens": getattr(usage_obj, 'completion_tokens', None),
398
- "total_tokens": getattr(usage_obj, 'total_tokens', None),
442
+ "prompt_tokens": getattr(usage_obj, "prompt_tokens", None),
443
+ "completion_tokens": getattr(usage_obj, "completion_tokens", None),
444
+ "total_tokens": getattr(usage_obj, "total_tokens", None),
399
445
  }
400
446
  except Exception:
401
447
  usage_dict = None
@@ -422,13 +468,13 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
422
468
  def _hit_api_sync(
423
469
  self,
424
470
  model: str,
425
- messages: List[Dict[str, Any]],
426
- lm_config: Dict[str, Any],
471
+ messages: list[dict[str, Any]],
472
+ lm_config: dict[str, Any],
427
473
  use_ephemeral_cache_only: bool = False,
428
474
  reasoning_effort: str = "high",
429
- tools: Optional[List[BaseTool]] = None,
475
+ tools: list[BaseTool] | None = None,
430
476
  ) -> BaseLMResponse:
431
- assert lm_config.get("response_model", None) is None, (
477
+ assert lm_config.get("response_model") is None, (
432
478
  "response_model is not supported for standard calls"
433
479
  )
434
480
  messages = special_orion_transform(model, messages)
@@ -441,8 +487,8 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
441
487
  model, messages, lm_config=lm_config, tools=tools
442
488
  )
443
489
  # During pytest runs, bypass returning cache to allow tests to inspect outgoing params
444
- IN_PYTEST = os.getenv("PYTEST_CURRENT_TEST") is not None
445
- if cache_result and not IN_PYTEST:
490
+ in_pytest = os.getenv("PYTEST_CURRENT_TEST") is not None
491
+ if cache_result and not in_pytest:
446
492
  return cache_result
447
493
 
448
494
  # Common API call params
@@ -514,7 +560,9 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
514
560
  err_obj = body.get("error") if isinstance(body.get("error"), dict) else {}
515
561
  code_val = err_obj.get("code")
516
562
  msg_val = err_obj.get("message")
517
- if code_val == "tool_use_failed" or (isinstance(msg_val, str) and "Failed to call a function" in msg_val):
563
+ if code_val == "tool_use_failed" or (
564
+ isinstance(msg_val, str) and "Failed to call a function" in msg_val
565
+ ):
518
566
  should_retry = True
519
567
  except Exception:
520
568
  pass
@@ -527,7 +575,10 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
527
575
  err_obj = j.get("error") if isinstance(j.get("error"), dict) else {}
528
576
  code_val = err_obj.get("code")
529
577
  msg_val = err_obj.get("message")
530
- if code_val == "tool_use_failed" or (isinstance(msg_val, str) and "Failed to call a function" in msg_val):
578
+ if code_val == "tool_use_failed" or (
579
+ isinstance(msg_val, str)
580
+ and "Failed to call a function" in msg_val
581
+ ):
531
582
  should_retry = True
532
583
  except Exception:
533
584
  pass
@@ -542,13 +593,17 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
542
593
  continue
543
594
  raise
544
595
  message = output.choices[0].message
545
- DEBUG = os.getenv("SYNTH_OPENAI_DEBUG") == "1"
546
- if DEBUG:
596
+ debug_sync = os.getenv("SYNTH_OPENAI_DEBUG") == "1"
597
+ if debug_sync:
547
598
  try:
548
- print(f"🔍 OPENAI DEBUG (sync): finish_reason={getattr(output.choices[0], 'finish_reason', None)}")
549
- usage = getattr(output, 'usage', None)
599
+ print(
600
+ f"🔍 OPENAI DEBUG (sync): finish_reason={getattr(output.choices[0], 'finish_reason', None)}"
601
+ )
602
+ usage = getattr(output, "usage", None)
550
603
  if usage:
551
- print(f"🔍 OPENAI DEBUG (sync): usage prompt_tokens={getattr(usage, 'prompt_tokens', None)}, completion_tokens={getattr(usage, 'completion_tokens', None)}, total_tokens={getattr(usage, 'total_tokens', None)}")
604
+ print(
605
+ f"🔍 OPENAI DEBUG (sync): usage prompt_tokens={getattr(usage, 'prompt_tokens', None)}, completion_tokens={getattr(usage, 'completion_tokens', None)}, total_tokens={getattr(usage, 'total_tokens', None)}"
606
+ )
552
607
  except Exception:
553
608
  pass
554
609
 
@@ -570,12 +625,12 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
570
625
  # Attach basic usage if available
571
626
  usage_dict = None
572
627
  try:
573
- usage_obj = getattr(output, 'usage', None)
628
+ usage_obj = getattr(output, "usage", None)
574
629
  if usage_obj is not None:
575
630
  usage_dict = {
576
- "prompt_tokens": getattr(usage_obj, 'prompt_tokens', None),
577
- "completion_tokens": getattr(usage_obj, 'completion_tokens', None),
578
- "total_tokens": getattr(usage_obj, 'total_tokens', None),
631
+ "prompt_tokens": getattr(usage_obj, "prompt_tokens", None),
632
+ "completion_tokens": getattr(usage_obj, "completion_tokens", None),
633
+ "total_tokens": getattr(usage_obj, "total_tokens", None),
579
634
  }
580
635
  except Exception:
581
636
  usage_dict = None
@@ -595,12 +650,12 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
595
650
  async def _hit_api_async_structured_output(
596
651
  self,
597
652
  model: str,
598
- messages: List[Dict[str, Any]],
653
+ messages: list[dict[str, Any]],
599
654
  response_model: BaseModel,
600
655
  temperature: float,
601
656
  use_ephemeral_cache_only: bool = False,
602
657
  reasoning_effort: str = "high",
603
- tools: Optional[List[BaseTool]] = None,
658
+ tools: list[BaseTool] | None = None,
604
659
  ) -> BaseLMResponse:
605
660
  lm_config = {
606
661
  "temperature": temperature,
@@ -608,7 +663,7 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
608
663
  "reasoning_effort": reasoning_effort,
609
664
  }
610
665
  used_cache_handler = get_cache_handler(use_ephemeral_cache_only)
611
- cache_result: Union[BaseLMResponse, None] = used_cache_handler.hit_managed_cache(
666
+ cache_result: BaseLMResponse | None = used_cache_handler.hit_managed_cache(
612
667
  model, messages, lm_config=lm_config, tools=tools
613
668
  )
614
669
  if cache_result is not None:
@@ -654,12 +709,12 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
654
709
  def _hit_api_sync_structured_output(
655
710
  self,
656
711
  model: str,
657
- messages: List[Dict[str, Any]],
712
+ messages: list[dict[str, Any]],
658
713
  response_model: BaseModel,
659
714
  temperature: float,
660
715
  use_ephemeral_cache_only: bool = False,
661
716
  reasoning_effort: str = "high",
662
- tools: Optional[List[BaseTool]] = None,
717
+ tools: list[BaseTool] | None = None,
663
718
  ) -> BaseLMResponse:
664
719
  lm_config = {
665
720
  "temperature": temperature,
@@ -667,7 +722,7 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
667
722
  "reasoning_effort": reasoning_effort,
668
723
  }
669
724
  used_cache_handler = get_cache_handler(use_ephemeral_cache_only)
670
- cache_result: Union[BaseLMResponse, None] = used_cache_handler.hit_managed_cache(
725
+ cache_result: BaseLMResponse | None = used_cache_handler.hit_managed_cache(
671
726
  model, messages, lm_config=lm_config, tools=tools
672
727
  )
673
728
  if cache_result is not None:
@@ -701,7 +756,9 @@ class OpenAIStandard(VendorBase, OpenAIResponsesAPIMixin):
701
756
  base_url_str_sync = str(base_url_obj) if base_url_obj is not None else ""
702
757
  except Exception:
703
758
  base_url_str_sync = ""
704
- if ("openai.com" in base_url_str_sync or "api.groq.com" in base_url_str_sync) and model.startswith("gpt-5"):
759
+ if (
760
+ "openai.com" in base_url_str_sync or "api.groq.com" in base_url_str_sync
761
+ ) and model.startswith("gpt-5"):
705
762
  if "max_tokens" in api_params:
706
763
  api_params["max_completion_tokens"] = api_params.pop("max_tokens")
707
764
  if "temperature" in api_params: