synth-ai 0.2.4.dev5__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 +21 -17
  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 +29 -0
  104. synth_ai/environments/examples/wordle/engine.py +398 -0
  105. synth_ai/environments/examples/wordle/environment.py +159 -0
  106. synth_ai/environments/examples/wordle/helpers/generate_instances_wordfreq.py +75 -0
  107. synth_ai/environments/examples/wordle/taskset.py +230 -0
  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 +11 -2
  111. synth_ai/environments/service/core_routes.py +137 -105
  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 +168 -0
  124. synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +213 -0
  125. synth_ai/learning/prompts/mipro.py +282 -1
  126. synth_ai/learning/prompts/random_search.py +246 -0
  127. synth_ai/learning/prompts/run_mipro_banking77.py +172 -0
  128. synth_ai/learning/prompts/run_random_search_banking77.py +324 -0
  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 +80 -0
  141. synth_ai/lm/overrides.py +206 -0
  142. synth_ai/lm/provider_support/__init__.py +1 -1
  143. synth_ai/lm/provider_support/anthropic.py +51 -24
  144. synth_ai/lm/provider_support/openai.py +51 -22
  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 +50 -25
  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 +144 -88
  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.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/METADATA +2 -11
  224. synth_ai-0.2.4.dev7.dist-info/RECORD +299 -0
  225. synth_ai-0.2.4.dev5.dist-info/RECORD +0 -287
  226. {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/WHEEL +0 -0
  227. {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/entry_points.txt +0 -0
  228. {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/licenses/LICENSE +0 -0
  229. {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/top_level.txt +0 -0
synth_ai/__init__.py CHANGED
@@ -2,27 +2,28 @@
2
2
  Synth AI - Software for aiding the best and multiplying the will.
3
3
  """
4
4
 
5
+ # Environment exports - moved from synth-env
6
+ from synth_ai.environments import * # noqa
7
+ import synth_ai.environments as environments # expose module name for __all__
5
8
  from synth_ai.lm.core.main import LM # Moved from zyk to lm for better organization
9
+ from synth_ai.lm.provider_support.anthropic import Anthropic, AsyncAnthropic
10
+
11
+ # Provider support exports - moved from synth-sdk to synth_ai/lm
12
+ from synth_ai.lm.provider_support.openai import AsyncOpenAI, OpenAI
6
13
 
7
14
  # Tracing exports - moved from synth-sdk (deprecated v1)
8
15
  from synth_ai.tracing_v1 import * # noqa
16
+ import synth_ai.tracing_v1 as tracing # expose module name for __all__
9
17
  from synth_ai.tracing_v1.abstractions import (
10
18
  EventPartitionElement,
19
+ RewardSignal,
11
20
  SystemTrace,
12
21
  TrainingQuestion,
13
- RewardSignal,
14
22
  )
15
23
  from synth_ai.tracing_v1.decorators import trace_event_async, trace_event_sync
16
24
  from synth_ai.tracing_v1.upload import upload
17
25
 
18
- # Provider support exports - moved from synth-sdk to synth_ai/lm
19
- from synth_ai.lm.provider_support.openai import OpenAI, AsyncOpenAI
20
- from synth_ai.lm.provider_support.anthropic import Anthropic, AsyncAnthropic
21
-
22
- # Environment exports - moved from synth-env
23
- from synth_ai.environments import * # noqa
24
-
25
- __version__ = "0.2.1.dev0"
26
+ __version__ = "0.2.4.dev7"
26
27
  __all__ = [
27
28
  "LM",
28
29
  "tracing",
@@ -31,4 +32,12 @@ __all__ = [
31
32
  "Anthropic",
32
33
  "AsyncAnthropic",
33
34
  "environments",
35
+ # v1 tracing legacy API re-exports
36
+ "EventPartitionElement",
37
+ "RewardSignal",
38
+ "SystemTrace",
39
+ "TrainingQuestion",
40
+ "trace_event_async",
41
+ "trace_event_sync",
42
+ "upload",
34
43
  ] # Explicitly define public API
synth_ai/cli/__init__.py CHANGED
@@ -7,13 +7,10 @@ pyproject entry point `synth_ai.cli:cli`.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- import importlib.util
11
- import pathlib
12
- import sys
13
-
14
10
  # Load environment variables from a local .env if present (repo root)
15
11
  try:
16
- from dotenv import load_dotenv, find_dotenv
12
+ from dotenv import find_dotenv, load_dotenv
13
+
17
14
  # Source .env early so CLI subcommands inherit config; do not override shell
18
15
  load_dotenv(find_dotenv(usecwd=True), override=False)
19
16
  except Exception:
@@ -26,41 +23,49 @@ from .root import cli # new canonical CLI entrypoint
26
23
  # Register subcommands from this package onto the group
27
24
  try:
28
25
  from . import watch as _watch
26
+
29
27
  _watch.register(cli)
30
28
  except Exception:
31
29
  pass
32
30
  try:
33
31
  from . import balance as _balance
32
+
34
33
  _balance.register(cli)
35
34
  except Exception:
36
35
  pass
37
36
  try:
38
37
  from . import man as _man
38
+
39
39
  _man.register(cli)
40
40
  except Exception:
41
41
  pass
42
42
  try:
43
43
  from . import traces as _traces
44
+
44
45
  _traces.register(cli)
45
46
  except Exception:
46
47
  pass
47
48
  try:
48
49
  from . import recent as _recent
50
+
49
51
  _recent.register(cli)
50
52
  except Exception:
51
53
  pass
52
54
  try:
53
55
  from . import calc as _calc
56
+
54
57
  _calc.register(cli)
55
58
  except Exception:
56
59
  pass
57
60
  try:
58
61
  from . import status as _status
62
+
59
63
  _status.register(cli)
60
64
  except Exception:
61
65
  pass
62
66
  try:
63
67
  from . import demo as _demo
68
+
64
69
  _demo.register(cli)
65
70
  except Exception:
66
71
  pass
synth_ai/cli/balance.py CHANGED
@@ -6,15 +6,14 @@ CLI: check remaining credit balance from Synth backend.
6
6
  from __future__ import annotations
7
7
 
8
8
  import os
9
+ from urllib.parse import urlparse
10
+
9
11
  import click
10
12
  import requests
11
13
  from requests import Response
12
- from urllib.parse import urlparse
14
+ from rich import box
13
15
  from rich.console import Console
14
- from rich.panel import Panel
15
16
  from rich.table import Table
16
- from rich import box
17
-
18
17
 
19
18
  PROD_BACKEND_BASE = "https://agent-learning.onrender.com/api/v1"
20
19
 
@@ -83,9 +82,7 @@ def register(cli):
83
82
 
84
83
  key_val, key_src = _resolve_api_key(api_key)
85
84
  if not key_val:
86
- console.print(
87
- "[red]Missing API key.[/red] Set via --api-key or SYNTH_API_KEY env var."
88
- )
85
+ console.print("[red]Missing API key.[/red] Set via --api-key or SYNTH_API_KEY env var.")
89
86
  return
90
87
 
91
88
  base = _ensure_api_v1_prefix(base_url)
@@ -127,19 +124,27 @@ def register(cli):
127
124
  if u.ok:
128
125
  uj = u.json()
129
126
  rows = uj.get("windows", [])
130
- windows = {int(r.get("window_hours")): r for r in rows if isinstance(r.get("window_hours"), int)}
127
+ windows = {
128
+ int(r.get("window_hours")): r
129
+ for r in rows
130
+ if isinstance(r.get("window_hours"), int)
131
+ }
132
+
131
133
  def _usd(c):
132
134
  try:
133
- return f"${(int(c)/100):,.2f}"
135
+ return f"${(int(c) / 100):,.2f}"
134
136
  except Exception:
135
137
  return "$0.00"
138
+
136
139
  if 24 in windows or 168 in windows:
137
- t = Table(title="Spend (Tokens vs GPU)", box=box.SIMPLE, header_style="bold")
140
+ t = Table(
141
+ title="Spend (Tokens vs GPU)", box=box.SIMPLE, header_style="bold"
142
+ )
138
143
  t.add_column("Window")
139
144
  t.add_column("Tokens", justify="right")
140
145
  t.add_column("GPU", justify="right")
141
146
  t.add_column("Total", justify="right")
142
- for h,label in ((24,"24h"),(168,"7d")):
147
+ for h, label in ((24, "24h"), (168, "7d")):
143
148
  if h in windows:
144
149
  w = windows[h]
145
150
  t.add_row(
@@ -167,15 +172,15 @@ def register(cli):
167
172
  t.add_column("Total", justify="right")
168
173
  t.add_row(
169
174
  "Current Month",
170
- f"${(cm.get('token_spend_cents',0)/100):,.2f}",
171
- f"${(cm.get('gpu_spend_cents',0)/100):,.2f}",
172
- f"${(cm.get('total_spend_cents',0)/100):,.2f}",
175
+ f"${(cm.get('token_spend_cents', 0) / 100):,.2f}",
176
+ f"${(cm.get('gpu_spend_cents', 0) / 100):,.2f}",
177
+ f"${(cm.get('total_spend_cents', 0) / 100):,.2f}",
173
178
  )
174
179
  t.add_row(
175
180
  "Last 30 days",
176
- f"${(l30.get('token_spend_cents',0)/100):,.2f}",
177
- f"${(l30.get('gpu_spend_cents',0)/100):,.2f}",
178
- f"${(l30.get('total_spend_cents',0)/100):,.2f}",
181
+ f"${(l30.get('token_spend_cents', 0) / 100):,.2f}",
182
+ f"${(l30.get('gpu_spend_cents', 0) / 100):,.2f}",
183
+ f"${(l30.get('total_spend_cents', 0) / 100):,.2f}",
179
184
  )
180
185
  console.print(t)
181
186
  except Exception:
synth_ai/cli/calc.py CHANGED
@@ -6,10 +6,10 @@ Safe evaluation of arithmetic expressions.
6
6
 
7
7
  import ast
8
8
  import operator as op
9
+
9
10
  import click
10
11
  from rich.console import Console
11
12
 
12
-
13
13
  # Supported operators
14
14
  _OPS = {
15
15
  ast.Add: op.add,
@@ -33,7 +33,7 @@ def _safe_eval(expr: str) -> float:
33
33
  if isinstance(n, ast.Num): # 3.8 and earlier
34
34
  return n.n
35
35
  if isinstance(n, ast.Constant): # 3.8+
36
- if isinstance(n.value, (int, float)):
36
+ if isinstance(n.value, int | float):
37
37
  return n.value
38
38
  raise ValueError("Only numeric constants are allowed")
39
39
  if isinstance(n, ast.BinOp) and type(n.op) in _OPS:
@@ -67,4 +67,3 @@ def register(cli):
67
67
  console.print(f"= [bold]{result}[/bold]")
68
68
  except Exception as e:
69
69
  console.print(f"[red]Error:[/red] {e}")
70
-
synth_ai/cli/demo.py CHANGED
@@ -11,12 +11,11 @@ from __future__ import annotations
11
11
  import os
12
12
  import subprocess
13
13
  from pathlib import Path
14
- from typing import List
15
14
 
16
15
  import click
17
16
 
18
17
 
19
- def _find_demo_scripts(root: Path) -> List[Path]:
18
+ def _find_demo_scripts(root: Path) -> list[Path]:
20
19
  if not root.exists():
21
20
  return []
22
21
  return sorted([p for p in root.rglob("run_demo.sh") if p.is_file()])
@@ -52,8 +51,8 @@ def register(cli):
52
51
  def _validate_choice(val: str) -> int:
53
52
  try:
54
53
  i = int(val)
55
- except Exception:
56
- raise click.BadParameter("Enter a number from the list")
54
+ except Exception as err:
55
+ raise click.BadParameter("Enter a number from the list") from err
57
56
  if i < 1 or i > len(demos):
58
57
  raise click.BadParameter(f"Choose a number between 1 and {len(demos)}")
59
58
  return i
@@ -71,4 +70,3 @@ def register(cli):
71
70
  click.echo(f"❌ Demo exited with non-zero status: {e.returncode}")
72
71
  except KeyboardInterrupt:
73
72
  click.echo("\n🛑 Demo interrupted by user")
74
-
@@ -3,22 +3,20 @@
3
3
  Synth AI CLI - Command line interface for Synth AI services.
4
4
  """
5
5
 
6
- import sys
6
+ import logging
7
7
  import os
8
- import subprocess
8
+ import shutil
9
9
  import signal
10
+ import subprocess
11
+ import sys
10
12
  import time
11
- import shutil
12
- from pathlib import Path
13
- from typing import Optional
14
- import logging
15
13
 
16
14
  import click
17
15
 
18
16
  logger = logging.getLogger(__name__)
19
17
 
20
18
 
21
- def find_sqld_binary() -> Optional[str]:
19
+ def find_sqld_binary() -> str | None:
22
20
  """Find the sqld binary in common locations."""
23
21
  # Check if sqld is in PATH
24
22
  sqld_path = shutil.which("sqld")
@@ -132,14 +130,18 @@ def env():
132
130
 
133
131
  @env.command("register")
134
132
  @click.option("--name", required=True, help="Environment name (e.g., 'MyEnv-v1')")
135
- @click.option("--module", "module_path", required=True, help="Python module path (e.g., 'my_package.env')")
133
+ @click.option(
134
+ "--module", "module_path", required=True, help="Python module path (e.g., 'my_package.env')"
135
+ )
136
136
  @click.option("--class-name", "class_name", required=True, help="Environment class name")
137
137
  @click.option("--description", help="Optional description")
138
138
  @click.option("--service-url", default="http://localhost:8901", help="Environment service URL")
139
- def register_env(name: str, module_path: str, class_name: str, description: Optional[str], service_url: str):
139
+ def register_env(
140
+ name: str, module_path: str, class_name: str, description: str | None, service_url: str
141
+ ):
140
142
  """Register a new environment with the service."""
141
143
  import requests
142
-
144
+
143
145
  payload = {
144
146
  "name": name,
145
147
  "module_path": module_path,
@@ -147,14 +149,14 @@ def register_env(name: str, module_path: str, class_name: str, description: Opti
147
149
  }
148
150
  if description:
149
151
  payload["description"] = description
150
-
152
+
151
153
  try:
152
154
  response = requests.post(f"{service_url}/registry/environments", json=payload, timeout=10)
153
155
  response.raise_for_status()
154
-
156
+
155
157
  result = response.json()
156
158
  click.echo(f"✅ {result['message']}")
157
-
159
+
158
160
  except requests.exceptions.ConnectionError:
159
161
  click.echo(f"❌ Could not connect to environment service at {service_url}")
160
162
  click.echo("💡 Make sure the service is running: synth-ai serve")
@@ -170,29 +172,29 @@ def register_env(name: str, module_path: str, class_name: str, description: Opti
170
172
  def list_envs(service_url: str):
171
173
  """List all registered environments."""
172
174
  import requests
173
-
175
+
174
176
  try:
175
177
  response = requests.get(f"{service_url}/registry/environments", timeout=10)
176
178
  response.raise_for_status()
177
-
179
+
178
180
  result = response.json()
179
181
  environments = result["environments"]
180
-
182
+
181
183
  if not environments:
182
184
  click.echo("No environments registered.")
183
185
  return
184
-
186
+
185
187
  click.echo(f"\n📦 Registered Environments ({result['total_count']}):")
186
188
  click.echo("=" * 60)
187
-
189
+
188
190
  for env in environments:
189
191
  click.echo(f"🌍 {env['name']}")
190
192
  click.echo(f" Class: {env['class_name']}")
191
193
  click.echo(f" Module: {env['module']}")
192
- if env['description']:
194
+ if env["description"]:
193
195
  click.echo(f" Description: {env['description']}")
194
196
  click.echo()
195
-
197
+
196
198
  except requests.exceptions.ConnectionError:
197
199
  click.echo(f"❌ Could not connect to environment service at {service_url}")
198
200
  click.echo("💡 Make sure the service is running: synth-ai serve")
@@ -206,14 +208,14 @@ def list_envs(service_url: str):
206
208
  def unregister_env(name: str, service_url: str):
207
209
  """Unregister an environment from the service."""
208
210
  import requests
209
-
211
+
210
212
  try:
211
213
  response = requests.delete(f"{service_url}/registry/environments/{name}", timeout=10)
212
214
  response.raise_for_status()
213
-
215
+
214
216
  result = response.json()
215
217
  click.echo(f"✅ {result['message']}")
216
-
218
+
217
219
  except requests.exceptions.ConnectionError:
218
220
  click.echo(f"❌ Could not connect to environment service at {service_url}")
219
221
  click.echo("💡 Make sure the service is running: synth-ai serve")
@@ -228,11 +230,14 @@ def unregister_env(name: str, service_url: str):
228
230
 
229
231
 
230
232
  @cli.command()
231
- @click.option("--url", default="sqlite+aiosqlite:///./synth_ai.db/dbs/default/data", help="Database URL")
233
+ @click.option(
234
+ "--url", default="sqlite+aiosqlite:///./synth_ai.db/dbs/default/data", help="Database URL"
235
+ )
232
236
  def view(url: str):
233
237
  """Launch the interactive TUI dashboard."""
234
238
  try:
235
239
  from .tui.dashboard import SynthDashboard
240
+
236
241
  app = SynthDashboard(db_url=url)
237
242
  app.run()
238
243
  except ImportError:
@@ -241,6 +246,7 @@ def view(url: str):
241
246
  except KeyboardInterrupt:
242
247
  click.echo("\n👋 Dashboard closed", err=True)
243
248
 
249
+
244
250
  # Note: subcommands (watch, experiments, experiment, usage, traces, status, recent, calc)
245
251
  # are registered from the package module synth_ai.cli at import time.
246
252
 
@@ -251,9 +257,23 @@ def view(url: str):
251
257
  @click.option("--env-port", default=8901, type=int, help="Port for environment service")
252
258
  @click.option("--no-sqld", is_flag=True, help="Skip starting sqld daemon")
253
259
  @click.option("--no-env", is_flag=True, help="Skip starting environment service")
254
- @click.option("--reload/--no-reload", default=False, help="Enable auto-reload (default: off). Or set SYNTH_RELOAD=1")
255
- @click.option("--force", is_flag=True, help="Kill any process already bound to --env-port without prompting")
256
- def serve(db_file: str, sqld_port: int, env_port: int, no_sqld: bool, no_env: bool, reload: bool, force: bool):
260
+ @click.option(
261
+ "--reload/--no-reload",
262
+ default=False,
263
+ help="Enable auto-reload (default: off). Or set SYNTH_RELOAD=1",
264
+ )
265
+ @click.option(
266
+ "--force", is_flag=True, help="Kill any process already bound to --env-port without prompting"
267
+ )
268
+ def serve(
269
+ db_file: str,
270
+ sqld_port: int,
271
+ env_port: int,
272
+ no_sqld: bool,
273
+ no_env: bool,
274
+ reload: bool,
275
+ force: bool,
276
+ ):
257
277
  """Start Synth AI services (sqld daemon and environment service)."""
258
278
 
259
279
  # Configure logging
@@ -307,7 +327,7 @@ def serve(db_file: str, sqld_port: int, env_port: int, no_sqld: bool, no_env: bo
307
327
  ]
308
328
 
309
329
  # Create log file
310
- sqld_log = open("sqld.log", "w")
330
+ sqld_log = open("sqld.log", "w") # noqa: SIM115
311
331
  proc = subprocess.Popen(sqld_cmd, stdout=sqld_log, stderr=subprocess.STDOUT)
312
332
  processes.append(proc)
313
333
 
@@ -317,7 +337,7 @@ def serve(db_file: str, sqld_port: int, env_port: int, no_sqld: bool, no_env: bo
317
337
  # Verify it started
318
338
  if proc.poll() is not None:
319
339
  click.echo("❌ Failed to start sqld. Check sqld.log for details.", err=True)
320
- with open("sqld.log", "r") as f:
340
+ with open("sqld.log") as f:
321
341
  click.echo("\nLast 10 lines of sqld.log:")
322
342
  lines = f.readlines()
323
343
  for line in lines[-10:]:
@@ -362,7 +382,9 @@ def serve(db_file: str, sqld_port: int, env_port: int, no_sqld: bool, no_env: bo
362
382
  # Try to find PIDs using lsof (macOS/Linux)
363
383
  pids: list[str] = []
364
384
  try:
365
- out = subprocess.run(["lsof", "-ti", f":{env_port}"], capture_output=True, text=True)
385
+ out = subprocess.run(
386
+ ["lsof", "-ti", f":{env_port}"], capture_output=True, text=True
387
+ )
366
388
  if out.returncode == 0 and out.stdout.strip():
367
389
  pids = [p for p in out.stdout.strip().splitlines() if p]
368
390
  except FileNotFoundError:
@@ -374,12 +396,16 @@ def serve(db_file: str, sqld_port: int, env_port: int, no_sqld: bool, no_env: bo
374
396
  time.sleep(0.5)
375
397
  else:
376
398
  pid_info = f" PIDs: {', '.join(pids)}" if pids else ""
377
- if click.confirm(f"⚠️ Port {env_port} is in use.{pid_info} Kill and continue?", default=True):
399
+ if click.confirm(
400
+ f"⚠️ Port {env_port} is in use.{pid_info} Kill and continue?", default=True
401
+ ):
378
402
  if pids:
379
403
  subprocess.run(["kill", "-9", *pids], check=False)
380
404
  time.sleep(0.5)
381
405
  else:
382
- click.echo("❌ Aborting. Re-run with --force to auto-kill or choose a different --env-port.")
406
+ click.echo(
407
+ "❌ Aborting. Re-run with --force to auto-kill or choose a different --env-port."
408
+ )
383
409
  sys.exit(1)
384
410
 
385
411
  # Set environment variables
synth_ai/cli/man.py CHANGED
@@ -5,11 +5,10 @@ CLI: human-friendly manual for Synth AI commands and options.
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
- import click
8
+ from rich import box
9
9
  from rich.console import Console
10
- from rich.table import Table
11
10
  from rich.panel import Panel
12
- from rich import box
11
+ from rich.table import Table
13
12
 
14
13
 
15
14
  def _commands_table() -> Table:
@@ -23,28 +22,23 @@ def _commands_table() -> Table:
23
22
  )
24
23
  t.add_row(
25
24
  "traces",
26
- "List local trace DBs, trace counts, experiments, and per-system counts.\n"
27
- "Options: --root",
25
+ "List local trace DBs, trace counts, experiments, and per-system counts.\nOptions: --root",
28
26
  )
29
27
  t.add_row(
30
28
  "experiments",
31
- "Snapshot table of experiments from the local traces DB.\n"
32
- "Options: --url, --limit",
29
+ "Snapshot table of experiments from the local traces DB.\nOptions: --url, --limit",
33
30
  )
34
31
  t.add_row(
35
32
  "experiment <id>",
36
- "Details and sessions for an experiment (accepts partial ID).\n"
37
- "Options: --url",
33
+ "Details and sessions for an experiment (accepts partial ID).\nOptions: --url",
38
34
  )
39
35
  t.add_row(
40
36
  "usage",
41
- "Model usage statistics (tokens, cost).\n"
42
- "Options: --url, --model",
37
+ "Model usage statistics (tokens, cost).\nOptions: --url, --model",
43
38
  )
44
39
  t.add_row(
45
40
  "status",
46
- "DB stats, systems, and environment service health.\n"
47
- "Options: --url, --service-url",
41
+ "DB stats, systems, and environment service health.\nOptions: --url, --service-url",
48
42
  )
49
43
  t.add_row(
50
44
  "calc '<expr>'",
@@ -52,8 +46,7 @@ def _commands_table() -> Table:
52
46
  )
53
47
  t.add_row(
54
48
  "env list | env register | env unregister",
55
- "Manage environment registry via the service.\n"
56
- "Options vary; see examples.",
49
+ "Manage environment registry via the service.\nOptions vary; see examples.",
57
50
  )
58
51
  return t
59
52
 
@@ -63,14 +56,22 @@ def _env_table() -> Table:
63
56
  t.add_column("Variable")
64
57
  t.add_column("Used By")
65
58
  t.add_column("Purpose")
66
- t.add_row("SYNTH_BACKEND_BASE_URL", "balance", "Backend base URL (preferred) e.g. http://localhost:8000/api/v1")
59
+ t.add_row(
60
+ "SYNTH_BACKEND_BASE_URL",
61
+ "balance",
62
+ "Backend base URL (preferred) e.g. http://localhost:8000/api/v1",
63
+ )
67
64
  t.add_row("BACKEND_BASE_URL", "balance", "Fallback backend base URL")
68
65
  t.add_row("LOCAL_BACKEND_URL", "balance", "Another fallback backend base URL")
69
66
  t.add_row("SYNTH_BASE_URL", "balance", "Generic base URL (may point to Modal, guarded)")
70
67
  t.add_row("SYNTH_BACKEND_API_KEY", "balance", "Backend API key (preferred)")
71
68
  t.add_row("SYNTH_API_KEY", "balance, env*", "API key used if backend-specific key not set")
72
69
  t.add_row("DEFAULT_DEV_API_KEY", "balance", "Dev fallback key for local testing")
73
- t.add_row("SYNTH_TRACES_ROOT", "traces", "Root directory of local trace DBs (default ./synth_ai.db/dbs)")
70
+ t.add_row(
71
+ "SYNTH_TRACES_ROOT",
72
+ "traces",
73
+ "Root directory of local trace DBs (default ./synth_ai.db/dbs)",
74
+ )
74
75
  return t
75
76
 
76
77
 
@@ -79,7 +80,10 @@ def _examples_table() -> Table:
79
80
  t.add_column("Command")
80
81
  t.add_column("Example")
81
82
  t.add_row("Balance (local backend)", "uvx . balance")
82
- t.add_row("Balance with URL+key", "uvx . balance --base-url http://localhost:8000 --api-key $SYNTH_API_KEY")
83
+ t.add_row(
84
+ "Balance with URL+key",
85
+ "uvx . balance --base-url http://localhost:8000 --api-key $SYNTH_API_KEY",
86
+ )
83
87
  t.add_row("Traces (default root)", "uvx . traces")
84
88
  t.add_row("Traces (custom root)", "uvx . traces --root /path/to/dbs")
85
89
  t.add_row("Experiments", "uvx . experiments --limit 20")
@@ -100,4 +104,3 @@ def register(cli):
100
104
  console.print(_commands_table())
101
105
  console.print(_env_table())
102
106
  console.print(_examples_table())
103
-
synth_ai/cli/recent.py CHANGED
@@ -5,12 +5,11 @@ CLI: experiments active in the last K hours with summary stats.
5
5
 
6
6
  import asyncio
7
7
  from datetime import datetime, timedelta
8
- from typing import Optional
9
8
 
10
9
  import click
10
+ from rich import box
11
11
  from rich.console import Console
12
12
  from rich.table import Table
13
- from rich import box
14
13
 
15
14
 
16
15
  def _fmt_int(v) -> str:
@@ -90,18 +89,20 @@ def register(cli):
90
89
  async def _run():
91
90
  df = await _fetch_recent(db_url, hours)
92
91
 
93
- table = Table(title=f"Experiments in last {hours:g}h", header_style="bold", box=box.SIMPLE)
92
+ table = Table(
93
+ title=f"Experiments in last {hours:g}h", header_style="bold", box=box.SIMPLE
94
+ )
94
95
  for col in ["Experiment", "Runs", "First", "Last", "Events", "Msgs", "Cost", "Tokens"]:
95
- table.add_column(col, justify="right" if col in {"Runs","Events","Msgs","Tokens"} else "left")
96
+ table.add_column(
97
+ col, justify="right" if col in {"Runs", "Events", "Msgs", "Tokens"} else "left"
98
+ )
96
99
 
97
100
  if df is None or df.empty:
98
101
  table.add_row("-", "0", "-", "-", "-", "-", "-", "-")
99
102
  else:
100
- count = 0
101
- for _, r in df.iterrows():
102
- if count >= limit:
103
+ for count, (_, r) in enumerate(df.iterrows(), start=1):
104
+ if count > limit:
103
105
  break
104
- count += 1
105
106
  name = r.get("name") or "Unnamed"
106
107
  exp_disp = f"{name[:28]} [dim]({_short(r.get('experiment_id'))})[/dim]"
107
108
  table.add_row(