synth-ai 0.2.10__py3-none-any.whl → 0.2.13.dev1__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.
- examples/agora_ex/README_MoE.md +224 -0
- examples/agora_ex/__init__.py +7 -0
- examples/agora_ex/agora_ex.py +65 -0
- examples/agora_ex/agora_ex_task_app.py +590 -0
- examples/agora_ex/configs/rl_lora_qwen3_moe_2xh200.toml +121 -0
- examples/agora_ex/reward_fn_grpo-human.py +129 -0
- examples/agora_ex/system_prompt_CURRENT.md +63 -0
- examples/agora_ex/task_app/agora_ex_task_app.py +590 -0
- examples/agora_ex/task_app/reward_fn_grpo-human.py +129 -0
- examples/agora_ex/task_app/system_prompt_CURRENT.md +63 -0
- examples/multi_step/configs/crafter_rl_outcome.toml +74 -0
- examples/multi_step/configs/crafter_rl_stepwise_hosted_judge.toml +175 -0
- examples/multi_step/configs/crafter_rl_stepwise_shaped.toml +83 -0
- examples/multi_step/configs/crafter_rl_stepwise_simple.toml +78 -0
- examples/multi_step/crafter_rl_lora.md +51 -10
- examples/multi_step/sse_metrics_streaming_notes.md +357 -0
- examples/multi_step/task_app_config_notes.md +494 -0
- examples/warming_up_to_rl/configs/eval_stepwise_complex.toml +35 -0
- examples/warming_up_to_rl/configs/eval_stepwise_consistent.toml +26 -0
- examples/warming_up_to_rl/configs/eval_stepwise_per_achievement.toml +36 -0
- examples/warming_up_to_rl/configs/eval_stepwise_simple.toml +32 -0
- examples/warming_up_to_rl/run_eval.py +267 -41
- examples/warming_up_to_rl/task_app/grpo_crafter.py +3 -33
- examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +109 -45
- examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +42 -46
- examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +376 -193
- synth_ai/__init__.py +41 -1
- synth_ai/api/train/builders.py +74 -33
- synth_ai/api/train/cli.py +29 -6
- synth_ai/api/train/configs/__init__.py +44 -0
- synth_ai/api/train/configs/rl.py +133 -0
- synth_ai/api/train/configs/sft.py +94 -0
- synth_ai/api/train/configs/shared.py +24 -0
- synth_ai/api/train/env_resolver.py +18 -19
- synth_ai/api/train/supported_algos.py +8 -5
- synth_ai/api/train/utils.py +6 -1
- synth_ai/cli/__init__.py +4 -2
- synth_ai/cli/_storage.py +19 -0
- synth_ai/cli/balance.py +14 -2
- synth_ai/cli/calc.py +37 -22
- synth_ai/cli/demo.py +38 -39
- synth_ai/cli/legacy_root_backup.py +12 -14
- synth_ai/cli/recent.py +12 -7
- synth_ai/cli/rl_demo.py +81 -102
- synth_ai/cli/status.py +4 -3
- synth_ai/cli/task_apps.py +146 -137
- synth_ai/cli/traces.py +4 -3
- synth_ai/cli/watch.py +3 -2
- synth_ai/demos/core/cli.py +121 -159
- synth_ai/environments/examples/crafter_classic/environment.py +16 -0
- synth_ai/evals/__init__.py +15 -0
- synth_ai/evals/client.py +85 -0
- synth_ai/evals/types.py +42 -0
- synth_ai/jobs/client.py +15 -3
- synth_ai/judge_schemas.py +127 -0
- synth_ai/rubrics/__init__.py +22 -0
- synth_ai/rubrics/validators.py +126 -0
- synth_ai/task/server.py +14 -7
- synth_ai/tracing_v3/decorators.py +51 -26
- synth_ai/tracing_v3/examples/basic_usage.py +12 -7
- synth_ai/tracing_v3/llm_call_record_helpers.py +107 -53
- synth_ai/tracing_v3/replica_sync.py +8 -4
- synth_ai/tracing_v3/serialization.py +130 -0
- synth_ai/tracing_v3/storage/utils.py +11 -9
- synth_ai/tracing_v3/turso/__init__.py +12 -0
- synth_ai/tracing_v3/turso/daemon.py +2 -1
- synth_ai/tracing_v3/turso/native_manager.py +28 -15
- {synth_ai-0.2.10.dist-info → synth_ai-0.2.13.dev1.dist-info}/METADATA +4 -2
- {synth_ai-0.2.10.dist-info → synth_ai-0.2.13.dev1.dist-info}/RECORD +73 -40
- {synth_ai-0.2.10.dist-info → synth_ai-0.2.13.dev1.dist-info}/entry_points.txt +0 -1
- {synth_ai-0.2.10.dist-info → synth_ai-0.2.13.dev1.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.10.dist-info → synth_ai-0.2.13.dev1.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.2.10.dist-info → synth_ai-0.2.13.dev1.dist-info}/top_level.txt +0 -0
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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)
|
synth_ai/api/train/utils.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
synth_ai/cli/_storage.py
ADDED
|
@@ -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, _ =
|
|
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
|
|
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
|
-
|
|
15
|
-
ast.Add:
|
|
16
|
-
ast.Sub:
|
|
17
|
-
ast.Mult:
|
|
18
|
-
ast.Div:
|
|
19
|
-
ast.FloorDiv:
|
|
20
|
-
ast.Mod:
|
|
21
|
-
ast.Pow:
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
61
|
+
result = _eval(node)
|
|
62
|
+
return float(result)
|
|
48
63
|
|
|
49
64
|
|
|
50
65
|
def register(cli):
|
synth_ai/cli/demo.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
CLI: interactive launcher for example demos and
|
|
3
|
+
CLI: interactive launcher for example demos and RL demo helpers.
|
|
4
4
|
|
|
5
|
-
- `synth-ai demo` (no subcommand) ->
|
|
6
|
-
- `synth-ai demo deploy|configure|run` ->
|
|
5
|
+
- `synth-ai demo` (no subcommand) -> initialize RL demo files into ./synth_demo/
|
|
6
|
+
- `synth-ai demo deploy|configure|run` -> invoke RL demo helpers directly.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
@@ -14,6 +14,8 @@ from pathlib import Path
|
|
|
14
14
|
|
|
15
15
|
import click
|
|
16
16
|
|
|
17
|
+
from synth_ai.demos.core import cli as demo_commands
|
|
18
|
+
|
|
17
19
|
|
|
18
20
|
def _find_demo_scripts(root: Path) -> list[Path]:
|
|
19
21
|
if not root.exists():
|
|
@@ -21,17 +23,23 @@ def _find_demo_scripts(root: Path) -> list[Path]:
|
|
|
21
23
|
return sorted([p for p in root.rglob("run_demo.sh") if p.is_file()])
|
|
22
24
|
|
|
23
25
|
|
|
24
|
-
def
|
|
25
|
-
|
|
26
|
+
def _run_demo_command(func, *args, **kwargs) -> None:
|
|
27
|
+
"""Invoke a demo command and exit via Click on non-zero status codes."""
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
result = func(*args, **kwargs)
|
|
31
|
+
except SystemExit as exc: # pragma: no cover - defensive
|
|
32
|
+
raise click.exceptions.Exit(exc.code or 1) from exc
|
|
33
|
+
|
|
34
|
+
if result is None:
|
|
35
|
+
return
|
|
26
36
|
|
|
27
37
|
try:
|
|
28
|
-
|
|
29
|
-
except
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if rc != 0:
|
|
34
|
-
sys.exit(rc)
|
|
38
|
+
code = int(result)
|
|
39
|
+
except (TypeError, ValueError):
|
|
40
|
+
return
|
|
41
|
+
if code != 0:
|
|
42
|
+
raise click.exceptions.Exit(code)
|
|
35
43
|
|
|
36
44
|
|
|
37
45
|
def register(cli):
|
|
@@ -92,11 +100,8 @@ def register(cli):
|
|
|
92
100
|
click.echo("\n🛑 Demo interrupted by user")
|
|
93
101
|
return
|
|
94
102
|
|
|
95
|
-
# Default:
|
|
96
|
-
|
|
97
|
-
if force:
|
|
98
|
-
args.append("--force")
|
|
99
|
-
_forward_to_new(args)
|
|
103
|
+
# Default: initialize RL demo files via new command
|
|
104
|
+
_run_demo_command(demo_commands.init, force=force)
|
|
100
105
|
|
|
101
106
|
# (prepare command removed; configure now prepares baseline TOML)
|
|
102
107
|
|
|
@@ -122,24 +127,21 @@ def register(cli):
|
|
|
122
127
|
help="Path to deploy_task_app.sh (optional legacy)",
|
|
123
128
|
)
|
|
124
129
|
def demo_deploy(local: bool, app: str | None, name: str, script: str | None):
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if script:
|
|
133
|
-
args.extend(["--script", script])
|
|
134
|
-
_forward_to_new(args)
|
|
130
|
+
_run_demo_command(
|
|
131
|
+
demo_commands.deploy,
|
|
132
|
+
local=local,
|
|
133
|
+
app=app,
|
|
134
|
+
name=name,
|
|
135
|
+
script=script,
|
|
136
|
+
)
|
|
135
137
|
|
|
136
138
|
@_dg.command("configure")
|
|
137
139
|
def demo_configure():
|
|
138
|
-
|
|
140
|
+
_run_demo_command(demo_commands.run)
|
|
139
141
|
|
|
140
142
|
@_dg.command("setup")
|
|
141
143
|
def demo_setup():
|
|
142
|
-
|
|
144
|
+
_run_demo_command(demo_commands.setup)
|
|
143
145
|
|
|
144
146
|
@_dg.command("run")
|
|
145
147
|
@click.option("--batch-size", type=int, default=None)
|
|
@@ -147,13 +149,10 @@ def register(cli):
|
|
|
147
149
|
@click.option("--model", type=str, default=None)
|
|
148
150
|
@click.option("--timeout", type=int, default=600)
|
|
149
151
|
def demo_run(batch_size: int | None, group_size: int | None, model: str | None, timeout: int):
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if timeout:
|
|
158
|
-
args.extend(["--timeout", str(timeout)])
|
|
159
|
-
_forward_to_new(args)
|
|
152
|
+
_run_demo_command(
|
|
153
|
+
demo_commands.run,
|
|
154
|
+
batch_size=batch_size,
|
|
155
|
+
group_size=group_size,
|
|
156
|
+
model=model,
|
|
157
|
+
timeout=timeout,
|
|
158
|
+
)
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
app =
|
|
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
|
-
|
|
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
|
-
|
|
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 = """
|