Skip to content

API reference

Auto-generated from docstrings via mkdocstrings. Source of truth remains the code in src/laya_serve/.

App

laya_serve.app

FastAPI application factory.

Endpoints (Jev-compatible):

  • POST /v1/systemone — evaluate state + questions, return typed answers.
  • GET /v1/models — list servable model names/aliases.
  • GET /healthz — liveness probe (not part of the Jev API).

Errors are always Jev-shaped ({"error": {"message", "field"}}): 401 bad key, 422 validation, 429 rate limit (with Retry-After), 529 transient overload (with Retry-After), 500 unexpected backend failure.

AuthError

Bases: Exception

Raised when bearer auth fails; handled as a Jev-shaped 401.

Source code in src/laya_serve/app.py
37
38
39
40
class AuthError(Exception):
    """Raised when bearer auth fails; handled as a Jev-shaped ``401``."""

    pass

Schemas

laya_serve.schemas

Jev-compatible Pydantic v2 schemas for the SystemOne API.

Mirrors POST /v1/systemone from the TypeSafe docs:

  • Request: {state, model, questions} where each question is one of choice / score / noul.
  • Response: {model, answers, usage} with typed answers keyed by the caller-chosen question ids.

Wire format notes:

  • Score probabilities / legend keys are strings on the wire ({"0": ...}); the Python SDK exposes them as ints. This server speaks the HTTP wire format.
  • instructions and criteria descriptions accept str | dict | list (structured prompts); dict/list values are rendered by the backend.

SystemOneRequest

Bases: BaseModel

POST /v1/systemone request body.

Source code in src/laya_serve/schemas.py
83
84
85
86
87
88
89
90
class SystemOneRequest(BaseModel):
    """``POST /v1/systemone`` request body."""

    model_config = ConfigDict(extra="forbid")

    state: StateT
    model: str = Field(min_length=1)
    questions: Annotated[dict[str, QuestionT], Field(min_length=1)]

SystemOneResponse

Bases: BaseModel

POST /v1/systemone response body.

Source code in src/laya_serve/schemas.py
132
133
134
135
136
137
138
139
class SystemOneResponse(BaseModel):
    """``POST /v1/systemone`` response body."""

    model_config = ConfigDict(extra="forbid")

    model: str
    answers: dict[str, AnswerT]
    usage: Usage

Settings

laya_serve.settings

Runtime configuration, sourced from environment variables.

All variables use the LAYA_SERVE_ prefix, e.g. LAYA_SERVE_PRELOAD=true.

Settings

Bases: BaseSettings

Source code in src/laya_serve/settings.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_prefix="LAYA_SERVE_", extra="ignore")

    # Model id reported in the ``model`` response field.
    serving_model: str = "laya-english"
    # Additional model names accepted in the request ``model`` field,
    # beyond the built-in Jev family (comma-separated via env).
    extra_models: str = ""

    @property
    def extra_models_set(self) -> frozenset[str]:
        """Parsed :attr:`extra_models` as a set of names."""
        return frozenset(part.strip() for part in self.extra_models.split(",") if part.strip())

    # Inference backend: "laya" (real weights) or "fake" (deterministic,
    # weight-free answers for tests and local dev).
    backend: str = "fake"

    # Passed through to the Laya Router when backend="laya".
    device: str | None = None
    max_loaded: int = 1
    preload: bool = False

    # When set, clients must send ``Authorization: Bearer <api_key>``.
    # Unset means no auth (local dev default).
    api_key: str | None = None

    # In-process fixed-window rate limit for ``POST /v1/systemone``,
    # in requests per 60s window per client identity (API key when
    # configured, else client IP). ``0`` (default) disables limiting to
    # preserve single-process dev behaviour; production behind multiple
    # workers should enforce limits at the gateway instead.
    rate_limit_per_minute: int = 0

extra_models_set property

Parsed :attr:extra_models as a set of names.

Service

laya_serve.service

Application service: orchestrates validation, inference, and compat shaping.

enforce_budgets(state, questions, backend)

Fail fast with 422 when the request exceeds the backend's token budgets.

Compares backend.count(state, questions) against the backend's max_total_tokens / max_state_question_tokens (Jev: 64k total, 32k state+longest-question). Backends that predate the budget interface skip enforcement. A broken counter fails open (warn + skip) so a counting bug can never turn a small request into a 500.

