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
synth_ai/sdk/task/in_process.py
CHANGED
|
@@ -409,40 +409,51 @@ async def _verify_preconfigured_url_ready(
|
|
|
409
409
|
|
|
410
410
|
|
|
411
411
|
class InProcessTaskApp:
|
|
412
|
-
"""
|
|
413
|
-
|
|
414
|
-
|
|
412
|
+
"""Context manager for running Local APIs in-process with automatic tunneling.
|
|
413
|
+
|
|
415
414
|
This class simplifies local development and demos by:
|
|
416
|
-
1. Starting a
|
|
415
|
+
1. Starting a Local API server in a background thread
|
|
417
416
|
2. Opening a tunnel automatically (Cloudflare by default, or use preconfigured URL)
|
|
418
|
-
3. Providing the tunnel URL for GEPA/MIPRO jobs
|
|
417
|
+
3. Providing the tunnel URL for GEPA/MIPRO/RL jobs
|
|
419
418
|
4. Cleaning up everything on exit
|
|
420
|
-
|
|
419
|
+
|
|
420
|
+
(Alias: also known as "task app" in older documentation)
|
|
421
|
+
|
|
421
422
|
Supports multiple input methods:
|
|
422
423
|
- FastAPI app instance (most direct)
|
|
423
424
|
- TaskAppConfig object
|
|
424
425
|
- Config factory function (Callable[[], TaskAppConfig])
|
|
425
|
-
-
|
|
426
|
-
|
|
426
|
+
- Local API file path (fallback for compatibility)
|
|
427
|
+
|
|
427
428
|
Tunnel modes:
|
|
428
429
|
- "quick": Cloudflare quick tunnel (default for local dev)
|
|
429
430
|
- "named": Cloudflare named/managed tunnel
|
|
430
431
|
- "local": No tunnel, use localhost URL directly
|
|
431
432
|
- "preconfigured": Use externally-provided URL (set via preconfigured_url param or
|
|
432
433
|
SYNTH_TASK_APP_URL env var). Useful for ngrok or other external tunnel providers.
|
|
433
|
-
|
|
434
|
+
|
|
435
|
+
Attributes:
|
|
436
|
+
url: The public URL of the running Local API (tunnel URL or localhost).
|
|
437
|
+
Available after entering the context manager.
|
|
438
|
+
local_url: The local URL (http://host:port) where the server is running.
|
|
439
|
+
port: The actual port the server is bound to (may differ from requested
|
|
440
|
+
port if auto_find_port=True).
|
|
441
|
+
host: The host the server is bound to.
|
|
442
|
+
tunnel_mode: The tunnel mode being used.
|
|
443
|
+
is_running: Whether the server is currently running.
|
|
444
|
+
|
|
434
445
|
Example:
|
|
435
446
|
```python
|
|
436
447
|
from synth_ai.sdk.task.in_process import InProcessTaskApp
|
|
437
448
|
from heartdisease_task_app import build_config
|
|
438
|
-
|
|
449
|
+
|
|
439
450
|
# Default: use Cloudflare quick tunnel
|
|
440
451
|
async with InProcessTaskApp(
|
|
441
452
|
config_factory=build_config,
|
|
442
453
|
port=8114,
|
|
443
454
|
) as task_app:
|
|
444
|
-
print(f"
|
|
445
|
-
|
|
455
|
+
print(f"Local API running at: {task_app.url}")
|
|
456
|
+
|
|
446
457
|
# Use preconfigured URL (e.g., from ngrok, localtunnel, etc.)
|
|
447
458
|
async with InProcessTaskApp(
|
|
448
459
|
config_factory=build_config,
|
|
@@ -450,7 +461,7 @@ class InProcessTaskApp:
|
|
|
450
461
|
tunnel_mode="preconfigured",
|
|
451
462
|
preconfigured_url="https://abc123.ngrok.io",
|
|
452
463
|
) as task_app:
|
|
453
|
-
print(f"
|
|
464
|
+
print(f"Local API running at: {task_app.url}")
|
|
454
465
|
```
|
|
455
466
|
"""
|
|
456
467
|
|
|
@@ -475,14 +486,13 @@ class InProcessTaskApp:
|
|
|
475
486
|
on_start: Optional[Callable[[InProcessTaskApp], None]] = None,
|
|
476
487
|
on_stop: Optional[Callable[[InProcessTaskApp], None]] = None,
|
|
477
488
|
):
|
|
478
|
-
"""
|
|
479
|
-
|
|
480
|
-
|
|
489
|
+
"""Initialize in-process Local API.
|
|
490
|
+
|
|
481
491
|
Args:
|
|
482
492
|
app: FastAPI app instance (most direct)
|
|
483
493
|
config: TaskAppConfig object
|
|
484
494
|
config_factory: Callable that returns TaskAppConfig
|
|
485
|
-
task_app_path: Path to
|
|
495
|
+
task_app_path: Path to Local API .py file (fallback, alias: task app)
|
|
486
496
|
port: Local port to run server on
|
|
487
497
|
host: Host to bind to (default: 127.0.0.1, use 0.0.0.0 for external access)
|
|
488
498
|
tunnel_mode: Tunnel mode - "quick", "named", "local", or "preconfigured"
|
|
@@ -594,7 +604,7 @@ class InProcessTaskApp:
|
|
|
594
604
|
else:
|
|
595
605
|
self._prefetched_tunnel_config = None
|
|
596
606
|
|
|
597
|
-
logger.
|
|
607
|
+
logger.debug(f"Starting in-process task app on {self.host}:{self.port}")
|
|
598
608
|
|
|
599
609
|
# For named tunnels, the port is baked into the tunnel config - we MUST use it
|
|
600
610
|
tunnel_config = getattr(self, "_prefetched_tunnel_config", None) or {}
|
|
@@ -624,7 +634,7 @@ class InProcessTaskApp:
|
|
|
624
634
|
f"Port {self.port} is in use, attempting to find available port..."
|
|
625
635
|
)
|
|
626
636
|
self.port = _find_available_port(self.host, self.port)
|
|
627
|
-
logger.
|
|
637
|
+
logger.debug(f"Using port {self.port} instead")
|
|
628
638
|
else:
|
|
629
639
|
# Try to kill process on port
|
|
630
640
|
logger.warning(
|
|
@@ -683,7 +693,7 @@ class InProcessTaskApp:
|
|
|
683
693
|
f"Task app at {self._task_app_path} must expose either:\n"
|
|
684
694
|
f" - An ASGI app via `app = FastAPI(...)` or factory function\n"
|
|
685
695
|
f" - A `build_config()` function that returns TaskAppConfig\n"
|
|
686
|
-
f" - Be registered with register_task_app()"
|
|
696
|
+
f" - Be registered with register_local_api() or register_task_app()"
|
|
687
697
|
) from None
|
|
688
698
|
|
|
689
699
|
# 2. Start uvicorn in background thread
|
|
@@ -719,7 +729,7 @@ class InProcessTaskApp:
|
|
|
719
729
|
await wait_for_health_check(
|
|
720
730
|
self.host, self.port, api_key, timeout=self.health_check_timeout
|
|
721
731
|
)
|
|
722
|
-
logger.
|
|
732
|
+
logger.debug(f"Health check passed for {self.host}:{self.port}")
|
|
723
733
|
|
|
724
734
|
# 4. Determine tunnel mode (env var can override)
|
|
725
735
|
mode = os.getenv("SYNTH_TUNNEL_MODE", self.tunnel_mode)
|
|
@@ -774,7 +784,7 @@ class InProcessTaskApp:
|
|
|
774
784
|
# Local mode: skip tunnel, use localhost
|
|
775
785
|
self.url = f"http://{self.host}:{self.port}"
|
|
776
786
|
self._tunnel_proc = None
|
|
777
|
-
logger.
|
|
787
|
+
logger.debug(f"Using local mode: {self.url}")
|
|
778
788
|
elif mode == "named":
|
|
779
789
|
# Named tunnel mode: fully automatic managed tunnel
|
|
780
790
|
# 1. Check for existing tunnel
|
|
@@ -31,7 +31,7 @@ from synth_ai.core.env import get_backend_from_env
|
|
|
31
31
|
from synth_ai.core.telemetry import log_info
|
|
32
32
|
from synth_ai.sdk.api.train.prompt_learning import PromptLearningJob
|
|
33
33
|
from synth_ai.sdk.api.train.rl import RLJob
|
|
34
|
-
from synth_ai.sdk.api.train.
|
|
34
|
+
from synth_ai.sdk.api.train.local_api import LocalAPIHealth, check_local_api_health
|
|
35
35
|
from synth_ai.sdk.api.train.utils import ensure_api_base
|
|
36
36
|
from synth_ai.sdk.task.in_process import InProcessTaskApp
|
|
37
37
|
|
|
@@ -52,7 +52,7 @@ class InProcessJobResult:
|
|
|
52
52
|
status: Dict[str, Any]
|
|
53
53
|
task_app_url: str
|
|
54
54
|
backend_url: str
|
|
55
|
-
task_app_health:
|
|
55
|
+
task_app_health: LocalAPIHealth | None = None
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
def _normalize_base_url(url: str) -> str:
|
|
@@ -234,9 +234,14 @@ async def run_in_process_job(
|
|
|
234
234
|
should_skip_health_check = skip_tunnel_verification or dns_verified_by_backend
|
|
235
235
|
if should_skip_health_check:
|
|
236
236
|
reason = "tunnel verification disabled" if skip_tunnel_verification else "backend verified DNS"
|
|
237
|
-
health =
|
|
237
|
+
health = LocalAPIHealth(
|
|
238
|
+
ok=True,
|
|
239
|
+
health_status=200,
|
|
240
|
+
task_info_status=200,
|
|
241
|
+
detail=f"Skipped ({reason})",
|
|
242
|
+
)
|
|
238
243
|
else:
|
|
239
|
-
health =
|
|
244
|
+
health = check_local_api_health(task_url, resolved_task_app_key)
|
|
240
245
|
if not health.ok:
|
|
241
246
|
raise RuntimeError(f"Task app health check failed for {task_url}: {health.detail}")
|
|
242
247
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This module provides:
|
|
4
4
|
- Flexible rubric models (Criterion, Rubric) for general task app use
|
|
5
|
-
- Strict validators (StrictCriterion, StrictRubric) for step-wise
|
|
5
|
+
- Strict validators (StrictCriterion, StrictRubric) for step-wise verifiers
|
|
6
6
|
- Loading utilities supporting JSON, YAML, and HTTP sources
|
|
7
7
|
- Blending utilities for composing rubrics
|
|
8
8
|
- Scoring utilities for events and outcomes
|
|
@@ -16,7 +16,7 @@ from .models import Criterion, Rubric
|
|
|
16
16
|
# Scoring
|
|
17
17
|
from .scoring import score_events_against_rubric, score_outcome_against_rubric
|
|
18
18
|
|
|
19
|
-
# Strict validators (for
|
|
19
|
+
# Strict validators (for verifier configs)
|
|
20
20
|
from .strict import (
|
|
21
21
|
StrictCriterion,
|
|
22
22
|
StrictRubric,
|
|
@@ -52,4 +52,3 @@ RubricSpec = StrictRubric
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
|
|
@@ -62,7 +62,7 @@ def load_rubric(source: str | dict[str, Any] | Rubric | None) -> Rubric | None:
|
|
|
62
62
|
Parsed Rubric instance or None if source is None
|
|
63
63
|
|
|
64
64
|
Raises:
|
|
65
|
-
ValueError: If the rubric format is incorrect (e.g., backend
|
|
65
|
+
ValueError: If the rubric format is incorrect (e.g., backend verifier format)
|
|
66
66
|
ValidationError: If the rubric fails schema validation
|
|
67
67
|
"""
|
|
68
68
|
if source is None:
|
|
@@ -77,7 +77,7 @@ def load_rubric(source: str | dict[str, Any] | Rubric | None) -> Rubric | None:
|
|
|
77
77
|
text, suffix = _load_text(str(source))
|
|
78
78
|
data = _parse_structured(text, suffix)
|
|
79
79
|
|
|
80
|
-
# Check if this looks like a backend
|
|
80
|
+
# Check if this looks like a backend verifier rubric (wrong format)
|
|
81
81
|
if (
|
|
82
82
|
isinstance(data, dict)
|
|
83
83
|
and "event" in data
|
|
@@ -88,9 +88,9 @@ def load_rubric(source: str | dict[str, Any] | Rubric | None) -> Rubric | None:
|
|
|
88
88
|
):
|
|
89
89
|
source_hint = f" ({source})" if isinstance(source, str) else ""
|
|
90
90
|
raise ValueError(
|
|
91
|
-
f"Rubric appears to be in backend
|
|
91
|
+
f"Rubric appears to be in backend verifier format (has 'event'/'outcome' keys){source_hint}. "
|
|
92
92
|
f"Task apps require rubrics with 'version', 'goal_text', and 'criteria' fields. "
|
|
93
|
-
f"Backend
|
|
93
|
+
f"Backend verifier rubrics should be named '*_backend_verifier.json' and loaded by verifier functions."
|
|
94
94
|
)
|
|
95
95
|
|
|
96
96
|
return Rubric.model_validate(data)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
"""Strict rubric validators for step-wise
|
|
1
|
+
"""Strict rubric validators for step-wise verifiers.
|
|
2
2
|
|
|
3
3
|
These validators enforce stricter constraints than the general-purpose rubrics:
|
|
4
4
|
- Weights must be ≤ 1.0 and sum to exactly 1.0
|
|
5
5
|
- Only weighted_sum aggregation is allowed
|
|
6
6
|
- All required fields must be non-empty
|
|
7
7
|
|
|
8
|
-
Used primarily for validation in
|
|
8
|
+
Used primarily for validation in verifier configurations.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
@@ -53,7 +53,7 @@ class StrictCriterion(pydantic.BaseModel):
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
class StrictRubric(pydantic.BaseModel):
|
|
56
|
-
"""Strict rubric definition for step-wise
|
|
56
|
+
"""Strict rubric definition for step-wise verifiers.
|
|
57
57
|
|
|
58
58
|
Enforces:
|
|
59
59
|
- Weights must sum to 1.0
|
|
@@ -146,4 +146,3 @@ def validate_rubric_files(paths: Iterable[Path]) -> list[StrictRubric]:
|
|
|
146
146
|
for path in paths:
|
|
147
147
|
validated.append(validate_rubric_file(path))
|
|
148
148
|
return validated
|
|
149
|
-
|
synth_ai/sdk/task/server.py
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
"""FastAPI scaffolding for Task Apps (local dev + deployment).
|
|
1
|
+
"""FastAPI scaffolding for Task Apps (local dev + deployment).
|
|
2
|
+
|
|
3
|
+
Prefer synth_ai.sdk.localapi.server moving forward. This module remains for
|
|
4
|
+
backward compatibility during the naming transition.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
from __future__ import annotations
|
|
4
8
|
|
|
5
9
|
import asyncio
|
|
6
10
|
import inspect
|
|
7
11
|
import os
|
|
12
|
+
import threading
|
|
8
13
|
from collections.abc import Awaitable, Callable, Iterable, Mapping, MutableMapping, Sequence
|
|
9
14
|
from contextlib import asynccontextmanager
|
|
10
15
|
from dataclasses import dataclass, field
|
|
@@ -66,7 +71,7 @@ class TaskAppConfig:
|
|
|
66
71
|
|
|
67
72
|
A TaskAppConfig defines all the components needed to create a task app:
|
|
68
73
|
- Task information (base_task_info)
|
|
69
|
-
- Task set description (
|
|
74
|
+
- Task set description (provide_taskset_description)
|
|
70
75
|
- Task instance provider (provide_task_instances)
|
|
71
76
|
- Rollout executor (rollout)
|
|
72
77
|
- Optional rubrics, datasets, proxy config, etc.
|
|
@@ -76,19 +81,18 @@ class TaskAppConfig:
|
|
|
76
81
|
|
|
77
82
|
Example:
|
|
78
83
|
>>> from synth_ai.sdk.task.server import TaskAppConfig, create_task_app
|
|
79
|
-
>>>
|
|
80
|
-
>>>
|
|
84
|
+
>>>
|
|
81
85
|
>>> def build_config() -> TaskAppConfig:
|
|
82
86
|
... return TaskAppConfig(
|
|
83
87
|
... app_id="my_task",
|
|
84
88
|
... name="My Task App",
|
|
85
89
|
... description="A simple task app",
|
|
86
|
-
...
|
|
87
|
-
... describe_taskset=lambda: {"splits": ["train", "val"]},
|
|
90
|
+
... provide_taskset_description=lambda: {"splits": ["train", "val"]},
|
|
88
91
|
... provide_task_instances=lambda seeds: [...],
|
|
89
92
|
... rollout=lambda req, r: {...},
|
|
93
|
+
... # base_task_info is optional - auto-derived from app_id/name
|
|
90
94
|
... )
|
|
91
|
-
>>>
|
|
95
|
+
>>>
|
|
92
96
|
>>> app = create_task_app(build_config())
|
|
93
97
|
>>> # app is a FastAPI instance ready to run
|
|
94
98
|
|
|
@@ -96,8 +100,8 @@ class TaskAppConfig:
|
|
|
96
100
|
app_id: Unique identifier for this task app
|
|
97
101
|
name: Human-readable name
|
|
98
102
|
description: Description of what this task app does
|
|
99
|
-
base_task_info: Base TaskInfo
|
|
100
|
-
|
|
103
|
+
base_task_info: Base TaskInfo (optional - auto-derived from app_id/name if not provided)
|
|
104
|
+
provide_taskset_description: Function that returns taskset metadata
|
|
101
105
|
provide_task_instances: Function that yields TaskInfo instances for given seeds
|
|
102
106
|
rollout: Function that executes a rollout request and returns response
|
|
103
107
|
dataset_registry: Optional registry for task datasets
|
|
@@ -116,10 +120,10 @@ class TaskAppConfig:
|
|
|
116
120
|
app_id: str
|
|
117
121
|
name: str
|
|
118
122
|
description: str
|
|
119
|
-
|
|
120
|
-
describe_taskset: TasksetDescriptor
|
|
123
|
+
provide_taskset_description: TasksetDescriptor
|
|
121
124
|
provide_task_instances: InstanceProvider
|
|
122
125
|
rollout: RolloutExecutor
|
|
126
|
+
base_task_info: TaskInfo | None = None # Auto-derived from app_id/name if not provided
|
|
123
127
|
dataset_registry: TaskDatasetRegistry | None = None
|
|
124
128
|
rubrics: RubricBundle | None = field(default_factory=RubricBundle)
|
|
125
129
|
proxy: ProxyConfig | None = None
|
|
@@ -139,10 +143,10 @@ class TaskAppConfig:
|
|
|
139
143
|
app_id=self.app_id,
|
|
140
144
|
name=self.name,
|
|
141
145
|
description=self.description,
|
|
142
|
-
|
|
143
|
-
describe_taskset=self.describe_taskset,
|
|
146
|
+
provide_taskset_description=self.provide_taskset_description,
|
|
144
147
|
provide_task_instances=self.provide_task_instances,
|
|
145
148
|
rollout=self.rollout,
|
|
149
|
+
base_task_info=self.base_task_info, # May be None - auto-derived in create_task_app
|
|
146
150
|
dataset_registry=self.dataset_registry,
|
|
147
151
|
rubrics=self.rubrics or RubricBundle(),
|
|
148
152
|
proxy=self.proxy,
|
|
@@ -157,6 +161,10 @@ class TaskAppConfig:
|
|
|
157
161
|
)
|
|
158
162
|
|
|
159
163
|
|
|
164
|
+
class LocalAPIConfig(TaskAppConfig):
|
|
165
|
+
"""Alias for TaskAppConfig with LocalAPI naming."""
|
|
166
|
+
|
|
167
|
+
|
|
160
168
|
def _maybe_await(result: Any) -> Awaitable[Any]:
|
|
161
169
|
if inspect.isawaitable(result):
|
|
162
170
|
return asyncio.ensure_future(result)
|
|
@@ -299,10 +307,10 @@ def create_task_app(config: TaskAppConfig) -> FastAPI:
|
|
|
299
307
|
... app_id="my_task",
|
|
300
308
|
... name="My Task",
|
|
301
309
|
... description="A task app",
|
|
302
|
-
...
|
|
303
|
-
... describe_taskset=lambda: {"splits": ["train"]},
|
|
310
|
+
... provide_taskset_description=lambda: {"splits": ["train"]},
|
|
304
311
|
... provide_task_instances=lambda seeds: [...],
|
|
305
312
|
... rollout=lambda req, r: {...},
|
|
313
|
+
... # base_task_info is optional - auto-derived from app_id/name
|
|
306
314
|
... )
|
|
307
315
|
>>>
|
|
308
316
|
>>> app = create_task_app(build_config())
|
|
@@ -312,6 +320,16 @@ def create_task_app(config: TaskAppConfig) -> FastAPI:
|
|
|
312
320
|
log_info("create_task_app invoked", ctx=ctx)
|
|
313
321
|
cfg = config.clone()
|
|
314
322
|
cfg.rubrics = cfg.rubrics or RubricBundle()
|
|
323
|
+
|
|
324
|
+
# Auto-derive base_task_info from app_id/name if not provided
|
|
325
|
+
if cfg.base_task_info is None:
|
|
326
|
+
cfg.base_task_info = TaskInfo(
|
|
327
|
+
task={"id": cfg.app_id, "name": cfg.name},
|
|
328
|
+
dataset={"id": cfg.app_id},
|
|
329
|
+
inference={},
|
|
330
|
+
limits={},
|
|
331
|
+
)
|
|
332
|
+
|
|
315
333
|
app = FastAPI(title=cfg.name, description=cfg.description)
|
|
316
334
|
|
|
317
335
|
for key, value in cfg.app_state.items():
|
|
@@ -431,7 +449,7 @@ def create_task_app(config: TaskAppConfig) -> FastAPI:
|
|
|
431
449
|
all_seeds.extend(_normalise_seeds(seeds))
|
|
432
450
|
|
|
433
451
|
if not all_seeds:
|
|
434
|
-
descriptor_result = await _maybe_await(cfg.
|
|
452
|
+
descriptor_result = await _maybe_await(cfg.provide_taskset_description())
|
|
435
453
|
return to_jsonable({"taskset": descriptor_result})
|
|
436
454
|
|
|
437
455
|
instances = await _maybe_await(cfg.provide_task_instances(all_seeds))
|
|
@@ -578,3 +596,45 @@ def run_task_app(
|
|
|
578
596
|
remove_service_record(port)
|
|
579
597
|
except Exception:
|
|
580
598
|
pass
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def run_server_background(
|
|
602
|
+
app: Any,
|
|
603
|
+
port: int,
|
|
604
|
+
host: str = "0.0.0.0",
|
|
605
|
+
) -> threading.Thread:
|
|
606
|
+
"""Start uvicorn server in a background daemon thread.
|
|
607
|
+
|
|
608
|
+
For manual control over task app lifecycle. If you want automatic
|
|
609
|
+
tunnel management, use InProcessTaskApp instead.
|
|
610
|
+
|
|
611
|
+
Args:
|
|
612
|
+
app: ASGI/FastAPI application
|
|
613
|
+
port: Port to bind
|
|
614
|
+
host: Host to bind (default 0.0.0.0 for tunnel access)
|
|
615
|
+
|
|
616
|
+
Returns:
|
|
617
|
+
Daemon thread running the server (stops when main process exits)
|
|
618
|
+
|
|
619
|
+
Example:
|
|
620
|
+
from synth_ai.sdk.task import run_server_background
|
|
621
|
+
from synth_ai.sdk.tunnels import wait_for_health_check
|
|
622
|
+
|
|
623
|
+
thread = run_server_background(my_app, port=8001)
|
|
624
|
+
await wait_for_health_check("localhost", 8001, api_key)
|
|
625
|
+
"""
|
|
626
|
+
import asyncio
|
|
627
|
+
import threading
|
|
628
|
+
|
|
629
|
+
import uvicorn
|
|
630
|
+
|
|
631
|
+
def serve() -> None:
|
|
632
|
+
config = uvicorn.Config(app, host=host, port=port, log_level="warning")
|
|
633
|
+
server = uvicorn.Server(config)
|
|
634
|
+
loop = asyncio.new_event_loop()
|
|
635
|
+
asyncio.set_event_loop(loop)
|
|
636
|
+
loop.run_until_complete(server.serve())
|
|
637
|
+
|
|
638
|
+
thread = threading.Thread(target=serve, daemon=True, name=f"uvicorn-{port}")
|
|
639
|
+
thread.start()
|
|
640
|
+
return thread
|