synth-ai 0.2.9.dev17__py3-none-any.whl → 0.2.12__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.

Potentially problematic release.


This version of synth-ai might be problematic. Click here for more details.

Files changed (56) hide show
  1. examples/dev/qwen3_32b_qlora_4xh100.toml +40 -0
  2. examples/multi_step/crafter_rl_lora.md +29 -0
  3. examples/multi_step/task_app_config_notes.md +488 -0
  4. examples/qwen_coder/infer_ft_smoke.py +1 -0
  5. examples/qwen_coder/scripts/infer_coder.sh +1 -0
  6. examples/qwen_coder/scripts/train_coder_30b.sh +1 -0
  7. examples/qwen_coder/subset_jsonl.py +1 -0
  8. examples/qwen_coder/todos.md +38 -0
  9. examples/qwen_coder/validate_jsonl.py +1 -0
  10. examples/vlm/PROPOSAL.md +53 -0
  11. examples/warming_up_to_rl/configs/eval_stepwise_complex.toml +33 -0
  12. examples/warming_up_to_rl/configs/eval_stepwise_consistent.toml +26 -0
  13. examples/warming_up_to_rl/configs/eval_stepwise_per_achievement.toml +36 -0
  14. examples/warming_up_to_rl/configs/eval_stepwise_simple.toml +30 -0
  15. examples/warming_up_to_rl/old/event_rewards.md +234 -0
  16. examples/warming_up_to_rl/old/notes.md +73 -0
  17. examples/warming_up_to_rl/run_eval.py +142 -25
  18. examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +146 -2
  19. synth_ai/__init__.py +5 -20
  20. synth_ai/api/train/builders.py +25 -14
  21. synth_ai/api/train/cli.py +29 -6
  22. synth_ai/api/train/env_resolver.py +18 -19
  23. synth_ai/api/train/supported_algos.py +8 -5
  24. synth_ai/api/train/utils.py +6 -1
  25. synth_ai/cli/__init__.py +4 -2
  26. synth_ai/cli/_storage.py +19 -0
  27. synth_ai/cli/balance.py +14 -2
  28. synth_ai/cli/calc.py +37 -22
  29. synth_ai/cli/legacy_root_backup.py +12 -14
  30. synth_ai/cli/recent.py +12 -7
  31. synth_ai/cli/root.py +1 -23
  32. synth_ai/cli/status.py +4 -3
  33. synth_ai/cli/task_apps.py +143 -137
  34. synth_ai/cli/traces.py +4 -3
  35. synth_ai/cli/watch.py +3 -2
  36. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_traces_sft_turso.py +738 -0
  37. synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/filter_traces_sft_turso.py +580 -0
  38. synth_ai/jobs/client.py +15 -3
  39. synth_ai/task/server.py +14 -7
  40. synth_ai/tracing_v3/decorators.py +51 -26
  41. synth_ai/tracing_v3/examples/basic_usage.py +12 -7
  42. synth_ai/tracing_v3/llm_call_record_helpers.py +107 -53
  43. synth_ai/tracing_v3/replica_sync.py +8 -4
  44. synth_ai/tracing_v3/storage/utils.py +11 -9
  45. synth_ai/tracing_v3/turso/__init__.py +12 -0
  46. synth_ai/tracing_v3/turso/daemon.py +2 -1
  47. synth_ai/tracing_v3/turso/native_manager.py +28 -15
  48. {synth_ai-0.2.9.dev17.dist-info → synth_ai-0.2.12.dist-info}/METADATA +33 -88
  49. {synth_ai-0.2.9.dev17.dist-info → synth_ai-0.2.12.dist-info}/RECORD +53 -41
  50. {synth_ai-0.2.9.dev17.dist-info → synth_ai-0.2.12.dist-info}/top_level.txt +0 -1
  51. synth/__init__.py +0 -14
  52. synth_ai/_docs_message.py +0 -10
  53. synth_ai/main.py +0 -5
  54. {synth_ai-0.2.9.dev17.dist-info → synth_ai-0.2.12.dist-info}/WHEEL +0 -0
  55. {synth_ai-0.2.9.dev17.dist-info → synth_ai-0.2.12.dist-info}/entry_points.txt +0 -0
  56. {synth_ai-0.2.9.dev17.dist-info → synth_ai-0.2.12.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import importlib
3
4
  import os
4
5
  from collections.abc import Callable, Iterable, MutableMapping
5
6
  from dataclasses import dataclass
@@ -11,6 +12,18 @@ from . import task_app
11
12
  from .utils import REPO_ROOT, mask_value, read_env_file, write_env_value
12
13
 
13
14
 
15
+ def _load_saved_env_path() -> Path | None:
16
+ try:
17
+ module = importlib.import_module("synth_ai.demos.demo_task_apps.core")
18
+ loader = module.load_env_file_path
19
+ saved_path = loader()
20
+ if saved_path:
21
+ return Path(saved_path)
22
+ except Exception:
23
+ return None
24
+ return None
25
+
26
+
14
27
  @dataclass(slots=True)
15
28
  class KeySpec:
16
29
  name: str
@@ -156,25 +169,11 @@ def resolve_env(
156
169
  raise click.ClickException(f"Env file not found: {path}")
157
170
  resolver = EnvResolver(provided)
158
171
  else:
159
- # Check for saved .env path from demo command
160
- try:
161
- from synth_ai.demos.demo_task_apps.core import load_env_file_path
162
-
163
- saved_env_path = load_env_file_path()
164
- if saved_env_path:
165
- saved_path = Path(saved_env_path)
166
- if saved_path.exists():
167
- click.echo(f"Using .env file: {saved_path}")
168
- resolver = EnvResolver([saved_path])
169
- else:
170
- # Saved path no longer exists, fall back to prompt
171
- resolver = EnvResolver(_collect_default_candidates(config_path))
172
- resolver.select_new_env()
173
- else:
174
- resolver = EnvResolver(_collect_default_candidates(config_path))
175
- resolver.select_new_env()
176
- except Exception:
177
- # If import fails or any error, fall back to original behavior
172
+ saved_path = _load_saved_env_path()
173
+ if saved_path and saved_path.exists():
174
+ click.echo(f"Using .env file: {saved_path}")
175
+ resolver = EnvResolver([saved_path])
176
+ else:
178
177
  resolver = EnvResolver(_collect_default_candidates(config_path))
179
178
  resolver.select_new_env()
180
179
 
@@ -1,13 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import importlib
3
4
  from collections.abc import Mapping
4
5
  from dataclasses import dataclass
5
6
 
6
- from synth_ai.api.models.supported import (
7
- RL_SUPPORTED_MODELS,
8
- SFT_SUPPORTED_MODELS,
9
- training_modes_for_model,
10
- )
7
+ try:
8
+ _models_module = importlib.import_module("synth_ai.api.models.supported")
9
+ RL_SUPPORTED_MODELS = _models_module.RL_SUPPORTED_MODELS
10
+ SFT_SUPPORTED_MODELS = _models_module.SFT_SUPPORTED_MODELS
11
+ training_modes_for_model = _models_module.training_modes_for_model
12
+ except Exception as exc: # pragma: no cover - critical dependency
13
+ raise RuntimeError("Unable to load supported model metadata") from exc
11
14
 
12
15
 
13
16
  @dataclass(frozen=True)
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import importlib
3
4
  import json
4
5
  import os
5
6
  import re
@@ -13,7 +14,11 @@ from pathlib import Path
13
14
  from typing import Any
14
15
 
15
16
  import requests
16
- from synth_ai.learning.sft import collect_sft_jsonl_errors
17
+
18
+ try:
19
+ collect_sft_jsonl_errors = importlib.import_module("synth_ai.learning.sft").collect_sft_jsonl_errors
20
+ except Exception as exc: # pragma: no cover - critical dependency
21
+ raise RuntimeError("Unable to load SFT JSONL helpers") from exc
17
22
 
18
23
  REPO_ROOT = Path(__file__).resolve().parents[3]
19
24
 
synth_ai/cli/__init__.py CHANGED
@@ -7,6 +7,8 @@ pyproject entry point `synth_ai.cli:cli`.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import importlib
11
+
10
12
  # Load environment variables from a local .env if present (repo root)
11
13
  try:
12
14
  from dotenv import find_dotenv, load_dotenv
@@ -89,8 +91,8 @@ try:
89
91
  except Exception:
90
92
  pass
91
93
  try:
92
- from synth_ai.api.train import register as _train_register
93
-
94
+ _train_module = importlib.import_module("synth_ai.api.train")
95
+ _train_register = _train_module.register
94
96
  _train_register(cli)
95
97
  except Exception:
96
98
  pass
@@ -0,0 +1,19 @@
1
+ """Dynamic loader for tracing storage components.
2
+
3
+ This avoids hard dependencies on the tracing_v3 storage package during import
4
+ time, which keeps CLI modules usable in constrained environments while still
5
+ allowing type checkers to resolve the symbols dynamically.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import importlib
11
+ from typing import Any
12
+
13
+
14
+ def load_storage() -> tuple[Any, Any]:
15
+ """Return (create_storage, StorageConfig) from tracing_v3.storage."""
16
+ storage_module = importlib.import_module("synth_ai.tracing_v3.storage")
17
+ create_storage = storage_module.create_storage
18
+ storage_config = storage_module.StorageConfig
19
+ return create_storage, storage_config
synth_ai/cli/balance.py CHANGED
@@ -5,7 +5,9 @@ CLI: check remaining credit balance from Synth backend.
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ import importlib
8
9
  import os
10
+ from collections.abc import Callable
9
11
 
10
12
  import click
11
13
  import requests
@@ -14,8 +16,18 @@ from rich import box
14
16
  from rich.console import Console
15
17
  from rich.table import Table
16
18
 
17
- from synth_ai.config.base_url import PROD_BASE_URL_DEFAULT, get_backend_from_env
18
19
 
20
+ def _load_base_url_module() -> tuple[str, Callable[[], tuple[str, str]]]:
21
+ try:
22
+ module = importlib.import_module("synth_ai.config.base_url")
23
+ default = module.PROD_BASE_URL_DEFAULT
24
+ getter = module.get_backend_from_env
25
+ return str(default), getter
26
+ except Exception:
27
+ return "https://agent-learning.onrender.com", lambda: ("https://agent-learning.onrender.com", "")
28
+
29
+
30
+ PROD_BASE_URL_DEFAULT, _get_backend_from_env = _load_base_url_module()
19
31
  PROD_BACKEND_BASE = f"{PROD_BASE_URL_DEFAULT.rstrip('/')}/api/v1"
20
32
 
21
33
 
@@ -25,7 +37,7 @@ def _get_default_base_url() -> str:
25
37
  val = os.getenv(var)
26
38
  if val:
27
39
  return val
28
- base, _ = get_backend_from_env()
40
+ base, _ = _get_backend_from_env()
29
41
  base = base.rstrip("/")
30
42
  if base.endswith("/api"):
31
43
  base = base[: -len("/api")]
synth_ai/cli/calc.py CHANGED
@@ -5,46 +5,61 @@ Safe evaluation of arithmetic expressions.
5
5
  """
6
6
 
7
7
  import ast
8
- import operator as op
8
+ import operator
9
+ from collections.abc import Callable
9
10
 
10
11
  import click
11
12
  from rich.console import Console
12
13
 
13
14
  # Supported operators
14
- _OPS = {
15
- ast.Add: op.add,
16
- ast.Sub: op.sub,
17
- ast.Mult: op.mul,
18
- ast.Div: op.truediv,
19
- ast.FloorDiv: op.floordiv,
20
- ast.Mod: op.mod,
21
- ast.Pow: op.pow,
22
- ast.USub: op.neg,
23
- ast.UAdd: op.pos,
15
+ _BINARY_OPS: dict[type[ast.AST], Callable[[float, float], float]] = {
16
+ ast.Add: operator.add,
17
+ ast.Sub: operator.sub,
18
+ ast.Mult: operator.mul,
19
+ ast.Div: operator.truediv,
20
+ ast.FloorDiv: operator.floordiv,
21
+ ast.Mod: operator.mod,
22
+ ast.Pow: operator.pow,
23
+ }
24
+
25
+ _UNARY_OPS: dict[type[ast.AST], Callable[[float], float]] = {
26
+ ast.USub: operator.neg,
27
+ ast.UAdd: operator.pos,
24
28
  }
25
29
 
26
30
 
27
31
  def _safe_eval(expr: str) -> float:
28
32
  node = ast.parse(expr, mode="eval")
29
33
 
30
- def _eval(n):
34
+ def _eval(n: ast.AST) -> float:
31
35
  if isinstance(n, ast.Expression):
32
36
  return _eval(n.body)
33
- if hasattr(ast, "Num") and isinstance(n, ast.Num): # 3.8 and earlier
34
- return n.n
35
- if isinstance(n, ast.Constant): # 3.8+
36
- if isinstance(n.value, int | float):
37
- return n.value
37
+ if isinstance(n, ast.Constant):
38
+ if isinstance(n.value, (int, float)):
39
+ return float(n.value)
40
+ raise ValueError("Only numeric constants are allowed")
41
+ num_node = getattr(ast, "Num", None)
42
+ if num_node is not None and isinstance(n, num_node): # pragma: no cover
43
+ numeric_value = getattr(n, "n", None)
44
+ if isinstance(numeric_value, (int, float)):
45
+ return float(numeric_value)
38
46
  raise ValueError("Only numeric constants are allowed")
39
- if isinstance(n, ast.BinOp) and type(n.op) in _OPS:
40
- return _OPS[type(n.op)](_eval(n.left), _eval(n.right))
41
- if isinstance(n, ast.UnaryOp) and type(n.op) in _OPS:
42
- return _OPS[type(n.op)](_eval(n.operand))
47
+ if isinstance(n, ast.BinOp):
48
+ op_type = type(n.op)
49
+ func = _BINARY_OPS.get(op_type)
50
+ if func:
51
+ return func(_eval(n.left), _eval(n.right))
52
+ if isinstance(n, ast.UnaryOp):
53
+ op_type = type(n.op)
54
+ func = _UNARY_OPS.get(op_type)
55
+ if func:
56
+ return func(_eval(n.operand))
43
57
  if isinstance(n, ast.Expr):
44
58
  return _eval(n.value)
45
59
  raise ValueError("Unsupported expression")
46
60
 
47
- return _eval(node)
61
+ result = _eval(node)
62
+ return float(result)
48
63
 
49
64
 
50
65
  def register(cli):
@@ -3,6 +3,7 @@
3
3
  Synth AI CLI - Command line interface for Synth AI services.
4
4
  """
5
5
 
6
+ import importlib
6
7
  import logging
7
8
  import os
8
9
  import shutil
@@ -12,6 +13,9 @@ import sys
12
13
  import time
13
14
 
14
15
  import click
16
+ import requests
17
+ from requests.exceptions import ConnectionError as RequestsConnectionError
18
+ from requests.exceptions import HTTPError
15
19
 
16
20
  logger = logging.getLogger(__name__)
17
21
 
@@ -140,8 +144,6 @@ def register_env(
140
144
  name: str, module_path: str, class_name: str, description: str | None, service_url: str
141
145
  ):
142
146
  """Register a new environment with the service."""
143
- import requests
144
-
145
147
  payload = {
146
148
  "name": name,
147
149
  "module_path": module_path,
@@ -157,10 +159,10 @@ def register_env(
157
159
  result = response.json()
158
160
  click.echo(f"✅ {result['message']}")
159
161
 
160
- except requests.exceptions.ConnectionError:
162
+ except RequestsConnectionError:
161
163
  click.echo(f"❌ Could not connect to environment service at {service_url}")
162
164
  click.echo("💡 Make sure the service is running: synth-ai serve")
163
- except requests.exceptions.HTTPError as e:
165
+ except HTTPError as e:
164
166
  error_detail = e.response.json().get("detail", str(e)) if e.response else str(e)
165
167
  click.echo(f"❌ Registration failed: {error_detail}")
166
168
  except Exception as e:
@@ -171,8 +173,6 @@ def register_env(
171
173
  @click.option("--service-url", default="http://localhost:8901", help="Environment service URL")
172
174
  def list_envs(service_url: str):
173
175
  """List all registered environments."""
174
- import requests
175
-
176
176
  try:
177
177
  response = requests.get(f"{service_url}/registry/environments", timeout=10)
178
178
  response.raise_for_status()
@@ -195,7 +195,7 @@ def list_envs(service_url: str):
195
195
  click.echo(f" Description: {env['description']}")
196
196
  click.echo()
197
197
 
198
- except requests.exceptions.ConnectionError:
198
+ except RequestsConnectionError:
199
199
  click.echo(f"❌ Could not connect to environment service at {service_url}")
200
200
  click.echo("💡 Make sure the service is running: synth-ai serve")
201
201
  except Exception as e:
@@ -207,8 +207,6 @@ def list_envs(service_url: str):
207
207
  @click.option("--service-url", default="http://localhost:8901", help="Environment service URL")
208
208
  def unregister_env(name: str, service_url: str):
209
209
  """Unregister an environment from the service."""
210
- import requests
211
-
212
210
  try:
213
211
  response = requests.delete(f"{service_url}/registry/environments/{name}", timeout=10)
214
212
  response.raise_for_status()
@@ -216,10 +214,10 @@ def unregister_env(name: str, service_url: str):
216
214
  result = response.json()
217
215
  click.echo(f"✅ {result['message']}")
218
216
 
219
- except requests.exceptions.ConnectionError:
217
+ except RequestsConnectionError:
220
218
  click.echo(f"❌ Could not connect to environment service at {service_url}")
221
219
  click.echo("💡 Make sure the service is running: synth-ai serve")
222
- except requests.exceptions.HTTPError as e:
220
+ except HTTPError as e:
223
221
  if e.response.status_code == 404:
224
222
  click.echo(f"❌ Environment '{name}' not found in registry")
225
223
  else:
@@ -236,9 +234,9 @@ def unregister_env(name: str, service_url: str):
236
234
  def view(url: str):
237
235
  """Launch the interactive TUI dashboard."""
238
236
  try:
239
- from .tui.dashboard import SynthDashboard
240
-
241
- app = SynthDashboard(db_url=url)
237
+ module = importlib.import_module(".tui.dashboard", __package__)
238
+ synth_dashboard_cls = module.SynthDashboard
239
+ app = synth_dashboard_cls(db_url=url)
242
240
  app.run()
243
241
  except ImportError:
244
242
  click.echo("❌ Textual not installed. Install with: pip install textual", err=True)
synth_ai/cli/recent.py CHANGED
@@ -5,40 +5,45 @@ 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 TYPE_CHECKING, Any
8
9
 
9
10
  import click
10
11
  from rich import box
11
12
  from rich.console import Console
12
13
  from rich.table import Table
13
14
 
15
+ from ._storage import load_storage
14
16
 
15
- def _fmt_int(v) -> str:
17
+ if TYPE_CHECKING: # pragma: no cover - typing only
18
+ import pandas as pd
19
+ else:
20
+ pd = Any # type: ignore[assignment]
21
+ def _fmt_int(v: Any) -> str:
16
22
  try:
17
23
  return f"{int(v):,}"
18
24
  except Exception:
19
25
  return "0"
20
26
 
21
27
 
22
- def _fmt_money(v) -> str:
28
+ def _fmt_money(v: Any) -> str:
23
29
  try:
24
30
  return f"${float(v or 0.0):.4f}"
25
31
  except Exception:
26
32
  return "$0.0000"
27
33
 
28
34
 
29
- def _fmt_time(v) -> str:
35
+ def _fmt_time(v: Any) -> str:
30
36
  try:
31
37
  return str(v)
32
38
  except Exception:
33
39
  return "-"
34
40
 
35
41
 
36
- async def _fetch_recent(db_url: str, hours: float):
37
- from synth_ai.tracing_v3.storage.factory import create_storage, StorageConfig
38
-
42
+ async def _fetch_recent(db_url: str, hours: float) -> "pd.DataFrame":
39
43
  start_time = datetime.now() - timedelta(hours=hours)
40
44
 
41
- db = create_storage(StorageConfig(connection_string=db_url))
45
+ create_storage, storage_config = load_storage()
46
+ db: Any = create_storage(storage_config(connection_string=db_url))
42
47
  await db.initialize()
43
48
  try:
44
49
  query = """
synth_ai/cli/root.py CHANGED
@@ -34,28 +34,6 @@ except Exception:
34
34
  from synth_ai import __version__ as __pkg_version__ # type: ignore
35
35
  except Exception:
36
36
  __pkg_version__ = "unknown"
37
-
38
-
39
- EQ_SIGN_DIVIDER = f"\n{'=' * 75}\n"
40
- nl = "\n"
41
- HELP_MESSAGE = f"""
42
- {EQ_SIGN_DIVIDER}
43
- Synth AI SDK
44
- {nl}
45
- Version {__pkg_version__}
46
- {EQ_SIGN_DIVIDER}
47
- {EQ_SIGN_DIVIDER}
48
- 🚀 Get started:
49
-
50
- - Guide for Fine-Tuning →
51
- https://docs.usesynth.ai/sdk/examples/fine-tuning-demo
52
-
53
- - Guide for Reinforcement Learning →
54
- https://docs.usesynth.ai/sdk/examples/math-rl-demo
55
-
56
- For all SDK docs, go to https://docs.usesynth.ai/sdk
57
- {EQ_SIGN_DIVIDER}
58
- """
59
37
 
60
38
 
61
39
  SQLD_VERSION = "v0.26.2"
@@ -171,7 +149,7 @@ def install_sqld() -> str:
171
149
 
172
150
 
173
151
  @click.group(
174
- help=HELP_MESSAGE
152
+ help=f"Synth AI v{__pkg_version__} - Software for aiding the best and multiplying the will."
175
153
  )
176
154
  @click.version_option(version=__pkg_version__, prog_name="synth-ai")
177
155
  def cli():
synth_ai/cli/status.py CHANGED
@@ -12,11 +12,12 @@ from rich.console import Console
12
12
  from rich.panel import Panel
13
13
  from rich.table import Table
14
14
 
15
+ from ._storage import load_storage
15
16
 
16
- async def _db_stats(db_url: str) -> dict:
17
- from synth_ai.tracing_v3.storage.factory import StorageConfig, create_storage
18
17
 
19
- db = create_storage(StorageConfig(connection_string=db_url))
18
+ async def _db_stats(db_url: str) -> dict:
19
+ create_storage, storage_config = load_storage()
20
+ db = create_storage(storage_config(connection_string=db_url))
20
21
  await db.initialize()
21
22
  try:
22
23
  out: dict = {}