Source code in src/laya_serve/service.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def enforce_budgets(state: Any, questions: dict[str, dict[str, Any]], backend: Backend) -> None:
    """Fail fast with ``422`` when the request exceeds the backend's token budgets.

    Compares ``backend.count(state, questions)`` against the backend's
    ``max_total_tokens`` / ``max_state_question_tokens`` (Jev: 64k total,
    32k state+longest-question). Backends that predate the budget
    interface skip enforcement. A broken counter fails open (warn + skip)
    so a counting bug can never turn a small request into a ``500``.
    """
    count = getattr(backend, "count", None)
    max_total = getattr(backend, "max_total_tokens", None)
    max_longest = getattr(backend, "max_state_question_tokens", None)
    if not callable(count) or max_total is None or max_longest is None:
        return
    try:
        total, state_longest = (int(n) for n in count(state, questions))
    except Exception:
        logger.warning("budget count failed; skipping enforcement", exc_info=True)
        return
    if state_longest > max_longest:
        raise compat.CompatError(
            f"context budget exceeded: state plus longest question is ~{state_longest} "
            f"tokens (limit {max_longest})",
            field="questions",
        )
    if total > max_total:
        raise compat.CompatError(
            f"context budget exceeded: state plus all questions is ~{total} tokens "
            f"(limit {max_total})",
            field="questions",
        )

evaluate(request, backend, settings)

Run one SystemOne request and return a Jev-shaped response dict.

Source code in src/laya_serve/service.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def evaluate(request: SystemOneRequest, backend: Backend, settings: Settings) -> dict[str, Any]:
    """Run one SystemOne request and return a Jev-shaped response dict."""
    serving_model = compat.resolve_model(
        request.model, backend.serving_model, settings.extra_models_set
    )
    questions = request.model_dump(mode="json", exclude_none=False)["questions"]
    enforce_budgets(request.state, questions, backend)
    try:
        raw = backend.predict(request.state, questions)
    except ValueError as exc:
        mapped = _as_budget_error(exc)
        if mapped is exc:
            raise
        raise mapped from exc
    return compat.shape_response(
        questions,
        raw["answers"],
        raw.get("usage", {}),
        serving_model,
    )

list_models(settings, backend)

Return the models listing: the serving checkpoint plus Jev aliases.

Source code in src/laya_serve/service.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def list_models(settings: Settings, backend: Backend) -> dict[str, Any]:
    """Return the models listing: the serving checkpoint plus Jev aliases."""
    from .schemas import ModelsResponse

    cards = [
        {
            "name": backend.serving_model,
            "description": "Laya decision model served with a Jev-compatible API.",
            "release_date": "unknown",
        }
    ]
    for alias in sorted(compat.JEV_KNOWN_MODELS | settings.extra_models_set):
        if alias == backend.serving_model:
            continue
        cards.append(
            {
                "name": alias,
                "description": f"Alias resolving to {backend.serving_model}.",
                "release_date": "unknown",
            }
        )
    return ModelsResponse.model_validate({"models": cards}).model_dump(mode="json")

Compat

laya_serve.compat

Translate raw Laya backend payloads into the Jev wire format.

Known divergences between Laya's Agent.system_one output and Jev (verified against laya/agent.py and docs.typesafe.ai/api.md):

  1. Laya attaches action: {act_probability} to every answer; Jev has no such field. Stripped here.
  2. Laya returns confidence on noul answers; Jev noul answers carry only {type, noul}. Stripped here.
  3. Laya reports model: "laya-rl-agent"; Jev echoes the resolved versioned model id. The caller supplies the id to report.
  4. Laya always reports output_tokens: 0; Jev reports a (free but nonzero) count. Preserved as-is and documented — this layer does not invent token counts.

All functions here are pure and backend-agnostic: they operate on plain dicts, so they are unit-testable without model weights.

CompatError

Bases: ValueError

Raised when a backend payload cannot be shaped into a Jev answer.

Source code in src/laya_serve/compat.py
48
49
50
51
52
53
class CompatError(ValueError):
    """Raised when a backend payload cannot be shaped into a Jev answer."""

    def __init__(self, message: str, field: str | None = None) -> None:
        super().__init__(message)
        self.field = field

