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 | |
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/legendkeys are strings on the wire ({"0": ...}); the Python SDK exposes them as ints. This server speaks the HTTP wire format. instructionsand criteria descriptions acceptstr | 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 | |
SystemOneResponse
¶
Bases: BaseModel
POST /v1/systemone response body.
Source code in src/laya_serve/schemas.py
132 133 134 135 136 137 138 139 | |
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 | |
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 | |
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 | |
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 | |
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):
- Laya attaches
action: {act_probability}to every answer; Jev has no such field. Stripped here. - Laya returns
confidenceonnoulanswers; Jevnoulanswers carry only{type, noul}. Stripped here. - Laya reports
model: "laya-rl-agent"; Jev echoes the resolved versioned model id. The caller supplies the id to report. - 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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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.5for noul. Used for tests and local API development. - :class:
LayaBackend— wrapslaya.Router. Import oflaya(and thereforetorch) 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 | |
count(state, questions)
¶
Return (total, state_plus_longest) token estimates for budgets.
Source code in src/laya_serve/inference.py
115 116 117 | |
predict(state, questions)
¶
Return {"answers": {...}, "usage": {...}} with raw backend answers.
Source code in src/laya_serve/inference.py
111 112 113 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
is_overload(exc)
¶
Return whether exc looks like transient overload (retryable).
Source code in src/laya_serve/inference.py
95 96 97 98 | |
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 | |
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 | |
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 | |
RateLimiter
¶
Fixed-window counter keyed by client identity.
requests_per_minute <= 0means 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 | |
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 | |
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 | |