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
@@ -5,14 +5,11 @@ This module contains the Responses API and Harmony encoding methods
5
5
  that extend the OpenAIStandard class functionality.
6
6
  """
7
7
 
8
- from typing import Any, Dict, List, Optional
9
8
  import uuid
10
- from pydantic import BaseModel
9
+ from typing import Any
11
10
 
12
11
  from synth_ai.lm.tools.base import BaseTool
13
12
  from synth_ai.lm.vendors.base import BaseLMResponse
14
- from synth_ai.lm.vendors.retries import MAX_BACKOFF
15
- import backoff
16
13
 
17
14
 
18
15
  def _silent_backoff_handler(_details):
@@ -27,23 +24,23 @@ DEFAULT_EXCEPTIONS_TO_RETRY = (
27
24
 
28
25
  class OpenAIResponsesAPIMixin:
29
26
  """Mixin class providing Responses API functionality for OpenAI vendors."""
30
-
27
+
31
28
  async def _hit_api_async_responses(
32
29
  self,
33
30
  model: str,
34
- messages: List[Dict[str, Any]],
35
- lm_config: Dict[str, Any],
36
- previous_response_id: Optional[str] = None,
31
+ messages: list[dict[str, Any]],
32
+ lm_config: dict[str, Any],
33
+ previous_response_id: str | None = None,
37
34
  use_ephemeral_cache_only: bool = False,
38
- tools: Optional[List[BaseTool]] = None,
35
+ tools: list[BaseTool] | None = None,
39
36
  ) -> BaseLMResponse:
40
37
  """Use OpenAI Responses API for supported models."""
41
-
38
+
42
39
  print(f"🔍 RESPONSES API: Called for model {model}")
43
40
  print(f"🔍 RESPONSES API: previous_response_id = {previous_response_id}")
44
-
41
+
45
42
  # Check if the client has responses attribute
46
- if not hasattr(self.async_client, 'responses'):
43
+ if not hasattr(self.async_client, "responses"):
47
44
  print("🔍 RESPONSES API: Client doesn't have responses attribute, using fallback")
48
45
  # Fallback - use chat completions with simulated response_id
49
46
  response = await self._hit_api_async(
@@ -53,21 +50,22 @@ class OpenAIResponsesAPIMixin:
53
50
  use_ephemeral_cache_only=use_ephemeral_cache_only,
54
51
  tools=tools,
55
52
  )
56
-
53
+
57
54
  # Add Responses API fields
58
55
  if not response.response_id:
59
56
  import uuid
57
+
60
58
  response.response_id = str(uuid.uuid4())
61
59
  response.api_type = "responses"
62
60
  return response
63
-
61
+
64
62
  # Use the official Responses API
65
63
  try:
66
64
  # Common API call params for Responses API
67
65
  api_params = {
68
66
  "model": model,
69
67
  }
70
-
68
+
71
69
  # For Responses API, we use 'input' parameter
72
70
  if previous_response_id:
73
71
  # Continue existing thread
@@ -92,35 +90,37 @@ class OpenAIResponsesAPIMixin:
92
90
  elif role == "assistant":
93
91
  input_parts.append(f"Assistant: {content}")
94
92
  api_params["input"] = "\n".join(input_parts)
95
-
93
+
96
94
  # Add tools if provided
97
95
  if tools and all(isinstance(tool, BaseTool) for tool in tools):
98
96
  api_params["tools"] = [tool.to_openai_tool() for tool in tools]
99
97
  elif tools:
100
98
  api_params["tools"] = tools
101
-
99
+
102
100
  # Add other parameters from lm_config if needed
103
101
  if "max_tokens" in lm_config:
104
102
  api_params["max_tokens"] = lm_config["max_tokens"]
105
-
103
+
106
104
  print(f"🔍 RESPONSES API: Calling with params: {list(api_params.keys())}")
107
-
105
+
108
106
  # Call the Responses API
109
107
  response = await self.async_client.responses.create(**api_params)
110
-
108
+
111
109
  print(f"🔍 RESPONSES API: Response received, type: {type(response)}")
112
-
110
+
113
111
  # Extract fields from response
114
- output_text = getattr(response, 'output_text', getattr(response, 'content', ''))
115
- reasoning_obj = getattr(response, 'reasoning', None)
116
- response_id = getattr(response, 'id', None)
117
-
112
+ output_text = getattr(response, "output_text", getattr(response, "content", ""))
113
+ reasoning_obj = getattr(response, "reasoning", None)
114
+ response_id = getattr(response, "id", None)
115
+
118
116
  # Debug reasoning type (only first time)
119
- if reasoning_obj and not hasattr(self, '_reasoning_logged'):
117
+ if reasoning_obj and not hasattr(self, "_reasoning_logged"):
120
118
  print(f"🔍 RESPONSES API: Reasoning type: {type(reasoning_obj)}")
121
- print(f"🔍 RESPONSES API: Reasoning attributes: {[x for x in dir(reasoning_obj) if not x.startswith('_')]}")
119
+ print(
120
+ f"🔍 RESPONSES API: Reasoning attributes: {[x for x in dir(reasoning_obj) if not x.startswith('_')]}"
121
+ )
122
122
  self._reasoning_logged = True
123
-
123
+
124
124
  # Handle reasoning - it might be an object or a string
125
125
  reasoning = None
126
126
  if reasoning_obj:
@@ -130,22 +130,23 @@ class OpenAIResponsesAPIMixin:
130
130
  else:
131
131
  # OpenAI returns a Reasoning object
132
132
  # Try to get summary first, but preserve entire object if no summary
133
- if hasattr(reasoning_obj, 'summary') and reasoning_obj.summary:
133
+ if hasattr(reasoning_obj, "summary") and reasoning_obj.summary:
134
134
  reasoning = reasoning_obj.summary
135
135
  else:
136
136
  # Preserve the full object structure as JSON
137
137
  # This includes effort level and any other fields
138
- if hasattr(reasoning_obj, 'model_dump_json'):
138
+ if hasattr(reasoning_obj, "model_dump_json"):
139
139
  reasoning = reasoning_obj.model_dump_json()
140
- elif hasattr(reasoning_obj, 'to_dict'):
140
+ elif hasattr(reasoning_obj, "to_dict"):
141
141
  import json
142
+
142
143
  reasoning = json.dumps(reasoning_obj.to_dict())
143
144
  else:
144
145
  reasoning = str(reasoning_obj)
145
-
146
+
146
147
  # Handle tool calls if present
147
148
  tool_calls = None
148
- if hasattr(response, 'tool_calls') and response.tool_calls:
149
+ if hasattr(response, "tool_calls") and response.tool_calls:
149
150
  tool_calls = [
150
151
  {
151
152
  "id": tc.id,
@@ -157,9 +158,9 @@ class OpenAIResponsesAPIMixin:
157
158
  }
158
159
  for tc in response.tool_calls
159
160
  ]
160
-
161
+
161
162
  print(f"🔍 RESPONSES API: Extracted response_id = {response_id}")
162
-
163
+
163
164
  return BaseLMResponse(
164
165
  raw_response=output_text,
165
166
  response_id=response_id,
@@ -167,7 +168,7 @@ class OpenAIResponsesAPIMixin:
167
168
  api_type="responses",
168
169
  tool_calls=tool_calls,
169
170
  )
170
-
171
+
171
172
  except (AttributeError, Exception) as e:
172
173
  print(f"🔍 RESPONSES API: Error calling Responses API: {e}")
173
174
  # No fallback - raise the error
@@ -176,68 +177,80 @@ class OpenAIResponsesAPIMixin:
176
177
  async def _hit_api_async_harmony(
177
178
  self,
178
179
  model: str,
179
- messages: List[Dict[str, Any]],
180
- lm_config: Dict[str, Any],
181
- previous_response_id: Optional[str] = None,
180
+ messages: list[dict[str, Any]],
181
+ lm_config: dict[str, Any],
182
+ previous_response_id: str | None = None,
182
183
  use_ephemeral_cache_only: bool = False,
183
- tools: Optional[List[BaseTool]] = None,
184
+ tools: list[BaseTool] | None = None,
184
185
  ) -> BaseLMResponse:
185
186
  """Use Harmony encoding for OSS-GPT models."""
186
187
  if not self.harmony_available:
187
- raise ImportError("openai-harmony package required for OSS-GPT models. Install with: pip install openai-harmony")
188
-
189
- from openai_harmony import Message, Role, Conversation
190
-
188
+ raise ImportError(
189
+ "openai-harmony package required for OSS-GPT models. Install with: pip install openai-harmony"
190
+ )
191
+
192
+ from openai_harmony import Conversation, Message, Role
193
+
191
194
  # Convert messages to Harmony format
192
195
  harmony_messages = []
193
196
  for msg in messages:
194
- role = Role.SYSTEM if msg["role"] == "system" else (
195
- Role.USER if msg["role"] == "user" else Role.ASSISTANT
197
+ role = (
198
+ Role.SYSTEM
199
+ if msg["role"] == "system"
200
+ else (Role.USER if msg["role"] == "user" else Role.ASSISTANT)
196
201
  )
197
202
  content = msg["content"]
198
203
  # Handle multimodal content
199
204
  if isinstance(content, list):
200
205
  # Extract text content for now
201
- text_parts = [part.get("text", "") for part in content if part.get("type") == "text"]
206
+ text_parts = [
207
+ part.get("text", "") for part in content if part.get("type") == "text"
208
+ ]
202
209
  content = " ".join(text_parts)
203
210
  harmony_messages.append(Message.from_role_and_content(role, content))
204
-
211
+
205
212
  conv = Conversation.from_messages(harmony_messages)
206
213
  tokens = self.harmony_enc.render_conversation_for_completion(conv, Role.ASSISTANT)
207
-
214
+
208
215
  # For now, we'll need to integrate with Synth GPU endpoint
209
216
  # This would require the actual endpoint to be configured
210
217
  # Placeholder for actual Synth GPU call
211
- import aiohttp
212
218
  import os
213
-
219
+
220
+ import aiohttp
221
+
214
222
  synth_gpu_endpoint = os.getenv("SYNTH_GPU_HARMONY_ENDPOINT")
215
223
  if not synth_gpu_endpoint:
216
224
  raise ValueError("SYNTH_GPU_HARMONY_ENDPOINT environment variable not set")
217
-
218
- async with aiohttp.ClientSession() as session:
219
- async with session.post(
225
+
226
+ async with aiohttp.ClientSession() as session, session.post(
220
227
  f"{synth_gpu_endpoint}/v1/completions",
221
228
  json={
222
229
  "model": model,
223
230
  "prompt": tokens,
224
231
  "max_tokens": lm_config.get("max_tokens", 4096),
225
232
  "temperature": lm_config.get("temperature", 0.8),
226
- }
233
+ },
227
234
  ) as resp:
228
235
  result = await resp.json()
229
-
236
+
230
237
  # Parse response using Harmony
231
238
  response_tokens = result.get("choices", [{}])[0].get("text", "")
232
- parsed = self.harmony_enc.parse_messages_from_completion_tokens(response_tokens, Role.ASSISTANT)
233
-
239
+ parsed = self.harmony_enc.parse_messages_from_completion_tokens(
240
+ response_tokens, Role.ASSISTANT
241
+ )
242
+
234
243
  if parsed:
235
- assistant_msg = parsed[-1].content_text() if hasattr(parsed[-1], 'content_text') else str(parsed[-1])
244
+ assistant_msg = (
245
+ parsed[-1].content_text()
246
+ if hasattr(parsed[-1], "content_text")
247
+ else str(parsed[-1])
248
+ )
236
249
  else:
237
250
  assistant_msg = response_tokens
238
-
251
+
239
252
  return BaseLMResponse(
240
253
  raw_response=assistant_msg,
241
254
  response_id=previous_response_id or str(uuid.uuid4()),
242
255
  api_type="harmony",
243
- )
256
+ )
@@ -1,6 +1,7 @@
1
- import backoff
2
1
  import os
3
2
 
3
+ import backoff
4
+
4
5
  # Number of retry attempts that some legacy decorators rely on.
5
6
  BACKOFF_TOLERANCE: int = 20
6
7
 
@@ -12,3 +13,10 @@ try:
12
13
  MAX_BACKOFF: int = max(1, int(os.getenv("SYNTH_AI_MAX_BACKOFF", "120")))
13
14
  except ValueError:
14
15
  MAX_BACKOFF = 120
16
+
17
+ # Re-export backoff for convenient import patterns elsewhere
18
+ __all__ = [
19
+ "BACKOFF_TOLERANCE",
20
+ "MAX_BACKOFF",
21
+ "backoff",
22
+ ]
@@ -1,22 +1,22 @@
1
- import re
2
- import os
3
- import json
4
1
  import asyncio
2
+ import json
3
+ import os
4
+ import random
5
+ import re
5
6
  import time
6
- from typing import Any, Dict, List, Optional, Tuple, Type
7
- import requests
7
+ from typing import Any
8
+
8
9
  import httpx
10
+ import requests
9
11
  from requests.adapters import HTTPAdapter
10
12
  from urllib3.util.retry import Retry
11
- import random
12
- from urllib.parse import urlparse
13
13
 
14
- from synth_ai.lm.vendors.base import BaseLMResponse, VendorBase
15
- from synth_ai.lm.tools.base import BaseTool
16
14
  from synth_ai.lm.caching.initialize import get_cache_handler
15
+ from synth_ai.lm.tools.base import BaseTool
16
+ from synth_ai.lm.vendors.base import BaseLMResponse, VendorBase
17
17
 
18
18
  # Exception types for retry
19
- CUSTOM_ENDPOINT_EXCEPTIONS_TO_RETRY: Tuple[Type[Exception], ...] = (
19
+ CUSTOM_ENDPOINT_EXCEPTIONS_TO_RETRY: tuple[type[Exception], ...] = (
20
20
  requests.RequestException,
21
21
  requests.Timeout,
22
22
  httpx.RequestError,
@@ -28,7 +28,7 @@ class CustomEndpointAPI(VendorBase):
28
28
  """Generic vendor client for custom OpenAI-compatible endpoints."""
29
29
 
30
30
  used_for_structured_outputs: bool = False
31
- exceptions_to_retry: List = list(CUSTOM_ENDPOINT_EXCEPTIONS_TO_RETRY)
31
+ exceptions_to_retry: list = list(CUSTOM_ENDPOINT_EXCEPTIONS_TO_RETRY)
32
32
 
33
33
  def __init__(self, endpoint_url: str):
34
34
  # Validate and sanitize URL
@@ -89,7 +89,7 @@ class CustomEndpointAPI(VendorBase):
89
89
 
90
90
  # Limit URL length
91
91
  if len(url) > 256:
92
- raise ValueError(f"Endpoint URL too long (max 256 chars)")
92
+ raise ValueError("Endpoint URL too long (max 256 chars)")
93
93
 
94
94
  # Basic URL format check
95
95
  if not re.match(r"^[a-zA-Z0-9\-._~:/?#\[\]@!$&\'()*+,;=]+$", url):
@@ -123,13 +123,13 @@ class CustomEndpointAPI(VendorBase):
123
123
  )
124
124
  return self.async_client
125
125
 
126
- def _get_timeout(self, lm_config: Dict[str, Any]) -> float:
126
+ def _get_timeout(self, lm_config: dict[str, Any]) -> float:
127
127
  """Get timeout with per-call override support."""
128
128
  return lm_config.get(
129
129
  "timeout", float(os.environ.get("CUSTOM_ENDPOINT_REQUEST_TIMEOUT", "30"))
130
130
  )
131
131
 
132
- def _get_temperature_override(self) -> Optional[float]:
132
+ def _get_temperature_override(self) -> float | None:
133
133
  """Get temperature override from environment for this specific endpoint."""
134
134
  # Create a safe env var key from the endpoint URL
135
135
  # e.g., "example.com/api" -> "CUSTOM_ENDPOINT_TEMP_EXAMPLE_COM_API"
@@ -140,7 +140,7 @@ class CustomEndpointAPI(VendorBase):
140
140
  temp_str = os.environ.get(env_key)
141
141
  return float(temp_str) if temp_str else None
142
142
 
143
- def _compress_tool_schema(self, schema: Dict[str, Any]) -> Dict[str, Any]:
143
+ def _compress_tool_schema(self, schema: dict[str, Any]) -> dict[str, Any]:
144
144
  """Compress JSON schema to reduce token usage."""
145
145
  if isinstance(schema, dict):
146
146
  # Remove verbose keys
@@ -157,7 +157,7 @@ class CustomEndpointAPI(VendorBase):
157
157
  return [self._compress_tool_schema(item) for item in schema]
158
158
  return schema
159
159
 
160
- def _inject_tools_into_prompt(self, system_message: str, tools: List[BaseTool]) -> str:
160
+ def _inject_tools_into_prompt(self, system_message: str, tools: list[BaseTool]) -> str:
161
161
  """Inject tool definitions with compressed schemas and clear output format."""
162
162
  if not tools:
163
163
  return system_message
@@ -185,8 +185,8 @@ IMPORTANT: To use a tool, respond with JSON wrapped in ```json fences:
185
185
  For regular responses, just respond normally without JSON fences."""