resolve_model(requested, serving_model, extra_models=frozenset())

Validate the requested model and return the id to report.

Known Jev names (plus operator-configured extras) resolve to serving_model — the checkpoint actually answering, e.g. "laya-english". Unknown names raise :class:CompatError.

Source code in src/laya_serve/compat.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def resolve_model(
    requested: str, serving_model: str, extra_models: frozenset[str] = frozenset()
) -> str:
    """Validate the requested ``model`` and return the id to report.

    Known Jev names (plus operator-configured extras) resolve to
    ``serving_model`` — the checkpoint actually answering, e.g.
    ``"laya-english"``. Unknown names raise :class:`CompatError`.
    """
    if requested in JEV_KNOWN_MODELS or requested in extra_models or requested == serving_model:
        return serving_model
    raise CompatError(
        f"unknown model {requested!r}; expected a Jev model name or the serving model {serving_model!r}",
        field="model",
    )

shape_choice(question_id, raw, options)

Shape a raw choice answer; drops Laya-only action.

Source code in src/laya_serve/compat.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def shape_choice(question_id: str, raw: dict[str, Any], options: list[str]) -> ChoiceAnswer:
    """Shape a raw ``choice`` answer; drops Laya-only ``action``."""
    choice = raw.get("choice")
    if choice not in options:
        raise CompatError(
            f"choice answer {question_id!r} selected {choice!r}, which is not one of {options!r}",
            field=f"answers.{question_id}.choice",
        )
    probs = raw.get("probabilities", {})
    if set(probs) != set(options):
        raise CompatError(
            f"choice answer {question_id!r} probabilities cover {sorted(probs)!r}, expected {options!r}",
            field=f"answers.{question_id}.probabilities",
        )
    return ChoiceAnswer(
        type="choice",
        choice=choice,
        probabilities={
            opt: _clamp01(probs[opt], f"answers.{question_id}.probabilities.{opt}")
            for opt in options
        },
        confidence=_clamp01(raw.get("confidence"), f"answers.{question_id}.confidence"),
    )

shape_noul(question_id, raw)

Shape a raw noul answer; drops Laya-only confidence/action.

Source code in src/laya_serve/compat.py
83
84
85
86
87
88
89
def shape_noul(question_id: str, raw: dict[str, Any]) -> NoulAnswer:
    """Shape a raw ``noul`` answer; drops Laya-only ``confidence``/``action``."""
    if "noul" not in raw:
        raise CompatError(
            f"noul answer {question_id!r} is missing 'noul'", field=f"answers.{question_id}"
        )
    return NoulAnswer(type="noul", noul=_clamp01(raw["noul"], f"answers.{question_id}.noul"))

shape_response(request_questions, raw_answers, usage, serving_model)

Shape a full backend result into a Jev response dict (validated by caller).

request_questions are the validated question dicts (with type and criteria); they define the expected answer shapes. raw_answers is the backend's per-question output. Unknown/extra backend keys (action, routing) are ignored.

Source code in src/laya_serve/compat.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
def shape_response(
    request_questions: dict[str, dict[str, Any]],
    raw_answers: dict[str, dict[str, Any]],
    usage: dict[str, Any],
    serving_model: str,
) -> dict[str, Any]:
    """Shape a full backend result into a Jev response dict (validated by caller).

    ``request_questions`` are the *validated* question dicts (with ``type``
    and ``criteria``); they define the expected answer shapes. ``raw_answers``
    is the backend's per-question output. Unknown/extra backend keys
    (``action``, ``routing``) are ignored.
    """
    if set(raw_answers) != set(request_questions):
        raise CompatError(
            f"backend answered {sorted(raw_answers)!r}, expected {sorted(request_questions)!r}",
            field="answers",
        )
    answers: dict[str, Any] = {}
    for qid, qdef in request_questions.items():
        raw = raw_answers[qid]
        qtype = qdef["type"]
        if qtype == "noul":
            answers[qid] = shape_noul(qid, raw)
        elif qtype == "choice":
            criteria = qdef["criteria"]
            options = list(criteria) if isinstance(criteria, dict) else list(criteria)
            answers[qid] = shape_choice(qid, raw, options)
        elif qtype == "score":
            answers[qid] = shape_score(qid, raw, list(qdef["criteria"]))
        else:  # pragma: no cover - schema validation rejects this first
            raise CompatError(f"unknown question type {qtype!r}", field=f"questions.{qid}.type")
    response = SystemOneResponse(
        model=serving_model,
        answers=answers,
        usage=Usage(
            input_tokens=max(0, int(usage.get("input_tokens", 0))),
            output_tokens=max(0, int(usage.get("output_tokens", 0))),
        ),
    )
    return response.model_dump(mode="json", exclude_none=False)

