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
synth_ai/lm/overrides.py CHANGED
@@ -1,14 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
- from contextlib import contextmanager
4
- from typing import Any, Dict, List, Optional, Tuple
5
3
  import contextvars
4
+ from contextlib import contextmanager
5
+ from typing import Any
6
6
 
7
7
  from synth_ai.lm.injection import (
8
- set_injection_rules,
9
- clear_injection_rules,
10
8
  apply_injection as _apply_injection,
11
9
  )
10
+ from synth_ai.lm.injection import (
11
+ clear_injection_rules,
12
+ set_injection_rules,
13
+ )
12
14
 
13
15
  # Context to hold a list of override specs to evaluate per-call
14
16
  # Each spec shape (minimal v1):
@@ -18,29 +20,29 @@ from synth_ai.lm.injection import (
18
20
  # "params": { ... api params to override ... },
19
21
  # "tools": { ... optional tools overrides ... },
20
22
  # }
21
- _override_specs_ctx: contextvars.ContextVar[Optional[List[Dict[str, Any]]]] = contextvars.ContextVar(
22
- "override_specs", default=None
23
+ _override_specs_ctx: contextvars.ContextVar[list[dict[str, Any]] | None] = (
24
+ contextvars.ContextVar("override_specs", default=None)
23
25
  )
24
26
 
25
27
  # ContextVars actually applied for the specific call once matched
26
- _param_overrides_ctx: contextvars.ContextVar[Optional[Dict[str, Any]]] = contextvars.ContextVar(
28
+ _param_overrides_ctx: contextvars.ContextVar[dict[str, Any] | None] = contextvars.ContextVar(
27
29
  "param_overrides", default=None
28
30
  )
29
- _tool_overrides_ctx: contextvars.ContextVar[Optional[Dict[str, Any]]] = contextvars.ContextVar(
31
+ _tool_overrides_ctx: contextvars.ContextVar[dict[str, Any] | None] = contextvars.ContextVar(
30
32
  "tool_overrides", default=None
31
33
  )
32
- _current_override_label_ctx: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
34
+ _current_override_label_ctx: contextvars.ContextVar[str | None] = contextvars.ContextVar(
33
35
  "override_label", default=None
34
36
  )
35
37
 
36
38
 
37
- def set_override_specs(specs: List[Dict[str, Any]]):
39
+ def set_override_specs(specs: list[dict[str, Any]]):
38
40
  if not isinstance(specs, list):
39
41
  raise ValueError("override specs must be a list of dicts")
40
42
  return _override_specs_ctx.set(specs)
41
43
 
42
44
 
43
- def get_override_specs() -> Optional[List[Dict[str, Any]]]:
45
+ def get_override_specs() -> list[dict[str, Any]] | None:
44
46
  return _override_specs_ctx.get()
45
47
 
46
48
 
@@ -48,7 +50,7 @@ def clear_override_specs(token) -> None:
48
50
  _override_specs_ctx.reset(token)
49
51
 
50
52
 
51
- def _matches(spec: Dict[str, Any], messages: List[Dict[str, Any]]) -> bool:
53
+ def _matches(spec: dict[str, Any], messages: list[dict[str, Any]]) -> bool:
52
54
  match = spec.get("match") or {}
53
55
  contains = match.get("contains")
54
56
  role = match.get("role") # optional
@@ -69,7 +71,7 @@ def _matches(spec: Dict[str, Any], messages: List[Dict[str, Any]]) -> bool:
69
71
  return False
70
72
 
71
73
 
72
- def resolve_override_for_messages(messages: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
74
+ def resolve_override_for_messages(messages: list[dict[str, Any]]) -> dict[str, Any] | None:
73
75
  specs = get_override_specs() or []
74
76
  for spec in specs:
75
77
  try:
@@ -81,12 +83,12 @@ def resolve_override_for_messages(messages: List[Dict[str, Any]]) -> Optional[Di
81
83
  return None
82
84
 
83
85
 
84
- def apply_injection(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
86
+ def apply_injection(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
85
87
  # Delegate to injection.apply_injection
86
88
  return _apply_injection(messages)
87
89
 
88
90
 
89
- def apply_param_overrides(api_params: Dict[str, Any]) -> Dict[str, Any]:
91
+ def apply_param_overrides(api_params: dict[str, Any]) -> dict[str, Any]:
90
92
  ov = _param_overrides_ctx.get()
91
93
  if not ov:
92
94
  return api_params
@@ -96,7 +98,7 @@ def apply_param_overrides(api_params: Dict[str, Any]) -> Dict[str, Any]:
96
98
  return api_params
97
99
 
98
100
 
99
- def apply_tool_overrides(api_params: Dict[str, Any]) -> Dict[str, Any]:
101
+ def apply_tool_overrides(api_params: dict[str, Any]) -> dict[str, Any]:
100
102
  """Apply tool overrides to OpenAI/Anthropic-like api_params in place.
101
103
 
102
104
  Supports keys under spec["tools"]:
@@ -138,7 +140,7 @@ def apply_tool_overrides(api_params: Dict[str, Any]) -> Dict[str, Any]:
138
140
 
139
141
 
140
142
  @contextmanager
141
- def use_overrides_for_messages(messages: List[Dict[str, Any]]):
143
+ def use_overrides_for_messages(messages: list[dict[str, Any]]):
142
144
  """Resolve an override spec against messages and apply its contexts within the scope.
143
145
 
144
146
  - Sets injection rules and param overrides if present on the matched spec.
@@ -174,7 +176,7 @@ def use_overrides_for_messages(messages: List[Dict[str, Any]]):
174
176
  _current_override_label_ctx.reset(label_tok)
175
177
 
176
178
 
177
- def get_current_override_label() -> Optional[str]:
179
+ def get_current_override_label() -> str | None:
178
180
  return _current_override_label_ctx.get()
179
181
 
180
182
 
@@ -189,7 +191,7 @@ class LMOverridesContext:
189
191
  run_pipeline()
190
192
  """
191
193
 
192
- def __init__(self, override_specs: Optional[List[Dict[str, Any]]] | Dict[str, Any] = None):
194
+ def __init__(self, override_specs: list[dict[str, Any]] | None | dict[str, Any] = None):
193
195
  if isinstance(override_specs, dict):
194
196
  override_specs = [override_specs]
195
197
  self._specs = override_specs or []
@@ -2,7 +2,7 @@
2
2
  Provider support for LLM services with integrated tracing.
3
3
  """
4
4
 
5
- from .openai import OpenAI, AsyncOpenAI
6
5
  from .anthropic import Anthropic, AsyncAnthropic
6
+ from .openai import AsyncOpenAI, OpenAI
7
7
 
8
8
  __all__ = ["OpenAI", "AsyncOpenAI", "Anthropic", "AsyncAnthropic"]
@@ -6,14 +6,13 @@ Analogous to the modified OpenAI version.
6
6
  import logging
7
7
  import types
8
8
  from dataclasses import dataclass
9
- from typing import Optional
10
9
 
11
10
  try:
12
11
  import anthropic
13
- except ImportError:
12
+ except ImportError as err:
14
13
  raise ModuleNotFoundError(
15
14
  "Please install anthropic to use this feature: 'pip install anthropic'"
16
- )
15
+ ) from err
17
16
 
18
17
  try:
19
18
  from anthropic import AsyncClient, Client
@@ -27,14 +26,15 @@ from langfuse.decorators import langfuse_context
27
26
  from langfuse.utils import _get_timestamp
28
27
  from langfuse.utils.langfuse_singleton import LangfuseSingleton
29
28
  from wrapt import wrap_function_wrapper
29
+
30
30
  from synth_ai.lm.overrides import (
31
- use_overrides_for_messages,
32
31
  apply_injection as apply_injection_overrides,
32
+ )
33
+ from synth_ai.lm.overrides import (
33
34
  apply_param_overrides,
34
35
  apply_tool_overrides,
36
+ use_overrides_for_messages,
35
37
  )
36
- from synth_ai.lm.injection import apply_injection
37
-
38
38
  from synth_ai.lm.provider_support.suppress_logging import *
39
39
  from synth_ai.tracing_v1.trackers import (
40
40
  synth_tracker_async,
@@ -799,7 +799,7 @@ class LangfuseAnthropicResponseGeneratorAsync:
799
799
 
800
800
 
801
801
  class AnthropicLangfuse:
802
- _langfuse: Optional[Langfuse] = None
802
+ _langfuse: Langfuse | None = None
803
803
 
804
804
  def initialize(self):
805
805
  self._langfuse = LangfuseSingleton().get(
@@ -946,14 +946,14 @@ class AnthropicLangfuse:
946
946
 
947
947
  anthropic.AsyncClient.__init__ = new_async_init
948
948
 
949
- setattr(anthropic, "langfuse_public_key", None)
950
- setattr(anthropic, "langfuse_secret_key", None)
951
- setattr(anthropic, "langfuse_host", None)
952
- setattr(anthropic, "langfuse_debug", None)
953
- setattr(anthropic, "langfuse_enabled", True)
954
- setattr(anthropic, "langfuse_sample_rate", None)
955
- setattr(anthropic, "langfuse_auth_check", self.langfuse_auth_check)
956
- setattr(anthropic, "flush_langfuse", self.flush)
949
+ anthropic.langfuse_public_key = None
950
+ anthropic.langfuse_secret_key = None
951
+ anthropic.langfuse_host = None
952
+ anthropic.langfuse_debug = None
953
+ anthropic.langfuse_enabled = True
954
+ anthropic.langfuse_sample_rate = None
955
+ anthropic.langfuse_auth_check = self.langfuse_auth_check
956
+ anthropic.flush_langfuse = self.flush
957
957
 
958
958
 
959
959
  modifier = AnthropicLangfuse()
@@ -4,7 +4,6 @@ import types
4
4
  from collections import defaultdict
5
5
  from dataclasses import dataclass
6
6
  from inspect import isclass
7
- from typing import List, Optional
8
7
 
9
8
  import openai.resources
10
9
  from langfuse import Langfuse
@@ -15,22 +14,25 @@ from langfuse.utils.langfuse_singleton import LangfuseSingleton
15
14
  from packaging.version import Version
16
15
  from pydantic import BaseModel
17
16
  from wrapt import wrap_function_wrapper
17
+
18
18
  from synth_ai.lm.overrides import (
19
- use_overrides_for_messages,
20
19
  apply_injection as apply_injection_overrides,
20
+ )
21
+ from synth_ai.lm.overrides import (
21
22
  apply_param_overrides,
22
23
  apply_tool_overrides,
24
+ use_overrides_for_messages,
23
25
  )
24
- from synth_ai.lm.injection import apply_injection
25
-
26
26
  from synth_ai.lm.provider_support.suppress_logging import *
27
27
  from synth_ai.tracing_v1.abstractions import MessageInputs
28
28
  from synth_ai.tracing_v1.trackers import synth_tracker_async, synth_tracker_sync
29
29
 
30
30
  try:
31
31
  import openai
32
- except ImportError:
33
- raise ModuleNotFoundError("Please install OpenAI to use this feature: 'pip install openai'")
32
+ except ImportError as err:
33
+ raise ModuleNotFoundError(
34
+ "Please install OpenAI to use this feature: 'pip install openai'"
35
+ ) from err
34
36
 
35
37
  # CREDIT TO LANGFUSE FOR OPEN-SOURCING THE CODE THAT THIS IS BASED ON
36
38
  # USING WITH MIT LICENSE PERMISSION
@@ -59,7 +61,7 @@ class OpenAiDefinition:
59
61
  method: str
60
62
  type: str
61
63
  sync: bool
62
- min_version: Optional[str] = None
64
+ min_version: str | None = None
63
65
 
64
66
 
65
67
  OPENAI_METHODS_V0 = [
@@ -212,7 +214,7 @@ def _extract_chat_response(kwargs: dict):
212
214
  Extracts the LLM output from the response.
213
215
  """
214
216
  response = {
215
- "role": kwargs.get("role", None),
217
+ "role": kwargs.get("role"),
216
218
  }
217
219
 
218
220
  if kwargs.get("function_call") is not None:
@@ -221,7 +223,7 @@ def _extract_chat_response(kwargs: dict):
221
223
  if kwargs.get("tool_calls") is not None:
222
224
  response.update({"tool_calls": kwargs["tool_calls"]})
223
225
 
224
- response["content"] = kwargs.get("content", None)
226
+ response["content"] = kwargs.get("content")
225
227
  return response
226
228
 
227
229
 
@@ -418,7 +420,7 @@ def _extract_streamed_openai_response(resource, chunks):
418
420
  usage = chunk_usage
419
421
 
420
422
  # Process choices
421
- choices = chunk.get("choices", [])
423
+ choices = chunk.get("choices", []) # noqa: F841
422
424
  # logger.debug(f"Extracted - model: {model}, choices: {choices}")
423
425
 
424
426
  # logger.debug(f"Final completion: {completion}")
@@ -762,7 +764,7 @@ async def _wrap_async(open_ai_resource: OpenAiDefinition, initialize, wrapped, a
762
764
 
763
765
 
764
766
  class OpenAILangfuse:
765
- _langfuse: Optional[Langfuse] = None
767
+ _langfuse: Langfuse | None = None
766
768
 
767
769
  def initialize(self):
768
770
  self._langfuse = LangfuseSingleton().get(
@@ -820,15 +822,15 @@ class OpenAILangfuse:
820
822
  else _wrap_async(resource, self.initialize),
821
823
  )
822
824
 
823
- setattr(openai, "langfuse_public_key", None)
824
- setattr(openai, "langfuse_secret_key", None)
825
- setattr(openai, "langfuse_host", None)
826
- setattr(openai, "langfuse_debug", None)
827
- setattr(openai, "langfuse_enabled", True)
828
- setattr(openai, "langfuse_sample_rate", None)
829
- setattr(openai, "langfuse_mask", None)
830
- setattr(openai, "langfuse_auth_check", self.langfuse_auth_check)
831
- setattr(openai, "flush_langfuse", self.flush)
825
+ openai.langfuse_public_key = None
826
+ openai.langfuse_secret_key = None
827
+ openai.langfuse_host = None
828
+ openai.langfuse_debug = None
829
+ openai.langfuse_enabled = True
830
+ openai.langfuse_sample_rate = None
831
+ openai.langfuse_mask = None
832
+ openai.langfuse_auth_check = self.langfuse_auth_check
833
+ openai.flush_langfuse = self.flush
832
834
 
833
835
 
834
836
  modifier = OpenAILangfuse()
@@ -843,7 +845,7 @@ def auth_check():
843
845
  return modifier._langfuse.auth_check()
844
846
 
845
847
 
846
- def _filter_image_data(messages: List[dict]):
848
+ def _filter_image_data(messages: list[dict]):
847
849
  """https://platform.openai.com/docs/guides/vision?lang=python
848
850
 
849
851
  The messages array remains the same, but the 'image_url' is removed from the 'content' array.
@@ -8,10 +8,12 @@ in the requested structured format (Pydantic models).
8
8
  import logging
9
9
  import time
10
10
  from abc import ABC, abstractmethod
11
- from typing import Any, Callable, Dict, List, Literal, Optional, Union
11
+ from collections.abc import Callable
12
+ from typing import Any, Literal
12
13
 
13
14
  from pydantic import BaseModel
14
15
 
16
+ from synth_ai.lm.constants import SPECIAL_BASE_TEMPS
15
17
  from synth_ai.lm.core.exceptions import StructuredOutputCoercionFailureException
16
18
  from synth_ai.lm.structured_outputs.inject import (
17
19
  inject_structured_output_instructions,
@@ -22,7 +24,6 @@ from synth_ai.lm.structured_outputs.rehabilitate import (
22
24
  pull_out_structured_output,
23
25
  )
24
26
  from synth_ai.lm.vendors.base import BaseLMResponse, VendorBase
25
- from synth_ai.lm.constants import SPECIAL_BASE_TEMPS
26
27
 
27
28
  logger = logging.getLogger(__name__)
28
29
 
@@ -30,26 +31,27 @@ logger = logging.getLogger(__name__)
30
31
  class StructuredHandlerBase(ABC):
31
32
  """
32
33
  Abstract base class for structured output handlers.
33
-
34
+
34
35
  Handles the logic for ensuring language models return properly formatted
35
36
  structured outputs, with retry logic and error handling.
36
-
37
+
37
38
  Attributes:
38
39
  core_client: Primary vendor client for API calls
39
40
  retry_client: Client used for retry attempts (may use different model)
40
41
  handler_params: Configuration parameters including retry count
41
42
  structured_output_mode: Either "stringified_json" or "forced_json"
42
43
  """
44
+
43
45
  core_client: VendorBase
44
46
  retry_client: VendorBase
45
- handler_params: Dict[str, Any]
47
+ handler_params: dict[str, Any]
46
48
  structured_output_mode: Literal["stringified_json", "forced_json"]
47
49
 
48
50
  def __init__(
49
51
  self,
50
52
  core_client: VendorBase,
51
53
  retry_client: VendorBase,
52
- handler_params: Optional[Dict[str, Any]] = None,
54
+ handler_params: dict[str, Any] | None = None,
53
55
  structured_output_mode: Literal["stringified_json", "forced_json"] = "stringified_json",
54
56
  ):
55
57
  self.core_client = core_client
@@ -59,7 +61,7 @@ class StructuredHandlerBase(ABC):
59
61
 
60
62
  async def call_async(
61
63
  self,
62
- messages: List[Dict[str, Any]],
64
+ messages: list[dict[str, Any]],
63
65
  model: str,
64
66
  response_model: BaseModel,
65
67
  temperature: float = 0.0,
@@ -74,7 +76,7 @@ class StructuredHandlerBase(ABC):
74
76
  model=model,
75
77
  response_model=response_model,
76
78
  api_call_method=self.core_client._hit_api_async_structured_output
77
- if (not not response_model and self.structured_output_mode == "forced_json")
79
+ if (response_model and self.structured_output_mode == "forced_json")
78
80
  else self.core_client._hit_api_async,
79
81
  temperature=temperature,
80
82
  use_ephemeral_cache_only=use_ephemeral_cache_only,
@@ -83,7 +85,7 @@ class StructuredHandlerBase(ABC):
83
85
 
84
86
  def call_sync(
85
87
  self,
86
- messages: List[Dict[str, Any]],
88
+ messages: list[dict[str, Any]],
87
89
  response_model: BaseModel,
88
90
  model: str,
89
91
  temperature: float = 0.0,
@@ -97,7 +99,7 @@ class StructuredHandlerBase(ABC):
97
99
  model=model,
98
100
  response_model=response_model,
99
101
  api_call_method=self.core_client._hit_api_sync_structured_output
100
- if (not not response_model and self.structured_output_mode == "forced_json")
102
+ if (response_model and self.structured_output_mode == "forced_json")
101
103
  else self.core_client._hit_api_sync,
102
104
  temperature=temperature,
103
105
  use_ephemeral_cache_only=use_ephemeral_cache_only,
@@ -107,7 +109,7 @@ class StructuredHandlerBase(ABC):
107
109
  @abstractmethod
108
110
  async def _process_call_async(
109
111
  self,
110
- messages: List[Dict[str, Any]],
112
+ messages: list[dict[str, Any]],
111
113
  model: str,
112
114
  response_model: BaseModel,
113
115
  api_call_method,
@@ -119,7 +121,7 @@ class StructuredHandlerBase(ABC):
119
121
  @abstractmethod
120
122
  def _process_call_sync(
121
123
  self,
122
- messages: List[Dict[str, Any]],
124
+ messages: list[dict[str, Any]],
123
125
  model: str,
124
126
  response_model: BaseModel,
125
127
  api_call_method,
@@ -132,24 +134,24 @@ class StructuredHandlerBase(ABC):
132
134
  class StringifiedJSONHandler(StructuredHandlerBase):
133
135
  core_client: VendorBase
134
136
  retry_client: VendorBase
135
- handler_params: Dict[str, Any]
137
+ handler_params: dict[str, Any]
136
138
 
137
139
  def __init__(
138
140
  self,
139
141
  core_client: VendorBase,
140
142
  retry_client: VendorBase,
141
- handler_params: Dict[str, Any] = {"retries": 3},
143
+ handler_params: dict[str, Any] | None = None,
142
144
  ):
143
145
  super().__init__(
144
146
  core_client,
145
147
  retry_client,
146
- handler_params,
148
+ handler_params or {"retries": 3},
147
149
  structured_output_mode="stringified_json",
148
150
  )
149
151
 
150
152
  async def _process_call_async(
151
153
  self,
152
- messages: List[Dict[str, Any]],
154
+ messages: list[dict[str, Any]],
153
155
  model: str,
154
156
  response_model: BaseModel,
155
157
  temperature: float,
@@ -170,7 +172,7 @@ class StringifiedJSONHandler(StructuredHandlerBase):
170
172
  response_model=response_model,
171
173
  previously_failed_error_messages=previously_failed_error_messages,
172
174
  )
173
- t0 = time.time()
175
+ # t0 = time.time() # unused
174
176
  raw_text_response_or_cached_hit = await api_call_method(
175
177
  messages=messages_with_json_formatting_instructions,
176
178
  model=model,
@@ -184,7 +186,7 @@ class StringifiedJSONHandler(StructuredHandlerBase):
184
186
  assert type(raw_text_response_or_cached_hit) in [str, BaseLMResponse], (
185
187
  f"Expected str or BaseLMResponse, got {type(raw_text_response_or_cached_hit)}"
186
188
  )
187
- if type(raw_text_response_or_cached_hit) == BaseLMResponse:
189
+ if isinstance(raw_text_response_or_cached_hit, BaseLMResponse):
188
190
  # print("Got cached hit, returning directly")
189
191
  raw_text_response = raw_text_response_or_cached_hit.raw_response
190
192
  else:
@@ -242,7 +244,7 @@ class StringifiedJSONHandler(StructuredHandlerBase):
242
244
 
243
245
  def _process_call_sync(
244
246
  self,
245
- messages: List[Dict[str, Any]],
247
+ messages: list[dict[str, Any]],
246
248
  model: str,
247
249
  response_model: BaseModel,
248
250
  temperature: float,
@@ -277,7 +279,7 @@ class StringifiedJSONHandler(StructuredHandlerBase):
277
279
  assert type(raw_text_response_or_cached_hit) in [str, BaseLMResponse], (
278
280
  f"Expected str or BaseLMResponse, got {type(raw_text_response_or_cached_hit)}"
279
281
  )
280
- if type(raw_text_response_or_cached_hit) == BaseLMResponse:
282
+ if isinstance(raw_text_response_or_cached_hit, BaseLMResponse):
281
283
  logger.info("Got cached hit, returning directly")
282
284
  raw_text_response = raw_text_response_or_cached_hit.raw_response
283
285
  else:
@@ -320,26 +322,26 @@ class StringifiedJSONHandler(StructuredHandlerBase):
320
322
  class ForcedJSONHandler(StructuredHandlerBase):
321
323
  core_client: VendorBase
322
324
  retry_client: VendorBase
323
- handler_params: Dict[str, Any]
325
+ handler_params: dict[str, Any]
324
326
 
325
327
  def __init__(
326
328
  self,
327
329
  core_client: VendorBase,
328
330
  retry_client: VendorBase,
329
- handler_params: Dict[str, Any] = {},
331
+ handler_params: dict[str, Any] | None = None,
330
332
  reasoning_effort: str = "high",
331
333
  ):
332
334
  super().__init__(
333
335
  core_client,
334
336
  retry_client,
335
- handler_params,
337
+ handler_params or {"retries": 3},
336
338
  structured_output_mode="forced_json",
337
339
  )
338
340
  self.reasoning_effort = reasoning_effort
339
341
 
340
342
  async def _process_call_async(
341
343
  self,
342
- messages: List[Dict[str, Any]],
344
+ messages: list[dict[str, Any]],
343
345
  model: str,
344
346
  response_model: BaseModel,
345
347
  api_call_method: Callable,
@@ -360,7 +362,7 @@ class ForcedJSONHandler(StructuredHandlerBase):
360
362
 
361
363
  def _process_call_sync(
362
364
  self,
363
- messages: List[Dict[str, Any]],
365
+ messages: list[dict[str, Any]],
364
366
  model: str,
365
367
  response_model: BaseModel,
366
368
  api_call_method: Callable,
@@ -380,16 +382,16 @@ class ForcedJSONHandler(StructuredHandlerBase):
380
382
 
381
383
 
382
384
  class StructuredOutputHandler:
383
- handler: Union[StringifiedJSONHandler, ForcedJSONHandler]
385
+ handler: StringifiedJSONHandler | ForcedJSONHandler
384
386
  mode: Literal["stringified_json", "forced_json"]
385
- handler_params: Dict[str, Any]
387
+ handler_params: dict[str, Any]
386
388
 
387
389
  def __init__(
388
390
  self,
389
391
  core_client: VendorBase,
390
392
  retry_client: VendorBase,
391
393
  mode: Literal["stringified_json", "forced_json"],
392
- handler_params: Dict[str, Any] = {},
394
+ handler_params: dict[str, Any] = {},
393
395
  ):
394
396
  self.mode = mode
395
397
  if self.mode == "stringified_json":
@@ -402,11 +404,11 @@ class StructuredOutputHandler:
402
404
 
403
405
  async def call_async(
404
406
  self,
405
- messages: List[Dict[str, Any]],
407
+ messages: list[dict[str, Any]],
406
408
  model: str,
407
409
  response_model: BaseModel,
408
410
  use_ephemeral_cache_only: bool = False,
409
- lm_config: Dict[str, Any] = {},
411
+ lm_config: dict[str, Any] = {},
410
412
  reasoning_effort: str = "high",
411
413
  ) -> BaseLMResponse:
412
414
  # print("Output handler call async")
@@ -421,11 +423,11 @@ class StructuredOutputHandler:
421
423
 
422
424
  def call_sync(
423
425
  self,
424
- messages: List[Dict[str, Any]],
426
+ messages: list[dict[str, Any]],
425
427
  model: str,
426
428
  response_model: BaseModel,
427
429
  use_ephemeral_cache_only: bool = False,
428
- lm_config: Dict[str, Any] = {},
430
+ lm_config: dict[str, Any] = {},
429
431
  reasoning_effort: str = "high",
430
432
  ) -> BaseLMResponse:
431
433
  return self.handler.call_sync(