186
186
 
187
187
  def _extract_tool_calls(
188
- self, content: str, tools: List[BaseTool]
189
- ) -> tuple[Optional[List], str]:
188
+ self, content: str, tools: list[BaseTool]
189
+ ) -> tuple[list | None, str]:
190
190
  """Extract and validate tool calls from response."""
191
191
  # Look for JSON fenced blocks
192
192
  json_pattern = r"```json\s*(\{.*?\})\s*```"
@@ -242,11 +242,11 @@ For regular responses, just respond normally without JSON fences."""
242
242
  async def _hit_api_async(
243
243
  self,
244
244
  model: str,
245
- messages: List[Dict[str, Any]],
246
- lm_config: Dict[str, Any],
245
+ messages: list[dict[str, Any]],
246
+ lm_config: dict[str, Any],
247
247
  use_ephemeral_cache_only: bool = False,
248
248
  reasoning_effort: str = "low",
249
- tools: Optional[List[BaseTool]] = None,
249
+ tools: list[BaseTool] | None = None,
250
250
  ) -> BaseLMResponse:
251
251
  """Async API call with comprehensive error handling and streaming support."""
252
252
 
@@ -314,7 +314,7 @@ For regular responses, just respond normally without JSON fences."""
314
314
 
315
315
  return lm_response
