sequence-base 0.4.23__tar.gz → 0.4.25__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.4.23 → sequence_base-0.4.25}/PKG-INFO +2 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/pyproject.toml +2 -2
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/__init__.py +2 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/constants/version.py +3 -1
- sequence_base-0.4.25/sequence_base/construct.py +103 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/contract/__init__.py +2 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/contract/benchmarks.py +6 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/contract/deployments.py +19 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/contract/policy.py +36 -2
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/msgpack_io.py +102 -10
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ActRequest.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ActResponse.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ActionChunk.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ActionContract.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ActionStep.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/AdapterPair.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/AddLocal.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/Autoscaler.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/BenchmarkConditionSpec.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/BenchmarkRegisterRequest.json +13 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/BenchmarkSpec.json +2 -2
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/BenchmarkStatusResponse.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/BootstrapResponse.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/Camera.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/CodeBundle.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/Concurrency.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/Conditions.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ConnectRequest.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ConnectResponse.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/DeploymentAccepted.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/DeploymentPolicy.json +33 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/DeploymentRequest.json +45 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/DeploymentStatusResponse.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/Embodiment.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ErrorBody.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ErrorCodes.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/Estimate.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/EvalDryRunResponse.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/EvalReport.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/EvalRequest.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/EvalResponse.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ImageFrame.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ImageSpec.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ImageStepAddLocal.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ImageStepApt.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ImageStepEnv.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ImageStepRun.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ImageStepTemplateFiles.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ImageStepUvPip.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/InferParam.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/Lease.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/MetricAgg.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/Observation.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ObservationContract.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/PolicyRegistration.json +48 -3
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/PolicySpec.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/Proprioception.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/RenderSpec.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/ReplaySpec.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/Resources.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/RolloutResult.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/SecretCreateRequest.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/SecretInfo.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/SecretRef.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/SeqFlags.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/Step.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/TaskAgg.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/TemplateFiles.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/Timeouts.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/UploadTarget.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/Variant.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/VolumeFile.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/VolumeInfo.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/VolumeMount.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/VolumeRef.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/VolumeUploadRequest.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/VolumeUploadResponse.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/WarmingError.json +1 -1
- {sequence_base-0.4.23 → sequence_base-0.4.25}/.gitignore +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/README.md +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/auth.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/codec/__init__.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/codec/image.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/constants/__init__.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/constants/cards.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/constants/headers.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/constants/limits.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/contract/accounts.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/contract/act.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/contract/adapters.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/contract/connect.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/contract/errors.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/contract/eval.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/contract/flags.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/contract/io.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/contract/templates.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/contract/volumes.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/contract/wire.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/errors/__init__.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/errors/transport.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/obs/__init__.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/obs/log.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/obs/rid.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/presets/__init__.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/presets/frames.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/presets/robot_arm.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schema_export.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/telemetry/__init__.py +0 -0
- {sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/telemetry/events.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sequence-base
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.25
|
|
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
5
|
Project-URL: Changelog, https://generalsequences.com/docs/changelog/
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -12,3 +12,4 @@ Provides-Extra: test
|
|
|
12
12
|
Requires-Dist: hypothesis>=6; extra == 'test'
|
|
13
13
|
Requires-Dist: jsonschema>=4; extra == 'test'
|
|
14
14
|
Requires-Dist: pytest; extra == 'test'
|
|
15
|
+
Requires-Dist: pyyaml; extra == 'test'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sequence-base"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.25"
|
|
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
|
requires-python = ">=3.10"
|
|
6
6
|
dependencies = ["pydantic>=2.0"]
|
|
@@ -10,7 +10,7 @@ Changelog = "https://generalsequences.com/docs/changelog/"
|
|
|
10
10
|
|
|
11
11
|
[project.optional-dependencies]
|
|
12
12
|
codec = ["pillow>=10", "numpy>=1.26"]
|
|
13
|
-
test = ["pytest", "jsonschema>=4", "hypothesis>=6"]
|
|
13
|
+
test = ["pytest", "jsonschema>=4", "hypothesis>=6", "pyyaml"]
|
|
14
14
|
|
|
15
15
|
[build-system]
|
|
16
16
|
requires = ["hatchling"]
|
|
@@ -76,6 +76,7 @@ from sequence_base.contract import (
|
|
|
76
76
|
Lease,
|
|
77
77
|
MetricAgg,
|
|
78
78
|
Observation,
|
|
79
|
+
PolicyConstructor,
|
|
79
80
|
PolicyRegistration,
|
|
80
81
|
PolicySpec,
|
|
81
82
|
Proprioception,
|
|
@@ -132,7 +133,7 @@ __all__ = [
|
|
|
132
133
|
"SeqFlags", "ImageStepUvPip", "ImageStepApt", "ImageStepEnv", "ImageStepRun", "AddLocal", "ImageStepAddLocal",
|
|
133
134
|
"FROZEN_PATH", "TemplateFiles", "ImageStepTemplateFiles", "ImageStep", "ImageSpec",
|
|
134
135
|
"VolumeMount", "VolumeRef", "Concurrency", "ActionContract", "Camera", "ObservationContract", "Embodiment",
|
|
135
|
-
"PolicySpec", "PolicyRegistration",
|
|
136
|
+
"PolicySpec", "PolicyRegistration", "PolicyConstructor",
|
|
136
137
|
|
|
137
138
|
"MAX_CODE_BUNDLE_BYTES", "CodeBundle",
|
|
138
139
|
"Resources", "Autoscaler", "Timeouts", "DeploymentPolicy", "InferParam", "DeploymentRequest",
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
How a policy class that names its own methods is built (PolicyRegistration.constructor): its constructor - or the
|
|
3
|
+
factory it names - called with the arguments its decorator was given, once two forms in them are filled in.
|
|
4
|
+
|
|
5
|
+
In one sentence: a value {"$config": path, "$set": {...}} becomes the YAML or JSON file at `path` - from the folder of
|
|
6
|
+
the class's file - read where the policy runs, with `$set`'s keys laid over its top level → in every string,
|
|
7
|
+
"{weights}" becomes the weights directory and "{weights.<part>}" a named part's → `cls.<make>(*args, **kwargs)`, or
|
|
8
|
+
`cls(*args, **kwargs)` without a factory.
|
|
9
|
+
|
|
10
|
+
WHY a config file is read where the policy runs: it is part of the project (XPolicyLab's deploy.yml beside its
|
|
11
|
+
model.py) and goes up with it, so one copy - the one the policy runs with - is read, and "{weights}" in it means the
|
|
12
|
+
worker's directory.
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
__all__ = ["CONFIG_KEY", "SET_KEY", "ConstructError", "build", "config", "expand", "is_config", "read_config"]
|
|
23
|
+
|
|
24
|
+
CONFIG_KEY, SET_KEY = "$config", "$set"
|
|
25
|
+
_WEIGHTS = re.compile(r"\{weights(?:\.([A-Za-z_][A-Za-z0-9_-]*))?\}")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ConstructError(ValueError):
|
|
29
|
+
"""Arguments that cannot be filled in: a config file missing, unreadable or of another kind, or a weights
|
|
30
|
+
placeholder with no weights - or no such part - to fill it."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def config(path: str, **overrides: Any) -> dict:
|
|
34
|
+
"""A config file's contents with `overrides` laid over its top level, as the worker reads it (`seq.config`)."""
|
|
35
|
+
if not isinstance(path, str) or not path:
|
|
36
|
+
raise ConstructError("a config file is a path, from the folder of the class's file")
|
|
37
|
+
if os.path.isabs(path):
|
|
38
|
+
raise ConstructError(f"{path}: give a config file's path from the folder of the class's file - the policy runs "
|
|
39
|
+
"it where the upload puts it")
|
|
40
|
+
return {CONFIG_KEY: path, SET_KEY: dict(overrides)}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def is_config(value: Any) -> bool:
|
|
44
|
+
return isinstance(value, dict) and set(value) == {CONFIG_KEY, SET_KEY} and isinstance(value[CONFIG_KEY], str)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def read_config(path: str) -> Any:
|
|
48
|
+
"""The YAML (.yml, .yaml) or JSON (.json) file at `path`."""
|
|
49
|
+
kind = os.path.splitext(path)[1].lower()
|
|
50
|
+
if kind not in (".json", ".yml", ".yaml"):
|
|
51
|
+
raise ConstructError(f"{path}: a config file is .json, .yml or .yaml")
|
|
52
|
+
if not os.path.isfile(path):
|
|
53
|
+
raise ConstructError(f"{path}: no such config file")
|
|
54
|
+
with open(path, encoding="utf-8") as f:
|
|
55
|
+
if kind == ".json":
|
|
56
|
+
return json.load(f)
|
|
57
|
+
try:
|
|
58
|
+
import yaml
|
|
59
|
+
except ImportError:
|
|
60
|
+
raise ConstructError(f"{path}: reading YAML needs pyyaml") from None
|
|
61
|
+
return yaml.safe_load(f)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def expand(value: Any, *, base_dir: str, weights: Any) -> Any:
|
|
65
|
+
"""`value` with its config files read (from `base_dir`) and its weights placeholders filled from `weights` - the
|
|
66
|
+
weights directory, or {part: directory} for named parts, or None for a policy without weights."""
|
|
67
|
+
if is_config(value):
|
|
68
|
+
loaded = read_config(os.path.join(base_dir, value[CONFIG_KEY]))
|
|
69
|
+
if value[SET_KEY] and not isinstance(loaded, dict):
|
|
70
|
+
raise ConstructError(f"{value[CONFIG_KEY]} holds a {type(loaded).__name__}, which has no keys to set")
|
|
71
|
+
merged = {**loaded, **value[SET_KEY]} if isinstance(loaded, dict) else loaded
|
|
72
|
+
return expand(merged, base_dir=base_dir, weights=weights)
|
|
73
|
+
if isinstance(value, dict):
|
|
74
|
+
return {k: expand(v, base_dir=base_dir, weights=weights) for k, v in value.items()}
|
|
75
|
+
if isinstance(value, (list, tuple)):
|
|
76
|
+
return [expand(v, base_dir=base_dir, weights=weights) for v in value]
|
|
77
|
+
if isinstance(value, str):
|
|
78
|
+
return _WEIGHTS.sub(lambda m: _weights_of(m.group(1), weights), value)
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _weights_of(part: str | None, weights: Any) -> str:
|
|
83
|
+
if weights is None:
|
|
84
|
+
raise ConstructError("{weights} names the weights directory, and this policy declares no weights=")
|
|
85
|
+
if isinstance(weights, dict):
|
|
86
|
+
if part is None:
|
|
87
|
+
raise ConstructError(f"its weights come in named parts ({', '.join(sorted(weights))}): name one, "
|
|
88
|
+
f"{{weights.<part>}}")
|
|
89
|
+
if part not in weights:
|
|
90
|
+
raise ConstructError(f"{{weights.{part}}}: its weights have no part {part!r} "
|
|
91
|
+
f"({', '.join(sorted(weights))})")
|
|
92
|
+
return str(weights[part])
|
|
93
|
+
if part is not None:
|
|
94
|
+
raise ConstructError(f"{{weights.{part}}} names a part, and its weights come whole: {{weights}}")
|
|
95
|
+
return str(weights)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def build(cls: type, constructor: Any, *, base_dir: str, weights: Any) -> Any:
|
|
99
|
+
"""The instance `constructor` (a PolicyConstructor) makes of `cls`: `cls.<make>(*args, **kwargs)` or `cls(...)`."""
|
|
100
|
+
args = expand(list(constructor.args), base_dir=base_dir, weights=weights)
|
|
101
|
+
kwargs = expand(dict(constructor.kwargs), base_dir=base_dir, weights=weights)
|
|
102
|
+
make = getattr(cls, constructor.make) if constructor.make else cls
|
|
103
|
+
return make(*args, **kwargs)
|
|
@@ -34,6 +34,7 @@ from sequence_base.contract.policy import (
|
|
|
34
34
|
FROZEN_PATH,
|
|
35
35
|
TemplateFiles,
|
|
36
36
|
ImageStepTemplateFiles,
|
|
37
|
+
PolicyConstructor,
|
|
37
38
|
PolicyRegistration,
|
|
38
39
|
PolicySpec,
|
|
39
40
|
VolumeMount,
|
|
@@ -121,7 +122,7 @@ __all__ = [
|
|
|
121
122
|
"ImageStepUvPip", "ImageStepApt", "ImageStepEnv", "ImageStepRun", "AddLocal", "ImageStepAddLocal",
|
|
122
123
|
"TemplateFiles", "ImageStepTemplateFiles", "FROZEN_PATH", "ImageStep", "ImageSpec",
|
|
123
124
|
"VolumeMount", "VolumeRef", "Concurrency", "ActionContract", "Camera", "ObservationContract", "Embodiment",
|
|
124
|
-
"PolicySpec", "PolicyRegistration",
|
|
125
|
+
"PolicySpec", "PolicyRegistration", "PolicyConstructor",
|
|
125
126
|
|
|
126
127
|
"MAX_CODE_BUNDLE_BYTES", "CodeBundle",
|
|
127
128
|
"Resources", "Autoscaler", "Timeouts", "DeploymentPolicy", "InferParam", "DeploymentRequest",
|
|
@@ -202,6 +202,19 @@ class DeploymentPolicy(BaseModel):
|
|
|
202
202
|
|
|
203
203
|
plan_every_s: float | None = Field(default=None, gt=0)
|
|
204
204
|
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
own_observations: Literal["latest", "since_last_act"] | None = None
|
|
209
|
+
own_actions: Literal["one", "steps"] | None = None
|
|
210
|
+
|
|
211
|
+
@model_validator(mode="after")
|
|
212
|
+
def _own_io(self) -> "DeploymentPolicy":
|
|
213
|
+
if (self.own_observations or self.own_actions) and (self.action or self.observation or self.embodiments):
|
|
214
|
+
raise ValueError("own_observations and own_actions are for a policy of its own inputs and outputs; this one "
|
|
215
|
+
"declares the robot-arm contract, where observation history and the action chunk say it")
|
|
216
|
+
return self
|
|
217
|
+
|
|
205
218
|
@model_validator(mode="after")
|
|
206
219
|
def _param_names(self) -> "DeploymentPolicy":
|
|
207
220
|
for name in self.params or {}:
|
|
@@ -283,6 +296,12 @@ class DeploymentRequest(BaseModel):
|
|
|
283
296
|
weights_name: str | None = None
|
|
284
297
|
extra_weights: dict[str, VolumeMount] | None = None
|
|
285
298
|
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
freeze: bool | None = None
|
|
304
|
+
|
|
286
305
|
@field_validator("name")
|
|
287
306
|
@classmethod
|
|
288
307
|
def _name_is_a_slug(cls, v: str) -> str:
|
|
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import re
|
|
12
12
|
|
|
13
|
-
from typing import Literal
|
|
13
|
+
from typing import Any, Literal
|
|
14
14
|
|
|
15
15
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
16
16
|
|
|
@@ -357,10 +357,31 @@ def check_weights_parts(weights_name: str | None, extra: dict) -> None:
|
|
|
357
357
|
raise ValueError(f"at most {MAX_WEIGHTS_PARTS} weights parts")
|
|
358
358
|
|
|
359
359
|
|
|
360
|
+
class PolicyConstructor(BaseModel):
|
|
361
|
+
"""How the worker builds a policy class that names its own methods (`@seq.policy(infer="get_action", ...)`): its
|
|
362
|
+
own constructor, or the factory it names (`make`, e.g. LeRobot's `from_pretrained`), called with `args` and
|
|
363
|
+
`kwargs` where `@seq.load` would run - the replica's cold start, the weights on disk. The values are the user's own,
|
|
364
|
+
as JSON carries them, but for two forms the worker fills in first (sequence_base.construct.expand): a config file's
|
|
365
|
+
contents, `{"$config": <its path from the class's file>, "$set": {<keys laid over it>}}`, YAML or JSON, and in any
|
|
366
|
+
string `"{weights}"` (the weights directory) or `"{weights.<part>}"` (a named part's).
|
|
367
|
+
"""
|
|
368
|
+
|
|
369
|
+
model_config = ConfigDict(extra="forbid")
|
|
370
|
+
|
|
371
|
+
make: str | None = Field(default=None, pattern=r"^[A-Za-z_][A-Za-z0-9_]*$",
|
|
372
|
+
description="a classmethod or staticmethod that returns the instance; None: the class")
|
|
373
|
+
args: list[Any] = Field(default_factory=list, description="positional arguments, in order")
|
|
374
|
+
kwargs: dict[str, Any] = Field(default_factory=dict, description="keyword arguments")
|
|
375
|
+
|
|
376
|
+
|
|
360
377
|
class PolicyRegistration(BaseModel):
|
|
361
378
|
"""What the harness loader introspects: the spec, the SeqFlags→method-name bindings, and the
|
|
362
379
|
`handler_ref` it importlib-loads the policy class from. From one of these the loader recovers which
|
|
363
|
-
method is which lifecycle entrypoint (load / infer / on_episode / plan …).
|
|
380
|
+
method is which lifecycle entrypoint (load / infer / on_episode / plan …).
|
|
381
|
+
|
|
382
|
+
A class that names its own methods (`constructor` set) is built by its constructor or factory, so it binds no LOAD,
|
|
383
|
+
and its bindings name its own methods: INFER (what answers an act), and as declared OBSERVE (each observation an
|
|
384
|
+
act carries, before INFER is called with none), RESET (an episode's first act) and ON_EPISODE (its end)."""
|
|
364
385
|
|
|
365
386
|
model_config = ConfigDict(extra="forbid")
|
|
366
387
|
|
|
@@ -374,3 +395,16 @@ class PolicyRegistration(BaseModel):
|
|
|
374
395
|
plan_every_s: float | None = Field(
|
|
375
396
|
default=None, gt=0, description="Seconds between @seq.plan calls; set iff a PLAN binding exists"
|
|
376
397
|
)
|
|
398
|
+
|
|
399
|
+
constructor: PolicyConstructor | None = None
|
|
400
|
+
|
|
401
|
+
@model_validator(mode="after")
|
|
402
|
+
def _built_by_its_constructor(self) -> "PolicyRegistration":
|
|
403
|
+
if self.constructor is not None:
|
|
404
|
+
if SeqFlags.LOAD in self.bindings:
|
|
405
|
+
raise ValueError("a policy built by its constructor (constructor) has no @seq.load: its constructor is "
|
|
406
|
+
"its load")
|
|
407
|
+
if SeqFlags.INFER not in self.bindings:
|
|
408
|
+
raise ValueError("a policy built by its constructor (constructor) names the method that answers an act "
|
|
409
|
+
"(an INFER binding)")
|
|
410
|
+
return self
|
|
@@ -6,7 +6,8 @@ Written in plain Python, so neither a worker nor a client installs a msgpack pac
|
|
|
6
6
|
In one sentence: a client takes the act's envelope ({"inputs", "seed", "episode_id", "params", ...}) → `pack` writes it
|
|
7
7
|
as msgpack, each numpy array as the map msgpack-numpy writes ({"nd": True, "type", "kind", "shape", "data"}) → a worker
|
|
8
8
|
that names FORMAT among its formats `unpack`s it back to the same dict, arrays as arrays, and serves it as it serves a
|
|
9
|
-
JSON act; an answer asked for in msgpack (Accept: MEDIA_TYPE) comes back the same way
|
|
9
|
+
JSON act; an answer asked for in msgpack (Accept: MEDIA_TYPE) comes back the same way - its arrays, to a client
|
|
10
|
+
without numpy, as the lists their tolist() gives, what the answer in JSON holds.
|
|
10
11
|
|
|
11
12
|
WHY a media type of its own: a worker reads a body that is not JSON as the policy's own inputs and hands it on
|
|
12
13
|
untouched - an `application/msgpack` body included, which some policies decode themselves - so a client sends this
|
|
@@ -24,8 +25,10 @@ anything.
|
|
|
24
25
|
"""
|
|
25
26
|
from __future__ import annotations
|
|
26
27
|
|
|
28
|
+
import re
|
|
27
29
|
import struct
|
|
28
|
-
|
|
30
|
+
import sys
|
|
31
|
+
from typing import Any, NamedTuple
|
|
29
32
|
|
|
30
33
|
__all__ = ["FORMAT", "MAX_DEPTH", "MEDIA_TYPE", "MsgpackError", "pack", "unpack"]
|
|
31
34
|
|
|
@@ -154,9 +157,10 @@ def _readable_dtype(dtype) -> None:
|
|
|
154
157
|
|
|
155
158
|
|
|
156
159
|
def unpack(data: bytes) -> Any:
|
|
157
|
-
"""The one msgpack value `data` holds, numpy arrays (msgpack-numpy's or openpi's layout) back as read-only arrays
|
|
158
|
-
|
|
159
|
-
an
|
|
160
|
+
"""The one msgpack value `data` holds, numpy arrays (msgpack-numpy's or openpi's layout) back as read-only arrays -
|
|
161
|
+
or, where numpy is not installed, as the nested lists of plain values their tolist() gives, what a JSON answer
|
|
162
|
+
holds. MsgpackError: truncated, more than one value, an extension type, nested past MAX_DEPTH, a length past the
|
|
163
|
+
body, or an array whose dtype holds objects."""
|
|
160
164
|
view = memoryview(bytes(data)) if not isinstance(data, bytes) else memoryview(data)
|
|
161
165
|
value, at = _Reader(view).read(0, 0)
|
|
162
166
|
if at != len(view):
|
|
@@ -284,12 +288,13 @@ def _array_of(m: dict) -> Any:
|
|
|
284
288
|
dtype, data = _dtype(_field(m, "type")), _field(m, "data")
|
|
285
289
|
if not isinstance(data, bytes) or len(data) != dtype.itemsize:
|
|
286
290
|
raise MsgpackError(f"a numpy {dtype} scalar whose data is not {dtype.itemsize} bytes")
|
|
287
|
-
|
|
291
|
+
np = _numpy()
|
|
292
|
+
return _plain_items(dtype, data, 1)[0] if np is None else np.frombuffer(data, dtype=dtype, count=1)[0]
|
|
288
293
|
if generic is True and has("dtype", "data"):
|
|
289
294
|
dtype, value = _dtype(_field(m, "dtype")), _field(m, "data")
|
|
290
295
|
if not isinstance(value, (bool, int, float, str, bytes)):
|
|
291
296
|
raise MsgpackError("a numpy scalar's map whose data is not a plain value")
|
|
292
|
-
return dtype.type(value)
|
|
297
|
+
return value if _numpy() is None else dtype.type(value)
|
|
293
298
|
return m
|
|
294
299
|
|
|
295
300
|
|
|
@@ -298,31 +303,118 @@ def _array(descr, shape, data):
|
|
|
298
303
|
if not isinstance(shape, list) or not all(isinstance(d, int) and not isinstance(d, bool) and d >= 0
|
|
299
304
|
for d in shape) or not isinstance(data, bytes):
|
|
300
305
|
raise MsgpackError("a numpy array's map without a shape of non-negative ints or its data as bytes")
|
|
306
|
+
if len(shape) > _MAX_DIMS:
|
|
307
|
+
raise MsgpackError(f"a numpy array of {len(shape)} dimensions: numpy's own are {_MAX_DIMS} at most")
|
|
301
308
|
count = 1
|
|
302
309
|
for d in shape:
|
|
303
310
|
count *= d
|
|
304
311
|
if len(data) != count * dtype.itemsize:
|
|
305
312
|
raise MsgpackError(f"a numpy array of shape {shape} and dtype {dtype} is {count * dtype.itemsize} bytes; its "
|
|
306
313
|
f"data is {len(data)}")
|
|
307
|
-
|
|
314
|
+
np = _numpy()
|
|
315
|
+
if np is None:
|
|
316
|
+
return _nested(_plain_items(dtype, data, count), shape)
|
|
317
|
+
return np.frombuffer(data, dtype=dtype, count=count).reshape(shape)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
_MAX_DIMS = 64
|
|
308
322
|
|
|
309
323
|
|
|
310
324
|
def _numpy():
|
|
325
|
+
"""numpy, or None where it is not installed: its arrays are then read as plain values (_Plain)."""
|
|
311
326
|
try:
|
|
312
327
|
import numpy
|
|
313
328
|
except ImportError:
|
|
314
|
-
|
|
329
|
+
return None
|
|
315
330
|
return numpy
|
|
316
331
|
|
|
317
332
|
|
|
318
333
|
def _dtype(descr):
|
|
319
|
-
|
|
334
|
+
"""`descr`, an array's type string, as numpy's dtype - or, where numpy is not installed, as a _Plain read here.
|
|
335
|
+
MsgpackError for one that holds objects, a structured one, and one that is not a type string."""
|
|
320
336
|
if not isinstance(descr, (str, bytes)):
|
|
321
337
|
raise MsgpackError("a numpy array's dtype is not a type string: a structured array is not carried, only "
|
|
322
338
|
"plain numeric, bool and string dtypes are")
|
|
339
|
+
np = _numpy()
|
|
340
|
+
if np is None:
|
|
341
|
+
return _plain(_text(descr))
|
|
323
342
|
try:
|
|
324
343
|
dtype = np.dtype(_text(descr))
|
|
325
344
|
except TypeError:
|
|
326
345
|
raise MsgpackError(f"not a numpy dtype: {_text(descr)!r}") from None
|
|
327
346
|
_readable_dtype(dtype)
|
|
328
347
|
return dtype
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class _Plain(NamedTuple):
|
|
357
|
+
"""An array's type string ('<f4', '|u1', '<U5' - numpy's dtype.str) read without numpy: struct's byte order, the
|
|
358
|
+
kind (numpy's letter) and one item's size in bytes."""
|
|
359
|
+
text: str
|
|
360
|
+
order: str
|
|
361
|
+
kind: str
|
|
362
|
+
itemsize: int
|
|
363
|
+
|
|
364
|
+
def __str__(self) -> str:
|
|
365
|
+
return self.text
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
_STRUCT = {("b", 1): "?", ("i", 1): "b", ("i", 2): "h", ("i", 4): "i", ("i", 8): "q", ("u", 1): "B", ("u", 2): "H",
|
|
371
|
+
("u", 4): "I", ("u", 8): "Q", ("f", 2): "e", ("f", 4): "f", ("f", 8): "d", ("c", 8): "f", ("c", 16): "d"}
|
|
372
|
+
_TYPE = re.compile(r"([<>|=]?)([A-Za-z])(.*)", re.DOTALL)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _plain(text: str) -> _Plain:
|
|
376
|
+
"""MsgpackError for a type string that holds objects or is structured - refused as with numpy - one whose items
|
|
377
|
+
are no bytes at all, and one only numpy reads (a long double, a date, a time span)."""
|
|
378
|
+
match = _TYPE.fullmatch(text)
|
|
379
|
+
order, kind, size = match.groups() if match else ("", "", "")
|
|
380
|
+
if kind in ("O", "V"):
|
|
381
|
+
raise MsgpackError(f"a numpy array of dtype {text} is not carried: only plain numeric, bool and string dtypes "
|
|
382
|
+
"are")
|
|
383
|
+
n = int(size) if size.isascii() and size.isdigit() else -1
|
|
384
|
+
if kind in ("S", "U") and n == 0:
|
|
385
|
+
raise MsgpackError(f"a numpy array of dtype {text}, whose items are 0 bytes")
|
|
386
|
+
itemsize = 4 * n if kind == "U" else n
|
|
387
|
+
if not ((kind in ("S", "U") and n > 0) or (kind, itemsize) in _STRUCT):
|
|
388
|
+
raise MsgpackError(f"this body holds a numpy array of dtype {text}, which only numpy reads, and numpy is not "
|
|
389
|
+
"installed here")
|
|
390
|
+
return _Plain(text, "<" if order in ("", "|") else order, kind, itemsize)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _plain_items(t: _Plain, data: bytes, count: int) -> list:
|
|
394
|
+
"""`count` items of type `t` from `data`, as tolist() gives them: ints, floats, complexes and bools; bytes and
|
|
395
|
+
strs with their trailing NULs cut, as numpy cuts them."""
|
|
396
|
+
size = t.itemsize
|
|
397
|
+
if t.kind == "S":
|
|
398
|
+
return [data[i:i + size].rstrip(b"\x00") for i in range(0, count * size, size)]
|
|
399
|
+
if t.kind == "U":
|
|
400
|
+
big = t.order == ">" or (t.order == "=" and sys.byteorder == "big")
|
|
401
|
+
try:
|
|
402
|
+
return [str(data[i:i + size], "utf-32-be" if big else "utf-32-le").rstrip("\x00")
|
|
403
|
+
for i in range(0, count * size, size)]
|
|
404
|
+
except UnicodeDecodeError:
|
|
405
|
+
raise MsgpackError(f"a numpy array of dtype {t} whose data is not UTF-32") from None
|
|
406
|
+
code = _STRUCT[(t.kind, size)]
|
|
407
|
+
if t.kind == "c":
|
|
408
|
+
parts = struct.unpack_from(f"{t.order}{2 * count}{code}", data)
|
|
409
|
+
return [complex(parts[i], parts[i + 1]) for i in range(0, 2 * count, 2)]
|
|
410
|
+
return list(struct.unpack_from(f"{t.order}{count}{code}", data))
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _nested(flat: list, shape: list) -> Any:
|
|
414
|
+
"""`flat`, in C order, as the nested lists of `shape`, as tolist() nests them - a 0-d array's one value as itself."""
|
|
415
|
+
if not shape:
|
|
416
|
+
return flat[0]
|
|
417
|
+
if len(shape) == 1:
|
|
418
|
+
return flat
|
|
419
|
+
step = len(flat) // shape[0] if shape[0] else 0
|
|
420
|
+
return [_nested(flat[i * step:(i + 1) * step], shape[1:]) for i in range(shape[0])]
|
{sequence_base-0.4.23 → sequence_base-0.4.25}/sequence_base/schemas/BenchmarkRegisterRequest.json
RENAMED
|
@@ -809,6 +809,18 @@
|
|
|
809
809
|
"default": null,
|
|
810
810
|
"title": "Envs"
|
|
811
811
|
},
|
|
812
|
+
"freeze": {
|
|
813
|
+
"anyOf": [
|
|
814
|
+
{
|
|
815
|
+
"type": "boolean"
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
"type": "null"
|
|
819
|
+
}
|
|
820
|
+
],
|
|
821
|
+
"default": null,
|
|
822
|
+
"title": "Freeze"
|
|
823
|
+
},
|
|
812
824
|
"gpu": {
|
|
813
825
|
"anyOf": [
|
|
814
826
|
{
|
|
@@ -974,5 +986,5 @@
|
|
|
974
986
|
],
|
|
975
987
|
"title": "BenchmarkRegisterRequest",
|
|
976
988
|
"type": "object",
|
|
977
|
-
"x-contract-version": "0.5.
|
|
989
|
+
"x-contract-version": "0.5.22"
|
|
978
990
|
}
|
|
@@ -379,7 +379,7 @@
|
|
|
379
379
|
],
|
|
380
380
|
"title": "SeqFlags",
|
|
381
381
|
"type": "integer",
|
|
382
|
-
"x-contract-version": "0.5.
|
|
382
|
+
"x-contract-version": "0.5.22",
|
|
383
383
|
"x-flag-names": {
|
|
384
384
|
"CHECK": 65536,
|
|
385
385
|
"INFER": 16,
|
|
@@ -628,5 +628,5 @@
|
|
|
628
628
|
],
|
|
629
629
|
"title": "BenchmarkSpec",
|
|
630
630
|
"type": "object",
|
|
631
|
-
"x-contract-version": "0.5.
|
|
631
|
+
"x-contract-version": "0.5.22"
|
|
632
632
|
}
|