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
@@ -6,43 +6,47 @@ based on model names or explicit provider specifications.
6
6
  """
7
7
 
8
8
  import re
9
- from typing import Any, List, Pattern, Optional, Dict
9
+ from re import Pattern
10
+ from typing import Any
10
11
 
11
12
  from synth_ai.lm.core.all import (
12
13
  AnthropicClient,
14
+ CustomEndpointClient,
13
15
  DeepSeekClient,
14
16
  GeminiClient,
15
- GroqClient,
16
17
  GrokClient,
18
+ GroqClient,
17
19
  # OpenAIClient,
18
20
  OpenAIStructuredOutputClient,
19
- TogetherClient,
20
- CustomEndpointClient,
21
21
  OpenRouterClient,
22
+ TogetherClient,
22
23
  )
24
+ from synth_ai.lm.core.synth_models import SYNTH_SUPPORTED_MODELS
23
25
 
24
26
  # Regular expressions to match model names to their respective providers
25
- openai_naming_regexes: List[Pattern] = [
27
+ openai_naming_regexes: list[Pattern] = [
26
28
  re.compile(r"^(ft:)?(o[1,3,4](-.*)?|gpt-.*)$"),
27
29
  ]
28
- openai_formatting_model_regexes: List[Pattern] = [
30
+ openai_formatting_model_regexes: list[Pattern] = [
29
31
  re.compile(r"^(ft:)?gpt-4o(-.*)?$"),
30
32
  ]
31
- anthropic_naming_regexes: List[Pattern] = [
33
+ anthropic_naming_regexes: list[Pattern] = [
32
34
  re.compile(r"^claude-.*$"),
33
35
  ]
34
- gemini_naming_regexes: List[Pattern] = [
36
+ gemini_naming_regexes: list[Pattern] = [
35
37
  re.compile(r"^gemini-.*$"),
36
38
  re.compile(r"^gemma[2-9].*$"),
37
39
  ]
38
- deepseek_naming_regexes: List[Pattern] = [
40
+ deepseek_naming_regexes: list[Pattern] = [
39
41
  re.compile(r"^deepseek-.*$"),
40
42
  ]
41
- together_naming_regexes: List[Pattern] = [
42
- re.compile(r"^.*\/.*$"),
43
+ # Synth-specific model patterns (Qwen3 and fine-tuned models)
44
+ synth_naming_regexes: list[Pattern] = [
45
+ re.compile(r"^ft:.*$"), # Fine-tuned models (ft:model-name)
46
+ re.compile(r"^Qwen/Qwen3.*$"), # Qwen3 models specifically (Qwen/Qwen3-*)
43
47
  ]
44
48
 
45
- groq_naming_regexes: List[Pattern] = [
49
+ groq_naming_regexes: list[Pattern] = [
46
50
  re.compile(r"^llama-3.3-70b-versatile$"),
47
51
  re.compile(r"^llama-3.1-8b-instant$"),
48
52
  re.compile(r"^qwen-2.5-32b$"),
@@ -60,7 +64,7 @@ groq_naming_regexes: List[Pattern] = [
60
64
  re.compile(r"^moonshotai/kimi-k2-instruct$"),
61
65
  ]
62
66
 
63
- grok_naming_regexes: List[Pattern] = [
67
+ grok_naming_regexes: list[Pattern] = [
64
68
  re.compile(r"^grok-3-beta$"),
65
69
  re.compile(r"^grok-3-mini-beta$"),
66
70
  re.compile(r"^grok-beta$"),
@@ -68,25 +72,23 @@ grok_naming_regexes: List[Pattern] = [
68
72
  ]
69
73
 
70
74
 
71
- openrouter_naming_regexes: List[Pattern] = [
75
+ openrouter_naming_regexes: list[Pattern] = [
72
76
  re.compile(r"^openrouter/.*$"), # openrouter/model-name pattern
73
77
  ]
74
78
 
75
- openrouter_naming_regexes: List[Pattern] = [
79
+ openrouter_naming_regexes: list[Pattern] = [
76
80
  re.compile(r"^openrouter/.*$"), # openrouter/model-name pattern
77
81
  ]
78
82
 
79
83
  # Custom endpoint patterns - check these before generic patterns
80
- custom_endpoint_naming_regexes: List[Pattern] = [
81
- # Modal endpoints: org--app.modal.run
82
- re.compile(r"^[a-zA-Z0-9\-]+--[a-zA-Z0-9\-]+\.modal\.run$"),
84
+ custom_endpoint_naming_regexes: list[Pattern] = [
83
85
  # Generic domain patterns for custom endpoints
84
86
  re.compile(r"^[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-]+\.[a-zA-Z]+$"), # domain.tld
85
87
  re.compile(r"^[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-]+\.[a-zA-Z]+\/[a-zA-Z0-9\-\/]+$"), # domain.tld/path
86
88
  ]
87
89
 
88
90
  # Provider mapping for explicit provider overrides
89
- PROVIDER_MAP: Dict[str, Any] = {
91
+ PROVIDER_MAP: dict[str, Any] = {
90
92
  "openai": OpenAIStructuredOutputClient,
91
93
  "anthropic": AnthropicClient,
92
94
  "groq": GroqClient,
@@ -104,7 +106,7 @@ def get_client(
104
106
  model_name: str,
105
107
  with_formatting: bool = False,
106
108
  synth_logging: bool = True,
107
- provider: Optional[str] = None,
109
+ provider: str | None = None,
108
110
  ) -> Any:
109
111
  """