shape_score(question_id, raw, levels)

Shape a raw score answer with stringified level keys; drops action.

Source code in src/laya_serve/compat.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def shape_score(
    question_id: str,
    raw: dict[str, Any],
    levels: list[StructuredText],
) -> ScoreAnswer:
    """Shape a raw ``score`` answer with stringified level keys; drops ``action``."""
    expected = [str(i) for i in range(len(levels))]
    probs = raw.get("probabilities", {})
    if set(map(str, probs)) != set(expected):
        raise CompatError(
            f"score answer {question_id!r} probabilities cover {sorted(map(str, probs))!r}, "
            f"expected levels {expected!r}",
            field=f"answers.{question_id}.probabilities",
        )
    try:
        score = float(raw["score"])
    except (KeyError, TypeError, ValueError) as exc:
        raise CompatError(
            f"score answer {question_id!r} is missing a numeric 'score'",
            field=f"answers.{question_id}.score",
        ) from exc
    if not 0.0 <= score <= float(len(levels) - 1):
        raise CompatError(
            f"score answer {question_id!r} has score {score!r} outside [0, {len(levels) - 1}]",
            field=f"answers.{question_id}.score",
        )
    return ScoreAnswer(
        type="score",
        score=score,
        legend={str(i): level for i, level in enumerate(levels)},
        probabilities={
            str(i): _clamp01(
                probs[i] if i in probs else probs[str(i)],
                f"answers.{question_id}.probabilities.{i}",
            )
            for i in range(len(levels))
        },
        confidence=_clamp01(raw.get("confidence"), f"answers.{question_id}.confidence"),
    )

Inference

laya_serve.inference

Inference backend abstraction.

The HTTP layer depends only on the :class:Backend protocol, never on torch/laya directly, so the server (and its tests) import and run without model weights installed:

  • :class:FakeBackend — deterministic, weight-free answers. Uniform distributions for choice/score, 0.5 for noul. Used for tests and local API development.
  • :class:LayaBackend — wraps laya.Router. Import of laya (and therefore torch) happens lazily inside the constructor, so merely importing this module stays cheap.

Context budgets (Jev: 64k total, 32k state+longest-question) are enforced pre-inference from :meth:Backend.count, so oversize requests fail fast with 422 instead of being silently truncated downstream. Enforcement uses the deterministic weight-free word count on every backend so the 200/422 boundary never depends on which checkpoints happen to be resident; per-request usage.input_tokens still comes from the Router.

Backend

Bases: Protocol

Minimal interface the API layer needs from any inference backend.

Source code in src/laya_serve/inference.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
class Backend(Protocol):
    """Minimal interface the API layer needs from any inference backend."""

    serving_model: str
    # Token budgets enforced pre-inference (Jev: 64k total, 32k
    # state+longest-question). Backends that predate this interface simply
    # omit them and skip enforcement.
    max_total_tokens: int
    max_state_question_tokens: int

    def predict(self, state: Any, questions: dict[str, dict[str, Any]]) -> dict[str, Any]:
        """Return ``{"answers": {...}, "usage": {...}}`` with raw backend answers."""
        ...

    def count(self, state: Any, questions: dict[str, dict[str, Any]]) -> tuple[int, int]:
        """Return ``(total, state_plus_longest)`` token estimates for budgets."""
        ...

count(state, questions)

Return (total, state_plus_longest) token estimates for budgets.

Source code in src/laya_serve/inference.py
115
116
117
def count(self, state: Any, questions: dict[str, dict[str, Any]]) -> tuple[int, int]:
    """Return ``(total, state_plus_longest)`` token estimates for budgets."""
    ...

predict(state, questions)

Return {"answers": {...}, "usage": {...}} with raw backend answers.