316
316
 
317
- except (httpx.RequestError, httpx.TimeoutException) as e:
317
+ except (httpx.RequestError, httpx.TimeoutException):
318
318
  if attempt == 2: # Last attempt
319
319
  raise
320
320
  await asyncio.sleep(self._exponential_backoff_with_jitter(attempt))
@@ -322,11 +322,11 @@ For regular responses, just respond normally without JSON fences."""
322
322
  def _hit_api_sync(
323
323
  self,
324
324
  model: str,
325
- messages: List[Dict[str, Any]],
326
- lm_config: Dict[str, Any],
325
+ messages: list[dict[str, Any]],
326
+ lm_config: dict[str, Any],
327
327
  use_ephemeral_cache_only: bool = False,
328
328
  reasoning_effort: str = "low",
329
- tools: Optional[List[BaseTool]] = None,
329
+ tools: list[BaseTool] | None = None,
330
330
  ) -> BaseLMResponse:
331
331
  """Sync version with same logic as async."""
332
332
 
@@ -393,7 +393,7 @@ For regular responses, just respond normally without JSON fences."""
393
393
 
394
394
  return lm_response
395
395
 
396
- except (requests.RequestException, requests.Timeout) as e:
396
+ except (requests.RequestException, requests.Timeout):
397
397
  if attempt == 2: # Last attempt