110
112
  Get a vendor client for the specified model.
@@ -178,7 +180,9 @@ def get_client(
178
180
  elif any(regex.match(model_name) for regex in custom_endpoint_naming_regexes):
179
181
  # Custom endpoints are passed as the endpoint URL
180
182
  return CustomEndpointClient(endpoint_url=model_name)
181
- elif any(regex.match(model_name) for regex in together_naming_regexes):
182
- return TogetherClient()
183
+ elif (any(regex.match(model_name) for regex in synth_naming_regexes) or
184
+ model_name in SYNTH_SUPPORTED_MODELS):
185
+ # Synth models use OpenAI-compatible client with custom endpoint
186
+ return OpenAIStructuredOutputClient(synth_logging=synth_logging)
183
187
  else:
184
188
  raise ValueError(f"Invalid model name: {model_name}")
synth_ai/lm/injection.py CHANGED
@@ -2,16 +2,16 @@ from __future__ import annotations
2
2
 
3
3
  import contextvars
4
4
  from contextlib import contextmanager
5
- from typing import Any, Dict, List, Optional
5
+ from typing import Any
6
6
 
7
- Rule = Dict[str, Any]
7
+ Rule = dict[str, Any]
8
8
 
9
- _rules_ctx: contextvars.ContextVar[Optional[List[Rule]]] = contextvars.ContextVar(
9
+ _rules_ctx: contextvars.ContextVar[list[Rule] | None] = contextvars.ContextVar(
10
10
  "injection_rules", default=None
11
11
  )
12
12
 
13
13
 
14
- def set_injection_rules(rules: List[Rule]):
14
+ def set_injection_rules(rules: list[Rule]):
15
15
  """Set prompt-injection rules for the current context and return a reset token.
16
16
 
17
17
  Each rule must be a dict with at least keys: "find" and "replace" (strings).
@@ -24,7 +24,7 @@ def set_injection_rules(rules: List[Rule]):
24
24
  return _rules_ctx.set(rules)
25
25
 
26
26
 
27
- def get_injection_rules() -> Optional[List[Rule]]:
27
+ def get_injection_rules() -> list[Rule] | None:
28
28
  """Get the current context's injection rules, if any."""
29
29
  return _rules_ctx.get()
30
30
 
@@ -34,7 +34,7 @@ def clear_injection_rules(token) -> None:
34
34
  _rules_ctx.reset(token)
35
35
 
36
36
 
37
- def apply_injection(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
37
+ def apply_injection(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
38
38
  """Apply ordered substring replacements to text parts of messages in place.
39
39
 
40
40
  - Only modifies `str` content or list parts where `part["type"] == "text"`.
@@ -71,11 +71,10 @@ def apply_injection(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
71
71
 
72
72
 
73
73
  @contextmanager
74
- def injection_rules_ctx(rules: List[Rule]):
74
+ def injection_rules_ctx(rules: list[Rule]):
75
75
  """Context manager to temporarily apply injection rules within the block."""
76
76
  tok = set_injection_rules(rules)
77
77
  try:
78
78
  yield
79
79
  finally:
80
80
  clear_injection_rules(tok)
81
-
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.