Source code in src/laya_serve/inference.py
111
112
113
def predict(self, state: Any, questions: dict[str, dict[str, Any]]) -> dict[str, Any]:
    """Return ``{"answers": {...}, "usage": {...}}`` with raw backend answers."""
    ...

FakeBackend

Deterministic weight-free backend for tests and local development.

Source code in src/laya_serve/inference.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
class FakeBackend:
    """Deterministic weight-free backend for tests and local development."""

    max_total_tokens = JEV_MAX_TOTAL_TOKENS
    max_state_question_tokens = JEV_MAX_STATE_QUESTION_TOKENS

    def __init__(self, serving_model: str = "laya-english") -> None:
        self.serving_model = serving_model
        self.calls: list[dict[str, Any]] = []

    def count(self, state: Any, questions: dict[str, dict[str, Any]]) -> tuple[int, int]:
        return count_request(state, questions)

    def predict(self, state: Any, questions: dict[str, dict[str, Any]]) -> dict[str, Any]:
        self.calls.append({"state": state, "questions": questions})
        answers: dict[str, Any] = {}
        for qid, qdef in questions.items():
            qtype = qdef["type"]
            if qtype == "noul":
                answers[qid] = {"type": "noul", "noul": 0.5}
            elif qtype == "choice":
                options = list(qdef["criteria"])
                prob = 1.0 / len(options)
                answers[qid] = {
                    "type": "choice",
                    "choice": options[0],
                    "probabilities": {opt: prob for opt in options},
                    "confidence": 0.0 if len(options) > 1 else 1.0,
                }
            elif qtype == "score":
                levels = list(qdef["criteria"])
                prob = 1.0 / len(levels)
                answers[qid] = {
                    "type": "score",
                    "score": (len(levels) - 1) / 2.0,
                    "legend": {str(i): level for i, level in enumerate(levels)},
                    "probabilities": {str(i): prob for i in range(len(levels))},
                    "confidence": 0.0,
                }
            else:  # pragma: no cover - validated upstream
                raise ValueError(f"unknown question type {qtype!r}")
        return {"answers": answers, "usage": {"input_tokens": 0, "output_tokens": 0}}

LayaBackend

Production backend wrapping laya.Router (lazy import).

Source code in src/laya_serve/inference.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
class LayaBackend:
    """Production backend wrapping ``laya.Router`` (lazy import)."""

    max_total_tokens = JEV_MAX_TOTAL_TOKENS
    max_state_question_tokens = JEV_MAX_STATE_QUESTION_TOKENS

    def __init__(self, settings: Settings) -> None:
        try:
            from laya import Router
        except ImportError as exc:
            raise RuntimeError(
                "laya is not installed; install the 'inference' extra "
                "(pip install 'laya-serve[inference]') or use backend='fake'"
            ) from exc
        self.serving_model = settings.serving_model
        self._router = Router(
            device=settings.device,
            max_loaded=settings.max_loaded,
            preload=settings.preload,
        )

    def predict(self, state: Any, questions: dict[str, dict[str, Any]]) -> dict[str, Any]:
        try:
            result = self._router.predict(state, questions)
        except OverloadedError:
            raise
        except Exception as exc:
            if is_overload(exc):
                raise OverloadedError(f"backend overloaded: {exc}") from exc
            raise
        # Router adds a non-Jev ``routing`` key; compat shaping ignores it.
        return {
            "answers": result["answers"],
            "usage": result.get("usage", {"input_tokens": 0, "output_tokens": 0}),
        }

    def count(self, state: Any, questions: dict[str, dict[str, Any]]) -> tuple[int, int]:
        """Estimate request size with the deterministic weight-free word count.

        Deliberately independent of resident checkpoint tokenizers so the
        ``200``/``422`` boundary is stable across cold starts, evictions,
        and ``max_loaded`` rotation. Word counts underestimate true tokens,
        so enforcement is lenient (fail-open); precise
        ``usage.input_tokens`` still comes from the Router in
        :meth:`predict`.
        """
        return count_request(state, questions)

count(state, questions)

Estimate request size with the deterministic weight-free word count.

Deliberately independent of resident checkpoint tokenizers so the 200/422 boundary is stable across cold starts, evictions, and max_loaded rotation. Word counts underestimate true tokens, so enforcement is lenient (fail-open); precise usage.input_tokens still comes from the Router in :meth:predict.

