synth-ai 0.4.1__py3-none-any.whl → 0.4.4__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.
- synth_ai/__init__.py +13 -13
- synth_ai/cli/__init__.py +6 -15
- synth_ai/cli/commands/eval/__init__.py +6 -15
- synth_ai/cli/commands/eval/config.py +338 -0
- synth_ai/cli/commands/eval/core.py +236 -1091
- synth_ai/cli/commands/eval/runner.py +704 -0
- synth_ai/cli/commands/eval/validation.py +44 -117
- synth_ai/cli/commands/filter/core.py +7 -7
- synth_ai/cli/commands/filter/validation.py +2 -2
- synth_ai/cli/commands/smoke/core.py +7 -17
- synth_ai/cli/commands/status/__init__.py +1 -64
- synth_ai/cli/commands/status/client.py +50 -151
- synth_ai/cli/commands/status/config.py +3 -83
- synth_ai/cli/commands/status/errors.py +4 -13
- synth_ai/cli/commands/status/subcommands/__init__.py +2 -8
- synth_ai/cli/commands/status/subcommands/config.py +13 -0
- synth_ai/cli/commands/status/subcommands/files.py +18 -63
- synth_ai/cli/commands/status/subcommands/jobs.py +28 -311
- synth_ai/cli/commands/status/subcommands/models.py +18 -62
- synth_ai/cli/commands/status/subcommands/runs.py +16 -63
- synth_ai/cli/commands/status/subcommands/session.py +67 -172
- synth_ai/cli/commands/status/subcommands/summary.py +24 -32
- synth_ai/cli/commands/status/subcommands/utils.py +41 -0
- synth_ai/cli/commands/status/utils.py +16 -107
- synth_ai/cli/commands/train/__init__.py +18 -20
- synth_ai/cli/commands/train/errors.py +3 -3
- synth_ai/cli/commands/train/prompt_learning_validation.py +15 -16
- synth_ai/cli/commands/train/validation.py +7 -7
- synth_ai/cli/commands/train/{judge_schemas.py → verifier_schemas.py} +33 -34
- synth_ai/cli/commands/train/verifier_validation.py +235 -0
- synth_ai/cli/demo_apps/demo_task_apps/math/config.toml +0 -1
- synth_ai/cli/demo_apps/demo_task_apps/math/modal_task_app.py +2 -6
- synth_ai/cli/demo_apps/math/config.toml +0 -1
- synth_ai/cli/demo_apps/math/modal_task_app.py +2 -6
- synth_ai/cli/demo_apps/mipro/task_app.py +25 -47
- synth_ai/cli/lib/apps/task_app.py +12 -13
- synth_ai/cli/lib/task_app_discovery.py +6 -6
- synth_ai/cli/lib/train_cfgs.py +10 -10
- synth_ai/cli/task_apps/__init__.py +11 -0
- synth_ai/cli/task_apps/commands.py +7 -15
- synth_ai/core/env.py +12 -1
- synth_ai/core/errors.py +1 -2
- synth_ai/core/integrations/cloudflare.py +209 -33
- synth_ai/core/tracing_v3/abstractions.py +46 -0
- synth_ai/data/__init__.py +3 -30
- synth_ai/data/enums.py +1 -20
- synth_ai/data/rewards.py +100 -3
- synth_ai/products/graph_evolve/__init__.py +1 -2
- synth_ai/products/graph_evolve/config.py +16 -16
- synth_ai/products/graph_evolve/converters/__init__.py +3 -3
- synth_ai/products/graph_evolve/converters/openai_sft.py +7 -7
- synth_ai/products/graph_evolve/examples/hotpotqa/config.toml +1 -1
- synth_ai/products/graph_gepa/__init__.py +23 -0
- synth_ai/products/graph_gepa/converters/__init__.py +19 -0
- synth_ai/products/graph_gepa/converters/openai_sft.py +29 -0
- synth_ai/sdk/__init__.py +45 -35
- synth_ai/sdk/api/eval/__init__.py +33 -0
- synth_ai/sdk/api/eval/job.py +732 -0
- synth_ai/sdk/api/research_agent/__init__.py +276 -66
- synth_ai/sdk/api/train/builders.py +181 -0
- synth_ai/sdk/api/train/cli.py +41 -33
- synth_ai/sdk/api/train/configs/__init__.py +6 -4
- synth_ai/sdk/api/train/configs/prompt_learning.py +127 -33
- synth_ai/sdk/api/train/configs/rl.py +264 -16
- synth_ai/sdk/api/train/configs/sft.py +165 -1
- synth_ai/sdk/api/train/graph_validators.py +12 -12
- synth_ai/sdk/api/train/graphgen.py +169 -51
- synth_ai/sdk/api/train/graphgen_models.py +95 -45
- synth_ai/sdk/api/train/local_api.py +10 -0
- synth_ai/sdk/api/train/pollers.py +36 -0
- synth_ai/sdk/api/train/prompt_learning.py +390 -60
- synth_ai/sdk/api/train/rl.py +41 -5
- synth_ai/sdk/api/train/sft.py +2 -0
- synth_ai/sdk/api/train/task_app.py +20 -0
- synth_ai/sdk/api/train/validators.py +17 -17
- synth_ai/sdk/graphs/completions.py +239 -33
- synth_ai/sdk/{judging/schemas.py → graphs/verifier_schemas.py} +23 -23
- synth_ai/sdk/learning/__init__.py +35 -5
- synth_ai/sdk/learning/context_learning_client.py +531 -0
- synth_ai/sdk/learning/context_learning_types.py +294 -0
- synth_ai/sdk/learning/prompt_learning_client.py +1 -1
- synth_ai/sdk/learning/prompt_learning_types.py +2 -1
- synth_ai/sdk/learning/rl/__init__.py +0 -4
- synth_ai/sdk/learning/rl/contracts.py +0 -4
- synth_ai/sdk/localapi/__init__.py +40 -0
- synth_ai/sdk/localapi/apps/__init__.py +28 -0
- synth_ai/sdk/localapi/client.py +10 -0
- synth_ai/sdk/localapi/contracts.py +10 -0
- synth_ai/sdk/localapi/helpers.py +519 -0
- synth_ai/sdk/localapi/rollouts.py +93 -0
- synth_ai/sdk/localapi/server.py +29 -0
- synth_ai/sdk/localapi/template.py +49 -0
- synth_ai/sdk/streaming/handlers.py +6 -6
- synth_ai/sdk/streaming/streamer.py +10 -6
- synth_ai/sdk/task/__init__.py +18 -5
- synth_ai/sdk/task/apps/__init__.py +37 -1
- synth_ai/sdk/task/client.py +9 -1
- synth_ai/sdk/task/config.py +6 -11
- synth_ai/sdk/task/contracts.py +137 -95
- synth_ai/sdk/task/in_process.py +32 -22
- synth_ai/sdk/task/in_process_runner.py +9 -4
- synth_ai/sdk/task/rubrics/__init__.py +2 -3
- synth_ai/sdk/task/rubrics/loaders.py +4 -4
- synth_ai/sdk/task/rubrics/strict.py +3 -4
- synth_ai/sdk/task/server.py +76 -16
- synth_ai/sdk/task/trace_correlation_helpers.py +190 -139
- synth_ai/sdk/task/validators.py +34 -49
- synth_ai/sdk/training/__init__.py +7 -16
- synth_ai/sdk/tunnels/__init__.py +118 -0
- synth_ai/sdk/tunnels/cleanup.py +83 -0
- synth_ai/sdk/tunnels/ports.py +120 -0
- synth_ai/sdk/tunnels/tunneled_api.py +363 -0
- {synth_ai-0.4.1.dist-info → synth_ai-0.4.4.dist-info}/METADATA +71 -4
- {synth_ai-0.4.1.dist-info → synth_ai-0.4.4.dist-info}/RECORD +118 -128
- synth_ai/cli/commands/baseline/__init__.py +0 -12
- synth_ai/cli/commands/baseline/core.py +0 -636
- synth_ai/cli/commands/baseline/list.py +0 -94
- synth_ai/cli/commands/eval/errors.py +0 -81
- synth_ai/cli/commands/status/formatters.py +0 -164
- synth_ai/cli/commands/status/subcommands/pricing.py +0 -23
- synth_ai/cli/commands/status/subcommands/usage.py +0 -203
- synth_ai/cli/commands/train/judge_validation.py +0 -305
- synth_ai/cli/usage.py +0 -159
- synth_ai/data/specs.py +0 -36
- synth_ai/sdk/api/research_agent/cli.py +0 -428
- synth_ai/sdk/api/research_agent/config.py +0 -357
- synth_ai/sdk/api/research_agent/job.py +0 -717
- synth_ai/sdk/baseline/__init__.py +0 -25
- synth_ai/sdk/baseline/config.py +0 -209
- synth_ai/sdk/baseline/discovery.py +0 -216
- synth_ai/sdk/baseline/execution.py +0 -154
- synth_ai/sdk/judging/__init__.py +0 -15
- synth_ai/sdk/judging/base.py +0 -24
- synth_ai/sdk/judging/client.py +0 -191
- synth_ai/sdk/judging/types.py +0 -42
- synth_ai/sdk/research_agent/__init__.py +0 -34
- synth_ai/sdk/research_agent/container_builder.py +0 -328
- synth_ai/sdk/research_agent/container_spec.py +0 -198
- synth_ai/sdk/research_agent/defaults.py +0 -34
- synth_ai/sdk/research_agent/results_collector.py +0 -69
- synth_ai/sdk/specs/__init__.py +0 -46
- synth_ai/sdk/specs/dataclasses.py +0 -149
- synth_ai/sdk/specs/loader.py +0 -144
- synth_ai/sdk/specs/serializer.py +0 -199
- synth_ai/sdk/specs/validation.py +0 -250
- synth_ai/sdk/tracing/__init__.py +0 -39
- synth_ai/sdk/usage/__init__.py +0 -37
- synth_ai/sdk/usage/client.py +0 -171
- synth_ai/sdk/usage/models.py +0 -261
- {synth_ai-0.4.1.dist-info → synth_ai-0.4.4.dist-info}/WHEEL +0 -0
- {synth_ai-0.4.1.dist-info → synth_ai-0.4.4.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.4.1.dist-info → synth_ai-0.4.4.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.4.1.dist-info → synth_ai-0.4.4.dist-info}/top_level.txt +0 -0
|
@@ -1,79 +1,35 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Status models subcommand."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
|
|
7
7
|
import click
|
|
8
|
-
from rich.json import JSON
|
|
9
8
|
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
from ..formatters import console, models_table, print_json
|
|
13
|
-
from ..utils import bail, common_options, resolve_context_config
|
|
9
|
+
from .config import StatusConfig
|
|
10
|
+
from .utils import StatusAPIClient, print_json
|
|
14
11
|
|
|
15
12
|
|
|
16
|
-
@click.group("models"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
ctx.ensure_object(dict)
|
|
13
|
+
@click.group("models")
|
|
14
|
+
def models_group() -> None:
|
|
15
|
+
return None
|
|
20
16
|
|
|
21
17
|
|
|
22
18
|
@models_group.command("list")
|
|
23
|
-
@
|
|
24
|
-
@click.option("--
|
|
25
|
-
@click.option("--
|
|
26
|
-
|
|
27
|
-
@click.pass_context
|
|
28
|
-
def list_models(
|
|
29
|
-
ctx: click.Context,
|
|
30
|
-
base_url: str | None,
|
|
31
|
-
api_key: str | None,
|
|
32
|
-
timeout: float,
|
|
33
|
-
limit: int | None,
|
|
34
|
-
model_type: str | None,
|
|
35
|
-
output_json: bool,
|
|
36
|
-
) -> None:
|
|
37
|
-
cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
|
|
38
|
-
|
|
19
|
+
@click.option("--limit", default=None, type=int)
|
|
20
|
+
@click.option("--type", "model_type", default=None)
|
|
21
|
+
@click.option("--json", "as_json", is_flag=True, default=False)
|
|
22
|
+
def list_models(limit: int | None, model_type: str | None, as_json: bool) -> None:
|
|
39
23
|
async def _run() -> None:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
except StatusAPIError as exc:
|
|
48
|
-
bail(f"Backend error: {exc}")
|
|
24
|
+
config = StatusConfig()
|
|
25
|
+
async with StatusAPIClient(config) as client:
|
|
26
|
+
data = await client.list_models(limit=limit, model_type=model_type) # type: ignore[attr-defined]
|
|
27
|
+
if as_json:
|
|
28
|
+
print_json(data)
|
|
29
|
+
else:
|
|
30
|
+
click.echo(data)
|
|
49
31
|
|
|
50
32
|
asyncio.run(_run())
|
|
51
33
|
|
|
52
34
|
|
|
53
|
-
|
|
54
|
-
@common_options()
|
|
55
|
-
@click.argument("model_id")
|
|
56
|
-
@click.option("--json", "output_json", is_flag=True)
|
|
57
|
-
@click.pass_context
|
|
58
|
-
def get_model(
|
|
59
|
-
ctx: click.Context,
|
|
60
|
-
base_url: str | None,
|
|
61
|
-
api_key: str | None,
|
|
62
|
-
timeout: float,
|
|
63
|
-
model_id: str,
|
|
64
|
-
output_json: bool,
|
|
65
|
-
) -> None:
|
|
66
|
-
cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
|
|
67
|
-
|
|
68
|
-
async def _run() -> None:
|
|
69
|
-
try:
|
|
70
|
-
async with StatusAPIClient(cfg) as client:
|
|
71
|
-
model = await client.get_model(model_id)
|
|
72
|
-
if output_json:
|
|
73
|
-
print_json(model)
|
|
74
|
-
else:
|
|
75
|
-
console.print(JSON.from_data(model))
|
|
76
|
-
except StatusAPIError as exc:
|
|
77
|
-
bail(f"Backend error: {exc}")
|
|
78
|
-
|
|
79
|
-
asyncio.run(_run())
|
|
35
|
+
__all__ = ["models_group"]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Status runs subcommand."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -6,76 +6,29 @@ import asyncio
|
|
|
6
6
|
|
|
7
7
|
import click
|
|
8
8
|
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
from ..formatters import console, events_panel, print_json, runs_table
|
|
12
|
-
from ..utils import bail, common_options, parse_relative_time, resolve_context_config
|
|
9
|
+
from .config import StatusConfig
|
|
10
|
+
from .utils import StatusAPIClient, print_json
|
|
13
11
|
|
|
14
12
|
|
|
15
|
-
@click.group("runs"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
ctx.ensure_object(dict)
|
|
13
|
+
@click.group("runs")
|
|
14
|
+
def runs_group() -> None:
|
|
15
|
+
return None
|
|
19
16
|
|
|
20
17
|
|
|
21
18
|
@runs_group.command("list")
|
|
22
|
-
@common_options()
|
|
23
19
|
@click.argument("job_id")
|
|
24
|
-
@click.option("--json", "
|
|
25
|
-
|
|
26
|
-
def list_runs(
|
|
27
|
-
ctx: click.Context,
|
|
28
|
-
base_url: str | None,
|
|
29
|
-
api_key: str | None,
|
|
30
|
-
timeout: float,
|
|
31
|
-
job_id: str,
|
|
32
|
-
output_json: bool,
|
|
33
|
-
) -> None:
|
|
34
|
-
cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
|
|
35
|
-
|
|
20
|
+
@click.option("--json", "as_json", is_flag=True, default=False)
|
|
21
|
+
def list_runs(job_id: str, as_json: bool) -> None:
|
|
36
22
|
async def _run() -> None:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
except StatusAPIError as exc:
|
|
45
|
-
bail(f"Backend error: {exc}")
|
|
23
|
+
config = StatusConfig()
|
|
24
|
+
async with StatusAPIClient(config) as client:
|
|
25
|
+
data = await client.list_job_runs(job_id) # type: ignore[attr-defined]
|
|
26
|
+
if as_json:
|
|
27
|
+
print_json(data)
|
|
28
|
+
else:
|
|
29
|
+
click.echo(data)
|
|
46
30
|
|
|
47
31
|
asyncio.run(_run())
|
|
48
32
|
|
|
49
33
|
|
|
50
|
-
|
|
51
|
-
@common_options()
|
|
52
|
-
@click.argument("job_id")
|
|
53
|
-
@click.option("--run", "run_id", required=True, help="Run identifier (number or ID) to inspect.")
|
|
54
|
-
@click.option("--since", help="Filter events after the supplied timestamp/relative offset.")
|
|
55
|
-
@click.option("--json", "output_json", is_flag=True)
|
|
56
|
-
@click.pass_context
|
|
57
|
-
def run_logs(
|
|
58
|
-
ctx: click.Context,
|
|
59
|
-
base_url: str | None,
|
|
60
|
-
api_key: str | None,
|
|
61
|
-
timeout: float,
|
|
62
|
-
job_id: str,
|
|
63
|
-
run_id: str,
|
|
64
|
-
since: str | None,
|
|
65
|
-
output_json: bool,
|
|
66
|
-
) -> None:
|
|
67
|
-
cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
|
|
68
|
-
since_filter = parse_relative_time(since)
|
|
69
|
-
|
|
70
|
-
async def _run() -> None:
|
|
71
|
-
try:
|
|
72
|
-
async with StatusAPIClient(cfg) as client:
|
|
73
|
-
events = await client.get_job_events(job_id, since=since_filter, run_id=run_id)
|
|
74
|
-
if output_json:
|
|
75
|
-
print_json(events)
|
|
76
|
-
else:
|
|
77
|
-
console.print(events_panel(events))
|
|
78
|
-
except StatusAPIError as exc:
|
|
79
|
-
bail(f"Backend error: {exc}")
|
|
80
|
-
|
|
81
|
-
asyncio.run(_run())
|
|
34
|
+
__all__ = ["runs_group"]
|
|
@@ -1,182 +1,77 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Status session subcommand."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
-
|
|
6
|
+
import os
|
|
7
|
+
from decimal import Decimal
|
|
7
8
|
|
|
8
9
|
import click
|
|
9
|
-
|
|
10
|
-
from
|
|
11
|
-
|
|
12
|
-
from synth_ai.cli.
|
|
13
|
-
from synth_ai.cli.local.session import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
10
|
+
|
|
11
|
+
from synth_ai.cli.commands.artifacts.config import resolve_backend_config
|
|
12
|
+
from synth_ai.cli.local.session.client import AgentSessionClient
|
|
13
|
+
from synth_ai.cli.local.session.exceptions import SessionNotFoundError
|
|
14
|
+
from synth_ai.cli.local.session.models import AgentSession
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _format_currency(value: Decimal) -> str:
|
|
18
|
+
return f"${value:.2f}"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _format_int(value: Decimal | int) -> str:
|
|
22
|
+
return f"{int(value):,}"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _session_exceeded(session: AgentSession) -> bool:
|
|
26
|
+
if session.status == "limit_exceeded":
|
|
27
|
+
return True
|
|
28
|
+
for limit in session.limits:
|
|
29
|
+
if limit.current_usage >= limit.limit_value:
|
|
30
|
+
return True
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _render_session(session: AgentSession) -> None:
|
|
35
|
+
click.echo(f"Session: {session.session_id}")
|
|
36
|
+
click.echo(f"Status: {session.status}")
|
|
37
|
+
click.echo(f"Usage: {_format_currency(session.usage.cost_usd)}")
|
|
38
|
+
click.echo(f"Tokens: {_format_int(session.usage.tokens)}")
|
|
39
|
+
for limit in session.limits:
|
|
40
|
+
if limit.metric_type == "cost_usd":
|
|
41
|
+
click.echo(f"Limit: {_format_currency(limit.limit_value)}")
|
|
42
|
+
elif limit.metric_type == "tokens":
|
|
43
|
+
click.echo(f"Limit: {_format_int(limit.limit_value)}")
|
|
44
|
+
if _session_exceeded(session):
|
|
45
|
+
click.echo("✗ EXCEEDED")
|
|
46
|
+
click.echo("WARNING: Session limits exceeded!")
|
|
47
|
+
else:
|
|
48
|
+
click.echo("✓ OK")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@click.command("session")
|
|
52
|
+
@click.option("--session-id", default="", help="Session ID to inspect.")
|
|
53
|
+
def session_status_cmd(session_id: str) -> None:
|
|
53
54
|
async def _run() -> None:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return
|
|
66
|
-
client = AgentSessionClient(f"{cfg.base_url}/api", cfg.api_key)
|
|
67
|
-
try:
|
|
68
|
-
async with client._http:
|
|
69
|
-
sessions = await client.list(status="active", limit=1)
|
|
70
|
-
if sessions:
|
|
71
|
-
session_id_to_use = sessions[0].session_id
|
|
72
|
-
else:
|
|
73
|
-
console.print("[yellow]No active session found.[/yellow]")
|
|
74
|
-
console.print("Create a session with: [cyan]uvx synth-ai codex[/cyan] or [cyan]uvx synth-ai opencode[/cyan]")
|
|
75
|
-
return
|
|
76
|
-
except Exception as e:
|
|
77
|
-
console.print(f"[red]Error fetching active session: {e}[/red]")
|
|
78
|
-
return
|
|
79
|
-
else:
|
|
80
|
-
session_id_to_use = session_id
|
|
81
|
-
|
|
82
|
-
if not cfg.api_key:
|
|
83
|
-
console.print("[red]API key is required. Set SYNTH_API_KEY environment variable.[/red]")
|
|
55
|
+
resolved_session_id = session_id or os.getenv("SYNTH_SESSION_ID", "")
|
|
56
|
+
config = resolve_backend_config()
|
|
57
|
+
client = AgentSessionClient(base_url=config.base_url, api_key=config.api_key)
|
|
58
|
+
|
|
59
|
+
if resolved_session_id:
|
|
60
|
+
try:
|
|
61
|
+
session = await client.get(resolved_session_id)
|
|
62
|
+
except SessionNotFoundError:
|
|
63
|
+
click.echo("Session not found")
|
|
64
|
+
return
|
|
65
|
+
_render_session(session)
|
|
84
66
|
return
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
table.add_column("Value", style="green")
|
|
93
|
-
|
|
94
|
-
table.add_row("Session ID", session.session_id)
|
|
95
|
-
table.add_row("Status", f"[bold]{session.status}[/bold]")
|
|
96
|
-
table.add_row("Created", session.created_at.strftime("%Y-%m-%d %H:%M:%S UTC"))
|
|
97
|
-
if session.ended_at:
|
|
98
|
-
table.add_row("Ended", session.ended_at.strftime("%Y-%m-%d %H:%M:%S UTC"))
|
|
99
|
-
if session.expires_at:
|
|
100
|
-
table.add_row("Expires", session.expires_at.strftime("%Y-%m-%d %H:%M:%S UTC"))
|
|
101
|
-
|
|
102
|
-
console.print(table)
|
|
103
|
-
console.print()
|
|
104
|
-
|
|
105
|
-
# Display usage summary
|
|
106
|
-
usage_table = Table(title="Usage Summary", show_header=True, header_style="bold cyan")
|
|
107
|
-
usage_table.add_column("Metric", style="cyan")
|
|
108
|
-
usage_table.add_column("Value", style="green")
|
|
109
|
-
|
|
110
|
-
usage_table.add_row("Tokens", f"{session.usage.tokens:,}")
|
|
111
|
-
usage_table.add_row("Cost (USD)", f"${session.usage.cost_usd:.4f}")
|
|
112
|
-
usage_table.add_row("GPU Hours", f"{session.usage.gpu_hours:.4f}")
|
|
113
|
-
usage_table.add_row("API Calls", f"{session.usage.api_calls:,}")
|
|
114
|
-
|
|
115
|
-
console.print(usage_table)
|
|
116
|
-
console.print()
|
|
117
|
-
|
|
118
|
-
# Display limits
|
|
119
|
-
if session.limits:
|
|
120
|
-
limits_table = Table(title="Session Limits", show_header=True, header_style="bold cyan")
|
|
121
|
-
limits_table.add_column("Type", style="cyan")
|
|
122
|
-
limits_table.add_column("Limit", style="green")
|
|
123
|
-
limits_table.add_column("Used", style="yellow")
|
|
124
|
-
limits_table.add_column("Remaining", style="green")
|
|
125
|
-
limits_table.add_column("Status", style="bold")
|
|
126
|
-
|
|
127
|
-
for limit in session.limits:
|
|
128
|
-
remaining = limit.remaining
|
|
129
|
-
status = "[green]✓ OK[/green]" if remaining > 0 else "[red]✗ EXCEEDED[/red]"
|
|
130
|
-
if limit.metric_type == "cost_usd":
|
|
131
|
-
limits_table.add_row(
|
|
132
|
-
"Cost (USD)",
|
|
133
|
-
f"${limit.limit_value:.2f}",
|
|
134
|
-
f"${limit.current_usage:.4f}",
|
|
135
|
-
f"${remaining:.4f}",
|
|
136
|
-
status
|
|
137
|
-
)
|
|
138
|
-
elif limit.metric_type == "tokens":
|
|
139
|
-
limits_table.add_row(
|
|
140
|
-
"Tokens",
|
|
141
|
-
f"{limit.limit_value:,.0f}",
|
|
142
|
-
f"{limit.current_usage:,.0f}",
|
|
143
|
-
f"{remaining:,.0f}",
|
|
144
|
-
status
|
|
145
|
-
)
|
|
146
|
-
elif limit.metric_type == "gpu_hours":
|
|
147
|
-
limits_table.add_row(
|
|
148
|
-
"GPU Hours",
|
|
149
|
-
f"{limit.limit_value:.2f}",
|
|
150
|
-
f"{limit.current_usage:.4f}",
|
|
151
|
-
f"{remaining:.4f}",
|
|
152
|
-
status
|
|
153
|
-
)
|
|
154
|
-
else:
|
|
155
|
-
limits_table.add_row(
|
|
156
|
-
limit.metric_type,
|
|
157
|
-
str(limit.limit_value),
|
|
158
|
-
str(limit.current_usage),
|
|
159
|
-
str(remaining),
|
|
160
|
-
status
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
console.print(limits_table)
|
|
164
|
-
|
|
165
|
-
# Warn if any limit exceeded
|
|
166
|
-
exceeded = [limit for limit in session.limits if limit.current_usage >= limit.limit_value]
|
|
167
|
-
if exceeded:
|
|
168
|
-
console.print()
|
|
169
|
-
console.print("[red][bold]⚠ WARNING: Session limits exceeded![/bold][/red]")
|
|
170
|
-
console.print("The session will reject new requests until limits are increased.")
|
|
171
|
-
for limit in exceeded:
|
|
172
|
-
console.print(f" - {limit.metric_type}: {limit.current_usage} >= {limit.limit_value}")
|
|
173
|
-
else:
|
|
174
|
-
console.print("[yellow]No limits configured for this session.[/yellow]")
|
|
175
|
-
|
|
176
|
-
except SessionNotFoundError:
|
|
177
|
-
console.print(f"[red]Session not found: {session_id_to_use}[/red]")
|
|
178
|
-
except Exception as e:
|
|
179
|
-
console.print(f"[red]Error fetching session: {e}[/red]")
|
|
180
|
-
|
|
67
|
+
|
|
68
|
+
sessions = await client.list(status="active")
|
|
69
|
+
if not sessions:
|
|
70
|
+
click.echo("No active session found")
|
|
71
|
+
return
|
|
72
|
+
_render_session(sessions[0])
|
|
73
|
+
|
|
181
74
|
asyncio.run(_run())
|
|
182
75
|
|
|
76
|
+
|
|
77
|
+
__all__ = ["session_status_cmd"]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Status summary command."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -6,42 +6,34 @@ import asyncio
|
|
|
6
6
|
|
|
7
7
|
import click
|
|
8
8
|
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def summary_command(
|
|
20
|
-
ctx: click.Context,
|
|
21
|
-
base_url: str | None,
|
|
22
|
-
api_key: str | None,
|
|
23
|
-
timeout: float,
|
|
24
|
-
limit: int,
|
|
25
|
-
) -> None:
|
|
26
|
-
cfg = resolve_context_config(ctx, base_url=base_url, api_key=api_key, timeout=timeout)
|
|
27
|
-
|
|
28
|
-
async def _run() -> tuple[list[dict[str, object]], list[dict[str, object]], list[dict[str, object]]]:
|
|
29
|
-
async with StatusAPIClient(cfg) as client:
|
|
9
|
+
from synth_ai.cli.commands.status.errors import StatusAPIError
|
|
10
|
+
from .config import StatusConfig
|
|
11
|
+
from .utils import StatusAPIClient
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.command("summary")
|
|
15
|
+
def summary_command() -> None:
|
|
16
|
+
async def _run() -> None:
|
|
17
|
+
config = StatusConfig()
|
|
18
|
+
async with StatusAPIClient(config) as client:
|
|
30
19
|
try:
|
|
31
|
-
jobs = await client.list_jobs(
|
|
20
|
+
jobs = await client.list_jobs() # type: ignore[attr-defined]
|
|
32
21
|
except StatusAPIError:
|
|
33
22
|
jobs = []
|
|
34
23
|
try:
|
|
35
|
-
|
|
24
|
+
await client.list_models() # type: ignore[attr-defined]
|
|
36
25
|
except StatusAPIError:
|
|
37
|
-
|
|
26
|
+
pass
|
|
38
27
|
try:
|
|
39
|
-
|
|
28
|
+
await client.list_files() # type: ignore[attr-defined]
|
|
40
29
|
except StatusAPIError:
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
click.echo("Training Jobs")
|
|
33
|
+
for job in jobs:
|
|
34
|
+
click.echo(str(job))
|
|
35
|
+
|
|
36
|
+
asyncio.run(_run())
|
|
37
|
+
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
console.print(jobs_table(jobs[:limit]))
|
|
46
|
-
console.print(models_table(models[:limit]))
|
|
47
|
-
console.print(files_table(files[:limit]))
|
|
39
|
+
__all__ = ["summary_command"]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Shared helpers for status subcommands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class StatusAPIClient:
|
|
12
|
+
def __init__(self, config: Any) -> None:
|
|
13
|
+
self.config = config
|
|
14
|
+
|
|
15
|
+
async def __aenter__(self) -> "StatusAPIClient":
|
|
16
|
+
return self
|
|
17
|
+
|
|
18
|
+
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
async def list_jobs(self, **_: Any) -> list[dict[str, Any]]:
|
|
22
|
+
return []
|
|
23
|
+
|
|
24
|
+
async def get_job_events(self, *_: Any, **__: Any) -> list[dict[str, Any]]:
|
|
25
|
+
return []
|
|
26
|
+
|
|
27
|
+
async def get_file(self, *_: Any, **__: Any) -> dict[str, Any]:
|
|
28
|
+
return {}
|
|
29
|
+
|
|
30
|
+
async def list_models(self, **_: Any) -> list[dict[str, Any]]:
|
|
31
|
+
return []
|
|
32
|
+
|
|
33
|
+
async def list_job_runs(self, *_: Any, **__: Any) -> list[dict[str, Any]]:
|
|
34
|
+
return []
|
|
35
|
+
|
|
36
|
+
async def list_files(self, **_: Any) -> list[dict[str, Any]]:
|
|
37
|
+
return []
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def print_json(data: Any) -> None:
|
|
41
|
+
click.echo(json.dumps(data))
|