Signed-off-by: zhangbin.327 <zhangbin.327@bytedance.com> Signed-off-by: Jack47 <scsvip@gmail.com> Co-authored-by: zhangbin.327 <zhangbin.327@bytedance.com> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
203 lines
8.0 KiB
Python
203 lines
8.0 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
"""Typed request boundary shared by AR-Diffusion models and serving APIs.
|
|
|
|
The transport layer owns event ordering and snapshots. Model adapters consume
|
|
the resulting immutable tick without learning about WebSocket messages.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping, Sequence
|
|
from dataclasses import dataclass
|
|
from types import MappingProxyType
|
|
from typing import Any
|
|
|
|
AR_DIFFUSION_TICK_KEY = "ar_diffusion_tick"
|
|
|
|
|
|
def _non_empty_string(value: object, *, field: str) -> str:
|
|
if not isinstance(value, str) or not value.strip():
|
|
raise ValueError(f"{field} must be a non-empty string.")
|
|
return value
|
|
|
|
|
|
def _non_negative_int(value: object, *, field: str) -> int:
|
|
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
|
|
raise ValueError(f"{field} must be a non-negative integer.")
|
|
return value
|
|
|
|
|
|
def _freeze_transport_value(value: Any, *, path: str) -> Any:
|
|
"""Copy JSON-like transport data into recursively immutable containers."""
|
|
if value is None or isinstance(value, (str, bool, int, float)):
|
|
return value
|
|
if isinstance(value, Mapping):
|
|
frozen: dict[str, Any] = {}
|
|
for key, item in value.items():
|
|
if not isinstance(key, str):
|
|
raise ValueError(f"{path} mapping keys must be strings.")
|
|
frozen[key] = _freeze_transport_value(item, path=f"{path}.{key}")
|
|
return MappingProxyType(frozen)
|
|
if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
|
|
return tuple(_freeze_transport_value(item, path=f"{path}[]") for item in value)
|
|
raise ValueError(
|
|
f"{path} must contain only transport-safe mappings, sequences, and scalar values; got {type(value).__name__}."
|
|
)
|
|
|
|
|
|
def _thaw_transport_value(value: Any) -> Any:
|
|
"""Return a detached JSON-serializable copy of an immutable snapshot."""
|
|
if isinstance(value, Mapping):
|
|
return {key: _thaw_transport_value(item) for key, item in value.items()}
|
|
if isinstance(value, tuple):
|
|
return [_thaw_transport_value(item) for item in value]
|
|
return value
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ARDiffusionControlInput:
|
|
"""One model-defined control track in a chunk-boundary snapshot."""
|
|
|
|
track: str
|
|
schema: str
|
|
data: Mapping[str, Any]
|
|
|
|
def __post_init__(self) -> None:
|
|
_non_empty_string(self.track, field="control.track")
|
|
_non_empty_string(self.schema, field="control.schema")
|
|
if not isinstance(self.data, Mapping):
|
|
raise ValueError("control.data must be a mapping.")
|
|
object.__setattr__(
|
|
self,
|
|
"data",
|
|
_freeze_transport_value(self.data, path=f"control[{self.track!r}].data"),
|
|
)
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"track": self.track,
|
|
"schema": self.schema,
|
|
"data": _thaw_transport_value(self.data),
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, value: object) -> ARDiffusionControlInput:
|
|
if not isinstance(value, Mapping):
|
|
raise ValueError("Each control must be a mapping.")
|
|
return cls(
|
|
track=value.get("track"), # type: ignore[arg-type]
|
|
schema=value.get("schema"), # type: ignore[arg-type]
|
|
data=value.get("data"), # type: ignore[arg-type]
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ARDiffusionTickRequest:
|
|
"""A single atomic AR-block computation.
|
|
|
|
``request_id`` identifies this computation, ``session_id`` identifies the
|
|
persistent rollout, and ``applied_event_ids`` records which accepted
|
|
interaction events are represented by this immutable snapshot.
|
|
"""
|
|
|
|
session_id: str
|
|
request_id: str
|
|
chunk_index: int
|
|
applied_event_ids: tuple[int, ...] = ()
|
|
prompt: str | None = None
|
|
controls: tuple[ARDiffusionControlInput, ...] = ()
|
|
reset: bool = False
|
|
close_session: bool = False
|
|
|
|
def __post_init__(self) -> None:
|
|
_non_empty_string(self.session_id, field="session_id")
|
|
_non_empty_string(self.request_id, field="request_id")
|
|
_non_negative_int(self.chunk_index, field="chunk_index")
|
|
if self.prompt is not None and not isinstance(self.prompt, str):
|
|
raise ValueError("prompt must be a string or None.")
|
|
if not isinstance(self.reset, bool):
|
|
raise ValueError("reset must be a boolean.")
|
|
if not isinstance(self.close_session, bool):
|
|
raise ValueError("close_session must be a boolean.")
|
|
|
|
event_ids = tuple(
|
|
_non_negative_int(event_id, field="applied_event_ids item") for event_id in self.applied_event_ids
|
|
)
|
|
if event_ids != tuple(sorted(set(event_ids))):
|
|
raise ValueError("applied_event_ids must be unique and strictly increasing.")
|
|
controls = tuple(self.controls)
|
|
if any(not isinstance(control, ARDiffusionControlInput) for control in controls):
|
|
raise ValueError("controls must contain ARDiffusionControlInput values.")
|
|
object.__setattr__(self, "applied_event_ids", event_ids)
|
|
object.__setattr__(self, "controls", controls)
|
|
|
|
def to_extra_args(self) -> dict[str, Any]:
|
|
"""Serialize the tick under one namespaced ``extra_args`` key."""
|
|
return {
|
|
AR_DIFFUSION_TICK_KEY: {
|
|
"session_id": self.session_id,
|
|
"request_id": self.request_id,
|
|
"chunk_index": self.chunk_index,
|
|
"applied_event_ids": list(self.applied_event_ids),
|
|
"prompt": self.prompt,
|
|
"controls": [control.to_dict() for control in self.controls],
|
|
"reset": self.reset,
|
|
"close_session": self.close_session,
|
|
}
|
|
}
|
|
|
|
@classmethod
|
|
def from_extra_args(
|
|
cls,
|
|
extra_args: Mapping[str, Any] | None,
|
|
) -> ARDiffusionTickRequest | None:
|
|
"""Parse a typed tick, returning ``None`` for legacy AR requests."""
|
|
if extra_args is None or AR_DIFFUSION_TICK_KEY not in extra_args:
|
|
return None
|
|
value = extra_args[AR_DIFFUSION_TICK_KEY]
|
|
if not isinstance(value, Mapping):
|
|
raise ValueError(f"{AR_DIFFUSION_TICK_KEY} must be a mapping.")
|
|
raw_event_ids = value.get("applied_event_ids", ())
|
|
raw_controls = value.get("controls", ())
|
|
if not isinstance(raw_event_ids, Sequence) or isinstance(raw_event_ids, (str, bytes)):
|
|
raise ValueError("applied_event_ids must be a sequence.")
|
|
if not isinstance(raw_controls, Sequence) or isinstance(raw_controls, (str, bytes)):
|
|
raise ValueError("controls must be a sequence.")
|
|
return cls(
|
|
session_id=value.get("session_id"), # type: ignore[arg-type]
|
|
request_id=value.get("request_id"), # type: ignore[arg-type]
|
|
chunk_index=value.get("chunk_index"), # type: ignore[arg-type]
|
|
applied_event_ids=tuple(raw_event_ids),
|
|
prompt=value.get("prompt"), # type: ignore[arg-type]
|
|
controls=tuple(ARDiffusionControlInput.from_dict(control) for control in raw_controls),
|
|
reset=value.get("reset", False), # type: ignore[arg-type]
|
|
close_session=value.get("close_session", False), # type: ignore[arg-type]
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ARDiffusionChunkMetadata:
|
|
"""Identity metadata returned with one generated AR block."""
|
|
|
|
session_id: str
|
|
request_id: str
|
|
chunk_index: int
|
|
applied_event_ids: tuple[int, ...] = ()
|
|
|
|
@classmethod
|
|
def from_tick(cls, tick: ARDiffusionTickRequest) -> ARDiffusionChunkMetadata:
|
|
return cls(
|
|
session_id=tick.session_id,
|
|
request_id=tick.request_id,
|
|
chunk_index=tick.chunk_index,
|
|
applied_event_ids=tick.applied_event_ids,
|
|
)
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"session_id": self.session_id,
|
|
"request_id": self.request_id,
|
|
"chunk_index": self.chunk_index,
|
|
"applied_event_ids": list(self.applied_event_ids),
|
|
}
|