Source code in src/laya_serve/inference.py
200
201
202
203
204
205
206
207
208
209
210
def count(self, state: Any, questions: dict[str, dict[str, Any]]) -> tuple[int, int]:
    """Estimate request size with the deterministic weight-free word count.

    Deliberately independent of resident checkpoint tokenizers so the
    ``200``/``422`` boundary is stable across cold starts, evictions,
    and ``max_loaded`` rotation. Word counts underestimate true tokens,
    so enforcement is lenient (fail-open); precise
    ``usage.input_tokens`` still comes from the Router in
    :meth:`predict`.
    """
    return count_request(state, questions)

OverloadedError

Bases: Exception

Transient backend overload; handled as a Jev-shaped 529.

Backends raise this when inference could succeed on retry after a short delay (GPU OOM under burst load, evicted checkpoint reload, upstream timeout). Clients retry with backoff, honoring Retry-After.

Source code in src/laya_serve/inference.py
60
61
62
63
64
65
66
67
68
69
class OverloadedError(Exception):
    """Transient backend overload; handled as a Jev-shaped ``529``.

    Backends raise this when inference could succeed on retry after a
    short delay (GPU OOM under burst load, evicted checkpoint reload,
    upstream timeout). Clients retry with backoff, honoring
    ``Retry-After``.
    """

    pass

build_backend(settings)

Instantiate the backend selected by settings.backend.

Source code in src/laya_serve/inference.py
213
214
215
216
217
218
219
def build_backend(settings: Settings) -> Backend:
    """Instantiate the backend selected by ``settings.backend``."""
    if settings.backend == "fake":
        return FakeBackend(serving_model=settings.serving_model)
    if settings.backend == "laya":
        return LayaBackend(settings)
    raise ValueError(f"unknown backend {settings.backend!r}; expected 'fake' or 'laya'")

count_request(state, questions)

Weight-free (total, state+longest-question) word count of a request.

Source code in src/laya_serve/inference.py
52
53
54
55
56
57
def count_request(state: Any, questions: dict[str, dict[str, Any]]) -> tuple[int, int]:
    """Weight-free (total, state+longest-question) word count of a request."""
    state_n = len(render_text(state).split())
    per_question = [len(render_question_text(q).split()) for q in questions.values()]
    longest = max(per_question, default=0)
    return state_n + sum(per_question), state_n + longest

is_overload(exc)

Return whether exc looks like transient overload (retryable).

Source code in src/laya_serve/inference.py
95
96
97
98
def is_overload(exc: BaseException) -> bool:
    """Return whether ``exc`` looks like transient overload (retryable)."""
    haystack = f"{type(exc).__name__} {exc}".lower()
    return any(marker in haystack for marker in _OVERLOAD_MARKERS)

render_question_text(qdef)

Rendered size basis of one question: instructions + criteria descriptions.

Source code in src/laya_serve/inference.py
41
42
43
44
45
46
47
48
49
def render_question_text(qdef: dict[str, Any]) -> str:
    """Rendered size basis of one question: instructions + criteria descriptions."""
    parts = [render_text(qdef.get("instructions", ""))]
    criteria = qdef.get("criteria")
    if isinstance(criteria, dict):
        parts.extend(render_text(v) for v in criteria.values() if v is not None)
    elif isinstance(criteria, list):
        parts.extend(render_text(level) for level in criteria)
    return " ".join(part for part in parts if part)

render_text(value)

Render one value as text, mirroring laya.common.render_criterion.

Source code in src/laya_serve/inference.py
34
35
36
37
38
def render_text(value: Any) -> str:
    """Render one value as text, mirroring ``laya.common.render_criterion``."""
    if isinstance(value, str):
        return value
    return json.dumps(value, ensure_ascii=False, separators=(", ", ": "), default=str)

Rate limiting

laya_serve.ratelimit

In-memory fixed-window rate limiter for Jev-compatible 429 responses.

Jev returns 429 Too Many Requests when a client exceeds its rate limit; SDKs retry with exponential backoff. This module provides a minimal, dependency-free limiter suitable for single-process deployments.