398
398
  raise
399
399
  time.sleep(self._exponential_backoff_with_jitter(attempt))
@@ -1,5 +1,5 @@
1
1
  import os
2
- from typing import Any, Dict, List, Optional, Tuple
2
+ from typing import Any
3
3
 
4
4
  from openai import AsyncOpenAI, OpenAI
5
5
 
@@ -19,18 +19,18 @@ class DeepSeekAPI(OpenAIStandard):
19
19
  base_url="https://api.deepseek.com",
20
20
  )
21
21
 
22
- def _convert_tools_to_openai_format(self, tools: List[BaseTool]) -> List[Dict]:
22
+ def _convert_tools_to_openai_format(self, tools: list[BaseTool]) -> list[dict]:
23
23
  return [tool.to_openai_tool() for tool in tools]
24
24
 
25
25
  async def _private_request_async(
26
26
  self,
27
- messages: List[Dict],
27
+ messages: list[dict],
28
28
  temperature: float = 0,
29
29
  model_name: str = "deepseek-chat",
30
30
  reasoning_effort: str = "high",
31
- tools: Optional[List[BaseTool]] = None,
32
- lm_config: Optional[Dict[str, Any]] = None,
33
- ) -> Tuple[str, Optional[List[Dict]]]:
31
+ tools: list[BaseTool] | None = None,
32
+ lm_config: dict[str, Any] | None = None,
33
+ ) -> tuple[str, list[dict] | None]:
34
34
  request_params = {
35
35
  "model": model_name,
36
36
  "messages": messages,
@@ -47,13 +47,13 @@ class DeepSeekAPI(OpenAIStandard):
47
47
 
48
48
  def _private_request_sync(
49
49
  self,
50
- messages: List[Dict],
50
+ messages: list[dict],
51
51
  temperature: float = 0,
52
52
  model_name: str = "deepseek-chat",
53
53
  reasoning_effort: str = "high",
54
- tools: Optional[List[BaseTool]] = None,
55
- lm_config: Optional[Dict[str, Any]] = None,
56
- ) -> Tuple[str, Optional[List[Dict]]]:
54
+ tools: list[BaseTool] | None = None,
55
+ lm_config: dict[str, Any] | None = None,
56
+ ) -> tuple[str, list[dict] | None]:
57
57
  request_params = {
58
58
  "model": model_name,
59
59
  "messages": messages,
@@ -1,5 +1,5 @@
1
1
  import os
2
- from typing import Any, Dict, List, Optional
2
+ from typing import Any
3
3
 
4
4
  from openai import AsyncOpenAI, OpenAI
5
5
 
@@ -19,7 +19,7 @@ class GrokAPI(OpenAIStandard):
19
19
  def __init__(
20
20
  self,
21
21
  *,
22
- api_key: Optional[str] = None,
22
+ api_key: str | None = None,
23
23
  base_url: str = "https://api.x.ai/v1",
24
24
  ) -> None:
25
25
  api_key = api_key or os.getenv("XAI_API_KEY")
@@ -35,11 +35,11 @@ class GrokAPI(OpenAIStandard):
35
35
  async def _hit_api_async(
36
36
  self,
37
37
  model: str,
38
- messages: List[Dict[str, Any]],
39
- lm_config: Dict[str, Any],
38
+ messages: list[dict[str, Any]],
39
+ lm_config: dict[str, Any],
40
40
  use_ephemeral_cache_only: bool = False,
41
41
  reasoning_effort: str = "high",
42
- tools: Optional[List[BaseTool]] = None,
42
+ tools: list[BaseTool] | None = None,
43
43
  ):
44
44
  if not model:
45
45
  raise ValueError("Model name is required for Grok API calls")
@@ -56,11 +56,11 @@ class GrokAPI(OpenAIStandard):
56
56
  def _hit_api_sync(
57
57
  self,
58
58
  model: str,
59
- messages: List[Dict[str, Any]],
60
- lm_config: Dict[str, Any],
59
+ messages: list[dict[str, Any]],
60
+ lm_config: dict[str, Any],
61
61
  use_ephemeral_cache_only: bool = False,
62
62
  reasoning_effort: str = "high",
63
- tools: Optional[List[BaseTool]] = None,
63
+ tools: list[BaseTool] | None = None,
64
64
  ):
65
65
  if not model:
66
66
  raise ValueError("Model name is required for Grok API calls")
@@ -1,4 +1,5 @@
1
- from openai import OpenAI, AsyncOpenAI
1
+ from openai import AsyncOpenAI, OpenAI
2
+
2
3
  from synth_ai.lm.vendors.openai_standard import OpenAIStandard
3
4
 
4
5
 
@@ -1,9 +1,11 @@
1
1
  import os
2
- from typing import Any, Dict, List, Optional
2
+ from typing import Any
3
+
3
4
  from openai import AsyncOpenAI, OpenAI
4
- from synth_ai.lm.vendors.openai_standard import OpenAIStandard
5
- from synth_ai.lm.vendors.base import BaseLMResponse
5
+
6
6
  from synth_ai.lm.tools.base import BaseTool
7
+ from synth_ai.lm.vendors.base import BaseLMResponse
8
+ from synth_ai.lm.vendors.openai_standard import OpenAIStandard
7
9
 
8
10
 
9
11
  class OpenRouterAPI(OpenAIStandard):
@@ -44,11 +46,11 @@ class OpenRouterAPI(OpenAIStandard):
44
46
  async def _hit_api_async(
45
47
  self,
46
48
  model: str,
47
- messages: List[Dict[str, Any]],
48
- lm_config: Dict[str, Any],
49
+ messages: list[dict[str, Any]],
50
+ lm_config: dict[str, Any],
49
51
  use_ephemeral_cache_only: bool = False,
50
52
  reasoning_effort: str = "high",
51
- tools: Optional[List[BaseTool]] = None,
53
+ tools: list[BaseTool] | None = None,
52
54
  ) -> BaseLMResponse:
53
55
  # Strip the 'openrouter/' prefix before calling the API
54
56
  model = self._strip_prefix(model)
@@ -59,11 +61,11 @@ class OpenRouterAPI(OpenAIStandard):
59
61
  def _hit_api_sync(
60
62
  self,
61
63
  model: str,
62
- messages: List[Dict[str, Any]],
63
- lm_config: Dict[str, Any],
64
+ messages: list[dict[str, Any]],
65
+ lm_config: dict[str, Any],
64
66
  use_ephemeral_cache_only: bool = False,
65
67
  reasoning_effort: str = "high",
66
- tools: Optional[List[BaseTool]] = None,
68
+ tools: list[BaseTool] | None = None,
67
69
  ) -> BaseLMResponse:
68
70
  # Strip the 'openrouter/' prefix before calling the API
69
71
  model = self._strip_prefix(model)