sequence-base 0.3.16__tar.gz → 0.3.17__tar.gz
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.
- {sequence_base-0.3.16 → sequence_base-0.3.17}/PKG-INFO +2 -2
- {sequence_base-0.3.16 → sequence_base-0.3.17}/README.md +2 -2
- {sequence_base-0.3.16 → sequence_base-0.3.17}/pyproject.toml +4 -2
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/__init__.py +16 -5
- sequence_base-0.3.17/sequence_base/codec/__init__.py +19 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/codec/image.py +11 -62
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/constants/env.py +0 -7
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/constants/headers.py +0 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/constants/version.py +2 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/contract/__init__.py +8 -1
- sequence_base-0.3.17/sequence_base/contract/act.py +17 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/contract/connect.py +2 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/contract/deployments.py +17 -13
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/contract/flags.py +14 -31
- sequence_base-0.3.17/sequence_base/contract/io.py +17 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/contract/policy.py +44 -7
- sequence_base-0.3.17/sequence_base/contract/wire.py +53 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/errors/transport.py +0 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/obs/log.py +1 -1
- sequence_base-0.3.17/sequence_base/presets/__init__.py +4 -0
- sequence_base-0.3.17/sequence_base/presets/robot_arm.py +197 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schema_export.py +8 -0
- sequence_base-0.3.17/sequence_base/schemas/ActRequest.json +69 -0
- sequence_base-0.3.17/sequence_base/schemas/ActResponse.json +79 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/ActionChunk.json +3 -2
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/ActionContract.json +3 -2
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/ActionStep.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/AdapterTransform.json +1 -1
- sequence_base-0.3.17/sequence_base/schemas/AddLocal.json +27 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/Autoscaler.json +2 -2
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/BenchmarkConditionSpec.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/BenchmarkRegisterRequest.json +47 -4
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/BenchmarkSpec.json +45 -3
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/BenchmarkStatusResponse.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/BootstrapResponse.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/Camera.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/ClampInfo.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/CodeBundle.json +2 -2
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/Concurrency.json +2 -2
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/Conditions.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/ConnectRequest.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/ConnectResponse.json +15 -2
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/DeploymentAccepted.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/DeploymentPolicy.json +27 -6
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/DeploymentRequest.json +74 -11
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/DeploymentStatusResponse.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/Embodiment.json +3 -2
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/ErrorBody.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/ErrorCodes.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/Estimate.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/EvalDryRunResponse.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/EvalReport.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/EvalRequest.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/EvalResponse.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/ImageFrame.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/ImageSpec.json +44 -2
- sequence_base-0.3.17/sequence_base/schemas/ImageStepAddLocal.json +42 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/ImageStepApt.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/ImageStepEnv.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/ImageStepRun.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/ImageStepUvPip.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/InferParam.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/Lease.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/LossyTransform.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/MetricAgg.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/Observation.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/ObservationContract.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/PolicyRegistration.json +72 -9
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/PolicySpec.json +71 -8
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/Proprioception.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/RenderSpec.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/Resources.json +2 -2
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/RolloutResult.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/SecretCreateRequest.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/SecretInfo.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/SecretRef.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/SeqFlags.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/Step.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/SuiteAgg.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/TaskAgg.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/Timeouts.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/UploadTarget.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/UsageReport.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/Variant.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/VolumeAttach.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/VolumeFile.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/VolumeInfo.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/VolumeMount.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/VolumeRef.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/VolumeUploadRequest.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/VolumeUploadResponse.json +1 -1
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/schemas/WarmingError.json +1 -1
- sequence_base-0.3.17/sequence_base/telemetry/__init__.py +8 -0
- sequence_base-0.3.17/sequence_base/telemetry/events.py +50 -0
- sequence_base-0.3.16/sequence_base/codec/__init__.py +0 -25
- sequence_base-0.3.16/sequence_base/codec/resize.py +0 -147
- sequence_base-0.3.16/sequence_base/contract/act.py +0 -100
- sequence_base-0.3.16/sequence_base/contract/io.py +0 -110
- sequence_base-0.3.16/sequence_base/telemetry/__init__.py +0 -13
- sequence_base-0.3.16/sequence_base/telemetry/events.py +0 -101
- {sequence_base-0.3.16 → sequence_base-0.3.17}/.gitignore +0 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/auth.py +0 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/constants/__init__.py +0 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/contract/accounts.py +0 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/contract/benchmarks.py +0 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/contract/errors.py +0 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/contract/eval.py +0 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/contract/templates.py +0 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/contract/volumes.py +0 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/errors/__init__.py +0 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/obs/__init__.py +0 -0
- {sequence_base-0.3.16 → sequence_base-0.3.17}/sequence_base/obs/rid.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sequence-base
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.17
|
|
4
4
|
Summary: Shared foundation for the Sequences platform — wire contract, correlation, error taxonomy, constants. Used by the robot SDK, gateway, and container harness.
|
|
5
|
-
Requires-Python: >=3.
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
6
|
Requires-Dist: pydantic>=2.0
|
|
7
7
|
Provides-Extra: codec
|
|
8
8
|
Requires-Dist: numpy>=1.26; extra == 'codec'
|
|
@@ -34,5 +34,5 @@ values (per-model client_resize, pricing, licensing) stay out — in container /
|
|
|
34
34
|
`pip install -e .` (core = pydantic). The image codec pulls PIL/numpy, so it is an optional extra —
|
|
35
35
|
`pip install -e ".[codec]"` — which the gateway (which never encodes/decodes) skips.
|
|
36
36
|
|
|
37
|
-
Extracted from
|
|
38
|
-
|
|
37
|
+
Extracted from the original backend's schema and observability modules. Renamed from `sequence-contract` once it
|
|
38
|
+
grew past just the contract.
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sequence-base"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.17"
|
|
4
4
|
description = "Shared foundation for the Sequences platform — wire contract, correlation, error taxonomy, constants. Used by the robot SDK, gateway, and container harness."
|
|
5
|
-
|
|
5
|
+
# 3.10, not 3.11: `seq` (which imports this) must run on ROS 2 Humble's Python 3.10, and nothing here needs 3.11.
|
|
6
|
+
# {2026-10-03 the full suite on CPython 3.10.20: "147 PASSED"; vermin --eval-annotations: "MINIMUM REQUIRED VERSIONS: 3.9"}
|
|
7
|
+
requires-python = ">=3.10"
|
|
6
8
|
dependencies = ["pydantic>=2.0"]
|
|
7
9
|
|
|
8
10
|
# codec (image encode/decode) is P1 and pulls PIL/numpy — an optional extra so the gateway, which
|
|
@@ -14,6 +14,10 @@ catalogue values stay in container / gateway / spec.py.
|
|
|
14
14
|
# contract — the full shared wire contract (obs/action + registry + deployments + connect + eval +
|
|
15
15
|
# accounts + error bodies). One definition per shape; see sequence_base/contract for the grouping.
|
|
16
16
|
from sequence_base.contract import ( # noqa: F401 (re-export surface)
|
|
17
|
+
ActRequest,
|
|
18
|
+
ActResponse,
|
|
19
|
+
AddLocal,
|
|
20
|
+
ImageStepAddLocal,
|
|
17
21
|
Camera,
|
|
18
22
|
Embodiment,
|
|
19
23
|
ObservationContract,
|
|
@@ -112,8 +116,6 @@ from sequence_base.errors.transport import (
|
|
|
112
116
|
# auth — Bearer secret helpers (gateway direct-connect / internal; container guard; worker emit)
|
|
113
117
|
from sequence_base import auth
|
|
114
118
|
|
|
115
|
-
# telemetry — cold-start event schema (worker emits, gateway ingests)
|
|
116
|
-
from sequence_base.telemetry.events import WorkerLogEvent
|
|
117
119
|
|
|
118
120
|
# NOTE: codec (image encode/decode) is intentionally NOT re-exported here — it needs PIL + numpy
|
|
119
121
|
# (the [codec] extra), and the gateway installs the light core. Import it explicitly where needed:
|
|
@@ -122,8 +124,10 @@ from sequence_base.telemetry.events import WorkerLogEvent
|
|
|
122
124
|
__all__ = [
|
|
123
125
|
# contract 4 — /act data plane
|
|
124
126
|
"ImageFrame", "Proprioception", "Observation", "ActionSpace", "ActionStep", "ActionChunk",
|
|
127
|
+
"ActRequest", "ActResponse",
|
|
125
128
|
# contract 1 — flags + policy registry
|
|
126
|
-
"SeqFlags", "ImageStepUvPip", "ImageStepApt", "ImageStepEnv", "ImageStepRun", "
|
|
129
|
+
"SeqFlags", "ImageStepUvPip", "ImageStepApt", "ImageStepEnv", "ImageStepRun", "AddLocal", "ImageStepAddLocal",
|
|
130
|
+
"ImageStep", "ImageSpec",
|
|
127
131
|
"VolumeMount", "VolumeRef", "Concurrency", "ActionContract", "Camera", "ObservationContract", "Embodiment",
|
|
128
132
|
"PolicySpec", "PolicyRegistration",
|
|
129
133
|
# contract 2 — deployments
|
|
@@ -150,7 +154,14 @@ __all__ = [
|
|
|
150
154
|
# errors (exception taxonomy)
|
|
151
155
|
"TransportUnavailable", "TransportWarming", "TransportRejected", "TransportOverloaded",
|
|
152
156
|
# auth + telemetry
|
|
153
|
-
"auth",
|
|
157
|
+
"auth",
|
|
154
158
|
]
|
|
155
159
|
|
|
156
|
-
|
|
160
|
+
try:
|
|
161
|
+
# The installed distribution's own number, so this can never drift from pyproject.toml again (it said 0.3.6
|
|
162
|
+
# while the package was 0.3.12). A source tree that was never installed reports "dev".
|
|
163
|
+
from importlib.metadata import PackageNotFoundError, version as _version
|
|
164
|
+
|
|
165
|
+
__version__ = _version("sequence-base")
|
|
166
|
+
except PackageNotFoundError: # pragma: no cover - running from an uninstalled checkout
|
|
167
|
+
__version__ = "dev"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Image codec — the frame <-> wire transform, shared so the SDK (encode) and the worker (decode) cannot drift
|
|
3
|
+
on the wire format.
|
|
4
|
+
|
|
5
|
+
Needs Pillow + numpy: `pip install 'sequence-base[codec]'`. The gateway, which neither encodes nor
|
|
6
|
+
decodes, installs the light core and never imports this.
|
|
7
|
+
|
|
8
|
+
from sequence_base.codec import encode, decode
|
|
9
|
+
"""
|
|
10
|
+
from sequence_base.codec.image import (
|
|
11
|
+
DEFAULT_JPEG_QUALITY,
|
|
12
|
+
MAX_UPLOAD_EDGE,
|
|
13
|
+
decode,
|
|
14
|
+
decode_depth,
|
|
15
|
+
encode,
|
|
16
|
+
encode_depth,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = ["encode", "decode", "encode_depth", "decode_depth", "DEFAULT_JPEG_QUALITY", "MAX_UPLOAD_EDGE"]
|
|
@@ -15,7 +15,6 @@ import base64
|
|
|
15
15
|
import io
|
|
16
16
|
from typing import Any
|
|
17
17
|
|
|
18
|
-
from .resize import MAX_UPLOAD_EDGE, ResizeSpec, filters, refuse_wrong_aspect, resize_with_pad
|
|
19
18
|
|
|
20
19
|
# Quality 95, and the number is measured rather than chosen for looking safe. On a real DROID frame
|
|
21
20
|
# with the sampling noise pinned, the encoding moves the resulting action by: q95 0.51% of action
|
|
@@ -25,20 +24,22 @@ from .resize import MAX_UPLOAD_EDGE, ResizeSpec, filters, refuse_wrong_aspect, r
|
|
|
25
24
|
# {sequences-models models/pi05_droid/handler.py "Q95 MAX|DELTA| 0.0048 0.51% ... Q85 1.86%"}
|
|
26
25
|
DEFAULT_JPEG_QUALITY = 95
|
|
27
26
|
|
|
27
|
+
# The longest edge `encode` uploads. Every policy resizes a frame to its own input size anyway, so pixels above
|
|
28
|
+
# that only cost transport — and past a point they do not cost, they fail: a body over ~2 MB was dropped by a
|
|
29
|
+
# worker queue with no error and no job id, surfacing as a hang. 640 is above every input size in use (pi0.5's
|
|
30
|
+
# 224), so it discards nothing a policy reads. [MEASURED 2026-09-12: capping the long edge at 640 took a
|
|
31
|
+
# three-view observation from 1.98 MB to 0.12 MB]
|
|
32
|
+
MAX_UPLOAD_EDGE = 640
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
|
|
35
|
+
def encode(frame: Any, *, quality: int = DEFAULT_JPEG_QUALITY) -> str:
|
|
30
36
|
"""
|
|
31
37
|
One camera frame to the base64 image that `ImageFrame.data` carries.
|
|
32
38
|
|
|
33
39
|
`frame` may be a numpy array, a PIL image, raw `bytes` already encoded, or a `str` already base64
|
|
34
40
|
— the last two pass through untouched, so a caller with their own pipeline is not forced through
|
|
35
|
-
this one.
|
|
36
|
-
|
|
37
|
-
`spec` (a `ResizeSpec`) carries the model's geometry. When it declares a training aspect ratio, a
|
|
38
|
-
frame outside it is REFUSED — see `refuse_wrong_aspect`, not quietly reshaped. With no spec (or no
|
|
39
|
-
`client_resize`) the frame falls to `MAX_UPLOAD_EDGE`. Codec is JPEG at `quality`, unconditionally
|
|
40
|
-
— every served handler asks for JPEG in as many words, so there is no size probe and no second
|
|
41
|
-
encode. {handlers pi05_droid/cosmos3/groot all "SEND BASE64 JPEG"}
|
|
41
|
+
this one. A frame whose long edge is over `MAX_UPLOAD_EDGE` is scaled down to it first. Codec is
|
|
42
|
+
JPEG at `quality`, unconditionally.
|
|
42
43
|
"""
|
|
43
44
|
if isinstance(frame, str):
|
|
44
45
|
return frame
|
|
@@ -56,59 +57,7 @@ def encode(frame: Any, spec: ResizeSpec | None = None, *, quality: int = DEFAULT
|
|
|
56
57
|
img = frame if hasattr(frame, "save") else Image.fromarray(frame)
|
|
57
58
|
if img.mode != "RGB":
|
|
58
59
|
img = img.convert("RGB")
|
|
59
|
-
|
|
60
|
-
# Downscale before encoding. Every model resizes the frame to its own input size anyway, so pixels
|
|
61
|
-
# above that only cost transport — and past a point they do not cost, they fail (a body over ~2 MB
|
|
62
|
-
# is dropped by the worker queue with no error and no job id, surfacing as a hang). The target and
|
|
63
|
-
# the filter both come from the spec; neither is derived here. `client_resize=None` means the
|
|
64
|
-
# model declined pre-resizing and is not a gap: it falls to the cap below, as an unknown model
|
|
65
|
-
# does. HOW the frame reaches that size comes from `input_resize` ("pad" = the server's own
|
|
66
|
-
# resize_with_pad, "stretch" = unconditional interpolate).
|
|
67
|
-
want = None
|
|
68
|
-
cr = spec.client_resize if spec else None
|
|
69
|
-
if cr:
|
|
70
|
-
# `shortest_edge` scales the short side to n and lets the ratio follow — the only way to
|
|
71
|
-
# express a server step defined as "SmallestMaxSize(256)" (1280x720 and 1920x1080 both to
|
|
72
|
-
# 455x256), which no fixed [w, h] covers. round(), matching albumentations. `size` is a fixed
|
|
73
|
-
# target.
|
|
74
|
-
if "shortest_edge" in cr:
|
|
75
|
-
# Shrink only. SmallestMaxSize also UPSCALES a frame whose short edge is under the target,
|
|
76
|
-
# and reproducing that here is strictly worse: the payload grows and the upscale happens
|
|
77
|
-
# in PIL when the server was going to do it in cv2 anyway. Leaving a small frame alone
|
|
78
|
-
# hands the whole operation to the server, where it is exact.
|
|
79
|
-
n = int(cr["shortest_edge"])
|
|
80
|
-
if min(img.size) > n:
|
|
81
|
-
scale = n / min(img.size)
|
|
82
|
-
want = (max(1, round(img.width * scale)), max(1, round(img.height * scale)))
|
|
83
|
-
else:
|
|
84
|
-
want = img.size
|
|
85
|
-
else:
|
|
86
|
-
want = (int(cr["size"][0]), int(cr["size"][1]))
|
|
87
|
-
if img.size != want:
|
|
88
|
-
# The filter is named by the server, not chosen here: this resize stands in for one the
|
|
89
|
-
# model's own transform would otherwise run, so it has to be the SAME operation. An
|
|
90
|
-
# unrecognised name raises rather than falling back — a silent default is how the LANCZOS
|
|
91
|
-
# bug shipped. {MEASURED: BILINEAR to pi05's content block is 0.000/255, LANCZOS 0.779/15}
|
|
92
|
-
try:
|
|
93
|
-
resample = filters()[cr["filter"]]
|
|
94
|
-
except KeyError:
|
|
95
|
-
raise ValueError(
|
|
96
|
-
f"{spec.label} asks for resize filter {cr['filter']!r}, which this version of "
|
|
97
|
-
f"sequence-base does not implement (has: {sorted(filters())}). Upgrade, or pass "
|
|
98
|
-
f"an already-encoded frame to bypass this step."
|
|
99
|
-
) from None
|
|
100
|
-
# Refused before either branch, not inside "pad": a stretch model distorts a wrong ratio
|
|
101
|
-
# instead of letterboxing it, which is worse rather than exempt.
|
|
102
|
-
refuse_wrong_aspect(img, spec.aspect_ratio, max(want), spec.label)
|
|
103
|
-
if "shortest_edge" in cr:
|
|
104
|
-
# Already proportional, so nothing to letterbox — and letterboxing would actively
|
|
105
|
-
# damage it (a spurious black row the server carries into its crop as if it were scene).
|
|
106
|
-
img = img.resize(want, resample)
|
|
107
|
-
elif (spec.input_resize or "pad") == "pad":
|
|
108
|
-
img = resize_with_pad(img, want[0], want[1], resample)
|
|
109
|
-
else:
|
|
110
|
-
img = img.resize(want, resample)
|
|
111
|
-
elif max(img.size) > MAX_UPLOAD_EDGE:
|
|
60
|
+
if max(img.size) > MAX_UPLOAD_EDGE:
|
|
112
61
|
scale = MAX_UPLOAD_EDGE / max(img.size)
|
|
113
62
|
# round() not int(): int() truncates a 640.9 on one axis and 359.9 on the other, drifting the
|
|
114
63
|
# aspect ratio by up to half a percent for no reason.
|
|
@@ -5,10 +5,3 @@ INTERNAL_SECRET = "SEQ_INTERNAL_SECRET" # auth for container → gateway teleme
|
|
|
5
5
|
GATEWAY_URL = "SEQ_GATEWAY_URL" # where the container sends telemetry
|
|
6
6
|
VOLUME_ROOT = "SEQ_VOLUME_ROOT" # where fetched weights land
|
|
7
7
|
MANIFEST = "SEQ_MANIFEST" # the policy manifest a container serves
|
|
8
|
-
|
|
9
|
-
# Weight store (Cloudflare R2).
|
|
10
|
-
DIST_ENDPOINT = "SEQ_DIST_ENDPOINT"
|
|
11
|
-
DIST_BUCKET = "SEQ_DIST_BUCKET"
|
|
12
|
-
DIST_KEY = "SEQ_DIST_KEY"
|
|
13
|
-
DIST_SECRET = "SEQ_DIST_SECRET"
|
|
14
|
-
DIST_REGION = "SEQ_DIST_REGION"
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
"""HTTP header names — one source, so sdk / gateway / container never spell them differently."""
|
|
2
2
|
|
|
3
3
|
REQUEST_ID = "X-Seq-Request-Id" # correlation id; == sequence_base.obs.rid.HEADER
|
|
4
|
-
MODEL = "X-Seq-Model" # gateway → worker model routing
|
|
5
4
|
AUTH_SCHEME = "Bearer" # scheme for SEQ_DIRECT_SECRET on the direct-connect path
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
# across deployments / repos can tell which version of a shape it is validating against. MUST change
|
|
5
5
|
# whenever any shared wire shape changes (a field added / removed / retyped) — that is what makes
|
|
6
6
|
# contract evolution mechanically detectable instead of eyeballed. Bump on any shape change.
|
|
7
|
-
CONTRACT_VERSION = "0.4.
|
|
7
|
+
CONTRACT_VERSION = "0.4.14" # 0.4.13 -> 0.4.14: the /act wire is opaque (ActRequest.inputs / ActResponse.outputs); action / observation contracts optional + DeploymentPolicy/PolicySpec/ConnectResponse .io; the robot-arm types live in sequence_base.presets.robot_arm (contract.act / contract.io re-export them); ImageStepAddLocal (seq.Image.add_local_*); ActionSpace.JOINT_VELOCITY (pi0.5 DROID's joint velocities + gripper position)
|
|
8
|
+
# previous: 0.4.13 # 0.4.12 -> 0.4.13: template://<name>[@v<n>] — official template weights (parse_template); gs:// / s3:// / https:// still accepted until their removal
|
|
8
9
|
# previous: 0.4.12 # 0.4.11 -> 0.4.12: one error definition — ErrorBody is every non-2xx body (detail, code, retryable, retry_after_s, request_id, hint, details), ErrorCode covers every case, ERRORS / ErrorCodes.json give each code its status, retryability, SDK class and CLI exit code
|
|
9
10
|
# previous: 0.4.11 # 0.4.10 -> 0.4.11: weights in named parts (weights_name, extra_weights)
|
|
10
11
|
# previous: 0.4.8 # 0.4.7 -> 0.4.8: ImageSpec.python, ImageStepRun.secrets, Resources.region, DeploymentRequest/BenchmarkRegisterRequest.image_context
|
|
@@ -12,6 +12,9 @@ from sequence_base.contract.act import (
|
|
|
12
12
|
Proprioception,
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
+
# contract 4, version 2 — the opaque /act envelope (inputs in, outputs out)
|
|
16
|
+
from sequence_base.contract.wire import ActRequest, ActResponse
|
|
17
|
+
|
|
15
18
|
# contract 1 — lifecycle flags + policy registry
|
|
16
19
|
from sequence_base.contract.flags import SeqFlags
|
|
17
20
|
from sequence_base.contract.policy import (
|
|
@@ -26,6 +29,8 @@ from sequence_base.contract.policy import (
|
|
|
26
29
|
ImageStepEnv,
|
|
27
30
|
ImageStepRun,
|
|
28
31
|
ImageStepUvPip,
|
|
32
|
+
AddLocal,
|
|
33
|
+
ImageStepAddLocal,
|
|
29
34
|
PolicyRegistration,
|
|
30
35
|
PolicySpec,
|
|
31
36
|
VolumeMount,
|
|
@@ -110,9 +115,11 @@ from sequence_base.contract.errors import ERRORS, ErrorBody, ErrorCode, ErrorSpe
|
|
|
110
115
|
__all__ = [
|
|
111
116
|
# contract 4
|
|
112
117
|
"ImageFrame", "Proprioception", "Observation", "ActionSpace", "ActionStep", "ActionChunk",
|
|
118
|
+
"ActRequest", "ActResponse",
|
|
113
119
|
# contract 1
|
|
114
120
|
"SeqFlags",
|
|
115
|
-
"ImageStepUvPip", "ImageStepApt", "ImageStepEnv", "ImageStepRun", "
|
|
121
|
+
"ImageStepUvPip", "ImageStepApt", "ImageStepEnv", "ImageStepRun", "AddLocal", "ImageStepAddLocal", "ImageStep",
|
|
122
|
+
"ImageSpec",
|
|
116
123
|
"VolumeMount", "VolumeRef", "Concurrency", "ActionContract", "Camera", "ObservationContract", "Embodiment",
|
|
117
124
|
"PolicySpec", "PolicyRegistration",
|
|
118
125
|
# contract 2
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The robot-arm /act data types, re-exported from their home in `sequence_base.presets.robot_arm`.
|
|
3
|
+
|
|
4
|
+
They are no longer the platform's wire: since contract 0.4.14 the worker passes a policy's `inputs` through untouched
|
|
5
|
+
(`sequence_base.contract.wire`). This module keeps `from sequence_base.contract.act import Observation` resolving to the
|
|
6
|
+
same classes for the gateway, harness and SDK code that still uses the preset.
|
|
7
|
+
"""
|
|
8
|
+
from sequence_base.presets.robot_arm import ( # noqa: F401 — re-exports
|
|
9
|
+
ActionChunk,
|
|
10
|
+
ActionSpace,
|
|
11
|
+
ActionStep,
|
|
12
|
+
ImageFrame,
|
|
13
|
+
Observation,
|
|
14
|
+
Proprioception,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = ["ImageFrame", "Proprioception", "Observation", "ActionSpace", "ActionStep", "ActionChunk"]
|
|
@@ -57,3 +57,5 @@ class ConnectResponse(BaseModel):
|
|
|
57
57
|
action: ActionContract | None = None
|
|
58
58
|
observation: ObservationContract | None = None
|
|
59
59
|
embodiment: str | None = None
|
|
60
|
+
# The deployment's own description of its inputs and outputs (DeploymentPolicy.io), when the author wrote one.
|
|
61
|
+
io: str | None = None
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
2
|
契约 2 — `POST /v1/deployments` (SDK `seq deploy` → gateway) and its responses.
|
|
3
3
|
|
|
4
|
-
The request is the normalized wire form of a PolicySpec: one big body (
|
|
5
|
-
|
|
4
|
+
The request is the normalized wire form of a PolicySpec: one big body (new config adds a field, never a
|
|
5
|
+
new endpoint) whose sub-shapes reuse contract 1's types
|
|
6
6
|
(ImageSpec, VolumeMount, Concurrency, ActionContract). Field names align with the PolicySpec side —
|
|
7
7
|
`gpu` is normalized to list[str] here, the memory field is `memory_gb` (goal 7).
|
|
8
8
|
|
|
@@ -47,7 +47,7 @@ __all__ = [
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
class Resources(BaseModel):
|
|
50
|
-
"""
|
|
50
|
+
"""The hardware a worker runs on. `gpu` is list[str] (the PolicySpec `str | list[str]` normalized)."""
|
|
51
51
|
|
|
52
52
|
model_config = ConfigDict(extra="forbid")
|
|
53
53
|
|
|
@@ -60,7 +60,7 @@ class Resources(BaseModel):
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
class Autoscaler(BaseModel):
|
|
63
|
-
"""
|
|
63
|
+
"""How the deployment scales. Defaults mirror PolicySpec so an omitted block scales to zero."""
|
|
64
64
|
|
|
65
65
|
model_config = ConfigDict(extra="forbid")
|
|
66
66
|
|
|
@@ -104,8 +104,9 @@ class InferParam(BaseModel):
|
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
class DeploymentPolicy(BaseModel):
|
|
107
|
-
"""The policy block of the deployments body. `handler_ref`
|
|
108
|
-
|
|
107
|
+
"""The policy block of the deployments body. Only `handler_ref` is required. The robot-arm preset's contracts
|
|
108
|
+
(`action`, `observation`, `embodiments`) are optional since contract 0.4.14: a deployment that declares none is held
|
|
109
|
+
to nothing and its inputs / outputs pass through the worker untouched.
|
|
109
110
|
|
|
110
111
|
`secrets` carries the policy's declared secret BUNDLES BY NAME ONLY (list[SecretRef]) — the gateway
|
|
111
112
|
validates each name exists and belongs to the deployer at submit time (accounts ⑦ / S3 goal 14), and the
|
|
@@ -116,7 +117,7 @@ class DeploymentPolicy(BaseModel):
|
|
|
116
117
|
model_config = ConfigDict(extra="forbid")
|
|
117
118
|
|
|
118
119
|
handler_ref: str
|
|
119
|
-
action: ActionContract
|
|
120
|
+
action: ActionContract | None = None
|
|
120
121
|
streaming: bool = False
|
|
121
122
|
frames_per_chunk: int | None = None
|
|
122
123
|
secrets: list[SecretRef] = Field(default_factory=list)
|
|
@@ -124,11 +125,14 @@ class DeploymentPolicy(BaseModel):
|
|
|
124
125
|
# contracts for a multi-embodiment policy — the worker picks one by the session's embodiment.
|
|
125
126
|
observation: ObservationContract | None = None
|
|
126
127
|
embodiments: dict[str, Embodiment] | None = None
|
|
127
|
-
# Did the author declare `action=` (or embodiments)?
|
|
128
|
-
#
|
|
129
|
-
#
|
|
130
|
-
#
|
|
128
|
+
# Did the author declare `action=` (or embodiments)? SDKs before contract 0.4.14 always sent an `action` (a
|
|
129
|
+
# placeholder when none was declared); only a declared contract is enforced on the worker and handed to the robot.
|
|
130
|
+
# None (an older SDK) is treated as undeclared, so an older policy is never refused for the placeholder it never
|
|
131
|
+
# chose. A current SDK sends `action` only when declared. {io-contract.md "旧 SDK 写的 POLICY 照样能 BUILD"}
|
|
131
132
|
action_declared: bool | None = None
|
|
133
|
+
# The author's own description of what infer() takes and returns; stored, handed to the robot at connect, never
|
|
134
|
+
# interpreted by the platform.
|
|
135
|
+
io: str | None = Field(default=None, max_length=4000)
|
|
132
136
|
# The inference parameters @seq.infer takes beyond the observation (name -> type, default). None: an older
|
|
133
137
|
# SDK that did not look; {}: the policy declares none.
|
|
134
138
|
params: dict[str, InferParam] | None = Field(default=None, max_length=32)
|
|
@@ -149,7 +153,7 @@ request limit (base64 adds a third)."""
|
|
|
149
153
|
|
|
150
154
|
class CodeBundle(BaseModel):
|
|
151
155
|
"""The policy's own source, shipped with the deploy so the worker can import `policy.handler_ref`
|
|
152
|
-
|
|
156
|
+
— the entrypoint module, mounted for it. A gzip'd tar of the handler's top-level module
|
|
153
157
|
or package, base64 on the wire, with the sha256 of the decoded bytes so a truncated or altered upload
|
|
154
158
|
is refused at the edge instead of failing as an ImportError on a GPU."""
|
|
155
159
|
|
|
@@ -203,7 +207,7 @@ class DeploymentRequest(BaseModel):
|
|
|
203
207
|
autoscaler: Autoscaler = Field(default_factory=Autoscaler)
|
|
204
208
|
# An omitted @seq.concurrent must NOT send null: the gateway would then have no concurrency to scale by.
|
|
205
209
|
# Materialize the spec default — contracts.md §2's example {"max_inputs": 1, "target_inputs": 1}, i.e.
|
|
206
|
-
# one input per container
|
|
210
|
+
# one input per container — same discipline as `autoscaler`
|
|
207
211
|
# (goal 13).
|
|
208
212
|
concurrency: Concurrency = Field(default_factory=lambda: Concurrency(max_inputs=1, target_inputs=1))
|
|
209
213
|
timeouts: Timeouts | None = None
|
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
reads back. One IntFlag, so a registration can carry several lifecycle entrypoints in one integer and
|
|
4
4
|
the harness recovers each by testing a bit.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
a thin adaptation of Modal's loader; the rest are platform-only lifecycle hooks Modal has no analogue
|
|
9
|
-
for (a warm-up hook, a plan step, and the benchmark SETUP / RESET / CHECK / SCORE quartet).
|
|
6
|
+
One bit per lifecycle hook: load, infer, an episode boundary and shutdown for a policy; a warm-up hook and a
|
|
7
|
+
plan step; and the benchmark SETUP / RESET / CHECK / SCORE quartet.
|
|
10
8
|
|
|
11
9
|
eval-raw revision: the platform provides the reset→step→check loop as a tool; the user defines the goal
|
|
12
10
|
through four hooks — SETUP (load sim + assets), RESET (build one episode's env), CHECK (per-step: read raw
|
|
@@ -14,10 +12,10 @@ env state → done + user-named metrics), SCORE (cross-episode aggregate). RESET
|
|
|
14
12
|
the new hook bits. The old single-hook `ROLLOUT` is DEPRECATED: bit 8192 stays reserved (never reused for
|
|
15
13
|
another member) but appears in NO bindings — the setup/reset/check/score model replaces it.
|
|
16
14
|
|
|
17
|
-
The exact bit values are the contract
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
The exact bit values are the contract: a registration serialized by one version must decode the same in
|
|
16
|
+
another. A member taking the wrong value, WARMUP collapsing back into LOAD, RESET/CHECK overlapping an
|
|
17
|
+
existing bit, or ROLLOUT being re-bound is caught bit-by-bit (see tests), not waved away with "an IntFlag
|
|
18
|
+
is naturally bit-composable".
|
|
21
19
|
"""
|
|
22
20
|
from __future__ import annotations
|
|
23
21
|
|
|
@@ -25,18 +23,18 @@ from enum import IntFlag
|
|
|
25
23
|
|
|
26
24
|
|
|
27
25
|
class SeqFlags(IntFlag):
|
|
28
|
-
# ──
|
|
29
|
-
LOAD = 2 #
|
|
30
|
-
SHUTDOWN = 8 #
|
|
31
|
-
INFER = 16 #
|
|
32
|
-
ON_EPISODE = 1024 #
|
|
26
|
+
# ── a policy's lifecycle ────────────────────────────────────────────────────────────────────────
|
|
27
|
+
LOAD = 2 # load weights into VRAM (one-shot; no snapshot bisection)
|
|
28
|
+
SHUTDOWN = 8 # teardown / release the container
|
|
29
|
+
INFER = 16 # the obs→ActionChunk call
|
|
30
|
+
ON_EPISODE = 1024 # sticky / session boundary for stateful policies
|
|
33
31
|
|
|
34
|
-
# ──
|
|
32
|
+
# ── each an independent bit, disjoint from the four above ──────────────────────────────────────
|
|
35
33
|
# WARMUP is its OWN bit, NOT an alias of LOAD (S1.yaml goal 5 — "WARMUP=1(独立 bit,明确不并入
|
|
36
34
|
# LOAD)"). A policy may bind @seq.load and @seq.warmup to two different methods → two independent
|
|
37
35
|
# bindings, so the bit must be distinct from LOAD. Value 1 is bit 0, unused by any mapped flag.
|
|
38
|
-
WARMUP = 1 # JIT warm-up hook
|
|
39
|
-
PLAN = 4 # a policy planning step
|
|
36
|
+
WARMUP = 1 # JIT warm-up hook, kept separate from LOAD
|
|
37
|
+
PLAN = 4 # a policy planning step — bit 2
|
|
40
38
|
# benchmark lifecycle (eval-raw: platform provides the loop tool, user hooks the goal in) — each an
|
|
41
39
|
# independent, disjoint bit, disjoint too from every mapped bit:
|
|
42
40
|
SETUP = 4096 # load sim + assets, once (≈ @seq.load)
|
|
@@ -49,21 +47,6 @@ class SeqFlags(IntFlag):
|
|
|
49
47
|
ROLLOUT = 8192
|
|
50
48
|
|
|
51
49
|
|
|
52
|
-
# Authoritative "member → Modal counterpart" list. Anything here must equal the bit above; anything in
|
|
53
|
-
# PLATFORM_ONLY must be disjoint from every mapped bit and from each other.
|
|
54
|
-
MODAL_MAPPING: dict[str, str] = {
|
|
55
|
-
"LOAD": "ENTER",
|
|
56
|
-
"SHUTDOWN": "EXIT",
|
|
57
|
-
"INFER": "CALLABLE_INTERFACE",
|
|
58
|
-
"ON_EPISODE": "~SESSIONED",
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
# Platform-only members Modal has no flag for. ROLLOUT is included: it is still a real (if deprecated)
|
|
62
|
-
# member and its bit must stay disjoint from every other, so the disjointness check must cover it.
|
|
63
|
-
PLATFORM_ONLY: frozenset[str] = frozenset(
|
|
64
|
-
{"WARMUP", "PLAN", "SETUP", "SCORE", "RESET", "CHECK", "ROLLOUT"}
|
|
65
|
-
)
|
|
66
|
-
|
|
67
50
|
# The benchmark lifecycle bits a live registration may bind (eval-raw): SETUP / RESET / CHECK / SCORE.
|
|
68
51
|
# ROLLOUT is deliberately absent — deprecated, never bound.
|
|
69
52
|
BENCHMARK_BINDABLE: frozenset[SeqFlags] = frozenset(
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The robot-arm I/O contracts (`action=`, `observation=`, `embodiments=`), re-exported from
|
|
3
|
+
`sequence_base.presets.robot_arm`.
|
|
4
|
+
|
|
5
|
+
Declaring one is optional since contract 0.4.14: a deployment that declares none is held to nothing, and its
|
|
6
|
+
`inputs` / `outputs` pass through the worker untouched. A declared contract is still enforced by the worker and
|
|
7
|
+
handed to the robot at connect. {gs-plans/custom-io/README.md "2. worker:原样交给用户代码"}
|
|
8
|
+
"""
|
|
9
|
+
from sequence_base.presets.robot_arm import ( # noqa: F401 — re-exports
|
|
10
|
+
PROPRIO_FIELDS,
|
|
11
|
+
ActionContract,
|
|
12
|
+
Camera,
|
|
13
|
+
Embodiment,
|
|
14
|
+
ObservationContract,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = ["ActionContract", "PROPRIO_FIELDS", "Camera", "ObservationContract", "Embodiment"]
|
|
@@ -30,6 +30,8 @@ __all__ = [
|
|
|
30
30
|
"ImageStepApt",
|
|
31
31
|
"ImageStepEnv",
|
|
32
32
|
"ImageStepRun",
|
|
33
|
+
"AddLocal",
|
|
34
|
+
"ImageStepAddLocal",
|
|
33
35
|
"ImageStep",
|
|
34
36
|
"ImageSpec",
|
|
35
37
|
"VolumeMount",
|
|
@@ -41,7 +43,7 @@ __all__ = [
|
|
|
41
43
|
]
|
|
42
44
|
|
|
43
45
|
|
|
44
|
-
# ── ImageSpec — a discriminated union of exactly
|
|
46
|
+
# ── ImageSpec — a discriminated union of exactly five step variants ───────────────────────────────
|
|
45
47
|
# Each variant carries exactly one key; extra="forbid" makes a dict that names two keys (or an unknown
|
|
46
48
|
# key) match NONE of them, so every step matches exactly one variant (goal 4). `run_commands` is its own
|
|
47
49
|
# variant (SDK contract S2 goal 5) so an arbitrary build command is an ordered step in the chain, never
|
|
@@ -69,11 +71,41 @@ class ImageStepRun(BaseModel):
|
|
|
69
71
|
secrets: list[SecretRef] | None = None
|
|
70
72
|
|
|
71
73
|
|
|
72
|
-
|
|
74
|
+
class AddLocal(BaseModel):
|
|
75
|
+
"""One local file or directory the author declared (`seq.Image.add_local_file / add_local_dir`): shipped inside the
|
|
76
|
+
deploy's code bundle at `src` and placed at `dst` in the image when it builds. Only the entry file (or its package)
|
|
77
|
+
is shipped otherwise. {gs-plans/custom-io/sdk-dx.md}"""
|
|
78
|
+
|
|
79
|
+
model_config = ConfigDict(extra="forbid")
|
|
80
|
+
|
|
81
|
+
src: str = Field(..., min_length=1, max_length=512, description="Path inside the code bundle (relative)")
|
|
82
|
+
dst: str = Field(..., min_length=2, max_length=512, description="Absolute path in the image")
|
|
83
|
+
|
|
84
|
+
@field_validator("src")
|
|
85
|
+
@classmethod
|
|
86
|
+
def _src_inside_bundle(cls, v: str) -> str:
|
|
87
|
+
if v.startswith("/") or "\\" in v or ".." in v.split("/"):
|
|
88
|
+
raise ValueError(f"add_local src {v!r} must be a relative path inside the code bundle, without '..'")
|
|
89
|
+
return v
|
|
90
|
+
|
|
91
|
+
@field_validator("dst")
|
|
92
|
+
@classmethod
|
|
93
|
+
def _dst_absolute(cls, v: str) -> str:
|
|
94
|
+
if not v.startswith("/") or ".." in v.split("/"):
|
|
95
|
+
raise ValueError(f"add_local dst {v!r} must be an absolute path in the image, without '..'")
|
|
96
|
+
return v
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ImageStepAddLocal(BaseModel):
|
|
100
|
+
model_config = ConfigDict(extra="forbid")
|
|
101
|
+
add_local: AddLocal
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
ImageStep = ImageStepUvPip | ImageStepApt | ImageStepEnv | ImageStepRun | ImageStepAddLocal
|
|
73
105
|
|
|
74
106
|
|
|
75
107
|
class ImageSpec(BaseModel):
|
|
76
|
-
"""Declarative image
|
|
108
|
+
"""Declarative image, built by the platform. Exactly one of
|
|
77
109
|
`base` (a pullable registry ref) or `dockerfile` (inline Dockerfile source) is set — the Dockerfile
|
|
78
110
|
is its OWN component, never smuggled into the `base` string (S2 goal 4), so the gateway can tell a
|
|
79
111
|
registry build from a Dockerfile build. `steps` is an ordered list of the four-variant union above
|
|
@@ -121,7 +153,7 @@ class VolumeRef(BaseModel):
|
|
|
121
153
|
|
|
122
154
|
|
|
123
155
|
class Concurrency(BaseModel):
|
|
124
|
-
"""
|
|
156
|
+
"""How many inputs one container serves at once: the most it takes, and the level it scales to hold. Both required."""
|
|
125
157
|
|
|
126
158
|
model_config = ConfigDict(extra="forbid")
|
|
127
159
|
|
|
@@ -130,8 +162,9 @@ class Concurrency(BaseModel):
|
|
|
130
162
|
|
|
131
163
|
|
|
132
164
|
class PolicySpec(BaseModel):
|
|
133
|
-
"""What `@seq.policy(...)` accrues — a policy's complete infra declaration, losslessly. `action`
|
|
134
|
-
|
|
165
|
+
"""What `@seq.policy(...)` accrues — a policy's complete infra declaration, losslessly. `action` /
|
|
166
|
+
`observation` / `embodiments` are optional (contract 0.4.14): a policy that declares none defines its I/O in its own
|
|
167
|
+
code, and the platform passes it through. {gs-plans/custom-io/README.md "2. worker:原样交给用户代码"}"""
|
|
135
168
|
|
|
136
169
|
model_config = ConfigDict(extra="forbid")
|
|
137
170
|
|
|
@@ -149,7 +182,8 @@ class PolicySpec(BaseModel):
|
|
|
149
182
|
concurrency: Concurrency | None = None
|
|
150
183
|
streaming: bool = False
|
|
151
184
|
frames_per_chunk: int | None = None
|
|
152
|
-
|
|
185
|
+
# The robot-arm preset's contracts, all optional: declared ones are enforced by the worker and handed to the robot.
|
|
186
|
+
action: ActionContract | None = None
|
|
153
187
|
# What the policy reads (None: undeclared — an older SDK); per-robot contracts when one model drives several.
|
|
154
188
|
observation: ObservationContract | None = None
|
|
155
189
|
embodiments: dict[str, Embodiment] | None = None
|
|
@@ -158,6 +192,9 @@ class PolicySpec(BaseModel):
|
|
|
158
192
|
# {name: directory}. Both None: one weights path, as before. {gs-plans/customization/options.md "多份权重文件"}
|
|
159
193
|
weights_name: str | None = None
|
|
160
194
|
extra_weights: dict[str, str] | None = None
|
|
195
|
+
# The author's own description of what infer() takes and returns. Stored and shown (connect hands it to the
|
|
196
|
+
# robot); the platform never interprets it.
|
|
197
|
+
io: str | None = Field(default=None, max_length=4000)
|
|
161
198
|
|
|
162
199
|
@field_validator("weights")
|
|
163
200
|
@classmethod
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The /act wire, version 2 — opaque. The platform carries a policy's inputs and outputs; it never reads them.
|
|
3
|
+
|
|
4
|
+
In one sentence: the robot's own code (a template's `client.py`) preprocesses raw sensor data into `inputs` → the SDK
|
|
5
|
+
posts them to the worker's /act unchanged → the worker hands them to the policy's `infer()` unchanged → whatever
|
|
6
|
+
`infer()` returns comes back as `outputs`, unchanged. What `inputs` and `outputs` hold is agreed between the two halves
|
|
7
|
+
of the user's own code, never by the platform. {gs-plans/custom-io/README.md "1. 线上格式:不透明"}
|
|
8
|
+
|
|
9
|
+
Alongside the payload the envelope carries only what the platform itself acts on: a seed (determinism), the episode
|
|
10
|
+
(per-episode state and `/act/episode_end`), the declared inference parameters, and — for a robot-arm preset policy with
|
|
11
|
+
several bodies — the embodiment chosen at connect.
|
|
12
|
+
|
|
13
|
+
Compatibility: a worker also accepts the version-1 body (`observation` instead of `inputs`), and a policy written
|
|
14
|
+
against the robot-arm preset (`infer(self, obs: Observation) -> ActionChunk`) also answers `action_chunk`, so older SDKs
|
|
15
|
+
and older deployments keep working in both directions.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
22
|
+
|
|
23
|
+
__all__ = ["ActRequest", "ActResponse"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ActRequest(BaseModel):
|
|
27
|
+
"""POST /act, version 2. `inputs` is any JSON value; the worker does not inspect it."""
|
|
28
|
+
|
|
29
|
+
model_config = ConfigDict(extra="forbid")
|
|
30
|
+
|
|
31
|
+
inputs: Any = Field(..., description="Whatever the policy's infer() takes, exactly as the robot's code built it")
|
|
32
|
+
seed: int | None = Field(default=None, description="Sampling seed, passed to infer() for a reproducible answer")
|
|
33
|
+
episode_id: str | None = Field(default=None, description="The episode this call belongs to (per-episode state)")
|
|
34
|
+
params: dict[str, Any] | None = Field(default=None,
|
|
35
|
+
description="Values for the inference parameters the policy declares")
|
|
36
|
+
embodiment: str | None = Field(default=None,
|
|
37
|
+
description="Robot-arm preset only: which declared embodiment this session drives")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ActResponse(BaseModel):
|
|
41
|
+
"""200 from /act, version 2. `outputs` is infer()'s return value, unchanged; the rest is platform metadata."""
|
|
42
|
+
|
|
43
|
+
model_config = ConfigDict(extra="forbid")
|
|
44
|
+
|
|
45
|
+
outputs: Any = Field(..., description="infer()'s return value, exactly as the policy returned it")
|
|
46
|
+
perf: dict[str, Any] | None = Field(default=None, description="Per-phase timings of this call")
|
|
47
|
+
usage: dict[str, Any] | None = Field(default=None, description="What this call counted towards the account")
|
|
48
|
+
warnings: list[str] = Field(default_factory=list, description="Non-fatal notes, e.g. actions clipped into bounds")
|
|
49
|
+
request_id: str | None = Field(default=None, description="This call's id; quote it when reporting a problem")
|
|
50
|
+
# The robot-arm preset's chunk under its version-1 key, so an SDK that predates version 2 still finds it. Present
|
|
51
|
+
# only when the policy answers with the preset's ActionChunk; then it equals `outputs`.
|
|
52
|
+
action_chunk: dict[str, Any] | None = Field(default=None,
|
|
53
|
+
description="Version-1 key for a robot-arm preset chunk (= outputs)")
|