Multi-worker / multi-replica deployments should enforce limits at the reverse proxy / gateway instead (see docs/deployment.md); per-process counters do not coordinate across workers.

RateLimitExceeded

Bases: Exception

Raised when a request exceeds the rate limit; handled as Jev 429.

Source code in src/laya_serve/ratelimit.py
58
59
60
61
62
63
class RateLimitExceeded(Exception):
    """Raised when a request exceeds the rate limit; handled as Jev ``429``."""

    def __init__(self, retry_after: int = 60) -> None:
        super().__init__("Rate limit exceeded. Back off and retry after a short delay.")
        self.retry_after = max(1, int(retry_after))

RateLimiter

Fixed-window counter keyed by client identity.

  • requests_per_minute <= 0 means disabled (allow everything).
  • Windows are 60-second fixed windows anchored on first use per key.
  • Thread-safe via a single lock; counters are tiny dicts.
Source code in src/laya_serve/ratelimit.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class RateLimiter:
    """Fixed-window counter keyed by client identity.

    * ``requests_per_minute <= 0`` means disabled (allow everything).
    * Windows are 60-second fixed windows anchored on first use per key.
    * Thread-safe via a single lock; counters are tiny dicts.
    """

    def __init__(self, requests_per_minute: int = 0) -> None:
        self.requests_per_minute = max(0, int(requests_per_minute))
        self._lock = threading.Lock()
        # key -> (window_start_monotonic, count_in_window)
        self._windows: dict[str, tuple[float, int]] = {}

    @property
    def enabled(self) -> bool:
        return self.requests_per_minute > 0

    def check(self, key: str, now: float | None = None) -> tuple[bool, int]:
        """Record one hit for ``key``.

        Returns ``(allowed, retry_after_seconds)``. When ``allowed`` is
        ``False``, ``retry_after_seconds`` is the whole seconds until the
        current window resets (>= 1).
        """
        if not self.enabled:
            return True, 0
        timestamp = time.monotonic() if now is None else now
        with self._lock:
            start, count = self._windows.get(key, (timestamp, 0))
            if timestamp - start >= 60.0:
                start, count = timestamp, 0
            count += 1
            self._windows[key] = (start, count)
            if count <= self.requests_per_minute:
                return True, 0
            retry_after = max(1, int(60.0 - (timestamp - start)) + 1)
            return False, retry_after

check(key, now=None)

Record one hit for key.

Returns (allowed, retry_after_seconds). When allowed is False, retry_after_seconds is the whole seconds until the current window resets (>= 1).

Source code in src/laya_serve/ratelimit.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def check(self, key: str, now: float | None = None) -> tuple[bool, int]:
    """Record one hit for ``key``.

    Returns ``(allowed, retry_after_seconds)``. When ``allowed`` is
    ``False``, ``retry_after_seconds`` is the whole seconds until the
    current window resets (>= 1).
    """
    if not self.enabled:
        return True, 0
    timestamp = time.monotonic() if now is None else now
    with self._lock:
        start, count = self._windows.get(key, (timestamp, 0))
        if timestamp - start >= 60.0:
            start, count = timestamp, 0
        count += 1
        self._windows[key] = (start, count)
        if count <= self.requests_per_minute:
            return True, 0
        retry_after = max(1, int(60.0 - (timestamp - start)) + 1)
        return False, retry_after

CLI

laya_serve.cli

CLI entry point: laya-serve.

main(argv=None)

Serve the app with uvicorn (host/port via flags, defaults 0.0.0.0:8000).

Source code in src/laya_serve/cli.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def main(argv: Sequence[str] | None = None) -> None:
    """Serve the app with uvicorn (host/port via flags, defaults ``0.0.0.0:8000``)."""
    import argparse

    parser = argparse.ArgumentParser(
        prog="laya-serve",
        description="Jev-compatible HTTP server for Laya decision models.",
    )
    parser.add_argument(
        "--version",
        action="version",
        version=f"%(prog)s {__version__}",
    )
    parser.add_argument("--host", default="0.0.0.0", help="Bind host (default: 0.0.0.0).")
    parser.add_argument("--port", type=int, default=8000, help="Bind port (default: 8000).")
    args = parser.parse_args(argv)

    settings = Settings()
    app = create_app(settings)
    uvicorn.run(app, host=args.host, port=args.port)