agentperf-local 0.1.0__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.
- agentperf_local/__init__.py +3 -0
- agentperf_local/__main__.py +6 -0
- agentperf_local/cli/__init__.py +51 -0
- agentperf_local/cli/inspect.py +55 -0
- agentperf_local/cli/options.py +243 -0
- agentperf_local/cli/parsers.py +395 -0
- agentperf_local/cli/replay.py +491 -0
- agentperf_local/cli/submit.py +137 -0
- agentperf_local/cli/workload.py +56 -0
- agentperf_local/client/__init__.py +1 -0
- agentperf_local/client/backends.py +38 -0
- agentperf_local/client/endpoint.py +56 -0
- agentperf_local/client/protocol.py +54 -0
- agentperf_local/client/python_client.py +133 -0
- agentperf_local/client/request.py +66 -0
- agentperf_local/client/rust_client.py +184 -0
- agentperf_local/common/__init__.py +1 -0
- agentperf_local/common/argparse_fields.py +61 -0
- agentperf_local/common/durable_files.py +214 -0
- agentperf_local/common/identity.py +58 -0
- agentperf_local/common/json_fields.py +203 -0
- agentperf_local/common/json_records.py +67 -0
- agentperf_local/common/json_types.py +43 -0
- agentperf_local/common/package_paths.py +6 -0
- agentperf_local/common/statistics.py +25 -0
- agentperf_local/common/units.py +9 -0
- agentperf_local/data/model-candidates-v2.json +3913 -0
- agentperf_local/data/replays/aa-mini-v1/manifest.json +19 -0
- agentperf_local/data/replays/aa-mini-v1/traces/aa-mini-library-01.jsonl +6 -0
- agentperf_local/data/replays/agentperf-default-v1/manifest-arm64.json +189 -0
- agentperf_local/data/replays/agentperf-default-v1/manifest-x86_64.json +189 -0
- agentperf_local/data/replays/agentperf-default-v1/swebench-instances.jsonl +3 -0
- agentperf_local/data/replays/agentperf-default-v1/traces/django__django-14500.jsonl +40 -0
- agentperf_local/data/replays/agentperf-default-v1/traces/django__django-15851.jsonl +28 -0
- agentperf_local/data/replays/agentperf-default-v1/traces/sphinx-doc__sphinx-10614.jsonl +40 -0
- agentperf_local/data/replays/agentperf-default-v1/traces/task_k8s_debugging.jsonl +5 -0
- agentperf_local/data/replays/agentperf-default-v1/traces/task_meeting_council_budget.jsonl +20 -0
- agentperf_local/data/replays/agentperf-default-v1/traces/task_meeting_council_votes.jsonl +21 -0
- agentperf_local/data/replays/agentperf-default-v1/traces/task_meeting_searchable_index.jsonl +9 -0
- agentperf_local/data/replays/agentperf-default-v1/traces/task_skill_search.jsonl +5 -0
- agentperf_local/data/replays/agentperf-default-v1/workspaces/task_k8s_debugging/deployment.yml +63 -0
- agentperf_local/data/replays/agentperf-default-v1/workspaces/task_meeting_council_budget/transcript.md +5023 -0
- agentperf_local/data/replays/agentperf-default-v1/workspaces/task_meeting_council_votes/transcript.md +5023 -0
- agentperf_local/data/replays/agentperf-default-v1/workspaces/task_meeting_searchable_index/meeting_transcript.md +1174 -0
- agentperf_local/data/replays/agentperf-default-v1/workspaces/task_skill_search/config/database.yml +15 -0
- agentperf_local/data/replays/agentperf-default-v1/workspaces/task_skill_search/config/settings.json +17 -0
- agentperf_local/deployment/__init__.py +1 -0
- agentperf_local/deployment/catalog.py +951 -0
- agentperf_local/deployment/context_policy.py +185 -0
- agentperf_local/deployment/endpoint_probes.py +361 -0
- agentperf_local/deployment/frameworks.py +336 -0
- agentperf_local/deployment/managed.py +1110 -0
- agentperf_local/deployment/managed_run.py +480 -0
- agentperf_local/deployment/model_cache.py +706 -0
- agentperf_local/deployment/qualification.py +679 -0
- agentperf_local/metrics/__init__.py +1 -0
- agentperf_local/metrics/decode.py +118 -0
- agentperf_local/metrics/request.py +136 -0
- agentperf_local/metrics/response.py +206 -0
- agentperf_local/metrics/tokenization.py +26 -0
- agentperf_local/provenance/__init__.py +1 -0
- agentperf_local/provenance/accelerator_probes.py +531 -0
- agentperf_local/provenance/benchmark.py +347 -0
- agentperf_local/provenance/context.py +75 -0
- agentperf_local/provenance/hardware.py +305 -0
- agentperf_local/provenance/hardware_facts.py +320 -0
- agentperf_local/replay/__init__.py +1 -0
- agentperf_local/replay/cache_isolation.py +81 -0
- agentperf_local/replay/config.py +200 -0
- agentperf_local/replay/fidelity.py +230 -0
- agentperf_local/replay/runner.py +842 -0
- agentperf_local/reports/__init__.py +1 -0
- agentperf_local/reports/progress.py +480 -0
- agentperf_local/reports/reporting.py +906 -0
- agentperf_local/submission/__init__.py +1 -0
- agentperf_local/submission/aggregate.py +952 -0
- agentperf_local/submission/bundle.py +595 -0
- agentperf_local/submission/client.py +528 -0
- agentperf_local/submission/evidence.py +490 -0
- agentperf_local/submission/private_audit.py +183 -0
- agentperf_local/telemetry/__init__.py +1 -0
- agentperf_local/telemetry/nvidia.py +581 -0
- agentperf_local/telemetry/power.py +443 -0
- agentperf_local/telemetry/reduction.py +278 -0
- agentperf_local/tools/__init__.py +1 -0
- agentperf_local/tools/docker.py +280 -0
- agentperf_local/tools/shell.py +64 -0
- agentperf_local/tui/__init__.py +1 -0
- agentperf_local/tui/app.py +2711 -0
- agentperf_local/tui/branding.py +139 -0
- agentperf_local/tui/controller.py +545 -0
- agentperf_local/tui/evidence.py +117 -0
- agentperf_local/tui/inputs.py +198 -0
- agentperf_local/tui/labels.py +178 -0
- agentperf_local/tui/messages.py +195 -0
- agentperf_local/tui/replay_contract.py +508 -0
- agentperf_local/tui/steps.py +35 -0
- agentperf_local/tui/styles.py +763 -0
- agentperf_local/tui/widgets.py +508 -0
- agentperf_local/workload/__init__.py +1 -0
- agentperf_local/workload/bundled.py +55 -0
- agentperf_local/workload/recording.py +442 -0
- agentperf_local/workload/schema.py +503 -0
- agentperf_local-0.1.0.dist-info/METADATA +266 -0
- agentperf_local-0.1.0.dist-info/RECORD +108 -0
- agentperf_local-0.1.0.dist-info/WHEEL +4 -0
- agentperf_local-0.1.0.dist-info/entry_points.txt +3 -0
- agentperf_local-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Run one agentperf-local command.
|
|
2
|
+
|
|
3
|
+
Every subcommand is defined in a sibling module and named here once.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import sys
|
|
10
|
+
from collections.abc import Callable, Sequence
|
|
11
|
+
|
|
12
|
+
from agentperf_local.cli.inspect import deployment_options_command, doctor_command
|
|
13
|
+
from agentperf_local.cli.options import INTERRUPTED_STATUS
|
|
14
|
+
from agentperf_local.cli.parsers import DEFAULT_COMMAND, build_parser
|
|
15
|
+
from agentperf_local.cli.replay import managed_run_command, run_command, tui_command
|
|
16
|
+
from agentperf_local.cli.submit import (
|
|
17
|
+
prepare_submission_command,
|
|
18
|
+
submission_status_command,
|
|
19
|
+
submit_command,
|
|
20
|
+
)
|
|
21
|
+
from agentperf_local.cli.workload import convert_command
|
|
22
|
+
from agentperf_local.common.argparse_fields import read_string
|
|
23
|
+
|
|
24
|
+
COMMANDS: dict[str, Callable[[argparse.Namespace], int]] = {
|
|
25
|
+
"convert": convert_command,
|
|
26
|
+
"run": run_command,
|
|
27
|
+
DEFAULT_COMMAND: tui_command,
|
|
28
|
+
"doctor": doctor_command,
|
|
29
|
+
"deployment-options": deployment_options_command,
|
|
30
|
+
"managed-run": managed_run_command,
|
|
31
|
+
"prepare-submission": prepare_submission_command,
|
|
32
|
+
"submit": submit_command,
|
|
33
|
+
"submission-status": submission_status_command,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
38
|
+
"""Run one command and return its process status.
|
|
39
|
+
|
|
40
|
+
An empty command line opens the TUI with its default settings.
|
|
41
|
+
"""
|
|
42
|
+
arguments = list(sys.argv[1:] if argv is None else argv)
|
|
43
|
+
namespace = build_parser().parse_args(arguments or [DEFAULT_COMMAND])
|
|
44
|
+
try:
|
|
45
|
+
return COMMANDS[read_string(namespace, "command")](namespace)
|
|
46
|
+
except KeyboardInterrupt:
|
|
47
|
+
print("error: interrupted", file=sys.stderr)
|
|
48
|
+
return INTERRUPTED_STATUS
|
|
49
|
+
except Exception as error:
|
|
50
|
+
print(f"error: {error}", file=sys.stderr)
|
|
51
|
+
return 1
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Report local facts: hardware and managed deployment choices."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from agentperf_local.cli.options import (
|
|
9
|
+
print_json,
|
|
10
|
+
read_bound_device,
|
|
11
|
+
read_managed_target,
|
|
12
|
+
)
|
|
13
|
+
from agentperf_local.common.argparse_fields import (
|
|
14
|
+
read_boolean,
|
|
15
|
+
)
|
|
16
|
+
from agentperf_local.deployment.frameworks import framework_offers
|
|
17
|
+
from agentperf_local.provenance.hardware import (
|
|
18
|
+
collect_hardware_snapshot,
|
|
19
|
+
)
|
|
20
|
+
from agentperf_local.reports.progress import render_hardware
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def doctor_command(namespace: argparse.Namespace) -> int:
|
|
24
|
+
snapshot = collect_hardware_snapshot()
|
|
25
|
+
if read_boolean(namespace, "json"):
|
|
26
|
+
print_json(snapshot.to_json())
|
|
27
|
+
else:
|
|
28
|
+
print(render_hardware(snapshot))
|
|
29
|
+
# A benchmark run binds exactly one accelerator, so any other count is a blocking finding.
|
|
30
|
+
accelerator_count = len(snapshot.accelerators)
|
|
31
|
+
if accelerator_count == 1:
|
|
32
|
+
return 0
|
|
33
|
+
print(f"doctor requires exactly one detected accelerator; found {accelerator_count}", file=sys.stderr)
|
|
34
|
+
return 1
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def deployment_options_command(namespace: argparse.Namespace) -> int:
|
|
38
|
+
candidate = read_managed_target(namespace).candidate
|
|
39
|
+
bound = read_bound_device(namespace)
|
|
40
|
+
snapshot = bound.snapshot
|
|
41
|
+
offers = framework_offers(snapshot, candidate)
|
|
42
|
+
can_deploy_now = any(offer.installed and offer.memory_fit is True for offer in offers)
|
|
43
|
+
print_json(
|
|
44
|
+
{
|
|
45
|
+
"kind": "managed_deployment_options",
|
|
46
|
+
"profile_id": candidate.profile_id,
|
|
47
|
+
"model": candidate.hf_repository,
|
|
48
|
+
"device_index": bound.device_index,
|
|
49
|
+
"hardware": snapshot.to_json(),
|
|
50
|
+
"offers": [offer.to_json() for offer in offers],
|
|
51
|
+
"can_deploy_now": can_deploy_now,
|
|
52
|
+
"upload_performed": False,
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
return 0 if can_deploy_now else 1
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""Read and validate one parsed command line, one value at a time."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import orjson
|
|
10
|
+
|
|
11
|
+
from agentperf_local.client.backends import CLIENT_BACKENDS, ClientBackend
|
|
12
|
+
from agentperf_local.common.argparse_fields import (
|
|
13
|
+
read_optional_integer,
|
|
14
|
+
read_optional_number,
|
|
15
|
+
read_optional_path,
|
|
16
|
+
read_optional_string,
|
|
17
|
+
read_path,
|
|
18
|
+
read_string,
|
|
19
|
+
)
|
|
20
|
+
from agentperf_local.common.json_fields import one_of
|
|
21
|
+
from agentperf_local.common.json_types import JsonObject
|
|
22
|
+
from agentperf_local.deployment.catalog import (
|
|
23
|
+
BUNDLED_MODEL_CATALOG_PATH,
|
|
24
|
+
DEPLOYMENT_FRAMEWORK_ORDER,
|
|
25
|
+
DeploymentFramework,
|
|
26
|
+
ModelCandidate,
|
|
27
|
+
ModelCatalog,
|
|
28
|
+
ModelDeployment,
|
|
29
|
+
load_model_catalog,
|
|
30
|
+
)
|
|
31
|
+
from agentperf_local.deployment.managed import (
|
|
32
|
+
DEPLOYMENT_LOG_FILENAME,
|
|
33
|
+
DEPLOYMENT_RECORD_FILENAME,
|
|
34
|
+
BoundDeploymentDevice,
|
|
35
|
+
bind_snapshot_to_device,
|
|
36
|
+
)
|
|
37
|
+
from agentperf_local.deployment.qualification import (
|
|
38
|
+
QUALIFICATION_FILENAME,
|
|
39
|
+
)
|
|
40
|
+
from agentperf_local.provenance.benchmark import (
|
|
41
|
+
MEASUREMENT_BINDING_FILENAME,
|
|
42
|
+
)
|
|
43
|
+
from agentperf_local.provenance.hardware import (
|
|
44
|
+
collect_hardware_snapshot,
|
|
45
|
+
)
|
|
46
|
+
from agentperf_local.replay.config import (
|
|
47
|
+
API_KEY_ENV_PATTERN,
|
|
48
|
+
DEFAULT_OUTPUT_TOKEN_MARGIN,
|
|
49
|
+
DEFAULT_TOOL_DELAY_SCALE,
|
|
50
|
+
OUTPUT_TOKEN_POLICIES,
|
|
51
|
+
OutputTokenPolicy,
|
|
52
|
+
SamplingPreset,
|
|
53
|
+
ToolReplayMode,
|
|
54
|
+
read_api_key_env,
|
|
55
|
+
)
|
|
56
|
+
from agentperf_local.reports.reporting import (
|
|
57
|
+
FAILURES_FILENAME,
|
|
58
|
+
SUMMARY_FILENAME,
|
|
59
|
+
TASKS_FILENAME,
|
|
60
|
+
TOOLS_FILENAME,
|
|
61
|
+
TURNS_FILENAME,
|
|
62
|
+
validate_run_artifact_output,
|
|
63
|
+
)
|
|
64
|
+
from agentperf_local.submission.client import (
|
|
65
|
+
read_submit_token,
|
|
66
|
+
)
|
|
67
|
+
from agentperf_local.telemetry.power import (
|
|
68
|
+
POWER_SUMMARY_FILENAME,
|
|
69
|
+
TELEMETRY_FILENAME,
|
|
70
|
+
)
|
|
71
|
+
from agentperf_local.workload.bundled import find_bundled_replay
|
|
72
|
+
from agentperf_local.workload.schema import MessageSource
|
|
73
|
+
|
|
74
|
+
MESSAGE_SOURCES: tuple[MessageSource, ...] = ("provider-request", "request-messages")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
SAMPLING_PRESETS: tuple[SamplingPreset, ...] = ("standard", "custom")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
TOOL_REPLAY_MODES: tuple[ToolReplayMode, ...] = ("none", "recorded", "live")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
DEFAULT_MANAGED_PROFILE_ID = "gemma4-12b-it-q4-0"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
BOUND_RESULT_FILENAMES = (
|
|
87
|
+
MEASUREMENT_BINDING_FILENAME,
|
|
88
|
+
SUMMARY_FILENAME,
|
|
89
|
+
TURNS_FILENAME,
|
|
90
|
+
TASKS_FILENAME,
|
|
91
|
+
TOOLS_FILENAME,
|
|
92
|
+
FAILURES_FILENAME,
|
|
93
|
+
DEPLOYMENT_RECORD_FILENAME,
|
|
94
|
+
TELEMETRY_FILENAME,
|
|
95
|
+
POWER_SUMMARY_FILENAME,
|
|
96
|
+
QUALIFICATION_FILENAME,
|
|
97
|
+
DEPLOYMENT_LOG_FILENAME,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
DEFAULT_MODEL_CATALOG_PATH = BUNDLED_MODEL_CATALOG_PATH
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# Shells report an interrupted process as 128 plus the signal number, and SIGINT is signal 2.
|
|
105
|
+
INTERRUPTED_STATUS = 130
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def nonempty_path(value: str) -> Path:
|
|
109
|
+
"""Parse one path argument, refusing the empty value.
|
|
110
|
+
|
|
111
|
+
An empty string becomes the current directory, which silently scatters new files
|
|
112
|
+
into the working tree; argparse prefixes the offending argument's name itself.
|
|
113
|
+
"""
|
|
114
|
+
if not value:
|
|
115
|
+
raise argparse.ArgumentTypeError("must not be empty")
|
|
116
|
+
return Path(value)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def read_api_key(namespace: argparse.Namespace) -> str | None:
|
|
120
|
+
"""Read one explicitly named endpoint secret."""
|
|
121
|
+
name = read_optional_string(namespace, "api_key_env")
|
|
122
|
+
if name is None:
|
|
123
|
+
return None
|
|
124
|
+
if API_KEY_ENV_PATTERN.fullmatch(name) is None:
|
|
125
|
+
raise ValueError("--api-key-env must name a portable environment variable")
|
|
126
|
+
value = read_api_key_env(name)
|
|
127
|
+
if value is None:
|
|
128
|
+
raise ValueError(f"API key environment variable {name} is not set, is empty, or contains whitespace")
|
|
129
|
+
return value
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def read_message_source(namespace: argparse.Namespace) -> MessageSource:
|
|
133
|
+
return one_of(read_string(namespace, "message_source"), MESSAGE_SOURCES, "--message-source")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def read_client_backend(namespace: argparse.Namespace) -> ClientBackend:
|
|
137
|
+
return one_of(read_string(namespace, "client"), CLIENT_BACKENDS, "--client")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def read_requested_output_token_policy(namespace: argparse.Namespace) -> OutputTokenPolicy | None:
|
|
141
|
+
"""Return the policy the user passed, or None when they left the choice to the command."""
|
|
142
|
+
policy = read_optional_string(namespace, "output_token_policy")
|
|
143
|
+
return None if policy is None else one_of(policy, OUTPUT_TOKEN_POLICIES, "--output-token-policy")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def read_replay_manifest_path(namespace: argparse.Namespace) -> Path:
|
|
147
|
+
"""Resolve the custom manifest, or the bundled replay's manifest for this host."""
|
|
148
|
+
manifest = read_optional_path(namespace, "manifest")
|
|
149
|
+
if manifest is not None:
|
|
150
|
+
return manifest
|
|
151
|
+
replay_id = read_string(namespace, "replay")
|
|
152
|
+
replay = find_bundled_replay(replay_id)
|
|
153
|
+
if replay is None:
|
|
154
|
+
raise ValueError(f"unknown bundled replay {replay_id}")
|
|
155
|
+
return replay.manifest_path
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def read_sampling_preset(namespace: argparse.Namespace) -> SamplingPreset:
|
|
159
|
+
return one_of(read_string(namespace, "sampling"), SAMPLING_PRESETS, "--sampling")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def read_tool_mode(namespace: argparse.Namespace) -> ToolReplayMode:
|
|
163
|
+
return one_of(read_string(namespace, "tool_mode"), TOOL_REPLAY_MODES, "--tool-mode")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def read_output_token_margin(namespace: argparse.Namespace, policy: OutputTokenPolicy) -> int:
|
|
167
|
+
"""Resolve the recorded-target margin and reject it under any other policy."""
|
|
168
|
+
margin = read_optional_integer(namespace, "output_token_margin")
|
|
169
|
+
if margin is None:
|
|
170
|
+
return DEFAULT_OUTPUT_TOKEN_MARGIN
|
|
171
|
+
if policy != "recorded":
|
|
172
|
+
raise ValueError("output token margin applies only to the recorded policy")
|
|
173
|
+
return margin
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def read_tool_delay_scale(namespace: argparse.Namespace, tool_mode: ToolReplayMode) -> float:
|
|
177
|
+
"""Resolve the recorded delay scale and reject it without tool replay."""
|
|
178
|
+
scale = read_optional_number(namespace, "tool_delay_scale")
|
|
179
|
+
if scale is None:
|
|
180
|
+
return DEFAULT_TOOL_DELAY_SCALE
|
|
181
|
+
if tool_mode == "none":
|
|
182
|
+
raise ValueError("tool delay scale requires a tool replay mode")
|
|
183
|
+
return scale
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def read_live_workspace_root(namespace: argparse.Namespace, tool_mode: ToolReplayMode) -> Path | None:
|
|
187
|
+
"""Resolve the live workspace root, defaulting live runs to a folder inside the output directory."""
|
|
188
|
+
root = read_optional_path(namespace, "live_workspace_root")
|
|
189
|
+
if root is not None or tool_mode != "live":
|
|
190
|
+
return root
|
|
191
|
+
return read_path(namespace, "output_dir") / "workspaces"
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def read_deployment_framework(namespace: argparse.Namespace) -> DeploymentFramework:
|
|
195
|
+
return one_of(read_string(namespace, "framework"), DEPLOYMENT_FRAMEWORK_ORDER, "--framework")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
199
|
+
class ManagedTarget:
|
|
200
|
+
"""Name the catalog, candidate, and recipe one managed command targets."""
|
|
201
|
+
|
|
202
|
+
catalog: ModelCatalog
|
|
203
|
+
candidate: ModelCandidate
|
|
204
|
+
deployment: ModelDeployment
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def read_managed_target(namespace: argparse.Namespace) -> ManagedTarget:
|
|
208
|
+
catalog = load_model_catalog(read_path(namespace, "catalog"))
|
|
209
|
+
candidate = catalog_candidate(catalog, read_string(namespace, "profile_id"))
|
|
210
|
+
if candidate.deployment is None:
|
|
211
|
+
raise ValueError(f"model profile {candidate.profile_id} does not define a managed deployment")
|
|
212
|
+
return ManagedTarget(catalog=catalog, candidate=candidate, deployment=candidate.deployment)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def read_bound_device(namespace: argparse.Namespace) -> BoundDeploymentDevice:
|
|
216
|
+
return bind_snapshot_to_device(collect_hardware_snapshot(), read_optional_integer(namespace, "device"))
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def require_fresh_output_dir(output_dir: Path, label: str) -> None:
|
|
220
|
+
"""Refuse an output directory that already holds any bound result artifact."""
|
|
221
|
+
validate_run_artifact_output(output_dir)
|
|
222
|
+
existing = tuple(
|
|
223
|
+
name for name in BOUND_RESULT_FILENAMES if (output_dir / name).exists() or (output_dir / name).is_symlink()
|
|
224
|
+
)
|
|
225
|
+
if existing:
|
|
226
|
+
raise ValueError(f"{label} output must be fresh; existing artifacts: {', '.join(existing)}")
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def catalog_candidate(catalog: ModelCatalog, profile_id: str) -> ModelCandidate:
|
|
230
|
+
candidate = next((model for model in catalog.models if model.profile_id == profile_id), None)
|
|
231
|
+
if candidate is None:
|
|
232
|
+
available = ", ".join(sorted(model.profile_id for model in catalog.models))
|
|
233
|
+
raise ValueError(f"model catalog does not contain profile {profile_id}; available: {available}")
|
|
234
|
+
return candidate
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def print_json(value: JsonObject) -> None:
|
|
238
|
+
print(orjson.dumps(value, option=orjson.OPT_INDENT_2).decode("utf-8"))
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def resolve_submit_token(namespace: argparse.Namespace, attribute: str = "token_env") -> str | None:
|
|
242
|
+
"""Read the submit token from the named variable; an unset variable means anonymous."""
|
|
243
|
+
return read_submit_token(read_string(namespace, attribute))
|